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.
- package/dist/api/server.js +65 -23
- package/dist/cli/launcher.js +9 -1
- package/dist/cli/menu.js +15 -2
- package/dist/integrations/projectFiles.js +16 -0
- package/dist/integrations/usage/claude.js +7 -7
- package/dist/integrations/usage/codex.js +5 -7
- package/dist/integrations/usage/index.js +2 -8
- package/dist/integrations/usage/walk.js +14 -0
- package/dist/overseer/stuckDetector.js +4 -10
- package/dist/shared/id.js +7 -0
- package/dist/shared/time.js +12 -0
- package/dist/store/cascade.js +24 -0
- package/dist/store/db.js +3 -0
- package/dist/store/projectStore.js +8 -6
- package/dist/store/schema.sql +1 -1
- package/dist/store/taskStore.js +2 -8
- package/package.json +1 -1
- package/web-dist/.next/BUILD_ID +1 -1
- package/web-dist/.next/build-manifest.json +3 -3
- package/web-dist/.next/server/app/_global-error.html +1 -1
- package/web-dist/.next/server/app/_global-error.rsc +1 -1
- package/web-dist/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web-dist/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web-dist/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web-dist/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web-dist/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web-dist/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/_not-found.html +1 -1
- package/web-dist/.next/server/app/_not-found.rsc +11 -11
- package/web-dist/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
- package/web-dist/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/_not-found.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/web-dist/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/web-dist/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/account/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/account/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/account.html +1 -1
- package/web-dist/.next/server/app/account.rsc +13 -13
- package/web-dist/.next/server/app/account.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/account.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/account.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/account.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/account.segments/account/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/account.segments/account.segment.rsc +3 -3
- package/web-dist/.next/server/app/dash/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/dash/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/dash.html +1 -1
- package/web-dist/.next/server/app/dash.rsc +13 -13
- package/web-dist/.next/server/app/dash.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/dash.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/dash.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/dash.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/dash.segments/dash/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/dash.segments/dash.segment.rsc +3 -3
- package/web-dist/.next/server/app/escalations/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/escalations/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/escalations.html +1 -1
- package/web-dist/.next/server/app/escalations.rsc +13 -13
- package/web-dist/.next/server/app/escalations.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/escalations.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/escalations.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/escalations.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/escalations.segments/escalations/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/escalations.segments/escalations.segment.rsc +3 -3
- package/web-dist/.next/server/app/index.html +1 -1
- package/web-dist/.next/server/app/index.rsc +13 -13
- package/web-dist/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/index.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/index.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/kanban/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/kanban/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/kanban.html +1 -1
- package/web-dist/.next/server/app/kanban.rsc +13 -13
- package/web-dist/.next/server/app/kanban.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/kanban.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/kanban.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/kanban.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/kanban.segments/kanban/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/kanban.segments/kanban.segment.rsc +3 -3
- package/web-dist/.next/server/app/onboarding/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/onboarding.html +1 -1
- package/web-dist/.next/server/app/onboarding.rsc +13 -13
- package/web-dist/.next/server/app/onboarding.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/onboarding.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/onboarding.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/onboarding.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/onboarding.segments/onboarding.segment.rsc +3 -3
- package/web-dist/.next/server/app/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/projects/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/projects.html +1 -1
- package/web-dist/.next/server/app/projects.rsc +13 -13
- package/web-dist/.next/server/app/projects.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/projects.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/projects.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/projects.segments/projects/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/projects.segments/projects.segment.rsc +3 -3
- package/web-dist/.next/server/app/sessions/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/sessions.html +1 -1
- package/web-dist/.next/server/app/sessions.rsc +13 -13
- package/web-dist/.next/server/app/sessions.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/sessions.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/sessions.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/sessions.segments/sessions/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/sessions.segments/sessions.segment.rsc +3 -3
- package/web-dist/.next/server/app/settings/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/settings.html +1 -1
- package/web-dist/.next/server/app/settings.rsc +13 -13
- package/web-dist/.next/server/app/settings.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/settings.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/web-dist/.next/server/app/tasks/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/tasks.html +1 -1
- package/web-dist/.next/server/app/tasks.rsc +13 -13
- package/web-dist/.next/server/app/tasks.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/tasks.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/web-dist/.next/server/app/timeline/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/timeline/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/timeline.html +1 -1
- package/web-dist/.next/server/app/timeline.rsc +13 -13
- package/web-dist/.next/server/app/timeline.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/timeline.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/timeline.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/timeline.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/timeline.segments/timeline/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/timeline.segments/timeline.segment.rsc +3 -3
- package/web-dist/.next/server/app/users/page.js.nft.json +1 -1
- package/web-dist/.next/server/app/users/page_client-reference-manifest.js +1 -1
- package/web-dist/.next/server/app/users.html +1 -1
- package/web-dist/.next/server/app/users.rsc +13 -13
- package/web-dist/.next/server/app/users.segments/_full.segment.rsc +13 -13
- package/web-dist/.next/server/app/users.segments/_head.segment.rsc +4 -4
- package/web-dist/.next/server/app/users.segments/_index.segment.rsc +6 -6
- package/web-dist/.next/server/app/users.segments/_tree.segment.rsc +2 -2
- package/web-dist/.next/server/app/users.segments/users/__PAGE__.segment.rsc +4 -4
- package/web-dist/.next/server/app/users.segments/users.segment.rsc +3 -3
- package/web-dist/.next/server/chunks/[root-of-the-server]__0bvp8h1._.js +1 -1
- package/web-dist/.next/server/chunks/[root-of-the-server]__1dgbigm._.js +1 -1
- package/web-dist/.next/server/chunks/[root-of-the-server]__1wxxtv8._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/{[root-of-the-server]__1tooevx._.js → [root-of-the-server]__0i_7o4p._.js} +2 -2
- package/web-dist/.next/server/chunks/ssr/_015zf-4._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/_04ezsju._.js +3 -0
- package/web-dist/.next/server/chunks/ssr/_04o_q14._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/_057a06r._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/_085pshu._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/_0tc9z5_._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/_0tzourm._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/_12jhzvy._.js +3 -0
- package/web-dist/.next/server/chunks/ssr/_136wthy._.js +3 -0
- package/web-dist/.next/server/chunks/ssr/_193-v_i._.js +3 -0
- package/web-dist/.next/server/chunks/ssr/_1heytlk._.js +3 -0
- package/web-dist/.next/server/chunks/ssr/_1mjzb9s._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/_1xwktd-._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/_1zscr7t._.js +3 -0
- package/web-dist/.next/server/chunks/ssr/app_dash_page_tsx_12v0wx-._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/app_kanban_page_tsx_06_8oyf._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/app_projects_page_tsx_1w-8z74._.js +3 -3
- package/web-dist/.next/server/chunks/ssr/app_tasks_page_tsx_1p6mxbw._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/app_timeline_page_tsx_0thxir0._.js +1 -1
- package/web-dist/.next/server/chunks/ssr/components_shell_Shell_tsx_1e5c27h._.js +1 -1
- package/web-dist/.next/server/middleware-build-manifest.js +3 -3
- package/web-dist/.next/server/pages/404.html +1 -1
- package/web-dist/.next/server/pages/500.html +1 -1
- package/web-dist/.next/static/chunks/0l1t1fcd-_jj9.js +1 -0
- package/web-dist/.next/static/chunks/11xbsx12drypd.js +1 -0
- package/web-dist/.next/static/chunks/186xbnkxm5iu3.js +1 -0
- package/web-dist/.next/static/chunks/1c_1_ca4-vgc7.js +1 -0
- package/web-dist/.next/static/chunks/{3wo9m5v__3pi2.js → 1f5f3qgbcjv4u.js} +1 -1
- package/web-dist/.next/static/chunks/1t-dast4rat3s.css +2 -0
- package/web-dist/.next/static/chunks/1wsag7zex3h4_.js +1 -0
- package/web-dist/.next/static/chunks/24unfsl4do--1.js +1 -0
- package/web-dist/.next/static/chunks/2bfjq22q8xyfs.js +1 -0
- package/web-dist/.next/static/chunks/2kuzf7llj_291.js +1 -0
- package/web-dist/.next/static/chunks/2pbhzo7fu4xcs.js +11 -0
- package/web-dist/.next/static/chunks/30ztbacooyerd.js +1 -0
- package/web-dist/.next/static/chunks/34bew8owm40fg.js +1 -0
- package/web-dist/.next/static/chunks/3a7pgnw_6io2v.js +1 -0
- package/web-dist/.next/static/chunks/3k-swqzkcvlzm.js +1 -0
- package/web-dist/.next/static/chunks/{2-j0lduvppz4v.js → 3saus_snl5ri7.js} +1 -1
- package/web-dist/.next/server/chunks/ssr/_09x5h4x._.js +0 -3
- package/web-dist/.next/server/chunks/ssr/_0lctmoh._.js +0 -3
- package/web-dist/.next/server/chunks/ssr/_0zho8fx._.js +0 -3
- package/web-dist/.next/server/chunks/ssr/_1812xdn._.js +0 -3
- package/web-dist/.next/server/chunks/ssr/_1m2qx8p._.js +0 -3
- package/web-dist/.next/server/chunks/ssr/_1w24b42._.js +0 -3
- package/web-dist/.next/server/chunks/ssr/app_sessions_page_tsx_0r1_4_3._.js +0 -3
- package/web-dist/.next/static/chunks/05jfk0-07tiga.js +0 -11
- package/web-dist/.next/static/chunks/0_5u1_tmtxso3.js +0 -1
- package/web-dist/.next/static/chunks/16rotf1pkrqyn.js +0 -1
- package/web-dist/.next/static/chunks/201p8-_l1h8w8.js +0 -1
- package/web-dist/.next/static/chunks/2_uoi0tzglv_7.js +0 -1
- package/web-dist/.next/static/chunks/2m810vyp96um0.js +0 -1
- package/web-dist/.next/static/chunks/2sm_cc2r9sjzp.js +0 -1
- package/web-dist/.next/static/chunks/2xur1zqckjlq3.js +0 -1
- package/web-dist/.next/static/chunks/35babynx5l240.js +0 -1
- package/web-dist/.next/static/chunks/3bdts7sstygr0.css +0 -2
- package/web-dist/.next/static/chunks/3i0jpc47nxz7y.js +0 -1
- package/web-dist/.next/static/chunks/3soklmhd2y9yp.js +0 -1
- package/web-dist/.next/static/chunks/3ybvfq13cp-hb.js +0 -1
- package/web-dist/.next/static/chunks/3yqt3l54sr5si.js +0 -1
- /package/web-dist/.next/static/{CxOYTELv4rEqlUTUze_od → -qI9ABgqZPR_Ri56jzVI5}/_buildManifest.js +0 -0
- /package/web-dist/.next/static/{CxOYTELv4rEqlUTUze_od → -qI9ABgqZPR_Ri56jzVI5}/_clientMiddlewareManifest.js +0 -0
- /package/web-dist/.next/static/{CxOYTELv4rEqlUTUze_od → -qI9ABgqZPR_Ri56jzVI5}/_ssgManifest.js +0 -0
package/dist/api/server.js
CHANGED
|
@@ -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 {
|
|
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 = () =>
|
|
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
|
-
|
|
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 ??
|
|
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.
|
|
997
|
-
|
|
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');
|
package/dist/cli/launcher.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
14
|
-
//
|
|
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
|
|
22
|
+
if (start == null)
|
|
22
23
|
continue;
|
|
23
24
|
sessions.push({ path: p, start });
|
|
24
25
|
}
|
|
25
|
-
|
|
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
|
|
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 {
|
|
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; `
|
|
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
|
|
19
|
+
if (start == null)
|
|
21
20
|
continue;
|
|
22
21
|
sessions.push({ path: f, start });
|
|
23
22
|
}
|
|
24
|
-
|
|
25
|
-
const best = sessions[nth];
|
|
23
|
+
const best = pickNthSession(sessions, sinceMs, nth);
|
|
26
24
|
if (!best)
|
|
27
25
|
return null;
|
|
28
|
-
return finalUsage(best
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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);
|
package/dist/store/schema.sql
CHANGED
|
@@ -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',
|
package/dist/store/taskStore.js
CHANGED
|
@@ -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
package/web-dist/.next/BUILD_ID
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
-qI9ABgqZPR_Ri56jzVI5
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
"static/chunks/0cz1d0mv5g_q7.js"
|
|
8
8
|
],
|
|
9
9
|
"lowPriorityFiles": [
|
|
10
|
-
"static/
|
|
11
|
-
"static/
|
|
12
|
-
"static/
|
|
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,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";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\":\"
|
|
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,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";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>
|