@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/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,40 +9588,10 @@ 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
|
-
// src/lib/
|
|
9417
|
-
function taskEventData(task, extra = {}) {
|
|
9418
|
-
return {
|
|
9419
|
-
id: task.id,
|
|
9420
|
-
task_id: task.id,
|
|
9421
|
-
short_id: task.short_id,
|
|
9422
|
-
title: task.title,
|
|
9423
|
-
description: task.description,
|
|
9424
|
-
status: task.status,
|
|
9425
|
-
priority: task.priority,
|
|
9426
|
-
project_id: task.project_id,
|
|
9427
|
-
parent_id: task.parent_id,
|
|
9428
|
-
plan_id: task.plan_id,
|
|
9429
|
-
task_list_id: task.task_list_id,
|
|
9430
|
-
agent_id: task.agent_id,
|
|
9431
|
-
assigned_to: task.assigned_to,
|
|
9432
|
-
session_id: task.session_id,
|
|
9433
|
-
working_dir: task.working_dir,
|
|
9434
|
-
tags: task.tags,
|
|
9435
|
-
metadata: task.metadata,
|
|
9436
|
-
version: task.version,
|
|
9437
|
-
created_at: task.created_at,
|
|
9438
|
-
updated_at: task.updated_at,
|
|
9439
|
-
started_at: task.started_at,
|
|
9440
|
-
completed_at: task.completed_at,
|
|
9441
|
-
due_at: task.due_at,
|
|
9442
|
-
requires_approval: task.requires_approval,
|
|
9443
|
-
approved_by: task.approved_by,
|
|
9444
|
-
approved_at: task.approved_at,
|
|
9445
|
-
...extra
|
|
9446
|
-
};
|
|
9447
|
-
}
|
|
9594
|
+
// src/lib/task-route-contract.ts
|
|
9448
9595
|
function booleanField(value) {
|
|
9449
9596
|
if (typeof value === "boolean")
|
|
9450
9597
|
return value;
|
|
@@ -9466,21 +9613,31 @@ function booleanField(value) {
|
|
|
9466
9613
|
function objectField(value) {
|
|
9467
9614
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
9468
9615
|
}
|
|
9469
|
-
function
|
|
9616
|
+
function collectBooleans(records, keys) {
|
|
9617
|
+
const values = [];
|
|
9470
9618
|
for (const record of records) {
|
|
9619
|
+
if (!record)
|
|
9620
|
+
continue;
|
|
9471
9621
|
for (const key of keys) {
|
|
9472
9622
|
const value = booleanField(record[key]);
|
|
9473
9623
|
if (value !== undefined)
|
|
9474
|
-
|
|
9624
|
+
values.push(value);
|
|
9475
9625
|
}
|
|
9476
9626
|
}
|
|
9477
|
-
return;
|
|
9627
|
+
return values;
|
|
9478
9628
|
}
|
|
9479
|
-
function
|
|
9480
|
-
const
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9629
|
+
function mergedBoolean(records, keys, trueWins) {
|
|
9630
|
+
const values = collectBooleans(records, keys);
|
|
9631
|
+
if (values.length === 0)
|
|
9632
|
+
return;
|
|
9633
|
+
if (trueWins)
|
|
9634
|
+
return values.some(Boolean);
|
|
9635
|
+
return values.includes(false) ? false : true;
|
|
9636
|
+
}
|
|
9637
|
+
function routingAutomationMetadata(task, taskList) {
|
|
9638
|
+
const taskAutomation = objectField(task.metadata.automation);
|
|
9639
|
+
const taskListAutomation = taskList ? objectField(taskList.metadata.automation) : undefined;
|
|
9640
|
+
const records = [task.metadata, taskAutomation, taskList?.metadata, taskListAutomation];
|
|
9484
9641
|
const result = {};
|
|
9485
9642
|
const aliases = [
|
|
9486
9643
|
["allowed", ["allowed", "automation_allowed", "automationAllowed"]],
|
|
@@ -9491,7 +9648,7 @@ function routingAutomationMetadata(task) {
|
|
|
9491
9648
|
["approval_required", ["approval_required", "approvalRequired"]]
|
|
9492
9649
|
];
|
|
9493
9650
|
for (const [canonical, keys] of aliases) {
|
|
9494
|
-
const value =
|
|
9651
|
+
const value = mergedBoolean(records, keys, canonical !== "allowed");
|
|
9495
9652
|
if (value !== undefined)
|
|
9496
9653
|
result[canonical] = value;
|
|
9497
9654
|
}
|
|
@@ -9499,23 +9656,88 @@ function routingAutomationMetadata(task) {
|
|
|
9499
9656
|
result.requires_approval = true;
|
|
9500
9657
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
9501
9658
|
}
|
|
9659
|
+
function routeEnabledForTask(task, taskList) {
|
|
9660
|
+
const explicit = booleanField(task.metadata.route_enabled);
|
|
9661
|
+
if (explicit !== undefined)
|
|
9662
|
+
return explicit;
|
|
9663
|
+
if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
|
|
9664
|
+
return true;
|
|
9665
|
+
const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
|
|
9666
|
+
if (taskListDefault !== undefined)
|
|
9667
|
+
return taskListDefault;
|
|
9668
|
+
return;
|
|
9669
|
+
}
|
|
9670
|
+
function stringField(value) {
|
|
9671
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
9672
|
+
}
|
|
9673
|
+
function workflowPointersFromMetadata(metadata) {
|
|
9674
|
+
const nested = objectField(metadata.workflow_invocation) ?? objectField(metadata.workflow) ?? {};
|
|
9675
|
+
return {
|
|
9676
|
+
current_workflow_invocation_id: stringField(metadata.current_workflow_invocation_id) ?? stringField(nested.current_workflow_invocation_id) ?? stringField(nested.invocation_id),
|
|
9677
|
+
current_run_id: stringField(metadata.current_run_id) ?? stringField(nested.current_run_id) ?? stringField(nested.run_id),
|
|
9678
|
+
latest_manifest_path: stringField(metadata.latest_manifest_path) ?? stringField(nested.latest_manifest_path) ?? stringField(nested.manifest_path),
|
|
9679
|
+
latest_evaluation_path: stringField(metadata.latest_evaluation_path) ?? stringField(nested.latest_evaluation_path) ?? stringField(nested.evaluation_path),
|
|
9680
|
+
workflow_state: stringField(metadata.workflow_state) ?? stringField(nested.workflow_state) ?? stringField(nested.state)
|
|
9681
|
+
};
|
|
9682
|
+
}
|
|
9683
|
+
function classifyProjectKind(path) {
|
|
9684
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
9685
|
+
}
|
|
9686
|
+
function isWorktreePath(path) {
|
|
9687
|
+
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
9688
|
+
}
|
|
9689
|
+
function inferRootProjectId(project) {
|
|
9690
|
+
return isWorktreePath(project.path) ? null : project.id;
|
|
9691
|
+
}
|
|
9692
|
+
var TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION = "todos.task_route_state.v1";
|
|
9693
|
+
|
|
9694
|
+
// src/lib/shared-events.ts
|
|
9695
|
+
function taskEventData(task, extra = {}) {
|
|
9696
|
+
return {
|
|
9697
|
+
id: task.id,
|
|
9698
|
+
task_id: task.id,
|
|
9699
|
+
short_id: task.short_id,
|
|
9700
|
+
title: task.title,
|
|
9701
|
+
description: task.description,
|
|
9702
|
+
status: task.status,
|
|
9703
|
+
priority: task.priority,
|
|
9704
|
+
project_id: task.project_id,
|
|
9705
|
+
parent_id: task.parent_id,
|
|
9706
|
+
plan_id: task.plan_id,
|
|
9707
|
+
task_list_id: task.task_list_id,
|
|
9708
|
+
agent_id: task.agent_id,
|
|
9709
|
+
assigned_to: task.assigned_to,
|
|
9710
|
+
session_id: task.session_id,
|
|
9711
|
+
working_dir: task.working_dir,
|
|
9712
|
+
tags: task.tags,
|
|
9713
|
+
metadata: task.metadata,
|
|
9714
|
+
version: task.version,
|
|
9715
|
+
created_at: task.created_at,
|
|
9716
|
+
updated_at: task.updated_at,
|
|
9717
|
+
started_at: task.started_at,
|
|
9718
|
+
completed_at: task.completed_at,
|
|
9719
|
+
due_at: task.due_at,
|
|
9720
|
+
requires_approval: task.requires_approval,
|
|
9721
|
+
approved_by: task.approved_by,
|
|
9722
|
+
approved_at: task.approved_at,
|
|
9723
|
+
...extra
|
|
9724
|
+
};
|
|
9725
|
+
}
|
|
9502
9726
|
function taskEventMetadata(task) {
|
|
9503
9727
|
const metadata = {
|
|
9504
9728
|
package: "@hasna/todos",
|
|
9505
9729
|
todos_event_schema_version: 1,
|
|
9730
|
+
route_state_schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
|
|
9506
9731
|
task_id: task.id,
|
|
9507
9732
|
task_short_id: task.short_id,
|
|
9508
9733
|
project_id: task.project_id,
|
|
9509
9734
|
task_list_id: task.task_list_id,
|
|
9510
9735
|
working_dir: task.working_dir
|
|
9511
9736
|
};
|
|
9512
|
-
const
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
const automation = routingAutomationMetadata(task);
|
|
9517
|
-
if (automation) {
|
|
9518
|
-
metadata.automation = automation;
|
|
9737
|
+
const pointers = workflowPointersFromMetadata(task.metadata);
|
|
9738
|
+
for (const [key, value] of Object.entries(pointers)) {
|
|
9739
|
+
if (value)
|
|
9740
|
+
metadata[key] = value;
|
|
9519
9741
|
}
|
|
9520
9742
|
try {
|
|
9521
9743
|
const project = task.project_id ? getProject(task.project_id) : null;
|
|
@@ -9544,18 +9766,20 @@ function taskEventMetadata(task) {
|
|
|
9544
9766
|
metadata.task_list_project_id = taskList.project_id;
|
|
9545
9767
|
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
9546
9768
|
}
|
|
9769
|
+
const routeEnabled = routeEnabledForTask(task, taskList);
|
|
9770
|
+
if (routeEnabled !== undefined) {
|
|
9771
|
+
metadata.route_enabled = routeEnabled;
|
|
9772
|
+
}
|
|
9773
|
+
const automation = routingAutomationMetadata(task, taskList);
|
|
9774
|
+
if (automation) {
|
|
9775
|
+
metadata.automation = automation;
|
|
9776
|
+
metadata.route_blocked_by_no_auto = automation.no_auto === true;
|
|
9777
|
+
metadata.route_blocked_by_manual = automation.manual === true || automation.manual_required === true;
|
|
9778
|
+
metadata.route_blocked_by_approval = (automation.requires_approval === true || automation.approval_required === true) && !task.approved_by;
|
|
9779
|
+
}
|
|
9547
9780
|
} catch {}
|
|
9548
9781
|
return metadata;
|
|
9549
9782
|
}
|
|
9550
|
-
function classifyProjectKind(path) {
|
|
9551
|
-
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
9552
|
-
}
|
|
9553
|
-
function isWorktreePath(path) {
|
|
9554
|
-
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
9555
|
-
}
|
|
9556
|
-
function inferRootProjectId(project) {
|
|
9557
|
-
return isWorktreePath(project.path) ? null : project.id;
|
|
9558
|
-
}
|
|
9559
9783
|
function readMachineLocalPath(project) {
|
|
9560
9784
|
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
9561
9785
|
if (!machineId)
|
|
@@ -9568,6 +9792,8 @@ function readMachineLocalPath(project) {
|
|
|
9568
9792
|
}
|
|
9569
9793
|
}
|
|
9570
9794
|
async function emitSharedTaskEvent(input) {
|
|
9795
|
+
if (!shouldEmitSharedTaskEvents(input.databasePath))
|
|
9796
|
+
return;
|
|
9571
9797
|
const data = taskEventData(input.task, input.data);
|
|
9572
9798
|
await new EventsClient().emit({
|
|
9573
9799
|
source: SOURCE,
|
|
@@ -9591,6 +9817,7 @@ var init_shared_events = __esm(() => {
|
|
|
9591
9817
|
init_database();
|
|
9592
9818
|
init_projects();
|
|
9593
9819
|
init_task_lists();
|
|
9820
|
+
init_event_emission_safety();
|
|
9594
9821
|
});
|
|
9595
9822
|
|
|
9596
9823
|
// src/lib/secret-redaction.ts
|
|
@@ -9839,8 +10066,9 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
|
9839
10066
|
const d = db || getDatabase();
|
|
9840
10067
|
const id = uuid();
|
|
9841
10068
|
const timestamp = now();
|
|
9842
|
-
|
|
9843
|
-
|
|
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]);
|
|
9844
10072
|
try {
|
|
9845
10073
|
const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
|
|
9846
10074
|
logActivity2({
|
|
@@ -9853,7 +10081,7 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
|
9853
10081
|
actor_id: agentId ?? undefined
|
|
9854
10082
|
}, d);
|
|
9855
10083
|
} catch {}
|
|
9856
|
-
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 };
|
|
9857
10085
|
}
|
|
9858
10086
|
function getTaskHistory(taskId, db) {
|
|
9859
10087
|
const d = db || getDatabase();
|
|
@@ -9865,6 +10093,7 @@ function getRecentActivity(limit = 50, db) {
|
|
|
9865
10093
|
}
|
|
9866
10094
|
var init_audit = __esm(() => {
|
|
9867
10095
|
init_database();
|
|
10096
|
+
init_storage_tombstones();
|
|
9868
10097
|
});
|
|
9869
10098
|
|
|
9870
10099
|
// src/db/webhooks.ts
|
|
@@ -10130,13 +10359,14 @@ function createTask(input, db) {
|
|
|
10130
10359
|
const d = db || getDatabase();
|
|
10131
10360
|
const timestamp = now();
|
|
10132
10361
|
const tags = input.tags || [];
|
|
10362
|
+
const machineId = currentStorageMachineId(d);
|
|
10133
10363
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
10134
10364
|
const assignedFromProject = input.assigned_from_project || null;
|
|
10135
10365
|
let id = uuid();
|
|
10136
10366
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
10137
10367
|
try {
|
|
10138
|
-
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)
|
|
10139
|
-
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, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10140
10370
|
id,
|
|
10141
10371
|
null,
|
|
10142
10372
|
input.project_id || null,
|
|
@@ -10173,7 +10403,8 @@ function createTask(input, db) {
|
|
|
10173
10403
|
input.spawned_from_session || null,
|
|
10174
10404
|
assignedBy || null,
|
|
10175
10405
|
assignedFromProject || null,
|
|
10176
|
-
input.task_type || null
|
|
10406
|
+
input.task_type || null,
|
|
10407
|
+
machineId
|
|
10177
10408
|
]);
|
|
10178
10409
|
break;
|
|
10179
10410
|
} catch (e) {
|
|
@@ -10189,9 +10420,10 @@ function createTask(input, db) {
|
|
|
10189
10420
|
}
|
|
10190
10421
|
const task = getTask(id, d);
|
|
10191
10422
|
const payload = taskEventData(task);
|
|
10423
|
+
const databasePath = databasePathFromDatabase(d);
|
|
10192
10424
|
dispatchWebhook2("task.created", payload, d).catch(() => {});
|
|
10193
|
-
emitLocalEventHooksQuiet({ type: "task.created", payload });
|
|
10194
|
-
emitSharedTaskEventQuiet({ type: "task.created", task });
|
|
10425
|
+
emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
|
|
10426
|
+
emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
|
|
10195
10427
|
return task;
|
|
10196
10428
|
}
|
|
10197
10429
|
function getTask(id, db) {
|
|
@@ -10609,29 +10841,39 @@ function updateTask(id, input, db) {
|
|
|
10609
10841
|
approved_by: input.approved_by ?? task.approved_by,
|
|
10610
10842
|
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
10611
10843
|
};
|
|
10844
|
+
const databasePath = databasePathFromDatabase(d);
|
|
10612
10845
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
10613
10846
|
const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
|
|
10614
10847
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
10615
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
10616
|
-
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 });
|
|
10617
10850
|
}
|
|
10618
10851
|
if (input.status !== undefined && input.status !== task.status) {
|
|
10619
10852
|
const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
|
|
10620
10853
|
dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
|
|
10621
|
-
emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
|
|
10622
|
-
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 });
|
|
10623
10856
|
}
|
|
10624
10857
|
if (input.approved_by !== undefined) {
|
|
10625
|
-
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 });
|
|
10626
10859
|
}
|
|
10627
10860
|
const updatePayload = taskEventData(updatedTask);
|
|
10628
10861
|
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
10629
|
-
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
|
|
10630
|
-
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
|
|
10862
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
|
|
10863
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
|
|
10631
10864
|
return updatedTask;
|
|
10632
10865
|
}
|
|
10633
10866
|
function deleteTask(id, db) {
|
|
10634
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);
|
|
10635
10877
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
10636
10878
|
return result.changes > 0;
|
|
10637
10879
|
}
|
|
@@ -10639,11 +10881,13 @@ var init_task_crud = __esm(() => {
|
|
|
10639
10881
|
init_types();
|
|
10640
10882
|
init_database();
|
|
10641
10883
|
init_completion_guard();
|
|
10884
|
+
init_event_emission_safety();
|
|
10642
10885
|
init_event_hooks();
|
|
10643
10886
|
init_shared_events();
|
|
10644
10887
|
init_audit();
|
|
10645
10888
|
init_webhooks();
|
|
10646
10889
|
init_checklists();
|
|
10890
|
+
init_storage_tombstones();
|
|
10647
10891
|
});
|
|
10648
10892
|
|
|
10649
10893
|
// src/lib/recurrence.ts
|
|
@@ -10792,8 +11036,9 @@ function resolveTemplateId(id, d) {
|
|
|
10792
11036
|
function createTemplate(input, db) {
|
|
10793
11037
|
const d = db || getDatabase();
|
|
10794
11038
|
const id = uuid();
|
|
10795
|
-
|
|
10796
|
-
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10797
11042
|
id,
|
|
10798
11043
|
input.name,
|
|
10799
11044
|
input.title_pattern,
|
|
@@ -10804,7 +11049,8 @@ function createTemplate(input, db) {
|
|
|
10804
11049
|
input.project_id || null,
|
|
10805
11050
|
input.plan_id || null,
|
|
10806
11051
|
JSON.stringify(input.metadata || {}),
|
|
10807
|
-
now()
|
|
11052
|
+
now(),
|
|
11053
|
+
machineId
|
|
10808
11054
|
]);
|
|
10809
11055
|
if (input.tasks && input.tasks.length > 0) {
|
|
10810
11056
|
addTemplateTasks(id, input.tasks, d);
|
|
@@ -10828,6 +11074,15 @@ function deleteTemplate(id, db) {
|
|
|
10828
11074
|
const resolved = resolveTemplateId(id, d);
|
|
10829
11075
|
if (!resolved)
|
|
10830
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);
|
|
10831
11086
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
10832
11087
|
}
|
|
10833
11088
|
function updateTemplate(id, updates, db) {
|
|
@@ -11197,6 +11452,7 @@ function previewTemplate(templateId, variables, db) {
|
|
|
11197
11452
|
var init_templates = __esm(() => {
|
|
11198
11453
|
init_database();
|
|
11199
11454
|
init_tasks();
|
|
11455
|
+
init_storage_tombstones();
|
|
11200
11456
|
});
|
|
11201
11457
|
|
|
11202
11458
|
// src/db/task-graph.ts
|
|
@@ -11363,6 +11619,7 @@ function getBlockingDeps(id, db) {
|
|
|
11363
11619
|
}
|
|
11364
11620
|
function startTask(id, agentId, db) {
|
|
11365
11621
|
const d = db || getDatabase();
|
|
11622
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11366
11623
|
const task = getTask(id, d);
|
|
11367
11624
|
if (!task)
|
|
11368
11625
|
throw new TaskNotFoundError(id);
|
|
@@ -11377,7 +11634,8 @@ function startTask(id, agentId, db) {
|
|
|
11377
11634
|
agent_id: agentId,
|
|
11378
11635
|
title: task.title,
|
|
11379
11636
|
blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
|
|
11380
|
-
}
|
|
11637
|
+
},
|
|
11638
|
+
databasePath
|
|
11381
11639
|
});
|
|
11382
11640
|
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
11383
11641
|
}
|
|
@@ -11399,12 +11657,13 @@ function startTask(id, agentId, db) {
|
|
|
11399
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 };
|
|
11400
11658
|
const payload = taskEventData(startedTask, { agent_id: agentId });
|
|
11401
11659
|
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
11402
|
-
emitLocalEventHooksQuiet({ type: "task.started", payload });
|
|
11403
|
-
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 });
|
|
11404
11662
|
return startedTask;
|
|
11405
11663
|
}
|
|
11406
11664
|
function completeTask(id, agentId, db, options) {
|
|
11407
11665
|
const d = db || getDatabase();
|
|
11666
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11408
11667
|
const task = getTask(id, d);
|
|
11409
11668
|
if (!task)
|
|
11410
11669
|
throw new TaskNotFoundError(id);
|
|
@@ -11450,8 +11709,8 @@ function completeTask(id, agentId, db, options) {
|
|
|
11450
11709
|
};
|
|
11451
11710
|
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
|
|
11452
11711
|
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
11453
|
-
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
|
|
11454
|
-
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 });
|
|
11455
11714
|
let spawnedTask = null;
|
|
11456
11715
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
11457
11716
|
spawnedTask = spawnNextRecurrence(task, d, timestamp);
|
|
@@ -11495,9 +11754,9 @@ function completeTask(id, agentId, db, options) {
|
|
|
11495
11754
|
const depTask = getTask(dep.id, d);
|
|
11496
11755
|
const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
|
|
11497
11756
|
dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
|
|
11498
|
-
emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
|
|
11757
|
+
emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
|
|
11499
11758
|
if (depTask)
|
|
11500
|
-
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
|
|
11759
|
+
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
|
|
11501
11760
|
}
|
|
11502
11761
|
}
|
|
11503
11762
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
@@ -11667,6 +11926,7 @@ function getTasksChangedSince(since, filters, db) {
|
|
|
11667
11926
|
}
|
|
11668
11927
|
function failTask(id, agentId, reason, options, db) {
|
|
11669
11928
|
const d = db || getDatabase();
|
|
11929
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11670
11930
|
const task = getTask(id, d);
|
|
11671
11931
|
if (!task)
|
|
11672
11932
|
throw new TaskNotFoundError(id);
|
|
@@ -11695,8 +11955,8 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
11695
11955
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
11696
11956
|
const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
|
|
11697
11957
|
dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
|
|
11698
|
-
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
|
|
11699
|
-
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 });
|
|
11700
11960
|
let retryTask;
|
|
11701
11961
|
if (options?.retry) {
|
|
11702
11962
|
const retryCount = (task.retry_count || 0) + 1;
|
|
@@ -11756,6 +12016,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
|
|
|
11756
12016
|
}
|
|
11757
12017
|
function stealTask(agentId, opts, db) {
|
|
11758
12018
|
const d = db || getDatabase();
|
|
12019
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11759
12020
|
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
11760
12021
|
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
11761
12022
|
if (staleTasks.length === 0)
|
|
@@ -11774,8 +12035,8 @@ function stealTask(agentId, opts, db) {
|
|
|
11774
12035
|
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
11775
12036
|
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
11776
12037
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
11777
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
11778
|
-
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 });
|
|
11779
12040
|
return stolenTask;
|
|
11780
12041
|
}
|
|
11781
12042
|
function claimOrSteal(agentId, filters, db) {
|
|
@@ -11823,6 +12084,7 @@ var init_task_lifecycle = __esm(() => {
|
|
|
11823
12084
|
init_types();
|
|
11824
12085
|
init_database();
|
|
11825
12086
|
init_completion_guard();
|
|
12087
|
+
init_event_emission_safety();
|
|
11826
12088
|
init_event_hooks();
|
|
11827
12089
|
init_shared_events();
|
|
11828
12090
|
init_audit();
|
|
@@ -12530,8 +12792,9 @@ function createPlan(input, db) {
|
|
|
12530
12792
|
const d = db || getDatabase();
|
|
12531
12793
|
const id = uuid();
|
|
12532
12794
|
const timestamp = now();
|
|
12533
|
-
|
|
12534
|
-
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
12535
12798
|
id,
|
|
12536
12799
|
input.project_id || null,
|
|
12537
12800
|
input.task_list_id || null,
|
|
@@ -12540,7 +12803,8 @@ function createPlan(input, db) {
|
|
|
12540
12803
|
input.description || null,
|
|
12541
12804
|
input.status || "active",
|
|
12542
12805
|
timestamp,
|
|
12543
|
-
timestamp
|
|
12806
|
+
timestamp,
|
|
12807
|
+
machineId
|
|
12544
12808
|
]);
|
|
12545
12809
|
return getPlan(id, d);
|
|
12546
12810
|
}
|
|
@@ -12588,19 +12852,30 @@ function updatePlan(id, input, db) {
|
|
|
12588
12852
|
const updated = getPlan(id, d);
|
|
12589
12853
|
emitLocalEventHooksQuiet({
|
|
12590
12854
|
type: "plan.updated",
|
|
12591
|
-
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)
|
|
12592
12857
|
});
|
|
12593
12858
|
return updated;
|
|
12594
12859
|
}
|
|
12595
12860
|
function deletePlan(id, db) {
|
|
12596
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);
|
|
12597
12870
|
const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
|
|
12598
12871
|
return result.changes > 0;
|
|
12599
12872
|
}
|
|
12600
12873
|
var init_plans = __esm(() => {
|
|
12601
12874
|
init_types();
|
|
12875
|
+
init_event_emission_safety();
|
|
12602
12876
|
init_event_hooks();
|
|
12603
12877
|
init_database();
|
|
12878
|
+
init_storage_tombstones();
|
|
12604
12879
|
});
|
|
12605
12880
|
|
|
12606
12881
|
// src/db/boards.ts
|
|
@@ -13007,20 +13282,20 @@ var init_boards = __esm(() => {
|
|
|
13007
13282
|
// src/lib/artifact-store.ts
|
|
13008
13283
|
import { createHash as createHash2 } from "crypto";
|
|
13009
13284
|
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync2, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
13010
|
-
import { basename, dirname as dirname4, join as join5, resolve as
|
|
13011
|
-
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";
|
|
13012
13287
|
function isInMemoryDb2(path) {
|
|
13013
13288
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
13014
13289
|
}
|
|
13015
13290
|
function artifactStoreRoot() {
|
|
13016
13291
|
if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
|
|
13017
|
-
return
|
|
13292
|
+
return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
|
|
13018
13293
|
if (process.env["TODOS_ARTIFACTS_DIR"])
|
|
13019
|
-
return
|
|
13294
|
+
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
13020
13295
|
const dbPath = getDatabasePath();
|
|
13021
13296
|
if (isInMemoryDb2(dbPath))
|
|
13022
|
-
return join5(
|
|
13023
|
-
return join5(dirname4(
|
|
13297
|
+
return join5(tmpdir2(), "hasna-todos-artifacts");
|
|
13298
|
+
return join5(dirname4(resolve7(dbPath)), "artifacts");
|
|
13024
13299
|
}
|
|
13025
13300
|
function artifactStorePath(relativePath) {
|
|
13026
13301
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
@@ -13067,7 +13342,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
13067
13342
|
return "application/octet-stream";
|
|
13068
13343
|
}
|
|
13069
13344
|
function storeArtifactContent(input) {
|
|
13070
|
-
const sourcePath =
|
|
13345
|
+
const sourcePath = resolve7(input.path);
|
|
13071
13346
|
if (!existsSync6(sourcePath))
|
|
13072
13347
|
return null;
|
|
13073
13348
|
const sourceStat = statSync2(sourcePath);
|
|
@@ -13764,7 +14039,11 @@ function startTaskRun(input, db) {
|
|
|
13764
14039
|
}, d);
|
|
13765
14040
|
}
|
|
13766
14041
|
const run = getTaskRun(id, d);
|
|
13767
|
-
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
|
+
});
|
|
13768
14047
|
return run;
|
|
13769
14048
|
}
|
|
13770
14049
|
function beginTaskRunTransaction(input, db) {
|
|
@@ -14033,7 +14312,8 @@ function finishTaskRun(input, db) {
|
|
|
14033
14312
|
const updated = getTaskRun(run.id, d);
|
|
14034
14313
|
emitLocalEventHooksQuiet({
|
|
14035
14314
|
type: `run.${input.status}`,
|
|
14036
|
-
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)
|
|
14037
14317
|
});
|
|
14038
14318
|
return updated;
|
|
14039
14319
|
}
|
|
@@ -14117,6 +14397,7 @@ function getTaskRunLedger(runId, db) {
|
|
|
14117
14397
|
var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
|
|
14118
14398
|
var init_task_runs = __esm(() => {
|
|
14119
14399
|
init_artifact_store();
|
|
14400
|
+
init_event_emission_safety();
|
|
14120
14401
|
init_event_hooks();
|
|
14121
14402
|
init_redaction();
|
|
14122
14403
|
init_types();
|
|
@@ -15885,7 +16166,7 @@ var init_task_crud2 = __esm(() => {
|
|
|
15885
16166
|
|
|
15886
16167
|
// src/lib/project-bootstrap.ts
|
|
15887
16168
|
import { existsSync as existsSync7, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
15888
|
-
import { basename as basename2, dirname as dirname5, resolve as
|
|
16169
|
+
import { basename as basename2, dirname as dirname5, resolve as resolve8 } from "path";
|
|
15889
16170
|
function safeStat(path) {
|
|
15890
16171
|
try {
|
|
15891
16172
|
return statSync3(path);
|
|
@@ -15894,7 +16175,7 @@ function safeStat(path) {
|
|
|
15894
16175
|
}
|
|
15895
16176
|
}
|
|
15896
16177
|
function canonicalPath(input) {
|
|
15897
|
-
const resolved =
|
|
16178
|
+
const resolved = resolve8(input);
|
|
15898
16179
|
const stats = safeStat(resolved);
|
|
15899
16180
|
if (stats?.isFile())
|
|
15900
16181
|
return dirname5(resolved);
|
|
@@ -15903,7 +16184,7 @@ function canonicalPath(input) {
|
|
|
15903
16184
|
function findUp(start, marker) {
|
|
15904
16185
|
let current = canonicalPath(start);
|
|
15905
16186
|
while (true) {
|
|
15906
|
-
if (existsSync7(
|
|
16187
|
+
if (existsSync7(resolve8(current, marker)))
|
|
15907
16188
|
return current;
|
|
15908
16189
|
const parent = dirname5(current);
|
|
15909
16190
|
if (parent === current)
|
|
@@ -15914,7 +16195,7 @@ function findUp(start, marker) {
|
|
|
15914
16195
|
function readPackageJson(path) {
|
|
15915
16196
|
if (!path)
|
|
15916
16197
|
return null;
|
|
15917
|
-
const file =
|
|
16198
|
+
const file = resolve8(path, "package.json");
|
|
15918
16199
|
if (!existsSync7(file))
|
|
15919
16200
|
return null;
|
|
15920
16201
|
try {
|
|
@@ -15937,7 +16218,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
15937
16218
|
if (rootPackage?.workspaces)
|
|
15938
16219
|
markers.push("package.json#workspaces");
|
|
15939
16220
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
15940
|
-
if (existsSync7(
|
|
16221
|
+
if (existsSync7(resolve8(root, marker)))
|
|
15941
16222
|
markers.push(marker);
|
|
15942
16223
|
}
|
|
15943
16224
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -15999,7 +16280,19 @@ function bootstrapProject(options = {}, db) {
|
|
|
15999
16280
|
}
|
|
16000
16281
|
setMachineLocalPath(project.id, discovery.projectPath, d);
|
|
16001
16282
|
const beforeTaskList = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(project.id, taskListSlug);
|
|
16002
|
-
|
|
16283
|
+
let taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
|
|
16284
|
+
if (options.routeEnabled && taskList.metadata.route_enabled !== true) {
|
|
16285
|
+
taskList = updateTaskList(taskList.id, {
|
|
16286
|
+
metadata: {
|
|
16287
|
+
...taskList.metadata,
|
|
16288
|
+
route_enabled: true,
|
|
16289
|
+
automation: {
|
|
16290
|
+
...taskList.metadata.automation && typeof taskList.metadata.automation === "object" && !Array.isArray(taskList.metadata.automation) ? taskList.metadata.automation : {},
|
|
16291
|
+
no_auto: false
|
|
16292
|
+
}
|
|
16293
|
+
}
|
|
16294
|
+
}, d);
|
|
16295
|
+
}
|
|
16003
16296
|
const createdSources = [];
|
|
16004
16297
|
for (const source of [
|
|
16005
16298
|
addSourceOnce(project.id, "local", "Project root", discovery.projectPath, { role: "project-root" }, d),
|
|
@@ -16469,7 +16762,7 @@ var init_retention_cleanup = __esm(() => {
|
|
|
16469
16762
|
|
|
16470
16763
|
// src/lib/mention-resolver.ts
|
|
16471
16764
|
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
16472
|
-
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";
|
|
16473
16766
|
function blankResolution(parsed) {
|
|
16474
16767
|
return {
|
|
16475
16768
|
input: parsed.input,
|
|
@@ -16492,11 +16785,11 @@ function backlink(kind, key, label, target = key) {
|
|
|
16492
16785
|
return { kind, key, label, target };
|
|
16493
16786
|
}
|
|
16494
16787
|
function normalizeWorkspace(workspace) {
|
|
16495
|
-
return
|
|
16788
|
+
return resolve9(workspace || process.cwd());
|
|
16496
16789
|
}
|
|
16497
16790
|
function isInside(root, absolutePath) {
|
|
16498
16791
|
const rel = relative3(root, absolutePath);
|
|
16499
|
-
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${
|
|
16792
|
+
return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep2}`) && !isAbsolute(rel);
|
|
16500
16793
|
}
|
|
16501
16794
|
function normalizeRelativePath(value) {
|
|
16502
16795
|
const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
|
|
@@ -16560,7 +16853,7 @@ function resolveFile(parsed, workspace) {
|
|
|
16560
16853
|
resolution.warnings.push("path is empty or escapes the workspace");
|
|
16561
16854
|
return resolution;
|
|
16562
16855
|
}
|
|
16563
|
-
const absolutePath =
|
|
16856
|
+
const absolutePath = resolve9(workspace, relPath);
|
|
16564
16857
|
if (!isInside(workspace, absolutePath)) {
|
|
16565
16858
|
resolution.path = relPath;
|
|
16566
16859
|
resolution.warnings.push("path escapes the workspace");
|
|
@@ -16905,9 +17198,9 @@ var init_mention_resolver = __esm(() => {
|
|
|
16905
17198
|
});
|
|
16906
17199
|
|
|
16907
17200
|
// src/lib/policy-packs.ts
|
|
16908
|
-
import { relative as relative4, resolve as
|
|
17201
|
+
import { relative as relative4, resolve as resolve10 } from "path";
|
|
16909
17202
|
function normalizePath3(path) {
|
|
16910
|
-
return
|
|
17203
|
+
return resolve10(path);
|
|
16911
17204
|
}
|
|
16912
17205
|
function unique4(values) {
|
|
16913
17206
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -16962,7 +17255,7 @@ function commandMatches(commands, pattern) {
|
|
|
16962
17255
|
}
|
|
16963
17256
|
function pathMatches(paths, pattern, root) {
|
|
16964
17257
|
return paths.filter((path) => {
|
|
16965
|
-
const candidate = path.startsWith("/") ? path :
|
|
17258
|
+
const candidate = path.startsWith("/") ? path : resolve10(root, path);
|
|
16966
17259
|
if (!isPathInside3(root, candidate))
|
|
16967
17260
|
return matchesPattern3(path, pattern);
|
|
16968
17261
|
return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
|
|
@@ -17309,7 +17602,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
|
|
|
17309
17602
|
if (action === "approved" || action === "rejected" || action === "expired") {
|
|
17310
17603
|
emitLocalEventHooksQuiet({
|
|
17311
17604
|
type: "approval.decided",
|
|
17312
|
-
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)
|
|
17313
17607
|
});
|
|
17314
17608
|
}
|
|
17315
17609
|
}
|
|
@@ -17449,6 +17743,7 @@ var init_approval_gates = __esm(() => {
|
|
|
17449
17743
|
init_task_runs();
|
|
17450
17744
|
init_tasks();
|
|
17451
17745
|
init_types();
|
|
17746
|
+
init_event_emission_safety();
|
|
17452
17747
|
init_event_hooks();
|
|
17453
17748
|
});
|
|
17454
17749
|
|
|
@@ -20015,7 +20310,7 @@ function canonicalize(value) {
|
|
|
20015
20310
|
function hash(value) {
|
|
20016
20311
|
return createHash4("sha256").update(value).digest("hex");
|
|
20017
20312
|
}
|
|
20018
|
-
function
|
|
20313
|
+
function parsePayload2(value) {
|
|
20019
20314
|
if (!value)
|
|
20020
20315
|
return {};
|
|
20021
20316
|
try {
|
|
@@ -20114,7 +20409,7 @@ function taskScopedRows(db, scope) {
|
|
|
20114
20409
|
FROM handoffs h
|
|
20115
20410
|
WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
|
|
20116
20411
|
`).all(scope.project_id ?? null).filter((row) => {
|
|
20117
|
-
const payload =
|
|
20412
|
+
const payload = parsePayload2(row.payload_json);
|
|
20118
20413
|
const taskRefs = parseStringArray(payload["task_ids"]);
|
|
20119
20414
|
const runRefs = parseStringArray(payload["run_ids"]);
|
|
20120
20415
|
if (scope.project_id && row.project_id !== scope.project_id)
|
|
@@ -20145,7 +20440,7 @@ function toLedgerEntries(rows) {
|
|
|
20145
20440
|
});
|
|
20146
20441
|
let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
|
|
20147
20442
|
return ordered.map((row, index) => {
|
|
20148
|
-
const payload =
|
|
20443
|
+
const payload = parsePayload2(row.payload_json);
|
|
20149
20444
|
const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
|
|
20150
20445
|
const chainHash = hash(`${previous}
|
|
20151
20446
|
${payloadHash}`);
|
|
@@ -20287,7 +20582,7 @@ var init_audit_ledger = __esm(() => {
|
|
|
20287
20582
|
|
|
20288
20583
|
// src/lib/release-compatibility.ts
|
|
20289
20584
|
import { readFileSync as readFileSync5 } from "fs";
|
|
20290
|
-
import { join as join7, resolve as
|
|
20585
|
+
import { join as join7, resolve as resolve11 } from "path";
|
|
20291
20586
|
import { Database as Database2 } from "bun:sqlite";
|
|
20292
20587
|
function pass(id, message, details) {
|
|
20293
20588
|
return { id, status: "passed", message, details };
|
|
@@ -20395,7 +20690,7 @@ function checkChangelog() {
|
|
|
20395
20690
|
];
|
|
20396
20691
|
}
|
|
20397
20692
|
function createReleaseCompatibilityReport(options = {}) {
|
|
20398
|
-
const root =
|
|
20693
|
+
const root = resolve11(options.root ?? process.cwd());
|
|
20399
20694
|
const packageJson = readPackageJson2(root);
|
|
20400
20695
|
const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
|
|
20401
20696
|
const checks = [
|
|
@@ -26775,7 +27070,7 @@ function classifyLog(text) {
|
|
|
26775
27070
|
async function sleep2(ms) {
|
|
26776
27071
|
if (ms <= 0)
|
|
26777
27072
|
return;
|
|
26778
|
-
await new Promise((
|
|
27073
|
+
await new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
26779
27074
|
}
|
|
26780
27075
|
async function runCommandProvider(provider, input) {
|
|
26781
27076
|
const commandTemplate = input.command || provider.command;
|
|
@@ -29528,7 +29823,7 @@ var init_local_bridge = __esm(() => {
|
|
|
29528
29823
|
// src/lib/local-backups.ts
|
|
29529
29824
|
import { createHash as createHash5 } from "crypto";
|
|
29530
29825
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
29531
|
-
import { dirname as dirname8, resolve as
|
|
29826
|
+
import { dirname as dirname8, resolve as resolve12 } from "path";
|
|
29532
29827
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
29533
29828
|
function stableJson(value) {
|
|
29534
29829
|
if (value === null || typeof value !== "object")
|
|
@@ -29630,14 +29925,14 @@ function createLocalBackup(options = {}, db) {
|
|
|
29630
29925
|
return backup;
|
|
29631
29926
|
}
|
|
29632
29927
|
function writeLocalBackupFile(backup, outputPath) {
|
|
29633
|
-
const path =
|
|
29928
|
+
const path = resolve12(outputPath);
|
|
29634
29929
|
mkdirSync6(dirname8(path), { recursive: true });
|
|
29635
29930
|
writeFileSync3(path, `${JSON.stringify(backup, null, 2)}
|
|
29636
29931
|
`);
|
|
29637
29932
|
return path;
|
|
29638
29933
|
}
|
|
29639
29934
|
function readLocalBackupFile(path) {
|
|
29640
|
-
return JSON.parse(readFileSync8(
|
|
29935
|
+
return JSON.parse(readFileSync8(resolve12(path), "utf-8"));
|
|
29641
29936
|
}
|
|
29642
29937
|
function verifyLocalBackup(value, options = {}, db) {
|
|
29643
29938
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -31087,7 +31382,7 @@ __export(exports_local_extensions, {
|
|
|
31087
31382
|
});
|
|
31088
31383
|
import { createHash as createHash8, createVerify } from "crypto";
|
|
31089
31384
|
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
31090
|
-
import { basename as basename5, join as join10, resolve as
|
|
31385
|
+
import { basename as basename5, join as join10, resolve as resolve13 } from "path";
|
|
31091
31386
|
function isObject2(value) {
|
|
31092
31387
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
31093
31388
|
}
|
|
@@ -31345,7 +31640,7 @@ function verifyExtensionSignature(input) {
|
|
|
31345
31640
|
return verifier.verify(input.public_key, decodeSignature(input.signature));
|
|
31346
31641
|
}
|
|
31347
31642
|
function inspectExtensionSource(source3) {
|
|
31348
|
-
const resolved =
|
|
31643
|
+
const resolved = resolve13(source3);
|
|
31349
31644
|
if (!existsSync13(resolved))
|
|
31350
31645
|
throw new Error(`extension source not found: ${source3}`);
|
|
31351
31646
|
const stat = statSync6(resolved);
|
|
@@ -31443,7 +31738,7 @@ function testExtensionCompatibility(sourceOrManifest) {
|
|
|
31443
31738
|
function projectExtensionSources(projectPath) {
|
|
31444
31739
|
if (!projectPath)
|
|
31445
31740
|
return [];
|
|
31446
|
-
const root =
|
|
31741
|
+
const root = resolve13(projectPath);
|
|
31447
31742
|
const candidates = [
|
|
31448
31743
|
join10(root, "todos.extension.json"),
|
|
31449
31744
|
join10(root, ".todos", "todos.extension.json")
|
|
@@ -31462,7 +31757,7 @@ function projectExtensionSources(projectPath) {
|
|
|
31462
31757
|
}
|
|
31463
31758
|
function discoverLocalExtensions(options = {}) {
|
|
31464
31759
|
const config = loadConfig();
|
|
31465
|
-
const projectPath = options.project_path ?
|
|
31760
|
+
const projectPath = options.project_path ? resolve13(options.project_path) : null;
|
|
31466
31761
|
const configuredSources = [
|
|
31467
31762
|
...config.extension_sources || [],
|
|
31468
31763
|
...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
|
|
@@ -31470,7 +31765,7 @@ function discoverLocalExtensions(options = {}) {
|
|
|
31470
31765
|
const sources = Array.from(new Set([
|
|
31471
31766
|
...configuredSources,
|
|
31472
31767
|
...projectExtensionSources(projectPath || undefined)
|
|
31473
|
-
])).map((source3) => projectPath && !source3.startsWith("/") ?
|
|
31768
|
+
])).map((source3) => projectPath && !source3.startsWith("/") ? resolve13(projectPath, source3) : resolve13(source3));
|
|
31474
31769
|
const warnings = [];
|
|
31475
31770
|
const discovered = [];
|
|
31476
31771
|
for (const source3 of sources) {
|
|
@@ -34420,7 +34715,8 @@ function writeQueue(task2, queue, actor, action, db) {
|
|
|
34420
34715
|
logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
|
|
34421
34716
|
emitLocalEventHooksQuiet({
|
|
34422
34717
|
type: `review.${action}`,
|
|
34423
|
-
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)
|
|
34424
34720
|
});
|
|
34425
34721
|
return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
|
|
34426
34722
|
}
|
|
@@ -34724,6 +35020,7 @@ var init_review_queues = __esm(() => {
|
|
|
34724
35020
|
init_database();
|
|
34725
35021
|
init_tasks();
|
|
34726
35022
|
init_config();
|
|
35023
|
+
init_event_emission_safety();
|
|
34727
35024
|
init_event_hooks();
|
|
34728
35025
|
init_task_contracts();
|
|
34729
35026
|
STATES = new Set(["requested", "claimed", "approved", "changes_requested", "returned", "reopened"]);
|
|
@@ -35879,7 +36176,7 @@ __export(exports_extract, {
|
|
|
35879
36176
|
});
|
|
35880
36177
|
import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
35881
36178
|
import { createHash as createHash10 } from "crypto";
|
|
35882
|
-
import { relative as relative5, resolve as
|
|
36179
|
+
import { relative as relative5, resolve as resolve14, join as join11 } from "path";
|
|
35883
36180
|
function stableHash(value) {
|
|
35884
36181
|
return createHash10("sha256").update(value).digest("hex");
|
|
35885
36182
|
}
|
|
@@ -35887,7 +36184,7 @@ function normalizePathForMatch(value) {
|
|
|
35887
36184
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
35888
36185
|
}
|
|
35889
36186
|
function readGitignorePatterns(basePath) {
|
|
35890
|
-
const root = statSync7(basePath).isFile() ?
|
|
36187
|
+
const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
|
|
35891
36188
|
const gitignorePath = join11(root, ".gitignore");
|
|
35892
36189
|
if (!existsSync14(gitignorePath))
|
|
35893
36190
|
return [];
|
|
@@ -36023,7 +36320,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
|
|
|
36023
36320
|
return files.sort();
|
|
36024
36321
|
}
|
|
36025
36322
|
function buildCodebaseIndex(options) {
|
|
36026
|
-
const basePath =
|
|
36323
|
+
const basePath = resolve14(options.path);
|
|
36027
36324
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
36028
36325
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
36029
36326
|
const excludes = options.exclude || [];
|
|
@@ -36034,7 +36331,7 @@ function buildCodebaseIndex(options) {
|
|
|
36034
36331
|
const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
|
|
36035
36332
|
try {
|
|
36036
36333
|
const source3 = readFileSync10(fullPath, "utf-8");
|
|
36037
|
-
const relPath = statSync7(basePath).isFile() ? relative5(
|
|
36334
|
+
const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
|
|
36038
36335
|
indexed.push({
|
|
36039
36336
|
file: relPath,
|
|
36040
36337
|
checksum: stableHash(source3).slice(0, 24),
|
|
@@ -36054,7 +36351,7 @@ function buildCodebaseIndex(options) {
|
|
|
36054
36351
|
};
|
|
36055
36352
|
}
|
|
36056
36353
|
function extractTodos(options, db) {
|
|
36057
|
-
const basePath =
|
|
36354
|
+
const basePath = resolve14(options.path);
|
|
36058
36355
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
36059
36356
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
36060
36357
|
const excludes = options.exclude || [];
|
|
@@ -36065,7 +36362,7 @@ function extractTodos(options, db) {
|
|
|
36065
36362
|
const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
|
|
36066
36363
|
try {
|
|
36067
36364
|
const source3 = readFileSync10(fullPath, "utf-8");
|
|
36068
|
-
const relPath = statSync7(basePath).isFile() ? relative5(
|
|
36365
|
+
const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
|
|
36069
36366
|
const comments = extractFromSource(source3, relPath, tags);
|
|
36070
36367
|
allComments.push(...comments);
|
|
36071
36368
|
} catch {}
|
|
@@ -36159,7 +36456,7 @@ async function watchSourceTodos(options, onRun) {
|
|
|
36159
36456
|
const interval = Math.max(100, options.interval_ms || 2000);
|
|
36160
36457
|
const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
|
|
36161
36458
|
const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
|
|
36162
|
-
const root =
|
|
36459
|
+
const root = resolve14(options.path);
|
|
36163
36460
|
const runs = [];
|
|
36164
36461
|
let previous = new Map;
|
|
36165
36462
|
for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
|
|
@@ -37542,8 +37839,8 @@ __export(exports_environment_snapshots, {
|
|
|
37542
37839
|
import { createHash as createHash11 } from "crypto";
|
|
37543
37840
|
import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
|
|
37544
37841
|
import { hostname as hostname2, platform, arch } from "os";
|
|
37545
|
-
import { dirname as dirname9, join as join13, resolve as
|
|
37546
|
-
import { tmpdir as
|
|
37842
|
+
import { dirname as dirname9, join as join13, resolve as resolve15 } from "path";
|
|
37843
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
37547
37844
|
function sha2566(value) {
|
|
37548
37845
|
return createHash11("sha256").update(value).digest("hex");
|
|
37549
37846
|
}
|
|
@@ -37656,15 +37953,15 @@ function commandEnv(env, includeValues) {
|
|
|
37656
37953
|
function defaultSnapshotDir() {
|
|
37657
37954
|
const dbPath = getDatabasePath();
|
|
37658
37955
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
37659
|
-
return join13(
|
|
37660
|
-
return join13(dirname9(
|
|
37956
|
+
return join13(tmpdir3(), "hasna-todos", "environment-snapshots");
|
|
37957
|
+
return join13(dirname9(resolve15(dbPath)), "environment-snapshots");
|
|
37661
37958
|
}
|
|
37662
37959
|
function snapshotWithId(snapshot) {
|
|
37663
37960
|
const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
|
|
37664
37961
|
return { id: `env_${digest}`, ...snapshot };
|
|
37665
37962
|
}
|
|
37666
37963
|
function captureEnvironmentSnapshot(input = {}) {
|
|
37667
|
-
const root =
|
|
37964
|
+
const root = resolve15(input.root || process.cwd());
|
|
37668
37965
|
const env = input.env || process.env;
|
|
37669
37966
|
const warnings = [];
|
|
37670
37967
|
const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
|
|
@@ -37704,13 +38001,13 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
37704
38001
|
});
|
|
37705
38002
|
}
|
|
37706
38003
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
37707
|
-
const path = outputPath ?
|
|
38004
|
+
const path = outputPath ? resolve15(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
37708
38005
|
ensureDir2(dirname9(path));
|
|
37709
38006
|
writeJsonFile(path, snapshot);
|
|
37710
38007
|
return path;
|
|
37711
38008
|
}
|
|
37712
38009
|
function readEnvironmentSnapshot(path) {
|
|
37713
|
-
const snapshot = readJsonFile(
|
|
38010
|
+
const snapshot = readJsonFile(resolve15(path));
|
|
37714
38011
|
if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
|
|
37715
38012
|
throw new Error(`Invalid environment snapshot: ${path}`);
|
|
37716
38013
|
}
|
|
@@ -38256,7 +38553,7 @@ var init_headless_boundaries = __esm(() => {
|
|
|
38256
38553
|
});
|
|
38257
38554
|
|
|
38258
38555
|
// src/server/routes.ts
|
|
38259
|
-
import { join as join14, resolve as
|
|
38556
|
+
import { join as join14, resolve as resolve16, sep as sep3 } from "path";
|
|
38260
38557
|
function parseFieldsParam(url) {
|
|
38261
38558
|
const fieldsParam = url.searchParams.get("fields");
|
|
38262
38559
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -38994,9 +39291,9 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
38994
39291
|
return null;
|
|
38995
39292
|
if (path !== "/") {
|
|
38996
39293
|
const filePath = join14(ctx.dashboardDir, path);
|
|
38997
|
-
const resolvedFile =
|
|
38998
|
-
const resolvedBase =
|
|
38999
|
-
if (!resolvedFile.startsWith(resolvedBase +
|
|
39294
|
+
const resolvedFile = resolve16(filePath);
|
|
39295
|
+
const resolvedBase = resolve16(ctx.dashboardDir);
|
|
39296
|
+
if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
|
|
39000
39297
|
return json2({ error: "Forbidden" }, 403);
|
|
39001
39298
|
}
|
|
39002
39299
|
const res2 = serveStaticFile2(filePath);
|