syntaur 0.27.0 → 0.32.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 (79) hide show
  1. package/dashboard/dist/assets/{_basePickBy-DPBuiT9A.js → _basePickBy-CV3s3ZBR.js} +1 -1
  2. package/dashboard/dist/assets/{_baseUniq-B5Q4dkW3.js → _baseUniq-BTBb-kpx.js} +1 -1
  3. package/dashboard/dist/assets/{arc-Bp71QC_v.js → arc-DroCaru_.js} +1 -1
  4. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CWHBISZ5.js → architectureDiagram-2XIMDMQ5-hL5g0oNx.js} +1 -1
  5. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-D0txIHgi.js → blockDiagram-WCTKOSBZ-H-mOZOGQ.js} +1 -1
  6. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-D_Hpnc38.js → c4Diagram-IC4MRINW-C7JTywql.js} +1 -1
  7. package/dashboard/dist/assets/channel-X6-lrXLw.js +1 -0
  8. package/dashboard/dist/assets/{chunk-4BX2VUAB-D0A_A8qn.js → chunk-4BX2VUAB-C5NgL7Ud.js} +1 -1
  9. package/dashboard/dist/assets/{chunk-55IACEB6-DuK8QvrD.js → chunk-55IACEB6-CpqlZIZp.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-FMBD7UC4-B5WfIDS6.js → chunk-FMBD7UC4-DOEJuCgN.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-JSJVCQXG-D3jB_ZJP.js → chunk-JSJVCQXG-DTvwZQeC.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-KX2RTZJC-DtxN1mOD.js → chunk-KX2RTZJC-DtM5R3Ro.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-NQ4KR5QH-4fQpgivN.js → chunk-NQ4KR5QH-BRf7XeyG.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-QZHKN3VN-BOf9TZCT.js → chunk-QZHKN3VN-CkRK4_hm.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-WL4C6EOR-D9HeEPWL.js → chunk-WL4C6EOR-D40xff82.js} +1 -1
  16. package/dashboard/dist/assets/classDiagram-VBA2DB6C-DfkR91Os.js +1 -0
  17. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-DfkR91Os.js +1 -0
  18. package/dashboard/dist/assets/clone--OUSRwbL.js +1 -0
  19. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CpzWcyB7.js → cose-bilkent-S5V4N54A-BzAx8dWI.js} +1 -1
  20. package/dashboard/dist/assets/{dagre-KLK3FWXG-CC9-omFF.js → dagre-KLK3FWXG-DqBzOGFn.js} +1 -1
  21. package/dashboard/dist/assets/{diagram-E7M64L7V-q_F9KKPz.js → diagram-E7M64L7V-BU3Nv4BP.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-IFDJBPK2-CbYvNpQB.js → diagram-IFDJBPK2-9173qxjV.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-P4PSJMXO-q8XUUKRC.js → diagram-P4PSJMXO-CDO7XNao.js} +1 -1
  24. package/dashboard/dist/assets/{erDiagram-INFDFZHY-Q-oL35fO.js → erDiagram-INFDFZHY-DkO9AjCM.js} +1 -1
  25. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-Cptj-2yF.js → flowDiagram-PKNHOUZH-9Fjhsq_p.js} +1 -1
  26. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-BYmgXBad.js → ganttDiagram-A5KZAMGK-FL3oGbo9.js} +1 -1
  27. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-DHF3w-Cn.js → gitGraphDiagram-K3NZZRJ6-FS9HpxFJ.js} +1 -1
  28. package/dashboard/dist/assets/{graph-Br4uG9xg.js → graph-COu71lol.js} +1 -1
  29. package/dashboard/dist/assets/index-BohN_jjP.css +1 -0
  30. package/dashboard/dist/assets/{index-dyJ_mu3x.js → index-D1FxzsMS.js} +84 -83
  31. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-Ckb3YLUI.js → infoDiagram-LFFYTUFH-Cb7nqRMJ.js} +1 -1
  32. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DSXXm4hL.js → ishikawaDiagram-PHBUUO56-DU44jQ_t.js} +1 -1
  33. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-D4JJ4wn_.js → journeyDiagram-4ABVD52K-Dvf5wmhX.js} +1 -1
  34. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-DZeWPcIi.js → kanban-definition-K7BYSVSG-NNnzIiBX.js} +1 -1
  35. package/dashboard/dist/assets/{layout-DU5mcBKh.js → layout-BWL1q6XW.js} +1 -1
  36. package/dashboard/dist/assets/{linear-h7AvdT63.js → linear-BpjXUE-L.js} +1 -1
  37. package/dashboard/dist/assets/{mermaid.core-DIOnVuDB.js → mermaid.core-C1YuKa7V.js} +4 -4
  38. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-BVSORv6W.js → mindmap-definition-YRQLILUH-BUS-7SSM.js} +1 -1
  39. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-BEdO084J.js → pieDiagram-SKSYHLDU-CId0D08y.js} +1 -1
  40. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-3Dc5mQ7q.js → quadrantDiagram-337W2JSQ-lrdlvDFz.js} +1 -1
  41. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-eu-8doSY.js → requirementDiagram-Z7DCOOCP-CijOr4BN.js} +1 -1
  42. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-jA292hzv.js → sankeyDiagram-WA2Y5GQK-Bz63rCum.js} +1 -1
  43. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-et31a6Tg.js → sequenceDiagram-2WXFIKYE-0ojwsRXQ.js} +1 -1
  44. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-D6MtTWaR.js → stateDiagram-RAJIS63D-DvTark-k.js} +1 -1
  45. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-CiT1CTy0.js +1 -0
  46. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-Oa_SYaCP.js → timeline-definition-YZTLITO2-B4EQprf1.js} +1 -1
  47. package/dashboard/dist/assets/{treemap-KZPCXAKY-vrIbKmuv.js → treemap-KZPCXAKY-ht_xOxVL.js} +1 -1
  48. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-B3UlkEHW.js → vennDiagram-LZ73GAT5-C4DqZkvq.js} +1 -1
  49. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-BLiVVy6A.js → xychartDiagram-JWTSCODW-eoymvv9D.js} +1 -1
  50. package/dashboard/dist/index.html +2 -2
  51. package/dist/dashboard/server.js +449 -124
  52. package/dist/dashboard/server.js.map +1 -1
  53. package/dist/index.js +5766 -3927
  54. package/dist/index.js.map +1 -1
  55. package/dist/launch/index.js +1191 -1181
  56. package/dist/launch/index.js.map +1 -1
  57. package/package.json +2 -1
  58. package/platforms/README.md +21 -0
  59. package/platforms/claude-code/skills/clear-assignment/SKILL.md +2 -2
  60. package/platforms/claude-code/skills/log-progress/SKILL.md +29 -48
  61. package/platforms/claude-code/skills/plan-assignment/SKILL.md +20 -1
  62. package/platforms/claude-code/skills/save-session-summary/SKILL.md +28 -29
  63. package/platforms/claude-code/skills/set-workspace/SKILL.md +25 -41
  64. package/platforms/codex/skills/clear-assignment/SKILL.md +2 -2
  65. package/platforms/codex/skills/log-progress/SKILL.md +29 -48
  66. package/platforms/codex/skills/plan-assignment/SKILL.md +20 -1
  67. package/platforms/codex/skills/save-session-summary/SKILL.md +28 -29
  68. package/platforms/codex/skills/set-workspace/SKILL.md +25 -41
  69. package/skills/clear-assignment/SKILL.md +2 -2
  70. package/skills/log-progress/SKILL.md +29 -48
  71. package/skills/plan-assignment/SKILL.md +20 -1
  72. package/skills/save-session-summary/SKILL.md +28 -29
  73. package/skills/set-workspace/SKILL.md +25 -41
  74. package/dashboard/dist/assets/channel-D41AslDq.js +0 -1
  75. package/dashboard/dist/assets/classDiagram-VBA2DB6C-BnKy62Yt.js +0 -1
  76. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BnKy62Yt.js +0 -1
  77. package/dashboard/dist/assets/clone-Cz7h9axV.js +0 -1
  78. package/dashboard/dist/assets/index-Ds1-e_jv.css +0 -1
  79. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-sYL-A3ib.js +0 -1
@@ -1798,9 +1798,11 @@ __export(config_exports, {
1798
1798
  AgentConfigError: () => AgentConfigError,
1799
1799
  BUILTIN_AGENTS: () => BUILTIN_AGENTS,
1800
1800
  DEFAULT_ASSIGNMENT_TYPES: () => DEFAULT_ASSIGNMENT_TYPES,
1801
+ DEFAULT_STATUS_COLORS: () => DEFAULT_STATUS_COLORS,
1801
1802
  PROMPT_ARG_POSITIONS: () => PROMPT_ARG_POSITIONS,
1802
1803
  TERMINAL_CHOICES: () => TERMINAL_CHOICES,
1803
1804
  TerminalConfigError: () => TerminalConfigError,
1805
+ buildDefaultStatusConfig: () => buildDefaultStatusConfig,
1804
1806
  deleteAgentsConfig: () => deleteAgentsConfig,
1805
1807
  deleteHotkeyBindingsConfig: () => deleteHotkeyBindingsConfig,
1806
1808
  deleteStatusConfig: () => deleteStatusConfig,
@@ -1811,8 +1813,11 @@ __export(config_exports, {
1811
1813
  getAssignmentTypes: () => getAssignmentTypes,
1812
1814
  getTerminal: () => getTerminal,
1813
1815
  parseAgentCommand: () => parseAgentCommand,
1816
+ parseStatusConfig: () => parseStatusConfig,
1814
1817
  parseTerminalConfig: () => parseTerminalConfig,
1815
1818
  readConfig: () => readConfig,
1819
+ serializeStatusConfig: () => serializeStatusConfig,
1820
+ toTitleCase: () => toTitleCase,
1816
1821
  updateAgentsConfig: () => updateAgentsConfig,
1817
1822
  updateBackupConfig: () => updateBackupConfig,
1818
1823
  updateIntegrationConfig: () => updateIntegrationConfig,
@@ -2058,6 +2063,24 @@ function parseStatusConfig(content) {
2058
2063
  transitions
2059
2064
  };
2060
2065
  }
2066
+ function toTitleCase(s) {
2067
+ return s.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2068
+ }
2069
+ function buildDefaultStatusConfig() {
2070
+ return {
2071
+ statuses: DEFAULT_STATUSES.map((id) => ({
2072
+ id,
2073
+ label: toTitleCase(id),
2074
+ color: DEFAULT_STATUS_COLORS[id] ?? "gray",
2075
+ terminal: id === "completed" || id === "failed"
2076
+ })),
2077
+ order: [...DEFAULT_STATUSES],
2078
+ transitions: Array.from(DEFAULT_TRANSITION_TABLE.entries()).map(([key, to]) => {
2079
+ const [from, command] = key.split(":");
2080
+ return { from, command, to };
2081
+ })
2082
+ };
2083
+ }
2061
2084
  function serializeStatusConfig(statuses) {
2062
2085
  const lines = [];
2063
2086
  lines.push("statuses:");
@@ -3127,7 +3150,7 @@ async function updateAgentsConfig(mutation, options = {}) {
3127
3150
  await writeAgentsConfig(next);
3128
3151
  return { previous, next, written: true };
3129
3152
  }
3130
- var DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, AgentConfigError, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
3153
+ var DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, AgentConfigError, DEFAULT_STATUS_COLORS, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
3131
3154
  var init_config2 = __esm({
3132
3155
  "src/utils/config.ts"() {
3133
3156
  "use strict";
@@ -3135,6 +3158,7 @@ var init_config2 = __esm({
3135
3158
  init_fs();
3136
3159
  init_config();
3137
3160
  init_fs_migration();
3161
+ init_lifecycle();
3138
3162
  init_hotkeysCatalog();
3139
3163
  init_agents_schema();
3140
3164
  init_terminal_schema();
@@ -3182,6 +3206,14 @@ var init_config2 = __esm({
3182
3206
  AUTO_CREATE_WORKTREE_VALUES = ["skip", "ask", "always"];
3183
3207
  AgentConfigError = class extends Error {
3184
3208
  };
3209
+ DEFAULT_STATUS_COLORS = {
3210
+ pending: "slate",
3211
+ in_progress: "teal",
3212
+ blocked: "amber",
3213
+ review: "violet",
3214
+ completed: "emerald",
3215
+ failed: "rose"
3216
+ };
3185
3217
  KNOWN_AGENT_SCALAR_FIELDS = /* @__PURE__ */ new Set([
3186
3218
  "id",
3187
3219
  "label",
@@ -4040,6 +4072,37 @@ function initSessionDb(dbPath) {
4040
4072
  UPDATE meta SET value = '4' WHERE key = 'schema_version';
4041
4073
  `);
4042
4074
  }
4075
+ const vBeforeV5 = database.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get()?.value;
4076
+ if (vBeforeV5 === "4") {
4077
+ database.exec(`
4078
+ CREATE TABLE sessions_v5 (
4079
+ session_id TEXT PRIMARY KEY,
4080
+ project_slug TEXT,
4081
+ assignment_slug TEXT,
4082
+ agent TEXT NOT NULL,
4083
+ started TEXT NOT NULL,
4084
+ ended TEXT,
4085
+ status TEXT NOT NULL DEFAULT 'active',
4086
+ path TEXT,
4087
+ description TEXT,
4088
+ transcript_path TEXT,
4089
+ pid INTEGER,
4090
+ pid_started_at TEXT,
4091
+ original_head_sha TEXT,
4092
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
4093
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
4094
+ );
4095
+ INSERT INTO sessions_v5
4096
+ SELECT session_id, project_slug, assignment_slug, agent, started, ended, status, path, description, transcript_path, pid, pid_started_at, NULL, created_at, updated_at
4097
+ FROM sessions;
4098
+ DROP TABLE sessions;
4099
+ ALTER TABLE sessions_v5 RENAME TO sessions;
4100
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_slug);
4101
+ CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(project_slug, assignment_slug);
4102
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
4103
+ UPDATE meta SET value = '5' WHERE key = 'schema_version';
4104
+ `);
4105
+ }
4043
4106
  });
4044
4107
  runMigrations.exclusive();
4045
4108
  db.exec(POST_MIGRATION_INDEXES_SQL);
@@ -4131,7 +4194,7 @@ var init_session_db = __esm({
4131
4194
  init_paths();
4132
4195
  init_fs();
4133
4196
  db = null;
4134
- SCHEMA_VERSION = "4";
4197
+ SCHEMA_VERSION = "5";
4135
4198
  SCHEMA_SQL = `
4136
4199
  CREATE TABLE IF NOT EXISTS sessions (
4137
4200
  session_id TEXT PRIMARY KEY,
@@ -4146,6 +4209,7 @@ CREATE TABLE IF NOT EXISTS sessions (
4146
4209
  transcript_path TEXT,
4147
4210
  pid INTEGER,
4148
4211
  pid_started_at TEXT,
4212
+ original_head_sha TEXT,
4149
4213
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
4150
4214
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
4151
4215
  );
@@ -4175,25 +4239,27 @@ function rowToSession(row) {
4175
4239
  description: row.description ?? null,
4176
4240
  transcriptPath: row.transcript_path ?? null,
4177
4241
  pid: row.pid ?? null,
4178
- pidStartedAt: row.pid_started_at ?? null
4242
+ pidStartedAt: row.pid_started_at ?? null,
4243
+ originalHeadSha: row.original_head_sha ?? null
4179
4244
  };
4180
4245
  }
4181
4246
  async function appendSession(_projectDir, session) {
4182
4247
  const db4 = getSessionDb();
4183
4248
  db4.prepare(`
4184
- INSERT INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path, description, transcript_path, pid, pid_started_at)
4185
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4249
+ INSERT INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path, description, transcript_path, pid, pid_started_at, original_head_sha)
4250
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4186
4251
  ON CONFLICT(session_id) DO UPDATE SET
4187
- project_slug = COALESCE(NULLIF(excluded.project_slug, ''), project_slug),
4188
- assignment_slug = COALESCE(NULLIF(excluded.assignment_slug, ''), assignment_slug),
4189
- agent = excluded.agent,
4190
- status = CASE WHEN status IN ('completed','stopped') THEN status ELSE excluded.status END,
4191
- path = COALESCE(NULLIF(excluded.path, ''), path),
4192
- description = COALESCE(NULLIF(excluded.description, ''), description),
4193
- transcript_path = COALESCE(NULLIF(excluded.transcript_path, ''), transcript_path),
4194
- pid = COALESCE(excluded.pid, pid),
4195
- pid_started_at = COALESCE(NULLIF(excluded.pid_started_at, ''), pid_started_at),
4196
- updated_at = datetime('now')
4252
+ project_slug = COALESCE(NULLIF(excluded.project_slug, ''), project_slug),
4253
+ assignment_slug = COALESCE(NULLIF(excluded.assignment_slug, ''), assignment_slug),
4254
+ agent = excluded.agent,
4255
+ status = CASE WHEN status IN ('completed','stopped') THEN status ELSE excluded.status END,
4256
+ path = COALESCE(NULLIF(excluded.path, ''), path),
4257
+ description = COALESCE(NULLIF(excluded.description, ''), description),
4258
+ transcript_path = COALESCE(NULLIF(excluded.transcript_path, ''), transcript_path),
4259
+ pid = COALESCE(excluded.pid, pid),
4260
+ pid_started_at = COALESCE(NULLIF(excluded.pid_started_at, ''), pid_started_at),
4261
+ original_head_sha = COALESCE(NULLIF(original_head_sha, ''), NULLIF(excluded.original_head_sha, '')),
4262
+ updated_at = datetime('now')
4197
4263
  `).run(
4198
4264
  session.sessionId,
4199
4265
  session.projectSlug ?? null,
@@ -4205,7 +4271,8 @@ async function appendSession(_projectDir, session) {
4205
4271
  session.description ?? null,
4206
4272
  session.transcriptPath ?? null,
4207
4273
  session.pid ?? null,
4208
- session.pidStartedAt ?? null
4274
+ session.pidStartedAt ?? null,
4275
+ session.originalHeadSha ?? null
4209
4276
  );
4210
4277
  }
4211
4278
  async function updateSessionStatus(_projectDir, sessionId, status) {
@@ -5063,9 +5130,6 @@ async function computeStandaloneRecords(assignmentsDir2) {
5063
5130
  records.sort((left, right) => compareTimestamps(right.record.updated, left.record.updated));
5064
5131
  return records;
5065
5132
  }
5066
- function toTitleCase(s) {
5067
- return s.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
5068
- }
5069
5133
  function getTransitionDefinitions(config) {
5070
5134
  if (!config.custom) return DEFAULT_TRANSITION_DEFINITIONS;
5071
5135
  const seen = /* @__PURE__ */ new Set();
@@ -5101,19 +5165,12 @@ async function getStatusConfig() {
5101
5165
  terminalStatuses: terminalSet.size > 0 ? terminalSet : /* @__PURE__ */ new Set(["completed", "failed"])
5102
5166
  };
5103
5167
  } else {
5168
+ const def = buildDefaultStatusConfig();
5104
5169
  _cachedConfig = {
5105
5170
  custom: false,
5106
- statuses: DEFAULT_STATUSES.map((id) => ({
5107
- id,
5108
- label: toTitleCase(id),
5109
- color: DEFAULT_STATUS_COLORS[id] ?? "gray",
5110
- terminal: id === "completed" || id === "failed"
5111
- })),
5112
- order: [...DEFAULT_STATUSES],
5113
- transitions: Array.from(DEFAULT_TRANSITION_TABLE.entries()).map(([key, to]) => {
5114
- const [from, command] = key.split(":");
5115
- return { from, command, to };
5116
- }),
5171
+ statuses: def.statuses,
5172
+ order: def.order,
5173
+ transitions: def.transitions,
5117
5174
  transitionTable: DEFAULT_TRANSITION_TABLE,
5118
5175
  terminalStatuses: /* @__PURE__ */ new Set(["completed", "failed"])
5119
5176
  };
@@ -6713,7 +6770,7 @@ async function getPlaybookDetail(playbooksDir2, slug) {
6713
6770
  enabled
6714
6771
  };
6715
6772
  }
6716
- var WorkspaceBlockedError, STALE_ASSIGNMENT_MS, RECENT_PROJECTS_LIMIT, RECENT_ACTIVITY_LIMIT, RECENT_SESSIONS_LIMIT, NEWEST_CREATED_LIMIT, SEGMENT_DISPLAY_CAP, STALE_LIMIT_DEFAULT, STALE_LIMIT_MAX, TERMINAL_STATUSES2, STATUS_TO_SEGMENT, HERO_PRIORITY, projectRecordsCache, standaloneRecordsCache, DEFAULT_TRANSITION_DEFINITIONS, DEFAULT_STATUS_COLORS, _cachedConfig, REFERENCED_BY_LIMIT, migratedProjectsDirs, DEFAULT_GRAPH_COLORS;
6773
+ var WorkspaceBlockedError, STALE_ASSIGNMENT_MS, RECENT_PROJECTS_LIMIT, RECENT_ACTIVITY_LIMIT, RECENT_SESSIONS_LIMIT, NEWEST_CREATED_LIMIT, SEGMENT_DISPLAY_CAP, STALE_LIMIT_DEFAULT, STALE_LIMIT_MAX, TERMINAL_STATUSES2, STATUS_TO_SEGMENT, HERO_PRIORITY, projectRecordsCache, standaloneRecordsCache, DEFAULT_TRANSITION_DEFINITIONS, _cachedConfig, REFERENCED_BY_LIMIT, migratedProjectsDirs, DEFAULT_GRAPH_COLORS;
6717
6774
  var init_api = __esm({
6718
6775
  "src/dashboard/api.ts"() {
6719
6776
  "use strict";
@@ -6828,14 +6885,6 @@ var init_api = __esm({
6828
6885
  requiresReason: false
6829
6886
  }
6830
6887
  ];
6831
- DEFAULT_STATUS_COLORS = {
6832
- pending: "slate",
6833
- in_progress: "teal",
6834
- blocked: "amber",
6835
- review: "violet",
6836
- completed: "emerald",
6837
- failed: "rose"
6838
- };
6839
6888
  _cachedConfig = null;
6840
6889
  REFERENCED_BY_LIMIT = 50;
6841
6890
  migratedProjectsDirs = /* @__PURE__ */ new Set();
@@ -6991,7 +7040,18 @@ function enrichSessions(sessions, agents, deps) {
6991
7040
 
6992
7041
  // src/dashboard/watcher.ts
6993
7042
  import { watch } from "chokidar";
6994
- import { basename as basename2, dirname as dirname3, relative, sep } from "path";
7043
+ import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, relative, sep } from "path";
7044
+ var defaultPathApi = { relative, isAbsolute: isAbsolute2 };
7045
+ function ignoreDotSegmentsBelow(root, pathApi = defaultPathApi) {
7046
+ return (p) => {
7047
+ const rel = pathApi.relative(root, p);
7048
+ if (!rel) return false;
7049
+ if (pathApi.isAbsolute(rel)) return false;
7050
+ const parts = rel.split(/[\\/]/);
7051
+ if (parts[0] === "..") return false;
7052
+ return parts.some((segment) => segment.startsWith("."));
7053
+ };
7054
+ }
6995
7055
  function createWatcher(options) {
6996
7056
  const { projectsDir, assignmentsDir: assignmentsDir2, serversDir: serversDir2, playbooksDir: playbooksDir2, todosDir: todosDir2, dbPath, onMessage, debounceMs = 300 } = options;
6997
7057
  const pendingEvents = /* @__PURE__ */ new Map();
@@ -6999,7 +7059,7 @@ function createWatcher(options) {
6999
7059
  ignoreInitial: true,
7000
7060
  persistent: true,
7001
7061
  depth: 10,
7002
- ignored: /(^|[\/\\])\../
7062
+ ignored: ignoreDotSegmentsBelow(projectsDir)
7003
7063
  });
7004
7064
  function handleProjectChange(filePath) {
7005
7065
  const rel = relative(projectsDir, filePath);
@@ -7068,7 +7128,7 @@ function createWatcher(options) {
7068
7128
  ignoreInitial: true,
7069
7129
  persistent: true,
7070
7130
  depth: 5,
7071
- ignored: /(^|[\/\\])\../
7131
+ ignored: ignoreDotSegmentsBelow(assignmentsDir2)
7072
7132
  });
7073
7133
  standaloneWatcher.on("change", handleStandaloneChange2);
7074
7134
  standaloneWatcher.on("add", handleStandaloneChange2);
@@ -7097,7 +7157,7 @@ function createWatcher(options) {
7097
7157
  ignoreInitial: true,
7098
7158
  persistent: true,
7099
7159
  depth: 1,
7100
- ignored: /(^|[\/\\])\../
7160
+ ignored: ignoreDotSegmentsBelow(serversDir2)
7101
7161
  });
7102
7162
  serversWatcher.on("change", handleServerChange2);
7103
7163
  serversWatcher.on("add", handleServerChange2);
@@ -7126,7 +7186,7 @@ function createWatcher(options) {
7126
7186
  ignoreInitial: true,
7127
7187
  persistent: true,
7128
7188
  depth: 1,
7129
- ignored: /(^|[\/\\])\../
7189
+ ignored: ignoreDotSegmentsBelow(playbooksDir2)
7130
7190
  });
7131
7191
  playbooksWatcher.on("change", handlePlaybookChange2);
7132
7192
  playbooksWatcher.on("add", handlePlaybookChange2);
@@ -7155,7 +7215,7 @@ function createWatcher(options) {
7155
7215
  ignoreInitial: true,
7156
7216
  persistent: true,
7157
7217
  depth: 1,
7158
- ignored: /(^|[\/\\])\../
7218
+ ignored: ignoreDotSegmentsBelow(todosDir2)
7159
7219
  });
7160
7220
  todosWatcher.on("change", handleTodoChange2);
7161
7221
  todosWatcher.on("add", handleTodoChange2);
@@ -7187,7 +7247,7 @@ function createWatcher(options) {
7187
7247
  ignoreInitial: true,
7188
7248
  persistent: true,
7189
7249
  depth: 0,
7190
- ignored: /(^|[\/\\])\../
7250
+ ignored: ignoreDotSegmentsBelow(dbDir)
7191
7251
  });
7192
7252
  leasesDbWatcher.on("change", handleDbChange2);
7193
7253
  leasesDbWatcher.on("add", handleDbChange2);
@@ -7909,7 +7969,7 @@ function createDashboardLayoutRouter() {
7909
7969
  init_lifecycle();
7910
7970
  init_slug();
7911
7971
  import { Router as Router2 } from "express";
7912
- import { resolve as resolve18, basename as basename3, isAbsolute as isAbsolute2 } from "path";
7972
+ import { resolve as resolve18, basename as basename3, isAbsolute as isAbsolute4 } from "path";
7913
7973
  import { rm, readFile as readFile15, open as fsOpen, stat as fsStat, realpath as fsRealpath } from "fs/promises";
7914
7974
  import { spawnSync as spawnSync3 } from "child_process";
7915
7975
 
@@ -7928,6 +7988,20 @@ init_frontmatter();
7928
7988
  init_fs();
7929
7989
  import { spawn } from "child_process";
7930
7990
  import { readFile as readFile13 } from "fs/promises";
7991
+
7992
+ // src/launch/cwd.ts
7993
+ import { existsSync, statSync as statSync2 } from "fs";
7994
+ import { isAbsolute as isAbsolute3 } from "path";
7995
+ function isExistingDir(p) {
7996
+ if (!p || !isAbsolute3(p)) return false;
7997
+ try {
7998
+ return existsSync(p) && statSync2(p).isDirectory();
7999
+ } catch {
8000
+ return false;
8001
+ }
8002
+ }
8003
+
8004
+ // src/utils/git-worktree.ts
7931
8005
  function run(command, args, cwd) {
7932
8006
  return new Promise((resolvePromise) => {
7933
8007
  const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
@@ -7962,11 +8036,11 @@ async function createWorktree(opts) {
7962
8036
  );
7963
8037
  }
7964
8038
  }
7965
- async function removeWorktree(repository, worktreePath) {
7966
- const result = await run(
7967
- "git",
7968
- ["-C", repository, "worktree", "remove", "--force", worktreePath]
7969
- );
8039
+ async function removeWorktree(repository, worktreePath, opts = {}) {
8040
+ const args = ["-C", repository, "worktree", "remove"];
8041
+ if (opts.force) args.push("--force");
8042
+ args.push(worktreePath);
8043
+ const result = await run("git", args);
7970
8044
  return { ok: result.code === 0, stderr: result.stderr };
7971
8045
  }
7972
8046
  async function deleteBranch(repository, branch) {
@@ -8007,6 +8081,82 @@ async function detectDefaultBranch(repository) {
8007
8081
  }
8008
8082
  return branches[0] ?? null;
8009
8083
  }
8084
+ async function captureHeadSha(dir) {
8085
+ const result = await run("git", ["-C", dir, "rev-parse", "HEAD"]);
8086
+ if (result.code !== 0) return null;
8087
+ const sha = result.stdout.trim();
8088
+ return sha.length > 0 ? sha : null;
8089
+ }
8090
+ async function branchCheckedOutAt(repository, branch) {
8091
+ const result = await run("git", ["-C", repository, "worktree", "list", "--porcelain"]);
8092
+ if (result.code !== 0) return null;
8093
+ let currentPath = null;
8094
+ for (const line of result.stdout.split("\n")) {
8095
+ if (line.startsWith("worktree ")) {
8096
+ currentPath = line.slice("worktree ".length).trim();
8097
+ } else if (line.startsWith("branch ")) {
8098
+ const ref = line.slice("branch ".length).trim();
8099
+ if (ref === `refs/heads/${branch}` && currentPath) return currentPath;
8100
+ }
8101
+ }
8102
+ return null;
8103
+ }
8104
+ async function recreateWorktree(opts) {
8105
+ const { repository, worktreePath, branch } = opts;
8106
+ const originalHeadSha = opts.originalHeadSha ?? null;
8107
+ await run("git", ["-C", repository, "worktree", "prune"]);
8108
+ const add = async (args) => {
8109
+ const result = await run("git", ["-C", repository, "worktree", "add", ...args]);
8110
+ if (result.code !== 0) {
8111
+ throw new GitWorktreeError(
8112
+ `git worktree add failed (exit ${result.code}): ${result.stderr.trim() || "(no stderr)"}`,
8113
+ result.stderr
8114
+ );
8115
+ }
8116
+ };
8117
+ const refExists = async (ref) => {
8118
+ const result = await run("git", [
8119
+ "-C",
8120
+ repository,
8121
+ "rev-parse",
8122
+ "--verify",
8123
+ "--quiet",
8124
+ ref
8125
+ ]);
8126
+ return result.code === 0;
8127
+ };
8128
+ if (branch && (await listBranches(repository)).includes(branch)) {
8129
+ const checkedOutAt = await branchCheckedOutAt(repository, branch);
8130
+ if (!checkedOutAt || checkedOutAt === worktreePath || !isExistingDir(checkedOutAt)) {
8131
+ await add([worktreePath, branch]);
8132
+ return { baseUsed: branch, exact: true, branch };
8133
+ }
8134
+ const detachBase = originalHeadSha && await refExists(`${originalHeadSha}^{commit}`) ? originalHeadSha : `refs/heads/${branch}`;
8135
+ await add(["--detach", worktreePath, detachBase]);
8136
+ return { baseUsed: detachBase, exact: detachBase === originalHeadSha, branch: null };
8137
+ }
8138
+ let baseUsed = null;
8139
+ if (originalHeadSha && await refExists(`${originalHeadSha}^{commit}`)) {
8140
+ baseUsed = originalHeadSha;
8141
+ } else if (branch && await refExists(`refs/remotes/origin/${branch}`)) {
8142
+ baseUsed = `refs/remotes/origin/${branch}`;
8143
+ } else {
8144
+ baseUsed = await detectDefaultBranch(repository);
8145
+ }
8146
+ if (!baseUsed) {
8147
+ throw new GitWorktreeError(
8148
+ `recreateWorktree: no base ref to recreate ${worktreePath} (no original sha, no origin/${branch ?? "<none>"}, no default branch)`,
8149
+ ""
8150
+ );
8151
+ }
8152
+ const exact = baseUsed === originalHeadSha;
8153
+ if (branch) {
8154
+ await add(["-b", branch, worktreePath, baseUsed]);
8155
+ return { baseUsed, exact, branch };
8156
+ }
8157
+ await add(["--detach", worktreePath, baseUsed]);
8158
+ return { baseUsed, exact, branch: null };
8159
+ }
8010
8160
  async function createWorktreeAndRecord(opts) {
8011
8161
  const { assignmentPath, repository, branch, worktreePath, parentBranch } = opts;
8012
8162
  await createWorktree({ repository, branch, worktreePath, parentBranch });
@@ -8020,7 +8170,7 @@ async function createWorktreeAndRecord(opts) {
8020
8170
  });
8021
8171
  await writeFileForce(assignmentPath, updated);
8022
8172
  } catch (writeErr) {
8023
- const cleanup = await removeWorktree(repository, worktreePath);
8173
+ const cleanup = await removeWorktree(repository, worktreePath, { force: true });
8024
8174
  const branchCleanup = await deleteBranch(repository, branch);
8025
8175
  const writeMsg = writeErr instanceof Error ? writeErr.message : String(writeErr);
8026
8176
  throw new Error(
@@ -8121,6 +8271,154 @@ function validateBranchName(name) {
8121
8271
  return null;
8122
8272
  }
8123
8273
 
8274
+ // src/dashboard/recreate-target.ts
8275
+ init_api();
8276
+ init_agent_sessions();
8277
+ async function resolveRecreateTarget(deps, target) {
8278
+ const { projectsDir, assignmentsDir: assignmentsDir2 } = deps;
8279
+ if (target.kind === "assignment") {
8280
+ const detail = "id" in target ? await getAssignmentDetailById(projectsDir, assignmentsDir2, target.id) : await getAssignmentDetail(
8281
+ projectsDir,
8282
+ target.projectSlug,
8283
+ target.assignmentSlug
8284
+ );
8285
+ if (!detail) return null;
8286
+ const worktreePath2 = detail.workspace.worktreePath ?? "";
8287
+ const repository2 = detail.workspace.repository ?? null;
8288
+ const branch2 = detail.workspace.branch ?? null;
8289
+ const missing2 = worktreePath2 !== "" && !isExistingDir(worktreePath2);
8290
+ return {
8291
+ kind: "assignment",
8292
+ id: detail.id,
8293
+ projectSlug: detail.projectSlug ?? null,
8294
+ assignmentSlug: detail.slug,
8295
+ worktreePath: worktreePath2,
8296
+ repository: repository2,
8297
+ branch: branch2,
8298
+ originalHeadSha: null,
8299
+ missing: missing2,
8300
+ recreatable: missing2 && isExistingDir(repository2)
8301
+ };
8302
+ }
8303
+ const session = getSessionById(target.id);
8304
+ if (!session) return null;
8305
+ let repository = null;
8306
+ let branch = null;
8307
+ let assignmentWorktreePath = "";
8308
+ if (session.projectSlug && session.assignmentSlug) {
8309
+ const detail = await getAssignmentDetail(
8310
+ projectsDir,
8311
+ session.projectSlug,
8312
+ session.assignmentSlug
8313
+ );
8314
+ if (detail) {
8315
+ repository = detail.workspace.repository ?? null;
8316
+ branch = detail.workspace.branch ?? null;
8317
+ assignmentWorktreePath = detail.workspace.worktreePath ?? "";
8318
+ }
8319
+ } else if (session.assignmentSlug) {
8320
+ const detail = await getAssignmentDetailById(
8321
+ projectsDir,
8322
+ assignmentsDir2,
8323
+ session.assignmentSlug
8324
+ );
8325
+ if (detail) {
8326
+ repository = detail.workspace.repository ?? null;
8327
+ branch = detail.workspace.branch ?? null;
8328
+ assignmentWorktreePath = detail.workspace.worktreePath ?? "";
8329
+ }
8330
+ }
8331
+ const worktreePath = session.path || assignmentWorktreePath;
8332
+ const missing = worktreePath !== "" && !isExistingDir(worktreePath);
8333
+ return {
8334
+ kind: "session",
8335
+ id: session.sessionId,
8336
+ projectSlug: session.projectSlug ?? null,
8337
+ assignmentSlug: session.assignmentSlug ?? null,
8338
+ worktreePath,
8339
+ repository,
8340
+ branch,
8341
+ originalHeadSha: session.originalHeadSha ?? null,
8342
+ missing,
8343
+ recreatable: missing && isExistingDir(repository)
8344
+ };
8345
+ }
8346
+
8347
+ // src/dashboard/worktree-recreate.ts
8348
+ async function recreateForTarget(deps, target) {
8349
+ const t = await resolveRecreateTarget(deps, target);
8350
+ if (!t) return { status: "not-found" };
8351
+ if (t.worktreePath === "") return { status: "no-path" };
8352
+ if (isExistingDir(t.worktreePath)) {
8353
+ return { status: "already-exists", branch: t.branch };
8354
+ }
8355
+ if (!t.repository) return { status: "no-repo" };
8356
+ const repoCheck = await assertRepoRoot(t.repository);
8357
+ if (!repoCheck.ok) {
8358
+ return { status: "bad-repo", httpStatus: repoCheck.status, error: repoCheck.error };
8359
+ }
8360
+ const key = `recreate:${t.worktreePath}`;
8361
+ if (worktreeInFlight.has(key)) return { status: "in-flight" };
8362
+ worktreeInFlight.add(key);
8363
+ try {
8364
+ const r = await recreateWorktree({
8365
+ repository: repoCheck.repo,
8366
+ worktreePath: t.worktreePath,
8367
+ branch: t.branch,
8368
+ originalHeadSha: t.originalHeadSha
8369
+ });
8370
+ return { status: "recreated", baseUsed: r.baseUsed, exact: r.exact, branch: r.branch };
8371
+ } finally {
8372
+ worktreeInFlight.delete(key);
8373
+ }
8374
+ }
8375
+ function recreateOutcomeToHttp(outcome) {
8376
+ switch (outcome.status) {
8377
+ case "not-found":
8378
+ return { httpStatus: 404, body: { error: "Target not found." } };
8379
+ case "no-path":
8380
+ return {
8381
+ httpStatus: 422,
8382
+ body: { error: "No recorded worktree path to recreate." }
8383
+ };
8384
+ case "no-repo":
8385
+ return {
8386
+ httpStatus: 422,
8387
+ body: {
8388
+ error: "Cannot recreate: no repository on record for this worktree."
8389
+ }
8390
+ };
8391
+ case "bad-repo":
8392
+ return { httpStatus: outcome.httpStatus, body: { error: outcome.error } };
8393
+ case "in-flight":
8394
+ return {
8395
+ httpStatus: 409,
8396
+ body: { error: "A recreate is already in progress for this worktree." }
8397
+ };
8398
+ case "already-exists":
8399
+ return {
8400
+ httpStatus: 200,
8401
+ body: {
8402
+ ok: true,
8403
+ alreadyExisted: true,
8404
+ baseUsed: outcome.branch ?? "HEAD",
8405
+ exact: true,
8406
+ branch: outcome.branch
8407
+ }
8408
+ };
8409
+ case "recreated":
8410
+ return {
8411
+ httpStatus: 200,
8412
+ body: {
8413
+ ok: true,
8414
+ baseUsed: outcome.baseUsed,
8415
+ exact: outcome.exact,
8416
+ branch: outcome.branch
8417
+ }
8418
+ };
8419
+ }
8420
+ }
8421
+
8124
8422
  // src/dashboard/repository-candidates.ts
8125
8423
  init_fs();
8126
8424
  init_parser();
@@ -8807,7 +9105,7 @@ async function assertRepoRoot(repoInput) {
8807
9105
  return { ok: false, status: 400, error: "`repository` is required." };
8808
9106
  }
8809
9107
  const repo = repoInput.trim();
8810
- if (!isAbsolute2(repo)) {
9108
+ if (!isAbsolute4(repo)) {
8811
9109
  return { ok: false, status: 400, error: "`repository` must be an absolute path." };
8812
9110
  }
8813
9111
  try {
@@ -10046,6 +10344,45 @@ ${entry}`;
10046
10344
  }
10047
10345
  }
10048
10346
  );
10347
+ router.post(
10348
+ "/api/projects/:slug/assignments/:aslug/worktree/recreate",
10349
+ async (req, res) => {
10350
+ try {
10351
+ const projectSlug = getParam(req.params.slug);
10352
+ const assignmentSlug = getParam(req.params.aslug);
10353
+ const outcome = await recreateForTarget(
10354
+ { projectsDir, assignmentsDir: assignmentsDir2 ?? "" },
10355
+ { kind: "assignment", projectSlug, assignmentSlug }
10356
+ );
10357
+ const { httpStatus, body } = recreateOutcomeToHttp(outcome);
10358
+ res.status(httpStatus).json(body);
10359
+ } catch (error) {
10360
+ console.error("Error recreating worktree:", error);
10361
+ res.status(500).json({ error: `Failed to recreate worktree: ${error.message}` });
10362
+ }
10363
+ }
10364
+ );
10365
+ router.post(
10366
+ "/api/assignments/:id/worktree/recreate",
10367
+ async (req, res) => {
10368
+ try {
10369
+ if (!assignmentsDir2) {
10370
+ res.status(501).json({ error: "Standalone assignments not configured on this server" });
10371
+ return;
10372
+ }
10373
+ const id = getParam(req.params.id);
10374
+ const outcome = await recreateForTarget(
10375
+ { projectsDir, assignmentsDir: assignmentsDir2 },
10376
+ { kind: "assignment", id }
10377
+ );
10378
+ const { httpStatus, body } = recreateOutcomeToHttp(outcome);
10379
+ res.status(httpStatus).json(body);
10380
+ } catch (error) {
10381
+ console.error("Error recreating worktree:", error);
10382
+ res.status(500).json({ error: `Failed to recreate worktree: ${error.message}` });
10383
+ }
10384
+ }
10385
+ );
10049
10386
  router.post("/api/projects/:slug/status-override", async (req, res) => {
10050
10387
  try {
10051
10388
  const projectSlug = getParam(req.params.slug);
@@ -11308,6 +11645,7 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
11308
11645
  const recordedPath = derivedPath ?? path ?? "";
11309
11646
  const pid = typeof rawPid === "number" && Number.isFinite(rawPid) && rawPid > 0 ? rawPid : null;
11310
11647
  const pidStartedAt = pid !== null ? captureProcessStartedAt(pid) : null;
11648
+ const originalHeadSha = isExistingDir(recordedPath) ? await captureHeadSha(recordedPath) : null;
11311
11649
  const session = {
11312
11650
  projectSlug: projectSlug || null,
11313
11651
  assignmentSlug: assignmentSlug || null,
@@ -11319,7 +11657,8 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
11319
11657
  description: description || null,
11320
11658
  transcriptPath: transcriptPath || null,
11321
11659
  pid,
11322
- pidStartedAt
11660
+ pidStartedAt,
11661
+ originalHeadSha
11323
11662
  };
11324
11663
  await appendSession("", session);
11325
11664
  broadcast?.({ type: "agent-sessions-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -11328,6 +11667,21 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
11328
11667
  res.status(500).json({ error: error instanceof Error ? error.message : "Registration failed" });
11329
11668
  }
11330
11669
  });
11670
+ router.post("/:sessionId/worktree/recreate", async (req, res) => {
11671
+ try {
11672
+ const { sessionId } = req.params;
11673
+ const outcome = await recreateForTarget(
11674
+ { projectsDir, assignmentsDir: assignmentsDir2 ?? "" },
11675
+ { kind: "session", id: sessionId }
11676
+ );
11677
+ const { httpStatus, body } = recreateOutcomeToHttp(outcome);
11678
+ res.status(httpStatus).json(body);
11679
+ } catch (error) {
11680
+ res.status(500).json({
11681
+ error: error instanceof Error ? error.message : "Failed to recreate worktree"
11682
+ });
11683
+ }
11684
+ });
11331
11685
  router.patch("/:sessionId", async (req, res) => {
11332
11686
  try {
11333
11687
  const { sessionId } = req.params;
@@ -11628,7 +11982,7 @@ import { Router as Router6 } from "express";
11628
11982
 
11629
11983
  // src/utils/terminal-probe.ts
11630
11984
  import { spawnSync as spawnSync4 } from "child_process";
11631
- import { existsSync } from "fs";
11985
+ import { existsSync as existsSync2 } from "fs";
11632
11986
  import { homedir as homedir2 } from "os";
11633
11987
  import { join as join2 } from "path";
11634
11988
  var APP_BUNDLE_IDS = {
@@ -11650,12 +12004,12 @@ function defaultApplicationsDirs() {
11650
12004
  }
11651
12005
  function findAppBundle(terminal, dirs = defaultApplicationsDirs()) {
11652
12006
  const fixed = APP_FIXED_PATHS[terminal];
11653
- if (fixed && existsSync(fixed)) return fixed;
12007
+ if (fixed && existsSync2(fixed)) return fixed;
11654
12008
  const bundleName = APP_BUNDLE_NAMES[terminal];
11655
12009
  if (bundleName) {
11656
12010
  for (const dir of dirs) {
11657
12011
  const candidate = join2(dir, bundleName);
11658
- if (existsSync(candidate)) return candidate;
12012
+ if (existsSync2(candidate)) return candidate;
11659
12013
  }
11660
12014
  }
11661
12015
  return null;
@@ -11692,50 +12046,6 @@ function probeTerminalInstalled(terminal, opts = {}) {
11692
12046
  return { ok: false, reason: "no-probe-available" };
11693
12047
  }
11694
12048
 
11695
- // src/dashboard/api-launch-preflight.ts
11696
- init_api();
11697
-
11698
- // src/launch/cwd.ts
11699
- import { existsSync as existsSync2, statSync as statSync2 } from "fs";
11700
- import { isAbsolute as isAbsolute3 } from "path";
11701
- function isExistingDir(p) {
11702
- if (!p || !isAbsolute3(p)) return false;
11703
- try {
11704
- return existsSync2(p) && statSync2(p).isDirectory();
11705
- } catch {
11706
- return false;
11707
- }
11708
- }
11709
- function resolveWorkspaceCwd(input) {
11710
- const { worktreePath, repository, branch, assignmentSlug } = input;
11711
- if (isExistingDir(worktreePath)) {
11712
- return { cwd: worktreePath, fallbackWarning: null, invalidReason: null };
11713
- }
11714
- if (isExistingDir(repository)) {
11715
- const fallbackWarning = worktreePath ? `syntaur: workspace.worktreePath ${worktreePath} is not an existing directory for ${assignmentSlug} \u2014 launching in ${repository}` : formatFallbackCwdWarning({
11716
- assignmentSlug,
11717
- workspaceDir: repository,
11718
- worktreePath,
11719
- branch
11720
- });
11721
- return { cwd: repository, fallbackWarning, invalidReason: null };
11722
- }
11723
- const shown = (p) => p && p.trim().length > 0 ? p : "(unset)";
11724
- return {
11725
- cwd: null,
11726
- fallbackWarning: null,
11727
- invalidReason: `workspace path invalid for ${assignmentSlug}: tried worktreePath ${shown(worktreePath)} and repository ${shown(repository)} \u2014 neither is an existing directory`
11728
- };
11729
- }
11730
- function formatFallbackCwdWarning(opts) {
11731
- const missing = [];
11732
- if (!opts.worktreePath) missing.push("worktreePath");
11733
- if (!opts.branch) missing.push("branch");
11734
- if (missing.length === 0) return null;
11735
- const fields = missing.map((m) => `workspace.${m}`).join(" and ");
11736
- return `syntaur: ${fields} not set for ${opts.assignmentSlug} \u2014 launching in ${opts.workspaceDir}`;
11737
- }
11738
-
11739
12049
  // src/dashboard/api-launch-preflight.ts
11740
12050
  function createLaunchPreflightRouter(projectsDir, assignmentsDir2) {
11741
12051
  const router = Router6();
@@ -11763,29 +12073,44 @@ function createLaunchPreflightRouter(projectsDir, assignmentsDir2) {
11763
12073
  return;
11764
12074
  }
11765
12075
  const target = body.target;
11766
- if (target && target.kind === "assignment" && typeof target.id === "string") {
11767
- const detail = await getAssignmentDetailById(
11768
- projectsDir,
11769
- assignmentsDir2,
11770
- target.id
12076
+ if (target && (target.kind === "assignment" || target.kind === "session") && typeof target.id === "string") {
12077
+ const resolved = await resolveRecreateTarget(
12078
+ { projectsDir, assignmentsDir: assignmentsDir2 },
12079
+ target.kind === "assignment" ? { kind: "assignment", id: target.id } : { kind: "session", id: target.id }
11771
12080
  );
11772
- if (detail) {
11773
- const picked = resolveWorkspaceCwd({
11774
- worktreePath: detail.workspace.worktreePath,
11775
- repository: detail.workspace.repository,
11776
- branch: detail.workspace.branch,
11777
- assignmentSlug: detail.slug
11778
- });
11779
- if (picked.cwd === null) {
11780
- const response2 = {
11781
- ok: false,
11782
- terminal,
11783
- reason: "workspace-path-invalid",
11784
- message: picked.invalidReason ?? "This assignment has no valid workspace directory."
11785
- };
11786
- res.json(response2);
11787
- return;
11788
- }
12081
+ if (resolved && resolved.missing) {
12082
+ const response2 = resolved.recreatable ? {
12083
+ ok: false,
12084
+ terminal,
12085
+ reason: "workspace-path-invalid",
12086
+ message: `Worktree ${resolved.worktreePath} was deleted.`,
12087
+ recreate: {
12088
+ kind: resolved.kind,
12089
+ id: resolved.id,
12090
+ projectSlug: resolved.projectSlug,
12091
+ assignmentSlug: resolved.assignmentSlug,
12092
+ deletedPath: resolved.worktreePath,
12093
+ repository: resolved.repository,
12094
+ branch: resolved.branch
12095
+ }
12096
+ } : {
12097
+ ok: false,
12098
+ terminal,
12099
+ reason: "workspace-path-invalid",
12100
+ message: `Worktree ${resolved.worktreePath} was deleted and can't be auto-recreated (no repository on record). Set a valid workspace for this assignment, then try again.`
12101
+ };
12102
+ res.json(response2);
12103
+ return;
12104
+ }
12105
+ if (resolved && resolved.kind === "assignment" && resolved.worktreePath === "" && !isExistingDir(resolved.repository)) {
12106
+ const response2 = {
12107
+ ok: false,
12108
+ terminal,
12109
+ reason: "workspace-path-invalid",
12110
+ message: "This assignment has no valid workspace directory. Set a repository or worktree, then try again."
12111
+ };
12112
+ res.json(response2);
12113
+ return;
11789
12114
  }
11790
12115
  }
11791
12116
  const response = { ok: true, terminal };