@hasna/todos 0.11.85 → 0.11.87
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 +55 -2
- 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/task-commands.d.ts +2 -0
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1106 -417
- package/dist/contracts.js +1 -1
- package/dist/db/comments.d.ts.map +1 -1
- package/dist/index.js +180 -5
- package/dist/mcp/index.js +483 -39
- package/dist/registry.js +1 -1
- package/dist/release-provenance.json +3 -3
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +21 -0
- package/dist/sdk/v1.generated.d.ts +54 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +13 -0
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +900 -384
- package/dist/server/openapi.d.ts +171 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts +7 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/comment-redaction-backfill.d.ts +32 -0
- package/dist/storage/comment-redaction-backfill.d.ts.map +1 -0
- package/dist/storage/index.d.ts +4 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/interfaces.d.ts +23 -0
- 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/postgres-sync.d.ts +6 -0
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage.d.ts +3 -3
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +184 -5
- package/package.json +2 -1
- package/vendor/hasna-contracts-0.5.1.tgz +0 -0
package/dist/mcp/index.js
CHANGED
|
@@ -14085,7 +14085,7 @@ function getComment(id, db) {
|
|
|
14085
14085
|
}
|
|
14086
14086
|
function listComments(taskId, db) {
|
|
14087
14087
|
const d = db || getDatabase();
|
|
14088
|
-
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
|
|
14088
|
+
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at, rowid").all(taskId);
|
|
14089
14089
|
}
|
|
14090
14090
|
function updateComment(id, input, db) {
|
|
14091
14091
|
const d = db || getDatabase();
|
|
@@ -22792,14 +22792,18 @@ function unwrapTask(raw) {
|
|
|
22792
22792
|
}
|
|
22793
22793
|
function toListQuery(filter = {}) {
|
|
22794
22794
|
const query = {};
|
|
22795
|
-
if (
|
|
22796
|
-
query["status"] = filter.status;
|
|
22797
|
-
if (
|
|
22798
|
-
query["priority"] = filter.priority;
|
|
22795
|
+
if (filter.status)
|
|
22796
|
+
query["status"] = Array.isArray(filter.status) ? filter.status.join(",") : filter.status;
|
|
22797
|
+
if (filter.priority)
|
|
22798
|
+
query["priority"] = Array.isArray(filter.priority) ? filter.priority.join(",") : filter.priority;
|
|
22799
22799
|
if (filter.project_id)
|
|
22800
22800
|
query["project_id"] = filter.project_id;
|
|
22801
|
+
if (filter.parent_id !== undefined)
|
|
22802
|
+
query["parent_id"] = filter.parent_id ?? "";
|
|
22801
22803
|
if (filter.plan_id)
|
|
22802
22804
|
query["plan_id"] = filter.plan_id;
|
|
22805
|
+
if (filter.task_list_id)
|
|
22806
|
+
query["task_list_id"] = filter.task_list_id;
|
|
22803
22807
|
if (filter.assigned_to)
|
|
22804
22808
|
query["assigned_to"] = filter.assigned_to;
|
|
22805
22809
|
if (filter.agent_id)
|
|
@@ -22849,10 +22853,19 @@ async function cloudListProjects(client) {
|
|
|
22849
22853
|
}
|
|
22850
22854
|
async function cloudAddComment(client, taskId, input) {
|
|
22851
22855
|
const raw = await client.transport.post(`/tasks/${encodeURIComponent(taskId)}/comments`, input);
|
|
22852
|
-
|
|
22853
|
-
|
|
22854
|
-
|
|
22855
|
-
return
|
|
22856
|
+
const comment = raw && typeof raw === "object" && "comment" in raw ? raw.comment : raw;
|
|
22857
|
+
if (!isTaskComment(comment))
|
|
22858
|
+
throw new Error("Invalid cloud comment response");
|
|
22859
|
+
return redactComment(comment);
|
|
22860
|
+
}
|
|
22861
|
+
function isTaskComment(value) {
|
|
22862
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
22863
|
+
return false;
|
|
22864
|
+
const comment = value;
|
|
22865
|
+
return typeof comment["id"] === "string" && typeof comment["task_id"] === "string" && (comment["agent_id"] === null || typeof comment["agent_id"] === "string") && (comment["session_id"] === null || typeof comment["session_id"] === "string") && typeof comment["content"] === "string" && (comment["type"] === "comment" || comment["type"] === "progress" || comment["type"] === "note") && (comment["progress_pct"] === null || typeof comment["progress_pct"] === "number") && typeof comment["created_at"] === "string";
|
|
22866
|
+
}
|
|
22867
|
+
function redactComment(comment) {
|
|
22868
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
22856
22869
|
}
|
|
22857
22870
|
async function cloudCountTasks(client, filter = {}) {
|
|
22858
22871
|
const { limit: _drop, offset: _o, ...rest } = filter;
|
|
@@ -22885,6 +22898,7 @@ async function cloudReleaseAgent(client, idOrName, sessionId) {
|
|
|
22885
22898
|
var _cache;
|
|
22886
22899
|
var init_cloud_router = __esm(() => {
|
|
22887
22900
|
init_storage();
|
|
22901
|
+
init_redaction();
|
|
22888
22902
|
});
|
|
22889
22903
|
|
|
22890
22904
|
// src/mcp/tools/task-crud.ts
|
|
@@ -46148,6 +46162,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
46148
46162
|
list: (filter = {}) => listTasks(filter, database()),
|
|
46149
46163
|
count: (filter = {}) => countTasks(filter, database()),
|
|
46150
46164
|
update: (id, input) => updateTask(id, input, database()),
|
|
46165
|
+
unlock: (id, agentId) => {
|
|
46166
|
+
unlockTask(id, agentId, database());
|
|
46167
|
+
return true;
|
|
46168
|
+
},
|
|
46151
46169
|
delete: (id) => deleteTask(id, database()),
|
|
46152
46170
|
start: (id, agentId) => startTask(id, agentId, database()),
|
|
46153
46171
|
complete: (id, agentId, options2) => completeTask(id, agentId, database(), options2),
|
|
@@ -46199,6 +46217,20 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
46199
46217
|
logTaskChange: (taskId, action, field, oldValue, newValue, agentId) => logTaskChange(taskId, action, field, oldValue, newValue, agentId, database()),
|
|
46200
46218
|
addComment: (input) => addComment(input, database()),
|
|
46201
46219
|
getComments: (taskId) => listComments(taskId, database()),
|
|
46220
|
+
getCommentsPage: (taskId, options2) => {
|
|
46221
|
+
if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
|
|
46222
|
+
throw new Error("Comment limit must be an integer between 1 and 1001");
|
|
46223
|
+
}
|
|
46224
|
+
let comments = listComments(taskId, database());
|
|
46225
|
+
comments = comments.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id));
|
|
46226
|
+
if (options2?.before) {
|
|
46227
|
+
const before = options2.before;
|
|
46228
|
+
comments = comments.filter((comment) => comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id);
|
|
46229
|
+
}
|
|
46230
|
+
if (options2?.limit !== undefined)
|
|
46231
|
+
comments = comments.slice(-options2.limit);
|
|
46232
|
+
return comments;
|
|
46233
|
+
},
|
|
46202
46234
|
getTaskHistory: (taskId) => getTaskHistory(taskId, database()),
|
|
46203
46235
|
getRecentActivity: (limit) => getRecentActivity(limit, database())
|
|
46204
46236
|
},
|
|
@@ -46256,6 +46288,12 @@ function postgresTodosSyncSchemaSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABL
|
|
|
46256
46288
|
)`
|
|
46257
46289
|
];
|
|
46258
46290
|
}
|
|
46291
|
+
function postgresTodosCommentCursorIndexSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABLE) {
|
|
46292
|
+
assertSafeIdentifier(tableName);
|
|
46293
|
+
return `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${tableName}_comment_task_created_idx
|
|
46294
|
+
ON ${tableName} (service, (payload->>'task_id'), (payload->>'created_at'), object_id)
|
|
46295
|
+
WHERE object_type = 'comments' AND deleted_at IS NULL`;
|
|
46296
|
+
}
|
|
46259
46297
|
|
|
46260
46298
|
class PostgresTodosSyncStore {
|
|
46261
46299
|
client;
|
|
@@ -48171,7 +48209,8 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
48171
48209
|
getActiveWork: (filters) => getActiveWork2(filters, store),
|
|
48172
48210
|
getChangedSince: (since, filters) => getChangedSince(since, filters, store),
|
|
48173
48211
|
lock: (id, agentId) => lockTask2(id, agentId, store),
|
|
48174
|
-
unlock: (id, agentId) => unlockTask2(id, agentId, store)
|
|
48212
|
+
unlock: (id, agentId) => unlockTask2(id, agentId, store),
|
|
48213
|
+
getByFingerprint: (fingerprint3) => store.getTaskByFingerprint(fingerprint3)
|
|
48175
48214
|
},
|
|
48176
48215
|
dependencies: {
|
|
48177
48216
|
add: (taskId, dependsOn, context) => addDependency2(taskId, dependsOn, store, context),
|
|
@@ -48239,7 +48278,24 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
48239
48278
|
audit: {
|
|
48240
48279
|
logTaskChange: (taskId, action, field, oldValue, newValue, agentId, context) => logTaskChange2(taskId, action, field, oldValue, newValue, agentId, store, context),
|
|
48241
48280
|
addComment: (input, context) => addComment2(input, store, context),
|
|
48242
|
-
getComments: async (taskId) =>
|
|
48281
|
+
getComments: async (taskId) => {
|
|
48282
|
+
const pages = [];
|
|
48283
|
+
let before;
|
|
48284
|
+
while (true) {
|
|
48285
|
+
const page = await store.listComments(taskId, { limit: 1000, ...before ? { before } : {} });
|
|
48286
|
+
if (page.length === 0)
|
|
48287
|
+
break;
|
|
48288
|
+
pages.unshift(page);
|
|
48289
|
+
if (page.length < 1000)
|
|
48290
|
+
break;
|
|
48291
|
+
const oldest = page[0];
|
|
48292
|
+
before = { created_at: oldest.created_at, id: oldest.id };
|
|
48293
|
+
}
|
|
48294
|
+
return pages.flat().map(redactComment2).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
48295
|
+
},
|
|
48296
|
+
getCommentsPage: async (taskId, options2) => {
|
|
48297
|
+
return (await store.listComments(taskId, options2)).map(redactComment2).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
48298
|
+
},
|
|
48243
48299
|
getTaskHistory: async (taskId) => (await store.list("audit_history")).filter((entry2) => entry2.task_id === taskId).sort((a, b) => a.created_at.localeCompare(b.created_at)),
|
|
48244
48300
|
getRecentActivity: async (limit = 20) => (await store.list("audit_history")).sort((a, b) => b.created_at.localeCompare(a.created_at)).slice(0, limit)
|
|
48245
48301
|
},
|
|
@@ -48289,6 +48345,27 @@ class PostgresJsonRecordStore {
|
|
|
48289
48345
|
async list(type) {
|
|
48290
48346
|
return (await this.listRecords(type)).map((record) => record.payload);
|
|
48291
48347
|
}
|
|
48348
|
+
async listComments(taskId, options = {}) {
|
|
48349
|
+
await this.ensureSchema();
|
|
48350
|
+
const limit = options.limit ?? 100;
|
|
48351
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
|
|
48352
|
+
throw new Error("Postgres comment limit must be an integer between 1 and 1001");
|
|
48353
|
+
}
|
|
48354
|
+
const params = [this.service, taskId];
|
|
48355
|
+
let cursorPredicate = "";
|
|
48356
|
+
if (options.before) {
|
|
48357
|
+
params.push(options.before.created_at, options.before.id);
|
|
48358
|
+
cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
|
|
48359
|
+
}
|
|
48360
|
+
params.push(limit);
|
|
48361
|
+
const result = await this.options.client.query(`/* todos:list-comments */ SELECT payload FROM ${this.tableName}
|
|
48362
|
+
WHERE service = $1 AND object_type = 'comments' AND deleted_at IS NULL
|
|
48363
|
+
AND payload->>'task_id' = $2
|
|
48364
|
+
${cursorPredicate}
|
|
48365
|
+
ORDER BY payload->>'created_at' DESC, object_id DESC
|
|
48366
|
+
LIMIT $${params.length}`, params);
|
|
48367
|
+
return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
|
|
48368
|
+
}
|
|
48292
48369
|
async listRecords(type) {
|
|
48293
48370
|
await this.ensureSchema();
|
|
48294
48371
|
const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
|
|
@@ -48360,6 +48437,17 @@ class PostgresJsonRecordStore {
|
|
|
48360
48437
|
const result = await this.options.client.query(sql, params);
|
|
48361
48438
|
return result.rows.map((row) => payloadRecord2(row.payload));
|
|
48362
48439
|
}
|
|
48440
|
+
async getTaskByFingerprint(fingerprint3) {
|
|
48441
|
+
await this.ensureSchema();
|
|
48442
|
+
const sql = `/* todos:task-by-fingerprint */ SELECT payload FROM ${this.tableName}
|
|
48443
|
+
WHERE service = $1 AND object_type = $2 AND deleted_at IS NULL
|
|
48444
|
+
AND payload->'metadata'->>'fingerprint' = $3
|
|
48445
|
+
ORDER BY payload->>'created_at' ASC
|
|
48446
|
+
LIMIT 1`;
|
|
48447
|
+
const result = await this.options.client.query(sql, [this.service, "tasks", fingerprint3]);
|
|
48448
|
+
const row = result.rows[0];
|
|
48449
|
+
return row ? payloadRecord2(row.payload) : null;
|
|
48450
|
+
}
|
|
48363
48451
|
async countTasks(filter) {
|
|
48364
48452
|
await this.ensureSchema();
|
|
48365
48453
|
const { where, params } = this.buildTaskFilterSql(filter);
|
|
@@ -48671,7 +48759,7 @@ async function lockTask2(id, agentId, store) {
|
|
|
48671
48759
|
async function unlockTask2(id, agentId, store) {
|
|
48672
48760
|
const task2 = await requireRecord("tasks", id, store);
|
|
48673
48761
|
if (agentId && task2.locked_by && task2.locked_by !== agentId) {
|
|
48674
|
-
throw new
|
|
48762
|
+
throw new LockError(id, task2.locked_by);
|
|
48675
48763
|
}
|
|
48676
48764
|
await patchTask(task2, { locked_by: null, locked_at: null }, store);
|
|
48677
48765
|
return true;
|
|
@@ -49036,13 +49124,16 @@ async function addComment2(input, store, context) {
|
|
|
49036
49124
|
task_id: input.task_id,
|
|
49037
49125
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
49038
49126
|
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
49039
|
-
content: input.content,
|
|
49127
|
+
content: redactEvidenceText(input.content),
|
|
49040
49128
|
type: input.type ?? "comment",
|
|
49041
49129
|
progress_pct: input.progress_pct ?? null,
|
|
49042
49130
|
created_at: new Date().toISOString()
|
|
49043
49131
|
};
|
|
49044
49132
|
return store.upsert("comments", comment, context);
|
|
49045
49133
|
}
|
|
49134
|
+
function redactComment2(comment) {
|
|
49135
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
49136
|
+
}
|
|
49046
49137
|
async function exportSnapshot(store) {
|
|
49047
49138
|
return {
|
|
49048
49139
|
exportedAt: new Date().toISOString(),
|
|
@@ -49188,7 +49279,106 @@ function numberValue3(value) {
|
|
|
49188
49279
|
return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
|
|
49189
49280
|
}
|
|
49190
49281
|
var CLOUD_LOCK_EXPIRY_MINUTES = 30, TASK_ORDER_BY = "ORDER BY CASE payload->>'priority' WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END ASC, payload->>'created_at' ASC, payload->>'id' ASC";
|
|
49191
|
-
var init_postgres_adapter = () => {
|
|
49282
|
+
var init_postgres_adapter = __esm(() => {
|
|
49283
|
+
init_types();
|
|
49284
|
+
init_redaction();
|
|
49285
|
+
});
|
|
49286
|
+
|
|
49287
|
+
// src/storage/comment-redaction-backfill.ts
|
|
49288
|
+
function assertSafeIdentifier2(value) {
|
|
49289
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
49290
|
+
throw new Error(`Unsafe Postgres identifier: ${value}`);
|
|
49291
|
+
}
|
|
49292
|
+
}
|
|
49293
|
+
function payloadObject(value) {
|
|
49294
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
49295
|
+
return value;
|
|
49296
|
+
if (typeof value !== "string")
|
|
49297
|
+
return null;
|
|
49298
|
+
try {
|
|
49299
|
+
const parsed = JSON.parse(value);
|
|
49300
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
49301
|
+
} catch {
|
|
49302
|
+
return null;
|
|
49303
|
+
}
|
|
49304
|
+
}
|
|
49305
|
+
async function backfillPostgresCommentRedaction(client, options = {}) {
|
|
49306
|
+
const apply = options.apply === true;
|
|
49307
|
+
if (apply && options.confirmation !== COMMENT_REDACTION_BACKFILL_CONFIRMATION) {
|
|
49308
|
+
throw new Error(`Applying the comment redaction backfill requires confirmation ${COMMENT_REDACTION_BACKFILL_CONFIRMATION}`);
|
|
49309
|
+
}
|
|
49310
|
+
const tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
|
|
49311
|
+
assertSafeIdentifier2(tableName);
|
|
49312
|
+
const service = options.service ?? "todos";
|
|
49313
|
+
const batchSize = options.batchSize ?? 100;
|
|
49314
|
+
if (!Number.isSafeInteger(batchSize) || batchSize < 1 || batchSize > 500) {
|
|
49315
|
+
throw new Error("Comment redaction backfill batchSize must be an integer between 1 and 500");
|
|
49316
|
+
}
|
|
49317
|
+
const result = {
|
|
49318
|
+
dry_run: !apply,
|
|
49319
|
+
scanned: 0,
|
|
49320
|
+
candidates: 0,
|
|
49321
|
+
updated: 0,
|
|
49322
|
+
conflicts: 0,
|
|
49323
|
+
batches: 0,
|
|
49324
|
+
remaining_candidates: 0
|
|
49325
|
+
};
|
|
49326
|
+
let afterId = "";
|
|
49327
|
+
while (true) {
|
|
49328
|
+
const page = await client.query(`/* todos:comment-redaction-backfill-scan */
|
|
49329
|
+
SELECT object_id, payload
|
|
49330
|
+
FROM ${tableName}
|
|
49331
|
+
WHERE service = $1 AND object_type = 'comments'
|
|
49332
|
+
AND object_id > $2
|
|
49333
|
+
ORDER BY object_id ASC
|
|
49334
|
+
LIMIT $3`, [service, afterId, batchSize]);
|
|
49335
|
+
if (page.rows.length === 0)
|
|
49336
|
+
break;
|
|
49337
|
+
result.batches += 1;
|
|
49338
|
+
for (const row of page.rows) {
|
|
49339
|
+
afterId = row.object_id;
|
|
49340
|
+
result.scanned += 1;
|
|
49341
|
+
const payload = payloadObject(row.payload);
|
|
49342
|
+
const original = payload?.["content"];
|
|
49343
|
+
if (typeof original !== "string")
|
|
49344
|
+
continue;
|
|
49345
|
+
const redacted = redactEvidenceText(original);
|
|
49346
|
+
if (redacted === original)
|
|
49347
|
+
continue;
|
|
49348
|
+
result.candidates += 1;
|
|
49349
|
+
if (!apply)
|
|
49350
|
+
continue;
|
|
49351
|
+
const nextPayload = { ...payload, content: redacted };
|
|
49352
|
+
const update = await client.query(`/* todos:comment-redaction-backfill-apply */
|
|
49353
|
+
UPDATE ${tableName}
|
|
49354
|
+
SET payload = $3::jsonb
|
|
49355
|
+
WHERE service = $1 AND object_type = 'comments' AND object_id = $2
|
|
49356
|
+
AND payload = $4::jsonb
|
|
49357
|
+
RETURNING object_id`, [service, row.object_id, nextPayload, row.payload]);
|
|
49358
|
+
if (update.rows.length === 1)
|
|
49359
|
+
result.updated += 1;
|
|
49360
|
+
else
|
|
49361
|
+
result.conflicts += 1;
|
|
49362
|
+
}
|
|
49363
|
+
if (page.rows.length < batchSize)
|
|
49364
|
+
break;
|
|
49365
|
+
}
|
|
49366
|
+
if (!apply) {
|
|
49367
|
+
result.remaining_candidates = result.candidates;
|
|
49368
|
+
return result;
|
|
49369
|
+
}
|
|
49370
|
+
const verification = await backfillPostgresCommentRedaction(client, {
|
|
49371
|
+
...options,
|
|
49372
|
+
apply: false,
|
|
49373
|
+
confirmation: undefined
|
|
49374
|
+
});
|
|
49375
|
+
result.remaining_candidates = verification.candidates;
|
|
49376
|
+
return result;
|
|
49377
|
+
}
|
|
49378
|
+
var COMMENT_REDACTION_BACKFILL_CONFIRMATION = "REDACT_STORED_TODOS_COMMENTS";
|
|
49379
|
+
var init_comment_redaction_backfill = __esm(() => {
|
|
49380
|
+
init_redaction();
|
|
49381
|
+
});
|
|
49192
49382
|
|
|
49193
49383
|
// src/server/cloud.ts
|
|
49194
49384
|
var exports_cloud = {};
|
|
@@ -49202,7 +49392,9 @@ __export(exports_cloud, {
|
|
|
49202
49392
|
getCloudStorageAdapter: () => getCloudStorageAdapter,
|
|
49203
49393
|
getApiKeyStore: () => getApiKeyStore,
|
|
49204
49394
|
ensureCloudSchema: () => ensureCloudSchema,
|
|
49395
|
+
ensureCloudCommentCursorIndex: () => ensureCloudCommentCursorIndex,
|
|
49205
49396
|
closeCloud: () => closeCloud,
|
|
49397
|
+
backfillCloudCommentRedaction: () => backfillCloudCommentRedaction,
|
|
49206
49398
|
TODOS_APP_SLUG: () => TODOS_APP_SLUG
|
|
49207
49399
|
});
|
|
49208
49400
|
function resolveCloudDatabaseUrl(env = process.env) {
|
|
@@ -49280,6 +49472,9 @@ async function ensureCloudSchema() {
|
|
|
49280
49472
|
})();
|
|
49281
49473
|
return schemaEnsured;
|
|
49282
49474
|
}
|
|
49475
|
+
async function ensureCloudCommentCursorIndex() {
|
|
49476
|
+
await getClient().query(postgresTodosCommentCursorIndexSql());
|
|
49477
|
+
}
|
|
49283
49478
|
async function normalizeCloudPayloads() {
|
|
49284
49479
|
const client = getClient();
|
|
49285
49480
|
const res = await client.query(`UPDATE todos_sync_records
|
|
@@ -49288,6 +49483,9 @@ async function normalizeCloudPayloads() {
|
|
|
49288
49483
|
RETURNING object_id AS id`);
|
|
49289
49484
|
return res.rows.length;
|
|
49290
49485
|
}
|
|
49486
|
+
function backfillCloudCommentRedaction(options = {}) {
|
|
49487
|
+
return backfillPostgresCommentRedaction(getClient(), { ...options, service: TODOS_APP_SLUG });
|
|
49488
|
+
}
|
|
49291
49489
|
async function pingCloud() {
|
|
49292
49490
|
const client = getClient();
|
|
49293
49491
|
const res = await client.query("select 1 as ok");
|
|
@@ -49308,6 +49506,7 @@ var init_cloud = __esm(() => {
|
|
|
49308
49506
|
init_auth();
|
|
49309
49507
|
init_cloud_client();
|
|
49310
49508
|
init_postgres_adapter();
|
|
49509
|
+
init_comment_redaction_backfill();
|
|
49311
49510
|
});
|
|
49312
49511
|
|
|
49313
49512
|
// src/server/openapi.ts
|
|
@@ -49331,6 +49530,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
49331
49530
|
schemas: {
|
|
49332
49531
|
Task: taskSchema,
|
|
49333
49532
|
Project: projectSchema,
|
|
49533
|
+
TaskComment: taskCommentSchema,
|
|
49334
49534
|
CreateTaskInput: {
|
|
49335
49535
|
type: "object",
|
|
49336
49536
|
required: ["title"],
|
|
@@ -49365,6 +49565,17 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
49365
49565
|
description: { type: "string" },
|
|
49366
49566
|
task_prefix: { type: "string" }
|
|
49367
49567
|
}
|
|
49568
|
+
},
|
|
49569
|
+
CreateTaskCommentInput: {
|
|
49570
|
+
type: "object",
|
|
49571
|
+
required: ["content"],
|
|
49572
|
+
properties: {
|
|
49573
|
+
content: { type: "string", minLength: 1 },
|
|
49574
|
+
agent_id: { type: "string" },
|
|
49575
|
+
session_id: { type: "string" },
|
|
49576
|
+
type: { type: "string", enum: ["comment", "progress", "note"] },
|
|
49577
|
+
progress_pct: { type: "number" }
|
|
49578
|
+
}
|
|
49368
49579
|
}
|
|
49369
49580
|
}
|
|
49370
49581
|
},
|
|
@@ -49463,6 +49674,61 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
49463
49674
|
}
|
|
49464
49675
|
}
|
|
49465
49676
|
},
|
|
49677
|
+
"/v1/tasks/{id}/comments": {
|
|
49678
|
+
get: {
|
|
49679
|
+
operationId: "listTaskComments",
|
|
49680
|
+
summary: "List a bounded page of task comments",
|
|
49681
|
+
description: "Returns the newest page in oldest-to-newest display order. Use next_cursor to request older pages; count is the page size, not a total. Pagination-aware clients must send limit during the mixed-version rollout.",
|
|
49682
|
+
parameters: [
|
|
49683
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
49684
|
+
{ name: "limit", in: "query", required: true, schema: { type: "integer", minimum: 1, maximum: 500, default: 100 } },
|
|
49685
|
+
{ name: "cursor", in: "query", schema: { type: "string" } }
|
|
49686
|
+
],
|
|
49687
|
+
responses: {
|
|
49688
|
+
"200": {
|
|
49689
|
+
content: {
|
|
49690
|
+
"application/json": {
|
|
49691
|
+
schema: {
|
|
49692
|
+
type: "object",
|
|
49693
|
+
required: ["comments", "count", "has_more", "next_cursor"],
|
|
49694
|
+
properties: {
|
|
49695
|
+
comments: { type: "array", maxItems: 500, items: { $ref: "#/components/schemas/TaskComment" } },
|
|
49696
|
+
count: { type: "integer", minimum: 0, maximum: 500 },
|
|
49697
|
+
has_more: { type: "boolean" },
|
|
49698
|
+
next_cursor: { type: "string", nullable: true }
|
|
49699
|
+
}
|
|
49700
|
+
}
|
|
49701
|
+
}
|
|
49702
|
+
}
|
|
49703
|
+
},
|
|
49704
|
+
"426": {
|
|
49705
|
+
description: "Upgrade required: a predecessor client omitted limit and the complete legacy history exceeds 500 comments, or the configured storage adapter lacks cursor pagination support."
|
|
49706
|
+
}
|
|
49707
|
+
}
|
|
49708
|
+
},
|
|
49709
|
+
post: {
|
|
49710
|
+
operationId: "createTaskComment",
|
|
49711
|
+
summary: "Create a task comment",
|
|
49712
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
49713
|
+
requestBody: {
|
|
49714
|
+
required: true,
|
|
49715
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/CreateTaskCommentInput" } } }
|
|
49716
|
+
},
|
|
49717
|
+
responses: {
|
|
49718
|
+
"201": {
|
|
49719
|
+
content: {
|
|
49720
|
+
"application/json": {
|
|
49721
|
+
schema: {
|
|
49722
|
+
type: "object",
|
|
49723
|
+
required: ["comment"],
|
|
49724
|
+
properties: { comment: { $ref: "#/components/schemas/TaskComment" } }
|
|
49725
|
+
}
|
|
49726
|
+
}
|
|
49727
|
+
}
|
|
49728
|
+
}
|
|
49729
|
+
}
|
|
49730
|
+
}
|
|
49731
|
+
},
|
|
49466
49732
|
"/v1/tasks/{id}/start": {
|
|
49467
49733
|
post: {
|
|
49468
49734
|
operationId: "startTask",
|
|
@@ -49581,7 +49847,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
49581
49847
|
}
|
|
49582
49848
|
};
|
|
49583
49849
|
}
|
|
49584
|
-
var taskSchema, projectSchema;
|
|
49850
|
+
var taskSchema, projectSchema, taskCommentSchema;
|
|
49585
49851
|
var init_openapi = __esm(() => {
|
|
49586
49852
|
init_package_version();
|
|
49587
49853
|
taskSchema = {
|
|
@@ -49612,6 +49878,20 @@ var init_openapi = __esm(() => {
|
|
|
49612
49878
|
updated_at: { type: "string" }
|
|
49613
49879
|
}
|
|
49614
49880
|
};
|
|
49881
|
+
taskCommentSchema = {
|
|
49882
|
+
type: "object",
|
|
49883
|
+
required: ["id", "task_id", "agent_id", "session_id", "content", "type", "progress_pct", "created_at"],
|
|
49884
|
+
properties: {
|
|
49885
|
+
id: { type: "string" },
|
|
49886
|
+
task_id: { type: "string" },
|
|
49887
|
+
agent_id: { type: "string", nullable: true },
|
|
49888
|
+
session_id: { type: "string", nullable: true },
|
|
49889
|
+
content: { type: "string" },
|
|
49890
|
+
type: { type: "string", enum: ["comment", "progress", "note"] },
|
|
49891
|
+
progress_pct: { type: "number", nullable: true },
|
|
49892
|
+
created_at: { type: "string", format: "date-time" }
|
|
49893
|
+
}
|
|
49894
|
+
};
|
|
49615
49895
|
});
|
|
49616
49896
|
|
|
49617
49897
|
// src/server/v1.ts
|
|
@@ -49641,6 +49921,29 @@ function contextFromPrincipal(principal, body) {
|
|
|
49641
49921
|
const agentId = body?.agent_id || principal.agent || undefined;
|
|
49642
49922
|
return agentId ? { agentId } : {};
|
|
49643
49923
|
}
|
|
49924
|
+
function redactComment3(comment) {
|
|
49925
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
49926
|
+
}
|
|
49927
|
+
function encodeCommentCursor(comment) {
|
|
49928
|
+
return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
|
|
49929
|
+
}
|
|
49930
|
+
function decodeCommentCursor(value) {
|
|
49931
|
+
if (value.length > 1024)
|
|
49932
|
+
throw new Error("invalid comment cursor");
|
|
49933
|
+
let parsed;
|
|
49934
|
+
try {
|
|
49935
|
+
parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
49936
|
+
} catch {
|
|
49937
|
+
throw new Error("invalid comment cursor");
|
|
49938
|
+
}
|
|
49939
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
49940
|
+
throw new Error("invalid comment cursor");
|
|
49941
|
+
const cursor = parsed;
|
|
49942
|
+
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) {
|
|
49943
|
+
throw new Error("invalid comment cursor");
|
|
49944
|
+
}
|
|
49945
|
+
return { created_at: cursor["created_at"], id: cursor["id"] };
|
|
49946
|
+
}
|
|
49644
49947
|
function normalizeImportSnapshot(raw) {
|
|
49645
49948
|
const body = raw && typeof raw === "object" ? raw : {};
|
|
49646
49949
|
const arr = (v) => Array.isArray(v) ? v : [];
|
|
@@ -49661,7 +49964,7 @@ function normalizeImportSnapshot(raw) {
|
|
|
49661
49964
|
function countSnapshotRecords(s) {
|
|
49662
49965
|
return s.tasks.length + s.projects.length + (s.projectMachinePaths?.length ?? 0) + s.plans.length + s.agents.length + s.taskLists.length + s.templates.length + s.auditHistory.length + (s.tombstones?.length ?? 0);
|
|
49663
49966
|
}
|
|
49664
|
-
async function handleV1Request(req, url) {
|
|
49967
|
+
async function handleV1Request(req, url, dependencies = {}) {
|
|
49665
49968
|
const path = url.pathname;
|
|
49666
49969
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
49667
49970
|
return null;
|
|
@@ -49670,7 +49973,7 @@ async function handleV1Request(req, url) {
|
|
|
49670
49973
|
const requiredScopes = [isWrite ? "todos:write" : "todos:read"];
|
|
49671
49974
|
let verifier;
|
|
49672
49975
|
try {
|
|
49673
|
-
verifier = getCloudVerifier();
|
|
49976
|
+
verifier = (dependencies.getVerifier ?? getCloudVerifier)();
|
|
49674
49977
|
} catch (e) {
|
|
49675
49978
|
return error(503, e.message);
|
|
49676
49979
|
}
|
|
@@ -49679,8 +49982,8 @@ async function handleV1Request(req, url) {
|
|
|
49679
49982
|
return error(decision.status, decision.message, { reason: decision.reason });
|
|
49680
49983
|
}
|
|
49681
49984
|
const principal = decision.principal;
|
|
49682
|
-
await ensureCloudSchema();
|
|
49683
|
-
const store = getCloudStorageAdapter();
|
|
49985
|
+
await (dependencies.ensureSchema ?? ensureCloudSchema)();
|
|
49986
|
+
const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
|
|
49684
49987
|
const segments = path.split("/").filter(Boolean);
|
|
49685
49988
|
const resource = segments[1];
|
|
49686
49989
|
const id = segments[2];
|
|
@@ -49708,13 +50011,74 @@ async function handleV1Request(req, url) {
|
|
|
49708
50011
|
missing
|
|
49709
50012
|
});
|
|
49710
50013
|
}
|
|
50014
|
+
if (id === "upsert" && !action) {
|
|
50015
|
+
if (method !== "POST")
|
|
50016
|
+
return error(405, `method ${method} not allowed on /v1/tasks/upsert`);
|
|
50017
|
+
if (typeof store.tasks.getByFingerprint !== "function") {
|
|
50018
|
+
return error(501, "fingerprint upsert is not supported by this storage backend");
|
|
50019
|
+
}
|
|
50020
|
+
const body = await readJson(req) ?? {};
|
|
50021
|
+
const fingerprint3 = typeof body.fingerprint === "string" ? body.fingerprint.trim() : "";
|
|
50022
|
+
if (!fingerprint3)
|
|
50023
|
+
return error(400, "fingerprint is required");
|
|
50024
|
+
if (typeof body.title !== "string" || !body.title.trim())
|
|
50025
|
+
return error(400, "title is required");
|
|
50026
|
+
const existing = await store.tasks.getByFingerprint(fingerprint3);
|
|
50027
|
+
const metadata = {
|
|
50028
|
+
...existing?.metadata ?? {},
|
|
50029
|
+
...body.metadata ?? {},
|
|
50030
|
+
fingerprint: fingerprint3
|
|
50031
|
+
};
|
|
50032
|
+
const fields = { metadata };
|
|
50033
|
+
for (const key of [
|
|
50034
|
+
"title",
|
|
50035
|
+
"description",
|
|
50036
|
+
"priority",
|
|
50037
|
+
"status",
|
|
50038
|
+
"project_id",
|
|
50039
|
+
"assigned_to",
|
|
50040
|
+
"working_dir",
|
|
50041
|
+
"plan_id",
|
|
50042
|
+
"task_list_id",
|
|
50043
|
+
"tags",
|
|
50044
|
+
"due_at",
|
|
50045
|
+
"estimated_minutes",
|
|
50046
|
+
"sla_minutes",
|
|
50047
|
+
"requires_approval",
|
|
50048
|
+
"recurrence_rule",
|
|
50049
|
+
"task_type"
|
|
50050
|
+
]) {
|
|
50051
|
+
const bag = body;
|
|
50052
|
+
if (bag[key] !== undefined)
|
|
50053
|
+
fields[key] = bag[key];
|
|
50054
|
+
}
|
|
50055
|
+
if (!existing) {
|
|
50056
|
+
const task2 = await store.tasks.create({ ...fields, title: body.title }, contextFromPrincipal(principal, body));
|
|
50057
|
+
return json2({ task: task2, created: true }, 201);
|
|
50058
|
+
}
|
|
50059
|
+
try {
|
|
50060
|
+
const task2 = await store.tasks.update(existing.id, { ...fields, version: existing.version }, contextFromPrincipal(principal, body));
|
|
50061
|
+
return json2({ task: task2, created: false });
|
|
50062
|
+
} catch (e) {
|
|
50063
|
+
const msg = e.message || "";
|
|
50064
|
+
if (msg.includes("version conflict"))
|
|
50065
|
+
return error(409, msg);
|
|
50066
|
+
throw e;
|
|
50067
|
+
}
|
|
50068
|
+
}
|
|
49711
50069
|
if (!id) {
|
|
49712
50070
|
if (method === "GET") {
|
|
49713
50071
|
const filter = {
|
|
49714
|
-
...url.searchParams.get("status") ? {
|
|
49715
|
-
|
|
50072
|
+
...url.searchParams.get("status") ? {
|
|
50073
|
+
status: url.searchParams.get("status").includes(",") ? url.searchParams.get("status").split(",") : url.searchParams.get("status")
|
|
50074
|
+
} : {},
|
|
50075
|
+
...url.searchParams.get("priority") ? {
|
|
50076
|
+
priority: url.searchParams.get("priority").includes(",") ? url.searchParams.get("priority").split(",") : url.searchParams.get("priority")
|
|
50077
|
+
} : {},
|
|
49716
50078
|
...url.searchParams.get("project_id") ? { project_id: url.searchParams.get("project_id") } : {},
|
|
50079
|
+
...url.searchParams.has("parent_id") ? { parent_id: url.searchParams.get("parent_id") || null, include_subtasks: true } : {},
|
|
49717
50080
|
...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {},
|
|
50081
|
+
...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
|
|
49718
50082
|
...url.searchParams.get("assigned_to") ? { assigned_to: url.searchParams.get("assigned_to") } : {},
|
|
49719
50083
|
...url.searchParams.get("agent_id") ? { agent_id: url.searchParams.get("agent_id") } : {},
|
|
49720
50084
|
...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
|
|
@@ -49738,8 +50102,47 @@ async function handleV1Request(req, url) {
|
|
|
49738
50102
|
if (action) {
|
|
49739
50103
|
if (action === "comments") {
|
|
49740
50104
|
if (method === "GET") {
|
|
49741
|
-
|
|
49742
|
-
|
|
50105
|
+
if (!await store.tasks.get(id))
|
|
50106
|
+
return error(404, "task not found");
|
|
50107
|
+
const rawLimit = url.searchParams.get("limit");
|
|
50108
|
+
const cursor = url.searchParams.get("cursor");
|
|
50109
|
+
if (rawLimit === null && cursor === null) {
|
|
50110
|
+
const storageContext = contextFromPrincipal(principal);
|
|
50111
|
+
const legacyPage = (await (store.audit.getCommentsPage ? store.audit.getCommentsPage(id, { limit: LEGACY_COMMENT_RESPONSE_LIMIT + 1 }, storageContext) : store.audit.getComments(id, storageContext))).map(redactComment3);
|
|
50112
|
+
if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
|
|
50113
|
+
return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
|
|
50114
|
+
}
|
|
50115
|
+
return json2({
|
|
50116
|
+
comments: legacyPage,
|
|
50117
|
+
count: legacyPage.length,
|
|
50118
|
+
has_more: false,
|
|
50119
|
+
next_cursor: null
|
|
50120
|
+
});
|
|
50121
|
+
}
|
|
50122
|
+
const limit = rawLimit === null ? DEFAULT_COMMENT_PAGE_SIZE : Number(rawLimit);
|
|
50123
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_COMMENT_PAGE_SIZE) {
|
|
50124
|
+
return error(400, `limit must be an integer between 1 and ${MAX_COMMENT_PAGE_SIZE}`);
|
|
50125
|
+
}
|
|
50126
|
+
let before;
|
|
50127
|
+
if (cursor) {
|
|
50128
|
+
try {
|
|
50129
|
+
before = decodeCommentCursor(cursor);
|
|
50130
|
+
} catch {
|
|
50131
|
+
return error(400, "invalid comment cursor");
|
|
50132
|
+
}
|
|
50133
|
+
}
|
|
50134
|
+
if (!store.audit.getCommentsPage) {
|
|
50135
|
+
return error(426, "storage adapter must be upgraded to support cursor-paginated comments");
|
|
50136
|
+
}
|
|
50137
|
+
const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
|
|
50138
|
+
const hasMore = page.length > limit;
|
|
50139
|
+
const comments = hasMore ? page.slice(1) : page;
|
|
50140
|
+
return json2({
|
|
50141
|
+
comments,
|
|
50142
|
+
count: comments.length,
|
|
50143
|
+
has_more: hasMore,
|
|
50144
|
+
next_cursor: hasMore && comments[0] ? encodeCommentCursor(comments[0]) : null
|
|
50145
|
+
});
|
|
49743
50146
|
}
|
|
49744
50147
|
if (method === "POST") {
|
|
49745
50148
|
const body2 = await readJson(req) ?? {};
|
|
@@ -49757,24 +50160,45 @@ async function handleV1Request(req, url) {
|
|
|
49757
50160
|
type: body2.type,
|
|
49758
50161
|
progress_pct: body2.progress_pct
|
|
49759
50162
|
}, contextFromPrincipal(principal, body2));
|
|
49760
|
-
return json2({ comment }, 201);
|
|
50163
|
+
return json2({ comment: redactComment3(comment) }, 201);
|
|
49761
50164
|
}
|
|
49762
50165
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
|
|
49763
50166
|
}
|
|
50167
|
+
if (action === "history") {
|
|
50168
|
+
if (method !== "GET")
|
|
50169
|
+
return error(405, `method ${method} not allowed on /v1/tasks/:id/history`);
|
|
50170
|
+
if (!await store.tasks.get(id))
|
|
50171
|
+
return error(404, "task not found");
|
|
50172
|
+
const history = await store.audit.getTaskHistory(id);
|
|
50173
|
+
return json2({ history, count: history.length });
|
|
50174
|
+
}
|
|
49764
50175
|
if (action === "lock" || action === "unlock") {
|
|
49765
50176
|
if (method !== "POST")
|
|
49766
50177
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/${action}`);
|
|
49767
|
-
if (typeof store.tasks.lock !== "function" || typeof store.tasks.unlock !== "function") {
|
|
49768
|
-
return error(501, "task locking is not supported by this storage backend");
|
|
49769
|
-
}
|
|
49770
50178
|
const body2 = await readJson(req) ?? {};
|
|
49771
50179
|
if (!await store.tasks.get(id))
|
|
49772
50180
|
return error(404, "task not found");
|
|
49773
50181
|
if (action === "lock") {
|
|
49774
|
-
|
|
49775
|
-
|
|
50182
|
+
if (typeof store.tasks.lock !== "function")
|
|
50183
|
+
return error(501, "task locking is not supported by this storage backend");
|
|
50184
|
+
const agentId3 = body2.agent_id || principal.agent || "todos-serve";
|
|
50185
|
+
return json2({ result: await store.tasks.lock(id, agentId3) });
|
|
49776
50186
|
}
|
|
49777
|
-
|
|
50187
|
+
if (typeof store.tasks.unlock !== "function")
|
|
50188
|
+
return error(501, "task unlocking is not supported by this storage backend");
|
|
50189
|
+
if (body2.force === true) {
|
|
50190
|
+
if (!principal.scopes.includes("todos:*"))
|
|
50191
|
+
return error(403, "force unlock requires todos:* scope");
|
|
50192
|
+
const released2 = await store.tasks.unlock(id);
|
|
50193
|
+
return json2({ success: released2 });
|
|
50194
|
+
}
|
|
50195
|
+
if (body2.agent_id && principal.agent && body2.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
|
|
50196
|
+
return error(403, "unlock agent_id must match the authenticated agent");
|
|
50197
|
+
}
|
|
50198
|
+
const agentId2 = principal.agent || body2.agent_id;
|
|
50199
|
+
if (!agentId2)
|
|
50200
|
+
return error(403, "unlock requires an agent-bound key or force=true");
|
|
50201
|
+
const released = await store.tasks.unlock(id, agentId2);
|
|
49778
50202
|
return json2({ success: released });
|
|
49779
50203
|
}
|
|
49780
50204
|
if (action === "dependencies") {
|
|
@@ -50057,12 +50481,28 @@ async function handleV1Request(req, url) {
|
|
|
50057
50481
|
const activity = await store.audit.getRecentActivity(limit);
|
|
50058
50482
|
return json2({ activity, count: activity.length });
|
|
50059
50483
|
}
|
|
50060
|
-
if (resource === "task-lists"
|
|
50061
|
-
if (method
|
|
50062
|
-
|
|
50063
|
-
|
|
50064
|
-
|
|
50065
|
-
|
|
50484
|
+
if (resource === "task-lists") {
|
|
50485
|
+
if (!id && method === "GET") {
|
|
50486
|
+
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
50487
|
+
const taskLists = await store.taskLists.list(projectId);
|
|
50488
|
+
return json2({ task_lists: taskLists, count: taskLists.length });
|
|
50489
|
+
}
|
|
50490
|
+
if (!id && method === "POST") {
|
|
50491
|
+
const body = await readJson(req);
|
|
50492
|
+
if (!body || typeof body.name !== "string" || !body.name.trim())
|
|
50493
|
+
return error(400, "name is required");
|
|
50494
|
+
const taskList = await store.taskLists.create(body, contextFromPrincipal(principal));
|
|
50495
|
+
return json2({ task_list: taskList }, 201);
|
|
50496
|
+
}
|
|
50497
|
+
if (id && method === "GET") {
|
|
50498
|
+
const taskList = await store.taskLists.get(id);
|
|
50499
|
+
return taskList ? json2({ task_list: taskList }) : error(404, "task list not found");
|
|
50500
|
+
}
|
|
50501
|
+
if (id && method === "DELETE") {
|
|
50502
|
+
const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
|
|
50503
|
+
return deleted ? json2({ deleted: true, id }) : error(404, "task list not found");
|
|
50504
|
+
}
|
|
50505
|
+
return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
|
|
50066
50506
|
}
|
|
50067
50507
|
if (resource === "dependencies" && !id) {
|
|
50068
50508
|
if (method !== "GET")
|
|
@@ -50070,8 +50510,8 @@ async function handleV1Request(req, url) {
|
|
|
50070
50510
|
if (typeof store.dependencies?.listAll !== "function") {
|
|
50071
50511
|
return error(501, "dependency edge listing is not supported by this storage backend");
|
|
50072
50512
|
}
|
|
50073
|
-
const
|
|
50074
|
-
return json2({ dependencies, count:
|
|
50513
|
+
const dependencies2 = await store.dependencies.listAll();
|
|
50514
|
+
return json2({ dependencies: dependencies2, count: dependencies2.length });
|
|
50075
50515
|
}
|
|
50076
50516
|
if (resource === "commits" && id) {
|
|
50077
50517
|
if (method !== "GET")
|
|
@@ -50128,12 +50568,16 @@ async function handleV1Request(req, url) {
|
|
|
50128
50568
|
}
|
|
50129
50569
|
return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
50130
50570
|
} catch (e) {
|
|
50571
|
+
if (e instanceof LockError)
|
|
50572
|
+
return error(409, e.message, { code: LockError.code });
|
|
50131
50573
|
return error(500, e.message || "internal error");
|
|
50132
50574
|
}
|
|
50133
50575
|
}
|
|
50134
|
-
var JSON_HEADERS;
|
|
50576
|
+
var JSON_HEADERS, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500;
|
|
50135
50577
|
var init_v1 = __esm(() => {
|
|
50578
|
+
init_types();
|
|
50136
50579
|
init_cloud();
|
|
50580
|
+
init_redaction();
|
|
50137
50581
|
JSON_HEADERS = { "Content-Type": "application/json" };
|
|
50138
50582
|
});
|
|
50139
50583
|
|