@hasna/todos 0.11.65 → 0.11.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -49
- package/dashboard/dist/assets/{index-aJefI7kh.js → index-DVotjwab.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/storage-commands.d.ts +5 -0
- package/dist/cli/commands/storage-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1034 -231
- package/dist/contracts.js +409 -129
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/plans.d.ts.map +1 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/storage-tombstones.d.ts +26 -0
- package/dist/db/storage-tombstones.d.ts.map +1 -0
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/task-lists.d.ts.map +1 -1
- package/dist/db/task-runs.d.ts.map +1 -1
- package/dist/db/templates.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +928 -219
- package/dist/lib/approval-gates.d.ts.map +1 -1
- package/dist/lib/event-emission-safety.d.ts +9 -0
- package/dist/lib/event-emission-safety.d.ts.map +1 -0
- package/dist/lib/event-hooks.d.ts +1 -0
- package/dist/lib/event-hooks.d.ts.map +1 -1
- package/dist/lib/feature-manifest.d.ts.map +1 -1
- package/dist/lib/project-bootstrap.d.ts +1 -0
- package/dist/lib/project-bootstrap.d.ts.map +1 -1
- package/dist/lib/review-queues.d.ts.map +1 -1
- package/dist/lib/shared-events.d.ts +1 -0
- package/dist/lib/shared-events.d.ts.map +1 -1
- package/dist/lib/task-route-contract.d.ts +33 -0
- package/dist/lib/task-route-contract.d.ts.map +1 -0
- package/dist/lib/task-routing.d.ts +55 -0
- package/dist/lib/task-routing.d.ts.map +1 -0
- package/dist/mcp/index.js +459 -162
- package/dist/registry.js +409 -129
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +482 -185
- package/dist/storage/interfaces.d.ts +20 -0
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/postgres-sync.d.ts +6 -1
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
- package/dist/storage.js +677 -152
- package/dist/types/index.d.ts +22 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/registry.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,
|
|
@@ -6068,6 +6098,80 @@ init_config();
|
|
|
6068
6098
|
init_types();
|
|
6069
6099
|
init_database();
|
|
6070
6100
|
init_machines();
|
|
6101
|
+
|
|
6102
|
+
// src/db/storage-tombstones.ts
|
|
6103
|
+
init_database();
|
|
6104
|
+
init_machines();
|
|
6105
|
+
function recordStorageTombstone(input, db) {
|
|
6106
|
+
const d = db ?? getDatabase();
|
|
6107
|
+
const deletedAt = input.deleted_at ?? now();
|
|
6108
|
+
const machineId = input.source_machine_id ?? currentStorageMachineId(d);
|
|
6109
|
+
d.run(`INSERT INTO storage_tombstones (
|
|
6110
|
+
id, object_type, object_id, deleted_at, updated_at, source_machine_id, payload, version
|
|
6111
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
6112
|
+
ON CONFLICT(object_type, object_id) DO UPDATE SET
|
|
6113
|
+
deleted_at = excluded.deleted_at,
|
|
6114
|
+
updated_at = excluded.updated_at,
|
|
6115
|
+
source_machine_id = excluded.source_machine_id,
|
|
6116
|
+
payload = excluded.payload,
|
|
6117
|
+
version = excluded.version
|
|
6118
|
+
WHERE storage_tombstones.updated_at IS NULL OR storage_tombstones.updated_at <= excluded.updated_at`, [
|
|
6119
|
+
uuid(),
|
|
6120
|
+
input.object_type,
|
|
6121
|
+
input.object_id,
|
|
6122
|
+
deletedAt,
|
|
6123
|
+
deletedAt,
|
|
6124
|
+
machineId,
|
|
6125
|
+
input.payload ? JSON.stringify(input.payload) : null,
|
|
6126
|
+
input.version ?? null
|
|
6127
|
+
]);
|
|
6128
|
+
return getStorageTombstone(input.object_type, input.object_id, d);
|
|
6129
|
+
}
|
|
6130
|
+
function getStorageTombstone(objectType, objectId, db) {
|
|
6131
|
+
const d = db ?? getDatabase();
|
|
6132
|
+
const row = d.query("SELECT * FROM storage_tombstones WHERE object_type = ? AND object_id = ?").get(objectType, objectId);
|
|
6133
|
+
return row ? rowToStorageTombstone(row) : null;
|
|
6134
|
+
}
|
|
6135
|
+
function listStorageTombstones(db) {
|
|
6136
|
+
const d = db ?? getDatabase();
|
|
6137
|
+
return d.query("SELECT * FROM storage_tombstones ORDER BY updated_at ASC, object_type ASC, object_id ASC").all().map(rowToStorageTombstone);
|
|
6138
|
+
}
|
|
6139
|
+
function shouldApplyStorageTombstone(tombstone, existingUpdatedAt) {
|
|
6140
|
+
const tombstoneClock = Date.parse(tombstone.updated_at || tombstone.deleted_at);
|
|
6141
|
+
if (!existingUpdatedAt)
|
|
6142
|
+
return true;
|
|
6143
|
+
const existingClock = Date.parse(existingUpdatedAt);
|
|
6144
|
+
if (Number.isNaN(tombstoneClock))
|
|
6145
|
+
return true;
|
|
6146
|
+
if (Number.isNaN(existingClock))
|
|
6147
|
+
return true;
|
|
6148
|
+
return tombstoneClock >= existingClock;
|
|
6149
|
+
}
|
|
6150
|
+
function rowToStorageTombstone(row) {
|
|
6151
|
+
return {
|
|
6152
|
+
...row,
|
|
6153
|
+
payload: parsePayload(row.payload)
|
|
6154
|
+
};
|
|
6155
|
+
}
|
|
6156
|
+
function parsePayload(value) {
|
|
6157
|
+
if (!value)
|
|
6158
|
+
return null;
|
|
6159
|
+
try {
|
|
6160
|
+
const parsed = JSON.parse(value);
|
|
6161
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
6162
|
+
} catch {
|
|
6163
|
+
return null;
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
function currentStorageMachineId(db) {
|
|
6167
|
+
try {
|
|
6168
|
+
return getMachineId(db);
|
|
6169
|
+
} catch {
|
|
6170
|
+
return null;
|
|
6171
|
+
}
|
|
6172
|
+
}
|
|
6173
|
+
|
|
6174
|
+
// src/db/projects.ts
|
|
6071
6175
|
function slugify(name) {
|
|
6072
6176
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
6073
6177
|
}
|
|
@@ -6097,8 +6201,9 @@ function createProject(input, db) {
|
|
|
6097
6201
|
const timestamp = now();
|
|
6098
6202
|
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
6099
6203
|
const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
|
|
6100
|
-
|
|
6101
|
-
|
|
6204
|
+
const machineId = currentStorageMachineId(d);
|
|
6205
|
+
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
|
|
6206
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
|
|
6102
6207
|
return getProject(id, d);
|
|
6103
6208
|
}
|
|
6104
6209
|
function getProject(id, db) {
|
|
@@ -6177,6 +6282,14 @@ function renameProject(id, input, db) {
|
|
|
6177
6282
|
}
|
|
6178
6283
|
function deleteProject(id, db) {
|
|
6179
6284
|
const d = db || getDatabase();
|
|
6285
|
+
const project = getProject(id, d);
|
|
6286
|
+
if (!project)
|
|
6287
|
+
return false;
|
|
6288
|
+
recordStorageTombstone({
|
|
6289
|
+
object_type: "projects",
|
|
6290
|
+
object_id: id,
|
|
6291
|
+
payload: project
|
|
6292
|
+
}, d);
|
|
6180
6293
|
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
6181
6294
|
return result.changes > 0;
|
|
6182
6295
|
}
|
|
@@ -6281,6 +6394,14 @@ function listMachineLocalPaths(projectId, db) {
|
|
|
6281
6394
|
function removeMachineLocalPath(projectId, machineId, db) {
|
|
6282
6395
|
const d = db || getDatabase();
|
|
6283
6396
|
const mid = machineId ?? getMachineId(d);
|
|
6397
|
+
const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, mid);
|
|
6398
|
+
if (!existing)
|
|
6399
|
+
return false;
|
|
6400
|
+
recordStorageTombstone({
|
|
6401
|
+
object_type: "project_machine_paths",
|
|
6402
|
+
object_id: existing.id,
|
|
6403
|
+
payload: existing
|
|
6404
|
+
}, d);
|
|
6284
6405
|
const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
|
|
6285
6406
|
return result.changes > 0;
|
|
6286
6407
|
}
|
|
@@ -6329,20 +6450,69 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
|
6329
6450
|
}
|
|
6330
6451
|
}
|
|
6331
6452
|
|
|
6453
|
+
// src/lib/event-emission-safety.ts
|
|
6454
|
+
init_database();
|
|
6455
|
+
init_sync_utils();
|
|
6456
|
+
import { tmpdir } from "os";
|
|
6457
|
+
import { resolve as resolve3, sep } from "path";
|
|
6458
|
+
function envFlag(name) {
|
|
6459
|
+
const value = process.env[name];
|
|
6460
|
+
return value === "1" || value === "true" || value === "yes";
|
|
6461
|
+
}
|
|
6462
|
+
function isUnder(parent, child) {
|
|
6463
|
+
const normalizedParent = resolve3(parent);
|
|
6464
|
+
const normalizedChild = resolve3(child);
|
|
6465
|
+
return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
|
|
6466
|
+
}
|
|
6467
|
+
function databasePathFromDatabase(db) {
|
|
6468
|
+
const filename = db?.filename;
|
|
6469
|
+
return typeof filename === "string" && filename.trim() ? filename : undefined;
|
|
6470
|
+
}
|
|
6471
|
+
function isEphemeralTodosDatabase(dbPath) {
|
|
6472
|
+
const resolvedPath = dbPath ?? getDatabasePath();
|
|
6473
|
+
if (resolvedPath === ":memory:" || resolvedPath.startsWith("file::memory:"))
|
|
6474
|
+
return true;
|
|
6475
|
+
return isUnder(tmpdir(), resolvedPath);
|
|
6476
|
+
}
|
|
6477
|
+
function hasExplicitSharedEventsStore() {
|
|
6478
|
+
return Boolean(process.env["HASNA_EVENTS_DIR"] || process.env["HASNA_EVENTS_HOME"]);
|
|
6479
|
+
}
|
|
6480
|
+
function usesIsolatedTodosHome() {
|
|
6481
|
+
return isUnder(tmpdir(), getTodosGlobalDir());
|
|
6482
|
+
}
|
|
6483
|
+
function shouldEmitSharedTaskEvents(dbPath) {
|
|
6484
|
+
if (envFlag("TODOS_DISABLE_SHARED_EVENTS"))
|
|
6485
|
+
return false;
|
|
6486
|
+
if (envFlag("TODOS_ALLOW_EPHEMERAL_SHARED_EVENTS"))
|
|
6487
|
+
return true;
|
|
6488
|
+
if (!isEphemeralTodosDatabase(dbPath))
|
|
6489
|
+
return true;
|
|
6490
|
+
return hasExplicitSharedEventsStore();
|
|
6491
|
+
}
|
|
6492
|
+
function shouldDeliverLocalLifecycleHooks(dbPath) {
|
|
6493
|
+
if (envFlag("TODOS_DISABLE_LOCAL_EVENT_HOOKS"))
|
|
6494
|
+
return false;
|
|
6495
|
+
if (envFlag("TODOS_ALLOW_EPHEMERAL_LOCAL_EVENT_HOOKS"))
|
|
6496
|
+
return true;
|
|
6497
|
+
if (!isEphemeralTodosDatabase(dbPath))
|
|
6498
|
+
return true;
|
|
6499
|
+
return usesIsolatedTodosHome();
|
|
6500
|
+
}
|
|
6501
|
+
|
|
6332
6502
|
// src/lib/event-hooks.ts
|
|
6333
6503
|
init_redaction();
|
|
6334
6504
|
import { createHash, randomUUID } from "crypto";
|
|
6335
6505
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
6336
|
-
import { dirname as dirname4, resolve as
|
|
6506
|
+
import { dirname as dirname4, resolve as resolve6 } from "path";
|
|
6337
6507
|
import { createConnection } from "net";
|
|
6338
6508
|
|
|
6339
6509
|
// src/lib/runner-sandbox.ts
|
|
6340
6510
|
init_config();
|
|
6341
|
-
import { relative as relative2, resolve as
|
|
6511
|
+
import { relative as relative2, resolve as resolve5 } from "path";
|
|
6342
6512
|
|
|
6343
6513
|
// src/lib/workspace-trust.ts
|
|
6344
6514
|
init_config();
|
|
6345
|
-
import { relative, resolve as
|
|
6515
|
+
import { relative, resolve as resolve4 } from "path";
|
|
6346
6516
|
var DEFAULT_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
6347
6517
|
var DEFAULT_ENV_REDACTIONS = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
6348
6518
|
var PRESET_DEFAULTS = {
|
|
@@ -6388,7 +6558,7 @@ var PRESET_DEFAULTS = {
|
|
|
6388
6558
|
}
|
|
6389
6559
|
};
|
|
6390
6560
|
function normalizePath(path) {
|
|
6391
|
-
return
|
|
6561
|
+
return resolve4(path);
|
|
6392
6562
|
}
|
|
6393
6563
|
function unique2(values) {
|
|
6394
6564
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -6525,7 +6695,7 @@ function checkWorkspacePermission(input = {}) {
|
|
|
6525
6695
|
var DEFAULT_COMMAND_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
6526
6696
|
var DEFAULT_ENV_REDACTIONS2 = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
6527
6697
|
function normalizePath2(path) {
|
|
6528
|
-
return
|
|
6698
|
+
return resolve5(path);
|
|
6529
6699
|
}
|
|
6530
6700
|
function unique3(values) {
|
|
6531
6701
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -6871,7 +7041,7 @@ async function deliverHook(hook, envelope) {
|
|
|
6871
7041
|
if (hook.target === "stdout") {
|
|
6872
7042
|
output = line.trim();
|
|
6873
7043
|
} else if (hook.target === "file") {
|
|
6874
|
-
const filePath =
|
|
7044
|
+
const filePath = resolve6(hook.file_path);
|
|
6875
7045
|
mkdirSync3(dirname4(filePath), { recursive: true });
|
|
6876
7046
|
appendFileSync(filePath, line);
|
|
6877
7047
|
} else if (hook.target === "socket") {
|
|
@@ -6946,6 +7116,8 @@ async function emitLocalEventHooks(input) {
|
|
|
6946
7116
|
return Promise.all(hooks.map((hook) => deliverHook(hook, envelope)));
|
|
6947
7117
|
}
|
|
6948
7118
|
function emitLocalEventHooksQuiet(input) {
|
|
7119
|
+
if (!shouldDeliverLocalLifecycleHooks(input.databasePath))
|
|
7120
|
+
return;
|
|
6949
7121
|
emitLocalEventHooks(input).catch(() => {});
|
|
6950
7122
|
}
|
|
6951
7123
|
async function testLocalEventHook(name, input) {
|
|
@@ -7262,7 +7434,7 @@ async function dispatchCommand(event, channel) {
|
|
|
7262
7434
|
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
7263
7435
|
HASNA_EVENT_JSON: eventJson
|
|
7264
7436
|
};
|
|
7265
|
-
return new Promise((
|
|
7437
|
+
return new Promise((resolve7) => {
|
|
7266
7438
|
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
7267
7439
|
cwd: channel.command.cwd,
|
|
7268
7440
|
env,
|
|
@@ -7280,7 +7452,7 @@ async function dispatchCommand(event, channel) {
|
|
|
7280
7452
|
});
|
|
7281
7453
|
child.on("error", (error) => {
|
|
7282
7454
|
clearTimeout(timeout);
|
|
7283
|
-
|
|
7455
|
+
resolve7({
|
|
7284
7456
|
attempt: 1,
|
|
7285
7457
|
status: "failed",
|
|
7286
7458
|
startedAt,
|
|
@@ -7293,7 +7465,7 @@ async function dispatchCommand(event, channel) {
|
|
|
7293
7465
|
child.on("close", (code, signal) => {
|
|
7294
7466
|
clearTimeout(timeout);
|
|
7295
7467
|
const success = code === 0;
|
|
7296
|
-
|
|
7468
|
+
resolve7({
|
|
7297
7469
|
attempt: 1,
|
|
7298
7470
|
status: success ? "success" : "failed",
|
|
7299
7471
|
startedAt,
|
|
@@ -7561,14 +7733,15 @@ function createTaskList(input, db) {
|
|
|
7561
7733
|
const id = uuid();
|
|
7562
7734
|
const timestamp = now();
|
|
7563
7735
|
const slug = input.slug || slugify(input.name);
|
|
7736
|
+
const machineId = currentStorageMachineId(d);
|
|
7564
7737
|
if (!input.project_id) {
|
|
7565
7738
|
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
7566
7739
|
if (existing) {
|
|
7567
7740
|
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
7568
7741
|
}
|
|
7569
7742
|
}
|
|
7570
|
-
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
7571
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
7743
|
+
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
|
|
7744
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
|
|
7572
7745
|
return getTaskList(id, d);
|
|
7573
7746
|
}
|
|
7574
7747
|
function getTaskList(id, db) {
|
|
@@ -7618,6 +7791,14 @@ function updateTaskList(id, input, db) {
|
|
|
7618
7791
|
}
|
|
7619
7792
|
function deleteTaskList(id, db) {
|
|
7620
7793
|
const d = db || getDatabase();
|
|
7794
|
+
const list = getTaskList(id, d);
|
|
7795
|
+
if (!list)
|
|
7796
|
+
return false;
|
|
7797
|
+
recordStorageTombstone({
|
|
7798
|
+
object_type: "task_lists",
|
|
7799
|
+
object_id: id,
|
|
7800
|
+
payload: list
|
|
7801
|
+
}, d);
|
|
7621
7802
|
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
7622
7803
|
}
|
|
7623
7804
|
function ensureTaskList(name, slug, projectId, db) {
|
|
@@ -7628,39 +7809,9 @@ function ensureTaskList(name, slug, projectId, db) {
|
|
|
7628
7809
|
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
7629
7810
|
}
|
|
7630
7811
|
|
|
7631
|
-
// src/lib/
|
|
7632
|
-
var
|
|
7633
|
-
|
|
7634
|
-
return {
|
|
7635
|
-
id: task.id,
|
|
7636
|
-
task_id: task.id,
|
|
7637
|
-
short_id: task.short_id,
|
|
7638
|
-
title: task.title,
|
|
7639
|
-
description: task.description,
|
|
7640
|
-
status: task.status,
|
|
7641
|
-
priority: task.priority,
|
|
7642
|
-
project_id: task.project_id,
|
|
7643
|
-
parent_id: task.parent_id,
|
|
7644
|
-
plan_id: task.plan_id,
|
|
7645
|
-
task_list_id: task.task_list_id,
|
|
7646
|
-
agent_id: task.agent_id,
|
|
7647
|
-
assigned_to: task.assigned_to,
|
|
7648
|
-
session_id: task.session_id,
|
|
7649
|
-
working_dir: task.working_dir,
|
|
7650
|
-
tags: task.tags,
|
|
7651
|
-
metadata: task.metadata,
|
|
7652
|
-
version: task.version,
|
|
7653
|
-
created_at: task.created_at,
|
|
7654
|
-
updated_at: task.updated_at,
|
|
7655
|
-
started_at: task.started_at,
|
|
7656
|
-
completed_at: task.completed_at,
|
|
7657
|
-
due_at: task.due_at,
|
|
7658
|
-
requires_approval: task.requires_approval,
|
|
7659
|
-
approved_by: task.approved_by,
|
|
7660
|
-
approved_at: task.approved_at,
|
|
7661
|
-
...extra
|
|
7662
|
-
};
|
|
7663
|
-
}
|
|
7812
|
+
// src/lib/task-route-contract.ts
|
|
7813
|
+
var TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION = "todos.task_route_state.v1";
|
|
7814
|
+
var TASK_WORKFLOW_POINTER_SCHEMA_VERSION = "todos.task_workflow_pointer.v1";
|
|
7664
7815
|
function booleanField(value) {
|
|
7665
7816
|
if (typeof value === "boolean")
|
|
7666
7817
|
return value;
|
|
@@ -7682,21 +7833,31 @@ function booleanField(value) {
|
|
|
7682
7833
|
function objectField(value) {
|
|
7683
7834
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
7684
7835
|
}
|
|
7685
|
-
function
|
|
7836
|
+
function collectBooleans(records, keys) {
|
|
7837
|
+
const values = [];
|
|
7686
7838
|
for (const record of records) {
|
|
7839
|
+
if (!record)
|
|
7840
|
+
continue;
|
|
7687
7841
|
for (const key of keys) {
|
|
7688
7842
|
const value = booleanField(record[key]);
|
|
7689
7843
|
if (value !== undefined)
|
|
7690
|
-
|
|
7844
|
+
values.push(value);
|
|
7691
7845
|
}
|
|
7692
7846
|
}
|
|
7693
|
-
return;
|
|
7847
|
+
return values;
|
|
7694
7848
|
}
|
|
7695
|
-
function
|
|
7696
|
-
const
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7849
|
+
function mergedBoolean(records, keys, trueWins) {
|
|
7850
|
+
const values = collectBooleans(records, keys);
|
|
7851
|
+
if (values.length === 0)
|
|
7852
|
+
return;
|
|
7853
|
+
if (trueWins)
|
|
7854
|
+
return values.some(Boolean);
|
|
7855
|
+
return values.includes(false) ? false : true;
|
|
7856
|
+
}
|
|
7857
|
+
function routingAutomationMetadata(task, taskList) {
|
|
7858
|
+
const taskAutomation = objectField(task.metadata.automation);
|
|
7859
|
+
const taskListAutomation = taskList ? objectField(taskList.metadata.automation) : undefined;
|
|
7860
|
+
const records = [task.metadata, taskAutomation, taskList?.metadata, taskListAutomation];
|
|
7700
7861
|
const result = {};
|
|
7701
7862
|
const aliases = [
|
|
7702
7863
|
["allowed", ["allowed", "automation_allowed", "automationAllowed"]],
|
|
@@ -7707,7 +7868,7 @@ function routingAutomationMetadata(task) {
|
|
|
7707
7868
|
["approval_required", ["approval_required", "approvalRequired"]]
|
|
7708
7869
|
];
|
|
7709
7870
|
for (const [canonical, keys] of aliases) {
|
|
7710
|
-
const value =
|
|
7871
|
+
const value = mergedBoolean(records, keys, canonical !== "allowed");
|
|
7711
7872
|
if (value !== undefined)
|
|
7712
7873
|
result[canonical] = value;
|
|
7713
7874
|
}
|
|
@@ -7715,23 +7876,91 @@ function routingAutomationMetadata(task) {
|
|
|
7715
7876
|
result.requires_approval = true;
|
|
7716
7877
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
7717
7878
|
}
|
|
7879
|
+
function routeEnabledForTask(task, taskList) {
|
|
7880
|
+
const explicit = booleanField(task.metadata.route_enabled);
|
|
7881
|
+
if (explicit !== undefined)
|
|
7882
|
+
return explicit;
|
|
7883
|
+
if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
|
|
7884
|
+
return true;
|
|
7885
|
+
const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
|
|
7886
|
+
if (taskListDefault !== undefined)
|
|
7887
|
+
return taskListDefault;
|
|
7888
|
+
return;
|
|
7889
|
+
}
|
|
7890
|
+
function stringField(value) {
|
|
7891
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
7892
|
+
}
|
|
7893
|
+
function workflowPointersFromMetadata(metadata) {
|
|
7894
|
+
const nested = objectField(metadata.workflow_invocation) ?? objectField(metadata.workflow) ?? {};
|
|
7895
|
+
return {
|
|
7896
|
+
current_workflow_invocation_id: stringField(metadata.current_workflow_invocation_id) ?? stringField(nested.current_workflow_invocation_id) ?? stringField(nested.invocation_id),
|
|
7897
|
+
current_run_id: stringField(metadata.current_run_id) ?? stringField(nested.current_run_id) ?? stringField(nested.run_id),
|
|
7898
|
+
latest_manifest_path: stringField(metadata.latest_manifest_path) ?? stringField(nested.latest_manifest_path) ?? stringField(nested.manifest_path),
|
|
7899
|
+
latest_evaluation_path: stringField(metadata.latest_evaluation_path) ?? stringField(nested.latest_evaluation_path) ?? stringField(nested.evaluation_path),
|
|
7900
|
+
workflow_state: stringField(metadata.workflow_state) ?? stringField(nested.workflow_state) ?? stringField(nested.state)
|
|
7901
|
+
};
|
|
7902
|
+
}
|
|
7903
|
+
function compactWorkflowPointers(pointers) {
|
|
7904
|
+
return Object.fromEntries(Object.entries(pointers).filter(([, value]) => typeof value === "string" && value.length > 0));
|
|
7905
|
+
}
|
|
7906
|
+
function classifyProjectKind(path) {
|
|
7907
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
7908
|
+
}
|
|
7909
|
+
function isWorktreePath(path) {
|
|
7910
|
+
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
7911
|
+
}
|
|
7912
|
+
function inferRootProjectId(project) {
|
|
7913
|
+
return isWorktreePath(project.path) ? null : project.id;
|
|
7914
|
+
}
|
|
7915
|
+
|
|
7916
|
+
// src/lib/shared-events.ts
|
|
7917
|
+
var SOURCE = "todos";
|
|
7918
|
+
function taskEventData(task, extra = {}) {
|
|
7919
|
+
return {
|
|
7920
|
+
id: task.id,
|
|
7921
|
+
task_id: task.id,
|
|
7922
|
+
short_id: task.short_id,
|
|
7923
|
+
title: task.title,
|
|
7924
|
+
description: task.description,
|
|
7925
|
+
status: task.status,
|
|
7926
|
+
priority: task.priority,
|
|
7927
|
+
project_id: task.project_id,
|
|
7928
|
+
parent_id: task.parent_id,
|
|
7929
|
+
plan_id: task.plan_id,
|
|
7930
|
+
task_list_id: task.task_list_id,
|
|
7931
|
+
agent_id: task.agent_id,
|
|
7932
|
+
assigned_to: task.assigned_to,
|
|
7933
|
+
session_id: task.session_id,
|
|
7934
|
+
working_dir: task.working_dir,
|
|
7935
|
+
tags: task.tags,
|
|
7936
|
+
metadata: task.metadata,
|
|
7937
|
+
version: task.version,
|
|
7938
|
+
created_at: task.created_at,
|
|
7939
|
+
updated_at: task.updated_at,
|
|
7940
|
+
started_at: task.started_at,
|
|
7941
|
+
completed_at: task.completed_at,
|
|
7942
|
+
due_at: task.due_at,
|
|
7943
|
+
requires_approval: task.requires_approval,
|
|
7944
|
+
approved_by: task.approved_by,
|
|
7945
|
+
approved_at: task.approved_at,
|
|
7946
|
+
...extra
|
|
7947
|
+
};
|
|
7948
|
+
}
|
|
7718
7949
|
function taskEventMetadata(task) {
|
|
7719
7950
|
const metadata = {
|
|
7720
7951
|
package: "@hasna/todos",
|
|
7721
7952
|
todos_event_schema_version: 1,
|
|
7953
|
+
route_state_schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
|
|
7722
7954
|
task_id: task.id,
|
|
7723
7955
|
task_short_id: task.short_id,
|
|
7724
7956
|
project_id: task.project_id,
|
|
7725
7957
|
task_list_id: task.task_list_id,
|
|
7726
7958
|
working_dir: task.working_dir
|
|
7727
7959
|
};
|
|
7728
|
-
const
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
const automation = routingAutomationMetadata(task);
|
|
7733
|
-
if (automation) {
|
|
7734
|
-
metadata.automation = automation;
|
|
7960
|
+
const pointers = workflowPointersFromMetadata(task.metadata);
|
|
7961
|
+
for (const [key, value] of Object.entries(pointers)) {
|
|
7962
|
+
if (value)
|
|
7963
|
+
metadata[key] = value;
|
|
7735
7964
|
}
|
|
7736
7965
|
try {
|
|
7737
7966
|
const project = task.project_id ? getProject(task.project_id) : null;
|
|
@@ -7760,18 +7989,20 @@ function taskEventMetadata(task) {
|
|
|
7760
7989
|
metadata.task_list_project_id = taskList.project_id;
|
|
7761
7990
|
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
7762
7991
|
}
|
|
7992
|
+
const routeEnabled = routeEnabledForTask(task, taskList);
|
|
7993
|
+
if (routeEnabled !== undefined) {
|
|
7994
|
+
metadata.route_enabled = routeEnabled;
|
|
7995
|
+
}
|
|
7996
|
+
const automation = routingAutomationMetadata(task, taskList);
|
|
7997
|
+
if (automation) {
|
|
7998
|
+
metadata.automation = automation;
|
|
7999
|
+
metadata.route_blocked_by_no_auto = automation.no_auto === true;
|
|
8000
|
+
metadata.route_blocked_by_manual = automation.manual === true || automation.manual_required === true;
|
|
8001
|
+
metadata.route_blocked_by_approval = (automation.requires_approval === true || automation.approval_required === true) && !task.approved_by;
|
|
8002
|
+
}
|
|
7763
8003
|
} catch {}
|
|
7764
8004
|
return metadata;
|
|
7765
8005
|
}
|
|
7766
|
-
function classifyProjectKind(path) {
|
|
7767
|
-
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
7768
|
-
}
|
|
7769
|
-
function isWorktreePath(path) {
|
|
7770
|
-
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
7771
|
-
}
|
|
7772
|
-
function inferRootProjectId(project) {
|
|
7773
|
-
return isWorktreePath(project.path) ? null : project.id;
|
|
7774
|
-
}
|
|
7775
8006
|
function readMachineLocalPath(project) {
|
|
7776
8007
|
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
7777
8008
|
if (!machineId)
|
|
@@ -7784,6 +8015,8 @@ function readMachineLocalPath(project) {
|
|
|
7784
8015
|
}
|
|
7785
8016
|
}
|
|
7786
8017
|
async function emitSharedTaskEvent(input) {
|
|
8018
|
+
if (!shouldEmitSharedTaskEvents(input.databasePath))
|
|
8019
|
+
return;
|
|
7787
8020
|
const data = taskEventData(input.task, input.data);
|
|
7788
8021
|
await new EventsClient().emit({
|
|
7789
8022
|
source: SOURCE,
|
|
@@ -7808,8 +8041,9 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
|
|
|
7808
8041
|
const d = db || getDatabase();
|
|
7809
8042
|
const id = uuid();
|
|
7810
8043
|
const timestamp = now();
|
|
7811
|
-
|
|
7812
|
-
|
|
8044
|
+
const machineId = currentStorageMachineId(d);
|
|
8045
|
+
d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
|
|
8046
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field2 || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp, machineId]);
|
|
7813
8047
|
try {
|
|
7814
8048
|
const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
|
|
7815
8049
|
logActivity2({
|
|
@@ -7822,7 +8056,7 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
|
|
|
7822
8056
|
actor_id: agentId ?? undefined
|
|
7823
8057
|
}, d);
|
|
7824
8058
|
} catch {}
|
|
7825
|
-
return { id, task_id: taskId, action, field: field2 || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
|
|
8059
|
+
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 };
|
|
7826
8060
|
}
|
|
7827
8061
|
function getTaskHistory(taskId, db) {
|
|
7828
8062
|
const d = db || getDatabase();
|
|
@@ -8171,13 +8405,14 @@ function createTask(input, db) {
|
|
|
8171
8405
|
const d = db || getDatabase();
|
|
8172
8406
|
const timestamp = now();
|
|
8173
8407
|
const tags = input.tags || [];
|
|
8408
|
+
const machineId = currentStorageMachineId(d);
|
|
8174
8409
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
8175
8410
|
const assignedFromProject = input.assigned_from_project || null;
|
|
8176
8411
|
let id = uuid();
|
|
8177
8412
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
8178
8413
|
try {
|
|
8179
|
-
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)
|
|
8180
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8414
|
+
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)
|
|
8415
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8181
8416
|
id,
|
|
8182
8417
|
null,
|
|
8183
8418
|
input.project_id || null,
|
|
@@ -8214,7 +8449,8 @@ function createTask(input, db) {
|
|
|
8214
8449
|
input.spawned_from_session || null,
|
|
8215
8450
|
assignedBy || null,
|
|
8216
8451
|
assignedFromProject || null,
|
|
8217
|
-
input.task_type || null
|
|
8452
|
+
input.task_type || null,
|
|
8453
|
+
machineId
|
|
8218
8454
|
]);
|
|
8219
8455
|
break;
|
|
8220
8456
|
} catch (e) {
|
|
@@ -8230,9 +8466,10 @@ function createTask(input, db) {
|
|
|
8230
8466
|
}
|
|
8231
8467
|
const task = getTask(id, d);
|
|
8232
8468
|
const payload = taskEventData(task);
|
|
8469
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8233
8470
|
dispatchWebhook2("task.created", payload, d).catch(() => {});
|
|
8234
|
-
emitLocalEventHooksQuiet({ type: "task.created", payload });
|
|
8235
|
-
emitSharedTaskEventQuiet({ type: "task.created", task });
|
|
8471
|
+
emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
|
|
8472
|
+
emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
|
|
8236
8473
|
return task;
|
|
8237
8474
|
}
|
|
8238
8475
|
function getTask(id, db) {
|
|
@@ -8650,29 +8887,39 @@ function updateTask(id, input, db) {
|
|
|
8650
8887
|
approved_by: input.approved_by ?? task.approved_by,
|
|
8651
8888
|
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
8652
8889
|
};
|
|
8890
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8653
8891
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
8654
8892
|
const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
|
|
8655
8893
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
8656
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
8657
|
-
emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
|
|
8894
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
8895
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to }, databasePath });
|
|
8658
8896
|
}
|
|
8659
8897
|
if (input.status !== undefined && input.status !== task.status) {
|
|
8660
8898
|
const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
|
|
8661
8899
|
dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
|
|
8662
|
-
emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
|
|
8663
|
-
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
|
|
8900
|
+
emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
|
|
8901
|
+
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
|
|
8664
8902
|
}
|
|
8665
8903
|
if (input.approved_by !== undefined) {
|
|
8666
|
-
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
8904
|
+
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title }, databasePath });
|
|
8667
8905
|
}
|
|
8668
8906
|
const updatePayload = taskEventData(updatedTask);
|
|
8669
8907
|
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
8670
|
-
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
|
|
8671
|
-
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
|
|
8908
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
|
|
8909
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
|
|
8672
8910
|
return updatedTask;
|
|
8673
8911
|
}
|
|
8674
8912
|
function deleteTask(id, db) {
|
|
8675
8913
|
const d = db || getDatabase();
|
|
8914
|
+
const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
8915
|
+
if (!row)
|
|
8916
|
+
return false;
|
|
8917
|
+
recordStorageTombstone({
|
|
8918
|
+
object_type: "tasks",
|
|
8919
|
+
object_id: id,
|
|
8920
|
+
payload: rowToTask(row),
|
|
8921
|
+
version: row.version
|
|
8922
|
+
}, d);
|
|
8676
8923
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
8677
8924
|
return result.changes > 0;
|
|
8678
8925
|
}
|
|
@@ -8804,8 +9051,9 @@ function resolveTemplateId(id, d) {
|
|
|
8804
9051
|
function createTemplate(input, db) {
|
|
8805
9052
|
const d = db || getDatabase();
|
|
8806
9053
|
const id = uuid();
|
|
8807
|
-
|
|
8808
|
-
|
|
9054
|
+
const machineId = currentStorageMachineId(d);
|
|
9055
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at, machine_id)
|
|
9056
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8809
9057
|
id,
|
|
8810
9058
|
input.name,
|
|
8811
9059
|
input.title_pattern,
|
|
@@ -8816,7 +9064,8 @@ function createTemplate(input, db) {
|
|
|
8816
9064
|
input.project_id || null,
|
|
8817
9065
|
input.plan_id || null,
|
|
8818
9066
|
JSON.stringify(input.metadata || {}),
|
|
8819
|
-
now()
|
|
9067
|
+
now(),
|
|
9068
|
+
machineId
|
|
8820
9069
|
]);
|
|
8821
9070
|
if (input.tasks && input.tasks.length > 0) {
|
|
8822
9071
|
addTemplateTasks(id, input.tasks, d);
|
|
@@ -8840,6 +9089,15 @@ function deleteTemplate(id, db) {
|
|
|
8840
9089
|
const resolved = resolveTemplateId(id, d);
|
|
8841
9090
|
if (!resolved)
|
|
8842
9091
|
return false;
|
|
9092
|
+
const template = getTemplate(resolved, d);
|
|
9093
|
+
if (!template)
|
|
9094
|
+
return false;
|
|
9095
|
+
recordStorageTombstone({
|
|
9096
|
+
object_type: "templates",
|
|
9097
|
+
object_id: resolved,
|
|
9098
|
+
payload: template,
|
|
9099
|
+
version: template.version
|
|
9100
|
+
}, d);
|
|
8843
9101
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
8844
9102
|
}
|
|
8845
9103
|
function updateTemplate(id, updates, db) {
|
|
@@ -9369,6 +9627,7 @@ function getBlockingDeps(id, db) {
|
|
|
9369
9627
|
}
|
|
9370
9628
|
function startTask(id, agentId, db) {
|
|
9371
9629
|
const d = db || getDatabase();
|
|
9630
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9372
9631
|
const task = getTask(id, d);
|
|
9373
9632
|
if (!task)
|
|
9374
9633
|
throw new TaskNotFoundError(id);
|
|
@@ -9383,7 +9642,8 @@ function startTask(id, agentId, db) {
|
|
|
9383
9642
|
agent_id: agentId,
|
|
9384
9643
|
title: task.title,
|
|
9385
9644
|
blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
|
|
9386
|
-
}
|
|
9645
|
+
},
|
|
9646
|
+
databasePath
|
|
9387
9647
|
});
|
|
9388
9648
|
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
9389
9649
|
}
|
|
@@ -9405,12 +9665,13 @@ function startTask(id, agentId, db) {
|
|
|
9405
9665
|
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 };
|
|
9406
9666
|
const payload = taskEventData(startedTask, { agent_id: agentId });
|
|
9407
9667
|
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
9408
|
-
emitLocalEventHooksQuiet({ type: "task.started", payload });
|
|
9409
|
-
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
|
|
9668
|
+
emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
|
|
9669
|
+
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
|
|
9410
9670
|
return startedTask;
|
|
9411
9671
|
}
|
|
9412
9672
|
function completeTask(id, agentId, db, options) {
|
|
9413
9673
|
const d = db || getDatabase();
|
|
9674
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9414
9675
|
const task = getTask(id, d);
|
|
9415
9676
|
if (!task)
|
|
9416
9677
|
throw new TaskNotFoundError(id);
|
|
@@ -9456,8 +9717,8 @@ function completeTask(id, agentId, db, options) {
|
|
|
9456
9717
|
};
|
|
9457
9718
|
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
|
|
9458
9719
|
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
9459
|
-
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
|
|
9460
|
-
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
|
|
9720
|
+
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
|
|
9721
|
+
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
|
|
9461
9722
|
let spawnedTask = null;
|
|
9462
9723
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
9463
9724
|
spawnedTask = spawnNextRecurrence(task, d, timestamp);
|
|
@@ -9501,9 +9762,9 @@ function completeTask(id, agentId, db, options) {
|
|
|
9501
9762
|
const depTask = getTask(dep.id, d);
|
|
9502
9763
|
const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
|
|
9503
9764
|
dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
|
|
9504
|
-
emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
|
|
9765
|
+
emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
|
|
9505
9766
|
if (depTask)
|
|
9506
|
-
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
|
|
9767
|
+
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
|
|
9507
9768
|
}
|
|
9508
9769
|
}
|
|
9509
9770
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
@@ -9673,6 +9934,7 @@ function getTasksChangedSince(since, filters, db) {
|
|
|
9673
9934
|
}
|
|
9674
9935
|
function failTask(id, agentId, reason, options, db) {
|
|
9675
9936
|
const d = db || getDatabase();
|
|
9937
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9676
9938
|
const task = getTask(id, d);
|
|
9677
9939
|
if (!task)
|
|
9678
9940
|
throw new TaskNotFoundError(id);
|
|
@@ -9701,8 +9963,8 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
9701
9963
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
9702
9964
|
const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
|
|
9703
9965
|
dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
|
|
9704
|
-
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
|
|
9705
|
-
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
|
|
9966
|
+
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
|
|
9967
|
+
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning", databasePath });
|
|
9706
9968
|
let retryTask;
|
|
9707
9969
|
if (options?.retry) {
|
|
9708
9970
|
const retryCount = (task.retry_count || 0) + 1;
|
|
@@ -9762,6 +10024,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
|
|
|
9762
10024
|
}
|
|
9763
10025
|
function stealTask(agentId, opts, db) {
|
|
9764
10026
|
const d = db || getDatabase();
|
|
10027
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9765
10028
|
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
9766
10029
|
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
9767
10030
|
if (staleTasks.length === 0)
|
|
@@ -9780,8 +10043,8 @@ function stealTask(agentId, opts, db) {
|
|
|
9780
10043
|
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
9781
10044
|
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
9782
10045
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
9783
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
9784
|
-
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
|
|
10046
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
10047
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
|
|
9785
10048
|
return stolenTask;
|
|
9786
10049
|
}
|
|
9787
10050
|
function claimOrSteal(agentId, filters, db) {
|
|
@@ -10372,8 +10635,9 @@ function createPlan(input, db) {
|
|
|
10372
10635
|
const d = db || getDatabase();
|
|
10373
10636
|
const id = uuid();
|
|
10374
10637
|
const timestamp = now();
|
|
10375
|
-
|
|
10376
|
-
|
|
10638
|
+
const machineId = currentStorageMachineId(d);
|
|
10639
|
+
d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
|
|
10640
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10377
10641
|
id,
|
|
10378
10642
|
input.project_id || null,
|
|
10379
10643
|
input.task_list_id || null,
|
|
@@ -10382,7 +10646,8 @@ function createPlan(input, db) {
|
|
|
10382
10646
|
input.description || null,
|
|
10383
10647
|
input.status || "active",
|
|
10384
10648
|
timestamp,
|
|
10385
|
-
timestamp
|
|
10649
|
+
timestamp,
|
|
10650
|
+
machineId
|
|
10386
10651
|
]);
|
|
10387
10652
|
return getPlan(id, d);
|
|
10388
10653
|
}
|
|
@@ -10430,12 +10695,21 @@ function updatePlan(id, input, db) {
|
|
|
10430
10695
|
const updated = getPlan(id, d);
|
|
10431
10696
|
emitLocalEventHooksQuiet({
|
|
10432
10697
|
type: "plan.updated",
|
|
10433
|
-
payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id }
|
|
10698
|
+
payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id },
|
|
10699
|
+
databasePath: databasePathFromDatabase(d)
|
|
10434
10700
|
});
|
|
10435
10701
|
return updated;
|
|
10436
10702
|
}
|
|
10437
10703
|
function deletePlan(id, db) {
|
|
10438
10704
|
const d = db || getDatabase();
|
|
10705
|
+
const plan = getPlan(id, d);
|
|
10706
|
+
if (!plan)
|
|
10707
|
+
return false;
|
|
10708
|
+
recordStorageTombstone({
|
|
10709
|
+
object_type: "plans",
|
|
10710
|
+
object_id: id,
|
|
10711
|
+
payload: plan
|
|
10712
|
+
}, d);
|
|
10439
10713
|
const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
|
|
10440
10714
|
return result.changes > 0;
|
|
10441
10715
|
}
|
|
@@ -10842,20 +11116,20 @@ init_database();
|
|
|
10842
11116
|
init_redaction();
|
|
10843
11117
|
import { createHash as createHash2 } from "crypto";
|
|
10844
11118
|
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
10845
|
-
import { basename, dirname as dirname5, join as join6, resolve as
|
|
10846
|
-
import { tmpdir } from "os";
|
|
11119
|
+
import { basename, dirname as dirname5, join as join6, resolve as resolve7 } from "path";
|
|
11120
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
10847
11121
|
function isInMemoryDb2(path) {
|
|
10848
11122
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
10849
11123
|
}
|
|
10850
11124
|
function artifactStoreRoot() {
|
|
10851
11125
|
if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
|
|
10852
|
-
return
|
|
11126
|
+
return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
|
|
10853
11127
|
if (process.env["TODOS_ARTIFACTS_DIR"])
|
|
10854
|
-
return
|
|
11128
|
+
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
10855
11129
|
const dbPath = getDatabasePath();
|
|
10856
11130
|
if (isInMemoryDb2(dbPath))
|
|
10857
|
-
return join6(
|
|
10858
|
-
return join6(dirname5(
|
|
11131
|
+
return join6(tmpdir2(), "hasna-todos-artifacts");
|
|
11132
|
+
return join6(dirname5(resolve7(dbPath)), "artifacts");
|
|
10859
11133
|
}
|
|
10860
11134
|
function artifactStorePath(relativePath) {
|
|
10861
11135
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
@@ -10902,7 +11176,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
10902
11176
|
return "application/octet-stream";
|
|
10903
11177
|
}
|
|
10904
11178
|
function storeArtifactContent(input) {
|
|
10905
|
-
const sourcePath =
|
|
11179
|
+
const sourcePath = resolve7(input.path);
|
|
10906
11180
|
if (!existsSync8(sourcePath))
|
|
10907
11181
|
return null;
|
|
10908
11182
|
const sourceStat = statSync2(sourcePath);
|
|
@@ -11058,19 +11332,19 @@ function importStoredArtifactContent(content) {
|
|
|
11058
11332
|
var DEFAULT_DELETED_RETENTION_DAYS = 30;
|
|
11059
11333
|
function getArtifactStoreRoot(dbPath) {
|
|
11060
11334
|
if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
|
|
11061
|
-
return
|
|
11335
|
+
return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
|
|
11062
11336
|
if (process.env["TODOS_ARTIFACTS_DIR"])
|
|
11063
|
-
return
|
|
11337
|
+
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
11064
11338
|
const path = dbPath ?? getDatabasePath();
|
|
11065
11339
|
if (isInMemoryDb2(path))
|
|
11066
|
-
return join6(
|
|
11067
|
-
return join6(dirname5(
|
|
11340
|
+
return join6(tmpdir2(), "hasna-todos-artifacts");
|
|
11341
|
+
return join6(dirname5(resolve7(path)), "artifacts");
|
|
11068
11342
|
}
|
|
11069
11343
|
function computeContentHash(path) {
|
|
11070
|
-
return sha256(readFileSync4(
|
|
11344
|
+
return sha256(readFileSync4(resolve7(path)));
|
|
11071
11345
|
}
|
|
11072
11346
|
function storeArtifactFile(input) {
|
|
11073
|
-
const sourcePath =
|
|
11347
|
+
const sourcePath = resolve7(input.sourcePath);
|
|
11074
11348
|
if (!existsSync8(sourcePath)) {
|
|
11075
11349
|
throw new Error(`Source file not found: ${input.sourcePath}`);
|
|
11076
11350
|
}
|
|
@@ -11119,7 +11393,7 @@ function buildArtifactExportManifest(artifacts, dbPath) {
|
|
|
11119
11393
|
};
|
|
11120
11394
|
}
|
|
11121
11395
|
function writeArtifactExportManifest(manifest, outputPath) {
|
|
11122
|
-
const destination =
|
|
11396
|
+
const destination = resolve7(outputPath);
|
|
11123
11397
|
mkdirSync4(dirname5(destination), { recursive: true });
|
|
11124
11398
|
writeFileSync2(destination, `${JSON.stringify(manifest, null, 2)}
|
|
11125
11399
|
`);
|
|
@@ -11506,7 +11780,11 @@ function startTaskRun(input, db) {
|
|
|
11506
11780
|
}, d);
|
|
11507
11781
|
}
|
|
11508
11782
|
const run = getTaskRun(id, d);
|
|
11509
|
-
emitLocalEventHooksQuiet({
|
|
11783
|
+
emitLocalEventHooksQuiet({
|
|
11784
|
+
type: "run.started",
|
|
11785
|
+
payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title },
|
|
11786
|
+
databasePath: databasePathFromDatabase(d)
|
|
11787
|
+
});
|
|
11510
11788
|
return run;
|
|
11511
11789
|
}
|
|
11512
11790
|
function beginTaskRunTransaction(input, db) {
|
|
@@ -11775,7 +12053,8 @@ function finishTaskRun(input, db) {
|
|
|
11775
12053
|
const updated = getTaskRun(run.id, d);
|
|
11776
12054
|
emitLocalEventHooksQuiet({
|
|
11777
12055
|
type: `run.${input.status}`,
|
|
11778
|
-
payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp }
|
|
12056
|
+
payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp },
|
|
12057
|
+
databasePath: databasePathFromDatabase(d)
|
|
11779
12058
|
});
|
|
11780
12059
|
return updated;
|
|
11781
12060
|
}
|
|
@@ -13109,7 +13388,7 @@ function importOnboardingFixture(options = {}) {
|
|
|
13109
13388
|
init_database();
|
|
13110
13389
|
import { createHash as createHash3 } from "crypto";
|
|
13111
13390
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
13112
|
-
import { dirname as dirname6, resolve as
|
|
13391
|
+
import { dirname as dirname6, resolve as resolve8 } from "path";
|
|
13113
13392
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
13114
13393
|
var TODOS_LOCAL_BACKUP_KIND = "hasna.todos.local-backup";
|
|
13115
13394
|
var TODOS_LOCAL_BACKUP_SCHEMA_VERSION = 1;
|
|
@@ -13216,14 +13495,14 @@ function createLocalBackup(options = {}, db) {
|
|
|
13216
13495
|
return backup;
|
|
13217
13496
|
}
|
|
13218
13497
|
function writeLocalBackupFile(backup, outputPath) {
|
|
13219
|
-
const path =
|
|
13498
|
+
const path = resolve8(outputPath);
|
|
13220
13499
|
mkdirSync6(dirname6(path), { recursive: true });
|
|
13221
13500
|
writeFileSync4(path, `${JSON.stringify(backup, null, 2)}
|
|
13222
13501
|
`);
|
|
13223
13502
|
return path;
|
|
13224
13503
|
}
|
|
13225
13504
|
function readLocalBackupFile(path) {
|
|
13226
|
-
return JSON.parse(readFileSync5(
|
|
13505
|
+
return JSON.parse(readFileSync5(resolve8(path), "utf-8"));
|
|
13227
13506
|
}
|
|
13228
13507
|
function verifyLocalBackup(value, options = {}, db) {
|
|
13229
13508
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -16191,7 +16470,8 @@ function writeQueue(task2, queue, actor, action, db) {
|
|
|
16191
16470
|
logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
|
|
16192
16471
|
emitLocalEventHooksQuiet({
|
|
16193
16472
|
type: `review.${action}`,
|
|
16194
|
-
payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by }
|
|
16473
|
+
payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by },
|
|
16474
|
+
databasePath: databasePathFromDatabase(d)
|
|
16195
16475
|
});
|
|
16196
16476
|
return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
|
|
16197
16477
|
}
|
|
@@ -16872,7 +17152,7 @@ function canonicalize(value) {
|
|
|
16872
17152
|
function hash(value) {
|
|
16873
17153
|
return createHash5("sha256").update(value).digest("hex");
|
|
16874
17154
|
}
|
|
16875
|
-
function
|
|
17155
|
+
function parsePayload2(value) {
|
|
16876
17156
|
if (!value)
|
|
16877
17157
|
return {};
|
|
16878
17158
|
try {
|
|
@@ -16971,7 +17251,7 @@ function taskScopedRows(db, scope) {
|
|
|
16971
17251
|
FROM handoffs h
|
|
16972
17252
|
WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
|
|
16973
17253
|
`).all(scope.project_id ?? null).filter((row) => {
|
|
16974
|
-
const payload =
|
|
17254
|
+
const payload = parsePayload2(row.payload_json);
|
|
16975
17255
|
const taskRefs = parseStringArray(payload["task_ids"]);
|
|
16976
17256
|
const runRefs = parseStringArray(payload["run_ids"]);
|
|
16977
17257
|
if (scope.project_id && row.project_id !== scope.project_id)
|
|
@@ -17002,7 +17282,7 @@ function toLedgerEntries(rows) {
|
|
|
17002
17282
|
});
|
|
17003
17283
|
let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
|
|
17004
17284
|
return ordered.map((row, index) => {
|
|
17005
|
-
const payload =
|
|
17285
|
+
const payload = parsePayload2(row.payload_json);
|
|
17006
17286
|
const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
|
|
17007
17287
|
const chainHash = hash(`${previous}
|
|
17008
17288
|
${payloadHash}`);
|
|
@@ -17137,7 +17417,7 @@ function renderLocalAuditLedgerMarkdown(ledger) {
|
|
|
17137
17417
|
init_migrations();
|
|
17138
17418
|
init_schema();
|
|
17139
17419
|
import { readFileSync as readFileSync6 } from "fs";
|
|
17140
|
-
import { join as join9, resolve as
|
|
17420
|
+
import { join as join9, resolve as resolve9 } from "path";
|
|
17141
17421
|
import { Database as Database2 } from "bun:sqlite";
|
|
17142
17422
|
var LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION = 1;
|
|
17143
17423
|
var EXPECTED_PACKAGE_NAME = "@hasna/todos";
|
|
@@ -17279,7 +17559,7 @@ function checkChangelog() {
|
|
|
17279
17559
|
];
|
|
17280
17560
|
}
|
|
17281
17561
|
function createReleaseCompatibilityReport(options = {}) {
|
|
17282
|
-
const root =
|
|
17562
|
+
const root = resolve9(options.root ?? process.cwd());
|
|
17283
17563
|
const packageJson = readPackageJson(root);
|
|
17284
17564
|
const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
|
|
17285
17565
|
const checks = [
|