syntaur 0.70.0 → 0.71.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 +1 -1
- package/dashboard/dist/assets/{_basePickBy-DKk6tHtk.js → _basePickBy-BfpzwNC3.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-DM-f7DWz.js → _baseUniq-wFEHBTCT.js} +1 -1
- package/dashboard/dist/assets/{arc-ZBlA3YdV.js → arc-Bdcw4sCg.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-BUmvtGTF.js → architectureDiagram-2XIMDMQ5-BessFAtJ.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-B3qxWK6s.js → blockDiagram-WCTKOSBZ-CmjyyZPp.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BEq_UJO-.js → c4Diagram-IC4MRINW-dT6p8cwc.js} +1 -1
- package/dashboard/dist/assets/channel-BOguQ5S4.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-C-Y9ryMm.js → chunk-4BX2VUAB-BlY-k8DY.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-CGdtbsjw.js → chunk-55IACEB6-CtQ0p0jb.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-DllxJhUp.js → chunk-FMBD7UC4-DEevt_Yh.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-jjMM8O5F.js → chunk-JSJVCQXG-CpjLL7Nh.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-B_6BPltQ.js → chunk-KX2RTZJC-BWNRms_o.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-D0hJiXHp.js → chunk-NQ4KR5QH-qo2zbylv.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-BCWo4hLS.js → chunk-QZHKN3VN-DRvojtzc.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-DH_jEAwg.js → chunk-WL4C6EOR-A40iPU5N.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-BLSHooma.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BLSHooma.js +1 -0
- package/dashboard/dist/assets/clone-iSEXw5tA.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-5ld00TOH.js → cose-bilkent-S5V4N54A-CYaO7nsk.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-Cnu6eQWy.js → dagre-KLK3FWXG-NX3inwVZ.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-_CBBKNP-.js → diagram-E7M64L7V-D9cJwhQW.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-DE6WjIb1.js → diagram-IFDJBPK2-Bo5sFrMg.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-DpW7UzNK.js → diagram-P4PSJMXO-DfRH0XU7.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-BD2409fE.js → erDiagram-INFDFZHY-BgP1bwgw.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-1p0khhFI.js → flowDiagram-PKNHOUZH-C8Wws1ci.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-B2zFyA4s.js → ganttDiagram-A5KZAMGK-BgJhUuj8.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-bH-4YH7h.js → gitGraphDiagram-K3NZZRJ6-CI9FgCgo.js} +1 -1
- package/dashboard/dist/assets/{graph-BT24B6iQ.js → graph-B_7GyeiA.js} +1 -1
- package/dashboard/dist/assets/{index-Cxqr3rQB.js → index-CbndxFlO.js} +3 -3
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-CMzP4Hcg.js → infoDiagram-LFFYTUFH-BEtZcRxI.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DMEmFC7M.js → ishikawaDiagram-PHBUUO56-0o9Bksuc.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CAtzYQUm.js → journeyDiagram-4ABVD52K-RyRU3as7.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-d1JVbtvX.js → kanban-definition-K7BYSVSG-hLrHHizB.js} +1 -1
- package/dashboard/dist/assets/{layout-BVuI38I_.js → layout-w458wydd.js} +1 -1
- package/dashboard/dist/assets/{linear-Bc8PGMbp.js → linear-CnRlR5_w.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-UtwFYLNj.js → mermaid.core-DJ_9ty0Y.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-DHc5RCDj.js → mindmap-definition-YRQLILUH-C7TIzJVC.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-9anIsdIA.js → pieDiagram-SKSYHLDU-BZ9ysteM.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-FZ0D9HnU.js → quadrantDiagram-337W2JSQ-DpaMVSO1.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-BkjCvH_u.js → requirementDiagram-Z7DCOOCP-EXFcskZZ.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DzqwYHDo.js → sankeyDiagram-WA2Y5GQK-BdImdXkY.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-BW4g5Ao-.js → sequenceDiagram-2WXFIKYE-CWN3faYf.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-D0tKU3Z0.js → stateDiagram-RAJIS63D-DIQdn-BF.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-1u62oWzq.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-Bu269QDX.js → timeline-definition-YZTLITO2-DMTIpUOE.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-BGu_rrva.js → treemap-KZPCXAKY-Y2AH2GB7.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-Cx_n5FRZ.js → vennDiagram-LZ73GAT5-BF4bJK-l.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-BOJsKV_W.js → xychartDiagram-JWTSCODW-DH-tOBC5.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/dashboard/server.js +1384 -540
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +2769 -1535
- package/dist/index.js.map +1 -1
- package/dist/launch/index.js +594 -109
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
- package/platforms/claude-code/hooks/enforce-boundaries.sh +49 -16
- package/platforms/claude-code/hooks/session-cleanup.sh +4 -2
- package/platforms/claude-code/skills/add-memory/SKILL.md +3 -2
- package/platforms/claude-code/skills/add-resource/SKILL.md +3 -2
- package/platforms/claude-code/skills/bundle-worktree/SKILL.md +3 -3
- package/platforms/claude-code/skills/capture-artifacts/SKILL.md +2 -2
- package/platforms/claude-code/skills/claim-resource/SKILL.md +3 -3
- package/platforms/claude-code/skills/clear-assignment/SKILL.md +9 -27
- package/platforms/claude-code/skills/complete-assignment/SKILL.md +3 -3
- package/platforms/claude-code/skills/create-assignment/SKILL.md +2 -2
- package/platforms/claude-code/skills/extend-resource/SKILL.md +2 -2
- package/platforms/claude-code/skills/grab-assignment/SKILL.md +17 -12
- package/platforms/claude-code/skills/grab-bundle/SKILL.md +10 -6
- package/platforms/claude-code/skills/log-progress/SKILL.md +6 -4
- package/platforms/claude-code/skills/plan-assignment/SKILL.md +3 -3
- package/platforms/claude-code/skills/plan-bundle/SKILL.md +4 -3
- package/platforms/claude-code/skills/replan/SKILL.md +9 -6
- package/platforms/claude-code/skills/resume-session/SKILL.md +14 -8
- package/platforms/claude-code/skills/save-session-summary/SKILL.md +4 -4
- package/platforms/claude-code/skills/set-workspace/SKILL.md +9 -6
- package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +9 -7
- package/platforms/claude-code/skills/syntaur-protocol/references/file-ownership.md +1 -1
- package/platforms/claude-code/skills/syntaur-worktree/SKILL.md +11 -7
- package/platforms/codex/.codex-plugin/plugin.json +1 -1
- package/platforms/codex/scripts/enforce-boundaries.sh +74 -12
- package/platforms/codex/scripts/session-cleanup.sh +20 -13
- package/platforms/codex/skills/add-memory/SKILL.md +3 -2
- package/platforms/codex/skills/add-resource/SKILL.md +3 -2
- package/platforms/codex/skills/bundle-worktree/SKILL.md +3 -3
- package/platforms/codex/skills/capture-artifacts/SKILL.md +2 -2
- package/platforms/codex/skills/claim-resource/SKILL.md +3 -3
- package/platforms/codex/skills/clear-assignment/SKILL.md +9 -27
- package/platforms/codex/skills/complete-assignment/SKILL.md +3 -3
- package/platforms/codex/skills/create-assignment/SKILL.md +2 -2
- package/platforms/codex/skills/extend-resource/SKILL.md +2 -2
- package/platforms/codex/skills/grab-assignment/SKILL.md +17 -12
- package/platforms/codex/skills/grab-bundle/SKILL.md +10 -6
- package/platforms/codex/skills/log-progress/SKILL.md +6 -4
- package/platforms/codex/skills/plan-assignment/SKILL.md +3 -3
- package/platforms/codex/skills/plan-bundle/SKILL.md +4 -3
- package/platforms/codex/skills/replan/SKILL.md +9 -6
- package/platforms/codex/skills/resume-session/SKILL.md +14 -8
- package/platforms/codex/skills/save-session-summary/SKILL.md +4 -4
- package/platforms/codex/skills/set-workspace/SKILL.md +9 -6
- package/platforms/codex/skills/syntaur-protocol/SKILL.md +9 -7
- package/platforms/codex/skills/syntaur-protocol/references/file-ownership.md +1 -1
- package/platforms/codex/skills/syntaur-worktree/SKILL.md +11 -7
- package/platforms/hermes/plugins/syntaur/__init__.py +29 -3
- package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
- package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
- package/platforms/hermes/plugins/syntaur/boundary.py +18 -9
- package/platforms/pi/extensions/syntaur/index.ts +84 -29
- package/skills/add-memory/SKILL.md +3 -2
- package/skills/add-resource/SKILL.md +3 -2
- package/skills/bundle-worktree/SKILL.md +3 -3
- package/skills/capture-artifacts/SKILL.md +2 -2
- package/skills/claim-resource/SKILL.md +3 -3
- package/skills/clear-assignment/SKILL.md +9 -27
- package/skills/complete-assignment/SKILL.md +3 -3
- package/skills/create-assignment/SKILL.md +2 -2
- package/skills/extend-resource/SKILL.md +2 -2
- package/skills/grab-assignment/SKILL.md +17 -12
- package/skills/grab-bundle/SKILL.md +10 -6
- package/skills/log-progress/SKILL.md +6 -4
- package/skills/plan-assignment/SKILL.md +3 -3
- package/skills/plan-bundle/SKILL.md +4 -3
- package/skills/replan/SKILL.md +9 -6
- package/skills/resume-session/SKILL.md +14 -8
- package/skills/save-session-summary/SKILL.md +4 -4
- package/skills/set-workspace/SKILL.md +9 -6
- package/skills/syntaur-protocol/SKILL.md +9 -7
- package/skills/syntaur-protocol/references/file-ownership.md +1 -1
- package/skills/syntaur-worktree/SKILL.md +11 -7
- package/dashboard/dist/assets/channel-fypxffzQ.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-1KnjQvtL.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-1KnjQvtL.js +0 -1
- package/dashboard/dist/assets/clone-CKMabBhS.js +0 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DxWQhjNO.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -36,9 +36,9 @@ function expandHome(p) {
|
|
|
36
36
|
return p;
|
|
37
37
|
}
|
|
38
38
|
function syntaurRoot() {
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
return resolve(expandHome(
|
|
39
|
+
const override3 = process.env.SYNTAUR_HOME;
|
|
40
|
+
if (override3 && override3.length > 0) {
|
|
41
|
+
return resolve(expandHome(override3));
|
|
42
42
|
}
|
|
43
43
|
return resolve(homedir(), ".syntaur");
|
|
44
44
|
}
|
|
@@ -452,6 +452,7 @@ function parseAssignmentFrontmatter(fileContent) {
|
|
|
452
452
|
planApproval: parsePlanApproval(frontmatter),
|
|
453
453
|
parked: getField2("parked") === "true",
|
|
454
454
|
reviewRequested: getField2("reviewRequested") === "true",
|
|
455
|
+
reworkRequested: getField2("reworkRequested") === "true",
|
|
455
456
|
implementationStarted: getField2("implementationStarted") === "true",
|
|
456
457
|
override: parseOverride(frontmatter),
|
|
457
458
|
facts: parseFactsMap(frontmatter),
|
|
@@ -634,11 +635,11 @@ function updatePlanApproval(fileContent, approval) {
|
|
|
634
635
|
approval === null ? null : { file: approval.file, digest: approval.digest, by: approval.by, at: approval.at }
|
|
635
636
|
);
|
|
636
637
|
}
|
|
637
|
-
function updateOverride(fileContent,
|
|
638
|
+
function updateOverride(fileContent, override3) {
|
|
638
639
|
return updateNestedBlock(
|
|
639
640
|
fileContent,
|
|
640
641
|
"override",
|
|
641
|
-
|
|
642
|
+
override3 === null ? null : { status: override3.status, source: override3.source, reason: override3.reason, at: override3.at }
|
|
642
643
|
);
|
|
643
644
|
}
|
|
644
645
|
function updateFactsMap(fileContent, name, value) {
|
|
@@ -1271,6 +1272,7 @@ function parseAssignmentFull(fileContent) {
|
|
|
1271
1272
|
disposition: getField(fm, "disposition"),
|
|
1272
1273
|
parked: getField(fm, "parked") === "true",
|
|
1273
1274
|
reviewRequested: getField(fm, "reviewRequested") === "true",
|
|
1275
|
+
reworkRequested: getField(fm, "reworkRequested") === "true",
|
|
1274
1276
|
implementationStarted: getField(fm, "implementationStarted") === "true",
|
|
1275
1277
|
planApproval: (() => {
|
|
1276
1278
|
const file = getNestedField(fm, "planApproval", "file");
|
|
@@ -2737,7 +2739,9 @@ var init_derive_config = __esm({
|
|
|
2737
2739
|
},
|
|
2738
2740
|
{
|
|
2739
2741
|
phase: "review",
|
|
2740
|
-
|
|
2742
|
+
// Rework (a new `implement` stage after `review`) must leave review even
|
|
2743
|
+
// when ACs stay checked — hence `AND NOT reworkRequested` (stage-fact-status-bridge).
|
|
2744
|
+
when: "(acAllChecked:true OR reviewRequested:true) AND NOT reworkRequested:true",
|
|
2741
2745
|
next: "Complete, or address review feedback"
|
|
2742
2746
|
}
|
|
2743
2747
|
],
|
|
@@ -2804,6 +2808,7 @@ var init_fields = __esm({
|
|
|
2804
2808
|
blocked: { kind: "bool" },
|
|
2805
2809
|
parked: { kind: "bool" },
|
|
2806
2810
|
reviewrequested: { kind: "bool", get: (i) => i["reviewRequested"] },
|
|
2811
|
+
reworkrequested: { kind: "bool", get: (i) => i["reworkRequested"] },
|
|
2807
2812
|
pinned: { kind: "bool" }
|
|
2808
2813
|
};
|
|
2809
2814
|
}
|
|
@@ -3597,6 +3602,7 @@ function queryFieldNames(declarations) {
|
|
|
3597
3602
|
"blocked",
|
|
3598
3603
|
"parked",
|
|
3599
3604
|
"reviewRequested",
|
|
3605
|
+
"reworkRequested",
|
|
3600
3606
|
"pinned"
|
|
3601
3607
|
];
|
|
3602
3608
|
const custom = [];
|
|
@@ -3635,6 +3641,7 @@ var init_fact_registry = __esm({
|
|
|
3635
3641
|
blocked: { kind: "bool" },
|
|
3636
3642
|
parked: { kind: "bool" },
|
|
3637
3643
|
reviewrequested: { kind: "bool", get: (i) => i["reviewRequested"] },
|
|
3644
|
+
reworkrequested: { kind: "bool", get: (i) => i["reworkRequested"] },
|
|
3638
3645
|
pinned: { kind: "bool" }
|
|
3639
3646
|
};
|
|
3640
3647
|
}
|
|
@@ -5633,7 +5640,7 @@ function compiledWhen(registry, when) {
|
|
|
5633
5640
|
return pred;
|
|
5634
5641
|
}
|
|
5635
5642
|
function deriveDimensions(input) {
|
|
5636
|
-
const { facts, derive, currentStatus, terminalStatuses, knownStatusIds, override } = input;
|
|
5643
|
+
const { facts, derive, currentStatus, terminalStatuses, knownStatusIds, override: override3 } = input;
|
|
5637
5644
|
const registry = input.registry ?? DERIVE_FIELDS;
|
|
5638
5645
|
if (terminalStatuses.has(currentStatus)) return null;
|
|
5639
5646
|
const ctx = { now: 0 };
|
|
@@ -5667,8 +5674,8 @@ function deriveDimensions(input) {
|
|
|
5667
5674
|
derivedStatus = phase;
|
|
5668
5675
|
}
|
|
5669
5676
|
let status = derivedStatus;
|
|
5670
|
-
if (
|
|
5671
|
-
status =
|
|
5677
|
+
if (override3 && override3.status && !terminalStatuses.has(override3.status) && knownStatusIds.has(override3.status)) {
|
|
5678
|
+
status = override3.status;
|
|
5672
5679
|
}
|
|
5673
5680
|
return { phase, disposition, derivedStatus, status, nextAction };
|
|
5674
5681
|
}
|
|
@@ -5954,6 +5961,18 @@ async function resolveAssignmentById(projectsDir, assignmentsDir2, id) {
|
|
|
5954
5961
|
}
|
|
5955
5962
|
return standaloneMatch ?? projectMatch ?? null;
|
|
5956
5963
|
}
|
|
5964
|
+
async function resolveAssignmentBySlug(projectsDir, assignmentsDir2, projectSlug, assignmentSlug) {
|
|
5965
|
+
const path = projectSlug ? resolve9(projectsDir, projectSlug, "assignments", assignmentSlug, "assignment.md") : resolve9(assignmentsDir2, assignmentSlug, "assignment.md");
|
|
5966
|
+
if (!await fileExists(path)) return { exists: false, id: null };
|
|
5967
|
+
try {
|
|
5968
|
+
const content = await readFile7(path, "utf-8");
|
|
5969
|
+
const [fm] = extractFrontmatter2(content);
|
|
5970
|
+
const id = getField(fm, "id");
|
|
5971
|
+
return { exists: true, id: id ?? null };
|
|
5972
|
+
} catch {
|
|
5973
|
+
return { exists: false, id: null };
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5957
5976
|
var init_assignment_resolver = __esm({
|
|
5958
5977
|
"src/utils/assignment-resolver.ts"() {
|
|
5959
5978
|
"use strict";
|
|
@@ -6389,6 +6408,7 @@ async function computeFactsDetailed(input) {
|
|
|
6389
6408
|
blocked: frontmatter.blockedReason !== null,
|
|
6390
6409
|
parked: frontmatter.parked,
|
|
6391
6410
|
reviewRequested: frontmatter.reviewRequested,
|
|
6411
|
+
reworkRequested: frontmatter.reworkRequested,
|
|
6392
6412
|
pinned: frontmatter.override !== null
|
|
6393
6413
|
};
|
|
6394
6414
|
const attestations = [];
|
|
@@ -7443,6 +7463,139 @@ var init_help = __esm({
|
|
|
7443
7463
|
}
|
|
7444
7464
|
});
|
|
7445
7465
|
|
|
7466
|
+
// src/db/engagement-schema.ts
|
|
7467
|
+
var ENGAGEMENT_SCHEMA_VERSION, ENGAGEMENT_DDL;
|
|
7468
|
+
var init_engagement_schema = __esm({
|
|
7469
|
+
"src/db/engagement-schema.ts"() {
|
|
7470
|
+
"use strict";
|
|
7471
|
+
ENGAGEMENT_SCHEMA_VERSION = "1";
|
|
7472
|
+
ENGAGEMENT_DDL = `
|
|
7473
|
+
CREATE TABLE IF NOT EXISTS engagement (
|
|
7474
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
7475
|
+
session_id TEXT NOT NULL,
|
|
7476
|
+
assignment_id TEXT,
|
|
7477
|
+
project_slug TEXT,
|
|
7478
|
+
assignment_slug TEXT,
|
|
7479
|
+
stage TEXT NOT NULL DEFAULT 'implement',
|
|
7480
|
+
started_at TEXT NOT NULL,
|
|
7481
|
+
ended_at TEXT,
|
|
7482
|
+
tokens_at_open TEXT,
|
|
7483
|
+
tokens_at_close TEXT,
|
|
7484
|
+
close_reason TEXT
|
|
7485
|
+
);
|
|
7486
|
+
CREATE UNIQUE INDEX IF NOT EXISTS one_active_per_session
|
|
7487
|
+
ON engagement(session_id) WHERE ended_at IS NULL;
|
|
7488
|
+
CREATE INDEX IF NOT EXISTS idx_engagement_session ON engagement(session_id);
|
|
7489
|
+
CREATE INDEX IF NOT EXISTS idx_engagement_assignment ON engagement(assignment_id);
|
|
7490
|
+
CREATE INDEX IF NOT EXISTS idx_engagement_slug ON engagement(project_slug, assignment_slug);
|
|
7491
|
+
`;
|
|
7492
|
+
}
|
|
7493
|
+
});
|
|
7494
|
+
|
|
7495
|
+
// src/db/engagement-backfill.ts
|
|
7496
|
+
import { readdirSync, readFileSync, statSync as statSync2 } from "fs";
|
|
7497
|
+
import { resolve as resolve13 } from "path";
|
|
7498
|
+
function safeReaddir(dir) {
|
|
7499
|
+
try {
|
|
7500
|
+
return readdirSync(dir, { withFileTypes: true });
|
|
7501
|
+
} catch {
|
|
7502
|
+
return [];
|
|
7503
|
+
}
|
|
7504
|
+
}
|
|
7505
|
+
function readAssignmentId(assignmentMdPath) {
|
|
7506
|
+
try {
|
|
7507
|
+
const fm = parseAssignmentFrontmatter(readFileSync(assignmentMdPath, "utf-8"));
|
|
7508
|
+
return fm.id || null;
|
|
7509
|
+
} catch {
|
|
7510
|
+
return null;
|
|
7511
|
+
}
|
|
7512
|
+
}
|
|
7513
|
+
function transcriptMtimeIso(path) {
|
|
7514
|
+
if (!path) return null;
|
|
7515
|
+
try {
|
|
7516
|
+
return statSync2(path).mtime.toISOString();
|
|
7517
|
+
} catch {
|
|
7518
|
+
return null;
|
|
7519
|
+
}
|
|
7520
|
+
}
|
|
7521
|
+
function projectKey(projectSlug, assignmentSlug) {
|
|
7522
|
+
return JSON.stringify([projectSlug, assignmentSlug]);
|
|
7523
|
+
}
|
|
7524
|
+
function buildSlugIdMaps() {
|
|
7525
|
+
const root = syntaurRoot();
|
|
7526
|
+
const project = /* @__PURE__ */ new Map();
|
|
7527
|
+
const standalone = /* @__PURE__ */ new Map();
|
|
7528
|
+
const projectsDir = resolve13(root, "projects");
|
|
7529
|
+
for (const proj of safeReaddir(projectsDir)) {
|
|
7530
|
+
if (!proj.isDirectory()) continue;
|
|
7531
|
+
const asgRoot = resolve13(projectsDir, proj.name, "assignments");
|
|
7532
|
+
for (const asg of safeReaddir(asgRoot)) {
|
|
7533
|
+
if (!asg.isDirectory()) continue;
|
|
7534
|
+
const id = readAssignmentId(resolve13(asgRoot, asg.name, "assignment.md"));
|
|
7535
|
+
if (id) project.set(projectKey(proj.name, asg.name), id);
|
|
7536
|
+
}
|
|
7537
|
+
}
|
|
7538
|
+
const standaloneDir = resolve13(root, "assignments");
|
|
7539
|
+
for (const asg of safeReaddir(standaloneDir)) {
|
|
7540
|
+
if (!asg.isDirectory()) continue;
|
|
7541
|
+
const id = readAssignmentId(resolve13(standaloneDir, asg.name, "assignment.md"));
|
|
7542
|
+
if (id) standalone.set(asg.name, id);
|
|
7543
|
+
}
|
|
7544
|
+
return { project, standalone };
|
|
7545
|
+
}
|
|
7546
|
+
function backfillEngagements(db5) {
|
|
7547
|
+
const { project, standalone } = buildSlugIdMaps();
|
|
7548
|
+
const sessions = db5.prepare(
|
|
7549
|
+
`SELECT session_id, project_slug, assignment_slug, status, started, ended,
|
|
7550
|
+
transcript_path, updated_at
|
|
7551
|
+
FROM sessions`
|
|
7552
|
+
).all();
|
|
7553
|
+
const insert = db5.prepare(
|
|
7554
|
+
`INSERT INTO engagement
|
|
7555
|
+
(session_id, assignment_id, project_slug, assignment_slug, stage,
|
|
7556
|
+
started_at, ended_at, tokens_at_open, tokens_at_close, close_reason)
|
|
7557
|
+
VALUES
|
|
7558
|
+
(@session_id, @assignment_id, @project_slug, @assignment_slug, 'implement',
|
|
7559
|
+
@started_at, @ended_at, NULL, NULL, @close_reason)`
|
|
7560
|
+
);
|
|
7561
|
+
const counts = { backfilled: 0, attributed: 0, unattributed: 0 };
|
|
7562
|
+
for (const s of sessions) {
|
|
7563
|
+
let assignmentId = null;
|
|
7564
|
+
if (s.project_slug && s.assignment_slug) {
|
|
7565
|
+
assignmentId = project.get(projectKey(s.project_slug, s.assignment_slug)) ?? null;
|
|
7566
|
+
} else if (!s.project_slug && s.assignment_slug) {
|
|
7567
|
+
assignmentId = standalone.get(s.assignment_slug) ?? null;
|
|
7568
|
+
}
|
|
7569
|
+
const isActive = s.status === "active";
|
|
7570
|
+
let endedAt = null;
|
|
7571
|
+
let closeReason = null;
|
|
7572
|
+
if (!isActive) {
|
|
7573
|
+
endedAt = s.ended ?? transcriptMtimeIso(s.transcript_path) ?? s.updated_at ?? s.started;
|
|
7574
|
+
closeReason = s.status === "completed" ? "completed" : "abandoned";
|
|
7575
|
+
}
|
|
7576
|
+
insert.run({
|
|
7577
|
+
session_id: s.session_id,
|
|
7578
|
+
assignment_id: assignmentId,
|
|
7579
|
+
project_slug: s.project_slug,
|
|
7580
|
+
assignment_slug: s.assignment_slug,
|
|
7581
|
+
started_at: s.started,
|
|
7582
|
+
ended_at: endedAt,
|
|
7583
|
+
close_reason: closeReason
|
|
7584
|
+
});
|
|
7585
|
+
counts.backfilled++;
|
|
7586
|
+
if (assignmentId) counts.attributed++;
|
|
7587
|
+
else counts.unattributed++;
|
|
7588
|
+
}
|
|
7589
|
+
return counts;
|
|
7590
|
+
}
|
|
7591
|
+
var init_engagement_backfill = __esm({
|
|
7592
|
+
"src/db/engagement-backfill.ts"() {
|
|
7593
|
+
"use strict";
|
|
7594
|
+
init_paths();
|
|
7595
|
+
init_frontmatter();
|
|
7596
|
+
}
|
|
7597
|
+
});
|
|
7598
|
+
|
|
7446
7599
|
// src/dashboard/session-db.ts
|
|
7447
7600
|
var session_db_exports = {};
|
|
7448
7601
|
__export(session_db_exports, {
|
|
@@ -7454,18 +7607,23 @@ __export(session_db_exports, {
|
|
|
7454
7607
|
resetSessionDb: () => resetSessionDb
|
|
7455
7608
|
});
|
|
7456
7609
|
import Database2 from "better-sqlite3";
|
|
7457
|
-
import { resolve as
|
|
7610
|
+
import { resolve as resolve14 } from "path";
|
|
7458
7611
|
import { readdir as readdir8 } from "fs/promises";
|
|
7459
7612
|
function initSessionDb(dbPath) {
|
|
7460
7613
|
if (db2) return db2;
|
|
7461
|
-
const finalPath = dbPath ??
|
|
7614
|
+
const finalPath = dbPath ?? resolve14(syntaurRoot(), "syntaur.db");
|
|
7462
7615
|
db2 = new Database2(finalPath);
|
|
7463
7616
|
db2.pragma("journal_mode = WAL");
|
|
7464
7617
|
db2.exec(SCHEMA_SQL2);
|
|
7618
|
+
db2.exec(ENGAGEMENT_DDL);
|
|
7465
7619
|
db2.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
|
|
7466
7620
|
"schema_version",
|
|
7467
7621
|
SCHEMA_VERSION
|
|
7468
7622
|
);
|
|
7623
|
+
db2.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
|
|
7624
|
+
"engagement_schema_version",
|
|
7625
|
+
ENGAGEMENT_SCHEMA_VERSION
|
|
7626
|
+
);
|
|
7469
7627
|
const database = db2;
|
|
7470
7628
|
const runMigrations = database.transaction(() => {
|
|
7471
7629
|
const vBeforeV2 = database.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get()?.value;
|
|
@@ -7592,9 +7750,43 @@ function initSessionDb(dbPath) {
|
|
|
7592
7750
|
UPDATE meta SET value = '5' WHERE key = 'schema_version';
|
|
7593
7751
|
`);
|
|
7594
7752
|
}
|
|
7753
|
+
const vBeforeV6 = database.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get()?.value;
|
|
7754
|
+
if (vBeforeV6 === "5") {
|
|
7755
|
+
database.exec(ENGAGEMENT_DDL);
|
|
7756
|
+
const counts = backfillEngagements(database);
|
|
7757
|
+
database.exec(`
|
|
7758
|
+
CREATE TABLE sessions_v6 (
|
|
7759
|
+
session_id TEXT PRIMARY KEY,
|
|
7760
|
+
agent TEXT NOT NULL,
|
|
7761
|
+
started TEXT NOT NULL,
|
|
7762
|
+
ended TEXT,
|
|
7763
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
7764
|
+
path TEXT,
|
|
7765
|
+
description TEXT,
|
|
7766
|
+
transcript_path TEXT,
|
|
7767
|
+
pid INTEGER,
|
|
7768
|
+
pid_started_at TEXT,
|
|
7769
|
+
original_head_sha TEXT,
|
|
7770
|
+
activity TEXT,
|
|
7771
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7772
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7773
|
+
);
|
|
7774
|
+
INSERT INTO sessions_v6
|
|
7775
|
+
SELECT session_id, agent, started, ended, status, path, description,
|
|
7776
|
+
transcript_path, pid, pid_started_at, original_head_sha, NULL,
|
|
7777
|
+
created_at, updated_at
|
|
7778
|
+
FROM sessions;
|
|
7779
|
+
DROP TABLE sessions;
|
|
7780
|
+
ALTER TABLE sessions_v6 RENAME TO sessions;
|
|
7781
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
7782
|
+
UPDATE meta SET value = '6' WHERE key = 'schema_version';
|
|
7783
|
+
`);
|
|
7784
|
+
console.log(
|
|
7785
|
+
`engagement backfill: backfilled=${counts.backfilled} attributed=${counts.attributed} unattributed=${counts.unattributed}`
|
|
7786
|
+
);
|
|
7787
|
+
}
|
|
7595
7788
|
});
|
|
7596
7789
|
runMigrations.exclusive();
|
|
7597
|
-
db2.exec(POST_MIGRATION_INDEXES_SQL);
|
|
7598
7790
|
return db2;
|
|
7599
7791
|
}
|
|
7600
7792
|
function isSessionDbInitialized() {
|
|
@@ -7626,20 +7818,38 @@ async function migrateFromMarkdown(projectsDir) {
|
|
|
7626
7818
|
const allSessions = [];
|
|
7627
7819
|
for (const entry of entries) {
|
|
7628
7820
|
if (!entry.isDirectory()) continue;
|
|
7629
|
-
const projectDir =
|
|
7630
|
-
const indexPath =
|
|
7821
|
+
const projectDir = resolve14(projectsDir, entry.name);
|
|
7822
|
+
const indexPath = resolve14(projectDir, "_index-sessions.md");
|
|
7631
7823
|
if (!await fileExists(indexPath)) continue;
|
|
7632
7824
|
const sessions = await parseMarkdownSessionsIndex(indexPath, entry.name);
|
|
7633
7825
|
allSessions.push(...sessions);
|
|
7634
7826
|
}
|
|
7635
7827
|
if (allSessions.length === 0) return 0;
|
|
7636
7828
|
const insert = database.prepare(`
|
|
7637
|
-
INSERT OR IGNORE INTO sessions (session_id,
|
|
7638
|
-
VALUES (?, ?, ?, ?,
|
|
7829
|
+
INSERT OR IGNORE INTO sessions (session_id, agent, started, status, path)
|
|
7830
|
+
VALUES (?, ?, ?, ?, ?)
|
|
7831
|
+
`);
|
|
7832
|
+
const insertEngagement = database.prepare(`
|
|
7833
|
+
INSERT INTO engagement (session_id, project_slug, assignment_slug, stage, started_at, ended_at, close_reason)
|
|
7834
|
+
SELECT @sid, @ps, @as, 'implement', @started, @ended, @reason
|
|
7835
|
+
WHERE NOT EXISTS (
|
|
7836
|
+
SELECT 1 FROM engagement WHERE session_id = @sid AND ended_at IS NULL
|
|
7837
|
+
)
|
|
7639
7838
|
`);
|
|
7640
7839
|
const insertAll = database.transaction((sessions) => {
|
|
7641
7840
|
for (const s of sessions) {
|
|
7642
|
-
insert.run(s.sessionId, s.
|
|
7841
|
+
const res = insert.run(s.sessionId, s.agent, s.started, s.status, s.path);
|
|
7842
|
+
if (res.changes > 0 && (s.projectSlug || s.assignmentSlug)) {
|
|
7843
|
+
const terminal = s.status === "completed" || s.status === "stopped";
|
|
7844
|
+
insertEngagement.run({
|
|
7845
|
+
sid: s.sessionId,
|
|
7846
|
+
ps: s.projectSlug ?? null,
|
|
7847
|
+
as: s.assignmentSlug ?? null,
|
|
7848
|
+
started: s.started,
|
|
7849
|
+
ended: terminal ? s.started : null,
|
|
7850
|
+
reason: terminal ? s.status === "completed" ? "completed" : "abandoned" : null
|
|
7851
|
+
});
|
|
7852
|
+
}
|
|
7643
7853
|
}
|
|
7644
7854
|
});
|
|
7645
7855
|
insertAll(allSessions);
|
|
@@ -7647,8 +7857,8 @@ async function migrateFromMarkdown(projectsDir) {
|
|
|
7647
7857
|
return allSessions.length;
|
|
7648
7858
|
}
|
|
7649
7859
|
async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
7650
|
-
const { readFile:
|
|
7651
|
-
const raw2 = await
|
|
7860
|
+
const { readFile: readFile31 } = await import("fs/promises");
|
|
7861
|
+
const raw2 = await readFile31(filePath, "utf-8");
|
|
7652
7862
|
const sessions = [];
|
|
7653
7863
|
const lines = raw2.split("\n");
|
|
7654
7864
|
let inTable = false;
|
|
@@ -7682,19 +7892,19 @@ async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
|
7682
7892
|
}
|
|
7683
7893
|
return sessions;
|
|
7684
7894
|
}
|
|
7685
|
-
var db2, SCHEMA_VERSION, SCHEMA_SQL2
|
|
7895
|
+
var db2, SCHEMA_VERSION, SCHEMA_SQL2;
|
|
7686
7896
|
var init_session_db = __esm({
|
|
7687
7897
|
"src/dashboard/session-db.ts"() {
|
|
7688
7898
|
"use strict";
|
|
7689
7899
|
init_paths();
|
|
7690
7900
|
init_fs();
|
|
7901
|
+
init_engagement_schema();
|
|
7902
|
+
init_engagement_backfill();
|
|
7691
7903
|
db2 = null;
|
|
7692
|
-
SCHEMA_VERSION = "
|
|
7904
|
+
SCHEMA_VERSION = "6";
|
|
7693
7905
|
SCHEMA_SQL2 = `
|
|
7694
7906
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
7695
7907
|
session_id TEXT PRIMARY KEY,
|
|
7696
|
-
project_slug TEXT,
|
|
7697
|
-
assignment_slug TEXT,
|
|
7698
7908
|
agent TEXT NOT NULL,
|
|
7699
7909
|
started TEXT NOT NULL,
|
|
7700
7910
|
ended TEXT,
|
|
@@ -7705,34 +7915,175 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
7705
7915
|
pid INTEGER,
|
|
7706
7916
|
pid_started_at TEXT,
|
|
7707
7917
|
original_head_sha TEXT,
|
|
7918
|
+
activity TEXT,
|
|
7708
7919
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7709
7920
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7710
7921
|
);
|
|
7711
7922
|
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
7712
7923
|
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
|
|
7713
7924
|
`;
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7925
|
+
}
|
|
7926
|
+
});
|
|
7927
|
+
|
|
7928
|
+
// src/db/engagement-tokens.ts
|
|
7929
|
+
function serializeSnapshot(snapshot) {
|
|
7930
|
+
return snapshot === null ? null : JSON.stringify(snapshot);
|
|
7931
|
+
}
|
|
7932
|
+
function parseSnapshot(json) {
|
|
7933
|
+
return json === null ? null : JSON.parse(json);
|
|
7934
|
+
}
|
|
7935
|
+
function tableExists(name) {
|
|
7936
|
+
const row = getSessionDb().prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(name);
|
|
7937
|
+
return row !== void 0;
|
|
7938
|
+
}
|
|
7939
|
+
function getCumulativeTokenSource() {
|
|
7940
|
+
return override ?? productionCumulativeTokenSource;
|
|
7941
|
+
}
|
|
7942
|
+
var productionCumulativeTokenSource, override;
|
|
7943
|
+
var init_engagement_tokens = __esm({
|
|
7944
|
+
"src/db/engagement-tokens.ts"() {
|
|
7945
|
+
"use strict";
|
|
7946
|
+
init_session_db();
|
|
7947
|
+
productionCumulativeTokenSource = async (sessionId) => {
|
|
7948
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7949
|
+
const db5 = getSessionDb();
|
|
7950
|
+
const collectorRow = db5.prepare("SELECT value FROM meta WHERE key = 'usage_last_collector_run'").get();
|
|
7951
|
+
const collectorRunAt = collectorRow?.value ?? null;
|
|
7952
|
+
const models = {};
|
|
7953
|
+
if (tableExists("usage_events")) {
|
|
7954
|
+
const rows = db5.prepare(
|
|
7955
|
+
`SELECT model, input_tokens, output_tokens, cache_creation_tokens,
|
|
7956
|
+
cache_read_tokens, total_tokens, total_cost
|
|
7957
|
+
FROM usage_events
|
|
7958
|
+
WHERE session_id = ?`
|
|
7959
|
+
).all(sessionId);
|
|
7960
|
+
for (const r of rows) {
|
|
7961
|
+
models[r.model] = {
|
|
7962
|
+
input: r.input_tokens,
|
|
7963
|
+
output: r.output_tokens,
|
|
7964
|
+
cacheCreation: r.cache_creation_tokens,
|
|
7965
|
+
cacheRead: r.cache_read_tokens,
|
|
7966
|
+
total: r.total_tokens,
|
|
7967
|
+
cost: r.total_cost
|
|
7968
|
+
};
|
|
7969
|
+
}
|
|
7970
|
+
}
|
|
7971
|
+
return { models, collectorRunAt, capturedAt };
|
|
7972
|
+
};
|
|
7973
|
+
override = null;
|
|
7974
|
+
}
|
|
7975
|
+
});
|
|
7976
|
+
|
|
7977
|
+
// src/db/engagement-db.ts
|
|
7978
|
+
function getOpenEngagement(sessionId) {
|
|
7979
|
+
const row = getSessionDb().prepare(
|
|
7980
|
+
"SELECT * FROM engagement WHERE session_id = ? AND ended_at IS NULL LIMIT 1"
|
|
7981
|
+
).get(sessionId);
|
|
7982
|
+
return row ?? null;
|
|
7983
|
+
}
|
|
7984
|
+
function getLatestEngagement(sessionId) {
|
|
7985
|
+
const row = getSessionDb().prepare(
|
|
7986
|
+
`SELECT * FROM engagement WHERE session_id = ?
|
|
7987
|
+
ORDER BY (ended_at IS NULL) DESC, started_at DESC, id DESC
|
|
7988
|
+
LIMIT 1`
|
|
7989
|
+
).get(sessionId);
|
|
7990
|
+
return row ?? null;
|
|
7991
|
+
}
|
|
7992
|
+
function hasAnyEngagement(sessionId) {
|
|
7993
|
+
return getSessionDb().prepare("SELECT 1 FROM engagement WHERE session_id = ? LIMIT 1").get(sessionId) !== void 0;
|
|
7994
|
+
}
|
|
7995
|
+
function insertClosedEngagement(input) {
|
|
7996
|
+
const res = getSessionDb().prepare(
|
|
7997
|
+
`INSERT INTO engagement
|
|
7998
|
+
(session_id, assignment_id, project_slug, assignment_slug, stage, started_at, ended_at, close_reason)
|
|
7999
|
+
VALUES (@sessionId, @assignmentId, @projectSlug, @assignmentSlug, @stage, @startedAt, @endedAt, @closeReason)`
|
|
8000
|
+
).run({
|
|
8001
|
+
sessionId: input.sessionId,
|
|
8002
|
+
assignmentId: input.assignmentId ?? null,
|
|
8003
|
+
projectSlug: input.projectSlug ?? null,
|
|
8004
|
+
assignmentSlug: input.assignmentSlug ?? null,
|
|
8005
|
+
stage: input.stage ?? DEFAULT_STAGE,
|
|
8006
|
+
startedAt: input.startedAt,
|
|
8007
|
+
endedAt: input.endedAt,
|
|
8008
|
+
closeReason: input.closeReason
|
|
8009
|
+
});
|
|
8010
|
+
return getEngagementById(Number(res.lastInsertRowid));
|
|
8011
|
+
}
|
|
8012
|
+
function getEngagementById(id) {
|
|
8013
|
+
const row = getSessionDb().prepare("SELECT * FROM engagement WHERE id = ?").get(id);
|
|
8014
|
+
if (!row) throw new Error(`engagement ${id} not found after insert`);
|
|
8015
|
+
return row;
|
|
8016
|
+
}
|
|
8017
|
+
function openEngagement(input) {
|
|
8018
|
+
const res = getSessionDb().prepare(
|
|
8019
|
+
`INSERT INTO engagement
|
|
8020
|
+
(session_id, assignment_id, project_slug, assignment_slug, stage, started_at, tokens_at_open)
|
|
8021
|
+
VALUES (@sessionId, @assignmentId, @projectSlug, @assignmentSlug, @stage, @startedAt, @tokensAtOpen)`
|
|
8022
|
+
).run({
|
|
8023
|
+
sessionId: input.sessionId,
|
|
8024
|
+
assignmentId: input.assignmentId ?? null,
|
|
8025
|
+
projectSlug: input.projectSlug ?? null,
|
|
8026
|
+
assignmentSlug: input.assignmentSlug ?? null,
|
|
8027
|
+
stage: input.stage ?? DEFAULT_STAGE,
|
|
8028
|
+
startedAt: input.startedAt,
|
|
8029
|
+
tokensAtOpen: serializeSnapshot(input.tokensAtOpen ?? null)
|
|
8030
|
+
});
|
|
8031
|
+
return getEngagementById(Number(res.lastInsertRowid));
|
|
8032
|
+
}
|
|
8033
|
+
function closeEngagementById(input) {
|
|
8034
|
+
const res = getSessionDb().prepare(
|
|
8035
|
+
`UPDATE engagement
|
|
8036
|
+
SET ended_at = @endedAt,
|
|
8037
|
+
tokens_at_close = @tokensAtClose,
|
|
8038
|
+
close_reason = @closeReason
|
|
8039
|
+
WHERE id = @id AND started_at = @startedAt AND ended_at IS NULL`
|
|
8040
|
+
).run({
|
|
8041
|
+
id: input.id,
|
|
8042
|
+
startedAt: input.startedAt,
|
|
8043
|
+
endedAt: input.endedAt,
|
|
8044
|
+
tokensAtClose: serializeSnapshot(input.tokensAtClose ?? null),
|
|
8045
|
+
closeReason: input.closeReason
|
|
8046
|
+
});
|
|
8047
|
+
return res.changes > 0;
|
|
8048
|
+
}
|
|
8049
|
+
function ensureOpenEngagement(input) {
|
|
8050
|
+
if (getOpenEngagement(input.sessionId)) return null;
|
|
8051
|
+
try {
|
|
8052
|
+
return openEngagement(input);
|
|
8053
|
+
} catch (err) {
|
|
8054
|
+
if (err instanceof Error && /UNIQUE constraint failed/.test(err.message)) {
|
|
8055
|
+
return null;
|
|
8056
|
+
}
|
|
8057
|
+
throw err;
|
|
8058
|
+
}
|
|
8059
|
+
}
|
|
8060
|
+
var DEFAULT_STAGE;
|
|
8061
|
+
var init_engagement_db = __esm({
|
|
8062
|
+
"src/db/engagement-db.ts"() {
|
|
8063
|
+
"use strict";
|
|
8064
|
+
init_session_db();
|
|
8065
|
+
init_engagement_tokens();
|
|
8066
|
+
DEFAULT_STAGE = "implement";
|
|
7718
8067
|
}
|
|
7719
8068
|
});
|
|
7720
8069
|
|
|
7721
8070
|
// src/dashboard/agent-sessions.ts
|
|
7722
8071
|
var agent_sessions_exports = {};
|
|
7723
8072
|
__export(agent_sessions_exports, {
|
|
8073
|
+
SessionResurrectionError: () => SessionResurrectionError,
|
|
7724
8074
|
appendSession: () => appendSession,
|
|
7725
8075
|
deleteSessions: () => deleteSessions,
|
|
7726
8076
|
getSessionById: () => getSessionById,
|
|
7727
8077
|
listAllSessions: () => listAllSessions,
|
|
7728
8078
|
listProjectSessions: () => listProjectSessions,
|
|
7729
8079
|
listSessionsByAssignment: () => listSessionsByAssignment,
|
|
8080
|
+
livenessStopSession: () => livenessStopSession,
|
|
7730
8081
|
parseSessionsIndex: () => parseSessionsIndex,
|
|
7731
8082
|
reconcileActiveSessions: () => reconcileActiveSessions,
|
|
7732
8083
|
updateSessionStatus: () => updateSessionStatus
|
|
7733
8084
|
});
|
|
7734
8085
|
import { readFile as readFile11 } from "fs/promises";
|
|
7735
|
-
import { resolve as
|
|
8086
|
+
import { resolve as resolve15 } from "path";
|
|
7736
8087
|
function rowToSession(row) {
|
|
7737
8088
|
return {
|
|
7738
8089
|
sessionId: row.session_id,
|
|
@@ -7748,28 +8099,64 @@ function rowToSession(row) {
|
|
|
7748
8099
|
pid: row.pid ?? null,
|
|
7749
8100
|
pidStartedAt: row.pid_started_at ?? null,
|
|
7750
8101
|
originalHeadSha: row.original_head_sha ?? null,
|
|
8102
|
+
activity: row.activity ?? null,
|
|
7751
8103
|
updatedAt: row.updated_at ?? null
|
|
7752
8104
|
};
|
|
7753
8105
|
}
|
|
7754
8106
|
async function parseSessionsIndex(_projectDir, projectSlug) {
|
|
7755
8107
|
const db5 = getSessionDb();
|
|
7756
|
-
const rows = db5.prepare(
|
|
8108
|
+
const rows = db5.prepare(`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? ORDER BY s.started DESC`).all(projectSlug);
|
|
7757
8109
|
return rows.map(rowToSession);
|
|
7758
8110
|
}
|
|
8111
|
+
function reopenEngagementIfMissing(sessionId, freshBinding, freshStartedAt, tokensAtOpen) {
|
|
8112
|
+
if (getOpenEngagement(sessionId)) return;
|
|
8113
|
+
if (freshBinding && (freshBinding.projectSlug || freshBinding.assignmentSlug)) {
|
|
8114
|
+
ensureOpenEngagement({
|
|
8115
|
+
sessionId,
|
|
8116
|
+
assignmentId: freshBinding.assignmentId ?? null,
|
|
8117
|
+
projectSlug: freshBinding.projectSlug,
|
|
8118
|
+
assignmentSlug: freshBinding.assignmentSlug,
|
|
8119
|
+
stage: "implement",
|
|
8120
|
+
startedAt: freshStartedAt,
|
|
8121
|
+
tokensAtOpen: tokensAtOpen ?? null
|
|
8122
|
+
});
|
|
8123
|
+
return;
|
|
8124
|
+
}
|
|
8125
|
+
const latest = getLatestEngagement(sessionId);
|
|
8126
|
+
if (latest && (latest.project_slug || latest.assignment_slug)) {
|
|
8127
|
+
ensureOpenEngagement({
|
|
8128
|
+
sessionId,
|
|
8129
|
+
assignmentId: latest.assignment_id,
|
|
8130
|
+
projectSlug: latest.project_slug,
|
|
8131
|
+
assignmentSlug: latest.assignment_slug,
|
|
8132
|
+
stage: "implement",
|
|
8133
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8134
|
+
tokensAtOpen: tokensAtOpen ?? null
|
|
8135
|
+
});
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
7759
8138
|
async function appendSession(_projectDir, session, opts) {
|
|
7760
8139
|
const db5 = getSessionDb();
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
8140
|
+
let openSnapshot = null;
|
|
8141
|
+
try {
|
|
8142
|
+
openSnapshot = await getCumulativeTokenSource()(session.sessionId);
|
|
8143
|
+
} catch {
|
|
8144
|
+
openSnapshot = null;
|
|
8145
|
+
}
|
|
8146
|
+
const upsert = db5.prepare(`
|
|
8147
|
+
INSERT INTO sessions (session_id, agent, started, status, path, description, transcript_path, pid, pid_started_at, original_head_sha)
|
|
8148
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7764
8149
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
7765
|
-
project_slug = COALESCE(NULLIF(excluded.project_slug, ''), project_slug),
|
|
7766
|
-
assignment_slug = COALESCE(NULLIF(excluded.assignment_slug, ''), assignment_slug),
|
|
7767
8150
|
agent = excluded.agent,
|
|
7768
8151
|
status = CASE
|
|
7769
8152
|
WHEN status = 'completed' THEN status
|
|
7770
8153
|
WHEN status = 'stopped' AND NOT (? AND excluded.status = 'active') THEN status
|
|
7771
8154
|
ELSE excluded.status
|
|
7772
8155
|
END,
|
|
8156
|
+
ended = CASE
|
|
8157
|
+
WHEN status = 'stopped' AND ? AND excluded.status = 'active' THEN NULL
|
|
8158
|
+
ELSE ended
|
|
8159
|
+
END,
|
|
7773
8160
|
path = COALESCE(NULLIF(excluded.path, ''), path),
|
|
7774
8161
|
description = COALESCE(NULLIF(excluded.description, ''), description),
|
|
7775
8162
|
transcript_path = COALESCE(NULLIF(excluded.transcript_path, ''), transcript_path),
|
|
@@ -7777,59 +8164,151 @@ async function appendSession(_projectDir, session, opts) {
|
|
|
7777
8164
|
pid_started_at = COALESCE(NULLIF(excluded.pid_started_at, ''), pid_started_at),
|
|
7778
8165
|
original_head_sha = COALESCE(NULLIF(original_head_sha, ''), NULLIF(excluded.original_head_sha, '')),
|
|
7779
8166
|
updated_at = datetime('now')
|
|
7780
|
-
`)
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
8167
|
+
`);
|
|
8168
|
+
const apply = db5.transaction(() => {
|
|
8169
|
+
upsert.run(
|
|
8170
|
+
session.sessionId,
|
|
8171
|
+
session.agent,
|
|
8172
|
+
session.started,
|
|
8173
|
+
session.status,
|
|
8174
|
+
session.path,
|
|
8175
|
+
session.description ?? null,
|
|
8176
|
+
session.transcriptPath ?? null,
|
|
8177
|
+
session.pid ?? null,
|
|
8178
|
+
session.pidStartedAt ?? null,
|
|
8179
|
+
session.originalHeadSha ?? null,
|
|
8180
|
+
opts?.reviveStopped ? 1 : 0,
|
|
8181
|
+
// status CASE: revive guard
|
|
8182
|
+
opts?.reviveStopped ? 1 : 0
|
|
8183
|
+
// ended CASE: clear stale ended on revive
|
|
8184
|
+
);
|
|
8185
|
+
const persisted = db5.prepare("SELECT status, ended FROM sessions WHERE session_id = ?").get(session.sessionId);
|
|
8186
|
+
const freshBinding = {
|
|
8187
|
+
assignmentId: session.assignmentId ?? null,
|
|
8188
|
+
projectSlug: session.projectSlug ?? null,
|
|
8189
|
+
assignmentSlug: session.assignmentSlug ?? null
|
|
8190
|
+
};
|
|
8191
|
+
if (persisted?.status === "active") {
|
|
8192
|
+
reopenEngagementIfMissing(
|
|
8193
|
+
session.sessionId,
|
|
8194
|
+
freshBinding,
|
|
8195
|
+
session.started ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8196
|
+
openSnapshot
|
|
8197
|
+
);
|
|
8198
|
+
} else if (persisted && (freshBinding.projectSlug || freshBinding.assignmentSlug) && !hasAnyEngagement(session.sessionId)) {
|
|
8199
|
+
insertClosedEngagement({
|
|
8200
|
+
sessionId: session.sessionId,
|
|
8201
|
+
projectSlug: freshBinding.projectSlug,
|
|
8202
|
+
assignmentSlug: freshBinding.assignmentSlug,
|
|
8203
|
+
startedAt: session.started ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8204
|
+
endedAt: persisted.ended ?? session.started ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8205
|
+
closeReason: persisted.status === "completed" ? "completed" : "abandoned"
|
|
8206
|
+
});
|
|
8207
|
+
}
|
|
8208
|
+
});
|
|
8209
|
+
apply.immediate();
|
|
7795
8210
|
}
|
|
7796
8211
|
async function updateSessionStatus(_projectDir, sessionId, status, endedAt) {
|
|
7797
8212
|
const db5 = getSessionDb();
|
|
7798
8213
|
const isTerminal = status === "completed" || status === "stopped";
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
8214
|
+
if (!isTerminal) {
|
|
8215
|
+
let reviveSnapshot = null;
|
|
8216
|
+
try {
|
|
8217
|
+
reviveSnapshot = await getCumulativeTokenSource()(sessionId);
|
|
8218
|
+
} catch {
|
|
8219
|
+
reviveSnapshot = null;
|
|
8220
|
+
}
|
|
8221
|
+
const apply2 = db5.transaction(() => {
|
|
8222
|
+
const current = db5.prepare("SELECT status FROM sessions WHERE session_id = ?").get(sessionId);
|
|
8223
|
+
if (!current) return false;
|
|
8224
|
+
if (current.status === "completed") {
|
|
8225
|
+
throw new SessionResurrectionError(
|
|
8226
|
+
`Refusing to revive completed session "${sessionId}" to active. A completed session is final.`
|
|
8227
|
+
);
|
|
8228
|
+
}
|
|
8229
|
+
const res = db5.prepare(
|
|
8230
|
+
"UPDATE sessions SET status = ?, ended = NULL, updated_at = datetime('now') WHERE session_id = ?"
|
|
8231
|
+
).run(status, sessionId);
|
|
8232
|
+
if (res.changes > 0) {
|
|
8233
|
+
reopenEngagementIfMissing(sessionId, null, (/* @__PURE__ */ new Date()).toISOString(), reviveSnapshot);
|
|
8234
|
+
}
|
|
8235
|
+
return res.changes > 0;
|
|
8236
|
+
});
|
|
8237
|
+
return apply2.immediate();
|
|
8238
|
+
}
|
|
8239
|
+
const captured = getOpenEngagement(sessionId);
|
|
8240
|
+
let snapshot = null;
|
|
8241
|
+
try {
|
|
8242
|
+
snapshot = await getCumulativeTokenSource()(sessionId);
|
|
8243
|
+
} catch {
|
|
8244
|
+
snapshot = null;
|
|
8245
|
+
}
|
|
8246
|
+
const apply = db5.transaction(() => {
|
|
8247
|
+
const stillTerminal = captured ? closeEngagementById({
|
|
8248
|
+
id: captured.id,
|
|
8249
|
+
startedAt: captured.started_at,
|
|
8250
|
+
closeReason: status === "completed" ? "completed" : "abandoned",
|
|
8251
|
+
tokensAtClose: snapshot,
|
|
8252
|
+
endedAt: endedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
8253
|
+
}) : getOpenEngagement(sessionId) === null;
|
|
8254
|
+
if (!stillTerminal) return false;
|
|
8255
|
+
const res = db5.prepare(
|
|
8256
|
+
"UPDATE sessions SET status = ?, ended = COALESCE(?, datetime('now')), updated_at = datetime('now') WHERE session_id = ?"
|
|
8257
|
+
).run(status, endedAt ?? null, sessionId);
|
|
8258
|
+
return res.changes > 0;
|
|
8259
|
+
});
|
|
8260
|
+
return apply.immediate();
|
|
8261
|
+
}
|
|
8262
|
+
function livenessStopSession(input) {
|
|
8263
|
+
const db5 = getSessionDb();
|
|
8264
|
+
const endedAt = input.endedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
8265
|
+
const apply = db5.transaction(() => {
|
|
8266
|
+
const stillDead = input.engagementId != null && input.engagementStartedAt != null ? closeEngagementById({
|
|
8267
|
+
id: input.engagementId,
|
|
8268
|
+
startedAt: input.engagementStartedAt,
|
|
8269
|
+
closeReason: "liveness_gc",
|
|
8270
|
+
tokensAtClose: input.tokensAtClose ?? null,
|
|
8271
|
+
endedAt
|
|
8272
|
+
}) : getOpenEngagement(input.sessionId) === null;
|
|
8273
|
+
if (!stillDead) return false;
|
|
8274
|
+
const res = db5.prepare(
|
|
8275
|
+
"UPDATE sessions SET status = 'stopped', ended = COALESCE(?, datetime('now')), updated_at = datetime('now') WHERE session_id = ? AND status = 'active'"
|
|
8276
|
+
).run(input.endedAt ?? null, input.sessionId);
|
|
8277
|
+
return res.changes > 0;
|
|
8278
|
+
});
|
|
8279
|
+
return apply.immediate();
|
|
7805
8280
|
}
|
|
7806
8281
|
async function listAllSessions(_projectsDir) {
|
|
7807
8282
|
const db5 = getSessionDb();
|
|
7808
|
-
const rows = db5.prepare(
|
|
8283
|
+
const rows = db5.prepare(`${SESSION_SELECT_WITH_BINDING} ORDER BY s.started DESC`).all();
|
|
7809
8284
|
return rows.map(rowToSession);
|
|
7810
8285
|
}
|
|
7811
8286
|
function getSessionById(sessionId) {
|
|
7812
8287
|
const db5 = getSessionDb();
|
|
7813
|
-
const row = db5.prepare(
|
|
8288
|
+
const row = db5.prepare(`${SESSION_SELECT_WITH_BINDING} WHERE s.session_id = ? LIMIT 1`).get(sessionId);
|
|
7814
8289
|
return row ? rowToSession(row) : null;
|
|
7815
8290
|
}
|
|
7816
8291
|
async function listProjectSessions(_projectsDir, projectSlug, assignmentSlug) {
|
|
7817
8292
|
const db5 = getSessionDb();
|
|
7818
8293
|
if (assignmentSlug) {
|
|
7819
8294
|
const rows2 = db5.prepare(
|
|
7820
|
-
|
|
8295
|
+
`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? AND e.assignment_slug = ? ORDER BY s.started DESC`
|
|
7821
8296
|
).all(projectSlug, assignmentSlug);
|
|
7822
8297
|
return rows2.map(rowToSession);
|
|
7823
8298
|
}
|
|
7824
|
-
const rows = db5.prepare(
|
|
8299
|
+
const rows = db5.prepare(`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? ORDER BY s.started DESC`).all(projectSlug);
|
|
7825
8300
|
return rows.map(rowToSession);
|
|
7826
8301
|
}
|
|
7827
8302
|
async function deleteSessions(sessionIds) {
|
|
7828
8303
|
if (sessionIds.length === 0) return 0;
|
|
7829
8304
|
const db5 = getSessionDb();
|
|
7830
8305
|
const placeholders = sessionIds.map(() => "?").join(", ");
|
|
7831
|
-
const
|
|
7832
|
-
|
|
8306
|
+
const run3 = db5.transaction(() => {
|
|
8307
|
+
db5.prepare(`DELETE FROM engagement WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
|
8308
|
+
const result = db5.prepare(`DELETE FROM sessions WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
|
8309
|
+
return result.changes;
|
|
8310
|
+
});
|
|
8311
|
+
return run3.immediate();
|
|
7833
8312
|
}
|
|
7834
8313
|
async function readAssignmentStatusFromPath(assignmentMdPath) {
|
|
7835
8314
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
@@ -7839,39 +8318,44 @@ async function readAssignmentStatusFromPath(assignmentMdPath) {
|
|
|
7839
8318
|
}
|
|
7840
8319
|
async function readAssignmentStatus(projectDir, assignmentSlug) {
|
|
7841
8320
|
return readAssignmentStatusFromPath(
|
|
7842
|
-
|
|
8321
|
+
resolve15(projectDir, "assignments", assignmentSlug, "assignment.md")
|
|
7843
8322
|
);
|
|
7844
8323
|
}
|
|
7845
8324
|
async function reconcileActiveSessions(projectsDir, assignmentsDir2) {
|
|
7846
8325
|
const db5 = getSessionDb();
|
|
7847
|
-
const activeSessions = db5.prepare(
|
|
8326
|
+
const activeSessions = db5.prepare(
|
|
8327
|
+
`SELECT s.*, e.project_slug AS project_slug, e.assignment_slug AS assignment_slug
|
|
8328
|
+
FROM sessions s
|
|
8329
|
+
JOIN engagement e ON e.session_id = s.session_id AND e.ended_at IS NULL
|
|
8330
|
+
WHERE s.status = 'active' AND e.assignment_slug IS NOT NULL`
|
|
8331
|
+
).all();
|
|
7848
8332
|
if (activeSessions.length === 0) return 0;
|
|
7849
8333
|
const assignmentStatuses = /* @__PURE__ */ new Map();
|
|
7850
8334
|
const seen = /* @__PURE__ */ new Set();
|
|
7851
8335
|
for (const session of activeSessions) {
|
|
7852
8336
|
const aslug = session.assignment_slug;
|
|
7853
8337
|
if (!aslug) continue;
|
|
7854
|
-
const
|
|
7855
|
-
const key = `${
|
|
8338
|
+
const projectKey2 = session.project_slug ?? "__standalone__";
|
|
8339
|
+
const key = `${projectKey2}/${aslug}`;
|
|
7856
8340
|
if (seen.has(key)) continue;
|
|
7857
8341
|
seen.add(key);
|
|
7858
8342
|
if (session.project_slug) {
|
|
7859
8343
|
const status = await readAssignmentStatus(
|
|
7860
|
-
|
|
8344
|
+
resolve15(projectsDir, session.project_slug),
|
|
7861
8345
|
aslug
|
|
7862
8346
|
);
|
|
7863
8347
|
if (status) assignmentStatuses.set(key, status);
|
|
7864
8348
|
} else if (assignmentsDir2) {
|
|
7865
8349
|
const status = await readAssignmentStatusFromPath(
|
|
7866
|
-
|
|
8350
|
+
resolve15(assignmentsDir2, aslug, "assignment.md")
|
|
7867
8351
|
);
|
|
7868
8352
|
if (status) assignmentStatuses.set(key, status);
|
|
7869
8353
|
}
|
|
7870
8354
|
}
|
|
7871
8355
|
let totalUpdated = 0;
|
|
7872
8356
|
for (const session of activeSessions) {
|
|
7873
|
-
const
|
|
7874
|
-
const key = `${
|
|
8357
|
+
const projectKey2 = session.project_slug ?? "__standalone__";
|
|
8358
|
+
const key = `${projectKey2}/${session.assignment_slug}`;
|
|
7875
8359
|
const assignmentStatus = assignmentStatuses.get(key);
|
|
7876
8360
|
if (!assignmentStatus || !DONE_ASSIGNMENT_STATUSES.has(assignmentStatus)) continue;
|
|
7877
8361
|
const newStatus = assignmentStatus === "failed" ? "stopped" : "completed";
|
|
@@ -7883,18 +8367,34 @@ async function reconcileActiveSessions(projectsDir, assignmentsDir2) {
|
|
|
7883
8367
|
async function listSessionsByAssignment(projectSlug, assignmentSlug) {
|
|
7884
8368
|
const db5 = getSessionDb();
|
|
7885
8369
|
const rows = projectSlug === null ? db5.prepare(
|
|
7886
|
-
|
|
8370
|
+
`${SESSION_SELECT_WITH_BINDING} WHERE e.assignment_slug = ? AND e.project_slug IS NULL ORDER BY s.started DESC`
|
|
7887
8371
|
).all(assignmentSlug) : db5.prepare(
|
|
7888
|
-
|
|
8372
|
+
`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? AND e.assignment_slug = ? ORDER BY s.started DESC`
|
|
7889
8373
|
).all(projectSlug, assignmentSlug);
|
|
7890
8374
|
return rows.map(rowToSession);
|
|
7891
8375
|
}
|
|
7892
|
-
var DONE_ASSIGNMENT_STATUSES;
|
|
8376
|
+
var SESSION_SELECT_WITH_BINDING, SessionResurrectionError, DONE_ASSIGNMENT_STATUSES;
|
|
7893
8377
|
var init_agent_sessions = __esm({
|
|
7894
8378
|
"src/dashboard/agent-sessions.ts"() {
|
|
7895
8379
|
"use strict";
|
|
7896
8380
|
init_fs();
|
|
7897
8381
|
init_session_db();
|
|
8382
|
+
init_engagement_db();
|
|
8383
|
+
init_engagement_tokens();
|
|
8384
|
+
SESSION_SELECT_WITH_BINDING = `
|
|
8385
|
+
SELECT s.*,
|
|
8386
|
+
e.project_slug AS project_slug,
|
|
8387
|
+
e.assignment_slug AS assignment_slug,
|
|
8388
|
+
e.assignment_id AS assignment_id
|
|
8389
|
+
FROM sessions s
|
|
8390
|
+
LEFT JOIN engagement e ON e.id = (
|
|
8391
|
+
SELECT e2.id FROM engagement e2
|
|
8392
|
+
WHERE e2.session_id = s.session_id
|
|
8393
|
+
ORDER BY (e2.ended_at IS NULL) DESC, e2.started_at DESC, e2.id DESC
|
|
8394
|
+
LIMIT 1
|
|
8395
|
+
)`;
|
|
8396
|
+
SessionResurrectionError = class extends Error {
|
|
8397
|
+
};
|
|
7898
8398
|
DONE_ASSIGNMENT_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "review"]);
|
|
7899
8399
|
}
|
|
7900
8400
|
});
|
|
@@ -7987,7 +8487,7 @@ var init_classify = __esm({
|
|
|
7987
8487
|
|
|
7988
8488
|
// src/dashboard/servers.ts
|
|
7989
8489
|
import { readdir as readdir9, readFile as readFile12, unlink as unlink2 } from "fs/promises";
|
|
7990
|
-
import { resolve as
|
|
8490
|
+
import { resolve as resolve16 } from "path";
|
|
7991
8491
|
function sanitizeSessionName(name) {
|
|
7992
8492
|
return name.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
7993
8493
|
}
|
|
@@ -8035,7 +8535,7 @@ async function registerSession(dir, rawName) {
|
|
|
8035
8535
|
lastRefreshed: now,
|
|
8036
8536
|
overrides: {}
|
|
8037
8537
|
});
|
|
8038
|
-
await writeFileForce(
|
|
8538
|
+
await writeFileForce(resolve16(dir, `${name}.md`), content);
|
|
8039
8539
|
return name;
|
|
8040
8540
|
}
|
|
8041
8541
|
async function listSessionFiles(dir) {
|
|
@@ -8044,7 +8544,7 @@ async function listSessionFiles(dir) {
|
|
|
8044
8544
|
return entries.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
|
|
8045
8545
|
}
|
|
8046
8546
|
async function readSessionFile(dir, name) {
|
|
8047
|
-
const filePath =
|
|
8547
|
+
const filePath = resolve16(dir, `${sanitizeSessionName(name)}.md`);
|
|
8048
8548
|
if (!await fileExists(filePath)) return null;
|
|
8049
8549
|
const raw2 = await readFile12(filePath, "utf-8");
|
|
8050
8550
|
const [frontmatter] = extractFrontmatter2(raw2);
|
|
@@ -8086,7 +8586,7 @@ async function readSessionFile(dir, name) {
|
|
|
8086
8586
|
};
|
|
8087
8587
|
}
|
|
8088
8588
|
async function removeSession(dir, name) {
|
|
8089
|
-
const filePath =
|
|
8589
|
+
const filePath = resolve16(dir, `${sanitizeSessionName(name)}.md`);
|
|
8090
8590
|
if (await fileExists(filePath)) {
|
|
8091
8591
|
await unlink2(filePath);
|
|
8092
8592
|
}
|
|
@@ -8095,7 +8595,7 @@ async function updateLastRefreshed(dir, name) {
|
|
|
8095
8595
|
const data = await readSessionFile(dir, name);
|
|
8096
8596
|
if (!data) return;
|
|
8097
8597
|
const content = buildSessionContent({ ...data, lastRefreshed: nowTimestamp2() });
|
|
8098
|
-
await writeFileForce(
|
|
8598
|
+
await writeFileForce(resolve16(dir, `${sanitizeSessionName(name)}.md`), content);
|
|
8099
8599
|
}
|
|
8100
8600
|
async function setOverride(dir, sessionName, windowIndex, paneIndex, assignment) {
|
|
8101
8601
|
const data = await readSessionFile(dir, sessionName);
|
|
@@ -8107,7 +8607,7 @@ async function setOverride(dir, sessionName, windowIndex, paneIndex, assignment)
|
|
|
8107
8607
|
delete data.overrides[key];
|
|
8108
8608
|
}
|
|
8109
8609
|
const content = buildSessionContent({ ...data });
|
|
8110
|
-
await writeFileForce(
|
|
8610
|
+
await writeFileForce(resolve16(dir, `${sanitizeSessionName(sessionName)}.md`), content);
|
|
8111
8611
|
}
|
|
8112
8612
|
async function registerAutoSession(dir, rawName, opts) {
|
|
8113
8613
|
const name = sanitizeSessionName(rawName);
|
|
@@ -8124,7 +8624,7 @@ async function registerAutoSession(dir, rawName, opts) {
|
|
|
8124
8624
|
ports: opts.ports,
|
|
8125
8625
|
cwd: opts.cwd
|
|
8126
8626
|
});
|
|
8127
|
-
await writeFileForce(
|
|
8627
|
+
await writeFileForce(resolve16(dir, `${name}.md`), content);
|
|
8128
8628
|
return name;
|
|
8129
8629
|
}
|
|
8130
8630
|
var init_servers = __esm({
|
|
@@ -8159,7 +8659,7 @@ __export(scanner_exports, {
|
|
|
8159
8659
|
});
|
|
8160
8660
|
import { execFile } from "child_process";
|
|
8161
8661
|
import { promisify } from "util";
|
|
8162
|
-
import { resolve as
|
|
8662
|
+
import { resolve as resolve17 } from "path";
|
|
8163
8663
|
import { realpath } from "fs/promises";
|
|
8164
8664
|
function clearScanCache() {
|
|
8165
8665
|
cache2 = null;
|
|
@@ -8175,8 +8675,8 @@ function scanKey(serversDir2, projectsDir, assignmentsDir2) {
|
|
|
8175
8675
|
return `${serversDir2}\0${projectsDir}\0${assignmentsDir2 ?? ""}`;
|
|
8176
8676
|
}
|
|
8177
8677
|
function delay(ms) {
|
|
8178
|
-
return new Promise((
|
|
8179
|
-
const timer3 = setTimeout(
|
|
8678
|
+
return new Promise((resolve49) => {
|
|
8679
|
+
const timer3 = setTimeout(resolve49, ms);
|
|
8180
8680
|
if (typeof timer3.unref === "function") {
|
|
8181
8681
|
timer3.unref();
|
|
8182
8682
|
}
|
|
@@ -8312,8 +8812,8 @@ async function getGitInfo(cwd) {
|
|
|
8312
8812
|
let isWorktree = false;
|
|
8313
8813
|
if (commonDir && gitDir && commonDir !== gitDir) {
|
|
8314
8814
|
try {
|
|
8315
|
-
const resolvedCommon = await realpath(
|
|
8316
|
-
const resolvedGit = await realpath(
|
|
8815
|
+
const resolvedCommon = await realpath(resolve17(cwd, commonDir));
|
|
8816
|
+
const resolvedGit = await realpath(resolve17(cwd, gitDir));
|
|
8317
8817
|
isWorktree = resolvedCommon !== resolvedGit;
|
|
8318
8818
|
} catch {
|
|
8319
8819
|
isWorktree = false;
|
|
@@ -8415,16 +8915,16 @@ async function scanSession(sessionData, lsofOutput, workspaceRecords, procSnapsh
|
|
|
8415
8915
|
const gitInfo = gitInfoCache.get(rp.cwd) ?? { branch: null, worktree: false };
|
|
8416
8916
|
const ports = panePorts.get(key) ?? [];
|
|
8417
8917
|
const urls = ports.map((p) => `http://localhost:${p}`);
|
|
8418
|
-
const
|
|
8918
|
+
const override3 = sessionData.overrides[key];
|
|
8419
8919
|
let assignment = null;
|
|
8420
|
-
if (
|
|
8920
|
+
if (override3) {
|
|
8421
8921
|
const rec = workspaceRecords.find(
|
|
8422
|
-
(r) => r.projectSlug ===
|
|
8922
|
+
(r) => r.projectSlug === override3.project && r.assignmentSlug === override3.assignment
|
|
8423
8923
|
);
|
|
8424
8924
|
assignment = {
|
|
8425
|
-
project:
|
|
8426
|
-
slug:
|
|
8427
|
-
title: rec?.assignmentTitle ??
|
|
8925
|
+
project: override3.project,
|
|
8926
|
+
slug: override3.assignment,
|
|
8927
|
+
title: rec?.assignmentTitle ?? override3.assignment
|
|
8428
8928
|
};
|
|
8429
8929
|
} else {
|
|
8430
8930
|
assignment = await autoLinkPane(rp.cwd, gitInfo.branch, workspaceRecords);
|
|
@@ -8478,16 +8978,16 @@ async function scanProcessSession(sessionData, lsofOutput, workspaceRecords) {
|
|
|
8478
8978
|
}
|
|
8479
8979
|
const ports = findListeningPorts(lsofOutput, /* @__PURE__ */ new Set([sessionData.pid]));
|
|
8480
8980
|
const gitInfo = await getGitInfo(sessionData.cwd);
|
|
8481
|
-
const
|
|
8981
|
+
const override3 = sessionData.overrides["0:0"];
|
|
8482
8982
|
let assignment = null;
|
|
8483
|
-
if (
|
|
8983
|
+
if (override3) {
|
|
8484
8984
|
const rec = workspaceRecords.find(
|
|
8485
|
-
(r) => r.projectSlug ===
|
|
8985
|
+
(r) => r.projectSlug === override3.project && r.assignmentSlug === override3.assignment
|
|
8486
8986
|
);
|
|
8487
8987
|
assignment = {
|
|
8488
|
-
project:
|
|
8489
|
-
slug:
|
|
8490
|
-
title: rec?.assignmentTitle ??
|
|
8988
|
+
project: override3.project,
|
|
8989
|
+
slug: override3.assignment,
|
|
8990
|
+
title: rec?.assignmentTitle ?? override3.assignment
|
|
8491
8991
|
};
|
|
8492
8992
|
} else {
|
|
8493
8993
|
assignment = await autoLinkPane(sessionData.cwd, gitInfo.branch, workspaceRecords);
|
|
@@ -8651,7 +9151,7 @@ __export(api_exports, {
|
|
|
8651
9151
|
resolveWorkspaceMembers: () => resolveWorkspaceMembers
|
|
8652
9152
|
});
|
|
8653
9153
|
import { readdir as readdir10, readFile as readFile13, writeFile as writeFile3, stat as stat2 } from "fs/promises";
|
|
8654
|
-
import { resolve as
|
|
9154
|
+
import { resolve as resolve18, dirname as dirname2, basename } from "path";
|
|
8655
9155
|
function clearFrontmatterField(content, key) {
|
|
8656
9156
|
const fieldRegex = new RegExp(`^(${escapeRegExp2(key)}:)\\s*.*$`, "m");
|
|
8657
9157
|
return content.replace(fieldRegex, `$1 null`);
|
|
@@ -8735,8 +9235,8 @@ async function computeStandaloneRecords(assignmentsDir2) {
|
|
|
8735
9235
|
const records = [];
|
|
8736
9236
|
for (const entry of entries) {
|
|
8737
9237
|
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
|
|
8738
|
-
const assignmentDir =
|
|
8739
|
-
const assignmentMdPath =
|
|
9238
|
+
const assignmentDir = resolve18(assignmentsDir2, entry.name);
|
|
9239
|
+
const assignmentMdPath = resolve18(assignmentDir, "assignment.md");
|
|
8740
9240
|
if (!await fileExists(assignmentMdPath)) continue;
|
|
8741
9241
|
try {
|
|
8742
9242
|
const content = await readFile13(assignmentMdPath, "utf-8");
|
|
@@ -8823,7 +9323,7 @@ async function listProjects(projectsDir) {
|
|
|
8823
9323
|
return projectRecords.filter((record) => !isProjectArchived(record.summary)).map((record) => record.summary);
|
|
8824
9324
|
}
|
|
8825
9325
|
async function readWorkspaceRegistry(projectsDir) {
|
|
8826
|
-
const registryPath =
|
|
9326
|
+
const registryPath = resolve18(dirname2(projectsDir), "workspaces.json");
|
|
8827
9327
|
try {
|
|
8828
9328
|
const raw2 = await readFile13(registryPath, "utf-8");
|
|
8829
9329
|
const parsed = JSON.parse(raw2);
|
|
@@ -8833,7 +9333,7 @@ async function readWorkspaceRegistry(projectsDir) {
|
|
|
8833
9333
|
}
|
|
8834
9334
|
}
|
|
8835
9335
|
async function writeWorkspaceRegistry(projectsDir, workspaces) {
|
|
8836
|
-
const registryPath =
|
|
9336
|
+
const registryPath = resolve18(dirname2(projectsDir), "workspaces.json");
|
|
8837
9337
|
await writeFile3(registryPath, JSON.stringify(workspaces, null, 2) + "\n", "utf-8");
|
|
8838
9338
|
}
|
|
8839
9339
|
async function listWorkspaces(projectsDir, assignmentsDir2) {
|
|
@@ -8924,7 +9424,7 @@ async function deleteWorkspace(projectsDir, name, opts = {}) {
|
|
|
8924
9424
|
if (cascade) {
|
|
8925
9425
|
const timestamp = nowTimestamp();
|
|
8926
9426
|
for (const slug of projectsReferencing) {
|
|
8927
|
-
const path =
|
|
9427
|
+
const path = resolve18(projectsDir, slug, "project.md");
|
|
8928
9428
|
const raw2 = await readFile13(path, "utf-8");
|
|
8929
9429
|
let next = clearFrontmatterField(raw2, "workspace");
|
|
8930
9430
|
next = setUpdatedField(next, timestamp);
|
|
@@ -8933,7 +9433,7 @@ async function deleteWorkspace(projectsDir, name, opts = {}) {
|
|
|
8933
9433
|
}
|
|
8934
9434
|
for (const id of standalonesReferencing) {
|
|
8935
9435
|
if (!opts.assignmentsDir) break;
|
|
8936
|
-
const path =
|
|
9436
|
+
const path = resolve18(opts.assignmentsDir, id, "assignment.md");
|
|
8937
9437
|
const raw2 = await readFile13(path, "utf-8");
|
|
8938
9438
|
let next = clearFrontmatterField(raw2, "workspaceGroup");
|
|
8939
9439
|
next = setUpdatedField(next, timestamp);
|
|
@@ -9212,7 +9712,7 @@ async function getEditableDocumentById(projectsDir, assignmentsDir2, documentTyp
|
|
|
9212
9712
|
}
|
|
9213
9713
|
const fileName = documentType === "assignment" ? "assignment.md" : documentType === "plan" ? "plan.md" : documentType === "scratchpad" ? "scratchpad.md" : documentType === "handoff" ? "handoff.md" : documentType === "decision-record" ? "decision-record.md" : null;
|
|
9214
9714
|
if (!fileName) return null;
|
|
9215
|
-
const filePath =
|
|
9715
|
+
const filePath = resolve18(resolved.assignmentDir, fileName);
|
|
9216
9716
|
if (!await fileExists(filePath)) return null;
|
|
9217
9717
|
const content = await readFile13(filePath, "utf-8");
|
|
9218
9718
|
const label = resolved.id;
|
|
@@ -9228,8 +9728,8 @@ async function getEditableDocumentById(projectsDir, assignmentsDir2, documentTyp
|
|
|
9228
9728
|
};
|
|
9229
9729
|
}
|
|
9230
9730
|
async function getProjectDetail(projectsDir, slug) {
|
|
9231
|
-
const projectPath =
|
|
9232
|
-
const projectMdPath =
|
|
9731
|
+
const projectPath = resolve18(projectsDir, slug);
|
|
9732
|
+
const projectMdPath = resolve18(projectPath, "project.md");
|
|
9233
9733
|
if (!await fileExists(projectMdPath)) {
|
|
9234
9734
|
return null;
|
|
9235
9735
|
}
|
|
@@ -9266,15 +9766,15 @@ async function getProjectDetail(projectsDir, slug) {
|
|
|
9266
9766
|
};
|
|
9267
9767
|
}
|
|
9268
9768
|
async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
9269
|
-
const assignmentDir =
|
|
9270
|
-
const assignmentMdPath =
|
|
9769
|
+
const assignmentDir = resolve18(projectsDir, projectSlug, "assignments", assignmentSlug);
|
|
9770
|
+
const assignmentMdPath = resolve18(assignmentDir, "assignment.md");
|
|
9271
9771
|
if (!await fileExists(assignmentMdPath)) {
|
|
9272
9772
|
return null;
|
|
9273
9773
|
}
|
|
9274
9774
|
const assignmentContent = await readFile13(assignmentMdPath, "utf-8");
|
|
9275
9775
|
const assignment = parseAssignmentFull(assignmentContent);
|
|
9276
9776
|
let projectWorkspace = null;
|
|
9277
|
-
const projectMdPath =
|
|
9777
|
+
const projectMdPath = resolve18(projectsDir, projectSlug, "project.md");
|
|
9278
9778
|
if (await fileExists(projectMdPath)) {
|
|
9279
9779
|
const projectContent = await readFile13(projectMdPath, "utf-8");
|
|
9280
9780
|
projectWorkspace = parseProject(projectContent).workspace;
|
|
@@ -9282,7 +9782,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
9282
9782
|
let plan = null;
|
|
9283
9783
|
const planFile = await latestPlanFile(assignmentDir);
|
|
9284
9784
|
if (planFile) {
|
|
9285
|
-
const planPath =
|
|
9785
|
+
const planPath = resolve18(assignmentDir, planFile);
|
|
9286
9786
|
if (await fileExists(planPath)) {
|
|
9287
9787
|
const planContent = await readFile13(planPath, "utf-8");
|
|
9288
9788
|
const parsed = parsePlan(planContent);
|
|
@@ -9294,7 +9794,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
9294
9794
|
}
|
|
9295
9795
|
}
|
|
9296
9796
|
let scratchpad = null;
|
|
9297
|
-
const scratchpadPath =
|
|
9797
|
+
const scratchpadPath = resolve18(assignmentDir, "scratchpad.md");
|
|
9298
9798
|
if (await fileExists(scratchpadPath)) {
|
|
9299
9799
|
const scratchpadContent = await readFile13(scratchpadPath, "utf-8");
|
|
9300
9800
|
const parsed = parseScratchpad(scratchpadContent);
|
|
@@ -9304,7 +9804,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
9304
9804
|
};
|
|
9305
9805
|
}
|
|
9306
9806
|
let handoff = null;
|
|
9307
|
-
const handoffPath =
|
|
9807
|
+
const handoffPath = resolve18(assignmentDir, "handoff.md");
|
|
9308
9808
|
if (await fileExists(handoffPath)) {
|
|
9309
9809
|
const handoffContent = await readFile13(handoffPath, "utf-8");
|
|
9310
9810
|
const parsed = parseHandoff(handoffContent);
|
|
@@ -9315,7 +9815,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
9315
9815
|
};
|
|
9316
9816
|
}
|
|
9317
9817
|
let decisionRecord = null;
|
|
9318
|
-
const decisionRecordPath =
|
|
9818
|
+
const decisionRecordPath = resolve18(assignmentDir, "decision-record.md");
|
|
9319
9819
|
if (await fileExists(decisionRecordPath)) {
|
|
9320
9820
|
const decisionRecordContent = await readFile13(decisionRecordPath, "utf-8");
|
|
9321
9821
|
const parsed = parseDecisionRecord(decisionRecordContent);
|
|
@@ -9326,7 +9826,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
9326
9826
|
};
|
|
9327
9827
|
}
|
|
9328
9828
|
let progress = null;
|
|
9329
|
-
const progressPath =
|
|
9829
|
+
const progressPath = resolve18(assignmentDir, "progress.md");
|
|
9330
9830
|
if (await fileExists(progressPath)) {
|
|
9331
9831
|
const progressContent = await readFile13(progressPath, "utf-8");
|
|
9332
9832
|
const parsed = parseProgress(progressContent);
|
|
@@ -9337,7 +9837,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
9337
9837
|
};
|
|
9338
9838
|
}
|
|
9339
9839
|
let comments = null;
|
|
9340
|
-
const commentsPath =
|
|
9840
|
+
const commentsPath = resolve18(assignmentDir, "comments.md");
|
|
9341
9841
|
if (await fileExists(commentsPath)) {
|
|
9342
9842
|
const commentsContent = await readFile13(commentsPath, "utf-8");
|
|
9343
9843
|
const parsed = parseComments(commentsContent);
|
|
@@ -9371,7 +9871,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
9371
9871
|
archivedReason: assignment.archivedReason,
|
|
9372
9872
|
...deriveStatusVirtuals(assignment, terminalStatuses),
|
|
9373
9873
|
override: assignment.override,
|
|
9374
|
-
derived: await buildDerivedDetail(assignment, assignmentDir,
|
|
9874
|
+
derived: await buildDerivedDetail(assignment, assignmentDir, resolve18(projectsDir, projectSlug)),
|
|
9375
9875
|
created: assignment.created,
|
|
9376
9876
|
updated: assignment.updated,
|
|
9377
9877
|
body: assignment.body,
|
|
@@ -9462,7 +9962,7 @@ async function computeReferencedBy(target, projectsDir, assignmentsDir2) {
|
|
|
9462
9962
|
slug: a.slug,
|
|
9463
9963
|
title: a.title,
|
|
9464
9964
|
projectSlug: rec.summary.slug,
|
|
9465
|
-
assignmentDir:
|
|
9965
|
+
assignmentDir: resolve18(rec.projectPath, "assignments", a.slug)
|
|
9466
9966
|
});
|
|
9467
9967
|
}
|
|
9468
9968
|
}
|
|
@@ -9495,14 +9995,14 @@ async function computeReferencedBy(target, projectsDir, assignmentsDir2) {
|
|
|
9495
9995
|
}
|
|
9496
9996
|
async function countMentionsInAssignment(sourceDir, target) {
|
|
9497
9997
|
const bodies = [];
|
|
9498
|
-
const assignmentMd =
|
|
9998
|
+
const assignmentMd = resolve18(sourceDir, "assignment.md");
|
|
9499
9999
|
if (await fileExists(assignmentMd)) {
|
|
9500
10000
|
const content = await readFile13(assignmentMd, "utf-8");
|
|
9501
10001
|
const todosMatch = content.match(/^## Todos\s*$([\s\S]*?)(?=^## |$(?![\r\n]))/m);
|
|
9502
10002
|
if (todosMatch) bodies.push(todosMatch[1]);
|
|
9503
10003
|
}
|
|
9504
10004
|
for (const filename of ["progress.md", "comments.md", "handoff.md"]) {
|
|
9505
|
-
const path =
|
|
10005
|
+
const path = resolve18(sourceDir, filename);
|
|
9506
10006
|
if (await fileExists(path)) {
|
|
9507
10007
|
try {
|
|
9508
10008
|
bodies.push(await readFile13(path, "utf-8"));
|
|
@@ -9563,45 +10063,45 @@ async function getAssignmentDetailById(projectsDir, assignmentsDir2, id) {
|
|
|
9563
10063
|
}
|
|
9564
10064
|
async function buildStandaloneAssignmentDetail(resolved) {
|
|
9565
10065
|
const assignmentDir = resolved.assignmentDir;
|
|
9566
|
-
const assignmentMdPath =
|
|
10066
|
+
const assignmentMdPath = resolve18(assignmentDir, "assignment.md");
|
|
9567
10067
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
9568
10068
|
const assignmentContent = await readFile13(assignmentMdPath, "utf-8");
|
|
9569
10069
|
const assignment = parseAssignmentFull(assignmentContent);
|
|
9570
10070
|
let plan = null;
|
|
9571
10071
|
const planFile = await latestPlanFile(assignmentDir);
|
|
9572
10072
|
if (planFile) {
|
|
9573
|
-
const planPath =
|
|
10073
|
+
const planPath = resolve18(assignmentDir, planFile);
|
|
9574
10074
|
if (await fileExists(planPath)) {
|
|
9575
10075
|
const parsed = parsePlan(await readFile13(planPath, "utf-8"));
|
|
9576
10076
|
plan = { status: parsed.status, updated: parsed.updated, body: parsed.body };
|
|
9577
10077
|
}
|
|
9578
10078
|
}
|
|
9579
10079
|
let scratchpad = null;
|
|
9580
|
-
const scratchpadPath =
|
|
10080
|
+
const scratchpadPath = resolve18(assignmentDir, "scratchpad.md");
|
|
9581
10081
|
if (await fileExists(scratchpadPath)) {
|
|
9582
10082
|
const parsed = parseScratchpad(await readFile13(scratchpadPath, "utf-8"));
|
|
9583
10083
|
scratchpad = { updated: parsed.updated, body: parsed.body };
|
|
9584
10084
|
}
|
|
9585
10085
|
let handoff = null;
|
|
9586
|
-
const handoffPath =
|
|
10086
|
+
const handoffPath = resolve18(assignmentDir, "handoff.md");
|
|
9587
10087
|
if (await fileExists(handoffPath)) {
|
|
9588
10088
|
const parsed = parseHandoff(await readFile13(handoffPath, "utf-8"));
|
|
9589
10089
|
handoff = { updated: parsed.updated, handoffCount: parsed.handoffCount, body: parsed.body };
|
|
9590
10090
|
}
|
|
9591
10091
|
let decisionRecord = null;
|
|
9592
|
-
const decisionRecordPath =
|
|
10092
|
+
const decisionRecordPath = resolve18(assignmentDir, "decision-record.md");
|
|
9593
10093
|
if (await fileExists(decisionRecordPath)) {
|
|
9594
10094
|
const parsed = parseDecisionRecord(await readFile13(decisionRecordPath, "utf-8"));
|
|
9595
10095
|
decisionRecord = { updated: parsed.updated, decisionCount: parsed.decisionCount, body: parsed.body };
|
|
9596
10096
|
}
|
|
9597
10097
|
let progress = null;
|
|
9598
|
-
const progressPath =
|
|
10098
|
+
const progressPath = resolve18(assignmentDir, "progress.md");
|
|
9599
10099
|
if (await fileExists(progressPath)) {
|
|
9600
10100
|
const parsed = parseProgress(await readFile13(progressPath, "utf-8"));
|
|
9601
10101
|
progress = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
|
|
9602
10102
|
}
|
|
9603
10103
|
let comments = null;
|
|
9604
|
-
const commentsPath =
|
|
10104
|
+
const commentsPath = resolve18(assignmentDir, "comments.md");
|
|
9605
10105
|
if (await fileExists(commentsPath)) {
|
|
9606
10106
|
const parsed = parseComments(await readFile13(commentsPath, "utf-8"));
|
|
9607
10107
|
comments = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
|
|
@@ -9667,8 +10167,8 @@ async function computeProjectRecords(projectsDir, traces) {
|
|
|
9667
10167
|
const projectDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
|
|
9668
10168
|
const maybeRecords = await Promise.all(
|
|
9669
10169
|
projectDirs.map(async (entry) => {
|
|
9670
|
-
const projectPath =
|
|
9671
|
-
const projectMdPath =
|
|
10170
|
+
const projectPath = resolve18(projectsDir, entry.name);
|
|
10171
|
+
const projectMdPath = resolve18(projectPath, "project.md");
|
|
9672
10172
|
if (!await fileExists(projectMdPath)) {
|
|
9673
10173
|
return null;
|
|
9674
10174
|
}
|
|
@@ -9715,7 +10215,7 @@ async function computeProjectRecords(projectsDir, traces) {
|
|
|
9715
10215
|
return records;
|
|
9716
10216
|
}
|
|
9717
10217
|
async function listAssignmentRecords(projectPath, traces) {
|
|
9718
|
-
const assignmentsDir2 =
|
|
10218
|
+
const assignmentsDir2 = resolve18(projectPath, "assignments");
|
|
9719
10219
|
if (!await fileExists(assignmentsDir2)) {
|
|
9720
10220
|
return [];
|
|
9721
10221
|
}
|
|
@@ -9723,7 +10223,7 @@ async function listAssignmentRecords(projectPath, traces) {
|
|
|
9723
10223
|
const dirEntries = entries.filter((entry) => entry.isDirectory());
|
|
9724
10224
|
const maybeRecords = await Promise.all(
|
|
9725
10225
|
dirEntries.map(async (entry) => {
|
|
9726
|
-
const assignmentMd =
|
|
10226
|
+
const assignmentMd = resolve18(assignmentsDir2, entry.name, "assignment.md");
|
|
9727
10227
|
if (!await fileExists(assignmentMd)) {
|
|
9728
10228
|
return null;
|
|
9729
10229
|
}
|
|
@@ -9739,7 +10239,7 @@ async function listAssignmentRecords(projectPath, traces) {
|
|
|
9739
10239
|
return records;
|
|
9740
10240
|
}
|
|
9741
10241
|
async function listResources(projectPath) {
|
|
9742
|
-
const resourcesDir =
|
|
10242
|
+
const resourcesDir = resolve18(projectPath, "resources");
|
|
9743
10243
|
if (!await fileExists(resourcesDir)) {
|
|
9744
10244
|
return [];
|
|
9745
10245
|
}
|
|
@@ -9749,7 +10249,7 @@ async function listResources(projectPath) {
|
|
|
9749
10249
|
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_")) {
|
|
9750
10250
|
continue;
|
|
9751
10251
|
}
|
|
9752
|
-
const filePath =
|
|
10252
|
+
const filePath = resolve18(resourcesDir, entry.name);
|
|
9753
10253
|
const content = await readFile13(filePath, "utf-8");
|
|
9754
10254
|
const parsed = parseResource(content);
|
|
9755
10255
|
results.push({
|
|
@@ -9765,7 +10265,7 @@ async function listResources(projectPath) {
|
|
|
9765
10265
|
return results;
|
|
9766
10266
|
}
|
|
9767
10267
|
async function listMemories(projectPath) {
|
|
9768
|
-
const memoriesDir =
|
|
10268
|
+
const memoriesDir = resolve18(projectPath, "memories");
|
|
9769
10269
|
if (!await fileExists(memoriesDir)) {
|
|
9770
10270
|
return [];
|
|
9771
10271
|
}
|
|
@@ -9775,7 +10275,7 @@ async function listMemories(projectPath) {
|
|
|
9775
10275
|
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_")) {
|
|
9776
10276
|
continue;
|
|
9777
10277
|
}
|
|
9778
|
-
const filePath =
|
|
10278
|
+
const filePath = resolve18(memoriesDir, entry.name);
|
|
9779
10279
|
const content = await readFile13(filePath, "utf-8");
|
|
9780
10280
|
const parsed = parseMemory(content);
|
|
9781
10281
|
results.push({
|
|
@@ -9824,8 +10324,8 @@ async function listAllResources(projectsDir) {
|
|
|
9824
10324
|
return all;
|
|
9825
10325
|
}
|
|
9826
10326
|
async function resolveProjectPath(projectsDir, projectSlug) {
|
|
9827
|
-
const direct =
|
|
9828
|
-
if (await fileExists(
|
|
10327
|
+
const direct = resolve18(projectsDir, projectSlug);
|
|
10328
|
+
if (await fileExists(resolve18(direct, "project.md"))) return direct;
|
|
9829
10329
|
const records = await listProjectRecords(projectsDir);
|
|
9830
10330
|
const match = records.find((r) => r.summary.slug === projectSlug);
|
|
9831
10331
|
return match ? match.projectPath : null;
|
|
@@ -9837,7 +10337,7 @@ async function getMemoryDetail(projectsDir, projectSlug, itemSlug) {
|
|
|
9837
10337
|
(p) => basename(p.projectPath) === projectSlug || p.summary.slug === projectSlug
|
|
9838
10338
|
);
|
|
9839
10339
|
if (!projectRecord) return null;
|
|
9840
|
-
const filePath =
|
|
10340
|
+
const filePath = resolve18(projectRecord.projectPath, "memories", `${itemSlug}.md`);
|
|
9841
10341
|
if (!await fileExists(filePath)) return null;
|
|
9842
10342
|
const content = await readFile13(filePath, "utf-8");
|
|
9843
10343
|
const parsed = parseMemory(content);
|
|
@@ -9863,7 +10363,7 @@ async function getResourceDetail(projectsDir, projectSlug, itemSlug) {
|
|
|
9863
10363
|
(p) => basename(p.projectPath) === projectSlug || p.summary.slug === projectSlug
|
|
9864
10364
|
);
|
|
9865
10365
|
if (!projectRecord) return null;
|
|
9866
|
-
const filePath =
|
|
10366
|
+
const filePath = resolve18(projectRecord.projectPath, "resources", `${itemSlug}.md`);
|
|
9867
10367
|
if (!await fileExists(filePath)) return null;
|
|
9868
10368
|
const content = await readFile13(filePath, "utf-8");
|
|
9869
10369
|
const parsed = parseResource(content);
|
|
@@ -9881,7 +10381,7 @@ async function getResourceDetail(projectsDir, projectSlug, itemSlug) {
|
|
|
9881
10381
|
};
|
|
9882
10382
|
}
|
|
9883
10383
|
async function loadDependencyGraph(projectPath, assignments) {
|
|
9884
|
-
const statusPath =
|
|
10384
|
+
const statusPath = resolve18(projectPath, "_status.md");
|
|
9885
10385
|
if (await fileExists(statusPath)) {
|
|
9886
10386
|
const statusContent = await readFile13(statusPath, "utf-8");
|
|
9887
10387
|
const parsed = parseStatus(statusContent);
|
|
@@ -10050,7 +10550,7 @@ function toAssignmentSummary(assignment, terminalStatuses) {
|
|
|
10050
10550
|
async function toAssignmentBoardItem(projectsDir, projectRecord, assignment) {
|
|
10051
10551
|
const config = await getStatusConfig();
|
|
10052
10552
|
const { terminalStatuses } = config;
|
|
10053
|
-
const assignmentDir =
|
|
10553
|
+
const assignmentDir = resolve18(projectRecord.projectPath, "assignments", assignment.slug);
|
|
10054
10554
|
const projectDir = projectRecord.projectPath;
|
|
10055
10555
|
let facts;
|
|
10056
10556
|
try {
|
|
@@ -10111,7 +10611,7 @@ async function getAvailableTransitions(projectsDir, projectSlug, assignmentSlug,
|
|
|
10111
10611
|
const config = await getStatusConfig();
|
|
10112
10612
|
const transitionDefs = getTransitionDefinitions(config);
|
|
10113
10613
|
const actions = [];
|
|
10114
|
-
const projectPath =
|
|
10614
|
+
const projectPath = resolve18(projectsDir, projectSlug);
|
|
10115
10615
|
const traces = options?.traces;
|
|
10116
10616
|
for (const definition of transitionDefs) {
|
|
10117
10617
|
const target = getTargetStatus(assignment.status, definition.command, config.transitionTable);
|
|
@@ -10159,7 +10659,7 @@ async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses, de
|
|
|
10159
10659
|
continue;
|
|
10160
10660
|
}
|
|
10161
10661
|
}
|
|
10162
|
-
const dependencyPath =
|
|
10662
|
+
const dependencyPath = resolve18(projectPath, "assignments", dependency, "assignment.md");
|
|
10163
10663
|
if (!await fileExists(dependencyPath)) {
|
|
10164
10664
|
unmet.push(`${dependency} (missing)`);
|
|
10165
10665
|
continue;
|
|
@@ -10239,13 +10739,13 @@ async function collectStaleCandidates(projectsDir, assignmentsDir2) {
|
|
|
10239
10739
|
const out = [];
|
|
10240
10740
|
for (const record of projectRecords) {
|
|
10241
10741
|
if (isProjectArchived(record.summary)) continue;
|
|
10242
|
-
const projectPath =
|
|
10742
|
+
const projectPath = resolve18(projectsDir, record.summary.slug);
|
|
10243
10743
|
const depMap = /* @__PURE__ */ new Map();
|
|
10244
10744
|
for (const a of record.assignments) depMap.set(a.slug, a.status);
|
|
10245
10745
|
for (const assignment of activeAssignments(record.assignments)) {
|
|
10246
10746
|
const depsSatisfied = assignment.dependsOn.length === 0 ? true : (await getUnmetDependencies(projectPath, assignment.dependsOn, terminalStatuses, depMap)).length === 0;
|
|
10247
10747
|
const lastActivityMs = await readProgressActivityMs(
|
|
10248
|
-
|
|
10748
|
+
resolve18(projectPath, "assignments", assignment.slug, "progress.md"),
|
|
10249
10749
|
now
|
|
10250
10750
|
);
|
|
10251
10751
|
const reasons = classifyAssignmentRecord(assignment, terminalStatuses, depsSatisfied, lastActivityMs, thresholds);
|
|
@@ -10256,7 +10756,7 @@ async function collectStaleCandidates(projectsDir, assignmentsDir2) {
|
|
|
10256
10756
|
}
|
|
10257
10757
|
for (const sr of standaloneRecords) {
|
|
10258
10758
|
if (sr.record.archived === true) continue;
|
|
10259
|
-
const lastActivityMs = await readProgressActivityMs(
|
|
10759
|
+
const lastActivityMs = await readProgressActivityMs(resolve18(sr.assignmentDir, "progress.md"), now);
|
|
10260
10760
|
const reasons = classifyAssignmentRecord(sr.record, terminalStatuses, true, lastActivityMs, thresholds);
|
|
10261
10761
|
if (reasons.length > 0) out.push({ assignmentId: sr.record.id, projectSlug: null, reasons });
|
|
10262
10762
|
}
|
|
@@ -10275,7 +10775,7 @@ async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standalo
|
|
|
10275
10775
|
depMap.set(a.slug, a.status);
|
|
10276
10776
|
}
|
|
10277
10777
|
const visibleAssignments = activeAssignments(record.assignments);
|
|
10278
|
-
const projectPath =
|
|
10778
|
+
const projectPath = resolve18(projectsDir, record.summary.slug);
|
|
10279
10779
|
const resolvedTransitions = await Promise.all(
|
|
10280
10780
|
visibleAssignments.map(async (assignment) => {
|
|
10281
10781
|
const t0 = traces ? performance.now() : 0;
|
|
@@ -10289,7 +10789,7 @@ async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standalo
|
|
|
10289
10789
|
if (traces) accumulatePhase(traces, "get-available-transitions", performance.now() - t0);
|
|
10290
10790
|
const depsSatisfied = assignment.dependsOn.length === 0 ? true : (await getUnmetDependencies(projectPath, assignment.dependsOn, terminalStatuses, depMap)).length === 0;
|
|
10291
10791
|
const lastActivityMs = await readProgressActivityMs(
|
|
10292
|
-
|
|
10792
|
+
resolve18(projectPath, "assignments", assignment.slug, "progress.md"),
|
|
10293
10793
|
now
|
|
10294
10794
|
);
|
|
10295
10795
|
return { assignment, availableTransitions, depsSatisfied, lastActivityMs };
|
|
@@ -10363,7 +10863,7 @@ async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standalo
|
|
|
10363
10863
|
const t0 = traces ? performance.now() : 0;
|
|
10364
10864
|
const availableTransitions = await getStandaloneAvailableTransitions(sr.record);
|
|
10365
10865
|
if (traces) accumulatePhase(traces, "get-available-transitions", performance.now() - t0);
|
|
10366
|
-
const lastActivityMs = await readProgressActivityMs(
|
|
10866
|
+
const lastActivityMs = await readProgressActivityMs(resolve18(sr.assignmentDir, "progress.md"), now);
|
|
10367
10867
|
return { sr, availableTransitions, lastActivityMs };
|
|
10368
10868
|
})
|
|
10369
10869
|
);
|
|
@@ -10533,7 +11033,7 @@ function parseTimestamp(timestamp) {
|
|
|
10533
11033
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
10534
11034
|
}
|
|
10535
11035
|
async function countOpenQuestions(projectPath, assignmentSlug) {
|
|
10536
|
-
const commentsPath =
|
|
11036
|
+
const commentsPath = resolve18(
|
|
10537
11037
|
projectPath,
|
|
10538
11038
|
"assignments",
|
|
10539
11039
|
assignmentSlug,
|
|
@@ -10564,21 +11064,21 @@ function getProjectActivityTimestamp(projectUpdated, assignments) {
|
|
|
10564
11064
|
function getDocumentPath(projectsDir, documentType, projectSlug, assignmentSlug) {
|
|
10565
11065
|
switch (documentType) {
|
|
10566
11066
|
case "project":
|
|
10567
|
-
return
|
|
11067
|
+
return resolve18(projectsDir, projectSlug, "project.md");
|
|
10568
11068
|
case "assignment":
|
|
10569
|
-
return assignmentSlug ?
|
|
11069
|
+
return assignmentSlug ? resolve18(projectsDir, projectSlug, "assignments", assignmentSlug, "assignment.md") : null;
|
|
10570
11070
|
case "plan":
|
|
10571
|
-
return assignmentSlug ?
|
|
11071
|
+
return assignmentSlug ? resolve18(projectsDir, projectSlug, "assignments", assignmentSlug, "plan.md") : null;
|
|
10572
11072
|
case "scratchpad":
|
|
10573
|
-
return assignmentSlug ?
|
|
11073
|
+
return assignmentSlug ? resolve18(projectsDir, projectSlug, "assignments", assignmentSlug, "scratchpad.md") : null;
|
|
10574
11074
|
case "handoff":
|
|
10575
|
-
return assignmentSlug ?
|
|
11075
|
+
return assignmentSlug ? resolve18(projectsDir, projectSlug, "assignments", assignmentSlug, "handoff.md") : null;
|
|
10576
11076
|
case "decision-record":
|
|
10577
|
-
return assignmentSlug ?
|
|
11077
|
+
return assignmentSlug ? resolve18(projectsDir, projectSlug, "assignments", assignmentSlug, "decision-record.md") : null;
|
|
10578
11078
|
case "memory":
|
|
10579
|
-
return assignmentSlug ?
|
|
11079
|
+
return assignmentSlug ? resolve18(projectsDir, projectSlug, "memories", `${assignmentSlug}.md`) : null;
|
|
10580
11080
|
case "resource":
|
|
10581
|
-
return assignmentSlug ?
|
|
11081
|
+
return assignmentSlug ? resolve18(projectsDir, projectSlug, "resources", `${assignmentSlug}.md`) : null;
|
|
10582
11082
|
default:
|
|
10583
11083
|
return null;
|
|
10584
11084
|
}
|
|
@@ -10615,7 +11115,7 @@ async function listPlaybooks(playbooksDir2) {
|
|
|
10615
11115
|
const playbooks = [];
|
|
10616
11116
|
for (const entry of entries) {
|
|
10617
11117
|
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_") || entry.name === "manifest.md") continue;
|
|
10618
|
-
const filePath =
|
|
11118
|
+
const filePath = resolve18(playbooksDir2, entry.name);
|
|
10619
11119
|
const raw2 = await readFile13(filePath, "utf-8");
|
|
10620
11120
|
const parsed = parsePlaybook(raw2);
|
|
10621
11121
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
@@ -11220,12 +11720,12 @@ __export(recompute_exports, {
|
|
|
11220
11720
|
});
|
|
11221
11721
|
import { createHash as createHash2 } from "crypto";
|
|
11222
11722
|
import { open, readdir as readdir12, readFile as readFile17, unlink as unlink5, stat as stat3 } from "fs/promises";
|
|
11223
|
-
import { basename as basename3, dirname as dirname4, resolve as
|
|
11723
|
+
import { basename as basename3, dirname as dirname4, resolve as resolve23 } from "path";
|
|
11224
11724
|
async function isDeriveMigrated() {
|
|
11225
|
-
return fileExists(
|
|
11725
|
+
return fileExists(resolve23(syntaurRoot(), MIGRATION_MARKER));
|
|
11226
11726
|
}
|
|
11227
11727
|
async function markDeriveMigrated() {
|
|
11228
|
-
await writeFileForce(
|
|
11728
|
+
await writeFileForce(resolve23(syntaurRoot(), MIGRATION_MARKER), `${nowTimestamp()}
|
|
11229
11729
|
`);
|
|
11230
11730
|
}
|
|
11231
11731
|
async function resolveDeriveContext() {
|
|
@@ -11242,7 +11742,7 @@ async function resolveDeriveContext() {
|
|
|
11242
11742
|
};
|
|
11243
11743
|
}
|
|
11244
11744
|
async function acquireLock(assignmentDir) {
|
|
11245
|
-
const lockPath =
|
|
11745
|
+
const lockPath = resolve23(assignmentDir, LOCK_FILE);
|
|
11246
11746
|
const token = `${process.pid}:${createHash2("sha256").update(`${Math.random()}${Date.now()}`).digest("hex").slice(0, 12)}`;
|
|
11247
11747
|
for (let attempt = 0; attempt <= LOCK_MAX_WAITS; attempt++) {
|
|
11248
11748
|
try {
|
|
@@ -11386,7 +11886,7 @@ async function recomputeAndWrite(assignmentPath, opts) {
|
|
|
11386
11886
|
}
|
|
11387
11887
|
}
|
|
11388
11888
|
async function recomputeDependents(projectDir, changedSlug, opts) {
|
|
11389
|
-
const assignmentsDir2 =
|
|
11889
|
+
const assignmentsDir2 = resolve23(projectDir, "assignments");
|
|
11390
11890
|
let entries;
|
|
11391
11891
|
try {
|
|
11392
11892
|
entries = await readdir12(assignmentsDir2);
|
|
@@ -11396,7 +11896,7 @@ async function recomputeDependents(projectDir, changedSlug, opts) {
|
|
|
11396
11896
|
const results = [];
|
|
11397
11897
|
for (const slug of entries) {
|
|
11398
11898
|
if (slug === changedSlug) continue;
|
|
11399
|
-
const path =
|
|
11899
|
+
const path = resolve23(assignmentsDir2, slug, "assignment.md");
|
|
11400
11900
|
if (!await fileExists(path)) continue;
|
|
11401
11901
|
try {
|
|
11402
11902
|
const fm = parseAssignmentFrontmatter(await readFile17(path, "utf-8"));
|
|
@@ -11426,8 +11926,8 @@ async function recomputeAll(projectsDir, standaloneDir, opts) {
|
|
|
11426
11926
|
} catch {
|
|
11427
11927
|
}
|
|
11428
11928
|
for (const project of projects) {
|
|
11429
|
-
const projectDir =
|
|
11430
|
-
const assignmentsDir2 =
|
|
11929
|
+
const projectDir = resolve23(projectsDir, project);
|
|
11930
|
+
const assignmentsDir2 = resolve23(projectDir, "assignments");
|
|
11431
11931
|
let slugs = [];
|
|
11432
11932
|
try {
|
|
11433
11933
|
slugs = await readdir12(assignmentsDir2);
|
|
@@ -11435,7 +11935,7 @@ async function recomputeAll(projectsDir, standaloneDir, opts) {
|
|
|
11435
11935
|
continue;
|
|
11436
11936
|
}
|
|
11437
11937
|
for (const slug of slugs) {
|
|
11438
|
-
const path =
|
|
11938
|
+
const path = resolve23(assignmentsDir2, slug, "assignment.md");
|
|
11439
11939
|
if (await fileExists(path)) await sweepOne(path, projectDir);
|
|
11440
11940
|
}
|
|
11441
11941
|
}
|
|
@@ -11446,7 +11946,7 @@ async function recomputeAll(projectsDir, standaloneDir, opts) {
|
|
|
11446
11946
|
} catch {
|
|
11447
11947
|
}
|
|
11448
11948
|
for (const id of ids) {
|
|
11449
|
-
const path =
|
|
11949
|
+
const path = resolve23(standaloneDir, id, "assignment.md");
|
|
11450
11950
|
if (await fileExists(path)) await sweepOne(path, null);
|
|
11451
11951
|
}
|
|
11452
11952
|
}
|
|
@@ -11454,7 +11954,7 @@ async function recomputeAll(projectsDir, standaloneDir, opts) {
|
|
|
11454
11954
|
}
|
|
11455
11955
|
async function recomputeAssignmentDir(assignmentDir, cause, by) {
|
|
11456
11956
|
try {
|
|
11457
|
-
const assignmentPath =
|
|
11957
|
+
const assignmentPath = resolve23(assignmentDir, "assignment.md");
|
|
11458
11958
|
if (!await fileExists(assignmentPath)) return null;
|
|
11459
11959
|
const parent = dirname4(assignmentDir);
|
|
11460
11960
|
const projectDir = basename3(parent) === "assignments" ? dirname4(parent) : null;
|
|
@@ -11486,6 +11986,27 @@ var init_recompute = __esm({
|
|
|
11486
11986
|
}
|
|
11487
11987
|
});
|
|
11488
11988
|
|
|
11989
|
+
// src/utils/process-info.ts
|
|
11990
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
11991
|
+
function captureProcessStartedAt(pid) {
|
|
11992
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
11993
|
+
try {
|
|
11994
|
+
const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
11995
|
+
encoding: "utf8",
|
|
11996
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
11997
|
+
});
|
|
11998
|
+
const trimmed = out.trim();
|
|
11999
|
+
return trimmed === "" ? null : trimmed;
|
|
12000
|
+
} catch {
|
|
12001
|
+
return null;
|
|
12002
|
+
}
|
|
12003
|
+
}
|
|
12004
|
+
var init_process_info = __esm({
|
|
12005
|
+
"src/utils/process-info.ts"() {
|
|
12006
|
+
"use strict";
|
|
12007
|
+
}
|
|
12008
|
+
});
|
|
12009
|
+
|
|
11489
12010
|
// src/utils/transcript.ts
|
|
11490
12011
|
import { open as open2 } from "fs/promises";
|
|
11491
12012
|
async function derivePathFromTranscript(transcriptPath) {
|
|
@@ -11549,31 +12070,10 @@ var init_transcript = __esm({
|
|
|
11549
12070
|
}
|
|
11550
12071
|
});
|
|
11551
12072
|
|
|
11552
|
-
// src/utils/process-info.ts
|
|
11553
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
11554
|
-
function captureProcessStartedAt(pid) {
|
|
11555
|
-
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
11556
|
-
try {
|
|
11557
|
-
const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
11558
|
-
encoding: "utf8",
|
|
11559
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
11560
|
-
});
|
|
11561
|
-
const trimmed = out.trim();
|
|
11562
|
-
return trimmed === "" ? null : trimmed;
|
|
11563
|
-
} catch {
|
|
11564
|
-
return null;
|
|
11565
|
-
}
|
|
11566
|
-
}
|
|
11567
|
-
var init_process_info = __esm({
|
|
11568
|
-
"src/utils/process-info.ts"() {
|
|
11569
|
-
"use strict";
|
|
11570
|
-
}
|
|
11571
|
-
});
|
|
11572
|
-
|
|
11573
12073
|
// src/usage/cwd-extractor.ts
|
|
11574
12074
|
import { open as open3, readdir as readdir13, stat as stat4 } from "fs/promises";
|
|
11575
|
-
import { join as
|
|
11576
|
-
import { homedir as
|
|
12075
|
+
import { join as join3 } from "path";
|
|
12076
|
+
import { homedir as homedir2 } from "os";
|
|
11577
12077
|
async function extractClaudeSessionMeta(jsonlPath) {
|
|
11578
12078
|
const cwd = await derivePathFromTranscript(jsonlPath);
|
|
11579
12079
|
if (!cwd) return null;
|
|
@@ -11646,12 +12146,12 @@ async function* walkClaudeProjects(opts = {}) {
|
|
|
11646
12146
|
const dirs = await listDirSafe(root);
|
|
11647
12147
|
for (const dirent of dirs) {
|
|
11648
12148
|
if (!dirent.isDirectory) continue;
|
|
11649
|
-
const dirPath =
|
|
12149
|
+
const dirPath = join3(root, dirent.name);
|
|
11650
12150
|
const files = await listDirSafe(dirPath);
|
|
11651
12151
|
let cachedCwd = null;
|
|
11652
12152
|
for (const f of files) {
|
|
11653
12153
|
if (!f.isFile || !f.name.endsWith(".jsonl")) continue;
|
|
11654
|
-
const filePath =
|
|
12154
|
+
const filePath = join3(dirPath, f.name);
|
|
11655
12155
|
if (opts.sinceMtimeMs !== void 0) {
|
|
11656
12156
|
const mtime = await mtimeMs(filePath);
|
|
11657
12157
|
if (mtime !== null && mtime < opts.sinceMtimeMs) continue;
|
|
@@ -11683,13 +12183,13 @@ async function* walkCodexSessions(opts = {}) {
|
|
|
11683
12183
|
if (meta) yield meta;
|
|
11684
12184
|
}
|
|
11685
12185
|
}
|
|
11686
|
-
function resolveCodexSessionsRoot(
|
|
11687
|
-
if (
|
|
12186
|
+
function resolveCodexSessionsRoot(override3) {
|
|
12187
|
+
if (override3) return expandHome(override3);
|
|
11688
12188
|
const fromSessionsEnv = process.env.CODEX_SESSIONS_DIR;
|
|
11689
12189
|
if (fromSessionsEnv && fromSessionsEnv.length > 0) return expandHome(fromSessionsEnv);
|
|
11690
12190
|
const fromHomeEnv = process.env.CODEX_HOME;
|
|
11691
|
-
if (fromHomeEnv && fromHomeEnv.length > 0) return
|
|
11692
|
-
return
|
|
12191
|
+
if (fromHomeEnv && fromHomeEnv.length > 0) return join3(expandHome(fromHomeEnv), "sessions");
|
|
12192
|
+
return join3(homedir2(), ".codex", "sessions");
|
|
11693
12193
|
}
|
|
11694
12194
|
async function extractPiSessionMeta(jsonlPath) {
|
|
11695
12195
|
const basename7 = jsonlPath.split("/").pop() ?? "";
|
|
@@ -11743,23 +12243,23 @@ async function extractPiSessionMeta(jsonlPath) {
|
|
|
11743
12243
|
});
|
|
11744
12244
|
}
|
|
11745
12245
|
}
|
|
11746
|
-
function resolvePiSessionsRoot(
|
|
11747
|
-
if (
|
|
12246
|
+
function resolvePiSessionsRoot(override3) {
|
|
12247
|
+
if (override3) return expandHome(override3);
|
|
11748
12248
|
const fromHomeEnv = process.env.PI_AGENT_DIR;
|
|
11749
|
-
if (fromHomeEnv && fromHomeEnv.length > 0) return
|
|
11750
|
-
return
|
|
12249
|
+
if (fromHomeEnv && fromHomeEnv.length > 0) return join3(expandHome(fromHomeEnv), "sessions");
|
|
12250
|
+
return join3(homedir2(), ".pi", "agent", "sessions");
|
|
11751
12251
|
}
|
|
11752
12252
|
async function* walkPiSessions(opts = {}) {
|
|
11753
12253
|
const root = resolvePiSessionsRoot(opts.root);
|
|
11754
12254
|
const dirs = await listDirSafe(root);
|
|
11755
12255
|
for (const dirent of dirs) {
|
|
11756
12256
|
if (!dirent.isDirectory) continue;
|
|
11757
|
-
const dirPath =
|
|
12257
|
+
const dirPath = join3(root, dirent.name);
|
|
11758
12258
|
const files = await listDirSafe(dirPath);
|
|
11759
12259
|
let cachedCwd = null;
|
|
11760
12260
|
for (const f of files) {
|
|
11761
12261
|
if (!f.isFile || !f.name.endsWith(".jsonl")) continue;
|
|
11762
|
-
const filePath =
|
|
12262
|
+
const filePath = join3(dirPath, f.name);
|
|
11763
12263
|
if (opts.sinceMtimeMs !== void 0) {
|
|
11764
12264
|
const mtime = await mtimeMs(filePath);
|
|
11765
12265
|
if (mtime !== null && mtime < opts.sinceMtimeMs) continue;
|
|
@@ -11799,7 +12299,7 @@ async function* walkJsonlRecursive(root) {
|
|
|
11799
12299
|
const current = stack.pop();
|
|
11800
12300
|
const entries = await listDirSafe(current);
|
|
11801
12301
|
for (const e of entries) {
|
|
11802
|
-
const full =
|
|
12302
|
+
const full = join3(current, e.name);
|
|
11803
12303
|
if (e.isDirectory) {
|
|
11804
12304
|
stack.push(full);
|
|
11805
12305
|
} else if (e.isFile && e.name.endsWith(".jsonl")) {
|
|
@@ -11910,17 +12410,17 @@ var init_cwd_extractor = __esm({
|
|
|
11910
12410
|
|
|
11911
12411
|
// src/utils/session-id.ts
|
|
11912
12412
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
11913
|
-
import { mkdirSync, readFileSync, statSync as
|
|
11914
|
-
import { homedir as
|
|
11915
|
-
import { dirname as dirname5, join as
|
|
12413
|
+
import { mkdirSync, readFileSync as readFileSync2, statSync as statSync4, writeFileSync } from "fs";
|
|
12414
|
+
import { homedir as homedir3 } from "os";
|
|
12415
|
+
import { dirname as dirname5, join as join4 } from "path";
|
|
11916
12416
|
function isSafeSessionId(value) {
|
|
11917
12417
|
return typeof value === "string" && value.length > 0 && value.length <= 256 && SAFE_SESSION_ID.test(value);
|
|
11918
12418
|
}
|
|
11919
12419
|
function readRuntimeMarker(pid, dir) {
|
|
11920
12420
|
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
11921
|
-
const path =
|
|
12421
|
+
const path = join4(dir, `${pid}.json`);
|
|
11922
12422
|
try {
|
|
11923
|
-
const parsed = JSON.parse(
|
|
12423
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
11924
12424
|
if (parsed && typeof parsed === "object" && typeof parsed.sessionId === "string" && parsed.sessionId.length > 0) {
|
|
11925
12425
|
return parsed;
|
|
11926
12426
|
}
|
|
@@ -11930,7 +12430,7 @@ function readRuntimeMarker(pid, dir) {
|
|
|
11930
12430
|
}
|
|
11931
12431
|
}
|
|
11932
12432
|
function writeRuntimeMarker(pid, marker, dir) {
|
|
11933
|
-
const path =
|
|
12433
|
+
const path = join4(dir, `${pid}.json`);
|
|
11934
12434
|
mkdirSync(dirname5(path), { recursive: true });
|
|
11935
12435
|
writeFileSync(path, JSON.stringify(marker));
|
|
11936
12436
|
}
|
|
@@ -11993,12 +12493,12 @@ var init_assignment_todos = __esm({
|
|
|
11993
12493
|
|
|
11994
12494
|
// src/utils/path-canon.ts
|
|
11995
12495
|
import { realpathSync as realpathSync2 } from "fs";
|
|
11996
|
-
import { resolve as
|
|
12496
|
+
import { resolve as resolve44 } from "path";
|
|
11997
12497
|
function canonicalPath(p) {
|
|
11998
12498
|
try {
|
|
11999
|
-
return realpathSync2(
|
|
12499
|
+
return realpathSync2(resolve44(p));
|
|
12000
12500
|
} catch {
|
|
12001
|
-
return
|
|
12501
|
+
return resolve44(p).replace(/\/+$/, "");
|
|
12002
12502
|
}
|
|
12003
12503
|
}
|
|
12004
12504
|
var init_path_canon = __esm({
|
|
@@ -12027,7 +12527,7 @@ var init_renderers = __esm({
|
|
|
12027
12527
|
});
|
|
12028
12528
|
|
|
12029
12529
|
// src/targets/user-descriptors.ts
|
|
12030
|
-
import { resolve as
|
|
12530
|
+
import { resolve as resolve45 } from "path";
|
|
12031
12531
|
import { readFile as readFile30, readdir as readdir18 } from "fs/promises";
|
|
12032
12532
|
var VALID_RENDERER_KEYS;
|
|
12033
12533
|
var init_user_descriptors = __esm({
|
|
@@ -12042,20 +12542,20 @@ var init_user_descriptors = __esm({
|
|
|
12042
12542
|
|
|
12043
12543
|
// src/targets/registry.ts
|
|
12044
12544
|
import { homedir as homedir7 } from "os";
|
|
12045
|
-
import { join as join10, resolve as
|
|
12545
|
+
import { join as join10, resolve as resolve46 } from "path";
|
|
12046
12546
|
function home(...segments) {
|
|
12047
|
-
return
|
|
12547
|
+
return resolve46(homedir7(), ...segments);
|
|
12048
12548
|
}
|
|
12049
12549
|
function hermesHome() {
|
|
12050
12550
|
const env = process.env.HERMES_HOME;
|
|
12051
|
-
return env && env.length > 0 ?
|
|
12551
|
+
return env && env.length > 0 ? resolve46(env) : home(".hermes");
|
|
12052
12552
|
}
|
|
12053
12553
|
function hermesSkillsDir() {
|
|
12054
|
-
return
|
|
12554
|
+
return resolve46(hermesHome(), "skills");
|
|
12055
12555
|
}
|
|
12056
12556
|
function codexHome() {
|
|
12057
12557
|
const env = process.env.CODEX_HOME;
|
|
12058
|
-
return env && env.length > 0 ?
|
|
12558
|
+
return env && env.length > 0 ? resolve46(env) : home(".codex");
|
|
12059
12559
|
}
|
|
12060
12560
|
function toDiscovered(meta) {
|
|
12061
12561
|
if (!meta) return null;
|
|
@@ -12126,7 +12626,7 @@ var init_registry = __esm({
|
|
|
12126
12626
|
skillsShAgentId: "codex",
|
|
12127
12627
|
nativePlugin: "codex",
|
|
12128
12628
|
detect: detectDir(codexHome()),
|
|
12129
|
-
skillsDir: { global:
|
|
12629
|
+
skillsDir: { global: resolve46(codexHome(), "skills") },
|
|
12130
12630
|
instructions: { files: [{ path: "AGENTS.md", renderer: "codexAgents" }] },
|
|
12131
12631
|
sessions: codexSessions
|
|
12132
12632
|
},
|
|
@@ -12195,7 +12695,7 @@ var init_registry = __esm({
|
|
|
12195
12695
|
tier3: {
|
|
12196
12696
|
kind: "hermes-plugin",
|
|
12197
12697
|
source: "platforms/hermes/plugins/syntaur",
|
|
12198
|
-
installDir: () =>
|
|
12698
|
+
installDir: () => resolve46(hermesHome(), "plugins", "syntaur"),
|
|
12199
12699
|
entry: "plugin.yaml"
|
|
12200
12700
|
}
|
|
12201
12701
|
}
|
|
@@ -12206,22 +12706,75 @@ var init_registry = __esm({
|
|
|
12206
12706
|
}
|
|
12207
12707
|
});
|
|
12208
12708
|
|
|
12709
|
+
// src/sessions/agent-view.ts
|
|
12710
|
+
import { execFile as execFile3 } from "child_process";
|
|
12711
|
+
import { promisify as promisify3 } from "util";
|
|
12712
|
+
function toActivityState(raw2) {
|
|
12713
|
+
switch (raw2) {
|
|
12714
|
+
case "working":
|
|
12715
|
+
case "idle":
|
|
12716
|
+
case "awaiting-input":
|
|
12717
|
+
return raw2;
|
|
12718
|
+
default:
|
|
12719
|
+
return null;
|
|
12720
|
+
}
|
|
12721
|
+
}
|
|
12722
|
+
function parseAgentView(json) {
|
|
12723
|
+
const live = /* @__PURE__ */ new Map();
|
|
12724
|
+
let parsed;
|
|
12725
|
+
try {
|
|
12726
|
+
parsed = JSON.parse(json);
|
|
12727
|
+
} catch {
|
|
12728
|
+
return live;
|
|
12729
|
+
}
|
|
12730
|
+
const entries = Array.isArray(parsed) ? parsed : Array.isArray(parsed.agents) ? parsed.agents : [];
|
|
12731
|
+
for (const entry of entries) {
|
|
12732
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
12733
|
+
const e = entry;
|
|
12734
|
+
const id = typeof e.session_id === "string" ? e.session_id : typeof e.sessionId === "string" ? e.sessionId : null;
|
|
12735
|
+
if (!id) continue;
|
|
12736
|
+
live.set(id, toActivityState(e.activity ?? e.status) ?? "idle");
|
|
12737
|
+
}
|
|
12738
|
+
return live;
|
|
12739
|
+
}
|
|
12740
|
+
function getAgentViewSource() {
|
|
12741
|
+
return override2 ?? productionAgentViewSource;
|
|
12742
|
+
}
|
|
12743
|
+
var execFileAsync, productionAgentViewSource, override2;
|
|
12744
|
+
var init_agent_view = __esm({
|
|
12745
|
+
"src/sessions/agent-view.ts"() {
|
|
12746
|
+
"use strict";
|
|
12747
|
+
execFileAsync = promisify3(execFile3);
|
|
12748
|
+
productionAgentViewSource = async () => {
|
|
12749
|
+
try {
|
|
12750
|
+
const { stdout } = await execFileAsync("claude", ["agents", "--json"], {
|
|
12751
|
+
timeout: 5e3,
|
|
12752
|
+
maxBuffer: 8 * 1024 * 1024
|
|
12753
|
+
});
|
|
12754
|
+
return parseAgentView(stdout);
|
|
12755
|
+
} catch {
|
|
12756
|
+
return /* @__PURE__ */ new Map();
|
|
12757
|
+
}
|
|
12758
|
+
};
|
|
12759
|
+
override2 = null;
|
|
12760
|
+
}
|
|
12761
|
+
});
|
|
12762
|
+
|
|
12209
12763
|
// src/sessions/scanner.ts
|
|
12210
12764
|
var scanner_exports2 = {};
|
|
12211
12765
|
__export(scanner_exports2, {
|
|
12212
12766
|
scanSessions: () => scanSessions
|
|
12213
12767
|
});
|
|
12214
|
-
import { execFile as
|
|
12215
|
-
import { promisify as
|
|
12216
|
-
import { statSync as
|
|
12217
|
-
import {
|
|
12218
|
-
import { resolve as resolve46 } from "path";
|
|
12768
|
+
import { execFile as execFile4, execFileSync as execFileSync4 } from "child_process";
|
|
12769
|
+
import { promisify as promisify4 } from "util";
|
|
12770
|
+
import { statSync as statSync5 } from "fs";
|
|
12771
|
+
import { resolve as resolve47 } from "path";
|
|
12219
12772
|
function emptySummary() {
|
|
12220
12773
|
return { discovered: 0, inserted: 0, revived: 0, swept: 0, skipped: 0, changed: false };
|
|
12221
12774
|
}
|
|
12222
12775
|
function defaultStatMtimeMs(path) {
|
|
12223
12776
|
try {
|
|
12224
|
-
return
|
|
12777
|
+
return statSync5(path).mtimeMs;
|
|
12225
12778
|
} catch {
|
|
12226
12779
|
return null;
|
|
12227
12780
|
}
|
|
@@ -12254,7 +12807,7 @@ async function defaultOpenFiles(files) {
|
|
|
12254
12807
|
const chunk = files.slice(i, i + LSOF_CHUNK);
|
|
12255
12808
|
let stdout = "";
|
|
12256
12809
|
try {
|
|
12257
|
-
const result = await
|
|
12810
|
+
const result = await execFileAsync2("lsof", ["-Fn", "--", ...chunk], {
|
|
12258
12811
|
maxBuffer: 8 * 1024 * 1024
|
|
12259
12812
|
});
|
|
12260
12813
|
stdout = result.stdout;
|
|
@@ -12273,23 +12826,12 @@ function canonicalizeOpenSet(open6) {
|
|
|
12273
12826
|
for (const p of open6) out.add(canonicalPath(p));
|
|
12274
12827
|
return out;
|
|
12275
12828
|
}
|
|
12276
|
-
async function
|
|
12829
|
+
async function isWorkspace(cwd, cache3) {
|
|
12277
12830
|
if (cache3.has(cwd)) return cache3.get(cwd);
|
|
12278
|
-
|
|
12279
|
-
const
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
const parsed = JSON.parse(await readFile31(path, "utf-8"));
|
|
12283
|
-
link = {
|
|
12284
|
-
projectSlug: typeof parsed.projectSlug === "string" ? parsed.projectSlug : null,
|
|
12285
|
-
assignmentSlug: typeof parsed.assignmentSlug === "string" ? parsed.assignmentSlug : null
|
|
12286
|
-
};
|
|
12287
|
-
} catch {
|
|
12288
|
-
link = { projectSlug: null, assignmentSlug: null };
|
|
12289
|
-
}
|
|
12290
|
-
}
|
|
12291
|
-
cache3.set(cwd, link);
|
|
12292
|
-
return link;
|
|
12831
|
+
const path = resolve47(cwd, ".syntaur", "context.json");
|
|
12832
|
+
const present = await fileExists(path);
|
|
12833
|
+
cache3.set(cwd, present);
|
|
12834
|
+
return present;
|
|
12293
12835
|
}
|
|
12294
12836
|
function readWatermark() {
|
|
12295
12837
|
const db5 = getSessionDb();
|
|
@@ -12313,9 +12855,16 @@ async function scanSessions(opts = {}, deps = {}) {
|
|
|
12313
12855
|
const openFiles = deps.openFiles ?? defaultOpenFiles;
|
|
12314
12856
|
const isPidAlive = deps.isPidAlive ?? defaultIsPidAlive;
|
|
12315
12857
|
const pidStartedAt = deps.pidStartedAt ?? defaultPidStartedAt;
|
|
12858
|
+
const agentView = deps.agentView ?? getAgentViewSource();
|
|
12316
12859
|
const targets = (deps.targets ?? AGENT_TARGETS).filter((t) => t.sessions !== void 0);
|
|
12317
12860
|
const scanStartMs = now();
|
|
12318
12861
|
const watermark = opts.full ? null : readWatermark();
|
|
12862
|
+
let liveActivity;
|
|
12863
|
+
try {
|
|
12864
|
+
liveActivity = await agentView();
|
|
12865
|
+
} catch {
|
|
12866
|
+
liveActivity = /* @__PURE__ */ new Map();
|
|
12867
|
+
}
|
|
12319
12868
|
const discovered = [];
|
|
12320
12869
|
for (const target of targets) {
|
|
12321
12870
|
const walk = target.sessions.walk({
|
|
@@ -12331,16 +12880,17 @@ async function scanSessions(opts = {}, deps = {}) {
|
|
|
12331
12880
|
const openSet = canonicalizeOpenSet(
|
|
12332
12881
|
await openFiles(discovered.map((d) => d.transcriptPath))
|
|
12333
12882
|
);
|
|
12334
|
-
const
|
|
12883
|
+
const workspaceCache = /* @__PURE__ */ new Map();
|
|
12335
12884
|
for (const d of discovered) {
|
|
12336
|
-
const
|
|
12337
|
-
if (autoTrack === "workspaces-only" &&
|
|
12885
|
+
const isWs = await isWorkspace(d.cwd, workspaceCache);
|
|
12886
|
+
if (autoTrack === "workspaces-only" && !isWs) {
|
|
12338
12887
|
summary.skipped += 1;
|
|
12339
12888
|
continue;
|
|
12340
12889
|
}
|
|
12341
12890
|
const mtime = statMtimeMs(d.transcriptPath);
|
|
12342
12891
|
const heldOpen = openSet.has(canonicalPath(d.transcriptPath));
|
|
12343
|
-
const
|
|
12892
|
+
const agentViewLive = liveActivity.has(d.sessionId);
|
|
12893
|
+
const isLive = heldOpen || agentViewLive || mtime !== null && now() - mtime < FRESH_MTIME_MS;
|
|
12344
12894
|
const prev = getSessionById(d.sessionId);
|
|
12345
12895
|
const status = isLive ? "active" : prev?.status ?? "stopped";
|
|
12346
12896
|
const started = d.startedAt ?? (mtime !== null ? new Date(mtime).toISOString() : new Date(now()).toISOString());
|
|
@@ -12348,8 +12898,11 @@ async function scanSessions(opts = {}, deps = {}) {
|
|
|
12348
12898
|
"",
|
|
12349
12899
|
{
|
|
12350
12900
|
sessionId: d.sessionId,
|
|
12351
|
-
|
|
12352
|
-
|
|
12901
|
+
// Auto-discovered sessions are UNATTRIBUTED: the scanner no longer binds
|
|
12902
|
+
// them to the cwd context.json assignment scalar (see isWorkspace). An
|
|
12903
|
+
// explicit grab/track opens an engagement edge to attribute them.
|
|
12904
|
+
projectSlug: null,
|
|
12905
|
+
assignmentSlug: null,
|
|
12353
12906
|
agent: d.agent,
|
|
12354
12907
|
started,
|
|
12355
12908
|
status,
|
|
@@ -12361,11 +12914,12 @@ async function scanSessions(opts = {}, deps = {}) {
|
|
|
12361
12914
|
originalHeadSha: null
|
|
12362
12915
|
},
|
|
12363
12916
|
// Narrow revival rule: only LIVE-PROCESS evidence (a process holding the
|
|
12364
|
-
// transcript open) may flip a stopped row back
|
|
12365
|
-
// alone must not — a session stopped moments ago
|
|
12366
|
-
// still has a fresh transcript for up to 5 minutes
|
|
12367
|
-
// active. `completed` always sticks (appendSession
|
|
12368
|
-
|
|
12917
|
+
// transcript open) OR Agent-View live evidence may flip a stopped row back
|
|
12918
|
+
// to active. mtime freshness alone must not — a session stopped moments ago
|
|
12919
|
+
// by its SessionEnd hook still has a fresh transcript for up to 5 minutes
|
|
12920
|
+
// and would flap back to active. `completed` always sticks (appendSession
|
|
12921
|
+
// enforces).
|
|
12922
|
+
{ reviveStopped: heldOpen || agentViewLive }
|
|
12369
12923
|
);
|
|
12370
12924
|
if (!isLive) {
|
|
12371
12925
|
const after = getSessionById(d.sessionId);
|
|
@@ -12378,19 +12932,25 @@ async function scanSessions(opts = {}, deps = {}) {
|
|
|
12378
12932
|
summary.inserted += 1;
|
|
12379
12933
|
summary.changed = true;
|
|
12380
12934
|
} else {
|
|
12381
|
-
if (prev.status === "stopped" && heldOpen) {
|
|
12935
|
+
if (prev.status === "stopped" && (heldOpen || agentViewLive)) {
|
|
12382
12936
|
summary.revived += 1;
|
|
12383
12937
|
summary.changed = true;
|
|
12384
12938
|
}
|
|
12385
|
-
if (link?.projectSlug && !prev.projectSlug || link?.assignmentSlug && !prev.assignmentSlug) {
|
|
12386
|
-
summary.changed = true;
|
|
12387
|
-
}
|
|
12388
12939
|
}
|
|
12389
12940
|
}
|
|
12390
12941
|
const db5 = getSessionDb();
|
|
12942
|
+
if (liveActivity.size > 0) {
|
|
12943
|
+
const setActivity = db5.prepare(
|
|
12944
|
+
"UPDATE sessions SET activity = ?, updated_at = datetime('now') WHERE session_id = ? AND activity IS NOT ?"
|
|
12945
|
+
);
|
|
12946
|
+
for (const [sid, activity] of liveActivity) {
|
|
12947
|
+
if (setActivity.run(activity, sid, activity).changes > 0) summary.changed = true;
|
|
12948
|
+
}
|
|
12949
|
+
}
|
|
12391
12950
|
const activeRows = db5.prepare("SELECT session_id, pid, pid_started_at, transcript_path FROM sessions WHERE status = 'active'").all();
|
|
12392
12951
|
const sweepCandidates = [];
|
|
12393
12952
|
for (const row of activeRows) {
|
|
12953
|
+
if (liveActivity.has(row.session_id)) continue;
|
|
12394
12954
|
if (row.pid !== null) {
|
|
12395
12955
|
const alive = isPidAlive(row.pid) && (!row.pid_started_at || (pidStartedAt(row.pid) ?? row.pid_started_at) === row.pid_started_at);
|
|
12396
12956
|
if (alive) continue;
|
|
@@ -12407,16 +12967,27 @@ async function scanSessions(opts = {}, deps = {}) {
|
|
|
12407
12967
|
)
|
|
12408
12968
|
);
|
|
12409
12969
|
for (const candidate of sweepCandidates) {
|
|
12970
|
+
let endedAt;
|
|
12410
12971
|
if (candidate.transcriptPath) {
|
|
12411
12972
|
if (sweepOpenSet.has(canonicalPath(candidate.transcriptPath))) continue;
|
|
12412
12973
|
const mtime = statMtimeMs(candidate.transcriptPath);
|
|
12413
12974
|
if (mtime !== null && now() - mtime < FRESH_MTIME_MS) continue;
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12975
|
+
endedAt = mtime !== null ? new Date(mtime).toISOString() : void 0;
|
|
12976
|
+
}
|
|
12977
|
+
const open6 = getOpenEngagement(candidate.sessionId);
|
|
12978
|
+
let snap = null;
|
|
12979
|
+
try {
|
|
12980
|
+
snap = await getCumulativeTokenSource()(candidate.sessionId);
|
|
12981
|
+
} catch {
|
|
12982
|
+
snap = null;
|
|
12983
|
+
}
|
|
12984
|
+
if (livenessStopSession({
|
|
12985
|
+
sessionId: candidate.sessionId,
|
|
12986
|
+
engagementId: open6?.id ?? null,
|
|
12987
|
+
engagementStartedAt: open6?.started_at ?? null,
|
|
12988
|
+
endedAt,
|
|
12989
|
+
tokensAtClose: snap
|
|
12990
|
+
})) {
|
|
12420
12991
|
summary.swept += 1;
|
|
12421
12992
|
summary.changed = true;
|
|
12422
12993
|
}
|
|
@@ -12424,7 +12995,7 @@ async function scanSessions(opts = {}, deps = {}) {
|
|
|
12424
12995
|
writeWatermark(scanStartMs);
|
|
12425
12996
|
return summary;
|
|
12426
12997
|
}
|
|
12427
|
-
var
|
|
12998
|
+
var execFileAsync2, FRESH_MTIME_MS, LSOF_CHUNK, WATERMARK_KEY;
|
|
12428
12999
|
var init_scanner2 = __esm({
|
|
12429
13000
|
"src/sessions/scanner.ts"() {
|
|
12430
13001
|
"use strict";
|
|
@@ -12435,7 +13006,10 @@ var init_scanner2 = __esm({
|
|
|
12435
13006
|
init_registry();
|
|
12436
13007
|
init_session_db();
|
|
12437
13008
|
init_agent_sessions();
|
|
12438
|
-
|
|
13009
|
+
init_engagement_db();
|
|
13010
|
+
init_engagement_tokens();
|
|
13011
|
+
init_agent_view();
|
|
13012
|
+
execFileAsync2 = promisify4(execFile4);
|
|
12439
13013
|
FRESH_MTIME_MS = 5 * 60 * 1e3;
|
|
12440
13014
|
LSOF_CHUNK = 64;
|
|
12441
13015
|
WATERMARK_KEY = "sessions_scan_last_ms";
|
|
@@ -12483,19 +13057,19 @@ init_assignment_resolver();
|
|
|
12483
13057
|
init_agent_sessions();
|
|
12484
13058
|
import express from "express";
|
|
12485
13059
|
import { createServer } from "http";
|
|
12486
|
-
import { resolve as
|
|
13060
|
+
import { resolve as resolve48 } from "path";
|
|
12487
13061
|
import { writeFile as writeFile8, unlink as unlink9 } from "fs/promises";
|
|
12488
13062
|
import { WebSocketServer, WebSocket } from "ws";
|
|
12489
13063
|
|
|
12490
13064
|
// src/dashboard/session-liveness.ts
|
|
12491
13065
|
import { execFileSync } from "child_process";
|
|
12492
|
-
import { statSync as
|
|
13066
|
+
import { statSync as statSync3 } from "fs";
|
|
12493
13067
|
var FIVE_MINUTES_MS = 5 * 60 * 1e3;
|
|
12494
13068
|
var DEFAULT_DEPS = {
|
|
12495
13069
|
now: () => Date.now(),
|
|
12496
13070
|
statMtimeMs: (path) => {
|
|
12497
13071
|
try {
|
|
12498
|
-
return
|
|
13072
|
+
return statSync3(path).mtimeMs;
|
|
12499
13073
|
} catch {
|
|
12500
13074
|
return null;
|
|
12501
13075
|
}
|
|
@@ -12991,10 +13565,10 @@ function mergePatch(current, patch) {
|
|
|
12991
13565
|
init_paths();
|
|
12992
13566
|
init_fs();
|
|
12993
13567
|
import { readFile as readFile14, rename as rename3, unlink as unlink3 } from "fs/promises";
|
|
12994
|
-
import { resolve as
|
|
13568
|
+
import { resolve as resolve19 } from "path";
|
|
12995
13569
|
function corruptFilePath() {
|
|
12996
13570
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
12997
|
-
return
|
|
13571
|
+
return resolve19(syntaurRoot(), `view-prefs.corrupt-${ts}.json`);
|
|
12998
13572
|
}
|
|
12999
13573
|
function isViewPrefsFileShape(value) {
|
|
13000
13574
|
if (!value || typeof value !== "object") return false;
|
|
@@ -13266,10 +13840,10 @@ init_paths();
|
|
|
13266
13840
|
init_fs();
|
|
13267
13841
|
import { readFile as readFile15, rename as rename4, unlink as unlink4 } from "fs/promises";
|
|
13268
13842
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
13269
|
-
import { resolve as
|
|
13843
|
+
import { resolve as resolve20 } from "path";
|
|
13270
13844
|
function corruptFilePath2() {
|
|
13271
13845
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13272
|
-
return
|
|
13846
|
+
return resolve20(syntaurRoot(), `saved-views.corrupt-${ts}.json`);
|
|
13273
13847
|
}
|
|
13274
13848
|
function isSavedViewsFileShape(value) {
|
|
13275
13849
|
if (!value || typeof value !== "object") return false;
|
|
@@ -13644,19 +14218,19 @@ init_timestamp();
|
|
|
13644
14218
|
init_fs();
|
|
13645
14219
|
init_git_worktree();
|
|
13646
14220
|
import { Router as Router2 } from "express";
|
|
13647
|
-
import { resolve as
|
|
14221
|
+
import { resolve as resolve24, basename as basename4, isAbsolute as isAbsolute4 } from "path";
|
|
13648
14222
|
import { rm, readFile as readFile18, open as fsOpen, stat as fsStat, realpath as fsRealpath } from "fs/promises";
|
|
13649
14223
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
13650
14224
|
|
|
13651
14225
|
// src/utils/worktree-defaults.ts
|
|
13652
14226
|
init_paths();
|
|
13653
14227
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
13654
|
-
import { resolve as
|
|
14228
|
+
import { resolve as resolve21 } from "path";
|
|
13655
14229
|
function computeWorktreeDefaults(opts) {
|
|
13656
14230
|
const repository = opts.existing.repository ?? detectCurrentGitRoot(opts.cwd);
|
|
13657
14231
|
const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
|
|
13658
14232
|
const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch(opts.cwd) ?? "main";
|
|
13659
|
-
const worktreeBase = repository ?
|
|
14233
|
+
const worktreeBase = repository ? resolve21(repository, ".worktrees", branch) : resolve21(
|
|
13660
14234
|
syntaurRoot(),
|
|
13661
14235
|
"worktrees",
|
|
13662
14236
|
opts.projectSlug || "standalone",
|
|
@@ -13875,7 +14449,7 @@ function recreateOutcomeToHttp(outcome) {
|
|
|
13875
14449
|
init_fs();
|
|
13876
14450
|
init_parser();
|
|
13877
14451
|
import { readdir as readdir11, readFile as readFile16 } from "fs/promises";
|
|
13878
|
-
import { resolve as
|
|
14452
|
+
import { resolve as resolve22 } from "path";
|
|
13879
14453
|
function toSourceAssignment(parsed, fallbackId) {
|
|
13880
14454
|
const repository = parsed.workspace.repository?.trim();
|
|
13881
14455
|
const branch = parsed.workspace.branch?.trim();
|
|
@@ -13892,29 +14466,29 @@ function toSourceAssignment(parsed, fallbackId) {
|
|
|
13892
14466
|
async function getProjectRepositoryCandidates(projectsDir, projectSlug) {
|
|
13893
14467
|
const seen = /* @__PURE__ */ new Set();
|
|
13894
14468
|
const out = [];
|
|
13895
|
-
const projectPath =
|
|
14469
|
+
const projectPath = resolve22(projectsDir, projectSlug, "project.md");
|
|
13896
14470
|
if (await fileExists(projectPath)) {
|
|
13897
14471
|
const project = parseProject(await readFile16(projectPath, "utf-8"));
|
|
13898
14472
|
for (const raw2 of project.repositories) {
|
|
13899
14473
|
const path = raw2.trim();
|
|
13900
14474
|
if (!path) continue;
|
|
13901
|
-
const abs =
|
|
14475
|
+
const abs = resolve22(path);
|
|
13902
14476
|
if (seen.has(abs)) continue;
|
|
13903
14477
|
seen.add(abs);
|
|
13904
14478
|
out.push({ path: abs, source: "project", sourceAssignmentSlug: null });
|
|
13905
14479
|
}
|
|
13906
14480
|
}
|
|
13907
|
-
const assignmentsDir2 =
|
|
14481
|
+
const assignmentsDir2 = resolve22(projectsDir, projectSlug, "assignments");
|
|
13908
14482
|
if (await fileExists(assignmentsDir2)) {
|
|
13909
14483
|
const entries = await readdir11(assignmentsDir2, { withFileTypes: true });
|
|
13910
14484
|
for (const entry of entries) {
|
|
13911
14485
|
if (!entry.isDirectory()) continue;
|
|
13912
|
-
const assignmentMd =
|
|
14486
|
+
const assignmentMd = resolve22(assignmentsDir2, entry.name, "assignment.md");
|
|
13913
14487
|
if (!await fileExists(assignmentMd)) continue;
|
|
13914
14488
|
const parsed = parseAssignmentFull(await readFile16(assignmentMd, "utf-8"));
|
|
13915
14489
|
const repo = parsed.workspace.repository?.trim();
|
|
13916
14490
|
if (!repo) continue;
|
|
13917
|
-
const abs =
|
|
14491
|
+
const abs = resolve22(repo);
|
|
13918
14492
|
if (seen.has(abs)) continue;
|
|
13919
14493
|
seen.add(abs);
|
|
13920
14494
|
out.push({ path: abs, source: "sibling", sourceAssignmentSlug: parsed.slug });
|
|
@@ -13932,12 +14506,12 @@ async function getStandaloneRepositoryCandidates(assignmentsDir2, excludeAssignm
|
|
|
13932
14506
|
for (const entry of entries) {
|
|
13933
14507
|
if (!entry.isDirectory()) continue;
|
|
13934
14508
|
if (entry.name === excludeAssignmentId) continue;
|
|
13935
|
-
const assignmentMd =
|
|
14509
|
+
const assignmentMd = resolve22(assignmentsDir2, entry.name, "assignment.md");
|
|
13936
14510
|
if (!await fileExists(assignmentMd)) continue;
|
|
13937
14511
|
const parsed = parseAssignmentFull(await readFile16(assignmentMd, "utf-8"));
|
|
13938
14512
|
const repo = parsed.workspace.repository?.trim();
|
|
13939
14513
|
if (!repo) continue;
|
|
13940
|
-
const abs =
|
|
14514
|
+
const abs = resolve22(repo);
|
|
13941
14515
|
if (seen.has(abs)) continue;
|
|
13942
14516
|
seen.add(abs);
|
|
13943
14517
|
out.push({ path: abs, source: "sibling", sourceAssignmentSlug: parsed.slug });
|
|
@@ -13945,7 +14519,7 @@ async function getStandaloneRepositoryCandidates(assignmentsDir2, excludeAssignm
|
|
|
13945
14519
|
return out;
|
|
13946
14520
|
}
|
|
13947
14521
|
async function getProjectSourceAssignments(projectsDir, projectSlug, excludeSlug) {
|
|
13948
|
-
const assignmentsDir2 =
|
|
14522
|
+
const assignmentsDir2 = resolve22(projectsDir, projectSlug, "assignments");
|
|
13949
14523
|
if (!await fileExists(assignmentsDir2)) return [];
|
|
13950
14524
|
const seen = /* @__PURE__ */ new Set();
|
|
13951
14525
|
const out = [];
|
|
@@ -13953,7 +14527,7 @@ async function getProjectSourceAssignments(projectsDir, projectSlug, excludeSlug
|
|
|
13953
14527
|
for (const entry of entries) {
|
|
13954
14528
|
if (!entry.isDirectory()) continue;
|
|
13955
14529
|
if (entry.name === excludeSlug) continue;
|
|
13956
|
-
const assignmentMd =
|
|
14530
|
+
const assignmentMd = resolve22(assignmentsDir2, entry.name, "assignment.md");
|
|
13957
14531
|
if (!await fileExists(assignmentMd)) continue;
|
|
13958
14532
|
const parsed = parseAssignmentFull(await readFile16(assignmentMd, "utf-8"));
|
|
13959
14533
|
const source = toSourceAssignment(parsed, entry.name);
|
|
@@ -13972,7 +14546,7 @@ async function getStandaloneSourceAssignments(assignmentsDir2, excludeAssignment
|
|
|
13972
14546
|
for (const entry of entries) {
|
|
13973
14547
|
if (!entry.isDirectory()) continue;
|
|
13974
14548
|
if (entry.name === excludeAssignmentId) continue;
|
|
13975
|
-
const assignmentMd =
|
|
14549
|
+
const assignmentMd = resolve22(assignmentsDir2, entry.name, "assignment.md");
|
|
13976
14550
|
if (!await fileExists(assignmentMd)) continue;
|
|
13977
14551
|
const parsed = parseAssignmentFull(await readFile16(assignmentMd, "utf-8"));
|
|
13978
14552
|
const source = toSourceAssignment(parsed, entry.name);
|
|
@@ -14667,7 +15241,7 @@ async function handleWorktreeCreate(req2, res, ctx) {
|
|
|
14667
15241
|
});
|
|
14668
15242
|
const branch = typeof bodyBranch === "string" && bodyBranch.trim() ? bodyBranch.trim() : defaults.branch;
|
|
14669
15243
|
const parentBranch = typeof bodyParent === "string" && bodyParent.trim() ? bodyParent.trim() : defaults.parentBranch;
|
|
14670
|
-
const worktreePath =
|
|
15244
|
+
const worktreePath = resolve24(repo, ".worktrees", branch);
|
|
14671
15245
|
const refCheck = spawnSync3("git", ["-C", repo, "check-ref-format", "--branch", branch], {
|
|
14672
15246
|
encoding: "utf-8"
|
|
14673
15247
|
});
|
|
@@ -14962,9 +15536,9 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
14962
15536
|
});
|
|
14963
15537
|
return;
|
|
14964
15538
|
}
|
|
14965
|
-
const folderPath =
|
|
15539
|
+
const folderPath = resolve24(projectDir, folder);
|
|
14966
15540
|
await ensureDir(folderPath);
|
|
14967
|
-
const filePath =
|
|
15541
|
+
const filePath = resolve24(folderPath, `${requestedSlug}.md`);
|
|
14968
15542
|
const timestamp = nowTimestamp();
|
|
14969
15543
|
let content = renderItemStub(kind, {
|
|
14970
15544
|
slug: requestedSlug,
|
|
@@ -15008,7 +15582,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
15008
15582
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
15009
15583
|
return;
|
|
15010
15584
|
}
|
|
15011
|
-
const filePath =
|
|
15585
|
+
const filePath = resolve24(projectDir, folder, `${itemSlug}.md`);
|
|
15012
15586
|
if (!await fileExists(filePath)) {
|
|
15013
15587
|
res.status(404).json({ error: `${kind === "memory" ? "Memory" : "Resource"} not found` });
|
|
15014
15588
|
return;
|
|
@@ -15044,7 +15618,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15044
15618
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
15045
15619
|
return;
|
|
15046
15620
|
}
|
|
15047
|
-
const filePath =
|
|
15621
|
+
const filePath = resolve24(projectDir, folder, `${itemSlug}.md`);
|
|
15048
15622
|
if (!await fileExists(filePath)) {
|
|
15049
15623
|
res.status(404).json({ error: `${kind === "memory" ? "Memory" : "Resource"} not found` });
|
|
15050
15624
|
return;
|
|
@@ -15078,26 +15652,26 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15078
15652
|
res.status(400).json({ error: `Invalid slug "${slug}". Must be lowercase and hyphen-separated.` });
|
|
15079
15653
|
return;
|
|
15080
15654
|
}
|
|
15081
|
-
const projectDir =
|
|
15655
|
+
const projectDir = resolve24(projectsDir, slug);
|
|
15082
15656
|
if (await fileExists(projectDir)) {
|
|
15083
15657
|
res.status(409).json({ error: `Project "${slug}" already exists` });
|
|
15084
15658
|
return;
|
|
15085
15659
|
}
|
|
15086
15660
|
const title = fields.title;
|
|
15087
15661
|
const timestamp = fields.created || nowTimestamp();
|
|
15088
|
-
await ensureDir(
|
|
15089
|
-
await ensureDir(
|
|
15090
|
-
await ensureDir(
|
|
15091
|
-
await writeFileForce(
|
|
15662
|
+
await ensureDir(resolve24(projectDir, "assignments"));
|
|
15663
|
+
await ensureDir(resolve24(projectDir, "resources"));
|
|
15664
|
+
await ensureDir(resolve24(projectDir, "memories"));
|
|
15665
|
+
await writeFileForce(resolve24(projectDir, "project.md"), content);
|
|
15092
15666
|
try {
|
|
15093
15667
|
const companions = [
|
|
15094
|
-
[
|
|
15095
|
-
[
|
|
15096
|
-
[
|
|
15097
|
-
[
|
|
15098
|
-
[
|
|
15099
|
-
[
|
|
15100
|
-
[
|
|
15668
|
+
[resolve24(projectDir, "manifest.md"), renderManifest({ slug, timestamp })],
|
|
15669
|
+
[resolve24(projectDir, "_index-assignments.md"), renderIndexAssignments({ slug, title, timestamp })],
|
|
15670
|
+
[resolve24(projectDir, "_index-plans.md"), renderIndexPlans({ slug, title, timestamp })],
|
|
15671
|
+
[resolve24(projectDir, "_index-decisions.md"), renderIndexDecisions({ slug, title, timestamp })],
|
|
15672
|
+
[resolve24(projectDir, "_status.md"), renderStatus({ slug, title, timestamp })],
|
|
15673
|
+
[resolve24(projectDir, "resources", "_index.md"), renderResourcesIndex({ slug, title, timestamp })],
|
|
15674
|
+
[resolve24(projectDir, "memories", "_index.md"), renderMemoriesIndex({ slug, title, timestamp })]
|
|
15101
15675
|
];
|
|
15102
15676
|
for (const [filePath, fileContent] of companions) {
|
|
15103
15677
|
await writeFileForce(filePath, fileContent);
|
|
@@ -15118,8 +15692,8 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15118
15692
|
router.post("/api/projects/:slug/assignments", async (req2, res) => {
|
|
15119
15693
|
try {
|
|
15120
15694
|
const projectSlug = getParam(req2.params.slug);
|
|
15121
|
-
const projectDir =
|
|
15122
|
-
const projectMdPath =
|
|
15695
|
+
const projectDir = resolve24(projectsDir, projectSlug);
|
|
15696
|
+
const projectMdPath = resolve24(projectDir, "project.md");
|
|
15123
15697
|
if (!await fileExists(projectMdPath)) {
|
|
15124
15698
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
15125
15699
|
return;
|
|
@@ -15149,7 +15723,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15149
15723
|
res.status(400).json({ error: `Invalid priority "${priority}". Must be low, medium, high, or critical.` });
|
|
15150
15724
|
return;
|
|
15151
15725
|
}
|
|
15152
|
-
const assignmentDir =
|
|
15726
|
+
const assignmentDir = resolve24(projectDir, "assignments", assignmentSlug);
|
|
15153
15727
|
if (await fileExists(assignmentDir)) {
|
|
15154
15728
|
res.status(409).json({
|
|
15155
15729
|
error: `Assignment "${assignmentSlug}" already exists in project "${projectSlug}"`
|
|
@@ -15167,12 +15741,12 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15167
15741
|
command: "create",
|
|
15168
15742
|
by: null
|
|
15169
15743
|
}) : content;
|
|
15170
|
-
await writeFileForce(
|
|
15744
|
+
await writeFileForce(resolve24(assignmentDir, "assignment.md"), seededContent);
|
|
15171
15745
|
try {
|
|
15172
15746
|
const companions = [
|
|
15173
|
-
[
|
|
15174
|
-
[
|
|
15175
|
-
[
|
|
15747
|
+
[resolve24(assignmentDir, "scratchpad.md"), renderScratchpad({ assignmentSlug, timestamp })],
|
|
15748
|
+
[resolve24(assignmentDir, "handoff.md"), renderHandoff({ assignmentSlug, timestamp })],
|
|
15749
|
+
[resolve24(assignmentDir, "decision-record.md"), renderDecisionRecord({ assignmentSlug, timestamp })]
|
|
15176
15750
|
];
|
|
15177
15751
|
for (const [filePath, fileContent] of companions) {
|
|
15178
15752
|
await writeFileForce(filePath, fileContent);
|
|
@@ -15200,7 +15774,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15200
15774
|
router.patch("/api/projects/:slug", async (req2, res) => {
|
|
15201
15775
|
try {
|
|
15202
15776
|
const projectSlug = getParam(req2.params.slug);
|
|
15203
|
-
const projectPath =
|
|
15777
|
+
const projectPath = resolve24(projectsDir, projectSlug, "project.md");
|
|
15204
15778
|
const currentContent = await readCurrentDocument(projectPath);
|
|
15205
15779
|
if (!currentContent) {
|
|
15206
15780
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
@@ -15233,7 +15807,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15233
15807
|
try {
|
|
15234
15808
|
const projectSlug = getParam(req2.params.slug);
|
|
15235
15809
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15236
|
-
const assignmentPath =
|
|
15810
|
+
const assignmentPath = resolve24(
|
|
15237
15811
|
projectsDir,
|
|
15238
15812
|
projectSlug,
|
|
15239
15813
|
"assignments",
|
|
@@ -15299,7 +15873,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15299
15873
|
try {
|
|
15300
15874
|
const projectSlug = getParam(req2.params.slug);
|
|
15301
15875
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15302
|
-
const assignmentPath =
|
|
15876
|
+
const assignmentPath = resolve24(
|
|
15303
15877
|
projectsDir,
|
|
15304
15878
|
projectSlug,
|
|
15305
15879
|
"assignments",
|
|
@@ -15335,7 +15909,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15335
15909
|
try {
|
|
15336
15910
|
const projectSlug = getParam(req2.params.slug);
|
|
15337
15911
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15338
|
-
const planPath =
|
|
15912
|
+
const planPath = resolve24(
|
|
15339
15913
|
projectsDir,
|
|
15340
15914
|
projectSlug,
|
|
15341
15915
|
"assignments",
|
|
@@ -15373,7 +15947,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15373
15947
|
try {
|
|
15374
15948
|
const projectSlug = getParam(req2.params.slug);
|
|
15375
15949
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15376
|
-
const scratchpadPath =
|
|
15950
|
+
const scratchpadPath = resolve24(
|
|
15377
15951
|
projectsDir,
|
|
15378
15952
|
projectSlug,
|
|
15379
15953
|
"assignments",
|
|
@@ -15411,7 +15985,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15411
15985
|
try {
|
|
15412
15986
|
const projectSlug = getParam(req2.params.slug);
|
|
15413
15987
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15414
|
-
const handoffPath =
|
|
15988
|
+
const handoffPath = resolve24(
|
|
15415
15989
|
projectsDir,
|
|
15416
15990
|
projectSlug,
|
|
15417
15991
|
"assignments",
|
|
@@ -15449,7 +16023,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15449
16023
|
try {
|
|
15450
16024
|
const projectSlug = getParam(req2.params.slug);
|
|
15451
16025
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15452
|
-
const decisionPath =
|
|
16026
|
+
const decisionPath = resolve24(
|
|
15453
16027
|
projectsDir,
|
|
15454
16028
|
projectSlug,
|
|
15455
16029
|
"assignments",
|
|
@@ -15487,7 +16061,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15487
16061
|
try {
|
|
15488
16062
|
const projectSlug = getParam(req2.params.slug);
|
|
15489
16063
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15490
|
-
const commentsPath =
|
|
16064
|
+
const commentsPath = resolve24(
|
|
15491
16065
|
projectsDir,
|
|
15492
16066
|
projectSlug,
|
|
15493
16067
|
"assignments",
|
|
@@ -15543,7 +16117,7 @@ ${entry}`;
|
|
|
15543
16117
|
}
|
|
15544
16118
|
await writeFileForce(commentsPath, next);
|
|
15545
16119
|
try {
|
|
15546
|
-
const assignmentMdPath =
|
|
16120
|
+
const assignmentMdPath = resolve24(projectsDir, projectSlug, "assignments", assignmentSlug, "assignment.md");
|
|
15547
16121
|
if (await fileExists(assignmentMdPath)) {
|
|
15548
16122
|
const fm = parseAssignmentFull(await readFile18(assignmentMdPath, "utf-8"));
|
|
15549
16123
|
emitDashboardEvent(fm.id, projectSlug, "comment-added", {
|
|
@@ -15568,7 +16142,7 @@ ${entry}`;
|
|
|
15568
16142
|
const projectSlug = getParam(req2.params.slug);
|
|
15569
16143
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15570
16144
|
const commentId = getParam(req2.params.commentId);
|
|
15571
|
-
const commentsPath =
|
|
16145
|
+
const commentsPath = resolve24(
|
|
15572
16146
|
projectsDir,
|
|
15573
16147
|
projectSlug,
|
|
15574
16148
|
"assignments",
|
|
@@ -15611,7 +16185,7 @@ ${entry}`;
|
|
|
15611
16185
|
await writeFileForce(commentsPath, withUpdated);
|
|
15612
16186
|
if (target.resolved !== true && resolved === true) {
|
|
15613
16187
|
try {
|
|
15614
|
-
const assignmentMdPath =
|
|
16188
|
+
const assignmentMdPath = resolve24(projectsDir, projectSlug, "assignments", assignmentSlug, "assignment.md");
|
|
15615
16189
|
if (await fileExists(assignmentMdPath)) {
|
|
15616
16190
|
const fm = parseAssignmentFull(await readFile18(assignmentMdPath, "utf-8"));
|
|
15617
16191
|
emitDashboardEvent(fm.id, projectSlug, "comment-resolved", { commentId });
|
|
@@ -15629,7 +16203,7 @@ ${entry}`;
|
|
|
15629
16203
|
router.post("/api/projects/:slug/move-workspace", async (req2, res) => {
|
|
15630
16204
|
try {
|
|
15631
16205
|
const projectSlug = getParam(req2.params.slug);
|
|
15632
|
-
const projectPath =
|
|
16206
|
+
const projectPath = resolve24(projectsDir, projectSlug, "project.md");
|
|
15633
16207
|
if (!await fileExists(projectPath)) {
|
|
15634
16208
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
15635
16209
|
return;
|
|
@@ -15677,7 +16251,7 @@ ${entry}`;
|
|
|
15677
16251
|
});
|
|
15678
16252
|
return;
|
|
15679
16253
|
}
|
|
15680
|
-
const assignmentPath =
|
|
16254
|
+
const assignmentPath = resolve24(resolved.assignmentDir, "assignment.md");
|
|
15681
16255
|
let content = await readFile18(assignmentPath, "utf-8");
|
|
15682
16256
|
content = setTopLevelField(content, "workspaceGroup", workspaceGroup ?? null);
|
|
15683
16257
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
@@ -15694,7 +16268,7 @@ ${entry}`;
|
|
|
15694
16268
|
async (req2, res) => {
|
|
15695
16269
|
try {
|
|
15696
16270
|
const projectSlug = getParam(req2.params.slug);
|
|
15697
|
-
const projectPath =
|
|
16271
|
+
const projectPath = resolve24(projectsDir, projectSlug, "project.md");
|
|
15698
16272
|
if (!await fileExists(projectPath)) {
|
|
15699
16273
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
15700
16274
|
return;
|
|
@@ -15753,7 +16327,7 @@ ${entry}`;
|
|
|
15753
16327
|
try {
|
|
15754
16328
|
const projectSlug = getParam(req2.params.slug);
|
|
15755
16329
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15756
|
-
const assignmentPath =
|
|
16330
|
+
const assignmentPath = resolve24(
|
|
15757
16331
|
projectsDir,
|
|
15758
16332
|
projectSlug,
|
|
15759
16333
|
"assignments",
|
|
@@ -15802,7 +16376,7 @@ ${entry}`;
|
|
|
15802
16376
|
try {
|
|
15803
16377
|
const projectSlug = getParam(req2.params.slug);
|
|
15804
16378
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15805
|
-
const assignmentPath =
|
|
16379
|
+
const assignmentPath = resolve24(
|
|
15806
16380
|
projectsDir,
|
|
15807
16381
|
projectSlug,
|
|
15808
16382
|
"assignments",
|
|
@@ -15861,7 +16435,7 @@ ${entry}`;
|
|
|
15861
16435
|
try {
|
|
15862
16436
|
const projectSlug = getParam(req2.params.slug);
|
|
15863
16437
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15864
|
-
const assignmentPath =
|
|
16438
|
+
const assignmentPath = resolve24(
|
|
15865
16439
|
projectsDir,
|
|
15866
16440
|
projectSlug,
|
|
15867
16441
|
"assignments",
|
|
@@ -15894,7 +16468,7 @@ ${entry}`;
|
|
|
15894
16468
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
15895
16469
|
return;
|
|
15896
16470
|
}
|
|
15897
|
-
const assignmentPath =
|
|
16471
|
+
const assignmentPath = resolve24(resolved.assignmentDir, "assignment.md");
|
|
15898
16472
|
const parsedForSlug = parseAssignmentFull(await readFile18(assignmentPath, "utf-8"));
|
|
15899
16473
|
const assignmentSlugForBranch = parsedForSlug.slug || resolved.id;
|
|
15900
16474
|
await handleWorktreeCreate(req2, res, {
|
|
@@ -15951,7 +16525,7 @@ ${entry}`;
|
|
|
15951
16525
|
router.post("/api/projects/:slug/status-override", async (req2, res) => {
|
|
15952
16526
|
try {
|
|
15953
16527
|
const projectSlug = getParam(req2.params.slug);
|
|
15954
|
-
const projectPath =
|
|
16528
|
+
const projectPath = resolve24(projectsDir, projectSlug, "project.md");
|
|
15955
16529
|
if (!await fileExists(projectPath)) {
|
|
15956
16530
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
15957
16531
|
return;
|
|
@@ -15978,7 +16552,7 @@ ${entry}`;
|
|
|
15978
16552
|
try {
|
|
15979
16553
|
const projectSlug = getParam(req2.params.slug);
|
|
15980
16554
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
15981
|
-
const assignmentPath =
|
|
16555
|
+
const assignmentPath = resolve24(
|
|
15982
16556
|
projectsDir,
|
|
15983
16557
|
projectSlug,
|
|
15984
16558
|
"assignments",
|
|
@@ -16009,7 +16583,7 @@ ${entry}`;
|
|
|
16009
16583
|
const result = await recomputeAndWrite2(assignmentPath, {
|
|
16010
16584
|
cause: clearing ? "unpin" : "pin",
|
|
16011
16585
|
by: "human",
|
|
16012
|
-
projectDir:
|
|
16586
|
+
projectDir: resolve24(projectsDir, projectSlug),
|
|
16013
16587
|
context,
|
|
16014
16588
|
mutate: (content) => {
|
|
16015
16589
|
if (clearing) return updateOverride2(content, null);
|
|
@@ -16040,7 +16614,7 @@ ${entry}`;
|
|
|
16040
16614
|
router.post("/api/projects/:slug/archive", async (req2, res) => {
|
|
16041
16615
|
try {
|
|
16042
16616
|
const projectSlug = getParam(req2.params.slug);
|
|
16043
|
-
const projectPath =
|
|
16617
|
+
const projectPath = resolve24(projectsDir, projectSlug, "project.md");
|
|
16044
16618
|
if (!await fileExists(projectPath)) {
|
|
16045
16619
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
16046
16620
|
return;
|
|
@@ -16057,7 +16631,7 @@ ${entry}`;
|
|
|
16057
16631
|
router.post("/api/projects/:slug/unarchive", async (req2, res) => {
|
|
16058
16632
|
try {
|
|
16059
16633
|
const projectSlug = getParam(req2.params.slug);
|
|
16060
|
-
const projectPath =
|
|
16634
|
+
const projectPath = resolve24(projectsDir, projectSlug, "project.md");
|
|
16061
16635
|
if (!await fileExists(projectPath)) {
|
|
16062
16636
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
16063
16637
|
return;
|
|
@@ -16074,7 +16648,7 @@ ${entry}`;
|
|
|
16074
16648
|
async function handleAssignmentArchive(req2, res, archived) {
|
|
16075
16649
|
const projectSlug = getParam(req2.params.slug);
|
|
16076
16650
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
16077
|
-
const assignmentPath =
|
|
16651
|
+
const assignmentPath = resolve24(projectsDir, projectSlug, "assignments", assignmentSlug, "assignment.md");
|
|
16078
16652
|
if (!await fileExists(assignmentPath)) {
|
|
16079
16653
|
res.status(404).json({ error: "Assignment not found" });
|
|
16080
16654
|
return;
|
|
@@ -16114,7 +16688,7 @@ ${entry}`;
|
|
|
16114
16688
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
16115
16689
|
return;
|
|
16116
16690
|
}
|
|
16117
|
-
const assignmentPath =
|
|
16691
|
+
const assignmentPath = resolve24(resolved.assignmentDir, "assignment.md");
|
|
16118
16692
|
const content = await readFile18(assignmentPath, "utf-8");
|
|
16119
16693
|
const reason = archived ? archiveReason(req2.body) : null;
|
|
16120
16694
|
await writeFileForce(assignmentPath, applyArchiveFields(content, archived, reason));
|
|
@@ -16143,7 +16717,7 @@ ${entry}`;
|
|
|
16143
16717
|
try {
|
|
16144
16718
|
const projectSlug = getParam(req2.params.slug);
|
|
16145
16719
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
16146
|
-
const assignmentPath =
|
|
16720
|
+
const assignmentPath = resolve24(
|
|
16147
16721
|
projectsDir,
|
|
16148
16722
|
projectSlug,
|
|
16149
16723
|
"assignments",
|
|
@@ -16181,7 +16755,7 @@ ${entry}`;
|
|
|
16181
16755
|
try {
|
|
16182
16756
|
const projectSlug = getParam(req2.params.slug);
|
|
16183
16757
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
16184
|
-
const assignmentPath =
|
|
16758
|
+
const assignmentPath = resolve24(
|
|
16185
16759
|
projectsDir,
|
|
16186
16760
|
projectSlug,
|
|
16187
16761
|
"assignments",
|
|
@@ -16219,8 +16793,8 @@ ${entry}`;
|
|
|
16219
16793
|
res.status(400).json({ error: `Unsupported transition command "${req2.params.command}"` });
|
|
16220
16794
|
return;
|
|
16221
16795
|
}
|
|
16222
|
-
const projectDir =
|
|
16223
|
-
const assignmentPath =
|
|
16796
|
+
const projectDir = resolve24(projectsDir, projectSlug);
|
|
16797
|
+
const assignmentPath = resolve24(projectDir, "assignments", assignmentSlug, "assignment.md");
|
|
16224
16798
|
if (!await fileExists(assignmentPath)) {
|
|
16225
16799
|
res.status(404).json({ error: "Assignment not found" });
|
|
16226
16800
|
return;
|
|
@@ -16301,8 +16875,8 @@ ${entry}`;
|
|
|
16301
16875
|
try {
|
|
16302
16876
|
const projectSlug = getParam(req2.params.slug);
|
|
16303
16877
|
const assignmentSlug = getParam(req2.params.aslug);
|
|
16304
|
-
const assignmentDir =
|
|
16305
|
-
const assignmentPath =
|
|
16878
|
+
const assignmentDir = resolve24(projectsDir, projectSlug, "assignments", assignmentSlug);
|
|
16879
|
+
const assignmentPath = resolve24(assignmentDir, "assignment.md");
|
|
16306
16880
|
if (!await fileExists(assignmentPath)) {
|
|
16307
16881
|
res.status(404).json({ error: `Assignment "${assignmentSlug}" not found in project "${projectSlug}"` });
|
|
16308
16882
|
return;
|
|
@@ -16357,7 +16931,7 @@ ${entry}`;
|
|
|
16357
16931
|
return;
|
|
16358
16932
|
}
|
|
16359
16933
|
const id2 = generateId();
|
|
16360
|
-
const assignmentDir2 =
|
|
16934
|
+
const assignmentDir2 = resolve24(assignmentsDir2, id2);
|
|
16361
16935
|
if (await fileExists(assignmentDir2)) {
|
|
16362
16936
|
res.status(500).json({ error: "UUID collision \u2014 try again" });
|
|
16363
16937
|
return;
|
|
@@ -16376,25 +16950,25 @@ ${entry}`;
|
|
|
16376
16950
|
by: null
|
|
16377
16951
|
});
|
|
16378
16952
|
}
|
|
16379
|
-
await writeFileForce(
|
|
16953
|
+
await writeFileForce(resolve24(assignmentDir2, "assignment.md"), normalizedContent);
|
|
16380
16954
|
await writeFileForce(
|
|
16381
|
-
|
|
16955
|
+
resolve24(assignmentDir2, "scratchpad.md"),
|
|
16382
16956
|
renderScratchpad({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
16383
16957
|
);
|
|
16384
16958
|
await writeFileForce(
|
|
16385
|
-
|
|
16959
|
+
resolve24(assignmentDir2, "handoff.md"),
|
|
16386
16960
|
renderHandoff({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
16387
16961
|
);
|
|
16388
16962
|
await writeFileForce(
|
|
16389
|
-
|
|
16963
|
+
resolve24(assignmentDir2, "decision-record.md"),
|
|
16390
16964
|
renderDecisionRecord({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
16391
16965
|
);
|
|
16392
16966
|
await writeFileForce(
|
|
16393
|
-
|
|
16967
|
+
resolve24(assignmentDir2, "progress.md"),
|
|
16394
16968
|
renderProgress({ assignment: id2, timestamp: timestamp2 })
|
|
16395
16969
|
);
|
|
16396
16970
|
await writeFileForce(
|
|
16397
|
-
|
|
16971
|
+
resolve24(assignmentDir2, "comments.md"),
|
|
16398
16972
|
renderComments({ assignment: id2, timestamp: timestamp2 })
|
|
16399
16973
|
);
|
|
16400
16974
|
if (seededHere) {
|
|
@@ -16419,7 +16993,7 @@ ${entry}`;
|
|
|
16419
16993
|
return;
|
|
16420
16994
|
}
|
|
16421
16995
|
const id = generateId();
|
|
16422
|
-
const assignmentDir =
|
|
16996
|
+
const assignmentDir = resolve24(assignmentsDir2, id);
|
|
16423
16997
|
if (await fileExists(assignmentDir)) {
|
|
16424
16998
|
res.status(500).json({ error: "UUID collision \u2014 try again" });
|
|
16425
16999
|
return;
|
|
@@ -16439,25 +17013,25 @@ ${entry}`;
|
|
|
16439
17013
|
project: null,
|
|
16440
17014
|
type: typeof type === "string" ? type : void 0
|
|
16441
17015
|
});
|
|
16442
|
-
await writeFileForce(
|
|
17016
|
+
await writeFileForce(resolve24(assignmentDir, "assignment.md"), assignmentContent);
|
|
16443
17017
|
await writeFileForce(
|
|
16444
|
-
|
|
17018
|
+
resolve24(assignmentDir, "scratchpad.md"),
|
|
16445
17019
|
renderScratchpad({ assignmentSlug: id, timestamp })
|
|
16446
17020
|
);
|
|
16447
17021
|
await writeFileForce(
|
|
16448
|
-
|
|
17022
|
+
resolve24(assignmentDir, "handoff.md"),
|
|
16449
17023
|
renderHandoff({ assignmentSlug: id, timestamp })
|
|
16450
17024
|
);
|
|
16451
17025
|
await writeFileForce(
|
|
16452
|
-
|
|
17026
|
+
resolve24(assignmentDir, "decision-record.md"),
|
|
16453
17027
|
renderDecisionRecord({ assignmentSlug: id, timestamp })
|
|
16454
17028
|
);
|
|
16455
17029
|
await writeFileForce(
|
|
16456
|
-
|
|
17030
|
+
resolve24(assignmentDir, "progress.md"),
|
|
16457
17031
|
renderProgress({ assignment: id, timestamp })
|
|
16458
17032
|
);
|
|
16459
17033
|
await writeFileForce(
|
|
16460
|
-
|
|
17034
|
+
resolve24(assignmentDir, "comments.md"),
|
|
16461
17035
|
renderComments({ assignment: id, timestamp })
|
|
16462
17036
|
);
|
|
16463
17037
|
emitDashboardEvent(id, null, "status-change", {
|
|
@@ -16590,7 +17164,7 @@ ${entry}`;
|
|
|
16590
17164
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
16591
17165
|
return;
|
|
16592
17166
|
}
|
|
16593
|
-
const assignmentPath =
|
|
17167
|
+
const assignmentPath = resolve24(resolved.assignmentDir, "assignment.md");
|
|
16594
17168
|
const currentContent = await readCurrentDocument(assignmentPath);
|
|
16595
17169
|
if (!currentContent) {
|
|
16596
17170
|
res.status(404).json({ error: "Assignment not found" });
|
|
@@ -16655,7 +17229,7 @@ ${entry}`;
|
|
|
16655
17229
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
16656
17230
|
return;
|
|
16657
17231
|
}
|
|
16658
|
-
const planPath =
|
|
17232
|
+
const planPath = resolve24(resolved.assignmentDir, "plan.md");
|
|
16659
17233
|
const currentContent = await readCurrentDocument(planPath);
|
|
16660
17234
|
if (!currentContent) {
|
|
16661
17235
|
res.status(404).json({ error: "Plan not found" });
|
|
@@ -16689,7 +17263,7 @@ ${entry}`;
|
|
|
16689
17263
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
16690
17264
|
return;
|
|
16691
17265
|
}
|
|
16692
|
-
const scratchpadPath =
|
|
17266
|
+
const scratchpadPath = resolve24(resolved.assignmentDir, "scratchpad.md");
|
|
16693
17267
|
const currentContent = await readCurrentDocument(scratchpadPath);
|
|
16694
17268
|
if (!currentContent) {
|
|
16695
17269
|
res.status(404).json({ error: "Scratchpad not found" });
|
|
@@ -16723,7 +17297,7 @@ ${entry}`;
|
|
|
16723
17297
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
16724
17298
|
return;
|
|
16725
17299
|
}
|
|
16726
|
-
const handoffPath =
|
|
17300
|
+
const handoffPath = resolve24(resolved.assignmentDir, "handoff.md");
|
|
16727
17301
|
const currentContent = await readCurrentDocument(handoffPath);
|
|
16728
17302
|
if (!currentContent) {
|
|
16729
17303
|
res.status(404).json({ error: "Handoff log not found" });
|
|
@@ -16763,7 +17337,7 @@ ${entry}`;
|
|
|
16763
17337
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
16764
17338
|
return;
|
|
16765
17339
|
}
|
|
16766
|
-
const decisionPath =
|
|
17340
|
+
const decisionPath = resolve24(resolved.assignmentDir, "decision-record.md");
|
|
16767
17341
|
const currentContent = await readCurrentDocument(decisionPath);
|
|
16768
17342
|
if (!currentContent) {
|
|
16769
17343
|
res.status(404).json({ error: "Decision record not found" });
|
|
@@ -16803,7 +17377,7 @@ ${entry}`;
|
|
|
16803
17377
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
16804
17378
|
return;
|
|
16805
17379
|
}
|
|
16806
|
-
const assignmentPath =
|
|
17380
|
+
const assignmentPath = resolve24(resolved.assignmentDir, "assignment.md");
|
|
16807
17381
|
if (!await fileExists(assignmentPath)) {
|
|
16808
17382
|
res.status(404).json({ error: "Assignment not found" });
|
|
16809
17383
|
return;
|
|
@@ -16825,7 +17399,7 @@ ${entry}`;
|
|
|
16825
17399
|
const { recomputeAndWrite: recomputeAndWrite2, resolveDeriveContext: resolveDeriveContext2 } = await Promise.resolve().then(() => (init_recompute(), recompute_exports));
|
|
16826
17400
|
const { updateOverride: updateOverride2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
|
|
16827
17401
|
const context = await resolveDeriveContext2();
|
|
16828
|
-
const projectDirForId = resolved.standalone ? null :
|
|
17402
|
+
const projectDirForId = resolved.standalone ? null : resolve24(resolved.assignmentDir, "..", "..");
|
|
16829
17403
|
const result = await recomputeAndWrite2(assignmentPath, {
|
|
16830
17404
|
cause: clearing ? "unpin" : "pin",
|
|
16831
17405
|
by: "human",
|
|
@@ -16865,7 +17439,7 @@ ${entry}`;
|
|
|
16865
17439
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
16866
17440
|
return;
|
|
16867
17441
|
}
|
|
16868
|
-
const assignmentPath =
|
|
17442
|
+
const assignmentPath = resolve24(resolved.assignmentDir, "assignment.md");
|
|
16869
17443
|
if (!await fileExists(assignmentPath)) {
|
|
16870
17444
|
res.status(404).json({ error: "Assignment not found" });
|
|
16871
17445
|
return;
|
|
@@ -16905,7 +17479,7 @@ ${entry}`;
|
|
|
16905
17479
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
16906
17480
|
return;
|
|
16907
17481
|
}
|
|
16908
|
-
const assignmentPath =
|
|
17482
|
+
const assignmentPath = resolve24(resolved.assignmentDir, "assignment.md");
|
|
16909
17483
|
if (!await fileExists(assignmentPath)) {
|
|
16910
17484
|
res.status(404).json({ error: "Assignment not found" });
|
|
16911
17485
|
return;
|
|
@@ -16938,7 +17512,7 @@ ${entry}`;
|
|
|
16938
17512
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
16939
17513
|
return;
|
|
16940
17514
|
}
|
|
16941
|
-
const assignmentPath =
|
|
17515
|
+
const assignmentPath = resolve24(resolved.assignmentDir, "assignment.md");
|
|
16942
17516
|
const currentContent = await readCurrentDocument(assignmentPath);
|
|
16943
17517
|
if (!currentContent) {
|
|
16944
17518
|
res.status(404).json({ error: "Assignment not found" });
|
|
@@ -16986,8 +17560,8 @@ ${entry}`;
|
|
|
16986
17560
|
}
|
|
16987
17561
|
const { recomputeAndWrite: recomputeAndWrite2, recomputeDependents: recomputeDependents2, resolveDeriveContext: resolveDeriveContext2 } = await Promise.resolve().then(() => (init_recompute(), recompute_exports));
|
|
16988
17562
|
const context = await resolveDeriveContext2();
|
|
16989
|
-
const byIdPath =
|
|
16990
|
-
const byIdProjectDir = resolved.standalone ? null :
|
|
17563
|
+
const byIdPath = resolve24(resolved.assignmentDir, "assignment.md");
|
|
17564
|
+
const byIdProjectDir = resolved.standalone ? null : resolve24(resolved.assignmentDir, "..", "..");
|
|
16991
17565
|
if (command === "block" || command === "unblock") {
|
|
16992
17566
|
const { updateAssignmentFile: updateAssignmentFile2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
|
|
16993
17567
|
const result = await recomputeAndWrite2(byIdPath, {
|
|
@@ -17108,7 +17682,7 @@ function validateTitleBody(body) {
|
|
|
17108
17682
|
return { ok: true, value: trimmed };
|
|
17109
17683
|
}
|
|
17110
17684
|
async function appendCommentTo(assignmentDir, assignmentRef, req2, res, reloadDetail) {
|
|
17111
|
-
const commentsPath =
|
|
17685
|
+
const commentsPath = resolve24(assignmentDir, "comments.md");
|
|
17112
17686
|
const { body, author, type, replyTo } = req2.body || {};
|
|
17113
17687
|
if (!body || typeof body !== "string" || !body.trim()) {
|
|
17114
17688
|
res.status(400).json({ error: "body is required" });
|
|
@@ -17155,7 +17729,7 @@ ${entry}`;
|
|
|
17155
17729
|
}
|
|
17156
17730
|
await writeFileForce(commentsPath, next);
|
|
17157
17731
|
try {
|
|
17158
|
-
const assignmentMdPath =
|
|
17732
|
+
const assignmentMdPath = resolve24(assignmentDir, "assignment.md");
|
|
17159
17733
|
if (await fileExists(assignmentMdPath)) {
|
|
17160
17734
|
const fm = parseAssignmentFull(await readFile18(assignmentMdPath, "utf-8"));
|
|
17161
17735
|
emitDashboardEvent(fm.id, fm.project, "comment-added", {
|
|
@@ -17172,7 +17746,7 @@ ${entry}`;
|
|
|
17172
17746
|
res.status(201).json({ assignment, comment: { id: comment.id } });
|
|
17173
17747
|
}
|
|
17174
17748
|
async function toggleCommentResolvedAt(assignmentDir, commentId, req2, res, reloadDetail) {
|
|
17175
|
-
const commentsPath =
|
|
17749
|
+
const commentsPath = resolve24(assignmentDir, "comments.md");
|
|
17176
17750
|
if (!await fileExists(commentsPath)) {
|
|
17177
17751
|
res.status(404).json({ error: "Comments file not found" });
|
|
17178
17752
|
return;
|
|
@@ -17206,7 +17780,7 @@ async function toggleCommentResolvedAt(assignmentDir, commentId, req2, res, relo
|
|
|
17206
17780
|
await writeFileForce(commentsPath, withUpdated);
|
|
17207
17781
|
if (target.resolved !== true && desired === true) {
|
|
17208
17782
|
try {
|
|
17209
|
-
const assignmentMdPath =
|
|
17783
|
+
const assignmentMdPath = resolve24(assignmentDir, "assignment.md");
|
|
17210
17784
|
if (await fileExists(assignmentMdPath)) {
|
|
17211
17785
|
const fm = parseAssignmentFull(await readFile18(assignmentMdPath, "utf-8"));
|
|
17212
17786
|
emitDashboardEvent(fm.id, fm.project, "comment-resolved", { commentId });
|
|
@@ -17338,9 +17912,12 @@ function createServersRouter(serversDir2, projectsDir, assignmentsDir2) {
|
|
|
17338
17912
|
// src/dashboard/api-agent-sessions.ts
|
|
17339
17913
|
init_agent_sessions();
|
|
17340
17914
|
init_fs();
|
|
17915
|
+
init_session_id();
|
|
17916
|
+
init_assignment_resolver();
|
|
17917
|
+
init_paths();
|
|
17341
17918
|
init_transcript();
|
|
17342
17919
|
import { Router as Router4 } from "express";
|
|
17343
|
-
import { resolve as
|
|
17920
|
+
import { resolve as resolve25 } from "path";
|
|
17344
17921
|
init_config2();
|
|
17345
17922
|
init_process_info();
|
|
17346
17923
|
init_git_worktree();
|
|
@@ -17364,7 +17941,7 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
|
|
|
17364
17941
|
try {
|
|
17365
17942
|
const { projectSlug } = req2.params;
|
|
17366
17943
|
const assignment = req2.query.assignment;
|
|
17367
|
-
const projectDir =
|
|
17944
|
+
const projectDir = resolve25(projectsDir, projectSlug);
|
|
17368
17945
|
if (!await fileExists(projectDir)) {
|
|
17369
17946
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
17370
17947
|
return;
|
|
@@ -17393,21 +17970,45 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
|
|
|
17393
17970
|
});
|
|
17394
17971
|
return;
|
|
17395
17972
|
}
|
|
17973
|
+
if (!isSafeSessionId(sessionId)) {
|
|
17974
|
+
res.status(400).json({
|
|
17975
|
+
error: "sessionId is malformed. Pass the real agent-generated session id."
|
|
17976
|
+
});
|
|
17977
|
+
return;
|
|
17978
|
+
}
|
|
17396
17979
|
if (projectSlug) {
|
|
17397
|
-
const projectDir =
|
|
17980
|
+
const projectDir = resolve25(projectsDir, projectSlug);
|
|
17398
17981
|
if (!await fileExists(projectDir)) {
|
|
17399
17982
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
17400
17983
|
return;
|
|
17401
17984
|
}
|
|
17402
17985
|
}
|
|
17986
|
+
let assignmentId = null;
|
|
17987
|
+
if (assignmentSlug) {
|
|
17988
|
+
const resolvedAssignment = await resolveAssignmentBySlug(
|
|
17989
|
+
projectsDir,
|
|
17990
|
+
assignmentsDir2 ?? assignmentsDir(),
|
|
17991
|
+
projectSlug || null,
|
|
17992
|
+
assignmentSlug
|
|
17993
|
+
);
|
|
17994
|
+
if (!resolvedAssignment.exists) {
|
|
17995
|
+
res.status(404).json({ error: `Assignment "${assignmentSlug}" not found` });
|
|
17996
|
+
return;
|
|
17997
|
+
}
|
|
17998
|
+
assignmentId = resolvedAssignment.id;
|
|
17999
|
+
}
|
|
17403
18000
|
const derivedPath = await derivePathFromTranscript(transcriptPath);
|
|
17404
18001
|
const recordedPath = derivedPath ?? path ?? "";
|
|
17405
18002
|
const pid = typeof rawPid === "number" && Number.isFinite(rawPid) && rawPid > 0 ? rawPid : null;
|
|
17406
18003
|
const pidStartedAt = pid !== null ? captureProcessStartedAt(pid) : null;
|
|
17407
18004
|
const originalHeadSha = isExistingDir(recordedPath) ? await captureHeadSha(recordedPath) : null;
|
|
17408
18005
|
const session = {
|
|
17409
|
-
|
|
18006
|
+
// L: a POST with no assignmentSlug is registration-only (unbound) — do
|
|
18007
|
+
// NOT open a project-bound engagement for an arbitrary session. Binding
|
|
18008
|
+
// requires a validated assignment selector (existence-checked above).
|
|
18009
|
+
projectSlug: assignmentSlug ? projectSlug || null : null,
|
|
17410
18010
|
assignmentSlug: assignmentSlug || null,
|
|
18011
|
+
assignmentId,
|
|
17411
18012
|
agent,
|
|
17412
18013
|
sessionId,
|
|
17413
18014
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17486,6 +18087,10 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
|
|
|
17486
18087
|
broadcast?.({ type: "agent-sessions-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
17487
18088
|
res.json({ updated: true });
|
|
17488
18089
|
} catch (error) {
|
|
18090
|
+
if (error instanceof SessionResurrectionError) {
|
|
18091
|
+
res.status(409).json({ error: error.message });
|
|
18092
|
+
return;
|
|
18093
|
+
}
|
|
17489
18094
|
res.status(500).json({ error: error instanceof Error ? error.message : "Update failed" });
|
|
17490
18095
|
}
|
|
17491
18096
|
});
|
|
@@ -17810,8 +18415,8 @@ import { Router as Router6 } from "express";
|
|
|
17810
18415
|
// src/utils/terminal-probe.ts
|
|
17811
18416
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
17812
18417
|
import { existsSync as existsSync2 } from "fs";
|
|
17813
|
-
import { homedir as
|
|
17814
|
-
import { join as
|
|
18418
|
+
import { homedir as homedir4 } from "os";
|
|
18419
|
+
import { join as join5 } from "path";
|
|
17815
18420
|
var APP_BUNDLE_IDS = {
|
|
17816
18421
|
"terminal-app": "com.apple.Terminal",
|
|
17817
18422
|
iterm: "com.googlecode.iterm2",
|
|
@@ -17828,7 +18433,7 @@ var APP_FIXED_PATHS = {
|
|
|
17828
18433
|
"terminal-app": "/System/Applications/Utilities/Terminal.app"
|
|
17829
18434
|
};
|
|
17830
18435
|
function defaultApplicationsDirs() {
|
|
17831
|
-
return ["/Applications",
|
|
18436
|
+
return ["/Applications", join5(homedir4(), "Applications")];
|
|
17832
18437
|
}
|
|
17833
18438
|
function findAppBundle(terminal, dirs = defaultApplicationsDirs()) {
|
|
17834
18439
|
const fixed = APP_FIXED_PATHS[terminal];
|
|
@@ -17836,7 +18441,7 @@ function findAppBundle(terminal, dirs = defaultApplicationsDirs()) {
|
|
|
17836
18441
|
const bundleName = APP_BUNDLE_NAMES[terminal];
|
|
17837
18442
|
if (bundleName) {
|
|
17838
18443
|
for (const dir of dirs) {
|
|
17839
|
-
const candidate =
|
|
18444
|
+
const candidate = join5(dir, bundleName);
|
|
17840
18445
|
if (existsSync2(candidate)) return candidate;
|
|
17841
18446
|
}
|
|
17842
18447
|
}
|
|
@@ -17866,11 +18471,11 @@ var defaultCmuxLsappinfoRunner = () => {
|
|
|
17866
18471
|
function resolveCmuxCli(applicationsDirsOverride, cliDirsOverride, lsappinfoRunnerOverride) {
|
|
17867
18472
|
const bundle = findAppBundle("cmux", applicationsDirsOverride);
|
|
17868
18473
|
if (bundle) {
|
|
17869
|
-
const cli =
|
|
18474
|
+
const cli = join5(bundle, "Contents/Resources/bin/cmux");
|
|
17870
18475
|
if (existsSync2(cli)) return cli;
|
|
17871
18476
|
}
|
|
17872
18477
|
for (const dir of cliDirsOverride ?? CMUX_CLI_DIRS) {
|
|
17873
|
-
const cli =
|
|
18478
|
+
const cli = join5(dir, "cmux");
|
|
17874
18479
|
if (existsSync2(cli)) return cli;
|
|
17875
18480
|
}
|
|
17876
18481
|
if (process.platform === "darwin") {
|
|
@@ -17879,7 +18484,7 @@ function resolveCmuxCli(applicationsDirsOverride, cliDirsOverride, lsappinfoRunn
|
|
|
17879
18484
|
if (stdout) {
|
|
17880
18485
|
const match = stdout.match(/"LSBundlePath"="([^"]+)"/);
|
|
17881
18486
|
if (match) {
|
|
17882
|
-
const cli =
|
|
18487
|
+
const cli = join5(match[1], "Contents/Resources/bin/cmux");
|
|
17883
18488
|
if (existsSync2(cli)) return cli;
|
|
17884
18489
|
}
|
|
17885
18490
|
}
|
|
@@ -18018,7 +18623,7 @@ init_agents_schema();
|
|
|
18018
18623
|
init_cwd();
|
|
18019
18624
|
import { spawn as spawn2 } from "child_process";
|
|
18020
18625
|
import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
|
|
18021
|
-
import { isAbsolute as isAbsolute5, resolve as
|
|
18626
|
+
import { isAbsolute as isAbsolute5, resolve as resolve26 } from "path";
|
|
18022
18627
|
init_paths();
|
|
18023
18628
|
init_playbooks();
|
|
18024
18629
|
init_cwd();
|
|
@@ -18255,7 +18860,7 @@ async function resolveSessionPlan(input, terminal) {
|
|
|
18255
18860
|
// src/launch/execute.ts
|
|
18256
18861
|
import { spawn as spawn3 } from "child_process";
|
|
18257
18862
|
import { homedir as homedir5 } from "os";
|
|
18258
|
-
import { basename as basename5, join as join6, resolve as
|
|
18863
|
+
import { basename as basename5, join as join6, resolve as resolve27 } from "path";
|
|
18259
18864
|
init_fs();
|
|
18260
18865
|
init_config2();
|
|
18261
18866
|
init_session_id();
|
|
@@ -18303,7 +18908,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
18303
18908
|
`Spawn failed: ${msg}. Verify the terminal is installed and on PATH.`
|
|
18304
18909
|
);
|
|
18305
18910
|
}
|
|
18306
|
-
await new Promise((
|
|
18911
|
+
await new Promise((resolve49, reject) => {
|
|
18307
18912
|
let settled = false;
|
|
18308
18913
|
let stderr = "";
|
|
18309
18914
|
const finishOk = () => {
|
|
@@ -18313,7 +18918,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
18313
18918
|
child.unref();
|
|
18314
18919
|
} catch {
|
|
18315
18920
|
}
|
|
18316
|
-
|
|
18921
|
+
resolve49();
|
|
18317
18922
|
};
|
|
18318
18923
|
const finishErr = (remediation) => {
|
|
18319
18924
|
if (settled) return;
|
|
@@ -18374,7 +18979,7 @@ async function registerLaunchAtBirth(plan, child) {
|
|
|
18374
18979
|
markerDir
|
|
18375
18980
|
);
|
|
18376
18981
|
if (!sessionId) return;
|
|
18377
|
-
if (autoTrack === "workspaces-only" && !await fileExists(
|
|
18982
|
+
if (autoTrack === "workspaces-only" && !await fileExists(resolve27(plan.cwd, ".syntaur", "context.json"))) {
|
|
18378
18983
|
return;
|
|
18379
18984
|
}
|
|
18380
18985
|
const { initSessionDb: initSessionDb2 } = await Promise.resolve().then(() => (init_session_db(), session_db_exports));
|
|
@@ -18550,8 +19155,8 @@ function appleScriptString(value) {
|
|
|
18550
19155
|
init_paths();
|
|
18551
19156
|
init_fs();
|
|
18552
19157
|
import { fileURLToPath } from "url";
|
|
18553
|
-
import { dirname as dirname6, resolve as
|
|
18554
|
-
import { realpathSync, readFileSync as
|
|
19158
|
+
import { dirname as dirname6, resolve as resolve28, join as join7 } from "path";
|
|
19159
|
+
import { realpathSync, readFileSync as readFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
18555
19160
|
|
|
18556
19161
|
// src/dashboard/api-launch-preflight.ts
|
|
18557
19162
|
init_assignment_resolver();
|
|
@@ -19617,7 +20222,7 @@ import { Router as Router12 } from "express";
|
|
|
19617
20222
|
init_paths();
|
|
19618
20223
|
import Database3 from "better-sqlite3";
|
|
19619
20224
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
19620
|
-
import { resolve as
|
|
20225
|
+
import { resolve as resolve29 } from "path";
|
|
19621
20226
|
var db3 = null;
|
|
19622
20227
|
var LEASE_SCHEMA_VERSION = "1";
|
|
19623
20228
|
var SCHEMA_SQL3 = `
|
|
@@ -19704,7 +20309,7 @@ function isBusyError(err) {
|
|
|
19704
20309
|
}
|
|
19705
20310
|
function initLeasesDb(dbPath) {
|
|
19706
20311
|
if (db3) return db3;
|
|
19707
|
-
const finalPath = dbPath ??
|
|
20312
|
+
const finalPath = dbPath ?? resolve29(syntaurRoot(), "syntaur.db");
|
|
19708
20313
|
db3 = new Database3(finalPath);
|
|
19709
20314
|
db3.pragma("journal_mode = WAL");
|
|
19710
20315
|
db3.pragma("busy_timeout = 5000");
|
|
@@ -19857,14 +20462,14 @@ init_paths();
|
|
|
19857
20462
|
init_timestamp();
|
|
19858
20463
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
19859
20464
|
import { readFile as readFile20, readdir as readdir14, rm as rm3 } from "fs/promises";
|
|
19860
|
-
import { resolve as
|
|
20465
|
+
import { resolve as resolve30 } from "path";
|
|
19861
20466
|
function schedulesDir() {
|
|
19862
|
-
const
|
|
19863
|
-
if (
|
|
19864
|
-
return
|
|
20467
|
+
const override3 = process.env.SYNTAUR_SCHEDULES_DIR;
|
|
20468
|
+
if (override3 && override3.length > 0) return resolve30(override3);
|
|
20469
|
+
return resolve30(syntaurRoot(), "schedules");
|
|
19865
20470
|
}
|
|
19866
20471
|
function jobPath(id) {
|
|
19867
|
-
return
|
|
20472
|
+
return resolve30(schedulesDir(), `${jobFileSafe(id)}.md`);
|
|
19868
20473
|
}
|
|
19869
20474
|
function jobFileSafe(id) {
|
|
19870
20475
|
if (!/^[A-Za-z0-9_-]+$/.test(id)) {
|
|
@@ -20007,7 +20612,7 @@ async function listJobs() {
|
|
|
20007
20612
|
for (const name of names) {
|
|
20008
20613
|
if (!name.endsWith(".md")) continue;
|
|
20009
20614
|
try {
|
|
20010
|
-
jobs.push(parseJobFile(await readFile20(
|
|
20615
|
+
jobs.push(parseJobFile(await readFile20(resolve30(schedulesDir(), name), "utf-8")));
|
|
20011
20616
|
} catch {
|
|
20012
20617
|
}
|
|
20013
20618
|
}
|
|
@@ -20018,12 +20623,12 @@ async function listJobs() {
|
|
|
20018
20623
|
// src/schedules/event-log.ts
|
|
20019
20624
|
init_timestamp();
|
|
20020
20625
|
import { appendFile, readFile as readFile21, mkdir as mkdir3 } from "fs/promises";
|
|
20021
|
-
import { dirname as dirname8, resolve as
|
|
20626
|
+
import { dirname as dirname8, resolve as resolve31 } from "path";
|
|
20022
20627
|
function eventLogPath(jobId) {
|
|
20023
20628
|
if (!/^[A-Za-z0-9_-]+$/.test(jobId)) {
|
|
20024
20629
|
throw new Error(`Unsafe schedule id: ${JSON.stringify(jobId)}`);
|
|
20025
20630
|
}
|
|
20026
|
-
return
|
|
20631
|
+
return resolve31(schedulesDir(), `${jobId}.jsonl`);
|
|
20027
20632
|
}
|
|
20028
20633
|
async function appendEvent(jobId, type, data) {
|
|
20029
20634
|
const event = { type, at: nowTimestamp(), ...data ? { data } : {} };
|
|
@@ -20056,7 +20661,7 @@ async function readEvents(jobId) {
|
|
|
20056
20661
|
// src/schedules/attempt.ts
|
|
20057
20662
|
import { createHash as createHash3 } from "crypto";
|
|
20058
20663
|
import { open as open4, readFile as readFile22, stat as stat5, unlink as unlink6 } from "fs/promises";
|
|
20059
|
-
import { resolve as
|
|
20664
|
+
import { resolve as resolve32 } from "path";
|
|
20060
20665
|
|
|
20061
20666
|
// src/schedules/triggers.ts
|
|
20062
20667
|
import { Cron } from "croner";
|
|
@@ -20235,7 +20840,7 @@ var LOCK_WAIT_MS2 = 50;
|
|
|
20235
20840
|
var LOCK_MAX_WAITS2 = 100;
|
|
20236
20841
|
var MAX_CONSUMED_EDGES = 50;
|
|
20237
20842
|
async function acquireJobLock(id) {
|
|
20238
|
-
const lockPath =
|
|
20843
|
+
const lockPath = resolve32(schedulesDir(), `${id}.lock`);
|
|
20239
20844
|
const token = `${process.pid}:${createHash3("sha256").update(`${Math.random()}${Date.now()}`).digest("hex").slice(0, 12)}`;
|
|
20240
20845
|
for (let attempt = 0; attempt <= LOCK_MAX_WAITS2; attempt++) {
|
|
20241
20846
|
try {
|
|
@@ -20550,7 +21155,7 @@ async function reapStale(jobs, deps) {
|
|
|
20550
21155
|
|
|
20551
21156
|
// src/schedules/unattended.ts
|
|
20552
21157
|
import { existsSync as existsSync3 } from "fs";
|
|
20553
|
-
import { resolve as
|
|
21158
|
+
import { resolve as resolve33 } from "path";
|
|
20554
21159
|
var UnattendedRefusalError = class extends Error {
|
|
20555
21160
|
constructor(message) {
|
|
20556
21161
|
super(message);
|
|
@@ -20566,7 +21171,7 @@ function assertUnattendedTerminalSupported(terminal) {
|
|
|
20566
21171
|
}
|
|
20567
21172
|
function isKillSwitchEngaged() {
|
|
20568
21173
|
if (process.env.SYNTAUR_SCHEDULES_DISABLED === "1") return true;
|
|
20569
|
-
return existsSync3(
|
|
21174
|
+
return existsSync3(resolve33(schedulesDir(), "KILL"));
|
|
20570
21175
|
}
|
|
20571
21176
|
function canFire(job, deps) {
|
|
20572
21177
|
const killSwitch = deps.killSwitch ?? isKillSwitchEngaged;
|
|
@@ -20718,25 +21323,25 @@ init_paths();
|
|
|
20718
21323
|
init_assignment_resolver();
|
|
20719
21324
|
init_frontmatter();
|
|
20720
21325
|
import { readFile as readFile23 } from "fs/promises";
|
|
20721
|
-
import { resolve as
|
|
21326
|
+
import { resolve as resolve34 } from "path";
|
|
20722
21327
|
|
|
20723
21328
|
// src/schedules/launch-ack.ts
|
|
20724
21329
|
init_session_id();
|
|
20725
|
-
import { readdirSync } from "fs";
|
|
21330
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
20726
21331
|
import { homedir as homedir6 } from "os";
|
|
20727
21332
|
import { join as join8 } from "path";
|
|
20728
21333
|
function defaultSleep(ms) {
|
|
20729
21334
|
return new Promise((r) => setTimeout(r, ms));
|
|
20730
21335
|
}
|
|
20731
21336
|
function runtimeSessionsDir() {
|
|
20732
|
-
const
|
|
20733
|
-
return
|
|
21337
|
+
const override3 = process.env.SYNTAUR_RUNTIME_SESSIONS_DIR;
|
|
21338
|
+
return override3 && override3.length > 0 ? override3 : join8(homedir6(), ".syntaur", "runtime", "sessions");
|
|
20734
21339
|
}
|
|
20735
21340
|
function defaultAckProbe(handle) {
|
|
20736
21341
|
const dir = runtimeSessionsDir();
|
|
20737
21342
|
let names;
|
|
20738
21343
|
try {
|
|
20739
|
-
names =
|
|
21344
|
+
names = readdirSync2(dir);
|
|
20740
21345
|
} catch {
|
|
20741
21346
|
return null;
|
|
20742
21347
|
}
|
|
@@ -20778,7 +21383,7 @@ async function defaultReadAssignment(assignmentId) {
|
|
|
20778
21383
|
const resolved = await resolveAssignmentById(projectsDir, assignmentsDir(), assignmentId);
|
|
20779
21384
|
if (!resolved) return null;
|
|
20780
21385
|
try {
|
|
20781
|
-
const content = await readFile23(
|
|
21386
|
+
const content = await readFile23(resolve34(resolved.assignmentDir, "assignment.md"), "utf-8");
|
|
20782
21387
|
return parseAssignmentFrontmatter(content);
|
|
20783
21388
|
} catch {
|
|
20784
21389
|
return null;
|
|
@@ -20870,7 +21475,7 @@ import { Router as Router14 } from "express";
|
|
|
20870
21475
|
// src/db/usage-db.ts
|
|
20871
21476
|
init_paths();
|
|
20872
21477
|
import Database4 from "better-sqlite3";
|
|
20873
|
-
import { resolve as
|
|
21478
|
+
import { resolve as resolve35 } from "path";
|
|
20874
21479
|
var db4 = null;
|
|
20875
21480
|
var USAGE_SCHEMA_VERSION = "1";
|
|
20876
21481
|
var SCHEMA_SQL4 = `
|
|
@@ -20944,7 +21549,7 @@ function nowIso2() {
|
|
|
20944
21549
|
}
|
|
20945
21550
|
function initUsageDb(dbPath) {
|
|
20946
21551
|
if (db4) return db4;
|
|
20947
|
-
const finalPath = dbPath ??
|
|
21552
|
+
const finalPath = dbPath ?? resolve35(syntaurRoot(), "syntaur.db");
|
|
20948
21553
|
db4 = new Database4(finalPath);
|
|
20949
21554
|
db4.pragma("journal_mode = WAL");
|
|
20950
21555
|
db4.pragma("busy_timeout = 5000");
|
|
@@ -21121,6 +21726,199 @@ function listDistinctTools() {
|
|
|
21121
21726
|
|
|
21122
21727
|
// src/dashboard/api-usage.ts
|
|
21123
21728
|
init_api();
|
|
21729
|
+
|
|
21730
|
+
// src/usage/engagement-cost.ts
|
|
21731
|
+
init_session_db();
|
|
21732
|
+
init_engagement_tokens();
|
|
21733
|
+
|
|
21734
|
+
// src/usage/pricing.ts
|
|
21735
|
+
var PER_MILLION = 1e6;
|
|
21736
|
+
var MODEL_PRICING = {
|
|
21737
|
+
// Moonshot Kimi K2.6 — the model pi emits today. Official Moonshot list price.
|
|
21738
|
+
// source: https://platform.moonshot.ai/ (official) — cross-checked
|
|
21739
|
+
// https://openrouter.ai/moonshotai/kimi-k2.6 (retrieved 2026-06-17)
|
|
21740
|
+
"moonshotai/kimi-k2.6": { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0.95 },
|
|
21741
|
+
// Moonshot Kimi K2.5 — prior Kimi model. Official Moonshot list price.
|
|
21742
|
+
// source: https://platform.moonshot.ai/ — cross-checked
|
|
21743
|
+
// https://openrouter.ai/moonshotai/kimi-k2.5 (retrieved 2026-06-17)
|
|
21744
|
+
"moonshotai/kimi-k2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0.6 },
|
|
21745
|
+
// Z.ai (Zhipu) GLM-5.2 — official z.ai platform list price.
|
|
21746
|
+
// source: https://docs.z.ai/guides/overview/pricing — cross-checked
|
|
21747
|
+
// https://openrouter.ai/z-ai/glm-5 (retrieved 2026-06-18)
|
|
21748
|
+
"zai-org/glm-5.2": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 1.4 },
|
|
21749
|
+
// MiniMax M2.5 — official MiniMax platform list price. No separately-pinned
|
|
21750
|
+
// cached-read rate is published, so cacheRead is set conservatively to the
|
|
21751
|
+
// input rate (upper bound); revise if MiniMax publishes a cache rate.
|
|
21752
|
+
// source: https://platform.minimax.io/docs/guides/pricing-token-plan
|
|
21753
|
+
// — cross-checked https://openrouter.ai/minimax/minimax-m2.5 (retrieved 2026-06-18)
|
|
21754
|
+
"minimaxai/minimax-m2.5": { input: 0.15, output: 0.9, cacheRead: 0.15, cacheWrite: 0.15 }
|
|
21755
|
+
// NOTE: opaque Synthetic tier aliases like `syn:large:text` have no public
|
|
21756
|
+
// per-token rate (they route to whatever Synthetic assigns), so they remain
|
|
21757
|
+
// unpriced (→ $0). Reseller discounts (e.g. DeepInfra K2.6 0.75/3.50/0.15) are
|
|
21758
|
+
// rejected by the canonical-source rule and are NOT used here.
|
|
21759
|
+
};
|
|
21760
|
+
function normalizeModelKey(model) {
|
|
21761
|
+
return model.replace(/^\s*\[[^\]]*\]\s*/, "").replace(/^hf:/i, "").trim().toLowerCase();
|
|
21762
|
+
}
|
|
21763
|
+
function priceForModel(model, tokens) {
|
|
21764
|
+
const rate = MODEL_PRICING[normalizeModelKey(model)];
|
|
21765
|
+
if (!rate) return null;
|
|
21766
|
+
return (tokens.inputTokens * rate.input + tokens.outputTokens * rate.output + tokens.cacheReadTokens * rate.cacheRead + tokens.cacheCreationTokens * rate.cacheWrite) / PER_MILLION;
|
|
21767
|
+
}
|
|
21768
|
+
|
|
21769
|
+
// src/usage/engagement-cost.ts
|
|
21770
|
+
var EMPTY = {
|
|
21771
|
+
cost: 0,
|
|
21772
|
+
pricedWindowCount: 0,
|
|
21773
|
+
uncomputableWindowCount: 0,
|
|
21774
|
+
negativeDeltaCount: 0
|
|
21775
|
+
};
|
|
21776
|
+
function engagementDb() {
|
|
21777
|
+
let db5;
|
|
21778
|
+
try {
|
|
21779
|
+
db5 = getSessionDb();
|
|
21780
|
+
} catch {
|
|
21781
|
+
return null;
|
|
21782
|
+
}
|
|
21783
|
+
const has = db5.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'engagement'").get();
|
|
21784
|
+
return has ? db5 : null;
|
|
21785
|
+
}
|
|
21786
|
+
function sinceBound(since) {
|
|
21787
|
+
return since ? `${since}T00:00:00.000Z` : void 0;
|
|
21788
|
+
}
|
|
21789
|
+
function untilBound(until) {
|
|
21790
|
+
return until ? `${until}T23:59:59.999Z` : void 0;
|
|
21791
|
+
}
|
|
21792
|
+
function toBuckets(open6, close) {
|
|
21793
|
+
const o = open6 ?? { input: 0, output: 0, cacheCreation: 0, cacheRead: 0, total: 0, cost: 0 };
|
|
21794
|
+
const c = close ?? { input: 0, output: 0, cacheCreation: 0, cacheRead: 0, total: 0, cost: 0 };
|
|
21795
|
+
return {
|
|
21796
|
+
inputTokens: Math.max(0, c.input - o.input),
|
|
21797
|
+
outputTokens: Math.max(0, c.output - o.output),
|
|
21798
|
+
cacheCreationTokens: Math.max(0, c.cacheCreation - o.cacheCreation),
|
|
21799
|
+
cacheReadTokens: Math.max(0, c.cacheRead - o.cacheRead)
|
|
21800
|
+
};
|
|
21801
|
+
}
|
|
21802
|
+
function windowCost(openJson, closeJson, modelFilter) {
|
|
21803
|
+
const open6 = parseSnapshot(openJson);
|
|
21804
|
+
const close = parseSnapshot(closeJson);
|
|
21805
|
+
if (!open6 || !close) return null;
|
|
21806
|
+
let cost = 0;
|
|
21807
|
+
let hadNegative = false;
|
|
21808
|
+
const models = /* @__PURE__ */ new Set([
|
|
21809
|
+
...Object.keys(open6.models),
|
|
21810
|
+
...Object.keys(close.models)
|
|
21811
|
+
]);
|
|
21812
|
+
for (const model of models) {
|
|
21813
|
+
if (modelFilter && model !== modelFilter) continue;
|
|
21814
|
+
const openCost = open6.models[model]?.cost ?? 0;
|
|
21815
|
+
const closeCost = close.models[model]?.cost ?? 0;
|
|
21816
|
+
let contribution = closeCost - openCost;
|
|
21817
|
+
if (contribution < 0) {
|
|
21818
|
+
hadNegative = true;
|
|
21819
|
+
contribution = 0;
|
|
21820
|
+
} else if (contribution === 0) {
|
|
21821
|
+
const buckets = toBuckets(open6.models[model], close.models[model]);
|
|
21822
|
+
if (buckets.inputTokens + buckets.outputTokens + buckets.cacheCreationTokens + buckets.cacheReadTokens > 0) {
|
|
21823
|
+
const priced = priceForModel(model, buckets);
|
|
21824
|
+
if (priced && priced > 0) contribution = priced;
|
|
21825
|
+
}
|
|
21826
|
+
}
|
|
21827
|
+
cost += contribution;
|
|
21828
|
+
}
|
|
21829
|
+
return { cost, hadNegative };
|
|
21830
|
+
}
|
|
21831
|
+
function rollupWindows(rows, modelFilter) {
|
|
21832
|
+
const out = { ...EMPTY };
|
|
21833
|
+
for (const row of rows) {
|
|
21834
|
+
const wc = windowCost(row.tokens_at_open, row.tokens_at_close, modelFilter);
|
|
21835
|
+
if (wc === null) {
|
|
21836
|
+
out.uncomputableWindowCount += 1;
|
|
21837
|
+
continue;
|
|
21838
|
+
}
|
|
21839
|
+
out.pricedWindowCount += 1;
|
|
21840
|
+
out.cost += wc.cost;
|
|
21841
|
+
if (wc.hadNegative) out.negativeDeltaCount += 1;
|
|
21842
|
+
}
|
|
21843
|
+
return out;
|
|
21844
|
+
}
|
|
21845
|
+
function assignmentWindowCost(opts) {
|
|
21846
|
+
const db5 = engagementDb();
|
|
21847
|
+
if (!db5) return { ...EMPTY };
|
|
21848
|
+
const clauses = ["ended_at IS NOT NULL"];
|
|
21849
|
+
const params2 = [];
|
|
21850
|
+
if (opts.assignmentId) {
|
|
21851
|
+
clauses.push("assignment_id = ?");
|
|
21852
|
+
params2.push(opts.assignmentId);
|
|
21853
|
+
} else {
|
|
21854
|
+
const proj = opts.projectSlug && opts.projectSlug.length > 0 ? opts.projectSlug : null;
|
|
21855
|
+
if (proj === null) {
|
|
21856
|
+
clauses.push("project_slug IS NULL");
|
|
21857
|
+
} else {
|
|
21858
|
+
clauses.push("project_slug = ?");
|
|
21859
|
+
params2.push(proj);
|
|
21860
|
+
}
|
|
21861
|
+
clauses.push("assignment_slug = ?");
|
|
21862
|
+
params2.push(opts.assignmentSlug ?? null);
|
|
21863
|
+
}
|
|
21864
|
+
const since = sinceBound(opts.since);
|
|
21865
|
+
const until = untilBound(opts.until);
|
|
21866
|
+
if (since) {
|
|
21867
|
+
clauses.push("ended_at >= ?");
|
|
21868
|
+
params2.push(since);
|
|
21869
|
+
}
|
|
21870
|
+
if (until) {
|
|
21871
|
+
clauses.push("ended_at <= ?");
|
|
21872
|
+
params2.push(until);
|
|
21873
|
+
}
|
|
21874
|
+
const rows = db5.prepare(
|
|
21875
|
+
`SELECT assignment_id, assignment_slug, tokens_at_open, tokens_at_close
|
|
21876
|
+
FROM engagement
|
|
21877
|
+
WHERE ${clauses.join(" AND ")}`
|
|
21878
|
+
).all(...params2);
|
|
21879
|
+
return rollupWindows(rows, opts.model);
|
|
21880
|
+
}
|
|
21881
|
+
function projectWindowCosts(opts) {
|
|
21882
|
+
const db5 = engagementDb();
|
|
21883
|
+
if (!db5) return /* @__PURE__ */ new Map();
|
|
21884
|
+
const clauses = ["ended_at IS NOT NULL", "project_slug = ?", "assignment_slug IS NOT NULL"];
|
|
21885
|
+
const params2 = [opts.projectSlug];
|
|
21886
|
+
const since = sinceBound(opts.since);
|
|
21887
|
+
const until = untilBound(opts.until);
|
|
21888
|
+
if (since) {
|
|
21889
|
+
clauses.push("ended_at >= ?");
|
|
21890
|
+
params2.push(since);
|
|
21891
|
+
}
|
|
21892
|
+
if (until) {
|
|
21893
|
+
clauses.push("ended_at <= ?");
|
|
21894
|
+
params2.push(until);
|
|
21895
|
+
}
|
|
21896
|
+
const rows = db5.prepare(
|
|
21897
|
+
`SELECT assignment_id, assignment_slug, tokens_at_open, tokens_at_close
|
|
21898
|
+
FROM engagement
|
|
21899
|
+
WHERE ${clauses.join(" AND ")}`
|
|
21900
|
+
).all(...params2);
|
|
21901
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
21902
|
+
const idForSlug = /* @__PURE__ */ new Map();
|
|
21903
|
+
for (const row of rows) {
|
|
21904
|
+
const slug = row.assignment_slug;
|
|
21905
|
+
const bucket = grouped.get(slug);
|
|
21906
|
+
if (bucket) bucket.push(row);
|
|
21907
|
+
else grouped.set(slug, [row]);
|
|
21908
|
+
if (row.assignment_id && !idForSlug.get(slug)) idForSlug.set(slug, row.assignment_id);
|
|
21909
|
+
}
|
|
21910
|
+
const out = /* @__PURE__ */ new Map();
|
|
21911
|
+
for (const [slug, bucket] of grouped) {
|
|
21912
|
+
out.set(slug, {
|
|
21913
|
+
assignmentSlug: slug,
|
|
21914
|
+
assignmentId: idForSlug.get(slug) ?? null,
|
|
21915
|
+
...rollupWindows(bucket, opts.model)
|
|
21916
|
+
});
|
|
21917
|
+
}
|
|
21918
|
+
return out;
|
|
21919
|
+
}
|
|
21920
|
+
|
|
21921
|
+
// src/dashboard/api-usage.ts
|
|
21124
21922
|
function createUsageRouter(projectsDir, assignmentsDir2) {
|
|
21125
21923
|
const router = Router14();
|
|
21126
21924
|
router.get("/facets", (_req, res) => {
|
|
@@ -21160,14 +21958,12 @@ function createUsageRouter(projectsDir, assignmentsDir2) {
|
|
|
21160
21958
|
try {
|
|
21161
21959
|
initUsageDb();
|
|
21162
21960
|
const projectSlug = req2.params.projectSlug;
|
|
21163
|
-
const
|
|
21164
|
-
|
|
21165
|
-
projectSlug
|
|
21166
|
-
});
|
|
21961
|
+
const common = extractCommonFilter(req2.query);
|
|
21962
|
+
const rows = listDaily({ ...common, projectSlug });
|
|
21167
21963
|
res.json({
|
|
21168
21964
|
projectSlug,
|
|
21169
21965
|
daily: rows,
|
|
21170
|
-
summary:
|
|
21966
|
+
summary: projectAssignmentRollup(projectSlug, rows, common)
|
|
21171
21967
|
});
|
|
21172
21968
|
} catch (error) {
|
|
21173
21969
|
res.status(500).json({
|
|
@@ -21193,7 +21989,13 @@ function createUsageRouter(projectsDir, assignmentsDir2) {
|
|
|
21193
21989
|
assignmentSlug,
|
|
21194
21990
|
daily: dailyRows,
|
|
21195
21991
|
events: eventRows,
|
|
21196
|
-
summary: buildAssignmentSummary(dailyRows
|
|
21992
|
+
summary: buildAssignmentSummary(dailyRows, {
|
|
21993
|
+
projectSlug,
|
|
21994
|
+
assignmentSlug,
|
|
21995
|
+
since: common.since,
|
|
21996
|
+
until: common.until,
|
|
21997
|
+
model: common.model
|
|
21998
|
+
})
|
|
21197
21999
|
});
|
|
21198
22000
|
} catch (error) {
|
|
21199
22001
|
res.status(500).json({
|
|
@@ -21218,7 +22020,15 @@ function createUsageRouter(projectsDir, assignmentsDir2) {
|
|
|
21218
22020
|
assignmentId: assignmentSlug,
|
|
21219
22021
|
daily: dailyRows,
|
|
21220
22022
|
events: eventRows,
|
|
21221
|
-
|
|
22023
|
+
// Standalone: engagement stores `project_slug IS NULL` (the reader maps
|
|
22024
|
+
// a null/empty projectSlug to the NULL match).
|
|
22025
|
+
summary: buildAssignmentSummary(dailyRows, {
|
|
22026
|
+
projectSlug: null,
|
|
22027
|
+
assignmentSlug,
|
|
22028
|
+
since: common.since,
|
|
22029
|
+
until: common.until,
|
|
22030
|
+
model: common.model
|
|
22031
|
+
})
|
|
21222
22032
|
});
|
|
21223
22033
|
} catch (error) {
|
|
21224
22034
|
res.status(500).json({
|
|
@@ -21268,13 +22078,17 @@ function byModelBreakdown(rows) {
|
|
|
21268
22078
|
}
|
|
21269
22079
|
return [...map.values()].sort((a, b) => b.totalTokens - a.totalTokens);
|
|
21270
22080
|
}
|
|
21271
|
-
function buildAssignmentSummary(rows) {
|
|
22081
|
+
function buildAssignmentSummary(rows, costKey) {
|
|
21272
22082
|
const totals = summarize(rows, "assignment")[0];
|
|
22083
|
+
const windows = assignmentWindowCost(costKey);
|
|
21273
22084
|
return {
|
|
21274
22085
|
totalTokens: totals?.totalTokens ?? 0,
|
|
21275
|
-
totalCost:
|
|
22086
|
+
totalCost: windows.cost,
|
|
21276
22087
|
lastEventDay: totals?.lastEventDay ?? null,
|
|
21277
|
-
byModel: byModelBreakdown(rows)
|
|
22088
|
+
byModel: byModelBreakdown(rows),
|
|
22089
|
+
pricedWindowCount: windows.pricedWindowCount,
|
|
22090
|
+
uncomputableWindowCount: windows.uncomputableWindowCount,
|
|
22091
|
+
negativeDeltaCount: windows.negativeDeltaCount
|
|
21278
22092
|
};
|
|
21279
22093
|
}
|
|
21280
22094
|
function summarize(rows, mode) {
|
|
@@ -21298,27 +22112,70 @@ function summarize(rows, mode) {
|
|
|
21298
22112
|
}
|
|
21299
22113
|
return [...map.values()].sort((a, b) => b.totalTokens - a.totalTokens);
|
|
21300
22114
|
}
|
|
22115
|
+
function projectAssignmentRollup(projectSlug, rows, common) {
|
|
22116
|
+
const byAssignment = /* @__PURE__ */ new Map();
|
|
22117
|
+
for (const row of summarize(rows, "assignment")) {
|
|
22118
|
+
byAssignment.set(row.assignmentSlug, {
|
|
22119
|
+
...row,
|
|
22120
|
+
totalCost: 0,
|
|
22121
|
+
// Counts present on EVERY per-assignment row (a daily-only assignment with
|
|
22122
|
+
// no closed window stays at 0/0/0); window overlay below replaces them.
|
|
22123
|
+
pricedWindowCount: 0,
|
|
22124
|
+
uncomputableWindowCount: 0,
|
|
22125
|
+
negativeDeltaCount: 0
|
|
22126
|
+
});
|
|
22127
|
+
}
|
|
22128
|
+
const windows = projectWindowCosts({
|
|
22129
|
+
projectSlug,
|
|
22130
|
+
since: common.since,
|
|
22131
|
+
until: common.until,
|
|
22132
|
+
model: common.model
|
|
22133
|
+
});
|
|
22134
|
+
for (const [assignmentSlug, w] of windows) {
|
|
22135
|
+
const existing = byAssignment.get(assignmentSlug);
|
|
22136
|
+
if (existing) {
|
|
22137
|
+
existing.totalCost = w.cost;
|
|
22138
|
+
existing.pricedWindowCount = w.pricedWindowCount;
|
|
22139
|
+
existing.uncomputableWindowCount = w.uncomputableWindowCount;
|
|
22140
|
+
existing.negativeDeltaCount = w.negativeDeltaCount;
|
|
22141
|
+
} else {
|
|
22142
|
+
byAssignment.set(assignmentSlug, {
|
|
22143
|
+
projectSlug,
|
|
22144
|
+
assignmentSlug,
|
|
22145
|
+
totalTokens: 0,
|
|
22146
|
+
totalCost: w.cost,
|
|
22147
|
+
lastEventDay: "",
|
|
22148
|
+
pricedWindowCount: w.pricedWindowCount,
|
|
22149
|
+
uncomputableWindowCount: w.uncomputableWindowCount,
|
|
22150
|
+
negativeDeltaCount: w.negativeDeltaCount
|
|
22151
|
+
});
|
|
22152
|
+
}
|
|
22153
|
+
}
|
|
22154
|
+
return [...byAssignment.values()].sort(
|
|
22155
|
+
(a, b) => b.totalCost - a.totalCost || b.totalTokens - a.totalTokens
|
|
22156
|
+
);
|
|
22157
|
+
}
|
|
21301
22158
|
|
|
21302
22159
|
// src/dashboard/api-events.ts
|
|
21303
22160
|
init_fs();
|
|
21304
22161
|
init_parser();
|
|
21305
22162
|
init_events_db();
|
|
21306
22163
|
import { Router as Router15 } from "express";
|
|
21307
|
-
import { resolve as
|
|
22164
|
+
import { resolve as resolve36 } from "path";
|
|
21308
22165
|
import { readFile as readFile24 } from "fs/promises";
|
|
21309
22166
|
function createEventsRouter(projectsDir, assignmentsDir2) {
|
|
21310
22167
|
const router = Router15();
|
|
21311
22168
|
router.get("/projects/:slug/assignments/:aslug/events", async (req2, res) => {
|
|
21312
22169
|
try {
|
|
21313
22170
|
const { slug, aslug } = req2.params;
|
|
21314
|
-
const assignmentMdPath =
|
|
22171
|
+
const assignmentMdPath = resolve36(
|
|
21315
22172
|
projectsDir,
|
|
21316
22173
|
slug,
|
|
21317
22174
|
"assignments",
|
|
21318
22175
|
aslug,
|
|
21319
22176
|
"assignment.md"
|
|
21320
22177
|
);
|
|
21321
|
-
const id = await
|
|
22178
|
+
const id = await readAssignmentId2(assignmentMdPath);
|
|
21322
22179
|
if (!id) {
|
|
21323
22180
|
res.json({ events: [] });
|
|
21324
22181
|
return;
|
|
@@ -21332,8 +22189,8 @@ function createEventsRouter(projectsDir, assignmentsDir2) {
|
|
|
21332
22189
|
router.get("/standalone/assignments/:id/events", async (req2, res) => {
|
|
21333
22190
|
try {
|
|
21334
22191
|
const { id: dirId } = req2.params;
|
|
21335
|
-
const assignmentMdPath =
|
|
21336
|
-
const id = await
|
|
22192
|
+
const assignmentMdPath = resolve36(assignmentsDir2, dirId, "assignment.md");
|
|
22193
|
+
const id = await readAssignmentId2(assignmentMdPath) ?? dirId;
|
|
21337
22194
|
res.json({ events: loadEvents(id, req2.query) });
|
|
21338
22195
|
} catch (error) {
|
|
21339
22196
|
console.warn("[events] failed to list standalone events:", error);
|
|
@@ -21342,7 +22199,7 @@ function createEventsRouter(projectsDir, assignmentsDir2) {
|
|
|
21342
22199
|
});
|
|
21343
22200
|
return router;
|
|
21344
22201
|
}
|
|
21345
|
-
async function
|
|
22202
|
+
async function readAssignmentId2(assignmentMdPath) {
|
|
21346
22203
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
21347
22204
|
const content = await readFile24(assignmentMdPath, "utf-8");
|
|
21348
22205
|
const [fm] = extractFrontmatter2(content);
|
|
@@ -21389,7 +22246,7 @@ init_assignment_walk();
|
|
|
21389
22246
|
init_parser();
|
|
21390
22247
|
init_facts();
|
|
21391
22248
|
init_state_machine();
|
|
21392
|
-
import { resolve as
|
|
22249
|
+
import { resolve as resolve37 } from "path";
|
|
21393
22250
|
import { readFile as readFile25 } from "fs/promises";
|
|
21394
22251
|
|
|
21395
22252
|
// src/inbox/types.ts
|
|
@@ -21570,7 +22427,7 @@ async function computeInbox(opts) {
|
|
|
21570
22427
|
if (opts.project !== void 0 && entry.projectSlug !== opts.project) continue;
|
|
21571
22428
|
let parsed;
|
|
21572
22429
|
try {
|
|
21573
|
-
const content = await readFile25(
|
|
22430
|
+
const content = await readFile25(resolve37(entry.assignmentDir, "assignment.md"), "utf-8");
|
|
21574
22431
|
parsed = parseAssignmentFull(content);
|
|
21575
22432
|
} catch {
|
|
21576
22433
|
continue;
|
|
@@ -21613,7 +22470,7 @@ async function computeInbox(opts) {
|
|
|
21613
22470
|
});
|
|
21614
22471
|
}
|
|
21615
22472
|
if (!typeFilter || typeFilter.has("question")) {
|
|
21616
|
-
const commentsPath =
|
|
22473
|
+
const commentsPath = resolve37(entry.assignmentDir, "comments.md");
|
|
21617
22474
|
if (await fileExists(commentsPath)) {
|
|
21618
22475
|
try {
|
|
21619
22476
|
const content = await readFile25(commentsPath, "utf-8");
|
|
@@ -21727,7 +22584,7 @@ init_slug();
|
|
|
21727
22584
|
init_timestamp();
|
|
21728
22585
|
init_fs();
|
|
21729
22586
|
import { Router as Router17 } from "express";
|
|
21730
|
-
import { resolve as
|
|
22587
|
+
import { resolve as resolve38 } from "path";
|
|
21731
22588
|
import { readFile as readFile26 } from "fs/promises";
|
|
21732
22589
|
init_playbooks();
|
|
21733
22590
|
function statusForPlaybookError(code) {
|
|
@@ -21810,7 +22667,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
21810
22667
|
res.status(404).json({ error: `Playbook "${req2.params.slug}" not found` });
|
|
21811
22668
|
return;
|
|
21812
22669
|
}
|
|
21813
|
-
const filePath =
|
|
22670
|
+
const filePath = resolve38(playbooksDir2, resolved.filename);
|
|
21814
22671
|
const content = await readFile26(filePath, "utf-8");
|
|
21815
22672
|
res.json({
|
|
21816
22673
|
documentType: "playbook",
|
|
@@ -21836,7 +22693,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
21836
22693
|
return;
|
|
21837
22694
|
}
|
|
21838
22695
|
await ensureDir(playbooksDir2);
|
|
21839
|
-
const filePath =
|
|
22696
|
+
const filePath = resolve38(playbooksDir2, `${slug}.md`);
|
|
21840
22697
|
if (await fileExists(filePath)) {
|
|
21841
22698
|
res.status(409).json({ error: `Playbook "${slug}" already exists` });
|
|
21842
22699
|
return;
|
|
@@ -21860,7 +22717,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
21860
22717
|
res.status(404).json({ error: `Playbook "${req2.params.slug}" not found` });
|
|
21861
22718
|
return;
|
|
21862
22719
|
}
|
|
21863
|
-
const filePath =
|
|
22720
|
+
const filePath = resolve38(playbooksDir2, resolved.filename);
|
|
21864
22721
|
await writeFileForce(filePath, content);
|
|
21865
22722
|
await rebuildPlaybookManifest(playbooksDir2);
|
|
21866
22723
|
res.json({ slug: resolved.slug, path: filePath });
|
|
@@ -21923,7 +22780,7 @@ init_uuid();
|
|
|
21923
22780
|
init_paths();
|
|
21924
22781
|
init_fs();
|
|
21925
22782
|
init_config2();
|
|
21926
|
-
import { resolve as
|
|
22783
|
+
import { resolve as resolve39 } from "path";
|
|
21927
22784
|
async function createAssignmentCommand(title, options) {
|
|
21928
22785
|
if (!title.trim()) {
|
|
21929
22786
|
throw new Error("Assignment title cannot be empty.");
|
|
@@ -21998,14 +22855,14 @@ async function createAssignmentCommand(title, options) {
|
|
|
21998
22855
|
if (options.oneOff) {
|
|
21999
22856
|
const standaloneRoot = assignmentsDir();
|
|
22000
22857
|
folderName = id;
|
|
22001
|
-
assignmentDir =
|
|
22858
|
+
assignmentDir = resolve39(standaloneRoot, folderName);
|
|
22002
22859
|
projectSlug = null;
|
|
22003
22860
|
await ensureDir(standaloneRoot);
|
|
22004
22861
|
} else {
|
|
22005
22862
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
22006
22863
|
projectSlug = options.project;
|
|
22007
|
-
const projectDir =
|
|
22008
|
-
const projectMdPath =
|
|
22864
|
+
const projectDir = resolve39(baseDir, projectSlug);
|
|
22865
|
+
const projectMdPath = resolve39(projectDir, "project.md");
|
|
22009
22866
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
22010
22867
|
throw new Error(
|
|
22011
22868
|
`Project "${projectSlug}" not found at ${projectDir}.
|
|
@@ -22013,9 +22870,9 @@ Run 'syntaur create-project' first or use --one-off.`
|
|
|
22013
22870
|
);
|
|
22014
22871
|
}
|
|
22015
22872
|
if (dependsOn.length > 0) {
|
|
22016
|
-
const depDirBase =
|
|
22873
|
+
const depDirBase = resolve39(projectDir, "assignments");
|
|
22017
22874
|
for (const dep of dependsOn) {
|
|
22018
|
-
const depDir =
|
|
22875
|
+
const depDir = resolve39(depDirBase, dep);
|
|
22019
22876
|
if (!await fileExists(depDir)) {
|
|
22020
22877
|
console.warn(
|
|
22021
22878
|
`Warning: dependency "${dep}" does not exist in project "${projectSlug}" yet.`
|
|
@@ -22024,7 +22881,7 @@ Run 'syntaur create-project' first or use --one-off.`
|
|
|
22024
22881
|
}
|
|
22025
22882
|
}
|
|
22026
22883
|
folderName = assignmentSlug;
|
|
22027
|
-
assignmentDir =
|
|
22884
|
+
assignmentDir = resolve39(projectDir, "assignments", folderName);
|
|
22028
22885
|
}
|
|
22029
22886
|
if (await fileExists(assignmentDir)) {
|
|
22030
22887
|
throw new Error(
|
|
@@ -22036,7 +22893,7 @@ Use --slug to specify a different slug.`
|
|
|
22036
22893
|
const companionAssignmentRef = projectSlug === null ? id : assignmentSlug;
|
|
22037
22894
|
const files = [
|
|
22038
22895
|
[
|
|
22039
|
-
|
|
22896
|
+
resolve39(assignmentDir, "assignment.md"),
|
|
22040
22897
|
renderAssignment({
|
|
22041
22898
|
id,
|
|
22042
22899
|
slug: assignmentSlug,
|
|
@@ -22054,35 +22911,35 @@ Use --slug to specify a different slug.`
|
|
|
22054
22911
|
})
|
|
22055
22912
|
],
|
|
22056
22913
|
[
|
|
22057
|
-
|
|
22914
|
+
resolve39(assignmentDir, "scratchpad.md"),
|
|
22058
22915
|
renderScratchpad({
|
|
22059
22916
|
assignmentSlug: companionAssignmentRef,
|
|
22060
22917
|
timestamp
|
|
22061
22918
|
})
|
|
22062
22919
|
],
|
|
22063
22920
|
[
|
|
22064
|
-
|
|
22921
|
+
resolve39(assignmentDir, "handoff.md"),
|
|
22065
22922
|
renderHandoff({
|
|
22066
22923
|
assignmentSlug: companionAssignmentRef,
|
|
22067
22924
|
timestamp
|
|
22068
22925
|
})
|
|
22069
22926
|
],
|
|
22070
22927
|
[
|
|
22071
|
-
|
|
22928
|
+
resolve39(assignmentDir, "decision-record.md"),
|
|
22072
22929
|
renderDecisionRecord({
|
|
22073
22930
|
assignmentSlug: companionAssignmentRef,
|
|
22074
22931
|
timestamp
|
|
22075
22932
|
})
|
|
22076
22933
|
],
|
|
22077
22934
|
[
|
|
22078
|
-
|
|
22935
|
+
resolve39(assignmentDir, "progress.md"),
|
|
22079
22936
|
renderProgress({
|
|
22080
22937
|
assignment: companionAssignmentRef,
|
|
22081
22938
|
timestamp
|
|
22082
22939
|
})
|
|
22083
22940
|
],
|
|
22084
22941
|
[
|
|
22085
|
-
|
|
22942
|
+
resolve39(assignmentDir, "comments.md"),
|
|
22086
22943
|
renderComments({
|
|
22087
22944
|
assignment: companionAssignmentRef,
|
|
22088
22945
|
timestamp
|
|
@@ -22256,7 +23113,7 @@ import { raw } from "express";
|
|
|
22256
23113
|
|
|
22257
23114
|
// src/todos/attachments.ts
|
|
22258
23115
|
import { mkdir as mkdir4, readdir as readdir15, stat as stat6, rename as rename5, rm as rm4, unlink as unlink7, writeFile as writeFile6, cp } from "fs/promises";
|
|
22259
|
-
import { resolve as
|
|
23116
|
+
import { resolve as resolve40, basename as basename6, dirname as dirname9, extname } from "path";
|
|
22260
23117
|
|
|
22261
23118
|
// src/utils/proof-artifact-id.ts
|
|
22262
23119
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
@@ -22343,12 +23200,12 @@ function sanitizeAttachmentName(name) {
|
|
|
22343
23200
|
return n;
|
|
22344
23201
|
}
|
|
22345
23202
|
function attachmentsRootDir(todosDir2) {
|
|
22346
|
-
return
|
|
23203
|
+
return resolve40(todosDir2, "attachments");
|
|
22347
23204
|
}
|
|
22348
23205
|
function attachmentDirFor(todosDir2, scopeId, todoId) {
|
|
22349
23206
|
assertScope(scopeId);
|
|
22350
23207
|
assertTodoId(todoId);
|
|
22351
|
-
return
|
|
23208
|
+
return resolve40(attachmentsRootDir(todosDir2), scopeId, todoId);
|
|
22352
23209
|
}
|
|
22353
23210
|
async function dirExists(p) {
|
|
22354
23211
|
try {
|
|
@@ -22362,7 +23219,7 @@ async function writeAttachment(todosDir2, scopeId, todoId, originalName, bytes)
|
|
|
22362
23219
|
await mkdir4(dir, { recursive: true });
|
|
22363
23220
|
const id = generateArtifactId();
|
|
22364
23221
|
const filename = sanitizeAttachmentName(originalName);
|
|
22365
|
-
await writeFile6(
|
|
23222
|
+
await writeFile6(resolve40(dir, `${id}__${filename}`), bytes);
|
|
22366
23223
|
return {
|
|
22367
23224
|
id,
|
|
22368
23225
|
filename,
|
|
@@ -22387,7 +23244,7 @@ async function listAttachments(todosDir2, scopeId, todoId) {
|
|
|
22387
23244
|
if (!ATTACHMENT_ID_RE.test(id)) continue;
|
|
22388
23245
|
const filename = stored.slice(sep2 + 2);
|
|
22389
23246
|
try {
|
|
22390
|
-
const st = await stat6(
|
|
23247
|
+
const st = await stat6(resolve40(dir, stored));
|
|
22391
23248
|
if (!st.isFile()) continue;
|
|
22392
23249
|
out.push({ id, filename, mime: mimeForName(filename), size: st.size, createdAt: st.mtime.toISOString() });
|
|
22393
23250
|
} catch {
|
|
@@ -22398,7 +23255,7 @@ async function listAttachments(todosDir2, scopeId, todoId) {
|
|
|
22398
23255
|
}
|
|
22399
23256
|
async function readScopeAttachments(todosDir2, scopeId) {
|
|
22400
23257
|
assertScope(scopeId);
|
|
22401
|
-
const scopeDir =
|
|
23258
|
+
const scopeDir = resolve40(attachmentsRootDir(todosDir2), scopeId);
|
|
22402
23259
|
let todoIds;
|
|
22403
23260
|
try {
|
|
22404
23261
|
todoIds = await readdir15(scopeDir);
|
|
@@ -22426,7 +23283,7 @@ async function resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId) {
|
|
|
22426
23283
|
const stored = names.find((n) => n.startsWith(prefix));
|
|
22427
23284
|
if (!stored) return null;
|
|
22428
23285
|
const filename = stored.slice(prefix.length);
|
|
22429
|
-
return { path:
|
|
23286
|
+
return { path: resolve40(dir, stored), filename, mime: mimeForName(filename) };
|
|
22430
23287
|
}
|
|
22431
23288
|
async function deleteAttachment(todosDir2, scopeId, todoId, attachmentId) {
|
|
22432
23289
|
const resolved = await resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId);
|
|
@@ -22842,8 +23699,8 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
22842
23699
|
router.post("/:workspace/archive", async (req2, res) => {
|
|
22843
23700
|
try {
|
|
22844
23701
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
22845
|
-
const { resolve:
|
|
22846
|
-
const { readFile:
|
|
23702
|
+
const { resolve: resolve49 } = await import("path");
|
|
23703
|
+
const { readFile: readFile31 } = await import("fs/promises");
|
|
22847
23704
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
22848
23705
|
const workspace = getWorkspaceParam(req2.params.workspace);
|
|
22849
23706
|
const outcome = await wsLock(workspace, async () => {
|
|
@@ -22859,10 +23716,10 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
22859
23716
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
22860
23717
|
);
|
|
22861
23718
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
22862
|
-
await ensureDir(
|
|
23719
|
+
await ensureDir(resolve49(todosDir2, "archive"));
|
|
22863
23720
|
let archContent = "";
|
|
22864
23721
|
if (await fileExists(archFile)) {
|
|
22865
|
-
archContent = await
|
|
23722
|
+
archContent = await readFile31(archFile, "utf-8");
|
|
22866
23723
|
archContent = archContent.trimEnd() + "\n\n";
|
|
22867
23724
|
} else {
|
|
22868
23725
|
archContent = `---
|
|
@@ -23163,7 +24020,7 @@ workspace: ${workspace}
|
|
|
23163
24020
|
const { readConfig: readConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
23164
24021
|
const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
23165
24022
|
const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
23166
|
-
const { readFile:
|
|
24023
|
+
const { readFile: readFile31 } = await import("fs/promises");
|
|
23167
24024
|
const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
|
|
23168
24025
|
const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
|
|
23169
24026
|
let assignmentRef;
|
|
@@ -23184,7 +24041,7 @@ workspace: ${workspace}
|
|
|
23184
24041
|
}
|
|
23185
24042
|
const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
|
|
23186
24043
|
if (!await fileExists2(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
|
|
23187
|
-
let content = await
|
|
24044
|
+
let content = await readFile31(assignmentMdPath, "utf-8");
|
|
23188
24045
|
content = appendTodosToAssignmentBody2(
|
|
23189
24046
|
content,
|
|
23190
24047
|
items.map((it) => ({
|
|
@@ -23380,7 +24237,7 @@ init_paths();
|
|
|
23380
24237
|
init_slug();
|
|
23381
24238
|
import { Router as Router20 } from "express";
|
|
23382
24239
|
import { mkdir as mkdir6, readFile as readFile27, rename as rename7 } from "fs/promises";
|
|
23383
|
-
import { resolve as
|
|
24240
|
+
import { resolve as resolve41, dirname as dirname11 } from "path";
|
|
23384
24241
|
init_api();
|
|
23385
24242
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
23386
24243
|
function touchItem4(item) {
|
|
@@ -23396,7 +24253,7 @@ function params(req2) {
|
|
|
23396
24253
|
return req2.params;
|
|
23397
24254
|
}
|
|
23398
24255
|
async function projectExists(projectsDir, slug) {
|
|
23399
|
-
return fileExists(
|
|
24256
|
+
return fileExists(resolve41(projectsDir, slug, "project.md"));
|
|
23400
24257
|
}
|
|
23401
24258
|
async function ensureProjectTodosDir(projectsDir, slug) {
|
|
23402
24259
|
const todosDir2 = projectTodosDir(projectsDir, slug);
|
|
@@ -23413,7 +24270,7 @@ async function ensureProjectTodosDir(projectsDir, slug) {
|
|
|
23413
24270
|
throw err;
|
|
23414
24271
|
}
|
|
23415
24272
|
try {
|
|
23416
|
-
await mkdir6(
|
|
24273
|
+
await mkdir6(resolve41(todosDir2, "archive"), { recursive: false });
|
|
23417
24274
|
} catch (err) {
|
|
23418
24275
|
const code = err.code;
|
|
23419
24276
|
if (code === "EEXIST") return;
|
|
@@ -24092,15 +24949,15 @@ workspace: ${slug}
|
|
|
24092
24949
|
if (tg.includes("/")) {
|
|
24093
24950
|
const parts = tg.split("/");
|
|
24094
24951
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
24095
|
-
assignmentDir =
|
|
24952
|
+
assignmentDir = resolve41(projectsDir, parts[0], "assignments", parts[1]);
|
|
24096
24953
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
24097
24954
|
} 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)) {
|
|
24098
|
-
assignmentDir =
|
|
24955
|
+
assignmentDir = resolve41(assignmentsDirFn(), tg);
|
|
24099
24956
|
assignmentRef = tg;
|
|
24100
24957
|
} else {
|
|
24101
24958
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
24102
24959
|
}
|
|
24103
|
-
const assignmentMdPath =
|
|
24960
|
+
const assignmentMdPath = resolve41(assignmentDir, "assignment.md");
|
|
24104
24961
|
if (!await fileExists(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
|
|
24105
24962
|
let content = await readFile27(assignmentMdPath, "utf-8");
|
|
24106
24963
|
content = appendTodosToAssignmentBody2(
|
|
@@ -24482,7 +25339,7 @@ init_fs();
|
|
|
24482
25339
|
init_paths();
|
|
24483
25340
|
init_slug();
|
|
24484
25341
|
import { Router as Router22 } from "express";
|
|
24485
|
-
import { resolve as
|
|
25342
|
+
import { resolve as resolve42 } from "path";
|
|
24486
25343
|
init_parser2();
|
|
24487
25344
|
function deriveStatus2(bundle, items) {
|
|
24488
25345
|
const members = bundle.todoIds.map((id) => items.find((i) => i.id === id)).filter((i) => i !== void 0);
|
|
@@ -24524,7 +25381,7 @@ function createProjectBundlesRouter(projectsDir, broadcast) {
|
|
|
24524
25381
|
router.get("/", async (req2, res) => {
|
|
24525
25382
|
try {
|
|
24526
25383
|
const slug = getProjectIdParam2(req2.params.projectId);
|
|
24527
|
-
const projectMd =
|
|
25384
|
+
const projectMd = resolve42(projectsDir, slug, "project.md");
|
|
24528
25385
|
if (!await fileExists(projectMd)) {
|
|
24529
25386
|
notFound2(res, slug);
|
|
24530
25387
|
return;
|
|
@@ -24554,7 +25411,7 @@ init_config2();
|
|
|
24554
25411
|
import { execFile as execFile2 } from "child_process";
|
|
24555
25412
|
import { promisify as promisify2 } from "util";
|
|
24556
25413
|
import { cp as cp2, mkdtemp, rm as rm5, readFile as readFile29, writeFile as writeFile7, unlink as unlink8, stat as stat7, open as open5, rename as rename8 } from "fs/promises";
|
|
24557
|
-
import { resolve as
|
|
25414
|
+
import { resolve as resolve43, join as join9 } from "path";
|
|
24558
25415
|
import { tmpdir } from "os";
|
|
24559
25416
|
var exec2 = promisify2(execFile2);
|
|
24560
25417
|
var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
|
|
@@ -24594,7 +25451,7 @@ async function resolveCategoryPath(category) {
|
|
|
24594
25451
|
case "servers":
|
|
24595
25452
|
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
24596
25453
|
case "config":
|
|
24597
|
-
return { sourcePath:
|
|
25454
|
+
return { sourcePath: resolve43(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
24598
25455
|
}
|
|
24599
25456
|
}
|
|
24600
25457
|
async function checkGitInstalled() {
|
|
@@ -24605,7 +25462,7 @@ async function checkGitInstalled() {
|
|
|
24605
25462
|
}
|
|
24606
25463
|
}
|
|
24607
25464
|
async function acquireLock2() {
|
|
24608
|
-
const lockPath =
|
|
25465
|
+
const lockPath = resolve43(syntaurRoot(), LOCK_FILE_NAME);
|
|
24609
25466
|
await ensureDir(syntaurRoot());
|
|
24610
25467
|
try {
|
|
24611
25468
|
const handle = await open5(lockPath, "wx");
|
|
@@ -24652,7 +25509,7 @@ async function copyRecursive(src, dest) {
|
|
|
24652
25509
|
await ensureDir(dest);
|
|
24653
25510
|
await cp2(src, dest, { recursive: true, force: true });
|
|
24654
25511
|
} else {
|
|
24655
|
-
await ensureDir(
|
|
25512
|
+
await ensureDir(resolve43(dest, ".."));
|
|
24656
25513
|
await cp2(src, dest, { force: true });
|
|
24657
25514
|
}
|
|
24658
25515
|
}
|
|
@@ -24700,7 +25557,7 @@ async function backupToGithub(overrides) {
|
|
|
24700
25557
|
}
|
|
24701
25558
|
if (category === "config") {
|
|
24702
25559
|
const sanitized = await readSanitizedConfig(sourcePath);
|
|
24703
|
-
await ensureDir(
|
|
25560
|
+
await ensureDir(resolve43(destPath, ".."));
|
|
24704
25561
|
await writeFile7(destPath, sanitized, "utf-8");
|
|
24705
25562
|
} else {
|
|
24706
25563
|
await copyRecursive(sourcePath, destPath);
|
|
@@ -24754,7 +25611,7 @@ async function backupToGithub(overrides) {
|
|
|
24754
25611
|
}
|
|
24755
25612
|
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
24756
25613
|
if (isFile) {
|
|
24757
|
-
await ensureDir(
|
|
25614
|
+
await ensureDir(resolve43(localPath, ".."));
|
|
24758
25615
|
await cp2(repoSrcPath, localPath, { force: true });
|
|
24759
25616
|
return;
|
|
24760
25617
|
}
|
|
@@ -24855,7 +25712,7 @@ async function restoreFromGithub(overrides) {
|
|
|
24855
25712
|
}
|
|
24856
25713
|
async function getBackupStatus() {
|
|
24857
25714
|
const config = await readConfig();
|
|
24858
|
-
const lockPath =
|
|
25715
|
+
const lockPath = resolve43(syntaurRoot(), LOCK_FILE_NAME);
|
|
24859
25716
|
const locked = await fileExists(lockPath);
|
|
24860
25717
|
return {
|
|
24861
25718
|
repo: config.backup?.repo ?? null,
|
|
@@ -25338,7 +26195,7 @@ async function runCcusage(opts = {}) {
|
|
|
25338
26195
|
};
|
|
25339
26196
|
}
|
|
25340
26197
|
function runOnce(binary, args, env, timeoutMs, maxOutputBytes) {
|
|
25341
|
-
return new Promise((
|
|
26198
|
+
return new Promise((resolve49) => {
|
|
25342
26199
|
const child = spawn4(binary, args, {
|
|
25343
26200
|
env: env ?? process.env,
|
|
25344
26201
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -25352,7 +26209,7 @@ function runOnce(binary, args, env, timeoutMs, maxOutputBytes) {
|
|
|
25352
26209
|
if (settled) return;
|
|
25353
26210
|
settled = true;
|
|
25354
26211
|
clearTimeout(timer3);
|
|
25355
|
-
|
|
26212
|
+
resolve49(result);
|
|
25356
26213
|
};
|
|
25357
26214
|
const timer3 = setTimeout(() => {
|
|
25358
26215
|
timedOut = true;
|
|
@@ -25403,9 +26260,13 @@ function resolveAttribution(input, db5) {
|
|
|
25403
26260
|
const database = db5 ?? getSessionDb();
|
|
25404
26261
|
const direct = database.prepare(
|
|
25405
26262
|
`SELECT project_slug, assignment_slug
|
|
25406
|
-
FROM
|
|
25407
|
-
WHERE session_id =
|
|
25408
|
-
|
|
26263
|
+
FROM engagement
|
|
26264
|
+
WHERE session_id = ?
|
|
26265
|
+
AND julianday(started_at) <= julianday(?)
|
|
26266
|
+
AND (ended_at IS NULL OR julianday(ended_at) > julianday(?))
|
|
26267
|
+
ORDER BY julianday(started_at) DESC
|
|
26268
|
+
LIMIT 1`
|
|
26269
|
+
).get(input.sessionId, input.eventTs, input.eventTs);
|
|
25409
26270
|
if (direct) {
|
|
25410
26271
|
return {
|
|
25411
26272
|
projectSlug: direct.project_slug,
|
|
@@ -25414,25 +26275,43 @@ function resolveAttribution(input, db5) {
|
|
|
25414
26275
|
}
|
|
25415
26276
|
if (input.cwd) {
|
|
25416
26277
|
const exact = database.prepare(
|
|
25417
|
-
`SELECT project_slug, assignment_slug
|
|
25418
|
-
FROM sessions
|
|
25419
|
-
|
|
25420
|
-
|
|
25421
|
-
AND
|
|
25422
|
-
|
|
26278
|
+
`SELECT e.project_slug AS project_slug, e.assignment_slug AS assignment_slug
|
|
26279
|
+
FROM sessions s
|
|
26280
|
+
JOIN engagement e ON e.session_id = s.session_id
|
|
26281
|
+
WHERE s.path = ?
|
|
26282
|
+
AND julianday(s.started) <= julianday(?)
|
|
26283
|
+
AND (s.ended IS NULL OR julianday(s.ended) >= julianday(?))
|
|
26284
|
+
AND julianday(e.started_at) <= julianday(?)
|
|
26285
|
+
AND (e.ended_at IS NULL OR julianday(e.ended_at) > julianday(?))
|
|
26286
|
+
ORDER BY julianday(s.started) DESC, julianday(e.started_at) DESC
|
|
25423
26287
|
LIMIT 1`
|
|
25424
|
-
).get(
|
|
26288
|
+
).get(
|
|
26289
|
+
input.cwd,
|
|
26290
|
+
input.eventTs,
|
|
26291
|
+
input.eventTs,
|
|
26292
|
+
input.eventTs,
|
|
26293
|
+
input.eventTs
|
|
26294
|
+
);
|
|
25425
26295
|
if (exact) {
|
|
25426
26296
|
return { projectSlug: exact.project_slug, assignmentSlug: exact.assignment_slug };
|
|
25427
26297
|
}
|
|
25428
26298
|
if (input.eventTs.endsWith("T00:00:00.000Z")) {
|
|
25429
26299
|
const sameDay = database.prepare(
|
|
25430
|
-
`SELECT DISTINCT project_slug, assignment_slug
|
|
25431
|
-
FROM sessions
|
|
25432
|
-
|
|
25433
|
-
|
|
25434
|
-
AND
|
|
25435
|
-
|
|
26300
|
+
`SELECT DISTINCT e.project_slug AS project_slug, e.assignment_slug AS assignment_slug
|
|
26301
|
+
FROM sessions s
|
|
26302
|
+
JOIN engagement e ON e.session_id = s.session_id
|
|
26303
|
+
WHERE s.path = ?
|
|
26304
|
+
AND date(s.started) <= date(?)
|
|
26305
|
+
AND (s.ended IS NULL OR date(s.ended) >= date(?))
|
|
26306
|
+
AND date(e.started_at) <= date(?)
|
|
26307
|
+
AND (e.ended_at IS NULL OR date(e.ended_at) >= date(?))`
|
|
26308
|
+
).all(
|
|
26309
|
+
input.cwd,
|
|
26310
|
+
input.eventTs,
|
|
26311
|
+
input.eventTs,
|
|
26312
|
+
input.eventTs,
|
|
26313
|
+
input.eventTs
|
|
26314
|
+
);
|
|
25436
26315
|
if (sameDay.length === 1) {
|
|
25437
26316
|
return {
|
|
25438
26317
|
projectSlug: sameDay[0].project_slug,
|
|
@@ -25503,41 +26382,6 @@ function runRollup() {
|
|
|
25503
26382
|
return { daysComputed, rowsWritten };
|
|
25504
26383
|
}
|
|
25505
26384
|
|
|
25506
|
-
// src/usage/pricing.ts
|
|
25507
|
-
var PER_MILLION = 1e6;
|
|
25508
|
-
var MODEL_PRICING = {
|
|
25509
|
-
// Moonshot Kimi K2.6 — the model pi emits today. Official Moonshot list price.
|
|
25510
|
-
// source: https://platform.moonshot.ai/ (official) — cross-checked
|
|
25511
|
-
// https://openrouter.ai/moonshotai/kimi-k2.6 (retrieved 2026-06-17)
|
|
25512
|
-
"moonshotai/kimi-k2.6": { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0.95 },
|
|
25513
|
-
// Moonshot Kimi K2.5 — prior Kimi model. Official Moonshot list price.
|
|
25514
|
-
// source: https://platform.moonshot.ai/ — cross-checked
|
|
25515
|
-
// https://openrouter.ai/moonshotai/kimi-k2.5 (retrieved 2026-06-17)
|
|
25516
|
-
"moonshotai/kimi-k2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0.6 },
|
|
25517
|
-
// Z.ai (Zhipu) GLM-5.2 — official z.ai platform list price.
|
|
25518
|
-
// source: https://docs.z.ai/guides/overview/pricing — cross-checked
|
|
25519
|
-
// https://openrouter.ai/z-ai/glm-5 (retrieved 2026-06-18)
|
|
25520
|
-
"zai-org/glm-5.2": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 1.4 },
|
|
25521
|
-
// MiniMax M2.5 — official MiniMax platform list price. No separately-pinned
|
|
25522
|
-
// cached-read rate is published, so cacheRead is set conservatively to the
|
|
25523
|
-
// input rate (upper bound); revise if MiniMax publishes a cache rate.
|
|
25524
|
-
// source: https://platform.minimax.io/docs/guides/pricing-token-plan
|
|
25525
|
-
// — cross-checked https://openrouter.ai/minimax/minimax-m2.5 (retrieved 2026-06-18)
|
|
25526
|
-
"minimaxai/minimax-m2.5": { input: 0.15, output: 0.9, cacheRead: 0.15, cacheWrite: 0.15 }
|
|
25527
|
-
// NOTE: opaque Synthetic tier aliases like `syn:large:text` have no public
|
|
25528
|
-
// per-token rate (they route to whatever Synthetic assigns), so they remain
|
|
25529
|
-
// unpriced (→ $0). Reseller discounts (e.g. DeepInfra K2.6 0.75/3.50/0.15) are
|
|
25530
|
-
// rejected by the canonical-source rule and are NOT used here.
|
|
25531
|
-
};
|
|
25532
|
-
function normalizeModelKey(model) {
|
|
25533
|
-
return model.replace(/^\s*\[[^\]]*\]\s*/, "").replace(/^hf:/i, "").trim().toLowerCase();
|
|
25534
|
-
}
|
|
25535
|
-
function priceForModel(model, tokens) {
|
|
25536
|
-
const rate = MODEL_PRICING[normalizeModelKey(model)];
|
|
25537
|
-
if (!rate) return null;
|
|
25538
|
-
return (tokens.inputTokens * rate.input + tokens.outputTokens * rate.output + tokens.cacheReadTokens * rate.cacheRead + tokens.cacheCreationTokens * rate.cacheWrite) / PER_MILLION;
|
|
25539
|
-
}
|
|
25540
|
-
|
|
25541
26385
|
// src/usage/collect.ts
|
|
25542
26386
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
25543
26387
|
function formatDayFromCcusageDate(yyyymmdd) {
|
|
@@ -25756,7 +26600,7 @@ function createDashboardServer(options) {
|
|
|
25756
26600
|
(async () => {
|
|
25757
26601
|
try {
|
|
25758
26602
|
const configResult = await migrateLegacyConfig(
|
|
25759
|
-
|
|
26603
|
+
resolve48(syntaurRoot(), "config.md")
|
|
25760
26604
|
);
|
|
25761
26605
|
const projectResult = await migrateLegacyProjectFiles(projectsDir);
|
|
25762
26606
|
const summary = summarizeMigration(projectResult, configResult);
|
|
@@ -26279,14 +27123,14 @@ function createDashboardServer(options) {
|
|
|
26279
27123
|
app.use("/api/backup", createBackupRouter());
|
|
26280
27124
|
if (serveStaticUi && dashboardDistPath) {
|
|
26281
27125
|
const sendOpts = { dotfiles: "allow" };
|
|
26282
|
-
app.use("/assets", express.static(
|
|
27126
|
+
app.use("/assets", express.static(resolve48(dashboardDistPath, "assets"), sendOpts));
|
|
26283
27127
|
app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
|
|
26284
27128
|
app.get("{*path}", async (req2, res) => {
|
|
26285
27129
|
if (req2.path.startsWith("/api") || req2.path === "/ws" || req2.path.startsWith("/assets")) {
|
|
26286
27130
|
res.status(404).json({ error: "Not Found" });
|
|
26287
27131
|
return;
|
|
26288
27132
|
}
|
|
26289
|
-
const indexPath =
|
|
27133
|
+
const indexPath = resolve48(dashboardDistPath, "index.html");
|
|
26290
27134
|
if (!await fileExists(indexPath)) {
|
|
26291
27135
|
res.status(503).send(
|
|
26292
27136
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
@@ -26322,8 +27166,8 @@ function createDashboardServer(options) {
|
|
|
26322
27166
|
if (!await migrationGate()) return;
|
|
26323
27167
|
try {
|
|
26324
27168
|
const context = await resolveDeriveContext2();
|
|
26325
|
-
const projectDir = projectSlug ?
|
|
26326
|
-
const path = projectDir ?
|
|
27169
|
+
const projectDir = projectSlug ? resolve48(projectsDir, projectSlug) : null;
|
|
27170
|
+
const path = projectDir ? resolve48(projectDir, "assignments", assignmentSlug, "assignment.md") : resolve48(assignmentsDir2, assignmentSlug, "assignment.md");
|
|
26327
27171
|
if (!await fileExists(path)) return;
|
|
26328
27172
|
const result = await recomputeAndWrite2(path, {
|
|
26329
27173
|
cause: "derive",
|
|
@@ -26376,8 +27220,8 @@ function createDashboardServer(options) {
|
|
|
26376
27220
|
serversDir: serversDir2,
|
|
26377
27221
|
playbooksDir: playbooksDir2,
|
|
26378
27222
|
todosDir: todosDir2,
|
|
26379
|
-
dbPath:
|
|
26380
|
-
configPath:
|
|
27223
|
+
dbPath: resolve48(syntaurRoot(), "syntaur.db"),
|
|
27224
|
+
configPath: resolve48(syntaurRoot(), "config.md"),
|
|
26381
27225
|
onMessage: broadcast,
|
|
26382
27226
|
onAssignmentChanged: (projectSlug, assignmentSlug) => {
|
|
26383
27227
|
void recomputeOne(projectSlug, assignmentSlug);
|
|
@@ -26445,7 +27289,7 @@ function createDashboardServer(options) {
|
|
|
26445
27289
|
}
|
|
26446
27290
|
});
|
|
26447
27291
|
server.listen(port, () => {
|
|
26448
|
-
const portFile =
|
|
27292
|
+
const portFile = resolve48(syntaurRoot(), "dashboard-port");
|
|
26449
27293
|
writeFile8(portFile, String(port), "utf-8").catch(() => {
|
|
26450
27294
|
});
|
|
26451
27295
|
resolvePromise();
|
|
@@ -26469,7 +27313,7 @@ function createDashboardServer(options) {
|
|
|
26469
27313
|
client.terminate();
|
|
26470
27314
|
}
|
|
26471
27315
|
clients.clear();
|
|
26472
|
-
const portFile =
|
|
27316
|
+
const portFile = resolve48(syntaurRoot(), "dashboard-port");
|
|
26473
27317
|
await unlink9(portFile).catch(() => {
|
|
26474
27318
|
});
|
|
26475
27319
|
server.closeAllConnections?.();
|