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.
- package/.claude-plugin/plugin.json +3 -1
- package/README.md +21 -0
- package/dashboard/dist/assets/{_basePickBy-DPIRyskD.js → _basePickBy-qEDLAY_J.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-CwicZZgc.js → _baseUniq-7tDs18Gn.js} +1 -1
- package/dashboard/dist/assets/{arc-CcHe1anq.js → arc-Brdw0dYW.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-4lwARCsm.js → architectureDiagram-2XIMDMQ5-BiEdLT9m.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-SdqET9RP.js → blockDiagram-WCTKOSBZ-HC-j_hPZ.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BAKj5SV2.js → c4Diagram-IC4MRINW-DA_rWbl7.js} +1 -1
- package/dashboard/dist/assets/channel-D2-caOn6.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-Dn95vjsS.js → chunk-4BX2VUAB-CF3hguyj.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-C6L5bDoe.js → chunk-55IACEB6-BTeCeIc_.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4---Go123J.js → chunk-FMBD7UC4-8qVvxmks.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-C9E63Ryg.js → chunk-JSJVCQXG-DCf8n7EO.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-Yyi0h479.js → chunk-KX2RTZJC-CcpjTmME.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-2Fh1XO45.js → chunk-NQ4KR5QH-BnFPKo4-.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-B4a3DKz-.js → chunk-QZHKN3VN-IPMVLLv2.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-XtMf_tFy.js → chunk-WL4C6EOR-D1pQmHqX.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-HFOb_XEt.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-HFOb_XEt.js +1 -0
- package/dashboard/dist/assets/clone-BrYOwgIq.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-8wHbmnAP.js → cose-bilkent-S5V4N54A-tvJjvBQr.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-uJZOLE_S.js → dagre-KLK3FWXG-qp1vt6LY.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-BZih6Ivf.js → diagram-E7M64L7V-VF5eizNr.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-CtR6WhET.js → diagram-IFDJBPK2-C3pCbJ1e.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-DTGhoDSE.js → diagram-P4PSJMXO-D-taXZwH.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-Yr93h1x4.js → erDiagram-INFDFZHY-QjY-NHVs.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CZ3QY4eZ.js → flowDiagram-PKNHOUZH-FjO8P5Tq.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CSf--huI.js → ganttDiagram-A5KZAMGK-Bsn7CyK3.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-CokKKyLR.js → gitGraphDiagram-K3NZZRJ6-BZ8DjhnF.js} +1 -1
- package/dashboard/dist/assets/{graph-DRs4cE7X.js → graph-AdliIYqX.js} +1 -1
- package/dashboard/dist/assets/index-BoR_oyUZ.css +1 -0
- package/dashboard/dist/assets/index-RCBI4aX2.js +525 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-C4CTdWLD.js → infoDiagram-LFFYTUFH-BfZjLA86.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DGrfYHHk.js → ishikawaDiagram-PHBUUO56-BSgSGMp5.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CEDbuCY2.js → journeyDiagram-4ABVD52K-BlQ7qeMC.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BjgAWDsE.js → kanban-definition-K7BYSVSG-CifO0AUu.js} +1 -1
- package/dashboard/dist/assets/{layout-Coi0pRnm.js → layout-B9r-gpD9.js} +1 -1
- package/dashboard/dist/assets/{linear-B4xKytgE.js → linear-CaaIRxVZ.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-dkpoLY2K.js → mermaid.core-C8wCWYw-.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-BgUfAXkB.js → mindmap-definition-YRQLILUH-BpX5T7iT.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-BBuMJroZ.js → pieDiagram-SKSYHLDU-BNGYtWrc.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-iXygRpen.js → quadrantDiagram-337W2JSQ-CR4GeM9D.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-DhJGFHd3.js → requirementDiagram-Z7DCOOCP-CXrPkMLl.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-CLkFLBq1.js → sankeyDiagram-WA2Y5GQK-CIzof7Dm.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-DQh28Zt-.js → sequenceDiagram-2WXFIKYE-BJZLt0O2.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-BZKsD9LJ.js → stateDiagram-RAJIS63D-x9gC6zy6.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-CN5Dcv7u.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-DCJlSWVw.js → timeline-definition-YZTLITO2-D_ZSQn1K.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-DEGy-zW9.js → treemap-KZPCXAKY-LPpMEco4.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-CfQAKq7b.js → vennDiagram-LZ73GAT5-CRFZQpr7.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-CgD-asFJ.js → xychartDiagram-JWTSCODW-Bivutynw.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +355 -81
- package/dist/dashboard/server.js.map +1 -1
- package/dist/db/leases-db.d.ts +178 -0
- package/dist/db/leases-db.js +557 -0
- package/dist/db/leases-db.js.map +1 -0
- package/dist/index.js +1219 -357
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/.claude-plugin/plugin.json +3 -1
- package/platforms/claude-code/skills/claim-resource/SKILL.md +89 -0
- package/platforms/claude-code/skills/release-resource/SKILL.md +65 -0
- package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +1 -0
- package/platforms/codex/skills/claim-resource/SKILL.md +89 -0
- package/platforms/codex/skills/release-resource/SKILL.md +65 -0
- package/platforms/codex/skills/syntaur-protocol/SKILL.md +1 -0
- package/skills/claim-resource/SKILL.md +89 -0
- package/skills/release-resource/SKILL.md +65 -0
- package/skills/syntaur-protocol/SKILL.md +1 -0
- package/dashboard/dist/assets/channel-DKdluNz1.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-93dPLc4W.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-93dPLc4W.js +0 -1
- package/dashboard/dist/assets/clone-vnWuXQgZ.js +0 -1
- package/dashboard/dist/assets/index-B9uCPhOL.js +0 -510
- package/dashboard/dist/assets/index-BKoccIZR.css +0 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DU5UnRV-.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -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
|
|
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
|
|
5499
|
+
return resolve16(todosDir2, `${workspace}.md`);
|
|
5500
5500
|
}
|
|
5501
5501
|
function logPath(todosDir2, workspace) {
|
|
5502
|
-
return
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
5723
|
-
const projectMdPath =
|
|
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 =
|
|
5731
|
+
const depDirBase = resolve17(projectDir, "assignments");
|
|
5732
5732
|
for (const dep of dependsOn) {
|
|
5733
|
-
const depDir =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
5770
|
+
resolve17(assignmentDir, "scratchpad.md"),
|
|
5771
5771
|
renderScratchpad({
|
|
5772
5772
|
assignmentSlug: companionAssignmentRef,
|
|
5773
5773
|
timestamp
|
|
5774
5774
|
})
|
|
5775
5775
|
],
|
|
5776
5776
|
[
|
|
5777
|
-
|
|
5777
|
+
resolve17(assignmentDir, "handoff.md"),
|
|
5778
5778
|
renderHandoff({
|
|
5779
5779
|
assignmentSlug: companionAssignmentRef,
|
|
5780
5780
|
timestamp
|
|
5781
5781
|
})
|
|
5782
5782
|
],
|
|
5783
5783
|
[
|
|
5784
|
-
|
|
5784
|
+
resolve17(assignmentDir, "decision-record.md"),
|
|
5785
5785
|
renderDecisionRecord({
|
|
5786
5786
|
assignmentSlug: companionAssignmentRef,
|
|
5787
5787
|
timestamp
|
|
5788
5788
|
})
|
|
5789
5789
|
],
|
|
5790
5790
|
[
|
|
5791
|
-
|
|
5791
|
+
resolve17(assignmentDir, "progress.md"),
|
|
5792
5792
|
renderProgress({
|
|
5793
5793
|
assignment: companionAssignmentRef,
|
|
5794
5794
|
timestamp
|
|
5795
5795
|
})
|
|
5796
5796
|
],
|
|
5797
5797
|
[
|
|
5798
|
-
|
|
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
|
|
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
|
|
6079
|
-
|
|
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
|
|
6104
|
+
const db3 = getSessionDb();
|
|
6105
6105
|
const isTerminal = status === "completed" || status === "stopped";
|
|
6106
|
-
const result = isTerminal ?
|
|
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) :
|
|
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
|
|
6115
|
-
const rows =
|
|
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
|
|
6119
|
+
const db3 = getSessionDb();
|
|
6120
6120
|
if (assignmentSlug) {
|
|
6121
|
-
const rows2 =
|
|
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 =
|
|
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
|
|
6131
|
+
const db3 = getSessionDb();
|
|
6132
6132
|
const placeholders = sessionIds.map(() => "?").join(", ");
|
|
6133
|
-
const result =
|
|
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
|
|
6150
|
-
const activeSessions =
|
|
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
|
|
6188
|
-
const rows = projectSlug === null ?
|
|
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) :
|
|
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
|
|
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",
|
|
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",
|
|
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:
|
|
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 "${
|
|
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:
|
|
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,
|
|
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
|
|
8367
|
-
import { resolve as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
8795
|
+
import { Router as Router6 } from "express";
|
|
8526
8796
|
import { readdir as readdir8 } from "fs/promises";
|
|
8527
|
-
import { resolve as resolvePath, dirname as
|
|
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 =
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
11403
|
+
const portFile = resolve20(syntaurRoot(), "dashboard-port");
|
|
11130
11404
|
await unlink4(portFile).catch(() => {
|
|
11131
11405
|
});
|
|
11132
11406
|
server.closeAllConnections?.();
|