@hasna/todos 0.11.66 → 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 +2 -2
- package/dashboard/dist/assets/{index-aJefI7kh.js → index-DVotjwab.js} +1 -1
- package/dashboard/dist/index.html +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/index.js +667 -169
- package/dist/contracts.js +301 -71
- 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.js +628 -160
- 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/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/mcp/index.js +343 -104
- package/dist/registry.js +301 -71
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +366 -127
- 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 +569 -94
- 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) {
|
|
@@ -7939,6 +8120,8 @@ function readMachineLocalPath(project) {
|
|
|
7939
8120
|
}
|
|
7940
8121
|
}
|
|
7941
8122
|
async function emitSharedTaskEvent(input) {
|
|
8123
|
+
if (!shouldEmitSharedTaskEvents(input.databasePath))
|
|
8124
|
+
return;
|
|
7942
8125
|
const data = taskEventData(input.task, input.data);
|
|
7943
8126
|
await new EventsClient().emit({
|
|
7944
8127
|
source: SOURCE,
|
|
@@ -7963,8 +8146,9 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
|
|
|
7963
8146
|
const d = db || getDatabase();
|
|
7964
8147
|
const id = uuid();
|
|
7965
8148
|
const timestamp = now();
|
|
7966
|
-
|
|
7967
|
-
|
|
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]);
|
|
7968
8152
|
try {
|
|
7969
8153
|
const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
|
|
7970
8154
|
logActivity2({
|
|
@@ -7977,7 +8161,7 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
|
|
|
7977
8161
|
actor_id: agentId ?? undefined
|
|
7978
8162
|
}, d);
|
|
7979
8163
|
} catch {}
|
|
7980
|
-
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 };
|
|
7981
8165
|
}
|
|
7982
8166
|
function getTaskHistory(taskId, db) {
|
|
7983
8167
|
const d = db || getDatabase();
|
|
@@ -8326,13 +8510,14 @@ function createTask(input, db) {
|
|
|
8326
8510
|
const d = db || getDatabase();
|
|
8327
8511
|
const timestamp = now();
|
|
8328
8512
|
const tags = input.tags || [];
|
|
8513
|
+
const machineId = currentStorageMachineId(d);
|
|
8329
8514
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
8330
8515
|
const assignedFromProject = input.assigned_from_project || null;
|
|
8331
8516
|
let id = uuid();
|
|
8332
8517
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
8333
8518
|
try {
|
|
8334
|
-
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)
|
|
8335
|
-
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, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8336
8521
|
id,
|
|
8337
8522
|
null,
|
|
8338
8523
|
input.project_id || null,
|
|
@@ -8369,7 +8554,8 @@ function createTask(input, db) {
|
|
|
8369
8554
|
input.spawned_from_session || null,
|
|
8370
8555
|
assignedBy || null,
|
|
8371
8556
|
assignedFromProject || null,
|
|
8372
|
-
input.task_type || null
|
|
8557
|
+
input.task_type || null,
|
|
8558
|
+
machineId
|
|
8373
8559
|
]);
|
|
8374
8560
|
break;
|
|
8375
8561
|
} catch (e) {
|
|
@@ -8385,9 +8571,10 @@ function createTask(input, db) {
|
|
|
8385
8571
|
}
|
|
8386
8572
|
const task = getTask(id, d);
|
|
8387
8573
|
const payload = taskEventData(task);
|
|
8574
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8388
8575
|
dispatchWebhook2("task.created", payload, d).catch(() => {});
|
|
8389
|
-
emitLocalEventHooksQuiet({ type: "task.created", payload });
|
|
8390
|
-
emitSharedTaskEventQuiet({ type: "task.created", task });
|
|
8576
|
+
emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
|
|
8577
|
+
emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
|
|
8391
8578
|
return task;
|
|
8392
8579
|
}
|
|
8393
8580
|
function getTask(id, db) {
|
|
@@ -8805,29 +8992,39 @@ function updateTask(id, input, db) {
|
|
|
8805
8992
|
approved_by: input.approved_by ?? task.approved_by,
|
|
8806
8993
|
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
8807
8994
|
};
|
|
8995
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8808
8996
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
8809
8997
|
const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
|
|
8810
8998
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
8811
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
8812
|
-
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 });
|
|
8813
9001
|
}
|
|
8814
9002
|
if (input.status !== undefined && input.status !== task.status) {
|
|
8815
9003
|
const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
|
|
8816
9004
|
dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
|
|
8817
|
-
emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
|
|
8818
|
-
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 });
|
|
8819
9007
|
}
|
|
8820
9008
|
if (input.approved_by !== undefined) {
|
|
8821
|
-
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 });
|
|
8822
9010
|
}
|
|
8823
9011
|
const updatePayload = taskEventData(updatedTask);
|
|
8824
9012
|
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
8825
|
-
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
|
|
8826
|
-
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
|
|
9013
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
|
|
9014
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
|
|
8827
9015
|
return updatedTask;
|
|
8828
9016
|
}
|
|
8829
9017
|
function deleteTask(id, db) {
|
|
8830
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);
|
|
8831
9028
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
8832
9029
|
return result.changes > 0;
|
|
8833
9030
|
}
|
|
@@ -8959,8 +9156,9 @@ function resolveTemplateId(id, d) {
|
|
|
8959
9156
|
function createTemplate(input, db) {
|
|
8960
9157
|
const d = db || getDatabase();
|
|
8961
9158
|
const id = uuid();
|
|
8962
|
-
|
|
8963
|
-
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8964
9162
|
id,
|
|
8965
9163
|
input.name,
|
|
8966
9164
|
input.title_pattern,
|
|
@@ -8971,7 +9169,8 @@ function createTemplate(input, db) {
|
|
|
8971
9169
|
input.project_id || null,
|
|
8972
9170
|
input.plan_id || null,
|
|
8973
9171
|
JSON.stringify(input.metadata || {}),
|
|
8974
|
-
now()
|
|
9172
|
+
now(),
|
|
9173
|
+
machineId
|
|
8975
9174
|
]);
|
|
8976
9175
|
if (input.tasks && input.tasks.length > 0) {
|
|
8977
9176
|
addTemplateTasks(id, input.tasks, d);
|
|
@@ -8995,6 +9194,15 @@ function deleteTemplate(id, db) {
|
|
|
8995
9194
|
const resolved = resolveTemplateId(id, d);
|
|
8996
9195
|
if (!resolved)
|
|
8997
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);
|
|
8998
9206
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
8999
9207
|
}
|
|
9000
9208
|
function updateTemplate(id, updates, db) {
|
|
@@ -9524,6 +9732,7 @@ function getBlockingDeps(id, db) {
|
|
|
9524
9732
|
}
|
|
9525
9733
|
function startTask(id, agentId, db) {
|
|
9526
9734
|
const d = db || getDatabase();
|
|
9735
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9527
9736
|
const task = getTask(id, d);
|
|
9528
9737
|
if (!task)
|
|
9529
9738
|
throw new TaskNotFoundError(id);
|
|
@@ -9538,7 +9747,8 @@ function startTask(id, agentId, db) {
|
|
|
9538
9747
|
agent_id: agentId,
|
|
9539
9748
|
title: task.title,
|
|
9540
9749
|
blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
|
|
9541
|
-
}
|
|
9750
|
+
},
|
|
9751
|
+
databasePath
|
|
9542
9752
|
});
|
|
9543
9753
|
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
9544
9754
|
}
|
|
@@ -9560,12 +9770,13 @@ function startTask(id, agentId, db) {
|
|
|
9560
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 };
|
|
9561
9771
|
const payload = taskEventData(startedTask, { agent_id: agentId });
|
|
9562
9772
|
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
9563
|
-
emitLocalEventHooksQuiet({ type: "task.started", payload });
|
|
9564
|
-
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 });
|
|
9565
9775
|
return startedTask;
|
|
9566
9776
|
}
|
|
9567
9777
|
function completeTask(id, agentId, db, options) {
|
|
9568
9778
|
const d = db || getDatabase();
|
|
9779
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9569
9780
|
const task = getTask(id, d);
|
|
9570
9781
|
if (!task)
|
|
9571
9782
|
throw new TaskNotFoundError(id);
|
|
@@ -9611,8 +9822,8 @@ function completeTask(id, agentId, db, options) {
|
|
|
9611
9822
|
};
|
|
9612
9823
|
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
|
|
9613
9824
|
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
9614
|
-
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
|
|
9615
|
-
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 });
|
|
9616
9827
|
let spawnedTask = null;
|
|
9617
9828
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
9618
9829
|
spawnedTask = spawnNextRecurrence(task, d, timestamp);
|
|
@@ -9656,9 +9867,9 @@ function completeTask(id, agentId, db, options) {
|
|
|
9656
9867
|
const depTask = getTask(dep.id, d);
|
|
9657
9868
|
const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
|
|
9658
9869
|
dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
|
|
9659
|
-
emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
|
|
9870
|
+
emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
|
|
9660
9871
|
if (depTask)
|
|
9661
|
-
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
|
|
9872
|
+
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
|
|
9662
9873
|
}
|
|
9663
9874
|
}
|
|
9664
9875
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
@@ -9828,6 +10039,7 @@ function getTasksChangedSince(since, filters, db) {
|
|
|
9828
10039
|
}
|
|
9829
10040
|
function failTask(id, agentId, reason, options, db) {
|
|
9830
10041
|
const d = db || getDatabase();
|
|
10042
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9831
10043
|
const task = getTask(id, d);
|
|
9832
10044
|
if (!task)
|
|
9833
10045
|
throw new TaskNotFoundError(id);
|
|
@@ -9856,8 +10068,8 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
9856
10068
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
9857
10069
|
const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
|
|
9858
10070
|
dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
|
|
9859
|
-
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
|
|
9860
|
-
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 });
|
|
9861
10073
|
let retryTask;
|
|
9862
10074
|
if (options?.retry) {
|
|
9863
10075
|
const retryCount = (task.retry_count || 0) + 1;
|
|
@@ -9917,6 +10129,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
|
|
|
9917
10129
|
}
|
|
9918
10130
|
function stealTask(agentId, opts, db) {
|
|
9919
10131
|
const d = db || getDatabase();
|
|
10132
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9920
10133
|
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
9921
10134
|
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
9922
10135
|
if (staleTasks.length === 0)
|
|
@@ -9935,8 +10148,8 @@ function stealTask(agentId, opts, db) {
|
|
|
9935
10148
|
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
9936
10149
|
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
9937
10150
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
9938
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
9939
|
-
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 });
|
|
9940
10153
|
return stolenTask;
|
|
9941
10154
|
}
|
|
9942
10155
|
function claimOrSteal(agentId, filters, db) {
|
|
@@ -10527,8 +10740,9 @@ function createPlan(input, db) {
|
|
|
10527
10740
|
const d = db || getDatabase();
|
|
10528
10741
|
const id = uuid();
|
|
10529
10742
|
const timestamp = now();
|
|
10530
|
-
|
|
10531
|
-
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10532
10746
|
id,
|
|
10533
10747
|
input.project_id || null,
|
|
10534
10748
|
input.task_list_id || null,
|
|
@@ -10537,7 +10751,8 @@ function createPlan(input, db) {
|
|
|
10537
10751
|
input.description || null,
|
|
10538
10752
|
input.status || "active",
|
|
10539
10753
|
timestamp,
|
|
10540
|
-
timestamp
|
|
10754
|
+
timestamp,
|
|
10755
|
+
machineId
|
|
10541
10756
|
]);
|
|
10542
10757
|
return getPlan(id, d);
|
|
10543
10758
|
}
|
|
@@ -10585,12 +10800,21 @@ function updatePlan(id, input, db) {
|
|
|
10585
10800
|
const updated = getPlan(id, d);
|
|
10586
10801
|
emitLocalEventHooksQuiet({
|
|
10587
10802
|
type: "plan.updated",
|
|
10588
|
-
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)
|
|
10589
10805
|
});
|
|
10590
10806
|
return updated;
|
|
10591
10807
|
}
|
|
10592
10808
|
function deletePlan(id, db) {
|
|
10593
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);
|
|
10594
10818
|
const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
|
|
10595
10819
|
return result.changes > 0;
|
|
10596
10820
|
}
|
|
@@ -10997,20 +11221,20 @@ init_database();
|
|
|
10997
11221
|
init_redaction();
|
|
10998
11222
|
import { createHash as createHash2 } from "crypto";
|
|
10999
11223
|
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
11000
|
-
import { basename, dirname as dirname5, join as join6, resolve as
|
|
11001
|
-
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";
|
|
11002
11226
|
function isInMemoryDb2(path) {
|
|
11003
11227
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
11004
11228
|
}
|
|
11005
11229
|
function artifactStoreRoot() {
|
|
11006
11230
|
if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
|
|
11007
|
-
return
|
|
11231
|
+
return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
|
|
11008
11232
|
if (process.env["TODOS_ARTIFACTS_DIR"])
|
|
11009
|
-
return
|
|
11233
|
+
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
11010
11234
|
const dbPath = getDatabasePath();
|
|
11011
11235
|
if (isInMemoryDb2(dbPath))
|
|
11012
|
-
return join6(
|
|
11013
|
-
return join6(dirname5(
|
|
11236
|
+
return join6(tmpdir2(), "hasna-todos-artifacts");
|
|
11237
|
+
return join6(dirname5(resolve7(dbPath)), "artifacts");
|
|
11014
11238
|
}
|
|
11015
11239
|
function artifactStorePath(relativePath) {
|
|
11016
11240
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
@@ -11057,7 +11281,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
11057
11281
|
return "application/octet-stream";
|
|
11058
11282
|
}
|
|
11059
11283
|
function storeArtifactContent(input) {
|
|
11060
|
-
const sourcePath =
|
|
11284
|
+
const sourcePath = resolve7(input.path);
|
|
11061
11285
|
if (!existsSync8(sourcePath))
|
|
11062
11286
|
return null;
|
|
11063
11287
|
const sourceStat = statSync2(sourcePath);
|
|
@@ -11213,19 +11437,19 @@ function importStoredArtifactContent(content) {
|
|
|
11213
11437
|
var DEFAULT_DELETED_RETENTION_DAYS = 30;
|
|
11214
11438
|
function getArtifactStoreRoot(dbPath) {
|
|
11215
11439
|
if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
|
|
11216
|
-
return
|
|
11440
|
+
return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
|
|
11217
11441
|
if (process.env["TODOS_ARTIFACTS_DIR"])
|
|
11218
|
-
return
|
|
11442
|
+
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
11219
11443
|
const path = dbPath ?? getDatabasePath();
|
|
11220
11444
|
if (isInMemoryDb2(path))
|
|
11221
|
-
return join6(
|
|
11222
|
-
return join6(dirname5(
|
|
11445
|
+
return join6(tmpdir2(), "hasna-todos-artifacts");
|
|
11446
|
+
return join6(dirname5(resolve7(path)), "artifacts");
|
|
11223
11447
|
}
|
|
11224
11448
|
function computeContentHash(path) {
|
|
11225
|
-
return sha256(readFileSync4(
|
|
11449
|
+
return sha256(readFileSync4(resolve7(path)));
|
|
11226
11450
|
}
|
|
11227
11451
|
function storeArtifactFile(input) {
|
|
11228
|
-
const sourcePath =
|
|
11452
|
+
const sourcePath = resolve7(input.sourcePath);
|
|
11229
11453
|
if (!existsSync8(sourcePath)) {
|
|
11230
11454
|
throw new Error(`Source file not found: ${input.sourcePath}`);
|
|
11231
11455
|
}
|
|
@@ -11274,7 +11498,7 @@ function buildArtifactExportManifest(artifacts, dbPath) {
|
|
|
11274
11498
|
};
|
|
11275
11499
|
}
|
|
11276
11500
|
function writeArtifactExportManifest(manifest, outputPath) {
|
|
11277
|
-
const destination =
|
|
11501
|
+
const destination = resolve7(outputPath);
|
|
11278
11502
|
mkdirSync4(dirname5(destination), { recursive: true });
|
|
11279
11503
|
writeFileSync2(destination, `${JSON.stringify(manifest, null, 2)}
|
|
11280
11504
|
`);
|
|
@@ -11661,7 +11885,11 @@ function startTaskRun(input, db) {
|
|
|
11661
11885
|
}, d);
|
|
11662
11886
|
}
|
|
11663
11887
|
const run = getTaskRun(id, d);
|
|
11664
|
-
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
|
+
});
|
|
11665
11893
|
return run;
|
|
11666
11894
|
}
|
|
11667
11895
|
function beginTaskRunTransaction(input, db) {
|
|
@@ -11930,7 +12158,8 @@ function finishTaskRun(input, db) {
|
|
|
11930
12158
|
const updated = getTaskRun(run.id, d);
|
|
11931
12159
|
emitLocalEventHooksQuiet({
|
|
11932
12160
|
type: `run.${input.status}`,
|
|
11933
|
-
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)
|
|
11934
12163
|
});
|
|
11935
12164
|
return updated;
|
|
11936
12165
|
}
|
|
@@ -13264,7 +13493,7 @@ function importOnboardingFixture(options = {}) {
|
|
|
13264
13493
|
init_database();
|
|
13265
13494
|
import { createHash as createHash3 } from "crypto";
|
|
13266
13495
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
13267
|
-
import { dirname as dirname6, resolve as
|
|
13496
|
+
import { dirname as dirname6, resolve as resolve8 } from "path";
|
|
13268
13497
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
13269
13498
|
var TODOS_LOCAL_BACKUP_KIND = "hasna.todos.local-backup";
|
|
13270
13499
|
var TODOS_LOCAL_BACKUP_SCHEMA_VERSION = 1;
|
|
@@ -13371,14 +13600,14 @@ function createLocalBackup(options = {}, db) {
|
|
|
13371
13600
|
return backup;
|
|
13372
13601
|
}
|
|
13373
13602
|
function writeLocalBackupFile(backup, outputPath) {
|
|
13374
|
-
const path =
|
|
13603
|
+
const path = resolve8(outputPath);
|
|
13375
13604
|
mkdirSync6(dirname6(path), { recursive: true });
|
|
13376
13605
|
writeFileSync4(path, `${JSON.stringify(backup, null, 2)}
|
|
13377
13606
|
`);
|
|
13378
13607
|
return path;
|
|
13379
13608
|
}
|
|
13380
13609
|
function readLocalBackupFile(path) {
|
|
13381
|
-
return JSON.parse(readFileSync5(
|
|
13610
|
+
return JSON.parse(readFileSync5(resolve8(path), "utf-8"));
|
|
13382
13611
|
}
|
|
13383
13612
|
function verifyLocalBackup(value, options = {}, db) {
|
|
13384
13613
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -16346,7 +16575,8 @@ function writeQueue(task2, queue, actor, action, db) {
|
|
|
16346
16575
|
logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
|
|
16347
16576
|
emitLocalEventHooksQuiet({
|
|
16348
16577
|
type: `review.${action}`,
|
|
16349
|
-
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)
|
|
16350
16580
|
});
|
|
16351
16581
|
return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
|
|
16352
16582
|
}
|
|
@@ -17027,7 +17257,7 @@ function canonicalize(value) {
|
|
|
17027
17257
|
function hash(value) {
|
|
17028
17258
|
return createHash5("sha256").update(value).digest("hex");
|
|
17029
17259
|
}
|
|
17030
|
-
function
|
|
17260
|
+
function parsePayload2(value) {
|
|
17031
17261
|
if (!value)
|
|
17032
17262
|
return {};
|
|
17033
17263
|
try {
|
|
@@ -17126,7 +17356,7 @@ function taskScopedRows(db, scope) {
|
|
|
17126
17356
|
FROM handoffs h
|
|
17127
17357
|
WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
|
|
17128
17358
|
`).all(scope.project_id ?? null).filter((row) => {
|
|
17129
|
-
const payload =
|
|
17359
|
+
const payload = parsePayload2(row.payload_json);
|
|
17130
17360
|
const taskRefs = parseStringArray(payload["task_ids"]);
|
|
17131
17361
|
const runRefs = parseStringArray(payload["run_ids"]);
|
|
17132
17362
|
if (scope.project_id && row.project_id !== scope.project_id)
|
|
@@ -17157,7 +17387,7 @@ function toLedgerEntries(rows) {
|
|
|
17157
17387
|
});
|
|
17158
17388
|
let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
|
|
17159
17389
|
return ordered.map((row, index) => {
|
|
17160
|
-
const payload =
|
|
17390
|
+
const payload = parsePayload2(row.payload_json);
|
|
17161
17391
|
const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
|
|
17162
17392
|
const chainHash = hash(`${previous}
|
|
17163
17393
|
${payloadHash}`);
|
|
@@ -17292,7 +17522,7 @@ function renderLocalAuditLedgerMarkdown(ledger) {
|
|
|
17292
17522
|
init_migrations();
|
|
17293
17523
|
init_schema();
|
|
17294
17524
|
import { readFileSync as readFileSync6 } from "fs";
|
|
17295
|
-
import { join as join9, resolve as
|
|
17525
|
+
import { join as join9, resolve as resolve9 } from "path";
|
|
17296
17526
|
import { Database as Database2 } from "bun:sqlite";
|
|
17297
17527
|
var LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION = 1;
|
|
17298
17528
|
var EXPECTED_PACKAGE_NAME = "@hasna/todos";
|
|
@@ -17434,7 +17664,7 @@ function checkChangelog() {
|
|
|
17434
17664
|
];
|
|
17435
17665
|
}
|
|
17436
17666
|
function createReleaseCompatibilityReport(options = {}) {
|
|
17437
|
-
const root =
|
|
17667
|
+
const root = resolve9(options.root ?? process.cwd());
|
|
17438
17668
|
const packageJson = readPackageJson(root);
|
|
17439
17669
|
const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
|
|
17440
17670
|
const checks = [
|
|
@@ -21762,6 +21992,7 @@ function rowToAgent(row) {
|
|
|
21762
21992
|
}
|
|
21763
21993
|
function registerAgent(input, db) {
|
|
21764
21994
|
const d = db || getDatabase();
|
|
21995
|
+
const machineId = currentStorageMachineId(d);
|
|
21765
21996
|
const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
|
|
21766
21997
|
const normalizedName = validateAgentName(input.name, existingNames);
|
|
21767
21998
|
const existing = getAgentByName(normalizedName, d);
|
|
@@ -21802,14 +22033,18 @@ function registerAgent(input, db) {
|
|
|
21802
22033
|
updates.push("active_project_id = ?");
|
|
21803
22034
|
params.push(input.project_id);
|
|
21804
22035
|
}
|
|
22036
|
+
if (!existing.machine_id && machineId) {
|
|
22037
|
+
updates.push("machine_id = ?");
|
|
22038
|
+
params.push(machineId);
|
|
22039
|
+
}
|
|
21805
22040
|
params.push(existing.id);
|
|
21806
22041
|
d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
21807
22042
|
return getAgent(existing.id, d);
|
|
21808
22043
|
}
|
|
21809
22044
|
const id = shortUuid();
|
|
21810
22045
|
const timestamp2 = now();
|
|
21811
|
-
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)
|
|
21812
|
-
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
21813
22048
|
id,
|
|
21814
22049
|
normalizedName,
|
|
21815
22050
|
input.description || null,
|
|
@@ -21825,7 +22060,8 @@ function registerAgent(input, db) {
|
|
|
21825
22060
|
timestamp2,
|
|
21826
22061
|
input.session_id || null,
|
|
21827
22062
|
input.working_dir || null,
|
|
21828
|
-
input.project_id && input.session_id ? input.project_id : null
|
|
22063
|
+
input.project_id && input.session_id ? input.project_id : null,
|
|
22064
|
+
machineId
|
|
21829
22065
|
]);
|
|
21830
22066
|
return getAgent(id, d);
|
|
21831
22067
|
}
|
|
@@ -22025,6 +22261,14 @@ var PROJECT_COLUMNS = [
|
|
|
22025
22261
|
"machine_id",
|
|
22026
22262
|
"synced_at"
|
|
22027
22263
|
];
|
|
22264
|
+
var PROJECT_MACHINE_PATH_COLUMNS = [
|
|
22265
|
+
"id",
|
|
22266
|
+
"project_id",
|
|
22267
|
+
"machine_id",
|
|
22268
|
+
"path",
|
|
22269
|
+
"created_at",
|
|
22270
|
+
"updated_at"
|
|
22271
|
+
];
|
|
22028
22272
|
var TASK_LIST_COLUMNS = [
|
|
22029
22273
|
"id",
|
|
22030
22274
|
"project_id",
|
|
@@ -22164,11 +22408,13 @@ function exportSqliteTodosStorageSnapshot(db) {
|
|
|
22164
22408
|
source: "sqlite",
|
|
22165
22409
|
tasks: listTasks({ include_archived: true }, d),
|
|
22166
22410
|
projects: listProjects(d),
|
|
22411
|
+
projectMachinePaths: listProjectMachinePaths(d),
|
|
22167
22412
|
plans: listPlans(undefined, d),
|
|
22168
22413
|
agents: listAgents({ include_archived: true }, d),
|
|
22169
22414
|
taskLists: listTaskLists(undefined, d),
|
|
22170
22415
|
templates: listTemplates(d),
|
|
22171
|
-
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d)
|
|
22416
|
+
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d),
|
|
22417
|
+
tombstones: listStorageTombstones(d)
|
|
22172
22418
|
};
|
|
22173
22419
|
}
|
|
22174
22420
|
function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
@@ -22176,13 +22422,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
22176
22422
|
const result = {
|
|
22177
22423
|
inserted: 0,
|
|
22178
22424
|
updated: 0,
|
|
22425
|
+
deleted: 0,
|
|
22179
22426
|
skipped: 0,
|
|
22180
22427
|
errors: []
|
|
22181
22428
|
};
|
|
22182
|
-
const applyRows = (table, columns, rows, updateClockColumn, afterUpsert) => {
|
|
22429
|
+
const applyRows = (objectType, table, columns, rows, updateClockColumn, afterUpsert) => {
|
|
22183
22430
|
for (const row of rows) {
|
|
22184
22431
|
try {
|
|
22185
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
|
+
}
|
|
22186
22438
|
const state = upsertById(d, table, columns, record, updateClockColumn);
|
|
22187
22439
|
if (state === "inserted")
|
|
22188
22440
|
result.inserted += 1;
|
|
@@ -22196,17 +22448,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
22196
22448
|
}
|
|
22197
22449
|
}
|
|
22198
22450
|
};
|
|
22199
|
-
applyRows("projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
|
|
22200
|
-
applyRows("
|
|
22201
|
-
applyRows("
|
|
22202
|
-
applyRows("
|
|
22203
|
-
applyRows("
|
|
22204
|
-
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) => {
|
|
22205
22458
|
if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
|
|
22206
22459
|
replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
|
|
22207
22460
|
}
|
|
22208
22461
|
});
|
|
22209
|
-
applyRows("task_history", AUDIT_COLUMNS, snapshot.auditHistory);
|
|
22462
|
+
applyRows("audit_history", "task_history", AUDIT_COLUMNS, snapshot.auditHistory);
|
|
22463
|
+
applyTombstones(d, snapshot.tombstones ?? [], result);
|
|
22210
22464
|
return result;
|
|
22211
22465
|
}
|
|
22212
22466
|
function upsertById(db, table, columns, row, updateClockColumn) {
|
|
@@ -22261,6 +22515,85 @@ function sortedTasks2(tasks) {
|
|
|
22261
22515
|
visit(task2);
|
|
22262
22516
|
return result;
|
|
22263
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
|
+
}
|
|
22264
22597
|
|
|
22265
22598
|
// src/storage/local-sqlite.ts
|
|
22266
22599
|
function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
@@ -22427,7 +22760,7 @@ class PostgresTodosSyncStore {
|
|
|
22427
22760
|
}
|
|
22428
22761
|
async pullSnapshot(options = {}) {
|
|
22429
22762
|
const params = [this.service];
|
|
22430
|
-
const filters = ["service = $1"
|
|
22763
|
+
const filters = ["service = $1"];
|
|
22431
22764
|
if (options.since) {
|
|
22432
22765
|
params.push(options.since);
|
|
22433
22766
|
filters.push(`updated_at > $${params.length}::timestamptz`);
|
|
@@ -22436,7 +22769,7 @@ class PostgresTodosSyncStore {
|
|
|
22436
22769
|
params.push(options.objectTypes);
|
|
22437
22770
|
filters.push(`object_type = ANY($${params.length}::text[])`);
|
|
22438
22771
|
}
|
|
22439
|
-
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}
|
|
22440
22773
|
WHERE ${filters.join(" AND ")}
|
|
22441
22774
|
ORDER BY updated_at ASC, object_type ASC, object_id ASC`, params);
|
|
22442
22775
|
return rowsToSnapshot(response.rows);
|
|
@@ -22460,11 +22793,20 @@ function snapshotEntries(snapshot) {
|
|
|
22460
22793
|
return [
|
|
22461
22794
|
...snapshot.tasks.map((payload) => entry("tasks", payload, snapshot.exportedAt)),
|
|
22462
22795
|
...snapshot.projects.map((payload) => entry("projects", payload, snapshot.exportedAt)),
|
|
22796
|
+
...(snapshot.projectMachinePaths ?? []).map((payload) => entry("project_machine_paths", payload, snapshot.exportedAt)),
|
|
22463
22797
|
...snapshot.plans.map((payload) => entry("plans", payload, snapshot.exportedAt)),
|
|
22464
22798
|
...snapshot.agents.map((payload) => entry("agents", payload, snapshot.exportedAt)),
|
|
22465
22799
|
...snapshot.taskLists.map((payload) => entry("task_lists", payload, snapshot.exportedAt)),
|
|
22466
22800
|
...snapshot.templates.map((payload) => entry("templates", payload, snapshot.exportedAt)),
|
|
22467
|
-
...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
|
+
}))
|
|
22468
22810
|
];
|
|
22469
22811
|
}
|
|
22470
22812
|
function entry(type, payload, fallbackUpdatedAt) {
|
|
@@ -22486,19 +22828,38 @@ function rowsToSnapshot(rows) {
|
|
|
22486
22828
|
source: "postgres",
|
|
22487
22829
|
tasks: [],
|
|
22488
22830
|
projects: [],
|
|
22831
|
+
projectMachinePaths: [],
|
|
22489
22832
|
plans: [],
|
|
22490
22833
|
agents: [],
|
|
22491
22834
|
taskLists: [],
|
|
22492
22835
|
templates: [],
|
|
22493
|
-
auditHistory: []
|
|
22836
|
+
auditHistory: [],
|
|
22837
|
+
tombstones: []
|
|
22494
22838
|
};
|
|
22495
22839
|
for (const row of rows) {
|
|
22496
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
|
+
}
|
|
22497
22855
|
if (row.object_type === "tasks")
|
|
22498
22856
|
snapshot.tasks.push(payload);
|
|
22499
22857
|
else if (row.object_type === "projects")
|
|
22500
22858
|
snapshot.projects.push(payload);
|
|
22501
|
-
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")
|
|
22502
22863
|
snapshot.plans.push(payload);
|
|
22503
22864
|
else if (row.object_type === "agents")
|
|
22504
22865
|
snapshot.agents.push(payload);
|
|
@@ -22519,6 +22880,8 @@ function payloadRecord(value) {
|
|
|
22519
22880
|
throw new Error("Postgres sync payload must be a JSON object");
|
|
22520
22881
|
}
|
|
22521
22882
|
function stringValue(value) {
|
|
22883
|
+
if (value instanceof Date)
|
|
22884
|
+
return value.toISOString();
|
|
22522
22885
|
return typeof value === "string" && value ? value : null;
|
|
22523
22886
|
}
|
|
22524
22887
|
function numberValue2(value) {
|
|
@@ -22673,6 +23036,9 @@ class PostgresJsonRecordStore {
|
|
|
22673
23036
|
this.tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
|
|
22674
23037
|
this.cursorTableName = options.cursorTableName ?? DEFAULT_TODOS_POSTGRES_CURSOR_TABLE;
|
|
22675
23038
|
}
|
|
23039
|
+
machineId(context) {
|
|
23040
|
+
return context?.requestId ?? this.sourceMachineId ?? null;
|
|
23041
|
+
}
|
|
22676
23042
|
async ensureSchema() {
|
|
22677
23043
|
this.schemaReady ??= (async () => {
|
|
22678
23044
|
for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
|
|
@@ -22704,6 +23070,25 @@ class PostgresJsonRecordStore {
|
|
|
22704
23070
|
updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString()
|
|
22705
23071
|
}));
|
|
22706
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
|
+
}
|
|
22707
23092
|
async upsert(type, value, context = {}) {
|
|
22708
23093
|
await this.ensureSchema();
|
|
22709
23094
|
const updatedAt = stringValue2(value.updated_at) ?? stringValue2(value.created_at) ?? new Date().toISOString();
|
|
@@ -22716,7 +23101,8 @@ class PostgresJsonRecordStore {
|
|
|
22716
23101
|
updated_at = EXCLUDED.updated_at,
|
|
22717
23102
|
deleted_at = NULL,
|
|
22718
23103
|
source_machine_id = EXCLUDED.source_machine_id,
|
|
22719
|
-
version = EXCLUDED.version
|
|
23104
|
+
version = EXCLUDED.version
|
|
23105
|
+
WHERE ${this.tableName}.updated_at IS NULL OR ${this.tableName}.updated_at <= EXCLUDED.updated_at`, [
|
|
22720
23106
|
this.service,
|
|
22721
23107
|
type,
|
|
22722
23108
|
value.id,
|
|
@@ -22733,13 +23119,58 @@ class PostgresJsonRecordStore {
|
|
|
22733
23119
|
if (!existing)
|
|
22734
23120
|
return false;
|
|
22735
23121
|
const timestamp2 = new Date().toISOString();
|
|
22736
|
-
|
|
22737
|
-
|
|
22738
|
-
|
|
22739
|
-
|
|
22740
|
-
|
|
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
|
+
]);
|
|
22741
23158
|
return true;
|
|
22742
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
|
+
}
|
|
22743
23174
|
async getCursor(name) {
|
|
22744
23175
|
await this.ensureSchema();
|
|
22745
23176
|
const result = await this.options.client.query(`SELECT value FROM ${this.cursorTableName} WHERE service = $1 AND cursor_name = $2`, [this.service, name]);
|
|
@@ -22808,7 +23239,10 @@ async function createTask2(input, store, context) {
|
|
|
22808
23239
|
runner_started_at: null,
|
|
22809
23240
|
runner_completed_at: null,
|
|
22810
23241
|
current_step: null,
|
|
22811
|
-
total_steps: null
|
|
23242
|
+
total_steps: null,
|
|
23243
|
+
machine_id: store.machineId(context),
|
|
23244
|
+
synced_at: null,
|
|
23245
|
+
archived_at: null
|
|
22812
23246
|
};
|
|
22813
23247
|
await store.upsert("tasks", task2, context);
|
|
22814
23248
|
await logTaskChange2(task2.id, "created", "status", null, task2.status, task2.assigned_by ?? task2.agent_id, store, context);
|
|
@@ -22933,7 +23367,9 @@ async function createProject2(input, store, context) {
|
|
|
22933
23367
|
task_prefix: input.task_prefix ?? await generateProjectPrefix(input.name, store),
|
|
22934
23368
|
task_counter: 0,
|
|
22935
23369
|
created_at: timestamp2,
|
|
22936
|
-
updated_at: timestamp2
|
|
23370
|
+
updated_at: timestamp2,
|
|
23371
|
+
machine_id: store.machineId(context),
|
|
23372
|
+
synced_at: null
|
|
22937
23373
|
};
|
|
22938
23374
|
return store.upsert("projects", project, context);
|
|
22939
23375
|
}
|
|
@@ -22953,7 +23389,9 @@ async function createPlan2(input, store, context) {
|
|
|
22953
23389
|
description: input.description ?? null,
|
|
22954
23390
|
status: input.status ?? "active",
|
|
22955
23391
|
created_at: timestamp2,
|
|
22956
|
-
updated_at: timestamp2
|
|
23392
|
+
updated_at: timestamp2,
|
|
23393
|
+
machine_id: store.machineId(context),
|
|
23394
|
+
synced_at: null
|
|
22957
23395
|
}, context);
|
|
22958
23396
|
}
|
|
22959
23397
|
async function updatePlan2(id, input, store) {
|
|
@@ -22983,7 +23421,9 @@ async function registerAgent2(input, store, context) {
|
|
|
22983
23421
|
last_seen_at: timestamp2,
|
|
22984
23422
|
session_id: input.session_id ?? context?.sessionId ?? existing?.session_id ?? null,
|
|
22985
23423
|
working_dir: input.working_dir ?? existing?.working_dir ?? null,
|
|
22986
|
-
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
|
|
22987
23427
|
};
|
|
22988
23428
|
return store.upsert("agents", agent, context);
|
|
22989
23429
|
}
|
|
@@ -23010,7 +23450,9 @@ async function createTaskList2(input, store, context) {
|
|
|
23010
23450
|
description: input.description ?? null,
|
|
23011
23451
|
metadata: input.metadata ?? {},
|
|
23012
23452
|
created_at: timestamp2,
|
|
23013
|
-
updated_at: timestamp2
|
|
23453
|
+
updated_at: timestamp2,
|
|
23454
|
+
machine_id: store.machineId(context),
|
|
23455
|
+
synced_at: null
|
|
23014
23456
|
}, context);
|
|
23015
23457
|
}
|
|
23016
23458
|
async function updateTaskList2(id, input, store) {
|
|
@@ -23036,7 +23478,9 @@ async function createTemplate2(input, store, context) {
|
|
|
23036
23478
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
23037
23479
|
plan_id: input.plan_id ?? null,
|
|
23038
23480
|
metadata: input.metadata ?? {},
|
|
23039
|
-
created_at: timestamp2
|
|
23481
|
+
created_at: timestamp2,
|
|
23482
|
+
machine_id: store.machineId(context),
|
|
23483
|
+
synced_at: null
|
|
23040
23484
|
}, context);
|
|
23041
23485
|
}
|
|
23042
23486
|
async function updateTemplate2(id, input, store) {
|
|
@@ -23061,7 +23505,8 @@ async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentI
|
|
|
23061
23505
|
old_value: oldValue ?? null,
|
|
23062
23506
|
new_value: newValue ?? null,
|
|
23063
23507
|
agent_id: agentId ?? context?.agentId ?? null,
|
|
23064
|
-
created_at: new Date().toISOString()
|
|
23508
|
+
created_at: new Date().toISOString(),
|
|
23509
|
+
machine_id: store.machineId(context)
|
|
23065
23510
|
};
|
|
23066
23511
|
return store.upsert("audit_history", entry2, context);
|
|
23067
23512
|
}
|
|
@@ -23084,18 +23529,21 @@ async function exportSnapshot(store) {
|
|
|
23084
23529
|
source: "postgres",
|
|
23085
23530
|
tasks: await store.list("tasks"),
|
|
23086
23531
|
projects: await store.list("projects"),
|
|
23532
|
+
projectMachinePaths: await store.list("project_machine_paths"),
|
|
23087
23533
|
plans: await store.list("plans"),
|
|
23088
23534
|
agents: await store.list("agents"),
|
|
23089
23535
|
taskLists: await store.list("task_lists"),
|
|
23090
23536
|
templates: await store.list("templates"),
|
|
23091
|
-
auditHistory: await store.list("audit_history")
|
|
23537
|
+
auditHistory: await store.list("audit_history"),
|
|
23538
|
+
tombstones: await store.listTombstones()
|
|
23092
23539
|
};
|
|
23093
23540
|
}
|
|
23094
23541
|
async function importSnapshot(snapshot, store, context) {
|
|
23095
|
-
const result = { inserted: 0, updated: 0, skipped: 0, errors: [] };
|
|
23542
|
+
const result = { inserted: 0, updated: 0, deleted: 0, skipped: 0, errors: [] };
|
|
23096
23543
|
const entries = [
|
|
23097
23544
|
...snapshot.tasks.map((row) => ["tasks", row]),
|
|
23098
23545
|
...snapshot.projects.map((row) => ["projects", row]),
|
|
23546
|
+
...(snapshot.projectMachinePaths ?? []).map((row) => ["project_machine_paths", row]),
|
|
23099
23547
|
...snapshot.plans.map((row) => ["plans", row]),
|
|
23100
23548
|
...snapshot.agents.map((row) => ["agents", row]),
|
|
23101
23549
|
...snapshot.taskLists.map((row) => ["task_lists", row]),
|
|
@@ -23114,6 +23562,25 @@ async function importSnapshot(snapshot, store, context) {
|
|
|
23114
23562
|
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
23115
23563
|
}
|
|
23116
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
|
+
}
|
|
23117
23584
|
return result;
|
|
23118
23585
|
}
|
|
23119
23586
|
async function requireRecord(type, id, store) {
|
|
@@ -23196,8 +23663,17 @@ function payloadRecord2(value) {
|
|
|
23196
23663
|
throw new Error("Postgres storage payload must be a JSON object");
|
|
23197
23664
|
}
|
|
23198
23665
|
function stringValue2(value) {
|
|
23666
|
+
if (value instanceof Date)
|
|
23667
|
+
return value.toISOString();
|
|
23199
23668
|
return typeof value === "string" && value ? value : null;
|
|
23200
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
|
+
}
|
|
23201
23677
|
function numberValue3(value) {
|
|
23202
23678
|
return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
|
|
23203
23679
|
}
|
|
@@ -24132,7 +24608,7 @@ class TodosClient {
|
|
|
24132
24608
|
return this._fetchWithRetry(path, { method: "DELETE" });
|
|
24133
24609
|
}
|
|
24134
24610
|
_sleep(ms) {
|
|
24135
|
-
return new Promise((
|
|
24611
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
24136
24612
|
}
|
|
24137
24613
|
async getHealth() {
|
|
24138
24614
|
return this._get("/api/health");
|
|
@@ -25514,7 +25990,7 @@ function renderRetrospectiveMarkdown(record) {
|
|
|
25514
25990
|
// src/lib/project-bootstrap.ts
|
|
25515
25991
|
init_database();
|
|
25516
25992
|
import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
|
|
25517
|
-
import { basename as basename2, dirname as dirname7, resolve as
|
|
25993
|
+
import { basename as basename2, dirname as dirname7, resolve as resolve10 } from "path";
|
|
25518
25994
|
function safeStat(path) {
|
|
25519
25995
|
try {
|
|
25520
25996
|
return statSync3(path);
|
|
@@ -25523,7 +25999,7 @@ function safeStat(path) {
|
|
|
25523
25999
|
}
|
|
25524
26000
|
}
|
|
25525
26001
|
function canonicalPath(input) {
|
|
25526
|
-
const resolved =
|
|
26002
|
+
const resolved = resolve10(input);
|
|
25527
26003
|
const stats2 = safeStat(resolved);
|
|
25528
26004
|
if (stats2?.isFile())
|
|
25529
26005
|
return dirname7(resolved);
|
|
@@ -25532,7 +26008,7 @@ function canonicalPath(input) {
|
|
|
25532
26008
|
function findUp(start, marker) {
|
|
25533
26009
|
let current = canonicalPath(start);
|
|
25534
26010
|
while (true) {
|
|
25535
|
-
if (existsSync9(
|
|
26011
|
+
if (existsSync9(resolve10(current, marker)))
|
|
25536
26012
|
return current;
|
|
25537
26013
|
const parent = dirname7(current);
|
|
25538
26014
|
if (parent === current)
|
|
@@ -25543,7 +26019,7 @@ function findUp(start, marker) {
|
|
|
25543
26019
|
function readPackageJson2(path) {
|
|
25544
26020
|
if (!path)
|
|
25545
26021
|
return null;
|
|
25546
|
-
const file =
|
|
26022
|
+
const file = resolve10(path, "package.json");
|
|
25547
26023
|
if (!existsSync9(file))
|
|
25548
26024
|
return null;
|
|
25549
26025
|
try {
|
|
@@ -25566,7 +26042,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
25566
26042
|
if (rootPackage?.workspaces)
|
|
25567
26043
|
markers.push("package.json#workspaces");
|
|
25568
26044
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
25569
|
-
if (existsSync9(
|
|
26045
|
+
if (existsSync9(resolve10(root, marker)))
|
|
25570
26046
|
markers.push(marker);
|
|
25571
26047
|
}
|
|
25572
26048
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -25848,6 +26324,8 @@ function pointerPatch(previous, input, key) {
|
|
|
25848
26324
|
if (!Object.prototype.hasOwnProperty.call(input, key))
|
|
25849
26325
|
return previous;
|
|
25850
26326
|
const value = input[key];
|
|
26327
|
+
if (value === undefined)
|
|
26328
|
+
return previous;
|
|
25851
26329
|
return typeof value === "string" && value.trim() ? value : undefined;
|
|
25852
26330
|
}
|
|
25853
26331
|
// src/db/api-keys.ts
|
|
@@ -26915,7 +27393,7 @@ function classifyLog(text) {
|
|
|
26915
27393
|
async function sleep2(ms) {
|
|
26916
27394
|
if (ms <= 0)
|
|
26917
27395
|
return;
|
|
26918
|
-
await new Promise((
|
|
27396
|
+
await new Promise((resolve11) => setTimeout(resolve11, ms));
|
|
26919
27397
|
}
|
|
26920
27398
|
async function runCommandProvider(provider, input) {
|
|
26921
27399
|
const commandTemplate = input.command || provider.command;
|
|
@@ -27271,11 +27749,11 @@ function writeVerificationExport(bundle, path) {
|
|
|
27271
27749
|
writeFileSync7(path, JSON.stringify(bundle, null, 2), "utf8");
|
|
27272
27750
|
}
|
|
27273
27751
|
// src/lib/policy-packs.ts
|
|
27274
|
-
import { relative as relative3, resolve as
|
|
27752
|
+
import { relative as relative3, resolve as resolve11 } from "path";
|
|
27275
27753
|
init_database();
|
|
27276
27754
|
init_config();
|
|
27277
27755
|
function normalizePath3(path) {
|
|
27278
|
-
return
|
|
27756
|
+
return resolve11(path);
|
|
27279
27757
|
}
|
|
27280
27758
|
function unique4(values) {
|
|
27281
27759
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -27330,7 +27808,7 @@ function commandMatches(commands, pattern) {
|
|
|
27330
27808
|
}
|
|
27331
27809
|
function pathMatches(paths, pattern, root) {
|
|
27332
27810
|
return paths.filter((path) => {
|
|
27333
|
-
const candidate = path.startsWith("/") ? path :
|
|
27811
|
+
const candidate = path.startsWith("/") ? path : resolve11(root, path);
|
|
27334
27812
|
if (!isPathInside3(root, candidate))
|
|
27335
27813
|
return matchesPattern3(path, pattern);
|
|
27336
27814
|
return matchesPattern3(path, pattern) || matchesPattern3(relative3(root, candidate), pattern);
|
|
@@ -28042,7 +28520,7 @@ function resetAgentAdapterCache() {}
|
|
|
28042
28520
|
// src/lib/git-traceability.ts
|
|
28043
28521
|
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
28044
28522
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
28045
|
-
import { resolve as
|
|
28523
|
+
import { resolve as resolve12 } from "path";
|
|
28046
28524
|
var GIT_TRACEABILITY_SCHEMA_VERSION = "todos.git_traceability.v1";
|
|
28047
28525
|
function runGit(args, cwd) {
|
|
28048
28526
|
const result = spawnSync2("git", args, { cwd, encoding: "utf8" });
|
|
@@ -28085,7 +28563,7 @@ function inspectGitCommit(sha, cwd) {
|
|
|
28085
28563
|
};
|
|
28086
28564
|
}
|
|
28087
28565
|
function loadCiSnapshot(path) {
|
|
28088
|
-
const target = path ?
|
|
28566
|
+
const target = path ? resolve12(path) : resolve12(process.cwd(), ".todos", "ci-snapshot.json");
|
|
28089
28567
|
if (!existsSync13(target))
|
|
28090
28568
|
return null;
|
|
28091
28569
|
try {
|
|
@@ -28183,7 +28661,7 @@ function formatTraceabilityReport(report) {
|
|
|
28183
28661
|
}
|
|
28184
28662
|
// src/lib/mention-resolver.ts
|
|
28185
28663
|
import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
|
|
28186
|
-
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";
|
|
28187
28665
|
init_database();
|
|
28188
28666
|
var PREFIXES = {
|
|
28189
28667
|
file: "file",
|
|
@@ -28259,11 +28737,11 @@ function backlink(kind, key, label, target = key) {
|
|
|
28259
28737
|
return { kind, key, label, target };
|
|
28260
28738
|
}
|
|
28261
28739
|
function normalizeWorkspace(workspace) {
|
|
28262
|
-
return
|
|
28740
|
+
return resolve13(workspace || process.cwd());
|
|
28263
28741
|
}
|
|
28264
28742
|
function isInside(root, absolutePath) {
|
|
28265
28743
|
const rel = relative4(root, absolutePath);
|
|
28266
|
-
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${
|
|
28744
|
+
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep2}`) && !isAbsolute(rel);
|
|
28267
28745
|
}
|
|
28268
28746
|
function normalizeRelativePath(value) {
|
|
28269
28747
|
const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
|
|
@@ -28327,7 +28805,7 @@ function resolveFile(parsed, workspace) {
|
|
|
28327
28805
|
resolution.warnings.push("path is empty or escapes the workspace");
|
|
28328
28806
|
return resolution;
|
|
28329
28807
|
}
|
|
28330
|
-
const absolutePath =
|
|
28808
|
+
const absolutePath = resolve13(workspace, relPath);
|
|
28331
28809
|
if (!isInside(workspace, absolutePath)) {
|
|
28332
28810
|
resolution.path = relPath;
|
|
28333
28811
|
resolution.warnings.push("path escapes the workspace");
|
|
@@ -29263,7 +29741,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
|
|
|
29263
29741
|
if (action === "approved" || action === "rejected" || action === "expired") {
|
|
29264
29742
|
emitLocalEventHooksQuiet({
|
|
29265
29743
|
type: "approval.decided",
|
|
29266
|
-
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)
|
|
29267
29746
|
});
|
|
29268
29747
|
}
|
|
29269
29748
|
}
|
|
@@ -31913,14 +32392,14 @@ function renderReleaseNotesMarkdown(document) {
|
|
|
31913
32392
|
init_database();
|
|
31914
32393
|
init_migrations();
|
|
31915
32394
|
import { existsSync as existsSync19, copyFileSync, mkdirSync as mkdirSync12, readFileSync as readFileSync16, statSync as statSync6, writeFileSync as writeFileSync10, unlinkSync } from "fs";
|
|
31916
|
-
import { dirname as dirname11, join as join15, resolve as
|
|
32395
|
+
import { dirname as dirname11, join as join15, resolve as resolve14 } from "path";
|
|
31917
32396
|
import { Database as Database3 } from "bun:sqlite";
|
|
31918
32397
|
var DB_BACKUP_SCHEMA = "todos.db_backup.v1";
|
|
31919
32398
|
function resolveDbPath(dbPath) {
|
|
31920
32399
|
if (dbPath)
|
|
31921
|
-
return
|
|
32400
|
+
return resolve14(dbPath);
|
|
31922
32401
|
if (process.env["TODOS_DB_PATH"] && process.env["TODOS_DB_PATH"] !== ":memory:") {
|
|
31923
|
-
return
|
|
32402
|
+
return resolve14(process.env["TODOS_DB_PATH"]);
|
|
31924
32403
|
}
|
|
31925
32404
|
const db = getDatabase();
|
|
31926
32405
|
const filename = db.filename;
|
|
@@ -31959,7 +32438,7 @@ function restoreDatabase(backupPath, targetPath) {
|
|
|
31959
32438
|
if (!integrity.ok) {
|
|
31960
32439
|
throw new Error(`Backup failed integrity check: ${integrity.errors.join("; ")}`);
|
|
31961
32440
|
}
|
|
31962
|
-
const target = targetPath ?
|
|
32441
|
+
const target = targetPath ? resolve14(targetPath) : resolveDbPath();
|
|
31963
32442
|
mkdirSync12(dirname11(target), { recursive: true });
|
|
31964
32443
|
const staging = `${target}.restore.tmp`;
|
|
31965
32444
|
copyFileSync(backupPath, staging);
|
|
@@ -31978,7 +32457,7 @@ function restoreDatabase(backupPath, targetPath) {
|
|
|
31978
32457
|
};
|
|
31979
32458
|
}
|
|
31980
32459
|
function checkDatabaseIntegrity(dbPath) {
|
|
31981
|
-
const path = dbPath ?
|
|
32460
|
+
const path = dbPath ? resolve14(dbPath) : resolveDbPath();
|
|
31982
32461
|
const errors = [];
|
|
31983
32462
|
if (!existsSync19(path)) {
|
|
31984
32463
|
return {
|
|
@@ -32043,7 +32522,7 @@ function checkDatabaseIntegrity(dbPath) {
|
|
|
32043
32522
|
};
|
|
32044
32523
|
}
|
|
32045
32524
|
function compactDatabase(dbPath) {
|
|
32046
|
-
const path = dbPath ?
|
|
32525
|
+
const path = dbPath ? resolve14(dbPath) : resolveDbPath();
|
|
32047
32526
|
const before = statSync6(path).size;
|
|
32048
32527
|
const db = new Database3(path);
|
|
32049
32528
|
db.exec("VACUUM");
|
|
@@ -32053,7 +32532,7 @@ function compactDatabase(dbPath) {
|
|
|
32053
32532
|
return { path, bytes_before: before, bytes_after: after };
|
|
32054
32533
|
}
|
|
32055
32534
|
function migrationDryRun(dbPath) {
|
|
32056
|
-
const path = dbPath ?
|
|
32535
|
+
const path = dbPath ? resolve14(dbPath) : resolveDbPath();
|
|
32057
32536
|
const db = new Database3(path, { readonly: true });
|
|
32058
32537
|
let current = 0;
|
|
32059
32538
|
try {
|
|
@@ -32077,7 +32556,7 @@ function migrationDryRun(dbPath) {
|
|
|
32077
32556
|
};
|
|
32078
32557
|
}
|
|
32079
32558
|
function defaultBackupPath(dbPath) {
|
|
32080
|
-
const base = dbPath ? dirname11(
|
|
32559
|
+
const base = dbPath ? dirname11(resolve14(dbPath)) : dirname11(resolveDbPath());
|
|
32081
32560
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
32082
32561
|
return join15(base, "backups", `todos-${stamp}.db`);
|
|
32083
32562
|
}
|
|
@@ -35741,8 +36220,8 @@ todos machines topology # full diagnostic report
|
|
|
35741
36220
|
import { createHash as createHash12 } from "crypto";
|
|
35742
36221
|
import { existsSync as existsSync20, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
|
|
35743
36222
|
import { hostname as hostname3, platform, arch } from "os";
|
|
35744
|
-
import { dirname as dirname15, join as join17, resolve as
|
|
35745
|
-
import { tmpdir as
|
|
36223
|
+
import { dirname as dirname15, join as join17, resolve as resolve15 } from "path";
|
|
36224
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
35746
36225
|
init_database();
|
|
35747
36226
|
init_redaction();
|
|
35748
36227
|
init_sync_utils();
|
|
@@ -35874,15 +36353,15 @@ function commandEnv(env, includeValues) {
|
|
|
35874
36353
|
function defaultSnapshotDir() {
|
|
35875
36354
|
const dbPath = getDatabasePath();
|
|
35876
36355
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
35877
|
-
return join17(
|
|
35878
|
-
return join17(dirname15(
|
|
36356
|
+
return join17(tmpdir3(), "hasna-todos", "environment-snapshots");
|
|
36357
|
+
return join17(dirname15(resolve15(dbPath)), "environment-snapshots");
|
|
35879
36358
|
}
|
|
35880
36359
|
function snapshotWithId(snapshot) {
|
|
35881
36360
|
const digest = sha2565(JSON.stringify(snapshot)).slice(0, 24);
|
|
35882
36361
|
return { id: `env_${digest}`, ...snapshot };
|
|
35883
36362
|
}
|
|
35884
36363
|
function captureEnvironmentSnapshot(input = {}) {
|
|
35885
|
-
const root =
|
|
36364
|
+
const root = resolve15(input.root || process.cwd());
|
|
35886
36365
|
const env = input.env || process.env;
|
|
35887
36366
|
const warnings = [];
|
|
35888
36367
|
const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
|
|
@@ -35922,13 +36401,13 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
35922
36401
|
});
|
|
35923
36402
|
}
|
|
35924
36403
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
35925
|
-
const path = outputPath ?
|
|
36404
|
+
const path = outputPath ? resolve15(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
35926
36405
|
ensureDir2(dirname15(path));
|
|
35927
36406
|
writeJsonFile(path, snapshot);
|
|
35928
36407
|
return path;
|
|
35929
36408
|
}
|
|
35930
36409
|
function readEnvironmentSnapshot(path) {
|
|
35931
|
-
const snapshot = readJsonFile(
|
|
36410
|
+
const snapshot = readJsonFile(resolve15(path));
|
|
35932
36411
|
if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
|
|
35933
36412
|
throw new Error(`Invalid environment snapshot: ${path}`);
|
|
35934
36413
|
}
|
|
@@ -37667,7 +38146,7 @@ function listLinkedTemplates(db, cwd) {
|
|
|
37667
38146
|
init_database();
|
|
37668
38147
|
import { mkdtempSync } from "fs";
|
|
37669
38148
|
import { join as join21 } from "path";
|
|
37670
|
-
import { tmpdir as
|
|
38149
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
37671
38150
|
var AGENT_WORKFLOW_DEMO_SCHEMA = "todos.agent_workflow_demo.v1";
|
|
37672
38151
|
var DEMO_DEFAULT_AGENT = "demoagent";
|
|
37673
38152
|
var DEMO_DEFAULT_PROJECT = "Agent Workflow Demo";
|
|
@@ -37682,7 +38161,7 @@ function setupEphemeralDemoDb(options = {}) {
|
|
|
37682
38161
|
if (options.db_path) {
|
|
37683
38162
|
db_path = options.db_path;
|
|
37684
38163
|
} else if (options.persist) {
|
|
37685
|
-
db_path = join21(mkdtempSync(join21(
|
|
38164
|
+
db_path = join21(mkdtempSync(join21(tmpdir4(), "todos-demo-")), "todos.db");
|
|
37686
38165
|
} else {
|
|
37687
38166
|
db_path = ":memory:";
|
|
37688
38167
|
}
|
|
@@ -38304,11 +38783,6 @@ var ALL_MCP_TOOLS = [
|
|
|
38304
38783
|
"template_history",
|
|
38305
38784
|
"todos_inbox",
|
|
38306
38785
|
"todos_retro",
|
|
38307
|
-
"todos_storage_conflicts",
|
|
38308
|
-
"todos_storage_feedback",
|
|
38309
|
-
"todos_storage_pull",
|
|
38310
|
-
"todos_storage_push",
|
|
38311
|
-
"todos_storage_status",
|
|
38312
38786
|
"trust_workspace",
|
|
38313
38787
|
"unarchive_agent",
|
|
38314
38788
|
"unarchive_task",
|
|
@@ -38445,12 +38919,6 @@ var MCP_GROUP_DEFS = [
|
|
|
38445
38919
|
description: "Sandbox, trust, secrets, crypto, and verification.",
|
|
38446
38920
|
match: (t) => /sandbox|trust|secret|crypto|verification|policy_pack|redact|scan_/.test(t)
|
|
38447
38921
|
},
|
|
38448
|
-
{
|
|
38449
|
-
id: "storage",
|
|
38450
|
-
name: "Storage bridge",
|
|
38451
|
-
description: "Optional storage sync tools (admin profile).",
|
|
38452
|
-
match: (t) => t.startsWith("todos_storage_") || t === "sync_all" || t === "migrate_pg"
|
|
38453
|
-
},
|
|
38454
38922
|
{
|
|
38455
38923
|
id: "workflow",
|
|
38456
38924
|
name: "Workflow utilities",
|
|
@@ -40541,7 +41009,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
40541
41009
|
// src/lib/extract.ts
|
|
40542
41010
|
import { existsSync as existsSync25, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
|
|
40543
41011
|
import { createHash as createHash14 } from "crypto";
|
|
40544
|
-
import { relative as relative6, resolve as
|
|
41012
|
+
import { relative as relative6, resolve as resolve16, join as join24 } from "path";
|
|
40545
41013
|
var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
|
|
40546
41014
|
var DEFAULT_EXTENSIONS = new Set([
|
|
40547
41015
|
".ts",
|
|
@@ -40611,7 +41079,7 @@ function normalizePathForMatch(value) {
|
|
|
40611
41079
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
40612
41080
|
}
|
|
40613
41081
|
function readGitignorePatterns(basePath) {
|
|
40614
|
-
const root = statSync8(basePath).isFile() ?
|
|
41082
|
+
const root = statSync8(basePath).isFile() ? resolve16(basePath, "..") : basePath;
|
|
40615
41083
|
const gitignorePath = join24(root, ".gitignore");
|
|
40616
41084
|
if (!existsSync25(gitignorePath))
|
|
40617
41085
|
return [];
|
|
@@ -40747,7 +41215,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
|
|
|
40747
41215
|
return files.sort();
|
|
40748
41216
|
}
|
|
40749
41217
|
function buildCodebaseIndex(options) {
|
|
40750
|
-
const basePath =
|
|
41218
|
+
const basePath = resolve16(options.path);
|
|
40751
41219
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
40752
41220
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
40753
41221
|
const excludes = options.exclude || [];
|
|
@@ -40758,7 +41226,7 @@ function buildCodebaseIndex(options) {
|
|
|
40758
41226
|
const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
|
|
40759
41227
|
try {
|
|
40760
41228
|
const source9 = readFileSync23(fullPath, "utf-8");
|
|
40761
|
-
const relPath = statSync8(basePath).isFile() ? relative6(
|
|
41229
|
+
const relPath = statSync8(basePath).isFile() ? relative6(resolve16(basePath, ".."), fullPath) : file;
|
|
40762
41230
|
indexed.push({
|
|
40763
41231
|
file: relPath,
|
|
40764
41232
|
checksum: stableHash(source9).slice(0, 24),
|
|
@@ -40778,7 +41246,7 @@ function buildCodebaseIndex(options) {
|
|
|
40778
41246
|
};
|
|
40779
41247
|
}
|
|
40780
41248
|
function extractTodos(options, db) {
|
|
40781
|
-
const basePath =
|
|
41249
|
+
const basePath = resolve16(options.path);
|
|
40782
41250
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
40783
41251
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
40784
41252
|
const excludes = options.exclude || [];
|
|
@@ -40789,7 +41257,7 @@ function extractTodos(options, db) {
|
|
|
40789
41257
|
const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
|
|
40790
41258
|
try {
|
|
40791
41259
|
const source9 = readFileSync23(fullPath, "utf-8");
|
|
40792
|
-
const relPath = statSync8(basePath).isFile() ? relative6(
|
|
41260
|
+
const relPath = statSync8(basePath).isFile() ? relative6(resolve16(basePath, ".."), fullPath) : file;
|
|
40793
41261
|
const comments = extractFromSource(source9, relPath, tags);
|
|
40794
41262
|
allComments.push(...comments);
|
|
40795
41263
|
} catch {}
|
|
@@ -40883,7 +41351,7 @@ async function watchSourceTodos(options, onRun) {
|
|
|
40883
41351
|
const interval = Math.max(100, options.interval_ms || 2000);
|
|
40884
41352
|
const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
|
|
40885
41353
|
const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
|
|
40886
|
-
const root =
|
|
41354
|
+
const root = resolve16(options.path);
|
|
40887
41355
|
const runs = [];
|
|
40888
41356
|
let previous = new Map;
|
|
40889
41357
|
for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
|
|
@@ -41756,7 +42224,7 @@ function renderAgentReplaySimulationMarkdown(simulation) {
|
|
|
41756
42224
|
init_config();
|
|
41757
42225
|
import { createHash as createHash16, createVerify } from "crypto";
|
|
41758
42226
|
import { existsSync as existsSync26, readdirSync as readdirSync5, readFileSync as readFileSync25, statSync as statSync9 } from "fs";
|
|
41759
|
-
import { basename as basename5, join as join25, resolve as
|
|
42227
|
+
import { basename as basename5, join as join25, resolve as resolve17 } from "path";
|
|
41760
42228
|
init_redaction();
|
|
41761
42229
|
function isObject2(value) {
|
|
41762
42230
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -42036,7 +42504,7 @@ function verifyExtensionSignature(input) {
|
|
|
42036
42504
|
return verifier.verify(input.public_key, decodeSignature(input.signature));
|
|
42037
42505
|
}
|
|
42038
42506
|
function inspectExtensionSource(source9) {
|
|
42039
|
-
const resolved =
|
|
42507
|
+
const resolved = resolve17(source9);
|
|
42040
42508
|
if (!existsSync26(resolved))
|
|
42041
42509
|
throw new Error(`extension source not found: ${source9}`);
|
|
42042
42510
|
const stat = statSync9(resolved);
|
|
@@ -42134,7 +42602,7 @@ function testExtensionCompatibility(sourceOrManifest) {
|
|
|
42134
42602
|
function projectExtensionSources(projectPath) {
|
|
42135
42603
|
if (!projectPath)
|
|
42136
42604
|
return [];
|
|
42137
|
-
const root =
|
|
42605
|
+
const root = resolve17(projectPath);
|
|
42138
42606
|
const candidates = [
|
|
42139
42607
|
join25(root, "todos.extension.json"),
|
|
42140
42608
|
join25(root, ".todos", "todos.extension.json")
|
|
@@ -42153,7 +42621,7 @@ function projectExtensionSources(projectPath) {
|
|
|
42153
42621
|
}
|
|
42154
42622
|
function discoverLocalExtensions(options = {}) {
|
|
42155
42623
|
const config = loadConfig();
|
|
42156
|
-
const projectPath = options.project_path ?
|
|
42624
|
+
const projectPath = options.project_path ? resolve17(options.project_path) : null;
|
|
42157
42625
|
const configuredSources = [
|
|
42158
42626
|
...config.extension_sources || [],
|
|
42159
42627
|
...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
|
|
@@ -42161,7 +42629,7 @@ function discoverLocalExtensions(options = {}) {
|
|
|
42161
42629
|
const sources = Array.from(new Set([
|
|
42162
42630
|
...configuredSources,
|
|
42163
42631
|
...projectExtensionSources(projectPath || undefined)
|
|
42164
|
-
])).map((source9) => projectPath && !source9.startsWith("/") ?
|
|
42632
|
+
])).map((source9) => projectPath && !source9.startsWith("/") ? resolve17(projectPath, source9) : resolve17(source9));
|
|
42165
42633
|
const warnings = [];
|
|
42166
42634
|
const discovered = [];
|
|
42167
42635
|
for (const source9 of sources) {
|