syntaur 0.57.0 → 0.58.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-JSKTFkEa.js → _basePickBy-BSxL8bBc.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-B4THfRv6.js → _baseUniq-1f9ohhNp.js} +1 -1
- package/dashboard/dist/assets/{arc-BT5oCPaC.js → arc-DlPK9j9g.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-C0wRHwPD.js → architectureDiagram-2XIMDMQ5-CcV5llaa.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-yMj3_K19.js → blockDiagram-WCTKOSBZ-DGkYzINJ.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-6fr_jElW.js → c4Diagram-IC4MRINW-CI4Z5c6N.js} +1 -1
- package/dashboard/dist/assets/channel-Qr2eMXkR.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-BS0AdM9N.js → chunk-4BX2VUAB-C8KasqJp.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-Bf3cMgj4.js → chunk-55IACEB6-CgAEilyk.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-BxhOy53m.js → chunk-FMBD7UC4-DsNiby6p.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-Da4w_8jA.js → chunk-JSJVCQXG-4eCDFRIx.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-p3BnTwvF.js → chunk-KX2RTZJC-DHia_zig.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-DfvY-FfQ.js → chunk-NQ4KR5QH-V-IiQwer.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-BY_yeD01.js → chunk-QZHKN3VN-BXTS8E6J.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-yQKlkPsr.js → chunk-WL4C6EOR-BuLlUWn0.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-DHkm1twH.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-DHkm1twH.js +1 -0
- package/dashboard/dist/assets/clone-ChZeNkMI.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CDoLPu4r.js → cose-bilkent-S5V4N54A-B1M1ENTo.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-BsEV2YI1.js → dagre-KLK3FWXG-3pyNBzWY.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-CF2uTCw-.js → diagram-E7M64L7V-B5-5pbKh.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-CCbV7q-H.js → diagram-IFDJBPK2-CTx5NFFi.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-C-hmCZ82.js → diagram-P4PSJMXO-B5KRvZqb.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-Bqmd6M31.js → erDiagram-INFDFZHY-BhVWsRrc.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-BoHMjR8s.js → flowDiagram-PKNHOUZH-CuoB0thO.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-Bwi2K6t0.js → ganttDiagram-A5KZAMGK-yNNalQY9.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-BLg_41tI.js → gitGraphDiagram-K3NZZRJ6-DfEhHzOH.js} +1 -1
- package/dashboard/dist/assets/{graph-D5SqATbG.js → graph-NaP7lLuY.js} +1 -1
- package/dashboard/dist/assets/index-BG_Ed2-k.js +644 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-MQGu_WQh.js → infoDiagram-LFFYTUFH-CDAMAxDE.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-Cq2VOOGw.js → ishikawaDiagram-PHBUUO56-DMRl5xeW.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-DMgBiVbw.js → journeyDiagram-4ABVD52K-DtuQQjW_.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-CW553HfT.js → kanban-definition-K7BYSVSG-DYGt44vU.js} +1 -1
- package/dashboard/dist/assets/{layout-CY_IT3u3.js → layout-B9sS0Gu9.js} +1 -1
- package/dashboard/dist/assets/{linear-BwR3oULZ.js → linear-DQcNFWB2.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-CmEGzmWo.js → mermaid.core-CzhsZ1QZ.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-D__wNWPY.js → mindmap-definition-YRQLILUH-CL8mKZYJ.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-BsFl-giQ.js → pieDiagram-SKSYHLDU-BULUJrhI.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-C-F2YU1C.js → quadrantDiagram-337W2JSQ-CWQC3mf-.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-C0_TmWRh.js → requirementDiagram-Z7DCOOCP-CYadvMn7.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DpoaSHiD.js → sankeyDiagram-WA2Y5GQK-e2AhGJiF.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-DBb9oieZ.js → sequenceDiagram-2WXFIKYE-CeWKKFdg.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-Cw5xaaAs.js → stateDiagram-RAJIS63D-BvN-wCps.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BV7kxiIj.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-rOhPVhqR.js → timeline-definition-YZTLITO2-DOLRfgbL.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-CD-TOoiS.js → treemap-KZPCXAKY-VqhsqWzF.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-BH78k9Wk.js → vennDiagram-LZ73GAT5-Dt-knC6b.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-1hqiJxk_.js → xychartDiagram-JWTSCODW-A3enOtVP.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/dashboard/server.js +208 -131
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +584 -591
- package/dist/index.js.map +1 -1
- 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/codex/.codex-plugin/plugin.json +1 -1
- 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/dashboard/dist/assets/channel-BZWZQgJF.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-obLZNHbe.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-obLZNHbe.js +0 -1
- package/dashboard/dist/assets/clone-gWuoHRfO.js +0 -1
- package/dashboard/dist/assets/index-CQaTQf_I.js +0 -639
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BpeHnq6R.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -1271,6 +1271,7 @@ __export(parser_exports, {
|
|
|
1271
1271
|
readLog: () => readLog,
|
|
1272
1272
|
serializeChecklist: () => serializeChecklist,
|
|
1273
1273
|
serializeChecklistItem: () => serializeChecklistItem,
|
|
1274
|
+
serializeLog: () => serializeLog,
|
|
1274
1275
|
serializeLogEntry: () => serializeLogEntry,
|
|
1275
1276
|
serializeMetaToken: () => serializeMetaToken,
|
|
1276
1277
|
writeChecklist: () => writeChecklist
|
|
@@ -1398,27 +1399,60 @@ function statusToMarker(item) {
|
|
|
1398
1399
|
return item.session ? `>:${sanitizeSession(item.session)}` : ">";
|
|
1399
1400
|
}
|
|
1400
1401
|
}
|
|
1402
|
+
function escapeDescription(description) {
|
|
1403
|
+
return description.replace(/\\/g, "\\\\").replace(/#/g, "\\#").replace(/\[/g, "\\[");
|
|
1404
|
+
}
|
|
1405
|
+
function unescapeDescription(escaped) {
|
|
1406
|
+
let out = "";
|
|
1407
|
+
for (let i = 0; i < escaped.length; i++) {
|
|
1408
|
+
if (escaped[i] === "\\" && i + 1 < escaped.length) {
|
|
1409
|
+
const next = escaped[i + 1];
|
|
1410
|
+
if (next === "\\" || next === "#" || next === "[") {
|
|
1411
|
+
out += next;
|
|
1412
|
+
i++;
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
out += escaped[i];
|
|
1417
|
+
}
|
|
1418
|
+
return out;
|
|
1419
|
+
}
|
|
1420
|
+
function findStructuralCut(rest) {
|
|
1421
|
+
for (let i = 0; i < rest.length; i++) {
|
|
1422
|
+
const ch = rest[i];
|
|
1423
|
+
if (ch !== "#" && ch !== "[") continue;
|
|
1424
|
+
let backslashes = 0;
|
|
1425
|
+
let j = i - 1;
|
|
1426
|
+
while (j >= 0 && rest[j] === "\\") {
|
|
1427
|
+
backslashes++;
|
|
1428
|
+
j--;
|
|
1429
|
+
}
|
|
1430
|
+
if (backslashes % 2 === 1) continue;
|
|
1431
|
+
if (ch === "#") {
|
|
1432
|
+
if (/[a-zA-Z0-9_-]/.test(rest[i + 1] ?? "")) return i;
|
|
1433
|
+
} else {
|
|
1434
|
+
return i;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return rest.length;
|
|
1438
|
+
}
|
|
1401
1439
|
function parseChecklistItem(line) {
|
|
1402
1440
|
const match = line.match(ITEM_REGEX);
|
|
1403
1441
|
if (!match) return null;
|
|
1404
1442
|
const marker = match[1];
|
|
1405
1443
|
const rest = match[2];
|
|
1406
1444
|
const { status, session } = parseStatus2(marker);
|
|
1407
|
-
const
|
|
1445
|
+
const cut = findStructuralCut(rest);
|
|
1446
|
+
const description = unescapeDescription(rest.slice(0, cut).trim());
|
|
1447
|
+
const tail = rest.slice(cut);
|
|
1448
|
+
const idMatch = tail.match(ID_REGEX);
|
|
1408
1449
|
const id = idMatch ? idMatch[1] : "";
|
|
1409
1450
|
const tags = [];
|
|
1410
1451
|
let tagMatch;
|
|
1411
1452
|
const tagRegex = new RegExp(TAG_REGEX.source, "g");
|
|
1412
|
-
while ((tagMatch = tagRegex.exec(
|
|
1453
|
+
while ((tagMatch = tagRegex.exec(tail)) !== null) {
|
|
1413
1454
|
tags.push(tagMatch[1]);
|
|
1414
1455
|
}
|
|
1415
|
-
let description = rest;
|
|
1416
|
-
const firstTagIdx = rest.search(/#[a-zA-Z0-9_-]/);
|
|
1417
|
-
const firstIdIdx = rest.search(/\[t:[a-f0-9]{4}\]/);
|
|
1418
|
-
const cutPoints = [firstTagIdx, firstIdIdx].filter((i) => i >= 0);
|
|
1419
|
-
if (cutPoints.length > 0) {
|
|
1420
|
-
description = rest.slice(0, Math.min(...cutPoints)).trim();
|
|
1421
|
-
}
|
|
1422
1456
|
const meta = parseMetaToken(line);
|
|
1423
1457
|
return {
|
|
1424
1458
|
id,
|
|
@@ -1439,7 +1473,7 @@ function parseChecklistItem(line) {
|
|
|
1439
1473
|
function serializeChecklistItem(item) {
|
|
1440
1474
|
const marker = statusToMarker(item);
|
|
1441
1475
|
const tagStr = item.tags.map((t) => `#${t}`).join(" ");
|
|
1442
|
-
const parts = [`- [${marker}] ${item.description}`];
|
|
1476
|
+
const parts = [`- [${marker}] ${escapeDescription(item.description)}`];
|
|
1443
1477
|
if (tagStr) parts.push(tagStr);
|
|
1444
1478
|
parts.push(`[t:${item.id}]`);
|
|
1445
1479
|
const meta = serializeMetaToken(item);
|
|
@@ -1538,6 +1572,18 @@ function serializeLogEntry(entry) {
|
|
|
1538
1572
|
if (entry.status) lines.push(`**Status:** ${entry.status}`);
|
|
1539
1573
|
return lines.join("\n");
|
|
1540
1574
|
}
|
|
1575
|
+
function serializeLog(log) {
|
|
1576
|
+
const header = `---
|
|
1577
|
+
workspace: ${log.workspace}
|
|
1578
|
+
---
|
|
1579
|
+
|
|
1580
|
+
# Todo Log
|
|
1581
|
+
`;
|
|
1582
|
+
if (log.entries.length === 0) {
|
|
1583
|
+
return header;
|
|
1584
|
+
}
|
|
1585
|
+
return header + "\n" + log.entries.map(serializeLogEntry).join("\n\n") + "\n";
|
|
1586
|
+
}
|
|
1541
1587
|
function checklistPath(todosDir2, workspace) {
|
|
1542
1588
|
return resolve2(todosDir2, `${workspace}.md`);
|
|
1543
1589
|
}
|
|
@@ -14274,7 +14320,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
14274
14320
|
};
|
|
14275
14321
|
const entry = formatCommentEntry(comment);
|
|
14276
14322
|
let next = setTopLevelField(currentContent, "entryCount", String(currentCount + 1));
|
|
14277
|
-
next = setTopLevelField(next, "updated",
|
|
14323
|
+
next = setTopLevelField(next, "updated", timestamp);
|
|
14278
14324
|
if (next.includes("No comments yet.")) {
|
|
14279
14325
|
next = next.replace("No comments yet.", entry.trimEnd());
|
|
14280
14326
|
} else {
|
|
@@ -14334,7 +14380,7 @@ ${entry}`;
|
|
|
14334
14380
|
res.status(500).json({ error: "Failed to update resolved flag" });
|
|
14335
14381
|
return;
|
|
14336
14382
|
}
|
|
14337
|
-
const withUpdated = setTopLevelField(next, "updated",
|
|
14383
|
+
const withUpdated = setTopLevelField(next, "updated", nowTimestamp());
|
|
14338
14384
|
await writeFileForce(commentsPath, withUpdated);
|
|
14339
14385
|
const assignment = await getAssignmentDetail(projectsDir, projectSlug, assignmentSlug);
|
|
14340
14386
|
res.json({ assignment });
|
|
@@ -15803,7 +15849,7 @@ async function appendCommentTo(assignmentDir, assignmentRef, req2, res, reloadDe
|
|
|
15803
15849
|
};
|
|
15804
15850
|
const entry = formatCommentEntry(comment);
|
|
15805
15851
|
let next = setTopLevelField(currentContent, "entryCount", String(currentCount + 1));
|
|
15806
|
-
next = setTopLevelField(next, "updated",
|
|
15852
|
+
next = setTopLevelField(next, "updated", timestamp);
|
|
15807
15853
|
if (next.includes("No comments yet.")) {
|
|
15808
15854
|
next = next.replace("No comments yet.", entry.trimEnd());
|
|
15809
15855
|
} else {
|
|
@@ -15846,7 +15892,7 @@ async function toggleCommentResolvedAt(assignmentDir, commentId, req2, res, relo
|
|
|
15846
15892
|
res.status(500).json({ error: "Failed to update resolved flag" });
|
|
15847
15893
|
return;
|
|
15848
15894
|
}
|
|
15849
|
-
const withUpdated = setTopLevelField(next, "updated",
|
|
15895
|
+
const withUpdated = setTopLevelField(next, "updated", nowTimestamp());
|
|
15850
15896
|
await writeFileForce(commentsPath, withUpdated);
|
|
15851
15897
|
const assignment = await reloadDetail();
|
|
15852
15898
|
res.json({ assignment });
|
|
@@ -18657,6 +18703,119 @@ async function readEvents(jobId) {
|
|
|
18657
18703
|
import { createHash as createHash3 } from "crypto";
|
|
18658
18704
|
import { open as open4, readFile as readFile21, stat as stat3, unlink as unlink6 } from "fs/promises";
|
|
18659
18705
|
import { resolve as resolve29 } from "path";
|
|
18706
|
+
|
|
18707
|
+
// src/schedules/triggers.ts
|
|
18708
|
+
import { Cron } from "croner";
|
|
18709
|
+
|
|
18710
|
+
// src/schedules/reset-window.ts
|
|
18711
|
+
var FIVE_HOURS_MS = 5 * 60 * 60 * 1e3;
|
|
18712
|
+
var ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
18713
|
+
function windowLengthMs(anchor) {
|
|
18714
|
+
return anchor.windowKind === "weekly" ? ONE_WEEK_MS : FIVE_HOURS_MS;
|
|
18715
|
+
}
|
|
18716
|
+
function predictReset(_provider, anchor) {
|
|
18717
|
+
const start = Date.parse(anchor.windowStartIso);
|
|
18718
|
+
if (Number.isNaN(start)) {
|
|
18719
|
+
throw new Error(`Invalid reset anchor windowStartIso: ${anchor.windowStartIso}`);
|
|
18720
|
+
}
|
|
18721
|
+
return new Date(start + windowLengthMs(anchor)).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
18722
|
+
}
|
|
18723
|
+
function verifyReset(provider, anchor, now) {
|
|
18724
|
+
const reset = predictReset(provider, anchor);
|
|
18725
|
+
if (now.getTime() >= Date.parse(reset)) return { eligible: true };
|
|
18726
|
+
return { eligible: false, rescheduleToIso: reset };
|
|
18727
|
+
}
|
|
18728
|
+
|
|
18729
|
+
// src/schedules/triggers.ts
|
|
18730
|
+
function iso(d) {
|
|
18731
|
+
return d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
18732
|
+
}
|
|
18733
|
+
function isRecurring(trigger) {
|
|
18734
|
+
return trigger.kind === "cron";
|
|
18735
|
+
}
|
|
18736
|
+
var notDue = { due: false };
|
|
18737
|
+
function evaluateTrigger(job, ctx) {
|
|
18738
|
+
const consumed = new Set(job.attempt.consumedEdges);
|
|
18739
|
+
const result = evaluateKind(job.trigger, job, ctx);
|
|
18740
|
+
if (result.due && result.dedupeKey && consumed.has(result.dedupeKey)) {
|
|
18741
|
+
return { ...result, due: false };
|
|
18742
|
+
}
|
|
18743
|
+
return result;
|
|
18744
|
+
}
|
|
18745
|
+
function evaluateKind(trigger, job, ctx) {
|
|
18746
|
+
const createdAtMs = Date.parse(job.createdAt);
|
|
18747
|
+
switch (trigger.kind) {
|
|
18748
|
+
case "at": {
|
|
18749
|
+
const at = Date.parse(trigger.at);
|
|
18750
|
+
const due = !Number.isNaN(at) && ctx.now.getTime() >= at;
|
|
18751
|
+
return { due, dedupeKey: `at:${trigger.at}`, nextFireIso: trigger.at };
|
|
18752
|
+
}
|
|
18753
|
+
case "in": {
|
|
18754
|
+
const anchor = Date.parse(trigger.anchorIso);
|
|
18755
|
+
const fireAt = anchor + trigger.durationMs;
|
|
18756
|
+
const due = !Number.isNaN(anchor) && ctx.now.getTime() >= fireAt;
|
|
18757
|
+
const fireIso = Number.isNaN(anchor) ? null : iso(new Date(fireAt));
|
|
18758
|
+
return { due, dedupeKey: `in:${fireAt}`, nextFireIso: fireIso };
|
|
18759
|
+
}
|
|
18760
|
+
case "cron":
|
|
18761
|
+
return evaluateCron(trigger, ctx.now, createdAtMs);
|
|
18762
|
+
case "after-reset":
|
|
18763
|
+
return evaluateAfterReset(trigger, ctx.now);
|
|
18764
|
+
case "when-status":
|
|
18765
|
+
return evaluateWhenStatus(trigger, job, ctx, createdAtMs);
|
|
18766
|
+
case "when-plan-lands":
|
|
18767
|
+
return evaluateWhenPlanLands(ctx, createdAtMs);
|
|
18768
|
+
}
|
|
18769
|
+
}
|
|
18770
|
+
function evaluateCron(trigger, now, createdAtMs) {
|
|
18771
|
+
let cron;
|
|
18772
|
+
try {
|
|
18773
|
+
cron = new Cron(trigger.expr, trigger.tz ? { timezone: trigger.tz } : {});
|
|
18774
|
+
} catch {
|
|
18775
|
+
return notDue;
|
|
18776
|
+
}
|
|
18777
|
+
const prevs = cron.previousRuns(1, new Date(now.getTime() + 1e3));
|
|
18778
|
+
let prev = prevs.length > 0 ? prevs[0] : null;
|
|
18779
|
+
if (prev && prev.getTime() > now.getTime()) prev = null;
|
|
18780
|
+
if (prev && !Number.isNaN(createdAtMs) && prev.getTime() < createdAtMs) prev = null;
|
|
18781
|
+
const next = cron.nextRun(now);
|
|
18782
|
+
if (!prev) return { due: false, nextFireIso: next ? iso(next) : null };
|
|
18783
|
+
return { due: true, dedupeKey: `cron:${iso(prev)}`, nextFireIso: next ? iso(next) : null };
|
|
18784
|
+
}
|
|
18785
|
+
function evaluateAfterReset(trigger, now) {
|
|
18786
|
+
const v = verifyReset(trigger.provider, trigger.anchor, now);
|
|
18787
|
+
const predicted = predictReset(trigger.provider, trigger.anchor);
|
|
18788
|
+
if (v.eligible) {
|
|
18789
|
+
return { due: true, dedupeKey: `after-reset:${trigger.anchor.windowStartIso}`, nextFireIso: predicted };
|
|
18790
|
+
}
|
|
18791
|
+
return { due: false, nextFireIso: predicted, rescheduleToIso: v.rescheduleToIso };
|
|
18792
|
+
}
|
|
18793
|
+
function evaluateWhenStatus(trigger, job, ctx, createdAtMs) {
|
|
18794
|
+
const fm = ctx.assignment;
|
|
18795
|
+
if (!fm) return notDue;
|
|
18796
|
+
const history = fm.statusHistory ?? [];
|
|
18797
|
+
for (let i = job.attempt.cursor; i < history.length; i++) {
|
|
18798
|
+
const atMs = Date.parse(history[i].at);
|
|
18799
|
+
const afterCreation = Number.isNaN(createdAtMs) || Number.isNaN(atMs) || atMs >= createdAtMs;
|
|
18800
|
+
if (history[i].to === trigger.status && afterCreation) {
|
|
18801
|
+
return { due: true, dedupeKey: `status:${i}:${history[i].at}`, nextCursor: i + 1 };
|
|
18802
|
+
}
|
|
18803
|
+
}
|
|
18804
|
+
return { due: false, nextCursor: history.length };
|
|
18805
|
+
}
|
|
18806
|
+
function evaluateWhenPlanLands(ctx, createdAtMs) {
|
|
18807
|
+
const fm = ctx.assignment;
|
|
18808
|
+
if (!fm) return notDue;
|
|
18809
|
+
const approval = fm.planApproval;
|
|
18810
|
+
if (!approval) return notDue;
|
|
18811
|
+
const approvedMs = Date.parse(approval.at);
|
|
18812
|
+
if (!Number.isNaN(createdAtMs) && !Number.isNaN(approvedMs) && approvedMs < createdAtMs) {
|
|
18813
|
+
return notDue;
|
|
18814
|
+
}
|
|
18815
|
+
return { due: true, dedupeKey: `plan:${approval.file}:${approval.digest}` };
|
|
18816
|
+
}
|
|
18817
|
+
|
|
18818
|
+
// src/schedules/attempt.ts
|
|
18660
18819
|
init_timestamp();
|
|
18661
18820
|
|
|
18662
18821
|
// src/schedules/types.ts
|
|
@@ -18855,6 +19014,15 @@ async function markLaunchFailed(job, reason) {
|
|
|
18855
19014
|
await appendEvent(job.id, "launch_failed", { reason });
|
|
18856
19015
|
return written;
|
|
18857
19016
|
}
|
|
19017
|
+
async function markCompleted(job) {
|
|
19018
|
+
const next = {
|
|
19019
|
+
...job,
|
|
19020
|
+
attempt: { ...job.attempt, state: "completed", claim: null }
|
|
19021
|
+
};
|
|
19022
|
+
const written = await writeJob(next);
|
|
19023
|
+
await appendEvent(job.id, "completed", { reason: "one-shot session ended" });
|
|
19024
|
+
return written;
|
|
19025
|
+
}
|
|
18858
19026
|
var TransitionError = class extends Error {
|
|
18859
19027
|
constructor(message) {
|
|
18860
19028
|
super(message);
|
|
@@ -18942,10 +19110,11 @@ function retryJob(id) {
|
|
|
18942
19110
|
() => ({ claim: null, lastError: null })
|
|
18943
19111
|
);
|
|
18944
19112
|
}
|
|
19113
|
+
var ONE_SHOT_COMPLETE_GRACE_MS = 6e4;
|
|
18945
19114
|
async function reapStale(jobs, deps) {
|
|
18946
19115
|
const now = deps.now();
|
|
18947
19116
|
const isLive = deps.isSessionLive ?? (() => true);
|
|
18948
|
-
const out = { reaped: [], stuck: [] };
|
|
19117
|
+
const out = { reaped: [], stuck: [], completed: [] };
|
|
18949
19118
|
for (const job of jobs) {
|
|
18950
19119
|
const a = job.attempt;
|
|
18951
19120
|
if ((a.state === "claimed" || a.state === "launching") && a.claim && now.getTime() > a.claim.expiresAt) {
|
|
@@ -18954,7 +19123,17 @@ async function reapStale(jobs, deps) {
|
|
|
18954
19123
|
out.reaped.push(job.id);
|
|
18955
19124
|
continue;
|
|
18956
19125
|
}
|
|
18957
|
-
if (a.state
|
|
19126
|
+
if (a.state !== "running") continue;
|
|
19127
|
+
if (!isRecurring(job.trigger) && a.sessionId && a.runningSince) {
|
|
19128
|
+
const graceMs = job.limits.maxRuntimeMs ?? ONE_SHOT_COMPLETE_GRACE_MS;
|
|
19129
|
+
const pastGrace = now.getTime() - Date.parse(a.runningSince) > graceMs;
|
|
19130
|
+
if (pastGrace && !isLive(a.sessionId, a.launchPid)) {
|
|
19131
|
+
await markCompleted(job);
|
|
19132
|
+
out.completed.push(job.id);
|
|
19133
|
+
continue;
|
|
19134
|
+
}
|
|
19135
|
+
}
|
|
19136
|
+
if (job.limits.maxRuntimeMs && a.runningSince) {
|
|
18958
19137
|
const overrun = now.getTime() - Date.parse(a.runningSince) > job.limits.maxRuntimeMs;
|
|
18959
19138
|
if (overrun && !isLive(a.sessionId, a.launchPid) && a.lastError !== "stuck:max-runtime") {
|
|
18960
19139
|
await writeJob({ ...job, attempt: { ...a, lastError: "stuck:max-runtime" } });
|
|
@@ -19138,117 +19317,6 @@ init_frontmatter();
|
|
|
19138
19317
|
import { readFile as readFile22 } from "fs/promises";
|
|
19139
19318
|
import { resolve as resolve31 } from "path";
|
|
19140
19319
|
|
|
19141
|
-
// src/schedules/triggers.ts
|
|
19142
|
-
import { Cron } from "croner";
|
|
19143
|
-
|
|
19144
|
-
// src/schedules/reset-window.ts
|
|
19145
|
-
var FIVE_HOURS_MS = 5 * 60 * 60 * 1e3;
|
|
19146
|
-
var ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
19147
|
-
function windowLengthMs(anchor) {
|
|
19148
|
-
return anchor.windowKind === "weekly" ? ONE_WEEK_MS : FIVE_HOURS_MS;
|
|
19149
|
-
}
|
|
19150
|
-
function predictReset(_provider, anchor) {
|
|
19151
|
-
const start = Date.parse(anchor.windowStartIso);
|
|
19152
|
-
if (Number.isNaN(start)) {
|
|
19153
|
-
throw new Error(`Invalid reset anchor windowStartIso: ${anchor.windowStartIso}`);
|
|
19154
|
-
}
|
|
19155
|
-
return new Date(start + windowLengthMs(anchor)).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
19156
|
-
}
|
|
19157
|
-
function verifyReset(provider, anchor, now) {
|
|
19158
|
-
const reset = predictReset(provider, anchor);
|
|
19159
|
-
if (now.getTime() >= Date.parse(reset)) return { eligible: true };
|
|
19160
|
-
return { eligible: false, rescheduleToIso: reset };
|
|
19161
|
-
}
|
|
19162
|
-
|
|
19163
|
-
// src/schedules/triggers.ts
|
|
19164
|
-
function iso(d) {
|
|
19165
|
-
return d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
19166
|
-
}
|
|
19167
|
-
function isRecurring(trigger) {
|
|
19168
|
-
return trigger.kind === "cron";
|
|
19169
|
-
}
|
|
19170
|
-
var notDue = { due: false };
|
|
19171
|
-
function evaluateTrigger(job, ctx) {
|
|
19172
|
-
const consumed = new Set(job.attempt.consumedEdges);
|
|
19173
|
-
const result = evaluateKind(job.trigger, job, ctx);
|
|
19174
|
-
if (result.due && result.dedupeKey && consumed.has(result.dedupeKey)) {
|
|
19175
|
-
return { ...result, due: false };
|
|
19176
|
-
}
|
|
19177
|
-
return result;
|
|
19178
|
-
}
|
|
19179
|
-
function evaluateKind(trigger, job, ctx) {
|
|
19180
|
-
const createdAtMs = Date.parse(job.createdAt);
|
|
19181
|
-
switch (trigger.kind) {
|
|
19182
|
-
case "at": {
|
|
19183
|
-
const at = Date.parse(trigger.at);
|
|
19184
|
-
const due = !Number.isNaN(at) && ctx.now.getTime() >= at;
|
|
19185
|
-
return { due, dedupeKey: `at:${trigger.at}`, nextFireIso: trigger.at };
|
|
19186
|
-
}
|
|
19187
|
-
case "in": {
|
|
19188
|
-
const anchor = Date.parse(trigger.anchorIso);
|
|
19189
|
-
const fireAt = anchor + trigger.durationMs;
|
|
19190
|
-
const due = !Number.isNaN(anchor) && ctx.now.getTime() >= fireAt;
|
|
19191
|
-
const fireIso = Number.isNaN(anchor) ? null : iso(new Date(fireAt));
|
|
19192
|
-
return { due, dedupeKey: `in:${fireAt}`, nextFireIso: fireIso };
|
|
19193
|
-
}
|
|
19194
|
-
case "cron":
|
|
19195
|
-
return evaluateCron(trigger, ctx.now, createdAtMs);
|
|
19196
|
-
case "after-reset":
|
|
19197
|
-
return evaluateAfterReset(trigger, ctx.now);
|
|
19198
|
-
case "when-status":
|
|
19199
|
-
return evaluateWhenStatus(trigger, job, ctx, createdAtMs);
|
|
19200
|
-
case "when-plan-lands":
|
|
19201
|
-
return evaluateWhenPlanLands(ctx, createdAtMs);
|
|
19202
|
-
}
|
|
19203
|
-
}
|
|
19204
|
-
function evaluateCron(trigger, now, createdAtMs) {
|
|
19205
|
-
let cron;
|
|
19206
|
-
try {
|
|
19207
|
-
cron = new Cron(trigger.expr, trigger.tz ? { timezone: trigger.tz } : {});
|
|
19208
|
-
} catch {
|
|
19209
|
-
return notDue;
|
|
19210
|
-
}
|
|
19211
|
-
const prevs = cron.previousRuns(1, new Date(now.getTime() + 1e3));
|
|
19212
|
-
let prev = prevs.length > 0 ? prevs[0] : null;
|
|
19213
|
-
if (prev && prev.getTime() > now.getTime()) prev = null;
|
|
19214
|
-
if (prev && !Number.isNaN(createdAtMs) && prev.getTime() < createdAtMs) prev = null;
|
|
19215
|
-
const next = cron.nextRun(now);
|
|
19216
|
-
if (!prev) return { due: false, nextFireIso: next ? iso(next) : null };
|
|
19217
|
-
return { due: true, dedupeKey: `cron:${iso(prev)}`, nextFireIso: next ? iso(next) : null };
|
|
19218
|
-
}
|
|
19219
|
-
function evaluateAfterReset(trigger, now) {
|
|
19220
|
-
const v = verifyReset(trigger.provider, trigger.anchor, now);
|
|
19221
|
-
const predicted = predictReset(trigger.provider, trigger.anchor);
|
|
19222
|
-
if (v.eligible) {
|
|
19223
|
-
return { due: true, dedupeKey: `after-reset:${trigger.anchor.windowStartIso}`, nextFireIso: predicted };
|
|
19224
|
-
}
|
|
19225
|
-
return { due: false, nextFireIso: predicted, rescheduleToIso: v.rescheduleToIso };
|
|
19226
|
-
}
|
|
19227
|
-
function evaluateWhenStatus(trigger, job, ctx, createdAtMs) {
|
|
19228
|
-
const fm = ctx.assignment;
|
|
19229
|
-
if (!fm) return notDue;
|
|
19230
|
-
const history = fm.statusHistory ?? [];
|
|
19231
|
-
for (let i = job.attempt.cursor; i < history.length; i++) {
|
|
19232
|
-
const atMs = Date.parse(history[i].at);
|
|
19233
|
-
const afterCreation = Number.isNaN(createdAtMs) || Number.isNaN(atMs) || atMs >= createdAtMs;
|
|
19234
|
-
if (history[i].to === trigger.status && afterCreation) {
|
|
19235
|
-
return { due: true, dedupeKey: `status:${i}:${history[i].at}`, nextCursor: i + 1 };
|
|
19236
|
-
}
|
|
19237
|
-
}
|
|
19238
|
-
return { due: false, nextCursor: history.length };
|
|
19239
|
-
}
|
|
19240
|
-
function evaluateWhenPlanLands(ctx, createdAtMs) {
|
|
19241
|
-
const fm = ctx.assignment;
|
|
19242
|
-
if (!fm) return notDue;
|
|
19243
|
-
const approval = fm.planApproval;
|
|
19244
|
-
if (!approval) return notDue;
|
|
19245
|
-
const approvedMs = Date.parse(approval.at);
|
|
19246
|
-
if (!Number.isNaN(createdAtMs) && !Number.isNaN(approvedMs) && approvedMs < createdAtMs) {
|
|
19247
|
-
return notDue;
|
|
19248
|
-
}
|
|
19249
|
-
return { due: true, dedupeKey: `plan:${approval.file}:${approval.digest}` };
|
|
19250
|
-
}
|
|
19251
|
-
|
|
19252
19320
|
// src/schedules/launch-ack.ts
|
|
19253
19321
|
init_session_id();
|
|
19254
19322
|
import { readdirSync } from "fs";
|
|
@@ -19330,11 +19398,12 @@ async function defaultResolvePlan(job) {
|
|
|
19330
19398
|
async function runTick(deps = {}) {
|
|
19331
19399
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
19332
19400
|
const attemptDeps = { now, isSessionLive: deps.isSessionLive };
|
|
19333
|
-
const result = { evaluated: 0, fired: [], failed: [], skipped: 0, reaped: [], stuck: [] };
|
|
19401
|
+
const result = { evaluated: 0, fired: [], failed: [], skipped: 0, reaped: [], stuck: [], completed: [] };
|
|
19334
19402
|
if (deps.reap !== false) {
|
|
19335
19403
|
const reap = await reapStale(await listJobs(), attemptDeps);
|
|
19336
19404
|
result.reaped = reap.reaped;
|
|
19337
19405
|
result.stuck = reap.stuck;
|
|
19406
|
+
result.completed = reap.completed;
|
|
19338
19407
|
}
|
|
19339
19408
|
const jobs = await listJobs();
|
|
19340
19409
|
for (const job of jobs) {
|
|
@@ -20999,6 +21068,9 @@ workspace: ${workspace}
|
|
|
20999
21068
|
await writeFileForce2(archFile, archContent);
|
|
21000
21069
|
checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
|
|
21001
21070
|
await writeChecklist(todosDir2, checklist);
|
|
21071
|
+
const archivedEntries = new Set(toArchive);
|
|
21072
|
+
log.entries = log.entries.filter((e) => !archivedEntries.has(e));
|
|
21073
|
+
await writeFileForce2(logPath(todosDir2, workspace), serializeLog(log));
|
|
21002
21074
|
for (const id of completedIds) {
|
|
21003
21075
|
await deleteAllAttachments(todosDir2, workspace, id);
|
|
21004
21076
|
}
|
|
@@ -21743,6 +21815,9 @@ workspace: ${slug}
|
|
|
21743
21815
|
checklist.workspace = slug;
|
|
21744
21816
|
checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
|
|
21745
21817
|
await writeChecklist(todosDir2, checklist);
|
|
21818
|
+
const archivedEntries = new Set(toArchive);
|
|
21819
|
+
log.entries = log.entries.filter((e) => !archivedEntries.has(e));
|
|
21820
|
+
await writeFileForce(logPath(todosDir2, slug), serializeLog(log));
|
|
21746
21821
|
for (const id of completedIds) {
|
|
21747
21822
|
await deleteAllAttachments(todosDir2, slug, id);
|
|
21748
21823
|
}
|
|
@@ -24355,9 +24430,11 @@ function createDashboardServer(options) {
|
|
|
24355
24430
|
return new Promise((resolvePromise, reject) => {
|
|
24356
24431
|
server.on("error", (err) => {
|
|
24357
24432
|
if (err.code === "EADDRINUSE") {
|
|
24358
|
-
|
|
24359
|
-
`Port ${port} is already in use
|
|
24360
|
-
)
|
|
24433
|
+
const inUse = new Error(
|
|
24434
|
+
`Port ${port} is already in use.`
|
|
24435
|
+
);
|
|
24436
|
+
inUse.code = "EADDRINUSE";
|
|
24437
|
+
reject(inUse);
|
|
24361
24438
|
} else {
|
|
24362
24439
|
reject(err);
|
|
24363
24440
|
}
|