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