syntaur 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.claude-plugin/plugin.json +3 -1
  2. package/README.md +21 -0
  3. package/dashboard/dist/assets/{_basePickBy-DPIRyskD.js → _basePickBy-qEDLAY_J.js} +1 -1
  4. package/dashboard/dist/assets/{_baseUniq-CwicZZgc.js → _baseUniq-7tDs18Gn.js} +1 -1
  5. package/dashboard/dist/assets/{arc-CcHe1anq.js → arc-Brdw0dYW.js} +1 -1
  6. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-4lwARCsm.js → architectureDiagram-2XIMDMQ5-BiEdLT9m.js} +1 -1
  7. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-SdqET9RP.js → blockDiagram-WCTKOSBZ-HC-j_hPZ.js} +1 -1
  8. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BAKj5SV2.js → c4Diagram-IC4MRINW-DA_rWbl7.js} +1 -1
  9. package/dashboard/dist/assets/channel-D2-caOn6.js +1 -0
  10. package/dashboard/dist/assets/{chunk-4BX2VUAB-Dn95vjsS.js → chunk-4BX2VUAB-CF3hguyj.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-55IACEB6-C6L5bDoe.js → chunk-55IACEB6-BTeCeIc_.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-FMBD7UC4---Go123J.js → chunk-FMBD7UC4-8qVvxmks.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-JSJVCQXG-C9E63Ryg.js → chunk-JSJVCQXG-DCf8n7EO.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-KX2RTZJC-Yyi0h479.js → chunk-KX2RTZJC-CcpjTmME.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-NQ4KR5QH-2Fh1XO45.js → chunk-NQ4KR5QH-BnFPKo4-.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-QZHKN3VN-B4a3DKz-.js → chunk-QZHKN3VN-IPMVLLv2.js} +1 -1
  17. package/dashboard/dist/assets/{chunk-WL4C6EOR-XtMf_tFy.js → chunk-WL4C6EOR-D1pQmHqX.js} +1 -1
  18. package/dashboard/dist/assets/classDiagram-VBA2DB6C-HFOb_XEt.js +1 -0
  19. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-HFOb_XEt.js +1 -0
  20. package/dashboard/dist/assets/clone-BrYOwgIq.js +1 -0
  21. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-8wHbmnAP.js → cose-bilkent-S5V4N54A-tvJjvBQr.js} +1 -1
  22. package/dashboard/dist/assets/{dagre-KLK3FWXG-uJZOLE_S.js → dagre-KLK3FWXG-qp1vt6LY.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-E7M64L7V-BZih6Ivf.js → diagram-E7M64L7V-VF5eizNr.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-IFDJBPK2-CtR6WhET.js → diagram-IFDJBPK2-C3pCbJ1e.js} +1 -1
  25. package/dashboard/dist/assets/{diagram-P4PSJMXO-DTGhoDSE.js → diagram-P4PSJMXO-D-taXZwH.js} +1 -1
  26. package/dashboard/dist/assets/{erDiagram-INFDFZHY-Yr93h1x4.js → erDiagram-INFDFZHY-QjY-NHVs.js} +1 -1
  27. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CZ3QY4eZ.js → flowDiagram-PKNHOUZH-FjO8P5Tq.js} +1 -1
  28. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CSf--huI.js → ganttDiagram-A5KZAMGK-Bsn7CyK3.js} +1 -1
  29. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-CokKKyLR.js → gitGraphDiagram-K3NZZRJ6-BZ8DjhnF.js} +1 -1
  30. package/dashboard/dist/assets/{graph-DRs4cE7X.js → graph-AdliIYqX.js} +1 -1
  31. package/dashboard/dist/assets/index-BoR_oyUZ.css +1 -0
  32. package/dashboard/dist/assets/index-RCBI4aX2.js +525 -0
  33. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-C4CTdWLD.js → infoDiagram-LFFYTUFH-BfZjLA86.js} +1 -1
  34. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DGrfYHHk.js → ishikawaDiagram-PHBUUO56-BSgSGMp5.js} +1 -1
  35. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CEDbuCY2.js → journeyDiagram-4ABVD52K-BlQ7qeMC.js} +1 -1
  36. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BjgAWDsE.js → kanban-definition-K7BYSVSG-CifO0AUu.js} +1 -1
  37. package/dashboard/dist/assets/{layout-Coi0pRnm.js → layout-B9r-gpD9.js} +1 -1
  38. package/dashboard/dist/assets/{linear-B4xKytgE.js → linear-CaaIRxVZ.js} +1 -1
  39. package/dashboard/dist/assets/{mermaid.core-dkpoLY2K.js → mermaid.core-C8wCWYw-.js} +4 -4
  40. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-BgUfAXkB.js → mindmap-definition-YRQLILUH-BpX5T7iT.js} +1 -1
  41. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-BBuMJroZ.js → pieDiagram-SKSYHLDU-BNGYtWrc.js} +1 -1
  42. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-iXygRpen.js → quadrantDiagram-337W2JSQ-CR4GeM9D.js} +1 -1
  43. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-DhJGFHd3.js → requirementDiagram-Z7DCOOCP-CXrPkMLl.js} +1 -1
  44. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-CLkFLBq1.js → sankeyDiagram-WA2Y5GQK-CIzof7Dm.js} +1 -1
  45. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-DQh28Zt-.js → sequenceDiagram-2WXFIKYE-BJZLt0O2.js} +1 -1
  46. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-BZKsD9LJ.js → stateDiagram-RAJIS63D-x9gC6zy6.js} +1 -1
  47. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-CN5Dcv7u.js +1 -0
  48. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-DCJlSWVw.js → timeline-definition-YZTLITO2-D_ZSQn1K.js} +1 -1
  49. package/dashboard/dist/assets/{treemap-KZPCXAKY-DEGy-zW9.js → treemap-KZPCXAKY-LPpMEco4.js} +1 -1
  50. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-CfQAKq7b.js → vennDiagram-LZ73GAT5-CRFZQpr7.js} +1 -1
  51. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-CgD-asFJ.js → xychartDiagram-JWTSCODW-Bivutynw.js} +1 -1
  52. package/dashboard/dist/index.html +2 -2
  53. package/dist/dashboard/server.js +355 -81
  54. package/dist/dashboard/server.js.map +1 -1
  55. package/dist/db/leases-db.d.ts +178 -0
  56. package/dist/db/leases-db.js +557 -0
  57. package/dist/db/leases-db.js.map +1 -0
  58. package/dist/index.js +1219 -357
  59. package/dist/index.js.map +1 -1
  60. package/package.json +1 -1
  61. package/platforms/claude-code/.claude-plugin/plugin.json +3 -1
  62. package/platforms/claude-code/skills/claim-resource/SKILL.md +89 -0
  63. package/platforms/claude-code/skills/release-resource/SKILL.md +65 -0
  64. package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +1 -0
  65. package/platforms/codex/skills/claim-resource/SKILL.md +89 -0
  66. package/platforms/codex/skills/release-resource/SKILL.md +65 -0
  67. package/platforms/codex/skills/syntaur-protocol/SKILL.md +1 -0
  68. package/skills/claim-resource/SKILL.md +89 -0
  69. package/skills/release-resource/SKILL.md +65 -0
  70. package/skills/syntaur-protocol/SKILL.md +1 -0
  71. package/dashboard/dist/assets/channel-DKdluNz1.js +0 -1
  72. package/dashboard/dist/assets/classDiagram-VBA2DB6C-93dPLc4W.js +0 -1
  73. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-93dPLc4W.js +0 -1
  74. package/dashboard/dist/assets/clone-vnWuXQgZ.js +0 -1
  75. package/dashboard/dist/assets/index-B9uCPhOL.js +0 -510
  76. package/dashboard/dist/assets/index-BKoccIZR.css +0 -1
  77. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DU5UnRV-.js +0 -1
@@ -5258,7 +5258,7 @@ __export(parser_exports, {
5258
5258
  });
5259
5259
  import { randomBytes } from "crypto";
5260
5260
  import { readFile as readFile12 } from "fs/promises";
5261
- import { resolve as resolve15 } from "path";
5261
+ import { resolve as resolve16 } from "path";
5262
5262
  function generateShortId() {
5263
5263
  return randomBytes(2).toString("hex");
5264
5264
  }
@@ -5496,10 +5496,10 @@ function serializeLogEntry(entry) {
5496
5496
  return lines.join("\n");
5497
5497
  }
5498
5498
  function checklistPath(todosDir2, workspace) {
5499
- return resolve15(todosDir2, `${workspace}.md`);
5499
+ return resolve16(todosDir2, `${workspace}.md`);
5500
5500
  }
5501
5501
  function logPath(todosDir2, workspace) {
5502
- return resolve15(todosDir2, `${workspace}-log.md`);
5502
+ return resolve16(todosDir2, `${workspace}-log.md`);
5503
5503
  }
5504
5504
  function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new Date()) {
5505
5505
  const year = now.getFullYear();
@@ -5523,7 +5523,7 @@ function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new D
5523
5523
  default:
5524
5524
  suffix = `${year}-${month}-${day}`;
5525
5525
  }
5526
- return resolve15(todosDir2, "archive", `${workspace}-${suffix}.md`);
5526
+ return resolve16(todosDir2, "archive", `${workspace}-${suffix}.md`);
5527
5527
  }
5528
5528
  async function readChecklist(todosDir2, workspace) {
5529
5529
  const path = checklistPath(todosDir2, workspace);
@@ -5638,7 +5638,7 @@ var create_assignment_exports = {};
5638
5638
  __export(create_assignment_exports, {
5639
5639
  createAssignmentCommand: () => createAssignmentCommand
5640
5640
  });
5641
- import { resolve as resolve16 } from "path";
5641
+ import { resolve as resolve17 } from "path";
5642
5642
  async function createAssignmentCommand(title, options) {
5643
5643
  if (!title.trim()) {
5644
5644
  throw new Error("Assignment title cannot be empty.");
@@ -5713,14 +5713,14 @@ async function createAssignmentCommand(title, options) {
5713
5713
  if (options.oneOff) {
5714
5714
  const standaloneRoot = assignmentsDir();
5715
5715
  folderName = id;
5716
- assignmentDir = resolve16(standaloneRoot, folderName);
5716
+ assignmentDir = resolve17(standaloneRoot, folderName);
5717
5717
  projectSlug = null;
5718
5718
  await ensureDir(standaloneRoot);
5719
5719
  } else {
5720
5720
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
5721
5721
  projectSlug = options.project;
5722
- const projectDir = resolve16(baseDir, projectSlug);
5723
- const projectMdPath = resolve16(projectDir, "project.md");
5722
+ const projectDir = resolve17(baseDir, projectSlug);
5723
+ const projectMdPath = resolve17(projectDir, "project.md");
5724
5724
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
5725
5725
  throw new Error(
5726
5726
  `Project "${projectSlug}" not found at ${projectDir}.
@@ -5728,9 +5728,9 @@ Run 'syntaur create-project' first or use --one-off.`
5728
5728
  );
5729
5729
  }
5730
5730
  if (dependsOn.length > 0) {
5731
- const depDirBase = resolve16(projectDir, "assignments");
5731
+ const depDirBase = resolve17(projectDir, "assignments");
5732
5732
  for (const dep of dependsOn) {
5733
- const depDir = resolve16(depDirBase, dep);
5733
+ const depDir = resolve17(depDirBase, dep);
5734
5734
  if (!await fileExists(depDir)) {
5735
5735
  console.warn(
5736
5736
  `Warning: dependency "${dep}" does not exist in project "${projectSlug}" yet.`
@@ -5739,7 +5739,7 @@ Run 'syntaur create-project' first or use --one-off.`
5739
5739
  }
5740
5740
  }
5741
5741
  folderName = assignmentSlug;
5742
- assignmentDir = resolve16(projectDir, "assignments", folderName);
5742
+ assignmentDir = resolve17(projectDir, "assignments", folderName);
5743
5743
  }
5744
5744
  if (await fileExists(assignmentDir)) {
5745
5745
  throw new Error(
@@ -5751,7 +5751,7 @@ Use --slug to specify a different slug.`
5751
5751
  const companionAssignmentRef = projectSlug === null ? id : assignmentSlug;
5752
5752
  const files = [
5753
5753
  [
5754
- resolve16(assignmentDir, "assignment.md"),
5754
+ resolve17(assignmentDir, "assignment.md"),
5755
5755
  renderAssignment({
5756
5756
  id,
5757
5757
  slug: assignmentSlug,
@@ -5767,35 +5767,35 @@ Use --slug to specify a different slug.`
5767
5767
  })
5768
5768
  ],
5769
5769
  [
5770
- resolve16(assignmentDir, "scratchpad.md"),
5770
+ resolve17(assignmentDir, "scratchpad.md"),
5771
5771
  renderScratchpad({
5772
5772
  assignmentSlug: companionAssignmentRef,
5773
5773
  timestamp
5774
5774
  })
5775
5775
  ],
5776
5776
  [
5777
- resolve16(assignmentDir, "handoff.md"),
5777
+ resolve17(assignmentDir, "handoff.md"),
5778
5778
  renderHandoff({
5779
5779
  assignmentSlug: companionAssignmentRef,
5780
5780
  timestamp
5781
5781
  })
5782
5782
  ],
5783
5783
  [
5784
- resolve16(assignmentDir, "decision-record.md"),
5784
+ resolve17(assignmentDir, "decision-record.md"),
5785
5785
  renderDecisionRecord({
5786
5786
  assignmentSlug: companionAssignmentRef,
5787
5787
  timestamp
5788
5788
  })
5789
5789
  ],
5790
5790
  [
5791
- resolve16(assignmentDir, "progress.md"),
5791
+ resolve17(assignmentDir, "progress.md"),
5792
5792
  renderProgress({
5793
5793
  assignment: companionAssignmentRef,
5794
5794
  timestamp
5795
5795
  })
5796
5796
  ],
5797
5797
  [
5798
- resolve16(assignmentDir, "comments.md"),
5798
+ resolve17(assignmentDir, "comments.md"),
5799
5799
  renderComments({
5800
5800
  assignment: companionAssignmentRef,
5801
5801
  timestamp
@@ -5860,7 +5860,7 @@ init_api();
5860
5860
  init_assignment_resolver();
5861
5861
  import express from "express";
5862
5862
  import { createServer } from "http";
5863
- import { resolve as resolve19 } from "path";
5863
+ import { resolve as resolve20 } from "path";
5864
5864
  import { writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
5865
5865
  import { WebSocketServer, WebSocket } from "ws";
5866
5866
 
@@ -6075,8 +6075,8 @@ function rowToSession(row) {
6075
6075
  };
6076
6076
  }
6077
6077
  async function appendSession(_projectDir, session) {
6078
- const db2 = getSessionDb();
6079
- db2.prepare(`
6078
+ const db3 = getSessionDb();
6079
+ db3.prepare(`
6080
6080
  INSERT INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path, description, transcript_path)
6081
6081
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
6082
6082
  ON CONFLICT(session_id) DO UPDATE SET
@@ -6101,36 +6101,36 @@ async function appendSession(_projectDir, session) {
6101
6101
  );
6102
6102
  }
6103
6103
  async function updateSessionStatus(_projectDir, sessionId, status) {
6104
- const db2 = getSessionDb();
6104
+ const db3 = getSessionDb();
6105
6105
  const isTerminal = status === "completed" || status === "stopped";
6106
- const result = isTerminal ? db2.prepare(
6106
+ const result = isTerminal ? db3.prepare(
6107
6107
  "UPDATE sessions SET status = ?, ended = datetime('now'), updated_at = datetime('now') WHERE session_id = ?"
6108
- ).run(status, sessionId) : db2.prepare(
6108
+ ).run(status, sessionId) : db3.prepare(
6109
6109
  "UPDATE sessions SET status = ?, updated_at = datetime('now') WHERE session_id = ?"
6110
6110
  ).run(status, sessionId);
6111
6111
  return result.changes > 0;
6112
6112
  }
6113
6113
  async function listAllSessions(_projectsDir) {
6114
- const db2 = getSessionDb();
6115
- const rows = db2.prepare("SELECT * FROM sessions ORDER BY started DESC").all();
6114
+ const db3 = getSessionDb();
6115
+ const rows = db3.prepare("SELECT * FROM sessions ORDER BY started DESC").all();
6116
6116
  return rows.map(rowToSession);
6117
6117
  }
6118
6118
  async function listProjectSessions(_projectsDir, projectSlug, assignmentSlug) {
6119
- const db2 = getSessionDb();
6119
+ const db3 = getSessionDb();
6120
6120
  if (assignmentSlug) {
6121
- const rows2 = db2.prepare(
6121
+ const rows2 = db3.prepare(
6122
6122
  "SELECT * FROM sessions WHERE project_slug = ? AND assignment_slug = ? ORDER BY started DESC"
6123
6123
  ).all(projectSlug, assignmentSlug);
6124
6124
  return rows2.map(rowToSession);
6125
6125
  }
6126
- const rows = db2.prepare("SELECT * FROM sessions WHERE project_slug = ? ORDER BY started DESC").all(projectSlug);
6126
+ const rows = db3.prepare("SELECT * FROM sessions WHERE project_slug = ? ORDER BY started DESC").all(projectSlug);
6127
6127
  return rows.map(rowToSession);
6128
6128
  }
6129
6129
  async function deleteSessions(sessionIds) {
6130
6130
  if (sessionIds.length === 0) return 0;
6131
- const db2 = getSessionDb();
6131
+ const db3 = getSessionDb();
6132
6132
  const placeholders = sessionIds.map(() => "?").join(", ");
6133
- const result = db2.prepare(`DELETE FROM sessions WHERE session_id IN (${placeholders})`).run(...sessionIds);
6133
+ const result = db3.prepare(`DELETE FROM sessions WHERE session_id IN (${placeholders})`).run(...sessionIds);
6134
6134
  return result.changes;
6135
6135
  }
6136
6136
  var DONE_ASSIGNMENT_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "review"]);
@@ -6146,8 +6146,8 @@ async function readAssignmentStatus(projectDir, assignmentSlug) {
6146
6146
  );
6147
6147
  }
6148
6148
  async function reconcileActiveSessions(projectsDir, assignmentsDir2) {
6149
- const db2 = getSessionDb();
6150
- const activeSessions = db2.prepare("SELECT * FROM sessions WHERE status = 'active' AND assignment_slug IS NOT NULL").all();
6149
+ const db3 = getSessionDb();
6150
+ const activeSessions = db3.prepare("SELECT * FROM sessions WHERE status = 'active' AND assignment_slug IS NOT NULL").all();
6151
6151
  if (activeSessions.length === 0) return 0;
6152
6152
  const assignmentStatuses = /* @__PURE__ */ new Map();
6153
6153
  const seen = /* @__PURE__ */ new Set();
@@ -6184,10 +6184,10 @@ async function reconcileActiveSessions(projectsDir, assignmentsDir2) {
6184
6184
  return totalUpdated;
6185
6185
  }
6186
6186
  async function listSessionsByAssignment(projectSlug, assignmentSlug) {
6187
- const db2 = getSessionDb();
6188
- const rows = projectSlug === null ? db2.prepare(
6187
+ const db3 = getSessionDb();
6188
+ const rows = projectSlug === null ? db3.prepare(
6189
6189
  "SELECT * FROM sessions WHERE assignment_slug = ? AND project_slug IS NULL ORDER BY started DESC"
6190
- ).all(assignmentSlug) : db2.prepare(
6190
+ ).all(assignmentSlug) : db3.prepare(
6191
6191
  "SELECT * FROM sessions WHERE project_slug = ? AND assignment_slug = ? ORDER BY started DESC"
6192
6192
  ).all(projectSlug, assignmentSlug);
6193
6193
  return rows.map(rowToSession);
@@ -6195,9 +6195,9 @@ async function listSessionsByAssignment(projectSlug, assignmentSlug) {
6195
6195
 
6196
6196
  // src/dashboard/watcher.ts
6197
6197
  import { watch } from "chokidar";
6198
- import { relative, sep } from "path";
6198
+ import { basename as basename2, dirname as dirname3, relative, sep } from "path";
6199
6199
  function createWatcher(options) {
6200
- const { projectsDir, assignmentsDir: assignmentsDir2, serversDir: serversDir2, playbooksDir: playbooksDir2, todosDir: todosDir2, onMessage, debounceMs = 300 } = options;
6200
+ const { projectsDir, assignmentsDir: assignmentsDir2, serversDir: serversDir2, playbooksDir: playbooksDir2, todosDir: todosDir2, dbPath, onMessage, debounceMs = 300 } = options;
6201
6201
  const pendingEvents = /* @__PURE__ */ new Map();
6202
6202
  const projectsWatcher = watch(projectsDir, {
6203
6203
  ignoreInitial: true,
@@ -6365,6 +6365,38 @@ function createWatcher(options) {
6365
6365
  todosWatcher.on("add", handleTodoChange2);
6366
6366
  todosWatcher.on("unlink", handleTodoChange2);
6367
6367
  }
6368
+ let leasesDbWatcher = null;
6369
+ if (dbPath) {
6370
+ let handleDbChange2 = function(filePath) {
6371
+ if (!basename2(filePath).startsWith(dbBase)) return;
6372
+ const debounceKey = "__leases-db__";
6373
+ const existing = pendingEvents.get(debounceKey);
6374
+ if (existing) clearTimeout(existing);
6375
+ pendingEvents.set(
6376
+ debounceKey,
6377
+ setTimeout(() => {
6378
+ pendingEvents.delete(debounceKey);
6379
+ const message = {
6380
+ type: "leases-updated",
6381
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6382
+ };
6383
+ onMessage(message);
6384
+ }, debounceMs)
6385
+ );
6386
+ };
6387
+ var handleDbChange = handleDbChange2;
6388
+ const dbDir = dirname3(dbPath);
6389
+ const dbBase = basename2(dbPath);
6390
+ leasesDbWatcher = watch(dbDir, {
6391
+ ignoreInitial: true,
6392
+ persistent: true,
6393
+ depth: 0,
6394
+ ignored: /(^|[\/\\])\../
6395
+ });
6396
+ leasesDbWatcher.on("change", handleDbChange2);
6397
+ leasesDbWatcher.on("add", handleDbChange2);
6398
+ leasesDbWatcher.on("unlink", handleDbChange2);
6399
+ }
6368
6400
  return {
6369
6401
  close: async () => {
6370
6402
  pendingEvents.forEach((timeout) => {
@@ -6376,6 +6408,7 @@ function createWatcher(options) {
6376
6408
  if (serversWatcher) await serversWatcher.close();
6377
6409
  if (playbooksWatcher) await playbooksWatcher.close();
6378
6410
  if (todosWatcher) await todosWatcher.close();
6411
+ if (leasesDbWatcher) await leasesDbWatcher.close();
6379
6412
  }
6380
6413
  };
6381
6414
  }
@@ -6393,7 +6426,7 @@ init_timestamp();
6393
6426
  init_fs();
6394
6427
  init_parser();
6395
6428
  import { Router } from "express";
6396
- import { resolve as resolve12, basename as basename2 } from "path";
6429
+ import { resolve as resolve12, basename as basename3 } from "path";
6397
6430
  import { rm, readFile as readFile10, open as fsOpen } from "fs/promises";
6398
6431
 
6399
6432
  // src/dashboard/acceptance-criteria.ts
@@ -6678,7 +6711,7 @@ function createWriteRouter(projectsDir, assignmentsDir2) {
6678
6711
  res.status(404).json({ error: `Project "${slug}" not found` });
6679
6712
  return;
6680
6713
  }
6681
- const document = await getEditableDocument(projectsDir, "memory", basename2(projectDir), itemSlug);
6714
+ const document = await getEditableDocument(projectsDir, "memory", basename3(projectDir), itemSlug);
6682
6715
  if (!document) {
6683
6716
  res.status(404).json({ error: "Memory not found" });
6684
6717
  return;
@@ -6697,7 +6730,7 @@ function createWriteRouter(projectsDir, assignmentsDir2) {
6697
6730
  res.status(404).json({ error: `Project "${slug}" not found` });
6698
6731
  return;
6699
6732
  }
6700
- const document = await getEditableDocument(projectsDir, "resource", basename2(projectDir), itemSlug);
6733
+ const document = await getEditableDocument(projectsDir, "resource", basename3(projectDir), itemSlug);
6701
6734
  if (!document) {
6702
6735
  res.status(404).json({ error: "Resource not found" });
6703
6736
  return;
@@ -6782,7 +6815,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
6782
6815
  let content = renderItemStub(kind, {
6783
6816
  slug: requestedSlug,
6784
6817
  name,
6785
- projectSlug: basename2(projectDir),
6818
+ projectSlug: basename3(projectDir),
6786
6819
  timestamp
6787
6820
  });
6788
6821
  const customBody = typeof body.body === "string" ? body.body : "";
@@ -6799,13 +6832,13 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
6799
6832
  } catch (err) {
6800
6833
  if (err.code === "EEXIST") {
6801
6834
  res.status(409).json({
6802
- error: `${kind === "memory" ? "Memory" : "Resource"} with slug "${requestedSlug}" already exists in project "${basename2(projectDir)}".`
6835
+ error: `${kind === "memory" ? "Memory" : "Resource"} with slug "${requestedSlug}" already exists in project "${basename3(projectDir)}".`
6803
6836
  });
6804
6837
  return;
6805
6838
  }
6806
6839
  throw err;
6807
6840
  }
6808
- res.status(201).json({ slug: requestedSlug, projectSlug: basename2(projectDir), content });
6841
+ res.status(201).json({ slug: requestedSlug, projectSlug: basename3(projectDir), content });
6809
6842
  } catch (error) {
6810
6843
  console.error(`Error creating ${kind}:`, error);
6811
6844
  res.status(500).json({ error: `Failed to create ${kind}: ${error.message}` });
@@ -6840,7 +6873,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
6840
6873
  ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
6841
6874
  merged = setTopLevelField(merged, "updated", nowTimestamp());
6842
6875
  await writeFileForce(filePath, merged);
6843
- const detail = await getItemDetail(kind, basename2(projectDir), itemSlug);
6876
+ const detail = await getItemDetail(kind, basename3(projectDir), itemSlug);
6844
6877
  res.json({ [kind]: detail, content: merged });
6845
6878
  } catch (error) {
6846
6879
  console.error(`Error updating ${kind}:`, error);
@@ -8355,6 +8388,243 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
8355
8388
  return router;
8356
8389
  }
8357
8390
 
8391
+ // src/dashboard/api-leases.ts
8392
+ import { Router as Router4 } from "express";
8393
+
8394
+ // src/db/leases-db.ts
8395
+ init_paths();
8396
+ import Database2 from "better-sqlite3";
8397
+ import { randomUUID as randomUUID2 } from "crypto";
8398
+ import { resolve as resolve14 } from "path";
8399
+ var db2 = null;
8400
+ var LEASE_SCHEMA_VERSION = "1";
8401
+ var SCHEMA_SQL2 = `
8402
+ CREATE TABLE IF NOT EXISTS meta (
8403
+ key TEXT PRIMARY KEY,
8404
+ value TEXT
8405
+ );
8406
+
8407
+ CREATE TABLE IF NOT EXISTS inventories (
8408
+ slug TEXT PRIMARY KEY,
8409
+ kind TEXT NOT NULL,
8410
+ display_name TEXT,
8411
+ default_ttl_s INTEGER NOT NULL CHECK (default_ttl_s > 0),
8412
+ created_at TEXT NOT NULL
8413
+ );
8414
+
8415
+ CREATE TABLE IF NOT EXISTS inventory_members (
8416
+ inventory_slug TEXT NOT NULL,
8417
+ member_id TEXT NOT NULL,
8418
+ status TEXT NOT NULL DEFAULT 'idle'
8419
+ CHECK (status IN ('idle','leased','retired')),
8420
+ generation INTEGER NOT NULL DEFAULT 0,
8421
+ metadata_json TEXT,
8422
+ last_used_at TEXT,
8423
+ retired_at TEXT,
8424
+ PRIMARY KEY (inventory_slug, member_id),
8425
+ FOREIGN KEY (inventory_slug) REFERENCES inventories(slug)
8426
+ );
8427
+
8428
+ CREATE INDEX IF NOT EXISTS idx_members_idle
8429
+ ON inventory_members (inventory_slug, status, last_used_at);
8430
+
8431
+ CREATE TABLE IF NOT EXISTS leases (
8432
+ lease_id TEXT PRIMARY KEY,
8433
+ inventory_slug TEXT NOT NULL,
8434
+ member_id TEXT NOT NULL,
8435
+ member_gen INTEGER NOT NULL,
8436
+ state TEXT NOT NULL
8437
+ CHECK (state IN ('active','released','expired','revoked')),
8438
+ granted_at TEXT NOT NULL,
8439
+ expires_at TEXT NOT NULL,
8440
+ released_at TEXT,
8441
+ requested_for TEXT,
8442
+ FOREIGN KEY (inventory_slug, member_id)
8443
+ REFERENCES inventory_members(inventory_slug, member_id)
8444
+ );
8445
+
8446
+ CREATE INDEX IF NOT EXISTS idx_leases_gc
8447
+ ON leases (state, expires_at);
8448
+
8449
+ CREATE TABLE IF NOT EXISTS lease_events (
8450
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
8451
+ lease_id TEXT NOT NULL,
8452
+ event TEXT NOT NULL,
8453
+ at TEXT NOT NULL,
8454
+ detail_json TEXT,
8455
+ FOREIGN KEY (lease_id) REFERENCES leases(lease_id)
8456
+ );
8457
+
8458
+ CREATE INDEX IF NOT EXISTS idx_events_lease
8459
+ ON lease_events (lease_id, at);
8460
+ `;
8461
+ var NotFoundError = class extends Error {
8462
+ constructor(id) {
8463
+ super(`lease ${id} not found`);
8464
+ this.id = id;
8465
+ this.name = "NotFoundError";
8466
+ }
8467
+ };
8468
+ var LeaseContentionError = class extends Error {
8469
+ constructor(inventorySlug) {
8470
+ super(`contention timeout on '${inventorySlug}'; retry`);
8471
+ this.inventorySlug = inventorySlug;
8472
+ this.name = "LeaseContentionError";
8473
+ }
8474
+ };
8475
+ function nowIso() {
8476
+ return (/* @__PURE__ */ new Date()).toISOString();
8477
+ }
8478
+ function isBusyError(err) {
8479
+ if (!err || typeof err !== "object") return false;
8480
+ const e = err;
8481
+ return e.code === "SQLITE_BUSY" || e.code === "SQLITE_BUSY_SNAPSHOT";
8482
+ }
8483
+ function initLeasesDb(dbPath) {
8484
+ if (db2) return db2;
8485
+ const finalPath = dbPath ?? resolve14(syntaurRoot(), "syntaur.db");
8486
+ db2 = new Database2(finalPath);
8487
+ db2.pragma("journal_mode = WAL");
8488
+ db2.pragma("busy_timeout = 5000");
8489
+ db2.pragma("foreign_keys = ON");
8490
+ const database = db2;
8491
+ const runMigrations = database.transaction(() => {
8492
+ database.exec(SCHEMA_SQL2);
8493
+ database.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run("lease_schema_version", LEASE_SCHEMA_VERSION);
8494
+ });
8495
+ runMigrations.exclusive();
8496
+ return db2;
8497
+ }
8498
+ function getLeasesDb() {
8499
+ if (!db2) {
8500
+ throw new Error("Leases database not initialized. Call initLeasesDb() first.");
8501
+ }
8502
+ return db2;
8503
+ }
8504
+ function closeLeasesDb() {
8505
+ if (db2) {
8506
+ db2.close();
8507
+ db2 = null;
8508
+ }
8509
+ }
8510
+ function listInventories() {
8511
+ const database = getLeasesDb();
8512
+ return database.prepare(
8513
+ `SELECT slug, kind, display_name, default_ttl_s, created_at
8514
+ FROM inventories ORDER BY slug`
8515
+ ).all();
8516
+ }
8517
+ function getInventoryDetail(slug) {
8518
+ const database = getLeasesDb();
8519
+ const inv = database.prepare(
8520
+ `SELECT slug, kind, display_name, default_ttl_s, created_at
8521
+ FROM inventories WHERE slug = ?`
8522
+ ).get(slug);
8523
+ if (!inv) return null;
8524
+ const members = database.prepare(
8525
+ `SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at
8526
+ FROM inventory_members WHERE inventory_slug = ?
8527
+ ORDER BY member_id`
8528
+ ).all(slug);
8529
+ const active_leases = database.prepare(
8530
+ `SELECT lease_id, inventory_slug, member_id, member_gen, state, granted_at, expires_at, released_at, requested_for
8531
+ FROM leases WHERE inventory_slug = ? AND state = 'active'
8532
+ ORDER BY granted_at`
8533
+ ).all(slug);
8534
+ return { inventory: inv, members, active_leases };
8535
+ }
8536
+ function forceReleaseLease(lease_id) {
8537
+ const database = getLeasesDb();
8538
+ try {
8539
+ const fn = database.transaction(() => {
8540
+ const now = nowIso();
8541
+ const lookup = database.prepare(
8542
+ `SELECT inventory_slug, member_id, member_gen, state
8543
+ FROM leases WHERE lease_id = ?`
8544
+ ).get(lease_id);
8545
+ if (!lookup) throw new NotFoundError(lease_id);
8546
+ if (lookup.state === "revoked") return { member_freed: false };
8547
+ const memberRes = database.prepare(
8548
+ `UPDATE inventory_members
8549
+ SET status = 'idle', generation = generation + 1
8550
+ WHERE inventory_slug = ? AND member_id = ?
8551
+ AND generation = ?`
8552
+ ).run(lookup.inventory_slug, lookup.member_id, lookup.member_gen);
8553
+ database.prepare(
8554
+ `UPDATE leases
8555
+ SET state = 'revoked', released_at = ?
8556
+ WHERE lease_id = ? AND state != 'revoked'`
8557
+ ).run(now, lease_id);
8558
+ const member_freed = memberRes.changes > 0;
8559
+ database.prepare(
8560
+ `INSERT INTO lease_events (lease_id, event, at, detail_json)
8561
+ VALUES (?, 'force_released', ?, ?)`
8562
+ ).run(lease_id, now, JSON.stringify({ member_freed }));
8563
+ return { member_freed };
8564
+ });
8565
+ return fn.immediate();
8566
+ } catch (err) {
8567
+ if (isBusyError(err)) throw new LeaseContentionError(lease_id);
8568
+ throw err;
8569
+ }
8570
+ }
8571
+
8572
+ // src/dashboard/api-leases.ts
8573
+ function createLeasesRouter(broadcast) {
8574
+ const router = Router4();
8575
+ router.get("/", (_req, res) => {
8576
+ try {
8577
+ initLeasesDb();
8578
+ const inventories = listInventories();
8579
+ const details = inventories.map((inv) => getInventoryDetail(inv.slug)).filter((d) => d !== null);
8580
+ res.json({ inventories: details });
8581
+ } catch (error) {
8582
+ res.status(500).json({
8583
+ error: error instanceof Error ? error.message : "List failed"
8584
+ });
8585
+ }
8586
+ });
8587
+ router.get("/:slug", (req, res) => {
8588
+ try {
8589
+ initLeasesDb();
8590
+ const detail = getInventoryDetail(req.params.slug);
8591
+ if (!detail) {
8592
+ res.status(404).json({ error: "Inventory not found" });
8593
+ return;
8594
+ }
8595
+ res.json(detail);
8596
+ } catch (error) {
8597
+ res.status(500).json({
8598
+ error: error instanceof Error ? error.message : "Read failed"
8599
+ });
8600
+ }
8601
+ });
8602
+ router.post("/:slug/force-release/:lease_id", (req, res) => {
8603
+ try {
8604
+ initLeasesDb();
8605
+ const result = forceReleaseLease(req.params.lease_id);
8606
+ broadcast?.({
8607
+ type: "leases-updated",
8608
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8609
+ });
8610
+ res.json({ ...result, lease_id: req.params.lease_id });
8611
+ } catch (error) {
8612
+ if (error instanceof NotFoundError) {
8613
+ res.status(404).json({ error: "Lease not found" });
8614
+ return;
8615
+ }
8616
+ if (error instanceof LeaseContentionError) {
8617
+ res.status(503).json({ error: "Contention timeout; retry" });
8618
+ return;
8619
+ }
8620
+ res.status(500).json({
8621
+ error: error instanceof Error ? error.message : "Force release failed"
8622
+ });
8623
+ }
8624
+ });
8625
+ return router;
8626
+ }
8627
+
8358
8628
  // src/dashboard/api-playbooks.ts
8359
8629
  init_api();
8360
8630
  init_parser();
@@ -8363,11 +8633,11 @@ init_timestamp();
8363
8633
  init_fs();
8364
8634
  init_playbook();
8365
8635
  init_playbooks();
8366
- import { Router as Router4 } from "express";
8367
- import { resolve as resolve14 } from "path";
8636
+ import { Router as Router5 } from "express";
8637
+ import { resolve as resolve15 } from "path";
8368
8638
  import { readFile as readFile11, unlink as unlink2 } from "fs/promises";
8369
8639
  function createPlaybooksRouter(playbooksDir2) {
8370
- const router = Router4();
8640
+ const router = Router5();
8371
8641
  router.get("/", async (_req, res) => {
8372
8642
  try {
8373
8643
  const playbooks = await listPlaybooks(playbooksDir2);
@@ -8434,7 +8704,7 @@ function createPlaybooksRouter(playbooksDir2) {
8434
8704
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
8435
8705
  return;
8436
8706
  }
8437
- const filePath = resolve14(playbooksDir2, resolved.filename);
8707
+ const filePath = resolve15(playbooksDir2, resolved.filename);
8438
8708
  const content = await readFile11(filePath, "utf-8");
8439
8709
  res.json({
8440
8710
  documentType: "playbook",
@@ -8460,7 +8730,7 @@ function createPlaybooksRouter(playbooksDir2) {
8460
8730
  return;
8461
8731
  }
8462
8732
  await ensureDir(playbooksDir2);
8463
- const filePath = resolve14(playbooksDir2, `${slug}.md`);
8733
+ const filePath = resolve15(playbooksDir2, `${slug}.md`);
8464
8734
  if (await fileExists(filePath)) {
8465
8735
  res.status(409).json({ error: `Playbook "${slug}" already exists` });
8466
8736
  return;
@@ -8484,7 +8754,7 @@ function createPlaybooksRouter(playbooksDir2) {
8484
8754
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
8485
8755
  return;
8486
8756
  }
8487
- const filePath = resolve14(playbooksDir2, resolved.filename);
8757
+ const filePath = resolve15(playbooksDir2, resolved.filename);
8488
8758
  await writeFileForce(filePath, content);
8489
8759
  await rebuildPlaybookManifest(playbooksDir2);
8490
8760
  res.json({ slug: resolved.slug, path: filePath });
@@ -8503,7 +8773,7 @@ function createPlaybooksRouter(playbooksDir2) {
8503
8773
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
8504
8774
  return;
8505
8775
  }
8506
- const filePath = resolve14(playbooksDir2, resolved.filename);
8776
+ const filePath = resolve15(playbooksDir2, resolved.filename);
8507
8777
  await unlink2(filePath);
8508
8778
  await removeFromDisabledList(resolved.slug);
8509
8779
  await rebuildPlaybookManifest(playbooksDir2);
@@ -8522,9 +8792,9 @@ init_fs_migration();
8522
8792
  init_parser2();
8523
8793
  init_fs();
8524
8794
  init_paths();
8525
- import { Router as Router5 } from "express";
8795
+ import { Router as Router6 } from "express";
8526
8796
  import { readdir as readdir8 } from "fs/promises";
8527
- import { resolve as resolvePath, dirname as dirname3 } from "path";
8797
+ import { resolve as resolvePath, dirname as dirname4 } from "path";
8528
8798
  import { rename as rename3, mkdir as mkdir2 } from "fs/promises";
8529
8799
 
8530
8800
  // src/dashboard/todos-locks.ts
@@ -8570,7 +8840,7 @@ function touchItem(item) {
8570
8840
  item.updatedAt = now;
8571
8841
  }
8572
8842
  function createTodosRouter(todosDir2, broadcast, projectsDir) {
8573
- const router = Router5();
8843
+ const router = Router6();
8574
8844
  function broadcastUpdate() {
8575
8845
  broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
8576
8846
  }
@@ -8700,7 +8970,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
8700
8970
  router.post("/:workspace/archive", async (req, res) => {
8701
8971
  try {
8702
8972
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
8703
- const { resolve: resolve20 } = await import("path");
8973
+ const { resolve: resolve21 } = await import("path");
8704
8974
  const { readFile: readFile15 } = await import("fs/promises");
8705
8975
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
8706
8976
  const workspace = getWorkspaceParam(req.params.workspace);
@@ -8717,7 +8987,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
8717
8987
  (e) => e.itemIds.every((id) => completedIds.has(id))
8718
8988
  );
8719
8989
  const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
8720
- await ensureDir(resolve20(todosDir2, "archive"));
8990
+ await ensureDir(resolve21(todosDir2, "archive"));
8721
8991
  let archContent = "";
8722
8992
  if (await fileExists(archFile)) {
8723
8993
  archContent = await readFile15(archFile, "utf-8");
@@ -9124,7 +9394,7 @@ workspace: ${workspace}
9124
9394
  if (await fileExists(newPlanDir)) {
9125
9395
  return { status: 409, error: "plan dir already exists in target" };
9126
9396
  }
9127
- await mkdir2(dirname3(newPlanDir), { recursive: true });
9397
+ await mkdir2(dirname4(newPlanDir), { recursive: true });
9128
9398
  await rename3(item.planDir, newPlanDir);
9129
9399
  item.planDir = newPlanDir;
9130
9400
  }
@@ -9200,9 +9470,9 @@ init_parser2();
9200
9470
  init_fs();
9201
9471
  init_paths();
9202
9472
  init_slug();
9203
- import { Router as Router6 } from "express";
9473
+ import { Router as Router7 } from "express";
9204
9474
  import { mkdir as mkdir3, readFile as readFile13, rename as rename4 } from "fs/promises";
9205
- import { resolve as resolve17, dirname as dirname4 } from "path";
9475
+ import { resolve as resolve18, dirname as dirname5 } from "path";
9206
9476
  var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
9207
9477
  function touchItem2(item) {
9208
9478
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -9217,7 +9487,7 @@ function params(req) {
9217
9487
  return req.params;
9218
9488
  }
9219
9489
  async function projectExists(projectsDir, slug) {
9220
- return fileExists(resolve17(projectsDir, slug, "project.md"));
9490
+ return fileExists(resolve18(projectsDir, slug, "project.md"));
9221
9491
  }
9222
9492
  async function ensureProjectTodosDir(projectsDir, slug) {
9223
9493
  const todosDir2 = projectTodosDir(projectsDir, slug);
@@ -9234,7 +9504,7 @@ async function ensureProjectTodosDir(projectsDir, slug) {
9234
9504
  throw err;
9235
9505
  }
9236
9506
  try {
9237
- await mkdir3(resolve17(todosDir2, "archive"), { recursive: false });
9507
+ await mkdir3(resolve18(todosDir2, "archive"), { recursive: false });
9238
9508
  } catch (err) {
9239
9509
  const code = err.code;
9240
9510
  if (code === "EEXIST") return;
@@ -9250,7 +9520,7 @@ function notFound(res, slug) {
9250
9520
  res.status(404).json({ error: `Project "${slug}" not found` });
9251
9521
  }
9252
9522
  function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
9253
- const router = Router6({ mergeParams: true });
9523
+ const router = Router7({ mergeParams: true });
9254
9524
  function broadcastUpdate(projectSlug) {
9255
9525
  broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
9256
9526
  }
@@ -9858,18 +10128,18 @@ workspace: ${slug}
9858
10128
  if (tg.includes("/")) {
9859
10129
  const parts = tg.split("/");
9860
10130
  if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
9861
- assignmentDir = resolve17(projectsDir, parts[0], "assignments", parts[1]);
10131
+ assignmentDir = resolve18(projectsDir, parts[0], "assignments", parts[1]);
9862
10132
  assignmentRef = `${parts[0]}/${parts[1]}`;
9863
10133
  } else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
9864
- assignmentDir = resolve17(assignmentsDirFn(), tg);
10134
+ assignmentDir = resolve18(assignmentsDirFn(), tg);
9865
10135
  assignmentRef = tg;
9866
10136
  } else {
9867
10137
  return { error: `Invalid target.assignment "${tg}"` };
9868
10138
  }
9869
- const assignmentMdPath2 = resolve17(assignmentDir, "assignment.md");
10139
+ const assignmentMdPath2 = resolve18(assignmentDir, "assignment.md");
9870
10140
  if (!await fileExists(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
9871
10141
  }
9872
- const assignmentMdPath = resolve17(assignmentDir, "assignment.md");
10142
+ const assignmentMdPath = resolve18(assignmentDir, "assignment.md");
9873
10143
  let content = await readFile13(assignmentMdPath, "utf-8");
9874
10144
  content = appendTodosToAssignmentBody2(
9875
10145
  content,
@@ -9999,7 +10269,7 @@ workspace: ${slug}
9999
10269
  if (await fileExists(newPlanDir)) {
10000
10270
  return { status: 409, error: "plan dir already exists in target" };
10001
10271
  }
10002
- await mkdir3(dirname4(newPlanDir), { recursive: true });
10272
+ await mkdir3(dirname5(newPlanDir), { recursive: true });
10003
10273
  await rename4(item.planDir, newPlanDir);
10004
10274
  item.planDir = newPlanDir;
10005
10275
  }
@@ -10062,7 +10332,7 @@ workspace: ${slug}
10062
10332
 
10063
10333
  // src/dashboard/api-backup.ts
10064
10334
  init_config2();
10065
- import { Router as Router7 } from "express";
10335
+ import { Router as Router8 } from "express";
10066
10336
 
10067
10337
  // src/utils/github-backup.ts
10068
10338
  init_paths();
@@ -10071,7 +10341,7 @@ init_config2();
10071
10341
  import { execFile as execFile2 } from "child_process";
10072
10342
  import { promisify as promisify2 } from "util";
10073
10343
  import { cp, mkdtemp, rm as rm2, readFile as readFile14, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename5 } from "fs/promises";
10074
- import { resolve as resolve18, join as join2 } from "path";
10344
+ import { resolve as resolve19, join as join2 } from "path";
10075
10345
  import { tmpdir } from "os";
10076
10346
  var exec2 = promisify2(execFile2);
10077
10347
  var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
@@ -10111,7 +10381,7 @@ async function resolveCategoryPath(category) {
10111
10381
  case "servers":
10112
10382
  return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
10113
10383
  case "config":
10114
- return { sourcePath: resolve18(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
10384
+ return { sourcePath: resolve19(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
10115
10385
  }
10116
10386
  }
10117
10387
  async function checkGitInstalled() {
@@ -10122,7 +10392,7 @@ async function checkGitInstalled() {
10122
10392
  }
10123
10393
  }
10124
10394
  async function acquireLock() {
10125
- const lockPath = resolve18(syntaurRoot(), LOCK_FILE_NAME);
10395
+ const lockPath = resolve19(syntaurRoot(), LOCK_FILE_NAME);
10126
10396
  await ensureDir(syntaurRoot());
10127
10397
  try {
10128
10398
  const handle = await open(lockPath, "wx");
@@ -10169,7 +10439,7 @@ async function copyRecursive(src, dest) {
10169
10439
  await ensureDir(dest);
10170
10440
  await cp(src, dest, { recursive: true, force: true });
10171
10441
  } else {
10172
- await ensureDir(resolve18(dest, ".."));
10442
+ await ensureDir(resolve19(dest, ".."));
10173
10443
  await cp(src, dest, { force: true });
10174
10444
  }
10175
10445
  }
@@ -10217,7 +10487,7 @@ async function backupToGithub(overrides) {
10217
10487
  }
10218
10488
  if (category === "config") {
10219
10489
  const sanitized = await readSanitizedConfig(sourcePath);
10220
- await ensureDir(resolve18(destPath, ".."));
10490
+ await ensureDir(resolve19(destPath, ".."));
10221
10491
  await writeFile4(destPath, sanitized, "utf-8");
10222
10492
  } else {
10223
10493
  await copyRecursive(sourcePath, destPath);
@@ -10271,7 +10541,7 @@ async function backupToGithub(overrides) {
10271
10541
  }
10272
10542
  async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
10273
10543
  if (isFile) {
10274
- await ensureDir(resolve18(localPath, ".."));
10544
+ await ensureDir(resolve19(localPath, ".."));
10275
10545
  await cp(repoSrcPath, localPath, { force: true });
10276
10546
  return;
10277
10547
  }
@@ -10372,7 +10642,7 @@ async function restoreFromGithub(overrides) {
10372
10642
  }
10373
10643
  async function getBackupStatus() {
10374
10644
  const config = await readConfig();
10375
- const lockPath = resolve18(syntaurRoot(), LOCK_FILE_NAME);
10645
+ const lockPath = resolve19(syntaurRoot(), LOCK_FILE_NAME);
10376
10646
  const locked = await fileExists(lockPath);
10377
10647
  return {
10378
10648
  repo: config.backup?.repo ?? null,
@@ -10385,7 +10655,7 @@ async function getBackupStatus() {
10385
10655
 
10386
10656
  // src/dashboard/api-backup.ts
10387
10657
  function createBackupRouter() {
10388
- const router = Router7();
10658
+ const router = Router8();
10389
10659
  router.get("/", async (_req, res) => {
10390
10660
  try {
10391
10661
  const status = await getBackupStatus();
@@ -10706,10 +10976,11 @@ function createDashboardServer(options) {
10706
10976
  migrateFromMarkdown(projectsDir).catch((err) => {
10707
10977
  console.error("Session migration from markdown failed:", err);
10708
10978
  });
10979
+ initLeasesDb();
10709
10980
  (async () => {
10710
10981
  try {
10711
10982
  const configResult = await migrateLegacyConfig(
10712
- resolve19(syntaurRoot(), "config.md")
10983
+ resolve20(syntaurRoot(), "config.md")
10713
10984
  );
10714
10985
  const projectResult = await migrateLegacyProjectFiles(projectsDir);
10715
10986
  const summary = summarizeMigration(projectResult, configResult);
@@ -11039,6 +11310,7 @@ function createDashboardServer(options) {
11039
11310
  });
11040
11311
  app.use(createWriteRouter(projectsDir, assignmentsDir2));
11041
11312
  app.use("/api/servers", createServersRouter(serversDir2, projectsDir, assignmentsDir2));
11313
+ app.use("/api/leases", createLeasesRouter(broadcast));
11042
11314
  app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2));
11043
11315
  app.use("/api/playbooks", createPlaybooksRouter(playbooksDir2));
11044
11316
  app.get("/api/memories", async (_req, res) => {
@@ -11064,14 +11336,14 @@ function createDashboardServer(options) {
11064
11336
  app.use("/api/backup", createBackupRouter());
11065
11337
  if (serveStaticUi && dashboardDistPath) {
11066
11338
  const sendOpts = { dotfiles: "allow" };
11067
- app.use("/assets", express.static(resolve19(dashboardDistPath, "assets"), sendOpts));
11339
+ app.use("/assets", express.static(resolve20(dashboardDistPath, "assets"), sendOpts));
11068
11340
  app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
11069
11341
  app.get("{*path}", async (req, res) => {
11070
11342
  if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
11071
11343
  res.status(404).json({ error: "Not Found" });
11072
11344
  return;
11073
11345
  }
11074
- const indexPath = resolve19(dashboardDistPath, "index.html");
11346
+ const indexPath = resolve20(dashboardDistPath, "index.html");
11075
11347
  if (!await fileExists(indexPath)) {
11076
11348
  res.status(503).send(
11077
11349
  'Dashboard not built. Run "npm run build:dashboard" first.'
@@ -11095,6 +11367,7 @@ function createDashboardServer(options) {
11095
11367
  serversDir: serversDir2,
11096
11368
  playbooksDir: playbooksDir2,
11097
11369
  todosDir: todosDir2,
11370
+ dbPath: resolve20(syntaurRoot(), "syntaur.db"),
11098
11371
  onMessage: broadcast
11099
11372
  });
11100
11373
  startAutodiscovery({ serversDir: serversDir2, projectsDir, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
@@ -11109,7 +11382,7 @@ function createDashboardServer(options) {
11109
11382
  }
11110
11383
  });
11111
11384
  server.listen(port, () => {
11112
- const portFile = resolve19(syntaurRoot(), "dashboard-port");
11385
+ const portFile = resolve20(syntaurRoot(), "dashboard-port");
11113
11386
  writeFile5(portFile, String(port), "utf-8").catch(() => {
11114
11387
  });
11115
11388
  resolvePromise();
@@ -11122,11 +11395,12 @@ function createDashboardServer(options) {
11122
11395
  await watcherHandle.close();
11123
11396
  }
11124
11397
  closeSessionDb();
11398
+ closeLeasesDb();
11125
11399
  for (const client of clients) {
11126
11400
  client.terminate();
11127
11401
  }
11128
11402
  clients.clear();
11129
- const portFile = resolve19(syntaurRoot(), "dashboard-port");
11403
+ const portFile = resolve20(syntaurRoot(), "dashboard-port");
11130
11404
  await unlink4(portFile).catch(() => {
11131
11405
  });
11132
11406
  server.closeAllConnections?.();