@hasna/todos 0.15.13 → 0.15.15
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/index.js +12117 -10745
- package/dist/contracts.js +1 -1
- package/dist/index.js +287 -53
- package/dist/mcp/index.js +2181 -810
- package/dist/mcp.js +1 -1
- package/dist/project-registration.js +12 -13
- package/dist/registry.js +1 -1
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +7 -0
- package/dist/sdk/v1.generated.d.ts +23 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +3 -0
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +19661 -18290
- package/dist/server/openapi.d.ts +134 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts +2 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +11 -12
- package/dist/task-manifest/authority.d.ts +3 -1
- package/dist/task-manifest/authority.d.ts.map +1 -1
- package/dist/task-manifest/backend.d.ts +18 -1
- package/dist/task-manifest/backend.d.ts.map +1 -1
- package/dist/task-manifest/http.d.ts +2 -1
- package/dist/task-manifest/http.d.ts.map +1 -1
- package/dist/task-manifest/index.d.ts +1 -1
- package/dist/task-manifest/index.d.ts.map +1 -1
- package/dist/task-manifest/postgres.d.ts +3 -1
- package/dist/task-manifest/postgres.d.ts.map +1 -1
- package/dist/task-manifest/schema-sql.d.ts +3 -1
- package/dist/task-manifest/schema-sql.d.ts.map +1 -1
- package/dist/task-manifest/schema.d.ts +2 -1
- package/dist/task-manifest/schema.d.ts.map +1 -1
- package/dist/task-manifest/sqlite.d.ts +4 -2
- package/dist/task-manifest/sqlite.d.ts.map +1 -1
- package/dist/task-manifest/types.d.ts +22 -1
- package/dist/task-manifest/types.d.ts.map +1 -1
- package/dist/task-manifest.js +275 -40
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -35415,7 +35415,7 @@ var package_default;
|
|
|
35415
35415
|
var init_package = __esm(() => {
|
|
35416
35416
|
package_default = {
|
|
35417
35417
|
name: "@hasna/todos",
|
|
35418
|
-
version: "0.15.
|
|
35418
|
+
version: "0.15.15",
|
|
35419
35419
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
35420
35420
|
type: "module",
|
|
35421
35421
|
main: "dist/index.js",
|
|
@@ -47421,21 +47421,21 @@ class PostgresJsonRecordStore {
|
|
|
47421
47421
|
), existing AS (
|
|
47422
47422
|
SELECT payload FROM ${this.tableName}
|
|
47423
47423
|
WHERE service = $1 AND object_type = 'plan_project_link_rollback_receipts'
|
|
47424
|
-
AND object_id = $
|
|
47424
|
+
AND object_id = $4 AND deleted_at IS NULL
|
|
47425
47425
|
FOR UPDATE
|
|
47426
47426
|
), checks AS (
|
|
47427
47427
|
SELECT
|
|
47428
47428
|
EXISTS (SELECT 1 FROM target_plan) AS plan_found,
|
|
47429
47429
|
COALESCE((SELECT payload->>'updated_at' = $3 FROM target_plan), false) AS plan_revision_ok,
|
|
47430
|
-
COALESCE((SELECT jsonb_object_agg(object_id, COALESCE(payload->'project_id', 'null'::jsonb) ORDER BY object_id) FROM member_tasks), '{}'::jsonb) = $
|
|
47431
|
-
AND COALESCE((SELECT jsonb_agg(object_id ORDER BY object_id) FROM member_tasks), '[]'::jsonb) = $
|
|
47430
|
+
COALESCE((SELECT jsonb_object_agg(object_id, COALESCE(payload->'project_id', 'null'::jsonb) ORDER BY object_id) FROM member_tasks), '{}'::jsonb) = $6::jsonb
|
|
47431
|
+
AND COALESCE((SELECT jsonb_agg(object_id ORDER BY object_id) FROM member_tasks), '[]'::jsonb) = $7::jsonb AS membership_ok,
|
|
47432
47432
|
EXISTS (SELECT 1 FROM existing) AS has_existing
|
|
47433
47433
|
), updated_plan AS (
|
|
47434
47434
|
UPDATE ${this.tableName} r SET
|
|
47435
|
-
payload = r.payload || jsonb_build_object('project_id', $
|
|
47436
|
-
updated_at = $
|
|
47435
|
+
payload = r.payload || jsonb_build_object('project_id', $8::jsonb, 'updated_at', $9::text),
|
|
47436
|
+
updated_at = $9::timestamptz,
|
|
47437
47437
|
version = COALESCE(r.version, 0) + 1,
|
|
47438
|
-
source_machine_id = COALESCE($
|
|
47438
|
+
source_machine_id = COALESCE($10, r.source_machine_id)
|
|
47439
47439
|
FROM checks
|
|
47440
47440
|
WHERE r.service = $1 AND r.object_type = 'plans' AND r.object_id = $2 AND r.deleted_at IS NULL
|
|
47441
47441
|
AND checks.plan_found AND checks.plan_revision_ok AND checks.membership_ok AND NOT checks.has_existing
|
|
@@ -47443,20 +47443,20 @@ class PostgresJsonRecordStore {
|
|
|
47443
47443
|
), updated_tasks AS (
|
|
47444
47444
|
UPDATE ${this.tableName} r SET
|
|
47445
47445
|
payload = r.payload || jsonb_build_object(
|
|
47446
|
-
'project_id', COALESCE($
|
|
47447
|
-
'updated_at', $
|
|
47446
|
+
'project_id', COALESCE($11::jsonb -> r.object_id, 'null'::jsonb),
|
|
47447
|
+
'updated_at', $9::text,
|
|
47448
47448
|
'version', COALESCE((r.payload->>'version')::int, 0) + 1
|
|
47449
47449
|
),
|
|
47450
|
-
updated_at = $
|
|
47450
|
+
updated_at = $9::timestamptz,
|
|
47451
47451
|
version = COALESCE(r.version, 0) + 1,
|
|
47452
|
-
source_machine_id = COALESCE($
|
|
47452
|
+
source_machine_id = COALESCE($10, r.source_machine_id)
|
|
47453
47453
|
WHERE r.service = $1 AND r.object_type = 'tasks' AND r.deleted_at IS NULL
|
|
47454
47454
|
AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM updated_plan)
|
|
47455
47455
|
RETURNING 1
|
|
47456
47456
|
), task_gate AS (SELECT count(*) AS count FROM updated_tasks), inserted AS (
|
|
47457
47457
|
INSERT INTO ${this.tableName}
|
|
47458
47458
|
(service, object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version)
|
|
47459
|
-
SELECT $1, 'plan_project_link_rollback_receipts', $
|
|
47459
|
+
SELECT $1, 'plan_project_link_rollback_receipts', $4, $5::jsonb, $9::timestamptz, NULL, $10, 1
|
|
47460
47460
|
FROM checks, task_gate
|
|
47461
47461
|
WHERE NOT checks.has_existing AND EXISTS (SELECT 1 FROM updated_plan)
|
|
47462
47462
|
RETURNING payload
|
|
@@ -47470,7 +47470,6 @@ class PostgresJsonRecordStore {
|
|
|
47470
47470
|
this.service,
|
|
47471
47471
|
input.plan_id,
|
|
47472
47472
|
input.expected_plan_revision,
|
|
47473
|
-
input.receipt_id,
|
|
47474
47473
|
input.rollback_receipt_id,
|
|
47475
47474
|
jsonbParam(rollback),
|
|
47476
47475
|
jsonbParam(currentTaskProjects),
|
|
@@ -50912,6 +50911,1282 @@ var init_project_registration = __esm(() => {
|
|
|
50912
50911
|
init_types4();
|
|
50913
50912
|
});
|
|
50914
50913
|
|
|
50914
|
+
// src/task-manifest/canonical.ts
|
|
50915
|
+
import { createHash as createHash16 } from "crypto";
|
|
50916
|
+
function canonicalize3(value) {
|
|
50917
|
+
if (Array.isArray(value))
|
|
50918
|
+
return value.map(canonicalize3);
|
|
50919
|
+
if (value !== null && typeof value === "object") {
|
|
50920
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize3(entry2)]));
|
|
50921
|
+
}
|
|
50922
|
+
return value;
|
|
50923
|
+
}
|
|
50924
|
+
function canonicalJson(value) {
|
|
50925
|
+
return JSON.stringify(canonicalize3(value));
|
|
50926
|
+
}
|
|
50927
|
+
function canonicalDigest(value) {
|
|
50928
|
+
return createHash16("sha256").update(canonicalJson(value)).digest("hex");
|
|
50929
|
+
}
|
|
50930
|
+
function deterministicUuid(namespace, ...parts) {
|
|
50931
|
+
const bytes = createHash16("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
|
|
50932
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
50933
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
50934
|
+
const hex = bytes.toString("hex");
|
|
50935
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
50936
|
+
}
|
|
50937
|
+
var init_canonical = () => {};
|
|
50938
|
+
|
|
50939
|
+
// src/task-manifest/types.ts
|
|
50940
|
+
var TODOS_TASK_MANIFEST_ROUTE = "todos.task-manifest.v1", TODOS_TASK_MANIFEST_SCHEMA_VERSION = 1, TodosTaskManifestError;
|
|
50941
|
+
var init_types5 = __esm(() => {
|
|
50942
|
+
TodosTaskManifestError = class TodosTaskManifestError extends Error {
|
|
50943
|
+
code;
|
|
50944
|
+
details;
|
|
50945
|
+
constructor(code, message, details = {}) {
|
|
50946
|
+
super(message);
|
|
50947
|
+
this.code = code;
|
|
50948
|
+
this.details = details;
|
|
50949
|
+
this.name = "TodosTaskManifestError";
|
|
50950
|
+
}
|
|
50951
|
+
};
|
|
50952
|
+
});
|
|
50953
|
+
|
|
50954
|
+
// src/task-manifest/schema.ts
|
|
50955
|
+
function parseTodosTaskManifest(input) {
|
|
50956
|
+
const parsed = schema.safeParse(input);
|
|
50957
|
+
if (!parsed.success) {
|
|
50958
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Invalid task manifest: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")}`, { issues: parsed.error.issues });
|
|
50959
|
+
}
|
|
50960
|
+
const taskKeys = new Set;
|
|
50961
|
+
let comments = 0;
|
|
50962
|
+
let verifications = 0;
|
|
50963
|
+
for (const entry2 of parsed.data.tasks) {
|
|
50964
|
+
if (taskKeys.has(entry2.key)) {
|
|
50965
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Duplicate task key: ${entry2.key}`);
|
|
50966
|
+
}
|
|
50967
|
+
taskKeys.add(entry2.key);
|
|
50968
|
+
comments += entry2.comments?.length ?? 0;
|
|
50969
|
+
verifications += entry2.verifications?.length ?? 0;
|
|
50970
|
+
}
|
|
50971
|
+
if (comments > TODOS_TASK_MANIFEST_BOUNDS.comments || verifications > TODOS_TASK_MANIFEST_BOUNDS.verifications) {
|
|
50972
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", "Task manifest nested resource bounds exceeded", {
|
|
50973
|
+
comments,
|
|
50974
|
+
verifications
|
|
50975
|
+
});
|
|
50976
|
+
}
|
|
50977
|
+
const seenDependencies = new Set;
|
|
50978
|
+
const dependencyGraph = new Map;
|
|
50979
|
+
for (const edge of parsed.data.dependencies ?? []) {
|
|
50980
|
+
if (!taskKeys.has(edge.task) || !taskKeys.has(edge.depends_on)) {
|
|
50981
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_FOREIGN_REFERENCE", `Dependency contains foreign task key: ${edge.task} -> ${edge.depends_on}`);
|
|
50982
|
+
}
|
|
50983
|
+
if (edge.task === edge.depends_on) {
|
|
50984
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Task ${edge.task} cannot depend on itself`);
|
|
50985
|
+
}
|
|
50986
|
+
const identity = `${edge.task}\x1F${edge.depends_on}`;
|
|
50987
|
+
if (seenDependencies.has(identity)) {
|
|
50988
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Duplicate dependency: ${edge.task} -> ${edge.depends_on}`);
|
|
50989
|
+
}
|
|
50990
|
+
seenDependencies.add(identity);
|
|
50991
|
+
const prerequisites = dependencyGraph.get(edge.task) ?? [];
|
|
50992
|
+
prerequisites.push(edge.depends_on);
|
|
50993
|
+
dependencyGraph.set(edge.task, prerequisites);
|
|
50994
|
+
}
|
|
50995
|
+
const visiting = new Set;
|
|
50996
|
+
const visited = new Set;
|
|
50997
|
+
const visit = (taskKey) => {
|
|
50998
|
+
if (visiting.has(taskKey)) {
|
|
50999
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Dependency cycle contains task ${taskKey}`);
|
|
51000
|
+
}
|
|
51001
|
+
if (visited.has(taskKey))
|
|
51002
|
+
return;
|
|
51003
|
+
visiting.add(taskKey);
|
|
51004
|
+
for (const prerequisite of dependencyGraph.get(taskKey) ?? [])
|
|
51005
|
+
visit(prerequisite);
|
|
51006
|
+
visiting.delete(taskKey);
|
|
51007
|
+
visited.add(taskKey);
|
|
51008
|
+
};
|
|
51009
|
+
for (const taskKey of taskKeys)
|
|
51010
|
+
visit(taskKey);
|
|
51011
|
+
return parsed.data;
|
|
51012
|
+
}
|
|
51013
|
+
function parseTodosTaskManifestCompensation(input) {
|
|
51014
|
+
const parsed = compensationSchema.safeParse(input);
|
|
51015
|
+
if (!parsed.success) {
|
|
51016
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Invalid task-manifest compensation: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")}`, { issues: parsed.error.issues });
|
|
51017
|
+
}
|
|
51018
|
+
return parsed.data;
|
|
51019
|
+
}
|
|
51020
|
+
function parseTodosTaskManifestBindingLookup(input) {
|
|
51021
|
+
const parsed = bindingLookupSchema.safeParse(input);
|
|
51022
|
+
if (!parsed.success) {
|
|
51023
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Invalid task-manifest binding lookup: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")}`, { issues: parsed.error.issues });
|
|
51024
|
+
}
|
|
51025
|
+
return parsed.data;
|
|
51026
|
+
}
|
|
51027
|
+
var TODOS_TASK_MANIFEST_BOUNDS, key, identifier, uuid2, scalar, boundedScalarRecord = (limit, field) => exports_external.record(exports_external.string().max(200), scalar).superRefine((value, context) => {
|
|
51028
|
+
if (Object.keys(value).length > limit) {
|
|
51029
|
+
context.addIssue({ code: exports_external.ZodIssueCode.custom, message: `${field} exceeds ${limit} fields` });
|
|
51030
|
+
}
|
|
51031
|
+
}), comment, verification, task2, dependency, effect, schema, compensationSchema, bindingLookupSchema;
|
|
51032
|
+
var init_schema2 = __esm(() => {
|
|
51033
|
+
init_zod();
|
|
51034
|
+
init_types();
|
|
51035
|
+
init_types5();
|
|
51036
|
+
TODOS_TASK_MANIFEST_BOUNDS = {
|
|
51037
|
+
tasks: 128,
|
|
51038
|
+
dependencies: 512,
|
|
51039
|
+
comments: 512,
|
|
51040
|
+
verifications: 512,
|
|
51041
|
+
effects: 64,
|
|
51042
|
+
metadata_fields: 128,
|
|
51043
|
+
effect_payload_fields: 128,
|
|
51044
|
+
request_bytes: 16777216,
|
|
51045
|
+
response_bytes: 1048576
|
|
51046
|
+
};
|
|
51047
|
+
key = exports_external.string().min(1).max(96).regex(/^[a-z][a-z0-9_-]*$/);
|
|
51048
|
+
identifier = exports_external.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
51049
|
+
uuid2 = exports_external.string().uuid();
|
|
51050
|
+
scalar = exports_external.union([exports_external.string().max(4096), exports_external.number().finite(), exports_external.boolean(), exports_external.null()]);
|
|
51051
|
+
comment = exports_external.object({
|
|
51052
|
+
content: exports_external.string().min(1).max(16384),
|
|
51053
|
+
type: exports_external.enum(["comment", "progress", "status_change", "system"]).optional(),
|
|
51054
|
+
progress_pct: exports_external.number().int().min(0).max(100).optional(),
|
|
51055
|
+
agent_id: identifier.optional(),
|
|
51056
|
+
session_id: identifier.optional()
|
|
51057
|
+
}).strict();
|
|
51058
|
+
verification = exports_external.object({
|
|
51059
|
+
command: exports_external.string().min(1).max(8192),
|
|
51060
|
+
status: exports_external.enum(["passed", "failed", "unknown"]).optional(),
|
|
51061
|
+
output_summary: exports_external.string().max(16384).optional(),
|
|
51062
|
+
artifact_path: exports_external.string().max(4096).optional(),
|
|
51063
|
+
agent_id: identifier.optional()
|
|
51064
|
+
}).strict();
|
|
51065
|
+
task2 = exports_external.object({
|
|
51066
|
+
key,
|
|
51067
|
+
title: exports_external.string().min(1).max(500),
|
|
51068
|
+
description: exports_external.string().max(64000).optional(),
|
|
51069
|
+
status: exports_external.enum(TASK_STATUSES).optional(),
|
|
51070
|
+
priority: exports_external.enum(TASK_PRIORITIES).optional(),
|
|
51071
|
+
assigned_to: identifier.optional(),
|
|
51072
|
+
created_by: identifier.optional(),
|
|
51073
|
+
tags: exports_external.array(exports_external.string().min(1).max(100)).max(64).optional(),
|
|
51074
|
+
metadata: boundedScalarRecord(TODOS_TASK_MANIFEST_BOUNDS.metadata_fields, "metadata").optional(),
|
|
51075
|
+
comments: exports_external.array(comment).max(64).optional(),
|
|
51076
|
+
verifications: exports_external.array(verification).max(64).optional()
|
|
51077
|
+
}).strict();
|
|
51078
|
+
dependency = exports_external.object({ task: key, depends_on: key }).strict();
|
|
51079
|
+
effect = exports_external.object({
|
|
51080
|
+
topic: exports_external.string().min(1).max(200),
|
|
51081
|
+
payload: boundedScalarRecord(TODOS_TASK_MANIFEST_BOUNDS.effect_payload_fields, "effect payload")
|
|
51082
|
+
}).strict();
|
|
51083
|
+
schema = exports_external.object({
|
|
51084
|
+
version: exports_external.literal(1),
|
|
51085
|
+
operation_id: identifier,
|
|
51086
|
+
idempotency_key: identifier,
|
|
51087
|
+
project_id: uuid2,
|
|
51088
|
+
task_list_id: uuid2.optional(),
|
|
51089
|
+
if_binding_version: exports_external.number().int().min(0).optional(),
|
|
51090
|
+
plan: exports_external.object({
|
|
51091
|
+
key,
|
|
51092
|
+
name: exports_external.string().min(1).max(500),
|
|
51093
|
+
description: exports_external.string().max(64000).optional(),
|
|
51094
|
+
status: exports_external.enum(["active", "completed", "archived"]).optional()
|
|
51095
|
+
}).strict(),
|
|
51096
|
+
tasks: exports_external.array(task2).min(1).max(TODOS_TASK_MANIFEST_BOUNDS.tasks),
|
|
51097
|
+
dependencies: exports_external.array(dependency).max(TODOS_TASK_MANIFEST_BOUNDS.dependencies).optional(),
|
|
51098
|
+
effects: exports_external.array(effect).max(TODOS_TASK_MANIFEST_BOUNDS.effects).optional()
|
|
51099
|
+
}).strict();
|
|
51100
|
+
compensationSchema = exports_external.object({
|
|
51101
|
+
receipt_id: uuid2,
|
|
51102
|
+
idempotency_key: identifier,
|
|
51103
|
+
if_binding_version: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER)
|
|
51104
|
+
}).strict();
|
|
51105
|
+
bindingLookupSchema = exports_external.object({
|
|
51106
|
+
authority: exports_external.string().min(1).max(64),
|
|
51107
|
+
route: exports_external.string().min(1).max(128),
|
|
51108
|
+
schema_version: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
|
51109
|
+
tenant_id: identifier,
|
|
51110
|
+
plan_id: uuid2,
|
|
51111
|
+
max_items: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER)
|
|
51112
|
+
}).strict();
|
|
51113
|
+
});
|
|
51114
|
+
|
|
51115
|
+
// src/task-manifest/backend.ts
|
|
51116
|
+
function validateTaskManifestBindingLookupRows(rows, tenantId, planId) {
|
|
51117
|
+
if (rows.length === 0) {
|
|
51118
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BINDING_NOT_FOUND", `Managed task-manifest binding not found for plan: ${planId}`, { plan_id: planId });
|
|
51119
|
+
}
|
|
51120
|
+
if (rows.length !== 1) {
|
|
51121
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_LOOKUP_CONFLICT", "Task-manifest plan lookup matched more than one binding", { plan_id: planId, matched_items: rows.length, max_items: 1 });
|
|
51122
|
+
}
|
|
51123
|
+
const row = rows[0];
|
|
51124
|
+
const bindingVersion = Number(row.binding_version);
|
|
51125
|
+
const state = row.state;
|
|
51126
|
+
if (row.binding_tenant_id !== tenantId || row.receipt_tenant_id !== tenantId || row.binding_plan_id !== planId || row.receipt_plan_id !== planId || row.receipt_authority !== "todos" || row.receipt_route !== "todos.task-manifest.v1" || Number(row.receipt_schema_version) !== 1 || row.receipt_kind !== "apply" || row.binding_operation_id !== row.receipt_operation_id || typeof row.apply_receipt_id !== "string" || !UUID_PATTERN2.test(row.apply_receipt_id) || !Number.isSafeInteger(bindingVersion) || bindingVersion < 1 || state !== "applied" && state !== "compensated") {
|
|
51127
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_LOOKUP_CONFLICT", "Task-manifest binding and immutable apply receipt disagree", { plan_id: planId });
|
|
51128
|
+
}
|
|
51129
|
+
return {
|
|
51130
|
+
plan_id: planId,
|
|
51131
|
+
apply_receipt_id: row.apply_receipt_id,
|
|
51132
|
+
binding_version: bindingVersion,
|
|
51133
|
+
state
|
|
51134
|
+
};
|
|
51135
|
+
}
|
|
51136
|
+
var UUID_PATTERN2;
|
|
51137
|
+
var init_backend = __esm(() => {
|
|
51138
|
+
init_types5();
|
|
51139
|
+
UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
51140
|
+
});
|
|
51141
|
+
|
|
51142
|
+
// src/task-manifest/reference-guard.ts
|
|
51143
|
+
function postgresTaskManifestForeignReferenceSql(tableName) {
|
|
51144
|
+
return `SELECT object_type, object_id
|
|
51145
|
+
FROM ${tableName}
|
|
51146
|
+
WHERE service = $1 AND deleted_at IS NULL
|
|
51147
|
+
AND NOT (object_type = 'plans' AND object_id = $2)
|
|
51148
|
+
AND NOT (object_type = 'tasks' AND object_id IN (
|
|
51149
|
+
SELECT value FROM jsonb_array_elements_text($3::jsonb)
|
|
51150
|
+
))
|
|
51151
|
+
AND NOT (object_type = 'dependencies' AND object_id IN (
|
|
51152
|
+
SELECT value FROM jsonb_array_elements_text($4::jsonb)
|
|
51153
|
+
))
|
|
51154
|
+
AND NOT (object_type = 'comments' AND object_id IN (
|
|
51155
|
+
SELECT value FROM jsonb_array_elements_text($5::jsonb)
|
|
51156
|
+
))
|
|
51157
|
+
AND NOT (object_type = 'verifications' AND object_id IN (
|
|
51158
|
+
SELECT value FROM jsonb_array_elements_text($6::jsonb)
|
|
51159
|
+
))
|
|
51160
|
+
AND jsonb_path_exists(
|
|
51161
|
+
payload,
|
|
51162
|
+
'$.** ? (@ == $refs[*])',
|
|
51163
|
+
jsonb_build_object('refs', $7::jsonb)
|
|
51164
|
+
)
|
|
51165
|
+
LIMIT 1`;
|
|
51166
|
+
}
|
|
51167
|
+
|
|
51168
|
+
// src/task-manifest/schema-sql.ts
|
|
51169
|
+
function sqlString(value) {
|
|
51170
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
51171
|
+
}
|
|
51172
|
+
function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
|
|
51173
|
+
const tenantDefault = sqlString(tenantId);
|
|
51174
|
+
return [
|
|
51175
|
+
`CREATE TABLE IF NOT EXISTS todos_task_manifest_receipts (
|
|
51176
|
+
receipt_id text PRIMARY KEY,
|
|
51177
|
+
tenant_id text NOT NULL,
|
|
51178
|
+
authority text NOT NULL CHECK(authority = 'todos'),
|
|
51179
|
+
route text NOT NULL,
|
|
51180
|
+
schema_version integer NOT NULL CHECK(schema_version = 1),
|
|
51181
|
+
kind text NOT NULL CHECK(kind IN ('apply', 'compensate')),
|
|
51182
|
+
operation_id text NOT NULL,
|
|
51183
|
+
idempotency_key text NOT NULL,
|
|
51184
|
+
request_digest text NOT NULL,
|
|
51185
|
+
result_digest text NOT NULL,
|
|
51186
|
+
binding_version integer NOT NULL,
|
|
51187
|
+
apply_receipt_id text,
|
|
51188
|
+
manifest_json jsonb,
|
|
51189
|
+
result_json jsonb NOT NULL,
|
|
51190
|
+
created_at timestamptz NOT NULL,
|
|
51191
|
+
UNIQUE(kind, idempotency_key)
|
|
51192
|
+
)`,
|
|
51193
|
+
`ALTER TABLE todos_task_manifest_receipts
|
|
51194
|
+
ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT ${tenantDefault}`,
|
|
51195
|
+
`ALTER TABLE todos_task_manifest_receipts
|
|
51196
|
+
ALTER COLUMN tenant_id DROP DEFAULT`,
|
|
51197
|
+
`CREATE TABLE IF NOT EXISTS todos_task_manifest_bindings (
|
|
51198
|
+
operation_id text PRIMARY KEY,
|
|
51199
|
+
tenant_id text NOT NULL,
|
|
51200
|
+
idempotency_key text NOT NULL UNIQUE,
|
|
51201
|
+
request_digest text NOT NULL,
|
|
51202
|
+
result_digest text NOT NULL,
|
|
51203
|
+
apply_receipt_id text NOT NULL UNIQUE REFERENCES todos_task_manifest_receipts(receipt_id),
|
|
51204
|
+
manifest_json jsonb NOT NULL,
|
|
51205
|
+
result_json jsonb NOT NULL,
|
|
51206
|
+
state text NOT NULL CHECK(state IN ('applied', 'compensated')),
|
|
51207
|
+
version integer NOT NULL,
|
|
51208
|
+
compensation_receipt_id text,
|
|
51209
|
+
created_at timestamptz NOT NULL,
|
|
51210
|
+
updated_at timestamptz NOT NULL
|
|
51211
|
+
)`,
|
|
51212
|
+
`ALTER TABLE todos_task_manifest_bindings
|
|
51213
|
+
ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT ${tenantDefault}`,
|
|
51214
|
+
`ALTER TABLE todos_task_manifest_bindings
|
|
51215
|
+
ALTER COLUMN tenant_id DROP DEFAULT`,
|
|
51216
|
+
`CREATE TABLE IF NOT EXISTS todos_task_manifest_outbox (
|
|
51217
|
+
id text PRIMARY KEY,
|
|
51218
|
+
apply_receipt_id text NOT NULL REFERENCES todos_task_manifest_receipts(receipt_id),
|
|
51219
|
+
topic text NOT NULL,
|
|
51220
|
+
payload jsonb NOT NULL,
|
|
51221
|
+
payload_digest text NOT NULL,
|
|
51222
|
+
status text NOT NULL CHECK(status IN ('pending', 'delivered', 'cancelled')),
|
|
51223
|
+
attempts integer NOT NULL DEFAULT 0,
|
|
51224
|
+
created_at timestamptz NOT NULL,
|
|
51225
|
+
delivered_at timestamptz
|
|
51226
|
+
)`,
|
|
51227
|
+
`CREATE INDEX IF NOT EXISTS todos_task_manifest_outbox_receipt_idx
|
|
51228
|
+
ON todos_task_manifest_outbox(apply_receipt_id, status)`,
|
|
51229
|
+
`CREATE INDEX IF NOT EXISTS todos_task_manifest_receipts_tenant_idx
|
|
51230
|
+
ON todos_task_manifest_receipts(tenant_id, receipt_id, kind)`,
|
|
51231
|
+
`CREATE INDEX IF NOT EXISTS todos_task_manifest_bindings_tenant_plan_idx
|
|
51232
|
+
ON todos_task_manifest_bindings(
|
|
51233
|
+
tenant_id,
|
|
51234
|
+
((result_json #>> '{graph,plan_id}'))
|
|
51235
|
+
)`,
|
|
51236
|
+
`CREATE OR REPLACE FUNCTION todos_task_manifest_receipts_immutable()
|
|
51237
|
+
RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN
|
|
51238
|
+
RAISE EXCEPTION 'todos task manifest receipts are immutable';
|
|
51239
|
+
END; $$`,
|
|
51240
|
+
`DROP TRIGGER IF EXISTS todos_task_manifest_receipts_immutable ON todos_task_manifest_receipts`,
|
|
51241
|
+
`CREATE TRIGGER todos_task_manifest_receipts_immutable
|
|
51242
|
+
BEFORE UPDATE OR DELETE ON todos_task_manifest_receipts
|
|
51243
|
+
FOR EACH ROW EXECUTE FUNCTION todos_task_manifest_receipts_immutable()`
|
|
51244
|
+
];
|
|
51245
|
+
}
|
|
51246
|
+
|
|
51247
|
+
// src/task-manifest/sqlite.ts
|
|
51248
|
+
var sqliteTails;
|
|
51249
|
+
var init_sqlite2 = __esm(() => {
|
|
51250
|
+
init_canonical();
|
|
51251
|
+
init_backend();
|
|
51252
|
+
init_types5();
|
|
51253
|
+
sqliteTails = new WeakMap;
|
|
51254
|
+
});
|
|
51255
|
+
|
|
51256
|
+
// src/task-manifest/postgres.ts
|
|
51257
|
+
function safeIdentifier2(value, field) {
|
|
51258
|
+
if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
|
|
51259
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `${field} must be a safe PostgreSQL identifier`);
|
|
51260
|
+
}
|
|
51261
|
+
return value;
|
|
51262
|
+
}
|
|
51263
|
+
function parseJson2(value) {
|
|
51264
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
51265
|
+
}
|
|
51266
|
+
function timestamp4(value) {
|
|
51267
|
+
return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
|
|
51268
|
+
}
|
|
51269
|
+
function fault(faults, point) {
|
|
51270
|
+
if (faults.points.has(point))
|
|
51271
|
+
throw new Error(`Injected task-manifest fault at ${point}`);
|
|
51272
|
+
}
|
|
51273
|
+
function receiptFromRow3(row) {
|
|
51274
|
+
return {
|
|
51275
|
+
receipt_id: String(row["receipt_id"]),
|
|
51276
|
+
authority: "todos",
|
|
51277
|
+
route: "todos.task-manifest.v1",
|
|
51278
|
+
schema_version: 1,
|
|
51279
|
+
kind: row["kind"],
|
|
51280
|
+
operation_id: String(row["operation_id"]),
|
|
51281
|
+
idempotency_key: String(row["idempotency_key"]),
|
|
51282
|
+
request_digest: String(row["request_digest"]),
|
|
51283
|
+
result_digest: String(row["result_digest"]),
|
|
51284
|
+
binding_version: Number(row["binding_version"]),
|
|
51285
|
+
apply_receipt_id: row["apply_receipt_id"] == null ? null : String(row["apply_receipt_id"]),
|
|
51286
|
+
created_at: timestamp4(row["created_at"])
|
|
51287
|
+
};
|
|
51288
|
+
}
|
|
51289
|
+
function taskPayload2(manifest, task3, taskId, planId, now4) {
|
|
51290
|
+
return {
|
|
51291
|
+
id: taskId,
|
|
51292
|
+
short_id: null,
|
|
51293
|
+
project_id: manifest.project_id,
|
|
51294
|
+
parent_id: null,
|
|
51295
|
+
plan_id: planId,
|
|
51296
|
+
task_list_id: manifest.task_list_id ?? null,
|
|
51297
|
+
title: task3.title,
|
|
51298
|
+
description: task3.description ?? null,
|
|
51299
|
+
status: task3.status ?? "pending",
|
|
51300
|
+
priority: task3.priority ?? "medium",
|
|
51301
|
+
agent_id: null,
|
|
51302
|
+
assigned_to: task3.assigned_to ?? null,
|
|
51303
|
+
session_id: null,
|
|
51304
|
+
working_dir: null,
|
|
51305
|
+
tags: task3.tags ?? [],
|
|
51306
|
+
metadata: task3.metadata ?? {},
|
|
51307
|
+
version: 1,
|
|
51308
|
+
locked_by: null,
|
|
51309
|
+
locked_at: null,
|
|
51310
|
+
created_at: now4,
|
|
51311
|
+
updated_at: now4,
|
|
51312
|
+
started_at: null,
|
|
51313
|
+
completed_at: null,
|
|
51314
|
+
due_at: null,
|
|
51315
|
+
estimated_minutes: null,
|
|
51316
|
+
actual_minutes: null,
|
|
51317
|
+
requires_approval: false,
|
|
51318
|
+
approved_by: null,
|
|
51319
|
+
approved_at: null,
|
|
51320
|
+
recurrence_rule: null,
|
|
51321
|
+
recurrence_parent_id: null,
|
|
51322
|
+
spawns_template_id: null,
|
|
51323
|
+
confidence: null,
|
|
51324
|
+
reason: null,
|
|
51325
|
+
spawned_from_session: null,
|
|
51326
|
+
assigned_by: null,
|
|
51327
|
+
created_by: task3.created_by ?? null,
|
|
51328
|
+
assigned_from_project: null,
|
|
51329
|
+
task_type: null,
|
|
51330
|
+
cost_tokens: 0,
|
|
51331
|
+
cost_usd: 0,
|
|
51332
|
+
delegated_from: null,
|
|
51333
|
+
delegation_depth: 0,
|
|
51334
|
+
retry_count: 0,
|
|
51335
|
+
max_retries: 0,
|
|
51336
|
+
retry_after: null,
|
|
51337
|
+
sla_minutes: null,
|
|
51338
|
+
runner_id: null,
|
|
51339
|
+
runner_started_at: null,
|
|
51340
|
+
runner_completed_at: null,
|
|
51341
|
+
current_step: null,
|
|
51342
|
+
total_steps: null,
|
|
51343
|
+
machine_id: null,
|
|
51344
|
+
synced_at: null,
|
|
51345
|
+
archived_at: null
|
|
51346
|
+
};
|
|
51347
|
+
}
|
|
51348
|
+
function planPayload(input) {
|
|
51349
|
+
return {
|
|
51350
|
+
id: input.graph.plan_id,
|
|
51351
|
+
slug: null,
|
|
51352
|
+
project_id: input.manifest.project_id,
|
|
51353
|
+
task_list_id: input.manifest.task_list_id ?? null,
|
|
51354
|
+
agent_id: null,
|
|
51355
|
+
name: input.manifest.plan.name,
|
|
51356
|
+
description: input.manifest.plan.description ?? null,
|
|
51357
|
+
status: input.manifest.plan.status ?? "active",
|
|
51358
|
+
created_at: input.now,
|
|
51359
|
+
updated_at: input.now,
|
|
51360
|
+
machine_id: null,
|
|
51361
|
+
synced_at: null
|
|
51362
|
+
};
|
|
51363
|
+
}
|
|
51364
|
+
function placeholders2(start, count2) {
|
|
51365
|
+
return Array.from({ length: count2 }, (_, index) => `$${start + index}`).join(",");
|
|
51366
|
+
}
|
|
51367
|
+
|
|
51368
|
+
class PostgresTodosTaskManifestBackend {
|
|
51369
|
+
client;
|
|
51370
|
+
kind = "postgresql";
|
|
51371
|
+
service;
|
|
51372
|
+
tableName;
|
|
51373
|
+
tenantId;
|
|
51374
|
+
schemaReady = null;
|
|
51375
|
+
constructor(client, options = {}) {
|
|
51376
|
+
this.client = client;
|
|
51377
|
+
this.service = options.service ?? "todos";
|
|
51378
|
+
this.tableName = safeIdentifier2(options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE, "tableName");
|
|
51379
|
+
this.tenantId = options.tenantId ?? "default";
|
|
51380
|
+
}
|
|
51381
|
+
async ensureSchema() {
|
|
51382
|
+
this.schemaReady ??= (async () => {
|
|
51383
|
+
for (const sql of postgresTodosSyncSchemaSql(this.tableName))
|
|
51384
|
+
await this.client.query(sql);
|
|
51385
|
+
for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
|
|
51386
|
+
await this.client.query(sql);
|
|
51387
|
+
})();
|
|
51388
|
+
await this.schemaReady;
|
|
51389
|
+
}
|
|
51390
|
+
async insertSync(tx, objectType2, objectId, payload, now4) {
|
|
51391
|
+
await tx.query(`INSERT INTO ${this.tableName} (
|
|
51392
|
+
service, object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version
|
|
51393
|
+
) VALUES ($1, $2, $3, $4::jsonb, $5, NULL, NULL, 1)`, [
|
|
51394
|
+
this.service,
|
|
51395
|
+
objectType2,
|
|
51396
|
+
objectId,
|
|
51397
|
+
canonicalJson(payload),
|
|
51398
|
+
now4
|
|
51399
|
+
]);
|
|
51400
|
+
}
|
|
51401
|
+
async apply(input, faults) {
|
|
51402
|
+
await this.ensureSchema();
|
|
51403
|
+
return this.client.transaction(async (tx) => {
|
|
51404
|
+
const { manifest } = input;
|
|
51405
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${manifest.operation_id}`]);
|
|
51406
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1Fidempotency\x1F${manifest.idempotency_key}`]);
|
|
51407
|
+
const existing = await tx.query("SELECT * FROM todos_task_manifest_bindings WHERE tenant_id = $1 AND operation_id = $2 LIMIT 1 FOR UPDATE", [this.tenantId, manifest.operation_id]);
|
|
51408
|
+
if (existing.rows[0]) {
|
|
51409
|
+
const binding = existing.rows[0];
|
|
51410
|
+
if (binding["idempotency_key"] !== manifest.idempotency_key || binding["request_digest"] !== input.request_digest) {
|
|
51411
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Operation is already bound to a different request");
|
|
51412
|
+
}
|
|
51413
|
+
if (binding["state"] !== "applied") {
|
|
51414
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", "Operation was already compensated");
|
|
51415
|
+
}
|
|
51416
|
+
return { ...parseJson2(binding["result_json"]), duplicate: true };
|
|
51417
|
+
}
|
|
51418
|
+
const reused = await tx.query("SELECT operation_id FROM todos_task_manifest_bindings WHERE tenant_id = $1 AND idempotency_key = $2 LIMIT 1", [this.tenantId, manifest.idempotency_key]);
|
|
51419
|
+
if (reused.rows[0])
|
|
51420
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Idempotency key is already used");
|
|
51421
|
+
if (manifest.if_binding_version !== undefined && manifest.if_binding_version !== 0) {
|
|
51422
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "New manifest binding version must be 0");
|
|
51423
|
+
}
|
|
51424
|
+
const project = await tx.query(`SELECT 1 AS found FROM ${this.tableName}
|
|
51425
|
+
WHERE service = $1 AND object_type = 'projects' AND object_id = $2 AND deleted_at IS NULL LIMIT 1`, [this.service, manifest.project_id]);
|
|
51426
|
+
if (!project.rows[0])
|
|
51427
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_FOREIGN_REFERENCE", "Project does not exist");
|
|
51428
|
+
if (manifest.task_list_id) {
|
|
51429
|
+
const taskList = await tx.query(`SELECT payload FROM ${this.tableName}
|
|
51430
|
+
WHERE service = $1 AND object_type = 'task_lists' AND object_id = $2 AND deleted_at IS NULL LIMIT 1`, [this.service, manifest.task_list_id]);
|
|
51431
|
+
const payload = taskList.rows[0] ? parseJson2(taskList.rows[0]["payload"]) : null;
|
|
51432
|
+
if (!payload || payload["project_id"] !== manifest.project_id) {
|
|
51433
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_FOREIGN_REFERENCE", "Task list does not belong to the project");
|
|
51434
|
+
}
|
|
51435
|
+
}
|
|
51436
|
+
const objectIds = [input.graph.plan_id, ...Object.values(input.graph.task_ids), ...input.graph.comment_ids, ...input.graph.verification_ids, ...input.graph.dependency_ids];
|
|
51437
|
+
const conflict = await tx.query(`SELECT object_id FROM ${this.tableName}
|
|
51438
|
+
WHERE service = $1 AND object_id IN (${placeholders2(2, objectIds.length)}) LIMIT 1`, [this.service, ...objectIds]);
|
|
51439
|
+
if (conflict.rows[0])
|
|
51440
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", "A deterministic graph id already exists");
|
|
51441
|
+
await this.insertSync(tx, "plans", input.graph.plan_id, planPayload(input), input.now);
|
|
51442
|
+
fault(faults, "after_plan_write");
|
|
51443
|
+
for (const task3 of manifest.tasks) {
|
|
51444
|
+
await this.insertSync(tx, "tasks", input.graph.task_ids[task3.key], taskPayload2(manifest, task3, input.graph.task_ids[task3.key], input.graph.plan_id, input.now), input.now);
|
|
51445
|
+
}
|
|
51446
|
+
fault(faults, "after_task_write");
|
|
51447
|
+
for (const [index, edge] of (manifest.dependencies ?? []).entries()) {
|
|
51448
|
+
await this.insertSync(tx, "dependencies", input.graph.dependency_ids[index], {
|
|
51449
|
+
id: input.graph.dependency_ids[index],
|
|
51450
|
+
task_id: input.graph.task_ids[edge.task],
|
|
51451
|
+
depends_on: input.graph.task_ids[edge.depends_on],
|
|
51452
|
+
created_at: input.now,
|
|
51453
|
+
updated_at: input.now
|
|
51454
|
+
}, input.now);
|
|
51455
|
+
}
|
|
51456
|
+
fault(faults, "after_dependency_write");
|
|
51457
|
+
let commentIndex = 0;
|
|
51458
|
+
for (const task3 of manifest.tasks)
|
|
51459
|
+
for (const comment2 of task3.comments ?? []) {
|
|
51460
|
+
const id = input.graph.comment_ids[commentIndex++];
|
|
51461
|
+
await this.insertSync(tx, "comments", id, {
|
|
51462
|
+
id,
|
|
51463
|
+
task_id: input.graph.task_ids[task3.key],
|
|
51464
|
+
agent_id: comment2.agent_id ?? null,
|
|
51465
|
+
session_id: comment2.session_id ?? null,
|
|
51466
|
+
content: comment2.content,
|
|
51467
|
+
type: comment2.type ?? "comment",
|
|
51468
|
+
progress_pct: comment2.progress_pct ?? null,
|
|
51469
|
+
created_at: input.now
|
|
51470
|
+
}, input.now);
|
|
51471
|
+
}
|
|
51472
|
+
fault(faults, "after_comment_write");
|
|
51473
|
+
let verificationIndex = 0;
|
|
51474
|
+
for (const task3 of manifest.tasks)
|
|
51475
|
+
for (const verification2 of task3.verifications ?? []) {
|
|
51476
|
+
const id = input.graph.verification_ids[verificationIndex++];
|
|
51477
|
+
await this.insertSync(tx, "verifications", id, {
|
|
51478
|
+
id,
|
|
51479
|
+
task_id: input.graph.task_ids[task3.key],
|
|
51480
|
+
command: verification2.command,
|
|
51481
|
+
status: verification2.status ?? "unknown",
|
|
51482
|
+
output_summary: verification2.output_summary ?? null,
|
|
51483
|
+
artifact_path: verification2.artifact_path ?? null,
|
|
51484
|
+
agent_id: verification2.agent_id ?? null,
|
|
51485
|
+
run_at: input.now,
|
|
51486
|
+
created_at: input.now,
|
|
51487
|
+
updated_at: input.now
|
|
51488
|
+
}, input.now);
|
|
51489
|
+
}
|
|
51490
|
+
fault(faults, "after_verification_write");
|
|
51491
|
+
const readback = await this.readback(tx, input.graph);
|
|
51492
|
+
const expected = {
|
|
51493
|
+
plans: 1,
|
|
51494
|
+
tasks: manifest.tasks.length,
|
|
51495
|
+
dependencies: manifest.dependencies?.length ?? 0,
|
|
51496
|
+
comments: input.graph.comment_ids.length,
|
|
51497
|
+
verifications: input.graph.verification_ids.length,
|
|
51498
|
+
complete: true
|
|
51499
|
+
};
|
|
51500
|
+
if (canonicalJson(readback) !== canonicalJson(expected)) {
|
|
51501
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_READBACK_MISMATCH", "Exact PostgreSQL graph readback did not match", { expected, readback });
|
|
51502
|
+
}
|
|
51503
|
+
const receipt = {
|
|
51504
|
+
receipt_id: input.receipt_id,
|
|
51505
|
+
authority: "todos",
|
|
51506
|
+
route: "todos.task-manifest.v1",
|
|
51507
|
+
schema_version: 1,
|
|
51508
|
+
kind: "apply",
|
|
51509
|
+
operation_id: manifest.operation_id,
|
|
51510
|
+
idempotency_key: manifest.idempotency_key,
|
|
51511
|
+
request_digest: input.request_digest,
|
|
51512
|
+
result_digest: input.result_digest,
|
|
51513
|
+
binding_version: 1,
|
|
51514
|
+
apply_receipt_id: null,
|
|
51515
|
+
created_at: input.now
|
|
51516
|
+
};
|
|
51517
|
+
const result = {
|
|
51518
|
+
duplicate: false,
|
|
51519
|
+
receipt,
|
|
51520
|
+
graph: input.graph,
|
|
51521
|
+
readback,
|
|
51522
|
+
outbox_ids: input.outbox.map((entry2) => entry2.id),
|
|
51523
|
+
result_digest: input.result_digest
|
|
51524
|
+
};
|
|
51525
|
+
const manifestJson = canonicalJson(manifest);
|
|
51526
|
+
const resultJson = canonicalJson(result);
|
|
51527
|
+
await tx.query(`INSERT INTO todos_task_manifest_receipts (
|
|
51528
|
+
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
|
|
51529
|
+
request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
|
|
51530
|
+
) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'apply', $3, $4, $5, $6, 1, NULL, $7::jsonb, $8::jsonb, $9)`, [
|
|
51531
|
+
input.receipt_id,
|
|
51532
|
+
this.tenantId,
|
|
51533
|
+
manifest.operation_id,
|
|
51534
|
+
manifest.idempotency_key,
|
|
51535
|
+
input.request_digest,
|
|
51536
|
+
input.result_digest,
|
|
51537
|
+
manifestJson,
|
|
51538
|
+
resultJson,
|
|
51539
|
+
input.now
|
|
51540
|
+
]);
|
|
51541
|
+
for (const entry2 of input.outbox) {
|
|
51542
|
+
await tx.query(`INSERT INTO todos_task_manifest_outbox (
|
|
51543
|
+
id, apply_receipt_id, topic, payload, payload_digest, status, created_at
|
|
51544
|
+
) VALUES ($1, $2, $3, $4::jsonb, $5, 'pending', $6)`, [
|
|
51545
|
+
entry2.id,
|
|
51546
|
+
input.receipt_id,
|
|
51547
|
+
entry2.topic,
|
|
51548
|
+
canonicalJson(entry2.payload),
|
|
51549
|
+
entry2.digest,
|
|
51550
|
+
input.now
|
|
51551
|
+
]);
|
|
51552
|
+
}
|
|
51553
|
+
fault(faults, "after_outbox_write");
|
|
51554
|
+
await tx.query(`INSERT INTO todos_task_manifest_bindings (
|
|
51555
|
+
operation_id, tenant_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
|
|
51556
|
+
manifest_json, result_json, state, version, created_at, updated_at
|
|
51557
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8::jsonb, 'applied', 1, $9, $9)`, [
|
|
51558
|
+
manifest.operation_id,
|
|
51559
|
+
this.tenantId,
|
|
51560
|
+
manifest.idempotency_key,
|
|
51561
|
+
input.request_digest,
|
|
51562
|
+
input.result_digest,
|
|
51563
|
+
input.receipt_id,
|
|
51564
|
+
manifestJson,
|
|
51565
|
+
resultJson,
|
|
51566
|
+
input.now
|
|
51567
|
+
]);
|
|
51568
|
+
fault(faults, "after_receipt_write");
|
|
51569
|
+
return result;
|
|
51570
|
+
});
|
|
51571
|
+
}
|
|
51572
|
+
async readExact(receiptId2) {
|
|
51573
|
+
await this.ensureSchema();
|
|
51574
|
+
const result = await this.client.query("SELECT result_json FROM todos_task_manifest_receipts WHERE tenant_id = $1 AND receipt_id = $2 AND kind = 'apply' LIMIT 1", [this.tenantId, receiptId2]);
|
|
51575
|
+
if (!result.rows[0])
|
|
51576
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", `Apply receipt not found: ${receiptId2}`);
|
|
51577
|
+
return { ...parseJson2(result.rows[0]["result_json"]), duplicate: false };
|
|
51578
|
+
}
|
|
51579
|
+
async lookupBindingByPlanId(planId) {
|
|
51580
|
+
await this.ensureSchema();
|
|
51581
|
+
const result = await this.client.query(`
|
|
51582
|
+
SELECT
|
|
51583
|
+
b.apply_receipt_id AS apply_receipt_id,
|
|
51584
|
+
b.state AS state,
|
|
51585
|
+
b.version AS binding_version,
|
|
51586
|
+
b.tenant_id AS binding_tenant_id,
|
|
51587
|
+
b.operation_id AS binding_operation_id,
|
|
51588
|
+
b.result_json #>> '{graph,plan_id}' AS binding_plan_id,
|
|
51589
|
+
r.tenant_id AS receipt_tenant_id,
|
|
51590
|
+
r.authority AS receipt_authority,
|
|
51591
|
+
r.route AS receipt_route,
|
|
51592
|
+
r.schema_version AS receipt_schema_version,
|
|
51593
|
+
r.kind AS receipt_kind,
|
|
51594
|
+
r.operation_id AS receipt_operation_id,
|
|
51595
|
+
r.result_json #>> '{graph,plan_id}' AS receipt_plan_id
|
|
51596
|
+
FROM todos_task_manifest_bindings b
|
|
51597
|
+
LEFT JOIN todos_task_manifest_receipts r
|
|
51598
|
+
ON r.receipt_id = b.apply_receipt_id
|
|
51599
|
+
AND r.tenant_id = b.tenant_id
|
|
51600
|
+
WHERE b.tenant_id = $1
|
|
51601
|
+
AND b.result_json #>> '{graph,plan_id}' = $2
|
|
51602
|
+
LIMIT 2
|
|
51603
|
+
`, [this.tenantId, planId]);
|
|
51604
|
+
return validateTaskManifestBindingLookupRows(result.rows, this.tenantId, planId);
|
|
51605
|
+
}
|
|
51606
|
+
async markOutboxDelivered(outboxId, deliveredAt) {
|
|
51607
|
+
await this.ensureSchema();
|
|
51608
|
+
await this.client.transaction(async (tx) => {
|
|
51609
|
+
const result = await tx.query(`UPDATE todos_task_manifest_outbox
|
|
51610
|
+
SET status = 'delivered', delivered_at = $1, attempts = attempts + 1
|
|
51611
|
+
WHERE id = $2 AND status = 'pending'
|
|
51612
|
+
AND EXISTS (
|
|
51613
|
+
SELECT 1 FROM todos_task_manifest_receipts r
|
|
51614
|
+
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
51615
|
+
AND r.tenant_id = $3
|
|
51616
|
+
)
|
|
51617
|
+
RETURNING id`, [deliveredAt, outboxId, this.tenantId]);
|
|
51618
|
+
if (!result.rows[0])
|
|
51619
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
|
|
51620
|
+
});
|
|
51621
|
+
}
|
|
51622
|
+
async compensate(input, receipt, compensationReceiptId, requestDigest, now4) {
|
|
51623
|
+
await this.ensureSchema();
|
|
51624
|
+
return this.client.transaction(async (tx) => {
|
|
51625
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${receipt.operation_id}`]);
|
|
51626
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1Fcompensation-idempotency\x1F${input.idempotency_key}`]);
|
|
51627
|
+
const previous = await tx.query(`SELECT apply_receipt_id, request_digest, result_json
|
|
51628
|
+
FROM todos_task_manifest_receipts
|
|
51629
|
+
WHERE tenant_id = $1 AND kind = 'compensate' AND idempotency_key = $2
|
|
51630
|
+
LIMIT 1`, [this.tenantId, input.idempotency_key]);
|
|
51631
|
+
if (previous.rows[0]) {
|
|
51632
|
+
const row = previous.rows[0];
|
|
51633
|
+
if (row["apply_receipt_id"] !== input.receipt_id || row["request_digest"] !== requestDigest) {
|
|
51634
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Compensation idempotency key is already used");
|
|
51635
|
+
}
|
|
51636
|
+
return { ...parseJson2(row["result_json"]), duplicate: true };
|
|
51637
|
+
}
|
|
51638
|
+
const applyRows = await tx.query("SELECT * FROM todos_task_manifest_receipts WHERE tenant_id = $1 AND receipt_id = $2 AND kind = 'apply' LIMIT 1", [this.tenantId, input.receipt_id]);
|
|
51639
|
+
const applyRow = applyRows.rows[0];
|
|
51640
|
+
if (!applyRow)
|
|
51641
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", "Apply receipt not found");
|
|
51642
|
+
const bindingRows = await tx.query("SELECT * FROM todos_task_manifest_bindings WHERE tenant_id = $1 AND operation_id = $2 LIMIT 1 FOR UPDATE", [this.tenantId, receipt.operation_id]);
|
|
51643
|
+
const binding = bindingRows.rows[0];
|
|
51644
|
+
if (!binding || Number(binding["version"]) !== input.if_binding_version) {
|
|
51645
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding version changed before compensation");
|
|
51646
|
+
}
|
|
51647
|
+
if (binding["state"] !== "applied")
|
|
51648
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not applied");
|
|
51649
|
+
const delivered = await tx.query(`SELECT o.id FROM todos_task_manifest_outbox o
|
|
51650
|
+
JOIN todos_task_manifest_receipts r ON r.receipt_id = o.apply_receipt_id
|
|
51651
|
+
WHERE r.tenant_id = $1 AND o.apply_receipt_id = $2 AND o.status = 'delivered'
|
|
51652
|
+
LIMIT 1`, [this.tenantId, input.receipt_id]);
|
|
51653
|
+
if (delivered.rows[0])
|
|
51654
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
|
|
51655
|
+
const applyResult = parseJson2(applyRow["result_json"]);
|
|
51656
|
+
const manifest = parseJson2(applyRow["manifest_json"]);
|
|
51657
|
+
const expectedEffects = [
|
|
51658
|
+
{
|
|
51659
|
+
topic: "todos.task-manifest.applied",
|
|
51660
|
+
payload: { operation_id: manifest.operation_id, project_id: manifest.project_id }
|
|
51661
|
+
},
|
|
51662
|
+
...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
|
|
51663
|
+
];
|
|
51664
|
+
const outboxRows = await tx.query(`SELECT id, topic, payload, payload_digest, status, attempts, delivered_at
|
|
51665
|
+
FROM todos_task_manifest_outbox
|
|
51666
|
+
WHERE apply_receipt_id = $1
|
|
51667
|
+
AND EXISTS (
|
|
51668
|
+
SELECT 1 FROM todos_task_manifest_receipts r
|
|
51669
|
+
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
51670
|
+
AND r.tenant_id = $2
|
|
51671
|
+
)
|
|
51672
|
+
ORDER BY id`, [input.receipt_id, this.tenantId]);
|
|
51673
|
+
if (outboxRows.rows.length !== expectedEffects.length) {
|
|
51674
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
|
|
51675
|
+
}
|
|
51676
|
+
const outboxById = new Map(outboxRows.rows.map((entry2) => [String(entry2["id"]), entry2]));
|
|
51677
|
+
for (const [index, expectedEffect] of expectedEffects.entries()) {
|
|
51678
|
+
const stored2 = outboxById.get(applyResult.outbox_ids[index]);
|
|
51679
|
+
if (!stored2 || stored2["topic"] !== expectedEffect.topic || canonicalJson(parseJson2(stored2["payload"])) !== canonicalJson(expectedEffect.payload) || stored2["payload_digest"] !== canonicalDigest(expectedEffect) || stored2["status"] !== "pending" || Number(stored2["attempts"]) !== 0 || stored2["delivered_at"] != null) {
|
|
51680
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
|
|
51681
|
+
}
|
|
51682
|
+
}
|
|
51683
|
+
const taskIds = Object.values(applyResult.graph.task_ids);
|
|
51684
|
+
const foreignReference = await tx.query(postgresTaskManifestForeignReferenceSql(this.tableName), [
|
|
51685
|
+
this.service,
|
|
51686
|
+
applyResult.graph.plan_id,
|
|
51687
|
+
taskIds,
|
|
51688
|
+
applyResult.graph.dependency_ids,
|
|
51689
|
+
applyResult.graph.comment_ids,
|
|
51690
|
+
applyResult.graph.verification_ids,
|
|
51691
|
+
[applyResult.graph.plan_id, ...taskIds]
|
|
51692
|
+
]);
|
|
51693
|
+
if (foreignReference.rows[0]) {
|
|
51694
|
+
const row = foreignReference.rows[0];
|
|
51695
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", `Compensation refused: foreign reference in ${String(row["object_type"])}:${String(row["object_id"])} would be changed`, row);
|
|
51696
|
+
}
|
|
51697
|
+
const actualReadback = await this.readback(tx, applyResult.graph);
|
|
51698
|
+
if (canonicalJson(actualReadback) !== canonicalJson(applyResult.readback)) {
|
|
51699
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: graph changed since apply");
|
|
51700
|
+
}
|
|
51701
|
+
const appliedAt = receiptFromRow3(applyRow).created_at;
|
|
51702
|
+
const expectedPayloads = new Map;
|
|
51703
|
+
expectedPayloads.set(applyResult.graph.plan_id, {
|
|
51704
|
+
type: "plans",
|
|
51705
|
+
payload: canonicalJson(planPayload({ manifest, graph: applyResult.graph, now: appliedAt }))
|
|
51706
|
+
});
|
|
51707
|
+
for (const task3 of manifest.tasks)
|
|
51708
|
+
expectedPayloads.set(applyResult.graph.task_ids[task3.key], {
|
|
51709
|
+
type: "tasks",
|
|
51710
|
+
payload: canonicalJson(taskPayload2(manifest, task3, applyResult.graph.task_ids[task3.key], applyResult.graph.plan_id, appliedAt))
|
|
51711
|
+
});
|
|
51712
|
+
for (const [index, edge] of (manifest.dependencies ?? []).entries())
|
|
51713
|
+
expectedPayloads.set(applyResult.graph.dependency_ids[index], {
|
|
51714
|
+
type: "dependencies",
|
|
51715
|
+
payload: canonicalJson({
|
|
51716
|
+
id: applyResult.graph.dependency_ids[index],
|
|
51717
|
+
task_id: applyResult.graph.task_ids[edge.task],
|
|
51718
|
+
depends_on: applyResult.graph.task_ids[edge.depends_on],
|
|
51719
|
+
created_at: appliedAt,
|
|
51720
|
+
updated_at: appliedAt
|
|
51721
|
+
})
|
|
51722
|
+
});
|
|
51723
|
+
let expectedCommentIndex = 0;
|
|
51724
|
+
for (const task3 of manifest.tasks)
|
|
51725
|
+
for (const comment2 of task3.comments ?? []) {
|
|
51726
|
+
const id = applyResult.graph.comment_ids[expectedCommentIndex++];
|
|
51727
|
+
expectedPayloads.set(id, {
|
|
51728
|
+
type: "comments",
|
|
51729
|
+
payload: canonicalJson({
|
|
51730
|
+
id,
|
|
51731
|
+
task_id: applyResult.graph.task_ids[task3.key],
|
|
51732
|
+
agent_id: comment2.agent_id ?? null,
|
|
51733
|
+
session_id: comment2.session_id ?? null,
|
|
51734
|
+
content: comment2.content,
|
|
51735
|
+
type: comment2.type ?? "comment",
|
|
51736
|
+
progress_pct: comment2.progress_pct ?? null,
|
|
51737
|
+
created_at: appliedAt
|
|
51738
|
+
})
|
|
51739
|
+
});
|
|
51740
|
+
}
|
|
51741
|
+
let expectedVerificationIndex = 0;
|
|
51742
|
+
for (const task3 of manifest.tasks)
|
|
51743
|
+
for (const verification2 of task3.verifications ?? []) {
|
|
51744
|
+
const id = applyResult.graph.verification_ids[expectedVerificationIndex++];
|
|
51745
|
+
expectedPayloads.set(id, {
|
|
51746
|
+
type: "verifications",
|
|
51747
|
+
payload: canonicalJson({
|
|
51748
|
+
id,
|
|
51749
|
+
task_id: applyResult.graph.task_ids[task3.key],
|
|
51750
|
+
command: verification2.command,
|
|
51751
|
+
status: verification2.status ?? "unknown",
|
|
51752
|
+
output_summary: verification2.output_summary ?? null,
|
|
51753
|
+
artifact_path: verification2.artifact_path ?? null,
|
|
51754
|
+
agent_id: verification2.agent_id ?? null,
|
|
51755
|
+
run_at: appliedAt,
|
|
51756
|
+
created_at: appliedAt,
|
|
51757
|
+
updated_at: appliedAt
|
|
51758
|
+
})
|
|
51759
|
+
});
|
|
51760
|
+
}
|
|
51761
|
+
const managedIds = [...expectedPayloads.keys()];
|
|
51762
|
+
const stored = await tx.query(`SELECT object_type, object_id, payload FROM ${this.tableName}
|
|
51763
|
+
WHERE service = $1 AND object_id IN (${placeholders2(2, managedIds.length)})`, [this.service, ...managedIds]);
|
|
51764
|
+
for (const row of stored.rows) {
|
|
51765
|
+
const expected = expectedPayloads.get(String(row["object_id"]));
|
|
51766
|
+
if (!expected || expected.type !== row["object_type"] || expected.payload !== canonicalJson(parseJson2(row["payload"]))) {
|
|
51767
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: managed graph values changed");
|
|
51768
|
+
}
|
|
51769
|
+
}
|
|
51770
|
+
if (stored.rows.length !== managedIds.length)
|
|
51771
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: managed graph is incomplete");
|
|
51772
|
+
await tx.query(`UPDATE todos_task_manifest_outbox
|
|
51773
|
+
SET status = 'cancelled'
|
|
51774
|
+
WHERE apply_receipt_id = $1 AND status = 'pending'
|
|
51775
|
+
AND EXISTS (
|
|
51776
|
+
SELECT 1 FROM todos_task_manifest_receipts r
|
|
51777
|
+
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
51778
|
+
AND r.tenant_id = $2
|
|
51779
|
+
)`, [input.receipt_id, this.tenantId]);
|
|
51780
|
+
const typedIds = [
|
|
51781
|
+
["dependencies", applyResult.graph.dependency_ids],
|
|
51782
|
+
["comments", applyResult.graph.comment_ids],
|
|
51783
|
+
["verifications", applyResult.graph.verification_ids],
|
|
51784
|
+
["tasks", taskIds],
|
|
51785
|
+
["plans", [applyResult.graph.plan_id]]
|
|
51786
|
+
];
|
|
51787
|
+
for (const [objectType2, ids2] of typedIds) {
|
|
51788
|
+
if (!ids2.length)
|
|
51789
|
+
continue;
|
|
51790
|
+
await tx.query(`DELETE FROM ${this.tableName} WHERE service = $1 AND object_type = $2
|
|
51791
|
+
AND object_id IN (${placeholders2(3, ids2.length)})`, [this.service, objectType2, ...ids2]);
|
|
51792
|
+
}
|
|
51793
|
+
const readback = await this.readback(tx, applyResult.graph);
|
|
51794
|
+
const result = { duplicate: false, receipt, absent: true, readback };
|
|
51795
|
+
await tx.query(`INSERT INTO todos_task_manifest_receipts (
|
|
51796
|
+
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
|
|
51797
|
+
request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
|
|
51798
|
+
) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'compensate', $3, $4, $5, $6, $7, $8, NULL, $9::jsonb, $10)`, [
|
|
51799
|
+
compensationReceiptId,
|
|
51800
|
+
this.tenantId,
|
|
51801
|
+
receipt.operation_id,
|
|
51802
|
+
input.idempotency_key,
|
|
51803
|
+
requestDigest,
|
|
51804
|
+
receipt.result_digest,
|
|
51805
|
+
receipt.binding_version,
|
|
51806
|
+
input.receipt_id,
|
|
51807
|
+
canonicalJson(result),
|
|
51808
|
+
now4
|
|
51809
|
+
]);
|
|
51810
|
+
const updated = await tx.query(`UPDATE todos_task_manifest_bindings
|
|
51811
|
+
SET state = 'compensated', version = $1, compensation_receipt_id = $2, updated_at = $3
|
|
51812
|
+
WHERE tenant_id = $4 AND operation_id = $5 AND state = 'applied' AND version = $6 RETURNING operation_id`, [
|
|
51813
|
+
receipt.binding_version,
|
|
51814
|
+
compensationReceiptId,
|
|
51815
|
+
now4,
|
|
51816
|
+
this.tenantId,
|
|
51817
|
+
receipt.operation_id,
|
|
51818
|
+
input.if_binding_version
|
|
51819
|
+
]);
|
|
51820
|
+
if (!updated.rows[0])
|
|
51821
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding changed during compensation");
|
|
51822
|
+
return result;
|
|
51823
|
+
});
|
|
51824
|
+
}
|
|
51825
|
+
async readback(tx, graph) {
|
|
51826
|
+
const count2 = async (objectType2, ids2) => {
|
|
51827
|
+
if (!ids2.length)
|
|
51828
|
+
return 0;
|
|
51829
|
+
const result = await tx.query(`SELECT count(*) AS count FROM ${this.tableName}
|
|
51830
|
+
WHERE service = $1 AND object_type = $2 AND deleted_at IS NULL
|
|
51831
|
+
AND object_id IN (${placeholders2(3, ids2.length)})`, [this.service, objectType2, ...ids2]);
|
|
51832
|
+
return Number(result.rows[0]?.count ?? 0);
|
|
51833
|
+
};
|
|
51834
|
+
return {
|
|
51835
|
+
plans: await count2("plans", [graph.plan_id]),
|
|
51836
|
+
tasks: await count2("tasks", Object.values(graph.task_ids)),
|
|
51837
|
+
dependencies: await count2("dependencies", graph.dependency_ids),
|
|
51838
|
+
comments: await count2("comments", graph.comment_ids),
|
|
51839
|
+
verifications: await count2("verifications", graph.verification_ids),
|
|
51840
|
+
complete: true
|
|
51841
|
+
};
|
|
51842
|
+
}
|
|
51843
|
+
}
|
|
51844
|
+
var init_postgres3 = __esm(() => {
|
|
51845
|
+
init_canonical();
|
|
51846
|
+
init_backend();
|
|
51847
|
+
init_postgres_sync();
|
|
51848
|
+
init_types5();
|
|
51849
|
+
});
|
|
51850
|
+
|
|
51851
|
+
// src/task-manifest/authority.ts
|
|
51852
|
+
function resolveTenantId(value) {
|
|
51853
|
+
const tenantId = value ?? "default";
|
|
51854
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(tenantId)) {
|
|
51855
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "tenantId must be a bounded exact authority identifier");
|
|
51856
|
+
}
|
|
51857
|
+
return tenantId;
|
|
51858
|
+
}
|
|
51859
|
+
function normalize(input, now4) {
|
|
51860
|
+
const parsed = parseTodosTaskManifest(input);
|
|
51861
|
+
const requestBytes = Buffer.byteLength(canonicalJson(parsed), "utf8");
|
|
51862
|
+
if (requestBytes > TODOS_TASK_MANIFEST_BOUNDS.request_bytes) {
|
|
51863
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", `Task manifest requires ${requestBytes} bytes but the bound is ${TODOS_TASK_MANIFEST_BOUNDS.request_bytes}`, { request_bytes: requestBytes, request_byte_limit: TODOS_TASK_MANIFEST_BOUNDS.request_bytes });
|
|
51864
|
+
}
|
|
51865
|
+
const manifest = sanitizeManifest(parsed);
|
|
51866
|
+
const task_ids = Object.fromEntries(manifest.tasks.map((task3) => [
|
|
51867
|
+
task3.key,
|
|
51868
|
+
deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "task", task3.key)
|
|
51869
|
+
]));
|
|
51870
|
+
const graph = {
|
|
51871
|
+
plan_id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "plan", manifest.plan.key),
|
|
51872
|
+
task_ids,
|
|
51873
|
+
comment_ids: manifest.tasks.flatMap((task3) => (task3.comments ?? []).map((_, index) => deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "comment", task3.key, String(index)))),
|
|
51874
|
+
verification_ids: manifest.tasks.flatMap((task3) => (task3.verifications ?? []).map((_, index) => deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "verification", task3.key, String(index)))),
|
|
51875
|
+
dependency_ids: (manifest.dependencies ?? []).map((edge) => `${task_ids[edge.task]}::${task_ids[edge.depends_on]}`)
|
|
51876
|
+
};
|
|
51877
|
+
const request_digest = canonicalDigest(parsed);
|
|
51878
|
+
const effectInputs = [
|
|
51879
|
+
{
|
|
51880
|
+
topic: "todos.task-manifest.applied",
|
|
51881
|
+
payload: { operation_id: manifest.operation_id, project_id: manifest.project_id }
|
|
51882
|
+
},
|
|
51883
|
+
...manifest.effects ?? []
|
|
51884
|
+
];
|
|
51885
|
+
const outbox = effectInputs.map((effect2, index) => {
|
|
51886
|
+
const payload = { ...effect2.payload };
|
|
51887
|
+
return {
|
|
51888
|
+
id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "outbox", String(index)),
|
|
51889
|
+
topic: effect2.topic,
|
|
51890
|
+
payload,
|
|
51891
|
+
digest: canonicalDigest({ topic: effect2.topic, payload })
|
|
51892
|
+
};
|
|
51893
|
+
});
|
|
51894
|
+
const result_digest = canonicalDigest({ manifest, graph, outbox });
|
|
51895
|
+
return {
|
|
51896
|
+
manifest,
|
|
51897
|
+
request_digest,
|
|
51898
|
+
result_digest,
|
|
51899
|
+
receipt_id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, "apply", manifest.operation_id, manifest.idempotency_key, request_digest),
|
|
51900
|
+
graph,
|
|
51901
|
+
outbox,
|
|
51902
|
+
now: now4
|
|
51903
|
+
};
|
|
51904
|
+
}
|
|
51905
|
+
function sanitizeManifest(manifest) {
|
|
51906
|
+
return {
|
|
51907
|
+
...manifest,
|
|
51908
|
+
plan: {
|
|
51909
|
+
...manifest.plan,
|
|
51910
|
+
name: sanitizePreWriteText(manifest.plan.name, "task_manifest.plan.name"),
|
|
51911
|
+
...manifest.plan.description !== undefined ? { description: sanitizePreWriteText(manifest.plan.description, "task_manifest.plan.description") } : {}
|
|
51912
|
+
},
|
|
51913
|
+
tasks: manifest.tasks.map((task3) => ({
|
|
51914
|
+
...task3,
|
|
51915
|
+
title: sanitizePreWriteText(task3.title, `task_manifest.tasks.${task3.key}.title`),
|
|
51916
|
+
...task3.description !== undefined ? { description: sanitizePreWriteText(task3.description, `task_manifest.tasks.${task3.key}.description`) } : {},
|
|
51917
|
+
...task3.tags !== undefined ? { tags: sanitizePreWriteValue(task3.tags, `task_manifest.tasks.${task3.key}.tags`) } : {},
|
|
51918
|
+
...task3.metadata !== undefined ? { metadata: sanitizePreWriteValue(task3.metadata, `task_manifest.tasks.${task3.key}.metadata`) } : {},
|
|
51919
|
+
...task3.comments !== undefined ? {
|
|
51920
|
+
comments: task3.comments.map((comment2, index) => ({
|
|
51921
|
+
...comment2,
|
|
51922
|
+
content: sanitizePreWriteText(comment2.content, `task_manifest.tasks.${task3.key}.comments.${index}.content`)
|
|
51923
|
+
}))
|
|
51924
|
+
} : {},
|
|
51925
|
+
...task3.verifications !== undefined ? {
|
|
51926
|
+
verifications: task3.verifications.map((verification2, index) => ({
|
|
51927
|
+
...verification2,
|
|
51928
|
+
command: sanitizePreWriteText(verification2.command, `task_manifest.tasks.${task3.key}.verifications.${index}.command`),
|
|
51929
|
+
...verification2.output_summary !== undefined ? { output_summary: sanitizePreWriteText(verification2.output_summary, `task_manifest.tasks.${task3.key}.verifications.${index}.output_summary`) } : {},
|
|
51930
|
+
...verification2.artifact_path !== undefined ? { artifact_path: sanitizePreWriteText(verification2.artifact_path, `task_manifest.tasks.${task3.key}.verifications.${index}.artifact_path`) } : {}
|
|
51931
|
+
}))
|
|
51932
|
+
} : {}
|
|
51933
|
+
})),
|
|
51934
|
+
...manifest.effects !== undefined ? {
|
|
51935
|
+
effects: manifest.effects.map((effect2, index) => ({
|
|
51936
|
+
topic: sanitizePreWriteText(effect2.topic, `task_manifest.effects.${index}.topic`),
|
|
51937
|
+
payload: sanitizePreWriteValue(effect2.payload, `task_manifest.effects.${index}.payload`)
|
|
51938
|
+
}))
|
|
51939
|
+
} : {}
|
|
51940
|
+
};
|
|
51941
|
+
}
|
|
51942
|
+
|
|
51943
|
+
class PackageOwnedTodosTaskManifestAuthority {
|
|
51944
|
+
backend;
|
|
51945
|
+
options;
|
|
51946
|
+
tenantId;
|
|
51947
|
+
constructor(backend, options = {}) {
|
|
51948
|
+
this.backend = backend;
|
|
51949
|
+
this.options = options;
|
|
51950
|
+
this.tenantId = resolveTenantId(options.tenantId);
|
|
51951
|
+
}
|
|
51952
|
+
async capability() {
|
|
51953
|
+
return {
|
|
51954
|
+
authority: "todos",
|
|
51955
|
+
route: TODOS_TASK_MANIFEST_ROUTE,
|
|
51956
|
+
schema_version: TODOS_TASK_MANIFEST_SCHEMA_VERSION,
|
|
51957
|
+
tenant_id: this.tenantId,
|
|
51958
|
+
backend: this.backend.kind,
|
|
51959
|
+
deterministic_ids: true,
|
|
51960
|
+
immutable_receipts: true,
|
|
51961
|
+
transactional_outbox: true,
|
|
51962
|
+
exact_bounded_readback: true,
|
|
51963
|
+
conditional_compensation: true,
|
|
51964
|
+
transcript_safe: false,
|
|
51965
|
+
bounds: { ...TODOS_TASK_MANIFEST_BOUNDS }
|
|
51966
|
+
};
|
|
51967
|
+
}
|
|
51968
|
+
now() {
|
|
51969
|
+
const value = this.options.now?.() ?? new Date().toISOString();
|
|
51970
|
+
if (!Number.isFinite(Date.parse(value))) {
|
|
51971
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "now() returned an invalid timestamp");
|
|
51972
|
+
}
|
|
51973
|
+
return value;
|
|
51974
|
+
}
|
|
51975
|
+
async prepareFaults() {
|
|
51976
|
+
const points = new Set;
|
|
51977
|
+
if (this.options.faultInjector) {
|
|
51978
|
+
for (const point of FAULT_POINTS) {
|
|
51979
|
+
if (await this.options.faultInjector(point) === true)
|
|
51980
|
+
points.add(point);
|
|
51981
|
+
}
|
|
51982
|
+
}
|
|
51983
|
+
return { points };
|
|
51984
|
+
}
|
|
51985
|
+
async apply(input) {
|
|
51986
|
+
const normalized = normalize(input, this.now());
|
|
51987
|
+
const faults = await this.prepareFaults();
|
|
51988
|
+
return this.bounded(await this.backend.apply(normalized, faults));
|
|
51989
|
+
}
|
|
51990
|
+
readExact(receiptId2) {
|
|
51991
|
+
if (!receiptId2 || receiptId2.length > 200) {
|
|
51992
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "receiptId must be a bounded exact identifier");
|
|
51993
|
+
}
|
|
51994
|
+
return this.backend.readExact(receiptId2).then((result) => this.bounded(result));
|
|
51995
|
+
}
|
|
51996
|
+
async lookupBinding(input) {
|
|
51997
|
+
const request = parseTodosTaskManifestBindingLookup(input);
|
|
51998
|
+
if (request.max_items !== 1) {
|
|
51999
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", "max_items must be exactly 1 for task-manifest binding lookup", { max_items: request.max_items, max_items_limit: 1 });
|
|
52000
|
+
}
|
|
52001
|
+
if (request.authority !== "todos" || request.route !== TODOS_TASK_MANIFEST_ROUTE || request.schema_version !== TODOS_TASK_MANIFEST_SCHEMA_VERSION || request.tenant_id !== this.tenantId) {
|
|
52002
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAPABILITY_MISMATCH", "Task-manifest binding lookup does not match this authority identity");
|
|
52003
|
+
}
|
|
52004
|
+
return this.bounded({
|
|
52005
|
+
authority: "todos",
|
|
52006
|
+
route: TODOS_TASK_MANIFEST_ROUTE,
|
|
52007
|
+
schema_version: TODOS_TASK_MANIFEST_SCHEMA_VERSION,
|
|
52008
|
+
tenant_id: this.tenantId,
|
|
52009
|
+
...await this.backend.lookupBindingByPlanId(request.plan_id)
|
|
52010
|
+
});
|
|
52011
|
+
}
|
|
52012
|
+
markOutboxDelivered(outboxId) {
|
|
52013
|
+
if (!outboxId || outboxId.length > 200) {
|
|
52014
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "outboxId must be a bounded exact identifier");
|
|
52015
|
+
}
|
|
52016
|
+
return this.backend.markOutboxDelivered(outboxId, this.now());
|
|
52017
|
+
}
|
|
52018
|
+
async compensate(input) {
|
|
52019
|
+
const request = parseTodosTaskManifestCompensation(input);
|
|
52020
|
+
const applied = await this.backend.readExact(request.receipt_id);
|
|
52021
|
+
const requestDigest = canonicalDigest(request);
|
|
52022
|
+
const compensationReceiptId = deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, "compensate", applied.receipt.operation_id, request.idempotency_key, requestDigest);
|
|
52023
|
+
const receipt = {
|
|
52024
|
+
receipt_id: compensationReceiptId,
|
|
52025
|
+
authority: "todos",
|
|
52026
|
+
route: TODOS_TASK_MANIFEST_ROUTE,
|
|
52027
|
+
schema_version: 1,
|
|
52028
|
+
kind: "compensate",
|
|
52029
|
+
operation_id: applied.receipt.operation_id,
|
|
52030
|
+
idempotency_key: request.idempotency_key,
|
|
52031
|
+
request_digest: requestDigest,
|
|
52032
|
+
result_digest: canonicalDigest({ absent: true, apply_receipt_id: applied.receipt.receipt_id }),
|
|
52033
|
+
binding_version: request.if_binding_version + 1,
|
|
52034
|
+
apply_receipt_id: applied.receipt.receipt_id,
|
|
52035
|
+
created_at: this.now()
|
|
52036
|
+
};
|
|
52037
|
+
return this.bounded(await this.backend.compensate(request, receipt, compensationReceiptId, requestDigest, receipt.created_at));
|
|
52038
|
+
}
|
|
52039
|
+
bounded(result) {
|
|
52040
|
+
const responseBytes2 = Buffer.byteLength(canonicalJson(result), "utf8");
|
|
52041
|
+
if (responseBytes2 > TODOS_TASK_MANIFEST_BOUNDS.response_bytes) {
|
|
52042
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", `Task-manifest response requires ${responseBytes2} bytes but the bound is ${TODOS_TASK_MANIFEST_BOUNDS.response_bytes}`, { response_bytes: responseBytes2, response_byte_limit: TODOS_TASK_MANIFEST_BOUNDS.response_bytes });
|
|
52043
|
+
}
|
|
52044
|
+
return result;
|
|
52045
|
+
}
|
|
52046
|
+
}
|
|
52047
|
+
function createPostgresTodosTaskManifestAuthority(client, options = {}) {
|
|
52048
|
+
if (!client || typeof client.transaction !== "function") {
|
|
52049
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE", "An authoritative PostgreSQL transaction(callback) client is required");
|
|
52050
|
+
}
|
|
52051
|
+
const tenantId = resolveTenantId(options.tenantId);
|
|
52052
|
+
return new PackageOwnedTodosTaskManifestAuthority(new PostgresTodosTaskManifestBackend(client, { ...options, tenantId }), { ...options, tenantId });
|
|
52053
|
+
}
|
|
52054
|
+
var FAULT_POINTS;
|
|
52055
|
+
var init_authority2 = __esm(() => {
|
|
52056
|
+
init_canonical();
|
|
52057
|
+
init_schema2();
|
|
52058
|
+
init_sqlite2();
|
|
52059
|
+
init_postgres3();
|
|
52060
|
+
init_prewrite_secrets();
|
|
52061
|
+
init_types5();
|
|
52062
|
+
init_schema2();
|
|
52063
|
+
FAULT_POINTS = [
|
|
52064
|
+
"after_plan_write",
|
|
52065
|
+
"after_task_write",
|
|
52066
|
+
"after_dependency_write",
|
|
52067
|
+
"after_comment_write",
|
|
52068
|
+
"after_verification_write",
|
|
52069
|
+
"after_outbox_write",
|
|
52070
|
+
"after_receipt_write"
|
|
52071
|
+
];
|
|
52072
|
+
});
|
|
52073
|
+
|
|
52074
|
+
// src/task-manifest/http.ts
|
|
52075
|
+
function json2(body, status = 200) {
|
|
52076
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS2 });
|
|
52077
|
+
}
|
|
52078
|
+
function status(error) {
|
|
52079
|
+
switch (error.code) {
|
|
52080
|
+
case "TODOS_TASK_MANIFEST_INVALID_INPUT":
|
|
52081
|
+
case "TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED":
|
|
52082
|
+
case "TODOS_TASK_MANIFEST_FOREIGN_REFERENCE":
|
|
52083
|
+
return 400;
|
|
52084
|
+
case "TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND":
|
|
52085
|
+
case "TODOS_TASK_MANIFEST_BINDING_NOT_FOUND":
|
|
52086
|
+
return 404;
|
|
52087
|
+
case "TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE":
|
|
52088
|
+
return 503;
|
|
52089
|
+
default:
|
|
52090
|
+
return 409;
|
|
52091
|
+
}
|
|
52092
|
+
}
|
|
52093
|
+
async function body(request) {
|
|
52094
|
+
const declaredLength = Number(request.headers.get("content-length") ?? 0);
|
|
52095
|
+
if (Number.isFinite(declaredLength) && declaredLength > TODOS_TASK_MANIFEST_BOUNDS.request_bytes) {
|
|
52096
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", "Task-manifest HTTP body exceeds the request bound");
|
|
52097
|
+
}
|
|
52098
|
+
const text2 = await boundedText(request, TODOS_TASK_MANIFEST_BOUNDS.request_bytes, "request");
|
|
52099
|
+
try {
|
|
52100
|
+
return JSON.parse(text2);
|
|
52101
|
+
} catch {
|
|
52102
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "Invalid JSON body");
|
|
52103
|
+
}
|
|
52104
|
+
}
|
|
52105
|
+
async function boundedText(message, limit, label) {
|
|
52106
|
+
if (!message.body)
|
|
52107
|
+
return "";
|
|
52108
|
+
const reader = message.body.getReader();
|
|
52109
|
+
const decoder = new TextDecoder;
|
|
52110
|
+
let bytes = 0;
|
|
52111
|
+
let text2 = "";
|
|
52112
|
+
try {
|
|
52113
|
+
while (true) {
|
|
52114
|
+
const { done, value } = await reader.read();
|
|
52115
|
+
if (done)
|
|
52116
|
+
break;
|
|
52117
|
+
bytes += value.byteLength;
|
|
52118
|
+
if (bytes > limit) {
|
|
52119
|
+
await reader.cancel();
|
|
52120
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", `Task-manifest HTTP ${label} exceeds the byte bound`);
|
|
52121
|
+
}
|
|
52122
|
+
text2 += decoder.decode(value, { stream: true });
|
|
52123
|
+
}
|
|
52124
|
+
return text2 + decoder.decode();
|
|
52125
|
+
} finally {
|
|
52126
|
+
reader.releaseLock();
|
|
52127
|
+
}
|
|
52128
|
+
}
|
|
52129
|
+
async function handleTodosTaskManifestHttpRequest(request, url, authority, basePath = "/v1/task-manifest") {
|
|
52130
|
+
if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`))
|
|
52131
|
+
return null;
|
|
52132
|
+
const action = url.pathname.slice(basePath.length).split("/").filter(Boolean).join("/");
|
|
52133
|
+
try {
|
|
52134
|
+
if ((action === "" || action === "capability") && request.method === "GET") {
|
|
52135
|
+
return json2({ capability: await authority.capability() });
|
|
52136
|
+
}
|
|
52137
|
+
if (request.method !== "POST")
|
|
52138
|
+
return json2({ error: "method not allowed" }, 405);
|
|
52139
|
+
if (action === "apply")
|
|
52140
|
+
return json2({ result: await authority.apply(await body(request)) }, 201);
|
|
52141
|
+
if (action === "read-exact") {
|
|
52142
|
+
const input = await body(request);
|
|
52143
|
+
if (!input || typeof input.receipt_id !== "string") {
|
|
52144
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "receipt_id is required");
|
|
52145
|
+
}
|
|
52146
|
+
return json2({ result: await authority.readExact(input.receipt_id) });
|
|
52147
|
+
}
|
|
52148
|
+
if (action === "bindings/lookup") {
|
|
52149
|
+
return json2({
|
|
52150
|
+
result: await authority.lookupBinding(await body(request))
|
|
52151
|
+
});
|
|
52152
|
+
}
|
|
52153
|
+
if (action === "compensate") {
|
|
52154
|
+
return json2({ result: await authority.compensate(await body(request)) }, 201);
|
|
52155
|
+
}
|
|
52156
|
+
if (action === "outbox/delivered") {
|
|
52157
|
+
const input = await body(request);
|
|
52158
|
+
if (!input || typeof input.outbox_id !== "string") {
|
|
52159
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "outbox_id is required");
|
|
52160
|
+
}
|
|
52161
|
+
await authority.markOutboxDelivered(input.outbox_id);
|
|
52162
|
+
return json2({ delivered: true });
|
|
52163
|
+
}
|
|
52164
|
+
return json2({ error: "unknown task-manifest route" }, 404);
|
|
52165
|
+
} catch (cause) {
|
|
52166
|
+
if (cause instanceof TodosTaskManifestError) {
|
|
52167
|
+
return json2({ error: cause.message, code: cause.code, details: cause.details, authoritative: true }, status(cause));
|
|
52168
|
+
}
|
|
52169
|
+
return json2({ error: cause instanceof Error ? cause.message : "task-manifest error" }, 500);
|
|
52170
|
+
}
|
|
52171
|
+
}
|
|
52172
|
+
var JSON_HEADERS2;
|
|
52173
|
+
var init_http3 = __esm(() => {
|
|
52174
|
+
init_types5();
|
|
52175
|
+
init_schema2();
|
|
52176
|
+
JSON_HEADERS2 = { "Content-Type": "application/json" };
|
|
52177
|
+
});
|
|
52178
|
+
|
|
52179
|
+
// src/task-manifest/index.ts
|
|
52180
|
+
var init_task_manifest = __esm(() => {
|
|
52181
|
+
init_authority2();
|
|
52182
|
+
init_postgres3();
|
|
52183
|
+
init_sqlite2();
|
|
52184
|
+
init_http3();
|
|
52185
|
+
init_canonical();
|
|
52186
|
+
init_schema2();
|
|
52187
|
+
init_types5();
|
|
52188
|
+
});
|
|
52189
|
+
|
|
50915
52190
|
// src/storage/comment-redaction-backfill.ts
|
|
50916
52191
|
function assertSafeIdentifier2(value) {
|
|
50917
52192
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
@@ -50995,12 +52270,12 @@ async function backfillPostgresCommentRedaction(client, options = {}) {
|
|
|
50995
52270
|
result.remaining_candidates = result.candidates;
|
|
50996
52271
|
return result;
|
|
50997
52272
|
}
|
|
50998
|
-
const
|
|
52273
|
+
const verification2 = await backfillPostgresCommentRedaction(client, {
|
|
50999
52274
|
...options,
|
|
51000
52275
|
apply: false,
|
|
51001
52276
|
confirmation: undefined
|
|
51002
52277
|
});
|
|
51003
|
-
result.remaining_candidates =
|
|
52278
|
+
result.remaining_candidates = verification2.candidates;
|
|
51004
52279
|
return result;
|
|
51005
52280
|
}
|
|
51006
52281
|
var COMMENT_REDACTION_BACKFILL_CONFIRMATION = "REDACT_STORED_TODOS_COMMENTS";
|
|
@@ -51019,6 +52294,7 @@ __export(exports_cloud, {
|
|
|
51019
52294
|
isPostgresBackendConfigured: () => isPostgresBackendConfigured,
|
|
51020
52295
|
isCloudModeEnabled: () => isCloudModeEnabled,
|
|
51021
52296
|
getCloudVerifier: () => getCloudVerifier,
|
|
52297
|
+
getCloudTaskManifestAuthority: () => getCloudTaskManifestAuthority,
|
|
51022
52298
|
getCloudStorageAdapter: () => getCloudStorageAdapter,
|
|
51023
52299
|
getCloudProjectRegistrationAuthority: () => getCloudProjectRegistrationAuthority,
|
|
51024
52300
|
getCloudPrGroupLedger: () => getCloudPrGroupLedger,
|
|
@@ -51046,6 +52322,9 @@ function isPostgresBackendConfigured(env = process.env) {
|
|
|
51046
52322
|
function isCloudModeEnabled(env = process.env) {
|
|
51047
52323
|
return isPostgresBackendConfigured(env);
|
|
51048
52324
|
}
|
|
52325
|
+
function getCloudTenantId() {
|
|
52326
|
+
return process.env.HASNA_TODOS_TENANT_ID ?? "default";
|
|
52327
|
+
}
|
|
51049
52328
|
function getClient() {
|
|
51050
52329
|
if (cachedClient)
|
|
51051
52330
|
return cachedClient;
|
|
@@ -51075,11 +52354,20 @@ function getCloudProjectRegistrationAuthority() {
|
|
|
51075
52354
|
cachedProjectRegistrationAuthority = createPostgresTodosProjectRegistrationAuthority(getClient(), {
|
|
51076
52355
|
service: TODOS_APP_SLUG,
|
|
51077
52356
|
authorityId: TODOS_APP_SLUG,
|
|
51078
|
-
tenantId:
|
|
52357
|
+
tenantId: getCloudTenantId(),
|
|
51079
52358
|
corpusId: process.env.HASNA_TODOS_CORPUS_ID ?? `${TODOS_APP_SLUG}:postgresql`
|
|
51080
52359
|
});
|
|
51081
52360
|
return cachedProjectRegistrationAuthority;
|
|
51082
52361
|
}
|
|
52362
|
+
function getCloudTaskManifestAuthority() {
|
|
52363
|
+
if (cachedTaskManifestAuthority)
|
|
52364
|
+
return cachedTaskManifestAuthority;
|
|
52365
|
+
cachedTaskManifestAuthority = createPostgresTodosTaskManifestAuthority(getClient(), {
|
|
52366
|
+
service: TODOS_APP_SLUG,
|
|
52367
|
+
tenantId: getCloudTenantId()
|
|
52368
|
+
});
|
|
52369
|
+
return cachedTaskManifestAuthority;
|
|
52370
|
+
}
|
|
51083
52371
|
function authClient() {
|
|
51084
52372
|
const client = getClient();
|
|
51085
52373
|
return {
|
|
@@ -51131,6 +52419,9 @@ async function ensureCloudSchema() {
|
|
|
51131
52419
|
for (const sql of postgresTodosProjectRegistrationSchemaSql()) {
|
|
51132
52420
|
await client.query(sql);
|
|
51133
52421
|
}
|
|
52422
|
+
for (const sql of postgresTodosTaskManifestSchemaSql(getCloudTenantId())) {
|
|
52423
|
+
await client.query(sql);
|
|
52424
|
+
}
|
|
51134
52425
|
await getApiKeyStore().ensureSchema();
|
|
51135
52426
|
})();
|
|
51136
52427
|
return schemaEnsured;
|
|
@@ -51172,15 +52463,17 @@ async function closeCloud() {
|
|
|
51172
52463
|
cachedVerifier = null;
|
|
51173
52464
|
cachedPrGroupLedger = null;
|
|
51174
52465
|
cachedProjectRegistrationAuthority = null;
|
|
52466
|
+
cachedTaskManifestAuthority = null;
|
|
51175
52467
|
schemaEnsured = null;
|
|
51176
52468
|
}
|
|
51177
|
-
var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, schemaEnsured = null;
|
|
52469
|
+
var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, cachedTaskManifestAuthority = null, schemaEnsured = null;
|
|
51178
52470
|
var init_cloud = __esm(() => {
|
|
51179
52471
|
init_cloud_client();
|
|
51180
52472
|
init_postgres_adapter();
|
|
51181
52473
|
init_ledger();
|
|
51182
52474
|
init_postgres();
|
|
51183
52475
|
init_project_registration();
|
|
52476
|
+
init_task_manifest();
|
|
51184
52477
|
init_postgres_sync();
|
|
51185
52478
|
init_comment_redaction_backfill();
|
|
51186
52479
|
});
|
|
@@ -51394,8 +52687,8 @@ function rowToOrg(row) {
|
|
|
51394
52687
|
function createOrg(input, db) {
|
|
51395
52688
|
const d = db || getDatabase();
|
|
51396
52689
|
const id = uuid();
|
|
51397
|
-
const
|
|
51398
|
-
d.run(`INSERT INTO orgs (id, name, description, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}),
|
|
52690
|
+
const timestamp5 = now();
|
|
52691
|
+
d.run(`INSERT INTO orgs (id, name, description, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp5, timestamp5]);
|
|
51399
52692
|
return getOrg(id, d);
|
|
51400
52693
|
}
|
|
51401
52694
|
function getOrg(id, db) {
|
|
@@ -51539,9 +52832,9 @@ function parseBoundedLimit(value, fallback, max) {
|
|
|
51539
52832
|
return fallback;
|
|
51540
52833
|
return Math.min(parsed, max);
|
|
51541
52834
|
}
|
|
51542
|
-
function mapTaskError(e,
|
|
52835
|
+
function mapTaskError(e, json4) {
|
|
51543
52836
|
if (e instanceof VersionConflictError) {
|
|
51544
|
-
return
|
|
52837
|
+
return json4({
|
|
51545
52838
|
error: e.message,
|
|
51546
52839
|
code: VersionConflictError.code,
|
|
51547
52840
|
expected_version: e.expectedVersion,
|
|
@@ -51549,23 +52842,23 @@ function mapTaskError(e, json3) {
|
|
|
51549
52842
|
}, 409);
|
|
51550
52843
|
}
|
|
51551
52844
|
if (e instanceof TaskNotFoundError) {
|
|
51552
|
-
return
|
|
52845
|
+
return json4({ error: e.message, code: TaskNotFoundError.code }, 404);
|
|
51553
52846
|
}
|
|
51554
52847
|
if (e instanceof LockError) {
|
|
51555
|
-
return
|
|
52848
|
+
return json4({ error: e.message, code: LockError.code }, 409);
|
|
51556
52849
|
}
|
|
51557
52850
|
if (e instanceof CompletionGuardError) {
|
|
51558
|
-
return
|
|
52851
|
+
return json4({
|
|
51559
52852
|
error: e.message,
|
|
51560
52853
|
code: CompletionGuardError.code,
|
|
51561
52854
|
retry_after: e.retryAfterSeconds ?? null
|
|
51562
52855
|
}, 409);
|
|
51563
52856
|
}
|
|
51564
52857
|
if (e instanceof TaskNotStartableError) {
|
|
51565
|
-
return
|
|
52858
|
+
return json4({ error: e.message, code: TaskNotStartableError.code }, 409);
|
|
51566
52859
|
}
|
|
51567
52860
|
if (e instanceof Error && / is blocked by /.test(e.message)) {
|
|
51568
|
-
return
|
|
52861
|
+
return json4({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
|
|
51569
52862
|
}
|
|
51570
52863
|
return null;
|
|
51571
52864
|
}
|
|
@@ -51650,11 +52943,11 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
51650
52943
|
}
|
|
51651
52944
|
});
|
|
51652
52945
|
}
|
|
51653
|
-
function handleHealth(_ctx,
|
|
52946
|
+
function handleHealth(_ctx, json4) {
|
|
51654
52947
|
const stats2 = getTaskStats();
|
|
51655
52948
|
const staleCount = getStaleTasks(30).length;
|
|
51656
52949
|
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
51657
|
-
return
|
|
52950
|
+
return json4({
|
|
51658
52951
|
status: staleCount === 0 && overdueRecurring === 0 ? "ok" : "warn",
|
|
51659
52952
|
tasks: stats2.total,
|
|
51660
52953
|
stale: staleCount,
|
|
@@ -51662,18 +52955,18 @@ function handleHealth(_ctx, json3) {
|
|
|
51662
52955
|
timestamp: new Date().toISOString()
|
|
51663
52956
|
});
|
|
51664
52957
|
}
|
|
51665
|
-
function handleHeadlessBoundary(_ctx,
|
|
52958
|
+
function handleHeadlessBoundary(_ctx, json4) {
|
|
51666
52959
|
const { getHeadlessBoundaryManifest: getHeadlessBoundaryManifest2 } = (init_headless_boundaries(), __toCommonJS(exports_headless_boundaries));
|
|
51667
|
-
return
|
|
52960
|
+
return json4(getHeadlessBoundaryManifest2());
|
|
51668
52961
|
}
|
|
51669
|
-
function handleStats(_ctx,
|
|
52962
|
+
function handleStats(_ctx, json4) {
|
|
51670
52963
|
const stats2 = getTaskStats();
|
|
51671
52964
|
const byStatus = stats2.by_status;
|
|
51672
52965
|
const projects = listProjects();
|
|
51673
52966
|
const agents = listAgents();
|
|
51674
52967
|
const staleCount = getStaleTasks(30).length;
|
|
51675
52968
|
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
51676
|
-
return
|
|
52969
|
+
return json4({
|
|
51677
52970
|
total_tasks: stats2.total,
|
|
51678
52971
|
pending: byStatus["pending"] ?? 0,
|
|
51679
52972
|
in_progress: byStatus["in_progress"] ?? 0,
|
|
@@ -51696,10 +52989,10 @@ function taskStatusQueryParam(url) {
|
|
|
51696
52989
|
return { ok: false, message: result.message };
|
|
51697
52990
|
return { ok: true, value: collapseEnumValues(result.values) };
|
|
51698
52991
|
}
|
|
51699
|
-
async function handleListTasks(_req, url, _ctx,
|
|
52992
|
+
async function handleListTasks(_req, url, _ctx, json4, taskToSummary2) {
|
|
51700
52993
|
const statusParam = taskStatusQueryParam(url);
|
|
51701
52994
|
if (!statusParam.ok)
|
|
51702
|
-
return
|
|
52995
|
+
return json4({ error: statusParam.message }, 400);
|
|
51703
52996
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51704
52997
|
const sessionId = url.searchParams.get("session_id") || undefined;
|
|
51705
52998
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
@@ -51714,60 +53007,60 @@ async function handleListTasks(_req, url, _ctx, json3, taskToSummary2) {
|
|
|
51714
53007
|
limit: limitParam ? parseInt(limitParam, 10) : undefined,
|
|
51715
53008
|
offset: offsetParam ? parseInt(offsetParam, 10) : undefined
|
|
51716
53009
|
});
|
|
51717
|
-
return
|
|
53010
|
+
return json4(tasks.map((t) => taskToSummary2(t, fields)));
|
|
51718
53011
|
}
|
|
51719
|
-
async function handleCreateTask(req, ctx,
|
|
53012
|
+
async function handleCreateTask(req, ctx, json4, taskToSummary2) {
|
|
51720
53013
|
try {
|
|
51721
|
-
const
|
|
51722
|
-
if (!
|
|
51723
|
-
return
|
|
51724
|
-
const createdBy =
|
|
51725
|
-
const
|
|
51726
|
-
title:
|
|
51727
|
-
description:
|
|
51728
|
-
priority:
|
|
51729
|
-
project_id:
|
|
51730
|
-
agent_id:
|
|
53014
|
+
const body2 = await req.json();
|
|
53015
|
+
if (!body2.title)
|
|
53016
|
+
return json4({ error: "Missing 'title'" }, 400);
|
|
53017
|
+
const createdBy = body2.created_by ?? body2.agent_id ?? "dashboard";
|
|
53018
|
+
const task3 = createTask({
|
|
53019
|
+
title: body2.title,
|
|
53020
|
+
description: body2.description,
|
|
53021
|
+
priority: body2.priority,
|
|
53022
|
+
project_id: body2.project_id,
|
|
53023
|
+
agent_id: body2.agent_id ?? createdBy,
|
|
51731
53024
|
created_by: createdBy,
|
|
51732
|
-
...
|
|
53025
|
+
...body2.assigned_to ? { assigned_to: body2.assigned_to } : {}
|
|
51733
53026
|
});
|
|
51734
|
-
ctx.broadcastEvent({ type: "task", task_id:
|
|
51735
|
-
return
|
|
53027
|
+
ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "created", agent_id: task3.agent_id, project_id: task3.project_id });
|
|
53028
|
+
return json4(taskToSummary2(task3), 201);
|
|
51736
53029
|
} catch (e) {
|
|
51737
|
-
return
|
|
53030
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
|
|
51738
53031
|
}
|
|
51739
53032
|
}
|
|
51740
|
-
async function handleUpsertTask(req, ctx,
|
|
53033
|
+
async function handleUpsertTask(req, ctx, json4, taskToSummary2) {
|
|
51741
53034
|
try {
|
|
51742
|
-
const
|
|
51743
|
-
if (typeof
|
|
51744
|
-
return
|
|
53035
|
+
const body2 = await req.json();
|
|
53036
|
+
if (typeof body2["fingerprint"] !== "string" || body2["fingerprint"].trim() === "") {
|
|
53037
|
+
return json4({ error: "Missing 'fingerprint'" }, 400);
|
|
51745
53038
|
}
|
|
51746
|
-
if (typeof
|
|
51747
|
-
return
|
|
53039
|
+
if (typeof body2["title"] !== "string" || body2["title"].trim() === "") {
|
|
53040
|
+
return json4({ error: "Missing 'title'" }, 400);
|
|
51748
53041
|
}
|
|
51749
|
-
const metadata =
|
|
51750
|
-
for (const
|
|
51751
|
-
if (
|
|
51752
|
-
metadata[
|
|
53042
|
+
const metadata = body2["metadata"] && typeof body2["metadata"] === "object" && !Array.isArray(body2["metadata"]) ? { ...body2["metadata"] } : {};
|
|
53043
|
+
for (const key2 of ["expectation_id", "expectation_fingerprint", "evidence_paths", "origin_loop_id", "origin_run_id", "expected", "observed", "acceptance"]) {
|
|
53044
|
+
if (body2[key2] !== undefined)
|
|
53045
|
+
metadata[key2] = body2[key2];
|
|
51753
53046
|
}
|
|
51754
53047
|
const result = upsertTaskByFingerprint({
|
|
51755
|
-
fingerprint:
|
|
51756
|
-
title:
|
|
51757
|
-
description: typeof
|
|
51758
|
-
status:
|
|
51759
|
-
priority:
|
|
51760
|
-
project_id: typeof
|
|
51761
|
-
task_list_id: typeof
|
|
51762
|
-
assigned_to: typeof
|
|
51763
|
-
working_dir: typeof
|
|
51764
|
-
tags: Array.isArray(
|
|
53048
|
+
fingerprint: body2["fingerprint"],
|
|
53049
|
+
title: body2["title"],
|
|
53050
|
+
description: typeof body2["description"] === "string" ? body2["description"] : undefined,
|
|
53051
|
+
status: body2["status"],
|
|
53052
|
+
priority: body2["priority"],
|
|
53053
|
+
project_id: typeof body2["project_id"] === "string" ? body2["project_id"] : undefined,
|
|
53054
|
+
task_list_id: typeof body2["task_list_id"] === "string" ? body2["task_list_id"] : undefined,
|
|
53055
|
+
assigned_to: typeof body2["assigned_to"] === "string" ? body2["assigned_to"] : undefined,
|
|
53056
|
+
working_dir: typeof body2["working_dir"] === "string" ? body2["working_dir"] : undefined,
|
|
53057
|
+
tags: Array.isArray(body2["tags"]) ? body2["tags"].filter((tag) => typeof tag === "string") : undefined,
|
|
51765
53058
|
metadata
|
|
51766
53059
|
});
|
|
51767
53060
|
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 });
|
|
51768
|
-
return
|
|
53061
|
+
return json4({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
|
|
51769
53062
|
} catch (e) {
|
|
51770
|
-
return
|
|
53063
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
|
|
51771
53064
|
}
|
|
51772
53065
|
}
|
|
51773
53066
|
function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
@@ -51819,21 +53112,21 @@ function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
|
51819
53112
|
}
|
|
51820
53113
|
});
|
|
51821
53114
|
}
|
|
51822
|
-
async function handleTasksBulk(req, _ctx,
|
|
53115
|
+
async function handleTasksBulk(req, _ctx, json4) {
|
|
51823
53116
|
try {
|
|
51824
|
-
const
|
|
51825
|
-
if (!
|
|
51826
|
-
return
|
|
53117
|
+
const body2 = await req.json();
|
|
53118
|
+
if (!body2.ids?.length || !body2.action)
|
|
53119
|
+
return json4({ error: "Missing ids or action" }, 400);
|
|
51827
53120
|
const results = [];
|
|
51828
|
-
for (const id of
|
|
53121
|
+
for (const id of body2.ids) {
|
|
51829
53122
|
try {
|
|
51830
|
-
if (
|
|
53123
|
+
if (body2.action === "delete") {
|
|
51831
53124
|
deleteTask(id);
|
|
51832
53125
|
results.push({ id, success: true });
|
|
51833
|
-
} else if (
|
|
53126
|
+
} else if (body2.action === "start") {
|
|
51834
53127
|
startTask(id, "dashboard");
|
|
51835
53128
|
results.push({ id, success: true });
|
|
51836
|
-
} else if (
|
|
53129
|
+
} else if (body2.action === "complete") {
|
|
51837
53130
|
completeTask(id, "dashboard");
|
|
51838
53131
|
results.push({ id, success: true });
|
|
51839
53132
|
}
|
|
@@ -51841,84 +53134,84 @@ async function handleTasksBulk(req, _ctx, json3) {
|
|
|
51841
53134
|
results.push({ id, success: false, error: e instanceof Error ? e.message : "Failed" });
|
|
51842
53135
|
}
|
|
51843
53136
|
}
|
|
51844
|
-
return
|
|
53137
|
+
return json4({ results, succeeded: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length });
|
|
51845
53138
|
} catch (e) {
|
|
51846
|
-
return
|
|
53139
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51847
53140
|
}
|
|
51848
53141
|
}
|
|
51849
|
-
function handleTasksStatus(_req, url, _ctx,
|
|
53142
|
+
function handleTasksStatus(_req, url, _ctx, json4) {
|
|
51850
53143
|
try {
|
|
51851
53144
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51852
53145
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
51853
|
-
const
|
|
51854
|
-
return
|
|
53146
|
+
const status2 = getStatus(projectId ? { project_id: projectId } : undefined, agentId);
|
|
53147
|
+
return json4(status2);
|
|
51855
53148
|
} catch (e) {
|
|
51856
|
-
return
|
|
53149
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51857
53150
|
}
|
|
51858
53151
|
}
|
|
51859
|
-
function handleTasksNext(_req, url, _ctx,
|
|
53152
|
+
function handleTasksNext(_req, url, _ctx, json4, taskToSummary2) {
|
|
51860
53153
|
try {
|
|
51861
53154
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51862
53155
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
51863
53156
|
const fields = parseFieldsParam(url);
|
|
51864
|
-
const
|
|
51865
|
-
return
|
|
53157
|
+
const task3 = getNextTask(agentId, projectId ? { project_id: projectId } : undefined);
|
|
53158
|
+
return json4({ task: task3 ? taskToSummary2(task3, fields) : null });
|
|
51866
53159
|
} catch (e) {
|
|
51867
|
-
return
|
|
53160
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51868
53161
|
}
|
|
51869
53162
|
}
|
|
51870
|
-
function handleTasksActive(_req, url, _ctx,
|
|
53163
|
+
function handleTasksActive(_req, url, _ctx, json4) {
|
|
51871
53164
|
try {
|
|
51872
53165
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51873
53166
|
const work = getActiveWork(projectId ? { project_id: projectId } : undefined);
|
|
51874
|
-
return
|
|
53167
|
+
return json4({ active: work, count: work.length });
|
|
51875
53168
|
} catch (e) {
|
|
51876
|
-
return
|
|
53169
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51877
53170
|
}
|
|
51878
53171
|
}
|
|
51879
|
-
function handleTasksStale(_req, url, _ctx,
|
|
53172
|
+
function handleTasksStale(_req, url, _ctx, json4, taskToSummary2) {
|
|
51880
53173
|
try {
|
|
51881
53174
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51882
53175
|
const minutes2 = parseInt(url.searchParams.get("minutes") || "30", 10);
|
|
51883
53176
|
const fields = parseFieldsParam(url);
|
|
51884
53177
|
const tasks = getStaleTasks(minutes2, projectId ? { project_id: projectId } : undefined);
|
|
51885
|
-
return
|
|
53178
|
+
return json4({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length });
|
|
51886
53179
|
} catch (e) {
|
|
51887
|
-
return
|
|
53180
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51888
53181
|
}
|
|
51889
53182
|
}
|
|
51890
|
-
function handleTasksChanged(_req, url, _ctx,
|
|
53183
|
+
function handleTasksChanged(_req, url, _ctx, json4, taskToSummary2) {
|
|
51891
53184
|
try {
|
|
51892
53185
|
const since = url.searchParams.get("since");
|
|
51893
53186
|
if (!since)
|
|
51894
|
-
return
|
|
53187
|
+
return json4({ error: "since parameter required (ISO date string)" }, 400);
|
|
51895
53188
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51896
53189
|
const fields = parseFieldsParam(url);
|
|
51897
53190
|
const tasks = getTasksChangedSince(since, projectId ? { project_id: projectId } : undefined);
|
|
51898
|
-
return
|
|
53191
|
+
return json4({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length, since });
|
|
51899
53192
|
} catch (e) {
|
|
51900
|
-
return
|
|
53193
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51901
53194
|
}
|
|
51902
53195
|
}
|
|
51903
|
-
function handleTasksContext(_req, url, _ctx,
|
|
53196
|
+
function handleTasksContext(_req, url, _ctx, json4, taskToSummary2) {
|
|
51904
53197
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
51905
53198
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51906
53199
|
const format = url.searchParams.get("format") || "text";
|
|
51907
53200
|
const fields = parseFieldsParam(url);
|
|
51908
53201
|
const filters = projectId ? { project_id: projectId } : undefined;
|
|
51909
|
-
const
|
|
53202
|
+
const status2 = getStatus(filters, agentId);
|
|
51910
53203
|
const next = getNextTask(agentId, filters);
|
|
51911
53204
|
if (format === "json") {
|
|
51912
|
-
return
|
|
53205
|
+
return json4({ status: status2, next_task: next ? taskToSummary2(next, fields) : null });
|
|
51913
53206
|
}
|
|
51914
53207
|
const lines = [];
|
|
51915
|
-
lines.push(`Tasks: ${
|
|
51916
|
-
if (
|
|
51917
|
-
lines.push(`${
|
|
51918
|
-
if (
|
|
51919
|
-
lines.push(`${
|
|
51920
|
-
if (
|
|
51921
|
-
lines.push(`Active: ${
|
|
53208
|
+
lines.push(`Tasks: ${status2.pending} pending | ${status2.in_progress} active | ${status2.completed} done`);
|
|
53209
|
+
if (status2.stale_count > 0)
|
|
53210
|
+
lines.push(`${status2.stale_count} stale tasks stuck in-progress`);
|
|
53211
|
+
if (status2.overdue_recurring > 0)
|
|
53212
|
+
lines.push(`${status2.overdue_recurring} overdue recurring tasks`);
|
|
53213
|
+
if (status2.active_work.length > 0) {
|
|
53214
|
+
lines.push(`Active: ${status2.active_work.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ")}`);
|
|
51922
53215
|
}
|
|
51923
53216
|
if (next)
|
|
51924
53217
|
lines.push(`Next up: ${next.short_id || next.id.slice(0, 8)} [${next.priority}] ${next.title}`);
|
|
@@ -51926,18 +53219,18 @@ function handleTasksContext(_req, url, _ctx, json3, taskToSummary2) {
|
|
|
51926
53219
|
`);
|
|
51927
53220
|
return new Response(text2, { headers: { "Content-Type": "text/plain" } });
|
|
51928
53221
|
}
|
|
51929
|
-
function handleTaskAttachments(id, _ctx,
|
|
51930
|
-
const
|
|
51931
|
-
if (!
|
|
51932
|
-
return
|
|
51933
|
-
const evidence =
|
|
53222
|
+
function handleTaskAttachments(id, _ctx, json4) {
|
|
53223
|
+
const task3 = getTask(id);
|
|
53224
|
+
if (!task3)
|
|
53225
|
+
return json4({ error: "Task not found" }, 404);
|
|
53226
|
+
const evidence = task3.metadata?._evidence || {};
|
|
51934
53227
|
const attachmentIds = evidence.attachments || [];
|
|
51935
|
-
return
|
|
53228
|
+
return json4({ task_id: id, short_id: task3.short_id, attachment_ids: attachmentIds, count: attachmentIds.length, files_changed: evidence.files_changed, commit_hash: evidence.commit_hash, notes: evidence.notes });
|
|
51936
53229
|
}
|
|
51937
|
-
async function handleTaskProgress(id, req, method, _ctx,
|
|
51938
|
-
const
|
|
51939
|
-
if (!
|
|
51940
|
-
return
|
|
53230
|
+
async function handleTaskProgress(id, req, method, _ctx, json4, url) {
|
|
53231
|
+
const task3 = getTask(id);
|
|
53232
|
+
if (!task3)
|
|
53233
|
+
return json4({ error: "Task not found" }, 404);
|
|
51941
53234
|
if (method === "GET") {
|
|
51942
53235
|
const all = listComments(id);
|
|
51943
53236
|
const progress = all.filter((c) => c.type === "progress");
|
|
@@ -51945,7 +53238,7 @@ async function handleTaskProgress(id, req, method, _ctx, json3, url) {
|
|
|
51945
53238
|
const format = url?.searchParams.get("format") || "compact";
|
|
51946
53239
|
const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 200);
|
|
51947
53240
|
const progressEntries = format === "full" ? progress : progress.slice(-limit);
|
|
51948
|
-
return
|
|
53241
|
+
return json4({
|
|
51949
53242
|
task_id: id,
|
|
51950
53243
|
progress_entries: progressEntries,
|
|
51951
53244
|
latest,
|
|
@@ -51960,119 +53253,119 @@ async function handleTaskProgress(id, req, method, _ctx, json3, url) {
|
|
|
51960
53253
|
}
|
|
51961
53254
|
if (method === "POST") {
|
|
51962
53255
|
try {
|
|
51963
|
-
const
|
|
51964
|
-
if (!
|
|
51965
|
-
return
|
|
51966
|
-
const
|
|
51967
|
-
return
|
|
53256
|
+
const body2 = await req.json();
|
|
53257
|
+
if (!body2.message)
|
|
53258
|
+
return json4({ error: "message required" }, 400);
|
|
53259
|
+
const comment2 = logProgress(id, body2.message, body2.pct_complete, body2.agent_id);
|
|
53260
|
+
return json4(comment2, 201);
|
|
51968
53261
|
} catch (e) {
|
|
51969
|
-
return
|
|
53262
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to log progress" }, 500);
|
|
51970
53263
|
}
|
|
51971
53264
|
}
|
|
51972
53265
|
return null;
|
|
51973
53266
|
}
|
|
51974
|
-
function handleGetTask(id, _ctx,
|
|
51975
|
-
const
|
|
51976
|
-
if (!
|
|
51977
|
-
return
|
|
51978
|
-
return
|
|
53267
|
+
function handleGetTask(id, _ctx, json4, taskToSummary2, url) {
|
|
53268
|
+
const task3 = getTask(id);
|
|
53269
|
+
if (!task3)
|
|
53270
|
+
return json4({ error: "Task not found" }, 404);
|
|
53271
|
+
return json4(taskToSummary2(task3, url ? parseFieldsParam(url) : undefined));
|
|
51979
53272
|
}
|
|
51980
|
-
async function handlePatchTask(id, req, _ctx,
|
|
53273
|
+
async function handlePatchTask(id, req, _ctx, json4, taskToSummary2) {
|
|
51981
53274
|
try {
|
|
51982
|
-
const
|
|
51983
|
-
const
|
|
51984
|
-
if (!
|
|
51985
|
-
return
|
|
53275
|
+
const body2 = await req.json();
|
|
53276
|
+
const task3 = getTask(id);
|
|
53277
|
+
if (!task3)
|
|
53278
|
+
return json4({ error: "Task not found" }, 404);
|
|
51986
53279
|
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"]);
|
|
51987
53280
|
const safeBody = {};
|
|
51988
|
-
for (const [
|
|
51989
|
-
if (ALLOWED.has(
|
|
51990
|
-
safeBody[
|
|
53281
|
+
for (const [key2, value] of Object.entries(body2)) {
|
|
53282
|
+
if (ALLOWED.has(key2))
|
|
53283
|
+
safeBody[key2] = value;
|
|
51991
53284
|
}
|
|
51992
|
-
const clientVersion = typeof
|
|
53285
|
+
const clientVersion = typeof body2["version"] === "number" ? body2["version"] : task3.version;
|
|
51993
53286
|
const updated = updateTask(id, {
|
|
51994
53287
|
...safeBody,
|
|
51995
53288
|
version: clientVersion
|
|
51996
53289
|
});
|
|
51997
|
-
return
|
|
53290
|
+
return json4(taskToSummary2(updated));
|
|
51998
53291
|
} catch (e) {
|
|
51999
|
-
const mapped = mapTaskError(e,
|
|
53292
|
+
const mapped = mapTaskError(e, json4);
|
|
52000
53293
|
if (mapped)
|
|
52001
53294
|
return mapped;
|
|
52002
|
-
return
|
|
53295
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to update task" }, 500);
|
|
52003
53296
|
}
|
|
52004
53297
|
}
|
|
52005
|
-
function handleDeleteTask(id, _ctx,
|
|
53298
|
+
function handleDeleteTask(id, _ctx, json4) {
|
|
52006
53299
|
const deleted = deleteTask(id);
|
|
52007
53300
|
if (!deleted)
|
|
52008
|
-
return
|
|
52009
|
-
return
|
|
53301
|
+
return json4({ error: "Task not found" }, 404);
|
|
53302
|
+
return json4({ success: true });
|
|
52010
53303
|
}
|
|
52011
|
-
function handleStartTask(id, ctx,
|
|
53304
|
+
function handleStartTask(id, ctx, json4, taskToSummary2) {
|
|
52012
53305
|
try {
|
|
52013
|
-
const
|
|
52014
|
-
ctx.broadcastEvent({ type: "task", task_id:
|
|
52015
|
-
return
|
|
53306
|
+
const task3 = startTask(id, "dashboard");
|
|
53307
|
+
ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "started", agent_id: "dashboard", project_id: task3.project_id });
|
|
53308
|
+
return json4(taskToSummary2(task3));
|
|
52016
53309
|
} catch (e) {
|
|
52017
|
-
const mapped = mapTaskError(e,
|
|
53310
|
+
const mapped = mapTaskError(e, json4);
|
|
52018
53311
|
if (mapped)
|
|
52019
53312
|
return mapped;
|
|
52020
|
-
return
|
|
53313
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to start task" }, 500);
|
|
52021
53314
|
}
|
|
52022
53315
|
}
|
|
52023
|
-
async function handleFailTask(id, req, ctx,
|
|
53316
|
+
async function handleFailTask(id, req, ctx, json4, taskToSummary2) {
|
|
52024
53317
|
try {
|
|
52025
|
-
const
|
|
52026
|
-
const result = failTask(id,
|
|
52027
|
-
ctx.broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id:
|
|
52028
|
-
return
|
|
53318
|
+
const body2 = await req.json().catch(() => ({}));
|
|
53319
|
+
const result = failTask(id, body2.agent_id, body2.reason, { retry: body2.retry, error_code: body2.error_code });
|
|
53320
|
+
ctx.broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body2.agent_id || null, project_id: result.task.project_id });
|
|
53321
|
+
return json4({ task: taskToSummary2(result.task), retry_task: result.retryTask ? taskToSummary2(result.retryTask) : null });
|
|
52029
53322
|
} catch (e) {
|
|
52030
|
-
return
|
|
53323
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500);
|
|
52031
53324
|
}
|
|
52032
53325
|
}
|
|
52033
|
-
function handleCompleteTask(id, ctx,
|
|
53326
|
+
function handleCompleteTask(id, ctx, json4, taskToSummary2) {
|
|
52034
53327
|
try {
|
|
52035
|
-
const
|
|
52036
|
-
ctx.broadcastEvent({ type: "task", task_id:
|
|
52037
|
-
return
|
|
53328
|
+
const task3 = completeTask(id, "dashboard");
|
|
53329
|
+
ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "completed", agent_id: "dashboard", project_id: task3.project_id });
|
|
53330
|
+
return json4(taskToSummary2(task3));
|
|
52038
53331
|
} catch (e) {
|
|
52039
|
-
const mapped = mapTaskError(e,
|
|
53332
|
+
const mapped = mapTaskError(e, json4);
|
|
52040
53333
|
if (mapped)
|
|
52041
53334
|
return mapped;
|
|
52042
|
-
return
|
|
53335
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500);
|
|
52043
53336
|
}
|
|
52044
53337
|
}
|
|
52045
|
-
function handleListProjects(url, _ctx,
|
|
53338
|
+
function handleListProjects(url, _ctx, json4) {
|
|
52046
53339
|
const pFieldsParam = url.searchParams.get("fields");
|
|
52047
53340
|
const pFields = pFieldsParam ? pFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
52048
53341
|
const projects = listProjects();
|
|
52049
|
-
return
|
|
53342
|
+
return json4(pFields ? projects.map((p) => Object.fromEntries(pFields.map((f) => [f, p[f] ?? null]))) : projects);
|
|
52050
53343
|
}
|
|
52051
|
-
async function handleCreateProject(req, _ctx,
|
|
53344
|
+
async function handleCreateProject(req, _ctx, json4) {
|
|
52052
53345
|
try {
|
|
52053
|
-
const
|
|
52054
|
-
if (!
|
|
52055
|
-
return
|
|
52056
|
-
const project = createProject({ name:
|
|
52057
|
-
return
|
|
53346
|
+
const body2 = await req.json();
|
|
53347
|
+
if (!body2.name || !body2.path)
|
|
53348
|
+
return json4({ error: "Missing name or path" }, 400);
|
|
53349
|
+
const project = createProject({ name: body2.name, path: body2.path, description: body2.description });
|
|
53350
|
+
return json4(project, 201);
|
|
52058
53351
|
} catch (e) {
|
|
52059
|
-
return
|
|
53352
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to create project" }, 500);
|
|
52060
53353
|
}
|
|
52061
53354
|
}
|
|
52062
|
-
function handleDeleteProject(id, _ctx,
|
|
53355
|
+
function handleDeleteProject(id, _ctx, json4) {
|
|
52063
53356
|
const deleted = deleteProject(id);
|
|
52064
53357
|
if (!deleted)
|
|
52065
|
-
return
|
|
52066
|
-
return
|
|
53358
|
+
return json4({ error: "Project not found" }, 404);
|
|
53359
|
+
return json4({ success: true });
|
|
52067
53360
|
}
|
|
52068
|
-
async function handleAgentMe(_req, url, _ctx,
|
|
53361
|
+
async function handleAgentMe(_req, url, _ctx, json4, taskToSummary2) {
|
|
52069
53362
|
try {
|
|
52070
53363
|
const name = url.searchParams.get("name");
|
|
52071
53364
|
if (!name)
|
|
52072
|
-
return
|
|
53365
|
+
return json4({ error: "Missing name param" }, 400);
|
|
52073
53366
|
const agentResult = registerAgent({ name });
|
|
52074
53367
|
if (isAgentConflict(agentResult))
|
|
52075
|
-
return
|
|
53368
|
+
return json4({ error: agentResult.message, conflict: true }, 409);
|
|
52076
53369
|
const agent = agentResult;
|
|
52077
53370
|
const tasks = listTasks({ assigned_to: agent.name });
|
|
52078
53371
|
const agentIdTasks = listTasks({ agent_id: agent.id });
|
|
@@ -52080,7 +53373,7 @@ async function handleAgentMe(_req, url, _ctx, json3, taskToSummary2) {
|
|
|
52080
53373
|
const pending = allTasks.filter((t) => t.status === "pending");
|
|
52081
53374
|
const inProgress = allTasks.filter((t) => t.status === "in_progress");
|
|
52082
53375
|
const completed = allTasks.filter((t) => t.status === "completed");
|
|
52083
|
-
return
|
|
53376
|
+
return json4({
|
|
52084
53377
|
agent,
|
|
52085
53378
|
pending_tasks: pending.map((t) => taskToSummary2(t)),
|
|
52086
53379
|
in_progress_tasks: inProgress.map((t) => taskToSummary2(t)),
|
|
@@ -52094,132 +53387,132 @@ async function handleAgentMe(_req, url, _ctx, json3, taskToSummary2) {
|
|
|
52094
53387
|
});
|
|
52095
53388
|
} catch (e) {
|
|
52096
53389
|
if (e instanceof InvalidAgentNameError)
|
|
52097
|
-
return
|
|
52098
|
-
return
|
|
53390
|
+
return json4({ error: e.message, suggestions: e.suggestions }, 400);
|
|
53391
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to get agent profile" }, 500);
|
|
52099
53392
|
}
|
|
52100
53393
|
}
|
|
52101
|
-
function handleAgentQueue(agentId, _ctx,
|
|
53394
|
+
function handleAgentQueue(agentId, _ctx, json4, taskToSummary2) {
|
|
52102
53395
|
const aliasSet = assignedToAliasSet(getDatabase(), agentId);
|
|
52103
53396
|
const pending = listTasks({ status: "pending" });
|
|
52104
53397
|
const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
|
|
52105
53398
|
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
52106
53399
|
queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
|
52107
|
-
return
|
|
53400
|
+
return json4(queue.map((t) => taskToSummary2(t)));
|
|
52108
53401
|
}
|
|
52109
|
-
async function handleClaimTask(req, _ctx,
|
|
53402
|
+
async function handleClaimTask(req, _ctx, json4, taskToSummary2) {
|
|
52110
53403
|
try {
|
|
52111
|
-
const
|
|
52112
|
-
const agentId =
|
|
52113
|
-
const
|
|
52114
|
-
return
|
|
53404
|
+
const body2 = await req.json();
|
|
53405
|
+
const agentId = body2.agent_id || "anonymous";
|
|
53406
|
+
const task3 = claimNextTask(agentId, body2.project_id ? { project_id: body2.project_id } : undefined);
|
|
53407
|
+
return json4({ task: task3 ? taskToSummary2(task3) : null });
|
|
52115
53408
|
} catch (e) {
|
|
52116
|
-
return
|
|
53409
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to claim" }, 500);
|
|
52117
53410
|
}
|
|
52118
53411
|
}
|
|
52119
|
-
function handleListOrgs(_ctx,
|
|
52120
|
-
return
|
|
53412
|
+
function handleListOrgs(_ctx, json4) {
|
|
53413
|
+
return json4(listOrgs());
|
|
52121
53414
|
}
|
|
52122
|
-
async function handleCreateOrg(req, _ctx,
|
|
53415
|
+
async function handleCreateOrg(req, _ctx, json4) {
|
|
52123
53416
|
try {
|
|
52124
|
-
const
|
|
52125
|
-
if (!
|
|
52126
|
-
return
|
|
52127
|
-
return
|
|
53417
|
+
const body2 = await req.json();
|
|
53418
|
+
if (!body2.name)
|
|
53419
|
+
return json4({ error: "Missing name" }, 400);
|
|
53420
|
+
return json4(createOrg(body2), 201);
|
|
52128
53421
|
} catch (e) {
|
|
52129
|
-
return
|
|
53422
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52130
53423
|
}
|
|
52131
53424
|
}
|
|
52132
|
-
async function handleUpdateOrg(id, req, _ctx,
|
|
53425
|
+
async function handleUpdateOrg(id, req, _ctx, json4) {
|
|
52133
53426
|
try {
|
|
52134
|
-
const
|
|
52135
|
-
return
|
|
53427
|
+
const body2 = await req.json();
|
|
53428
|
+
return json4(updateOrg(id, body2));
|
|
52136
53429
|
} catch (e) {
|
|
52137
|
-
return
|
|
53430
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52138
53431
|
}
|
|
52139
53432
|
}
|
|
52140
|
-
function handleDeleteOrg(id, _ctx,
|
|
53433
|
+
function handleDeleteOrg(id, _ctx, json4) {
|
|
52141
53434
|
const deleted = deleteOrg(id);
|
|
52142
|
-
return
|
|
53435
|
+
return json4(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
52143
53436
|
}
|
|
52144
|
-
function handleOrgChart(_ctx,
|
|
52145
|
-
return
|
|
53437
|
+
function handleOrgChart(_ctx, json4) {
|
|
53438
|
+
return json4(getOrgChart());
|
|
52146
53439
|
}
|
|
52147
|
-
function handleAgentTeam(agentId, _ctx,
|
|
52148
|
-
return
|
|
53440
|
+
function handleAgentTeam(agentId, _ctx, json4) {
|
|
53441
|
+
return json4(getDirectReports(decodeURIComponent(agentId)));
|
|
52149
53442
|
}
|
|
52150
|
-
function handleListAgents(url, _ctx,
|
|
53443
|
+
function handleListAgents(url, _ctx, json4) {
|
|
52151
53444
|
const aFieldsParam = url.searchParams.get("fields");
|
|
52152
53445
|
const aFields = aFieldsParam ? aFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
52153
53446
|
const agents = listAgents();
|
|
52154
|
-
return
|
|
53447
|
+
return json4(aFields ? agents.map((a) => Object.fromEntries(aFields.map((f) => [f, a[f] ?? null]))) : agents);
|
|
52155
53448
|
}
|
|
52156
|
-
async function handleRegisterAgent(req, _ctx,
|
|
53449
|
+
async function handleRegisterAgent(req, _ctx, json4) {
|
|
52157
53450
|
try {
|
|
52158
|
-
const
|
|
52159
|
-
if (!
|
|
52160
|
-
return
|
|
52161
|
-
const result = registerAgent({ name:
|
|
53451
|
+
const body2 = await req.json();
|
|
53452
|
+
if (!body2.name)
|
|
53453
|
+
return json4({ error: "Missing name" }, 400);
|
|
53454
|
+
const result = registerAgent({ name: body2.name, description: body2.description, session_id: body2.session_id, working_dir: body2.working_dir });
|
|
52162
53455
|
if (isAgentConflict(result))
|
|
52163
|
-
return
|
|
52164
|
-
return
|
|
53456
|
+
return json4({ error: result.message, conflict: true }, 409);
|
|
53457
|
+
return json4(result, 201);
|
|
52165
53458
|
} catch (e) {
|
|
52166
53459
|
if (e instanceof InvalidAgentNameError)
|
|
52167
|
-
return
|
|
52168
|
-
return
|
|
53460
|
+
return json4({ error: e.message, suggestions: e.suggestions }, 400);
|
|
53461
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500);
|
|
52169
53462
|
}
|
|
52170
53463
|
}
|
|
52171
|
-
async function handleUpdateAgent(id, req, _ctx,
|
|
53464
|
+
async function handleUpdateAgent(id, req, _ctx, json4) {
|
|
52172
53465
|
try {
|
|
52173
|
-
const
|
|
52174
|
-
const agent = updateAgent(id,
|
|
52175
|
-
return
|
|
53466
|
+
const body2 = await req.json();
|
|
53467
|
+
const agent = updateAgent(id, body2);
|
|
53468
|
+
return json4(agent);
|
|
52176
53469
|
} catch (e) {
|
|
52177
53470
|
if (e instanceof InvalidAgentNameError)
|
|
52178
|
-
return
|
|
52179
|
-
return
|
|
53471
|
+
return json4({ error: e.message, suggestions: e.suggestions }, 400);
|
|
53472
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to update agent" }, 500);
|
|
52180
53473
|
}
|
|
52181
53474
|
}
|
|
52182
|
-
function handleDeleteAgent(id, _ctx,
|
|
53475
|
+
function handleDeleteAgent(id, _ctx, json4) {
|
|
52183
53476
|
const deleted = deleteAgent(id);
|
|
52184
53477
|
if (!deleted)
|
|
52185
|
-
return
|
|
52186
|
-
return
|
|
53478
|
+
return json4({ error: "Agent not found" }, 404);
|
|
53479
|
+
return json4({ success: true });
|
|
52187
53480
|
}
|
|
52188
|
-
async function handleBulkDeleteAgents(req, _ctx,
|
|
53481
|
+
async function handleBulkDeleteAgents(req, _ctx, json4) {
|
|
52189
53482
|
try {
|
|
52190
|
-
const
|
|
52191
|
-
if (!
|
|
52192
|
-
return
|
|
53483
|
+
const body2 = await req.json();
|
|
53484
|
+
if (!body2.ids?.length || body2.action !== "delete")
|
|
53485
|
+
return json4({ error: "Missing ids or invalid action" }, 400);
|
|
52193
53486
|
let succeeded = 0;
|
|
52194
|
-
for (const id of
|
|
53487
|
+
for (const id of body2.ids) {
|
|
52195
53488
|
if (deleteAgent(id))
|
|
52196
53489
|
succeeded++;
|
|
52197
53490
|
}
|
|
52198
|
-
return
|
|
53491
|
+
return json4({ succeeded, failed: body2.ids.length - succeeded });
|
|
52199
53492
|
} catch (e) {
|
|
52200
|
-
return
|
|
53493
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52201
53494
|
}
|
|
52202
53495
|
}
|
|
52203
|
-
async function handleBulkDeleteProjects(req, _ctx,
|
|
53496
|
+
async function handleBulkDeleteProjects(req, _ctx, json4) {
|
|
52204
53497
|
try {
|
|
52205
|
-
const
|
|
52206
|
-
if (!
|
|
52207
|
-
return
|
|
53498
|
+
const body2 = await req.json();
|
|
53499
|
+
if (!body2.ids?.length || body2.action !== "delete")
|
|
53500
|
+
return json4({ error: "Missing ids or invalid action" }, 400);
|
|
52208
53501
|
let succeeded = 0;
|
|
52209
|
-
for (const id of
|
|
53502
|
+
for (const id of body2.ids) {
|
|
52210
53503
|
if (deleteProject(id))
|
|
52211
53504
|
succeeded++;
|
|
52212
53505
|
}
|
|
52213
|
-
return
|
|
53506
|
+
return json4({ succeeded, failed: body2.ids.length - succeeded });
|
|
52214
53507
|
} catch (e) {
|
|
52215
|
-
return
|
|
53508
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52216
53509
|
}
|
|
52217
53510
|
}
|
|
52218
|
-
function handleDoctor(_ctx,
|
|
53511
|
+
function handleDoctor(_ctx, json4) {
|
|
52219
53512
|
const { runTodosDoctor: runTodosDoctor2 } = (init_doctor(), __toCommonJS(exports_doctor));
|
|
52220
|
-
return
|
|
53513
|
+
return json4(runTodosDoctor2({ apply: false }));
|
|
52221
53514
|
}
|
|
52222
|
-
function handleReport(_req, url, _ctx,
|
|
53515
|
+
function handleReport(_req, url, _ctx, json4) {
|
|
52223
53516
|
const days = parseInt(url.searchParams.get("days") || "7", 10);
|
|
52224
53517
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
52225
53518
|
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
@@ -52235,114 +53528,114 @@ function handleReport(_req, url, _ctx, json3) {
|
|
|
52235
53528
|
byDay[day] = (byDay[day] || 0) + 1;
|
|
52236
53529
|
}
|
|
52237
53530
|
const completionRate = changed.length > 0 ? Math.round(completed.length / changed.length * 100) : 0;
|
|
52238
|
-
return
|
|
53531
|
+
return json4({ days, period_since: since, total: all.length, stats: stats2, changed: changed.length, completed: completed.length, failed: failed.length, completion_rate: completionRate, by_day: byDay });
|
|
52239
53532
|
}
|
|
52240
|
-
function handleActivity(_req, url, _ctx,
|
|
53533
|
+
function handleActivity(_req, url, _ctx, json4) {
|
|
52241
53534
|
const limit = parseInt(url.searchParams.get("limit") || "50", 10);
|
|
52242
|
-
return
|
|
53535
|
+
return json4(getRecentActivity(limit));
|
|
52243
53536
|
}
|
|
52244
|
-
function handleTaskHistory(id, _ctx,
|
|
53537
|
+
function handleTaskHistory(id, _ctx, json4, url) {
|
|
52245
53538
|
const history = getTaskHistory(id);
|
|
52246
53539
|
const format = url?.searchParams.get("format") || "compact";
|
|
52247
53540
|
const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 500);
|
|
52248
|
-
return
|
|
53541
|
+
return json4(format === "full" ? history : history.slice(0, limit));
|
|
52249
53542
|
}
|
|
52250
|
-
function handleListWebhooks(_ctx,
|
|
52251
|
-
return
|
|
53543
|
+
function handleListWebhooks(_ctx, json4) {
|
|
53544
|
+
return json4(listWebhooks());
|
|
52252
53545
|
}
|
|
52253
|
-
async function handleCreateWebhook(req, _ctx,
|
|
53546
|
+
async function handleCreateWebhook(req, _ctx, json4) {
|
|
52254
53547
|
try {
|
|
52255
|
-
const
|
|
52256
|
-
if (!
|
|
52257
|
-
return
|
|
52258
|
-
return
|
|
53548
|
+
const body2 = await req.json();
|
|
53549
|
+
if (!body2.url)
|
|
53550
|
+
return json4({ error: "Missing url" }, 400);
|
|
53551
|
+
return json4(createWebhook(body2), 201);
|
|
52259
53552
|
} catch (e) {
|
|
52260
|
-
return
|
|
53553
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52261
53554
|
}
|
|
52262
53555
|
}
|
|
52263
|
-
function handleDeleteWebhook(id, _ctx,
|
|
53556
|
+
function handleDeleteWebhook(id, _ctx, json4) {
|
|
52264
53557
|
const deleted = deleteWebhook(id);
|
|
52265
|
-
return
|
|
53558
|
+
return json4(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
52266
53559
|
}
|
|
52267
|
-
function handleListTemplates(_ctx,
|
|
52268
|
-
return
|
|
53560
|
+
function handleListTemplates(_ctx, json4) {
|
|
53561
|
+
return json4(listTemplates());
|
|
52269
53562
|
}
|
|
52270
|
-
async function handleCreateTemplate(req, _ctx,
|
|
53563
|
+
async function handleCreateTemplate(req, _ctx, json4) {
|
|
52271
53564
|
try {
|
|
52272
|
-
const
|
|
52273
|
-
if (!
|
|
52274
|
-
return
|
|
52275
|
-
return
|
|
53565
|
+
const body2 = await req.json();
|
|
53566
|
+
if (!body2.name || !body2.title_pattern)
|
|
53567
|
+
return json4({ error: "Missing name or title_pattern" }, 400);
|
|
53568
|
+
return json4(createTemplate(body2), 201);
|
|
52276
53569
|
} catch (e) {
|
|
52277
|
-
return
|
|
53570
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52278
53571
|
}
|
|
52279
53572
|
}
|
|
52280
|
-
function handleDeleteTemplate(id, _ctx,
|
|
53573
|
+
function handleDeleteTemplate(id, _ctx, json4) {
|
|
52281
53574
|
const deleted = deleteTemplate(id);
|
|
52282
|
-
return
|
|
53575
|
+
return json4(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
52283
53576
|
}
|
|
52284
|
-
function handleListPlans(url, _ctx,
|
|
53577
|
+
function handleListPlans(url, _ctx, json4) {
|
|
52285
53578
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
52286
53579
|
const plans = listPlans(projectId);
|
|
52287
|
-
return
|
|
53580
|
+
return json4(plans);
|
|
52288
53581
|
}
|
|
52289
|
-
async function handleCreatePlan(req, _ctx,
|
|
53582
|
+
async function handleCreatePlan(req, _ctx, json4) {
|
|
52290
53583
|
try {
|
|
52291
|
-
const
|
|
52292
|
-
if (!
|
|
52293
|
-
return
|
|
53584
|
+
const body2 = await req.json();
|
|
53585
|
+
if (!body2.name)
|
|
53586
|
+
return json4({ error: "Missing 'name'" }, 400);
|
|
52294
53587
|
const plan = createPlan({
|
|
52295
|
-
name:
|
|
52296
|
-
slug:
|
|
52297
|
-
description:
|
|
52298
|
-
project_id:
|
|
52299
|
-
task_list_id:
|
|
52300
|
-
agent_id:
|
|
52301
|
-
status:
|
|
52302
|
-
});
|
|
52303
|
-
return
|
|
53588
|
+
name: body2.name,
|
|
53589
|
+
slug: body2.slug,
|
|
53590
|
+
description: body2.description,
|
|
53591
|
+
project_id: body2.project_id,
|
|
53592
|
+
task_list_id: body2.task_list_id,
|
|
53593
|
+
agent_id: body2.agent_id,
|
|
53594
|
+
status: body2.status
|
|
53595
|
+
});
|
|
53596
|
+
return json4(plan, 201);
|
|
52304
53597
|
} catch (e) {
|
|
52305
|
-
return
|
|
53598
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to create plan" }, 500);
|
|
52306
53599
|
}
|
|
52307
53600
|
}
|
|
52308
|
-
async function handleBulkDeletePlans(req, _ctx,
|
|
53601
|
+
async function handleBulkDeletePlans(req, _ctx, json4) {
|
|
52309
53602
|
try {
|
|
52310
|
-
const
|
|
52311
|
-
if (!
|
|
52312
|
-
return
|
|
53603
|
+
const body2 = await req.json();
|
|
53604
|
+
if (!body2.ids?.length || body2.action !== "delete")
|
|
53605
|
+
return json4({ error: "Missing ids or invalid action" }, 400);
|
|
52313
53606
|
let succeeded = 0;
|
|
52314
|
-
for (const id of
|
|
53607
|
+
for (const id of body2.ids) {
|
|
52315
53608
|
if (deletePlan(id))
|
|
52316
53609
|
succeeded++;
|
|
52317
53610
|
}
|
|
52318
|
-
return
|
|
53611
|
+
return json4({ succeeded, failed: body2.ids.length - succeeded });
|
|
52319
53612
|
} catch (e) {
|
|
52320
|
-
return
|
|
53613
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52321
53614
|
}
|
|
52322
53615
|
}
|
|
52323
|
-
function handleGetPlan(id, _ctx,
|
|
53616
|
+
function handleGetPlan(id, _ctx, json4, taskToSummary2) {
|
|
52324
53617
|
const plan = getPlan(id);
|
|
52325
53618
|
if (!plan)
|
|
52326
|
-
return
|
|
53619
|
+
return json4({ error: "Plan not found" }, 404);
|
|
52327
53620
|
const tasks = listTasks({ plan_id: id });
|
|
52328
|
-
return
|
|
53621
|
+
return json4({ ...plan, tasks: tasks.map((t) => taskToSummary2(t)) });
|
|
52329
53622
|
}
|
|
52330
|
-
async function handleUpdatePlan(id, req, _ctx,
|
|
53623
|
+
async function handleUpdatePlan(id, req, _ctx, json4) {
|
|
52331
53624
|
try {
|
|
52332
|
-
const
|
|
52333
|
-
const plan = updatePlan(id,
|
|
52334
|
-
return
|
|
53625
|
+
const body2 = await req.json();
|
|
53626
|
+
const plan = updatePlan(id, body2);
|
|
53627
|
+
return json4(plan);
|
|
52335
53628
|
} catch (e) {
|
|
52336
|
-
return
|
|
53629
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to update plan" }, 500);
|
|
52337
53630
|
}
|
|
52338
53631
|
}
|
|
52339
|
-
function handleDeletePlan(id, _ctx,
|
|
53632
|
+
function handleDeletePlan(id, _ctx, json4) {
|
|
52340
53633
|
const deleted = deletePlan(id);
|
|
52341
53634
|
if (!deleted)
|
|
52342
|
-
return
|
|
52343
|
-
return
|
|
53635
|
+
return json4({ error: "Plan not found" }, 404);
|
|
53636
|
+
return json4({ success: true });
|
|
52344
53637
|
}
|
|
52345
|
-
function handleStaticFiles(path, method, ctx,
|
|
53638
|
+
function handleStaticFiles(path, method, ctx, json4, serveStaticFile2) {
|
|
52346
53639
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
52347
53640
|
return null;
|
|
52348
53641
|
if (path !== "/") {
|
|
@@ -52350,7 +53643,7 @@ function handleStaticFiles(path, method, ctx, json3, serveStaticFile2) {
|
|
|
52350
53643
|
const resolvedFile = resolve16(filePath);
|
|
52351
53644
|
const resolvedBase = resolve16(ctx.dashboardDir);
|
|
52352
53645
|
if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
|
|
52353
|
-
return
|
|
53646
|
+
return json4({ error: "Forbidden" }, 403);
|
|
52354
53647
|
}
|
|
52355
53648
|
const res2 = serveStaticFile2(filePath);
|
|
52356
53649
|
if (res2)
|
|
@@ -52399,6 +53692,9 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
52399
53692
|
schemas: {
|
|
52400
53693
|
Task: taskSchema,
|
|
52401
53694
|
Project: projectSchema,
|
|
53695
|
+
TaskManifestBindingLookupRequest: taskManifestBindingLookupRequestSchema,
|
|
53696
|
+
TaskManifestBindingLookupResult: taskManifestBindingLookupResultSchema,
|
|
53697
|
+
TaskManifestBindingLookupResponse: taskManifestBindingLookupResponseSchema,
|
|
52402
53698
|
TaskList: taskListSchema,
|
|
52403
53699
|
ProjectTaskListEnsureReceipt: projectTaskListEnsureReceiptSchema,
|
|
52404
53700
|
ProjectTaskListEnsureResult: projectTaskListEnsureResultSchema,
|
|
@@ -53374,6 +54670,32 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
53374
54670
|
},
|
|
53375
54671
|
security: [{ apiKey: [] }],
|
|
53376
54672
|
paths: {
|
|
54673
|
+
"/v1/task-manifest/bindings/lookup": {
|
|
54674
|
+
post: {
|
|
54675
|
+
operationId: "lookupTaskManifestBinding",
|
|
54676
|
+
summary: "Recover one exact task-manifest apply receipt from its managed plan id",
|
|
54677
|
+
requestBody: {
|
|
54678
|
+
required: true,
|
|
54679
|
+
content: {
|
|
54680
|
+
"application/json": {
|
|
54681
|
+
schema: { $ref: "#/components/schemas/TaskManifestBindingLookupRequest" }
|
|
54682
|
+
}
|
|
54683
|
+
}
|
|
54684
|
+
},
|
|
54685
|
+
responses: {
|
|
54686
|
+
"200": {
|
|
54687
|
+
content: {
|
|
54688
|
+
"application/json": {
|
|
54689
|
+
schema: { $ref: "#/components/schemas/TaskManifestBindingLookupResponse" }
|
|
54690
|
+
}
|
|
54691
|
+
}
|
|
54692
|
+
},
|
|
54693
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
54694
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
54695
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
54696
|
+
}
|
|
54697
|
+
}
|
|
54698
|
+
},
|
|
53377
54699
|
"/v1/tasks": {
|
|
53378
54700
|
get: {
|
|
53379
54701
|
operationId: "listTasks",
|
|
@@ -54104,7 +55426,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
54104
55426
|
}
|
|
54105
55427
|
};
|
|
54106
55428
|
}
|
|
54107
|
-
var taskSchema, projectSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
|
|
55429
|
+
var taskSchema, projectSchema, taskManifestBindingLookupRequestSchema, taskManifestBindingLookupResultSchema, taskManifestBindingLookupResponseSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
|
|
54108
55430
|
var init_openapi = __esm(() => {
|
|
54109
55431
|
init_package_version();
|
|
54110
55432
|
init_types();
|
|
@@ -54140,6 +55462,51 @@ var init_openapi = __esm(() => {
|
|
|
54140
55462
|
updated_at: { type: "string" }
|
|
54141
55463
|
}
|
|
54142
55464
|
};
|
|
55465
|
+
taskManifestBindingLookupRequestSchema = {
|
|
55466
|
+
type: "object",
|
|
55467
|
+
additionalProperties: false,
|
|
55468
|
+
required: ["authority", "route", "schema_version", "tenant_id", "plan_id", "max_items"],
|
|
55469
|
+
properties: {
|
|
55470
|
+
authority: { type: "string", enum: ["todos"] },
|
|
55471
|
+
route: { type: "string", enum: ["todos.task-manifest.v1"] },
|
|
55472
|
+
schema_version: { type: "integer", enum: [1] },
|
|
55473
|
+
tenant_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
55474
|
+
plan_id: { type: "string", format: "uuid" },
|
|
55475
|
+
max_items: { type: "integer", enum: [1] }
|
|
55476
|
+
}
|
|
55477
|
+
};
|
|
55478
|
+
taskManifestBindingLookupResultSchema = {
|
|
55479
|
+
type: "object",
|
|
55480
|
+
additionalProperties: false,
|
|
55481
|
+
required: [
|
|
55482
|
+
"authority",
|
|
55483
|
+
"route",
|
|
55484
|
+
"schema_version",
|
|
55485
|
+
"tenant_id",
|
|
55486
|
+
"plan_id",
|
|
55487
|
+
"apply_receipt_id",
|
|
55488
|
+
"binding_version",
|
|
55489
|
+
"state"
|
|
55490
|
+
],
|
|
55491
|
+
properties: {
|
|
55492
|
+
authority: { type: "string", enum: ["todos"] },
|
|
55493
|
+
route: { type: "string", enum: ["todos.task-manifest.v1"] },
|
|
55494
|
+
schema_version: { type: "integer", enum: [1] },
|
|
55495
|
+
tenant_id: { type: "string" },
|
|
55496
|
+
plan_id: { type: "string", format: "uuid" },
|
|
55497
|
+
apply_receipt_id: { type: "string", format: "uuid" },
|
|
55498
|
+
binding_version: { type: "integer", minimum: 1 },
|
|
55499
|
+
state: { type: "string", enum: ["applied", "compensated"] }
|
|
55500
|
+
}
|
|
55501
|
+
};
|
|
55502
|
+
taskManifestBindingLookupResponseSchema = {
|
|
55503
|
+
type: "object",
|
|
55504
|
+
additionalProperties: false,
|
|
55505
|
+
required: ["result"],
|
|
55506
|
+
properties: {
|
|
55507
|
+
result: { $ref: "#/components/schemas/TaskManifestBindingLookupResult" }
|
|
55508
|
+
}
|
|
55509
|
+
};
|
|
54143
55510
|
taskListSchema = {
|
|
54144
55511
|
type: "object",
|
|
54145
55512
|
properties: {
|
|
@@ -54425,8 +55792,8 @@ var exports_pr_groups = {};
|
|
|
54425
55792
|
__export(exports_pr_groups, {
|
|
54426
55793
|
handlePrGroupHttpRequest: () => handlePrGroupHttpRequest
|
|
54427
55794
|
});
|
|
54428
|
-
function
|
|
54429
|
-
return new Response(JSON.stringify(
|
|
55795
|
+
function json4(body2, status2 = 200) {
|
|
55796
|
+
return new Response(JSON.stringify(body2), { status: status2, headers: JSON_HEADERS3 });
|
|
54430
55797
|
}
|
|
54431
55798
|
function errorStatus2(error) {
|
|
54432
55799
|
switch (error.code) {
|
|
@@ -54460,26 +55827,26 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
|
|
|
54460
55827
|
const method = req.method.toUpperCase();
|
|
54461
55828
|
try {
|
|
54462
55829
|
if (!groupId && method === "POST" && action === undefined) {
|
|
54463
|
-
return
|
|
55830
|
+
return json4({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
|
|
54464
55831
|
}
|
|
54465
55832
|
if (groupId === "admit" && !action) {
|
|
54466
55833
|
if (method !== "POST")
|
|
54467
|
-
return
|
|
54468
|
-
const
|
|
54469
|
-
if (!
|
|
54470
|
-
return
|
|
54471
|
-
return
|
|
55834
|
+
return json4({ error: "method not allowed" }, 405);
|
|
55835
|
+
const body2 = await readJson2(req);
|
|
55836
|
+
if (!body2)
|
|
55837
|
+
return json4({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
55838
|
+
return json4(await ledger.admit(body2), 201);
|
|
54472
55839
|
}
|
|
54473
55840
|
if (!groupId)
|
|
54474
|
-
return
|
|
55841
|
+
return json4({ error: "PR group id is required", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
54475
55842
|
if (!action && method === "GET") {
|
|
54476
|
-
return
|
|
55843
|
+
return json4({ view: await ledger.get(groupId) });
|
|
54477
55844
|
}
|
|
54478
55845
|
if (action === "events") {
|
|
54479
55846
|
if (method === "GET") {
|
|
54480
55847
|
const limit = url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined;
|
|
54481
55848
|
const afterSequence = url.searchParams.has("after_sequence") ? Number(url.searchParams.get("after_sequence")) : undefined;
|
|
54482
|
-
return
|
|
55849
|
+
return json4({
|
|
54483
55850
|
history: await ledger.events(groupId, {
|
|
54484
55851
|
...limit !== undefined ? { limit } : {},
|
|
54485
55852
|
...afterSequence !== undefined ? { after_sequence: afterSequence } : {}
|
|
@@ -54487,54 +55854,54 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
|
|
|
54487
55854
|
});
|
|
54488
55855
|
}
|
|
54489
55856
|
if (method === "POST") {
|
|
54490
|
-
const
|
|
54491
|
-
if (!
|
|
54492
|
-
return
|
|
54493
|
-
return
|
|
54494
|
-
...
|
|
55857
|
+
const body2 = await readJson2(req);
|
|
55858
|
+
if (!body2)
|
|
55859
|
+
return json4({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
55860
|
+
return json4(await ledger.append({
|
|
55861
|
+
...body2,
|
|
54495
55862
|
group_id: groupId,
|
|
54496
55863
|
authenticated_actor_id: principal?.actor_id ?? undefined,
|
|
54497
55864
|
authenticated_actor_run_id: principal?.actor_run_id ?? undefined
|
|
54498
55865
|
}), 201);
|
|
54499
55866
|
}
|
|
54500
|
-
return
|
|
55867
|
+
return json4({ error: "method not allowed" }, 405);
|
|
54501
55868
|
}
|
|
54502
55869
|
if (action === "recover") {
|
|
54503
55870
|
if (method !== "POST")
|
|
54504
|
-
return
|
|
54505
|
-
const
|
|
54506
|
-
if (!
|
|
54507
|
-
return
|
|
54508
|
-
return
|
|
54509
|
-
...
|
|
55871
|
+
return json4({ error: "method not allowed" }, 405);
|
|
55872
|
+
const body2 = await readJson2(req);
|
|
55873
|
+
if (!body2)
|
|
55874
|
+
return json4({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
55875
|
+
return json4(await ledger.recover({
|
|
55876
|
+
...body2,
|
|
54510
55877
|
group_id: groupId
|
|
54511
55878
|
}), 201);
|
|
54512
55879
|
}
|
|
54513
|
-
return
|
|
55880
|
+
return json4({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
|
|
54514
55881
|
} catch (cause) {
|
|
54515
55882
|
if (cause instanceof PrGroupLedgerError) {
|
|
54516
|
-
return
|
|
55883
|
+
return json4({
|
|
54517
55884
|
error: cause.message,
|
|
54518
55885
|
code: cause.code,
|
|
54519
55886
|
details: cause.details,
|
|
54520
55887
|
authoritative: true
|
|
54521
55888
|
}, errorStatus2(cause));
|
|
54522
55889
|
}
|
|
54523
|
-
return
|
|
55890
|
+
return json4({
|
|
54524
55891
|
error: cause instanceof Error ? cause.message : "internal PR-group error",
|
|
54525
55892
|
code: "PR_GROUP_ATOMICITY_UNAVAILABLE"
|
|
54526
55893
|
}, 500);
|
|
54527
55894
|
}
|
|
54528
55895
|
}
|
|
54529
|
-
var
|
|
55896
|
+
var JSON_HEADERS3;
|
|
54530
55897
|
var init_pr_groups = __esm(() => {
|
|
54531
55898
|
init_types3();
|
|
54532
|
-
|
|
55899
|
+
JSON_HEADERS3 = { "Content-Type": "application/json" };
|
|
54533
55900
|
});
|
|
54534
55901
|
|
|
54535
55902
|
// src/lib/comment-cursor.ts
|
|
54536
|
-
function encodeCommentCursor(
|
|
54537
|
-
return Buffer.from(JSON.stringify({ created_at:
|
|
55903
|
+
function encodeCommentCursor(comment2) {
|
|
55904
|
+
return Buffer.from(JSON.stringify({ created_at: comment2.created_at, id: comment2.id }), "utf8").toString("base64url");
|
|
54538
55905
|
}
|
|
54539
55906
|
function decodeCommentCursor(value) {
|
|
54540
55907
|
if (value.length > MAX_COMMENT_CURSOR_LENGTH)
|
|
@@ -54556,26 +55923,26 @@ function decodeCommentCursor(value) {
|
|
|
54556
55923
|
var MAX_COMMENT_CURSOR_LENGTH = 1024;
|
|
54557
55924
|
|
|
54558
55925
|
// src/lib/project-task-list-ensure.ts
|
|
54559
|
-
import { createHash as
|
|
54560
|
-
function
|
|
55926
|
+
import { createHash as createHash17 } from "crypto";
|
|
55927
|
+
function canonicalJson2(value) {
|
|
54561
55928
|
if (value === null || typeof value !== "object")
|
|
54562
55929
|
return JSON.stringify(value);
|
|
54563
55930
|
if (Array.isArray(value))
|
|
54564
|
-
return `[${value.map(
|
|
54565
|
-
return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([
|
|
55931
|
+
return `[${value.map(canonicalJson2).join(",")}]`;
|
|
55932
|
+
return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key2, item]) => `${JSON.stringify(key2)}:${canonicalJson2(item)}`).join(",")}}`;
|
|
54566
55933
|
}
|
|
54567
55934
|
function digest(value) {
|
|
54568
|
-
return
|
|
55935
|
+
return createHash17("sha256").update(canonicalJson2(value)).digest("hex");
|
|
54569
55936
|
}
|
|
54570
55937
|
function deriveIdempotencyKey(projectId, slug) {
|
|
54571
55938
|
return `ptlk_${digest({ project_id: projectId, slug }).slice(0, 48)}`;
|
|
54572
55939
|
}
|
|
54573
55940
|
function normalizeIdempotencyKey(value, projectId, slug) {
|
|
54574
|
-
const
|
|
54575
|
-
if (
|
|
55941
|
+
const key2 = value?.trim() || deriveIdempotencyKey(projectId, slug);
|
|
55942
|
+
if (key2.length < 8 || key2.length > 128 || !/^[A-Za-z0-9._:-]+$/.test(key2)) {
|
|
54576
55943
|
throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_IDEMPOTENCY_KEY_INVALID", "idempotency_key must be 8-128 ASCII letters, digits, dots, underscores, colons, or hyphens");
|
|
54577
55944
|
}
|
|
54578
|
-
return
|
|
55945
|
+
return key2;
|
|
54579
55946
|
}
|
|
54580
55947
|
function receiptId2(projectId, slug, idempotencyKey) {
|
|
54581
55948
|
return `ptlr_${digest({ project_id: projectId, slug, idempotency_key: idempotencyKey }).slice(0, 48)}`;
|
|
@@ -54838,7 +56205,7 @@ async function exactPlanProjectLinkState(store, planId, projectId) {
|
|
|
54838
56205
|
}
|
|
54839
56206
|
async function planPlanProjectLink(store, planId, projectId) {
|
|
54840
56207
|
const state = await exactPlanProjectLinkState(store, planId, projectId);
|
|
54841
|
-
const alreadyLinked = state.plan.project_id === state.project.id && state.tasks.every((
|
|
56208
|
+
const alreadyLinked = state.plan.project_id === state.project.id && state.tasks.every((task3) => task3.project_id === state.project.id);
|
|
54842
56209
|
return {
|
|
54843
56210
|
mode: "plan",
|
|
54844
56211
|
action: alreadyLinked ? "already_linked" : "would_link",
|
|
@@ -54850,14 +56217,14 @@ async function applyPlanProjectLink(store, planId, projectId, options) {
|
|
|
54850
56217
|
if (!store.planProjectLinks) {
|
|
54851
56218
|
throw new PlanProjectLinkError("PLAN_PROJECT_LINK_UNSUPPORTED", "This storage backend cannot atomically link an existing plan and its tasks", { storage_kind: store.kind });
|
|
54852
56219
|
}
|
|
54853
|
-
const
|
|
56220
|
+
const key2 = normalizePlanProjectLinkIdempotencyKey(options.idempotency_key);
|
|
54854
56221
|
return store.planProjectLinks.apply({
|
|
54855
56222
|
plan_id: planId,
|
|
54856
56223
|
project_id: projectId,
|
|
54857
56224
|
expected_plan_revision: options.expected_plan_revision,
|
|
54858
56225
|
expected_project_revision: options.expected_project_revision,
|
|
54859
|
-
idempotency_key:
|
|
54860
|
-
receipt_id: planProjectLinkReceiptId(
|
|
56226
|
+
idempotency_key: key2,
|
|
56227
|
+
receipt_id: planProjectLinkReceiptId(key2),
|
|
54861
56228
|
created_at: new Date().toISOString()
|
|
54862
56229
|
});
|
|
54863
56230
|
}
|
|
@@ -54886,11 +56253,11 @@ __export(exports_v1, {
|
|
|
54886
56253
|
handleV1Request: () => handleV1Request,
|
|
54887
56254
|
countSnapshotRecords: () => countSnapshotRecords
|
|
54888
56255
|
});
|
|
54889
|
-
function
|
|
54890
|
-
return new Response(JSON.stringify(
|
|
56256
|
+
function json5(body2, status2 = 200) {
|
|
56257
|
+
return new Response(JSON.stringify(body2), { status: status2, headers: JSON_HEADERS4 });
|
|
54891
56258
|
}
|
|
54892
|
-
function error(
|
|
54893
|
-
return
|
|
56259
|
+
function error(status2, message, extra) {
|
|
56260
|
+
return json5({ error: message, ...extra ?? {} }, status2);
|
|
54894
56261
|
}
|
|
54895
56262
|
function enumQueryParam(url, name, vocabulary) {
|
|
54896
56263
|
const raw = url.searchParams.get(name);
|
|
@@ -54919,112 +56286,112 @@ function parseSinceCursor(raw) {
|
|
|
54919
56286
|
function validateTaskCompletion(value) {
|
|
54920
56287
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
54921
56288
|
return { ok: false, message: "completion body must be an object" };
|
|
54922
|
-
const
|
|
56289
|
+
const body2 = value;
|
|
54923
56290
|
const allowed = new Set(["agent_id", "attachment_ids", "files_changed", "test_results", "commit_hash", "notes", "confidence"]);
|
|
54924
|
-
const unknown = Object.keys(
|
|
56291
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
54925
56292
|
if (unknown)
|
|
54926
56293
|
return { ok: false, message: `unknown completion field: ${unknown}` };
|
|
54927
|
-
if (
|
|
56294
|
+
if (body2.agent_id !== undefined && (typeof body2.agent_id !== "string" || !body2.agent_id.trim())) {
|
|
54928
56295
|
return { ok: false, message: "agent_id must be a non-empty string" };
|
|
54929
56296
|
}
|
|
54930
56297
|
for (const field of ["attachment_ids", "files_changed"]) {
|
|
54931
|
-
const value2 =
|
|
56298
|
+
const value2 = body2[field];
|
|
54932
56299
|
if (value2 !== undefined && (!Array.isArray(value2) || value2.some((item) => typeof item !== "string" || !item.trim()))) {
|
|
54933
56300
|
return { ok: false, message: `${field} must be an array of non-empty strings` };
|
|
54934
56301
|
}
|
|
54935
56302
|
}
|
|
54936
56303
|
for (const field of ["test_results", "commit_hash", "notes"]) {
|
|
54937
|
-
if (
|
|
56304
|
+
if (body2[field] !== undefined && typeof body2[field] !== "string") {
|
|
54938
56305
|
return { ok: false, message: `${field} must be a string` };
|
|
54939
56306
|
}
|
|
54940
56307
|
}
|
|
54941
|
-
if (
|
|
56308
|
+
if (body2.confidence !== undefined && (typeof body2.confidence !== "number" || !Number.isFinite(body2.confidence) || body2.confidence < 0 || body2.confidence > 1)) {
|
|
54942
56309
|
return { ok: false, message: "confidence must be a number between 0 and 1" };
|
|
54943
56310
|
}
|
|
54944
56311
|
return {
|
|
54945
56312
|
ok: true,
|
|
54946
|
-
...typeof
|
|
56313
|
+
...typeof body2.agent_id === "string" ? { agentId: body2.agent_id } : {},
|
|
54947
56314
|
options: {
|
|
54948
|
-
...Array.isArray(
|
|
54949
|
-
...Array.isArray(
|
|
54950
|
-
...typeof
|
|
54951
|
-
...typeof
|
|
54952
|
-
...typeof
|
|
54953
|
-
...typeof
|
|
56315
|
+
...Array.isArray(body2.attachment_ids) ? { attachment_ids: body2.attachment_ids } : {},
|
|
56316
|
+
...Array.isArray(body2.files_changed) ? { files_changed: body2.files_changed } : {},
|
|
56317
|
+
...typeof body2.test_results === "string" ? { test_results: body2.test_results } : {},
|
|
56318
|
+
...typeof body2.commit_hash === "string" ? { commit_hash: body2.commit_hash } : {},
|
|
56319
|
+
...typeof body2.notes === "string" ? { notes: body2.notes } : {},
|
|
56320
|
+
...typeof body2.confidence === "number" ? { confidence: body2.confidence } : {}
|
|
54954
56321
|
}
|
|
54955
56322
|
};
|
|
54956
56323
|
}
|
|
54957
56324
|
function validateProjectPatch(value) {
|
|
54958
56325
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
54959
56326
|
return { ok: false, message: "project patch must be an object" };
|
|
54960
|
-
const
|
|
56327
|
+
const body2 = value;
|
|
54961
56328
|
const allowed = new Set(["name", "path", "description"]);
|
|
54962
|
-
const unknown = Object.keys(
|
|
56329
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
54963
56330
|
if (unknown)
|
|
54964
56331
|
return { ok: false, message: `unknown project field: ${unknown}` };
|
|
54965
|
-
if (Object.keys(
|
|
56332
|
+
if (Object.keys(body2).length === 0)
|
|
54966
56333
|
return { ok: false, message: "project patch must not be empty" };
|
|
54967
|
-
if (
|
|
56334
|
+
if (body2["name"] !== undefined && (typeof body2["name"] !== "string" || !body2["name"].trim()))
|
|
54968
56335
|
return { ok: false, message: "name must be a non-empty string" };
|
|
54969
|
-
if (
|
|
56336
|
+
if (body2["path"] !== undefined && (typeof body2["path"] !== "string" || !body2["path"].trim()))
|
|
54970
56337
|
return { ok: false, message: "path must be a non-empty string" };
|
|
54971
|
-
if (
|
|
56338
|
+
if (body2["description"] !== undefined && body2["description"] !== null && typeof body2["description"] !== "string")
|
|
54972
56339
|
return { ok: false, message: "description must be a string or null" };
|
|
54973
|
-
return { ok: true, patch:
|
|
56340
|
+
return { ok: true, patch: body2 };
|
|
54974
56341
|
}
|
|
54975
56342
|
function validateProjectCreate(value) {
|
|
54976
56343
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
54977
56344
|
return { ok: false, message: "project body must be an object" };
|
|
54978
|
-
const
|
|
56345
|
+
const body2 = value;
|
|
54979
56346
|
const allowed = new Set(["name", "path", "description", "task_list_id", "task_prefix"]);
|
|
54980
|
-
const unknown = Object.keys(
|
|
56347
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
54981
56348
|
if (unknown)
|
|
54982
56349
|
return { ok: false, message: `unknown project field: ${unknown}` };
|
|
54983
|
-
if (typeof
|
|
56350
|
+
if (typeof body2["name"] !== "string" || !body2["name"].trim())
|
|
54984
56351
|
return { ok: false, message: "name must be a non-empty string" };
|
|
54985
|
-
if (!normalizeSlug(
|
|
56352
|
+
if (!normalizeSlug(body2["name"]))
|
|
54986
56353
|
return { ok: false, message: "name must produce a non-empty canonical slug" };
|
|
54987
|
-
if (typeof
|
|
56354
|
+
if (typeof body2["path"] !== "string" || !body2["path"].trim())
|
|
54988
56355
|
return { ok: false, message: "path must be a non-empty string" };
|
|
54989
|
-
if (
|
|
56356
|
+
if (body2["description"] !== undefined && typeof body2["description"] !== "string")
|
|
54990
56357
|
return { ok: false, message: "description must be a string" };
|
|
54991
|
-
if (
|
|
56358
|
+
if (body2["task_list_id"] !== undefined && !isCanonicalSlug(body2["task_list_id"])) {
|
|
54992
56359
|
return { ok: false, message: "task_list_id must be non-empty canonical kebab-case" };
|
|
54993
56360
|
}
|
|
54994
|
-
if (
|
|
56361
|
+
if (body2["task_prefix"] !== undefined && (typeof body2["task_prefix"] !== "string" || !body2["task_prefix"].trim())) {
|
|
54995
56362
|
return { ok: false, message: "task_prefix must be a non-empty string" };
|
|
54996
56363
|
}
|
|
54997
|
-
return { ok: true, input:
|
|
56364
|
+
return { ok: true, input: body2 };
|
|
54998
56365
|
}
|
|
54999
56366
|
function validatePlanCreate(value) {
|
|
55000
56367
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
55001
56368
|
return { ok: false, message: "plan body must be an object" };
|
|
55002
|
-
const
|
|
56369
|
+
const body2 = value;
|
|
55003
56370
|
const allowed = new Set(["title", "name", "slug", "description", "project_id", "task_list_id", "agent_id", "status"]);
|
|
55004
|
-
const unknown = Object.keys(
|
|
56371
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55005
56372
|
if (unknown)
|
|
55006
56373
|
return { ok: false, message: `unknown plan field: ${unknown}` };
|
|
55007
|
-
if (
|
|
56374
|
+
if (body2.name !== undefined && (typeof body2.name !== "string" || !body2.name.trim()))
|
|
55008
56375
|
return { ok: false, message: "name must be a non-empty string" };
|
|
55009
|
-
if (
|
|
56376
|
+
if (body2.title !== undefined && (typeof body2.title !== "string" || !body2.title.trim()))
|
|
55010
56377
|
return { ok: false, message: "title must be a non-empty string" };
|
|
55011
|
-
if (typeof
|
|
56378
|
+
if (typeof body2.name === "string" && typeof body2.title === "string" && body2.name !== body2.title) {
|
|
55012
56379
|
return { ok: false, message: "name and title must match when both are provided" };
|
|
55013
56380
|
}
|
|
55014
|
-
const name =
|
|
56381
|
+
const name = body2.name ?? body2.title;
|
|
55015
56382
|
if (!name)
|
|
55016
56383
|
return { ok: false, message: "name is required" };
|
|
55017
56384
|
for (const field of ["slug", "project_id", "task_list_id", "agent_id"]) {
|
|
55018
|
-
if (
|
|
56385
|
+
if (body2[field] !== undefined && (typeof body2[field] !== "string" || !body2[field].trim())) {
|
|
55019
56386
|
return { ok: false, message: `${field} must be a non-empty string` };
|
|
55020
56387
|
}
|
|
55021
56388
|
}
|
|
55022
|
-
const slug = typeof
|
|
55023
|
-
if (
|
|
56389
|
+
const slug = typeof body2.slug === "string" ? normalizeSlug(body2.slug) : undefined;
|
|
56390
|
+
if (body2.slug !== undefined && !slug)
|
|
55024
56391
|
return { ok: false, message: "slug must produce a non-empty canonical slug" };
|
|
55025
|
-
if (
|
|
56392
|
+
if (body2.description !== undefined && typeof body2.description !== "string")
|
|
55026
56393
|
return { ok: false, message: "description must be a string" };
|
|
55027
|
-
if (
|
|
56394
|
+
if (body2.status !== undefined && (typeof body2.status !== "string" || !["active", "completed", "archived"].includes(body2.status))) {
|
|
55028
56395
|
return { ok: false, message: "status must be active, completed, or archived" };
|
|
55029
56396
|
}
|
|
55030
56397
|
return {
|
|
@@ -55032,102 +56399,102 @@ function validatePlanCreate(value) {
|
|
|
55032
56399
|
input: {
|
|
55033
56400
|
name,
|
|
55034
56401
|
...slug ? { slug } : {},
|
|
55035
|
-
...typeof
|
|
55036
|
-
...typeof
|
|
55037
|
-
...typeof
|
|
55038
|
-
...typeof
|
|
55039
|
-
...typeof
|
|
56402
|
+
...typeof body2.description === "string" ? { description: body2.description } : {},
|
|
56403
|
+
...typeof body2.project_id === "string" ? { project_id: body2.project_id } : {},
|
|
56404
|
+
...typeof body2.task_list_id === "string" ? { task_list_id: body2.task_list_id } : {},
|
|
56405
|
+
...typeof body2.agent_id === "string" ? { agent_id: body2.agent_id } : {},
|
|
56406
|
+
...typeof body2.status === "string" ? { status: body2.status } : {}
|
|
55040
56407
|
}
|
|
55041
56408
|
};
|
|
55042
56409
|
}
|
|
55043
56410
|
function validateTemplateTask(value) {
|
|
55044
56411
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
55045
56412
|
return null;
|
|
55046
|
-
const
|
|
56413
|
+
const body2 = value;
|
|
55047
56414
|
const allowed = new Set(["position", "title_pattern", "description", "priority", "tags", "task_type", "condition", "include_template_id", "depends_on", "depends_on_positions", "metadata"]);
|
|
55048
|
-
if (Object.keys(
|
|
56415
|
+
if (Object.keys(body2).some((key2) => !allowed.has(key2)))
|
|
55049
56416
|
return null;
|
|
55050
|
-
if (typeof
|
|
56417
|
+
if (typeof body2.title_pattern !== "string" || !body2.title_pattern.trim())
|
|
55051
56418
|
return null;
|
|
55052
|
-
if (
|
|
56419
|
+
if (body2.position !== undefined && (typeof body2.position !== "number" || !Number.isSafeInteger(body2.position) || body2.position < 0))
|
|
55053
56420
|
return null;
|
|
55054
|
-
if (
|
|
56421
|
+
if (body2.description !== undefined && body2.description !== null && typeof body2.description !== "string")
|
|
55055
56422
|
return null;
|
|
55056
|
-
if (
|
|
56423
|
+
if (body2.priority !== undefined && (typeof body2.priority !== "string" || !["low", "medium", "high", "critical"].includes(body2.priority)))
|
|
55057
56424
|
return null;
|
|
55058
|
-
if (
|
|
56425
|
+
if (body2.tags !== undefined && (!Array.isArray(body2.tags) || body2.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
55059
56426
|
return null;
|
|
55060
56427
|
for (const field of ["task_type", "condition", "include_template_id"]) {
|
|
55061
|
-
if (
|
|
56428
|
+
if (body2[field] !== undefined && body2[field] !== null && (typeof body2[field] !== "string" || !body2[field].trim()))
|
|
55062
56429
|
return null;
|
|
55063
56430
|
}
|
|
55064
|
-
if (
|
|
56431
|
+
if (body2.depends_on !== undefined && body2.depends_on_positions !== undefined)
|
|
55065
56432
|
return null;
|
|
55066
|
-
const dependencies =
|
|
56433
|
+
const dependencies = body2.depends_on ?? body2.depends_on_positions;
|
|
55067
56434
|
if (dependencies !== undefined && (!Array.isArray(dependencies) || dependencies.some((position) => !Number.isSafeInteger(position) || position < 0)))
|
|
55068
56435
|
return null;
|
|
55069
|
-
if (
|
|
56436
|
+
if (body2.metadata !== undefined && (!body2.metadata || typeof body2.metadata !== "object" || Array.isArray(body2.metadata)))
|
|
55070
56437
|
return null;
|
|
55071
56438
|
return {
|
|
55072
|
-
title_pattern:
|
|
55073
|
-
...typeof
|
|
55074
|
-
...typeof
|
|
55075
|
-
...Array.isArray(
|
|
55076
|
-
...typeof
|
|
55077
|
-
...typeof
|
|
55078
|
-
...typeof
|
|
56439
|
+
title_pattern: body2.title_pattern,
|
|
56440
|
+
...typeof body2.description === "string" ? { description: body2.description } : {},
|
|
56441
|
+
...typeof body2.priority === "string" ? { priority: body2.priority } : {},
|
|
56442
|
+
...Array.isArray(body2.tags) ? { tags: body2.tags } : {},
|
|
56443
|
+
...typeof body2.task_type === "string" ? { task_type: body2.task_type } : {},
|
|
56444
|
+
...typeof body2.condition === "string" ? { condition: body2.condition } : {},
|
|
56445
|
+
...typeof body2.include_template_id === "string" ? { include_template_id: body2.include_template_id } : {},
|
|
55079
56446
|
...Array.isArray(dependencies) ? { depends_on: dependencies } : {},
|
|
55080
|
-
...
|
|
56447
|
+
...body2.metadata && typeof body2.metadata === "object" && !Array.isArray(body2.metadata) ? { metadata: body2.metadata } : {}
|
|
55081
56448
|
};
|
|
55082
56449
|
}
|
|
55083
56450
|
function validateTemplateCreate(value) {
|
|
55084
56451
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
55085
56452
|
return { ok: false, message: "template body must be an object" };
|
|
55086
|
-
const
|
|
56453
|
+
const body2 = value;
|
|
55087
56454
|
const allowed = new Set(["name", "title_pattern", "description", "priority", "tags", "variables", "project_id", "plan_id", "metadata", "tasks"]);
|
|
55088
|
-
const unknown = Object.keys(
|
|
56455
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55089
56456
|
if (unknown)
|
|
55090
56457
|
return { ok: false, message: `unknown template field: ${unknown}` };
|
|
55091
|
-
if (typeof
|
|
56458
|
+
if (typeof body2.name !== "string" || !body2.name.trim())
|
|
55092
56459
|
return { ok: false, message: "name must be a non-empty string" };
|
|
55093
|
-
if (typeof
|
|
56460
|
+
if (typeof body2.title_pattern !== "string" || !body2.title_pattern.trim())
|
|
55094
56461
|
return { ok: false, message: "title_pattern must be a non-empty string" };
|
|
55095
|
-
if (
|
|
56462
|
+
if (body2.description !== undefined && body2.description !== null && typeof body2.description !== "string")
|
|
55096
56463
|
return { ok: false, message: "description must be a string or null" };
|
|
55097
|
-
if (
|
|
56464
|
+
if (body2.priority !== undefined && (typeof body2.priority !== "string" || !["low", "medium", "high", "critical"].includes(body2.priority)))
|
|
55098
56465
|
return { ok: false, message: "priority must be low, medium, high, or critical" };
|
|
55099
|
-
if (
|
|
56466
|
+
if (body2.tags !== undefined && (!Array.isArray(body2.tags) || body2.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
55100
56467
|
return { ok: false, message: "tags must be an array of non-empty strings" };
|
|
55101
|
-
if (
|
|
56468
|
+
if (body2.variables !== undefined && (!Array.isArray(body2.variables) || body2.variables.some((variable) => !variable || typeof variable !== "object" || Array.isArray(variable) || typeof variable.name !== "string" || !variable.name || typeof variable.required !== "boolean" || variable.default !== undefined && typeof variable.default !== "string" || variable.description !== undefined && typeof variable.description !== "string"))) {
|
|
55102
56469
|
return { ok: false, message: "variables must be valid template variable objects" };
|
|
55103
56470
|
}
|
|
55104
56471
|
for (const field of ["project_id", "plan_id"]) {
|
|
55105
|
-
if (
|
|
56472
|
+
if (body2[field] !== undefined && body2[field] !== null && (typeof body2[field] !== "string" || !body2[field].trim()))
|
|
55106
56473
|
return { ok: false, message: `${field} must be a non-empty string or null` };
|
|
55107
56474
|
}
|
|
55108
|
-
if (
|
|
56475
|
+
if (body2.metadata !== undefined && (!body2.metadata || typeof body2.metadata !== "object" || Array.isArray(body2.metadata)))
|
|
55109
56476
|
return { ok: false, message: "metadata must be an object" };
|
|
55110
|
-
const tasks =
|
|
55111
|
-
if (tasks === null || tasks.some((
|
|
56477
|
+
const tasks = body2.tasks === undefined ? [] : Array.isArray(body2.tasks) ? body2.tasks.map(validateTemplateTask) : null;
|
|
56478
|
+
if (tasks === null || tasks.some((task3) => task3 === null))
|
|
55112
56479
|
return { ok: false, message: "tasks must be valid template task objects" };
|
|
55113
56480
|
const taskInputs = tasks;
|
|
55114
|
-
for (const [position,
|
|
55115
|
-
if ((
|
|
56481
|
+
for (const [position, task3] of taskInputs.entries()) {
|
|
56482
|
+
if ((task3.depends_on ?? []).some((dependency2) => dependency2 >= position)) {
|
|
55116
56483
|
return { ok: false, message: "template task dependencies must reference earlier task positions" };
|
|
55117
56484
|
}
|
|
55118
56485
|
}
|
|
55119
56486
|
return {
|
|
55120
56487
|
ok: true,
|
|
55121
56488
|
input: {
|
|
55122
|
-
name:
|
|
55123
|
-
title_pattern:
|
|
55124
|
-
...typeof
|
|
55125
|
-
...typeof
|
|
55126
|
-
...Array.isArray(
|
|
55127
|
-
...Array.isArray(
|
|
55128
|
-
...typeof
|
|
55129
|
-
...typeof
|
|
55130
|
-
...
|
|
56489
|
+
name: body2.name,
|
|
56490
|
+
title_pattern: body2.title_pattern,
|
|
56491
|
+
...typeof body2.description === "string" ? { description: body2.description } : {},
|
|
56492
|
+
...typeof body2.priority === "string" ? { priority: body2.priority } : {},
|
|
56493
|
+
...Array.isArray(body2.tags) ? { tags: body2.tags } : {},
|
|
56494
|
+
...Array.isArray(body2.variables) ? { variables: body2.variables } : {},
|
|
56495
|
+
...typeof body2.project_id === "string" ? { project_id: body2.project_id } : {},
|
|
56496
|
+
...typeof body2.plan_id === "string" ? { plan_id: body2.plan_id } : {},
|
|
56497
|
+
...body2.metadata && typeof body2.metadata === "object" && !Array.isArray(body2.metadata) ? { metadata: body2.metadata } : {},
|
|
55131
56498
|
tasks: taskInputs
|
|
55132
56499
|
}
|
|
55133
56500
|
};
|
|
@@ -55135,25 +56502,25 @@ function validateTemplateCreate(value) {
|
|
|
55135
56502
|
function validateTemplatePatch(value) {
|
|
55136
56503
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
55137
56504
|
return { ok: false, message: "template patch must be an object" };
|
|
55138
|
-
const
|
|
56505
|
+
const body2 = value;
|
|
55139
56506
|
const allowed = new Set(["name", "title_pattern", "description", "priority", "tags", "variables", "project_id", "plan_id", "metadata"]);
|
|
55140
|
-
const unknown = Object.keys(
|
|
56507
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55141
56508
|
if (unknown)
|
|
55142
56509
|
return { ok: false, message: `unknown template field: ${unknown}` };
|
|
55143
|
-
if (Object.keys(
|
|
56510
|
+
if (Object.keys(body2).length === 0)
|
|
55144
56511
|
return { ok: false, message: "template patch must not be empty" };
|
|
55145
|
-
const templateLike = { name:
|
|
56512
|
+
const templateLike = { name: body2.name ?? "template", title_pattern: body2.title_pattern ?? "template", ...body2 };
|
|
55146
56513
|
const validated = validateTemplateCreate(templateLike);
|
|
55147
56514
|
if (!validated.ok)
|
|
55148
56515
|
return validated;
|
|
55149
56516
|
const { name: _name, title_pattern: _title, tasks: _tasks, ...patch } = validated.input;
|
|
55150
56517
|
return { ok: true, patch: {
|
|
55151
|
-
...
|
|
55152
|
-
...
|
|
56518
|
+
...body2.name !== undefined ? { name: validated.input.name } : {},
|
|
56519
|
+
...body2.title_pattern !== undefined ? { title_pattern: validated.input.title_pattern } : {},
|
|
55153
56520
|
...patch,
|
|
55154
|
-
...
|
|
55155
|
-
...
|
|
55156
|
-
...
|
|
56521
|
+
...body2.description === null ? { description: null } : {},
|
|
56522
|
+
...body2.project_id === null ? { project_id: null } : {},
|
|
56523
|
+
...body2.plan_id === null ? { plan_id: null } : {}
|
|
55157
56524
|
} };
|
|
55158
56525
|
}
|
|
55159
56526
|
async function readJson3(req) {
|
|
@@ -55176,29 +56543,29 @@ async function readOptionalJson(req) {
|
|
|
55176
56543
|
return { ok: false };
|
|
55177
56544
|
}
|
|
55178
56545
|
}
|
|
55179
|
-
function contextFromPrincipal(principal,
|
|
55180
|
-
const agentId =
|
|
56546
|
+
function contextFromPrincipal(principal, body2) {
|
|
56547
|
+
const agentId = body2?.agent_id || principal.agent || undefined;
|
|
55181
56548
|
return agentId ? { agentId } : {};
|
|
55182
56549
|
}
|
|
55183
|
-
function redactComment3(
|
|
55184
|
-
return { ...
|
|
56550
|
+
function redactComment3(comment2) {
|
|
56551
|
+
return { ...comment2, content: redactEvidenceText(comment2.content) };
|
|
55185
56552
|
}
|
|
55186
56553
|
function normalizeImportSnapshot(raw) {
|
|
55187
|
-
const
|
|
56554
|
+
const body2 = raw && typeof raw === "object" ? raw : {};
|
|
55188
56555
|
const arr = (v) => Array.isArray(v) ? v : [];
|
|
55189
56556
|
return {
|
|
55190
|
-
exportedAt: typeof
|
|
55191
|
-
source: typeof
|
|
55192
|
-
tasks: arr(
|
|
55193
|
-
projects: arr(
|
|
55194
|
-
projectMachinePaths: arr(
|
|
55195
|
-
plans: arr(
|
|
55196
|
-
agents: arr(
|
|
55197
|
-
taskLists: arr(
|
|
55198
|
-
templates: arr(
|
|
55199
|
-
templateTasks: arr(
|
|
55200
|
-
auditHistory: arr(
|
|
55201
|
-
tombstones: arr(
|
|
56557
|
+
exportedAt: typeof body2["exportedAt"] === "string" ? body2["exportedAt"] : new Date().toISOString(),
|
|
56558
|
+
source: typeof body2["source"] === "string" ? body2["source"] : "sqlite",
|
|
56559
|
+
tasks: arr(body2["tasks"]),
|
|
56560
|
+
projects: arr(body2["projects"]),
|
|
56561
|
+
projectMachinePaths: arr(body2["projectMachinePaths"]),
|
|
56562
|
+
plans: arr(body2["plans"]),
|
|
56563
|
+
agents: arr(body2["agents"]),
|
|
56564
|
+
taskLists: arr(body2["taskLists"]),
|
|
56565
|
+
templates: arr(body2["templates"]),
|
|
56566
|
+
templateTasks: arr(body2["templateTasks"]),
|
|
56567
|
+
auditHistory: arr(body2["auditHistory"]),
|
|
56568
|
+
tombstones: arr(body2["tombstones"])
|
|
55202
56569
|
};
|
|
55203
56570
|
}
|
|
55204
56571
|
function countSnapshotRecords(s) {
|
|
@@ -55229,6 +56596,9 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55229
56596
|
if (path === "/v1/project-registration" || path.startsWith("/v1/project-registration/")) {
|
|
55230
56597
|
return handleTodosProjectRegistrationHttpRequest(req, url, (dependencies.getProjectRegistrationAuthority ?? getCloudProjectRegistrationAuthority)());
|
|
55231
56598
|
}
|
|
56599
|
+
if (path === "/v1/task-manifest" || path.startsWith("/v1/task-manifest/")) {
|
|
56600
|
+
return handleTodosTaskManifestHttpRequest(req, url, (dependencies.getTaskManifestAuthority ?? getCloudTaskManifestAuthority)());
|
|
56601
|
+
}
|
|
55232
56602
|
const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
|
|
55233
56603
|
const segments = path.split("/").filter(Boolean);
|
|
55234
56604
|
const resource = segments[1];
|
|
@@ -55240,8 +56610,8 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55240
56610
|
if (id === "exists" && !action) {
|
|
55241
56611
|
if (method !== "POST")
|
|
55242
56612
|
return error(405, `method ${method} not allowed on /v1/tasks/exists`);
|
|
55243
|
-
const
|
|
55244
|
-
const ids2 = Array.isArray(
|
|
56613
|
+
const body2 = await readJson3(req);
|
|
56614
|
+
const ids2 = Array.isArray(body2?.ids) ? Array.from(new Set(body2.ids.filter((v) => typeof v === "string" && v.length > 0))) : [];
|
|
55245
56615
|
if (ids2.length === 0)
|
|
55246
56616
|
return error(400, "provide a non-empty string array `ids`");
|
|
55247
56617
|
if (ids2.length > 5000)
|
|
@@ -55250,7 +56620,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55250
56620
|
const presentSet = new Set(found.map((t) => t.id));
|
|
55251
56621
|
const present = ids2.filter((i) => presentSet.has(i));
|
|
55252
56622
|
const missing = ids2.filter((i) => !presentSet.has(i));
|
|
55253
|
-
return
|
|
56623
|
+
return json5({
|
|
55254
56624
|
requested: ids2.length,
|
|
55255
56625
|
present_count: present.length,
|
|
55256
56626
|
missing_count: missing.length,
|
|
@@ -55263,20 +56633,20 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55263
56633
|
if (typeof store.tasks.getByFingerprint !== "function") {
|
|
55264
56634
|
return error(501, "fingerprint upsert is not supported by this storage backend");
|
|
55265
56635
|
}
|
|
55266
|
-
const
|
|
55267
|
-
const fingerprint3 = typeof
|
|
56636
|
+
const body2 = await readJson3(req) ?? {};
|
|
56637
|
+
const fingerprint3 = typeof body2.fingerprint === "string" ? body2.fingerprint.trim() : "";
|
|
55268
56638
|
if (!fingerprint3)
|
|
55269
56639
|
return error(400, "fingerprint is required");
|
|
55270
|
-
if (typeof
|
|
56640
|
+
if (typeof body2.title !== "string" || !body2.title.trim())
|
|
55271
56641
|
return error(400, "title is required");
|
|
55272
56642
|
const existing = await store.tasks.getByFingerprint(fingerprint3);
|
|
55273
56643
|
const metadata = {
|
|
55274
56644
|
...existing?.metadata ?? {},
|
|
55275
|
-
...
|
|
56645
|
+
...body2.metadata ?? {},
|
|
55276
56646
|
fingerprint: fingerprint3
|
|
55277
56647
|
};
|
|
55278
56648
|
const fields = { metadata };
|
|
55279
|
-
for (const
|
|
56649
|
+
for (const key2 of [
|
|
55280
56650
|
"title",
|
|
55281
56651
|
"description",
|
|
55282
56652
|
"priority",
|
|
@@ -55294,17 +56664,17 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55294
56664
|
"recurrence_rule",
|
|
55295
56665
|
"task_type"
|
|
55296
56666
|
]) {
|
|
55297
|
-
const bag =
|
|
55298
|
-
if (bag[
|
|
55299
|
-
fields[
|
|
56667
|
+
const bag = body2;
|
|
56668
|
+
if (bag[key2] !== undefined)
|
|
56669
|
+
fields[key2] = bag[key2];
|
|
55300
56670
|
}
|
|
55301
56671
|
if (!existing) {
|
|
55302
|
-
const
|
|
55303
|
-
return
|
|
56672
|
+
const task3 = await store.tasks.create({ ...fields, title: body2.title }, contextFromPrincipal(principal, body2));
|
|
56673
|
+
return json5({ task: task3, created: true }, 201);
|
|
55304
56674
|
}
|
|
55305
56675
|
try {
|
|
55306
|
-
const
|
|
55307
|
-
return
|
|
56676
|
+
const task3 = await store.tasks.update(existing.id, { ...fields, version: existing.version }, contextFromPrincipal(principal, body2));
|
|
56677
|
+
return json5({ task: task3, created: false });
|
|
55308
56678
|
} catch (e) {
|
|
55309
56679
|
const msg = e.message || "";
|
|
55310
56680
|
if (msg.includes("version conflict"))
|
|
@@ -55352,32 +56722,32 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55352
56722
|
const tasks = await store.tasks.list(filter);
|
|
55353
56723
|
const { limit: _l, offset: _o, ...countFilter } = filter;
|
|
55354
56724
|
const total = await store.tasks.count(countFilter);
|
|
55355
|
-
return
|
|
56725
|
+
return json5({ tasks, count: tasks.length, total });
|
|
55356
56726
|
}
|
|
55357
56727
|
if (method === "POST") {
|
|
55358
|
-
const
|
|
55359
|
-
if (!
|
|
56728
|
+
const body2 = await readJson3(req);
|
|
56729
|
+
if (!body2 || typeof body2.title !== "string" || !body2.title.trim()) {
|
|
55360
56730
|
return error(400, "title is required");
|
|
55361
56731
|
}
|
|
55362
|
-
const storageContext = contextFromPrincipal(principal,
|
|
55363
|
-
if (
|
|
55364
|
-
if (typeof
|
|
56732
|
+
const storageContext = contextFromPrincipal(principal, body2);
|
|
56733
|
+
if (body2.parent_id !== undefined) {
|
|
56734
|
+
if (typeof body2.parent_id !== "string" || !body2.parent_id.trim()) {
|
|
55365
56735
|
return error(400, "parent_id must be a non-empty task id", {
|
|
55366
56736
|
code: "PARENT_TASK_ID_INVALID"
|
|
55367
56737
|
});
|
|
55368
56738
|
}
|
|
55369
|
-
if (!await store.tasks.get(
|
|
55370
|
-
return error(404, `parent task not found: ${
|
|
56739
|
+
if (!await store.tasks.get(body2.parent_id, storageContext)) {
|
|
56740
|
+
return error(404, `parent task not found: ${body2.parent_id}`, {
|
|
55371
56741
|
code: "PARENT_TASK_NOT_FOUND"
|
|
55372
56742
|
});
|
|
55373
56743
|
}
|
|
55374
56744
|
}
|
|
55375
|
-
const created = await store.tasks.create(
|
|
56745
|
+
const created = await store.tasks.create(body2, storageContext);
|
|
55376
56746
|
const persisted = created?.id ? await store.tasks.get(created.id, storageContext) : null;
|
|
55377
|
-
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== (
|
|
56747
|
+
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== (body2.parent_id ?? null)) {
|
|
55378
56748
|
return error(500, "TASK_CREATE_PERSISTENCE_UNVERIFIED: task create was acknowledged but authoritative readback did not return the same stored task id and parent_id", { code: "TASK_CREATE_PERSISTENCE_UNVERIFIED" });
|
|
55379
56749
|
}
|
|
55380
|
-
return
|
|
56750
|
+
return json5({ task: persisted }, 201);
|
|
55381
56751
|
}
|
|
55382
56752
|
return error(405, `method ${method} not allowed on /v1/tasks`);
|
|
55383
56753
|
}
|
|
@@ -55394,7 +56764,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55394
56764
|
if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
|
|
55395
56765
|
return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
|
|
55396
56766
|
}
|
|
55397
|
-
return
|
|
56767
|
+
return json5({
|
|
55398
56768
|
comments: legacyPage,
|
|
55399
56769
|
count: legacyPage.length,
|
|
55400
56770
|
has_more: false,
|
|
@@ -55419,7 +56789,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55419
56789
|
const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
|
|
55420
56790
|
const hasMore = page.length > limit;
|
|
55421
56791
|
const comments = hasMore ? page.slice(1) : page;
|
|
55422
|
-
return
|
|
56792
|
+
return json5({
|
|
55423
56793
|
comments,
|
|
55424
56794
|
count: comments.length,
|
|
55425
56795
|
has_more: hasMore,
|
|
@@ -55427,22 +56797,22 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55427
56797
|
});
|
|
55428
56798
|
}
|
|
55429
56799
|
if (method === "POST") {
|
|
55430
|
-
const
|
|
55431
|
-
if (typeof
|
|
56800
|
+
const body3 = await readJson3(req) ?? {};
|
|
56801
|
+
if (typeof body3.content !== "string" || !body3.content.trim()) {
|
|
55432
56802
|
return error(400, "content is required");
|
|
55433
56803
|
}
|
|
55434
56804
|
const target = await store.tasks.get(id);
|
|
55435
56805
|
if (!target)
|
|
55436
56806
|
return error(404, "task not found");
|
|
55437
|
-
const
|
|
56807
|
+
const comment2 = await store.audit.addComment({
|
|
55438
56808
|
task_id: id,
|
|
55439
|
-
content:
|
|
55440
|
-
agent_id:
|
|
55441
|
-
session_id:
|
|
55442
|
-
type:
|
|
55443
|
-
progress_pct:
|
|
55444
|
-
}, contextFromPrincipal(principal,
|
|
55445
|
-
return
|
|
56809
|
+
content: body3.content,
|
|
56810
|
+
agent_id: body3.agent_id ?? principal.agent ?? undefined,
|
|
56811
|
+
session_id: body3.session_id,
|
|
56812
|
+
type: body3.type,
|
|
56813
|
+
progress_pct: body3.progress_pct
|
|
56814
|
+
}, contextFromPrincipal(principal, body3));
|
|
56815
|
+
return json5({ comment: redactComment3(comment2) }, 201);
|
|
55446
56816
|
}
|
|
55447
56817
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
|
|
55448
56818
|
}
|
|
@@ -55452,36 +56822,36 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55452
56822
|
if (!await store.tasks.get(id))
|
|
55453
56823
|
return error(404, "task not found");
|
|
55454
56824
|
const history = await store.audit.getTaskHistory(id);
|
|
55455
|
-
return
|
|
56825
|
+
return json5({ history, count: history.length });
|
|
55456
56826
|
}
|
|
55457
56827
|
if (action === "lock" || action === "unlock") {
|
|
55458
56828
|
if (method !== "POST")
|
|
55459
56829
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/${action}`);
|
|
55460
|
-
const
|
|
56830
|
+
const body3 = await readJson3(req) ?? {};
|
|
55461
56831
|
if (!await store.tasks.get(id))
|
|
55462
56832
|
return error(404, "task not found");
|
|
55463
56833
|
if (action === "lock") {
|
|
55464
56834
|
if (typeof store.tasks.lock !== "function")
|
|
55465
56835
|
return error(501, "task locking is not supported by this storage backend");
|
|
55466
|
-
const agentId3 =
|
|
55467
|
-
return
|
|
56836
|
+
const agentId3 = body3.agent_id || principal.agent || "todos-serve";
|
|
56837
|
+
return json5({ result: await store.tasks.lock(id, agentId3) });
|
|
55468
56838
|
}
|
|
55469
56839
|
if (typeof store.tasks.unlock !== "function")
|
|
55470
56840
|
return error(501, "task unlocking is not supported by this storage backend");
|
|
55471
|
-
if (
|
|
56841
|
+
if (body3.force === true) {
|
|
55472
56842
|
if (!principal.scopes.includes("todos:*"))
|
|
55473
56843
|
return error(403, "force unlock requires todos:* scope");
|
|
55474
56844
|
const released2 = await store.tasks.unlock(id);
|
|
55475
|
-
return
|
|
56845
|
+
return json5({ success: released2 });
|
|
55476
56846
|
}
|
|
55477
|
-
if (
|
|
56847
|
+
if (body3.agent_id && body3.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
|
|
55478
56848
|
return error(403, "unlock agent_id must match the authenticated agent");
|
|
55479
56849
|
}
|
|
55480
|
-
const agentId2 =
|
|
56850
|
+
const agentId2 = body3.agent_id || principal.agent;
|
|
55481
56851
|
if (!agentId2)
|
|
55482
56852
|
return error(403, "unlock requires an agent-bound key or force=true");
|
|
55483
56853
|
const released = await store.tasks.unlock(id, agentId2);
|
|
55484
|
-
return
|
|
56854
|
+
return json5({ success: released });
|
|
55485
56855
|
}
|
|
55486
56856
|
if (action === "dependencies") {
|
|
55487
56857
|
if (!store.dependencies)
|
|
@@ -55490,16 +56860,16 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55490
56860
|
if (!await store.tasks.get(id))
|
|
55491
56861
|
return error(404, "task not found");
|
|
55492
56862
|
const edges = await store.dependencies.list(id);
|
|
55493
|
-
return
|
|
56863
|
+
return json5(edges);
|
|
55494
56864
|
}
|
|
55495
56865
|
if (method === "POST") {
|
|
55496
|
-
const
|
|
55497
|
-
if (typeof
|
|
56866
|
+
const body3 = await readJson3(req) ?? {};
|
|
56867
|
+
if (typeof body3.depends_on !== "string" || !body3.depends_on.trim()) {
|
|
55498
56868
|
return error(400, "depends_on is required");
|
|
55499
56869
|
}
|
|
55500
56870
|
try {
|
|
55501
|
-
const
|
|
55502
|
-
return
|
|
56871
|
+
const dependency2 = await store.dependencies.add(id, body3.depends_on, contextFromPrincipal(principal));
|
|
56872
|
+
return json5({ dependency: dependency2 }, 201);
|
|
55503
56873
|
} catch (e) {
|
|
55504
56874
|
const msg = e.message || "";
|
|
55505
56875
|
if (msg.includes("not found"))
|
|
@@ -55513,7 +56883,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55513
56883
|
if (!subId)
|
|
55514
56884
|
return error(400, "dependency target id is required (/v1/tasks/:id/dependencies/:dep)");
|
|
55515
56885
|
const removed = await store.dependencies.remove(id, subId);
|
|
55516
|
-
return
|
|
56886
|
+
return json5({ removed });
|
|
55517
56887
|
}
|
|
55518
56888
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/dependencies`);
|
|
55519
56889
|
}
|
|
@@ -55524,23 +56894,23 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55524
56894
|
if (!await store.tasks.get(id))
|
|
55525
56895
|
return error(404, "task not found");
|
|
55526
56896
|
const verifications = await store.verifications.list(id);
|
|
55527
|
-
return
|
|
56897
|
+
return json5({ verifications, count: verifications.length });
|
|
55528
56898
|
}
|
|
55529
56899
|
if (method === "POST") {
|
|
55530
|
-
const
|
|
55531
|
-
if (typeof
|
|
56900
|
+
const body3 = await readJson3(req) ?? {};
|
|
56901
|
+
if (typeof body3.command !== "string" || !body3.command.trim()) {
|
|
55532
56902
|
return error(400, "command is required");
|
|
55533
56903
|
}
|
|
55534
56904
|
try {
|
|
55535
|
-
const
|
|
56905
|
+
const verification2 = await store.verifications.add({
|
|
55536
56906
|
task_id: id,
|
|
55537
|
-
command:
|
|
55538
|
-
status:
|
|
55539
|
-
output_summary:
|
|
55540
|
-
artifact_path:
|
|
55541
|
-
agent_id:
|
|
55542
|
-
}, contextFromPrincipal(principal,
|
|
55543
|
-
return
|
|
56907
|
+
command: body3.command,
|
|
56908
|
+
status: body3.status,
|
|
56909
|
+
output_summary: body3.output_summary,
|
|
56910
|
+
artifact_path: body3.artifact_path,
|
|
56911
|
+
agent_id: body3.agent_id
|
|
56912
|
+
}, contextFromPrincipal(principal, body3));
|
|
56913
|
+
return json5({ verification: verification2 }, 201);
|
|
55544
56914
|
} catch (e) {
|
|
55545
56915
|
const msg = e.message || "";
|
|
55546
56916
|
if (msg.includes("not found"))
|
|
@@ -55557,21 +56927,21 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55557
56927
|
if (!await store.tasks.get(id))
|
|
55558
56928
|
return error(404, "task not found");
|
|
55559
56929
|
const commits = await store.commits.list(id);
|
|
55560
|
-
return
|
|
56930
|
+
return json5({ commits, count: commits.length });
|
|
55561
56931
|
}
|
|
55562
56932
|
if (method === "POST") {
|
|
55563
|
-
const
|
|
55564
|
-
if (typeof
|
|
56933
|
+
const body3 = await readJson3(req) ?? {};
|
|
56934
|
+
if (typeof body3.sha !== "string" || !body3.sha.trim())
|
|
55565
56935
|
return error(400, "sha is required");
|
|
55566
56936
|
try {
|
|
55567
56937
|
const commit = await store.commits.add({
|
|
55568
56938
|
task_id: id,
|
|
55569
|
-
sha:
|
|
55570
|
-
message:
|
|
55571
|
-
author:
|
|
55572
|
-
files_changed: Array.isArray(
|
|
56939
|
+
sha: body3.sha,
|
|
56940
|
+
message: body3.message,
|
|
56941
|
+
author: body3.author,
|
|
56942
|
+
files_changed: Array.isArray(body3.files_changed) ? body3.files_changed : undefined
|
|
55573
56943
|
}, contextFromPrincipal(principal));
|
|
55574
|
-
return
|
|
56944
|
+
return json5({ commit }, 201);
|
|
55575
56945
|
} catch (e) {
|
|
55576
56946
|
const msg = e.message || "";
|
|
55577
56947
|
if (msg.includes("not found"))
|
|
@@ -55588,23 +56958,23 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55588
56958
|
if (!await store.tasks.get(id))
|
|
55589
56959
|
return error(404, "task not found");
|
|
55590
56960
|
const refs = await store.gitRefs.list(id);
|
|
55591
|
-
return
|
|
56961
|
+
return json5({ refs, count: refs.length });
|
|
55592
56962
|
}
|
|
55593
56963
|
if (method === "POST") {
|
|
55594
|
-
const
|
|
55595
|
-
const refType =
|
|
55596
|
-
if (typeof
|
|
56964
|
+
const body3 = await readJson3(req) ?? {};
|
|
56965
|
+
const refType = body3.ref_type === "pull_request" || body3.ref_type === "branch" ? body3.ref_type : "branch";
|
|
56966
|
+
if (typeof body3.name !== "string" || !body3.name.trim())
|
|
55597
56967
|
return error(400, "name is required");
|
|
55598
56968
|
try {
|
|
55599
56969
|
const ref = await store.gitRefs.add({
|
|
55600
56970
|
task_id: id,
|
|
55601
56971
|
ref_type: refType,
|
|
55602
|
-
name:
|
|
55603
|
-
url:
|
|
55604
|
-
provider:
|
|
55605
|
-
metadata:
|
|
56972
|
+
name: body3.name,
|
|
56973
|
+
url: body3.url,
|
|
56974
|
+
provider: body3.provider,
|
|
56975
|
+
metadata: body3.metadata
|
|
55606
56976
|
}, contextFromPrincipal(principal));
|
|
55607
|
-
return
|
|
56977
|
+
return json5({ ref }, 201);
|
|
55608
56978
|
} catch (e) {
|
|
55609
56979
|
const msg = e.message || "";
|
|
55610
56980
|
if (msg.includes("not found"))
|
|
@@ -55617,32 +56987,32 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55617
56987
|
const actionJson = await readOptionalJson(req);
|
|
55618
56988
|
if (!actionJson.ok)
|
|
55619
56989
|
return error(400, "invalid JSON body");
|
|
55620
|
-
const
|
|
55621
|
-
const agentId = typeof
|
|
56990
|
+
const body2 = actionJson.value && typeof actionJson.value === "object" && !Array.isArray(actionJson.value) ? actionJson.value : {};
|
|
56991
|
+
const agentId = typeof body2.agent_id === "string" ? body2.agent_id : principal.agent || "todos-serve";
|
|
55622
56992
|
if (action === "start" && method === "POST") {
|
|
55623
|
-
return
|
|
56993
|
+
return json5({ task: await store.tasks.start(id, agentId) });
|
|
55624
56994
|
}
|
|
55625
56995
|
if (action === "complete" && method === "POST") {
|
|
55626
56996
|
const parsed = validateTaskCompletion(actionJson.value);
|
|
55627
56997
|
if (!parsed.ok)
|
|
55628
56998
|
return error(400, parsed.message);
|
|
55629
|
-
return
|
|
55630
|
-
task: await store.tasks.complete(id, parsed.agentId || principal.agent || "todos-serve", parsed.options, contextFromPrincipal(principal,
|
|
56999
|
+
return json5({
|
|
57000
|
+
task: await store.tasks.complete(id, parsed.agentId || principal.agent || "todos-serve", parsed.options, contextFromPrincipal(principal, body2))
|
|
55631
57001
|
});
|
|
55632
57002
|
}
|
|
55633
57003
|
if (action === "fail" && method === "POST") {
|
|
55634
|
-
return
|
|
57004
|
+
return json5({ result: await store.tasks.fail(id, agentId, typeof body2.reason === "string" ? body2.reason : "failed", {}) });
|
|
55635
57005
|
}
|
|
55636
57006
|
if (action === "claim" && method === "POST") {
|
|
55637
|
-
return
|
|
57007
|
+
return json5({ task: await store.tasks.claimNext(agentId, {}) });
|
|
55638
57008
|
}
|
|
55639
57009
|
return error(404, `unknown task action: ${action}`);
|
|
55640
57010
|
}
|
|
55641
57011
|
if (method === "GET") {
|
|
55642
|
-
let
|
|
55643
|
-
if (!
|
|
57012
|
+
let task3 = await store.tasks.get(id);
|
|
57013
|
+
if (!task3 && typeof store.tasks.resolveRef === "function") {
|
|
55644
57014
|
try {
|
|
55645
|
-
|
|
57015
|
+
task3 = await store.tasks.resolveRef(id);
|
|
55646
57016
|
} catch (e) {
|
|
55647
57017
|
if (e instanceof TaskReferenceAmbiguousError) {
|
|
55648
57018
|
return error(409, e.message, {
|
|
@@ -55657,22 +57027,22 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55657
57027
|
throw e;
|
|
55658
57028
|
}
|
|
55659
57029
|
}
|
|
55660
|
-
return
|
|
57030
|
+
return task3 ? json5({ task: task3 }) : error(404, "task not found");
|
|
55661
57031
|
}
|
|
55662
57032
|
if (method === "PATCH" || method === "PUT") {
|
|
55663
|
-
const
|
|
55664
|
-
if (!
|
|
57033
|
+
const body2 = await readJson3(req);
|
|
57034
|
+
if (!body2)
|
|
55665
57035
|
return error(400, "invalid JSON body");
|
|
55666
57036
|
const current = await store.tasks.get(id);
|
|
55667
57037
|
if (!current)
|
|
55668
57038
|
return error(404, "task not found");
|
|
55669
57039
|
const patch = {
|
|
55670
|
-
...
|
|
55671
|
-
version: typeof
|
|
57040
|
+
...body2,
|
|
57041
|
+
version: typeof body2.version === "number" ? body2.version : current.version
|
|
55672
57042
|
};
|
|
55673
57043
|
try {
|
|
55674
|
-
const
|
|
55675
|
-
return
|
|
57044
|
+
const task3 = await store.tasks.update(id, patch);
|
|
57045
|
+
return task3 ? json5({ task: task3 }) : error(404, "task not found");
|
|
55676
57046
|
} catch (e) {
|
|
55677
57047
|
const msg = e.message || "";
|
|
55678
57048
|
if (msg.includes("version conflict"))
|
|
@@ -55682,7 +57052,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55682
57052
|
}
|
|
55683
57053
|
if (method === "DELETE") {
|
|
55684
57054
|
await store.tasks.delete(id, contextFromPrincipal(principal));
|
|
55685
|
-
return
|
|
57055
|
+
return json5({ deleted: true, id });
|
|
55686
57056
|
}
|
|
55687
57057
|
return error(405, `method ${method} not allowed on /v1/tasks/:id`);
|
|
55688
57058
|
}
|
|
@@ -55690,111 +57060,111 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55690
57060
|
if (!id) {
|
|
55691
57061
|
if (method === "GET") {
|
|
55692
57062
|
const projects = await store.projects.list();
|
|
55693
|
-
return
|
|
57063
|
+
return json5({ projects, count: projects.length });
|
|
55694
57064
|
}
|
|
55695
57065
|
if (method === "POST") {
|
|
55696
|
-
const
|
|
55697
|
-
if (!
|
|
57066
|
+
const body2 = await readJson3(req);
|
|
57067
|
+
if (!body2)
|
|
55698
57068
|
return error(400, "invalid JSON body");
|
|
55699
|
-
const validated = validateProjectCreate(
|
|
57069
|
+
const validated = validateProjectCreate(body2);
|
|
55700
57070
|
if (!validated.ok)
|
|
55701
57071
|
return error(400, validated.message);
|
|
55702
57072
|
const project = await store.projects.create(validated.input, contextFromPrincipal(principal));
|
|
55703
|
-
return
|
|
57073
|
+
return json5({ project }, 201);
|
|
55704
57074
|
}
|
|
55705
57075
|
return error(405, `method ${method} not allowed on /v1/projects`);
|
|
55706
57076
|
}
|
|
55707
57077
|
if (action === "task-list" && subId === "ensure") {
|
|
55708
57078
|
if (method === "GET") {
|
|
55709
|
-
return
|
|
57079
|
+
return json5(await planProjectTaskListEnsure(store, id));
|
|
55710
57080
|
}
|
|
55711
57081
|
if (method !== "POST") {
|
|
55712
57082
|
return error(405, `method ${method} not allowed on /v1/projects/:id/task-list/ensure`);
|
|
55713
57083
|
}
|
|
55714
|
-
const
|
|
55715
|
-
if (!
|
|
57084
|
+
const body2 = await readJson3(req);
|
|
57085
|
+
if (!body2)
|
|
55716
57086
|
return error(400, "invalid JSON body");
|
|
55717
|
-
const unknown = Object.keys(
|
|
57087
|
+
const unknown = Object.keys(body2).find((key2) => !["expected_project_revision", "idempotency_key"].includes(key2));
|
|
55718
57088
|
if (unknown)
|
|
55719
57089
|
return error(400, `unknown task-list ensure field: ${unknown}`);
|
|
55720
|
-
if (typeof
|
|
57090
|
+
if (typeof body2.expected_project_revision !== "string" || !body2.expected_project_revision.trim()) {
|
|
55721
57091
|
return error(400, "expected_project_revision must be a non-empty string from a fresh ensure plan");
|
|
55722
57092
|
}
|
|
55723
|
-
if (
|
|
57093
|
+
if (body2.idempotency_key !== undefined && typeof body2.idempotency_key !== "string") {
|
|
55724
57094
|
return error(400, "idempotency_key must be a string");
|
|
55725
57095
|
}
|
|
55726
57096
|
const result = await applyProjectTaskListEnsure(store, id, {
|
|
55727
|
-
expected_project_revision:
|
|
55728
|
-
...typeof
|
|
57097
|
+
expected_project_revision: body2.expected_project_revision,
|
|
57098
|
+
...typeof body2.idempotency_key === "string" ? { idempotency_key: body2.idempotency_key } : {}
|
|
55729
57099
|
});
|
|
55730
|
-
return
|
|
57100
|
+
return json5(result, result.action === "created" ? 201 : 200);
|
|
55731
57101
|
}
|
|
55732
57102
|
if (action === "task-list" && subId === "rollback") {
|
|
55733
57103
|
if (method !== "POST") {
|
|
55734
57104
|
return error(405, `method ${method} not allowed on /v1/projects/:id/task-list/rollback`);
|
|
55735
57105
|
}
|
|
55736
|
-
const
|
|
55737
|
-
if (!
|
|
57106
|
+
const body2 = await readJson3(req);
|
|
57107
|
+
if (!body2)
|
|
55738
57108
|
return error(400, "invalid JSON body");
|
|
55739
|
-
const unknown = Object.keys(
|
|
57109
|
+
const unknown = Object.keys(body2).find((key2) => !["receipt_id", "expected_task_list_revision"].includes(key2));
|
|
55740
57110
|
if (unknown)
|
|
55741
57111
|
return error(400, `unknown task-list rollback field: ${unknown}`);
|
|
55742
|
-
if (typeof
|
|
57112
|
+
if (typeof body2.receipt_id !== "string" || !body2.receipt_id.trim()) {
|
|
55743
57113
|
return error(400, "receipt_id must be a non-empty string");
|
|
55744
57114
|
}
|
|
55745
|
-
if (typeof
|
|
57115
|
+
if (typeof body2.expected_task_list_revision !== "string" || !body2.expected_task_list_revision.trim()) {
|
|
55746
57116
|
return error(400, "expected_task_list_revision must be a non-empty string from the accepted receipt");
|
|
55747
57117
|
}
|
|
55748
|
-
return
|
|
55749
|
-
receipt_id:
|
|
55750
|
-
expected_task_list_revision:
|
|
57118
|
+
return json5(await rollbackProjectTaskListEnsure(store, id, {
|
|
57119
|
+
receipt_id: body2.receipt_id,
|
|
57120
|
+
expected_task_list_revision: body2.expected_task_list_revision
|
|
55751
57121
|
}));
|
|
55752
57122
|
}
|
|
55753
57123
|
if (action === "rename") {
|
|
55754
57124
|
if (method !== "POST")
|
|
55755
57125
|
return error(405, `method ${method} not allowed on /v1/projects/:id/rename`);
|
|
55756
|
-
const
|
|
55757
|
-
if (!
|
|
57126
|
+
const body2 = await readJson3(req);
|
|
57127
|
+
if (!body2 || typeof body2.new_slug !== "string" || !body2.new_slug.trim() || !normalizeSlug(body2.new_slug)) {
|
|
55758
57128
|
return error(400, "new_slug must be a non-empty string");
|
|
55759
57129
|
}
|
|
55760
|
-
if (
|
|
57130
|
+
if (body2.name !== undefined && (typeof body2.name !== "string" || !body2.name.trim())) {
|
|
55761
57131
|
return error(400, "name must be a non-empty string");
|
|
55762
57132
|
}
|
|
55763
|
-
const unknownField = Object.keys(
|
|
57133
|
+
const unknownField = Object.keys(body2).find((key2) => !["new_slug", "name"].includes(key2));
|
|
55764
57134
|
if (unknownField)
|
|
55765
57135
|
return error(400, `unknown project rename field: ${unknownField}`);
|
|
55766
|
-
return
|
|
57136
|
+
return json5(await store.projects.rename(id, body2, contextFromPrincipal(principal)));
|
|
55767
57137
|
}
|
|
55768
57138
|
if (method === "GET") {
|
|
55769
57139
|
const project = await store.projects.get(id);
|
|
55770
|
-
return project ?
|
|
57140
|
+
return project ? json5({ project }) : error(404, "project not found");
|
|
55771
57141
|
}
|
|
55772
57142
|
if (method === "PATCH" || method === "PUT") {
|
|
55773
|
-
const
|
|
55774
|
-
if (!
|
|
57143
|
+
const body2 = await readJson3(req);
|
|
57144
|
+
if (!body2)
|
|
55775
57145
|
return error(400, "invalid JSON body");
|
|
55776
|
-
const validated = validateProjectPatch(
|
|
57146
|
+
const validated = validateProjectPatch(body2);
|
|
55777
57147
|
if (!validated.ok)
|
|
55778
57148
|
return error(400, validated.message);
|
|
55779
57149
|
if (!await store.projects.get(id))
|
|
55780
57150
|
return error(404, "project not found");
|
|
55781
57151
|
const project = await store.projects.update(id, validated.patch);
|
|
55782
|
-
return
|
|
57152
|
+
return json5({ project });
|
|
55783
57153
|
}
|
|
55784
57154
|
if (method === "DELETE") {
|
|
55785
57155
|
await store.projects.delete(id, contextFromPrincipal(principal));
|
|
55786
|
-
return
|
|
57156
|
+
return json5({ deleted: true, id });
|
|
55787
57157
|
}
|
|
55788
57158
|
return error(405, `method ${method} not allowed on /v1/projects/:id`);
|
|
55789
57159
|
}
|
|
55790
57160
|
if (resource === "plans") {
|
|
55791
57161
|
if (!id && method === "GET") {
|
|
55792
57162
|
const plans = await store.plans.list(url.searchParams.get("project_id") ?? undefined);
|
|
55793
|
-
return
|
|
57163
|
+
return json5({ plans, count: plans.length });
|
|
55794
57164
|
}
|
|
55795
57165
|
if (!id && method === "POST") {
|
|
55796
|
-
const
|
|
55797
|
-
const validated = validatePlanCreate(
|
|
57166
|
+
const body2 = await readJson3(req);
|
|
57167
|
+
const validated = validatePlanCreate(body2);
|
|
55798
57168
|
if (!validated.ok)
|
|
55799
57169
|
return error(400, validated.message);
|
|
55800
57170
|
if (validated.input.slug) {
|
|
@@ -55808,104 +57178,104 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55808
57178
|
}
|
|
55809
57179
|
}
|
|
55810
57180
|
const plan = await store.plans.create(validated.input, contextFromPrincipal(principal, validated.input));
|
|
55811
|
-
return
|
|
57181
|
+
return json5({ plan }, 201);
|
|
55812
57182
|
}
|
|
55813
57183
|
if (id && action === "project-link" && !subId) {
|
|
55814
57184
|
if (method === "GET") {
|
|
55815
57185
|
const projectId = url.searchParams.get("project_id");
|
|
55816
57186
|
if (!projectId?.trim())
|
|
55817
57187
|
return error(400, "project_id query parameter is required");
|
|
55818
|
-
return
|
|
57188
|
+
return json5(await planPlanProjectLink(store, id, projectId));
|
|
55819
57189
|
}
|
|
55820
57190
|
if (method !== "POST")
|
|
55821
57191
|
return error(405, `method ${method} not allowed on /v1/plans/:id/project-link`);
|
|
55822
|
-
const
|
|
55823
|
-
if (!
|
|
57192
|
+
const body2 = await readJson3(req);
|
|
57193
|
+
if (!body2)
|
|
55824
57194
|
return error(400, "invalid JSON body");
|
|
55825
57195
|
const allowed = new Set(["project_id", "expected_plan_revision", "expected_project_revision", "idempotency_key"]);
|
|
55826
|
-
const unknown = Object.keys(
|
|
57196
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55827
57197
|
if (unknown)
|
|
55828
57198
|
return error(400, `unknown plan-project-link field: ${unknown}`);
|
|
55829
57199
|
for (const field of ["project_id", "expected_plan_revision", "expected_project_revision", "idempotency_key"]) {
|
|
55830
|
-
if (typeof
|
|
57200
|
+
if (typeof body2[field] !== "string" || !body2[field].trim()) {
|
|
55831
57201
|
return error(400, `${field} must be a non-empty string`);
|
|
55832
57202
|
}
|
|
55833
57203
|
}
|
|
55834
|
-
const result = await applyPlanProjectLink(store, id,
|
|
55835
|
-
expected_plan_revision:
|
|
55836
|
-
expected_project_revision:
|
|
55837
|
-
idempotency_key:
|
|
57204
|
+
const result = await applyPlanProjectLink(store, id, body2.project_id, {
|
|
57205
|
+
expected_plan_revision: body2.expected_plan_revision,
|
|
57206
|
+
expected_project_revision: body2.expected_project_revision,
|
|
57207
|
+
idempotency_key: body2.idempotency_key
|
|
55838
57208
|
});
|
|
55839
|
-
return
|
|
57209
|
+
return json5(result, result.action === "linked" ? 201 : 200);
|
|
55840
57210
|
}
|
|
55841
57211
|
if (id && action === "project-link" && subId === "rollback") {
|
|
55842
57212
|
if (method !== "POST")
|
|
55843
57213
|
return error(405, `method ${method} not allowed on /v1/plans/:id/project-link/rollback`);
|
|
55844
|
-
const
|
|
55845
|
-
if (!
|
|
57214
|
+
const body2 = await readJson3(req);
|
|
57215
|
+
if (!body2)
|
|
55846
57216
|
return error(400, "invalid JSON body");
|
|
55847
57217
|
const allowed = new Set(["project_id", "receipt_id", "expected_plan_revision"]);
|
|
55848
|
-
const unknown = Object.keys(
|
|
57218
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55849
57219
|
if (unknown)
|
|
55850
57220
|
return error(400, `unknown plan-project-link rollback field: ${unknown}`);
|
|
55851
57221
|
for (const field of ["project_id", "receipt_id", "expected_plan_revision"]) {
|
|
55852
|
-
if (typeof
|
|
57222
|
+
if (typeof body2[field] !== "string" || !body2[field].trim()) {
|
|
55853
57223
|
return error(400, `${field} must be a non-empty string`);
|
|
55854
57224
|
}
|
|
55855
57225
|
}
|
|
55856
|
-
return
|
|
55857
|
-
receipt_id:
|
|
55858
|
-
expected_plan_revision:
|
|
57226
|
+
return json5(await rollbackPlanProjectLink(store, id, body2.project_id, {
|
|
57227
|
+
receipt_id: body2.receipt_id,
|
|
57228
|
+
expected_plan_revision: body2.expected_plan_revision
|
|
55859
57229
|
}));
|
|
55860
57230
|
}
|
|
55861
57231
|
if (id && method === "GET") {
|
|
55862
57232
|
const plan = await store.plans.get(id);
|
|
55863
|
-
return plan ?
|
|
57233
|
+
return plan ? json5({ plan }) : error(404, "plan not found");
|
|
55864
57234
|
}
|
|
55865
57235
|
if (id && (method === "PATCH" || method === "PUT")) {
|
|
55866
|
-
const
|
|
55867
|
-
if (!
|
|
57236
|
+
const body2 = await readJson3(req);
|
|
57237
|
+
if (!body2 || Object.keys(body2).length === 0)
|
|
55868
57238
|
return error(400, "plan patch is required");
|
|
55869
57239
|
const allowed = new Set(["name", "slug", "description", "status", "task_list_id", "agent_id"]);
|
|
55870
|
-
const unknownField = Object.keys(
|
|
57240
|
+
const unknownField = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55871
57241
|
if (unknownField)
|
|
55872
57242
|
return error(400, `unknown plan field: ${unknownField}`);
|
|
55873
57243
|
for (const field of ["name", "slug", "task_list_id", "agent_id"]) {
|
|
55874
|
-
if (
|
|
57244
|
+
if (body2[field] !== undefined && (typeof body2[field] !== "string" || !body2[field].trim())) {
|
|
55875
57245
|
return error(400, `${field} must be a non-empty string`);
|
|
55876
57246
|
}
|
|
55877
57247
|
}
|
|
55878
|
-
if (typeof
|
|
55879
|
-
const slug = normalizeSlug(
|
|
57248
|
+
if (typeof body2.slug === "string") {
|
|
57249
|
+
const slug = normalizeSlug(body2.slug);
|
|
55880
57250
|
if (!slug)
|
|
55881
57251
|
return error(400, "slug must produce a non-empty canonical slug");
|
|
55882
|
-
|
|
57252
|
+
body2.slug = slug;
|
|
55883
57253
|
}
|
|
55884
|
-
if (
|
|
57254
|
+
if (body2.description !== undefined && typeof body2.description !== "string") {
|
|
55885
57255
|
return error(400, "description must be a string");
|
|
55886
57256
|
}
|
|
55887
|
-
if (
|
|
57257
|
+
if (body2.status !== undefined && (typeof body2.status !== "string" || !["active", "completed", "archived"].includes(body2.status))) {
|
|
55888
57258
|
return error(400, "status must be active, completed, or archived");
|
|
55889
57259
|
}
|
|
55890
57260
|
const existing = await store.plans.get(id);
|
|
55891
57261
|
if (!existing)
|
|
55892
57262
|
return error(404, "plan not found");
|
|
55893
|
-
if (typeof
|
|
55894
|
-
const duplicate = (await store.plans.list(existing.project_id ?? undefined)).find((plan2) => plan2.id !== id && plan2.project_id === existing.project_id && plan2.slug ===
|
|
57263
|
+
if (typeof body2.slug === "string") {
|
|
57264
|
+
const duplicate = (await store.plans.list(existing.project_id ?? undefined)).find((plan2) => plan2.id !== id && plan2.project_id === existing.project_id && plan2.slug === body2.slug);
|
|
55895
57265
|
if (duplicate) {
|
|
55896
|
-
return error(409, `Plan slug already exists in this scope: ${
|
|
57266
|
+
return error(409, `Plan slug already exists in this scope: ${body2.slug}`, {
|
|
55897
57267
|
code: "PLAN_SLUG_CONFLICT",
|
|
55898
57268
|
conflict: true
|
|
55899
57269
|
});
|
|
55900
57270
|
}
|
|
55901
57271
|
}
|
|
55902
|
-
const plan = await store.plans.update(id,
|
|
55903
|
-
return
|
|
57272
|
+
const plan = await store.plans.update(id, body2);
|
|
57273
|
+
return json5({ plan });
|
|
55904
57274
|
}
|
|
55905
57275
|
if (id && method === "DELETE") {
|
|
55906
57276
|
if (!await store.plans.delete(id, contextFromPrincipal(principal)))
|
|
55907
57277
|
return error(404, "plan not found");
|
|
55908
|
-
return
|
|
57278
|
+
return json5({ deleted: true, id });
|
|
55909
57279
|
}
|
|
55910
57280
|
if (id)
|
|
55911
57281
|
return error(405, `method ${method} not allowed on /v1/plans/:id`);
|
|
@@ -55914,50 +57284,50 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55914
57284
|
if (!id && method === "GET") {
|
|
55915
57285
|
const projectId = url.searchParams.get("project_id");
|
|
55916
57286
|
const templates = (await store.templates.list()).filter((template) => projectId === null || template.project_id === projectId);
|
|
55917
|
-
return
|
|
57287
|
+
return json5({ templates, count: templates.length });
|
|
55918
57288
|
}
|
|
55919
57289
|
if (!id && method === "POST") {
|
|
55920
|
-
const
|
|
55921
|
-
const validated = validateTemplateCreate(
|
|
57290
|
+
const body2 = await readJson3(req);
|
|
57291
|
+
const validated = validateTemplateCreate(body2);
|
|
55922
57292
|
if (!validated.ok)
|
|
55923
57293
|
return error(400, validated.message);
|
|
55924
57294
|
const template = await store.templates.create(validated.input, contextFromPrincipal(principal));
|
|
55925
|
-
return
|
|
57295
|
+
return json5({ template: await store.templates.getWithTasks(template.id) }, 201);
|
|
55926
57296
|
}
|
|
55927
57297
|
if (!id)
|
|
55928
57298
|
return error(405, `method ${method} not allowed on /v1/templates`);
|
|
55929
57299
|
if (method === "GET") {
|
|
55930
57300
|
const template = await store.templates.getWithTasks(id);
|
|
55931
|
-
return template ?
|
|
57301
|
+
return template ? json5({ template }) : error(404, "template not found");
|
|
55932
57302
|
}
|
|
55933
57303
|
if (method === "PATCH" || method === "PUT") {
|
|
55934
|
-
const
|
|
55935
|
-
const validated = validateTemplatePatch(
|
|
57304
|
+
const body2 = await readJson3(req);
|
|
57305
|
+
const validated = validateTemplatePatch(body2);
|
|
55936
57306
|
if (!validated.ok)
|
|
55937
57307
|
return error(400, validated.message);
|
|
55938
57308
|
const template = await store.templates.update(id, validated.patch, contextFromPrincipal(principal));
|
|
55939
|
-
return template ?
|
|
57309
|
+
return template ? json5({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
|
|
55940
57310
|
}
|
|
55941
57311
|
if (method === "DELETE") {
|
|
55942
57312
|
const deleted = await store.templates.delete(id, contextFromPrincipal(principal));
|
|
55943
|
-
return deleted ?
|
|
57313
|
+
return deleted ? json5({ deleted: true, id }) : error(404, "template not found");
|
|
55944
57314
|
}
|
|
55945
57315
|
return error(405, `method ${method} not allowed on /v1/templates/:id`);
|
|
55946
57316
|
}
|
|
55947
57317
|
if (resource === "agents") {
|
|
55948
57318
|
if (!id && method === "GET") {
|
|
55949
57319
|
const agents = await store.agents.list();
|
|
55950
|
-
return
|
|
57320
|
+
return json5({ agents, count: agents.length });
|
|
55951
57321
|
}
|
|
55952
57322
|
if (!id && method === "POST") {
|
|
55953
|
-
const
|
|
55954
|
-
if (!
|
|
57323
|
+
const body2 = await readJson3(req);
|
|
57324
|
+
if (!body2 || typeof body2.name !== "string" || !body2.name.trim())
|
|
55955
57325
|
return error(400, "name is required");
|
|
55956
|
-
const result = await store.agents.register(
|
|
57326
|
+
const result = await store.agents.register(body2, contextFromPrincipal(principal));
|
|
55957
57327
|
if (result && typeof result === "object" && "conflict" in result) {
|
|
55958
57328
|
return error(409, result.message ?? "agent name conflict", { conflict: true });
|
|
55959
57329
|
}
|
|
55960
|
-
return
|
|
57330
|
+
return json5({ agent: result }, 201);
|
|
55961
57331
|
}
|
|
55962
57332
|
if (id && action === "heartbeat") {
|
|
55963
57333
|
if (method !== "POST")
|
|
@@ -55966,7 +57336,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55966
57336
|
return error(501, "agent heartbeat is not supported by this storage backend");
|
|
55967
57337
|
}
|
|
55968
57338
|
const agent = await store.agents.heartbeat(id, contextFromPrincipal(principal));
|
|
55969
|
-
return agent ?
|
|
57339
|
+
return agent ? json5({ agent }) : error(404, "agent not found");
|
|
55970
57340
|
}
|
|
55971
57341
|
if (id && action === "release") {
|
|
55972
57342
|
if (method !== "POST")
|
|
@@ -55974,18 +57344,18 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55974
57344
|
if (typeof store.agents.release !== "function") {
|
|
55975
57345
|
return error(501, "agent release is not supported by this storage backend");
|
|
55976
57346
|
}
|
|
55977
|
-
const
|
|
55978
|
-
const result = await store.agents.release(id,
|
|
57347
|
+
const body2 = await readJson3(req) ?? {};
|
|
57348
|
+
const result = await store.agents.release(id, body2.session_id, contextFromPrincipal(principal));
|
|
55979
57349
|
if (!result)
|
|
55980
57350
|
return error(404, "agent not found");
|
|
55981
57351
|
if (!result.released) {
|
|
55982
57352
|
return error(409, "release denied: session_id does not match agent's current session", { released: false });
|
|
55983
57353
|
}
|
|
55984
|
-
return
|
|
57354
|
+
return json5({ agent: result.agent, released: true });
|
|
55985
57355
|
}
|
|
55986
57356
|
if (id && method === "GET") {
|
|
55987
57357
|
const agent = await store.agents.get(id);
|
|
55988
|
-
return agent ?
|
|
57358
|
+
return agent ? json5({ agent }) : error(404, "agent not found");
|
|
55989
57359
|
}
|
|
55990
57360
|
}
|
|
55991
57361
|
if (resource === "activity" && !id) {
|
|
@@ -55994,66 +57364,66 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55994
57364
|
const limitParam = url.searchParams.get("limit");
|
|
55995
57365
|
const limit = limitParam ? Math.max(1, Math.min(1e4, Number(limitParam) || 50)) : 50;
|
|
55996
57366
|
const activity = await store.audit.getRecentActivity(limit);
|
|
55997
|
-
return
|
|
57367
|
+
return json5({ activity, count: activity.length });
|
|
55998
57368
|
}
|
|
55999
57369
|
if (resource === "task-lists") {
|
|
56000
57370
|
if (!id && method === "GET") {
|
|
56001
57371
|
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
56002
57372
|
const taskLists = await store.taskLists.list(projectId);
|
|
56003
|
-
return
|
|
57373
|
+
return json5({ task_lists: taskLists, count: taskLists.length });
|
|
56004
57374
|
}
|
|
56005
57375
|
if (!id && method === "POST") {
|
|
56006
|
-
const
|
|
56007
|
-
if (!
|
|
57376
|
+
const body2 = await readJson3(req);
|
|
57377
|
+
if (!body2 || typeof body2.name !== "string" || !body2.name.trim())
|
|
56008
57378
|
return error(400, "name is required");
|
|
56009
|
-
const unknownField = Object.keys(
|
|
57379
|
+
const unknownField = Object.keys(body2).find((key2) => !["name", "slug", "project_id", "description", "metadata"].includes(key2));
|
|
56010
57380
|
if (unknownField)
|
|
56011
57381
|
return error(400, `unsupported task-list create field: ${unknownField}`);
|
|
56012
|
-
if (
|
|
57382
|
+
if (body2.slug !== undefined && typeof body2.slug !== "string")
|
|
56013
57383
|
return error(400, "slug must be a string");
|
|
56014
|
-
if (
|
|
57384
|
+
if (body2.project_id !== undefined && (typeof body2.project_id !== "string" || !body2.project_id.trim()))
|
|
56015
57385
|
return error(400, "project_id must be a non-empty string");
|
|
56016
|
-
if (
|
|
57386
|
+
if (body2.description !== undefined && typeof body2.description !== "string")
|
|
56017
57387
|
return error(400, "description must be a string");
|
|
56018
|
-
if (
|
|
57388
|
+
if (body2.metadata !== undefined && (!body2.metadata || typeof body2.metadata !== "object" || Array.isArray(body2.metadata))) {
|
|
56019
57389
|
return error(400, "metadata must be an object");
|
|
56020
57390
|
}
|
|
56021
|
-
if (!normalizeSlug(
|
|
57391
|
+
if (!normalizeSlug(body2.slug === undefined ? body2.name : body2.slug)) {
|
|
56022
57392
|
return error(400, "task-list slug must be non-empty kebab-case");
|
|
56023
57393
|
}
|
|
56024
|
-
const taskList = await store.taskLists.create(
|
|
56025
|
-
return
|
|
57394
|
+
const taskList = await store.taskLists.create(body2, contextFromPrincipal(principal));
|
|
57395
|
+
return json5({ task_list: taskList }, 201);
|
|
56026
57396
|
}
|
|
56027
57397
|
if (id && method === "GET") {
|
|
56028
57398
|
const taskList = await store.taskLists.get(id);
|
|
56029
|
-
return taskList ?
|
|
57399
|
+
return taskList ? json5({ task_list: taskList }) : error(404, "task list not found");
|
|
56030
57400
|
}
|
|
56031
57401
|
if (id && (method === "PATCH" || method === "PUT")) {
|
|
56032
|
-
const
|
|
56033
|
-
if (!
|
|
57402
|
+
const body2 = await readJson3(req);
|
|
57403
|
+
if (!body2)
|
|
56034
57404
|
return error(400, "invalid JSON body");
|
|
56035
|
-
const unknownField = Object.keys(
|
|
57405
|
+
const unknownField = Object.keys(body2).find((key2) => !["slug", "name", "description", "metadata"].includes(key2));
|
|
56036
57406
|
if (unknownField)
|
|
56037
57407
|
return error(400, `unsupported task-list update field: ${unknownField}`);
|
|
56038
|
-
if (Object.keys(
|
|
57408
|
+
if (Object.keys(body2).length === 0)
|
|
56039
57409
|
return error(400, "task-list update must not be empty");
|
|
56040
|
-
if (
|
|
57410
|
+
if (body2.slug !== undefined && (typeof body2.slug !== "string" || !normalizeSlug(body2.slug)))
|
|
56041
57411
|
return error(400, "slug must be a non-empty string");
|
|
56042
|
-
if (
|
|
57412
|
+
if (body2.name !== undefined && (typeof body2.name !== "string" || !body2.name.trim()))
|
|
56043
57413
|
return error(400, "name must be a non-empty string");
|
|
56044
|
-
if (
|
|
57414
|
+
if (body2.description !== undefined && typeof body2.description !== "string")
|
|
56045
57415
|
return error(400, "description must be a string");
|
|
56046
|
-
if (
|
|
57416
|
+
if (body2.metadata !== undefined && (!body2.metadata || typeof body2.metadata !== "object" || Array.isArray(body2.metadata))) {
|
|
56047
57417
|
return error(400, "metadata must be an object");
|
|
56048
57418
|
}
|
|
56049
57419
|
if (!await store.taskLists.get(id))
|
|
56050
57420
|
return error(404, "task list not found");
|
|
56051
|
-
const taskList = await store.taskLists.update(id,
|
|
56052
|
-
return
|
|
57421
|
+
const taskList = await store.taskLists.update(id, body2);
|
|
57422
|
+
return json5({ task_list: taskList });
|
|
56053
57423
|
}
|
|
56054
57424
|
if (id && method === "DELETE") {
|
|
56055
57425
|
const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
|
|
56056
|
-
return deleted ?
|
|
57426
|
+
return deleted ? json5({ deleted: true, id }) : error(404, "task list not found");
|
|
56057
57427
|
}
|
|
56058
57428
|
return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
|
|
56059
57429
|
}
|
|
@@ -56064,7 +57434,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56064
57434
|
return error(501, "dependency edge listing is not supported by this storage backend");
|
|
56065
57435
|
}
|
|
56066
57436
|
const dependencies2 = await store.dependencies.listAll();
|
|
56067
|
-
return
|
|
57437
|
+
return json5({ dependencies: dependencies2, count: dependencies2.length });
|
|
56068
57438
|
}
|
|
56069
57439
|
if (resource === "commits" && id) {
|
|
56070
57440
|
if (method !== "GET")
|
|
@@ -56072,7 +57442,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56072
57442
|
if (!store.commits)
|
|
56073
57443
|
return error(501, "commit links are not supported by this storage backend");
|
|
56074
57444
|
const commit = await store.commits.find(id);
|
|
56075
|
-
return
|
|
57445
|
+
return json5({ commit: commit ?? null });
|
|
56076
57446
|
}
|
|
56077
57447
|
if (resource === "refs" && id) {
|
|
56078
57448
|
if (method !== "GET")
|
|
@@ -56086,7 +57456,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56086
57456
|
return error(400, "ref path segment has invalid percent encoding");
|
|
56087
57457
|
}
|
|
56088
57458
|
const refs = await store.gitRefs.find(decodedRef);
|
|
56089
|
-
return
|
|
57459
|
+
return json5({ refs, count: refs.length });
|
|
56090
57460
|
}
|
|
56091
57461
|
if (resource === "next" && !id) {
|
|
56092
57462
|
if (method !== "GET")
|
|
@@ -56097,8 +57467,8 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56097
57467
|
...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
|
|
56098
57468
|
...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {}
|
|
56099
57469
|
};
|
|
56100
|
-
const
|
|
56101
|
-
return
|
|
57470
|
+
const task3 = await store.tasks.getNext(agent, filters);
|
|
57471
|
+
return json5({ task: task3 ?? null });
|
|
56102
57472
|
}
|
|
56103
57473
|
if (resource === "stats" && method === "GET") {
|
|
56104
57474
|
const [tasks, tasksAll, projects] = await Promise.all([
|
|
@@ -56106,7 +57476,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56106
57476
|
store.tasks.count({ include_subtasks: true }),
|
|
56107
57477
|
store.projects.list()
|
|
56108
57478
|
]);
|
|
56109
|
-
return
|
|
57479
|
+
return json5({ tasks, tasks_all: tasksAll, subtasks: tasksAll - tasks, projects: projects.length });
|
|
56110
57480
|
}
|
|
56111
57481
|
if (resource === "integrity" && !id) {
|
|
56112
57482
|
if (method !== "GET")
|
|
@@ -56115,7 +57485,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56115
57485
|
return error(501, "referential-integrity reporting is not supported by this storage backend");
|
|
56116
57486
|
}
|
|
56117
57487
|
const integrity = await store.integrity.report();
|
|
56118
|
-
return
|
|
57488
|
+
return json5({ integrity });
|
|
56119
57489
|
}
|
|
56120
57490
|
if (resource === "import") {
|
|
56121
57491
|
if (method !== "POST")
|
|
@@ -56132,17 +57502,17 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56132
57502
|
return error(400, "empty snapshot: provide at least one record array (tasks/projects/plans/...)");
|
|
56133
57503
|
}
|
|
56134
57504
|
const result = await store.sync.importSnapshot(snapshot, contextFromPrincipal(principal));
|
|
56135
|
-
return
|
|
57505
|
+
return json5({ result, received });
|
|
56136
57506
|
}
|
|
56137
57507
|
return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
56138
57508
|
} catch (e) {
|
|
56139
57509
|
if (e instanceof PlanProjectLinkError) {
|
|
56140
|
-
const
|
|
56141
|
-
return error(
|
|
57510
|
+
const status2 = e.code === "PLAN_PROJECT_LINK_PLAN_NOT_FOUND" || e.code === "PLAN_PROJECT_LINK_PROJECT_NOT_FOUND" || e.code === "PLAN_PROJECT_LINK_RECEIPT_NOT_FOUND" ? 404 : e.code === "PLAN_PROJECT_LINK_IDEMPOTENCY_KEY_INVALID" ? 400 : e.code === "PLAN_PROJECT_LINK_UNSUPPORTED" ? 501 : 409;
|
|
57511
|
+
return error(status2, e.message, { code: e.code, conflict: status2 === 409, ...e.details });
|
|
56142
57512
|
}
|
|
56143
57513
|
if (e instanceof ProjectTaskListEnsureError) {
|
|
56144
|
-
const
|
|
56145
|
-
return error(
|
|
57514
|
+
const status2 = e.code === "PROJECT_NOT_FOUND" || e.code === "PROJECT_TASK_LIST_RECEIPT_NOT_FOUND" ? 404 : e.code === "PROJECT_TASK_LIST_IDEMPOTENCY_KEY_INVALID" ? 400 : 409;
|
|
57515
|
+
return error(status2, e.message, { code: e.code, conflict: status2 === 409, ...e.details });
|
|
56146
57516
|
}
|
|
56147
57517
|
if (e instanceof TaskReferenceAmbiguousError) {
|
|
56148
57518
|
return error(409, e.message, {
|
|
@@ -56166,21 +57536,22 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56166
57536
|
return error(500, e.message || "internal error");
|
|
56167
57537
|
}
|
|
56168
57538
|
}
|
|
56169
|
-
var
|
|
57539
|
+
var JSON_HEADERS4, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500, RFC3339_DATE_TIME;
|
|
56170
57540
|
var init_v1 = __esm(() => {
|
|
56171
57541
|
init_types();
|
|
56172
57542
|
init_cloud();
|
|
56173
57543
|
init_pr_groups();
|
|
56174
57544
|
init_project_registration();
|
|
57545
|
+
init_task_manifest();
|
|
56175
57546
|
init_redaction();
|
|
56176
57547
|
init_project_task_list_ensure();
|
|
56177
57548
|
init_plan_project_link();
|
|
56178
|
-
|
|
57549
|
+
JSON_HEADERS4 = { "Content-Type": "application/json" };
|
|
56179
57550
|
RFC3339_DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/;
|
|
56180
57551
|
});
|
|
56181
57552
|
|
|
56182
57553
|
// src/pr-groups/sqlite.ts
|
|
56183
|
-
function
|
|
57554
|
+
function parseJson3(value) {
|
|
56184
57555
|
return JSON.parse(value);
|
|
56185
57556
|
}
|
|
56186
57557
|
function groupFromRow2(row) {
|
|
@@ -56207,9 +57578,9 @@ function eventFromRow2(row) {
|
|
|
56207
57578
|
sequence: Number(row["sequence"]),
|
|
56208
57579
|
pr_number: row["pr_number"] === null ? null : Number(row["pr_number"]),
|
|
56209
57580
|
repair_cycle: row["repair_cycle"] === null ? null : Number(row["repair_cycle"]),
|
|
56210
|
-
ci_proof: row["ci_proof"] ?
|
|
56211
|
-
cleanup_proof: row["cleanup_proof"] ?
|
|
56212
|
-
metadata:
|
|
57581
|
+
ci_proof: row["ci_proof"] ? parseJson3(String(row["ci_proof"])) : null,
|
|
57582
|
+
cleanup_proof: row["cleanup_proof"] ? parseJson3(String(row["cleanup_proof"])) : null,
|
|
57583
|
+
metadata: parseJson3(String(row["metadata"] ?? "{}"))
|
|
56213
57584
|
};
|
|
56214
57585
|
}
|
|
56215
57586
|
|
|
@@ -56273,8 +57644,8 @@ class SqlitePrGroupTransaction {
|
|
|
56273
57644
|
WHERE id = ?
|
|
56274
57645
|
`).run(attempt.status, attempt.started_at, attempt.last_heartbeat_at, attempt.handed_off_at, attempt.fenced_at, attempt.terminal_at, attempt.updated_at, attempt.id);
|
|
56275
57646
|
}
|
|
56276
|
-
async getEventByIdempotency(groupId,
|
|
56277
|
-
const row = this.db.query("SELECT * FROM pr_group_events WHERE group_id = ? AND idempotency_key = ? LIMIT 1").get(groupId,
|
|
57647
|
+
async getEventByIdempotency(groupId, key2) {
|
|
57648
|
+
const row = this.db.query("SELECT * FROM pr_group_events WHERE group_id = ? AND idempotency_key = ? LIMIT 1").get(groupId, key2);
|
|
56278
57649
|
return row ? eventFromRow2(row) : null;
|
|
56279
57650
|
}
|
|
56280
57651
|
async findEventByReceiptKey(receiptKey) {
|
|
@@ -56284,14 +57655,14 @@ class SqlitePrGroupTransaction {
|
|
|
56284
57655
|
async findEvent(groupId, filters) {
|
|
56285
57656
|
const clauses = ["group_id = ?", "event_type = ?"];
|
|
56286
57657
|
const values = [groupId, filters.event_type];
|
|
56287
|
-
for (const
|
|
56288
|
-
if (!(
|
|
57658
|
+
for (const key2 of ["attempt_id", "head_sha", "outcome", "receipt_key"]) {
|
|
57659
|
+
if (!(key2 in filters))
|
|
56289
57660
|
continue;
|
|
56290
|
-
if (filters[
|
|
56291
|
-
clauses.push(`${
|
|
57661
|
+
if (filters[key2] === null)
|
|
57662
|
+
clauses.push(`${key2} IS NULL`);
|
|
56292
57663
|
else {
|
|
56293
|
-
clauses.push(`${
|
|
56294
|
-
values.push(String(filters[
|
|
57664
|
+
clauses.push(`${key2} = ?`);
|
|
57665
|
+
values.push(String(filters[key2]));
|
|
56295
57666
|
}
|
|
56296
57667
|
}
|
|
56297
57668
|
const row = this.db.query(`SELECT * FROM pr_group_events WHERE ${clauses.join(" AND ")} ORDER BY sequence DESC LIMIT 1`).get(...values);
|
|
@@ -56386,7 +57757,7 @@ class SqlitePrGroupLedgerPersistence {
|
|
|
56386
57757
|
}
|
|
56387
57758
|
}
|
|
56388
57759
|
var sqliteTransactionTails2;
|
|
56389
|
-
var
|
|
57760
|
+
var init_sqlite3 = __esm(() => {
|
|
56390
57761
|
sqliteTransactionTails2 = new WeakMap;
|
|
56391
57762
|
});
|
|
56392
57763
|
|
|
@@ -56417,10 +57788,10 @@ function createLocalPrGroupLedger(db = getDatabase()) {
|
|
|
56417
57788
|
var init_pr_groups2 = __esm(() => {
|
|
56418
57789
|
init_database();
|
|
56419
57790
|
init_ledger();
|
|
56420
|
-
|
|
57791
|
+
init_sqlite3();
|
|
56421
57792
|
init_types3();
|
|
56422
57793
|
init_ledger();
|
|
56423
|
-
|
|
57794
|
+
init_sqlite3();
|
|
56424
57795
|
init_http_client();
|
|
56425
57796
|
init_postgres();
|
|
56426
57797
|
});
|
|
@@ -56431,7 +57802,7 @@ __export(exports_serve, {
|
|
|
56431
57802
|
taskToSummary: () => taskToSummary,
|
|
56432
57803
|
startServer: () => startServer,
|
|
56433
57804
|
serveStaticFile: () => serveStaticFile,
|
|
56434
|
-
json: () =>
|
|
57805
|
+
json: () => json3,
|
|
56435
57806
|
checkAuth: () => checkAuth,
|
|
56436
57807
|
SECURITY_HEADERS: () => SECURITY_HEADERS,
|
|
56437
57808
|
MIME_TYPES: () => MIME_TYPES
|
|
@@ -56517,9 +57888,9 @@ function checkRateLimit(ip) {
|
|
|
56517
57888
|
}
|
|
56518
57889
|
return { allowed: true };
|
|
56519
57890
|
}
|
|
56520
|
-
function
|
|
57891
|
+
function json3(data, status2 = 200, headers) {
|
|
56521
57892
|
return new Response(JSON.stringify(data), {
|
|
56522
|
-
status,
|
|
57893
|
+
status: status2,
|
|
56523
57894
|
headers: {
|
|
56524
57895
|
"Content-Type": "application/json",
|
|
56525
57896
|
...SECURITY_HEADERS,
|
|
@@ -56539,29 +57910,29 @@ function serveStaticFile(filePath) {
|
|
|
56539
57910
|
}
|
|
56540
57911
|
});
|
|
56541
57912
|
}
|
|
56542
|
-
function taskToSummary(
|
|
57913
|
+
function taskToSummary(task3, fields) {
|
|
56543
57914
|
const full = {
|
|
56544
|
-
id:
|
|
56545
|
-
short_id:
|
|
56546
|
-
title:
|
|
56547
|
-
description:
|
|
56548
|
-
status:
|
|
56549
|
-
priority:
|
|
56550
|
-
project_id:
|
|
56551
|
-
plan_id:
|
|
56552
|
-
task_list_id:
|
|
56553
|
-
agent_id:
|
|
56554
|
-
assigned_to:
|
|
56555
|
-
working_dir:
|
|
56556
|
-
locked_by:
|
|
56557
|
-
tags:
|
|
56558
|
-
metadata:
|
|
56559
|
-
version:
|
|
56560
|
-
created_at:
|
|
56561
|
-
updated_at:
|
|
56562
|
-
completed_at:
|
|
56563
|
-
due_at:
|
|
56564
|
-
recurrence_rule:
|
|
57915
|
+
id: task3.id,
|
|
57916
|
+
short_id: task3.short_id,
|
|
57917
|
+
title: task3.title,
|
|
57918
|
+
description: task3.description,
|
|
57919
|
+
status: task3.status,
|
|
57920
|
+
priority: task3.priority,
|
|
57921
|
+
project_id: task3.project_id,
|
|
57922
|
+
plan_id: task3.plan_id,
|
|
57923
|
+
task_list_id: task3.task_list_id,
|
|
57924
|
+
agent_id: task3.agent_id,
|
|
57925
|
+
assigned_to: task3.assigned_to,
|
|
57926
|
+
working_dir: task3.working_dir,
|
|
57927
|
+
locked_by: task3.locked_by,
|
|
57928
|
+
tags: task3.tags,
|
|
57929
|
+
metadata: task3.metadata,
|
|
57930
|
+
version: task3.version,
|
|
57931
|
+
created_at: task3.created_at,
|
|
57932
|
+
updated_at: task3.updated_at,
|
|
57933
|
+
completed_at: task3.completed_at,
|
|
57934
|
+
due_at: task3.due_at,
|
|
57935
|
+
recurrence_rule: task3.recurrence_rule
|
|
56565
57936
|
};
|
|
56566
57937
|
if (!fields || fields.length === 0)
|
|
56567
57938
|
return full;
|
|
@@ -56662,7 +58033,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56662
58033
|
"Access-Control-Allow-Headers": "Content-Type, X-API-Key, Authorization",
|
|
56663
58034
|
Vary: "Origin"
|
|
56664
58035
|
} : undefined;
|
|
56665
|
-
const jsonWithCors = (data,
|
|
58036
|
+
const jsonWithCors = (data, status2 = 200) => json3(data, status2, corsHeaders);
|
|
56666
58037
|
if (method === "OPTIONS") {
|
|
56667
58038
|
return new Response(null, {
|
|
56668
58039
|
headers: corsHeaders || {
|
|
@@ -56743,13 +58114,13 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56743
58114
|
return res;
|
|
56744
58115
|
}
|
|
56745
58116
|
if (path === "/api/health" && method === "GET") {
|
|
56746
|
-
return handleHealth(ctx,
|
|
58117
|
+
return handleHealth(ctx, json3);
|
|
56747
58118
|
}
|
|
56748
58119
|
if (path === "/api/headless" && method === "GET") {
|
|
56749
|
-
return handleHeadlessBoundary(ctx,
|
|
58120
|
+
return handleHeadlessBoundary(ctx, json3);
|
|
56750
58121
|
}
|
|
56751
58122
|
if (path === "/api/stats" && method === "GET") {
|
|
56752
|
-
return handleStats(ctx,
|
|
58123
|
+
return handleStats(ctx, json3);
|
|
56753
58124
|
}
|
|
56754
58125
|
if (path === "/api/tasks" && method === "GET") {
|
|
56755
58126
|
return handleListTasks(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -56764,16 +58135,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56764
58135
|
return handleTasksExport(req, url, ctx, jsonWithCors, taskToSummary);
|
|
56765
58136
|
}
|
|
56766
58137
|
if (path === "/api/tasks/bulk" && method === "POST") {
|
|
56767
|
-
return handleTasksBulk(req, ctx,
|
|
58138
|
+
return handleTasksBulk(req, ctx, json3);
|
|
56768
58139
|
}
|
|
56769
58140
|
if (path === "/api/tasks/status" && method === "GET") {
|
|
56770
|
-
return handleTasksStatus(req, url, ctx,
|
|
58141
|
+
return handleTasksStatus(req, url, ctx, json3);
|
|
56771
58142
|
}
|
|
56772
58143
|
if (path === "/api/tasks/next" && method === "GET") {
|
|
56773
58144
|
return handleTasksNext(req, url, ctx, jsonWithCors, taskToSummary);
|
|
56774
58145
|
}
|
|
56775
58146
|
if (path === "/api/tasks/active" && method === "GET") {
|
|
56776
|
-
return handleTasksActive(req, url, ctx,
|
|
58147
|
+
return handleTasksActive(req, url, ctx, json3);
|
|
56777
58148
|
}
|
|
56778
58149
|
if (path === "/api/tasks/stale" && method === "GET") {
|
|
56779
58150
|
return handleTasksStale(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -56786,11 +58157,11 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56786
58157
|
}
|
|
56787
58158
|
const attachmentsMatch = path.match(/^\/api\/tasks\/([^/]+)\/attachments$/);
|
|
56788
58159
|
if (attachmentsMatch && method === "GET") {
|
|
56789
|
-
return handleTaskAttachments(attachmentsMatch[1], ctx,
|
|
58160
|
+
return handleTaskAttachments(attachmentsMatch[1], ctx, json3);
|
|
56790
58161
|
}
|
|
56791
58162
|
const progressMatch = path.match(/^\/api\/tasks\/([^/]+)\/progress$/);
|
|
56792
58163
|
if (progressMatch) {
|
|
56793
|
-
const res = await handleTaskProgress(progressMatch[1], req, method, ctx,
|
|
58164
|
+
const res = await handleTaskProgress(progressMatch[1], req, method, ctx, json3, url);
|
|
56794
58165
|
if (res !== null)
|
|
56795
58166
|
return res;
|
|
56796
58167
|
}
|
|
@@ -56804,7 +58175,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56804
58175
|
return handlePatchTask(id, req, ctx, jsonWithCors, taskToSummary);
|
|
56805
58176
|
}
|
|
56806
58177
|
if (method === "DELETE") {
|
|
56807
|
-
return handleDeleteTask(id, ctx,
|
|
58178
|
+
return handleDeleteTask(id, ctx, json3);
|
|
56808
58179
|
}
|
|
56809
58180
|
}
|
|
56810
58181
|
const startMatch = path.match(/^\/api\/tasks\/([^/]+)\/start$/);
|
|
@@ -56820,7 +58191,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56820
58191
|
return handleCompleteTask(completeMatch[1], ctx, jsonWithCors, taskToSummary);
|
|
56821
58192
|
}
|
|
56822
58193
|
if (path === "/api/projects" && method === "GET") {
|
|
56823
|
-
return handleListProjects(url, ctx,
|
|
58194
|
+
return handleListProjects(url, ctx, json3);
|
|
56824
58195
|
}
|
|
56825
58196
|
if (path === "/api/agents/me" && method === "GET") {
|
|
56826
58197
|
return handleAgentMe(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -56833,92 +58204,92 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56833
58204
|
return handleClaimTask(req, ctx, jsonWithCors, taskToSummary);
|
|
56834
58205
|
}
|
|
56835
58206
|
if (path === "/api/orgs" && method === "GET") {
|
|
56836
|
-
return handleListOrgs(ctx,
|
|
58207
|
+
return handleListOrgs(ctx, json3);
|
|
56837
58208
|
}
|
|
56838
58209
|
if (path === "/api/orgs" && method === "POST") {
|
|
56839
|
-
return handleCreateOrg(req, ctx,
|
|
58210
|
+
return handleCreateOrg(req, ctx, json3);
|
|
56840
58211
|
}
|
|
56841
58212
|
const orgMatch = path.match(/^\/api\/orgs\/([^/]+)$/);
|
|
56842
58213
|
if (orgMatch && method === "PATCH") {
|
|
56843
|
-
return handleUpdateOrg(orgMatch[1], req, ctx,
|
|
58214
|
+
return handleUpdateOrg(orgMatch[1], req, ctx, json3);
|
|
56844
58215
|
}
|
|
56845
58216
|
if (orgMatch && method === "DELETE") {
|
|
56846
|
-
return handleDeleteOrg(orgMatch[1], ctx,
|
|
58217
|
+
return handleDeleteOrg(orgMatch[1], ctx, json3);
|
|
56847
58218
|
}
|
|
56848
58219
|
if (path === "/api/org" && method === "GET") {
|
|
56849
|
-
return handleOrgChart(ctx,
|
|
58220
|
+
return handleOrgChart(ctx, json3);
|
|
56850
58221
|
}
|
|
56851
58222
|
const teamMatch = path.match(/^\/api\/agents\/([^/]+)\/team$/);
|
|
56852
58223
|
if (teamMatch && method === "GET") {
|
|
56853
|
-
return handleAgentTeam(teamMatch[1], ctx,
|
|
58224
|
+
return handleAgentTeam(teamMatch[1], ctx, json3);
|
|
56854
58225
|
}
|
|
56855
58226
|
if (path === "/api/agents" && method === "GET") {
|
|
56856
|
-
return handleListAgents(url, ctx,
|
|
58227
|
+
return handleListAgents(url, ctx, json3);
|
|
56857
58228
|
}
|
|
56858
58229
|
if (path === "/api/projects" && method === "POST") {
|
|
56859
|
-
return handleCreateProject(req, ctx,
|
|
58230
|
+
return handleCreateProject(req, ctx, json3);
|
|
56860
58231
|
}
|
|
56861
58232
|
const projectDeleteMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
56862
58233
|
if (projectDeleteMatch && method === "DELETE") {
|
|
56863
|
-
return handleDeleteProject(projectDeleteMatch[1], ctx,
|
|
58234
|
+
return handleDeleteProject(projectDeleteMatch[1], ctx, json3);
|
|
56864
58235
|
}
|
|
56865
58236
|
if (path === "/api/agents" && method === "POST") {
|
|
56866
|
-
return handleRegisterAgent(req, ctx,
|
|
58237
|
+
return handleRegisterAgent(req, ctx, json3);
|
|
56867
58238
|
}
|
|
56868
58239
|
const agentMatch = path.match(/^\/api\/agents\/([^/]+)$/);
|
|
56869
58240
|
if (agentMatch && method === "PATCH") {
|
|
56870
|
-
return handleUpdateAgent(agentMatch[1], req, ctx,
|
|
58241
|
+
return handleUpdateAgent(agentMatch[1], req, ctx, json3);
|
|
56871
58242
|
}
|
|
56872
58243
|
if (agentMatch && method === "DELETE") {
|
|
56873
|
-
return handleDeleteAgent(agentMatch[1], ctx,
|
|
58244
|
+
return handleDeleteAgent(agentMatch[1], ctx, json3);
|
|
56874
58245
|
}
|
|
56875
58246
|
if (path === "/api/agents/bulk" && method === "POST") {
|
|
56876
|
-
return handleBulkDeleteAgents(req, ctx,
|
|
58247
|
+
return handleBulkDeleteAgents(req, ctx, json3);
|
|
56877
58248
|
}
|
|
56878
58249
|
if (path === "/api/projects/bulk" && method === "POST") {
|
|
56879
|
-
return handleBulkDeleteProjects(req, ctx,
|
|
58250
|
+
return handleBulkDeleteProjects(req, ctx, json3);
|
|
56880
58251
|
}
|
|
56881
58252
|
if (path === "/api/doctor" && method === "GET") {
|
|
56882
|
-
return handleDoctor(ctx,
|
|
58253
|
+
return handleDoctor(ctx, json3);
|
|
56883
58254
|
}
|
|
56884
58255
|
if (path === "/api/report" && method === "GET") {
|
|
56885
|
-
return handleReport(req, url, ctx,
|
|
58256
|
+
return handleReport(req, url, ctx, json3);
|
|
56886
58257
|
}
|
|
56887
58258
|
if (path === "/api/activity" && method === "GET") {
|
|
56888
|
-
return handleActivity(req, url, ctx,
|
|
58259
|
+
return handleActivity(req, url, ctx, json3);
|
|
56889
58260
|
}
|
|
56890
58261
|
const historyMatch = path.match(/^\/api\/tasks\/([^/]+)\/history$/);
|
|
56891
58262
|
if (historyMatch && method === "GET") {
|
|
56892
|
-
return handleTaskHistory(historyMatch[1], ctx,
|
|
58263
|
+
return handleTaskHistory(historyMatch[1], ctx, json3, url);
|
|
56893
58264
|
}
|
|
56894
58265
|
if (path === "/api/webhooks" && method === "GET") {
|
|
56895
|
-
return handleListWebhooks(ctx,
|
|
58266
|
+
return handleListWebhooks(ctx, json3);
|
|
56896
58267
|
}
|
|
56897
58268
|
if (path === "/api/webhooks" && method === "POST") {
|
|
56898
|
-
return handleCreateWebhook(req, ctx,
|
|
58269
|
+
return handleCreateWebhook(req, ctx, json3);
|
|
56899
58270
|
}
|
|
56900
58271
|
const webhookMatch = path.match(/^\/api\/webhooks\/([^/]+)$/);
|
|
56901
58272
|
if (webhookMatch && method === "DELETE") {
|
|
56902
|
-
return handleDeleteWebhook(webhookMatch[1], ctx,
|
|
58273
|
+
return handleDeleteWebhook(webhookMatch[1], ctx, json3);
|
|
56903
58274
|
}
|
|
56904
58275
|
if (path === "/api/templates" && method === "GET") {
|
|
56905
|
-
return handleListTemplates(ctx,
|
|
58276
|
+
return handleListTemplates(ctx, json3);
|
|
56906
58277
|
}
|
|
56907
58278
|
if (path === "/api/templates" && method === "POST") {
|
|
56908
|
-
return handleCreateTemplate(req, ctx,
|
|
58279
|
+
return handleCreateTemplate(req, ctx, json3);
|
|
56909
58280
|
}
|
|
56910
58281
|
const templateMatch = path.match(/^\/api\/templates\/([^/]+)$/);
|
|
56911
58282
|
if (templateMatch && method === "DELETE") {
|
|
56912
|
-
return handleDeleteTemplate(templateMatch[1], ctx,
|
|
58283
|
+
return handleDeleteTemplate(templateMatch[1], ctx, json3);
|
|
56913
58284
|
}
|
|
56914
58285
|
if (path === "/api/plans" && method === "GET") {
|
|
56915
|
-
return handleListPlans(url, ctx,
|
|
58286
|
+
return handleListPlans(url, ctx, json3);
|
|
56916
58287
|
}
|
|
56917
58288
|
if (path === "/api/plans" && method === "POST") {
|
|
56918
|
-
return handleCreatePlan(req, ctx,
|
|
58289
|
+
return handleCreatePlan(req, ctx, json3);
|
|
56919
58290
|
}
|
|
56920
58291
|
if (path === "/api/plans/bulk" && method === "POST") {
|
|
56921
|
-
return handleBulkDeletePlans(req, ctx,
|
|
58292
|
+
return handleBulkDeletePlans(req, ctx, json3);
|
|
56922
58293
|
}
|
|
56923
58294
|
const planMatch = path.match(/^\/api\/plans\/([^/]+)$/);
|
|
56924
58295
|
if (planMatch) {
|
|
@@ -56927,16 +58298,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56927
58298
|
return handleGetPlan(id, ctx, jsonWithCors, taskToSummary);
|
|
56928
58299
|
}
|
|
56929
58300
|
if (method === "PATCH") {
|
|
56930
|
-
return handleUpdatePlan(id, req, ctx,
|
|
58301
|
+
return handleUpdatePlan(id, req, ctx, json3);
|
|
56931
58302
|
}
|
|
56932
58303
|
if (method === "DELETE") {
|
|
56933
|
-
return handleDeletePlan(id, ctx,
|
|
58304
|
+
return handleDeletePlan(id, ctx, json3);
|
|
56934
58305
|
}
|
|
56935
58306
|
}
|
|
56936
58307
|
const staticRes = handleStaticFiles(path, method, ctx, jsonWithCors, serveStaticFile);
|
|
56937
58308
|
if (staticRes)
|
|
56938
58309
|
return staticRes;
|
|
56939
|
-
return
|
|
58310
|
+
return json3({ error: "Not found" }, 404);
|
|
56940
58311
|
}
|
|
56941
58312
|
});
|
|
56942
58313
|
const shutdown = () => {
|
|
@@ -57136,52 +58507,52 @@ function resolveId(partialId, table = "tasks") {
|
|
|
57136
58507
|
}
|
|
57137
58508
|
return id;
|
|
57138
58509
|
}
|
|
57139
|
-
function formatTask(
|
|
57140
|
-
const id =
|
|
57141
|
-
const assigned =
|
|
57142
|
-
const lockState = lockDisplayState(
|
|
58510
|
+
function formatTask(task3) {
|
|
58511
|
+
const id = task3.short_id || task3.id.slice(0, 8);
|
|
58512
|
+
const assigned = task3.assigned_to ? ` -> ${task3.assigned_to}` : "";
|
|
58513
|
+
const lockState = lockDisplayState(task3.locked_by, task3.locked_at);
|
|
57143
58514
|
const lock = lockState.held ? ` [locked:${lockState.holder}]` : "";
|
|
57144
|
-
const recur =
|
|
57145
|
-
return `${id} ${
|
|
58515
|
+
const recur = task3.recurrence_rule ? ` [\u21BB]` : "";
|
|
58516
|
+
return `${id} ${task3.status.padEnd(11)} ${task3.priority.padEnd(8)} ${task3.title}${assigned}${lock}${recur}`;
|
|
57146
58517
|
}
|
|
57147
|
-
function formatTaskDetail(
|
|
58518
|
+
function formatTaskDetail(task3, maxDescriptionChars) {
|
|
57148
58519
|
const parts = [
|
|
57149
|
-
`ID: ${
|
|
57150
|
-
`Title: ${
|
|
57151
|
-
`Status: ${
|
|
57152
|
-
`Priority: ${
|
|
58520
|
+
`ID: ${task3.id}`,
|
|
58521
|
+
`Title: ${task3.title}`,
|
|
58522
|
+
`Status: ${task3.status}`,
|
|
58523
|
+
`Priority: ${task3.priority}`
|
|
57153
58524
|
];
|
|
57154
|
-
if (
|
|
57155
|
-
const desc = maxDescriptionChars &&
|
|
58525
|
+
if (task3.description) {
|
|
58526
|
+
const desc = maxDescriptionChars && task3.description.length > maxDescriptionChars ? task3.description.slice(0, maxDescriptionChars) + "\u2026" : task3.description;
|
|
57156
58527
|
parts.push(`Description: ${desc}`);
|
|
57157
58528
|
}
|
|
57158
|
-
if (
|
|
57159
|
-
parts.push(`Assigned to: ${
|
|
57160
|
-
if (
|
|
57161
|
-
parts.push(`Agent: ${
|
|
57162
|
-
const detailLock = lockDisplayState(
|
|
58529
|
+
if (task3.assigned_to)
|
|
58530
|
+
parts.push(`Assigned to: ${task3.assigned_to}`);
|
|
58531
|
+
if (task3.agent_id)
|
|
58532
|
+
parts.push(`Agent: ${task3.agent_id}`);
|
|
58533
|
+
const detailLock = lockDisplayState(task3.locked_by, task3.locked_at);
|
|
57163
58534
|
if (detailLock.held)
|
|
57164
58535
|
parts.push(`Locked by: ${detailLock.holder}`);
|
|
57165
58536
|
else if (detailLock.expired)
|
|
57166
58537
|
parts.push(`Lock: ${formatExpiredLock(detailLock)}`);
|
|
57167
|
-
if (
|
|
57168
|
-
parts.push(`Parent: ${
|
|
57169
|
-
if (
|
|
57170
|
-
parts.push(`Project: ${
|
|
57171
|
-
if (
|
|
57172
|
-
parts.push(`Plan: ${
|
|
57173
|
-
if (
|
|
57174
|
-
parts.push(`Due: ${
|
|
57175
|
-
if (
|
|
57176
|
-
parts.push(`Tags: ${
|
|
57177
|
-
if (
|
|
57178
|
-
parts.push(`Recurrence: ${
|
|
57179
|
-
if (
|
|
57180
|
-
parts.push(`Recurrence parent: ${
|
|
57181
|
-
parts.push(`Version: ${
|
|
57182
|
-
parts.push(`Created: ${
|
|
57183
|
-
if (
|
|
57184
|
-
parts.push(`Completed: ${
|
|
58538
|
+
if (task3.parent_id)
|
|
58539
|
+
parts.push(`Parent: ${task3.parent_id}`);
|
|
58540
|
+
if (task3.project_id)
|
|
58541
|
+
parts.push(`Project: ${task3.project_id}`);
|
|
58542
|
+
if (task3.plan_id)
|
|
58543
|
+
parts.push(`Plan: ${task3.plan_id}`);
|
|
58544
|
+
if (task3.due_at)
|
|
58545
|
+
parts.push(`Due: ${task3.due_at.slice(0, 10)}`);
|
|
58546
|
+
if (task3.tags.length > 0)
|
|
58547
|
+
parts.push(`Tags: ${task3.tags.join(", ")}`);
|
|
58548
|
+
if (task3.recurrence_rule)
|
|
58549
|
+
parts.push(`Recurrence: ${task3.recurrence_rule}`);
|
|
58550
|
+
if (task3.recurrence_parent_id)
|
|
58551
|
+
parts.push(`Recurrence parent: ${task3.recurrence_parent_id}`);
|
|
58552
|
+
parts.push(`Version: ${task3.version}`);
|
|
58553
|
+
parts.push(`Created: ${task3.created_at}`);
|
|
58554
|
+
if (task3.completed_at)
|
|
58555
|
+
parts.push(`Completed: ${task3.completed_at}`);
|
|
57185
58556
|
return parts.join(`
|
|
57186
58557
|
`);
|
|
57187
58558
|
}
|