syntaur 0.12.0 → 0.14.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 +5 -1
- package/README.md +23 -0
- package/dashboard/dist/assets/{_basePickBy-XOV2F7dX.js → _basePickBy-qEDLAY_J.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-Dccfk3fK.js → _baseUniq-7tDs18Gn.js} +1 -1
- package/dashboard/dist/assets/{arc-TwJ5RF-H.js → arc-Brdw0dYW.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CpkVJudw.js → architectureDiagram-2XIMDMQ5-BiEdLT9m.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-Ck6VQ4Fh.js → blockDiagram-WCTKOSBZ-HC-j_hPZ.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-B5V7YG03.js → c4Diagram-IC4MRINW-DA_rWbl7.js} +1 -1
- package/dashboard/dist/assets/channel-D2-caOn6.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-rBdyOxAe.js → chunk-4BX2VUAB-CF3hguyj.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-DKlNXjYt.js → chunk-55IACEB6-BTeCeIc_.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-Cqm0PtUI.js → chunk-FMBD7UC4-8qVvxmks.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-CQyXQcG3.js → chunk-JSJVCQXG-DCf8n7EO.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-DmqjHPpJ.js → chunk-KX2RTZJC-CcpjTmME.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-CHXoBcYa.js → chunk-NQ4KR5QH-BnFPKo4-.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-Dd4sChBS.js → chunk-QZHKN3VN-IPMVLLv2.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-D-QLnkY8.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-VZi6uV56.js → cose-bilkent-S5V4N54A-tvJjvBQr.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-CtT97Qhx.js → dagre-KLK3FWXG-qp1vt6LY.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-BjYTNNHb.js → diagram-E7M64L7V-VF5eizNr.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-qj8_r6fb.js → diagram-IFDJBPK2-C3pCbJ1e.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-DlfWo7Vt.js → diagram-P4PSJMXO-D-taXZwH.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-Ch_nApTv.js → erDiagram-INFDFZHY-QjY-NHVs.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-EOVsqNyi.js → flowDiagram-PKNHOUZH-FjO8P5Tq.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CS-lx0B-.js → ganttDiagram-A5KZAMGK-Bsn7CyK3.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Ba6RDzPy.js → gitGraphDiagram-K3NZZRJ6-BZ8DjhnF.js} +1 -1
- package/dashboard/dist/assets/{graph-BKaAyLk-.js → graph-AdliIYqX.js} +1 -1
- package/dashboard/dist/assets/{index-BTWPZaIA.css → index-BoR_oyUZ.css} +1 -1
- package/dashboard/dist/assets/index-RCBI4aX2.js +525 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-Cr20xEsd.js → infoDiagram-LFFYTUFH-BfZjLA86.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CuILV9ph.js → ishikawaDiagram-PHBUUO56-BSgSGMp5.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CO-l7Mpm.js → journeyDiagram-4ABVD52K-BlQ7qeMC.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-CcirPw_S.js → kanban-definition-K7BYSVSG-CifO0AUu.js} +1 -1
- package/dashboard/dist/assets/{layout-BXvr8dMw.js → layout-B9r-gpD9.js} +1 -1
- package/dashboard/dist/assets/{linear-B85hgYnX.js → linear-CaaIRxVZ.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-DcZN9Xp2.js → mermaid.core-C8wCWYw-.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-Bxi6p5_3.js → mindmap-definition-YRQLILUH-BpX5T7iT.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-DOSvU0UD.js → pieDiagram-SKSYHLDU-BNGYtWrc.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-Cmo-5tW9.js → quadrantDiagram-337W2JSQ-CR4GeM9D.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Dw8alwuI.js → requirementDiagram-Z7DCOOCP-CXrPkMLl.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-ZPB-rzOg.js → sankeyDiagram-WA2Y5GQK-CIzof7Dm.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-Ct8bkK2h.js → sequenceDiagram-2WXFIKYE-BJZLt0O2.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DnGOXlmx.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-AUFmbJjr.js → timeline-definition-YZTLITO2-D_ZSQn1K.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-Dw5FDFAV.js → treemap-KZPCXAKY-LPpMEco4.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-DfxicU4N.js → vennDiagram-LZ73GAT5-CRFZQpr7.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW--9N0V1r_.js → xychartDiagram-JWTSCODW-Bivutynw.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +419 -84
- package/dist/dashboard/server.js.map +1 -1
- package/dist/db/leases-db.d.ts +227 -0
- package/dist/db/leases-db.js +679 -0
- package/dist/db/leases-db.js.map +1 -0
- package/dist/index.js +1825 -445
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/.claude-plugin/plugin.json +5 -1
- package/platforms/claude-code/skills/claim-resource/SKILL.md +89 -0
- package/platforms/claude-code/skills/extend-resource/SKILL.md +83 -0
- package/platforms/claude-code/skills/list-resources/SKILL.md +74 -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/extend-resource/SKILL.md +83 -0
- package/platforms/codex/skills/list-resources/SKILL.md +74 -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/extend-resource/SKILL.md +83 -0
- package/skills/list-resources/SKILL.md +74 -0
- package/skills/release-resource/SKILL.md +65 -0
- package/skills/syntaur-protocol/SKILL.md +1 -0
- package/dashboard/dist/assets/channel-AEMTX70R.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-LzuOAGzu.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-LzuOAGzu.js +0 -1
- package/dashboard/dist/assets/clone-Cw63QGUJ.js +0 -1
- package/dashboard/dist/assets/index-Dk9PBm5B.js +0 -510
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-COGzlZxn.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);
|
|
@@ -8250,6 +8283,65 @@ function createServersRouter(serversDir2, projectsDir, assignmentsDir2) {
|
|
|
8250
8283
|
import { Router as Router3 } from "express";
|
|
8251
8284
|
import { resolve as resolve13 } from "path";
|
|
8252
8285
|
init_fs();
|
|
8286
|
+
|
|
8287
|
+
// src/utils/transcript.ts
|
|
8288
|
+
import { open } from "fs/promises";
|
|
8289
|
+
var MAX_LINES_SCANNED = 50;
|
|
8290
|
+
async function derivePathFromTranscript(transcriptPath) {
|
|
8291
|
+
if (!transcriptPath) return null;
|
|
8292
|
+
let handle;
|
|
8293
|
+
try {
|
|
8294
|
+
handle = await open(transcriptPath, "r");
|
|
8295
|
+
} catch {
|
|
8296
|
+
return null;
|
|
8297
|
+
}
|
|
8298
|
+
try {
|
|
8299
|
+
const stream = handle.createReadStream({ encoding: "utf-8" });
|
|
8300
|
+
let buffer = "";
|
|
8301
|
+
let scanned = 0;
|
|
8302
|
+
for await (const chunk of stream) {
|
|
8303
|
+
buffer += chunk;
|
|
8304
|
+
let nl = buffer.indexOf("\n");
|
|
8305
|
+
while (nl !== -1) {
|
|
8306
|
+
const line = buffer.slice(0, nl);
|
|
8307
|
+
buffer = buffer.slice(nl + 1);
|
|
8308
|
+
const cwd = extractCwd(line);
|
|
8309
|
+
if (cwd) {
|
|
8310
|
+
stream.destroy();
|
|
8311
|
+
return cwd;
|
|
8312
|
+
}
|
|
8313
|
+
scanned++;
|
|
8314
|
+
if (scanned >= MAX_LINES_SCANNED) {
|
|
8315
|
+
stream.destroy();
|
|
8316
|
+
return null;
|
|
8317
|
+
}
|
|
8318
|
+
nl = buffer.indexOf("\n");
|
|
8319
|
+
}
|
|
8320
|
+
}
|
|
8321
|
+
if (buffer.length > 0) {
|
|
8322
|
+
const cwd = extractCwd(buffer);
|
|
8323
|
+
if (cwd) return cwd;
|
|
8324
|
+
}
|
|
8325
|
+
return null;
|
|
8326
|
+
} finally {
|
|
8327
|
+
await handle.close().catch(() => {
|
|
8328
|
+
});
|
|
8329
|
+
}
|
|
8330
|
+
}
|
|
8331
|
+
function extractCwd(line) {
|
|
8332
|
+
const trimmed = line.trim();
|
|
8333
|
+
if (trimmed.length === 0 || trimmed[0] !== "{") return null;
|
|
8334
|
+
try {
|
|
8335
|
+
const parsed = JSON.parse(trimmed);
|
|
8336
|
+
if (typeof parsed.cwd === "string" && parsed.cwd.length > 0) {
|
|
8337
|
+
return parsed.cwd;
|
|
8338
|
+
}
|
|
8339
|
+
} catch {
|
|
8340
|
+
}
|
|
8341
|
+
return null;
|
|
8342
|
+
}
|
|
8343
|
+
|
|
8344
|
+
// src/dashboard/api-agent-sessions.ts
|
|
8253
8345
|
function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
|
|
8254
8346
|
const router = Router3();
|
|
8255
8347
|
router.get("/", async (_req, res) => {
|
|
@@ -8297,6 +8389,8 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
|
|
|
8297
8389
|
return;
|
|
8298
8390
|
}
|
|
8299
8391
|
}
|
|
8392
|
+
const derivedPath = await derivePathFromTranscript(transcriptPath);
|
|
8393
|
+
const recordedPath = derivedPath ?? path ?? "";
|
|
8300
8394
|
const session = {
|
|
8301
8395
|
projectSlug: projectSlug || null,
|
|
8302
8396
|
assignmentSlug: assignmentSlug || null,
|
|
@@ -8304,7 +8398,7 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
|
|
|
8304
8398
|
sessionId,
|
|
8305
8399
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8306
8400
|
status: "active",
|
|
8307
|
-
path:
|
|
8401
|
+
path: recordedPath,
|
|
8308
8402
|
description: description || null,
|
|
8309
8403
|
transcriptPath: transcriptPath || null
|
|
8310
8404
|
};
|
|
@@ -8355,6 +8449,243 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
|
|
|
8355
8449
|
return router;
|
|
8356
8450
|
}
|
|
8357
8451
|
|
|
8452
|
+
// src/dashboard/api-leases.ts
|
|
8453
|
+
import { Router as Router4 } from "express";
|
|
8454
|
+
|
|
8455
|
+
// src/db/leases-db.ts
|
|
8456
|
+
init_paths();
|
|
8457
|
+
import Database2 from "better-sqlite3";
|
|
8458
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
8459
|
+
import { resolve as resolve14 } from "path";
|
|
8460
|
+
var db2 = null;
|
|
8461
|
+
var LEASE_SCHEMA_VERSION = "1";
|
|
8462
|
+
var SCHEMA_SQL2 = `
|
|
8463
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
8464
|
+
key TEXT PRIMARY KEY,
|
|
8465
|
+
value TEXT
|
|
8466
|
+
);
|
|
8467
|
+
|
|
8468
|
+
CREATE TABLE IF NOT EXISTS inventories (
|
|
8469
|
+
slug TEXT PRIMARY KEY,
|
|
8470
|
+
kind TEXT NOT NULL,
|
|
8471
|
+
display_name TEXT,
|
|
8472
|
+
default_ttl_s INTEGER NOT NULL CHECK (default_ttl_s > 0),
|
|
8473
|
+
created_at TEXT NOT NULL
|
|
8474
|
+
);
|
|
8475
|
+
|
|
8476
|
+
CREATE TABLE IF NOT EXISTS inventory_members (
|
|
8477
|
+
inventory_slug TEXT NOT NULL,
|
|
8478
|
+
member_id TEXT NOT NULL,
|
|
8479
|
+
status TEXT NOT NULL DEFAULT 'idle'
|
|
8480
|
+
CHECK (status IN ('idle','leased','retired')),
|
|
8481
|
+
generation INTEGER NOT NULL DEFAULT 0,
|
|
8482
|
+
metadata_json TEXT,
|
|
8483
|
+
last_used_at TEXT,
|
|
8484
|
+
retired_at TEXT,
|
|
8485
|
+
PRIMARY KEY (inventory_slug, member_id),
|
|
8486
|
+
FOREIGN KEY (inventory_slug) REFERENCES inventories(slug)
|
|
8487
|
+
);
|
|
8488
|
+
|
|
8489
|
+
CREATE INDEX IF NOT EXISTS idx_members_idle
|
|
8490
|
+
ON inventory_members (inventory_slug, status, last_used_at);
|
|
8491
|
+
|
|
8492
|
+
CREATE TABLE IF NOT EXISTS leases (
|
|
8493
|
+
lease_id TEXT PRIMARY KEY,
|
|
8494
|
+
inventory_slug TEXT NOT NULL,
|
|
8495
|
+
member_id TEXT NOT NULL,
|
|
8496
|
+
member_gen INTEGER NOT NULL,
|
|
8497
|
+
state TEXT NOT NULL
|
|
8498
|
+
CHECK (state IN ('active','released','expired','revoked')),
|
|
8499
|
+
granted_at TEXT NOT NULL,
|
|
8500
|
+
expires_at TEXT NOT NULL,
|
|
8501
|
+
released_at TEXT,
|
|
8502
|
+
requested_for TEXT,
|
|
8503
|
+
FOREIGN KEY (inventory_slug, member_id)
|
|
8504
|
+
REFERENCES inventory_members(inventory_slug, member_id)
|
|
8505
|
+
);
|
|
8506
|
+
|
|
8507
|
+
CREATE INDEX IF NOT EXISTS idx_leases_gc
|
|
8508
|
+
ON leases (state, expires_at);
|
|
8509
|
+
|
|
8510
|
+
CREATE TABLE IF NOT EXISTS lease_events (
|
|
8511
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8512
|
+
lease_id TEXT NOT NULL,
|
|
8513
|
+
event TEXT NOT NULL,
|
|
8514
|
+
at TEXT NOT NULL,
|
|
8515
|
+
detail_json TEXT,
|
|
8516
|
+
FOREIGN KEY (lease_id) REFERENCES leases(lease_id)
|
|
8517
|
+
);
|
|
8518
|
+
|
|
8519
|
+
CREATE INDEX IF NOT EXISTS idx_events_lease
|
|
8520
|
+
ON lease_events (lease_id, at);
|
|
8521
|
+
`;
|
|
8522
|
+
var NotFoundError = class extends Error {
|
|
8523
|
+
constructor(id) {
|
|
8524
|
+
super(`lease ${id} not found`);
|
|
8525
|
+
this.id = id;
|
|
8526
|
+
this.name = "NotFoundError";
|
|
8527
|
+
}
|
|
8528
|
+
};
|
|
8529
|
+
var LeaseContentionError = class extends Error {
|
|
8530
|
+
constructor(inventorySlug) {
|
|
8531
|
+
super(`contention timeout on '${inventorySlug}'; retry`);
|
|
8532
|
+
this.inventorySlug = inventorySlug;
|
|
8533
|
+
this.name = "LeaseContentionError";
|
|
8534
|
+
}
|
|
8535
|
+
};
|
|
8536
|
+
function nowIso() {
|
|
8537
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
8538
|
+
}
|
|
8539
|
+
function isBusyError(err) {
|
|
8540
|
+
if (!err || typeof err !== "object") return false;
|
|
8541
|
+
const e = err;
|
|
8542
|
+
return e.code === "SQLITE_BUSY" || e.code === "SQLITE_BUSY_SNAPSHOT";
|
|
8543
|
+
}
|
|
8544
|
+
function initLeasesDb(dbPath) {
|
|
8545
|
+
if (db2) return db2;
|
|
8546
|
+
const finalPath = dbPath ?? resolve14(syntaurRoot(), "syntaur.db");
|
|
8547
|
+
db2 = new Database2(finalPath);
|
|
8548
|
+
db2.pragma("journal_mode = WAL");
|
|
8549
|
+
db2.pragma("busy_timeout = 5000");
|
|
8550
|
+
db2.pragma("foreign_keys = ON");
|
|
8551
|
+
const database = db2;
|
|
8552
|
+
const runMigrations = database.transaction(() => {
|
|
8553
|
+
database.exec(SCHEMA_SQL2);
|
|
8554
|
+
database.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run("lease_schema_version", LEASE_SCHEMA_VERSION);
|
|
8555
|
+
});
|
|
8556
|
+
runMigrations.exclusive();
|
|
8557
|
+
return db2;
|
|
8558
|
+
}
|
|
8559
|
+
function getLeasesDb() {
|
|
8560
|
+
if (!db2) {
|
|
8561
|
+
throw new Error("Leases database not initialized. Call initLeasesDb() first.");
|
|
8562
|
+
}
|
|
8563
|
+
return db2;
|
|
8564
|
+
}
|
|
8565
|
+
function closeLeasesDb() {
|
|
8566
|
+
if (db2) {
|
|
8567
|
+
db2.close();
|
|
8568
|
+
db2 = null;
|
|
8569
|
+
}
|
|
8570
|
+
}
|
|
8571
|
+
function listInventories() {
|
|
8572
|
+
const database = getLeasesDb();
|
|
8573
|
+
return database.prepare(
|
|
8574
|
+
`SELECT slug, kind, display_name, default_ttl_s, created_at
|
|
8575
|
+
FROM inventories ORDER BY slug`
|
|
8576
|
+
).all();
|
|
8577
|
+
}
|
|
8578
|
+
function getInventoryDetail(slug) {
|
|
8579
|
+
const database = getLeasesDb();
|
|
8580
|
+
const inv = database.prepare(
|
|
8581
|
+
`SELECT slug, kind, display_name, default_ttl_s, created_at
|
|
8582
|
+
FROM inventories WHERE slug = ?`
|
|
8583
|
+
).get(slug);
|
|
8584
|
+
if (!inv) return null;
|
|
8585
|
+
const members = database.prepare(
|
|
8586
|
+
`SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at
|
|
8587
|
+
FROM inventory_members WHERE inventory_slug = ?
|
|
8588
|
+
ORDER BY member_id`
|
|
8589
|
+
).all(slug);
|
|
8590
|
+
const active_leases = database.prepare(
|
|
8591
|
+
`SELECT lease_id, inventory_slug, member_id, member_gen, state, granted_at, expires_at, released_at, requested_for
|
|
8592
|
+
FROM leases WHERE inventory_slug = ? AND state = 'active'
|
|
8593
|
+
ORDER BY granted_at`
|
|
8594
|
+
).all(slug);
|
|
8595
|
+
return { inventory: inv, members, active_leases };
|
|
8596
|
+
}
|
|
8597
|
+
function forceReleaseLease(lease_id) {
|
|
8598
|
+
const database = getLeasesDb();
|
|
8599
|
+
try {
|
|
8600
|
+
const fn = database.transaction(() => {
|
|
8601
|
+
const now = nowIso();
|
|
8602
|
+
const lookup = database.prepare(
|
|
8603
|
+
`SELECT inventory_slug, member_id, member_gen, state
|
|
8604
|
+
FROM leases WHERE lease_id = ?`
|
|
8605
|
+
).get(lease_id);
|
|
8606
|
+
if (!lookup) throw new NotFoundError(lease_id);
|
|
8607
|
+
if (lookup.state === "revoked") return { member_freed: false };
|
|
8608
|
+
const memberRes = database.prepare(
|
|
8609
|
+
`UPDATE inventory_members
|
|
8610
|
+
SET status = 'idle', generation = generation + 1
|
|
8611
|
+
WHERE inventory_slug = ? AND member_id = ?
|
|
8612
|
+
AND generation = ?`
|
|
8613
|
+
).run(lookup.inventory_slug, lookup.member_id, lookup.member_gen);
|
|
8614
|
+
database.prepare(
|
|
8615
|
+
`UPDATE leases
|
|
8616
|
+
SET state = 'revoked', released_at = ?
|
|
8617
|
+
WHERE lease_id = ? AND state != 'revoked'`
|
|
8618
|
+
).run(now, lease_id);
|
|
8619
|
+
const member_freed = memberRes.changes > 0;
|
|
8620
|
+
database.prepare(
|
|
8621
|
+
`INSERT INTO lease_events (lease_id, event, at, detail_json)
|
|
8622
|
+
VALUES (?, 'force_released', ?, ?)`
|
|
8623
|
+
).run(lease_id, now, JSON.stringify({ member_freed }));
|
|
8624
|
+
return { member_freed };
|
|
8625
|
+
});
|
|
8626
|
+
return fn.immediate();
|
|
8627
|
+
} catch (err) {
|
|
8628
|
+
if (isBusyError(err)) throw new LeaseContentionError(lease_id);
|
|
8629
|
+
throw err;
|
|
8630
|
+
}
|
|
8631
|
+
}
|
|
8632
|
+
|
|
8633
|
+
// src/dashboard/api-leases.ts
|
|
8634
|
+
function createLeasesRouter(broadcast) {
|
|
8635
|
+
const router = Router4();
|
|
8636
|
+
router.get("/", (_req, res) => {
|
|
8637
|
+
try {
|
|
8638
|
+
initLeasesDb();
|
|
8639
|
+
const inventories = listInventories();
|
|
8640
|
+
const details = inventories.map((inv) => getInventoryDetail(inv.slug)).filter((d) => d !== null);
|
|
8641
|
+
res.json({ inventories: details });
|
|
8642
|
+
} catch (error) {
|
|
8643
|
+
res.status(500).json({
|
|
8644
|
+
error: error instanceof Error ? error.message : "List failed"
|
|
8645
|
+
});
|
|
8646
|
+
}
|
|
8647
|
+
});
|
|
8648
|
+
router.get("/:slug", (req, res) => {
|
|
8649
|
+
try {
|
|
8650
|
+
initLeasesDb();
|
|
8651
|
+
const detail = getInventoryDetail(req.params.slug);
|
|
8652
|
+
if (!detail) {
|
|
8653
|
+
res.status(404).json({ error: "Inventory not found" });
|
|
8654
|
+
return;
|
|
8655
|
+
}
|
|
8656
|
+
res.json(detail);
|
|
8657
|
+
} catch (error) {
|
|
8658
|
+
res.status(500).json({
|
|
8659
|
+
error: error instanceof Error ? error.message : "Read failed"
|
|
8660
|
+
});
|
|
8661
|
+
}
|
|
8662
|
+
});
|
|
8663
|
+
router.post("/:slug/force-release/:lease_id", (req, res) => {
|
|
8664
|
+
try {
|
|
8665
|
+
initLeasesDb();
|
|
8666
|
+
const result = forceReleaseLease(req.params.lease_id);
|
|
8667
|
+
broadcast?.({
|
|
8668
|
+
type: "leases-updated",
|
|
8669
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8670
|
+
});
|
|
8671
|
+
res.json({ ...result, lease_id: req.params.lease_id });
|
|
8672
|
+
} catch (error) {
|
|
8673
|
+
if (error instanceof NotFoundError) {
|
|
8674
|
+
res.status(404).json({ error: "Lease not found" });
|
|
8675
|
+
return;
|
|
8676
|
+
}
|
|
8677
|
+
if (error instanceof LeaseContentionError) {
|
|
8678
|
+
res.status(503).json({ error: "Contention timeout; retry" });
|
|
8679
|
+
return;
|
|
8680
|
+
}
|
|
8681
|
+
res.status(500).json({
|
|
8682
|
+
error: error instanceof Error ? error.message : "Force release failed"
|
|
8683
|
+
});
|
|
8684
|
+
}
|
|
8685
|
+
});
|
|
8686
|
+
return router;
|
|
8687
|
+
}
|
|
8688
|
+
|
|
8358
8689
|
// src/dashboard/api-playbooks.ts
|
|
8359
8690
|
init_api();
|
|
8360
8691
|
init_parser();
|
|
@@ -8363,11 +8694,11 @@ init_timestamp();
|
|
|
8363
8694
|
init_fs();
|
|
8364
8695
|
init_playbook();
|
|
8365
8696
|
init_playbooks();
|
|
8366
|
-
import { Router as
|
|
8367
|
-
import { resolve as
|
|
8697
|
+
import { Router as Router5 } from "express";
|
|
8698
|
+
import { resolve as resolve15 } from "path";
|
|
8368
8699
|
import { readFile as readFile11, unlink as unlink2 } from "fs/promises";
|
|
8369
8700
|
function createPlaybooksRouter(playbooksDir2) {
|
|
8370
|
-
const router =
|
|
8701
|
+
const router = Router5();
|
|
8371
8702
|
router.get("/", async (_req, res) => {
|
|
8372
8703
|
try {
|
|
8373
8704
|
const playbooks = await listPlaybooks(playbooksDir2);
|
|
@@ -8434,7 +8765,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
8434
8765
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
8435
8766
|
return;
|
|
8436
8767
|
}
|
|
8437
|
-
const filePath =
|
|
8768
|
+
const filePath = resolve15(playbooksDir2, resolved.filename);
|
|
8438
8769
|
const content = await readFile11(filePath, "utf-8");
|
|
8439
8770
|
res.json({
|
|
8440
8771
|
documentType: "playbook",
|
|
@@ -8460,7 +8791,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
8460
8791
|
return;
|
|
8461
8792
|
}
|
|
8462
8793
|
await ensureDir(playbooksDir2);
|
|
8463
|
-
const filePath =
|
|
8794
|
+
const filePath = resolve15(playbooksDir2, `${slug}.md`);
|
|
8464
8795
|
if (await fileExists(filePath)) {
|
|
8465
8796
|
res.status(409).json({ error: `Playbook "${slug}" already exists` });
|
|
8466
8797
|
return;
|
|
@@ -8484,7 +8815,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
8484
8815
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
8485
8816
|
return;
|
|
8486
8817
|
}
|
|
8487
|
-
const filePath =
|
|
8818
|
+
const filePath = resolve15(playbooksDir2, resolved.filename);
|
|
8488
8819
|
await writeFileForce(filePath, content);
|
|
8489
8820
|
await rebuildPlaybookManifest(playbooksDir2);
|
|
8490
8821
|
res.json({ slug: resolved.slug, path: filePath });
|
|
@@ -8503,7 +8834,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
8503
8834
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
8504
8835
|
return;
|
|
8505
8836
|
}
|
|
8506
|
-
const filePath =
|
|
8837
|
+
const filePath = resolve15(playbooksDir2, resolved.filename);
|
|
8507
8838
|
await unlink2(filePath);
|
|
8508
8839
|
await removeFromDisabledList(resolved.slug);
|
|
8509
8840
|
await rebuildPlaybookManifest(playbooksDir2);
|
|
@@ -8522,9 +8853,9 @@ init_fs_migration();
|
|
|
8522
8853
|
init_parser2();
|
|
8523
8854
|
init_fs();
|
|
8524
8855
|
init_paths();
|
|
8525
|
-
import { Router as
|
|
8856
|
+
import { Router as Router6 } from "express";
|
|
8526
8857
|
import { readdir as readdir8 } from "fs/promises";
|
|
8527
|
-
import { resolve as resolvePath, dirname as
|
|
8858
|
+
import { resolve as resolvePath, dirname as dirname4 } from "path";
|
|
8528
8859
|
import { rename as rename3, mkdir as mkdir2 } from "fs/promises";
|
|
8529
8860
|
|
|
8530
8861
|
// src/dashboard/todos-locks.ts
|
|
@@ -8570,7 +8901,7 @@ function touchItem(item) {
|
|
|
8570
8901
|
item.updatedAt = now;
|
|
8571
8902
|
}
|
|
8572
8903
|
function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
8573
|
-
const router =
|
|
8904
|
+
const router = Router6();
|
|
8574
8905
|
function broadcastUpdate() {
|
|
8575
8906
|
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8576
8907
|
}
|
|
@@ -8700,7 +9031,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
8700
9031
|
router.post("/:workspace/archive", async (req, res) => {
|
|
8701
9032
|
try {
|
|
8702
9033
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
8703
|
-
const { resolve:
|
|
9034
|
+
const { resolve: resolve21 } = await import("path");
|
|
8704
9035
|
const { readFile: readFile15 } = await import("fs/promises");
|
|
8705
9036
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
8706
9037
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
@@ -8717,7 +9048,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
8717
9048
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
8718
9049
|
);
|
|
8719
9050
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
8720
|
-
await ensureDir(
|
|
9051
|
+
await ensureDir(resolve21(todosDir2, "archive"));
|
|
8721
9052
|
let archContent = "";
|
|
8722
9053
|
if (await fileExists(archFile)) {
|
|
8723
9054
|
archContent = await readFile15(archFile, "utf-8");
|
|
@@ -9124,7 +9455,7 @@ workspace: ${workspace}
|
|
|
9124
9455
|
if (await fileExists(newPlanDir)) {
|
|
9125
9456
|
return { status: 409, error: "plan dir already exists in target" };
|
|
9126
9457
|
}
|
|
9127
|
-
await mkdir2(
|
|
9458
|
+
await mkdir2(dirname4(newPlanDir), { recursive: true });
|
|
9128
9459
|
await rename3(item.planDir, newPlanDir);
|
|
9129
9460
|
item.planDir = newPlanDir;
|
|
9130
9461
|
}
|
|
@@ -9200,9 +9531,9 @@ init_parser2();
|
|
|
9200
9531
|
init_fs();
|
|
9201
9532
|
init_paths();
|
|
9202
9533
|
init_slug();
|
|
9203
|
-
import { Router as
|
|
9534
|
+
import { Router as Router7 } from "express";
|
|
9204
9535
|
import { mkdir as mkdir3, readFile as readFile13, rename as rename4 } from "fs/promises";
|
|
9205
|
-
import { resolve as
|
|
9536
|
+
import { resolve as resolve18, dirname as dirname5 } from "path";
|
|
9206
9537
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
9207
9538
|
function touchItem2(item) {
|
|
9208
9539
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9217,7 +9548,7 @@ function params(req) {
|
|
|
9217
9548
|
return req.params;
|
|
9218
9549
|
}
|
|
9219
9550
|
async function projectExists(projectsDir, slug) {
|
|
9220
|
-
return fileExists(
|
|
9551
|
+
return fileExists(resolve18(projectsDir, slug, "project.md"));
|
|
9221
9552
|
}
|
|
9222
9553
|
async function ensureProjectTodosDir(projectsDir, slug) {
|
|
9223
9554
|
const todosDir2 = projectTodosDir(projectsDir, slug);
|
|
@@ -9234,7 +9565,7 @@ async function ensureProjectTodosDir(projectsDir, slug) {
|
|
|
9234
9565
|
throw err;
|
|
9235
9566
|
}
|
|
9236
9567
|
try {
|
|
9237
|
-
await mkdir3(
|
|
9568
|
+
await mkdir3(resolve18(todosDir2, "archive"), { recursive: false });
|
|
9238
9569
|
} catch (err) {
|
|
9239
9570
|
const code = err.code;
|
|
9240
9571
|
if (code === "EEXIST") return;
|
|
@@ -9250,7 +9581,7 @@ function notFound(res, slug) {
|
|
|
9250
9581
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
9251
9582
|
}
|
|
9252
9583
|
function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
|
|
9253
|
-
const router =
|
|
9584
|
+
const router = Router7({ mergeParams: true });
|
|
9254
9585
|
function broadcastUpdate(projectSlug) {
|
|
9255
9586
|
broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9256
9587
|
}
|
|
@@ -9858,18 +10189,18 @@ workspace: ${slug}
|
|
|
9858
10189
|
if (tg.includes("/")) {
|
|
9859
10190
|
const parts = tg.split("/");
|
|
9860
10191
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
9861
|
-
assignmentDir =
|
|
10192
|
+
assignmentDir = resolve18(projectsDir, parts[0], "assignments", parts[1]);
|
|
9862
10193
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
9863
10194
|
} 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 =
|
|
10195
|
+
assignmentDir = resolve18(assignmentsDirFn(), tg);
|
|
9865
10196
|
assignmentRef = tg;
|
|
9866
10197
|
} else {
|
|
9867
10198
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
9868
10199
|
}
|
|
9869
|
-
const assignmentMdPath2 =
|
|
10200
|
+
const assignmentMdPath2 = resolve18(assignmentDir, "assignment.md");
|
|
9870
10201
|
if (!await fileExists(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
|
|
9871
10202
|
}
|
|
9872
|
-
const assignmentMdPath =
|
|
10203
|
+
const assignmentMdPath = resolve18(assignmentDir, "assignment.md");
|
|
9873
10204
|
let content = await readFile13(assignmentMdPath, "utf-8");
|
|
9874
10205
|
content = appendTodosToAssignmentBody2(
|
|
9875
10206
|
content,
|
|
@@ -9999,7 +10330,7 @@ workspace: ${slug}
|
|
|
9999
10330
|
if (await fileExists(newPlanDir)) {
|
|
10000
10331
|
return { status: 409, error: "plan dir already exists in target" };
|
|
10001
10332
|
}
|
|
10002
|
-
await mkdir3(
|
|
10333
|
+
await mkdir3(dirname5(newPlanDir), { recursive: true });
|
|
10003
10334
|
await rename4(item.planDir, newPlanDir);
|
|
10004
10335
|
item.planDir = newPlanDir;
|
|
10005
10336
|
}
|
|
@@ -10062,7 +10393,7 @@ workspace: ${slug}
|
|
|
10062
10393
|
|
|
10063
10394
|
// src/dashboard/api-backup.ts
|
|
10064
10395
|
init_config2();
|
|
10065
|
-
import { Router as
|
|
10396
|
+
import { Router as Router8 } from "express";
|
|
10066
10397
|
|
|
10067
10398
|
// src/utils/github-backup.ts
|
|
10068
10399
|
init_paths();
|
|
@@ -10070,8 +10401,8 @@ init_fs();
|
|
|
10070
10401
|
init_config2();
|
|
10071
10402
|
import { execFile as execFile2 } from "child_process";
|
|
10072
10403
|
import { promisify as promisify2 } from "util";
|
|
10073
|
-
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
|
|
10404
|
+
import { cp, mkdtemp, rm as rm2, readFile as readFile14, writeFile as writeFile4, unlink as unlink3, stat, open as open2, rename as rename5 } from "fs/promises";
|
|
10405
|
+
import { resolve as resolve19, join as join2 } from "path";
|
|
10075
10406
|
import { tmpdir } from "os";
|
|
10076
10407
|
var exec2 = promisify2(execFile2);
|
|
10077
10408
|
var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
|
|
@@ -10111,7 +10442,7 @@ async function resolveCategoryPath(category) {
|
|
|
10111
10442
|
case "servers":
|
|
10112
10443
|
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
10113
10444
|
case "config":
|
|
10114
|
-
return { sourcePath:
|
|
10445
|
+
return { sourcePath: resolve19(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
10115
10446
|
}
|
|
10116
10447
|
}
|
|
10117
10448
|
async function checkGitInstalled() {
|
|
@@ -10122,10 +10453,10 @@ async function checkGitInstalled() {
|
|
|
10122
10453
|
}
|
|
10123
10454
|
}
|
|
10124
10455
|
async function acquireLock() {
|
|
10125
|
-
const lockPath =
|
|
10456
|
+
const lockPath = resolve19(syntaurRoot(), LOCK_FILE_NAME);
|
|
10126
10457
|
await ensureDir(syntaurRoot());
|
|
10127
10458
|
try {
|
|
10128
|
-
const handle = await
|
|
10459
|
+
const handle = await open2(lockPath, "wx");
|
|
10129
10460
|
await handle.write(String(process.pid));
|
|
10130
10461
|
await handle.close();
|
|
10131
10462
|
return lockPath;
|
|
@@ -10169,7 +10500,7 @@ async function copyRecursive(src, dest) {
|
|
|
10169
10500
|
await ensureDir(dest);
|
|
10170
10501
|
await cp(src, dest, { recursive: true, force: true });
|
|
10171
10502
|
} else {
|
|
10172
|
-
await ensureDir(
|
|
10503
|
+
await ensureDir(resolve19(dest, ".."));
|
|
10173
10504
|
await cp(src, dest, { force: true });
|
|
10174
10505
|
}
|
|
10175
10506
|
}
|
|
@@ -10217,7 +10548,7 @@ async function backupToGithub(overrides) {
|
|
|
10217
10548
|
}
|
|
10218
10549
|
if (category === "config") {
|
|
10219
10550
|
const sanitized = await readSanitizedConfig(sourcePath);
|
|
10220
|
-
await ensureDir(
|
|
10551
|
+
await ensureDir(resolve19(destPath, ".."));
|
|
10221
10552
|
await writeFile4(destPath, sanitized, "utf-8");
|
|
10222
10553
|
} else {
|
|
10223
10554
|
await copyRecursive(sourcePath, destPath);
|
|
@@ -10271,7 +10602,7 @@ async function backupToGithub(overrides) {
|
|
|
10271
10602
|
}
|
|
10272
10603
|
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
10273
10604
|
if (isFile) {
|
|
10274
|
-
await ensureDir(
|
|
10605
|
+
await ensureDir(resolve19(localPath, ".."));
|
|
10275
10606
|
await cp(repoSrcPath, localPath, { force: true });
|
|
10276
10607
|
return;
|
|
10277
10608
|
}
|
|
@@ -10372,7 +10703,7 @@ async function restoreFromGithub(overrides) {
|
|
|
10372
10703
|
}
|
|
10373
10704
|
async function getBackupStatus() {
|
|
10374
10705
|
const config = await readConfig();
|
|
10375
|
-
const lockPath =
|
|
10706
|
+
const lockPath = resolve19(syntaurRoot(), LOCK_FILE_NAME);
|
|
10376
10707
|
const locked = await fileExists(lockPath);
|
|
10377
10708
|
return {
|
|
10378
10709
|
repo: config.backup?.repo ?? null,
|
|
@@ -10385,7 +10716,7 @@ async function getBackupStatus() {
|
|
|
10385
10716
|
|
|
10386
10717
|
// src/dashboard/api-backup.ts
|
|
10387
10718
|
function createBackupRouter() {
|
|
10388
|
-
const router =
|
|
10719
|
+
const router = Router8();
|
|
10389
10720
|
router.get("/", async (_req, res) => {
|
|
10390
10721
|
try {
|
|
10391
10722
|
const status = await getBackupStatus();
|
|
@@ -10706,10 +11037,11 @@ function createDashboardServer(options) {
|
|
|
10706
11037
|
migrateFromMarkdown(projectsDir).catch((err) => {
|
|
10707
11038
|
console.error("Session migration from markdown failed:", err);
|
|
10708
11039
|
});
|
|
11040
|
+
initLeasesDb();
|
|
10709
11041
|
(async () => {
|
|
10710
11042
|
try {
|
|
10711
11043
|
const configResult = await migrateLegacyConfig(
|
|
10712
|
-
|
|
11044
|
+
resolve20(syntaurRoot(), "config.md")
|
|
10713
11045
|
);
|
|
10714
11046
|
const projectResult = await migrateLegacyProjectFiles(projectsDir);
|
|
10715
11047
|
const summary = summarizeMigration(projectResult, configResult);
|
|
@@ -11039,6 +11371,7 @@ function createDashboardServer(options) {
|
|
|
11039
11371
|
});
|
|
11040
11372
|
app.use(createWriteRouter(projectsDir, assignmentsDir2));
|
|
11041
11373
|
app.use("/api/servers", createServersRouter(serversDir2, projectsDir, assignmentsDir2));
|
|
11374
|
+
app.use("/api/leases", createLeasesRouter(broadcast));
|
|
11042
11375
|
app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2));
|
|
11043
11376
|
app.use("/api/playbooks", createPlaybooksRouter(playbooksDir2));
|
|
11044
11377
|
app.get("/api/memories", async (_req, res) => {
|
|
@@ -11064,14 +11397,14 @@ function createDashboardServer(options) {
|
|
|
11064
11397
|
app.use("/api/backup", createBackupRouter());
|
|
11065
11398
|
if (serveStaticUi && dashboardDistPath) {
|
|
11066
11399
|
const sendOpts = { dotfiles: "allow" };
|
|
11067
|
-
app.use("/assets", express.static(
|
|
11400
|
+
app.use("/assets", express.static(resolve20(dashboardDistPath, "assets"), sendOpts));
|
|
11068
11401
|
app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
|
|
11069
11402
|
app.get("{*path}", async (req, res) => {
|
|
11070
11403
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
11071
11404
|
res.status(404).json({ error: "Not Found" });
|
|
11072
11405
|
return;
|
|
11073
11406
|
}
|
|
11074
|
-
const indexPath =
|
|
11407
|
+
const indexPath = resolve20(dashboardDistPath, "index.html");
|
|
11075
11408
|
if (!await fileExists(indexPath)) {
|
|
11076
11409
|
res.status(503).send(
|
|
11077
11410
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
@@ -11095,6 +11428,7 @@ function createDashboardServer(options) {
|
|
|
11095
11428
|
serversDir: serversDir2,
|
|
11096
11429
|
playbooksDir: playbooksDir2,
|
|
11097
11430
|
todosDir: todosDir2,
|
|
11431
|
+
dbPath: resolve20(syntaurRoot(), "syntaur.db"),
|
|
11098
11432
|
onMessage: broadcast
|
|
11099
11433
|
});
|
|
11100
11434
|
startAutodiscovery({ serversDir: serversDir2, projectsDir, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
|
|
@@ -11109,7 +11443,7 @@ function createDashboardServer(options) {
|
|
|
11109
11443
|
}
|
|
11110
11444
|
});
|
|
11111
11445
|
server.listen(port, () => {
|
|
11112
|
-
const portFile =
|
|
11446
|
+
const portFile = resolve20(syntaurRoot(), "dashboard-port");
|
|
11113
11447
|
writeFile5(portFile, String(port), "utf-8").catch(() => {
|
|
11114
11448
|
});
|
|
11115
11449
|
resolvePromise();
|
|
@@ -11122,11 +11456,12 @@ function createDashboardServer(options) {
|
|
|
11122
11456
|
await watcherHandle.close();
|
|
11123
11457
|
}
|
|
11124
11458
|
closeSessionDb();
|
|
11459
|
+
closeLeasesDb();
|
|
11125
11460
|
for (const client of clients) {
|
|
11126
11461
|
client.terminate();
|
|
11127
11462
|
}
|
|
11128
11463
|
clients.clear();
|
|
11129
|
-
const portFile =
|
|
11464
|
+
const portFile = resolve20(syntaurRoot(), "dashboard-port");
|
|
11130
11465
|
await unlink4(portFile).catch(() => {
|
|
11131
11466
|
});
|
|
11132
11467
|
server.closeAllConnections?.();
|