@runfusion/fusion 0.9.2 → 0.9.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 (31) hide show
  1. package/dist/bin.js +582 -431
  2. package/dist/client/assets/{AgentDetailView-CcvwSbwq.js → AgentDetailView-D9UWpTYr.js} +3 -3
  3. package/dist/client/assets/{AgentsView-m8dBmo63.js → AgentsView-DeCfRupM.js} +3 -3
  4. package/dist/client/assets/{ChatView-BKlUxTL8.js → ChatView-ChlqnJfu.js} +1 -1
  5. package/dist/client/assets/{DevServerView-BygenPXh.js → DevServerView-B7EjWlgc.js} +1 -1
  6. package/dist/client/assets/{DirectoryPicker-BosMLSTv.js → DirectoryPicker-crtmkC00.js} +1 -1
  7. package/dist/client/assets/{DocumentsView-BfEPLY3E.js → DocumentsView-BLxVoopL.js} +1 -1
  8. package/dist/client/assets/{InsightsView-B8FDRckf.js → InsightsView-CcdTychV.js} +1 -1
  9. package/dist/client/assets/{MemoryView-RT80_hUg.js → MemoryView-rSwx9Md8.js} +1 -1
  10. package/dist/client/assets/{NodesView-UaaYrNBq.js → NodesView-Bwz0cHKV.js} +1 -1
  11. package/dist/client/assets/{PiExtensionsManager-Cjtt84Cv.js → PiExtensionsManager-Uo3E8Ae7.js} +1 -1
  12. package/dist/client/assets/{PluginManager-BuhG8uF_.js → PluginManager-HtW8xVY8.js} +1 -1
  13. package/dist/client/assets/{ResearchView-DLDnrYVm.js → ResearchView-BV-iy9g8.js} +1 -1
  14. package/dist/client/assets/{RoadmapsView-DP7o1HsL.js → RoadmapsView-C2j64cbz.js} +1 -1
  15. package/dist/client/assets/{SettingsModal-BWkxqiIg.js → SettingsModal-CVd9kNk7.js} +1 -1
  16. package/dist/client/assets/{SettingsModal-BAZQbTpw.js → SettingsModal-ClnT1Lcv.js} +3 -3
  17. package/dist/client/assets/{SetupWizardModal-BZFsWHpZ.js → SetupWizardModal-Dy-vQpTm.js} +1 -1
  18. package/dist/client/assets/{SkillsView-87geY7V_.js → SkillsView-BQwTyjxc.js} +1 -1
  19. package/dist/client/assets/{TodoView-DaYieTys.js → TodoView-Dce4DrzU.js} +1 -1
  20. package/dist/client/assets/{folder-open-Di63O7Be.js → folder-open-DWUflP4Q.js} +1 -1
  21. package/dist/client/assets/{index-BFOt3vs-.js → index-Bs3RZu5I.js} +29 -29
  22. package/dist/client/assets/{index-BKZuWpfH.css → index-C3-q81dV.css} +1 -1
  23. package/dist/client/assets/{list-checks-DQ8ece3U.js → list-checks-CusZ_RMn.js} +1 -1
  24. package/dist/client/assets/{star-CHOSzU9a.js → star-C-NXZn1F.js} +1 -1
  25. package/dist/client/assets/{upload-D7w2Kq_t.js → upload-CXJ5L6L4.js} +1 -1
  26. package/dist/client/assets/{users-YG5FjHxk.js → users-YaA3x5mt.js} +1 -1
  27. package/dist/client/index.html +2 -2
  28. package/dist/client/version.json +1 -1
  29. package/dist/extension.js +228 -68
  30. package/dist/pi-claude-cli/package.json +1 -1
  31. package/package.json +1 -1
package/dist/extension.js CHANGED
@@ -890,6 +890,12 @@ function hasAgentIdentity(agent) {
890
890
  if (!agent) return false;
891
891
  return !!(agent.soul?.trim() || agent.instructionsText?.trim() || agent.instructionsPath?.trim() || agent.memory?.trim());
892
892
  }
893
+ function getDefaultHeartbeatProcedurePath(agentId) {
894
+ if (!agentId || typeof agentId !== "string") {
895
+ throw new Error("getDefaultHeartbeatProcedurePath requires a non-empty agentId");
896
+ }
897
+ return `.fusion/agents/${agentId}/HEARTBEAT.md`;
898
+ }
893
899
  function agentToConfigSnapshot(agent) {
894
900
  return {
895
901
  name: agent.name,
@@ -4370,8 +4376,9 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
4370
4376
  });
4371
4377
 
4372
4378
  // ../core/src/agent-store.ts
4373
- import { mkdir, readFile, writeFile, readdir, unlink, rename } from "node:fs/promises";
4374
- import { basename, join as join3, resolve as resolve2 } from "node:path";
4379
+ import { mkdir, readFile, writeFile, readdir, unlink, rename, access } from "node:fs/promises";
4380
+ import { constants as fsConstants } from "node:fs";
4381
+ import { basename, dirname, join as join3, resolve as resolve2 } from "node:path";
4375
4382
  import { randomUUID, randomBytes, createHash } from "node:crypto";
4376
4383
  import { EventEmitter } from "node:events";
4377
4384
  function resolveCreationRuntimeConfig(incoming, metadata) {
@@ -4427,9 +4434,75 @@ var init_agent_store = __esm({
4427
4434
  * Should be called before other operations.
4428
4435
  */
4429
4436
  async init() {
4430
- const _ = this.db;
4437
+ void this.db;
4431
4438
  await mkdir(this.agentsDir, { recursive: true });
4432
4439
  await this.importLegacyFileDataOnce();
4440
+ await this.migrateHeartbeatProcedurePathOnce();
4441
+ }
4442
+ /**
4443
+ * One-shot migration that re-points every non-ephemeral agent off the
4444
+ * legacy shared `.fusion/HEARTBEAT.md` path onto their own per-agent
4445
+ * `.fusion/agents/<id>/HEARTBEAT.md` file. The legacy file's contents
4446
+ * are copied to the new location when present so operator edits are
4447
+ * preserved across the upgrade. The legacy file itself is left in place
4448
+ * — the migration is non-destructive in case the operator wants a
4449
+ * reference copy.
4450
+ *
4451
+ * Idempotent: tracks completion in the `__meta` table and short-circuits
4452
+ * on subsequent calls. Failures during file copy are logged via the
4453
+ * legacy console (no log dependency in core) and do not block startup —
4454
+ * the agent's `heartbeatProcedurePath` is still flipped, and the engine's
4455
+ * heartbeat resolver will fall back to the built-in template until the
4456
+ * file is seeded on next dashboard interaction.
4457
+ */
4458
+ async migrateHeartbeatProcedurePathOnce() {
4459
+ const migrationKey = "heartbeatProcedurePathPerAgent";
4460
+ const migrationVersion = "1";
4461
+ const row = this.db.prepare("SELECT value FROM __meta WHERE key = ?").get(migrationKey);
4462
+ if (row?.value === migrationVersion) {
4463
+ return;
4464
+ }
4465
+ let legacyContent = null;
4466
+ try {
4467
+ legacyContent = await readFile(join3(this.rootDir, "HEARTBEAT.md"), "utf-8");
4468
+ } catch {
4469
+ legacyContent = null;
4470
+ }
4471
+ const agents = await this.listAgents({ includeEphemeral: false });
4472
+ let migratedCount = 0;
4473
+ for (const agent of agents) {
4474
+ if (agent.heartbeatProcedurePath !== DEFAULT_HEARTBEAT_PROCEDURE_PATH) {
4475
+ continue;
4476
+ }
4477
+ const newRelPath = getDefaultHeartbeatProcedurePath(agent.id);
4478
+ const newAbsPath = join3(this.rootDir, "..", newRelPath);
4479
+ if (legacyContent !== null) {
4480
+ try {
4481
+ await mkdir(dirname(newAbsPath), { recursive: true });
4482
+ try {
4483
+ await access(newAbsPath, fsConstants.F_OK);
4484
+ } catch {
4485
+ await writeFile(newAbsPath, legacyContent, "utf-8");
4486
+ }
4487
+ } catch {
4488
+ }
4489
+ }
4490
+ const updated = {
4491
+ ...agent,
4492
+ heartbeatProcedurePath: newRelPath,
4493
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4494
+ };
4495
+ await this.writeAgent(updated);
4496
+ migratedCount += 1;
4497
+ }
4498
+ this.db.prepare(`
4499
+ INSERT INTO __meta (key, value)
4500
+ VALUES (?, ?)
4501
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
4502
+ `).run(migrationKey, migrationVersion);
4503
+ if (migratedCount > 0) {
4504
+ this.db.bumpLastModified();
4505
+ }
4433
4506
  }
4434
4507
  /**
4435
4508
  * One-way migration helper for projects that still have legacy agent JSON
@@ -4562,7 +4635,7 @@ var init_agent_store = __esm({
4562
4635
  const metadata = input.metadata ?? {};
4563
4636
  const runtimeConfig = resolveCreationRuntimeConfig(input.runtimeConfig, metadata);
4564
4637
  const ephemeral = isEphemeralAgent({ metadata, name: input.name, role: input.role, reportsTo: input.reportsTo });
4565
- const resolvedHeartbeatProcedurePath = input.heartbeatProcedurePath ?? (ephemeral ? void 0 : DEFAULT_HEARTBEAT_PROCEDURE_PATH);
4638
+ const resolvedHeartbeatProcedurePath = input.heartbeatProcedurePath ?? (ephemeral ? void 0 : getDefaultHeartbeatProcedurePath(agentId));
4566
4639
  const agent = {
4567
4640
  id: agentId,
4568
4641
  name: input.name.trim(),
@@ -6496,7 +6569,7 @@ var init_task_priority = __esm({
6496
6569
 
6497
6570
  // ../core/src/global-settings.ts
6498
6571
  import { homedir } from "node:os";
6499
- import { dirname, join as join5 } from "node:path";
6572
+ import { dirname as dirname2, join as join5 } from "node:path";
6500
6573
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3, rename as rename2, chmod } from "node:fs/promises";
6501
6574
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, renameSync } from "node:fs";
6502
6575
  function getHomeDir() {
@@ -6526,7 +6599,7 @@ function resolveGlobalDir(dir) {
6526
6599
  const legacyDir = legacyGlobalDir();
6527
6600
  if (existsSync3(legacyDir)) {
6528
6601
  try {
6529
- mkdirSync2(dirname(preferredDir), { recursive: true });
6602
+ mkdirSync2(dirname2(preferredDir), { recursive: true });
6530
6603
  renameSync(legacyDir, preferredDir);
6531
6604
  return preferredDir;
6532
6605
  } catch {
@@ -6536,7 +6609,7 @@ function resolveGlobalDir(dir) {
6536
6609
  const legacyDirOriginal = legacyGlobalDirOriginal();
6537
6610
  if (existsSync3(legacyDirOriginal)) {
6538
6611
  try {
6539
- mkdirSync2(dirname(preferredDir), { recursive: true });
6612
+ mkdirSync2(dirname2(preferredDir), { recursive: true });
6540
6613
  renameSync(legacyDirOriginal, preferredDir);
6541
6614
  return preferredDir;
6542
6615
  } catch {
@@ -12318,11 +12391,11 @@ var init_todo_store = __esm({
12318
12391
 
12319
12392
  // ../core/src/app-version.ts
12320
12393
  import { readFileSync } from "node:fs";
12321
- import { join as join9, dirname as dirname2 } from "node:path";
12394
+ import { join as join9, dirname as dirname3 } from "node:path";
12322
12395
  import { fileURLToPath as fileURLToPath2 } from "node:url";
12323
12396
  function getAppVersion() {
12324
12397
  if (cachedVersion !== null) return cachedVersion;
12325
- const __dirname2 = dirname2(fileURLToPath2(import.meta.url));
12398
+ const __dirname2 = dirname3(fileURLToPath2(import.meta.url));
12326
12399
  let currentDir = __dirname2;
12327
12400
  for (let i = 0; i < 10; i++) {
12328
12401
  try {
@@ -12334,7 +12407,7 @@ function getAppVersion() {
12334
12407
  }
12335
12408
  } catch {
12336
12409
  }
12337
- const parentDir = dirname2(currentDir);
12410
+ const parentDir = dirname3(currentDir);
12338
12411
  if (parentDir === currentDir) break;
12339
12412
  currentDir = parentDir;
12340
12413
  }
@@ -16341,7 +16414,7 @@ __export(check_disk_space_exports, {
16341
16414
  getFirstExistingParentPath: () => getFirstExistingParentPath
16342
16415
  });
16343
16416
  import { execFile } from "node:child_process";
16344
- import { access } from "node:fs/promises";
16417
+ import { access as access2 } from "node:fs/promises";
16345
16418
  import { release } from "node:os";
16346
16419
  import { normalize, sep as sep2 } from "node:path";
16347
16420
  import { platform } from "node:process";
@@ -16378,7 +16451,7 @@ async function hasPowerShell3(dependencies) {
16378
16451
  function checkDiskSpace(directoryPath, dependencies = {
16379
16452
  platform,
16380
16453
  release: release(),
16381
- fsAccess: access,
16454
+ fsAccess: access2,
16382
16455
  pathNormalize: normalize,
16383
16456
  pathSep: sep2,
16384
16457
  cpExecFile: promisify(execFile)
@@ -18524,7 +18597,7 @@ __export(migration_exports, {
18524
18597
  });
18525
18598
  import { existsSync as existsSync9 } from "node:fs";
18526
18599
  import { homedir as homedir2, tmpdir } from "node:os";
18527
- import { isAbsolute as isAbsolute3, join as join12, resolve as resolve5, basename as basename3, dirname as dirname3 } from "node:path";
18600
+ import { isAbsolute as isAbsolute3, join as join12, resolve as resolve5, basename as basename3, dirname as dirname4 } from "node:path";
18528
18601
  function getHomeDir2() {
18529
18602
  return process.env.HOME || process.env.USERPROFILE || homedir2();
18530
18603
  }
@@ -18624,7 +18697,7 @@ var init_migration = __esm({
18624
18697
  const visited = /* @__PURE__ */ new Set();
18625
18698
  let current = resolve5(startDir);
18626
18699
  const home = getHomeDir2();
18627
- const root = dirname3(current) === current ? current : "/";
18700
+ const root = dirname4(current) === current ? current : "/";
18628
18701
  const systemTmp = resolve5(tmpdir());
18629
18702
  while (current !== home && current !== root && current !== systemTmp) {
18630
18703
  if (visited.has(current)) break;
@@ -18638,7 +18711,7 @@ var init_migration = __esm({
18638
18711
  });
18639
18712
  break;
18640
18713
  }
18641
- const parent = dirname3(current);
18714
+ const parent = dirname4(current);
18642
18715
  if (parent === current) break;
18643
18716
  current = parent;
18644
18717
  }
@@ -28939,9 +29012,9 @@ __export(memory_backend_exports, {
28939
29012
  writeMemory: () => writeMemory,
28940
29013
  writeProjectMemoryFile: () => writeProjectMemoryFile
28941
29014
  });
28942
- import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir6, access as access2, constants, readdir as readdir4, stat as stat2 } from "node:fs/promises";
29015
+ import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir6, access as access3, constants, readdir as readdir4, stat as stat2 } from "node:fs/promises";
28943
29016
  import { existsSync as existsSync11 } from "node:fs";
28944
- import { basename as basename4, dirname as dirname4, isAbsolute as isAbsolute4, join as join15, normalize as normalize2, relative, resolve as resolve6, sep as sep3 } from "node:path";
29017
+ import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute4, join as join15, normalize as normalize2, relative, resolve as resolve6, sep as sep3 } from "node:path";
28945
29018
  import { createHash as createHash3 } from "node:crypto";
28946
29019
  function shouldSkipBackgroundQmdRefresh() {
28947
29020
  return (process.env.VITEST === "true" || process.env.NODE_ENV === "test") && process.env.FUSION_ENABLE_QMD_REFRESH_IN_TESTS !== "1";
@@ -29101,7 +29174,7 @@ async function readProjectMemoryFileContent(rootDir, path2) {
29101
29174
  }
29102
29175
  async function writeProjectMemoryFile(rootDir, path2, content) {
29103
29176
  const { absPath } = resolveMemoryFilePath(rootDir, path2);
29104
- await mkdir6(dirname4(absPath), { recursive: true });
29177
+ await mkdir6(dirname5(absPath), { recursive: true });
29105
29178
  const tmpPath = `${absPath}.tmp`;
29106
29179
  await writeFile5(tmpPath, content, "utf-8");
29107
29180
  const { rename: rename6 } = await import("node:fs/promises");
@@ -29220,7 +29293,7 @@ async function readAgentMemoryFile(rootDir, agentId, path2) {
29220
29293
  }
29221
29294
  async function writeAgentMemoryFile(rootDir, agentId, path2, content) {
29222
29295
  const { absPath } = await resolveAgentMemoryFilePath(rootDir, agentId, path2);
29223
- await mkdir6(dirname4(absPath), { recursive: true });
29296
+ await mkdir6(dirname5(absPath), { recursive: true });
29224
29297
  const tmpPath = `${absPath}.tmp`;
29225
29298
  await writeFile5(tmpPath, content, "utf-8");
29226
29299
  const { rename: rename6 } = await import("node:fs/promises");
@@ -29366,7 +29439,7 @@ function normalizeQmdSearchResultPath(rootDir, rawPath) {
29366
29439
  }
29367
29440
  const lowerCandidate = candidate.toLowerCase();
29368
29441
  const normalizedBaseName = basename4(candidate).toLowerCase();
29369
- const normalizedDirName = dirname4(lowerCandidate).replace(/\\/g, "/");
29442
+ const normalizedDirName = dirname5(lowerCandidate).replace(/\\/g, "/");
29370
29443
  if (normalizedBaseName === "memory.md" && (normalizedDirName === ".fusion" || normalizedDirName.endsWith("/.fusion"))) {
29371
29444
  return `${MEMORY_WORKSPACE_PATH}/${MEMORY_LONG_TERM_FILENAME}`;
29372
29445
  }
@@ -29690,7 +29763,7 @@ var init_memory_backend = __esm({
29690
29763
  }
29691
29764
  async exists(rootDir) {
29692
29765
  try {
29693
- await access2(this.getLongTermPath(rootDir), constants.R_OK);
29766
+ await access3(this.getLongTermPath(rootDir), constants.R_OK);
29694
29767
  return true;
29695
29768
  } catch {
29696
29769
  return false;
@@ -32737,6 +32810,16 @@ ${newTask.description}
32737
32810
  } else if (updates.sessionFile !== void 0) {
32738
32811
  task.sessionFile = updates.sessionFile;
32739
32812
  }
32813
+ if (updates.executionStartedAt === null) {
32814
+ task.executionStartedAt = void 0;
32815
+ } else if (updates.executionStartedAt !== void 0) {
32816
+ task.executionStartedAt = updates.executionStartedAt;
32817
+ }
32818
+ if (updates.executionCompletedAt === null) {
32819
+ task.executionCompletedAt = void 0;
32820
+ } else if (updates.executionCompletedAt !== void 0) {
32821
+ task.executionCompletedAt = updates.executionCompletedAt;
32822
+ }
32740
32823
  if (updates.workflowStepResults === null) {
32741
32824
  task.workflowStepResults = void 0;
32742
32825
  } else if (updates.workflowStepResults !== void 0) {
@@ -36273,7 +36356,7 @@ var init_dispatcher = __esm({
36273
36356
  });
36274
36357
 
36275
36358
  // ../core/src/plugin-loader.ts
36276
- import { basename as basename6, dirname as dirname5, extname, isAbsolute as isAbsolute6, resolve as resolve8 } from "node:path";
36359
+ import { basename as basename6, dirname as dirname6, extname, isAbsolute as isAbsolute6, resolve as resolve8 } from "node:path";
36277
36360
  import { copyFile, rm } from "node:fs/promises";
36278
36361
  import { pathToFileURL } from "node:url";
36279
36362
  import { EventEmitter as EventEmitter15 } from "node:events";
@@ -36430,7 +36513,7 @@ var init_plugin_loader = __esm({
36430
36513
  moduleImportVersion += 1;
36431
36514
  const ext = extname(path2);
36432
36515
  const baseName = basename6(path2, ext);
36433
- const reloadedPath = resolve8(dirname5(path2), `.${baseName}.reload-${moduleImportVersion}${ext}`);
36516
+ const reloadedPath = resolve8(dirname6(path2), `.${baseName}.reload-${moduleImportVersion}${ext}`);
36434
36517
  await copyFile(path2, reloadedPath);
36435
36518
  try {
36436
36519
  mod = await import(pathToFileURL(reloadedPath).href);
@@ -38055,7 +38138,7 @@ var init_mission_types = __esm({
38055
38138
  // ../core/src/memory-insights.ts
38056
38139
  import { readFile as readFile10, writeFile as writeFile8, mkdir as mkdir9 } from "node:fs/promises";
38057
38140
  import { existsSync as existsSync16 } from "node:fs";
38058
- import { dirname as dirname6, join as join19 } from "node:path";
38141
+ import { dirname as dirname7, join as join19 } from "node:path";
38059
38142
  async function readWorkingMemory(rootDir) {
38060
38143
  const filePath = join19(rootDir, MEMORY_WORKING_PATH);
38061
38144
  if (!existsSync16(filePath)) {
@@ -38080,7 +38163,7 @@ async function writeInsightsMemory(rootDir, content) {
38080
38163
  }
38081
38164
  async function writeWorkingMemory(rootDir, content) {
38082
38165
  const filePath = join19(rootDir, MEMORY_WORKING_PATH);
38083
- const dir = dirname6(filePath);
38166
+ const dir = dirname7(filePath);
38084
38167
  if (!existsSync16(dir)) {
38085
38168
  await mkdir9(dir, { recursive: true });
38086
38169
  }
@@ -50099,6 +50182,7 @@ __export(src_exports, {
50099
50182
  getCurrentRepo: () => getCurrentRepo,
50100
50183
  getDefaultDailyMemoryScaffold: () => getDefaultDailyMemoryScaffold,
50101
50184
  getDefaultDreamsScaffold: () => getDefaultDreamsScaffold,
50185
+ getDefaultHeartbeatProcedurePath: () => getDefaultHeartbeatProcedurePath,
50102
50186
  getDefaultInsightsTemplate: () => getDefaultInsightsTemplate,
50103
50187
  getDefaultLongTermMemoryScaffold: () => getDefaultLongTermMemoryScaffold,
50104
50188
  getDefaultMemoryScaffold: () => getDefaultMemoryScaffold,
@@ -51951,7 +52035,7 @@ import { existsSync as existsSync21, readFileSync as readFileSync7 } from "node:
51951
52035
  import { exec } from "node:child_process";
51952
52036
  import { promisify as promisify2 } from "node:util";
51953
52037
  import { createRequire as createRequire2 } from "node:module";
51954
- import { basename as basename7, dirname as dirname7, join as join26, relative as relative3, isAbsolute as isAbsolute7, resolve as resolve11 } from "node:path";
52038
+ import { basename as basename7, dirname as dirname8, join as join26, relative as relative3, isAbsolute as isAbsolute7, resolve as resolve11 } from "node:path";
51955
52039
  import {
51956
52040
  createAgentSession,
51957
52041
  createBashTool,
@@ -52346,7 +52430,7 @@ function siblingAgentDir(agentDir, siblingRoot) {
52346
52430
  if (basename7(agentDir) !== "agent") {
52347
52431
  return void 0;
52348
52432
  }
52349
- return join26(dirname7(dirname7(agentDir)), siblingRoot, "agent");
52433
+ return join26(dirname8(dirname8(agentDir)), siblingRoot, "agent");
52350
52434
  }
52351
52435
  function createReadOnlyPiSettingsView(cwd, agentDir) {
52352
52436
  const projectRoot = resolvePiExtensionProjectRoot(cwd);
@@ -52386,7 +52470,7 @@ function resolveVendoredClaudeCliEntry() {
52386
52470
  if (!Array.isArray(extensions) || extensions.length === 0) return null;
52387
52471
  const entry = extensions[0];
52388
52472
  if (typeof entry !== "string" || entry.length === 0) return null;
52389
- const path2 = resolve11(dirname7(pkgJsonPath), entry);
52473
+ const path2 = resolve11(dirname8(pkgJsonPath), entry);
52390
52474
  return existsSync21(path2) ? path2 : null;
52391
52475
  } catch {
52392
52476
  return null;
@@ -53241,9 +53325,9 @@ var init_usage_limit_detector = __esm({
53241
53325
  });
53242
53326
 
53243
53327
  // ../engine/src/agent-instructions.ts
53244
- import { readFile as readFile12, writeFile as writeFile11, mkdir as mkdir12, access as access3 } from "node:fs/promises";
53245
- import { constants as fsConstants } from "node:fs";
53246
- import { isAbsolute as isAbsolute8, resolve as resolve12, relative as relative4, normalize as normalize3, sep as sep5, dirname as dirname8 } from "node:path";
53328
+ import { readFile as readFile12, writeFile as writeFile11, mkdir as mkdir12, access as access4 } from "node:fs/promises";
53329
+ import { constants as fsConstants2 } from "node:fs";
53330
+ import { isAbsolute as isAbsolute8, resolve as resolve12, relative as relative4, normalize as normalize3, sep as sep5, dirname as dirname9 } from "node:path";
53247
53331
  function trimAndClamp(value, maxLength, label, agentId) {
53248
53332
  const trimmed = value.trim();
53249
53333
  if (!trimmed) {
@@ -53327,12 +53411,12 @@ async function ensureDefaultHeartbeatProcedureFile(rootDir, procedurePathRel, de
53327
53411
  return null;
53328
53412
  }
53329
53413
  try {
53330
- await access3(filePath, fsConstants.F_OK);
53414
+ await access4(filePath, fsConstants2.F_OK);
53331
53415
  return filePath;
53332
53416
  } catch {
53333
53417
  }
53334
53418
  try {
53335
- await mkdir12(dirname8(filePath), { recursive: true });
53419
+ await mkdir12(dirname9(filePath), { recursive: true });
53336
53420
  await writeFile11(filePath, defaultContent, "utf-8");
53337
53421
  log4.log(`Seeded default heartbeat procedure file at ${filePath}`);
53338
53422
  return filePath;
@@ -62211,8 +62295,12 @@ Lint, tests, and typecheck are also hard quality gates:
62211
62295
  throw new Error("task missing during workflow rerun bounce");
62212
62296
  }
62213
62297
  if (latestTask.column === "in-progress") {
62298
+ const originalExecutionStartedAt = latestTask.executionStartedAt;
62214
62299
  await this.store.moveTask(taskId, "todo");
62215
- await this.store.updateTask(taskId, { worktree: worktreePath });
62300
+ await this.store.updateTask(taskId, {
62301
+ worktree: worktreePath,
62302
+ executionStartedAt: originalExecutionStartedAt ?? null
62303
+ });
62216
62304
  await this.store.moveTask(taskId, "in-progress");
62217
62305
  return "bounced";
62218
62306
  }
@@ -68559,7 +68647,7 @@ function isTickableState(state) {
68559
68647
  function isHeartbeatManaged(agent) {
68560
68648
  return !isEphemeralAgent(agent);
68561
68649
  }
68562
- var HEARTBEAT_SYSTEM_PROMPT, HEARTBEAT_NO_TASK_SYSTEM_PROMPT, HEARTBEAT_PROCEDURE, heartbeatDoneParams, HeartbeatMonitor, HeartbeatTriggerScheduler;
68650
+ var HEARTBEAT_SYSTEM_PROMPT, HEARTBEAT_NO_TASK_SYSTEM_PROMPT, HEARTBEAT_PROCEDURE, heartbeatDoneParams, HeartbeatMonitor, OVERDUE_FIRE_JITTER_MS, HeartbeatTriggerScheduler;
68563
68651
  var init_agent_heartbeat = __esm({
68564
68652
  "../engine/src/agent-heartbeat.ts"() {
68565
68653
  "use strict";
@@ -69982,6 +70070,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
69982
70070
  this.onTerminated?.(tracked.agentId);
69983
70071
  }
69984
70072
  };
70073
+ OVERDUE_FIRE_JITTER_MS = 5e3;
69985
70074
  HeartbeatTriggerScheduler = class _HeartbeatTriggerScheduler {
69986
70075
  store;
69987
70076
  callback;
@@ -70019,7 +70108,11 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
70019
70108
  this.unwatchAssignments();
70020
70109
  this.unwatchAgentLifecycle();
70021
70110
  for (const [agentId, timer] of this.timers) {
70022
- clearInterval(timer.handle);
70111
+ if (timer.kind === "timeout") {
70112
+ clearTimeout(timer.handle);
70113
+ } else {
70114
+ clearInterval(timer.handle);
70115
+ }
70023
70116
  heartbeatLog.log(`Cleared timer for ${agentId}`);
70024
70117
  }
70025
70118
  this.timers.clear();
@@ -70035,10 +70128,19 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
70035
70128
  static DEFAULT_HEARTBEAT_INTERVAL_MS = 36e5;
70036
70129
  /**
70037
70130
  * Register an agent for timer-based heartbeat triggers.
70131
+ *
70132
+ * The first fire is phase-aligned to `options.lastHeartbeatAt + intervalMs`
70133
+ * when supplied. This means a process restart resumes each agent's
70134
+ * existing schedule rather than waiting up to a full interval before the
70135
+ * first tick — the previous behavior caused agents on long intervals
70136
+ * (e.g. 1h) to appear "overdue" in the UI for nearly a full interval after
70137
+ * every dashboard restart even though nothing was actually wrong with them.
70138
+ *
70038
70139
  * @param agentId - The agent ID
70039
70140
  * @param config - Per-agent heartbeat config
70141
+ * @param options - Optional registration context (e.g., lastHeartbeatAt)
70040
70142
  */
70041
- registerAgent(agentId, config) {
70143
+ registerAgent(agentId, config, options) {
70042
70144
  if (config.enabled === false) {
70043
70145
  this.unregisterAgent(agentId);
70044
70146
  return;
@@ -70052,12 +70154,13 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
70052
70154
  const intervalMs = Math.max(1e3, Math.round(rawIntervalMs));
70053
70155
  const registrationEpoch = (this.registrationEpochs.get(agentId) ?? 0) + 1;
70054
70156
  this.registrationEpochs.set(agentId, registrationEpoch);
70055
- this.applyTimerRegistration(agentId, intervalMs, 1, usingDefaultInterval);
70157
+ const lastHeartbeatAt = options?.lastHeartbeatAt ?? null;
70158
+ this.applyTimerRegistration(agentId, intervalMs, 1, usingDefaultInterval, lastHeartbeatAt);
70056
70159
  if (this.taskStore && typeof this.taskStore.getSettings === "function") {
70057
- void this.applyProjectMultiplierRegistration(agentId, intervalMs, usingDefaultInterval, registrationEpoch);
70160
+ void this.applyProjectMultiplierRegistration(agentId, intervalMs, usingDefaultInterval, registrationEpoch, lastHeartbeatAt);
70058
70161
  }
70059
70162
  }
70060
- async applyProjectMultiplierRegistration(agentId, baseIntervalMs, usingDefaultInterval, expectedEpoch) {
70163
+ async applyProjectMultiplierRegistration(agentId, baseIntervalMs, usingDefaultInterval, expectedEpoch, lastHeartbeatAt) {
70061
70164
  let multiplier = 1;
70062
70165
  try {
70063
70166
  const settings = await getHeartbeatMemorySettings(this.taskStore);
@@ -70071,23 +70174,68 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
70071
70174
  if (this.registrationEpochs.get(agentId) !== expectedEpoch) {
70072
70175
  return;
70073
70176
  }
70074
- this.applyTimerRegistration(agentId, baseIntervalMs, multiplier, usingDefaultInterval);
70177
+ this.applyTimerRegistration(agentId, baseIntervalMs, multiplier, usingDefaultInterval, lastHeartbeatAt);
70178
+ }
70179
+ /**
70180
+ * Compute the delay until the agent's next scheduled fire, given when it
70181
+ * last heartbeat. When `lastHeartbeatAt` is missing or unparseable, falls
70182
+ * back to a full-interval delay (matching the original behavior for
70183
+ * agents that have never ticked). When the next fire is already overdue,
70184
+ * returns a small randomized jitter to spread thundering herds at boot.
70185
+ */
70186
+ static computeInitialDelayMs(intervalMs, lastHeartbeatAt, now = Date.now()) {
70187
+ if (!lastHeartbeatAt) {
70188
+ return intervalMs;
70189
+ }
70190
+ const lastMs = Date.parse(lastHeartbeatAt);
70191
+ if (!Number.isFinite(lastMs)) {
70192
+ return intervalMs;
70193
+ }
70194
+ const remaining = lastMs + intervalMs - now;
70195
+ if (remaining <= 0) {
70196
+ return Math.floor(Math.random() * OVERDUE_FIRE_JITTER_MS);
70197
+ }
70198
+ return Math.min(remaining, intervalMs);
70075
70199
  }
70076
- applyTimerRegistration(agentId, baseIntervalMs, multiplier, usingDefaultInterval) {
70200
+ applyTimerRegistration(agentId, baseIntervalMs, multiplier, usingDefaultInterval, lastHeartbeatAt) {
70077
70201
  const effectiveIntervalMs = Math.max(1e3, Math.round(baseIntervalMs * multiplier));
70202
+ const initialDelayMs = _HeartbeatTriggerScheduler.computeInitialDelayMs(
70203
+ effectiveIntervalMs,
70204
+ lastHeartbeatAt
70205
+ );
70078
70206
  this.clearAgentTimer(agentId);
70079
- const handle = setInterval(() => {
70080
- void this.onTimerTick(agentId, effectiveIntervalMs);
70081
- }, effectiveIntervalMs);
70082
- this.timers.set(agentId, { intervalMs: effectiveIntervalMs, handle });
70207
+ const armSteadyInterval = () => {
70208
+ const intervalHandle = setInterval(() => {
70209
+ void this.onTimerTick(agentId, effectiveIntervalMs);
70210
+ }, effectiveIntervalMs);
70211
+ this.timers.set(agentId, {
70212
+ intervalMs: effectiveIntervalMs,
70213
+ kind: "interval",
70214
+ handle: intervalHandle
70215
+ });
70216
+ };
70217
+ if (initialDelayMs >= effectiveIntervalMs) {
70218
+ armSteadyInterval();
70219
+ } else {
70220
+ const timeoutHandle = setTimeout(() => {
70221
+ void this.onTimerTick(agentId, effectiveIntervalMs);
70222
+ armSteadyInterval();
70223
+ }, initialDelayMs);
70224
+ this.timers.set(agentId, {
70225
+ intervalMs: effectiveIntervalMs,
70226
+ kind: "timeout",
70227
+ handle: timeoutHandle
70228
+ });
70229
+ }
70230
+ const phaseSuffix = lastHeartbeatAt ? `, first fire in ${initialDelayMs}ms (phase-aligned to lastHeartbeatAt)` : "";
70083
70231
  if (multiplier !== 1) {
70084
70232
  heartbeatLog.log(
70085
- `Registered timer for ${agentId} (every ${baseIntervalMs}ms, multiplier ${multiplier} \u2192 ${effectiveIntervalMs}ms effective)`
70233
+ `Registered timer for ${agentId} (every ${baseIntervalMs}ms, multiplier ${multiplier} \u2192 ${effectiveIntervalMs}ms effective${phaseSuffix})`
70086
70234
  );
70087
70235
  return;
70088
70236
  }
70089
70237
  heartbeatLog.log(
70090
- usingDefaultInterval ? `Registered timer for ${agentId} (every ${effectiveIntervalMs}ms, default interval)` : `Registered timer for ${agentId} (every ${effectiveIntervalMs}ms)`
70238
+ usingDefaultInterval ? `Registered timer for ${agentId} (every ${effectiveIntervalMs}ms, default interval${phaseSuffix})` : `Registered timer for ${agentId} (every ${effectiveIntervalMs}ms${phaseSuffix})`
70091
70239
  );
70092
70240
  }
70093
70241
  clearAgentTimer(agentId) {
@@ -70095,7 +70243,11 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
70095
70243
  if (!timer) {
70096
70244
  return;
70097
70245
  }
70098
- clearInterval(timer.handle);
70246
+ if (timer.kind === "timeout") {
70247
+ clearTimeout(timer.handle);
70248
+ } else {
70249
+ clearInterval(timer.handle);
70250
+ }
70099
70251
  this.timers.delete(agentId);
70100
70252
  }
70101
70253
  static resolveHeartbeatMultiplier(rawMultiplier) {
@@ -70223,7 +70375,9 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
70223
70375
  if (this.timers.has(agent.id)) {
70224
70376
  return;
70225
70377
  }
70226
- this.registerAgent(agent.id, this.getAgentTimerConfig(agent));
70378
+ this.registerAgent(agent.id, this.getAgentTimerConfig(agent), {
70379
+ lastHeartbeatAt: agent.lastHeartbeatAt
70380
+ });
70227
70381
  heartbeatLog.log(`Timer armed for ${agent.id} (${reason})`);
70228
70382
  }
70229
70383
  async syncTimerForAgentFromStore(agentId, reason) {
@@ -70236,7 +70390,9 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
70236
70390
  this.unregisterAgent(agentId);
70237
70391
  return;
70238
70392
  }
70239
- this.registerAgent(agent.id, this.getAgentTimerConfig(agent));
70393
+ this.registerAgent(agent.id, this.getAgentTimerConfig(agent), {
70394
+ lastHeartbeatAt: agent.lastHeartbeatAt
70395
+ });
70240
70396
  heartbeatLog.log(`Timer refreshed for ${agent.id} (${reason})`);
70241
70397
  }
70242
70398
  didHeartbeatScheduleChange(revision) {
@@ -75076,11 +75232,15 @@ var init_in_process_runtime = __esm({
75076
75232
  for (const agent of agents) {
75077
75233
  if (!isTimerManagedAgent(agent)) continue;
75078
75234
  const rc = agent.runtimeConfig;
75079
- this.triggerScheduler.registerAgent(agent.id, {
75080
- enabled: rc?.enabled,
75081
- heartbeatIntervalMs: rc?.heartbeatIntervalMs,
75082
- maxConcurrentRuns: rc?.maxConcurrentRuns
75083
- });
75235
+ this.triggerScheduler.registerAgent(
75236
+ agent.id,
75237
+ {
75238
+ enabled: rc?.enabled,
75239
+ heartbeatIntervalMs: rc?.heartbeatIntervalMs,
75240
+ maxConcurrentRuns: rc?.maxConcurrentRuns
75241
+ },
75242
+ { lastHeartbeatAt: agent.lastHeartbeatAt }
75243
+ );
75084
75244
  registeredCount++;
75085
75245
  }
75086
75246
  if (agents.length > 0) {
@@ -75767,7 +75927,7 @@ var init_ipc_host = __esm({
75767
75927
  import { EventEmitter as EventEmitter20 } from "node:events";
75768
75928
  import { fork } from "node:child_process";
75769
75929
  import { fileURLToPath as fileURLToPath3 } from "node:url";
75770
- import { dirname as dirname9, join as join36 } from "node:path";
75930
+ import { dirname as dirname10, join as join36 } from "node:path";
75771
75931
  var HealthMonitor, ChildProcessRuntime;
75772
75932
  var init_child_process_runtime = __esm({
75773
75933
  "../engine/src/runtimes/child-process-runtime.ts"() {
@@ -75927,7 +76087,7 @@ var init_child_process_runtime = __esm({
75927
76087
  */
75928
76088
  getWorkerPath() {
75929
76089
  const isCompiled = !import.meta.url.endsWith(".ts");
75930
- const currentDir = dirname9(fileURLToPath3(import.meta.url));
76090
+ const currentDir = dirname10(fileURLToPath3(import.meta.url));
75931
76091
  const workerFile = isCompiled ? "child-process-worker.js" : "child-process-worker.ts";
75932
76092
  return join36(currentDir, workerFile);
75933
76093
  }
@@ -77224,7 +77384,7 @@ var init_gridlock_detector = __esm({
77224
77384
  });
77225
77385
 
77226
77386
  // ../engine/src/remote-access/provider-adapters.ts
77227
- import { accessSync, constants as fsConstants2 } from "node:fs";
77387
+ import { accessSync, constants as fsConstants3 } from "node:fs";
77228
77388
  function isAbsoluteOrPathLike(input) {
77229
77389
  return input.startsWith("/") || input.startsWith("./") || input.startsWith("../");
77230
77390
  }
@@ -77371,7 +77531,7 @@ var init_provider_adapters = __esm({
77371
77531
  if ("credentialsPath" in config && config.credentialsPath !== void 0) {
77372
77532
  assertNonEmpty(config.credentialsPath, "credentialsPath");
77373
77533
  if (isAbsoluteOrPathLike(config.credentialsPath)) {
77374
- accessSync(config.credentialsPath, fsConstants2.R_OK);
77534
+ accessSync(config.credentialsPath, fsConstants3.R_OK);
77375
77535
  }
77376
77536
  }
77377
77537
  },
@@ -89328,7 +89488,7 @@ var init_exec_file = __esm({
89328
89488
 
89329
89489
  // ../dashboard/src/routes/register-project-routes.ts
89330
89490
  import * as fsPromises from "node:fs/promises";
89331
- var access4, stat6, mkdir13, readdir8, rm2;
89491
+ var access5, stat6, mkdir13, readdir8, rm2;
89332
89492
  var init_register_project_routes = __esm({
89333
89493
  "../dashboard/src/routes/register-project-routes.ts"() {
89334
89494
  "use strict";
@@ -89337,7 +89497,7 @@ var init_register_project_routes = __esm({
89337
89497
  init_exec_file();
89338
89498
  init_project_store_resolver();
89339
89499
  ({
89340
- access: access4,
89500
+ access: access5,
89341
89501
  stat: stat6,
89342
89502
  mkdir: mkdir13,
89343
89503
  readdir: readdir8,
@@ -89494,14 +89654,14 @@ var init_agent_generation = __esm({
89494
89654
 
89495
89655
  // ../dashboard/src/routes/register-agent-import-export-generation-routes.ts
89496
89656
  import * as fsPromises2 from "node:fs/promises";
89497
- var mkdtemp, access5, stat7, mkdir14, rm3, fsWriteFile;
89657
+ var mkdtemp, access6, stat7, mkdir14, rm3, fsWriteFile;
89498
89658
  var init_register_agent_import_export_generation_routes = __esm({
89499
89659
  "../dashboard/src/routes/register-agent-import-export-generation-routes.ts"() {
89500
89660
  "use strict";
89501
89661
  init_api_error();
89502
89662
  init_ai_session_diagnostics();
89503
89663
  init_agent_generation();
89504
- ({ mkdtemp, access: access5, stat: stat7, mkdir: mkdir14, rm: rm3, writeFile: fsWriteFile } = fsPromises2);
89664
+ ({ mkdtemp, access: access6, stat: stat7, mkdir: mkdir14, rm: rm3, writeFile: fsWriteFile } = fsPromises2);
89505
89665
  }
89506
89666
  });
89507
89667
 
@@ -94433,7 +94593,7 @@ var init_auth_middleware = __esm({
94433
94593
 
94434
94594
  // ../dashboard/src/server.ts
94435
94595
  import express from "express";
94436
- import { join as join37, dirname as dirname10 } from "node:path";
94596
+ import { join as join37, dirname as dirname11 } from "node:path";
94437
94597
  import { fileURLToPath as fileURLToPath4 } from "node:url";
94438
94598
  function clearAiSessionCleanupInterval() {
94439
94599
  if (!aiSessionCleanupIntervalHandle) {
@@ -94469,7 +94629,7 @@ var init_server = __esm({
94469
94629
  init_auth_middleware();
94470
94630
  init_remote_auth();
94471
94631
  init_cli_package_version();
94472
- __dirname = dirname10(fileURLToPath4(import.meta.url));
94632
+ __dirname = dirname11(fileURLToPath4(import.meta.url));
94473
94633
  MIN_AI_SESSION_TTL_MS = 10 * 60 * 1e3;
94474
94634
  MAX_AI_SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
94475
94635
  MIN_AI_SESSION_CLEANUP_INTERVAL_MS = 60 * 1e3;
@@ -94541,7 +94701,7 @@ var init_src4 = __esm({
94541
94701
  });
94542
94702
 
94543
94703
  // src/project-context.ts
94544
- import { resolve as resolve16, dirname as dirname11, basename as basename9 } from "node:path";
94704
+ import { resolve as resolve16, dirname as dirname12, basename as basename9 } from "node:path";
94545
94705
  async function resolveProject(projectNameFlag, cwd = process.cwd(), globalDir) {
94546
94706
  const central = new CentralCore(globalDir);
94547
94707
  await central.init();
@@ -94631,7 +94791,7 @@ async function detectProjectFromCwd(cwd, central) {
94631
94791
  path: currentDir
94632
94792
  };
94633
94793
  }
94634
- const parentDir = dirname11(currentDir);
94794
+ const parentDir = dirname12(currentDir);
94635
94795
  if (parentDir === currentDir) {
94636
94796
  break;
94637
94797
  }