syntaur 0.69.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 +1427 -550
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +2816 -1559
- package/dist/index.js.map +1 -1
- package/dist/launch/index.js +913 -141
- 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
|
}
|
|
@@ -317,7 +317,7 @@ function getTargetStatus(_from, command, table) {
|
|
|
317
317
|
if (!table) {
|
|
318
318
|
return DEFAULT_COMMAND_TARGETS.get(command) ?? null;
|
|
319
319
|
}
|
|
320
|
-
return table.get(command) ?? table.get(
|
|
320
|
+
return table.get(`${_from}:${command}`) ?? table.get(command) ?? null;
|
|
321
321
|
}
|
|
322
322
|
var DEFAULT_COMMAND_TARGETS, DEFAULT_TRANSITION_TABLE;
|
|
323
323
|
var init_state_machine = __esm({
|
|
@@ -359,6 +359,288 @@ var init_state_machine = __esm({
|
|
|
359
359
|
});
|
|
360
360
|
|
|
361
361
|
// src/lifecycle/frontmatter.ts
|
|
362
|
+
function extractFrontmatter(fileContent) {
|
|
363
|
+
const match = fileContent.match(/^---\n([\s\S]*?)\n---/);
|
|
364
|
+
if (!match) {
|
|
365
|
+
throw new Error("No frontmatter found in file. Expected --- delimiters.");
|
|
366
|
+
}
|
|
367
|
+
const frontmatterBlock = match[1];
|
|
368
|
+
const body = fileContent.slice(match[0].length);
|
|
369
|
+
return [frontmatterBlock, body];
|
|
370
|
+
}
|
|
371
|
+
function parseSimpleValue(raw) {
|
|
372
|
+
const trimmed = raw.trim();
|
|
373
|
+
if (trimmed === "null" || trimmed === "~" || trimmed === "") return null;
|
|
374
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
|
|
375
|
+
return trimmed.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
376
|
+
}
|
|
377
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length >= 2) {
|
|
378
|
+
return trimmed.slice(1, -1);
|
|
379
|
+
}
|
|
380
|
+
return trimmed;
|
|
381
|
+
}
|
|
382
|
+
function parseDependsOn(frontmatter) {
|
|
383
|
+
const inlineMatch = frontmatter.match(/^dependsOn:\s*\[\s*\]/m);
|
|
384
|
+
if (inlineMatch) return [];
|
|
385
|
+
const results = [];
|
|
386
|
+
const blockMatch = frontmatter.match(/^dependsOn:\s*\n((?:\s+-\s+.*\n?)*)/m);
|
|
387
|
+
if (blockMatch) {
|
|
388
|
+
const items = blockMatch[1].matchAll(/^\s+-\s+(.+)$/gm);
|
|
389
|
+
for (const item of items) {
|
|
390
|
+
results.push(item[1].trim());
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return results;
|
|
394
|
+
}
|
|
395
|
+
function parseLinks(frontmatter) {
|
|
396
|
+
const inlineMatch = frontmatter.match(/^links:\s*\[\s*\]/m);
|
|
397
|
+
if (inlineMatch) return [];
|
|
398
|
+
const results = [];
|
|
399
|
+
const blockMatch = frontmatter.match(/^links:\s*\n((?:\s+-\s+.*\n?)*)/m);
|
|
400
|
+
if (blockMatch) {
|
|
401
|
+
const items = blockMatch[1].matchAll(/^\s+-\s+(.+)$/gm);
|
|
402
|
+
for (const item of items) {
|
|
403
|
+
results.push(item[1].trim());
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return results;
|
|
407
|
+
}
|
|
408
|
+
function parseExternalIds(frontmatter) {
|
|
409
|
+
const inlineMatch = frontmatter.match(/^externalIds:\s*\[\s*\]/m);
|
|
410
|
+
if (inlineMatch) return [];
|
|
411
|
+
const results = [];
|
|
412
|
+
const blockMatch = frontmatter.match(
|
|
413
|
+
/^externalIds:\s*\n((?:\s+-\s+[\s\S]*?)(?=^\w|\n---))/m
|
|
414
|
+
);
|
|
415
|
+
if (!blockMatch) return [];
|
|
416
|
+
const itemBlocks = blockMatch[1].split(/\n\s+-\s+/).filter(Boolean);
|
|
417
|
+
for (const block of itemBlocks) {
|
|
418
|
+
const lines = block.split("\n");
|
|
419
|
+
const entry = {};
|
|
420
|
+
for (const line of lines) {
|
|
421
|
+
const colonIdx = line.indexOf(":");
|
|
422
|
+
if (colonIdx < 0) continue;
|
|
423
|
+
const key = line.slice(0, colonIdx).trim().replace(/^-\s+/, "");
|
|
424
|
+
if (!key) continue;
|
|
425
|
+
entry[key] = parseSimpleValue(line.slice(colonIdx + 1));
|
|
426
|
+
}
|
|
427
|
+
if (entry["system"] && entry["id"]) {
|
|
428
|
+
results.push({
|
|
429
|
+
system: entry["system"],
|
|
430
|
+
id: entry["id"],
|
|
431
|
+
url: entry["url"] || null
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return results;
|
|
436
|
+
}
|
|
437
|
+
function parseStatusHistory(frontmatter) {
|
|
438
|
+
if (/^statusHistory:\s*\[\s*\]/m.test(frontmatter)) return [];
|
|
439
|
+
const headerMatch = frontmatter.match(/^statusHistory:\s*$/m);
|
|
440
|
+
if (!headerMatch) return [];
|
|
441
|
+
const headerStart = headerMatch.index ?? frontmatter.indexOf(headerMatch[0]);
|
|
442
|
+
const bodyStart = headerStart + headerMatch[0].length + 1;
|
|
443
|
+
const after = frontmatter.slice(bodyStart);
|
|
444
|
+
const bodyLines = [];
|
|
445
|
+
for (const line of after.split("\n")) {
|
|
446
|
+
if (line.length === 0) {
|
|
447
|
+
bodyLines.push(line);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (line[0] !== " " && line[0] !== " ") break;
|
|
451
|
+
bodyLines.push(line);
|
|
452
|
+
}
|
|
453
|
+
const body = bodyLines.join("\n");
|
|
454
|
+
const results = [];
|
|
455
|
+
const itemBlocks = body.split(/\n\s+-\s+/).filter((b) => b.trim().length > 0);
|
|
456
|
+
for (const block of itemBlocks) {
|
|
457
|
+
const entry = {};
|
|
458
|
+
for (const line of block.split("\n")) {
|
|
459
|
+
const colonIdx = line.indexOf(":");
|
|
460
|
+
if (colonIdx < 0) continue;
|
|
461
|
+
const key = line.slice(0, colonIdx).trim().replace(/^-\s+/, "");
|
|
462
|
+
if (!key) continue;
|
|
463
|
+
entry[key] = parseSimpleValue(line.slice(colonIdx + 1));
|
|
464
|
+
}
|
|
465
|
+
if (!entry["to"]) continue;
|
|
466
|
+
const result = {
|
|
467
|
+
at: entry["at"] ?? "",
|
|
468
|
+
from: entry["from"] ?? null,
|
|
469
|
+
to: entry["to"],
|
|
470
|
+
command: entry["command"] ?? "",
|
|
471
|
+
by: entry["by"] ?? null
|
|
472
|
+
};
|
|
473
|
+
if (entry["reason"] != null) result.reason = entry["reason"];
|
|
474
|
+
if ("phaseFrom" in entry) result.phaseFrom = entry["phaseFrom"];
|
|
475
|
+
if ("phaseTo" in entry) result.phaseTo = entry["phaseTo"];
|
|
476
|
+
if ("dispositionFrom" in entry) result.dispositionFrom = entry["dispositionFrom"];
|
|
477
|
+
if ("dispositionTo" in entry) result.dispositionTo = entry["dispositionTo"];
|
|
478
|
+
results.push(result);
|
|
479
|
+
}
|
|
480
|
+
return results;
|
|
481
|
+
}
|
|
482
|
+
function parseNestedBlock(frontmatter, header) {
|
|
483
|
+
if (new RegExp(`^${header}:\\s*(null|~)\\s*$`, "m").test(frontmatter)) return null;
|
|
484
|
+
const headerMatch = frontmatter.match(new RegExp(`^${header}:\\s*$`, "m"));
|
|
485
|
+
if (!headerMatch) return null;
|
|
486
|
+
const headerStart = headerMatch.index ?? frontmatter.indexOf(headerMatch[0]);
|
|
487
|
+
const after = frontmatter.slice(headerStart + headerMatch[0].length + 1);
|
|
488
|
+
const out = {};
|
|
489
|
+
for (const line of after.split("\n")) {
|
|
490
|
+
if (line.length === 0) continue;
|
|
491
|
+
if (line[0] !== " " && line[0] !== " ") break;
|
|
492
|
+
const colonIdx = line.indexOf(":");
|
|
493
|
+
if (colonIdx < 0) continue;
|
|
494
|
+
const key = line.slice(0, colonIdx).trim();
|
|
495
|
+
if (!key) continue;
|
|
496
|
+
out[key] = parseSimpleValue(line.slice(colonIdx + 1));
|
|
497
|
+
}
|
|
498
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
499
|
+
}
|
|
500
|
+
function parsePlanApproval(frontmatter) {
|
|
501
|
+
const block = parseNestedBlock(frontmatter, "planApproval");
|
|
502
|
+
if (!block || !block["file"] || !block["digest"]) return null;
|
|
503
|
+
return {
|
|
504
|
+
file: block["file"],
|
|
505
|
+
digest: block["digest"],
|
|
506
|
+
by: block["by"] ?? null,
|
|
507
|
+
at: block["at"] ?? ""
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function parseOverride(frontmatter) {
|
|
511
|
+
const block = parseNestedBlock(frontmatter, "override");
|
|
512
|
+
if (!block || !block["status"]) return null;
|
|
513
|
+
return {
|
|
514
|
+
status: block["status"],
|
|
515
|
+
source: block["source"] ?? "human",
|
|
516
|
+
reason: block["reason"] ?? null,
|
|
517
|
+
at: block["at"] ?? ""
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function parseFactsMap(frontmatter) {
|
|
521
|
+
const block = parseNestedBlock(frontmatter, "facts");
|
|
522
|
+
if (!block) return {};
|
|
523
|
+
const out = {};
|
|
524
|
+
for (const [k, v] of Object.entries(block)) {
|
|
525
|
+
if (v === null) continue;
|
|
526
|
+
out[k] = v;
|
|
527
|
+
}
|
|
528
|
+
return out;
|
|
529
|
+
}
|
|
530
|
+
function parseAttestations(frontmatter) {
|
|
531
|
+
if (/^attestations:\s*\[\s*\]/m.test(frontmatter)) return [];
|
|
532
|
+
const headerMatch = frontmatter.match(/^attestations:\s*$/m);
|
|
533
|
+
if (!headerMatch) return [];
|
|
534
|
+
const headerStart = headerMatch.index ?? frontmatter.indexOf(headerMatch[0]);
|
|
535
|
+
const bodyStart = headerStart + headerMatch[0].length + 1;
|
|
536
|
+
const after = frontmatter.slice(bodyStart);
|
|
537
|
+
const bodyLines = [];
|
|
538
|
+
for (const line of after.split("\n")) {
|
|
539
|
+
if (line.length === 0) {
|
|
540
|
+
bodyLines.push(line);
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (line[0] !== " " && line[0] !== " ") break;
|
|
544
|
+
bodyLines.push(line);
|
|
545
|
+
}
|
|
546
|
+
const body = bodyLines.join("\n");
|
|
547
|
+
const results = [];
|
|
548
|
+
const itemBlocks = body.split(/\n\s+-\s+/).filter((b) => b.trim().length > 0);
|
|
549
|
+
for (const block of itemBlocks) {
|
|
550
|
+
const entry = {};
|
|
551
|
+
for (const line of block.split("\n")) {
|
|
552
|
+
const colonIdx = line.indexOf(":");
|
|
553
|
+
if (colonIdx < 0) continue;
|
|
554
|
+
const key = line.slice(0, colonIdx).trim().replace(/^-\s+/, "");
|
|
555
|
+
if (!key) continue;
|
|
556
|
+
entry[key] = parseSimpleValue(line.slice(colonIdx + 1));
|
|
557
|
+
}
|
|
558
|
+
const verdict = entry["verdict"];
|
|
559
|
+
if (!entry["fact"] || !entry["actor"] || !verdict || !entry["at"]) continue;
|
|
560
|
+
if (verdict !== "approved" && verdict !== "changes-requested") continue;
|
|
561
|
+
const record = {
|
|
562
|
+
fact: entry["fact"],
|
|
563
|
+
actor: entry["actor"],
|
|
564
|
+
verdict,
|
|
565
|
+
at: entry["at"]
|
|
566
|
+
};
|
|
567
|
+
if (entry["note"] != null) record.note = entry["note"];
|
|
568
|
+
if (entry["file"] != null) record.file = entry["file"];
|
|
569
|
+
if (entry["digest"] != null) record.digest = entry["digest"];
|
|
570
|
+
if (entry["commit"] != null) record.commit = entry["commit"];
|
|
571
|
+
results.push(record);
|
|
572
|
+
}
|
|
573
|
+
return results;
|
|
574
|
+
}
|
|
575
|
+
function parseWorkspace(frontmatter) {
|
|
576
|
+
const defaults = {
|
|
577
|
+
repository: null,
|
|
578
|
+
worktreePath: null,
|
|
579
|
+
branch: null,
|
|
580
|
+
parentBranch: null
|
|
581
|
+
};
|
|
582
|
+
const fields = ["repository", "worktreePath", "branch", "parentBranch"];
|
|
583
|
+
for (const field of fields) {
|
|
584
|
+
const match = frontmatter.match(new RegExp(`^\\s+${field}:\\s*(.*)$`, "m"));
|
|
585
|
+
if (match) {
|
|
586
|
+
defaults[field] = parseSimpleValue(match[1]);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return defaults;
|
|
590
|
+
}
|
|
591
|
+
function parseTags(frontmatter) {
|
|
592
|
+
const inlineMatch = frontmatter.match(/^tags:\s*\[\s*\]/m);
|
|
593
|
+
if (inlineMatch) return [];
|
|
594
|
+
const results = [];
|
|
595
|
+
const blockMatch = frontmatter.match(/^tags:\s*\n((?:\s+-\s+.*\n?)*)/m);
|
|
596
|
+
if (blockMatch) {
|
|
597
|
+
const items = blockMatch[1].matchAll(/^\s+-\s+(.+)$/gm);
|
|
598
|
+
for (const item of items) {
|
|
599
|
+
results.push(item[1].trim());
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return results;
|
|
603
|
+
}
|
|
604
|
+
function parseAssignmentFrontmatter(fileContent) {
|
|
605
|
+
const [frontmatter] = extractFrontmatter(fileContent);
|
|
606
|
+
function getField2(key) {
|
|
607
|
+
const match = frontmatter.match(new RegExp(`^${key}:\\s*(.*)$`, "m"));
|
|
608
|
+
if (!match) return null;
|
|
609
|
+
return parseSimpleValue(match[1]);
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
id: getField2("id") ?? "",
|
|
613
|
+
slug: getField2("slug") ?? "",
|
|
614
|
+
title: getField2("title") ?? "",
|
|
615
|
+
project: getField2("project"),
|
|
616
|
+
type: getField2("type"),
|
|
617
|
+
status: getField2("status") ?? "pending",
|
|
618
|
+
priority: getField2("priority") ?? "medium",
|
|
619
|
+
created: getField2("created") ?? "",
|
|
620
|
+
updated: getField2("updated") ?? "",
|
|
621
|
+
assignee: getField2("assignee"),
|
|
622
|
+
externalIds: parseExternalIds(frontmatter),
|
|
623
|
+
statusHistory: parseStatusHistory(frontmatter),
|
|
624
|
+
dependsOn: parseDependsOn(frontmatter),
|
|
625
|
+
links: parseLinks(frontmatter),
|
|
626
|
+
blockedReason: getField2("blockedReason"),
|
|
627
|
+
workspace: parseWorkspace(frontmatter),
|
|
628
|
+
tags: parseTags(frontmatter),
|
|
629
|
+
archived: getField2("archived") === "true",
|
|
630
|
+
archivedAt: getField2("archivedAt"),
|
|
631
|
+
archivedReason: getField2("archivedReason"),
|
|
632
|
+
phase: getField2("phase"),
|
|
633
|
+
disposition: getField2("disposition"),
|
|
634
|
+
planApproval: parsePlanApproval(frontmatter),
|
|
635
|
+
parked: getField2("parked") === "true",
|
|
636
|
+
reviewRequested: getField2("reviewRequested") === "true",
|
|
637
|
+
reworkRequested: getField2("reworkRequested") === "true",
|
|
638
|
+
implementationStarted: getField2("implementationStarted") === "true",
|
|
639
|
+
override: parseOverride(frontmatter),
|
|
640
|
+
facts: parseFactsMap(frontmatter),
|
|
641
|
+
attestations: parseAttestations(frontmatter)
|
|
642
|
+
};
|
|
643
|
+
}
|
|
362
644
|
var init_frontmatter = __esm({
|
|
363
645
|
"src/lifecycle/frontmatter.ts"() {
|
|
364
646
|
"use strict";
|
|
@@ -393,7 +675,7 @@ var init_event_emit = __esm({
|
|
|
393
675
|
});
|
|
394
676
|
|
|
395
677
|
// src/dashboard/parser.ts
|
|
396
|
-
function
|
|
678
|
+
function extractFrontmatter2(fileContent) {
|
|
397
679
|
const match = fileContent.match(/^---\n([\s\S]*?)\n---/);
|
|
398
680
|
if (!match) {
|
|
399
681
|
return ["", fileContent];
|
|
@@ -402,7 +684,7 @@ function extractFrontmatter(fileContent) {
|
|
|
402
684
|
const body = fileContent.slice(match[0].length).trim();
|
|
403
685
|
return [frontmatterBlock, body];
|
|
404
686
|
}
|
|
405
|
-
function
|
|
687
|
+
function parseSimpleValue2(raw) {
|
|
406
688
|
const trimmed = raw.trim();
|
|
407
689
|
if (trimmed === "null" || trimmed === "~" || trimmed === "") return null;
|
|
408
690
|
if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
|
|
@@ -416,7 +698,7 @@ function parseSimpleValue(raw) {
|
|
|
416
698
|
function getField(frontmatter, key) {
|
|
417
699
|
const match = frontmatter.match(new RegExp(`^${key}:\\s*(.*)$`, "m"));
|
|
418
700
|
if (!match) return null;
|
|
419
|
-
return
|
|
701
|
+
return parseSimpleValue2(match[1]);
|
|
420
702
|
}
|
|
421
703
|
function getNestedField(frontmatter, parent, key) {
|
|
422
704
|
const parentRegex = new RegExp(`^${parent}:\\s*\\n((?:\\s+.*\\n?)*)`, "m");
|
|
@@ -425,7 +707,7 @@ function getNestedField(frontmatter, parent, key) {
|
|
|
425
707
|
const block = parentMatch[1];
|
|
426
708
|
const fieldMatch = block.match(new RegExp(`^\\s+${key}:\\s*(.*)$`, "m"));
|
|
427
709
|
if (!fieldMatch) return null;
|
|
428
|
-
return
|
|
710
|
+
return parseSimpleValue2(fieldMatch[1]);
|
|
429
711
|
}
|
|
430
712
|
function parseListField(frontmatter, fieldName) {
|
|
431
713
|
const inlineMatch = frontmatter.match(new RegExp(`^${fieldName}:\\s*\\[\\s*\\]`, "m"));
|
|
@@ -450,7 +732,7 @@ function unquoteYamlString(value) {
|
|
|
450
732
|
return value;
|
|
451
733
|
}
|
|
452
734
|
function parseProject(fileContent) {
|
|
453
|
-
const [fm, body] =
|
|
735
|
+
const [fm, body] = extractFrontmatter2(fileContent);
|
|
454
736
|
const slug = getField(fm, "slug") ?? getField(fm, "mission") ?? "";
|
|
455
737
|
return {
|
|
456
738
|
id: getField(fm, "id") ?? "",
|
|
@@ -465,12 +747,12 @@ function parseProject(fileContent) {
|
|
|
465
747
|
tags: parseListField(fm, "tags"),
|
|
466
748
|
workspace: getField(fm, "workspace"),
|
|
467
749
|
repositories: parseListField(fm, "repositories").map(unquoteYamlString),
|
|
468
|
-
externalIds:
|
|
750
|
+
externalIds: parseExternalIds2(fm),
|
|
469
751
|
body
|
|
470
752
|
};
|
|
471
753
|
}
|
|
472
754
|
function parseStatus(fileContent) {
|
|
473
|
-
const [fm, body] =
|
|
755
|
+
const [fm, body] = extractFrontmatter2(fileContent);
|
|
474
756
|
const progress = { total: 0 };
|
|
475
757
|
const progressMatch = fm.match(/^progress:\s*\n((?:\s+.*\n?)*)/m);
|
|
476
758
|
if (progressMatch) {
|
|
@@ -494,7 +776,7 @@ function parseStatus(fileContent) {
|
|
|
494
776
|
body
|
|
495
777
|
};
|
|
496
778
|
}
|
|
497
|
-
function
|
|
779
|
+
function parseExternalIds2(frontmatter) {
|
|
498
780
|
const inlineMatch = frontmatter.match(/^externalIds:\s*\[\s*\]/m);
|
|
499
781
|
if (inlineMatch) return [];
|
|
500
782
|
const results = [];
|
|
@@ -511,7 +793,7 @@ function parseExternalIds(frontmatter) {
|
|
|
511
793
|
if (colonIdx < 0) continue;
|
|
512
794
|
const key = line.slice(0, colonIdx).trim().replace(/^-\s+/, "");
|
|
513
795
|
if (!key) continue;
|
|
514
|
-
entry[key] =
|
|
796
|
+
entry[key] = parseSimpleValue2(line.slice(colonIdx + 1));
|
|
515
797
|
}
|
|
516
798
|
if (entry["system"] && entry["id"]) {
|
|
517
799
|
results.push({
|
|
@@ -523,7 +805,7 @@ function parseExternalIds(frontmatter) {
|
|
|
523
805
|
}
|
|
524
806
|
return results;
|
|
525
807
|
}
|
|
526
|
-
function
|
|
808
|
+
function parseStatusHistory2(frontmatter) {
|
|
527
809
|
if (/^statusHistory:\s*\[\s*\]/m.test(frontmatter)) return [];
|
|
528
810
|
const headerMatch = frontmatter.match(/^statusHistory:\s*$/m);
|
|
529
811
|
if (!headerMatch) return [];
|
|
@@ -549,7 +831,7 @@ function parseStatusHistory(frontmatter) {
|
|
|
549
831
|
if (colonIdx < 0) continue;
|
|
550
832
|
const key = line.slice(0, colonIdx).trim().replace(/^-\s+/, "");
|
|
551
833
|
if (!key) continue;
|
|
552
|
-
entry[key] =
|
|
834
|
+
entry[key] = parseSimpleValue2(line.slice(colonIdx + 1));
|
|
553
835
|
}
|
|
554
836
|
if (!entry["to"]) continue;
|
|
555
837
|
const result = {
|
|
@@ -568,7 +850,7 @@ function parseStatusHistory(frontmatter) {
|
|
|
568
850
|
}
|
|
569
851
|
return results;
|
|
570
852
|
}
|
|
571
|
-
function
|
|
853
|
+
function parseFactsMap2(frontmatter) {
|
|
572
854
|
const headerMatch = frontmatter.match(/^facts:\s*$/m);
|
|
573
855
|
if (!headerMatch) return {};
|
|
574
856
|
const headerStart = headerMatch.index ?? frontmatter.indexOf(headerMatch[0]);
|
|
@@ -581,13 +863,13 @@ function parseFactsMap(frontmatter) {
|
|
|
581
863
|
if (colonIdx < 0) continue;
|
|
582
864
|
const key = line.slice(0, colonIdx).trim();
|
|
583
865
|
if (!key) continue;
|
|
584
|
-
const value =
|
|
866
|
+
const value = parseSimpleValue2(line.slice(colonIdx + 1));
|
|
585
867
|
if (value === null) continue;
|
|
586
868
|
out[key] = value;
|
|
587
869
|
}
|
|
588
870
|
return out;
|
|
589
871
|
}
|
|
590
|
-
function
|
|
872
|
+
function parseAttestations2(frontmatter) {
|
|
591
873
|
if (/^attestations:\s*\[\s*\]/m.test(frontmatter)) return [];
|
|
592
874
|
const headerMatch = frontmatter.match(/^attestations:\s*$/m);
|
|
593
875
|
if (!headerMatch) return [];
|
|
@@ -613,7 +895,7 @@ function parseAttestations(frontmatter) {
|
|
|
613
895
|
if (colonIdx < 0) continue;
|
|
614
896
|
const key = line.slice(0, colonIdx).trim().replace(/^-\s+/, "");
|
|
615
897
|
if (!key) continue;
|
|
616
|
-
entry[key] =
|
|
898
|
+
entry[key] = parseSimpleValue2(line.slice(colonIdx + 1));
|
|
617
899
|
}
|
|
618
900
|
const verdict = entry["verdict"];
|
|
619
901
|
if (!entry["fact"] || !entry["actor"] || !verdict || !entry["at"]) continue;
|
|
@@ -633,7 +915,7 @@ function parseAttestations(frontmatter) {
|
|
|
633
915
|
return results;
|
|
634
916
|
}
|
|
635
917
|
function parseAssignmentFull(fileContent) {
|
|
636
|
-
const [fm, body] =
|
|
918
|
+
const [fm, body] = extractFrontmatter2(fileContent);
|
|
637
919
|
return {
|
|
638
920
|
id: getField(fm, "id") ?? "",
|
|
639
921
|
slug: getField(fm, "slug") ?? "",
|
|
@@ -653,8 +935,8 @@ function parseAssignmentFull(fileContent) {
|
|
|
653
935
|
branch: getNestedField(fm, "workspace", "branch"),
|
|
654
936
|
parentBranch: getNestedField(fm, "workspace", "parentBranch")
|
|
655
937
|
},
|
|
656
|
-
externalIds:
|
|
657
|
-
statusHistory:
|
|
938
|
+
externalIds: parseExternalIds2(fm),
|
|
939
|
+
statusHistory: parseStatusHistory2(fm),
|
|
658
940
|
tags: parseListField(fm, "tags"),
|
|
659
941
|
archived: getField(fm, "archived") === "true",
|
|
660
942
|
archivedAt: getField(fm, "archivedAt"),
|
|
@@ -666,6 +948,7 @@ function parseAssignmentFull(fileContent) {
|
|
|
666
948
|
disposition: getField(fm, "disposition"),
|
|
667
949
|
parked: getField(fm, "parked") === "true",
|
|
668
950
|
reviewRequested: getField(fm, "reviewRequested") === "true",
|
|
951
|
+
reworkRequested: getField(fm, "reworkRequested") === "true",
|
|
669
952
|
implementationStarted: getField(fm, "implementationStarted") === "true",
|
|
670
953
|
planApproval: (() => {
|
|
671
954
|
const file = getNestedField(fm, "planApproval", "file");
|
|
@@ -688,12 +971,12 @@ function parseAssignmentFull(fileContent) {
|
|
|
688
971
|
at: getNestedField(fm, "override", "at") ?? ""
|
|
689
972
|
};
|
|
690
973
|
})(),
|
|
691
|
-
facts:
|
|
692
|
-
attestations:
|
|
974
|
+
facts: parseFactsMap2(fm),
|
|
975
|
+
attestations: parseAttestations2(fm)
|
|
693
976
|
};
|
|
694
977
|
}
|
|
695
978
|
function parsePlan(fileContent) {
|
|
696
|
-
const [fm, body] =
|
|
979
|
+
const [fm, body] = extractFrontmatter2(fileContent);
|
|
697
980
|
return {
|
|
698
981
|
assignment: getField(fm, "assignment") ?? "",
|
|
699
982
|
status: getField(fm, "status") ?? "",
|
|
@@ -703,7 +986,7 @@ function parsePlan(fileContent) {
|
|
|
703
986
|
};
|
|
704
987
|
}
|
|
705
988
|
function parseScratchpad(fileContent) {
|
|
706
|
-
const [fm, body] =
|
|
989
|
+
const [fm, body] = extractFrontmatter2(fileContent);
|
|
707
990
|
return {
|
|
708
991
|
assignment: getField(fm, "assignment") ?? "",
|
|
709
992
|
updated: getField(fm, "updated") ?? "",
|
|
@@ -711,7 +994,7 @@ function parseScratchpad(fileContent) {
|
|
|
711
994
|
};
|
|
712
995
|
}
|
|
713
996
|
function parseHandoff(fileContent) {
|
|
714
|
-
const [fm, body] =
|
|
997
|
+
const [fm, body] = extractFrontmatter2(fileContent);
|
|
715
998
|
return {
|
|
716
999
|
assignment: getField(fm, "assignment") ?? "",
|
|
717
1000
|
handoffCount: parseInt(getField(fm, "handoffCount") ?? "0", 10),
|
|
@@ -720,7 +1003,7 @@ function parseHandoff(fileContent) {
|
|
|
720
1003
|
};
|
|
721
1004
|
}
|
|
722
1005
|
function parseDecisionRecord(fileContent) {
|
|
723
|
-
const [fm, body] =
|
|
1006
|
+
const [fm, body] = extractFrontmatter2(fileContent);
|
|
724
1007
|
return {
|
|
725
1008
|
assignment: getField(fm, "assignment") ?? "",
|
|
726
1009
|
decisionCount: parseInt(getField(fm, "decisionCount") ?? "0", 10),
|
|
@@ -729,7 +1012,7 @@ function parseDecisionRecord(fileContent) {
|
|
|
729
1012
|
};
|
|
730
1013
|
}
|
|
731
1014
|
function parseComments(fileContent) {
|
|
732
|
-
const [fm, body] =
|
|
1015
|
+
const [fm, body] = extractFrontmatter2(fileContent);
|
|
733
1016
|
const entries = [];
|
|
734
1017
|
const sections = body.split(
|
|
735
1018
|
/^## (?=[^\n]*\n\s*\*\*Recorded:\*\*[^\n]*\n\*\*Author:\*\*[^\n]*\n\*\*Type:\*\*\s*(?:question|note|feedback)\b)/m
|
|
@@ -764,7 +1047,7 @@ function parseComments(fileContent) {
|
|
|
764
1047
|
};
|
|
765
1048
|
}
|
|
766
1049
|
function parseProgress(fileContent) {
|
|
767
|
-
const [fm, body] =
|
|
1050
|
+
const [fm, body] = extractFrontmatter2(fileContent);
|
|
768
1051
|
const entries = [];
|
|
769
1052
|
const sections = body.split(/^## /m).slice(1);
|
|
770
1053
|
for (const section of sections) {
|
|
@@ -783,7 +1066,7 @@ function parseProgress(fileContent) {
|
|
|
783
1066
|
};
|
|
784
1067
|
}
|
|
785
1068
|
function parsePlaybook(fileContent) {
|
|
786
|
-
const [fm, body] =
|
|
1069
|
+
const [fm, body] = extractFrontmatter2(fileContent);
|
|
787
1070
|
return {
|
|
788
1071
|
slug: getField(fm, "slug") ?? "",
|
|
789
1072
|
name: getField(fm, "name") ?? "",
|
|
@@ -1145,7 +1428,9 @@ var init_derive_config = __esm({
|
|
|
1145
1428
|
},
|
|
1146
1429
|
{
|
|
1147
1430
|
phase: "review",
|
|
1148
|
-
|
|
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",
|
|
1149
1434
|
next: "Complete, or address review feedback"
|
|
1150
1435
|
}
|
|
1151
1436
|
],
|
|
@@ -1212,6 +1497,7 @@ var init_fields = __esm({
|
|
|
1212
1497
|
blocked: { kind: "bool" },
|
|
1213
1498
|
parked: { kind: "bool" },
|
|
1214
1499
|
reviewrequested: { kind: "bool", get: (i) => i["reviewRequested"] },
|
|
1500
|
+
reworkrequested: { kind: "bool", get: (i) => i["reworkRequested"] },
|
|
1215
1501
|
pinned: { kind: "bool" }
|
|
1216
1502
|
};
|
|
1217
1503
|
}
|
|
@@ -1991,6 +2277,7 @@ function queryFieldNames(declarations) {
|
|
|
1991
2277
|
"blocked",
|
|
1992
2278
|
"parked",
|
|
1993
2279
|
"reviewRequested",
|
|
2280
|
+
"reworkRequested",
|
|
1994
2281
|
"pinned"
|
|
1995
2282
|
];
|
|
1996
2283
|
const custom = [];
|
|
@@ -2029,6 +2316,7 @@ var init_fact_registry = __esm({
|
|
|
2029
2316
|
blocked: { kind: "bool" },
|
|
2030
2317
|
parked: { kind: "bool" },
|
|
2031
2318
|
reviewrequested: { kind: "bool", get: (i) => i["reviewRequested"] },
|
|
2319
|
+
reworkrequested: { kind: "bool", get: (i) => i["reworkRequested"] },
|
|
2032
2320
|
pinned: { kind: "bool" }
|
|
2033
2321
|
};
|
|
2034
2322
|
}
|
|
@@ -3951,7 +4239,7 @@ async function resolveAssignmentById(projectsDir, assignmentsDir, id) {
|
|
|
3951
4239
|
let workspaceGroup = null;
|
|
3952
4240
|
try {
|
|
3953
4241
|
const content = await readFile6(standalonePath, "utf-8");
|
|
3954
|
-
const [fm] =
|
|
4242
|
+
const [fm] = extractFrontmatter2(content);
|
|
3955
4243
|
workspaceGroup = getField(fm, "workspaceGroup");
|
|
3956
4244
|
} catch {
|
|
3957
4245
|
}
|
|
@@ -3979,7 +4267,7 @@ async function resolveAssignmentById(projectsDir, assignmentsDir, id) {
|
|
|
3979
4267
|
if (!await fileExists(aPath)) continue;
|
|
3980
4268
|
try {
|
|
3981
4269
|
const content = await readFile6(aPath, "utf-8");
|
|
3982
|
-
const [fm] =
|
|
4270
|
+
const [fm] = extractFrontmatter2(content);
|
|
3983
4271
|
const fileId = getField(fm, "id");
|
|
3984
4272
|
if (fileId === id) {
|
|
3985
4273
|
projectMatch = {
|
|
@@ -4063,7 +4351,7 @@ function compiledWhen(registry, when) {
|
|
|
4063
4351
|
return pred;
|
|
4064
4352
|
}
|
|
4065
4353
|
function deriveDimensions(input) {
|
|
4066
|
-
const { facts, derive, currentStatus, terminalStatuses, knownStatusIds, override } = input;
|
|
4354
|
+
const { facts, derive, currentStatus, terminalStatuses, knownStatusIds, override: override2 } = input;
|
|
4067
4355
|
const registry = input.registry ?? DERIVE_FIELDS;
|
|
4068
4356
|
if (terminalStatuses.has(currentStatus)) return null;
|
|
4069
4357
|
const ctx = { now: 0 };
|
|
@@ -4097,8 +4385,8 @@ function deriveDimensions(input) {
|
|
|
4097
4385
|
derivedStatus = phase;
|
|
4098
4386
|
}
|
|
4099
4387
|
let status = derivedStatus;
|
|
4100
|
-
if (
|
|
4101
|
-
status =
|
|
4388
|
+
if (override2 && override2.status && !terminalStatuses.has(override2.status) && knownStatusIds.has(override2.status)) {
|
|
4389
|
+
status = override2.status;
|
|
4102
4390
|
}
|
|
4103
4391
|
return { phase, disposition, derivedStatus, status, nextAction };
|
|
4104
4392
|
}
|
|
@@ -4322,8 +4610,7 @@ async function areDependenciesSatisfied(projectDir, dependsOn, terminalStatuses)
|
|
|
4322
4610
|
if (!await fileExists(depPath)) return false;
|
|
4323
4611
|
try {
|
|
4324
4612
|
const content = await readFile9(depPath, "utf-8");
|
|
4325
|
-
const
|
|
4326
|
-
const status = m ? m[1].trim() : "";
|
|
4613
|
+
const { status } = parseAssignmentFrontmatter(content);
|
|
4327
4614
|
if (!terminalStatuses.has(status)) return false;
|
|
4328
4615
|
} catch {
|
|
4329
4616
|
return false;
|
|
@@ -4390,6 +4677,7 @@ async function computeFactsDetailed(input) {
|
|
|
4390
4677
|
blocked: frontmatter.blockedReason !== null,
|
|
4391
4678
|
parked: frontmatter.parked,
|
|
4392
4679
|
reviewRequested: frontmatter.reviewRequested,
|
|
4680
|
+
reworkRequested: frontmatter.reworkRequested,
|
|
4393
4681
|
pinned: frontmatter.override !== null
|
|
4394
4682
|
};
|
|
4395
4683
|
const attestations = [];
|
|
@@ -4438,6 +4726,7 @@ var init_facts = __esm({
|
|
|
4438
4726
|
init_fs();
|
|
4439
4727
|
init_git_worktree();
|
|
4440
4728
|
init_derive();
|
|
4729
|
+
init_frontmatter();
|
|
4441
4730
|
HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
|
|
4442
4731
|
PLAN_FILE_RE = /^plan(?:-v(\d+))?\.md$/;
|
|
4443
4732
|
}
|
|
@@ -4700,6 +4989,139 @@ var init_help = __esm({
|
|
|
4700
4989
|
}
|
|
4701
4990
|
});
|
|
4702
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
|
+
|
|
4703
5125
|
// src/dashboard/session-db.ts
|
|
4704
5126
|
var session_db_exports = {};
|
|
4705
5127
|
__export(session_db_exports, {
|
|
@@ -4711,18 +5133,23 @@ __export(session_db_exports, {
|
|
|
4711
5133
|
resetSessionDb: () => resetSessionDb
|
|
4712
5134
|
});
|
|
4713
5135
|
import Database2 from "better-sqlite3";
|
|
4714
|
-
import { resolve as
|
|
5136
|
+
import { resolve as resolve14 } from "path";
|
|
4715
5137
|
import { readdir as readdir8 } from "fs/promises";
|
|
4716
5138
|
function initSessionDb(dbPath) {
|
|
4717
5139
|
if (db) return db;
|
|
4718
|
-
const finalPath = dbPath ??
|
|
5140
|
+
const finalPath = dbPath ?? resolve14(syntaurRoot(), "syntaur.db");
|
|
4719
5141
|
db = new Database2(finalPath);
|
|
4720
5142
|
db.pragma("journal_mode = WAL");
|
|
4721
5143
|
db.exec(SCHEMA_SQL);
|
|
5144
|
+
db.exec(ENGAGEMENT_DDL);
|
|
4722
5145
|
db.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
|
|
4723
5146
|
"schema_version",
|
|
4724
5147
|
SCHEMA_VERSION
|
|
4725
5148
|
);
|
|
5149
|
+
db.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
|
|
5150
|
+
"engagement_schema_version",
|
|
5151
|
+
ENGAGEMENT_SCHEMA_VERSION
|
|
5152
|
+
);
|
|
4726
5153
|
const database = db;
|
|
4727
5154
|
const runMigrations = database.transaction(() => {
|
|
4728
5155
|
const vBeforeV2 = database.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get()?.value;
|
|
@@ -4849,9 +5276,43 @@ function initSessionDb(dbPath) {
|
|
|
4849
5276
|
UPDATE meta SET value = '5' WHERE key = 'schema_version';
|
|
4850
5277
|
`);
|
|
4851
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
|
+
}
|
|
4852
5314
|
});
|
|
4853
5315
|
runMigrations.exclusive();
|
|
4854
|
-
db.exec(POST_MIGRATION_INDEXES_SQL);
|
|
4855
5316
|
return db;
|
|
4856
5317
|
}
|
|
4857
5318
|
function isSessionDbInitialized() {
|
|
@@ -4883,20 +5344,38 @@ async function migrateFromMarkdown(projectsDir) {
|
|
|
4883
5344
|
const allSessions = [];
|
|
4884
5345
|
for (const entry of entries) {
|
|
4885
5346
|
if (!entry.isDirectory()) continue;
|
|
4886
|
-
const projectDir =
|
|
4887
|
-
const indexPath =
|
|
5347
|
+
const projectDir = resolve14(projectsDir, entry.name);
|
|
5348
|
+
const indexPath = resolve14(projectDir, "_index-sessions.md");
|
|
4888
5349
|
if (!await fileExists(indexPath)) continue;
|
|
4889
5350
|
const sessions = await parseMarkdownSessionsIndex(indexPath, entry.name);
|
|
4890
5351
|
allSessions.push(...sessions);
|
|
4891
5352
|
}
|
|
4892
5353
|
if (allSessions.length === 0) return 0;
|
|
4893
5354
|
const insert = database.prepare(`
|
|
4894
|
-
INSERT OR IGNORE INTO sessions (session_id,
|
|
4895
|
-
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
|
+
)
|
|
4896
5364
|
`);
|
|
4897
5365
|
const insertAll = database.transaction((sessions) => {
|
|
4898
5366
|
for (const s of sessions) {
|
|
4899
|
-
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
|
+
}
|
|
4900
5379
|
}
|
|
4901
5380
|
});
|
|
4902
5381
|
insertAll(allSessions);
|
|
@@ -4939,19 +5418,19 @@ async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
|
4939
5418
|
}
|
|
4940
5419
|
return sessions;
|
|
4941
5420
|
}
|
|
4942
|
-
var db, SCHEMA_VERSION, SCHEMA_SQL
|
|
5421
|
+
var db, SCHEMA_VERSION, SCHEMA_SQL;
|
|
4943
5422
|
var init_session_db = __esm({
|
|
4944
5423
|
"src/dashboard/session-db.ts"() {
|
|
4945
5424
|
"use strict";
|
|
4946
5425
|
init_paths();
|
|
4947
5426
|
init_fs();
|
|
5427
|
+
init_engagement_schema();
|
|
5428
|
+
init_engagement_backfill();
|
|
4948
5429
|
db = null;
|
|
4949
|
-
SCHEMA_VERSION = "
|
|
5430
|
+
SCHEMA_VERSION = "6";
|
|
4950
5431
|
SCHEMA_SQL = `
|
|
4951
5432
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
4952
5433
|
session_id TEXT PRIMARY KEY,
|
|
4953
|
-
project_slug TEXT,
|
|
4954
|
-
assignment_slug TEXT,
|
|
4955
5434
|
agent TEXT NOT NULL,
|
|
4956
5435
|
started TEXT NOT NULL,
|
|
4957
5436
|
ended TEXT,
|
|
@@ -4962,34 +5441,172 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
4962
5441
|
pid INTEGER,
|
|
4963
5442
|
pid_started_at TEXT,
|
|
4964
5443
|
original_head_sha TEXT,
|
|
5444
|
+
activity TEXT,
|
|
4965
5445
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
4966
5446
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
4967
5447
|
);
|
|
4968
5448
|
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
4969
5449
|
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
|
|
4970
5450
|
`;
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
5451
|
+
}
|
|
5452
|
+
});
|
|
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";
|
|
4975
5590
|
}
|
|
4976
5591
|
});
|
|
4977
5592
|
|
|
4978
5593
|
// src/dashboard/agent-sessions.ts
|
|
4979
5594
|
var agent_sessions_exports = {};
|
|
4980
5595
|
__export(agent_sessions_exports, {
|
|
5596
|
+
SessionResurrectionError: () => SessionResurrectionError,
|
|
4981
5597
|
appendSession: () => appendSession,
|
|
4982
5598
|
deleteSessions: () => deleteSessions,
|
|
4983
5599
|
getSessionById: () => getSessionById,
|
|
4984
5600
|
listAllSessions: () => listAllSessions,
|
|
4985
5601
|
listProjectSessions: () => listProjectSessions,
|
|
4986
5602
|
listSessionsByAssignment: () => listSessionsByAssignment,
|
|
5603
|
+
livenessStopSession: () => livenessStopSession,
|
|
4987
5604
|
parseSessionsIndex: () => parseSessionsIndex,
|
|
4988
5605
|
reconcileActiveSessions: () => reconcileActiveSessions,
|
|
4989
5606
|
updateSessionStatus: () => updateSessionStatus
|
|
4990
5607
|
});
|
|
4991
5608
|
import { readFile as readFile11 } from "fs/promises";
|
|
4992
|
-
import { resolve as
|
|
5609
|
+
import { resolve as resolve15 } from "path";
|
|
4993
5610
|
function rowToSession(row) {
|
|
4994
5611
|
return {
|
|
4995
5612
|
sessionId: row.session_id,
|
|
@@ -5005,28 +5622,64 @@ function rowToSession(row) {
|
|
|
5005
5622
|
pid: row.pid ?? null,
|
|
5006
5623
|
pidStartedAt: row.pid_started_at ?? null,
|
|
5007
5624
|
originalHeadSha: row.original_head_sha ?? null,
|
|
5625
|
+
activity: row.activity ?? null,
|
|
5008
5626
|
updatedAt: row.updated_at ?? null
|
|
5009
5627
|
};
|
|
5010
5628
|
}
|
|
5011
5629
|
async function parseSessionsIndex(_projectDir, projectSlug) {
|
|
5012
5630
|
const db2 = getSessionDb();
|
|
5013
|
-
const rows = db2.prepare(
|
|
5631
|
+
const rows = db2.prepare(`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? ORDER BY s.started DESC`).all(projectSlug);
|
|
5014
5632
|
return rows.map(rowToSession);
|
|
5015
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
|
+
}
|
|
5016
5661
|
async function appendSession(_projectDir, session, opts) {
|
|
5017
5662
|
const db2 = getSessionDb();
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5021
5672
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
5022
|
-
project_slug = COALESCE(NULLIF(excluded.project_slug, ''), project_slug),
|
|
5023
|
-
assignment_slug = COALESCE(NULLIF(excluded.assignment_slug, ''), assignment_slug),
|
|
5024
5673
|
agent = excluded.agent,
|
|
5025
5674
|
status = CASE
|
|
5026
5675
|
WHEN status = 'completed' THEN status
|
|
5027
5676
|
WHEN status = 'stopped' AND NOT (? AND excluded.status = 'active') THEN status
|
|
5028
5677
|
ELSE excluded.status
|
|
5029
5678
|
END,
|
|
5679
|
+
ended = CASE
|
|
5680
|
+
WHEN status = 'stopped' AND ? AND excluded.status = 'active' THEN NULL
|
|
5681
|
+
ELSE ended
|
|
5682
|
+
END,
|
|
5030
5683
|
path = COALESCE(NULLIF(excluded.path, ''), path),
|
|
5031
5684
|
description = COALESCE(NULLIF(excluded.description, ''), description),
|
|
5032
5685
|
transcript_path = COALESCE(NULLIF(excluded.transcript_path, ''), transcript_path),
|
|
@@ -5034,59 +5687,151 @@ async function appendSession(_projectDir, session, opts) {
|
|
|
5034
5687
|
pid_started_at = COALESCE(NULLIF(excluded.pid_started_at, ''), pid_started_at),
|
|
5035
5688
|
original_head_sha = COALESCE(NULLIF(original_head_sha, ''), NULLIF(excluded.original_head_sha, '')),
|
|
5036
5689
|
updated_at = datetime('now')
|
|
5037
|
-
`)
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
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();
|
|
5052
5733
|
}
|
|
5053
5734
|
async function updateSessionStatus(_projectDir, sessionId, status, endedAt) {
|
|
5054
5735
|
const db2 = getSessionDb();
|
|
5055
5736
|
const isTerminal = status === "completed" || status === "stopped";
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
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();
|
|
5062
5803
|
}
|
|
5063
5804
|
async function listAllSessions(_projectsDir) {
|
|
5064
5805
|
const db2 = getSessionDb();
|
|
5065
|
-
const rows = db2.prepare(
|
|
5806
|
+
const rows = db2.prepare(`${SESSION_SELECT_WITH_BINDING} ORDER BY s.started DESC`).all();
|
|
5066
5807
|
return rows.map(rowToSession);
|
|
5067
5808
|
}
|
|
5068
5809
|
function getSessionById(sessionId) {
|
|
5069
5810
|
const db2 = getSessionDb();
|
|
5070
|
-
const row = db2.prepare(
|
|
5811
|
+
const row = db2.prepare(`${SESSION_SELECT_WITH_BINDING} WHERE s.session_id = ? LIMIT 1`).get(sessionId);
|
|
5071
5812
|
return row ? rowToSession(row) : null;
|
|
5072
5813
|
}
|
|
5073
5814
|
async function listProjectSessions(_projectsDir, projectSlug, assignmentSlug) {
|
|
5074
5815
|
const db2 = getSessionDb();
|
|
5075
5816
|
if (assignmentSlug) {
|
|
5076
5817
|
const rows2 = db2.prepare(
|
|
5077
|
-
|
|
5818
|
+
`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? AND e.assignment_slug = ? ORDER BY s.started DESC`
|
|
5078
5819
|
).all(projectSlug, assignmentSlug);
|
|
5079
5820
|
return rows2.map(rowToSession);
|
|
5080
5821
|
}
|
|
5081
|
-
const rows = db2.prepare(
|
|
5822
|
+
const rows = db2.prepare(`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? ORDER BY s.started DESC`).all(projectSlug);
|
|
5082
5823
|
return rows.map(rowToSession);
|
|
5083
5824
|
}
|
|
5084
5825
|
async function deleteSessions(sessionIds) {
|
|
5085
5826
|
if (sessionIds.length === 0) return 0;
|
|
5086
5827
|
const db2 = getSessionDb();
|
|
5087
5828
|
const placeholders = sessionIds.map(() => "?").join(", ");
|
|
5088
|
-
const
|
|
5089
|
-
|
|
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();
|
|
5090
5835
|
}
|
|
5091
5836
|
async function readAssignmentStatusFromPath(assignmentMdPath) {
|
|
5092
5837
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
@@ -5096,39 +5841,44 @@ async function readAssignmentStatusFromPath(assignmentMdPath) {
|
|
|
5096
5841
|
}
|
|
5097
5842
|
async function readAssignmentStatus(projectDir, assignmentSlug) {
|
|
5098
5843
|
return readAssignmentStatusFromPath(
|
|
5099
|
-
|
|
5844
|
+
resolve15(projectDir, "assignments", assignmentSlug, "assignment.md")
|
|
5100
5845
|
);
|
|
5101
5846
|
}
|
|
5102
5847
|
async function reconcileActiveSessions(projectsDir, assignmentsDir) {
|
|
5103
5848
|
const db2 = getSessionDb();
|
|
5104
|
-
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();
|
|
5105
5855
|
if (activeSessions.length === 0) return 0;
|
|
5106
5856
|
const assignmentStatuses = /* @__PURE__ */ new Map();
|
|
5107
5857
|
const seen = /* @__PURE__ */ new Set();
|
|
5108
5858
|
for (const session of activeSessions) {
|
|
5109
5859
|
const aslug = session.assignment_slug;
|
|
5110
5860
|
if (!aslug) continue;
|
|
5111
|
-
const
|
|
5112
|
-
const key = `${
|
|
5861
|
+
const projectKey2 = session.project_slug ?? "__standalone__";
|
|
5862
|
+
const key = `${projectKey2}/${aslug}`;
|
|
5113
5863
|
if (seen.has(key)) continue;
|
|
5114
5864
|
seen.add(key);
|
|
5115
5865
|
if (session.project_slug) {
|
|
5116
5866
|
const status = await readAssignmentStatus(
|
|
5117
|
-
|
|
5867
|
+
resolve15(projectsDir, session.project_slug),
|
|
5118
5868
|
aslug
|
|
5119
5869
|
);
|
|
5120
5870
|
if (status) assignmentStatuses.set(key, status);
|
|
5121
5871
|
} else if (assignmentsDir) {
|
|
5122
5872
|
const status = await readAssignmentStatusFromPath(
|
|
5123
|
-
|
|
5873
|
+
resolve15(assignmentsDir, aslug, "assignment.md")
|
|
5124
5874
|
);
|
|
5125
5875
|
if (status) assignmentStatuses.set(key, status);
|
|
5126
5876
|
}
|
|
5127
5877
|
}
|
|
5128
5878
|
let totalUpdated = 0;
|
|
5129
5879
|
for (const session of activeSessions) {
|
|
5130
|
-
const
|
|
5131
|
-
const key = `${
|
|
5880
|
+
const projectKey2 = session.project_slug ?? "__standalone__";
|
|
5881
|
+
const key = `${projectKey2}/${session.assignment_slug}`;
|
|
5132
5882
|
const assignmentStatus = assignmentStatuses.get(key);
|
|
5133
5883
|
if (!assignmentStatus || !DONE_ASSIGNMENT_STATUSES.has(assignmentStatus)) continue;
|
|
5134
5884
|
const newStatus = assignmentStatus === "failed" ? "stopped" : "completed";
|
|
@@ -5140,18 +5890,34 @@ async function reconcileActiveSessions(projectsDir, assignmentsDir) {
|
|
|
5140
5890
|
async function listSessionsByAssignment(projectSlug, assignmentSlug) {
|
|
5141
5891
|
const db2 = getSessionDb();
|
|
5142
5892
|
const rows = projectSlug === null ? db2.prepare(
|
|
5143
|
-
|
|
5893
|
+
`${SESSION_SELECT_WITH_BINDING} WHERE e.assignment_slug = ? AND e.project_slug IS NULL ORDER BY s.started DESC`
|
|
5144
5894
|
).all(assignmentSlug) : db2.prepare(
|
|
5145
|
-
|
|
5895
|
+
`${SESSION_SELECT_WITH_BINDING} WHERE e.project_slug = ? AND e.assignment_slug = ? ORDER BY s.started DESC`
|
|
5146
5896
|
).all(projectSlug, assignmentSlug);
|
|
5147
5897
|
return rows.map(rowToSession);
|
|
5148
5898
|
}
|
|
5149
|
-
var DONE_ASSIGNMENT_STATUSES;
|
|
5899
|
+
var SESSION_SELECT_WITH_BINDING, SessionResurrectionError, DONE_ASSIGNMENT_STATUSES;
|
|
5150
5900
|
var init_agent_sessions = __esm({
|
|
5151
5901
|
"src/dashboard/agent-sessions.ts"() {
|
|
5152
5902
|
"use strict";
|
|
5153
5903
|
init_fs();
|
|
5154
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
|
+
};
|
|
5155
5921
|
DONE_ASSIGNMENT_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "review"]);
|
|
5156
5922
|
}
|
|
5157
5923
|
});
|
|
@@ -5181,7 +5947,7 @@ var init_classify = __esm({
|
|
|
5181
5947
|
|
|
5182
5948
|
// src/dashboard/api.ts
|
|
5183
5949
|
import { readdir as readdir9, readFile as readFile12, writeFile as writeFile3, stat as stat2 } from "fs/promises";
|
|
5184
|
-
import { resolve as
|
|
5950
|
+
import { resolve as resolve16, dirname as dirname2, basename } from "path";
|
|
5185
5951
|
function activeAssignments(items) {
|
|
5186
5952
|
return items.filter((item) => item.archived !== true);
|
|
5187
5953
|
}
|
|
@@ -5205,8 +5971,8 @@ async function computeStandaloneRecords(assignmentsDir) {
|
|
|
5205
5971
|
const records = [];
|
|
5206
5972
|
for (const entry of entries) {
|
|
5207
5973
|
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
|
|
5208
|
-
const assignmentDir =
|
|
5209
|
-
const assignmentMdPath =
|
|
5974
|
+
const assignmentDir = resolve16(assignmentsDir, entry.name);
|
|
5975
|
+
const assignmentMdPath = resolve16(assignmentDir, "assignment.md");
|
|
5210
5976
|
if (!await fileExists(assignmentMdPath)) continue;
|
|
5211
5977
|
try {
|
|
5212
5978
|
const content = await readFile12(assignmentMdPath, "utf-8");
|
|
@@ -5310,15 +6076,15 @@ async function getStandaloneAvailableTransitions(assignment) {
|
|
|
5310
6076
|
return actions;
|
|
5311
6077
|
}
|
|
5312
6078
|
async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
5313
|
-
const assignmentDir =
|
|
5314
|
-
const assignmentMdPath =
|
|
6079
|
+
const assignmentDir = resolve16(projectsDir, projectSlug, "assignments", assignmentSlug);
|
|
6080
|
+
const assignmentMdPath = resolve16(assignmentDir, "assignment.md");
|
|
5315
6081
|
if (!await fileExists(assignmentMdPath)) {
|
|
5316
6082
|
return null;
|
|
5317
6083
|
}
|
|
5318
6084
|
const assignmentContent = await readFile12(assignmentMdPath, "utf-8");
|
|
5319
6085
|
const assignment = parseAssignmentFull(assignmentContent);
|
|
5320
6086
|
let projectWorkspace = null;
|
|
5321
|
-
const projectMdPath =
|
|
6087
|
+
const projectMdPath = resolve16(projectsDir, projectSlug, "project.md");
|
|
5322
6088
|
if (await fileExists(projectMdPath)) {
|
|
5323
6089
|
const projectContent = await readFile12(projectMdPath, "utf-8");
|
|
5324
6090
|
projectWorkspace = parseProject(projectContent).workspace;
|
|
@@ -5326,7 +6092,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5326
6092
|
let plan = null;
|
|
5327
6093
|
const planFile = await latestPlanFile(assignmentDir);
|
|
5328
6094
|
if (planFile) {
|
|
5329
|
-
const planPath =
|
|
6095
|
+
const planPath = resolve16(assignmentDir, planFile);
|
|
5330
6096
|
if (await fileExists(planPath)) {
|
|
5331
6097
|
const planContent = await readFile12(planPath, "utf-8");
|
|
5332
6098
|
const parsed = parsePlan(planContent);
|
|
@@ -5338,7 +6104,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5338
6104
|
}
|
|
5339
6105
|
}
|
|
5340
6106
|
let scratchpad = null;
|
|
5341
|
-
const scratchpadPath =
|
|
6107
|
+
const scratchpadPath = resolve16(assignmentDir, "scratchpad.md");
|
|
5342
6108
|
if (await fileExists(scratchpadPath)) {
|
|
5343
6109
|
const scratchpadContent = await readFile12(scratchpadPath, "utf-8");
|
|
5344
6110
|
const parsed = parseScratchpad(scratchpadContent);
|
|
@@ -5348,7 +6114,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5348
6114
|
};
|
|
5349
6115
|
}
|
|
5350
6116
|
let handoff = null;
|
|
5351
|
-
const handoffPath =
|
|
6117
|
+
const handoffPath = resolve16(assignmentDir, "handoff.md");
|
|
5352
6118
|
if (await fileExists(handoffPath)) {
|
|
5353
6119
|
const handoffContent = await readFile12(handoffPath, "utf-8");
|
|
5354
6120
|
const parsed = parseHandoff(handoffContent);
|
|
@@ -5359,7 +6125,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5359
6125
|
};
|
|
5360
6126
|
}
|
|
5361
6127
|
let decisionRecord = null;
|
|
5362
|
-
const decisionRecordPath =
|
|
6128
|
+
const decisionRecordPath = resolve16(assignmentDir, "decision-record.md");
|
|
5363
6129
|
if (await fileExists(decisionRecordPath)) {
|
|
5364
6130
|
const decisionRecordContent = await readFile12(decisionRecordPath, "utf-8");
|
|
5365
6131
|
const parsed = parseDecisionRecord(decisionRecordContent);
|
|
@@ -5370,7 +6136,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5370
6136
|
};
|
|
5371
6137
|
}
|
|
5372
6138
|
let progress = null;
|
|
5373
|
-
const progressPath =
|
|
6139
|
+
const progressPath = resolve16(assignmentDir, "progress.md");
|
|
5374
6140
|
if (await fileExists(progressPath)) {
|
|
5375
6141
|
const progressContent = await readFile12(progressPath, "utf-8");
|
|
5376
6142
|
const parsed = parseProgress(progressContent);
|
|
@@ -5381,7 +6147,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5381
6147
|
};
|
|
5382
6148
|
}
|
|
5383
6149
|
let comments = null;
|
|
5384
|
-
const commentsPath =
|
|
6150
|
+
const commentsPath = resolve16(assignmentDir, "comments.md");
|
|
5385
6151
|
if (await fileExists(commentsPath)) {
|
|
5386
6152
|
const commentsContent = await readFile12(commentsPath, "utf-8");
|
|
5387
6153
|
const parsed = parseComments(commentsContent);
|
|
@@ -5415,7 +6181,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5415
6181
|
archivedReason: assignment.archivedReason,
|
|
5416
6182
|
...deriveStatusVirtuals(assignment, terminalStatuses),
|
|
5417
6183
|
override: assignment.override,
|
|
5418
|
-
derived: await buildDerivedDetail(assignment, assignmentDir,
|
|
6184
|
+
derived: await buildDerivedDetail(assignment, assignmentDir, resolve16(projectsDir, projectSlug)),
|
|
5419
6185
|
created: assignment.created,
|
|
5420
6186
|
updated: assignment.updated,
|
|
5421
6187
|
body: assignment.body,
|
|
@@ -5506,7 +6272,7 @@ async function computeReferencedBy(target, projectsDir, assignmentsDir) {
|
|
|
5506
6272
|
slug: a.slug,
|
|
5507
6273
|
title: a.title,
|
|
5508
6274
|
projectSlug: rec.summary.slug,
|
|
5509
|
-
assignmentDir:
|
|
6275
|
+
assignmentDir: resolve16(rec.projectPath, "assignments", a.slug)
|
|
5510
6276
|
});
|
|
5511
6277
|
}
|
|
5512
6278
|
}
|
|
@@ -5539,14 +6305,14 @@ async function computeReferencedBy(target, projectsDir, assignmentsDir) {
|
|
|
5539
6305
|
}
|
|
5540
6306
|
async function countMentionsInAssignment(sourceDir, target) {
|
|
5541
6307
|
const bodies = [];
|
|
5542
|
-
const assignmentMd =
|
|
6308
|
+
const assignmentMd = resolve16(sourceDir, "assignment.md");
|
|
5543
6309
|
if (await fileExists(assignmentMd)) {
|
|
5544
6310
|
const content = await readFile12(assignmentMd, "utf-8");
|
|
5545
6311
|
const todosMatch = content.match(/^## Todos\s*$([\s\S]*?)(?=^## |$(?![\r\n]))/m);
|
|
5546
6312
|
if (todosMatch) bodies.push(todosMatch[1]);
|
|
5547
6313
|
}
|
|
5548
6314
|
for (const filename of ["progress.md", "comments.md", "handoff.md"]) {
|
|
5549
|
-
const path =
|
|
6315
|
+
const path = resolve16(sourceDir, filename);
|
|
5550
6316
|
if (await fileExists(path)) {
|
|
5551
6317
|
try {
|
|
5552
6318
|
bodies.push(await readFile12(path, "utf-8"));
|
|
@@ -5607,45 +6373,45 @@ async function getAssignmentDetailById(projectsDir, assignmentsDir, id) {
|
|
|
5607
6373
|
}
|
|
5608
6374
|
async function buildStandaloneAssignmentDetail(resolved) {
|
|
5609
6375
|
const assignmentDir = resolved.assignmentDir;
|
|
5610
|
-
const assignmentMdPath =
|
|
6376
|
+
const assignmentMdPath = resolve16(assignmentDir, "assignment.md");
|
|
5611
6377
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
5612
6378
|
const assignmentContent = await readFile12(assignmentMdPath, "utf-8");
|
|
5613
6379
|
const assignment = parseAssignmentFull(assignmentContent);
|
|
5614
6380
|
let plan = null;
|
|
5615
6381
|
const planFile = await latestPlanFile(assignmentDir);
|
|
5616
6382
|
if (planFile) {
|
|
5617
|
-
const planPath =
|
|
6383
|
+
const planPath = resolve16(assignmentDir, planFile);
|
|
5618
6384
|
if (await fileExists(planPath)) {
|
|
5619
6385
|
const parsed = parsePlan(await readFile12(planPath, "utf-8"));
|
|
5620
6386
|
plan = { status: parsed.status, updated: parsed.updated, body: parsed.body };
|
|
5621
6387
|
}
|
|
5622
6388
|
}
|
|
5623
6389
|
let scratchpad = null;
|
|
5624
|
-
const scratchpadPath =
|
|
6390
|
+
const scratchpadPath = resolve16(assignmentDir, "scratchpad.md");
|
|
5625
6391
|
if (await fileExists(scratchpadPath)) {
|
|
5626
6392
|
const parsed = parseScratchpad(await readFile12(scratchpadPath, "utf-8"));
|
|
5627
6393
|
scratchpad = { updated: parsed.updated, body: parsed.body };
|
|
5628
6394
|
}
|
|
5629
6395
|
let handoff = null;
|
|
5630
|
-
const handoffPath =
|
|
6396
|
+
const handoffPath = resolve16(assignmentDir, "handoff.md");
|
|
5631
6397
|
if (await fileExists(handoffPath)) {
|
|
5632
6398
|
const parsed = parseHandoff(await readFile12(handoffPath, "utf-8"));
|
|
5633
6399
|
handoff = { updated: parsed.updated, handoffCount: parsed.handoffCount, body: parsed.body };
|
|
5634
6400
|
}
|
|
5635
6401
|
let decisionRecord = null;
|
|
5636
|
-
const decisionRecordPath =
|
|
6402
|
+
const decisionRecordPath = resolve16(assignmentDir, "decision-record.md");
|
|
5637
6403
|
if (await fileExists(decisionRecordPath)) {
|
|
5638
6404
|
const parsed = parseDecisionRecord(await readFile12(decisionRecordPath, "utf-8"));
|
|
5639
6405
|
decisionRecord = { updated: parsed.updated, decisionCount: parsed.decisionCount, body: parsed.body };
|
|
5640
6406
|
}
|
|
5641
6407
|
let progress = null;
|
|
5642
|
-
const progressPath =
|
|
6408
|
+
const progressPath = resolve16(assignmentDir, "progress.md");
|
|
5643
6409
|
if (await fileExists(progressPath)) {
|
|
5644
6410
|
const parsed = parseProgress(await readFile12(progressPath, "utf-8"));
|
|
5645
6411
|
progress = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
|
|
5646
6412
|
}
|
|
5647
6413
|
let comments = null;
|
|
5648
|
-
const commentsPath =
|
|
6414
|
+
const commentsPath = resolve16(assignmentDir, "comments.md");
|
|
5649
6415
|
if (await fileExists(commentsPath)) {
|
|
5650
6416
|
const parsed = parseComments(await readFile12(commentsPath, "utf-8"));
|
|
5651
6417
|
comments = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
|
|
@@ -5711,8 +6477,8 @@ async function computeProjectRecords(projectsDir, traces) {
|
|
|
5711
6477
|
const projectDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
|
|
5712
6478
|
const maybeRecords = await Promise.all(
|
|
5713
6479
|
projectDirs.map(async (entry) => {
|
|
5714
|
-
const projectPath =
|
|
5715
|
-
const projectMdPath =
|
|
6480
|
+
const projectPath = resolve16(projectsDir, entry.name);
|
|
6481
|
+
const projectMdPath = resolve16(projectPath, "project.md");
|
|
5716
6482
|
if (!await fileExists(projectMdPath)) {
|
|
5717
6483
|
return null;
|
|
5718
6484
|
}
|
|
@@ -5759,7 +6525,7 @@ async function computeProjectRecords(projectsDir, traces) {
|
|
|
5759
6525
|
return records;
|
|
5760
6526
|
}
|
|
5761
6527
|
async function listAssignmentRecords(projectPath, traces) {
|
|
5762
|
-
const assignmentsDir =
|
|
6528
|
+
const assignmentsDir = resolve16(projectPath, "assignments");
|
|
5763
6529
|
if (!await fileExists(assignmentsDir)) {
|
|
5764
6530
|
return [];
|
|
5765
6531
|
}
|
|
@@ -5767,7 +6533,7 @@ async function listAssignmentRecords(projectPath, traces) {
|
|
|
5767
6533
|
const dirEntries = entries.filter((entry) => entry.isDirectory());
|
|
5768
6534
|
const maybeRecords = await Promise.all(
|
|
5769
6535
|
dirEntries.map(async (entry) => {
|
|
5770
|
-
const assignmentMd =
|
|
6536
|
+
const assignmentMd = resolve16(assignmentsDir, entry.name, "assignment.md");
|
|
5771
6537
|
if (!await fileExists(assignmentMd)) {
|
|
5772
6538
|
return null;
|
|
5773
6539
|
}
|
|
@@ -5783,7 +6549,7 @@ async function listAssignmentRecords(projectPath, traces) {
|
|
|
5783
6549
|
return records;
|
|
5784
6550
|
}
|
|
5785
6551
|
async function loadDependencyGraph(projectPath, assignments) {
|
|
5786
|
-
const statusPath =
|
|
6552
|
+
const statusPath = resolve16(projectPath, "_status.md");
|
|
5787
6553
|
if (await fileExists(statusPath)) {
|
|
5788
6554
|
const statusContent = await readFile12(statusPath, "utf-8");
|
|
5789
6555
|
const parsed = parseStatus(statusContent);
|
|
@@ -5958,7 +6724,7 @@ async function getAvailableTransitions(projectsDir, projectSlug, assignmentSlug,
|
|
|
5958
6724
|
const config = await getStatusConfig();
|
|
5959
6725
|
const transitionDefs = getTransitionDefinitions(config);
|
|
5960
6726
|
const actions = [];
|
|
5961
|
-
const projectPath =
|
|
6727
|
+
const projectPath = resolve16(projectsDir, projectSlug);
|
|
5962
6728
|
const traces = options?.traces;
|
|
5963
6729
|
for (const definition of transitionDefs) {
|
|
5964
6730
|
const target = getTargetStatus(assignment.status, definition.command, config.transitionTable);
|
|
@@ -6006,7 +6772,7 @@ async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses, de
|
|
|
6006
6772
|
continue;
|
|
6007
6773
|
}
|
|
6008
6774
|
}
|
|
6009
|
-
const dependencyPath =
|
|
6775
|
+
const dependencyPath = resolve16(projectPath, "assignments", dependency, "assignment.md");
|
|
6010
6776
|
if (!await fileExists(dependencyPath)) {
|
|
6011
6777
|
unmet.push(`${dependency} (missing)`);
|
|
6012
6778
|
continue;
|
|
@@ -6027,7 +6793,7 @@ function parseTimestamp(timestamp) {
|
|
|
6027
6793
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
6028
6794
|
}
|
|
6029
6795
|
async function countOpenQuestions(projectPath, assignmentSlug) {
|
|
6030
|
-
const commentsPath =
|
|
6796
|
+
const commentsPath = resolve16(
|
|
6031
6797
|
projectPath,
|
|
6032
6798
|
"assignments",
|
|
6033
6799
|
assignmentSlug,
|
|
@@ -6392,7 +7158,7 @@ init_agents_schema();
|
|
|
6392
7158
|
init_cwd();
|
|
6393
7159
|
import { spawn as spawn2 } from "child_process";
|
|
6394
7160
|
import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
|
|
6395
|
-
import { isAbsolute as isAbsolute3, resolve as
|
|
7161
|
+
import { isAbsolute as isAbsolute3, resolve as resolve17 } from "path";
|
|
6396
7162
|
init_paths();
|
|
6397
7163
|
init_playbooks();
|
|
6398
7164
|
init_cwd();
|
|
@@ -6595,6 +7361,12 @@ async function resolveSessionPlan(input, terminal) {
|
|
|
6595
7361
|
}
|
|
6596
7362
|
}
|
|
6597
7363
|
}
|
|
7364
|
+
if (!cwd.trim()) {
|
|
7365
|
+
throw new LaunchError(
|
|
7366
|
+
"workspace-path-invalid",
|
|
7367
|
+
`Session ${input.id} has no recorded working directory and no linked assignment workspace resolved \u2014 refusing to launch in an unknown directory.`
|
|
7368
|
+
);
|
|
7369
|
+
}
|
|
6598
7370
|
const agent = getAgents(input.config).find((a) => a.id === session.agent);
|
|
6599
7371
|
if (!agent) {
|
|
6600
7372
|
throw new LaunchError(
|
|
@@ -6623,7 +7395,7 @@ async function resolveSessionPlan(input, terminal) {
|
|
|
6623
7395
|
// src/launch/execute.ts
|
|
6624
7396
|
import { spawn as spawn3 } from "child_process";
|
|
6625
7397
|
import { homedir as homedir5 } from "os";
|
|
6626
|
-
import { basename as basename2, join as join6, resolve as
|
|
7398
|
+
import { basename as basename2, join as join6, resolve as resolve18 } from "path";
|
|
6627
7399
|
|
|
6628
7400
|
// src/utils/terminal-probe.ts
|
|
6629
7401
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -6701,7 +7473,7 @@ init_config2();
|
|
|
6701
7473
|
|
|
6702
7474
|
// src/utils/session-id.ts
|
|
6703
7475
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
6704
|
-
import { mkdirSync, readFileSync, statSync as
|
|
7476
|
+
import { mkdirSync, readFileSync as readFileSync2, statSync as statSync3, writeFileSync } from "fs";
|
|
6705
7477
|
import { homedir as homedir4 } from "os";
|
|
6706
7478
|
import { dirname as dirname3, join as join5 } from "path";
|
|
6707
7479
|
|
|
@@ -6785,7 +7557,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
6785
7557
|
`Spawn failed: ${msg}. Verify the terminal is installed and on PATH.`
|
|
6786
7558
|
);
|
|
6787
7559
|
}
|
|
6788
|
-
await new Promise((
|
|
7560
|
+
await new Promise((resolve20, reject) => {
|
|
6789
7561
|
let settled = false;
|
|
6790
7562
|
let stderr = "";
|
|
6791
7563
|
const finishOk = () => {
|
|
@@ -6795,7 +7567,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
6795
7567
|
child.unref();
|
|
6796
7568
|
} catch {
|
|
6797
7569
|
}
|
|
6798
|
-
|
|
7570
|
+
resolve20();
|
|
6799
7571
|
};
|
|
6800
7572
|
const finishErr = (remediation) => {
|
|
6801
7573
|
if (settled) return;
|
|
@@ -6856,7 +7628,7 @@ async function registerLaunchAtBirth(plan, child) {
|
|
|
6856
7628
|
markerDir
|
|
6857
7629
|
);
|
|
6858
7630
|
if (!sessionId) return;
|
|
6859
|
-
if (autoTrack === "workspaces-only" && !await fileExists(
|
|
7631
|
+
if (autoTrack === "workspaces-only" && !await fileExists(resolve18(plan.cwd, ".syntaur", "context.json"))) {
|
|
6860
7632
|
return;
|
|
6861
7633
|
}
|
|
6862
7634
|
const { initSessionDb: initSessionDb2 } = await Promise.resolve().then(() => (init_session_db(), session_db_exports));
|
|
@@ -7032,8 +7804,8 @@ function appleScriptString(value) {
|
|
|
7032
7804
|
init_paths();
|
|
7033
7805
|
init_fs();
|
|
7034
7806
|
import { fileURLToPath } from "url";
|
|
7035
|
-
import { dirname as dirname4, resolve as
|
|
7036
|
-
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";
|
|
7037
7809
|
var NPX_PATTERNS = [
|
|
7038
7810
|
{ kind: "npm", re: /\/_npx\/([^/]+)\/node_modules(?:\/|$)/ },
|
|
7039
7811
|
{ kind: "pnpm", re: /\/pnpm\/dlx\/([^/]+)\/node_modules(?:\/|$)/ },
|
|
@@ -7058,7 +7830,7 @@ function normalizeSlashes(p) {
|
|
|
7058
7830
|
}
|
|
7059
7831
|
function detectInstallKind(scriptUrl, opts = {}) {
|
|
7060
7832
|
const realpath = opts.realpath ?? realpathSync.native;
|
|
7061
|
-
const readFile13 = opts.readFile ?? ((p) =>
|
|
7833
|
+
const readFile13 = opts.readFile ?? ((p) => readFileSync3(p, "utf-8"));
|
|
7062
7834
|
const ua = opts.envUserAgent !== void 0 ? opts.envUserAgent : process.env.npm_config_user_agent ?? "";
|
|
7063
7835
|
const resolved = resolveScriptPath(scriptUrl, realpath);
|
|
7064
7836
|
if (resolved === null) {
|
|
@@ -7111,7 +7883,7 @@ function extractNpxHash(scriptUrl, opts = {}) {
|
|
|
7111
7883
|
return null;
|
|
7112
7884
|
}
|
|
7113
7885
|
function nudgeStampDir() {
|
|
7114
|
-
return
|
|
7886
|
+
return resolve19(syntaurRoot(), "npx-handler-nudge");
|
|
7115
7887
|
}
|
|
7116
7888
|
function sanitizeHash(hash) {
|
|
7117
7889
|
return hash.replace(/[^A-Za-z0-9_-]/g, "_") || "_";
|