syntaur 0.62.0 → 0.64.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.
Files changed (67) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +20 -0
  3. package/dashboard/dist/assets/{_basePickBy-C0DmX-Lf.js → _basePickBy-CwwFEBj8.js} +1 -1
  4. package/dashboard/dist/assets/{_baseUniq-B656N5Fp.js → _baseUniq-BoxAvlar.js} +1 -1
  5. package/dashboard/dist/assets/{arc-DWrED_i3.js → arc-DohD31yM.js} +1 -1
  6. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-B7gQ_B6F.js → architectureDiagram-2XIMDMQ5-J4SdvLJs.js} +1 -1
  7. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-CrqgprVY.js → blockDiagram-WCTKOSBZ-BHuFRK4q.js} +1 -1
  8. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-B6JBjUpY.js → c4Diagram-IC4MRINW-C5jiXpiB.js} +1 -1
  9. package/dashboard/dist/assets/channel-3-4_gf6X.js +1 -0
  10. package/dashboard/dist/assets/{chunk-4BX2VUAB-Co4Kcyxc.js → chunk-4BX2VUAB-CcShTIhb.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-55IACEB6-D9r_hMAn.js → chunk-55IACEB6-BJDZp3Ih.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-FMBD7UC4-Dxxe9AQy.js → chunk-FMBD7UC4-DgX8V3qh.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-JSJVCQXG-CXn2IalW.js → chunk-JSJVCQXG-DnYWrOFd.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-KX2RTZJC-C0lpRUjK.js → chunk-KX2RTZJC-CvsjbfkS.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-NQ4KR5QH-CE2rv8cX.js → chunk-NQ4KR5QH-DVLwGTNn.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-QZHKN3VN-Drs_1Xrc.js → chunk-QZHKN3VN-CFNsM5VV.js} +1 -1
  17. package/dashboard/dist/assets/{chunk-WL4C6EOR-IlTQx43D.js → chunk-WL4C6EOR-BgvnUwmv.js} +1 -1
  18. package/dashboard/dist/assets/classDiagram-VBA2DB6C-CrATshpz.js +1 -0
  19. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-CrATshpz.js +1 -0
  20. package/dashboard/dist/assets/clone-CfV5MG52.js +1 -0
  21. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-C4Xv3sEQ.js → cose-bilkent-S5V4N54A-C4aLmHe0.js} +1 -1
  22. package/dashboard/dist/assets/{dagre-KLK3FWXG-BzfEk7Lk.js → dagre-KLK3FWXG-BN-9NtEU.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-E7M64L7V-CPwBFGGc.js → diagram-E7M64L7V-CeYAeYg6.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-IFDJBPK2-DSbGA26W.js → diagram-IFDJBPK2-DwKlGh9_.js} +1 -1
  25. package/dashboard/dist/assets/{diagram-P4PSJMXO-XGhn9Qv-.js → diagram-P4PSJMXO-Brl0MDaZ.js} +1 -1
  26. package/dashboard/dist/assets/{erDiagram-INFDFZHY-XeTWZkc6.js → erDiagram-INFDFZHY-DUyLXkUI.js} +1 -1
  27. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-7vHLCNpk.js → flowDiagram-PKNHOUZH-DBgZTa3b.js} +1 -1
  28. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-C9ifnMpV.js → ganttDiagram-A5KZAMGK-CTMXhGer.js} +1 -1
  29. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-2o_Myer6.js → gitGraphDiagram-K3NZZRJ6-CMeLom6N.js} +1 -1
  30. package/dashboard/dist/assets/{graph-D9VPbOXW.js → graph-BQZrNogB.js} +1 -1
  31. package/dashboard/dist/assets/{index-BLRRdPLK.css → index-DlUgV5eO.css} +1 -1
  32. package/dashboard/dist/assets/index-tUNHiaW4.js +659 -0
  33. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-BlCEEtf8.js → infoDiagram-LFFYTUFH-CfiYCGUc.js} +1 -1
  34. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-BGYOHhaE.js → ishikawaDiagram-PHBUUO56-WSdBFOoI.js} +1 -1
  35. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-Cn3nH440.js → journeyDiagram-4ABVD52K-u-S5-YRq.js} +1 -1
  36. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-Beagbum1.js → kanban-definition-K7BYSVSG-B5sU56Sm.js} +1 -1
  37. package/dashboard/dist/assets/{layout-DXJOlege.js → layout-CH9z9Vzx.js} +1 -1
  38. package/dashboard/dist/assets/{linear-gb5BSwpC.js → linear-C-SiDLxL.js} +1 -1
  39. package/dashboard/dist/assets/{mermaid.core-leU3TCkv.js → mermaid.core-CGgij72_.js} +4 -4
  40. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-DFGnCvnf.js → mindmap-definition-YRQLILUH-yu7UCSjb.js} +1 -1
  41. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-HmtQWxMR.js → pieDiagram-SKSYHLDU-BGSUgFeI.js} +1 -1
  42. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-BTDN-lUs.js → quadrantDiagram-337W2JSQ-XnJqntVQ.js} +1 -1
  43. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Da0Yc9Cc.js → requirementDiagram-Z7DCOOCP-CZ5tl3qr.js} +1 -1
  44. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DgfucBhp.js → sankeyDiagram-WA2Y5GQK-CgcGiKDB.js} +1 -1
  45. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-CFIS7rpy.js → sequenceDiagram-2WXFIKYE--GDQSqiF.js} +1 -1
  46. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-CYPPhI5Y.js → stateDiagram-RAJIS63D-sbaNsQ3O.js} +1 -1
  47. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-Cr-Lm_NG.js +1 -0
  48. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-BidLs2_X.js → timeline-definition-YZTLITO2-DieNq8qp.js} +1 -1
  49. package/dashboard/dist/assets/{treemap-KZPCXAKY-cEHQHmiX.js → treemap-KZPCXAKY-CND0AGzG.js} +1 -1
  50. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-B78ndeUP.js → vennDiagram-LZ73GAT5-C32CbTtv.js} +1 -1
  51. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-DhNlfMcT.js → xychartDiagram-JWTSCODW-I-8Tu4ki.js} +1 -1
  52. package/dashboard/dist/index.html +2 -2
  53. package/dist/dashboard/server.js +441 -100
  54. package/dist/dashboard/server.js.map +1 -1
  55. package/dist/index.js +1011 -561
  56. package/dist/index.js.map +1 -1
  57. package/package.json +1 -1
  58. package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
  59. package/platforms/codex/.codex-plugin/plugin.json +1 -1
  60. package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
  61. package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
  62. package/dashboard/dist/assets/channel-CDkiQtSs.js +0 -1
  63. package/dashboard/dist/assets/classDiagram-VBA2DB6C-C8nQGnrI.js +0 -1
  64. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-C8nQGnrI.js +0 -1
  65. package/dashboard/dist/assets/clone-BYliW2bC.js +0 -1
  66. package/dashboard/dist/assets/index-n4_qf3_i.js +0 -644
  67. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DC2NHdS4.js +0 -1
@@ -6215,7 +6215,7 @@ async function computeFactsDetailed(input) {
6215
6215
  const ac = countRealAcceptanceCriteria(body);
6216
6216
  const needsPlanDigest = frontmatter.planApproval !== null || declarations.some((d) => d.type === "attestation" && d.binds === "plan");
6217
6217
  const planFile = await latestPlanFile(assignmentDir);
6218
- const [planFileContent, unresolvedQuestions, depsSatisfied] = await Promise.all([
6218
+ const [planFileContent, unresolvedQuestions2, depsSatisfied] = await Promise.all([
6219
6219
  needsPlanDigest && planFile ? readFile9(resolve10(assignmentDir, planFile), "utf-8").catch(() => null) : Promise.resolve(null),
6220
6220
  countUnresolvedQuestions(assignmentDir),
6221
6221
  areDependenciesSatisfied(projectDir, frontmatter.dependsOn, terminalStatuses)
@@ -6233,7 +6233,7 @@ async function computeFactsDetailed(input) {
6233
6233
  workspaceSet: frontmatter.workspace.repository !== null && frontmatter.workspace.branch !== null,
6234
6234
  implementationStarted: frontmatter.implementationStarted,
6235
6235
  depsSatisfied,
6236
- unresolvedQuestions,
6236
+ unresolvedQuestions: unresolvedQuestions2,
6237
6237
  blocked: frontmatter.blockedReason !== null,
6238
6238
  parked: frontmatter.parked,
6239
6239
  reviewRequested: frontmatter.reviewRequested,
@@ -7494,8 +7494,8 @@ async function migrateFromMarkdown(projectsDir) {
7494
7494
  return allSessions.length;
7495
7495
  }
7496
7496
  async function parseMarkdownSessionsIndex(filePath, projectSlug) {
7497
- const { readFile: readFile31 } = await import("fs/promises");
7498
- const raw2 = await readFile31(filePath, "utf-8");
7497
+ const { readFile: readFile32 } = await import("fs/promises");
7498
+ const raw2 = await readFile32(filePath, "utf-8");
7499
7499
  const sessions = [];
7500
7500
  const lines = raw2.split("\n");
7501
7501
  let inTable = false;
@@ -7954,8 +7954,8 @@ function scanKey(serversDir2, projectsDir, assignmentsDir2) {
7954
7954
  return `${serversDir2}\0${projectsDir}\0${assignmentsDir2 ?? ""}`;
7955
7955
  }
7956
7956
  function delay(ms) {
7957
- return new Promise((resolve46) => {
7958
- const timer3 = setTimeout(resolve46, ms);
7957
+ return new Promise((resolve47) => {
7958
+ const timer3 = setTimeout(resolve47, ms);
7959
7959
  if (typeof timer3.unref === "function") {
7960
7960
  timer3.unref();
7961
7961
  }
@@ -11662,8 +11662,8 @@ var init_renderers = __esm({
11662
11662
  });
11663
11663
 
11664
11664
  // src/targets/user-descriptors.ts
11665
- import { resolve as resolve42 } from "path";
11666
- import { readFile as readFile29, readdir as readdir18 } from "fs/promises";
11665
+ import { resolve as resolve43 } from "path";
11666
+ import { readFile as readFile30, readdir as readdir18 } from "fs/promises";
11667
11667
  var VALID_RENDERER_KEYS;
11668
11668
  var init_user_descriptors = __esm({
11669
11669
  "src/targets/user-descriptors.ts"() {
@@ -11677,20 +11677,20 @@ var init_user_descriptors = __esm({
11677
11677
 
11678
11678
  // src/targets/registry.ts
11679
11679
  import { homedir as homedir7 } from "os";
11680
- import { join as join10, resolve as resolve43 } from "path";
11680
+ import { join as join10, resolve as resolve44 } from "path";
11681
11681
  function home(...segments) {
11682
- return resolve43(homedir7(), ...segments);
11682
+ return resolve44(homedir7(), ...segments);
11683
11683
  }
11684
11684
  function hermesHome() {
11685
11685
  const env = process.env.HERMES_HOME;
11686
- return env && env.length > 0 ? resolve43(env) : home(".hermes");
11686
+ return env && env.length > 0 ? resolve44(env) : home(".hermes");
11687
11687
  }
11688
11688
  function hermesSkillsDir() {
11689
- return resolve43(hermesHome(), "skills");
11689
+ return resolve44(hermesHome(), "skills");
11690
11690
  }
11691
11691
  function codexHome() {
11692
11692
  const env = process.env.CODEX_HOME;
11693
- return env && env.length > 0 ? resolve43(env) : home(".codex");
11693
+ return env && env.length > 0 ? resolve44(env) : home(".codex");
11694
11694
  }
11695
11695
  function toDiscovered(meta) {
11696
11696
  if (!meta) return null;
@@ -11751,7 +11751,7 @@ var init_registry = __esm({
11751
11751
  skillsShAgentId: "codex",
11752
11752
  nativePlugin: "codex",
11753
11753
  detect: detectDir(codexHome()),
11754
- skillsDir: { global: resolve43(codexHome(), "skills") },
11754
+ skillsDir: { global: resolve44(codexHome(), "skills") },
11755
11755
  instructions: { files: [{ path: "AGENTS.md", renderer: "codexAgents" }] },
11756
11756
  sessions: codexSessions
11757
11757
  },
@@ -11819,7 +11819,7 @@ var init_registry = __esm({
11819
11819
  tier3: {
11820
11820
  kind: "hermes-plugin",
11821
11821
  source: "platforms/hermes/plugins/syntaur",
11822
- installDir: () => resolve43(hermesHome(), "plugins", "syntaur"),
11822
+ installDir: () => resolve44(hermesHome(), "plugins", "syntaur"),
11823
11823
  entry: "plugin.yaml"
11824
11824
  }
11825
11825
  }
@@ -11838,8 +11838,8 @@ __export(scanner_exports2, {
11838
11838
  import { execFile as execFile3, execFileSync as execFileSync4 } from "child_process";
11839
11839
  import { promisify as promisify3 } from "util";
11840
11840
  import { statSync as statSync4 } from "fs";
11841
- import { readFile as readFile30 } from "fs/promises";
11842
- import { resolve as resolve44 } from "path";
11841
+ import { readFile as readFile31 } from "fs/promises";
11842
+ import { resolve as resolve45 } from "path";
11843
11843
  function emptySummary() {
11844
11844
  return { discovered: 0, inserted: 0, revived: 0, swept: 0, skipped: 0, changed: false };
11845
11845
  }
@@ -11895,10 +11895,10 @@ async function defaultOpenFiles(files) {
11895
11895
  async function readContextLink(cwd, cache3) {
11896
11896
  if (cache3.has(cwd)) return cache3.get(cwd);
11897
11897
  let link = null;
11898
- const path = resolve44(cwd, ".syntaur", "context.json");
11898
+ const path = resolve45(cwd, ".syntaur", "context.json");
11899
11899
  if (await fileExists(path)) {
11900
11900
  try {
11901
- const parsed = JSON.parse(await readFile30(path, "utf-8"));
11901
+ const parsed = JSON.parse(await readFile31(path, "utf-8"));
11902
11902
  link = {
11903
11903
  projectSlug: typeof parsed.projectSlug === "string" ? parsed.projectSlug : null,
11904
11904
  assignmentSlug: typeof parsed.assignmentSlug === "string" ? parsed.assignmentSlug : null
@@ -12063,7 +12063,7 @@ init_assignment_resolver();
12063
12063
  init_agent_sessions();
12064
12064
  import express from "express";
12065
12065
  import { createServer } from "http";
12066
- import { resolve as resolve45 } from "path";
12066
+ import { resolve as resolve46 } from "path";
12067
12067
  import { writeFile as writeFile8, unlink as unlink9 } from "fs/promises";
12068
12068
  import { WebSocketServer, WebSocket } from "ws";
12069
12069
 
@@ -17857,7 +17857,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
17857
17857
  `Spawn failed: ${msg}. Verify the terminal is installed and on PATH.`
17858
17858
  );
17859
17859
  }
17860
- await new Promise((resolve46, reject) => {
17860
+ await new Promise((resolve47, reject) => {
17861
17861
  let settled = false;
17862
17862
  let stderr = "";
17863
17863
  const finishOk = () => {
@@ -17867,7 +17867,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
17867
17867
  child.unref();
17868
17868
  } catch {
17869
17869
  }
17870
- resolve46();
17870
+ resolve47();
17871
17871
  };
17872
17872
  const finishErr = (remediation) => {
17873
17873
  if (settled) return;
@@ -20934,15 +20934,355 @@ function parseFilters(query) {
20934
20934
  return out;
20935
20935
  }
20936
20936
 
20937
+ // src/dashboard/api-inbox.ts
20938
+ import { Router as Router16 } from "express";
20939
+
20940
+ // src/inbox/index.ts
20941
+ init_fs();
20942
+ init_assignment_walk();
20943
+ init_parser();
20944
+ init_facts();
20945
+ init_state_machine();
20946
+ import { resolve as resolve36 } from "path";
20947
+ import { readFile as readFile25 } from "fs/promises";
20948
+
20949
+ // src/inbox/types.ts
20950
+ var INBOX_CATEGORIES = [
20951
+ "review",
20952
+ "blocked",
20953
+ "question",
20954
+ "plan-approval"
20955
+ ];
20956
+
20957
+ // src/inbox/index.ts
20958
+ function isReview(a) {
20959
+ return a.status === "review";
20960
+ }
20961
+ function isBlocked(a) {
20962
+ return a.status === "blocked";
20963
+ }
20964
+ function isUnresolvedQuestion(c) {
20965
+ return c.type === "question" && c.resolved !== true;
20966
+ }
20967
+ function unresolvedQuestions(comments) {
20968
+ return comments.filter(isUnresolvedQuestion);
20969
+ }
20970
+ async function isPlanAwaitingApproval(a, assignmentDir) {
20971
+ if (a.status !== "ready_for_planning") return false;
20972
+ const latest = await latestPlanFile(assignmentDir);
20973
+ if (latest === null) return false;
20974
+ const approved = await isPlanApproved(assignmentDir, { planApproval: a.planApproval });
20975
+ return !approved;
20976
+ }
20977
+ function canonicalRfc3339(ms) {
20978
+ return new Date(ms).toISOString().replace(/\.\d{3}Z$/, "Z");
20979
+ }
20980
+ function validTimestamp(value) {
20981
+ if (typeof value !== "string") return null;
20982
+ const t = value.trim();
20983
+ if (t.length === 0) return null;
20984
+ const ms = Date.parse(t);
20985
+ return Number.isNaN(ms) ? null : canonicalRfc3339(ms);
20986
+ }
20987
+ function latestStatusHistoryAt(a) {
20988
+ let best = null;
20989
+ for (const e of a.statusHistory) {
20990
+ const at = validTimestamp(e.at);
20991
+ if (at === null) continue;
20992
+ const ms = Date.parse(at);
20993
+ if (best === null || ms >= best.ms) best = { at, ms };
20994
+ }
20995
+ return best?.at ?? null;
20996
+ }
20997
+ function latestStatusHistoryAtWhere(a, pred) {
20998
+ let best = null;
20999
+ for (const e of a.statusHistory) {
21000
+ if (!pred(e)) continue;
21001
+ const at = validTimestamp(e.at);
21002
+ if (at === null) continue;
21003
+ const ms = Date.parse(at);
21004
+ if (best === null || ms >= best.ms) best = { at, ms };
21005
+ }
21006
+ return best?.at ?? null;
21007
+ }
21008
+ function resolveSince(category, a, now, comment) {
21009
+ let primary = null;
21010
+ switch (category) {
21011
+ case "review":
21012
+ primary = latestStatusHistoryAtWhere(a, (e) => e.to === "review");
21013
+ break;
21014
+ case "blocked":
21015
+ primary = latestStatusHistoryAtWhere(a, (e) => e.dispositionTo === "blocked");
21016
+ break;
21017
+ case "question":
21018
+ primary = validTimestamp(comment?.timestamp);
21019
+ break;
21020
+ case "plan-approval":
21021
+ primary = latestStatusHistoryAt(a);
21022
+ break;
21023
+ }
21024
+ return primary ?? latestStatusHistoryAt(a) ?? validTimestamp(a.updated) ?? validTimestamp(a.created) ?? canonicalRfc3339(now);
21025
+ }
21026
+ function computeAgeMs(since, now) {
21027
+ const ms = Date.parse(since);
21028
+ if (Number.isNaN(ms)) return 0;
21029
+ return Math.max(0, now - ms);
21030
+ }
21031
+ var KNOWN_TRANSITION_CLI_VERBS = /* @__PURE__ */ new Set([
21032
+ "start",
21033
+ "complete",
21034
+ "fail",
21035
+ "reopen",
21036
+ "block",
21037
+ "unblock",
21038
+ "review"
21039
+ ]);
21040
+ function deriveReviewVerbs(config) {
21041
+ const candidates = /* @__PURE__ */ new Set();
21042
+ for (const t of config.transitions) {
21043
+ if (t.from === "review") candidates.add(t.command);
21044
+ }
21045
+ for (const key of config.transitionTable.keys()) {
21046
+ if (key.startsWith("review:")) candidates.add(key.slice("review:".length));
21047
+ }
21048
+ const blockedParked = config.blockedParkedStatuses ?? /* @__PURE__ */ new Set();
21049
+ const terminalAccept = [];
21050
+ const activeReopen = [];
21051
+ for (const command of candidates) {
21052
+ const target = getTargetStatus("review", command, config.transitionTable);
21053
+ if (target === null) continue;
21054
+ const isTerminal = config.terminalStatuses.has(target);
21055
+ if (isTerminal && command !== "fail" && KNOWN_TRANSITION_CLI_VERBS.has(command)) {
21056
+ terminalAccept.push(command);
21057
+ }
21058
+ if (!isTerminal && !blockedParked.has(target) && (command === "start" || command === "reopen")) {
21059
+ activeReopen.push(command);
21060
+ }
21061
+ }
21062
+ const accept = terminalAccept.find((c) => c === "complete") ?? terminalAccept[0] ?? null;
21063
+ const reopen = activeReopen.find((c) => c === "start") ?? activeReopen.find((c) => c === "reopen") ?? null;
21064
+ return { accept, reopen };
21065
+ }
21066
+ function targetAndProject(item) {
21067
+ if (item.project === null) {
21068
+ return { target: item.assignmentId, projectFlag: "" };
21069
+ }
21070
+ return { target: item.assignmentSlug, projectFlag: ` --project ${item.project}` };
21071
+ }
21072
+ function buildAction(category, item, ctx) {
21073
+ const { target, projectFlag } = targetAndProject(item);
21074
+ switch (category) {
21075
+ case "review":
21076
+ if (ctx.acceptCommand) {
21077
+ return {
21078
+ verb: "Accept",
21079
+ command: `syntaur ${ctx.acceptCommand} ${target}${projectFlag}`
21080
+ };
21081
+ }
21082
+ if (ctx.reopenCommand) {
21083
+ return {
21084
+ verb: "Reopen",
21085
+ command: `syntaur ${ctx.reopenCommand} ${target}${projectFlag}`
21086
+ };
21087
+ }
21088
+ return {
21089
+ verb: "Review",
21090
+ command: `syntaur timeline ${target}${projectFlag}`
21091
+ };
21092
+ case "blocked":
21093
+ return {
21094
+ verb: "Unblock",
21095
+ command: `syntaur unblock ${target}${projectFlag}`
21096
+ };
21097
+ case "question":
21098
+ return {
21099
+ verb: "Answer",
21100
+ command: `syntaur comment ${target} "<answer>" --reply-to ${ctx.commentId ?? ""}${projectFlag}`
21101
+ };
21102
+ case "plan-approval":
21103
+ return {
21104
+ verb: "Approve plan",
21105
+ command: `syntaur plan approve ${target}${projectFlag}`
21106
+ };
21107
+ }
21108
+ }
21109
+ function orderByUrgency(items) {
21110
+ return [...items].sort((x, y) => y.ageMs - x.ageMs);
21111
+ }
21112
+ function summarizeQuestion(c) {
21113
+ const body = c.body.replace(/\s+/g, " ").trim();
21114
+ const clipped = body.length > 140 ? `${body.slice(0, 137)}...` : body;
21115
+ return clipped.length > 0 ? clipped : "(empty question)";
21116
+ }
21117
+ async function computeInbox(opts) {
21118
+ const now = opts.now ?? Date.now();
21119
+ const typeFilter = opts.types && opts.types.length > 0 ? new Set(opts.types) : null;
21120
+ const reviewVerbs = deriveReviewVerbs(opts.statusConfig);
21121
+ const walk = await listAssignmentsByProject(opts.projectsDir, opts.assignmentsDir);
21122
+ const matched = [];
21123
+ for (const entry of walk.withAssignmentMd) {
21124
+ if (opts.project !== void 0 && entry.projectSlug !== opts.project) continue;
21125
+ let parsed;
21126
+ try {
21127
+ const content = await readFile25(resolve36(entry.assignmentDir, "assignment.md"), "utf-8");
21128
+ parsed = parseAssignmentFull(content);
21129
+ } catch {
21130
+ continue;
21131
+ }
21132
+ if (parsed.archived) continue;
21133
+ if (parsed.disposition === "parked" || parsed.disposition === "terminal") continue;
21134
+ if (opts.statusConfig.terminalStatuses.has(parsed.status)) continue;
21135
+ const project = entry.projectSlug;
21136
+ const assignmentSlug = entry.assignmentSlug;
21137
+ const assignmentId = parsed.id;
21138
+ const title = parsed.title;
21139
+ const baseItem = { project, assignmentSlug, assignmentId };
21140
+ if ((!typeFilter || typeFilter.has("review")) && isReview(parsed)) {
21141
+ const since = resolveSince("review", parsed, now);
21142
+ matched.push({
21143
+ ...baseItem,
21144
+ title,
21145
+ category: "review",
21146
+ since,
21147
+ ageMs: computeAgeMs(since, now),
21148
+ summary: parsed.reviewRequested ? "Review requested \u2014 awaiting accept or reopen." : "Awaiting review \u2014 accept or reopen.",
21149
+ acceptCommand: reviewVerbs.accept,
21150
+ reopenCommand: reviewVerbs.reopen,
21151
+ action: buildAction("review", baseItem, {
21152
+ acceptCommand: reviewVerbs.accept,
21153
+ reopenCommand: reviewVerbs.reopen
21154
+ })
21155
+ });
21156
+ }
21157
+ if ((!typeFilter || typeFilter.has("blocked")) && isBlocked(parsed)) {
21158
+ const since = resolveSince("blocked", parsed, now);
21159
+ matched.push({
21160
+ ...baseItem,
21161
+ title,
21162
+ category: "blocked",
21163
+ since,
21164
+ ageMs: computeAgeMs(since, now),
21165
+ summary: parsed.blockedReason ? `Blocked: ${parsed.blockedReason}` : "Blocked \u2014 awaiting unblock.",
21166
+ action: buildAction("blocked", baseItem, {})
21167
+ });
21168
+ }
21169
+ if (!typeFilter || typeFilter.has("question")) {
21170
+ const commentsPath = resolve36(entry.assignmentDir, "comments.md");
21171
+ if (await fileExists(commentsPath)) {
21172
+ try {
21173
+ const content = await readFile25(commentsPath, "utf-8");
21174
+ const parsedComments = parseComments(content);
21175
+ for (const c of unresolvedQuestions(parsedComments.entries)) {
21176
+ const since = resolveSince("question", parsed, now, c);
21177
+ matched.push({
21178
+ ...baseItem,
21179
+ title,
21180
+ category: "question",
21181
+ since,
21182
+ ageMs: computeAgeMs(since, now),
21183
+ summary: summarizeQuestion(c),
21184
+ commentId: c.id,
21185
+ action: buildAction("question", baseItem, {
21186
+ commentId: c.id
21187
+ })
21188
+ });
21189
+ }
21190
+ } catch {
21191
+ }
21192
+ }
21193
+ }
21194
+ if (!typeFilter || typeFilter.has("plan-approval")) {
21195
+ if (await isPlanAwaitingApproval(parsed, entry.assignmentDir)) {
21196
+ const since = resolveSince("plan-approval", parsed, now);
21197
+ matched.push({
21198
+ ...baseItem,
21199
+ title,
21200
+ category: "plan-approval",
21201
+ since,
21202
+ ageMs: computeAgeMs(since, now),
21203
+ summary: "Plan awaiting approval.",
21204
+ action: buildAction("plan-approval", baseItem, {})
21205
+ });
21206
+ }
21207
+ }
21208
+ }
21209
+ const counts = {
21210
+ review: 0,
21211
+ blocked: 0,
21212
+ question: 0,
21213
+ "plan-approval": 0
21214
+ };
21215
+ for (const item of matched) counts[item.category]++;
21216
+ const total = matched.length;
21217
+ const ordered = [];
21218
+ for (const category of INBOX_CATEGORIES) {
21219
+ ordered.push(...orderByUrgency(matched.filter((i) => i.category === category)));
21220
+ }
21221
+ const items = opts.limit !== void 0 && opts.limit >= 0 ? ordered.slice(0, opts.limit) : ordered;
21222
+ return { items, counts, total };
21223
+ }
21224
+
21225
+ // src/dashboard/api-inbox.ts
21226
+ init_api();
21227
+ init_config2();
21228
+ function createInboxRouter(projectsDir, assignmentsDir2) {
21229
+ const router = Router16();
21230
+ router.get("/inbox", async (req2, res) => {
21231
+ try {
21232
+ const project = typeof req2.query.project === "string" && req2.query.project.length > 0 ? req2.query.project : void 0;
21233
+ let types;
21234
+ if (typeof req2.query.type === "string" && req2.query.type.length > 0) {
21235
+ const raw2 = req2.query.type.split(",").map((t) => t.trim()).filter(Boolean);
21236
+ const unknown = raw2.filter((t) => !INBOX_CATEGORIES.includes(t));
21237
+ if (unknown.length > 0) {
21238
+ res.status(400).json({
21239
+ error: `Unknown inbox type(s): ${unknown.map((u) => JSON.stringify(u)).join(", ")}. Valid types: ${INBOX_CATEGORIES.join(", ")}.`
21240
+ });
21241
+ return;
21242
+ }
21243
+ types = raw2;
21244
+ }
21245
+ let limit;
21246
+ if (typeof req2.query.limit === "string") {
21247
+ const n = Number(req2.query.limit);
21248
+ if (Number.isInteger(n) && n > 0) limit = n;
21249
+ }
21250
+ const resolved = await getStatusConfig();
21251
+ const headline = (resolved.derive ?? DEFAULT_DERIVE_CONFIG).headline;
21252
+ const blockedParkedStatuses = new Set(
21253
+ [headline.blocked, headline.parked].filter(Boolean)
21254
+ );
21255
+ const statusConfig = { ...resolved, blockedParkedStatuses };
21256
+ const result = await computeInbox({
21257
+ projectsDir,
21258
+ assignmentsDir: assignmentsDir2,
21259
+ project,
21260
+ types,
21261
+ limit,
21262
+ statusConfig
21263
+ });
21264
+ res.json(result);
21265
+ } catch (error) {
21266
+ console.warn("[inbox] failed to compute inbox:", error);
21267
+ res.json({
21268
+ items: [],
21269
+ counts: { review: 0, blocked: 0, question: 0, "plan-approval": 0 },
21270
+ total: 0
21271
+ });
21272
+ }
21273
+ });
21274
+ return router;
21275
+ }
21276
+
20937
21277
  // src/dashboard/api-playbooks.ts
20938
21278
  init_api();
20939
21279
  init_parser();
20940
21280
  init_slug();
20941
21281
  init_timestamp();
20942
21282
  init_fs();
20943
- import { Router as Router16 } from "express";
20944
- import { resolve as resolve36 } from "path";
20945
- import { readFile as readFile25 } from "fs/promises";
21283
+ import { Router as Router17 } from "express";
21284
+ import { resolve as resolve37 } from "path";
21285
+ import { readFile as readFile26 } from "fs/promises";
20946
21286
  init_playbooks();
20947
21287
  function statusForPlaybookError(code) {
20948
21288
  switch (code) {
@@ -20957,7 +21297,7 @@ function statusForPlaybookError(code) {
20957
21297
  }
20958
21298
  }
20959
21299
  function createPlaybooksRouter(playbooksDir2) {
20960
- const router = Router16();
21300
+ const router = Router17();
20961
21301
  router.get("/", async (_req, res) => {
20962
21302
  try {
20963
21303
  const playbooks = await listPlaybooks(playbooksDir2);
@@ -21024,8 +21364,8 @@ function createPlaybooksRouter(playbooksDir2) {
21024
21364
  res.status(404).json({ error: `Playbook "${req2.params.slug}" not found` });
21025
21365
  return;
21026
21366
  }
21027
- const filePath = resolve36(playbooksDir2, resolved.filename);
21028
- const content = await readFile25(filePath, "utf-8");
21367
+ const filePath = resolve37(playbooksDir2, resolved.filename);
21368
+ const content = await readFile26(filePath, "utf-8");
21029
21369
  res.json({
21030
21370
  documentType: "playbook",
21031
21371
  title: `Edit Playbook: ${resolved.slug}`,
@@ -21050,7 +21390,7 @@ function createPlaybooksRouter(playbooksDir2) {
21050
21390
  return;
21051
21391
  }
21052
21392
  await ensureDir(playbooksDir2);
21053
- const filePath = resolve36(playbooksDir2, `${slug}.md`);
21393
+ const filePath = resolve37(playbooksDir2, `${slug}.md`);
21054
21394
  if (await fileExists(filePath)) {
21055
21395
  res.status(409).json({ error: `Playbook "${slug}" already exists` });
21056
21396
  return;
@@ -21074,7 +21414,7 @@ function createPlaybooksRouter(playbooksDir2) {
21074
21414
  res.status(404).json({ error: `Playbook "${req2.params.slug}" not found` });
21075
21415
  return;
21076
21416
  }
21077
- const filePath = resolve36(playbooksDir2, resolved.filename);
21417
+ const filePath = resolve37(playbooksDir2, resolved.filename);
21078
21418
  await writeFileForce(filePath, content);
21079
21419
  await rebuildPlaybookManifest(playbooksDir2);
21080
21420
  res.json({ slug: resolved.slug, path: filePath });
@@ -21121,7 +21461,7 @@ init_fs_migration();
21121
21461
  init_parser2();
21122
21462
  init_fs();
21123
21463
  init_paths();
21124
- import { Router as Router18 } from "express";
21464
+ import { Router as Router19 } from "express";
21125
21465
  import { readdir as readdir16 } from "fs/promises";
21126
21466
  import { resolve as resolvePath, dirname as dirname10 } from "path";
21127
21467
  import { rename as rename6, mkdir as mkdir5 } from "fs/promises";
@@ -21137,7 +21477,7 @@ init_uuid();
21137
21477
  init_paths();
21138
21478
  init_fs();
21139
21479
  init_config2();
21140
- import { resolve as resolve37 } from "path";
21480
+ import { resolve as resolve38 } from "path";
21141
21481
  async function createAssignmentCommand(title, options) {
21142
21482
  if (!title.trim()) {
21143
21483
  throw new Error("Assignment title cannot be empty.");
@@ -21212,14 +21552,14 @@ async function createAssignmentCommand(title, options) {
21212
21552
  if (options.oneOff) {
21213
21553
  const standaloneRoot = assignmentsDir();
21214
21554
  folderName = id;
21215
- assignmentDir = resolve37(standaloneRoot, folderName);
21555
+ assignmentDir = resolve38(standaloneRoot, folderName);
21216
21556
  projectSlug = null;
21217
21557
  await ensureDir(standaloneRoot);
21218
21558
  } else {
21219
21559
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
21220
21560
  projectSlug = options.project;
21221
- const projectDir = resolve37(baseDir, projectSlug);
21222
- const projectMdPath = resolve37(projectDir, "project.md");
21561
+ const projectDir = resolve38(baseDir, projectSlug);
21562
+ const projectMdPath = resolve38(projectDir, "project.md");
21223
21563
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
21224
21564
  throw new Error(
21225
21565
  `Project "${projectSlug}" not found at ${projectDir}.
@@ -21227,9 +21567,9 @@ Run 'syntaur create-project' first or use --one-off.`
21227
21567
  );
21228
21568
  }
21229
21569
  if (dependsOn.length > 0) {
21230
- const depDirBase = resolve37(projectDir, "assignments");
21570
+ const depDirBase = resolve38(projectDir, "assignments");
21231
21571
  for (const dep of dependsOn) {
21232
- const depDir = resolve37(depDirBase, dep);
21572
+ const depDir = resolve38(depDirBase, dep);
21233
21573
  if (!await fileExists(depDir)) {
21234
21574
  console.warn(
21235
21575
  `Warning: dependency "${dep}" does not exist in project "${projectSlug}" yet.`
@@ -21238,7 +21578,7 @@ Run 'syntaur create-project' first or use --one-off.`
21238
21578
  }
21239
21579
  }
21240
21580
  folderName = assignmentSlug;
21241
- assignmentDir = resolve37(projectDir, "assignments", folderName);
21581
+ assignmentDir = resolve38(projectDir, "assignments", folderName);
21242
21582
  }
21243
21583
  if (await fileExists(assignmentDir)) {
21244
21584
  throw new Error(
@@ -21250,7 +21590,7 @@ Use --slug to specify a different slug.`
21250
21590
  const companionAssignmentRef = projectSlug === null ? id : assignmentSlug;
21251
21591
  const files = [
21252
21592
  [
21253
- resolve37(assignmentDir, "assignment.md"),
21593
+ resolve38(assignmentDir, "assignment.md"),
21254
21594
  renderAssignment({
21255
21595
  id,
21256
21596
  slug: assignmentSlug,
@@ -21268,35 +21608,35 @@ Use --slug to specify a different slug.`
21268
21608
  })
21269
21609
  ],
21270
21610
  [
21271
- resolve37(assignmentDir, "scratchpad.md"),
21611
+ resolve38(assignmentDir, "scratchpad.md"),
21272
21612
  renderScratchpad({
21273
21613
  assignmentSlug: companionAssignmentRef,
21274
21614
  timestamp
21275
21615
  })
21276
21616
  ],
21277
21617
  [
21278
- resolve37(assignmentDir, "handoff.md"),
21618
+ resolve38(assignmentDir, "handoff.md"),
21279
21619
  renderHandoff({
21280
21620
  assignmentSlug: companionAssignmentRef,
21281
21621
  timestamp
21282
21622
  })
21283
21623
  ],
21284
21624
  [
21285
- resolve37(assignmentDir, "decision-record.md"),
21625
+ resolve38(assignmentDir, "decision-record.md"),
21286
21626
  renderDecisionRecord({
21287
21627
  assignmentSlug: companionAssignmentRef,
21288
21628
  timestamp
21289
21629
  })
21290
21630
  ],
21291
21631
  [
21292
- resolve37(assignmentDir, "progress.md"),
21632
+ resolve38(assignmentDir, "progress.md"),
21293
21633
  renderProgress({
21294
21634
  assignment: companionAssignmentRef,
21295
21635
  timestamp
21296
21636
  })
21297
21637
  ],
21298
21638
  [
21299
- resolve37(assignmentDir, "comments.md"),
21639
+ resolve38(assignmentDir, "comments.md"),
21300
21640
  renderComments({
21301
21641
  assignment: companionAssignmentRef,
21302
21642
  timestamp
@@ -21470,7 +21810,7 @@ import { raw } from "express";
21470
21810
 
21471
21811
  // src/todos/attachments.ts
21472
21812
  import { mkdir as mkdir4, readdir as readdir15, stat as stat5, rename as rename5, rm as rm4, unlink as unlink7, writeFile as writeFile6, cp } from "fs/promises";
21473
- import { resolve as resolve38, basename as basename5, dirname as dirname9, extname } from "path";
21813
+ import { resolve as resolve39, basename as basename5, dirname as dirname9, extname } from "path";
21474
21814
 
21475
21815
  // src/utils/proof-artifact-id.ts
21476
21816
  import { randomBytes as randomBytes2 } from "crypto";
@@ -21557,12 +21897,12 @@ function sanitizeAttachmentName(name) {
21557
21897
  return n;
21558
21898
  }
21559
21899
  function attachmentsRootDir(todosDir2) {
21560
- return resolve38(todosDir2, "attachments");
21900
+ return resolve39(todosDir2, "attachments");
21561
21901
  }
21562
21902
  function attachmentDirFor(todosDir2, scopeId, todoId) {
21563
21903
  assertScope(scopeId);
21564
21904
  assertTodoId(todoId);
21565
- return resolve38(attachmentsRootDir(todosDir2), scopeId, todoId);
21905
+ return resolve39(attachmentsRootDir(todosDir2), scopeId, todoId);
21566
21906
  }
21567
21907
  async function dirExists(p) {
21568
21908
  try {
@@ -21576,7 +21916,7 @@ async function writeAttachment(todosDir2, scopeId, todoId, originalName, bytes)
21576
21916
  await mkdir4(dir, { recursive: true });
21577
21917
  const id = generateArtifactId();
21578
21918
  const filename = sanitizeAttachmentName(originalName);
21579
- await writeFile6(resolve38(dir, `${id}__${filename}`), bytes);
21919
+ await writeFile6(resolve39(dir, `${id}__${filename}`), bytes);
21580
21920
  return {
21581
21921
  id,
21582
21922
  filename,
@@ -21601,7 +21941,7 @@ async function listAttachments(todosDir2, scopeId, todoId) {
21601
21941
  if (!ATTACHMENT_ID_RE.test(id)) continue;
21602
21942
  const filename = stored.slice(sep2 + 2);
21603
21943
  try {
21604
- const st = await stat5(resolve38(dir, stored));
21944
+ const st = await stat5(resolve39(dir, stored));
21605
21945
  if (!st.isFile()) continue;
21606
21946
  out.push({ id, filename, mime: mimeForName(filename), size: st.size, createdAt: st.mtime.toISOString() });
21607
21947
  } catch {
@@ -21612,7 +21952,7 @@ async function listAttachments(todosDir2, scopeId, todoId) {
21612
21952
  }
21613
21953
  async function readScopeAttachments(todosDir2, scopeId) {
21614
21954
  assertScope(scopeId);
21615
- const scopeDir = resolve38(attachmentsRootDir(todosDir2), scopeId);
21955
+ const scopeDir = resolve39(attachmentsRootDir(todosDir2), scopeId);
21616
21956
  let todoIds;
21617
21957
  try {
21618
21958
  todoIds = await readdir15(scopeDir);
@@ -21640,7 +21980,7 @@ async function resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId) {
21640
21980
  const stored = names.find((n) => n.startsWith(prefix));
21641
21981
  if (!stored) return null;
21642
21982
  const filename = stored.slice(prefix.length);
21643
- return { path: resolve38(dir, stored), filename, mime: mimeForName(filename) };
21983
+ return { path: resolve39(dir, stored), filename, mime: mimeForName(filename) };
21644
21984
  }
21645
21985
  async function deleteAttachment(todosDir2, scopeId, todoId, attachmentId) {
21646
21986
  const resolved = await resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId);
@@ -21802,7 +22142,7 @@ function touchItem3(item) {
21802
22142
  item.updatedAt = now;
21803
22143
  }
21804
22144
  function createTodosRouter(todosDir2, broadcast, projectsDir) {
21805
- const router = Router18();
22145
+ const router = Router19();
21806
22146
  installRecordsInvalidation(router);
21807
22147
  function broadcastUpdate() {
21808
22148
  broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -22052,8 +22392,8 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
22052
22392
  router.post("/:workspace/archive", async (req2, res) => {
22053
22393
  try {
22054
22394
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
22055
- const { resolve: resolve46 } = await import("path");
22056
- const { readFile: readFile31 } = await import("fs/promises");
22395
+ const { resolve: resolve47 } = await import("path");
22396
+ const { readFile: readFile32 } = await import("fs/promises");
22057
22397
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
22058
22398
  const workspace = getWorkspaceParam(req2.params.workspace);
22059
22399
  const outcome = await wsLock(workspace, async () => {
@@ -22069,10 +22409,10 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
22069
22409
  (e) => e.itemIds.every((id) => completedIds.has(id))
22070
22410
  );
22071
22411
  const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
22072
- await ensureDir(resolve46(todosDir2, "archive"));
22412
+ await ensureDir(resolve47(todosDir2, "archive"));
22073
22413
  let archContent = "";
22074
22414
  if (await fileExists(archFile)) {
22075
- archContent = await readFile31(archFile, "utf-8");
22415
+ archContent = await readFile32(archFile, "utf-8");
22076
22416
  archContent = archContent.trimEnd() + "\n\n";
22077
22417
  } else {
22078
22418
  archContent = `---
@@ -22364,7 +22704,7 @@ workspace: ${workspace}
22364
22704
  const { readConfig: readConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
22365
22705
  const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
22366
22706
  const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
22367
- const { readFile: readFile31 } = await import("fs/promises");
22707
+ const { readFile: readFile32 } = await import("fs/promises");
22368
22708
  const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
22369
22709
  const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
22370
22710
  let assignmentRef;
@@ -22385,7 +22725,7 @@ workspace: ${workspace}
22385
22725
  }
22386
22726
  const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
22387
22727
  if (!await fileExists2(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
22388
- let content = await readFile31(assignmentMdPath, "utf-8");
22728
+ let content = await readFile32(assignmentMdPath, "utf-8");
22389
22729
  content = appendTodosToAssignmentBody2(
22390
22730
  content,
22391
22731
  items.map((it) => ({
@@ -22579,9 +22919,9 @@ init_parser2();
22579
22919
  init_fs();
22580
22920
  init_paths();
22581
22921
  init_slug();
22582
- import { Router as Router19 } from "express";
22583
- import { mkdir as mkdir6, readFile as readFile26, rename as rename7 } from "fs/promises";
22584
- import { resolve as resolve39, dirname as dirname11 } from "path";
22922
+ import { Router as Router20 } from "express";
22923
+ import { mkdir as mkdir6, readFile as readFile27, rename as rename7 } from "fs/promises";
22924
+ import { resolve as resolve40, dirname as dirname11 } from "path";
22585
22925
  init_api();
22586
22926
  var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
22587
22927
  function touchItem4(item) {
@@ -22597,7 +22937,7 @@ function params(req2) {
22597
22937
  return req2.params;
22598
22938
  }
22599
22939
  async function projectExists(projectsDir, slug) {
22600
- return fileExists(resolve39(projectsDir, slug, "project.md"));
22940
+ return fileExists(resolve40(projectsDir, slug, "project.md"));
22601
22941
  }
22602
22942
  async function ensureProjectTodosDir(projectsDir, slug) {
22603
22943
  const todosDir2 = projectTodosDir(projectsDir, slug);
@@ -22614,7 +22954,7 @@ async function ensureProjectTodosDir(projectsDir, slug) {
22614
22954
  throw err;
22615
22955
  }
22616
22956
  try {
22617
- await mkdir6(resolve39(todosDir2, "archive"), { recursive: false });
22957
+ await mkdir6(resolve40(todosDir2, "archive"), { recursive: false });
22618
22958
  } catch (err) {
22619
22959
  const code = err.code;
22620
22960
  if (code === "EEXIST") return;
@@ -22630,7 +22970,7 @@ function notFound(res, slug) {
22630
22970
  res.status(404).json({ error: `Project "${slug}" not found` });
22631
22971
  }
22632
22972
  function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
22633
- const router = Router19({ mergeParams: true });
22973
+ const router = Router20({ mergeParams: true });
22634
22974
  installRecordsInvalidation(router);
22635
22975
  function broadcastUpdate(projectSlug) {
22636
22976
  broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -22818,7 +23158,7 @@ function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
22818
23158
  const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
22819
23159
  let archContent = "";
22820
23160
  if (await fileExists(archFile)) {
22821
- archContent = await readFile26(archFile, "utf-8");
23161
+ archContent = await readFile27(archFile, "utf-8");
22822
23162
  archContent = archContent.trimEnd() + "\n\n";
22823
23163
  } else {
22824
23164
  archContent = `---
@@ -23280,17 +23620,17 @@ workspace: ${slug}
23280
23620
  if (tg.includes("/")) {
23281
23621
  const parts = tg.split("/");
23282
23622
  if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
23283
- assignmentDir = resolve39(projectsDir, parts[0], "assignments", parts[1]);
23623
+ assignmentDir = resolve40(projectsDir, parts[0], "assignments", parts[1]);
23284
23624
  assignmentRef = `${parts[0]}/${parts[1]}`;
23285
23625
  } else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
23286
- assignmentDir = resolve39(assignmentsDirFn(), tg);
23626
+ assignmentDir = resolve40(assignmentsDirFn(), tg);
23287
23627
  assignmentRef = tg;
23288
23628
  } else {
23289
23629
  return { error: `Invalid target.assignment "${tg}"` };
23290
23630
  }
23291
- const assignmentMdPath = resolve39(assignmentDir, "assignment.md");
23631
+ const assignmentMdPath = resolve40(assignmentDir, "assignment.md");
23292
23632
  if (!await fileExists(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
23293
- let content = await readFile26(assignmentMdPath, "utf-8");
23633
+ let content = await readFile27(assignmentMdPath, "utf-8");
23294
23634
  content = appendTodosToAssignmentBody2(
23295
23635
  content,
23296
23636
  items.map((it) => ({
@@ -23492,7 +23832,7 @@ workspace: ${slug}
23492
23832
  }
23493
23833
 
23494
23834
  // src/dashboard/api-bundles.ts
23495
- import { Router as Router20 } from "express";
23835
+ import { Router as Router21 } from "express";
23496
23836
  import { readdir as readdir17 } from "fs/promises";
23497
23837
 
23498
23838
  // src/todos/bundle-parser.ts
@@ -23501,7 +23841,7 @@ init_fs();
23501
23841
  init_paths();
23502
23842
  init_parser2();
23503
23843
  import { randomBytes as randomBytes3 } from "crypto";
23504
- import { readFile as readFile27 } from "fs/promises";
23844
+ import { readFile as readFile28 } from "fs/promises";
23505
23845
  var BUNDLE_ID_REGEX = /^[a-f0-9]{4}$/;
23506
23846
  var SCOPE_VALUES = /* @__PURE__ */ new Set(["workspace", "project", "global"]);
23507
23847
  var SCOPE_ID_REGEX = /^[a-z0-9_][a-z0-9_-]*$/;
@@ -23568,7 +23908,7 @@ function parseBundles(content) {
23568
23908
  async function readBundles(todosDir2) {
23569
23909
  const path = bundlesPath(todosDir2);
23570
23910
  if (!await fileExists(path)) return [];
23571
- const content = await readFile27(path, "utf-8");
23911
+ const content = await readFile28(path, "utf-8");
23572
23912
  return parseBundles(content).bundles;
23573
23913
  }
23574
23914
 
@@ -23603,7 +23943,7 @@ function annotate(bundle, items) {
23603
23943
  }
23604
23944
  function createBundlesRouter(todosDir2, broadcast) {
23605
23945
  void broadcast;
23606
- const router = Router20();
23946
+ const router = Router21();
23607
23947
  function validateWorkspace(req2, res, next) {
23608
23948
  const workspace = getWorkspaceParam2(req2.params.workspace);
23609
23949
  if (workspace && !WORKSPACE_REGEX3.test(workspace)) {
@@ -23669,8 +24009,8 @@ function createBundlesRouter(todosDir2, broadcast) {
23669
24009
  init_fs();
23670
24010
  init_paths();
23671
24011
  init_slug();
23672
- import { Router as Router21 } from "express";
23673
- import { resolve as resolve40 } from "path";
24012
+ import { Router as Router22 } from "express";
24013
+ import { resolve as resolve41 } from "path";
23674
24014
  init_parser2();
23675
24015
  function deriveStatus2(bundle, items) {
23676
24016
  const members = bundle.todoIds.map((id) => items.find((i) => i.id === id)).filter((i) => i !== void 0);
@@ -23699,7 +24039,7 @@ function notFound2(res, slug) {
23699
24039
  }
23700
24040
  function createProjectBundlesRouter(projectsDir, broadcast) {
23701
24041
  void broadcast;
23702
- const router = Router21({ mergeParams: true });
24042
+ const router = Router22({ mergeParams: true });
23703
24043
  function validateProjectId(req2, res, next) {
23704
24044
  const slug = getProjectIdParam2(req2.params.projectId);
23705
24045
  if (!slug || !isValidSlug(slug)) {
@@ -23712,7 +24052,7 @@ function createProjectBundlesRouter(projectsDir, broadcast) {
23712
24052
  router.get("/", async (req2, res) => {
23713
24053
  try {
23714
24054
  const slug = getProjectIdParam2(req2.params.projectId);
23715
- const projectMd = resolve40(projectsDir, slug, "project.md");
24055
+ const projectMd = resolve41(projectsDir, slug, "project.md");
23716
24056
  if (!await fileExists(projectMd)) {
23717
24057
  notFound2(res, slug);
23718
24058
  return;
@@ -23733,7 +24073,7 @@ function createProjectBundlesRouter(projectsDir, broadcast) {
23733
24073
  init_config2();
23734
24074
  init_api();
23735
24075
  init_scanner();
23736
- import { Router as Router22 } from "express";
24076
+ import { Router as Router23 } from "express";
23737
24077
 
23738
24078
  // src/utils/github-backup.ts
23739
24079
  init_paths();
@@ -23741,8 +24081,8 @@ init_fs();
23741
24081
  init_config2();
23742
24082
  import { execFile as execFile2 } from "child_process";
23743
24083
  import { promisify as promisify2 } from "util";
23744
- import { cp as cp2, mkdtemp, rm as rm5, readFile as readFile28, writeFile as writeFile7, unlink as unlink8, stat as stat6, open as open5, rename as rename8 } from "fs/promises";
23745
- import { resolve as resolve41, join as join9 } from "path";
24084
+ import { cp as cp2, mkdtemp, rm as rm5, readFile as readFile29, writeFile as writeFile7, unlink as unlink8, stat as stat6, open as open5, rename as rename8 } from "fs/promises";
24085
+ import { resolve as resolve42, join as join9 } from "path";
23746
24086
  import { tmpdir } from "os";
23747
24087
  var exec2 = promisify2(execFile2);
23748
24088
  var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
@@ -23782,7 +24122,7 @@ async function resolveCategoryPath(category) {
23782
24122
  case "servers":
23783
24123
  return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
23784
24124
  case "config":
23785
- return { sourcePath: resolve41(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
24125
+ return { sourcePath: resolve42(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
23786
24126
  }
23787
24127
  }
23788
24128
  async function checkGitInstalled() {
@@ -23793,7 +24133,7 @@ async function checkGitInstalled() {
23793
24133
  }
23794
24134
  }
23795
24135
  async function acquireLock2() {
23796
- const lockPath = resolve41(syntaurRoot(), LOCK_FILE_NAME);
24136
+ const lockPath = resolve42(syntaurRoot(), LOCK_FILE_NAME);
23797
24137
  await ensureDir(syntaurRoot());
23798
24138
  try {
23799
24139
  const handle = await open5(lockPath, "wx");
@@ -23802,7 +24142,7 @@ async function acquireLock2() {
23802
24142
  return lockPath;
23803
24143
  } catch (err) {
23804
24144
  if (err.code === "EEXIST") {
23805
- const pid = await readFile28(lockPath, "utf-8").catch(() => "");
24145
+ const pid = await readFile29(lockPath, "utf-8").catch(() => "");
23806
24146
  throw new Error(
23807
24147
  `Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
23808
24148
  );
@@ -23840,7 +24180,7 @@ async function copyRecursive(src, dest) {
23840
24180
  await ensureDir(dest);
23841
24181
  await cp2(src, dest, { recursive: true, force: true });
23842
24182
  } else {
23843
- await ensureDir(resolve41(dest, ".."));
24183
+ await ensureDir(resolve42(dest, ".."));
23844
24184
  await cp2(src, dest, { force: true });
23845
24185
  }
23846
24186
  }
@@ -23849,7 +24189,7 @@ function resolveCategoriesStrict(csv) {
23849
24189
  return parseCategoriesStrict(parts);
23850
24190
  }
23851
24191
  async function readSanitizedConfig(configPath) {
23852
- const content = await readFile28(configPath, "utf-8");
24192
+ const content = await readFile29(configPath, "utf-8");
23853
24193
  return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
23854
24194
  }
23855
24195
  async function backupToGithub(overrides) {
@@ -23888,7 +24228,7 @@ async function backupToGithub(overrides) {
23888
24228
  }
23889
24229
  if (category === "config") {
23890
24230
  const sanitized = await readSanitizedConfig(sourcePath);
23891
- await ensureDir(resolve41(destPath, ".."));
24231
+ await ensureDir(resolve42(destPath, ".."));
23892
24232
  await writeFile7(destPath, sanitized, "utf-8");
23893
24233
  } else {
23894
24234
  await copyRecursive(sourcePath, destPath);
@@ -23942,7 +24282,7 @@ async function backupToGithub(overrides) {
23942
24282
  }
23943
24283
  async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
23944
24284
  if (isFile) {
23945
- await ensureDir(resolve41(localPath, ".."));
24285
+ await ensureDir(resolve42(localPath, ".."));
23946
24286
  await cp2(repoSrcPath, localPath, { force: true });
23947
24287
  return;
23948
24288
  }
@@ -24043,7 +24383,7 @@ async function restoreFromGithub(overrides) {
24043
24383
  }
24044
24384
  async function getBackupStatus() {
24045
24385
  const config = await readConfig();
24046
- const lockPath = resolve41(syntaurRoot(), LOCK_FILE_NAME);
24386
+ const lockPath = resolve42(syntaurRoot(), LOCK_FILE_NAME);
24047
24387
  const locked = await fileExists(lockPath);
24048
24388
  return {
24049
24389
  repo: config.backup?.repo ?? null,
@@ -24056,7 +24396,7 @@ async function getBackupStatus() {
24056
24396
 
24057
24397
  // src/dashboard/api-backup.ts
24058
24398
  function createBackupRouter() {
24059
- const router = Router22();
24399
+ const router = Router23();
24060
24400
  router.get("/", async (_req, res) => {
24061
24401
  try {
24062
24402
  const status = await getBackupStatus();
@@ -24526,7 +24866,7 @@ async function runCcusage(opts = {}) {
24526
24866
  };
24527
24867
  }
24528
24868
  function runOnce(binary, args, env, timeoutMs, maxOutputBytes) {
24529
- return new Promise((resolve46) => {
24869
+ return new Promise((resolve47) => {
24530
24870
  const child = spawn4(binary, args, {
24531
24871
  env: env ?? process.env,
24532
24872
  stdio: ["ignore", "pipe", "pipe"]
@@ -24540,7 +24880,7 @@ function runOnce(binary, args, env, timeoutMs, maxOutputBytes) {
24540
24880
  if (settled) return;
24541
24881
  settled = true;
24542
24882
  clearTimeout(timer3);
24543
- resolve46(result);
24883
+ resolve47(result);
24544
24884
  };
24545
24885
  const timer3 = setTimeout(() => {
24546
24886
  timedOut = true;
@@ -24839,7 +25179,7 @@ function createDashboardServer(options) {
24839
25179
  (async () => {
24840
25180
  try {
24841
25181
  const configResult = await migrateLegacyConfig(
24842
- resolve45(syntaurRoot(), "config.md")
25182
+ resolve46(syntaurRoot(), "config.md")
24843
25183
  );
24844
25184
  const projectResult = await migrateLegacyProjectFiles(projectsDir);
24845
25185
  const summary = summarizeMigration(projectResult, configResult);
@@ -25329,6 +25669,7 @@ function createDashboardServer(options) {
25329
25669
  app.use("/api/schedules", createSchedulesRouter(broadcast));
25330
25670
  app.use("/api/usage", createUsageRouter(projectsDir, assignmentsDir2));
25331
25671
  app.use("/api", createEventsRouter(projectsDir, assignmentsDir2));
25672
+ app.use("/api", createInboxRouter(projectsDir, assignmentsDir2));
25332
25673
  app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2));
25333
25674
  app.use("/api/config/agents", createAgentsRouter());
25334
25675
  app.use(
@@ -25361,14 +25702,14 @@ function createDashboardServer(options) {
25361
25702
  app.use("/api/backup", createBackupRouter());
25362
25703
  if (serveStaticUi && dashboardDistPath) {
25363
25704
  const sendOpts = { dotfiles: "allow" };
25364
- app.use("/assets", express.static(resolve45(dashboardDistPath, "assets"), sendOpts));
25705
+ app.use("/assets", express.static(resolve46(dashboardDistPath, "assets"), sendOpts));
25365
25706
  app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
25366
25707
  app.get("{*path}", async (req2, res) => {
25367
25708
  if (req2.path.startsWith("/api") || req2.path === "/ws" || req2.path.startsWith("/assets")) {
25368
25709
  res.status(404).json({ error: "Not Found" });
25369
25710
  return;
25370
25711
  }
25371
- const indexPath = resolve45(dashboardDistPath, "index.html");
25712
+ const indexPath = resolve46(dashboardDistPath, "index.html");
25372
25713
  if (!await fileExists(indexPath)) {
25373
25714
  res.status(503).send(
25374
25715
  'Dashboard not built. Run "npm run build:dashboard" first.'
@@ -25402,8 +25743,8 @@ function createDashboardServer(options) {
25402
25743
  if (!await migrationGate()) return;
25403
25744
  try {
25404
25745
  const context = await resolveDeriveContext2();
25405
- const projectDir = projectSlug ? resolve45(projectsDir, projectSlug) : null;
25406
- const path = projectDir ? resolve45(projectDir, "assignments", assignmentSlug, "assignment.md") : resolve45(assignmentsDir2, assignmentSlug, "assignment.md");
25746
+ const projectDir = projectSlug ? resolve46(projectsDir, projectSlug) : null;
25747
+ const path = projectDir ? resolve46(projectDir, "assignments", assignmentSlug, "assignment.md") : resolve46(assignmentsDir2, assignmentSlug, "assignment.md");
25407
25748
  if (!await fileExists(path)) return;
25408
25749
  const result = await recomputeAndWrite2(path, {
25409
25750
  cause: "derive",
@@ -25456,8 +25797,8 @@ function createDashboardServer(options) {
25456
25797
  serversDir: serversDir2,
25457
25798
  playbooksDir: playbooksDir2,
25458
25799
  todosDir: todosDir2,
25459
- dbPath: resolve45(syntaurRoot(), "syntaur.db"),
25460
- configPath: resolve45(syntaurRoot(), "config.md"),
25800
+ dbPath: resolve46(syntaurRoot(), "syntaur.db"),
25801
+ configPath: resolve46(syntaurRoot(), "config.md"),
25461
25802
  onMessage: broadcast,
25462
25803
  onAssignmentChanged: (projectSlug, assignmentSlug) => {
25463
25804
  void recomputeOne(projectSlug, assignmentSlug);
@@ -25493,7 +25834,7 @@ function createDashboardServer(options) {
25493
25834
  }
25494
25835
  });
25495
25836
  server.listen(port, () => {
25496
- const portFile = resolve45(syntaurRoot(), "dashboard-port");
25837
+ const portFile = resolve46(syntaurRoot(), "dashboard-port");
25497
25838
  writeFile8(portFile, String(port), "utf-8").catch(() => {
25498
25839
  });
25499
25840
  resolvePromise();
@@ -25513,7 +25854,7 @@ function createDashboardServer(options) {
25513
25854
  client.terminate();
25514
25855
  }
25515
25856
  clients.clear();
25516
- const portFile = resolve45(syntaurRoot(), "dashboard-port");
25857
+ const portFile = resolve46(syntaurRoot(), "dashboard-port");
25517
25858
  await unlink9(portFile).catch(() => {
25518
25859
  });
25519
25860
  server.closeAllConnections?.();