@hasna/todos 0.11.66 → 0.11.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dashboard/dist/assets/{index-aJefI7kh.js → index-DVotjwab.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/cli/commands/storage-commands.d.ts +5 -0
- package/dist/cli/commands/storage-commands.d.ts.map +1 -1
- package/dist/cli/index.js +667 -169
- package/dist/contracts.js +301 -71
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/plans.d.ts.map +1 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/storage-tombstones.d.ts +26 -0
- package/dist/db/storage-tombstones.d.ts.map +1 -0
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/task-lists.d.ts.map +1 -1
- package/dist/db/task-runs.d.ts.map +1 -1
- package/dist/db/templates.d.ts.map +1 -1
- package/dist/index.js +628 -160
- package/dist/lib/approval-gates.d.ts.map +1 -1
- package/dist/lib/event-emission-safety.d.ts +9 -0
- package/dist/lib/event-emission-safety.d.ts.map +1 -0
- package/dist/lib/event-hooks.d.ts +1 -0
- package/dist/lib/event-hooks.d.ts.map +1 -1
- package/dist/lib/feature-manifest.d.ts.map +1 -1
- package/dist/lib/review-queues.d.ts.map +1 -1
- package/dist/lib/shared-events.d.ts +1 -0
- package/dist/lib/shared-events.d.ts.map +1 -1
- package/dist/mcp/index.js +343 -104
- package/dist/registry.js +301 -71
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +366 -127
- package/dist/storage/interfaces.d.ts +20 -0
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/postgres-sync.d.ts +6 -1
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
- package/dist/storage.js +569 -94
- package/dist/types/index.d.ts +22 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -1183,6 +1183,22 @@ var init_migrations = __esm(() => {
|
|
|
1183
1183
|
CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source);
|
|
1184
1184
|
CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint);
|
|
1185
1185
|
INSERT OR IGNORE INTO _migrations (id) VALUES (62);
|
|
1186
|
+
`,
|
|
1187
|
+
`
|
|
1188
|
+
CREATE TABLE IF NOT EXISTS storage_tombstones (
|
|
1189
|
+
id TEXT PRIMARY KEY,
|
|
1190
|
+
object_type TEXT NOT NULL,
|
|
1191
|
+
object_id TEXT NOT NULL,
|
|
1192
|
+
deleted_at TEXT NOT NULL,
|
|
1193
|
+
updated_at TEXT NOT NULL,
|
|
1194
|
+
source_machine_id TEXT,
|
|
1195
|
+
payload TEXT,
|
|
1196
|
+
version INTEGER,
|
|
1197
|
+
UNIQUE(object_type, object_id)
|
|
1198
|
+
);
|
|
1199
|
+
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id);
|
|
1200
|
+
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at);
|
|
1201
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (63);
|
|
1186
1202
|
`
|
|
1187
1203
|
];
|
|
1188
1204
|
});
|
|
@@ -1704,6 +1720,20 @@ function ensureSchema(db) {
|
|
|
1704
1720
|
)`);
|
|
1705
1721
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
|
|
1706
1722
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
|
|
1723
|
+
ensureTable("storage_tombstones", `
|
|
1724
|
+
CREATE TABLE storage_tombstones (
|
|
1725
|
+
id TEXT PRIMARY KEY,
|
|
1726
|
+
object_type TEXT NOT NULL,
|
|
1727
|
+
object_id TEXT NOT NULL,
|
|
1728
|
+
deleted_at TEXT NOT NULL,
|
|
1729
|
+
updated_at TEXT NOT NULL,
|
|
1730
|
+
source_machine_id TEXT,
|
|
1731
|
+
payload TEXT,
|
|
1732
|
+
version INTEGER,
|
|
1733
|
+
UNIQUE(object_type, object_id)
|
|
1734
|
+
)`);
|
|
1735
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id)");
|
|
1736
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at)");
|
|
1707
1737
|
ensureTable("machines", `
|
|
1708
1738
|
CREATE TABLE machines (
|
|
1709
1739
|
id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
|
|
@@ -2765,6 +2795,65 @@ var init_database = __esm(() => {
|
|
|
2765
2795
|
ALLOWED_TABLES = new Set(["tasks", "projects", "agents", "plans", "task_lists", "task_templates", "project_knowledge_records", "project_risks", "local_retrospectives"]);
|
|
2766
2796
|
});
|
|
2767
2797
|
|
|
2798
|
+
// src/db/storage-tombstones.ts
|
|
2799
|
+
function recordStorageTombstone(input, db) {
|
|
2800
|
+
const d = db ?? getDatabase();
|
|
2801
|
+
const deletedAt = input.deleted_at ?? now();
|
|
2802
|
+
const machineId = input.source_machine_id ?? currentStorageMachineId(d);
|
|
2803
|
+
d.run(`INSERT INTO storage_tombstones (
|
|
2804
|
+
id, object_type, object_id, deleted_at, updated_at, source_machine_id, payload, version
|
|
2805
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
2806
|
+
ON CONFLICT(object_type, object_id) DO UPDATE SET
|
|
2807
|
+
deleted_at = excluded.deleted_at,
|
|
2808
|
+
updated_at = excluded.updated_at,
|
|
2809
|
+
source_machine_id = excluded.source_machine_id,
|
|
2810
|
+
payload = excluded.payload,
|
|
2811
|
+
version = excluded.version
|
|
2812
|
+
WHERE storage_tombstones.updated_at IS NULL OR storage_tombstones.updated_at <= excluded.updated_at`, [
|
|
2813
|
+
uuid(),
|
|
2814
|
+
input.object_type,
|
|
2815
|
+
input.object_id,
|
|
2816
|
+
deletedAt,
|
|
2817
|
+
deletedAt,
|
|
2818
|
+
machineId,
|
|
2819
|
+
input.payload ? JSON.stringify(input.payload) : null,
|
|
2820
|
+
input.version ?? null
|
|
2821
|
+
]);
|
|
2822
|
+
return getStorageTombstone(input.object_type, input.object_id, d);
|
|
2823
|
+
}
|
|
2824
|
+
function getStorageTombstone(objectType, objectId, db) {
|
|
2825
|
+
const d = db ?? getDatabase();
|
|
2826
|
+
const row = d.query("SELECT * FROM storage_tombstones WHERE object_type = ? AND object_id = ?").get(objectType, objectId);
|
|
2827
|
+
return row ? rowToStorageTombstone(row) : null;
|
|
2828
|
+
}
|
|
2829
|
+
function rowToStorageTombstone(row) {
|
|
2830
|
+
return {
|
|
2831
|
+
...row,
|
|
2832
|
+
payload: parsePayload(row.payload)
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
function parsePayload(value) {
|
|
2836
|
+
if (!value)
|
|
2837
|
+
return null;
|
|
2838
|
+
try {
|
|
2839
|
+
const parsed = JSON.parse(value);
|
|
2840
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
2841
|
+
} catch {
|
|
2842
|
+
return null;
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
function currentStorageMachineId(db) {
|
|
2846
|
+
try {
|
|
2847
|
+
return getMachineId(db);
|
|
2848
|
+
} catch {
|
|
2849
|
+
return null;
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
var init_storage_tombstones = __esm(() => {
|
|
2853
|
+
init_database();
|
|
2854
|
+
init_machines();
|
|
2855
|
+
});
|
|
2856
|
+
|
|
2768
2857
|
// src/db/agent-names.ts
|
|
2769
2858
|
function normalizeAgentNameInput(name) {
|
|
2770
2859
|
return name.trim().toLowerCase();
|
|
@@ -3173,6 +3262,7 @@ function rowToAgent(row) {
|
|
|
3173
3262
|
}
|
|
3174
3263
|
function registerAgent(input, db) {
|
|
3175
3264
|
const d = db || getDatabase();
|
|
3265
|
+
const machineId = currentStorageMachineId(d);
|
|
3176
3266
|
const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
|
|
3177
3267
|
const normalizedName = validateAgentName(input.name, existingNames);
|
|
3178
3268
|
const existing = getAgentByName(normalizedName, d);
|
|
@@ -3213,14 +3303,18 @@ function registerAgent(input, db) {
|
|
|
3213
3303
|
updates.push("active_project_id = ?");
|
|
3214
3304
|
params.push(input.project_id);
|
|
3215
3305
|
}
|
|
3306
|
+
if (!existing.machine_id && machineId) {
|
|
3307
|
+
updates.push("machine_id = ?");
|
|
3308
|
+
params.push(machineId);
|
|
3309
|
+
}
|
|
3216
3310
|
params.push(existing.id);
|
|
3217
3311
|
d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
3218
3312
|
return getAgent(existing.id, d);
|
|
3219
3313
|
}
|
|
3220
3314
|
const id = shortUuid();
|
|
3221
3315
|
const timestamp = now();
|
|
3222
|
-
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)
|
|
3223
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3316
|
+
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)
|
|
3317
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3224
3318
|
id,
|
|
3225
3319
|
normalizedName,
|
|
3226
3320
|
input.description || null,
|
|
@@ -3236,7 +3330,8 @@ function registerAgent(input, db) {
|
|
|
3236
3330
|
timestamp,
|
|
3237
3331
|
input.session_id || null,
|
|
3238
3332
|
input.working_dir || null,
|
|
3239
|
-
input.project_id && input.session_id ? input.project_id : null
|
|
3333
|
+
input.project_id && input.session_id ? input.project_id : null,
|
|
3334
|
+
machineId
|
|
3240
3335
|
]);
|
|
3241
3336
|
return getAgent(id, d);
|
|
3242
3337
|
}
|
|
@@ -3419,6 +3514,7 @@ function getCapableAgents(capabilities, opts, db) {
|
|
|
3419
3514
|
}
|
|
3420
3515
|
var init_agents = __esm(() => {
|
|
3421
3516
|
init_database();
|
|
3517
|
+
init_storage_tombstones();
|
|
3422
3518
|
init_agent_names();
|
|
3423
3519
|
});
|
|
3424
3520
|
|
|
@@ -7767,8 +7863,9 @@ function createProject(input, db) {
|
|
|
7767
7863
|
const timestamp = now();
|
|
7768
7864
|
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
7769
7865
|
const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
|
|
7770
|
-
|
|
7771
|
-
|
|
7866
|
+
const machineId = currentStorageMachineId(d);
|
|
7867
|
+
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
|
|
7868
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
|
|
7772
7869
|
return getProject(id, d);
|
|
7773
7870
|
}
|
|
7774
7871
|
function getProject(id, db) {
|
|
@@ -7847,6 +7944,14 @@ function renameProject(id, input, db) {
|
|
|
7847
7944
|
}
|
|
7848
7945
|
function deleteProject(id, db) {
|
|
7849
7946
|
const d = db || getDatabase();
|
|
7947
|
+
const project = getProject(id, d);
|
|
7948
|
+
if (!project)
|
|
7949
|
+
return false;
|
|
7950
|
+
recordStorageTombstone({
|
|
7951
|
+
object_type: "projects",
|
|
7952
|
+
object_id: id,
|
|
7953
|
+
payload: project
|
|
7954
|
+
}, d);
|
|
7850
7955
|
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
7851
7956
|
return result.changes > 0;
|
|
7852
7957
|
}
|
|
@@ -7951,6 +8056,14 @@ function listMachineLocalPaths(projectId, db) {
|
|
|
7951
8056
|
function removeMachineLocalPath(projectId, machineId, db) {
|
|
7952
8057
|
const d = db || getDatabase();
|
|
7953
8058
|
const mid = machineId ?? getMachineId(d);
|
|
8059
|
+
const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, mid);
|
|
8060
|
+
if (!existing)
|
|
8061
|
+
return false;
|
|
8062
|
+
recordStorageTombstone({
|
|
8063
|
+
object_type: "project_machine_paths",
|
|
8064
|
+
object_id: existing.id,
|
|
8065
|
+
payload: existing
|
|
8066
|
+
}, d);
|
|
7954
8067
|
const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
|
|
7955
8068
|
return result.changes > 0;
|
|
7956
8069
|
}
|
|
@@ -7958,6 +8071,7 @@ var init_projects = __esm(() => {
|
|
|
7958
8071
|
init_types();
|
|
7959
8072
|
init_database();
|
|
7960
8073
|
init_machines();
|
|
8074
|
+
init_storage_tombstones();
|
|
7961
8075
|
});
|
|
7962
8076
|
|
|
7963
8077
|
// src/lib/completion-guard.ts
|
|
@@ -8009,6 +8123,57 @@ var init_completion_guard = __esm(() => {
|
|
|
8009
8123
|
init_projects();
|
|
8010
8124
|
});
|
|
8011
8125
|
|
|
8126
|
+
// src/lib/event-emission-safety.ts
|
|
8127
|
+
import { tmpdir } from "os";
|
|
8128
|
+
import { resolve as resolve3, sep } from "path";
|
|
8129
|
+
function envFlag(name) {
|
|
8130
|
+
const value = process.env[name];
|
|
8131
|
+
return value === "1" || value === "true" || value === "yes";
|
|
8132
|
+
}
|
|
8133
|
+
function isUnder(parent, child) {
|
|
8134
|
+
const normalizedParent = resolve3(parent);
|
|
8135
|
+
const normalizedChild = resolve3(child);
|
|
8136
|
+
return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
|
|
8137
|
+
}
|
|
8138
|
+
function databasePathFromDatabase(db) {
|
|
8139
|
+
const filename = db?.filename;
|
|
8140
|
+
return typeof filename === "string" && filename.trim() ? filename : undefined;
|
|
8141
|
+
}
|
|
8142
|
+
function isEphemeralTodosDatabase(dbPath) {
|
|
8143
|
+
const resolvedPath = dbPath ?? getDatabasePath();
|
|
8144
|
+
if (resolvedPath === ":memory:" || resolvedPath.startsWith("file::memory:"))
|
|
8145
|
+
return true;
|
|
8146
|
+
return isUnder(tmpdir(), resolvedPath);
|
|
8147
|
+
}
|
|
8148
|
+
function hasExplicitSharedEventsStore() {
|
|
8149
|
+
return Boolean(process.env["HASNA_EVENTS_DIR"] || process.env["HASNA_EVENTS_HOME"]);
|
|
8150
|
+
}
|
|
8151
|
+
function usesIsolatedTodosHome() {
|
|
8152
|
+
return isUnder(tmpdir(), getTodosGlobalDir());
|
|
8153
|
+
}
|
|
8154
|
+
function shouldEmitSharedTaskEvents(dbPath) {
|
|
8155
|
+
if (envFlag("TODOS_DISABLE_SHARED_EVENTS"))
|
|
8156
|
+
return false;
|
|
8157
|
+
if (envFlag("TODOS_ALLOW_EPHEMERAL_SHARED_EVENTS"))
|
|
8158
|
+
return true;
|
|
8159
|
+
if (!isEphemeralTodosDatabase(dbPath))
|
|
8160
|
+
return true;
|
|
8161
|
+
return hasExplicitSharedEventsStore();
|
|
8162
|
+
}
|
|
8163
|
+
function shouldDeliverLocalLifecycleHooks(dbPath) {
|
|
8164
|
+
if (envFlag("TODOS_DISABLE_LOCAL_EVENT_HOOKS"))
|
|
8165
|
+
return false;
|
|
8166
|
+
if (envFlag("TODOS_ALLOW_EPHEMERAL_LOCAL_EVENT_HOOKS"))
|
|
8167
|
+
return true;
|
|
8168
|
+
if (!isEphemeralTodosDatabase(dbPath))
|
|
8169
|
+
return true;
|
|
8170
|
+
return usesIsolatedTodosHome();
|
|
8171
|
+
}
|
|
8172
|
+
var init_event_emission_safety = __esm(() => {
|
|
8173
|
+
init_database();
|
|
8174
|
+
init_sync_utils();
|
|
8175
|
+
});
|
|
8176
|
+
|
|
8012
8177
|
// src/lib/redaction.ts
|
|
8013
8178
|
function unique(values) {
|
|
8014
8179
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -8109,9 +8274,9 @@ var init_redaction = __esm(() => {
|
|
|
8109
8274
|
});
|
|
8110
8275
|
|
|
8111
8276
|
// src/lib/workspace-trust.ts
|
|
8112
|
-
import { relative, resolve as
|
|
8277
|
+
import { relative, resolve as resolve4 } from "path";
|
|
8113
8278
|
function normalizePath(path) {
|
|
8114
|
-
return
|
|
8279
|
+
return resolve4(path);
|
|
8115
8280
|
}
|
|
8116
8281
|
function unique2(values) {
|
|
8117
8282
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -8293,9 +8458,9 @@ var init_workspace_trust = __esm(() => {
|
|
|
8293
8458
|
});
|
|
8294
8459
|
|
|
8295
8460
|
// src/lib/runner-sandbox.ts
|
|
8296
|
-
import { relative as relative2, resolve as
|
|
8461
|
+
import { relative as relative2, resolve as resolve5 } from "path";
|
|
8297
8462
|
function normalizePath2(path) {
|
|
8298
|
-
return
|
|
8463
|
+
return resolve5(path);
|
|
8299
8464
|
}
|
|
8300
8465
|
function unique3(values) {
|
|
8301
8466
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -8485,7 +8650,7 @@ var init_runner_sandbox = __esm(() => {
|
|
|
8485
8650
|
// src/lib/event-hooks.ts
|
|
8486
8651
|
import { createHash, randomUUID } from "crypto";
|
|
8487
8652
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
8488
|
-
import { dirname as dirname3, resolve as
|
|
8653
|
+
import { dirname as dirname3, resolve as resolve6 } from "path";
|
|
8489
8654
|
import { createConnection } from "net";
|
|
8490
8655
|
function safeName(name) {
|
|
8491
8656
|
const trimmed = name.trim();
|
|
@@ -8623,7 +8788,7 @@ async function deliverHook(hook, envelope) {
|
|
|
8623
8788
|
if (hook.target === "stdout") {
|
|
8624
8789
|
output = line.trim();
|
|
8625
8790
|
} else if (hook.target === "file") {
|
|
8626
|
-
const filePath =
|
|
8791
|
+
const filePath = resolve6(hook.file_path);
|
|
8627
8792
|
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
8628
8793
|
appendFileSync(filePath, line);
|
|
8629
8794
|
} else if (hook.target === "socket") {
|
|
@@ -8698,6 +8863,8 @@ async function emitLocalEventHooks(input) {
|
|
|
8698
8863
|
return Promise.all(hooks.map((hook) => deliverHook(hook, envelope)));
|
|
8699
8864
|
}
|
|
8700
8865
|
function emitLocalEventHooksQuiet(input) {
|
|
8866
|
+
if (!shouldDeliverLocalLifecycleHooks(input.databasePath))
|
|
8867
|
+
return;
|
|
8701
8868
|
emitLocalEventHooks(input).catch(() => {});
|
|
8702
8869
|
}
|
|
8703
8870
|
async function testLocalEventHook(name, input) {
|
|
@@ -8711,6 +8878,7 @@ var init_event_hooks = __esm(() => {
|
|
|
8711
8878
|
init_redaction();
|
|
8712
8879
|
init_runner_sandbox();
|
|
8713
8880
|
init_config();
|
|
8881
|
+
init_event_emission_safety();
|
|
8714
8882
|
LOCAL_EVENT_TYPES = [
|
|
8715
8883
|
"task.created",
|
|
8716
8884
|
"task.assigned",
|
|
@@ -9043,7 +9211,7 @@ async function dispatchCommand(event, channel) {
|
|
|
9043
9211
|
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
9044
9212
|
HASNA_EVENT_JSON: eventJson
|
|
9045
9213
|
};
|
|
9046
|
-
return new Promise((
|
|
9214
|
+
return new Promise((resolve7) => {
|
|
9047
9215
|
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
9048
9216
|
cwd: channel.command.cwd,
|
|
9049
9217
|
env,
|
|
@@ -9061,7 +9229,7 @@ async function dispatchCommand(event, channel) {
|
|
|
9061
9229
|
});
|
|
9062
9230
|
child.on("error", (error) => {
|
|
9063
9231
|
clearTimeout(timeout);
|
|
9064
|
-
|
|
9232
|
+
resolve7({
|
|
9065
9233
|
attempt: 1,
|
|
9066
9234
|
status: "failed",
|
|
9067
9235
|
startedAt,
|
|
@@ -9074,7 +9242,7 @@ async function dispatchCommand(event, channel) {
|
|
|
9074
9242
|
child.on("close", (code, signal) => {
|
|
9075
9243
|
clearTimeout(timeout);
|
|
9076
9244
|
const success = code === 0;
|
|
9077
|
-
|
|
9245
|
+
resolve7({
|
|
9078
9246
|
attempt: 1,
|
|
9079
9247
|
status: success ? "success" : "failed",
|
|
9080
9248
|
startedAt,
|
|
@@ -9341,14 +9509,15 @@ function createTaskList(input, db) {
|
|
|
9341
9509
|
const id = uuid();
|
|
9342
9510
|
const timestamp = now();
|
|
9343
9511
|
const slug = input.slug || slugify(input.name);
|
|
9512
|
+
const machineId = currentStorageMachineId(d);
|
|
9344
9513
|
if (!input.project_id) {
|
|
9345
9514
|
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
9346
9515
|
if (existing) {
|
|
9347
9516
|
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
9348
9517
|
}
|
|
9349
9518
|
}
|
|
9350
|
-
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
9351
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
9519
|
+
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
|
|
9520
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
|
|
9352
9521
|
return getTaskList(id, d);
|
|
9353
9522
|
}
|
|
9354
9523
|
function getTaskList(id, db) {
|
|
@@ -9398,6 +9567,14 @@ function updateTaskList(id, input, db) {
|
|
|
9398
9567
|
}
|
|
9399
9568
|
function deleteTaskList(id, db) {
|
|
9400
9569
|
const d = db || getDatabase();
|
|
9570
|
+
const list = getTaskList(id, d);
|
|
9571
|
+
if (!list)
|
|
9572
|
+
return false;
|
|
9573
|
+
recordStorageTombstone({
|
|
9574
|
+
object_type: "task_lists",
|
|
9575
|
+
object_id: id,
|
|
9576
|
+
payload: list
|
|
9577
|
+
}, d);
|
|
9401
9578
|
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
9402
9579
|
}
|
|
9403
9580
|
function ensureTaskList(name, slug, projectId, db) {
|
|
@@ -9411,6 +9588,7 @@ var init_task_lists = __esm(() => {
|
|
|
9411
9588
|
init_types();
|
|
9412
9589
|
init_database();
|
|
9413
9590
|
init_projects();
|
|
9591
|
+
init_storage_tombstones();
|
|
9414
9592
|
});
|
|
9415
9593
|
|
|
9416
9594
|
// src/lib/task-route-contract.ts
|
|
@@ -9614,6 +9792,8 @@ function readMachineLocalPath(project) {
|
|
|
9614
9792
|
}
|
|
9615
9793
|
}
|
|
9616
9794
|
async function emitSharedTaskEvent(input) {
|
|
9795
|
+
if (!shouldEmitSharedTaskEvents(input.databasePath))
|
|
9796
|
+
return;
|
|
9617
9797
|
const data = taskEventData(input.task, input.data);
|
|
9618
9798
|
await new EventsClient().emit({
|
|
9619
9799
|
source: SOURCE,
|
|
@@ -9637,6 +9817,7 @@ var init_shared_events = __esm(() => {
|
|
|
9637
9817
|
init_database();
|
|
9638
9818
|
init_projects();
|
|
9639
9819
|
init_task_lists();
|
|
9820
|
+
init_event_emission_safety();
|
|
9640
9821
|
});
|
|
9641
9822
|
|
|
9642
9823
|
// src/lib/secret-redaction.ts
|
|
@@ -9885,8 +10066,9 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
|
9885
10066
|
const d = db || getDatabase();
|
|
9886
10067
|
const id = uuid();
|
|
9887
10068
|
const timestamp = now();
|
|
9888
|
-
|
|
9889
|
-
|
|
10069
|
+
const machineId = currentStorageMachineId(d);
|
|
10070
|
+
d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
|
|
10071
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp, machineId]);
|
|
9890
10072
|
try {
|
|
9891
10073
|
const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
|
|
9892
10074
|
logActivity2({
|
|
@@ -9899,7 +10081,7 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
|
9899
10081
|
actor_id: agentId ?? undefined
|
|
9900
10082
|
}, d);
|
|
9901
10083
|
} catch {}
|
|
9902
|
-
return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
|
|
10084
|
+
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 };
|
|
9903
10085
|
}
|
|
9904
10086
|
function getTaskHistory(taskId, db) {
|
|
9905
10087
|
const d = db || getDatabase();
|
|
@@ -9911,6 +10093,7 @@ function getRecentActivity(limit = 50, db) {
|
|
|
9911
10093
|
}
|
|
9912
10094
|
var init_audit = __esm(() => {
|
|
9913
10095
|
init_database();
|
|
10096
|
+
init_storage_tombstones();
|
|
9914
10097
|
});
|
|
9915
10098
|
|
|
9916
10099
|
// src/db/webhooks.ts
|
|
@@ -10176,13 +10359,14 @@ function createTask(input, db) {
|
|
|
10176
10359
|
const d = db || getDatabase();
|
|
10177
10360
|
const timestamp = now();
|
|
10178
10361
|
const tags = input.tags || [];
|
|
10362
|
+
const machineId = currentStorageMachineId(d);
|
|
10179
10363
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
10180
10364
|
const assignedFromProject = input.assigned_from_project || null;
|
|
10181
10365
|
let id = uuid();
|
|
10182
10366
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
10183
10367
|
try {
|
|
10184
|
-
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)
|
|
10185
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10368
|
+
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)
|
|
10369
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10186
10370
|
id,
|
|
10187
10371
|
null,
|
|
10188
10372
|
input.project_id || null,
|
|
@@ -10219,7 +10403,8 @@ function createTask(input, db) {
|
|
|
10219
10403
|
input.spawned_from_session || null,
|
|
10220
10404
|
assignedBy || null,
|
|
10221
10405
|
assignedFromProject || null,
|
|
10222
|
-
input.task_type || null
|
|
10406
|
+
input.task_type || null,
|
|
10407
|
+
machineId
|
|
10223
10408
|
]);
|
|
10224
10409
|
break;
|
|
10225
10410
|
} catch (e) {
|
|
@@ -10235,9 +10420,10 @@ function createTask(input, db) {
|
|
|
10235
10420
|
}
|
|
10236
10421
|
const task = getTask(id, d);
|
|
10237
10422
|
const payload = taskEventData(task);
|
|
10423
|
+
const databasePath = databasePathFromDatabase(d);
|
|
10238
10424
|
dispatchWebhook2("task.created", payload, d).catch(() => {});
|
|
10239
|
-
emitLocalEventHooksQuiet({ type: "task.created", payload });
|
|
10240
|
-
emitSharedTaskEventQuiet({ type: "task.created", task });
|
|
10425
|
+
emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
|
|
10426
|
+
emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
|
|
10241
10427
|
return task;
|
|
10242
10428
|
}
|
|
10243
10429
|
function getTask(id, db) {
|
|
@@ -10655,29 +10841,39 @@ function updateTask(id, input, db) {
|
|
|
10655
10841
|
approved_by: input.approved_by ?? task.approved_by,
|
|
10656
10842
|
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
10657
10843
|
};
|
|
10844
|
+
const databasePath = databasePathFromDatabase(d);
|
|
10658
10845
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
10659
10846
|
const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
|
|
10660
10847
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
10661
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
10662
|
-
emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
|
|
10848
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
10849
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to }, databasePath });
|
|
10663
10850
|
}
|
|
10664
10851
|
if (input.status !== undefined && input.status !== task.status) {
|
|
10665
10852
|
const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
|
|
10666
10853
|
dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
|
|
10667
|
-
emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
|
|
10668
|
-
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
|
|
10854
|
+
emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
|
|
10855
|
+
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
|
|
10669
10856
|
}
|
|
10670
10857
|
if (input.approved_by !== undefined) {
|
|
10671
|
-
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
10858
|
+
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title }, databasePath });
|
|
10672
10859
|
}
|
|
10673
10860
|
const updatePayload = taskEventData(updatedTask);
|
|
10674
10861
|
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
10675
|
-
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
|
|
10676
|
-
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
|
|
10862
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
|
|
10863
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
|
|
10677
10864
|
return updatedTask;
|
|
10678
10865
|
}
|
|
10679
10866
|
function deleteTask(id, db) {
|
|
10680
10867
|
const d = db || getDatabase();
|
|
10868
|
+
const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
10869
|
+
if (!row)
|
|
10870
|
+
return false;
|
|
10871
|
+
recordStorageTombstone({
|
|
10872
|
+
object_type: "tasks",
|
|
10873
|
+
object_id: id,
|
|
10874
|
+
payload: rowToTask(row),
|
|
10875
|
+
version: row.version
|
|
10876
|
+
}, d);
|
|
10681
10877
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
10682
10878
|
return result.changes > 0;
|
|
10683
10879
|
}
|
|
@@ -10685,11 +10881,13 @@ var init_task_crud = __esm(() => {
|
|
|
10685
10881
|
init_types();
|
|
10686
10882
|
init_database();
|
|
10687
10883
|
init_completion_guard();
|
|
10884
|
+
init_event_emission_safety();
|
|
10688
10885
|
init_event_hooks();
|
|
10689
10886
|
init_shared_events();
|
|
10690
10887
|
init_audit();
|
|
10691
10888
|
init_webhooks();
|
|
10692
10889
|
init_checklists();
|
|
10890
|
+
init_storage_tombstones();
|
|
10693
10891
|
});
|
|
10694
10892
|
|
|
10695
10893
|
// src/lib/recurrence.ts
|
|
@@ -10838,8 +11036,9 @@ function resolveTemplateId(id, d) {
|
|
|
10838
11036
|
function createTemplate(input, db) {
|
|
10839
11037
|
const d = db || getDatabase();
|
|
10840
11038
|
const id = uuid();
|
|
10841
|
-
|
|
10842
|
-
|
|
11039
|
+
const machineId = currentStorageMachineId(d);
|
|
11040
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at, machine_id)
|
|
11041
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10843
11042
|
id,
|
|
10844
11043
|
input.name,
|
|
10845
11044
|
input.title_pattern,
|
|
@@ -10850,7 +11049,8 @@ function createTemplate(input, db) {
|
|
|
10850
11049
|
input.project_id || null,
|
|
10851
11050
|
input.plan_id || null,
|
|
10852
11051
|
JSON.stringify(input.metadata || {}),
|
|
10853
|
-
now()
|
|
11052
|
+
now(),
|
|
11053
|
+
machineId
|
|
10854
11054
|
]);
|
|
10855
11055
|
if (input.tasks && input.tasks.length > 0) {
|
|
10856
11056
|
addTemplateTasks(id, input.tasks, d);
|
|
@@ -10874,6 +11074,15 @@ function deleteTemplate(id, db) {
|
|
|
10874
11074
|
const resolved = resolveTemplateId(id, d);
|
|
10875
11075
|
if (!resolved)
|
|
10876
11076
|
return false;
|
|
11077
|
+
const template = getTemplate(resolved, d);
|
|
11078
|
+
if (!template)
|
|
11079
|
+
return false;
|
|
11080
|
+
recordStorageTombstone({
|
|
11081
|
+
object_type: "templates",
|
|
11082
|
+
object_id: resolved,
|
|
11083
|
+
payload: template,
|
|
11084
|
+
version: template.version
|
|
11085
|
+
}, d);
|
|
10877
11086
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
10878
11087
|
}
|
|
10879
11088
|
function updateTemplate(id, updates, db) {
|
|
@@ -11243,6 +11452,7 @@ function previewTemplate(templateId, variables, db) {
|
|
|
11243
11452
|
var init_templates = __esm(() => {
|
|
11244
11453
|
init_database();
|
|
11245
11454
|
init_tasks();
|
|
11455
|
+
init_storage_tombstones();
|
|
11246
11456
|
});
|
|
11247
11457
|
|
|
11248
11458
|
// src/db/task-graph.ts
|
|
@@ -11409,6 +11619,7 @@ function getBlockingDeps(id, db) {
|
|
|
11409
11619
|
}
|
|
11410
11620
|
function startTask(id, agentId, db) {
|
|
11411
11621
|
const d = db || getDatabase();
|
|
11622
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11412
11623
|
const task = getTask(id, d);
|
|
11413
11624
|
if (!task)
|
|
11414
11625
|
throw new TaskNotFoundError(id);
|
|
@@ -11423,7 +11634,8 @@ function startTask(id, agentId, db) {
|
|
|
11423
11634
|
agent_id: agentId,
|
|
11424
11635
|
title: task.title,
|
|
11425
11636
|
blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
|
|
11426
|
-
}
|
|
11637
|
+
},
|
|
11638
|
+
databasePath
|
|
11427
11639
|
});
|
|
11428
11640
|
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
11429
11641
|
}
|
|
@@ -11445,12 +11657,13 @@ function startTask(id, agentId, db) {
|
|
|
11445
11657
|
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 };
|
|
11446
11658
|
const payload = taskEventData(startedTask, { agent_id: agentId });
|
|
11447
11659
|
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
11448
|
-
emitLocalEventHooksQuiet({ type: "task.started", payload });
|
|
11449
|
-
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
|
|
11660
|
+
emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
|
|
11661
|
+
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
|
|
11450
11662
|
return startedTask;
|
|
11451
11663
|
}
|
|
11452
11664
|
function completeTask(id, agentId, db, options) {
|
|
11453
11665
|
const d = db || getDatabase();
|
|
11666
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11454
11667
|
const task = getTask(id, d);
|
|
11455
11668
|
if (!task)
|
|
11456
11669
|
throw new TaskNotFoundError(id);
|
|
@@ -11496,8 +11709,8 @@ function completeTask(id, agentId, db, options) {
|
|
|
11496
11709
|
};
|
|
11497
11710
|
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
|
|
11498
11711
|
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
11499
|
-
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
|
|
11500
|
-
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
|
|
11712
|
+
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
|
|
11713
|
+
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
|
|
11501
11714
|
let spawnedTask = null;
|
|
11502
11715
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
11503
11716
|
spawnedTask = spawnNextRecurrence(task, d, timestamp);
|
|
@@ -11541,9 +11754,9 @@ function completeTask(id, agentId, db, options) {
|
|
|
11541
11754
|
const depTask = getTask(dep.id, d);
|
|
11542
11755
|
const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
|
|
11543
11756
|
dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
|
|
11544
|
-
emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
|
|
11757
|
+
emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
|
|
11545
11758
|
if (depTask)
|
|
11546
|
-
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
|
|
11759
|
+
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
|
|
11547
11760
|
}
|
|
11548
11761
|
}
|
|
11549
11762
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
@@ -11713,6 +11926,7 @@ function getTasksChangedSince(since, filters, db) {
|
|
|
11713
11926
|
}
|
|
11714
11927
|
function failTask(id, agentId, reason, options, db) {
|
|
11715
11928
|
const d = db || getDatabase();
|
|
11929
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11716
11930
|
const task = getTask(id, d);
|
|
11717
11931
|
if (!task)
|
|
11718
11932
|
throw new TaskNotFoundError(id);
|
|
@@ -11741,8 +11955,8 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
11741
11955
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
11742
11956
|
const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
|
|
11743
11957
|
dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
|
|
11744
|
-
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
|
|
11745
|
-
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
|
|
11958
|
+
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
|
|
11959
|
+
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning", databasePath });
|
|
11746
11960
|
let retryTask;
|
|
11747
11961
|
if (options?.retry) {
|
|
11748
11962
|
const retryCount = (task.retry_count || 0) + 1;
|
|
@@ -11802,6 +12016,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
|
|
|
11802
12016
|
}
|
|
11803
12017
|
function stealTask(agentId, opts, db) {
|
|
11804
12018
|
const d = db || getDatabase();
|
|
12019
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11805
12020
|
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
11806
12021
|
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
11807
12022
|
if (staleTasks.length === 0)
|
|
@@ -11820,8 +12035,8 @@ function stealTask(agentId, opts, db) {
|
|
|
11820
12035
|
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
11821
12036
|
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
11822
12037
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
11823
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
11824
|
-
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
|
|
12038
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
12039
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
|
|
11825
12040
|
return stolenTask;
|
|
11826
12041
|
}
|
|
11827
12042
|
function claimOrSteal(agentId, filters, db) {
|
|
@@ -11869,6 +12084,7 @@ var init_task_lifecycle = __esm(() => {
|
|
|
11869
12084
|
init_types();
|
|
11870
12085
|
init_database();
|
|
11871
12086
|
init_completion_guard();
|
|
12087
|
+
init_event_emission_safety();
|
|
11872
12088
|
init_event_hooks();
|
|
11873
12089
|
init_shared_events();
|
|
11874
12090
|
init_audit();
|
|
@@ -12576,8 +12792,9 @@ function createPlan(input, db) {
|
|
|
12576
12792
|
const d = db || getDatabase();
|
|
12577
12793
|
const id = uuid();
|
|
12578
12794
|
const timestamp = now();
|
|
12579
|
-
|
|
12580
|
-
|
|
12795
|
+
const machineId = currentStorageMachineId(d);
|
|
12796
|
+
d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
|
|
12797
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
12581
12798
|
id,
|
|
12582
12799
|
input.project_id || null,
|
|
12583
12800
|
input.task_list_id || null,
|
|
@@ -12586,7 +12803,8 @@ function createPlan(input, db) {
|
|
|
12586
12803
|
input.description || null,
|
|
12587
12804
|
input.status || "active",
|
|
12588
12805
|
timestamp,
|
|
12589
|
-
timestamp
|
|
12806
|
+
timestamp,
|
|
12807
|
+
machineId
|
|
12590
12808
|
]);
|
|
12591
12809
|
return getPlan(id, d);
|
|
12592
12810
|
}
|
|
@@ -12634,19 +12852,30 @@ function updatePlan(id, input, db) {
|
|
|
12634
12852
|
const updated = getPlan(id, d);
|
|
12635
12853
|
emitLocalEventHooksQuiet({
|
|
12636
12854
|
type: "plan.updated",
|
|
12637
|
-
payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id }
|
|
12855
|
+
payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id },
|
|
12856
|
+
databasePath: databasePathFromDatabase(d)
|
|
12638
12857
|
});
|
|
12639
12858
|
return updated;
|
|
12640
12859
|
}
|
|
12641
12860
|
function deletePlan(id, db) {
|
|
12642
12861
|
const d = db || getDatabase();
|
|
12862
|
+
const plan = getPlan(id, d);
|
|
12863
|
+
if (!plan)
|
|
12864
|
+
return false;
|
|
12865
|
+
recordStorageTombstone({
|
|
12866
|
+
object_type: "plans",
|
|
12867
|
+
object_id: id,
|
|
12868
|
+
payload: plan
|
|
12869
|
+
}, d);
|
|
12643
12870
|
const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
|
|
12644
12871
|
return result.changes > 0;
|
|
12645
12872
|
}
|
|
12646
12873
|
var init_plans = __esm(() => {
|
|
12647
12874
|
init_types();
|
|
12875
|
+
init_event_emission_safety();
|
|
12648
12876
|
init_event_hooks();
|
|
12649
12877
|
init_database();
|
|
12878
|
+
init_storage_tombstones();
|
|
12650
12879
|
});
|
|
12651
12880
|
|
|
12652
12881
|
// src/db/boards.ts
|
|
@@ -13053,20 +13282,20 @@ var init_boards = __esm(() => {
|
|
|
13053
13282
|
// src/lib/artifact-store.ts
|
|
13054
13283
|
import { createHash as createHash2 } from "crypto";
|
|
13055
13284
|
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync2, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
13056
|
-
import { basename, dirname as dirname4, join as join5, resolve as
|
|
13057
|
-
import { tmpdir } from "os";
|
|
13285
|
+
import { basename, dirname as dirname4, join as join5, resolve as resolve7 } from "path";
|
|
13286
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
13058
13287
|
function isInMemoryDb2(path) {
|
|
13059
13288
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
13060
13289
|
}
|
|
13061
13290
|
function artifactStoreRoot() {
|
|
13062
13291
|
if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
|
|
13063
|
-
return
|
|
13292
|
+
return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
|
|
13064
13293
|
if (process.env["TODOS_ARTIFACTS_DIR"])
|
|
13065
|
-
return
|
|
13294
|
+
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
13066
13295
|
const dbPath = getDatabasePath();
|
|
13067
13296
|
if (isInMemoryDb2(dbPath))
|
|
13068
|
-
return join5(
|
|
13069
|
-
return join5(dirname4(
|
|
13297
|
+
return join5(tmpdir2(), "hasna-todos-artifacts");
|
|
13298
|
+
return join5(dirname4(resolve7(dbPath)), "artifacts");
|
|
13070
13299
|
}
|
|
13071
13300
|
function artifactStorePath(relativePath) {
|
|
13072
13301
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
@@ -13113,7 +13342,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
13113
13342
|
return "application/octet-stream";
|
|
13114
13343
|
}
|
|
13115
13344
|
function storeArtifactContent(input) {
|
|
13116
|
-
const sourcePath =
|
|
13345
|
+
const sourcePath = resolve7(input.path);
|
|
13117
13346
|
if (!existsSync6(sourcePath))
|
|
13118
13347
|
return null;
|
|
13119
13348
|
const sourceStat = statSync2(sourcePath);
|
|
@@ -13810,7 +14039,11 @@ function startTaskRun(input, db) {
|
|
|
13810
14039
|
}, d);
|
|
13811
14040
|
}
|
|
13812
14041
|
const run = getTaskRun(id, d);
|
|
13813
|
-
emitLocalEventHooksQuiet({
|
|
14042
|
+
emitLocalEventHooksQuiet({
|
|
14043
|
+
type: "run.started",
|
|
14044
|
+
payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title },
|
|
14045
|
+
databasePath: databasePathFromDatabase(d)
|
|
14046
|
+
});
|
|
13814
14047
|
return run;
|
|
13815
14048
|
}
|
|
13816
14049
|
function beginTaskRunTransaction(input, db) {
|
|
@@ -14079,7 +14312,8 @@ function finishTaskRun(input, db) {
|
|
|
14079
14312
|
const updated = getTaskRun(run.id, d);
|
|
14080
14313
|
emitLocalEventHooksQuiet({
|
|
14081
14314
|
type: `run.${input.status}`,
|
|
14082
|
-
payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp }
|
|
14315
|
+
payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp },
|
|
14316
|
+
databasePath: databasePathFromDatabase(d)
|
|
14083
14317
|
});
|
|
14084
14318
|
return updated;
|
|
14085
14319
|
}
|
|
@@ -14163,6 +14397,7 @@ function getTaskRunLedger(runId, db) {
|
|
|
14163
14397
|
var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
|
|
14164
14398
|
var init_task_runs = __esm(() => {
|
|
14165
14399
|
init_artifact_store();
|
|
14400
|
+
init_event_emission_safety();
|
|
14166
14401
|
init_event_hooks();
|
|
14167
14402
|
init_redaction();
|
|
14168
14403
|
init_types();
|
|
@@ -15931,7 +16166,7 @@ var init_task_crud2 = __esm(() => {
|
|
|
15931
16166
|
|
|
15932
16167
|
// src/lib/project-bootstrap.ts
|
|
15933
16168
|
import { existsSync as existsSync7, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
15934
|
-
import { basename as basename2, dirname as dirname5, resolve as
|
|
16169
|
+
import { basename as basename2, dirname as dirname5, resolve as resolve8 } from "path";
|
|
15935
16170
|
function safeStat(path) {
|
|
15936
16171
|
try {
|
|
15937
16172
|
return statSync3(path);
|
|
@@ -15940,7 +16175,7 @@ function safeStat(path) {
|
|
|
15940
16175
|
}
|
|
15941
16176
|
}
|
|
15942
16177
|
function canonicalPath(input) {
|
|
15943
|
-
const resolved =
|
|
16178
|
+
const resolved = resolve8(input);
|
|
15944
16179
|
const stats = safeStat(resolved);
|
|
15945
16180
|
if (stats?.isFile())
|
|
15946
16181
|
return dirname5(resolved);
|
|
@@ -15949,7 +16184,7 @@ function canonicalPath(input) {
|
|
|
15949
16184
|
function findUp(start, marker) {
|
|
15950
16185
|
let current = canonicalPath(start);
|
|
15951
16186
|
while (true) {
|
|
15952
|
-
if (existsSync7(
|
|
16187
|
+
if (existsSync7(resolve8(current, marker)))
|
|
15953
16188
|
return current;
|
|
15954
16189
|
const parent = dirname5(current);
|
|
15955
16190
|
if (parent === current)
|
|
@@ -15960,7 +16195,7 @@ function findUp(start, marker) {
|
|
|
15960
16195
|
function readPackageJson(path) {
|
|
15961
16196
|
if (!path)
|
|
15962
16197
|
return null;
|
|
15963
|
-
const file =
|
|
16198
|
+
const file = resolve8(path, "package.json");
|
|
15964
16199
|
if (!existsSync7(file))
|
|
15965
16200
|
return null;
|
|
15966
16201
|
try {
|
|
@@ -15983,7 +16218,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
15983
16218
|
if (rootPackage?.workspaces)
|
|
15984
16219
|
markers.push("package.json#workspaces");
|
|
15985
16220
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
15986
|
-
if (existsSync7(
|
|
16221
|
+
if (existsSync7(resolve8(root, marker)))
|
|
15987
16222
|
markers.push(marker);
|
|
15988
16223
|
}
|
|
15989
16224
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -16527,7 +16762,7 @@ var init_retention_cleanup = __esm(() => {
|
|
|
16527
16762
|
|
|
16528
16763
|
// src/lib/mention-resolver.ts
|
|
16529
16764
|
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
16530
|
-
import { basename as basename3, isAbsolute, join as join6, relative as relative3, resolve as
|
|
16765
|
+
import { basename as basename3, isAbsolute, join as join6, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
|
|
16531
16766
|
function blankResolution(parsed) {
|
|
16532
16767
|
return {
|
|
16533
16768
|
input: parsed.input,
|
|
@@ -16550,11 +16785,11 @@ function backlink(kind, key, label, target = key) {
|
|
|
16550
16785
|
return { kind, key, label, target };
|
|
16551
16786
|
}
|
|
16552
16787
|
function normalizeWorkspace(workspace) {
|
|
16553
|
-
return
|
|
16788
|
+
return resolve9(workspace || process.cwd());
|
|
16554
16789
|
}
|
|
16555
16790
|
function isInside(root, absolutePath) {
|
|
16556
16791
|
const rel = relative3(root, absolutePath);
|
|
16557
|
-
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${
|
|
16792
|
+
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep2}`) && !isAbsolute(rel);
|
|
16558
16793
|
}
|
|
16559
16794
|
function normalizeRelativePath(value) {
|
|
16560
16795
|
const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
|
|
@@ -16618,7 +16853,7 @@ function resolveFile(parsed, workspace) {
|
|
|
16618
16853
|
resolution.warnings.push("path is empty or escapes the workspace");
|
|
16619
16854
|
return resolution;
|
|
16620
16855
|
}
|
|
16621
|
-
const absolutePath =
|
|
16856
|
+
const absolutePath = resolve9(workspace, relPath);
|
|
16622
16857
|
if (!isInside(workspace, absolutePath)) {
|
|
16623
16858
|
resolution.path = relPath;
|
|
16624
16859
|
resolution.warnings.push("path escapes the workspace");
|
|
@@ -16963,9 +17198,9 @@ var init_mention_resolver = __esm(() => {
|
|
|
16963
17198
|
});
|
|
16964
17199
|
|
|
16965
17200
|
// src/lib/policy-packs.ts
|
|
16966
|
-
import { relative as relative4, resolve as
|
|
17201
|
+
import { relative as relative4, resolve as resolve10 } from "path";
|
|
16967
17202
|
function normalizePath3(path) {
|
|
16968
|
-
return
|
|
17203
|
+
return resolve10(path);
|
|
16969
17204
|
}
|
|
16970
17205
|
function unique4(values) {
|
|
16971
17206
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -17020,7 +17255,7 @@ function commandMatches(commands, pattern) {
|
|
|
17020
17255
|
}
|
|
17021
17256
|
function pathMatches(paths, pattern, root) {
|
|
17022
17257
|
return paths.filter((path) => {
|
|
17023
|
-
const candidate = path.startsWith("/") ? path :
|
|
17258
|
+
const candidate = path.startsWith("/") ? path : resolve10(root, path);
|
|
17024
17259
|
if (!isPathInside3(root, candidate))
|
|
17025
17260
|
return matchesPattern3(path, pattern);
|
|
17026
17261
|
return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
|
|
@@ -17367,7 +17602,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
|
|
|
17367
17602
|
if (action === "approved" || action === "rejected" || action === "expired") {
|
|
17368
17603
|
emitLocalEventHooksQuiet({
|
|
17369
17604
|
type: "approval.decided",
|
|
17370
|
-
payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id }
|
|
17605
|
+
payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
|
|
17606
|
+
databasePath: databasePathFromDatabase(db)
|
|
17371
17607
|
});
|
|
17372
17608
|
}
|
|
17373
17609
|
}
|
|
@@ -17507,6 +17743,7 @@ var init_approval_gates = __esm(() => {
|
|
|
17507
17743
|
init_task_runs();
|
|
17508
17744
|
init_tasks();
|
|
17509
17745
|
init_types();
|
|
17746
|
+
init_event_emission_safety();
|
|
17510
17747
|
init_event_hooks();
|
|
17511
17748
|
});
|
|
17512
17749
|
|
|
@@ -20073,7 +20310,7 @@ function canonicalize(value) {
|
|
|
20073
20310
|
function hash(value) {
|
|
20074
20311
|
return createHash4("sha256").update(value).digest("hex");
|
|
20075
20312
|
}
|
|
20076
|
-
function
|
|
20313
|
+
function parsePayload2(value) {
|
|
20077
20314
|
if (!value)
|
|
20078
20315
|
return {};
|
|
20079
20316
|
try {
|
|
@@ -20172,7 +20409,7 @@ function taskScopedRows(db, scope) {
|
|
|
20172
20409
|
FROM handoffs h
|
|
20173
20410
|
WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
|
|
20174
20411
|
`).all(scope.project_id ?? null).filter((row) => {
|
|
20175
|
-
const payload =
|
|
20412
|
+
const payload = parsePayload2(row.payload_json);
|
|
20176
20413
|
const taskRefs = parseStringArray(payload["task_ids"]);
|
|
20177
20414
|
const runRefs = parseStringArray(payload["run_ids"]);
|
|
20178
20415
|
if (scope.project_id && row.project_id !== scope.project_id)
|
|
@@ -20203,7 +20440,7 @@ function toLedgerEntries(rows) {
|
|
|
20203
20440
|
});
|
|
20204
20441
|
let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
|
|
20205
20442
|
return ordered.map((row, index) => {
|
|
20206
|
-
const payload =
|
|
20443
|
+
const payload = parsePayload2(row.payload_json);
|
|
20207
20444
|
const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
|
|
20208
20445
|
const chainHash = hash(`${previous}
|
|
20209
20446
|
${payloadHash}`);
|
|
@@ -20345,7 +20582,7 @@ var init_audit_ledger = __esm(() => {
|
|
|
20345
20582
|
|
|
20346
20583
|
// src/lib/release-compatibility.ts
|
|
20347
20584
|
import { readFileSync as readFileSync5 } from "fs";
|
|
20348
|
-
import { join as join7, resolve as
|
|
20585
|
+
import { join as join7, resolve as resolve11 } from "path";
|
|
20349
20586
|
import { Database as Database2 } from "bun:sqlite";
|
|
20350
20587
|
function pass(id, message, details) {
|
|
20351
20588
|
return { id, status: "passed", message, details };
|
|
@@ -20453,7 +20690,7 @@ function checkChangelog() {
|
|
|
20453
20690
|
];
|
|
20454
20691
|
}
|
|
20455
20692
|
function createReleaseCompatibilityReport(options = {}) {
|
|
20456
|
-
const root =
|
|
20693
|
+
const root = resolve11(options.root ?? process.cwd());
|
|
20457
20694
|
const packageJson = readPackageJson2(root);
|
|
20458
20695
|
const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
|
|
20459
20696
|
const checks = [
|
|
@@ -26833,7 +27070,7 @@ function classifyLog(text) {
|
|
|
26833
27070
|
async function sleep2(ms) {
|
|
26834
27071
|
if (ms <= 0)
|
|
26835
27072
|
return;
|
|
26836
|
-
await new Promise((
|
|
27073
|
+
await new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
26837
27074
|
}
|
|
26838
27075
|
async function runCommandProvider(provider, input) {
|
|
26839
27076
|
const commandTemplate = input.command || provider.command;
|
|
@@ -29586,7 +29823,7 @@ var init_local_bridge = __esm(() => {
|
|
|
29586
29823
|
// src/lib/local-backups.ts
|
|
29587
29824
|
import { createHash as createHash5 } from "crypto";
|
|
29588
29825
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
29589
|
-
import { dirname as dirname8, resolve as
|
|
29826
|
+
import { dirname as dirname8, resolve as resolve12 } from "path";
|
|
29590
29827
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
29591
29828
|
function stableJson(value) {
|
|
29592
29829
|
if (value === null || typeof value !== "object")
|
|
@@ -29688,14 +29925,14 @@ function createLocalBackup(options = {}, db) {
|
|
|
29688
29925
|
return backup;
|
|
29689
29926
|
}
|
|
29690
29927
|
function writeLocalBackupFile(backup, outputPath) {
|
|
29691
|
-
const path =
|
|
29928
|
+
const path = resolve12(outputPath);
|
|
29692
29929
|
mkdirSync6(dirname8(path), { recursive: true });
|
|
29693
29930
|
writeFileSync3(path, `${JSON.stringify(backup, null, 2)}
|
|
29694
29931
|
`);
|
|
29695
29932
|
return path;
|
|
29696
29933
|
}
|
|
29697
29934
|
function readLocalBackupFile(path) {
|
|
29698
|
-
return JSON.parse(readFileSync8(
|
|
29935
|
+
return JSON.parse(readFileSync8(resolve12(path), "utf-8"));
|
|
29699
29936
|
}
|
|
29700
29937
|
function verifyLocalBackup(value, options = {}, db) {
|
|
29701
29938
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -31145,7 +31382,7 @@ __export(exports_local_extensions, {
|
|
|
31145
31382
|
});
|
|
31146
31383
|
import { createHash as createHash8, createVerify } from "crypto";
|
|
31147
31384
|
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
31148
|
-
import { basename as basename5, join as join10, resolve as
|
|
31385
|
+
import { basename as basename5, join as join10, resolve as resolve13 } from "path";
|
|
31149
31386
|
function isObject2(value) {
|
|
31150
31387
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
31151
31388
|
}
|
|
@@ -31403,7 +31640,7 @@ function verifyExtensionSignature(input) {
|
|
|
31403
31640
|
return verifier.verify(input.public_key, decodeSignature(input.signature));
|
|
31404
31641
|
}
|
|
31405
31642
|
function inspectExtensionSource(source3) {
|
|
31406
|
-
const resolved =
|
|
31643
|
+
const resolved = resolve13(source3);
|
|
31407
31644
|
if (!existsSync13(resolved))
|
|
31408
31645
|
throw new Error(`extension source not found: ${source3}`);
|
|
31409
31646
|
const stat = statSync6(resolved);
|
|
@@ -31501,7 +31738,7 @@ function testExtensionCompatibility(sourceOrManifest) {
|
|
|
31501
31738
|
function projectExtensionSources(projectPath) {
|
|
31502
31739
|
if (!projectPath)
|
|
31503
31740
|
return [];
|
|
31504
|
-
const root =
|
|
31741
|
+
const root = resolve13(projectPath);
|
|
31505
31742
|
const candidates = [
|
|
31506
31743
|
join10(root, "todos.extension.json"),
|
|
31507
31744
|
join10(root, ".todos", "todos.extension.json")
|
|
@@ -31520,7 +31757,7 @@ function projectExtensionSources(projectPath) {
|
|
|
31520
31757
|
}
|
|
31521
31758
|
function discoverLocalExtensions(options = {}) {
|
|
31522
31759
|
const config = loadConfig();
|
|
31523
|
-
const projectPath = options.project_path ?
|
|
31760
|
+
const projectPath = options.project_path ? resolve13(options.project_path) : null;
|
|
31524
31761
|
const configuredSources = [
|
|
31525
31762
|
...config.extension_sources || [],
|
|
31526
31763
|
...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
|
|
@@ -31528,7 +31765,7 @@ function discoverLocalExtensions(options = {}) {
|
|
|
31528
31765
|
const sources = Array.from(new Set([
|
|
31529
31766
|
...configuredSources,
|
|
31530
31767
|
...projectExtensionSources(projectPath || undefined)
|
|
31531
|
-
])).map((source3) => projectPath && !source3.startsWith("/") ?
|
|
31768
|
+
])).map((source3) => projectPath && !source3.startsWith("/") ? resolve13(projectPath, source3) : resolve13(source3));
|
|
31532
31769
|
const warnings = [];
|
|
31533
31770
|
const discovered = [];
|
|
31534
31771
|
for (const source3 of sources) {
|
|
@@ -34478,7 +34715,8 @@ function writeQueue(task2, queue, actor, action, db) {
|
|
|
34478
34715
|
logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
|
|
34479
34716
|
emitLocalEventHooksQuiet({
|
|
34480
34717
|
type: `review.${action}`,
|
|
34481
|
-
payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by }
|
|
34718
|
+
payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by },
|
|
34719
|
+
databasePath: databasePathFromDatabase(d)
|
|
34482
34720
|
});
|
|
34483
34721
|
return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
|
|
34484
34722
|
}
|
|
@@ -34782,6 +35020,7 @@ var init_review_queues = __esm(() => {
|
|
|
34782
35020
|
init_database();
|
|
34783
35021
|
init_tasks();
|
|
34784
35022
|
init_config();
|
|
35023
|
+
init_event_emission_safety();
|
|
34785
35024
|
init_event_hooks();
|
|
34786
35025
|
init_task_contracts();
|
|
34787
35026
|
STATES = new Set(["requested", "claimed", "approved", "changes_requested", "returned", "reopened"]);
|
|
@@ -35937,7 +36176,7 @@ __export(exports_extract, {
|
|
|
35937
36176
|
});
|
|
35938
36177
|
import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
35939
36178
|
import { createHash as createHash10 } from "crypto";
|
|
35940
|
-
import { relative as relative5, resolve as
|
|
36179
|
+
import { relative as relative5, resolve as resolve14, join as join11 } from "path";
|
|
35941
36180
|
function stableHash(value) {
|
|
35942
36181
|
return createHash10("sha256").update(value).digest("hex");
|
|
35943
36182
|
}
|
|
@@ -35945,7 +36184,7 @@ function normalizePathForMatch(value) {
|
|
|
35945
36184
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
35946
36185
|
}
|
|
35947
36186
|
function readGitignorePatterns(basePath) {
|
|
35948
|
-
const root = statSync7(basePath).isFile() ?
|
|
36187
|
+
const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
|
|
35949
36188
|
const gitignorePath = join11(root, ".gitignore");
|
|
35950
36189
|
if (!existsSync14(gitignorePath))
|
|
35951
36190
|
return [];
|
|
@@ -36081,7 +36320,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
|
|
|
36081
36320
|
return files.sort();
|
|
36082
36321
|
}
|
|
36083
36322
|
function buildCodebaseIndex(options) {
|
|
36084
|
-
const basePath =
|
|
36323
|
+
const basePath = resolve14(options.path);
|
|
36085
36324
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
36086
36325
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
36087
36326
|
const excludes = options.exclude || [];
|
|
@@ -36092,7 +36331,7 @@ function buildCodebaseIndex(options) {
|
|
|
36092
36331
|
const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
|
|
36093
36332
|
try {
|
|
36094
36333
|
const source3 = readFileSync10(fullPath, "utf-8");
|
|
36095
|
-
const relPath = statSync7(basePath).isFile() ? relative5(
|
|
36334
|
+
const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
|
|
36096
36335
|
indexed.push({
|
|
36097
36336
|
file: relPath,
|
|
36098
36337
|
checksum: stableHash(source3).slice(0, 24),
|
|
@@ -36112,7 +36351,7 @@ function buildCodebaseIndex(options) {
|
|
|
36112
36351
|
};
|
|
36113
36352
|
}
|
|
36114
36353
|
function extractTodos(options, db) {
|
|
36115
|
-
const basePath =
|
|
36354
|
+
const basePath = resolve14(options.path);
|
|
36116
36355
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
36117
36356
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
36118
36357
|
const excludes = options.exclude || [];
|
|
@@ -36123,7 +36362,7 @@ function extractTodos(options, db) {
|
|
|
36123
36362
|
const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
|
|
36124
36363
|
try {
|
|
36125
36364
|
const source3 = readFileSync10(fullPath, "utf-8");
|
|
36126
|
-
const relPath = statSync7(basePath).isFile() ? relative5(
|
|
36365
|
+
const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
|
|
36127
36366
|
const comments = extractFromSource(source3, relPath, tags);
|
|
36128
36367
|
allComments.push(...comments);
|
|
36129
36368
|
} catch {}
|
|
@@ -36217,7 +36456,7 @@ async function watchSourceTodos(options, onRun) {
|
|
|
36217
36456
|
const interval = Math.max(100, options.interval_ms || 2000);
|
|
36218
36457
|
const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
|
|
36219
36458
|
const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
|
|
36220
|
-
const root =
|
|
36459
|
+
const root = resolve14(options.path);
|
|
36221
36460
|
const runs = [];
|
|
36222
36461
|
let previous = new Map;
|
|
36223
36462
|
for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
|
|
@@ -37600,8 +37839,8 @@ __export(exports_environment_snapshots, {
|
|
|
37600
37839
|
import { createHash as createHash11 } from "crypto";
|
|
37601
37840
|
import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
|
|
37602
37841
|
import { hostname as hostname2, platform, arch } from "os";
|
|
37603
|
-
import { dirname as dirname9, join as join13, resolve as
|
|
37604
|
-
import { tmpdir as
|
|
37842
|
+
import { dirname as dirname9, join as join13, resolve as resolve15 } from "path";
|
|
37843
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
37605
37844
|
function sha2566(value) {
|
|
37606
37845
|
return createHash11("sha256").update(value).digest("hex");
|
|
37607
37846
|
}
|
|
@@ -37714,15 +37953,15 @@ function commandEnv(env, includeValues) {
|
|
|
37714
37953
|
function defaultSnapshotDir() {
|
|
37715
37954
|
const dbPath = getDatabasePath();
|
|
37716
37955
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
37717
|
-
return join13(
|
|
37718
|
-
return join13(dirname9(
|
|
37956
|
+
return join13(tmpdir3(), "hasna-todos", "environment-snapshots");
|
|
37957
|
+
return join13(dirname9(resolve15(dbPath)), "environment-snapshots");
|
|
37719
37958
|
}
|
|
37720
37959
|
function snapshotWithId(snapshot) {
|
|
37721
37960
|
const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
|
|
37722
37961
|
return { id: `env_${digest}`, ...snapshot };
|
|
37723
37962
|
}
|
|
37724
37963
|
function captureEnvironmentSnapshot(input = {}) {
|
|
37725
|
-
const root =
|
|
37964
|
+
const root = resolve15(input.root || process.cwd());
|
|
37726
37965
|
const env = input.env || process.env;
|
|
37727
37966
|
const warnings = [];
|
|
37728
37967
|
const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
|
|
@@ -37762,13 +38001,13 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
37762
38001
|
});
|
|
37763
38002
|
}
|
|
37764
38003
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
37765
|
-
const path = outputPath ?
|
|
38004
|
+
const path = outputPath ? resolve15(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
37766
38005
|
ensureDir2(dirname9(path));
|
|
37767
38006
|
writeJsonFile(path, snapshot);
|
|
37768
38007
|
return path;
|
|
37769
38008
|
}
|
|
37770
38009
|
function readEnvironmentSnapshot(path) {
|
|
37771
|
-
const snapshot = readJsonFile(
|
|
38010
|
+
const snapshot = readJsonFile(resolve15(path));
|
|
37772
38011
|
if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
|
|
37773
38012
|
throw new Error(`Invalid environment snapshot: ${path}`);
|
|
37774
38013
|
}
|
|
@@ -38314,7 +38553,7 @@ var init_headless_boundaries = __esm(() => {
|
|
|
38314
38553
|
});
|
|
38315
38554
|
|
|
38316
38555
|
// src/server/routes.ts
|
|
38317
|
-
import { join as join14, resolve as
|
|
38556
|
+
import { join as join14, resolve as resolve16, sep as sep3 } from "path";
|
|
38318
38557
|
function parseFieldsParam(url) {
|
|
38319
38558
|
const fieldsParam = url.searchParams.get("fields");
|
|
38320
38559
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -39052,9 +39291,9 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
39052
39291
|
return null;
|
|
39053
39292
|
if (path !== "/") {
|
|
39054
39293
|
const filePath = join14(ctx.dashboardDir, path);
|
|
39055
|
-
const resolvedFile =
|
|
39056
|
-
const resolvedBase =
|
|
39057
|
-
if (!resolvedFile.startsWith(resolvedBase +
|
|
39294
|
+
const resolvedFile = resolve16(filePath);
|
|
39295
|
+
const resolvedBase = resolve16(ctx.dashboardDir);
|
|
39296
|
+
if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
|
|
39058
39297
|
return json2({ error: "Forbidden" }, 403);
|
|
39059
39298
|
}
|
|
39060
39299
|
const res2 = serveStaticFile2(filePath);
|