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/launch/index.js
CHANGED
|
@@ -35,9 +35,9 @@ function expandHome(p) {
|
|
|
35
35
|
return p;
|
|
36
36
|
}
|
|
37
37
|
function syntaurRoot() {
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
return resolve(expandHome(
|
|
38
|
+
const override2 = process.env.SYNTAUR_HOME;
|
|
39
|
+
if (override2 && override2.length > 0) {
|
|
40
|
+
return resolve(expandHome(override2));
|
|
41
41
|
}
|
|
42
42
|
return resolve(homedir(), ".syntaur");
|
|
43
43
|
}
|
|
@@ -634,6 +634,7 @@ function parseAssignmentFrontmatter(fileContent) {
|
|
|
634
634
|
planApproval: parsePlanApproval(frontmatter),
|
|
635
635
|
parked: getField2("parked") === "true",
|
|
636
636
|
reviewRequested: getField2("reviewRequested") === "true",
|
|
637
|
+
reworkRequested: getField2("reworkRequested") === "true",
|
|
637
638
|
implementationStarted: getField2("implementationStarted") === "true",
|
|
638
639
|
override: parseOverride(frontmatter),
|
|
639
640
|
facts: parseFactsMap(frontmatter),
|
|
@@ -947,6 +948,7 @@ function parseAssignmentFull(fileContent) {
|
|
|
947
948
|
disposition: getField(fm, "disposition"),
|
|
948
949
|
parked: getField(fm, "parked") === "true",
|
|
949
950
|
reviewRequested: getField(fm, "reviewRequested") === "true",
|
|
951
|
+
reworkRequested: getField(fm, "reworkRequested") === "true",
|
|
950
952
|
implementationStarted: getField(fm, "implementationStarted") === "true",
|
|
951
953
|
planApproval: (() => {
|
|
952
954
|
const file = getNestedField(fm, "planApproval", "file");
|
|
@@ -1426,7 +1428,9 @@ var init_derive_config = __esm({
|
|
|
1426
1428
|
},
|
|
1427
1429
|
{
|
|
1428
1430
|
phase: "review",
|
|
1429
|
-
|
|
1431
|
+
// Rework (a new `implement` stage after `review`) must leave review even
|
|
1432
|
+
// when ACs stay checked — hence `AND NOT reworkRequested` (stage-fact-status-bridge).
|
|
1433
|
+
when: "(acAllChecked:true OR reviewRequested:true) AND NOT reworkRequested:true",
|
|
1430
1434
|
next: "Complete, or address review feedback"
|
|
1431
1435
|
}
|
|
1432
1436
|
],
|
|
@@ -1493,6 +1497,7 @@ var init_fields = __esm({
|
|
|
1493
1497
|
blocked: { kind: "bool" },
|
|
1494
1498
|
parked: { kind: "bool" },
|
|
1495
1499
|
reviewrequested: { kind: "bool", get: (i) => i["reviewRequested"] },
|
|
1500
|
+
reworkrequested: { kind: "bool", get: (i) => i["reworkRequested"] },
|
|
1496
1501
|
pinned: { kind: "bool" }
|
|
1497
1502
|
};
|
|
1498
1503
|
}
|
|
@@ -2272,6 +2277,7 @@ function queryFieldNames(declarations) {
|
|
|
2272
2277
|
"blocked",
|
|
2273
2278
|
"parked",
|
|
2274
2279
|
"reviewRequested",
|
|
2280
|
+
"reworkRequested",
|
|
2275
2281
|
"pinned"
|
|
2276
2282
|
];
|
|
2277
2283
|
const custom = [];
|
|
@@ -2310,6 +2316,7 @@ var init_fact_registry = __esm({
|
|
|
2310
2316
|
blocked: { kind: "bool" },
|
|
2311
2317
|
parked: { kind: "bool" },
|
|
2312
2318
|
reviewrequested: { kind: "bool", get: (i) => i["reviewRequested"] },
|
|
2319
|
+
reworkrequested: { kind: "bool", get: (i) => i["reworkRequested"] },
|
|
2313
2320
|
pinned: { kind: "bool" }
|
|
2314
2321
|
};
|
|
2315
2322
|
}
|
|
@@ -4344,7 +4351,7 @@ function compiledWhen(registry, when) {
|
|
|
4344
4351
|
return pred;
|
|
4345
4352
|
}
|
|
4346
4353
|
function deriveDimensions(input) {
|
|
4347
|
-
const { facts, derive, currentStatus, terminalStatuses, knownStatusIds, override } = input;
|
|
4354
|
+
const { facts, derive, currentStatus, terminalStatuses, knownStatusIds, override: override2 } = input;
|
|
4348
4355
|
const registry = input.registry ?? DERIVE_FIELDS;
|
|
4349
4356
|
if (terminalStatuses.has(currentStatus)) return null;
|
|
4350
4357
|
const ctx = { now: 0 };
|
|
@@ -4378,8 +4385,8 @@ function deriveDimensions(input) {
|
|
|
4378
4385
|
derivedStatus = phase;
|
|
4379
4386
|
}
|
|
4380
4387
|
let status = derivedStatus;
|
|
4381
|
-
if (
|
|
4382
|
-
status =
|
|
4388
|
+
if (override2 && override2.status && !terminalStatuses.has(override2.status) && knownStatusIds.has(override2.status)) {
|
|
4389
|
+
status = override2.status;
|
|
4383
4390
|
}
|
|
4384
4391
|
return { phase, disposition, derivedStatus, status, nextAction };
|
|
4385
4392
|
}
|
|
@@ -4670,6 +4677,7 @@ async function computeFactsDetailed(input) {
|
|
|
4670
4677
|
blocked: frontmatter.blockedReason !== null,
|
|
4671
4678
|
parked: frontmatter.parked,
|
|
4672
4679
|
reviewRequested: frontmatter.reviewRequested,
|
|
4680
|
+
reworkRequested: frontmatter.reworkRequested,
|
|
4673
4681
|
pinned: frontmatter.override !== null
|
|
4674
4682
|
};
|
|
4675
4683
|
const attestations = [];
|
|
@@ -4981,6 +4989,139 @@ var init_help = __esm({
|
|
|
4981
4989
|
}
|
|
4982
4990
|
});
|
|
4983
4991
|
|
|
4992
|
+
// src/db/engagement-schema.ts
|
|
4993
|
+
var ENGAGEMENT_SCHEMA_VERSION, ENGAGEMENT_DDL;
|
|
4994
|
+
var init_engagement_schema = __esm({
|
|
4995
|
+
"src/db/engagement-schema.ts"() {
|
|
4996
|
+
"use strict";
|
|
4997
|
+
ENGAGEMENT_SCHEMA_VERSION = "1";
|
|
4998
|
+
ENGAGEMENT_DDL = `
|
|
4999
|
+
CREATE TABLE IF NOT EXISTS engagement (
|
|
5000
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5001
|
+
session_id TEXT NOT NULL,
|
|
5002
|
+
assignment_id TEXT,
|
|
5003
|
+
project_slug TEXT,
|
|
5004
|
+
assignment_slug TEXT,
|
|
5005
|
+
stage TEXT NOT NULL DEFAULT 'implement',
|
|
5006
|
+
started_at TEXT NOT NULL,
|
|
5007
|
+
ended_at TEXT,
|
|
5008
|
+
tokens_at_open TEXT,
|
|
5009
|
+
tokens_at_close TEXT,
|
|
5010
|
+
close_reason TEXT
|
|
5011
|
+
);
|
|
5012
|
+
CREATE UNIQUE INDEX IF NOT EXISTS one_active_per_session
|
|
5013
|
+
ON engagement(session_id) WHERE ended_at IS NULL;
|
|
5014
|
+
CREATE INDEX IF NOT EXISTS idx_engagement_session ON engagement(session_id);
|
|
5015
|
+
CREATE INDEX IF NOT EXISTS idx_engagement_assignment ON engagement(assignment_id);
|
|
5016
|
+
CREATE INDEX IF NOT EXISTS idx_engagement_slug ON engagement(project_slug, assignment_slug);
|
|
5017
|
+
`;
|
|
5018
|
+
}
|
|
5019
|
+
});
|
|
5020
|
+
|
|
5021
|
+
// src/db/engagement-backfill.ts
|
|
5022
|
+
import { readdirSync, readFileSync, statSync as statSync2 } from "fs";
|
|
5023
|
+
import { resolve as resolve13 } from "path";
|
|
5024
|
+
function safeReaddir(dir) {
|
|
5025
|
+
try {
|
|
5026
|
+
return readdirSync(dir, { withFileTypes: true });
|
|
5027
|
+
} catch {
|
|
5028
|
+
return [];
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
function readAssignmentId(assignmentMdPath) {
|
|
5032
|
+
try {
|
|
5033
|
+
const fm = parseAssignmentFrontmatter(readFileSync(assignmentMdPath, "utf-8"));
|
|
5034
|
+
return fm.id || null;
|
|
5035
|
+
} catch {
|
|
5036
|
+
return null;
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
function transcriptMtimeIso(path) {
|
|
5040
|
+
if (!path) return null;
|
|
5041
|
+
try {
|
|
5042
|
+
return statSync2(path).mtime.toISOString();
|
|
5043
|
+
} catch {
|
|
5044
|
+
return null;
|
|
5045
|
+
}
|
|
5046
|
+
}
|
|
5047
|
+
function projectKey(projectSlug, assignmentSlug) {
|
|
5048
|
+
return JSON.stringify([projectSlug, assignmentSlug]);
|
|
5049
|
+
}
|
|
5050
|
+
function buildSlugIdMaps() {
|
|
5051
|
+
const root = syntaurRoot();
|
|
5052
|
+
const project = /* @__PURE__ */ new Map();
|
|
5053
|
+
const standalone = /* @__PURE__ */ new Map();
|
|
5054
|
+
const projectsDir = resolve13(root, "projects");
|
|
5055
|
+
for (const proj of safeReaddir(projectsDir)) {
|
|
5056
|
+
if (!proj.isDirectory()) continue;
|
|
5057
|
+
const asgRoot = resolve13(projectsDir, proj.name, "assignments");
|
|
5058
|
+
for (const asg of safeReaddir(asgRoot)) {
|
|
5059
|
+
if (!asg.isDirectory()) continue;
|
|
5060
|
+
const id = readAssignmentId(resolve13(asgRoot, asg.name, "assignment.md"));
|
|
5061
|
+
if (id) project.set(projectKey(proj.name, asg.name), id);
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
const standaloneDir = resolve13(root, "assignments");
|
|
5065
|
+
for (const asg of safeReaddir(standaloneDir)) {
|
|
5066
|
+
if (!asg.isDirectory()) continue;
|
|
5067
|
+
const id = readAssignmentId(resolve13(standaloneDir, asg.name, "assignment.md"));
|
|
5068
|
+
if (id) standalone.set(asg.name, id);
|
|
5069
|
+
}
|
|
5070
|
+
return { project, standalone };
|
|
5071
|
+
}
|
|
5072
|
+
function backfillEngagements(db2) {
|
|
5073
|
+
const { project, standalone } = buildSlugIdMaps();
|
|
5074
|
+
const sessions = db2.prepare(
|
|
5075
|
+
`SELECT session_id, project_slug, assignment_slug, status, started, ended,
|
|
5076
|
+
transcript_path, updated_at
|
|
5077
|
+
FROM sessions`
|
|
5078
|
+
).all();
|
|
5079
|
+
const insert = db2.prepare(
|
|
5080
|
+
`INSERT INTO engagement
|
|
5081
|
+
(session_id, assignment_id, project_slug, assignment_slug, stage,
|
|
5082
|
+
started_at, ended_at, tokens_at_open, tokens_at_close, close_reason)
|
|
5083
|
+
VALUES
|
|
5084
|
+
(@session_id, @assignment_id, @project_slug, @assignment_slug, 'implement',
|
|
5085
|
+
@started_at, @ended_at, NULL, NULL, @close_reason)`
|
|
5086
|
+
);
|
|
5087
|
+
const counts = { backfilled: 0, attributed: 0, unattributed: 0 };
|
|
5088
|
+
for (const s of sessions) {
|
|
5089
|
+
let assignmentId = null;
|
|
5090
|
+
if (s.project_slug && s.assignment_slug) {
|
|
5091
|
+
assignmentId = project.get(projectKey(s.project_slug, s.assignment_slug)) ?? null;
|
|
5092
|
+
} else if (!s.project_slug && s.assignment_slug) {
|
|
5093
|
+
assignmentId = standalone.get(s.assignment_slug) ?? null;
|
|
5094
|
+
}
|
|
5095
|
+
const isActive = s.status === "active";
|
|
5096
|
+
let endedAt = null;
|
|
5097
|
+
let closeReason = null;
|
|
5098
|
+
if (!isActive) {
|
|
5099
|
+
endedAt = s.ended ?? transcriptMtimeIso(s.transcript_path) ?? s.updated_at ?? s.started;
|
|
5100
|
+
closeReason = s.status === "completed" ? "completed" : "abandoned";
|
|
5101
|
+
}
|
|
5102
|
+
insert.run({
|
|
5103
|
+
session_id: s.session_id,
|
|
5104
|
+
assignment_id: assignmentId,
|
|
5105
|
+
project_slug: s.project_slug,
|
|
5106
|
+
assignment_slug: s.assignment_slug,
|
|
5107
|
+
started_at: s.started,
|
|
5108
|
+
ended_at: endedAt,
|
|
5109
|
+
close_reason: closeReason
|
|
5110
|
+
});
|
|
5111
|
+
counts.backfilled++;
|
|
5112
|
+
if (assignmentId) counts.attributed++;
|
|
5113
|
+
else counts.unattributed++;
|
|
5114
|
+
}
|
|
5115
|
+
return counts;
|
|
5116
|
+
}
|
|
5117
|
+
var init_engagement_backfill = __esm({
|
|
5118
|
+
"src/db/engagement-backfill.ts"() {
|
|
5119
|
+
"use strict";
|
|
5120
|
+
init_paths();
|
|
5121
|
+
init_frontmatter();
|
|
5122
|
+
}
|
|
5123
|
+
});
|
|
5124
|
+
|
|
4984
5125
|
// src/dashboard/session-db.ts
|
|
4985
5126
|
var session_db_exports = {};
|
|
4986
5127
|
__export(session_db_exports, {
|
|
@@ -4992,18 +5133,23 @@ __export(session_db_exports, {
|
|
|
4992
5133
|
resetSessionDb: () => resetSessionDb
|
|
4993
5134
|
});
|
|
4994
5135
|
import Database2 from "better-sqlite3";
|
|
4995
|
-
import { resolve as
|
|
5136
|
+
import { resolve as resolve14 } from "path";
|
|
4996
5137
|
import { readdir as readdir8 } from "fs/promises";
|
|
4997
5138
|
function initSessionDb(dbPath) {
|
|
4998
5139
|
if (db) return db;
|
|
4999
|
-
const finalPath = dbPath ??
|
|
5140
|
+
const finalPath = dbPath ?? resolve14(syntaurRoot(), "syntaur.db");
|
|
5000
5141
|
db = new Database2(finalPath);
|
|
5001
5142
|
db.pragma("journal_mode = WAL");
|
|
5002
5143
|
db.exec(SCHEMA_SQL);
|
|
5144
|
+
db.exec(ENGAGEMENT_DDL);
|
|
5003
5145
|
db.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
|
|
5004
5146
|
"schema_version",
|
|
5005
5147
|
SCHEMA_VERSION
|
|
5006
5148
|
);
|
|
5149
|
+
db.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
|
|
5150
|
+
"engagement_schema_version",
|
|
5151
|
+
ENGAGEMENT_SCHEMA_VERSION
|
|
5152
|
+
);
|
|
5007
5153
|
const database = db;
|
|
5008
5154
|
const runMigrations = database.transaction(() => {
|
|
5009
5155
|
const vBeforeV2 = database.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get()?.value;
|
|
@@ -5130,9 +5276,43 @@ function initSessionDb(dbPath) {
|
|
|
5130
5276
|
UPDATE meta SET value = '5' WHERE key = 'schema_version';
|
|
5131
5277
|
`);
|
|
5132
5278
|
}
|
|
5279
|
+
const vBeforeV6 = database.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get()?.value;
|
|
5280
|
+
if (vBeforeV6 === "5") {
|
|
5281
|
+
database.exec(ENGAGEMENT_DDL);
|
|
5282
|
+
const counts = backfillEngagements(database);
|
|
5283
|
+
database.exec(`
|
|
5284
|
+
CREATE TABLE sessions_v6 (
|
|
5285
|
+
session_id TEXT PRIMARY KEY,
|
|
5286
|
+
agent TEXT NOT NULL,
|
|
5287
|
+
started TEXT NOT NULL,
|
|
5288
|
+
ended TEXT,
|
|
5289
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
5290
|
+
path TEXT,
|
|
5291
|
+
description TEXT,
|
|
5292
|
+
transcript_path TEXT,
|
|
5293
|
+
pid INTEGER,
|
|
5294
|
+
pid_started_at TEXT,
|
|
5295
|
+
original_head_sha TEXT,
|
|
5296
|
+
activity TEXT,
|
|
5297
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
5298
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
5299
|
+
);
|
|
5300
|
+
INSERT INTO sessions_v6
|
|
5301
|
+
SELECT session_id, agent, started, ended, status, path, description,
|
|
5302
|
+
transcript_path, pid, pid_started_at, original_head_sha, NULL,
|
|
5303
|
+
created_at, updated_at
|
|
5304
|
+
FROM sessions;
|
|
5305
|
+
DROP TABLE sessions;
|
|
5306
|
+
ALTER TABLE sessions_v6 RENAME TO sessions;
|
|
5307
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
5308
|
+
UPDATE meta SET value = '6' WHERE key = 'schema_version';
|
|
5309
|
+
`);
|
|
5310
|
+
console.log(
|
|
5311
|
+
`engagement backfill: backfilled=${counts.backfilled} attributed=${counts.attributed} unattributed=${counts.unattributed}`
|
|
5312
|
+
);
|
|
5313
|
+
}
|
|
5133
5314
|
});
|
|
5134
5315
|
runMigrations.exclusive();
|
|
5135
|
-
db.exec(POST_MIGRATION_INDEXES_SQL);
|
|
5136
5316
|
return db;
|
|
5137
5317
|
}
|
|
5138
5318
|
function isSessionDbInitialized() {
|
|
@@ -5164,20 +5344,38 @@ async function migrateFromMarkdown(projectsDir) {
|
|
|
5164
5344
|
const allSessions = [];
|
|
5165
5345
|
for (const entry of entries) {
|
|
5166
5346
|
if (!entry.isDirectory()) continue;
|
|
5167
|
-
const projectDir =
|
|
5168
|
-
const indexPath =
|
|
5347
|
+
const projectDir = resolve14(projectsDir, entry.name);
|
|
5348
|
+
const indexPath = resolve14(projectDir, "_index-sessions.md");
|
|
5169
5349
|
if (!await fileExists(indexPath)) continue;
|
|
5170
5350
|
const sessions = await parseMarkdownSessionsIndex(indexPath, entry.name);
|
|
5171
5351
|
allSessions.push(...sessions);
|
|
5172
5352
|
}
|
|
5173
5353
|
if (allSessions.length === 0) return 0;
|
|
5174
5354
|
const insert = database.prepare(`
|
|
5175
|
-
INSERT OR IGNORE INTO sessions (session_id,
|
|
5176
|
-
VALUES (?, ?, ?, ?,
|
|
5355
|
+
INSERT OR IGNORE INTO sessions (session_id, agent, started, status, path)
|
|
5356
|
+
VALUES (?, ?, ?, ?, ?)
|
|
5357
|
+
`);
|
|
5358
|
+
const insertEngagement = database.prepare(`
|
|
5359
|
+
INSERT INTO engagement (session_id, project_slug, assignment_slug, stage, started_at, ended_at, close_reason)
|
|
5360
|
+
SELECT @sid, @ps, @as, 'implement', @started, @ended, @reason
|
|
5361
|
+
WHERE NOT EXISTS (
|
|
5362
|
+
SELECT 1 FROM engagement WHERE session_id = @sid AND ended_at IS NULL
|
|
5363
|
+
)
|
|
5177
5364
|
`);
|
|
5178
5365
|
const insertAll = database.transaction((sessions) => {
|
|
5179
5366
|
for (const s of sessions) {
|
|
5180
|
-
insert.run(s.sessionId, s.
|
|
5367
|
+
const res = insert.run(s.sessionId, s.agent, s.started, s.status, s.path);
|
|
5368
|
+
if (res.changes > 0 && (s.projectSlug || s.assignmentSlug)) {
|
|
5369
|
+
const terminal = s.status === "completed" || s.status === "stopped";
|
|
5370
|
+
insertEngagement.run({
|
|
5371
|
+
sid: s.sessionId,
|
|
5372
|
+
ps: s.projectSlug ?? null,
|
|
5373
|
+
as: s.assignmentSlug ?? null,
|
|
5374
|
+
started: s.started,
|
|
5375
|
+
ended: terminal ? s.started : null,
|
|
5376
|
+
reason: terminal ? s.status === "completed" ? "completed" : "abandoned" : null
|
|
5377
|
+
});
|
|
5378
|
+
}
|
|
5181
5379
|
}
|
|
5182
5380
|
});
|
|
5183
5381
|
insertAll(allSessions);
|
|
@@ -5220,19 +5418,19 @@ async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
|
5220
5418
|
}
|
|
5221
5419
|
return sessions;
|
|
5222
5420
|
}
|
|
5223
|
-
var db, SCHEMA_VERSION, SCHEMA_SQL
|
|
5421
|
+
var db, SCHEMA_VERSION, SCHEMA_SQL;
|
|
5224
5422
|
var init_session_db = __esm({
|
|
5225
5423
|
"src/dashboard/session-db.ts"() {
|
|
5226
5424
|
"use strict";
|
|
5227
5425
|
init_paths();
|
|
5228
5426
|
init_fs();
|
|
5427
|
+
init_engagement_schema();
|
|
5428
|
+
init_engagement_backfill();
|
|
5229
5429
|
db = null;
|
|
5230
|
-
SCHEMA_VERSION = "
|
|
5430
|
+
SCHEMA_VERSION = "6";
|
|
5231
5431
|
SCHEMA_SQL = `
|
|
5232
5432
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
5233
5433
|
session_id TEXT PRIMARY KEY,
|
|
5234
|
-
project_slug TEXT,
|
|
5235
|
-
assignment_slug TEXT,
|
|
5236
5434
|
agent TEXT NOT NULL,
|
|
5237
5435
|
started TEXT NOT NULL,
|
|
5238
5436
|
ended TEXT,
|
|
@@ -5243,34 +5441,172 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
5243
5441
|
pid INTEGER,
|
|
5244
5442
|
pid_started_at TEXT,
|
|
5245
5443
|
original_head_sha TEXT,
|
|
5444
|
+
activity TEXT,
|
|
5246
5445
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
5247
5446
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
5248
5447
|
);
|
|
5249
5448
|
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
5250
5449
|
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
|
|
5251
|
-
`;
|
|
5252
|
-
POST_MIGRATION_INDEXES_SQL = `
|
|
5253
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_slug);
|
|
5254
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(project_slug, assignment_slug);
|
|
5255
5450
|
`;
|
|
5256
5451
|
}
|
|
5257
5452
|
});
|
|
5258
5453
|
|
|
5454
|
+
// src/db/engagement-tokens.ts
|
|
5455
|
+
function serializeSnapshot(snapshot) {
|
|
5456
|
+
return snapshot === null ? null : JSON.stringify(snapshot);
|
|
5457
|
+
}
|
|
5458
|
+
function tableExists(name) {
|
|
5459
|
+
const row = getSessionDb().prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(name);
|
|
5460
|
+
return row !== void 0;
|
|
5461
|
+
}
|
|
5462
|
+
function getCumulativeTokenSource() {
|
|
5463
|
+
return override ?? productionCumulativeTokenSource;
|
|
5464
|
+
}
|
|
5465
|
+
var productionCumulativeTokenSource, override;
|
|
5466
|
+
var init_engagement_tokens = __esm({
|
|
5467
|
+
"src/db/engagement-tokens.ts"() {
|
|
5468
|
+
"use strict";
|
|
5469
|
+
init_session_db();
|
|
5470
|
+
productionCumulativeTokenSource = async (sessionId) => {
|
|
5471
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5472
|
+
const db2 = getSessionDb();
|
|
5473
|
+
const collectorRow = db2.prepare("SELECT value FROM meta WHERE key = 'usage_last_collector_run'").get();
|
|
5474
|
+
const collectorRunAt = collectorRow?.value ?? null;
|
|
5475
|
+
const models = {};
|
|
5476
|
+
if (tableExists("usage_events")) {
|
|
5477
|
+
const rows = db2.prepare(
|
|
5478
|
+
`SELECT model, input_tokens, output_tokens, cache_creation_tokens,
|
|
5479
|
+
cache_read_tokens, total_tokens, total_cost
|
|
5480
|
+
FROM usage_events
|
|
5481
|
+
WHERE session_id = ?`
|
|
5482
|
+
).all(sessionId);
|
|
5483
|
+
for (const r of rows) {
|
|
5484
|
+
models[r.model] = {
|
|
5485
|
+
input: r.input_tokens,
|
|
5486
|
+
output: r.output_tokens,
|
|
5487
|
+
cacheCreation: r.cache_creation_tokens,
|
|
5488
|
+
cacheRead: r.cache_read_tokens,
|
|
5489
|
+
total: r.total_tokens,
|
|
5490
|
+
cost: r.total_cost
|
|
5491
|
+
};
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
return { models, collectorRunAt, capturedAt };
|
|
5495
|
+
};
|
|
5496
|
+
override = null;
|
|
5497
|
+
}
|
|
5498
|
+
});
|
|
5499
|
+
|
|
5500
|
+
// src/db/engagement-db.ts
|
|
5501
|
+
function getOpenEngagement(sessionId) {
|
|
5502
|
+
const row = getSessionDb().prepare(
|
|
5503
|
+
"SELECT * FROM engagement WHERE session_id = ? AND ended_at IS NULL LIMIT 1"
|
|
5504
|
+
).get(sessionId);
|
|
5505
|
+
return row ?? null;
|
|
5506
|
+
}
|
|
5507
|
+
function getLatestEngagement(sessionId) {
|
|
5508
|
+
const row = getSessionDb().prepare(
|
|
5509
|
+
`SELECT * FROM engagement WHERE session_id = ?
|
|
5510
|
+
ORDER BY (ended_at IS NULL) DESC, started_at DESC, id DESC
|
|
5511
|
+
LIMIT 1`
|
|
5512
|
+
).get(sessionId);
|
|
5513
|
+
return row ?? null;
|
|
5514
|
+
}
|
|
5515
|
+
function hasAnyEngagement(sessionId) {
|
|
5516
|
+
return getSessionDb().prepare("SELECT 1 FROM engagement WHERE session_id = ? LIMIT 1").get(sessionId) !== void 0;
|
|
5517
|
+
}
|
|
5518
|
+
function insertClosedEngagement(input) {
|
|
5519
|
+
const res = getSessionDb().prepare(
|
|
5520
|
+
`INSERT INTO engagement
|
|
5521
|
+
(session_id, assignment_id, project_slug, assignment_slug, stage, started_at, ended_at, close_reason)
|
|
5522
|
+
VALUES (@sessionId, @assignmentId, @projectSlug, @assignmentSlug, @stage, @startedAt, @endedAt, @closeReason)`
|
|
5523
|
+
).run({
|
|
5524
|
+
sessionId: input.sessionId,
|
|
5525
|
+
assignmentId: input.assignmentId ?? null,
|
|
5526
|
+
projectSlug: input.projectSlug ?? null,
|
|
5527
|
+
assignmentSlug: input.assignmentSlug ?? null,
|
|
5528
|
+
stage: input.stage ?? DEFAULT_STAGE,
|
|
5529
|
+
startedAt: input.startedAt,
|
|
5530
|
+
endedAt: input.endedAt,
|
|
5531
|
+
closeReason: input.closeReason
|
|
5532
|
+
});
|
|
5533
|
+
return getEngagementById(Number(res.lastInsertRowid));
|
|
5534
|
+
}
|
|
5535
|
+
function getEngagementById(id) {
|
|
5536
|
+
const row = getSessionDb().prepare("SELECT * FROM engagement WHERE id = ?").get(id);
|
|
5537
|
+
if (!row) throw new Error(`engagement ${id} not found after insert`);
|
|
5538
|
+
return row;
|
|
5539
|
+
}
|
|
5540
|
+
function openEngagement(input) {
|
|
5541
|
+
const res = getSessionDb().prepare(
|
|
5542
|
+
`INSERT INTO engagement
|
|
5543
|
+
(session_id, assignment_id, project_slug, assignment_slug, stage, started_at, tokens_at_open)
|
|
5544
|
+
VALUES (@sessionId, @assignmentId, @projectSlug, @assignmentSlug, @stage, @startedAt, @tokensAtOpen)`
|
|
5545
|
+
).run({
|
|
5546
|
+
sessionId: input.sessionId,
|
|
5547
|
+
assignmentId: input.assignmentId ?? null,
|
|
5548
|
+
projectSlug: input.projectSlug ?? null,
|
|
5549
|
+
assignmentSlug: input.assignmentSlug ?? null,
|
|
5550
|
+
stage: input.stage ?? DEFAULT_STAGE,
|
|
5551
|
+
startedAt: input.startedAt,
|
|
5552
|
+
tokensAtOpen: serializeSnapshot(input.tokensAtOpen ?? null)
|
|
5553
|
+
});
|
|
5554
|
+
return getEngagementById(Number(res.lastInsertRowid));
|
|
5555
|
+
}
|
|
5556
|
+
function closeEngagementById(input) {
|
|
5557
|
+
const res = getSessionDb().prepare(
|
|
5558
|
+
`UPDATE engagement
|
|
5559
|
+
SET ended_at = @endedAt,
|
|
5560
|
+
tokens_at_close = @tokensAtClose,
|
|
5561
|
+
close_reason = @closeReason
|
|
5562
|
+
WHERE id = @id AND started_at = @startedAt AND ended_at IS NULL`
|
|
5563
|
+
).run({
|
|
5564
|
+
id: input.id,
|
|
5565
|
+
startedAt: input.startedAt,
|
|
5566
|
+
endedAt: input.endedAt,
|
|
5567
|
+
tokensAtClose: serializeSnapshot(input.tokensAtClose ?? null),
|
|
5568
|
+
closeReason: input.closeReason
|
|
5569
|
+
});
|
|
5570
|
+
return res.changes > 0;
|
|
5571
|
+
}
|
|
5572
|
+
function ensureOpenEngagement(input) {
|
|
5573
|
+
if (getOpenEngagement(input.sessionId)) return null;
|
|
5574
|
+
try {
|
|
5575
|
+
return openEngagement(input);
|
|
5576
|
+
} catch (err) {
|
|
5577
|
+
if (err instanceof Error && /UNIQUE constraint failed/.test(err.message)) {
|
|
5578
|
+
return null;
|
|
5579
|
+
}
|
|
5580
|
+
throw err;
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
var DEFAULT_STAGE;
|
|
5584
|
+
var init_engagement_db = __esm({
|
|
5585
|
+
"src/db/engagement-db.ts"() {
|
|
5586
|
+
"use strict";
|
|
5587
|
+
init_session_db();
|
|
5588
|
+
init_engagement_tokens();
|
|
5589
|
+
DEFAULT_STAGE = "implement";
|
|
5590
|
+
}
|
|
5591
|
+
});
|
|
5592
|
+
|
|
5259
5593
|
// src/dashboard/agent-sessions.ts
|
|
5260
5594
|
var agent_sessions_exports = {};
|
|
5261
5595
|
__export(agent_sessions_exports, {
|
|
5596
|
+
SessionResurrectionError: () => SessionResurrectionError,
|
|
5262
5597
|
appendSession: () => appendSession,
|
|
5263
5598
|
deleteSessions: () => deleteSessions,
|
|
5264
5599
|
getSessionById: () => getSessionById,
|
|
5265
5600
|
listAllSessions: () => listAllSessions,
|
|
5266
5601
|
listProjectSessions: () => listProjectSessions,
|
|
5267
5602
|
listSessionsByAssignment: () => listSessionsByAssignment,
|
|
5603
|
+
livenessStopSession: () => livenessStopSession,
|
|
5268
5604
|
parseSessionsIndex: () => parseSessionsIndex,
|
|
5269
5605
|
reconcileActiveSessions: () => reconcileActiveSessions,
|
|
5270
5606
|
updateSessionStatus: () => updateSessionStatus
|
|
5271
5607
|
});
|
|
5272
5608
|
import { readFile as readFile11 } from "fs/promises";
|
|
5273
|
-
import { resolve as
|
|
5609
|
+
import { resolve as resolve15 } from "path";
|
|
5274
5610
|
function rowToSession(row) {
|
|
5275
5611
|
return {
|
|
5276
5612
|
sessionId: row.session_id,
|
|
@@ -5286,28 +5622,64 @@ function rowToSession(row) {
|
|
|
5286
5622
|
pid: row.pid ?? null,
|
|
5287
5623
|
pidStartedAt: row.pid_started_at ?? null,
|
|
5288
5624
|
originalHeadSha: row.original_head_sha ?? null,
|
|
5625
|
+
activity: row.activity ?? null,
|
|
5289
5626
|
updatedAt: row.updated_at ?? null
|
|
5290
5627
|
};
|
|
5291
5628
|
}
|
|
5292
5629
|
async function parseSessionsIndex(_projectDir, projectSlug) {
|
|
5293
5630
|
const db2 = getSessionDb();
|
|
5294
|
-
const rows = db2.prepare(
|
|
5631
|
+
const rows = db2.prepare(`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? ORDER BY s.started DESC`).all(projectSlug);
|
|
5295
5632
|
return rows.map(rowToSession);
|
|
5296
5633
|
}
|
|
5634
|
+
function reopenEngagementIfMissing(sessionId, freshBinding, freshStartedAt, tokensAtOpen) {
|
|
5635
|
+
if (getOpenEngagement(sessionId)) return;
|
|
5636
|
+
if (freshBinding && (freshBinding.projectSlug || freshBinding.assignmentSlug)) {
|
|
5637
|
+
ensureOpenEngagement({
|
|
5638
|
+
sessionId,
|
|
5639
|
+
assignmentId: freshBinding.assignmentId ?? null,
|
|
5640
|
+
projectSlug: freshBinding.projectSlug,
|
|
5641
|
+
assignmentSlug: freshBinding.assignmentSlug,
|
|
5642
|
+
stage: "implement",
|
|
5643
|
+
startedAt: freshStartedAt,
|
|
5644
|
+
tokensAtOpen: tokensAtOpen ?? null
|
|
5645
|
+
});
|
|
5646
|
+
return;
|
|
5647
|
+
}
|
|
5648
|
+
const latest = getLatestEngagement(sessionId);
|
|
5649
|
+
if (latest && (latest.project_slug || latest.assignment_slug)) {
|
|
5650
|
+
ensureOpenEngagement({
|
|
5651
|
+
sessionId,
|
|
5652
|
+
assignmentId: latest.assignment_id,
|
|
5653
|
+
projectSlug: latest.project_slug,
|
|
5654
|
+
assignmentSlug: latest.assignment_slug,
|
|
5655
|
+
stage: "implement",
|
|
5656
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5657
|
+
tokensAtOpen: tokensAtOpen ?? null
|
|
5658
|
+
});
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
5297
5661
|
async function appendSession(_projectDir, session, opts) {
|
|
5298
5662
|
const db2 = getSessionDb();
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5663
|
+
let openSnapshot = null;
|
|
5664
|
+
try {
|
|
5665
|
+
openSnapshot = await getCumulativeTokenSource()(session.sessionId);
|
|
5666
|
+
} catch {
|
|
5667
|
+
openSnapshot = null;
|
|
5668
|
+
}
|
|
5669
|
+
const upsert = db2.prepare(`
|
|
5670
|
+
INSERT INTO sessions (session_id, agent, started, status, path, description, transcript_path, pid, pid_started_at, original_head_sha)
|
|
5671
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5302
5672
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
5303
|
-
project_slug = COALESCE(NULLIF(excluded.project_slug, ''), project_slug),
|
|
5304
|
-
assignment_slug = COALESCE(NULLIF(excluded.assignment_slug, ''), assignment_slug),
|
|
5305
5673
|
agent = excluded.agent,
|
|
5306
5674
|
status = CASE
|
|
5307
5675
|
WHEN status = 'completed' THEN status
|
|
5308
5676
|
WHEN status = 'stopped' AND NOT (? AND excluded.status = 'active') THEN status
|
|
5309
5677
|
ELSE excluded.status
|
|
5310
5678
|
END,
|
|
5679
|
+
ended = CASE
|
|
5680
|
+
WHEN status = 'stopped' AND ? AND excluded.status = 'active' THEN NULL
|
|
5681
|
+
ELSE ended
|
|
5682
|
+
END,
|
|
5311
5683
|
path = COALESCE(NULLIF(excluded.path, ''), path),
|
|
5312
5684
|
description = COALESCE(NULLIF(excluded.description, ''), description),
|
|
5313
5685
|
transcript_path = COALESCE(NULLIF(excluded.transcript_path, ''), transcript_path),
|
|
@@ -5315,59 +5687,151 @@ async function appendSession(_projectDir, session, opts) {
|
|
|
5315
5687
|
pid_started_at = COALESCE(NULLIF(excluded.pid_started_at, ''), pid_started_at),
|
|
5316
5688
|
original_head_sha = COALESCE(NULLIF(original_head_sha, ''), NULLIF(excluded.original_head_sha, '')),
|
|
5317
5689
|
updated_at = datetime('now')
|
|
5318
|
-
`)
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5690
|
+
`);
|
|
5691
|
+
const apply = db2.transaction(() => {
|
|
5692
|
+
upsert.run(
|
|
5693
|
+
session.sessionId,
|
|
5694
|
+
session.agent,
|
|
5695
|
+
session.started,
|
|
5696
|
+
session.status,
|
|
5697
|
+
session.path,
|
|
5698
|
+
session.description ?? null,
|
|
5699
|
+
session.transcriptPath ?? null,
|
|
5700
|
+
session.pid ?? null,
|
|
5701
|
+
session.pidStartedAt ?? null,
|
|
5702
|
+
session.originalHeadSha ?? null,
|
|
5703
|
+
opts?.reviveStopped ? 1 : 0,
|
|
5704
|
+
// status CASE: revive guard
|
|
5705
|
+
opts?.reviveStopped ? 1 : 0
|
|
5706
|
+
// ended CASE: clear stale ended on revive
|
|
5707
|
+
);
|
|
5708
|
+
const persisted = db2.prepare("SELECT status, ended FROM sessions WHERE session_id = ?").get(session.sessionId);
|
|
5709
|
+
const freshBinding = {
|
|
5710
|
+
assignmentId: session.assignmentId ?? null,
|
|
5711
|
+
projectSlug: session.projectSlug ?? null,
|
|
5712
|
+
assignmentSlug: session.assignmentSlug ?? null
|
|
5713
|
+
};
|
|
5714
|
+
if (persisted?.status === "active") {
|
|
5715
|
+
reopenEngagementIfMissing(
|
|
5716
|
+
session.sessionId,
|
|
5717
|
+
freshBinding,
|
|
5718
|
+
session.started ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5719
|
+
openSnapshot
|
|
5720
|
+
);
|
|
5721
|
+
} else if (persisted && (freshBinding.projectSlug || freshBinding.assignmentSlug) && !hasAnyEngagement(session.sessionId)) {
|
|
5722
|
+
insertClosedEngagement({
|
|
5723
|
+
sessionId: session.sessionId,
|
|
5724
|
+
projectSlug: freshBinding.projectSlug,
|
|
5725
|
+
assignmentSlug: freshBinding.assignmentSlug,
|
|
5726
|
+
startedAt: session.started ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5727
|
+
endedAt: persisted.ended ?? session.started ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5728
|
+
closeReason: persisted.status === "completed" ? "completed" : "abandoned"
|
|
5729
|
+
});
|
|
5730
|
+
}
|
|
5731
|
+
});
|
|
5732
|
+
apply.immediate();
|
|
5333
5733
|
}
|
|
5334
5734
|
async function updateSessionStatus(_projectDir, sessionId, status, endedAt) {
|
|
5335
5735
|
const db2 = getSessionDb();
|
|
5336
5736
|
const isTerminal = status === "completed" || status === "stopped";
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5737
|
+
if (!isTerminal) {
|
|
5738
|
+
let reviveSnapshot = null;
|
|
5739
|
+
try {
|
|
5740
|
+
reviveSnapshot = await getCumulativeTokenSource()(sessionId);
|
|
5741
|
+
} catch {
|
|
5742
|
+
reviveSnapshot = null;
|
|
5743
|
+
}
|
|
5744
|
+
const apply2 = db2.transaction(() => {
|
|
5745
|
+
const current = db2.prepare("SELECT status FROM sessions WHERE session_id = ?").get(sessionId);
|
|
5746
|
+
if (!current) return false;
|
|
5747
|
+
if (current.status === "completed") {
|
|
5748
|
+
throw new SessionResurrectionError(
|
|
5749
|
+
`Refusing to revive completed session "${sessionId}" to active. A completed session is final.`
|
|
5750
|
+
);
|
|
5751
|
+
}
|
|
5752
|
+
const res = db2.prepare(
|
|
5753
|
+
"UPDATE sessions SET status = ?, ended = NULL, updated_at = datetime('now') WHERE session_id = ?"
|
|
5754
|
+
).run(status, sessionId);
|
|
5755
|
+
if (res.changes > 0) {
|
|
5756
|
+
reopenEngagementIfMissing(sessionId, null, (/* @__PURE__ */ new Date()).toISOString(), reviveSnapshot);
|
|
5757
|
+
}
|
|
5758
|
+
return res.changes > 0;
|
|
5759
|
+
});
|
|
5760
|
+
return apply2.immediate();
|
|
5761
|
+
}
|
|
5762
|
+
const captured = getOpenEngagement(sessionId);
|
|
5763
|
+
let snapshot = null;
|
|
5764
|
+
try {
|
|
5765
|
+
snapshot = await getCumulativeTokenSource()(sessionId);
|
|
5766
|
+
} catch {
|
|
5767
|
+
snapshot = null;
|
|
5768
|
+
}
|
|
5769
|
+
const apply = db2.transaction(() => {
|
|
5770
|
+
const stillTerminal = captured ? closeEngagementById({
|
|
5771
|
+
id: captured.id,
|
|
5772
|
+
startedAt: captured.started_at,
|
|
5773
|
+
closeReason: status === "completed" ? "completed" : "abandoned",
|
|
5774
|
+
tokensAtClose: snapshot,
|
|
5775
|
+
endedAt: endedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5776
|
+
}) : getOpenEngagement(sessionId) === null;
|
|
5777
|
+
if (!stillTerminal) return false;
|
|
5778
|
+
const res = db2.prepare(
|
|
5779
|
+
"UPDATE sessions SET status = ?, ended = COALESCE(?, datetime('now')), updated_at = datetime('now') WHERE session_id = ?"
|
|
5780
|
+
).run(status, endedAt ?? null, sessionId);
|
|
5781
|
+
return res.changes > 0;
|
|
5782
|
+
});
|
|
5783
|
+
return apply.immediate();
|
|
5784
|
+
}
|
|
5785
|
+
function livenessStopSession(input) {
|
|
5786
|
+
const db2 = getSessionDb();
|
|
5787
|
+
const endedAt = input.endedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5788
|
+
const apply = db2.transaction(() => {
|
|
5789
|
+
const stillDead = input.engagementId != null && input.engagementStartedAt != null ? closeEngagementById({
|
|
5790
|
+
id: input.engagementId,
|
|
5791
|
+
startedAt: input.engagementStartedAt,
|
|
5792
|
+
closeReason: "liveness_gc",
|
|
5793
|
+
tokensAtClose: input.tokensAtClose ?? null,
|
|
5794
|
+
endedAt
|
|
5795
|
+
}) : getOpenEngagement(input.sessionId) === null;
|
|
5796
|
+
if (!stillDead) return false;
|
|
5797
|
+
const res = db2.prepare(
|
|
5798
|
+
"UPDATE sessions SET status = 'stopped', ended = COALESCE(?, datetime('now')), updated_at = datetime('now') WHERE session_id = ? AND status = 'active'"
|
|
5799
|
+
).run(input.endedAt ?? null, input.sessionId);
|
|
5800
|
+
return res.changes > 0;
|
|
5801
|
+
});
|
|
5802
|
+
return apply.immediate();
|
|
5343
5803
|
}
|
|
5344
5804
|
async function listAllSessions(_projectsDir) {
|
|
5345
5805
|
const db2 = getSessionDb();
|
|
5346
|
-
const rows = db2.prepare(
|
|
5806
|
+
const rows = db2.prepare(`${SESSION_SELECT_WITH_BINDING} ORDER BY s.started DESC`).all();
|
|
5347
5807
|
return rows.map(rowToSession);
|
|
5348
5808
|
}
|
|
5349
5809
|
function getSessionById(sessionId) {
|
|
5350
5810
|
const db2 = getSessionDb();
|
|
5351
|
-
const row = db2.prepare(
|
|
5811
|
+
const row = db2.prepare(`${SESSION_SELECT_WITH_BINDING} WHERE s.session_id = ? LIMIT 1`).get(sessionId);
|
|
5352
5812
|
return row ? rowToSession(row) : null;
|
|
5353
5813
|
}
|
|
5354
5814
|
async function listProjectSessions(_projectsDir, projectSlug, assignmentSlug) {
|
|
5355
5815
|
const db2 = getSessionDb();
|
|
5356
5816
|
if (assignmentSlug) {
|
|
5357
5817
|
const rows2 = db2.prepare(
|
|
5358
|
-
|
|
5818
|
+
`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? AND e.assignment_slug = ? ORDER BY s.started DESC`
|
|
5359
5819
|
).all(projectSlug, assignmentSlug);
|
|
5360
5820
|
return rows2.map(rowToSession);
|
|
5361
5821
|
}
|
|
5362
|
-
const rows = db2.prepare(
|
|
5822
|
+
const rows = db2.prepare(`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? ORDER BY s.started DESC`).all(projectSlug);
|
|
5363
5823
|
return rows.map(rowToSession);
|
|
5364
5824
|
}
|
|
5365
5825
|
async function deleteSessions(sessionIds) {
|
|
5366
5826
|
if (sessionIds.length === 0) return 0;
|
|
5367
5827
|
const db2 = getSessionDb();
|
|
5368
5828
|
const placeholders = sessionIds.map(() => "?").join(", ");
|
|
5369
|
-
const
|
|
5370
|
-
|
|
5829
|
+
const run2 = db2.transaction(() => {
|
|
5830
|
+
db2.prepare(`DELETE FROM engagement WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
|
5831
|
+
const result = db2.prepare(`DELETE FROM sessions WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
|
5832
|
+
return result.changes;
|
|
5833
|
+
});
|
|
5834
|
+
return run2.immediate();
|
|
5371
5835
|
}
|
|
5372
5836
|
async function readAssignmentStatusFromPath(assignmentMdPath) {
|
|
5373
5837
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
@@ -5377,39 +5841,44 @@ async function readAssignmentStatusFromPath(assignmentMdPath) {
|
|
|
5377
5841
|
}
|
|
5378
5842
|
async function readAssignmentStatus(projectDir, assignmentSlug) {
|
|
5379
5843
|
return readAssignmentStatusFromPath(
|
|
5380
|
-
|
|
5844
|
+
resolve15(projectDir, "assignments", assignmentSlug, "assignment.md")
|
|
5381
5845
|
);
|
|
5382
5846
|
}
|
|
5383
5847
|
async function reconcileActiveSessions(projectsDir, assignmentsDir) {
|
|
5384
5848
|
const db2 = getSessionDb();
|
|
5385
|
-
const activeSessions = db2.prepare(
|
|
5849
|
+
const activeSessions = db2.prepare(
|
|
5850
|
+
`SELECT s.*, e.project_slug AS project_slug, e.assignment_slug AS assignment_slug
|
|
5851
|
+
FROM sessions s
|
|
5852
|
+
JOIN engagement e ON e.session_id = s.session_id AND e.ended_at IS NULL
|
|
5853
|
+
WHERE s.status = 'active' AND e.assignment_slug IS NOT NULL`
|
|
5854
|
+
).all();
|
|
5386
5855
|
if (activeSessions.length === 0) return 0;
|
|
5387
5856
|
const assignmentStatuses = /* @__PURE__ */ new Map();
|
|
5388
5857
|
const seen = /* @__PURE__ */ new Set();
|
|
5389
5858
|
for (const session of activeSessions) {
|
|
5390
5859
|
const aslug = session.assignment_slug;
|
|
5391
5860
|
if (!aslug) continue;
|
|
5392
|
-
const
|
|
5393
|
-
const key = `${
|
|
5861
|
+
const projectKey2 = session.project_slug ?? "__standalone__";
|
|
5862
|
+
const key = `${projectKey2}/${aslug}`;
|
|
5394
5863
|
if (seen.has(key)) continue;
|
|
5395
5864
|
seen.add(key);
|
|
5396
5865
|
if (session.project_slug) {
|
|
5397
5866
|
const status = await readAssignmentStatus(
|
|
5398
|
-
|
|
5867
|
+
resolve15(projectsDir, session.project_slug),
|
|
5399
5868
|
aslug
|
|
5400
5869
|
);
|
|
5401
5870
|
if (status) assignmentStatuses.set(key, status);
|
|
5402
5871
|
} else if (assignmentsDir) {
|
|
5403
5872
|
const status = await readAssignmentStatusFromPath(
|
|
5404
|
-
|
|
5873
|
+
resolve15(assignmentsDir, aslug, "assignment.md")
|
|
5405
5874
|
);
|
|
5406
5875
|
if (status) assignmentStatuses.set(key, status);
|
|
5407
5876
|
}
|
|
5408
5877
|
}
|
|
5409
5878
|
let totalUpdated = 0;
|
|
5410
5879
|
for (const session of activeSessions) {
|
|
5411
|
-
const
|
|
5412
|
-
const key = `${
|
|
5880
|
+
const projectKey2 = session.project_slug ?? "__standalone__";
|
|
5881
|
+
const key = `${projectKey2}/${session.assignment_slug}`;
|
|
5413
5882
|
const assignmentStatus = assignmentStatuses.get(key);
|
|
5414
5883
|
if (!assignmentStatus || !DONE_ASSIGNMENT_STATUSES.has(assignmentStatus)) continue;
|
|
5415
5884
|
const newStatus = assignmentStatus === "failed" ? "stopped" : "completed";
|
|
@@ -5421,18 +5890,34 @@ async function reconcileActiveSessions(projectsDir, assignmentsDir) {
|
|
|
5421
5890
|
async function listSessionsByAssignment(projectSlug, assignmentSlug) {
|
|
5422
5891
|
const db2 = getSessionDb();
|
|
5423
5892
|
const rows = projectSlug === null ? db2.prepare(
|
|
5424
|
-
|
|
5893
|
+
`${SESSION_SELECT_WITH_BINDING} WHERE e.assignment_slug = ? AND e.project_slug IS NULL ORDER BY s.started DESC`
|
|
5425
5894
|
).all(assignmentSlug) : db2.prepare(
|
|
5426
|
-
|
|
5895
|
+
`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? AND e.assignment_slug = ? ORDER BY s.started DESC`
|
|
5427
5896
|
).all(projectSlug, assignmentSlug);
|
|
5428
5897
|
return rows.map(rowToSession);
|
|
5429
5898
|
}
|
|
5430
|
-
var DONE_ASSIGNMENT_STATUSES;
|
|
5899
|
+
var SESSION_SELECT_WITH_BINDING, SessionResurrectionError, DONE_ASSIGNMENT_STATUSES;
|
|
5431
5900
|
var init_agent_sessions = __esm({
|
|
5432
5901
|
"src/dashboard/agent-sessions.ts"() {
|
|
5433
5902
|
"use strict";
|
|
5434
5903
|
init_fs();
|
|
5435
5904
|
init_session_db();
|
|
5905
|
+
init_engagement_db();
|
|
5906
|
+
init_engagement_tokens();
|
|
5907
|
+
SESSION_SELECT_WITH_BINDING = `
|
|
5908
|
+
SELECT s.*,
|
|
5909
|
+
e.project_slug AS project_slug,
|
|
5910
|
+
e.assignment_slug AS assignment_slug,
|
|
5911
|
+
e.assignment_id AS assignment_id
|
|
5912
|
+
FROM sessions s
|
|
5913
|
+
LEFT JOIN engagement e ON e.id = (
|
|
5914
|
+
SELECT e2.id FROM engagement e2
|
|
5915
|
+
WHERE e2.session_id = s.session_id
|
|
5916
|
+
ORDER BY (e2.ended_at IS NULL) DESC, e2.started_at DESC, e2.id DESC
|
|
5917
|
+
LIMIT 1
|
|
5918
|
+
)`;
|
|
5919
|
+
SessionResurrectionError = class extends Error {
|
|
5920
|
+
};
|
|
5436
5921
|
DONE_ASSIGNMENT_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "review"]);
|
|
5437
5922
|
}
|
|
5438
5923
|
});
|
|
@@ -5462,7 +5947,7 @@ var init_classify = __esm({
|
|
|
5462
5947
|
|
|
5463
5948
|
// src/dashboard/api.ts
|
|
5464
5949
|
import { readdir as readdir9, readFile as readFile12, writeFile as writeFile3, stat as stat2 } from "fs/promises";
|
|
5465
|
-
import { resolve as
|
|
5950
|
+
import { resolve as resolve16, dirname as dirname2, basename } from "path";
|
|
5466
5951
|
function activeAssignments(items) {
|
|
5467
5952
|
return items.filter((item) => item.archived !== true);
|
|
5468
5953
|
}
|
|
@@ -5486,8 +5971,8 @@ async function computeStandaloneRecords(assignmentsDir) {
|
|
|
5486
5971
|
const records = [];
|
|
5487
5972
|
for (const entry of entries) {
|
|
5488
5973
|
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
|
|
5489
|
-
const assignmentDir =
|
|
5490
|
-
const assignmentMdPath =
|
|
5974
|
+
const assignmentDir = resolve16(assignmentsDir, entry.name);
|
|
5975
|
+
const assignmentMdPath = resolve16(assignmentDir, "assignment.md");
|
|
5491
5976
|
if (!await fileExists(assignmentMdPath)) continue;
|
|
5492
5977
|
try {
|
|
5493
5978
|
const content = await readFile12(assignmentMdPath, "utf-8");
|
|
@@ -5591,15 +6076,15 @@ async function getStandaloneAvailableTransitions(assignment) {
|
|
|
5591
6076
|
return actions;
|
|
5592
6077
|
}
|
|
5593
6078
|
async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
5594
|
-
const assignmentDir =
|
|
5595
|
-
const assignmentMdPath =
|
|
6079
|
+
const assignmentDir = resolve16(projectsDir, projectSlug, "assignments", assignmentSlug);
|
|
6080
|
+
const assignmentMdPath = resolve16(assignmentDir, "assignment.md");
|
|
5596
6081
|
if (!await fileExists(assignmentMdPath)) {
|
|
5597
6082
|
return null;
|
|
5598
6083
|
}
|
|
5599
6084
|
const assignmentContent = await readFile12(assignmentMdPath, "utf-8");
|
|
5600
6085
|
const assignment = parseAssignmentFull(assignmentContent);
|
|
5601
6086
|
let projectWorkspace = null;
|
|
5602
|
-
const projectMdPath =
|
|
6087
|
+
const projectMdPath = resolve16(projectsDir, projectSlug, "project.md");
|
|
5603
6088
|
if (await fileExists(projectMdPath)) {
|
|
5604
6089
|
const projectContent = await readFile12(projectMdPath, "utf-8");
|
|
5605
6090
|
projectWorkspace = parseProject(projectContent).workspace;
|
|
@@ -5607,7 +6092,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5607
6092
|
let plan = null;
|
|
5608
6093
|
const planFile = await latestPlanFile(assignmentDir);
|
|
5609
6094
|
if (planFile) {
|
|
5610
|
-
const planPath =
|
|
6095
|
+
const planPath = resolve16(assignmentDir, planFile);
|
|
5611
6096
|
if (await fileExists(planPath)) {
|
|
5612
6097
|
const planContent = await readFile12(planPath, "utf-8");
|
|
5613
6098
|
const parsed = parsePlan(planContent);
|
|
@@ -5619,7 +6104,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5619
6104
|
}
|
|
5620
6105
|
}
|
|
5621
6106
|
let scratchpad = null;
|
|
5622
|
-
const scratchpadPath =
|
|
6107
|
+
const scratchpadPath = resolve16(assignmentDir, "scratchpad.md");
|
|
5623
6108
|
if (await fileExists(scratchpadPath)) {
|
|
5624
6109
|
const scratchpadContent = await readFile12(scratchpadPath, "utf-8");
|
|
5625
6110
|
const parsed = parseScratchpad(scratchpadContent);
|
|
@@ -5629,7 +6114,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5629
6114
|
};
|
|
5630
6115
|
}
|
|
5631
6116
|
let handoff = null;
|
|
5632
|
-
const handoffPath =
|
|
6117
|
+
const handoffPath = resolve16(assignmentDir, "handoff.md");
|
|
5633
6118
|
if (await fileExists(handoffPath)) {
|
|
5634
6119
|
const handoffContent = await readFile12(handoffPath, "utf-8");
|
|
5635
6120
|
const parsed = parseHandoff(handoffContent);
|
|
@@ -5640,7 +6125,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5640
6125
|
};
|
|
5641
6126
|
}
|
|
5642
6127
|
let decisionRecord = null;
|
|
5643
|
-
const decisionRecordPath =
|
|
6128
|
+
const decisionRecordPath = resolve16(assignmentDir, "decision-record.md");
|
|
5644
6129
|
if (await fileExists(decisionRecordPath)) {
|
|
5645
6130
|
const decisionRecordContent = await readFile12(decisionRecordPath, "utf-8");
|
|
5646
6131
|
const parsed = parseDecisionRecord(decisionRecordContent);
|
|
@@ -5651,7 +6136,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5651
6136
|
};
|
|
5652
6137
|
}
|
|
5653
6138
|
let progress = null;
|
|
5654
|
-
const progressPath =
|
|
6139
|
+
const progressPath = resolve16(assignmentDir, "progress.md");
|
|
5655
6140
|
if (await fileExists(progressPath)) {
|
|
5656
6141
|
const progressContent = await readFile12(progressPath, "utf-8");
|
|
5657
6142
|
const parsed = parseProgress(progressContent);
|
|
@@ -5662,7 +6147,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5662
6147
|
};
|
|
5663
6148
|
}
|
|
5664
6149
|
let comments = null;
|
|
5665
|
-
const commentsPath =
|
|
6150
|
+
const commentsPath = resolve16(assignmentDir, "comments.md");
|
|
5666
6151
|
if (await fileExists(commentsPath)) {
|
|
5667
6152
|
const commentsContent = await readFile12(commentsPath, "utf-8");
|
|
5668
6153
|
const parsed = parseComments(commentsContent);
|
|
@@ -5696,7 +6181,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5696
6181
|
archivedReason: assignment.archivedReason,
|
|
5697
6182
|
...deriveStatusVirtuals(assignment, terminalStatuses),
|
|
5698
6183
|
override: assignment.override,
|
|
5699
|
-
derived: await buildDerivedDetail(assignment, assignmentDir,
|
|
6184
|
+
derived: await buildDerivedDetail(assignment, assignmentDir, resolve16(projectsDir, projectSlug)),
|
|
5700
6185
|
created: assignment.created,
|
|
5701
6186
|
updated: assignment.updated,
|
|
5702
6187
|
body: assignment.body,
|
|
@@ -5787,7 +6272,7 @@ async function computeReferencedBy(target, projectsDir, assignmentsDir) {
|
|
|
5787
6272
|
slug: a.slug,
|
|
5788
6273
|
title: a.title,
|
|
5789
6274
|
projectSlug: rec.summary.slug,
|
|
5790
|
-
assignmentDir:
|
|
6275
|
+
assignmentDir: resolve16(rec.projectPath, "assignments", a.slug)
|
|
5791
6276
|
});
|
|
5792
6277
|
}
|
|
5793
6278
|
}
|
|
@@ -5820,14 +6305,14 @@ async function computeReferencedBy(target, projectsDir, assignmentsDir) {
|
|
|
5820
6305
|
}
|
|
5821
6306
|
async function countMentionsInAssignment(sourceDir, target) {
|
|
5822
6307
|
const bodies = [];
|
|
5823
|
-
const assignmentMd =
|
|
6308
|
+
const assignmentMd = resolve16(sourceDir, "assignment.md");
|
|
5824
6309
|
if (await fileExists(assignmentMd)) {
|
|
5825
6310
|
const content = await readFile12(assignmentMd, "utf-8");
|
|
5826
6311
|
const todosMatch = content.match(/^## Todos\s*$([\s\S]*?)(?=^## |$(?![\r\n]))/m);
|
|
5827
6312
|
if (todosMatch) bodies.push(todosMatch[1]);
|
|
5828
6313
|
}
|
|
5829
6314
|
for (const filename of ["progress.md", "comments.md", "handoff.md"]) {
|
|
5830
|
-
const path =
|
|
6315
|
+
const path = resolve16(sourceDir, filename);
|
|
5831
6316
|
if (await fileExists(path)) {
|
|
5832
6317
|
try {
|
|
5833
6318
|
bodies.push(await readFile12(path, "utf-8"));
|
|
@@ -5888,45 +6373,45 @@ async function getAssignmentDetailById(projectsDir, assignmentsDir, id) {
|
|
|
5888
6373
|
}
|
|
5889
6374
|
async function buildStandaloneAssignmentDetail(resolved) {
|
|
5890
6375
|
const assignmentDir = resolved.assignmentDir;
|
|
5891
|
-
const assignmentMdPath =
|
|
6376
|
+
const assignmentMdPath = resolve16(assignmentDir, "assignment.md");
|
|
5892
6377
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
5893
6378
|
const assignmentContent = await readFile12(assignmentMdPath, "utf-8");
|
|
5894
6379
|
const assignment = parseAssignmentFull(assignmentContent);
|
|
5895
6380
|
let plan = null;
|
|
5896
6381
|
const planFile = await latestPlanFile(assignmentDir);
|
|
5897
6382
|
if (planFile) {
|
|
5898
|
-
const planPath =
|
|
6383
|
+
const planPath = resolve16(assignmentDir, planFile);
|
|
5899
6384
|
if (await fileExists(planPath)) {
|
|
5900
6385
|
const parsed = parsePlan(await readFile12(planPath, "utf-8"));
|
|
5901
6386
|
plan = { status: parsed.status, updated: parsed.updated, body: parsed.body };
|
|
5902
6387
|
}
|
|
5903
6388
|
}
|
|
5904
6389
|
let scratchpad = null;
|
|
5905
|
-
const scratchpadPath =
|
|
6390
|
+
const scratchpadPath = resolve16(assignmentDir, "scratchpad.md");
|
|
5906
6391
|
if (await fileExists(scratchpadPath)) {
|
|
5907
6392
|
const parsed = parseScratchpad(await readFile12(scratchpadPath, "utf-8"));
|
|
5908
6393
|
scratchpad = { updated: parsed.updated, body: parsed.body };
|
|
5909
6394
|
}
|
|
5910
6395
|
let handoff = null;
|
|
5911
|
-
const handoffPath =
|
|
6396
|
+
const handoffPath = resolve16(assignmentDir, "handoff.md");
|
|
5912
6397
|
if (await fileExists(handoffPath)) {
|
|
5913
6398
|
const parsed = parseHandoff(await readFile12(handoffPath, "utf-8"));
|
|
5914
6399
|
handoff = { updated: parsed.updated, handoffCount: parsed.handoffCount, body: parsed.body };
|
|
5915
6400
|
}
|
|
5916
6401
|
let decisionRecord = null;
|
|
5917
|
-
const decisionRecordPath =
|
|
6402
|
+
const decisionRecordPath = resolve16(assignmentDir, "decision-record.md");
|
|
5918
6403
|
if (await fileExists(decisionRecordPath)) {
|
|
5919
6404
|
const parsed = parseDecisionRecord(await readFile12(decisionRecordPath, "utf-8"));
|
|
5920
6405
|
decisionRecord = { updated: parsed.updated, decisionCount: parsed.decisionCount, body: parsed.body };
|
|
5921
6406
|
}
|
|
5922
6407
|
let progress = null;
|
|
5923
|
-
const progressPath =
|
|
6408
|
+
const progressPath = resolve16(assignmentDir, "progress.md");
|
|
5924
6409
|
if (await fileExists(progressPath)) {
|
|
5925
6410
|
const parsed = parseProgress(await readFile12(progressPath, "utf-8"));
|
|
5926
6411
|
progress = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
|
|
5927
6412
|
}
|
|
5928
6413
|
let comments = null;
|
|
5929
|
-
const commentsPath =
|
|
6414
|
+
const commentsPath = resolve16(assignmentDir, "comments.md");
|
|
5930
6415
|
if (await fileExists(commentsPath)) {
|
|
5931
6416
|
const parsed = parseComments(await readFile12(commentsPath, "utf-8"));
|
|
5932
6417
|
comments = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
|
|
@@ -5992,8 +6477,8 @@ async function computeProjectRecords(projectsDir, traces) {
|
|
|
5992
6477
|
const projectDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
|
|
5993
6478
|
const maybeRecords = await Promise.all(
|
|
5994
6479
|
projectDirs.map(async (entry) => {
|
|
5995
|
-
const projectPath =
|
|
5996
|
-
const projectMdPath =
|
|
6480
|
+
const projectPath = resolve16(projectsDir, entry.name);
|
|
6481
|
+
const projectMdPath = resolve16(projectPath, "project.md");
|
|
5997
6482
|
if (!await fileExists(projectMdPath)) {
|
|
5998
6483
|
return null;
|
|
5999
6484
|
}
|
|
@@ -6040,7 +6525,7 @@ async function computeProjectRecords(projectsDir, traces) {
|
|
|
6040
6525
|
return records;
|
|
6041
6526
|
}
|
|
6042
6527
|
async function listAssignmentRecords(projectPath, traces) {
|
|
6043
|
-
const assignmentsDir =
|
|
6528
|
+
const assignmentsDir = resolve16(projectPath, "assignments");
|
|
6044
6529
|
if (!await fileExists(assignmentsDir)) {
|
|
6045
6530
|
return [];
|
|
6046
6531
|
}
|
|
@@ -6048,7 +6533,7 @@ async function listAssignmentRecords(projectPath, traces) {
|
|
|
6048
6533
|
const dirEntries = entries.filter((entry) => entry.isDirectory());
|
|
6049
6534
|
const maybeRecords = await Promise.all(
|
|
6050
6535
|
dirEntries.map(async (entry) => {
|
|
6051
|
-
const assignmentMd =
|
|
6536
|
+
const assignmentMd = resolve16(assignmentsDir, entry.name, "assignment.md");
|
|
6052
6537
|
if (!await fileExists(assignmentMd)) {
|
|
6053
6538
|
return null;
|
|
6054
6539
|
}
|
|
@@ -6064,7 +6549,7 @@ async function listAssignmentRecords(projectPath, traces) {
|
|
|
6064
6549
|
return records;
|
|
6065
6550
|
}
|
|
6066
6551
|
async function loadDependencyGraph(projectPath, assignments) {
|
|
6067
|
-
const statusPath =
|
|
6552
|
+
const statusPath = resolve16(projectPath, "_status.md");
|
|
6068
6553
|
if (await fileExists(statusPath)) {
|
|
6069
6554
|
const statusContent = await readFile12(statusPath, "utf-8");
|
|
6070
6555
|
const parsed = parseStatus(statusContent);
|
|
@@ -6239,7 +6724,7 @@ async function getAvailableTransitions(projectsDir, projectSlug, assignmentSlug,
|
|
|
6239
6724
|
const config = await getStatusConfig();
|
|
6240
6725
|
const transitionDefs = getTransitionDefinitions(config);
|
|
6241
6726
|
const actions = [];
|
|
6242
|
-
const projectPath =
|
|
6727
|
+
const projectPath = resolve16(projectsDir, projectSlug);
|
|
6243
6728
|
const traces = options?.traces;
|
|
6244
6729
|
for (const definition of transitionDefs) {
|
|
6245
6730
|
const target = getTargetStatus(assignment.status, definition.command, config.transitionTable);
|
|
@@ -6287,7 +6772,7 @@ async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses, de
|
|
|
6287
6772
|
continue;
|
|
6288
6773
|
}
|
|
6289
6774
|
}
|
|
6290
|
-
const dependencyPath =
|
|
6775
|
+
const dependencyPath = resolve16(projectPath, "assignments", dependency, "assignment.md");
|
|
6291
6776
|
if (!await fileExists(dependencyPath)) {
|
|
6292
6777
|
unmet.push(`${dependency} (missing)`);
|
|
6293
6778
|
continue;
|
|
@@ -6308,7 +6793,7 @@ function parseTimestamp(timestamp) {
|
|
|
6308
6793
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
6309
6794
|
}
|
|
6310
6795
|
async function countOpenQuestions(projectPath, assignmentSlug) {
|
|
6311
|
-
const commentsPath =
|
|
6796
|
+
const commentsPath = resolve16(
|
|
6312
6797
|
projectPath,
|
|
6313
6798
|
"assignments",
|
|
6314
6799
|
assignmentSlug,
|
|
@@ -6673,7 +7158,7 @@ init_agents_schema();
|
|
|
6673
7158
|
init_cwd();
|
|
6674
7159
|
import { spawn as spawn2 } from "child_process";
|
|
6675
7160
|
import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
|
|
6676
|
-
import { isAbsolute as isAbsolute3, resolve as
|
|
7161
|
+
import { isAbsolute as isAbsolute3, resolve as resolve17 } from "path";
|
|
6677
7162
|
init_paths();
|
|
6678
7163
|
init_playbooks();
|
|
6679
7164
|
init_cwd();
|
|
@@ -6910,7 +7395,7 @@ async function resolveSessionPlan(input, terminal) {
|
|
|
6910
7395
|
// src/launch/execute.ts
|
|
6911
7396
|
import { spawn as spawn3 } from "child_process";
|
|
6912
7397
|
import { homedir as homedir5 } from "os";
|
|
6913
|
-
import { basename as basename2, join as join6, resolve as
|
|
7398
|
+
import { basename as basename2, join as join6, resolve as resolve18 } from "path";
|
|
6914
7399
|
|
|
6915
7400
|
// src/utils/terminal-probe.ts
|
|
6916
7401
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -6988,7 +7473,7 @@ init_config2();
|
|
|
6988
7473
|
|
|
6989
7474
|
// src/utils/session-id.ts
|
|
6990
7475
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
6991
|
-
import { mkdirSync, readFileSync, statSync as
|
|
7476
|
+
import { mkdirSync, readFileSync as readFileSync2, statSync as statSync3, writeFileSync } from "fs";
|
|
6992
7477
|
import { homedir as homedir4 } from "os";
|
|
6993
7478
|
import { dirname as dirname3, join as join5 } from "path";
|
|
6994
7479
|
|
|
@@ -7072,7 +7557,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
7072
7557
|
`Spawn failed: ${msg}. Verify the terminal is installed and on PATH.`
|
|
7073
7558
|
);
|
|
7074
7559
|
}
|
|
7075
|
-
await new Promise((
|
|
7560
|
+
await new Promise((resolve20, reject) => {
|
|
7076
7561
|
let settled = false;
|
|
7077
7562
|
let stderr = "";
|
|
7078
7563
|
const finishOk = () => {
|
|
@@ -7082,7 +7567,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
7082
7567
|
child.unref();
|
|
7083
7568
|
} catch {
|
|
7084
7569
|
}
|
|
7085
|
-
|
|
7570
|
+
resolve20();
|
|
7086
7571
|
};
|
|
7087
7572
|
const finishErr = (remediation) => {
|
|
7088
7573
|
if (settled) return;
|
|
@@ -7143,7 +7628,7 @@ async function registerLaunchAtBirth(plan, child) {
|
|
|
7143
7628
|
markerDir
|
|
7144
7629
|
);
|
|
7145
7630
|
if (!sessionId) return;
|
|
7146
|
-
if (autoTrack === "workspaces-only" && !await fileExists(
|
|
7631
|
+
if (autoTrack === "workspaces-only" && !await fileExists(resolve18(plan.cwd, ".syntaur", "context.json"))) {
|
|
7147
7632
|
return;
|
|
7148
7633
|
}
|
|
7149
7634
|
const { initSessionDb: initSessionDb2 } = await Promise.resolve().then(() => (init_session_db(), session_db_exports));
|
|
@@ -7319,8 +7804,8 @@ function appleScriptString(value) {
|
|
|
7319
7804
|
init_paths();
|
|
7320
7805
|
init_fs();
|
|
7321
7806
|
import { fileURLToPath } from "url";
|
|
7322
|
-
import { dirname as dirname4, resolve as
|
|
7323
|
-
import { realpathSync, readFileSync as
|
|
7807
|
+
import { dirname as dirname4, resolve as resolve19, join as join7 } from "path";
|
|
7808
|
+
import { realpathSync, readFileSync as readFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
7324
7809
|
var NPX_PATTERNS = [
|
|
7325
7810
|
{ kind: "npm", re: /\/_npx\/([^/]+)\/node_modules(?:\/|$)/ },
|
|
7326
7811
|
{ kind: "pnpm", re: /\/pnpm\/dlx\/([^/]+)\/node_modules(?:\/|$)/ },
|
|
@@ -7345,7 +7830,7 @@ function normalizeSlashes(p) {
|
|
|
7345
7830
|
}
|
|
7346
7831
|
function detectInstallKind(scriptUrl, opts = {}) {
|
|
7347
7832
|
const realpath = opts.realpath ?? realpathSync.native;
|
|
7348
|
-
const readFile13 = opts.readFile ?? ((p) =>
|
|
7833
|
+
const readFile13 = opts.readFile ?? ((p) => readFileSync3(p, "utf-8"));
|
|
7349
7834
|
const ua = opts.envUserAgent !== void 0 ? opts.envUserAgent : process.env.npm_config_user_agent ?? "";
|
|
7350
7835
|
const resolved = resolveScriptPath(scriptUrl, realpath);
|
|
7351
7836
|
if (resolved === null) {
|
|
@@ -7398,7 +7883,7 @@ function extractNpxHash(scriptUrl, opts = {}) {
|
|
|
7398
7883
|
return null;
|
|
7399
7884
|
}
|
|
7400
7885
|
function nudgeStampDir() {
|
|
7401
|
-
return
|
|
7886
|
+
return resolve19(syntaurRoot(), "npx-handler-nudge");
|
|
7402
7887
|
}
|
|
7403
7888
|
function sanitizeHash(hash) {
|
|
7404
7889
|
return hash.replace(/[^A-Za-z0-9_-]/g, "_") || "_";
|