orcasynth 1.4.0 → 1.4.3

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 (222) hide show
  1. package/dist/api/server.js +65 -23
  2. package/dist/cli/launcher.js +9 -1
  3. package/dist/cli/menu.js +15 -2
  4. package/dist/integrations/projectFiles.js +16 -0
  5. package/dist/integrations/usage/claude.js +7 -7
  6. package/dist/integrations/usage/codex.js +5 -7
  7. package/dist/integrations/usage/index.js +2 -8
  8. package/dist/integrations/usage/walk.js +14 -0
  9. package/dist/overseer/stuckDetector.js +4 -10
  10. package/dist/shared/id.js +7 -0
  11. package/dist/shared/time.js +12 -0
  12. package/dist/store/cascade.js +24 -0
  13. package/dist/store/db.js +3 -0
  14. package/dist/store/projectStore.js +8 -6
  15. package/dist/store/schema.sql +1 -1
  16. package/dist/store/taskStore.js +2 -8
  17. package/package.json +1 -1
  18. package/web-dist/.next/BUILD_ID +1 -1
  19. package/web-dist/.next/build-manifest.json +3 -3
  20. package/web-dist/.next/server/app/_global-error.html +1 -1
  21. package/web-dist/.next/server/app/_global-error.rsc +1 -1
  22. package/web-dist/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  23. package/web-dist/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  24. package/web-dist/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  25. package/web-dist/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  26. package/web-dist/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  27. package/web-dist/.next/server/app/_not-found/page.js.nft.json +1 -1
  28. package/web-dist/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  29. package/web-dist/.next/server/app/_not-found.html +1 -1
  30. package/web-dist/.next/server/app/_not-found.rsc +11 -11
  31. package/web-dist/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
  32. package/web-dist/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  33. package/web-dist/.next/server/app/_not-found.segments/_index.segment.rsc +6 -6
  34. package/web-dist/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  35. package/web-dist/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  36. package/web-dist/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  37. package/web-dist/.next/server/app/account/page.js.nft.json +1 -1
  38. package/web-dist/.next/server/app/account/page_client-reference-manifest.js +1 -1
  39. package/web-dist/.next/server/app/account.html +1 -1
  40. package/web-dist/.next/server/app/account.rsc +13 -13
  41. package/web-dist/.next/server/app/account.segments/_full.segment.rsc +13 -13
  42. package/web-dist/.next/server/app/account.segments/_head.segment.rsc +4 -4
  43. package/web-dist/.next/server/app/account.segments/_index.segment.rsc +6 -6
  44. package/web-dist/.next/server/app/account.segments/_tree.segment.rsc +2 -2
  45. package/web-dist/.next/server/app/account.segments/account/__PAGE__.segment.rsc +4 -4
  46. package/web-dist/.next/server/app/account.segments/account.segment.rsc +3 -3
  47. package/web-dist/.next/server/app/dash/page.js.nft.json +1 -1
  48. package/web-dist/.next/server/app/dash/page_client-reference-manifest.js +1 -1
  49. package/web-dist/.next/server/app/dash.html +1 -1
  50. package/web-dist/.next/server/app/dash.rsc +13 -13
  51. package/web-dist/.next/server/app/dash.segments/_full.segment.rsc +13 -13
  52. package/web-dist/.next/server/app/dash.segments/_head.segment.rsc +4 -4
  53. package/web-dist/.next/server/app/dash.segments/_index.segment.rsc +6 -6
  54. package/web-dist/.next/server/app/dash.segments/_tree.segment.rsc +2 -2
  55. package/web-dist/.next/server/app/dash.segments/dash/__PAGE__.segment.rsc +4 -4
  56. package/web-dist/.next/server/app/dash.segments/dash.segment.rsc +3 -3
  57. package/web-dist/.next/server/app/escalations/page.js.nft.json +1 -1
  58. package/web-dist/.next/server/app/escalations/page_client-reference-manifest.js +1 -1
  59. package/web-dist/.next/server/app/escalations.html +1 -1
  60. package/web-dist/.next/server/app/escalations.rsc +13 -13
  61. package/web-dist/.next/server/app/escalations.segments/_full.segment.rsc +13 -13
  62. package/web-dist/.next/server/app/escalations.segments/_head.segment.rsc +4 -4
  63. package/web-dist/.next/server/app/escalations.segments/_index.segment.rsc +6 -6
  64. package/web-dist/.next/server/app/escalations.segments/_tree.segment.rsc +2 -2
  65. package/web-dist/.next/server/app/escalations.segments/escalations/__PAGE__.segment.rsc +4 -4
  66. package/web-dist/.next/server/app/escalations.segments/escalations.segment.rsc +3 -3
  67. package/web-dist/.next/server/app/index.html +1 -1
  68. package/web-dist/.next/server/app/index.rsc +13 -13
  69. package/web-dist/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  70. package/web-dist/.next/server/app/index.segments/_full.segment.rsc +13 -13
  71. package/web-dist/.next/server/app/index.segments/_head.segment.rsc +4 -4
  72. package/web-dist/.next/server/app/index.segments/_index.segment.rsc +6 -6
  73. package/web-dist/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  74. package/web-dist/.next/server/app/kanban/page.js.nft.json +1 -1
  75. package/web-dist/.next/server/app/kanban/page_client-reference-manifest.js +1 -1
  76. package/web-dist/.next/server/app/kanban.html +1 -1
  77. package/web-dist/.next/server/app/kanban.rsc +13 -13
  78. package/web-dist/.next/server/app/kanban.segments/_full.segment.rsc +13 -13
  79. package/web-dist/.next/server/app/kanban.segments/_head.segment.rsc +4 -4
  80. package/web-dist/.next/server/app/kanban.segments/_index.segment.rsc +6 -6
  81. package/web-dist/.next/server/app/kanban.segments/_tree.segment.rsc +2 -2
  82. package/web-dist/.next/server/app/kanban.segments/kanban/__PAGE__.segment.rsc +4 -4
  83. package/web-dist/.next/server/app/kanban.segments/kanban.segment.rsc +3 -3
  84. package/web-dist/.next/server/app/onboarding/page.js.nft.json +1 -1
  85. package/web-dist/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  86. package/web-dist/.next/server/app/onboarding.html +1 -1
  87. package/web-dist/.next/server/app/onboarding.rsc +13 -13
  88. package/web-dist/.next/server/app/onboarding.segments/_full.segment.rsc +13 -13
  89. package/web-dist/.next/server/app/onboarding.segments/_head.segment.rsc +4 -4
  90. package/web-dist/.next/server/app/onboarding.segments/_index.segment.rsc +6 -6
  91. package/web-dist/.next/server/app/onboarding.segments/_tree.segment.rsc +2 -2
  92. package/web-dist/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +4 -4
  93. package/web-dist/.next/server/app/onboarding.segments/onboarding.segment.rsc +3 -3
  94. package/web-dist/.next/server/app/page.js.nft.json +1 -1
  95. package/web-dist/.next/server/app/page_client-reference-manifest.js +1 -1
  96. package/web-dist/.next/server/app/projects/page.js.nft.json +1 -1
  97. package/web-dist/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  98. package/web-dist/.next/server/app/projects.html +1 -1
  99. package/web-dist/.next/server/app/projects.rsc +13 -13
  100. package/web-dist/.next/server/app/projects.segments/_full.segment.rsc +13 -13
  101. package/web-dist/.next/server/app/projects.segments/_head.segment.rsc +4 -4
  102. package/web-dist/.next/server/app/projects.segments/_index.segment.rsc +6 -6
  103. package/web-dist/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
  104. package/web-dist/.next/server/app/projects.segments/projects/__PAGE__.segment.rsc +4 -4
  105. package/web-dist/.next/server/app/projects.segments/projects.segment.rsc +3 -3
  106. package/web-dist/.next/server/app/sessions/page.js.nft.json +1 -1
  107. package/web-dist/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  108. package/web-dist/.next/server/app/sessions.html +1 -1
  109. package/web-dist/.next/server/app/sessions.rsc +13 -13
  110. package/web-dist/.next/server/app/sessions.segments/_full.segment.rsc +13 -13
  111. package/web-dist/.next/server/app/sessions.segments/_head.segment.rsc +4 -4
  112. package/web-dist/.next/server/app/sessions.segments/_index.segment.rsc +6 -6
  113. package/web-dist/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
  114. package/web-dist/.next/server/app/sessions.segments/sessions/__PAGE__.segment.rsc +4 -4
  115. package/web-dist/.next/server/app/sessions.segments/sessions.segment.rsc +3 -3
  116. package/web-dist/.next/server/app/settings/page.js.nft.json +1 -1
  117. package/web-dist/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  118. package/web-dist/.next/server/app/settings.html +1 -1
  119. package/web-dist/.next/server/app/settings.rsc +13 -13
  120. package/web-dist/.next/server/app/settings.segments/_full.segment.rsc +13 -13
  121. package/web-dist/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  122. package/web-dist/.next/server/app/settings.segments/_index.segment.rsc +6 -6
  123. package/web-dist/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  124. package/web-dist/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  125. package/web-dist/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  126. package/web-dist/.next/server/app/tasks/page.js.nft.json +1 -1
  127. package/web-dist/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  128. package/web-dist/.next/server/app/tasks.html +1 -1
  129. package/web-dist/.next/server/app/tasks.rsc +13 -13
  130. package/web-dist/.next/server/app/tasks.segments/_full.segment.rsc +13 -13
  131. package/web-dist/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  132. package/web-dist/.next/server/app/tasks.segments/_index.segment.rsc +6 -6
  133. package/web-dist/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  134. package/web-dist/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  135. package/web-dist/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  136. package/web-dist/.next/server/app/timeline/page.js.nft.json +1 -1
  137. package/web-dist/.next/server/app/timeline/page_client-reference-manifest.js +1 -1
  138. package/web-dist/.next/server/app/timeline.html +1 -1
  139. package/web-dist/.next/server/app/timeline.rsc +13 -13
  140. package/web-dist/.next/server/app/timeline.segments/_full.segment.rsc +13 -13
  141. package/web-dist/.next/server/app/timeline.segments/_head.segment.rsc +4 -4
  142. package/web-dist/.next/server/app/timeline.segments/_index.segment.rsc +6 -6
  143. package/web-dist/.next/server/app/timeline.segments/_tree.segment.rsc +2 -2
  144. package/web-dist/.next/server/app/timeline.segments/timeline/__PAGE__.segment.rsc +4 -4
  145. package/web-dist/.next/server/app/timeline.segments/timeline.segment.rsc +3 -3
  146. package/web-dist/.next/server/app/users/page.js.nft.json +1 -1
  147. package/web-dist/.next/server/app/users/page_client-reference-manifest.js +1 -1
  148. package/web-dist/.next/server/app/users.html +1 -1
  149. package/web-dist/.next/server/app/users.rsc +13 -13
  150. package/web-dist/.next/server/app/users.segments/_full.segment.rsc +13 -13
  151. package/web-dist/.next/server/app/users.segments/_head.segment.rsc +4 -4
  152. package/web-dist/.next/server/app/users.segments/_index.segment.rsc +6 -6
  153. package/web-dist/.next/server/app/users.segments/_tree.segment.rsc +2 -2
  154. package/web-dist/.next/server/app/users.segments/users/__PAGE__.segment.rsc +4 -4
  155. package/web-dist/.next/server/app/users.segments/users.segment.rsc +3 -3
  156. package/web-dist/.next/server/chunks/[root-of-the-server]__0bvp8h1._.js +1 -1
  157. package/web-dist/.next/server/chunks/[root-of-the-server]__1dgbigm._.js +1 -1
  158. package/web-dist/.next/server/chunks/[root-of-the-server]__1wxxtv8._.js +1 -1
  159. package/web-dist/.next/server/chunks/ssr/{[root-of-the-server]__1tooevx._.js → [root-of-the-server]__0i_7o4p._.js} +2 -2
  160. package/web-dist/.next/server/chunks/ssr/_015zf-4._.js +1 -1
  161. package/web-dist/.next/server/chunks/ssr/_04ezsju._.js +3 -0
  162. package/web-dist/.next/server/chunks/ssr/_04o_q14._.js +1 -1
  163. package/web-dist/.next/server/chunks/ssr/_057a06r._.js +1 -1
  164. package/web-dist/.next/server/chunks/ssr/_085pshu._.js +1 -1
  165. package/web-dist/.next/server/chunks/ssr/_0tc9z5_._.js +1 -1
  166. package/web-dist/.next/server/chunks/ssr/_0tzourm._.js +1 -1
  167. package/web-dist/.next/server/chunks/ssr/_12jhzvy._.js +3 -0
  168. package/web-dist/.next/server/chunks/ssr/_136wthy._.js +3 -0
  169. package/web-dist/.next/server/chunks/ssr/_193-v_i._.js +3 -0
  170. package/web-dist/.next/server/chunks/ssr/_1heytlk._.js +3 -0
  171. package/web-dist/.next/server/chunks/ssr/_1mjzb9s._.js +1 -1
  172. package/web-dist/.next/server/chunks/ssr/_1xwktd-._.js +1 -1
  173. package/web-dist/.next/server/chunks/ssr/_1zscr7t._.js +3 -0
  174. package/web-dist/.next/server/chunks/ssr/app_dash_page_tsx_12v0wx-._.js +1 -1
  175. package/web-dist/.next/server/chunks/ssr/app_kanban_page_tsx_06_8oyf._.js +1 -1
  176. package/web-dist/.next/server/chunks/ssr/app_projects_page_tsx_1w-8z74._.js +3 -3
  177. package/web-dist/.next/server/chunks/ssr/app_tasks_page_tsx_1p6mxbw._.js +1 -1
  178. package/web-dist/.next/server/chunks/ssr/app_timeline_page_tsx_0thxir0._.js +1 -1
  179. package/web-dist/.next/server/chunks/ssr/components_shell_Shell_tsx_1e5c27h._.js +1 -1
  180. package/web-dist/.next/server/middleware-build-manifest.js +3 -3
  181. package/web-dist/.next/server/pages/404.html +1 -1
  182. package/web-dist/.next/server/pages/500.html +1 -1
  183. package/web-dist/.next/static/chunks/0l1t1fcd-_jj9.js +1 -0
  184. package/web-dist/.next/static/chunks/11xbsx12drypd.js +1 -0
  185. package/web-dist/.next/static/chunks/186xbnkxm5iu3.js +1 -0
  186. package/web-dist/.next/static/chunks/1c_1_ca4-vgc7.js +1 -0
  187. package/web-dist/.next/static/chunks/{3wo9m5v__3pi2.js → 1f5f3qgbcjv4u.js} +1 -1
  188. package/web-dist/.next/static/chunks/1t-dast4rat3s.css +2 -0
  189. package/web-dist/.next/static/chunks/1wsag7zex3h4_.js +1 -0
  190. package/web-dist/.next/static/chunks/24unfsl4do--1.js +1 -0
  191. package/web-dist/.next/static/chunks/2bfjq22q8xyfs.js +1 -0
  192. package/web-dist/.next/static/chunks/2kuzf7llj_291.js +1 -0
  193. package/web-dist/.next/static/chunks/2pbhzo7fu4xcs.js +11 -0
  194. package/web-dist/.next/static/chunks/30ztbacooyerd.js +1 -0
  195. package/web-dist/.next/static/chunks/34bew8owm40fg.js +1 -0
  196. package/web-dist/.next/static/chunks/3a7pgnw_6io2v.js +1 -0
  197. package/web-dist/.next/static/chunks/3k-swqzkcvlzm.js +1 -0
  198. package/web-dist/.next/static/chunks/{2-j0lduvppz4v.js → 3saus_snl5ri7.js} +1 -1
  199. package/web-dist/.next/server/chunks/ssr/_09x5h4x._.js +0 -3
  200. package/web-dist/.next/server/chunks/ssr/_0lctmoh._.js +0 -3
  201. package/web-dist/.next/server/chunks/ssr/_0zho8fx._.js +0 -3
  202. package/web-dist/.next/server/chunks/ssr/_1812xdn._.js +0 -3
  203. package/web-dist/.next/server/chunks/ssr/_1m2qx8p._.js +0 -3
  204. package/web-dist/.next/server/chunks/ssr/_1w24b42._.js +0 -3
  205. package/web-dist/.next/server/chunks/ssr/app_sessions_page_tsx_0r1_4_3._.js +0 -3
  206. package/web-dist/.next/static/chunks/05jfk0-07tiga.js +0 -11
  207. package/web-dist/.next/static/chunks/0_5u1_tmtxso3.js +0 -1
  208. package/web-dist/.next/static/chunks/16rotf1pkrqyn.js +0 -1
  209. package/web-dist/.next/static/chunks/201p8-_l1h8w8.js +0 -1
  210. package/web-dist/.next/static/chunks/2_uoi0tzglv_7.js +0 -1
  211. package/web-dist/.next/static/chunks/2m810vyp96um0.js +0 -1
  212. package/web-dist/.next/static/chunks/2sm_cc2r9sjzp.js +0 -1
  213. package/web-dist/.next/static/chunks/2xur1zqckjlq3.js +0 -1
  214. package/web-dist/.next/static/chunks/35babynx5l240.js +0 -1
  215. package/web-dist/.next/static/chunks/3bdts7sstygr0.css +0 -2
  216. package/web-dist/.next/static/chunks/3i0jpc47nxz7y.js +0 -1
  217. package/web-dist/.next/static/chunks/3soklmhd2y9yp.js +0 -1
  218. package/web-dist/.next/static/chunks/3ybvfq13cp-hb.js +0 -1
  219. package/web-dist/.next/static/chunks/3yqt3l54sr5si.js +0 -1
  220. /package/web-dist/.next/static/{CxOYTELv4rEqlUTUze_od → -qI9ABgqZPR_Ri56jzVI5}/_buildManifest.js +0 -0
  221. /package/web-dist/.next/static/{CxOYTELv4rEqlUTUze_od → -qI9ABgqZPR_Ri56jzVI5}/_clientMiddlewareManifest.js +0 -0
  222. /package/web-dist/.next/static/{CxOYTELv4rEqlUTUze_od → -qI9ABgqZPR_Ri56jzVI5}/_ssgManifest.js +0 -0
@@ -1,11 +1,12 @@
1
- import { basename, join } from 'node:path';
1
+ import { basename, dirname, join } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
2
3
  import { homedir } from 'node:os';
3
4
  import { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync } from 'node:fs';
4
- import { randomBytes, createHmac, timingSafeEqual } from 'node:crypto';
5
+ import { createHmac, timingSafeEqual } from 'node:crypto';
5
6
  import { hermesStatus, installHermesPlugin } from '../integrations/hermesInstall.js';
6
7
  import { detectClis } from '../integrations/cliDetection.js';
7
8
  import { readTaskUsage } from '../integrations/usage/index.js';
8
- import { listProjectFiles, readProjectFile, writeProjectFile, readProjectBytes, createProjectFile, createProjectDir, deleteProjectEntry, renameProjectEntry, copyProjectEntry, projectFileAtHead, projectFileDiff, projectCommitDiff, projectCommitFiles, projectCommitFileDiff, projectCommitLog, projectChangedFiles, projectWorkingDiff, projectReviewDiff } from '../integrations/projectFiles.js';
9
+ import { listProjectFiles, readProjectFile, writeProjectFile, readProjectBytes, createProjectFile, createProjectDir, deleteProjectEntry, renameProjectEntry, copyProjectEntry, projectFileAtHead, projectFileDiff, projectCommitDiff, projectCommitFiles, projectCommitFileDiff, projectCommitLog, projectChangedFiles, projectWorkingDiff, projectReviewDiff, isProjectImage } from '../integrations/projectFiles.js';
9
10
  import { Hono } from 'hono';
10
11
  import { cors } from 'hono/cors';
11
12
  import { streamSSE } from 'hono/streaming';
@@ -21,10 +22,21 @@ import { uniqueName } from '../daemon/uniqueName.js';
21
22
  import { assembleMissionDetail } from '../store/missionDetail.js';
22
23
  import { authMiddleware } from './auth.js';
23
24
  import { logger } from '../shared/logger.js';
25
+ import { shortId } from '../shared/id.js';
24
26
  /** How many times an L3 mission auto-re-spawns a phase that the post-done review rejected before it
25
27
  * gives up and escalates to a human. Mirrors the stuck detector's `maxRelaunch` (2) so the two
26
28
  * bounded-retry loops behave consistently. */
27
29
  const REVIEW_FIX_BUDGET = 2;
30
+ /** This package's version, read once from its package.json (two dirs up from dist/api/server.js, and
31
+ * likewise from src/api/server.ts in dev/tests). Surfaced on /health so the web UI can show it. */
32
+ const ORCA_VERSION = (() => {
33
+ try {
34
+ return JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json'), 'utf8')).version ?? '0.0.0';
35
+ }
36
+ catch {
37
+ return '0.0.0';
38
+ }
39
+ })();
28
40
  export function createServer(d) {
29
41
  const log = logger('api');
30
42
  // Core reasoning stores are optional in deps for back-compat with existing call sites/tests; the
@@ -42,7 +54,7 @@ export function createServer(d) {
42
54
  log.error('unhandled route error', err);
43
55
  return c.json({ error: 'internal error' }, 500);
44
56
  });
45
- app.get('/health', c => c.json({ ok: true }));
57
+ app.get('/health', c => c.json({ ok: true, version: ORCA_VERSION }));
46
58
  // Public: lets the web decide whether to show onboarding (no users yet) or the login form.
47
59
  app.get('/setup', c => c.json({ needsSetup: d.users ? d.users.count() === 0 : false }));
48
60
  if (d.users) {
@@ -457,7 +469,7 @@ export function createServer(d) {
457
469
  function persistPlan(job) {
458
470
  const path = pathFor(job.projectId);
459
471
  const allowedExecs = d.config.get().allowedExecs;
460
- const newId = () => `${basename(path)}-${randomBytes(4).toString('hex')}`;
472
+ const newId = () => shortId(basename(path));
461
473
  const epicId = job.epicId ?? newId();
462
474
  let epic = d.tasks.get(epicId);
463
475
  if (!epic) {
@@ -562,7 +574,8 @@ export function createServer(d) {
562
574
  return c.json({ error: 'forbidden' }, 403);
563
575
  }
564
576
  const id = Number(c.req.param('id'));
565
- if (!d.projects.get(id))
577
+ const cur = d.projects.get(id);
578
+ if (!cur)
566
579
  return c.json({ error: 'project not found' }, 404);
567
580
  const b = await c.req.json();
568
581
  const patch = {};
@@ -570,6 +583,13 @@ export function createServer(d) {
570
583
  patch.path = b.path.trim();
571
584
  if (typeof b.notes === 'string')
572
585
  patch.notes = b.notes;
586
+ // Icon is a project-relative image path. '' clears it; anything else must resolve to a real image
587
+ // file inside the project root (guards against path traversal / pointing at a non-image).
588
+ if (typeof b.icon === 'string') {
589
+ if (b.icon !== '' && !isProjectImage(cur.path, b.icon))
590
+ return c.json({ error: 'invalid icon path' }, 400);
591
+ patch.icon = b.icon;
592
+ }
573
593
  return c.json(d.projects.update(id, patch));
574
594
  });
575
595
  // Remove a project from orca entirely: cascades to its tasks, missions, agents and access grants
@@ -891,12 +911,35 @@ export function createServer(d) {
891
911
  }
892
912
  return c.json(scoped);
893
913
  });
914
+ /** Release the dependents a phase's review gate was holding: clear this phase's `gatedby:<id>` hold
915
+ * and re-open each dependent that no OTHER review still gates (a DAG dependent can be held by several
916
+ * predecessors at once). Re-check 'blocked' so a human's manual change is never overridden. Single
917
+ * source of truth for both an agent-approved verdict and a human approval, so they behave
918
+ * identically. Returns the ids actually re-opened. */
919
+ function releaseGatedDependents(phaseId) {
920
+ const reopened = [];
921
+ for (const e of d.tasks.allDeps()) {
922
+ if (e.depends_on_id !== phaseId)
923
+ continue;
924
+ const dep = d.tasks.get(e.task_id);
925
+ if (!dep || !dep.labels.includes(`gatedby:${phaseId}`))
926
+ continue;
927
+ d.tasks.removeLabel(dep.id, `gatedby:${phaseId}`);
928
+ const stillGated = d.tasks.get(dep.id).labels.some((l) => l.startsWith('gatedby:'));
929
+ if (!stillGated && dep.status === 'blocked') {
930
+ d.tasks.setStatus(dep.id, 'open');
931
+ d.bus.publish({ type: 'task', taskId: dep.id, status: 'open' });
932
+ reopened.push(dep.id);
933
+ }
934
+ }
935
+ return reopened;
936
+ }
894
937
  app.post('/tasks', async (c) => {
895
938
  const b = await c.req.json();
896
939
  const target = resolveTarget(c, b.project_id);
897
940
  if ('error' in target)
898
941
  return c.json({ error: target.error }, target.status);
899
- const id = b.id ?? `${basename(target.project.path)}-${randomBytes(4).toString('hex')}`;
942
+ const id = b.id ?? shortId(basename(target.project.path));
900
943
  const created = d.tasks.create({ id, project_id: target.project.id, title: b.title, type: b.type, priority: b.priority, description: b.description, scheduled_at: b.scheduled_at, autostart: b.autostart });
901
944
  if (Array.isArray(b.deps))
902
945
  d.tasks.setDeps(created.id, b.deps);
@@ -993,22 +1036,8 @@ export function createServer(d) {
993
1036
  d.bus.publish({ type: 'review', missionId: mission.id, taskId: id, approve: approved, rationale: verdict.rationale });
994
1037
  if (approved) {
995
1038
  // Gate opens: release the gated dependents and tick so the next phase spawns promptly
996
- // rather than waiting up to the 90s interval. Re-check 'blocked' so a human's manual
997
- // change is never overridden.
998
- for (const depId of gated) {
999
- const dep = d.tasks.get(depId);
1000
- if (!dep)
1001
- continue;
1002
- d.tasks.removeLabel(depId, `gatedby:${id}`); // clear this review's hold…
1003
- // …and re-open only when no OTHER review still gates the dependent (a DAG dependent
1004
- // can be held by several predecessors at once). Re-check 'blocked' so a human's
1005
- // manual change is never overridden.
1006
- const stillGated = d.tasks.get(depId).labels.some((l) => l.startsWith('gatedby:'));
1007
- if (!stillGated && dep.status === 'blocked') {
1008
- d.tasks.setStatus(dep.id, 'open');
1009
- d.bus.publish({ type: 'task', taskId: dep.id, status: 'open' });
1010
- }
1011
- }
1039
+ // rather than waiting up to the 90s interval.
1040
+ releaseGatedDependents(id);
1012
1041
  void d.engine.tick(mission.id).catch((e) => log.error('post-review tick failed', e));
1013
1042
  return;
1014
1043
  }
@@ -1042,6 +1071,19 @@ export function createServer(d) {
1042
1071
  d.tasks.setDeps(id, b.deps);
1043
1072
  return c.json(d.tasks.get(id));
1044
1073
  });
1074
+ // Human approval of an escalated phase: accept its result and release the review gate it holds,
1075
+ // re-opening only the dependents no OTHER predecessor still gates (mirrors the agent-approved
1076
+ // verdict). The escalations inbox calls this instead of blindly opening every blocked dependent.
1077
+ app.post('/tasks/:id/approve-gate', c => {
1078
+ const id = c.req.param('id');
1079
+ const existing = d.tasks.get(id);
1080
+ if (!existing)
1081
+ return c.json({ error: 'task not found' }, 404);
1082
+ if (!canAccessProject(c, existing.project_id))
1083
+ return c.json({ error: 'forbidden' }, 403);
1084
+ const released = releaseGatedDependents(id);
1085
+ return c.json({ released });
1086
+ });
1045
1087
  app.get('/tasks/:id/deps', c => c.json(d.tasks.depsFor(c.req.param('id'))));
1046
1088
  app.delete('/tasks/:id', async (c) => {
1047
1089
  const id = c.req.param('id');
@@ -97,12 +97,20 @@ export async function start(env, deps) {
97
97
  const webPid = existing && isAlive(existing.web.pid) ? existing.web.pid
98
98
  : launch(webServer(), { PORT: String(webPort), HOSTNAME: '127.0.0.1', ORCA_DAEMON_URL: `http://127.0.0.1:${daemonPort}` });
99
99
  // Wait for the daemon to answer; the web proxies it, so it comes up second.
100
+ let healthy = false;
100
101
  for (let i = 0; i < attempts; i++) {
101
- if (await portHealthy(fetchFn, daemonPort, '/health'))
102
+ if (await portHealthy(fetchFn, daemonPort, '/health')) {
103
+ healthy = true;
102
104
  break;
105
+ }
103
106
  await new Promise((r) => setTimeout(r, pollMs));
104
107
  }
108
+ // Record state even on failure so `orca down`/`status` can see and clean up the spawned pids.
105
109
  const state = { daemon: { pid: daemonPid, port: daemonPort }, web: { pid: webPid, port: webPort }, version: deps.version, startedAt: now() };
106
110
  writeState(env, state);
111
+ // But never report success when the daemon never answered: a wedged or crash-looping daemon would
112
+ // otherwise be written as "orca is up". Surface it so the operator knows to check the logs.
113
+ if (!healthy)
114
+ throw new Error(`orca daemon did not become healthy on :${daemonPort} after ${Math.round((attempts * pollMs) / 1000)}s — check the logs in ${logDir(env)}`);
107
115
  return state;
108
116
  }
package/dist/cli/menu.js CHANGED
@@ -111,15 +111,28 @@ export async function menu(env, version) {
111
111
  continue;
112
112
  }
113
113
  if (action === 'open') {
114
+ // start() throws if the daemon never comes up — show it rather than opening a dead URL.
114
115
  if (!running) {
115
- await runLifecycle('up', env, deps);
116
+ try {
117
+ await runLifecycle('up', env, deps);
118
+ }
119
+ catch (e) {
120
+ p.log.error(e.message);
121
+ continue;
122
+ }
116
123
  }
117
124
  openUrl(webUrl);
118
125
  p.log.success(`Opening ${webUrl}`);
119
126
  continue;
120
127
  }
121
128
  if (action === 'up') {
122
- await runLifecycle('up', env, deps);
129
+ try {
130
+ await runLifecycle('up', env, deps);
131
+ }
132
+ catch (e) {
133
+ p.log.error(e.message);
134
+ continue;
135
+ }
123
136
  // A brand-new install has no admin yet — offer the wizard right after the daemon is up.
124
137
  try {
125
138
  if (await isFirstRun(fetch, BASE) && await runSetupWizard(BASE)) {
@@ -40,6 +40,22 @@ function safe(root, rel, forWrite = false) {
40
40
  throw new Error('path outside project');
41
41
  return abs;
42
42
  }
43
+ // Image extensions a project icon may point at. Matches what `/raw` serves and what the picker shows.
44
+ const IMAGE_EXT = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'ico', 'bmp', 'avif']);
45
+ /** True when `rel` resolves to a regular image file strictly inside the project root — used to validate
46
+ * a chosen project icon before persisting it. Never throws: a traversal/symlink escape, a missing
47
+ * file, a directory or a non-image extension all return false. */
48
+ export function isProjectImage(root, rel) {
49
+ if (!IMAGE_EXT.has((rel.split('.').pop() ?? '').toLowerCase()))
50
+ return false;
51
+ try {
52
+ const abs = safe(root, rel);
53
+ return statSync(abs).isFile();
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
43
59
  /** Flat list of a project's files and directories (relative paths), skipping VCS/build dirs. */
44
60
  export function listProjectFiles(root, maxDepth = 8) {
45
61
  const r = resolve(root);
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, readdirSync, existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { EMPTY_USAGE, SESSION_MATCH_SKEW_MS } from './types.js';
3
+ import { EMPTY_USAGE } from './types.js';
4
+ import { pickNthSession } from './walk.js';
4
5
  /** claude-code stores one JSONL transcript per session under
5
6
  * ~/.claude/projects/<encoded-cwd>/<sessionUuid>.jsonl, where each assistant event carries
6
7
  * `message.usage`. Pick the session that started when this spawn ran and sum its usage.
@@ -10,24 +11,23 @@ export function claudeUsage(home, dir, sinceMs, nth = 0) {
10
11
  const projDir = join(home, '.claude', 'projects', dir.replace(/[/._]/g, '-'));
11
12
  if (!existsSync(projDir))
12
13
  return null;
13
- // Transcripts started at/after the spawn window, ordered by start; `nth` picks one so
14
- // concurrent agents in the same project map to distinct sessions instead of colliding.
14
+ // Transcripts that carry a start time; `pickNthSession` keeps those in the spawn window,
15
+ // orders by start, and picks the nth so concurrent agents map to distinct sessions.
15
16
  const sessions = [];
16
17
  for (const name of readdirSync(projDir)) {
17
18
  if (!name.endsWith('.jsonl'))
18
19
  continue;
19
20
  const p = join(projDir, name);
20
21
  const start = firstEventMs(p);
21
- if (start == null || start < sinceMs - SESSION_MATCH_SKEW_MS)
22
+ if (start == null)
22
23
  continue;
23
24
  sessions.push({ path: p, start });
24
25
  }
25
- sessions.sort((a, b) => a.start - b.start);
26
- const best = sessions[nth];
26
+ const best = pickNthSession(sessions, sinceMs, nth);
27
27
  if (!best)
28
28
  return null;
29
29
  const u = { ...EMPTY_USAGE };
30
- for (const line of readFileSync(best.path, 'utf8').split('\n')) {
30
+ for (const line of readFileSync(best, 'utf8').split('\n')) {
31
31
  if (!line.trim())
32
32
  continue;
33
33
  try {
@@ -1,8 +1,7 @@
1
1
  import { readFileSync, existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { basename } from 'node:path';
4
- import { SESSION_MATCH_SKEW_MS } from './types.js';
5
- import { walkFiles } from './walk.js';
4
+ import { pickNthSession, walkFiles } from './walk.js';
6
5
  /** codex stores one rollout JSONL per session under ~/.codex/sessions/<Y>/<M>/<D>/rollout-*.jsonl,
7
6
  * carrying a cumulative `total_token_usage` object. Pick the rollout started when this spawn ran
8
7
  * and read its final cumulative usage. codex does not record cost (costUsd stays null). */
@@ -11,21 +10,20 @@ export function codexUsage(home, _dir, sinceMs, nth = 0) {
11
10
  if (!existsSync(root))
12
11
  return null;
13
12
  // codex rollouts aren't dir-scoped on disk, so concurrent codex agents can only be
14
- // disambiguated by start order; `nth` picks the rank-th rollout in the spawn window.
13
+ // disambiguated by start order; `pickNthSession` picks the rank-th rollout in the spawn window.
15
14
  const sessions = [];
16
15
  for (const f of walkFiles(root)) {
17
16
  if (!basename(f).startsWith('rollout-') || !f.endsWith('.jsonl'))
18
17
  continue;
19
18
  const start = rolloutStartMs(f);
20
- if (start == null || start < sinceMs - SESSION_MATCH_SKEW_MS)
19
+ if (start == null)
21
20
  continue;
22
21
  sessions.push({ path: f, start });
23
22
  }
24
- sessions.sort((a, b) => a.start - b.start);
25
- const best = sessions[nth];
23
+ const best = pickNthSession(sessions, sinceMs, nth);
26
24
  if (!best)
27
25
  return null;
28
- return finalUsage(best.path);
26
+ return finalUsage(best);
29
27
  }
30
28
  /** Start time of a rollout: its first event's ISO timestamp, else the timestamp in its filename. */
31
29
  function rolloutStartMs(path) {
@@ -4,13 +4,7 @@ import { opencodeUsage } from './opencode.js';
4
4
  import { claudeUsage } from './claude.js';
5
5
  import { codexUsage } from './codex.js';
6
6
  import { SESSION_MATCH_SKEW_MS } from './types.js';
7
- /** Parse a SQLite ("2026-06-19 11:13:20", UTC) or ISO timestamp to epoch ms. */
8
- function parseTs(ts) {
9
- if (!ts)
10
- return 0;
11
- const ms = Date.parse(ts.includes('T') ? ts : ts.replace(' ', 'T') + 'Z');
12
- return Number.isNaN(ms) ? 0 : ms;
13
- }
7
+ import { parseDbTs } from '../../shared/time.js';
14
8
  /** The precise spawn time (epoch ms) the agent launched, from the `started:<ms>` label — this is
15
9
  * sub-second and reflects real spawn order, unlike whole-second `created_at` (set at row insert).
16
10
  * Falls back to created_at for tasks launched before this label existed. */
@@ -21,7 +15,7 @@ function startedMs(task) {
21
15
  if (Number.isFinite(ms))
22
16
  return ms;
23
17
  }
24
- return parseTs(task.created_at);
18
+ return parseDbTs(task.created_at);
25
19
  }
26
20
  /** The resolved CLI program + model for a task (program normalized: 'opencode' | 'claude-code' |
27
21
  * 'codex' | …). */
@@ -1,5 +1,19 @@
1
1
  import { readdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { SESSION_MATCH_SKEW_MS } from './types.js';
4
+ /**
5
+ * From a set of session candidates `{path, start}`, pick the nth one that started within the spawn
6
+ * window (`start >= sinceMs - SESSION_MATCH_SKEW_MS`), ordered by start time. `nth` lets concurrent
7
+ * agents in the same project map to distinct sessions instead of colliding. Returns null when no
8
+ * candidate qualifies or `nth` is out of range. Single source of truth for the claude/codex
9
+ * session-select logic (opencode selects via SQL).
10
+ */
11
+ export function pickNthSession(candidates, sinceMs, nth) {
12
+ const inWindow = candidates
13
+ .filter((c) => c.start >= sinceMs - SESSION_MATCH_SKEW_MS)
14
+ .sort((a, b) => a.start - b.start);
15
+ return inWindow[nth]?.path ?? null;
16
+ }
3
17
  /** Recursively yield every file path under `dir` (depth-limited). Returns [] if dir is missing. */
4
18
  export function walkFiles(dir, maxDepth = 4) {
5
19
  const out = [];
@@ -1,3 +1,4 @@
1
+ import { parseDbTs } from '../shared/time.js';
1
2
  const agentOf = (t) => t.labels.find((l) => l.startsWith('agent:'))?.slice('agent:'.length) ?? null;
2
3
  /** Epoch-ms the task's agent was spawned: the precise `started:<ms>` label, falling back to the
3
4
  * whole-second `created_at` (stored UTC). Null only for a task that has neither. */
@@ -8,16 +9,9 @@ function startedOf(t) {
8
9
  if (Number.isFinite(n))
9
10
  return n;
10
11
  }
11
- if (t.created_at) {
12
- // SQLite `datetime('now')` is `YYYY-MM-DD HH:MM:SS` (UTC, no zone) normalise to ISO + 'Z'. But
13
- // if it already carries a zone (a 'T' separator implies an ISO string), parse it as-is — appending
14
- // a second 'Z' would yield `...ZZ` and a NaN. Brittle-format guard for #54.
15
- const iso = t.created_at.includes('T') ? t.created_at : t.created_at.replace(' ', 'T') + 'Z';
16
- const n = Date.parse(iso);
17
- if (Number.isFinite(n))
18
- return n;
19
- }
20
- return null;
12
+ // SQLite `datetime('now')` is `YYYY-MM-DD HH:MM:SS` (UTC, no zone); parseDbTs normalises it and
13
+ // returns 0 for an absent/unparseable value, which maps back to null (no usable start time).
14
+ return parseDbTs(t.created_at) || null;
21
15
  }
22
16
  /** in_progress tasks whose agent tmux session is no longer live — the agent exited or crashed
23
17
  * (no `orca close`), or the task never got an agent label. Shared by the startup zombie
@@ -0,0 +1,7 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ /** A short, collision-resistant id: `<prefix>-<random hex>` (e.g. `orca-1a2b3c4d`). `bytes` random
3
+ * bytes (default 4 → 8 hex chars). Single source of truth for the project-scoped task/epic id shape
4
+ * generated in the API. */
5
+ export function shortId(prefix, bytes = 4) {
6
+ return `${prefix}-${randomBytes(bytes).toString('hex')}`;
7
+ }
@@ -0,0 +1,12 @@
1
+ /** Parse a SQLite ("2026-06-19 11:13:20", space-separated UTC, no zone) or ISO timestamp to epoch
2
+ * ms. Returns 0 for an empty/null/unparseable value — callers treat 0 as "no usable time". SQLite
3
+ * emits a zone-less space form, so it's normalised to ISO and tagged UTC, but only when the value
4
+ * doesn't already carry a zone (a 'T' separator or trailing 'Z'), so an already-UTC string never
5
+ * gets a second 'Z' (which would yield an Invalid Date). Single source of truth for DB-string parsing. */
6
+ export function parseDbTs(ts) {
7
+ if (!ts)
8
+ return 0;
9
+ const norm = ts.includes('T') ? ts : ts.replace(' ', 'T') + (ts.endsWith('Z') ? '' : 'Z');
10
+ const ms = Date.parse(norm);
11
+ return Number.isNaN(ms) ? 0 : ms;
12
+ }
@@ -0,0 +1,24 @@
1
+ /** Delete tasks and everything scoped to them — the missions they drove and their dependency edges
2
+ * (in FK-safe order) — for either a whole project or an epic's subtree. The schema has no FK
3
+ * cascade, so this is the single source of truth for that teardown, shared by project removal and
4
+ * epic/mission deletion. Caller is responsible for its own transaction and for any non-task rows
5
+ * (a project also drops agents and access grants). Returns how many task rows were removed.
6
+ *
7
+ * - 'project': every task with `project_id = id`.
8
+ * - 'epic': the task `id` and its whole descendant subtree. */
9
+ export function deleteTasksAndDeps(db, scope, id) {
10
+ const ids = scope === 'project'
11
+ ? db.prepare('SELECT id FROM tasks WHERE project_id = ?').all(id).map((r) => r.id)
12
+ : [String(id), ...db.prepare(`WITH RECURSIVE sub(id) AS (
13
+ SELECT id FROM tasks WHERE parent_id = @root
14
+ UNION
15
+ SELECT t.id FROM tasks t JOIN sub ON t.parent_id = sub.id
16
+ )
17
+ SELECT id FROM sub`).all({ root: id }).map((r) => r.id)];
18
+ if (ids.length === 0)
19
+ return 0;
20
+ const ph = ids.map(() => '?').join(',');
21
+ db.prepare(`DELETE FROM missions WHERE epic_id IN (${ph})`).run(...ids);
22
+ db.prepare(`DELETE FROM task_deps WHERE task_id IN (${ph}) OR depends_on_id IN (${ph})`).run(...ids, ...ids);
23
+ return db.prepare(`DELETE FROM tasks WHERE id IN (${ph})`).run(...ids).changes;
24
+ }
package/dist/store/db.js CHANGED
@@ -23,6 +23,9 @@ export function openDb(path) {
23
23
  // exists is skipped via PRAGMA table_info, so we never rely on swallowing arbitrary ALTER errors
24
24
  // (a real failure — disk full, lock — now surfaces instead of being silently caught).
25
25
  addColumn(db, 'projects', 'notes', "TEXT NOT NULL DEFAULT ''");
26
+ // Project icon: a project-relative path to an image file already in the repo (e.g. assets/logo.png).
27
+ // Empty = the default folder glyph. Never an uploaded copy — it references a file in the project.
28
+ addColumn(db, 'projects', 'icon', "TEXT NOT NULL DEFAULT ''");
26
29
  addColumn(db, 'tasks', 'description', "TEXT NOT NULL DEFAULT ''");
27
30
  addColumn(db, 'tasks', 'scheduled_at', 'TEXT');
28
31
  addColumn(db, 'tasks', 'autostart', 'INTEGER NOT NULL DEFAULT 0');
@@ -1,3 +1,4 @@
1
+ import { deleteTasksAndDeps } from './cascade.js';
1
2
  export class ProjectStore {
2
3
  db;
3
4
  constructor(db) {
@@ -9,14 +10,16 @@ export class ProjectStore {
9
10
  }
10
11
  list() { return this.db.prepare('SELECT * FROM projects ORDER BY id').all(); }
11
12
  get(id) { return this.db.prepare('SELECT * FROM projects WHERE id = ?').get(id) ?? null; }
12
- /** Update a project's path and/or Pilot notes. The slug is the stable identifier and stays immutable. */
13
+ /** Update a project's path, Pilot notes and/or icon. The slug is the stable identifier and stays
14
+ * immutable. `icon` is a project-relative image path (or '' to clear it back to the default glyph). */
13
15
  update(id, patch) {
14
16
  const cur = this.get(id);
15
17
  if (!cur)
16
18
  return null;
17
19
  const path = patch.path ?? cur.path;
18
20
  const notes = patch.notes ?? cur.notes;
19
- this.db.prepare('UPDATE projects SET path = ?, notes = ? WHERE id = ?').run(path, notes, id);
21
+ const icon = patch.icon ?? cur.icon;
22
+ this.db.prepare('UPDATE projects SET path = ?, notes = ?, icon = ? WHERE id = ?').run(path, notes, icon, id);
20
23
  return this.get(id);
21
24
  }
22
25
  /** Remove a project from the registry and everything scoped to it: its tasks (+ their deps and any
@@ -27,10 +30,9 @@ export class ProjectStore {
27
30
  if (!this.get(id))
28
31
  return false;
29
32
  this.db.transaction(() => {
30
- this.db.prepare('DELETE FROM missions WHERE epic_id IN (SELECT id FROM tasks WHERE project_id = ?)').run(id);
31
- this.db.prepare('DELETE FROM task_deps WHERE task_id IN (SELECT id FROM tasks WHERE project_id = ?)' +
32
- ' OR depends_on_id IN (SELECT id FROM tasks WHERE project_id = ?)').run(id, id);
33
- this.db.prepare('DELETE FROM tasks WHERE project_id = ?').run(id);
33
+ // Tasks + their missions and dep edges go through the shared cascade; the agents, access
34
+ // grants and the project row itself are project-only and stay here.
35
+ deleteTasksAndDeps(this.db, 'project', id);
34
36
  this.db.prepare('DELETE FROM agents WHERE project_id = ?').run(id);
35
37
  this.db.prepare('DELETE FROM user_projects WHERE project_id = ?').run(id);
36
38
  this.db.prepare('DELETE FROM projects WHERE id = ?').run(id);
@@ -1,4 +1,4 @@
1
- CREATE TABLE IF NOT EXISTS projects (id INTEGER PRIMARY KEY, slug TEXT UNIQUE NOT NULL, path TEXT NOT NULL, notes TEXT NOT NULL DEFAULT '');
1
+ CREATE TABLE IF NOT EXISTS projects (id INTEGER PRIMARY KEY, slug TEXT UNIQUE NOT NULL, path TEXT NOT NULL, notes TEXT NOT NULL DEFAULT '', icon TEXT NOT NULL DEFAULT '');
2
2
  CREATE TABLE IF NOT EXISTS tasks (
3
3
  id TEXT PRIMARY KEY, project_id INTEGER NOT NULL, title TEXT NOT NULL,
4
4
  type TEXT NOT NULL DEFAULT 'task', status TEXT NOT NULL DEFAULT 'open',
@@ -1,3 +1,4 @@
1
+ import { deleteTasksAndDeps } from './cascade.js';
1
2
  const toTask = (r) => ({ ...r, labels: r.labels ? r.labels.split(',').filter(Boolean) : [] });
2
3
  export class TaskStore {
3
4
  db;
@@ -84,14 +85,7 @@ export class TaskStore {
84
85
  * dependency edges, and any mission those tasks drove. Used to remove a mission outright (not just
85
86
  * disengage it). Returns how many task rows were removed. */
86
87
  deleteEpic(epicId) {
87
- return this.db.transaction(() => {
88
- const ids = [epicId, ...this.descendants(epicId).map((t) => t.id)];
89
- const ph = ids.map(() => '?').join(',');
90
- this.db.prepare(`DELETE FROM missions WHERE epic_id IN (${ph})`).run(...ids);
91
- this.db.prepare(`DELETE FROM task_deps WHERE task_id IN (${ph}) OR depends_on_id IN (${ph})`).run(...ids, ...ids);
92
- const r = this.db.prepare(`DELETE FROM tasks WHERE id IN (${ph})`).run(...ids);
93
- return { tasks: r.changes };
94
- })();
88
+ return this.db.transaction(() => ({ tasks: deleteTasksAndDeps(this.db, 'epic', epicId) }))();
95
89
  }
96
90
  /** Wipe ALL tasks, their dependency edges and every mission — the operational data reset used by
97
91
  * the admin cleanup. Projects/users/config are untouched. Returns the row counts removed. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orcasynth",
3
- "version": "1.4.0",
3
+ "version": "1.4.3",
4
4
  "description": "ORCA — an autonomous coding-agent orchestrator with an autopilot overseer, daemon API and web UI. Install globally and run `orca`.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1 +1 @@
1
- CxOYTELv4rEqlUTUze_od
1
+ -qI9ABgqZPR_Ri56jzVI5
@@ -7,9 +7,9 @@
7
7
  "static/chunks/0cz1d0mv5g_q7.js"
8
8
  ],
9
9
  "lowPriorityFiles": [
10
- "static/CxOYTELv4rEqlUTUze_od/_buildManifest.js",
11
- "static/CxOYTELv4rEqlUTUze_od/_ssgManifest.js",
12
- "static/CxOYTELv4rEqlUTUze_od/_clientMiddlewareManifest.js"
10
+ "static/-qI9ABgqZPR_Ri56jzVI5/_buildManifest.js",
11
+ "static/-qI9ABgqZPR_Ri56jzVI5/_ssgManifest.js",
12
+ "static/-qI9ABgqZPR_Ri56jzVI5/_clientMiddlewareManifest.js"
13
13
  ],
14
14
  "rootMainFiles": [
15
15
  "static/chunks/310vm2bl3xxpt.js",
@@ -1 +1 @@
1
- <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/310vm2bl3xxpt.js"/><script src="/_next/static/chunks/17tpdgx22zl4v.js" async=""></script><script src="/_next/static/chunks/2nykiepra7i1k.js" async=""></script><script src="/_next/static/chunks/0n-zjr76qg7uq.js" async=""></script><script src="/_next/static/chunks/0paxexg6-m0de.js" async=""></script><script src="/_next/static/chunks/turbopack-2put530kkhy0n.js" async=""></script><script src="/_next/static/chunks/05-c3ty_6dwfk.js" async=""></script><script src="/_next/static/chunks/0c179dg4znere.js" async=""></script><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/0cz1d0mv5g_q7.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/310vm2bl3xxpt.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[39756,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"default\"]\n3:I[37457,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"default\"]\n4:I[97367,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[97367,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"ViewportBoundary\"]\na:I[97367,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"MetadataBoundary\"]\nc:I[68027,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],null]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"CxOYTELv4rEqlUTUze_od\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>
1
+ <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/310vm2bl3xxpt.js"/><script src="/_next/static/chunks/17tpdgx22zl4v.js" async=""></script><script src="/_next/static/chunks/2nykiepra7i1k.js" async=""></script><script src="/_next/static/chunks/0n-zjr76qg7uq.js" async=""></script><script src="/_next/static/chunks/0paxexg6-m0de.js" async=""></script><script src="/_next/static/chunks/turbopack-2put530kkhy0n.js" async=""></script><script src="/_next/static/chunks/05-c3ty_6dwfk.js" async=""></script><script src="/_next/static/chunks/0c179dg4znere.js" async=""></script><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/0cz1d0mv5g_q7.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/310vm2bl3xxpt.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[39756,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"default\"]\n3:I[37457,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"default\"]\n4:I[97367,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[97367,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"ViewportBoundary\"]\na:I[97367,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"MetadataBoundary\"]\nc:I[68027,[\"/_next/static/chunks/05-c3ty_6dwfk.js\",\"/_next/static/chunks/0c179dg4znere.js\"],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],null]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"-qI9ABgqZPR_Ri56jzVI5\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>