@hasna/todos 0.15.35 → 0.15.46
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 +52 -18
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/agent-commands.d.ts.map +1 -1
- package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-subtree-transfer-commands.d.ts +3 -0
- package/dist/cli/commands/task-subtree-transfer-commands.d.ts.map +1 -0
- package/dist/cli/index.js +3430 -827
- package/dist/cli/stage-a.d.ts.map +1 -1
- package/dist/contracts.js +66 -58
- package/dist/db/plan-comments.d.ts +14 -0
- package/dist/db/plan-comments.d.ts.map +1 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-graph.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/task-lists.d.ts.map +1 -1
- package/dist/db/task-relations.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1897 -334
- package/dist/lib/comment-cursor.d.ts +19 -0
- package/dist/lib/comment-cursor.d.ts.map +1 -1
- package/dist/lib/import-export-bridge.d.ts.map +1 -1
- package/dist/lib/instant-compare.d.ts +35 -0
- package/dist/lib/instant-compare.d.ts.map +1 -0
- package/dist/mcp/index.js +2403 -501
- package/dist/mcp.js +8 -4
- package/dist/pr-groups/postgres.d.ts.map +1 -1
- package/dist/project-registration/postgres.d.ts.map +1 -1
- package/dist/project-registration.js +290 -81
- package/dist/registry.d.ts +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +73 -58
- 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 +35 -0
- package/dist/sdk/v1.generated.d.ts +141 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +12 -2
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +5161 -8425
- package/dist/server/openapi.d.ts +2714 -1937
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts +2 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/config.d.ts +12 -30
- package/dist/storage/config.d.ts.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/interfaces.d.ts +13 -2
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/local-sqlite.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/timestamp-backfill.d.ts +62 -0
- package/dist/storage/timestamp-backfill.d.ts.map +1 -0
- package/dist/storage.d.ts +2 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +268 -74
- package/dist/task-manifest/postgres.d.ts.map +1 -1
- package/dist/task-manifest.js +15 -6
- package/dist/task-subtree-transfer/authority.d.ts +27 -0
- package/dist/task-subtree-transfer/authority.d.ts.map +1 -0
- package/dist/task-subtree-transfer/backend.d.ts +9 -0
- package/dist/task-subtree-transfer/backend.d.ts.map +1 -0
- package/dist/task-subtree-transfer/canonical.d.ts +14 -0
- package/dist/task-subtree-transfer/canonical.d.ts.map +1 -0
- package/dist/task-subtree-transfer/http.d.ts +16 -0
- package/dist/task-subtree-transfer/http.d.ts.map +1 -0
- package/dist/task-subtree-transfer/index.d.ts +6 -0
- package/dist/task-subtree-transfer/index.d.ts.map +1 -0
- package/dist/task-subtree-transfer/planner.d.ts +24 -0
- package/dist/task-subtree-transfer/planner.d.ts.map +1 -0
- package/dist/task-subtree-transfer/postgres.d.ts +19 -0
- package/dist/task-subtree-transfer/postgres.d.ts.map +1 -0
- package/dist/task-subtree-transfer/schema-sql.d.ts +5 -0
- package/dist/task-subtree-transfer/schema-sql.d.ts.map +1 -0
- package/dist/task-subtree-transfer/sqlite.d.ts +16 -0
- package/dist/task-subtree-transfer/sqlite.d.ts.map +1 -0
- package/dist/task-subtree-transfer/types.d.ts +148 -0
- package/dist/task-subtree-transfer/types.d.ts.map +1 -0
- package/dist/task-subtree-transfer.d.ts +2 -0
- package/dist/task-subtree-transfer.d.ts.map +1 -0
- package/dist/task-subtree-transfer.js +1878 -0
- package/dist/testing.d.ts +41 -7
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +44 -1
- package/dist/types/index.d.ts +24 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +8 -4
package/dist/mcp/index.js
CHANGED
|
@@ -2888,6 +2888,20 @@ function ensureSchema(db) {
|
|
|
2888
2888
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
|
|
2889
2889
|
ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
|
|
2890
2890
|
ensureColumn("task_comments", "progress_pct", "INTEGER");
|
|
2891
|
+
ensureTable("plan_comments", `
|
|
2892
|
+
CREATE TABLE plan_comments (
|
|
2893
|
+
id TEXT PRIMARY KEY,
|
|
2894
|
+
plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
2895
|
+
agent_id TEXT,
|
|
2896
|
+
session_id TEXT,
|
|
2897
|
+
content TEXT NOT NULL,
|
|
2898
|
+
type TEXT DEFAULT 'comment' CHECK(type IN ('comment', 'progress', 'note')),
|
|
2899
|
+
progress_pct INTEGER CHECK(progress_pct IS NULL OR (progress_pct >= 0 AND progress_pct <= 100)),
|
|
2900
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2901
|
+
machine_id TEXT,
|
|
2902
|
+
synced_at TEXT
|
|
2903
|
+
)`);
|
|
2904
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plan_comments_plan ON plan_comments(plan_id)");
|
|
2891
2905
|
ensureColumn("projects", "machine_id", "TEXT");
|
|
2892
2906
|
ensureColumn("projects", "synced_at", "TEXT");
|
|
2893
2907
|
ensureColumn("tasks", "machine_id", "TEXT");
|
|
@@ -3884,6 +3898,7 @@ var init_machines = __esm(() => {
|
|
|
3884
3898
|
"task_lists",
|
|
3885
3899
|
"plans",
|
|
3886
3900
|
"task_comments",
|
|
3901
|
+
"plan_comments",
|
|
3887
3902
|
"sessions",
|
|
3888
3903
|
"task_history",
|
|
3889
3904
|
"webhooks",
|
|
@@ -4112,20 +4127,16 @@ var init_sync_utils = __esm(() => {
|
|
|
4112
4127
|
// src/storage/config.ts
|
|
4113
4128
|
var exports_config = {};
|
|
4114
4129
|
__export(exports_config, {
|
|
4115
|
-
parseStorageMode: () => parseStorageMode,
|
|
4116
4130
|
parseStorageBackend: () => parseStorageBackend,
|
|
4117
4131
|
loadTodosStorageConfig: () => loadTodosStorageConfig,
|
|
4118
4132
|
loadStorageConfig: () => loadStorageConfig,
|
|
4119
4133
|
isTodosShadowEnabled: () => isTodosShadowEnabled,
|
|
4120
|
-
isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
|
|
4121
4134
|
isTodosPostgresBackend: () => isTodosPostgresBackend,
|
|
4122
4135
|
getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
|
|
4123
|
-
getTodosStorageMode: () => getTodosStorageMode,
|
|
4124
4136
|
getTodosStorageEnvName: () => getTodosStorageEnvName,
|
|
4125
4137
|
getTodosStorageDatabaseUrl: () => getTodosStorageDatabaseUrl,
|
|
4126
4138
|
getTodosStorageDatabaseEnv: () => getTodosStorageDatabaseEnv,
|
|
4127
4139
|
getTodosStorageBackend: () => getTodosStorageBackend,
|
|
4128
|
-
getStorageMode: () => getStorageMode,
|
|
4129
4140
|
getStorageDatabaseUrl: () => getStorageDatabaseUrl,
|
|
4130
4141
|
getStorageDatabaseEnv: () => getStorageDatabaseEnv,
|
|
4131
4142
|
getCanonicalTodosRdsConfig: () => getCanonicalTodosRdsConfig,
|
|
@@ -4135,7 +4146,6 @@ __export(exports_config, {
|
|
|
4135
4146
|
TODOS_STORAGE_FALLBACK_ENV: () => TODOS_STORAGE_FALLBACK_ENV,
|
|
4136
4147
|
TODOS_STORAGE_ENV: () => TODOS_STORAGE_ENV,
|
|
4137
4148
|
STORAGE_TABLES: () => STORAGE_TABLES,
|
|
4138
|
-
REMOVED_STORAGE_MODE_ENV_KEYS: () => REMOVED_STORAGE_MODE_ENV_KEYS,
|
|
4139
4149
|
CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV: () => CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV,
|
|
4140
4150
|
CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
|
|
4141
4151
|
CANONICAL_TODOS_RDS_CLUSTER_ENV: () => CANONICAL_TODOS_RDS_CLUSTER_ENV
|
|
@@ -4190,9 +4200,6 @@ function loadStorageConfig(env = process.env) {
|
|
|
4190
4200
|
function isTodosPostgresBackend(config) {
|
|
4191
4201
|
return config.mode === "postgres";
|
|
4192
4202
|
}
|
|
4193
|
-
function isTodosRemoteStorageEnabled(config) {
|
|
4194
|
-
return isTodosPostgresBackend(config);
|
|
4195
|
-
}
|
|
4196
4203
|
function assertTodosRemoteStorageConfig(config) {
|
|
4197
4204
|
if (!isTodosPostgresBackend(config))
|
|
4198
4205
|
return;
|
|
@@ -4208,32 +4215,15 @@ function parseStorageBackend(value) {
|
|
|
4208
4215
|
return "sqlite";
|
|
4209
4216
|
if (normalized === "postgres" || normalized === "postgresql")
|
|
4210
4217
|
return "postgres";
|
|
4211
|
-
if (["local", "remote", "cloud", "hybrid", "self_hosted"].includes(normalized)) {
|
|
4212
|
-
throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} selects the backend. Deployment modes no longer exist: ` + `delete the storage-mode variable and set ${TODOS_STORAGE_ENV.databaseUrl} to select the ` + `postgresql backend, or leave it unset for sqlite.`);
|
|
4213
|
-
}
|
|
4214
4218
|
throw new Error(`Storage backend must be sqlite or postgres`);
|
|
4215
4219
|
}
|
|
4216
|
-
function parseStorageMode(value) {
|
|
4217
|
-
return parseStorageBackend(value);
|
|
4218
|
-
}
|
|
4219
4220
|
function getTodosStorageBackend(env = process.env) {
|
|
4220
|
-
for (const key of REMOVED_STORAGE_MODE_ENV_KEYS) {
|
|
4221
|
-
if (Object.hasOwn(env, key) && env[key] !== undefined) {
|
|
4222
|
-
throw new Error(`${key} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `Set ${TODOS_STORAGE_ENV.databaseUrl} to select the postgresql backend, ` + `or leave it unset for sqlite.`);
|
|
4223
|
-
}
|
|
4224
|
-
}
|
|
4225
4221
|
if (!getTodosStorageDatabaseUrl(env))
|
|
4226
4222
|
return "sqlite";
|
|
4227
4223
|
if (isTodosShadowEnabled(env))
|
|
4228
4224
|
return "sqlite";
|
|
4229
4225
|
return "postgres";
|
|
4230
4226
|
}
|
|
4231
|
-
function getTodosStorageMode(env = process.env) {
|
|
4232
|
-
return getTodosStorageBackend(env);
|
|
4233
|
-
}
|
|
4234
|
-
function getStorageMode(env = process.env) {
|
|
4235
|
-
return getTodosStorageBackend(env);
|
|
4236
|
-
}
|
|
4237
4227
|
function isTodosShadowEnabled(env = process.env) {
|
|
4238
4228
|
return parseBoolean(readStorageEnv(env, "shadow").value, false);
|
|
4239
4229
|
}
|
|
@@ -4300,7 +4290,7 @@ function parsePositiveInteger(value, fallback) {
|
|
|
4300
4290
|
}
|
|
4301
4291
|
return parsed;
|
|
4302
4292
|
}
|
|
4303
|
-
var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV,
|
|
4293
|
+
var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV, CANONICAL_TODOS_RDS_CLUSTER_ENV = "HASNA_TODOS_RDS_CLUSTER", CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV = "HASNA_TODOS_RDS_RUNTIME_PATH", CANONICAL_TODOS_RDS_DATABASE = "todos";
|
|
4304
4294
|
var init_config = __esm(() => {
|
|
4305
4295
|
TODOS_STORAGE_TABLES = [
|
|
4306
4296
|
"todos_sync_records",
|
|
@@ -4339,12 +4329,6 @@ var init_config = __esm(() => {
|
|
|
4339
4329
|
syncBatchSize: "TODOS_SYNC_BATCH_SIZE",
|
|
4340
4330
|
syncDryRun: "TODOS_SYNC_DRY_RUN"
|
|
4341
4331
|
};
|
|
4342
|
-
REMOVED_STORAGE_MODE_ENV_KEYS = [
|
|
4343
|
-
"HASNA_TODOS_STORAGE_MODE",
|
|
4344
|
-
"HASNA_TODOS_MODE",
|
|
4345
|
-
"TODOS_STORAGE_MODE",
|
|
4346
|
-
"TODOS_MODE"
|
|
4347
|
-
];
|
|
4348
4332
|
});
|
|
4349
4333
|
|
|
4350
4334
|
// src/storage/shadow-outbox-schema.ts
|
|
@@ -11747,22 +11731,31 @@ function updateTaskList(id, input, db) {
|
|
|
11747
11731
|
throw new TaskListNotFoundError(id);
|
|
11748
11732
|
const sets = ["updated_at = ?"];
|
|
11749
11733
|
const params = [now()];
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
|
|
11753
|
-
|
|
11754
|
-
|
|
11734
|
+
const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? existing.project_id : input.project_id;
|
|
11735
|
+
const scopeChanged = projectId !== existing.project_id;
|
|
11736
|
+
const effectiveSlug = input.slug !== undefined ? slugify(input.slug) : existing.slug;
|
|
11737
|
+
if (input.slug !== undefined && !effectiveSlug) {
|
|
11738
|
+
throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
|
|
11739
|
+
}
|
|
11740
|
+
if (scopeChanged || input.slug !== undefined && effectiveSlug !== existing.slug) {
|
|
11741
|
+
const duplicate = projectId ? d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ? AND id != ?").get(projectId, effectiveSlug, id) : d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ? AND id != ?").get(effectiveSlug, id);
|
|
11755
11742
|
if (duplicate) {
|
|
11756
|
-
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${
|
|
11743
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
|
|
11757
11744
|
}
|
|
11758
|
-
if (
|
|
11745
|
+
if (scopeChanged || effectiveSlug !== existing.slug) {
|
|
11759
11746
|
releaseCanonicalSlugClaims("task_list", id, d);
|
|
11760
|
-
if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(
|
|
11761
|
-
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${
|
|
11747
|
+
if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(projectId), effectiveSlug, id, d)) {
|
|
11748
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
|
|
11762
11749
|
}
|
|
11763
11750
|
}
|
|
11751
|
+
}
|
|
11752
|
+
if (input.slug !== undefined) {
|
|
11764
11753
|
sets.push("slug = ?");
|
|
11765
|
-
params.push(
|
|
11754
|
+
params.push(effectiveSlug);
|
|
11755
|
+
}
|
|
11756
|
+
if (projectId !== existing.project_id) {
|
|
11757
|
+
sets.push("project_id = ?");
|
|
11758
|
+
params.push(projectId);
|
|
11766
11759
|
}
|
|
11767
11760
|
if (input.name !== undefined) {
|
|
11768
11761
|
sets.push("name = ?");
|
|
@@ -13296,7 +13289,7 @@ function getTaskGraph(taskId, direction = "both", db) {
|
|
|
13296
13289
|
const deps = getTaskDependencies(t.id, d);
|
|
13297
13290
|
const hasUnfinishedDeps = deps.some((dep) => {
|
|
13298
13291
|
const depTask = getTask(dep.depends_on, d);
|
|
13299
|
-
return depTask && depTask.status
|
|
13292
|
+
return depTask && isBlockingDependencyStatus(depTask.status);
|
|
13300
13293
|
});
|
|
13301
13294
|
return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
|
|
13302
13295
|
}
|
|
@@ -13556,7 +13549,7 @@ function getBlockingDeps(id, db) {
|
|
|
13556
13549
|
const blocking = [];
|
|
13557
13550
|
for (const dep of deps) {
|
|
13558
13551
|
const task = getTask(dep.depends_on, d);
|
|
13559
|
-
if (task && task.status
|
|
13552
|
+
if (task && isBlockingDependencyStatus(task.status))
|
|
13560
13553
|
blocking.push(task);
|
|
13561
13554
|
}
|
|
13562
13555
|
return blocking;
|
|
@@ -13880,7 +13873,7 @@ function getNextTask(agentId, filters, db) {
|
|
|
13880
13873
|
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
13881
13874
|
params.push(...filters.tags);
|
|
13882
13875
|
}
|
|
13883
|
-
conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status
|
|
13876
|
+
conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status NOT IN ('completed', 'cancelled'))");
|
|
13884
13877
|
const where = conditions.join(" AND ");
|
|
13885
13878
|
let recentProjectIds = [];
|
|
13886
13879
|
const assignedAliasParams = [];
|
|
@@ -13924,7 +13917,7 @@ function getActiveWork(filters, db) {
|
|
|
13924
13917
|
}
|
|
13925
13918
|
function getTasksChangedSince(since, filters, db) {
|
|
13926
13919
|
const d = db || getDatabase();
|
|
13927
|
-
const conditions = ["updated_at > ?"];
|
|
13920
|
+
const conditions = ["(julianday(updated_at) IS NULL OR julianday(updated_at) > julianday(?))"];
|
|
13928
13921
|
const params = [since];
|
|
13929
13922
|
if (filters?.project_id) {
|
|
13930
13923
|
conditions.push("project_id = ?");
|
|
@@ -13959,8 +13952,8 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
13959
13952
|
const safeMeta = sanitizePreWriteValue(meta, "task.failure.metadata");
|
|
13960
13953
|
const timestamp2 = now();
|
|
13961
13954
|
const failTx = d.transaction(() => {
|
|
13962
|
-
const res = d.run(`UPDATE tasks SET status = 'failed', reason = ?, locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
13963
|
-
WHERE id = ? AND version = ?`, [safeReason, JSON.stringify(safeMeta), timestamp2, id, task.version]);
|
|
13955
|
+
const res = d.run(`UPDATE tasks SET status = 'failed', reason = ?, locked_by = NULL, locked_at = NULL, completed_at = COALESCE(completed_at, ?), metadata = ?, version = version + 1, updated_at = ?
|
|
13956
|
+
WHERE id = ? AND version = ?`, [safeReason, timestamp2, JSON.stringify(safeMeta), timestamp2, id, task.version]);
|
|
13964
13957
|
if (res.changes === 0) {
|
|
13965
13958
|
const current = getTask(id, d);
|
|
13966
13959
|
throw new VersionConflictError(id, task.version, current?.version ?? -1);
|
|
@@ -13972,6 +13965,7 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
13972
13965
|
status: "failed",
|
|
13973
13966
|
locked_by: null,
|
|
13974
13967
|
locked_at: null,
|
|
13968
|
+
completed_at: task.completed_at ?? timestamp2,
|
|
13975
13969
|
reason: safeReason,
|
|
13976
13970
|
metadata: safeMeta,
|
|
13977
13971
|
version: task.version + 1,
|
|
@@ -14620,13 +14614,20 @@ function updateTaskStored(id, input, db) {
|
|
|
14620
14614
|
if (isTerminalStatus(input.status)) {
|
|
14621
14615
|
sets.push("locked_by = NULL");
|
|
14622
14616
|
sets.push("locked_at = NULL");
|
|
14623
|
-
|
|
14624
|
-
|
|
14625
|
-
|
|
14626
|
-
|
|
14617
|
+
if (input.status === "completed") {
|
|
14618
|
+
sets.push("completed_at = ?");
|
|
14619
|
+
params.push(completionTimestamp);
|
|
14620
|
+
} else {
|
|
14621
|
+
sets.push("completed_at = COALESCE(completed_at, ?)");
|
|
14622
|
+
params.push(completionTimestamp);
|
|
14623
|
+
}
|
|
14627
14624
|
} else if (task.status === "completed" && input.completed_at === undefined) {
|
|
14628
14625
|
sets.push("completed_at = NULL");
|
|
14629
14626
|
}
|
|
14627
|
+
if (input.status === "in_progress") {
|
|
14628
|
+
sets.push("started_at = COALESCE(started_at, ?)");
|
|
14629
|
+
params.push(timestamp2);
|
|
14630
|
+
}
|
|
14630
14631
|
}
|
|
14631
14632
|
if (input.priority !== undefined) {
|
|
14632
14633
|
sets.push("priority = ?");
|
|
@@ -14764,7 +14765,6 @@ function updateTaskStored(id, input, db) {
|
|
|
14764
14765
|
if (input.approved_by !== undefined)
|
|
14765
14766
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
14766
14767
|
const reopened = input.status !== undefined && input.status !== "completed" && task.status === "completed" && input.completed_at === undefined;
|
|
14767
|
-
const completedNow = input.status === "completed";
|
|
14768
14768
|
const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
|
|
14769
14769
|
const updatedTask = {
|
|
14770
14770
|
...task,
|
|
@@ -14775,7 +14775,8 @@ function updateTaskStored(id, input, db) {
|
|
|
14775
14775
|
updated_at: timestamp2,
|
|
14776
14776
|
locked_by: terminalNow ? null : task.locked_by,
|
|
14777
14777
|
locked_at: terminalNow ? null : task.locked_at,
|
|
14778
|
-
completed_at:
|
|
14778
|
+
completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : terminalNow ? task.completed_at ?? completionTimestamp : reopened ? null : task.completed_at,
|
|
14779
|
+
started_at: input.status === "in_progress" ? task.started_at ?? timestamp2 : task.started_at,
|
|
14779
14780
|
sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
|
|
14780
14781
|
actual_minutes: input.actual_minutes ?? task.actual_minutes,
|
|
14781
14782
|
confidence: input.confidence !== undefined ? input.confidence : task.confidence,
|
|
@@ -14836,6 +14837,9 @@ function deleteTask(id, db) {
|
|
|
14836
14837
|
version: row.version
|
|
14837
14838
|
}, d);
|
|
14838
14839
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
14840
|
+
if (result.changes > 0) {
|
|
14841
|
+
d.run("DELETE FROM task_dependencies WHERE task_id = ? OR depends_on = ?", [id, id]);
|
|
14842
|
+
}
|
|
14839
14843
|
return result.changes > 0;
|
|
14840
14844
|
}
|
|
14841
14845
|
var init_task_crud = __esm(() => {
|
|
@@ -15100,8 +15104,10 @@ function bulkDeleteTasks(taskIds, force = false, db) {
|
|
|
15100
15104
|
continue;
|
|
15101
15105
|
}
|
|
15102
15106
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
15103
|
-
if (result.changes > 0)
|
|
15107
|
+
if (result.changes > 0) {
|
|
15108
|
+
d.run("DELETE FROM task_dependencies WHERE task_id = ? OR depends_on = ?", [id, id]);
|
|
15104
15109
|
deleted++;
|
|
15110
|
+
}
|
|
15105
15111
|
} catch (e) {
|
|
15106
15112
|
failed.push({ id, error: e instanceof Error ? e.message : String(e) });
|
|
15107
15113
|
}
|
|
@@ -18861,6 +18867,60 @@ var init_assignee_context = __esm(() => {
|
|
|
18861
18867
|
init_assignee_validation();
|
|
18862
18868
|
});
|
|
18863
18869
|
|
|
18870
|
+
// src/lib/instant-compare.ts
|
|
18871
|
+
function isLeapYear(year) {
|
|
18872
|
+
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
18873
|
+
}
|
|
18874
|
+
function sqliteJulianDay(value) {
|
|
18875
|
+
const m = SQLITE_STAMP.exec(value);
|
|
18876
|
+
if (!m)
|
|
18877
|
+
return null;
|
|
18878
|
+
const year = Number(m[1]);
|
|
18879
|
+
const month = Number(m[2]);
|
|
18880
|
+
const day = Number(m[3]);
|
|
18881
|
+
const hour = m[4] === undefined ? 0 : Number(m[4]);
|
|
18882
|
+
const minute = m[5] === undefined ? 0 : Number(m[5]);
|
|
18883
|
+
const second = m[6] === undefined ? 0 : Number(m[6]);
|
|
18884
|
+
const frac = m[7];
|
|
18885
|
+
const sign = m[8];
|
|
18886
|
+
const offsetHour = m[9];
|
|
18887
|
+
const offsetMinute = m[10];
|
|
18888
|
+
if (month < 1 || month > 12)
|
|
18889
|
+
return null;
|
|
18890
|
+
if (hour > 23 || minute > 59 || second > 59)
|
|
18891
|
+
return null;
|
|
18892
|
+
const maxDay = (DAYS_IN_MONTH[month - 1] ?? 0) + (month === 2 && isLeapYear(year) ? 1 : 0);
|
|
18893
|
+
if (day < 1 || day > maxDay)
|
|
18894
|
+
return null;
|
|
18895
|
+
let offsetMinutes = 0;
|
|
18896
|
+
if (sign !== undefined) {
|
|
18897
|
+
const oh = Number(offsetHour ?? "0");
|
|
18898
|
+
const om = Number(offsetMinute ?? "0");
|
|
18899
|
+
if (oh > 23 || om > 59)
|
|
18900
|
+
return null;
|
|
18901
|
+
offsetMinutes = oh * 60 + om;
|
|
18902
|
+
if (sign === "-")
|
|
18903
|
+
offsetMinutes = -offsetMinutes;
|
|
18904
|
+
}
|
|
18905
|
+
const micros = frac === undefined ? 0 : Number((frac + "000000").slice(0, 6));
|
|
18906
|
+
const ms = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
18907
|
+
return (ms + micros / 1000) / 86400000 + 2440587.5 - offsetMinutes / 1440;
|
|
18908
|
+
}
|
|
18909
|
+
function changedSinceStampNewer(stamp, since) {
|
|
18910
|
+
const stampJd = sqliteJulianDay(stamp);
|
|
18911
|
+
if (stampJd === null)
|
|
18912
|
+
return true;
|
|
18913
|
+
const sinceJd = sqliteJulianDay(since);
|
|
18914
|
+
if (sinceJd === null)
|
|
18915
|
+
return false;
|
|
18916
|
+
return stampJd > sinceJd;
|
|
18917
|
+
}
|
|
18918
|
+
var SQLITE_STAMP, DAYS_IN_MONTH;
|
|
18919
|
+
var init_instant_compare = __esm(() => {
|
|
18920
|
+
SQLITE_STAMP = /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(?:Z|([+-])(\d{2}):(\d{2}))?)?$/;
|
|
18921
|
+
DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
18922
|
+
});
|
|
18923
|
+
|
|
18864
18924
|
// src/lib/plan-project-link-contract.ts
|
|
18865
18925
|
import { createHash as createHash3 } from "crypto";
|
|
18866
18926
|
function canonicalPlanProjectLinkJson(value) {
|
|
@@ -21187,20 +21247,25 @@ var init_page_validation = __esm(() => {
|
|
|
21187
21247
|
import { resolveStorageClient } from "@hasna/contracts/client/storage";
|
|
21188
21248
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
21189
21249
|
import { resolve as resolvePath } from "path";
|
|
21190
|
-
function
|
|
21191
|
-
|
|
21192
|
-
|
|
21193
|
-
|
|
21194
|
-
|
|
21195
|
-
|
|
21250
|
+
function emitTodosLocalFallbackNotice(env) {
|
|
21251
|
+
if (todosLocalFallbackNoticeEmitted)
|
|
21252
|
+
return;
|
|
21253
|
+
todosLocalFallbackNoticeEmitted = true;
|
|
21254
|
+
const notice = {
|
|
21255
|
+
event: "todos-local-fallback",
|
|
21256
|
+
transport: "sqlite",
|
|
21257
|
+
source: "default",
|
|
21258
|
+
apiUrlPresent: Boolean(env.HASNA_TODOS_API_URL?.trim()),
|
|
21259
|
+
apiKeyPresent: Boolean(env.HASNA_TODOS_API_KEY?.trim()),
|
|
21260
|
+
notice: "No hosted API config (HASNA_TODOS_API_URL + HASNA_TODOS_API_KEY) is present; " + "using local SQLite. Hosted todos are NOT visible in this output."
|
|
21261
|
+
};
|
|
21262
|
+
console.error(JSON.stringify(notice));
|
|
21196
21263
|
}
|
|
21197
|
-
function
|
|
21198
|
-
assertNoLegacyStorageMode(env);
|
|
21264
|
+
function resolveTodosCliTransport(env = process.env) {
|
|
21199
21265
|
const urlValue = env.HASNA_TODOS_API_URL?.trim();
|
|
21200
21266
|
const keyValue = env.HASNA_TODOS_API_KEY?.trim();
|
|
21201
21267
|
if (urlValue && keyValue) {
|
|
21202
21268
|
return {
|
|
21203
|
-
mode: "http",
|
|
21204
21269
|
transport: "http",
|
|
21205
21270
|
selected: true,
|
|
21206
21271
|
source: "HASNA_TODOS_API_URL+HASNA_TODOS_API_KEY"
|
|
@@ -21212,15 +21277,15 @@ function resolveTodosCliStorageMode(env = process.env) {
|
|
|
21212
21277
|
if (keyValue) {
|
|
21213
21278
|
throw new Error("REMOTE_API_URL_MISSING: remote Todos storage requires HASNA_TODOS_API_URL; local SQLite fallback is disabled");
|
|
21214
21279
|
}
|
|
21280
|
+
emitTodosLocalFallbackNotice(env);
|
|
21215
21281
|
return {
|
|
21216
|
-
mode: "sqlite",
|
|
21217
21282
|
transport: "sqlite",
|
|
21218
21283
|
selected: false,
|
|
21219
21284
|
source: "default"
|
|
21220
21285
|
};
|
|
21221
21286
|
}
|
|
21222
21287
|
function requestedTransport(env) {
|
|
21223
|
-
return
|
|
21288
|
+
return resolveTodosCliTransport(env).transport;
|
|
21224
21289
|
}
|
|
21225
21290
|
function normalizeRemoteAuthorityUrl(value) {
|
|
21226
21291
|
const trimmed = value?.trim();
|
|
@@ -21254,13 +21319,13 @@ function normalizeRemoteAuthorityUrl(value) {
|
|
|
21254
21319
|
function getTodosRemoteAuthorityConfigStatus(env = process.env) {
|
|
21255
21320
|
let resolution;
|
|
21256
21321
|
try {
|
|
21257
|
-
resolution =
|
|
21322
|
+
resolution = resolveTodosCliTransport(env);
|
|
21258
21323
|
} catch (error) {
|
|
21259
21324
|
const issue = error instanceof Error ? error.message : String(error);
|
|
21260
21325
|
return {
|
|
21261
21326
|
selected: true,
|
|
21262
21327
|
ok: false,
|
|
21263
|
-
|
|
21328
|
+
transport: "invalid",
|
|
21264
21329
|
api_url_configured: Boolean(env.HASNA_TODOS_API_URL?.trim()),
|
|
21265
21330
|
api_key_configured: Boolean(env.HASNA_TODOS_API_KEY?.trim()),
|
|
21266
21331
|
v1_base_url: null,
|
|
@@ -21268,12 +21333,12 @@ function getTodosRemoteAuthorityConfigStatus(env = process.env) {
|
|
|
21268
21333
|
local_fallback: false
|
|
21269
21334
|
};
|
|
21270
21335
|
}
|
|
21271
|
-
const {
|
|
21336
|
+
const { transport, selected } = resolution;
|
|
21272
21337
|
if (!selected) {
|
|
21273
21338
|
return {
|
|
21274
21339
|
selected: false,
|
|
21275
21340
|
ok: true,
|
|
21276
|
-
|
|
21341
|
+
transport,
|
|
21277
21342
|
api_url_configured: false,
|
|
21278
21343
|
api_key_configured: false,
|
|
21279
21344
|
v1_base_url: null,
|
|
@@ -21298,7 +21363,7 @@ function getTodosRemoteAuthorityConfigStatus(env = process.env) {
|
|
|
21298
21363
|
return {
|
|
21299
21364
|
selected: true,
|
|
21300
21365
|
ok: issues.length === 0,
|
|
21301
|
-
|
|
21366
|
+
transport,
|
|
21302
21367
|
api_url_configured: apiUrl !== null,
|
|
21303
21368
|
api_key_configured: apiKeyConfigured,
|
|
21304
21369
|
v1_base_url: apiUrl ? `${apiUrl}/v1` : null,
|
|
@@ -21342,7 +21407,8 @@ function classifyRemoteRequestError(baseUrl, route, error) {
|
|
|
21342
21407
|
}
|
|
21343
21408
|
const message = error instanceof Error ? error.message : String(error);
|
|
21344
21409
|
if (error instanceof Error && error.name === "AbortError" || /abort|timed?\s*out/i.test(message)) {
|
|
21345
|
-
|
|
21410
|
+
const unboundedNote = route === "/tasks" ? "; this is likely an UNBOUNDED task read timing out (bounded task reads from this authority return in ~1 s) \u2014 retry with a bounded/count-specific shape such as todos list --limit; local SQLite fallback is disabled" : "; local SQLite fallback is disabled";
|
|
21411
|
+
throw new Error(`REMOTE_API_TIMEOUT: configured Todos authority ${baseUrl} timed out for ${route}${unboundedNote}`, { cause: error });
|
|
21346
21412
|
}
|
|
21347
21413
|
if (status === undefined) {
|
|
21348
21414
|
throw new Error(`REMOTE_API_UNREACHABLE: configured Todos authority ${baseUrl} could not be reached for ${route}; ` + "local SQLite fallback is disabled", { cause: error });
|
|
@@ -21429,11 +21495,10 @@ function getTodosCloudClient(env = process.env, requestTimeoutMs = REMOTE_REQUES
|
|
|
21429
21495
|
fetchImpl: (input, init) => globalThis.fetch(input, { ...init, redirect: "manual" }),
|
|
21430
21496
|
timeoutMs: requestTimeoutMs
|
|
21431
21497
|
});
|
|
21432
|
-
if (resolved.transport === "cloud-http")
|
|
21433
|
-
return protectRemoteClient(resolved.client, requestTimeoutMs);
|
|
21434
21498
|
const transportName = resolved.transport;
|
|
21435
|
-
if (transportName === "http")
|
|
21499
|
+
if (transportName === "cloud-http" || transportName === "http") {
|
|
21436
21500
|
return protectRemoteClient(resolved.client, requestTimeoutMs);
|
|
21501
|
+
}
|
|
21437
21502
|
return null;
|
|
21438
21503
|
}
|
|
21439
21504
|
function unwrapTask(raw) {
|
|
@@ -21905,10 +21970,11 @@ async function cloudResolveTaskListRef(client, ref, projectId) {
|
|
|
21905
21970
|
return input.toLowerCase();
|
|
21906
21971
|
return (await cloudResolveTaskList(client, ref, projectId)).id;
|
|
21907
21972
|
}
|
|
21908
|
-
var UUID_RE, completionCapabilityCache, retryCapabilityCache, taskCreatorCapabilityCache, gitRefCapabilityCache, remoteCommandCapabilityCache,
|
|
21973
|
+
var UUID_RE, completionCapabilityCache, retryCapabilityCache, taskCreatorCapabilityCache, gitRefCapabilityCache, remoteCommandCapabilityCache, todosLocalFallbackNoticeEmitted = false, REMOTE_REQUEST_TIMEOUT_MS = 1e4, PRIORITY_RANK, listTagsCapabilityCache;
|
|
21909
21974
|
var init_cloud_router = __esm(() => {
|
|
21910
21975
|
init_types();
|
|
21911
21976
|
init_redaction();
|
|
21977
|
+
init_instant_compare();
|
|
21912
21978
|
init_plan_project_link_contract();
|
|
21913
21979
|
init_http_client();
|
|
21914
21980
|
init_adoption_validation();
|
|
@@ -21919,12 +21985,6 @@ var init_cloud_router = __esm(() => {
|
|
|
21919
21985
|
taskCreatorCapabilityCache = new Map;
|
|
21920
21986
|
gitRefCapabilityCache = new Map;
|
|
21921
21987
|
remoteCommandCapabilityCache = new Map;
|
|
21922
|
-
LEGACY_STORAGE_MODE_KEYS = [
|
|
21923
|
-
"HASNA_TODOS_STORAGE_MODE",
|
|
21924
|
-
"HASNA_TODOS_MODE",
|
|
21925
|
-
"TODOS_STORAGE_MODE",
|
|
21926
|
-
"TODOS_MODE"
|
|
21927
|
-
];
|
|
21928
21988
|
PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
21929
21989
|
listTagsCapabilityCache = new Map;
|
|
21930
21990
|
});
|
|
@@ -34719,7 +34779,7 @@ function scoreHealth(scope, scopeId, db) {
|
|
|
34719
34779
|
const blockers = d.query(`SELECT dep.id, dep.short_id, dep.title, dep.status
|
|
34720
34780
|
FROM task_dependencies td
|
|
34721
34781
|
JOIN tasks dep ON dep.id = td.depends_on
|
|
34722
|
-
WHERE td.task_id = ? AND dep.status
|
|
34782
|
+
WHERE td.task_id = ? AND dep.status NOT IN ('completed', 'cancelled')`).all(task.id);
|
|
34723
34783
|
return { id: task.id, short_id: task.short_id, title: redactEvidenceText(task.title), blockers };
|
|
34724
34784
|
}).filter((entry) => entry.blockers.length > 0);
|
|
34725
34785
|
const overdue = tasks.filter((task) => activeTaskIds.has(task.id) && Boolean(task.due_at && task.due_at < generatedAt)).map((task) => ({ id: task.id, short_id: task.short_id, title: redactEvidenceText(task.title), due_at: task.due_at }));
|
|
@@ -36042,7 +36102,7 @@ var package_default;
|
|
|
36042
36102
|
var init_package = __esm(() => {
|
|
36043
36103
|
package_default = {
|
|
36044
36104
|
name: "@hasna/todos",
|
|
36045
|
-
version: "0.15.
|
|
36105
|
+
version: "0.15.46",
|
|
36046
36106
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
36047
36107
|
type: "module",
|
|
36048
36108
|
main: "dist/index.js",
|
|
@@ -36088,6 +36148,10 @@ var init_package = __esm(() => {
|
|
|
36088
36148
|
"./task-manifest": {
|
|
36089
36149
|
types: "./dist/task-manifest.d.ts",
|
|
36090
36150
|
import: "./dist/task-manifest.js"
|
|
36151
|
+
},
|
|
36152
|
+
"./task-subtree-transfer": {
|
|
36153
|
+
types: "./dist/task-subtree-transfer.d.ts",
|
|
36154
|
+
import: "./dist/task-subtree-transfer.js"
|
|
36091
36155
|
}
|
|
36092
36156
|
},
|
|
36093
36157
|
workspaces: [
|
|
@@ -36101,8 +36165,8 @@ var init_package = __esm(() => {
|
|
|
36101
36165
|
"README.md"
|
|
36102
36166
|
],
|
|
36103
36167
|
scripts: {
|
|
36104
|
-
build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
36105
|
-
"build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
36168
|
+
build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --root src --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
36169
|
+
"build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
36106
36170
|
migrate: "bun run src/server/index.ts migrate",
|
|
36107
36171
|
"backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
|
|
36108
36172
|
"generate:sdk": "bun run scripts/generate-sdk.ts",
|
|
@@ -36156,7 +36220,7 @@ var init_package = __esm(() => {
|
|
|
36156
36220
|
author: "Andrei Hasna <andrei@hasna.com>",
|
|
36157
36221
|
license: "Apache-2.0",
|
|
36158
36222
|
dependencies: {
|
|
36159
|
-
"@hasna/contracts": "0.
|
|
36223
|
+
"@hasna/contracts": "0.13.4",
|
|
36160
36224
|
"@hasna/events": "^0.1.11",
|
|
36161
36225
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
36162
36226
|
chalk: "^5.4.1",
|
|
@@ -45433,6 +45497,75 @@ var init_cloud_client = __esm(() => {
|
|
|
45433
45497
|
init_config();
|
|
45434
45498
|
});
|
|
45435
45499
|
|
|
45500
|
+
// src/db/plan-comments.ts
|
|
45501
|
+
function addPlanComment(input, db) {
|
|
45502
|
+
const d = db || getDatabase();
|
|
45503
|
+
if (!getPlan(input.plan_id, d)) {
|
|
45504
|
+
throw new PlanNotFoundError(input.plan_id);
|
|
45505
|
+
}
|
|
45506
|
+
const id = uuid();
|
|
45507
|
+
const timestamp4 = now();
|
|
45508
|
+
d.run(`INSERT INTO plan_comments (id, plan_id, agent_id, session_id, content, type, progress_pct, created_at)
|
|
45509
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
45510
|
+
id,
|
|
45511
|
+
input.plan_id,
|
|
45512
|
+
input.agent_id || null,
|
|
45513
|
+
input.session_id || null,
|
|
45514
|
+
sanitizePreWriteText(input.content, "comment.content"),
|
|
45515
|
+
input.type || "comment",
|
|
45516
|
+
input.progress_pct ?? null,
|
|
45517
|
+
timestamp4
|
|
45518
|
+
]);
|
|
45519
|
+
return getPlanComment(id, d);
|
|
45520
|
+
}
|
|
45521
|
+
function getPlanComment(id, db) {
|
|
45522
|
+
const d = db || getDatabase();
|
|
45523
|
+
return d.query("SELECT * FROM plan_comments WHERE id = ?").get(id);
|
|
45524
|
+
}
|
|
45525
|
+
function listPlanComments(planId, db) {
|
|
45526
|
+
const d = db || getDatabase();
|
|
45527
|
+
return d.query("SELECT * FROM plan_comments WHERE plan_id = ? ORDER BY created_at, rowid").all(planId);
|
|
45528
|
+
}
|
|
45529
|
+
var init_plan_comments = __esm(() => {
|
|
45530
|
+
init_types();
|
|
45531
|
+
init_database();
|
|
45532
|
+
init_plans();
|
|
45533
|
+
init_prewrite_secrets();
|
|
45534
|
+
});
|
|
45535
|
+
|
|
45536
|
+
// src/lib/comment-cursor.ts
|
|
45537
|
+
function encodeCommentCursor(comment) {
|
|
45538
|
+
return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
|
|
45539
|
+
}
|
|
45540
|
+
function decodeCommentCursor(value) {
|
|
45541
|
+
if (value.length > MAX_COMMENT_CURSOR_LENGTH)
|
|
45542
|
+
throw new Error("invalid comment cursor");
|
|
45543
|
+
let parsed;
|
|
45544
|
+
try {
|
|
45545
|
+
parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
45546
|
+
} catch {
|
|
45547
|
+
throw new Error("invalid comment cursor");
|
|
45548
|
+
}
|
|
45549
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
45550
|
+
throw new Error("invalid comment cursor");
|
|
45551
|
+
const cursor = parsed;
|
|
45552
|
+
if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
|
|
45553
|
+
throw new Error("invalid comment cursor");
|
|
45554
|
+
}
|
|
45555
|
+
return { created_at: cursor["created_at"], id: cursor["id"] };
|
|
45556
|
+
}
|
|
45557
|
+
function isStrictlyOlder(comment, before) {
|
|
45558
|
+
return comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id;
|
|
45559
|
+
}
|
|
45560
|
+
function compareCommentKeyset(left, right) {
|
|
45561
|
+
if (isStrictlyOlder(left, right))
|
|
45562
|
+
return -1;
|
|
45563
|
+
if (isStrictlyOlder(right, left))
|
|
45564
|
+
return 1;
|
|
45565
|
+
return 0;
|
|
45566
|
+
}
|
|
45567
|
+
var MAX_COMMENT_CURSOR_LENGTH = 1024;
|
|
45568
|
+
|
|
45436
45569
|
// src/db/plan-project-links.ts
|
|
45437
45570
|
function getPlanProjectLinkReceipt(receiptId, db) {
|
|
45438
45571
|
const d = db || getDatabase();
|
|
@@ -46163,7 +46296,7 @@ function matchesExtraFilters(task2, filter) {
|
|
|
46163
46296
|
}
|
|
46164
46297
|
if (filter.tags?.length) {
|
|
46165
46298
|
const taskTags = new Set(task2.tags ?? []);
|
|
46166
|
-
if (!filter.tags.
|
|
46299
|
+
if (!filter.tags.some((tag) => taskTags.has(tag)))
|
|
46167
46300
|
return false;
|
|
46168
46301
|
}
|
|
46169
46302
|
if (filter.include_subtasks !== true && filter.parent_id === undefined && task2.parent_id)
|
|
@@ -46239,7 +46372,22 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
46239
46372
|
list: (projectId) => listPlans(projectId, database()),
|
|
46240
46373
|
update: (id, input) => updatePlan(id, input, database()),
|
|
46241
46374
|
completeAtRevision: (id, expectedUpdatedAt) => completePlanAtRevision(id, expectedUpdatedAt, database()),
|
|
46242
|
-
delete: (id) => deletePlan(id, database())
|
|
46375
|
+
delete: (id) => deletePlan(id, database()),
|
|
46376
|
+
addComment: (input) => addPlanComment(input, database()),
|
|
46377
|
+
getComments: (planId) => listPlanComments(planId, database()),
|
|
46378
|
+
getCommentsPage: (planId, options2) => {
|
|
46379
|
+
if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
|
|
46380
|
+
throw new Error("Plan comment limit must be an integer between 1 and 1001");
|
|
46381
|
+
}
|
|
46382
|
+
let comments = listPlanComments(planId, database());
|
|
46383
|
+
comments = comments.sort(compareCommentKeyset);
|
|
46384
|
+
if (options2?.before) {
|
|
46385
|
+
const before = options2.before;
|
|
46386
|
+
comments = comments.filter((comment) => isStrictlyOlder(comment, before));
|
|
46387
|
+
}
|
|
46388
|
+
const limit = options2?.limit ?? 100;
|
|
46389
|
+
return comments.slice(-limit);
|
|
46390
|
+
}
|
|
46243
46391
|
},
|
|
46244
46392
|
planProjectLinks: {
|
|
46245
46393
|
apply: (input) => applyPlanProjectLinkSqlite(input, database()),
|
|
@@ -46280,10 +46428,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
46280
46428
|
throw new Error("Comment limit must be an integer between 1 and 1001");
|
|
46281
46429
|
}
|
|
46282
46430
|
let comments = listComments(taskId, database());
|
|
46283
|
-
comments = comments.sort(
|
|
46431
|
+
comments = comments.sort(compareCommentKeyset);
|
|
46284
46432
|
if (options2?.before) {
|
|
46285
46433
|
const before = options2.before;
|
|
46286
|
-
comments = comments.filter((comment) => comment
|
|
46434
|
+
comments = comments.filter((comment) => isStrictlyOlder(comment, before));
|
|
46287
46435
|
}
|
|
46288
46436
|
if (options2?.limit !== undefined)
|
|
46289
46437
|
comments = comments.slice(-options2.limit);
|
|
@@ -46314,6 +46462,7 @@ var init_local_sqlite = __esm(() => {
|
|
|
46314
46462
|
init_tasks();
|
|
46315
46463
|
init_projects();
|
|
46316
46464
|
init_plans();
|
|
46465
|
+
init_plan_comments();
|
|
46317
46466
|
init_agents();
|
|
46318
46467
|
init_task_lists();
|
|
46319
46468
|
init_templates();
|
|
@@ -47226,7 +47375,26 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
47226
47375
|
list: async (projectId) => (await store.list("plans")).filter((plan) => projectId === undefined || plan.project_id === projectId).sort((a, b) => a.name.localeCompare(b.name)),
|
|
47227
47376
|
update: (id, input) => updatePlan2(id, input, store),
|
|
47228
47377
|
completeAtRevision: (id, expectedUpdatedAt, context) => store.completePlanAtRevision(id, expectedUpdatedAt, context),
|
|
47229
|
-
delete: (id, context) => store.deletePlan(id, context)
|
|
47378
|
+
delete: (id, context) => store.deletePlan(id, context),
|
|
47379
|
+
addComment: (input, context) => addPlanComment2(input, store, context),
|
|
47380
|
+
getComments: async (planId) => {
|
|
47381
|
+
const pages = [];
|
|
47382
|
+
let before;
|
|
47383
|
+
while (true) {
|
|
47384
|
+
const page = await store.listPlanComments(planId, { limit: 1000, ...before ? { before } : {} });
|
|
47385
|
+
if (page.length === 0)
|
|
47386
|
+
break;
|
|
47387
|
+
pages.unshift(page);
|
|
47388
|
+
if (page.length < 1000)
|
|
47389
|
+
break;
|
|
47390
|
+
const oldest = page[0];
|
|
47391
|
+
before = { created_at: oldest.created_at, id: oldest.id };
|
|
47392
|
+
}
|
|
47393
|
+
return pages.flat().map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
47394
|
+
},
|
|
47395
|
+
getCommentsPage: async (planId, options2) => {
|
|
47396
|
+
return (await store.listPlanComments(planId, options2)).map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
47397
|
+
}
|
|
47230
47398
|
},
|
|
47231
47399
|
planProjectLinks: {
|
|
47232
47400
|
apply: (input, context) => store.applyPlanProjectLink(input, context),
|
|
@@ -47320,11 +47488,16 @@ class PostgresJsonRecordStore {
|
|
|
47320
47488
|
return context?.requestId ?? this.sourceMachineId ?? null;
|
|
47321
47489
|
}
|
|
47322
47490
|
async ensureSchema() {
|
|
47323
|
-
this.schemaReady
|
|
47324
|
-
|
|
47325
|
-
|
|
47326
|
-
|
|
47327
|
-
|
|
47491
|
+
if (!this.schemaReady) {
|
|
47492
|
+
this.schemaReady = (async () => {
|
|
47493
|
+
for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
|
|
47494
|
+
await this.options.client.query(sql);
|
|
47495
|
+
}
|
|
47496
|
+
})().catch((error) => {
|
|
47497
|
+
this.schemaReady = null;
|
|
47498
|
+
throw error;
|
|
47499
|
+
});
|
|
47500
|
+
}
|
|
47328
47501
|
await this.schemaReady;
|
|
47329
47502
|
}
|
|
47330
47503
|
async get(type, id) {
|
|
@@ -47444,6 +47617,27 @@ class PostgresJsonRecordStore {
|
|
|
47444
47617
|
LIMIT $${params.length}`, params);
|
|
47445
47618
|
return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
|
|
47446
47619
|
}
|
|
47620
|
+
async listPlanComments(planId, options = {}) {
|
|
47621
|
+
await this.ensureSchema();
|
|
47622
|
+
const limit = options.limit ?? 100;
|
|
47623
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
|
|
47624
|
+
throw new Error("Postgres plan comment limit must be an integer between 1 and 1001");
|
|
47625
|
+
}
|
|
47626
|
+
const params = [this.service, planId];
|
|
47627
|
+
let cursorPredicate = "";
|
|
47628
|
+
if (options.before) {
|
|
47629
|
+
params.push(options.before.created_at, options.before.id);
|
|
47630
|
+
cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
|
|
47631
|
+
}
|
|
47632
|
+
params.push(limit);
|
|
47633
|
+
const result = await this.options.client.query(`/* todos:list-plan-comments */ SELECT payload FROM ${this.tableName}
|
|
47634
|
+
WHERE service = $1 AND object_type = 'plan_comments' AND deleted_at IS NULL
|
|
47635
|
+
AND payload->>'plan_id' = $2
|
|
47636
|
+
${cursorPredicate}
|
|
47637
|
+
ORDER BY payload->>'created_at' DESC, object_id DESC
|
|
47638
|
+
LIMIT $${params.length}`, params);
|
|
47639
|
+
return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
|
|
47640
|
+
}
|
|
47447
47641
|
async listRecords(type) {
|
|
47448
47642
|
await this.ensureSchema();
|
|
47449
47643
|
const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
|
|
@@ -48605,6 +48799,15 @@ class PostgresJsonRecordStore {
|
|
|
48605
48799
|
WHERE r.service = $1 AND r.object_type = 'tasks' AND r.deleted_at IS NULL
|
|
48606
48800
|
AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
|
|
48607
48801
|
RETURNING 1
|
|
48802
|
+
), deleted_plan_comments AS (
|
|
48803
|
+
UPDATE ${this.tableName} r SET
|
|
48804
|
+
deleted_at = $3::timestamptz,
|
|
48805
|
+
updated_at = $3::timestamptz,
|
|
48806
|
+
source_machine_id = COALESCE($4, r.source_machine_id),
|
|
48807
|
+
version = COALESCE(r.version, 0) + 1
|
|
48808
|
+
WHERE r.service = $1 AND r.object_type = 'plan_comments' AND r.deleted_at IS NULL
|
|
48809
|
+
AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
|
|
48810
|
+
RETURNING 1
|
|
48608
48811
|
), deleted_plan AS (
|
|
48609
48812
|
UPDATE ${this.tableName} r SET
|
|
48610
48813
|
deleted_at = $3::timestamptz,
|
|
@@ -48780,7 +48983,8 @@ async function updateTask2(id, input, store, context) {
|
|
|
48780
48983
|
task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
|
|
48781
48984
|
parent_id: input.parent_id !== undefined ? input.parent_id : existing.parent_id,
|
|
48782
48985
|
created_by: existing.created_by,
|
|
48783
|
-
completed_at:
|
|
48986
|
+
completed_at: input.status === "completed" ? input.completed_at ?? new Date().toISOString() : input.completed_at !== undefined ? input.completed_at : input.status !== undefined && isTerminalStatus(input.status) ? existing.completed_at ?? new Date().toISOString() : reopened ? null : existing.completed_at,
|
|
48987
|
+
started_at: input.status === "in_progress" ? existing.started_at ?? new Date().toISOString() : existing.started_at
|
|
48784
48988
|
};
|
|
48785
48989
|
const storedTask = await store.upsertTaskWithPlanMembershipGuard(task2, [existing.plan_id, effectivePlanId].filter((planId) => Boolean(planId)), input.project_id !== undefined, context, {
|
|
48786
48990
|
operation: "update",
|
|
@@ -48825,7 +49029,8 @@ async function failTask2(id, agentId, reason, options, store) {
|
|
|
48825
49029
|
status: "failed",
|
|
48826
49030
|
assigned_to: task2.assigned_to ?? agentId ?? null,
|
|
48827
49031
|
reason: reason ?? task2.reason,
|
|
48828
|
-
retry_after: options?.retry_after ?? task2.retry_after
|
|
49032
|
+
retry_after: options?.retry_after ?? task2.retry_after,
|
|
49033
|
+
completed_at: task2.completed_at ?? new Date().toISOString()
|
|
48829
49034
|
}, store);
|
|
48830
49035
|
if (!options?.retry)
|
|
48831
49036
|
return { task: failed };
|
|
@@ -49054,7 +49259,7 @@ async function getActiveWork2(filters, store) {
|
|
|
49054
49259
|
}));
|
|
49055
49260
|
}
|
|
49056
49261
|
async function getChangedSince(since, filters, store) {
|
|
49057
|
-
return (await listTasks3(filters ?? {}, store)).filter((task2) => task2.updated_at
|
|
49262
|
+
return (await listTasks3(filters ?? {}, store)).filter((task2) => changedSinceStampNewer(task2.updated_at ?? "", since));
|
|
49058
49263
|
}
|
|
49059
49264
|
async function createProject2(input, store, context) {
|
|
49060
49265
|
const timestamp4 = new Date().toISOString();
|
|
@@ -49270,16 +49475,22 @@ async function createTaskList2(input, store, context) {
|
|
|
49270
49475
|
async function updateTaskList2(id, input, store) {
|
|
49271
49476
|
const list = await requireRecord("task_lists", id, store);
|
|
49272
49477
|
const patch = definedPatch(input);
|
|
49478
|
+
const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? list.project_id : input.project_id;
|
|
49273
49479
|
if (input.slug !== undefined) {
|
|
49274
49480
|
const slug = slugifyRaw(input.slug);
|
|
49275
49481
|
if (!slug)
|
|
49276
49482
|
throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
|
|
49277
|
-
|
|
49483
|
+
patch.slug = slug;
|
|
49484
|
+
}
|
|
49485
|
+
const effectiveSlug = patch.slug ?? list.slug;
|
|
49486
|
+
if (projectId !== list.project_id || effectiveSlug !== list.slug) {
|
|
49487
|
+
const duplicate = (await store.list("task_lists")).find((candidate) => candidate.id !== id && candidate.project_id === projectId && candidate.slug === effectiveSlug);
|
|
49278
49488
|
if (duplicate) {
|
|
49279
|
-
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${
|
|
49489
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
|
|
49280
49490
|
}
|
|
49281
|
-
patch.slug = slug;
|
|
49282
49491
|
}
|
|
49492
|
+
if (projectId !== list.project_id)
|
|
49493
|
+
patch.project_id = projectId;
|
|
49283
49494
|
return store.upsert("task_lists", {
|
|
49284
49495
|
...list,
|
|
49285
49496
|
...patch,
|
|
@@ -49372,6 +49583,22 @@ async function addComment2(input, store, context) {
|
|
|
49372
49583
|
function redactComment2(comment) {
|
|
49373
49584
|
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
49374
49585
|
}
|
|
49586
|
+
async function addPlanComment2(input, store, context) {
|
|
49587
|
+
const comment = {
|
|
49588
|
+
id: randomUUID4(),
|
|
49589
|
+
plan_id: input.plan_id,
|
|
49590
|
+
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
49591
|
+
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
49592
|
+
content: redactEvidenceText(input.content),
|
|
49593
|
+
type: input.type ?? "comment",
|
|
49594
|
+
progress_pct: input.progress_pct ?? null,
|
|
49595
|
+
created_at: new Date().toISOString()
|
|
49596
|
+
};
|
|
49597
|
+
return store.upsert("plan_comments", comment, context);
|
|
49598
|
+
}
|
|
49599
|
+
function redactPlanComment(comment) {
|
|
49600
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
49601
|
+
}
|
|
49375
49602
|
async function exportSnapshot(store) {
|
|
49376
49603
|
return {
|
|
49377
49604
|
exportedAt: new Date().toISOString(),
|
|
@@ -49591,6 +49818,7 @@ var CLOUD_LOCK_EXPIRY_MINUTES = 30, TASK_ORDER_TIEBREAK = "CASE payload->>'prior
|
|
|
49591
49818
|
var init_postgres_adapter = __esm(() => {
|
|
49592
49819
|
init_types();
|
|
49593
49820
|
init_creator_identity();
|
|
49821
|
+
init_instant_compare();
|
|
49594
49822
|
init_plan_project_link_contract();
|
|
49595
49823
|
init_stale_lock_handoff();
|
|
49596
49824
|
init_postgres_sync();
|
|
@@ -50021,10 +50249,19 @@ class PostgresPrGroupLedgerPersistence {
|
|
|
50021
50249
|
this.client = client;
|
|
50022
50250
|
}
|
|
50023
50251
|
async ensureSchema() {
|
|
50024
|
-
this.schemaReady
|
|
50025
|
-
|
|
50026
|
-
|
|
50027
|
-
|
|
50252
|
+
if (this.schemaReady === null) {
|
|
50253
|
+
const attempt = (async () => {
|
|
50254
|
+
for (const statement of postgresPrGroupSchemaSql())
|
|
50255
|
+
await this.client.query(statement);
|
|
50256
|
+
})();
|
|
50257
|
+
this.schemaReady = attempt;
|
|
50258
|
+
try {
|
|
50259
|
+
await attempt;
|
|
50260
|
+
} catch (error) {
|
|
50261
|
+
this.schemaReady = null;
|
|
50262
|
+
throw error;
|
|
50263
|
+
}
|
|
50264
|
+
}
|
|
50028
50265
|
return this.schemaReady;
|
|
50029
50266
|
}
|
|
50030
50267
|
async transaction(fn) {
|
|
@@ -50442,14 +50679,23 @@ class PostgresTodosProjectRegistrationBackend {
|
|
|
50442
50679
|
this.cursorTableName = safeIdentifier(options.cursorTableName ?? "todos_sync_cursors", "cursorTableName");
|
|
50443
50680
|
}
|
|
50444
50681
|
async ensureSchema() {
|
|
50445
|
-
this.schemaReady
|
|
50446
|
-
|
|
50447
|
-
|
|
50448
|
-
|
|
50449
|
-
|
|
50450
|
-
|
|
50682
|
+
if (this.schemaReady === null) {
|
|
50683
|
+
const attempt = (async () => {
|
|
50684
|
+
for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
|
|
50685
|
+
await this.client.query(statement);
|
|
50686
|
+
}
|
|
50687
|
+
for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
|
|
50688
|
+
await this.client.query(statement);
|
|
50689
|
+
}
|
|
50690
|
+
})();
|
|
50691
|
+
this.schemaReady = attempt;
|
|
50692
|
+
try {
|
|
50693
|
+
await attempt;
|
|
50694
|
+
} catch (error) {
|
|
50695
|
+
this.schemaReady = null;
|
|
50696
|
+
throw error;
|
|
50451
50697
|
}
|
|
50452
|
-
}
|
|
50698
|
+
}
|
|
50453
50699
|
await this.schemaReady;
|
|
50454
50700
|
}
|
|
50455
50701
|
async transaction(fn) {
|
|
@@ -53159,12 +53405,21 @@ class PostgresTodosTaskManifestBackend {
|
|
|
53159
53405
|
this.tenantId = options.tenantId ?? "default";
|
|
53160
53406
|
}
|
|
53161
53407
|
async ensureSchema() {
|
|
53162
|
-
this.schemaReady
|
|
53163
|
-
|
|
53164
|
-
|
|
53165
|
-
|
|
53166
|
-
|
|
53167
|
-
|
|
53408
|
+
if (this.schemaReady === null) {
|
|
53409
|
+
const attempt = (async () => {
|
|
53410
|
+
for (const sql of postgresTodosSyncSchemaSql(this.tableName))
|
|
53411
|
+
await this.client.query(sql);
|
|
53412
|
+
for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
|
|
53413
|
+
await this.client.query(sql);
|
|
53414
|
+
})();
|
|
53415
|
+
this.schemaReady = attempt;
|
|
53416
|
+
try {
|
|
53417
|
+
await attempt;
|
|
53418
|
+
} catch (error) {
|
|
53419
|
+
this.schemaReady = null;
|
|
53420
|
+
throw error;
|
|
53421
|
+
}
|
|
53422
|
+
}
|
|
53168
53423
|
await this.schemaReady;
|
|
53169
53424
|
}
|
|
53170
53425
|
async insertSync(tx, objectType2, objectId, payload, now4) {
|
|
@@ -54191,6 +54446,933 @@ var init_task_manifest = __esm(() => {
|
|
|
54191
54446
|
init_types5();
|
|
54192
54447
|
});
|
|
54193
54448
|
|
|
54449
|
+
// src/task-subtree-transfer/types.ts
|
|
54450
|
+
var TODOS_TASK_SUBTREE_TRANSFER_ROUTE = "todos.task-subtree-transfer.v1", TODOS_TASK_SUBTREE_TRANSFER_SCHEMA_VERSION = 1, TodosTaskSubtreeTransferError;
|
|
54451
|
+
var init_types6 = __esm(() => {
|
|
54452
|
+
TodosTaskSubtreeTransferError = class TodosTaskSubtreeTransferError extends Error {
|
|
54453
|
+
code;
|
|
54454
|
+
details;
|
|
54455
|
+
constructor(code, message, details = {}) {
|
|
54456
|
+
super(message);
|
|
54457
|
+
this.code = code;
|
|
54458
|
+
this.details = details;
|
|
54459
|
+
this.name = "TodosTaskSubtreeTransferError";
|
|
54460
|
+
}
|
|
54461
|
+
};
|
|
54462
|
+
});
|
|
54463
|
+
|
|
54464
|
+
// src/task-subtree-transfer/canonical.ts
|
|
54465
|
+
function taskSubtreeTransferRequestDigest(input) {
|
|
54466
|
+
const { idempotency_key: _idempotencyKey, ...request } = input;
|
|
54467
|
+
return canonicalDigest(request);
|
|
54468
|
+
}
|
|
54469
|
+
function taskSubtreeTransferRollbackRequestDigest(input) {
|
|
54470
|
+
const { idempotency_key: _idempotencyKey, ...request } = input;
|
|
54471
|
+
return canonicalDigest(request);
|
|
54472
|
+
}
|
|
54473
|
+
function deriveTodosTaskSubtreeTransferIdempotencyKey(input) {
|
|
54474
|
+
return `tstk_${canonicalDigest({
|
|
54475
|
+
route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
|
|
54476
|
+
...input
|
|
54477
|
+
}).slice(0, 48)}`;
|
|
54478
|
+
}
|
|
54479
|
+
var init_canonical2 = __esm(() => {
|
|
54480
|
+
init_canonical();
|
|
54481
|
+
init_types6();
|
|
54482
|
+
});
|
|
54483
|
+
|
|
54484
|
+
// src/task-subtree-transfer/planner.ts
|
|
54485
|
+
function sortedTaskImage(task3) {
|
|
54486
|
+
return {
|
|
54487
|
+
task_id: task3.task_id,
|
|
54488
|
+
project_id: task3.project_id,
|
|
54489
|
+
parent_id: task3.parent_id,
|
|
54490
|
+
plan_id: task3.plan_id,
|
|
54491
|
+
task_list_id: task3.task_list_id,
|
|
54492
|
+
version: task3.version,
|
|
54493
|
+
updated_at: task3.updated_at
|
|
54494
|
+
};
|
|
54495
|
+
}
|
|
54496
|
+
function sourcePopulationDigest(tasks) {
|
|
54497
|
+
return canonicalDigest(tasks.map((task3) => ({
|
|
54498
|
+
...sortedTaskImage(task3),
|
|
54499
|
+
archived_at: task3.archived_at ?? null
|
|
54500
|
+
})).sort((left, right) => left.task_id.localeCompare(right.task_id)));
|
|
54501
|
+
}
|
|
54502
|
+
function closure(tasks, rootTaskId) {
|
|
54503
|
+
const byId = new Map(tasks.map((task3) => [task3.task_id, task3]));
|
|
54504
|
+
if (!byId.has(rootTaskId)) {
|
|
54505
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_NOT_FOUND", `Root task not found in the exact source project: ${rootTaskId}`, { root_task_id: rootTaskId });
|
|
54506
|
+
}
|
|
54507
|
+
const children = new Map;
|
|
54508
|
+
for (const task3 of tasks) {
|
|
54509
|
+
if (!task3.parent_id)
|
|
54510
|
+
continue;
|
|
54511
|
+
const siblings = children.get(task3.parent_id) ?? [];
|
|
54512
|
+
siblings.push(task3.task_id);
|
|
54513
|
+
children.set(task3.parent_id, siblings);
|
|
54514
|
+
}
|
|
54515
|
+
for (const ids2 of children.values())
|
|
54516
|
+
ids2.sort();
|
|
54517
|
+
const visiting = new Set;
|
|
54518
|
+
const visited = new Set;
|
|
54519
|
+
const result = [];
|
|
54520
|
+
const visit = (id) => {
|
|
54521
|
+
if (visiting.has(id)) {
|
|
54522
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_HIERARCHY_CYCLE", "Task hierarchy contains a cycle in the requested descendant closure", { task_id: id });
|
|
54523
|
+
}
|
|
54524
|
+
if (visited.has(id))
|
|
54525
|
+
return;
|
|
54526
|
+
const task3 = byId.get(id);
|
|
54527
|
+
if (!task3) {
|
|
54528
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_FOREIGN_REFERENCE", "Task hierarchy references a parent outside the exact source population", { task_id: id });
|
|
54529
|
+
}
|
|
54530
|
+
visiting.add(id);
|
|
54531
|
+
result.push(task3);
|
|
54532
|
+
for (const child of children.get(id) ?? [])
|
|
54533
|
+
visit(child);
|
|
54534
|
+
visiting.delete(id);
|
|
54535
|
+
visited.add(id);
|
|
54536
|
+
};
|
|
54537
|
+
visit(rootTaskId);
|
|
54538
|
+
return result.sort((left, right) => left.task_id.localeCompare(right.task_id));
|
|
54539
|
+
}
|
|
54540
|
+
function prepareTransfer(snapshot, input) {
|
|
54541
|
+
if (!snapshot.destination_project_found || !snapshot.destination_task_list_found || !snapshot.destination_parent_found) {
|
|
54542
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_FOREIGN_REFERENCE", "Destination project, task list, or parent does not match the exact requested target");
|
|
54543
|
+
}
|
|
54544
|
+
const moved = closure(snapshot.source_tasks, input.root_task_id);
|
|
54545
|
+
const movedIds = new Set(moved.map((task3) => task3.task_id));
|
|
54546
|
+
if (input.destination_parent_id && movedIds.has(input.destination_parent_id)) {
|
|
54547
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_HIERARCHY_CYCLE", "Destination parent cannot be inside the transferred subtree", { destination_parent_id: input.destination_parent_id });
|
|
54548
|
+
}
|
|
54549
|
+
const planIds = [...new Set(moved.map((task3) => task3.plan_id).filter((id) => Boolean(id)))].sort();
|
|
54550
|
+
const allPlanTasks = new Map;
|
|
54551
|
+
for (const task3 of snapshot.plan_tasks) {
|
|
54552
|
+
if (!task3.plan_id)
|
|
54553
|
+
continue;
|
|
54554
|
+
const rows = allPlanTasks.get(task3.plan_id) ?? [];
|
|
54555
|
+
rows.push(task3);
|
|
54556
|
+
allPlanTasks.set(task3.plan_id, rows);
|
|
54557
|
+
}
|
|
54558
|
+
const contained = [];
|
|
54559
|
+
const shared = [];
|
|
54560
|
+
const planById = new Map(snapshot.plans.map((plan) => [plan.plan_id, plan]));
|
|
54561
|
+
for (const planId of planIds) {
|
|
54562
|
+
const plan = planById.get(planId);
|
|
54563
|
+
if (!plan || plan.project_id !== input.source_project_id) {
|
|
54564
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "A moved task references a plan absent from the exact source project", { plan_id: planId });
|
|
54565
|
+
}
|
|
54566
|
+
const members = allPlanTasks.get(planId) ?? [];
|
|
54567
|
+
if (members.length === 0) {
|
|
54568
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "A moved task references a plan with no authoritative task membership", { plan_id: planId });
|
|
54569
|
+
}
|
|
54570
|
+
if (members.every((task3) => movedIds.has(task3.task_id)))
|
|
54571
|
+
contained.push(planId);
|
|
54572
|
+
else
|
|
54573
|
+
shared.push(planId);
|
|
54574
|
+
}
|
|
54575
|
+
const inspection = {
|
|
54576
|
+
source_project_id: input.source_project_id,
|
|
54577
|
+
destination_project_id: input.destination_project_id,
|
|
54578
|
+
destination_task_list_id: input.destination_task_list_id,
|
|
54579
|
+
root_task_id: input.root_task_id,
|
|
54580
|
+
destination_parent_id: input.destination_parent_id,
|
|
54581
|
+
expected_root_parent_id: moved.find((task3) => task3.task_id === input.root_task_id).parent_id,
|
|
54582
|
+
source_population_digest: sourcePopulationDigest(snapshot.source_tasks),
|
|
54583
|
+
expected_tasks: moved.map((task3) => ({ task_id: task3.task_id, version: task3.version })),
|
|
54584
|
+
contained_plan_ids: contained,
|
|
54585
|
+
shared_plan_ids: shared,
|
|
54586
|
+
complete: true
|
|
54587
|
+
};
|
|
54588
|
+
return {
|
|
54589
|
+
inspection,
|
|
54590
|
+
prior_tasks: moved.map(sortedTaskImage),
|
|
54591
|
+
prior_plans: contained.map((id) => planById.get(id)),
|
|
54592
|
+
task_plan_targets: new Map(moved.map((task3) => [task3.task_id, task3.plan_id]))
|
|
54593
|
+
};
|
|
54594
|
+
}
|
|
54595
|
+
function validateApplySnapshot(prepared, request, plans) {
|
|
54596
|
+
if (request.expected_root_parent_id !== prepared.inspection.expected_root_parent_id) {
|
|
54597
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Root parent changed since inspection");
|
|
54598
|
+
}
|
|
54599
|
+
if (request.source_population_digest !== prepared.inspection.source_population_digest) {
|
|
54600
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_POPULATION_DRIFT", "Complete source-project task population changed since inspection");
|
|
54601
|
+
}
|
|
54602
|
+
const expected = request.expected_tasks.map((task3) => ({ task_id: task3.task_id, version: task3.version })).sort((left, right) => left.task_id.localeCompare(right.task_id));
|
|
54603
|
+
if (JSON.stringify(expected) !== JSON.stringify(prepared.inspection.expected_tasks)) {
|
|
54604
|
+
const actualById = new Map(prepared.inspection.expected_tasks.map((task3) => [task3.task_id, task3.version]));
|
|
54605
|
+
const sameIds = expected.length === prepared.inspection.expected_tasks.length && expected.every((task3) => actualById.has(task3.task_id));
|
|
54606
|
+
throw new TodosTaskSubtreeTransferError(sameIds ? "TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT" : "TODOS_TASK_SUBTREE_TRANSFER_CLOSURE_DRIFT", sameIds ? "At least one exact task version changed since inspection" : "Exact descendant closure changed since inspection");
|
|
54607
|
+
}
|
|
54608
|
+
const requestedSources = request.shared_plan_splits.map((mapping) => mapping.source_plan_id).sort();
|
|
54609
|
+
if (JSON.stringify(requestedSources) !== JSON.stringify(prepared.inspection.shared_plan_ids)) {
|
|
54610
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PARTIAL_PLAN", "Shared plans require one exact explicit destination-plan mapping each", {
|
|
54611
|
+
expected_shared_plan_ids: prepared.inspection.shared_plan_ids,
|
|
54612
|
+
received_shared_plan_ids: requestedSources
|
|
54613
|
+
});
|
|
54614
|
+
}
|
|
54615
|
+
const planById = new Map(plans.map((plan) => [plan.plan_id, plan]));
|
|
54616
|
+
const destinationIds = new Set;
|
|
54617
|
+
for (const mapping of request.shared_plan_splits) {
|
|
54618
|
+
if (destinationIds.has(mapping.destination_plan_id)) {
|
|
54619
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "Two shared source plans cannot collapse into one destination plan");
|
|
54620
|
+
}
|
|
54621
|
+
destinationIds.add(mapping.destination_plan_id);
|
|
54622
|
+
const destination = planById.get(mapping.destination_plan_id);
|
|
54623
|
+
if (!destination || destination.project_id !== request.destination_project_id || destination.task_list_id !== request.destination_task_list_id) {
|
|
54624
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "Shared-plan destination does not match the exact destination project and task list", { destination_plan_id: mapping.destination_plan_id });
|
|
54625
|
+
}
|
|
54626
|
+
for (const [taskId, planId] of prepared.task_plan_targets) {
|
|
54627
|
+
if (planId === mapping.source_plan_id) {
|
|
54628
|
+
prepared.task_plan_targets.set(taskId, mapping.destination_plan_id);
|
|
54629
|
+
}
|
|
54630
|
+
}
|
|
54631
|
+
}
|
|
54632
|
+
}
|
|
54633
|
+
var init_planner = __esm(() => {
|
|
54634
|
+
init_canonical2();
|
|
54635
|
+
init_types6();
|
|
54636
|
+
});
|
|
54637
|
+
|
|
54638
|
+
// src/task-subtree-transfer/schema-sql.ts
|
|
54639
|
+
function postgresTodosTaskSubtreeTransferSchemaSql() {
|
|
54640
|
+
return [
|
|
54641
|
+
`CREATE TABLE IF NOT EXISTS todos_task_subtree_transfer_receipts (
|
|
54642
|
+
receipt_id text PRIMARY KEY,
|
|
54643
|
+
tenant_id text NOT NULL,
|
|
54644
|
+
kind text NOT NULL CHECK(kind IN ('apply', 'rollback')),
|
|
54645
|
+
operation_id text NOT NULL,
|
|
54646
|
+
step_id text NOT NULL,
|
|
54647
|
+
idempotency_key text NOT NULL,
|
|
54648
|
+
request_digest text NOT NULL,
|
|
54649
|
+
precondition_digest text NOT NULL,
|
|
54650
|
+
result_digest text NOT NULL,
|
|
54651
|
+
apply_receipt_id text,
|
|
54652
|
+
request_json jsonb NOT NULL,
|
|
54653
|
+
result_json jsonb NOT NULL,
|
|
54654
|
+
created_at timestamptz NOT NULL,
|
|
54655
|
+
UNIQUE(tenant_id, kind, operation_id, step_id),
|
|
54656
|
+
UNIQUE(tenant_id, kind, idempotency_key)
|
|
54657
|
+
)`,
|
|
54658
|
+
`CREATE OR REPLACE FUNCTION reject_todos_task_subtree_transfer_receipt_mutation()
|
|
54659
|
+
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
54660
|
+
BEGIN
|
|
54661
|
+
RAISE EXCEPTION 'todos task subtree transfer receipts are immutable';
|
|
54662
|
+
END $$`,
|
|
54663
|
+
`DROP TRIGGER IF EXISTS todos_task_subtree_transfer_receipts_immutable_update
|
|
54664
|
+
ON todos_task_subtree_transfer_receipts`,
|
|
54665
|
+
`CREATE TRIGGER todos_task_subtree_transfer_receipts_immutable_update
|
|
54666
|
+
BEFORE UPDATE OR DELETE ON todos_task_subtree_transfer_receipts
|
|
54667
|
+
FOR EACH ROW EXECUTE FUNCTION reject_todos_task_subtree_transfer_receipt_mutation()`
|
|
54668
|
+
];
|
|
54669
|
+
}
|
|
54670
|
+
|
|
54671
|
+
// src/task-subtree-transfer/postgres.ts
|
|
54672
|
+
function safeIdentifier3(value, fallback) {
|
|
54673
|
+
const candidate = value ?? fallback;
|
|
54674
|
+
if (!/^[a-z_][a-z0-9_]*$/.test(candidate)) {
|
|
54675
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "PostgreSQL table name must be a safe identifier");
|
|
54676
|
+
}
|
|
54677
|
+
return candidate;
|
|
54678
|
+
}
|
|
54679
|
+
function timestamp5(value) {
|
|
54680
|
+
return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
|
|
54681
|
+
}
|
|
54682
|
+
function payloadOf(row) {
|
|
54683
|
+
return typeof row.payload === "string" ? JSON.parse(row.payload) : row.payload;
|
|
54684
|
+
}
|
|
54685
|
+
function taskRecord(row) {
|
|
54686
|
+
const payload = payloadOf(row);
|
|
54687
|
+
return {
|
|
54688
|
+
task_id: String(payload.id ?? row.object_id),
|
|
54689
|
+
project_id: payload.project_id == null ? null : String(payload.project_id),
|
|
54690
|
+
parent_id: payload.parent_id == null ? null : String(payload.parent_id),
|
|
54691
|
+
plan_id: payload.plan_id == null ? null : String(payload.plan_id),
|
|
54692
|
+
task_list_id: payload.task_list_id == null ? null : String(payload.task_list_id),
|
|
54693
|
+
version: Number(payload.version ?? row.version ?? 1),
|
|
54694
|
+
updated_at: timestamp5(payload.updated_at ?? row.updated_at),
|
|
54695
|
+
archived_at: payload.archived_at == null ? null : String(payload.archived_at)
|
|
54696
|
+
};
|
|
54697
|
+
}
|
|
54698
|
+
function planRecord(row) {
|
|
54699
|
+
const payload = payloadOf(row);
|
|
54700
|
+
return {
|
|
54701
|
+
plan_id: String(payload.id ?? row.object_id),
|
|
54702
|
+
project_id: payload.project_id == null ? null : String(payload.project_id),
|
|
54703
|
+
task_list_id: payload.task_list_id == null ? null : String(payload.task_list_id),
|
|
54704
|
+
updated_at: timestamp5(payload.updated_at ?? row.updated_at)
|
|
54705
|
+
};
|
|
54706
|
+
}
|
|
54707
|
+
|
|
54708
|
+
class PostgresTodosTaskSubtreeTransferBackend {
|
|
54709
|
+
client;
|
|
54710
|
+
kind = "postgresql";
|
|
54711
|
+
service;
|
|
54712
|
+
tableName;
|
|
54713
|
+
tenantId;
|
|
54714
|
+
schemaReady = null;
|
|
54715
|
+
constructor(client, options = {}) {
|
|
54716
|
+
this.client = client;
|
|
54717
|
+
this.service = options.service ?? "todos";
|
|
54718
|
+
this.tableName = safeIdentifier3(options.tableName, DEFAULT_TODOS_POSTGRES_SYNC_TABLE);
|
|
54719
|
+
this.tenantId = options.tenantId ?? "default";
|
|
54720
|
+
}
|
|
54721
|
+
async ensureSchema() {
|
|
54722
|
+
if (this.schemaReady === null) {
|
|
54723
|
+
const attempt = (async () => {
|
|
54724
|
+
for (const sql of postgresTodosSyncSchemaSql(this.tableName))
|
|
54725
|
+
await this.client.query(sql);
|
|
54726
|
+
for (const sql of postgresTodosTaskSubtreeTransferSchemaSql())
|
|
54727
|
+
await this.client.query(sql);
|
|
54728
|
+
})();
|
|
54729
|
+
this.schemaReady = attempt;
|
|
54730
|
+
try {
|
|
54731
|
+
await attempt;
|
|
54732
|
+
} catch (error) {
|
|
54733
|
+
this.schemaReady = null;
|
|
54734
|
+
throw error;
|
|
54735
|
+
}
|
|
54736
|
+
}
|
|
54737
|
+
await this.schemaReady;
|
|
54738
|
+
}
|
|
54739
|
+
async snapshot(client, input, forUpdate = false) {
|
|
54740
|
+
const lock = forUpdate ? " FOR UPDATE" : "";
|
|
54741
|
+
const source3 = await client.query(`SELECT object_id, payload, updated_at, version FROM ${this.tableName}
|
|
54742
|
+
WHERE service = $1 AND object_type = 'tasks' AND deleted_at IS NULL
|
|
54743
|
+
AND payload->>'project_id' = $2 ORDER BY object_id${lock}`, [this.service, input.source_project_id]);
|
|
54744
|
+
const allTasks = await client.query(`SELECT object_id, payload, updated_at, version FROM ${this.tableName}
|
|
54745
|
+
WHERE service = $1 AND object_type = 'tasks' AND deleted_at IS NULL ORDER BY object_id${lock}`, [this.service]);
|
|
54746
|
+
const plans = await client.query(`SELECT object_id, payload, updated_at, version FROM ${this.tableName}
|
|
54747
|
+
WHERE service = $1 AND object_type = 'plans' AND deleted_at IS NULL ORDER BY object_id${lock}`, [this.service]);
|
|
54748
|
+
const destinationProject = await client.query(`SELECT 1 FROM ${this.tableName}
|
|
54749
|
+
WHERE service = $1 AND object_type = 'projects' AND object_id = $2
|
|
54750
|
+
AND deleted_at IS NULL LIMIT 1${lock}`, [this.service, input.destination_project_id]);
|
|
54751
|
+
const destinationList = await client.query(`SELECT 1 FROM ${this.tableName}
|
|
54752
|
+
WHERE service = $1 AND object_type = 'task_lists' AND object_id = $2
|
|
54753
|
+
AND deleted_at IS NULL AND payload->>'project_id' = $3 LIMIT 1`, [this.service, input.destination_task_list_id, input.destination_project_id]);
|
|
54754
|
+
const destinationParent = input.destination_parent_id === null ? true : (await client.query(`SELECT 1 FROM ${this.tableName}
|
|
54755
|
+
WHERE service = $1 AND object_type = 'tasks' AND object_id = $2
|
|
54756
|
+
AND deleted_at IS NULL AND payload->>'project_id' = $3 LIMIT 1`, [this.service, input.destination_parent_id, input.destination_project_id])).rows.length === 1;
|
|
54757
|
+
return {
|
|
54758
|
+
source_tasks: source3.rows.map(taskRecord),
|
|
54759
|
+
plan_tasks: allTasks.rows.map(taskRecord),
|
|
54760
|
+
plans: plans.rows.map(planRecord),
|
|
54761
|
+
destination_project_found: destinationProject.rows.length === 1,
|
|
54762
|
+
destination_task_list_found: destinationList.rows.length === 1,
|
|
54763
|
+
destination_parent_found: destinationParent
|
|
54764
|
+
};
|
|
54765
|
+
}
|
|
54766
|
+
async inspect(input) {
|
|
54767
|
+
await this.ensureSchema();
|
|
54768
|
+
return prepareTransfer(await this.snapshot(this.client, input), input).inspection;
|
|
54769
|
+
}
|
|
54770
|
+
async insertPayload(client, task3, update, now4) {
|
|
54771
|
+
const current = await client.query(`SELECT payload FROM ${this.tableName}
|
|
54772
|
+
WHERE service = $1 AND object_type = 'tasks' AND object_id = $2
|
|
54773
|
+
AND deleted_at IS NULL FOR UPDATE`, [this.service, task3.task_id]);
|
|
54774
|
+
const row = current.rows[0];
|
|
54775
|
+
if (!row)
|
|
54776
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_NOT_FOUND", `Task not found: ${task3.task_id}`);
|
|
54777
|
+
const payload = payloadOf(row);
|
|
54778
|
+
if (Number(payload.version ?? 1) !== task3.version) {
|
|
54779
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Task changed during transfer", { task_id: task3.task_id });
|
|
54780
|
+
}
|
|
54781
|
+
const next = {
|
|
54782
|
+
...payload,
|
|
54783
|
+
...update,
|
|
54784
|
+
version: task3.version + 1,
|
|
54785
|
+
updated_at: now4
|
|
54786
|
+
};
|
|
54787
|
+
const result = await client.query(`UPDATE ${this.tableName} SET payload = $1::jsonb, updated_at = $2, version = $3
|
|
54788
|
+
WHERE service = $4 AND object_type = 'tasks' AND object_id = $5
|
|
54789
|
+
AND deleted_at IS NULL AND version = $6
|
|
54790
|
+
RETURNING object_id`, [canonicalJson(next), now4, task3.version + 1, this.service, task3.task_id, task3.version]);
|
|
54791
|
+
if (result.rows.length !== 1) {
|
|
54792
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Task CAS failed", { task_id: task3.task_id });
|
|
54793
|
+
}
|
|
54794
|
+
}
|
|
54795
|
+
async apply(input, options) {
|
|
54796
|
+
await this.ensureSchema();
|
|
54797
|
+
if (!this.client.transaction) {
|
|
54798
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ATOMICITY_UNAVAILABLE", "PostgreSQL subtree transfer requires an authoritative transaction-capable client");
|
|
54799
|
+
}
|
|
54800
|
+
return this.client.transaction(async (tx) => {
|
|
54801
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended('todos:task-parent-integrity', 0))");
|
|
54802
|
+
const requestDigest = taskSubtreeTransferRequestDigest(input);
|
|
54803
|
+
const existing = await tx.query(`SELECT result_json, request_digest, precondition_digest, idempotency_key
|
|
54804
|
+
FROM todos_task_subtree_transfer_receipts
|
|
54805
|
+
WHERE tenant_id = $1 AND kind = 'apply'
|
|
54806
|
+
AND (idempotency_key = $2 OR (operation_id = $3 AND step_id = $4))
|
|
54807
|
+
ORDER BY created_at ASC LIMIT 1`, [this.tenantId, input.idempotency_key, input.operation_id, input.step_id]);
|
|
54808
|
+
if (existing.rows[0]) {
|
|
54809
|
+
const row = existing.rows[0];
|
|
54810
|
+
if (row.request_digest !== requestDigest || row.precondition_digest !== input.precondition_digest || row.idempotency_key !== input.idempotency_key) {
|
|
54811
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_CONFLICT", "The operation was already accepted for a different request");
|
|
54812
|
+
}
|
|
54813
|
+
return { ...typeof row.result_json === "string" ? JSON.parse(row.result_json) : row.result_json, duplicate: true };
|
|
54814
|
+
}
|
|
54815
|
+
const snapshot = await this.snapshot(tx, input, true);
|
|
54816
|
+
const prepared = prepareTransfer(snapshot, input);
|
|
54817
|
+
validateApplySnapshot(prepared, input, snapshot.plans);
|
|
54818
|
+
const now4 = options.now?.() ?? new Date().toISOString();
|
|
54819
|
+
const priorImage = {
|
|
54820
|
+
tasks: prepared.prior_tasks,
|
|
54821
|
+
plans: prepared.prior_plans
|
|
54822
|
+
};
|
|
54823
|
+
for (const task3 of prepared.prior_tasks) {
|
|
54824
|
+
await this.insertPayload(tx, task3, {
|
|
54825
|
+
project_id: input.destination_project_id,
|
|
54826
|
+
task_list_id: input.destination_task_list_id,
|
|
54827
|
+
plan_id: prepared.task_plan_targets.get(task3.task_id) ?? null,
|
|
54828
|
+
parent_id: task3.task_id === input.root_task_id ? input.destination_parent_id : task3.parent_id
|
|
54829
|
+
}, now4);
|
|
54830
|
+
}
|
|
54831
|
+
if (options.faultInjector && await options.faultInjector("after_task_writes"))
|
|
54832
|
+
throw new Error("Injected task-subtree-transfer fault at after_task_writes");
|
|
54833
|
+
for (const plan of prepared.prior_plans) {
|
|
54834
|
+
const row = await tx.query(`SELECT payload FROM ${this.tableName}
|
|
54835
|
+
WHERE service = $1 AND object_type = 'plans' AND object_id = $2
|
|
54836
|
+
AND deleted_at IS NULL FOR UPDATE`, [this.service, plan.plan_id]);
|
|
54837
|
+
const current = row.rows[0] ? payloadOf(row.rows[0]) : null;
|
|
54838
|
+
if (!current || String(current.project_id ?? "") !== String(plan.project_id ?? "") || (current.task_list_id ?? null) !== plan.task_list_id || timestamp5(current.updated_at ?? row.rows[0]?.updated_at) !== plan.updated_at) {
|
|
54839
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Contained plan changed during transfer", { plan_id: plan.plan_id });
|
|
54840
|
+
}
|
|
54841
|
+
const result2 = await tx.query(`UPDATE ${this.tableName} SET payload = jsonb_set(jsonb_set(jsonb_set(payload, '{project_id}', to_jsonb($1::text)), '{task_list_id}', to_jsonb($2::text)), '{updated_at}', to_jsonb($3::text)), updated_at = $3
|
|
54842
|
+
WHERE service = $4 AND object_type = 'plans' AND object_id = $5 AND deleted_at IS NULL
|
|
54843
|
+
RETURNING object_id`, [input.destination_project_id, input.destination_task_list_id, now4, this.service, plan.plan_id]);
|
|
54844
|
+
if (result2.rows.length !== 1) {
|
|
54845
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Contained plan update did not affect exactly one row", { plan_id: plan.plan_id });
|
|
54846
|
+
}
|
|
54847
|
+
}
|
|
54848
|
+
if (options.faultInjector && await options.faultInjector("after_plan_writes"))
|
|
54849
|
+
throw new Error("Injected task-subtree-transfer fault at after_plan_writes");
|
|
54850
|
+
const postImage = {
|
|
54851
|
+
tasks: prepared.prior_tasks.map((task3) => ({
|
|
54852
|
+
...task3,
|
|
54853
|
+
project_id: input.destination_project_id,
|
|
54854
|
+
task_list_id: input.destination_task_list_id,
|
|
54855
|
+
plan_id: prepared.task_plan_targets.get(task3.task_id) ?? null,
|
|
54856
|
+
parent_id: task3.task_id === input.root_task_id ? input.destination_parent_id : task3.parent_id,
|
|
54857
|
+
version: task3.version + 1,
|
|
54858
|
+
updated_at: now4
|
|
54859
|
+
})),
|
|
54860
|
+
plans: prepared.prior_plans.map((plan) => ({ ...plan, project_id: input.destination_project_id, task_list_id: input.destination_task_list_id, updated_at: now4 }))
|
|
54861
|
+
};
|
|
54862
|
+
const resultDigest = canonicalDigest({ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE, request_digest: requestDigest, prior_image: priorImage, post_image: postImage });
|
|
54863
|
+
const receipt = {
|
|
54864
|
+
receipt_id: deterministicUuid(TODOS_TASK_SUBTREE_TRANSFER_ROUTE, "apply", input.operation_id, input.step_id, input.idempotency_key, requestDigest),
|
|
54865
|
+
authority: "todos",
|
|
54866
|
+
route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
|
|
54867
|
+
schema_version: 1,
|
|
54868
|
+
kind: "apply",
|
|
54869
|
+
operation_id: input.operation_id,
|
|
54870
|
+
step_id: input.step_id,
|
|
54871
|
+
idempotency_key: input.idempotency_key,
|
|
54872
|
+
request_digest: requestDigest,
|
|
54873
|
+
precondition_digest: input.precondition_digest,
|
|
54874
|
+
result_digest: resultDigest,
|
|
54875
|
+
apply_receipt_id: null,
|
|
54876
|
+
source_project_id: input.source_project_id,
|
|
54877
|
+
destination_project_id: input.destination_project_id,
|
|
54878
|
+
destination_task_list_id: input.destination_task_list_id,
|
|
54879
|
+
root_task_id: input.root_task_id,
|
|
54880
|
+
source_population_digest: input.source_population_digest,
|
|
54881
|
+
prior_image: priorImage,
|
|
54882
|
+
post_image: postImage,
|
|
54883
|
+
shared_plan_splits: input.shared_plan_splits,
|
|
54884
|
+
created_at: now4
|
|
54885
|
+
};
|
|
54886
|
+
const result = { duplicate: false, receipt, moved_task_ids: prepared.prior_tasks.map((task3) => task3.task_id), moved_plan_ids: prepared.prior_plans.map((plan) => plan.plan_id), complete: true };
|
|
54887
|
+
await tx.query(`INSERT INTO todos_task_subtree_transfer_receipts (
|
|
54888
|
+
receipt_id, tenant_id, kind, operation_id, step_id, idempotency_key,
|
|
54889
|
+
request_digest, precondition_digest, result_digest, apply_receipt_id,
|
|
54890
|
+
request_json, result_json, created_at
|
|
54891
|
+
) VALUES ($1, $2, 'apply', $3, $4, $5, $6, $7, $8, NULL, $9::jsonb, $10::jsonb, $11)`, [receipt.receipt_id, this.tenantId, receipt.operation_id, receipt.step_id, receipt.idempotency_key, receipt.request_digest, receipt.precondition_digest, receipt.result_digest, canonicalJson(input), canonicalJson(result), now4]);
|
|
54892
|
+
if (options.faultInjector && await options.faultInjector("after_receipt_write"))
|
|
54893
|
+
throw new Error("Injected task-subtree-transfer fault at after_receipt_write");
|
|
54894
|
+
return result;
|
|
54895
|
+
});
|
|
54896
|
+
}
|
|
54897
|
+
async readExact(receiptId2) {
|
|
54898
|
+
await this.ensureSchema();
|
|
54899
|
+
const row = await this.client.query(`SELECT result_json FROM todos_task_subtree_transfer_receipts WHERE tenant_id = $1 AND receipt_id = $2 LIMIT 1`, [this.tenantId, receiptId2]);
|
|
54900
|
+
if (!row.rows[0])
|
|
54901
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND", `Transfer receipt not found: ${receiptId2}`);
|
|
54902
|
+
return { ...typeof row.rows[0].result_json === "string" ? JSON.parse(String(row.rows[0].result_json)) : row.rows[0].result_json, duplicate: false };
|
|
54903
|
+
}
|
|
54904
|
+
async rollback(input, options) {
|
|
54905
|
+
await this.ensureSchema();
|
|
54906
|
+
if (!this.client.transaction)
|
|
54907
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ATOMICITY_UNAVAILABLE", "PostgreSQL subtree rollback requires a transaction-capable client");
|
|
54908
|
+
return this.client.transaction(async (tx) => {
|
|
54909
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended('todos:task-parent-integrity', 0))");
|
|
54910
|
+
const requestDigest = taskSubtreeTransferRollbackRequestDigest(input);
|
|
54911
|
+
const existing = await tx.query(`SELECT result_json, request_digest, precondition_digest, idempotency_key
|
|
54912
|
+
FROM todos_task_subtree_transfer_receipts
|
|
54913
|
+
WHERE tenant_id = $1 AND kind = 'rollback'
|
|
54914
|
+
AND (idempotency_key = $2 OR (operation_id = $3 AND step_id = $4))
|
|
54915
|
+
ORDER BY created_at ASC LIMIT 1`, [this.tenantId, input.idempotency_key, input.operation_id, input.step_id]);
|
|
54916
|
+
if (existing.rows[0]) {
|
|
54917
|
+
const row = existing.rows[0];
|
|
54918
|
+
if (row.request_digest !== requestDigest || row.precondition_digest !== input.precondition_digest || row.idempotency_key !== input.idempotency_key)
|
|
54919
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_CONFLICT", "The rollback operation was already accepted for a different request");
|
|
54920
|
+
return { ...typeof row.result_json === "string" ? JSON.parse(String(row.result_json)) : row.result_json, duplicate: true };
|
|
54921
|
+
}
|
|
54922
|
+
const applyRow = await tx.query(`SELECT result_json FROM todos_task_subtree_transfer_receipts WHERE tenant_id = $1 AND kind = 'apply' AND receipt_id = $2 LIMIT 1`, [this.tenantId, input.receipt_id]);
|
|
54923
|
+
if (!applyRow.rows[0])
|
|
54924
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND", `Apply receipt not found: ${input.receipt_id}`);
|
|
54925
|
+
const applied = typeof applyRow.rows[0].result_json === "string" ? JSON.parse(String(applyRow.rows[0].result_json)) : applyRow.rows[0].result_json;
|
|
54926
|
+
if (applied.receipt.operation_id !== input.operation_id) {
|
|
54927
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Rollback operation does not match the apply receipt");
|
|
54928
|
+
}
|
|
54929
|
+
const expectedPrecondition = canonicalDigest({ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE, direction: "rollback", operation_id: input.operation_id, step_id: input.step_id, apply_receipt_id: input.receipt_id, apply_result_digest: applied.receipt.result_digest });
|
|
54930
|
+
if (input.precondition_digest !== expectedPrecondition)
|
|
54931
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_DIGEST_MISMATCH", "Rollback precondition digest does not match the exact apply receipt", { expected_precondition_digest: expectedPrecondition });
|
|
54932
|
+
const now4 = options.now?.() ?? new Date().toISOString();
|
|
54933
|
+
for (const task3 of applied.receipt.post_image.tasks) {
|
|
54934
|
+
const row = await tx.query(`SELECT payload FROM ${this.tableName} WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL FOR UPDATE`, [this.service, task3.task_id]);
|
|
54935
|
+
if (!row.rows[0])
|
|
54936
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Transferred task disappeared", { task_id: task3.task_id });
|
|
54937
|
+
const payload = payloadOf(row.rows[0]);
|
|
54938
|
+
if (Number(payload.version ?? 1) !== task3.version || String(payload.project_id ?? "") !== String(task3.project_id ?? "") || (payload.parent_id ?? null) !== task3.parent_id || (payload.plan_id ?? null) !== task3.plan_id || (payload.task_list_id ?? null) !== task3.task_list_id)
|
|
54939
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Transferred task changed after apply", { task_id: task3.task_id });
|
|
54940
|
+
}
|
|
54941
|
+
for (const task3 of applied.receipt.prior_image.tasks) {
|
|
54942
|
+
const row = await tx.query(`SELECT payload FROM ${this.tableName} WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL`, [this.service, task3.task_id]);
|
|
54943
|
+
const payload = row.rows[0] ? payloadOf(row.rows[0]) : null;
|
|
54944
|
+
if (!payload)
|
|
54945
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Task disappeared during rollback", { task_id: task3.task_id });
|
|
54946
|
+
const next = { ...payload, project_id: task3.project_id, task_list_id: task3.task_list_id, plan_id: task3.plan_id, parent_id: task3.parent_id, version: task3.version + 2, updated_at: now4 };
|
|
54947
|
+
const result2 = await tx.query(`UPDATE ${this.tableName} SET payload = $1::jsonb, updated_at = $2, version = $3
|
|
54948
|
+
WHERE service = $4 AND object_type = 'tasks' AND object_id = $5 AND deleted_at IS NULL AND version = $6
|
|
54949
|
+
RETURNING object_id`, [canonicalJson(next), now4, task3.version + 2, this.service, task3.task_id, task3.version + 1]);
|
|
54950
|
+
if (result2.rows.length !== 1) {
|
|
54951
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Task rollback did not affect exactly one row", { task_id: task3.task_id });
|
|
54952
|
+
}
|
|
54953
|
+
}
|
|
54954
|
+
if (options.faultInjector && await options.faultInjector("after_rollback_task_writes"))
|
|
54955
|
+
throw new Error("Injected task-subtree-transfer fault at after_rollback_task_writes");
|
|
54956
|
+
for (const plan of applied.receipt.prior_image.plans) {
|
|
54957
|
+
const post = applied.receipt.post_image.plans.find((candidate) => candidate.plan_id === plan.plan_id);
|
|
54958
|
+
const result2 = await tx.query(`UPDATE ${this.tableName}
|
|
54959
|
+
SET payload = jsonb_set(
|
|
54960
|
+
jsonb_set(
|
|
54961
|
+
jsonb_set(payload, '{project_id}', to_jsonb($1::text)),
|
|
54962
|
+
'{task_list_id}',
|
|
54963
|
+
COALESCE(to_jsonb($2::text), 'null'::jsonb)
|
|
54964
|
+
),
|
|
54965
|
+
'{updated_at}',
|
|
54966
|
+
to_jsonb($3::text)
|
|
54967
|
+
), updated_at = $3
|
|
54968
|
+
WHERE service = $4 AND object_type = 'plans' AND object_id = $5 AND deleted_at IS NULL
|
|
54969
|
+
AND payload->>'project_id' = $6 AND COALESCE(payload->>'task_list_id', '') = COALESCE($7, '') AND payload->>'updated_at' = $8
|
|
54970
|
+
RETURNING object_id`, [plan.project_id, plan.task_list_id, now4, this.service, plan.plan_id, post.project_id, post.task_list_id, post.updated_at]);
|
|
54971
|
+
if (result2.rows.length !== 1) {
|
|
54972
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Contained plan rollback did not affect exactly one row", { plan_id: plan.plan_id });
|
|
54973
|
+
}
|
|
54974
|
+
}
|
|
54975
|
+
if (options.faultInjector && await options.faultInjector("after_rollback_plan_writes"))
|
|
54976
|
+
throw new Error("Injected task-subtree-transfer fault at after_rollback_plan_writes");
|
|
54977
|
+
const restored = {
|
|
54978
|
+
tasks: applied.receipt.prior_image.tasks.map((task3) => ({ ...task3, version: task3.version + 2, updated_at: now4 })),
|
|
54979
|
+
plans: applied.receipt.prior_image.plans.map((plan) => ({ ...plan, updated_at: now4 }))
|
|
54980
|
+
};
|
|
54981
|
+
const resultDigest = canonicalDigest({ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE, direction: "rollback", apply_receipt_id: input.receipt_id, prior_image: applied.receipt.post_image, post_image: restored });
|
|
54982
|
+
const receipt = { ...applied.receipt, receipt_id: deterministicUuid(TODOS_TASK_SUBTREE_TRANSFER_ROUTE, "rollback", input.operation_id, input.step_id, input.idempotency_key, input.receipt_id), kind: "rollback", operation_id: input.operation_id, step_id: input.step_id, idempotency_key: input.idempotency_key, request_digest: requestDigest, precondition_digest: input.precondition_digest, result_digest: resultDigest, apply_receipt_id: input.receipt_id, prior_image: applied.receipt.post_image, post_image: restored, created_at: now4 };
|
|
54983
|
+
const result = { duplicate: false, receipt, moved_task_ids: applied.moved_task_ids, moved_plan_ids: applied.moved_plan_ids, complete: true };
|
|
54984
|
+
await tx.query(`INSERT INTO todos_task_subtree_transfer_receipts (
|
|
54985
|
+
receipt_id, tenant_id, kind, operation_id, step_id, idempotency_key,
|
|
54986
|
+
request_digest, precondition_digest, result_digest, apply_receipt_id,
|
|
54987
|
+
request_json, result_json, created_at
|
|
54988
|
+
) VALUES ($1, $2, 'rollback', $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb, $12)`, [receipt.receipt_id, this.tenantId, receipt.operation_id, receipt.step_id, receipt.idempotency_key, receipt.request_digest, receipt.precondition_digest, receipt.result_digest, receipt.apply_receipt_id, canonicalJson(input), canonicalJson(result), now4]);
|
|
54989
|
+
if (options.faultInjector && await options.faultInjector("after_rollback_receipt_write"))
|
|
54990
|
+
throw new Error("Injected task-subtree-transfer fault at after_rollback_receipt_write");
|
|
54991
|
+
return result;
|
|
54992
|
+
});
|
|
54993
|
+
}
|
|
54994
|
+
}
|
|
54995
|
+
var init_postgres4 = __esm(() => {
|
|
54996
|
+
init_postgres_sync();
|
|
54997
|
+
init_canonical2();
|
|
54998
|
+
init_planner();
|
|
54999
|
+
init_types6();
|
|
55000
|
+
});
|
|
55001
|
+
|
|
55002
|
+
// src/task-subtree-transfer/sqlite.ts
|
|
55003
|
+
var sqliteTails2;
|
|
55004
|
+
var init_sqlite3 = __esm(() => {
|
|
55005
|
+
init_canonical2();
|
|
55006
|
+
init_planner();
|
|
55007
|
+
init_types6();
|
|
55008
|
+
sqliteTails2 = new WeakMap;
|
|
55009
|
+
});
|
|
55010
|
+
|
|
55011
|
+
// src/task-subtree-transfer/authority.ts
|
|
55012
|
+
function boundedString(value, field, max = 200) {
|
|
55013
|
+
if (typeof value !== "string" || value.length === 0 || value.length > max) {
|
|
55014
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `${field} must be a non-empty bounded string`);
|
|
55015
|
+
}
|
|
55016
|
+
return value;
|
|
55017
|
+
}
|
|
55018
|
+
function uuid3(value, field) {
|
|
55019
|
+
const parsed = boundedString(value, field, 80);
|
|
55020
|
+
if (!UUID.test(parsed)) {
|
|
55021
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `${field} must be a UUID`);
|
|
55022
|
+
}
|
|
55023
|
+
return parsed;
|
|
55024
|
+
}
|
|
55025
|
+
function digest2(value, field) {
|
|
55026
|
+
const parsed = boundedString(value, field, 64);
|
|
55027
|
+
if (!DIGEST.test(parsed)) {
|
|
55028
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `${field} must be a SHA-256 digest`);
|
|
55029
|
+
}
|
|
55030
|
+
return parsed.toLowerCase();
|
|
55031
|
+
}
|
|
55032
|
+
function nullableUuid(value, field) {
|
|
55033
|
+
if (value === null)
|
|
55034
|
+
return null;
|
|
55035
|
+
return uuid3(value, field);
|
|
55036
|
+
}
|
|
55037
|
+
function strictObject(input, fields) {
|
|
55038
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
55039
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Request must be a JSON object");
|
|
55040
|
+
}
|
|
55041
|
+
const object = input;
|
|
55042
|
+
const allowed = new Set(fields);
|
|
55043
|
+
for (const key2 of Object.keys(object)) {
|
|
55044
|
+
if (!allowed.has(key2)) {
|
|
55045
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `Unknown field: ${key2}`);
|
|
55046
|
+
}
|
|
55047
|
+
}
|
|
55048
|
+
return object;
|
|
55049
|
+
}
|
|
55050
|
+
function parseTodosTaskSubtreeTransferInspect(input) {
|
|
55051
|
+
const value = strictObject(input, INSPECT_FIELDS);
|
|
55052
|
+
const request = {
|
|
55053
|
+
source_project_id: uuid3(value.source_project_id, "source_project_id"),
|
|
55054
|
+
destination_project_id: uuid3(value.destination_project_id, "destination_project_id"),
|
|
55055
|
+
destination_task_list_id: uuid3(value.destination_task_list_id, "destination_task_list_id"),
|
|
55056
|
+
root_task_id: uuid3(value.root_task_id, "root_task_id"),
|
|
55057
|
+
destination_parent_id: nullableUuid(value.destination_parent_id, "destination_parent_id")
|
|
55058
|
+
};
|
|
55059
|
+
if (request.source_project_id === request.destination_project_id) {
|
|
55060
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "source_project_id and destination_project_id must identify different Projects");
|
|
55061
|
+
}
|
|
55062
|
+
return request;
|
|
55063
|
+
}
|
|
55064
|
+
function parseTodosTaskSubtreeTransferApply(input) {
|
|
55065
|
+
const value = strictObject(input, APPLY_FIELDS);
|
|
55066
|
+
if (value.version !== 1) {
|
|
55067
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "version must be 1");
|
|
55068
|
+
}
|
|
55069
|
+
if (!Array.isArray(value.expected_tasks) || value.expected_tasks.length === 0) {
|
|
55070
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "expected_tasks must be a non-empty array");
|
|
55071
|
+
}
|
|
55072
|
+
const expected_tasks = value.expected_tasks.map((item, index) => {
|
|
55073
|
+
const row = strictObject(item, ["task_id", "version"]);
|
|
55074
|
+
const version = row.version;
|
|
55075
|
+
if (!Number.isSafeInteger(version) || Number(version) < 1) {
|
|
55076
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `expected_tasks.${index}.version must be a positive integer`);
|
|
55077
|
+
}
|
|
55078
|
+
return { task_id: uuid3(row.task_id, `expected_tasks.${index}.task_id`), version: Number(version) };
|
|
55079
|
+
});
|
|
55080
|
+
const ids2 = new Set;
|
|
55081
|
+
for (const row of expected_tasks) {
|
|
55082
|
+
if (ids2.has(row.task_id)) {
|
|
55083
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "expected_tasks must not contain duplicate task IDs");
|
|
55084
|
+
}
|
|
55085
|
+
ids2.add(row.task_id);
|
|
55086
|
+
}
|
|
55087
|
+
if (!Array.isArray(value.shared_plan_splits)) {
|
|
55088
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "shared_plan_splits must be an array");
|
|
55089
|
+
}
|
|
55090
|
+
const shared_plan_splits = value.shared_plan_splits.map((item, index) => {
|
|
55091
|
+
const row = strictObject(item, ["source_plan_id", "destination_plan_id"]);
|
|
55092
|
+
return {
|
|
55093
|
+
source_plan_id: uuid3(row.source_plan_id, `shared_plan_splits.${index}.source_plan_id`),
|
|
55094
|
+
destination_plan_id: uuid3(row.destination_plan_id, `shared_plan_splits.${index}.destination_plan_id`)
|
|
55095
|
+
};
|
|
55096
|
+
});
|
|
55097
|
+
return {
|
|
55098
|
+
...parseTodosTaskSubtreeTransferInspect({
|
|
55099
|
+
source_project_id: value.source_project_id,
|
|
55100
|
+
destination_project_id: value.destination_project_id,
|
|
55101
|
+
destination_task_list_id: value.destination_task_list_id,
|
|
55102
|
+
root_task_id: value.root_task_id,
|
|
55103
|
+
destination_parent_id: value.destination_parent_id
|
|
55104
|
+
}),
|
|
55105
|
+
version: 1,
|
|
55106
|
+
operation_id: boundedString(value.operation_id, "operation_id"),
|
|
55107
|
+
step_id: boundedString(value.step_id, "step_id"),
|
|
55108
|
+
idempotency_key: boundedString(value.idempotency_key, "idempotency_key", 240),
|
|
55109
|
+
precondition_digest: digest2(value.precondition_digest, "precondition_digest"),
|
|
55110
|
+
expected_root_parent_id: nullableUuid(value.expected_root_parent_id, "expected_root_parent_id"),
|
|
55111
|
+
source_population_digest: digest2(value.source_population_digest, "source_population_digest"),
|
|
55112
|
+
expected_tasks,
|
|
55113
|
+
shared_plan_splits
|
|
55114
|
+
};
|
|
55115
|
+
}
|
|
55116
|
+
function parseTodosTaskSubtreeTransferRollback(input) {
|
|
55117
|
+
const value = strictObject(input, [
|
|
55118
|
+
"receipt_id",
|
|
55119
|
+
"operation_id",
|
|
55120
|
+
"step_id",
|
|
55121
|
+
"idempotency_key",
|
|
55122
|
+
"precondition_digest"
|
|
55123
|
+
]);
|
|
55124
|
+
return {
|
|
55125
|
+
receipt_id: uuid3(value.receipt_id, "receipt_id"),
|
|
55126
|
+
operation_id: boundedString(value.operation_id, "operation_id"),
|
|
55127
|
+
step_id: boundedString(value.step_id, "step_id"),
|
|
55128
|
+
idempotency_key: boundedString(value.idempotency_key, "idempotency_key", 240),
|
|
55129
|
+
precondition_digest: digest2(value.precondition_digest, "precondition_digest")
|
|
55130
|
+
};
|
|
55131
|
+
}
|
|
55132
|
+
function deriveTodosTaskSubtreeTransferApplyPreconditionDigest(input) {
|
|
55133
|
+
const {
|
|
55134
|
+
source_project_id,
|
|
55135
|
+
destination_project_id,
|
|
55136
|
+
destination_task_list_id,
|
|
55137
|
+
root_task_id,
|
|
55138
|
+
expected_root_parent_id,
|
|
55139
|
+
destination_parent_id,
|
|
55140
|
+
source_population_digest,
|
|
55141
|
+
expected_tasks,
|
|
55142
|
+
shared_plan_splits
|
|
55143
|
+
} = input;
|
|
55144
|
+
return canonicalDigest({
|
|
55145
|
+
route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
|
|
55146
|
+
direction: "apply",
|
|
55147
|
+
source_project_id,
|
|
55148
|
+
destination_project_id,
|
|
55149
|
+
destination_task_list_id,
|
|
55150
|
+
root_task_id,
|
|
55151
|
+
expected_root_parent_id,
|
|
55152
|
+
destination_parent_id,
|
|
55153
|
+
source_population_digest,
|
|
55154
|
+
expected_tasks,
|
|
55155
|
+
shared_plan_splits
|
|
55156
|
+
});
|
|
55157
|
+
}
|
|
55158
|
+
function resolveTenantId2(value) {
|
|
55159
|
+
const tenant = value ?? "default";
|
|
55160
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(tenant)) {
|
|
55161
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "tenantId must be a bounded exact authority identifier");
|
|
55162
|
+
}
|
|
55163
|
+
return tenant;
|
|
55164
|
+
}
|
|
55165
|
+
|
|
55166
|
+
class PackageOwnedTodosTaskSubtreeTransferAuthority {
|
|
55167
|
+
backend;
|
|
55168
|
+
tenantId;
|
|
55169
|
+
backendOptions;
|
|
55170
|
+
constructor(backend, options = {}) {
|
|
55171
|
+
this.backend = backend;
|
|
55172
|
+
this.tenantId = resolveTenantId2(options.tenantId);
|
|
55173
|
+
this.backendOptions = { ...options, tenantId: this.tenantId };
|
|
55174
|
+
}
|
|
55175
|
+
async capability() {
|
|
55176
|
+
return {
|
|
55177
|
+
authority: "todos",
|
|
55178
|
+
route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
|
|
55179
|
+
schema_version: TODOS_TASK_SUBTREE_TRANSFER_SCHEMA_VERSION,
|
|
55180
|
+
tenant_id: this.tenantId,
|
|
55181
|
+
backend: this.backend.kind,
|
|
55182
|
+
exact_descendant_closure: true,
|
|
55183
|
+
complete_source_population_digest: true,
|
|
55184
|
+
per_task_version_cas: true,
|
|
55185
|
+
explicit_shared_plan_splits: true,
|
|
55186
|
+
atomic_apply: true,
|
|
55187
|
+
immutable_forward_inverse_receipts: true,
|
|
55188
|
+
prior_image_receipts: true,
|
|
55189
|
+
cas_protected_rollback: true,
|
|
55190
|
+
preserves_descendant_parent_ids: true,
|
|
55191
|
+
preserves_task_and_relation_identities: true
|
|
55192
|
+
};
|
|
55193
|
+
}
|
|
55194
|
+
async inspect(input) {
|
|
55195
|
+
return this.backend.inspect(parseTodosTaskSubtreeTransferInspect(input));
|
|
55196
|
+
}
|
|
55197
|
+
async apply(input) {
|
|
55198
|
+
const request = parseTodosTaskSubtreeTransferApply(input);
|
|
55199
|
+
const expected = deriveTodosTaskSubtreeTransferApplyPreconditionDigest(request);
|
|
55200
|
+
if (request.precondition_digest !== expected) {
|
|
55201
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_DIGEST_MISMATCH", "precondition_digest does not match the exact transfer target and source snapshot", { expected_precondition_digest: expected });
|
|
55202
|
+
}
|
|
55203
|
+
const requestDigest = taskSubtreeTransferRequestDigest(request);
|
|
55204
|
+
const expectedKey = deriveTodosTaskSubtreeTransferIdempotencyKey({
|
|
55205
|
+
operation_id: request.operation_id,
|
|
55206
|
+
step_id: request.step_id,
|
|
55207
|
+
direction: "apply",
|
|
55208
|
+
target_selector: request.root_task_id,
|
|
55209
|
+
request_digest: requestDigest,
|
|
55210
|
+
precondition_digest: request.precondition_digest
|
|
55211
|
+
});
|
|
55212
|
+
if (request.idempotency_key !== expectedKey) {
|
|
55213
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the exact transfer request", { expected_idempotency_key: expectedKey });
|
|
55214
|
+
}
|
|
55215
|
+
return this.backend.apply(request, this.backendOptions);
|
|
55216
|
+
}
|
|
55217
|
+
async readExact(receiptIdValue) {
|
|
55218
|
+
return this.backend.readExact(uuid3(receiptIdValue, "receipt_id"));
|
|
55219
|
+
}
|
|
55220
|
+
async rollback(input) {
|
|
55221
|
+
const request = parseTodosTaskSubtreeTransferRollback(input);
|
|
55222
|
+
const requestDigest = taskSubtreeTransferRollbackRequestDigest(request);
|
|
55223
|
+
const expectedKey = deriveTodosTaskSubtreeTransferIdempotencyKey({
|
|
55224
|
+
operation_id: request.operation_id,
|
|
55225
|
+
step_id: request.step_id,
|
|
55226
|
+
direction: "rollback",
|
|
55227
|
+
target_selector: request.receipt_id,
|
|
55228
|
+
request_digest: requestDigest,
|
|
55229
|
+
precondition_digest: request.precondition_digest
|
|
55230
|
+
});
|
|
55231
|
+
if (request.idempotency_key !== expectedKey) {
|
|
55232
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_MISMATCH", "rollback idempotency_key does not match the exact rollback request", { expected_idempotency_key: expectedKey });
|
|
55233
|
+
}
|
|
55234
|
+
return this.backend.rollback(request, this.backendOptions);
|
|
55235
|
+
}
|
|
55236
|
+
}
|
|
55237
|
+
function createPostgresTodosTaskSubtreeTransferAuthority(client, options = {}) {
|
|
55238
|
+
return new PackageOwnedTodosTaskSubtreeTransferAuthority(new PostgresTodosTaskSubtreeTransferBackend(client, options), options);
|
|
55239
|
+
}
|
|
55240
|
+
var UUID, DIGEST, INSPECT_FIELDS, APPLY_FIELDS;
|
|
55241
|
+
var init_authority3 = __esm(() => {
|
|
55242
|
+
init_canonical2();
|
|
55243
|
+
init_postgres4();
|
|
55244
|
+
init_sqlite3();
|
|
55245
|
+
init_types6();
|
|
55246
|
+
init_canonical2();
|
|
55247
|
+
UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
55248
|
+
DIGEST = /^[0-9a-f]{64}$/i;
|
|
55249
|
+
INSPECT_FIELDS = [
|
|
55250
|
+
"source_project_id",
|
|
55251
|
+
"destination_project_id",
|
|
55252
|
+
"destination_task_list_id",
|
|
55253
|
+
"root_task_id",
|
|
55254
|
+
"destination_parent_id"
|
|
55255
|
+
];
|
|
55256
|
+
APPLY_FIELDS = [
|
|
55257
|
+
...INSPECT_FIELDS,
|
|
55258
|
+
"version",
|
|
55259
|
+
"operation_id",
|
|
55260
|
+
"step_id",
|
|
55261
|
+
"idempotency_key",
|
|
55262
|
+
"precondition_digest",
|
|
55263
|
+
"expected_root_parent_id",
|
|
55264
|
+
"source_population_digest",
|
|
55265
|
+
"expected_tasks",
|
|
55266
|
+
"shared_plan_splits"
|
|
55267
|
+
];
|
|
55268
|
+
});
|
|
55269
|
+
|
|
55270
|
+
// src/task-subtree-transfer/http.ts
|
|
55271
|
+
function json3(body2, status2 = 200) {
|
|
55272
|
+
return new Response(JSON.stringify(body2), { status: status2, headers: JSON_HEADERS3 });
|
|
55273
|
+
}
|
|
55274
|
+
function status2(error) {
|
|
55275
|
+
switch (error.code) {
|
|
55276
|
+
case "TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT":
|
|
55277
|
+
case "TODOS_TASK_SUBTREE_TRANSFER_FOREIGN_REFERENCE":
|
|
55278
|
+
case "TODOS_TASK_SUBTREE_TRANSFER_HIERARCHY_CYCLE":
|
|
55279
|
+
return 400;
|
|
55280
|
+
case "TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND":
|
|
55281
|
+
case "TODOS_TASK_SUBTREE_TRANSFER_NOT_FOUND":
|
|
55282
|
+
return 404;
|
|
55283
|
+
case "TODOS_TASK_SUBTREE_TRANSFER_ATOMICITY_UNAVAILABLE":
|
|
55284
|
+
return 503;
|
|
55285
|
+
default:
|
|
55286
|
+
return 409;
|
|
55287
|
+
}
|
|
55288
|
+
}
|
|
55289
|
+
async function boundedText2(message, limit) {
|
|
55290
|
+
if (!message.body)
|
|
55291
|
+
return "";
|
|
55292
|
+
const reader = message.body.getReader();
|
|
55293
|
+
const decoder = new TextDecoder;
|
|
55294
|
+
let bytes = 0;
|
|
55295
|
+
let text2 = "";
|
|
55296
|
+
try {
|
|
55297
|
+
while (true) {
|
|
55298
|
+
const { done, value } = await reader.read();
|
|
55299
|
+
if (done)
|
|
55300
|
+
break;
|
|
55301
|
+
bytes += value.byteLength;
|
|
55302
|
+
if (bytes > limit) {
|
|
55303
|
+
await reader.cancel();
|
|
55304
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Task-subtree-transfer HTTP body exceeds the byte bound");
|
|
55305
|
+
}
|
|
55306
|
+
text2 += decoder.decode(value, { stream: true });
|
|
55307
|
+
}
|
|
55308
|
+
return text2 + decoder.decode();
|
|
55309
|
+
} finally {
|
|
55310
|
+
reader.releaseLock();
|
|
55311
|
+
}
|
|
55312
|
+
}
|
|
55313
|
+
async function readBody(request) {
|
|
55314
|
+
const declaredLength = Number(request.headers.get("content-length") ?? 0);
|
|
55315
|
+
if (Number.isFinite(declaredLength) && declaredLength > REQUEST_BYTES) {
|
|
55316
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Task-subtree-transfer request exceeds the byte bound");
|
|
55317
|
+
}
|
|
55318
|
+
const text2 = await boundedText2(request, REQUEST_BYTES);
|
|
55319
|
+
try {
|
|
55320
|
+
return JSON.parse(text2);
|
|
55321
|
+
} catch {
|
|
55322
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Invalid JSON body");
|
|
55323
|
+
}
|
|
55324
|
+
}
|
|
55325
|
+
async function handleTodosTaskSubtreeTransferHttpRequest(request, url, authority, basePath = "/v1/task-subtree-transfer") {
|
|
55326
|
+
if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`))
|
|
55327
|
+
return null;
|
|
55328
|
+
const action = url.pathname.slice(basePath.length).split("/").filter(Boolean).join("/");
|
|
55329
|
+
try {
|
|
55330
|
+
if ((action === "" || action === "capability") && request.method === "GET") {
|
|
55331
|
+
return json3({ capability: await authority.capability() });
|
|
55332
|
+
}
|
|
55333
|
+
if (request.method !== "POST")
|
|
55334
|
+
return json3({ error: "method not allowed" }, 405);
|
|
55335
|
+
if (action === "inspect")
|
|
55336
|
+
return json3({ inspection: await authority.inspect(await readBody(request)) });
|
|
55337
|
+
if (action === "apply")
|
|
55338
|
+
return json3({ result: await authority.apply(await readBody(request)) }, 201);
|
|
55339
|
+
if (action === "rollback")
|
|
55340
|
+
return json3({ result: await authority.rollback(await readBody(request)) }, 201);
|
|
55341
|
+
if (action === "read-exact") {
|
|
55342
|
+
const input = await readBody(request);
|
|
55343
|
+
if (!input || typeof input.receipt_id !== "string") {
|
|
55344
|
+
throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "receipt_id is required");
|
|
55345
|
+
}
|
|
55346
|
+
return json3({ result: await authority.readExact(input.receipt_id) });
|
|
55347
|
+
}
|
|
55348
|
+
return json3({ error: "unknown task-subtree-transfer route" }, 404);
|
|
55349
|
+
} catch (cause) {
|
|
55350
|
+
if (cause instanceof TodosTaskSubtreeTransferError) {
|
|
55351
|
+
return json3({
|
|
55352
|
+
error: cause.message,
|
|
55353
|
+
code: cause.code,
|
|
55354
|
+
details: cause.details,
|
|
55355
|
+
authoritative: true
|
|
55356
|
+
}, status2(cause));
|
|
55357
|
+
}
|
|
55358
|
+
return json3({ error: "internal task-subtree-transfer error" }, 500);
|
|
55359
|
+
}
|
|
55360
|
+
}
|
|
55361
|
+
var JSON_HEADERS3, REQUEST_BYTES, RESPONSE_BYTES;
|
|
55362
|
+
var init_http4 = __esm(() => {
|
|
55363
|
+
init_types6();
|
|
55364
|
+
JSON_HEADERS3 = { "Content-Type": "application/json" };
|
|
55365
|
+
REQUEST_BYTES = 2 * 1024 * 1024;
|
|
55366
|
+
RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
55367
|
+
});
|
|
55368
|
+
|
|
55369
|
+
// src/task-subtree-transfer/index.ts
|
|
55370
|
+
var init_task_subtree_transfer = __esm(() => {
|
|
55371
|
+
init_authority3();
|
|
55372
|
+
init_http4();
|
|
55373
|
+
init_types6();
|
|
55374
|
+
});
|
|
55375
|
+
|
|
54194
55376
|
// src/storage/comment-redaction-backfill.ts
|
|
54195
55377
|
function assertSafeIdentifier2(value) {
|
|
54196
55378
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
@@ -54288,6 +55470,207 @@ var init_comment_redaction_backfill = __esm(() => {
|
|
|
54288
55470
|
init_postgres_sync();
|
|
54289
55471
|
});
|
|
54290
55472
|
|
|
55473
|
+
// src/storage/timestamp-backfill.ts
|
|
55474
|
+
function toIso(value) {
|
|
55475
|
+
if (!value || value === "null")
|
|
55476
|
+
return null;
|
|
55477
|
+
const parsed = new Date(value);
|
|
55478
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
|
|
55479
|
+
}
|
|
55480
|
+
async function backfillMissingTimestamps(client, options = {}) {
|
|
55481
|
+
const service = options.service ?? "todos";
|
|
55482
|
+
const tableName = options.tableName ?? "todos_sync_records";
|
|
55483
|
+
const batchSize = Math.min(Math.max(options.batchSize ?? 100, 1), 500);
|
|
55484
|
+
const apply = Boolean(options.apply);
|
|
55485
|
+
if (apply && options.confirmation !== TIMESTAMP_BACKFILL_CONFIRMATION) {
|
|
55486
|
+
throw new Error(`timestamp-backfill: --apply requires --confirm=${TIMESTAMP_BACKFILL_CONFIRMATION}`);
|
|
55487
|
+
}
|
|
55488
|
+
if (apply && !options.evidencePath) {
|
|
55489
|
+
throw new Error("timestamp-backfill: --apply requires --evidence-path so the pre-change state is preserved before mutation");
|
|
55490
|
+
}
|
|
55491
|
+
let scanned = 0;
|
|
55492
|
+
let candidates = 0;
|
|
55493
|
+
let completedAtBackfilled = 0;
|
|
55494
|
+
let startedAtBackfilled = 0;
|
|
55495
|
+
let remainingAfterApply = 0;
|
|
55496
|
+
let batches = 0;
|
|
55497
|
+
let cursor = null;
|
|
55498
|
+
for (;; ) {
|
|
55499
|
+
const pageResult = await client.query(`/* todos:timestamp-backfill-candidates */ SELECT object_id,
|
|
55500
|
+
payload->>'status' AS status,
|
|
55501
|
+
payload->>'started_at' AS started_at,
|
|
55502
|
+
payload->>'completed_at' AS completed_at,
|
|
55503
|
+
payload->>'updated_at' AS updated_at,
|
|
55504
|
+
payload->'metadata'->'_failure'->>'failed_at' AS failed_at
|
|
55505
|
+
FROM ${tableName}
|
|
55506
|
+
WHERE service = $1 AND object_type = 'tasks' AND deleted_at IS NULL
|
|
55507
|
+
AND ((payload->>'status' = 'completed'
|
|
55508
|
+
AND ((payload->>'completed_at') IS NULL OR (payload->>'completed_at') = 'null'))
|
|
55509
|
+
OR (payload->>'status' = 'failed'
|
|
55510
|
+
AND ((payload->>'started_at') IS NULL OR (payload->>'started_at') = 'null'
|
|
55511
|
+
OR (payload->>'completed_at') IS NULL OR (payload->>'completed_at') = 'null'))
|
|
55512
|
+
OR (payload->>'status' = 'cancelled'
|
|
55513
|
+
AND ((payload->>'started_at') IS NULL OR (payload->>'started_at') = 'null'
|
|
55514
|
+
OR (payload->>'completed_at') IS NULL OR (payload->>'completed_at') = 'null')))
|
|
55515
|
+
AND ($2::text IS NULL OR object_id > $2)
|
|
55516
|
+
ORDER BY object_id
|
|
55517
|
+
LIMIT $3`, [service, cursor, batchSize]);
|
|
55518
|
+
const page = pageResult.rows;
|
|
55519
|
+
batches += 1;
|
|
55520
|
+
scanned += page.length;
|
|
55521
|
+
if (page.length === 0)
|
|
55522
|
+
break;
|
|
55523
|
+
const batch = page.map((row) => ({
|
|
55524
|
+
...row,
|
|
55525
|
+
started_at: toIso(row.started_at),
|
|
55526
|
+
completed_at: toIso(row.completed_at),
|
|
55527
|
+
updated_at: toIso(row.updated_at),
|
|
55528
|
+
failed_at: toIso(row.failed_at)
|
|
55529
|
+
}));
|
|
55530
|
+
const taskIds = batch.map((row) => row.object_id);
|
|
55531
|
+
const historyResult = await client.query(`/* todos:timestamp-backfill-history */ SELECT
|
|
55532
|
+
payload->>'task_id' AS task_id,
|
|
55533
|
+
payload->>'action' AS action,
|
|
55534
|
+
payload->>'new_value' AS new_value,
|
|
55535
|
+
payload->>'created_at' AS created_at
|
|
55536
|
+
FROM ${tableName}
|
|
55537
|
+
WHERE service = $1 AND object_type = 'audit_history' AND deleted_at IS NULL
|
|
55538
|
+
AND payload->>'action' IN ('start', 'complete', 'fail')
|
|
55539
|
+
AND payload->>'task_id' = ANY($2::text[])`, [service, taskIds]);
|
|
55540
|
+
const historyRows = historyResult.rows;
|
|
55541
|
+
const byTask = new Map;
|
|
55542
|
+
for (const row of historyRows) {
|
|
55543
|
+
if (!row.task_id)
|
|
55544
|
+
continue;
|
|
55545
|
+
const list = byTask.get(row.task_id) ?? [];
|
|
55546
|
+
list.push(row);
|
|
55547
|
+
byTask.set(row.task_id, list);
|
|
55548
|
+
}
|
|
55549
|
+
for (const row of batch) {
|
|
55550
|
+
candidates += 1;
|
|
55551
|
+
const entries = (byTask.get(row.object_id) ?? []).sort((a, b) => (a.created_at ?? "").localeCompare(b.created_at ?? ""));
|
|
55552
|
+
let derivedStartedAt = null;
|
|
55553
|
+
let startedSource = null;
|
|
55554
|
+
if (!row.started_at) {
|
|
55555
|
+
const start = entries.find((entry2) => entry2.action === "start");
|
|
55556
|
+
const candidate = toIso(start?.created_at);
|
|
55557
|
+
if (candidate) {
|
|
55558
|
+
derivedStartedAt = candidate;
|
|
55559
|
+
startedSource = "audit_history";
|
|
55560
|
+
}
|
|
55561
|
+
}
|
|
55562
|
+
let derivedCompletedAt = null;
|
|
55563
|
+
let completedSource = null;
|
|
55564
|
+
if (!row.completed_at) {
|
|
55565
|
+
const terminal = entries.filter((entry2) => entry2.action === "complete" || entry2.action === "fail");
|
|
55566
|
+
if (terminal.length > 0) {
|
|
55567
|
+
const latest = terminal[terminal.length - 1];
|
|
55568
|
+
const candidate = toIso(latest.created_at);
|
|
55569
|
+
if (candidate) {
|
|
55570
|
+
derivedCompletedAt = candidate;
|
|
55571
|
+
completedSource = "audit_history";
|
|
55572
|
+
}
|
|
55573
|
+
}
|
|
55574
|
+
if (!derivedCompletedAt && row.failed_at && row.status === "failed") {
|
|
55575
|
+
const candidate = toIso(row.failed_at);
|
|
55576
|
+
if (candidate) {
|
|
55577
|
+
derivedCompletedAt = candidate;
|
|
55578
|
+
completedSource = "failure_metadata";
|
|
55579
|
+
}
|
|
55580
|
+
}
|
|
55581
|
+
if (!derivedCompletedAt) {
|
|
55582
|
+
const candidate = toIso(row.updated_at);
|
|
55583
|
+
if (candidate) {
|
|
55584
|
+
derivedCompletedAt = candidate;
|
|
55585
|
+
completedSource = "updated_at";
|
|
55586
|
+
}
|
|
55587
|
+
}
|
|
55588
|
+
}
|
|
55589
|
+
const evidenceRow = {
|
|
55590
|
+
object_id: row.object_id,
|
|
55591
|
+
status: row.status,
|
|
55592
|
+
started_at: row.started_at,
|
|
55593
|
+
completed_at: row.completed_at,
|
|
55594
|
+
updated_at: row.updated_at,
|
|
55595
|
+
derived_started_at: derivedStartedAt,
|
|
55596
|
+
derived_completed_at: derivedCompletedAt,
|
|
55597
|
+
started_source: startedSource,
|
|
55598
|
+
completed_source: completedSource
|
|
55599
|
+
};
|
|
55600
|
+
if (apply && options.evidencePath) {
|
|
55601
|
+
const { appendFileSync: appendFileSync2, mkdirSync: mkdirSync8 } = await import("fs");
|
|
55602
|
+
const { dirname: dirname9 } = await import("path");
|
|
55603
|
+
mkdirSync8(dirname9(options.evidencePath), { recursive: true });
|
|
55604
|
+
appendFileSync2(options.evidencePath, JSON.stringify(evidenceRow) + `
|
|
55605
|
+
`);
|
|
55606
|
+
}
|
|
55607
|
+
let completedAtLanded = false;
|
|
55608
|
+
let startedAtLanded = false;
|
|
55609
|
+
if (apply) {
|
|
55610
|
+
const willFillStarted = row.started_at === null && derivedStartedAt !== null;
|
|
55611
|
+
const willFillCompleted = row.completed_at === null && derivedCompletedAt !== null;
|
|
55612
|
+
if (willFillStarted || willFillCompleted) {
|
|
55613
|
+
const result = await client.query(`/* todos:timestamp-backfill-apply */ UPDATE ${tableName} SET
|
|
55614
|
+
payload = jsonb_set(
|
|
55615
|
+
jsonb_set(
|
|
55616
|
+
payload,
|
|
55617
|
+
'{completed_at}',
|
|
55618
|
+
CASE WHEN $3::text IS NOT NULL
|
|
55619
|
+
AND ((payload->>'completed_at') IS NULL OR (payload->>'completed_at') = 'null')
|
|
55620
|
+
THEN to_jsonb($3::text) ELSE payload->'completed_at' END,
|
|
55621
|
+
true
|
|
55622
|
+
),
|
|
55623
|
+
'{started_at}',
|
|
55624
|
+
CASE WHEN $4::text IS NOT NULL
|
|
55625
|
+
AND ((payload->>'started_at') IS NULL OR (payload->>'started_at') = 'null')
|
|
55626
|
+
THEN to_jsonb($4::text) ELSE payload->'started_at' END,
|
|
55627
|
+
true
|
|
55628
|
+
)
|
|
55629
|
+
WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL
|
|
55630
|
+
AND (($3::text IS NOT NULL
|
|
55631
|
+
AND ((payload->>'completed_at') IS NULL OR (payload->>'completed_at') = 'null'))
|
|
55632
|
+
OR ($4::text IS NOT NULL
|
|
55633
|
+
AND ((payload->>'started_at') IS NULL OR (payload->>'started_at') = 'null')))
|
|
55634
|
+
RETURNING object_id,
|
|
55635
|
+
($3::text IS NOT NULL AND payload->>'completed_at' = $3::text) AS completed_written,
|
|
55636
|
+
($4::text IS NOT NULL AND payload->>'started_at' = $4::text) AS started_written`, [service, row.object_id, derivedCompletedAt, derivedStartedAt]);
|
|
55637
|
+
if (result.rows.length > 0) {
|
|
55638
|
+
const landed = result.rows[0];
|
|
55639
|
+
completedAtLanded = willFillCompleted && landed.completed_written;
|
|
55640
|
+
startedAtLanded = willFillStarted && landed.started_written;
|
|
55641
|
+
if (completedAtLanded)
|
|
55642
|
+
completedAtBackfilled += 1;
|
|
55643
|
+
if (startedAtLanded)
|
|
55644
|
+
startedAtBackfilled += 1;
|
|
55645
|
+
}
|
|
55646
|
+
}
|
|
55647
|
+
}
|
|
55648
|
+
if (row.completed_at === null && !completedAtLanded) {
|
|
55649
|
+
remainingAfterApply += 1;
|
|
55650
|
+
} else if ((row.status === "failed" || row.status === "cancelled") && row.started_at === null && !startedAtLanded) {
|
|
55651
|
+
remainingAfterApply += 1;
|
|
55652
|
+
}
|
|
55653
|
+
}
|
|
55654
|
+
const last = batch[batch.length - 1];
|
|
55655
|
+
if (last.object_id === cursor)
|
|
55656
|
+
break;
|
|
55657
|
+
cursor = last.object_id;
|
|
55658
|
+
if (page.length < batchSize)
|
|
55659
|
+
break;
|
|
55660
|
+
}
|
|
55661
|
+
return {
|
|
55662
|
+
dry_run: !apply,
|
|
55663
|
+
scanned,
|
|
55664
|
+
candidates,
|
|
55665
|
+
completed_at_backfilled: apply ? completedAtBackfilled : 0,
|
|
55666
|
+
started_at_backfilled: apply ? startedAtBackfilled : 0,
|
|
55667
|
+
batches,
|
|
55668
|
+
remaining_candidates: apply ? remainingAfterApply : candidates,
|
|
55669
|
+
evidence_path: options.evidencePath ?? null
|
|
55670
|
+
};
|
|
55671
|
+
}
|
|
55672
|
+
var TIMESTAMP_BACKFILL_CONFIRMATION = "BACKFILL_TODOS_TERMINAL_TIMESTAMPS";
|
|
55673
|
+
|
|
54291
55674
|
// src/server/cloud.ts
|
|
54292
55675
|
var exports_cloud = {};
|
|
54293
55676
|
__export(exports_cloud, {
|
|
@@ -54296,8 +55679,8 @@ __export(exports_cloud, {
|
|
|
54296
55679
|
pingCloud: () => pingCloud,
|
|
54297
55680
|
normalizeCloudPayloads: () => normalizeCloudPayloads,
|
|
54298
55681
|
isPostgresBackendConfigured: () => isPostgresBackendConfigured,
|
|
54299
|
-
isCloudModeEnabled: () => isCloudModeEnabled,
|
|
54300
55682
|
getCloudVerifier: () => getCloudVerifier,
|
|
55683
|
+
getCloudTaskSubtreeTransferAuthority: () => getCloudTaskSubtreeTransferAuthority,
|
|
54301
55684
|
getCloudTaskManifestAuthority: () => getCloudTaskManifestAuthority,
|
|
54302
55685
|
getCloudStorageAdapter: () => getCloudStorageAdapter,
|
|
54303
55686
|
getCloudProjectRegistrationAuthority: () => getCloudProjectRegistrationAuthority,
|
|
@@ -54309,6 +55692,7 @@ __export(exports_cloud, {
|
|
|
54309
55692
|
ensureCloudSchema: () => ensureCloudSchema,
|
|
54310
55693
|
ensureCloudCommentCursorIndex: () => ensureCloudCommentCursorIndex,
|
|
54311
55694
|
closeCloud: () => closeCloud,
|
|
55695
|
+
backfillCloudTimestamps: () => backfillCloudTimestamps,
|
|
54312
55696
|
backfillCloudCommentRedaction: () => backfillCloudCommentRedaction,
|
|
54313
55697
|
TODOS_APP_SLUG: () => TODOS_APP_SLUG
|
|
54314
55698
|
});
|
|
@@ -54323,9 +55707,6 @@ function resolveSigningSecret(env = process.env) {
|
|
|
54323
55707
|
function isPostgresBackendConfigured(env = process.env) {
|
|
54324
55708
|
return Boolean(resolveCloudDatabaseUrl(env));
|
|
54325
55709
|
}
|
|
54326
|
-
function isCloudModeEnabled(env = process.env) {
|
|
54327
|
-
return isPostgresBackendConfigured(env);
|
|
54328
|
-
}
|
|
54329
55710
|
function getCloudTenantId() {
|
|
54330
55711
|
return process.env.HASNA_TODOS_TENANT_ID ?? "default";
|
|
54331
55712
|
}
|
|
@@ -54372,6 +55753,15 @@ function getCloudTaskManifestAuthority() {
|
|
|
54372
55753
|
});
|
|
54373
55754
|
return cachedTaskManifestAuthority;
|
|
54374
55755
|
}
|
|
55756
|
+
function getCloudTaskSubtreeTransferAuthority() {
|
|
55757
|
+
if (cachedTaskSubtreeTransferAuthority)
|
|
55758
|
+
return cachedTaskSubtreeTransferAuthority;
|
|
55759
|
+
cachedTaskSubtreeTransferAuthority = createPostgresTodosTaskSubtreeTransferAuthority(getClient(), {
|
|
55760
|
+
service: TODOS_APP_SLUG,
|
|
55761
|
+
tenantId: getCloudTenantId()
|
|
55762
|
+
});
|
|
55763
|
+
return cachedTaskSubtreeTransferAuthority;
|
|
55764
|
+
}
|
|
54375
55765
|
function authClient() {
|
|
54376
55766
|
const client = getClient();
|
|
54377
55767
|
return {
|
|
@@ -54405,7 +55795,7 @@ function getCloudVerifier() {
|
|
|
54405
55795
|
cachedVerifier = verifyApiKey2({
|
|
54406
55796
|
app: TODOS_APP_SLUG,
|
|
54407
55797
|
signingSecret,
|
|
54408
|
-
|
|
55798
|
+
keyStatus: store.keyStatus
|
|
54409
55799
|
});
|
|
54410
55800
|
return cachedVerifier;
|
|
54411
55801
|
}
|
|
@@ -54426,8 +55816,14 @@ async function ensureCloudSchema() {
|
|
|
54426
55816
|
for (const sql of postgresTodosTaskManifestSchemaSql(getCloudTenantId())) {
|
|
54427
55817
|
await client.query(sql);
|
|
54428
55818
|
}
|
|
55819
|
+
for (const sql of postgresTodosTaskSubtreeTransferSchemaSql()) {
|
|
55820
|
+
await client.query(sql);
|
|
55821
|
+
}
|
|
54429
55822
|
await getApiKeyStore().ensureSchema();
|
|
54430
|
-
})()
|
|
55823
|
+
})().catch((error) => {
|
|
55824
|
+
schemaEnsured = null;
|
|
55825
|
+
throw error;
|
|
55826
|
+
});
|
|
54431
55827
|
return schemaEnsured;
|
|
54432
55828
|
}
|
|
54433
55829
|
async function ensureCloudCommentCursorIndex() {
|
|
@@ -54453,6 +55849,9 @@ async function normalizeCloudPayloads() {
|
|
|
54453
55849
|
function backfillCloudCommentRedaction(options = {}) {
|
|
54454
55850
|
return backfillPostgresCommentRedaction(getClient(), { ...options, service: TODOS_APP_SLUG });
|
|
54455
55851
|
}
|
|
55852
|
+
function backfillCloudTimestamps(options = {}) {
|
|
55853
|
+
return backfillMissingTimestamps(getClient(), { ...options, service: TODOS_APP_SLUG });
|
|
55854
|
+
}
|
|
54456
55855
|
async function pingCloud() {
|
|
54457
55856
|
const client = getClient();
|
|
54458
55857
|
const res = await client.query("select 1 as ok");
|
|
@@ -54468,9 +55867,10 @@ async function closeCloud() {
|
|
|
54468
55867
|
cachedPrGroupLedger = null;
|
|
54469
55868
|
cachedProjectRegistrationAuthority = null;
|
|
54470
55869
|
cachedTaskManifestAuthority = null;
|
|
55870
|
+
cachedTaskSubtreeTransferAuthority = null;
|
|
54471
55871
|
schemaEnsured = null;
|
|
54472
55872
|
}
|
|
54473
|
-
var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, cachedTaskManifestAuthority = null, schemaEnsured = null;
|
|
55873
|
+
var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, cachedTaskManifestAuthority = null, cachedTaskSubtreeTransferAuthority = null, schemaEnsured = null;
|
|
54474
55874
|
var init_cloud = __esm(() => {
|
|
54475
55875
|
init_cloud_client();
|
|
54476
55876
|
init_postgres_adapter();
|
|
@@ -54478,6 +55878,7 @@ var init_cloud = __esm(() => {
|
|
|
54478
55878
|
init_postgres();
|
|
54479
55879
|
init_project_registration();
|
|
54480
55880
|
init_task_manifest();
|
|
55881
|
+
init_task_subtree_transfer();
|
|
54481
55882
|
init_postgres_sync();
|
|
54482
55883
|
init_comment_redaction_backfill();
|
|
54483
55884
|
});
|
|
@@ -54691,8 +56092,8 @@ function rowToOrg(row) {
|
|
|
54691
56092
|
function createOrg(input, db) {
|
|
54692
56093
|
const d = db || getDatabase();
|
|
54693
56094
|
const id = uuid();
|
|
54694
|
-
const
|
|
54695
|
-
d.run(`INSERT INTO orgs (id, name, description, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}),
|
|
56095
|
+
const timestamp6 = now();
|
|
56096
|
+
d.run(`INSERT INTO orgs (id, name, description, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp6, timestamp6]);
|
|
54696
56097
|
return getOrg(id, d);
|
|
54697
56098
|
}
|
|
54698
56099
|
function getOrg(id, db) {
|
|
@@ -54836,9 +56237,9 @@ function parseBoundedLimit(value, fallback, max) {
|
|
|
54836
56237
|
return fallback;
|
|
54837
56238
|
return Math.min(parsed, max);
|
|
54838
56239
|
}
|
|
54839
|
-
function mapTaskError(e,
|
|
56240
|
+
function mapTaskError(e, json5) {
|
|
54840
56241
|
if (e instanceof VersionConflictError) {
|
|
54841
|
-
return
|
|
56242
|
+
return json5({
|
|
54842
56243
|
error: e.message,
|
|
54843
56244
|
code: VersionConflictError.code,
|
|
54844
56245
|
expected_version: e.expectedVersion,
|
|
@@ -54846,23 +56247,23 @@ function mapTaskError(e, json4) {
|
|
|
54846
56247
|
}, 409);
|
|
54847
56248
|
}
|
|
54848
56249
|
if (e instanceof TaskNotFoundError) {
|
|
54849
|
-
return
|
|
56250
|
+
return json5({ error: e.message, code: TaskNotFoundError.code }, 404);
|
|
54850
56251
|
}
|
|
54851
56252
|
if (e instanceof LockError) {
|
|
54852
|
-
return
|
|
56253
|
+
return json5({ error: e.message, code: LockError.code }, 409);
|
|
54853
56254
|
}
|
|
54854
56255
|
if (e instanceof CompletionGuardError) {
|
|
54855
|
-
return
|
|
56256
|
+
return json5({
|
|
54856
56257
|
error: e.message,
|
|
54857
56258
|
code: CompletionGuardError.code,
|
|
54858
56259
|
retry_after: e.retryAfterSeconds ?? null
|
|
54859
56260
|
}, 409);
|
|
54860
56261
|
}
|
|
54861
56262
|
if (e instanceof TaskNotStartableError) {
|
|
54862
|
-
return
|
|
56263
|
+
return json5({ error: e.message, code: TaskNotStartableError.code }, 409);
|
|
54863
56264
|
}
|
|
54864
56265
|
if (e instanceof Error && / is blocked by /.test(e.message)) {
|
|
54865
|
-
return
|
|
56266
|
+
return json5({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
|
|
54866
56267
|
}
|
|
54867
56268
|
return null;
|
|
54868
56269
|
}
|
|
@@ -54947,11 +56348,11 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
54947
56348
|
}
|
|
54948
56349
|
});
|
|
54949
56350
|
}
|
|
54950
|
-
function handleHealth(_ctx,
|
|
56351
|
+
function handleHealth(_ctx, json5) {
|
|
54951
56352
|
const stats2 = getTaskStats();
|
|
54952
56353
|
const staleCount = getStaleTasks(30).length;
|
|
54953
56354
|
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
54954
|
-
return
|
|
56355
|
+
return json5({
|
|
54955
56356
|
status: staleCount === 0 && overdueRecurring === 0 ? "ok" : "warn",
|
|
54956
56357
|
tasks: stats2.total,
|
|
54957
56358
|
stale: staleCount,
|
|
@@ -54959,18 +56360,18 @@ function handleHealth(_ctx, json4) {
|
|
|
54959
56360
|
timestamp: new Date().toISOString()
|
|
54960
56361
|
});
|
|
54961
56362
|
}
|
|
54962
|
-
function handleHeadlessBoundary(_ctx,
|
|
56363
|
+
function handleHeadlessBoundary(_ctx, json5) {
|
|
54963
56364
|
const { getHeadlessBoundaryManifest: getHeadlessBoundaryManifest2 } = (init_headless_boundaries(), __toCommonJS(exports_headless_boundaries));
|
|
54964
|
-
return
|
|
56365
|
+
return json5(getHeadlessBoundaryManifest2());
|
|
54965
56366
|
}
|
|
54966
|
-
function handleStats(_ctx,
|
|
56367
|
+
function handleStats(_ctx, json5) {
|
|
54967
56368
|
const stats2 = getTaskStats();
|
|
54968
56369
|
const byStatus = stats2.by_status;
|
|
54969
56370
|
const projects = listProjects();
|
|
54970
56371
|
const agents = listAgents();
|
|
54971
56372
|
const staleCount = getStaleTasks(30).length;
|
|
54972
56373
|
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
54973
|
-
return
|
|
56374
|
+
return json5({
|
|
54974
56375
|
total_tasks: stats2.total,
|
|
54975
56376
|
pending: byStatus["pending"] ?? 0,
|
|
54976
56377
|
in_progress: byStatus["in_progress"] ?? 0,
|
|
@@ -54993,10 +56394,10 @@ function taskStatusQueryParam(url) {
|
|
|
54993
56394
|
return { ok: false, message: result.message };
|
|
54994
56395
|
return { ok: true, value: collapseEnumValues(result.values) };
|
|
54995
56396
|
}
|
|
54996
|
-
async function handleListTasks(_req, url, _ctx,
|
|
56397
|
+
async function handleListTasks(_req, url, _ctx, json5, taskToSummary2) {
|
|
54997
56398
|
const statusParam = taskStatusQueryParam(url);
|
|
54998
56399
|
if (!statusParam.ok)
|
|
54999
|
-
return
|
|
56400
|
+
return json5({ error: statusParam.message }, 400);
|
|
55000
56401
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
55001
56402
|
const sessionId = url.searchParams.get("session_id") || undefined;
|
|
55002
56403
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
@@ -55011,13 +56412,13 @@ async function handleListTasks(_req, url, _ctx, json4, taskToSummary2) {
|
|
|
55011
56412
|
limit: limitParam ? parseInt(limitParam, 10) : undefined,
|
|
55012
56413
|
offset: offsetParam ? parseInt(offsetParam, 10) : undefined
|
|
55013
56414
|
});
|
|
55014
|
-
return
|
|
56415
|
+
return json5(tasks.map((t) => taskToSummary2(t, fields)));
|
|
55015
56416
|
}
|
|
55016
|
-
async function handleCreateTask(req, ctx,
|
|
56417
|
+
async function handleCreateTask(req, ctx, json5, taskToSummary2) {
|
|
55017
56418
|
try {
|
|
55018
56419
|
const body2 = await req.json();
|
|
55019
56420
|
if (!body2.title)
|
|
55020
|
-
return
|
|
56421
|
+
return json5({ error: "Missing 'title'" }, 400);
|
|
55021
56422
|
const createdBy = body2.created_by ?? body2.agent_id ?? "dashboard";
|
|
55022
56423
|
const task3 = createTask({
|
|
55023
56424
|
title: body2.title,
|
|
@@ -55029,19 +56430,19 @@ async function handleCreateTask(req, ctx, json4, taskToSummary2) {
|
|
|
55029
56430
|
...body2.assigned_to ? { assigned_to: body2.assigned_to } : {}
|
|
55030
56431
|
});
|
|
55031
56432
|
ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "created", agent_id: task3.agent_id, project_id: task3.project_id });
|
|
55032
|
-
return
|
|
56433
|
+
return json5(taskToSummary2(task3), 201);
|
|
55033
56434
|
} catch (e) {
|
|
55034
|
-
return
|
|
56435
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
|
|
55035
56436
|
}
|
|
55036
56437
|
}
|
|
55037
|
-
async function handleUpsertTask(req, ctx,
|
|
56438
|
+
async function handleUpsertTask(req, ctx, json5, taskToSummary2) {
|
|
55038
56439
|
try {
|
|
55039
56440
|
const body2 = await req.json();
|
|
55040
56441
|
if (typeof body2["fingerprint"] !== "string" || body2["fingerprint"].trim() === "") {
|
|
55041
|
-
return
|
|
56442
|
+
return json5({ error: "Missing 'fingerprint'" }, 400);
|
|
55042
56443
|
}
|
|
55043
56444
|
if (typeof body2["title"] !== "string" || body2["title"].trim() === "") {
|
|
55044
|
-
return
|
|
56445
|
+
return json5({ error: "Missing 'title'" }, 400);
|
|
55045
56446
|
}
|
|
55046
56447
|
const metadata = body2["metadata"] && typeof body2["metadata"] === "object" && !Array.isArray(body2["metadata"]) ? { ...body2["metadata"] } : {};
|
|
55047
56448
|
for (const key2 of ["expectation_id", "expectation_fingerprint", "evidence_paths", "origin_loop_id", "origin_run_id", "expected", "observed", "acceptance"]) {
|
|
@@ -55062,9 +56463,9 @@ async function handleUpsertTask(req, ctx, json4, taskToSummary2) {
|
|
|
55062
56463
|
metadata
|
|
55063
56464
|
});
|
|
55064
56465
|
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 });
|
|
55065
|
-
return
|
|
56466
|
+
return json5({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
|
|
55066
56467
|
} catch (e) {
|
|
55067
|
-
return
|
|
56468
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
|
|
55068
56469
|
}
|
|
55069
56470
|
}
|
|
55070
56471
|
function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
@@ -55116,11 +56517,11 @@ function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
|
55116
56517
|
}
|
|
55117
56518
|
});
|
|
55118
56519
|
}
|
|
55119
|
-
async function handleTasksBulk(req, _ctx,
|
|
56520
|
+
async function handleTasksBulk(req, _ctx, json5) {
|
|
55120
56521
|
try {
|
|
55121
56522
|
const body2 = await req.json();
|
|
55122
56523
|
if (!body2.ids?.length || !body2.action)
|
|
55123
|
-
return
|
|
56524
|
+
return json5({ error: "Missing ids or action" }, 400);
|
|
55124
56525
|
const results = [];
|
|
55125
56526
|
for (const id of body2.ids) {
|
|
55126
56527
|
try {
|
|
@@ -55138,84 +56539,84 @@ async function handleTasksBulk(req, _ctx, json4) {
|
|
|
55138
56539
|
results.push({ id, success: false, error: e instanceof Error ? e.message : "Failed" });
|
|
55139
56540
|
}
|
|
55140
56541
|
}
|
|
55141
|
-
return
|
|
56542
|
+
return json5({ results, succeeded: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length });
|
|
55142
56543
|
} catch (e) {
|
|
55143
|
-
return
|
|
56544
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55144
56545
|
}
|
|
55145
56546
|
}
|
|
55146
|
-
function handleTasksStatus(_req, url, _ctx,
|
|
56547
|
+
function handleTasksStatus(_req, url, _ctx, json5) {
|
|
55147
56548
|
try {
|
|
55148
56549
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
55149
56550
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
55150
|
-
const
|
|
55151
|
-
return
|
|
56551
|
+
const status3 = getStatus(projectId ? { project_id: projectId } : undefined, agentId);
|
|
56552
|
+
return json5(status3);
|
|
55152
56553
|
} catch (e) {
|
|
55153
|
-
return
|
|
56554
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55154
56555
|
}
|
|
55155
56556
|
}
|
|
55156
|
-
function handleTasksNext(_req, url, _ctx,
|
|
56557
|
+
function handleTasksNext(_req, url, _ctx, json5, taskToSummary2) {
|
|
55157
56558
|
try {
|
|
55158
56559
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
55159
56560
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
55160
56561
|
const fields = parseFieldsParam(url);
|
|
55161
56562
|
const task3 = getNextTask(agentId, projectId ? { project_id: projectId } : undefined);
|
|
55162
|
-
return
|
|
56563
|
+
return json5({ task: task3 ? taskToSummary2(task3, fields) : null });
|
|
55163
56564
|
} catch (e) {
|
|
55164
|
-
return
|
|
56565
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55165
56566
|
}
|
|
55166
56567
|
}
|
|
55167
|
-
function handleTasksActive(_req, url, _ctx,
|
|
56568
|
+
function handleTasksActive(_req, url, _ctx, json5) {
|
|
55168
56569
|
try {
|
|
55169
56570
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
55170
56571
|
const work = getActiveWork(projectId ? { project_id: projectId } : undefined);
|
|
55171
|
-
return
|
|
56572
|
+
return json5({ active: work, count: work.length });
|
|
55172
56573
|
} catch (e) {
|
|
55173
|
-
return
|
|
56574
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55174
56575
|
}
|
|
55175
56576
|
}
|
|
55176
|
-
function handleTasksStale(_req, url, _ctx,
|
|
56577
|
+
function handleTasksStale(_req, url, _ctx, json5, taskToSummary2) {
|
|
55177
56578
|
try {
|
|
55178
56579
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
55179
56580
|
const minutes2 = parseInt(url.searchParams.get("minutes") || "30", 10);
|
|
55180
56581
|
const fields = parseFieldsParam(url);
|
|
55181
56582
|
const tasks = getStaleTasks(minutes2, projectId ? { project_id: projectId } : undefined);
|
|
55182
|
-
return
|
|
56583
|
+
return json5({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length });
|
|
55183
56584
|
} catch (e) {
|
|
55184
|
-
return
|
|
56585
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55185
56586
|
}
|
|
55186
56587
|
}
|
|
55187
|
-
function handleTasksChanged(_req, url, _ctx,
|
|
56588
|
+
function handleTasksChanged(_req, url, _ctx, json5, taskToSummary2) {
|
|
55188
56589
|
try {
|
|
55189
56590
|
const since = url.searchParams.get("since");
|
|
55190
56591
|
if (!since)
|
|
55191
|
-
return
|
|
56592
|
+
return json5({ error: "since parameter required (ISO date string)" }, 400);
|
|
55192
56593
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
55193
56594
|
const fields = parseFieldsParam(url);
|
|
55194
56595
|
const tasks = getTasksChangedSince(since, projectId ? { project_id: projectId } : undefined);
|
|
55195
|
-
return
|
|
56596
|
+
return json5({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length, since });
|
|
55196
56597
|
} catch (e) {
|
|
55197
|
-
return
|
|
56598
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55198
56599
|
}
|
|
55199
56600
|
}
|
|
55200
|
-
function handleTasksContext(_req, url, _ctx,
|
|
56601
|
+
function handleTasksContext(_req, url, _ctx, json5, taskToSummary2) {
|
|
55201
56602
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
55202
56603
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
55203
56604
|
const format = url.searchParams.get("format") || "text";
|
|
55204
56605
|
const fields = parseFieldsParam(url);
|
|
55205
56606
|
const filters = projectId ? { project_id: projectId } : undefined;
|
|
55206
|
-
const
|
|
56607
|
+
const status3 = getStatus(filters, agentId);
|
|
55207
56608
|
const next = getNextTask(agentId, filters);
|
|
55208
56609
|
if (format === "json") {
|
|
55209
|
-
return
|
|
56610
|
+
return json5({ status: status3, next_task: next ? taskToSummary2(next, fields) : null });
|
|
55210
56611
|
}
|
|
55211
56612
|
const lines = [];
|
|
55212
|
-
lines.push(`Tasks: ${
|
|
55213
|
-
if (
|
|
55214
|
-
lines.push(`${
|
|
55215
|
-
if (
|
|
55216
|
-
lines.push(`${
|
|
55217
|
-
if (
|
|
55218
|
-
lines.push(`Active: ${
|
|
56613
|
+
lines.push(`Tasks: ${status3.pending} pending | ${status3.in_progress} active | ${status3.completed} done`);
|
|
56614
|
+
if (status3.stale_count > 0)
|
|
56615
|
+
lines.push(`${status3.stale_count} stale tasks stuck in-progress`);
|
|
56616
|
+
if (status3.overdue_recurring > 0)
|
|
56617
|
+
lines.push(`${status3.overdue_recurring} overdue recurring tasks`);
|
|
56618
|
+
if (status3.active_work.length > 0) {
|
|
56619
|
+
lines.push(`Active: ${status3.active_work.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ")}`);
|
|
55219
56620
|
}
|
|
55220
56621
|
if (next)
|
|
55221
56622
|
lines.push(`Next up: ${next.short_id || next.id.slice(0, 8)} [${next.priority}] ${next.title}`);
|
|
@@ -55223,18 +56624,18 @@ function handleTasksContext(_req, url, _ctx, json4, taskToSummary2) {
|
|
|
55223
56624
|
`);
|
|
55224
56625
|
return new Response(text2, { headers: { "Content-Type": "text/plain" } });
|
|
55225
56626
|
}
|
|
55226
|
-
function handleTaskAttachments(id, _ctx,
|
|
56627
|
+
function handleTaskAttachments(id, _ctx, json5) {
|
|
55227
56628
|
const task3 = getTask(id);
|
|
55228
56629
|
if (!task3)
|
|
55229
|
-
return
|
|
56630
|
+
return json5({ error: "Task not found" }, 404);
|
|
55230
56631
|
const evidence = task3.metadata?._evidence || {};
|
|
55231
56632
|
const attachmentIds = evidence.attachments || [];
|
|
55232
|
-
return
|
|
56633
|
+
return json5({ 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 });
|
|
55233
56634
|
}
|
|
55234
|
-
async function handleTaskProgress(id, req, method, _ctx,
|
|
56635
|
+
async function handleTaskProgress(id, req, method, _ctx, json5, url) {
|
|
55235
56636
|
const task3 = getTask(id);
|
|
55236
56637
|
if (!task3)
|
|
55237
|
-
return
|
|
56638
|
+
return json5({ error: "Task not found" }, 404);
|
|
55238
56639
|
if (method === "GET") {
|
|
55239
56640
|
const all = listComments(id);
|
|
55240
56641
|
const progress = all.filter((c) => c.type === "progress");
|
|
@@ -55242,7 +56643,7 @@ async function handleTaskProgress(id, req, method, _ctx, json4, url) {
|
|
|
55242
56643
|
const format = url?.searchParams.get("format") || "compact";
|
|
55243
56644
|
const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 200);
|
|
55244
56645
|
const progressEntries = format === "full" ? progress : progress.slice(-limit);
|
|
55245
|
-
return
|
|
56646
|
+
return json5({
|
|
55246
56647
|
task_id: id,
|
|
55247
56648
|
progress_entries: progressEntries,
|
|
55248
56649
|
latest,
|
|
@@ -55259,27 +56660,27 @@ async function handleTaskProgress(id, req, method, _ctx, json4, url) {
|
|
|
55259
56660
|
try {
|
|
55260
56661
|
const body2 = await req.json();
|
|
55261
56662
|
if (!body2.message)
|
|
55262
|
-
return
|
|
56663
|
+
return json5({ error: "message required" }, 400);
|
|
55263
56664
|
const comment2 = logProgress(id, body2.message, body2.pct_complete, body2.agent_id);
|
|
55264
|
-
return
|
|
56665
|
+
return json5(comment2, 201);
|
|
55265
56666
|
} catch (e) {
|
|
55266
|
-
return
|
|
56667
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to log progress" }, 500);
|
|
55267
56668
|
}
|
|
55268
56669
|
}
|
|
55269
56670
|
return null;
|
|
55270
56671
|
}
|
|
55271
|
-
function handleGetTask(id, _ctx,
|
|
56672
|
+
function handleGetTask(id, _ctx, json5, taskToSummary2, url) {
|
|
55272
56673
|
const task3 = getTask(id);
|
|
55273
56674
|
if (!task3)
|
|
55274
|
-
return
|
|
55275
|
-
return
|
|
56675
|
+
return json5({ error: "Task not found" }, 404);
|
|
56676
|
+
return json5(taskToSummary2(task3, url ? parseFieldsParam(url) : undefined));
|
|
55276
56677
|
}
|
|
55277
|
-
async function handlePatchTask(id, req, _ctx,
|
|
56678
|
+
async function handlePatchTask(id, req, _ctx, json5, taskToSummary2) {
|
|
55278
56679
|
try {
|
|
55279
56680
|
const body2 = await req.json();
|
|
55280
56681
|
const task3 = getTask(id);
|
|
55281
56682
|
if (!task3)
|
|
55282
|
-
return
|
|
56683
|
+
return json5({ error: "Task not found" }, 404);
|
|
55283
56684
|
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"]);
|
|
55284
56685
|
const safeBody = {};
|
|
55285
56686
|
for (const [key2, value] of Object.entries(body2)) {
|
|
@@ -55291,85 +56692,85 @@ async function handlePatchTask(id, req, _ctx, json4, taskToSummary2) {
|
|
|
55291
56692
|
...safeBody,
|
|
55292
56693
|
version: clientVersion
|
|
55293
56694
|
});
|
|
55294
|
-
return
|
|
56695
|
+
return json5(taskToSummary2(updated));
|
|
55295
56696
|
} catch (e) {
|
|
55296
|
-
const mapped = mapTaskError(e,
|
|
56697
|
+
const mapped = mapTaskError(e, json5);
|
|
55297
56698
|
if (mapped)
|
|
55298
56699
|
return mapped;
|
|
55299
|
-
return
|
|
56700
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to update task" }, 500);
|
|
55300
56701
|
}
|
|
55301
56702
|
}
|
|
55302
|
-
function handleDeleteTask(id, _ctx,
|
|
56703
|
+
function handleDeleteTask(id, _ctx, json5) {
|
|
55303
56704
|
const deleted = deleteTask(id);
|
|
55304
56705
|
if (!deleted)
|
|
55305
|
-
return
|
|
55306
|
-
return
|
|
56706
|
+
return json5({ error: "Task not found" }, 404);
|
|
56707
|
+
return json5({ success: true });
|
|
55307
56708
|
}
|
|
55308
|
-
function handleStartTask(id, ctx,
|
|
56709
|
+
function handleStartTask(id, ctx, json5, taskToSummary2) {
|
|
55309
56710
|
try {
|
|
55310
56711
|
const task3 = startTask(id, "dashboard");
|
|
55311
56712
|
ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "started", agent_id: "dashboard", project_id: task3.project_id });
|
|
55312
|
-
return
|
|
56713
|
+
return json5(taskToSummary2(task3));
|
|
55313
56714
|
} catch (e) {
|
|
55314
|
-
const mapped = mapTaskError(e,
|
|
56715
|
+
const mapped = mapTaskError(e, json5);
|
|
55315
56716
|
if (mapped)
|
|
55316
56717
|
return mapped;
|
|
55317
|
-
return
|
|
56718
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to start task" }, 500);
|
|
55318
56719
|
}
|
|
55319
56720
|
}
|
|
55320
|
-
async function handleFailTask(id, req, ctx,
|
|
56721
|
+
async function handleFailTask(id, req, ctx, json5, taskToSummary2) {
|
|
55321
56722
|
try {
|
|
55322
56723
|
const body2 = await req.json().catch(() => ({}));
|
|
55323
56724
|
const result = failTask(id, body2.agent_id, body2.reason, { retry: body2.retry, error_code: body2.error_code });
|
|
55324
56725
|
ctx.broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body2.agent_id || null, project_id: result.task.project_id });
|
|
55325
|
-
return
|
|
56726
|
+
return json5({ task: taskToSummary2(result.task), retry_task: result.retryTask ? taskToSummary2(result.retryTask) : null });
|
|
55326
56727
|
} catch (e) {
|
|
55327
|
-
return
|
|
56728
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500);
|
|
55328
56729
|
}
|
|
55329
56730
|
}
|
|
55330
|
-
function handleCompleteTask(id, ctx,
|
|
56731
|
+
function handleCompleteTask(id, ctx, json5, taskToSummary2) {
|
|
55331
56732
|
try {
|
|
55332
56733
|
const task3 = completeTask(id, "dashboard");
|
|
55333
56734
|
ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "completed", agent_id: "dashboard", project_id: task3.project_id });
|
|
55334
|
-
return
|
|
56735
|
+
return json5(taskToSummary2(task3));
|
|
55335
56736
|
} catch (e) {
|
|
55336
|
-
const mapped = mapTaskError(e,
|
|
56737
|
+
const mapped = mapTaskError(e, json5);
|
|
55337
56738
|
if (mapped)
|
|
55338
56739
|
return mapped;
|
|
55339
|
-
return
|
|
56740
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500);
|
|
55340
56741
|
}
|
|
55341
56742
|
}
|
|
55342
|
-
function handleListProjects(url, _ctx,
|
|
56743
|
+
function handleListProjects(url, _ctx, json5) {
|
|
55343
56744
|
const pFieldsParam = url.searchParams.get("fields");
|
|
55344
56745
|
const pFields = pFieldsParam ? pFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
55345
56746
|
const projects = listProjects();
|
|
55346
|
-
return
|
|
56747
|
+
return json5(pFields ? projects.map((p) => Object.fromEntries(pFields.map((f) => [f, p[f] ?? null]))) : projects);
|
|
55347
56748
|
}
|
|
55348
|
-
async function handleCreateProject(req, _ctx,
|
|
56749
|
+
async function handleCreateProject(req, _ctx, json5) {
|
|
55349
56750
|
try {
|
|
55350
56751
|
const body2 = await req.json();
|
|
55351
56752
|
if (!body2.name || !body2.path)
|
|
55352
|
-
return
|
|
56753
|
+
return json5({ error: "Missing name or path" }, 400);
|
|
55353
56754
|
const project = createProject({ name: body2.name, path: body2.path, description: body2.description });
|
|
55354
|
-
return
|
|
56755
|
+
return json5(project, 201);
|
|
55355
56756
|
} catch (e) {
|
|
55356
|
-
return
|
|
56757
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to create project" }, 500);
|
|
55357
56758
|
}
|
|
55358
56759
|
}
|
|
55359
|
-
function handleDeleteProject(id, _ctx,
|
|
56760
|
+
function handleDeleteProject(id, _ctx, json5) {
|
|
55360
56761
|
const deleted = deleteProject(id);
|
|
55361
56762
|
if (!deleted)
|
|
55362
|
-
return
|
|
55363
|
-
return
|
|
56763
|
+
return json5({ error: "Project not found" }, 404);
|
|
56764
|
+
return json5({ success: true });
|
|
55364
56765
|
}
|
|
55365
|
-
async function handleAgentMe(_req, url, _ctx,
|
|
56766
|
+
async function handleAgentMe(_req, url, _ctx, json5, taskToSummary2) {
|
|
55366
56767
|
try {
|
|
55367
56768
|
const name = url.searchParams.get("name");
|
|
55368
56769
|
if (!name)
|
|
55369
|
-
return
|
|
56770
|
+
return json5({ error: "Missing name param" }, 400);
|
|
55370
56771
|
const agentResult = registerAgent({ name });
|
|
55371
56772
|
if (isAgentConflict(agentResult))
|
|
55372
|
-
return
|
|
56773
|
+
return json5({ error: agentResult.message, conflict: true }, 409);
|
|
55373
56774
|
const agent = agentResult;
|
|
55374
56775
|
const tasks = listTasks({ assigned_to: agent.name });
|
|
55375
56776
|
const agentIdTasks = listTasks({ agent_id: agent.id });
|
|
@@ -55377,7 +56778,7 @@ async function handleAgentMe(_req, url, _ctx, json4, taskToSummary2) {
|
|
|
55377
56778
|
const pending = allTasks.filter((t) => t.status === "pending");
|
|
55378
56779
|
const inProgress = allTasks.filter((t) => t.status === "in_progress");
|
|
55379
56780
|
const completed = allTasks.filter((t) => t.status === "completed");
|
|
55380
|
-
return
|
|
56781
|
+
return json5({
|
|
55381
56782
|
agent,
|
|
55382
56783
|
pending_tasks: pending.map((t) => taskToSummary2(t)),
|
|
55383
56784
|
in_progress_tasks: inProgress.map((t) => taskToSummary2(t)),
|
|
@@ -55391,132 +56792,132 @@ async function handleAgentMe(_req, url, _ctx, json4, taskToSummary2) {
|
|
|
55391
56792
|
});
|
|
55392
56793
|
} catch (e) {
|
|
55393
56794
|
if (e instanceof InvalidAgentNameError)
|
|
55394
|
-
return
|
|
55395
|
-
return
|
|
56795
|
+
return json5({ error: e.message, suggestions: e.suggestions }, 400);
|
|
56796
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to get agent profile" }, 500);
|
|
55396
56797
|
}
|
|
55397
56798
|
}
|
|
55398
|
-
function handleAgentQueue(agentId, _ctx,
|
|
56799
|
+
function handleAgentQueue(agentId, _ctx, json5, taskToSummary2) {
|
|
55399
56800
|
const aliasSet = assignedToAliasSet(getDatabase(), agentId);
|
|
55400
56801
|
const pending = listTasks({ status: "pending" });
|
|
55401
56802
|
const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
|
|
55402
56803
|
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
55403
56804
|
queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
|
55404
|
-
return
|
|
56805
|
+
return json5(queue.map((t) => taskToSummary2(t)));
|
|
55405
56806
|
}
|
|
55406
|
-
async function handleClaimTask(req, _ctx,
|
|
56807
|
+
async function handleClaimTask(req, _ctx, json5, taskToSummary2) {
|
|
55407
56808
|
try {
|
|
55408
56809
|
const body2 = await req.json();
|
|
55409
56810
|
const agentId = body2.agent_id || "anonymous";
|
|
55410
56811
|
const task3 = claimNextTask(agentId, body2.project_id ? { project_id: body2.project_id } : undefined);
|
|
55411
|
-
return
|
|
56812
|
+
return json5({ task: task3 ? taskToSummary2(task3) : null });
|
|
55412
56813
|
} catch (e) {
|
|
55413
|
-
return
|
|
56814
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to claim" }, 500);
|
|
55414
56815
|
}
|
|
55415
56816
|
}
|
|
55416
|
-
function handleListOrgs(_ctx,
|
|
55417
|
-
return
|
|
56817
|
+
function handleListOrgs(_ctx, json5) {
|
|
56818
|
+
return json5(listOrgs());
|
|
55418
56819
|
}
|
|
55419
|
-
async function handleCreateOrg(req, _ctx,
|
|
56820
|
+
async function handleCreateOrg(req, _ctx, json5) {
|
|
55420
56821
|
try {
|
|
55421
56822
|
const body2 = await req.json();
|
|
55422
56823
|
if (!body2.name)
|
|
55423
|
-
return
|
|
55424
|
-
return
|
|
56824
|
+
return json5({ error: "Missing name" }, 400);
|
|
56825
|
+
return json5(createOrg(body2), 201);
|
|
55425
56826
|
} catch (e) {
|
|
55426
|
-
return
|
|
56827
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55427
56828
|
}
|
|
55428
56829
|
}
|
|
55429
|
-
async function handleUpdateOrg(id, req, _ctx,
|
|
56830
|
+
async function handleUpdateOrg(id, req, _ctx, json5) {
|
|
55430
56831
|
try {
|
|
55431
56832
|
const body2 = await req.json();
|
|
55432
|
-
return
|
|
56833
|
+
return json5(updateOrg(id, body2));
|
|
55433
56834
|
} catch (e) {
|
|
55434
|
-
return
|
|
56835
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55435
56836
|
}
|
|
55436
56837
|
}
|
|
55437
|
-
function handleDeleteOrg(id, _ctx,
|
|
56838
|
+
function handleDeleteOrg(id, _ctx, json5) {
|
|
55438
56839
|
const deleted = deleteOrg(id);
|
|
55439
|
-
return
|
|
56840
|
+
return json5(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
55440
56841
|
}
|
|
55441
|
-
function handleOrgChart(_ctx,
|
|
55442
|
-
return
|
|
56842
|
+
function handleOrgChart(_ctx, json5) {
|
|
56843
|
+
return json5(getOrgChart());
|
|
55443
56844
|
}
|
|
55444
|
-
function handleAgentTeam(agentId, _ctx,
|
|
55445
|
-
return
|
|
56845
|
+
function handleAgentTeam(agentId, _ctx, json5) {
|
|
56846
|
+
return json5(getDirectReports(decodeURIComponent(agentId)));
|
|
55446
56847
|
}
|
|
55447
|
-
function handleListAgents(url, _ctx,
|
|
56848
|
+
function handleListAgents(url, _ctx, json5) {
|
|
55448
56849
|
const aFieldsParam = url.searchParams.get("fields");
|
|
55449
56850
|
const aFields = aFieldsParam ? aFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
55450
56851
|
const agents = listAgents();
|
|
55451
|
-
return
|
|
56852
|
+
return json5(aFields ? agents.map((a) => Object.fromEntries(aFields.map((f) => [f, a[f] ?? null]))) : agents);
|
|
55452
56853
|
}
|
|
55453
|
-
async function handleRegisterAgent(req, _ctx,
|
|
56854
|
+
async function handleRegisterAgent(req, _ctx, json5) {
|
|
55454
56855
|
try {
|
|
55455
56856
|
const body2 = await req.json();
|
|
55456
56857
|
if (!body2.name)
|
|
55457
|
-
return
|
|
56858
|
+
return json5({ error: "Missing name" }, 400);
|
|
55458
56859
|
const result = registerAgent({ name: body2.name, description: body2.description, session_id: body2.session_id, working_dir: body2.working_dir });
|
|
55459
56860
|
if (isAgentConflict(result))
|
|
55460
|
-
return
|
|
55461
|
-
return
|
|
56861
|
+
return json5({ error: result.message, conflict: true }, 409);
|
|
56862
|
+
return json5(result, 201);
|
|
55462
56863
|
} catch (e) {
|
|
55463
56864
|
if (e instanceof InvalidAgentNameError)
|
|
55464
|
-
return
|
|
55465
|
-
return
|
|
56865
|
+
return json5({ error: e.message, suggestions: e.suggestions }, 400);
|
|
56866
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500);
|
|
55466
56867
|
}
|
|
55467
56868
|
}
|
|
55468
|
-
async function handleUpdateAgent(id, req, _ctx,
|
|
56869
|
+
async function handleUpdateAgent(id, req, _ctx, json5) {
|
|
55469
56870
|
try {
|
|
55470
56871
|
const body2 = await req.json();
|
|
55471
56872
|
const agent = updateAgent(id, body2);
|
|
55472
|
-
return
|
|
56873
|
+
return json5(agent);
|
|
55473
56874
|
} catch (e) {
|
|
55474
56875
|
if (e instanceof InvalidAgentNameError)
|
|
55475
|
-
return
|
|
55476
|
-
return
|
|
56876
|
+
return json5({ error: e.message, suggestions: e.suggestions }, 400);
|
|
56877
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to update agent" }, 500);
|
|
55477
56878
|
}
|
|
55478
56879
|
}
|
|
55479
|
-
function handleDeleteAgent(id, _ctx,
|
|
56880
|
+
function handleDeleteAgent(id, _ctx, json5) {
|
|
55480
56881
|
const deleted = deleteAgent(id);
|
|
55481
56882
|
if (!deleted)
|
|
55482
|
-
return
|
|
55483
|
-
return
|
|
56883
|
+
return json5({ error: "Agent not found" }, 404);
|
|
56884
|
+
return json5({ success: true });
|
|
55484
56885
|
}
|
|
55485
|
-
async function handleBulkDeleteAgents(req, _ctx,
|
|
56886
|
+
async function handleBulkDeleteAgents(req, _ctx, json5) {
|
|
55486
56887
|
try {
|
|
55487
56888
|
const body2 = await req.json();
|
|
55488
56889
|
if (!body2.ids?.length || body2.action !== "delete")
|
|
55489
|
-
return
|
|
56890
|
+
return json5({ error: "Missing ids or invalid action" }, 400);
|
|
55490
56891
|
let succeeded = 0;
|
|
55491
56892
|
for (const id of body2.ids) {
|
|
55492
56893
|
if (deleteAgent(id))
|
|
55493
56894
|
succeeded++;
|
|
55494
56895
|
}
|
|
55495
|
-
return
|
|
56896
|
+
return json5({ succeeded, failed: body2.ids.length - succeeded });
|
|
55496
56897
|
} catch (e) {
|
|
55497
|
-
return
|
|
56898
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55498
56899
|
}
|
|
55499
56900
|
}
|
|
55500
|
-
async function handleBulkDeleteProjects(req, _ctx,
|
|
56901
|
+
async function handleBulkDeleteProjects(req, _ctx, json5) {
|
|
55501
56902
|
try {
|
|
55502
56903
|
const body2 = await req.json();
|
|
55503
56904
|
if (!body2.ids?.length || body2.action !== "delete")
|
|
55504
|
-
return
|
|
56905
|
+
return json5({ error: "Missing ids or invalid action" }, 400);
|
|
55505
56906
|
let succeeded = 0;
|
|
55506
56907
|
for (const id of body2.ids) {
|
|
55507
56908
|
if (deleteProject(id))
|
|
55508
56909
|
succeeded++;
|
|
55509
56910
|
}
|
|
55510
|
-
return
|
|
56911
|
+
return json5({ succeeded, failed: body2.ids.length - succeeded });
|
|
55511
56912
|
} catch (e) {
|
|
55512
|
-
return
|
|
56913
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55513
56914
|
}
|
|
55514
56915
|
}
|
|
55515
|
-
function handleDoctor(_ctx,
|
|
56916
|
+
function handleDoctor(_ctx, json5) {
|
|
55516
56917
|
const { runTodosDoctor: runTodosDoctor2 } = (init_doctor(), __toCommonJS(exports_doctor));
|
|
55517
|
-
return
|
|
56918
|
+
return json5(runTodosDoctor2({ apply: false }));
|
|
55518
56919
|
}
|
|
55519
|
-
function handleReport(_req, url, _ctx,
|
|
56920
|
+
function handleReport(_req, url, _ctx, json5) {
|
|
55520
56921
|
const days = parseInt(url.searchParams.get("days") || "7", 10);
|
|
55521
56922
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
55522
56923
|
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
@@ -55532,62 +56933,62 @@ function handleReport(_req, url, _ctx, json4) {
|
|
|
55532
56933
|
byDay[day] = (byDay[day] || 0) + 1;
|
|
55533
56934
|
}
|
|
55534
56935
|
const completionRate = changed.length > 0 ? Math.round(completed.length / changed.length * 100) : 0;
|
|
55535
|
-
return
|
|
56936
|
+
return json5({ days, period_since: since, total: all.length, stats: stats2, changed: changed.length, completed: completed.length, failed: failed.length, completion_rate: completionRate, by_day: byDay });
|
|
55536
56937
|
}
|
|
55537
|
-
function handleActivity(_req, url, _ctx,
|
|
56938
|
+
function handleActivity(_req, url, _ctx, json5) {
|
|
55538
56939
|
const limit = parseInt(url.searchParams.get("limit") || "50", 10);
|
|
55539
|
-
return
|
|
56940
|
+
return json5(getRecentActivity(limit));
|
|
55540
56941
|
}
|
|
55541
|
-
function handleTaskHistory(id, _ctx,
|
|
56942
|
+
function handleTaskHistory(id, _ctx, json5, url) {
|
|
55542
56943
|
const history = getTaskHistory(id);
|
|
55543
56944
|
const format = url?.searchParams.get("format") || "compact";
|
|
55544
56945
|
const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 500);
|
|
55545
|
-
return
|
|
56946
|
+
return json5(format === "full" ? history : history.slice(0, limit));
|
|
55546
56947
|
}
|
|
55547
|
-
function handleListWebhooks(_ctx,
|
|
55548
|
-
return
|
|
56948
|
+
function handleListWebhooks(_ctx, json5) {
|
|
56949
|
+
return json5(listWebhooks());
|
|
55549
56950
|
}
|
|
55550
|
-
async function handleCreateWebhook(req, _ctx,
|
|
56951
|
+
async function handleCreateWebhook(req, _ctx, json5) {
|
|
55551
56952
|
try {
|
|
55552
56953
|
const body2 = await req.json();
|
|
55553
56954
|
if (!body2.url)
|
|
55554
|
-
return
|
|
55555
|
-
return
|
|
56955
|
+
return json5({ error: "Missing url" }, 400);
|
|
56956
|
+
return json5(createWebhook(body2), 201);
|
|
55556
56957
|
} catch (e) {
|
|
55557
|
-
return
|
|
56958
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55558
56959
|
}
|
|
55559
56960
|
}
|
|
55560
|
-
function handleDeleteWebhook(id, _ctx,
|
|
56961
|
+
function handleDeleteWebhook(id, _ctx, json5) {
|
|
55561
56962
|
const deleted = deleteWebhook(id);
|
|
55562
|
-
return
|
|
56963
|
+
return json5(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
55563
56964
|
}
|
|
55564
|
-
function handleListTemplates(_ctx,
|
|
55565
|
-
return
|
|
56965
|
+
function handleListTemplates(_ctx, json5) {
|
|
56966
|
+
return json5(listTemplates());
|
|
55566
56967
|
}
|
|
55567
|
-
async function handleCreateTemplate(req, _ctx,
|
|
56968
|
+
async function handleCreateTemplate(req, _ctx, json5) {
|
|
55568
56969
|
try {
|
|
55569
56970
|
const body2 = await req.json();
|
|
55570
56971
|
if (!body2.name || !body2.title_pattern)
|
|
55571
|
-
return
|
|
55572
|
-
return
|
|
56972
|
+
return json5({ error: "Missing name or title_pattern" }, 400);
|
|
56973
|
+
return json5(createTemplate(body2), 201);
|
|
55573
56974
|
} catch (e) {
|
|
55574
|
-
return
|
|
56975
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55575
56976
|
}
|
|
55576
56977
|
}
|
|
55577
|
-
function handleDeleteTemplate(id, _ctx,
|
|
56978
|
+
function handleDeleteTemplate(id, _ctx, json5) {
|
|
55578
56979
|
const deleted = deleteTemplate(id);
|
|
55579
|
-
return
|
|
56980
|
+
return json5(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
55580
56981
|
}
|
|
55581
|
-
function handleListPlans(url, _ctx,
|
|
56982
|
+
function handleListPlans(url, _ctx, json5) {
|
|
55582
56983
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
55583
56984
|
const plans = listPlans(projectId);
|
|
55584
|
-
return
|
|
56985
|
+
return json5(plans);
|
|
55585
56986
|
}
|
|
55586
|
-
async function handleCreatePlan(req, _ctx,
|
|
56987
|
+
async function handleCreatePlan(req, _ctx, json5) {
|
|
55587
56988
|
try {
|
|
55588
56989
|
const body2 = await req.json();
|
|
55589
56990
|
if (!body2.name)
|
|
55590
|
-
return
|
|
56991
|
+
return json5({ error: "Missing 'name'" }, 400);
|
|
55591
56992
|
const plan = createPlan({
|
|
55592
56993
|
name: body2.name,
|
|
55593
56994
|
slug: body2.slug,
|
|
@@ -55597,49 +56998,49 @@ async function handleCreatePlan(req, _ctx, json4) {
|
|
|
55597
56998
|
agent_id: body2.agent_id,
|
|
55598
56999
|
status: body2.status
|
|
55599
57000
|
});
|
|
55600
|
-
return
|
|
57001
|
+
return json5(plan, 201);
|
|
55601
57002
|
} catch (e) {
|
|
55602
|
-
return
|
|
57003
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to create plan" }, 500);
|
|
55603
57004
|
}
|
|
55604
57005
|
}
|
|
55605
|
-
async function handleBulkDeletePlans(req, _ctx,
|
|
57006
|
+
async function handleBulkDeletePlans(req, _ctx, json5) {
|
|
55606
57007
|
try {
|
|
55607
57008
|
const body2 = await req.json();
|
|
55608
57009
|
if (!body2.ids?.length || body2.action !== "delete")
|
|
55609
|
-
return
|
|
57010
|
+
return json5({ error: "Missing ids or invalid action" }, 400);
|
|
55610
57011
|
let succeeded = 0;
|
|
55611
57012
|
for (const id of body2.ids) {
|
|
55612
57013
|
if (deletePlan(id))
|
|
55613
57014
|
succeeded++;
|
|
55614
57015
|
}
|
|
55615
|
-
return
|
|
57016
|
+
return json5({ succeeded, failed: body2.ids.length - succeeded });
|
|
55616
57017
|
} catch (e) {
|
|
55617
|
-
return
|
|
57018
|
+
return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
55618
57019
|
}
|
|
55619
57020
|
}
|
|
55620
|
-
function handleGetPlan(id, _ctx,
|
|
57021
|
+
function handleGetPlan(id, _ctx, json5, taskToSummary2) {
|
|
55621
57022
|
const plan = getPlan(id);
|
|
55622
57023
|
if (!plan)
|
|
55623
|
-
return
|
|
57024
|
+
return json5({ error: "Plan not found" }, 404);
|
|
55624
57025
|
const tasks = listTasks({ plan_id: id });
|
|
55625
|
-
return
|
|
57026
|
+
return json5({ ...plan, tasks: tasks.map((t) => taskToSummary2(t)) });
|
|
55626
57027
|
}
|
|
55627
|
-
async function handleUpdatePlan(id, req, _ctx,
|
|
57028
|
+
async function handleUpdatePlan(id, req, _ctx, json5) {
|
|
55628
57029
|
try {
|
|
55629
57030
|
const body2 = await req.json();
|
|
55630
57031
|
const plan = updatePlan(id, body2);
|
|
55631
|
-
return
|
|
57032
|
+
return json5(plan);
|
|
55632
57033
|
} catch (e) {
|
|
55633
|
-
return
|
|
57034
|
+
return json5({ error: e instanceof Error ? e.message : "Failed to update plan" }, 500);
|
|
55634
57035
|
}
|
|
55635
57036
|
}
|
|
55636
|
-
function handleDeletePlan(id, _ctx,
|
|
57037
|
+
function handleDeletePlan(id, _ctx, json5) {
|
|
55637
57038
|
const deleted = deletePlan(id);
|
|
55638
57039
|
if (!deleted)
|
|
55639
|
-
return
|
|
55640
|
-
return
|
|
57040
|
+
return json5({ error: "Plan not found" }, 404);
|
|
57041
|
+
return json5({ success: true });
|
|
55641
57042
|
}
|
|
55642
|
-
function handleStaticFiles(path, method, ctx,
|
|
57043
|
+
function handleStaticFiles(path, method, ctx, json5, serveStaticFile2) {
|
|
55643
57044
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
55644
57045
|
return null;
|
|
55645
57046
|
if (path !== "/") {
|
|
@@ -55647,7 +57048,7 @@ function handleStaticFiles(path, method, ctx, json4, serveStaticFile2) {
|
|
|
55647
57048
|
const resolvedFile = resolve16(filePath);
|
|
55648
57049
|
const resolvedBase = resolve16(ctx.dashboardDir);
|
|
55649
57050
|
if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
|
|
55650
|
-
return
|
|
57051
|
+
return json5({ error: "Forbidden" }, 403);
|
|
55651
57052
|
}
|
|
55652
57053
|
const res2 = serveStaticFile2(filePath);
|
|
55653
57054
|
if (res2)
|
|
@@ -55703,6 +57104,19 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
55703
57104
|
TaskManifestReceipt: taskManifestReceiptSchema,
|
|
55704
57105
|
TaskManifestApplyResult: taskManifestApplyResultSchema,
|
|
55705
57106
|
TaskManifestApplyResponse: taskManifestApplyResponseSchema,
|
|
57107
|
+
TaskSubtreeTransferExpectedTask: taskSubtreeTransferExpectedTaskSchema,
|
|
57108
|
+
TaskSubtreeTransferSharedPlanSplit: taskSubtreeTransferSharedPlanSplitSchema,
|
|
57109
|
+
TaskSubtreeTransferInspectRequest: taskSubtreeTransferInspectRequestSchema,
|
|
57110
|
+
TaskSubtreeTransferInspection: taskSubtreeTransferInspectionSchema,
|
|
57111
|
+
TaskSubtreeTransferApplyRequest: taskSubtreeTransferApplyRequestSchema,
|
|
57112
|
+
TaskSubtreeTransferTaskImage: taskSubtreeTransferTaskImageSchema,
|
|
57113
|
+
TaskSubtreeTransferPlanImage: taskSubtreeTransferPlanImageSchema,
|
|
57114
|
+
TaskSubtreeTransferImage: taskSubtreeTransferImageSchema,
|
|
57115
|
+
TaskSubtreeTransferReceipt: taskSubtreeTransferReceiptSchema,
|
|
57116
|
+
TaskSubtreeTransferResult: taskSubtreeTransferResultSchema,
|
|
57117
|
+
TaskSubtreeTransferCapability: taskSubtreeTransferCapabilitySchema,
|
|
57118
|
+
TaskSubtreeTransferRollbackRequest: taskSubtreeTransferRollbackRequestSchema,
|
|
57119
|
+
TaskSubtreeTransferReadExactRequest: taskSubtreeTransferReadExactRequestSchema,
|
|
55706
57120
|
TaskManifestCompensateRequest: taskManifestCompensateRequestSchema,
|
|
55707
57121
|
TaskManifestCompensationResult: taskManifestCompensationResultSchema,
|
|
55708
57122
|
TaskManifestCompensateResponse: taskManifestCompensateResponseSchema,
|
|
@@ -55907,7 +57321,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
55907
57321
|
slug: { type: "string", minLength: 1, pattern: ".*[A-Za-z0-9].*" },
|
|
55908
57322
|
name: { type: "string" },
|
|
55909
57323
|
description: { type: "string" },
|
|
55910
|
-
metadata: { type: "object", additionalProperties: true }
|
|
57324
|
+
metadata: { type: "object", additionalProperties: true },
|
|
57325
|
+
project_id: { type: "string", nullable: true }
|
|
55911
57326
|
}
|
|
55912
57327
|
},
|
|
55913
57328
|
CreateTaskCommentInput: {
|
|
@@ -57051,6 +58466,160 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
57051
58466
|
}
|
|
57052
58467
|
}
|
|
57053
58468
|
},
|
|
58469
|
+
"/v1/task-subtree-transfer/capability": {
|
|
58470
|
+
get: {
|
|
58471
|
+
operationId: "getTaskSubtreeTransferCapability",
|
|
58472
|
+
summary: "Read the current task-subtree-transfer authority capability and tenant",
|
|
58473
|
+
responses: {
|
|
58474
|
+
"200": {
|
|
58475
|
+
content: {
|
|
58476
|
+
"application/json": {
|
|
58477
|
+
schema: {
|
|
58478
|
+
type: "object",
|
|
58479
|
+
additionalProperties: false,
|
|
58480
|
+
required: ["capability"],
|
|
58481
|
+
properties: {
|
|
58482
|
+
capability: { $ref: "#/components/schemas/TaskSubtreeTransferCapability" }
|
|
58483
|
+
}
|
|
58484
|
+
}
|
|
58485
|
+
}
|
|
58486
|
+
}
|
|
58487
|
+
},
|
|
58488
|
+
"401": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
58489
|
+
"503": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
58490
|
+
}
|
|
58491
|
+
}
|
|
58492
|
+
},
|
|
58493
|
+
"/v1/task-subtree-transfer/inspect": {
|
|
58494
|
+
post: {
|
|
58495
|
+
operationId: "inspectTaskSubtreeTransfer",
|
|
58496
|
+
summary: "Inspect one exact task subtree and complete source population snapshot",
|
|
58497
|
+
requestBody: {
|
|
58498
|
+
required: true,
|
|
58499
|
+
content: {
|
|
58500
|
+
"application/json": {
|
|
58501
|
+
schema: { $ref: "#/components/schemas/TaskSubtreeTransferInspectRequest" }
|
|
58502
|
+
}
|
|
58503
|
+
}
|
|
58504
|
+
},
|
|
58505
|
+
responses: {
|
|
58506
|
+
"200": {
|
|
58507
|
+
content: {
|
|
58508
|
+
"application/json": {
|
|
58509
|
+
schema: {
|
|
58510
|
+
type: "object",
|
|
58511
|
+
additionalProperties: false,
|
|
58512
|
+
required: ["inspection"],
|
|
58513
|
+
properties: {
|
|
58514
|
+
inspection: { $ref: "#/components/schemas/TaskSubtreeTransferInspection" }
|
|
58515
|
+
}
|
|
58516
|
+
}
|
|
58517
|
+
}
|
|
58518
|
+
}
|
|
58519
|
+
},
|
|
58520
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
58521
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
58522
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
58523
|
+
}
|
|
58524
|
+
}
|
|
58525
|
+
},
|
|
58526
|
+
"/v1/task-subtree-transfer/apply": {
|
|
58527
|
+
post: {
|
|
58528
|
+
operationId: "applyTaskSubtreeTransfer",
|
|
58529
|
+
summary: "Apply one exact task subtree transfer atomically with CAS and immutable receipt",
|
|
58530
|
+
requestBody: {
|
|
58531
|
+
required: true,
|
|
58532
|
+
content: {
|
|
58533
|
+
"application/json": {
|
|
58534
|
+
schema: { $ref: "#/components/schemas/TaskSubtreeTransferApplyRequest" }
|
|
58535
|
+
}
|
|
58536
|
+
}
|
|
58537
|
+
},
|
|
58538
|
+
responses: {
|
|
58539
|
+
"201": {
|
|
58540
|
+
content: {
|
|
58541
|
+
"application/json": {
|
|
58542
|
+
schema: {
|
|
58543
|
+
type: "object",
|
|
58544
|
+
additionalProperties: false,
|
|
58545
|
+
required: ["result"],
|
|
58546
|
+
properties: {
|
|
58547
|
+
result: { $ref: "#/components/schemas/TaskSubtreeTransferResult" }
|
|
58548
|
+
}
|
|
58549
|
+
}
|
|
58550
|
+
}
|
|
58551
|
+
}
|
|
58552
|
+
},
|
|
58553
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
58554
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
58555
|
+
"503": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
58556
|
+
}
|
|
58557
|
+
}
|
|
58558
|
+
},
|
|
58559
|
+
"/v1/task-subtree-transfer/read-exact": {
|
|
58560
|
+
post: {
|
|
58561
|
+
operationId: "readExactTaskSubtreeTransfer",
|
|
58562
|
+
summary: "Read one exact immutable task-subtree-transfer receipt",
|
|
58563
|
+
requestBody: {
|
|
58564
|
+
required: true,
|
|
58565
|
+
content: {
|
|
58566
|
+
"application/json": {
|
|
58567
|
+
schema: { $ref: "#/components/schemas/TaskSubtreeTransferReadExactRequest" }
|
|
58568
|
+
}
|
|
58569
|
+
}
|
|
58570
|
+
},
|
|
58571
|
+
responses: {
|
|
58572
|
+
"200": {
|
|
58573
|
+
content: {
|
|
58574
|
+
"application/json": {
|
|
58575
|
+
schema: {
|
|
58576
|
+
type: "object",
|
|
58577
|
+
additionalProperties: false,
|
|
58578
|
+
required: ["result"],
|
|
58579
|
+
properties: {
|
|
58580
|
+
result: { $ref: "#/components/schemas/TaskSubtreeTransferResult" }
|
|
58581
|
+
}
|
|
58582
|
+
}
|
|
58583
|
+
}
|
|
58584
|
+
}
|
|
58585
|
+
},
|
|
58586
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
58587
|
+
}
|
|
58588
|
+
}
|
|
58589
|
+
},
|
|
58590
|
+
"/v1/task-subtree-transfer/rollback": {
|
|
58591
|
+
post: {
|
|
58592
|
+
operationId: "rollbackTaskSubtreeTransfer",
|
|
58593
|
+
summary: "Rollback one exact task-subtree-transfer receipt with CAS protection",
|
|
58594
|
+
requestBody: {
|
|
58595
|
+
required: true,
|
|
58596
|
+
content: {
|
|
58597
|
+
"application/json": {
|
|
58598
|
+
schema: { $ref: "#/components/schemas/TaskSubtreeTransferRollbackRequest" }
|
|
58599
|
+
}
|
|
58600
|
+
}
|
|
58601
|
+
},
|
|
58602
|
+
responses: {
|
|
58603
|
+
"201": {
|
|
58604
|
+
content: {
|
|
58605
|
+
"application/json": {
|
|
58606
|
+
schema: {
|
|
58607
|
+
type: "object",
|
|
58608
|
+
additionalProperties: false,
|
|
58609
|
+
required: ["result"],
|
|
58610
|
+
properties: {
|
|
58611
|
+
result: { $ref: "#/components/schemas/TaskSubtreeTransferResult" }
|
|
58612
|
+
}
|
|
58613
|
+
}
|
|
58614
|
+
}
|
|
58615
|
+
}
|
|
58616
|
+
},
|
|
58617
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
58618
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
58619
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
58620
|
+
}
|
|
58621
|
+
}
|
|
58622
|
+
},
|
|
57054
58623
|
"/v1/task-manifest/capability": {
|
|
57055
58624
|
get: {
|
|
57056
58625
|
operationId: "getTaskManifestCapability",
|
|
@@ -58004,7 +59573,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
58004
59573
|
}
|
|
58005
59574
|
});
|
|
58006
59575
|
}
|
|
58007
|
-
var taskSchema, taskManifestBoundsSchema, taskManifestCapabilitySchema, taskManifestCapabilityResponseSchema, projectSchema, taskManifestBindingLookupRequestSchema, taskManifestBindingLookupResultSchema, taskManifestSchema, taskManifestReceiptSchema, taskManifestApplyResultSchema, taskManifestApplyResponseSchema, taskManifestCompensateRequestSchema, taskManifestCompensationResultSchema, taskManifestCompensateResponseSchema, taskManifestReadExactRequestSchema, taskManifestBindingLookupResponseSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, staleLockHandoffInputSchema, staleLockHandoffReceiptSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema, projectRegistrationBoundsProperties, projectRegistrationReceiptSchema, projectRegistrationCapabilitySchema, projectRegistrationRequestSchema, projectRegistrationLookupRequestSchema, priorRegistrationAdoptionValidationSchema, projectResourceSchema, projectResourcePageSchema;
|
|
59576
|
+
var taskSchema, taskManifestBoundsSchema, taskManifestCapabilitySchema, taskManifestCapabilityResponseSchema, projectSchema, taskManifestBindingLookupRequestSchema, taskManifestBindingLookupResultSchema, taskManifestSchema, taskManifestReceiptSchema, taskManifestApplyResultSchema, taskManifestApplyResponseSchema, taskSubtreeTransferExpectedTaskSchema, taskSubtreeTransferSharedPlanSplitSchema, taskSubtreeTransferInspectRequestSchema, taskSubtreeTransferInspectionSchema, taskSubtreeTransferApplyRequestSchema, taskSubtreeTransferTaskImageSchema, taskSubtreeTransferPlanImageSchema, taskSubtreeTransferImageSchema, taskSubtreeTransferReceiptSchema, taskSubtreeTransferResultSchema, taskSubtreeTransferCapabilitySchema, taskSubtreeTransferRollbackRequestSchema, taskSubtreeTransferReadExactRequestSchema, taskManifestCompensateRequestSchema, taskManifestCompensationResultSchema, taskManifestCompensateResponseSchema, taskManifestReadExactRequestSchema, taskManifestBindingLookupResponseSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, staleLockHandoffInputSchema, staleLockHandoffReceiptSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema, projectRegistrationBoundsProperties, projectRegistrationReceiptSchema, projectRegistrationCapabilitySchema, projectRegistrationRequestSchema, projectRegistrationLookupRequestSchema, priorRegistrationAdoptionValidationSchema, projectResourceSchema, projectResourcePageSchema;
|
|
58008
59577
|
var init_openapi = __esm(() => {
|
|
58009
59578
|
init_package_version();
|
|
58010
59579
|
init_types();
|
|
@@ -58282,6 +59851,288 @@ var init_openapi = __esm(() => {
|
|
|
58282
59851
|
required: ["result"],
|
|
58283
59852
|
properties: { result: { $ref: "#/components/schemas/TaskManifestApplyResult" } }
|
|
58284
59853
|
};
|
|
59854
|
+
taskSubtreeTransferExpectedTaskSchema = {
|
|
59855
|
+
type: "object",
|
|
59856
|
+
additionalProperties: false,
|
|
59857
|
+
required: ["task_id", "version"],
|
|
59858
|
+
properties: {
|
|
59859
|
+
task_id: { type: "string", format: "uuid" },
|
|
59860
|
+
version: { type: "integer", minimum: 1 }
|
|
59861
|
+
}
|
|
59862
|
+
};
|
|
59863
|
+
taskSubtreeTransferSharedPlanSplitSchema = {
|
|
59864
|
+
type: "object",
|
|
59865
|
+
additionalProperties: false,
|
|
59866
|
+
required: ["source_plan_id", "destination_plan_id"],
|
|
59867
|
+
properties: {
|
|
59868
|
+
source_plan_id: { type: "string", format: "uuid" },
|
|
59869
|
+
destination_plan_id: { type: "string", format: "uuid" }
|
|
59870
|
+
}
|
|
59871
|
+
};
|
|
59872
|
+
taskSubtreeTransferInspectRequestSchema = {
|
|
59873
|
+
type: "object",
|
|
59874
|
+
additionalProperties: false,
|
|
59875
|
+
required: [
|
|
59876
|
+
"source_project_id",
|
|
59877
|
+
"destination_project_id",
|
|
59878
|
+
"destination_task_list_id",
|
|
59879
|
+
"root_task_id",
|
|
59880
|
+
"destination_parent_id"
|
|
59881
|
+
],
|
|
59882
|
+
properties: {
|
|
59883
|
+
source_project_id: { type: "string", format: "uuid" },
|
|
59884
|
+
destination_project_id: { type: "string", format: "uuid" },
|
|
59885
|
+
destination_task_list_id: { type: "string", format: "uuid" },
|
|
59886
|
+
root_task_id: { type: "string", format: "uuid" },
|
|
59887
|
+
destination_parent_id: { type: "string", format: "uuid", nullable: true }
|
|
59888
|
+
}
|
|
59889
|
+
};
|
|
59890
|
+
taskSubtreeTransferInspectionSchema = {
|
|
59891
|
+
type: "object",
|
|
59892
|
+
additionalProperties: false,
|
|
59893
|
+
required: [
|
|
59894
|
+
"source_project_id",
|
|
59895
|
+
"destination_project_id",
|
|
59896
|
+
"destination_task_list_id",
|
|
59897
|
+
"root_task_id",
|
|
59898
|
+
"destination_parent_id",
|
|
59899
|
+
"expected_root_parent_id",
|
|
59900
|
+
"source_population_digest",
|
|
59901
|
+
"expected_tasks",
|
|
59902
|
+
"contained_plan_ids",
|
|
59903
|
+
"shared_plan_ids",
|
|
59904
|
+
"complete"
|
|
59905
|
+
],
|
|
59906
|
+
properties: {
|
|
59907
|
+
...taskSubtreeTransferInspectRequestSchema.properties,
|
|
59908
|
+
expected_root_parent_id: { type: "string", format: "uuid", nullable: true },
|
|
59909
|
+
source_population_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
59910
|
+
expected_tasks: {
|
|
59911
|
+
type: "array",
|
|
59912
|
+
minItems: 1,
|
|
59913
|
+
items: { $ref: "#/components/schemas/TaskSubtreeTransferExpectedTask" }
|
|
59914
|
+
},
|
|
59915
|
+
contained_plan_ids: {
|
|
59916
|
+
type: "array",
|
|
59917
|
+
items: { type: "string", format: "uuid" }
|
|
59918
|
+
},
|
|
59919
|
+
shared_plan_ids: {
|
|
59920
|
+
type: "array",
|
|
59921
|
+
items: { type: "string", format: "uuid" }
|
|
59922
|
+
},
|
|
59923
|
+
complete: { type: "boolean", enum: [true] }
|
|
59924
|
+
}
|
|
59925
|
+
};
|
|
59926
|
+
taskSubtreeTransferApplyRequestSchema = {
|
|
59927
|
+
type: "object",
|
|
59928
|
+
additionalProperties: false,
|
|
59929
|
+
required: [
|
|
59930
|
+
"version",
|
|
59931
|
+
"operation_id",
|
|
59932
|
+
"step_id",
|
|
59933
|
+
"idempotency_key",
|
|
59934
|
+
"precondition_digest",
|
|
59935
|
+
"source_project_id",
|
|
59936
|
+
"destination_project_id",
|
|
59937
|
+
"destination_task_list_id",
|
|
59938
|
+
"root_task_id",
|
|
59939
|
+
"destination_parent_id",
|
|
59940
|
+
"expected_root_parent_id",
|
|
59941
|
+
"source_population_digest",
|
|
59942
|
+
"expected_tasks",
|
|
59943
|
+
"shared_plan_splits"
|
|
59944
|
+
],
|
|
59945
|
+
properties: {
|
|
59946
|
+
...taskSubtreeTransferInspectRequestSchema.properties,
|
|
59947
|
+
version: { type: "integer", enum: [1] },
|
|
59948
|
+
operation_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
59949
|
+
step_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
59950
|
+
idempotency_key: { type: "string", minLength: 1, maxLength: 240 },
|
|
59951
|
+
precondition_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
59952
|
+
expected_root_parent_id: { type: "string", format: "uuid", nullable: true },
|
|
59953
|
+
source_population_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
59954
|
+
expected_tasks: {
|
|
59955
|
+
type: "array",
|
|
59956
|
+
minItems: 1,
|
|
59957
|
+
items: { $ref: "#/components/schemas/TaskSubtreeTransferExpectedTask" }
|
|
59958
|
+
},
|
|
59959
|
+
shared_plan_splits: {
|
|
59960
|
+
type: "array",
|
|
59961
|
+
items: { $ref: "#/components/schemas/TaskSubtreeTransferSharedPlanSplit" }
|
|
59962
|
+
}
|
|
59963
|
+
}
|
|
59964
|
+
};
|
|
59965
|
+
taskSubtreeTransferTaskImageSchema = {
|
|
59966
|
+
type: "object",
|
|
59967
|
+
additionalProperties: false,
|
|
59968
|
+
required: [
|
|
59969
|
+
"task_id",
|
|
59970
|
+
"project_id",
|
|
59971
|
+
"parent_id",
|
|
59972
|
+
"plan_id",
|
|
59973
|
+
"task_list_id",
|
|
59974
|
+
"version",
|
|
59975
|
+
"updated_at"
|
|
59976
|
+
],
|
|
59977
|
+
properties: {
|
|
59978
|
+
task_id: { type: "string", format: "uuid" },
|
|
59979
|
+
project_id: { type: "string", format: "uuid", nullable: true },
|
|
59980
|
+
parent_id: { type: "string", format: "uuid", nullable: true },
|
|
59981
|
+
plan_id: { type: "string", format: "uuid", nullable: true },
|
|
59982
|
+
task_list_id: { type: "string", format: "uuid", nullable: true },
|
|
59983
|
+
version: { type: "integer", minimum: 1 },
|
|
59984
|
+
updated_at: { type: "string", format: "date-time" }
|
|
59985
|
+
}
|
|
59986
|
+
};
|
|
59987
|
+
taskSubtreeTransferPlanImageSchema = {
|
|
59988
|
+
type: "object",
|
|
59989
|
+
additionalProperties: false,
|
|
59990
|
+
required: ["plan_id", "project_id", "task_list_id", "updated_at"],
|
|
59991
|
+
properties: {
|
|
59992
|
+
plan_id: { type: "string", format: "uuid" },
|
|
59993
|
+
project_id: { type: "string", format: "uuid", nullable: true },
|
|
59994
|
+
task_list_id: { type: "string", format: "uuid", nullable: true },
|
|
59995
|
+
updated_at: { type: "string", format: "date-time" }
|
|
59996
|
+
}
|
|
59997
|
+
};
|
|
59998
|
+
taskSubtreeTransferImageSchema = {
|
|
59999
|
+
type: "object",
|
|
60000
|
+
additionalProperties: false,
|
|
60001
|
+
required: ["tasks", "plans"],
|
|
60002
|
+
properties: {
|
|
60003
|
+
tasks: {
|
|
60004
|
+
type: "array",
|
|
60005
|
+
items: { $ref: "#/components/schemas/TaskSubtreeTransferTaskImage" }
|
|
60006
|
+
},
|
|
60007
|
+
plans: {
|
|
60008
|
+
type: "array",
|
|
60009
|
+
items: { $ref: "#/components/schemas/TaskSubtreeTransferPlanImage" }
|
|
60010
|
+
}
|
|
60011
|
+
}
|
|
60012
|
+
};
|
|
60013
|
+
taskSubtreeTransferReceiptSchema = {
|
|
60014
|
+
type: "object",
|
|
60015
|
+
additionalProperties: false,
|
|
60016
|
+
required: [
|
|
60017
|
+
"receipt_id",
|
|
60018
|
+
"authority",
|
|
60019
|
+
"route",
|
|
60020
|
+
"schema_version",
|
|
60021
|
+
"kind",
|
|
60022
|
+
"operation_id",
|
|
60023
|
+
"step_id",
|
|
60024
|
+
"idempotency_key",
|
|
60025
|
+
"request_digest",
|
|
60026
|
+
"precondition_digest",
|
|
60027
|
+
"result_digest",
|
|
60028
|
+
"apply_receipt_id",
|
|
60029
|
+
"source_project_id",
|
|
60030
|
+
"destination_project_id",
|
|
60031
|
+
"destination_task_list_id",
|
|
60032
|
+
"root_task_id",
|
|
60033
|
+
"source_population_digest",
|
|
60034
|
+
"prior_image",
|
|
60035
|
+
"post_image",
|
|
60036
|
+
"shared_plan_splits",
|
|
60037
|
+
"created_at"
|
|
60038
|
+
],
|
|
60039
|
+
properties: {
|
|
60040
|
+
receipt_id: { type: "string", format: "uuid" },
|
|
60041
|
+
authority: { type: "string", enum: ["todos"] },
|
|
60042
|
+
route: { type: "string", enum: ["todos.task-subtree-transfer.v1"] },
|
|
60043
|
+
schema_version: { type: "integer", enum: [1] },
|
|
60044
|
+
kind: { type: "string", enum: ["apply", "rollback"] },
|
|
60045
|
+
operation_id: { type: "string" },
|
|
60046
|
+
step_id: { type: "string" },
|
|
60047
|
+
idempotency_key: { type: "string" },
|
|
60048
|
+
request_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
60049
|
+
precondition_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
60050
|
+
result_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
60051
|
+
apply_receipt_id: { type: "string", format: "uuid", nullable: true },
|
|
60052
|
+
source_project_id: { type: "string", format: "uuid" },
|
|
60053
|
+
destination_project_id: { type: "string", format: "uuid" },
|
|
60054
|
+
destination_task_list_id: { type: "string", format: "uuid" },
|
|
60055
|
+
root_task_id: { type: "string", format: "uuid" },
|
|
60056
|
+
source_population_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
60057
|
+
prior_image: { $ref: "#/components/schemas/TaskSubtreeTransferImage" },
|
|
60058
|
+
post_image: { $ref: "#/components/schemas/TaskSubtreeTransferImage" },
|
|
60059
|
+
shared_plan_splits: {
|
|
60060
|
+
type: "array",
|
|
60061
|
+
items: { $ref: "#/components/schemas/TaskSubtreeTransferSharedPlanSplit" }
|
|
60062
|
+
},
|
|
60063
|
+
created_at: { type: "string", format: "date-time" }
|
|
60064
|
+
}
|
|
60065
|
+
};
|
|
60066
|
+
taskSubtreeTransferResultSchema = {
|
|
60067
|
+
type: "object",
|
|
60068
|
+
additionalProperties: false,
|
|
60069
|
+
required: ["duplicate", "receipt", "moved_task_ids", "moved_plan_ids", "complete"],
|
|
60070
|
+
properties: {
|
|
60071
|
+
duplicate: { type: "boolean" },
|
|
60072
|
+
receipt: { $ref: "#/components/schemas/TaskSubtreeTransferReceipt" },
|
|
60073
|
+
moved_task_ids: { type: "array", items: { type: "string", format: "uuid" } },
|
|
60074
|
+
moved_plan_ids: { type: "array", items: { type: "string", format: "uuid" } },
|
|
60075
|
+
complete: { type: "boolean", enum: [true] }
|
|
60076
|
+
}
|
|
60077
|
+
};
|
|
60078
|
+
taskSubtreeTransferCapabilitySchema = {
|
|
60079
|
+
type: "object",
|
|
60080
|
+
additionalProperties: false,
|
|
60081
|
+
required: [
|
|
60082
|
+
"authority",
|
|
60083
|
+
"route",
|
|
60084
|
+
"schema_version",
|
|
60085
|
+
"tenant_id",
|
|
60086
|
+
"backend",
|
|
60087
|
+
"exact_descendant_closure",
|
|
60088
|
+
"complete_source_population_digest",
|
|
60089
|
+
"per_task_version_cas",
|
|
60090
|
+
"explicit_shared_plan_splits",
|
|
60091
|
+
"atomic_apply",
|
|
60092
|
+
"immutable_forward_inverse_receipts",
|
|
60093
|
+
"prior_image_receipts",
|
|
60094
|
+
"cas_protected_rollback",
|
|
60095
|
+
"preserves_descendant_parent_ids",
|
|
60096
|
+
"preserves_task_and_relation_identities"
|
|
60097
|
+
],
|
|
60098
|
+
properties: {
|
|
60099
|
+
authority: { type: "string", enum: ["todos"] },
|
|
60100
|
+
route: { type: "string", enum: ["todos.task-subtree-transfer.v1"] },
|
|
60101
|
+
schema_version: { type: "integer", enum: [1] },
|
|
60102
|
+
tenant_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
60103
|
+
backend: { type: "string", enum: ["sqlite", "postgresql", "http"] },
|
|
60104
|
+
exact_descendant_closure: { type: "boolean", enum: [true] },
|
|
60105
|
+
complete_source_population_digest: { type: "boolean", enum: [true] },
|
|
60106
|
+
per_task_version_cas: { type: "boolean", enum: [true] },
|
|
60107
|
+
explicit_shared_plan_splits: { type: "boolean", enum: [true] },
|
|
60108
|
+
atomic_apply: { type: "boolean", enum: [true] },
|
|
60109
|
+
immutable_forward_inverse_receipts: { type: "boolean", enum: [true] },
|
|
60110
|
+
prior_image_receipts: { type: "boolean", enum: [true] },
|
|
60111
|
+
cas_protected_rollback: { type: "boolean", enum: [true] },
|
|
60112
|
+
preserves_descendant_parent_ids: { type: "boolean", enum: [true] },
|
|
60113
|
+
preserves_task_and_relation_identities: { type: "boolean", enum: [true] }
|
|
60114
|
+
}
|
|
60115
|
+
};
|
|
60116
|
+
taskSubtreeTransferRollbackRequestSchema = {
|
|
60117
|
+
type: "object",
|
|
60118
|
+
additionalProperties: false,
|
|
60119
|
+
required: ["receipt_id", "operation_id", "step_id", "idempotency_key", "precondition_digest"],
|
|
60120
|
+
properties: {
|
|
60121
|
+
receipt_id: { type: "string", format: "uuid" },
|
|
60122
|
+
operation_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
60123
|
+
step_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
60124
|
+
idempotency_key: { type: "string", minLength: 1, maxLength: 240 },
|
|
60125
|
+
precondition_digest: { type: "string", pattern: "^[0-9a-f]{64}$" }
|
|
60126
|
+
}
|
|
60127
|
+
};
|
|
60128
|
+
taskSubtreeTransferReadExactRequestSchema = {
|
|
60129
|
+
type: "object",
|
|
60130
|
+
additionalProperties: false,
|
|
60131
|
+
required: ["receipt_id"],
|
|
60132
|
+
properties: {
|
|
60133
|
+
receipt_id: { type: "string", format: "uuid" }
|
|
60134
|
+
}
|
|
60135
|
+
};
|
|
58285
60136
|
taskManifestCompensateRequestSchema = {
|
|
58286
60137
|
type: "object",
|
|
58287
60138
|
additionalProperties: false,
|
|
@@ -58960,8 +60811,8 @@ var exports_pr_groups = {};
|
|
|
58960
60811
|
__export(exports_pr_groups, {
|
|
58961
60812
|
handlePrGroupHttpRequest: () => handlePrGroupHttpRequest
|
|
58962
60813
|
});
|
|
58963
|
-
function
|
|
58964
|
-
return new Response(JSON.stringify(body2), { status:
|
|
60814
|
+
function json5(body2, status3 = 200) {
|
|
60815
|
+
return new Response(JSON.stringify(body2), { status: status3, headers: JSON_HEADERS4 });
|
|
58965
60816
|
}
|
|
58966
60817
|
function errorStatus2(error) {
|
|
58967
60818
|
switch (error.code) {
|
|
@@ -58995,26 +60846,26 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
|
|
|
58995
60846
|
const method = req.method.toUpperCase();
|
|
58996
60847
|
try {
|
|
58997
60848
|
if (!groupId && method === "POST" && action === undefined) {
|
|
58998
|
-
return
|
|
60849
|
+
return json5({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
|
|
58999
60850
|
}
|
|
59000
60851
|
if (groupId === "admit" && !action) {
|
|
59001
60852
|
if (method !== "POST")
|
|
59002
|
-
return
|
|
60853
|
+
return json5({ error: "method not allowed" }, 405);
|
|
59003
60854
|
const body2 = await readJson2(req);
|
|
59004
60855
|
if (!body2)
|
|
59005
|
-
return
|
|
59006
|
-
return
|
|
60856
|
+
return json5({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
60857
|
+
return json5(await ledger.admit(body2), 201);
|
|
59007
60858
|
}
|
|
59008
60859
|
if (!groupId)
|
|
59009
|
-
return
|
|
60860
|
+
return json5({ error: "PR group id is required", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
59010
60861
|
if (!action && method === "GET") {
|
|
59011
|
-
return
|
|
60862
|
+
return json5({ view: await ledger.get(groupId) });
|
|
59012
60863
|
}
|
|
59013
60864
|
if (action === "events") {
|
|
59014
60865
|
if (method === "GET") {
|
|
59015
60866
|
const limit = url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined;
|
|
59016
60867
|
const afterSequence = url.searchParams.has("after_sequence") ? Number(url.searchParams.get("after_sequence")) : undefined;
|
|
59017
|
-
return
|
|
60868
|
+
return json5({
|
|
59018
60869
|
history: await ledger.events(groupId, {
|
|
59019
60870
|
...limit !== undefined ? { limit } : {},
|
|
59020
60871
|
...afterSequence !== undefined ? { after_sequence: afterSequence } : {}
|
|
@@ -59024,72 +60875,49 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
|
|
|
59024
60875
|
if (method === "POST") {
|
|
59025
60876
|
const body2 = await readJson2(req);
|
|
59026
60877
|
if (!body2)
|
|
59027
|
-
return
|
|
59028
|
-
return
|
|
60878
|
+
return json5({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
60879
|
+
return json5(await ledger.append({
|
|
59029
60880
|
...body2,
|
|
59030
60881
|
group_id: groupId,
|
|
59031
60882
|
authenticated_actor_id: principal?.actor_id ?? undefined,
|
|
59032
60883
|
authenticated_actor_run_id: principal?.actor_run_id ?? undefined
|
|
59033
60884
|
}), 201);
|
|
59034
60885
|
}
|
|
59035
|
-
return
|
|
60886
|
+
return json5({ error: "method not allowed" }, 405);
|
|
59036
60887
|
}
|
|
59037
60888
|
if (action === "recover") {
|
|
59038
60889
|
if (method !== "POST")
|
|
59039
|
-
return
|
|
60890
|
+
return json5({ error: "method not allowed" }, 405);
|
|
59040
60891
|
const body2 = await readJson2(req);
|
|
59041
60892
|
if (!body2)
|
|
59042
|
-
return
|
|
59043
|
-
return
|
|
60893
|
+
return json5({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
60894
|
+
return json5(await ledger.recover({
|
|
59044
60895
|
...body2,
|
|
59045
60896
|
group_id: groupId
|
|
59046
60897
|
}), 201);
|
|
59047
60898
|
}
|
|
59048
|
-
return
|
|
60899
|
+
return json5({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
|
|
59049
60900
|
} catch (cause) {
|
|
59050
60901
|
if (cause instanceof PrGroupLedgerError) {
|
|
59051
|
-
return
|
|
60902
|
+
return json5({
|
|
59052
60903
|
error: cause.message,
|
|
59053
60904
|
code: cause.code,
|
|
59054
60905
|
details: cause.details,
|
|
59055
60906
|
authoritative: true
|
|
59056
60907
|
}, errorStatus2(cause));
|
|
59057
60908
|
}
|
|
59058
|
-
return
|
|
60909
|
+
return json5({
|
|
59059
60910
|
error: cause instanceof Error ? cause.message : "internal PR-group error",
|
|
59060
60911
|
code: "PR_GROUP_ATOMICITY_UNAVAILABLE"
|
|
59061
60912
|
}, 500);
|
|
59062
60913
|
}
|
|
59063
60914
|
}
|
|
59064
|
-
var
|
|
60915
|
+
var JSON_HEADERS4;
|
|
59065
60916
|
var init_pr_groups = __esm(() => {
|
|
59066
60917
|
init_types3();
|
|
59067
|
-
|
|
60918
|
+
JSON_HEADERS4 = { "Content-Type": "application/json" };
|
|
59068
60919
|
});
|
|
59069
60920
|
|
|
59070
|
-
// src/lib/comment-cursor.ts
|
|
59071
|
-
function encodeCommentCursor(comment2) {
|
|
59072
|
-
return Buffer.from(JSON.stringify({ created_at: comment2.created_at, id: comment2.id }), "utf8").toString("base64url");
|
|
59073
|
-
}
|
|
59074
|
-
function decodeCommentCursor(value) {
|
|
59075
|
-
if (value.length > MAX_COMMENT_CURSOR_LENGTH)
|
|
59076
|
-
throw new Error("invalid comment cursor");
|
|
59077
|
-
let parsed;
|
|
59078
|
-
try {
|
|
59079
|
-
parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
59080
|
-
} catch {
|
|
59081
|
-
throw new Error("invalid comment cursor");
|
|
59082
|
-
}
|
|
59083
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
59084
|
-
throw new Error("invalid comment cursor");
|
|
59085
|
-
const cursor = parsed;
|
|
59086
|
-
if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
|
|
59087
|
-
throw new Error("invalid comment cursor");
|
|
59088
|
-
}
|
|
59089
|
-
return { created_at: cursor["created_at"], id: cursor["id"] };
|
|
59090
|
-
}
|
|
59091
|
-
var MAX_COMMENT_CURSOR_LENGTH = 1024;
|
|
59092
|
-
|
|
59093
60921
|
// src/lib/project-task-list-ensure.ts
|
|
59094
60922
|
import { createHash as createHash17 } from "crypto";
|
|
59095
60923
|
function canonicalJson2(value) {
|
|
@@ -59099,11 +60927,11 @@ function canonicalJson2(value) {
|
|
|
59099
60927
|
return `[${value.map(canonicalJson2).join(",")}]`;
|
|
59100
60928
|
return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key2, item]) => `${JSON.stringify(key2)}:${canonicalJson2(item)}`).join(",")}}`;
|
|
59101
60929
|
}
|
|
59102
|
-
function
|
|
60930
|
+
function digest3(value) {
|
|
59103
60931
|
return createHash17("sha256").update(canonicalJson2(value)).digest("hex");
|
|
59104
60932
|
}
|
|
59105
60933
|
function deriveIdempotencyKey(projectId, slug) {
|
|
59106
|
-
return `ptlk_${
|
|
60934
|
+
return `ptlk_${digest3({ project_id: projectId, slug }).slice(0, 48)}`;
|
|
59107
60935
|
}
|
|
59108
60936
|
function normalizeIdempotencyKey(value, projectId, slug) {
|
|
59109
60937
|
const key2 = value?.trim() || deriveIdempotencyKey(projectId, slug);
|
|
@@ -59113,12 +60941,12 @@ function normalizeIdempotencyKey(value, projectId, slug) {
|
|
|
59113
60941
|
return key2;
|
|
59114
60942
|
}
|
|
59115
60943
|
function receiptId2(projectId, slug, idempotencyKey2) {
|
|
59116
|
-
return `ptlr_${
|
|
60944
|
+
return `ptlr_${digest3({ project_id: projectId, slug, idempotency_key: idempotencyKey2 }).slice(0, 48)}`;
|
|
59117
60945
|
}
|
|
59118
60946
|
function semanticListDigest(list) {
|
|
59119
60947
|
const metadata = { ...list.metadata ?? {} };
|
|
59120
60948
|
delete metadata[RECEIPT_METADATA_KEY];
|
|
59121
|
-
return
|
|
60949
|
+
return digest3({
|
|
59122
60950
|
project_id: list.project_id,
|
|
59123
60951
|
slug: list.slug,
|
|
59124
60952
|
name: list.name,
|
|
@@ -59147,7 +60975,7 @@ function receiptFor(store, project, list, idempotencyKey2) {
|
|
|
59147
60975
|
}
|
|
59148
60976
|
return {
|
|
59149
60977
|
schema_version: PROJECT_TASK_LIST_ENSURE_SCHEMA_VERSION,
|
|
59150
|
-
receipt_id: owned ? marker.receipt_id : `ptlr_existing_${
|
|
60978
|
+
receipt_id: owned ? marker.receipt_id : `ptlr_existing_${digest3({ project_id: project.id, task_list_id: list.id }).slice(0, 39)}`,
|
|
59151
60979
|
idempotency_key: owned ? marker.idempotency_key : idempotencyKey2,
|
|
59152
60980
|
project_id: project.id,
|
|
59153
60981
|
task_list_id: list.id,
|
|
@@ -59326,7 +61154,7 @@ async function rollbackProjectTaskListEnsure(store, projectId, options) {
|
|
|
59326
61154
|
project_id: project.id,
|
|
59327
61155
|
task_list_id: list.id,
|
|
59328
61156
|
accepted_receipt_id: options.receipt_id,
|
|
59329
|
-
rollback_receipt_id: `ptlr_inverse_${
|
|
61157
|
+
rollback_receipt_id: `ptlr_inverse_${digest3({ accepted_receipt_id: options.receipt_id }).slice(0, 38)}`,
|
|
59330
61158
|
removed_at: new Date().toISOString()
|
|
59331
61159
|
};
|
|
59332
61160
|
}
|
|
@@ -59421,11 +61249,11 @@ __export(exports_v1, {
|
|
|
59421
61249
|
handleV1Request: () => handleV1Request,
|
|
59422
61250
|
countSnapshotRecords: () => countSnapshotRecords
|
|
59423
61251
|
});
|
|
59424
|
-
function
|
|
59425
|
-
return new Response(JSON.stringify(body2), { status:
|
|
61252
|
+
function json6(body2, status3 = 200) {
|
|
61253
|
+
return new Response(JSON.stringify(body2), { status: status3, headers: JSON_HEADERS5 });
|
|
59426
61254
|
}
|
|
59427
|
-
function error(
|
|
59428
|
-
return
|
|
61255
|
+
function error(status3, message, extra) {
|
|
61256
|
+
return json6({ error: message, ...extra ?? {} }, status3);
|
|
59429
61257
|
}
|
|
59430
61258
|
function enumQueryParam(url, name, vocabulary) {
|
|
59431
61259
|
const raw = url.searchParams.get(name);
|
|
@@ -59764,6 +61592,9 @@ function contextFromPrincipal(principal, body2) {
|
|
|
59764
61592
|
function redactComment3(comment2) {
|
|
59765
61593
|
return { ...comment2, content: redactEvidenceText(comment2.content) };
|
|
59766
61594
|
}
|
|
61595
|
+
function redactPlanComment2(comment2) {
|
|
61596
|
+
return { ...comment2, content: redactEvidenceText(comment2.content) };
|
|
61597
|
+
}
|
|
59767
61598
|
function normalizeImportSnapshot(raw) {
|
|
59768
61599
|
const body2 = raw && typeof raw === "object" ? raw : {};
|
|
59769
61600
|
const arr = (v) => Array.isArray(v) ? v : [];
|
|
@@ -59872,6 +61703,9 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
59872
61703
|
if (path === "/v1/task-manifest" || path.startsWith("/v1/task-manifest/")) {
|
|
59873
61704
|
return handleTodosTaskManifestHttpRequest(req, url, (dependencies.getTaskManifestAuthority ?? getCloudTaskManifestAuthority)());
|
|
59874
61705
|
}
|
|
61706
|
+
if (path === "/v1/task-subtree-transfer" || path.startsWith("/v1/task-subtree-transfer/")) {
|
|
61707
|
+
return handleTodosTaskSubtreeTransferHttpRequest(req, url, (dependencies.getTaskSubtreeTransferAuthority ?? getCloudTaskSubtreeTransferAuthority)());
|
|
61708
|
+
}
|
|
59875
61709
|
const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
|
|
59876
61710
|
const segments = path.split("/").filter(Boolean);
|
|
59877
61711
|
const resource = segments[1];
|
|
@@ -59893,7 +61727,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
59893
61727
|
const presentSet = new Set(found.map((t) => t.id));
|
|
59894
61728
|
const present = ids2.filter((i) => presentSet.has(i));
|
|
59895
61729
|
const missing = ids2.filter((i) => !presentSet.has(i));
|
|
59896
|
-
return
|
|
61730
|
+
return json6({
|
|
59897
61731
|
requested: ids2.length,
|
|
59898
61732
|
present_count: present.length,
|
|
59899
61733
|
missing_count: missing.length,
|
|
@@ -59943,11 +61777,11 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
59943
61777
|
}
|
|
59944
61778
|
if (!existing) {
|
|
59945
61779
|
const task3 = await store.tasks.create({ ...fields, title: body2.title }, contextFromPrincipal(principal, body2));
|
|
59946
|
-
return
|
|
61780
|
+
return json6({ task: task3, created: true }, 201);
|
|
59947
61781
|
}
|
|
59948
61782
|
try {
|
|
59949
61783
|
const task3 = await store.tasks.update(existing.id, { ...fields, version: existing.version }, contextFromPrincipal(principal, body2));
|
|
59950
|
-
return
|
|
61784
|
+
return json6({ task: task3, created: false });
|
|
59951
61785
|
} catch (e) {
|
|
59952
61786
|
const msg = e.message || "";
|
|
59953
61787
|
if (msg.includes("version conflict"))
|
|
@@ -59995,7 +61829,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
59995
61829
|
const tasks = await store.tasks.list(filter);
|
|
59996
61830
|
const { limit: _l, offset: _o, ...countFilter } = filter;
|
|
59997
61831
|
const total = await store.tasks.count(countFilter);
|
|
59998
|
-
return
|
|
61832
|
+
return json6({ tasks, count: tasks.length, total });
|
|
59999
61833
|
}
|
|
60000
61834
|
if (method === "POST") {
|
|
60001
61835
|
const body2 = await readJson3(req);
|
|
@@ -60020,7 +61854,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60020
61854
|
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== (body2.parent_id ?? null) || (persisted.plan_id ?? null) !== (body2.plan_id ?? null) || body2.created_by !== undefined && persisted.created_by !== body2.created_by) {
|
|
60021
61855
|
return error(500, "TASK_CREATE_PERSISTENCE_UNVERIFIED: task create was acknowledged but authoritative readback did not return the same stored task id, parent_id, plan_id, and explicit created_by", { code: "TASK_CREATE_PERSISTENCE_UNVERIFIED" });
|
|
60022
61856
|
}
|
|
60023
|
-
return
|
|
61857
|
+
return json6({ task: persisted }, 201);
|
|
60024
61858
|
}
|
|
60025
61859
|
return error(405, `method ${method} not allowed on /v1/tasks`);
|
|
60026
61860
|
}
|
|
@@ -60062,7 +61896,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60062
61896
|
new_holder: body3.new_holder,
|
|
60063
61897
|
reason: body3.reason
|
|
60064
61898
|
}, contextFromPrincipal(principal));
|
|
60065
|
-
return
|
|
61899
|
+
return json6({ receipt });
|
|
60066
61900
|
}
|
|
60067
61901
|
if (action === "comments") {
|
|
60068
61902
|
if (method === "GET") {
|
|
@@ -60076,7 +61910,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60076
61910
|
if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
|
|
60077
61911
|
return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
|
|
60078
61912
|
}
|
|
60079
|
-
return
|
|
61913
|
+
return json6({
|
|
60080
61914
|
comments: legacyPage,
|
|
60081
61915
|
count: legacyPage.length,
|
|
60082
61916
|
has_more: false,
|
|
@@ -60101,7 +61935,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60101
61935
|
const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
|
|
60102
61936
|
const hasMore = page.length > limit;
|
|
60103
61937
|
const comments = hasMore ? page.slice(1) : page;
|
|
60104
|
-
return
|
|
61938
|
+
return json6({
|
|
60105
61939
|
comments,
|
|
60106
61940
|
count: comments.length,
|
|
60107
61941
|
has_more: hasMore,
|
|
@@ -60124,7 +61958,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60124
61958
|
type: body3.type,
|
|
60125
61959
|
progress_pct: body3.progress_pct
|
|
60126
61960
|
}, contextFromPrincipal(principal, body3));
|
|
60127
|
-
return
|
|
61961
|
+
return json6({ comment: redactComment3(comment2) }, 201);
|
|
60128
61962
|
}
|
|
60129
61963
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
|
|
60130
61964
|
}
|
|
@@ -60134,7 +61968,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60134
61968
|
if (!await store.tasks.get(id))
|
|
60135
61969
|
return error(404, "task not found");
|
|
60136
61970
|
const history = await store.audit.getTaskHistory(id);
|
|
60137
|
-
return
|
|
61971
|
+
return json6({ history, count: history.length });
|
|
60138
61972
|
}
|
|
60139
61973
|
if (action === "lock" || action === "unlock") {
|
|
60140
61974
|
if (method !== "POST")
|
|
@@ -60146,7 +61980,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60146
61980
|
if (typeof store.tasks.lock !== "function")
|
|
60147
61981
|
return error(501, "task locking is not supported by this storage backend");
|
|
60148
61982
|
const agentId3 = body3.agent_id || principal.agent || "todos-serve";
|
|
60149
|
-
return
|
|
61983
|
+
return json6({ result: await store.tasks.lock(id, agentId3) });
|
|
60150
61984
|
}
|
|
60151
61985
|
if (typeof store.tasks.unlock !== "function")
|
|
60152
61986
|
return error(501, "task unlocking is not supported by this storage backend");
|
|
@@ -60154,7 +61988,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60154
61988
|
if (!principal.scopes.includes("todos:*"))
|
|
60155
61989
|
return error(403, "force unlock requires todos:* scope");
|
|
60156
61990
|
const released2 = await store.tasks.unlock(id);
|
|
60157
|
-
return
|
|
61991
|
+
return json6({ success: released2 });
|
|
60158
61992
|
}
|
|
60159
61993
|
if (body3.agent_id && body3.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
|
|
60160
61994
|
return error(403, "unlock agent_id must match the authenticated agent");
|
|
@@ -60163,7 +61997,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60163
61997
|
if (!agentId2)
|
|
60164
61998
|
return error(403, "unlock requires an agent-bound key or force=true");
|
|
60165
61999
|
const released = await store.tasks.unlock(id, agentId2);
|
|
60166
|
-
return
|
|
62000
|
+
return json6({ success: released });
|
|
60167
62001
|
}
|
|
60168
62002
|
if (action === "dependencies") {
|
|
60169
62003
|
if (!store.dependencies)
|
|
@@ -60172,7 +62006,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60172
62006
|
if (!await store.tasks.get(id))
|
|
60173
62007
|
return error(404, "task not found");
|
|
60174
62008
|
const edges = await store.dependencies.list(id);
|
|
60175
|
-
return
|
|
62009
|
+
return json6(edges);
|
|
60176
62010
|
}
|
|
60177
62011
|
if (method === "POST") {
|
|
60178
62012
|
const body3 = await readJson3(req) ?? {};
|
|
@@ -60181,7 +62015,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60181
62015
|
}
|
|
60182
62016
|
try {
|
|
60183
62017
|
const dependency2 = await store.dependencies.add(id, body3.depends_on, contextFromPrincipal(principal));
|
|
60184
|
-
return
|
|
62018
|
+
return json6({ dependency: dependency2 }, 201);
|
|
60185
62019
|
} catch (e) {
|
|
60186
62020
|
const msg = e.message || "";
|
|
60187
62021
|
if (msg.includes("not found"))
|
|
@@ -60195,7 +62029,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60195
62029
|
if (!subId)
|
|
60196
62030
|
return error(400, "dependency target id is required (/v1/tasks/:id/dependencies/:dep)");
|
|
60197
62031
|
const removed = await store.dependencies.remove(id, subId);
|
|
60198
|
-
return
|
|
62032
|
+
return json6({ removed });
|
|
60199
62033
|
}
|
|
60200
62034
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/dependencies`);
|
|
60201
62035
|
}
|
|
@@ -60206,7 +62040,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60206
62040
|
if (!await store.tasks.get(id))
|
|
60207
62041
|
return error(404, "task not found");
|
|
60208
62042
|
const verifications = await store.verifications.list(id);
|
|
60209
|
-
return
|
|
62043
|
+
return json6({ verifications, count: verifications.length });
|
|
60210
62044
|
}
|
|
60211
62045
|
if (method === "POST") {
|
|
60212
62046
|
const body3 = await readJson3(req) ?? {};
|
|
@@ -60222,7 +62056,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60222
62056
|
artifact_path: body3.artifact_path,
|
|
60223
62057
|
agent_id: body3.agent_id
|
|
60224
62058
|
}, contextFromPrincipal(principal, body3));
|
|
60225
|
-
return
|
|
62059
|
+
return json6({ verification: verification2 }, 201);
|
|
60226
62060
|
} catch (e) {
|
|
60227
62061
|
const msg = e.message || "";
|
|
60228
62062
|
if (msg.includes("not found"))
|
|
@@ -60239,7 +62073,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60239
62073
|
if (!await store.tasks.get(id))
|
|
60240
62074
|
return error(404, "task not found");
|
|
60241
62075
|
const commits = await store.commits.list(id);
|
|
60242
|
-
return
|
|
62076
|
+
return json6({ commits, count: commits.length });
|
|
60243
62077
|
}
|
|
60244
62078
|
if (method === "POST") {
|
|
60245
62079
|
const body3 = await readJson3(req) ?? {};
|
|
@@ -60253,7 +62087,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60253
62087
|
author: body3.author,
|
|
60254
62088
|
files_changed: Array.isArray(body3.files_changed) ? body3.files_changed : undefined
|
|
60255
62089
|
}, contextFromPrincipal(principal));
|
|
60256
|
-
return
|
|
62090
|
+
return json6({ commit }, 201);
|
|
60257
62091
|
} catch (e) {
|
|
60258
62092
|
const msg = e.message || "";
|
|
60259
62093
|
if (msg.includes("not found"))
|
|
@@ -60270,7 +62104,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60270
62104
|
if (!await store.tasks.get(id))
|
|
60271
62105
|
return error(404, "task not found");
|
|
60272
62106
|
const refs = await store.gitRefs.list(id);
|
|
60273
|
-
return
|
|
62107
|
+
return json6({ refs, count: refs.length });
|
|
60274
62108
|
}
|
|
60275
62109
|
if (method === "POST") {
|
|
60276
62110
|
const body3 = await readJson3(req) ?? {};
|
|
@@ -60286,7 +62120,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60286
62120
|
provider: body3.provider,
|
|
60287
62121
|
metadata: body3.metadata
|
|
60288
62122
|
}, contextFromPrincipal(principal));
|
|
60289
|
-
return
|
|
62123
|
+
return json6({ ref }, 201);
|
|
60290
62124
|
} catch (e) {
|
|
60291
62125
|
const msg = e.message || "";
|
|
60292
62126
|
if (msg.includes("not found"))
|
|
@@ -60302,13 +62136,13 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60302
62136
|
const body2 = actionJson.value && typeof actionJson.value === "object" && !Array.isArray(actionJson.value) ? actionJson.value : {};
|
|
60303
62137
|
const agentId = typeof body2.agent_id === "string" ? body2.agent_id : principal.agent || "todos-serve";
|
|
60304
62138
|
if (action === "start" && method === "POST") {
|
|
60305
|
-
return
|
|
62139
|
+
return json6({ task: await store.tasks.start(id, agentId) });
|
|
60306
62140
|
}
|
|
60307
62141
|
if (action === "complete" && method === "POST") {
|
|
60308
62142
|
const parsed = validateTaskCompletion(actionJson.value);
|
|
60309
62143
|
if (!parsed.ok)
|
|
60310
62144
|
return error(400, parsed.message);
|
|
60311
|
-
return
|
|
62145
|
+
return json6({
|
|
60312
62146
|
task: await store.tasks.complete(id, parsed.agentId || principal.agent || "todos-serve", parsed.options, contextFromPrincipal(principal, body2))
|
|
60313
62147
|
});
|
|
60314
62148
|
}
|
|
@@ -60316,12 +62150,12 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60316
62150
|
const parsed = validateTaskFailure(actionJson.value);
|
|
60317
62151
|
if (!parsed.ok)
|
|
60318
62152
|
return error(400, parsed.message);
|
|
60319
|
-
return
|
|
62153
|
+
return json6({
|
|
60320
62154
|
result: await store.tasks.fail(id, parsed.agentId || principal.agent || "todos-serve", parsed.reason, { retry: parsed.retry }, contextFromPrincipal(principal, body2))
|
|
60321
62155
|
});
|
|
60322
62156
|
}
|
|
60323
62157
|
if (action === "claim" && method === "POST") {
|
|
60324
|
-
return
|
|
62158
|
+
return json6({ task: await store.tasks.claimNext(agentId, {}) });
|
|
60325
62159
|
}
|
|
60326
62160
|
return error(404, `unknown task action: ${action}`);
|
|
60327
62161
|
}
|
|
@@ -60344,7 +62178,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60344
62178
|
throw e;
|
|
60345
62179
|
}
|
|
60346
62180
|
}
|
|
60347
|
-
return task3 ?
|
|
62181
|
+
return task3 ? json6({ task: task3 }) : error(404, "task not found");
|
|
60348
62182
|
}
|
|
60349
62183
|
if (method === "PATCH" || method === "PUT") {
|
|
60350
62184
|
const rawBody = await readJson3(req);
|
|
@@ -60363,7 +62197,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60363
62197
|
};
|
|
60364
62198
|
try {
|
|
60365
62199
|
const task3 = await store.tasks.update(id, patch);
|
|
60366
|
-
return task3 ?
|
|
62200
|
+
return task3 ? json6({ task: task3 }) : error(404, "task not found");
|
|
60367
62201
|
} catch (e) {
|
|
60368
62202
|
const msg = e.message || "";
|
|
60369
62203
|
if (msg.includes("version conflict"))
|
|
@@ -60373,7 +62207,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60373
62207
|
}
|
|
60374
62208
|
if (method === "DELETE") {
|
|
60375
62209
|
await store.tasks.delete(id, contextFromPrincipal(principal));
|
|
60376
|
-
return
|
|
62210
|
+
return json6({ deleted: true, id });
|
|
60377
62211
|
}
|
|
60378
62212
|
return error(405, `method ${method} not allowed on /v1/tasks/:id`);
|
|
60379
62213
|
}
|
|
@@ -60381,7 +62215,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60381
62215
|
if (!id) {
|
|
60382
62216
|
if (method === "GET") {
|
|
60383
62217
|
const projects = await store.projects.list();
|
|
60384
|
-
return
|
|
62218
|
+
return json6({ projects, count: projects.length });
|
|
60385
62219
|
}
|
|
60386
62220
|
if (method === "POST") {
|
|
60387
62221
|
const body2 = await readJson3(req);
|
|
@@ -60391,13 +62225,13 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60391
62225
|
if (!validated.ok)
|
|
60392
62226
|
return error(400, validated.message);
|
|
60393
62227
|
const project = await store.projects.create(validated.input, contextFromPrincipal(principal));
|
|
60394
|
-
return
|
|
62228
|
+
return json6({ project }, 201);
|
|
60395
62229
|
}
|
|
60396
62230
|
return error(405, `method ${method} not allowed on /v1/projects`);
|
|
60397
62231
|
}
|
|
60398
62232
|
if (action === "task-list" && subId === "ensure") {
|
|
60399
62233
|
if (method === "GET") {
|
|
60400
|
-
return
|
|
62234
|
+
return json6(await planProjectTaskListEnsure(store, id));
|
|
60401
62235
|
}
|
|
60402
62236
|
if (method !== "POST") {
|
|
60403
62237
|
return error(405, `method ${method} not allowed on /v1/projects/:id/task-list/ensure`);
|
|
@@ -60418,7 +62252,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60418
62252
|
expected_project_revision: body2.expected_project_revision,
|
|
60419
62253
|
...typeof body2.idempotency_key === "string" ? { idempotency_key: body2.idempotency_key } : {}
|
|
60420
62254
|
});
|
|
60421
|
-
return
|
|
62255
|
+
return json6(result, result.action === "created" ? 201 : 200);
|
|
60422
62256
|
}
|
|
60423
62257
|
if (action === "task-list" && subId === "rollback") {
|
|
60424
62258
|
if (method !== "POST") {
|
|
@@ -60436,7 +62270,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60436
62270
|
if (typeof body2.expected_task_list_revision !== "string" || !body2.expected_task_list_revision.trim()) {
|
|
60437
62271
|
return error(400, "expected_task_list_revision must be a non-empty string from the accepted receipt");
|
|
60438
62272
|
}
|
|
60439
|
-
return
|
|
62273
|
+
return json6(await rollbackProjectTaskListEnsure(store, id, {
|
|
60440
62274
|
receipt_id: body2.receipt_id,
|
|
60441
62275
|
expected_task_list_revision: body2.expected_task_list_revision
|
|
60442
62276
|
}));
|
|
@@ -60454,11 +62288,11 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60454
62288
|
const unknownField = Object.keys(body2).find((key2) => !["new_slug", "name"].includes(key2));
|
|
60455
62289
|
if (unknownField)
|
|
60456
62290
|
return error(400, `unknown project rename field: ${unknownField}`);
|
|
60457
|
-
return
|
|
62291
|
+
return json6(await store.projects.rename(id, body2, contextFromPrincipal(principal)));
|
|
60458
62292
|
}
|
|
60459
62293
|
if (method === "GET") {
|
|
60460
62294
|
const project = await store.projects.get(id);
|
|
60461
|
-
return project ?
|
|
62295
|
+
return project ? json6({ project }) : error(404, "project not found");
|
|
60462
62296
|
}
|
|
60463
62297
|
if (method === "PATCH" || method === "PUT") {
|
|
60464
62298
|
const body2 = await readJson3(req);
|
|
@@ -60470,18 +62304,18 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60470
62304
|
if (!await store.projects.get(id))
|
|
60471
62305
|
return error(404, "project not found");
|
|
60472
62306
|
const project = await store.projects.update(id, validated.patch);
|
|
60473
|
-
return
|
|
62307
|
+
return json6({ project });
|
|
60474
62308
|
}
|
|
60475
62309
|
if (method === "DELETE") {
|
|
60476
62310
|
await store.projects.delete(id, contextFromPrincipal(principal));
|
|
60477
|
-
return
|
|
62311
|
+
return json6({ deleted: true, id });
|
|
60478
62312
|
}
|
|
60479
62313
|
return error(405, `method ${method} not allowed on /v1/projects/:id`);
|
|
60480
62314
|
}
|
|
60481
62315
|
if (resource === "plans") {
|
|
60482
62316
|
if (!id && method === "GET") {
|
|
60483
62317
|
const plans = await store.plans.list(url.searchParams.get("project_id") ?? undefined);
|
|
60484
|
-
return
|
|
62318
|
+
return json6({ plans, count: plans.length });
|
|
60485
62319
|
}
|
|
60486
62320
|
if (!id && method === "POST") {
|
|
60487
62321
|
const body2 = await readJson3(req);
|
|
@@ -60499,14 +62333,14 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60499
62333
|
}
|
|
60500
62334
|
}
|
|
60501
62335
|
const plan = await store.plans.create(validated.input, contextFromPrincipal(principal, validated.input));
|
|
60502
|
-
return
|
|
62336
|
+
return json6({ plan }, 201);
|
|
60503
62337
|
}
|
|
60504
62338
|
if (id && action === "project-link" && !subId) {
|
|
60505
62339
|
if (method === "GET") {
|
|
60506
62340
|
const projectId = url.searchParams.get("project_id");
|
|
60507
62341
|
if (!projectId?.trim())
|
|
60508
62342
|
return error(400, "project_id query parameter is required");
|
|
60509
|
-
return
|
|
62343
|
+
return json6(await planPlanProjectLink(store, id, projectId));
|
|
60510
62344
|
}
|
|
60511
62345
|
if (method !== "POST")
|
|
60512
62346
|
return error(405, `method ${method} not allowed on /v1/plans/:id/project-link`);
|
|
@@ -60527,7 +62361,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60527
62361
|
expected_project_revision: body2.expected_project_revision,
|
|
60528
62362
|
idempotency_key: body2.idempotency_key
|
|
60529
62363
|
});
|
|
60530
|
-
return
|
|
62364
|
+
return json6(result, result.action === "linked" ? 201 : 200);
|
|
60531
62365
|
}
|
|
60532
62366
|
if (id && action === "project-link" && subId === "rollback") {
|
|
60533
62367
|
if (method !== "POST")
|
|
@@ -60544,14 +62378,69 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60544
62378
|
return error(400, `${field} must be a non-empty string`);
|
|
60545
62379
|
}
|
|
60546
62380
|
}
|
|
60547
|
-
return
|
|
62381
|
+
return json6(await rollbackPlanProjectLink(store, id, body2.project_id, {
|
|
60548
62382
|
receipt_id: body2.receipt_id,
|
|
60549
62383
|
expected_plan_revision: body2.expected_plan_revision
|
|
60550
62384
|
}));
|
|
60551
62385
|
}
|
|
62386
|
+
if (id && action === "comments") {
|
|
62387
|
+
if (!await store.plans.get(id))
|
|
62388
|
+
return error(404, "plan not found");
|
|
62389
|
+
if (method === "GET") {
|
|
62390
|
+
if (store.plans.getCommentsPage) {
|
|
62391
|
+
const rawLimit = url.searchParams.get("limit");
|
|
62392
|
+
const cursor = url.searchParams.get("cursor");
|
|
62393
|
+
const limit = rawLimit === null ? DEFAULT_COMMENT_PAGE_SIZE : Number(rawLimit);
|
|
62394
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_COMMENT_PAGE_SIZE) {
|
|
62395
|
+
return error(400, `limit must be an integer between 1 and ${MAX_COMMENT_PAGE_SIZE}`);
|
|
62396
|
+
}
|
|
62397
|
+
let before;
|
|
62398
|
+
if (cursor) {
|
|
62399
|
+
try {
|
|
62400
|
+
before = decodeCommentCursor(cursor);
|
|
62401
|
+
} catch {
|
|
62402
|
+
return error(400, "invalid comment cursor");
|
|
62403
|
+
}
|
|
62404
|
+
}
|
|
62405
|
+
const page = (await store.plans.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactPlanComment2);
|
|
62406
|
+
const hasMore = page.length > limit;
|
|
62407
|
+
const comments2 = hasMore ? page.slice(1) : page;
|
|
62408
|
+
return json6({
|
|
62409
|
+
comments: comments2,
|
|
62410
|
+
count: comments2.length,
|
|
62411
|
+
has_more: hasMore,
|
|
62412
|
+
next_cursor: hasMore && comments2[0] ? encodeCommentCursor(comments2[0]) : null
|
|
62413
|
+
});
|
|
62414
|
+
}
|
|
62415
|
+
const comments = (await store.plans.getComments?.(id, contextFromPrincipal(principal)) ?? []).map(redactPlanComment2);
|
|
62416
|
+
return json6({ comments, count: comments.length });
|
|
62417
|
+
}
|
|
62418
|
+
if (method === "POST") {
|
|
62419
|
+
const body2 = await readJson3(req) ?? {};
|
|
62420
|
+
if (typeof body2.content !== "string" || !body2.content.trim()) {
|
|
62421
|
+
return error(400, "content is required");
|
|
62422
|
+
}
|
|
62423
|
+
const target = await store.plans.get(id);
|
|
62424
|
+
if (!target)
|
|
62425
|
+
return error(404, "plan not found");
|
|
62426
|
+
if (!store.plans.addComment) {
|
|
62427
|
+
return error(501, "plan comments are not supported by this storage backend");
|
|
62428
|
+
}
|
|
62429
|
+
const comment2 = await store.plans.addComment({
|
|
62430
|
+
plan_id: id,
|
|
62431
|
+
content: body2.content,
|
|
62432
|
+
agent_id: body2.agent_id ?? principal.agent ?? undefined,
|
|
62433
|
+
session_id: body2.session_id,
|
|
62434
|
+
type: body2.type,
|
|
62435
|
+
progress_pct: body2.progress_pct
|
|
62436
|
+
}, contextFromPrincipal(principal, body2));
|
|
62437
|
+
return json6({ comment: redactPlanComment2(comment2) }, 201);
|
|
62438
|
+
}
|
|
62439
|
+
return error(405, `method ${method} not allowed on /v1/plans/:id/comments`);
|
|
62440
|
+
}
|
|
60552
62441
|
if (id && method === "GET") {
|
|
60553
62442
|
const plan = await store.plans.get(id);
|
|
60554
|
-
return plan ?
|
|
62443
|
+
return plan ? json6({ plan }) : error(404, "plan not found");
|
|
60555
62444
|
}
|
|
60556
62445
|
if (id && (method === "PATCH" || method === "PUT")) {
|
|
60557
62446
|
const body2 = await readJson3(req);
|
|
@@ -60591,12 +62480,12 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60591
62480
|
}
|
|
60592
62481
|
}
|
|
60593
62482
|
const plan = await store.plans.update(id, body2);
|
|
60594
|
-
return
|
|
62483
|
+
return json6({ plan });
|
|
60595
62484
|
}
|
|
60596
62485
|
if (id && method === "DELETE") {
|
|
60597
62486
|
if (!await store.plans.delete(id, contextFromPrincipal(principal)))
|
|
60598
62487
|
return error(404, "plan not found");
|
|
60599
|
-
return
|
|
62488
|
+
return json6({ deleted: true, id });
|
|
60600
62489
|
}
|
|
60601
62490
|
if (id)
|
|
60602
62491
|
return error(405, `method ${method} not allowed on /v1/plans/:id`);
|
|
@@ -60605,7 +62494,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60605
62494
|
if (!id && method === "GET") {
|
|
60606
62495
|
const projectId = url.searchParams.get("project_id");
|
|
60607
62496
|
const templates = (await store.templates.list()).filter((template) => projectId === null || template.project_id === projectId);
|
|
60608
|
-
return
|
|
62497
|
+
return json6({ templates, count: templates.length });
|
|
60609
62498
|
}
|
|
60610
62499
|
if (!id && method === "POST") {
|
|
60611
62500
|
const body2 = await readJson3(req);
|
|
@@ -60613,13 +62502,13 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60613
62502
|
if (!validated.ok)
|
|
60614
62503
|
return error(400, validated.message);
|
|
60615
62504
|
const template = await store.templates.create(validated.input, contextFromPrincipal(principal));
|
|
60616
|
-
return
|
|
62505
|
+
return json6({ template: await store.templates.getWithTasks(template.id) }, 201);
|
|
60617
62506
|
}
|
|
60618
62507
|
if (!id)
|
|
60619
62508
|
return error(405, `method ${method} not allowed on /v1/templates`);
|
|
60620
62509
|
if (method === "GET") {
|
|
60621
62510
|
const template = await store.templates.getWithTasks(id);
|
|
60622
|
-
return template ?
|
|
62511
|
+
return template ? json6({ template }) : error(404, "template not found");
|
|
60623
62512
|
}
|
|
60624
62513
|
if (method === "PATCH" || method === "PUT") {
|
|
60625
62514
|
const body2 = await readJson3(req);
|
|
@@ -60627,18 +62516,18 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60627
62516
|
if (!validated.ok)
|
|
60628
62517
|
return error(400, validated.message);
|
|
60629
62518
|
const template = await store.templates.update(id, validated.patch, contextFromPrincipal(principal));
|
|
60630
|
-
return template ?
|
|
62519
|
+
return template ? json6({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
|
|
60631
62520
|
}
|
|
60632
62521
|
if (method === "DELETE") {
|
|
60633
62522
|
const deleted = await store.templates.delete(id, contextFromPrincipal(principal));
|
|
60634
|
-
return deleted ?
|
|
62523
|
+
return deleted ? json6({ deleted: true, id }) : error(404, "template not found");
|
|
60635
62524
|
}
|
|
60636
62525
|
return error(405, `method ${method} not allowed on /v1/templates/:id`);
|
|
60637
62526
|
}
|
|
60638
62527
|
if (resource === "agents") {
|
|
60639
62528
|
if (!id && method === "GET") {
|
|
60640
62529
|
const agents = await store.agents.list();
|
|
60641
|
-
return
|
|
62530
|
+
return json6({ agents, count: agents.length });
|
|
60642
62531
|
}
|
|
60643
62532
|
if (!id && method === "POST") {
|
|
60644
62533
|
const body2 = await readJson3(req);
|
|
@@ -60648,7 +62537,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60648
62537
|
if (result && typeof result === "object" && "conflict" in result) {
|
|
60649
62538
|
return error(409, result.message ?? "agent name conflict", { conflict: true });
|
|
60650
62539
|
}
|
|
60651
|
-
return
|
|
62540
|
+
return json6({ agent: result }, 201);
|
|
60652
62541
|
}
|
|
60653
62542
|
if (id && action === "heartbeat") {
|
|
60654
62543
|
if (method !== "POST")
|
|
@@ -60657,7 +62546,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60657
62546
|
return error(501, "agent heartbeat is not supported by this storage backend");
|
|
60658
62547
|
}
|
|
60659
62548
|
const agent = await store.agents.heartbeat(id, contextFromPrincipal(principal));
|
|
60660
|
-
return agent ?
|
|
62549
|
+
return agent ? json6({ agent }) : error(404, "agent not found");
|
|
60661
62550
|
}
|
|
60662
62551
|
if (id && action === "release") {
|
|
60663
62552
|
if (method !== "POST")
|
|
@@ -60672,11 +62561,11 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60672
62561
|
if (!result.released) {
|
|
60673
62562
|
return error(409, "release denied: session_id does not match agent's current session", { released: false });
|
|
60674
62563
|
}
|
|
60675
|
-
return
|
|
62564
|
+
return json6({ agent: result.agent, released: true });
|
|
60676
62565
|
}
|
|
60677
62566
|
if (id && method === "GET") {
|
|
60678
62567
|
const agent = await store.agents.get(id);
|
|
60679
|
-
return agent ?
|
|
62568
|
+
return agent ? json6({ agent }) : error(404, "agent not found");
|
|
60680
62569
|
}
|
|
60681
62570
|
}
|
|
60682
62571
|
if (resource === "activity" && !id) {
|
|
@@ -60685,13 +62574,13 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60685
62574
|
const limitParam = url.searchParams.get("limit");
|
|
60686
62575
|
const limit = limitParam ? Math.max(1, Math.min(1e4, Number(limitParam) || 50)) : 50;
|
|
60687
62576
|
const activity = await store.audit.getRecentActivity(limit);
|
|
60688
|
-
return
|
|
62577
|
+
return json6({ activity, count: activity.length });
|
|
60689
62578
|
}
|
|
60690
62579
|
if (resource === "task-lists") {
|
|
60691
62580
|
if (!id && method === "GET") {
|
|
60692
62581
|
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
60693
62582
|
const taskLists = await store.taskLists.list(projectId);
|
|
60694
|
-
return
|
|
62583
|
+
return json6({ task_lists: taskLists, count: taskLists.length });
|
|
60695
62584
|
}
|
|
60696
62585
|
if (!id && method === "POST") {
|
|
60697
62586
|
const body2 = await readJson3(req);
|
|
@@ -60713,17 +62602,17 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60713
62602
|
return error(400, "task-list slug must be non-empty kebab-case");
|
|
60714
62603
|
}
|
|
60715
62604
|
const taskList = await store.taskLists.create(body2, contextFromPrincipal(principal));
|
|
60716
|
-
return
|
|
62605
|
+
return json6({ task_list: taskList }, 201);
|
|
60717
62606
|
}
|
|
60718
62607
|
if (id && method === "GET") {
|
|
60719
62608
|
const taskList = await store.taskLists.get(id);
|
|
60720
|
-
return taskList ?
|
|
62609
|
+
return taskList ? json6({ task_list: taskList }) : error(404, "task list not found");
|
|
60721
62610
|
}
|
|
60722
62611
|
if (id && (method === "PATCH" || method === "PUT")) {
|
|
60723
62612
|
const body2 = await readJson3(req);
|
|
60724
62613
|
if (!body2)
|
|
60725
62614
|
return error(400, "invalid JSON body");
|
|
60726
|
-
const unknownField = Object.keys(body2).find((key2) => !["slug", "name", "description", "metadata"].includes(key2));
|
|
62615
|
+
const unknownField = Object.keys(body2).find((key2) => !["slug", "name", "description", "metadata", "project_id"].includes(key2));
|
|
60727
62616
|
if (unknownField)
|
|
60728
62617
|
return error(400, `unsupported task-list update field: ${unknownField}`);
|
|
60729
62618
|
if (Object.keys(body2).length === 0)
|
|
@@ -60737,14 +62626,26 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60737
62626
|
if (body2.metadata !== undefined && (!body2.metadata || typeof body2.metadata !== "object" || Array.isArray(body2.metadata))) {
|
|
60738
62627
|
return error(400, "metadata must be an object");
|
|
60739
62628
|
}
|
|
62629
|
+
if (body2.project_id !== undefined) {
|
|
62630
|
+
if (body2.project_id !== null && (typeof body2.project_id !== "string" || !body2.project_id.trim())) {
|
|
62631
|
+
return error(400, "project_id must be a non-empty string or null");
|
|
62632
|
+
}
|
|
62633
|
+
if (typeof body2.project_id === "string" && body2.project_id.trim() !== "") {
|
|
62634
|
+
const target = await store.projects.get(body2.project_id.trim());
|
|
62635
|
+
if (!target) {
|
|
62636
|
+
return error(400, `task-list project_id must reference a registered project: ${body2.project_id}`);
|
|
62637
|
+
}
|
|
62638
|
+
body2.project_id = target.id;
|
|
62639
|
+
}
|
|
62640
|
+
}
|
|
60740
62641
|
if (!await store.taskLists.get(id))
|
|
60741
62642
|
return error(404, "task list not found");
|
|
60742
62643
|
const taskList = await store.taskLists.update(id, body2);
|
|
60743
|
-
return
|
|
62644
|
+
return json6({ task_list: taskList });
|
|
60744
62645
|
}
|
|
60745
62646
|
if (id && method === "DELETE") {
|
|
60746
62647
|
const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
|
|
60747
|
-
return deleted ?
|
|
62648
|
+
return deleted ? json6({ deleted: true, id }) : error(404, "task list not found");
|
|
60748
62649
|
}
|
|
60749
62650
|
return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
|
|
60750
62651
|
}
|
|
@@ -60755,7 +62656,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60755
62656
|
return error(501, "dependency edge listing is not supported by this storage backend");
|
|
60756
62657
|
}
|
|
60757
62658
|
const dependencies2 = await store.dependencies.listAll();
|
|
60758
|
-
return
|
|
62659
|
+
return json6({ dependencies: dependencies2, count: dependencies2.length });
|
|
60759
62660
|
}
|
|
60760
62661
|
if (resource === "commits" && id) {
|
|
60761
62662
|
if (method !== "GET")
|
|
@@ -60763,7 +62664,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60763
62664
|
if (!store.commits)
|
|
60764
62665
|
return error(501, "commit links are not supported by this storage backend");
|
|
60765
62666
|
const commit = await store.commits.find(id);
|
|
60766
|
-
return
|
|
62667
|
+
return json6({ commit: commit ?? null });
|
|
60767
62668
|
}
|
|
60768
62669
|
if (resource === "refs" && id) {
|
|
60769
62670
|
if (method !== "GET")
|
|
@@ -60777,7 +62678,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60777
62678
|
return error(400, "ref path segment has invalid percent encoding");
|
|
60778
62679
|
}
|
|
60779
62680
|
const refs = await store.gitRefs.find(decodedRef);
|
|
60780
|
-
return
|
|
62681
|
+
return json6({ refs, count: refs.length });
|
|
60781
62682
|
}
|
|
60782
62683
|
if (resource === "next" && !id) {
|
|
60783
62684
|
if (method !== "GET")
|
|
@@ -60789,7 +62690,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60789
62690
|
...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {}
|
|
60790
62691
|
};
|
|
60791
62692
|
const task3 = await store.tasks.getNext(agent, filters);
|
|
60792
|
-
return
|
|
62693
|
+
return json6({ task: task3 ?? null });
|
|
60793
62694
|
}
|
|
60794
62695
|
if (resource === "stats" && method === "GET") {
|
|
60795
62696
|
const [tasks, tasksAll, projects] = await Promise.all([
|
|
@@ -60797,7 +62698,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60797
62698
|
store.tasks.count({ include_subtasks: true }),
|
|
60798
62699
|
store.projects.list()
|
|
60799
62700
|
]);
|
|
60800
|
-
return
|
|
62701
|
+
return json6({ tasks, tasks_all: tasksAll, subtasks: tasksAll - tasks, projects: projects.length });
|
|
60801
62702
|
}
|
|
60802
62703
|
if (resource === "integrity" && !id) {
|
|
60803
62704
|
if (method !== "GET")
|
|
@@ -60806,7 +62707,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60806
62707
|
return error(501, "referential-integrity reporting is not supported by this storage backend");
|
|
60807
62708
|
}
|
|
60808
62709
|
const integrity = await store.integrity.report();
|
|
60809
|
-
return
|
|
62710
|
+
return json6({ integrity });
|
|
60810
62711
|
}
|
|
60811
62712
|
if (resource === "import") {
|
|
60812
62713
|
if (method !== "POST")
|
|
@@ -60831,7 +62732,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60831
62732
|
}
|
|
60832
62733
|
const operation = completionImports.operations[0];
|
|
60833
62734
|
const completed = await store.plans.completeAtRevision(operation.id, operation.expected_updated_at, contextFromPrincipal(principal));
|
|
60834
|
-
return
|
|
62735
|
+
return json6({
|
|
60835
62736
|
result: {
|
|
60836
62737
|
inserted: 0,
|
|
60837
62738
|
updated: completed.applied ? 1 : 0,
|
|
@@ -60870,17 +62771,17 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60870
62771
|
audit_history_id: failure.auditHistoryId
|
|
60871
62772
|
});
|
|
60872
62773
|
}
|
|
60873
|
-
return
|
|
62774
|
+
return json6({ result, received });
|
|
60874
62775
|
}
|
|
60875
62776
|
return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
60876
62777
|
} catch (e) {
|
|
60877
62778
|
if (e instanceof PlanProjectLinkError) {
|
|
60878
|
-
const
|
|
60879
|
-
return error(
|
|
62779
|
+
const status3 = 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;
|
|
62780
|
+
return error(status3, e.message, { code: e.code, conflict: status3 === 409, ...e.details });
|
|
60880
62781
|
}
|
|
60881
62782
|
if (e instanceof ProjectTaskListEnsureError) {
|
|
60882
|
-
const
|
|
60883
|
-
return error(
|
|
62783
|
+
const status3 = e.code === "PROJECT_NOT_FOUND" || e.code === "PROJECT_TASK_LIST_RECEIPT_NOT_FOUND" ? 404 : e.code === "PROJECT_TASK_LIST_IDEMPOTENCY_KEY_INVALID" ? 400 : 409;
|
|
62784
|
+
return error(status3, e.message, { code: e.code, conflict: status3 === 409, ...e.details });
|
|
60884
62785
|
}
|
|
60885
62786
|
if (e instanceof TaskReferenceAmbiguousError) {
|
|
60886
62787
|
return error(409, e.message, {
|
|
@@ -60902,10 +62803,10 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60902
62803
|
});
|
|
60903
62804
|
}
|
|
60904
62805
|
if (e instanceof StaleLockHandoffError) {
|
|
60905
|
-
const
|
|
60906
|
-
return error(
|
|
62806
|
+
const status3 = e.code === "STALE_LOCK_HANDOFF_INVALID_TASK_ID" || e.code === "STALE_LOCK_HANDOFF_INVALID_INPUT" ? 400 : e.code === "STALE_LOCK_HANDOFF_ACTOR_MISMATCH" ? 403 : 409;
|
|
62807
|
+
return error(status3, e.message, {
|
|
60907
62808
|
code: e.code,
|
|
60908
|
-
conflict:
|
|
62809
|
+
conflict: status3 === 409,
|
|
60909
62810
|
...e.details
|
|
60910
62811
|
});
|
|
60911
62812
|
}
|
|
@@ -60933,19 +62834,20 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
60933
62834
|
return error(500, e.message || "internal error");
|
|
60934
62835
|
}
|
|
60935
62836
|
}
|
|
60936
|
-
var
|
|
62837
|
+
var JSON_HEADERS5, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500, RFC3339_DATE_TIME;
|
|
60937
62838
|
var init_v1 = __esm(() => {
|
|
60938
62839
|
init_types();
|
|
60939
62840
|
init_cloud();
|
|
60940
62841
|
init_pr_groups();
|
|
60941
62842
|
init_project_registration();
|
|
60942
62843
|
init_task_manifest();
|
|
62844
|
+
init_task_subtree_transfer();
|
|
60943
62845
|
init_redaction();
|
|
60944
62846
|
init_project_task_list_ensure();
|
|
60945
62847
|
init_plan_project_link();
|
|
60946
62848
|
init_stale_lock_handoff();
|
|
60947
62849
|
init_audit_history_import();
|
|
60948
|
-
|
|
62850
|
+
JSON_HEADERS5 = { "Content-Type": "application/json" };
|
|
60949
62851
|
RFC3339_DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/;
|
|
60950
62852
|
});
|
|
60951
62853
|
|
|
@@ -61156,7 +63058,7 @@ class SqlitePrGroupLedgerPersistence {
|
|
|
61156
63058
|
}
|
|
61157
63059
|
}
|
|
61158
63060
|
var sqliteTransactionTails2;
|
|
61159
|
-
var
|
|
63061
|
+
var init_sqlite4 = __esm(() => {
|
|
61160
63062
|
sqliteTransactionTails2 = new WeakMap;
|
|
61161
63063
|
});
|
|
61162
63064
|
|
|
@@ -61187,10 +63089,10 @@ function createLocalPrGroupLedger(db = getDatabase()) {
|
|
|
61187
63089
|
var init_pr_groups2 = __esm(() => {
|
|
61188
63090
|
init_database();
|
|
61189
63091
|
init_ledger();
|
|
61190
|
-
|
|
63092
|
+
init_sqlite4();
|
|
61191
63093
|
init_types3();
|
|
61192
63094
|
init_ledger();
|
|
61193
|
-
|
|
63095
|
+
init_sqlite4();
|
|
61194
63096
|
init_http_client();
|
|
61195
63097
|
init_postgres();
|
|
61196
63098
|
});
|
|
@@ -61201,7 +63103,7 @@ __export(exports_serve, {
|
|
|
61201
63103
|
taskToSummary: () => taskToSummary,
|
|
61202
63104
|
startServer: () => startServer,
|
|
61203
63105
|
serveStaticFile: () => serveStaticFile,
|
|
61204
|
-
json: () =>
|
|
63106
|
+
json: () => json4,
|
|
61205
63107
|
checkAuth: () => checkAuth,
|
|
61206
63108
|
SECURITY_HEADERS: () => SECURITY_HEADERS,
|
|
61207
63109
|
MIME_TYPES: () => MIME_TYPES
|
|
@@ -61287,9 +63189,9 @@ function checkRateLimit(ip) {
|
|
|
61287
63189
|
}
|
|
61288
63190
|
return { allowed: true };
|
|
61289
63191
|
}
|
|
61290
|
-
function
|
|
63192
|
+
function json4(data, status3 = 200, headers) {
|
|
61291
63193
|
return new Response(JSON.stringify(data), {
|
|
61292
|
-
status:
|
|
63194
|
+
status: status3,
|
|
61293
63195
|
headers: {
|
|
61294
63196
|
"Content-Type": "application/json",
|
|
61295
63197
|
...SECURITY_HEADERS,
|
|
@@ -61432,7 +63334,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
61432
63334
|
"Access-Control-Allow-Headers": "Content-Type, X-API-Key, Authorization",
|
|
61433
63335
|
Vary: "Origin"
|
|
61434
63336
|
} : undefined;
|
|
61435
|
-
const jsonWithCors = (data,
|
|
63337
|
+
const jsonWithCors = (data, status3 = 200) => json4(data, status3, corsHeaders);
|
|
61436
63338
|
if (method === "OPTIONS") {
|
|
61437
63339
|
return new Response(null, {
|
|
61438
63340
|
headers: corsHeaders || {
|
|
@@ -61513,13 +63415,13 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
61513
63415
|
return res;
|
|
61514
63416
|
}
|
|
61515
63417
|
if (path === "/api/health" && method === "GET") {
|
|
61516
|
-
return handleHealth(ctx,
|
|
63418
|
+
return handleHealth(ctx, json4);
|
|
61517
63419
|
}
|
|
61518
63420
|
if (path === "/api/headless" && method === "GET") {
|
|
61519
|
-
return handleHeadlessBoundary(ctx,
|
|
63421
|
+
return handleHeadlessBoundary(ctx, json4);
|
|
61520
63422
|
}
|
|
61521
63423
|
if (path === "/api/stats" && method === "GET") {
|
|
61522
|
-
return handleStats(ctx,
|
|
63424
|
+
return handleStats(ctx, json4);
|
|
61523
63425
|
}
|
|
61524
63426
|
if (path === "/api/tasks" && method === "GET") {
|
|
61525
63427
|
return handleListTasks(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -61534,16 +63436,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
61534
63436
|
return handleTasksExport(req, url, ctx, jsonWithCors, taskToSummary);
|
|
61535
63437
|
}
|
|
61536
63438
|
if (path === "/api/tasks/bulk" && method === "POST") {
|
|
61537
|
-
return handleTasksBulk(req, ctx,
|
|
63439
|
+
return handleTasksBulk(req, ctx, json4);
|
|
61538
63440
|
}
|
|
61539
63441
|
if (path === "/api/tasks/status" && method === "GET") {
|
|
61540
|
-
return handleTasksStatus(req, url, ctx,
|
|
63442
|
+
return handleTasksStatus(req, url, ctx, json4);
|
|
61541
63443
|
}
|
|
61542
63444
|
if (path === "/api/tasks/next" && method === "GET") {
|
|
61543
63445
|
return handleTasksNext(req, url, ctx, jsonWithCors, taskToSummary);
|
|
61544
63446
|
}
|
|
61545
63447
|
if (path === "/api/tasks/active" && method === "GET") {
|
|
61546
|
-
return handleTasksActive(req, url, ctx,
|
|
63448
|
+
return handleTasksActive(req, url, ctx, json4);
|
|
61547
63449
|
}
|
|
61548
63450
|
if (path === "/api/tasks/stale" && method === "GET") {
|
|
61549
63451
|
return handleTasksStale(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -61556,11 +63458,11 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
61556
63458
|
}
|
|
61557
63459
|
const attachmentsMatch = path.match(/^\/api\/tasks\/([^/]+)\/attachments$/);
|
|
61558
63460
|
if (attachmentsMatch && method === "GET") {
|
|
61559
|
-
return handleTaskAttachments(attachmentsMatch[1], ctx,
|
|
63461
|
+
return handleTaskAttachments(attachmentsMatch[1], ctx, json4);
|
|
61560
63462
|
}
|
|
61561
63463
|
const progressMatch = path.match(/^\/api\/tasks\/([^/]+)\/progress$/);
|
|
61562
63464
|
if (progressMatch) {
|
|
61563
|
-
const res = await handleTaskProgress(progressMatch[1], req, method, ctx,
|
|
63465
|
+
const res = await handleTaskProgress(progressMatch[1], req, method, ctx, json4, url);
|
|
61564
63466
|
if (res !== null)
|
|
61565
63467
|
return res;
|
|
61566
63468
|
}
|
|
@@ -61574,7 +63476,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
61574
63476
|
return handlePatchTask(id, req, ctx, jsonWithCors, taskToSummary);
|
|
61575
63477
|
}
|
|
61576
63478
|
if (method === "DELETE") {
|
|
61577
|
-
return handleDeleteTask(id, ctx,
|
|
63479
|
+
return handleDeleteTask(id, ctx, json4);
|
|
61578
63480
|
}
|
|
61579
63481
|
}
|
|
61580
63482
|
const startMatch = path.match(/^\/api\/tasks\/([^/]+)\/start$/);
|
|
@@ -61590,7 +63492,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
61590
63492
|
return handleCompleteTask(completeMatch[1], ctx, jsonWithCors, taskToSummary);
|
|
61591
63493
|
}
|
|
61592
63494
|
if (path === "/api/projects" && method === "GET") {
|
|
61593
|
-
return handleListProjects(url, ctx,
|
|
63495
|
+
return handleListProjects(url, ctx, json4);
|
|
61594
63496
|
}
|
|
61595
63497
|
if (path === "/api/agents/me" && method === "GET") {
|
|
61596
63498
|
return handleAgentMe(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -61603,92 +63505,92 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
61603
63505
|
return handleClaimTask(req, ctx, jsonWithCors, taskToSummary);
|
|
61604
63506
|
}
|
|
61605
63507
|
if (path === "/api/orgs" && method === "GET") {
|
|
61606
|
-
return handleListOrgs(ctx,
|
|
63508
|
+
return handleListOrgs(ctx, json4);
|
|
61607
63509
|
}
|
|
61608
63510
|
if (path === "/api/orgs" && method === "POST") {
|
|
61609
|
-
return handleCreateOrg(req, ctx,
|
|
63511
|
+
return handleCreateOrg(req, ctx, json4);
|
|
61610
63512
|
}
|
|
61611
63513
|
const orgMatch = path.match(/^\/api\/orgs\/([^/]+)$/);
|
|
61612
63514
|
if (orgMatch && method === "PATCH") {
|
|
61613
|
-
return handleUpdateOrg(orgMatch[1], req, ctx,
|
|
63515
|
+
return handleUpdateOrg(orgMatch[1], req, ctx, json4);
|
|
61614
63516
|
}
|
|
61615
63517
|
if (orgMatch && method === "DELETE") {
|
|
61616
|
-
return handleDeleteOrg(orgMatch[1], ctx,
|
|
63518
|
+
return handleDeleteOrg(orgMatch[1], ctx, json4);
|
|
61617
63519
|
}
|
|
61618
63520
|
if (path === "/api/org" && method === "GET") {
|
|
61619
|
-
return handleOrgChart(ctx,
|
|
63521
|
+
return handleOrgChart(ctx, json4);
|
|
61620
63522
|
}
|
|
61621
63523
|
const teamMatch = path.match(/^\/api\/agents\/([^/]+)\/team$/);
|
|
61622
63524
|
if (teamMatch && method === "GET") {
|
|
61623
|
-
return handleAgentTeam(teamMatch[1], ctx,
|
|
63525
|
+
return handleAgentTeam(teamMatch[1], ctx, json4);
|
|
61624
63526
|
}
|
|
61625
63527
|
if (path === "/api/agents" && method === "GET") {
|
|
61626
|
-
return handleListAgents(url, ctx,
|
|
63528
|
+
return handleListAgents(url, ctx, json4);
|
|
61627
63529
|
}
|
|
61628
63530
|
if (path === "/api/projects" && method === "POST") {
|
|
61629
|
-
return handleCreateProject(req, ctx,
|
|
63531
|
+
return handleCreateProject(req, ctx, json4);
|
|
61630
63532
|
}
|
|
61631
63533
|
const projectDeleteMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
61632
63534
|
if (projectDeleteMatch && method === "DELETE") {
|
|
61633
|
-
return handleDeleteProject(projectDeleteMatch[1], ctx,
|
|
63535
|
+
return handleDeleteProject(projectDeleteMatch[1], ctx, json4);
|
|
61634
63536
|
}
|
|
61635
63537
|
if (path === "/api/agents" && method === "POST") {
|
|
61636
|
-
return handleRegisterAgent(req, ctx,
|
|
63538
|
+
return handleRegisterAgent(req, ctx, json4);
|
|
61637
63539
|
}
|
|
61638
63540
|
const agentMatch = path.match(/^\/api\/agents\/([^/]+)$/);
|
|
61639
63541
|
if (agentMatch && method === "PATCH") {
|
|
61640
|
-
return handleUpdateAgent(agentMatch[1], req, ctx,
|
|
63542
|
+
return handleUpdateAgent(agentMatch[1], req, ctx, json4);
|
|
61641
63543
|
}
|
|
61642
63544
|
if (agentMatch && method === "DELETE") {
|
|
61643
|
-
return handleDeleteAgent(agentMatch[1], ctx,
|
|
63545
|
+
return handleDeleteAgent(agentMatch[1], ctx, json4);
|
|
61644
63546
|
}
|
|
61645
63547
|
if (path === "/api/agents/bulk" && method === "POST") {
|
|
61646
|
-
return handleBulkDeleteAgents(req, ctx,
|
|
63548
|
+
return handleBulkDeleteAgents(req, ctx, json4);
|
|
61647
63549
|
}
|
|
61648
63550
|
if (path === "/api/projects/bulk" && method === "POST") {
|
|
61649
|
-
return handleBulkDeleteProjects(req, ctx,
|
|
63551
|
+
return handleBulkDeleteProjects(req, ctx, json4);
|
|
61650
63552
|
}
|
|
61651
63553
|
if (path === "/api/doctor" && method === "GET") {
|
|
61652
|
-
return handleDoctor(ctx,
|
|
63554
|
+
return handleDoctor(ctx, json4);
|
|
61653
63555
|
}
|
|
61654
63556
|
if (path === "/api/report" && method === "GET") {
|
|
61655
|
-
return handleReport(req, url, ctx,
|
|
63557
|
+
return handleReport(req, url, ctx, json4);
|
|
61656
63558
|
}
|
|
61657
63559
|
if (path === "/api/activity" && method === "GET") {
|
|
61658
|
-
return handleActivity(req, url, ctx,
|
|
63560
|
+
return handleActivity(req, url, ctx, json4);
|
|
61659
63561
|
}
|
|
61660
63562
|
const historyMatch = path.match(/^\/api\/tasks\/([^/]+)\/history$/);
|
|
61661
63563
|
if (historyMatch && method === "GET") {
|
|
61662
|
-
return handleTaskHistory(historyMatch[1], ctx,
|
|
63564
|
+
return handleTaskHistory(historyMatch[1], ctx, json4, url);
|
|
61663
63565
|
}
|
|
61664
63566
|
if (path === "/api/webhooks" && method === "GET") {
|
|
61665
|
-
return handleListWebhooks(ctx,
|
|
63567
|
+
return handleListWebhooks(ctx, json4);
|
|
61666
63568
|
}
|
|
61667
63569
|
if (path === "/api/webhooks" && method === "POST") {
|
|
61668
|
-
return handleCreateWebhook(req, ctx,
|
|
63570
|
+
return handleCreateWebhook(req, ctx, json4);
|
|
61669
63571
|
}
|
|
61670
63572
|
const webhookMatch = path.match(/^\/api\/webhooks\/([^/]+)$/);
|
|
61671
63573
|
if (webhookMatch && method === "DELETE") {
|
|
61672
|
-
return handleDeleteWebhook(webhookMatch[1], ctx,
|
|
63574
|
+
return handleDeleteWebhook(webhookMatch[1], ctx, json4);
|
|
61673
63575
|
}
|
|
61674
63576
|
if (path === "/api/templates" && method === "GET") {
|
|
61675
|
-
return handleListTemplates(ctx,
|
|
63577
|
+
return handleListTemplates(ctx, json4);
|
|
61676
63578
|
}
|
|
61677
63579
|
if (path === "/api/templates" && method === "POST") {
|
|
61678
|
-
return handleCreateTemplate(req, ctx,
|
|
63580
|
+
return handleCreateTemplate(req, ctx, json4);
|
|
61679
63581
|
}
|
|
61680
63582
|
const templateMatch = path.match(/^\/api\/templates\/([^/]+)$/);
|
|
61681
63583
|
if (templateMatch && method === "DELETE") {
|
|
61682
|
-
return handleDeleteTemplate(templateMatch[1], ctx,
|
|
63584
|
+
return handleDeleteTemplate(templateMatch[1], ctx, json4);
|
|
61683
63585
|
}
|
|
61684
63586
|
if (path === "/api/plans" && method === "GET") {
|
|
61685
|
-
return handleListPlans(url, ctx,
|
|
63587
|
+
return handleListPlans(url, ctx, json4);
|
|
61686
63588
|
}
|
|
61687
63589
|
if (path === "/api/plans" && method === "POST") {
|
|
61688
|
-
return handleCreatePlan(req, ctx,
|
|
63590
|
+
return handleCreatePlan(req, ctx, json4);
|
|
61689
63591
|
}
|
|
61690
63592
|
if (path === "/api/plans/bulk" && method === "POST") {
|
|
61691
|
-
return handleBulkDeletePlans(req, ctx,
|
|
63593
|
+
return handleBulkDeletePlans(req, ctx, json4);
|
|
61692
63594
|
}
|
|
61693
63595
|
const planMatch = path.match(/^\/api\/plans\/([^/]+)$/);
|
|
61694
63596
|
if (planMatch) {
|
|
@@ -61697,16 +63599,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
61697
63599
|
return handleGetPlan(id, ctx, jsonWithCors, taskToSummary);
|
|
61698
63600
|
}
|
|
61699
63601
|
if (method === "PATCH") {
|
|
61700
|
-
return handleUpdatePlan(id, req, ctx,
|
|
63602
|
+
return handleUpdatePlan(id, req, ctx, json4);
|
|
61701
63603
|
}
|
|
61702
63604
|
if (method === "DELETE") {
|
|
61703
|
-
return handleDeletePlan(id, ctx,
|
|
63605
|
+
return handleDeletePlan(id, ctx, json4);
|
|
61704
63606
|
}
|
|
61705
63607
|
}
|
|
61706
63608
|
const staticRes = handleStaticFiles(path, method, ctx, jsonWithCors, serveStaticFile);
|
|
61707
63609
|
if (staticRes)
|
|
61708
63610
|
return staticRes;
|
|
61709
|
-
return
|
|
63611
|
+
return json4({ error: "Not found" }, 404);
|
|
61710
63612
|
}
|
|
61711
63613
|
});
|
|
61712
63614
|
const shutdown = () => {
|