syntaur 0.76.0 → 0.77.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 (68) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dashboard/dist/assets/{_basePickBy-BiwSunXO.js → _basePickBy-CmtR3VlC.js} +1 -1
  3. package/dashboard/dist/assets/{_baseUniq-DsjKPXxP.js → _baseUniq-d5yVqkuO.js} +1 -1
  4. package/dashboard/dist/assets/{arc-DlEs4wsF.js → arc-BTfvlUzk.js} +1 -1
  5. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-DPvCrO57.js → architectureDiagram-2XIMDMQ5-ComKmfQn.js} +1 -1
  6. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-CpNdCY-r.js → blockDiagram-WCTKOSBZ-69xtTOqC.js} +1 -1
  7. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-CC29QJCs.js → c4Diagram-IC4MRINW-B5MsrIyR.js} +1 -1
  8. package/dashboard/dist/assets/channel-B55sx3Pw.js +1 -0
  9. package/dashboard/dist/assets/{chunk-4BX2VUAB-DKyy-k2D.js → chunk-4BX2VUAB-BV-rQx2S.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-55IACEB6-DjobngQk.js → chunk-55IACEB6-BHoZ0_2C.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-FMBD7UC4-BUftm7dV.js → chunk-FMBD7UC4-BZppz6s9.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-JSJVCQXG-Nv7rQv4t.js → chunk-JSJVCQXG-BKJIycjQ.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-KX2RTZJC-Dg6UhNB9.js → chunk-KX2RTZJC-Drv6rL3f.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-NQ4KR5QH-D8neQrJ_.js → chunk-NQ4KR5QH-u1ws00gN.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-QZHKN3VN-Uj9c1naA.js → chunk-QZHKN3VN-zCyNaZcO.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-WL4C6EOR-DfeSsP3z.js → chunk-WL4C6EOR-Dz0hDBUx.js} +1 -1
  17. package/dashboard/dist/assets/classDiagram-VBA2DB6C-Kgg-J05j.js +1 -0
  18. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Kgg-J05j.js +1 -0
  19. package/dashboard/dist/assets/clone-DXxPBQmb.js +1 -0
  20. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CLfZNKcO.js → cose-bilkent-S5V4N54A-CO0Ms-ys.js} +1 -1
  21. package/dashboard/dist/assets/{dagre-KLK3FWXG-DyJLICZx.js → dagre-KLK3FWXG-BDBGlQ5i.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-E7M64L7V-DhQc7Ta3.js → diagram-E7M64L7V-C1jhhA3a.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-IFDJBPK2-C6kFO7hB.js → diagram-IFDJBPK2-DYA2G930.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-P4PSJMXO-B6oCiOXk.js → diagram-P4PSJMXO-iox20fhC.js} +1 -1
  25. package/dashboard/dist/assets/{erDiagram-INFDFZHY-BsMPaSwJ.js → erDiagram-INFDFZHY-CdrG4ZUW.js} +1 -1
  26. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-DLxEqKtZ.js → flowDiagram-PKNHOUZH-CMuagI-6.js} +1 -1
  27. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-D70pvqxG.js → ganttDiagram-A5KZAMGK-C8jgdfRO.js} +1 -1
  28. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-D74VHtlE.js → gitGraphDiagram-K3NZZRJ6-Chu0FgBt.js} +1 -1
  29. package/dashboard/dist/assets/{graph-C8bz_IEO.js → graph-RWP7g4cL.js} +1 -1
  30. package/dashboard/dist/assets/{index-h-X_8om3.js → index-BFpKR1Z0.js} +163 -148
  31. package/dashboard/dist/assets/{index-BetqH_3H.css → index-C6KBRx3q.css} +1 -1
  32. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-DivBd0Pz.js → infoDiagram-LFFYTUFH-bIlmxe7x.js} +1 -1
  33. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-BGihUeOE.js → ishikawaDiagram-PHBUUO56-CvvXAmzr.js} +1 -1
  34. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-C7U4cWHQ.js → journeyDiagram-4ABVD52K-BlSVnlQk.js} +1 -1
  35. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-CFp_hDTm.js → kanban-definition-K7BYSVSG-D6kAus7L.js} +1 -1
  36. package/dashboard/dist/assets/{layout-DMSDPqCs.js → layout-BIoVL4m2.js} +1 -1
  37. package/dashboard/dist/assets/{linear-CCC-meYc.js → linear-B4ySyXON.js} +1 -1
  38. package/dashboard/dist/assets/{mermaid.core-BDa2cc2K.js → mermaid.core-B_8IjgQO.js} +4 -4
  39. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-DLKvONhO.js → mindmap-definition-YRQLILUH-B6RTS8xi.js} +1 -1
  40. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-DAJoGZ93.js → pieDiagram-SKSYHLDU-EntB8Xmg.js} +1 -1
  41. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-Bo7haX1W.js → quadrantDiagram-337W2JSQ-UTEbe4g6.js} +1 -1
  42. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-uEvGz33v.js → requirementDiagram-Z7DCOOCP-qG2oV68i.js} +1 -1
  43. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-cjYoCGCM.js → sankeyDiagram-WA2Y5GQK-C-EzJpn8.js} +1 -1
  44. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-CYXcNklt.js → sequenceDiagram-2WXFIKYE-rCjfcAr5.js} +1 -1
  45. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-B5We7jAw.js → stateDiagram-RAJIS63D-C3hoQmLC.js} +1 -1
  46. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-rHFeeHzp.js +1 -0
  47. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-CEqVmnVQ.js → timeline-definition-YZTLITO2-2gGWKHHW.js} +1 -1
  48. package/dashboard/dist/assets/{treemap-KZPCXAKY-CE1puPeN.js → treemap-KZPCXAKY-BV6TTFvC.js} +1 -1
  49. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-CiiuRUjK.js → vennDiagram-LZ73GAT5-C1aeXQ29.js} +1 -1
  50. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-B0OuxFm9.js → xychartDiagram-JWTSCODW-BDbba6Mg.js} +1 -1
  51. package/dashboard/dist/index.html +2 -2
  52. package/dist/dashboard/server.js +845 -147
  53. package/dist/dashboard/server.js.map +1 -1
  54. package/dist/index.js +1290 -547
  55. package/dist/index.js.map +1 -1
  56. package/dist/launch/index.d.ts +41 -0
  57. package/dist/launch/index.js +200 -21
  58. package/dist/launch/index.js.map +1 -1
  59. package/package.json +1 -1
  60. package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
  61. package/platforms/codex/.codex-plugin/plugin.json +1 -1
  62. package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
  63. package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
  64. package/dashboard/dist/assets/channel-IutbH48W.js +0 -1
  65. package/dashboard/dist/assets/classDiagram-VBA2DB6C-8IywRj9I.js +0 -1
  66. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-8IywRj9I.js +0 -1
  67. package/dashboard/dist/assets/clone-C6saGiCd.js +0 -1
  68. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-CX9-EpBi.js +0 -1
@@ -2547,6 +2547,14 @@ function agentNameArgs(agent) {
2547
2547
  const n = agent.agentName?.trim();
2548
2548
  return n ? ["--agent", n] : [];
2549
2549
  }
2550
+ function resolveRunner(agent) {
2551
+ if (agent.runner) return agent.runner;
2552
+ if (agent.agentName && agent.agentName.trim() !== "") return "claude";
2553
+ const cmd = agent.command ?? "";
2554
+ if (/claude/i.test(cmd) || agent.id === "claude") return "claude";
2555
+ if (/codex/i.test(cmd) || agent.id === "codex") return "codex";
2556
+ return "pi";
2557
+ }
2550
2558
  function stripModelFlags(args) {
2551
2559
  const out = [];
2552
2560
  for (let i = 0; i < args.length; i++) {
@@ -3817,6 +3825,7 @@ __export(config_exports, {
3817
3825
  parseStatusConfig: () => parseStatusConfig,
3818
3826
  parseTerminalConfig: () => parseTerminalConfig,
3819
3827
  readConfig: () => readConfig,
3828
+ serializeAgentDiscoveryConfig: () => serializeAgentDiscoveryConfig,
3820
3829
  serializeSearchConfig: () => serializeSearchConfig,
3821
3830
  serializeStatusConfig: () => serializeStatusConfig,
3822
3831
  toTitleCase: () => toTitleCase,
@@ -3830,6 +3839,7 @@ __export(config_exports, {
3830
3839
  validateDeriveShape: () => validateDeriveShape,
3831
3840
  validateFactDeclarations: () => validateFactDeclarations,
3832
3841
  validateStalenessConfig: () => validateStalenessConfig,
3842
+ writeAgentDiscoveryConfig: () => writeAgentDiscoveryConfig,
3833
3843
  writeAgentsConfig: () => writeAgentsConfig,
3834
3844
  writeHotkeyBindingsConfig: () => writeHotkeyBindingsConfig,
3835
3845
  writeSearchConfig: () => writeSearchConfig,
@@ -3922,6 +3932,50 @@ function validateAgentList(agents) {
3922
3932
  `agent "${agent.id}" sets both agentName and model \u2014 the agent definition's own model is authoritative; remove the profile model`
3923
3933
  );
3924
3934
  }
3935
+ if (agent.runner !== void 0 && !RUNNER_KINDS.includes(agent.runner)) {
3936
+ throw new AgentConfigError(
3937
+ `agent "${agent.id}" has invalid runner "${agent.runner}" \u2014 must be one of ${RUNNER_KINDS.join(", ")}`
3938
+ );
3939
+ }
3940
+ if (agent.sourceKind !== void 0 && !AGENT_SOURCE_KINDS.includes(agent.sourceKind)) {
3941
+ throw new AgentConfigError(
3942
+ `agent "${agent.id}" has invalid sourceKind "${agent.sourceKind}" \u2014 must be one of ${AGENT_SOURCE_KINDS.join(", ")}`
3943
+ );
3944
+ }
3945
+ if (agent.sourcePath !== void 0 && /[\r\n]/.test(agent.sourcePath)) {
3946
+ throw new AgentConfigError(
3947
+ `agent "${agent.id}" has invalid sourcePath \u2014 must be a single line (no newlines)`
3948
+ );
3949
+ }
3950
+ if (agent.sourceRepo !== void 0 && /[\r\n]/.test(agent.sourceRepo)) {
3951
+ throw new AgentConfigError(
3952
+ `agent "${agent.id}" has invalid sourceRepo \u2014 must be a single line (no newlines)`
3953
+ );
3954
+ }
3955
+ if (agent.runner === "claude" && agent.workdir !== void 0 && agent.workdir.trim() !== "") {
3956
+ throw new AgentConfigError(
3957
+ `agent "${agent.id}" has runner "claude" but sets workdir \u2014 a claude agent uses agentName, not workdir`
3958
+ );
3959
+ }
3960
+ if ((agent.runner === "pi" || agent.runner === "codex") && agent.agentName !== void 0 && agent.agentName.trim() !== "") {
3961
+ throw new AgentConfigError(
3962
+ `agent "${agent.id}" has runner "${agent.runner}" but sets agentName \u2014 a directory agent uses workdir, not agentName`
3963
+ );
3964
+ }
3965
+ if (agent.runner !== void 0 && agent.sourceKind !== void 0) {
3966
+ const directorySource = agent.sourceKind === "directory";
3967
+ const claudeRunner = agent.runner === "claude";
3968
+ if (directorySource && claudeRunner) {
3969
+ throw new AgentConfigError(
3970
+ `agent "${agent.id}" has sourceKind "directory" but runner "claude" \u2014 a directory source is a pi/codex agent`
3971
+ );
3972
+ }
3973
+ if (!directorySource && !claudeRunner) {
3974
+ throw new AgentConfigError(
3975
+ `agent "${agent.id}" has sourceKind "${agent.sourceKind}" but runner "${agent.runner}" \u2014 a ${agent.sourceKind} source is a claude agent`
3976
+ );
3977
+ }
3978
+ }
3925
3979
  validateSessionInvocation(agent, "resume", agent.resume);
3926
3980
  validateSessionInvocation(agent, "fork", agent.fork);
3927
3981
  if (agent.default) defaults++;
@@ -4024,6 +4078,21 @@ function parseInstalledAgents(fm) {
4024
4078
  }
4025
4079
  return Object.keys(installedAgents).length > 0 ? { installedAgents } : {};
4026
4080
  }
4081
+ function parseAgentDiscoveryFromFm(fm) {
4082
+ const flag = (key, def) => {
4083
+ const v = fm[key];
4084
+ if (v === void 0) return def;
4085
+ return String(v).toLowerCase() === "true";
4086
+ };
4087
+ const rootsRaw = fm["agentDiscovery.roots"];
4088
+ const roots = rootsRaw !== void 0 && String(rootsRaw).trim() !== "" ? String(rootsRaw).split(":").map((r) => r.trim()).filter((r) => r.length > 0) : [];
4089
+ return {
4090
+ claudeGlobal: flag("agentDiscovery.claudeGlobal", true),
4091
+ claudeProject: flag("agentDiscovery.claudeProject", true),
4092
+ directory: flag("agentDiscovery.directory", true),
4093
+ roots: roots.length > 0 ? roots : DEFAULT_CONFIG.agentDiscovery.roots.slice()
4094
+ };
4095
+ }
4027
4096
  function parseStatusConfig(content) {
4028
4097
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4029
4098
  if (!match) return null;
@@ -4532,6 +4601,43 @@ ${cleanedFm}
4532
4601
  ---${afterFrontmatter}`;
4533
4602
  await writeFileForce(configPath, newContent);
4534
4603
  }
4604
+ function serializeAgentDiscoveryConfig(cfg) {
4605
+ return [
4606
+ "agentDiscovery:",
4607
+ ` claudeGlobal: ${cfg.claudeGlobal}`,
4608
+ ` claudeProject: ${cfg.claudeProject}`,
4609
+ ` directory: ${cfg.directory}`,
4610
+ ` roots: ${cfg.roots.join(":")}`
4611
+ ].join("\n");
4612
+ }
4613
+ async function writeAgentDiscoveryConfig(cfg, standaloneDefaultCwd) {
4614
+ const configPath = resolve7(syntaurRoot(), "config.md");
4615
+ const block = serializeAgentDiscoveryConfig(cfg);
4616
+ const scalarLine = standaloneDefaultCwd ? `standaloneDefaultCwd: ${standaloneDefaultCwd}` : "";
4617
+ const additions = [block, scalarLine].filter(Boolean).join("\n");
4618
+ const existing = await fileExists(configPath) ? await readFile5(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
4619
+ const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
4620
+ if (!fmMatch) {
4621
+ const content = `---
4622
+ version: "2.0"
4623
+ defaultProjectDir: ${defaultProjectDir()}
4624
+ ${additions}
4625
+ ---
4626
+ ${existing}`;
4627
+ await writeFileForce(configPath, content);
4628
+ return;
4629
+ }
4630
+ const fmBlock = fmMatch[2];
4631
+ const afterFrontmatter = existing.slice(fmMatch[0].length);
4632
+ let cleanedFm = stripTopLevelBlock(fmBlock, "agentDiscovery");
4633
+ cleanedFm = stripTopLevelScalar(cleanedFm, "standaloneDefaultCwd");
4634
+ const newFm = `${cleanedFm}
4635
+ ${additions}`.replace(/^\n+/, "").replace(/\n+$/, "");
4636
+ const newContent = `---
4637
+ ${newFm}
4638
+ ---${afterFrontmatter}`;
4639
+ await writeFileForce(configPath, newContent);
4640
+ }
4535
4641
  function stripTopLevelScalar(fmBlock, key) {
4536
4642
  const lines = fmBlock.split("\n");
4537
4643
  const keyRegex = new RegExp(`^${key}:\\s*\\S`);
@@ -4744,6 +4850,10 @@ function parseAgentsConfig(content) {
4744
4850
  ...current.launchPrompt ? { launchPrompt: current.launchPrompt } : {},
4745
4851
  ...current.agentName ? { agentName: current.agentName } : {},
4746
4852
  ...current.workdir ? { workdir: current.workdir } : {},
4853
+ ...current.runner ? { runner: current.runner } : {},
4854
+ ...current.sourceKind ? { sourceKind: current.sourceKind } : {},
4855
+ ...current.sourcePath ? { sourcePath: current.sourcePath } : {},
4856
+ ...current.sourceRepo ? { sourceRepo: current.sourceRepo } : {},
4747
4857
  ...current.resume ? { resume: current.resume } : {},
4748
4858
  ...current.fork ? { fork: current.fork } : {}
4749
4859
  });
@@ -4941,6 +5051,18 @@ function assignAgentField(target, key, rawValue) {
4941
5051
  case "workdir":
4942
5052
  target.workdir = value;
4943
5053
  break;
5054
+ case "runner":
5055
+ target.runner = value;
5056
+ break;
5057
+ case "sourceKind":
5058
+ target.sourceKind = value;
5059
+ break;
5060
+ case "sourcePath":
5061
+ target.sourcePath = value;
5062
+ break;
5063
+ case "sourceRepo":
5064
+ target.sourceRepo = value;
5065
+ break;
4944
5066
  }
4945
5067
  }
4946
5068
  function yamlQuoteScalar(value) {
@@ -4976,6 +5098,18 @@ function serializeAgentsConfig(agents) {
4976
5098
  if (a.workdir) {
4977
5099
  lines.push(` workdir: ${yamlQuoteScalar(a.workdir)}`);
4978
5100
  }
5101
+ if (a.runner) {
5102
+ lines.push(` runner: ${yamlQuoteScalar(a.runner)}`);
5103
+ }
5104
+ if (a.sourceKind) {
5105
+ lines.push(` sourceKind: ${yamlQuoteScalar(a.sourceKind)}`);
5106
+ }
5107
+ if (a.sourcePath) {
5108
+ lines.push(` sourcePath: ${yamlQuoteScalar(a.sourcePath)}`);
5109
+ }
5110
+ if (a.sourceRepo) {
5111
+ lines.push(` sourceRepo: ${yamlQuoteScalar(a.sourceRepo)}`);
5112
+ }
4979
5113
  if (a.args && a.args.length > 0) {
4980
5114
  lines.push(` args:`);
4981
5115
  for (const arg of a.args) {
@@ -5461,7 +5595,12 @@ async function readConfig() {
5461
5595
  searchConfig: parseSearchConfig(content),
5462
5596
  workspaceVisibility: parseWorkspaceVisibilityConfig(fmBlock),
5463
5597
  staleness: parseStalenessConfig(content),
5464
- stalenessWatchdog: String(fm["stalenessWatchdog"]).toLowerCase() === "true"
5598
+ stalenessWatchdog: String(fm["stalenessWatchdog"]).toLowerCase() === "true",
5599
+ standaloneDefaultCwd: parseOptionalAbsolutePath(
5600
+ fm["standaloneDefaultCwd"],
5601
+ "standaloneDefaultCwd"
5602
+ ),
5603
+ agentDiscovery: parseAgentDiscoveryFromFm(fm)
5465
5604
  };
5466
5605
  }
5467
5606
  function getAssignmentTypes(config) {
@@ -5524,7 +5663,7 @@ async function updateAgentsConfig(mutation, options = {}) {
5524
5663
  await writeAgentsConfig(next);
5525
5664
  return { previous, next, written: true };
5526
5665
  }
5527
- var STALENESS_KEY_TO_FIELD, DURATION_RE, DURATION_UNIT_MS, DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, SESSION_AUTO_TRACK_VALUES, AgentConfigError, DEFAULT_STATUS_COLORS, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
5666
+ var STALENESS_KEY_TO_FIELD, DURATION_RE, DURATION_UNIT_MS, DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, SESSION_AUTO_TRACK_VALUES, RUNNER_KINDS, AGENT_SOURCE_KINDS, AgentConfigError, DEFAULT_STATUS_COLORS, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
5528
5667
  var init_config2 = __esm({
5529
5668
  "src/utils/config.ts"() {
5530
5669
  "use strict";
@@ -5600,10 +5739,23 @@ var init_config2 = __esm({
5600
5739
  hidden: []
5601
5740
  },
5602
5741
  staleness: null,
5603
- stalenessWatchdog: false
5742
+ stalenessWatchdog: false,
5743
+ standaloneDefaultCwd: null,
5744
+ agentDiscovery: {
5745
+ claudeGlobal: true,
5746
+ claudeProject: true,
5747
+ directory: true,
5748
+ roots: ["~"]
5749
+ }
5604
5750
  };
5605
5751
  AUTO_CREATE_WORKTREE_VALUES = ["skip", "ask", "always"];
5606
5752
  SESSION_AUTO_TRACK_VALUES = ["all", "workspaces-only", "off"];
5753
+ RUNNER_KINDS = ["claude", "pi", "codex"];
5754
+ AGENT_SOURCE_KINDS = [
5755
+ "claude-global",
5756
+ "claude-project",
5757
+ "directory"
5758
+ ];
5607
5759
  AgentConfigError = class extends Error {
5608
5760
  };
5609
5761
  DEFAULT_STATUS_COLORS = {
@@ -5625,7 +5777,11 @@ var init_config2 = __esm({
5625
5777
  "playbook",
5626
5778
  "launchPrompt",
5627
5779
  "agentName",
5628
- "workdir"
5780
+ "workdir",
5781
+ "runner",
5782
+ "sourceKind",
5783
+ "sourcePath",
5784
+ "sourceRepo"
5629
5785
  ]);
5630
5786
  migratedConfigPaths = /* @__PURE__ */ new Set();
5631
5787
  TerminalConfigError = class extends Error {
@@ -6023,6 +6179,7 @@ var init_assignment_resolver = __esm({
6023
6179
 
6024
6180
  // src/launch/cwd.ts
6025
6181
  import { existsSync, statSync } from "fs";
6182
+ import { homedir as homedir2 } from "os";
6026
6183
  import { isAbsolute as isAbsolute2 } from "path";
6027
6184
  function isExistingDir(p) {
6028
6185
  if (!p || !isAbsolute2(p)) return false;
@@ -6068,6 +6225,31 @@ function resolveLaunchCwd(agent, worktreeCwd) {
6068
6225
  }
6069
6226
  return { spawnCwd: worktreeCwd, worktreePath: worktreeCwd, invalidReason: null };
6070
6227
  }
6228
+ function resolveStandaloneCwd(agent, standaloneDefaultCwd) {
6229
+ const wd = agent.workdir?.trim();
6230
+ if (wd) {
6231
+ const expanded = expandHome(wd);
6232
+ if (!isExistingDir(expanded)) {
6233
+ return {
6234
+ cwd: null,
6235
+ invalidReason: `agent "${agent.id}" workdir ${agent.workdir} (resolved ${expanded}) is not an existing directory \u2014 a standalone launch requires a valid workdir.`
6236
+ };
6237
+ }
6238
+ return { cwd: expanded, invalidReason: null };
6239
+ }
6240
+ if (resolveRunner(agent) === "claude") {
6241
+ const configured = standaloneDefaultCwd?.trim();
6242
+ if (configured) {
6243
+ const expanded = expandHome(configured);
6244
+ if (isExistingDir(expanded)) return { cwd: expanded, invalidReason: null };
6245
+ }
6246
+ return { cwd: homedir2(), invalidReason: null };
6247
+ }
6248
+ return {
6249
+ cwd: null,
6250
+ invalidReason: `agent "${agent.id}" has no workdir \u2014 a standalone launch of a ${resolveRunner(agent)} agent requires a valid workdir.`
6251
+ };
6252
+ }
6071
6253
  function formatFallbackCwdWarning(opts) {
6072
6254
  const missing = [];
6073
6255
  if (!opts.worktreePath) missing.push("worktreePath");
@@ -6079,6 +6261,7 @@ function formatFallbackCwdWarning(opts) {
6079
6261
  var init_cwd = __esm({
6080
6262
  "src/launch/cwd.ts"() {
6081
6263
  "use strict";
6264
+ init_agents_schema();
6082
6265
  init_paths();
6083
6266
  }
6084
6267
  });
@@ -7913,8 +8096,8 @@ async function migrateFromMarkdown(projectsDir) {
7913
8096
  return allSessions.length;
7914
8097
  }
7915
8098
  async function parseMarkdownSessionsIndex(filePath, projectSlug) {
7916
- const { readFile: readFile32 } = await import("fs/promises");
7917
- const raw2 = await readFile32(filePath, "utf-8");
8099
+ const { readFile: readFile34 } = await import("fs/promises");
8100
+ const raw2 = await readFile34(filePath, "utf-8");
7918
8101
  const sessions = [];
7919
8102
  const lines = raw2.split("\n");
7920
8103
  let inTable = false;
@@ -8734,7 +8917,7 @@ function emptyScan() {
8734
8917
  return { sessions: [], tmuxAvailable: false };
8735
8918
  }
8736
8919
  function scanKey(serversDir2, projectsDir, assignmentsDir2) {
8737
- return `${serversDir2}\0${projectsDir}\0${assignmentsDir2 ?? ""}`;
8920
+ return JSON.stringify([serversDir2, projectsDir, assignmentsDir2 ?? ""]);
8738
8921
  }
8739
8922
  function delay(ms) {
8740
8923
  return new Promise((resolve49) => {
@@ -12157,12 +12340,12 @@ var init_transcript = __esm({
12157
12340
  // src/usage/cwd-extractor.ts
12158
12341
  import { open as open3, readdir as readdir13, stat as stat4 } from "fs/promises";
12159
12342
  import { join as join3 } from "path";
12160
- import { homedir as homedir2 } from "os";
12343
+ import { homedir as homedir3 } from "os";
12161
12344
  async function extractClaudeSessionMeta(jsonlPath) {
12162
12345
  const cwd = await derivePathFromTranscript(jsonlPath);
12163
12346
  if (!cwd) return null;
12164
- const basename7 = jsonlPath.split("/").pop() ?? "";
12165
- const sessionId = basename7.replace(/\.jsonl$/, "");
12347
+ const basename8 = jsonlPath.split("/").pop() ?? "";
12348
+ const sessionId = basename8.replace(/\.jsonl$/, "");
12166
12349
  if (!sessionId) return null;
12167
12350
  const startTs = await readFirstTimestamp(jsonlPath);
12168
12351
  const endTs = await readLastTimestamp(jsonlPath);
@@ -12257,8 +12440,8 @@ async function* walkClaudeProjects(opts = {}) {
12257
12440
  async function* walkCodexSessions(opts = {}) {
12258
12441
  const root = resolveCodexSessionsRoot(opts.root);
12259
12442
  for await (const filePath of walkJsonlRecursive(root)) {
12260
- const basename7 = filePath.split("/").pop() ?? "";
12261
- if (!basename7.endsWith(".jsonl")) continue;
12443
+ const basename8 = filePath.split("/").pop() ?? "";
12444
+ if (!basename8.endsWith(".jsonl")) continue;
12262
12445
  if (opts.sinceMtimeMs !== void 0) {
12263
12446
  const mtime = await mtimeMs(filePath);
12264
12447
  if (mtime !== null && mtime < opts.sinceMtimeMs) continue;
@@ -12273,13 +12456,13 @@ function resolveCodexSessionsRoot(override3) {
12273
12456
  if (fromSessionsEnv && fromSessionsEnv.length > 0) return expandHome(fromSessionsEnv);
12274
12457
  const fromHomeEnv = process.env.CODEX_HOME;
12275
12458
  if (fromHomeEnv && fromHomeEnv.length > 0) return join3(expandHome(fromHomeEnv), "sessions");
12276
- return join3(homedir2(), ".codex", "sessions");
12459
+ return join3(homedir3(), ".codex", "sessions");
12277
12460
  }
12278
12461
  async function extractPiSessionMeta(jsonlPath) {
12279
- const basename7 = jsonlPath.split("/").pop() ?? "";
12280
- const underscoreIdx = basename7.lastIndexOf("_");
12462
+ const basename8 = jsonlPath.split("/").pop() ?? "";
12463
+ const underscoreIdx = basename8.lastIndexOf("_");
12281
12464
  if (underscoreIdx === -1) return null;
12282
- const sessionId = basename7.slice(underscoreIdx + 1).replace(/\.jsonl$/, "");
12465
+ const sessionId = basename8.slice(underscoreIdx + 1).replace(/\.jsonl$/, "");
12283
12466
  if (!sessionId) return null;
12284
12467
  let handle;
12285
12468
  try {
@@ -12331,7 +12514,7 @@ function resolvePiSessionsRoot(override3) {
12331
12514
  if (override3) return expandHome(override3);
12332
12515
  const fromHomeEnv = process.env.PI_AGENT_DIR;
12333
12516
  if (fromHomeEnv && fromHomeEnv.length > 0) return join3(expandHome(fromHomeEnv), "sessions");
12334
- return join3(homedir2(), ".pi", "agent", "sessions");
12517
+ return join3(homedir3(), ".pi", "agent", "sessions");
12335
12518
  }
12336
12519
  async function* walkPiSessions(opts = {}) {
12337
12520
  const root = resolvePiSessionsRoot(opts.root);
@@ -12495,7 +12678,7 @@ var init_cwd_extractor = __esm({
12495
12678
  // src/utils/session-id.ts
12496
12679
  import { execFileSync as execFileSync3 } from "child_process";
12497
12680
  import { mkdirSync, readFileSync as readFileSync2, statSync as statSync4, writeFileSync } from "fs";
12498
- import { homedir as homedir3 } from "os";
12681
+ import { homedir as homedir4 } from "os";
12499
12682
  import { dirname as dirname5, join as join4 } from "path";
12500
12683
  function isSafeSessionId(value) {
12501
12684
  return typeof value === "string" && value.length > 0 && value.length <= 256 && SAFE_SESSION_ID.test(value);
@@ -12562,10 +12745,10 @@ var init_user_descriptors = __esm({
12562
12745
  });
12563
12746
 
12564
12747
  // src/targets/registry.ts
12565
- import { homedir as homedir4 } from "os";
12748
+ import { homedir as homedir5 } from "os";
12566
12749
  import { join as join5, resolve as resolve27 } from "path";
12567
12750
  function home(...segments) {
12568
- return resolve27(homedir4(), ...segments);
12751
+ return resolve27(homedir5(), ...segments);
12569
12752
  }
12570
12753
  function hermesHome() {
12571
12754
  const env = process.env.HERMES_HOME;
@@ -13146,7 +13329,7 @@ init_agent_sessions();
13146
13329
  import express from "express";
13147
13330
  import { createServer } from "http";
13148
13331
  import { resolve as resolve48 } from "path";
13149
- import { writeFile as writeFile8, unlink as unlink9 } from "fs/promises";
13332
+ import { writeFile as writeFile9, unlink as unlink9 } from "fs/promises";
13150
13333
  import { WebSocketServer, WebSocket } from "ws";
13151
13334
 
13152
13335
  // src/dashboard/session-liveness.ts
@@ -18203,6 +18386,7 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
18203
18386
  init_config2();
18204
18387
  init_agents_schema();
18205
18388
  import { Router as Router5 } from "express";
18389
+ import { access as access4 } from "fs/promises";
18206
18390
 
18207
18391
  // src/targets/agent-definitions.ts
18208
18392
  init_parser();
@@ -18251,7 +18435,293 @@ async function discoverClaudeAgents(root) {
18251
18435
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
18252
18436
  }
18253
18437
 
18438
+ // src/targets/agent-discovery.ts
18439
+ init_paths();
18440
+ init_parser();
18441
+ import { access as access2, readdir as readdir16, readFile as readFile21 } from "fs/promises";
18442
+ import { join as join7 } from "path";
18443
+ async function pathExists(p) {
18444
+ try {
18445
+ await access2(p);
18446
+ return true;
18447
+ } catch {
18448
+ return false;
18449
+ }
18450
+ }
18451
+ function coerceRunner(raw2) {
18452
+ return raw2 === "claude" || raw2 === "pi" || raw2 === "codex" ? raw2 : void 0;
18453
+ }
18454
+ async function readSyntaurMeta(file) {
18455
+ let content;
18456
+ try {
18457
+ content = await readFile21(file, "utf-8");
18458
+ } catch {
18459
+ return null;
18460
+ }
18461
+ const [fm] = extractFrontmatter2(content);
18462
+ if (!fm) return null;
18463
+ if (!/^syntaur:[ \t]*$/m.test(fm)) return null;
18464
+ return {
18465
+ name: getNestedField(fm, "syntaur", "name")?.trim() || void 0,
18466
+ runner: coerceRunner(getNestedField(fm, "syntaur", "runner")?.trim() || void 0),
18467
+ description: getNestedField(fm, "syntaur", "description")?.trim() || void 0
18468
+ };
18469
+ }
18470
+ async function fileHasSyntaurBlock(file) {
18471
+ return await readSyntaurMeta(file) !== null;
18472
+ }
18473
+ function claudeAlreadyRegistered(agents, path, name) {
18474
+ return agents.some((a) => a.sourcePath === path || !!a.agentName && a.agentName === name);
18475
+ }
18476
+ function directoryAlreadyRegistered(agents, dir) {
18477
+ return agents.some((a) => a.sourcePath === dir || a.workdir === dir);
18478
+ }
18479
+ async function toClaudeCandidate(d, source, agents, sourceRepo) {
18480
+ return {
18481
+ name: d.name,
18482
+ runner: "claude",
18483
+ description: d.description,
18484
+ path: d.path,
18485
+ source,
18486
+ ...sourceRepo ? { sourceRepo } : {},
18487
+ recommended: await fileHasSyntaurBlock(d.path),
18488
+ alreadyRegistered: claudeAlreadyRegistered(agents, d.path, d.name)
18489
+ };
18490
+ }
18491
+ async function inspectDirCandidate(dir, basename8, agents) {
18492
+ const hasPi = await pathExists(join7(dir, ".pi"));
18493
+ const hasMcp = await pathExists(join7(dir, ".mcp.json"));
18494
+ const hasSyntaurMd = await pathExists(join7(dir, "SYNTAUR.md"));
18495
+ const hasAgentsMd = await pathExists(join7(dir, "AGENTS.md"));
18496
+ let syntaur = null;
18497
+ if (hasSyntaurMd) syntaur = await readSyntaurMeta(join7(dir, "SYNTAUR.md"));
18498
+ if (!syntaur && hasAgentsMd) syntaur = await readSyntaurMeta(join7(dir, "AGENTS.md"));
18499
+ const strong = hasPi || hasMcp || syntaur !== null;
18500
+ if (!strong) return null;
18501
+ return {
18502
+ name: syntaur?.name ?? basename8,
18503
+ // A directory agent is pi/codex by definition; a contradictory
18504
+ // `syntaur.runner: claude` opt-in is clamped to pi so the candidate never
18505
+ // carries an impossible directory+claude combo.
18506
+ runner: syntaur?.runner === "codex" ? "codex" : "pi",
18507
+ description: syntaur?.description,
18508
+ path: dir,
18509
+ source: "directory",
18510
+ recommended: syntaur !== null,
18511
+ alreadyRegistered: directoryAlreadyRegistered(agents, dir)
18512
+ };
18513
+ }
18514
+ async function discoverAgents(input) {
18515
+ const out = [];
18516
+ if (input.claudeGlobal) {
18517
+ for (const d of await discoverClaudeAgents()) {
18518
+ out.push(await toClaudeCandidate(d, "claude-global", input.agents));
18519
+ }
18520
+ }
18521
+ if (input.claudeProject && input.repo) {
18522
+ const projRoot = join7(input.repo, ".claude", "agents");
18523
+ for (const d of await discoverClaudeAgents(projRoot)) {
18524
+ out.push(await toClaudeCandidate(d, "claude-project", input.agents, input.repo));
18525
+ }
18526
+ }
18527
+ if (input.directory || input.claudeProject) {
18528
+ for (const rawRoot of input.roots) {
18529
+ const root = expandHome(rawRoot);
18530
+ let entries;
18531
+ try {
18532
+ entries = await readdir16(root, { withFileTypes: true });
18533
+ } catch {
18534
+ continue;
18535
+ }
18536
+ for (const e of entries) {
18537
+ if (!e.isDirectory()) continue;
18538
+ const dir = join7(root, e.name);
18539
+ if (input.directory) {
18540
+ const dirCand = await inspectDirCandidate(dir, e.name, input.agents);
18541
+ if (dirCand) out.push(dirCand);
18542
+ }
18543
+ if (input.claudeProject && await pathExists(join7(dir, ".claude", "agents"))) {
18544
+ const projRoot = join7(dir, ".claude", "agents");
18545
+ for (const d of await discoverClaudeAgents(projRoot)) {
18546
+ out.push(await toClaudeCandidate(d, "claude-project", input.agents, dir));
18547
+ }
18548
+ }
18549
+ }
18550
+ }
18551
+ }
18552
+ const seen = /* @__PURE__ */ new Set();
18553
+ const deduped = [];
18554
+ for (const c of out) {
18555
+ const key = JSON.stringify([c.source, c.path, c.name]);
18556
+ if (seen.has(key)) continue;
18557
+ seen.add(key);
18558
+ deduped.push(c);
18559
+ }
18560
+ deduped.sort(
18561
+ (a, b) => a.recommended === b.recommended ? a.name.localeCompare(b.name) : a.recommended ? -1 : 1
18562
+ );
18563
+ return deduped;
18564
+ }
18565
+
18566
+ // src/targets/agent-authoring.ts
18567
+ init_paths();
18568
+ init_parser();
18569
+ init_agents_schema();
18570
+ import { mkdir as mkdir2, writeFile as writeFile4, access as access3, readFile as readFile22, stat as stat5 } from "fs/promises";
18571
+ import { homedir as homedir6 } from "os";
18572
+ import { basename as basename5, dirname as dirname6, isAbsolute as isAbsolute5, join as join8 } from "path";
18573
+ var RUNNER_COMMAND = {
18574
+ claude: "claude",
18575
+ pi: "pi",
18576
+ codex: "codex"
18577
+ };
18578
+ function requireAbsolutePath(raw2, what = "path") {
18579
+ const p = expandHome(raw2.trim());
18580
+ if (!isAbsolute5(p)) {
18581
+ throw new Error(`${what} must be an absolute path (got ${JSON.stringify(raw2)})`);
18582
+ }
18583
+ return p;
18584
+ }
18585
+ function slugifyAgentId(name) {
18586
+ const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
18587
+ const safe = /^[a-z0-9]/.test(slug) ? slug : `agent-${slug}`;
18588
+ return AGENT_ID_PATTERN.test(safe) ? safe : "agent";
18589
+ }
18590
+ function uniqueAgentId(base, existingIds) {
18591
+ const taken = new Set(existingIds);
18592
+ if (!taken.has(base)) return base;
18593
+ for (let n = 2; ; n++) {
18594
+ const candidate = `${base}-${n}`;
18595
+ if (!taken.has(candidate)) return candidate;
18596
+ }
18597
+ }
18598
+ function buildRegisteredAgent(input) {
18599
+ const id = uniqueAgentId(slugifyAgentId(input.name), input.existingIds);
18600
+ const agent = {
18601
+ id,
18602
+ label: input.name,
18603
+ command: RUNNER_COMMAND[input.runner],
18604
+ runner: input.runner,
18605
+ sourceKind: input.sourceKind,
18606
+ sourcePath: input.sourcePath
18607
+ };
18608
+ if (input.sourceRepo) agent.sourceRepo = input.sourceRepo;
18609
+ if (input.runner === "claude") {
18610
+ agent.agentName = input.name;
18611
+ } else {
18612
+ agent.workdir = input.sourcePath;
18613
+ }
18614
+ return agent;
18615
+ }
18616
+ async function pathExists2(p) {
18617
+ try {
18618
+ await access3(p);
18619
+ return true;
18620
+ } catch {
18621
+ return false;
18622
+ }
18623
+ }
18624
+ function frontmatterLines(fields) {
18625
+ const lines = [];
18626
+ for (const [k, v] of Object.entries(fields)) {
18627
+ if (v !== void 0 && v !== "") lines.push(`${k}: ${JSON.stringify(v)}`);
18628
+ }
18629
+ return lines;
18630
+ }
18631
+ function syntaurBlock(name, runner, description) {
18632
+ const lines = ["syntaur:", ` name: ${JSON.stringify(name)}`, ` runner: ${runner}`];
18633
+ if (description) lines.push(` description: ${JSON.stringify(description)}`);
18634
+ return lines;
18635
+ }
18636
+ async function authorAgentDef(input) {
18637
+ const slug = slugifyAgentId(input.name);
18638
+ if (input.runner === "claude") {
18639
+ const dir2 = input.location ? requireAbsolutePath(input.location, "location") : join8(homedir6(), ".claude", "agents");
18640
+ const path = join8(dir2, `${slug}.md`);
18641
+ if (await pathExists2(path)) {
18642
+ throw new Error(`refusing to overwrite existing def at ${path}`);
18643
+ }
18644
+ const fm2 = [
18645
+ "---",
18646
+ ...frontmatterLines({
18647
+ name: input.name,
18648
+ description: input.description,
18649
+ model: input.model
18650
+ }),
18651
+ ...syntaurBlock(input.name, "claude", input.description),
18652
+ "---"
18653
+ ];
18654
+ await mkdir2(dir2, { recursive: true });
18655
+ await writeFile4(path, `${fm2.join("\n")}
18656
+
18657
+ ${input.instructions.trim()}
18658
+ `);
18659
+ if (!input.location) {
18660
+ return { path, sourceKind: "claude-global" };
18661
+ }
18662
+ const sourceRepo = basename5(dir2) === "agents" && basename5(dirname6(dir2)) === ".claude" ? dirname6(dirname6(dir2)) : void 0;
18663
+ return { path, sourceKind: "claude-project", ...sourceRepo ? { sourceRepo } : {} };
18664
+ }
18665
+ const parent = input.location ? requireAbsolutePath(input.location, "location") : homedir6();
18666
+ const dir = join8(parent, slug);
18667
+ const agentsMd = join8(dir, "AGENTS.md");
18668
+ if (await pathExists2(agentsMd)) {
18669
+ throw new Error(`refusing to overwrite existing def at ${agentsMd}`);
18670
+ }
18671
+ const fm = ["---", ...syntaurBlock(input.name, input.runner, input.description), "---"];
18672
+ await mkdir2(dir, { recursive: true });
18673
+ await writeFile4(agentsMd, `${fm.join("\n")}
18674
+
18675
+ ${input.instructions.trim()}
18676
+ `);
18677
+ return { path: dir, sourceKind: "directory" };
18678
+ }
18679
+ async function inferManualAdd(rawPath) {
18680
+ const path = requireAbsolutePath(rawPath);
18681
+ const info = await stat5(path);
18682
+ if (info.isFile()) {
18683
+ const content = await readFile22(path, "utf-8");
18684
+ const [fm] = extractFrontmatter2(content);
18685
+ const name = getNestedField(fm, "syntaur", "name")?.trim() || getField(fm, "name")?.trim() || basename5(path).replace(/\.md$/, "");
18686
+ const description = getNestedField(fm, "syntaur", "description")?.trim() || getField(fm, "description")?.trim() || void 0;
18687
+ return { name, runner: "claude", sourceKind: "claude-global", sourcePath: path, description };
18688
+ }
18689
+ let syntaurName;
18690
+ let syntaurRunner;
18691
+ let syntaurDesc;
18692
+ for (const f of ["SYNTAUR.md", "AGENTS.md"]) {
18693
+ const p = join8(path, f);
18694
+ if (await pathExists2(p)) {
18695
+ const [fm] = extractFrontmatter2(await readFile22(p, "utf-8"));
18696
+ syntaurName = getNestedField(fm, "syntaur", "name")?.trim() || syntaurName;
18697
+ const r = getNestedField(fm, "syntaur", "runner")?.trim();
18698
+ if (r === "claude" || r === "pi" || r === "codex") syntaurRunner = r;
18699
+ syntaurDesc = getNestedField(fm, "syntaur", "description")?.trim() || syntaurDesc;
18700
+ if (syntaurName || syntaurRunner) break;
18701
+ }
18702
+ }
18703
+ return {
18704
+ name: syntaurName ?? basename5(path),
18705
+ // A directory (pi/codex) agent is never claude — clamp a contradictory
18706
+ // `syntaur.runner: claude` opt-in to pi (parity with inspectDirCandidate).
18707
+ runner: syntaurRunner === "codex" ? "codex" : "pi",
18708
+ sourceKind: "directory",
18709
+ sourcePath: path,
18710
+ description: syntaurDesc
18711
+ };
18712
+ }
18713
+
18254
18714
  // src/dashboard/api-agents.ts
18715
+ async function pathExists3(p) {
18716
+ try {
18717
+ await access4(p);
18718
+ return true;
18719
+ } catch {
18720
+ return false;
18721
+ }
18722
+ }
18723
+ var RUNNER_KINDS_SET = /* @__PURE__ */ new Set(["claude", "pi", "codex"]);
18724
+ var SOURCE_KINDS_SET = /* @__PURE__ */ new Set(["claude-global", "claude-project", "directory"]);
18255
18725
  function mapAgentErrorToFieldErrors(err) {
18256
18726
  const message = err.message;
18257
18727
  let match = message.match(/^agent id "([^"]*)" is invalid/);
@@ -18546,6 +19016,22 @@ function coerceAgentRow(raw2, index) {
18546
19016
  const workdir = entry.workdir.trim();
18547
19017
  if (workdir) cleaned.workdir = workdir;
18548
19018
  }
19019
+ for (const field of ["runner", "sourceKind", "sourcePath", "sourceRepo"]) {
19020
+ const raw3 = entry[field];
19021
+ if (raw3 === void 0) continue;
19022
+ if (typeof raw3 !== "string") {
19023
+ return {
19024
+ ok: false,
19025
+ status: 400,
19026
+ body: {
19027
+ error: `agents[${index}].${field} must be a string`,
19028
+ fieldErrors: [{ id, field, message: `${field} must be a string` }]
19029
+ }
19030
+ };
19031
+ }
19032
+ const v = raw3.trim();
19033
+ if (v) cleaned[field] = v;
19034
+ }
18549
19035
  return { ok: true, value: cleaned };
18550
19036
  }
18551
19037
  function createAgentsRouter() {
@@ -18559,6 +19045,164 @@ function createAgentsRouter() {
18559
19045
  res.status(500).json({ error: "Failed to discover Claude agents" });
18560
19046
  }
18561
19047
  });
19048
+ router.get("/discovered", async (req2, res) => {
19049
+ try {
19050
+ const config = await readConfig();
19051
+ const agents = config.agents ?? BUILTIN_AGENTS;
19052
+ const repo = typeof req2.query.repo === "string" && req2.query.repo ? req2.query.repo : null;
19053
+ const d = config.agentDiscovery;
19054
+ const candidates = await discoverAgents({
19055
+ claudeGlobal: d.claudeGlobal,
19056
+ claudeProject: d.claudeProject,
19057
+ directory: d.directory,
19058
+ roots: d.roots,
19059
+ repo,
19060
+ agents
19061
+ });
19062
+ res.json({ candidates });
19063
+ } catch (err) {
19064
+ console.error("Error discovering agents:", err);
19065
+ res.status(500).json({ error: "Failed to discover agents" });
19066
+ }
19067
+ });
19068
+ async function appendAndPersist(agent, res) {
19069
+ const config = await readConfig();
19070
+ const base = config.agents ?? BUILTIN_AGENTS;
19071
+ if (agent.sourcePath && base.some((a) => a.sourcePath === agent.sourcePath)) {
19072
+ res.status(409).json({ error: `already registered: ${agent.sourcePath}` });
19073
+ return null;
19074
+ }
19075
+ const next = [...base, agent];
19076
+ try {
19077
+ await writeAgentsConfig(next);
19078
+ } catch (err) {
19079
+ if (err instanceof AgentConfigError) {
19080
+ res.status(400).json(mapAgentErrorToFieldErrors(err));
19081
+ return null;
19082
+ }
19083
+ throw err;
19084
+ }
19085
+ return next;
19086
+ }
19087
+ router.post("/register", async (req2, res) => {
19088
+ try {
19089
+ const b = req2.body ?? {};
19090
+ if (!b.path || !b.name || !b.runner || !b.sourceKind) {
19091
+ res.status(400).json({ error: "register requires path, name, runner, sourceKind" });
19092
+ return;
19093
+ }
19094
+ if (!RUNNER_KINDS_SET.has(b.runner) || !SOURCE_KINDS_SET.has(b.sourceKind)) {
19095
+ res.status(400).json({ error: "invalid runner or sourceKind" });
19096
+ return;
19097
+ }
19098
+ let regPath;
19099
+ let sourceRepo;
19100
+ try {
19101
+ regPath = requireAbsolutePath(b.path);
19102
+ sourceRepo = b.sourceRepo ? requireAbsolutePath(b.sourceRepo, "sourceRepo") : void 0;
19103
+ } catch (err) {
19104
+ res.status(400).json({ error: err instanceof Error ? err.message : "invalid path" });
19105
+ return;
19106
+ }
19107
+ if (!await pathExists3(regPath)) {
19108
+ res.status(400).json({ error: `path not found: ${regPath}` });
19109
+ return;
19110
+ }
19111
+ const config = await readConfig();
19112
+ const base = config.agents ?? BUILTIN_AGENTS;
19113
+ const agent = buildRegisteredAgent({
19114
+ name: b.name,
19115
+ runner: b.runner,
19116
+ sourceKind: b.sourceKind,
19117
+ sourcePath: regPath,
19118
+ sourceRepo,
19119
+ description: b.description,
19120
+ existingIds: base.map((a) => a.id)
19121
+ });
19122
+ const next = await appendAndPersist(agent, res);
19123
+ if (!next) return;
19124
+ res.json({ agent, agents: next, custom: true });
19125
+ } catch (err) {
19126
+ console.error("Error registering agent:", err);
19127
+ res.status(500).json({ error: "Failed to register agent" });
19128
+ }
19129
+ });
19130
+ router.post("/manual-add", async (req2, res) => {
19131
+ try {
19132
+ const p = (req2.body ?? {}).path;
19133
+ if (typeof p !== "string" || !p) {
19134
+ res.status(400).json({ error: "manual-add requires a path" });
19135
+ return;
19136
+ }
19137
+ let inferred;
19138
+ try {
19139
+ inferred = await inferManualAdd(p);
19140
+ } catch {
19141
+ res.status(400).json({ error: `path not found or unreadable: ${p}` });
19142
+ return;
19143
+ }
19144
+ const config = await readConfig();
19145
+ const base = config.agents ?? BUILTIN_AGENTS;
19146
+ const agent = buildRegisteredAgent({
19147
+ name: inferred.name,
19148
+ runner: inferred.runner,
19149
+ sourceKind: inferred.sourceKind,
19150
+ sourcePath: inferred.sourcePath,
19151
+ description: inferred.description,
19152
+ existingIds: base.map((a) => a.id)
19153
+ });
19154
+ const next = await appendAndPersist(agent, res);
19155
+ if (!next) return;
19156
+ res.json({ agent, agents: next, custom: true });
19157
+ } catch (err) {
19158
+ console.error("Error manual-adding agent:", err);
19159
+ res.status(500).json({ error: "Failed to manual-add agent" });
19160
+ }
19161
+ });
19162
+ router.post("/create", async (req2, res) => {
19163
+ try {
19164
+ const b = req2.body ?? {};
19165
+ if (!b.name || !b.runner || typeof b.instructions !== "string") {
19166
+ res.status(400).json({ error: "create requires name, runner, instructions" });
19167
+ return;
19168
+ }
19169
+ if (!RUNNER_KINDS_SET.has(b.runner)) {
19170
+ res.status(400).json({ error: "invalid runner" });
19171
+ return;
19172
+ }
19173
+ let authored;
19174
+ try {
19175
+ authored = await authorAgentDef({
19176
+ name: b.name,
19177
+ runner: b.runner,
19178
+ model: b.model,
19179
+ description: b.description,
19180
+ instructions: b.instructions,
19181
+ location: b.location
19182
+ });
19183
+ } catch (err) {
19184
+ res.status(400).json({ error: err instanceof Error ? err.message : "failed to author def" });
19185
+ return;
19186
+ }
19187
+ const config = await readConfig();
19188
+ const base = config.agents ?? BUILTIN_AGENTS;
19189
+ const agent = buildRegisteredAgent({
19190
+ name: b.name,
19191
+ runner: b.runner,
19192
+ sourceKind: authored.sourceKind,
19193
+ sourcePath: authored.path,
19194
+ sourceRepo: authored.sourceRepo,
19195
+ description: b.description,
19196
+ existingIds: base.map((a) => a.id)
19197
+ });
19198
+ const next = await appendAndPersist(agent, res);
19199
+ if (!next) return;
19200
+ res.json({ agent, path: authored.path, agents: next, custom: true });
19201
+ } catch (err) {
19202
+ console.error("Error creating agent:", err);
19203
+ res.status(500).json({ error: "Failed to create agent" });
19204
+ }
19205
+ });
18562
19206
  router.get("/", async (_req, res) => {
18563
19207
  try {
18564
19208
  const config = await readConfig();
@@ -18621,8 +19265,8 @@ import { Router as Router6 } from "express";
18621
19265
  // src/utils/terminal-probe.ts
18622
19266
  import { spawnSync as spawnSync4 } from "child_process";
18623
19267
  import { existsSync as existsSync2 } from "fs";
18624
- import { homedir as homedir5 } from "os";
18625
- import { join as join7 } from "path";
19268
+ import { homedir as homedir7 } from "os";
19269
+ import { join as join9 } from "path";
18626
19270
  var APP_BUNDLE_IDS = {
18627
19271
  "terminal-app": "com.apple.Terminal",
18628
19272
  iterm: "com.googlecode.iterm2",
@@ -18639,7 +19283,7 @@ var APP_FIXED_PATHS = {
18639
19283
  "terminal-app": "/System/Applications/Utilities/Terminal.app"
18640
19284
  };
18641
19285
  function defaultApplicationsDirs() {
18642
- return ["/Applications", join7(homedir5(), "Applications")];
19286
+ return ["/Applications", join9(homedir7(), "Applications")];
18643
19287
  }
18644
19288
  function findAppBundle(terminal, dirs = defaultApplicationsDirs()) {
18645
19289
  const fixed = APP_FIXED_PATHS[terminal];
@@ -18647,7 +19291,7 @@ function findAppBundle(terminal, dirs = defaultApplicationsDirs()) {
18647
19291
  const bundleName = APP_BUNDLE_NAMES[terminal];
18648
19292
  if (bundleName) {
18649
19293
  for (const dir of dirs) {
18650
- const candidate = join7(dir, bundleName);
19294
+ const candidate = join9(dir, bundleName);
18651
19295
  if (existsSync2(candidate)) return candidate;
18652
19296
  }
18653
19297
  }
@@ -18677,11 +19321,11 @@ var defaultCmuxLsappinfoRunner = () => {
18677
19321
  function resolveCmuxCli(applicationsDirsOverride, cliDirsOverride, lsappinfoRunnerOverride) {
18678
19322
  const bundle = findAppBundle("cmux", applicationsDirsOverride);
18679
19323
  if (bundle) {
18680
- const cli = join7(bundle, "Contents/Resources/bin/cmux");
19324
+ const cli = join9(bundle, "Contents/Resources/bin/cmux");
18681
19325
  if (existsSync2(cli)) return cli;
18682
19326
  }
18683
19327
  for (const dir of cliDirsOverride ?? CMUX_CLI_DIRS) {
18684
- const cli = join7(dir, "cmux");
19328
+ const cli = join9(dir, "cmux");
18685
19329
  if (existsSync2(cli)) return cli;
18686
19330
  }
18687
19331
  if (process.platform === "darwin") {
@@ -18690,7 +19334,7 @@ function resolveCmuxCli(applicationsDirsOverride, cliDirsOverride, lsappinfoRunn
18690
19334
  if (stdout) {
18691
19335
  const match = stdout.match(/"LSBundlePath"="([^"]+)"/);
18692
19336
  if (match) {
18693
- const cli = join7(match[1], "Contents/Resources/bin/cmux");
19337
+ const cli = join9(match[1], "Contents/Resources/bin/cmux");
18694
19338
  if (existsSync2(cli)) return cli;
18695
19339
  }
18696
19340
  }
@@ -18861,15 +19505,15 @@ init_agent_sessions();
18861
19505
 
18862
19506
  // src/launch/argv.ts
18863
19507
  init_agents_schema();
18864
- import { isAbsolute as isAbsolute6 } from "path";
19508
+ import { isAbsolute as isAbsolute7 } from "path";
18865
19509
 
18866
19510
  // src/tui/launch.ts
18867
19511
  init_api();
18868
19512
  init_agents_schema();
18869
19513
  init_cwd();
18870
19514
  import { spawn as spawn2 } from "child_process";
18871
- import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
18872
- import { isAbsolute as isAbsolute5, resolve as resolve28 } from "path";
19515
+ import { mkdir as mkdir3, writeFile as writeFile5 } from "fs/promises";
19516
+ import { isAbsolute as isAbsolute6, resolve as resolve28 } from "path";
18873
19517
  init_paths();
18874
19518
  init_playbooks();
18875
19519
  init_cwd();
@@ -18887,7 +19531,7 @@ function buildAgentArgv(agent, prompt, env = process.env) {
18887
19531
  const requested = env.SHELL;
18888
19532
  let shell = requested;
18889
19533
  let warning = null;
18890
- if (!shell || !isAbsolute5(shell)) {
19534
+ if (!shell || !isAbsolute6(shell)) {
18891
19535
  warning = `syntaur: $SHELL ${requested ? `("${requested}") is not absolute` : "is unset"} \u2014 falling back to /bin/sh for shell-alias resolution`;
18892
19536
  shell = "/bin/sh";
18893
19537
  }
@@ -18922,7 +19566,7 @@ function buildSessionArgv(agent, sessionId, mode, env = process.env) {
18922
19566
  const requested = env.SHELL;
18923
19567
  let shell = requested;
18924
19568
  let warning = null;
18925
- if (!shell || !isAbsolute6(shell)) {
19569
+ if (!shell || !isAbsolute7(shell)) {
18926
19570
  warning = `syntaur: $SHELL ${requested ? `("${requested}") is not absolute` : "is unset"} \u2014 falling back to /bin/sh for shell-alias resolution`;
18927
19571
  shell = "/bin/sh";
18928
19572
  }
@@ -18982,18 +19626,14 @@ async function resolveStandalonePlan(input, terminal) {
18982
19626
  `Agent "${agentId}" requested for a standalone launch is not in your agents list.`
18983
19627
  );
18984
19628
  }
18985
- const wd = agent.workdir?.trim();
18986
- if (!wd) {
18987
- throw new LaunchError(
18988
- "workspace-path-invalid",
18989
- `Agent "${agent.id}" has no workdir \u2014 a standalone launch requires a valid workdir.`
18990
- );
18991
- }
18992
- const expanded = expandHome(wd);
18993
- if (!isExistingDir(expanded)) {
19629
+ const { cwd: spawnCwd, invalidReason } = resolveStandaloneCwd(
19630
+ agent,
19631
+ input.config.standaloneDefaultCwd
19632
+ );
19633
+ if (spawnCwd === null) {
18994
19634
  throw new LaunchError(
18995
19635
  "workspace-path-invalid",
18996
- `Agent "${agent.id}" workdir ${agent.workdir} (resolved ${expanded}) is not an existing directory \u2014 a standalone launch requires a valid workdir.`
19636
+ invalidReason ?? `Agent "${agent.id}" has no valid standalone launch directory.`
18997
19637
  );
18998
19638
  }
18999
19639
  const knownPlaybookSlugs = await listPlaybookSlugs(playbooksDir());
@@ -19007,7 +19647,7 @@ async function resolveStandalonePlan(input, terminal) {
19007
19647
  const { argv, shellFallbackWarning } = buildFreshArgv(agent, prompt);
19008
19648
  return {
19009
19649
  terminal,
19010
- cwd: expanded,
19650
+ cwd: spawnCwd,
19011
19651
  argv,
19012
19652
  env: process.env,
19013
19653
  agentId: agent.id,
@@ -19174,8 +19814,8 @@ async function resolveSessionPlan(input, terminal) {
19174
19814
 
19175
19815
  // src/launch/execute.ts
19176
19816
  import { spawn as spawn3 } from "child_process";
19177
- import { homedir as homedir6 } from "os";
19178
- import { basename as basename5, join as join8, resolve as resolve29 } from "path";
19817
+ import { homedir as homedir8 } from "os";
19818
+ import { basename as basename6, join as join10, resolve as resolve29 } from "path";
19179
19819
  init_fs();
19180
19820
  init_config2();
19181
19821
  init_session_id();
@@ -19195,7 +19835,7 @@ var TerminalNotFoundError = class extends Error {
19195
19835
  var realSpawn = (command, args, options) => spawn3(command, args, options);
19196
19836
  var WRAPPER_COMMANDS = /* @__PURE__ */ new Set(["osascript", "open", "sh"]);
19197
19837
  function isWrapperCommand(command) {
19198
- return WRAPPER_COMMANDS.has(basename5(command));
19838
+ return WRAPPER_COMMANDS.has(basename6(command));
19199
19839
  }
19200
19840
  var WRAPPER_EXIT_TIMEOUT_MS = 1500;
19201
19841
  async function executeLaunchPlan(plan, spawnFn = realSpawn) {
@@ -19281,7 +19921,7 @@ async function registerLaunchAtBirth(plan, child) {
19281
19921
  const sessionId = plan.session?.sessionId ?? null;
19282
19922
  const procStart = captureProcessStartedAt(pid);
19283
19923
  const envDir = process.env.SYNTAUR_RUNTIME_SESSIONS_DIR;
19284
- const markerDir = envDir && envDir.length > 0 ? envDir : join8(homedir6(), ".syntaur", "runtime", "sessions");
19924
+ const markerDir = envDir && envDir.length > 0 ? envDir : join10(homedir8(), ".syntaur", "runtime", "sessions");
19285
19925
  writeRuntimeMarker(
19286
19926
  pid,
19287
19927
  {
@@ -19470,7 +20110,7 @@ function appleScriptString(value) {
19470
20110
  init_paths();
19471
20111
  init_fs();
19472
20112
  import { fileURLToPath } from "url";
19473
- import { dirname as dirname6, resolve as resolve30, join as join9 } from "path";
20113
+ import { dirname as dirname7, resolve as resolve30, join as join11 } from "path";
19474
20114
  import { realpathSync, readFileSync as readFileSync3, mkdirSync as mkdirSync2 } from "fs";
19475
20115
 
19476
20116
  // src/dashboard/api-launch-preflight.ts
@@ -19863,21 +20503,78 @@ function createWorkspaceVisibilityConfigRouter() {
19863
20503
  return router;
19864
20504
  }
19865
20505
 
20506
+ // src/dashboard/api-agent-discovery-config.ts
20507
+ init_config2();
20508
+ import { Router as Router11 } from "express";
20509
+ function createAgentDiscoveryConfigRouter() {
20510
+ const router = Router11();
20511
+ router.get("/", async (_req, res) => {
20512
+ try {
20513
+ const config = await readConfig();
20514
+ res.json({
20515
+ agentDiscovery: config.agentDiscovery,
20516
+ standaloneDefaultCwd: config.standaloneDefaultCwd
20517
+ });
20518
+ } catch (error) {
20519
+ console.error("Error getting agent-discovery config:", error);
20520
+ res.status(500).json({ error: "Failed to get agent-discovery config" });
20521
+ }
20522
+ });
20523
+ router.post("/", async (req2, res) => {
20524
+ try {
20525
+ const b = req2.body ?? {};
20526
+ const d = b.agentDiscovery ?? {};
20527
+ let roots;
20528
+ try {
20529
+ roots = (Array.isArray(d.roots) ? d.roots : []).filter((r) => typeof r === "string" && r.trim() !== "").map((r) => {
20530
+ requireAbsolutePath(r, "root");
20531
+ return r.trim();
20532
+ });
20533
+ } catch (err) {
20534
+ res.status(400).json({ error: err instanceof Error ? err.message : "invalid root" });
20535
+ return;
20536
+ }
20537
+ const cfg = {
20538
+ // Default-true semantics: only an explicit `false` turns a source off.
20539
+ claudeGlobal: d.claudeGlobal !== false,
20540
+ claudeProject: d.claudeProject !== false,
20541
+ directory: d.directory !== false,
20542
+ roots: roots.length > 0 ? roots : ["~"]
20543
+ };
20544
+ let cwd = null;
20545
+ if (typeof b.standaloneDefaultCwd === "string" && b.standaloneDefaultCwd.trim()) {
20546
+ try {
20547
+ cwd = requireAbsolutePath(b.standaloneDefaultCwd, "standaloneDefaultCwd");
20548
+ } catch (err) {
20549
+ res.status(400).json({ error: err instanceof Error ? err.message : "invalid cwd" });
20550
+ return;
20551
+ }
20552
+ }
20553
+ await writeAgentDiscoveryConfig(cfg, cwd);
20554
+ res.json({ agentDiscovery: cfg, standaloneDefaultCwd: cwd });
20555
+ } catch (error) {
20556
+ console.error("Error saving agent-discovery config:", error);
20557
+ res.status(500).json({ error: "Failed to save agent-discovery config" });
20558
+ }
20559
+ });
20560
+ return router;
20561
+ }
20562
+
19866
20563
  // src/dashboard/api-status-config.ts
19867
20564
  init_config2();
19868
20565
  init_fact_registry();
19869
20566
  init_state_machine();
19870
20567
  init_api();
19871
20568
  init_derive();
19872
- import { Router as Router11 } from "express";
20569
+ import { Router as Router12 } from "express";
19873
20570
 
19874
20571
  // src/utils/status-config-resolution.ts
19875
20572
  init_frontmatter();
19876
20573
  init_event_emit();
19877
20574
  init_assignment_walk();
19878
20575
  init_timestamp();
19879
- import { readFile as readFile21, writeFile as writeFile5, rm as rm2 } from "fs/promises";
19880
- import { dirname as dirname7 } from "path";
20576
+ import { readFile as readFile23, writeFile as writeFile6, rm as rm2 } from "fs/promises";
20577
+ import { dirname as dirname8 } from "path";
19881
20578
  var StatusResolutionError = class extends Error {
19882
20579
  constructor(message, code) {
19883
20580
  super(message);
@@ -19895,7 +20592,7 @@ async function scanAssignmentsByStatus(projectsDir, standaloneDir, ids) {
19895
20592
  const assignmentPath = `${entry.assignmentDir}/assignment.md`;
19896
20593
  let content;
19897
20594
  try {
19898
- content = await readFile21(assignmentPath, "utf-8");
20595
+ content = await readFile23(assignmentPath, "utf-8");
19899
20596
  } catch (err) {
19900
20597
  const code = err?.code;
19901
20598
  if (code === "ENOENT") {
@@ -19958,7 +20655,7 @@ async function applyStatusResolutions(resolutions, affected, validTargets) {
19958
20655
  const list = affected.get(r.id) ?? [];
19959
20656
  for (const a of list) {
19960
20657
  try {
19961
- const content = await readFile21(a.path, "utf-8");
20658
+ const content = await readFile23(a.path, "utf-8");
19962
20659
  buffer.set(a.path, content);
19963
20660
  } catch (err) {
19964
20661
  const code = err?.code;
@@ -19987,7 +20684,7 @@ async function applyStatusResolutions(resolutions, affected, validTargets) {
19987
20684
  for (const a of list) {
19988
20685
  let current;
19989
20686
  try {
19990
- current = await readFile21(a.path, "utf-8");
20687
+ current = await readFile23(a.path, "utf-8");
19991
20688
  } catch (err) {
19992
20689
  const code = err?.code;
19993
20690
  if (code === "ENOENT") {
@@ -20010,7 +20707,7 @@ async function applyStatusResolutions(resolutions, affected, validTargets) {
20010
20707
  }),
20011
20708
  { at: now, from: r.id, to: r.target, command: "remap", by: null }
20012
20709
  );
20013
- await writeFile5(a.path, next, "utf-8");
20710
+ await writeFile6(a.path, next, "utf-8");
20014
20711
  pendingRemapEvents.push({
20015
20712
  assignmentId: fm.id,
20016
20713
  projectSlug: a.projectSlug,
@@ -20028,7 +20725,7 @@ async function applyStatusResolutions(resolutions, affected, validTargets) {
20028
20725
  for (const p of writtenPaths) {
20029
20726
  const original = buffer.get(p);
20030
20727
  if (original !== void 0) {
20031
- await writeFile5(p, original, "utf-8").catch((rollbackErr) => {
20728
+ await writeFile6(p, original, "utf-8").catch((rollbackErr) => {
20032
20729
  console.error(
20033
20730
  `status-config-resolution: rollback write failed for ${p}:`,
20034
20731
  rollbackErr
@@ -20058,7 +20755,7 @@ async function applyStatusResolutions(resolutions, affected, validTargets) {
20058
20755
  const list = affected.get(r.id) ?? [];
20059
20756
  for (const a of list) {
20060
20757
  try {
20061
- const current = await readFile21(a.path, "utf-8");
20758
+ const current = await readFile23(a.path, "utf-8");
20062
20759
  const fm = parseAssignmentFrontmatter(current);
20063
20760
  if (fm.status !== r.id) {
20064
20761
  console.warn(
@@ -20069,7 +20766,7 @@ async function applyStatusResolutions(resolutions, affected, validTargets) {
20069
20766
  } catch {
20070
20767
  continue;
20071
20768
  }
20072
- const assignmentDir = dirname7(a.path);
20769
+ const assignmentDir = dirname8(a.path);
20073
20770
  try {
20074
20771
  await rm2(assignmentDir, { recursive: true, force: true });
20075
20772
  deleted++;
@@ -20212,7 +20909,7 @@ function mapResolutionErrorToHttp(err, applied) {
20212
20909
  }
20213
20910
  }
20214
20911
  function createStatusConfigRouter(projectsDir, assignmentsDir2) {
20215
- const router = Router11();
20912
+ const router = Router12();
20216
20913
  installRecordsInvalidation(router);
20217
20914
  router.get("/", async (_req, res) => {
20218
20915
  try {
@@ -20532,7 +21229,7 @@ function createStatusConfigRouter(projectsDir, assignmentsDir2) {
20532
21229
  }
20533
21230
 
20534
21231
  // src/dashboard/api-leases.ts
20535
- import { Router as Router12 } from "express";
21232
+ import { Router as Router13 } from "express";
20536
21233
 
20537
21234
  // src/db/leases-db.ts
20538
21235
  init_paths();
@@ -20714,7 +21411,7 @@ function forceReleaseLease(lease_id) {
20714
21411
 
20715
21412
  // src/dashboard/api-leases.ts
20716
21413
  function createLeasesRouter(broadcast) {
20717
- const router = Router12();
21414
+ const router = Router13();
20718
21415
  router.get("/", (_req, res) => {
20719
21416
  try {
20720
21417
  initLeasesDb();
@@ -20770,14 +21467,14 @@ function createLeasesRouter(broadcast) {
20770
21467
 
20771
21468
  // src/dashboard/api-schedules.ts
20772
21469
  init_timestamp();
20773
- import { Router as Router13 } from "express";
21470
+ import { Router as Router14 } from "express";
20774
21471
 
20775
21472
  // src/schedules/store.ts
20776
21473
  init_fs();
20777
21474
  init_paths();
20778
21475
  init_timestamp();
20779
21476
  import { randomUUID as randomUUID4 } from "crypto";
20780
- import { readFile as readFile22, readdir as readdir16, rm as rm3 } from "fs/promises";
21477
+ import { readFile as readFile24, readdir as readdir17, rm as rm3 } from "fs/promises";
20781
21478
  import { resolve as resolve32 } from "path";
20782
21479
  function schedulesDir() {
20783
21480
  const override3 = process.env.SYNTAUR_SCHEDULES_DIR;
@@ -20910,7 +21607,7 @@ function validateJob(raw2) {
20910
21607
  async function readJob(id) {
20911
21608
  const path = jobPath(id);
20912
21609
  if (!await fileExists(path)) return null;
20913
- return parseJobFile(await readFile22(path, "utf-8"));
21610
+ return parseJobFile(await readFile24(path, "utf-8"));
20914
21611
  }
20915
21612
  async function writeJob(job) {
20916
21613
  const next = { ...job, updatedAt: nowTimestamp() };
@@ -20920,7 +21617,7 @@ async function writeJob(job) {
20920
21617
  async function listJobs() {
20921
21618
  let names;
20922
21619
  try {
20923
- names = await readdir16(schedulesDir());
21620
+ names = await readdir17(schedulesDir());
20924
21621
  } catch {
20925
21622
  return [];
20926
21623
  }
@@ -20928,7 +21625,7 @@ async function listJobs() {
20928
21625
  for (const name of names) {
20929
21626
  if (!name.endsWith(".md")) continue;
20930
21627
  try {
20931
- jobs.push(parseJobFile(await readFile22(resolve32(schedulesDir(), name), "utf-8")));
21628
+ jobs.push(parseJobFile(await readFile24(resolve32(schedulesDir(), name), "utf-8")));
20932
21629
  } catch {
20933
21630
  }
20934
21631
  }
@@ -20938,8 +21635,8 @@ async function listJobs() {
20938
21635
 
20939
21636
  // src/schedules/event-log.ts
20940
21637
  init_timestamp();
20941
- import { appendFile, readFile as readFile23, mkdir as mkdir3 } from "fs/promises";
20942
- import { dirname as dirname8, resolve as resolve33 } from "path";
21638
+ import { appendFile, readFile as readFile25, mkdir as mkdir4 } from "fs/promises";
21639
+ import { dirname as dirname9, resolve as resolve33 } from "path";
20943
21640
  function eventLogPath(jobId) {
20944
21641
  if (!/^[A-Za-z0-9_-]+$/.test(jobId)) {
20945
21642
  throw new Error(`Unsafe schedule id: ${JSON.stringify(jobId)}`);
@@ -20949,7 +21646,7 @@ function eventLogPath(jobId) {
20949
21646
  async function appendEvent(jobId, type, data) {
20950
21647
  const event = { type, at: nowTimestamp(), ...data ? { data } : {} };
20951
21648
  const path = eventLogPath(jobId);
20952
- await mkdir3(dirname8(path), { recursive: true });
21649
+ await mkdir4(dirname9(path), { recursive: true });
20953
21650
  await appendFile(path, `${JSON.stringify(event)}
20954
21651
  `, "utf-8");
20955
21652
  return event;
@@ -20957,7 +21654,7 @@ async function appendEvent(jobId, type, data) {
20957
21654
  async function readEvents(jobId) {
20958
21655
  let content;
20959
21656
  try {
20960
- content = await readFile23(eventLogPath(jobId), "utf-8");
21657
+ content = await readFile25(eventLogPath(jobId), "utf-8");
20961
21658
  } catch {
20962
21659
  return [];
20963
21660
  }
@@ -20976,7 +21673,7 @@ async function readEvents(jobId) {
20976
21673
 
20977
21674
  // src/schedules/attempt.ts
20978
21675
  import { createHash as createHash3 } from "crypto";
20979
- import { open as open4, readFile as readFile24, stat as stat5, unlink as unlink6 } from "fs/promises";
21676
+ import { open as open4, readFile as readFile26, stat as stat6, unlink as unlink6 } from "fs/promises";
20980
21677
  import { resolve as resolve34 } from "path";
20981
21678
 
20982
21679
  // src/schedules/triggers.ts
@@ -21165,7 +21862,7 @@ async function acquireJobLock(id) {
21165
21862
  await handle.close();
21166
21863
  return async () => {
21167
21864
  try {
21168
- const current = await readFile24(lockPath, "utf-8");
21865
+ const current = await readFile26(lockPath, "utf-8");
21169
21866
  if (current === token) await unlink6(lockPath);
21170
21867
  } catch {
21171
21868
  }
@@ -21174,7 +21871,7 @@ async function acquireJobLock(id) {
21174
21871
  const code = err.code;
21175
21872
  if (code !== "EEXIST") throw err;
21176
21873
  try {
21177
- const info = await stat5(lockPath);
21874
+ const info = await stat6(lockPath);
21178
21875
  if (Date.now() - info.mtimeMs > LOCK_STALE_MS2) {
21179
21876
  await unlink6(lockPath).catch(() => {
21180
21877
  });
@@ -21513,7 +22210,7 @@ function canFire(job, deps) {
21513
22210
  init_agent_sessions();
21514
22211
  init_session_db();
21515
22212
  function createSchedulesRouter(broadcast) {
21516
- const router = Router13();
22213
+ const router = Router14();
21517
22214
  const notify = () => broadcast?.({ type: "schedules-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
21518
22215
  router.get("/", async (_req, res) => {
21519
22216
  try {
@@ -21638,20 +22335,20 @@ init_config2();
21638
22335
  init_paths();
21639
22336
  init_assignment_resolver();
21640
22337
  init_frontmatter();
21641
- import { readFile as readFile25 } from "fs/promises";
22338
+ import { readFile as readFile27 } from "fs/promises";
21642
22339
  import { resolve as resolve36 } from "path";
21643
22340
 
21644
22341
  // src/schedules/launch-ack.ts
21645
22342
  init_session_id();
21646
22343
  import { readdirSync as readdirSync2 } from "fs";
21647
- import { homedir as homedir7 } from "os";
21648
- import { join as join10 } from "path";
22344
+ import { homedir as homedir9 } from "os";
22345
+ import { join as join12 } from "path";
21649
22346
  function defaultSleep(ms) {
21650
22347
  return new Promise((r) => setTimeout(r, ms));
21651
22348
  }
21652
22349
  function runtimeSessionsDir() {
21653
22350
  const override3 = process.env.SYNTAUR_RUNTIME_SESSIONS_DIR;
21654
- return override3 && override3.length > 0 ? override3 : join10(homedir7(), ".syntaur", "runtime", "sessions");
22351
+ return override3 && override3.length > 0 ? override3 : join12(homedir9(), ".syntaur", "runtime", "sessions");
21655
22352
  }
21656
22353
  function defaultAckProbe(handle) {
21657
22354
  const dir = runtimeSessionsDir();
@@ -21699,7 +22396,7 @@ async function defaultReadAssignment(assignmentId) {
21699
22396
  const resolved = await resolveAssignmentById(projectsDir, assignmentsDir(), assignmentId);
21700
22397
  if (!resolved) return null;
21701
22398
  try {
21702
- const content = await readFile25(resolve36(resolved.assignmentDir, "assignment.md"), "utf-8");
22399
+ const content = await readFile27(resolve36(resolved.assignmentDir, "assignment.md"), "utf-8");
21703
22400
  return parseAssignmentFrontmatter(content);
21704
22401
  } catch {
21705
22402
  return null;
@@ -21786,7 +22483,7 @@ async function runTick(deps = {}) {
21786
22483
  }
21787
22484
 
21788
22485
  // src/dashboard/api-usage.ts
21789
- import { Router as Router14 } from "express";
22486
+ import { Router as Router15 } from "express";
21790
22487
 
21791
22488
  // src/db/usage-db.ts
21792
22489
  init_paths();
@@ -22236,7 +22933,7 @@ function projectWindowCosts(opts) {
22236
22933
 
22237
22934
  // src/dashboard/api-usage.ts
22238
22935
  function createUsageRouter(projectsDir, assignmentsDir2) {
22239
- const router = Router14();
22936
+ const router = Router15();
22240
22937
  router.get("/facets", (_req, res) => {
22241
22938
  try {
22242
22939
  initUsageDb();
@@ -22477,11 +23174,11 @@ function projectAssignmentRollup(projectSlug, rows, common) {
22477
23174
  init_fs();
22478
23175
  init_parser();
22479
23176
  init_events_db();
22480
- import { Router as Router15 } from "express";
23177
+ import { Router as Router16 } from "express";
22481
23178
  import { resolve as resolve38 } from "path";
22482
- import { readFile as readFile26 } from "fs/promises";
23179
+ import { readFile as readFile28 } from "fs/promises";
22483
23180
  function createEventsRouter(projectsDir, assignmentsDir2) {
22484
- const router = Router15();
23181
+ const router = Router16();
22485
23182
  router.get("/projects/:slug/assignments/:aslug/events", async (req2, res) => {
22486
23183
  try {
22487
23184
  const { slug, aslug } = req2.params;
@@ -22518,7 +23215,7 @@ function createEventsRouter(projectsDir, assignmentsDir2) {
22518
23215
  }
22519
23216
  async function readAssignmentId2(assignmentMdPath) {
22520
23217
  if (!await fileExists(assignmentMdPath)) return null;
22521
- const content = await readFile26(assignmentMdPath, "utf-8");
23218
+ const content = await readFile28(assignmentMdPath, "utf-8");
22522
23219
  const [fm] = extractFrontmatter2(content);
22523
23220
  const id = getField(fm, "id");
22524
23221
  return id && id.length > 0 ? id : null;
@@ -22555,7 +23252,7 @@ function parseFilters(query) {
22555
23252
  }
22556
23253
 
22557
23254
  // src/dashboard/api-inbox.ts
22558
- import { Router as Router16 } from "express";
23255
+ import { Router as Router17 } from "express";
22559
23256
 
22560
23257
  // src/inbox/index.ts
22561
23258
  init_fs();
@@ -22564,7 +23261,7 @@ init_parser();
22564
23261
  init_facts();
22565
23262
  init_state_machine();
22566
23263
  import { resolve as resolve39 } from "path";
22567
- import { readFile as readFile27 } from "fs/promises";
23264
+ import { readFile as readFile29 } from "fs/promises";
22568
23265
 
22569
23266
  // src/inbox/types.ts
22570
23267
  var INBOX_CATEGORIES = [
@@ -22744,7 +23441,7 @@ async function computeInbox(opts) {
22744
23441
  if (opts.project !== void 0 && entry.projectSlug !== opts.project) continue;
22745
23442
  let parsed;
22746
23443
  try {
22747
- const content = await readFile27(resolve39(entry.assignmentDir, "assignment.md"), "utf-8");
23444
+ const content = await readFile29(resolve39(entry.assignmentDir, "assignment.md"), "utf-8");
22748
23445
  parsed = parseAssignmentFull(content);
22749
23446
  } catch {
22750
23447
  continue;
@@ -22790,7 +23487,7 @@ async function computeInbox(opts) {
22790
23487
  const commentsPath = resolve39(entry.assignmentDir, "comments.md");
22791
23488
  if (await fileExists(commentsPath)) {
22792
23489
  try {
22793
- const content = await readFile27(commentsPath, "utf-8");
23490
+ const content = await readFile29(commentsPath, "utf-8");
22794
23491
  const parsedComments = parseComments(content);
22795
23492
  for (const c of unresolvedQuestions(parsedComments.entries)) {
22796
23493
  const since = resolveSince("question", parsed, now, c);
@@ -22846,7 +23543,7 @@ async function computeInbox(opts) {
22846
23543
  init_api();
22847
23544
  init_config2();
22848
23545
  function createInboxRouter(projectsDir, assignmentsDir2) {
22849
- const router = Router16();
23546
+ const router = Router17();
22850
23547
  router.get("/inbox", async (req2, res) => {
22851
23548
  try {
22852
23549
  const project = typeof req2.query.project === "string" && req2.query.project.length > 0 ? req2.query.project : void 0;
@@ -22900,9 +23597,9 @@ init_parser();
22900
23597
  init_slug();
22901
23598
  init_timestamp();
22902
23599
  init_fs();
22903
- import { Router as Router17 } from "express";
23600
+ import { Router as Router18 } from "express";
22904
23601
  import { resolve as resolve40 } from "path";
22905
- import { readFile as readFile28 } from "fs/promises";
23602
+ import { readFile as readFile30 } from "fs/promises";
22906
23603
  init_playbooks();
22907
23604
  function statusForPlaybookError(code) {
22908
23605
  switch (code) {
@@ -22917,7 +23614,7 @@ function statusForPlaybookError(code) {
22917
23614
  }
22918
23615
  }
22919
23616
  function createPlaybooksRouter(playbooksDir2) {
22920
- const router = Router17();
23617
+ const router = Router18();
22921
23618
  router.get("/", async (_req, res) => {
22922
23619
  try {
22923
23620
  const playbooks = await listPlaybooks(playbooksDir2);
@@ -22985,7 +23682,7 @@ function createPlaybooksRouter(playbooksDir2) {
22985
23682
  return;
22986
23683
  }
22987
23684
  const filePath = resolve40(playbooksDir2, resolved.filename);
22988
- const content = await readFile28(filePath, "utf-8");
23685
+ const content = await readFile30(filePath, "utf-8");
22989
23686
  res.json({
22990
23687
  documentType: "playbook",
22991
23688
  title: `Edit Playbook: ${resolved.slug}`,
@@ -23081,10 +23778,10 @@ init_fs_migration();
23081
23778
  init_parser2();
23082
23779
  init_fs();
23083
23780
  init_paths();
23084
- import { Router as Router19 } from "express";
23085
- import { readdir as readdir18 } from "fs/promises";
23086
- import { resolve as resolvePath, dirname as dirname10 } from "path";
23087
- import { rename as rename6, mkdir as mkdir5 } from "fs/promises";
23781
+ import { Router as Router20 } from "express";
23782
+ import { readdir as readdir19 } from "fs/promises";
23783
+ import { resolve as resolvePath, dirname as dirname11 } from "path";
23784
+ import { rename as rename6, mkdir as mkdir6 } from "fs/promises";
23088
23785
  init_slug();
23089
23786
 
23090
23787
  // src/utils/promote-todos.ts
@@ -23429,8 +24126,8 @@ init_api();
23429
24126
  import { raw } from "express";
23430
24127
 
23431
24128
  // src/todos/attachments.ts
23432
- import { mkdir as mkdir4, readdir as readdir17, stat as stat6, rename as rename5, rm as rm4, unlink as unlink7, writeFile as writeFile6, cp } from "fs/promises";
23433
- import { resolve as resolve42, basename as basename6, dirname as dirname9, extname } from "path";
24129
+ import { mkdir as mkdir5, readdir as readdir18, stat as stat7, rename as rename5, rm as rm4, unlink as unlink7, writeFile as writeFile7, cp } from "fs/promises";
24130
+ import { resolve as resolve42, basename as basename7, dirname as dirname10, extname } from "path";
23434
24131
 
23435
24132
  // src/utils/proof-artifact-id.ts
23436
24133
  import { randomBytes as randomBytes2 } from "crypto";
@@ -23503,7 +24200,7 @@ function isSafeInlineMime(mime) {
23503
24200
  return SAFE_INLINE_MIME.has(mime);
23504
24201
  }
23505
24202
  function sanitizeAttachmentName(name) {
23506
- let n = basename6(name || "").replace(/["'\\/]/g, "_");
24203
+ let n = basename7(name || "").replace(/["'\\/]/g, "_");
23507
24204
  n = Array.from(n, (ch) => {
23508
24205
  const code = ch.charCodeAt(0);
23509
24206
  return code < 32 || code === 127 ? "_" : ch;
@@ -23526,17 +24223,17 @@ function attachmentDirFor(todosDir2, scopeId, todoId) {
23526
24223
  }
23527
24224
  async function dirExists(p) {
23528
24225
  try {
23529
- return (await stat6(p)).isDirectory();
24226
+ return (await stat7(p)).isDirectory();
23530
24227
  } catch {
23531
24228
  return false;
23532
24229
  }
23533
24230
  }
23534
24231
  async function writeAttachment(todosDir2, scopeId, todoId, originalName, bytes) {
23535
24232
  const dir = attachmentDirFor(todosDir2, scopeId, todoId);
23536
- await mkdir4(dir, { recursive: true });
24233
+ await mkdir5(dir, { recursive: true });
23537
24234
  const id = generateArtifactId();
23538
24235
  const filename = sanitizeAttachmentName(originalName);
23539
- await writeFile6(resolve42(dir, `${id}__${filename}`), bytes);
24236
+ await writeFile7(resolve42(dir, `${id}__${filename}`), bytes);
23540
24237
  return {
23541
24238
  id,
23542
24239
  filename,
@@ -23549,7 +24246,7 @@ async function listAttachments(todosDir2, scopeId, todoId) {
23549
24246
  const dir = attachmentDirFor(todosDir2, scopeId, todoId);
23550
24247
  let names;
23551
24248
  try {
23552
- names = await readdir17(dir);
24249
+ names = await readdir18(dir);
23553
24250
  } catch {
23554
24251
  return [];
23555
24252
  }
@@ -23561,7 +24258,7 @@ async function listAttachments(todosDir2, scopeId, todoId) {
23561
24258
  if (!ATTACHMENT_ID_RE.test(id)) continue;
23562
24259
  const filename = stored.slice(sep2 + 2);
23563
24260
  try {
23564
- const st = await stat6(resolve42(dir, stored));
24261
+ const st = await stat7(resolve42(dir, stored));
23565
24262
  if (!st.isFile()) continue;
23566
24263
  out.push({ id, filename, mime: mimeForName(filename), size: st.size, createdAt: st.mtime.toISOString() });
23567
24264
  } catch {
@@ -23575,7 +24272,7 @@ async function readScopeAttachments(todosDir2, scopeId) {
23575
24272
  const scopeDir = resolve42(attachmentsRootDir(todosDir2), scopeId);
23576
24273
  let todoIds;
23577
24274
  try {
23578
- todoIds = await readdir17(scopeDir);
24275
+ todoIds = await readdir18(scopeDir);
23579
24276
  } catch {
23580
24277
  return {};
23581
24278
  }
@@ -23592,7 +24289,7 @@ async function resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId) {
23592
24289
  const dir = attachmentDirFor(todosDir2, scopeId, todoId);
23593
24290
  let names;
23594
24291
  try {
23595
- names = await readdir17(dir);
24292
+ names = await readdir18(dir);
23596
24293
  } catch {
23597
24294
  return null;
23598
24295
  }
@@ -23620,7 +24317,7 @@ async function moveAttachments(srcTodosDir, srcScopeId, dstTodosDir, dstScopeId,
23620
24317
  const src = attachmentDirFor(srcTodosDir, srcScopeId, todoId);
23621
24318
  if (!await dirExists(src)) return;
23622
24319
  const dst = attachmentDirFor(dstTodosDir, dstScopeId, todoId);
23623
- await mkdir4(dirname9(dst), { recursive: true });
24320
+ await mkdir5(dirname10(dst), { recursive: true });
23624
24321
  try {
23625
24322
  await rename5(src, dst);
23626
24323
  } catch (err) {
@@ -23762,7 +24459,7 @@ function touchItem3(item) {
23762
24459
  item.updatedAt = now;
23763
24460
  }
23764
24461
  function createTodosRouter(todosDir2, broadcast, projectsDir) {
23765
- const router = Router19();
24462
+ const router = Router20();
23766
24463
  installRecordsInvalidation(router);
23767
24464
  function broadcastUpdate() {
23768
24465
  broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -23896,7 +24593,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
23896
24593
  router.get("/", async (_req, res) => {
23897
24594
  try {
23898
24595
  await ensureDir(todosDir2);
23899
- const files = await readdir18(todosDir2).catch(() => []);
24596
+ const files = await readdir19(todosDir2).catch(() => []);
23900
24597
  const workspaces = [];
23901
24598
  for (const file of files) {
23902
24599
  if (typeof file !== "string") continue;
@@ -24017,7 +24714,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
24017
24714
  try {
24018
24715
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
24019
24716
  const { resolve: resolve49 } = await import("path");
24020
- const { readFile: readFile32 } = await import("fs/promises");
24717
+ const { readFile: readFile34 } = await import("fs/promises");
24021
24718
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
24022
24719
  const workspace = getWorkspaceParam(req2.params.workspace);
24023
24720
  const outcome = await wsLock(workspace, async () => {
@@ -24036,7 +24733,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
24036
24733
  await ensureDir(resolve49(todosDir2, "archive"));
24037
24734
  let archContent = "";
24038
24735
  if (await fileExists(archFile)) {
24039
- archContent = await readFile32(archFile, "utf-8");
24736
+ archContent = await readFile34(archFile, "utf-8");
24040
24737
  archContent = archContent.trimEnd() + "\n\n";
24041
24738
  } else {
24042
24739
  archContent = `---
@@ -24337,7 +25034,7 @@ workspace: ${workspace}
24337
25034
  const { readConfig: readConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
24338
25035
  const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
24339
25036
  const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
24340
- const { readFile: readFile32 } = await import("fs/promises");
25037
+ const { readFile: readFile34 } = await import("fs/promises");
24341
25038
  const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
24342
25039
  const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
24343
25040
  let assignmentRef;
@@ -24358,7 +25055,7 @@ workspace: ${workspace}
24358
25055
  }
24359
25056
  const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
24360
25057
  if (!await fileExists2(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
24361
- let content = await readFile32(assignmentMdPath, "utf-8");
25058
+ let content = await readFile34(assignmentMdPath, "utf-8");
24362
25059
  content = appendTodosToAssignmentBody2(
24363
25060
  content,
24364
25061
  items.map((it) => ({
@@ -24475,7 +25172,7 @@ workspace: ${workspace}
24475
25172
  return { status: 409, error: "attachments already exist in target" };
24476
25173
  }
24477
25174
  if (item.planDir && newPlanDir) {
24478
- await mkdir5(dirname10(newPlanDir), { recursive: true });
25175
+ await mkdir6(dirname11(newPlanDir), { recursive: true });
24479
25176
  await rename6(item.planDir, newPlanDir);
24480
25177
  item.planDir = newPlanDir;
24481
25178
  }
@@ -24552,9 +25249,9 @@ init_parser2();
24552
25249
  init_fs();
24553
25250
  init_paths();
24554
25251
  init_slug();
24555
- import { Router as Router20 } from "express";
24556
- import { mkdir as mkdir6, readFile as readFile29, rename as rename7 } from "fs/promises";
24557
- import { resolve as resolve43, dirname as dirname11 } from "path";
25252
+ import { Router as Router21 } from "express";
25253
+ import { mkdir as mkdir7, readFile as readFile31, rename as rename7 } from "fs/promises";
25254
+ import { resolve as resolve43, dirname as dirname12 } from "path";
24558
25255
  init_api();
24559
25256
  var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
24560
25257
  function touchItem4(item) {
@@ -24575,7 +25272,7 @@ async function projectExists(projectsDir, slug) {
24575
25272
  async function ensureProjectTodosDir(projectsDir, slug) {
24576
25273
  const todosDir2 = projectTodosDir(projectsDir, slug);
24577
25274
  try {
24578
- await mkdir6(todosDir2, { recursive: false });
25275
+ await mkdir7(todosDir2, { recursive: false });
24579
25276
  } catch (err) {
24580
25277
  const code = err.code;
24581
25278
  if (code === "EEXIST") return;
@@ -24587,7 +25284,7 @@ async function ensureProjectTodosDir(projectsDir, slug) {
24587
25284
  throw err;
24588
25285
  }
24589
25286
  try {
24590
- await mkdir6(resolve43(todosDir2, "archive"), { recursive: false });
25287
+ await mkdir7(resolve43(todosDir2, "archive"), { recursive: false });
24591
25288
  } catch (err) {
24592
25289
  const code = err.code;
24593
25290
  if (code === "EEXIST") return;
@@ -24603,7 +25300,7 @@ function notFound(res, slug) {
24603
25300
  res.status(404).json({ error: `Project "${slug}" not found` });
24604
25301
  }
24605
25302
  function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
24606
- const router = Router20({ mergeParams: true });
25303
+ const router = Router21({ mergeParams: true });
24607
25304
  installRecordsInvalidation(router);
24608
25305
  function broadcastUpdate(projectSlug) {
24609
25306
  broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -24795,7 +25492,7 @@ function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
24795
25492
  const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
24796
25493
  let archContent = "";
24797
25494
  if (await fileExists(archFile)) {
24798
- archContent = await readFile29(archFile, "utf-8");
25495
+ archContent = await readFile31(archFile, "utf-8");
24799
25496
  archContent = archContent.trimEnd() + "\n\n";
24800
25497
  } else {
24801
25498
  archContent = `---
@@ -25276,7 +25973,7 @@ workspace: ${slug}
25276
25973
  }
25277
25974
  const assignmentMdPath = resolve43(assignmentDir, "assignment.md");
25278
25975
  if (!await fileExists(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
25279
- let content = await readFile29(assignmentMdPath, "utf-8");
25976
+ let content = await readFile31(assignmentMdPath, "utf-8");
25280
25977
  content = appendTodosToAssignmentBody2(
25281
25978
  content,
25282
25979
  items.map((it) => ({
@@ -25415,7 +26112,7 @@ workspace: ${slug}
25415
26112
  return { status: 409, error: "attachments already exist in target" };
25416
26113
  }
25417
26114
  if (item.planDir && newPlanDir) {
25418
- await mkdir6(dirname11(newPlanDir), { recursive: true });
26115
+ await mkdir7(dirname12(newPlanDir), { recursive: true });
25419
26116
  await rename7(item.planDir, newPlanDir);
25420
26117
  item.planDir = newPlanDir;
25421
26118
  }
@@ -25478,8 +26175,8 @@ workspace: ${slug}
25478
26175
  }
25479
26176
 
25480
26177
  // src/dashboard/api-bundles.ts
25481
- import { Router as Router21 } from "express";
25482
- import { readdir as readdir19 } from "fs/promises";
26178
+ import { Router as Router22 } from "express";
26179
+ import { readdir as readdir20 } from "fs/promises";
25483
26180
 
25484
26181
  // src/todos/bundle-parser.ts
25485
26182
  init_parser();
@@ -25487,7 +26184,7 @@ init_fs();
25487
26184
  init_paths();
25488
26185
  init_parser2();
25489
26186
  import { randomBytes as randomBytes3 } from "crypto";
25490
- import { readFile as readFile30 } from "fs/promises";
26187
+ import { readFile as readFile32 } from "fs/promises";
25491
26188
  var BUNDLE_ID_REGEX = /^[a-f0-9]{4}$/;
25492
26189
  var SCOPE_VALUES = /* @__PURE__ */ new Set(["workspace", "project", "global"]);
25493
26190
  var SCOPE_ID_REGEX = /^[a-z0-9_][a-z0-9_-]*$/;
@@ -25554,7 +26251,7 @@ function parseBundles(content) {
25554
26251
  async function readBundles(todosDir2) {
25555
26252
  const path = bundlesPath(todosDir2);
25556
26253
  if (!await fileExists(path)) return [];
25557
- const content = await readFile30(path, "utf-8");
26254
+ const content = await readFile32(path, "utf-8");
25558
26255
  return parseBundles(content).bundles;
25559
26256
  }
25560
26257
 
@@ -25589,7 +26286,7 @@ function annotate(bundle, items) {
25589
26286
  }
25590
26287
  function createBundlesRouter(todosDir2, broadcast) {
25591
26288
  void broadcast;
25592
- const router = Router21();
26289
+ const router = Router22();
25593
26290
  function validateWorkspace(req2, res, next) {
25594
26291
  const workspace = getWorkspaceParam2(req2.params.workspace);
25595
26292
  if (workspace && !WORKSPACE_REGEX3.test(workspace)) {
@@ -25603,7 +26300,7 @@ function createBundlesRouter(todosDir2, broadcast) {
25603
26300
  try {
25604
26301
  await ensureDir(todosDir2);
25605
26302
  const bundles = await readBundles(todosDir2);
25606
- const workspaceFiles = await readdir19(todosDir2).catch(() => []);
26303
+ const workspaceFiles = await readdir20(todosDir2).catch(() => []);
25607
26304
  const itemsByKey = /* @__PURE__ */ new Map();
25608
26305
  for (const f of workspaceFiles) {
25609
26306
  if (typeof f !== "string") continue;
@@ -25655,7 +26352,7 @@ function createBundlesRouter(todosDir2, broadcast) {
25655
26352
  init_fs();
25656
26353
  init_paths();
25657
26354
  init_slug();
25658
- import { Router as Router22 } from "express";
26355
+ import { Router as Router23 } from "express";
25659
26356
  import { resolve as resolve44 } from "path";
25660
26357
  init_parser2();
25661
26358
  function deriveStatus2(bundle, items) {
@@ -25685,7 +26382,7 @@ function notFound2(res, slug) {
25685
26382
  }
25686
26383
  function createProjectBundlesRouter(projectsDir, broadcast) {
25687
26384
  void broadcast;
25688
- const router = Router22({ mergeParams: true });
26385
+ const router = Router23({ mergeParams: true });
25689
26386
  function validateProjectId(req2, res, next) {
25690
26387
  const slug = getProjectIdParam2(req2.params.projectId);
25691
26388
  if (!slug || !isValidSlug(slug)) {
@@ -25719,7 +26416,7 @@ function createProjectBundlesRouter(projectsDir, broadcast) {
25719
26416
  init_config2();
25720
26417
  init_api();
25721
26418
  init_scanner();
25722
- import { Router as Router23 } from "express";
26419
+ import { Router as Router24 } from "express";
25723
26420
 
25724
26421
  // src/utils/github-backup.ts
25725
26422
  init_paths();
@@ -25727,8 +26424,8 @@ init_fs();
25727
26424
  init_config2();
25728
26425
  import { execFile as execFile2 } from "child_process";
25729
26426
  import { promisify as promisify2 } from "util";
25730
- import { cp as cp2, mkdtemp, rm as rm5, readFile as readFile31, writeFile as writeFile7, unlink as unlink8, stat as stat7, open as open5, rename as rename8 } from "fs/promises";
25731
- import { resolve as resolve45, join as join11 } from "path";
26427
+ import { cp as cp2, mkdtemp, rm as rm5, readFile as readFile33, writeFile as writeFile8, unlink as unlink8, stat as stat8, open as open5, rename as rename8 } from "fs/promises";
26428
+ import { resolve as resolve45, join as join13 } from "path";
25732
26429
  import { tmpdir } from "os";
25733
26430
  var exec2 = promisify2(execFile2);
25734
26431
  var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
@@ -25788,7 +26485,7 @@ async function acquireLock2() {
25788
26485
  return lockPath;
25789
26486
  } catch (err) {
25790
26487
  if (err.code === "EEXIST") {
25791
- const pid = await readFile31(lockPath, "utf-8").catch(() => "");
26488
+ const pid = await readFile33(lockPath, "utf-8").catch(() => "");
25792
26489
  throw new Error(
25793
26490
  `Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
25794
26491
  );
@@ -25821,7 +26518,7 @@ async function cloneOrInit(repoUrl, destDir) {
25821
26518
  }
25822
26519
  async function copyRecursive(src, dest) {
25823
26520
  if (!await fileExists(src)) return;
25824
- const s = await stat7(src);
26521
+ const s = await stat8(src);
25825
26522
  if (s.isDirectory()) {
25826
26523
  await ensureDir(dest);
25827
26524
  await cp2(src, dest, { recursive: true, force: true });
@@ -25835,7 +26532,7 @@ function resolveCategoriesStrict(csv) {
25835
26532
  return parseCategoriesStrict(parts);
25836
26533
  }
25837
26534
  async function readSanitizedConfig(configPath) {
25838
- const content = await readFile31(configPath, "utf-8");
26535
+ const content = await readFile33(configPath, "utf-8");
25839
26536
  return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
25840
26537
  }
25841
26538
  async function backupToGithub(overrides) {
@@ -25858,11 +26555,11 @@ async function backupToGithub(overrides) {
25858
26555
  let tmpDir = null;
25859
26556
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
25860
26557
  try {
25861
- tmpDir = await mkdtemp(join11(tmpdir(), "syntaur-backup-"));
26558
+ tmpDir = await mkdtemp(join13(tmpdir(), "syntaur-backup-"));
25862
26559
  await cloneOrInit(repo, tmpDir);
25863
26560
  for (const category of categories) {
25864
26561
  const { sourcePath, repoPath, isFile } = await resolveCategoryPath(category);
25865
- const destPath = join11(tmpDir, repoPath);
26562
+ const destPath = join13(tmpDir, repoPath);
25866
26563
  if (isFile) {
25867
26564
  await rm5(destPath, { force: true });
25868
26565
  } else {
@@ -25875,7 +26572,7 @@ async function backupToGithub(overrides) {
25875
26572
  if (category === "config") {
25876
26573
  const sanitized = await readSanitizedConfig(sourcePath);
25877
26574
  await ensureDir(resolve45(destPath, ".."));
25878
- await writeFile7(destPath, sanitized, "utf-8");
26575
+ await writeFile8(destPath, sanitized, "utf-8");
25879
26576
  } else {
25880
26577
  await copyRecursive(sourcePath, destPath);
25881
26578
  }
@@ -25990,7 +26687,7 @@ async function restoreFromGithub(overrides) {
25990
26687
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
25991
26688
  try {
25992
26689
  await updateBackupConfig({ lastRestore: timestamp });
25993
- tmpDir = await mkdtemp(join11(tmpdir(), "syntaur-restore-"));
26690
+ tmpDir = await mkdtemp(join13(tmpdir(), "syntaur-restore-"));
25994
26691
  await cloneOrInit(repo, tmpDir);
25995
26692
  for (const category of categories) {
25996
26693
  if (category === "config") {
@@ -25999,7 +26696,7 @@ async function restoreFromGithub(overrides) {
25999
26696
  }
26000
26697
  try {
26001
26698
  const { sourcePath: localPath, repoPath, isFile } = await resolveCategoryPath(category);
26002
- const repoSrcPath = join11(tmpDir, repoPath);
26699
+ const repoSrcPath = join13(tmpDir, repoPath);
26003
26700
  if (!await fileExists(repoSrcPath)) {
26004
26701
  console.warn(`Category "${category}" not found in backup repo, skipping.`);
26005
26702
  continue;
@@ -26042,7 +26739,7 @@ async function getBackupStatus() {
26042
26739
 
26043
26740
  // src/dashboard/api-backup.ts
26044
26741
  function createBackupRouter() {
26045
- const router = Router23();
26742
+ const router = Router24();
26046
26743
  router.get("/", async (_req, res) => {
26047
26744
  try {
26048
26745
  const status = await getBackupStatus();
@@ -27410,6 +28107,7 @@ function createDashboardServer(options) {
27410
28107
  app.use("/api", createInboxRouter(projectsDir, assignmentsDir2));
27411
28108
  app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2));
27412
28109
  app.use("/api/config/agents", createAgentsRouter());
28110
+ app.use("/api/config/agent-discovery", createAgentDiscoveryConfigRouter());
27413
28111
  app.use(
27414
28112
  "/api/launch",
27415
28113
  createLaunchPreflightRouter(projectsDir, assignmentsDir2)
@@ -27607,7 +28305,7 @@ function createDashboardServer(options) {
27607
28305
  });
27608
28306
  server.listen(port, () => {
27609
28307
  const portFile = resolve48(syntaurRoot(), "dashboard-port");
27610
- writeFile8(portFile, String(port), "utf-8").catch(() => {
28308
+ writeFile9(portFile, String(port), "utf-8").catch(() => {
27611
28309
  });
27612
28310
  resolvePromise();
27613
28311
  });