@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/cli/index.js
CHANGED
|
@@ -1011,7 +1011,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1011
1011
|
this._exitCallback = (err) => {
|
|
1012
1012
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
1013
1013
|
throw err;
|
|
1014
|
-
}
|
|
1014
|
+
}
|
|
1015
1015
|
};
|
|
1016
1016
|
}
|
|
1017
1017
|
return this;
|
|
@@ -3266,6 +3266,22 @@ var init_migrations = __esm(() => {
|
|
|
3266
3266
|
CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source);
|
|
3267
3267
|
CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint);
|
|
3268
3268
|
INSERT OR IGNORE INTO _migrations (id) VALUES (62);
|
|
3269
|
+
`,
|
|
3270
|
+
`
|
|
3271
|
+
CREATE TABLE IF NOT EXISTS storage_tombstones (
|
|
3272
|
+
id TEXT PRIMARY KEY,
|
|
3273
|
+
object_type TEXT NOT NULL,
|
|
3274
|
+
object_id TEXT NOT NULL,
|
|
3275
|
+
deleted_at TEXT NOT NULL,
|
|
3276
|
+
updated_at TEXT NOT NULL,
|
|
3277
|
+
source_machine_id TEXT,
|
|
3278
|
+
payload TEXT,
|
|
3279
|
+
version INTEGER,
|
|
3280
|
+
UNIQUE(object_type, object_id)
|
|
3281
|
+
);
|
|
3282
|
+
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id);
|
|
3283
|
+
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at);
|
|
3284
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (63);
|
|
3269
3285
|
`
|
|
3270
3286
|
];
|
|
3271
3287
|
});
|
|
@@ -3787,6 +3803,20 @@ function ensureSchema(db) {
|
|
|
3787
3803
|
)`);
|
|
3788
3804
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
|
|
3789
3805
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
|
|
3806
|
+
ensureTable("storage_tombstones", `
|
|
3807
|
+
CREATE TABLE storage_tombstones (
|
|
3808
|
+
id TEXT PRIMARY KEY,
|
|
3809
|
+
object_type TEXT NOT NULL,
|
|
3810
|
+
object_id TEXT NOT NULL,
|
|
3811
|
+
deleted_at TEXT NOT NULL,
|
|
3812
|
+
updated_at TEXT NOT NULL,
|
|
3813
|
+
source_machine_id TEXT,
|
|
3814
|
+
payload TEXT,
|
|
3815
|
+
version INTEGER,
|
|
3816
|
+
UNIQUE(object_type, object_id)
|
|
3817
|
+
)`);
|
|
3818
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id)");
|
|
3819
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at)");
|
|
3790
3820
|
ensureTable("machines", `
|
|
3791
3821
|
CREATE TABLE machines (
|
|
3792
3822
|
id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
|
|
@@ -4981,6 +5011,80 @@ var init_types = __esm(() => {
|
|
|
4981
5011
|
};
|
|
4982
5012
|
});
|
|
4983
5013
|
|
|
5014
|
+
// src/db/storage-tombstones.ts
|
|
5015
|
+
function recordStorageTombstone(input, db) {
|
|
5016
|
+
const d = db ?? getDatabase();
|
|
5017
|
+
const deletedAt = input.deleted_at ?? now();
|
|
5018
|
+
const machineId = input.source_machine_id ?? currentStorageMachineId(d);
|
|
5019
|
+
d.run(`INSERT INTO storage_tombstones (
|
|
5020
|
+
id, object_type, object_id, deleted_at, updated_at, source_machine_id, payload, version
|
|
5021
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
5022
|
+
ON CONFLICT(object_type, object_id) DO UPDATE SET
|
|
5023
|
+
deleted_at = excluded.deleted_at,
|
|
5024
|
+
updated_at = excluded.updated_at,
|
|
5025
|
+
source_machine_id = excluded.source_machine_id,
|
|
5026
|
+
payload = excluded.payload,
|
|
5027
|
+
version = excluded.version
|
|
5028
|
+
WHERE storage_tombstones.updated_at IS NULL OR storage_tombstones.updated_at <= excluded.updated_at`, [
|
|
5029
|
+
uuid(),
|
|
5030
|
+
input.object_type,
|
|
5031
|
+
input.object_id,
|
|
5032
|
+
deletedAt,
|
|
5033
|
+
deletedAt,
|
|
5034
|
+
machineId,
|
|
5035
|
+
input.payload ? JSON.stringify(input.payload) : null,
|
|
5036
|
+
input.version ?? null
|
|
5037
|
+
]);
|
|
5038
|
+
return getStorageTombstone(input.object_type, input.object_id, d);
|
|
5039
|
+
}
|
|
5040
|
+
function getStorageTombstone(objectType, objectId, db) {
|
|
5041
|
+
const d = db ?? getDatabase();
|
|
5042
|
+
const row = d.query("SELECT * FROM storage_tombstones WHERE object_type = ? AND object_id = ?").get(objectType, objectId);
|
|
5043
|
+
return row ? rowToStorageTombstone(row) : null;
|
|
5044
|
+
}
|
|
5045
|
+
function listStorageTombstones(db) {
|
|
5046
|
+
const d = db ?? getDatabase();
|
|
5047
|
+
return d.query("SELECT * FROM storage_tombstones ORDER BY updated_at ASC, object_type ASC, object_id ASC").all().map(rowToStorageTombstone);
|
|
5048
|
+
}
|
|
5049
|
+
function shouldApplyStorageTombstone(tombstone, existingUpdatedAt) {
|
|
5050
|
+
const tombstoneClock = Date.parse(tombstone.updated_at || tombstone.deleted_at);
|
|
5051
|
+
if (!existingUpdatedAt)
|
|
5052
|
+
return true;
|
|
5053
|
+
const existingClock = Date.parse(existingUpdatedAt);
|
|
5054
|
+
if (Number.isNaN(tombstoneClock))
|
|
5055
|
+
return true;
|
|
5056
|
+
if (Number.isNaN(existingClock))
|
|
5057
|
+
return true;
|
|
5058
|
+
return tombstoneClock >= existingClock;
|
|
5059
|
+
}
|
|
5060
|
+
function rowToStorageTombstone(row) {
|
|
5061
|
+
return {
|
|
5062
|
+
...row,
|
|
5063
|
+
payload: parsePayload(row.payload)
|
|
5064
|
+
};
|
|
5065
|
+
}
|
|
5066
|
+
function parsePayload(value) {
|
|
5067
|
+
if (!value)
|
|
5068
|
+
return null;
|
|
5069
|
+
try {
|
|
5070
|
+
const parsed = JSON.parse(value);
|
|
5071
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
5072
|
+
} catch {
|
|
5073
|
+
return null;
|
|
5074
|
+
}
|
|
5075
|
+
}
|
|
5076
|
+
function currentStorageMachineId(db) {
|
|
5077
|
+
try {
|
|
5078
|
+
return getMachineId(db);
|
|
5079
|
+
} catch {
|
|
5080
|
+
return null;
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5083
|
+
var init_storage_tombstones = __esm(() => {
|
|
5084
|
+
init_database();
|
|
5085
|
+
init_machines();
|
|
5086
|
+
});
|
|
5087
|
+
|
|
4984
5088
|
// src/db/projects.ts
|
|
4985
5089
|
var exports_projects = {};
|
|
4986
5090
|
__export(exports_projects, {
|
|
@@ -5032,8 +5136,9 @@ function createProject(input, db) {
|
|
|
5032
5136
|
const timestamp = now();
|
|
5033
5137
|
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
5034
5138
|
const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
|
|
5035
|
-
|
|
5036
|
-
|
|
5139
|
+
const machineId = currentStorageMachineId(d);
|
|
5140
|
+
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
|
|
5141
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
|
|
5037
5142
|
return getProject(id, d);
|
|
5038
5143
|
}
|
|
5039
5144
|
function getProject(id, db) {
|
|
@@ -5112,6 +5217,14 @@ function renameProject(id, input, db) {
|
|
|
5112
5217
|
}
|
|
5113
5218
|
function deleteProject(id, db) {
|
|
5114
5219
|
const d = db || getDatabase();
|
|
5220
|
+
const project = getProject(id, d);
|
|
5221
|
+
if (!project)
|
|
5222
|
+
return false;
|
|
5223
|
+
recordStorageTombstone({
|
|
5224
|
+
object_type: "projects",
|
|
5225
|
+
object_id: id,
|
|
5226
|
+
payload: project
|
|
5227
|
+
}, d);
|
|
5115
5228
|
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
5116
5229
|
return result.changes > 0;
|
|
5117
5230
|
}
|
|
@@ -5216,6 +5329,14 @@ function listMachineLocalPaths(projectId, db) {
|
|
|
5216
5329
|
function removeMachineLocalPath(projectId, machineId, db) {
|
|
5217
5330
|
const d = db || getDatabase();
|
|
5218
5331
|
const mid = machineId ?? getMachineId(d);
|
|
5332
|
+
const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, mid);
|
|
5333
|
+
if (!existing)
|
|
5334
|
+
return false;
|
|
5335
|
+
recordStorageTombstone({
|
|
5336
|
+
object_type: "project_machine_paths",
|
|
5337
|
+
object_id: existing.id,
|
|
5338
|
+
payload: existing
|
|
5339
|
+
}, d);
|
|
5219
5340
|
const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
|
|
5220
5341
|
return result.changes > 0;
|
|
5221
5342
|
}
|
|
@@ -5223,6 +5344,7 @@ var init_projects = __esm(() => {
|
|
|
5223
5344
|
init_types();
|
|
5224
5345
|
init_database();
|
|
5225
5346
|
init_machines();
|
|
5347
|
+
init_storage_tombstones();
|
|
5226
5348
|
});
|
|
5227
5349
|
|
|
5228
5350
|
// src/lib/sync-utils.ts
|
|
@@ -5467,6 +5589,57 @@ var init_completion_guard = __esm(() => {
|
|
|
5467
5589
|
init_projects();
|
|
5468
5590
|
});
|
|
5469
5591
|
|
|
5592
|
+
// src/lib/event-emission-safety.ts
|
|
5593
|
+
import { tmpdir } from "os";
|
|
5594
|
+
import { resolve as resolve3, sep } from "path";
|
|
5595
|
+
function envFlag(name) {
|
|
5596
|
+
const value = process.env[name];
|
|
5597
|
+
return value === "1" || value === "true" || value === "yes";
|
|
5598
|
+
}
|
|
5599
|
+
function isUnder(parent, child) {
|
|
5600
|
+
const normalizedParent = resolve3(parent);
|
|
5601
|
+
const normalizedChild = resolve3(child);
|
|
5602
|
+
return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
|
|
5603
|
+
}
|
|
5604
|
+
function databasePathFromDatabase(db) {
|
|
5605
|
+
const filename = db?.filename;
|
|
5606
|
+
return typeof filename === "string" && filename.trim() ? filename : undefined;
|
|
5607
|
+
}
|
|
5608
|
+
function isEphemeralTodosDatabase(dbPath) {
|
|
5609
|
+
const resolvedPath = dbPath ?? getDatabasePath();
|
|
5610
|
+
if (resolvedPath === ":memory:" || resolvedPath.startsWith("file::memory:"))
|
|
5611
|
+
return true;
|
|
5612
|
+
return isUnder(tmpdir(), resolvedPath);
|
|
5613
|
+
}
|
|
5614
|
+
function hasExplicitSharedEventsStore() {
|
|
5615
|
+
return Boolean(process.env["HASNA_EVENTS_DIR"] || process.env["HASNA_EVENTS_HOME"]);
|
|
5616
|
+
}
|
|
5617
|
+
function usesIsolatedTodosHome() {
|
|
5618
|
+
return isUnder(tmpdir(), getTodosGlobalDir());
|
|
5619
|
+
}
|
|
5620
|
+
function shouldEmitSharedTaskEvents(dbPath) {
|
|
5621
|
+
if (envFlag("TODOS_DISABLE_SHARED_EVENTS"))
|
|
5622
|
+
return false;
|
|
5623
|
+
if (envFlag("TODOS_ALLOW_EPHEMERAL_SHARED_EVENTS"))
|
|
5624
|
+
return true;
|
|
5625
|
+
if (!isEphemeralTodosDatabase(dbPath))
|
|
5626
|
+
return true;
|
|
5627
|
+
return hasExplicitSharedEventsStore();
|
|
5628
|
+
}
|
|
5629
|
+
function shouldDeliverLocalLifecycleHooks(dbPath) {
|
|
5630
|
+
if (envFlag("TODOS_DISABLE_LOCAL_EVENT_HOOKS"))
|
|
5631
|
+
return false;
|
|
5632
|
+
if (envFlag("TODOS_ALLOW_EPHEMERAL_LOCAL_EVENT_HOOKS"))
|
|
5633
|
+
return true;
|
|
5634
|
+
if (!isEphemeralTodosDatabase(dbPath))
|
|
5635
|
+
return true;
|
|
5636
|
+
return usesIsolatedTodosHome();
|
|
5637
|
+
}
|
|
5638
|
+
var init_event_emission_safety = __esm(() => {
|
|
5639
|
+
init_database();
|
|
5640
|
+
init_sync_utils();
|
|
5641
|
+
});
|
|
5642
|
+
|
|
5470
5643
|
// src/lib/redaction.ts
|
|
5471
5644
|
var exports_redaction = {};
|
|
5472
5645
|
__export(exports_redaction, {
|
|
@@ -5587,9 +5760,9 @@ __export(exports_workspace_trust, {
|
|
|
5587
5760
|
getWorkspaceTrustStatus: () => getWorkspaceTrustStatus,
|
|
5588
5761
|
checkWorkspacePermission: () => checkWorkspacePermission
|
|
5589
5762
|
});
|
|
5590
|
-
import { relative, resolve as
|
|
5763
|
+
import { relative, resolve as resolve4 } from "path";
|
|
5591
5764
|
function normalizePath(path) {
|
|
5592
|
-
return
|
|
5765
|
+
return resolve4(path);
|
|
5593
5766
|
}
|
|
5594
5767
|
function unique2(values) {
|
|
5595
5768
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -5780,9 +5953,9 @@ __export(exports_runner_sandbox, {
|
|
|
5780
5953
|
explainRunnerSandbox: () => explainRunnerSandbox,
|
|
5781
5954
|
checkRunnerSandbox: () => checkRunnerSandbox
|
|
5782
5955
|
});
|
|
5783
|
-
import { relative as relative2, resolve as
|
|
5956
|
+
import { relative as relative2, resolve as resolve5 } from "path";
|
|
5784
5957
|
function normalizePath2(path) {
|
|
5785
|
-
return
|
|
5958
|
+
return resolve5(path);
|
|
5786
5959
|
}
|
|
5787
5960
|
function unique3(values) {
|
|
5788
5961
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -5986,7 +6159,7 @@ __export(exports_event_hooks, {
|
|
|
5986
6159
|
});
|
|
5987
6160
|
import { createHash, randomUUID } from "crypto";
|
|
5988
6161
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
5989
|
-
import { dirname as dirname4, resolve as
|
|
6162
|
+
import { dirname as dirname4, resolve as resolve6 } from "path";
|
|
5990
6163
|
import { createConnection } from "net";
|
|
5991
6164
|
function safeName(name) {
|
|
5992
6165
|
const trimmed = name.trim();
|
|
@@ -6124,7 +6297,7 @@ async function deliverHook(hook, envelope) {
|
|
|
6124
6297
|
if (hook.target === "stdout") {
|
|
6125
6298
|
output = line.trim();
|
|
6126
6299
|
} else if (hook.target === "file") {
|
|
6127
|
-
const filePath =
|
|
6300
|
+
const filePath = resolve6(hook.file_path);
|
|
6128
6301
|
mkdirSync3(dirname4(filePath), { recursive: true });
|
|
6129
6302
|
appendFileSync(filePath, line);
|
|
6130
6303
|
} else if (hook.target === "socket") {
|
|
@@ -6199,6 +6372,8 @@ async function emitLocalEventHooks(input) {
|
|
|
6199
6372
|
return Promise.all(hooks.map((hook) => deliverHook(hook, envelope)));
|
|
6200
6373
|
}
|
|
6201
6374
|
function emitLocalEventHooksQuiet(input) {
|
|
6375
|
+
if (!shouldDeliverLocalLifecycleHooks(input.databasePath))
|
|
6376
|
+
return;
|
|
6202
6377
|
emitLocalEventHooks(input).catch(() => {});
|
|
6203
6378
|
}
|
|
6204
6379
|
async function testLocalEventHook(name, input) {
|
|
@@ -6212,6 +6387,7 @@ var init_event_hooks = __esm(() => {
|
|
|
6212
6387
|
init_redaction();
|
|
6213
6388
|
init_runner_sandbox();
|
|
6214
6389
|
init_config();
|
|
6390
|
+
init_event_emission_safety();
|
|
6215
6391
|
LOCAL_EVENT_TYPES = [
|
|
6216
6392
|
"task.created",
|
|
6217
6393
|
"task.assigned",
|
|
@@ -6544,7 +6720,7 @@ async function dispatchCommand(event, channel) {
|
|
|
6544
6720
|
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
6545
6721
|
HASNA_EVENT_JSON: eventJson
|
|
6546
6722
|
};
|
|
6547
|
-
return new Promise((
|
|
6723
|
+
return new Promise((resolve7) => {
|
|
6548
6724
|
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
6549
6725
|
cwd: channel.command.cwd,
|
|
6550
6726
|
env,
|
|
@@ -6562,7 +6738,7 @@ async function dispatchCommand(event, channel) {
|
|
|
6562
6738
|
});
|
|
6563
6739
|
child.on("error", (error) => {
|
|
6564
6740
|
clearTimeout(timeout);
|
|
6565
|
-
|
|
6741
|
+
resolve7({
|
|
6566
6742
|
attempt: 1,
|
|
6567
6743
|
status: "failed",
|
|
6568
6744
|
startedAt,
|
|
@@ -6575,7 +6751,7 @@ async function dispatchCommand(event, channel) {
|
|
|
6575
6751
|
child.on("close", (code, signal) => {
|
|
6576
6752
|
clearTimeout(timeout);
|
|
6577
6753
|
const success = code === 0;
|
|
6578
|
-
|
|
6754
|
+
resolve7({
|
|
6579
6755
|
attempt: 1,
|
|
6580
6756
|
status: success ? "success" : "failed",
|
|
6581
6757
|
startedAt,
|
|
@@ -6842,14 +7018,15 @@ function createTaskList(input, db) {
|
|
|
6842
7018
|
const id = uuid();
|
|
6843
7019
|
const timestamp = now();
|
|
6844
7020
|
const slug = input.slug || slugify(input.name);
|
|
7021
|
+
const machineId = currentStorageMachineId(d);
|
|
6845
7022
|
if (!input.project_id) {
|
|
6846
7023
|
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
6847
7024
|
if (existing) {
|
|
6848
7025
|
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
6849
7026
|
}
|
|
6850
7027
|
}
|
|
6851
|
-
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
6852
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
7028
|
+
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
|
|
7029
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
|
|
6853
7030
|
return getTaskList(id, d);
|
|
6854
7031
|
}
|
|
6855
7032
|
function getTaskList(id, db) {
|
|
@@ -6899,6 +7076,14 @@ function updateTaskList(id, input, db) {
|
|
|
6899
7076
|
}
|
|
6900
7077
|
function deleteTaskList(id, db) {
|
|
6901
7078
|
const d = db || getDatabase();
|
|
7079
|
+
const list = getTaskList(id, d);
|
|
7080
|
+
if (!list)
|
|
7081
|
+
return false;
|
|
7082
|
+
recordStorageTombstone({
|
|
7083
|
+
object_type: "task_lists",
|
|
7084
|
+
object_id: id,
|
|
7085
|
+
payload: list
|
|
7086
|
+
}, d);
|
|
6902
7087
|
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
6903
7088
|
}
|
|
6904
7089
|
function ensureTaskList(name, slug, projectId, db) {
|
|
@@ -6912,6 +7097,7 @@ var init_task_lists = __esm(() => {
|
|
|
6912
7097
|
init_types();
|
|
6913
7098
|
init_database();
|
|
6914
7099
|
init_projects();
|
|
7100
|
+
init_storage_tombstones();
|
|
6915
7101
|
});
|
|
6916
7102
|
|
|
6917
7103
|
// src/lib/task-route-contract.ts
|
|
@@ -7118,6 +7304,8 @@ function readMachineLocalPath(project) {
|
|
|
7118
7304
|
}
|
|
7119
7305
|
}
|
|
7120
7306
|
async function emitSharedTaskEvent(input) {
|
|
7307
|
+
if (!shouldEmitSharedTaskEvents(input.databasePath))
|
|
7308
|
+
return;
|
|
7121
7309
|
const data = taskEventData(input.task, input.data);
|
|
7122
7310
|
await new EventsClient().emit({
|
|
7123
7311
|
source: SOURCE,
|
|
@@ -7141,6 +7329,7 @@ var init_shared_events = __esm(() => {
|
|
|
7141
7329
|
init_database();
|
|
7142
7330
|
init_projects();
|
|
7143
7331
|
init_task_lists();
|
|
7332
|
+
init_event_emission_safety();
|
|
7144
7333
|
});
|
|
7145
7334
|
|
|
7146
7335
|
// src/lib/secret-redaction.ts
|
|
@@ -7396,8 +7585,9 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
|
7396
7585
|
const d = db || getDatabase();
|
|
7397
7586
|
const id = uuid();
|
|
7398
7587
|
const timestamp = now();
|
|
7399
|
-
|
|
7400
|
-
|
|
7588
|
+
const machineId = currentStorageMachineId(d);
|
|
7589
|
+
d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
|
|
7590
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp, machineId]);
|
|
7401
7591
|
try {
|
|
7402
7592
|
const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
|
|
7403
7593
|
logActivity2({
|
|
@@ -7410,7 +7600,7 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
|
7410
7600
|
actor_id: agentId ?? undefined
|
|
7411
7601
|
}, d);
|
|
7412
7602
|
} catch {}
|
|
7413
|
-
return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
|
|
7603
|
+
return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp, machine_id: machineId };
|
|
7414
7604
|
}
|
|
7415
7605
|
function getTaskHistory(taskId, db) {
|
|
7416
7606
|
const d = db || getDatabase();
|
|
@@ -7448,6 +7638,7 @@ function getRecap(hours = 8, projectId, db) {
|
|
|
7448
7638
|
}
|
|
7449
7639
|
var init_audit = __esm(() => {
|
|
7450
7640
|
init_database();
|
|
7641
|
+
init_storage_tombstones();
|
|
7451
7642
|
});
|
|
7452
7643
|
|
|
7453
7644
|
// src/db/webhooks.ts
|
|
@@ -7713,13 +7904,14 @@ function createTask(input, db) {
|
|
|
7713
7904
|
const d = db || getDatabase();
|
|
7714
7905
|
const timestamp = now();
|
|
7715
7906
|
const tags = input.tags || [];
|
|
7907
|
+
const machineId = currentStorageMachineId(d);
|
|
7716
7908
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
7717
7909
|
const assignedFromProject = input.assigned_from_project || null;
|
|
7718
7910
|
let id = uuid();
|
|
7719
7911
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
7720
7912
|
try {
|
|
7721
|
-
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)
|
|
7722
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
7913
|
+
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)
|
|
7914
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
7723
7915
|
id,
|
|
7724
7916
|
null,
|
|
7725
7917
|
input.project_id || null,
|
|
@@ -7756,7 +7948,8 @@ function createTask(input, db) {
|
|
|
7756
7948
|
input.spawned_from_session || null,
|
|
7757
7949
|
assignedBy || null,
|
|
7758
7950
|
assignedFromProject || null,
|
|
7759
|
-
input.task_type || null
|
|
7951
|
+
input.task_type || null,
|
|
7952
|
+
machineId
|
|
7760
7953
|
]);
|
|
7761
7954
|
break;
|
|
7762
7955
|
} catch (e) {
|
|
@@ -7772,9 +7965,10 @@ function createTask(input, db) {
|
|
|
7772
7965
|
}
|
|
7773
7966
|
const task = getTask(id, d);
|
|
7774
7967
|
const payload = taskEventData(task);
|
|
7968
|
+
const databasePath = databasePathFromDatabase(d);
|
|
7775
7969
|
dispatchWebhook2("task.created", payload, d).catch(() => {});
|
|
7776
|
-
emitLocalEventHooksQuiet({ type: "task.created", payload });
|
|
7777
|
-
emitSharedTaskEventQuiet({ type: "task.created", task });
|
|
7970
|
+
emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
|
|
7971
|
+
emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
|
|
7778
7972
|
return task;
|
|
7779
7973
|
}
|
|
7780
7974
|
function getTask(id, db) {
|
|
@@ -8192,29 +8386,39 @@ function updateTask(id, input, db) {
|
|
|
8192
8386
|
approved_by: input.approved_by ?? task.approved_by,
|
|
8193
8387
|
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
8194
8388
|
};
|
|
8389
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8195
8390
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
8196
8391
|
const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
|
|
8197
8392
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
8198
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
8199
|
-
emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
|
|
8393
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
8394
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to }, databasePath });
|
|
8200
8395
|
}
|
|
8201
8396
|
if (input.status !== undefined && input.status !== task.status) {
|
|
8202
8397
|
const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
|
|
8203
8398
|
dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
|
|
8204
|
-
emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
|
|
8205
|
-
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
|
|
8399
|
+
emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
|
|
8400
|
+
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
|
|
8206
8401
|
}
|
|
8207
8402
|
if (input.approved_by !== undefined) {
|
|
8208
|
-
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
8403
|
+
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title }, databasePath });
|
|
8209
8404
|
}
|
|
8210
8405
|
const updatePayload = taskEventData(updatedTask);
|
|
8211
8406
|
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
8212
|
-
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
|
|
8213
|
-
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
|
|
8407
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
|
|
8408
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
|
|
8214
8409
|
return updatedTask;
|
|
8215
8410
|
}
|
|
8216
8411
|
function deleteTask(id, db) {
|
|
8217
8412
|
const d = db || getDatabase();
|
|
8413
|
+
const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
8414
|
+
if (!row)
|
|
8415
|
+
return false;
|
|
8416
|
+
recordStorageTombstone({
|
|
8417
|
+
object_type: "tasks",
|
|
8418
|
+
object_id: id,
|
|
8419
|
+
payload: rowToTask(row),
|
|
8420
|
+
version: row.version
|
|
8421
|
+
}, d);
|
|
8218
8422
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
8219
8423
|
return result.changes > 0;
|
|
8220
8424
|
}
|
|
@@ -8222,11 +8426,13 @@ var init_task_crud = __esm(() => {
|
|
|
8222
8426
|
init_types();
|
|
8223
8427
|
init_database();
|
|
8224
8428
|
init_completion_guard();
|
|
8429
|
+
init_event_emission_safety();
|
|
8225
8430
|
init_event_hooks();
|
|
8226
8431
|
init_shared_events();
|
|
8227
8432
|
init_audit();
|
|
8228
8433
|
init_webhooks();
|
|
8229
8434
|
init_checklists();
|
|
8435
|
+
init_storage_tombstones();
|
|
8230
8436
|
});
|
|
8231
8437
|
|
|
8232
8438
|
// src/lib/recurrence.ts
|
|
@@ -8375,8 +8581,9 @@ function resolveTemplateId(id, d) {
|
|
|
8375
8581
|
function createTemplate(input, db) {
|
|
8376
8582
|
const d = db || getDatabase();
|
|
8377
8583
|
const id = uuid();
|
|
8378
|
-
|
|
8379
|
-
|
|
8584
|
+
const machineId = currentStorageMachineId(d);
|
|
8585
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at, machine_id)
|
|
8586
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8380
8587
|
id,
|
|
8381
8588
|
input.name,
|
|
8382
8589
|
input.title_pattern,
|
|
@@ -8387,7 +8594,8 @@ function createTemplate(input, db) {
|
|
|
8387
8594
|
input.project_id || null,
|
|
8388
8595
|
input.plan_id || null,
|
|
8389
8596
|
JSON.stringify(input.metadata || {}),
|
|
8390
|
-
now()
|
|
8597
|
+
now(),
|
|
8598
|
+
machineId
|
|
8391
8599
|
]);
|
|
8392
8600
|
if (input.tasks && input.tasks.length > 0) {
|
|
8393
8601
|
addTemplateTasks(id, input.tasks, d);
|
|
@@ -8411,6 +8619,15 @@ function deleteTemplate(id, db) {
|
|
|
8411
8619
|
const resolved = resolveTemplateId(id, d);
|
|
8412
8620
|
if (!resolved)
|
|
8413
8621
|
return false;
|
|
8622
|
+
const template = getTemplate(resolved, d);
|
|
8623
|
+
if (!template)
|
|
8624
|
+
return false;
|
|
8625
|
+
recordStorageTombstone({
|
|
8626
|
+
object_type: "templates",
|
|
8627
|
+
object_id: resolved,
|
|
8628
|
+
payload: template,
|
|
8629
|
+
version: template.version
|
|
8630
|
+
}, d);
|
|
8414
8631
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
8415
8632
|
}
|
|
8416
8633
|
function updateTemplate(id, updates, db) {
|
|
@@ -8780,6 +8997,7 @@ function previewTemplate(templateId, variables, db) {
|
|
|
8780
8997
|
var init_templates = __esm(() => {
|
|
8781
8998
|
init_database();
|
|
8782
8999
|
init_tasks();
|
|
9000
|
+
init_storage_tombstones();
|
|
8783
9001
|
});
|
|
8784
9002
|
|
|
8785
9003
|
// src/db/task-graph.ts
|
|
@@ -8946,6 +9164,7 @@ function getBlockingDeps(id, db) {
|
|
|
8946
9164
|
}
|
|
8947
9165
|
function startTask(id, agentId, db) {
|
|
8948
9166
|
const d = db || getDatabase();
|
|
9167
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8949
9168
|
const task = getTask(id, d);
|
|
8950
9169
|
if (!task)
|
|
8951
9170
|
throw new TaskNotFoundError(id);
|
|
@@ -8960,7 +9179,8 @@ function startTask(id, agentId, db) {
|
|
|
8960
9179
|
agent_id: agentId,
|
|
8961
9180
|
title: task.title,
|
|
8962
9181
|
blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
|
|
8963
|
-
}
|
|
9182
|
+
},
|
|
9183
|
+
databasePath
|
|
8964
9184
|
});
|
|
8965
9185
|
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
8966
9186
|
}
|
|
@@ -8982,12 +9202,13 @@ function startTask(id, agentId, db) {
|
|
|
8982
9202
|
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 };
|
|
8983
9203
|
const payload = taskEventData(startedTask, { agent_id: agentId });
|
|
8984
9204
|
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
8985
|
-
emitLocalEventHooksQuiet({ type: "task.started", payload });
|
|
8986
|
-
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
|
|
9205
|
+
emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
|
|
9206
|
+
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
|
|
8987
9207
|
return startedTask;
|
|
8988
9208
|
}
|
|
8989
9209
|
function completeTask(id, agentId, db, options) {
|
|
8990
9210
|
const d = db || getDatabase();
|
|
9211
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8991
9212
|
const task = getTask(id, d);
|
|
8992
9213
|
if (!task)
|
|
8993
9214
|
throw new TaskNotFoundError(id);
|
|
@@ -9033,8 +9254,8 @@ function completeTask(id, agentId, db, options) {
|
|
|
9033
9254
|
};
|
|
9034
9255
|
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
|
|
9035
9256
|
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
9036
|
-
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
|
|
9037
|
-
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
|
|
9257
|
+
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
|
|
9258
|
+
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
|
|
9038
9259
|
let spawnedTask = null;
|
|
9039
9260
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
9040
9261
|
spawnedTask = spawnNextRecurrence(task, d, timestamp);
|
|
@@ -9078,9 +9299,9 @@ function completeTask(id, agentId, db, options) {
|
|
|
9078
9299
|
const depTask = getTask(dep.id, d);
|
|
9079
9300
|
const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
|
|
9080
9301
|
dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
|
|
9081
|
-
emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
|
|
9302
|
+
emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
|
|
9082
9303
|
if (depTask)
|
|
9083
|
-
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
|
|
9304
|
+
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
|
|
9084
9305
|
}
|
|
9085
9306
|
}
|
|
9086
9307
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
@@ -9250,6 +9471,7 @@ function getTasksChangedSince(since, filters, db) {
|
|
|
9250
9471
|
}
|
|
9251
9472
|
function failTask(id, agentId, reason, options, db) {
|
|
9252
9473
|
const d = db || getDatabase();
|
|
9474
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9253
9475
|
const task = getTask(id, d);
|
|
9254
9476
|
if (!task)
|
|
9255
9477
|
throw new TaskNotFoundError(id);
|
|
@@ -9278,8 +9500,8 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
9278
9500
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
9279
9501
|
const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
|
|
9280
9502
|
dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
|
|
9281
|
-
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
|
|
9282
|
-
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
|
|
9503
|
+
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
|
|
9504
|
+
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning", databasePath });
|
|
9283
9505
|
let retryTask;
|
|
9284
9506
|
if (options?.retry) {
|
|
9285
9507
|
const retryCount = (task.retry_count || 0) + 1;
|
|
@@ -9339,6 +9561,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
|
|
|
9339
9561
|
}
|
|
9340
9562
|
function stealTask(agentId, opts, db) {
|
|
9341
9563
|
const d = db || getDatabase();
|
|
9564
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9342
9565
|
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
9343
9566
|
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
9344
9567
|
if (staleTasks.length === 0)
|
|
@@ -9357,8 +9580,8 @@ function stealTask(agentId, opts, db) {
|
|
|
9357
9580
|
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
9358
9581
|
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
9359
9582
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
9360
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
9361
|
-
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
|
|
9583
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
9584
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
|
|
9362
9585
|
return stolenTask;
|
|
9363
9586
|
}
|
|
9364
9587
|
function claimOrSteal(agentId, filters, db) {
|
|
@@ -9406,6 +9629,7 @@ var init_task_lifecycle = __esm(() => {
|
|
|
9406
9629
|
init_types();
|
|
9407
9630
|
init_database();
|
|
9408
9631
|
init_completion_guard();
|
|
9632
|
+
init_event_emission_safety();
|
|
9409
9633
|
init_event_hooks();
|
|
9410
9634
|
init_shared_events();
|
|
9411
9635
|
init_audit();
|
|
@@ -10113,8 +10337,9 @@ function createPlan(input, db) {
|
|
|
10113
10337
|
const d = db || getDatabase();
|
|
10114
10338
|
const id = uuid();
|
|
10115
10339
|
const timestamp = now();
|
|
10116
|
-
|
|
10117
|
-
|
|
10340
|
+
const machineId = currentStorageMachineId(d);
|
|
10341
|
+
d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
|
|
10342
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10118
10343
|
id,
|
|
10119
10344
|
input.project_id || null,
|
|
10120
10345
|
input.task_list_id || null,
|
|
@@ -10123,7 +10348,8 @@ function createPlan(input, db) {
|
|
|
10123
10348
|
input.description || null,
|
|
10124
10349
|
input.status || "active",
|
|
10125
10350
|
timestamp,
|
|
10126
|
-
timestamp
|
|
10351
|
+
timestamp,
|
|
10352
|
+
machineId
|
|
10127
10353
|
]);
|
|
10128
10354
|
return getPlan(id, d);
|
|
10129
10355
|
}
|
|
@@ -10171,19 +10397,30 @@ function updatePlan(id, input, db) {
|
|
|
10171
10397
|
const updated = getPlan(id, d);
|
|
10172
10398
|
emitLocalEventHooksQuiet({
|
|
10173
10399
|
type: "plan.updated",
|
|
10174
|
-
payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id }
|
|
10400
|
+
payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id },
|
|
10401
|
+
databasePath: databasePathFromDatabase(d)
|
|
10175
10402
|
});
|
|
10176
10403
|
return updated;
|
|
10177
10404
|
}
|
|
10178
10405
|
function deletePlan(id, db) {
|
|
10179
10406
|
const d = db || getDatabase();
|
|
10407
|
+
const plan = getPlan(id, d);
|
|
10408
|
+
if (!plan)
|
|
10409
|
+
return false;
|
|
10410
|
+
recordStorageTombstone({
|
|
10411
|
+
object_type: "plans",
|
|
10412
|
+
object_id: id,
|
|
10413
|
+
payload: plan
|
|
10414
|
+
}, d);
|
|
10180
10415
|
const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
|
|
10181
10416
|
return result.changes > 0;
|
|
10182
10417
|
}
|
|
10183
10418
|
var init_plans = __esm(() => {
|
|
10184
10419
|
init_types();
|
|
10420
|
+
init_event_emission_safety();
|
|
10185
10421
|
init_event_hooks();
|
|
10186
10422
|
init_database();
|
|
10423
|
+
init_storage_tombstones();
|
|
10187
10424
|
});
|
|
10188
10425
|
|
|
10189
10426
|
// src/db/boards.ts
|
|
@@ -10590,20 +10827,20 @@ var init_boards = __esm(() => {
|
|
|
10590
10827
|
// src/lib/artifact-store.ts
|
|
10591
10828
|
import { createHash as createHash2 } from "crypto";
|
|
10592
10829
|
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
10593
|
-
import { basename, dirname as dirname5, join as join6, resolve as
|
|
10594
|
-
import { tmpdir } from "os";
|
|
10830
|
+
import { basename, dirname as dirname5, join as join6, resolve as resolve7 } from "path";
|
|
10831
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
10595
10832
|
function isInMemoryDb2(path) {
|
|
10596
10833
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
10597
10834
|
}
|
|
10598
10835
|
function artifactStoreRoot() {
|
|
10599
10836
|
if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
|
|
10600
|
-
return
|
|
10837
|
+
return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
|
|
10601
10838
|
if (process.env["TODOS_ARTIFACTS_DIR"])
|
|
10602
|
-
return
|
|
10839
|
+
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
10603
10840
|
const dbPath = getDatabasePath();
|
|
10604
10841
|
if (isInMemoryDb2(dbPath))
|
|
10605
|
-
return join6(
|
|
10606
|
-
return join6(dirname5(
|
|
10842
|
+
return join6(tmpdir2(), "hasna-todos-artifacts");
|
|
10843
|
+
return join6(dirname5(resolve7(dbPath)), "artifacts");
|
|
10607
10844
|
}
|
|
10608
10845
|
function artifactStorePath(relativePath) {
|
|
10609
10846
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
@@ -10650,7 +10887,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
10650
10887
|
return "application/octet-stream";
|
|
10651
10888
|
}
|
|
10652
10889
|
function storeArtifactContent(input) {
|
|
10653
|
-
const sourcePath =
|
|
10890
|
+
const sourcePath = resolve7(input.path);
|
|
10654
10891
|
if (!existsSync7(sourcePath))
|
|
10655
10892
|
return null;
|
|
10656
10893
|
const sourceStat = statSync2(sourcePath);
|
|
@@ -11367,7 +11604,11 @@ function startTaskRun(input, db) {
|
|
|
11367
11604
|
}, d);
|
|
11368
11605
|
}
|
|
11369
11606
|
const run = getTaskRun(id, d);
|
|
11370
|
-
emitLocalEventHooksQuiet({
|
|
11607
|
+
emitLocalEventHooksQuiet({
|
|
11608
|
+
type: "run.started",
|
|
11609
|
+
payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title },
|
|
11610
|
+
databasePath: databasePathFromDatabase(d)
|
|
11611
|
+
});
|
|
11371
11612
|
return run;
|
|
11372
11613
|
}
|
|
11373
11614
|
function beginTaskRunTransaction(input, db) {
|
|
@@ -11636,7 +11877,8 @@ function finishTaskRun(input, db) {
|
|
|
11636
11877
|
const updated = getTaskRun(run.id, d);
|
|
11637
11878
|
emitLocalEventHooksQuiet({
|
|
11638
11879
|
type: `run.${input.status}`,
|
|
11639
|
-
payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp }
|
|
11880
|
+
payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp },
|
|
11881
|
+
databasePath: databasePathFromDatabase(d)
|
|
11640
11882
|
});
|
|
11641
11883
|
return updated;
|
|
11642
11884
|
}
|
|
@@ -11720,6 +11962,7 @@ function getTaskRunLedger(runId, db) {
|
|
|
11720
11962
|
var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
|
|
11721
11963
|
var init_task_runs = __esm(() => {
|
|
11722
11964
|
init_artifact_store();
|
|
11965
|
+
init_event_emission_safety();
|
|
11723
11966
|
init_event_hooks();
|
|
11724
11967
|
init_redaction();
|
|
11725
11968
|
init_types();
|
|
@@ -12217,7 +12460,7 @@ __export(exports_helpers, {
|
|
|
12217
12460
|
});
|
|
12218
12461
|
import chalk from "chalk";
|
|
12219
12462
|
import { execSync } from "child_process";
|
|
12220
|
-
import { resolve as
|
|
12463
|
+
import { resolve as resolve8 } from "path";
|
|
12221
12464
|
function handleError(e) {
|
|
12222
12465
|
console.error(chalk.red(e instanceof Error ? e.message : String(e)));
|
|
12223
12466
|
process.exit(1);
|
|
@@ -12246,7 +12489,7 @@ function detectGitRoot() {
|
|
|
12246
12489
|
}
|
|
12247
12490
|
function resolveExplicitProject(input) {
|
|
12248
12491
|
const db = getDatabase();
|
|
12249
|
-
const byPath = getProjectByPath(
|
|
12492
|
+
const byPath = getProjectByPath(resolve8(input), db);
|
|
12250
12493
|
if (byPath)
|
|
12251
12494
|
return byPath;
|
|
12252
12495
|
const id = resolvePartialId(db, "projects", input);
|
|
@@ -12515,6 +12758,8 @@ function pointerPatch(previous, input, key) {
|
|
|
12515
12758
|
if (!Object.prototype.hasOwnProperty.call(input, key))
|
|
12516
12759
|
return previous;
|
|
12517
12760
|
const value = input[key];
|
|
12761
|
+
if (value === undefined)
|
|
12762
|
+
return previous;
|
|
12518
12763
|
return typeof value === "string" && value.trim() ? value : undefined;
|
|
12519
12764
|
}
|
|
12520
12765
|
var init_task_routing = __esm(() => {
|
|
@@ -12531,7 +12776,7 @@ __export(exports_task_commands, {
|
|
|
12531
12776
|
registerTaskCommands: () => registerTaskCommands
|
|
12532
12777
|
});
|
|
12533
12778
|
import chalk2 from "chalk";
|
|
12534
|
-
import { basename as basename3, resolve as
|
|
12779
|
+
import { basename as basename3, resolve as resolve9 } from "path";
|
|
12535
12780
|
function resolveProjectIdOrSlug(input) {
|
|
12536
12781
|
const db = getDatabase();
|
|
12537
12782
|
const byId = getProject(input, db);
|
|
@@ -12541,7 +12786,7 @@ function resolveProjectIdOrSlug(input) {
|
|
|
12541
12786
|
if (row)
|
|
12542
12787
|
return row.id;
|
|
12543
12788
|
if (isPathLike(input)) {
|
|
12544
|
-
const projectPath =
|
|
12789
|
+
const projectPath = resolve9(input);
|
|
12545
12790
|
const byPath = getProjectByPath(projectPath, db);
|
|
12546
12791
|
return (byPath ?? ensureProject(basename3(projectPath), projectPath, db)).id;
|
|
12547
12792
|
}
|
|
@@ -12701,7 +12946,7 @@ function registerTaskCommands(program2) {
|
|
|
12701
12946
|
task_list_id: taskListId,
|
|
12702
12947
|
tags: parseTags(opts.tags),
|
|
12703
12948
|
metadata: buildExpectationMetadata(opts),
|
|
12704
|
-
working_dir: opts.workingDir ?
|
|
12949
|
+
working_dir: opts.workingDir ? resolve9(opts.workingDir) : process.cwd(),
|
|
12705
12950
|
project_id: projectId,
|
|
12706
12951
|
assigned_to: opts.assign,
|
|
12707
12952
|
agent_id: globalOpts.agent,
|
|
@@ -15272,7 +15517,7 @@ __export(exports_project_bootstrap, {
|
|
|
15272
15517
|
bootstrapProject: () => bootstrapProject
|
|
15273
15518
|
});
|
|
15274
15519
|
import { existsSync as existsSync10, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
15275
|
-
import { basename as basename4, dirname as dirname6, resolve as
|
|
15520
|
+
import { basename as basename4, dirname as dirname6, resolve as resolve10 } from "path";
|
|
15276
15521
|
function safeStat(path) {
|
|
15277
15522
|
try {
|
|
15278
15523
|
return statSync3(path);
|
|
@@ -15281,7 +15526,7 @@ function safeStat(path) {
|
|
|
15281
15526
|
}
|
|
15282
15527
|
}
|
|
15283
15528
|
function canonicalPath(input) {
|
|
15284
|
-
const resolved =
|
|
15529
|
+
const resolved = resolve10(input);
|
|
15285
15530
|
const stats = safeStat(resolved);
|
|
15286
15531
|
if (stats?.isFile())
|
|
15287
15532
|
return dirname6(resolved);
|
|
@@ -15290,7 +15535,7 @@ function canonicalPath(input) {
|
|
|
15290
15535
|
function findUp(start, marker) {
|
|
15291
15536
|
let current = canonicalPath(start);
|
|
15292
15537
|
while (true) {
|
|
15293
|
-
if (existsSync10(
|
|
15538
|
+
if (existsSync10(resolve10(current, marker)))
|
|
15294
15539
|
return current;
|
|
15295
15540
|
const parent = dirname6(current);
|
|
15296
15541
|
if (parent === current)
|
|
@@ -15301,7 +15546,7 @@ function findUp(start, marker) {
|
|
|
15301
15546
|
function readPackageJson(path) {
|
|
15302
15547
|
if (!path)
|
|
15303
15548
|
return null;
|
|
15304
|
-
const file =
|
|
15549
|
+
const file = resolve10(path, "package.json");
|
|
15305
15550
|
if (!existsSync10(file))
|
|
15306
15551
|
return null;
|
|
15307
15552
|
try {
|
|
@@ -15324,7 +15569,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
15324
15569
|
if (rootPackage?.workspaces)
|
|
15325
15570
|
markers.push("package.json#workspaces");
|
|
15326
15571
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
15327
|
-
if (existsSync10(
|
|
15572
|
+
if (existsSync10(resolve10(root, marker)))
|
|
15328
15573
|
markers.push(marker);
|
|
15329
15574
|
}
|
|
15330
15575
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -15460,7 +15705,7 @@ __export(exports_extract, {
|
|
|
15460
15705
|
});
|
|
15461
15706
|
import { existsSync as existsSync11, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
15462
15707
|
import { createHash as createHash3 } from "crypto";
|
|
15463
|
-
import { relative as relative3, resolve as
|
|
15708
|
+
import { relative as relative3, resolve as resolve11, join as join10 } from "path";
|
|
15464
15709
|
function stableHash(value) {
|
|
15465
15710
|
return createHash3("sha256").update(value).digest("hex");
|
|
15466
15711
|
}
|
|
@@ -15468,7 +15713,7 @@ function normalizePathForMatch(value) {
|
|
|
15468
15713
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
15469
15714
|
}
|
|
15470
15715
|
function readGitignorePatterns(basePath) {
|
|
15471
|
-
const root = statSync4(basePath).isFile() ?
|
|
15716
|
+
const root = statSync4(basePath).isFile() ? resolve11(basePath, "..") : basePath;
|
|
15472
15717
|
const gitignorePath = join10(root, ".gitignore");
|
|
15473
15718
|
if (!existsSync11(gitignorePath))
|
|
15474
15719
|
return [];
|
|
@@ -15604,7 +15849,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
|
|
|
15604
15849
|
return files.sort();
|
|
15605
15850
|
}
|
|
15606
15851
|
function buildCodebaseIndex(options) {
|
|
15607
|
-
const basePath =
|
|
15852
|
+
const basePath = resolve11(options.path);
|
|
15608
15853
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
15609
15854
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
15610
15855
|
const excludes = options.exclude || [];
|
|
@@ -15615,7 +15860,7 @@ function buildCodebaseIndex(options) {
|
|
|
15615
15860
|
const fullPath = statSync4(basePath).isFile() ? basePath : join10(basePath, file);
|
|
15616
15861
|
try {
|
|
15617
15862
|
const source = readFileSync6(fullPath, "utf-8");
|
|
15618
|
-
const relPath = statSync4(basePath).isFile() ? relative3(
|
|
15863
|
+
const relPath = statSync4(basePath).isFile() ? relative3(resolve11(basePath, ".."), fullPath) : file;
|
|
15619
15864
|
indexed.push({
|
|
15620
15865
|
file: relPath,
|
|
15621
15866
|
checksum: stableHash(source).slice(0, 24),
|
|
@@ -15635,7 +15880,7 @@ function buildCodebaseIndex(options) {
|
|
|
15635
15880
|
};
|
|
15636
15881
|
}
|
|
15637
15882
|
function extractTodos(options, db) {
|
|
15638
|
-
const basePath =
|
|
15883
|
+
const basePath = resolve11(options.path);
|
|
15639
15884
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
15640
15885
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
15641
15886
|
const excludes = options.exclude || [];
|
|
@@ -15646,7 +15891,7 @@ function extractTodos(options, db) {
|
|
|
15646
15891
|
const fullPath = statSync4(basePath).isFile() ? basePath : join10(basePath, file);
|
|
15647
15892
|
try {
|
|
15648
15893
|
const source = readFileSync6(fullPath, "utf-8");
|
|
15649
|
-
const relPath = statSync4(basePath).isFile() ? relative3(
|
|
15894
|
+
const relPath = statSync4(basePath).isFile() ? relative3(resolve11(basePath, ".."), fullPath) : file;
|
|
15650
15895
|
const comments = extractFromSource(source, relPath, tags);
|
|
15651
15896
|
allComments.push(...comments);
|
|
15652
15897
|
} catch {}
|
|
@@ -15740,7 +15985,7 @@ async function watchSourceTodos(options, onRun) {
|
|
|
15740
15985
|
const interval = Math.max(100, options.interval_ms || 2000);
|
|
15741
15986
|
const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
|
|
15742
15987
|
const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
|
|
15743
|
-
const root =
|
|
15988
|
+
const root = resolve11(options.path);
|
|
15744
15989
|
const runs = [];
|
|
15745
15990
|
let previous = new Map;
|
|
15746
15991
|
for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
|
|
@@ -16911,7 +17156,7 @@ __export(exports_project_commands, {
|
|
|
16911
17156
|
registerProjectCommands: () => registerProjectCommands
|
|
16912
17157
|
});
|
|
16913
17158
|
import chalk4 from "chalk";
|
|
16914
|
-
import { basename as basename5, resolve as
|
|
17159
|
+
import { basename as basename5, resolve as resolve12 } from "path";
|
|
16915
17160
|
function collectOption(value, previous = []) {
|
|
16916
17161
|
return [...previous, value];
|
|
16917
17162
|
}
|
|
@@ -17253,7 +17498,7 @@ function registerProjectCommands(program2) {
|
|
|
17253
17498
|
program2.command("projects").description("List and manage projects").option("--add <path>", "Register a project by path").option("--name <name>", "Project name (with --add)").option("--task-list-id <id>", "Custom task list ID (with --add)").action(async (opts) => {
|
|
17254
17499
|
const globalOpts = program2.opts();
|
|
17255
17500
|
if (opts.add) {
|
|
17256
|
-
const projectPath =
|
|
17501
|
+
const projectPath = resolve12(opts.add);
|
|
17257
17502
|
const name = opts.name || basename5(projectPath);
|
|
17258
17503
|
const existing = getProjectByPath(projectPath);
|
|
17259
17504
|
let project;
|
|
@@ -17337,7 +17582,7 @@ function registerProjectCommands(program2) {
|
|
|
17337
17582
|
console.error(chalk4.red(`Project not found: ${projectId}`));
|
|
17338
17583
|
process.exit(1);
|
|
17339
17584
|
}
|
|
17340
|
-
const entry = setMachineLocalPath2(resolved,
|
|
17585
|
+
const entry = setMachineLocalPath2(resolved, resolve12(projectPath));
|
|
17341
17586
|
if (useJson) {
|
|
17342
17587
|
output(entry, true);
|
|
17343
17588
|
} else {
|
|
@@ -17404,7 +17649,7 @@ function registerProjectCommands(program2) {
|
|
|
17404
17649
|
const patterns = opts.pattern ? opts.pattern.split(",").map((t) => t.trim().toUpperCase()) : undefined;
|
|
17405
17650
|
const taskListId = opts.list ? resolveTaskListId(opts.list) : undefined;
|
|
17406
17651
|
const result = extractTodos2({
|
|
17407
|
-
path:
|
|
17652
|
+
path: resolve12(scanPath),
|
|
17408
17653
|
patterns,
|
|
17409
17654
|
project_id: projectId,
|
|
17410
17655
|
task_list_id: taskListId,
|
|
@@ -17465,7 +17710,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17465
17710
|
const taskListId = opts.list ? resolveTaskListId(opts.list) : undefined;
|
|
17466
17711
|
const maxRuns = opts.maxRuns ? parseInt(opts.maxRuns, 10) : 1;
|
|
17467
17712
|
const result = await watchSourceTodos2({
|
|
17468
|
-
path:
|
|
17713
|
+
path: resolve12(scanPath),
|
|
17469
17714
|
patterns,
|
|
17470
17715
|
project_id: projectId,
|
|
17471
17716
|
task_list_id: taskListId,
|
|
@@ -17502,7 +17747,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17502
17747
|
const writeOutput = async (content) => {
|
|
17503
17748
|
if (opts.output) {
|
|
17504
17749
|
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
17505
|
-
writeFileSync5(
|
|
17750
|
+
writeFileSync5(resolve12(opts.output), content.endsWith(`
|
|
17506
17751
|
`) ? content : `${content}
|
|
17507
17752
|
`);
|
|
17508
17753
|
} else {
|
|
@@ -17517,12 +17762,12 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17517
17762
|
const exported = opts.encrypt ? createEncryptedBridgeBundle2(bundle, { profile: opts.encryptionProfile }) : bundle;
|
|
17518
17763
|
const json = JSON.stringify(exported, null, 2);
|
|
17519
17764
|
await writeOutput(json);
|
|
17520
|
-
emitLocalEventHooksQuiet3({ type: "export.finished", payload: { format: "bridge", encrypted: Boolean(opts.encrypt), project_id: projectId, output: opts.output ?
|
|
17765
|
+
emitLocalEventHooksQuiet3({ type: "export.finished", payload: { format: "bridge", encrypted: Boolean(opts.encrypt), project_id: projectId, output: opts.output ? resolve12(opts.output) : null, stats: bundle.stats } });
|
|
17521
17766
|
if (!opts.encrypt && !opts.allowPlaintextSensitive) {
|
|
17522
17767
|
console.error(chalk4.yellow("Warning: bridge exports are plaintext JSON. Use --encrypt for sensitive metadata, evidence, and artifact bundles."));
|
|
17523
17768
|
}
|
|
17524
17769
|
if (opts.output && !globalOpts.json) {
|
|
17525
|
-
console.log(chalk4.green(`${opts.encrypt ? "Encrypted bridge export" : "Bridge export"} written to ${
|
|
17770
|
+
console.log(chalk4.green(`${opts.encrypt ? "Encrypted bridge export" : "Bridge export"} written to ${resolve12(opts.output)}`));
|
|
17526
17771
|
}
|
|
17527
17772
|
return;
|
|
17528
17773
|
}
|
|
@@ -17535,7 +17780,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17535
17780
|
await writeOutput(JSON.stringify(tasks, null, 2));
|
|
17536
17781
|
}
|
|
17537
17782
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
17538
|
-
emitLocalEventHooksQuiet2({ type: "export.finished", payload: { format: opts.format, project_id: projectId, output: opts.output ?
|
|
17783
|
+
emitLocalEventHooksQuiet2({ type: "export.finished", payload: { format: opts.format, project_id: projectId, output: opts.output ? resolve12(opts.output) : null, count: exportedCount } });
|
|
17539
17784
|
});
|
|
17540
17785
|
program2.command("bridge-import <file>").description("Dry-run or apply a local hasna/todos bridge export bundle").option("--apply", "Apply the import. Defaults to dry-run.").option("--decrypt", "Decrypt an encrypted bridge export before importing").option("--resolve-conflicts", "Safely merge existing local tasks by filling blank fields, unioning tags, and recording unresolved divergences").action(async (file, opts) => {
|
|
17541
17786
|
const globalOpts = program2.opts();
|
|
@@ -17543,13 +17788,13 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17543
17788
|
const { readFileSync: readFileSync7 } = await import("fs");
|
|
17544
17789
|
const { importLocalBridgeBundle: importLocalBridgeBundle2 } = await Promise.resolve().then(() => (init_local_bridge(), exports_local_bridge));
|
|
17545
17790
|
const { decryptBridgeBundle: decryptBridgeBundle2, isEncryptedBridgeBundle: isEncryptedBridgeBundle2 } = await Promise.resolve().then(() => (init_local_encryption(), exports_local_encryption));
|
|
17546
|
-
const parsed = JSON.parse(readFileSync7(
|
|
17791
|
+
const parsed = JSON.parse(readFileSync7(resolve12(file), "utf-8"));
|
|
17547
17792
|
const bundle = isEncryptedBridgeBundle2(parsed) ? opts.decrypt ? decryptBridgeBundle2(parsed) : (() => {
|
|
17548
17793
|
throw new Error("Bridge bundle is encrypted. Re-run with --decrypt and the configured key environment variable set.");
|
|
17549
17794
|
})() : parsed;
|
|
17550
17795
|
const result = importLocalBridgeBundle2(bundle, { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
|
|
17551
17796
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
17552
|
-
emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file:
|
|
17797
|
+
emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve12(file), dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, conflicts: result.conflicts.length, issues: result.issues.length } });
|
|
17553
17798
|
if (globalOpts.json) {
|
|
17554
17799
|
output(result, true);
|
|
17555
17800
|
return;
|
|
@@ -17579,9 +17824,9 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17579
17824
|
try {
|
|
17580
17825
|
const { readFileSync: readFileSync7 } = await import("fs");
|
|
17581
17826
|
const { importTodosMarkdown: importTodosMarkdown2 } = await Promise.resolve().then(() => (init_todos_md(), exports_todos_md));
|
|
17582
|
-
const result = importTodosMarkdown2(readFileSync7(
|
|
17827
|
+
const result = importTodosMarkdown2(readFileSync7(resolve12(file), "utf-8"), { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
|
|
17583
17828
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
17584
|
-
emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file:
|
|
17829
|
+
emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve12(file), format: "todos.md", dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, issues: result.issues.length } });
|
|
17585
17830
|
if (globalOpts.json) {
|
|
17586
17831
|
output(result, true);
|
|
17587
17832
|
return;
|
|
@@ -18076,6 +18321,7 @@ function rowToAgent(row) {
|
|
|
18076
18321
|
}
|
|
18077
18322
|
function registerAgent(input, db) {
|
|
18078
18323
|
const d = db || getDatabase();
|
|
18324
|
+
const machineId = currentStorageMachineId(d);
|
|
18079
18325
|
const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
|
|
18080
18326
|
const normalizedName = validateAgentName(input.name, existingNames);
|
|
18081
18327
|
const existing = getAgentByName(normalizedName, d);
|
|
@@ -18116,14 +18362,18 @@ function registerAgent(input, db) {
|
|
|
18116
18362
|
updates.push("active_project_id = ?");
|
|
18117
18363
|
params.push(input.project_id);
|
|
18118
18364
|
}
|
|
18365
|
+
if (!existing.machine_id && machineId) {
|
|
18366
|
+
updates.push("machine_id = ?");
|
|
18367
|
+
params.push(machineId);
|
|
18368
|
+
}
|
|
18119
18369
|
params.push(existing.id);
|
|
18120
18370
|
d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
18121
18371
|
return getAgent(existing.id, d);
|
|
18122
18372
|
}
|
|
18123
18373
|
const id = shortUuid();
|
|
18124
18374
|
const timestamp = now();
|
|
18125
|
-
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)
|
|
18126
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
18375
|
+
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)
|
|
18376
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
18127
18377
|
id,
|
|
18128
18378
|
normalizedName,
|
|
18129
18379
|
input.description || null,
|
|
@@ -18139,7 +18389,8 @@ function registerAgent(input, db) {
|
|
|
18139
18389
|
timestamp,
|
|
18140
18390
|
input.session_id || null,
|
|
18141
18391
|
input.working_dir || null,
|
|
18142
|
-
input.project_id && input.session_id ? input.project_id : null
|
|
18392
|
+
input.project_id && input.session_id ? input.project_id : null,
|
|
18393
|
+
machineId
|
|
18143
18394
|
]);
|
|
18144
18395
|
return getAgent(id, d);
|
|
18145
18396
|
}
|
|
@@ -18322,6 +18573,7 @@ function getCapableAgents(capabilities, opts, db) {
|
|
|
18322
18573
|
}
|
|
18323
18574
|
var init_agents = __esm(() => {
|
|
18324
18575
|
init_database();
|
|
18576
|
+
init_storage_tombstones();
|
|
18325
18577
|
init_agent_names();
|
|
18326
18578
|
});
|
|
18327
18579
|
|
|
@@ -19584,7 +19836,7 @@ __export(exports_local_extensions, {
|
|
|
19584
19836
|
});
|
|
19585
19837
|
import { createHash as createHash5, createVerify } from "crypto";
|
|
19586
19838
|
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
19587
|
-
import { basename as basename6, join as join11, resolve as
|
|
19839
|
+
import { basename as basename6, join as join11, resolve as resolve13 } from "path";
|
|
19588
19840
|
function isObject(value) {
|
|
19589
19841
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
19590
19842
|
}
|
|
@@ -19842,7 +20094,7 @@ function verifyExtensionSignature(input) {
|
|
|
19842
20094
|
return verifier.verify(input.public_key, decodeSignature(input.signature));
|
|
19843
20095
|
}
|
|
19844
20096
|
function inspectExtensionSource(source2) {
|
|
19845
|
-
const resolved =
|
|
20097
|
+
const resolved = resolve13(source2);
|
|
19846
20098
|
if (!existsSync13(resolved))
|
|
19847
20099
|
throw new Error(`extension source not found: ${source2}`);
|
|
19848
20100
|
const stat = statSync5(resolved);
|
|
@@ -19940,7 +20192,7 @@ function testExtensionCompatibility(sourceOrManifest) {
|
|
|
19940
20192
|
function projectExtensionSources(projectPath) {
|
|
19941
20193
|
if (!projectPath)
|
|
19942
20194
|
return [];
|
|
19943
|
-
const root =
|
|
20195
|
+
const root = resolve13(projectPath);
|
|
19944
20196
|
const candidates = [
|
|
19945
20197
|
join11(root, "todos.extension.json"),
|
|
19946
20198
|
join11(root, ".todos", "todos.extension.json")
|
|
@@ -19959,7 +20211,7 @@ function projectExtensionSources(projectPath) {
|
|
|
19959
20211
|
}
|
|
19960
20212
|
function discoverLocalExtensions(options = {}) {
|
|
19961
20213
|
const config = loadConfig();
|
|
19962
|
-
const projectPath = options.project_path ?
|
|
20214
|
+
const projectPath = options.project_path ? resolve13(options.project_path) : null;
|
|
19963
20215
|
const configuredSources = [
|
|
19964
20216
|
...config.extension_sources || [],
|
|
19965
20217
|
...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
|
|
@@ -19967,7 +20219,7 @@ function discoverLocalExtensions(options = {}) {
|
|
|
19967
20219
|
const sources = Array.from(new Set([
|
|
19968
20220
|
...configuredSources,
|
|
19969
20221
|
...projectExtensionSources(projectPath || undefined)
|
|
19970
|
-
])).map((source2) => projectPath && !source2.startsWith("/") ?
|
|
20222
|
+
])).map((source2) => projectPath && !source2.startsWith("/") ? resolve13(projectPath, source2) : resolve13(source2));
|
|
19971
20223
|
const warnings = [];
|
|
19972
20224
|
const discovered = [];
|
|
19973
20225
|
for (const source2 of sources) {
|
|
@@ -20227,9 +20479,9 @@ __export(exports_policy_packs, {
|
|
|
20227
20479
|
getPolicyPack: () => getPolicyPack,
|
|
20228
20480
|
explainPolicyPack: () => explainPolicyPack
|
|
20229
20481
|
});
|
|
20230
|
-
import { relative as relative4, resolve as
|
|
20482
|
+
import { relative as relative4, resolve as resolve14 } from "path";
|
|
20231
20483
|
function normalizePath3(path) {
|
|
20232
|
-
return
|
|
20484
|
+
return resolve14(path);
|
|
20233
20485
|
}
|
|
20234
20486
|
function unique5(values) {
|
|
20235
20487
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -20284,7 +20536,7 @@ function commandMatches(commands, pattern) {
|
|
|
20284
20536
|
}
|
|
20285
20537
|
function pathMatches(paths, pattern, root) {
|
|
20286
20538
|
return paths.filter((path) => {
|
|
20287
|
-
const candidate = path.startsWith("/") ? path :
|
|
20539
|
+
const candidate = path.startsWith("/") ? path : resolve14(root, path);
|
|
20288
20540
|
if (!isPathInside3(root, candidate))
|
|
20289
20541
|
return matchesPattern3(path, pattern);
|
|
20290
20542
|
return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
|
|
@@ -20641,7 +20893,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
|
|
|
20641
20893
|
if (action === "approved" || action === "rejected" || action === "expired") {
|
|
20642
20894
|
emitLocalEventHooksQuiet({
|
|
20643
20895
|
type: "approval.decided",
|
|
20644
|
-
payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id }
|
|
20896
|
+
payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
|
|
20897
|
+
databasePath: databasePathFromDatabase(db)
|
|
20645
20898
|
});
|
|
20646
20899
|
}
|
|
20647
20900
|
}
|
|
@@ -20787,6 +21040,7 @@ var init_approval_gates = __esm(() => {
|
|
|
20787
21040
|
init_task_runs();
|
|
20788
21041
|
init_tasks();
|
|
20789
21042
|
init_types();
|
|
21043
|
+
init_event_emission_safety();
|
|
20790
21044
|
init_event_hooks();
|
|
20791
21045
|
});
|
|
20792
21046
|
|
|
@@ -21638,7 +21892,7 @@ var init_doctor = __esm(() => {
|
|
|
21638
21892
|
});
|
|
21639
21893
|
|
|
21640
21894
|
// src/server/routes.ts
|
|
21641
|
-
import { join as join13, resolve as
|
|
21895
|
+
import { join as join13, resolve as resolve15, sep as sep2 } from "path";
|
|
21642
21896
|
function parseFieldsParam(url) {
|
|
21643
21897
|
const fieldsParam = url.searchParams.get("fields");
|
|
21644
21898
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -22376,9 +22630,9 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
22376
22630
|
return null;
|
|
22377
22631
|
if (path !== "/") {
|
|
22378
22632
|
const filePath = join13(ctx.dashboardDir, path);
|
|
22379
|
-
const resolvedFile =
|
|
22380
|
-
const resolvedBase =
|
|
22381
|
-
if (!resolvedFile.startsWith(resolvedBase +
|
|
22633
|
+
const resolvedFile = resolve15(filePath);
|
|
22634
|
+
const resolvedBase = resolve15(ctx.dashboardDir);
|
|
22635
|
+
if (!resolvedFile.startsWith(resolvedBase + sep2) && resolvedFile !== resolvedBase) {
|
|
22382
22636
|
return json2({ error: "Forbidden" }, 403);
|
|
22383
22637
|
}
|
|
22384
22638
|
const res2 = serveStaticFile2(filePath);
|
|
@@ -27381,7 +27635,7 @@ __export(exports_mention_resolver, {
|
|
|
27381
27635
|
resolveMentions: () => resolveMentions
|
|
27382
27636
|
});
|
|
27383
27637
|
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync7 } from "fs";
|
|
27384
|
-
import { basename as basename8, isAbsolute, join as join14, relative as relative5, resolve as
|
|
27638
|
+
import { basename as basename8, isAbsolute, join as join14, relative as relative5, resolve as resolve16, sep as sep3 } from "path";
|
|
27385
27639
|
function blankResolution(parsed) {
|
|
27386
27640
|
return {
|
|
27387
27641
|
input: parsed.input,
|
|
@@ -27404,11 +27658,11 @@ function backlink(kind, key, label, target = key) {
|
|
|
27404
27658
|
return { kind, key, label, target };
|
|
27405
27659
|
}
|
|
27406
27660
|
function normalizeWorkspace(workspace) {
|
|
27407
|
-
return
|
|
27661
|
+
return resolve16(workspace || process.cwd());
|
|
27408
27662
|
}
|
|
27409
27663
|
function isInside(root, absolutePath) {
|
|
27410
27664
|
const rel = relative5(root, absolutePath);
|
|
27411
|
-
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${
|
|
27665
|
+
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep3}`) && !isAbsolute(rel);
|
|
27412
27666
|
}
|
|
27413
27667
|
function normalizeRelativePath(value) {
|
|
27414
27668
|
const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
|
|
@@ -27472,7 +27726,7 @@ function resolveFile(parsed, workspace) {
|
|
|
27472
27726
|
resolution.warnings.push("path is empty or escapes the workspace");
|
|
27473
27727
|
return resolution;
|
|
27474
27728
|
}
|
|
27475
|
-
const absolutePath =
|
|
27729
|
+
const absolutePath = resolve16(workspace, relPath);
|
|
27476
27730
|
if (!isInside(workspace, absolutePath)) {
|
|
27477
27731
|
resolution.path = relPath;
|
|
27478
27732
|
resolution.warnings.push("path escapes the workspace");
|
|
@@ -29999,7 +30253,7 @@ function canonicalize(value) {
|
|
|
29999
30253
|
function hash(value) {
|
|
30000
30254
|
return createHash7("sha256").update(value).digest("hex");
|
|
30001
30255
|
}
|
|
30002
|
-
function
|
|
30256
|
+
function parsePayload2(value) {
|
|
30003
30257
|
if (!value)
|
|
30004
30258
|
return {};
|
|
30005
30259
|
try {
|
|
@@ -30098,7 +30352,7 @@ function taskScopedRows(db, scope) {
|
|
|
30098
30352
|
FROM handoffs h
|
|
30099
30353
|
WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
|
|
30100
30354
|
`).all(scope.project_id ?? null).filter((row) => {
|
|
30101
|
-
const payload =
|
|
30355
|
+
const payload = parsePayload2(row.payload_json);
|
|
30102
30356
|
const taskRefs = parseStringArray(payload["task_ids"]);
|
|
30103
30357
|
const runRefs = parseStringArray(payload["run_ids"]);
|
|
30104
30358
|
if (scope.project_id && row.project_id !== scope.project_id)
|
|
@@ -30129,7 +30383,7 @@ function toLedgerEntries(rows) {
|
|
|
30129
30383
|
});
|
|
30130
30384
|
let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
|
|
30131
30385
|
return ordered.map((row, index) => {
|
|
30132
|
-
const payload =
|
|
30386
|
+
const payload = parsePayload2(row.payload_json);
|
|
30133
30387
|
const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
|
|
30134
30388
|
const chainHash = hash(`${previous}
|
|
30135
30389
|
${payloadHash}`);
|
|
@@ -30277,7 +30531,7 @@ __export(exports_release_compatibility, {
|
|
|
30277
30531
|
LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
|
|
30278
30532
|
});
|
|
30279
30533
|
import { readFileSync as readFileSync9 } from "fs";
|
|
30280
|
-
import { join as join15, resolve as
|
|
30534
|
+
import { join as join15, resolve as resolve17 } from "path";
|
|
30281
30535
|
import { Database as Database2 } from "bun:sqlite";
|
|
30282
30536
|
function pass(id, message, details) {
|
|
30283
30537
|
return { id, status: "passed", message, details };
|
|
@@ -30385,7 +30639,7 @@ function checkChangelog() {
|
|
|
30385
30639
|
];
|
|
30386
30640
|
}
|
|
30387
30641
|
function createReleaseCompatibilityReport(options = {}) {
|
|
30388
|
-
const root =
|
|
30642
|
+
const root = resolve17(options.root ?? process.cwd());
|
|
30389
30643
|
const packageJson = readPackageJson2(root);
|
|
30390
30644
|
const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
|
|
30391
30645
|
const checks = [
|
|
@@ -36065,7 +36319,7 @@ function classifyLog(text) {
|
|
|
36065
36319
|
async function sleep3(ms) {
|
|
36066
36320
|
if (ms <= 0)
|
|
36067
36321
|
return;
|
|
36068
|
-
await new Promise((
|
|
36322
|
+
await new Promise((resolve18) => setTimeout(resolve18, ms));
|
|
36069
36323
|
}
|
|
36070
36324
|
async function runCommandProvider(provider, input) {
|
|
36071
36325
|
const commandTemplate = input.command || provider.command;
|
|
@@ -38415,7 +38669,7 @@ __export(exports_local_backups, {
|
|
|
38415
38669
|
});
|
|
38416
38670
|
import { createHash as createHash8 } from "crypto";
|
|
38417
38671
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
38418
|
-
import { dirname as dirname8, resolve as
|
|
38672
|
+
import { dirname as dirname8, resolve as resolve18 } from "path";
|
|
38419
38673
|
import { mkdirSync as mkdirSync7 } from "fs";
|
|
38420
38674
|
function stableJson(value) {
|
|
38421
38675
|
if (value === null || typeof value !== "object")
|
|
@@ -38517,14 +38771,14 @@ function createLocalBackup(options = {}, db) {
|
|
|
38517
38771
|
return backup;
|
|
38518
38772
|
}
|
|
38519
38773
|
function writeLocalBackupFile(backup, outputPath) {
|
|
38520
|
-
const path =
|
|
38774
|
+
const path = resolve18(outputPath);
|
|
38521
38775
|
mkdirSync7(dirname8(path), { recursive: true });
|
|
38522
38776
|
writeFileSync5(path, `${JSON.stringify(backup, null, 2)}
|
|
38523
38777
|
`);
|
|
38524
38778
|
return path;
|
|
38525
38779
|
}
|
|
38526
38780
|
function readLocalBackupFile(path) {
|
|
38527
|
-
return JSON.parse(readFileSync11(
|
|
38781
|
+
return JSON.parse(readFileSync11(resolve18(path), "utf-8"));
|
|
38528
38782
|
}
|
|
38529
38783
|
function verifyLocalBackup(value, options = {}, db) {
|
|
38530
38784
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -42820,7 +43074,8 @@ function writeQueue(task2, queue, actor, action, db) {
|
|
|
42820
43074
|
logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
|
|
42821
43075
|
emitLocalEventHooksQuiet({
|
|
42822
43076
|
type: `review.${action}`,
|
|
42823
|
-
payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by }
|
|
43077
|
+
payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by },
|
|
43078
|
+
databasePath: databasePathFromDatabase(d)
|
|
42824
43079
|
});
|
|
42825
43080
|
return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
|
|
42826
43081
|
}
|
|
@@ -43124,6 +43379,7 @@ var init_review_queues = __esm(() => {
|
|
|
43124
43379
|
init_database();
|
|
43125
43380
|
init_tasks();
|
|
43126
43381
|
init_config();
|
|
43382
|
+
init_event_emission_safety();
|
|
43127
43383
|
init_event_hooks();
|
|
43128
43384
|
init_task_contracts();
|
|
43129
43385
|
STATES = new Set(["requested", "claimed", "approved", "changes_requested", "returned", "reopened"]);
|
|
@@ -45222,8 +45478,8 @@ __export(exports_environment_snapshots, {
|
|
|
45222
45478
|
import { createHash as createHash12 } from "crypto";
|
|
45223
45479
|
import { existsSync as existsSync17, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
45224
45480
|
import { hostname as hostname2, platform, arch } from "os";
|
|
45225
|
-
import { dirname as dirname9, join as join17, resolve as
|
|
45226
|
-
import { tmpdir as
|
|
45481
|
+
import { dirname as dirname9, join as join17, resolve as resolve19 } from "path";
|
|
45482
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
45227
45483
|
function sha2566(value) {
|
|
45228
45484
|
return createHash12("sha256").update(value).digest("hex");
|
|
45229
45485
|
}
|
|
@@ -45336,15 +45592,15 @@ function commandEnv(env, includeValues) {
|
|
|
45336
45592
|
function defaultSnapshotDir() {
|
|
45337
45593
|
const dbPath = getDatabasePath();
|
|
45338
45594
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
45339
|
-
return join17(
|
|
45340
|
-
return join17(dirname9(
|
|
45595
|
+
return join17(tmpdir3(), "hasna-todos", "environment-snapshots");
|
|
45596
|
+
return join17(dirname9(resolve19(dbPath)), "environment-snapshots");
|
|
45341
45597
|
}
|
|
45342
45598
|
function snapshotWithId(snapshot) {
|
|
45343
45599
|
const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
|
|
45344
45600
|
return { id: `env_${digest}`, ...snapshot };
|
|
45345
45601
|
}
|
|
45346
45602
|
function captureEnvironmentSnapshot(input = {}) {
|
|
45347
|
-
const root =
|
|
45603
|
+
const root = resolve19(input.root || process.cwd());
|
|
45348
45604
|
const env = input.env || process.env;
|
|
45349
45605
|
const warnings = [];
|
|
45350
45606
|
const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
|
|
@@ -45384,13 +45640,13 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
45384
45640
|
});
|
|
45385
45641
|
}
|
|
45386
45642
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
45387
|
-
const path = outputPath ?
|
|
45643
|
+
const path = outputPath ? resolve19(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
45388
45644
|
ensureDir2(dirname9(path));
|
|
45389
45645
|
writeJsonFile(path, snapshot);
|
|
45390
45646
|
return path;
|
|
45391
45647
|
}
|
|
45392
45648
|
function readEnvironmentSnapshot(path) {
|
|
45393
|
-
const snapshot = readJsonFile(
|
|
45649
|
+
const snapshot = readJsonFile(resolve19(path));
|
|
45394
45650
|
if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
|
|
45395
45651
|
throw new Error(`Invalid environment snapshot: ${path}`);
|
|
45396
45652
|
}
|
|
@@ -53241,7 +53497,7 @@ __export(exports_machines, {
|
|
|
53241
53497
|
import chalk10 from "chalk";
|
|
53242
53498
|
import { execSync as execSync4 } from "child_process";
|
|
53243
53499
|
import { readFileSync as readFileSync17, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
53244
|
-
import { tmpdir as
|
|
53500
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
53245
53501
|
import { join as join21 } from "path";
|
|
53246
53502
|
function getOrCreateLocalMachineName() {
|
|
53247
53503
|
return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
|
|
@@ -53280,7 +53536,7 @@ function remoteTempPath(sshAddress) {
|
|
|
53280
53536
|
}
|
|
53281
53537
|
function readRemoteBridgeBundle(sshAddress) {
|
|
53282
53538
|
const remotePath = remoteTempPath(sshAddress);
|
|
53283
|
-
const localPath = join21(
|
|
53539
|
+
const localPath = join21(tmpdir4(), `todos-bridge-pull-${uuid()}.json`);
|
|
53284
53540
|
try {
|
|
53285
53541
|
runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
|
|
53286
53542
|
scpFromRemote(sshAddress, remotePath, localPath);
|
|
@@ -53295,7 +53551,7 @@ function readRemoteBridgeBundle(sshAddress) {
|
|
|
53295
53551
|
}
|
|
53296
53552
|
}
|
|
53297
53553
|
function writeLocalBridgeBundle() {
|
|
53298
|
-
const localPath = join21(
|
|
53554
|
+
const localPath = join21(tmpdir4(), `todos-bridge-push-${uuid()}.json`);
|
|
53299
53555
|
writeFileSync10(localPath, JSON.stringify(createLocalBridgeBundle(), null, 2));
|
|
53300
53556
|
return localPath;
|
|
53301
53557
|
}
|
|
@@ -54359,7 +54615,7 @@ __export(exports_onboarding_commands, {
|
|
|
54359
54615
|
registerOnboardingCommands: () => registerOnboardingCommands
|
|
54360
54616
|
});
|
|
54361
54617
|
import chalk17 from "chalk";
|
|
54362
|
-
import { resolve as
|
|
54618
|
+
import { resolve as resolve20 } from "path";
|
|
54363
54619
|
function registerOnboardingCommands(program2) {
|
|
54364
54620
|
program2.command("onboarding").alias("demo-fixtures").description("List, show, write, or import bundled local onboarding fixtures").option("--show <name>", "Show one fixture bridge bundle as JSON").option("--write <dir>", "Write all bundled fixture bridge bundles to a directory").option("--import <name>", "Dry-run or apply an onboarding fixture import").option("--apply", "Apply an onboarding fixture import. Defaults to dry-run.").option("--resolve-conflicts", "Safely merge existing local tasks while preserving divergent fields").action(async (opts) => {
|
|
54365
54621
|
const globalOpts = program2.opts();
|
|
@@ -54375,7 +54631,7 @@ function registerOnboardingCommands(program2) {
|
|
|
54375
54631
|
return;
|
|
54376
54632
|
}
|
|
54377
54633
|
if (opts.write) {
|
|
54378
|
-
const result = writeOnboardingFixtureFiles2(
|
|
54634
|
+
const result = writeOnboardingFixtureFiles2(resolve20(opts.write));
|
|
54379
54635
|
if (globalOpts.json) {
|
|
54380
54636
|
output(result, true);
|
|
54381
54637
|
return;
|
|
@@ -57955,7 +58211,7 @@ __export(exports_sdk_fixture_commands, {
|
|
|
57955
58211
|
registerSdkFixtureCommands: () => registerSdkFixtureCommands
|
|
57956
58212
|
});
|
|
57957
58213
|
import chalk19 from "chalk";
|
|
57958
|
-
import { resolve as
|
|
58214
|
+
import { resolve as resolve21 } from "path";
|
|
57959
58215
|
function registerSdkFixtureCommands(program2) {
|
|
57960
58216
|
program2.command("sdk-fixtures").description("List, show, or write local SDK integration fixtures").option("--show", "Print the full fixture pack JSON").option("--write <dir>", "Write fixture pack, bridge fixture, contract snapshots, and example index to a directory").action(async (opts) => {
|
|
57961
58217
|
const globalOpts = program2.opts();
|
|
@@ -57966,7 +58222,7 @@ function registerSdkFixtureCommands(program2) {
|
|
|
57966
58222
|
writeSdkIntegrationFixtures: writeSdkIntegrationFixtures2
|
|
57967
58223
|
} = await Promise.resolve().then(() => (init_sdk_integration_fixtures(), exports_sdk_integration_fixtures));
|
|
57968
58224
|
if (opts.write) {
|
|
57969
|
-
const result = writeSdkIntegrationFixtures2(
|
|
58225
|
+
const result = writeSdkIntegrationFixtures2(resolve21(opts.write));
|
|
57970
58226
|
if (globalOpts.json) {
|
|
57971
58227
|
console.log(JSON.stringify(result));
|
|
57972
58228
|
return;
|
|
@@ -58788,7 +59044,7 @@ __export(exports_local_backup_commands, {
|
|
|
58788
59044
|
registerLocalBackupCommands: () => registerLocalBackupCommands
|
|
58789
59045
|
});
|
|
58790
59046
|
import chalk26 from "chalk";
|
|
58791
|
-
import { resolve as
|
|
59047
|
+
import { resolve as resolve22 } from "path";
|
|
58792
59048
|
function globalOptions6(program2) {
|
|
58793
59049
|
const command = program2;
|
|
58794
59050
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
@@ -58810,10 +59066,10 @@ function registerLocalBackupCommands(program2) {
|
|
|
58810
59066
|
const projectId = opts.projectId ?? autoProject(globalOpts);
|
|
58811
59067
|
const backupBundle = createLocalBackup2({
|
|
58812
59068
|
project_id: projectId,
|
|
58813
|
-
output_path: opts.output ?
|
|
59069
|
+
output_path: opts.output ? resolve22(opts.output) : undefined
|
|
58814
59070
|
});
|
|
58815
59071
|
const result = {
|
|
58816
|
-
output_path: opts.output ?
|
|
59072
|
+
output_path: opts.output ? resolve22(opts.output) : null,
|
|
58817
59073
|
backup: backupBundle
|
|
58818
59074
|
};
|
|
58819
59075
|
if (opts.json || globalOpts.json) {
|
|
@@ -59072,11 +59328,13 @@ function exportSqliteTodosStorageSnapshot(db) {
|
|
|
59072
59328
|
source: "sqlite",
|
|
59073
59329
|
tasks: listTasks({ include_archived: true }, d),
|
|
59074
59330
|
projects: listProjects(d),
|
|
59331
|
+
projectMachinePaths: listProjectMachinePaths(d),
|
|
59075
59332
|
plans: listPlans(undefined, d),
|
|
59076
59333
|
agents: listAgents({ include_archived: true }, d),
|
|
59077
59334
|
taskLists: listTaskLists(undefined, d),
|
|
59078
59335
|
templates: listTemplates(d),
|
|
59079
|
-
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d)
|
|
59336
|
+
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d),
|
|
59337
|
+
tombstones: listStorageTombstones(d)
|
|
59080
59338
|
};
|
|
59081
59339
|
}
|
|
59082
59340
|
function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
@@ -59084,13 +59342,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
59084
59342
|
const result = {
|
|
59085
59343
|
inserted: 0,
|
|
59086
59344
|
updated: 0,
|
|
59345
|
+
deleted: 0,
|
|
59087
59346
|
skipped: 0,
|
|
59088
59347
|
errors: []
|
|
59089
59348
|
};
|
|
59090
|
-
const applyRows = (table, columns, rows, updateClockColumn, afterUpsert) => {
|
|
59349
|
+
const applyRows = (objectType2, table, columns, rows, updateClockColumn, afterUpsert) => {
|
|
59091
59350
|
for (const row of rows) {
|
|
59092
59351
|
try {
|
|
59093
59352
|
const record = asRecord2(row);
|
|
59353
|
+
const tombstone = typeof record["id"] === "string" ? getStorageTombstone(objectType2, record["id"], d) : null;
|
|
59354
|
+
if (tombstone && shouldApplyStorageTombstone(tombstone, rowClock(record, updateClockColumn))) {
|
|
59355
|
+
result.skipped += 1;
|
|
59356
|
+
continue;
|
|
59357
|
+
}
|
|
59094
59358
|
const state = upsertById(d, table, columns, record, updateClockColumn);
|
|
59095
59359
|
if (state === "inserted")
|
|
59096
59360
|
result.inserted += 1;
|
|
@@ -59104,17 +59368,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
59104
59368
|
}
|
|
59105
59369
|
}
|
|
59106
59370
|
};
|
|
59107
|
-
applyRows("projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
|
|
59108
|
-
applyRows("
|
|
59109
|
-
applyRows("
|
|
59110
|
-
applyRows("
|
|
59111
|
-
applyRows("
|
|
59112
|
-
applyRows("
|
|
59371
|
+
applyRows("projects", "projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
|
|
59372
|
+
applyRows("project_machine_paths", "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS, snapshot.projectMachinePaths ?? [], "updated_at");
|
|
59373
|
+
applyRows("agents", "agents", AGENT_COLUMNS, snapshot.agents, "last_seen_at");
|
|
59374
|
+
applyRows("task_lists", "task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
|
|
59375
|
+
applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
|
|
59376
|
+
applyRows("templates", "task_templates", TEMPLATE_COLUMNS, snapshot.templates);
|
|
59377
|
+
applyRows("tasks", "tasks", TASK_COLUMNS, sortedTasks2(snapshot.tasks), "updated_at", (row, changed) => {
|
|
59113
59378
|
if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
|
|
59114
59379
|
replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
|
|
59115
59380
|
}
|
|
59116
59381
|
});
|
|
59117
|
-
applyRows("task_history", AUDIT_COLUMNS, snapshot.auditHistory);
|
|
59382
|
+
applyRows("audit_history", "task_history", AUDIT_COLUMNS, snapshot.auditHistory);
|
|
59383
|
+
applyTombstones(d, snapshot.tombstones ?? [], result);
|
|
59118
59384
|
return result;
|
|
59119
59385
|
}
|
|
59120
59386
|
function upsertById(db, table, columns, row, updateClockColumn) {
|
|
@@ -59169,7 +59435,86 @@ function sortedTasks2(tasks) {
|
|
|
59169
59435
|
visit(task2);
|
|
59170
59436
|
return result;
|
|
59171
59437
|
}
|
|
59172
|
-
|
|
59438
|
+
function applyTombstones(db, tombstones, result) {
|
|
59439
|
+
for (const tombstone of tombstones) {
|
|
59440
|
+
try {
|
|
59441
|
+
recordStorageTombstone({
|
|
59442
|
+
object_type: tombstone.object_type,
|
|
59443
|
+
object_id: tombstone.object_id,
|
|
59444
|
+
deleted_at: tombstone.deleted_at,
|
|
59445
|
+
source_machine_id: tombstone.source_machine_id ?? null,
|
|
59446
|
+
payload: tombstone.payload ?? null,
|
|
59447
|
+
version: tombstone.version ?? null
|
|
59448
|
+
}, db);
|
|
59449
|
+
const table = tableForTombstone(tombstone.object_type);
|
|
59450
|
+
const existing = existingClock(db, table, tombstone.object_id);
|
|
59451
|
+
if (!shouldApplyStorageTombstone(tombstone, existing)) {
|
|
59452
|
+
result.skipped += 1;
|
|
59453
|
+
continue;
|
|
59454
|
+
}
|
|
59455
|
+
const deletedTags = table === "tasks" ? db.run("DELETE FROM task_tags WHERE task_id = ?", [tombstone.object_id]).changes : 0;
|
|
59456
|
+
const deleted = db.run(`DELETE FROM ${table} WHERE id = ?`, [tombstone.object_id]).changes;
|
|
59457
|
+
if (deleted > 0 || deletedTags > 0)
|
|
59458
|
+
result.deleted = (result.deleted ?? 0) + 1;
|
|
59459
|
+
else
|
|
59460
|
+
result.skipped += 1;
|
|
59461
|
+
} catch (error) {
|
|
59462
|
+
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
59463
|
+
}
|
|
59464
|
+
}
|
|
59465
|
+
}
|
|
59466
|
+
function tableForTombstone(objectType2) {
|
|
59467
|
+
if (objectType2 === "tasks")
|
|
59468
|
+
return "tasks";
|
|
59469
|
+
if (objectType2 === "projects")
|
|
59470
|
+
return "projects";
|
|
59471
|
+
if (objectType2 === "project_machine_paths")
|
|
59472
|
+
return "project_machine_paths";
|
|
59473
|
+
if (objectType2 === "plans")
|
|
59474
|
+
return "plans";
|
|
59475
|
+
if (objectType2 === "agents")
|
|
59476
|
+
return "agents";
|
|
59477
|
+
if (objectType2 === "task_lists")
|
|
59478
|
+
return "task_lists";
|
|
59479
|
+
if (objectType2 === "templates")
|
|
59480
|
+
return "task_templates";
|
|
59481
|
+
return "task_history";
|
|
59482
|
+
}
|
|
59483
|
+
function listRows(db, table, columns) {
|
|
59484
|
+
return db.query(`SELECT ${columns.join(", ")} FROM ${table} ORDER BY id`).all();
|
|
59485
|
+
}
|
|
59486
|
+
function listProjectMachinePaths(db) {
|
|
59487
|
+
return listRows(db, "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS).map((row) => ({
|
|
59488
|
+
id: String(row.id),
|
|
59489
|
+
project_id: String(row.project_id),
|
|
59490
|
+
machine_id: String(row.machine_id),
|
|
59491
|
+
path: String(row.path),
|
|
59492
|
+
created_at: String(row.created_at),
|
|
59493
|
+
updated_at: String(row.updated_at)
|
|
59494
|
+
}));
|
|
59495
|
+
}
|
|
59496
|
+
function existingClock(db, table, id) {
|
|
59497
|
+
const clockColumns = clockColumnsForTable(table);
|
|
59498
|
+
const row = db.query(`SELECT ${clockColumns.join(", ")} FROM ${table} WHERE id = ?`).get(id);
|
|
59499
|
+
return row?.updated_at ?? row?.last_seen_at ?? row?.created_at ?? null;
|
|
59500
|
+
}
|
|
59501
|
+
function rowClock(row, updateClockColumn) {
|
|
59502
|
+
const value = updateClockColumn ? row[updateClockColumn] : null;
|
|
59503
|
+
return stringClock(value) ?? stringClock(row["updated_at"]) ?? stringClock(row["last_seen_at"]) ?? stringClock(row["created_at"]);
|
|
59504
|
+
}
|
|
59505
|
+
function stringClock(value) {
|
|
59506
|
+
return typeof value === "string" && value ? value : null;
|
|
59507
|
+
}
|
|
59508
|
+
function clockColumnsForTable(table) {
|
|
59509
|
+
if (table === "agents")
|
|
59510
|
+
return ["last_seen_at", "created_at"];
|
|
59511
|
+
if (table === "task_templates")
|
|
59512
|
+
return ["created_at"];
|
|
59513
|
+
if (table === "task_history")
|
|
59514
|
+
return ["created_at"];
|
|
59515
|
+
return ["updated_at", "created_at"];
|
|
59516
|
+
}
|
|
59517
|
+
var PROJECT_COLUMNS, PROJECT_MACHINE_PATH_COLUMNS, TASK_LIST_COLUMNS, PLAN_COLUMNS, AGENT_COLUMNS, TEMPLATE_COLUMNS, TASK_COLUMNS, AUDIT_COLUMNS, JSON_COLUMNS, BOOLEAN_COLUMNS;
|
|
59173
59518
|
var init_sqlite_snapshot = __esm(() => {
|
|
59174
59519
|
init_database();
|
|
59175
59520
|
init_agents();
|
|
@@ -59179,6 +59524,7 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
59179
59524
|
init_task_lists();
|
|
59180
59525
|
init_tasks();
|
|
59181
59526
|
init_templates();
|
|
59527
|
+
init_storage_tombstones();
|
|
59182
59528
|
PROJECT_COLUMNS = [
|
|
59183
59529
|
"id",
|
|
59184
59530
|
"name",
|
|
@@ -59192,6 +59538,14 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
59192
59538
|
"machine_id",
|
|
59193
59539
|
"synced_at"
|
|
59194
59540
|
];
|
|
59541
|
+
PROJECT_MACHINE_PATH_COLUMNS = [
|
|
59542
|
+
"id",
|
|
59543
|
+
"project_id",
|
|
59544
|
+
"machine_id",
|
|
59545
|
+
"path",
|
|
59546
|
+
"created_at",
|
|
59547
|
+
"updated_at"
|
|
59548
|
+
];
|
|
59195
59549
|
TASK_LIST_COLUMNS = [
|
|
59196
59550
|
"id",
|
|
59197
59551
|
"project_id",
|
|
@@ -59501,7 +59855,7 @@ class PostgresTodosSyncStore {
|
|
|
59501
59855
|
}
|
|
59502
59856
|
async pullSnapshot(options = {}) {
|
|
59503
59857
|
const params = [this.service];
|
|
59504
|
-
const filters = ["service = $1"
|
|
59858
|
+
const filters = ["service = $1"];
|
|
59505
59859
|
if (options.since) {
|
|
59506
59860
|
params.push(options.since);
|
|
59507
59861
|
filters.push(`updated_at > $${params.length}::timestamptz`);
|
|
@@ -59510,7 +59864,7 @@ class PostgresTodosSyncStore {
|
|
|
59510
59864
|
params.push(options.objectTypes);
|
|
59511
59865
|
filters.push(`object_type = ANY($${params.length}::text[])`);
|
|
59512
59866
|
}
|
|
59513
|
-
const response = await this.client.query(`SELECT object_type, payload FROM ${this.tableName}
|
|
59867
|
+
const response = await this.client.query(`SELECT object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version FROM ${this.tableName}
|
|
59514
59868
|
WHERE ${filters.join(" AND ")}
|
|
59515
59869
|
ORDER BY updated_at ASC, object_type ASC, object_id ASC`, params);
|
|
59516
59870
|
return rowsToSnapshot(response.rows);
|
|
@@ -59534,11 +59888,20 @@ function snapshotEntries(snapshot) {
|
|
|
59534
59888
|
return [
|
|
59535
59889
|
...snapshot.tasks.map((payload) => entry("tasks", payload, snapshot.exportedAt)),
|
|
59536
59890
|
...snapshot.projects.map((payload) => entry("projects", payload, snapshot.exportedAt)),
|
|
59891
|
+
...(snapshot.projectMachinePaths ?? []).map((payload) => entry("project_machine_paths", payload, snapshot.exportedAt)),
|
|
59537
59892
|
...snapshot.plans.map((payload) => entry("plans", payload, snapshot.exportedAt)),
|
|
59538
59893
|
...snapshot.agents.map((payload) => entry("agents", payload, snapshot.exportedAt)),
|
|
59539
59894
|
...snapshot.taskLists.map((payload) => entry("task_lists", payload, snapshot.exportedAt)),
|
|
59540
59895
|
...snapshot.templates.map((payload) => entry("templates", payload, snapshot.exportedAt)),
|
|
59541
|
-
...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt))
|
|
59896
|
+
...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt)),
|
|
59897
|
+
...(snapshot.tombstones ?? []).map((tombstone) => ({
|
|
59898
|
+
type: tombstone.object_type,
|
|
59899
|
+
id: tombstone.object_id,
|
|
59900
|
+
payload: tombstone.payload ?? { id: tombstone.object_id, deleted_at: tombstone.deleted_at },
|
|
59901
|
+
updatedAt: tombstone.updated_at || tombstone.deleted_at,
|
|
59902
|
+
deletedAt: tombstone.deleted_at,
|
|
59903
|
+
version: tombstone.version ?? null
|
|
59904
|
+
}))
|
|
59542
59905
|
];
|
|
59543
59906
|
}
|
|
59544
59907
|
function entry(type, payload, fallbackUpdatedAt) {
|
|
@@ -59560,19 +59923,38 @@ function rowsToSnapshot(rows) {
|
|
|
59560
59923
|
source: "postgres",
|
|
59561
59924
|
tasks: [],
|
|
59562
59925
|
projects: [],
|
|
59926
|
+
projectMachinePaths: [],
|
|
59563
59927
|
plans: [],
|
|
59564
59928
|
agents: [],
|
|
59565
59929
|
taskLists: [],
|
|
59566
59930
|
templates: [],
|
|
59567
|
-
auditHistory: []
|
|
59931
|
+
auditHistory: [],
|
|
59932
|
+
tombstones: []
|
|
59568
59933
|
};
|
|
59569
59934
|
for (const row of rows) {
|
|
59570
59935
|
const payload = payloadRecord(row.payload);
|
|
59936
|
+
const deletedAt = stringValue(row.deleted_at);
|
|
59937
|
+
if (deletedAt) {
|
|
59938
|
+
snapshot.tombstones ??= [];
|
|
59939
|
+
snapshot.tombstones.push({
|
|
59940
|
+
object_type: row.object_type,
|
|
59941
|
+
object_id: stringValue(row.object_id) ?? stringValue(payload["id"]) ?? "",
|
|
59942
|
+
deleted_at: deletedAt,
|
|
59943
|
+
updated_at: stringValue(row.updated_at) ?? deletedAt,
|
|
59944
|
+
source_machine_id: stringValue(row.source_machine_id),
|
|
59945
|
+
payload,
|
|
59946
|
+
version: numberValue2(row.version)
|
|
59947
|
+
});
|
|
59948
|
+
continue;
|
|
59949
|
+
}
|
|
59571
59950
|
if (row.object_type === "tasks")
|
|
59572
59951
|
snapshot.tasks.push(payload);
|
|
59573
59952
|
else if (row.object_type === "projects")
|
|
59574
59953
|
snapshot.projects.push(payload);
|
|
59575
|
-
else if (row.object_type === "
|
|
59954
|
+
else if (row.object_type === "project_machine_paths") {
|
|
59955
|
+
snapshot.projectMachinePaths ??= [];
|
|
59956
|
+
snapshot.projectMachinePaths.push(payload);
|
|
59957
|
+
} else if (row.object_type === "plans")
|
|
59576
59958
|
snapshot.plans.push(payload);
|
|
59577
59959
|
else if (row.object_type === "agents")
|
|
59578
59960
|
snapshot.agents.push(payload);
|
|
@@ -59593,6 +59975,8 @@ function payloadRecord(value) {
|
|
|
59593
59975
|
throw new Error("Postgres sync payload must be a JSON object");
|
|
59594
59976
|
}
|
|
59595
59977
|
function stringValue(value) {
|
|
59978
|
+
if (value instanceof Date)
|
|
59979
|
+
return value.toISOString();
|
|
59596
59980
|
return typeof value === "string" && value ? value : null;
|
|
59597
59981
|
}
|
|
59598
59982
|
function numberValue2(value) {
|
|
@@ -59751,6 +60135,9 @@ class PostgresJsonRecordStore {
|
|
|
59751
60135
|
this.tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
|
|
59752
60136
|
this.cursorTableName = options.cursorTableName ?? DEFAULT_TODOS_POSTGRES_CURSOR_TABLE;
|
|
59753
60137
|
}
|
|
60138
|
+
machineId(context) {
|
|
60139
|
+
return context?.requestId ?? this.sourceMachineId ?? null;
|
|
60140
|
+
}
|
|
59754
60141
|
async ensureSchema() {
|
|
59755
60142
|
this.schemaReady ??= (async () => {
|
|
59756
60143
|
for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
|
|
@@ -59782,6 +60169,25 @@ class PostgresJsonRecordStore {
|
|
|
59782
60169
|
updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString()
|
|
59783
60170
|
}));
|
|
59784
60171
|
}
|
|
60172
|
+
async listTombstones() {
|
|
60173
|
+
await this.ensureSchema();
|
|
60174
|
+
const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version
|
|
60175
|
+
FROM ${this.tableName}
|
|
60176
|
+
WHERE service = $1 AND deleted_at IS NOT NULL
|
|
60177
|
+
ORDER BY updated_at ASC, object_type ASC, object_id ASC`, [this.service]);
|
|
60178
|
+
return result.rows.map((row) => {
|
|
60179
|
+
const deletedAt = stringValue2(row.deleted_at) ?? stringValue2(row.updated_at) ?? new Date().toISOString();
|
|
60180
|
+
return {
|
|
60181
|
+
object_type: row.object_type,
|
|
60182
|
+
object_id: row.object_id,
|
|
60183
|
+
deleted_at: deletedAt,
|
|
60184
|
+
updated_at: stringValue2(row.updated_at) ?? deletedAt,
|
|
60185
|
+
source_machine_id: stringValue2(row.source_machine_id),
|
|
60186
|
+
payload: payloadRecord2(row.payload),
|
|
60187
|
+
version: numberValue3(row.version)
|
|
60188
|
+
};
|
|
60189
|
+
});
|
|
60190
|
+
}
|
|
59785
60191
|
async upsert(type, value, context = {}) {
|
|
59786
60192
|
await this.ensureSchema();
|
|
59787
60193
|
const updatedAt = stringValue2(value.updated_at) ?? stringValue2(value.created_at) ?? new Date().toISOString();
|
|
@@ -59794,7 +60200,8 @@ class PostgresJsonRecordStore {
|
|
|
59794
60200
|
updated_at = EXCLUDED.updated_at,
|
|
59795
60201
|
deleted_at = NULL,
|
|
59796
60202
|
source_machine_id = EXCLUDED.source_machine_id,
|
|
59797
|
-
version = EXCLUDED.version
|
|
60203
|
+
version = EXCLUDED.version
|
|
60204
|
+
WHERE ${this.tableName}.updated_at IS NULL OR ${this.tableName}.updated_at <= EXCLUDED.updated_at`, [
|
|
59798
60205
|
this.service,
|
|
59799
60206
|
type,
|
|
59800
60207
|
value.id,
|
|
@@ -59811,13 +60218,58 @@ class PostgresJsonRecordStore {
|
|
|
59811
60218
|
if (!existing)
|
|
59812
60219
|
return false;
|
|
59813
60220
|
const timestamp3 = new Date().toISOString();
|
|
59814
|
-
|
|
59815
|
-
|
|
59816
|
-
|
|
59817
|
-
|
|
59818
|
-
|
|
60221
|
+
return this.tombstone({
|
|
60222
|
+
object_type: type,
|
|
60223
|
+
object_id: id,
|
|
60224
|
+
deleted_at: timestamp3,
|
|
60225
|
+
updated_at: timestamp3,
|
|
60226
|
+
payload: existing,
|
|
60227
|
+
version: numberValue3(existing["version"])
|
|
60228
|
+
}, context);
|
|
60229
|
+
}
|
|
60230
|
+
async tombstone(tombstone, context = {}) {
|
|
60231
|
+
await this.ensureSchema();
|
|
60232
|
+
const deletedAt = stringValue2(tombstone.deleted_at) ?? new Date().toISOString();
|
|
60233
|
+
const updatedAt = stringValue2(tombstone.updated_at) ?? deletedAt;
|
|
60234
|
+
const existing = await this.clock(tombstone.object_type, tombstone.object_id);
|
|
60235
|
+
if (existing && compareClock(existing.updatedAt, updatedAt) > 0)
|
|
60236
|
+
return false;
|
|
60237
|
+
await this.options.client.query(`INSERT INTO ${this.tableName} (
|
|
60238
|
+
service, object_type, object_id, payload, updated_at,
|
|
60239
|
+
deleted_at, source_machine_id, version
|
|
60240
|
+
) VALUES ($1, $2, $3, $4::jsonb, $5::timestamptz, $6::timestamptz, $7, $8)
|
|
60241
|
+
ON CONFLICT (service, object_type, object_id) DO UPDATE SET
|
|
60242
|
+
payload = EXCLUDED.payload,
|
|
60243
|
+
updated_at = EXCLUDED.updated_at,
|
|
60244
|
+
deleted_at = EXCLUDED.deleted_at,
|
|
60245
|
+
source_machine_id = EXCLUDED.source_machine_id,
|
|
60246
|
+
version = EXCLUDED.version
|
|
60247
|
+
WHERE ${this.tableName}.updated_at IS NULL OR ${this.tableName}.updated_at <= EXCLUDED.updated_at`, [
|
|
60248
|
+
this.service,
|
|
60249
|
+
tombstone.object_type,
|
|
60250
|
+
tombstone.object_id,
|
|
60251
|
+
JSON.stringify(tombstone.payload ?? { id: tombstone.object_id, deleted_at: deletedAt }),
|
|
60252
|
+
updatedAt,
|
|
60253
|
+
deletedAt,
|
|
60254
|
+
tombstone.source_machine_id ?? context.requestId ?? this.sourceMachineId ?? null,
|
|
60255
|
+
tombstone.version ?? null
|
|
60256
|
+
]);
|
|
59819
60257
|
return true;
|
|
59820
60258
|
}
|
|
60259
|
+
async clock(type, id) {
|
|
60260
|
+
await this.ensureSchema();
|
|
60261
|
+
const result = await this.options.client.query(`SELECT object_type, object_id, updated_at, deleted_at
|
|
60262
|
+
FROM ${this.tableName}
|
|
60263
|
+
WHERE service = $1 AND object_type = $2 AND object_id = $3
|
|
60264
|
+
LIMIT 1`, [this.service, type, id]);
|
|
60265
|
+
const row = result.rows[0];
|
|
60266
|
+
if (!row)
|
|
60267
|
+
return null;
|
|
60268
|
+
return {
|
|
60269
|
+
updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString(),
|
|
60270
|
+
deletedAt: stringValue2(row.deleted_at)
|
|
60271
|
+
};
|
|
60272
|
+
}
|
|
59821
60273
|
async getCursor(name) {
|
|
59822
60274
|
await this.ensureSchema();
|
|
59823
60275
|
const result = await this.options.client.query(`SELECT value FROM ${this.cursorTableName} WHERE service = $1 AND cursor_name = $2`, [this.service, name]);
|
|
@@ -59886,7 +60338,10 @@ async function createTask3(input, store, context) {
|
|
|
59886
60338
|
runner_started_at: null,
|
|
59887
60339
|
runner_completed_at: null,
|
|
59888
60340
|
current_step: null,
|
|
59889
|
-
total_steps: null
|
|
60341
|
+
total_steps: null,
|
|
60342
|
+
machine_id: store.machineId(context),
|
|
60343
|
+
synced_at: null,
|
|
60344
|
+
archived_at: null
|
|
59890
60345
|
};
|
|
59891
60346
|
await store.upsert("tasks", task2, context);
|
|
59892
60347
|
await logTaskChange2(task2.id, "created", "status", null, task2.status, task2.assigned_by ?? task2.agent_id, store, context);
|
|
@@ -60011,7 +60466,9 @@ async function createProject2(input, store, context) {
|
|
|
60011
60466
|
task_prefix: input.task_prefix ?? await generateProjectPrefix(input.name, store),
|
|
60012
60467
|
task_counter: 0,
|
|
60013
60468
|
created_at: timestamp3,
|
|
60014
|
-
updated_at: timestamp3
|
|
60469
|
+
updated_at: timestamp3,
|
|
60470
|
+
machine_id: store.machineId(context),
|
|
60471
|
+
synced_at: null
|
|
60015
60472
|
};
|
|
60016
60473
|
return store.upsert("projects", project, context);
|
|
60017
60474
|
}
|
|
@@ -60031,7 +60488,9 @@ async function createPlan2(input, store, context) {
|
|
|
60031
60488
|
description: input.description ?? null,
|
|
60032
60489
|
status: input.status ?? "active",
|
|
60033
60490
|
created_at: timestamp3,
|
|
60034
|
-
updated_at: timestamp3
|
|
60491
|
+
updated_at: timestamp3,
|
|
60492
|
+
machine_id: store.machineId(context),
|
|
60493
|
+
synced_at: null
|
|
60035
60494
|
}, context);
|
|
60036
60495
|
}
|
|
60037
60496
|
async function updatePlan2(id, input, store) {
|
|
@@ -60061,7 +60520,9 @@ async function registerAgent2(input, store, context) {
|
|
|
60061
60520
|
last_seen_at: timestamp3,
|
|
60062
60521
|
session_id: input.session_id ?? context?.sessionId ?? existing?.session_id ?? null,
|
|
60063
60522
|
working_dir: input.working_dir ?? existing?.working_dir ?? null,
|
|
60064
|
-
active_project_id: input.project_id ?? context?.projectId ?? existing?.active_project_id ?? null
|
|
60523
|
+
active_project_id: input.project_id ?? context?.projectId ?? existing?.active_project_id ?? null,
|
|
60524
|
+
machine_id: existing?.machine_id ?? store.machineId(context),
|
|
60525
|
+
synced_at: existing?.synced_at ?? null
|
|
60065
60526
|
};
|
|
60066
60527
|
return store.upsert("agents", agent, context);
|
|
60067
60528
|
}
|
|
@@ -60088,7 +60549,9 @@ async function createTaskList2(input, store, context) {
|
|
|
60088
60549
|
description: input.description ?? null,
|
|
60089
60550
|
metadata: input.metadata ?? {},
|
|
60090
60551
|
created_at: timestamp3,
|
|
60091
|
-
updated_at: timestamp3
|
|
60552
|
+
updated_at: timestamp3,
|
|
60553
|
+
machine_id: store.machineId(context),
|
|
60554
|
+
synced_at: null
|
|
60092
60555
|
}, context);
|
|
60093
60556
|
}
|
|
60094
60557
|
async function updateTaskList2(id, input, store) {
|
|
@@ -60114,7 +60577,9 @@ async function createTemplate2(input, store, context) {
|
|
|
60114
60577
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
60115
60578
|
plan_id: input.plan_id ?? null,
|
|
60116
60579
|
metadata: input.metadata ?? {},
|
|
60117
|
-
created_at: timestamp3
|
|
60580
|
+
created_at: timestamp3,
|
|
60581
|
+
machine_id: store.machineId(context),
|
|
60582
|
+
synced_at: null
|
|
60118
60583
|
}, context);
|
|
60119
60584
|
}
|
|
60120
60585
|
async function updateTemplate2(id, input, store) {
|
|
@@ -60139,7 +60604,8 @@ async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentI
|
|
|
60139
60604
|
old_value: oldValue ?? null,
|
|
60140
60605
|
new_value: newValue ?? null,
|
|
60141
60606
|
agent_id: agentId ?? context?.agentId ?? null,
|
|
60142
|
-
created_at: new Date().toISOString()
|
|
60607
|
+
created_at: new Date().toISOString(),
|
|
60608
|
+
machine_id: store.machineId(context)
|
|
60143
60609
|
};
|
|
60144
60610
|
return store.upsert("audit_history", entry2, context);
|
|
60145
60611
|
}
|
|
@@ -60162,18 +60628,21 @@ async function exportSnapshot(store) {
|
|
|
60162
60628
|
source: "postgres",
|
|
60163
60629
|
tasks: await store.list("tasks"),
|
|
60164
60630
|
projects: await store.list("projects"),
|
|
60631
|
+
projectMachinePaths: await store.list("project_machine_paths"),
|
|
60165
60632
|
plans: await store.list("plans"),
|
|
60166
60633
|
agents: await store.list("agents"),
|
|
60167
60634
|
taskLists: await store.list("task_lists"),
|
|
60168
60635
|
templates: await store.list("templates"),
|
|
60169
|
-
auditHistory: await store.list("audit_history")
|
|
60636
|
+
auditHistory: await store.list("audit_history"),
|
|
60637
|
+
tombstones: await store.listTombstones()
|
|
60170
60638
|
};
|
|
60171
60639
|
}
|
|
60172
60640
|
async function importSnapshot(snapshot, store, context) {
|
|
60173
|
-
const result = { inserted: 0, updated: 0, skipped: 0, errors: [] };
|
|
60641
|
+
const result = { inserted: 0, updated: 0, deleted: 0, skipped: 0, errors: [] };
|
|
60174
60642
|
const entries = [
|
|
60175
60643
|
...snapshot.tasks.map((row) => ["tasks", row]),
|
|
60176
60644
|
...snapshot.projects.map((row) => ["projects", row]),
|
|
60645
|
+
...(snapshot.projectMachinePaths ?? []).map((row) => ["project_machine_paths", row]),
|
|
60177
60646
|
...snapshot.plans.map((row) => ["plans", row]),
|
|
60178
60647
|
...snapshot.agents.map((row) => ["agents", row]),
|
|
60179
60648
|
...snapshot.taskLists.map((row) => ["task_lists", row]),
|
|
@@ -60192,6 +60661,25 @@ async function importSnapshot(snapshot, store, context) {
|
|
|
60192
60661
|
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
60193
60662
|
}
|
|
60194
60663
|
}
|
|
60664
|
+
for (const tombstone of snapshot.tombstones ?? []) {
|
|
60665
|
+
try {
|
|
60666
|
+
const deleted = await store.tombstone({
|
|
60667
|
+
object_type: tombstone.object_type,
|
|
60668
|
+
object_id: tombstone.object_id,
|
|
60669
|
+
deleted_at: tombstone.deleted_at,
|
|
60670
|
+
updated_at: tombstone.updated_at,
|
|
60671
|
+
source_machine_id: tombstone.source_machine_id ?? null,
|
|
60672
|
+
payload: tombstone.payload ?? null,
|
|
60673
|
+
version: tombstone.version ?? null
|
|
60674
|
+
}, context);
|
|
60675
|
+
if (deleted)
|
|
60676
|
+
result.deleted = (result.deleted ?? 0) + 1;
|
|
60677
|
+
else
|
|
60678
|
+
result.skipped += 1;
|
|
60679
|
+
} catch (error) {
|
|
60680
|
+
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
60681
|
+
}
|
|
60682
|
+
}
|
|
60195
60683
|
return result;
|
|
60196
60684
|
}
|
|
60197
60685
|
async function requireRecord(type, id, store) {
|
|
@@ -60274,8 +60762,17 @@ function payloadRecord2(value) {
|
|
|
60274
60762
|
throw new Error("Postgres storage payload must be a JSON object");
|
|
60275
60763
|
}
|
|
60276
60764
|
function stringValue2(value) {
|
|
60765
|
+
if (value instanceof Date)
|
|
60766
|
+
return value.toISOString();
|
|
60277
60767
|
return typeof value === "string" && value ? value : null;
|
|
60278
60768
|
}
|
|
60769
|
+
function compareClock(left, right) {
|
|
60770
|
+
const leftClock = Date.parse(left);
|
|
60771
|
+
const rightClock = Date.parse(right);
|
|
60772
|
+
if (Number.isNaN(leftClock) || Number.isNaN(rightClock))
|
|
60773
|
+
return left.localeCompare(right);
|
|
60774
|
+
return leftClock - rightClock;
|
|
60775
|
+
}
|
|
60279
60776
|
function numberValue3(value) {
|
|
60280
60777
|
return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
|
|
60281
60778
|
}
|
|
@@ -60949,6 +61446,7 @@ var init_native_storage_status = __esm(() => {
|
|
|
60949
61446
|
// src/cli/commands/storage-commands.ts
|
|
60950
61447
|
var exports_storage_commands = {};
|
|
60951
61448
|
__export(exports_storage_commands, {
|
|
61449
|
+
s3CredentialsFromEnv: () => s3CredentialsFromEnv,
|
|
60952
61450
|
registerStorageCommands: () => registerStorageCommands
|
|
60953
61451
|
});
|
|
60954
61452
|
import chalk27 from "chalk";
|
|
@@ -61027,13 +61525,13 @@ function artifactFilter(opts) {
|
|
|
61027
61525
|
...opts.includeAlreadySynced ? { includeAlreadySynced: true } : {}
|
|
61028
61526
|
};
|
|
61029
61527
|
}
|
|
61030
|
-
function s3CredentialsFromEnv() {
|
|
61031
|
-
const { TODOS_STORAGE_ENV: TODOS_STORAGE_ENV2 } = (init_storage(), __toCommonJS(exports_storage));
|
|
61032
|
-
const accessKeyId =
|
|
61033
|
-
const secretAccessKey =
|
|
61034
|
-
const sessionToken =
|
|
61528
|
+
function s3CredentialsFromEnv(env = process.env) {
|
|
61529
|
+
const { TODOS_STORAGE_ENV: TODOS_STORAGE_ENV2, TODOS_STORAGE_FALLBACK_ENV: TODOS_STORAGE_FALLBACK_ENV2 } = (init_storage(), __toCommonJS(exports_storage));
|
|
61530
|
+
const accessKeyId = (env[TODOS_STORAGE_ENV2.s3AccessKeyId] ?? env[TODOS_STORAGE_FALLBACK_ENV2.s3AccessKeyId])?.trim();
|
|
61531
|
+
const secretAccessKey = (env[TODOS_STORAGE_ENV2.s3SecretAccessKey] ?? env[TODOS_STORAGE_FALLBACK_ENV2.s3SecretAccessKey])?.trim();
|
|
61532
|
+
const sessionToken = (env[TODOS_STORAGE_ENV2.s3SessionToken] ?? env[TODOS_STORAGE_FALLBACK_ENV2.s3SessionToken])?.trim();
|
|
61035
61533
|
if (!accessKeyId || !secretAccessKey) {
|
|
61036
|
-
throw new Error(`${TODOS_STORAGE_ENV2.s3AccessKeyId} and ${TODOS_STORAGE_ENV2.s3SecretAccessKey} are required for --apply`);
|
|
61534
|
+
throw new Error(`${TODOS_STORAGE_ENV2.s3AccessKeyId}/${TODOS_STORAGE_FALLBACK_ENV2.s3AccessKeyId} and ${TODOS_STORAGE_ENV2.s3SecretAccessKey}/${TODOS_STORAGE_FALLBACK_ENV2.s3SecretAccessKey} are required for --apply`);
|
|
61037
61535
|
}
|
|
61038
61536
|
return {
|
|
61039
61537
|
accessKeyId,
|