@hasna/todos 0.15.14 → 0.15.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/mcp-hooks-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +12470 -10747
- package/dist/contracts.js +1 -1
- package/dist/index.js +358 -47
- package/dist/lib/agent-adapter-docs.d.ts +10 -0
- package/dist/lib/agent-adapter-docs.d.ts.map +1 -1
- package/dist/mcp/index.js +2290 -803
- package/dist/mcp.js +1 -1
- package/dist/project-registration.js +1 -1
- 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 +19806 -18319
- 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/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 +2 -2
- 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 +24 -1
- package/dist/task-manifest/types.d.ts.map +1 -1
- package/dist/task-manifest.js +357 -46
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -21041,10 +21041,72 @@ async function requireTagsFilterCapability(client) {
|
|
|
21041
21041
|
throw new Error(`REMOTE_TAGS_FILTER_UNSUPPORTED: configured Todos authority ${authority} does not advertise the tags ` + "query param on GET /v1/tasks; deploy the current @hasna/todos /v1 server to filter by tag; " + "no unfiltered task read was issued");
|
|
21042
21042
|
}
|
|
21043
21043
|
}
|
|
21044
|
-
|
|
21044
|
+
function parseCloudTaskTotal(raw) {
|
|
21045
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
21046
|
+
return;
|
|
21047
|
+
const total = raw.total;
|
|
21048
|
+
return typeof total === "number" && Number.isSafeInteger(total) && total >= 0 ? total : undefined;
|
|
21049
|
+
}
|
|
21050
|
+
async function requestRawCloudTaskPage(client, filter) {
|
|
21045
21051
|
const res = await requiredRemoteRoute(client, "/v1/tasks", () => client.list("tasks", { query: toListQuery(filter) }));
|
|
21046
21052
|
const envelope = res.raw;
|
|
21047
|
-
return
|
|
21053
|
+
return {
|
|
21054
|
+
tasks: Array.isArray(envelope?.tasks) ? envelope.tasks : res.items,
|
|
21055
|
+
total: parseCloudTaskTotal(res.raw)
|
|
21056
|
+
};
|
|
21057
|
+
}
|
|
21058
|
+
function cloudTaskListFilterError(code, taskListId, detail) {
|
|
21059
|
+
return new Error(`${code}: hosted authority returned rows outside requested task_list_id ${taskListId}; ${detail}; refusing an incomplete exact-list result`);
|
|
21060
|
+
}
|
|
21061
|
+
async function requestCloudTaskPage(client, filter) {
|
|
21062
|
+
const firstPage = await requestRawCloudTaskPage(client, filter);
|
|
21063
|
+
const tasks = firstPage.tasks;
|
|
21064
|
+
if (filter.task_list_id === undefined)
|
|
21065
|
+
return tasks;
|
|
21066
|
+
const taskListId = filter.task_list_id;
|
|
21067
|
+
if (tasks.every((task) => task.task_list_id === taskListId))
|
|
21068
|
+
return tasks;
|
|
21069
|
+
const scanLimit = filter.limit;
|
|
21070
|
+
const startOffset = filter.offset ?? 0;
|
|
21071
|
+
if (startOffset !== 0 || scanLimit === undefined || !Number.isSafeInteger(scanLimit) || scanLimit <= 0 || firstPage.total === undefined) {
|
|
21072
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, "the response does not provide complete bounded total/offset pagination evidence");
|
|
21073
|
+
}
|
|
21074
|
+
const total = firstPage.total;
|
|
21075
|
+
if (total > scanLimit) {
|
|
21076
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_INCOMPLETE", taskListId, `reported total ${total} exceeds bounded scan limit ${scanLimit}`);
|
|
21077
|
+
}
|
|
21078
|
+
if (tasks.length > total) {
|
|
21079
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, `the first page contains ${tasks.length} rows but reports total ${total}`);
|
|
21080
|
+
}
|
|
21081
|
+
const seenTaskIds = new Set;
|
|
21082
|
+
for (const task of tasks) {
|
|
21083
|
+
if (seenTaskIds.has(task.id)) {
|
|
21084
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, `the first page repeats task id ${task.id}`);
|
|
21085
|
+
}
|
|
21086
|
+
seenTaskIds.add(task.id);
|
|
21087
|
+
}
|
|
21088
|
+
while (tasks.length < total) {
|
|
21089
|
+
const remaining = total - tasks.length;
|
|
21090
|
+
const page = await requestRawCloudTaskPage(client, {
|
|
21091
|
+
...filter,
|
|
21092
|
+
offset: tasks.length,
|
|
21093
|
+
limit: Math.min(scanLimit - tasks.length, remaining)
|
|
21094
|
+
});
|
|
21095
|
+
if (page.total !== total) {
|
|
21096
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, `pagination total changed from ${total} to ${String(page.total)}`);
|
|
21097
|
+
}
|
|
21098
|
+
if (page.tasks.length === 0 || tasks.length + page.tasks.length > total) {
|
|
21099
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, "pagination did not make bounded progress toward the reported total");
|
|
21100
|
+
}
|
|
21101
|
+
for (const task of page.tasks) {
|
|
21102
|
+
if (seenTaskIds.has(task.id)) {
|
|
21103
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, `pagination repeats task id ${task.id}`);
|
|
21104
|
+
}
|
|
21105
|
+
seenTaskIds.add(task.id);
|
|
21106
|
+
}
|
|
21107
|
+
tasks.push(...page.tasks);
|
|
21108
|
+
}
|
|
21109
|
+
return tasks.filter((task) => task.task_list_id === taskListId);
|
|
21048
21110
|
}
|
|
21049
21111
|
async function cloudListTasks(client, filter = {}) {
|
|
21050
21112
|
if (filter.tags?.length)
|
|
@@ -21083,12 +21145,10 @@ async function cloudGetTask(client, id) {
|
|
|
21083
21145
|
}
|
|
21084
21146
|
async function cloudCreateTask(client, input) {
|
|
21085
21147
|
const expectedParentId = typeof input["parent_id"] === "string" ? input["parent_id"] : null;
|
|
21086
|
-
const created = unwrapTask(await requiredRemoteRoute(client, "/v1/tasks", () =>
|
|
21148
|
+
const created = unwrapTask(await requiredRemoteRoute(client, "/v1/tasks", () => client.create("tasks", input, { retry: false }), ["PARENT_TASK_NOT_FOUND"]));
|
|
21087
21149
|
if (!created || typeof created.id !== "string" || !created.id.trim()) {
|
|
21088
21150
|
throw new Error(`REMOTE_API_INCOMPATIBLE: configured Todos authority ${remoteAuthorityBase(client)} returned a task create ` + "response without a stored task id; no success row or local SQLite fallback is permitted");
|
|
21089
21151
|
}
|
|
21090
|
-
if (expectedParentId === null)
|
|
21091
|
-
return created;
|
|
21092
21152
|
const persisted = await cloudGetTask(client, created.id);
|
|
21093
21153
|
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== expectedParentId) {
|
|
21094
21154
|
throw new Error(`TASK_CREATE_PERSISTENCE_UNVERIFIED: configured Todos authority ${remoteAuthorityBase(client)} accepted ` + `POST /v1/tasks but authoritative GET /v1/tasks/${encodeURIComponent(created.id)} did not return the same ` + "stored task id and parent_id; no success row or local SQLite fallback is permitted");
|
|
@@ -35415,7 +35475,7 @@ var package_default;
|
|
|
35415
35475
|
var init_package = __esm(() => {
|
|
35416
35476
|
package_default = {
|
|
35417
35477
|
name: "@hasna/todos",
|
|
35418
|
-
version: "0.15.
|
|
35478
|
+
version: "0.15.16",
|
|
35419
35479
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
35420
35480
|
type: "module",
|
|
35421
35481
|
main: "dist/index.js",
|
|
@@ -50911,6 +50971,1337 @@ var init_project_registration = __esm(() => {
|
|
|
50911
50971
|
init_types4();
|
|
50912
50972
|
});
|
|
50913
50973
|
|
|
50974
|
+
// src/task-manifest/canonical.ts
|
|
50975
|
+
import { createHash as createHash16 } from "crypto";
|
|
50976
|
+
function canonicalize3(value) {
|
|
50977
|
+
if (Array.isArray(value))
|
|
50978
|
+
return value.map(canonicalize3);
|
|
50979
|
+
if (value !== null && typeof value === "object") {
|
|
50980
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize3(entry2)]));
|
|
50981
|
+
}
|
|
50982
|
+
return value;
|
|
50983
|
+
}
|
|
50984
|
+
function canonicalJson(value) {
|
|
50985
|
+
return JSON.stringify(canonicalize3(value));
|
|
50986
|
+
}
|
|
50987
|
+
function canonicalDigest(value) {
|
|
50988
|
+
return createHash16("sha256").update(canonicalJson(value)).digest("hex");
|
|
50989
|
+
}
|
|
50990
|
+
function deterministicUuid(namespace, ...parts) {
|
|
50991
|
+
const bytes = createHash16("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
|
|
50992
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
50993
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
50994
|
+
const hex = bytes.toString("hex");
|
|
50995
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
50996
|
+
}
|
|
50997
|
+
var init_canonical = () => {};
|
|
50998
|
+
|
|
50999
|
+
// src/task-manifest/types.ts
|
|
51000
|
+
var TODOS_TASK_MANIFEST_ROUTE = "todos.task-manifest.v1", TODOS_TASK_MANIFEST_SCHEMA_VERSION = 1, TodosTaskManifestError;
|
|
51001
|
+
var init_types5 = __esm(() => {
|
|
51002
|
+
TodosTaskManifestError = class TodosTaskManifestError extends Error {
|
|
51003
|
+
code;
|
|
51004
|
+
details;
|
|
51005
|
+
constructor(code, message, details = {}) {
|
|
51006
|
+
super(message);
|
|
51007
|
+
this.code = code;
|
|
51008
|
+
this.details = details;
|
|
51009
|
+
this.name = "TodosTaskManifestError";
|
|
51010
|
+
}
|
|
51011
|
+
};
|
|
51012
|
+
});
|
|
51013
|
+
|
|
51014
|
+
// src/task-manifest/schema.ts
|
|
51015
|
+
function parseTodosTaskManifest(input) {
|
|
51016
|
+
const parsed = schema.safeParse(input);
|
|
51017
|
+
if (!parsed.success) {
|
|
51018
|
+
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 });
|
|
51019
|
+
}
|
|
51020
|
+
const taskKeys = new Set;
|
|
51021
|
+
let comments = 0;
|
|
51022
|
+
let verifications = 0;
|
|
51023
|
+
for (const entry2 of parsed.data.tasks) {
|
|
51024
|
+
if (taskKeys.has(entry2.key)) {
|
|
51025
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Duplicate task key: ${entry2.key}`);
|
|
51026
|
+
}
|
|
51027
|
+
taskKeys.add(entry2.key);
|
|
51028
|
+
comments += entry2.comments?.length ?? 0;
|
|
51029
|
+
verifications += entry2.verifications?.length ?? 0;
|
|
51030
|
+
}
|
|
51031
|
+
if (comments > TODOS_TASK_MANIFEST_BOUNDS.comments || verifications > TODOS_TASK_MANIFEST_BOUNDS.verifications) {
|
|
51032
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", "Task manifest nested resource bounds exceeded", {
|
|
51033
|
+
comments,
|
|
51034
|
+
verifications
|
|
51035
|
+
});
|
|
51036
|
+
}
|
|
51037
|
+
const seenDependencies = new Set;
|
|
51038
|
+
const dependencyGraph = new Map;
|
|
51039
|
+
for (const edge of parsed.data.dependencies ?? []) {
|
|
51040
|
+
if (!taskKeys.has(edge.task) || !taskKeys.has(edge.depends_on)) {
|
|
51041
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_FOREIGN_REFERENCE", `Dependency contains foreign task key: ${edge.task} -> ${edge.depends_on}`);
|
|
51042
|
+
}
|
|
51043
|
+
if (edge.task === edge.depends_on) {
|
|
51044
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Task ${edge.task} cannot depend on itself`);
|
|
51045
|
+
}
|
|
51046
|
+
const identity = `${edge.task}\x1F${edge.depends_on}`;
|
|
51047
|
+
if (seenDependencies.has(identity)) {
|
|
51048
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Duplicate dependency: ${edge.task} -> ${edge.depends_on}`);
|
|
51049
|
+
}
|
|
51050
|
+
seenDependencies.add(identity);
|
|
51051
|
+
const prerequisites = dependencyGraph.get(edge.task) ?? [];
|
|
51052
|
+
prerequisites.push(edge.depends_on);
|
|
51053
|
+
dependencyGraph.set(edge.task, prerequisites);
|
|
51054
|
+
}
|
|
51055
|
+
const visiting = new Set;
|
|
51056
|
+
const visited = new Set;
|
|
51057
|
+
const visit = (taskKey) => {
|
|
51058
|
+
if (visiting.has(taskKey)) {
|
|
51059
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Dependency cycle contains task ${taskKey}`);
|
|
51060
|
+
}
|
|
51061
|
+
if (visited.has(taskKey))
|
|
51062
|
+
return;
|
|
51063
|
+
visiting.add(taskKey);
|
|
51064
|
+
for (const prerequisite of dependencyGraph.get(taskKey) ?? [])
|
|
51065
|
+
visit(prerequisite);
|
|
51066
|
+
visiting.delete(taskKey);
|
|
51067
|
+
visited.add(taskKey);
|
|
51068
|
+
};
|
|
51069
|
+
for (const taskKey of taskKeys)
|
|
51070
|
+
visit(taskKey);
|
|
51071
|
+
return parsed.data;
|
|
51072
|
+
}
|
|
51073
|
+
function parseTodosTaskManifestCompensation(input) {
|
|
51074
|
+
const parsed = compensationSchema.safeParse(input);
|
|
51075
|
+
if (!parsed.success) {
|
|
51076
|
+
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 });
|
|
51077
|
+
}
|
|
51078
|
+
return parsed.data;
|
|
51079
|
+
}
|
|
51080
|
+
function parseTodosTaskManifestBindingLookup(input) {
|
|
51081
|
+
const parsed = bindingLookupSchema.safeParse(input);
|
|
51082
|
+
if (!parsed.success) {
|
|
51083
|
+
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 });
|
|
51084
|
+
}
|
|
51085
|
+
return parsed.data;
|
|
51086
|
+
}
|
|
51087
|
+
var TODOS_TASK_MANIFEST_BOUNDS, key, identifier, uuid2, scalar, boundedScalarRecord = (limit, field) => exports_external.record(exports_external.string().max(200), scalar).superRefine((value, context) => {
|
|
51088
|
+
if (Object.keys(value).length > limit) {
|
|
51089
|
+
context.addIssue({ code: exports_external.ZodIssueCode.custom, message: `${field} exceeds ${limit} fields` });
|
|
51090
|
+
}
|
|
51091
|
+
}), comment, verification, task2, dependency, effect, schema, compensationSchema, bindingLookupSchema;
|
|
51092
|
+
var init_schema2 = __esm(() => {
|
|
51093
|
+
init_zod();
|
|
51094
|
+
init_types();
|
|
51095
|
+
init_types5();
|
|
51096
|
+
TODOS_TASK_MANIFEST_BOUNDS = {
|
|
51097
|
+
tasks: 128,
|
|
51098
|
+
dependencies: 512,
|
|
51099
|
+
comments: 512,
|
|
51100
|
+
verifications: 512,
|
|
51101
|
+
effects: 64,
|
|
51102
|
+
metadata_fields: 128,
|
|
51103
|
+
effect_payload_fields: 128,
|
|
51104
|
+
request_bytes: 16777216,
|
|
51105
|
+
response_bytes: 1048576
|
|
51106
|
+
};
|
|
51107
|
+
key = exports_external.string().min(1).max(96).regex(/^[a-z][a-z0-9_-]*$/);
|
|
51108
|
+
identifier = exports_external.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
51109
|
+
uuid2 = exports_external.string().uuid();
|
|
51110
|
+
scalar = exports_external.union([exports_external.string().max(4096), exports_external.number().finite(), exports_external.boolean(), exports_external.null()]);
|
|
51111
|
+
comment = exports_external.object({
|
|
51112
|
+
content: exports_external.string().min(1).max(16384),
|
|
51113
|
+
type: exports_external.enum(["comment", "progress", "status_change", "system"]).optional(),
|
|
51114
|
+
progress_pct: exports_external.number().int().min(0).max(100).optional(),
|
|
51115
|
+
agent_id: identifier.optional(),
|
|
51116
|
+
session_id: identifier.optional()
|
|
51117
|
+
}).strict();
|
|
51118
|
+
verification = exports_external.object({
|
|
51119
|
+
command: exports_external.string().min(1).max(8192),
|
|
51120
|
+
status: exports_external.enum(["passed", "failed", "unknown"]).optional(),
|
|
51121
|
+
output_summary: exports_external.string().max(16384).optional(),
|
|
51122
|
+
artifact_path: exports_external.string().max(4096).optional(),
|
|
51123
|
+
agent_id: identifier.optional()
|
|
51124
|
+
}).strict();
|
|
51125
|
+
task2 = exports_external.object({
|
|
51126
|
+
key,
|
|
51127
|
+
title: exports_external.string().min(1).max(500),
|
|
51128
|
+
description: exports_external.string().max(64000).optional(),
|
|
51129
|
+
status: exports_external.enum(TASK_STATUSES).optional(),
|
|
51130
|
+
priority: exports_external.enum(TASK_PRIORITIES).optional(),
|
|
51131
|
+
assigned_to: identifier.optional(),
|
|
51132
|
+
created_by: identifier.optional(),
|
|
51133
|
+
tags: exports_external.array(exports_external.string().min(1).max(100)).max(64).optional(),
|
|
51134
|
+
metadata: boundedScalarRecord(TODOS_TASK_MANIFEST_BOUNDS.metadata_fields, "metadata").optional(),
|
|
51135
|
+
comments: exports_external.array(comment).max(64).optional(),
|
|
51136
|
+
verifications: exports_external.array(verification).max(64).optional()
|
|
51137
|
+
}).strict();
|
|
51138
|
+
dependency = exports_external.object({ task: key, depends_on: key }).strict();
|
|
51139
|
+
effect = exports_external.object({
|
|
51140
|
+
topic: exports_external.string().min(1).max(200),
|
|
51141
|
+
payload: boundedScalarRecord(TODOS_TASK_MANIFEST_BOUNDS.effect_payload_fields, "effect payload")
|
|
51142
|
+
}).strict();
|
|
51143
|
+
schema = exports_external.object({
|
|
51144
|
+
version: exports_external.literal(1),
|
|
51145
|
+
operation_id: identifier,
|
|
51146
|
+
idempotency_key: identifier,
|
|
51147
|
+
project_id: uuid2,
|
|
51148
|
+
task_list_id: uuid2.optional(),
|
|
51149
|
+
if_binding_version: exports_external.number().int().min(0).optional(),
|
|
51150
|
+
plan: exports_external.object({
|
|
51151
|
+
key,
|
|
51152
|
+
name: exports_external.string().min(1).max(500),
|
|
51153
|
+
description: exports_external.string().max(64000).optional(),
|
|
51154
|
+
status: exports_external.enum(["active", "completed", "archived"]).optional()
|
|
51155
|
+
}).strict(),
|
|
51156
|
+
tasks: exports_external.array(task2).min(1).max(TODOS_TASK_MANIFEST_BOUNDS.tasks),
|
|
51157
|
+
dependencies: exports_external.array(dependency).max(TODOS_TASK_MANIFEST_BOUNDS.dependencies).optional(),
|
|
51158
|
+
effects: exports_external.array(effect).max(TODOS_TASK_MANIFEST_BOUNDS.effects).optional()
|
|
51159
|
+
}).strict();
|
|
51160
|
+
compensationSchema = exports_external.object({
|
|
51161
|
+
receipt_id: uuid2,
|
|
51162
|
+
idempotency_key: identifier,
|
|
51163
|
+
if_binding_version: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER)
|
|
51164
|
+
}).strict();
|
|
51165
|
+
bindingLookupSchema = exports_external.object({
|
|
51166
|
+
authority: exports_external.string().min(1).max(64),
|
|
51167
|
+
route: exports_external.string().min(1).max(128),
|
|
51168
|
+
schema_version: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
|
51169
|
+
tenant_id: identifier,
|
|
51170
|
+
plan_id: uuid2,
|
|
51171
|
+
max_items: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER)
|
|
51172
|
+
}).strict();
|
|
51173
|
+
});
|
|
51174
|
+
|
|
51175
|
+
// src/task-manifest/backend.ts
|
|
51176
|
+
function validateTaskManifestBindingLookupRows(rows, tenantId, planId) {
|
|
51177
|
+
if (rows.length === 0) {
|
|
51178
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BINDING_NOT_FOUND", `Managed task-manifest binding not found for plan: ${planId}`, { plan_id: planId });
|
|
51179
|
+
}
|
|
51180
|
+
if (rows.length !== 1) {
|
|
51181
|
+
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 });
|
|
51182
|
+
}
|
|
51183
|
+
const row = rows[0];
|
|
51184
|
+
const bindingVersion = Number(row.binding_version);
|
|
51185
|
+
const state = row.state;
|
|
51186
|
+
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") {
|
|
51187
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_LOOKUP_CONFLICT", "Task-manifest binding and immutable apply receipt disagree", { plan_id: planId });
|
|
51188
|
+
}
|
|
51189
|
+
return {
|
|
51190
|
+
plan_id: planId,
|
|
51191
|
+
apply_receipt_id: row.apply_receipt_id,
|
|
51192
|
+
binding_version: bindingVersion,
|
|
51193
|
+
state
|
|
51194
|
+
};
|
|
51195
|
+
}
|
|
51196
|
+
var UUID_PATTERN2;
|
|
51197
|
+
var init_backend = __esm(() => {
|
|
51198
|
+
init_types5();
|
|
51199
|
+
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;
|
|
51200
|
+
});
|
|
51201
|
+
|
|
51202
|
+
// src/task-manifest/reference-guard.ts
|
|
51203
|
+
function postgresTaskManifestForeignReferenceSql(tableName) {
|
|
51204
|
+
return `SELECT object_type, object_id
|
|
51205
|
+
FROM ${tableName}
|
|
51206
|
+
WHERE service = $1 AND deleted_at IS NULL
|
|
51207
|
+
AND NOT (object_type = 'plans' AND object_id = $2)
|
|
51208
|
+
AND NOT (object_type = 'tasks' AND object_id IN (
|
|
51209
|
+
SELECT value FROM jsonb_array_elements_text($3::jsonb)
|
|
51210
|
+
))
|
|
51211
|
+
AND NOT (object_type = 'dependencies' AND object_id IN (
|
|
51212
|
+
SELECT value FROM jsonb_array_elements_text($4::jsonb)
|
|
51213
|
+
))
|
|
51214
|
+
AND NOT (object_type = 'comments' AND object_id IN (
|
|
51215
|
+
SELECT value FROM jsonb_array_elements_text($5::jsonb)
|
|
51216
|
+
))
|
|
51217
|
+
AND NOT (object_type = 'verifications' AND object_id IN (
|
|
51218
|
+
SELECT value FROM jsonb_array_elements_text($6::jsonb)
|
|
51219
|
+
))
|
|
51220
|
+
AND jsonb_path_exists(
|
|
51221
|
+
payload,
|
|
51222
|
+
'$.** ? (@ == $refs[*])',
|
|
51223
|
+
jsonb_build_object('refs', $7::jsonb)
|
|
51224
|
+
)
|
|
51225
|
+
LIMIT 1`;
|
|
51226
|
+
}
|
|
51227
|
+
|
|
51228
|
+
// src/task-manifest/schema-sql.ts
|
|
51229
|
+
function sqlString(value) {
|
|
51230
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
51231
|
+
}
|
|
51232
|
+
function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
|
|
51233
|
+
const tenantDefault = sqlString(tenantId);
|
|
51234
|
+
return [
|
|
51235
|
+
`CREATE TABLE IF NOT EXISTS todos_task_manifest_receipts (
|
|
51236
|
+
receipt_id text PRIMARY KEY,
|
|
51237
|
+
tenant_id text NOT NULL,
|
|
51238
|
+
authority text NOT NULL CHECK(authority = 'todos'),
|
|
51239
|
+
route text NOT NULL,
|
|
51240
|
+
schema_version integer NOT NULL CHECK(schema_version = 1),
|
|
51241
|
+
kind text NOT NULL CHECK(kind IN ('apply', 'compensate')),
|
|
51242
|
+
operation_id text NOT NULL,
|
|
51243
|
+
idempotency_key text NOT NULL,
|
|
51244
|
+
request_digest text NOT NULL,
|
|
51245
|
+
result_digest text NOT NULL,
|
|
51246
|
+
binding_version integer NOT NULL,
|
|
51247
|
+
apply_receipt_id text,
|
|
51248
|
+
manifest_json jsonb,
|
|
51249
|
+
result_json jsonb NOT NULL,
|
|
51250
|
+
created_at timestamptz NOT NULL,
|
|
51251
|
+
UNIQUE(kind, idempotency_key)
|
|
51252
|
+
)`,
|
|
51253
|
+
`ALTER TABLE todos_task_manifest_receipts
|
|
51254
|
+
ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT ${tenantDefault}`,
|
|
51255
|
+
`ALTER TABLE todos_task_manifest_receipts
|
|
51256
|
+
ALTER COLUMN tenant_id DROP DEFAULT`,
|
|
51257
|
+
`CREATE TABLE IF NOT EXISTS todos_task_manifest_bindings (
|
|
51258
|
+
operation_id text PRIMARY KEY,
|
|
51259
|
+
tenant_id text NOT NULL,
|
|
51260
|
+
idempotency_key text NOT NULL UNIQUE,
|
|
51261
|
+
request_digest text NOT NULL,
|
|
51262
|
+
result_digest text NOT NULL,
|
|
51263
|
+
apply_receipt_id text NOT NULL UNIQUE REFERENCES todos_task_manifest_receipts(receipt_id),
|
|
51264
|
+
manifest_json jsonb NOT NULL,
|
|
51265
|
+
result_json jsonb NOT NULL,
|
|
51266
|
+
state text NOT NULL CHECK(state IN ('applied', 'compensated')),
|
|
51267
|
+
version integer NOT NULL,
|
|
51268
|
+
compensation_receipt_id text,
|
|
51269
|
+
created_at timestamptz NOT NULL,
|
|
51270
|
+
updated_at timestamptz NOT NULL
|
|
51271
|
+
)`,
|
|
51272
|
+
`ALTER TABLE todos_task_manifest_bindings
|
|
51273
|
+
ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT ${tenantDefault}`,
|
|
51274
|
+
`ALTER TABLE todos_task_manifest_bindings
|
|
51275
|
+
ALTER COLUMN tenant_id DROP DEFAULT`,
|
|
51276
|
+
`CREATE TABLE IF NOT EXISTS todos_task_manifest_outbox (
|
|
51277
|
+
id text PRIMARY KEY,
|
|
51278
|
+
apply_receipt_id text NOT NULL REFERENCES todos_task_manifest_receipts(receipt_id),
|
|
51279
|
+
topic text NOT NULL,
|
|
51280
|
+
payload jsonb NOT NULL,
|
|
51281
|
+
payload_digest text NOT NULL,
|
|
51282
|
+
status text NOT NULL CHECK(status IN ('pending', 'delivered', 'cancelled')),
|
|
51283
|
+
attempts integer NOT NULL DEFAULT 0,
|
|
51284
|
+
created_at timestamptz NOT NULL,
|
|
51285
|
+
delivered_at timestamptz
|
|
51286
|
+
)`,
|
|
51287
|
+
`CREATE INDEX IF NOT EXISTS todos_task_manifest_outbox_receipt_idx
|
|
51288
|
+
ON todos_task_manifest_outbox(apply_receipt_id, status)`,
|
|
51289
|
+
`CREATE INDEX IF NOT EXISTS todos_task_manifest_receipts_tenant_idx
|
|
51290
|
+
ON todos_task_manifest_receipts(tenant_id, receipt_id, kind)`,
|
|
51291
|
+
`CREATE INDEX IF NOT EXISTS todos_task_manifest_bindings_tenant_plan_idx
|
|
51292
|
+
ON todos_task_manifest_bindings(
|
|
51293
|
+
tenant_id,
|
|
51294
|
+
((result_json #>> '{graph,plan_id}'))
|
|
51295
|
+
)`,
|
|
51296
|
+
`CREATE OR REPLACE FUNCTION todos_task_manifest_receipts_immutable()
|
|
51297
|
+
RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN
|
|
51298
|
+
RAISE EXCEPTION 'todos task manifest receipts are immutable';
|
|
51299
|
+
END; $$`,
|
|
51300
|
+
`DROP TRIGGER IF EXISTS todos_task_manifest_receipts_immutable ON todos_task_manifest_receipts`,
|
|
51301
|
+
`CREATE TRIGGER todos_task_manifest_receipts_immutable
|
|
51302
|
+
BEFORE UPDATE OR DELETE ON todos_task_manifest_receipts
|
|
51303
|
+
FOR EACH ROW EXECUTE FUNCTION todos_task_manifest_receipts_immutable()`
|
|
51304
|
+
];
|
|
51305
|
+
}
|
|
51306
|
+
|
|
51307
|
+
// src/task-manifest/sqlite.ts
|
|
51308
|
+
var sqliteTails;
|
|
51309
|
+
var init_sqlite2 = __esm(() => {
|
|
51310
|
+
init_canonical();
|
|
51311
|
+
init_backend();
|
|
51312
|
+
init_types5();
|
|
51313
|
+
sqliteTails = new WeakMap;
|
|
51314
|
+
});
|
|
51315
|
+
|
|
51316
|
+
// src/task-manifest/postgres.ts
|
|
51317
|
+
function safeIdentifier2(value, field) {
|
|
51318
|
+
if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
|
|
51319
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `${field} must be a safe PostgreSQL identifier`);
|
|
51320
|
+
}
|
|
51321
|
+
return value;
|
|
51322
|
+
}
|
|
51323
|
+
function parseJson2(value) {
|
|
51324
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
51325
|
+
}
|
|
51326
|
+
function timestamp4(value) {
|
|
51327
|
+
return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
|
|
51328
|
+
}
|
|
51329
|
+
function fault(faults, point) {
|
|
51330
|
+
if (faults.points.has(point))
|
|
51331
|
+
throw new Error(`Injected task-manifest fault at ${point}`);
|
|
51332
|
+
}
|
|
51333
|
+
function receiptFromRow3(row) {
|
|
51334
|
+
return {
|
|
51335
|
+
receipt_id: String(row["receipt_id"]),
|
|
51336
|
+
authority: "todos",
|
|
51337
|
+
route: "todos.task-manifest.v1",
|
|
51338
|
+
schema_version: 1,
|
|
51339
|
+
kind: row["kind"],
|
|
51340
|
+
operation_id: String(row["operation_id"]),
|
|
51341
|
+
idempotency_key: String(row["idempotency_key"]),
|
|
51342
|
+
request_digest: String(row["request_digest"]),
|
|
51343
|
+
result_digest: String(row["result_digest"]),
|
|
51344
|
+
binding_version: Number(row["binding_version"]),
|
|
51345
|
+
apply_receipt_id: row["apply_receipt_id"] == null ? null : String(row["apply_receipt_id"]),
|
|
51346
|
+
created_at: timestamp4(row["created_at"])
|
|
51347
|
+
};
|
|
51348
|
+
}
|
|
51349
|
+
function taskPayload2(manifest, task3, taskId, planId, now4) {
|
|
51350
|
+
return {
|
|
51351
|
+
id: taskId,
|
|
51352
|
+
short_id: null,
|
|
51353
|
+
project_id: manifest.project_id,
|
|
51354
|
+
parent_id: null,
|
|
51355
|
+
plan_id: planId,
|
|
51356
|
+
task_list_id: manifest.task_list_id ?? null,
|
|
51357
|
+
title: task3.title,
|
|
51358
|
+
description: task3.description ?? null,
|
|
51359
|
+
status: task3.status ?? "pending",
|
|
51360
|
+
priority: task3.priority ?? "medium",
|
|
51361
|
+
agent_id: null,
|
|
51362
|
+
assigned_to: task3.assigned_to ?? null,
|
|
51363
|
+
session_id: null,
|
|
51364
|
+
working_dir: null,
|
|
51365
|
+
tags: task3.tags ?? [],
|
|
51366
|
+
metadata: task3.metadata ?? {},
|
|
51367
|
+
version: 1,
|
|
51368
|
+
locked_by: null,
|
|
51369
|
+
locked_at: null,
|
|
51370
|
+
created_at: now4,
|
|
51371
|
+
updated_at: now4,
|
|
51372
|
+
started_at: null,
|
|
51373
|
+
completed_at: null,
|
|
51374
|
+
due_at: null,
|
|
51375
|
+
estimated_minutes: null,
|
|
51376
|
+
actual_minutes: null,
|
|
51377
|
+
requires_approval: false,
|
|
51378
|
+
approved_by: null,
|
|
51379
|
+
approved_at: null,
|
|
51380
|
+
recurrence_rule: null,
|
|
51381
|
+
recurrence_parent_id: null,
|
|
51382
|
+
spawns_template_id: null,
|
|
51383
|
+
confidence: null,
|
|
51384
|
+
reason: null,
|
|
51385
|
+
spawned_from_session: null,
|
|
51386
|
+
assigned_by: null,
|
|
51387
|
+
created_by: task3.created_by ?? null,
|
|
51388
|
+
assigned_from_project: null,
|
|
51389
|
+
task_type: null,
|
|
51390
|
+
cost_tokens: 0,
|
|
51391
|
+
cost_usd: 0,
|
|
51392
|
+
delegated_from: null,
|
|
51393
|
+
delegation_depth: 0,
|
|
51394
|
+
retry_count: 0,
|
|
51395
|
+
max_retries: 0,
|
|
51396
|
+
retry_after: null,
|
|
51397
|
+
sla_minutes: null,
|
|
51398
|
+
runner_id: null,
|
|
51399
|
+
runner_started_at: null,
|
|
51400
|
+
runner_completed_at: null,
|
|
51401
|
+
current_step: null,
|
|
51402
|
+
total_steps: null,
|
|
51403
|
+
machine_id: null,
|
|
51404
|
+
synced_at: null,
|
|
51405
|
+
archived_at: null
|
|
51406
|
+
};
|
|
51407
|
+
}
|
|
51408
|
+
function planPayload(input) {
|
|
51409
|
+
return {
|
|
51410
|
+
id: input.graph.plan_id,
|
|
51411
|
+
slug: null,
|
|
51412
|
+
project_id: input.manifest.project_id,
|
|
51413
|
+
task_list_id: input.manifest.task_list_id ?? null,
|
|
51414
|
+
agent_id: null,
|
|
51415
|
+
name: input.manifest.plan.name,
|
|
51416
|
+
description: input.manifest.plan.description ?? null,
|
|
51417
|
+
status: input.manifest.plan.status ?? "active",
|
|
51418
|
+
created_at: input.now,
|
|
51419
|
+
updated_at: input.now,
|
|
51420
|
+
machine_id: null,
|
|
51421
|
+
synced_at: null
|
|
51422
|
+
};
|
|
51423
|
+
}
|
|
51424
|
+
function placeholders2(start, count2) {
|
|
51425
|
+
return Array.from({ length: count2 }, (_, index) => `$${start + index}`).join(",");
|
|
51426
|
+
}
|
|
51427
|
+
|
|
51428
|
+
class PostgresTodosTaskManifestBackend {
|
|
51429
|
+
client;
|
|
51430
|
+
kind = "postgresql";
|
|
51431
|
+
service;
|
|
51432
|
+
tableName;
|
|
51433
|
+
tenantId;
|
|
51434
|
+
schemaReady = null;
|
|
51435
|
+
constructor(client, options = {}) {
|
|
51436
|
+
this.client = client;
|
|
51437
|
+
this.service = options.service ?? "todos";
|
|
51438
|
+
this.tableName = safeIdentifier2(options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE, "tableName");
|
|
51439
|
+
this.tenantId = options.tenantId ?? "default";
|
|
51440
|
+
}
|
|
51441
|
+
async ensureSchema() {
|
|
51442
|
+
this.schemaReady ??= (async () => {
|
|
51443
|
+
for (const sql of postgresTodosSyncSchemaSql(this.tableName))
|
|
51444
|
+
await this.client.query(sql);
|
|
51445
|
+
for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
|
|
51446
|
+
await this.client.query(sql);
|
|
51447
|
+
})();
|
|
51448
|
+
await this.schemaReady;
|
|
51449
|
+
}
|
|
51450
|
+
async insertSync(tx, objectType2, objectId, payload, now4) {
|
|
51451
|
+
await tx.query(`INSERT INTO ${this.tableName} (
|
|
51452
|
+
service, object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version
|
|
51453
|
+
) VALUES ($1, $2, $3, $4::jsonb, $5, NULL, NULL, 1)`, [
|
|
51454
|
+
this.service,
|
|
51455
|
+
objectType2,
|
|
51456
|
+
objectId,
|
|
51457
|
+
canonicalJson(payload),
|
|
51458
|
+
now4
|
|
51459
|
+
]);
|
|
51460
|
+
}
|
|
51461
|
+
async apply(input, faults) {
|
|
51462
|
+
await this.ensureSchema();
|
|
51463
|
+
return this.client.transaction(async (tx) => {
|
|
51464
|
+
const { manifest } = input;
|
|
51465
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${manifest.operation_id}`]);
|
|
51466
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1Fidempotency\x1F${manifest.idempotency_key}`]);
|
|
51467
|
+
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]);
|
|
51468
|
+
if (existing.rows[0]) {
|
|
51469
|
+
const binding = existing.rows[0];
|
|
51470
|
+
if (binding["idempotency_key"] !== manifest.idempotency_key || binding["request_digest"] !== input.request_digest) {
|
|
51471
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Operation is already bound to a different request");
|
|
51472
|
+
}
|
|
51473
|
+
if (binding["state"] !== "applied") {
|
|
51474
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", "Operation was already compensated");
|
|
51475
|
+
}
|
|
51476
|
+
return { ...parseJson2(binding["result_json"]), duplicate: true };
|
|
51477
|
+
}
|
|
51478
|
+
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]);
|
|
51479
|
+
if (reused.rows[0])
|
|
51480
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Idempotency key is already used");
|
|
51481
|
+
if (manifest.if_binding_version !== undefined && manifest.if_binding_version !== 0) {
|
|
51482
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "New manifest binding version must be 0");
|
|
51483
|
+
}
|
|
51484
|
+
const project = await tx.query(`SELECT 1 AS found FROM ${this.tableName}
|
|
51485
|
+
WHERE service = $1 AND object_type = 'projects' AND object_id = $2 AND deleted_at IS NULL LIMIT 1`, [this.service, manifest.project_id]);
|
|
51486
|
+
if (!project.rows[0])
|
|
51487
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_FOREIGN_REFERENCE", "Project does not exist");
|
|
51488
|
+
if (manifest.task_list_id) {
|
|
51489
|
+
const taskList = await tx.query(`SELECT payload FROM ${this.tableName}
|
|
51490
|
+
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]);
|
|
51491
|
+
const payload = taskList.rows[0] ? parseJson2(taskList.rows[0]["payload"]) : null;
|
|
51492
|
+
if (!payload || payload["project_id"] !== manifest.project_id) {
|
|
51493
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_FOREIGN_REFERENCE", "Task list does not belong to the project");
|
|
51494
|
+
}
|
|
51495
|
+
}
|
|
51496
|
+
const objectIds = [input.graph.plan_id, ...Object.values(input.graph.task_ids), ...input.graph.comment_ids, ...input.graph.verification_ids, ...input.graph.dependency_ids];
|
|
51497
|
+
const conflict = await tx.query(`SELECT object_id FROM ${this.tableName}
|
|
51498
|
+
WHERE service = $1 AND object_id IN (${placeholders2(2, objectIds.length)}) LIMIT 1`, [this.service, ...objectIds]);
|
|
51499
|
+
if (conflict.rows[0])
|
|
51500
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", "A deterministic graph id already exists");
|
|
51501
|
+
await this.insertSync(tx, "plans", input.graph.plan_id, planPayload(input), input.now);
|
|
51502
|
+
fault(faults, "after_plan_write");
|
|
51503
|
+
for (const task3 of manifest.tasks) {
|
|
51504
|
+
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);
|
|
51505
|
+
}
|
|
51506
|
+
fault(faults, "after_task_write");
|
|
51507
|
+
for (const [index, edge] of (manifest.dependencies ?? []).entries()) {
|
|
51508
|
+
await this.insertSync(tx, "dependencies", input.graph.dependency_ids[index], {
|
|
51509
|
+
id: input.graph.dependency_ids[index],
|
|
51510
|
+
task_id: input.graph.task_ids[edge.task],
|
|
51511
|
+
depends_on: input.graph.task_ids[edge.depends_on],
|
|
51512
|
+
created_at: input.now,
|
|
51513
|
+
updated_at: input.now
|
|
51514
|
+
}, input.now);
|
|
51515
|
+
}
|
|
51516
|
+
fault(faults, "after_dependency_write");
|
|
51517
|
+
let commentIndex = 0;
|
|
51518
|
+
for (const task3 of manifest.tasks)
|
|
51519
|
+
for (const comment2 of task3.comments ?? []) {
|
|
51520
|
+
const id = input.graph.comment_ids[commentIndex++];
|
|
51521
|
+
await this.insertSync(tx, "comments", id, {
|
|
51522
|
+
id,
|
|
51523
|
+
task_id: input.graph.task_ids[task3.key],
|
|
51524
|
+
agent_id: comment2.agent_id ?? null,
|
|
51525
|
+
session_id: comment2.session_id ?? null,
|
|
51526
|
+
content: comment2.content,
|
|
51527
|
+
type: comment2.type ?? "comment",
|
|
51528
|
+
progress_pct: comment2.progress_pct ?? null,
|
|
51529
|
+
created_at: input.now
|
|
51530
|
+
}, input.now);
|
|
51531
|
+
}
|
|
51532
|
+
fault(faults, "after_comment_write");
|
|
51533
|
+
let verificationIndex = 0;
|
|
51534
|
+
for (const task3 of manifest.tasks)
|
|
51535
|
+
for (const verification2 of task3.verifications ?? []) {
|
|
51536
|
+
const id = input.graph.verification_ids[verificationIndex++];
|
|
51537
|
+
await this.insertSync(tx, "verifications", id, {
|
|
51538
|
+
id,
|
|
51539
|
+
task_id: input.graph.task_ids[task3.key],
|
|
51540
|
+
command: verification2.command,
|
|
51541
|
+
status: verification2.status ?? "unknown",
|
|
51542
|
+
output_summary: verification2.output_summary ?? null,
|
|
51543
|
+
artifact_path: verification2.artifact_path ?? null,
|
|
51544
|
+
agent_id: verification2.agent_id ?? null,
|
|
51545
|
+
run_at: input.now,
|
|
51546
|
+
created_at: input.now,
|
|
51547
|
+
updated_at: input.now
|
|
51548
|
+
}, input.now);
|
|
51549
|
+
}
|
|
51550
|
+
fault(faults, "after_verification_write");
|
|
51551
|
+
const readback = await this.readback(tx, input.graph);
|
|
51552
|
+
const expected = {
|
|
51553
|
+
plans: 1,
|
|
51554
|
+
tasks: manifest.tasks.length,
|
|
51555
|
+
dependencies: manifest.dependencies?.length ?? 0,
|
|
51556
|
+
comments: input.graph.comment_ids.length,
|
|
51557
|
+
verifications: input.graph.verification_ids.length,
|
|
51558
|
+
complete: true
|
|
51559
|
+
};
|
|
51560
|
+
if (canonicalJson(readback) !== canonicalJson(expected)) {
|
|
51561
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_READBACK_MISMATCH", "Exact PostgreSQL graph readback did not match", { expected, readback });
|
|
51562
|
+
}
|
|
51563
|
+
const receipt = {
|
|
51564
|
+
receipt_id: input.receipt_id,
|
|
51565
|
+
authority: "todos",
|
|
51566
|
+
route: "todos.task-manifest.v1",
|
|
51567
|
+
schema_version: 1,
|
|
51568
|
+
kind: "apply",
|
|
51569
|
+
operation_id: manifest.operation_id,
|
|
51570
|
+
idempotency_key: manifest.idempotency_key,
|
|
51571
|
+
request_digest: input.request_digest,
|
|
51572
|
+
result_digest: input.result_digest,
|
|
51573
|
+
binding_version: 1,
|
|
51574
|
+
apply_receipt_id: null,
|
|
51575
|
+
created_at: input.now
|
|
51576
|
+
};
|
|
51577
|
+
const result = {
|
|
51578
|
+
duplicate: false,
|
|
51579
|
+
receipt,
|
|
51580
|
+
graph: input.graph,
|
|
51581
|
+
readback,
|
|
51582
|
+
outbox_ids: input.outbox.map((entry2) => entry2.id),
|
|
51583
|
+
result_digest: input.result_digest
|
|
51584
|
+
};
|
|
51585
|
+
const manifestJson = canonicalJson(manifest);
|
|
51586
|
+
const resultJson = canonicalJson(result);
|
|
51587
|
+
await tx.query(`INSERT INTO todos_task_manifest_receipts (
|
|
51588
|
+
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
|
|
51589
|
+
request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
|
|
51590
|
+
) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'apply', $3, $4, $5, $6, 1, NULL, $7::jsonb, $8::jsonb, $9)`, [
|
|
51591
|
+
input.receipt_id,
|
|
51592
|
+
this.tenantId,
|
|
51593
|
+
manifest.operation_id,
|
|
51594
|
+
manifest.idempotency_key,
|
|
51595
|
+
input.request_digest,
|
|
51596
|
+
input.result_digest,
|
|
51597
|
+
manifestJson,
|
|
51598
|
+
resultJson,
|
|
51599
|
+
input.now
|
|
51600
|
+
]);
|
|
51601
|
+
for (const entry2 of input.outbox) {
|
|
51602
|
+
await tx.query(`INSERT INTO todos_task_manifest_outbox (
|
|
51603
|
+
id, apply_receipt_id, topic, payload, payload_digest, status, created_at
|
|
51604
|
+
) VALUES ($1, $2, $3, $4::jsonb, $5, 'pending', $6)`, [
|
|
51605
|
+
entry2.id,
|
|
51606
|
+
input.receipt_id,
|
|
51607
|
+
entry2.topic,
|
|
51608
|
+
canonicalJson(entry2.payload),
|
|
51609
|
+
entry2.digest,
|
|
51610
|
+
input.now
|
|
51611
|
+
]);
|
|
51612
|
+
}
|
|
51613
|
+
fault(faults, "after_outbox_write");
|
|
51614
|
+
await tx.query(`INSERT INTO todos_task_manifest_bindings (
|
|
51615
|
+
operation_id, tenant_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
|
|
51616
|
+
manifest_json, result_json, state, version, created_at, updated_at
|
|
51617
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8::jsonb, 'applied', 1, $9, $9)`, [
|
|
51618
|
+
manifest.operation_id,
|
|
51619
|
+
this.tenantId,
|
|
51620
|
+
manifest.idempotency_key,
|
|
51621
|
+
input.request_digest,
|
|
51622
|
+
input.result_digest,
|
|
51623
|
+
input.receipt_id,
|
|
51624
|
+
manifestJson,
|
|
51625
|
+
resultJson,
|
|
51626
|
+
input.now
|
|
51627
|
+
]);
|
|
51628
|
+
fault(faults, "after_receipt_write");
|
|
51629
|
+
return result;
|
|
51630
|
+
});
|
|
51631
|
+
}
|
|
51632
|
+
async readExact(receiptId2) {
|
|
51633
|
+
await this.ensureSchema();
|
|
51634
|
+
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]);
|
|
51635
|
+
if (!result.rows[0])
|
|
51636
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", `Apply receipt not found: ${receiptId2}`);
|
|
51637
|
+
return { ...parseJson2(result.rows[0]["result_json"]), duplicate: false };
|
|
51638
|
+
}
|
|
51639
|
+
async lookupBindingByPlanId(planId) {
|
|
51640
|
+
await this.ensureSchema();
|
|
51641
|
+
const result = await this.client.query(`
|
|
51642
|
+
SELECT
|
|
51643
|
+
b.apply_receipt_id AS apply_receipt_id,
|
|
51644
|
+
b.state AS state,
|
|
51645
|
+
b.version AS binding_version,
|
|
51646
|
+
b.tenant_id AS binding_tenant_id,
|
|
51647
|
+
b.operation_id AS binding_operation_id,
|
|
51648
|
+
b.result_json #>> '{graph,plan_id}' AS binding_plan_id,
|
|
51649
|
+
r.tenant_id AS receipt_tenant_id,
|
|
51650
|
+
r.authority AS receipt_authority,
|
|
51651
|
+
r.route AS receipt_route,
|
|
51652
|
+
r.schema_version AS receipt_schema_version,
|
|
51653
|
+
r.kind AS receipt_kind,
|
|
51654
|
+
r.operation_id AS receipt_operation_id,
|
|
51655
|
+
r.result_json #>> '{graph,plan_id}' AS receipt_plan_id
|
|
51656
|
+
FROM todos_task_manifest_bindings b
|
|
51657
|
+
LEFT JOIN todos_task_manifest_receipts r
|
|
51658
|
+
ON r.receipt_id = b.apply_receipt_id
|
|
51659
|
+
AND r.tenant_id = b.tenant_id
|
|
51660
|
+
WHERE b.tenant_id = $1
|
|
51661
|
+
AND b.result_json #>> '{graph,plan_id}' = $2
|
|
51662
|
+
LIMIT 2
|
|
51663
|
+
`, [this.tenantId, planId]);
|
|
51664
|
+
return validateTaskManifestBindingLookupRows(result.rows, this.tenantId, planId);
|
|
51665
|
+
}
|
|
51666
|
+
async markOutboxDelivered(outboxId, deliveredAt) {
|
|
51667
|
+
await this.ensureSchema();
|
|
51668
|
+
await this.client.transaction(async (tx) => {
|
|
51669
|
+
const owned = await tx.query(`SELECT r.operation_id
|
|
51670
|
+
FROM todos_task_manifest_outbox o
|
|
51671
|
+
JOIN todos_task_manifest_receipts r
|
|
51672
|
+
ON r.receipt_id = o.apply_receipt_id
|
|
51673
|
+
WHERE r.tenant_id = $1
|
|
51674
|
+
AND r.authority = 'todos'
|
|
51675
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51676
|
+
AND r.schema_version = 1
|
|
51677
|
+
AND r.kind = 'apply'
|
|
51678
|
+
AND o.id = $2
|
|
51679
|
+
LIMIT 1`, [this.tenantId, outboxId]);
|
|
51680
|
+
const operationId = owned.rows[0]?.operation_id;
|
|
51681
|
+
if (operationId == null) {
|
|
51682
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
|
|
51683
|
+
}
|
|
51684
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${String(operationId)}`]);
|
|
51685
|
+
const result = await tx.query(`UPDATE todos_task_manifest_outbox
|
|
51686
|
+
SET status = 'delivered', delivered_at = $1, attempts = attempts + 1
|
|
51687
|
+
WHERE id = $2 AND status = 'pending'
|
|
51688
|
+
AND EXISTS (
|
|
51689
|
+
SELECT 1 FROM todos_task_manifest_receipts r
|
|
51690
|
+
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
51691
|
+
AND r.tenant_id = $3
|
|
51692
|
+
AND r.authority = 'todos'
|
|
51693
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51694
|
+
AND r.schema_version = 1
|
|
51695
|
+
AND r.kind = 'apply'
|
|
51696
|
+
)
|
|
51697
|
+
RETURNING id`, [deliveredAt, outboxId, this.tenantId]);
|
|
51698
|
+
if (result.rows[0])
|
|
51699
|
+
return;
|
|
51700
|
+
const existing = await tx.query(`SELECT o.status
|
|
51701
|
+
FROM todos_task_manifest_outbox o
|
|
51702
|
+
JOIN todos_task_manifest_receipts r
|
|
51703
|
+
ON r.receipt_id = o.apply_receipt_id
|
|
51704
|
+
WHERE r.tenant_id = $1
|
|
51705
|
+
AND r.authority = 'todos'
|
|
51706
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51707
|
+
AND r.schema_version = 1
|
|
51708
|
+
AND r.kind = 'apply'
|
|
51709
|
+
AND o.id = $2
|
|
51710
|
+
LIMIT 1`, [this.tenantId, outboxId]);
|
|
51711
|
+
if (existing.rows[0]?.status === "delivered")
|
|
51712
|
+
return;
|
|
51713
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
|
|
51714
|
+
});
|
|
51715
|
+
}
|
|
51716
|
+
async compensate(input, receipt, compensationReceiptId, requestDigest, now4) {
|
|
51717
|
+
await this.ensureSchema();
|
|
51718
|
+
return this.client.transaction(async (tx) => {
|
|
51719
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${receipt.operation_id}`]);
|
|
51720
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1Fcompensation-idempotency\x1F${input.idempotency_key}`]);
|
|
51721
|
+
const previous = await tx.query(`SELECT apply_receipt_id, request_digest, result_json
|
|
51722
|
+
FROM todos_task_manifest_receipts
|
|
51723
|
+
WHERE tenant_id = $1 AND kind = 'compensate' AND idempotency_key = $2
|
|
51724
|
+
LIMIT 1`, [this.tenantId, input.idempotency_key]);
|
|
51725
|
+
if (previous.rows[0]) {
|
|
51726
|
+
const row = previous.rows[0];
|
|
51727
|
+
if (row["apply_receipt_id"] !== input.receipt_id || row["request_digest"] !== requestDigest) {
|
|
51728
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Compensation idempotency key is already used");
|
|
51729
|
+
}
|
|
51730
|
+
return { ...parseJson2(row["result_json"]), duplicate: true };
|
|
51731
|
+
}
|
|
51732
|
+
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]);
|
|
51733
|
+
const applyRow = applyRows.rows[0];
|
|
51734
|
+
if (!applyRow)
|
|
51735
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", "Apply receipt not found");
|
|
51736
|
+
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]);
|
|
51737
|
+
const binding = bindingRows.rows[0];
|
|
51738
|
+
if (!binding || Number(binding["version"]) !== input.if_binding_version) {
|
|
51739
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding version changed before compensation");
|
|
51740
|
+
}
|
|
51741
|
+
if (binding["state"] !== "applied")
|
|
51742
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not applied");
|
|
51743
|
+
const delivered = await tx.query(`SELECT o.id FROM todos_task_manifest_outbox o
|
|
51744
|
+
JOIN todos_task_manifest_receipts r ON r.receipt_id = o.apply_receipt_id
|
|
51745
|
+
WHERE r.tenant_id = $1
|
|
51746
|
+
AND r.authority = 'todos'
|
|
51747
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51748
|
+
AND r.schema_version = 1
|
|
51749
|
+
AND r.kind = 'apply'
|
|
51750
|
+
AND o.apply_receipt_id = $2
|
|
51751
|
+
AND o.status = 'delivered'
|
|
51752
|
+
LIMIT 1`, [this.tenantId, input.receipt_id]);
|
|
51753
|
+
if (delivered.rows[0])
|
|
51754
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
|
|
51755
|
+
const applyResult = parseJson2(applyRow["result_json"]);
|
|
51756
|
+
const manifest = parseJson2(applyRow["manifest_json"]);
|
|
51757
|
+
const expectedEffects = [
|
|
51758
|
+
{
|
|
51759
|
+
topic: "todos.task-manifest.applied",
|
|
51760
|
+
payload: { operation_id: manifest.operation_id, project_id: manifest.project_id }
|
|
51761
|
+
},
|
|
51762
|
+
...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
|
|
51763
|
+
];
|
|
51764
|
+
const outboxRows = await tx.query(`SELECT o.id, o.topic, o.payload, o.payload_digest, o.status, o.attempts, o.delivered_at
|
|
51765
|
+
FROM todos_task_manifest_outbox o
|
|
51766
|
+
WHERE o.apply_receipt_id = $1
|
|
51767
|
+
AND EXISTS (
|
|
51768
|
+
SELECT 1 FROM todos_task_manifest_receipts r
|
|
51769
|
+
WHERE r.receipt_id = o.apply_receipt_id
|
|
51770
|
+
AND r.tenant_id = $2
|
|
51771
|
+
AND r.authority = 'todos'
|
|
51772
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51773
|
+
AND r.schema_version = 1
|
|
51774
|
+
AND r.kind = 'apply'
|
|
51775
|
+
)
|
|
51776
|
+
ORDER BY o.id
|
|
51777
|
+
FOR UPDATE OF o`, [input.receipt_id, this.tenantId]);
|
|
51778
|
+
if (outboxRows.rows.length !== expectedEffects.length) {
|
|
51779
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
|
|
51780
|
+
}
|
|
51781
|
+
const outboxById = new Map(outboxRows.rows.map((entry2) => [String(entry2["id"]), entry2]));
|
|
51782
|
+
for (const [index, expectedEffect] of expectedEffects.entries()) {
|
|
51783
|
+
const stored2 = outboxById.get(applyResult.outbox_ids[index]);
|
|
51784
|
+
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) {
|
|
51785
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
|
|
51786
|
+
}
|
|
51787
|
+
}
|
|
51788
|
+
const taskIds = Object.values(applyResult.graph.task_ids);
|
|
51789
|
+
const foreignReference = await tx.query(postgresTaskManifestForeignReferenceSql(this.tableName), [
|
|
51790
|
+
this.service,
|
|
51791
|
+
applyResult.graph.plan_id,
|
|
51792
|
+
taskIds,
|
|
51793
|
+
applyResult.graph.dependency_ids,
|
|
51794
|
+
applyResult.graph.comment_ids,
|
|
51795
|
+
applyResult.graph.verification_ids,
|
|
51796
|
+
[applyResult.graph.plan_id, ...taskIds]
|
|
51797
|
+
]);
|
|
51798
|
+
if (foreignReference.rows[0]) {
|
|
51799
|
+
const row = foreignReference.rows[0];
|
|
51800
|
+
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);
|
|
51801
|
+
}
|
|
51802
|
+
const actualReadback = await this.readback(tx, applyResult.graph);
|
|
51803
|
+
if (canonicalJson(actualReadback) !== canonicalJson(applyResult.readback)) {
|
|
51804
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: graph changed since apply");
|
|
51805
|
+
}
|
|
51806
|
+
const appliedAt = receiptFromRow3(applyRow).created_at;
|
|
51807
|
+
const expectedPayloads = new Map;
|
|
51808
|
+
expectedPayloads.set(applyResult.graph.plan_id, {
|
|
51809
|
+
type: "plans",
|
|
51810
|
+
payload: canonicalJson(planPayload({ manifest, graph: applyResult.graph, now: appliedAt }))
|
|
51811
|
+
});
|
|
51812
|
+
for (const task3 of manifest.tasks)
|
|
51813
|
+
expectedPayloads.set(applyResult.graph.task_ids[task3.key], {
|
|
51814
|
+
type: "tasks",
|
|
51815
|
+
payload: canonicalJson(taskPayload2(manifest, task3, applyResult.graph.task_ids[task3.key], applyResult.graph.plan_id, appliedAt))
|
|
51816
|
+
});
|
|
51817
|
+
for (const [index, edge] of (manifest.dependencies ?? []).entries())
|
|
51818
|
+
expectedPayloads.set(applyResult.graph.dependency_ids[index], {
|
|
51819
|
+
type: "dependencies",
|
|
51820
|
+
payload: canonicalJson({
|
|
51821
|
+
id: applyResult.graph.dependency_ids[index],
|
|
51822
|
+
task_id: applyResult.graph.task_ids[edge.task],
|
|
51823
|
+
depends_on: applyResult.graph.task_ids[edge.depends_on],
|
|
51824
|
+
created_at: appliedAt,
|
|
51825
|
+
updated_at: appliedAt
|
|
51826
|
+
})
|
|
51827
|
+
});
|
|
51828
|
+
let expectedCommentIndex = 0;
|
|
51829
|
+
for (const task3 of manifest.tasks)
|
|
51830
|
+
for (const comment2 of task3.comments ?? []) {
|
|
51831
|
+
const id = applyResult.graph.comment_ids[expectedCommentIndex++];
|
|
51832
|
+
expectedPayloads.set(id, {
|
|
51833
|
+
type: "comments",
|
|
51834
|
+
payload: canonicalJson({
|
|
51835
|
+
id,
|
|
51836
|
+
task_id: applyResult.graph.task_ids[task3.key],
|
|
51837
|
+
agent_id: comment2.agent_id ?? null,
|
|
51838
|
+
session_id: comment2.session_id ?? null,
|
|
51839
|
+
content: comment2.content,
|
|
51840
|
+
type: comment2.type ?? "comment",
|
|
51841
|
+
progress_pct: comment2.progress_pct ?? null,
|
|
51842
|
+
created_at: appliedAt
|
|
51843
|
+
})
|
|
51844
|
+
});
|
|
51845
|
+
}
|
|
51846
|
+
let expectedVerificationIndex = 0;
|
|
51847
|
+
for (const task3 of manifest.tasks)
|
|
51848
|
+
for (const verification2 of task3.verifications ?? []) {
|
|
51849
|
+
const id = applyResult.graph.verification_ids[expectedVerificationIndex++];
|
|
51850
|
+
expectedPayloads.set(id, {
|
|
51851
|
+
type: "verifications",
|
|
51852
|
+
payload: canonicalJson({
|
|
51853
|
+
id,
|
|
51854
|
+
task_id: applyResult.graph.task_ids[task3.key],
|
|
51855
|
+
command: verification2.command,
|
|
51856
|
+
status: verification2.status ?? "unknown",
|
|
51857
|
+
output_summary: verification2.output_summary ?? null,
|
|
51858
|
+
artifact_path: verification2.artifact_path ?? null,
|
|
51859
|
+
agent_id: verification2.agent_id ?? null,
|
|
51860
|
+
run_at: appliedAt,
|
|
51861
|
+
created_at: appliedAt,
|
|
51862
|
+
updated_at: appliedAt
|
|
51863
|
+
})
|
|
51864
|
+
});
|
|
51865
|
+
}
|
|
51866
|
+
const managedIds = [...expectedPayloads.keys()];
|
|
51867
|
+
const stored = await tx.query(`SELECT object_type, object_id, payload FROM ${this.tableName}
|
|
51868
|
+
WHERE service = $1 AND object_id IN (${placeholders2(2, managedIds.length)})`, [this.service, ...managedIds]);
|
|
51869
|
+
for (const row of stored.rows) {
|
|
51870
|
+
const expected = expectedPayloads.get(String(row["object_id"]));
|
|
51871
|
+
if (!expected || expected.type !== row["object_type"] || expected.payload !== canonicalJson(parseJson2(row["payload"]))) {
|
|
51872
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: managed graph values changed");
|
|
51873
|
+
}
|
|
51874
|
+
}
|
|
51875
|
+
if (stored.rows.length !== managedIds.length)
|
|
51876
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: managed graph is incomplete");
|
|
51877
|
+
const cancelled = await tx.query(`UPDATE todos_task_manifest_outbox
|
|
51878
|
+
SET status = 'cancelled'
|
|
51879
|
+
WHERE apply_receipt_id = $1 AND status = 'pending'
|
|
51880
|
+
AND EXISTS (
|
|
51881
|
+
SELECT 1 FROM todos_task_manifest_receipts r
|
|
51882
|
+
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
51883
|
+
AND r.tenant_id = $2
|
|
51884
|
+
AND r.authority = 'todos'
|
|
51885
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51886
|
+
AND r.schema_version = 1
|
|
51887
|
+
AND r.kind = 'apply'
|
|
51888
|
+
)
|
|
51889
|
+
RETURNING id`, [input.receipt_id, this.tenantId]);
|
|
51890
|
+
const cancelledIds = new Set(cancelled.rows.map((row) => String(row.id)));
|
|
51891
|
+
if (cancelledIds.size !== applyResult.outbox_ids.length || applyResult.outbox_ids.some((id) => !cancelledIds.has(id))) {
|
|
51892
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: failed to cancel every expected outbox row");
|
|
51893
|
+
}
|
|
51894
|
+
const typedIds = [
|
|
51895
|
+
["dependencies", applyResult.graph.dependency_ids],
|
|
51896
|
+
["comments", applyResult.graph.comment_ids],
|
|
51897
|
+
["verifications", applyResult.graph.verification_ids],
|
|
51898
|
+
["tasks", taskIds],
|
|
51899
|
+
["plans", [applyResult.graph.plan_id]]
|
|
51900
|
+
];
|
|
51901
|
+
for (const [objectType2, ids2] of typedIds) {
|
|
51902
|
+
if (!ids2.length)
|
|
51903
|
+
continue;
|
|
51904
|
+
await tx.query(`DELETE FROM ${this.tableName} WHERE service = $1 AND object_type = $2
|
|
51905
|
+
AND object_id IN (${placeholders2(3, ids2.length)})`, [this.service, objectType2, ...ids2]);
|
|
51906
|
+
}
|
|
51907
|
+
const readback = await this.readback(tx, applyResult.graph);
|
|
51908
|
+
const result = { duplicate: false, receipt, absent: true, readback };
|
|
51909
|
+
await tx.query(`INSERT INTO todos_task_manifest_receipts (
|
|
51910
|
+
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
|
|
51911
|
+
request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
|
|
51912
|
+
) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'compensate', $3, $4, $5, $6, $7, $8, NULL, $9::jsonb, $10)`, [
|
|
51913
|
+
compensationReceiptId,
|
|
51914
|
+
this.tenantId,
|
|
51915
|
+
receipt.operation_id,
|
|
51916
|
+
input.idempotency_key,
|
|
51917
|
+
requestDigest,
|
|
51918
|
+
receipt.result_digest,
|
|
51919
|
+
receipt.binding_version,
|
|
51920
|
+
input.receipt_id,
|
|
51921
|
+
canonicalJson(result),
|
|
51922
|
+
now4
|
|
51923
|
+
]);
|
|
51924
|
+
const updated = await tx.query(`UPDATE todos_task_manifest_bindings
|
|
51925
|
+
SET state = 'compensated', version = $1, compensation_receipt_id = $2, updated_at = $3
|
|
51926
|
+
WHERE tenant_id = $4 AND operation_id = $5 AND state = 'applied' AND version = $6 RETURNING operation_id`, [
|
|
51927
|
+
receipt.binding_version,
|
|
51928
|
+
compensationReceiptId,
|
|
51929
|
+
now4,
|
|
51930
|
+
this.tenantId,
|
|
51931
|
+
receipt.operation_id,
|
|
51932
|
+
input.if_binding_version
|
|
51933
|
+
]);
|
|
51934
|
+
if (!updated.rows[0])
|
|
51935
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding changed during compensation");
|
|
51936
|
+
return result;
|
|
51937
|
+
});
|
|
51938
|
+
}
|
|
51939
|
+
async readback(tx, graph) {
|
|
51940
|
+
const count2 = async (objectType2, ids2) => {
|
|
51941
|
+
if (!ids2.length)
|
|
51942
|
+
return 0;
|
|
51943
|
+
const result = await tx.query(`SELECT count(*) AS count FROM ${this.tableName}
|
|
51944
|
+
WHERE service = $1 AND object_type = $2 AND deleted_at IS NULL
|
|
51945
|
+
AND object_id IN (${placeholders2(3, ids2.length)})`, [this.service, objectType2, ...ids2]);
|
|
51946
|
+
return Number(result.rows[0]?.count ?? 0);
|
|
51947
|
+
};
|
|
51948
|
+
return {
|
|
51949
|
+
plans: await count2("plans", [graph.plan_id]),
|
|
51950
|
+
tasks: await count2("tasks", Object.values(graph.task_ids)),
|
|
51951
|
+
dependencies: await count2("dependencies", graph.dependency_ids),
|
|
51952
|
+
comments: await count2("comments", graph.comment_ids),
|
|
51953
|
+
verifications: await count2("verifications", graph.verification_ids),
|
|
51954
|
+
complete: true
|
|
51955
|
+
};
|
|
51956
|
+
}
|
|
51957
|
+
}
|
|
51958
|
+
var init_postgres3 = __esm(() => {
|
|
51959
|
+
init_canonical();
|
|
51960
|
+
init_backend();
|
|
51961
|
+
init_postgres_sync();
|
|
51962
|
+
init_types5();
|
|
51963
|
+
});
|
|
51964
|
+
|
|
51965
|
+
// src/task-manifest/authority.ts
|
|
51966
|
+
function resolveTenantId(value) {
|
|
51967
|
+
const tenantId = value ?? "default";
|
|
51968
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(tenantId)) {
|
|
51969
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "tenantId must be a bounded exact authority identifier");
|
|
51970
|
+
}
|
|
51971
|
+
return tenantId;
|
|
51972
|
+
}
|
|
51973
|
+
function normalize(input, now4) {
|
|
51974
|
+
const parsed = parseTodosTaskManifest(input);
|
|
51975
|
+
const requestBytes = Buffer.byteLength(canonicalJson(parsed), "utf8");
|
|
51976
|
+
if (requestBytes > TODOS_TASK_MANIFEST_BOUNDS.request_bytes) {
|
|
51977
|
+
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 });
|
|
51978
|
+
}
|
|
51979
|
+
const manifest = sanitizeManifest(parsed);
|
|
51980
|
+
const task_ids = Object.fromEntries(manifest.tasks.map((task3) => [
|
|
51981
|
+
task3.key,
|
|
51982
|
+
deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "task", task3.key)
|
|
51983
|
+
]));
|
|
51984
|
+
const graph = {
|
|
51985
|
+
plan_id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "plan", manifest.plan.key),
|
|
51986
|
+
task_ids,
|
|
51987
|
+
comment_ids: manifest.tasks.flatMap((task3) => (task3.comments ?? []).map((_, index) => deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "comment", task3.key, String(index)))),
|
|
51988
|
+
verification_ids: manifest.tasks.flatMap((task3) => (task3.verifications ?? []).map((_, index) => deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "verification", task3.key, String(index)))),
|
|
51989
|
+
dependency_ids: (manifest.dependencies ?? []).map((edge) => `${task_ids[edge.task]}::${task_ids[edge.depends_on]}`)
|
|
51990
|
+
};
|
|
51991
|
+
const request_digest = canonicalDigest(parsed);
|
|
51992
|
+
const effectInputs = [
|
|
51993
|
+
{
|
|
51994
|
+
topic: "todos.task-manifest.applied",
|
|
51995
|
+
payload: { operation_id: manifest.operation_id, project_id: manifest.project_id }
|
|
51996
|
+
},
|
|
51997
|
+
...manifest.effects ?? []
|
|
51998
|
+
];
|
|
51999
|
+
const outbox = effectInputs.map((effect2, index) => {
|
|
52000
|
+
const payload = { ...effect2.payload };
|
|
52001
|
+
return {
|
|
52002
|
+
id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "outbox", String(index)),
|
|
52003
|
+
topic: effect2.topic,
|
|
52004
|
+
payload,
|
|
52005
|
+
digest: canonicalDigest({ topic: effect2.topic, payload })
|
|
52006
|
+
};
|
|
52007
|
+
});
|
|
52008
|
+
const result_digest = canonicalDigest({ manifest, graph, outbox });
|
|
52009
|
+
return {
|
|
52010
|
+
manifest,
|
|
52011
|
+
request_digest,
|
|
52012
|
+
result_digest,
|
|
52013
|
+
receipt_id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, "apply", manifest.operation_id, manifest.idempotency_key, request_digest),
|
|
52014
|
+
graph,
|
|
52015
|
+
outbox,
|
|
52016
|
+
now: now4
|
|
52017
|
+
};
|
|
52018
|
+
}
|
|
52019
|
+
function sanitizeManifest(manifest) {
|
|
52020
|
+
return {
|
|
52021
|
+
...manifest,
|
|
52022
|
+
plan: {
|
|
52023
|
+
...manifest.plan,
|
|
52024
|
+
name: sanitizePreWriteText(manifest.plan.name, "task_manifest.plan.name"),
|
|
52025
|
+
...manifest.plan.description !== undefined ? { description: sanitizePreWriteText(manifest.plan.description, "task_manifest.plan.description") } : {}
|
|
52026
|
+
},
|
|
52027
|
+
tasks: manifest.tasks.map((task3) => ({
|
|
52028
|
+
...task3,
|
|
52029
|
+
title: sanitizePreWriteText(task3.title, `task_manifest.tasks.${task3.key}.title`),
|
|
52030
|
+
...task3.description !== undefined ? { description: sanitizePreWriteText(task3.description, `task_manifest.tasks.${task3.key}.description`) } : {},
|
|
52031
|
+
...task3.tags !== undefined ? { tags: sanitizePreWriteValue(task3.tags, `task_manifest.tasks.${task3.key}.tags`) } : {},
|
|
52032
|
+
...task3.metadata !== undefined ? { metadata: sanitizePreWriteValue(task3.metadata, `task_manifest.tasks.${task3.key}.metadata`) } : {},
|
|
52033
|
+
...task3.comments !== undefined ? {
|
|
52034
|
+
comments: task3.comments.map((comment2, index) => ({
|
|
52035
|
+
...comment2,
|
|
52036
|
+
content: sanitizePreWriteText(comment2.content, `task_manifest.tasks.${task3.key}.comments.${index}.content`)
|
|
52037
|
+
}))
|
|
52038
|
+
} : {},
|
|
52039
|
+
...task3.verifications !== undefined ? {
|
|
52040
|
+
verifications: task3.verifications.map((verification2, index) => ({
|
|
52041
|
+
...verification2,
|
|
52042
|
+
command: sanitizePreWriteText(verification2.command, `task_manifest.tasks.${task3.key}.verifications.${index}.command`),
|
|
52043
|
+
...verification2.output_summary !== undefined ? { output_summary: sanitizePreWriteText(verification2.output_summary, `task_manifest.tasks.${task3.key}.verifications.${index}.output_summary`) } : {},
|
|
52044
|
+
...verification2.artifact_path !== undefined ? { artifact_path: sanitizePreWriteText(verification2.artifact_path, `task_manifest.tasks.${task3.key}.verifications.${index}.artifact_path`) } : {}
|
|
52045
|
+
}))
|
|
52046
|
+
} : {}
|
|
52047
|
+
})),
|
|
52048
|
+
...manifest.effects !== undefined ? {
|
|
52049
|
+
effects: manifest.effects.map((effect2, index) => ({
|
|
52050
|
+
topic: sanitizePreWriteText(effect2.topic, `task_manifest.effects.${index}.topic`),
|
|
52051
|
+
payload: sanitizePreWriteValue(effect2.payload, `task_manifest.effects.${index}.payload`)
|
|
52052
|
+
}))
|
|
52053
|
+
} : {}
|
|
52054
|
+
};
|
|
52055
|
+
}
|
|
52056
|
+
|
|
52057
|
+
class PackageOwnedTodosTaskManifestAuthority {
|
|
52058
|
+
backend;
|
|
52059
|
+
options;
|
|
52060
|
+
tenantId;
|
|
52061
|
+
constructor(backend, options = {}) {
|
|
52062
|
+
this.backend = backend;
|
|
52063
|
+
this.options = options;
|
|
52064
|
+
this.tenantId = resolveTenantId(options.tenantId);
|
|
52065
|
+
}
|
|
52066
|
+
async capability() {
|
|
52067
|
+
return {
|
|
52068
|
+
authority: "todos",
|
|
52069
|
+
route: TODOS_TASK_MANIFEST_ROUTE,
|
|
52070
|
+
schema_version: TODOS_TASK_MANIFEST_SCHEMA_VERSION,
|
|
52071
|
+
tenant_id: this.tenantId,
|
|
52072
|
+
backend: this.backend.kind,
|
|
52073
|
+
deterministic_ids: true,
|
|
52074
|
+
immutable_receipts: true,
|
|
52075
|
+
transactional_outbox: true,
|
|
52076
|
+
idempotent_outbox_delivery: true,
|
|
52077
|
+
exact_bounded_readback: true,
|
|
52078
|
+
conditional_compensation: true,
|
|
52079
|
+
transcript_safe: false,
|
|
52080
|
+
bounds: { ...TODOS_TASK_MANIFEST_BOUNDS }
|
|
52081
|
+
};
|
|
52082
|
+
}
|
|
52083
|
+
now() {
|
|
52084
|
+
const value = this.options.now?.() ?? new Date().toISOString();
|
|
52085
|
+
if (!Number.isFinite(Date.parse(value))) {
|
|
52086
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "now() returned an invalid timestamp");
|
|
52087
|
+
}
|
|
52088
|
+
return value;
|
|
52089
|
+
}
|
|
52090
|
+
async prepareFaults() {
|
|
52091
|
+
const points = new Set;
|
|
52092
|
+
if (this.options.faultInjector) {
|
|
52093
|
+
for (const point of FAULT_POINTS) {
|
|
52094
|
+
if (await this.options.faultInjector(point) === true)
|
|
52095
|
+
points.add(point);
|
|
52096
|
+
}
|
|
52097
|
+
}
|
|
52098
|
+
return { points };
|
|
52099
|
+
}
|
|
52100
|
+
async apply(input) {
|
|
52101
|
+
const normalized = normalize(input, this.now());
|
|
52102
|
+
const faults = await this.prepareFaults();
|
|
52103
|
+
return this.bounded(await this.backend.apply(normalized, faults));
|
|
52104
|
+
}
|
|
52105
|
+
readExact(receiptId2) {
|
|
52106
|
+
if (!receiptId2 || receiptId2.length > 200) {
|
|
52107
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "receiptId must be a bounded exact identifier");
|
|
52108
|
+
}
|
|
52109
|
+
return this.backend.readExact(receiptId2).then((result) => this.bounded(result));
|
|
52110
|
+
}
|
|
52111
|
+
async lookupBinding(input) {
|
|
52112
|
+
const request = parseTodosTaskManifestBindingLookup(input);
|
|
52113
|
+
if (request.max_items !== 1) {
|
|
52114
|
+
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 });
|
|
52115
|
+
}
|
|
52116
|
+
if (request.authority !== "todos" || request.route !== TODOS_TASK_MANIFEST_ROUTE || request.schema_version !== TODOS_TASK_MANIFEST_SCHEMA_VERSION || request.tenant_id !== this.tenantId) {
|
|
52117
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAPABILITY_MISMATCH", "Task-manifest binding lookup does not match this authority identity");
|
|
52118
|
+
}
|
|
52119
|
+
return this.bounded({
|
|
52120
|
+
authority: "todos",
|
|
52121
|
+
route: TODOS_TASK_MANIFEST_ROUTE,
|
|
52122
|
+
schema_version: TODOS_TASK_MANIFEST_SCHEMA_VERSION,
|
|
52123
|
+
tenant_id: this.tenantId,
|
|
52124
|
+
...await this.backend.lookupBindingByPlanId(request.plan_id)
|
|
52125
|
+
});
|
|
52126
|
+
}
|
|
52127
|
+
markOutboxDelivered(outboxId) {
|
|
52128
|
+
if (!outboxId || outboxId.length > 200) {
|
|
52129
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "outboxId must be a bounded exact identifier");
|
|
52130
|
+
}
|
|
52131
|
+
return this.backend.markOutboxDelivered(outboxId, this.now());
|
|
52132
|
+
}
|
|
52133
|
+
async compensate(input) {
|
|
52134
|
+
const request = parseTodosTaskManifestCompensation(input);
|
|
52135
|
+
const applied = await this.backend.readExact(request.receipt_id);
|
|
52136
|
+
const requestDigest = canonicalDigest(request);
|
|
52137
|
+
const compensationReceiptId = deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, "compensate", applied.receipt.operation_id, request.idempotency_key, requestDigest);
|
|
52138
|
+
const receipt = {
|
|
52139
|
+
receipt_id: compensationReceiptId,
|
|
52140
|
+
authority: "todos",
|
|
52141
|
+
route: TODOS_TASK_MANIFEST_ROUTE,
|
|
52142
|
+
schema_version: 1,
|
|
52143
|
+
kind: "compensate",
|
|
52144
|
+
operation_id: applied.receipt.operation_id,
|
|
52145
|
+
idempotency_key: request.idempotency_key,
|
|
52146
|
+
request_digest: requestDigest,
|
|
52147
|
+
result_digest: canonicalDigest({ absent: true, apply_receipt_id: applied.receipt.receipt_id }),
|
|
52148
|
+
binding_version: request.if_binding_version + 1,
|
|
52149
|
+
apply_receipt_id: applied.receipt.receipt_id,
|
|
52150
|
+
created_at: this.now()
|
|
52151
|
+
};
|
|
52152
|
+
return this.bounded(await this.backend.compensate(request, receipt, compensationReceiptId, requestDigest, receipt.created_at));
|
|
52153
|
+
}
|
|
52154
|
+
bounded(result) {
|
|
52155
|
+
const responseBytes2 = Buffer.byteLength(canonicalJson(result), "utf8");
|
|
52156
|
+
if (responseBytes2 > TODOS_TASK_MANIFEST_BOUNDS.response_bytes) {
|
|
52157
|
+
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 });
|
|
52158
|
+
}
|
|
52159
|
+
return result;
|
|
52160
|
+
}
|
|
52161
|
+
}
|
|
52162
|
+
function createPostgresTodosTaskManifestAuthority(client, options = {}) {
|
|
52163
|
+
if (!client || typeof client.transaction !== "function") {
|
|
52164
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE", "An authoritative PostgreSQL transaction(callback) client is required");
|
|
52165
|
+
}
|
|
52166
|
+
const tenantId = resolveTenantId(options.tenantId);
|
|
52167
|
+
return new PackageOwnedTodosTaskManifestAuthority(new PostgresTodosTaskManifestBackend(client, { ...options, tenantId }), { ...options, tenantId });
|
|
52168
|
+
}
|
|
52169
|
+
var FAULT_POINTS;
|
|
52170
|
+
var init_authority2 = __esm(() => {
|
|
52171
|
+
init_canonical();
|
|
52172
|
+
init_schema2();
|
|
52173
|
+
init_sqlite2();
|
|
52174
|
+
init_postgres3();
|
|
52175
|
+
init_prewrite_secrets();
|
|
52176
|
+
init_types5();
|
|
52177
|
+
init_schema2();
|
|
52178
|
+
FAULT_POINTS = [
|
|
52179
|
+
"after_plan_write",
|
|
52180
|
+
"after_task_write",
|
|
52181
|
+
"after_dependency_write",
|
|
52182
|
+
"after_comment_write",
|
|
52183
|
+
"after_verification_write",
|
|
52184
|
+
"after_outbox_write",
|
|
52185
|
+
"after_receipt_write"
|
|
52186
|
+
];
|
|
52187
|
+
});
|
|
52188
|
+
|
|
52189
|
+
// src/task-manifest/http.ts
|
|
52190
|
+
function json2(body, status = 200) {
|
|
52191
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS2 });
|
|
52192
|
+
}
|
|
52193
|
+
function status(error) {
|
|
52194
|
+
switch (error.code) {
|
|
52195
|
+
case "TODOS_TASK_MANIFEST_INVALID_INPUT":
|
|
52196
|
+
case "TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED":
|
|
52197
|
+
case "TODOS_TASK_MANIFEST_FOREIGN_REFERENCE":
|
|
52198
|
+
return 400;
|
|
52199
|
+
case "TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND":
|
|
52200
|
+
case "TODOS_TASK_MANIFEST_BINDING_NOT_FOUND":
|
|
52201
|
+
return 404;
|
|
52202
|
+
case "TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE":
|
|
52203
|
+
return 503;
|
|
52204
|
+
default:
|
|
52205
|
+
return 409;
|
|
52206
|
+
}
|
|
52207
|
+
}
|
|
52208
|
+
async function body(request) {
|
|
52209
|
+
const declaredLength = Number(request.headers.get("content-length") ?? 0);
|
|
52210
|
+
if (Number.isFinite(declaredLength) && declaredLength > TODOS_TASK_MANIFEST_BOUNDS.request_bytes) {
|
|
52211
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", "Task-manifest HTTP body exceeds the request bound");
|
|
52212
|
+
}
|
|
52213
|
+
const text2 = await boundedText(request, TODOS_TASK_MANIFEST_BOUNDS.request_bytes, "request");
|
|
52214
|
+
try {
|
|
52215
|
+
return JSON.parse(text2);
|
|
52216
|
+
} catch {
|
|
52217
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "Invalid JSON body");
|
|
52218
|
+
}
|
|
52219
|
+
}
|
|
52220
|
+
async function boundedText(message, limit, label) {
|
|
52221
|
+
if (!message.body)
|
|
52222
|
+
return "";
|
|
52223
|
+
const reader = message.body.getReader();
|
|
52224
|
+
const decoder = new TextDecoder;
|
|
52225
|
+
let bytes = 0;
|
|
52226
|
+
let text2 = "";
|
|
52227
|
+
try {
|
|
52228
|
+
while (true) {
|
|
52229
|
+
const { done, value } = await reader.read();
|
|
52230
|
+
if (done)
|
|
52231
|
+
break;
|
|
52232
|
+
bytes += value.byteLength;
|
|
52233
|
+
if (bytes > limit) {
|
|
52234
|
+
await reader.cancel();
|
|
52235
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", `Task-manifest HTTP ${label} exceeds the byte bound`);
|
|
52236
|
+
}
|
|
52237
|
+
text2 += decoder.decode(value, { stream: true });
|
|
52238
|
+
}
|
|
52239
|
+
return text2 + decoder.decode();
|
|
52240
|
+
} finally {
|
|
52241
|
+
reader.releaseLock();
|
|
52242
|
+
}
|
|
52243
|
+
}
|
|
52244
|
+
async function handleTodosTaskManifestHttpRequest(request, url, authority, basePath = "/v1/task-manifest") {
|
|
52245
|
+
if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`))
|
|
52246
|
+
return null;
|
|
52247
|
+
const action = url.pathname.slice(basePath.length).split("/").filter(Boolean).join("/");
|
|
52248
|
+
try {
|
|
52249
|
+
if ((action === "" || action === "capability") && request.method === "GET") {
|
|
52250
|
+
return json2({ capability: await authority.capability() });
|
|
52251
|
+
}
|
|
52252
|
+
if (request.method !== "POST")
|
|
52253
|
+
return json2({ error: "method not allowed" }, 405);
|
|
52254
|
+
if (action === "apply")
|
|
52255
|
+
return json2({ result: await authority.apply(await body(request)) }, 201);
|
|
52256
|
+
if (action === "read-exact") {
|
|
52257
|
+
const input = await body(request);
|
|
52258
|
+
if (!input || typeof input.receipt_id !== "string") {
|
|
52259
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "receipt_id is required");
|
|
52260
|
+
}
|
|
52261
|
+
return json2({ result: await authority.readExact(input.receipt_id) });
|
|
52262
|
+
}
|
|
52263
|
+
if (action === "bindings/lookup") {
|
|
52264
|
+
return json2({
|
|
52265
|
+
result: await authority.lookupBinding(await body(request))
|
|
52266
|
+
});
|
|
52267
|
+
}
|
|
52268
|
+
if (action === "compensate") {
|
|
52269
|
+
return json2({ result: await authority.compensate(await body(request)) }, 201);
|
|
52270
|
+
}
|
|
52271
|
+
if (action === "outbox/delivered") {
|
|
52272
|
+
const input = await body(request);
|
|
52273
|
+
if (!input || typeof input.outbox_id !== "string") {
|
|
52274
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "outbox_id is required");
|
|
52275
|
+
}
|
|
52276
|
+
await authority.markOutboxDelivered(input.outbox_id);
|
|
52277
|
+
return json2({ delivered: true });
|
|
52278
|
+
}
|
|
52279
|
+
return json2({ error: "unknown task-manifest route" }, 404);
|
|
52280
|
+
} catch (cause) {
|
|
52281
|
+
if (cause instanceof TodosTaskManifestError) {
|
|
52282
|
+
return json2({ error: cause.message, code: cause.code, details: cause.details, authoritative: true }, status(cause));
|
|
52283
|
+
}
|
|
52284
|
+
return json2({ error: cause instanceof Error ? cause.message : "task-manifest error" }, 500);
|
|
52285
|
+
}
|
|
52286
|
+
}
|
|
52287
|
+
var JSON_HEADERS2;
|
|
52288
|
+
var init_http3 = __esm(() => {
|
|
52289
|
+
init_types5();
|
|
52290
|
+
init_schema2();
|
|
52291
|
+
JSON_HEADERS2 = { "Content-Type": "application/json" };
|
|
52292
|
+
});
|
|
52293
|
+
|
|
52294
|
+
// src/task-manifest/index.ts
|
|
52295
|
+
var init_task_manifest = __esm(() => {
|
|
52296
|
+
init_authority2();
|
|
52297
|
+
init_postgres3();
|
|
52298
|
+
init_sqlite2();
|
|
52299
|
+
init_http3();
|
|
52300
|
+
init_canonical();
|
|
52301
|
+
init_schema2();
|
|
52302
|
+
init_types5();
|
|
52303
|
+
});
|
|
52304
|
+
|
|
50914
52305
|
// src/storage/comment-redaction-backfill.ts
|
|
50915
52306
|
function assertSafeIdentifier2(value) {
|
|
50916
52307
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
@@ -50994,12 +52385,12 @@ async function backfillPostgresCommentRedaction(client, options = {}) {
|
|
|
50994
52385
|
result.remaining_candidates = result.candidates;
|
|
50995
52386
|
return result;
|
|
50996
52387
|
}
|
|
50997
|
-
const
|
|
52388
|
+
const verification2 = await backfillPostgresCommentRedaction(client, {
|
|
50998
52389
|
...options,
|
|
50999
52390
|
apply: false,
|
|
51000
52391
|
confirmation: undefined
|
|
51001
52392
|
});
|
|
51002
|
-
result.remaining_candidates =
|
|
52393
|
+
result.remaining_candidates = verification2.candidates;
|
|
51003
52394
|
return result;
|
|
51004
52395
|
}
|
|
51005
52396
|
var COMMENT_REDACTION_BACKFILL_CONFIRMATION = "REDACT_STORED_TODOS_COMMENTS";
|
|
@@ -51018,6 +52409,7 @@ __export(exports_cloud, {
|
|
|
51018
52409
|
isPostgresBackendConfigured: () => isPostgresBackendConfigured,
|
|
51019
52410
|
isCloudModeEnabled: () => isCloudModeEnabled,
|
|
51020
52411
|
getCloudVerifier: () => getCloudVerifier,
|
|
52412
|
+
getCloudTaskManifestAuthority: () => getCloudTaskManifestAuthority,
|
|
51021
52413
|
getCloudStorageAdapter: () => getCloudStorageAdapter,
|
|
51022
52414
|
getCloudProjectRegistrationAuthority: () => getCloudProjectRegistrationAuthority,
|
|
51023
52415
|
getCloudPrGroupLedger: () => getCloudPrGroupLedger,
|
|
@@ -51045,6 +52437,9 @@ function isPostgresBackendConfigured(env = process.env) {
|
|
|
51045
52437
|
function isCloudModeEnabled(env = process.env) {
|
|
51046
52438
|
return isPostgresBackendConfigured(env);
|
|
51047
52439
|
}
|
|
52440
|
+
function getCloudTenantId() {
|
|
52441
|
+
return process.env.HASNA_TODOS_TENANT_ID ?? "default";
|
|
52442
|
+
}
|
|
51048
52443
|
function getClient() {
|
|
51049
52444
|
if (cachedClient)
|
|
51050
52445
|
return cachedClient;
|
|
@@ -51074,11 +52469,20 @@ function getCloudProjectRegistrationAuthority() {
|
|
|
51074
52469
|
cachedProjectRegistrationAuthority = createPostgresTodosProjectRegistrationAuthority(getClient(), {
|
|
51075
52470
|
service: TODOS_APP_SLUG,
|
|
51076
52471
|
authorityId: TODOS_APP_SLUG,
|
|
51077
|
-
tenantId:
|
|
52472
|
+
tenantId: getCloudTenantId(),
|
|
51078
52473
|
corpusId: process.env.HASNA_TODOS_CORPUS_ID ?? `${TODOS_APP_SLUG}:postgresql`
|
|
51079
52474
|
});
|
|
51080
52475
|
return cachedProjectRegistrationAuthority;
|
|
51081
52476
|
}
|
|
52477
|
+
function getCloudTaskManifestAuthority() {
|
|
52478
|
+
if (cachedTaskManifestAuthority)
|
|
52479
|
+
return cachedTaskManifestAuthority;
|
|
52480
|
+
cachedTaskManifestAuthority = createPostgresTodosTaskManifestAuthority(getClient(), {
|
|
52481
|
+
service: TODOS_APP_SLUG,
|
|
52482
|
+
tenantId: getCloudTenantId()
|
|
52483
|
+
});
|
|
52484
|
+
return cachedTaskManifestAuthority;
|
|
52485
|
+
}
|
|
51082
52486
|
function authClient() {
|
|
51083
52487
|
const client = getClient();
|
|
51084
52488
|
return {
|
|
@@ -51130,6 +52534,9 @@ async function ensureCloudSchema() {
|
|
|
51130
52534
|
for (const sql of postgresTodosProjectRegistrationSchemaSql()) {
|
|
51131
52535
|
await client.query(sql);
|
|
51132
52536
|
}
|
|
52537
|
+
for (const sql of postgresTodosTaskManifestSchemaSql(getCloudTenantId())) {
|
|
52538
|
+
await client.query(sql);
|
|
52539
|
+
}
|
|
51133
52540
|
await getApiKeyStore().ensureSchema();
|
|
51134
52541
|
})();
|
|
51135
52542
|
return schemaEnsured;
|
|
@@ -51171,15 +52578,17 @@ async function closeCloud() {
|
|
|
51171
52578
|
cachedVerifier = null;
|
|
51172
52579
|
cachedPrGroupLedger = null;
|
|
51173
52580
|
cachedProjectRegistrationAuthority = null;
|
|
52581
|
+
cachedTaskManifestAuthority = null;
|
|
51174
52582
|
schemaEnsured = null;
|
|
51175
52583
|
}
|
|
51176
|
-
var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, schemaEnsured = null;
|
|
52584
|
+
var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, cachedTaskManifestAuthority = null, schemaEnsured = null;
|
|
51177
52585
|
var init_cloud = __esm(() => {
|
|
51178
52586
|
init_cloud_client();
|
|
51179
52587
|
init_postgres_adapter();
|
|
51180
52588
|
init_ledger();
|
|
51181
52589
|
init_postgres();
|
|
51182
52590
|
init_project_registration();
|
|
52591
|
+
init_task_manifest();
|
|
51183
52592
|
init_postgres_sync();
|
|
51184
52593
|
init_comment_redaction_backfill();
|
|
51185
52594
|
});
|
|
@@ -51393,8 +52802,8 @@ function rowToOrg(row) {
|
|
|
51393
52802
|
function createOrg(input, db) {
|
|
51394
52803
|
const d = db || getDatabase();
|
|
51395
52804
|
const id = uuid();
|
|
51396
|
-
const
|
|
51397
|
-
d.run(`INSERT INTO orgs (id, name, description, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}),
|
|
52805
|
+
const timestamp5 = now();
|
|
52806
|
+
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]);
|
|
51398
52807
|
return getOrg(id, d);
|
|
51399
52808
|
}
|
|
51400
52809
|
function getOrg(id, db) {
|
|
@@ -51538,9 +52947,9 @@ function parseBoundedLimit(value, fallback, max) {
|
|
|
51538
52947
|
return fallback;
|
|
51539
52948
|
return Math.min(parsed, max);
|
|
51540
52949
|
}
|
|
51541
|
-
function mapTaskError(e,
|
|
52950
|
+
function mapTaskError(e, json4) {
|
|
51542
52951
|
if (e instanceof VersionConflictError) {
|
|
51543
|
-
return
|
|
52952
|
+
return json4({
|
|
51544
52953
|
error: e.message,
|
|
51545
52954
|
code: VersionConflictError.code,
|
|
51546
52955
|
expected_version: e.expectedVersion,
|
|
@@ -51548,23 +52957,23 @@ function mapTaskError(e, json3) {
|
|
|
51548
52957
|
}, 409);
|
|
51549
52958
|
}
|
|
51550
52959
|
if (e instanceof TaskNotFoundError) {
|
|
51551
|
-
return
|
|
52960
|
+
return json4({ error: e.message, code: TaskNotFoundError.code }, 404);
|
|
51552
52961
|
}
|
|
51553
52962
|
if (e instanceof LockError) {
|
|
51554
|
-
return
|
|
52963
|
+
return json4({ error: e.message, code: LockError.code }, 409);
|
|
51555
52964
|
}
|
|
51556
52965
|
if (e instanceof CompletionGuardError) {
|
|
51557
|
-
return
|
|
52966
|
+
return json4({
|
|
51558
52967
|
error: e.message,
|
|
51559
52968
|
code: CompletionGuardError.code,
|
|
51560
52969
|
retry_after: e.retryAfterSeconds ?? null
|
|
51561
52970
|
}, 409);
|
|
51562
52971
|
}
|
|
51563
52972
|
if (e instanceof TaskNotStartableError) {
|
|
51564
|
-
return
|
|
52973
|
+
return json4({ error: e.message, code: TaskNotStartableError.code }, 409);
|
|
51565
52974
|
}
|
|
51566
52975
|
if (e instanceof Error && / is blocked by /.test(e.message)) {
|
|
51567
|
-
return
|
|
52976
|
+
return json4({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
|
|
51568
52977
|
}
|
|
51569
52978
|
return null;
|
|
51570
52979
|
}
|
|
@@ -51649,11 +53058,11 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
51649
53058
|
}
|
|
51650
53059
|
});
|
|
51651
53060
|
}
|
|
51652
|
-
function handleHealth(_ctx,
|
|
53061
|
+
function handleHealth(_ctx, json4) {
|
|
51653
53062
|
const stats2 = getTaskStats();
|
|
51654
53063
|
const staleCount = getStaleTasks(30).length;
|
|
51655
53064
|
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
51656
|
-
return
|
|
53065
|
+
return json4({
|
|
51657
53066
|
status: staleCount === 0 && overdueRecurring === 0 ? "ok" : "warn",
|
|
51658
53067
|
tasks: stats2.total,
|
|
51659
53068
|
stale: staleCount,
|
|
@@ -51661,18 +53070,18 @@ function handleHealth(_ctx, json3) {
|
|
|
51661
53070
|
timestamp: new Date().toISOString()
|
|
51662
53071
|
});
|
|
51663
53072
|
}
|
|
51664
|
-
function handleHeadlessBoundary(_ctx,
|
|
53073
|
+
function handleHeadlessBoundary(_ctx, json4) {
|
|
51665
53074
|
const { getHeadlessBoundaryManifest: getHeadlessBoundaryManifest2 } = (init_headless_boundaries(), __toCommonJS(exports_headless_boundaries));
|
|
51666
|
-
return
|
|
53075
|
+
return json4(getHeadlessBoundaryManifest2());
|
|
51667
53076
|
}
|
|
51668
|
-
function handleStats(_ctx,
|
|
53077
|
+
function handleStats(_ctx, json4) {
|
|
51669
53078
|
const stats2 = getTaskStats();
|
|
51670
53079
|
const byStatus = stats2.by_status;
|
|
51671
53080
|
const projects = listProjects();
|
|
51672
53081
|
const agents = listAgents();
|
|
51673
53082
|
const staleCount = getStaleTasks(30).length;
|
|
51674
53083
|
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
51675
|
-
return
|
|
53084
|
+
return json4({
|
|
51676
53085
|
total_tasks: stats2.total,
|
|
51677
53086
|
pending: byStatus["pending"] ?? 0,
|
|
51678
53087
|
in_progress: byStatus["in_progress"] ?? 0,
|
|
@@ -51695,10 +53104,10 @@ function taskStatusQueryParam(url) {
|
|
|
51695
53104
|
return { ok: false, message: result.message };
|
|
51696
53105
|
return { ok: true, value: collapseEnumValues(result.values) };
|
|
51697
53106
|
}
|
|
51698
|
-
async function handleListTasks(_req, url, _ctx,
|
|
53107
|
+
async function handleListTasks(_req, url, _ctx, json4, taskToSummary2) {
|
|
51699
53108
|
const statusParam = taskStatusQueryParam(url);
|
|
51700
53109
|
if (!statusParam.ok)
|
|
51701
|
-
return
|
|
53110
|
+
return json4({ error: statusParam.message }, 400);
|
|
51702
53111
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51703
53112
|
const sessionId = url.searchParams.get("session_id") || undefined;
|
|
51704
53113
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
@@ -51713,60 +53122,60 @@ async function handleListTasks(_req, url, _ctx, json3, taskToSummary2) {
|
|
|
51713
53122
|
limit: limitParam ? parseInt(limitParam, 10) : undefined,
|
|
51714
53123
|
offset: offsetParam ? parseInt(offsetParam, 10) : undefined
|
|
51715
53124
|
});
|
|
51716
|
-
return
|
|
53125
|
+
return json4(tasks.map((t) => taskToSummary2(t, fields)));
|
|
51717
53126
|
}
|
|
51718
|
-
async function handleCreateTask(req, ctx,
|
|
53127
|
+
async function handleCreateTask(req, ctx, json4, taskToSummary2) {
|
|
51719
53128
|
try {
|
|
51720
|
-
const
|
|
51721
|
-
if (!
|
|
51722
|
-
return
|
|
51723
|
-
const createdBy =
|
|
51724
|
-
const
|
|
51725
|
-
title:
|
|
51726
|
-
description:
|
|
51727
|
-
priority:
|
|
51728
|
-
project_id:
|
|
51729
|
-
agent_id:
|
|
53129
|
+
const body2 = await req.json();
|
|
53130
|
+
if (!body2.title)
|
|
53131
|
+
return json4({ error: "Missing 'title'" }, 400);
|
|
53132
|
+
const createdBy = body2.created_by ?? body2.agent_id ?? "dashboard";
|
|
53133
|
+
const task3 = createTask({
|
|
53134
|
+
title: body2.title,
|
|
53135
|
+
description: body2.description,
|
|
53136
|
+
priority: body2.priority,
|
|
53137
|
+
project_id: body2.project_id,
|
|
53138
|
+
agent_id: body2.agent_id ?? createdBy,
|
|
51730
53139
|
created_by: createdBy,
|
|
51731
|
-
...
|
|
53140
|
+
...body2.assigned_to ? { assigned_to: body2.assigned_to } : {}
|
|
51732
53141
|
});
|
|
51733
|
-
ctx.broadcastEvent({ type: "task", task_id:
|
|
51734
|
-
return
|
|
53142
|
+
ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "created", agent_id: task3.agent_id, project_id: task3.project_id });
|
|
53143
|
+
return json4(taskToSummary2(task3), 201);
|
|
51735
53144
|
} catch (e) {
|
|
51736
|
-
return
|
|
53145
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
|
|
51737
53146
|
}
|
|
51738
53147
|
}
|
|
51739
|
-
async function handleUpsertTask(req, ctx,
|
|
53148
|
+
async function handleUpsertTask(req, ctx, json4, taskToSummary2) {
|
|
51740
53149
|
try {
|
|
51741
|
-
const
|
|
51742
|
-
if (typeof
|
|
51743
|
-
return
|
|
53150
|
+
const body2 = await req.json();
|
|
53151
|
+
if (typeof body2["fingerprint"] !== "string" || body2["fingerprint"].trim() === "") {
|
|
53152
|
+
return json4({ error: "Missing 'fingerprint'" }, 400);
|
|
51744
53153
|
}
|
|
51745
|
-
if (typeof
|
|
51746
|
-
return
|
|
53154
|
+
if (typeof body2["title"] !== "string" || body2["title"].trim() === "") {
|
|
53155
|
+
return json4({ error: "Missing 'title'" }, 400);
|
|
51747
53156
|
}
|
|
51748
|
-
const metadata =
|
|
51749
|
-
for (const
|
|
51750
|
-
if (
|
|
51751
|
-
metadata[
|
|
53157
|
+
const metadata = body2["metadata"] && typeof body2["metadata"] === "object" && !Array.isArray(body2["metadata"]) ? { ...body2["metadata"] } : {};
|
|
53158
|
+
for (const key2 of ["expectation_id", "expectation_fingerprint", "evidence_paths", "origin_loop_id", "origin_run_id", "expected", "observed", "acceptance"]) {
|
|
53159
|
+
if (body2[key2] !== undefined)
|
|
53160
|
+
metadata[key2] = body2[key2];
|
|
51752
53161
|
}
|
|
51753
53162
|
const result = upsertTaskByFingerprint({
|
|
51754
|
-
fingerprint:
|
|
51755
|
-
title:
|
|
51756
|
-
description: typeof
|
|
51757
|
-
status:
|
|
51758
|
-
priority:
|
|
51759
|
-
project_id: typeof
|
|
51760
|
-
task_list_id: typeof
|
|
51761
|
-
assigned_to: typeof
|
|
51762
|
-
working_dir: typeof
|
|
51763
|
-
tags: Array.isArray(
|
|
53163
|
+
fingerprint: body2["fingerprint"],
|
|
53164
|
+
title: body2["title"],
|
|
53165
|
+
description: typeof body2["description"] === "string" ? body2["description"] : undefined,
|
|
53166
|
+
status: body2["status"],
|
|
53167
|
+
priority: body2["priority"],
|
|
53168
|
+
project_id: typeof body2["project_id"] === "string" ? body2["project_id"] : undefined,
|
|
53169
|
+
task_list_id: typeof body2["task_list_id"] === "string" ? body2["task_list_id"] : undefined,
|
|
53170
|
+
assigned_to: typeof body2["assigned_to"] === "string" ? body2["assigned_to"] : undefined,
|
|
53171
|
+
working_dir: typeof body2["working_dir"] === "string" ? body2["working_dir"] : undefined,
|
|
53172
|
+
tags: Array.isArray(body2["tags"]) ? body2["tags"].filter((tag) => typeof tag === "string") : undefined,
|
|
51764
53173
|
metadata
|
|
51765
53174
|
});
|
|
51766
53175
|
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 });
|
|
51767
|
-
return
|
|
53176
|
+
return json4({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
|
|
51768
53177
|
} catch (e) {
|
|
51769
|
-
return
|
|
53178
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
|
|
51770
53179
|
}
|
|
51771
53180
|
}
|
|
51772
53181
|
function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
@@ -51818,21 +53227,21 @@ function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
|
51818
53227
|
}
|
|
51819
53228
|
});
|
|
51820
53229
|
}
|
|
51821
|
-
async function handleTasksBulk(req, _ctx,
|
|
53230
|
+
async function handleTasksBulk(req, _ctx, json4) {
|
|
51822
53231
|
try {
|
|
51823
|
-
const
|
|
51824
|
-
if (!
|
|
51825
|
-
return
|
|
53232
|
+
const body2 = await req.json();
|
|
53233
|
+
if (!body2.ids?.length || !body2.action)
|
|
53234
|
+
return json4({ error: "Missing ids or action" }, 400);
|
|
51826
53235
|
const results = [];
|
|
51827
|
-
for (const id of
|
|
53236
|
+
for (const id of body2.ids) {
|
|
51828
53237
|
try {
|
|
51829
|
-
if (
|
|
53238
|
+
if (body2.action === "delete") {
|
|
51830
53239
|
deleteTask(id);
|
|
51831
53240
|
results.push({ id, success: true });
|
|
51832
|
-
} else if (
|
|
53241
|
+
} else if (body2.action === "start") {
|
|
51833
53242
|
startTask(id, "dashboard");
|
|
51834
53243
|
results.push({ id, success: true });
|
|
51835
|
-
} else if (
|
|
53244
|
+
} else if (body2.action === "complete") {
|
|
51836
53245
|
completeTask(id, "dashboard");
|
|
51837
53246
|
results.push({ id, success: true });
|
|
51838
53247
|
}
|
|
@@ -51840,84 +53249,84 @@ async function handleTasksBulk(req, _ctx, json3) {
|
|
|
51840
53249
|
results.push({ id, success: false, error: e instanceof Error ? e.message : "Failed" });
|
|
51841
53250
|
}
|
|
51842
53251
|
}
|
|
51843
|
-
return
|
|
53252
|
+
return json4({ results, succeeded: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length });
|
|
51844
53253
|
} catch (e) {
|
|
51845
|
-
return
|
|
53254
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51846
53255
|
}
|
|
51847
53256
|
}
|
|
51848
|
-
function handleTasksStatus(_req, url, _ctx,
|
|
53257
|
+
function handleTasksStatus(_req, url, _ctx, json4) {
|
|
51849
53258
|
try {
|
|
51850
53259
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51851
53260
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
51852
|
-
const
|
|
51853
|
-
return
|
|
53261
|
+
const status2 = getStatus(projectId ? { project_id: projectId } : undefined, agentId);
|
|
53262
|
+
return json4(status2);
|
|
51854
53263
|
} catch (e) {
|
|
51855
|
-
return
|
|
53264
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51856
53265
|
}
|
|
51857
53266
|
}
|
|
51858
|
-
function handleTasksNext(_req, url, _ctx,
|
|
53267
|
+
function handleTasksNext(_req, url, _ctx, json4, taskToSummary2) {
|
|
51859
53268
|
try {
|
|
51860
53269
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51861
53270
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
51862
53271
|
const fields = parseFieldsParam(url);
|
|
51863
|
-
const
|
|
51864
|
-
return
|
|
53272
|
+
const task3 = getNextTask(agentId, projectId ? { project_id: projectId } : undefined);
|
|
53273
|
+
return json4({ task: task3 ? taskToSummary2(task3, fields) : null });
|
|
51865
53274
|
} catch (e) {
|
|
51866
|
-
return
|
|
53275
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51867
53276
|
}
|
|
51868
53277
|
}
|
|
51869
|
-
function handleTasksActive(_req, url, _ctx,
|
|
53278
|
+
function handleTasksActive(_req, url, _ctx, json4) {
|
|
51870
53279
|
try {
|
|
51871
53280
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51872
53281
|
const work = getActiveWork(projectId ? { project_id: projectId } : undefined);
|
|
51873
|
-
return
|
|
53282
|
+
return json4({ active: work, count: work.length });
|
|
51874
53283
|
} catch (e) {
|
|
51875
|
-
return
|
|
53284
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51876
53285
|
}
|
|
51877
53286
|
}
|
|
51878
|
-
function handleTasksStale(_req, url, _ctx,
|
|
53287
|
+
function handleTasksStale(_req, url, _ctx, json4, taskToSummary2) {
|
|
51879
53288
|
try {
|
|
51880
53289
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51881
53290
|
const minutes2 = parseInt(url.searchParams.get("minutes") || "30", 10);
|
|
51882
53291
|
const fields = parseFieldsParam(url);
|
|
51883
53292
|
const tasks = getStaleTasks(minutes2, projectId ? { project_id: projectId } : undefined);
|
|
51884
|
-
return
|
|
53293
|
+
return json4({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length });
|
|
51885
53294
|
} catch (e) {
|
|
51886
|
-
return
|
|
53295
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51887
53296
|
}
|
|
51888
53297
|
}
|
|
51889
|
-
function handleTasksChanged(_req, url, _ctx,
|
|
53298
|
+
function handleTasksChanged(_req, url, _ctx, json4, taskToSummary2) {
|
|
51890
53299
|
try {
|
|
51891
53300
|
const since = url.searchParams.get("since");
|
|
51892
53301
|
if (!since)
|
|
51893
|
-
return
|
|
53302
|
+
return json4({ error: "since parameter required (ISO date string)" }, 400);
|
|
51894
53303
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51895
53304
|
const fields = parseFieldsParam(url);
|
|
51896
53305
|
const tasks = getTasksChangedSince(since, projectId ? { project_id: projectId } : undefined);
|
|
51897
|
-
return
|
|
53306
|
+
return json4({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length, since });
|
|
51898
53307
|
} catch (e) {
|
|
51899
|
-
return
|
|
53308
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
51900
53309
|
}
|
|
51901
53310
|
}
|
|
51902
|
-
function handleTasksContext(_req, url, _ctx,
|
|
53311
|
+
function handleTasksContext(_req, url, _ctx, json4, taskToSummary2) {
|
|
51903
53312
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
51904
53313
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
51905
53314
|
const format = url.searchParams.get("format") || "text";
|
|
51906
53315
|
const fields = parseFieldsParam(url);
|
|
51907
53316
|
const filters = projectId ? { project_id: projectId } : undefined;
|
|
51908
|
-
const
|
|
53317
|
+
const status2 = getStatus(filters, agentId);
|
|
51909
53318
|
const next = getNextTask(agentId, filters);
|
|
51910
53319
|
if (format === "json") {
|
|
51911
|
-
return
|
|
53320
|
+
return json4({ status: status2, next_task: next ? taskToSummary2(next, fields) : null });
|
|
51912
53321
|
}
|
|
51913
53322
|
const lines = [];
|
|
51914
|
-
lines.push(`Tasks: ${
|
|
51915
|
-
if (
|
|
51916
|
-
lines.push(`${
|
|
51917
|
-
if (
|
|
51918
|
-
lines.push(`${
|
|
51919
|
-
if (
|
|
51920
|
-
lines.push(`Active: ${
|
|
53323
|
+
lines.push(`Tasks: ${status2.pending} pending | ${status2.in_progress} active | ${status2.completed} done`);
|
|
53324
|
+
if (status2.stale_count > 0)
|
|
53325
|
+
lines.push(`${status2.stale_count} stale tasks stuck in-progress`);
|
|
53326
|
+
if (status2.overdue_recurring > 0)
|
|
53327
|
+
lines.push(`${status2.overdue_recurring} overdue recurring tasks`);
|
|
53328
|
+
if (status2.active_work.length > 0) {
|
|
53329
|
+
lines.push(`Active: ${status2.active_work.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ")}`);
|
|
51921
53330
|
}
|
|
51922
53331
|
if (next)
|
|
51923
53332
|
lines.push(`Next up: ${next.short_id || next.id.slice(0, 8)} [${next.priority}] ${next.title}`);
|
|
@@ -51925,18 +53334,18 @@ function handleTasksContext(_req, url, _ctx, json3, taskToSummary2) {
|
|
|
51925
53334
|
`);
|
|
51926
53335
|
return new Response(text2, { headers: { "Content-Type": "text/plain" } });
|
|
51927
53336
|
}
|
|
51928
|
-
function handleTaskAttachments(id, _ctx,
|
|
51929
|
-
const
|
|
51930
|
-
if (!
|
|
51931
|
-
return
|
|
51932
|
-
const evidence =
|
|
53337
|
+
function handleTaskAttachments(id, _ctx, json4) {
|
|
53338
|
+
const task3 = getTask(id);
|
|
53339
|
+
if (!task3)
|
|
53340
|
+
return json4({ error: "Task not found" }, 404);
|
|
53341
|
+
const evidence = task3.metadata?._evidence || {};
|
|
51933
53342
|
const attachmentIds = evidence.attachments || [];
|
|
51934
|
-
return
|
|
53343
|
+
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 });
|
|
51935
53344
|
}
|
|
51936
|
-
async function handleTaskProgress(id, req, method, _ctx,
|
|
51937
|
-
const
|
|
51938
|
-
if (!
|
|
51939
|
-
return
|
|
53345
|
+
async function handleTaskProgress(id, req, method, _ctx, json4, url) {
|
|
53346
|
+
const task3 = getTask(id);
|
|
53347
|
+
if (!task3)
|
|
53348
|
+
return json4({ error: "Task not found" }, 404);
|
|
51940
53349
|
if (method === "GET") {
|
|
51941
53350
|
const all = listComments(id);
|
|
51942
53351
|
const progress = all.filter((c) => c.type === "progress");
|
|
@@ -51944,7 +53353,7 @@ async function handleTaskProgress(id, req, method, _ctx, json3, url) {
|
|
|
51944
53353
|
const format = url?.searchParams.get("format") || "compact";
|
|
51945
53354
|
const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 200);
|
|
51946
53355
|
const progressEntries = format === "full" ? progress : progress.slice(-limit);
|
|
51947
|
-
return
|
|
53356
|
+
return json4({
|
|
51948
53357
|
task_id: id,
|
|
51949
53358
|
progress_entries: progressEntries,
|
|
51950
53359
|
latest,
|
|
@@ -51959,119 +53368,119 @@ async function handleTaskProgress(id, req, method, _ctx, json3, url) {
|
|
|
51959
53368
|
}
|
|
51960
53369
|
if (method === "POST") {
|
|
51961
53370
|
try {
|
|
51962
|
-
const
|
|
51963
|
-
if (!
|
|
51964
|
-
return
|
|
51965
|
-
const
|
|
51966
|
-
return
|
|
53371
|
+
const body2 = await req.json();
|
|
53372
|
+
if (!body2.message)
|
|
53373
|
+
return json4({ error: "message required" }, 400);
|
|
53374
|
+
const comment2 = logProgress(id, body2.message, body2.pct_complete, body2.agent_id);
|
|
53375
|
+
return json4(comment2, 201);
|
|
51967
53376
|
} catch (e) {
|
|
51968
|
-
return
|
|
53377
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to log progress" }, 500);
|
|
51969
53378
|
}
|
|
51970
53379
|
}
|
|
51971
53380
|
return null;
|
|
51972
53381
|
}
|
|
51973
|
-
function handleGetTask(id, _ctx,
|
|
51974
|
-
const
|
|
51975
|
-
if (!
|
|
51976
|
-
return
|
|
51977
|
-
return
|
|
53382
|
+
function handleGetTask(id, _ctx, json4, taskToSummary2, url) {
|
|
53383
|
+
const task3 = getTask(id);
|
|
53384
|
+
if (!task3)
|
|
53385
|
+
return json4({ error: "Task not found" }, 404);
|
|
53386
|
+
return json4(taskToSummary2(task3, url ? parseFieldsParam(url) : undefined));
|
|
51978
53387
|
}
|
|
51979
|
-
async function handlePatchTask(id, req, _ctx,
|
|
53388
|
+
async function handlePatchTask(id, req, _ctx, json4, taskToSummary2) {
|
|
51980
53389
|
try {
|
|
51981
|
-
const
|
|
51982
|
-
const
|
|
51983
|
-
if (!
|
|
51984
|
-
return
|
|
53390
|
+
const body2 = await req.json();
|
|
53391
|
+
const task3 = getTask(id);
|
|
53392
|
+
if (!task3)
|
|
53393
|
+
return json4({ error: "Task not found" }, 404);
|
|
51985
53394
|
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"]);
|
|
51986
53395
|
const safeBody = {};
|
|
51987
|
-
for (const [
|
|
51988
|
-
if (ALLOWED.has(
|
|
51989
|
-
safeBody[
|
|
53396
|
+
for (const [key2, value] of Object.entries(body2)) {
|
|
53397
|
+
if (ALLOWED.has(key2))
|
|
53398
|
+
safeBody[key2] = value;
|
|
51990
53399
|
}
|
|
51991
|
-
const clientVersion = typeof
|
|
53400
|
+
const clientVersion = typeof body2["version"] === "number" ? body2["version"] : task3.version;
|
|
51992
53401
|
const updated = updateTask(id, {
|
|
51993
53402
|
...safeBody,
|
|
51994
53403
|
version: clientVersion
|
|
51995
53404
|
});
|
|
51996
|
-
return
|
|
53405
|
+
return json4(taskToSummary2(updated));
|
|
51997
53406
|
} catch (e) {
|
|
51998
|
-
const mapped = mapTaskError(e,
|
|
53407
|
+
const mapped = mapTaskError(e, json4);
|
|
51999
53408
|
if (mapped)
|
|
52000
53409
|
return mapped;
|
|
52001
|
-
return
|
|
53410
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to update task" }, 500);
|
|
52002
53411
|
}
|
|
52003
53412
|
}
|
|
52004
|
-
function handleDeleteTask(id, _ctx,
|
|
53413
|
+
function handleDeleteTask(id, _ctx, json4) {
|
|
52005
53414
|
const deleted = deleteTask(id);
|
|
52006
53415
|
if (!deleted)
|
|
52007
|
-
return
|
|
52008
|
-
return
|
|
53416
|
+
return json4({ error: "Task not found" }, 404);
|
|
53417
|
+
return json4({ success: true });
|
|
52009
53418
|
}
|
|
52010
|
-
function handleStartTask(id, ctx,
|
|
53419
|
+
function handleStartTask(id, ctx, json4, taskToSummary2) {
|
|
52011
53420
|
try {
|
|
52012
|
-
const
|
|
52013
|
-
ctx.broadcastEvent({ type: "task", task_id:
|
|
52014
|
-
return
|
|
53421
|
+
const task3 = startTask(id, "dashboard");
|
|
53422
|
+
ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "started", agent_id: "dashboard", project_id: task3.project_id });
|
|
53423
|
+
return json4(taskToSummary2(task3));
|
|
52015
53424
|
} catch (e) {
|
|
52016
|
-
const mapped = mapTaskError(e,
|
|
53425
|
+
const mapped = mapTaskError(e, json4);
|
|
52017
53426
|
if (mapped)
|
|
52018
53427
|
return mapped;
|
|
52019
|
-
return
|
|
53428
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to start task" }, 500);
|
|
52020
53429
|
}
|
|
52021
53430
|
}
|
|
52022
|
-
async function handleFailTask(id, req, ctx,
|
|
53431
|
+
async function handleFailTask(id, req, ctx, json4, taskToSummary2) {
|
|
52023
53432
|
try {
|
|
52024
|
-
const
|
|
52025
|
-
const result = failTask(id,
|
|
52026
|
-
ctx.broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id:
|
|
52027
|
-
return
|
|
53433
|
+
const body2 = await req.json().catch(() => ({}));
|
|
53434
|
+
const result = failTask(id, body2.agent_id, body2.reason, { retry: body2.retry, error_code: body2.error_code });
|
|
53435
|
+
ctx.broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body2.agent_id || null, project_id: result.task.project_id });
|
|
53436
|
+
return json4({ task: taskToSummary2(result.task), retry_task: result.retryTask ? taskToSummary2(result.retryTask) : null });
|
|
52028
53437
|
} catch (e) {
|
|
52029
|
-
return
|
|
53438
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500);
|
|
52030
53439
|
}
|
|
52031
53440
|
}
|
|
52032
|
-
function handleCompleteTask(id, ctx,
|
|
53441
|
+
function handleCompleteTask(id, ctx, json4, taskToSummary2) {
|
|
52033
53442
|
try {
|
|
52034
|
-
const
|
|
52035
|
-
ctx.broadcastEvent({ type: "task", task_id:
|
|
52036
|
-
return
|
|
53443
|
+
const task3 = completeTask(id, "dashboard");
|
|
53444
|
+
ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "completed", agent_id: "dashboard", project_id: task3.project_id });
|
|
53445
|
+
return json4(taskToSummary2(task3));
|
|
52037
53446
|
} catch (e) {
|
|
52038
|
-
const mapped = mapTaskError(e,
|
|
53447
|
+
const mapped = mapTaskError(e, json4);
|
|
52039
53448
|
if (mapped)
|
|
52040
53449
|
return mapped;
|
|
52041
|
-
return
|
|
53450
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500);
|
|
52042
53451
|
}
|
|
52043
53452
|
}
|
|
52044
|
-
function handleListProjects(url, _ctx,
|
|
53453
|
+
function handleListProjects(url, _ctx, json4) {
|
|
52045
53454
|
const pFieldsParam = url.searchParams.get("fields");
|
|
52046
53455
|
const pFields = pFieldsParam ? pFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
52047
53456
|
const projects = listProjects();
|
|
52048
|
-
return
|
|
53457
|
+
return json4(pFields ? projects.map((p) => Object.fromEntries(pFields.map((f) => [f, p[f] ?? null]))) : projects);
|
|
52049
53458
|
}
|
|
52050
|
-
async function handleCreateProject(req, _ctx,
|
|
53459
|
+
async function handleCreateProject(req, _ctx, json4) {
|
|
52051
53460
|
try {
|
|
52052
|
-
const
|
|
52053
|
-
if (!
|
|
52054
|
-
return
|
|
52055
|
-
const project = createProject({ name:
|
|
52056
|
-
return
|
|
53461
|
+
const body2 = await req.json();
|
|
53462
|
+
if (!body2.name || !body2.path)
|
|
53463
|
+
return json4({ error: "Missing name or path" }, 400);
|
|
53464
|
+
const project = createProject({ name: body2.name, path: body2.path, description: body2.description });
|
|
53465
|
+
return json4(project, 201);
|
|
52057
53466
|
} catch (e) {
|
|
52058
|
-
return
|
|
53467
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to create project" }, 500);
|
|
52059
53468
|
}
|
|
52060
53469
|
}
|
|
52061
|
-
function handleDeleteProject(id, _ctx,
|
|
53470
|
+
function handleDeleteProject(id, _ctx, json4) {
|
|
52062
53471
|
const deleted = deleteProject(id);
|
|
52063
53472
|
if (!deleted)
|
|
52064
|
-
return
|
|
52065
|
-
return
|
|
53473
|
+
return json4({ error: "Project not found" }, 404);
|
|
53474
|
+
return json4({ success: true });
|
|
52066
53475
|
}
|
|
52067
|
-
async function handleAgentMe(_req, url, _ctx,
|
|
53476
|
+
async function handleAgentMe(_req, url, _ctx, json4, taskToSummary2) {
|
|
52068
53477
|
try {
|
|
52069
53478
|
const name = url.searchParams.get("name");
|
|
52070
53479
|
if (!name)
|
|
52071
|
-
return
|
|
53480
|
+
return json4({ error: "Missing name param" }, 400);
|
|
52072
53481
|
const agentResult = registerAgent({ name });
|
|
52073
53482
|
if (isAgentConflict(agentResult))
|
|
52074
|
-
return
|
|
53483
|
+
return json4({ error: agentResult.message, conflict: true }, 409);
|
|
52075
53484
|
const agent = agentResult;
|
|
52076
53485
|
const tasks = listTasks({ assigned_to: agent.name });
|
|
52077
53486
|
const agentIdTasks = listTasks({ agent_id: agent.id });
|
|
@@ -52079,7 +53488,7 @@ async function handleAgentMe(_req, url, _ctx, json3, taskToSummary2) {
|
|
|
52079
53488
|
const pending = allTasks.filter((t) => t.status === "pending");
|
|
52080
53489
|
const inProgress = allTasks.filter((t) => t.status === "in_progress");
|
|
52081
53490
|
const completed = allTasks.filter((t) => t.status === "completed");
|
|
52082
|
-
return
|
|
53491
|
+
return json4({
|
|
52083
53492
|
agent,
|
|
52084
53493
|
pending_tasks: pending.map((t) => taskToSummary2(t)),
|
|
52085
53494
|
in_progress_tasks: inProgress.map((t) => taskToSummary2(t)),
|
|
@@ -52093,132 +53502,132 @@ async function handleAgentMe(_req, url, _ctx, json3, taskToSummary2) {
|
|
|
52093
53502
|
});
|
|
52094
53503
|
} catch (e) {
|
|
52095
53504
|
if (e instanceof InvalidAgentNameError)
|
|
52096
|
-
return
|
|
52097
|
-
return
|
|
53505
|
+
return json4({ error: e.message, suggestions: e.suggestions }, 400);
|
|
53506
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to get agent profile" }, 500);
|
|
52098
53507
|
}
|
|
52099
53508
|
}
|
|
52100
|
-
function handleAgentQueue(agentId, _ctx,
|
|
53509
|
+
function handleAgentQueue(agentId, _ctx, json4, taskToSummary2) {
|
|
52101
53510
|
const aliasSet = assignedToAliasSet(getDatabase(), agentId);
|
|
52102
53511
|
const pending = listTasks({ status: "pending" });
|
|
52103
53512
|
const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
|
|
52104
53513
|
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
52105
53514
|
queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
|
52106
|
-
return
|
|
53515
|
+
return json4(queue.map((t) => taskToSummary2(t)));
|
|
52107
53516
|
}
|
|
52108
|
-
async function handleClaimTask(req, _ctx,
|
|
53517
|
+
async function handleClaimTask(req, _ctx, json4, taskToSummary2) {
|
|
52109
53518
|
try {
|
|
52110
|
-
const
|
|
52111
|
-
const agentId =
|
|
52112
|
-
const
|
|
52113
|
-
return
|
|
53519
|
+
const body2 = await req.json();
|
|
53520
|
+
const agentId = body2.agent_id || "anonymous";
|
|
53521
|
+
const task3 = claimNextTask(agentId, body2.project_id ? { project_id: body2.project_id } : undefined);
|
|
53522
|
+
return json4({ task: task3 ? taskToSummary2(task3) : null });
|
|
52114
53523
|
} catch (e) {
|
|
52115
|
-
return
|
|
53524
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to claim" }, 500);
|
|
52116
53525
|
}
|
|
52117
53526
|
}
|
|
52118
|
-
function handleListOrgs(_ctx,
|
|
52119
|
-
return
|
|
53527
|
+
function handleListOrgs(_ctx, json4) {
|
|
53528
|
+
return json4(listOrgs());
|
|
52120
53529
|
}
|
|
52121
|
-
async function handleCreateOrg(req, _ctx,
|
|
53530
|
+
async function handleCreateOrg(req, _ctx, json4) {
|
|
52122
53531
|
try {
|
|
52123
|
-
const
|
|
52124
|
-
if (!
|
|
52125
|
-
return
|
|
52126
|
-
return
|
|
53532
|
+
const body2 = await req.json();
|
|
53533
|
+
if (!body2.name)
|
|
53534
|
+
return json4({ error: "Missing name" }, 400);
|
|
53535
|
+
return json4(createOrg(body2), 201);
|
|
52127
53536
|
} catch (e) {
|
|
52128
|
-
return
|
|
53537
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52129
53538
|
}
|
|
52130
53539
|
}
|
|
52131
|
-
async function handleUpdateOrg(id, req, _ctx,
|
|
53540
|
+
async function handleUpdateOrg(id, req, _ctx, json4) {
|
|
52132
53541
|
try {
|
|
52133
|
-
const
|
|
52134
|
-
return
|
|
53542
|
+
const body2 = await req.json();
|
|
53543
|
+
return json4(updateOrg(id, body2));
|
|
52135
53544
|
} catch (e) {
|
|
52136
|
-
return
|
|
53545
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52137
53546
|
}
|
|
52138
53547
|
}
|
|
52139
|
-
function handleDeleteOrg(id, _ctx,
|
|
53548
|
+
function handleDeleteOrg(id, _ctx, json4) {
|
|
52140
53549
|
const deleted = deleteOrg(id);
|
|
52141
|
-
return
|
|
53550
|
+
return json4(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
52142
53551
|
}
|
|
52143
|
-
function handleOrgChart(_ctx,
|
|
52144
|
-
return
|
|
53552
|
+
function handleOrgChart(_ctx, json4) {
|
|
53553
|
+
return json4(getOrgChart());
|
|
52145
53554
|
}
|
|
52146
|
-
function handleAgentTeam(agentId, _ctx,
|
|
52147
|
-
return
|
|
53555
|
+
function handleAgentTeam(agentId, _ctx, json4) {
|
|
53556
|
+
return json4(getDirectReports(decodeURIComponent(agentId)));
|
|
52148
53557
|
}
|
|
52149
|
-
function handleListAgents(url, _ctx,
|
|
53558
|
+
function handleListAgents(url, _ctx, json4) {
|
|
52150
53559
|
const aFieldsParam = url.searchParams.get("fields");
|
|
52151
53560
|
const aFields = aFieldsParam ? aFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
52152
53561
|
const agents = listAgents();
|
|
52153
|
-
return
|
|
53562
|
+
return json4(aFields ? agents.map((a) => Object.fromEntries(aFields.map((f) => [f, a[f] ?? null]))) : agents);
|
|
52154
53563
|
}
|
|
52155
|
-
async function handleRegisterAgent(req, _ctx,
|
|
53564
|
+
async function handleRegisterAgent(req, _ctx, json4) {
|
|
52156
53565
|
try {
|
|
52157
|
-
const
|
|
52158
|
-
if (!
|
|
52159
|
-
return
|
|
52160
|
-
const result = registerAgent({ name:
|
|
53566
|
+
const body2 = await req.json();
|
|
53567
|
+
if (!body2.name)
|
|
53568
|
+
return json4({ error: "Missing name" }, 400);
|
|
53569
|
+
const result = registerAgent({ name: body2.name, description: body2.description, session_id: body2.session_id, working_dir: body2.working_dir });
|
|
52161
53570
|
if (isAgentConflict(result))
|
|
52162
|
-
return
|
|
52163
|
-
return
|
|
53571
|
+
return json4({ error: result.message, conflict: true }, 409);
|
|
53572
|
+
return json4(result, 201);
|
|
52164
53573
|
} catch (e) {
|
|
52165
53574
|
if (e instanceof InvalidAgentNameError)
|
|
52166
|
-
return
|
|
52167
|
-
return
|
|
53575
|
+
return json4({ error: e.message, suggestions: e.suggestions }, 400);
|
|
53576
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500);
|
|
52168
53577
|
}
|
|
52169
53578
|
}
|
|
52170
|
-
async function handleUpdateAgent(id, req, _ctx,
|
|
53579
|
+
async function handleUpdateAgent(id, req, _ctx, json4) {
|
|
52171
53580
|
try {
|
|
52172
|
-
const
|
|
52173
|
-
const agent = updateAgent(id,
|
|
52174
|
-
return
|
|
53581
|
+
const body2 = await req.json();
|
|
53582
|
+
const agent = updateAgent(id, body2);
|
|
53583
|
+
return json4(agent);
|
|
52175
53584
|
} catch (e) {
|
|
52176
53585
|
if (e instanceof InvalidAgentNameError)
|
|
52177
|
-
return
|
|
52178
|
-
return
|
|
53586
|
+
return json4({ error: e.message, suggestions: e.suggestions }, 400);
|
|
53587
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to update agent" }, 500);
|
|
52179
53588
|
}
|
|
52180
53589
|
}
|
|
52181
|
-
function handleDeleteAgent(id, _ctx,
|
|
53590
|
+
function handleDeleteAgent(id, _ctx, json4) {
|
|
52182
53591
|
const deleted = deleteAgent(id);
|
|
52183
53592
|
if (!deleted)
|
|
52184
|
-
return
|
|
52185
|
-
return
|
|
53593
|
+
return json4({ error: "Agent not found" }, 404);
|
|
53594
|
+
return json4({ success: true });
|
|
52186
53595
|
}
|
|
52187
|
-
async function handleBulkDeleteAgents(req, _ctx,
|
|
53596
|
+
async function handleBulkDeleteAgents(req, _ctx, json4) {
|
|
52188
53597
|
try {
|
|
52189
|
-
const
|
|
52190
|
-
if (!
|
|
52191
|
-
return
|
|
53598
|
+
const body2 = await req.json();
|
|
53599
|
+
if (!body2.ids?.length || body2.action !== "delete")
|
|
53600
|
+
return json4({ error: "Missing ids or invalid action" }, 400);
|
|
52192
53601
|
let succeeded = 0;
|
|
52193
|
-
for (const id of
|
|
53602
|
+
for (const id of body2.ids) {
|
|
52194
53603
|
if (deleteAgent(id))
|
|
52195
53604
|
succeeded++;
|
|
52196
53605
|
}
|
|
52197
|
-
return
|
|
53606
|
+
return json4({ succeeded, failed: body2.ids.length - succeeded });
|
|
52198
53607
|
} catch (e) {
|
|
52199
|
-
return
|
|
53608
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52200
53609
|
}
|
|
52201
53610
|
}
|
|
52202
|
-
async function handleBulkDeleteProjects(req, _ctx,
|
|
53611
|
+
async function handleBulkDeleteProjects(req, _ctx, json4) {
|
|
52203
53612
|
try {
|
|
52204
|
-
const
|
|
52205
|
-
if (!
|
|
52206
|
-
return
|
|
53613
|
+
const body2 = await req.json();
|
|
53614
|
+
if (!body2.ids?.length || body2.action !== "delete")
|
|
53615
|
+
return json4({ error: "Missing ids or invalid action" }, 400);
|
|
52207
53616
|
let succeeded = 0;
|
|
52208
|
-
for (const id of
|
|
53617
|
+
for (const id of body2.ids) {
|
|
52209
53618
|
if (deleteProject(id))
|
|
52210
53619
|
succeeded++;
|
|
52211
53620
|
}
|
|
52212
|
-
return
|
|
53621
|
+
return json4({ succeeded, failed: body2.ids.length - succeeded });
|
|
52213
53622
|
} catch (e) {
|
|
52214
|
-
return
|
|
53623
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52215
53624
|
}
|
|
52216
53625
|
}
|
|
52217
|
-
function handleDoctor(_ctx,
|
|
53626
|
+
function handleDoctor(_ctx, json4) {
|
|
52218
53627
|
const { runTodosDoctor: runTodosDoctor2 } = (init_doctor(), __toCommonJS(exports_doctor));
|
|
52219
|
-
return
|
|
53628
|
+
return json4(runTodosDoctor2({ apply: false }));
|
|
52220
53629
|
}
|
|
52221
|
-
function handleReport(_req, url, _ctx,
|
|
53630
|
+
function handleReport(_req, url, _ctx, json4) {
|
|
52222
53631
|
const days = parseInt(url.searchParams.get("days") || "7", 10);
|
|
52223
53632
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
52224
53633
|
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
@@ -52234,114 +53643,114 @@ function handleReport(_req, url, _ctx, json3) {
|
|
|
52234
53643
|
byDay[day] = (byDay[day] || 0) + 1;
|
|
52235
53644
|
}
|
|
52236
53645
|
const completionRate = changed.length > 0 ? Math.round(completed.length / changed.length * 100) : 0;
|
|
52237
|
-
return
|
|
53646
|
+
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 });
|
|
52238
53647
|
}
|
|
52239
|
-
function handleActivity(_req, url, _ctx,
|
|
53648
|
+
function handleActivity(_req, url, _ctx, json4) {
|
|
52240
53649
|
const limit = parseInt(url.searchParams.get("limit") || "50", 10);
|
|
52241
|
-
return
|
|
53650
|
+
return json4(getRecentActivity(limit));
|
|
52242
53651
|
}
|
|
52243
|
-
function handleTaskHistory(id, _ctx,
|
|
53652
|
+
function handleTaskHistory(id, _ctx, json4, url) {
|
|
52244
53653
|
const history = getTaskHistory(id);
|
|
52245
53654
|
const format = url?.searchParams.get("format") || "compact";
|
|
52246
53655
|
const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 500);
|
|
52247
|
-
return
|
|
53656
|
+
return json4(format === "full" ? history : history.slice(0, limit));
|
|
52248
53657
|
}
|
|
52249
|
-
function handleListWebhooks(_ctx,
|
|
52250
|
-
return
|
|
53658
|
+
function handleListWebhooks(_ctx, json4) {
|
|
53659
|
+
return json4(listWebhooks());
|
|
52251
53660
|
}
|
|
52252
|
-
async function handleCreateWebhook(req, _ctx,
|
|
53661
|
+
async function handleCreateWebhook(req, _ctx, json4) {
|
|
52253
53662
|
try {
|
|
52254
|
-
const
|
|
52255
|
-
if (!
|
|
52256
|
-
return
|
|
52257
|
-
return
|
|
53663
|
+
const body2 = await req.json();
|
|
53664
|
+
if (!body2.url)
|
|
53665
|
+
return json4({ error: "Missing url" }, 400);
|
|
53666
|
+
return json4(createWebhook(body2), 201);
|
|
52258
53667
|
} catch (e) {
|
|
52259
|
-
return
|
|
53668
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52260
53669
|
}
|
|
52261
53670
|
}
|
|
52262
|
-
function handleDeleteWebhook(id, _ctx,
|
|
53671
|
+
function handleDeleteWebhook(id, _ctx, json4) {
|
|
52263
53672
|
const deleted = deleteWebhook(id);
|
|
52264
|
-
return
|
|
53673
|
+
return json4(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
52265
53674
|
}
|
|
52266
|
-
function handleListTemplates(_ctx,
|
|
52267
|
-
return
|
|
53675
|
+
function handleListTemplates(_ctx, json4) {
|
|
53676
|
+
return json4(listTemplates());
|
|
52268
53677
|
}
|
|
52269
|
-
async function handleCreateTemplate(req, _ctx,
|
|
53678
|
+
async function handleCreateTemplate(req, _ctx, json4) {
|
|
52270
53679
|
try {
|
|
52271
|
-
const
|
|
52272
|
-
if (!
|
|
52273
|
-
return
|
|
52274
|
-
return
|
|
53680
|
+
const body2 = await req.json();
|
|
53681
|
+
if (!body2.name || !body2.title_pattern)
|
|
53682
|
+
return json4({ error: "Missing name or title_pattern" }, 400);
|
|
53683
|
+
return json4(createTemplate(body2), 201);
|
|
52275
53684
|
} catch (e) {
|
|
52276
|
-
return
|
|
53685
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52277
53686
|
}
|
|
52278
53687
|
}
|
|
52279
|
-
function handleDeleteTemplate(id, _ctx,
|
|
53688
|
+
function handleDeleteTemplate(id, _ctx, json4) {
|
|
52280
53689
|
const deleted = deleteTemplate(id);
|
|
52281
|
-
return
|
|
53690
|
+
return json4(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
52282
53691
|
}
|
|
52283
|
-
function handleListPlans(url, _ctx,
|
|
53692
|
+
function handleListPlans(url, _ctx, json4) {
|
|
52284
53693
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
52285
53694
|
const plans = listPlans(projectId);
|
|
52286
|
-
return
|
|
53695
|
+
return json4(plans);
|
|
52287
53696
|
}
|
|
52288
|
-
async function handleCreatePlan(req, _ctx,
|
|
53697
|
+
async function handleCreatePlan(req, _ctx, json4) {
|
|
52289
53698
|
try {
|
|
52290
|
-
const
|
|
52291
|
-
if (!
|
|
52292
|
-
return
|
|
53699
|
+
const body2 = await req.json();
|
|
53700
|
+
if (!body2.name)
|
|
53701
|
+
return json4({ error: "Missing 'name'" }, 400);
|
|
52293
53702
|
const plan = createPlan({
|
|
52294
|
-
name:
|
|
52295
|
-
slug:
|
|
52296
|
-
description:
|
|
52297
|
-
project_id:
|
|
52298
|
-
task_list_id:
|
|
52299
|
-
agent_id:
|
|
52300
|
-
status:
|
|
52301
|
-
});
|
|
52302
|
-
return
|
|
53703
|
+
name: body2.name,
|
|
53704
|
+
slug: body2.slug,
|
|
53705
|
+
description: body2.description,
|
|
53706
|
+
project_id: body2.project_id,
|
|
53707
|
+
task_list_id: body2.task_list_id,
|
|
53708
|
+
agent_id: body2.agent_id,
|
|
53709
|
+
status: body2.status
|
|
53710
|
+
});
|
|
53711
|
+
return json4(plan, 201);
|
|
52303
53712
|
} catch (e) {
|
|
52304
|
-
return
|
|
53713
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to create plan" }, 500);
|
|
52305
53714
|
}
|
|
52306
53715
|
}
|
|
52307
|
-
async function handleBulkDeletePlans(req, _ctx,
|
|
53716
|
+
async function handleBulkDeletePlans(req, _ctx, json4) {
|
|
52308
53717
|
try {
|
|
52309
|
-
const
|
|
52310
|
-
if (!
|
|
52311
|
-
return
|
|
53718
|
+
const body2 = await req.json();
|
|
53719
|
+
if (!body2.ids?.length || body2.action !== "delete")
|
|
53720
|
+
return json4({ error: "Missing ids or invalid action" }, 400);
|
|
52312
53721
|
let succeeded = 0;
|
|
52313
|
-
for (const id of
|
|
53722
|
+
for (const id of body2.ids) {
|
|
52314
53723
|
if (deletePlan(id))
|
|
52315
53724
|
succeeded++;
|
|
52316
53725
|
}
|
|
52317
|
-
return
|
|
53726
|
+
return json4({ succeeded, failed: body2.ids.length - succeeded });
|
|
52318
53727
|
} catch (e) {
|
|
52319
|
-
return
|
|
53728
|
+
return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
52320
53729
|
}
|
|
52321
53730
|
}
|
|
52322
|
-
function handleGetPlan(id, _ctx,
|
|
53731
|
+
function handleGetPlan(id, _ctx, json4, taskToSummary2) {
|
|
52323
53732
|
const plan = getPlan(id);
|
|
52324
53733
|
if (!plan)
|
|
52325
|
-
return
|
|
53734
|
+
return json4({ error: "Plan not found" }, 404);
|
|
52326
53735
|
const tasks = listTasks({ plan_id: id });
|
|
52327
|
-
return
|
|
53736
|
+
return json4({ ...plan, tasks: tasks.map((t) => taskToSummary2(t)) });
|
|
52328
53737
|
}
|
|
52329
|
-
async function handleUpdatePlan(id, req, _ctx,
|
|
53738
|
+
async function handleUpdatePlan(id, req, _ctx, json4) {
|
|
52330
53739
|
try {
|
|
52331
|
-
const
|
|
52332
|
-
const plan = updatePlan(id,
|
|
52333
|
-
return
|
|
53740
|
+
const body2 = await req.json();
|
|
53741
|
+
const plan = updatePlan(id, body2);
|
|
53742
|
+
return json4(plan);
|
|
52334
53743
|
} catch (e) {
|
|
52335
|
-
return
|
|
53744
|
+
return json4({ error: e instanceof Error ? e.message : "Failed to update plan" }, 500);
|
|
52336
53745
|
}
|
|
52337
53746
|
}
|
|
52338
|
-
function handleDeletePlan(id, _ctx,
|
|
53747
|
+
function handleDeletePlan(id, _ctx, json4) {
|
|
52339
53748
|
const deleted = deletePlan(id);
|
|
52340
53749
|
if (!deleted)
|
|
52341
|
-
return
|
|
52342
|
-
return
|
|
53750
|
+
return json4({ error: "Plan not found" }, 404);
|
|
53751
|
+
return json4({ success: true });
|
|
52343
53752
|
}
|
|
52344
|
-
function handleStaticFiles(path, method, ctx,
|
|
53753
|
+
function handleStaticFiles(path, method, ctx, json4, serveStaticFile2) {
|
|
52345
53754
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
52346
53755
|
return null;
|
|
52347
53756
|
if (path !== "/") {
|
|
@@ -52349,7 +53758,7 @@ function handleStaticFiles(path, method, ctx, json3, serveStaticFile2) {
|
|
|
52349
53758
|
const resolvedFile = resolve16(filePath);
|
|
52350
53759
|
const resolvedBase = resolve16(ctx.dashboardDir);
|
|
52351
53760
|
if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
|
|
52352
|
-
return
|
|
53761
|
+
return json4({ error: "Forbidden" }, 403);
|
|
52353
53762
|
}
|
|
52354
53763
|
const res2 = serveStaticFile2(filePath);
|
|
52355
53764
|
if (res2)
|
|
@@ -52398,6 +53807,9 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
52398
53807
|
schemas: {
|
|
52399
53808
|
Task: taskSchema,
|
|
52400
53809
|
Project: projectSchema,
|
|
53810
|
+
TaskManifestBindingLookupRequest: taskManifestBindingLookupRequestSchema,
|
|
53811
|
+
TaskManifestBindingLookupResult: taskManifestBindingLookupResultSchema,
|
|
53812
|
+
TaskManifestBindingLookupResponse: taskManifestBindingLookupResponseSchema,
|
|
52401
53813
|
TaskList: taskListSchema,
|
|
52402
53814
|
ProjectTaskListEnsureReceipt: projectTaskListEnsureReceiptSchema,
|
|
52403
53815
|
ProjectTaskListEnsureResult: projectTaskListEnsureResultSchema,
|
|
@@ -53373,6 +54785,32 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
53373
54785
|
},
|
|
53374
54786
|
security: [{ apiKey: [] }],
|
|
53375
54787
|
paths: {
|
|
54788
|
+
"/v1/task-manifest/bindings/lookup": {
|
|
54789
|
+
post: {
|
|
54790
|
+
operationId: "lookupTaskManifestBinding",
|
|
54791
|
+
summary: "Recover one exact task-manifest apply receipt from its managed plan id",
|
|
54792
|
+
requestBody: {
|
|
54793
|
+
required: true,
|
|
54794
|
+
content: {
|
|
54795
|
+
"application/json": {
|
|
54796
|
+
schema: { $ref: "#/components/schemas/TaskManifestBindingLookupRequest" }
|
|
54797
|
+
}
|
|
54798
|
+
}
|
|
54799
|
+
},
|
|
54800
|
+
responses: {
|
|
54801
|
+
"200": {
|
|
54802
|
+
content: {
|
|
54803
|
+
"application/json": {
|
|
54804
|
+
schema: { $ref: "#/components/schemas/TaskManifestBindingLookupResponse" }
|
|
54805
|
+
}
|
|
54806
|
+
}
|
|
54807
|
+
},
|
|
54808
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
54809
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
54810
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
54811
|
+
}
|
|
54812
|
+
}
|
|
54813
|
+
},
|
|
53376
54814
|
"/v1/tasks": {
|
|
53377
54815
|
get: {
|
|
53378
54816
|
operationId: "listTasks",
|
|
@@ -54103,7 +55541,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
54103
55541
|
}
|
|
54104
55542
|
};
|
|
54105
55543
|
}
|
|
54106
|
-
var taskSchema, projectSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
|
|
55544
|
+
var taskSchema, projectSchema, taskManifestBindingLookupRequestSchema, taskManifestBindingLookupResultSchema, taskManifestBindingLookupResponseSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
|
|
54107
55545
|
var init_openapi = __esm(() => {
|
|
54108
55546
|
init_package_version();
|
|
54109
55547
|
init_types();
|
|
@@ -54139,6 +55577,51 @@ var init_openapi = __esm(() => {
|
|
|
54139
55577
|
updated_at: { type: "string" }
|
|
54140
55578
|
}
|
|
54141
55579
|
};
|
|
55580
|
+
taskManifestBindingLookupRequestSchema = {
|
|
55581
|
+
type: "object",
|
|
55582
|
+
additionalProperties: false,
|
|
55583
|
+
required: ["authority", "route", "schema_version", "tenant_id", "plan_id", "max_items"],
|
|
55584
|
+
properties: {
|
|
55585
|
+
authority: { type: "string", enum: ["todos"] },
|
|
55586
|
+
route: { type: "string", enum: ["todos.task-manifest.v1"] },
|
|
55587
|
+
schema_version: { type: "integer", enum: [1] },
|
|
55588
|
+
tenant_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
55589
|
+
plan_id: { type: "string", format: "uuid" },
|
|
55590
|
+
max_items: { type: "integer", enum: [1] }
|
|
55591
|
+
}
|
|
55592
|
+
};
|
|
55593
|
+
taskManifestBindingLookupResultSchema = {
|
|
55594
|
+
type: "object",
|
|
55595
|
+
additionalProperties: false,
|
|
55596
|
+
required: [
|
|
55597
|
+
"authority",
|
|
55598
|
+
"route",
|
|
55599
|
+
"schema_version",
|
|
55600
|
+
"tenant_id",
|
|
55601
|
+
"plan_id",
|
|
55602
|
+
"apply_receipt_id",
|
|
55603
|
+
"binding_version",
|
|
55604
|
+
"state"
|
|
55605
|
+
],
|
|
55606
|
+
properties: {
|
|
55607
|
+
authority: { type: "string", enum: ["todos"] },
|
|
55608
|
+
route: { type: "string", enum: ["todos.task-manifest.v1"] },
|
|
55609
|
+
schema_version: { type: "integer", enum: [1] },
|
|
55610
|
+
tenant_id: { type: "string" },
|
|
55611
|
+
plan_id: { type: "string", format: "uuid" },
|
|
55612
|
+
apply_receipt_id: { type: "string", format: "uuid" },
|
|
55613
|
+
binding_version: { type: "integer", minimum: 1 },
|
|
55614
|
+
state: { type: "string", enum: ["applied", "compensated"] }
|
|
55615
|
+
}
|
|
55616
|
+
};
|
|
55617
|
+
taskManifestBindingLookupResponseSchema = {
|
|
55618
|
+
type: "object",
|
|
55619
|
+
additionalProperties: false,
|
|
55620
|
+
required: ["result"],
|
|
55621
|
+
properties: {
|
|
55622
|
+
result: { $ref: "#/components/schemas/TaskManifestBindingLookupResult" }
|
|
55623
|
+
}
|
|
55624
|
+
};
|
|
54142
55625
|
taskListSchema = {
|
|
54143
55626
|
type: "object",
|
|
54144
55627
|
properties: {
|
|
@@ -54424,8 +55907,8 @@ var exports_pr_groups = {};
|
|
|
54424
55907
|
__export(exports_pr_groups, {
|
|
54425
55908
|
handlePrGroupHttpRequest: () => handlePrGroupHttpRequest
|
|
54426
55909
|
});
|
|
54427
|
-
function
|
|
54428
|
-
return new Response(JSON.stringify(
|
|
55910
|
+
function json4(body2, status2 = 200) {
|
|
55911
|
+
return new Response(JSON.stringify(body2), { status: status2, headers: JSON_HEADERS3 });
|
|
54429
55912
|
}
|
|
54430
55913
|
function errorStatus2(error) {
|
|
54431
55914
|
switch (error.code) {
|
|
@@ -54459,26 +55942,26 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
|
|
|
54459
55942
|
const method = req.method.toUpperCase();
|
|
54460
55943
|
try {
|
|
54461
55944
|
if (!groupId && method === "POST" && action === undefined) {
|
|
54462
|
-
return
|
|
55945
|
+
return json4({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
|
|
54463
55946
|
}
|
|
54464
55947
|
if (groupId === "admit" && !action) {
|
|
54465
55948
|
if (method !== "POST")
|
|
54466
|
-
return
|
|
54467
|
-
const
|
|
54468
|
-
if (!
|
|
54469
|
-
return
|
|
54470
|
-
return
|
|
55949
|
+
return json4({ error: "method not allowed" }, 405);
|
|
55950
|
+
const body2 = await readJson2(req);
|
|
55951
|
+
if (!body2)
|
|
55952
|
+
return json4({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
55953
|
+
return json4(await ledger.admit(body2), 201);
|
|
54471
55954
|
}
|
|
54472
55955
|
if (!groupId)
|
|
54473
|
-
return
|
|
55956
|
+
return json4({ error: "PR group id is required", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
54474
55957
|
if (!action && method === "GET") {
|
|
54475
|
-
return
|
|
55958
|
+
return json4({ view: await ledger.get(groupId) });
|
|
54476
55959
|
}
|
|
54477
55960
|
if (action === "events") {
|
|
54478
55961
|
if (method === "GET") {
|
|
54479
55962
|
const limit = url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined;
|
|
54480
55963
|
const afterSequence = url.searchParams.has("after_sequence") ? Number(url.searchParams.get("after_sequence")) : undefined;
|
|
54481
|
-
return
|
|
55964
|
+
return json4({
|
|
54482
55965
|
history: await ledger.events(groupId, {
|
|
54483
55966
|
...limit !== undefined ? { limit } : {},
|
|
54484
55967
|
...afterSequence !== undefined ? { after_sequence: afterSequence } : {}
|
|
@@ -54486,54 +55969,54 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
|
|
|
54486
55969
|
});
|
|
54487
55970
|
}
|
|
54488
55971
|
if (method === "POST") {
|
|
54489
|
-
const
|
|
54490
|
-
if (!
|
|
54491
|
-
return
|
|
54492
|
-
return
|
|
54493
|
-
...
|
|
55972
|
+
const body2 = await readJson2(req);
|
|
55973
|
+
if (!body2)
|
|
55974
|
+
return json4({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
55975
|
+
return json4(await ledger.append({
|
|
55976
|
+
...body2,
|
|
54494
55977
|
group_id: groupId,
|
|
54495
55978
|
authenticated_actor_id: principal?.actor_id ?? undefined,
|
|
54496
55979
|
authenticated_actor_run_id: principal?.actor_run_id ?? undefined
|
|
54497
55980
|
}), 201);
|
|
54498
55981
|
}
|
|
54499
|
-
return
|
|
55982
|
+
return json4({ error: "method not allowed" }, 405);
|
|
54500
55983
|
}
|
|
54501
55984
|
if (action === "recover") {
|
|
54502
55985
|
if (method !== "POST")
|
|
54503
|
-
return
|
|
54504
|
-
const
|
|
54505
|
-
if (!
|
|
54506
|
-
return
|
|
54507
|
-
return
|
|
54508
|
-
...
|
|
55986
|
+
return json4({ error: "method not allowed" }, 405);
|
|
55987
|
+
const body2 = await readJson2(req);
|
|
55988
|
+
if (!body2)
|
|
55989
|
+
return json4({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
55990
|
+
return json4(await ledger.recover({
|
|
55991
|
+
...body2,
|
|
54509
55992
|
group_id: groupId
|
|
54510
55993
|
}), 201);
|
|
54511
55994
|
}
|
|
54512
|
-
return
|
|
55995
|
+
return json4({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
|
|
54513
55996
|
} catch (cause) {
|
|
54514
55997
|
if (cause instanceof PrGroupLedgerError) {
|
|
54515
|
-
return
|
|
55998
|
+
return json4({
|
|
54516
55999
|
error: cause.message,
|
|
54517
56000
|
code: cause.code,
|
|
54518
56001
|
details: cause.details,
|
|
54519
56002
|
authoritative: true
|
|
54520
56003
|
}, errorStatus2(cause));
|
|
54521
56004
|
}
|
|
54522
|
-
return
|
|
56005
|
+
return json4({
|
|
54523
56006
|
error: cause instanceof Error ? cause.message : "internal PR-group error",
|
|
54524
56007
|
code: "PR_GROUP_ATOMICITY_UNAVAILABLE"
|
|
54525
56008
|
}, 500);
|
|
54526
56009
|
}
|
|
54527
56010
|
}
|
|
54528
|
-
var
|
|
56011
|
+
var JSON_HEADERS3;
|
|
54529
56012
|
var init_pr_groups = __esm(() => {
|
|
54530
56013
|
init_types3();
|
|
54531
|
-
|
|
56014
|
+
JSON_HEADERS3 = { "Content-Type": "application/json" };
|
|
54532
56015
|
});
|
|
54533
56016
|
|
|
54534
56017
|
// src/lib/comment-cursor.ts
|
|
54535
|
-
function encodeCommentCursor(
|
|
54536
|
-
return Buffer.from(JSON.stringify({ created_at:
|
|
56018
|
+
function encodeCommentCursor(comment2) {
|
|
56019
|
+
return Buffer.from(JSON.stringify({ created_at: comment2.created_at, id: comment2.id }), "utf8").toString("base64url");
|
|
54537
56020
|
}
|
|
54538
56021
|
function decodeCommentCursor(value) {
|
|
54539
56022
|
if (value.length > MAX_COMMENT_CURSOR_LENGTH)
|
|
@@ -54555,26 +56038,26 @@ function decodeCommentCursor(value) {
|
|
|
54555
56038
|
var MAX_COMMENT_CURSOR_LENGTH = 1024;
|
|
54556
56039
|
|
|
54557
56040
|
// src/lib/project-task-list-ensure.ts
|
|
54558
|
-
import { createHash as
|
|
54559
|
-
function
|
|
56041
|
+
import { createHash as createHash17 } from "crypto";
|
|
56042
|
+
function canonicalJson2(value) {
|
|
54560
56043
|
if (value === null || typeof value !== "object")
|
|
54561
56044
|
return JSON.stringify(value);
|
|
54562
56045
|
if (Array.isArray(value))
|
|
54563
|
-
return `[${value.map(
|
|
54564
|
-
return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([
|
|
56046
|
+
return `[${value.map(canonicalJson2).join(",")}]`;
|
|
56047
|
+
return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key2, item]) => `${JSON.stringify(key2)}:${canonicalJson2(item)}`).join(",")}}`;
|
|
54565
56048
|
}
|
|
54566
56049
|
function digest(value) {
|
|
54567
|
-
return
|
|
56050
|
+
return createHash17("sha256").update(canonicalJson2(value)).digest("hex");
|
|
54568
56051
|
}
|
|
54569
56052
|
function deriveIdempotencyKey(projectId, slug) {
|
|
54570
56053
|
return `ptlk_${digest({ project_id: projectId, slug }).slice(0, 48)}`;
|
|
54571
56054
|
}
|
|
54572
56055
|
function normalizeIdempotencyKey(value, projectId, slug) {
|
|
54573
|
-
const
|
|
54574
|
-
if (
|
|
56056
|
+
const key2 = value?.trim() || deriveIdempotencyKey(projectId, slug);
|
|
56057
|
+
if (key2.length < 8 || key2.length > 128 || !/^[A-Za-z0-9._:-]+$/.test(key2)) {
|
|
54575
56058
|
throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_IDEMPOTENCY_KEY_INVALID", "idempotency_key must be 8-128 ASCII letters, digits, dots, underscores, colons, or hyphens");
|
|
54576
56059
|
}
|
|
54577
|
-
return
|
|
56060
|
+
return key2;
|
|
54578
56061
|
}
|
|
54579
56062
|
function receiptId2(projectId, slug, idempotencyKey) {
|
|
54580
56063
|
return `ptlr_${digest({ project_id: projectId, slug, idempotency_key: idempotencyKey }).slice(0, 48)}`;
|
|
@@ -54837,7 +56320,7 @@ async function exactPlanProjectLinkState(store, planId, projectId) {
|
|
|
54837
56320
|
}
|
|
54838
56321
|
async function planPlanProjectLink(store, planId, projectId) {
|
|
54839
56322
|
const state = await exactPlanProjectLinkState(store, planId, projectId);
|
|
54840
|
-
const alreadyLinked = state.plan.project_id === state.project.id && state.tasks.every((
|
|
56323
|
+
const alreadyLinked = state.plan.project_id === state.project.id && state.tasks.every((task3) => task3.project_id === state.project.id);
|
|
54841
56324
|
return {
|
|
54842
56325
|
mode: "plan",
|
|
54843
56326
|
action: alreadyLinked ? "already_linked" : "would_link",
|
|
@@ -54849,14 +56332,14 @@ async function applyPlanProjectLink(store, planId, projectId, options) {
|
|
|
54849
56332
|
if (!store.planProjectLinks) {
|
|
54850
56333
|
throw new PlanProjectLinkError("PLAN_PROJECT_LINK_UNSUPPORTED", "This storage backend cannot atomically link an existing plan and its tasks", { storage_kind: store.kind });
|
|
54851
56334
|
}
|
|
54852
|
-
const
|
|
56335
|
+
const key2 = normalizePlanProjectLinkIdempotencyKey(options.idempotency_key);
|
|
54853
56336
|
return store.planProjectLinks.apply({
|
|
54854
56337
|
plan_id: planId,
|
|
54855
56338
|
project_id: projectId,
|
|
54856
56339
|
expected_plan_revision: options.expected_plan_revision,
|
|
54857
56340
|
expected_project_revision: options.expected_project_revision,
|
|
54858
|
-
idempotency_key:
|
|
54859
|
-
receipt_id: planProjectLinkReceiptId(
|
|
56341
|
+
idempotency_key: key2,
|
|
56342
|
+
receipt_id: planProjectLinkReceiptId(key2),
|
|
54860
56343
|
created_at: new Date().toISOString()
|
|
54861
56344
|
});
|
|
54862
56345
|
}
|
|
@@ -54885,11 +56368,11 @@ __export(exports_v1, {
|
|
|
54885
56368
|
handleV1Request: () => handleV1Request,
|
|
54886
56369
|
countSnapshotRecords: () => countSnapshotRecords
|
|
54887
56370
|
});
|
|
54888
|
-
function
|
|
54889
|
-
return new Response(JSON.stringify(
|
|
56371
|
+
function json5(body2, status2 = 200) {
|
|
56372
|
+
return new Response(JSON.stringify(body2), { status: status2, headers: JSON_HEADERS4 });
|
|
54890
56373
|
}
|
|
54891
|
-
function error(
|
|
54892
|
-
return
|
|
56374
|
+
function error(status2, message, extra) {
|
|
56375
|
+
return json5({ error: message, ...extra ?? {} }, status2);
|
|
54893
56376
|
}
|
|
54894
56377
|
function enumQueryParam(url, name, vocabulary) {
|
|
54895
56378
|
const raw = url.searchParams.get(name);
|
|
@@ -54918,112 +56401,112 @@ function parseSinceCursor(raw) {
|
|
|
54918
56401
|
function validateTaskCompletion(value) {
|
|
54919
56402
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
54920
56403
|
return { ok: false, message: "completion body must be an object" };
|
|
54921
|
-
const
|
|
56404
|
+
const body2 = value;
|
|
54922
56405
|
const allowed = new Set(["agent_id", "attachment_ids", "files_changed", "test_results", "commit_hash", "notes", "confidence"]);
|
|
54923
|
-
const unknown = Object.keys(
|
|
56406
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
54924
56407
|
if (unknown)
|
|
54925
56408
|
return { ok: false, message: `unknown completion field: ${unknown}` };
|
|
54926
|
-
if (
|
|
56409
|
+
if (body2.agent_id !== undefined && (typeof body2.agent_id !== "string" || !body2.agent_id.trim())) {
|
|
54927
56410
|
return { ok: false, message: "agent_id must be a non-empty string" };
|
|
54928
56411
|
}
|
|
54929
56412
|
for (const field of ["attachment_ids", "files_changed"]) {
|
|
54930
|
-
const value2 =
|
|
56413
|
+
const value2 = body2[field];
|
|
54931
56414
|
if (value2 !== undefined && (!Array.isArray(value2) || value2.some((item) => typeof item !== "string" || !item.trim()))) {
|
|
54932
56415
|
return { ok: false, message: `${field} must be an array of non-empty strings` };
|
|
54933
56416
|
}
|
|
54934
56417
|
}
|
|
54935
56418
|
for (const field of ["test_results", "commit_hash", "notes"]) {
|
|
54936
|
-
if (
|
|
56419
|
+
if (body2[field] !== undefined && typeof body2[field] !== "string") {
|
|
54937
56420
|
return { ok: false, message: `${field} must be a string` };
|
|
54938
56421
|
}
|
|
54939
56422
|
}
|
|
54940
|
-
if (
|
|
56423
|
+
if (body2.confidence !== undefined && (typeof body2.confidence !== "number" || !Number.isFinite(body2.confidence) || body2.confidence < 0 || body2.confidence > 1)) {
|
|
54941
56424
|
return { ok: false, message: "confidence must be a number between 0 and 1" };
|
|
54942
56425
|
}
|
|
54943
56426
|
return {
|
|
54944
56427
|
ok: true,
|
|
54945
|
-
...typeof
|
|
56428
|
+
...typeof body2.agent_id === "string" ? { agentId: body2.agent_id } : {},
|
|
54946
56429
|
options: {
|
|
54947
|
-
...Array.isArray(
|
|
54948
|
-
...Array.isArray(
|
|
54949
|
-
...typeof
|
|
54950
|
-
...typeof
|
|
54951
|
-
...typeof
|
|
54952
|
-
...typeof
|
|
56430
|
+
...Array.isArray(body2.attachment_ids) ? { attachment_ids: body2.attachment_ids } : {},
|
|
56431
|
+
...Array.isArray(body2.files_changed) ? { files_changed: body2.files_changed } : {},
|
|
56432
|
+
...typeof body2.test_results === "string" ? { test_results: body2.test_results } : {},
|
|
56433
|
+
...typeof body2.commit_hash === "string" ? { commit_hash: body2.commit_hash } : {},
|
|
56434
|
+
...typeof body2.notes === "string" ? { notes: body2.notes } : {},
|
|
56435
|
+
...typeof body2.confidence === "number" ? { confidence: body2.confidence } : {}
|
|
54953
56436
|
}
|
|
54954
56437
|
};
|
|
54955
56438
|
}
|
|
54956
56439
|
function validateProjectPatch(value) {
|
|
54957
56440
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
54958
56441
|
return { ok: false, message: "project patch must be an object" };
|
|
54959
|
-
const
|
|
56442
|
+
const body2 = value;
|
|
54960
56443
|
const allowed = new Set(["name", "path", "description"]);
|
|
54961
|
-
const unknown = Object.keys(
|
|
56444
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
54962
56445
|
if (unknown)
|
|
54963
56446
|
return { ok: false, message: `unknown project field: ${unknown}` };
|
|
54964
|
-
if (Object.keys(
|
|
56447
|
+
if (Object.keys(body2).length === 0)
|
|
54965
56448
|
return { ok: false, message: "project patch must not be empty" };
|
|
54966
|
-
if (
|
|
56449
|
+
if (body2["name"] !== undefined && (typeof body2["name"] !== "string" || !body2["name"].trim()))
|
|
54967
56450
|
return { ok: false, message: "name must be a non-empty string" };
|
|
54968
|
-
if (
|
|
56451
|
+
if (body2["path"] !== undefined && (typeof body2["path"] !== "string" || !body2["path"].trim()))
|
|
54969
56452
|
return { ok: false, message: "path must be a non-empty string" };
|
|
54970
|
-
if (
|
|
56453
|
+
if (body2["description"] !== undefined && body2["description"] !== null && typeof body2["description"] !== "string")
|
|
54971
56454
|
return { ok: false, message: "description must be a string or null" };
|
|
54972
|
-
return { ok: true, patch:
|
|
56455
|
+
return { ok: true, patch: body2 };
|
|
54973
56456
|
}
|
|
54974
56457
|
function validateProjectCreate(value) {
|
|
54975
56458
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
54976
56459
|
return { ok: false, message: "project body must be an object" };
|
|
54977
|
-
const
|
|
56460
|
+
const body2 = value;
|
|
54978
56461
|
const allowed = new Set(["name", "path", "description", "task_list_id", "task_prefix"]);
|
|
54979
|
-
const unknown = Object.keys(
|
|
56462
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
54980
56463
|
if (unknown)
|
|
54981
56464
|
return { ok: false, message: `unknown project field: ${unknown}` };
|
|
54982
|
-
if (typeof
|
|
56465
|
+
if (typeof body2["name"] !== "string" || !body2["name"].trim())
|
|
54983
56466
|
return { ok: false, message: "name must be a non-empty string" };
|
|
54984
|
-
if (!normalizeSlug(
|
|
56467
|
+
if (!normalizeSlug(body2["name"]))
|
|
54985
56468
|
return { ok: false, message: "name must produce a non-empty canonical slug" };
|
|
54986
|
-
if (typeof
|
|
56469
|
+
if (typeof body2["path"] !== "string" || !body2["path"].trim())
|
|
54987
56470
|
return { ok: false, message: "path must be a non-empty string" };
|
|
54988
|
-
if (
|
|
56471
|
+
if (body2["description"] !== undefined && typeof body2["description"] !== "string")
|
|
54989
56472
|
return { ok: false, message: "description must be a string" };
|
|
54990
|
-
if (
|
|
56473
|
+
if (body2["task_list_id"] !== undefined && !isCanonicalSlug(body2["task_list_id"])) {
|
|
54991
56474
|
return { ok: false, message: "task_list_id must be non-empty canonical kebab-case" };
|
|
54992
56475
|
}
|
|
54993
|
-
if (
|
|
56476
|
+
if (body2["task_prefix"] !== undefined && (typeof body2["task_prefix"] !== "string" || !body2["task_prefix"].trim())) {
|
|
54994
56477
|
return { ok: false, message: "task_prefix must be a non-empty string" };
|
|
54995
56478
|
}
|
|
54996
|
-
return { ok: true, input:
|
|
56479
|
+
return { ok: true, input: body2 };
|
|
54997
56480
|
}
|
|
54998
56481
|
function validatePlanCreate(value) {
|
|
54999
56482
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
55000
56483
|
return { ok: false, message: "plan body must be an object" };
|
|
55001
|
-
const
|
|
56484
|
+
const body2 = value;
|
|
55002
56485
|
const allowed = new Set(["title", "name", "slug", "description", "project_id", "task_list_id", "agent_id", "status"]);
|
|
55003
|
-
const unknown = Object.keys(
|
|
56486
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55004
56487
|
if (unknown)
|
|
55005
56488
|
return { ok: false, message: `unknown plan field: ${unknown}` };
|
|
55006
|
-
if (
|
|
56489
|
+
if (body2.name !== undefined && (typeof body2.name !== "string" || !body2.name.trim()))
|
|
55007
56490
|
return { ok: false, message: "name must be a non-empty string" };
|
|
55008
|
-
if (
|
|
56491
|
+
if (body2.title !== undefined && (typeof body2.title !== "string" || !body2.title.trim()))
|
|
55009
56492
|
return { ok: false, message: "title must be a non-empty string" };
|
|
55010
|
-
if (typeof
|
|
56493
|
+
if (typeof body2.name === "string" && typeof body2.title === "string" && body2.name !== body2.title) {
|
|
55011
56494
|
return { ok: false, message: "name and title must match when both are provided" };
|
|
55012
56495
|
}
|
|
55013
|
-
const name =
|
|
56496
|
+
const name = body2.name ?? body2.title;
|
|
55014
56497
|
if (!name)
|
|
55015
56498
|
return { ok: false, message: "name is required" };
|
|
55016
56499
|
for (const field of ["slug", "project_id", "task_list_id", "agent_id"]) {
|
|
55017
|
-
if (
|
|
56500
|
+
if (body2[field] !== undefined && (typeof body2[field] !== "string" || !body2[field].trim())) {
|
|
55018
56501
|
return { ok: false, message: `${field} must be a non-empty string` };
|
|
55019
56502
|
}
|
|
55020
56503
|
}
|
|
55021
|
-
const slug = typeof
|
|
55022
|
-
if (
|
|
56504
|
+
const slug = typeof body2.slug === "string" ? normalizeSlug(body2.slug) : undefined;
|
|
56505
|
+
if (body2.slug !== undefined && !slug)
|
|
55023
56506
|
return { ok: false, message: "slug must produce a non-empty canonical slug" };
|
|
55024
|
-
if (
|
|
56507
|
+
if (body2.description !== undefined && typeof body2.description !== "string")
|
|
55025
56508
|
return { ok: false, message: "description must be a string" };
|
|
55026
|
-
if (
|
|
56509
|
+
if (body2.status !== undefined && (typeof body2.status !== "string" || !["active", "completed", "archived"].includes(body2.status))) {
|
|
55027
56510
|
return { ok: false, message: "status must be active, completed, or archived" };
|
|
55028
56511
|
}
|
|
55029
56512
|
return {
|
|
@@ -55031,102 +56514,102 @@ function validatePlanCreate(value) {
|
|
|
55031
56514
|
input: {
|
|
55032
56515
|
name,
|
|
55033
56516
|
...slug ? { slug } : {},
|
|
55034
|
-
...typeof
|
|
55035
|
-
...typeof
|
|
55036
|
-
...typeof
|
|
55037
|
-
...typeof
|
|
55038
|
-
...typeof
|
|
56517
|
+
...typeof body2.description === "string" ? { description: body2.description } : {},
|
|
56518
|
+
...typeof body2.project_id === "string" ? { project_id: body2.project_id } : {},
|
|
56519
|
+
...typeof body2.task_list_id === "string" ? { task_list_id: body2.task_list_id } : {},
|
|
56520
|
+
...typeof body2.agent_id === "string" ? { agent_id: body2.agent_id } : {},
|
|
56521
|
+
...typeof body2.status === "string" ? { status: body2.status } : {}
|
|
55039
56522
|
}
|
|
55040
56523
|
};
|
|
55041
56524
|
}
|
|
55042
56525
|
function validateTemplateTask(value) {
|
|
55043
56526
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
55044
56527
|
return null;
|
|
55045
|
-
const
|
|
56528
|
+
const body2 = value;
|
|
55046
56529
|
const allowed = new Set(["position", "title_pattern", "description", "priority", "tags", "task_type", "condition", "include_template_id", "depends_on", "depends_on_positions", "metadata"]);
|
|
55047
|
-
if (Object.keys(
|
|
56530
|
+
if (Object.keys(body2).some((key2) => !allowed.has(key2)))
|
|
55048
56531
|
return null;
|
|
55049
|
-
if (typeof
|
|
56532
|
+
if (typeof body2.title_pattern !== "string" || !body2.title_pattern.trim())
|
|
55050
56533
|
return null;
|
|
55051
|
-
if (
|
|
56534
|
+
if (body2.position !== undefined && (typeof body2.position !== "number" || !Number.isSafeInteger(body2.position) || body2.position < 0))
|
|
55052
56535
|
return null;
|
|
55053
|
-
if (
|
|
56536
|
+
if (body2.description !== undefined && body2.description !== null && typeof body2.description !== "string")
|
|
55054
56537
|
return null;
|
|
55055
|
-
if (
|
|
56538
|
+
if (body2.priority !== undefined && (typeof body2.priority !== "string" || !["low", "medium", "high", "critical"].includes(body2.priority)))
|
|
55056
56539
|
return null;
|
|
55057
|
-
if (
|
|
56540
|
+
if (body2.tags !== undefined && (!Array.isArray(body2.tags) || body2.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
55058
56541
|
return null;
|
|
55059
56542
|
for (const field of ["task_type", "condition", "include_template_id"]) {
|
|
55060
|
-
if (
|
|
56543
|
+
if (body2[field] !== undefined && body2[field] !== null && (typeof body2[field] !== "string" || !body2[field].trim()))
|
|
55061
56544
|
return null;
|
|
55062
56545
|
}
|
|
55063
|
-
if (
|
|
56546
|
+
if (body2.depends_on !== undefined && body2.depends_on_positions !== undefined)
|
|
55064
56547
|
return null;
|
|
55065
|
-
const dependencies =
|
|
56548
|
+
const dependencies = body2.depends_on ?? body2.depends_on_positions;
|
|
55066
56549
|
if (dependencies !== undefined && (!Array.isArray(dependencies) || dependencies.some((position) => !Number.isSafeInteger(position) || position < 0)))
|
|
55067
56550
|
return null;
|
|
55068
|
-
if (
|
|
56551
|
+
if (body2.metadata !== undefined && (!body2.metadata || typeof body2.metadata !== "object" || Array.isArray(body2.metadata)))
|
|
55069
56552
|
return null;
|
|
55070
56553
|
return {
|
|
55071
|
-
title_pattern:
|
|
55072
|
-
...typeof
|
|
55073
|
-
...typeof
|
|
55074
|
-
...Array.isArray(
|
|
55075
|
-
...typeof
|
|
55076
|
-
...typeof
|
|
55077
|
-
...typeof
|
|
56554
|
+
title_pattern: body2.title_pattern,
|
|
56555
|
+
...typeof body2.description === "string" ? { description: body2.description } : {},
|
|
56556
|
+
...typeof body2.priority === "string" ? { priority: body2.priority } : {},
|
|
56557
|
+
...Array.isArray(body2.tags) ? { tags: body2.tags } : {},
|
|
56558
|
+
...typeof body2.task_type === "string" ? { task_type: body2.task_type } : {},
|
|
56559
|
+
...typeof body2.condition === "string" ? { condition: body2.condition } : {},
|
|
56560
|
+
...typeof body2.include_template_id === "string" ? { include_template_id: body2.include_template_id } : {},
|
|
55078
56561
|
...Array.isArray(dependencies) ? { depends_on: dependencies } : {},
|
|
55079
|
-
...
|
|
56562
|
+
...body2.metadata && typeof body2.metadata === "object" && !Array.isArray(body2.metadata) ? { metadata: body2.metadata } : {}
|
|
55080
56563
|
};
|
|
55081
56564
|
}
|
|
55082
56565
|
function validateTemplateCreate(value) {
|
|
55083
56566
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
55084
56567
|
return { ok: false, message: "template body must be an object" };
|
|
55085
|
-
const
|
|
56568
|
+
const body2 = value;
|
|
55086
56569
|
const allowed = new Set(["name", "title_pattern", "description", "priority", "tags", "variables", "project_id", "plan_id", "metadata", "tasks"]);
|
|
55087
|
-
const unknown = Object.keys(
|
|
56570
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55088
56571
|
if (unknown)
|
|
55089
56572
|
return { ok: false, message: `unknown template field: ${unknown}` };
|
|
55090
|
-
if (typeof
|
|
56573
|
+
if (typeof body2.name !== "string" || !body2.name.trim())
|
|
55091
56574
|
return { ok: false, message: "name must be a non-empty string" };
|
|
55092
|
-
if (typeof
|
|
56575
|
+
if (typeof body2.title_pattern !== "string" || !body2.title_pattern.trim())
|
|
55093
56576
|
return { ok: false, message: "title_pattern must be a non-empty string" };
|
|
55094
|
-
if (
|
|
56577
|
+
if (body2.description !== undefined && body2.description !== null && typeof body2.description !== "string")
|
|
55095
56578
|
return { ok: false, message: "description must be a string or null" };
|
|
55096
|
-
if (
|
|
56579
|
+
if (body2.priority !== undefined && (typeof body2.priority !== "string" || !["low", "medium", "high", "critical"].includes(body2.priority)))
|
|
55097
56580
|
return { ok: false, message: "priority must be low, medium, high, or critical" };
|
|
55098
|
-
if (
|
|
56581
|
+
if (body2.tags !== undefined && (!Array.isArray(body2.tags) || body2.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
55099
56582
|
return { ok: false, message: "tags must be an array of non-empty strings" };
|
|
55100
|
-
if (
|
|
56583
|
+
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"))) {
|
|
55101
56584
|
return { ok: false, message: "variables must be valid template variable objects" };
|
|
55102
56585
|
}
|
|
55103
56586
|
for (const field of ["project_id", "plan_id"]) {
|
|
55104
|
-
if (
|
|
56587
|
+
if (body2[field] !== undefined && body2[field] !== null && (typeof body2[field] !== "string" || !body2[field].trim()))
|
|
55105
56588
|
return { ok: false, message: `${field} must be a non-empty string or null` };
|
|
55106
56589
|
}
|
|
55107
|
-
if (
|
|
56590
|
+
if (body2.metadata !== undefined && (!body2.metadata || typeof body2.metadata !== "object" || Array.isArray(body2.metadata)))
|
|
55108
56591
|
return { ok: false, message: "metadata must be an object" };
|
|
55109
|
-
const tasks =
|
|
55110
|
-
if (tasks === null || tasks.some((
|
|
56592
|
+
const tasks = body2.tasks === undefined ? [] : Array.isArray(body2.tasks) ? body2.tasks.map(validateTemplateTask) : null;
|
|
56593
|
+
if (tasks === null || tasks.some((task3) => task3 === null))
|
|
55111
56594
|
return { ok: false, message: "tasks must be valid template task objects" };
|
|
55112
56595
|
const taskInputs = tasks;
|
|
55113
|
-
for (const [position,
|
|
55114
|
-
if ((
|
|
56596
|
+
for (const [position, task3] of taskInputs.entries()) {
|
|
56597
|
+
if ((task3.depends_on ?? []).some((dependency2) => dependency2 >= position)) {
|
|
55115
56598
|
return { ok: false, message: "template task dependencies must reference earlier task positions" };
|
|
55116
56599
|
}
|
|
55117
56600
|
}
|
|
55118
56601
|
return {
|
|
55119
56602
|
ok: true,
|
|
55120
56603
|
input: {
|
|
55121
|
-
name:
|
|
55122
|
-
title_pattern:
|
|
55123
|
-
...typeof
|
|
55124
|
-
...typeof
|
|
55125
|
-
...Array.isArray(
|
|
55126
|
-
...Array.isArray(
|
|
55127
|
-
...typeof
|
|
55128
|
-
...typeof
|
|
55129
|
-
...
|
|
56604
|
+
name: body2.name,
|
|
56605
|
+
title_pattern: body2.title_pattern,
|
|
56606
|
+
...typeof body2.description === "string" ? { description: body2.description } : {},
|
|
56607
|
+
...typeof body2.priority === "string" ? { priority: body2.priority } : {},
|
|
56608
|
+
...Array.isArray(body2.tags) ? { tags: body2.tags } : {},
|
|
56609
|
+
...Array.isArray(body2.variables) ? { variables: body2.variables } : {},
|
|
56610
|
+
...typeof body2.project_id === "string" ? { project_id: body2.project_id } : {},
|
|
56611
|
+
...typeof body2.plan_id === "string" ? { plan_id: body2.plan_id } : {},
|
|
56612
|
+
...body2.metadata && typeof body2.metadata === "object" && !Array.isArray(body2.metadata) ? { metadata: body2.metadata } : {},
|
|
55130
56613
|
tasks: taskInputs
|
|
55131
56614
|
}
|
|
55132
56615
|
};
|
|
@@ -55134,25 +56617,25 @@ function validateTemplateCreate(value) {
|
|
|
55134
56617
|
function validateTemplatePatch(value) {
|
|
55135
56618
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
55136
56619
|
return { ok: false, message: "template patch must be an object" };
|
|
55137
|
-
const
|
|
56620
|
+
const body2 = value;
|
|
55138
56621
|
const allowed = new Set(["name", "title_pattern", "description", "priority", "tags", "variables", "project_id", "plan_id", "metadata"]);
|
|
55139
|
-
const unknown = Object.keys(
|
|
56622
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55140
56623
|
if (unknown)
|
|
55141
56624
|
return { ok: false, message: `unknown template field: ${unknown}` };
|
|
55142
|
-
if (Object.keys(
|
|
56625
|
+
if (Object.keys(body2).length === 0)
|
|
55143
56626
|
return { ok: false, message: "template patch must not be empty" };
|
|
55144
|
-
const templateLike = { name:
|
|
56627
|
+
const templateLike = { name: body2.name ?? "template", title_pattern: body2.title_pattern ?? "template", ...body2 };
|
|
55145
56628
|
const validated = validateTemplateCreate(templateLike);
|
|
55146
56629
|
if (!validated.ok)
|
|
55147
56630
|
return validated;
|
|
55148
56631
|
const { name: _name, title_pattern: _title, tasks: _tasks, ...patch } = validated.input;
|
|
55149
56632
|
return { ok: true, patch: {
|
|
55150
|
-
...
|
|
55151
|
-
...
|
|
56633
|
+
...body2.name !== undefined ? { name: validated.input.name } : {},
|
|
56634
|
+
...body2.title_pattern !== undefined ? { title_pattern: validated.input.title_pattern } : {},
|
|
55152
56635
|
...patch,
|
|
55153
|
-
...
|
|
55154
|
-
...
|
|
55155
|
-
...
|
|
56636
|
+
...body2.description === null ? { description: null } : {},
|
|
56637
|
+
...body2.project_id === null ? { project_id: null } : {},
|
|
56638
|
+
...body2.plan_id === null ? { plan_id: null } : {}
|
|
55156
56639
|
} };
|
|
55157
56640
|
}
|
|
55158
56641
|
async function readJson3(req) {
|
|
@@ -55175,29 +56658,29 @@ async function readOptionalJson(req) {
|
|
|
55175
56658
|
return { ok: false };
|
|
55176
56659
|
}
|
|
55177
56660
|
}
|
|
55178
|
-
function contextFromPrincipal(principal,
|
|
55179
|
-
const agentId =
|
|
56661
|
+
function contextFromPrincipal(principal, body2) {
|
|
56662
|
+
const agentId = body2?.agent_id || principal.agent || undefined;
|
|
55180
56663
|
return agentId ? { agentId } : {};
|
|
55181
56664
|
}
|
|
55182
|
-
function redactComment3(
|
|
55183
|
-
return { ...
|
|
56665
|
+
function redactComment3(comment2) {
|
|
56666
|
+
return { ...comment2, content: redactEvidenceText(comment2.content) };
|
|
55184
56667
|
}
|
|
55185
56668
|
function normalizeImportSnapshot(raw) {
|
|
55186
|
-
const
|
|
56669
|
+
const body2 = raw && typeof raw === "object" ? raw : {};
|
|
55187
56670
|
const arr = (v) => Array.isArray(v) ? v : [];
|
|
55188
56671
|
return {
|
|
55189
|
-
exportedAt: typeof
|
|
55190
|
-
source: typeof
|
|
55191
|
-
tasks: arr(
|
|
55192
|
-
projects: arr(
|
|
55193
|
-
projectMachinePaths: arr(
|
|
55194
|
-
plans: arr(
|
|
55195
|
-
agents: arr(
|
|
55196
|
-
taskLists: arr(
|
|
55197
|
-
templates: arr(
|
|
55198
|
-
templateTasks: arr(
|
|
55199
|
-
auditHistory: arr(
|
|
55200
|
-
tombstones: arr(
|
|
56672
|
+
exportedAt: typeof body2["exportedAt"] === "string" ? body2["exportedAt"] : new Date().toISOString(),
|
|
56673
|
+
source: typeof body2["source"] === "string" ? body2["source"] : "sqlite",
|
|
56674
|
+
tasks: arr(body2["tasks"]),
|
|
56675
|
+
projects: arr(body2["projects"]),
|
|
56676
|
+
projectMachinePaths: arr(body2["projectMachinePaths"]),
|
|
56677
|
+
plans: arr(body2["plans"]),
|
|
56678
|
+
agents: arr(body2["agents"]),
|
|
56679
|
+
taskLists: arr(body2["taskLists"]),
|
|
56680
|
+
templates: arr(body2["templates"]),
|
|
56681
|
+
templateTasks: arr(body2["templateTasks"]),
|
|
56682
|
+
auditHistory: arr(body2["auditHistory"]),
|
|
56683
|
+
tombstones: arr(body2["tombstones"])
|
|
55201
56684
|
};
|
|
55202
56685
|
}
|
|
55203
56686
|
function countSnapshotRecords(s) {
|
|
@@ -55228,6 +56711,9 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55228
56711
|
if (path === "/v1/project-registration" || path.startsWith("/v1/project-registration/")) {
|
|
55229
56712
|
return handleTodosProjectRegistrationHttpRequest(req, url, (dependencies.getProjectRegistrationAuthority ?? getCloudProjectRegistrationAuthority)());
|
|
55230
56713
|
}
|
|
56714
|
+
if (path === "/v1/task-manifest" || path.startsWith("/v1/task-manifest/")) {
|
|
56715
|
+
return handleTodosTaskManifestHttpRequest(req, url, (dependencies.getTaskManifestAuthority ?? getCloudTaskManifestAuthority)());
|
|
56716
|
+
}
|
|
55231
56717
|
const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
|
|
55232
56718
|
const segments = path.split("/").filter(Boolean);
|
|
55233
56719
|
const resource = segments[1];
|
|
@@ -55239,8 +56725,8 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55239
56725
|
if (id === "exists" && !action) {
|
|
55240
56726
|
if (method !== "POST")
|
|
55241
56727
|
return error(405, `method ${method} not allowed on /v1/tasks/exists`);
|
|
55242
|
-
const
|
|
55243
|
-
const ids2 = Array.isArray(
|
|
56728
|
+
const body2 = await readJson3(req);
|
|
56729
|
+
const ids2 = Array.isArray(body2?.ids) ? Array.from(new Set(body2.ids.filter((v) => typeof v === "string" && v.length > 0))) : [];
|
|
55244
56730
|
if (ids2.length === 0)
|
|
55245
56731
|
return error(400, "provide a non-empty string array `ids`");
|
|
55246
56732
|
if (ids2.length > 5000)
|
|
@@ -55249,7 +56735,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55249
56735
|
const presentSet = new Set(found.map((t) => t.id));
|
|
55250
56736
|
const present = ids2.filter((i) => presentSet.has(i));
|
|
55251
56737
|
const missing = ids2.filter((i) => !presentSet.has(i));
|
|
55252
|
-
return
|
|
56738
|
+
return json5({
|
|
55253
56739
|
requested: ids2.length,
|
|
55254
56740
|
present_count: present.length,
|
|
55255
56741
|
missing_count: missing.length,
|
|
@@ -55262,20 +56748,20 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55262
56748
|
if (typeof store.tasks.getByFingerprint !== "function") {
|
|
55263
56749
|
return error(501, "fingerprint upsert is not supported by this storage backend");
|
|
55264
56750
|
}
|
|
55265
|
-
const
|
|
55266
|
-
const fingerprint3 = typeof
|
|
56751
|
+
const body2 = await readJson3(req) ?? {};
|
|
56752
|
+
const fingerprint3 = typeof body2.fingerprint === "string" ? body2.fingerprint.trim() : "";
|
|
55267
56753
|
if (!fingerprint3)
|
|
55268
56754
|
return error(400, "fingerprint is required");
|
|
55269
|
-
if (typeof
|
|
56755
|
+
if (typeof body2.title !== "string" || !body2.title.trim())
|
|
55270
56756
|
return error(400, "title is required");
|
|
55271
56757
|
const existing = await store.tasks.getByFingerprint(fingerprint3);
|
|
55272
56758
|
const metadata = {
|
|
55273
56759
|
...existing?.metadata ?? {},
|
|
55274
|
-
...
|
|
56760
|
+
...body2.metadata ?? {},
|
|
55275
56761
|
fingerprint: fingerprint3
|
|
55276
56762
|
};
|
|
55277
56763
|
const fields = { metadata };
|
|
55278
|
-
for (const
|
|
56764
|
+
for (const key2 of [
|
|
55279
56765
|
"title",
|
|
55280
56766
|
"description",
|
|
55281
56767
|
"priority",
|
|
@@ -55293,17 +56779,17 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55293
56779
|
"recurrence_rule",
|
|
55294
56780
|
"task_type"
|
|
55295
56781
|
]) {
|
|
55296
|
-
const bag =
|
|
55297
|
-
if (bag[
|
|
55298
|
-
fields[
|
|
56782
|
+
const bag = body2;
|
|
56783
|
+
if (bag[key2] !== undefined)
|
|
56784
|
+
fields[key2] = bag[key2];
|
|
55299
56785
|
}
|
|
55300
56786
|
if (!existing) {
|
|
55301
|
-
const
|
|
55302
|
-
return
|
|
56787
|
+
const task3 = await store.tasks.create({ ...fields, title: body2.title }, contextFromPrincipal(principal, body2));
|
|
56788
|
+
return json5({ task: task3, created: true }, 201);
|
|
55303
56789
|
}
|
|
55304
56790
|
try {
|
|
55305
|
-
const
|
|
55306
|
-
return
|
|
56791
|
+
const task3 = await store.tasks.update(existing.id, { ...fields, version: existing.version }, contextFromPrincipal(principal, body2));
|
|
56792
|
+
return json5({ task: task3, created: false });
|
|
55307
56793
|
} catch (e) {
|
|
55308
56794
|
const msg = e.message || "";
|
|
55309
56795
|
if (msg.includes("version conflict"))
|
|
@@ -55351,32 +56837,32 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55351
56837
|
const tasks = await store.tasks.list(filter);
|
|
55352
56838
|
const { limit: _l, offset: _o, ...countFilter } = filter;
|
|
55353
56839
|
const total = await store.tasks.count(countFilter);
|
|
55354
|
-
return
|
|
56840
|
+
return json5({ tasks, count: tasks.length, total });
|
|
55355
56841
|
}
|
|
55356
56842
|
if (method === "POST") {
|
|
55357
|
-
const
|
|
55358
|
-
if (!
|
|
56843
|
+
const body2 = await readJson3(req);
|
|
56844
|
+
if (!body2 || typeof body2.title !== "string" || !body2.title.trim()) {
|
|
55359
56845
|
return error(400, "title is required");
|
|
55360
56846
|
}
|
|
55361
|
-
const storageContext = contextFromPrincipal(principal,
|
|
55362
|
-
if (
|
|
55363
|
-
if (typeof
|
|
56847
|
+
const storageContext = contextFromPrincipal(principal, body2);
|
|
56848
|
+
if (body2.parent_id !== undefined) {
|
|
56849
|
+
if (typeof body2.parent_id !== "string" || !body2.parent_id.trim()) {
|
|
55364
56850
|
return error(400, "parent_id must be a non-empty task id", {
|
|
55365
56851
|
code: "PARENT_TASK_ID_INVALID"
|
|
55366
56852
|
});
|
|
55367
56853
|
}
|
|
55368
|
-
if (!await store.tasks.get(
|
|
55369
|
-
return error(404, `parent task not found: ${
|
|
56854
|
+
if (!await store.tasks.get(body2.parent_id, storageContext)) {
|
|
56855
|
+
return error(404, `parent task not found: ${body2.parent_id}`, {
|
|
55370
56856
|
code: "PARENT_TASK_NOT_FOUND"
|
|
55371
56857
|
});
|
|
55372
56858
|
}
|
|
55373
56859
|
}
|
|
55374
|
-
const created = await store.tasks.create(
|
|
56860
|
+
const created = await store.tasks.create(body2, storageContext);
|
|
55375
56861
|
const persisted = created?.id ? await store.tasks.get(created.id, storageContext) : null;
|
|
55376
|
-
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== (
|
|
56862
|
+
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== (body2.parent_id ?? null)) {
|
|
55377
56863
|
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" });
|
|
55378
56864
|
}
|
|
55379
|
-
return
|
|
56865
|
+
return json5({ task: persisted }, 201);
|
|
55380
56866
|
}
|
|
55381
56867
|
return error(405, `method ${method} not allowed on /v1/tasks`);
|
|
55382
56868
|
}
|
|
@@ -55393,7 +56879,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55393
56879
|
if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
|
|
55394
56880
|
return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
|
|
55395
56881
|
}
|
|
55396
|
-
return
|
|
56882
|
+
return json5({
|
|
55397
56883
|
comments: legacyPage,
|
|
55398
56884
|
count: legacyPage.length,
|
|
55399
56885
|
has_more: false,
|
|
@@ -55418,7 +56904,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55418
56904
|
const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
|
|
55419
56905
|
const hasMore = page.length > limit;
|
|
55420
56906
|
const comments = hasMore ? page.slice(1) : page;
|
|
55421
|
-
return
|
|
56907
|
+
return json5({
|
|
55422
56908
|
comments,
|
|
55423
56909
|
count: comments.length,
|
|
55424
56910
|
has_more: hasMore,
|
|
@@ -55426,22 +56912,22 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55426
56912
|
});
|
|
55427
56913
|
}
|
|
55428
56914
|
if (method === "POST") {
|
|
55429
|
-
const
|
|
55430
|
-
if (typeof
|
|
56915
|
+
const body3 = await readJson3(req) ?? {};
|
|
56916
|
+
if (typeof body3.content !== "string" || !body3.content.trim()) {
|
|
55431
56917
|
return error(400, "content is required");
|
|
55432
56918
|
}
|
|
55433
56919
|
const target = await store.tasks.get(id);
|
|
55434
56920
|
if (!target)
|
|
55435
56921
|
return error(404, "task not found");
|
|
55436
|
-
const
|
|
56922
|
+
const comment2 = await store.audit.addComment({
|
|
55437
56923
|
task_id: id,
|
|
55438
|
-
content:
|
|
55439
|
-
agent_id:
|
|
55440
|
-
session_id:
|
|
55441
|
-
type:
|
|
55442
|
-
progress_pct:
|
|
55443
|
-
}, contextFromPrincipal(principal,
|
|
55444
|
-
return
|
|
56924
|
+
content: body3.content,
|
|
56925
|
+
agent_id: body3.agent_id ?? principal.agent ?? undefined,
|
|
56926
|
+
session_id: body3.session_id,
|
|
56927
|
+
type: body3.type,
|
|
56928
|
+
progress_pct: body3.progress_pct
|
|
56929
|
+
}, contextFromPrincipal(principal, body3));
|
|
56930
|
+
return json5({ comment: redactComment3(comment2) }, 201);
|
|
55445
56931
|
}
|
|
55446
56932
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
|
|
55447
56933
|
}
|
|
@@ -55451,36 +56937,36 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55451
56937
|
if (!await store.tasks.get(id))
|
|
55452
56938
|
return error(404, "task not found");
|
|
55453
56939
|
const history = await store.audit.getTaskHistory(id);
|
|
55454
|
-
return
|
|
56940
|
+
return json5({ history, count: history.length });
|
|
55455
56941
|
}
|
|
55456
56942
|
if (action === "lock" || action === "unlock") {
|
|
55457
56943
|
if (method !== "POST")
|
|
55458
56944
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/${action}`);
|
|
55459
|
-
const
|
|
56945
|
+
const body3 = await readJson3(req) ?? {};
|
|
55460
56946
|
if (!await store.tasks.get(id))
|
|
55461
56947
|
return error(404, "task not found");
|
|
55462
56948
|
if (action === "lock") {
|
|
55463
56949
|
if (typeof store.tasks.lock !== "function")
|
|
55464
56950
|
return error(501, "task locking is not supported by this storage backend");
|
|
55465
|
-
const agentId3 =
|
|
55466
|
-
return
|
|
56951
|
+
const agentId3 = body3.agent_id || principal.agent || "todos-serve";
|
|
56952
|
+
return json5({ result: await store.tasks.lock(id, agentId3) });
|
|
55467
56953
|
}
|
|
55468
56954
|
if (typeof store.tasks.unlock !== "function")
|
|
55469
56955
|
return error(501, "task unlocking is not supported by this storage backend");
|
|
55470
|
-
if (
|
|
56956
|
+
if (body3.force === true) {
|
|
55471
56957
|
if (!principal.scopes.includes("todos:*"))
|
|
55472
56958
|
return error(403, "force unlock requires todos:* scope");
|
|
55473
56959
|
const released2 = await store.tasks.unlock(id);
|
|
55474
|
-
return
|
|
56960
|
+
return json5({ success: released2 });
|
|
55475
56961
|
}
|
|
55476
|
-
if (
|
|
56962
|
+
if (body3.agent_id && body3.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
|
|
55477
56963
|
return error(403, "unlock agent_id must match the authenticated agent");
|
|
55478
56964
|
}
|
|
55479
|
-
const agentId2 =
|
|
56965
|
+
const agentId2 = body3.agent_id || principal.agent;
|
|
55480
56966
|
if (!agentId2)
|
|
55481
56967
|
return error(403, "unlock requires an agent-bound key or force=true");
|
|
55482
56968
|
const released = await store.tasks.unlock(id, agentId2);
|
|
55483
|
-
return
|
|
56969
|
+
return json5({ success: released });
|
|
55484
56970
|
}
|
|
55485
56971
|
if (action === "dependencies") {
|
|
55486
56972
|
if (!store.dependencies)
|
|
@@ -55489,16 +56975,16 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55489
56975
|
if (!await store.tasks.get(id))
|
|
55490
56976
|
return error(404, "task not found");
|
|
55491
56977
|
const edges = await store.dependencies.list(id);
|
|
55492
|
-
return
|
|
56978
|
+
return json5(edges);
|
|
55493
56979
|
}
|
|
55494
56980
|
if (method === "POST") {
|
|
55495
|
-
const
|
|
55496
|
-
if (typeof
|
|
56981
|
+
const body3 = await readJson3(req) ?? {};
|
|
56982
|
+
if (typeof body3.depends_on !== "string" || !body3.depends_on.trim()) {
|
|
55497
56983
|
return error(400, "depends_on is required");
|
|
55498
56984
|
}
|
|
55499
56985
|
try {
|
|
55500
|
-
const
|
|
55501
|
-
return
|
|
56986
|
+
const dependency2 = await store.dependencies.add(id, body3.depends_on, contextFromPrincipal(principal));
|
|
56987
|
+
return json5({ dependency: dependency2 }, 201);
|
|
55502
56988
|
} catch (e) {
|
|
55503
56989
|
const msg = e.message || "";
|
|
55504
56990
|
if (msg.includes("not found"))
|
|
@@ -55512,7 +56998,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55512
56998
|
if (!subId)
|
|
55513
56999
|
return error(400, "dependency target id is required (/v1/tasks/:id/dependencies/:dep)");
|
|
55514
57000
|
const removed = await store.dependencies.remove(id, subId);
|
|
55515
|
-
return
|
|
57001
|
+
return json5({ removed });
|
|
55516
57002
|
}
|
|
55517
57003
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/dependencies`);
|
|
55518
57004
|
}
|
|
@@ -55523,23 +57009,23 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55523
57009
|
if (!await store.tasks.get(id))
|
|
55524
57010
|
return error(404, "task not found");
|
|
55525
57011
|
const verifications = await store.verifications.list(id);
|
|
55526
|
-
return
|
|
57012
|
+
return json5({ verifications, count: verifications.length });
|
|
55527
57013
|
}
|
|
55528
57014
|
if (method === "POST") {
|
|
55529
|
-
const
|
|
55530
|
-
if (typeof
|
|
57015
|
+
const body3 = await readJson3(req) ?? {};
|
|
57016
|
+
if (typeof body3.command !== "string" || !body3.command.trim()) {
|
|
55531
57017
|
return error(400, "command is required");
|
|
55532
57018
|
}
|
|
55533
57019
|
try {
|
|
55534
|
-
const
|
|
57020
|
+
const verification2 = await store.verifications.add({
|
|
55535
57021
|
task_id: id,
|
|
55536
|
-
command:
|
|
55537
|
-
status:
|
|
55538
|
-
output_summary:
|
|
55539
|
-
artifact_path:
|
|
55540
|
-
agent_id:
|
|
55541
|
-
}, contextFromPrincipal(principal,
|
|
55542
|
-
return
|
|
57022
|
+
command: body3.command,
|
|
57023
|
+
status: body3.status,
|
|
57024
|
+
output_summary: body3.output_summary,
|
|
57025
|
+
artifact_path: body3.artifact_path,
|
|
57026
|
+
agent_id: body3.agent_id
|
|
57027
|
+
}, contextFromPrincipal(principal, body3));
|
|
57028
|
+
return json5({ verification: verification2 }, 201);
|
|
55543
57029
|
} catch (e) {
|
|
55544
57030
|
const msg = e.message || "";
|
|
55545
57031
|
if (msg.includes("not found"))
|
|
@@ -55556,21 +57042,21 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55556
57042
|
if (!await store.tasks.get(id))
|
|
55557
57043
|
return error(404, "task not found");
|
|
55558
57044
|
const commits = await store.commits.list(id);
|
|
55559
|
-
return
|
|
57045
|
+
return json5({ commits, count: commits.length });
|
|
55560
57046
|
}
|
|
55561
57047
|
if (method === "POST") {
|
|
55562
|
-
const
|
|
55563
|
-
if (typeof
|
|
57048
|
+
const body3 = await readJson3(req) ?? {};
|
|
57049
|
+
if (typeof body3.sha !== "string" || !body3.sha.trim())
|
|
55564
57050
|
return error(400, "sha is required");
|
|
55565
57051
|
try {
|
|
55566
57052
|
const commit = await store.commits.add({
|
|
55567
57053
|
task_id: id,
|
|
55568
|
-
sha:
|
|
55569
|
-
message:
|
|
55570
|
-
author:
|
|
55571
|
-
files_changed: Array.isArray(
|
|
57054
|
+
sha: body3.sha,
|
|
57055
|
+
message: body3.message,
|
|
57056
|
+
author: body3.author,
|
|
57057
|
+
files_changed: Array.isArray(body3.files_changed) ? body3.files_changed : undefined
|
|
55572
57058
|
}, contextFromPrincipal(principal));
|
|
55573
|
-
return
|
|
57059
|
+
return json5({ commit }, 201);
|
|
55574
57060
|
} catch (e) {
|
|
55575
57061
|
const msg = e.message || "";
|
|
55576
57062
|
if (msg.includes("not found"))
|
|
@@ -55587,23 +57073,23 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55587
57073
|
if (!await store.tasks.get(id))
|
|
55588
57074
|
return error(404, "task not found");
|
|
55589
57075
|
const refs = await store.gitRefs.list(id);
|
|
55590
|
-
return
|
|
57076
|
+
return json5({ refs, count: refs.length });
|
|
55591
57077
|
}
|
|
55592
57078
|
if (method === "POST") {
|
|
55593
|
-
const
|
|
55594
|
-
const refType =
|
|
55595
|
-
if (typeof
|
|
57079
|
+
const body3 = await readJson3(req) ?? {};
|
|
57080
|
+
const refType = body3.ref_type === "pull_request" || body3.ref_type === "branch" ? body3.ref_type : "branch";
|
|
57081
|
+
if (typeof body3.name !== "string" || !body3.name.trim())
|
|
55596
57082
|
return error(400, "name is required");
|
|
55597
57083
|
try {
|
|
55598
57084
|
const ref = await store.gitRefs.add({
|
|
55599
57085
|
task_id: id,
|
|
55600
57086
|
ref_type: refType,
|
|
55601
|
-
name:
|
|
55602
|
-
url:
|
|
55603
|
-
provider:
|
|
55604
|
-
metadata:
|
|
57087
|
+
name: body3.name,
|
|
57088
|
+
url: body3.url,
|
|
57089
|
+
provider: body3.provider,
|
|
57090
|
+
metadata: body3.metadata
|
|
55605
57091
|
}, contextFromPrincipal(principal));
|
|
55606
|
-
return
|
|
57092
|
+
return json5({ ref }, 201);
|
|
55607
57093
|
} catch (e) {
|
|
55608
57094
|
const msg = e.message || "";
|
|
55609
57095
|
if (msg.includes("not found"))
|
|
@@ -55616,32 +57102,32 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55616
57102
|
const actionJson = await readOptionalJson(req);
|
|
55617
57103
|
if (!actionJson.ok)
|
|
55618
57104
|
return error(400, "invalid JSON body");
|
|
55619
|
-
const
|
|
55620
|
-
const agentId = typeof
|
|
57105
|
+
const body2 = actionJson.value && typeof actionJson.value === "object" && !Array.isArray(actionJson.value) ? actionJson.value : {};
|
|
57106
|
+
const agentId = typeof body2.agent_id === "string" ? body2.agent_id : principal.agent || "todos-serve";
|
|
55621
57107
|
if (action === "start" && method === "POST") {
|
|
55622
|
-
return
|
|
57108
|
+
return json5({ task: await store.tasks.start(id, agentId) });
|
|
55623
57109
|
}
|
|
55624
57110
|
if (action === "complete" && method === "POST") {
|
|
55625
57111
|
const parsed = validateTaskCompletion(actionJson.value);
|
|
55626
57112
|
if (!parsed.ok)
|
|
55627
57113
|
return error(400, parsed.message);
|
|
55628
|
-
return
|
|
55629
|
-
task: await store.tasks.complete(id, parsed.agentId || principal.agent || "todos-serve", parsed.options, contextFromPrincipal(principal,
|
|
57114
|
+
return json5({
|
|
57115
|
+
task: await store.tasks.complete(id, parsed.agentId || principal.agent || "todos-serve", parsed.options, contextFromPrincipal(principal, body2))
|
|
55630
57116
|
});
|
|
55631
57117
|
}
|
|
55632
57118
|
if (action === "fail" && method === "POST") {
|
|
55633
|
-
return
|
|
57119
|
+
return json5({ result: await store.tasks.fail(id, agentId, typeof body2.reason === "string" ? body2.reason : "failed", {}) });
|
|
55634
57120
|
}
|
|
55635
57121
|
if (action === "claim" && method === "POST") {
|
|
55636
|
-
return
|
|
57122
|
+
return json5({ task: await store.tasks.claimNext(agentId, {}) });
|
|
55637
57123
|
}
|
|
55638
57124
|
return error(404, `unknown task action: ${action}`);
|
|
55639
57125
|
}
|
|
55640
57126
|
if (method === "GET") {
|
|
55641
|
-
let
|
|
55642
|
-
if (!
|
|
57127
|
+
let task3 = await store.tasks.get(id);
|
|
57128
|
+
if (!task3 && typeof store.tasks.resolveRef === "function") {
|
|
55643
57129
|
try {
|
|
55644
|
-
|
|
57130
|
+
task3 = await store.tasks.resolveRef(id);
|
|
55645
57131
|
} catch (e) {
|
|
55646
57132
|
if (e instanceof TaskReferenceAmbiguousError) {
|
|
55647
57133
|
return error(409, e.message, {
|
|
@@ -55656,22 +57142,22 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55656
57142
|
throw e;
|
|
55657
57143
|
}
|
|
55658
57144
|
}
|
|
55659
|
-
return
|
|
57145
|
+
return task3 ? json5({ task: task3 }) : error(404, "task not found");
|
|
55660
57146
|
}
|
|
55661
57147
|
if (method === "PATCH" || method === "PUT") {
|
|
55662
|
-
const
|
|
55663
|
-
if (!
|
|
57148
|
+
const body2 = await readJson3(req);
|
|
57149
|
+
if (!body2)
|
|
55664
57150
|
return error(400, "invalid JSON body");
|
|
55665
57151
|
const current = await store.tasks.get(id);
|
|
55666
57152
|
if (!current)
|
|
55667
57153
|
return error(404, "task not found");
|
|
55668
57154
|
const patch = {
|
|
55669
|
-
...
|
|
55670
|
-
version: typeof
|
|
57155
|
+
...body2,
|
|
57156
|
+
version: typeof body2.version === "number" ? body2.version : current.version
|
|
55671
57157
|
};
|
|
55672
57158
|
try {
|
|
55673
|
-
const
|
|
55674
|
-
return
|
|
57159
|
+
const task3 = await store.tasks.update(id, patch);
|
|
57160
|
+
return task3 ? json5({ task: task3 }) : error(404, "task not found");
|
|
55675
57161
|
} catch (e) {
|
|
55676
57162
|
const msg = e.message || "";
|
|
55677
57163
|
if (msg.includes("version conflict"))
|
|
@@ -55681,7 +57167,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55681
57167
|
}
|
|
55682
57168
|
if (method === "DELETE") {
|
|
55683
57169
|
await store.tasks.delete(id, contextFromPrincipal(principal));
|
|
55684
|
-
return
|
|
57170
|
+
return json5({ deleted: true, id });
|
|
55685
57171
|
}
|
|
55686
57172
|
return error(405, `method ${method} not allowed on /v1/tasks/:id`);
|
|
55687
57173
|
}
|
|
@@ -55689,111 +57175,111 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55689
57175
|
if (!id) {
|
|
55690
57176
|
if (method === "GET") {
|
|
55691
57177
|
const projects = await store.projects.list();
|
|
55692
|
-
return
|
|
57178
|
+
return json5({ projects, count: projects.length });
|
|
55693
57179
|
}
|
|
55694
57180
|
if (method === "POST") {
|
|
55695
|
-
const
|
|
55696
|
-
if (!
|
|
57181
|
+
const body2 = await readJson3(req);
|
|
57182
|
+
if (!body2)
|
|
55697
57183
|
return error(400, "invalid JSON body");
|
|
55698
|
-
const validated = validateProjectCreate(
|
|
57184
|
+
const validated = validateProjectCreate(body2);
|
|
55699
57185
|
if (!validated.ok)
|
|
55700
57186
|
return error(400, validated.message);
|
|
55701
57187
|
const project = await store.projects.create(validated.input, contextFromPrincipal(principal));
|
|
55702
|
-
return
|
|
57188
|
+
return json5({ project }, 201);
|
|
55703
57189
|
}
|
|
55704
57190
|
return error(405, `method ${method} not allowed on /v1/projects`);
|
|
55705
57191
|
}
|
|
55706
57192
|
if (action === "task-list" && subId === "ensure") {
|
|
55707
57193
|
if (method === "GET") {
|
|
55708
|
-
return
|
|
57194
|
+
return json5(await planProjectTaskListEnsure(store, id));
|
|
55709
57195
|
}
|
|
55710
57196
|
if (method !== "POST") {
|
|
55711
57197
|
return error(405, `method ${method} not allowed on /v1/projects/:id/task-list/ensure`);
|
|
55712
57198
|
}
|
|
55713
|
-
const
|
|
55714
|
-
if (!
|
|
57199
|
+
const body2 = await readJson3(req);
|
|
57200
|
+
if (!body2)
|
|
55715
57201
|
return error(400, "invalid JSON body");
|
|
55716
|
-
const unknown = Object.keys(
|
|
57202
|
+
const unknown = Object.keys(body2).find((key2) => !["expected_project_revision", "idempotency_key"].includes(key2));
|
|
55717
57203
|
if (unknown)
|
|
55718
57204
|
return error(400, `unknown task-list ensure field: ${unknown}`);
|
|
55719
|
-
if (typeof
|
|
57205
|
+
if (typeof body2.expected_project_revision !== "string" || !body2.expected_project_revision.trim()) {
|
|
55720
57206
|
return error(400, "expected_project_revision must be a non-empty string from a fresh ensure plan");
|
|
55721
57207
|
}
|
|
55722
|
-
if (
|
|
57208
|
+
if (body2.idempotency_key !== undefined && typeof body2.idempotency_key !== "string") {
|
|
55723
57209
|
return error(400, "idempotency_key must be a string");
|
|
55724
57210
|
}
|
|
55725
57211
|
const result = await applyProjectTaskListEnsure(store, id, {
|
|
55726
|
-
expected_project_revision:
|
|
55727
|
-
...typeof
|
|
57212
|
+
expected_project_revision: body2.expected_project_revision,
|
|
57213
|
+
...typeof body2.idempotency_key === "string" ? { idempotency_key: body2.idempotency_key } : {}
|
|
55728
57214
|
});
|
|
55729
|
-
return
|
|
57215
|
+
return json5(result, result.action === "created" ? 201 : 200);
|
|
55730
57216
|
}
|
|
55731
57217
|
if (action === "task-list" && subId === "rollback") {
|
|
55732
57218
|
if (method !== "POST") {
|
|
55733
57219
|
return error(405, `method ${method} not allowed on /v1/projects/:id/task-list/rollback`);
|
|
55734
57220
|
}
|
|
55735
|
-
const
|
|
55736
|
-
if (!
|
|
57221
|
+
const body2 = await readJson3(req);
|
|
57222
|
+
if (!body2)
|
|
55737
57223
|
return error(400, "invalid JSON body");
|
|
55738
|
-
const unknown = Object.keys(
|
|
57224
|
+
const unknown = Object.keys(body2).find((key2) => !["receipt_id", "expected_task_list_revision"].includes(key2));
|
|
55739
57225
|
if (unknown)
|
|
55740
57226
|
return error(400, `unknown task-list rollback field: ${unknown}`);
|
|
55741
|
-
if (typeof
|
|
57227
|
+
if (typeof body2.receipt_id !== "string" || !body2.receipt_id.trim()) {
|
|
55742
57228
|
return error(400, "receipt_id must be a non-empty string");
|
|
55743
57229
|
}
|
|
55744
|
-
if (typeof
|
|
57230
|
+
if (typeof body2.expected_task_list_revision !== "string" || !body2.expected_task_list_revision.trim()) {
|
|
55745
57231
|
return error(400, "expected_task_list_revision must be a non-empty string from the accepted receipt");
|
|
55746
57232
|
}
|
|
55747
|
-
return
|
|
55748
|
-
receipt_id:
|
|
55749
|
-
expected_task_list_revision:
|
|
57233
|
+
return json5(await rollbackProjectTaskListEnsure(store, id, {
|
|
57234
|
+
receipt_id: body2.receipt_id,
|
|
57235
|
+
expected_task_list_revision: body2.expected_task_list_revision
|
|
55750
57236
|
}));
|
|
55751
57237
|
}
|
|
55752
57238
|
if (action === "rename") {
|
|
55753
57239
|
if (method !== "POST")
|
|
55754
57240
|
return error(405, `method ${method} not allowed on /v1/projects/:id/rename`);
|
|
55755
|
-
const
|
|
55756
|
-
if (!
|
|
57241
|
+
const body2 = await readJson3(req);
|
|
57242
|
+
if (!body2 || typeof body2.new_slug !== "string" || !body2.new_slug.trim() || !normalizeSlug(body2.new_slug)) {
|
|
55757
57243
|
return error(400, "new_slug must be a non-empty string");
|
|
55758
57244
|
}
|
|
55759
|
-
if (
|
|
57245
|
+
if (body2.name !== undefined && (typeof body2.name !== "string" || !body2.name.trim())) {
|
|
55760
57246
|
return error(400, "name must be a non-empty string");
|
|
55761
57247
|
}
|
|
55762
|
-
const unknownField = Object.keys(
|
|
57248
|
+
const unknownField = Object.keys(body2).find((key2) => !["new_slug", "name"].includes(key2));
|
|
55763
57249
|
if (unknownField)
|
|
55764
57250
|
return error(400, `unknown project rename field: ${unknownField}`);
|
|
55765
|
-
return
|
|
57251
|
+
return json5(await store.projects.rename(id, body2, contextFromPrincipal(principal)));
|
|
55766
57252
|
}
|
|
55767
57253
|
if (method === "GET") {
|
|
55768
57254
|
const project = await store.projects.get(id);
|
|
55769
|
-
return project ?
|
|
57255
|
+
return project ? json5({ project }) : error(404, "project not found");
|
|
55770
57256
|
}
|
|
55771
57257
|
if (method === "PATCH" || method === "PUT") {
|
|
55772
|
-
const
|
|
55773
|
-
if (!
|
|
57258
|
+
const body2 = await readJson3(req);
|
|
57259
|
+
if (!body2)
|
|
55774
57260
|
return error(400, "invalid JSON body");
|
|
55775
|
-
const validated = validateProjectPatch(
|
|
57261
|
+
const validated = validateProjectPatch(body2);
|
|
55776
57262
|
if (!validated.ok)
|
|
55777
57263
|
return error(400, validated.message);
|
|
55778
57264
|
if (!await store.projects.get(id))
|
|
55779
57265
|
return error(404, "project not found");
|
|
55780
57266
|
const project = await store.projects.update(id, validated.patch);
|
|
55781
|
-
return
|
|
57267
|
+
return json5({ project });
|
|
55782
57268
|
}
|
|
55783
57269
|
if (method === "DELETE") {
|
|
55784
57270
|
await store.projects.delete(id, contextFromPrincipal(principal));
|
|
55785
|
-
return
|
|
57271
|
+
return json5({ deleted: true, id });
|
|
55786
57272
|
}
|
|
55787
57273
|
return error(405, `method ${method} not allowed on /v1/projects/:id`);
|
|
55788
57274
|
}
|
|
55789
57275
|
if (resource === "plans") {
|
|
55790
57276
|
if (!id && method === "GET") {
|
|
55791
57277
|
const plans = await store.plans.list(url.searchParams.get("project_id") ?? undefined);
|
|
55792
|
-
return
|
|
57278
|
+
return json5({ plans, count: plans.length });
|
|
55793
57279
|
}
|
|
55794
57280
|
if (!id && method === "POST") {
|
|
55795
|
-
const
|
|
55796
|
-
const validated = validatePlanCreate(
|
|
57281
|
+
const body2 = await readJson3(req);
|
|
57282
|
+
const validated = validatePlanCreate(body2);
|
|
55797
57283
|
if (!validated.ok)
|
|
55798
57284
|
return error(400, validated.message);
|
|
55799
57285
|
if (validated.input.slug) {
|
|
@@ -55807,104 +57293,104 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55807
57293
|
}
|
|
55808
57294
|
}
|
|
55809
57295
|
const plan = await store.plans.create(validated.input, contextFromPrincipal(principal, validated.input));
|
|
55810
|
-
return
|
|
57296
|
+
return json5({ plan }, 201);
|
|
55811
57297
|
}
|
|
55812
57298
|
if (id && action === "project-link" && !subId) {
|
|
55813
57299
|
if (method === "GET") {
|
|
55814
57300
|
const projectId = url.searchParams.get("project_id");
|
|
55815
57301
|
if (!projectId?.trim())
|
|
55816
57302
|
return error(400, "project_id query parameter is required");
|
|
55817
|
-
return
|
|
57303
|
+
return json5(await planPlanProjectLink(store, id, projectId));
|
|
55818
57304
|
}
|
|
55819
57305
|
if (method !== "POST")
|
|
55820
57306
|
return error(405, `method ${method} not allowed on /v1/plans/:id/project-link`);
|
|
55821
|
-
const
|
|
55822
|
-
if (!
|
|
57307
|
+
const body2 = await readJson3(req);
|
|
57308
|
+
if (!body2)
|
|
55823
57309
|
return error(400, "invalid JSON body");
|
|
55824
57310
|
const allowed = new Set(["project_id", "expected_plan_revision", "expected_project_revision", "idempotency_key"]);
|
|
55825
|
-
const unknown = Object.keys(
|
|
57311
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55826
57312
|
if (unknown)
|
|
55827
57313
|
return error(400, `unknown plan-project-link field: ${unknown}`);
|
|
55828
57314
|
for (const field of ["project_id", "expected_plan_revision", "expected_project_revision", "idempotency_key"]) {
|
|
55829
|
-
if (typeof
|
|
57315
|
+
if (typeof body2[field] !== "string" || !body2[field].trim()) {
|
|
55830
57316
|
return error(400, `${field} must be a non-empty string`);
|
|
55831
57317
|
}
|
|
55832
57318
|
}
|
|
55833
|
-
const result = await applyPlanProjectLink(store, id,
|
|
55834
|
-
expected_plan_revision:
|
|
55835
|
-
expected_project_revision:
|
|
55836
|
-
idempotency_key:
|
|
57319
|
+
const result = await applyPlanProjectLink(store, id, body2.project_id, {
|
|
57320
|
+
expected_plan_revision: body2.expected_plan_revision,
|
|
57321
|
+
expected_project_revision: body2.expected_project_revision,
|
|
57322
|
+
idempotency_key: body2.idempotency_key
|
|
55837
57323
|
});
|
|
55838
|
-
return
|
|
57324
|
+
return json5(result, result.action === "linked" ? 201 : 200);
|
|
55839
57325
|
}
|
|
55840
57326
|
if (id && action === "project-link" && subId === "rollback") {
|
|
55841
57327
|
if (method !== "POST")
|
|
55842
57328
|
return error(405, `method ${method} not allowed on /v1/plans/:id/project-link/rollback`);
|
|
55843
|
-
const
|
|
55844
|
-
if (!
|
|
57329
|
+
const body2 = await readJson3(req);
|
|
57330
|
+
if (!body2)
|
|
55845
57331
|
return error(400, "invalid JSON body");
|
|
55846
57332
|
const allowed = new Set(["project_id", "receipt_id", "expected_plan_revision"]);
|
|
55847
|
-
const unknown = Object.keys(
|
|
57333
|
+
const unknown = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55848
57334
|
if (unknown)
|
|
55849
57335
|
return error(400, `unknown plan-project-link rollback field: ${unknown}`);
|
|
55850
57336
|
for (const field of ["project_id", "receipt_id", "expected_plan_revision"]) {
|
|
55851
|
-
if (typeof
|
|
57337
|
+
if (typeof body2[field] !== "string" || !body2[field].trim()) {
|
|
55852
57338
|
return error(400, `${field} must be a non-empty string`);
|
|
55853
57339
|
}
|
|
55854
57340
|
}
|
|
55855
|
-
return
|
|
55856
|
-
receipt_id:
|
|
55857
|
-
expected_plan_revision:
|
|
57341
|
+
return json5(await rollbackPlanProjectLink(store, id, body2.project_id, {
|
|
57342
|
+
receipt_id: body2.receipt_id,
|
|
57343
|
+
expected_plan_revision: body2.expected_plan_revision
|
|
55858
57344
|
}));
|
|
55859
57345
|
}
|
|
55860
57346
|
if (id && method === "GET") {
|
|
55861
57347
|
const plan = await store.plans.get(id);
|
|
55862
|
-
return plan ?
|
|
57348
|
+
return plan ? json5({ plan }) : error(404, "plan not found");
|
|
55863
57349
|
}
|
|
55864
57350
|
if (id && (method === "PATCH" || method === "PUT")) {
|
|
55865
|
-
const
|
|
55866
|
-
if (!
|
|
57351
|
+
const body2 = await readJson3(req);
|
|
57352
|
+
if (!body2 || Object.keys(body2).length === 0)
|
|
55867
57353
|
return error(400, "plan patch is required");
|
|
55868
57354
|
const allowed = new Set(["name", "slug", "description", "status", "task_list_id", "agent_id"]);
|
|
55869
|
-
const unknownField = Object.keys(
|
|
57355
|
+
const unknownField = Object.keys(body2).find((key2) => !allowed.has(key2));
|
|
55870
57356
|
if (unknownField)
|
|
55871
57357
|
return error(400, `unknown plan field: ${unknownField}`);
|
|
55872
57358
|
for (const field of ["name", "slug", "task_list_id", "agent_id"]) {
|
|
55873
|
-
if (
|
|
57359
|
+
if (body2[field] !== undefined && (typeof body2[field] !== "string" || !body2[field].trim())) {
|
|
55874
57360
|
return error(400, `${field} must be a non-empty string`);
|
|
55875
57361
|
}
|
|
55876
57362
|
}
|
|
55877
|
-
if (typeof
|
|
55878
|
-
const slug = normalizeSlug(
|
|
57363
|
+
if (typeof body2.slug === "string") {
|
|
57364
|
+
const slug = normalizeSlug(body2.slug);
|
|
55879
57365
|
if (!slug)
|
|
55880
57366
|
return error(400, "slug must produce a non-empty canonical slug");
|
|
55881
|
-
|
|
57367
|
+
body2.slug = slug;
|
|
55882
57368
|
}
|
|
55883
|
-
if (
|
|
57369
|
+
if (body2.description !== undefined && typeof body2.description !== "string") {
|
|
55884
57370
|
return error(400, "description must be a string");
|
|
55885
57371
|
}
|
|
55886
|
-
if (
|
|
57372
|
+
if (body2.status !== undefined && (typeof body2.status !== "string" || !["active", "completed", "archived"].includes(body2.status))) {
|
|
55887
57373
|
return error(400, "status must be active, completed, or archived");
|
|
55888
57374
|
}
|
|
55889
57375
|
const existing = await store.plans.get(id);
|
|
55890
57376
|
if (!existing)
|
|
55891
57377
|
return error(404, "plan not found");
|
|
55892
|
-
if (typeof
|
|
55893
|
-
const duplicate = (await store.plans.list(existing.project_id ?? undefined)).find((plan2) => plan2.id !== id && plan2.project_id === existing.project_id && plan2.slug ===
|
|
57378
|
+
if (typeof body2.slug === "string") {
|
|
57379
|
+
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);
|
|
55894
57380
|
if (duplicate) {
|
|
55895
|
-
return error(409, `Plan slug already exists in this scope: ${
|
|
57381
|
+
return error(409, `Plan slug already exists in this scope: ${body2.slug}`, {
|
|
55896
57382
|
code: "PLAN_SLUG_CONFLICT",
|
|
55897
57383
|
conflict: true
|
|
55898
57384
|
});
|
|
55899
57385
|
}
|
|
55900
57386
|
}
|
|
55901
|
-
const plan = await store.plans.update(id,
|
|
55902
|
-
return
|
|
57387
|
+
const plan = await store.plans.update(id, body2);
|
|
57388
|
+
return json5({ plan });
|
|
55903
57389
|
}
|
|
55904
57390
|
if (id && method === "DELETE") {
|
|
55905
57391
|
if (!await store.plans.delete(id, contextFromPrincipal(principal)))
|
|
55906
57392
|
return error(404, "plan not found");
|
|
55907
|
-
return
|
|
57393
|
+
return json5({ deleted: true, id });
|
|
55908
57394
|
}
|
|
55909
57395
|
if (id)
|
|
55910
57396
|
return error(405, `method ${method} not allowed on /v1/plans/:id`);
|
|
@@ -55913,50 +57399,50 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55913
57399
|
if (!id && method === "GET") {
|
|
55914
57400
|
const projectId = url.searchParams.get("project_id");
|
|
55915
57401
|
const templates = (await store.templates.list()).filter((template) => projectId === null || template.project_id === projectId);
|
|
55916
|
-
return
|
|
57402
|
+
return json5({ templates, count: templates.length });
|
|
55917
57403
|
}
|
|
55918
57404
|
if (!id && method === "POST") {
|
|
55919
|
-
const
|
|
55920
|
-
const validated = validateTemplateCreate(
|
|
57405
|
+
const body2 = await readJson3(req);
|
|
57406
|
+
const validated = validateTemplateCreate(body2);
|
|
55921
57407
|
if (!validated.ok)
|
|
55922
57408
|
return error(400, validated.message);
|
|
55923
57409
|
const template = await store.templates.create(validated.input, contextFromPrincipal(principal));
|
|
55924
|
-
return
|
|
57410
|
+
return json5({ template: await store.templates.getWithTasks(template.id) }, 201);
|
|
55925
57411
|
}
|
|
55926
57412
|
if (!id)
|
|
55927
57413
|
return error(405, `method ${method} not allowed on /v1/templates`);
|
|
55928
57414
|
if (method === "GET") {
|
|
55929
57415
|
const template = await store.templates.getWithTasks(id);
|
|
55930
|
-
return template ?
|
|
57416
|
+
return template ? json5({ template }) : error(404, "template not found");
|
|
55931
57417
|
}
|
|
55932
57418
|
if (method === "PATCH" || method === "PUT") {
|
|
55933
|
-
const
|
|
55934
|
-
const validated = validateTemplatePatch(
|
|
57419
|
+
const body2 = await readJson3(req);
|
|
57420
|
+
const validated = validateTemplatePatch(body2);
|
|
55935
57421
|
if (!validated.ok)
|
|
55936
57422
|
return error(400, validated.message);
|
|
55937
57423
|
const template = await store.templates.update(id, validated.patch, contextFromPrincipal(principal));
|
|
55938
|
-
return template ?
|
|
57424
|
+
return template ? json5({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
|
|
55939
57425
|
}
|
|
55940
57426
|
if (method === "DELETE") {
|
|
55941
57427
|
const deleted = await store.templates.delete(id, contextFromPrincipal(principal));
|
|
55942
|
-
return deleted ?
|
|
57428
|
+
return deleted ? json5({ deleted: true, id }) : error(404, "template not found");
|
|
55943
57429
|
}
|
|
55944
57430
|
return error(405, `method ${method} not allowed on /v1/templates/:id`);
|
|
55945
57431
|
}
|
|
55946
57432
|
if (resource === "agents") {
|
|
55947
57433
|
if (!id && method === "GET") {
|
|
55948
57434
|
const agents = await store.agents.list();
|
|
55949
|
-
return
|
|
57435
|
+
return json5({ agents, count: agents.length });
|
|
55950
57436
|
}
|
|
55951
57437
|
if (!id && method === "POST") {
|
|
55952
|
-
const
|
|
55953
|
-
if (!
|
|
57438
|
+
const body2 = await readJson3(req);
|
|
57439
|
+
if (!body2 || typeof body2.name !== "string" || !body2.name.trim())
|
|
55954
57440
|
return error(400, "name is required");
|
|
55955
|
-
const result = await store.agents.register(
|
|
57441
|
+
const result = await store.agents.register(body2, contextFromPrincipal(principal));
|
|
55956
57442
|
if (result && typeof result === "object" && "conflict" in result) {
|
|
55957
57443
|
return error(409, result.message ?? "agent name conflict", { conflict: true });
|
|
55958
57444
|
}
|
|
55959
|
-
return
|
|
57445
|
+
return json5({ agent: result }, 201);
|
|
55960
57446
|
}
|
|
55961
57447
|
if (id && action === "heartbeat") {
|
|
55962
57448
|
if (method !== "POST")
|
|
@@ -55965,7 +57451,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55965
57451
|
return error(501, "agent heartbeat is not supported by this storage backend");
|
|
55966
57452
|
}
|
|
55967
57453
|
const agent = await store.agents.heartbeat(id, contextFromPrincipal(principal));
|
|
55968
|
-
return agent ?
|
|
57454
|
+
return agent ? json5({ agent }) : error(404, "agent not found");
|
|
55969
57455
|
}
|
|
55970
57456
|
if (id && action === "release") {
|
|
55971
57457
|
if (method !== "POST")
|
|
@@ -55973,18 +57459,18 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55973
57459
|
if (typeof store.agents.release !== "function") {
|
|
55974
57460
|
return error(501, "agent release is not supported by this storage backend");
|
|
55975
57461
|
}
|
|
55976
|
-
const
|
|
55977
|
-
const result = await store.agents.release(id,
|
|
57462
|
+
const body2 = await readJson3(req) ?? {};
|
|
57463
|
+
const result = await store.agents.release(id, body2.session_id, contextFromPrincipal(principal));
|
|
55978
57464
|
if (!result)
|
|
55979
57465
|
return error(404, "agent not found");
|
|
55980
57466
|
if (!result.released) {
|
|
55981
57467
|
return error(409, "release denied: session_id does not match agent's current session", { released: false });
|
|
55982
57468
|
}
|
|
55983
|
-
return
|
|
57469
|
+
return json5({ agent: result.agent, released: true });
|
|
55984
57470
|
}
|
|
55985
57471
|
if (id && method === "GET") {
|
|
55986
57472
|
const agent = await store.agents.get(id);
|
|
55987
|
-
return agent ?
|
|
57473
|
+
return agent ? json5({ agent }) : error(404, "agent not found");
|
|
55988
57474
|
}
|
|
55989
57475
|
}
|
|
55990
57476
|
if (resource === "activity" && !id) {
|
|
@@ -55993,66 +57479,66 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
55993
57479
|
const limitParam = url.searchParams.get("limit");
|
|
55994
57480
|
const limit = limitParam ? Math.max(1, Math.min(1e4, Number(limitParam) || 50)) : 50;
|
|
55995
57481
|
const activity = await store.audit.getRecentActivity(limit);
|
|
55996
|
-
return
|
|
57482
|
+
return json5({ activity, count: activity.length });
|
|
55997
57483
|
}
|
|
55998
57484
|
if (resource === "task-lists") {
|
|
55999
57485
|
if (!id && method === "GET") {
|
|
56000
57486
|
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
56001
57487
|
const taskLists = await store.taskLists.list(projectId);
|
|
56002
|
-
return
|
|
57488
|
+
return json5({ task_lists: taskLists, count: taskLists.length });
|
|
56003
57489
|
}
|
|
56004
57490
|
if (!id && method === "POST") {
|
|
56005
|
-
const
|
|
56006
|
-
if (!
|
|
57491
|
+
const body2 = await readJson3(req);
|
|
57492
|
+
if (!body2 || typeof body2.name !== "string" || !body2.name.trim())
|
|
56007
57493
|
return error(400, "name is required");
|
|
56008
|
-
const unknownField = Object.keys(
|
|
57494
|
+
const unknownField = Object.keys(body2).find((key2) => !["name", "slug", "project_id", "description", "metadata"].includes(key2));
|
|
56009
57495
|
if (unknownField)
|
|
56010
57496
|
return error(400, `unsupported task-list create field: ${unknownField}`);
|
|
56011
|
-
if (
|
|
57497
|
+
if (body2.slug !== undefined && typeof body2.slug !== "string")
|
|
56012
57498
|
return error(400, "slug must be a string");
|
|
56013
|
-
if (
|
|
57499
|
+
if (body2.project_id !== undefined && (typeof body2.project_id !== "string" || !body2.project_id.trim()))
|
|
56014
57500
|
return error(400, "project_id must be a non-empty string");
|
|
56015
|
-
if (
|
|
57501
|
+
if (body2.description !== undefined && typeof body2.description !== "string")
|
|
56016
57502
|
return error(400, "description must be a string");
|
|
56017
|
-
if (
|
|
57503
|
+
if (body2.metadata !== undefined && (!body2.metadata || typeof body2.metadata !== "object" || Array.isArray(body2.metadata))) {
|
|
56018
57504
|
return error(400, "metadata must be an object");
|
|
56019
57505
|
}
|
|
56020
|
-
if (!normalizeSlug(
|
|
57506
|
+
if (!normalizeSlug(body2.slug === undefined ? body2.name : body2.slug)) {
|
|
56021
57507
|
return error(400, "task-list slug must be non-empty kebab-case");
|
|
56022
57508
|
}
|
|
56023
|
-
const taskList = await store.taskLists.create(
|
|
56024
|
-
return
|
|
57509
|
+
const taskList = await store.taskLists.create(body2, contextFromPrincipal(principal));
|
|
57510
|
+
return json5({ task_list: taskList }, 201);
|
|
56025
57511
|
}
|
|
56026
57512
|
if (id && method === "GET") {
|
|
56027
57513
|
const taskList = await store.taskLists.get(id);
|
|
56028
|
-
return taskList ?
|
|
57514
|
+
return taskList ? json5({ task_list: taskList }) : error(404, "task list not found");
|
|
56029
57515
|
}
|
|
56030
57516
|
if (id && (method === "PATCH" || method === "PUT")) {
|
|
56031
|
-
const
|
|
56032
|
-
if (!
|
|
57517
|
+
const body2 = await readJson3(req);
|
|
57518
|
+
if (!body2)
|
|
56033
57519
|
return error(400, "invalid JSON body");
|
|
56034
|
-
const unknownField = Object.keys(
|
|
57520
|
+
const unknownField = Object.keys(body2).find((key2) => !["slug", "name", "description", "metadata"].includes(key2));
|
|
56035
57521
|
if (unknownField)
|
|
56036
57522
|
return error(400, `unsupported task-list update field: ${unknownField}`);
|
|
56037
|
-
if (Object.keys(
|
|
57523
|
+
if (Object.keys(body2).length === 0)
|
|
56038
57524
|
return error(400, "task-list update must not be empty");
|
|
56039
|
-
if (
|
|
57525
|
+
if (body2.slug !== undefined && (typeof body2.slug !== "string" || !normalizeSlug(body2.slug)))
|
|
56040
57526
|
return error(400, "slug must be a non-empty string");
|
|
56041
|
-
if (
|
|
57527
|
+
if (body2.name !== undefined && (typeof body2.name !== "string" || !body2.name.trim()))
|
|
56042
57528
|
return error(400, "name must be a non-empty string");
|
|
56043
|
-
if (
|
|
57529
|
+
if (body2.description !== undefined && typeof body2.description !== "string")
|
|
56044
57530
|
return error(400, "description must be a string");
|
|
56045
|
-
if (
|
|
57531
|
+
if (body2.metadata !== undefined && (!body2.metadata || typeof body2.metadata !== "object" || Array.isArray(body2.metadata))) {
|
|
56046
57532
|
return error(400, "metadata must be an object");
|
|
56047
57533
|
}
|
|
56048
57534
|
if (!await store.taskLists.get(id))
|
|
56049
57535
|
return error(404, "task list not found");
|
|
56050
|
-
const taskList = await store.taskLists.update(id,
|
|
56051
|
-
return
|
|
57536
|
+
const taskList = await store.taskLists.update(id, body2);
|
|
57537
|
+
return json5({ task_list: taskList });
|
|
56052
57538
|
}
|
|
56053
57539
|
if (id && method === "DELETE") {
|
|
56054
57540
|
const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
|
|
56055
|
-
return deleted ?
|
|
57541
|
+
return deleted ? json5({ deleted: true, id }) : error(404, "task list not found");
|
|
56056
57542
|
}
|
|
56057
57543
|
return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
|
|
56058
57544
|
}
|
|
@@ -56063,7 +57549,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56063
57549
|
return error(501, "dependency edge listing is not supported by this storage backend");
|
|
56064
57550
|
}
|
|
56065
57551
|
const dependencies2 = await store.dependencies.listAll();
|
|
56066
|
-
return
|
|
57552
|
+
return json5({ dependencies: dependencies2, count: dependencies2.length });
|
|
56067
57553
|
}
|
|
56068
57554
|
if (resource === "commits" && id) {
|
|
56069
57555
|
if (method !== "GET")
|
|
@@ -56071,7 +57557,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56071
57557
|
if (!store.commits)
|
|
56072
57558
|
return error(501, "commit links are not supported by this storage backend");
|
|
56073
57559
|
const commit = await store.commits.find(id);
|
|
56074
|
-
return
|
|
57560
|
+
return json5({ commit: commit ?? null });
|
|
56075
57561
|
}
|
|
56076
57562
|
if (resource === "refs" && id) {
|
|
56077
57563
|
if (method !== "GET")
|
|
@@ -56085,7 +57571,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56085
57571
|
return error(400, "ref path segment has invalid percent encoding");
|
|
56086
57572
|
}
|
|
56087
57573
|
const refs = await store.gitRefs.find(decodedRef);
|
|
56088
|
-
return
|
|
57574
|
+
return json5({ refs, count: refs.length });
|
|
56089
57575
|
}
|
|
56090
57576
|
if (resource === "next" && !id) {
|
|
56091
57577
|
if (method !== "GET")
|
|
@@ -56096,8 +57582,8 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56096
57582
|
...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
|
|
56097
57583
|
...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {}
|
|
56098
57584
|
};
|
|
56099
|
-
const
|
|
56100
|
-
return
|
|
57585
|
+
const task3 = await store.tasks.getNext(agent, filters);
|
|
57586
|
+
return json5({ task: task3 ?? null });
|
|
56101
57587
|
}
|
|
56102
57588
|
if (resource === "stats" && method === "GET") {
|
|
56103
57589
|
const [tasks, tasksAll, projects] = await Promise.all([
|
|
@@ -56105,7 +57591,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56105
57591
|
store.tasks.count({ include_subtasks: true }),
|
|
56106
57592
|
store.projects.list()
|
|
56107
57593
|
]);
|
|
56108
|
-
return
|
|
57594
|
+
return json5({ tasks, tasks_all: tasksAll, subtasks: tasksAll - tasks, projects: projects.length });
|
|
56109
57595
|
}
|
|
56110
57596
|
if (resource === "integrity" && !id) {
|
|
56111
57597
|
if (method !== "GET")
|
|
@@ -56114,7 +57600,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56114
57600
|
return error(501, "referential-integrity reporting is not supported by this storage backend");
|
|
56115
57601
|
}
|
|
56116
57602
|
const integrity = await store.integrity.report();
|
|
56117
|
-
return
|
|
57603
|
+
return json5({ integrity });
|
|
56118
57604
|
}
|
|
56119
57605
|
if (resource === "import") {
|
|
56120
57606
|
if (method !== "POST")
|
|
@@ -56131,17 +57617,17 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56131
57617
|
return error(400, "empty snapshot: provide at least one record array (tasks/projects/plans/...)");
|
|
56132
57618
|
}
|
|
56133
57619
|
const result = await store.sync.importSnapshot(snapshot, contextFromPrincipal(principal));
|
|
56134
|
-
return
|
|
57620
|
+
return json5({ result, received });
|
|
56135
57621
|
}
|
|
56136
57622
|
return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
56137
57623
|
} catch (e) {
|
|
56138
57624
|
if (e instanceof PlanProjectLinkError) {
|
|
56139
|
-
const
|
|
56140
|
-
return error(
|
|
57625
|
+
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;
|
|
57626
|
+
return error(status2, e.message, { code: e.code, conflict: status2 === 409, ...e.details });
|
|
56141
57627
|
}
|
|
56142
57628
|
if (e instanceof ProjectTaskListEnsureError) {
|
|
56143
|
-
const
|
|
56144
|
-
return error(
|
|
57629
|
+
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;
|
|
57630
|
+
return error(status2, e.message, { code: e.code, conflict: status2 === 409, ...e.details });
|
|
56145
57631
|
}
|
|
56146
57632
|
if (e instanceof TaskReferenceAmbiguousError) {
|
|
56147
57633
|
return error(409, e.message, {
|
|
@@ -56165,21 +57651,22 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
56165
57651
|
return error(500, e.message || "internal error");
|
|
56166
57652
|
}
|
|
56167
57653
|
}
|
|
56168
|
-
var
|
|
57654
|
+
var JSON_HEADERS4, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500, RFC3339_DATE_TIME;
|
|
56169
57655
|
var init_v1 = __esm(() => {
|
|
56170
57656
|
init_types();
|
|
56171
57657
|
init_cloud();
|
|
56172
57658
|
init_pr_groups();
|
|
56173
57659
|
init_project_registration();
|
|
57660
|
+
init_task_manifest();
|
|
56174
57661
|
init_redaction();
|
|
56175
57662
|
init_project_task_list_ensure();
|
|
56176
57663
|
init_plan_project_link();
|
|
56177
|
-
|
|
57664
|
+
JSON_HEADERS4 = { "Content-Type": "application/json" };
|
|
56178
57665
|
RFC3339_DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/;
|
|
56179
57666
|
});
|
|
56180
57667
|
|
|
56181
57668
|
// src/pr-groups/sqlite.ts
|
|
56182
|
-
function
|
|
57669
|
+
function parseJson3(value) {
|
|
56183
57670
|
return JSON.parse(value);
|
|
56184
57671
|
}
|
|
56185
57672
|
function groupFromRow2(row) {
|
|
@@ -56206,9 +57693,9 @@ function eventFromRow2(row) {
|
|
|
56206
57693
|
sequence: Number(row["sequence"]),
|
|
56207
57694
|
pr_number: row["pr_number"] === null ? null : Number(row["pr_number"]),
|
|
56208
57695
|
repair_cycle: row["repair_cycle"] === null ? null : Number(row["repair_cycle"]),
|
|
56209
|
-
ci_proof: row["ci_proof"] ?
|
|
56210
|
-
cleanup_proof: row["cleanup_proof"] ?
|
|
56211
|
-
metadata:
|
|
57696
|
+
ci_proof: row["ci_proof"] ? parseJson3(String(row["ci_proof"])) : null,
|
|
57697
|
+
cleanup_proof: row["cleanup_proof"] ? parseJson3(String(row["cleanup_proof"])) : null,
|
|
57698
|
+
metadata: parseJson3(String(row["metadata"] ?? "{}"))
|
|
56212
57699
|
};
|
|
56213
57700
|
}
|
|
56214
57701
|
|
|
@@ -56272,8 +57759,8 @@ class SqlitePrGroupTransaction {
|
|
|
56272
57759
|
WHERE id = ?
|
|
56273
57760
|
`).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);
|
|
56274
57761
|
}
|
|
56275
|
-
async getEventByIdempotency(groupId,
|
|
56276
|
-
const row = this.db.query("SELECT * FROM pr_group_events WHERE group_id = ? AND idempotency_key = ? LIMIT 1").get(groupId,
|
|
57762
|
+
async getEventByIdempotency(groupId, key2) {
|
|
57763
|
+
const row = this.db.query("SELECT * FROM pr_group_events WHERE group_id = ? AND idempotency_key = ? LIMIT 1").get(groupId, key2);
|
|
56277
57764
|
return row ? eventFromRow2(row) : null;
|
|
56278
57765
|
}
|
|
56279
57766
|
async findEventByReceiptKey(receiptKey) {
|
|
@@ -56283,14 +57770,14 @@ class SqlitePrGroupTransaction {
|
|
|
56283
57770
|
async findEvent(groupId, filters) {
|
|
56284
57771
|
const clauses = ["group_id = ?", "event_type = ?"];
|
|
56285
57772
|
const values = [groupId, filters.event_type];
|
|
56286
|
-
for (const
|
|
56287
|
-
if (!(
|
|
57773
|
+
for (const key2 of ["attempt_id", "head_sha", "outcome", "receipt_key"]) {
|
|
57774
|
+
if (!(key2 in filters))
|
|
56288
57775
|
continue;
|
|
56289
|
-
if (filters[
|
|
56290
|
-
clauses.push(`${
|
|
57776
|
+
if (filters[key2] === null)
|
|
57777
|
+
clauses.push(`${key2} IS NULL`);
|
|
56291
57778
|
else {
|
|
56292
|
-
clauses.push(`${
|
|
56293
|
-
values.push(String(filters[
|
|
57779
|
+
clauses.push(`${key2} = ?`);
|
|
57780
|
+
values.push(String(filters[key2]));
|
|
56294
57781
|
}
|
|
56295
57782
|
}
|
|
56296
57783
|
const row = this.db.query(`SELECT * FROM pr_group_events WHERE ${clauses.join(" AND ")} ORDER BY sequence DESC LIMIT 1`).get(...values);
|
|
@@ -56385,7 +57872,7 @@ class SqlitePrGroupLedgerPersistence {
|
|
|
56385
57872
|
}
|
|
56386
57873
|
}
|
|
56387
57874
|
var sqliteTransactionTails2;
|
|
56388
|
-
var
|
|
57875
|
+
var init_sqlite3 = __esm(() => {
|
|
56389
57876
|
sqliteTransactionTails2 = new WeakMap;
|
|
56390
57877
|
});
|
|
56391
57878
|
|
|
@@ -56416,10 +57903,10 @@ function createLocalPrGroupLedger(db = getDatabase()) {
|
|
|
56416
57903
|
var init_pr_groups2 = __esm(() => {
|
|
56417
57904
|
init_database();
|
|
56418
57905
|
init_ledger();
|
|
56419
|
-
|
|
57906
|
+
init_sqlite3();
|
|
56420
57907
|
init_types3();
|
|
56421
57908
|
init_ledger();
|
|
56422
|
-
|
|
57909
|
+
init_sqlite3();
|
|
56423
57910
|
init_http_client();
|
|
56424
57911
|
init_postgres();
|
|
56425
57912
|
});
|
|
@@ -56430,7 +57917,7 @@ __export(exports_serve, {
|
|
|
56430
57917
|
taskToSummary: () => taskToSummary,
|
|
56431
57918
|
startServer: () => startServer,
|
|
56432
57919
|
serveStaticFile: () => serveStaticFile,
|
|
56433
|
-
json: () =>
|
|
57920
|
+
json: () => json3,
|
|
56434
57921
|
checkAuth: () => checkAuth,
|
|
56435
57922
|
SECURITY_HEADERS: () => SECURITY_HEADERS,
|
|
56436
57923
|
MIME_TYPES: () => MIME_TYPES
|
|
@@ -56516,9 +58003,9 @@ function checkRateLimit(ip) {
|
|
|
56516
58003
|
}
|
|
56517
58004
|
return { allowed: true };
|
|
56518
58005
|
}
|
|
56519
|
-
function
|
|
58006
|
+
function json3(data, status2 = 200, headers) {
|
|
56520
58007
|
return new Response(JSON.stringify(data), {
|
|
56521
|
-
status,
|
|
58008
|
+
status: status2,
|
|
56522
58009
|
headers: {
|
|
56523
58010
|
"Content-Type": "application/json",
|
|
56524
58011
|
...SECURITY_HEADERS,
|
|
@@ -56538,29 +58025,29 @@ function serveStaticFile(filePath) {
|
|
|
56538
58025
|
}
|
|
56539
58026
|
});
|
|
56540
58027
|
}
|
|
56541
|
-
function taskToSummary(
|
|
58028
|
+
function taskToSummary(task3, fields) {
|
|
56542
58029
|
const full = {
|
|
56543
|
-
id:
|
|
56544
|
-
short_id:
|
|
56545
|
-
title:
|
|
56546
|
-
description:
|
|
56547
|
-
status:
|
|
56548
|
-
priority:
|
|
56549
|
-
project_id:
|
|
56550
|
-
plan_id:
|
|
56551
|
-
task_list_id:
|
|
56552
|
-
agent_id:
|
|
56553
|
-
assigned_to:
|
|
56554
|
-
working_dir:
|
|
56555
|
-
locked_by:
|
|
56556
|
-
tags:
|
|
56557
|
-
metadata:
|
|
56558
|
-
version:
|
|
56559
|
-
created_at:
|
|
56560
|
-
updated_at:
|
|
56561
|
-
completed_at:
|
|
56562
|
-
due_at:
|
|
56563
|
-
recurrence_rule:
|
|
58030
|
+
id: task3.id,
|
|
58031
|
+
short_id: task3.short_id,
|
|
58032
|
+
title: task3.title,
|
|
58033
|
+
description: task3.description,
|
|
58034
|
+
status: task3.status,
|
|
58035
|
+
priority: task3.priority,
|
|
58036
|
+
project_id: task3.project_id,
|
|
58037
|
+
plan_id: task3.plan_id,
|
|
58038
|
+
task_list_id: task3.task_list_id,
|
|
58039
|
+
agent_id: task3.agent_id,
|
|
58040
|
+
assigned_to: task3.assigned_to,
|
|
58041
|
+
working_dir: task3.working_dir,
|
|
58042
|
+
locked_by: task3.locked_by,
|
|
58043
|
+
tags: task3.tags,
|
|
58044
|
+
metadata: task3.metadata,
|
|
58045
|
+
version: task3.version,
|
|
58046
|
+
created_at: task3.created_at,
|
|
58047
|
+
updated_at: task3.updated_at,
|
|
58048
|
+
completed_at: task3.completed_at,
|
|
58049
|
+
due_at: task3.due_at,
|
|
58050
|
+
recurrence_rule: task3.recurrence_rule
|
|
56564
58051
|
};
|
|
56565
58052
|
if (!fields || fields.length === 0)
|
|
56566
58053
|
return full;
|
|
@@ -56661,7 +58148,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56661
58148
|
"Access-Control-Allow-Headers": "Content-Type, X-API-Key, Authorization",
|
|
56662
58149
|
Vary: "Origin"
|
|
56663
58150
|
} : undefined;
|
|
56664
|
-
const jsonWithCors = (data,
|
|
58151
|
+
const jsonWithCors = (data, status2 = 200) => json3(data, status2, corsHeaders);
|
|
56665
58152
|
if (method === "OPTIONS") {
|
|
56666
58153
|
return new Response(null, {
|
|
56667
58154
|
headers: corsHeaders || {
|
|
@@ -56742,13 +58229,13 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56742
58229
|
return res;
|
|
56743
58230
|
}
|
|
56744
58231
|
if (path === "/api/health" && method === "GET") {
|
|
56745
|
-
return handleHealth(ctx,
|
|
58232
|
+
return handleHealth(ctx, json3);
|
|
56746
58233
|
}
|
|
56747
58234
|
if (path === "/api/headless" && method === "GET") {
|
|
56748
|
-
return handleHeadlessBoundary(ctx,
|
|
58235
|
+
return handleHeadlessBoundary(ctx, json3);
|
|
56749
58236
|
}
|
|
56750
58237
|
if (path === "/api/stats" && method === "GET") {
|
|
56751
|
-
return handleStats(ctx,
|
|
58238
|
+
return handleStats(ctx, json3);
|
|
56752
58239
|
}
|
|
56753
58240
|
if (path === "/api/tasks" && method === "GET") {
|
|
56754
58241
|
return handleListTasks(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -56763,16 +58250,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56763
58250
|
return handleTasksExport(req, url, ctx, jsonWithCors, taskToSummary);
|
|
56764
58251
|
}
|
|
56765
58252
|
if (path === "/api/tasks/bulk" && method === "POST") {
|
|
56766
|
-
return handleTasksBulk(req, ctx,
|
|
58253
|
+
return handleTasksBulk(req, ctx, json3);
|
|
56767
58254
|
}
|
|
56768
58255
|
if (path === "/api/tasks/status" && method === "GET") {
|
|
56769
|
-
return handleTasksStatus(req, url, ctx,
|
|
58256
|
+
return handleTasksStatus(req, url, ctx, json3);
|
|
56770
58257
|
}
|
|
56771
58258
|
if (path === "/api/tasks/next" && method === "GET") {
|
|
56772
58259
|
return handleTasksNext(req, url, ctx, jsonWithCors, taskToSummary);
|
|
56773
58260
|
}
|
|
56774
58261
|
if (path === "/api/tasks/active" && method === "GET") {
|
|
56775
|
-
return handleTasksActive(req, url, ctx,
|
|
58262
|
+
return handleTasksActive(req, url, ctx, json3);
|
|
56776
58263
|
}
|
|
56777
58264
|
if (path === "/api/tasks/stale" && method === "GET") {
|
|
56778
58265
|
return handleTasksStale(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -56785,11 +58272,11 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56785
58272
|
}
|
|
56786
58273
|
const attachmentsMatch = path.match(/^\/api\/tasks\/([^/]+)\/attachments$/);
|
|
56787
58274
|
if (attachmentsMatch && method === "GET") {
|
|
56788
|
-
return handleTaskAttachments(attachmentsMatch[1], ctx,
|
|
58275
|
+
return handleTaskAttachments(attachmentsMatch[1], ctx, json3);
|
|
56789
58276
|
}
|
|
56790
58277
|
const progressMatch = path.match(/^\/api\/tasks\/([^/]+)\/progress$/);
|
|
56791
58278
|
if (progressMatch) {
|
|
56792
|
-
const res = await handleTaskProgress(progressMatch[1], req, method, ctx,
|
|
58279
|
+
const res = await handleTaskProgress(progressMatch[1], req, method, ctx, json3, url);
|
|
56793
58280
|
if (res !== null)
|
|
56794
58281
|
return res;
|
|
56795
58282
|
}
|
|
@@ -56803,7 +58290,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56803
58290
|
return handlePatchTask(id, req, ctx, jsonWithCors, taskToSummary);
|
|
56804
58291
|
}
|
|
56805
58292
|
if (method === "DELETE") {
|
|
56806
|
-
return handleDeleteTask(id, ctx,
|
|
58293
|
+
return handleDeleteTask(id, ctx, json3);
|
|
56807
58294
|
}
|
|
56808
58295
|
}
|
|
56809
58296
|
const startMatch = path.match(/^\/api\/tasks\/([^/]+)\/start$/);
|
|
@@ -56819,7 +58306,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56819
58306
|
return handleCompleteTask(completeMatch[1], ctx, jsonWithCors, taskToSummary);
|
|
56820
58307
|
}
|
|
56821
58308
|
if (path === "/api/projects" && method === "GET") {
|
|
56822
|
-
return handleListProjects(url, ctx,
|
|
58309
|
+
return handleListProjects(url, ctx, json3);
|
|
56823
58310
|
}
|
|
56824
58311
|
if (path === "/api/agents/me" && method === "GET") {
|
|
56825
58312
|
return handleAgentMe(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -56832,92 +58319,92 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56832
58319
|
return handleClaimTask(req, ctx, jsonWithCors, taskToSummary);
|
|
56833
58320
|
}
|
|
56834
58321
|
if (path === "/api/orgs" && method === "GET") {
|
|
56835
|
-
return handleListOrgs(ctx,
|
|
58322
|
+
return handleListOrgs(ctx, json3);
|
|
56836
58323
|
}
|
|
56837
58324
|
if (path === "/api/orgs" && method === "POST") {
|
|
56838
|
-
return handleCreateOrg(req, ctx,
|
|
58325
|
+
return handleCreateOrg(req, ctx, json3);
|
|
56839
58326
|
}
|
|
56840
58327
|
const orgMatch = path.match(/^\/api\/orgs\/([^/]+)$/);
|
|
56841
58328
|
if (orgMatch && method === "PATCH") {
|
|
56842
|
-
return handleUpdateOrg(orgMatch[1], req, ctx,
|
|
58329
|
+
return handleUpdateOrg(orgMatch[1], req, ctx, json3);
|
|
56843
58330
|
}
|
|
56844
58331
|
if (orgMatch && method === "DELETE") {
|
|
56845
|
-
return handleDeleteOrg(orgMatch[1], ctx,
|
|
58332
|
+
return handleDeleteOrg(orgMatch[1], ctx, json3);
|
|
56846
58333
|
}
|
|
56847
58334
|
if (path === "/api/org" && method === "GET") {
|
|
56848
|
-
return handleOrgChart(ctx,
|
|
58335
|
+
return handleOrgChart(ctx, json3);
|
|
56849
58336
|
}
|
|
56850
58337
|
const teamMatch = path.match(/^\/api\/agents\/([^/]+)\/team$/);
|
|
56851
58338
|
if (teamMatch && method === "GET") {
|
|
56852
|
-
return handleAgentTeam(teamMatch[1], ctx,
|
|
58339
|
+
return handleAgentTeam(teamMatch[1], ctx, json3);
|
|
56853
58340
|
}
|
|
56854
58341
|
if (path === "/api/agents" && method === "GET") {
|
|
56855
|
-
return handleListAgents(url, ctx,
|
|
58342
|
+
return handleListAgents(url, ctx, json3);
|
|
56856
58343
|
}
|
|
56857
58344
|
if (path === "/api/projects" && method === "POST") {
|
|
56858
|
-
return handleCreateProject(req, ctx,
|
|
58345
|
+
return handleCreateProject(req, ctx, json3);
|
|
56859
58346
|
}
|
|
56860
58347
|
const projectDeleteMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
56861
58348
|
if (projectDeleteMatch && method === "DELETE") {
|
|
56862
|
-
return handleDeleteProject(projectDeleteMatch[1], ctx,
|
|
58349
|
+
return handleDeleteProject(projectDeleteMatch[1], ctx, json3);
|
|
56863
58350
|
}
|
|
56864
58351
|
if (path === "/api/agents" && method === "POST") {
|
|
56865
|
-
return handleRegisterAgent(req, ctx,
|
|
58352
|
+
return handleRegisterAgent(req, ctx, json3);
|
|
56866
58353
|
}
|
|
56867
58354
|
const agentMatch = path.match(/^\/api\/agents\/([^/]+)$/);
|
|
56868
58355
|
if (agentMatch && method === "PATCH") {
|
|
56869
|
-
return handleUpdateAgent(agentMatch[1], req, ctx,
|
|
58356
|
+
return handleUpdateAgent(agentMatch[1], req, ctx, json3);
|
|
56870
58357
|
}
|
|
56871
58358
|
if (agentMatch && method === "DELETE") {
|
|
56872
|
-
return handleDeleteAgent(agentMatch[1], ctx,
|
|
58359
|
+
return handleDeleteAgent(agentMatch[1], ctx, json3);
|
|
56873
58360
|
}
|
|
56874
58361
|
if (path === "/api/agents/bulk" && method === "POST") {
|
|
56875
|
-
return handleBulkDeleteAgents(req, ctx,
|
|
58362
|
+
return handleBulkDeleteAgents(req, ctx, json3);
|
|
56876
58363
|
}
|
|
56877
58364
|
if (path === "/api/projects/bulk" && method === "POST") {
|
|
56878
|
-
return handleBulkDeleteProjects(req, ctx,
|
|
58365
|
+
return handleBulkDeleteProjects(req, ctx, json3);
|
|
56879
58366
|
}
|
|
56880
58367
|
if (path === "/api/doctor" && method === "GET") {
|
|
56881
|
-
return handleDoctor(ctx,
|
|
58368
|
+
return handleDoctor(ctx, json3);
|
|
56882
58369
|
}
|
|
56883
58370
|
if (path === "/api/report" && method === "GET") {
|
|
56884
|
-
return handleReport(req, url, ctx,
|
|
58371
|
+
return handleReport(req, url, ctx, json3);
|
|
56885
58372
|
}
|
|
56886
58373
|
if (path === "/api/activity" && method === "GET") {
|
|
56887
|
-
return handleActivity(req, url, ctx,
|
|
58374
|
+
return handleActivity(req, url, ctx, json3);
|
|
56888
58375
|
}
|
|
56889
58376
|
const historyMatch = path.match(/^\/api\/tasks\/([^/]+)\/history$/);
|
|
56890
58377
|
if (historyMatch && method === "GET") {
|
|
56891
|
-
return handleTaskHistory(historyMatch[1], ctx,
|
|
58378
|
+
return handleTaskHistory(historyMatch[1], ctx, json3, url);
|
|
56892
58379
|
}
|
|
56893
58380
|
if (path === "/api/webhooks" && method === "GET") {
|
|
56894
|
-
return handleListWebhooks(ctx,
|
|
58381
|
+
return handleListWebhooks(ctx, json3);
|
|
56895
58382
|
}
|
|
56896
58383
|
if (path === "/api/webhooks" && method === "POST") {
|
|
56897
|
-
return handleCreateWebhook(req, ctx,
|
|
58384
|
+
return handleCreateWebhook(req, ctx, json3);
|
|
56898
58385
|
}
|
|
56899
58386
|
const webhookMatch = path.match(/^\/api\/webhooks\/([^/]+)$/);
|
|
56900
58387
|
if (webhookMatch && method === "DELETE") {
|
|
56901
|
-
return handleDeleteWebhook(webhookMatch[1], ctx,
|
|
58388
|
+
return handleDeleteWebhook(webhookMatch[1], ctx, json3);
|
|
56902
58389
|
}
|
|
56903
58390
|
if (path === "/api/templates" && method === "GET") {
|
|
56904
|
-
return handleListTemplates(ctx,
|
|
58391
|
+
return handleListTemplates(ctx, json3);
|
|
56905
58392
|
}
|
|
56906
58393
|
if (path === "/api/templates" && method === "POST") {
|
|
56907
|
-
return handleCreateTemplate(req, ctx,
|
|
58394
|
+
return handleCreateTemplate(req, ctx, json3);
|
|
56908
58395
|
}
|
|
56909
58396
|
const templateMatch = path.match(/^\/api\/templates\/([^/]+)$/);
|
|
56910
58397
|
if (templateMatch && method === "DELETE") {
|
|
56911
|
-
return handleDeleteTemplate(templateMatch[1], ctx,
|
|
58398
|
+
return handleDeleteTemplate(templateMatch[1], ctx, json3);
|
|
56912
58399
|
}
|
|
56913
58400
|
if (path === "/api/plans" && method === "GET") {
|
|
56914
|
-
return handleListPlans(url, ctx,
|
|
58401
|
+
return handleListPlans(url, ctx, json3);
|
|
56915
58402
|
}
|
|
56916
58403
|
if (path === "/api/plans" && method === "POST") {
|
|
56917
|
-
return handleCreatePlan(req, ctx,
|
|
58404
|
+
return handleCreatePlan(req, ctx, json3);
|
|
56918
58405
|
}
|
|
56919
58406
|
if (path === "/api/plans/bulk" && method === "POST") {
|
|
56920
|
-
return handleBulkDeletePlans(req, ctx,
|
|
58407
|
+
return handleBulkDeletePlans(req, ctx, json3);
|
|
56921
58408
|
}
|
|
56922
58409
|
const planMatch = path.match(/^\/api\/plans\/([^/]+)$/);
|
|
56923
58410
|
if (planMatch) {
|
|
@@ -56926,16 +58413,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
56926
58413
|
return handleGetPlan(id, ctx, jsonWithCors, taskToSummary);
|
|
56927
58414
|
}
|
|
56928
58415
|
if (method === "PATCH") {
|
|
56929
|
-
return handleUpdatePlan(id, req, ctx,
|
|
58416
|
+
return handleUpdatePlan(id, req, ctx, json3);
|
|
56930
58417
|
}
|
|
56931
58418
|
if (method === "DELETE") {
|
|
56932
|
-
return handleDeletePlan(id, ctx,
|
|
58419
|
+
return handleDeletePlan(id, ctx, json3);
|
|
56933
58420
|
}
|
|
56934
58421
|
}
|
|
56935
58422
|
const staticRes = handleStaticFiles(path, method, ctx, jsonWithCors, serveStaticFile);
|
|
56936
58423
|
if (staticRes)
|
|
56937
58424
|
return staticRes;
|
|
56938
|
-
return
|
|
58425
|
+
return json3({ error: "Not found" }, 404);
|
|
56939
58426
|
}
|
|
56940
58427
|
});
|
|
56941
58428
|
const shutdown = () => {
|
|
@@ -57135,52 +58622,52 @@ function resolveId(partialId, table = "tasks") {
|
|
|
57135
58622
|
}
|
|
57136
58623
|
return id;
|
|
57137
58624
|
}
|
|
57138
|
-
function formatTask(
|
|
57139
|
-
const id =
|
|
57140
|
-
const assigned =
|
|
57141
|
-
const lockState = lockDisplayState(
|
|
58625
|
+
function formatTask(task3) {
|
|
58626
|
+
const id = task3.short_id || task3.id.slice(0, 8);
|
|
58627
|
+
const assigned = task3.assigned_to ? ` -> ${task3.assigned_to}` : "";
|
|
58628
|
+
const lockState = lockDisplayState(task3.locked_by, task3.locked_at);
|
|
57142
58629
|
const lock = lockState.held ? ` [locked:${lockState.holder}]` : "";
|
|
57143
|
-
const recur =
|
|
57144
|
-
return `${id} ${
|
|
58630
|
+
const recur = task3.recurrence_rule ? ` [\u21BB]` : "";
|
|
58631
|
+
return `${id} ${task3.status.padEnd(11)} ${task3.priority.padEnd(8)} ${task3.title}${assigned}${lock}${recur}`;
|
|
57145
58632
|
}
|
|
57146
|
-
function formatTaskDetail(
|
|
58633
|
+
function formatTaskDetail(task3, maxDescriptionChars) {
|
|
57147
58634
|
const parts = [
|
|
57148
|
-
`ID: ${
|
|
57149
|
-
`Title: ${
|
|
57150
|
-
`Status: ${
|
|
57151
|
-
`Priority: ${
|
|
58635
|
+
`ID: ${task3.id}`,
|
|
58636
|
+
`Title: ${task3.title}`,
|
|
58637
|
+
`Status: ${task3.status}`,
|
|
58638
|
+
`Priority: ${task3.priority}`
|
|
57152
58639
|
];
|
|
57153
|
-
if (
|
|
57154
|
-
const desc = maxDescriptionChars &&
|
|
58640
|
+
if (task3.description) {
|
|
58641
|
+
const desc = maxDescriptionChars && task3.description.length > maxDescriptionChars ? task3.description.slice(0, maxDescriptionChars) + "\u2026" : task3.description;
|
|
57155
58642
|
parts.push(`Description: ${desc}`);
|
|
57156
58643
|
}
|
|
57157
|
-
if (
|
|
57158
|
-
parts.push(`Assigned to: ${
|
|
57159
|
-
if (
|
|
57160
|
-
parts.push(`Agent: ${
|
|
57161
|
-
const detailLock = lockDisplayState(
|
|
58644
|
+
if (task3.assigned_to)
|
|
58645
|
+
parts.push(`Assigned to: ${task3.assigned_to}`);
|
|
58646
|
+
if (task3.agent_id)
|
|
58647
|
+
parts.push(`Agent: ${task3.agent_id}`);
|
|
58648
|
+
const detailLock = lockDisplayState(task3.locked_by, task3.locked_at);
|
|
57162
58649
|
if (detailLock.held)
|
|
57163
58650
|
parts.push(`Locked by: ${detailLock.holder}`);
|
|
57164
58651
|
else if (detailLock.expired)
|
|
57165
58652
|
parts.push(`Lock: ${formatExpiredLock(detailLock)}`);
|
|
57166
|
-
if (
|
|
57167
|
-
parts.push(`Parent: ${
|
|
57168
|
-
if (
|
|
57169
|
-
parts.push(`Project: ${
|
|
57170
|
-
if (
|
|
57171
|
-
parts.push(`Plan: ${
|
|
57172
|
-
if (
|
|
57173
|
-
parts.push(`Due: ${
|
|
57174
|
-
if (
|
|
57175
|
-
parts.push(`Tags: ${
|
|
57176
|
-
if (
|
|
57177
|
-
parts.push(`Recurrence: ${
|
|
57178
|
-
if (
|
|
57179
|
-
parts.push(`Recurrence parent: ${
|
|
57180
|
-
parts.push(`Version: ${
|
|
57181
|
-
parts.push(`Created: ${
|
|
57182
|
-
if (
|
|
57183
|
-
parts.push(`Completed: ${
|
|
58653
|
+
if (task3.parent_id)
|
|
58654
|
+
parts.push(`Parent: ${task3.parent_id}`);
|
|
58655
|
+
if (task3.project_id)
|
|
58656
|
+
parts.push(`Project: ${task3.project_id}`);
|
|
58657
|
+
if (task3.plan_id)
|
|
58658
|
+
parts.push(`Plan: ${task3.plan_id}`);
|
|
58659
|
+
if (task3.due_at)
|
|
58660
|
+
parts.push(`Due: ${task3.due_at.slice(0, 10)}`);
|
|
58661
|
+
if (task3.tags.length > 0)
|
|
58662
|
+
parts.push(`Tags: ${task3.tags.join(", ")}`);
|
|
58663
|
+
if (task3.recurrence_rule)
|
|
58664
|
+
parts.push(`Recurrence: ${task3.recurrence_rule}`);
|
|
58665
|
+
if (task3.recurrence_parent_id)
|
|
58666
|
+
parts.push(`Recurrence parent: ${task3.recurrence_parent_id}`);
|
|
58667
|
+
parts.push(`Version: ${task3.version}`);
|
|
58668
|
+
parts.push(`Created: ${task3.created_at}`);
|
|
58669
|
+
if (task3.completed_at)
|
|
58670
|
+
parts.push(`Completed: ${task3.completed_at}`);
|
|
57184
58671
|
return parts.join(`
|
|
57185
58672
|
`);
|
|
57186
58673
|
}
|