@hasna/todos 0.11.65 → 0.11.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -49
- package/dashboard/dist/assets/{index-aJefI7kh.js → index-DVotjwab.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/storage-commands.d.ts +5 -0
- package/dist/cli/commands/storage-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1034 -231
- package/dist/contracts.js +409 -129
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/plans.d.ts.map +1 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/storage-tombstones.d.ts +26 -0
- package/dist/db/storage-tombstones.d.ts.map +1 -0
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/task-lists.d.ts.map +1 -1
- package/dist/db/task-runs.d.ts.map +1 -1
- package/dist/db/templates.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +928 -219
- package/dist/lib/approval-gates.d.ts.map +1 -1
- package/dist/lib/event-emission-safety.d.ts +9 -0
- package/dist/lib/event-emission-safety.d.ts.map +1 -0
- package/dist/lib/event-hooks.d.ts +1 -0
- package/dist/lib/event-hooks.d.ts.map +1 -1
- package/dist/lib/feature-manifest.d.ts.map +1 -1
- package/dist/lib/project-bootstrap.d.ts +1 -0
- package/dist/lib/project-bootstrap.d.ts.map +1 -1
- package/dist/lib/review-queues.d.ts.map +1 -1
- package/dist/lib/shared-events.d.ts +1 -0
- package/dist/lib/shared-events.d.ts.map +1 -1
- package/dist/lib/task-route-contract.d.ts +33 -0
- package/dist/lib/task-route-contract.d.ts.map +1 -0
- package/dist/lib/task-routing.d.ts +55 -0
- package/dist/lib/task-routing.d.ts.map +1 -0
- package/dist/mcp/index.js +459 -162
- package/dist/registry.js +409 -129
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +482 -185
- package/dist/storage/interfaces.d.ts +20 -0
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/postgres-sync.d.ts +6 -1
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
- package/dist/storage.js +677 -152
- package/dist/types/index.d.ts +22 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/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,40 +7097,10 @@ 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
|
-
// src/lib/
|
|
6918
|
-
function taskEventData(task, extra = {}) {
|
|
6919
|
-
return {
|
|
6920
|
-
id: task.id,
|
|
6921
|
-
task_id: task.id,
|
|
6922
|
-
short_id: task.short_id,
|
|
6923
|
-
title: task.title,
|
|
6924
|
-
description: task.description,
|
|
6925
|
-
status: task.status,
|
|
6926
|
-
priority: task.priority,
|
|
6927
|
-
project_id: task.project_id,
|
|
6928
|
-
parent_id: task.parent_id,
|
|
6929
|
-
plan_id: task.plan_id,
|
|
6930
|
-
task_list_id: task.task_list_id,
|
|
6931
|
-
agent_id: task.agent_id,
|
|
6932
|
-
assigned_to: task.assigned_to,
|
|
6933
|
-
session_id: task.session_id,
|
|
6934
|
-
working_dir: task.working_dir,
|
|
6935
|
-
tags: task.tags,
|
|
6936
|
-
metadata: task.metadata,
|
|
6937
|
-
version: task.version,
|
|
6938
|
-
created_at: task.created_at,
|
|
6939
|
-
updated_at: task.updated_at,
|
|
6940
|
-
started_at: task.started_at,
|
|
6941
|
-
completed_at: task.completed_at,
|
|
6942
|
-
due_at: task.due_at,
|
|
6943
|
-
requires_approval: task.requires_approval,
|
|
6944
|
-
approved_by: task.approved_by,
|
|
6945
|
-
approved_at: task.approved_at,
|
|
6946
|
-
...extra
|
|
6947
|
-
};
|
|
6948
|
-
}
|
|
7103
|
+
// src/lib/task-route-contract.ts
|
|
6949
7104
|
function booleanField(value) {
|
|
6950
7105
|
if (typeof value === "boolean")
|
|
6951
7106
|
return value;
|
|
@@ -6967,21 +7122,31 @@ function booleanField(value) {
|
|
|
6967
7122
|
function objectField(value) {
|
|
6968
7123
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
6969
7124
|
}
|
|
6970
|
-
function
|
|
7125
|
+
function collectBooleans(records, keys) {
|
|
7126
|
+
const values = [];
|
|
6971
7127
|
for (const record of records) {
|
|
7128
|
+
if (!record)
|
|
7129
|
+
continue;
|
|
6972
7130
|
for (const key of keys) {
|
|
6973
7131
|
const value = booleanField(record[key]);
|
|
6974
7132
|
if (value !== undefined)
|
|
6975
|
-
|
|
7133
|
+
values.push(value);
|
|
6976
7134
|
}
|
|
6977
7135
|
}
|
|
6978
|
-
return;
|
|
7136
|
+
return values;
|
|
6979
7137
|
}
|
|
6980
|
-
function
|
|
6981
|
-
const
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
7138
|
+
function mergedBoolean(records, keys, trueWins) {
|
|
7139
|
+
const values = collectBooleans(records, keys);
|
|
7140
|
+
if (values.length === 0)
|
|
7141
|
+
return;
|
|
7142
|
+
if (trueWins)
|
|
7143
|
+
return values.some(Boolean);
|
|
7144
|
+
return values.includes(false) ? false : true;
|
|
7145
|
+
}
|
|
7146
|
+
function routingAutomationMetadata(task, taskList) {
|
|
7147
|
+
const taskAutomation = objectField(task.metadata.automation);
|
|
7148
|
+
const taskListAutomation = taskList ? objectField(taskList.metadata.automation) : undefined;
|
|
7149
|
+
const records = [task.metadata, taskAutomation, taskList?.metadata, taskListAutomation];
|
|
6985
7150
|
const result = {};
|
|
6986
7151
|
const aliases = [
|
|
6987
7152
|
["allowed", ["allowed", "automation_allowed", "automationAllowed"]],
|
|
@@ -6992,7 +7157,7 @@ function routingAutomationMetadata(task) {
|
|
|
6992
7157
|
["approval_required", ["approval_required", "approvalRequired"]]
|
|
6993
7158
|
];
|
|
6994
7159
|
for (const [canonical, keys] of aliases) {
|
|
6995
|
-
const value =
|
|
7160
|
+
const value = mergedBoolean(records, keys, canonical !== "allowed");
|
|
6996
7161
|
if (value !== undefined)
|
|
6997
7162
|
result[canonical] = value;
|
|
6998
7163
|
}
|
|
@@ -7000,23 +7165,91 @@ function routingAutomationMetadata(task) {
|
|
|
7000
7165
|
result.requires_approval = true;
|
|
7001
7166
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
7002
7167
|
}
|
|
7168
|
+
function routeEnabledForTask(task, taskList) {
|
|
7169
|
+
const explicit = booleanField(task.metadata.route_enabled);
|
|
7170
|
+
if (explicit !== undefined)
|
|
7171
|
+
return explicit;
|
|
7172
|
+
if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
|
|
7173
|
+
return true;
|
|
7174
|
+
const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
|
|
7175
|
+
if (taskListDefault !== undefined)
|
|
7176
|
+
return taskListDefault;
|
|
7177
|
+
return;
|
|
7178
|
+
}
|
|
7179
|
+
function stringField(value) {
|
|
7180
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
7181
|
+
}
|
|
7182
|
+
function workflowPointersFromMetadata(metadata) {
|
|
7183
|
+
const nested = objectField(metadata.workflow_invocation) ?? objectField(metadata.workflow) ?? {};
|
|
7184
|
+
return {
|
|
7185
|
+
current_workflow_invocation_id: stringField(metadata.current_workflow_invocation_id) ?? stringField(nested.current_workflow_invocation_id) ?? stringField(nested.invocation_id),
|
|
7186
|
+
current_run_id: stringField(metadata.current_run_id) ?? stringField(nested.current_run_id) ?? stringField(nested.run_id),
|
|
7187
|
+
latest_manifest_path: stringField(metadata.latest_manifest_path) ?? stringField(nested.latest_manifest_path) ?? stringField(nested.manifest_path),
|
|
7188
|
+
latest_evaluation_path: stringField(metadata.latest_evaluation_path) ?? stringField(nested.latest_evaluation_path) ?? stringField(nested.evaluation_path),
|
|
7189
|
+
workflow_state: stringField(metadata.workflow_state) ?? stringField(nested.workflow_state) ?? stringField(nested.state)
|
|
7190
|
+
};
|
|
7191
|
+
}
|
|
7192
|
+
function compactWorkflowPointers(pointers) {
|
|
7193
|
+
return Object.fromEntries(Object.entries(pointers).filter(([, value]) => typeof value === "string" && value.length > 0));
|
|
7194
|
+
}
|
|
7195
|
+
function classifyProjectKind(path) {
|
|
7196
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
7197
|
+
}
|
|
7198
|
+
function isWorktreePath(path) {
|
|
7199
|
+
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
7200
|
+
}
|
|
7201
|
+
function inferRootProjectId(project) {
|
|
7202
|
+
return isWorktreePath(project.path) ? null : project.id;
|
|
7203
|
+
}
|
|
7204
|
+
var TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION = "todos.task_route_state.v1", TASK_WORKFLOW_POINTER_SCHEMA_VERSION = "todos.task_workflow_pointer.v1";
|
|
7205
|
+
|
|
7206
|
+
// src/lib/shared-events.ts
|
|
7207
|
+
function taskEventData(task, extra = {}) {
|
|
7208
|
+
return {
|
|
7209
|
+
id: task.id,
|
|
7210
|
+
task_id: task.id,
|
|
7211
|
+
short_id: task.short_id,
|
|
7212
|
+
title: task.title,
|
|
7213
|
+
description: task.description,
|
|
7214
|
+
status: task.status,
|
|
7215
|
+
priority: task.priority,
|
|
7216
|
+
project_id: task.project_id,
|
|
7217
|
+
parent_id: task.parent_id,
|
|
7218
|
+
plan_id: task.plan_id,
|
|
7219
|
+
task_list_id: task.task_list_id,
|
|
7220
|
+
agent_id: task.agent_id,
|
|
7221
|
+
assigned_to: task.assigned_to,
|
|
7222
|
+
session_id: task.session_id,
|
|
7223
|
+
working_dir: task.working_dir,
|
|
7224
|
+
tags: task.tags,
|
|
7225
|
+
metadata: task.metadata,
|
|
7226
|
+
version: task.version,
|
|
7227
|
+
created_at: task.created_at,
|
|
7228
|
+
updated_at: task.updated_at,
|
|
7229
|
+
started_at: task.started_at,
|
|
7230
|
+
completed_at: task.completed_at,
|
|
7231
|
+
due_at: task.due_at,
|
|
7232
|
+
requires_approval: task.requires_approval,
|
|
7233
|
+
approved_by: task.approved_by,
|
|
7234
|
+
approved_at: task.approved_at,
|
|
7235
|
+
...extra
|
|
7236
|
+
};
|
|
7237
|
+
}
|
|
7003
7238
|
function taskEventMetadata(task) {
|
|
7004
7239
|
const metadata = {
|
|
7005
7240
|
package: "@hasna/todos",
|
|
7006
7241
|
todos_event_schema_version: 1,
|
|
7242
|
+
route_state_schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
|
|
7007
7243
|
task_id: task.id,
|
|
7008
7244
|
task_short_id: task.short_id,
|
|
7009
7245
|
project_id: task.project_id,
|
|
7010
7246
|
task_list_id: task.task_list_id,
|
|
7011
7247
|
working_dir: task.working_dir
|
|
7012
7248
|
};
|
|
7013
|
-
const
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
const automation = routingAutomationMetadata(task);
|
|
7018
|
-
if (automation) {
|
|
7019
|
-
metadata.automation = automation;
|
|
7249
|
+
const pointers = workflowPointersFromMetadata(task.metadata);
|
|
7250
|
+
for (const [key, value] of Object.entries(pointers)) {
|
|
7251
|
+
if (value)
|
|
7252
|
+
metadata[key] = value;
|
|
7020
7253
|
}
|
|
7021
7254
|
try {
|
|
7022
7255
|
const project = task.project_id ? getProject(task.project_id) : null;
|
|
@@ -7045,18 +7278,20 @@ function taskEventMetadata(task) {
|
|
|
7045
7278
|
metadata.task_list_project_id = taskList.project_id;
|
|
7046
7279
|
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
7047
7280
|
}
|
|
7281
|
+
const routeEnabled = routeEnabledForTask(task, taskList);
|
|
7282
|
+
if (routeEnabled !== undefined) {
|
|
7283
|
+
metadata.route_enabled = routeEnabled;
|
|
7284
|
+
}
|
|
7285
|
+
const automation = routingAutomationMetadata(task, taskList);
|
|
7286
|
+
if (automation) {
|
|
7287
|
+
metadata.automation = automation;
|
|
7288
|
+
metadata.route_blocked_by_no_auto = automation.no_auto === true;
|
|
7289
|
+
metadata.route_blocked_by_manual = automation.manual === true || automation.manual_required === true;
|
|
7290
|
+
metadata.route_blocked_by_approval = (automation.requires_approval === true || automation.approval_required === true) && !task.approved_by;
|
|
7291
|
+
}
|
|
7048
7292
|
} catch {}
|
|
7049
7293
|
return metadata;
|
|
7050
7294
|
}
|
|
7051
|
-
function classifyProjectKind(path) {
|
|
7052
|
-
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
7053
|
-
}
|
|
7054
|
-
function isWorktreePath(path) {
|
|
7055
|
-
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
7056
|
-
}
|
|
7057
|
-
function inferRootProjectId(project) {
|
|
7058
|
-
return isWorktreePath(project.path) ? null : project.id;
|
|
7059
|
-
}
|
|
7060
7295
|
function readMachineLocalPath(project) {
|
|
7061
7296
|
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
7062
7297
|
if (!machineId)
|
|
@@ -7069,6 +7304,8 @@ function readMachineLocalPath(project) {
|
|
|
7069
7304
|
}
|
|
7070
7305
|
}
|
|
7071
7306
|
async function emitSharedTaskEvent(input) {
|
|
7307
|
+
if (!shouldEmitSharedTaskEvents(input.databasePath))
|
|
7308
|
+
return;
|
|
7072
7309
|
const data = taskEventData(input.task, input.data);
|
|
7073
7310
|
await new EventsClient().emit({
|
|
7074
7311
|
source: SOURCE,
|
|
@@ -7092,6 +7329,7 @@ var init_shared_events = __esm(() => {
|
|
|
7092
7329
|
init_database();
|
|
7093
7330
|
init_projects();
|
|
7094
7331
|
init_task_lists();
|
|
7332
|
+
init_event_emission_safety();
|
|
7095
7333
|
});
|
|
7096
7334
|
|
|
7097
7335
|
// src/lib/secret-redaction.ts
|
|
@@ -7347,8 +7585,9 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
|
7347
7585
|
const d = db || getDatabase();
|
|
7348
7586
|
const id = uuid();
|
|
7349
7587
|
const timestamp = now();
|
|
7350
|
-
|
|
7351
|
-
|
|
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]);
|
|
7352
7591
|
try {
|
|
7353
7592
|
const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
|
|
7354
7593
|
logActivity2({
|
|
@@ -7361,7 +7600,7 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
|
7361
7600
|
actor_id: agentId ?? undefined
|
|
7362
7601
|
}, d);
|
|
7363
7602
|
} catch {}
|
|
7364
|
-
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 };
|
|
7365
7604
|
}
|
|
7366
7605
|
function getTaskHistory(taskId, db) {
|
|
7367
7606
|
const d = db || getDatabase();
|
|
@@ -7399,6 +7638,7 @@ function getRecap(hours = 8, projectId, db) {
|
|
|
7399
7638
|
}
|
|
7400
7639
|
var init_audit = __esm(() => {
|
|
7401
7640
|
init_database();
|
|
7641
|
+
init_storage_tombstones();
|
|
7402
7642
|
});
|
|
7403
7643
|
|
|
7404
7644
|
// src/db/webhooks.ts
|
|
@@ -7664,13 +7904,14 @@ function createTask(input, db) {
|
|
|
7664
7904
|
const d = db || getDatabase();
|
|
7665
7905
|
const timestamp = now();
|
|
7666
7906
|
const tags = input.tags || [];
|
|
7907
|
+
const machineId = currentStorageMachineId(d);
|
|
7667
7908
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
7668
7909
|
const assignedFromProject = input.assigned_from_project || null;
|
|
7669
7910
|
let id = uuid();
|
|
7670
7911
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
7671
7912
|
try {
|
|
7672
|
-
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)
|
|
7673
|
-
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, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
7674
7915
|
id,
|
|
7675
7916
|
null,
|
|
7676
7917
|
input.project_id || null,
|
|
@@ -7707,7 +7948,8 @@ function createTask(input, db) {
|
|
|
7707
7948
|
input.spawned_from_session || null,
|
|
7708
7949
|
assignedBy || null,
|
|
7709
7950
|
assignedFromProject || null,
|
|
7710
|
-
input.task_type || null
|
|
7951
|
+
input.task_type || null,
|
|
7952
|
+
machineId
|
|
7711
7953
|
]);
|
|
7712
7954
|
break;
|
|
7713
7955
|
} catch (e) {
|
|
@@ -7723,9 +7965,10 @@ function createTask(input, db) {
|
|
|
7723
7965
|
}
|
|
7724
7966
|
const task = getTask(id, d);
|
|
7725
7967
|
const payload = taskEventData(task);
|
|
7968
|
+
const databasePath = databasePathFromDatabase(d);
|
|
7726
7969
|
dispatchWebhook2("task.created", payload, d).catch(() => {});
|
|
7727
|
-
emitLocalEventHooksQuiet({ type: "task.created", payload });
|
|
7728
|
-
emitSharedTaskEventQuiet({ type: "task.created", task });
|
|
7970
|
+
emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
|
|
7971
|
+
emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
|
|
7729
7972
|
return task;
|
|
7730
7973
|
}
|
|
7731
7974
|
function getTask(id, db) {
|
|
@@ -8143,29 +8386,39 @@ function updateTask(id, input, db) {
|
|
|
8143
8386
|
approved_by: input.approved_by ?? task.approved_by,
|
|
8144
8387
|
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
8145
8388
|
};
|
|
8389
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8146
8390
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
8147
8391
|
const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
|
|
8148
8392
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
8149
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
8150
|
-
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 });
|
|
8151
8395
|
}
|
|
8152
8396
|
if (input.status !== undefined && input.status !== task.status) {
|
|
8153
8397
|
const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
|
|
8154
8398
|
dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
|
|
8155
|
-
emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
|
|
8156
|
-
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 });
|
|
8157
8401
|
}
|
|
8158
8402
|
if (input.approved_by !== undefined) {
|
|
8159
|
-
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 });
|
|
8160
8404
|
}
|
|
8161
8405
|
const updatePayload = taskEventData(updatedTask);
|
|
8162
8406
|
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
8163
|
-
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
|
|
8164
|
-
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
|
|
8407
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
|
|
8408
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
|
|
8165
8409
|
return updatedTask;
|
|
8166
8410
|
}
|
|
8167
8411
|
function deleteTask(id, db) {
|
|
8168
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);
|
|
8169
8422
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
8170
8423
|
return result.changes > 0;
|
|
8171
8424
|
}
|
|
@@ -8173,11 +8426,13 @@ var init_task_crud = __esm(() => {
|
|
|
8173
8426
|
init_types();
|
|
8174
8427
|
init_database();
|
|
8175
8428
|
init_completion_guard();
|
|
8429
|
+
init_event_emission_safety();
|
|
8176
8430
|
init_event_hooks();
|
|
8177
8431
|
init_shared_events();
|
|
8178
8432
|
init_audit();
|
|
8179
8433
|
init_webhooks();
|
|
8180
8434
|
init_checklists();
|
|
8435
|
+
init_storage_tombstones();
|
|
8181
8436
|
});
|
|
8182
8437
|
|
|
8183
8438
|
// src/lib/recurrence.ts
|
|
@@ -8326,8 +8581,9 @@ function resolveTemplateId(id, d) {
|
|
|
8326
8581
|
function createTemplate(input, db) {
|
|
8327
8582
|
const d = db || getDatabase();
|
|
8328
8583
|
const id = uuid();
|
|
8329
|
-
|
|
8330
|
-
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8331
8587
|
id,
|
|
8332
8588
|
input.name,
|
|
8333
8589
|
input.title_pattern,
|
|
@@ -8338,7 +8594,8 @@ function createTemplate(input, db) {
|
|
|
8338
8594
|
input.project_id || null,
|
|
8339
8595
|
input.plan_id || null,
|
|
8340
8596
|
JSON.stringify(input.metadata || {}),
|
|
8341
|
-
now()
|
|
8597
|
+
now(),
|
|
8598
|
+
machineId
|
|
8342
8599
|
]);
|
|
8343
8600
|
if (input.tasks && input.tasks.length > 0) {
|
|
8344
8601
|
addTemplateTasks(id, input.tasks, d);
|
|
@@ -8362,6 +8619,15 @@ function deleteTemplate(id, db) {
|
|
|
8362
8619
|
const resolved = resolveTemplateId(id, d);
|
|
8363
8620
|
if (!resolved)
|
|
8364
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);
|
|
8365
8631
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
8366
8632
|
}
|
|
8367
8633
|
function updateTemplate(id, updates, db) {
|
|
@@ -8731,6 +8997,7 @@ function previewTemplate(templateId, variables, db) {
|
|
|
8731
8997
|
var init_templates = __esm(() => {
|
|
8732
8998
|
init_database();
|
|
8733
8999
|
init_tasks();
|
|
9000
|
+
init_storage_tombstones();
|
|
8734
9001
|
});
|
|
8735
9002
|
|
|
8736
9003
|
// src/db/task-graph.ts
|
|
@@ -8897,6 +9164,7 @@ function getBlockingDeps(id, db) {
|
|
|
8897
9164
|
}
|
|
8898
9165
|
function startTask(id, agentId, db) {
|
|
8899
9166
|
const d = db || getDatabase();
|
|
9167
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8900
9168
|
const task = getTask(id, d);
|
|
8901
9169
|
if (!task)
|
|
8902
9170
|
throw new TaskNotFoundError(id);
|
|
@@ -8911,7 +9179,8 @@ function startTask(id, agentId, db) {
|
|
|
8911
9179
|
agent_id: agentId,
|
|
8912
9180
|
title: task.title,
|
|
8913
9181
|
blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
|
|
8914
|
-
}
|
|
9182
|
+
},
|
|
9183
|
+
databasePath
|
|
8915
9184
|
});
|
|
8916
9185
|
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
8917
9186
|
}
|
|
@@ -8933,12 +9202,13 @@ function startTask(id, agentId, db) {
|
|
|
8933
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 };
|
|
8934
9203
|
const payload = taskEventData(startedTask, { agent_id: agentId });
|
|
8935
9204
|
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
8936
|
-
emitLocalEventHooksQuiet({ type: "task.started", payload });
|
|
8937
|
-
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 });
|
|
8938
9207
|
return startedTask;
|
|
8939
9208
|
}
|
|
8940
9209
|
function completeTask(id, agentId, db, options) {
|
|
8941
9210
|
const d = db || getDatabase();
|
|
9211
|
+
const databasePath = databasePathFromDatabase(d);
|
|
8942
9212
|
const task = getTask(id, d);
|
|
8943
9213
|
if (!task)
|
|
8944
9214
|
throw new TaskNotFoundError(id);
|
|
@@ -8984,8 +9254,8 @@ function completeTask(id, agentId, db, options) {
|
|
|
8984
9254
|
};
|
|
8985
9255
|
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
|
|
8986
9256
|
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
8987
|
-
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
|
|
8988
|
-
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 });
|
|
8989
9259
|
let spawnedTask = null;
|
|
8990
9260
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
8991
9261
|
spawnedTask = spawnNextRecurrence(task, d, timestamp);
|
|
@@ -9029,9 +9299,9 @@ function completeTask(id, agentId, db, options) {
|
|
|
9029
9299
|
const depTask = getTask(dep.id, d);
|
|
9030
9300
|
const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
|
|
9031
9301
|
dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
|
|
9032
|
-
emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
|
|
9302
|
+
emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
|
|
9033
9303
|
if (depTask)
|
|
9034
|
-
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
|
|
9304
|
+
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
|
|
9035
9305
|
}
|
|
9036
9306
|
}
|
|
9037
9307
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
@@ -9201,6 +9471,7 @@ function getTasksChangedSince(since, filters, db) {
|
|
|
9201
9471
|
}
|
|
9202
9472
|
function failTask(id, agentId, reason, options, db) {
|
|
9203
9473
|
const d = db || getDatabase();
|
|
9474
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9204
9475
|
const task = getTask(id, d);
|
|
9205
9476
|
if (!task)
|
|
9206
9477
|
throw new TaskNotFoundError(id);
|
|
@@ -9229,8 +9500,8 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
9229
9500
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
9230
9501
|
const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
|
|
9231
9502
|
dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
|
|
9232
|
-
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
|
|
9233
|
-
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 });
|
|
9234
9505
|
let retryTask;
|
|
9235
9506
|
if (options?.retry) {
|
|
9236
9507
|
const retryCount = (task.retry_count || 0) + 1;
|
|
@@ -9290,6 +9561,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
|
|
|
9290
9561
|
}
|
|
9291
9562
|
function stealTask(agentId, opts, db) {
|
|
9292
9563
|
const d = db || getDatabase();
|
|
9564
|
+
const databasePath = databasePathFromDatabase(d);
|
|
9293
9565
|
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
9294
9566
|
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
9295
9567
|
if (staleTasks.length === 0)
|
|
@@ -9308,8 +9580,8 @@ function stealTask(agentId, opts, db) {
|
|
|
9308
9580
|
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
9309
9581
|
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
9310
9582
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
9311
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
9312
|
-
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 });
|
|
9313
9585
|
return stolenTask;
|
|
9314
9586
|
}
|
|
9315
9587
|
function claimOrSteal(agentId, filters, db) {
|
|
@@ -9357,6 +9629,7 @@ var init_task_lifecycle = __esm(() => {
|
|
|
9357
9629
|
init_types();
|
|
9358
9630
|
init_database();
|
|
9359
9631
|
init_completion_guard();
|
|
9632
|
+
init_event_emission_safety();
|
|
9360
9633
|
init_event_hooks();
|
|
9361
9634
|
init_shared_events();
|
|
9362
9635
|
init_audit();
|
|
@@ -10064,8 +10337,9 @@ function createPlan(input, db) {
|
|
|
10064
10337
|
const d = db || getDatabase();
|
|
10065
10338
|
const id = uuid();
|
|
10066
10339
|
const timestamp = now();
|
|
10067
|
-
|
|
10068
|
-
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10069
10343
|
id,
|
|
10070
10344
|
input.project_id || null,
|
|
10071
10345
|
input.task_list_id || null,
|
|
@@ -10074,7 +10348,8 @@ function createPlan(input, db) {
|
|
|
10074
10348
|
input.description || null,
|
|
10075
10349
|
input.status || "active",
|
|
10076
10350
|
timestamp,
|
|
10077
|
-
timestamp
|
|
10351
|
+
timestamp,
|
|
10352
|
+
machineId
|
|
10078
10353
|
]);
|
|
10079
10354
|
return getPlan(id, d);
|
|
10080
10355
|
}
|
|
@@ -10122,19 +10397,30 @@ function updatePlan(id, input, db) {
|
|
|
10122
10397
|
const updated = getPlan(id, d);
|
|
10123
10398
|
emitLocalEventHooksQuiet({
|
|
10124
10399
|
type: "plan.updated",
|
|
10125
|
-
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)
|
|
10126
10402
|
});
|
|
10127
10403
|
return updated;
|
|
10128
10404
|
}
|
|
10129
10405
|
function deletePlan(id, db) {
|
|
10130
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);
|
|
10131
10415
|
const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
|
|
10132
10416
|
return result.changes > 0;
|
|
10133
10417
|
}
|
|
10134
10418
|
var init_plans = __esm(() => {
|
|
10135
10419
|
init_types();
|
|
10420
|
+
init_event_emission_safety();
|
|
10136
10421
|
init_event_hooks();
|
|
10137
10422
|
init_database();
|
|
10423
|
+
init_storage_tombstones();
|
|
10138
10424
|
});
|
|
10139
10425
|
|
|
10140
10426
|
// src/db/boards.ts
|
|
@@ -10541,20 +10827,20 @@ var init_boards = __esm(() => {
|
|
|
10541
10827
|
// src/lib/artifact-store.ts
|
|
10542
10828
|
import { createHash as createHash2 } from "crypto";
|
|
10543
10829
|
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
10544
|
-
import { basename, dirname as dirname5, join as join6, resolve as
|
|
10545
|
-
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";
|
|
10546
10832
|
function isInMemoryDb2(path) {
|
|
10547
10833
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
10548
10834
|
}
|
|
10549
10835
|
function artifactStoreRoot() {
|
|
10550
10836
|
if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
|
|
10551
|
-
return
|
|
10837
|
+
return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
|
|
10552
10838
|
if (process.env["TODOS_ARTIFACTS_DIR"])
|
|
10553
|
-
return
|
|
10839
|
+
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
10554
10840
|
const dbPath = getDatabasePath();
|
|
10555
10841
|
if (isInMemoryDb2(dbPath))
|
|
10556
|
-
return join6(
|
|
10557
|
-
return join6(dirname5(
|
|
10842
|
+
return join6(tmpdir2(), "hasna-todos-artifacts");
|
|
10843
|
+
return join6(dirname5(resolve7(dbPath)), "artifacts");
|
|
10558
10844
|
}
|
|
10559
10845
|
function artifactStorePath(relativePath) {
|
|
10560
10846
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
@@ -10601,7 +10887,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
10601
10887
|
return "application/octet-stream";
|
|
10602
10888
|
}
|
|
10603
10889
|
function storeArtifactContent(input) {
|
|
10604
|
-
const sourcePath =
|
|
10890
|
+
const sourcePath = resolve7(input.path);
|
|
10605
10891
|
if (!existsSync7(sourcePath))
|
|
10606
10892
|
return null;
|
|
10607
10893
|
const sourceStat = statSync2(sourcePath);
|
|
@@ -11318,7 +11604,11 @@ function startTaskRun(input, db) {
|
|
|
11318
11604
|
}, d);
|
|
11319
11605
|
}
|
|
11320
11606
|
const run = getTaskRun(id, d);
|
|
11321
|
-
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
|
+
});
|
|
11322
11612
|
return run;
|
|
11323
11613
|
}
|
|
11324
11614
|
function beginTaskRunTransaction(input, db) {
|
|
@@ -11587,7 +11877,8 @@ function finishTaskRun(input, db) {
|
|
|
11587
11877
|
const updated = getTaskRun(run.id, d);
|
|
11588
11878
|
emitLocalEventHooksQuiet({
|
|
11589
11879
|
type: `run.${input.status}`,
|
|
11590
|
-
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)
|
|
11591
11882
|
});
|
|
11592
11883
|
return updated;
|
|
11593
11884
|
}
|
|
@@ -11671,6 +11962,7 @@ function getTaskRunLedger(runId, db) {
|
|
|
11671
11962
|
var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
|
|
11672
11963
|
var init_task_runs = __esm(() => {
|
|
11673
11964
|
init_artifact_store();
|
|
11965
|
+
init_event_emission_safety();
|
|
11674
11966
|
init_event_hooks();
|
|
11675
11967
|
init_redaction();
|
|
11676
11968
|
init_types();
|
|
@@ -12168,7 +12460,7 @@ __export(exports_helpers, {
|
|
|
12168
12460
|
});
|
|
12169
12461
|
import chalk from "chalk";
|
|
12170
12462
|
import { execSync } from "child_process";
|
|
12171
|
-
import { resolve as
|
|
12463
|
+
import { resolve as resolve8 } from "path";
|
|
12172
12464
|
function handleError(e) {
|
|
12173
12465
|
console.error(chalk.red(e instanceof Error ? e.message : String(e)));
|
|
12174
12466
|
process.exit(1);
|
|
@@ -12197,7 +12489,7 @@ function detectGitRoot() {
|
|
|
12197
12489
|
}
|
|
12198
12490
|
function resolveExplicitProject(input) {
|
|
12199
12491
|
const db = getDatabase();
|
|
12200
|
-
const byPath = getProjectByPath(
|
|
12492
|
+
const byPath = getProjectByPath(resolve8(input), db);
|
|
12201
12493
|
if (byPath)
|
|
12202
12494
|
return byPath;
|
|
12203
12495
|
const id = resolvePartialId(db, "projects", input);
|
|
@@ -12298,13 +12590,193 @@ var init_helpers = __esm(() => {
|
|
|
12298
12590
|
};
|
|
12299
12591
|
});
|
|
12300
12592
|
|
|
12593
|
+
// src/lib/task-routing.ts
|
|
12594
|
+
var exports_task_routing = {};
|
|
12595
|
+
__export(exports_task_routing, {
|
|
12596
|
+
setTaskWorkflowPointers: () => setTaskWorkflowPointers,
|
|
12597
|
+
getTaskRouteState: () => getTaskRouteState
|
|
12598
|
+
});
|
|
12599
|
+
function classifyProjectKind2(path) {
|
|
12600
|
+
if (!path)
|
|
12601
|
+
return null;
|
|
12602
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
12603
|
+
}
|
|
12604
|
+
function machineLocalPath(project, db) {
|
|
12605
|
+
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
12606
|
+
if (!machineId)
|
|
12607
|
+
return null;
|
|
12608
|
+
try {
|
|
12609
|
+
const row = db.query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
|
|
12610
|
+
return row?.path ?? null;
|
|
12611
|
+
} catch {
|
|
12612
|
+
return null;
|
|
12613
|
+
}
|
|
12614
|
+
}
|
|
12615
|
+
function resolveProject(task, db) {
|
|
12616
|
+
const project = task.project_id ? getProject(task.project_id, db) : null;
|
|
12617
|
+
const projectPath = project ? machineLocalPath(project, db) ?? project.path : task.working_dir;
|
|
12618
|
+
return { project, projectPath: projectPath ?? null };
|
|
12619
|
+
}
|
|
12620
|
+
function resolveTaskList(task, project, db) {
|
|
12621
|
+
if (task.task_list_id) {
|
|
12622
|
+
return getTaskList(task.task_list_id, db) ?? (project ? getTaskListBySlug(task.task_list_id, project.id, db) : null);
|
|
12623
|
+
}
|
|
12624
|
+
if (project?.task_list_id) {
|
|
12625
|
+
return getTaskListBySlug(project.task_list_id, project.id, db);
|
|
12626
|
+
}
|
|
12627
|
+
return null;
|
|
12628
|
+
}
|
|
12629
|
+
function isTerminal(status) {
|
|
12630
|
+
return status === "completed" || status === "cancelled" || status === "failed";
|
|
12631
|
+
}
|
|
12632
|
+
function routeConcurrencyKey(task, project, taskList, projectPath) {
|
|
12633
|
+
if (project?.id)
|
|
12634
|
+
return `project:${project.id}`;
|
|
12635
|
+
if (taskList?.id)
|
|
12636
|
+
return `task-list:${taskList.id}`;
|
|
12637
|
+
if (projectPath)
|
|
12638
|
+
return `path:${projectPath}`;
|
|
12639
|
+
return `task:${task.id}`;
|
|
12640
|
+
}
|
|
12641
|
+
function getTaskRouteState(taskOrId, db) {
|
|
12642
|
+
const d = db || getDatabase();
|
|
12643
|
+
const task = typeof taskOrId === "string" ? getTask(taskOrId, d) : taskOrId;
|
|
12644
|
+
if (!task)
|
|
12645
|
+
throw new Error(`Task not found: ${taskOrId}`);
|
|
12646
|
+
const { project, projectPath } = resolveProject(task, d);
|
|
12647
|
+
const taskList = resolveTaskList(task, project, d);
|
|
12648
|
+
const automation = routingAutomationMetadata(task, taskList) ?? {};
|
|
12649
|
+
const routeEnabled = routeEnabledForTask(task, taskList) === true;
|
|
12650
|
+
const tagOptIn = task.tags.includes("auto:route") || task.tags.includes("route:enabled");
|
|
12651
|
+
const locked = Boolean(task.locked_by && !isLockExpired(task.locked_at));
|
|
12652
|
+
const blockers = getBlockingDeps(task.id, d);
|
|
12653
|
+
const blocked = blockers.length > 0;
|
|
12654
|
+
const terminal = isTerminal(task.status);
|
|
12655
|
+
const requiresApproval = automation.requires_approval === true || task.requires_approval === true;
|
|
12656
|
+
const approvalRequired = automation.approval_required === true;
|
|
12657
|
+
const approved = Boolean(task.approved_by);
|
|
12658
|
+
const gates = {
|
|
12659
|
+
route_enabled: routeEnabled,
|
|
12660
|
+
tag_opt_in: tagOptIn,
|
|
12661
|
+
no_auto: automation.no_auto === true,
|
|
12662
|
+
manual: automation.manual === true,
|
|
12663
|
+
manual_required: automation.manual_required === true,
|
|
12664
|
+
requires_approval: requiresApproval,
|
|
12665
|
+
approval_required: approvalRequired,
|
|
12666
|
+
approved,
|
|
12667
|
+
locked,
|
|
12668
|
+
blocked,
|
|
12669
|
+
terminal
|
|
12670
|
+
};
|
|
12671
|
+
const reasons = [];
|
|
12672
|
+
if (task.status !== "pending")
|
|
12673
|
+
reasons.push("task_not_pending");
|
|
12674
|
+
if (terminal)
|
|
12675
|
+
reasons.push("task_terminal");
|
|
12676
|
+
if (!routeEnabled)
|
|
12677
|
+
reasons.push("route_not_enabled");
|
|
12678
|
+
if (locked)
|
|
12679
|
+
reasons.push("task_locked");
|
|
12680
|
+
if (blocked)
|
|
12681
|
+
reasons.push("task_blocked");
|
|
12682
|
+
if (gates.no_auto)
|
|
12683
|
+
reasons.push("no_auto");
|
|
12684
|
+
if (gates.manual)
|
|
12685
|
+
reasons.push("manual");
|
|
12686
|
+
if (gates.manual_required)
|
|
12687
|
+
reasons.push("manual_required");
|
|
12688
|
+
if (requiresApproval && !approved)
|
|
12689
|
+
reasons.push("requires_approval");
|
|
12690
|
+
if (approvalRequired && !approved)
|
|
12691
|
+
reasons.push("approval_required");
|
|
12692
|
+
if (automation.allowed === false)
|
|
12693
|
+
reasons.push("automation_disallowed");
|
|
12694
|
+
return {
|
|
12695
|
+
schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
|
|
12696
|
+
task_id: task.id,
|
|
12697
|
+
task_short_id: task.short_id,
|
|
12698
|
+
status: task.status,
|
|
12699
|
+
eligible: reasons.length === 0,
|
|
12700
|
+
reasons,
|
|
12701
|
+
blockers: blockers.map((blocker) => ({
|
|
12702
|
+
id: blocker.id,
|
|
12703
|
+
short_id: blocker.short_id,
|
|
12704
|
+
title: blocker.title,
|
|
12705
|
+
status: blocker.status
|
|
12706
|
+
})),
|
|
12707
|
+
gates,
|
|
12708
|
+
automation: Object.keys(automation).length > 0 ? automation : null,
|
|
12709
|
+
route: {
|
|
12710
|
+
project_id: project?.id ?? task.project_id,
|
|
12711
|
+
project_path: projectPath,
|
|
12712
|
+
working_dir: task.working_dir ?? projectPath,
|
|
12713
|
+
project_kind: classifyProjectKind2(projectPath),
|
|
12714
|
+
task_list_id: taskList?.id ?? task.task_list_id,
|
|
12715
|
+
task_list_slug: taskList?.slug ?? null,
|
|
12716
|
+
task_list_name: taskList?.name ?? null,
|
|
12717
|
+
concurrency_key: routeConcurrencyKey(task, project, taskList, projectPath)
|
|
12718
|
+
},
|
|
12719
|
+
pointers: workflowPointersFromMetadata(task.metadata)
|
|
12720
|
+
};
|
|
12721
|
+
}
|
|
12722
|
+
function setTaskWorkflowPointers(taskId, input, db) {
|
|
12723
|
+
const d = db || getDatabase();
|
|
12724
|
+
const task = getTask(taskId, d);
|
|
12725
|
+
if (!task)
|
|
12726
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
12727
|
+
const previous = workflowPointersFromMetadata(task.metadata);
|
|
12728
|
+
const next = compactWorkflowPointers({
|
|
12729
|
+
current_workflow_invocation_id: pointerPatch(previous.current_workflow_invocation_id, input, "current_workflow_invocation_id"),
|
|
12730
|
+
current_run_id: pointerPatch(previous.current_run_id, input, "current_run_id"),
|
|
12731
|
+
latest_manifest_path: pointerPatch(previous.latest_manifest_path, input, "latest_manifest_path"),
|
|
12732
|
+
latest_evaluation_path: pointerPatch(previous.latest_evaluation_path, input, "latest_evaluation_path"),
|
|
12733
|
+
workflow_state: pointerPatch(previous.workflow_state, input, "workflow_state")
|
|
12734
|
+
});
|
|
12735
|
+
const timestamp = now();
|
|
12736
|
+
const {
|
|
12737
|
+
current_workflow_invocation_id,
|
|
12738
|
+
current_run_id,
|
|
12739
|
+
latest_manifest_path,
|
|
12740
|
+
latest_evaluation_path,
|
|
12741
|
+
workflow_state,
|
|
12742
|
+
workflow_invocation,
|
|
12743
|
+
...baseMetadata
|
|
12744
|
+
} = task.metadata;
|
|
12745
|
+
const metadata = {
|
|
12746
|
+
...baseMetadata,
|
|
12747
|
+
...next,
|
|
12748
|
+
workflow_invocation: {
|
|
12749
|
+
schema_version: TASK_WORKFLOW_POINTER_SCHEMA_VERSION,
|
|
12750
|
+
...next,
|
|
12751
|
+
updated_at: timestamp,
|
|
12752
|
+
updated_by: input.actor ?? null
|
|
12753
|
+
}
|
|
12754
|
+
};
|
|
12755
|
+
return updateTask(task.id, { version: task.version, metadata }, d);
|
|
12756
|
+
}
|
|
12757
|
+
function pointerPatch(previous, input, key) {
|
|
12758
|
+
if (!Object.prototype.hasOwnProperty.call(input, key))
|
|
12759
|
+
return previous;
|
|
12760
|
+
const value = input[key];
|
|
12761
|
+
if (value === undefined)
|
|
12762
|
+
return previous;
|
|
12763
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
12764
|
+
}
|
|
12765
|
+
var init_task_routing = __esm(() => {
|
|
12766
|
+
init_database();
|
|
12767
|
+
init_projects();
|
|
12768
|
+
init_task_crud();
|
|
12769
|
+
init_task_lifecycle();
|
|
12770
|
+
init_task_lists();
|
|
12771
|
+
});
|
|
12772
|
+
|
|
12301
12773
|
// src/cli/commands/task-commands.ts
|
|
12302
12774
|
var exports_task_commands = {};
|
|
12303
12775
|
__export(exports_task_commands, {
|
|
12304
12776
|
registerTaskCommands: () => registerTaskCommands
|
|
12305
12777
|
});
|
|
12306
12778
|
import chalk2 from "chalk";
|
|
12307
|
-
import { basename as basename3, resolve as
|
|
12779
|
+
import { basename as basename3, resolve as resolve9 } from "path";
|
|
12308
12780
|
function resolveProjectIdOrSlug(input) {
|
|
12309
12781
|
const db = getDatabase();
|
|
12310
12782
|
const byId = getProject(input, db);
|
|
@@ -12314,7 +12786,7 @@ function resolveProjectIdOrSlug(input) {
|
|
|
12314
12786
|
if (row)
|
|
12315
12787
|
return row.id;
|
|
12316
12788
|
if (isPathLike(input)) {
|
|
12317
|
-
const projectPath =
|
|
12789
|
+
const projectPath = resolve9(input);
|
|
12318
12790
|
const byPath = getProjectByPath(projectPath, db);
|
|
12319
12791
|
return (byPath ?? ensureProject(basename3(projectPath), projectPath, db)).id;
|
|
12320
12792
|
}
|
|
@@ -12367,6 +12839,11 @@ function parseJsonValue(value) {
|
|
|
12367
12839
|
return value;
|
|
12368
12840
|
}
|
|
12369
12841
|
}
|
|
12842
|
+
function pointerOption(value, clear) {
|
|
12843
|
+
if (value !== undefined)
|
|
12844
|
+
return value;
|
|
12845
|
+
return clear ? null : undefined;
|
|
12846
|
+
}
|
|
12370
12847
|
function parseTags(value) {
|
|
12371
12848
|
return value ? value.split(",").map((tag) => tag.trim()).filter(Boolean) : undefined;
|
|
12372
12849
|
}
|
|
@@ -12469,7 +12946,7 @@ function registerTaskCommands(program2) {
|
|
|
12469
12946
|
task_list_id: taskListId,
|
|
12470
12947
|
tags: parseTags(opts.tags),
|
|
12471
12948
|
metadata: buildExpectationMetadata(opts),
|
|
12472
|
-
working_dir: opts.workingDir ?
|
|
12949
|
+
working_dir: opts.workingDir ? resolve9(opts.workingDir) : process.cwd(),
|
|
12473
12950
|
project_id: projectId,
|
|
12474
12951
|
assigned_to: opts.assign,
|
|
12475
12952
|
agent_id: globalOpts.agent,
|
|
@@ -12485,6 +12962,63 @@ function registerTaskCommands(program2) {
|
|
|
12485
12962
|
console.log(formatTaskLine(result.task));
|
|
12486
12963
|
}
|
|
12487
12964
|
});
|
|
12965
|
+
task.command("route-state <id>").description("Show deterministic routing eligibility and workflow pointers for a task").action(async (id) => {
|
|
12966
|
+
const globalOpts = program2.opts();
|
|
12967
|
+
const resolvedId = resolveTaskId(id);
|
|
12968
|
+
const { getTaskRouteState: getTaskRouteState2 } = await Promise.resolve().then(() => (init_task_routing(), exports_task_routing));
|
|
12969
|
+
let state;
|
|
12970
|
+
try {
|
|
12971
|
+
state = getTaskRouteState2(resolvedId);
|
|
12972
|
+
} catch (e) {
|
|
12973
|
+
handleError(e);
|
|
12974
|
+
}
|
|
12975
|
+
if (globalOpts.json) {
|
|
12976
|
+
output(state, true);
|
|
12977
|
+
return;
|
|
12978
|
+
}
|
|
12979
|
+
console.log(chalk2.bold("Task route state"));
|
|
12980
|
+
console.log(` ${chalk2.dim("Task:")} ${state.task_short_id || state.task_id.slice(0, 8)}`);
|
|
12981
|
+
console.log(` ${chalk2.dim("Eligible:")} ${state.eligible ? chalk2.green("yes") : chalk2.yellow("no")}`);
|
|
12982
|
+
console.log(` ${chalk2.dim("Reasons:")} ${state.reasons.length > 0 ? state.reasons.join(", ") : "none"}`);
|
|
12983
|
+
console.log(` ${chalk2.dim("Route:")} ${state.route.concurrency_key}`);
|
|
12984
|
+
if (state.pointers.current_workflow_invocation_id) {
|
|
12985
|
+
console.log(` ${chalk2.dim("Invocation:")} ${state.pointers.current_workflow_invocation_id}`);
|
|
12986
|
+
}
|
|
12987
|
+
if (state.pointers.current_run_id) {
|
|
12988
|
+
console.log(` ${chalk2.dim("Run:")} ${state.pointers.current_run_id}`);
|
|
12989
|
+
}
|
|
12990
|
+
if (state.pointers.latest_manifest_path) {
|
|
12991
|
+
console.log(` ${chalk2.dim("Manifest:")} ${state.pointers.latest_manifest_path}`);
|
|
12992
|
+
}
|
|
12993
|
+
});
|
|
12994
|
+
task.command("workflow-pointers <id>").description("Update OpenLoops workflow invocation/run artifact pointers on a task").option("--invocation <id>", "Current workflow invocation ID").option("--run <id>", "Current workflow run ID").option("--manifest <path>", "Latest run manifest path").option("--evaluation <path>", "Latest evaluator artifact path").option("--state <state>", "Human-visible workflow state").option("--actor <agent>", "Agent or workflow updating the pointers").option("--clear", "Clear all workflow pointers before applying explicit pointer values").option("--clear-invocation", "Clear current workflow invocation ID").option("--clear-run", "Clear current workflow run ID").option("--clear-manifest", "Clear latest run manifest path").option("--clear-evaluation", "Clear latest evaluator artifact path").option("--clear-state", "Clear human-visible workflow state").action(async (id, opts) => {
|
|
12995
|
+
const globalOpts = program2.opts();
|
|
12996
|
+
const resolvedId = resolveTaskId(id);
|
|
12997
|
+
const { getTaskRouteState: getTaskRouteState2, setTaskWorkflowPointers: setTaskWorkflowPointers2 } = await Promise.resolve().then(() => (init_task_routing(), exports_task_routing));
|
|
12998
|
+
let taskResult;
|
|
12999
|
+
try {
|
|
13000
|
+
taskResult = setTaskWorkflowPointers2(resolvedId, {
|
|
13001
|
+
current_workflow_invocation_id: pointerOption(opts.invocation, Boolean(opts.clear || opts.clearInvocation)),
|
|
13002
|
+
current_run_id: pointerOption(opts.run, Boolean(opts.clear || opts.clearRun)),
|
|
13003
|
+
latest_manifest_path: pointerOption(opts.manifest, Boolean(opts.clear || opts.clearManifest)),
|
|
13004
|
+
latest_evaluation_path: pointerOption(opts.evaluation, Boolean(opts.clear || opts.clearEvaluation)),
|
|
13005
|
+
workflow_state: pointerOption(opts.state, Boolean(opts.clear || opts.clearState)),
|
|
13006
|
+
actor: opts.actor || globalOpts.agent || "cli"
|
|
13007
|
+
});
|
|
13008
|
+
} catch (e) {
|
|
13009
|
+
handleError(e);
|
|
13010
|
+
}
|
|
13011
|
+
const state = getTaskRouteState2(taskResult.id);
|
|
13012
|
+
if (globalOpts.json) {
|
|
13013
|
+
output({ task: taskResult, route_state: state }, true);
|
|
13014
|
+
return;
|
|
13015
|
+
}
|
|
13016
|
+
console.log(chalk2.green("Workflow pointers updated:"));
|
|
13017
|
+
console.log(formatTaskLine(taskResult));
|
|
13018
|
+
if (state.pointers.latest_manifest_path) {
|
|
13019
|
+
console.log(` ${chalk2.dim("Manifest:")} ${state.pointers.latest_manifest_path}`);
|
|
13020
|
+
}
|
|
13021
|
+
});
|
|
12488
13022
|
program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <id>", "Filter by task list ID").option("--task-list <id>", "Filter by task list ID (alias for --list)").option("--project-name <name>", "Filter by project name").option("--agent-name <name>", "Filter by agent name/assigned").option("--sort <field>", "Sort by: updated, created, priority, status").option("--format <fmt>", "Output format: table (default), compact, csv, json").option("--due-today", "Only tasks due today or earlier").option("--overdue", "Only overdue tasks (past due_at)").option("--recurring", "Only recurring tasks").option("--limit <n>", "Max tasks to return").action((opts) => {
|
|
12489
13023
|
const globalOpts = program2.opts();
|
|
12490
13024
|
opts.tags = opts.tags || opts.tag;
|
|
@@ -14983,7 +15517,7 @@ __export(exports_project_bootstrap, {
|
|
|
14983
15517
|
bootstrapProject: () => bootstrapProject
|
|
14984
15518
|
});
|
|
14985
15519
|
import { existsSync as existsSync10, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
14986
|
-
import { basename as basename4, dirname as dirname6, resolve as
|
|
15520
|
+
import { basename as basename4, dirname as dirname6, resolve as resolve10 } from "path";
|
|
14987
15521
|
function safeStat(path) {
|
|
14988
15522
|
try {
|
|
14989
15523
|
return statSync3(path);
|
|
@@ -14992,7 +15526,7 @@ function safeStat(path) {
|
|
|
14992
15526
|
}
|
|
14993
15527
|
}
|
|
14994
15528
|
function canonicalPath(input) {
|
|
14995
|
-
const resolved =
|
|
15529
|
+
const resolved = resolve10(input);
|
|
14996
15530
|
const stats = safeStat(resolved);
|
|
14997
15531
|
if (stats?.isFile())
|
|
14998
15532
|
return dirname6(resolved);
|
|
@@ -15001,7 +15535,7 @@ function canonicalPath(input) {
|
|
|
15001
15535
|
function findUp(start, marker) {
|
|
15002
15536
|
let current = canonicalPath(start);
|
|
15003
15537
|
while (true) {
|
|
15004
|
-
if (existsSync10(
|
|
15538
|
+
if (existsSync10(resolve10(current, marker)))
|
|
15005
15539
|
return current;
|
|
15006
15540
|
const parent = dirname6(current);
|
|
15007
15541
|
if (parent === current)
|
|
@@ -15012,7 +15546,7 @@ function findUp(start, marker) {
|
|
|
15012
15546
|
function readPackageJson(path) {
|
|
15013
15547
|
if (!path)
|
|
15014
15548
|
return null;
|
|
15015
|
-
const file =
|
|
15549
|
+
const file = resolve10(path, "package.json");
|
|
15016
15550
|
if (!existsSync10(file))
|
|
15017
15551
|
return null;
|
|
15018
15552
|
try {
|
|
@@ -15035,7 +15569,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
15035
15569
|
if (rootPackage?.workspaces)
|
|
15036
15570
|
markers.push("package.json#workspaces");
|
|
15037
15571
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
15038
|
-
if (existsSync10(
|
|
15572
|
+
if (existsSync10(resolve10(root, marker)))
|
|
15039
15573
|
markers.push(marker);
|
|
15040
15574
|
}
|
|
15041
15575
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -15097,7 +15631,19 @@ function bootstrapProject(options = {}, db) {
|
|
|
15097
15631
|
}
|
|
15098
15632
|
setMachineLocalPath(project.id, discovery.projectPath, d);
|
|
15099
15633
|
const beforeTaskList = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(project.id, taskListSlug);
|
|
15100
|
-
|
|
15634
|
+
let taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
|
|
15635
|
+
if (options.routeEnabled && taskList.metadata.route_enabled !== true) {
|
|
15636
|
+
taskList = updateTaskList(taskList.id, {
|
|
15637
|
+
metadata: {
|
|
15638
|
+
...taskList.metadata,
|
|
15639
|
+
route_enabled: true,
|
|
15640
|
+
automation: {
|
|
15641
|
+
...taskList.metadata.automation && typeof taskList.metadata.automation === "object" && !Array.isArray(taskList.metadata.automation) ? taskList.metadata.automation : {},
|
|
15642
|
+
no_auto: false
|
|
15643
|
+
}
|
|
15644
|
+
}
|
|
15645
|
+
}, d);
|
|
15646
|
+
}
|
|
15101
15647
|
const createdSources = [];
|
|
15102
15648
|
for (const source of [
|
|
15103
15649
|
addSourceOnce(project.id, "local", "Project root", discovery.projectPath, { role: "project-root" }, d),
|
|
@@ -15159,7 +15705,7 @@ __export(exports_extract, {
|
|
|
15159
15705
|
});
|
|
15160
15706
|
import { existsSync as existsSync11, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
15161
15707
|
import { createHash as createHash3 } from "crypto";
|
|
15162
|
-
import { relative as relative3, resolve as
|
|
15708
|
+
import { relative as relative3, resolve as resolve11, join as join10 } from "path";
|
|
15163
15709
|
function stableHash(value) {
|
|
15164
15710
|
return createHash3("sha256").update(value).digest("hex");
|
|
15165
15711
|
}
|
|
@@ -15167,7 +15713,7 @@ function normalizePathForMatch(value) {
|
|
|
15167
15713
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
15168
15714
|
}
|
|
15169
15715
|
function readGitignorePatterns(basePath) {
|
|
15170
|
-
const root = statSync4(basePath).isFile() ?
|
|
15716
|
+
const root = statSync4(basePath).isFile() ? resolve11(basePath, "..") : basePath;
|
|
15171
15717
|
const gitignorePath = join10(root, ".gitignore");
|
|
15172
15718
|
if (!existsSync11(gitignorePath))
|
|
15173
15719
|
return [];
|
|
@@ -15303,7 +15849,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
|
|
|
15303
15849
|
return files.sort();
|
|
15304
15850
|
}
|
|
15305
15851
|
function buildCodebaseIndex(options) {
|
|
15306
|
-
const basePath =
|
|
15852
|
+
const basePath = resolve11(options.path);
|
|
15307
15853
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
15308
15854
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
15309
15855
|
const excludes = options.exclude || [];
|
|
@@ -15314,7 +15860,7 @@ function buildCodebaseIndex(options) {
|
|
|
15314
15860
|
const fullPath = statSync4(basePath).isFile() ? basePath : join10(basePath, file);
|
|
15315
15861
|
try {
|
|
15316
15862
|
const source = readFileSync6(fullPath, "utf-8");
|
|
15317
|
-
const relPath = statSync4(basePath).isFile() ? relative3(
|
|
15863
|
+
const relPath = statSync4(basePath).isFile() ? relative3(resolve11(basePath, ".."), fullPath) : file;
|
|
15318
15864
|
indexed.push({
|
|
15319
15865
|
file: relPath,
|
|
15320
15866
|
checksum: stableHash(source).slice(0, 24),
|
|
@@ -15334,7 +15880,7 @@ function buildCodebaseIndex(options) {
|
|
|
15334
15880
|
};
|
|
15335
15881
|
}
|
|
15336
15882
|
function extractTodos(options, db) {
|
|
15337
|
-
const basePath =
|
|
15883
|
+
const basePath = resolve11(options.path);
|
|
15338
15884
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
15339
15885
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
15340
15886
|
const excludes = options.exclude || [];
|
|
@@ -15345,7 +15891,7 @@ function extractTodos(options, db) {
|
|
|
15345
15891
|
const fullPath = statSync4(basePath).isFile() ? basePath : join10(basePath, file);
|
|
15346
15892
|
try {
|
|
15347
15893
|
const source = readFileSync6(fullPath, "utf-8");
|
|
15348
|
-
const relPath = statSync4(basePath).isFile() ? relative3(
|
|
15894
|
+
const relPath = statSync4(basePath).isFile() ? relative3(resolve11(basePath, ".."), fullPath) : file;
|
|
15349
15895
|
const comments = extractFromSource(source, relPath, tags);
|
|
15350
15896
|
allComments.push(...comments);
|
|
15351
15897
|
} catch {}
|
|
@@ -15439,7 +15985,7 @@ async function watchSourceTodos(options, onRun) {
|
|
|
15439
15985
|
const interval = Math.max(100, options.interval_ms || 2000);
|
|
15440
15986
|
const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
|
|
15441
15987
|
const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
|
|
15442
|
-
const root =
|
|
15988
|
+
const root = resolve11(options.path);
|
|
15443
15989
|
const runs = [];
|
|
15444
15990
|
let previous = new Map;
|
|
15445
15991
|
for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
|
|
@@ -16610,7 +17156,7 @@ __export(exports_project_commands, {
|
|
|
16610
17156
|
registerProjectCommands: () => registerProjectCommands
|
|
16611
17157
|
});
|
|
16612
17158
|
import chalk4 from "chalk";
|
|
16613
|
-
import { basename as basename5, resolve as
|
|
17159
|
+
import { basename as basename5, resolve as resolve12 } from "path";
|
|
16614
17160
|
function collectOption(value, previous = []) {
|
|
16615
17161
|
return [...previous, value];
|
|
16616
17162
|
}
|
|
@@ -16703,7 +17249,7 @@ function buildSearchFilters(query, opts, projectId) {
|
|
|
16703
17249
|
return Object.fromEntries(Object.entries(filters).filter(([, value]) => value !== undefined));
|
|
16704
17250
|
}
|
|
16705
17251
|
function registerProjectCommands(program2) {
|
|
16706
|
-
program2.command("project-bootstrap [path]").description("Discover a local workspace and initialize project task state").option("--name <name>", "Project display name").option("--task-list <slug>", "Default task list slug").option("--dry-run", "Show discovery without writing local state").action(async (inputPath, opts) => {
|
|
17252
|
+
program2.command("project-bootstrap [path]").description("Discover a local workspace and initialize project task state").option("--name <name>", "Project display name").option("--task-list <slug>", "Default task list slug").option("--route-enabled", "Mark the default task list as eligible for OpenLoops task-created routing").option("--dry-run", "Show discovery without writing local state").action(async (inputPath, opts) => {
|
|
16707
17253
|
const globalOpts = program2.opts();
|
|
16708
17254
|
try {
|
|
16709
17255
|
const { bootstrapProject: bootstrapProject2 } = await Promise.resolve().then(() => (init_project_bootstrap(), exports_project_bootstrap));
|
|
@@ -16711,6 +17257,7 @@ function registerProjectCommands(program2) {
|
|
|
16711
17257
|
path: inputPath || globalOpts.project || process.cwd(),
|
|
16712
17258
|
name: opts.name,
|
|
16713
17259
|
taskListSlug: opts.taskList,
|
|
17260
|
+
routeEnabled: Boolean(opts.routeEnabled),
|
|
16714
17261
|
dryRun: opts.dryRun
|
|
16715
17262
|
});
|
|
16716
17263
|
if (globalOpts.json) {
|
|
@@ -16732,6 +17279,8 @@ function registerProjectCommands(program2) {
|
|
|
16732
17279
|
console.log(` ${chalk4.dim("Project:")} ${result.project.id.slice(0, 8)} ${result.created.project ? "(created)" : "(existing)"}`);
|
|
16733
17280
|
if (result.taskList)
|
|
16734
17281
|
console.log(` ${chalk4.dim("Task list:")} ${result.taskList.slug} ${result.created.taskList ? "(created)" : "(existing)"}`);
|
|
17282
|
+
if (result.taskList?.metadata.route_enabled === true)
|
|
17283
|
+
console.log(` ${chalk4.dim("Routing:")} route-enabled`);
|
|
16735
17284
|
if (result.created.sources.length > 0) {
|
|
16736
17285
|
console.log(` ${chalk4.dim("Sources:")} ${result.created.sources.join(", ")}`);
|
|
16737
17286
|
}
|
|
@@ -16949,7 +17498,7 @@ function registerProjectCommands(program2) {
|
|
|
16949
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) => {
|
|
16950
17499
|
const globalOpts = program2.opts();
|
|
16951
17500
|
if (opts.add) {
|
|
16952
|
-
const projectPath =
|
|
17501
|
+
const projectPath = resolve12(opts.add);
|
|
16953
17502
|
const name = opts.name || basename5(projectPath);
|
|
16954
17503
|
const existing = getProjectByPath(projectPath);
|
|
16955
17504
|
let project;
|
|
@@ -17033,7 +17582,7 @@ function registerProjectCommands(program2) {
|
|
|
17033
17582
|
console.error(chalk4.red(`Project not found: ${projectId}`));
|
|
17034
17583
|
process.exit(1);
|
|
17035
17584
|
}
|
|
17036
|
-
const entry = setMachineLocalPath2(resolved,
|
|
17585
|
+
const entry = setMachineLocalPath2(resolved, resolve12(projectPath));
|
|
17037
17586
|
if (useJson) {
|
|
17038
17587
|
output(entry, true);
|
|
17039
17588
|
} else {
|
|
@@ -17100,7 +17649,7 @@ function registerProjectCommands(program2) {
|
|
|
17100
17649
|
const patterns = opts.pattern ? opts.pattern.split(",").map((t) => t.trim().toUpperCase()) : undefined;
|
|
17101
17650
|
const taskListId = opts.list ? resolveTaskListId(opts.list) : undefined;
|
|
17102
17651
|
const result = extractTodos2({
|
|
17103
|
-
path:
|
|
17652
|
+
path: resolve12(scanPath),
|
|
17104
17653
|
patterns,
|
|
17105
17654
|
project_id: projectId,
|
|
17106
17655
|
task_list_id: taskListId,
|
|
@@ -17161,7 +17710,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17161
17710
|
const taskListId = opts.list ? resolveTaskListId(opts.list) : undefined;
|
|
17162
17711
|
const maxRuns = opts.maxRuns ? parseInt(opts.maxRuns, 10) : 1;
|
|
17163
17712
|
const result = await watchSourceTodos2({
|
|
17164
|
-
path:
|
|
17713
|
+
path: resolve12(scanPath),
|
|
17165
17714
|
patterns,
|
|
17166
17715
|
project_id: projectId,
|
|
17167
17716
|
task_list_id: taskListId,
|
|
@@ -17198,7 +17747,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17198
17747
|
const writeOutput = async (content) => {
|
|
17199
17748
|
if (opts.output) {
|
|
17200
17749
|
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
17201
|
-
writeFileSync5(
|
|
17750
|
+
writeFileSync5(resolve12(opts.output), content.endsWith(`
|
|
17202
17751
|
`) ? content : `${content}
|
|
17203
17752
|
`);
|
|
17204
17753
|
} else {
|
|
@@ -17213,12 +17762,12 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17213
17762
|
const exported = opts.encrypt ? createEncryptedBridgeBundle2(bundle, { profile: opts.encryptionProfile }) : bundle;
|
|
17214
17763
|
const json = JSON.stringify(exported, null, 2);
|
|
17215
17764
|
await writeOutput(json);
|
|
17216
|
-
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 } });
|
|
17217
17766
|
if (!opts.encrypt && !opts.allowPlaintextSensitive) {
|
|
17218
17767
|
console.error(chalk4.yellow("Warning: bridge exports are plaintext JSON. Use --encrypt for sensitive metadata, evidence, and artifact bundles."));
|
|
17219
17768
|
}
|
|
17220
17769
|
if (opts.output && !globalOpts.json) {
|
|
17221
|
-
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)}`));
|
|
17222
17771
|
}
|
|
17223
17772
|
return;
|
|
17224
17773
|
}
|
|
@@ -17231,7 +17780,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17231
17780
|
await writeOutput(JSON.stringify(tasks, null, 2));
|
|
17232
17781
|
}
|
|
17233
17782
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
17234
|
-
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 } });
|
|
17235
17784
|
});
|
|
17236
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) => {
|
|
17237
17786
|
const globalOpts = program2.opts();
|
|
@@ -17239,13 +17788,13 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17239
17788
|
const { readFileSync: readFileSync7 } = await import("fs");
|
|
17240
17789
|
const { importLocalBridgeBundle: importLocalBridgeBundle2 } = await Promise.resolve().then(() => (init_local_bridge(), exports_local_bridge));
|
|
17241
17790
|
const { decryptBridgeBundle: decryptBridgeBundle2, isEncryptedBridgeBundle: isEncryptedBridgeBundle2 } = await Promise.resolve().then(() => (init_local_encryption(), exports_local_encryption));
|
|
17242
|
-
const parsed = JSON.parse(readFileSync7(
|
|
17791
|
+
const parsed = JSON.parse(readFileSync7(resolve12(file), "utf-8"));
|
|
17243
17792
|
const bundle = isEncryptedBridgeBundle2(parsed) ? opts.decrypt ? decryptBridgeBundle2(parsed) : (() => {
|
|
17244
17793
|
throw new Error("Bridge bundle is encrypted. Re-run with --decrypt and the configured key environment variable set.");
|
|
17245
17794
|
})() : parsed;
|
|
17246
17795
|
const result = importLocalBridgeBundle2(bundle, { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
|
|
17247
17796
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
17248
|
-
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 } });
|
|
17249
17798
|
if (globalOpts.json) {
|
|
17250
17799
|
output(result, true);
|
|
17251
17800
|
return;
|
|
@@ -17275,9 +17824,9 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
17275
17824
|
try {
|
|
17276
17825
|
const { readFileSync: readFileSync7 } = await import("fs");
|
|
17277
17826
|
const { importTodosMarkdown: importTodosMarkdown2 } = await Promise.resolve().then(() => (init_todos_md(), exports_todos_md));
|
|
17278
|
-
const result = importTodosMarkdown2(readFileSync7(
|
|
17827
|
+
const result = importTodosMarkdown2(readFileSync7(resolve12(file), "utf-8"), { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
|
|
17279
17828
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
17280
|
-
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 } });
|
|
17281
17830
|
if (globalOpts.json) {
|
|
17282
17831
|
output(result, true);
|
|
17283
17832
|
return;
|
|
@@ -17772,6 +18321,7 @@ function rowToAgent(row) {
|
|
|
17772
18321
|
}
|
|
17773
18322
|
function registerAgent(input, db) {
|
|
17774
18323
|
const d = db || getDatabase();
|
|
18324
|
+
const machineId = currentStorageMachineId(d);
|
|
17775
18325
|
const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
|
|
17776
18326
|
const normalizedName = validateAgentName(input.name, existingNames);
|
|
17777
18327
|
const existing = getAgentByName(normalizedName, d);
|
|
@@ -17812,14 +18362,18 @@ function registerAgent(input, db) {
|
|
|
17812
18362
|
updates.push("active_project_id = ?");
|
|
17813
18363
|
params.push(input.project_id);
|
|
17814
18364
|
}
|
|
18365
|
+
if (!existing.machine_id && machineId) {
|
|
18366
|
+
updates.push("machine_id = ?");
|
|
18367
|
+
params.push(machineId);
|
|
18368
|
+
}
|
|
17815
18369
|
params.push(existing.id);
|
|
17816
18370
|
d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
17817
18371
|
return getAgent(existing.id, d);
|
|
17818
18372
|
}
|
|
17819
18373
|
const id = shortUuid();
|
|
17820
18374
|
const timestamp = now();
|
|
17821
|
-
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)
|
|
17822
|
-
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
17823
18377
|
id,
|
|
17824
18378
|
normalizedName,
|
|
17825
18379
|
input.description || null,
|
|
@@ -17835,7 +18389,8 @@ function registerAgent(input, db) {
|
|
|
17835
18389
|
timestamp,
|
|
17836
18390
|
input.session_id || null,
|
|
17837
18391
|
input.working_dir || null,
|
|
17838
|
-
input.project_id && input.session_id ? input.project_id : null
|
|
18392
|
+
input.project_id && input.session_id ? input.project_id : null,
|
|
18393
|
+
machineId
|
|
17839
18394
|
]);
|
|
17840
18395
|
return getAgent(id, d);
|
|
17841
18396
|
}
|
|
@@ -18018,6 +18573,7 @@ function getCapableAgents(capabilities, opts, db) {
|
|
|
18018
18573
|
}
|
|
18019
18574
|
var init_agents = __esm(() => {
|
|
18020
18575
|
init_database();
|
|
18576
|
+
init_storage_tombstones();
|
|
18021
18577
|
init_agent_names();
|
|
18022
18578
|
});
|
|
18023
18579
|
|
|
@@ -19280,7 +19836,7 @@ __export(exports_local_extensions, {
|
|
|
19280
19836
|
});
|
|
19281
19837
|
import { createHash as createHash5, createVerify } from "crypto";
|
|
19282
19838
|
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
19283
|
-
import { basename as basename6, join as join11, resolve as
|
|
19839
|
+
import { basename as basename6, join as join11, resolve as resolve13 } from "path";
|
|
19284
19840
|
function isObject(value) {
|
|
19285
19841
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
19286
19842
|
}
|
|
@@ -19538,7 +20094,7 @@ function verifyExtensionSignature(input) {
|
|
|
19538
20094
|
return verifier.verify(input.public_key, decodeSignature(input.signature));
|
|
19539
20095
|
}
|
|
19540
20096
|
function inspectExtensionSource(source2) {
|
|
19541
|
-
const resolved =
|
|
20097
|
+
const resolved = resolve13(source2);
|
|
19542
20098
|
if (!existsSync13(resolved))
|
|
19543
20099
|
throw new Error(`extension source not found: ${source2}`);
|
|
19544
20100
|
const stat = statSync5(resolved);
|
|
@@ -19636,7 +20192,7 @@ function testExtensionCompatibility(sourceOrManifest) {
|
|
|
19636
20192
|
function projectExtensionSources(projectPath) {
|
|
19637
20193
|
if (!projectPath)
|
|
19638
20194
|
return [];
|
|
19639
|
-
const root =
|
|
20195
|
+
const root = resolve13(projectPath);
|
|
19640
20196
|
const candidates = [
|
|
19641
20197
|
join11(root, "todos.extension.json"),
|
|
19642
20198
|
join11(root, ".todos", "todos.extension.json")
|
|
@@ -19655,7 +20211,7 @@ function projectExtensionSources(projectPath) {
|
|
|
19655
20211
|
}
|
|
19656
20212
|
function discoverLocalExtensions(options = {}) {
|
|
19657
20213
|
const config = loadConfig();
|
|
19658
|
-
const projectPath = options.project_path ?
|
|
20214
|
+
const projectPath = options.project_path ? resolve13(options.project_path) : null;
|
|
19659
20215
|
const configuredSources = [
|
|
19660
20216
|
...config.extension_sources || [],
|
|
19661
20217
|
...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
|
|
@@ -19663,7 +20219,7 @@ function discoverLocalExtensions(options = {}) {
|
|
|
19663
20219
|
const sources = Array.from(new Set([
|
|
19664
20220
|
...configuredSources,
|
|
19665
20221
|
...projectExtensionSources(projectPath || undefined)
|
|
19666
|
-
])).map((source2) => projectPath && !source2.startsWith("/") ?
|
|
20222
|
+
])).map((source2) => projectPath && !source2.startsWith("/") ? resolve13(projectPath, source2) : resolve13(source2));
|
|
19667
20223
|
const warnings = [];
|
|
19668
20224
|
const discovered = [];
|
|
19669
20225
|
for (const source2 of sources) {
|
|
@@ -19923,9 +20479,9 @@ __export(exports_policy_packs, {
|
|
|
19923
20479
|
getPolicyPack: () => getPolicyPack,
|
|
19924
20480
|
explainPolicyPack: () => explainPolicyPack
|
|
19925
20481
|
});
|
|
19926
|
-
import { relative as relative4, resolve as
|
|
20482
|
+
import { relative as relative4, resolve as resolve14 } from "path";
|
|
19927
20483
|
function normalizePath3(path) {
|
|
19928
|
-
return
|
|
20484
|
+
return resolve14(path);
|
|
19929
20485
|
}
|
|
19930
20486
|
function unique5(values) {
|
|
19931
20487
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -19980,7 +20536,7 @@ function commandMatches(commands, pattern) {
|
|
|
19980
20536
|
}
|
|
19981
20537
|
function pathMatches(paths, pattern, root) {
|
|
19982
20538
|
return paths.filter((path) => {
|
|
19983
|
-
const candidate = path.startsWith("/") ? path :
|
|
20539
|
+
const candidate = path.startsWith("/") ? path : resolve14(root, path);
|
|
19984
20540
|
if (!isPathInside3(root, candidate))
|
|
19985
20541
|
return matchesPattern3(path, pattern);
|
|
19986
20542
|
return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
|
|
@@ -20337,7 +20893,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
|
|
|
20337
20893
|
if (action === "approved" || action === "rejected" || action === "expired") {
|
|
20338
20894
|
emitLocalEventHooksQuiet({
|
|
20339
20895
|
type: "approval.decided",
|
|
20340
|
-
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)
|
|
20341
20898
|
});
|
|
20342
20899
|
}
|
|
20343
20900
|
}
|
|
@@ -20483,6 +21040,7 @@ var init_approval_gates = __esm(() => {
|
|
|
20483
21040
|
init_task_runs();
|
|
20484
21041
|
init_tasks();
|
|
20485
21042
|
init_types();
|
|
21043
|
+
init_event_emission_safety();
|
|
20486
21044
|
init_event_hooks();
|
|
20487
21045
|
});
|
|
20488
21046
|
|
|
@@ -21334,7 +21892,7 @@ var init_doctor = __esm(() => {
|
|
|
21334
21892
|
});
|
|
21335
21893
|
|
|
21336
21894
|
// src/server/routes.ts
|
|
21337
|
-
import { join as join13, resolve as
|
|
21895
|
+
import { join as join13, resolve as resolve15, sep as sep2 } from "path";
|
|
21338
21896
|
function parseFieldsParam(url) {
|
|
21339
21897
|
const fieldsParam = url.searchParams.get("fields");
|
|
21340
21898
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -22072,9 +22630,9 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
22072
22630
|
return null;
|
|
22073
22631
|
if (path !== "/") {
|
|
22074
22632
|
const filePath = join13(ctx.dashboardDir, path);
|
|
22075
|
-
const resolvedFile =
|
|
22076
|
-
const resolvedBase =
|
|
22077
|
-
if (!resolvedFile.startsWith(resolvedBase +
|
|
22633
|
+
const resolvedFile = resolve15(filePath);
|
|
22634
|
+
const resolvedBase = resolve15(ctx.dashboardDir);
|
|
22635
|
+
if (!resolvedFile.startsWith(resolvedBase + sep2) && resolvedFile !== resolvedBase) {
|
|
22078
22636
|
return json2({ error: "Forbidden" }, 403);
|
|
22079
22637
|
}
|
|
22080
22638
|
const res2 = serveStaticFile2(filePath);
|
|
@@ -27077,7 +27635,7 @@ __export(exports_mention_resolver, {
|
|
|
27077
27635
|
resolveMentions: () => resolveMentions
|
|
27078
27636
|
});
|
|
27079
27637
|
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync7 } from "fs";
|
|
27080
|
-
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";
|
|
27081
27639
|
function blankResolution(parsed) {
|
|
27082
27640
|
return {
|
|
27083
27641
|
input: parsed.input,
|
|
@@ -27100,11 +27658,11 @@ function backlink(kind, key, label, target = key) {
|
|
|
27100
27658
|
return { kind, key, label, target };
|
|
27101
27659
|
}
|
|
27102
27660
|
function normalizeWorkspace(workspace) {
|
|
27103
|
-
return
|
|
27661
|
+
return resolve16(workspace || process.cwd());
|
|
27104
27662
|
}
|
|
27105
27663
|
function isInside(root, absolutePath) {
|
|
27106
27664
|
const rel = relative5(root, absolutePath);
|
|
27107
|
-
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${
|
|
27665
|
+
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep3}`) && !isAbsolute(rel);
|
|
27108
27666
|
}
|
|
27109
27667
|
function normalizeRelativePath(value) {
|
|
27110
27668
|
const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
|
|
@@ -27168,7 +27726,7 @@ function resolveFile(parsed, workspace) {
|
|
|
27168
27726
|
resolution.warnings.push("path is empty or escapes the workspace");
|
|
27169
27727
|
return resolution;
|
|
27170
27728
|
}
|
|
27171
|
-
const absolutePath =
|
|
27729
|
+
const absolutePath = resolve16(workspace, relPath);
|
|
27172
27730
|
if (!isInside(workspace, absolutePath)) {
|
|
27173
27731
|
resolution.path = relPath;
|
|
27174
27732
|
resolution.warnings.push("path escapes the workspace");
|
|
@@ -29695,7 +30253,7 @@ function canonicalize(value) {
|
|
|
29695
30253
|
function hash(value) {
|
|
29696
30254
|
return createHash7("sha256").update(value).digest("hex");
|
|
29697
30255
|
}
|
|
29698
|
-
function
|
|
30256
|
+
function parsePayload2(value) {
|
|
29699
30257
|
if (!value)
|
|
29700
30258
|
return {};
|
|
29701
30259
|
try {
|
|
@@ -29794,7 +30352,7 @@ function taskScopedRows(db, scope) {
|
|
|
29794
30352
|
FROM handoffs h
|
|
29795
30353
|
WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
|
|
29796
30354
|
`).all(scope.project_id ?? null).filter((row) => {
|
|
29797
|
-
const payload =
|
|
30355
|
+
const payload = parsePayload2(row.payload_json);
|
|
29798
30356
|
const taskRefs = parseStringArray(payload["task_ids"]);
|
|
29799
30357
|
const runRefs = parseStringArray(payload["run_ids"]);
|
|
29800
30358
|
if (scope.project_id && row.project_id !== scope.project_id)
|
|
@@ -29825,7 +30383,7 @@ function toLedgerEntries(rows) {
|
|
|
29825
30383
|
});
|
|
29826
30384
|
let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
|
|
29827
30385
|
return ordered.map((row, index) => {
|
|
29828
|
-
const payload =
|
|
30386
|
+
const payload = parsePayload2(row.payload_json);
|
|
29829
30387
|
const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
|
|
29830
30388
|
const chainHash = hash(`${previous}
|
|
29831
30389
|
${payloadHash}`);
|
|
@@ -29973,7 +30531,7 @@ __export(exports_release_compatibility, {
|
|
|
29973
30531
|
LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
|
|
29974
30532
|
});
|
|
29975
30533
|
import { readFileSync as readFileSync9 } from "fs";
|
|
29976
|
-
import { join as join15, resolve as
|
|
30534
|
+
import { join as join15, resolve as resolve17 } from "path";
|
|
29977
30535
|
import { Database as Database2 } from "bun:sqlite";
|
|
29978
30536
|
function pass(id, message, details) {
|
|
29979
30537
|
return { id, status: "passed", message, details };
|
|
@@ -30081,7 +30639,7 @@ function checkChangelog() {
|
|
|
30081
30639
|
];
|
|
30082
30640
|
}
|
|
30083
30641
|
function createReleaseCompatibilityReport(options = {}) {
|
|
30084
|
-
const root =
|
|
30642
|
+
const root = resolve17(options.root ?? process.cwd());
|
|
30085
30643
|
const packageJson = readPackageJson2(root);
|
|
30086
30644
|
const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
|
|
30087
30645
|
const checks = [
|
|
@@ -35761,7 +36319,7 @@ function classifyLog(text) {
|
|
|
35761
36319
|
async function sleep3(ms) {
|
|
35762
36320
|
if (ms <= 0)
|
|
35763
36321
|
return;
|
|
35764
|
-
await new Promise((
|
|
36322
|
+
await new Promise((resolve18) => setTimeout(resolve18, ms));
|
|
35765
36323
|
}
|
|
35766
36324
|
async function runCommandProvider(provider, input) {
|
|
35767
36325
|
const commandTemplate = input.command || provider.command;
|
|
@@ -37700,7 +38258,7 @@ function limitValue(value) {
|
|
|
37700
38258
|
return 20;
|
|
37701
38259
|
return Math.max(1, Math.min(500, Math.trunc(value)));
|
|
37702
38260
|
}
|
|
37703
|
-
function
|
|
38261
|
+
function isTerminal2(task) {
|
|
37704
38262
|
return task.status === "completed" || task.status === "failed" || task.status === "cancelled";
|
|
37705
38263
|
}
|
|
37706
38264
|
function sameAgent(task, agentId) {
|
|
@@ -37742,7 +38300,7 @@ function summarizeTask(task) {
|
|
|
37742
38300
|
function overdueTasks(tasks, nowIso) {
|
|
37743
38301
|
const now4 = Date.parse(nowIso);
|
|
37744
38302
|
return tasks.filter((task) => {
|
|
37745
|
-
if (
|
|
38303
|
+
if (isTerminal2(task) || !task.due_at)
|
|
37746
38304
|
return false;
|
|
37747
38305
|
const due = Date.parse(task.due_at);
|
|
37748
38306
|
return Number.isFinite(due) && due < now4;
|
|
@@ -38111,7 +38669,7 @@ __export(exports_local_backups, {
|
|
|
38111
38669
|
});
|
|
38112
38670
|
import { createHash as createHash8 } from "crypto";
|
|
38113
38671
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
38114
|
-
import { dirname as dirname8, resolve as
|
|
38672
|
+
import { dirname as dirname8, resolve as resolve18 } from "path";
|
|
38115
38673
|
import { mkdirSync as mkdirSync7 } from "fs";
|
|
38116
38674
|
function stableJson(value) {
|
|
38117
38675
|
if (value === null || typeof value !== "object")
|
|
@@ -38213,14 +38771,14 @@ function createLocalBackup(options = {}, db) {
|
|
|
38213
38771
|
return backup;
|
|
38214
38772
|
}
|
|
38215
38773
|
function writeLocalBackupFile(backup, outputPath) {
|
|
38216
|
-
const path =
|
|
38774
|
+
const path = resolve18(outputPath);
|
|
38217
38775
|
mkdirSync7(dirname8(path), { recursive: true });
|
|
38218
38776
|
writeFileSync5(path, `${JSON.stringify(backup, null, 2)}
|
|
38219
38777
|
`);
|
|
38220
38778
|
return path;
|
|
38221
38779
|
}
|
|
38222
38780
|
function readLocalBackupFile(path) {
|
|
38223
|
-
return JSON.parse(readFileSync11(
|
|
38781
|
+
return JSON.parse(readFileSync11(resolve18(path), "utf-8"));
|
|
38224
38782
|
}
|
|
38225
38783
|
function verifyLocalBackup(value, options = {}, db) {
|
|
38226
38784
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -42516,7 +43074,8 @@ function writeQueue(task2, queue, actor, action, db) {
|
|
|
42516
43074
|
logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
|
|
42517
43075
|
emitLocalEventHooksQuiet({
|
|
42518
43076
|
type: `review.${action}`,
|
|
42519
|
-
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)
|
|
42520
43079
|
});
|
|
42521
43080
|
return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
|
|
42522
43081
|
}
|
|
@@ -42820,6 +43379,7 @@ var init_review_queues = __esm(() => {
|
|
|
42820
43379
|
init_database();
|
|
42821
43380
|
init_tasks();
|
|
42822
43381
|
init_config();
|
|
43382
|
+
init_event_emission_safety();
|
|
42823
43383
|
init_event_hooks();
|
|
42824
43384
|
init_task_contracts();
|
|
42825
43385
|
STATES = new Set(["requested", "claimed", "approved", "changes_requested", "returned", "reopened"]);
|
|
@@ -44918,8 +45478,8 @@ __export(exports_environment_snapshots, {
|
|
|
44918
45478
|
import { createHash as createHash12 } from "crypto";
|
|
44919
45479
|
import { existsSync as existsSync17, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
44920
45480
|
import { hostname as hostname2, platform, arch } from "os";
|
|
44921
|
-
import { dirname as dirname9, join as join17, resolve as
|
|
44922
|
-
import { tmpdir as
|
|
45481
|
+
import { dirname as dirname9, join as join17, resolve as resolve19 } from "path";
|
|
45482
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
44923
45483
|
function sha2566(value) {
|
|
44924
45484
|
return createHash12("sha256").update(value).digest("hex");
|
|
44925
45485
|
}
|
|
@@ -45032,15 +45592,15 @@ function commandEnv(env, includeValues) {
|
|
|
45032
45592
|
function defaultSnapshotDir() {
|
|
45033
45593
|
const dbPath = getDatabasePath();
|
|
45034
45594
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
45035
|
-
return join17(
|
|
45036
|
-
return join17(dirname9(
|
|
45595
|
+
return join17(tmpdir3(), "hasna-todos", "environment-snapshots");
|
|
45596
|
+
return join17(dirname9(resolve19(dbPath)), "environment-snapshots");
|
|
45037
45597
|
}
|
|
45038
45598
|
function snapshotWithId(snapshot) {
|
|
45039
45599
|
const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
|
|
45040
45600
|
return { id: `env_${digest}`, ...snapshot };
|
|
45041
45601
|
}
|
|
45042
45602
|
function captureEnvironmentSnapshot(input = {}) {
|
|
45043
|
-
const root =
|
|
45603
|
+
const root = resolve19(input.root || process.cwd());
|
|
45044
45604
|
const env = input.env || process.env;
|
|
45045
45605
|
const warnings = [];
|
|
45046
45606
|
const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
|
|
@@ -45080,13 +45640,13 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
45080
45640
|
});
|
|
45081
45641
|
}
|
|
45082
45642
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
45083
|
-
const path = outputPath ?
|
|
45643
|
+
const path = outputPath ? resolve19(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
45084
45644
|
ensureDir2(dirname9(path));
|
|
45085
45645
|
writeJsonFile(path, snapshot);
|
|
45086
45646
|
return path;
|
|
45087
45647
|
}
|
|
45088
45648
|
function readEnvironmentSnapshot(path) {
|
|
45089
|
-
const snapshot = readJsonFile(
|
|
45649
|
+
const snapshot = readJsonFile(resolve19(path));
|
|
45090
45650
|
if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
|
|
45091
45651
|
throw new Error(`Invalid environment snapshot: ${path}`);
|
|
45092
45652
|
}
|
|
@@ -50321,7 +50881,8 @@ Repairs`));
|
|
|
50321
50881
|
});
|
|
50322
50882
|
const limited2 = ready.slice(0, parseInt(opts.limit, 10));
|
|
50323
50883
|
if (opts.json || globalOpts.json) {
|
|
50324
|
-
|
|
50884
|
+
const { getTaskRouteState: getTaskRouteState2 } = await Promise.resolve().then(() => (init_task_routing(), exports_task_routing));
|
|
50885
|
+
console.log(JSON.stringify(limited2.map((task2) => ({ ...task2, route_state: getTaskRouteState2(task2, db) }))));
|
|
50325
50886
|
return;
|
|
50326
50887
|
}
|
|
50327
50888
|
if (limited2.length === 0) {
|
|
@@ -52936,7 +53497,7 @@ __export(exports_machines, {
|
|
|
52936
53497
|
import chalk10 from "chalk";
|
|
52937
53498
|
import { execSync as execSync4 } from "child_process";
|
|
52938
53499
|
import { readFileSync as readFileSync17, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
52939
|
-
import { tmpdir as
|
|
53500
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
52940
53501
|
import { join as join21 } from "path";
|
|
52941
53502
|
function getOrCreateLocalMachineName() {
|
|
52942
53503
|
return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
|
|
@@ -52975,7 +53536,7 @@ function remoteTempPath(sshAddress) {
|
|
|
52975
53536
|
}
|
|
52976
53537
|
function readRemoteBridgeBundle(sshAddress) {
|
|
52977
53538
|
const remotePath = remoteTempPath(sshAddress);
|
|
52978
|
-
const localPath = join21(
|
|
53539
|
+
const localPath = join21(tmpdir4(), `todos-bridge-pull-${uuid()}.json`);
|
|
52979
53540
|
try {
|
|
52980
53541
|
runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
|
|
52981
53542
|
scpFromRemote(sshAddress, remotePath, localPath);
|
|
@@ -52990,7 +53551,7 @@ function readRemoteBridgeBundle(sshAddress) {
|
|
|
52990
53551
|
}
|
|
52991
53552
|
}
|
|
52992
53553
|
function writeLocalBridgeBundle() {
|
|
52993
|
-
const localPath = join21(
|
|
53554
|
+
const localPath = join21(tmpdir4(), `todos-bridge-push-${uuid()}.json`);
|
|
52994
53555
|
writeFileSync10(localPath, JSON.stringify(createLocalBridgeBundle(), null, 2));
|
|
52995
53556
|
return localPath;
|
|
52996
53557
|
}
|
|
@@ -54054,7 +54615,7 @@ __export(exports_onboarding_commands, {
|
|
|
54054
54615
|
registerOnboardingCommands: () => registerOnboardingCommands
|
|
54055
54616
|
});
|
|
54056
54617
|
import chalk17 from "chalk";
|
|
54057
|
-
import { resolve as
|
|
54618
|
+
import { resolve as resolve20 } from "path";
|
|
54058
54619
|
function registerOnboardingCommands(program2) {
|
|
54059
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) => {
|
|
54060
54621
|
const globalOpts = program2.opts();
|
|
@@ -54070,7 +54631,7 @@ function registerOnboardingCommands(program2) {
|
|
|
54070
54631
|
return;
|
|
54071
54632
|
}
|
|
54072
54633
|
if (opts.write) {
|
|
54073
|
-
const result = writeOnboardingFixtureFiles2(
|
|
54634
|
+
const result = writeOnboardingFixtureFiles2(resolve20(opts.write));
|
|
54074
54635
|
if (globalOpts.json) {
|
|
54075
54636
|
output(result, true);
|
|
54076
54637
|
return;
|
|
@@ -57650,7 +58211,7 @@ __export(exports_sdk_fixture_commands, {
|
|
|
57650
58211
|
registerSdkFixtureCommands: () => registerSdkFixtureCommands
|
|
57651
58212
|
});
|
|
57652
58213
|
import chalk19 from "chalk";
|
|
57653
|
-
import { resolve as
|
|
58214
|
+
import { resolve as resolve21 } from "path";
|
|
57654
58215
|
function registerSdkFixtureCommands(program2) {
|
|
57655
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) => {
|
|
57656
58217
|
const globalOpts = program2.opts();
|
|
@@ -57661,7 +58222,7 @@ function registerSdkFixtureCommands(program2) {
|
|
|
57661
58222
|
writeSdkIntegrationFixtures: writeSdkIntegrationFixtures2
|
|
57662
58223
|
} = await Promise.resolve().then(() => (init_sdk_integration_fixtures(), exports_sdk_integration_fixtures));
|
|
57663
58224
|
if (opts.write) {
|
|
57664
|
-
const result = writeSdkIntegrationFixtures2(
|
|
58225
|
+
const result = writeSdkIntegrationFixtures2(resolve21(opts.write));
|
|
57665
58226
|
if (globalOpts.json) {
|
|
57666
58227
|
console.log(JSON.stringify(result));
|
|
57667
58228
|
return;
|
|
@@ -58483,7 +59044,7 @@ __export(exports_local_backup_commands, {
|
|
|
58483
59044
|
registerLocalBackupCommands: () => registerLocalBackupCommands
|
|
58484
59045
|
});
|
|
58485
59046
|
import chalk26 from "chalk";
|
|
58486
|
-
import { resolve as
|
|
59047
|
+
import { resolve as resolve22 } from "path";
|
|
58487
59048
|
function globalOptions6(program2) {
|
|
58488
59049
|
const command = program2;
|
|
58489
59050
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
@@ -58505,10 +59066,10 @@ function registerLocalBackupCommands(program2) {
|
|
|
58505
59066
|
const projectId = opts.projectId ?? autoProject(globalOpts);
|
|
58506
59067
|
const backupBundle = createLocalBackup2({
|
|
58507
59068
|
project_id: projectId,
|
|
58508
|
-
output_path: opts.output ?
|
|
59069
|
+
output_path: opts.output ? resolve22(opts.output) : undefined
|
|
58509
59070
|
});
|
|
58510
59071
|
const result = {
|
|
58511
|
-
output_path: opts.output ?
|
|
59072
|
+
output_path: opts.output ? resolve22(opts.output) : null,
|
|
58512
59073
|
backup: backupBundle
|
|
58513
59074
|
};
|
|
58514
59075
|
if (opts.json || globalOpts.json) {
|
|
@@ -58767,11 +59328,13 @@ function exportSqliteTodosStorageSnapshot(db) {
|
|
|
58767
59328
|
source: "sqlite",
|
|
58768
59329
|
tasks: listTasks({ include_archived: true }, d),
|
|
58769
59330
|
projects: listProjects(d),
|
|
59331
|
+
projectMachinePaths: listProjectMachinePaths(d),
|
|
58770
59332
|
plans: listPlans(undefined, d),
|
|
58771
59333
|
agents: listAgents({ include_archived: true }, d),
|
|
58772
59334
|
taskLists: listTaskLists(undefined, d),
|
|
58773
59335
|
templates: listTemplates(d),
|
|
58774
|
-
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d)
|
|
59336
|
+
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d),
|
|
59337
|
+
tombstones: listStorageTombstones(d)
|
|
58775
59338
|
};
|
|
58776
59339
|
}
|
|
58777
59340
|
function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
@@ -58779,13 +59342,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
58779
59342
|
const result = {
|
|
58780
59343
|
inserted: 0,
|
|
58781
59344
|
updated: 0,
|
|
59345
|
+
deleted: 0,
|
|
58782
59346
|
skipped: 0,
|
|
58783
59347
|
errors: []
|
|
58784
59348
|
};
|
|
58785
|
-
const applyRows = (table, columns, rows, updateClockColumn, afterUpsert) => {
|
|
59349
|
+
const applyRows = (objectType2, table, columns, rows, updateClockColumn, afterUpsert) => {
|
|
58786
59350
|
for (const row of rows) {
|
|
58787
59351
|
try {
|
|
58788
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
|
+
}
|
|
58789
59358
|
const state = upsertById(d, table, columns, record, updateClockColumn);
|
|
58790
59359
|
if (state === "inserted")
|
|
58791
59360
|
result.inserted += 1;
|
|
@@ -58799,17 +59368,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
58799
59368
|
}
|
|
58800
59369
|
}
|
|
58801
59370
|
};
|
|
58802
|
-
applyRows("projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
|
|
58803
|
-
applyRows("
|
|
58804
|
-
applyRows("
|
|
58805
|
-
applyRows("
|
|
58806
|
-
applyRows("
|
|
58807
|
-
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) => {
|
|
58808
59378
|
if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
|
|
58809
59379
|
replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
|
|
58810
59380
|
}
|
|
58811
59381
|
});
|
|
58812
|
-
applyRows("task_history", AUDIT_COLUMNS, snapshot.auditHistory);
|
|
59382
|
+
applyRows("audit_history", "task_history", AUDIT_COLUMNS, snapshot.auditHistory);
|
|
59383
|
+
applyTombstones(d, snapshot.tombstones ?? [], result);
|
|
58813
59384
|
return result;
|
|
58814
59385
|
}
|
|
58815
59386
|
function upsertById(db, table, columns, row, updateClockColumn) {
|
|
@@ -58864,7 +59435,86 @@ function sortedTasks2(tasks) {
|
|
|
58864
59435
|
visit(task2);
|
|
58865
59436
|
return result;
|
|
58866
59437
|
}
|
|
58867
|
-
|
|
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;
|
|
58868
59518
|
var init_sqlite_snapshot = __esm(() => {
|
|
58869
59519
|
init_database();
|
|
58870
59520
|
init_agents();
|
|
@@ -58874,6 +59524,7 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
58874
59524
|
init_task_lists();
|
|
58875
59525
|
init_tasks();
|
|
58876
59526
|
init_templates();
|
|
59527
|
+
init_storage_tombstones();
|
|
58877
59528
|
PROJECT_COLUMNS = [
|
|
58878
59529
|
"id",
|
|
58879
59530
|
"name",
|
|
@@ -58887,6 +59538,14 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
58887
59538
|
"machine_id",
|
|
58888
59539
|
"synced_at"
|
|
58889
59540
|
];
|
|
59541
|
+
PROJECT_MACHINE_PATH_COLUMNS = [
|
|
59542
|
+
"id",
|
|
59543
|
+
"project_id",
|
|
59544
|
+
"machine_id",
|
|
59545
|
+
"path",
|
|
59546
|
+
"created_at",
|
|
59547
|
+
"updated_at"
|
|
59548
|
+
];
|
|
58890
59549
|
TASK_LIST_COLUMNS = [
|
|
58891
59550
|
"id",
|
|
58892
59551
|
"project_id",
|
|
@@ -59196,7 +59855,7 @@ class PostgresTodosSyncStore {
|
|
|
59196
59855
|
}
|
|
59197
59856
|
async pullSnapshot(options = {}) {
|
|
59198
59857
|
const params = [this.service];
|
|
59199
|
-
const filters = ["service = $1"
|
|
59858
|
+
const filters = ["service = $1"];
|
|
59200
59859
|
if (options.since) {
|
|
59201
59860
|
params.push(options.since);
|
|
59202
59861
|
filters.push(`updated_at > $${params.length}::timestamptz`);
|
|
@@ -59205,7 +59864,7 @@ class PostgresTodosSyncStore {
|
|
|
59205
59864
|
params.push(options.objectTypes);
|
|
59206
59865
|
filters.push(`object_type = ANY($${params.length}::text[])`);
|
|
59207
59866
|
}
|
|
59208
|
-
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}
|
|
59209
59868
|
WHERE ${filters.join(" AND ")}
|
|
59210
59869
|
ORDER BY updated_at ASC, object_type ASC, object_id ASC`, params);
|
|
59211
59870
|
return rowsToSnapshot(response.rows);
|
|
@@ -59229,11 +59888,20 @@ function snapshotEntries(snapshot) {
|
|
|
59229
59888
|
return [
|
|
59230
59889
|
...snapshot.tasks.map((payload) => entry("tasks", payload, snapshot.exportedAt)),
|
|
59231
59890
|
...snapshot.projects.map((payload) => entry("projects", payload, snapshot.exportedAt)),
|
|
59891
|
+
...(snapshot.projectMachinePaths ?? []).map((payload) => entry("project_machine_paths", payload, snapshot.exportedAt)),
|
|
59232
59892
|
...snapshot.plans.map((payload) => entry("plans", payload, snapshot.exportedAt)),
|
|
59233
59893
|
...snapshot.agents.map((payload) => entry("agents", payload, snapshot.exportedAt)),
|
|
59234
59894
|
...snapshot.taskLists.map((payload) => entry("task_lists", payload, snapshot.exportedAt)),
|
|
59235
59895
|
...snapshot.templates.map((payload) => entry("templates", payload, snapshot.exportedAt)),
|
|
59236
|
-
...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
|
+
}))
|
|
59237
59905
|
];
|
|
59238
59906
|
}
|
|
59239
59907
|
function entry(type, payload, fallbackUpdatedAt) {
|
|
@@ -59255,19 +59923,38 @@ function rowsToSnapshot(rows) {
|
|
|
59255
59923
|
source: "postgres",
|
|
59256
59924
|
tasks: [],
|
|
59257
59925
|
projects: [],
|
|
59926
|
+
projectMachinePaths: [],
|
|
59258
59927
|
plans: [],
|
|
59259
59928
|
agents: [],
|
|
59260
59929
|
taskLists: [],
|
|
59261
59930
|
templates: [],
|
|
59262
|
-
auditHistory: []
|
|
59931
|
+
auditHistory: [],
|
|
59932
|
+
tombstones: []
|
|
59263
59933
|
};
|
|
59264
59934
|
for (const row of rows) {
|
|
59265
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
|
+
}
|
|
59266
59950
|
if (row.object_type === "tasks")
|
|
59267
59951
|
snapshot.tasks.push(payload);
|
|
59268
59952
|
else if (row.object_type === "projects")
|
|
59269
59953
|
snapshot.projects.push(payload);
|
|
59270
|
-
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")
|
|
59271
59958
|
snapshot.plans.push(payload);
|
|
59272
59959
|
else if (row.object_type === "agents")
|
|
59273
59960
|
snapshot.agents.push(payload);
|
|
@@ -59288,6 +59975,8 @@ function payloadRecord(value) {
|
|
|
59288
59975
|
throw new Error("Postgres sync payload must be a JSON object");
|
|
59289
59976
|
}
|
|
59290
59977
|
function stringValue(value) {
|
|
59978
|
+
if (value instanceof Date)
|
|
59979
|
+
return value.toISOString();
|
|
59291
59980
|
return typeof value === "string" && value ? value : null;
|
|
59292
59981
|
}
|
|
59293
59982
|
function numberValue2(value) {
|
|
@@ -59446,6 +60135,9 @@ class PostgresJsonRecordStore {
|
|
|
59446
60135
|
this.tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
|
|
59447
60136
|
this.cursorTableName = options.cursorTableName ?? DEFAULT_TODOS_POSTGRES_CURSOR_TABLE;
|
|
59448
60137
|
}
|
|
60138
|
+
machineId(context) {
|
|
60139
|
+
return context?.requestId ?? this.sourceMachineId ?? null;
|
|
60140
|
+
}
|
|
59449
60141
|
async ensureSchema() {
|
|
59450
60142
|
this.schemaReady ??= (async () => {
|
|
59451
60143
|
for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
|
|
@@ -59477,6 +60169,25 @@ class PostgresJsonRecordStore {
|
|
|
59477
60169
|
updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString()
|
|
59478
60170
|
}));
|
|
59479
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
|
+
}
|
|
59480
60191
|
async upsert(type, value, context = {}) {
|
|
59481
60192
|
await this.ensureSchema();
|
|
59482
60193
|
const updatedAt = stringValue2(value.updated_at) ?? stringValue2(value.created_at) ?? new Date().toISOString();
|
|
@@ -59489,7 +60200,8 @@ class PostgresJsonRecordStore {
|
|
|
59489
60200
|
updated_at = EXCLUDED.updated_at,
|
|
59490
60201
|
deleted_at = NULL,
|
|
59491
60202
|
source_machine_id = EXCLUDED.source_machine_id,
|
|
59492
|
-
version = EXCLUDED.version
|
|
60203
|
+
version = EXCLUDED.version
|
|
60204
|
+
WHERE ${this.tableName}.updated_at IS NULL OR ${this.tableName}.updated_at <= EXCLUDED.updated_at`, [
|
|
59493
60205
|
this.service,
|
|
59494
60206
|
type,
|
|
59495
60207
|
value.id,
|
|
@@ -59506,13 +60218,58 @@ class PostgresJsonRecordStore {
|
|
|
59506
60218
|
if (!existing)
|
|
59507
60219
|
return false;
|
|
59508
60220
|
const timestamp3 = new Date().toISOString();
|
|
59509
|
-
|
|
59510
|
-
|
|
59511
|
-
|
|
59512
|
-
|
|
59513
|
-
|
|
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
|
+
]);
|
|
59514
60257
|
return true;
|
|
59515
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
|
+
}
|
|
59516
60273
|
async getCursor(name) {
|
|
59517
60274
|
await this.ensureSchema();
|
|
59518
60275
|
const result = await this.options.client.query(`SELECT value FROM ${this.cursorTableName} WHERE service = $1 AND cursor_name = $2`, [this.service, name]);
|
|
@@ -59581,7 +60338,10 @@ async function createTask3(input, store, context) {
|
|
|
59581
60338
|
runner_started_at: null,
|
|
59582
60339
|
runner_completed_at: null,
|
|
59583
60340
|
current_step: null,
|
|
59584
|
-
total_steps: null
|
|
60341
|
+
total_steps: null,
|
|
60342
|
+
machine_id: store.machineId(context),
|
|
60343
|
+
synced_at: null,
|
|
60344
|
+
archived_at: null
|
|
59585
60345
|
};
|
|
59586
60346
|
await store.upsert("tasks", task2, context);
|
|
59587
60347
|
await logTaskChange2(task2.id, "created", "status", null, task2.status, task2.assigned_by ?? task2.agent_id, store, context);
|
|
@@ -59706,7 +60466,9 @@ async function createProject2(input, store, context) {
|
|
|
59706
60466
|
task_prefix: input.task_prefix ?? await generateProjectPrefix(input.name, store),
|
|
59707
60467
|
task_counter: 0,
|
|
59708
60468
|
created_at: timestamp3,
|
|
59709
|
-
updated_at: timestamp3
|
|
60469
|
+
updated_at: timestamp3,
|
|
60470
|
+
machine_id: store.machineId(context),
|
|
60471
|
+
synced_at: null
|
|
59710
60472
|
};
|
|
59711
60473
|
return store.upsert("projects", project, context);
|
|
59712
60474
|
}
|
|
@@ -59726,7 +60488,9 @@ async function createPlan2(input, store, context) {
|
|
|
59726
60488
|
description: input.description ?? null,
|
|
59727
60489
|
status: input.status ?? "active",
|
|
59728
60490
|
created_at: timestamp3,
|
|
59729
|
-
updated_at: timestamp3
|
|
60491
|
+
updated_at: timestamp3,
|
|
60492
|
+
machine_id: store.machineId(context),
|
|
60493
|
+
synced_at: null
|
|
59730
60494
|
}, context);
|
|
59731
60495
|
}
|
|
59732
60496
|
async function updatePlan2(id, input, store) {
|
|
@@ -59756,7 +60520,9 @@ async function registerAgent2(input, store, context) {
|
|
|
59756
60520
|
last_seen_at: timestamp3,
|
|
59757
60521
|
session_id: input.session_id ?? context?.sessionId ?? existing?.session_id ?? null,
|
|
59758
60522
|
working_dir: input.working_dir ?? existing?.working_dir ?? null,
|
|
59759
|
-
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
|
|
59760
60526
|
};
|
|
59761
60527
|
return store.upsert("agents", agent, context);
|
|
59762
60528
|
}
|
|
@@ -59783,7 +60549,9 @@ async function createTaskList2(input, store, context) {
|
|
|
59783
60549
|
description: input.description ?? null,
|
|
59784
60550
|
metadata: input.metadata ?? {},
|
|
59785
60551
|
created_at: timestamp3,
|
|
59786
|
-
updated_at: timestamp3
|
|
60552
|
+
updated_at: timestamp3,
|
|
60553
|
+
machine_id: store.machineId(context),
|
|
60554
|
+
synced_at: null
|
|
59787
60555
|
}, context);
|
|
59788
60556
|
}
|
|
59789
60557
|
async function updateTaskList2(id, input, store) {
|
|
@@ -59809,7 +60577,9 @@ async function createTemplate2(input, store, context) {
|
|
|
59809
60577
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
59810
60578
|
plan_id: input.plan_id ?? null,
|
|
59811
60579
|
metadata: input.metadata ?? {},
|
|
59812
|
-
created_at: timestamp3
|
|
60580
|
+
created_at: timestamp3,
|
|
60581
|
+
machine_id: store.machineId(context),
|
|
60582
|
+
synced_at: null
|
|
59813
60583
|
}, context);
|
|
59814
60584
|
}
|
|
59815
60585
|
async function updateTemplate2(id, input, store) {
|
|
@@ -59834,7 +60604,8 @@ async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentI
|
|
|
59834
60604
|
old_value: oldValue ?? null,
|
|
59835
60605
|
new_value: newValue ?? null,
|
|
59836
60606
|
agent_id: agentId ?? context?.agentId ?? null,
|
|
59837
|
-
created_at: new Date().toISOString()
|
|
60607
|
+
created_at: new Date().toISOString(),
|
|
60608
|
+
machine_id: store.machineId(context)
|
|
59838
60609
|
};
|
|
59839
60610
|
return store.upsert("audit_history", entry2, context);
|
|
59840
60611
|
}
|
|
@@ -59857,18 +60628,21 @@ async function exportSnapshot(store) {
|
|
|
59857
60628
|
source: "postgres",
|
|
59858
60629
|
tasks: await store.list("tasks"),
|
|
59859
60630
|
projects: await store.list("projects"),
|
|
60631
|
+
projectMachinePaths: await store.list("project_machine_paths"),
|
|
59860
60632
|
plans: await store.list("plans"),
|
|
59861
60633
|
agents: await store.list("agents"),
|
|
59862
60634
|
taskLists: await store.list("task_lists"),
|
|
59863
60635
|
templates: await store.list("templates"),
|
|
59864
|
-
auditHistory: await store.list("audit_history")
|
|
60636
|
+
auditHistory: await store.list("audit_history"),
|
|
60637
|
+
tombstones: await store.listTombstones()
|
|
59865
60638
|
};
|
|
59866
60639
|
}
|
|
59867
60640
|
async function importSnapshot(snapshot, store, context) {
|
|
59868
|
-
const result = { inserted: 0, updated: 0, skipped: 0, errors: [] };
|
|
60641
|
+
const result = { inserted: 0, updated: 0, deleted: 0, skipped: 0, errors: [] };
|
|
59869
60642
|
const entries = [
|
|
59870
60643
|
...snapshot.tasks.map((row) => ["tasks", row]),
|
|
59871
60644
|
...snapshot.projects.map((row) => ["projects", row]),
|
|
60645
|
+
...(snapshot.projectMachinePaths ?? []).map((row) => ["project_machine_paths", row]),
|
|
59872
60646
|
...snapshot.plans.map((row) => ["plans", row]),
|
|
59873
60647
|
...snapshot.agents.map((row) => ["agents", row]),
|
|
59874
60648
|
...snapshot.taskLists.map((row) => ["task_lists", row]),
|
|
@@ -59887,6 +60661,25 @@ async function importSnapshot(snapshot, store, context) {
|
|
|
59887
60661
|
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
59888
60662
|
}
|
|
59889
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
|
+
}
|
|
59890
60683
|
return result;
|
|
59891
60684
|
}
|
|
59892
60685
|
async function requireRecord(type, id, store) {
|
|
@@ -59969,8 +60762,17 @@ function payloadRecord2(value) {
|
|
|
59969
60762
|
throw new Error("Postgres storage payload must be a JSON object");
|
|
59970
60763
|
}
|
|
59971
60764
|
function stringValue2(value) {
|
|
60765
|
+
if (value instanceof Date)
|
|
60766
|
+
return value.toISOString();
|
|
59972
60767
|
return typeof value === "string" && value ? value : null;
|
|
59973
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
|
+
}
|
|
59974
60776
|
function numberValue3(value) {
|
|
59975
60777
|
return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
|
|
59976
60778
|
}
|
|
@@ -60644,6 +61446,7 @@ var init_native_storage_status = __esm(() => {
|
|
|
60644
61446
|
// src/cli/commands/storage-commands.ts
|
|
60645
61447
|
var exports_storage_commands = {};
|
|
60646
61448
|
__export(exports_storage_commands, {
|
|
61449
|
+
s3CredentialsFromEnv: () => s3CredentialsFromEnv,
|
|
60647
61450
|
registerStorageCommands: () => registerStorageCommands
|
|
60648
61451
|
});
|
|
60649
61452
|
import chalk27 from "chalk";
|
|
@@ -60722,13 +61525,13 @@ function artifactFilter(opts) {
|
|
|
60722
61525
|
...opts.includeAlreadySynced ? { includeAlreadySynced: true } : {}
|
|
60723
61526
|
};
|
|
60724
61527
|
}
|
|
60725
|
-
function s3CredentialsFromEnv() {
|
|
60726
|
-
const { TODOS_STORAGE_ENV: TODOS_STORAGE_ENV2 } = (init_storage(), __toCommonJS(exports_storage));
|
|
60727
|
-
const accessKeyId =
|
|
60728
|
-
const secretAccessKey =
|
|
60729
|
-
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();
|
|
60730
61533
|
if (!accessKeyId || !secretAccessKey) {
|
|
60731
|
-
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`);
|
|
60732
61535
|
}
|
|
60733
61536
|
return {
|
|
60734
61537
|
accessKeyId,
|