@hasna/todos 0.15.5 → 0.15.7
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/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +3086 -1332
- package/dist/contracts.js +213 -3
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1950 -26
- package/dist/lib/comment-cursor.d.ts +40 -0
- package/dist/lib/comment-cursor.d.ts.map +1 -0
- package/dist/mcp/index.js +2096 -391
- package/dist/mcp.js +7 -3
- package/dist/project-registration/authority.d.ts +45 -0
- package/dist/project-registration/authority.d.ts.map +1 -0
- package/dist/project-registration/backend.d.ts +83 -0
- package/dist/project-registration/backend.d.ts.map +1 -0
- package/dist/project-registration/http.d.ts +24 -0
- package/dist/project-registration/http.d.ts.map +1 -0
- package/dist/project-registration/index.d.ts +8 -0
- package/dist/project-registration/index.d.ts.map +1 -0
- package/dist/project-registration/postgres.d.ts +30 -0
- package/dist/project-registration/postgres.d.ts.map +1 -0
- package/dist/project-registration/schema.d.ts +3 -0
- package/dist/project-registration/schema.d.ts.map +1 -0
- package/dist/project-registration/sqlite.d.ts +17 -0
- package/dist/project-registration/sqlite.d.ts.map +1 -0
- package/dist/project-registration/types.d.ts +155 -0
- package/dist/project-registration/types.d.ts.map +1 -0
- package/dist/project-registration.d.ts +2 -0
- package/dist/project-registration.d.ts.map +1 -0
- package/dist/project-registration.js +17689 -0
- package/dist/registry.d.ts +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +220 -3
- package/dist/release-provenance.json +5 -5
- package/dist/server/cloud.d.ts +3 -0
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +4386 -2681
- package/dist/server/v1.d.ts +2 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +206 -0
- package/package.json +7 -3
package/dist/mcp/index.js
CHANGED
|
@@ -228,6 +228,206 @@ var init_types = __esm(() => {
|
|
|
228
228
|
};
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
+
// src/project-registration/schema.ts
|
|
232
|
+
function sqliteTodosProjectRegistrationSchemaSql() {
|
|
233
|
+
return `
|
|
234
|
+
CREATE TABLE IF NOT EXISTS todos_project_registration_receipts (
|
|
235
|
+
receipt_id TEXT PRIMARY KEY,
|
|
236
|
+
authority TEXT NOT NULL CHECK(authority = 'todos'),
|
|
237
|
+
route TEXT NOT NULL,
|
|
238
|
+
package_version TEXT NOT NULL,
|
|
239
|
+
authority_id TEXT NOT NULL,
|
|
240
|
+
tenant_id TEXT NOT NULL,
|
|
241
|
+
corpus_id TEXT NOT NULL,
|
|
242
|
+
operation_id TEXT NOT NULL,
|
|
243
|
+
step_id TEXT NOT NULL,
|
|
244
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
|
|
245
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
|
|
246
|
+
target_selector TEXT NOT NULL,
|
|
247
|
+
idempotency_key TEXT NOT NULL,
|
|
248
|
+
request_digest TEXT NOT NULL,
|
|
249
|
+
precondition_digest TEXT NOT NULL,
|
|
250
|
+
normalized_call_digest TEXT NOT NULL,
|
|
251
|
+
outcome TEXT NOT NULL CHECK(outcome IN (
|
|
252
|
+
'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
|
|
253
|
+
)),
|
|
254
|
+
reason TEXT,
|
|
255
|
+
target_id TEXT,
|
|
256
|
+
result_revision TEXT,
|
|
257
|
+
result_digest TEXT,
|
|
258
|
+
duplicate_of_receipt_id TEXT,
|
|
259
|
+
accepted_receipt_id TEXT,
|
|
260
|
+
created_by_operation INTEGER NOT NULL CHECK(created_by_operation IN (0, 1)),
|
|
261
|
+
created_at TEXT NOT NULL
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
CREATE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_lookup
|
|
265
|
+
ON todos_project_registration_receipts (
|
|
266
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
267
|
+
resource_kind, direction, idempotency_key
|
|
268
|
+
);
|
|
269
|
+
CREATE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_step
|
|
270
|
+
ON todos_project_registration_receipts (
|
|
271
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
272
|
+
resource_kind, direction, outcome
|
|
273
|
+
);
|
|
274
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_accepted_step
|
|
275
|
+
ON todos_project_registration_receipts (
|
|
276
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
277
|
+
resource_kind, direction
|
|
278
|
+
)
|
|
279
|
+
WHERE outcome = 'accepted';
|
|
280
|
+
CREATE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_target
|
|
281
|
+
ON todos_project_registration_receipts (
|
|
282
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
CREATE TABLE IF NOT EXISTS todos_project_registration_bindings (
|
|
286
|
+
authority_id TEXT NOT NULL,
|
|
287
|
+
tenant_id TEXT NOT NULL,
|
|
288
|
+
corpus_id TEXT NOT NULL,
|
|
289
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
|
|
290
|
+
target_selector TEXT NOT NULL,
|
|
291
|
+
operation_id TEXT NOT NULL,
|
|
292
|
+
step_id TEXT NOT NULL,
|
|
293
|
+
direction TEXT NOT NULL CHECK(direction = 'forward'),
|
|
294
|
+
idempotency_key TEXT NOT NULL,
|
|
295
|
+
request_digest TEXT NOT NULL,
|
|
296
|
+
precondition_digest TEXT NOT NULL,
|
|
297
|
+
normalized_call_digest TEXT NOT NULL,
|
|
298
|
+
state TEXT NOT NULL CHECK(state IN (
|
|
299
|
+
'pending', 'accepted', 'terminal_nonacceptance', 'removed'
|
|
300
|
+
)),
|
|
301
|
+
target_id TEXT,
|
|
302
|
+
accepted_receipt_id TEXT,
|
|
303
|
+
result_revision TEXT,
|
|
304
|
+
result_digest TEXT,
|
|
305
|
+
removed_receipt_id TEXT,
|
|
306
|
+
created_at TEXT NOT NULL,
|
|
307
|
+
updated_at TEXT NOT NULL,
|
|
308
|
+
PRIMARY KEY(
|
|
309
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector
|
|
310
|
+
),
|
|
311
|
+
UNIQUE(accepted_receipt_id)
|
|
312
|
+
);
|
|
313
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_todos_project_registration_binding_target
|
|
314
|
+
ON todos_project_registration_bindings(
|
|
315
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
316
|
+
)
|
|
317
|
+
WHERE target_id IS NOT NULL;
|
|
318
|
+
|
|
319
|
+
CREATE TRIGGER IF NOT EXISTS todos_project_registration_receipts_immutable_update
|
|
320
|
+
BEFORE UPDATE ON todos_project_registration_receipts
|
|
321
|
+
BEGIN
|
|
322
|
+
SELECT RAISE(ABORT, 'todos project registration receipts are immutable');
|
|
323
|
+
END;
|
|
324
|
+
|
|
325
|
+
CREATE TRIGGER IF NOT EXISTS todos_project_registration_receipts_immutable_delete
|
|
326
|
+
BEFORE DELETE ON todos_project_registration_receipts
|
|
327
|
+
BEGIN
|
|
328
|
+
SELECT RAISE(ABORT, 'todos project registration receipts are immutable');
|
|
329
|
+
END;
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
function postgresTodosProjectRegistrationSchemaSql() {
|
|
333
|
+
return [
|
|
334
|
+
`CREATE TABLE IF NOT EXISTS todos_project_registration_receipts (
|
|
335
|
+
receipt_id text PRIMARY KEY,
|
|
336
|
+
authority text NOT NULL CHECK(authority = 'todos'),
|
|
337
|
+
route text NOT NULL,
|
|
338
|
+
package_version text NOT NULL,
|
|
339
|
+
authority_id text NOT NULL,
|
|
340
|
+
tenant_id text NOT NULL,
|
|
341
|
+
corpus_id text NOT NULL,
|
|
342
|
+
operation_id text NOT NULL,
|
|
343
|
+
step_id text NOT NULL,
|
|
344
|
+
resource_kind text NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
|
|
345
|
+
direction text NOT NULL CHECK(direction IN ('forward', 'inverse')),
|
|
346
|
+
target_selector text NOT NULL,
|
|
347
|
+
idempotency_key text NOT NULL,
|
|
348
|
+
request_digest text NOT NULL,
|
|
349
|
+
precondition_digest text NOT NULL,
|
|
350
|
+
normalized_call_digest text NOT NULL,
|
|
351
|
+
outcome text NOT NULL CHECK(outcome IN (
|
|
352
|
+
'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
|
|
353
|
+
)),
|
|
354
|
+
reason text,
|
|
355
|
+
target_id text,
|
|
356
|
+
result_revision text,
|
|
357
|
+
result_digest text,
|
|
358
|
+
duplicate_of_receipt_id text,
|
|
359
|
+
accepted_receipt_id text,
|
|
360
|
+
created_by_operation boolean NOT NULL,
|
|
361
|
+
created_at timestamptz NOT NULL
|
|
362
|
+
)`,
|
|
363
|
+
`CREATE INDEX IF NOT EXISTS todos_project_registration_receipts_lookup_idx
|
|
364
|
+
ON todos_project_registration_receipts (
|
|
365
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
366
|
+
resource_kind, direction, idempotency_key
|
|
367
|
+
)`,
|
|
368
|
+
`CREATE INDEX IF NOT EXISTS todos_project_registration_receipts_step_idx
|
|
369
|
+
ON todos_project_registration_receipts (
|
|
370
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
371
|
+
resource_kind, direction, outcome
|
|
372
|
+
)`,
|
|
373
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS todos_project_registration_receipts_accepted_step_uidx
|
|
374
|
+
ON todos_project_registration_receipts (
|
|
375
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
376
|
+
resource_kind, direction
|
|
377
|
+
)
|
|
378
|
+
WHERE outcome = 'accepted'`,
|
|
379
|
+
`CREATE INDEX IF NOT EXISTS todos_project_registration_receipts_target_idx
|
|
380
|
+
ON todos_project_registration_receipts (
|
|
381
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
382
|
+
)`,
|
|
383
|
+
`CREATE TABLE IF NOT EXISTS todos_project_registration_bindings (
|
|
384
|
+
authority_id text NOT NULL,
|
|
385
|
+
tenant_id text NOT NULL,
|
|
386
|
+
corpus_id text NOT NULL,
|
|
387
|
+
resource_kind text NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
|
|
388
|
+
target_selector text NOT NULL,
|
|
389
|
+
operation_id text NOT NULL,
|
|
390
|
+
step_id text NOT NULL,
|
|
391
|
+
direction text NOT NULL CHECK(direction = 'forward'),
|
|
392
|
+
idempotency_key text NOT NULL,
|
|
393
|
+
request_digest text NOT NULL,
|
|
394
|
+
precondition_digest text NOT NULL,
|
|
395
|
+
normalized_call_digest text NOT NULL,
|
|
396
|
+
state text NOT NULL CHECK(state IN (
|
|
397
|
+
'pending', 'accepted', 'terminal_nonacceptance', 'removed'
|
|
398
|
+
)),
|
|
399
|
+
target_id text,
|
|
400
|
+
accepted_receipt_id text UNIQUE,
|
|
401
|
+
result_revision text,
|
|
402
|
+
result_digest text,
|
|
403
|
+
removed_receipt_id text,
|
|
404
|
+
created_at timestamptz NOT NULL,
|
|
405
|
+
updated_at timestamptz NOT NULL,
|
|
406
|
+
PRIMARY KEY(
|
|
407
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector
|
|
408
|
+
)
|
|
409
|
+
)`,
|
|
410
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS todos_project_registration_binding_target_uidx
|
|
411
|
+
ON todos_project_registration_bindings(
|
|
412
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
413
|
+
)
|
|
414
|
+
WHERE target_id IS NOT NULL`,
|
|
415
|
+
`CREATE OR REPLACE FUNCTION todos_project_registration_receipts_immutable()
|
|
416
|
+
RETURNS trigger
|
|
417
|
+
LANGUAGE plpgsql
|
|
418
|
+
AS $$
|
|
419
|
+
BEGIN
|
|
420
|
+
RAISE EXCEPTION 'todos project registration receipts are immutable';
|
|
421
|
+
END;
|
|
422
|
+
$$`,
|
|
423
|
+
`DROP TRIGGER IF EXISTS todos_project_registration_receipts_immutable
|
|
424
|
+
ON todos_project_registration_receipts`,
|
|
425
|
+
`CREATE TRIGGER todos_project_registration_receipts_immutable
|
|
426
|
+
BEFORE UPDATE OR DELETE ON todos_project_registration_receipts
|
|
427
|
+
FOR EACH ROW EXECUTE FUNCTION todos_project_registration_receipts_immutable()`
|
|
428
|
+
];
|
|
429
|
+
}
|
|
430
|
+
|
|
231
431
|
// src/db/migrations.ts
|
|
232
432
|
var MIGRATIONS;
|
|
233
433
|
var init_migrations = __esm(() => {
|
|
@@ -1833,6 +2033,11 @@ var init_migrations = __esm(() => {
|
|
|
1833
2033
|
INSERT OR IGNORE INTO _migrations (id) VALUES (68);
|
|
1834
2034
|
COMMIT;
|
|
1835
2035
|
PRAGMA foreign_keys = ON;
|
|
2036
|
+
`,
|
|
2037
|
+
`BEGIN;
|
|
2038
|
+
${sqliteTodosProjectRegistrationSchemaSql()}
|
|
2039
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (69);
|
|
2040
|
+
COMMIT;
|
|
1836
2041
|
`
|
|
1837
2042
|
];
|
|
1838
2043
|
});
|
|
@@ -3035,6 +3240,7 @@ function ensureSchema(db) {
|
|
|
3035
3240
|
)`);
|
|
3036
3241
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(prefix)");
|
|
3037
3242
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(revoked_at, expires_at)");
|
|
3243
|
+
db.exec(sqliteTodosProjectRegistrationSchemaSql());
|
|
3038
3244
|
ensureTable("pr_groups", `
|
|
3039
3245
|
CREATE TABLE pr_groups (
|
|
3040
3246
|
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
@@ -20684,6 +20890,16 @@ async function cloudListProjects(client) {
|
|
|
20684
20890
|
const envelope = res.raw;
|
|
20685
20891
|
return Array.isArray(envelope?.projects) ? envelope.projects : res.items;
|
|
20686
20892
|
}
|
|
20893
|
+
function unwrapProject(raw) {
|
|
20894
|
+
if (raw && typeof raw === "object" && "project" in raw) {
|
|
20895
|
+
return raw.project;
|
|
20896
|
+
}
|
|
20897
|
+
return raw;
|
|
20898
|
+
}
|
|
20899
|
+
async function cloudGetProjectById(client, id) {
|
|
20900
|
+
const raw = await client.get("projects", id);
|
|
20901
|
+
return raw == null ? null : unwrapProject(raw);
|
|
20902
|
+
}
|
|
20687
20903
|
function cloudProjectSlug(value) {
|
|
20688
20904
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
20689
20905
|
}
|
|
@@ -20790,29 +21006,63 @@ async function cloudListTaskLists(client, projectId) {
|
|
|
20790
21006
|
return envelope.taskLists;
|
|
20791
21007
|
return Array.isArray(raw) ? raw : [];
|
|
20792
21008
|
}
|
|
21009
|
+
function unwrapTaskList(raw) {
|
|
21010
|
+
if (raw && typeof raw === "object" && "task_list" in raw) {
|
|
21011
|
+
return raw.task_list;
|
|
21012
|
+
}
|
|
21013
|
+
return raw;
|
|
21014
|
+
}
|
|
21015
|
+
async function cloudGetTaskList(client, id) {
|
|
21016
|
+
const raw = await client.get("task-lists", id);
|
|
21017
|
+
return raw == null ? null : unwrapTaskList(raw);
|
|
21018
|
+
}
|
|
21019
|
+
function resolveTaskListFromCandidates(lists, input) {
|
|
21020
|
+
const normalizedIdRef = input.toLowerCase();
|
|
21021
|
+
const matchGroups = [
|
|
21022
|
+
lists.filter((list) => list.id.toLowerCase() === normalizedIdRef),
|
|
21023
|
+
lists.filter((list) => list.slug === input),
|
|
21024
|
+
lists.filter((list) => list.id.toLowerCase().startsWith(normalizedIdRef))
|
|
21025
|
+
];
|
|
21026
|
+
for (const matches of matchGroups) {
|
|
21027
|
+
if (matches.length === 1)
|
|
21028
|
+
return matches[0].id;
|
|
21029
|
+
if (matches.length > 1) {
|
|
21030
|
+
throw new Error(`Task list reference is ambiguous: "${input}"`);
|
|
21031
|
+
}
|
|
21032
|
+
}
|
|
21033
|
+
return null;
|
|
21034
|
+
}
|
|
21035
|
+
async function legacyProjectTaskLists(client, projectId) {
|
|
21036
|
+
const project = await cloudGetProjectById(client, projectId);
|
|
21037
|
+
if (!project?.task_list_id)
|
|
21038
|
+
return [];
|
|
21039
|
+
return (await cloudListTaskLists(client)).filter((list) => list.project_id == null && list.slug === project.task_list_id);
|
|
21040
|
+
}
|
|
20793
21041
|
async function cloudResolveTaskListRef(client, ref, projectId) {
|
|
20794
21042
|
const input = ref.trim();
|
|
20795
21043
|
const normalizedIdRef = input.toLowerCase();
|
|
20796
21044
|
if (UUID_RE.test(input) && !projectId)
|
|
20797
21045
|
return normalizedIdRef;
|
|
20798
|
-
|
|
20799
|
-
|
|
20800
|
-
|
|
20801
|
-
|
|
20802
|
-
|
|
20803
|
-
|
|
20804
|
-
|
|
20805
|
-
|
|
20806
|
-
|
|
20807
|
-
|
|
20808
|
-
|
|
20809
|
-
|
|
20810
|
-
}
|
|
20811
|
-
const
|
|
20812
|
-
if (
|
|
20813
|
-
return
|
|
20814
|
-
if (
|
|
20815
|
-
|
|
21046
|
+
if (UUID_RE.test(input) && projectId) {
|
|
21047
|
+
const direct = await cloudGetTaskList(client, normalizedIdRef);
|
|
21048
|
+
if (direct?.id?.toLowerCase() === normalizedIdRef) {
|
|
21049
|
+
if (direct.project_id === projectId)
|
|
21050
|
+
return direct.id;
|
|
21051
|
+
if (direct.project_id == null) {
|
|
21052
|
+
const project = await cloudGetProjectById(client, projectId);
|
|
21053
|
+
if (project?.task_list_id === direct.slug)
|
|
21054
|
+
return direct.id;
|
|
21055
|
+
}
|
|
21056
|
+
throw new Error(`Task list not found: "${input}"`);
|
|
21057
|
+
}
|
|
21058
|
+
}
|
|
21059
|
+
const scopedMatch = resolveTaskListFromCandidates(await cloudListTaskLists(client, projectId), input);
|
|
21060
|
+
if (scopedMatch)
|
|
21061
|
+
return scopedMatch;
|
|
21062
|
+
if (projectId) {
|
|
21063
|
+
const legacyMatch = resolveTaskListFromCandidates(await legacyProjectTaskLists(client, projectId), input);
|
|
21064
|
+
if (legacyMatch)
|
|
21065
|
+
return legacyMatch;
|
|
20816
21066
|
}
|
|
20817
21067
|
throw new Error(`Task list not found: "${input}"`);
|
|
20818
21068
|
}
|
|
@@ -34926,7 +35176,7 @@ var package_default;
|
|
|
34926
35176
|
var init_package = __esm(() => {
|
|
34927
35177
|
package_default = {
|
|
34928
35178
|
name: "@hasna/todos",
|
|
34929
|
-
version: "0.15.
|
|
35179
|
+
version: "0.15.7",
|
|
34930
35180
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
34931
35181
|
type: "module",
|
|
34932
35182
|
main: "dist/index.js",
|
|
@@ -34964,6 +35214,10 @@ var init_package = __esm(() => {
|
|
|
34964
35214
|
"./testing": {
|
|
34965
35215
|
types: "./dist/testing.d.ts",
|
|
34966
35216
|
import: "./dist/testing.js"
|
|
35217
|
+
},
|
|
35218
|
+
"./project-registration": {
|
|
35219
|
+
types: "./dist/project-registration.d.ts",
|
|
35220
|
+
import: "./dist/project-registration.js"
|
|
34967
35221
|
}
|
|
34968
35222
|
},
|
|
34969
35223
|
workspaces: [
|
|
@@ -34976,8 +35230,8 @@ var init_package = __esm(() => {
|
|
|
34976
35230
|
"README.md"
|
|
34977
35231
|
],
|
|
34978
35232
|
scripts: {
|
|
34979
|
-
build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
34980
|
-
"build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
35233
|
+
build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
35234
|
+
"build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
34981
35235
|
migrate: "bun run src/server/index.ts migrate",
|
|
34982
35236
|
"backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
|
|
34983
35237
|
"generate:sdk": "bun run scripts/generate-sdk.ts",
|
|
@@ -47723,6 +47977,1433 @@ var init_postgres = __esm(() => {
|
|
|
47723
47977
|
init_types3();
|
|
47724
47978
|
});
|
|
47725
47979
|
|
|
47980
|
+
// src/project-registration/types.ts
|
|
47981
|
+
var TODOS_PROJECT_REGISTRATION_ROUTE = "todos.project-registration.v1", TODOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1", TodosProjectRegistrationError;
|
|
47982
|
+
var init_types4 = __esm(() => {
|
|
47983
|
+
TodosProjectRegistrationError = class TodosProjectRegistrationError extends Error {
|
|
47984
|
+
code;
|
|
47985
|
+
details;
|
|
47986
|
+
constructor(code, message, details = {}) {
|
|
47987
|
+
super(message);
|
|
47988
|
+
this.code = code;
|
|
47989
|
+
this.details = details;
|
|
47990
|
+
this.name = "TodosProjectRegistrationError";
|
|
47991
|
+
}
|
|
47992
|
+
};
|
|
47993
|
+
});
|
|
47994
|
+
|
|
47995
|
+
// src/project-registration/postgres.ts
|
|
47996
|
+
function safeIdentifier(value, field) {
|
|
47997
|
+
if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
|
|
47998
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} must be a safe PostgreSQL identifier`);
|
|
47999
|
+
}
|
|
48000
|
+
return value;
|
|
48001
|
+
}
|
|
48002
|
+
function normalizeTimestamp2(value) {
|
|
48003
|
+
return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
|
|
48004
|
+
}
|
|
48005
|
+
function parsePayload3(value) {
|
|
48006
|
+
if (typeof value === "string")
|
|
48007
|
+
return JSON.parse(value);
|
|
48008
|
+
return value;
|
|
48009
|
+
}
|
|
48010
|
+
function receiptFromRow(row) {
|
|
48011
|
+
return {
|
|
48012
|
+
...row,
|
|
48013
|
+
authority: "todos",
|
|
48014
|
+
created_by_operation: Boolean(row["created_by_operation"]),
|
|
48015
|
+
created_at: normalizeTimestamp2(row["created_at"])
|
|
48016
|
+
};
|
|
48017
|
+
}
|
|
48018
|
+
function bindingFromRow(row) {
|
|
48019
|
+
return {
|
|
48020
|
+
...row,
|
|
48021
|
+
created_at: normalizeTimestamp2(row["created_at"]),
|
|
48022
|
+
updated_at: normalizeTimestamp2(row["updated_at"])
|
|
48023
|
+
};
|
|
48024
|
+
}
|
|
48025
|
+
|
|
48026
|
+
class PostgresTodosProjectRegistrationTransaction {
|
|
48027
|
+
client;
|
|
48028
|
+
service;
|
|
48029
|
+
tableName;
|
|
48030
|
+
storage;
|
|
48031
|
+
constructor(client, service, tableName, cursorTableName) {
|
|
48032
|
+
this.client = client;
|
|
48033
|
+
this.service = service;
|
|
48034
|
+
this.tableName = tableName;
|
|
48035
|
+
this.storage = createPostgresTodosStorageAdapter({
|
|
48036
|
+
client,
|
|
48037
|
+
service,
|
|
48038
|
+
tableName,
|
|
48039
|
+
cursorTableName
|
|
48040
|
+
});
|
|
48041
|
+
}
|
|
48042
|
+
async lockStep(identity) {
|
|
48043
|
+
const key = [
|
|
48044
|
+
identity.authority_id,
|
|
48045
|
+
identity.tenant_id,
|
|
48046
|
+
identity.corpus_id,
|
|
48047
|
+
identity.operation_id,
|
|
48048
|
+
identity.step_id,
|
|
48049
|
+
identity.resource_kind,
|
|
48050
|
+
identity.direction
|
|
48051
|
+
].join("\x1F");
|
|
48052
|
+
await this.client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [key]);
|
|
48053
|
+
}
|
|
48054
|
+
async getReceiptForLookup(identity) {
|
|
48055
|
+
const result = await this.client.query(`
|
|
48056
|
+
SELECT * FROM todos_project_registration_receipts
|
|
48057
|
+
WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
|
|
48058
|
+
AND operation_id = $4 AND step_id = $5 AND resource_kind = $6
|
|
48059
|
+
AND direction = $7 AND idempotency_key = $8 AND target_selector = $9
|
|
48060
|
+
ORDER BY CASE outcome
|
|
48061
|
+
WHEN 'terminal_nonacceptance' THEN 0
|
|
48062
|
+
WHEN 'duplicate_of_accepted' THEN 1
|
|
48063
|
+
ELSE 2
|
|
48064
|
+
END, created_at DESC, receipt_id DESC
|
|
48065
|
+
LIMIT 1
|
|
48066
|
+
`, [
|
|
48067
|
+
identity.authority_id,
|
|
48068
|
+
identity.tenant_id,
|
|
48069
|
+
identity.corpus_id,
|
|
48070
|
+
identity.operation_id,
|
|
48071
|
+
identity.step_id,
|
|
48072
|
+
identity.resource_kind,
|
|
48073
|
+
identity.direction,
|
|
48074
|
+
identity.idempotency_key,
|
|
48075
|
+
identity.target_selector
|
|
48076
|
+
]);
|
|
48077
|
+
return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
|
|
48078
|
+
}
|
|
48079
|
+
async getReceiptById(receiptId) {
|
|
48080
|
+
const result = await this.client.query("SELECT * FROM todos_project_registration_receipts WHERE receipt_id = $1 LIMIT 1", [receiptId]);
|
|
48081
|
+
return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
|
|
48082
|
+
}
|
|
48083
|
+
async getAcceptedReceiptForStep(identity) {
|
|
48084
|
+
const result = await this.client.query(`
|
|
48085
|
+
SELECT * FROM todos_project_registration_receipts
|
|
48086
|
+
WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
|
|
48087
|
+
AND operation_id = $4 AND step_id = $5 AND resource_kind = $6
|
|
48088
|
+
AND direction = $7 AND outcome = 'accepted'
|
|
48089
|
+
ORDER BY created_at ASC, receipt_id ASC
|
|
48090
|
+
LIMIT 1
|
|
48091
|
+
FOR UPDATE
|
|
48092
|
+
`, [
|
|
48093
|
+
identity.authority_id,
|
|
48094
|
+
identity.tenant_id,
|
|
48095
|
+
identity.corpus_id,
|
|
48096
|
+
identity.operation_id,
|
|
48097
|
+
identity.step_id,
|
|
48098
|
+
identity.resource_kind,
|
|
48099
|
+
identity.direction
|
|
48100
|
+
]);
|
|
48101
|
+
return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
|
|
48102
|
+
}
|
|
48103
|
+
async insertReceipt(receipt) {
|
|
48104
|
+
const result = await this.client.query(`
|
|
48105
|
+
INSERT INTO todos_project_registration_receipts (
|
|
48106
|
+
receipt_id, authority, route, package_version, authority_id, tenant_id,
|
|
48107
|
+
corpus_id, operation_id, step_id, resource_kind, direction,
|
|
48108
|
+
target_selector, idempotency_key, request_digest, precondition_digest,
|
|
48109
|
+
normalized_call_digest, outcome, reason, target_id, result_revision,
|
|
48110
|
+
result_digest, duplicate_of_receipt_id, accepted_receipt_id,
|
|
48111
|
+
created_by_operation, created_at
|
|
48112
|
+
) VALUES (
|
|
48113
|
+
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,
|
|
48114
|
+
$19,$20,$21,$22,$23,$24,$25
|
|
48115
|
+
)
|
|
48116
|
+
ON CONFLICT (receipt_id) DO NOTHING
|
|
48117
|
+
RETURNING receipt_id
|
|
48118
|
+
`, [
|
|
48119
|
+
receipt.receipt_id,
|
|
48120
|
+
receipt.authority,
|
|
48121
|
+
receipt.route,
|
|
48122
|
+
receipt.package_version,
|
|
48123
|
+
receipt.authority_id,
|
|
48124
|
+
receipt.tenant_id,
|
|
48125
|
+
receipt.corpus_id,
|
|
48126
|
+
receipt.operation_id,
|
|
48127
|
+
receipt.step_id,
|
|
48128
|
+
receipt.resource_kind,
|
|
48129
|
+
receipt.direction,
|
|
48130
|
+
receipt.target_selector,
|
|
48131
|
+
receipt.idempotency_key,
|
|
48132
|
+
receipt.request_digest,
|
|
48133
|
+
receipt.precondition_digest,
|
|
48134
|
+
receipt.normalized_call_digest,
|
|
48135
|
+
receipt.outcome,
|
|
48136
|
+
receipt.reason,
|
|
48137
|
+
receipt.target_id,
|
|
48138
|
+
receipt.result_revision,
|
|
48139
|
+
receipt.result_digest,
|
|
48140
|
+
receipt.duplicate_of_receipt_id,
|
|
48141
|
+
receipt.accepted_receipt_id,
|
|
48142
|
+
receipt.created_by_operation,
|
|
48143
|
+
receipt.created_at
|
|
48144
|
+
]);
|
|
48145
|
+
return result.rows.length === 1;
|
|
48146
|
+
}
|
|
48147
|
+
async getBinding(scope, resourceKind, targetSelector) {
|
|
48148
|
+
const result = await this.client.query(`
|
|
48149
|
+
SELECT * FROM todos_project_registration_bindings
|
|
48150
|
+
WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
|
|
48151
|
+
AND resource_kind = $4 AND target_selector = $5
|
|
48152
|
+
LIMIT 1
|
|
48153
|
+
FOR UPDATE
|
|
48154
|
+
`, [
|
|
48155
|
+
scope.authority_id,
|
|
48156
|
+
scope.tenant_id,
|
|
48157
|
+
scope.corpus_id,
|
|
48158
|
+
resourceKind,
|
|
48159
|
+
targetSelector
|
|
48160
|
+
]);
|
|
48161
|
+
return result.rows[0] ? bindingFromRow(result.rows[0]) : null;
|
|
48162
|
+
}
|
|
48163
|
+
async claimBinding(binding) {
|
|
48164
|
+
const result = await this.client.query(`
|
|
48165
|
+
INSERT INTO todos_project_registration_bindings (
|
|
48166
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector,
|
|
48167
|
+
operation_id, step_id, direction, idempotency_key, request_digest,
|
|
48168
|
+
precondition_digest, normalized_call_digest, state, target_id,
|
|
48169
|
+
accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
|
|
48170
|
+
created_at, updated_at
|
|
48171
|
+
) VALUES (
|
|
48172
|
+
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,
|
|
48173
|
+
$18,$19,$20
|
|
48174
|
+
)
|
|
48175
|
+
ON CONFLICT (
|
|
48176
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector
|
|
48177
|
+
) DO NOTHING
|
|
48178
|
+
RETURNING target_selector
|
|
48179
|
+
`, [
|
|
48180
|
+
binding.authority_id,
|
|
48181
|
+
binding.tenant_id,
|
|
48182
|
+
binding.corpus_id,
|
|
48183
|
+
binding.resource_kind,
|
|
48184
|
+
binding.target_selector,
|
|
48185
|
+
binding.operation_id,
|
|
48186
|
+
binding.step_id,
|
|
48187
|
+
binding.direction,
|
|
48188
|
+
binding.idempotency_key,
|
|
48189
|
+
binding.request_digest,
|
|
48190
|
+
binding.precondition_digest,
|
|
48191
|
+
binding.normalized_call_digest,
|
|
48192
|
+
binding.state,
|
|
48193
|
+
binding.target_id,
|
|
48194
|
+
binding.accepted_receipt_id,
|
|
48195
|
+
binding.result_revision,
|
|
48196
|
+
binding.result_digest,
|
|
48197
|
+
binding.removed_receipt_id,
|
|
48198
|
+
binding.created_at,
|
|
48199
|
+
binding.updated_at
|
|
48200
|
+
]);
|
|
48201
|
+
return result.rows.length === 1;
|
|
48202
|
+
}
|
|
48203
|
+
async setBindingAccepted(scope, resourceKind, targetSelector, update) {
|
|
48204
|
+
const result = await this.client.query(`
|
|
48205
|
+
UPDATE todos_project_registration_bindings
|
|
48206
|
+
SET state = 'accepted', target_id = $1, accepted_receipt_id = $2,
|
|
48207
|
+
result_revision = $3, result_digest = $4, updated_at = $5
|
|
48208
|
+
WHERE authority_id = $6 AND tenant_id = $7 AND corpus_id = $8
|
|
48209
|
+
AND resource_kind = $9 AND target_selector = $10 AND state = 'pending'
|
|
48210
|
+
RETURNING target_selector
|
|
48211
|
+
`, [
|
|
48212
|
+
update.target_id,
|
|
48213
|
+
update.accepted_receipt_id,
|
|
48214
|
+
update.result_revision,
|
|
48215
|
+
update.result_digest,
|
|
48216
|
+
update.updated_at,
|
|
48217
|
+
scope.authority_id,
|
|
48218
|
+
scope.tenant_id,
|
|
48219
|
+
scope.corpus_id,
|
|
48220
|
+
resourceKind,
|
|
48221
|
+
targetSelector
|
|
48222
|
+
]);
|
|
48223
|
+
if (result.rows.length !== 1) {
|
|
48224
|
+
throw new Error("Todos project registration binding was not pending at acceptance");
|
|
48225
|
+
}
|
|
48226
|
+
}
|
|
48227
|
+
async setBindingTerminal(scope, resourceKind, targetSelector, updatedAt) {
|
|
48228
|
+
await this.client.query(`
|
|
48229
|
+
UPDATE todos_project_registration_bindings
|
|
48230
|
+
SET state = 'terminal_nonacceptance', updated_at = $1
|
|
48231
|
+
WHERE authority_id = $2 AND tenant_id = $3 AND corpus_id = $4
|
|
48232
|
+
AND resource_kind = $5 AND target_selector = $6 AND state = 'pending'
|
|
48233
|
+
`, [
|
|
48234
|
+
updatedAt,
|
|
48235
|
+
scope.authority_id,
|
|
48236
|
+
scope.tenant_id,
|
|
48237
|
+
scope.corpus_id,
|
|
48238
|
+
resourceKind,
|
|
48239
|
+
targetSelector
|
|
48240
|
+
]);
|
|
48241
|
+
}
|
|
48242
|
+
async setBindingRemoved(scope, resourceKind, targetSelector, removedReceiptId, updatedAt) {
|
|
48243
|
+
const result = await this.client.query(`
|
|
48244
|
+
UPDATE todos_project_registration_bindings
|
|
48245
|
+
SET state = 'removed', removed_receipt_id = $1, updated_at = $2
|
|
48246
|
+
WHERE authority_id = $3 AND tenant_id = $4 AND corpus_id = $5
|
|
48247
|
+
AND resource_kind = $6 AND target_selector = $7 AND state = 'accepted'
|
|
48248
|
+
RETURNING target_selector
|
|
48249
|
+
`, [
|
|
48250
|
+
removedReceiptId,
|
|
48251
|
+
updatedAt,
|
|
48252
|
+
scope.authority_id,
|
|
48253
|
+
scope.tenant_id,
|
|
48254
|
+
scope.corpus_id,
|
|
48255
|
+
resourceKind,
|
|
48256
|
+
targetSelector
|
|
48257
|
+
]);
|
|
48258
|
+
if (result.rows.length !== 1) {
|
|
48259
|
+
throw new Error("Todos project registration binding was not accepted at removal");
|
|
48260
|
+
}
|
|
48261
|
+
}
|
|
48262
|
+
async findProjectConflict(path, taskListSlug) {
|
|
48263
|
+
const result = await this.client.query(`
|
|
48264
|
+
SELECT payload FROM ${this.tableName}
|
|
48265
|
+
WHERE service = $1 AND object_type = 'projects' AND deleted_at IS NULL
|
|
48266
|
+
AND (payload->>'path' = $2 OR payload->>'task_list_id' = $3)
|
|
48267
|
+
ORDER BY payload->>'created_at' ASC, object_id ASC
|
|
48268
|
+
LIMIT 1
|
|
48269
|
+
`, [this.service, path, taskListSlug]);
|
|
48270
|
+
return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
|
|
48271
|
+
}
|
|
48272
|
+
async findTaskListConflict(projectId, slug) {
|
|
48273
|
+
const result = await this.client.query(`
|
|
48274
|
+
SELECT payload FROM ${this.tableName}
|
|
48275
|
+
WHERE service = $1 AND object_type = 'task_lists' AND deleted_at IS NULL
|
|
48276
|
+
AND payload->>'project_id' = $2 AND payload->>'slug' = $3
|
|
48277
|
+
ORDER BY payload->>'created_at' ASC, object_id ASC
|
|
48278
|
+
LIMIT 1
|
|
48279
|
+
`, [this.service, projectId, slug]);
|
|
48280
|
+
return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
|
|
48281
|
+
}
|
|
48282
|
+
async createProject(input) {
|
|
48283
|
+
return await this.storage.projects.create(input);
|
|
48284
|
+
}
|
|
48285
|
+
async createTaskList(input) {
|
|
48286
|
+
return await this.storage.taskLists.create(input);
|
|
48287
|
+
}
|
|
48288
|
+
async getProject(id) {
|
|
48289
|
+
return await this.storage.projects.get(id);
|
|
48290
|
+
}
|
|
48291
|
+
async getTaskList(id) {
|
|
48292
|
+
return await this.storage.taskLists.get(id);
|
|
48293
|
+
}
|
|
48294
|
+
async lockCompensationWrites() {
|
|
48295
|
+
await this.client.query(`LOCK TABLE ${this.tableName} IN SHARE ROW EXCLUSIVE MODE`);
|
|
48296
|
+
}
|
|
48297
|
+
async hasDependents(resourceKind, targetId) {
|
|
48298
|
+
const referencePredicate = resourceKind === "project" ? `(
|
|
48299
|
+
payload->>'project_id' = $2
|
|
48300
|
+
OR payload->>'active_project_id' = $2
|
|
48301
|
+
OR payload->>'assigned_from_project' = $2
|
|
48302
|
+
OR payload->>'external_project_id' = $2
|
|
48303
|
+
)` : "payload->>'task_list_id' = $2";
|
|
48304
|
+
const result = await this.client.query(`
|
|
48305
|
+
SELECT EXISTS (
|
|
48306
|
+
SELECT 1 FROM ${this.tableName}
|
|
48307
|
+
WHERE service = $1 AND deleted_at IS NULL
|
|
48308
|
+
AND ${referencePredicate}
|
|
48309
|
+
LIMIT 1
|
|
48310
|
+
) AS exists
|
|
48311
|
+
`, [this.service, targetId]);
|
|
48312
|
+
return result.rows[0]?.exists === true;
|
|
48313
|
+
}
|
|
48314
|
+
async deleteProject(id) {
|
|
48315
|
+
return await this.storage.projects.delete(id);
|
|
48316
|
+
}
|
|
48317
|
+
async deleteTaskList(id) {
|
|
48318
|
+
return await this.storage.taskLists.delete(id);
|
|
48319
|
+
}
|
|
48320
|
+
}
|
|
48321
|
+
|
|
48322
|
+
class PostgresTodosProjectRegistrationBackend {
|
|
48323
|
+
client;
|
|
48324
|
+
kind = "postgresql";
|
|
48325
|
+
service;
|
|
48326
|
+
tableName;
|
|
48327
|
+
cursorTableName;
|
|
48328
|
+
schemaReady = null;
|
|
48329
|
+
constructor(client, options = {}) {
|
|
48330
|
+
this.client = client;
|
|
48331
|
+
this.service = options.service ?? "todos";
|
|
48332
|
+
this.tableName = safeIdentifier(options.tableName ?? "todos_sync_records", "tableName");
|
|
48333
|
+
this.cursorTableName = safeIdentifier(options.cursorTableName ?? "todos_sync_cursors", "cursorTableName");
|
|
48334
|
+
}
|
|
48335
|
+
async ensureSchema() {
|
|
48336
|
+
this.schemaReady ??= (async () => {
|
|
48337
|
+
for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
|
|
48338
|
+
await this.client.query(statement);
|
|
48339
|
+
}
|
|
48340
|
+
for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
|
|
48341
|
+
await this.client.query(statement);
|
|
48342
|
+
}
|
|
48343
|
+
})();
|
|
48344
|
+
await this.schemaReady;
|
|
48345
|
+
}
|
|
48346
|
+
async transaction(fn) {
|
|
48347
|
+
await this.ensureSchema();
|
|
48348
|
+
if (typeof this.client.transaction !== "function") {
|
|
48349
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "PostgreSQL project registration requires an authoritative transaction");
|
|
48350
|
+
}
|
|
48351
|
+
return this.client.transaction((transaction) => fn(new PostgresTodosProjectRegistrationTransaction(transaction, this.service, this.tableName, this.cursorTableName)));
|
|
48352
|
+
}
|
|
48353
|
+
async direct() {
|
|
48354
|
+
await this.ensureSchema();
|
|
48355
|
+
return new PostgresTodosProjectRegistrationTransaction(this.client, this.service, this.tableName, this.cursorTableName);
|
|
48356
|
+
}
|
|
48357
|
+
async getReceiptForLookup(identity) {
|
|
48358
|
+
return (await this.direct()).getReceiptForLookup(identity);
|
|
48359
|
+
}
|
|
48360
|
+
async getReceiptById(receiptId) {
|
|
48361
|
+
return (await this.direct()).getReceiptById(receiptId);
|
|
48362
|
+
}
|
|
48363
|
+
async getBinding(scope, resourceKind, targetSelector) {
|
|
48364
|
+
return (await this.direct()).getBinding(scope, resourceKind, targetSelector);
|
|
48365
|
+
}
|
|
48366
|
+
async getProject(id) {
|
|
48367
|
+
return (await this.direct()).getProject(id);
|
|
48368
|
+
}
|
|
48369
|
+
async getTaskList(id) {
|
|
48370
|
+
return (await this.direct()).getTaskList(id);
|
|
48371
|
+
}
|
|
48372
|
+
}
|
|
48373
|
+
var init_postgres2 = __esm(() => {
|
|
48374
|
+
init_postgres_adapter();
|
|
48375
|
+
init_postgres_sync();
|
|
48376
|
+
init_types4();
|
|
48377
|
+
});
|
|
48378
|
+
|
|
48379
|
+
// src/project-registration/sqlite.ts
|
|
48380
|
+
var sqliteTransactionTails, PROJECT_REFERENCE_COLUMNS, TASK_LIST_REFERENCE_COLUMNS;
|
|
48381
|
+
var init_sqlite = __esm(() => {
|
|
48382
|
+
init_local_sqlite();
|
|
48383
|
+
sqliteTransactionTails = new WeakMap;
|
|
48384
|
+
PROJECT_REFERENCE_COLUMNS = new Set([
|
|
48385
|
+
"project_id",
|
|
48386
|
+
"active_project_id",
|
|
48387
|
+
"assigned_from_project",
|
|
48388
|
+
"external_project_id"
|
|
48389
|
+
]);
|
|
48390
|
+
TASK_LIST_REFERENCE_COLUMNS = new Set(["task_list_id"]);
|
|
48391
|
+
});
|
|
48392
|
+
|
|
48393
|
+
// src/project-registration/authority.ts
|
|
48394
|
+
import { createHash as createHash14 } from "crypto";
|
|
48395
|
+
function canonicalProjectRegistrationJson(value) {
|
|
48396
|
+
return JSON.stringify(canonicalize2(value));
|
|
48397
|
+
}
|
|
48398
|
+
function canonicalize2(value) {
|
|
48399
|
+
if (Array.isArray(value))
|
|
48400
|
+
return value.map(canonicalize2);
|
|
48401
|
+
if (!value || typeof value !== "object")
|
|
48402
|
+
return value;
|
|
48403
|
+
const out = {};
|
|
48404
|
+
for (const key of Object.keys(value).sort()) {
|
|
48405
|
+
const entry2 = value[key];
|
|
48406
|
+
if (entry2 !== undefined)
|
|
48407
|
+
out[key] = canonicalize2(entry2);
|
|
48408
|
+
}
|
|
48409
|
+
return out;
|
|
48410
|
+
}
|
|
48411
|
+
function digestProjectRegistrationValue(value) {
|
|
48412
|
+
return createHash14("sha256").update(canonicalProjectRegistrationJson(value)).digest("hex");
|
|
48413
|
+
}
|
|
48414
|
+
function deriveTodosProjectRegistrationIdempotencyKey(input) {
|
|
48415
|
+
return `prk_${digestProjectRegistrationValue({
|
|
48416
|
+
route: TODOS_PROJECT_REGISTRATION_CALLER_ROUTE,
|
|
48417
|
+
...input
|
|
48418
|
+
}).slice(0, 48)}`;
|
|
48419
|
+
}
|
|
48420
|
+
function responseBytes(value) {
|
|
48421
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
48422
|
+
}
|
|
48423
|
+
function assertBounds(bounds) {
|
|
48424
|
+
if (!Number.isSafeInteger(bounds.response_byte_limit) || bounds.response_byte_limit <= 0) {
|
|
48425
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "response_byte_limit must be a positive integer");
|
|
48426
|
+
}
|
|
48427
|
+
if (!Number.isSafeInteger(bounds.time_budget_ms) || bounds.time_budget_ms <= 0) {
|
|
48428
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "time_budget_ms must be a positive integer");
|
|
48429
|
+
}
|
|
48430
|
+
}
|
|
48431
|
+
function assertResourceKind(value) {
|
|
48432
|
+
if (value !== "project" && value !== "task_list") {
|
|
48433
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project or task_list");
|
|
48434
|
+
}
|
|
48435
|
+
}
|
|
48436
|
+
function assertDirection(value) {
|
|
48437
|
+
if (value !== "forward" && value !== "inverse") {
|
|
48438
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "direction must be forward or inverse");
|
|
48439
|
+
}
|
|
48440
|
+
}
|
|
48441
|
+
function assertWithinBounds(value, bounds, startedAt) {
|
|
48442
|
+
const bytes = responseBytes(value);
|
|
48443
|
+
if (bytes > bounds.response_byte_limit) {
|
|
48444
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RESPONSE_TOO_LARGE", `registration response requires ${bytes} bytes but the bound is ${bounds.response_byte_limit}`, { response_bytes: bytes, response_byte_limit: bounds.response_byte_limit });
|
|
48445
|
+
}
|
|
48446
|
+
const elapsed = Date.now() - startedAt;
|
|
48447
|
+
if (elapsed > bounds.time_budget_ms) {
|
|
48448
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED", `registration call took ${elapsed}ms but the bound is ${bounds.time_budget_ms}ms`, { elapsed_ms: elapsed, time_budget_ms: bounds.time_budget_ms });
|
|
48449
|
+
}
|
|
48450
|
+
return { response_bytes: bytes, elapsed_ms: elapsed };
|
|
48451
|
+
}
|
|
48452
|
+
function withResponseControl(payload, bounds, startedAt) {
|
|
48453
|
+
const envelope = {
|
|
48454
|
+
...payload,
|
|
48455
|
+
response_control: {
|
|
48456
|
+
response_byte_limit: bounds.response_byte_limit,
|
|
48457
|
+
time_budget_ms: bounds.time_budget_ms,
|
|
48458
|
+
response_bytes: 0,
|
|
48459
|
+
elapsed_ms: 0,
|
|
48460
|
+
complete: true,
|
|
48461
|
+
truncated: false
|
|
48462
|
+
}
|
|
48463
|
+
};
|
|
48464
|
+
for (let attempt = 0;attempt < 8; attempt += 1) {
|
|
48465
|
+
const measured = assertWithinBounds(envelope, bounds, startedAt);
|
|
48466
|
+
const stable3 = envelope.response_control.response_bytes === measured.response_bytes && envelope.response_control.elapsed_ms === measured.elapsed_ms;
|
|
48467
|
+
envelope.response_control = {
|
|
48468
|
+
response_byte_limit: bounds.response_byte_limit,
|
|
48469
|
+
time_budget_ms: bounds.time_budget_ms,
|
|
48470
|
+
response_bytes: measured.response_bytes,
|
|
48471
|
+
elapsed_ms: measured.elapsed_ms,
|
|
48472
|
+
complete: true,
|
|
48473
|
+
truncated: false
|
|
48474
|
+
};
|
|
48475
|
+
if (stable3)
|
|
48476
|
+
break;
|
|
48477
|
+
}
|
|
48478
|
+
const finalMeasurement = assertWithinBounds(envelope, bounds, startedAt);
|
|
48479
|
+
envelope.response_control.response_bytes = finalMeasurement.response_bytes;
|
|
48480
|
+
envelope.response_control.elapsed_ms = finalMeasurement.elapsed_ms;
|
|
48481
|
+
return envelope;
|
|
48482
|
+
}
|
|
48483
|
+
function requireString(value, field, options = {}) {
|
|
48484
|
+
const min = options.min ?? 1;
|
|
48485
|
+
const max = options.max ?? 512;
|
|
48486
|
+
if (typeof value !== "string" || value.length < min || value.length > max || /[\u0000-\u001f]/.test(value) || options.pattern && !options.pattern.test(value)) {
|
|
48487
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} is not a valid bounded registration identifier`);
|
|
48488
|
+
}
|
|
48489
|
+
return value;
|
|
48490
|
+
}
|
|
48491
|
+
function exactKeys(value, expected, field) {
|
|
48492
|
+
const actual = Object.keys(value).sort();
|
|
48493
|
+
const wanted = [...expected].sort();
|
|
48494
|
+
if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
|
|
48495
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} must contain exactly: ${wanted.join(", ")}`);
|
|
48496
|
+
}
|
|
48497
|
+
}
|
|
48498
|
+
function publicReceipt(row) {
|
|
48499
|
+
const {
|
|
48500
|
+
target_selector: _targetSelector,
|
|
48501
|
+
normalized_call_digest: _normalizedCallDigest,
|
|
48502
|
+
...receipt
|
|
48503
|
+
} = row;
|
|
48504
|
+
return receipt;
|
|
48505
|
+
}
|
|
48506
|
+
function projectRegistrationPath(projectId) {
|
|
48507
|
+
return `hasna-project://${encodeURIComponent(projectId)}`;
|
|
48508
|
+
}
|
|
48509
|
+
function taskListSlug(projectSlug) {
|
|
48510
|
+
const slug = normalizeSlug(projectSlug);
|
|
48511
|
+
if (!slug || slug !== projectSlug) {
|
|
48512
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project_slug must be canonical kebab-case");
|
|
48513
|
+
}
|
|
48514
|
+
return `todos-${slug}`;
|
|
48515
|
+
}
|
|
48516
|
+
function deterministicTaskPrefix(projectSlug) {
|
|
48517
|
+
const letters = projectSlug.replace(/[^a-z0-9]/gi, "").toUpperCase();
|
|
48518
|
+
return (letters.slice(0, 3) || "PRJ").padEnd(3, "X");
|
|
48519
|
+
}
|
|
48520
|
+
function projectRecord(project) {
|
|
48521
|
+
return {
|
|
48522
|
+
target_id: project.id,
|
|
48523
|
+
revision: project.updated_at,
|
|
48524
|
+
digest: digestProjectRegistrationValue({
|
|
48525
|
+
id: project.id,
|
|
48526
|
+
name: project.name,
|
|
48527
|
+
path: project.path,
|
|
48528
|
+
description: project.description,
|
|
48529
|
+
task_list_id: project.task_list_id,
|
|
48530
|
+
task_prefix: project.task_prefix,
|
|
48531
|
+
task_counter: project.task_counter,
|
|
48532
|
+
created_at: project.created_at,
|
|
48533
|
+
updated_at: project.updated_at
|
|
48534
|
+
})
|
|
48535
|
+
};
|
|
48536
|
+
}
|
|
48537
|
+
function taskListRecord(taskList) {
|
|
48538
|
+
return {
|
|
48539
|
+
target_id: taskList.id,
|
|
48540
|
+
revision: taskList.updated_at,
|
|
48541
|
+
digest: digestProjectRegistrationValue({
|
|
48542
|
+
id: taskList.id,
|
|
48543
|
+
project_id: taskList.project_id,
|
|
48544
|
+
slug: taskList.slug,
|
|
48545
|
+
name: taskList.name,
|
|
48546
|
+
description: taskList.description,
|
|
48547
|
+
metadata: taskList.metadata,
|
|
48548
|
+
created_at: taskList.created_at,
|
|
48549
|
+
updated_at: taskList.updated_at
|
|
48550
|
+
})
|
|
48551
|
+
};
|
|
48552
|
+
}
|
|
48553
|
+
function receiptId(input) {
|
|
48554
|
+
return `tpr_${digestProjectRegistrationValue(input).slice(0, 40)}`;
|
|
48555
|
+
}
|
|
48556
|
+
function capabilityMatches(request, capability) {
|
|
48557
|
+
return request.authority_route === capability.route && request.package_version === capability.package_version && request.authority_id === capability.authority_id && request.tenant_id === capability.tenant_id && request.corpus_id === capability.corpus_id;
|
|
48558
|
+
}
|
|
48559
|
+
function authorityScope(capability) {
|
|
48560
|
+
return {
|
|
48561
|
+
authority_id: capability.authority_id,
|
|
48562
|
+
tenant_id: capability.tenant_id,
|
|
48563
|
+
corpus_id: capability.corpus_id
|
|
48564
|
+
};
|
|
48565
|
+
}
|
|
48566
|
+
function assertCapabilityRequest(request, capability) {
|
|
48567
|
+
if (!capabilityMatches(request, capability)) {
|
|
48568
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "registration request does not match this authority capability identity");
|
|
48569
|
+
}
|
|
48570
|
+
}
|
|
48571
|
+
function normalizedCallDigest(request) {
|
|
48572
|
+
return digestProjectRegistrationValue({
|
|
48573
|
+
authority_route: request.authority_route,
|
|
48574
|
+
package_version: request.package_version,
|
|
48575
|
+
authority_id: request.authority_id,
|
|
48576
|
+
tenant_id: request.tenant_id,
|
|
48577
|
+
corpus_id: request.corpus_id,
|
|
48578
|
+
operation_id: request.operation_id,
|
|
48579
|
+
step_id: request.step_id,
|
|
48580
|
+
resource_kind: request.resource_kind,
|
|
48581
|
+
direction: request.direction,
|
|
48582
|
+
target_selector: request.target_selector,
|
|
48583
|
+
idempotency_key: request.idempotency_key,
|
|
48584
|
+
request_digest: request.request_digest,
|
|
48585
|
+
precondition_digest: request.precondition_digest,
|
|
48586
|
+
project_id: request.project_id,
|
|
48587
|
+
project_slug: request.project_slug,
|
|
48588
|
+
project_name: request.project_name,
|
|
48589
|
+
desired: request.desired,
|
|
48590
|
+
accepted_receipt_id: request.accepted_receipt?.receipt_id ?? null
|
|
48591
|
+
});
|
|
48592
|
+
}
|
|
48593
|
+
function assertCommonRequest(request, capability) {
|
|
48594
|
+
assertBounds(request);
|
|
48595
|
+
assertResourceKind(request.resource_kind);
|
|
48596
|
+
assertDirection(request.direction);
|
|
48597
|
+
assertCapabilityRequest(request, capability);
|
|
48598
|
+
requireString(request.operation_id, "operation_id", {
|
|
48599
|
+
min: 8,
|
|
48600
|
+
max: 128,
|
|
48601
|
+
pattern: OPERATION_PATTERN
|
|
48602
|
+
});
|
|
48603
|
+
requireString(request.step_id, "step_id", {
|
|
48604
|
+
min: 3,
|
|
48605
|
+
max: 128,
|
|
48606
|
+
pattern: STEP_PATTERN
|
|
48607
|
+
});
|
|
48608
|
+
requireString(request.target_selector, "target_selector", { max: 512 });
|
|
48609
|
+
requireString(request.project_id, "project_id", {
|
|
48610
|
+
min: 16,
|
|
48611
|
+
max: 128,
|
|
48612
|
+
pattern: WORKSPACE_ID_PATTERN
|
|
48613
|
+
});
|
|
48614
|
+
requireString(request.project_name, "project_name", { max: 256 });
|
|
48615
|
+
requireString(request.project_slug, "project_slug", { max: 128 });
|
|
48616
|
+
requireString(request.request_digest, "request_digest", {
|
|
48617
|
+
min: 64,
|
|
48618
|
+
max: 64,
|
|
48619
|
+
pattern: SHA256_PATTERN
|
|
48620
|
+
});
|
|
48621
|
+
requireString(request.precondition_digest, "precondition_digest", {
|
|
48622
|
+
min: 64,
|
|
48623
|
+
max: 64,
|
|
48624
|
+
pattern: SHA256_PATTERN
|
|
48625
|
+
});
|
|
48626
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
48627
|
+
min: 52,
|
|
48628
|
+
max: 52,
|
|
48629
|
+
pattern: IDEMPOTENCY_PATTERN
|
|
48630
|
+
});
|
|
48631
|
+
if (!request.desired || typeof request.desired !== "object" || Array.isArray(request.desired)) {
|
|
48632
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "desired must be a JSON object");
|
|
48633
|
+
}
|
|
48634
|
+
const expectedKey = deriveTodosProjectRegistrationIdempotencyKey({
|
|
48635
|
+
operation_id: request.operation_id,
|
|
48636
|
+
step_id: request.step_id,
|
|
48637
|
+
direction: request.direction,
|
|
48638
|
+
target_selector: request.target_selector,
|
|
48639
|
+
request_digest: request.request_digest,
|
|
48640
|
+
precondition_digest: request.precondition_digest
|
|
48641
|
+
});
|
|
48642
|
+
if (request.idempotency_key !== expectedKey) {
|
|
48643
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the deterministic operation/step/direction payload", { expected: expectedKey });
|
|
48644
|
+
}
|
|
48645
|
+
taskListSlug(request.project_slug);
|
|
48646
|
+
}
|
|
48647
|
+
function assertForwardRequest(request, capability) {
|
|
48648
|
+
assertCommonRequest(request, capability);
|
|
48649
|
+
if (request.direction !== "forward") {
|
|
48650
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "create requires direction=forward");
|
|
48651
|
+
}
|
|
48652
|
+
const expectedRequestDigest = digestProjectRegistrationValue(request.desired);
|
|
48653
|
+
const expectedPreconditionDigest = digestProjectRegistrationValue({
|
|
48654
|
+
target_selector: request.target_selector,
|
|
48655
|
+
expected: "absent"
|
|
48656
|
+
});
|
|
48657
|
+
if (request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest) {
|
|
48658
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "request_digest or precondition_digest does not match normalized forward semantics", {
|
|
48659
|
+
expected_request_digest: expectedRequestDigest,
|
|
48660
|
+
expected_precondition_digest: expectedPreconditionDigest
|
|
48661
|
+
});
|
|
48662
|
+
}
|
|
48663
|
+
if (request.resource_kind === "project") {
|
|
48664
|
+
exactKeys(request.desired, ["source_project_id", "source_project_slug", "name"], "project desired");
|
|
48665
|
+
if (request.desired["source_project_id"] !== request.project_id || request.desired["source_project_slug"] !== request.project_slug || request.desired["name"] !== request.project_name || request.target_selector !== request.project_id) {
|
|
48666
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project desired state and target selector must match the complete Projects identity");
|
|
48667
|
+
}
|
|
48668
|
+
return;
|
|
48669
|
+
}
|
|
48670
|
+
if (request.resource_kind === "task_list") {
|
|
48671
|
+
exactKeys(request.desired, ["todos_project_id", "source_project_id", "name"], "task-list desired");
|
|
48672
|
+
const todosProjectId = request.desired["todos_project_id"];
|
|
48673
|
+
if (typeof todosProjectId !== "string" || !UUID_PATTERN.test(todosProjectId)) {
|
|
48674
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "task-list create requires the exact full Todos project UUID");
|
|
48675
|
+
}
|
|
48676
|
+
if (request.target_selector !== `${todosProjectId}:default` || request.desired["source_project_id"] !== request.project_id || request.desired["name"] !== request.project_name) {
|
|
48677
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "task-list desired state must bind the exact Todos project id and Projects identity");
|
|
48678
|
+
}
|
|
48679
|
+
return;
|
|
48680
|
+
}
|
|
48681
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "unsupported registration resource kind");
|
|
48682
|
+
}
|
|
48683
|
+
function assertInverseRequest(request, capability) {
|
|
48684
|
+
assertCommonRequest(request, capability);
|
|
48685
|
+
if (request.direction !== "inverse") {
|
|
48686
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "compensate requires direction=inverse");
|
|
48687
|
+
}
|
|
48688
|
+
const accepted = request.accepted_receipt;
|
|
48689
|
+
if (!accepted || accepted.authority !== "todos" || accepted.route !== capability.route || accepted.package_version !== capability.package_version || accepted.authority_id !== capability.authority_id || accepted.tenant_id !== capability.tenant_id || accepted.corpus_id !== capability.corpus_id || accepted.operation_id !== request.operation_id || accepted.step_id !== request.step_id || accepted.resource_kind !== request.resource_kind || accepted.direction !== "forward" || accepted.outcome !== "accepted" || !accepted.created_by_operation || !accepted.target_id || !accepted.result_revision || !accepted.result_digest) {
|
|
48690
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "inverse requires the complete accepted forward receipt created by this operation");
|
|
48691
|
+
}
|
|
48692
|
+
exactKeys(request.desired, ["accepted_receipt_id", "target_id"], "inverse desired");
|
|
48693
|
+
const expectedDesired = {
|
|
48694
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
48695
|
+
target_id: accepted.target_id
|
|
48696
|
+
};
|
|
48697
|
+
const expectedPrecondition = {
|
|
48698
|
+
expected_revision: accepted.result_revision,
|
|
48699
|
+
expected_digest: accepted.result_digest
|
|
48700
|
+
};
|
|
48701
|
+
const expectedRequestDigest = digestProjectRegistrationValue(expectedDesired);
|
|
48702
|
+
const expectedPreconditionDigest = digestProjectRegistrationValue(expectedPrecondition);
|
|
48703
|
+
if (canonicalProjectRegistrationJson(request.desired) !== canonicalProjectRegistrationJson(expectedDesired) || request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest || request.target_selector !== accepted.target_id) {
|
|
48704
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse request does not match the accepted receipt and exact readback precondition");
|
|
48705
|
+
}
|
|
48706
|
+
return accepted;
|
|
48707
|
+
}
|
|
48708
|
+
function makeReceipt(input, createdAt2) {
|
|
48709
|
+
return {
|
|
48710
|
+
...input,
|
|
48711
|
+
receipt_id: receiptId(input),
|
|
48712
|
+
created_at: createdAt2
|
|
48713
|
+
};
|
|
48714
|
+
}
|
|
48715
|
+
function receiptBase(request, callDigest, capability) {
|
|
48716
|
+
return {
|
|
48717
|
+
authority: "todos",
|
|
48718
|
+
route: capability.route,
|
|
48719
|
+
package_version: capability.package_version,
|
|
48720
|
+
authority_id: capability.authority_id,
|
|
48721
|
+
tenant_id: capability.tenant_id,
|
|
48722
|
+
corpus_id: capability.corpus_id,
|
|
48723
|
+
operation_id: request.operation_id,
|
|
48724
|
+
step_id: request.step_id,
|
|
48725
|
+
resource_kind: request.resource_kind,
|
|
48726
|
+
direction: request.direction,
|
|
48727
|
+
target_selector: request.target_selector,
|
|
48728
|
+
idempotency_key: request.idempotency_key,
|
|
48729
|
+
request_digest: request.request_digest,
|
|
48730
|
+
precondition_digest: request.precondition_digest,
|
|
48731
|
+
normalized_call_digest: callDigest
|
|
48732
|
+
};
|
|
48733
|
+
}
|
|
48734
|
+
function makeAcceptedReceipt(request, callDigest, capability, record, createdAt2) {
|
|
48735
|
+
return makeReceipt({
|
|
48736
|
+
...receiptBase(request, callDigest, capability),
|
|
48737
|
+
outcome: "accepted",
|
|
48738
|
+
reason: null,
|
|
48739
|
+
target_id: record.target_id,
|
|
48740
|
+
result_revision: record.revision,
|
|
48741
|
+
result_digest: record.digest,
|
|
48742
|
+
duplicate_of_receipt_id: null,
|
|
48743
|
+
accepted_receipt_id: request.direction === "inverse" ? request.accepted_receipt.receipt_id : null,
|
|
48744
|
+
created_by_operation: true
|
|
48745
|
+
}, createdAt2);
|
|
48746
|
+
}
|
|
48747
|
+
function makeDuplicateReceipt(request, callDigest, capability, accepted, createdAt2) {
|
|
48748
|
+
return makeReceipt({
|
|
48749
|
+
...receiptBase(request, callDigest, capability),
|
|
48750
|
+
outcome: "duplicate_of_accepted",
|
|
48751
|
+
reason: null,
|
|
48752
|
+
target_id: accepted.target_id,
|
|
48753
|
+
result_revision: accepted.result_revision,
|
|
48754
|
+
result_digest: accepted.result_digest,
|
|
48755
|
+
duplicate_of_receipt_id: accepted.receipt_id,
|
|
48756
|
+
accepted_receipt_id: null,
|
|
48757
|
+
created_by_operation: false
|
|
48758
|
+
}, createdAt2);
|
|
48759
|
+
}
|
|
48760
|
+
function makeTerminalReceipt(request, callDigest, capability, reason, createdAt2, options = {}) {
|
|
48761
|
+
return makeReceipt({
|
|
48762
|
+
...receiptBase(request, callDigest, capability),
|
|
48763
|
+
outcome: "terminal_nonacceptance",
|
|
48764
|
+
reason,
|
|
48765
|
+
target_id: options.targetId ?? null,
|
|
48766
|
+
result_revision: null,
|
|
48767
|
+
result_digest: null,
|
|
48768
|
+
duplicate_of_receipt_id: null,
|
|
48769
|
+
accepted_receipt_id: options.acceptedReceiptId ?? null,
|
|
48770
|
+
created_by_operation: false
|
|
48771
|
+
}, createdAt2);
|
|
48772
|
+
}
|
|
48773
|
+
async function insertDeterministicReceipt(transaction, receipt) {
|
|
48774
|
+
if (await transaction.insertReceipt(receipt))
|
|
48775
|
+
return receipt;
|
|
48776
|
+
const existing = await transaction.getReceiptById(receipt.receipt_id);
|
|
48777
|
+
const { created_at: _existingCreatedAt, ...existingContent } = existing ?? {};
|
|
48778
|
+
const { created_at: _receiptCreatedAt, ...receiptContent } = receipt;
|
|
48779
|
+
if (!existing || canonicalProjectRegistrationJson(existingContent) !== canonicalProjectRegistrationJson(receiptContent)) {
|
|
48780
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "deterministic receipt id is occupied by different immutable content", { receipt_id: receipt.receipt_id });
|
|
48781
|
+
}
|
|
48782
|
+
return existing;
|
|
48783
|
+
}
|
|
48784
|
+
function bindingFor(request, callDigest, timestamp4, capability) {
|
|
48785
|
+
return {
|
|
48786
|
+
...authorityScope(capability),
|
|
48787
|
+
resource_kind: request.resource_kind,
|
|
48788
|
+
target_selector: request.target_selector,
|
|
48789
|
+
operation_id: request.operation_id,
|
|
48790
|
+
step_id: request.step_id,
|
|
48791
|
+
direction: "forward",
|
|
48792
|
+
idempotency_key: request.idempotency_key,
|
|
48793
|
+
request_digest: request.request_digest,
|
|
48794
|
+
precondition_digest: request.precondition_digest,
|
|
48795
|
+
normalized_call_digest: callDigest,
|
|
48796
|
+
state: "pending",
|
|
48797
|
+
target_id: null,
|
|
48798
|
+
accepted_receipt_id: null,
|
|
48799
|
+
result_revision: null,
|
|
48800
|
+
result_digest: null,
|
|
48801
|
+
removed_receipt_id: null,
|
|
48802
|
+
created_at: timestamp4,
|
|
48803
|
+
updated_at: timestamp4
|
|
48804
|
+
};
|
|
48805
|
+
}
|
|
48806
|
+
|
|
48807
|
+
class PackageOwnedTodosProjectRegistrationAuthority {
|
|
48808
|
+
backend;
|
|
48809
|
+
authority = "todos";
|
|
48810
|
+
capabilityValue;
|
|
48811
|
+
now;
|
|
48812
|
+
faultInjector;
|
|
48813
|
+
constructor(backend, options = {}) {
|
|
48814
|
+
this.backend = backend;
|
|
48815
|
+
this.capabilityValue = {
|
|
48816
|
+
authority: "todos",
|
|
48817
|
+
route: TODOS_PROJECT_REGISTRATION_ROUTE,
|
|
48818
|
+
package_version: options.packageVersion ?? getPackageVersion(import.meta.url),
|
|
48819
|
+
authority_id: options.authorityId ?? "todos",
|
|
48820
|
+
tenant_id: options.tenantId ?? backend.kind,
|
|
48821
|
+
corpus_id: options.corpusId ?? `todos:${backend.kind}`,
|
|
48822
|
+
supported_resources: ["project", "task_list"],
|
|
48823
|
+
conditional_create: true,
|
|
48824
|
+
immutable_receipts: true,
|
|
48825
|
+
exact_terminal_lookup: true,
|
|
48826
|
+
exact_readback: true,
|
|
48827
|
+
conditional_inverse: true,
|
|
48828
|
+
ambiguous_outcome_reconciliation: true
|
|
48829
|
+
};
|
|
48830
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
48831
|
+
this.faultInjector = options.faultInjector;
|
|
48832
|
+
}
|
|
48833
|
+
async capability() {
|
|
48834
|
+
return {
|
|
48835
|
+
...this.capabilityValue,
|
|
48836
|
+
supported_resources: [...this.capabilityValue.supported_resources]
|
|
48837
|
+
};
|
|
48838
|
+
}
|
|
48839
|
+
async fault(point, request) {
|
|
48840
|
+
if (!this.faultInjector)
|
|
48841
|
+
return;
|
|
48842
|
+
try {
|
|
48843
|
+
await this.faultInjector(point, {
|
|
48844
|
+
operation_id: request.operation_id,
|
|
48845
|
+
step_id: request.step_id,
|
|
48846
|
+
resource_kind: request.resource_kind,
|
|
48847
|
+
direction: request.direction
|
|
48848
|
+
});
|
|
48849
|
+
} catch (cause) {
|
|
48850
|
+
throw new WriteBoundaryError(point, cause);
|
|
48851
|
+
}
|
|
48852
|
+
}
|
|
48853
|
+
async afterCommit(request) {
|
|
48854
|
+
await this.faultInjector?.("after_commit", {
|
|
48855
|
+
operation_id: request.operation_id,
|
|
48856
|
+
step_id: request.step_id,
|
|
48857
|
+
resource_kind: request.resource_kind,
|
|
48858
|
+
direction: request.direction
|
|
48859
|
+
});
|
|
48860
|
+
}
|
|
48861
|
+
async duplicateFor(transaction, request, callDigest, accepted) {
|
|
48862
|
+
const duplicate = makeDuplicateReceipt(request, callDigest, this.capabilityValue, accepted, this.now());
|
|
48863
|
+
return insertDeterministicReceipt(transaction, duplicate);
|
|
48864
|
+
}
|
|
48865
|
+
async terminalFor(transaction, request, callDigest, reason, options = {}) {
|
|
48866
|
+
return insertDeterministicReceipt(transaction, makeTerminalReceipt(request, callDigest, this.capabilityValue, reason, this.now(), options));
|
|
48867
|
+
}
|
|
48868
|
+
async existingForwardResolution(transaction, request, callDigest) {
|
|
48869
|
+
const exact = await transaction.getReceiptForLookup({
|
|
48870
|
+
...authorityScope(this.capabilityValue),
|
|
48871
|
+
operation_id: request.operation_id,
|
|
48872
|
+
step_id: request.step_id,
|
|
48873
|
+
resource_kind: request.resource_kind,
|
|
48874
|
+
direction: request.direction,
|
|
48875
|
+
idempotency_key: request.idempotency_key,
|
|
48876
|
+
target_selector: request.target_selector
|
|
48877
|
+
});
|
|
48878
|
+
if (exact) {
|
|
48879
|
+
if (exact.outcome === "terminal_nonacceptance")
|
|
48880
|
+
return exact;
|
|
48881
|
+
const accepted2 = exact.outcome === "accepted" ? exact : await transaction.getReceiptById(exact.duplicate_of_receipt_id);
|
|
48882
|
+
if (!accepted2) {
|
|
48883
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "duplicate receipt points to a missing accepted receipt");
|
|
48884
|
+
}
|
|
48885
|
+
if (accepted2.normalized_call_digest !== callDigest) {
|
|
48886
|
+
return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted2.target_id });
|
|
48887
|
+
}
|
|
48888
|
+
return this.duplicateFor(transaction, request, callDigest, accepted2);
|
|
48889
|
+
}
|
|
48890
|
+
const accepted = await transaction.getAcceptedReceiptForStep({
|
|
48891
|
+
...authorityScope(this.capabilityValue),
|
|
48892
|
+
operation_id: request.operation_id,
|
|
48893
|
+
step_id: request.step_id,
|
|
48894
|
+
resource_kind: request.resource_kind,
|
|
48895
|
+
direction: "forward"
|
|
48896
|
+
});
|
|
48897
|
+
if (!accepted)
|
|
48898
|
+
return null;
|
|
48899
|
+
if (accepted.normalized_call_digest === callDigest) {
|
|
48900
|
+
return this.duplicateFor(transaction, request, callDigest, accepted);
|
|
48901
|
+
}
|
|
48902
|
+
return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted.target_id });
|
|
48903
|
+
}
|
|
48904
|
+
async createObject(transaction, request) {
|
|
48905
|
+
if (request.resource_kind === "project") {
|
|
48906
|
+
const path = projectRegistrationPath(request.project_id);
|
|
48907
|
+
const slug2 = taskListSlug(request.project_slug);
|
|
48908
|
+
const conflict2 = await transaction.findProjectConflict(path, slug2);
|
|
48909
|
+
if (conflict2) {
|
|
48910
|
+
return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict2.id });
|
|
48911
|
+
}
|
|
48912
|
+
await this.fault("before_object_write", request);
|
|
48913
|
+
const project = await transaction.createProject({
|
|
48914
|
+
name: request.project_name,
|
|
48915
|
+
path,
|
|
48916
|
+
description: `Registered from Projects workspace ${request.project_id}`,
|
|
48917
|
+
task_list_id: slug2,
|
|
48918
|
+
task_prefix: deterministicTaskPrefix(request.project_slug)
|
|
48919
|
+
});
|
|
48920
|
+
await this.fault("after_object_write", request);
|
|
48921
|
+
return projectRecord(project);
|
|
48922
|
+
}
|
|
48923
|
+
const todosProjectId = String(request.desired["todos_project_id"]);
|
|
48924
|
+
const sourceBinding = await transaction.getBinding(authorityScope(this.capabilityValue), "project", request.project_id);
|
|
48925
|
+
if (!sourceBinding || sourceBinding.state !== "accepted" || sourceBinding.target_id !== todosProjectId) {
|
|
48926
|
+
return this.terminalFor(transaction, request, normalizedCallDigest(request), "exact_parent_registration_missing", { targetId: todosProjectId });
|
|
48927
|
+
}
|
|
48928
|
+
const parent = await transaction.getProject(todosProjectId);
|
|
48929
|
+
if (!parent) {
|
|
48930
|
+
return this.terminalFor(transaction, request, normalizedCallDigest(request), "exact_parent_project_missing", { targetId: todosProjectId });
|
|
48931
|
+
}
|
|
48932
|
+
const slug = taskListSlug(request.project_slug);
|
|
48933
|
+
const conflict = await transaction.findTaskListConflict(todosProjectId, slug);
|
|
48934
|
+
if (conflict) {
|
|
48935
|
+
return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict.id });
|
|
48936
|
+
}
|
|
48937
|
+
await this.fault("before_object_write", request);
|
|
48938
|
+
const taskList = await transaction.createTaskList({
|
|
48939
|
+
name: request.project_name,
|
|
48940
|
+
slug,
|
|
48941
|
+
project_id: todosProjectId,
|
|
48942
|
+
metadata: {
|
|
48943
|
+
source_project_id: request.project_id,
|
|
48944
|
+
registration_authority: "todos"
|
|
48945
|
+
}
|
|
48946
|
+
});
|
|
48947
|
+
await this.fault("after_object_write", request);
|
|
48948
|
+
if (taskList.project_id !== todosProjectId) {
|
|
48949
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "task-list create did not preserve the exact full Todos project id");
|
|
48950
|
+
}
|
|
48951
|
+
return taskListRecord(taskList);
|
|
48952
|
+
}
|
|
48953
|
+
async create(request) {
|
|
48954
|
+
const startedAt = Date.now();
|
|
48955
|
+
assertForwardRequest(request, this.capabilityValue);
|
|
48956
|
+
const callDigest = normalizedCallDigest(request);
|
|
48957
|
+
try {
|
|
48958
|
+
const row = await this.backend.transaction(async (transaction) => {
|
|
48959
|
+
await transaction.lockStep({
|
|
48960
|
+
...authorityScope(this.capabilityValue),
|
|
48961
|
+
operation_id: request.operation_id,
|
|
48962
|
+
step_id: request.step_id,
|
|
48963
|
+
resource_kind: request.resource_kind,
|
|
48964
|
+
direction: request.direction
|
|
48965
|
+
});
|
|
48966
|
+
const resolved = await this.existingForwardResolution(transaction, request, callDigest);
|
|
48967
|
+
if (resolved)
|
|
48968
|
+
return resolved;
|
|
48969
|
+
const timestamp4 = this.now();
|
|
48970
|
+
const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp4, this.capabilityValue));
|
|
48971
|
+
if (!claimed) {
|
|
48972
|
+
const binding = await transaction.getBinding(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector);
|
|
48973
|
+
if (binding?.state === "accepted" && binding.normalized_call_digest === callDigest && binding.accepted_receipt_id) {
|
|
48974
|
+
const accepted2 = await transaction.getReceiptById(binding.accepted_receipt_id);
|
|
48975
|
+
if (accepted2) {
|
|
48976
|
+
return this.duplicateFor(transaction, request, callDigest, accepted2);
|
|
48977
|
+
}
|
|
48978
|
+
}
|
|
48979
|
+
return this.terminalFor(transaction, request, callDigest, binding?.state === "removed" ? "target_registration_was_removed" : "target_already_registered", { targetId: binding?.target_id ?? null });
|
|
48980
|
+
}
|
|
48981
|
+
const recordOrTerminal = await this.createObject(transaction, request);
|
|
48982
|
+
if ("outcome" in recordOrTerminal) {
|
|
48983
|
+
await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
|
|
48984
|
+
return recordOrTerminal;
|
|
48985
|
+
}
|
|
48986
|
+
const accepted = makeAcceptedReceipt(request, callDigest, this.capabilityValue, recordOrTerminal, this.now());
|
|
48987
|
+
await this.fault("before_receipt_write", request);
|
|
48988
|
+
const stored = await insertDeterministicReceipt(transaction, accepted);
|
|
48989
|
+
await this.fault("after_receipt_write", request);
|
|
48990
|
+
await transaction.setBindingAccepted(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, {
|
|
48991
|
+
target_id: recordOrTerminal.target_id,
|
|
48992
|
+
accepted_receipt_id: stored.receipt_id,
|
|
48993
|
+
result_revision: recordOrTerminal.revision,
|
|
48994
|
+
result_digest: recordOrTerminal.digest,
|
|
48995
|
+
updated_at: this.now()
|
|
48996
|
+
});
|
|
48997
|
+
return stored;
|
|
48998
|
+
});
|
|
48999
|
+
await this.afterCommit(request);
|
|
49000
|
+
const receipt = publicReceipt(row);
|
|
49001
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
49002
|
+
return receipt;
|
|
49003
|
+
} catch (error) {
|
|
49004
|
+
if (!(error instanceof WriteBoundaryError))
|
|
49005
|
+
throw error;
|
|
49006
|
+
const terminal = await this.recordWriteFailure(request, callDigest, error.point);
|
|
49007
|
+
const receipt = publicReceipt(terminal);
|
|
49008
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
49009
|
+
return receipt;
|
|
49010
|
+
}
|
|
49011
|
+
}
|
|
49012
|
+
async recordWriteFailure(request, callDigest, point) {
|
|
49013
|
+
return this.backend.transaction(async (transaction) => {
|
|
49014
|
+
await transaction.lockStep({
|
|
49015
|
+
...authorityScope(this.capabilityValue),
|
|
49016
|
+
operation_id: request.operation_id,
|
|
49017
|
+
step_id: request.step_id,
|
|
49018
|
+
resource_kind: request.resource_kind,
|
|
49019
|
+
direction: request.direction
|
|
49020
|
+
});
|
|
49021
|
+
const exact = await transaction.getReceiptForLookup({
|
|
49022
|
+
...authorityScope(this.capabilityValue),
|
|
49023
|
+
operation_id: request.operation_id,
|
|
49024
|
+
step_id: request.step_id,
|
|
49025
|
+
resource_kind: request.resource_kind,
|
|
49026
|
+
direction: request.direction,
|
|
49027
|
+
idempotency_key: request.idempotency_key,
|
|
49028
|
+
target_selector: request.target_selector
|
|
49029
|
+
});
|
|
49030
|
+
if (exact)
|
|
49031
|
+
return exact;
|
|
49032
|
+
const accepted = await transaction.getAcceptedReceiptForStep({
|
|
49033
|
+
...authorityScope(this.capabilityValue),
|
|
49034
|
+
operation_id: request.operation_id,
|
|
49035
|
+
step_id: request.step_id,
|
|
49036
|
+
resource_kind: request.resource_kind,
|
|
49037
|
+
direction: request.direction
|
|
49038
|
+
});
|
|
49039
|
+
if (accepted) {
|
|
49040
|
+
return accepted.normalized_call_digest === callDigest ? this.duplicateFor(transaction, request, callDigest, accepted) : this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted.target_id });
|
|
49041
|
+
}
|
|
49042
|
+
const timestamp4 = this.now();
|
|
49043
|
+
const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp4, this.capabilityValue));
|
|
49044
|
+
const terminal = await this.terminalFor(transaction, request, callDigest, `write_failed:${point}`);
|
|
49045
|
+
if (claimed) {
|
|
49046
|
+
await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
|
|
49047
|
+
}
|
|
49048
|
+
return terminal;
|
|
49049
|
+
});
|
|
49050
|
+
}
|
|
49051
|
+
async readExact(request) {
|
|
49052
|
+
const startedAt = Date.now();
|
|
49053
|
+
assertBounds(request);
|
|
49054
|
+
assertResourceKind(request.resource_kind);
|
|
49055
|
+
if (!UUID_PATTERN.test(request.target_id)) {
|
|
49056
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "exact readback requires a complete Todos object UUID");
|
|
49057
|
+
}
|
|
49058
|
+
const record = request.resource_kind === "project" ? await this.backend.getProject(request.target_id).then((value) => value ? projectRecord(value) : null) : await this.backend.getTaskList(request.target_id).then((value) => value ? taskListRecord(value) : null);
|
|
49059
|
+
if (!record) {
|
|
49060
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", `registered ${request.resource_kind} was not found by exact id`, { target_id: request.target_id });
|
|
49061
|
+
}
|
|
49062
|
+
assertWithinBounds(record, request, startedAt);
|
|
49063
|
+
return record;
|
|
49064
|
+
}
|
|
49065
|
+
async lookupReceipt(request) {
|
|
49066
|
+
const startedAt = Date.now();
|
|
49067
|
+
assertBounds(request);
|
|
49068
|
+
assertResourceKind(request.resource_kind);
|
|
49069
|
+
assertDirection(request.direction);
|
|
49070
|
+
if (request.max_items !== 1) {
|
|
49071
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "max_items must be exactly 1 for terminal receipt lookup");
|
|
49072
|
+
}
|
|
49073
|
+
if (request.authority !== "todos" || request.authority_route !== this.capabilityValue.route || request.package_version !== this.capabilityValue.package_version || request.authority_id !== this.capabilityValue.authority_id || request.tenant_id !== this.capabilityValue.tenant_id || request.corpus_id !== this.capabilityValue.corpus_id) {
|
|
49074
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "receipt lookup does not match this authority capability identity");
|
|
49075
|
+
}
|
|
49076
|
+
requireString(request.operation_id, "operation_id", {
|
|
49077
|
+
min: 8,
|
|
49078
|
+
max: 128,
|
|
49079
|
+
pattern: OPERATION_PATTERN
|
|
49080
|
+
});
|
|
49081
|
+
requireString(request.step_id, "step_id", {
|
|
49082
|
+
min: 3,
|
|
49083
|
+
max: 128,
|
|
49084
|
+
pattern: STEP_PATTERN
|
|
49085
|
+
});
|
|
49086
|
+
requireString(request.target_selector, "target_selector", { max: 512 });
|
|
49087
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
49088
|
+
min: 52,
|
|
49089
|
+
max: 52,
|
|
49090
|
+
pattern: IDEMPOTENCY_PATTERN
|
|
49091
|
+
});
|
|
49092
|
+
if (request.target_id !== undefined && !UUID_PATTERN.test(request.target_id)) {
|
|
49093
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "receipt lookup target_id must be a complete Todos object UUID");
|
|
49094
|
+
}
|
|
49095
|
+
const receipt = await this.backend.getReceiptForLookup({
|
|
49096
|
+
...authorityScope(this.capabilityValue),
|
|
49097
|
+
operation_id: request.operation_id,
|
|
49098
|
+
step_id: request.step_id,
|
|
49099
|
+
resource_kind: request.resource_kind,
|
|
49100
|
+
direction: request.direction,
|
|
49101
|
+
idempotency_key: request.idempotency_key,
|
|
49102
|
+
target_selector: request.target_selector
|
|
49103
|
+
});
|
|
49104
|
+
if (!receipt || request.target_id && receipt.target_id !== request.target_id) {
|
|
49105
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "no exact terminal receipt matched the bounded lookup");
|
|
49106
|
+
}
|
|
49107
|
+
return withResponseControl({ receipt: publicReceipt(receipt) }, request, startedAt);
|
|
49108
|
+
}
|
|
49109
|
+
async storedAcceptedReceipt(request, supplied) {
|
|
49110
|
+
const stored = await this.backend.getReceiptById(supplied.receipt_id);
|
|
49111
|
+
if (!stored || stored.outcome !== "accepted" || !stored.created_by_operation || canonicalProjectRegistrationJson(publicReceipt(stored)) !== canonicalProjectRegistrationJson(supplied)) {
|
|
49112
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt is not an exact immutable receipt owned by this authority", { receipt_id: supplied.receipt_id });
|
|
49113
|
+
}
|
|
49114
|
+
if (stored.operation_id !== request.operation_id || stored.step_id !== request.step_id || stored.resource_kind !== request.resource_kind || stored.target_id !== supplied.target_id) {
|
|
49115
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt does not own this exact operation step and target");
|
|
49116
|
+
}
|
|
49117
|
+
return stored;
|
|
49118
|
+
}
|
|
49119
|
+
async compensate(request) {
|
|
49120
|
+
const startedAt = Date.now();
|
|
49121
|
+
const suppliedAccepted = assertInverseRequest(request, this.capabilityValue);
|
|
49122
|
+
const accepted = await this.storedAcceptedReceipt(request, suppliedAccepted);
|
|
49123
|
+
const callDigest = normalizedCallDigest(request);
|
|
49124
|
+
try {
|
|
49125
|
+
const row = await this.backend.transaction(async (transaction) => {
|
|
49126
|
+
await transaction.lockStep({
|
|
49127
|
+
...authorityScope(this.capabilityValue),
|
|
49128
|
+
operation_id: request.operation_id,
|
|
49129
|
+
step_id: request.step_id,
|
|
49130
|
+
resource_kind: request.resource_kind,
|
|
49131
|
+
direction: request.direction
|
|
49132
|
+
});
|
|
49133
|
+
const exact = await transaction.getReceiptForLookup({
|
|
49134
|
+
...authorityScope(this.capabilityValue),
|
|
49135
|
+
operation_id: request.operation_id,
|
|
49136
|
+
step_id: request.step_id,
|
|
49137
|
+
resource_kind: request.resource_kind,
|
|
49138
|
+
direction: "inverse",
|
|
49139
|
+
idempotency_key: request.idempotency_key,
|
|
49140
|
+
target_selector: request.target_selector
|
|
49141
|
+
});
|
|
49142
|
+
if (exact)
|
|
49143
|
+
return exact;
|
|
49144
|
+
const storedAccepted = await transaction.getReceiptById(accepted.receipt_id);
|
|
49145
|
+
if (!storedAccepted || storedAccepted.outcome !== "accepted" || !storedAccepted.created_by_operation) {
|
|
49146
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt disappeared before conditional inverse");
|
|
49147
|
+
}
|
|
49148
|
+
const binding = await transaction.getBinding(authorityScope(this.capabilityValue), accepted.resource_kind, accepted.target_selector);
|
|
49149
|
+
if (!binding || binding.state !== "accepted" || binding.accepted_receipt_id !== accepted.receipt_id || binding.target_id !== accepted.target_id) {
|
|
49150
|
+
return this.terminalFor(transaction, request, callDigest, "target_not_owned_by_receipt", {
|
|
49151
|
+
targetId: accepted.target_id,
|
|
49152
|
+
acceptedReceiptId: accepted.receipt_id
|
|
49153
|
+
});
|
|
49154
|
+
}
|
|
49155
|
+
await transaction.lockCompensationWrites();
|
|
49156
|
+
const object = request.resource_kind === "project" ? await transaction.getProject(accepted.target_id) : await transaction.getTaskList(accepted.target_id);
|
|
49157
|
+
if (!object) {
|
|
49158
|
+
return this.terminalFor(transaction, request, callDigest, "target_missing_before_inverse", {
|
|
49159
|
+
targetId: accepted.target_id,
|
|
49160
|
+
acceptedReceiptId: accepted.receipt_id
|
|
49161
|
+
});
|
|
49162
|
+
}
|
|
49163
|
+
const current = request.resource_kind === "project" ? projectRecord(object) : taskListRecord(object);
|
|
49164
|
+
if (current.revision !== accepted.result_revision || current.digest !== accepted.result_digest) {
|
|
49165
|
+
return this.terminalFor(transaction, request, callDigest, "target_drifted", {
|
|
49166
|
+
targetId: accepted.target_id,
|
|
49167
|
+
acceptedReceiptId: accepted.receipt_id
|
|
49168
|
+
});
|
|
49169
|
+
}
|
|
49170
|
+
if (await transaction.hasDependents(request.resource_kind, accepted.target_id)) {
|
|
49171
|
+
return this.terminalFor(transaction, request, callDigest, "target_has_dependents", {
|
|
49172
|
+
targetId: accepted.target_id,
|
|
49173
|
+
acceptedReceiptId: accepted.receipt_id
|
|
49174
|
+
});
|
|
49175
|
+
}
|
|
49176
|
+
await this.fault("before_object_write", request);
|
|
49177
|
+
const deleted = request.resource_kind === "project" ? await transaction.deleteProject(accepted.target_id) : await transaction.deleteTaskList(accepted.target_id);
|
|
49178
|
+
if (!deleted) {
|
|
49179
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "conditional inverse could not delete the exact accepted target");
|
|
49180
|
+
}
|
|
49181
|
+
await this.fault("after_object_write", request);
|
|
49182
|
+
const inverseRecord = {
|
|
49183
|
+
target_id: accepted.target_id,
|
|
49184
|
+
revision: "absent",
|
|
49185
|
+
digest: digestProjectRegistrationValue({
|
|
49186
|
+
target_id: accepted.target_id,
|
|
49187
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
49188
|
+
absent: true
|
|
49189
|
+
})
|
|
49190
|
+
};
|
|
49191
|
+
const inverse = makeAcceptedReceipt(request, callDigest, this.capabilityValue, inverseRecord, this.now());
|
|
49192
|
+
await this.fault("before_receipt_write", request);
|
|
49193
|
+
const stored = await insertDeterministicReceipt(transaction, inverse);
|
|
49194
|
+
await this.fault("after_receipt_write", request);
|
|
49195
|
+
await transaction.setBindingRemoved(authorityScope(this.capabilityValue), accepted.resource_kind, accepted.target_selector, stored.receipt_id, this.now());
|
|
49196
|
+
return stored;
|
|
49197
|
+
});
|
|
49198
|
+
await this.afterCommit(request);
|
|
49199
|
+
const receipt = publicReceipt(row);
|
|
49200
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
49201
|
+
return receipt;
|
|
49202
|
+
} catch (error) {
|
|
49203
|
+
if (!(error instanceof WriteBoundaryError))
|
|
49204
|
+
throw error;
|
|
49205
|
+
const terminal = await this.backend.transaction(async (transaction) => {
|
|
49206
|
+
await transaction.lockStep({
|
|
49207
|
+
...authorityScope(this.capabilityValue),
|
|
49208
|
+
operation_id: request.operation_id,
|
|
49209
|
+
step_id: request.step_id,
|
|
49210
|
+
resource_kind: request.resource_kind,
|
|
49211
|
+
direction: request.direction
|
|
49212
|
+
});
|
|
49213
|
+
return this.terminalFor(transaction, request, callDigest, `write_failed:${error.point}`, {
|
|
49214
|
+
targetId: accepted.target_id,
|
|
49215
|
+
acceptedReceiptId: accepted.receipt_id
|
|
49216
|
+
});
|
|
49217
|
+
});
|
|
49218
|
+
const receipt = publicReceipt(terminal);
|
|
49219
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
49220
|
+
return receipt;
|
|
49221
|
+
}
|
|
49222
|
+
}
|
|
49223
|
+
async verifyInverse(request) {
|
|
49224
|
+
const startedAt = Date.now();
|
|
49225
|
+
const accepted = assertInverseRequest(request, this.capabilityValue);
|
|
49226
|
+
await this.storedAcceptedReceipt(request, accepted);
|
|
49227
|
+
const receipt = await this.backend.getReceiptForLookup({
|
|
49228
|
+
...authorityScope(this.capabilityValue),
|
|
49229
|
+
operation_id: request.operation_id,
|
|
49230
|
+
step_id: request.step_id,
|
|
49231
|
+
resource_kind: request.resource_kind,
|
|
49232
|
+
direction: "inverse",
|
|
49233
|
+
idempotency_key: request.idempotency_key,
|
|
49234
|
+
target_selector: request.target_selector
|
|
49235
|
+
});
|
|
49236
|
+
if (!receipt || receipt.outcome !== "accepted" || receipt.accepted_receipt_id !== accepted.receipt_id || receipt.result_revision !== "absent" || !receipt.result_digest) {
|
|
49237
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "accepted conditional inverse receipt was not found");
|
|
49238
|
+
}
|
|
49239
|
+
const object = request.resource_kind === "project" ? await this.backend.getProject(accepted.target_id) : await this.backend.getTaskList(accepted.target_id);
|
|
49240
|
+
if (object) {
|
|
49241
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification found the accepted target still present");
|
|
49242
|
+
}
|
|
49243
|
+
const verification = {
|
|
49244
|
+
target_id: accepted.target_id,
|
|
49245
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
49246
|
+
absent: true,
|
|
49247
|
+
digest: digestProjectRegistrationValue({
|
|
49248
|
+
target_id: accepted.target_id,
|
|
49249
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
49250
|
+
absent: true
|
|
49251
|
+
})
|
|
49252
|
+
};
|
|
49253
|
+
assertWithinBounds(verification, request, startedAt);
|
|
49254
|
+
if (verification.digest !== receipt.result_digest) {
|
|
49255
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification digest does not match the immutable receipt");
|
|
49256
|
+
}
|
|
49257
|
+
return verification;
|
|
49258
|
+
}
|
|
49259
|
+
}
|
|
49260
|
+
function createPostgresTodosProjectRegistrationAuthority(client, options = {}) {
|
|
49261
|
+
const {
|
|
49262
|
+
service,
|
|
49263
|
+
tableName,
|
|
49264
|
+
cursorTableName,
|
|
49265
|
+
...authorityOptions
|
|
49266
|
+
} = options;
|
|
49267
|
+
return new PackageOwnedTodosProjectRegistrationAuthority(new PostgresTodosProjectRegistrationBackend(client, {
|
|
49268
|
+
service,
|
|
49269
|
+
tableName,
|
|
49270
|
+
cursorTableName
|
|
49271
|
+
}), authorityOptions);
|
|
49272
|
+
}
|
|
49273
|
+
var UUID_PATTERN, WORKSPACE_ID_PATTERN, OPERATION_PATTERN, STEP_PATTERN, SHA256_PATTERN, IDEMPOTENCY_PATTERN, WriteBoundaryError;
|
|
49274
|
+
var init_authority = __esm(() => {
|
|
49275
|
+
init_package_version();
|
|
49276
|
+
init_postgres2();
|
|
49277
|
+
init_sqlite();
|
|
49278
|
+
init_types4();
|
|
49279
|
+
UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
49280
|
+
WORKSPACE_ID_PATTERN = /^wks_[A-Za-z0-9][A-Za-z0-9_-]{11,}$/;
|
|
49281
|
+
OPERATION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
49282
|
+
STEP_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
|
|
49283
|
+
SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
49284
|
+
IDEMPOTENCY_PATTERN = /^prk_[0-9a-f]{48}$/;
|
|
49285
|
+
WriteBoundaryError = class WriteBoundaryError extends Error {
|
|
49286
|
+
point;
|
|
49287
|
+
cause;
|
|
49288
|
+
constructor(point, cause) {
|
|
49289
|
+
super(`Todos project registration failed at ${point}`);
|
|
49290
|
+
this.point = point;
|
|
49291
|
+
this.cause = cause;
|
|
49292
|
+
}
|
|
49293
|
+
};
|
|
49294
|
+
});
|
|
49295
|
+
|
|
49296
|
+
// src/project-registration/http.ts
|
|
49297
|
+
function json(body, status = 200) {
|
|
49298
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
|
|
49299
|
+
}
|
|
49300
|
+
function errorStatus(error) {
|
|
49301
|
+
switch (error.code) {
|
|
49302
|
+
case "TODOS_PROJECT_REGISTRATION_INVALID_INPUT":
|
|
49303
|
+
case "TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS":
|
|
49304
|
+
case "TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH":
|
|
49305
|
+
case "TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH":
|
|
49306
|
+
case "TODOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH":
|
|
49307
|
+
case "TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED":
|
|
49308
|
+
return 400;
|
|
49309
|
+
case "TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND":
|
|
49310
|
+
case "TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND":
|
|
49311
|
+
case "TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND":
|
|
49312
|
+
return 404;
|
|
49313
|
+
case "TODOS_PROJECT_REGISTRATION_RESPONSE_TOO_LARGE":
|
|
49314
|
+
return 413;
|
|
49315
|
+
case "TODOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED":
|
|
49316
|
+
return 408;
|
|
49317
|
+
case "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE":
|
|
49318
|
+
return 503;
|
|
49319
|
+
default:
|
|
49320
|
+
return 409;
|
|
49321
|
+
}
|
|
49322
|
+
}
|
|
49323
|
+
async function readJson(req) {
|
|
49324
|
+
try {
|
|
49325
|
+
const value = await req.json();
|
|
49326
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
49327
|
+
} catch {
|
|
49328
|
+
return null;
|
|
49329
|
+
}
|
|
49330
|
+
}
|
|
49331
|
+
async function handleTodosProjectRegistrationHttpRequest(req, url, authority, basePath = "/v1/project-registration") {
|
|
49332
|
+
const path = url.pathname;
|
|
49333
|
+
if (path !== basePath && !path.startsWith(`${basePath}/`))
|
|
49334
|
+
return null;
|
|
49335
|
+
const action = path.slice(basePath.length).split("/").filter(Boolean).join("/");
|
|
49336
|
+
const method = req.method.toUpperCase();
|
|
49337
|
+
try {
|
|
49338
|
+
if ((action === "" || action === "capability") && method === "GET") {
|
|
49339
|
+
return json({ capability: await authority.capability() });
|
|
49340
|
+
}
|
|
49341
|
+
if (method !== "POST")
|
|
49342
|
+
return json({ error: "method not allowed" }, 405);
|
|
49343
|
+
const body = await readJson(req);
|
|
49344
|
+
if (!body) {
|
|
49345
|
+
return json({
|
|
49346
|
+
error: "invalid JSON body",
|
|
49347
|
+
code: "TODOS_PROJECT_REGISTRATION_INVALID_INPUT"
|
|
49348
|
+
}, 400);
|
|
49349
|
+
}
|
|
49350
|
+
if (action === "create") {
|
|
49351
|
+
return json({
|
|
49352
|
+
receipt: await authority.create(body)
|
|
49353
|
+
}, 201);
|
|
49354
|
+
}
|
|
49355
|
+
if (action === "receipts/lookup") {
|
|
49356
|
+
return json(await authority.lookupReceipt(body));
|
|
49357
|
+
}
|
|
49358
|
+
if (action === "read-exact") {
|
|
49359
|
+
return json({
|
|
49360
|
+
record: await authority.readExact(body)
|
|
49361
|
+
});
|
|
49362
|
+
}
|
|
49363
|
+
if (action === "compensate") {
|
|
49364
|
+
return json({
|
|
49365
|
+
receipt: await authority.compensate(body)
|
|
49366
|
+
}, 201);
|
|
49367
|
+
}
|
|
49368
|
+
if (action === "verify-inverse") {
|
|
49369
|
+
return json({
|
|
49370
|
+
verification: await authority.verifyInverse(body)
|
|
49371
|
+
});
|
|
49372
|
+
}
|
|
49373
|
+
return json({
|
|
49374
|
+
error: "unknown Todos project-registration route",
|
|
49375
|
+
code: "TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND"
|
|
49376
|
+
}, 404);
|
|
49377
|
+
} catch (cause) {
|
|
49378
|
+
if (cause instanceof TodosProjectRegistrationError) {
|
|
49379
|
+
return json({
|
|
49380
|
+
error: cause.message,
|
|
49381
|
+
code: cause.code,
|
|
49382
|
+
details: cause.details,
|
|
49383
|
+
authoritative: true
|
|
49384
|
+
}, errorStatus(cause));
|
|
49385
|
+
}
|
|
49386
|
+
return json({
|
|
49387
|
+
error: cause instanceof Error ? cause.message : "internal registration error",
|
|
49388
|
+
code: "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE"
|
|
49389
|
+
}, 500);
|
|
49390
|
+
}
|
|
49391
|
+
}
|
|
49392
|
+
var JSON_HEADERS;
|
|
49393
|
+
var init_http2 = __esm(() => {
|
|
49394
|
+
init_types4();
|
|
49395
|
+
JSON_HEADERS = { "Content-Type": "application/json" };
|
|
49396
|
+
});
|
|
49397
|
+
|
|
49398
|
+
// src/project-registration/index.ts
|
|
49399
|
+
var init_project_registration = __esm(() => {
|
|
49400
|
+
init_authority();
|
|
49401
|
+
init_http2();
|
|
49402
|
+
init_postgres2();
|
|
49403
|
+
init_sqlite();
|
|
49404
|
+
init_types4();
|
|
49405
|
+
});
|
|
49406
|
+
|
|
47726
49407
|
// src/storage/comment-redaction-backfill.ts
|
|
47727
49408
|
function assertSafeIdentifier2(value) {
|
|
47728
49409
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
@@ -47831,6 +49512,7 @@ __export(exports_cloud, {
|
|
|
47831
49512
|
isCloudModeEnabled: () => isCloudModeEnabled,
|
|
47832
49513
|
getCloudVerifier: () => getCloudVerifier,
|
|
47833
49514
|
getCloudStorageAdapter: () => getCloudStorageAdapter,
|
|
49515
|
+
getCloudProjectRegistrationAuthority: () => getCloudProjectRegistrationAuthority,
|
|
47834
49516
|
getCloudPrGroupLedger: () => getCloudPrGroupLedger,
|
|
47835
49517
|
getApiKeyStore: () => getApiKeyStore,
|
|
47836
49518
|
ensureCloudTaskShortIdIndex: () => ensureCloudTaskShortIdIndex,
|
|
@@ -47879,6 +49561,17 @@ function getCloudPrGroupLedger() {
|
|
|
47879
49561
|
cachedPrGroupLedger = new PrGroupLedger(new PostgresPrGroupLedgerPersistence(getClient()));
|
|
47880
49562
|
return cachedPrGroupLedger;
|
|
47881
49563
|
}
|
|
49564
|
+
function getCloudProjectRegistrationAuthority() {
|
|
49565
|
+
if (cachedProjectRegistrationAuthority)
|
|
49566
|
+
return cachedProjectRegistrationAuthority;
|
|
49567
|
+
cachedProjectRegistrationAuthority = createPostgresTodosProjectRegistrationAuthority(getClient(), {
|
|
49568
|
+
service: TODOS_APP_SLUG,
|
|
49569
|
+
authorityId: TODOS_APP_SLUG,
|
|
49570
|
+
tenantId: process.env.HASNA_TODOS_TENANT_ID ?? "default",
|
|
49571
|
+
corpusId: process.env.HASNA_TODOS_CORPUS_ID ?? `${TODOS_APP_SLUG}:postgresql`
|
|
49572
|
+
});
|
|
49573
|
+
return cachedProjectRegistrationAuthority;
|
|
49574
|
+
}
|
|
47882
49575
|
function authClient() {
|
|
47883
49576
|
const client = getClient();
|
|
47884
49577
|
return {
|
|
@@ -47927,6 +49620,9 @@ async function ensureCloudSchema() {
|
|
|
47927
49620
|
for (const sql of postgresPrGroupSchemaSql()) {
|
|
47928
49621
|
await client.query(sql);
|
|
47929
49622
|
}
|
|
49623
|
+
for (const sql of postgresTodosProjectRegistrationSchemaSql()) {
|
|
49624
|
+
await client.query(sql);
|
|
49625
|
+
}
|
|
47930
49626
|
await getApiKeyStore().ensureSchema();
|
|
47931
49627
|
})();
|
|
47932
49628
|
return schemaEnsured;
|
|
@@ -47967,14 +49663,16 @@ async function closeCloud() {
|
|
|
47967
49663
|
cachedStore = null;
|
|
47968
49664
|
cachedVerifier = null;
|
|
47969
49665
|
cachedPrGroupLedger = null;
|
|
49666
|
+
cachedProjectRegistrationAuthority = null;
|
|
47970
49667
|
schemaEnsured = null;
|
|
47971
49668
|
}
|
|
47972
|
-
var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, schemaEnsured = null;
|
|
49669
|
+
var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, schemaEnsured = null;
|
|
47973
49670
|
var init_cloud = __esm(() => {
|
|
47974
49671
|
init_cloud_client();
|
|
47975
49672
|
init_postgres_adapter();
|
|
47976
49673
|
init_ledger();
|
|
47977
49674
|
init_postgres();
|
|
49675
|
+
init_project_registration();
|
|
47978
49676
|
init_postgres_sync();
|
|
47979
49677
|
init_comment_redaction_backfill();
|
|
47980
49678
|
});
|
|
@@ -48333,9 +50031,9 @@ function parseBoundedLimit(value, fallback, max) {
|
|
|
48333
50031
|
return fallback;
|
|
48334
50032
|
return Math.min(parsed, max);
|
|
48335
50033
|
}
|
|
48336
|
-
function mapTaskError(e,
|
|
50034
|
+
function mapTaskError(e, json3) {
|
|
48337
50035
|
if (e instanceof VersionConflictError) {
|
|
48338
|
-
return
|
|
50036
|
+
return json3({
|
|
48339
50037
|
error: e.message,
|
|
48340
50038
|
code: VersionConflictError.code,
|
|
48341
50039
|
expected_version: e.expectedVersion,
|
|
@@ -48343,23 +50041,23 @@ function mapTaskError(e, json2) {
|
|
|
48343
50041
|
}, 409);
|
|
48344
50042
|
}
|
|
48345
50043
|
if (e instanceof TaskNotFoundError) {
|
|
48346
|
-
return
|
|
50044
|
+
return json3({ error: e.message, code: TaskNotFoundError.code }, 404);
|
|
48347
50045
|
}
|
|
48348
50046
|
if (e instanceof LockError) {
|
|
48349
|
-
return
|
|
50047
|
+
return json3({ error: e.message, code: LockError.code }, 409);
|
|
48350
50048
|
}
|
|
48351
50049
|
if (e instanceof CompletionGuardError) {
|
|
48352
|
-
return
|
|
50050
|
+
return json3({
|
|
48353
50051
|
error: e.message,
|
|
48354
50052
|
code: CompletionGuardError.code,
|
|
48355
50053
|
retry_after: e.retryAfterSeconds ?? null
|
|
48356
50054
|
}, 409);
|
|
48357
50055
|
}
|
|
48358
50056
|
if (e instanceof TaskNotStartableError) {
|
|
48359
|
-
return
|
|
50057
|
+
return json3({ error: e.message, code: TaskNotStartableError.code }, 409);
|
|
48360
50058
|
}
|
|
48361
50059
|
if (e instanceof Error && / is blocked by /.test(e.message)) {
|
|
48362
|
-
return
|
|
50060
|
+
return json3({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
|
|
48363
50061
|
}
|
|
48364
50062
|
return null;
|
|
48365
50063
|
}
|
|
@@ -48444,11 +50142,11 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
48444
50142
|
}
|
|
48445
50143
|
});
|
|
48446
50144
|
}
|
|
48447
|
-
function handleHealth(_ctx,
|
|
50145
|
+
function handleHealth(_ctx, json3) {
|
|
48448
50146
|
const stats2 = getTaskStats();
|
|
48449
50147
|
const staleCount = getStaleTasks(30).length;
|
|
48450
50148
|
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
48451
|
-
return
|
|
50149
|
+
return json3({
|
|
48452
50150
|
status: staleCount === 0 && overdueRecurring === 0 ? "ok" : "warn",
|
|
48453
50151
|
tasks: stats2.total,
|
|
48454
50152
|
stale: staleCount,
|
|
@@ -48456,18 +50154,18 @@ function handleHealth(_ctx, json2) {
|
|
|
48456
50154
|
timestamp: new Date().toISOString()
|
|
48457
50155
|
});
|
|
48458
50156
|
}
|
|
48459
|
-
function handleHeadlessBoundary(_ctx,
|
|
50157
|
+
function handleHeadlessBoundary(_ctx, json3) {
|
|
48460
50158
|
const { getHeadlessBoundaryManifest: getHeadlessBoundaryManifest2 } = (init_headless_boundaries(), __toCommonJS(exports_headless_boundaries));
|
|
48461
|
-
return
|
|
50159
|
+
return json3(getHeadlessBoundaryManifest2());
|
|
48462
50160
|
}
|
|
48463
|
-
function handleStats(_ctx,
|
|
50161
|
+
function handleStats(_ctx, json3) {
|
|
48464
50162
|
const stats2 = getTaskStats();
|
|
48465
50163
|
const byStatus = stats2.by_status;
|
|
48466
50164
|
const projects = listProjects();
|
|
48467
50165
|
const agents = listAgents();
|
|
48468
50166
|
const staleCount = getStaleTasks(30).length;
|
|
48469
50167
|
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
48470
|
-
return
|
|
50168
|
+
return json3({
|
|
48471
50169
|
total_tasks: stats2.total,
|
|
48472
50170
|
pending: byStatus["pending"] ?? 0,
|
|
48473
50171
|
in_progress: byStatus["in_progress"] ?? 0,
|
|
@@ -48490,10 +50188,10 @@ function taskStatusQueryParam(url) {
|
|
|
48490
50188
|
return { ok: false, message: result.message };
|
|
48491
50189
|
return { ok: true, value: collapseEnumValues(result.values) };
|
|
48492
50190
|
}
|
|
48493
|
-
async function handleListTasks(_req, url, _ctx,
|
|
50191
|
+
async function handleListTasks(_req, url, _ctx, json3, taskToSummary2) {
|
|
48494
50192
|
const statusParam = taskStatusQueryParam(url);
|
|
48495
50193
|
if (!statusParam.ok)
|
|
48496
|
-
return
|
|
50194
|
+
return json3({ error: statusParam.message }, 400);
|
|
48497
50195
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48498
50196
|
const sessionId = url.searchParams.get("session_id") || undefined;
|
|
48499
50197
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
@@ -48508,13 +50206,13 @@ async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
48508
50206
|
limit: limitParam ? parseInt(limitParam, 10) : undefined,
|
|
48509
50207
|
offset: offsetParam ? parseInt(offsetParam, 10) : undefined
|
|
48510
50208
|
});
|
|
48511
|
-
return
|
|
50209
|
+
return json3(tasks.map((t) => taskToSummary2(t, fields)));
|
|
48512
50210
|
}
|
|
48513
|
-
async function handleCreateTask(req, ctx,
|
|
50211
|
+
async function handleCreateTask(req, ctx, json3, taskToSummary2) {
|
|
48514
50212
|
try {
|
|
48515
50213
|
const body = await req.json();
|
|
48516
50214
|
if (!body.title)
|
|
48517
|
-
return
|
|
50215
|
+
return json3({ error: "Missing 'title'" }, 400);
|
|
48518
50216
|
const createdBy = body.created_by ?? body.agent_id ?? "dashboard";
|
|
48519
50217
|
const task2 = createTask({
|
|
48520
50218
|
title: body.title,
|
|
@@ -48526,19 +50224,19 @@ async function handleCreateTask(req, ctx, json2, taskToSummary2) {
|
|
|
48526
50224
|
...body.assigned_to ? { assigned_to: body.assigned_to } : {}
|
|
48527
50225
|
});
|
|
48528
50226
|
ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "created", agent_id: task2.agent_id, project_id: task2.project_id });
|
|
48529
|
-
return
|
|
50227
|
+
return json3(taskToSummary2(task2), 201);
|
|
48530
50228
|
} catch (e) {
|
|
48531
|
-
return
|
|
50229
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
|
|
48532
50230
|
}
|
|
48533
50231
|
}
|
|
48534
|
-
async function handleUpsertTask(req, ctx,
|
|
50232
|
+
async function handleUpsertTask(req, ctx, json3, taskToSummary2) {
|
|
48535
50233
|
try {
|
|
48536
50234
|
const body = await req.json();
|
|
48537
50235
|
if (typeof body["fingerprint"] !== "string" || body["fingerprint"].trim() === "") {
|
|
48538
|
-
return
|
|
50236
|
+
return json3({ error: "Missing 'fingerprint'" }, 400);
|
|
48539
50237
|
}
|
|
48540
50238
|
if (typeof body["title"] !== "string" || body["title"].trim() === "") {
|
|
48541
|
-
return
|
|
50239
|
+
return json3({ error: "Missing 'title'" }, 400);
|
|
48542
50240
|
}
|
|
48543
50241
|
const metadata = body["metadata"] && typeof body["metadata"] === "object" && !Array.isArray(body["metadata"]) ? { ...body["metadata"] } : {};
|
|
48544
50242
|
for (const key of ["expectation_id", "expectation_fingerprint", "evidence_paths", "origin_loop_id", "origin_run_id", "expected", "observed", "acceptance"]) {
|
|
@@ -48559,9 +50257,9 @@ async function handleUpsertTask(req, ctx, json2, taskToSummary2) {
|
|
|
48559
50257
|
metadata
|
|
48560
50258
|
});
|
|
48561
50259
|
ctx.broadcastEvent({ type: "task", task_id: result.task.id, action: result.created ? "created" : "updated", agent_id: result.task.agent_id, project_id: result.task.project_id });
|
|
48562
|
-
return
|
|
50260
|
+
return json3({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
|
|
48563
50261
|
} catch (e) {
|
|
48564
|
-
return
|
|
50262
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
|
|
48565
50263
|
}
|
|
48566
50264
|
}
|
|
48567
50265
|
function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
@@ -48613,11 +50311,11 @@ function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
|
48613
50311
|
}
|
|
48614
50312
|
});
|
|
48615
50313
|
}
|
|
48616
|
-
async function handleTasksBulk(req, _ctx,
|
|
50314
|
+
async function handleTasksBulk(req, _ctx, json3) {
|
|
48617
50315
|
try {
|
|
48618
50316
|
const body = await req.json();
|
|
48619
50317
|
if (!body.ids?.length || !body.action)
|
|
48620
|
-
return
|
|
50318
|
+
return json3({ error: "Missing ids or action" }, 400);
|
|
48621
50319
|
const results = [];
|
|
48622
50320
|
for (const id of body.ids) {
|
|
48623
50321
|
try {
|
|
@@ -48635,66 +50333,66 @@ async function handleTasksBulk(req, _ctx, json2) {
|
|
|
48635
50333
|
results.push({ id, success: false, error: e instanceof Error ? e.message : "Failed" });
|
|
48636
50334
|
}
|
|
48637
50335
|
}
|
|
48638
|
-
return
|
|
50336
|
+
return json3({ results, succeeded: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length });
|
|
48639
50337
|
} catch (e) {
|
|
48640
|
-
return
|
|
50338
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48641
50339
|
}
|
|
48642
50340
|
}
|
|
48643
|
-
function handleTasksStatus(_req, url, _ctx,
|
|
50341
|
+
function handleTasksStatus(_req, url, _ctx, json3) {
|
|
48644
50342
|
try {
|
|
48645
50343
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48646
50344
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
48647
50345
|
const status = getStatus(projectId ? { project_id: projectId } : undefined, agentId);
|
|
48648
|
-
return
|
|
50346
|
+
return json3(status);
|
|
48649
50347
|
} catch (e) {
|
|
48650
|
-
return
|
|
50348
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48651
50349
|
}
|
|
48652
50350
|
}
|
|
48653
|
-
function handleTasksNext(_req, url, _ctx,
|
|
50351
|
+
function handleTasksNext(_req, url, _ctx, json3, taskToSummary2) {
|
|
48654
50352
|
try {
|
|
48655
50353
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48656
50354
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
48657
50355
|
const fields = parseFieldsParam(url);
|
|
48658
50356
|
const task2 = getNextTask(agentId, projectId ? { project_id: projectId } : undefined);
|
|
48659
|
-
return
|
|
50357
|
+
return json3({ task: task2 ? taskToSummary2(task2, fields) : null });
|
|
48660
50358
|
} catch (e) {
|
|
48661
|
-
return
|
|
50359
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48662
50360
|
}
|
|
48663
50361
|
}
|
|
48664
|
-
function handleTasksActive(_req, url, _ctx,
|
|
50362
|
+
function handleTasksActive(_req, url, _ctx, json3) {
|
|
48665
50363
|
try {
|
|
48666
50364
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48667
50365
|
const work = getActiveWork(projectId ? { project_id: projectId } : undefined);
|
|
48668
|
-
return
|
|
50366
|
+
return json3({ active: work, count: work.length });
|
|
48669
50367
|
} catch (e) {
|
|
48670
|
-
return
|
|
50368
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48671
50369
|
}
|
|
48672
50370
|
}
|
|
48673
|
-
function handleTasksStale(_req, url, _ctx,
|
|
50371
|
+
function handleTasksStale(_req, url, _ctx, json3, taskToSummary2) {
|
|
48674
50372
|
try {
|
|
48675
50373
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48676
50374
|
const minutes2 = parseInt(url.searchParams.get("minutes") || "30", 10);
|
|
48677
50375
|
const fields = parseFieldsParam(url);
|
|
48678
50376
|
const tasks = getStaleTasks(minutes2, projectId ? { project_id: projectId } : undefined);
|
|
48679
|
-
return
|
|
50377
|
+
return json3({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length });
|
|
48680
50378
|
} catch (e) {
|
|
48681
|
-
return
|
|
50379
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48682
50380
|
}
|
|
48683
50381
|
}
|
|
48684
|
-
function handleTasksChanged(_req, url, _ctx,
|
|
50382
|
+
function handleTasksChanged(_req, url, _ctx, json3, taskToSummary2) {
|
|
48685
50383
|
try {
|
|
48686
50384
|
const since = url.searchParams.get("since");
|
|
48687
50385
|
if (!since)
|
|
48688
|
-
return
|
|
50386
|
+
return json3({ error: "since parameter required (ISO date string)" }, 400);
|
|
48689
50387
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48690
50388
|
const fields = parseFieldsParam(url);
|
|
48691
50389
|
const tasks = getTasksChangedSince(since, projectId ? { project_id: projectId } : undefined);
|
|
48692
|
-
return
|
|
50390
|
+
return json3({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length, since });
|
|
48693
50391
|
} catch (e) {
|
|
48694
|
-
return
|
|
50392
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48695
50393
|
}
|
|
48696
50394
|
}
|
|
48697
|
-
function handleTasksContext(_req, url, _ctx,
|
|
50395
|
+
function handleTasksContext(_req, url, _ctx, json3, taskToSummary2) {
|
|
48698
50396
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
48699
50397
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48700
50398
|
const format = url.searchParams.get("format") || "text";
|
|
@@ -48703,7 +50401,7 @@ function handleTasksContext(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
48703
50401
|
const status = getStatus(filters, agentId);
|
|
48704
50402
|
const next = getNextTask(agentId, filters);
|
|
48705
50403
|
if (format === "json") {
|
|
48706
|
-
return
|
|
50404
|
+
return json3({ status, next_task: next ? taskToSummary2(next, fields) : null });
|
|
48707
50405
|
}
|
|
48708
50406
|
const lines = [];
|
|
48709
50407
|
lines.push(`Tasks: ${status.pending} pending | ${status.in_progress} active | ${status.completed} done`);
|
|
@@ -48720,18 +50418,18 @@ function handleTasksContext(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
48720
50418
|
`);
|
|
48721
50419
|
return new Response(text2, { headers: { "Content-Type": "text/plain" } });
|
|
48722
50420
|
}
|
|
48723
|
-
function handleTaskAttachments(id, _ctx,
|
|
50421
|
+
function handleTaskAttachments(id, _ctx, json3) {
|
|
48724
50422
|
const task2 = getTask(id);
|
|
48725
50423
|
if (!task2)
|
|
48726
|
-
return
|
|
50424
|
+
return json3({ error: "Task not found" }, 404);
|
|
48727
50425
|
const evidence = task2.metadata?._evidence || {};
|
|
48728
50426
|
const attachmentIds = evidence.attachments || [];
|
|
48729
|
-
return
|
|
50427
|
+
return json3({ task_id: id, short_id: task2.short_id, attachment_ids: attachmentIds, count: attachmentIds.length, files_changed: evidence.files_changed, commit_hash: evidence.commit_hash, notes: evidence.notes });
|
|
48730
50428
|
}
|
|
48731
|
-
async function handleTaskProgress(id, req, method, _ctx,
|
|
50429
|
+
async function handleTaskProgress(id, req, method, _ctx, json3, url) {
|
|
48732
50430
|
const task2 = getTask(id);
|
|
48733
50431
|
if (!task2)
|
|
48734
|
-
return
|
|
50432
|
+
return json3({ error: "Task not found" }, 404);
|
|
48735
50433
|
if (method === "GET") {
|
|
48736
50434
|
const all = listComments(id);
|
|
48737
50435
|
const progress = all.filter((c) => c.type === "progress");
|
|
@@ -48739,7 +50437,7 @@ async function handleTaskProgress(id, req, method, _ctx, json2, url) {
|
|
|
48739
50437
|
const format = url?.searchParams.get("format") || "compact";
|
|
48740
50438
|
const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 200);
|
|
48741
50439
|
const progressEntries = format === "full" ? progress : progress.slice(-limit);
|
|
48742
|
-
return
|
|
50440
|
+
return json3({
|
|
48743
50441
|
task_id: id,
|
|
48744
50442
|
progress_entries: progressEntries,
|
|
48745
50443
|
latest,
|
|
@@ -48756,27 +50454,27 @@ async function handleTaskProgress(id, req, method, _ctx, json2, url) {
|
|
|
48756
50454
|
try {
|
|
48757
50455
|
const body = await req.json();
|
|
48758
50456
|
if (!body.message)
|
|
48759
|
-
return
|
|
50457
|
+
return json3({ error: "message required" }, 400);
|
|
48760
50458
|
const comment = logProgress(id, body.message, body.pct_complete, body.agent_id);
|
|
48761
|
-
return
|
|
50459
|
+
return json3(comment, 201);
|
|
48762
50460
|
} catch (e) {
|
|
48763
|
-
return
|
|
50461
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to log progress" }, 500);
|
|
48764
50462
|
}
|
|
48765
50463
|
}
|
|
48766
50464
|
return null;
|
|
48767
50465
|
}
|
|
48768
|
-
function handleGetTask(id, _ctx,
|
|
50466
|
+
function handleGetTask(id, _ctx, json3, taskToSummary2, url) {
|
|
48769
50467
|
const task2 = getTask(id);
|
|
48770
50468
|
if (!task2)
|
|
48771
|
-
return
|
|
48772
|
-
return
|
|
50469
|
+
return json3({ error: "Task not found" }, 404);
|
|
50470
|
+
return json3(taskToSummary2(task2, url ? parseFieldsParam(url) : undefined));
|
|
48773
50471
|
}
|
|
48774
|
-
async function handlePatchTask(id, req, _ctx,
|
|
50472
|
+
async function handlePatchTask(id, req, _ctx, json3, taskToSummary2) {
|
|
48775
50473
|
try {
|
|
48776
50474
|
const body = await req.json();
|
|
48777
50475
|
const task2 = getTask(id);
|
|
48778
50476
|
if (!task2)
|
|
48779
|
-
return
|
|
50477
|
+
return json3({ error: "Task not found" }, 404);
|
|
48780
50478
|
const ALLOWED = new Set(["title", "description", "status", "priority", "assigned_to", "plan_id", "task_list_id", "tags", "metadata", "due_at", "estimated_minutes", "actual_minutes", "confidence", "retry_count", "max_retries", "retry_after", "task_type"]);
|
|
48781
50479
|
const safeBody = {};
|
|
48782
50480
|
for (const [key, value] of Object.entries(body)) {
|
|
@@ -48788,85 +50486,85 @@ async function handlePatchTask(id, req, _ctx, json2, taskToSummary2) {
|
|
|
48788
50486
|
...safeBody,
|
|
48789
50487
|
version: clientVersion
|
|
48790
50488
|
});
|
|
48791
|
-
return
|
|
50489
|
+
return json3(taskToSummary2(updated));
|
|
48792
50490
|
} catch (e) {
|
|
48793
|
-
const mapped = mapTaskError(e,
|
|
50491
|
+
const mapped = mapTaskError(e, json3);
|
|
48794
50492
|
if (mapped)
|
|
48795
50493
|
return mapped;
|
|
48796
|
-
return
|
|
50494
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to update task" }, 500);
|
|
48797
50495
|
}
|
|
48798
50496
|
}
|
|
48799
|
-
function handleDeleteTask(id, _ctx,
|
|
50497
|
+
function handleDeleteTask(id, _ctx, json3) {
|
|
48800
50498
|
const deleted = deleteTask(id);
|
|
48801
50499
|
if (!deleted)
|
|
48802
|
-
return
|
|
48803
|
-
return
|
|
50500
|
+
return json3({ error: "Task not found" }, 404);
|
|
50501
|
+
return json3({ success: true });
|
|
48804
50502
|
}
|
|
48805
|
-
function handleStartTask(id, ctx,
|
|
50503
|
+
function handleStartTask(id, ctx, json3, taskToSummary2) {
|
|
48806
50504
|
try {
|
|
48807
50505
|
const task2 = startTask(id, "dashboard");
|
|
48808
50506
|
ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "started", agent_id: "dashboard", project_id: task2.project_id });
|
|
48809
|
-
return
|
|
50507
|
+
return json3(taskToSummary2(task2));
|
|
48810
50508
|
} catch (e) {
|
|
48811
|
-
const mapped = mapTaskError(e,
|
|
50509
|
+
const mapped = mapTaskError(e, json3);
|
|
48812
50510
|
if (mapped)
|
|
48813
50511
|
return mapped;
|
|
48814
|
-
return
|
|
50512
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to start task" }, 500);
|
|
48815
50513
|
}
|
|
48816
50514
|
}
|
|
48817
|
-
async function handleFailTask(id, req, ctx,
|
|
50515
|
+
async function handleFailTask(id, req, ctx, json3, taskToSummary2) {
|
|
48818
50516
|
try {
|
|
48819
50517
|
const body = await req.json().catch(() => ({}));
|
|
48820
50518
|
const result = failTask(id, body.agent_id, body.reason, { retry: body.retry, error_code: body.error_code });
|
|
48821
50519
|
ctx.broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body.agent_id || null, project_id: result.task.project_id });
|
|
48822
|
-
return
|
|
50520
|
+
return json3({ task: taskToSummary2(result.task), retry_task: result.retryTask ? taskToSummary2(result.retryTask) : null });
|
|
48823
50521
|
} catch (e) {
|
|
48824
|
-
return
|
|
50522
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500);
|
|
48825
50523
|
}
|
|
48826
50524
|
}
|
|
48827
|
-
function handleCompleteTask(id, ctx,
|
|
50525
|
+
function handleCompleteTask(id, ctx, json3, taskToSummary2) {
|
|
48828
50526
|
try {
|
|
48829
50527
|
const task2 = completeTask(id, "dashboard");
|
|
48830
50528
|
ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "completed", agent_id: "dashboard", project_id: task2.project_id });
|
|
48831
|
-
return
|
|
50529
|
+
return json3(taskToSummary2(task2));
|
|
48832
50530
|
} catch (e) {
|
|
48833
|
-
const mapped = mapTaskError(e,
|
|
50531
|
+
const mapped = mapTaskError(e, json3);
|
|
48834
50532
|
if (mapped)
|
|
48835
50533
|
return mapped;
|
|
48836
|
-
return
|
|
50534
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500);
|
|
48837
50535
|
}
|
|
48838
50536
|
}
|
|
48839
|
-
function handleListProjects(url, _ctx,
|
|
50537
|
+
function handleListProjects(url, _ctx, json3) {
|
|
48840
50538
|
const pFieldsParam = url.searchParams.get("fields");
|
|
48841
50539
|
const pFields = pFieldsParam ? pFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
48842
50540
|
const projects = listProjects();
|
|
48843
|
-
return
|
|
50541
|
+
return json3(pFields ? projects.map((p) => Object.fromEntries(pFields.map((f) => [f, p[f] ?? null]))) : projects);
|
|
48844
50542
|
}
|
|
48845
|
-
async function handleCreateProject(req, _ctx,
|
|
50543
|
+
async function handleCreateProject(req, _ctx, json3) {
|
|
48846
50544
|
try {
|
|
48847
50545
|
const body = await req.json();
|
|
48848
50546
|
if (!body.name || !body.path)
|
|
48849
|
-
return
|
|
50547
|
+
return json3({ error: "Missing name or path" }, 400);
|
|
48850
50548
|
const project = createProject({ name: body.name, path: body.path, description: body.description });
|
|
48851
|
-
return
|
|
50549
|
+
return json3(project, 201);
|
|
48852
50550
|
} catch (e) {
|
|
48853
|
-
return
|
|
50551
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to create project" }, 500);
|
|
48854
50552
|
}
|
|
48855
50553
|
}
|
|
48856
|
-
function handleDeleteProject(id, _ctx,
|
|
50554
|
+
function handleDeleteProject(id, _ctx, json3) {
|
|
48857
50555
|
const deleted = deleteProject(id);
|
|
48858
50556
|
if (!deleted)
|
|
48859
|
-
return
|
|
48860
|
-
return
|
|
50557
|
+
return json3({ error: "Project not found" }, 404);
|
|
50558
|
+
return json3({ success: true });
|
|
48861
50559
|
}
|
|
48862
|
-
async function handleAgentMe(_req, url, _ctx,
|
|
50560
|
+
async function handleAgentMe(_req, url, _ctx, json3, taskToSummary2) {
|
|
48863
50561
|
try {
|
|
48864
50562
|
const name = url.searchParams.get("name");
|
|
48865
50563
|
if (!name)
|
|
48866
|
-
return
|
|
50564
|
+
return json3({ error: "Missing name param" }, 400);
|
|
48867
50565
|
const agentResult = registerAgent({ name });
|
|
48868
50566
|
if (isAgentConflict(agentResult))
|
|
48869
|
-
return
|
|
50567
|
+
return json3({ error: agentResult.message, conflict: true }, 409);
|
|
48870
50568
|
const agent = agentResult;
|
|
48871
50569
|
const tasks = listTasks({ assigned_to: agent.name });
|
|
48872
50570
|
const agentIdTasks = listTasks({ agent_id: agent.id });
|
|
@@ -48874,7 +50572,7 @@ async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
48874
50572
|
const pending = allTasks.filter((t) => t.status === "pending");
|
|
48875
50573
|
const inProgress = allTasks.filter((t) => t.status === "in_progress");
|
|
48876
50574
|
const completed = allTasks.filter((t) => t.status === "completed");
|
|
48877
|
-
return
|
|
50575
|
+
return json3({
|
|
48878
50576
|
agent,
|
|
48879
50577
|
pending_tasks: pending.map((t) => taskToSummary2(t)),
|
|
48880
50578
|
in_progress_tasks: inProgress.map((t) => taskToSummary2(t)),
|
|
@@ -48888,132 +50586,132 @@ async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
48888
50586
|
});
|
|
48889
50587
|
} catch (e) {
|
|
48890
50588
|
if (e instanceof InvalidAgentNameError)
|
|
48891
|
-
return
|
|
48892
|
-
return
|
|
50589
|
+
return json3({ error: e.message, suggestions: e.suggestions }, 400);
|
|
50590
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to get agent profile" }, 500);
|
|
48893
50591
|
}
|
|
48894
50592
|
}
|
|
48895
|
-
function handleAgentQueue(agentId, _ctx,
|
|
50593
|
+
function handleAgentQueue(agentId, _ctx, json3, taskToSummary2) {
|
|
48896
50594
|
const aliasSet = assignedToAliasSet(getDatabase(), agentId);
|
|
48897
50595
|
const pending = listTasks({ status: "pending" });
|
|
48898
50596
|
const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
|
|
48899
50597
|
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
48900
50598
|
queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
|
48901
|
-
return
|
|
50599
|
+
return json3(queue.map((t) => taskToSummary2(t)));
|
|
48902
50600
|
}
|
|
48903
|
-
async function handleClaimTask(req, _ctx,
|
|
50601
|
+
async function handleClaimTask(req, _ctx, json3, taskToSummary2) {
|
|
48904
50602
|
try {
|
|
48905
50603
|
const body = await req.json();
|
|
48906
50604
|
const agentId = body.agent_id || "anonymous";
|
|
48907
50605
|
const task2 = claimNextTask(agentId, body.project_id ? { project_id: body.project_id } : undefined);
|
|
48908
|
-
return
|
|
50606
|
+
return json3({ task: task2 ? taskToSummary2(task2) : null });
|
|
48909
50607
|
} catch (e) {
|
|
48910
|
-
return
|
|
50608
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to claim" }, 500);
|
|
48911
50609
|
}
|
|
48912
50610
|
}
|
|
48913
|
-
function handleListOrgs(_ctx,
|
|
48914
|
-
return
|
|
50611
|
+
function handleListOrgs(_ctx, json3) {
|
|
50612
|
+
return json3(listOrgs());
|
|
48915
50613
|
}
|
|
48916
|
-
async function handleCreateOrg(req, _ctx,
|
|
50614
|
+
async function handleCreateOrg(req, _ctx, json3) {
|
|
48917
50615
|
try {
|
|
48918
50616
|
const body = await req.json();
|
|
48919
50617
|
if (!body.name)
|
|
48920
|
-
return
|
|
48921
|
-
return
|
|
50618
|
+
return json3({ error: "Missing name" }, 400);
|
|
50619
|
+
return json3(createOrg(body), 201);
|
|
48922
50620
|
} catch (e) {
|
|
48923
|
-
return
|
|
50621
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48924
50622
|
}
|
|
48925
50623
|
}
|
|
48926
|
-
async function handleUpdateOrg(id, req, _ctx,
|
|
50624
|
+
async function handleUpdateOrg(id, req, _ctx, json3) {
|
|
48927
50625
|
try {
|
|
48928
50626
|
const body = await req.json();
|
|
48929
|
-
return
|
|
50627
|
+
return json3(updateOrg(id, body));
|
|
48930
50628
|
} catch (e) {
|
|
48931
|
-
return
|
|
50629
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48932
50630
|
}
|
|
48933
50631
|
}
|
|
48934
|
-
function handleDeleteOrg(id, _ctx,
|
|
50632
|
+
function handleDeleteOrg(id, _ctx, json3) {
|
|
48935
50633
|
const deleted = deleteOrg(id);
|
|
48936
|
-
return
|
|
50634
|
+
return json3(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
48937
50635
|
}
|
|
48938
|
-
function handleOrgChart(_ctx,
|
|
48939
|
-
return
|
|
50636
|
+
function handleOrgChart(_ctx, json3) {
|
|
50637
|
+
return json3(getOrgChart());
|
|
48940
50638
|
}
|
|
48941
|
-
function handleAgentTeam(agentId, _ctx,
|
|
48942
|
-
return
|
|
50639
|
+
function handleAgentTeam(agentId, _ctx, json3) {
|
|
50640
|
+
return json3(getDirectReports(decodeURIComponent(agentId)));
|
|
48943
50641
|
}
|
|
48944
|
-
function handleListAgents(url, _ctx,
|
|
50642
|
+
function handleListAgents(url, _ctx, json3) {
|
|
48945
50643
|
const aFieldsParam = url.searchParams.get("fields");
|
|
48946
50644
|
const aFields = aFieldsParam ? aFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
48947
50645
|
const agents = listAgents();
|
|
48948
|
-
return
|
|
50646
|
+
return json3(aFields ? agents.map((a) => Object.fromEntries(aFields.map((f) => [f, a[f] ?? null]))) : agents);
|
|
48949
50647
|
}
|
|
48950
|
-
async function handleRegisterAgent(req, _ctx,
|
|
50648
|
+
async function handleRegisterAgent(req, _ctx, json3) {
|
|
48951
50649
|
try {
|
|
48952
50650
|
const body = await req.json();
|
|
48953
50651
|
if (!body.name)
|
|
48954
|
-
return
|
|
50652
|
+
return json3({ error: "Missing name" }, 400);
|
|
48955
50653
|
const result = registerAgent({ name: body.name, description: body.description, session_id: body.session_id, working_dir: body.working_dir });
|
|
48956
50654
|
if (isAgentConflict(result))
|
|
48957
|
-
return
|
|
48958
|
-
return
|
|
50655
|
+
return json3({ error: result.message, conflict: true }, 409);
|
|
50656
|
+
return json3(result, 201);
|
|
48959
50657
|
} catch (e) {
|
|
48960
50658
|
if (e instanceof InvalidAgentNameError)
|
|
48961
|
-
return
|
|
48962
|
-
return
|
|
50659
|
+
return json3({ error: e.message, suggestions: e.suggestions }, 400);
|
|
50660
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500);
|
|
48963
50661
|
}
|
|
48964
50662
|
}
|
|
48965
|
-
async function handleUpdateAgent(id, req, _ctx,
|
|
50663
|
+
async function handleUpdateAgent(id, req, _ctx, json3) {
|
|
48966
50664
|
try {
|
|
48967
50665
|
const body = await req.json();
|
|
48968
50666
|
const agent = updateAgent(id, body);
|
|
48969
|
-
return
|
|
50667
|
+
return json3(agent);
|
|
48970
50668
|
} catch (e) {
|
|
48971
50669
|
if (e instanceof InvalidAgentNameError)
|
|
48972
|
-
return
|
|
48973
|
-
return
|
|
50670
|
+
return json3({ error: e.message, suggestions: e.suggestions }, 400);
|
|
50671
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to update agent" }, 500);
|
|
48974
50672
|
}
|
|
48975
50673
|
}
|
|
48976
|
-
function handleDeleteAgent(id, _ctx,
|
|
50674
|
+
function handleDeleteAgent(id, _ctx, json3) {
|
|
48977
50675
|
const deleted = deleteAgent(id);
|
|
48978
50676
|
if (!deleted)
|
|
48979
|
-
return
|
|
48980
|
-
return
|
|
50677
|
+
return json3({ error: "Agent not found" }, 404);
|
|
50678
|
+
return json3({ success: true });
|
|
48981
50679
|
}
|
|
48982
|
-
async function handleBulkDeleteAgents(req, _ctx,
|
|
50680
|
+
async function handleBulkDeleteAgents(req, _ctx, json3) {
|
|
48983
50681
|
try {
|
|
48984
50682
|
const body = await req.json();
|
|
48985
50683
|
if (!body.ids?.length || body.action !== "delete")
|
|
48986
|
-
return
|
|
50684
|
+
return json3({ error: "Missing ids or invalid action" }, 400);
|
|
48987
50685
|
let succeeded = 0;
|
|
48988
50686
|
for (const id of body.ids) {
|
|
48989
50687
|
if (deleteAgent(id))
|
|
48990
50688
|
succeeded++;
|
|
48991
50689
|
}
|
|
48992
|
-
return
|
|
50690
|
+
return json3({ succeeded, failed: body.ids.length - succeeded });
|
|
48993
50691
|
} catch (e) {
|
|
48994
|
-
return
|
|
50692
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48995
50693
|
}
|
|
48996
50694
|
}
|
|
48997
|
-
async function handleBulkDeleteProjects(req, _ctx,
|
|
50695
|
+
async function handleBulkDeleteProjects(req, _ctx, json3) {
|
|
48998
50696
|
try {
|
|
48999
50697
|
const body = await req.json();
|
|
49000
50698
|
if (!body.ids?.length || body.action !== "delete")
|
|
49001
|
-
return
|
|
50699
|
+
return json3({ error: "Missing ids or invalid action" }, 400);
|
|
49002
50700
|
let succeeded = 0;
|
|
49003
50701
|
for (const id of body.ids) {
|
|
49004
50702
|
if (deleteProject(id))
|
|
49005
50703
|
succeeded++;
|
|
49006
50704
|
}
|
|
49007
|
-
return
|
|
50705
|
+
return json3({ succeeded, failed: body.ids.length - succeeded });
|
|
49008
50706
|
} catch (e) {
|
|
49009
|
-
return
|
|
50707
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
49010
50708
|
}
|
|
49011
50709
|
}
|
|
49012
|
-
function handleDoctor(_ctx,
|
|
50710
|
+
function handleDoctor(_ctx, json3) {
|
|
49013
50711
|
const { runTodosDoctor: runTodosDoctor2 } = (init_doctor(), __toCommonJS(exports_doctor));
|
|
49014
|
-
return
|
|
50712
|
+
return json3(runTodosDoctor2({ apply: false }));
|
|
49015
50713
|
}
|
|
49016
|
-
function handleReport(_req, url, _ctx,
|
|
50714
|
+
function handleReport(_req, url, _ctx, json3) {
|
|
49017
50715
|
const days = parseInt(url.searchParams.get("days") || "7", 10);
|
|
49018
50716
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
49019
50717
|
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
@@ -49029,62 +50727,62 @@ function handleReport(_req, url, _ctx, json2) {
|
|
|
49029
50727
|
byDay[day] = (byDay[day] || 0) + 1;
|
|
49030
50728
|
}
|
|
49031
50729
|
const completionRate = changed.length > 0 ? Math.round(completed.length / changed.length * 100) : 0;
|
|
49032
|
-
return
|
|
50730
|
+
return json3({ days, period_since: since, total: all.length, stats: stats2, changed: changed.length, completed: completed.length, failed: failed.length, completion_rate: completionRate, by_day: byDay });
|
|
49033
50731
|
}
|
|
49034
|
-
function handleActivity(_req, url, _ctx,
|
|
50732
|
+
function handleActivity(_req, url, _ctx, json3) {
|
|
49035
50733
|
const limit = parseInt(url.searchParams.get("limit") || "50", 10);
|
|
49036
|
-
return
|
|
50734
|
+
return json3(getRecentActivity(limit));
|
|
49037
50735
|
}
|
|
49038
|
-
function handleTaskHistory(id, _ctx,
|
|
50736
|
+
function handleTaskHistory(id, _ctx, json3, url) {
|
|
49039
50737
|
const history = getTaskHistory(id);
|
|
49040
50738
|
const format = url?.searchParams.get("format") || "compact";
|
|
49041
50739
|
const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 500);
|
|
49042
|
-
return
|
|
50740
|
+
return json3(format === "full" ? history : history.slice(0, limit));
|
|
49043
50741
|
}
|
|
49044
|
-
function handleListWebhooks(_ctx,
|
|
49045
|
-
return
|
|
50742
|
+
function handleListWebhooks(_ctx, json3) {
|
|
50743
|
+
return json3(listWebhooks());
|
|
49046
50744
|
}
|
|
49047
|
-
async function handleCreateWebhook(req, _ctx,
|
|
50745
|
+
async function handleCreateWebhook(req, _ctx, json3) {
|
|
49048
50746
|
try {
|
|
49049
50747
|
const body = await req.json();
|
|
49050
50748
|
if (!body.url)
|
|
49051
|
-
return
|
|
49052
|
-
return
|
|
50749
|
+
return json3({ error: "Missing url" }, 400);
|
|
50750
|
+
return json3(createWebhook(body), 201);
|
|
49053
50751
|
} catch (e) {
|
|
49054
|
-
return
|
|
50752
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
49055
50753
|
}
|
|
49056
50754
|
}
|
|
49057
|
-
function handleDeleteWebhook(id, _ctx,
|
|
50755
|
+
function handleDeleteWebhook(id, _ctx, json3) {
|
|
49058
50756
|
const deleted = deleteWebhook(id);
|
|
49059
|
-
return
|
|
50757
|
+
return json3(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
49060
50758
|
}
|
|
49061
|
-
function handleListTemplates(_ctx,
|
|
49062
|
-
return
|
|
50759
|
+
function handleListTemplates(_ctx, json3) {
|
|
50760
|
+
return json3(listTemplates());
|
|
49063
50761
|
}
|
|
49064
|
-
async function handleCreateTemplate(req, _ctx,
|
|
50762
|
+
async function handleCreateTemplate(req, _ctx, json3) {
|
|
49065
50763
|
try {
|
|
49066
50764
|
const body = await req.json();
|
|
49067
50765
|
if (!body.name || !body.title_pattern)
|
|
49068
|
-
return
|
|
49069
|
-
return
|
|
50766
|
+
return json3({ error: "Missing name or title_pattern" }, 400);
|
|
50767
|
+
return json3(createTemplate(body), 201);
|
|
49070
50768
|
} catch (e) {
|
|
49071
|
-
return
|
|
50769
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
49072
50770
|
}
|
|
49073
50771
|
}
|
|
49074
|
-
function handleDeleteTemplate(id, _ctx,
|
|
50772
|
+
function handleDeleteTemplate(id, _ctx, json3) {
|
|
49075
50773
|
const deleted = deleteTemplate(id);
|
|
49076
|
-
return
|
|
50774
|
+
return json3(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
49077
50775
|
}
|
|
49078
|
-
function handleListPlans(url, _ctx,
|
|
50776
|
+
function handleListPlans(url, _ctx, json3) {
|
|
49079
50777
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
49080
50778
|
const plans = listPlans(projectId);
|
|
49081
|
-
return
|
|
50779
|
+
return json3(plans);
|
|
49082
50780
|
}
|
|
49083
|
-
async function handleCreatePlan(req, _ctx,
|
|
50781
|
+
async function handleCreatePlan(req, _ctx, json3) {
|
|
49084
50782
|
try {
|
|
49085
50783
|
const body = await req.json();
|
|
49086
50784
|
if (!body.name)
|
|
49087
|
-
return
|
|
50785
|
+
return json3({ error: "Missing 'name'" }, 400);
|
|
49088
50786
|
const plan = createPlan({
|
|
49089
50787
|
name: body.name,
|
|
49090
50788
|
slug: body.slug,
|
|
@@ -49094,49 +50792,49 @@ async function handleCreatePlan(req, _ctx, json2) {
|
|
|
49094
50792
|
agent_id: body.agent_id,
|
|
49095
50793
|
status: body.status
|
|
49096
50794
|
});
|
|
49097
|
-
return
|
|
50795
|
+
return json3(plan, 201);
|
|
49098
50796
|
} catch (e) {
|
|
49099
|
-
return
|
|
50797
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to create plan" }, 500);
|
|
49100
50798
|
}
|
|
49101
50799
|
}
|
|
49102
|
-
async function handleBulkDeletePlans(req, _ctx,
|
|
50800
|
+
async function handleBulkDeletePlans(req, _ctx, json3) {
|
|
49103
50801
|
try {
|
|
49104
50802
|
const body = await req.json();
|
|
49105
50803
|
if (!body.ids?.length || body.action !== "delete")
|
|
49106
|
-
return
|
|
50804
|
+
return json3({ error: "Missing ids or invalid action" }, 400);
|
|
49107
50805
|
let succeeded = 0;
|
|
49108
50806
|
for (const id of body.ids) {
|
|
49109
50807
|
if (deletePlan(id))
|
|
49110
50808
|
succeeded++;
|
|
49111
50809
|
}
|
|
49112
|
-
return
|
|
50810
|
+
return json3({ succeeded, failed: body.ids.length - succeeded });
|
|
49113
50811
|
} catch (e) {
|
|
49114
|
-
return
|
|
50812
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
49115
50813
|
}
|
|
49116
50814
|
}
|
|
49117
|
-
function handleGetPlan(id, _ctx,
|
|
50815
|
+
function handleGetPlan(id, _ctx, json3, taskToSummary2) {
|
|
49118
50816
|
const plan = getPlan(id);
|
|
49119
50817
|
if (!plan)
|
|
49120
|
-
return
|
|
50818
|
+
return json3({ error: "Plan not found" }, 404);
|
|
49121
50819
|
const tasks = listTasks({ plan_id: id });
|
|
49122
|
-
return
|
|
50820
|
+
return json3({ ...plan, tasks: tasks.map((t) => taskToSummary2(t)) });
|
|
49123
50821
|
}
|
|
49124
|
-
async function handleUpdatePlan(id, req, _ctx,
|
|
50822
|
+
async function handleUpdatePlan(id, req, _ctx, json3) {
|
|
49125
50823
|
try {
|
|
49126
50824
|
const body = await req.json();
|
|
49127
50825
|
const plan = updatePlan(id, body);
|
|
49128
|
-
return
|
|
50826
|
+
return json3(plan);
|
|
49129
50827
|
} catch (e) {
|
|
49130
|
-
return
|
|
50828
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to update plan" }, 500);
|
|
49131
50829
|
}
|
|
49132
50830
|
}
|
|
49133
|
-
function handleDeletePlan(id, _ctx,
|
|
50831
|
+
function handleDeletePlan(id, _ctx, json3) {
|
|
49134
50832
|
const deleted = deletePlan(id);
|
|
49135
50833
|
if (!deleted)
|
|
49136
|
-
return
|
|
49137
|
-
return
|
|
50834
|
+
return json3({ error: "Plan not found" }, 404);
|
|
50835
|
+
return json3({ success: true });
|
|
49138
50836
|
}
|
|
49139
|
-
function handleStaticFiles(path, method, ctx,
|
|
50837
|
+
function handleStaticFiles(path, method, ctx, json3, serveStaticFile2) {
|
|
49140
50838
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
49141
50839
|
return null;
|
|
49142
50840
|
if (path !== "/") {
|
|
@@ -49144,7 +50842,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
49144
50842
|
const resolvedFile = resolve16(filePath);
|
|
49145
50843
|
const resolvedBase = resolve16(ctx.dashboardDir);
|
|
49146
50844
|
if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
|
|
49147
|
-
return
|
|
50845
|
+
return json3({ error: "Forbidden" }, 403);
|
|
49148
50846
|
}
|
|
49149
50847
|
const res2 = serveStaticFile2(filePath);
|
|
49150
50848
|
if (res2)
|
|
@@ -50805,10 +52503,10 @@ var exports_pr_groups = {};
|
|
|
50805
52503
|
__export(exports_pr_groups, {
|
|
50806
52504
|
handlePrGroupHttpRequest: () => handlePrGroupHttpRequest
|
|
50807
52505
|
});
|
|
50808
|
-
function
|
|
50809
|
-
return new Response(JSON.stringify(body), { status, headers:
|
|
52506
|
+
function json3(body, status = 200) {
|
|
52507
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS2 });
|
|
50810
52508
|
}
|
|
50811
|
-
function
|
|
52509
|
+
function errorStatus2(error) {
|
|
50812
52510
|
switch (error.code) {
|
|
50813
52511
|
case "PR_GROUP_INVALID_INPUT":
|
|
50814
52512
|
case "PR_GROUP_EXACT_HEAD_REQUIRED":
|
|
@@ -50822,7 +52520,7 @@ function errorStatus(error) {
|
|
|
50822
52520
|
return 409;
|
|
50823
52521
|
}
|
|
50824
52522
|
}
|
|
50825
|
-
async function
|
|
52523
|
+
async function readJson2(req) {
|
|
50826
52524
|
try {
|
|
50827
52525
|
const value = await req.json();
|
|
50828
52526
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -50840,26 +52538,26 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
|
|
|
50840
52538
|
const method = req.method.toUpperCase();
|
|
50841
52539
|
try {
|
|
50842
52540
|
if (!groupId && method === "POST" && action === undefined) {
|
|
50843
|
-
return
|
|
52541
|
+
return json3({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
|
|
50844
52542
|
}
|
|
50845
52543
|
if (groupId === "admit" && !action) {
|
|
50846
52544
|
if (method !== "POST")
|
|
50847
|
-
return
|
|
50848
|
-
const body = await
|
|
52545
|
+
return json3({ error: "method not allowed" }, 405);
|
|
52546
|
+
const body = await readJson2(req);
|
|
50849
52547
|
if (!body)
|
|
50850
|
-
return
|
|
50851
|
-
return
|
|
52548
|
+
return json3({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
52549
|
+
return json3(await ledger.admit(body), 201);
|
|
50852
52550
|
}
|
|
50853
52551
|
if (!groupId)
|
|
50854
|
-
return
|
|
52552
|
+
return json3({ error: "PR group id is required", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
50855
52553
|
if (!action && method === "GET") {
|
|
50856
|
-
return
|
|
52554
|
+
return json3({ view: await ledger.get(groupId) });
|
|
50857
52555
|
}
|
|
50858
52556
|
if (action === "events") {
|
|
50859
52557
|
if (method === "GET") {
|
|
50860
52558
|
const limit = url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined;
|
|
50861
52559
|
const afterSequence = url.searchParams.has("after_sequence") ? Number(url.searchParams.get("after_sequence")) : undefined;
|
|
50862
|
-
return
|
|
52560
|
+
return json3({
|
|
50863
52561
|
history: await ledger.events(groupId, {
|
|
50864
52562
|
...limit !== undefined ? { limit } : {},
|
|
50865
52563
|
...afterSequence !== undefined ? { after_sequence: afterSequence } : {}
|
|
@@ -50867,51 +52565,74 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
|
|
|
50867
52565
|
});
|
|
50868
52566
|
}
|
|
50869
52567
|
if (method === "POST") {
|
|
50870
|
-
const body = await
|
|
52568
|
+
const body = await readJson2(req);
|
|
50871
52569
|
if (!body)
|
|
50872
|
-
return
|
|
50873
|
-
return
|
|
52570
|
+
return json3({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
52571
|
+
return json3(await ledger.append({
|
|
50874
52572
|
...body,
|
|
50875
52573
|
group_id: groupId,
|
|
50876
52574
|
authenticated_actor_id: principal?.actor_id ?? undefined,
|
|
50877
52575
|
authenticated_actor_run_id: principal?.actor_run_id ?? undefined
|
|
50878
52576
|
}), 201);
|
|
50879
52577
|
}
|
|
50880
|
-
return
|
|
52578
|
+
return json3({ error: "method not allowed" }, 405);
|
|
50881
52579
|
}
|
|
50882
52580
|
if (action === "recover") {
|
|
50883
52581
|
if (method !== "POST")
|
|
50884
|
-
return
|
|
50885
|
-
const body = await
|
|
52582
|
+
return json3({ error: "method not allowed" }, 405);
|
|
52583
|
+
const body = await readJson2(req);
|
|
50886
52584
|
if (!body)
|
|
50887
|
-
return
|
|
50888
|
-
return
|
|
52585
|
+
return json3({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
52586
|
+
return json3(await ledger.recover({
|
|
50889
52587
|
...body,
|
|
50890
52588
|
group_id: groupId
|
|
50891
52589
|
}), 201);
|
|
50892
52590
|
}
|
|
50893
|
-
return
|
|
52591
|
+
return json3({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
|
|
50894
52592
|
} catch (cause) {
|
|
50895
52593
|
if (cause instanceof PrGroupLedgerError) {
|
|
50896
|
-
return
|
|
52594
|
+
return json3({
|
|
50897
52595
|
error: cause.message,
|
|
50898
52596
|
code: cause.code,
|
|
50899
52597
|
details: cause.details,
|
|
50900
52598
|
authoritative: true
|
|
50901
|
-
},
|
|
52599
|
+
}, errorStatus2(cause));
|
|
50902
52600
|
}
|
|
50903
|
-
return
|
|
52601
|
+
return json3({
|
|
50904
52602
|
error: cause instanceof Error ? cause.message : "internal PR-group error",
|
|
50905
52603
|
code: "PR_GROUP_ATOMICITY_UNAVAILABLE"
|
|
50906
52604
|
}, 500);
|
|
50907
52605
|
}
|
|
50908
52606
|
}
|
|
50909
|
-
var
|
|
52607
|
+
var JSON_HEADERS2;
|
|
50910
52608
|
var init_pr_groups = __esm(() => {
|
|
50911
52609
|
init_types3();
|
|
50912
|
-
|
|
52610
|
+
JSON_HEADERS2 = { "Content-Type": "application/json" };
|
|
50913
52611
|
});
|
|
50914
52612
|
|
|
52613
|
+
// src/lib/comment-cursor.ts
|
|
52614
|
+
function encodeCommentCursor(comment) {
|
|
52615
|
+
return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
|
|
52616
|
+
}
|
|
52617
|
+
function decodeCommentCursor(value) {
|
|
52618
|
+
if (value.length > MAX_COMMENT_CURSOR_LENGTH)
|
|
52619
|
+
throw new Error("invalid comment cursor");
|
|
52620
|
+
let parsed;
|
|
52621
|
+
try {
|
|
52622
|
+
parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
52623
|
+
} catch {
|
|
52624
|
+
throw new Error("invalid comment cursor");
|
|
52625
|
+
}
|
|
52626
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
52627
|
+
throw new Error("invalid comment cursor");
|
|
52628
|
+
const cursor = parsed;
|
|
52629
|
+
if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
|
|
52630
|
+
throw new Error("invalid comment cursor");
|
|
52631
|
+
}
|
|
52632
|
+
return { created_at: cursor["created_at"], id: cursor["id"] };
|
|
52633
|
+
}
|
|
52634
|
+
var MAX_COMMENT_CURSOR_LENGTH = 1024;
|
|
52635
|
+
|
|
50915
52636
|
// src/server/v1.ts
|
|
50916
52637
|
var exports_v1 = {};
|
|
50917
52638
|
__export(exports_v1, {
|
|
@@ -50919,11 +52640,11 @@ __export(exports_v1, {
|
|
|
50919
52640
|
handleV1Request: () => handleV1Request,
|
|
50920
52641
|
countSnapshotRecords: () => countSnapshotRecords
|
|
50921
52642
|
});
|
|
50922
|
-
function
|
|
50923
|
-
return new Response(JSON.stringify(body), { status, headers:
|
|
52643
|
+
function json4(body, status = 200) {
|
|
52644
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS3 });
|
|
50924
52645
|
}
|
|
50925
52646
|
function error(status, message, extra) {
|
|
50926
|
-
return
|
|
52647
|
+
return json4({ error: message, ...extra ?? {} }, status);
|
|
50927
52648
|
}
|
|
50928
52649
|
function enumQueryParam(url, name, vocabulary) {
|
|
50929
52650
|
const raw = url.searchParams.get(name);
|
|
@@ -51174,7 +52895,7 @@ function validateTemplatePatch(value) {
|
|
|
51174
52895
|
...body.plan_id === null ? { plan_id: null } : {}
|
|
51175
52896
|
} };
|
|
51176
52897
|
}
|
|
51177
|
-
async function
|
|
52898
|
+
async function readJson3(req) {
|
|
51178
52899
|
try {
|
|
51179
52900
|
const text2 = await req.text();
|
|
51180
52901
|
if (!text2)
|
|
@@ -51201,26 +52922,6 @@ function contextFromPrincipal(principal, body) {
|
|
|
51201
52922
|
function redactComment3(comment) {
|
|
51202
52923
|
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
51203
52924
|
}
|
|
51204
|
-
function encodeCommentCursor(comment) {
|
|
51205
|
-
return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
|
|
51206
|
-
}
|
|
51207
|
-
function decodeCommentCursor(value) {
|
|
51208
|
-
if (value.length > 1024)
|
|
51209
|
-
throw new Error("invalid comment cursor");
|
|
51210
|
-
let parsed;
|
|
51211
|
-
try {
|
|
51212
|
-
parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
51213
|
-
} catch {
|
|
51214
|
-
throw new Error("invalid comment cursor");
|
|
51215
|
-
}
|
|
51216
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
51217
|
-
throw new Error("invalid comment cursor");
|
|
51218
|
-
const cursor = parsed;
|
|
51219
|
-
if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
|
|
51220
|
-
throw new Error("invalid comment cursor");
|
|
51221
|
-
}
|
|
51222
|
-
return { created_at: cursor["created_at"], id: cursor["id"] };
|
|
51223
|
-
}
|
|
51224
52925
|
function normalizeImportSnapshot(raw) {
|
|
51225
52926
|
const body = raw && typeof raw === "object" ? raw : {};
|
|
51226
52927
|
const arr = (v) => Array.isArray(v) ? v : [];
|
|
@@ -51264,6 +52965,9 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51264
52965
|
if (path === "/v1/pr-groups" || path.startsWith("/v1/pr-groups/")) {
|
|
51265
52966
|
return handlePrGroupHttpRequest(req, url, (dependencies.getPrGroupLedger ?? getCloudPrGroupLedger)(), "/v1/pr-groups", { actor_id: principal.agent, actor_run_id: principal.kid });
|
|
51266
52967
|
}
|
|
52968
|
+
if (path === "/v1/project-registration" || path.startsWith("/v1/project-registration/")) {
|
|
52969
|
+
return handleTodosProjectRegistrationHttpRequest(req, url, (dependencies.getProjectRegistrationAuthority ?? getCloudProjectRegistrationAuthority)());
|
|
52970
|
+
}
|
|
51267
52971
|
const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
|
|
51268
52972
|
const segments = path.split("/").filter(Boolean);
|
|
51269
52973
|
const resource = segments[1];
|
|
@@ -51275,7 +52979,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51275
52979
|
if (id === "exists" && !action) {
|
|
51276
52980
|
if (method !== "POST")
|
|
51277
52981
|
return error(405, `method ${method} not allowed on /v1/tasks/exists`);
|
|
51278
|
-
const body = await
|
|
52982
|
+
const body = await readJson3(req);
|
|
51279
52983
|
const ids2 = Array.isArray(body?.ids) ? Array.from(new Set(body.ids.filter((v) => typeof v === "string" && v.length > 0))) : [];
|
|
51280
52984
|
if (ids2.length === 0)
|
|
51281
52985
|
return error(400, "provide a non-empty string array `ids`");
|
|
@@ -51285,7 +52989,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51285
52989
|
const presentSet = new Set(found.map((t) => t.id));
|
|
51286
52990
|
const present = ids2.filter((i) => presentSet.has(i));
|
|
51287
52991
|
const missing = ids2.filter((i) => !presentSet.has(i));
|
|
51288
|
-
return
|
|
52992
|
+
return json4({
|
|
51289
52993
|
requested: ids2.length,
|
|
51290
52994
|
present_count: present.length,
|
|
51291
52995
|
missing_count: missing.length,
|
|
@@ -51298,7 +53002,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51298
53002
|
if (typeof store.tasks.getByFingerprint !== "function") {
|
|
51299
53003
|
return error(501, "fingerprint upsert is not supported by this storage backend");
|
|
51300
53004
|
}
|
|
51301
|
-
const body = await
|
|
53005
|
+
const body = await readJson3(req) ?? {};
|
|
51302
53006
|
const fingerprint3 = typeof body.fingerprint === "string" ? body.fingerprint.trim() : "";
|
|
51303
53007
|
if (!fingerprint3)
|
|
51304
53008
|
return error(400, "fingerprint is required");
|
|
@@ -51335,11 +53039,11 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51335
53039
|
}
|
|
51336
53040
|
if (!existing) {
|
|
51337
53041
|
const task2 = await store.tasks.create({ ...fields, title: body.title }, contextFromPrincipal(principal, body));
|
|
51338
|
-
return
|
|
53042
|
+
return json4({ task: task2, created: true }, 201);
|
|
51339
53043
|
}
|
|
51340
53044
|
try {
|
|
51341
53045
|
const task2 = await store.tasks.update(existing.id, { ...fields, version: existing.version }, contextFromPrincipal(principal, body));
|
|
51342
|
-
return
|
|
53046
|
+
return json4({ task: task2, created: false });
|
|
51343
53047
|
} catch (e) {
|
|
51344
53048
|
const msg = e.message || "";
|
|
51345
53049
|
if (msg.includes("version conflict"))
|
|
@@ -51381,15 +53085,15 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51381
53085
|
const tasks = await store.tasks.list(filter);
|
|
51382
53086
|
const { limit: _l, offset: _o, ...countFilter } = filter;
|
|
51383
53087
|
const total = await store.tasks.count(countFilter);
|
|
51384
|
-
return
|
|
53088
|
+
return json4({ tasks, count: tasks.length, total });
|
|
51385
53089
|
}
|
|
51386
53090
|
if (method === "POST") {
|
|
51387
|
-
const body = await
|
|
53091
|
+
const body = await readJson3(req);
|
|
51388
53092
|
if (!body || typeof body.title !== "string" || !body.title.trim()) {
|
|
51389
53093
|
return error(400, "title is required");
|
|
51390
53094
|
}
|
|
51391
53095
|
const task2 = await store.tasks.create(body, contextFromPrincipal(principal, body));
|
|
51392
|
-
return
|
|
53096
|
+
return json4({ task: task2 }, 201);
|
|
51393
53097
|
}
|
|
51394
53098
|
return error(405, `method ${method} not allowed on /v1/tasks`);
|
|
51395
53099
|
}
|
|
@@ -51406,7 +53110,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51406
53110
|
if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
|
|
51407
53111
|
return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
|
|
51408
53112
|
}
|
|
51409
|
-
return
|
|
53113
|
+
return json4({
|
|
51410
53114
|
comments: legacyPage,
|
|
51411
53115
|
count: legacyPage.length,
|
|
51412
53116
|
has_more: false,
|
|
@@ -51431,7 +53135,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51431
53135
|
const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
|
|
51432
53136
|
const hasMore = page.length > limit;
|
|
51433
53137
|
const comments = hasMore ? page.slice(1) : page;
|
|
51434
|
-
return
|
|
53138
|
+
return json4({
|
|
51435
53139
|
comments,
|
|
51436
53140
|
count: comments.length,
|
|
51437
53141
|
has_more: hasMore,
|
|
@@ -51439,7 +53143,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51439
53143
|
});
|
|
51440
53144
|
}
|
|
51441
53145
|
if (method === "POST") {
|
|
51442
|
-
const body2 = await
|
|
53146
|
+
const body2 = await readJson3(req) ?? {};
|
|
51443
53147
|
if (typeof body2.content !== "string" || !body2.content.trim()) {
|
|
51444
53148
|
return error(400, "content is required");
|
|
51445
53149
|
}
|
|
@@ -51454,7 +53158,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51454
53158
|
type: body2.type,
|
|
51455
53159
|
progress_pct: body2.progress_pct
|
|
51456
53160
|
}, contextFromPrincipal(principal, body2));
|
|
51457
|
-
return
|
|
53161
|
+
return json4({ comment: redactComment3(comment) }, 201);
|
|
51458
53162
|
}
|
|
51459
53163
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
|
|
51460
53164
|
}
|
|
@@ -51464,19 +53168,19 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51464
53168
|
if (!await store.tasks.get(id))
|
|
51465
53169
|
return error(404, "task not found");
|
|
51466
53170
|
const history = await store.audit.getTaskHistory(id);
|
|
51467
|
-
return
|
|
53171
|
+
return json4({ history, count: history.length });
|
|
51468
53172
|
}
|
|
51469
53173
|
if (action === "lock" || action === "unlock") {
|
|
51470
53174
|
if (method !== "POST")
|
|
51471
53175
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/${action}`);
|
|
51472
|
-
const body2 = await
|
|
53176
|
+
const body2 = await readJson3(req) ?? {};
|
|
51473
53177
|
if (!await store.tasks.get(id))
|
|
51474
53178
|
return error(404, "task not found");
|
|
51475
53179
|
if (action === "lock") {
|
|
51476
53180
|
if (typeof store.tasks.lock !== "function")
|
|
51477
53181
|
return error(501, "task locking is not supported by this storage backend");
|
|
51478
53182
|
const agentId3 = body2.agent_id || principal.agent || "todos-serve";
|
|
51479
|
-
return
|
|
53183
|
+
return json4({ result: await store.tasks.lock(id, agentId3) });
|
|
51480
53184
|
}
|
|
51481
53185
|
if (typeof store.tasks.unlock !== "function")
|
|
51482
53186
|
return error(501, "task unlocking is not supported by this storage backend");
|
|
@@ -51484,7 +53188,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51484
53188
|
if (!principal.scopes.includes("todos:*"))
|
|
51485
53189
|
return error(403, "force unlock requires todos:* scope");
|
|
51486
53190
|
const released2 = await store.tasks.unlock(id);
|
|
51487
|
-
return
|
|
53191
|
+
return json4({ success: released2 });
|
|
51488
53192
|
}
|
|
51489
53193
|
if (body2.agent_id && body2.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
|
|
51490
53194
|
return error(403, "unlock agent_id must match the authenticated agent");
|
|
@@ -51493,7 +53197,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51493
53197
|
if (!agentId2)
|
|
51494
53198
|
return error(403, "unlock requires an agent-bound key or force=true");
|
|
51495
53199
|
const released = await store.tasks.unlock(id, agentId2);
|
|
51496
|
-
return
|
|
53200
|
+
return json4({ success: released });
|
|
51497
53201
|
}
|
|
51498
53202
|
if (action === "dependencies") {
|
|
51499
53203
|
if (!store.dependencies)
|
|
@@ -51502,16 +53206,16 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51502
53206
|
if (!await store.tasks.get(id))
|
|
51503
53207
|
return error(404, "task not found");
|
|
51504
53208
|
const edges = await store.dependencies.list(id);
|
|
51505
|
-
return
|
|
53209
|
+
return json4(edges);
|
|
51506
53210
|
}
|
|
51507
53211
|
if (method === "POST") {
|
|
51508
|
-
const body2 = await
|
|
53212
|
+
const body2 = await readJson3(req) ?? {};
|
|
51509
53213
|
if (typeof body2.depends_on !== "string" || !body2.depends_on.trim()) {
|
|
51510
53214
|
return error(400, "depends_on is required");
|
|
51511
53215
|
}
|
|
51512
53216
|
try {
|
|
51513
53217
|
const dependency = await store.dependencies.add(id, body2.depends_on, contextFromPrincipal(principal));
|
|
51514
|
-
return
|
|
53218
|
+
return json4({ dependency }, 201);
|
|
51515
53219
|
} catch (e) {
|
|
51516
53220
|
const msg = e.message || "";
|
|
51517
53221
|
if (msg.includes("not found"))
|
|
@@ -51525,7 +53229,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51525
53229
|
if (!subId)
|
|
51526
53230
|
return error(400, "dependency target id is required (/v1/tasks/:id/dependencies/:dep)");
|
|
51527
53231
|
const removed = await store.dependencies.remove(id, subId);
|
|
51528
|
-
return
|
|
53232
|
+
return json4({ removed });
|
|
51529
53233
|
}
|
|
51530
53234
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/dependencies`);
|
|
51531
53235
|
}
|
|
@@ -51536,10 +53240,10 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51536
53240
|
if (!await store.tasks.get(id))
|
|
51537
53241
|
return error(404, "task not found");
|
|
51538
53242
|
const verifications = await store.verifications.list(id);
|
|
51539
|
-
return
|
|
53243
|
+
return json4({ verifications, count: verifications.length });
|
|
51540
53244
|
}
|
|
51541
53245
|
if (method === "POST") {
|
|
51542
|
-
const body2 = await
|
|
53246
|
+
const body2 = await readJson3(req) ?? {};
|
|
51543
53247
|
if (typeof body2.command !== "string" || !body2.command.trim()) {
|
|
51544
53248
|
return error(400, "command is required");
|
|
51545
53249
|
}
|
|
@@ -51552,7 +53256,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51552
53256
|
artifact_path: body2.artifact_path,
|
|
51553
53257
|
agent_id: body2.agent_id
|
|
51554
53258
|
}, contextFromPrincipal(principal, body2));
|
|
51555
|
-
return
|
|
53259
|
+
return json4({ verification }, 201);
|
|
51556
53260
|
} catch (e) {
|
|
51557
53261
|
const msg = e.message || "";
|
|
51558
53262
|
if (msg.includes("not found"))
|
|
@@ -51569,10 +53273,10 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51569
53273
|
if (!await store.tasks.get(id))
|
|
51570
53274
|
return error(404, "task not found");
|
|
51571
53275
|
const commits = await store.commits.list(id);
|
|
51572
|
-
return
|
|
53276
|
+
return json4({ commits, count: commits.length });
|
|
51573
53277
|
}
|
|
51574
53278
|
if (method === "POST") {
|
|
51575
|
-
const body2 = await
|
|
53279
|
+
const body2 = await readJson3(req) ?? {};
|
|
51576
53280
|
if (typeof body2.sha !== "string" || !body2.sha.trim())
|
|
51577
53281
|
return error(400, "sha is required");
|
|
51578
53282
|
try {
|
|
@@ -51583,7 +53287,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51583
53287
|
author: body2.author,
|
|
51584
53288
|
files_changed: Array.isArray(body2.files_changed) ? body2.files_changed : undefined
|
|
51585
53289
|
}, contextFromPrincipal(principal));
|
|
51586
|
-
return
|
|
53290
|
+
return json4({ commit }, 201);
|
|
51587
53291
|
} catch (e) {
|
|
51588
53292
|
const msg = e.message || "";
|
|
51589
53293
|
if (msg.includes("not found"))
|
|
@@ -51600,10 +53304,10 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51600
53304
|
if (!await store.tasks.get(id))
|
|
51601
53305
|
return error(404, "task not found");
|
|
51602
53306
|
const refs = await store.gitRefs.list(id);
|
|
51603
|
-
return
|
|
53307
|
+
return json4({ refs, count: refs.length });
|
|
51604
53308
|
}
|
|
51605
53309
|
if (method === "POST") {
|
|
51606
|
-
const body2 = await
|
|
53310
|
+
const body2 = await readJson3(req) ?? {};
|
|
51607
53311
|
const refType = body2.ref_type === "pull_request" || body2.ref_type === "branch" ? body2.ref_type : "branch";
|
|
51608
53312
|
if (typeof body2.name !== "string" || !body2.name.trim())
|
|
51609
53313
|
return error(400, "name is required");
|
|
@@ -51616,7 +53320,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51616
53320
|
provider: body2.provider,
|
|
51617
53321
|
metadata: body2.metadata
|
|
51618
53322
|
}, contextFromPrincipal(principal));
|
|
51619
|
-
return
|
|
53323
|
+
return json4({ ref }, 201);
|
|
51620
53324
|
} catch (e) {
|
|
51621
53325
|
const msg = e.message || "";
|
|
51622
53326
|
if (msg.includes("not found"))
|
|
@@ -51632,21 +53336,21 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51632
53336
|
const body = actionJson.value && typeof actionJson.value === "object" && !Array.isArray(actionJson.value) ? actionJson.value : {};
|
|
51633
53337
|
const agentId = typeof body.agent_id === "string" ? body.agent_id : principal.agent || "todos-serve";
|
|
51634
53338
|
if (action === "start" && method === "POST") {
|
|
51635
|
-
return
|
|
53339
|
+
return json4({ task: await store.tasks.start(id, agentId) });
|
|
51636
53340
|
}
|
|
51637
53341
|
if (action === "complete" && method === "POST") {
|
|
51638
53342
|
const parsed = validateTaskCompletion(actionJson.value);
|
|
51639
53343
|
if (!parsed.ok)
|
|
51640
53344
|
return error(400, parsed.message);
|
|
51641
|
-
return
|
|
53345
|
+
return json4({
|
|
51642
53346
|
task: await store.tasks.complete(id, parsed.agentId || principal.agent || "todos-serve", parsed.options, contextFromPrincipal(principal, body))
|
|
51643
53347
|
});
|
|
51644
53348
|
}
|
|
51645
53349
|
if (action === "fail" && method === "POST") {
|
|
51646
|
-
return
|
|
53350
|
+
return json4({ result: await store.tasks.fail(id, agentId, typeof body.reason === "string" ? body.reason : "failed", {}) });
|
|
51647
53351
|
}
|
|
51648
53352
|
if (action === "claim" && method === "POST") {
|
|
51649
|
-
return
|
|
53353
|
+
return json4({ task: await store.tasks.claimNext(agentId, {}) });
|
|
51650
53354
|
}
|
|
51651
53355
|
return error(404, `unknown task action: ${action}`);
|
|
51652
53356
|
}
|
|
@@ -51669,10 +53373,10 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51669
53373
|
throw e;
|
|
51670
53374
|
}
|
|
51671
53375
|
}
|
|
51672
|
-
return task2 ?
|
|
53376
|
+
return task2 ? json4({ task: task2 }) : error(404, "task not found");
|
|
51673
53377
|
}
|
|
51674
53378
|
if (method === "PATCH" || method === "PUT") {
|
|
51675
|
-
const body = await
|
|
53379
|
+
const body = await readJson3(req);
|
|
51676
53380
|
if (!body)
|
|
51677
53381
|
return error(400, "invalid JSON body");
|
|
51678
53382
|
const current = await store.tasks.get(id);
|
|
@@ -51684,7 +53388,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51684
53388
|
};
|
|
51685
53389
|
try {
|
|
51686
53390
|
const task2 = await store.tasks.update(id, patch);
|
|
51687
|
-
return task2 ?
|
|
53391
|
+
return task2 ? json4({ task: task2 }) : error(404, "task not found");
|
|
51688
53392
|
} catch (e) {
|
|
51689
53393
|
const msg = e.message || "";
|
|
51690
53394
|
if (msg.includes("version conflict"))
|
|
@@ -51694,7 +53398,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51694
53398
|
}
|
|
51695
53399
|
if (method === "DELETE") {
|
|
51696
53400
|
await store.tasks.delete(id, contextFromPrincipal(principal));
|
|
51697
|
-
return
|
|
53401
|
+
return json4({ deleted: true, id });
|
|
51698
53402
|
}
|
|
51699
53403
|
return error(405, `method ${method} not allowed on /v1/tasks/:id`);
|
|
51700
53404
|
}
|
|
@@ -51702,24 +53406,24 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51702
53406
|
if (!id) {
|
|
51703
53407
|
if (method === "GET") {
|
|
51704
53408
|
const projects = await store.projects.list();
|
|
51705
|
-
return
|
|
53409
|
+
return json4({ projects, count: projects.length });
|
|
51706
53410
|
}
|
|
51707
53411
|
if (method === "POST") {
|
|
51708
|
-
const body = await
|
|
53412
|
+
const body = await readJson3(req);
|
|
51709
53413
|
if (!body)
|
|
51710
53414
|
return error(400, "invalid JSON body");
|
|
51711
53415
|
const validated = validateProjectCreate(body);
|
|
51712
53416
|
if (!validated.ok)
|
|
51713
53417
|
return error(400, validated.message);
|
|
51714
53418
|
const project = await store.projects.create(validated.input, contextFromPrincipal(principal));
|
|
51715
|
-
return
|
|
53419
|
+
return json4({ project }, 201);
|
|
51716
53420
|
}
|
|
51717
53421
|
return error(405, `method ${method} not allowed on /v1/projects`);
|
|
51718
53422
|
}
|
|
51719
53423
|
if (action === "rename") {
|
|
51720
53424
|
if (method !== "POST")
|
|
51721
53425
|
return error(405, `method ${method} not allowed on /v1/projects/:id/rename`);
|
|
51722
|
-
const body = await
|
|
53426
|
+
const body = await readJson3(req);
|
|
51723
53427
|
if (!body || typeof body.new_slug !== "string" || !body.new_slug.trim() || !normalizeSlug(body.new_slug)) {
|
|
51724
53428
|
return error(400, "new_slug must be a non-empty string");
|
|
51725
53429
|
}
|
|
@@ -51729,14 +53433,14 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51729
53433
|
const unknownField = Object.keys(body).find((key) => !["new_slug", "name"].includes(key));
|
|
51730
53434
|
if (unknownField)
|
|
51731
53435
|
return error(400, `unknown project rename field: ${unknownField}`);
|
|
51732
|
-
return
|
|
53436
|
+
return json4(await store.projects.rename(id, body, contextFromPrincipal(principal)));
|
|
51733
53437
|
}
|
|
51734
53438
|
if (method === "GET") {
|
|
51735
53439
|
const project = await store.projects.get(id);
|
|
51736
|
-
return project ?
|
|
53440
|
+
return project ? json4({ project }) : error(404, "project not found");
|
|
51737
53441
|
}
|
|
51738
53442
|
if (method === "PATCH" || method === "PUT") {
|
|
51739
|
-
const body = await
|
|
53443
|
+
const body = await readJson3(req);
|
|
51740
53444
|
if (!body)
|
|
51741
53445
|
return error(400, "invalid JSON body");
|
|
51742
53446
|
const validated = validateProjectPatch(body);
|
|
@@ -51745,21 +53449,21 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51745
53449
|
if (!await store.projects.get(id))
|
|
51746
53450
|
return error(404, "project not found");
|
|
51747
53451
|
const project = await store.projects.update(id, validated.patch);
|
|
51748
|
-
return
|
|
53452
|
+
return json4({ project });
|
|
51749
53453
|
}
|
|
51750
53454
|
if (method === "DELETE") {
|
|
51751
53455
|
await store.projects.delete(id, contextFromPrincipal(principal));
|
|
51752
|
-
return
|
|
53456
|
+
return json4({ deleted: true, id });
|
|
51753
53457
|
}
|
|
51754
53458
|
return error(405, `method ${method} not allowed on /v1/projects/:id`);
|
|
51755
53459
|
}
|
|
51756
53460
|
if (resource === "plans") {
|
|
51757
53461
|
if (!id && method === "GET") {
|
|
51758
53462
|
const plans = await store.plans.list(url.searchParams.get("project_id") ?? undefined);
|
|
51759
|
-
return
|
|
53463
|
+
return json4({ plans, count: plans.length });
|
|
51760
53464
|
}
|
|
51761
53465
|
if (!id && method === "POST") {
|
|
51762
|
-
const body = await
|
|
53466
|
+
const body = await readJson3(req);
|
|
51763
53467
|
const validated = validatePlanCreate(body);
|
|
51764
53468
|
if (!validated.ok)
|
|
51765
53469
|
return error(400, validated.message);
|
|
@@ -51774,14 +53478,14 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51774
53478
|
}
|
|
51775
53479
|
}
|
|
51776
53480
|
const plan = await store.plans.create(validated.input, contextFromPrincipal(principal, validated.input));
|
|
51777
|
-
return
|
|
53481
|
+
return json4({ plan }, 201);
|
|
51778
53482
|
}
|
|
51779
53483
|
if (id && method === "GET") {
|
|
51780
53484
|
const plan = await store.plans.get(id);
|
|
51781
|
-
return plan ?
|
|
53485
|
+
return plan ? json4({ plan }) : error(404, "plan not found");
|
|
51782
53486
|
}
|
|
51783
53487
|
if (id && (method === "PATCH" || method === "PUT")) {
|
|
51784
|
-
const body = await
|
|
53488
|
+
const body = await readJson3(req);
|
|
51785
53489
|
if (!body || Object.keys(body).length === 0)
|
|
51786
53490
|
return error(400, "plan patch is required");
|
|
51787
53491
|
const allowed = new Set(["name", "slug", "description", "status", "task_list_id", "agent_id"]);
|
|
@@ -51818,12 +53522,12 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51818
53522
|
}
|
|
51819
53523
|
}
|
|
51820
53524
|
const plan = await store.plans.update(id, body);
|
|
51821
|
-
return
|
|
53525
|
+
return json4({ plan });
|
|
51822
53526
|
}
|
|
51823
53527
|
if (id && method === "DELETE") {
|
|
51824
53528
|
if (!await store.plans.delete(id, contextFromPrincipal(principal)))
|
|
51825
53529
|
return error(404, "plan not found");
|
|
51826
|
-
return
|
|
53530
|
+
return json4({ deleted: true, id });
|
|
51827
53531
|
}
|
|
51828
53532
|
if (id)
|
|
51829
53533
|
return error(405, `method ${method} not allowed on /v1/plans/:id`);
|
|
@@ -51832,50 +53536,50 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51832
53536
|
if (!id && method === "GET") {
|
|
51833
53537
|
const projectId = url.searchParams.get("project_id");
|
|
51834
53538
|
const templates = (await store.templates.list()).filter((template) => projectId === null || template.project_id === projectId);
|
|
51835
|
-
return
|
|
53539
|
+
return json4({ templates, count: templates.length });
|
|
51836
53540
|
}
|
|
51837
53541
|
if (!id && method === "POST") {
|
|
51838
|
-
const body = await
|
|
53542
|
+
const body = await readJson3(req);
|
|
51839
53543
|
const validated = validateTemplateCreate(body);
|
|
51840
53544
|
if (!validated.ok)
|
|
51841
53545
|
return error(400, validated.message);
|
|
51842
53546
|
const template = await store.templates.create(validated.input, contextFromPrincipal(principal));
|
|
51843
|
-
return
|
|
53547
|
+
return json4({ template: await store.templates.getWithTasks(template.id) }, 201);
|
|
51844
53548
|
}
|
|
51845
53549
|
if (!id)
|
|
51846
53550
|
return error(405, `method ${method} not allowed on /v1/templates`);
|
|
51847
53551
|
if (method === "GET") {
|
|
51848
53552
|
const template = await store.templates.getWithTasks(id);
|
|
51849
|
-
return template ?
|
|
53553
|
+
return template ? json4({ template }) : error(404, "template not found");
|
|
51850
53554
|
}
|
|
51851
53555
|
if (method === "PATCH" || method === "PUT") {
|
|
51852
|
-
const body = await
|
|
53556
|
+
const body = await readJson3(req);
|
|
51853
53557
|
const validated = validateTemplatePatch(body);
|
|
51854
53558
|
if (!validated.ok)
|
|
51855
53559
|
return error(400, validated.message);
|
|
51856
53560
|
const template = await store.templates.update(id, validated.patch, contextFromPrincipal(principal));
|
|
51857
|
-
return template ?
|
|
53561
|
+
return template ? json4({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
|
|
51858
53562
|
}
|
|
51859
53563
|
if (method === "DELETE") {
|
|
51860
53564
|
const deleted = await store.templates.delete(id, contextFromPrincipal(principal));
|
|
51861
|
-
return deleted ?
|
|
53565
|
+
return deleted ? json4({ deleted: true, id }) : error(404, "template not found");
|
|
51862
53566
|
}
|
|
51863
53567
|
return error(405, `method ${method} not allowed on /v1/templates/:id`);
|
|
51864
53568
|
}
|
|
51865
53569
|
if (resource === "agents") {
|
|
51866
53570
|
if (!id && method === "GET") {
|
|
51867
53571
|
const agents = await store.agents.list();
|
|
51868
|
-
return
|
|
53572
|
+
return json4({ agents, count: agents.length });
|
|
51869
53573
|
}
|
|
51870
53574
|
if (!id && method === "POST") {
|
|
51871
|
-
const body = await
|
|
53575
|
+
const body = await readJson3(req);
|
|
51872
53576
|
if (!body || typeof body.name !== "string" || !body.name.trim())
|
|
51873
53577
|
return error(400, "name is required");
|
|
51874
53578
|
const result = await store.agents.register(body, contextFromPrincipal(principal));
|
|
51875
53579
|
if (result && typeof result === "object" && "conflict" in result) {
|
|
51876
53580
|
return error(409, result.message ?? "agent name conflict", { conflict: true });
|
|
51877
53581
|
}
|
|
51878
|
-
return
|
|
53582
|
+
return json4({ agent: result }, 201);
|
|
51879
53583
|
}
|
|
51880
53584
|
if (id && action === "heartbeat") {
|
|
51881
53585
|
if (method !== "POST")
|
|
@@ -51884,7 +53588,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51884
53588
|
return error(501, "agent heartbeat is not supported by this storage backend");
|
|
51885
53589
|
}
|
|
51886
53590
|
const agent = await store.agents.heartbeat(id, contextFromPrincipal(principal));
|
|
51887
|
-
return agent ?
|
|
53591
|
+
return agent ? json4({ agent }) : error(404, "agent not found");
|
|
51888
53592
|
}
|
|
51889
53593
|
if (id && action === "release") {
|
|
51890
53594
|
if (method !== "POST")
|
|
@@ -51892,18 +53596,18 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51892
53596
|
if (typeof store.agents.release !== "function") {
|
|
51893
53597
|
return error(501, "agent release is not supported by this storage backend");
|
|
51894
53598
|
}
|
|
51895
|
-
const body = await
|
|
53599
|
+
const body = await readJson3(req) ?? {};
|
|
51896
53600
|
const result = await store.agents.release(id, body.session_id, contextFromPrincipal(principal));
|
|
51897
53601
|
if (!result)
|
|
51898
53602
|
return error(404, "agent not found");
|
|
51899
53603
|
if (!result.released) {
|
|
51900
53604
|
return error(409, "release denied: session_id does not match agent's current session", { released: false });
|
|
51901
53605
|
}
|
|
51902
|
-
return
|
|
53606
|
+
return json4({ agent: result.agent, released: true });
|
|
51903
53607
|
}
|
|
51904
53608
|
if (id && method === "GET") {
|
|
51905
53609
|
const agent = await store.agents.get(id);
|
|
51906
|
-
return agent ?
|
|
53610
|
+
return agent ? json4({ agent }) : error(404, "agent not found");
|
|
51907
53611
|
}
|
|
51908
53612
|
}
|
|
51909
53613
|
if (resource === "activity" && !id) {
|
|
@@ -51912,16 +53616,16 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51912
53616
|
const limitParam = url.searchParams.get("limit");
|
|
51913
53617
|
const limit = limitParam ? Math.max(1, Math.min(1e4, Number(limitParam) || 50)) : 50;
|
|
51914
53618
|
const activity = await store.audit.getRecentActivity(limit);
|
|
51915
|
-
return
|
|
53619
|
+
return json4({ activity, count: activity.length });
|
|
51916
53620
|
}
|
|
51917
53621
|
if (resource === "task-lists") {
|
|
51918
53622
|
if (!id && method === "GET") {
|
|
51919
53623
|
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
51920
53624
|
const taskLists = await store.taskLists.list(projectId);
|
|
51921
|
-
return
|
|
53625
|
+
return json4({ task_lists: taskLists, count: taskLists.length });
|
|
51922
53626
|
}
|
|
51923
53627
|
if (!id && method === "POST") {
|
|
51924
|
-
const body = await
|
|
53628
|
+
const body = await readJson3(req);
|
|
51925
53629
|
if (!body || typeof body.name !== "string" || !body.name.trim())
|
|
51926
53630
|
return error(400, "name is required");
|
|
51927
53631
|
const unknownField = Object.keys(body).find((key) => !["name", "slug", "project_id", "description", "metadata"].includes(key));
|
|
@@ -51940,14 +53644,14 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51940
53644
|
return error(400, "task-list slug must be non-empty kebab-case");
|
|
51941
53645
|
}
|
|
51942
53646
|
const taskList = await store.taskLists.create(body, contextFromPrincipal(principal));
|
|
51943
|
-
return
|
|
53647
|
+
return json4({ task_list: taskList }, 201);
|
|
51944
53648
|
}
|
|
51945
53649
|
if (id && method === "GET") {
|
|
51946
53650
|
const taskList = await store.taskLists.get(id);
|
|
51947
|
-
return taskList ?
|
|
53651
|
+
return taskList ? json4({ task_list: taskList }) : error(404, "task list not found");
|
|
51948
53652
|
}
|
|
51949
53653
|
if (id && (method === "PATCH" || method === "PUT")) {
|
|
51950
|
-
const body = await
|
|
53654
|
+
const body = await readJson3(req);
|
|
51951
53655
|
if (!body)
|
|
51952
53656
|
return error(400, "invalid JSON body");
|
|
51953
53657
|
const unknownField = Object.keys(body).find((key) => !["slug", "name", "description", "metadata"].includes(key));
|
|
@@ -51967,11 +53671,11 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51967
53671
|
if (!await store.taskLists.get(id))
|
|
51968
53672
|
return error(404, "task list not found");
|
|
51969
53673
|
const taskList = await store.taskLists.update(id, body);
|
|
51970
|
-
return
|
|
53674
|
+
return json4({ task_list: taskList });
|
|
51971
53675
|
}
|
|
51972
53676
|
if (id && method === "DELETE") {
|
|
51973
53677
|
const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
|
|
51974
|
-
return deleted ?
|
|
53678
|
+
return deleted ? json4({ deleted: true, id }) : error(404, "task list not found");
|
|
51975
53679
|
}
|
|
51976
53680
|
return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
|
|
51977
53681
|
}
|
|
@@ -51982,7 +53686,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51982
53686
|
return error(501, "dependency edge listing is not supported by this storage backend");
|
|
51983
53687
|
}
|
|
51984
53688
|
const dependencies2 = await store.dependencies.listAll();
|
|
51985
|
-
return
|
|
53689
|
+
return json4({ dependencies: dependencies2, count: dependencies2.length });
|
|
51986
53690
|
}
|
|
51987
53691
|
if (resource === "commits" && id) {
|
|
51988
53692
|
if (method !== "GET")
|
|
@@ -51990,7 +53694,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51990
53694
|
if (!store.commits)
|
|
51991
53695
|
return error(501, "commit links are not supported by this storage backend");
|
|
51992
53696
|
const commit = await store.commits.find(id);
|
|
51993
|
-
return
|
|
53697
|
+
return json4({ commit: commit ?? null });
|
|
51994
53698
|
}
|
|
51995
53699
|
if (resource === "refs" && id) {
|
|
51996
53700
|
if (method !== "GET")
|
|
@@ -52004,7 +53708,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52004
53708
|
return error(400, "ref path segment has invalid percent encoding");
|
|
52005
53709
|
}
|
|
52006
53710
|
const refs = await store.gitRefs.find(decodedRef);
|
|
52007
|
-
return
|
|
53711
|
+
return json4({ refs, count: refs.length });
|
|
52008
53712
|
}
|
|
52009
53713
|
if (resource === "next" && !id) {
|
|
52010
53714
|
if (method !== "GET")
|
|
@@ -52016,7 +53720,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52016
53720
|
...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {}
|
|
52017
53721
|
};
|
|
52018
53722
|
const task2 = await store.tasks.getNext(agent, filters);
|
|
52019
|
-
return
|
|
53723
|
+
return json4({ task: task2 ?? null });
|
|
52020
53724
|
}
|
|
52021
53725
|
if (resource === "stats" && method === "GET") {
|
|
52022
53726
|
const [tasks, tasksAll, projects] = await Promise.all([
|
|
@@ -52024,7 +53728,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52024
53728
|
store.tasks.count({ include_subtasks: true }),
|
|
52025
53729
|
store.projects.list()
|
|
52026
53730
|
]);
|
|
52027
|
-
return
|
|
53731
|
+
return json4({ tasks, tasks_all: tasksAll, subtasks: tasksAll - tasks, projects: projects.length });
|
|
52028
53732
|
}
|
|
52029
53733
|
if (resource === "integrity" && !id) {
|
|
52030
53734
|
if (method !== "GET")
|
|
@@ -52033,7 +53737,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52033
53737
|
return error(501, "referential-integrity reporting is not supported by this storage backend");
|
|
52034
53738
|
}
|
|
52035
53739
|
const integrity = await store.integrity.report();
|
|
52036
|
-
return
|
|
53740
|
+
return json4({ integrity });
|
|
52037
53741
|
}
|
|
52038
53742
|
if (resource === "import") {
|
|
52039
53743
|
if (method !== "POST")
|
|
@@ -52041,7 +53745,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52041
53745
|
if (typeof store.sync.importSnapshot !== "function") {
|
|
52042
53746
|
return error(501, "snapshot import is not supported by this storage backend");
|
|
52043
53747
|
}
|
|
52044
|
-
const raw = await
|
|
53748
|
+
const raw = await readJson3(req);
|
|
52045
53749
|
if (raw === null)
|
|
52046
53750
|
return error(400, "invalid JSON body");
|
|
52047
53751
|
const snapshot = normalizeImportSnapshot(raw);
|
|
@@ -52050,7 +53754,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52050
53754
|
return error(400, "empty snapshot: provide at least one record array (tasks/projects/plans/...)");
|
|
52051
53755
|
}
|
|
52052
53756
|
const result = await store.sync.importSnapshot(snapshot, contextFromPrincipal(principal));
|
|
52053
|
-
return
|
|
53757
|
+
return json4({ result, received });
|
|
52054
53758
|
}
|
|
52055
53759
|
return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
52056
53760
|
} catch (e) {
|
|
@@ -52073,13 +53777,14 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52073
53777
|
return error(500, e.message || "internal error");
|
|
52074
53778
|
}
|
|
52075
53779
|
}
|
|
52076
|
-
var
|
|
53780
|
+
var JSON_HEADERS3, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500;
|
|
52077
53781
|
var init_v1 = __esm(() => {
|
|
52078
53782
|
init_types();
|
|
52079
53783
|
init_cloud();
|
|
52080
53784
|
init_pr_groups();
|
|
53785
|
+
init_project_registration();
|
|
52081
53786
|
init_redaction();
|
|
52082
|
-
|
|
53787
|
+
JSON_HEADERS3 = { "Content-Type": "application/json" };
|
|
52083
53788
|
});
|
|
52084
53789
|
|
|
52085
53790
|
// src/pr-groups/sqlite.ts
|
|
@@ -52238,12 +53943,12 @@ class SqlitePrGroupLedgerPersistence {
|
|
|
52238
53943
|
this.tx = new SqlitePrGroupTransaction(db);
|
|
52239
53944
|
}
|
|
52240
53945
|
async transaction(fn) {
|
|
52241
|
-
const previous =
|
|
53946
|
+
const previous = sqliteTransactionTails2.get(this.db) ?? Promise.resolve();
|
|
52242
53947
|
let release;
|
|
52243
53948
|
const current = new Promise((resolve17) => {
|
|
52244
53949
|
release = resolve17;
|
|
52245
53950
|
});
|
|
52246
|
-
|
|
53951
|
+
sqliteTransactionTails2.set(this.db, current);
|
|
52247
53952
|
await previous;
|
|
52248
53953
|
try {
|
|
52249
53954
|
this.db.exec("BEGIN IMMEDIATE");
|
|
@@ -52257,8 +53962,8 @@ class SqlitePrGroupLedgerPersistence {
|
|
|
52257
53962
|
throw error2;
|
|
52258
53963
|
} finally {
|
|
52259
53964
|
release();
|
|
52260
|
-
if (
|
|
52261
|
-
|
|
53965
|
+
if (sqliteTransactionTails2.get(this.db) === current)
|
|
53966
|
+
sqliteTransactionTails2.delete(this.db);
|
|
52262
53967
|
}
|
|
52263
53968
|
}
|
|
52264
53969
|
getGroup(id) {
|
|
@@ -52288,9 +53993,9 @@ class SqlitePrGroupLedgerPersistence {
|
|
|
52288
53993
|
return row ? eventFromRow2(row) : null;
|
|
52289
53994
|
}
|
|
52290
53995
|
}
|
|
52291
|
-
var
|
|
52292
|
-
var
|
|
52293
|
-
|
|
53996
|
+
var sqliteTransactionTails2;
|
|
53997
|
+
var init_sqlite2 = __esm(() => {
|
|
53998
|
+
sqliteTransactionTails2 = new WeakMap;
|
|
52294
53999
|
});
|
|
52295
54000
|
|
|
52296
54001
|
// src/pr-groups/index.ts
|
|
@@ -52320,10 +54025,10 @@ function createLocalPrGroupLedger(db = getDatabase()) {
|
|
|
52320
54025
|
var init_pr_groups2 = __esm(() => {
|
|
52321
54026
|
init_database();
|
|
52322
54027
|
init_ledger();
|
|
52323
|
-
|
|
54028
|
+
init_sqlite2();
|
|
52324
54029
|
init_types3();
|
|
52325
54030
|
init_ledger();
|
|
52326
|
-
|
|
54031
|
+
init_sqlite2();
|
|
52327
54032
|
init_http_client();
|
|
52328
54033
|
init_postgres();
|
|
52329
54034
|
});
|
|
@@ -52334,7 +54039,7 @@ __export(exports_serve, {
|
|
|
52334
54039
|
taskToSummary: () => taskToSummary,
|
|
52335
54040
|
startServer: () => startServer,
|
|
52336
54041
|
serveStaticFile: () => serveStaticFile,
|
|
52337
|
-
json: () =>
|
|
54042
|
+
json: () => json2,
|
|
52338
54043
|
checkAuth: () => checkAuth,
|
|
52339
54044
|
SECURITY_HEADERS: () => SECURITY_HEADERS,
|
|
52340
54045
|
MIME_TYPES: () => MIME_TYPES
|
|
@@ -52420,7 +54125,7 @@ function checkRateLimit(ip) {
|
|
|
52420
54125
|
}
|
|
52421
54126
|
return { allowed: true };
|
|
52422
54127
|
}
|
|
52423
|
-
function
|
|
54128
|
+
function json2(data, status = 200, headers) {
|
|
52424
54129
|
return new Response(JSON.stringify(data), {
|
|
52425
54130
|
status,
|
|
52426
54131
|
headers: {
|
|
@@ -52565,7 +54270,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52565
54270
|
"Access-Control-Allow-Headers": "Content-Type, X-API-Key, Authorization",
|
|
52566
54271
|
Vary: "Origin"
|
|
52567
54272
|
} : undefined;
|
|
52568
|
-
const jsonWithCors = (data, status = 200) =>
|
|
54273
|
+
const jsonWithCors = (data, status = 200) => json2(data, status, corsHeaders);
|
|
52569
54274
|
if (method === "OPTIONS") {
|
|
52570
54275
|
return new Response(null, {
|
|
52571
54276
|
headers: corsHeaders || {
|
|
@@ -52646,13 +54351,13 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52646
54351
|
return res;
|
|
52647
54352
|
}
|
|
52648
54353
|
if (path === "/api/health" && method === "GET") {
|
|
52649
|
-
return handleHealth(ctx,
|
|
54354
|
+
return handleHealth(ctx, json2);
|
|
52650
54355
|
}
|
|
52651
54356
|
if (path === "/api/headless" && method === "GET") {
|
|
52652
|
-
return handleHeadlessBoundary(ctx,
|
|
54357
|
+
return handleHeadlessBoundary(ctx, json2);
|
|
52653
54358
|
}
|
|
52654
54359
|
if (path === "/api/stats" && method === "GET") {
|
|
52655
|
-
return handleStats(ctx,
|
|
54360
|
+
return handleStats(ctx, json2);
|
|
52656
54361
|
}
|
|
52657
54362
|
if (path === "/api/tasks" && method === "GET") {
|
|
52658
54363
|
return handleListTasks(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -52667,16 +54372,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52667
54372
|
return handleTasksExport(req, url, ctx, jsonWithCors, taskToSummary);
|
|
52668
54373
|
}
|
|
52669
54374
|
if (path === "/api/tasks/bulk" && method === "POST") {
|
|
52670
|
-
return handleTasksBulk(req, ctx,
|
|
54375
|
+
return handleTasksBulk(req, ctx, json2);
|
|
52671
54376
|
}
|
|
52672
54377
|
if (path === "/api/tasks/status" && method === "GET") {
|
|
52673
|
-
return handleTasksStatus(req, url, ctx,
|
|
54378
|
+
return handleTasksStatus(req, url, ctx, json2);
|
|
52674
54379
|
}
|
|
52675
54380
|
if (path === "/api/tasks/next" && method === "GET") {
|
|
52676
54381
|
return handleTasksNext(req, url, ctx, jsonWithCors, taskToSummary);
|
|
52677
54382
|
}
|
|
52678
54383
|
if (path === "/api/tasks/active" && method === "GET") {
|
|
52679
|
-
return handleTasksActive(req, url, ctx,
|
|
54384
|
+
return handleTasksActive(req, url, ctx, json2);
|
|
52680
54385
|
}
|
|
52681
54386
|
if (path === "/api/tasks/stale" && method === "GET") {
|
|
52682
54387
|
return handleTasksStale(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -52689,11 +54394,11 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52689
54394
|
}
|
|
52690
54395
|
const attachmentsMatch = path.match(/^\/api\/tasks\/([^/]+)\/attachments$/);
|
|
52691
54396
|
if (attachmentsMatch && method === "GET") {
|
|
52692
|
-
return handleTaskAttachments(attachmentsMatch[1], ctx,
|
|
54397
|
+
return handleTaskAttachments(attachmentsMatch[1], ctx, json2);
|
|
52693
54398
|
}
|
|
52694
54399
|
const progressMatch = path.match(/^\/api\/tasks\/([^/]+)\/progress$/);
|
|
52695
54400
|
if (progressMatch) {
|
|
52696
|
-
const res = await handleTaskProgress(progressMatch[1], req, method, ctx,
|
|
54401
|
+
const res = await handleTaskProgress(progressMatch[1], req, method, ctx, json2, url);
|
|
52697
54402
|
if (res !== null)
|
|
52698
54403
|
return res;
|
|
52699
54404
|
}
|
|
@@ -52707,7 +54412,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52707
54412
|
return handlePatchTask(id, req, ctx, jsonWithCors, taskToSummary);
|
|
52708
54413
|
}
|
|
52709
54414
|
if (method === "DELETE") {
|
|
52710
|
-
return handleDeleteTask(id, ctx,
|
|
54415
|
+
return handleDeleteTask(id, ctx, json2);
|
|
52711
54416
|
}
|
|
52712
54417
|
}
|
|
52713
54418
|
const startMatch = path.match(/^\/api\/tasks\/([^/]+)\/start$/);
|
|
@@ -52723,7 +54428,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52723
54428
|
return handleCompleteTask(completeMatch[1], ctx, jsonWithCors, taskToSummary);
|
|
52724
54429
|
}
|
|
52725
54430
|
if (path === "/api/projects" && method === "GET") {
|
|
52726
|
-
return handleListProjects(url, ctx,
|
|
54431
|
+
return handleListProjects(url, ctx, json2);
|
|
52727
54432
|
}
|
|
52728
54433
|
if (path === "/api/agents/me" && method === "GET") {
|
|
52729
54434
|
return handleAgentMe(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -52736,92 +54441,92 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52736
54441
|
return handleClaimTask(req, ctx, jsonWithCors, taskToSummary);
|
|
52737
54442
|
}
|
|
52738
54443
|
if (path === "/api/orgs" && method === "GET") {
|
|
52739
|
-
return handleListOrgs(ctx,
|
|
54444
|
+
return handleListOrgs(ctx, json2);
|
|
52740
54445
|
}
|
|
52741
54446
|
if (path === "/api/orgs" && method === "POST") {
|
|
52742
|
-
return handleCreateOrg(req, ctx,
|
|
54447
|
+
return handleCreateOrg(req, ctx, json2);
|
|
52743
54448
|
}
|
|
52744
54449
|
const orgMatch = path.match(/^\/api\/orgs\/([^/]+)$/);
|
|
52745
54450
|
if (orgMatch && method === "PATCH") {
|
|
52746
|
-
return handleUpdateOrg(orgMatch[1], req, ctx,
|
|
54451
|
+
return handleUpdateOrg(orgMatch[1], req, ctx, json2);
|
|
52747
54452
|
}
|
|
52748
54453
|
if (orgMatch && method === "DELETE") {
|
|
52749
|
-
return handleDeleteOrg(orgMatch[1], ctx,
|
|
54454
|
+
return handleDeleteOrg(orgMatch[1], ctx, json2);
|
|
52750
54455
|
}
|
|
52751
54456
|
if (path === "/api/org" && method === "GET") {
|
|
52752
|
-
return handleOrgChart(ctx,
|
|
54457
|
+
return handleOrgChart(ctx, json2);
|
|
52753
54458
|
}
|
|
52754
54459
|
const teamMatch = path.match(/^\/api\/agents\/([^/]+)\/team$/);
|
|
52755
54460
|
if (teamMatch && method === "GET") {
|
|
52756
|
-
return handleAgentTeam(teamMatch[1], ctx,
|
|
54461
|
+
return handleAgentTeam(teamMatch[1], ctx, json2);
|
|
52757
54462
|
}
|
|
52758
54463
|
if (path === "/api/agents" && method === "GET") {
|
|
52759
|
-
return handleListAgents(url, ctx,
|
|
54464
|
+
return handleListAgents(url, ctx, json2);
|
|
52760
54465
|
}
|
|
52761
54466
|
if (path === "/api/projects" && method === "POST") {
|
|
52762
|
-
return handleCreateProject(req, ctx,
|
|
54467
|
+
return handleCreateProject(req, ctx, json2);
|
|
52763
54468
|
}
|
|
52764
54469
|
const projectDeleteMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
52765
54470
|
if (projectDeleteMatch && method === "DELETE") {
|
|
52766
|
-
return handleDeleteProject(projectDeleteMatch[1], ctx,
|
|
54471
|
+
return handleDeleteProject(projectDeleteMatch[1], ctx, json2);
|
|
52767
54472
|
}
|
|
52768
54473
|
if (path === "/api/agents" && method === "POST") {
|
|
52769
|
-
return handleRegisterAgent(req, ctx,
|
|
54474
|
+
return handleRegisterAgent(req, ctx, json2);
|
|
52770
54475
|
}
|
|
52771
54476
|
const agentMatch = path.match(/^\/api\/agents\/([^/]+)$/);
|
|
52772
54477
|
if (agentMatch && method === "PATCH") {
|
|
52773
|
-
return handleUpdateAgent(agentMatch[1], req, ctx,
|
|
54478
|
+
return handleUpdateAgent(agentMatch[1], req, ctx, json2);
|
|
52774
54479
|
}
|
|
52775
54480
|
if (agentMatch && method === "DELETE") {
|
|
52776
|
-
return handleDeleteAgent(agentMatch[1], ctx,
|
|
54481
|
+
return handleDeleteAgent(agentMatch[1], ctx, json2);
|
|
52777
54482
|
}
|
|
52778
54483
|
if (path === "/api/agents/bulk" && method === "POST") {
|
|
52779
|
-
return handleBulkDeleteAgents(req, ctx,
|
|
54484
|
+
return handleBulkDeleteAgents(req, ctx, json2);
|
|
52780
54485
|
}
|
|
52781
54486
|
if (path === "/api/projects/bulk" && method === "POST") {
|
|
52782
|
-
return handleBulkDeleteProjects(req, ctx,
|
|
54487
|
+
return handleBulkDeleteProjects(req, ctx, json2);
|
|
52783
54488
|
}
|
|
52784
54489
|
if (path === "/api/doctor" && method === "GET") {
|
|
52785
|
-
return handleDoctor(ctx,
|
|
54490
|
+
return handleDoctor(ctx, json2);
|
|
52786
54491
|
}
|
|
52787
54492
|
if (path === "/api/report" && method === "GET") {
|
|
52788
|
-
return handleReport(req, url, ctx,
|
|
54493
|
+
return handleReport(req, url, ctx, json2);
|
|
52789
54494
|
}
|
|
52790
54495
|
if (path === "/api/activity" && method === "GET") {
|
|
52791
|
-
return handleActivity(req, url, ctx,
|
|
54496
|
+
return handleActivity(req, url, ctx, json2);
|
|
52792
54497
|
}
|
|
52793
54498
|
const historyMatch = path.match(/^\/api\/tasks\/([^/]+)\/history$/);
|
|
52794
54499
|
if (historyMatch && method === "GET") {
|
|
52795
|
-
return handleTaskHistory(historyMatch[1], ctx,
|
|
54500
|
+
return handleTaskHistory(historyMatch[1], ctx, json2, url);
|
|
52796
54501
|
}
|
|
52797
54502
|
if (path === "/api/webhooks" && method === "GET") {
|
|
52798
|
-
return handleListWebhooks(ctx,
|
|
54503
|
+
return handleListWebhooks(ctx, json2);
|
|
52799
54504
|
}
|
|
52800
54505
|
if (path === "/api/webhooks" && method === "POST") {
|
|
52801
|
-
return handleCreateWebhook(req, ctx,
|
|
54506
|
+
return handleCreateWebhook(req, ctx, json2);
|
|
52802
54507
|
}
|
|
52803
54508
|
const webhookMatch = path.match(/^\/api\/webhooks\/([^/]+)$/);
|
|
52804
54509
|
if (webhookMatch && method === "DELETE") {
|
|
52805
|
-
return handleDeleteWebhook(webhookMatch[1], ctx,
|
|
54510
|
+
return handleDeleteWebhook(webhookMatch[1], ctx, json2);
|
|
52806
54511
|
}
|
|
52807
54512
|
if (path === "/api/templates" && method === "GET") {
|
|
52808
|
-
return handleListTemplates(ctx,
|
|
54513
|
+
return handleListTemplates(ctx, json2);
|
|
52809
54514
|
}
|
|
52810
54515
|
if (path === "/api/templates" && method === "POST") {
|
|
52811
|
-
return handleCreateTemplate(req, ctx,
|
|
54516
|
+
return handleCreateTemplate(req, ctx, json2);
|
|
52812
54517
|
}
|
|
52813
54518
|
const templateMatch = path.match(/^\/api\/templates\/([^/]+)$/);
|
|
52814
54519
|
if (templateMatch && method === "DELETE") {
|
|
52815
|
-
return handleDeleteTemplate(templateMatch[1], ctx,
|
|
54520
|
+
return handleDeleteTemplate(templateMatch[1], ctx, json2);
|
|
52816
54521
|
}
|
|
52817
54522
|
if (path === "/api/plans" && method === "GET") {
|
|
52818
|
-
return handleListPlans(url, ctx,
|
|
54523
|
+
return handleListPlans(url, ctx, json2);
|
|
52819
54524
|
}
|
|
52820
54525
|
if (path === "/api/plans" && method === "POST") {
|
|
52821
|
-
return handleCreatePlan(req, ctx,
|
|
54526
|
+
return handleCreatePlan(req, ctx, json2);
|
|
52822
54527
|
}
|
|
52823
54528
|
if (path === "/api/plans/bulk" && method === "POST") {
|
|
52824
|
-
return handleBulkDeletePlans(req, ctx,
|
|
54529
|
+
return handleBulkDeletePlans(req, ctx, json2);
|
|
52825
54530
|
}
|
|
52826
54531
|
const planMatch = path.match(/^\/api\/plans\/([^/]+)$/);
|
|
52827
54532
|
if (planMatch) {
|
|
@@ -52830,16 +54535,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52830
54535
|
return handleGetPlan(id, ctx, jsonWithCors, taskToSummary);
|
|
52831
54536
|
}
|
|
52832
54537
|
if (method === "PATCH") {
|
|
52833
|
-
return handleUpdatePlan(id, req, ctx,
|
|
54538
|
+
return handleUpdatePlan(id, req, ctx, json2);
|
|
52834
54539
|
}
|
|
52835
54540
|
if (method === "DELETE") {
|
|
52836
|
-
return handleDeletePlan(id, ctx,
|
|
54541
|
+
return handleDeletePlan(id, ctx, json2);
|
|
52837
54542
|
}
|
|
52838
54543
|
}
|
|
52839
54544
|
const staticRes = handleStaticFiles(path, method, ctx, jsonWithCors, serveStaticFile);
|
|
52840
54545
|
if (staticRes)
|
|
52841
54546
|
return staticRes;
|
|
52842
|
-
return
|
|
54547
|
+
return json2({ error: "Not found" }, 404);
|
|
52843
54548
|
}
|
|
52844
54549
|
});
|
|
52845
54550
|
const shutdown = () => {
|