@hasna/todos 0.11.65 → 0.11.67
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/README.md +78 -49
- package/dashboard/dist/assets/{index-aJefI7kh.js → index-DVotjwab.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/storage-commands.d.ts +5 -0
- package/dist/cli/commands/storage-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1034 -231
- package/dist/contracts.js +409 -129
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/plans.d.ts.map +1 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/storage-tombstones.d.ts +26 -0
- package/dist/db/storage-tombstones.d.ts.map +1 -0
- package/dist/db/task-crud.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-runs.d.ts.map +1 -1
- package/dist/db/templates.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +928 -219
- package/dist/lib/approval-gates.d.ts.map +1 -1
- package/dist/lib/event-emission-safety.d.ts +9 -0
- package/dist/lib/event-emission-safety.d.ts.map +1 -0
- package/dist/lib/event-hooks.d.ts +1 -0
- package/dist/lib/event-hooks.d.ts.map +1 -1
- package/dist/lib/feature-manifest.d.ts.map +1 -1
- package/dist/lib/project-bootstrap.d.ts +1 -0
- package/dist/lib/project-bootstrap.d.ts.map +1 -1
- package/dist/lib/review-queues.d.ts.map +1 -1
- package/dist/lib/shared-events.d.ts +1 -0
- package/dist/lib/shared-events.d.ts.map +1 -1
- package/dist/lib/task-route-contract.d.ts +33 -0
- package/dist/lib/task-route-contract.d.ts.map +1 -0
- package/dist/lib/task-routing.d.ts +55 -0
- package/dist/lib/task-routing.d.ts.map +1 -0
- package/dist/mcp/index.js +459 -162
- package/dist/registry.js +409 -129
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +482 -185
- package/dist/storage/interfaces.d.ts +20 -0
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/postgres-sync.d.ts +6 -1
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
- package/dist/storage.js +677 -152
- package/dist/types/index.d.ts +22 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1182,6 +1182,22 @@ var init_migrations = __esm(() => {
|
|
|
1182
1182
|
CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source);
|
|
1183
1183
|
CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint);
|
|
1184
1184
|
INSERT OR IGNORE INTO _migrations (id) VALUES (62);
|
|
1185
|
+
`,
|
|
1186
|
+
`
|
|
1187
|
+
CREATE TABLE IF NOT EXISTS storage_tombstones (
|
|
1188
|
+
id TEXT PRIMARY KEY,
|
|
1189
|
+
object_type TEXT NOT NULL,
|
|
1190
|
+
object_id TEXT NOT NULL,
|
|
1191
|
+
deleted_at TEXT NOT NULL,
|
|
1192
|
+
updated_at TEXT NOT NULL,
|
|
1193
|
+
source_machine_id TEXT,
|
|
1194
|
+
payload TEXT,
|
|
1195
|
+
version INTEGER,
|
|
1196
|
+
UNIQUE(object_type, object_id)
|
|
1197
|
+
);
|
|
1198
|
+
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id);
|
|
1199
|
+
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at);
|
|
1200
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (63);
|
|
1185
1201
|
`
|
|
1186
1202
|
];
|
|
1187
1203
|
});
|
|
@@ -1703,6 +1719,20 @@ function ensureSchema(db) {
|
|
|
1703
1719
|
)`);
|
|
1704
1720
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
|
|
1705
1721
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
|
|
1722
|
+
ensureTable("storage_tombstones", `
|
|
1723
|
+
CREATE TABLE storage_tombstones (
|
|
1724
|
+
id TEXT PRIMARY KEY,
|
|
1725
|
+
object_type TEXT NOT NULL,
|
|
1726
|
+
object_id TEXT NOT NULL,
|
|
1727
|
+
deleted_at TEXT NOT NULL,
|
|
1728
|
+
updated_at TEXT NOT NULL,
|
|
1729
|
+
source_machine_id TEXT,
|
|
1730
|
+
payload TEXT,
|
|
1731
|
+
version INTEGER,
|
|
1732
|
+
UNIQUE(object_type, object_id)
|
|
1733
|
+
)`);
|
|
1734
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id)");
|
|
1735
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at)");
|
|
1706
1736
|
ensureTable("machines", `
|
|
1707
1737
|
CREATE TABLE machines (
|
|
1708
1738
|
id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
|
|
@@ -6173,6 +6203,80 @@ init_config();
|
|
|
6173
6203
|
init_types();
|
|
6174
6204
|
init_database();
|
|
6175
6205
|
init_machines();
|
|
6206
|
+
|
|
6207
|
+
// src/db/storage-tombstones.ts
|
|
6208
|
+
init_database();
|
|
6209
|
+
init_machines();
|
|
6210
|
+
function recordStorageTombstone(input, db) {
|
|
6211
|
+
const d = db ?? getDatabase();
|
|
6212
|
+
const deletedAt = input.deleted_at ?? now();
|
|
6213
|
+
const machineId = input.source_machine_id ?? currentStorageMachineId(d);
|
|
6214
|
+
d.run(`INSERT INTO storage_tombstones (
|
|
6215
|
+
id, object_type, object_id, deleted_at, updated_at, source_machine_id, payload, version
|
|
6216
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
6217
|
+
ON CONFLICT(object_type, object_id) DO UPDATE SET
|
|
6218
|
+
deleted_at = excluded.deleted_at,
|
|
6219
|
+
updated_at = excluded.updated_at,
|
|
6220
|
+
source_machine_id = excluded.source_machine_id,
|
|
6221
|
+
payload = excluded.payload,
|
|
6222
|
+
version = excluded.version
|
|
6223
|
+
WHERE storage_tombstones.updated_at IS NULL OR storage_tombstones.updated_at <= excluded.updated_at`, [
|
|
6224
|
+
uuid(),
|
|
6225
|
+
input.object_type,
|
|
6226
|
+
input.object_id,
|
|
6227
|
+
deletedAt,
|
|
6228
|
+
deletedAt,
|
|
6229
|
+
machineId,
|
|
6230
|
+
input.payload ? JSON.stringify(input.payload) : null,
|
|
6231
|
+
input.version ?? null
|
|
6232
|
+
]);
|
|
6233
|
+
return getStorageTombstone(input.object_type, input.object_id, d);
|
|
6234
|
+
}
|
|
6235
|
+
function getStorageTombstone(objectType, objectId, db) {
|
|
6236
|
+
const d = db ?? getDatabase();
|
|
6237
|
+
const row = d.query("SELECT * FROM storage_tombstones WHERE object_type = ? AND object_id = ?").get(objectType, objectId);
|
|
6238
|
+
return row ? rowToStorageTombstone(row) : null;
|
|
6239
|
+
}
|
|
6240
|
+
function listStorageTombstones(db) {
|
|
6241
|
+
const d = db ?? getDatabase();
|
|
6242
|
+
return d.query("SELECT * FROM storage_tombstones ORDER BY updated_at ASC, object_type ASC, object_id ASC").all().map(rowToStorageTombstone);
|
|
6243
|
+
}
|
|
6244
|
+
function shouldApplyStorageTombstone(tombstone, existingUpdatedAt) {
|
|
6245
|
+
const tombstoneClock = Date.parse(tombstone.updated_at || tombstone.deleted_at);
|
|
6246
|
+
if (!existingUpdatedAt)
|
|
6247
|
+
return true;
|
|
6248
|
+
const existingClock = Date.parse(existingUpdatedAt);
|
|
6249
|
+
if (Number.isNaN(tombstoneClock))
|
|
6250
|
+
return true;
|
|
6251
|
+
if (Number.isNaN(existingClock))
|
|
6252
|
+
return true;
|
|
6253
|
+
return tombstoneClock >= existingClock;
|
|
6254
|
+
}
|
|
6255
|
+
function rowToStorageTombstone(row) {
|
|
6256
|
+
return {
|
|
6257
|
+
...row,
|
|
6258
|
+
payload: parsePayload(row.payload)
|
|
6259
|
+
};
|
|
6260
|
+
}
|
|
6261
|
+
function parsePayload(value) {
|
|
6262
|
+
if (!value)
|
|
6263
|
+
return null;
|
|
6264
|
+
try {
|
|
6265
|
+
const parsed = JSON.parse(value);
|
|
6266
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
6267
|
+
} catch {
|
|
6268
|
+
return null;
|
|
6269
|
+
}
|
|
6270
|
+
}
|
|
6271
|
+
function currentStorageMachineId(db) {
|
|
6272
|
+
try {
|
|
6273
|
+
return getMachineId(db);
|
|
6274
|
+
} catch {
|
|
6275
|
+
return null;
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
|
|
6279
|
+
// src/db/projects.ts
|
|
6176
6280
|
function slugify(name) {
|
|
6177
6281
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
6178
6282
|
}
|
|
@@ -6202,8 +6306,9 @@ function createProject(input, db) {
|
|
|
6202
6306
|
const timestamp = now();
|
|
6203
6307
|
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
6204
6308
|
const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
|
|
6205
|
-
|
|
6206
|
-
|
|
6309
|
+
const machineId = currentStorageMachineId(d);
|
|
6310
|
+
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
|
|
6311
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
|
|
6207
6312
|
return getProject(id, d);
|
|
6208
6313
|
}
|
|
6209
6314
|
function getProject(id, db) {
|
|
@@ -6282,6 +6387,14 @@ function renameProject(id, input, db) {
|
|
|
6282
6387
|
}
|
|
6283
6388
|
function deleteProject(id, db) {
|
|
6284
6389
|
const d = db || getDatabase();
|
|
6390
|
+
const project = getProject(id, d);
|
|
6391
|
+
if (!project)
|
|
6392
|
+
return false;
|
|
6393
|
+
recordStorageTombstone({
|
|
6394
|
+
object_type: "projects",
|
|
6395
|
+
object_id: id,
|
|
6396
|
+
payload: project
|
|
6397
|
+
}, d);
|
|
6285
6398
|
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
6286
6399
|
return result.changes > 0;
|
|
6287
6400
|
}
|
|
@@ -6386,6 +6499,14 @@ function listMachineLocalPaths(projectId, db) {
|
|
|
6386
6499
|
function removeMachineLocalPath(projectId, machineId, db) {
|
|
6387
6500
|
const d = db || getDatabase();
|
|
6388
6501
|
const mid = machineId ?? getMachineId(d);
|
|
6502
|
+
const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, mid);
|
|
6503
|
+
if (!existing)
|
|
6504
|
+
return false;
|
|
6505
|
+
recordStorageTombstone({
|
|
6506
|
+
object_type: "project_machine_paths",
|
|
6507
|
+
object_id: existing.id,
|
|
6508
|
+
payload: existing
|
|
6509
|
+
}, d);
|
|
6389
6510
|
const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
|
|
6390
6511
|
return result.changes > 0;
|
|
6391
6512
|
}
|
|
@@ -6434,20 +6555,69 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
|
6434
6555
|
}
|
|
6435
6556
|
}
|
|
6436
6557
|
|
|
6558
|
+
// src/lib/event-emission-safety.ts
|
|
6559
|
+
init_database();
|
|
6560
|
+
init_sync_utils();
|
|
6561
|
+
import { tmpdir } from "os";
|
|
6562
|
+
import { resolve as resolve3, sep } from "path";
|
|
6563
|
+
function envFlag(name) {
|
|
6564
|
+
const value = process.env[name];
|
|
6565
|
+
return value === "1" || value === "true" || value === "yes";
|
|
6566
|
+
}
|
|
6567
|
+
function isUnder(parent, child) {
|
|
6568
|
+
const normalizedParent = resolve3(parent);
|
|
6569
|
+
const normalizedChild = resolve3(child);
|
|
6570
|
+
return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
|
|
6571
|
+
}
|
|
6572
|
+
function databasePathFromDatabase(db) {
|
|
6573
|
+
const filename = db?.filename;
|
|
6574
|
+
return typeof filename === "string" && filename.trim() ? filename : undefined;
|
|
6575
|
+
}
|
|
6576
|
+
function isEphemeralTodosDatabase(dbPath) {
|
|
6577
|
+
const resolvedPath = dbPath ?? getDatabasePath();
|
|
6578
|
+
if (resolvedPath === ":memory:" || resolvedPath.startsWith("file::memory:"))
|
|
6579
|
+
return true;
|
|
6580
|
+
return isUnder(tmpdir(), resolvedPath);
|
|
6581
|
+
}
|
|
6582
|
+
function hasExplicitSharedEventsStore() {
|
|
6583
|
+
return Boolean(process.env["HASNA_EVENTS_DIR"] || process.env["HASNA_EVENTS_HOME"]);
|
|
6584
|
+
}
|
|
6585
|
+
function usesIsolatedTodosHome() {
|
|
6586
|
+
return isUnder(tmpdir(), getTodosGlobalDir());
|
|
6587
|
+
}
|
|
6588
|
+
function shouldEmitSharedTaskEvents(dbPath) {
|
|
6589
|
+
if (envFlag("TODOS_DISABLE_SHARED_EVENTS"))
|
|
6590
|
+
return false;
|
|
6591
|
+
if (envFlag("TODOS_ALLOW_EPHEMERAL_SHARED_EVENTS"))
|
|
6592
|
+
return true;
|
|
6593
|
+
if (!isEphemeralTodosDatabase(dbPath))
|
|
6594
|
+
return true;
|
|
6595
|
+
return hasExplicitSharedEventsStore();
|
|
6596
|
+
}
|
|
6597
|
+
function shouldDeliverLocalLifecycleHooks(dbPath) {
|
|
6598
|
+
if (envFlag("TODOS_DISABLE_LOCAL_EVENT_HOOKS"))
|
|
6599
|
+
return false;
|
|
6600
|
+
if (envFlag("TODOS_ALLOW_EPHEMERAL_LOCAL_EVENT_HOOKS"))
|
|
6601
|
+
return true;
|
|
6602
|
+
if (!isEphemeralTodosDatabase(dbPath))
|
|
6603
|
+
return true;
|
|
6604
|
+
return usesIsolatedTodosHome();
|
|
6605
|
+
}
|
|
6606
|
+
|
|
6437
6607
|
// src/lib/event-hooks.ts
|
|
6438
6608
|
init_redaction();
|
|
6439
6609
|
import { createHash, randomUUID } from "crypto";
|
|
6440
6610
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
6441
|
-
import { dirname as dirname4, resolve as
|
|
6611
|
+
import { dirname as dirname4, resolve as resolve6 } from "path";
|
|
6442
6612
|
import { createConnection } from "net";
|
|
6443
6613
|
|
|
6444
6614
|
// src/lib/runner-sandbox.ts
|
|
6445
6615
|
init_config();
|
|
6446
|
-
import { relative as relative2, resolve as
|
|
6616
|
+
import { relative as relative2, resolve as resolve5 } from "path";
|
|
6447
6617
|
|
|
6448
6618
|
// src/lib/workspace-trust.ts
|
|
6449
6619
|
init_config();
|
|
6450
|
-
import { relative, resolve as
|
|
6620
|
+
import { relative, resolve as resolve4 } from "path";
|
|
6451
6621
|
var DEFAULT_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
6452
6622
|
var DEFAULT_ENV_REDACTIONS = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
6453
6623
|
var PRESET_DEFAULTS = {
|
|
@@ -6493,7 +6663,7 @@ var PRESET_DEFAULTS = {
|
|
|
6493
6663
|
}
|
|
6494
6664
|
};
|
|
6495
6665
|
function normalizePath(path) {
|
|
6496
|
-
return
|
|
6666
|
+
return resolve4(path);
|
|
6497
6667
|
}
|
|
6498
6668
|
function unique2(values) {
|
|
6499
6669
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -6630,7 +6800,7 @@ function checkWorkspacePermission(input = {}) {
|
|
|
6630
6800
|
var DEFAULT_COMMAND_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
6631
6801
|
var DEFAULT_ENV_REDACTIONS2 = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
6632
6802
|
function normalizePath2(path) {
|
|
6633
|
-
return
|
|
6803
|
+
return resolve5(path);
|
|
6634
6804
|
}
|
|
6635
6805
|
function unique3(values) {
|
|
6636
6806
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -6976,7 +7146,7 @@ async function deliverHook(hook, envelope) {
|
|
|
6976
7146
|
if (hook.target === "stdout") {
|
|
6977
7147
|
output = line.trim();
|
|
6978
7148
|
} else if (hook.target === "file") {
|
|
6979
|
-
const filePath =
|
|
7149
|
+
const filePath = resolve6(hook.file_path);
|
|
6980
7150
|
mkdirSync3(dirname4(filePath), { recursive: true });
|
|
6981
7151
|
appendFileSync(filePath, line);
|
|
6982
7152
|
} else if (hook.target === "socket") {
|
|
@@ -7051,6 +7221,8 @@ async function emitLocalEventHooks(input) {
|
|
|
7051
7221
|
return Promise.all(hooks.map((hook) => deliverHook(hook, envelope)));
|
|
7052
7222
|
}
|
|
7053
7223
|
function emitLocalEventHooksQuiet(input) {
|
|
7224
|
+
if (!shouldDeliverLocalLifecycleHooks(input.databasePath))
|
|
7225
|
+
return;
|
|
7054
7226
|
emitLocalEventHooks(input).catch(() => {});
|
|
7055
7227
|
}
|
|
7056
7228
|
async function testLocalEventHook(name, input) {
|
|
@@ -7367,7 +7539,7 @@ async function dispatchCommand(event, channel) {
|
|
|
7367
7539
|
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
7368
7540
|
HASNA_EVENT_JSON: eventJson
|
|
7369
7541
|
};
|
|
7370
|
-
return new Promise((
|
|
7542
|
+
return new Promise((resolve7) => {
|
|
7371
7543
|
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
7372
7544
|
cwd: channel.command.cwd,
|
|
7373
7545
|
env,
|
|
@@ -7385,7 +7557,7 @@ async function dispatchCommand(event, channel) {
|
|
|
7385
7557
|
});
|
|
7386
7558
|
child.on("error", (error) => {
|
|
7387
7559
|
clearTimeout(timeout);
|
|
7388
|
-
|
|
7560
|
+
resolve7({
|
|
7389
7561
|
attempt: 1,
|
|
7390
7562
|
status: "failed",
|
|
7391
7563
|
startedAt,
|
|
@@ -7398,7 +7570,7 @@ async function dispatchCommand(event, channel) {
|
|
|
7398
7570
|
child.on("close", (code, signal) => {
|
|
7399
7571
|
clearTimeout(timeout);
|
|
7400
7572
|
const success = code === 0;
|
|
7401
|
-
|
|
7573
|
+
resolve7({
|
|
7402
7574
|
attempt: 1,
|
|
7403
7575
|
status: success ? "success" : "failed",
|
|
7404
7576
|
startedAt,
|
|
@@ -7666,14 +7838,15 @@ function createTaskList(input, db) {
|
|
|
7666
7838
|
const id = uuid();
|
|
7667
7839
|
const timestamp = now();
|
|
7668
7840
|
const slug = input.slug || slugify(input.name);
|
|
7841
|
+
const machineId = currentStorageMachineId(d);
|
|
7669
7842
|
if (!input.project_id) {
|
|
7670
7843
|
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
7671
7844
|
if (existing) {
|
|
7672
7845
|
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
7673
7846
|
}
|
|
7674
7847
|
}
|
|
7675
|
-
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
7676
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
7848
|
+
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
|
|
7849
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
|
|
7677
7850
|
return getTaskList(id, d);
|
|
7678
7851
|
}
|
|
7679
7852
|
function getTaskList(id, db) {
|
|
@@ -7723,6 +7896,14 @@ function updateTaskList(id, input, db) {
|
|
|
7723
7896
|
}
|
|
7724
7897
|
function deleteTaskList(id, db) {
|
|
7725
7898
|
const d = db || getDatabase();
|
|
7899
|
+
const list = getTaskList(id, d);
|
|
7900
|
+
if (!list)
|
|
7901
|
+
return false;
|
|
7902
|
+
recordStorageTombstone({
|
|
7903
|
+
object_type: "task_lists",
|
|
7904
|
+
object_id: id,
|
|
7905
|
+
payload: list
|
|
7906
|
+
}, d);
|
|
7726
7907
|
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
7727
7908
|
}
|
|
7728
7909
|
function ensureTaskList(name, slug, projectId, db) {
|
|
@@ -7733,39 +7914,9 @@ function ensureTaskList(name, slug, projectId, db) {
|
|
|
7733
7914
|
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
7734
7915
|
}
|
|
7735
7916
|
|
|
7736
|
-
// src/lib/
|
|
7737
|
-
var
|
|
7738
|
-
|
|
7739
|
-
return {
|
|
7740
|
-
id: task.id,
|
|
7741
|
-
task_id: task.id,
|
|
7742
|
-
short_id: task.short_id,
|
|
7743
|
-
title: task.title,
|
|
7744
|
-
description: task.description,
|
|
7745
|
-
status: task.status,
|
|
7746
|
-
priority: task.priority,
|
|
7747
|
-
project_id: task.project_id,
|
|
7748
|
-
parent_id: task.parent_id,
|
|
7749
|
-
plan_id: task.plan_id,
|
|
7750
|
-
task_list_id: task.task_list_id,
|
|
7751
|
-
agent_id: task.agent_id,
|
|
7752
|
-
assigned_to: task.assigned_to,
|
|
7753
|
-
session_id: task.session_id,
|
|
7754
|
-
working_dir: task.working_dir,
|
|
7755
|
-
tags: task.tags,
|
|
7756
|
-
metadata: task.metadata,
|
|
7757
|
-
version: task.version,
|
|
7758
|
-
created_at: task.created_at,
|
|
7759
|
-
updated_at: task.updated_at,
|
|
7760
|
-
started_at: task.started_at,
|
|
7761
|
-
completed_at: task.completed_at,
|
|
7762
|
-
due_at: task.due_at,
|
|
7763
|
-
requires_approval: task.requires_approval,
|
|
7764
|
-
approved_by: task.approved_by,
|
|
7765
|
-
approved_at: task.approved_at,
|
|
7766
|
-
...extra
|
|
7767
|
-
};
|
|
7768
|
-
}
|
|
7917
|
+
// src/lib/task-route-contract.ts
|
|
7918
|
+
var TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION = "todos.task_route_state.v1";
|
|
7919
|
+
var TASK_WORKFLOW_POINTER_SCHEMA_VERSION = "todos.task_workflow_pointer.v1";
|
|
7769
7920
|
function booleanField(value) {
|
|
7770
7921
|
if (typeof value === "boolean")
|
|
7771
7922
|
return value;
|
|
@@ -7787,21 +7938,31 @@ function booleanField(value) {
|
|
|
7787
7938
|
function objectField(value) {
|
|
7788
7939
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
7789
7940
|
}
|
|
7790
|
-
function
|
|
7941
|
+
function collectBooleans(records, keys) {
|
|
7942
|
+
const values = [];
|
|
7791
7943
|
for (const record of records) {
|
|
7944
|
+
if (!record)
|
|
7945
|
+
continue;
|
|
7792
7946
|
for (const key of keys) {
|
|
7793
7947
|
const value = booleanField(record[key]);
|
|
7794
7948
|
if (value !== undefined)
|
|
7795
|
-
|
|
7949
|
+
values.push(value);
|
|
7796
7950
|
}
|
|
7797
7951
|
}
|
|
7798
|
-
return;
|
|
7952
|
+
return values;
|
|
7799
7953
|
}
|
|
7800
|
-
function
|
|
7801
|
-
const
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7954
|
+
function mergedBoolean(records, keys, trueWins) {
|
|
7955
|
+
const values = collectBooleans(records, keys);
|
|
7956
|
+
if (values.length === 0)
|
|
7957
|
+
return;
|
|
7958
|
+
if (trueWins)
|
|
7959
|
+
return values.some(Boolean);
|
|
7960
|
+
return values.includes(false) ? false : true;
|
|
7961
|
+
}
|
|
7962
|
+
function routingAutomationMetadata(task, taskList) {
|
|
7963
|
+
const taskAutomation = objectField(task.metadata.automation);
|
|
7964
|
+
const taskListAutomation = taskList ? objectField(taskList.metadata.automation) : undefined;
|
|
7965
|
+
const records = [task.metadata, taskAutomation, taskList?.metadata, taskListAutomation];
|
|
7805
7966
|
const result = {};
|
|
7806
7967
|
const aliases = [
|
|
7807
7968
|
["allowed", ["allowed", "automation_allowed", "automationAllowed"]],
|
|
@@ -7812,7 +7973,7 @@ function routingAutomationMetadata(task) {
|
|
|
7812
7973
|
["approval_required", ["approval_required", "approvalRequired"]]
|
|
7813
7974
|
];
|
|
7814
7975
|
for (const [canonical, keys] of aliases) {
|
|
7815
|
-
const value =
|
|
7976
|
+
const value = mergedBoolean(records, keys, canonical !== "allowed");
|
|
7816
7977
|
if (value !== undefined)
|
|
7817
7978
|
result[canonical] = value;
|
|
7818
7979
|
}
|
|
@@ -7820,23 +7981,91 @@ function routingAutomationMetadata(task) {
|
|
|
7820
7981
|
result.requires_approval = true;
|
|
7821
7982
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
7822
7983
|
}
|
|
7984
|
+
function routeEnabledForTask(task, taskList) {
|
|
7985
|
+
const explicit = booleanField(task.metadata.route_enabled);
|
|
7986
|
+
if (explicit !== undefined)
|
|
7987
|
+
return explicit;
|
|
7988
|
+
if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
|
|
7989
|
+
return true;
|
|
7990
|
+
const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
|
|
7991
|
+
if (taskListDefault !== undefined)
|
|
7992
|
+
return taskListDefault;
|
|
7993
|
+
return;
|
|
7994
|
+
}
|
|
7995
|
+
function stringField(value) {
|
|
7996
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
7997
|
+
}
|
|
7998
|
+
function workflowPointersFromMetadata(metadata) {
|
|
7999
|
+
const nested = objectField(metadata.workflow_invocation) ?? objectField(metadata.workflow) ?? {};
|
|
8000
|
+
return {
|
|
8001
|
+
current_workflow_invocation_id: stringField(metadata.current_workflow_invocation_id) ?? stringField(nested.current_workflow_invocation_id) ?? stringField(nested.invocation_id),
|
|
8002
|
+
current_run_id: stringField(metadata.current_run_id) ?? stringField(nested.current_run_id) ?? stringField(nested.run_id),
|
|
8003
|
+
latest_manifest_path: stringField(metadata.latest_manifest_path) ?? stringField(nested.latest_manifest_path) ?? stringField(nested.manifest_path),
|
|
8004
|
+
latest_evaluation_path: stringField(metadata.latest_evaluation_path) ?? stringField(nested.latest_evaluation_path) ?? stringField(nested.evaluation_path),
|
|
8005
|
+
workflow_state: stringField(metadata.workflow_state) ?? stringField(nested.workflow_state) ?? stringField(nested.state)
|
|
8006
|
+
};
|
|
8007
|
+
}
|
|
8008
|
+
function compactWorkflowPointers(pointers) {
|
|
8009
|
+
return Object.fromEntries(Object.entries(pointers).filter(([, value]) => typeof value === "string" && value.length > 0));
|
|
8010
|
+
}
|
|
8011
|
+
function classifyProjectKind(path) {
|
|
8012
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
8013
|
+
}
|
|
8014
|
+
function isWorktreePath(path) {
|
|
8015
|
+
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
8016
|
+
}
|
|
8017
|
+
function inferRootProjectId(project) {
|
|
8018
|
+
return isWorktreePath(project.path) ? null : project.id;
|
|
8019
|
+
}
|
|
8020
|
+
|
|
8021
|
+
// src/lib/shared-events.ts
|
|
8022
|
+
var SOURCE = "todos";
|
|
8023
|
+
function taskEventData(task, extra = {}) {
|
|
8024
|
+
return {
|
|
8025
|
+
id: task.id,
|
|
8026
|
+
task_id: task.id,
|
|
8027
|
+
short_id: task.short_id,
|
|
8028
|
+
title: task.title,
|
|
8029
|
+
description: task.description,
|
|
8030
|
+
status: task.status,
|
|
8031
|
+
priority: task.priority,
|
|
8032
|
+
project_id: task.project_id,
|
|
8033
|
+
parent_id: task.parent_id,
|
|
8034
|
+
plan_id: task.plan_id,
|
|
8035
|
+
task_list_id: task.task_list_id,
|
|
8036
|
+
agent_id: task.agent_id,
|
|
8037
|
+
assigned_to: task.assigned_to,
|
|
8038
|
+
session_id: task.session_id,
|
|
8039
|
+
working_dir: task.working_dir,
|
|
8040
|
+
tags: task.tags,
|
|
8041
|
+
metadata: task.metadata,
|
|
8042
|
+
version: task.version,
|
|
8043
|
+
created_at: task.created_at,
|
|
8044
|
+
updated_at: task.updated_at,
|
|
8045
|
+
started_at: task.started_at,
|
|
8046
|
+
completed_at: task.completed_at,
|
|
8047
|
+
due_at: task.due_at,
|
|
8048
|
+
requires_approval: task.requires_approval,
|
|
8049
|
+
approved_by: task.approved_by,
|
|
8050
|
+
approved_at: task.approved_at,
|
|
8051
|
+
...extra
|
|
8052
|
+
};
|
|
8053
|
+
}
|
|
7823
8054
|
function taskEventMetadata(task) {
|
|
7824
8055
|
const metadata = {
|
|
7825
8056
|
package: "@hasna/todos",
|
|
7826
8057
|
todos_event_schema_version: 1,
|
|
8058
|
+
route_state_schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
|
|
7827
8059
|
task_id: task.id,
|
|
7828
8060
|
task_short_id: task.short_id,
|
|
7829
8061
|
project_id: task.project_id,
|
|
7830
8062
|
task_list_id: task.task_list_id,
|
|
7831
8063
|
working_dir: task.working_dir
|
|
7832
8064
|
};
|
|
7833
|
-
const
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
const automation = routingAutomationMetadata(task);
|
|
7838
|
-
if (automation) {
|
|
7839
|
-
metadata.automation = automation;
|
|
8065
|
+
const pointers = workflowPointersFromMetadata(task.metadata);
|
|
8066
|
+
for (const [key, value] of Object.entries(pointers)) {
|
|
8067
|
+
if (value)
|
|
8068
|
+
metadata[key] = value;
|
|
7840
8069
|
}
|
|
7841
8070
|
try {
|
|
7842
8071
|
const project = task.project_id ? getProject(task.project_id) : null;
|
|
@@ -7865,18 +8094,20 @@ function taskEventMetadata(task) {
|
|
|
7865
8094
|
metadata.task_list_project_id = taskList.project_id;
|
|
7866
8095
|
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
7867
8096
|
}
|
|
8097
|
+
const routeEnabled = routeEnabledForTask(task, taskList);
|
|
8098
|
+
if (routeEnabled !== undefined) {
|
|
8099
|
+
metadata.route_enabled = routeEnabled;
|
|
8100
|
+
}
|
|
8101
|
+
const automation = routingAutomationMetadata(task, taskList);
|
|
8102
|
+
if (automation) {
|
|
8103
|
+
metadata.automation = automation;
|
|
8104
|
+
metadata.route_blocked_by_no_auto = automation.no_auto === true;
|
|
8105
|
+
metadata.route_blocked_by_manual = automation.manual === true || automation.manual_required === true;
|
|
8106
|
+
metadata.route_blocked_by_approval = (automation.requires_approval === true || automation.approval_required === true) && !task.approved_by;
|
|
8107
|
+
}
|
|
7868
8108
|
} catch {}
|
|
7869
8109
|
return metadata;
|
|
7870
8110
|
}
|
|
7871
|
-
function classifyProjectKind(path) {
|
|
7872
|
-
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
7873
|
-
}
|
|
7874
|
-
function isWorktreePath(path) {
|
|
7875
|
-
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
7876
|
-
}
|
|
7877
|
-
function inferRootProjectId(project) {
|
|
7878
|
-
return isWorktreePath(project.path) ? null : project.id;
|
|
7879
|
-
}
|
|
7880
8111
|
function readMachineLocalPath(project) {
|
|
7881
8112
|
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
7882
8113
|
if (!machineId)
|
|
@@ -7889,6 +8120,8 @@ function readMachineLocalPath(project) {
|
|
|
7889
8120
|
}
|
|
7890
8121
|
}
|
|
7891
8122
|
async function emitSharedTaskEvent(input) {
|
|
8123
|
+
if (!shouldEmitSharedTaskEvents(input.databasePath))
|
|
8124
|
+
return;
|
|
7892
8125
|
const data = taskEventData(input.task, input.data);
|
|
7893
8126
|
await new EventsClient().emit({
|
|
7894
8127
|
source: SOURCE,
|
|
@@ -7913,8 +8146,9 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
|
|
|
7913
8146
|
const d = db || getDatabase();
|
|
7914
8147
|
const id = uuid();
|
|
7915
8148
|
const timestamp = now();
|
|
7916
|
-
|
|
7917
|
-
|
|
8149
|
+
const machineId = currentStorageMachineId(d);
|
|
8150
|
+
d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
|
|
8151
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field2 || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp, machineId]);
|
|
7918
8152
|
try {
|
|
7919
8153
|
const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
|
|
7920
8154
|
logActivity2({
|
|
@@ -7927,7 +8161,7 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
|
|
|
7927
8161
|
actor_id: agentId ?? undefined
|
|
7928
8162
|
}, d);
|
|
7929
8163
|
} catch {}
|
|
7930
|
-
return { id, task_id: taskId, action, field: field2 || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
|
|
8164
|
+
return { id, task_id: taskId, action, field: field2 || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp, machine_id: machineId };
|
|
7931
8165
|
}
|
|
7932
8166
|
function getTaskHistory(taskId, db) {
|
|
7933
8167
|
const d = db || getDatabase();
|
|
@@ -8276,13 +8510,14 @@ function createTask(input, db) {
|
|
|
8276
8510
|
const d = db || getDatabase();
|
|
8277
8511
|
const timestamp = now();
|
|
8278
8512
|
const tags = input.tags || [];
|
|
8513
|
+
const machineId = currentStorageMachineId(d);
|
|
8279
8514
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
8280
8515
|
const assignedFromProject = input.assigned_from_project || null;
|
|
8281
8516
|
let id = uuid();
|
|
8282
8517
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
8283
8518
|
try {
|
|
8284
|
-
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, sla_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
|
|
8285
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8519
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, sla_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type, machine_id)
|
|
8520
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8286
8521
|
id,
|
|
8287
8522
|
null,
|
|
8288
8523
|
input.project_id || null,
|
|
@@ -8319,7 +8554,8 @@ function createTask(input, db) {
|
|
|
8319
8554
|
input.spawned_from_session || null,
|
|
8320
8555
|
assignedBy || null,
|
|
8321
8556
|
assignedFromProject || null,
|
|
8322
|
-
input.task_type || null
|
|
8557
|
+
input.task_type || null,
|
|
8558
|
+
machineId
|
|
8323
8559
|
]);
|
|
8324
8560
|
break;
|
|
8325
8561
|
} catch (e) {
|
|
@@ -8335,9 +8571,10 @@ function createTask(input, db) {
|
|
|
8335
8571
|
}
|
|
8336
8572
|
const task = getTask(id, d);
|
|
8337
8573
|
const payload = taskEventData(task);
|
|
8574
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8338
8575
|
dispatchWebhook2("task.created", payload, d).catch(() => {});
|
|
8339
|
-
emitLocalEventHooksQuiet({ type: "task.created", payload });
|
|
8340
|
-
emitSharedTaskEventQuiet({ type: "task.created", task });
|
|
8576
|
+
emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
|
|
8577
|
+
emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
|
|
8341
8578
|
return task;
|
|
8342
8579
|
}
|
|
8343
8580
|
function getTask(id, db) {
|
|
@@ -8755,29 +8992,39 @@ function updateTask(id, input, db) {
|
|
|
8755
8992
|
approved_by: input.approved_by ?? task.approved_by,
|
|
8756
8993
|
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
8757
8994
|
};
|
|
8995
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8758
8996
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
8759
8997
|
const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
|
|
8760
8998
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
8761
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
8762
|
-
emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
|
|
8999
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
9000
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to }, databasePath });
|
|
8763
9001
|
}
|
|
8764
9002
|
if (input.status !== undefined && input.status !== task.status) {
|
|
8765
9003
|
const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
|
|
8766
9004
|
dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
|
|
8767
|
-
emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
|
|
8768
|
-
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
|
|
9005
|
+
emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
|
|
9006
|
+
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
|
|
8769
9007
|
}
|
|
8770
9008
|
if (input.approved_by !== undefined) {
|
|
8771
|
-
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
9009
|
+
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title }, databasePath });
|
|
8772
9010
|
}
|
|
8773
9011
|
const updatePayload = taskEventData(updatedTask);
|
|
8774
9012
|
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
8775
|
-
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
|
|
8776
|
-
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
|
|
9013
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
|
|
9014
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
|
|
8777
9015
|
return updatedTask;
|
|
8778
9016
|
}
|
|
8779
9017
|
function deleteTask(id, db) {
|
|
8780
9018
|
const d = db || getDatabase();
|
|
9019
|
+
const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
9020
|
+
if (!row)
|
|
9021
|
+
return false;
|
|
9022
|
+
recordStorageTombstone({
|
|
9023
|
+
object_type: "tasks",
|
|
9024
|
+
object_id: id,
|
|
9025
|
+
payload: rowToTask(row),
|
|
9026
|
+
version: row.version
|
|
9027
|
+
}, d);
|
|
8781
9028
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
8782
9029
|
return result.changes > 0;
|
|
8783
9030
|
}
|
|
@@ -8909,8 +9156,9 @@ function resolveTemplateId(id, d) {
|
|
|
8909
9156
|
function createTemplate(input, db) {
|
|
8910
9157
|
const d = db || getDatabase();
|
|
8911
9158
|
const id = uuid();
|
|
8912
|
-
|
|
8913
|
-
|
|
9159
|
+
const machineId = currentStorageMachineId(d);
|
|
9160
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at, machine_id)
|
|
9161
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8914
9162
|
id,
|
|
8915
9163
|
input.name,
|
|
8916
9164
|
input.title_pattern,
|
|
@@ -8921,7 +9169,8 @@ function createTemplate(input, db) {
|
|
|
8921
9169
|
input.project_id || null,
|
|
8922
9170
|
input.plan_id || null,
|
|
8923
9171
|
JSON.stringify(input.metadata || {}),
|
|
8924
|
-
now()
|
|
9172
|
+
now(),
|
|
9173
|
+
machineId
|
|
8925
9174
|
]);
|
|
8926
9175
|
if (input.tasks && input.tasks.length > 0) {
|
|
8927
9176
|
addTemplateTasks(id, input.tasks, d);
|
|
@@ -8945,6 +9194,15 @@ function deleteTemplate(id, db) {
|
|
|
8945
9194
|
const resolved = resolveTemplateId(id, d);
|
|
8946
9195
|
if (!resolved)
|
|
8947
9196
|
return false;
|
|
9197
|
+
const template = getTemplate(resolved, d);
|
|
9198
|
+
if (!template)
|
|
9199
|
+
return false;
|
|
9200
|
+
recordStorageTombstone({
|
|
9201
|
+
object_type: "templates",
|
|
9202
|
+
object_id: resolved,
|
|
9203
|
+
payload: template,
|
|
9204
|
+
version: template.version
|
|
9205
|
+
}, d);
|
|
8948
9206
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
8949
9207
|
}
|
|
8950
9208
|
function updateTemplate(id, updates, db) {
|
|
@@ -9474,6 +9732,7 @@ function getBlockingDeps(id, db) {
|
|
|
9474
9732
|
}
|
|
9475
9733
|
function startTask(id, agentId, db) {
|
|
9476
9734
|
const d = db || getDatabase();
|
|
9735
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9477
9736
|
const task = getTask(id, d);
|
|
9478
9737
|
if (!task)
|
|
9479
9738
|
throw new TaskNotFoundError(id);
|
|
@@ -9488,7 +9747,8 @@ function startTask(id, agentId, db) {
|
|
|
9488
9747
|
agent_id: agentId,
|
|
9489
9748
|
title: task.title,
|
|
9490
9749
|
blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
|
|
9491
|
-
}
|
|
9750
|
+
},
|
|
9751
|
+
databasePath
|
|
9492
9752
|
});
|
|
9493
9753
|
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
9494
9754
|
}
|
|
@@ -9510,12 +9770,13 @@ function startTask(id, agentId, db) {
|
|
|
9510
9770
|
const startedTask = { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, started_at: task.started_at || timestamp, version: task.version + 1, updated_at: timestamp };
|
|
9511
9771
|
const payload = taskEventData(startedTask, { agent_id: agentId });
|
|
9512
9772
|
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
9513
|
-
emitLocalEventHooksQuiet({ type: "task.started", payload });
|
|
9514
|
-
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
|
|
9773
|
+
emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
|
|
9774
|
+
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
|
|
9515
9775
|
return startedTask;
|
|
9516
9776
|
}
|
|
9517
9777
|
function completeTask(id, agentId, db, options) {
|
|
9518
9778
|
const d = db || getDatabase();
|
|
9779
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9519
9780
|
const task = getTask(id, d);
|
|
9520
9781
|
if (!task)
|
|
9521
9782
|
throw new TaskNotFoundError(id);
|
|
@@ -9561,8 +9822,8 @@ function completeTask(id, agentId, db, options) {
|
|
|
9561
9822
|
};
|
|
9562
9823
|
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
|
|
9563
9824
|
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
9564
|
-
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
|
|
9565
|
-
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
|
|
9825
|
+
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
|
|
9826
|
+
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
|
|
9566
9827
|
let spawnedTask = null;
|
|
9567
9828
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
9568
9829
|
spawnedTask = spawnNextRecurrence(task, d, timestamp);
|
|
@@ -9606,9 +9867,9 @@ function completeTask(id, agentId, db, options) {
|
|
|
9606
9867
|
const depTask = getTask(dep.id, d);
|
|
9607
9868
|
const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
|
|
9608
9869
|
dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
|
|
9609
|
-
emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
|
|
9870
|
+
emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
|
|
9610
9871
|
if (depTask)
|
|
9611
|
-
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
|
|
9872
|
+
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
|
|
9612
9873
|
}
|
|
9613
9874
|
}
|
|
9614
9875
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
@@ -9778,6 +10039,7 @@ function getTasksChangedSince(since, filters, db) {
|
|
|
9778
10039
|
}
|
|
9779
10040
|
function failTask(id, agentId, reason, options, db) {
|
|
9780
10041
|
const d = db || getDatabase();
|
|
10042
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9781
10043
|
const task = getTask(id, d);
|
|
9782
10044
|
if (!task)
|
|
9783
10045
|
throw new TaskNotFoundError(id);
|
|
@@ -9806,8 +10068,8 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
9806
10068
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
9807
10069
|
const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
|
|
9808
10070
|
dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
|
|
9809
|
-
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
|
|
9810
|
-
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
|
|
10071
|
+
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
|
|
10072
|
+
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning", databasePath });
|
|
9811
10073
|
let retryTask;
|
|
9812
10074
|
if (options?.retry) {
|
|
9813
10075
|
const retryCount = (task.retry_count || 0) + 1;
|
|
@@ -9867,6 +10129,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
|
|
|
9867
10129
|
}
|
|
9868
10130
|
function stealTask(agentId, opts, db) {
|
|
9869
10131
|
const d = db || getDatabase();
|
|
10132
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9870
10133
|
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
9871
10134
|
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
9872
10135
|
if (staleTasks.length === 0)
|
|
@@ -9885,8 +10148,8 @@ function stealTask(agentId, opts, db) {
|
|
|
9885
10148
|
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
9886
10149
|
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
9887
10150
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
9888
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
9889
|
-
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
|
|
10151
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
10152
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
|
|
9890
10153
|
return stolenTask;
|
|
9891
10154
|
}
|
|
9892
10155
|
function claimOrSteal(agentId, filters, db) {
|
|
@@ -10477,8 +10740,9 @@ function createPlan(input, db) {
|
|
|
10477
10740
|
const d = db || getDatabase();
|
|
10478
10741
|
const id = uuid();
|
|
10479
10742
|
const timestamp = now();
|
|
10480
|
-
|
|
10481
|
-
|
|
10743
|
+
const machineId = currentStorageMachineId(d);
|
|
10744
|
+
d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
|
|
10745
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10482
10746
|
id,
|
|
10483
10747
|
input.project_id || null,
|
|
10484
10748
|
input.task_list_id || null,
|
|
@@ -10487,7 +10751,8 @@ function createPlan(input, db) {
|
|
|
10487
10751
|
input.description || null,
|
|
10488
10752
|
input.status || "active",
|
|
10489
10753
|
timestamp,
|
|
10490
|
-
timestamp
|
|
10754
|
+
timestamp,
|
|
10755
|
+
machineId
|
|
10491
10756
|
]);
|
|
10492
10757
|
return getPlan(id, d);
|
|
10493
10758
|
}
|
|
@@ -10535,12 +10800,21 @@ function updatePlan(id, input, db) {
|
|
|
10535
10800
|
const updated = getPlan(id, d);
|
|
10536
10801
|
emitLocalEventHooksQuiet({
|
|
10537
10802
|
type: "plan.updated",
|
|
10538
|
-
payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id }
|
|
10803
|
+
payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id },
|
|
10804
|
+
databasePath: databasePathFromDatabase(d)
|
|
10539
10805
|
});
|
|
10540
10806
|
return updated;
|
|
10541
10807
|
}
|
|
10542
10808
|
function deletePlan(id, db) {
|
|
10543
10809
|
const d = db || getDatabase();
|
|
10810
|
+
const plan = getPlan(id, d);
|
|
10811
|
+
if (!plan)
|
|
10812
|
+
return false;
|
|
10813
|
+
recordStorageTombstone({
|
|
10814
|
+
object_type: "plans",
|
|
10815
|
+
object_id: id,
|
|
10816
|
+
payload: plan
|
|
10817
|
+
}, d);
|
|
10544
10818
|
const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
|
|
10545
10819
|
return result.changes > 0;
|
|
10546
10820
|
}
|
|
@@ -10947,20 +11221,20 @@ init_database();
|
|
|
10947
11221
|
init_redaction();
|
|
10948
11222
|
import { createHash as createHash2 } from "crypto";
|
|
10949
11223
|
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
10950
|
-
import { basename, dirname as dirname5, join as join6, resolve as
|
|
10951
|
-
import { tmpdir } from "os";
|
|
11224
|
+
import { basename, dirname as dirname5, join as join6, resolve as resolve7 } from "path";
|
|
11225
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
10952
11226
|
function isInMemoryDb2(path) {
|
|
10953
11227
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
10954
11228
|
}
|
|
10955
11229
|
function artifactStoreRoot() {
|
|
10956
11230
|
if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
|
|
10957
|
-
return
|
|
11231
|
+
return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
|
|
10958
11232
|
if (process.env["TODOS_ARTIFACTS_DIR"])
|
|
10959
|
-
return
|
|
11233
|
+
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
10960
11234
|
const dbPath = getDatabasePath();
|
|
10961
11235
|
if (isInMemoryDb2(dbPath))
|
|
10962
|
-
return join6(
|
|
10963
|
-
return join6(dirname5(
|
|
11236
|
+
return join6(tmpdir2(), "hasna-todos-artifacts");
|
|
11237
|
+
return join6(dirname5(resolve7(dbPath)), "artifacts");
|
|
10964
11238
|
}
|
|
10965
11239
|
function artifactStorePath(relativePath) {
|
|
10966
11240
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
@@ -11007,7 +11281,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
11007
11281
|
return "application/octet-stream";
|
|
11008
11282
|
}
|
|
11009
11283
|
function storeArtifactContent(input) {
|
|
11010
|
-
const sourcePath =
|
|
11284
|
+
const sourcePath = resolve7(input.path);
|
|
11011
11285
|
if (!existsSync8(sourcePath))
|
|
11012
11286
|
return null;
|
|
11013
11287
|
const sourceStat = statSync2(sourcePath);
|
|
@@ -11163,19 +11437,19 @@ function importStoredArtifactContent(content) {
|
|
|
11163
11437
|
var DEFAULT_DELETED_RETENTION_DAYS = 30;
|
|
11164
11438
|
function getArtifactStoreRoot(dbPath) {
|
|
11165
11439
|
if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
|
|
11166
|
-
return
|
|
11440
|
+
return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
|
|
11167
11441
|
if (process.env["TODOS_ARTIFACTS_DIR"])
|
|
11168
|
-
return
|
|
11442
|
+
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
11169
11443
|
const path = dbPath ?? getDatabasePath();
|
|
11170
11444
|
if (isInMemoryDb2(path))
|
|
11171
|
-
return join6(
|
|
11172
|
-
return join6(dirname5(
|
|
11445
|
+
return join6(tmpdir2(), "hasna-todos-artifacts");
|
|
11446
|
+
return join6(dirname5(resolve7(path)), "artifacts");
|
|
11173
11447
|
}
|
|
11174
11448
|
function computeContentHash(path) {
|
|
11175
|
-
return sha256(readFileSync4(
|
|
11449
|
+
return sha256(readFileSync4(resolve7(path)));
|
|
11176
11450
|
}
|
|
11177
11451
|
function storeArtifactFile(input) {
|
|
11178
|
-
const sourcePath =
|
|
11452
|
+
const sourcePath = resolve7(input.sourcePath);
|
|
11179
11453
|
if (!existsSync8(sourcePath)) {
|
|
11180
11454
|
throw new Error(`Source file not found: ${input.sourcePath}`);
|
|
11181
11455
|
}
|
|
@@ -11224,7 +11498,7 @@ function buildArtifactExportManifest(artifacts, dbPath) {
|
|
|
11224
11498
|
};
|
|
11225
11499
|
}
|
|
11226
11500
|
function writeArtifactExportManifest(manifest, outputPath) {
|
|
11227
|
-
const destination =
|
|
11501
|
+
const destination = resolve7(outputPath);
|
|
11228
11502
|
mkdirSync4(dirname5(destination), { recursive: true });
|
|
11229
11503
|
writeFileSync2(destination, `${JSON.stringify(manifest, null, 2)}
|
|
11230
11504
|
`);
|
|
@@ -11611,7 +11885,11 @@ function startTaskRun(input, db) {
|
|
|
11611
11885
|
}, d);
|
|
11612
11886
|
}
|
|
11613
11887
|
const run = getTaskRun(id, d);
|
|
11614
|
-
emitLocalEventHooksQuiet({
|
|
11888
|
+
emitLocalEventHooksQuiet({
|
|
11889
|
+
type: "run.started",
|
|
11890
|
+
payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title },
|
|
11891
|
+
databasePath: databasePathFromDatabase(d)
|
|
11892
|
+
});
|
|
11615
11893
|
return run;
|
|
11616
11894
|
}
|
|
11617
11895
|
function beginTaskRunTransaction(input, db) {
|
|
@@ -11880,7 +12158,8 @@ function finishTaskRun(input, db) {
|
|
|
11880
12158
|
const updated = getTaskRun(run.id, d);
|
|
11881
12159
|
emitLocalEventHooksQuiet({
|
|
11882
12160
|
type: `run.${input.status}`,
|
|
11883
|
-
payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp }
|
|
12161
|
+
payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp },
|
|
12162
|
+
databasePath: databasePathFromDatabase(d)
|
|
11884
12163
|
});
|
|
11885
12164
|
return updated;
|
|
11886
12165
|
}
|
|
@@ -13214,7 +13493,7 @@ function importOnboardingFixture(options = {}) {
|
|
|
13214
13493
|
init_database();
|
|
13215
13494
|
import { createHash as createHash3 } from "crypto";
|
|
13216
13495
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
13217
|
-
import { dirname as dirname6, resolve as
|
|
13496
|
+
import { dirname as dirname6, resolve as resolve8 } from "path";
|
|
13218
13497
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
13219
13498
|
var TODOS_LOCAL_BACKUP_KIND = "hasna.todos.local-backup";
|
|
13220
13499
|
var TODOS_LOCAL_BACKUP_SCHEMA_VERSION = 1;
|
|
@@ -13321,14 +13600,14 @@ function createLocalBackup(options = {}, db) {
|
|
|
13321
13600
|
return backup;
|
|
13322
13601
|
}
|
|
13323
13602
|
function writeLocalBackupFile(backup, outputPath) {
|
|
13324
|
-
const path =
|
|
13603
|
+
const path = resolve8(outputPath);
|
|
13325
13604
|
mkdirSync6(dirname6(path), { recursive: true });
|
|
13326
13605
|
writeFileSync4(path, `${JSON.stringify(backup, null, 2)}
|
|
13327
13606
|
`);
|
|
13328
13607
|
return path;
|
|
13329
13608
|
}
|
|
13330
13609
|
function readLocalBackupFile(path) {
|
|
13331
|
-
return JSON.parse(readFileSync5(
|
|
13610
|
+
return JSON.parse(readFileSync5(resolve8(path), "utf-8"));
|
|
13332
13611
|
}
|
|
13333
13612
|
function verifyLocalBackup(value, options = {}, db) {
|
|
13334
13613
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -16296,7 +16575,8 @@ function writeQueue(task2, queue, actor, action, db) {
|
|
|
16296
16575
|
logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
|
|
16297
16576
|
emitLocalEventHooksQuiet({
|
|
16298
16577
|
type: `review.${action}`,
|
|
16299
|
-
payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by }
|
|
16578
|
+
payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by },
|
|
16579
|
+
databasePath: databasePathFromDatabase(d)
|
|
16300
16580
|
});
|
|
16301
16581
|
return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
|
|
16302
16582
|
}
|
|
@@ -16977,7 +17257,7 @@ function canonicalize(value) {
|
|
|
16977
17257
|
function hash(value) {
|
|
16978
17258
|
return createHash5("sha256").update(value).digest("hex");
|
|
16979
17259
|
}
|
|
16980
|
-
function
|
|
17260
|
+
function parsePayload2(value) {
|
|
16981
17261
|
if (!value)
|
|
16982
17262
|
return {};
|
|
16983
17263
|
try {
|
|
@@ -17076,7 +17356,7 @@ function taskScopedRows(db, scope) {
|
|
|
17076
17356
|
FROM handoffs h
|
|
17077
17357
|
WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
|
|
17078
17358
|
`).all(scope.project_id ?? null).filter((row) => {
|
|
17079
|
-
const payload =
|
|
17359
|
+
const payload = parsePayload2(row.payload_json);
|
|
17080
17360
|
const taskRefs = parseStringArray(payload["task_ids"]);
|
|
17081
17361
|
const runRefs = parseStringArray(payload["run_ids"]);
|
|
17082
17362
|
if (scope.project_id && row.project_id !== scope.project_id)
|
|
@@ -17107,7 +17387,7 @@ function toLedgerEntries(rows) {
|
|
|
17107
17387
|
});
|
|
17108
17388
|
let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
|
|
17109
17389
|
return ordered.map((row, index) => {
|
|
17110
|
-
const payload =
|
|
17390
|
+
const payload = parsePayload2(row.payload_json);
|
|
17111
17391
|
const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
|
|
17112
17392
|
const chainHash = hash(`${previous}
|
|
17113
17393
|
${payloadHash}`);
|
|
@@ -17242,7 +17522,7 @@ function renderLocalAuditLedgerMarkdown(ledger) {
|
|
|
17242
17522
|
init_migrations();
|
|
17243
17523
|
init_schema();
|
|
17244
17524
|
import { readFileSync as readFileSync6 } from "fs";
|
|
17245
|
-
import { join as join9, resolve as
|
|
17525
|
+
import { join as join9, resolve as resolve9 } from "path";
|
|
17246
17526
|
import { Database as Database2 } from "bun:sqlite";
|
|
17247
17527
|
var LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION = 1;
|
|
17248
17528
|
var EXPECTED_PACKAGE_NAME = "@hasna/todos";
|
|
@@ -17384,7 +17664,7 @@ function checkChangelog() {
|
|
|
17384
17664
|
];
|
|
17385
17665
|
}
|
|
17386
17666
|
function createReleaseCompatibilityReport(options = {}) {
|
|
17387
|
-
const root =
|
|
17667
|
+
const root = resolve9(options.root ?? process.cwd());
|
|
17388
17668
|
const packageJson = readPackageJson(root);
|
|
17389
17669
|
const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
|
|
17390
17670
|
const checks = [
|
|
@@ -21712,6 +21992,7 @@ function rowToAgent(row) {
|
|
|
21712
21992
|
}
|
|
21713
21993
|
function registerAgent(input, db) {
|
|
21714
21994
|
const d = db || getDatabase();
|
|
21995
|
+
const machineId = currentStorageMachineId(d);
|
|
21715
21996
|
const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
|
|
21716
21997
|
const normalizedName = validateAgentName(input.name, existingNames);
|
|
21717
21998
|
const existing = getAgentByName(normalizedName, d);
|
|
@@ -21752,14 +22033,18 @@ function registerAgent(input, db) {
|
|
|
21752
22033
|
updates.push("active_project_id = ?");
|
|
21753
22034
|
params.push(input.project_id);
|
|
21754
22035
|
}
|
|
22036
|
+
if (!existing.machine_id && machineId) {
|
|
22037
|
+
updates.push("machine_id = ?");
|
|
22038
|
+
params.push(machineId);
|
|
22039
|
+
}
|
|
21755
22040
|
params.push(existing.id);
|
|
21756
22041
|
d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
21757
22042
|
return getAgent(existing.id, d);
|
|
21758
22043
|
}
|
|
21759
22044
|
const id = shortUuid();
|
|
21760
22045
|
const timestamp2 = now();
|
|
21761
|
-
d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, capabilities, reports_to, org_id, metadata, created_at, last_seen_at, session_id, working_dir, active_project_id)
|
|
21762
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
22046
|
+
d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, capabilities, reports_to, org_id, metadata, created_at, last_seen_at, session_id, working_dir, active_project_id, machine_id)
|
|
22047
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
21763
22048
|
id,
|
|
21764
22049
|
normalizedName,
|
|
21765
22050
|
input.description || null,
|
|
@@ -21775,7 +22060,8 @@ function registerAgent(input, db) {
|
|
|
21775
22060
|
timestamp2,
|
|
21776
22061
|
input.session_id || null,
|
|
21777
22062
|
input.working_dir || null,
|
|
21778
|
-
input.project_id && input.session_id ? input.project_id : null
|
|
22063
|
+
input.project_id && input.session_id ? input.project_id : null,
|
|
22064
|
+
machineId
|
|
21779
22065
|
]);
|
|
21780
22066
|
return getAgent(id, d);
|
|
21781
22067
|
}
|
|
@@ -21975,6 +22261,14 @@ var PROJECT_COLUMNS = [
|
|
|
21975
22261
|
"machine_id",
|
|
21976
22262
|
"synced_at"
|
|
21977
22263
|
];
|
|
22264
|
+
var PROJECT_MACHINE_PATH_COLUMNS = [
|
|
22265
|
+
"id",
|
|
22266
|
+
"project_id",
|
|
22267
|
+
"machine_id",
|
|
22268
|
+
"path",
|
|
22269
|
+
"created_at",
|
|
22270
|
+
"updated_at"
|
|
22271
|
+
];
|
|
21978
22272
|
var TASK_LIST_COLUMNS = [
|
|
21979
22273
|
"id",
|
|
21980
22274
|
"project_id",
|
|
@@ -22114,11 +22408,13 @@ function exportSqliteTodosStorageSnapshot(db) {
|
|
|
22114
22408
|
source: "sqlite",
|
|
22115
22409
|
tasks: listTasks({ include_archived: true }, d),
|
|
22116
22410
|
projects: listProjects(d),
|
|
22411
|
+
projectMachinePaths: listProjectMachinePaths(d),
|
|
22117
22412
|
plans: listPlans(undefined, d),
|
|
22118
22413
|
agents: listAgents({ include_archived: true }, d),
|
|
22119
22414
|
taskLists: listTaskLists(undefined, d),
|
|
22120
22415
|
templates: listTemplates(d),
|
|
22121
|
-
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d)
|
|
22416
|
+
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d),
|
|
22417
|
+
tombstones: listStorageTombstones(d)
|
|
22122
22418
|
};
|
|
22123
22419
|
}
|
|
22124
22420
|
function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
@@ -22126,13 +22422,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
22126
22422
|
const result = {
|
|
22127
22423
|
inserted: 0,
|
|
22128
22424
|
updated: 0,
|
|
22425
|
+
deleted: 0,
|
|
22129
22426
|
skipped: 0,
|
|
22130
22427
|
errors: []
|
|
22131
22428
|
};
|
|
22132
|
-
const applyRows = (table, columns, rows, updateClockColumn, afterUpsert) => {
|
|
22429
|
+
const applyRows = (objectType, table, columns, rows, updateClockColumn, afterUpsert) => {
|
|
22133
22430
|
for (const row of rows) {
|
|
22134
22431
|
try {
|
|
22135
22432
|
const record = asRecord2(row);
|
|
22433
|
+
const tombstone = typeof record["id"] === "string" ? getStorageTombstone(objectType, record["id"], d) : null;
|
|
22434
|
+
if (tombstone && shouldApplyStorageTombstone(tombstone, rowClock(record, updateClockColumn))) {
|
|
22435
|
+
result.skipped += 1;
|
|
22436
|
+
continue;
|
|
22437
|
+
}
|
|
22136
22438
|
const state = upsertById(d, table, columns, record, updateClockColumn);
|
|
22137
22439
|
if (state === "inserted")
|
|
22138
22440
|
result.inserted += 1;
|
|
@@ -22146,17 +22448,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
22146
22448
|
}
|
|
22147
22449
|
}
|
|
22148
22450
|
};
|
|
22149
|
-
applyRows("projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
|
|
22150
|
-
applyRows("
|
|
22151
|
-
applyRows("
|
|
22152
|
-
applyRows("
|
|
22153
|
-
applyRows("
|
|
22154
|
-
applyRows("
|
|
22451
|
+
applyRows("projects", "projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
|
|
22452
|
+
applyRows("project_machine_paths", "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS, snapshot.projectMachinePaths ?? [], "updated_at");
|
|
22453
|
+
applyRows("agents", "agents", AGENT_COLUMNS, snapshot.agents, "last_seen_at");
|
|
22454
|
+
applyRows("task_lists", "task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
|
|
22455
|
+
applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
|
|
22456
|
+
applyRows("templates", "task_templates", TEMPLATE_COLUMNS, snapshot.templates);
|
|
22457
|
+
applyRows("tasks", "tasks", TASK_COLUMNS, sortedTasks2(snapshot.tasks), "updated_at", (row, changed) => {
|
|
22155
22458
|
if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
|
|
22156
22459
|
replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
|
|
22157
22460
|
}
|
|
22158
22461
|
});
|
|
22159
|
-
applyRows("task_history", AUDIT_COLUMNS, snapshot.auditHistory);
|
|
22462
|
+
applyRows("audit_history", "task_history", AUDIT_COLUMNS, snapshot.auditHistory);
|
|
22463
|
+
applyTombstones(d, snapshot.tombstones ?? [], result);
|
|
22160
22464
|
return result;
|
|
22161
22465
|
}
|
|
22162
22466
|
function upsertById(db, table, columns, row, updateClockColumn) {
|
|
@@ -22211,6 +22515,85 @@ function sortedTasks2(tasks) {
|
|
|
22211
22515
|
visit(task2);
|
|
22212
22516
|
return result;
|
|
22213
22517
|
}
|
|
22518
|
+
function applyTombstones(db, tombstones, result) {
|
|
22519
|
+
for (const tombstone of tombstones) {
|
|
22520
|
+
try {
|
|
22521
|
+
recordStorageTombstone({
|
|
22522
|
+
object_type: tombstone.object_type,
|
|
22523
|
+
object_id: tombstone.object_id,
|
|
22524
|
+
deleted_at: tombstone.deleted_at,
|
|
22525
|
+
source_machine_id: tombstone.source_machine_id ?? null,
|
|
22526
|
+
payload: tombstone.payload ?? null,
|
|
22527
|
+
version: tombstone.version ?? null
|
|
22528
|
+
}, db);
|
|
22529
|
+
const table = tableForTombstone(tombstone.object_type);
|
|
22530
|
+
const existing = existingClock(db, table, tombstone.object_id);
|
|
22531
|
+
if (!shouldApplyStorageTombstone(tombstone, existing)) {
|
|
22532
|
+
result.skipped += 1;
|
|
22533
|
+
continue;
|
|
22534
|
+
}
|
|
22535
|
+
const deletedTags = table === "tasks" ? db.run("DELETE FROM task_tags WHERE task_id = ?", [tombstone.object_id]).changes : 0;
|
|
22536
|
+
const deleted = db.run(`DELETE FROM ${table} WHERE id = ?`, [tombstone.object_id]).changes;
|
|
22537
|
+
if (deleted > 0 || deletedTags > 0)
|
|
22538
|
+
result.deleted = (result.deleted ?? 0) + 1;
|
|
22539
|
+
else
|
|
22540
|
+
result.skipped += 1;
|
|
22541
|
+
} catch (error) {
|
|
22542
|
+
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
22543
|
+
}
|
|
22544
|
+
}
|
|
22545
|
+
}
|
|
22546
|
+
function tableForTombstone(objectType) {
|
|
22547
|
+
if (objectType === "tasks")
|
|
22548
|
+
return "tasks";
|
|
22549
|
+
if (objectType === "projects")
|
|
22550
|
+
return "projects";
|
|
22551
|
+
if (objectType === "project_machine_paths")
|
|
22552
|
+
return "project_machine_paths";
|
|
22553
|
+
if (objectType === "plans")
|
|
22554
|
+
return "plans";
|
|
22555
|
+
if (objectType === "agents")
|
|
22556
|
+
return "agents";
|
|
22557
|
+
if (objectType === "task_lists")
|
|
22558
|
+
return "task_lists";
|
|
22559
|
+
if (objectType === "templates")
|
|
22560
|
+
return "task_templates";
|
|
22561
|
+
return "task_history";
|
|
22562
|
+
}
|
|
22563
|
+
function listRows(db, table, columns) {
|
|
22564
|
+
return db.query(`SELECT ${columns.join(", ")} FROM ${table} ORDER BY id`).all();
|
|
22565
|
+
}
|
|
22566
|
+
function listProjectMachinePaths(db) {
|
|
22567
|
+
return listRows(db, "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS).map((row) => ({
|
|
22568
|
+
id: String(row.id),
|
|
22569
|
+
project_id: String(row.project_id),
|
|
22570
|
+
machine_id: String(row.machine_id),
|
|
22571
|
+
path: String(row.path),
|
|
22572
|
+
created_at: String(row.created_at),
|
|
22573
|
+
updated_at: String(row.updated_at)
|
|
22574
|
+
}));
|
|
22575
|
+
}
|
|
22576
|
+
function existingClock(db, table, id) {
|
|
22577
|
+
const clockColumns = clockColumnsForTable(table);
|
|
22578
|
+
const row = db.query(`SELECT ${clockColumns.join(", ")} FROM ${table} WHERE id = ?`).get(id);
|
|
22579
|
+
return row?.updated_at ?? row?.last_seen_at ?? row?.created_at ?? null;
|
|
22580
|
+
}
|
|
22581
|
+
function rowClock(row, updateClockColumn) {
|
|
22582
|
+
const value = updateClockColumn ? row[updateClockColumn] : null;
|
|
22583
|
+
return stringClock(value) ?? stringClock(row["updated_at"]) ?? stringClock(row["last_seen_at"]) ?? stringClock(row["created_at"]);
|
|
22584
|
+
}
|
|
22585
|
+
function stringClock(value) {
|
|
22586
|
+
return typeof value === "string" && value ? value : null;
|
|
22587
|
+
}
|
|
22588
|
+
function clockColumnsForTable(table) {
|
|
22589
|
+
if (table === "agents")
|
|
22590
|
+
return ["last_seen_at", "created_at"];
|
|
22591
|
+
if (table === "task_templates")
|
|
22592
|
+
return ["created_at"];
|
|
22593
|
+
if (table === "task_history")
|
|
22594
|
+
return ["created_at"];
|
|
22595
|
+
return ["updated_at", "created_at"];
|
|
22596
|
+
}
|
|
22214
22597
|
|
|
22215
22598
|
// src/storage/local-sqlite.ts
|
|
22216
22599
|
function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
@@ -22377,7 +22760,7 @@ class PostgresTodosSyncStore {
|
|
|
22377
22760
|
}
|
|
22378
22761
|
async pullSnapshot(options = {}) {
|
|
22379
22762
|
const params = [this.service];
|
|
22380
|
-
const filters = ["service = $1"
|
|
22763
|
+
const filters = ["service = $1"];
|
|
22381
22764
|
if (options.since) {
|
|
22382
22765
|
params.push(options.since);
|
|
22383
22766
|
filters.push(`updated_at > $${params.length}::timestamptz`);
|
|
@@ -22386,7 +22769,7 @@ class PostgresTodosSyncStore {
|
|
|
22386
22769
|
params.push(options.objectTypes);
|
|
22387
22770
|
filters.push(`object_type = ANY($${params.length}::text[])`);
|
|
22388
22771
|
}
|
|
22389
|
-
const response = await this.client.query(`SELECT object_type, payload FROM ${this.tableName}
|
|
22772
|
+
const response = await this.client.query(`SELECT object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version FROM ${this.tableName}
|
|
22390
22773
|
WHERE ${filters.join(" AND ")}
|
|
22391
22774
|
ORDER BY updated_at ASC, object_type ASC, object_id ASC`, params);
|
|
22392
22775
|
return rowsToSnapshot(response.rows);
|
|
@@ -22410,11 +22793,20 @@ function snapshotEntries(snapshot) {
|
|
|
22410
22793
|
return [
|
|
22411
22794
|
...snapshot.tasks.map((payload) => entry("tasks", payload, snapshot.exportedAt)),
|
|
22412
22795
|
...snapshot.projects.map((payload) => entry("projects", payload, snapshot.exportedAt)),
|
|
22796
|
+
...(snapshot.projectMachinePaths ?? []).map((payload) => entry("project_machine_paths", payload, snapshot.exportedAt)),
|
|
22413
22797
|
...snapshot.plans.map((payload) => entry("plans", payload, snapshot.exportedAt)),
|
|
22414
22798
|
...snapshot.agents.map((payload) => entry("agents", payload, snapshot.exportedAt)),
|
|
22415
22799
|
...snapshot.taskLists.map((payload) => entry("task_lists", payload, snapshot.exportedAt)),
|
|
22416
22800
|
...snapshot.templates.map((payload) => entry("templates", payload, snapshot.exportedAt)),
|
|
22417
|
-
...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt))
|
|
22801
|
+
...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt)),
|
|
22802
|
+
...(snapshot.tombstones ?? []).map((tombstone) => ({
|
|
22803
|
+
type: tombstone.object_type,
|
|
22804
|
+
id: tombstone.object_id,
|
|
22805
|
+
payload: tombstone.payload ?? { id: tombstone.object_id, deleted_at: tombstone.deleted_at },
|
|
22806
|
+
updatedAt: tombstone.updated_at || tombstone.deleted_at,
|
|
22807
|
+
deletedAt: tombstone.deleted_at,
|
|
22808
|
+
version: tombstone.version ?? null
|
|
22809
|
+
}))
|
|
22418
22810
|
];
|
|
22419
22811
|
}
|
|
22420
22812
|
function entry(type, payload, fallbackUpdatedAt) {
|
|
@@ -22436,19 +22828,38 @@ function rowsToSnapshot(rows) {
|
|
|
22436
22828
|
source: "postgres",
|
|
22437
22829
|
tasks: [],
|
|
22438
22830
|
projects: [],
|
|
22831
|
+
projectMachinePaths: [],
|
|
22439
22832
|
plans: [],
|
|
22440
22833
|
agents: [],
|
|
22441
22834
|
taskLists: [],
|
|
22442
22835
|
templates: [],
|
|
22443
|
-
auditHistory: []
|
|
22836
|
+
auditHistory: [],
|
|
22837
|
+
tombstones: []
|
|
22444
22838
|
};
|
|
22445
22839
|
for (const row of rows) {
|
|
22446
22840
|
const payload = payloadRecord(row.payload);
|
|
22841
|
+
const deletedAt = stringValue(row.deleted_at);
|
|
22842
|
+
if (deletedAt) {
|
|
22843
|
+
snapshot.tombstones ??= [];
|
|
22844
|
+
snapshot.tombstones.push({
|
|
22845
|
+
object_type: row.object_type,
|
|
22846
|
+
object_id: stringValue(row.object_id) ?? stringValue(payload["id"]) ?? "",
|
|
22847
|
+
deleted_at: deletedAt,
|
|
22848
|
+
updated_at: stringValue(row.updated_at) ?? deletedAt,
|
|
22849
|
+
source_machine_id: stringValue(row.source_machine_id),
|
|
22850
|
+
payload,
|
|
22851
|
+
version: numberValue2(row.version)
|
|
22852
|
+
});
|
|
22853
|
+
continue;
|
|
22854
|
+
}
|
|
22447
22855
|
if (row.object_type === "tasks")
|
|
22448
22856
|
snapshot.tasks.push(payload);
|
|
22449
22857
|
else if (row.object_type === "projects")
|
|
22450
22858
|
snapshot.projects.push(payload);
|
|
22451
|
-
else if (row.object_type === "
|
|
22859
|
+
else if (row.object_type === "project_machine_paths") {
|
|
22860
|
+
snapshot.projectMachinePaths ??= [];
|
|
22861
|
+
snapshot.projectMachinePaths.push(payload);
|
|
22862
|
+
} else if (row.object_type === "plans")
|
|
22452
22863
|
snapshot.plans.push(payload);
|
|
22453
22864
|
else if (row.object_type === "agents")
|
|
22454
22865
|
snapshot.agents.push(payload);
|
|
@@ -22469,6 +22880,8 @@ function payloadRecord(value) {
|
|
|
22469
22880
|
throw new Error("Postgres sync payload must be a JSON object");
|
|
22470
22881
|
}
|
|
22471
22882
|
function stringValue(value) {
|
|
22883
|
+
if (value instanceof Date)
|
|
22884
|
+
return value.toISOString();
|
|
22472
22885
|
return typeof value === "string" && value ? value : null;
|
|
22473
22886
|
}
|
|
22474
22887
|
function numberValue2(value) {
|
|
@@ -22623,6 +23036,9 @@ class PostgresJsonRecordStore {
|
|
|
22623
23036
|
this.tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
|
|
22624
23037
|
this.cursorTableName = options.cursorTableName ?? DEFAULT_TODOS_POSTGRES_CURSOR_TABLE;
|
|
22625
23038
|
}
|
|
23039
|
+
machineId(context) {
|
|
23040
|
+
return context?.requestId ?? this.sourceMachineId ?? null;
|
|
23041
|
+
}
|
|
22626
23042
|
async ensureSchema() {
|
|
22627
23043
|
this.schemaReady ??= (async () => {
|
|
22628
23044
|
for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
|
|
@@ -22654,6 +23070,25 @@ class PostgresJsonRecordStore {
|
|
|
22654
23070
|
updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString()
|
|
22655
23071
|
}));
|
|
22656
23072
|
}
|
|
23073
|
+
async listTombstones() {
|
|
23074
|
+
await this.ensureSchema();
|
|
23075
|
+
const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version
|
|
23076
|
+
FROM ${this.tableName}
|
|
23077
|
+
WHERE service = $1 AND deleted_at IS NOT NULL
|
|
23078
|
+
ORDER BY updated_at ASC, object_type ASC, object_id ASC`, [this.service]);
|
|
23079
|
+
return result.rows.map((row) => {
|
|
23080
|
+
const deletedAt = stringValue2(row.deleted_at) ?? stringValue2(row.updated_at) ?? new Date().toISOString();
|
|
23081
|
+
return {
|
|
23082
|
+
object_type: row.object_type,
|
|
23083
|
+
object_id: row.object_id,
|
|
23084
|
+
deleted_at: deletedAt,
|
|
23085
|
+
updated_at: stringValue2(row.updated_at) ?? deletedAt,
|
|
23086
|
+
source_machine_id: stringValue2(row.source_machine_id),
|
|
23087
|
+
payload: payloadRecord2(row.payload),
|
|
23088
|
+
version: numberValue3(row.version)
|
|
23089
|
+
};
|
|
23090
|
+
});
|
|
23091
|
+
}
|
|
22657
23092
|
async upsert(type, value, context = {}) {
|
|
22658
23093
|
await this.ensureSchema();
|
|
22659
23094
|
const updatedAt = stringValue2(value.updated_at) ?? stringValue2(value.created_at) ?? new Date().toISOString();
|
|
@@ -22666,7 +23101,8 @@ class PostgresJsonRecordStore {
|
|
|
22666
23101
|
updated_at = EXCLUDED.updated_at,
|
|
22667
23102
|
deleted_at = NULL,
|
|
22668
23103
|
source_machine_id = EXCLUDED.source_machine_id,
|
|
22669
|
-
version = EXCLUDED.version
|
|
23104
|
+
version = EXCLUDED.version
|
|
23105
|
+
WHERE ${this.tableName}.updated_at IS NULL OR ${this.tableName}.updated_at <= EXCLUDED.updated_at`, [
|
|
22670
23106
|
this.service,
|
|
22671
23107
|
type,
|
|
22672
23108
|
value.id,
|
|
@@ -22683,13 +23119,58 @@ class PostgresJsonRecordStore {
|
|
|
22683
23119
|
if (!existing)
|
|
22684
23120
|
return false;
|
|
22685
23121
|
const timestamp2 = new Date().toISOString();
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
23122
|
+
return this.tombstone({
|
|
23123
|
+
object_type: type,
|
|
23124
|
+
object_id: id,
|
|
23125
|
+
deleted_at: timestamp2,
|
|
23126
|
+
updated_at: timestamp2,
|
|
23127
|
+
payload: existing,
|
|
23128
|
+
version: numberValue3(existing["version"])
|
|
23129
|
+
}, context);
|
|
23130
|
+
}
|
|
23131
|
+
async tombstone(tombstone, context = {}) {
|
|
23132
|
+
await this.ensureSchema();
|
|
23133
|
+
const deletedAt = stringValue2(tombstone.deleted_at) ?? new Date().toISOString();
|
|
23134
|
+
const updatedAt = stringValue2(tombstone.updated_at) ?? deletedAt;
|
|
23135
|
+
const existing = await this.clock(tombstone.object_type, tombstone.object_id);
|
|
23136
|
+
if (existing && compareClock(existing.updatedAt, updatedAt) > 0)
|
|
23137
|
+
return false;
|
|
23138
|
+
await this.options.client.query(`INSERT INTO ${this.tableName} (
|
|
23139
|
+
service, object_type, object_id, payload, updated_at,
|
|
23140
|
+
deleted_at, source_machine_id, version
|
|
23141
|
+
) VALUES ($1, $2, $3, $4::jsonb, $5::timestamptz, $6::timestamptz, $7, $8)
|
|
23142
|
+
ON CONFLICT (service, object_type, object_id) DO UPDATE SET
|
|
23143
|
+
payload = EXCLUDED.payload,
|
|
23144
|
+
updated_at = EXCLUDED.updated_at,
|
|
23145
|
+
deleted_at = EXCLUDED.deleted_at,
|
|
23146
|
+
source_machine_id = EXCLUDED.source_machine_id,
|
|
23147
|
+
version = EXCLUDED.version
|
|
23148
|
+
WHERE ${this.tableName}.updated_at IS NULL OR ${this.tableName}.updated_at <= EXCLUDED.updated_at`, [
|
|
23149
|
+
this.service,
|
|
23150
|
+
tombstone.object_type,
|
|
23151
|
+
tombstone.object_id,
|
|
23152
|
+
JSON.stringify(tombstone.payload ?? { id: tombstone.object_id, deleted_at: deletedAt }),
|
|
23153
|
+
updatedAt,
|
|
23154
|
+
deletedAt,
|
|
23155
|
+
tombstone.source_machine_id ?? context.requestId ?? this.sourceMachineId ?? null,
|
|
23156
|
+
tombstone.version ?? null
|
|
23157
|
+
]);
|
|
22691
23158
|
return true;
|
|
22692
23159
|
}
|
|
23160
|
+
async clock(type, id) {
|
|
23161
|
+
await this.ensureSchema();
|
|
23162
|
+
const result = await this.options.client.query(`SELECT object_type, object_id, updated_at, deleted_at
|
|
23163
|
+
FROM ${this.tableName}
|
|
23164
|
+
WHERE service = $1 AND object_type = $2 AND object_id = $3
|
|
23165
|
+
LIMIT 1`, [this.service, type, id]);
|
|
23166
|
+
const row = result.rows[0];
|
|
23167
|
+
if (!row)
|
|
23168
|
+
return null;
|
|
23169
|
+
return {
|
|
23170
|
+
updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString(),
|
|
23171
|
+
deletedAt: stringValue2(row.deleted_at)
|
|
23172
|
+
};
|
|
23173
|
+
}
|
|
22693
23174
|
async getCursor(name) {
|
|
22694
23175
|
await this.ensureSchema();
|
|
22695
23176
|
const result = await this.options.client.query(`SELECT value FROM ${this.cursorTableName} WHERE service = $1 AND cursor_name = $2`, [this.service, name]);
|
|
@@ -22758,7 +23239,10 @@ async function createTask2(input, store, context) {
|
|
|
22758
23239
|
runner_started_at: null,
|
|
22759
23240
|
runner_completed_at: null,
|
|
22760
23241
|
current_step: null,
|
|
22761
|
-
total_steps: null
|
|
23242
|
+
total_steps: null,
|
|
23243
|
+
machine_id: store.machineId(context),
|
|
23244
|
+
synced_at: null,
|
|
23245
|
+
archived_at: null
|
|
22762
23246
|
};
|
|
22763
23247
|
await store.upsert("tasks", task2, context);
|
|
22764
23248
|
await logTaskChange2(task2.id, "created", "status", null, task2.status, task2.assigned_by ?? task2.agent_id, store, context);
|
|
@@ -22883,7 +23367,9 @@ async function createProject2(input, store, context) {
|
|
|
22883
23367
|
task_prefix: input.task_prefix ?? await generateProjectPrefix(input.name, store),
|
|
22884
23368
|
task_counter: 0,
|
|
22885
23369
|
created_at: timestamp2,
|
|
22886
|
-
updated_at: timestamp2
|
|
23370
|
+
updated_at: timestamp2,
|
|
23371
|
+
machine_id: store.machineId(context),
|
|
23372
|
+
synced_at: null
|
|
22887
23373
|
};
|
|
22888
23374
|
return store.upsert("projects", project, context);
|
|
22889
23375
|
}
|
|
@@ -22903,7 +23389,9 @@ async function createPlan2(input, store, context) {
|
|
|
22903
23389
|
description: input.description ?? null,
|
|
22904
23390
|
status: input.status ?? "active",
|
|
22905
23391
|
created_at: timestamp2,
|
|
22906
|
-
updated_at: timestamp2
|
|
23392
|
+
updated_at: timestamp2,
|
|
23393
|
+
machine_id: store.machineId(context),
|
|
23394
|
+
synced_at: null
|
|
22907
23395
|
}, context);
|
|
22908
23396
|
}
|
|
22909
23397
|
async function updatePlan2(id, input, store) {
|
|
@@ -22933,7 +23421,9 @@ async function registerAgent2(input, store, context) {
|
|
|
22933
23421
|
last_seen_at: timestamp2,
|
|
22934
23422
|
session_id: input.session_id ?? context?.sessionId ?? existing?.session_id ?? null,
|
|
22935
23423
|
working_dir: input.working_dir ?? existing?.working_dir ?? null,
|
|
22936
|
-
active_project_id: input.project_id ?? context?.projectId ?? existing?.active_project_id ?? null
|
|
23424
|
+
active_project_id: input.project_id ?? context?.projectId ?? existing?.active_project_id ?? null,
|
|
23425
|
+
machine_id: existing?.machine_id ?? store.machineId(context),
|
|
23426
|
+
synced_at: existing?.synced_at ?? null
|
|
22937
23427
|
};
|
|
22938
23428
|
return store.upsert("agents", agent, context);
|
|
22939
23429
|
}
|
|
@@ -22960,7 +23450,9 @@ async function createTaskList2(input, store, context) {
|
|
|
22960
23450
|
description: input.description ?? null,
|
|
22961
23451
|
metadata: input.metadata ?? {},
|
|
22962
23452
|
created_at: timestamp2,
|
|
22963
|
-
updated_at: timestamp2
|
|
23453
|
+
updated_at: timestamp2,
|
|
23454
|
+
machine_id: store.machineId(context),
|
|
23455
|
+
synced_at: null
|
|
22964
23456
|
}, context);
|
|
22965
23457
|
}
|
|
22966
23458
|
async function updateTaskList2(id, input, store) {
|
|
@@ -22986,7 +23478,9 @@ async function createTemplate2(input, store, context) {
|
|
|
22986
23478
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
22987
23479
|
plan_id: input.plan_id ?? null,
|
|
22988
23480
|
metadata: input.metadata ?? {},
|
|
22989
|
-
created_at: timestamp2
|
|
23481
|
+
created_at: timestamp2,
|
|
23482
|
+
machine_id: store.machineId(context),
|
|
23483
|
+
synced_at: null
|
|
22990
23484
|
}, context);
|
|
22991
23485
|
}
|
|
22992
23486
|
async function updateTemplate2(id, input, store) {
|
|
@@ -23011,7 +23505,8 @@ async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentI
|
|
|
23011
23505
|
old_value: oldValue ?? null,
|
|
23012
23506
|
new_value: newValue ?? null,
|
|
23013
23507
|
agent_id: agentId ?? context?.agentId ?? null,
|
|
23014
|
-
created_at: new Date().toISOString()
|
|
23508
|
+
created_at: new Date().toISOString(),
|
|
23509
|
+
machine_id: store.machineId(context)
|
|
23015
23510
|
};
|
|
23016
23511
|
return store.upsert("audit_history", entry2, context);
|
|
23017
23512
|
}
|
|
@@ -23034,18 +23529,21 @@ async function exportSnapshot(store) {
|
|
|
23034
23529
|
source: "postgres",
|
|
23035
23530
|
tasks: await store.list("tasks"),
|
|
23036
23531
|
projects: await store.list("projects"),
|
|
23532
|
+
projectMachinePaths: await store.list("project_machine_paths"),
|
|
23037
23533
|
plans: await store.list("plans"),
|
|
23038
23534
|
agents: await store.list("agents"),
|
|
23039
23535
|
taskLists: await store.list("task_lists"),
|
|
23040
23536
|
templates: await store.list("templates"),
|
|
23041
|
-
auditHistory: await store.list("audit_history")
|
|
23537
|
+
auditHistory: await store.list("audit_history"),
|
|
23538
|
+
tombstones: await store.listTombstones()
|
|
23042
23539
|
};
|
|
23043
23540
|
}
|
|
23044
23541
|
async function importSnapshot(snapshot, store, context) {
|
|
23045
|
-
const result = { inserted: 0, updated: 0, skipped: 0, errors: [] };
|
|
23542
|
+
const result = { inserted: 0, updated: 0, deleted: 0, skipped: 0, errors: [] };
|
|
23046
23543
|
const entries = [
|
|
23047
23544
|
...snapshot.tasks.map((row) => ["tasks", row]),
|
|
23048
23545
|
...snapshot.projects.map((row) => ["projects", row]),
|
|
23546
|
+
...(snapshot.projectMachinePaths ?? []).map((row) => ["project_machine_paths", row]),
|
|
23049
23547
|
...snapshot.plans.map((row) => ["plans", row]),
|
|
23050
23548
|
...snapshot.agents.map((row) => ["agents", row]),
|
|
23051
23549
|
...snapshot.taskLists.map((row) => ["task_lists", row]),
|
|
@@ -23064,6 +23562,25 @@ async function importSnapshot(snapshot, store, context) {
|
|
|
23064
23562
|
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
23065
23563
|
}
|
|
23066
23564
|
}
|
|
23565
|
+
for (const tombstone of snapshot.tombstones ?? []) {
|
|
23566
|
+
try {
|
|
23567
|
+
const deleted = await store.tombstone({
|
|
23568
|
+
object_type: tombstone.object_type,
|
|
23569
|
+
object_id: tombstone.object_id,
|
|
23570
|
+
deleted_at: tombstone.deleted_at,
|
|
23571
|
+
updated_at: tombstone.updated_at,
|
|
23572
|
+
source_machine_id: tombstone.source_machine_id ?? null,
|
|
23573
|
+
payload: tombstone.payload ?? null,
|
|
23574
|
+
version: tombstone.version ?? null
|
|
23575
|
+
}, context);
|
|
23576
|
+
if (deleted)
|
|
23577
|
+
result.deleted = (result.deleted ?? 0) + 1;
|
|
23578
|
+
else
|
|
23579
|
+
result.skipped += 1;
|
|
23580
|
+
} catch (error) {
|
|
23581
|
+
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
23582
|
+
}
|
|
23583
|
+
}
|
|
23067
23584
|
return result;
|
|
23068
23585
|
}
|
|
23069
23586
|
async function requireRecord(type, id, store) {
|
|
@@ -23146,8 +23663,17 @@ function payloadRecord2(value) {
|
|
|
23146
23663
|
throw new Error("Postgres storage payload must be a JSON object");
|
|
23147
23664
|
}
|
|
23148
23665
|
function stringValue2(value) {
|
|
23666
|
+
if (value instanceof Date)
|
|
23667
|
+
return value.toISOString();
|
|
23149
23668
|
return typeof value === "string" && value ? value : null;
|
|
23150
23669
|
}
|
|
23670
|
+
function compareClock(left, right) {
|
|
23671
|
+
const leftClock = Date.parse(left);
|
|
23672
|
+
const rightClock = Date.parse(right);
|
|
23673
|
+
if (Number.isNaN(leftClock) || Number.isNaN(rightClock))
|
|
23674
|
+
return left.localeCompare(right);
|
|
23675
|
+
return leftClock - rightClock;
|
|
23676
|
+
}
|
|
23151
23677
|
function numberValue3(value) {
|
|
23152
23678
|
return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
|
|
23153
23679
|
}
|
|
@@ -24082,7 +24608,7 @@ class TodosClient {
|
|
|
24082
24608
|
return this._fetchWithRetry(path, { method: "DELETE" });
|
|
24083
24609
|
}
|
|
24084
24610
|
_sleep(ms) {
|
|
24085
|
-
return new Promise((
|
|
24611
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
24086
24612
|
}
|
|
24087
24613
|
async getHealth() {
|
|
24088
24614
|
return this._get("/api/health");
|
|
@@ -25464,7 +25990,7 @@ function renderRetrospectiveMarkdown(record) {
|
|
|
25464
25990
|
// src/lib/project-bootstrap.ts
|
|
25465
25991
|
init_database();
|
|
25466
25992
|
import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
|
|
25467
|
-
import { basename as basename2, dirname as dirname7, resolve as
|
|
25993
|
+
import { basename as basename2, dirname as dirname7, resolve as resolve10 } from "path";
|
|
25468
25994
|
function safeStat(path) {
|
|
25469
25995
|
try {
|
|
25470
25996
|
return statSync3(path);
|
|
@@ -25473,7 +25999,7 @@ function safeStat(path) {
|
|
|
25473
25999
|
}
|
|
25474
26000
|
}
|
|
25475
26001
|
function canonicalPath(input) {
|
|
25476
|
-
const resolved =
|
|
26002
|
+
const resolved = resolve10(input);
|
|
25477
26003
|
const stats2 = safeStat(resolved);
|
|
25478
26004
|
if (stats2?.isFile())
|
|
25479
26005
|
return dirname7(resolved);
|
|
@@ -25482,7 +26008,7 @@ function canonicalPath(input) {
|
|
|
25482
26008
|
function findUp(start, marker) {
|
|
25483
26009
|
let current = canonicalPath(start);
|
|
25484
26010
|
while (true) {
|
|
25485
|
-
if (existsSync9(
|
|
26011
|
+
if (existsSync9(resolve10(current, marker)))
|
|
25486
26012
|
return current;
|
|
25487
26013
|
const parent = dirname7(current);
|
|
25488
26014
|
if (parent === current)
|
|
@@ -25493,7 +26019,7 @@ function findUp(start, marker) {
|
|
|
25493
26019
|
function readPackageJson2(path) {
|
|
25494
26020
|
if (!path)
|
|
25495
26021
|
return null;
|
|
25496
|
-
const file =
|
|
26022
|
+
const file = resolve10(path, "package.json");
|
|
25497
26023
|
if (!existsSync9(file))
|
|
25498
26024
|
return null;
|
|
25499
26025
|
try {
|
|
@@ -25516,7 +26042,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
25516
26042
|
if (rootPackage?.workspaces)
|
|
25517
26043
|
markers.push("package.json#workspaces");
|
|
25518
26044
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
25519
|
-
if (existsSync9(
|
|
26045
|
+
if (existsSync9(resolve10(root, marker)))
|
|
25520
26046
|
markers.push(marker);
|
|
25521
26047
|
}
|
|
25522
26048
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -25578,7 +26104,19 @@ function bootstrapProject(options = {}, db) {
|
|
|
25578
26104
|
}
|
|
25579
26105
|
setMachineLocalPath(project.id, discovery.projectPath, d);
|
|
25580
26106
|
const beforeTaskList = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(project.id, taskListSlug);
|
|
25581
|
-
|
|
26107
|
+
let taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
|
|
26108
|
+
if (options.routeEnabled && taskList.metadata.route_enabled !== true) {
|
|
26109
|
+
taskList = updateTaskList(taskList.id, {
|
|
26110
|
+
metadata: {
|
|
26111
|
+
...taskList.metadata,
|
|
26112
|
+
route_enabled: true,
|
|
26113
|
+
automation: {
|
|
26114
|
+
...taskList.metadata.automation && typeof taskList.metadata.automation === "object" && !Array.isArray(taskList.metadata.automation) ? taskList.metadata.automation : {},
|
|
26115
|
+
no_auto: false
|
|
26116
|
+
}
|
|
26117
|
+
}
|
|
26118
|
+
}, d);
|
|
26119
|
+
}
|
|
25582
26120
|
const createdSources = [];
|
|
25583
26121
|
for (const source9 of [
|
|
25584
26122
|
addSourceOnce(project.id, "local", "Project root", discovery.projectPath, { role: "project-root" }, d),
|
|
@@ -25622,6 +26160,174 @@ function getProjectByPathForBootstrap(path, db) {
|
|
|
25622
26160
|
WHERE pmp.path = ?`).get(path);
|
|
25623
26161
|
return machineRow ?? null;
|
|
25624
26162
|
}
|
|
26163
|
+
// src/lib/task-routing.ts
|
|
26164
|
+
init_database();
|
|
26165
|
+
function classifyProjectKind2(path) {
|
|
26166
|
+
if (!path)
|
|
26167
|
+
return null;
|
|
26168
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
26169
|
+
}
|
|
26170
|
+
function machineLocalPath(project, db) {
|
|
26171
|
+
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
26172
|
+
if (!machineId)
|
|
26173
|
+
return null;
|
|
26174
|
+
try {
|
|
26175
|
+
const row = db.query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
|
|
26176
|
+
return row?.path ?? null;
|
|
26177
|
+
} catch {
|
|
26178
|
+
return null;
|
|
26179
|
+
}
|
|
26180
|
+
}
|
|
26181
|
+
function resolveProject(task2, db) {
|
|
26182
|
+
const project = task2.project_id ? getProject(task2.project_id, db) : null;
|
|
26183
|
+
const projectPath = project ? machineLocalPath(project, db) ?? project.path : task2.working_dir;
|
|
26184
|
+
return { project, projectPath: projectPath ?? null };
|
|
26185
|
+
}
|
|
26186
|
+
function resolveTaskList(task2, project, db) {
|
|
26187
|
+
if (task2.task_list_id) {
|
|
26188
|
+
return getTaskList(task2.task_list_id, db) ?? (project ? getTaskListBySlug(task2.task_list_id, project.id, db) : null);
|
|
26189
|
+
}
|
|
26190
|
+
if (project?.task_list_id) {
|
|
26191
|
+
return getTaskListBySlug(project.task_list_id, project.id, db);
|
|
26192
|
+
}
|
|
26193
|
+
return null;
|
|
26194
|
+
}
|
|
26195
|
+
function isTerminal2(status) {
|
|
26196
|
+
return status === "completed" || status === "cancelled" || status === "failed";
|
|
26197
|
+
}
|
|
26198
|
+
function routeConcurrencyKey(task2, project, taskList, projectPath) {
|
|
26199
|
+
if (project?.id)
|
|
26200
|
+
return `project:${project.id}`;
|
|
26201
|
+
if (taskList?.id)
|
|
26202
|
+
return `task-list:${taskList.id}`;
|
|
26203
|
+
if (projectPath)
|
|
26204
|
+
return `path:${projectPath}`;
|
|
26205
|
+
return `task:${task2.id}`;
|
|
26206
|
+
}
|
|
26207
|
+
function getTaskRouteState(taskOrId, db) {
|
|
26208
|
+
const d = db || getDatabase();
|
|
26209
|
+
const task2 = typeof taskOrId === "string" ? getTask(taskOrId, d) : taskOrId;
|
|
26210
|
+
if (!task2)
|
|
26211
|
+
throw new Error(`Task not found: ${taskOrId}`);
|
|
26212
|
+
const { project, projectPath } = resolveProject(task2, d);
|
|
26213
|
+
const taskList = resolveTaskList(task2, project, d);
|
|
26214
|
+
const automation = routingAutomationMetadata(task2, taskList) ?? {};
|
|
26215
|
+
const routeEnabled = routeEnabledForTask(task2, taskList) === true;
|
|
26216
|
+
const tagOptIn = task2.tags.includes("auto:route") || task2.tags.includes("route:enabled");
|
|
26217
|
+
const locked = Boolean(task2.locked_by && !isLockExpired(task2.locked_at));
|
|
26218
|
+
const blockers = getBlockingDeps(task2.id, d);
|
|
26219
|
+
const blocked = blockers.length > 0;
|
|
26220
|
+
const terminal = isTerminal2(task2.status);
|
|
26221
|
+
const requiresApproval = automation.requires_approval === true || task2.requires_approval === true;
|
|
26222
|
+
const approvalRequired = automation.approval_required === true;
|
|
26223
|
+
const approved = Boolean(task2.approved_by);
|
|
26224
|
+
const gates = {
|
|
26225
|
+
route_enabled: routeEnabled,
|
|
26226
|
+
tag_opt_in: tagOptIn,
|
|
26227
|
+
no_auto: automation.no_auto === true,
|
|
26228
|
+
manual: automation.manual === true,
|
|
26229
|
+
manual_required: automation.manual_required === true,
|
|
26230
|
+
requires_approval: requiresApproval,
|
|
26231
|
+
approval_required: approvalRequired,
|
|
26232
|
+
approved,
|
|
26233
|
+
locked,
|
|
26234
|
+
blocked,
|
|
26235
|
+
terminal
|
|
26236
|
+
};
|
|
26237
|
+
const reasons = [];
|
|
26238
|
+
if (task2.status !== "pending")
|
|
26239
|
+
reasons.push("task_not_pending");
|
|
26240
|
+
if (terminal)
|
|
26241
|
+
reasons.push("task_terminal");
|
|
26242
|
+
if (!routeEnabled)
|
|
26243
|
+
reasons.push("route_not_enabled");
|
|
26244
|
+
if (locked)
|
|
26245
|
+
reasons.push("task_locked");
|
|
26246
|
+
if (blocked)
|
|
26247
|
+
reasons.push("task_blocked");
|
|
26248
|
+
if (gates.no_auto)
|
|
26249
|
+
reasons.push("no_auto");
|
|
26250
|
+
if (gates.manual)
|
|
26251
|
+
reasons.push("manual");
|
|
26252
|
+
if (gates.manual_required)
|
|
26253
|
+
reasons.push("manual_required");
|
|
26254
|
+
if (requiresApproval && !approved)
|
|
26255
|
+
reasons.push("requires_approval");
|
|
26256
|
+
if (approvalRequired && !approved)
|
|
26257
|
+
reasons.push("approval_required");
|
|
26258
|
+
if (automation.allowed === false)
|
|
26259
|
+
reasons.push("automation_disallowed");
|
|
26260
|
+
return {
|
|
26261
|
+
schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
|
|
26262
|
+
task_id: task2.id,
|
|
26263
|
+
task_short_id: task2.short_id,
|
|
26264
|
+
status: task2.status,
|
|
26265
|
+
eligible: reasons.length === 0,
|
|
26266
|
+
reasons,
|
|
26267
|
+
blockers: blockers.map((blocker) => ({
|
|
26268
|
+
id: blocker.id,
|
|
26269
|
+
short_id: blocker.short_id,
|
|
26270
|
+
title: blocker.title,
|
|
26271
|
+
status: blocker.status
|
|
26272
|
+
})),
|
|
26273
|
+
gates,
|
|
26274
|
+
automation: Object.keys(automation).length > 0 ? automation : null,
|
|
26275
|
+
route: {
|
|
26276
|
+
project_id: project?.id ?? task2.project_id,
|
|
26277
|
+
project_path: projectPath,
|
|
26278
|
+
working_dir: task2.working_dir ?? projectPath,
|
|
26279
|
+
project_kind: classifyProjectKind2(projectPath),
|
|
26280
|
+
task_list_id: taskList?.id ?? task2.task_list_id,
|
|
26281
|
+
task_list_slug: taskList?.slug ?? null,
|
|
26282
|
+
task_list_name: taskList?.name ?? null,
|
|
26283
|
+
concurrency_key: routeConcurrencyKey(task2, project, taskList, projectPath)
|
|
26284
|
+
},
|
|
26285
|
+
pointers: workflowPointersFromMetadata(task2.metadata)
|
|
26286
|
+
};
|
|
26287
|
+
}
|
|
26288
|
+
function setTaskWorkflowPointers(taskId, input, db) {
|
|
26289
|
+
const d = db || getDatabase();
|
|
26290
|
+
const task2 = getTask(taskId, d);
|
|
26291
|
+
if (!task2)
|
|
26292
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
26293
|
+
const previous = workflowPointersFromMetadata(task2.metadata);
|
|
26294
|
+
const next = compactWorkflowPointers({
|
|
26295
|
+
current_workflow_invocation_id: pointerPatch(previous.current_workflow_invocation_id, input, "current_workflow_invocation_id"),
|
|
26296
|
+
current_run_id: pointerPatch(previous.current_run_id, input, "current_run_id"),
|
|
26297
|
+
latest_manifest_path: pointerPatch(previous.latest_manifest_path, input, "latest_manifest_path"),
|
|
26298
|
+
latest_evaluation_path: pointerPatch(previous.latest_evaluation_path, input, "latest_evaluation_path"),
|
|
26299
|
+
workflow_state: pointerPatch(previous.workflow_state, input, "workflow_state")
|
|
26300
|
+
});
|
|
26301
|
+
const timestamp2 = now();
|
|
26302
|
+
const {
|
|
26303
|
+
current_workflow_invocation_id,
|
|
26304
|
+
current_run_id,
|
|
26305
|
+
latest_manifest_path,
|
|
26306
|
+
latest_evaluation_path,
|
|
26307
|
+
workflow_state,
|
|
26308
|
+
workflow_invocation,
|
|
26309
|
+
...baseMetadata
|
|
26310
|
+
} = task2.metadata;
|
|
26311
|
+
const metadata = {
|
|
26312
|
+
...baseMetadata,
|
|
26313
|
+
...next,
|
|
26314
|
+
workflow_invocation: {
|
|
26315
|
+
schema_version: TASK_WORKFLOW_POINTER_SCHEMA_VERSION,
|
|
26316
|
+
...next,
|
|
26317
|
+
updated_at: timestamp2,
|
|
26318
|
+
updated_by: input.actor ?? null
|
|
26319
|
+
}
|
|
26320
|
+
};
|
|
26321
|
+
return updateTask(task2.id, { version: task2.version, metadata }, d);
|
|
26322
|
+
}
|
|
26323
|
+
function pointerPatch(previous, input, key) {
|
|
26324
|
+
if (!Object.prototype.hasOwnProperty.call(input, key))
|
|
26325
|
+
return previous;
|
|
26326
|
+
const value = input[key];
|
|
26327
|
+
if (value === undefined)
|
|
26328
|
+
return previous;
|
|
26329
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
26330
|
+
}
|
|
25625
26331
|
// src/db/api-keys.ts
|
|
25626
26332
|
init_database();
|
|
25627
26333
|
import { createHash as createHash10, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
@@ -26687,7 +27393,7 @@ function classifyLog(text) {
|
|
|
26687
27393
|
async function sleep2(ms) {
|
|
26688
27394
|
if (ms <= 0)
|
|
26689
27395
|
return;
|
|
26690
|
-
await new Promise((
|
|
27396
|
+
await new Promise((resolve11) => setTimeout(resolve11, ms));
|
|
26691
27397
|
}
|
|
26692
27398
|
async function runCommandProvider(provider, input) {
|
|
26693
27399
|
const commandTemplate = input.command || provider.command;
|
|
@@ -27043,11 +27749,11 @@ function writeVerificationExport(bundle, path) {
|
|
|
27043
27749
|
writeFileSync7(path, JSON.stringify(bundle, null, 2), "utf8");
|
|
27044
27750
|
}
|
|
27045
27751
|
// src/lib/policy-packs.ts
|
|
27046
|
-
import { relative as relative3, resolve as
|
|
27752
|
+
import { relative as relative3, resolve as resolve11 } from "path";
|
|
27047
27753
|
init_database();
|
|
27048
27754
|
init_config();
|
|
27049
27755
|
function normalizePath3(path) {
|
|
27050
|
-
return
|
|
27756
|
+
return resolve11(path);
|
|
27051
27757
|
}
|
|
27052
27758
|
function unique4(values) {
|
|
27053
27759
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -27102,7 +27808,7 @@ function commandMatches(commands, pattern) {
|
|
|
27102
27808
|
}
|
|
27103
27809
|
function pathMatches(paths, pattern, root) {
|
|
27104
27810
|
return paths.filter((path) => {
|
|
27105
|
-
const candidate = path.startsWith("/") ? path :
|
|
27811
|
+
const candidate = path.startsWith("/") ? path : resolve11(root, path);
|
|
27106
27812
|
if (!isPathInside3(root, candidate))
|
|
27107
27813
|
return matchesPattern3(path, pattern);
|
|
27108
27814
|
return matchesPattern3(path, pattern) || matchesPattern3(relative3(root, candidate), pattern);
|
|
@@ -27814,7 +28520,7 @@ function resetAgentAdapterCache() {}
|
|
|
27814
28520
|
// src/lib/git-traceability.ts
|
|
27815
28521
|
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
27816
28522
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
27817
|
-
import { resolve as
|
|
28523
|
+
import { resolve as resolve12 } from "path";
|
|
27818
28524
|
var GIT_TRACEABILITY_SCHEMA_VERSION = "todos.git_traceability.v1";
|
|
27819
28525
|
function runGit(args, cwd) {
|
|
27820
28526
|
const result = spawnSync2("git", args, { cwd, encoding: "utf8" });
|
|
@@ -27857,7 +28563,7 @@ function inspectGitCommit(sha, cwd) {
|
|
|
27857
28563
|
};
|
|
27858
28564
|
}
|
|
27859
28565
|
function loadCiSnapshot(path) {
|
|
27860
|
-
const target = path ?
|
|
28566
|
+
const target = path ? resolve12(path) : resolve12(process.cwd(), ".todos", "ci-snapshot.json");
|
|
27861
28567
|
if (!existsSync13(target))
|
|
27862
28568
|
return null;
|
|
27863
28569
|
try {
|
|
@@ -27955,7 +28661,7 @@ function formatTraceabilityReport(report) {
|
|
|
27955
28661
|
}
|
|
27956
28662
|
// src/lib/mention-resolver.ts
|
|
27957
28663
|
import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
|
|
27958
|
-
import { basename as basename3, isAbsolute, join as join12, relative as relative4, resolve as
|
|
28664
|
+
import { basename as basename3, isAbsolute, join as join12, relative as relative4, resolve as resolve13, sep as sep2 } from "path";
|
|
27959
28665
|
init_database();
|
|
27960
28666
|
var PREFIXES = {
|
|
27961
28667
|
file: "file",
|
|
@@ -28031,11 +28737,11 @@ function backlink(kind, key, label, target = key) {
|
|
|
28031
28737
|
return { kind, key, label, target };
|
|
28032
28738
|
}
|
|
28033
28739
|
function normalizeWorkspace(workspace) {
|
|
28034
|
-
return
|
|
28740
|
+
return resolve13(workspace || process.cwd());
|
|
28035
28741
|
}
|
|
28036
28742
|
function isInside(root, absolutePath) {
|
|
28037
28743
|
const rel = relative4(root, absolutePath);
|
|
28038
|
-
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${
|
|
28744
|
+
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep2}`) && !isAbsolute(rel);
|
|
28039
28745
|
}
|
|
28040
28746
|
function normalizeRelativePath(value) {
|
|
28041
28747
|
const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
|
|
@@ -28099,7 +28805,7 @@ function resolveFile(parsed, workspace) {
|
|
|
28099
28805
|
resolution.warnings.push("path is empty or escapes the workspace");
|
|
28100
28806
|
return resolution;
|
|
28101
28807
|
}
|
|
28102
|
-
const absolutePath =
|
|
28808
|
+
const absolutePath = resolve13(workspace, relPath);
|
|
28103
28809
|
if (!isInside(workspace, absolutePath)) {
|
|
28104
28810
|
resolution.path = relPath;
|
|
28105
28811
|
resolution.warnings.push("path escapes the workspace");
|
|
@@ -29035,7 +29741,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
|
|
|
29035
29741
|
if (action === "approved" || action === "rejected" || action === "expired") {
|
|
29036
29742
|
emitLocalEventHooksQuiet({
|
|
29037
29743
|
type: "approval.decided",
|
|
29038
|
-
payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id }
|
|
29744
|
+
payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
|
|
29745
|
+
databasePath: databasePathFromDatabase(db)
|
|
29039
29746
|
});
|
|
29040
29747
|
}
|
|
29041
29748
|
}
|
|
@@ -31685,14 +32392,14 @@ function renderReleaseNotesMarkdown(document) {
|
|
|
31685
32392
|
init_database();
|
|
31686
32393
|
init_migrations();
|
|
31687
32394
|
import { existsSync as existsSync19, copyFileSync, mkdirSync as mkdirSync12, readFileSync as readFileSync16, statSync as statSync6, writeFileSync as writeFileSync10, unlinkSync } from "fs";
|
|
31688
|
-
import { dirname as dirname11, join as join15, resolve as
|
|
32395
|
+
import { dirname as dirname11, join as join15, resolve as resolve14 } from "path";
|
|
31689
32396
|
import { Database as Database3 } from "bun:sqlite";
|
|
31690
32397
|
var DB_BACKUP_SCHEMA = "todos.db_backup.v1";
|
|
31691
32398
|
function resolveDbPath(dbPath) {
|
|
31692
32399
|
if (dbPath)
|
|
31693
|
-
return
|
|
32400
|
+
return resolve14(dbPath);
|
|
31694
32401
|
if (process.env["TODOS_DB_PATH"] && process.env["TODOS_DB_PATH"] !== ":memory:") {
|
|
31695
|
-
return
|
|
32402
|
+
return resolve14(process.env["TODOS_DB_PATH"]);
|
|
31696
32403
|
}
|
|
31697
32404
|
const db = getDatabase();
|
|
31698
32405
|
const filename = db.filename;
|
|
@@ -31731,7 +32438,7 @@ function restoreDatabase(backupPath, targetPath) {
|
|
|
31731
32438
|
if (!integrity.ok) {
|
|
31732
32439
|
throw new Error(`Backup failed integrity check: ${integrity.errors.join("; ")}`);
|
|
31733
32440
|
}
|
|
31734
|
-
const target = targetPath ?
|
|
32441
|
+
const target = targetPath ? resolve14(targetPath) : resolveDbPath();
|
|
31735
32442
|
mkdirSync12(dirname11(target), { recursive: true });
|
|
31736
32443
|
const staging = `${target}.restore.tmp`;
|
|
31737
32444
|
copyFileSync(backupPath, staging);
|
|
@@ -31750,7 +32457,7 @@ function restoreDatabase(backupPath, targetPath) {
|
|
|
31750
32457
|
};
|
|
31751
32458
|
}
|
|
31752
32459
|
function checkDatabaseIntegrity(dbPath) {
|
|
31753
|
-
const path = dbPath ?
|
|
32460
|
+
const path = dbPath ? resolve14(dbPath) : resolveDbPath();
|
|
31754
32461
|
const errors = [];
|
|
31755
32462
|
if (!existsSync19(path)) {
|
|
31756
32463
|
return {
|
|
@@ -31815,7 +32522,7 @@ function checkDatabaseIntegrity(dbPath) {
|
|
|
31815
32522
|
};
|
|
31816
32523
|
}
|
|
31817
32524
|
function compactDatabase(dbPath) {
|
|
31818
|
-
const path = dbPath ?
|
|
32525
|
+
const path = dbPath ? resolve14(dbPath) : resolveDbPath();
|
|
31819
32526
|
const before = statSync6(path).size;
|
|
31820
32527
|
const db = new Database3(path);
|
|
31821
32528
|
db.exec("VACUUM");
|
|
@@ -31825,7 +32532,7 @@ function compactDatabase(dbPath) {
|
|
|
31825
32532
|
return { path, bytes_before: before, bytes_after: after };
|
|
31826
32533
|
}
|
|
31827
32534
|
function migrationDryRun(dbPath) {
|
|
31828
|
-
const path = dbPath ?
|
|
32535
|
+
const path = dbPath ? resolve14(dbPath) : resolveDbPath();
|
|
31829
32536
|
const db = new Database3(path, { readonly: true });
|
|
31830
32537
|
let current = 0;
|
|
31831
32538
|
try {
|
|
@@ -31849,7 +32556,7 @@ function migrationDryRun(dbPath) {
|
|
|
31849
32556
|
};
|
|
31850
32557
|
}
|
|
31851
32558
|
function defaultBackupPath(dbPath) {
|
|
31852
|
-
const base = dbPath ? dirname11(
|
|
32559
|
+
const base = dbPath ? dirname11(resolve14(dbPath)) : dirname11(resolveDbPath());
|
|
31853
32560
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
31854
32561
|
return join15(base, "backups", `todos-${stamp}.db`);
|
|
31855
32562
|
}
|
|
@@ -35513,8 +36220,8 @@ todos machines topology # full diagnostic report
|
|
|
35513
36220
|
import { createHash as createHash12 } from "crypto";
|
|
35514
36221
|
import { existsSync as existsSync20, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
|
|
35515
36222
|
import { hostname as hostname3, platform, arch } from "os";
|
|
35516
|
-
import { dirname as dirname15, join as join17, resolve as
|
|
35517
|
-
import { tmpdir as
|
|
36223
|
+
import { dirname as dirname15, join as join17, resolve as resolve15 } from "path";
|
|
36224
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
35518
36225
|
init_database();
|
|
35519
36226
|
init_redaction();
|
|
35520
36227
|
init_sync_utils();
|
|
@@ -35646,15 +36353,15 @@ function commandEnv(env, includeValues) {
|
|
|
35646
36353
|
function defaultSnapshotDir() {
|
|
35647
36354
|
const dbPath = getDatabasePath();
|
|
35648
36355
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
35649
|
-
return join17(
|
|
35650
|
-
return join17(dirname15(
|
|
36356
|
+
return join17(tmpdir3(), "hasna-todos", "environment-snapshots");
|
|
36357
|
+
return join17(dirname15(resolve15(dbPath)), "environment-snapshots");
|
|
35651
36358
|
}
|
|
35652
36359
|
function snapshotWithId(snapshot) {
|
|
35653
36360
|
const digest = sha2565(JSON.stringify(snapshot)).slice(0, 24);
|
|
35654
36361
|
return { id: `env_${digest}`, ...snapshot };
|
|
35655
36362
|
}
|
|
35656
36363
|
function captureEnvironmentSnapshot(input = {}) {
|
|
35657
|
-
const root =
|
|
36364
|
+
const root = resolve15(input.root || process.cwd());
|
|
35658
36365
|
const env = input.env || process.env;
|
|
35659
36366
|
const warnings = [];
|
|
35660
36367
|
const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
|
|
@@ -35694,13 +36401,13 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
35694
36401
|
});
|
|
35695
36402
|
}
|
|
35696
36403
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
35697
|
-
const path = outputPath ?
|
|
36404
|
+
const path = outputPath ? resolve15(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
35698
36405
|
ensureDir2(dirname15(path));
|
|
35699
36406
|
writeJsonFile(path, snapshot);
|
|
35700
36407
|
return path;
|
|
35701
36408
|
}
|
|
35702
36409
|
function readEnvironmentSnapshot(path) {
|
|
35703
|
-
const snapshot = readJsonFile(
|
|
36410
|
+
const snapshot = readJsonFile(resolve15(path));
|
|
35704
36411
|
if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
|
|
35705
36412
|
throw new Error(`Invalid environment snapshot: ${path}`);
|
|
35706
36413
|
}
|
|
@@ -37439,7 +38146,7 @@ function listLinkedTemplates(db, cwd) {
|
|
|
37439
38146
|
init_database();
|
|
37440
38147
|
import { mkdtempSync } from "fs";
|
|
37441
38148
|
import { join as join21 } from "path";
|
|
37442
|
-
import { tmpdir as
|
|
38149
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
37443
38150
|
var AGENT_WORKFLOW_DEMO_SCHEMA = "todos.agent_workflow_demo.v1";
|
|
37444
38151
|
var DEMO_DEFAULT_AGENT = "demoagent";
|
|
37445
38152
|
var DEMO_DEFAULT_PROJECT = "Agent Workflow Demo";
|
|
@@ -37454,7 +38161,7 @@ function setupEphemeralDemoDb(options = {}) {
|
|
|
37454
38161
|
if (options.db_path) {
|
|
37455
38162
|
db_path = options.db_path;
|
|
37456
38163
|
} else if (options.persist) {
|
|
37457
|
-
db_path = join21(mkdtempSync(join21(
|
|
38164
|
+
db_path = join21(mkdtempSync(join21(tmpdir4(), "todos-demo-")), "todos.db");
|
|
37458
38165
|
} else {
|
|
37459
38166
|
db_path = ":memory:";
|
|
37460
38167
|
}
|
|
@@ -38076,11 +38783,6 @@ var ALL_MCP_TOOLS = [
|
|
|
38076
38783
|
"template_history",
|
|
38077
38784
|
"todos_inbox",
|
|
38078
38785
|
"todos_retro",
|
|
38079
|
-
"todos_storage_conflicts",
|
|
38080
|
-
"todos_storage_feedback",
|
|
38081
|
-
"todos_storage_pull",
|
|
38082
|
-
"todos_storage_push",
|
|
38083
|
-
"todos_storage_status",
|
|
38084
38786
|
"trust_workspace",
|
|
38085
38787
|
"unarchive_agent",
|
|
38086
38788
|
"unarchive_task",
|
|
@@ -38217,12 +38919,6 @@ var MCP_GROUP_DEFS = [
|
|
|
38217
38919
|
description: "Sandbox, trust, secrets, crypto, and verification.",
|
|
38218
38920
|
match: (t) => /sandbox|trust|secret|crypto|verification|policy_pack|redact|scan_/.test(t)
|
|
38219
38921
|
},
|
|
38220
|
-
{
|
|
38221
|
-
id: "storage",
|
|
38222
|
-
name: "Storage bridge",
|
|
38223
|
-
description: "Optional storage sync tools (admin profile).",
|
|
38224
|
-
match: (t) => t.startsWith("todos_storage_") || t === "sync_all" || t === "migrate_pg"
|
|
38225
|
-
},
|
|
38226
38922
|
{
|
|
38227
38923
|
id: "workflow",
|
|
38228
38924
|
name: "Workflow utilities",
|
|
@@ -40313,7 +41009,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
40313
41009
|
// src/lib/extract.ts
|
|
40314
41010
|
import { existsSync as existsSync25, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
|
|
40315
41011
|
import { createHash as createHash14 } from "crypto";
|
|
40316
|
-
import { relative as relative6, resolve as
|
|
41012
|
+
import { relative as relative6, resolve as resolve16, join as join24 } from "path";
|
|
40317
41013
|
var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
|
|
40318
41014
|
var DEFAULT_EXTENSIONS = new Set([
|
|
40319
41015
|
".ts",
|
|
@@ -40383,7 +41079,7 @@ function normalizePathForMatch(value) {
|
|
|
40383
41079
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
40384
41080
|
}
|
|
40385
41081
|
function readGitignorePatterns(basePath) {
|
|
40386
|
-
const root = statSync8(basePath).isFile() ?
|
|
41082
|
+
const root = statSync8(basePath).isFile() ? resolve16(basePath, "..") : basePath;
|
|
40387
41083
|
const gitignorePath = join24(root, ".gitignore");
|
|
40388
41084
|
if (!existsSync25(gitignorePath))
|
|
40389
41085
|
return [];
|
|
@@ -40519,7 +41215,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
|
|
|
40519
41215
|
return files.sort();
|
|
40520
41216
|
}
|
|
40521
41217
|
function buildCodebaseIndex(options) {
|
|
40522
|
-
const basePath =
|
|
41218
|
+
const basePath = resolve16(options.path);
|
|
40523
41219
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
40524
41220
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
40525
41221
|
const excludes = options.exclude || [];
|
|
@@ -40530,7 +41226,7 @@ function buildCodebaseIndex(options) {
|
|
|
40530
41226
|
const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
|
|
40531
41227
|
try {
|
|
40532
41228
|
const source9 = readFileSync23(fullPath, "utf-8");
|
|
40533
|
-
const relPath = statSync8(basePath).isFile() ? relative6(
|
|
41229
|
+
const relPath = statSync8(basePath).isFile() ? relative6(resolve16(basePath, ".."), fullPath) : file;
|
|
40534
41230
|
indexed.push({
|
|
40535
41231
|
file: relPath,
|
|
40536
41232
|
checksum: stableHash(source9).slice(0, 24),
|
|
@@ -40550,7 +41246,7 @@ function buildCodebaseIndex(options) {
|
|
|
40550
41246
|
};
|
|
40551
41247
|
}
|
|
40552
41248
|
function extractTodos(options, db) {
|
|
40553
|
-
const basePath =
|
|
41249
|
+
const basePath = resolve16(options.path);
|
|
40554
41250
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
40555
41251
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
40556
41252
|
const excludes = options.exclude || [];
|
|
@@ -40561,7 +41257,7 @@ function extractTodos(options, db) {
|
|
|
40561
41257
|
const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
|
|
40562
41258
|
try {
|
|
40563
41259
|
const source9 = readFileSync23(fullPath, "utf-8");
|
|
40564
|
-
const relPath = statSync8(basePath).isFile() ? relative6(
|
|
41260
|
+
const relPath = statSync8(basePath).isFile() ? relative6(resolve16(basePath, ".."), fullPath) : file;
|
|
40565
41261
|
const comments = extractFromSource(source9, relPath, tags);
|
|
40566
41262
|
allComments.push(...comments);
|
|
40567
41263
|
} catch {}
|
|
@@ -40655,7 +41351,7 @@ async function watchSourceTodos(options, onRun) {
|
|
|
40655
41351
|
const interval = Math.max(100, options.interval_ms || 2000);
|
|
40656
41352
|
const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
|
|
40657
41353
|
const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
|
|
40658
|
-
const root =
|
|
41354
|
+
const root = resolve16(options.path);
|
|
40659
41355
|
const runs = [];
|
|
40660
41356
|
let previous = new Map;
|
|
40661
41357
|
for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
|
|
@@ -41528,7 +42224,7 @@ function renderAgentReplaySimulationMarkdown(simulation) {
|
|
|
41528
42224
|
init_config();
|
|
41529
42225
|
import { createHash as createHash16, createVerify } from "crypto";
|
|
41530
42226
|
import { existsSync as existsSync26, readdirSync as readdirSync5, readFileSync as readFileSync25, statSync as statSync9 } from "fs";
|
|
41531
|
-
import { basename as basename5, join as join25, resolve as
|
|
42227
|
+
import { basename as basename5, join as join25, resolve as resolve17 } from "path";
|
|
41532
42228
|
init_redaction();
|
|
41533
42229
|
function isObject2(value) {
|
|
41534
42230
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -41808,7 +42504,7 @@ function verifyExtensionSignature(input) {
|
|
|
41808
42504
|
return verifier.verify(input.public_key, decodeSignature(input.signature));
|
|
41809
42505
|
}
|
|
41810
42506
|
function inspectExtensionSource(source9) {
|
|
41811
|
-
const resolved =
|
|
42507
|
+
const resolved = resolve17(source9);
|
|
41812
42508
|
if (!existsSync26(resolved))
|
|
41813
42509
|
throw new Error(`extension source not found: ${source9}`);
|
|
41814
42510
|
const stat = statSync9(resolved);
|
|
@@ -41906,7 +42602,7 @@ function testExtensionCompatibility(sourceOrManifest) {
|
|
|
41906
42602
|
function projectExtensionSources(projectPath) {
|
|
41907
42603
|
if (!projectPath)
|
|
41908
42604
|
return [];
|
|
41909
|
-
const root =
|
|
42605
|
+
const root = resolve17(projectPath);
|
|
41910
42606
|
const candidates = [
|
|
41911
42607
|
join25(root, "todos.extension.json"),
|
|
41912
42608
|
join25(root, ".todos", "todos.extension.json")
|
|
@@ -41925,7 +42621,7 @@ function projectExtensionSources(projectPath) {
|
|
|
41925
42621
|
}
|
|
41926
42622
|
function discoverLocalExtensions(options = {}) {
|
|
41927
42623
|
const config = loadConfig();
|
|
41928
|
-
const projectPath = options.project_path ?
|
|
42624
|
+
const projectPath = options.project_path ? resolve17(options.project_path) : null;
|
|
41929
42625
|
const configuredSources = [
|
|
41930
42626
|
...config.extension_sources || [],
|
|
41931
42627
|
...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
|
|
@@ -41933,7 +42629,7 @@ function discoverLocalExtensions(options = {}) {
|
|
|
41933
42629
|
const sources = Array.from(new Set([
|
|
41934
42630
|
...configuredSources,
|
|
41935
42631
|
...projectExtensionSources(projectPath || undefined)
|
|
41936
|
-
])).map((source9) => projectPath && !source9.startsWith("/") ?
|
|
42632
|
+
])).map((source9) => projectPath && !source9.startsWith("/") ? resolve17(projectPath, source9) : resolve17(source9));
|
|
41937
42633
|
const warnings = [];
|
|
41938
42634
|
const discovered = [];
|
|
41939
42635
|
for (const source9 of sources) {
|
|
@@ -44081,6 +44777,7 @@ export {
|
|
|
44081
44777
|
writeBackupManifest,
|
|
44082
44778
|
writeArtifactExportManifest,
|
|
44083
44779
|
wrapWithSchemaVersion,
|
|
44780
|
+
workflowPointersFromMetadata,
|
|
44084
44781
|
watchSourceTodos,
|
|
44085
44782
|
verifyTaskRunArtifacts,
|
|
44086
44783
|
verifyStoredArtifact,
|
|
@@ -44180,6 +44877,7 @@ export {
|
|
|
44180
44877
|
shouldRegisterToolForProfile2 as shouldRegisterToolForAccessProfile,
|
|
44181
44878
|
setupEphemeralDemoDb,
|
|
44182
44879
|
setTaskWorkflowState,
|
|
44880
|
+
setTaskWorkflowPointers,
|
|
44183
44881
|
setTaskStatus,
|
|
44184
44882
|
setTaskPriorityMeta,
|
|
44185
44883
|
setTaskPriority,
|
|
@@ -44222,6 +44920,8 @@ export {
|
|
|
44222
44920
|
runNextAgentDispatch,
|
|
44223
44921
|
runDueDispatches,
|
|
44224
44922
|
runAgentWorkflowDemo,
|
|
44923
|
+
routingAutomationMetadata,
|
|
44924
|
+
routeEnabledForTask,
|
|
44225
44925
|
revokeApiKey,
|
|
44226
44926
|
returnReviewItem,
|
|
44227
44927
|
retryAgentRunDispatch,
|
|
@@ -44358,6 +45058,7 @@ export {
|
|
|
44358
45058
|
parseFeedback,
|
|
44359
45059
|
parseErrorPaste,
|
|
44360
45060
|
parseCiLog,
|
|
45061
|
+
objectField,
|
|
44361
45062
|
now,
|
|
44362
45063
|
notifyUpcomingDeadlines2 as notifyUpcomingDeadlines,
|
|
44363
45064
|
normalizeTesterIssueReport,
|
|
@@ -44490,6 +45191,7 @@ export {
|
|
|
44490
45191
|
linkRunVerification,
|
|
44491
45192
|
linkRunArtifact,
|
|
44492
45193
|
issueToTask,
|
|
45194
|
+
isWorktreePath,
|
|
44493
45195
|
isValidRecurrenceRule,
|
|
44494
45196
|
isTodosRemoteStorageEnabled,
|
|
44495
45197
|
isSnapshotStale,
|
|
@@ -44503,6 +45205,7 @@ export {
|
|
|
44503
45205
|
inspectGitCommit,
|
|
44504
45206
|
inspectExtensionSource,
|
|
44505
45207
|
initBuiltinTemplates,
|
|
45208
|
+
inferRootProjectId,
|
|
44506
45209
|
importUserScaffold,
|
|
44507
45210
|
importTodosMarkdown,
|
|
44508
45211
|
importTemplateFromFile,
|
|
@@ -44554,6 +45257,7 @@ export {
|
|
|
44554
45257
|
getTaskStats,
|
|
44555
45258
|
getTaskRunLedger,
|
|
44556
45259
|
getTaskRun,
|
|
45260
|
+
getTaskRouteState,
|
|
44557
45261
|
getTaskReview,
|
|
44558
45262
|
getTaskRelationships,
|
|
44559
45263
|
getTaskRelationship,
|
|
@@ -44924,6 +45628,7 @@ export {
|
|
|
44924
45628
|
completeAgentRun,
|
|
44925
45629
|
compareEnvironmentSnapshots,
|
|
44926
45630
|
compareEnvironmentSnapshotFiles,
|
|
45631
|
+
compactWorkflowPointers,
|
|
44927
45632
|
compactScaleStorage,
|
|
44928
45633
|
compactDatabase,
|
|
44929
45634
|
collectCliCommandEntries,
|
|
@@ -44934,6 +45639,7 @@ export {
|
|
|
44934
45639
|
clearActiveModel,
|
|
44935
45640
|
cleanupArtifacts,
|
|
44936
45641
|
cleanExpiredLocks,
|
|
45642
|
+
classifyProjectKind,
|
|
44937
45643
|
claimReviewItem,
|
|
44938
45644
|
claimPlanStep,
|
|
44939
45645
|
claimOrSteal,
|
|
@@ -44978,6 +45684,7 @@ export {
|
|
|
44978
45684
|
buildCodebaseIndex,
|
|
44979
45685
|
buildArtifactExportManifest,
|
|
44980
45686
|
bootstrapProject,
|
|
45687
|
+
booleanField,
|
|
44981
45688
|
beginTaskRunTransaction,
|
|
44982
45689
|
backupDatabase,
|
|
44983
45690
|
autoReleaseStaleAgents,
|
|
@@ -45028,6 +45735,7 @@ export {
|
|
|
45028
45735
|
TaskNotFoundError,
|
|
45029
45736
|
TaskListNotFoundError,
|
|
45030
45737
|
TUI_DASHBOARD_VIEWS,
|
|
45738
|
+
TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
|
|
45031
45739
|
TODOS_STORAGE_TABLES,
|
|
45032
45740
|
TODOS_STORAGE_FALLBACK_ENV,
|
|
45033
45741
|
TODOS_STORAGE_ENV,
|
|
@@ -45061,6 +45769,7 @@ export {
|
|
|
45061
45769
|
TESTERS_ISSUE_REPORT_SCHEMA_VERSION,
|
|
45062
45770
|
TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
|
|
45063
45771
|
TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION,
|
|
45772
|
+
TASK_WORKFLOW_POINTER_SCHEMA_VERSION,
|
|
45064
45773
|
TASK_STATUSES,
|
|
45065
45774
|
TASK_SCHEDULING_SCHEMA,
|
|
45066
45775
|
TASK_PRIORITIES,
|