@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/storage.js
CHANGED
|
@@ -42,20 +42,16 @@ var __require = import.meta.require;
|
|
|
42
42
|
// src/storage/config.ts
|
|
43
43
|
var exports_config = {};
|
|
44
44
|
__export(exports_config, {
|
|
45
|
-
parseStorageMode: () => parseStorageMode,
|
|
46
45
|
parseStorageBackend: () => parseStorageBackend,
|
|
47
46
|
loadTodosStorageConfig: () => loadTodosStorageConfig,
|
|
48
47
|
loadStorageConfig: () => loadStorageConfig,
|
|
49
48
|
isTodosShadowEnabled: () => isTodosShadowEnabled,
|
|
50
|
-
isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
|
|
51
49
|
isTodosPostgresBackend: () => isTodosPostgresBackend,
|
|
52
50
|
getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
|
|
53
|
-
getTodosStorageMode: () => getTodosStorageMode,
|
|
54
51
|
getTodosStorageEnvName: () => getTodosStorageEnvName,
|
|
55
52
|
getTodosStorageDatabaseUrl: () => getTodosStorageDatabaseUrl,
|
|
56
53
|
getTodosStorageDatabaseEnv: () => getTodosStorageDatabaseEnv,
|
|
57
54
|
getTodosStorageBackend: () => getTodosStorageBackend,
|
|
58
|
-
getStorageMode: () => getStorageMode,
|
|
59
55
|
getStorageDatabaseUrl: () => getStorageDatabaseUrl,
|
|
60
56
|
getStorageDatabaseEnv: () => getStorageDatabaseEnv,
|
|
61
57
|
getCanonicalTodosRdsConfig: () => getCanonicalTodosRdsConfig,
|
|
@@ -65,7 +61,6 @@ __export(exports_config, {
|
|
|
65
61
|
TODOS_STORAGE_FALLBACK_ENV: () => TODOS_STORAGE_FALLBACK_ENV,
|
|
66
62
|
TODOS_STORAGE_ENV: () => TODOS_STORAGE_ENV,
|
|
67
63
|
STORAGE_TABLES: () => STORAGE_TABLES,
|
|
68
|
-
REMOVED_STORAGE_MODE_ENV_KEYS: () => REMOVED_STORAGE_MODE_ENV_KEYS,
|
|
69
64
|
CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV: () => CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV,
|
|
70
65
|
CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
|
|
71
66
|
CANONICAL_TODOS_RDS_CLUSTER_ENV: () => CANONICAL_TODOS_RDS_CLUSTER_ENV
|
|
@@ -120,9 +115,6 @@ function loadStorageConfig(env = process.env) {
|
|
|
120
115
|
function isTodosPostgresBackend(config) {
|
|
121
116
|
return config.mode === "postgres";
|
|
122
117
|
}
|
|
123
|
-
function isTodosRemoteStorageEnabled(config) {
|
|
124
|
-
return isTodosPostgresBackend(config);
|
|
125
|
-
}
|
|
126
118
|
function assertTodosRemoteStorageConfig(config) {
|
|
127
119
|
if (!isTodosPostgresBackend(config))
|
|
128
120
|
return;
|
|
@@ -138,32 +130,15 @@ function parseStorageBackend(value) {
|
|
|
138
130
|
return "sqlite";
|
|
139
131
|
if (normalized === "postgres" || normalized === "postgresql")
|
|
140
132
|
return "postgres";
|
|
141
|
-
if (["local", "remote", "cloud", "hybrid", "self_hosted"].includes(normalized)) {
|
|
142
|
-
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.`);
|
|
143
|
-
}
|
|
144
133
|
throw new Error(`Storage backend must be sqlite or postgres`);
|
|
145
134
|
}
|
|
146
|
-
function parseStorageMode(value) {
|
|
147
|
-
return parseStorageBackend(value);
|
|
148
|
-
}
|
|
149
135
|
function getTodosStorageBackend(env = process.env) {
|
|
150
|
-
for (const key of REMOVED_STORAGE_MODE_ENV_KEYS) {
|
|
151
|
-
if (Object.hasOwn(env, key) && env[key] !== undefined) {
|
|
152
|
-
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.`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
136
|
if (!getTodosStorageDatabaseUrl(env))
|
|
156
137
|
return "sqlite";
|
|
157
138
|
if (isTodosShadowEnabled(env))
|
|
158
139
|
return "sqlite";
|
|
159
140
|
return "postgres";
|
|
160
141
|
}
|
|
161
|
-
function getTodosStorageMode(env = process.env) {
|
|
162
|
-
return getTodosStorageBackend(env);
|
|
163
|
-
}
|
|
164
|
-
function getStorageMode(env = process.env) {
|
|
165
|
-
return getTodosStorageBackend(env);
|
|
166
|
-
}
|
|
167
142
|
function isTodosShadowEnabled(env = process.env) {
|
|
168
143
|
return parseBoolean(readStorageEnv(env, "shadow").value, false);
|
|
169
144
|
}
|
|
@@ -230,7 +205,7 @@ function parsePositiveInteger(value, fallback) {
|
|
|
230
205
|
}
|
|
231
206
|
return parsed;
|
|
232
207
|
}
|
|
233
|
-
var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV,
|
|
208
|
+
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";
|
|
234
209
|
var init_config = __esm(() => {
|
|
235
210
|
TODOS_STORAGE_TABLES = [
|
|
236
211
|
"todos_sync_records",
|
|
@@ -269,12 +244,6 @@ var init_config = __esm(() => {
|
|
|
269
244
|
syncBatchSize: "TODOS_SYNC_BATCH_SIZE",
|
|
270
245
|
syncDryRun: "TODOS_SYNC_DRY_RUN"
|
|
271
246
|
};
|
|
272
|
-
REMOVED_STORAGE_MODE_ENV_KEYS = [
|
|
273
|
-
"HASNA_TODOS_STORAGE_MODE",
|
|
274
|
-
"HASNA_TODOS_MODE",
|
|
275
|
-
"TODOS_STORAGE_MODE",
|
|
276
|
-
"TODOS_MODE"
|
|
277
|
-
];
|
|
278
247
|
});
|
|
279
248
|
|
|
280
249
|
// src/types/index.ts
|
|
@@ -3127,6 +3096,20 @@ function ensureSchema(db) {
|
|
|
3127
3096
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
|
|
3128
3097
|
ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
|
|
3129
3098
|
ensureColumn("task_comments", "progress_pct", "INTEGER");
|
|
3099
|
+
ensureTable("plan_comments", `
|
|
3100
|
+
CREATE TABLE plan_comments (
|
|
3101
|
+
id TEXT PRIMARY KEY,
|
|
3102
|
+
plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
3103
|
+
agent_id TEXT,
|
|
3104
|
+
session_id TEXT,
|
|
3105
|
+
content TEXT NOT NULL,
|
|
3106
|
+
type TEXT DEFAULT 'comment' CHECK(type IN ('comment', 'progress', 'note')),
|
|
3107
|
+
progress_pct INTEGER CHECK(progress_pct IS NULL OR (progress_pct >= 0 AND progress_pct <= 100)),
|
|
3108
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3109
|
+
machine_id TEXT,
|
|
3110
|
+
synced_at TEXT
|
|
3111
|
+
)`);
|
|
3112
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plan_comments_plan ON plan_comments(plan_id)");
|
|
3130
3113
|
ensureColumn("projects", "machine_id", "TEXT");
|
|
3131
3114
|
ensureColumn("projects", "synced_at", "TEXT");
|
|
3132
3115
|
ensureColumn("tasks", "machine_id", "TEXT");
|
|
@@ -4108,6 +4091,7 @@ var init_machines = __esm(() => {
|
|
|
4108
4091
|
"task_lists",
|
|
4109
4092
|
"plans",
|
|
4110
4093
|
"task_comments",
|
|
4094
|
+
"plan_comments",
|
|
4111
4095
|
"sessions",
|
|
4112
4096
|
"task_history",
|
|
4113
4097
|
"webhooks",
|
|
@@ -6857,22 +6841,31 @@ function updateTaskList(id, input, db) {
|
|
|
6857
6841
|
throw new TaskListNotFoundError(id);
|
|
6858
6842
|
const sets = ["updated_at = ?"];
|
|
6859
6843
|
const params = [now()];
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6844
|
+
const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? existing.project_id : input.project_id;
|
|
6845
|
+
const scopeChanged = projectId !== existing.project_id;
|
|
6846
|
+
const effectiveSlug = input.slug !== undefined ? slugify(input.slug) : existing.slug;
|
|
6847
|
+
if (input.slug !== undefined && !effectiveSlug) {
|
|
6848
|
+
throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
|
|
6849
|
+
}
|
|
6850
|
+
if (scopeChanged || input.slug !== undefined && effectiveSlug !== existing.slug) {
|
|
6851
|
+
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);
|
|
6865
6852
|
if (duplicate) {
|
|
6866
|
-
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${
|
|
6853
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
|
|
6867
6854
|
}
|
|
6868
|
-
if (
|
|
6855
|
+
if (scopeChanged || effectiveSlug !== existing.slug) {
|
|
6869
6856
|
releaseCanonicalSlugClaims("task_list", id, d);
|
|
6870
|
-
if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(
|
|
6871
|
-
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${
|
|
6857
|
+
if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(projectId), effectiveSlug, id, d)) {
|
|
6858
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
|
|
6872
6859
|
}
|
|
6873
6860
|
}
|
|
6861
|
+
}
|
|
6862
|
+
if (input.slug !== undefined) {
|
|
6874
6863
|
sets.push("slug = ?");
|
|
6875
|
-
params.push(
|
|
6864
|
+
params.push(effectiveSlug);
|
|
6865
|
+
}
|
|
6866
|
+
if (projectId !== existing.project_id) {
|
|
6867
|
+
sets.push("project_id = ?");
|
|
6868
|
+
params.push(projectId);
|
|
6876
6869
|
}
|
|
6877
6870
|
if (input.name !== undefined) {
|
|
6878
6871
|
sets.push("name = ?");
|
|
@@ -8658,7 +8651,7 @@ function getTaskGraph(taskId, direction = "both", db) {
|
|
|
8658
8651
|
const deps = getTaskDependencies(t.id, d);
|
|
8659
8652
|
const hasUnfinishedDeps = deps.some((dep) => {
|
|
8660
8653
|
const depTask = getTask(dep.depends_on, d);
|
|
8661
|
-
return depTask && depTask.status
|
|
8654
|
+
return depTask && isBlockingDependencyStatus(depTask.status);
|
|
8662
8655
|
});
|
|
8663
8656
|
return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
|
|
8664
8657
|
}
|
|
@@ -8918,7 +8911,7 @@ function getBlockingDeps(id, db) {
|
|
|
8918
8911
|
const blocking = [];
|
|
8919
8912
|
for (const dep of deps) {
|
|
8920
8913
|
const task = getTask(dep.depends_on, d);
|
|
8921
|
-
if (task && task.status
|
|
8914
|
+
if (task && isBlockingDependencyStatus(task.status))
|
|
8922
8915
|
blocking.push(task);
|
|
8923
8916
|
}
|
|
8924
8917
|
return blocking;
|
|
@@ -9242,7 +9235,7 @@ function getNextTask(agentId, filters, db) {
|
|
|
9242
9235
|
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
9243
9236
|
params.push(...filters.tags);
|
|
9244
9237
|
}
|
|
9245
|
-
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
|
|
9238
|
+
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'))");
|
|
9246
9239
|
const where = conditions.join(" AND ");
|
|
9247
9240
|
let recentProjectIds = [];
|
|
9248
9241
|
const assignedAliasParams = [];
|
|
@@ -9286,7 +9279,7 @@ function getActiveWork(filters, db) {
|
|
|
9286
9279
|
}
|
|
9287
9280
|
function getTasksChangedSince(since, filters, db) {
|
|
9288
9281
|
const d = db || getDatabase();
|
|
9289
|
-
const conditions = ["updated_at > ?"];
|
|
9282
|
+
const conditions = ["(julianday(updated_at) IS NULL OR julianday(updated_at) > julianday(?))"];
|
|
9290
9283
|
const params = [since];
|
|
9291
9284
|
if (filters?.project_id) {
|
|
9292
9285
|
conditions.push("project_id = ?");
|
|
@@ -9321,8 +9314,8 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
9321
9314
|
const safeMeta = sanitizePreWriteValue(meta, "task.failure.metadata");
|
|
9322
9315
|
const timestamp2 = now();
|
|
9323
9316
|
const failTx = d.transaction(() => {
|
|
9324
|
-
const res = d.run(`UPDATE tasks SET status = 'failed', reason = ?, locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
9325
|
-
WHERE id = ? AND version = ?`, [safeReason, JSON.stringify(safeMeta), timestamp2, id, task.version]);
|
|
9317
|
+
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 = ?
|
|
9318
|
+
WHERE id = ? AND version = ?`, [safeReason, timestamp2, JSON.stringify(safeMeta), timestamp2, id, task.version]);
|
|
9326
9319
|
if (res.changes === 0) {
|
|
9327
9320
|
const current = getTask(id, d);
|
|
9328
9321
|
throw new VersionConflictError(id, task.version, current?.version ?? -1);
|
|
@@ -9334,6 +9327,7 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
9334
9327
|
status: "failed",
|
|
9335
9328
|
locked_by: null,
|
|
9336
9329
|
locked_at: null,
|
|
9330
|
+
completed_at: task.completed_at ?? timestamp2,
|
|
9337
9331
|
reason: safeReason,
|
|
9338
9332
|
metadata: safeMeta,
|
|
9339
9333
|
version: task.version + 1,
|
|
@@ -9982,13 +9976,20 @@ function updateTaskStored(id, input, db) {
|
|
|
9982
9976
|
if (isTerminalStatus(input.status)) {
|
|
9983
9977
|
sets.push("locked_by = NULL");
|
|
9984
9978
|
sets.push("locked_at = NULL");
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9979
|
+
if (input.status === "completed") {
|
|
9980
|
+
sets.push("completed_at = ?");
|
|
9981
|
+
params.push(completionTimestamp);
|
|
9982
|
+
} else {
|
|
9983
|
+
sets.push("completed_at = COALESCE(completed_at, ?)");
|
|
9984
|
+
params.push(completionTimestamp);
|
|
9985
|
+
}
|
|
9989
9986
|
} else if (task.status === "completed" && input.completed_at === undefined) {
|
|
9990
9987
|
sets.push("completed_at = NULL");
|
|
9991
9988
|
}
|
|
9989
|
+
if (input.status === "in_progress") {
|
|
9990
|
+
sets.push("started_at = COALESCE(started_at, ?)");
|
|
9991
|
+
params.push(timestamp2);
|
|
9992
|
+
}
|
|
9992
9993
|
}
|
|
9993
9994
|
if (input.priority !== undefined) {
|
|
9994
9995
|
sets.push("priority = ?");
|
|
@@ -10126,7 +10127,6 @@ function updateTaskStored(id, input, db) {
|
|
|
10126
10127
|
if (input.approved_by !== undefined)
|
|
10127
10128
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
10128
10129
|
const reopened = input.status !== undefined && input.status !== "completed" && task.status === "completed" && input.completed_at === undefined;
|
|
10129
|
-
const completedNow = input.status === "completed";
|
|
10130
10130
|
const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
|
|
10131
10131
|
const updatedTask = {
|
|
10132
10132
|
...task,
|
|
@@ -10137,7 +10137,8 @@ function updateTaskStored(id, input, db) {
|
|
|
10137
10137
|
updated_at: timestamp2,
|
|
10138
10138
|
locked_by: terminalNow ? null : task.locked_by,
|
|
10139
10139
|
locked_at: terminalNow ? null : task.locked_at,
|
|
10140
|
-
completed_at:
|
|
10140
|
+
completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : terminalNow ? task.completed_at ?? completionTimestamp : reopened ? null : task.completed_at,
|
|
10141
|
+
started_at: input.status === "in_progress" ? task.started_at ?? timestamp2 : task.started_at,
|
|
10141
10142
|
sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
|
|
10142
10143
|
actual_minutes: input.actual_minutes ?? task.actual_minutes,
|
|
10143
10144
|
confidence: input.confidence !== undefined ? input.confidence : task.confidence,
|
|
@@ -10198,6 +10199,9 @@ function deleteTask(id, db) {
|
|
|
10198
10199
|
version: row.version
|
|
10199
10200
|
}, d);
|
|
10200
10201
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
10202
|
+
if (result.changes > 0) {
|
|
10203
|
+
d.run("DELETE FROM task_dependencies WHERE task_id = ? OR depends_on = ?", [id, id]);
|
|
10204
|
+
}
|
|
10201
10205
|
return result.changes > 0;
|
|
10202
10206
|
}
|
|
10203
10207
|
var init_task_crud = __esm(() => {
|
|
@@ -12967,6 +12971,40 @@ init_tasks();
|
|
|
12967
12971
|
init_projects();
|
|
12968
12972
|
init_plans();
|
|
12969
12973
|
|
|
12974
|
+
// src/db/plan-comments.ts
|
|
12975
|
+
init_types();
|
|
12976
|
+
init_database();
|
|
12977
|
+
init_plans();
|
|
12978
|
+
init_prewrite_secrets();
|
|
12979
|
+
function addPlanComment(input, db) {
|
|
12980
|
+
const d = db || getDatabase();
|
|
12981
|
+
if (!getPlan(input.plan_id, d)) {
|
|
12982
|
+
throw new PlanNotFoundError(input.plan_id);
|
|
12983
|
+
}
|
|
12984
|
+
const id = uuid();
|
|
12985
|
+
const timestamp2 = now();
|
|
12986
|
+
d.run(`INSERT INTO plan_comments (id, plan_id, agent_id, session_id, content, type, progress_pct, created_at)
|
|
12987
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
12988
|
+
id,
|
|
12989
|
+
input.plan_id,
|
|
12990
|
+
input.agent_id || null,
|
|
12991
|
+
input.session_id || null,
|
|
12992
|
+
sanitizePreWriteText(input.content, "comment.content"),
|
|
12993
|
+
input.type || "comment",
|
|
12994
|
+
input.progress_pct ?? null,
|
|
12995
|
+
timestamp2
|
|
12996
|
+
]);
|
|
12997
|
+
return getPlanComment(id, d);
|
|
12998
|
+
}
|
|
12999
|
+
function getPlanComment(id, db) {
|
|
13000
|
+
const d = db || getDatabase();
|
|
13001
|
+
return d.query("SELECT * FROM plan_comments WHERE id = ?").get(id);
|
|
13002
|
+
}
|
|
13003
|
+
function listPlanComments(planId, db) {
|
|
13004
|
+
const d = db || getDatabase();
|
|
13005
|
+
return d.query("SELECT * FROM plan_comments WHERE plan_id = ? ORDER BY created_at, rowid").all(planId);
|
|
13006
|
+
}
|
|
13007
|
+
|
|
12970
13008
|
// src/db/agents.ts
|
|
12971
13009
|
init_types();
|
|
12972
13010
|
init_database();
|
|
@@ -13624,6 +13662,20 @@ init_task_lists();
|
|
|
13624
13662
|
init_templates();
|
|
13625
13663
|
init_audit();
|
|
13626
13664
|
init_comments();
|
|
13665
|
+
|
|
13666
|
+
// src/lib/comment-cursor.ts
|
|
13667
|
+
function isStrictlyOlder(comment, before) {
|
|
13668
|
+
return comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id;
|
|
13669
|
+
}
|
|
13670
|
+
function compareCommentKeyset(left, right) {
|
|
13671
|
+
if (isStrictlyOlder(left, right))
|
|
13672
|
+
return -1;
|
|
13673
|
+
if (isStrictlyOlder(right, left))
|
|
13674
|
+
return 1;
|
|
13675
|
+
return 0;
|
|
13676
|
+
}
|
|
13677
|
+
|
|
13678
|
+
// src/storage/local-sqlite.ts
|
|
13627
13679
|
init_database();
|
|
13628
13680
|
|
|
13629
13681
|
// src/db/integrity.ts
|
|
@@ -14644,7 +14696,7 @@ function matchesExtraFilters(task, filter) {
|
|
|
14644
14696
|
}
|
|
14645
14697
|
if (filter.tags?.length) {
|
|
14646
14698
|
const taskTags = new Set(task.tags ?? []);
|
|
14647
|
-
if (!filter.tags.
|
|
14699
|
+
if (!filter.tags.some((tag) => taskTags.has(tag)))
|
|
14648
14700
|
return false;
|
|
14649
14701
|
}
|
|
14650
14702
|
if (filter.include_subtasks !== true && filter.parent_id === undefined && task.parent_id)
|
|
@@ -14720,7 +14772,22 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
14720
14772
|
list: (projectId) => listPlans(projectId, database()),
|
|
14721
14773
|
update: (id, input) => updatePlan(id, input, database()),
|
|
14722
14774
|
completeAtRevision: (id, expectedUpdatedAt) => completePlanAtRevision(id, expectedUpdatedAt, database()),
|
|
14723
|
-
delete: (id) => deletePlan(id, database())
|
|
14775
|
+
delete: (id) => deletePlan(id, database()),
|
|
14776
|
+
addComment: (input) => addPlanComment(input, database()),
|
|
14777
|
+
getComments: (planId) => listPlanComments(planId, database()),
|
|
14778
|
+
getCommentsPage: (planId, options2) => {
|
|
14779
|
+
if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
|
|
14780
|
+
throw new Error("Plan comment limit must be an integer between 1 and 1001");
|
|
14781
|
+
}
|
|
14782
|
+
let comments = listPlanComments(planId, database());
|
|
14783
|
+
comments = comments.sort(compareCommentKeyset);
|
|
14784
|
+
if (options2?.before) {
|
|
14785
|
+
const before = options2.before;
|
|
14786
|
+
comments = comments.filter((comment) => isStrictlyOlder(comment, before));
|
|
14787
|
+
}
|
|
14788
|
+
const limit = options2?.limit ?? 100;
|
|
14789
|
+
return comments.slice(-limit);
|
|
14790
|
+
}
|
|
14724
14791
|
},
|
|
14725
14792
|
planProjectLinks: {
|
|
14726
14793
|
apply: (input) => applyPlanProjectLinkSqlite(input, database()),
|
|
@@ -14761,10 +14828,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
14761
14828
|
throw new Error("Comment limit must be an integer between 1 and 1001");
|
|
14762
14829
|
}
|
|
14763
14830
|
let comments = listComments(taskId, database());
|
|
14764
|
-
comments = comments.sort(
|
|
14831
|
+
comments = comments.sort(compareCommentKeyset);
|
|
14765
14832
|
if (options2?.before) {
|
|
14766
14833
|
const before = options2.before;
|
|
14767
|
-
comments = comments.filter((comment) => comment
|
|
14834
|
+
comments = comments.filter((comment) => isStrictlyOlder(comment, before));
|
|
14768
14835
|
}
|
|
14769
14836
|
if (options2?.limit !== undefined)
|
|
14770
14837
|
comments = comments.slice(-options2.limit);
|
|
@@ -14793,6 +14860,59 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
14793
14860
|
init_types();
|
|
14794
14861
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
14795
14862
|
init_creator_identity();
|
|
14863
|
+
|
|
14864
|
+
// src/lib/instant-compare.ts
|
|
14865
|
+
var SQLITE_STAMP = /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(?:Z|([+-])(\d{2}):(\d{2}))?)?$/;
|
|
14866
|
+
var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
14867
|
+
function isLeapYear(year) {
|
|
14868
|
+
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
14869
|
+
}
|
|
14870
|
+
function sqliteJulianDay(value) {
|
|
14871
|
+
const m = SQLITE_STAMP.exec(value);
|
|
14872
|
+
if (!m)
|
|
14873
|
+
return null;
|
|
14874
|
+
const year = Number(m[1]);
|
|
14875
|
+
const month = Number(m[2]);
|
|
14876
|
+
const day = Number(m[3]);
|
|
14877
|
+
const hour = m[4] === undefined ? 0 : Number(m[4]);
|
|
14878
|
+
const minute = m[5] === undefined ? 0 : Number(m[5]);
|
|
14879
|
+
const second = m[6] === undefined ? 0 : Number(m[6]);
|
|
14880
|
+
const frac = m[7];
|
|
14881
|
+
const sign = m[8];
|
|
14882
|
+
const offsetHour = m[9];
|
|
14883
|
+
const offsetMinute = m[10];
|
|
14884
|
+
if (month < 1 || month > 12)
|
|
14885
|
+
return null;
|
|
14886
|
+
if (hour > 23 || minute > 59 || second > 59)
|
|
14887
|
+
return null;
|
|
14888
|
+
const maxDay = (DAYS_IN_MONTH[month - 1] ?? 0) + (month === 2 && isLeapYear(year) ? 1 : 0);
|
|
14889
|
+
if (day < 1 || day > maxDay)
|
|
14890
|
+
return null;
|
|
14891
|
+
let offsetMinutes = 0;
|
|
14892
|
+
if (sign !== undefined) {
|
|
14893
|
+
const oh = Number(offsetHour ?? "0");
|
|
14894
|
+
const om = Number(offsetMinute ?? "0");
|
|
14895
|
+
if (oh > 23 || om > 59)
|
|
14896
|
+
return null;
|
|
14897
|
+
offsetMinutes = oh * 60 + om;
|
|
14898
|
+
if (sign === "-")
|
|
14899
|
+
offsetMinutes = -offsetMinutes;
|
|
14900
|
+
}
|
|
14901
|
+
const micros = frac === undefined ? 0 : Number((frac + "000000").slice(0, 6));
|
|
14902
|
+
const ms = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
14903
|
+
return (ms + micros / 1000) / 86400000 + 2440587.5 - offsetMinutes / 1440;
|
|
14904
|
+
}
|
|
14905
|
+
function changedSinceStampNewer(stamp, since) {
|
|
14906
|
+
const stampJd = sqliteJulianDay(stamp);
|
|
14907
|
+
if (stampJd === null)
|
|
14908
|
+
return true;
|
|
14909
|
+
const sinceJd = sqliteJulianDay(since);
|
|
14910
|
+
if (sinceJd === null)
|
|
14911
|
+
return false;
|
|
14912
|
+
return stampJd > sinceJd;
|
|
14913
|
+
}
|
|
14914
|
+
|
|
14915
|
+
// src/storage/postgres-adapter.ts
|
|
14796
14916
|
init_stale_lock_handoff();
|
|
14797
14917
|
|
|
14798
14918
|
// src/storage/postgres-sync.ts
|
|
@@ -15294,7 +15414,26 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
15294
15414
|
list: async (projectId) => (await store.list("plans")).filter((plan) => projectId === undefined || plan.project_id === projectId).sort((a, b) => a.name.localeCompare(b.name)),
|
|
15295
15415
|
update: (id, input) => updatePlan2(id, input, store),
|
|
15296
15416
|
completeAtRevision: (id, expectedUpdatedAt, context) => store.completePlanAtRevision(id, expectedUpdatedAt, context),
|
|
15297
|
-
delete: (id, context) => store.deletePlan(id, context)
|
|
15417
|
+
delete: (id, context) => store.deletePlan(id, context),
|
|
15418
|
+
addComment: (input, context) => addPlanComment2(input, store, context),
|
|
15419
|
+
getComments: async (planId) => {
|
|
15420
|
+
const pages = [];
|
|
15421
|
+
let before;
|
|
15422
|
+
while (true) {
|
|
15423
|
+
const page = await store.listPlanComments(planId, { limit: 1000, ...before ? { before } : {} });
|
|
15424
|
+
if (page.length === 0)
|
|
15425
|
+
break;
|
|
15426
|
+
pages.unshift(page);
|
|
15427
|
+
if (page.length < 1000)
|
|
15428
|
+
break;
|
|
15429
|
+
const oldest = page[0];
|
|
15430
|
+
before = { created_at: oldest.created_at, id: oldest.id };
|
|
15431
|
+
}
|
|
15432
|
+
return pages.flat().map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
15433
|
+
},
|
|
15434
|
+
getCommentsPage: async (planId, options2) => {
|
|
15435
|
+
return (await store.listPlanComments(planId, options2)).map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
15436
|
+
}
|
|
15298
15437
|
},
|
|
15299
15438
|
planProjectLinks: {
|
|
15300
15439
|
apply: (input, context) => store.applyPlanProjectLink(input, context),
|
|
@@ -15388,11 +15527,16 @@ class PostgresJsonRecordStore {
|
|
|
15388
15527
|
return context?.requestId ?? this.sourceMachineId ?? null;
|
|
15389
15528
|
}
|
|
15390
15529
|
async ensureSchema() {
|
|
15391
|
-
this.schemaReady
|
|
15392
|
-
|
|
15393
|
-
|
|
15394
|
-
|
|
15395
|
-
|
|
15530
|
+
if (!this.schemaReady) {
|
|
15531
|
+
this.schemaReady = (async () => {
|
|
15532
|
+
for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
|
|
15533
|
+
await this.options.client.query(sql);
|
|
15534
|
+
}
|
|
15535
|
+
})().catch((error) => {
|
|
15536
|
+
this.schemaReady = null;
|
|
15537
|
+
throw error;
|
|
15538
|
+
});
|
|
15539
|
+
}
|
|
15396
15540
|
await this.schemaReady;
|
|
15397
15541
|
}
|
|
15398
15542
|
async get(type, id) {
|
|
@@ -15512,6 +15656,27 @@ class PostgresJsonRecordStore {
|
|
|
15512
15656
|
LIMIT $${params.length}`, params);
|
|
15513
15657
|
return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
|
|
15514
15658
|
}
|
|
15659
|
+
async listPlanComments(planId, options = {}) {
|
|
15660
|
+
await this.ensureSchema();
|
|
15661
|
+
const limit = options.limit ?? 100;
|
|
15662
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
|
|
15663
|
+
throw new Error("Postgres plan comment limit must be an integer between 1 and 1001");
|
|
15664
|
+
}
|
|
15665
|
+
const params = [this.service, planId];
|
|
15666
|
+
let cursorPredicate = "";
|
|
15667
|
+
if (options.before) {
|
|
15668
|
+
params.push(options.before.created_at, options.before.id);
|
|
15669
|
+
cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
|
|
15670
|
+
}
|
|
15671
|
+
params.push(limit);
|
|
15672
|
+
const result = await this.options.client.query(`/* todos:list-plan-comments */ SELECT payload FROM ${this.tableName}
|
|
15673
|
+
WHERE service = $1 AND object_type = 'plan_comments' AND deleted_at IS NULL
|
|
15674
|
+
AND payload->>'plan_id' = $2
|
|
15675
|
+
${cursorPredicate}
|
|
15676
|
+
ORDER BY payload->>'created_at' DESC, object_id DESC
|
|
15677
|
+
LIMIT $${params.length}`, params);
|
|
15678
|
+
return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
|
|
15679
|
+
}
|
|
15515
15680
|
async listRecords(type) {
|
|
15516
15681
|
await this.ensureSchema();
|
|
15517
15682
|
const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
|
|
@@ -16673,6 +16838,15 @@ class PostgresJsonRecordStore {
|
|
|
16673
16838
|
WHERE r.service = $1 AND r.object_type = 'tasks' AND r.deleted_at IS NULL
|
|
16674
16839
|
AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
|
|
16675
16840
|
RETURNING 1
|
|
16841
|
+
), deleted_plan_comments AS (
|
|
16842
|
+
UPDATE ${this.tableName} r SET
|
|
16843
|
+
deleted_at = $3::timestamptz,
|
|
16844
|
+
updated_at = $3::timestamptz,
|
|
16845
|
+
source_machine_id = COALESCE($4, r.source_machine_id),
|
|
16846
|
+
version = COALESCE(r.version, 0) + 1
|
|
16847
|
+
WHERE r.service = $1 AND r.object_type = 'plan_comments' AND r.deleted_at IS NULL
|
|
16848
|
+
AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
|
|
16849
|
+
RETURNING 1
|
|
16676
16850
|
), deleted_plan AS (
|
|
16677
16851
|
UPDATE ${this.tableName} r SET
|
|
16678
16852
|
deleted_at = $3::timestamptz,
|
|
@@ -16848,7 +17022,8 @@ async function updateTask2(id, input, store, context) {
|
|
|
16848
17022
|
task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
|
|
16849
17023
|
parent_id: input.parent_id !== undefined ? input.parent_id : existing.parent_id,
|
|
16850
17024
|
created_by: existing.created_by,
|
|
16851
|
-
completed_at:
|
|
17025
|
+
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,
|
|
17026
|
+
started_at: input.status === "in_progress" ? existing.started_at ?? new Date().toISOString() : existing.started_at
|
|
16852
17027
|
};
|
|
16853
17028
|
const storedTask = await store.upsertTaskWithPlanMembershipGuard(task, [existing.plan_id, effectivePlanId].filter((planId) => Boolean(planId)), input.project_id !== undefined, context, {
|
|
16854
17029
|
operation: "update",
|
|
@@ -16893,7 +17068,8 @@ async function failTask2(id, agentId, reason, options, store) {
|
|
|
16893
17068
|
status: "failed",
|
|
16894
17069
|
assigned_to: task.assigned_to ?? agentId ?? null,
|
|
16895
17070
|
reason: reason ?? task.reason,
|
|
16896
|
-
retry_after: options?.retry_after ?? task.retry_after
|
|
17071
|
+
retry_after: options?.retry_after ?? task.retry_after,
|
|
17072
|
+
completed_at: task.completed_at ?? new Date().toISOString()
|
|
16897
17073
|
}, store);
|
|
16898
17074
|
if (!options?.retry)
|
|
16899
17075
|
return { task: failed };
|
|
@@ -17125,7 +17301,7 @@ async function getActiveWork2(filters, store) {
|
|
|
17125
17301
|
}));
|
|
17126
17302
|
}
|
|
17127
17303
|
async function getChangedSince(since, filters, store) {
|
|
17128
|
-
return (await listTasks2(filters ?? {}, store)).filter((task) => task.updated_at
|
|
17304
|
+
return (await listTasks2(filters ?? {}, store)).filter((task) => changedSinceStampNewer(task.updated_at ?? "", since));
|
|
17129
17305
|
}
|
|
17130
17306
|
async function createProject2(input, store, context) {
|
|
17131
17307
|
const timestamp2 = new Date().toISOString();
|
|
@@ -17341,16 +17517,22 @@ async function createTaskList2(input, store, context) {
|
|
|
17341
17517
|
async function updateTaskList2(id, input, store) {
|
|
17342
17518
|
const list = await requireRecord("task_lists", id, store);
|
|
17343
17519
|
const patch = definedPatch(input);
|
|
17520
|
+
const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? list.project_id : input.project_id;
|
|
17344
17521
|
if (input.slug !== undefined) {
|
|
17345
17522
|
const slug = slugifyRaw(input.slug);
|
|
17346
17523
|
if (!slug)
|
|
17347
17524
|
throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
|
|
17348
|
-
|
|
17525
|
+
patch.slug = slug;
|
|
17526
|
+
}
|
|
17527
|
+
const effectiveSlug = patch.slug ?? list.slug;
|
|
17528
|
+
if (projectId !== list.project_id || effectiveSlug !== list.slug) {
|
|
17529
|
+
const duplicate = (await store.list("task_lists")).find((candidate) => candidate.id !== id && candidate.project_id === projectId && candidate.slug === effectiveSlug);
|
|
17349
17530
|
if (duplicate) {
|
|
17350
|
-
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${
|
|
17531
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
|
|
17351
17532
|
}
|
|
17352
|
-
patch.slug = slug;
|
|
17353
17533
|
}
|
|
17534
|
+
if (projectId !== list.project_id)
|
|
17535
|
+
patch.project_id = projectId;
|
|
17354
17536
|
return store.upsert("task_lists", {
|
|
17355
17537
|
...list,
|
|
17356
17538
|
...patch,
|
|
@@ -17443,6 +17625,22 @@ async function addComment2(input, store, context) {
|
|
|
17443
17625
|
function redactComment(comment) {
|
|
17444
17626
|
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
17445
17627
|
}
|
|
17628
|
+
async function addPlanComment2(input, store, context) {
|
|
17629
|
+
const comment = {
|
|
17630
|
+
id: randomUUID3(),
|
|
17631
|
+
plan_id: input.plan_id,
|
|
17632
|
+
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
17633
|
+
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
17634
|
+
content: redactEvidenceText(input.content),
|
|
17635
|
+
type: input.type ?? "comment",
|
|
17636
|
+
progress_pct: input.progress_pct ?? null,
|
|
17637
|
+
created_at: new Date().toISOString()
|
|
17638
|
+
};
|
|
17639
|
+
return store.upsert("plan_comments", comment, context);
|
|
17640
|
+
}
|
|
17641
|
+
function redactPlanComment(comment) {
|
|
17642
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
17643
|
+
}
|
|
17446
17644
|
async function exportSnapshot(store) {
|
|
17447
17645
|
return {
|
|
17448
17646
|
exportedAt: new Date().toISOString(),
|
|
@@ -19051,24 +19249,20 @@ export {
|
|
|
19051
19249
|
postgresTodosScopedSlugIndexStatusSql,
|
|
19052
19250
|
postgresTodosCommentCursorIndexSql,
|
|
19053
19251
|
planRunArtifactsS3Sync,
|
|
19054
|
-
parseStorageMode,
|
|
19055
19252
|
parseStorageBackend,
|
|
19056
19253
|
maybeInstallShadowCapture2 as maybeInstallShadowCapture,
|
|
19057
19254
|
loadTodosStorageConfig,
|
|
19058
19255
|
loadStorageConfig,
|
|
19059
19256
|
isTodosShadowEnabled,
|
|
19060
|
-
isTodosRemoteStorageEnabled,
|
|
19061
19257
|
isTodosPostgresBackend,
|
|
19062
19258
|
isCommentRedactionBackfillComplete,
|
|
19063
19259
|
installShadowOutboxSchema,
|
|
19064
19260
|
importSqliteTodosStorageSnapshot,
|
|
19065
19261
|
getTodosStorageShadowEnvName,
|
|
19066
|
-
getTodosStorageMode,
|
|
19067
19262
|
getTodosStorageEnvName,
|
|
19068
19263
|
getTodosStorageDatabaseUrl,
|
|
19069
19264
|
getTodosStorageDatabaseEnv,
|
|
19070
19265
|
getTodosStorageBackend,
|
|
19071
|
-
getStorageMode,
|
|
19072
19266
|
getStorageDatabaseUrl,
|
|
19073
19267
|
getStorageDatabaseEnv,
|
|
19074
19268
|
getRuntimeShadowOutbox,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/task-manifest/postgres.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAE/B,KAAK,wBAAwB,EAC9B,MAAM,cAAc,CAAC;AAItB,OAAO,EAEL,KAAK,yCAAyC,EAE9C,KAAK,4BAA4B,EACjC,KAAK,kCAAkC,EACvC,KAAK,mCAAmC,EAExC,KAAK,+BAA+B,EACpC,KAAK,wBAAwB,EAE9B,MAAM,YAAY,CAAC;AAgKpB,qBAAa,gCAAiC,YAAW,wBAAwB;IAQ7E,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAU;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,WAAW,CAA8B;gBAG9B,MAAM,EAAE,+BAA+B,EACxD,OAAO,GAAE,yCAA8C;YAO3C,YAAY;
|
|
1
|
+
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/task-manifest/postgres.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAE/B,KAAK,wBAAwB,EAC9B,MAAM,cAAc,CAAC;AAItB,OAAO,EAEL,KAAK,yCAAyC,EAE9C,KAAK,4BAA4B,EACjC,KAAK,kCAAkC,EACvC,KAAK,mCAAmC,EAExC,KAAK,+BAA+B,EACpC,KAAK,wBAAwB,EAE9B,MAAM,YAAY,CAAC;AAgKpB,qBAAa,gCAAiC,YAAW,wBAAwB;IAQ7E,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAU;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,WAAW,CAA8B;gBAG9B,MAAM,EAAE,+BAA+B,EACxD,OAAO,GAAE,yCAA8C;YAO3C,YAAY;YAqBZ,UAAU;YAcV,eAAe;IAyCvB,KAAK,CAAC,KAAK,EAAE,sBAAsB,EAAE,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAAC,4BAA4B,CAAC;IAwJ/G,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,CAAC;IAenE,qBAAqB,CAAC,MAAM,EAAE,MAAM;IA8BpC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0DzE,UAAU,CACd,KAAK,EAAE,kCAAkC,EACzC,OAAO,EAAE,wBAAwB,EACjC,qBAAqB,EAAE,MAAM,EAC7B,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,mCAAmC,CAAC;YAyPjC,QAAQ;CAcvB"}
|
package/dist/task-manifest.js
CHANGED
|
@@ -6613,12 +6613,21 @@ class PostgresTodosTaskManifestBackend {
|
|
|
6613
6613
|
this.tenantId = options.tenantId ?? "default";
|
|
6614
6614
|
}
|
|
6615
6615
|
async ensureSchema() {
|
|
6616
|
-
this.schemaReady
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6616
|
+
if (this.schemaReady === null) {
|
|
6617
|
+
const attempt = (async () => {
|
|
6618
|
+
for (const sql of postgresTodosSyncSchemaSql(this.tableName))
|
|
6619
|
+
await this.client.query(sql);
|
|
6620
|
+
for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
|
|
6621
|
+
await this.client.query(sql);
|
|
6622
|
+
})();
|
|
6623
|
+
this.schemaReady = attempt;
|
|
6624
|
+
try {
|
|
6625
|
+
await attempt;
|
|
6626
|
+
} catch (error) {
|
|
6627
|
+
this.schemaReady = null;
|
|
6628
|
+
throw error;
|
|
6629
|
+
}
|
|
6630
|
+
}
|
|
6622
6631
|
await this.schemaReady;
|
|
6623
6632
|
}
|
|
6624
6633
|
async insertSync(tx, objectType2, objectId, payload, now) {
|