@hasna/todos 0.11.53 → 0.11.54
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/dist/cli/index.js +1156 -475
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +2 -2
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -1011,7 +1011,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1011
1011
|
this._exitCallback = (err) => {
|
|
1012
1012
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
1013
1013
|
throw err;
|
|
1014
|
-
}
|
|
1014
|
+
}
|
|
1015
1015
|
};
|
|
1016
1016
|
}
|
|
1017
1017
|
return this;
|
|
@@ -2119,15 +2119,15 @@ var init_esm = __esm(() => {
|
|
|
2119
2119
|
});
|
|
2120
2120
|
|
|
2121
2121
|
// src/lib/package-version.ts
|
|
2122
|
-
import { existsSync, readFileSync } from "fs";
|
|
2123
|
-
import { dirname, join } from "path";
|
|
2122
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
2123
|
+
import { dirname, join as join2 } from "path";
|
|
2124
2124
|
import { fileURLToPath } from "url";
|
|
2125
2125
|
function getPackageVersion(fromUrl = import.meta.url) {
|
|
2126
2126
|
try {
|
|
2127
2127
|
let dir = dirname(fileURLToPath(fromUrl));
|
|
2128
2128
|
for (let i = 0;i < 5; i++) {
|
|
2129
|
-
const pkgPath =
|
|
2130
|
-
if (
|
|
2129
|
+
const pkgPath = join2(dir, "package.json");
|
|
2130
|
+
if (existsSync2(pkgPath)) {
|
|
2131
2131
|
return JSON.parse(readFileSync(pkgPath, "utf-8")).version || "0.0.0";
|
|
2132
2132
|
}
|
|
2133
2133
|
const parent = dirname(dir);
|
|
@@ -4285,7 +4285,7 @@ var init_schema = __esm(() => {
|
|
|
4285
4285
|
});
|
|
4286
4286
|
|
|
4287
4287
|
// src/db/machines.ts
|
|
4288
|
-
import { existsSync as
|
|
4288
|
+
import { existsSync as existsSync3 } from "fs";
|
|
4289
4289
|
import { hostname as osHostname, platform as osPlatform, arch as osArch } from "os";
|
|
4290
4290
|
import { resolve } from "path";
|
|
4291
4291
|
import { spawnSync } from "child_process";
|
|
@@ -4351,11 +4351,11 @@ function getOrCreateLocalMachine(db) {
|
|
|
4351
4351
|
const plat = osPlatform();
|
|
4352
4352
|
const existing = d.query("SELECT * FROM machines WHERE name = ?").get(name);
|
|
4353
4353
|
if (existing) {
|
|
4354
|
-
d.run("UPDATE machines SET hostname = ?, platform = ?, last_seen_at = ? WHERE id = ?", [host, plat,
|
|
4355
|
-
return rowToMachine({ ...existing, hostname: host, platform: plat, last_seen_at:
|
|
4354
|
+
d.run("UPDATE machines SET hostname = ?, platform = ?, last_seen_at = ? WHERE id = ?", [host, plat, now2(), existing.id]);
|
|
4355
|
+
return rowToMachine({ ...existing, hostname: host, platform: plat, last_seen_at: now2() });
|
|
4356
4356
|
}
|
|
4357
4357
|
const id = uuid();
|
|
4358
|
-
const ts =
|
|
4358
|
+
const ts = now2();
|
|
4359
4359
|
d.run("INSERT INTO machines (id, name, hostname, platform, last_seen_at, metadata, created_at) VALUES (?, ?, ?, ?, ?, '{}', ?)", [id, name, host, plat, ts, ts]);
|
|
4360
4360
|
return { id, name, hostname: host, platform: plat, ssh_address: null, is_primary: false, last_seen_at: ts, archived_at: null, metadata: {}, created_at: ts };
|
|
4361
4361
|
}
|
|
@@ -4391,7 +4391,7 @@ function registerMachine(name, opts, db) {
|
|
|
4391
4391
|
opts.hostname ?? existing.hostname,
|
|
4392
4392
|
opts.platform ?? existing.platform,
|
|
4393
4393
|
opts.ssh_address ?? existing.ssh_address,
|
|
4394
|
-
|
|
4394
|
+
now2(),
|
|
4395
4395
|
JSON.stringify(metadata),
|
|
4396
4396
|
existing.id
|
|
4397
4397
|
]);
|
|
@@ -4401,7 +4401,7 @@ function registerMachine(name, opts, db) {
|
|
|
4401
4401
|
return getMachine(existing.id, d);
|
|
4402
4402
|
}
|
|
4403
4403
|
const id = uuid();
|
|
4404
|
-
const ts =
|
|
4404
|
+
const ts = now2();
|
|
4405
4405
|
const host = opts.hostname || osHostname();
|
|
4406
4406
|
const plat = opts.platform || osPlatform();
|
|
4407
4407
|
d.run("INSERT INTO machines (id, name, hostname, platform, ssh_address, last_seen_at, is_primary, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)", [id, name, host, plat, opts.ssh_address ?? null, ts, JSON.stringify(metadata), ts]);
|
|
@@ -4423,7 +4423,7 @@ function updateMachineHeartbeat(idOrName, opts = {}, db) {
|
|
|
4423
4423
|
}, d);
|
|
4424
4424
|
}
|
|
4425
4425
|
const metadata = topologyMetadata({ arch: osArch(), ...opts }, parseMetadata(row.metadata));
|
|
4426
|
-
const ts =
|
|
4426
|
+
const ts = now2();
|
|
4427
4427
|
d.run("UPDATE machines SET hostname = ?, platform = ?, ssh_address = ?, last_seen_at = ?, metadata = ? WHERE id = ?", [
|
|
4428
4428
|
opts.hostname ?? row.hostname ?? osHostname(),
|
|
4429
4429
|
opts.platform ?? row.platform ?? osPlatform(),
|
|
@@ -4492,7 +4492,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
|
|
|
4492
4492
|
message: `${project.name} has ${distinctPaths.length} different machine-local paths`
|
|
4493
4493
|
});
|
|
4494
4494
|
}
|
|
4495
|
-
if (localRow && !
|
|
4495
|
+
if (localRow && !existsSync3(localRow.path)) {
|
|
4496
4496
|
pathIssues.push({
|
|
4497
4497
|
type: "path_missing",
|
|
4498
4498
|
project_id: project.id,
|
|
@@ -4503,7 +4503,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
|
|
|
4503
4503
|
message: `Local path does not exist on this machine: ${localRow.path}`
|
|
4504
4504
|
});
|
|
4505
4505
|
}
|
|
4506
|
-
if (!localRow && project.path && machineById.has(localMachine.id) && !
|
|
4506
|
+
if (!localRow && project.path && machineById.has(localMachine.id) && !existsSync3(project.path)) {
|
|
4507
4507
|
pathIssues.push({
|
|
4508
4508
|
type: "path_missing",
|
|
4509
4509
|
project_id: project.id,
|
|
@@ -4551,7 +4551,7 @@ function archiveMachine(id, db) {
|
|
|
4551
4551
|
if (activeCount.cnt > 0) {
|
|
4552
4552
|
throw new Error(`Cannot archive machine with ${activeCount.cnt} active/pending tasks`);
|
|
4553
4553
|
}
|
|
4554
|
-
d.run("UPDATE machines SET archived_at = ? WHERE id = ?", [
|
|
4554
|
+
d.run("UPDATE machines SET archived_at = ? WHERE id = ?", [now2(), id]);
|
|
4555
4555
|
}
|
|
4556
4556
|
function unarchiveMachine(id, db) {
|
|
4557
4557
|
const d = db || getDatabase();
|
|
@@ -4616,7 +4616,7 @@ __export(exports_database, {
|
|
|
4616
4616
|
uuid: () => uuid,
|
|
4617
4617
|
resolvePartialId: () => resolvePartialId,
|
|
4618
4618
|
resetDatabase: () => resetDatabase,
|
|
4619
|
-
now: () =>
|
|
4619
|
+
now: () => now2,
|
|
4620
4620
|
lockExpiryCutoff: () => lockExpiryCutoff,
|
|
4621
4621
|
isLockExpired: () => isLockExpired,
|
|
4622
4622
|
getDatabasePath: () => getDatabasePath,
|
|
@@ -4626,8 +4626,8 @@ __export(exports_database, {
|
|
|
4626
4626
|
LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
|
|
4627
4627
|
});
|
|
4628
4628
|
import { Database } from "bun:sqlite";
|
|
4629
|
-
import { existsSync as
|
|
4630
|
-
import { dirname as dirname2, join as
|
|
4629
|
+
import { existsSync as existsSync4, mkdirSync } from "fs";
|
|
4630
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
4631
4631
|
function isInMemoryDb(path) {
|
|
4632
4632
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
4633
4633
|
}
|
|
@@ -4636,8 +4636,8 @@ function findNearestProjectDb(startDir) {
|
|
|
4636
4636
|
const stopAt = gitRoot ? resolve2(gitRoot) : resolve2(startDir);
|
|
4637
4637
|
let dir = resolve2(startDir);
|
|
4638
4638
|
while (true) {
|
|
4639
|
-
const candidate =
|
|
4640
|
-
if (
|
|
4639
|
+
const candidate = join3(dir, ".hasna", "todos", "todos.db");
|
|
4640
|
+
if (existsSync4(candidate))
|
|
4641
4641
|
return candidate;
|
|
4642
4642
|
if (dir === stopAt)
|
|
4643
4643
|
break;
|
|
@@ -4651,7 +4651,7 @@ function findNearestProjectDb(startDir) {
|
|
|
4651
4651
|
function findGitRoot(startDir) {
|
|
4652
4652
|
let dir = resolve2(startDir);
|
|
4653
4653
|
while (true) {
|
|
4654
|
-
if (
|
|
4654
|
+
if (existsSync4(join3(dir, ".git")))
|
|
4655
4655
|
return dir;
|
|
4656
4656
|
const parent = dirname2(dir);
|
|
4657
4657
|
if (parent === dir)
|
|
@@ -4674,11 +4674,11 @@ function getDbPath() {
|
|
|
4674
4674
|
if (process.env["TODOS_DB_SCOPE"] === "project") {
|
|
4675
4675
|
const gitRoot = findGitRoot(cwd);
|
|
4676
4676
|
if (gitRoot) {
|
|
4677
|
-
return
|
|
4677
|
+
return join3(gitRoot, ".hasna", "todos", "todos.db");
|
|
4678
4678
|
}
|
|
4679
4679
|
}
|
|
4680
4680
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
4681
|
-
return
|
|
4681
|
+
return join3(home, ".hasna", "todos", "todos.db");
|
|
4682
4682
|
}
|
|
4683
4683
|
function getDatabasePath() {
|
|
4684
4684
|
return getDbPath();
|
|
@@ -4687,7 +4687,7 @@ function ensureDir(filePath) {
|
|
|
4687
4687
|
if (isInMemoryDb(filePath))
|
|
4688
4688
|
return;
|
|
4689
4689
|
const dir = dirname2(resolve2(filePath));
|
|
4690
|
-
if (!
|
|
4690
|
+
if (!existsSync4(dir)) {
|
|
4691
4691
|
mkdirSync(dir, { recursive: true });
|
|
4692
4692
|
}
|
|
4693
4693
|
}
|
|
@@ -4722,7 +4722,7 @@ function resetDatabase() {
|
|
|
4722
4722
|
_db = null;
|
|
4723
4723
|
_dbPath = null;
|
|
4724
4724
|
}
|
|
4725
|
-
function
|
|
4725
|
+
function now2() {
|
|
4726
4726
|
return new Date().toISOString();
|
|
4727
4727
|
}
|
|
4728
4728
|
function uuid() {
|
|
@@ -4958,7 +4958,7 @@ function generatePrefix(name, db) {
|
|
|
4958
4958
|
function createProject(input, db) {
|
|
4959
4959
|
const d = db || getDatabase();
|
|
4960
4960
|
const id = uuid();
|
|
4961
|
-
const timestamp =
|
|
4961
|
+
const timestamp = now2();
|
|
4962
4962
|
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
4963
4963
|
const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
|
|
4964
4964
|
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
|
|
@@ -4992,7 +4992,7 @@ function updateProject(id, input, db) {
|
|
|
4992
4992
|
if (!project)
|
|
4993
4993
|
throw new ProjectNotFoundError(id);
|
|
4994
4994
|
const sets = ["updated_at = ?"];
|
|
4995
|
-
const params = [
|
|
4995
|
+
const params = [now2()];
|
|
4996
4996
|
if (input.name !== undefined) {
|
|
4997
4997
|
sets.push("name = ?");
|
|
4998
4998
|
params.push(input.name);
|
|
@@ -5019,7 +5019,7 @@ function renameProject(id, input, db) {
|
|
|
5019
5019
|
if (!project)
|
|
5020
5020
|
throw new ProjectNotFoundError(id);
|
|
5021
5021
|
let taskListsUpdated = 0;
|
|
5022
|
-
const ts =
|
|
5022
|
+
const ts = now2();
|
|
5023
5023
|
if (input.new_slug !== undefined) {
|
|
5024
5024
|
const normalised = input.new_slug.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "");
|
|
5025
5025
|
if (!normalised)
|
|
@@ -5053,7 +5053,7 @@ function rowToSource(row) {
|
|
|
5053
5053
|
function addProjectSource(input, db) {
|
|
5054
5054
|
const d = db || getDatabase();
|
|
5055
5055
|
const id = uuid();
|
|
5056
|
-
const timestamp =
|
|
5056
|
+
const timestamp = now2();
|
|
5057
5057
|
d.run(`INSERT INTO project_sources (id, project_id, type, name, uri, description, metadata, created_at, updated_at)
|
|
5058
5058
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5059
5059
|
id,
|
|
@@ -5091,7 +5091,7 @@ function nextTaskShortId(projectId, db) {
|
|
|
5091
5091
|
const project = getProject(projectId, d);
|
|
5092
5092
|
if (!project || !project.task_prefix)
|
|
5093
5093
|
return null;
|
|
5094
|
-
d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [
|
|
5094
|
+
d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now2(), projectId]);
|
|
5095
5095
|
const updated = getProject(projectId, d);
|
|
5096
5096
|
const padded = String(updated.task_counter).padStart(5, "0");
|
|
5097
5097
|
return `${updated.task_prefix}-${padded}`;
|
|
@@ -5102,7 +5102,7 @@ function ensureProject(name, path, db) {
|
|
|
5102
5102
|
if (existing) {
|
|
5103
5103
|
if (!existing.task_prefix) {
|
|
5104
5104
|
const prefix = generatePrefix(existing.name, d);
|
|
5105
|
-
d.run("UPDATE projects SET task_prefix = ?, updated_at = ? WHERE id = ?", [prefix,
|
|
5105
|
+
d.run("UPDATE projects SET task_prefix = ?, updated_at = ? WHERE id = ?", [prefix, now2(), existing.id]);
|
|
5106
5106
|
return getProject(existing.id, d);
|
|
5107
5107
|
}
|
|
5108
5108
|
setMachineLocalPath(existing.id, path, d);
|
|
@@ -5115,7 +5115,7 @@ function ensureProject(name, path, db) {
|
|
|
5115
5115
|
function setMachineLocalPath(projectId, path, db) {
|
|
5116
5116
|
const d = db || getDatabase();
|
|
5117
5117
|
const machineId = getMachineId(d);
|
|
5118
|
-
const ts =
|
|
5118
|
+
const ts = now2();
|
|
5119
5119
|
const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, machineId);
|
|
5120
5120
|
if (existing) {
|
|
5121
5121
|
if (existing.path !== path) {
|
|
@@ -5155,20 +5155,20 @@ var init_projects = __esm(() => {
|
|
|
5155
5155
|
});
|
|
5156
5156
|
|
|
5157
5157
|
// src/lib/sync-utils.ts
|
|
5158
|
-
import { existsSync as
|
|
5159
|
-
import { join as
|
|
5158
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "fs";
|
|
5159
|
+
import { join as join4 } from "path";
|
|
5160
5160
|
function getHomeDir() {
|
|
5161
5161
|
return process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
5162
5162
|
}
|
|
5163
5163
|
function getTodosGlobalDir() {
|
|
5164
|
-
return
|
|
5164
|
+
return join4(getHomeDir(), ".hasna", "todos");
|
|
5165
5165
|
}
|
|
5166
5166
|
function ensureDir2(dir) {
|
|
5167
|
-
if (!
|
|
5167
|
+
if (!existsSync5(dir))
|
|
5168
5168
|
mkdirSync2(dir, { recursive: true });
|
|
5169
5169
|
}
|
|
5170
5170
|
function listJsonFiles(dir) {
|
|
5171
|
-
if (!
|
|
5171
|
+
if (!existsSync5(dir))
|
|
5172
5172
|
return [];
|
|
5173
5173
|
return readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
5174
5174
|
}
|
|
@@ -5184,14 +5184,14 @@ function writeJsonFile(path, data) {
|
|
|
5184
5184
|
`);
|
|
5185
5185
|
}
|
|
5186
5186
|
function readHighWaterMark(dir) {
|
|
5187
|
-
const path =
|
|
5188
|
-
if (!
|
|
5187
|
+
const path = join4(dir, ".highwatermark");
|
|
5188
|
+
if (!existsSync5(path))
|
|
5189
5189
|
return 1;
|
|
5190
5190
|
const val = parseInt(readFileSync2(path, "utf-8").trim(), 10);
|
|
5191
5191
|
return isNaN(val) ? 1 : val;
|
|
5192
5192
|
}
|
|
5193
5193
|
function writeHighWaterMark(dir, value) {
|
|
5194
|
-
writeFileSync(
|
|
5194
|
+
writeFileSync(join4(dir, ".highwatermark"), String(value));
|
|
5195
5195
|
}
|
|
5196
5196
|
function getFileMtimeMs(path) {
|
|
5197
5197
|
try {
|
|
@@ -5233,10 +5233,10 @@ __export(exports_config, {
|
|
|
5233
5233
|
getAgentTaskListId: () => getAgentTaskListId,
|
|
5234
5234
|
getAgentPoolForProject: () => getAgentPoolForProject
|
|
5235
5235
|
});
|
|
5236
|
-
import { existsSync as
|
|
5237
|
-
import { dirname as dirname3, join as
|
|
5236
|
+
import { existsSync as existsSync6 } from "fs";
|
|
5237
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
5238
5238
|
function getConfigPath() {
|
|
5239
|
-
return
|
|
5239
|
+
return join5(getTodosGlobalDir(), "config.json");
|
|
5240
5240
|
}
|
|
5241
5241
|
function resetConfig() {
|
|
5242
5242
|
cached = null;
|
|
@@ -5247,7 +5247,7 @@ function normalizeAgent(agent) {
|
|
|
5247
5247
|
function loadConfig() {
|
|
5248
5248
|
if (cached)
|
|
5249
5249
|
return cached;
|
|
5250
|
-
if (!
|
|
5250
|
+
if (!existsSync6(getConfigPath())) {
|
|
5251
5251
|
cached = {};
|
|
5252
5252
|
return cached;
|
|
5253
5253
|
}
|
|
@@ -5400,7 +5400,7 @@ var init_completion_guard = __esm(() => {
|
|
|
5400
5400
|
var exports_redaction = {};
|
|
5401
5401
|
__export(exports_redaction, {
|
|
5402
5402
|
upsertSecretSafetyConfig: () => upsertSecretSafetyConfig,
|
|
5403
|
-
redactValue: () =>
|
|
5403
|
+
redactValue: () => redactValue2,
|
|
5404
5404
|
redactEvidenceText: () => redactEvidenceText,
|
|
5405
5405
|
listSecretFindings: () => listSecretFindings,
|
|
5406
5406
|
hasSecretFindings: () => hasSecretFindings,
|
|
@@ -5440,18 +5440,18 @@ function redactEvidenceText(value) {
|
|
|
5440
5440
|
}
|
|
5441
5441
|
return redacted;
|
|
5442
5442
|
}
|
|
5443
|
-
function
|
|
5443
|
+
function redactValue2(value) {
|
|
5444
5444
|
if (typeof value === "string")
|
|
5445
5445
|
return redactEvidenceText(value);
|
|
5446
5446
|
if (Array.isArray(value))
|
|
5447
|
-
return value.map(
|
|
5447
|
+
return value.map(redactValue2);
|
|
5448
5448
|
if (value && typeof value === "object") {
|
|
5449
5449
|
const redacted = {};
|
|
5450
5450
|
for (const [key, child] of Object.entries(value)) {
|
|
5451
5451
|
if (isSecretKey(key)) {
|
|
5452
5452
|
redacted[key] = "[REDACTED]";
|
|
5453
5453
|
} else {
|
|
5454
|
-
redacted[key] =
|
|
5454
|
+
redacted[key] = redactValue2(child);
|
|
5455
5455
|
}
|
|
5456
5456
|
}
|
|
5457
5457
|
return redacted;
|
|
@@ -5913,7 +5913,7 @@ __export(exports_event_hooks, {
|
|
|
5913
5913
|
emitLocalEventHooks: () => emitLocalEventHooks,
|
|
5914
5914
|
LOCAL_EVENT_TYPES: () => LOCAL_EVENT_TYPES
|
|
5915
5915
|
});
|
|
5916
|
-
import { createHash, randomUUID } from "crypto";
|
|
5916
|
+
import { createHash, randomUUID as randomUUID3 } from "crypto";
|
|
5917
5917
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
5918
5918
|
import { dirname as dirname4, resolve as resolve5 } from "path";
|
|
5919
5919
|
import { createConnection } from "net";
|
|
@@ -5974,10 +5974,10 @@ function canonicalEvent(input) {
|
|
|
5974
5974
|
}
|
|
5975
5975
|
function buildEnvelope(type, payload, timestamp = new Date().toISOString()) {
|
|
5976
5976
|
const base = {
|
|
5977
|
-
id:
|
|
5977
|
+
id: randomUUID3(),
|
|
5978
5978
|
type,
|
|
5979
5979
|
timestamp,
|
|
5980
|
-
payload:
|
|
5980
|
+
payload: redactValue2(payload ?? {}),
|
|
5981
5981
|
source: { package: "@hasna/todos", local_only: true }
|
|
5982
5982
|
};
|
|
5983
5983
|
const digest = createHash("sha256").update(canonicalEvent(base)).digest("hex");
|
|
@@ -6191,7 +6191,7 @@ function redactText(text, options = {}) {
|
|
|
6191
6191
|
return out;
|
|
6192
6192
|
}
|
|
6193
6193
|
function redactExportRecord(record) {
|
|
6194
|
-
const base =
|
|
6194
|
+
const base = redactValue2(record);
|
|
6195
6195
|
for (const [key, value] of Object.entries(base)) {
|
|
6196
6196
|
if (typeof value === "string") {
|
|
6197
6197
|
base[key] = redactText(value);
|
|
@@ -6269,7 +6269,7 @@ function redactActivityRecord(record) {
|
|
|
6269
6269
|
function logActivity(input, db) {
|
|
6270
6270
|
const d = db || getDatabase();
|
|
6271
6271
|
const id = uuid();
|
|
6272
|
-
const ts = input.created_at ??
|
|
6272
|
+
const ts = input.created_at ?? now2();
|
|
6273
6273
|
const machineId = input.machine_id ?? getMachineId2();
|
|
6274
6274
|
d.run(`INSERT INTO activity_log (
|
|
6275
6275
|
id, entity_type, entity_id, action, field, old_value, new_value,
|
|
@@ -6337,7 +6337,7 @@ function exportActivityLog(filter = {}, db) {
|
|
|
6337
6337
|
const records = listActivity({ ...filter, limit: filter.limit ?? 1000, order: "asc" }, db).map(redactActivityRecord);
|
|
6338
6338
|
return {
|
|
6339
6339
|
schema_version: ACTIVITY_LOG_SCHEMA,
|
|
6340
|
-
exported_at:
|
|
6340
|
+
exported_at: now2(),
|
|
6341
6341
|
records
|
|
6342
6342
|
};
|
|
6343
6343
|
}
|
|
@@ -6418,7 +6418,7 @@ __export(exports_audit, {
|
|
|
6418
6418
|
function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
6419
6419
|
const d = db || getDatabase();
|
|
6420
6420
|
const id = uuid();
|
|
6421
|
-
const timestamp =
|
|
6421
|
+
const timestamp = now2();
|
|
6422
6422
|
d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
|
|
6423
6423
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
|
|
6424
6424
|
try {
|
|
@@ -6577,7 +6577,7 @@ function createWebhook(input, db) {
|
|
|
6577
6577
|
input.task_list_id || null,
|
|
6578
6578
|
input.agent_id || null,
|
|
6579
6579
|
input.task_id || null,
|
|
6580
|
-
|
|
6580
|
+
now2()
|
|
6581
6581
|
]);
|
|
6582
6582
|
return getWebhook(id, d);
|
|
6583
6583
|
}
|
|
@@ -6596,7 +6596,7 @@ function deleteWebhook(id, db) {
|
|
|
6596
6596
|
}
|
|
6597
6597
|
function logDelivery(d, webhookId, event, payload, statusCode, response, attempt) {
|
|
6598
6598
|
const id = uuid();
|
|
6599
|
-
d.run(`INSERT INTO webhook_deliveries (id, webhook_id, event, payload, status_code, response, attempt, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, webhookId, event, payload, statusCode, response, attempt,
|
|
6599
|
+
d.run(`INSERT INTO webhook_deliveries (id, webhook_id, event, payload, status_code, response, attempt, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, webhookId, event, payload, statusCode, response, attempt, now2()]);
|
|
6600
6600
|
}
|
|
6601
6601
|
function matchesScope(wh, payload) {
|
|
6602
6602
|
if (wh.project_id && payload.project_id !== wh.project_id)
|
|
@@ -6666,14 +6666,14 @@ async function deliverWebhook(wh, event, body, attempt, db) {
|
|
|
6666
6666
|
activeDeliveries--;
|
|
6667
6667
|
}
|
|
6668
6668
|
}
|
|
6669
|
-
async function
|
|
6669
|
+
async function dispatchWebhook2(event, payload, db) {
|
|
6670
6670
|
const d = db || getDatabase();
|
|
6671
6671
|
const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
6672
6672
|
const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
|
|
6673
6673
|
for (const wh of webhooks) {
|
|
6674
6674
|
if (!matchesScope(wh, payloadObj))
|
|
6675
6675
|
continue;
|
|
6676
|
-
const body = JSON.stringify({ event, payload, timestamp:
|
|
6676
|
+
const body = JSON.stringify({ event, payload, timestamp: now2() });
|
|
6677
6677
|
deliverWebhook(wh, event, body, 1, d).catch((err) => {
|
|
6678
6678
|
console.error(`[webhook] Dispatch failed for webhook ${wh.id}:`, err);
|
|
6679
6679
|
});
|
|
@@ -6723,7 +6723,7 @@ function replaceTaskTags(taskId, tags, db) {
|
|
|
6723
6723
|
}
|
|
6724
6724
|
function createTask(input, db) {
|
|
6725
6725
|
const d = db || getDatabase();
|
|
6726
|
-
const timestamp =
|
|
6726
|
+
const timestamp = now2();
|
|
6727
6727
|
const tags = input.tags || [];
|
|
6728
6728
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
6729
6729
|
const assignedFromProject = input.assigned_from_project || null;
|
|
@@ -6783,7 +6783,7 @@ function createTask(input, db) {
|
|
|
6783
6783
|
insertTaskTags(id, tags, d);
|
|
6784
6784
|
}
|
|
6785
6785
|
const task = getTask(id, d);
|
|
6786
|
-
|
|
6786
|
+
dispatchWebhook2("task.created", { id: task.id, short_id: task.short_id, title: task.title, status: task.status, priority: task.priority, project_id: task.project_id, assigned_to: task.assigned_to }, d).catch(() => {});
|
|
6787
6787
|
return task;
|
|
6788
6788
|
}
|
|
6789
6789
|
function getTask(id, db) {
|
|
@@ -7002,7 +7002,7 @@ function updateTask(id, input, db) {
|
|
|
7002
7002
|
if (task.version !== input.version) {
|
|
7003
7003
|
throw new VersionConflictError(id, input.version, task.version);
|
|
7004
7004
|
}
|
|
7005
|
-
const timestamp =
|
|
7005
|
+
const timestamp = now2();
|
|
7006
7006
|
const completionTimestamp = input.completed_at ?? timestamp;
|
|
7007
7007
|
const sets = ["version = version + 1", "updated_at = ?"];
|
|
7008
7008
|
const params = [timestamp];
|
|
@@ -7097,7 +7097,7 @@ function updateTask(id, input, db) {
|
|
|
7097
7097
|
sets.push("approved_by = ?");
|
|
7098
7098
|
params.push(input.approved_by);
|
|
7099
7099
|
sets.push("approved_at = ?");
|
|
7100
|
-
params.push(
|
|
7100
|
+
params.push(now2());
|
|
7101
7101
|
}
|
|
7102
7102
|
if (input.recurrence_rule !== undefined) {
|
|
7103
7103
|
sets.push("recurrence_rule = ?");
|
|
@@ -7128,11 +7128,11 @@ function updateTask(id, input, db) {
|
|
|
7128
7128
|
if (input.approved_by !== undefined)
|
|
7129
7129
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
7130
7130
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
7131
|
-
|
|
7131
|
+
dispatchWebhook2("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
|
|
7132
7132
|
emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id, assigned_to: input.assigned_to, title: task.title } });
|
|
7133
7133
|
}
|
|
7134
7134
|
if (input.status !== undefined && input.status !== task.status) {
|
|
7135
|
-
|
|
7135
|
+
dispatchWebhook2("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
|
|
7136
7136
|
emitLocalEventHooksQuiet({ type: "task.status_changed", payload: { id, old_status: task.status, new_status: input.status, title: task.title } });
|
|
7137
7137
|
}
|
|
7138
7138
|
if (input.approved_by !== undefined) {
|
|
@@ -7330,7 +7330,7 @@ function createTemplate(input, db) {
|
|
|
7330
7330
|
input.project_id || null,
|
|
7331
7331
|
input.plan_id || null,
|
|
7332
7332
|
JSON.stringify(input.metadata || {}),
|
|
7333
|
-
|
|
7333
|
+
now2()
|
|
7334
7334
|
]);
|
|
7335
7335
|
if (input.tasks && input.tasks.length > 0) {
|
|
7336
7336
|
addTemplateTasks(id, input.tasks, d);
|
|
@@ -7375,7 +7375,7 @@ function updateTemplate(id, updates, db) {
|
|
|
7375
7375
|
metadata: current.metadata,
|
|
7376
7376
|
tasks: current.tasks
|
|
7377
7377
|
});
|
|
7378
|
-
d.run(`INSERT INTO template_versions (id, template_id, version, snapshot, created_at) VALUES (?, ?, ?, ?, ?)`, [uuid(), resolved, current.version, snapshot,
|
|
7378
|
+
d.run(`INSERT INTO template_versions (id, template_id, version, snapshot, created_at) VALUES (?, ?, ?, ?, ?)`, [uuid(), resolved, current.version, snapshot, now2()]);
|
|
7379
7379
|
}
|
|
7380
7380
|
const sets = ["version = version + 1"];
|
|
7381
7381
|
const values = [];
|
|
@@ -7459,7 +7459,7 @@ function addTemplateTasks(templateId, tasks, db) {
|
|
|
7459
7459
|
task.include_template_id || null,
|
|
7460
7460
|
JSON.stringify(task.depends_on || []),
|
|
7461
7461
|
JSON.stringify(task.metadata || {}),
|
|
7462
|
-
|
|
7462
|
+
now2()
|
|
7463
7463
|
]);
|
|
7464
7464
|
const row = d.query("SELECT * FROM template_tasks WHERE id = ?").get(id);
|
|
7465
7465
|
if (row)
|
|
@@ -7821,7 +7821,7 @@ function moveTask(taskId, target, db) {
|
|
|
7821
7821
|
if (!task)
|
|
7822
7822
|
throw new TaskNotFoundError(taskId);
|
|
7823
7823
|
const sets = ["updated_at = ?", "version = version + 1"];
|
|
7824
|
-
const params = [
|
|
7824
|
+
const params = [now2()];
|
|
7825
7825
|
if (target.task_list_id !== undefined) {
|
|
7826
7826
|
sets.push("task_list_id = ?");
|
|
7827
7827
|
params.push(target.task_list_id);
|
|
@@ -7908,7 +7908,7 @@ function startTask(id, agentId, db) {
|
|
|
7908
7908
|
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
7909
7909
|
}
|
|
7910
7910
|
const cutoff = lockExpiryCutoff();
|
|
7911
|
-
const timestamp =
|
|
7911
|
+
const timestamp = now2();
|
|
7912
7912
|
const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, started_at = COALESCE(started_at, ?), version = version + 1, updated_at = ?
|
|
7913
7913
|
WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp, timestamp, timestamp, id, agentId, cutoff]);
|
|
7914
7914
|
if (result.changes === 0) {
|
|
@@ -7922,7 +7922,7 @@ function startTask(id, agentId, db) {
|
|
|
7922
7922
|
throw new Error(`Task ${id} could not be started because it changed during claim`);
|
|
7923
7923
|
}
|
|
7924
7924
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
7925
|
-
|
|
7925
|
+
dispatchWebhook2("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
7926
7926
|
emitLocalEventHooksQuiet({ type: "task.started", payload: { id, agent_id: agentId, title: task.title } });
|
|
7927
7927
|
return { ...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 };
|
|
7928
7928
|
}
|
|
@@ -7944,7 +7944,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
7944
7944
|
completionMeta._completion = { confidence: options.confidence };
|
|
7945
7945
|
}
|
|
7946
7946
|
const hasMeta = Object.keys(completionMeta).length > 0;
|
|
7947
|
-
const timestamp = options?.completed_at ||
|
|
7947
|
+
const timestamp = options?.completed_at || now2();
|
|
7948
7948
|
const confidence = options?.confidence !== undefined ? options.confidence : null;
|
|
7949
7949
|
const tx = d.transaction(() => {
|
|
7950
7950
|
if (hasMeta) {
|
|
@@ -7960,7 +7960,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
7960
7960
|
});
|
|
7961
7961
|
tx();
|
|
7962
7962
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
7963
|
-
|
|
7963
|
+
dispatchWebhook2("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
|
|
7964
7964
|
emitLocalEventHooksQuiet({ type: "task.completed", payload: { id, agent_id: agentId, title: task.title, completed_at: timestamp } });
|
|
7965
7965
|
let spawnedTask = null;
|
|
7966
7966
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
@@ -8002,7 +8002,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
8002
8002
|
if (unblockedDeps.length > 0) {
|
|
8003
8003
|
meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
|
|
8004
8004
|
for (const dep of unblockedDeps) {
|
|
8005
|
-
|
|
8005
|
+
dispatchWebhook2("task.unblocked", { id: dep.id, unblocked_by: id, title: dep.title }, d).catch(() => {});
|
|
8006
8006
|
emitLocalEventHooksQuiet({ type: "task.unblocked", payload: { id: dep.id, unblocked_by: id, title: dep.title } });
|
|
8007
8007
|
}
|
|
8008
8008
|
}
|
|
@@ -8020,13 +8020,13 @@ function lockTask(id, agentId, db) {
|
|
|
8020
8020
|
};
|
|
8021
8021
|
}
|
|
8022
8022
|
if (task.locked_by === agentId && !isLockExpired(task.locked_at)) {
|
|
8023
|
-
const timestamp2 =
|
|
8023
|
+
const timestamp2 = now2();
|
|
8024
8024
|
d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND locked_by = ?`, [timestamp2, timestamp2, id, agentId]);
|
|
8025
8025
|
logTaskChange(id, "lock_renew", "locked_by", agentId, agentId, agentId, d);
|
|
8026
8026
|
return { success: true, locked_by: agentId, locked_at: timestamp2, expires_at: lockExpiresAt(timestamp2) };
|
|
8027
8027
|
}
|
|
8028
8028
|
const cutoff = lockExpiryCutoff();
|
|
8029
|
-
const timestamp =
|
|
8029
|
+
const timestamp = now2();
|
|
8030
8030
|
const result = d.run(`UPDATE tasks SET locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
|
|
8031
8031
|
WHERE id = ? AND status NOT IN ('completed', 'cancelled') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, timestamp, timestamp, id, agentId, cutoff]);
|
|
8032
8032
|
if (result.changes === 0) {
|
|
@@ -8063,7 +8063,7 @@ function unlockTask(id, agentId, db) {
|
|
|
8063
8063
|
if (agentId && task.locked_by && task.locked_by !== agentId) {
|
|
8064
8064
|
throw new LockError(id, task.locked_by);
|
|
8065
8065
|
}
|
|
8066
|
-
const timestamp =
|
|
8066
|
+
const timestamp = now2();
|
|
8067
8067
|
d.run(`UPDATE tasks SET locked_by = NULL, locked_at = NULL, version = version + 1, updated_at = ?
|
|
8068
8068
|
WHERE id = ?`, [timestamp, id]);
|
|
8069
8069
|
return true;
|
|
@@ -8182,15 +8182,15 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
8182
8182
|
reason: reason || "Unknown failure",
|
|
8183
8183
|
error_code: options?.error_code || null,
|
|
8184
8184
|
failed_by: agentId || null,
|
|
8185
|
-
failed_at:
|
|
8185
|
+
failed_at: now2(),
|
|
8186
8186
|
retry_requested: options?.retry || false
|
|
8187
8187
|
}
|
|
8188
8188
|
};
|
|
8189
|
-
const timestamp =
|
|
8189
|
+
const timestamp = now2();
|
|
8190
8190
|
d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
8191
8191
|
WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
|
|
8192
8192
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
8193
|
-
|
|
8193
|
+
dispatchWebhook2("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
8194
8194
|
emitLocalEventHooksQuiet({ type: "task.failed", payload: { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title } });
|
|
8195
8195
|
const failedTask = {
|
|
8196
8196
|
...task,
|
|
@@ -8267,7 +8267,7 @@ function stealTask(agentId, opts, db) {
|
|
|
8267
8267
|
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
8268
8268
|
staleTasks.sort((a, b) => (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9));
|
|
8269
8269
|
const target = staleTasks[0];
|
|
8270
|
-
const timestamp =
|
|
8270
|
+
const timestamp = now2();
|
|
8271
8271
|
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
8272
8272
|
const result = d.run(`UPDATE tasks SET assigned_to = ?, locked_by = ?, locked_at = ?, updated_at = ?, version = version + 1
|
|
8273
8273
|
WHERE id = ? AND status = 'in_progress' AND (updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))`, [agentId, agentId, timestamp, timestamp, target.id, cutoff, cutoff]);
|
|
@@ -8275,7 +8275,7 @@ function stealTask(agentId, opts, db) {
|
|
|
8275
8275
|
return null;
|
|
8276
8276
|
logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
|
|
8277
8277
|
logTaskChange(target.id, "steal", "locked_by", target.locked_by, agentId, agentId, d);
|
|
8278
|
-
|
|
8278
|
+
dispatchWebhook2("task.assigned", { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to }, d).catch(() => {});
|
|
8279
8279
|
emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to } });
|
|
8280
8280
|
return { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
8281
8281
|
}
|
|
@@ -8344,7 +8344,7 @@ function getStatus(filters, agentId, options, db) {
|
|
|
8344
8344
|
const next_task = getNextTask(agentId, filters, d);
|
|
8345
8345
|
const stale = getStaleTasks(30, filters, d);
|
|
8346
8346
|
const conditions = ["recurrence_rule IS NOT NULL", "status = 'pending'", "due_at < ?"];
|
|
8347
|
-
const params = [
|
|
8347
|
+
const params = [now2()];
|
|
8348
8348
|
if (filters?.project_id) {
|
|
8349
8349
|
conditions.push("project_id = ?");
|
|
8350
8350
|
params.push(filters.project_id);
|
|
@@ -8453,7 +8453,7 @@ function redistributeStaleTasks(agentId, options, db) {
|
|
|
8453
8453
|
const maxAge = options?.max_age_minutes ?? 60;
|
|
8454
8454
|
const stale = getStaleTasks(maxAge, options?.project_id ? { project_id: options.project_id } : undefined, d);
|
|
8455
8455
|
const limited = options?.limit ? stale.slice(0, options.limit) : stale;
|
|
8456
|
-
const timestamp =
|
|
8456
|
+
const timestamp = now2();
|
|
8457
8457
|
const released = [];
|
|
8458
8458
|
for (const t of limited) {
|
|
8459
8459
|
d.run(`UPDATE tasks SET locked_by = NULL, locked_at = NULL, status = 'pending', version = version + 1, updated_at = ? WHERE id = ?`, [timestamp, t.id]);
|
|
@@ -8600,7 +8600,7 @@ function archiveTasks(options, db) {
|
|
|
8600
8600
|
conditions.push("updated_at < ?");
|
|
8601
8601
|
params.push(cutoff);
|
|
8602
8602
|
}
|
|
8603
|
-
const ts =
|
|
8603
|
+
const ts = now2();
|
|
8604
8604
|
const result = d.run(`UPDATE tasks SET archived_at = ? WHERE ${conditions.join(" AND ")}`, [ts, ...params]);
|
|
8605
8605
|
return { archived: result.changes };
|
|
8606
8606
|
}
|
|
@@ -8787,7 +8787,7 @@ function assertPositiveMinutes(minutes) {
|
|
|
8787
8787
|
function recalculateTaskActualMinutes(taskId, db) {
|
|
8788
8788
|
const row = db.query("SELECT COALESCE(SUM(minutes), 0) as total FROM task_time_logs WHERE task_id = ?").get(taskId);
|
|
8789
8789
|
const total = Number(row.total || 0);
|
|
8790
|
-
db.run("UPDATE tasks SET actual_minutes = ?, updated_at = ? WHERE id = ?", [total,
|
|
8790
|
+
db.run("UPDATE tasks SET actual_minutes = ?, updated_at = ? WHERE id = ?", [total, now2(), taskId]);
|
|
8791
8791
|
return total;
|
|
8792
8792
|
}
|
|
8793
8793
|
function logTime(input, db) {
|
|
@@ -8795,7 +8795,7 @@ function logTime(input, db) {
|
|
|
8795
8795
|
if (!getTask(input.task_id, d))
|
|
8796
8796
|
throw new Error(`Task not found: ${input.task_id}`);
|
|
8797
8797
|
const id = uuid();
|
|
8798
|
-
const ts =
|
|
8798
|
+
const ts = now2();
|
|
8799
8799
|
const minutes = assertPositiveMinutes(input.minutes);
|
|
8800
8800
|
d.run(`INSERT INTO task_time_logs (id, task_id, run_id, focus_session_id, agent_id, minutes, started_at, ended_at, notes, created_at)
|
|
8801
8801
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.task_id, input.run_id || null, input.focus_session_id || null, input.agent_id || null, minutes, input.started_at || null, input.ended_at || null, input.notes || null, ts]);
|
|
@@ -8822,7 +8822,7 @@ function startFocusSession(input, db) {
|
|
|
8822
8822
|
if (input.task_id && !getTask(input.task_id, d))
|
|
8823
8823
|
throw new Error(`Task not found: ${input.task_id}`);
|
|
8824
8824
|
const id = uuid();
|
|
8825
|
-
const ts =
|
|
8825
|
+
const ts = now2();
|
|
8826
8826
|
const startedAt = input.started_at || ts;
|
|
8827
8827
|
d.run(`INSERT INTO focus_sessions (
|
|
8828
8828
|
id, task_id, plan_id, run_id, agent_id, title, status, started_at, last_resumed_at,
|
|
@@ -8892,7 +8892,7 @@ function pauseFocusSession(id, pausedAt, db) {
|
|
|
8892
8892
|
throw new Error(`Focus session not found: ${id}`);
|
|
8893
8893
|
if (session.status !== "active")
|
|
8894
8894
|
throw new Error(`Focus session is ${session.status}, not active`);
|
|
8895
|
-
const ts =
|
|
8895
|
+
const ts = now2();
|
|
8896
8896
|
const pauseAt = pausedAt || ts;
|
|
8897
8897
|
const minutes = session.actual_minutes + minutesBetween(session.last_resumed_at, pauseAt);
|
|
8898
8898
|
d.run("UPDATE focus_sessions SET status = 'paused', actual_minutes = ?, paused_at = ?, last_resumed_at = NULL, updated_at = ? WHERE id = ?", [minutes, pauseAt, ts, id]);
|
|
@@ -8905,7 +8905,7 @@ function resumeFocusSession(id, resumedAt, db) {
|
|
|
8905
8905
|
throw new Error(`Focus session not found: ${id}`);
|
|
8906
8906
|
if (session.status !== "paused")
|
|
8907
8907
|
throw new Error(`Focus session is ${session.status}, not paused`);
|
|
8908
|
-
const ts =
|
|
8908
|
+
const ts = now2();
|
|
8909
8909
|
const resumed = resumedAt || ts;
|
|
8910
8910
|
d.run("UPDATE focus_sessions SET status = 'active', paused_at = NULL, last_resumed_at = ?, updated_at = ? WHERE id = ?", [resumed, ts, id]);
|
|
8911
8911
|
return getFocusSession(id, d);
|
|
@@ -8917,7 +8917,7 @@ function stopFocusSession(input, db) {
|
|
|
8917
8917
|
throw new Error(`Focus session not found: ${input.id}`);
|
|
8918
8918
|
if (session.status === "completed" || session.status === "cancelled")
|
|
8919
8919
|
return session;
|
|
8920
|
-
const ts =
|
|
8920
|
+
const ts = now2();
|
|
8921
8921
|
const endedAt = input.ended_at || ts;
|
|
8922
8922
|
const finalMinutes = session.status === "active" ? session.actual_minutes + minutesBetween(session.last_resumed_at, endedAt) : session.actual_minutes;
|
|
8923
8923
|
const finalStatus = input.status || "completed";
|
|
@@ -8996,7 +8996,7 @@ function getTimeReport(opts, db) {
|
|
|
8996
8996
|
function watchTask(taskId, agentId, db) {
|
|
8997
8997
|
const d = db || getDatabase();
|
|
8998
8998
|
const id = uuid();
|
|
8999
|
-
const ts =
|
|
8999
|
+
const ts = now2();
|
|
9000
9000
|
d.run(`INSERT OR IGNORE INTO task_watchers (id, task_id, agent_id, created_at) VALUES (?, ?, ?, ?)`, [id, taskId, agentId, ts]);
|
|
9001
9001
|
const existing = d.query(`SELECT * FROM task_watchers WHERE task_id = ? AND agent_id = ?`).get(taskId, agentId);
|
|
9002
9002
|
return existing;
|
|
@@ -9012,11 +9012,11 @@ function getTaskWatchers(taskId, db) {
|
|
|
9012
9012
|
}
|
|
9013
9013
|
function notifyWatchers(taskId, event, data, db) {
|
|
9014
9014
|
const watchers = getTaskWatchers(taskId, db);
|
|
9015
|
-
|
|
9015
|
+
dispatchWebhook2(`task.watcher.${event}`, { task_id: taskId, watchers: watchers.map((w) => w.agent_id), ...data }, db).catch(() => {});
|
|
9016
9016
|
}
|
|
9017
9017
|
function logCost(taskId, tokens, usd, db) {
|
|
9018
9018
|
const d = db || getDatabase();
|
|
9019
|
-
d.run("UPDATE tasks SET cost_tokens = cost_tokens + ?, cost_usd = cost_usd + ?, updated_at = ? WHERE id = ?", [tokens, usd,
|
|
9019
|
+
d.run("UPDATE tasks SET cost_tokens = cost_tokens + ?, cost_usd = cost_usd + ?, updated_at = ? WHERE id = ?", [tokens, usd, now2(), taskId]);
|
|
9020
9020
|
}
|
|
9021
9021
|
var init_task_relations = __esm(() => {
|
|
9022
9022
|
init_database();
|
|
@@ -9029,7 +9029,7 @@ var init_task_relations = __esm(() => {
|
|
|
9029
9029
|
function createPlan(input, db) {
|
|
9030
9030
|
const d = db || getDatabase();
|
|
9031
9031
|
const id = uuid();
|
|
9032
|
-
const timestamp =
|
|
9032
|
+
const timestamp = now2();
|
|
9033
9033
|
d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at)
|
|
9034
9034
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
9035
9035
|
id,
|
|
@@ -9062,7 +9062,7 @@ function updatePlan(id, input, db) {
|
|
|
9062
9062
|
if (!plan)
|
|
9063
9063
|
throw new PlanNotFoundError(id);
|
|
9064
9064
|
const sets = ["updated_at = ?"];
|
|
9065
|
-
const params = [
|
|
9065
|
+
const params = [now2()];
|
|
9066
9066
|
if (input.name !== undefined) {
|
|
9067
9067
|
sets.push("name = ?");
|
|
9068
9068
|
params.push(input.name);
|
|
@@ -9104,7 +9104,7 @@ var init_plans = __esm(() => {
|
|
|
9104
9104
|
});
|
|
9105
9105
|
|
|
9106
9106
|
// src/db/boards.ts
|
|
9107
|
-
function
|
|
9107
|
+
function parseJsonObject2(value) {
|
|
9108
9108
|
if (!value)
|
|
9109
9109
|
return {};
|
|
9110
9110
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -9147,7 +9147,7 @@ function rowToTaskBoard(row) {
|
|
|
9147
9147
|
return {
|
|
9148
9148
|
...row,
|
|
9149
9149
|
lanes: normalizeLanes(row.scope, parseJsonArray(row.lanes)),
|
|
9150
|
-
filters:
|
|
9150
|
+
filters: parseJsonObject2(row.filters)
|
|
9151
9151
|
};
|
|
9152
9152
|
}
|
|
9153
9153
|
function maybeFilter(value) {
|
|
@@ -9156,7 +9156,7 @@ function maybeFilter(value) {
|
|
|
9156
9156
|
function createTaskBoard(input, db) {
|
|
9157
9157
|
const d = db || getDatabase();
|
|
9158
9158
|
const id = uuid();
|
|
9159
|
-
const timestamp =
|
|
9159
|
+
const timestamp = now2();
|
|
9160
9160
|
const scope = input.scope || "tasks";
|
|
9161
9161
|
const lanes = normalizeLanes(scope, input.lanes);
|
|
9162
9162
|
d.run(`INSERT INTO task_boards (id, name, scope, project_id, task_list_id, plan_id, agent_id, lanes, filters, created_at, updated_at)
|
|
@@ -9220,7 +9220,7 @@ function updateTaskBoard(idOrName, input, db) {
|
|
|
9220
9220
|
if (!board)
|
|
9221
9221
|
throw new Error(`Board not found: ${idOrName}`);
|
|
9222
9222
|
const sets = ["updated_at = ?"];
|
|
9223
|
-
const params = [
|
|
9223
|
+
const params = [now2()];
|
|
9224
9224
|
if (input.name !== undefined) {
|
|
9225
9225
|
sets.push("name = ?");
|
|
9226
9226
|
params.push(input.name);
|
|
@@ -9375,7 +9375,7 @@ function buildTaskBoardSnapshot(idOrBoard, db) {
|
|
|
9375
9375
|
const lanes = buildLaneSnapshots(board, cards);
|
|
9376
9376
|
return {
|
|
9377
9377
|
board,
|
|
9378
|
-
generated_at:
|
|
9378
|
+
generated_at: now2(),
|
|
9379
9379
|
lanes,
|
|
9380
9380
|
totals: {
|
|
9381
9381
|
cards: cards.length,
|
|
@@ -9442,7 +9442,7 @@ function exportTaskBoardBundle(idOrName, db) {
|
|
|
9442
9442
|
return {
|
|
9443
9443
|
kind: "hasna.todos.task-board",
|
|
9444
9444
|
schemaVersion: 1,
|
|
9445
|
-
exportedAt:
|
|
9445
|
+
exportedAt: now2(),
|
|
9446
9446
|
boards
|
|
9447
9447
|
};
|
|
9448
9448
|
}
|
|
@@ -9506,8 +9506,8 @@ var init_boards = __esm(() => {
|
|
|
9506
9506
|
|
|
9507
9507
|
// src/lib/artifact-store.ts
|
|
9508
9508
|
import { createHash as createHash2 } from "crypto";
|
|
9509
|
-
import { existsSync as
|
|
9510
|
-
import { basename, dirname as dirname5, join as
|
|
9509
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
9510
|
+
import { basename, dirname as dirname5, join as join6, resolve as resolve6 } from "path";
|
|
9511
9511
|
import { tmpdir } from "os";
|
|
9512
9512
|
function isInMemoryDb2(path) {
|
|
9513
9513
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
@@ -9519,15 +9519,15 @@ function artifactStoreRoot() {
|
|
|
9519
9519
|
return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
9520
9520
|
const dbPath = getDatabasePath();
|
|
9521
9521
|
if (isInMemoryDb2(dbPath))
|
|
9522
|
-
return
|
|
9523
|
-
return
|
|
9522
|
+
return join6(tmpdir(), "hasna-todos-artifacts");
|
|
9523
|
+
return join6(dirname5(resolve6(dbPath)), "artifacts");
|
|
9524
9524
|
}
|
|
9525
9525
|
function artifactStorePath(relativePath) {
|
|
9526
9526
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
9527
9527
|
if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
|
|
9528
9528
|
throw new Error("Invalid artifact store path");
|
|
9529
9529
|
}
|
|
9530
|
-
return
|
|
9530
|
+
return join6(artifactStoreRoot(), normalized);
|
|
9531
9531
|
}
|
|
9532
9532
|
function sha256(buffer) {
|
|
9533
9533
|
return createHash2("sha256").update(buffer).digest("hex");
|
|
@@ -9568,7 +9568,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
9568
9568
|
}
|
|
9569
9569
|
function storeArtifactContent(input) {
|
|
9570
9570
|
const sourcePath = resolve6(input.path);
|
|
9571
|
-
if (!
|
|
9571
|
+
if (!existsSync7(sourcePath))
|
|
9572
9572
|
return null;
|
|
9573
9573
|
const sourceStat = statSync2(sourcePath);
|
|
9574
9574
|
if (!sourceStat.isFile())
|
|
@@ -9585,9 +9585,9 @@ function storeArtifactContent(input) {
|
|
|
9585
9585
|
redactionStatus = "redacted";
|
|
9586
9586
|
}
|
|
9587
9587
|
const storedSha = sha256(storedBuffer);
|
|
9588
|
-
const relativePath =
|
|
9588
|
+
const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
|
|
9589
9589
|
const destination = artifactStorePath(relativePath);
|
|
9590
|
-
if (!
|
|
9590
|
+
if (!existsSync7(destination)) {
|
|
9591
9591
|
mkdirSync4(dirname5(destination), { recursive: true });
|
|
9592
9592
|
writeFileSync2(destination, storedBuffer);
|
|
9593
9593
|
}
|
|
@@ -9647,7 +9647,7 @@ function verifyStoredArtifact(input) {
|
|
|
9647
9647
|
};
|
|
9648
9648
|
}
|
|
9649
9649
|
const storedPath = artifactStorePath(store.relative_path);
|
|
9650
|
-
if (!
|
|
9650
|
+
if (!existsSync7(storedPath)) {
|
|
9651
9651
|
return {
|
|
9652
9652
|
id: input.id,
|
|
9653
9653
|
path: input.path,
|
|
@@ -9741,7 +9741,7 @@ function addComment(input, db) {
|
|
|
9741
9741
|
throw new TaskNotFoundError(input.task_id);
|
|
9742
9742
|
}
|
|
9743
9743
|
const id = uuid();
|
|
9744
|
-
const timestamp =
|
|
9744
|
+
const timestamp = now2();
|
|
9745
9745
|
d.run(`INSERT INTO task_comments (id, task_id, agent_id, session_id, content, type, progress_pct, created_at)
|
|
9746
9746
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
9747
9747
|
id,
|
|
@@ -9805,7 +9805,7 @@ __export(exports_task_files, {
|
|
|
9805
9805
|
function addTaskFile(input, db) {
|
|
9806
9806
|
const d = db || getDatabase();
|
|
9807
9807
|
const id = uuid();
|
|
9808
|
-
const timestamp =
|
|
9808
|
+
const timestamp = now2();
|
|
9809
9809
|
const existing = d.query("SELECT id FROM task_files WHERE task_id = ? AND path = ?").get(input.task_id, input.path);
|
|
9810
9810
|
if (existing) {
|
|
9811
9811
|
d.run("UPDATE task_files SET status = ?, agent_id = ?, note = ?, updated_at = ? WHERE id = ?", [input.status || "active", input.agent_id || null, input.note || null, timestamp, existing.id]);
|
|
@@ -9829,7 +9829,7 @@ function findTasksByFile(path, db) {
|
|
|
9829
9829
|
}
|
|
9830
9830
|
function updateTaskFileStatus(taskId, path, status, agentId, db) {
|
|
9831
9831
|
const d = db || getDatabase();
|
|
9832
|
-
const timestamp =
|
|
9832
|
+
const timestamp = now2();
|
|
9833
9833
|
d.run("UPDATE task_files SET status = ?, agent_id = COALESCE(?, agent_id), updated_at = ? WHERE task_id = ? AND path = ?", [status, agentId || null, timestamp, taskId, path]);
|
|
9834
9834
|
const row = d.query("SELECT * FROM task_files WHERE task_id = ? AND path = ?").get(taskId, path);
|
|
9835
9835
|
return row;
|
|
@@ -10040,7 +10040,7 @@ function linkTaskToCommit(input, db) {
|
|
|
10040
10040
|
input.release_tag ?? null,
|
|
10041
10041
|
input.repo_path ?? null,
|
|
10042
10042
|
traceJson ?? "{}",
|
|
10043
|
-
|
|
10043
|
+
now2()
|
|
10044
10044
|
]);
|
|
10045
10045
|
return rowToCommit(d.query("SELECT * FROM task_commits WHERE id = ?").get(id));
|
|
10046
10046
|
}
|
|
@@ -10061,7 +10061,7 @@ function unlinkTaskCommit(taskId, sha, db) {
|
|
|
10061
10061
|
}
|
|
10062
10062
|
function linkTaskGitRef(input, db) {
|
|
10063
10063
|
const d = db || getDatabase();
|
|
10064
|
-
const timestamp =
|
|
10064
|
+
const timestamp = now2();
|
|
10065
10065
|
const metadata = JSON.stringify(input.metadata || {});
|
|
10066
10066
|
const existing = d.query("SELECT * FROM task_git_refs WHERE task_id = ? AND ref_type = ? AND name = ?").get(input.task_id, input.ref_type, input.name);
|
|
10067
10067
|
if (existing) {
|
|
@@ -10083,7 +10083,7 @@ function findTasksByGitRef(ref, db) {
|
|
|
10083
10083
|
function addTaskVerification(input, db) {
|
|
10084
10084
|
const d = db || getDatabase();
|
|
10085
10085
|
const id = uuid();
|
|
10086
|
-
const runAt = input.run_at ||
|
|
10086
|
+
const runAt = input.run_at || now2();
|
|
10087
10087
|
d.run("INSERT INTO task_verifications (id, task_id, command, status, output_summary, artifact_path, agent_id, run_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
10088
10088
|
id,
|
|
10089
10089
|
input.task_id,
|
|
@@ -10093,7 +10093,7 @@ function addTaskVerification(input, db) {
|
|
|
10093
10093
|
input.artifact_path ?? null,
|
|
10094
10094
|
input.agent_id ?? null,
|
|
10095
10095
|
runAt,
|
|
10096
|
-
|
|
10096
|
+
now2()
|
|
10097
10097
|
]);
|
|
10098
10098
|
return rowToVerification(d.query("SELECT * FROM task_verifications WHERE id = ?").get(id));
|
|
10099
10099
|
}
|
|
@@ -10179,7 +10179,7 @@ function startTaskRun(input, db) {
|
|
|
10179
10179
|
if (!getTask(input.task_id, d))
|
|
10180
10180
|
throw new TaskNotFoundError(input.task_id);
|
|
10181
10181
|
const id = uuid();
|
|
10182
|
-
const timestamp = input.started_at ||
|
|
10182
|
+
const timestamp = input.started_at || now2();
|
|
10183
10183
|
if (input.claim && input.agent_id) {
|
|
10184
10184
|
startTask(input.task_id, input.agent_id, d);
|
|
10185
10185
|
}
|
|
@@ -10189,7 +10189,7 @@ function startTaskRun(input, db) {
|
|
|
10189
10189
|
input.agent_id ?? null,
|
|
10190
10190
|
input.title ? redactEvidenceText(input.title) : null,
|
|
10191
10191
|
input.summary ? redactEvidenceText(input.summary) : null,
|
|
10192
|
-
JSON.stringify(
|
|
10192
|
+
JSON.stringify(redactValue2(input.metadata || {})),
|
|
10193
10193
|
timestamp,
|
|
10194
10194
|
timestamp,
|
|
10195
10195
|
timestamp
|
|
@@ -10223,14 +10223,14 @@ function addTaskRunEvent(input, db) {
|
|
|
10223
10223
|
if (!run)
|
|
10224
10224
|
throw new Error(`Run not found: ${input.run_id}`);
|
|
10225
10225
|
const id = uuid();
|
|
10226
|
-
const timestamp = input.created_at ||
|
|
10226
|
+
const timestamp = input.created_at || now2();
|
|
10227
10227
|
d.run("INSERT INTO task_run_events (id, run_id, task_id, event_type, message, data, agent_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
10228
10228
|
id,
|
|
10229
10229
|
run.id,
|
|
10230
10230
|
run.task_id,
|
|
10231
10231
|
input.event_type,
|
|
10232
10232
|
input.message ? redactEvidenceText(input.message) : null,
|
|
10233
|
-
JSON.stringify(
|
|
10233
|
+
JSON.stringify(redactValue2(input.data || {})),
|
|
10234
10234
|
input.agent_id ?? run.agent_id,
|
|
10235
10235
|
timestamp
|
|
10236
10236
|
]);
|
|
@@ -10247,7 +10247,7 @@ function addTaskRunCommand(input, db) {
|
|
|
10247
10247
|
throw new Error(`Run not found: ${input.run_id}`);
|
|
10248
10248
|
const id = uuid();
|
|
10249
10249
|
const status = input.status || "unknown";
|
|
10250
|
-
const timestamp =
|
|
10250
|
+
const timestamp = now2();
|
|
10251
10251
|
const command = redactEvidenceText(input.command);
|
|
10252
10252
|
const outputSummary = input.output_summary ? redactEvidenceText(input.output_summary) : null;
|
|
10253
10253
|
const artifactPath = input.artifact_path ? redactEvidenceText(input.artifact_path) : null;
|
|
@@ -10324,10 +10324,10 @@ function addTaskRunArtifact(input, db) {
|
|
|
10324
10324
|
if (!run)
|
|
10325
10325
|
throw new Error(`Run not found: ${input.run_id}`);
|
|
10326
10326
|
const id = uuid();
|
|
10327
|
-
const timestamp =
|
|
10327
|
+
const timestamp = now2();
|
|
10328
10328
|
const path = redactEvidenceText(input.path);
|
|
10329
10329
|
const description = input.description ? redactEvidenceText(input.description) : null;
|
|
10330
|
-
const metadata =
|
|
10330
|
+
const metadata = redactValue2(input.metadata || {});
|
|
10331
10331
|
let sizeBytes = input.size_bytes ?? null;
|
|
10332
10332
|
let digest = input.sha256 ?? null;
|
|
10333
10333
|
const stored = input.store_content !== false ? storeArtifactContent({ path: input.path, metadata, retention_days: input.retention_days, created_at: timestamp }) : null;
|
|
@@ -10377,7 +10377,7 @@ function finishTaskRun(input, db) {
|
|
|
10377
10377
|
const run = getTaskRun(runId, d);
|
|
10378
10378
|
if (!run)
|
|
10379
10379
|
throw new Error(`Run not found: ${input.run_id}`);
|
|
10380
|
-
const timestamp = input.completed_at ||
|
|
10380
|
+
const timestamp = input.completed_at || now2();
|
|
10381
10381
|
const summary = input.summary ? redactEvidenceText(input.summary) : null;
|
|
10382
10382
|
d.run("UPDATE task_runs SET status = ?, summary = COALESCE(?, summary), completed_at = ?, updated_at = ? WHERE id = ?", [input.status, summary, timestamp, timestamp, run.id]);
|
|
10383
10383
|
addTaskRunEvent({
|
|
@@ -10427,7 +10427,7 @@ var init_task_runs = __esm(() => {
|
|
|
10427
10427
|
});
|
|
10428
10428
|
|
|
10429
10429
|
// src/db/calendar.ts
|
|
10430
|
-
function
|
|
10430
|
+
function parseJsonObject3(value) {
|
|
10431
10431
|
if (!value)
|
|
10432
10432
|
return {};
|
|
10433
10433
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -10442,7 +10442,7 @@ function parseJsonObject2(value) {
|
|
|
10442
10442
|
}
|
|
10443
10443
|
}
|
|
10444
10444
|
function rowToCalendarItem(row) {
|
|
10445
|
-
return { ...row, metadata:
|
|
10445
|
+
return { ...row, metadata: parseJsonObject3(row.metadata) };
|
|
10446
10446
|
}
|
|
10447
10447
|
function eventFromLocal(item) {
|
|
10448
10448
|
return {
|
|
@@ -10543,7 +10543,7 @@ function inWindow(event, query) {
|
|
|
10543
10543
|
function createCalendarItem(input, db) {
|
|
10544
10544
|
const d = db || getDatabase();
|
|
10545
10545
|
const id = uuid();
|
|
10546
|
-
const timestamp =
|
|
10546
|
+
const timestamp = now2();
|
|
10547
10547
|
const kind = input.kind || "work_block";
|
|
10548
10548
|
d.run(`INSERT INTO local_calendar_items (
|
|
10549
10549
|
id, kind, title, description, starts_at, ends_at, timezone, project_id, task_id, plan_id, run_id,
|
|
@@ -10702,7 +10702,7 @@ function recurrenceToRrule(rule) {
|
|
|
10702
10702
|
}
|
|
10703
10703
|
function exportCalendarIcs(options = {}, db) {
|
|
10704
10704
|
const events = listCalendarEvents(options, db);
|
|
10705
|
-
const generatedAt = options.generated_at ||
|
|
10705
|
+
const generatedAt = options.generated_at || now2();
|
|
10706
10706
|
const lines = [
|
|
10707
10707
|
"BEGIN:VCALENDAR",
|
|
10708
10708
|
"VERSION:2.0",
|
|
@@ -11202,8 +11202,8 @@ function registerTaskCommands(program2) {
|
|
|
11202
11202
|
tasks = tasks.filter((t) => t.due_at && t.due_at <= todayEnd.toISOString());
|
|
11203
11203
|
}
|
|
11204
11204
|
if (opts.overdue) {
|
|
11205
|
-
const
|
|
11206
|
-
tasks = tasks.filter((t) => t.due_at && t.due_at <
|
|
11205
|
+
const now3 = new Date().toISOString();
|
|
11206
|
+
tasks = tasks.filter((t) => t.due_at && t.due_at < now3 && t.status !== "completed");
|
|
11207
11207
|
}
|
|
11208
11208
|
if (opts.sort) {
|
|
11209
11209
|
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
@@ -11776,7 +11776,7 @@ __export(exports_builtin_templates, {
|
|
|
11776
11776
|
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
11777
11777
|
});
|
|
11778
11778
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
11779
|
-
import { join as
|
|
11779
|
+
import { join as join7 } from "path";
|
|
11780
11780
|
function templateMetadata(template) {
|
|
11781
11781
|
return {
|
|
11782
11782
|
source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
@@ -11835,7 +11835,7 @@ function writeBuiltinTemplateFiles(directory) {
|
|
|
11835
11835
|
mkdirSync5(directory, { recursive: true });
|
|
11836
11836
|
const files = [];
|
|
11837
11837
|
for (const entry of exportBuiltinTemplateFiles()) {
|
|
11838
|
-
const path =
|
|
11838
|
+
const path = join7(directory, entry.filename);
|
|
11839
11839
|
writeFileSync3(path, `${JSON.stringify(entry.template, null, 2)}
|
|
11840
11840
|
`, "utf-8");
|
|
11841
11841
|
files.push(path);
|
|
@@ -12560,7 +12560,7 @@ function setTaskLocalFields(taskId, input, db) {
|
|
|
12560
12560
|
throw new TaskNotFoundError(taskId);
|
|
12561
12561
|
const currentFields = getTaskLocalFields(taskId, d);
|
|
12562
12562
|
const labels = input.labels !== undefined ? normalizeList(input.labels) : currentFields.labels;
|
|
12563
|
-
const custom = input.custom !== undefined ?
|
|
12563
|
+
const custom = input.custom !== undefined ? redactValue2(input.merge_custom === false ? input.custom : { ...currentFields.custom, ...input.custom }) : currentFields.custom;
|
|
12564
12564
|
const nextFields = {
|
|
12565
12565
|
labels,
|
|
12566
12566
|
priority: input.priority || task.priority,
|
|
@@ -12804,7 +12804,7 @@ function likePattern(query) {
|
|
|
12804
12804
|
return null;
|
|
12805
12805
|
return `%${trimmed}%`;
|
|
12806
12806
|
}
|
|
12807
|
-
function
|
|
12807
|
+
function parseJsonObject4(value) {
|
|
12808
12808
|
if (!value)
|
|
12809
12809
|
return {};
|
|
12810
12810
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -12819,7 +12819,7 @@ function parseJsonObject3(value) {
|
|
|
12819
12819
|
}
|
|
12820
12820
|
}
|
|
12821
12821
|
function rowToTaskRun(row) {
|
|
12822
|
-
return { ...row, metadata:
|
|
12822
|
+
return { ...row, metadata: parseJsonObject4(row.metadata) };
|
|
12823
12823
|
}
|
|
12824
12824
|
function taskMatchesSavedFilters(task, filters, db) {
|
|
12825
12825
|
if (filters.plan_id && task.plan_id !== filters.plan_id)
|
|
@@ -13020,7 +13020,7 @@ function runSavedSearch(filters = {}, scope = "tasks", db) {
|
|
|
13020
13020
|
function saveSearchView(input, db) {
|
|
13021
13021
|
const d = db || getDatabase();
|
|
13022
13022
|
const name = normalizeName(input.name);
|
|
13023
|
-
const timestamp =
|
|
13023
|
+
const timestamp = now2();
|
|
13024
13024
|
const existing = getSearchView(name, d);
|
|
13025
13025
|
if (existing) {
|
|
13026
13026
|
d.run(`UPDATE saved_search_views
|
|
@@ -13077,16 +13077,16 @@ var init_saved_search_views = __esm(() => {
|
|
|
13077
13077
|
});
|
|
13078
13078
|
|
|
13079
13079
|
// src/lib/claude-tasks.ts
|
|
13080
|
-
import { existsSync as
|
|
13081
|
-
import { join as
|
|
13080
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
13081
|
+
import { join as join8 } from "path";
|
|
13082
13082
|
function getTaskListDir(taskListId) {
|
|
13083
|
-
return
|
|
13083
|
+
return join8(HOME, ".claude", "tasks", taskListId);
|
|
13084
13084
|
}
|
|
13085
13085
|
function readClaudeTask(dir, filename) {
|
|
13086
|
-
return readJsonFile(
|
|
13086
|
+
return readJsonFile(join8(dir, filename));
|
|
13087
13087
|
}
|
|
13088
13088
|
function writeClaudeTask(dir, task) {
|
|
13089
|
-
writeJsonFile(
|
|
13089
|
+
writeJsonFile(join8(dir, `${task.id}.json`), task);
|
|
13090
13090
|
}
|
|
13091
13091
|
function toClaudeStatus(status) {
|
|
13092
13092
|
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
@@ -13098,14 +13098,14 @@ function toSqliteStatus(status) {
|
|
|
13098
13098
|
return status;
|
|
13099
13099
|
}
|
|
13100
13100
|
function readPrefixCounter(dir) {
|
|
13101
|
-
const path =
|
|
13102
|
-
if (!
|
|
13101
|
+
const path = join8(dir, ".prefix-counter");
|
|
13102
|
+
if (!existsSync8(path))
|
|
13103
13103
|
return 0;
|
|
13104
13104
|
const val = parseInt(readFileSync4(path, "utf-8").trim(), 10);
|
|
13105
13105
|
return isNaN(val) ? 0 : val;
|
|
13106
13106
|
}
|
|
13107
13107
|
function writePrefixCounter(dir, value) {
|
|
13108
|
-
writeFileSync4(
|
|
13108
|
+
writeFileSync4(join8(dir, ".prefix-counter"), String(value));
|
|
13109
13109
|
}
|
|
13110
13110
|
function formatPrefixedSubject(title, prefix, counter) {
|
|
13111
13111
|
const padded = String(counter).padStart(5, "0");
|
|
@@ -13132,7 +13132,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
|
|
|
13132
13132
|
}
|
|
13133
13133
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
13134
13134
|
const dir = getTaskListDir(taskListId);
|
|
13135
|
-
if (!
|
|
13135
|
+
if (!existsSync8(dir))
|
|
13136
13136
|
ensureDir2(dir);
|
|
13137
13137
|
const filter = {};
|
|
13138
13138
|
if (projectId)
|
|
@@ -13141,7 +13141,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
13141
13141
|
const existingByTodosId = new Map;
|
|
13142
13142
|
const files = listJsonFiles(dir);
|
|
13143
13143
|
for (const f of files) {
|
|
13144
|
-
const path =
|
|
13144
|
+
const path = join8(dir, f);
|
|
13145
13145
|
const ct = readClaudeTask(dir, f);
|
|
13146
13146
|
if (ct?.metadata?.["todos_id"]) {
|
|
13147
13147
|
existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -13228,7 +13228,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
13228
13228
|
}
|
|
13229
13229
|
function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
13230
13230
|
const dir = getTaskListDir(taskListId);
|
|
13231
|
-
if (!
|
|
13231
|
+
if (!existsSync8(dir)) {
|
|
13232
13232
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
13233
13233
|
}
|
|
13234
13234
|
const files = readdirSync2(dir).filter((f) => f.endsWith(".json"));
|
|
@@ -13248,7 +13248,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
13248
13248
|
}
|
|
13249
13249
|
for (const f of files) {
|
|
13250
13250
|
try {
|
|
13251
|
-
const filePath =
|
|
13251
|
+
const filePath = join8(dir, f);
|
|
13252
13252
|
const ct = readClaudeTask(dir, f);
|
|
13253
13253
|
if (!ct)
|
|
13254
13254
|
continue;
|
|
@@ -13321,20 +13321,20 @@ var init_claude_tasks = __esm(() => {
|
|
|
13321
13321
|
});
|
|
13322
13322
|
|
|
13323
13323
|
// src/lib/agent-tasks.ts
|
|
13324
|
-
import { existsSync as
|
|
13325
|
-
import { join as
|
|
13324
|
+
import { existsSync as existsSync9 } from "fs";
|
|
13325
|
+
import { join as join9 } from "path";
|
|
13326
13326
|
function agentBaseDir(agent) {
|
|
13327
13327
|
const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
|
|
13328
|
-
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] ||
|
|
13328
|
+
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join9(getTodosGlobalDir(), "agents");
|
|
13329
13329
|
}
|
|
13330
13330
|
function getTaskListDir2(agent, taskListId) {
|
|
13331
|
-
return
|
|
13331
|
+
return join9(agentBaseDir(agent), agent, taskListId);
|
|
13332
13332
|
}
|
|
13333
13333
|
function readAgentTask(dir, filename) {
|
|
13334
|
-
return readJsonFile(
|
|
13334
|
+
return readJsonFile(join9(dir, filename));
|
|
13335
13335
|
}
|
|
13336
13336
|
function writeAgentTask(dir, task) {
|
|
13337
|
-
writeJsonFile(
|
|
13337
|
+
writeJsonFile(join9(dir, `${task.id}.json`), task);
|
|
13338
13338
|
}
|
|
13339
13339
|
function taskToAgentTask(task, externalId, existingMeta) {
|
|
13340
13340
|
return {
|
|
@@ -13359,7 +13359,7 @@ function metadataKey(agent) {
|
|
|
13359
13359
|
}
|
|
13360
13360
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
13361
13361
|
const dir = getTaskListDir2(agent, taskListId);
|
|
13362
|
-
if (!
|
|
13362
|
+
if (!existsSync9(dir))
|
|
13363
13363
|
ensureDir2(dir);
|
|
13364
13364
|
const filter = {};
|
|
13365
13365
|
if (projectId)
|
|
@@ -13368,7 +13368,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
13368
13368
|
const existingByTodosId = new Map;
|
|
13369
13369
|
const files = listJsonFiles(dir);
|
|
13370
13370
|
for (const f of files) {
|
|
13371
|
-
const path =
|
|
13371
|
+
const path = join9(dir, f);
|
|
13372
13372
|
const at = readAgentTask(dir, f);
|
|
13373
13373
|
if (at?.metadata?.["todos_id"]) {
|
|
13374
13374
|
existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -13442,7 +13442,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
13442
13442
|
}
|
|
13443
13443
|
function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
13444
13444
|
const dir = getTaskListDir2(agent, taskListId);
|
|
13445
|
-
if (!
|
|
13445
|
+
if (!existsSync9(dir)) {
|
|
13446
13446
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
13447
13447
|
}
|
|
13448
13448
|
const files = listJsonFiles(dir);
|
|
@@ -13461,7 +13461,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
13461
13461
|
}
|
|
13462
13462
|
for (const f of files) {
|
|
13463
13463
|
try {
|
|
13464
|
-
const filePath =
|
|
13464
|
+
const filePath = join9(dir, f);
|
|
13465
13465
|
const at = readAgentTask(dir, f);
|
|
13466
13466
|
if (!at)
|
|
13467
13467
|
continue;
|
|
@@ -13618,7 +13618,7 @@ function rowToTaskList(row) {
|
|
|
13618
13618
|
function createTaskList(input, db) {
|
|
13619
13619
|
const d = db || getDatabase();
|
|
13620
13620
|
const id = uuid();
|
|
13621
|
-
const timestamp =
|
|
13621
|
+
const timestamp = now2();
|
|
13622
13622
|
const slug = input.slug || slugify(input.name);
|
|
13623
13623
|
if (!input.project_id) {
|
|
13624
13624
|
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
@@ -13658,7 +13658,7 @@ function updateTaskList(id, input, db) {
|
|
|
13658
13658
|
if (!existing)
|
|
13659
13659
|
throw new TaskListNotFoundError(id);
|
|
13660
13660
|
const sets = ["updated_at = ?"];
|
|
13661
|
-
const params = [
|
|
13661
|
+
const params = [now2()];
|
|
13662
13662
|
if (input.name !== undefined) {
|
|
13663
13663
|
sets.push("name = ?");
|
|
13664
13664
|
params.push(input.name);
|
|
@@ -13698,7 +13698,7 @@ __export(exports_project_bootstrap, {
|
|
|
13698
13698
|
discoverProjectWorkspace: () => discoverProjectWorkspace,
|
|
13699
13699
|
bootstrapProject: () => bootstrapProject
|
|
13700
13700
|
});
|
|
13701
|
-
import { existsSync as
|
|
13701
|
+
import { existsSync as existsSync10, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
13702
13702
|
import { basename as basename4, dirname as dirname6, resolve as resolve9 } from "path";
|
|
13703
13703
|
function safeStat(path) {
|
|
13704
13704
|
try {
|
|
@@ -13717,7 +13717,7 @@ function canonicalPath(input) {
|
|
|
13717
13717
|
function findUp(start, marker) {
|
|
13718
13718
|
let current = canonicalPath(start);
|
|
13719
13719
|
while (true) {
|
|
13720
|
-
if (
|
|
13720
|
+
if (existsSync10(resolve9(current, marker)))
|
|
13721
13721
|
return current;
|
|
13722
13722
|
const parent = dirname6(current);
|
|
13723
13723
|
if (parent === current)
|
|
@@ -13729,7 +13729,7 @@ function readPackageJson(path) {
|
|
|
13729
13729
|
if (!path)
|
|
13730
13730
|
return null;
|
|
13731
13731
|
const file = resolve9(path, "package.json");
|
|
13732
|
-
if (!
|
|
13732
|
+
if (!existsSync10(file))
|
|
13733
13733
|
return null;
|
|
13734
13734
|
try {
|
|
13735
13735
|
const parsed = JSON.parse(readFileSync5(file, "utf-8"));
|
|
@@ -13751,7 +13751,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
13751
13751
|
if (rootPackage?.workspaces)
|
|
13752
13752
|
markers.push("package.json#workspaces");
|
|
13753
13753
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
13754
|
-
if (
|
|
13754
|
+
if (existsSync10(resolve9(root, marker)))
|
|
13755
13755
|
markers.push(marker);
|
|
13756
13756
|
}
|
|
13757
13757
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -13873,9 +13873,9 @@ __export(exports_extract, {
|
|
|
13873
13873
|
buildCodebaseIndex: () => buildCodebaseIndex,
|
|
13874
13874
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
13875
13875
|
});
|
|
13876
|
-
import { existsSync as
|
|
13876
|
+
import { existsSync as existsSync11, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
13877
13877
|
import { createHash as createHash3 } from "crypto";
|
|
13878
|
-
import { relative as relative3, resolve as resolve10, join as
|
|
13878
|
+
import { relative as relative3, resolve as resolve10, join as join10 } from "path";
|
|
13879
13879
|
function stableHash(value) {
|
|
13880
13880
|
return createHash3("sha256").update(value).digest("hex");
|
|
13881
13881
|
}
|
|
@@ -13884,8 +13884,8 @@ function normalizePathForMatch(value) {
|
|
|
13884
13884
|
}
|
|
13885
13885
|
function readGitignorePatterns(basePath) {
|
|
13886
13886
|
const root = statSync4(basePath).isFile() ? resolve10(basePath, "..") : basePath;
|
|
13887
|
-
const gitignorePath =
|
|
13888
|
-
if (!
|
|
13887
|
+
const gitignorePath = join10(root, ".gitignore");
|
|
13888
|
+
if (!existsSync11(gitignorePath))
|
|
13889
13889
|
return [];
|
|
13890
13890
|
try {
|
|
13891
13891
|
return readFileSync6(gitignorePath, "utf-8").split(`
|
|
@@ -14027,7 +14027,7 @@ function buildCodebaseIndex(options) {
|
|
|
14027
14027
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
14028
14028
|
const indexed = [];
|
|
14029
14029
|
for (const file of files) {
|
|
14030
|
-
const fullPath = statSync4(basePath).isFile() ? basePath :
|
|
14030
|
+
const fullPath = statSync4(basePath).isFile() ? basePath : join10(basePath, file);
|
|
14031
14031
|
try {
|
|
14032
14032
|
const source = readFileSync6(fullPath, "utf-8");
|
|
14033
14033
|
const relPath = statSync4(basePath).isFile() ? relative3(resolve10(basePath, ".."), fullPath) : file;
|
|
@@ -14058,7 +14058,7 @@ function extractTodos(options, db) {
|
|
|
14058
14058
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
14059
14059
|
const allComments = [];
|
|
14060
14060
|
for (const file of files) {
|
|
14061
|
-
const fullPath = statSync4(basePath).isFile() ? basePath :
|
|
14061
|
+
const fullPath = statSync4(basePath).isFile() ? basePath : join10(basePath, file);
|
|
14062
14062
|
try {
|
|
14063
14063
|
const source = readFileSync6(fullPath, "utf-8");
|
|
14064
14064
|
const relPath = statSync4(basePath).isFile() ? relative3(resolve10(basePath, ".."), fullPath) : file;
|
|
@@ -14266,7 +14266,7 @@ function packageSource(version) {
|
|
|
14266
14266
|
function emptyCounts() {
|
|
14267
14267
|
return Object.fromEntries(dataKeys.map((key) => [key, 0]));
|
|
14268
14268
|
}
|
|
14269
|
-
function
|
|
14269
|
+
function parseJsonObject5(value) {
|
|
14270
14270
|
if (!value)
|
|
14271
14271
|
return {};
|
|
14272
14272
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -14312,34 +14312,34 @@ function rowToTask3(row) {
|
|
|
14312
14312
|
return {
|
|
14313
14313
|
...row,
|
|
14314
14314
|
tags: parseJsonArray2(row.tags),
|
|
14315
|
-
metadata:
|
|
14315
|
+
metadata: parseJsonObject5(row.metadata),
|
|
14316
14316
|
requires_approval: Boolean(row.requires_approval)
|
|
14317
14317
|
};
|
|
14318
14318
|
}
|
|
14319
14319
|
function rowToTaskList2(row) {
|
|
14320
|
-
return { ...row, metadata:
|
|
14320
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
14321
14321
|
}
|
|
14322
14322
|
function rowWithMetadata(row) {
|
|
14323
|
-
return { ...row, metadata:
|
|
14323
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
14324
14324
|
}
|
|
14325
14325
|
function rowToRunEvent(row) {
|
|
14326
|
-
return { ...row, data:
|
|
14326
|
+
return { ...row, data: parseJsonObject5(row.data) };
|
|
14327
14327
|
}
|
|
14328
14328
|
function rowToCommit2(row) {
|
|
14329
14329
|
return { ...row, files_changed: row.files_changed ? parseJsonArray2(row.files_changed) : null };
|
|
14330
14330
|
}
|
|
14331
14331
|
function rowToSavedView(row) {
|
|
14332
|
-
return { ...row, filters:
|
|
14332
|
+
return { ...row, filters: parseJsonObject5(row.filters) };
|
|
14333
14333
|
}
|
|
14334
14334
|
function rowToTaskBoard2(row) {
|
|
14335
14335
|
return {
|
|
14336
14336
|
...row,
|
|
14337
14337
|
lanes: parseJsonArray2(row.lanes),
|
|
14338
|
-
filters:
|
|
14338
|
+
filters: parseJsonObject5(row.filters)
|
|
14339
14339
|
};
|
|
14340
14340
|
}
|
|
14341
14341
|
function rowToCalendarItem2(row) {
|
|
14342
|
-
return { ...row, metadata:
|
|
14342
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
14343
14343
|
}
|
|
14344
14344
|
function bridgeStats(data) {
|
|
14345
14345
|
return Object.fromEntries(dataKeys.map((key) => [key, data[key].length]));
|
|
@@ -14350,7 +14350,7 @@ function createLocalBridgeBundle(options = {}, db) {
|
|
|
14350
14350
|
const runRows = queryByTaskIds(d, "SELECT * FROM task_runs WHERE task_id IN (__TASK_IDS__) ORDER BY started_at, created_at", taskIds);
|
|
14351
14351
|
const runIds = runRows.map((row) => String(row.id));
|
|
14352
14352
|
const project = options.project_id ? d.query("SELECT * FROM projects WHERE id = ?").get(options.project_id) : null;
|
|
14353
|
-
const data =
|
|
14353
|
+
const data = redactValue2({
|
|
14354
14354
|
projects: options.project_id ? project ? [project] : [] : d.query("SELECT * FROM projects ORDER BY name").all(),
|
|
14355
14355
|
task_lists: (options.project_id ? d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM task_lists ORDER BY name").all()).map(rowToTaskList2),
|
|
14356
14356
|
plans: options.project_id ? d.query("SELECT * FROM plans WHERE project_id = ? ORDER BY created_at").all(options.project_id) : d.query("SELECT * FROM plans ORDER BY created_at").all(),
|
|
@@ -14379,7 +14379,7 @@ function createLocalBridgeBundle(options = {}, db) {
|
|
|
14379
14379
|
return {
|
|
14380
14380
|
schemaVersion: TODOS_LOCAL_BRIDGE_SCHEMA_VERSION,
|
|
14381
14381
|
kind: TODOS_LOCAL_BRIDGE_KIND,
|
|
14382
|
-
exportedAt: options.generatedAt ??
|
|
14382
|
+
exportedAt: options.generatedAt ?? now2(),
|
|
14383
14383
|
package: packageSource(options.version ?? getPackageVersion(import.meta.url)),
|
|
14384
14384
|
source: {
|
|
14385
14385
|
project_id: options.project_id ?? null,
|
|
@@ -14565,7 +14565,7 @@ function safeMergeTask(db, incoming, options) {
|
|
|
14565
14565
|
prefer: "local",
|
|
14566
14566
|
local_updated_at: current.updated_at,
|
|
14567
14567
|
remote_updated_at: incoming.updated_at,
|
|
14568
|
-
detected_at:
|
|
14568
|
+
detected_at: now2(),
|
|
14569
14569
|
notes: buildDivergenceNotes(unresolvedFields)
|
|
14570
14570
|
}, 10);
|
|
14571
14571
|
}
|
|
@@ -14798,8 +14798,8 @@ __export(exports_local_encryption, {
|
|
|
14798
14798
|
DEFAULT_ENCRYPTION_PROFILE: () => DEFAULT_ENCRYPTION_PROFILE,
|
|
14799
14799
|
DEFAULT_ENCRYPTION_KEY_ENV: () => DEFAULT_ENCRYPTION_KEY_ENV
|
|
14800
14800
|
});
|
|
14801
|
-
import { createCipheriv, createDecipheriv, createHash as createHash4, randomBytes, scryptSync, timingSafeEqual } from "crypto";
|
|
14802
|
-
function
|
|
14801
|
+
import { createCipheriv, createDecipheriv, createHash as createHash4, randomBytes, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
14802
|
+
function now3() {
|
|
14803
14803
|
return new Date().toISOString();
|
|
14804
14804
|
}
|
|
14805
14805
|
function sha2562(value) {
|
|
@@ -14836,7 +14836,7 @@ function upsertEncryptionProfile(input) {
|
|
|
14836
14836
|
const name = normalizeProfileName(input.name);
|
|
14837
14837
|
const config = loadConfig();
|
|
14838
14838
|
const existing = config.encryption_profiles?.[name];
|
|
14839
|
-
const timestamp =
|
|
14839
|
+
const timestamp = now3();
|
|
14840
14840
|
const profile = {
|
|
14841
14841
|
name,
|
|
14842
14842
|
algorithm: "aes-256-gcm",
|
|
@@ -14871,7 +14871,7 @@ function removeEncryptionProfile(name) {
|
|
|
14871
14871
|
function encryptionProfileStatus(name = DEFAULT_ENCRYPTION_PROFILE, env = process.env) {
|
|
14872
14872
|
const profile = ensureEncryptionProfile(name);
|
|
14873
14873
|
return {
|
|
14874
|
-
profile:
|
|
14874
|
+
profile: redactValue2(profile),
|
|
14875
14875
|
locked: !env[profile.key_env],
|
|
14876
14876
|
key_env: profile.key_env,
|
|
14877
14877
|
key_present: Boolean(env[profile.key_env])
|
|
@@ -14893,7 +14893,7 @@ function encryptString(plaintext, options = {}) {
|
|
|
14893
14893
|
return {
|
|
14894
14894
|
schemaVersion: TODOS_ENCRYPTION_SCHEMA_VERSION,
|
|
14895
14895
|
kind: TODOS_ENCRYPTED_VALUE_KIND,
|
|
14896
|
-
encryptedAt: options.encryptedAt ??
|
|
14896
|
+
encryptedAt: options.encryptedAt ?? now3(),
|
|
14897
14897
|
profile: profile.name,
|
|
14898
14898
|
key_env: profile.key_env,
|
|
14899
14899
|
algorithm: "aes-256-gcm",
|
|
@@ -14928,7 +14928,7 @@ function decryptString(envelope, env = process.env) {
|
|
|
14928
14928
|
]).toString("utf8");
|
|
14929
14929
|
const expected = Buffer.from(envelope.plaintext_sha256, "hex");
|
|
14930
14930
|
const actual = Buffer.from(sha2562(plaintext), "hex");
|
|
14931
|
-
if (expected.length !== actual.length || !
|
|
14931
|
+
if (expected.length !== actual.length || !timingSafeEqual2(expected, actual)) {
|
|
14932
14932
|
throw new EncryptedPayloadError("decrypted payload checksum mismatch");
|
|
14933
14933
|
}
|
|
14934
14934
|
return plaintext;
|
|
@@ -15028,7 +15028,7 @@ function applyExportProfile(data, options = {}) {
|
|
|
15028
15028
|
if (profile === "encrypted") {
|
|
15029
15029
|
return { profile, data: encryptSensitiveFields(data, { profile: DEFAULT_ENCRYPTION_PROFILE, env: options.env }), warnings };
|
|
15030
15030
|
}
|
|
15031
|
-
return { profile, data:
|
|
15031
|
+
return { profile, data: redactValue2(data), warnings };
|
|
15032
15032
|
}
|
|
15033
15033
|
var TODOS_ENCRYPTED_VALUE_KIND = "hasna.todos.encrypted-value", TODOS_ENCRYPTED_BRIDGE_KIND = "hasna.todos.encrypted-bridge", TODOS_ENCRYPTION_SCHEMA_VERSION = 1, DEFAULT_ENCRYPTION_PROFILE = "default", DEFAULT_ENCRYPTION_KEY_ENV = "TODOS_ENCRYPTION_KEY", EncryptionKeyUnavailableError, EncryptedPayloadError;
|
|
15034
15034
|
var init_local_encryption = __esm(() => {
|
|
@@ -15279,7 +15279,7 @@ function importTodosMarkdown(markdown, options = {}, db) {
|
|
|
15279
15279
|
assigned_to: item.assigned_to,
|
|
15280
15280
|
project_id: project?.id,
|
|
15281
15281
|
plan_id: item.planName ? planByName.get(item.planName) : undefined,
|
|
15282
|
-
metadata: { imported_from: TODOS_MARKDOWN_SCHEMA, imported_at:
|
|
15282
|
+
metadata: { imported_from: TODOS_MARKDOWN_SCHEMA, imported_at: now2() }
|
|
15283
15283
|
}, d);
|
|
15284
15284
|
taskByTitle.set(item.title, task.id);
|
|
15285
15285
|
inserted.tasks++;
|
|
@@ -16166,7 +16166,7 @@ function normalizeGeneratedAgentNames(db) {
|
|
|
16166
16166
|
}
|
|
16167
16167
|
existing.delete(oldName);
|
|
16168
16168
|
existing.add(replacement);
|
|
16169
|
-
db.run("UPDATE agents SET name = ?, last_seen_at = ? WHERE id = ?", [replacement,
|
|
16169
|
+
db.run("UPDATE agents SET name = ?, last_seen_at = ? WHERE id = ?", [replacement, now2(), agent.id]);
|
|
16170
16170
|
const referenceUpdates = updateReferences(db, oldName, replacement);
|
|
16171
16171
|
renamed.push({
|
|
16172
16172
|
id: agent.id,
|
|
@@ -16475,7 +16475,7 @@ function registerAgent(input, db) {
|
|
|
16475
16475
|
}
|
|
16476
16476
|
}
|
|
16477
16477
|
const updates = ["last_seen_at = ?", "status = 'active'"];
|
|
16478
|
-
const params = [
|
|
16478
|
+
const params = [now2()];
|
|
16479
16479
|
if (input.session_id && !sameSession) {
|
|
16480
16480
|
updates.push("session_id = ?");
|
|
16481
16481
|
params.push(input.session_id);
|
|
@@ -16497,7 +16497,7 @@ function registerAgent(input, db) {
|
|
|
16497
16497
|
return getAgent(existing.id, d);
|
|
16498
16498
|
}
|
|
16499
16499
|
const id = shortUuid();
|
|
16500
|
-
const timestamp =
|
|
16500
|
+
const timestamp = now2();
|
|
16501
16501
|
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)
|
|
16502
16502
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
16503
16503
|
id,
|
|
@@ -16576,7 +16576,7 @@ function listAgents(opts, db) {
|
|
|
16576
16576
|
}
|
|
16577
16577
|
function updateAgentActivity(id, db) {
|
|
16578
16578
|
const d = db || getDatabase();
|
|
16579
|
-
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [
|
|
16579
|
+
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now2(), id]);
|
|
16580
16580
|
}
|
|
16581
16581
|
function updateAgent(id, input, db) {
|
|
16582
16582
|
const d = db || getDatabase();
|
|
@@ -16584,7 +16584,7 @@ function updateAgent(id, input, db) {
|
|
|
16584
16584
|
if (!agent)
|
|
16585
16585
|
throw new Error(`Agent not found: ${id}`);
|
|
16586
16586
|
const sets = ["last_seen_at = ?"];
|
|
16587
|
-
const params = [
|
|
16587
|
+
const params = [now2()];
|
|
16588
16588
|
if (input.name !== undefined) {
|
|
16589
16589
|
const existingNames = d.query("SELECT name FROM agents WHERE id != ?").all(id).map((row) => row.name);
|
|
16590
16590
|
const newName = validateAgentName(input.name, existingNames);
|
|
@@ -16643,16 +16643,16 @@ function updateAgent(id, input, db) {
|
|
|
16643
16643
|
}
|
|
16644
16644
|
function deleteAgent(id, db) {
|
|
16645
16645
|
const d = db || getDatabase();
|
|
16646
|
-
return d.run("UPDATE agents SET status = 'archived', last_seen_at = ? WHERE id = ?", [
|
|
16646
|
+
return d.run("UPDATE agents SET status = 'archived', last_seen_at = ? WHERE id = ?", [now2(), id]).changes > 0;
|
|
16647
16647
|
}
|
|
16648
16648
|
function archiveAgent(id, db) {
|
|
16649
16649
|
const d = db || getDatabase();
|
|
16650
|
-
d.run("UPDATE agents SET status = 'archived', last_seen_at = ? WHERE id = ?", [
|
|
16650
|
+
d.run("UPDATE agents SET status = 'archived', last_seen_at = ? WHERE id = ?", [now2(), id]);
|
|
16651
16651
|
return getAgent(id, d);
|
|
16652
16652
|
}
|
|
16653
16653
|
function unarchiveAgent(id, db) {
|
|
16654
16654
|
const d = db || getDatabase();
|
|
16655
|
-
d.run("UPDATE agents SET status = 'active', last_seen_at = ? WHERE id = ?", [
|
|
16655
|
+
d.run("UPDATE agents SET status = 'active', last_seen_at = ? WHERE id = ?", [now2(), id]);
|
|
16656
16656
|
return getAgent(id, d);
|
|
16657
16657
|
}
|
|
16658
16658
|
function getDirectReports(agentId, db) {
|
|
@@ -17078,7 +17078,7 @@ __export(exports_retention_cleanup, {
|
|
|
17078
17078
|
applyRetentionCleanup: () => applyRetentionCleanup,
|
|
17079
17079
|
RETENTION_CLEANUP_CONFIRMATION: () => RETENTION_CLEANUP_CONFIRMATION
|
|
17080
17080
|
});
|
|
17081
|
-
import { existsSync as
|
|
17081
|
+
import { existsSync as existsSync12, unlinkSync } from "fs";
|
|
17082
17082
|
function normalizeScopes(scopes) {
|
|
17083
17083
|
if (!scopes || scopes.length === 0)
|
|
17084
17084
|
return [...ALL_SCOPES];
|
|
@@ -17281,7 +17281,7 @@ function applyRetentionCleanup(input, db) {
|
|
|
17281
17281
|
for (const artifact of report.candidates.artifact_files) {
|
|
17282
17282
|
try {
|
|
17283
17283
|
const path = artifactStorePath(artifact.relative_path);
|
|
17284
|
-
if (!
|
|
17284
|
+
if (!existsSync12(path)) {
|
|
17285
17285
|
report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
|
|
17286
17286
|
continue;
|
|
17287
17287
|
}
|
|
@@ -17948,8 +17948,8 @@ __export(exports_local_extensions, {
|
|
|
17948
17948
|
discoverLocalExtensions: () => discoverLocalExtensions
|
|
17949
17949
|
});
|
|
17950
17950
|
import { createHash as createHash5, createVerify } from "crypto";
|
|
17951
|
-
import { existsSync as
|
|
17952
|
-
import { basename as basename6, join as
|
|
17951
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
17952
|
+
import { basename as basename6, join as join11, resolve as resolve12 } from "path";
|
|
17953
17953
|
function isObject(value) {
|
|
17954
17954
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
17955
17955
|
}
|
|
@@ -18208,10 +18208,10 @@ function verifyExtensionSignature(input) {
|
|
|
18208
18208
|
}
|
|
18209
18209
|
function inspectExtensionSource(source2) {
|
|
18210
18210
|
const resolved = resolve12(source2);
|
|
18211
|
-
if (!
|
|
18211
|
+
if (!existsSync13(resolved))
|
|
18212
18212
|
throw new Error(`extension source not found: ${source2}`);
|
|
18213
18213
|
const stat = statSync5(resolved);
|
|
18214
|
-
const manifestPath = stat.isDirectory() ? [
|
|
18214
|
+
const manifestPath = stat.isDirectory() ? [join11(resolved, "todos.extension.json"), join11(resolved, "extension.json")].find(existsSync13) : resolved;
|
|
18215
18215
|
if (!manifestPath)
|
|
18216
18216
|
throw new Error(`extension directory ${source2} is missing todos.extension.json`);
|
|
18217
18217
|
const raw = readFileSync7(manifestPath);
|
|
@@ -18307,20 +18307,20 @@ function projectExtensionSources(projectPath) {
|
|
|
18307
18307
|
return [];
|
|
18308
18308
|
const root = resolve12(projectPath);
|
|
18309
18309
|
const candidates = [
|
|
18310
|
-
|
|
18311
|
-
|
|
18310
|
+
join11(root, "todos.extension.json"),
|
|
18311
|
+
join11(root, ".todos", "todos.extension.json")
|
|
18312
18312
|
];
|
|
18313
|
-
const extensionDir =
|
|
18314
|
-
if (
|
|
18313
|
+
const extensionDir = join11(root, ".todos", "extensions");
|
|
18314
|
+
if (existsSync13(extensionDir)) {
|
|
18315
18315
|
for (const entry of readdirSync3(extensionDir)) {
|
|
18316
18316
|
if (entry.startsWith("."))
|
|
18317
18317
|
continue;
|
|
18318
|
-
const full =
|
|
18318
|
+
const full = join11(extensionDir, entry);
|
|
18319
18319
|
if (statSync5(full).isDirectory() || entry.endsWith(".json"))
|
|
18320
18320
|
candidates.push(full);
|
|
18321
18321
|
}
|
|
18322
18322
|
}
|
|
18323
|
-
return candidates.filter(
|
|
18323
|
+
return candidates.filter(existsSync13);
|
|
18324
18324
|
}
|
|
18325
18325
|
function discoverLocalExtensions(options = {}) {
|
|
18326
18326
|
const config = loadConfig();
|
|
@@ -18374,7 +18374,7 @@ function installLocalExtension(input) {
|
|
|
18374
18374
|
version: inspected.manifest.version,
|
|
18375
18375
|
source: inspected.source,
|
|
18376
18376
|
source_type: inspected.source_type,
|
|
18377
|
-
manifest:
|
|
18377
|
+
manifest: redactValue2(inspected.manifest),
|
|
18378
18378
|
checksum: inspected.checksum,
|
|
18379
18379
|
signature_verified: signatureVerified,
|
|
18380
18380
|
trusted,
|
|
@@ -18856,7 +18856,7 @@ var init_policy_packs = __esm(() => {
|
|
|
18856
18856
|
// src/db/checkpoints.ts
|
|
18857
18857
|
function upsertCheckpoint(task_id, step, updates, db) {
|
|
18858
18858
|
const d = db || getDatabase();
|
|
18859
|
-
const timestamp =
|
|
18859
|
+
const timestamp = now2();
|
|
18860
18860
|
const existing = d.query("SELECT id FROM task_checkpoints WHERE task_id = ? AND step = ?").get(task_id, step);
|
|
18861
18861
|
const id = existing?.id ?? uuid();
|
|
18862
18862
|
const agentId = updates.agent_id ?? null;
|
|
@@ -19016,7 +19016,7 @@ function writeGate(input, status, decision, db) {
|
|
|
19016
19016
|
const step = stepForGate(input.gate);
|
|
19017
19017
|
const existing = getCheckpoint(input.task_id, step, d);
|
|
19018
19018
|
const existingData = existing?.data || {};
|
|
19019
|
-
const timestamp = decision?.decided_at ||
|
|
19019
|
+
const timestamp = decision?.decided_at || now2();
|
|
19020
19020
|
const existingRunId = typeof existingData["run_id"] === "string" ? existingData["run_id"] : undefined;
|
|
19021
19021
|
const runId = input.run_id ? resolveTaskRunId(input.run_id, d) : existingRunId;
|
|
19022
19022
|
const data = {
|
|
@@ -19077,7 +19077,7 @@ function approveApprovalGate(input, db) {
|
|
|
19077
19077
|
plan_id: existing.plan_id || undefined,
|
|
19078
19078
|
run_id: existing.run_id || undefined,
|
|
19079
19079
|
expires_at: existing.expires_at || undefined
|
|
19080
|
-
}, "approved", { decided_by: input.reviewer, decided_at:
|
|
19080
|
+
}, "approved", { decided_by: input.reviewer, decided_at: now2(), note: input.note }, d);
|
|
19081
19081
|
logApprovalEvent(input.task_id, "approved", gate, input.reviewer, d);
|
|
19082
19082
|
return gate;
|
|
19083
19083
|
}
|
|
@@ -19097,7 +19097,7 @@ function rejectApprovalGate(input, db) {
|
|
|
19097
19097
|
plan_id: existing.plan_id || undefined,
|
|
19098
19098
|
run_id: existing.run_id || undefined,
|
|
19099
19099
|
expires_at: existing.expires_at || undefined
|
|
19100
|
-
}, "rejected", { decided_by: input.reviewer, decided_at:
|
|
19100
|
+
}, "rejected", { decided_by: input.reviewer, decided_at: now2(), note: input.note, reason: input.reason || input.note }, d);
|
|
19101
19101
|
logApprovalEvent(input.task_id, "rejected", gate, input.reviewer, d);
|
|
19102
19102
|
return gate;
|
|
19103
19103
|
}
|
|
@@ -19117,7 +19117,7 @@ function expireApprovalGate(input, db) {
|
|
|
19117
19117
|
plan_id: existing.plan_id || undefined,
|
|
19118
19118
|
run_id: existing.run_id || undefined,
|
|
19119
19119
|
expires_at: existing.expires_at || undefined
|
|
19120
|
-
}, "expired", { decided_by: input.reviewer, decided_at:
|
|
19120
|
+
}, "expired", { decided_by: input.reviewer, decided_at: now2(), reason: input.reason || "expired" }, d);
|
|
19121
19121
|
logApprovalEvent(input.task_id, "expired", gate, input.reviewer, d);
|
|
19122
19122
|
return gate;
|
|
19123
19123
|
}
|
|
@@ -19302,7 +19302,7 @@ function removeTerminalNotificationRule(name) {
|
|
|
19302
19302
|
}
|
|
19303
19303
|
function evaluateTerminalWatchRules(input, rules = listTerminalNotificationRules()) {
|
|
19304
19304
|
const timestamp = input.timestamp || new Date().toISOString();
|
|
19305
|
-
const payload =
|
|
19305
|
+
const payload = redactValue2(input.payload || {});
|
|
19306
19306
|
return rules.map((rule) => {
|
|
19307
19307
|
const skipped = [];
|
|
19308
19308
|
const severity = eventSeverity(input.type);
|
|
@@ -19388,7 +19388,7 @@ var init_terminal_notifications = __esm(() => {
|
|
|
19388
19388
|
});
|
|
19389
19389
|
|
|
19390
19390
|
// src/db/api-keys.ts
|
|
19391
|
-
import { createHash as createHash6, randomBytes as randomBytes2, timingSafeEqual as
|
|
19391
|
+
import { createHash as createHash6, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
19392
19392
|
function rowToRecord(row) {
|
|
19393
19393
|
return {
|
|
19394
19394
|
id: row.id,
|
|
@@ -19407,7 +19407,7 @@ function hashApiKey(key) {
|
|
|
19407
19407
|
function safeEqualHex(a, b) {
|
|
19408
19408
|
if (a.length !== b.length)
|
|
19409
19409
|
return false;
|
|
19410
|
-
return
|
|
19410
|
+
return timingSafeEqual3(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
19411
19411
|
}
|
|
19412
19412
|
function generatePlaintextKey() {
|
|
19413
19413
|
return `tdos_${randomBytes2(32).toString("base64url")}`;
|
|
@@ -19418,7 +19418,7 @@ function createApiKey(input, db) {
|
|
|
19418
19418
|
if (!name)
|
|
19419
19419
|
throw new Error("API key name is required");
|
|
19420
19420
|
const key = generatePlaintextKey();
|
|
19421
|
-
const timestamp =
|
|
19421
|
+
const timestamp = now2();
|
|
19422
19422
|
const id = uuid();
|
|
19423
19423
|
const prefix = key.slice(0, 12);
|
|
19424
19424
|
d.run(`INSERT INTO api_keys (id, name, key_hash, prefix, permissions, created_at, expires_at)
|
|
@@ -19442,18 +19442,18 @@ function listApiKeys(opts, db) {
|
|
|
19442
19442
|
}
|
|
19443
19443
|
function hasActiveApiKeys(db) {
|
|
19444
19444
|
const d = db || getDatabase();
|
|
19445
|
-
const row = d.query("SELECT COUNT(*) AS count FROM api_keys WHERE revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)").get(
|
|
19445
|
+
const row = d.query("SELECT COUNT(*) AS count FROM api_keys WHERE revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)").get(now2());
|
|
19446
19446
|
return (row?.count ?? 0) > 0;
|
|
19447
19447
|
}
|
|
19448
19448
|
function verifyApiKey(key, db) {
|
|
19449
19449
|
const d = db || getDatabase();
|
|
19450
19450
|
const candidateHash = hashApiKey(key);
|
|
19451
|
-
const rows = d.query("SELECT * FROM api_keys WHERE revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)").all(
|
|
19451
|
+
const rows = d.query("SELECT * FROM api_keys WHERE revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)").all(now2());
|
|
19452
19452
|
for (const row of rows) {
|
|
19453
19453
|
if (!safeEqualHex(candidateHash, row.key_hash))
|
|
19454
19454
|
continue;
|
|
19455
|
-
d.run("UPDATE api_keys SET last_used_at = ? WHERE id = ?", [
|
|
19456
|
-
return rowToRecord({ ...row, last_used_at:
|
|
19455
|
+
d.run("UPDATE api_keys SET last_used_at = ? WHERE id = ?", [now2(), row.id]);
|
|
19456
|
+
return rowToRecord({ ...row, last_used_at: now2() });
|
|
19457
19457
|
}
|
|
19458
19458
|
return null;
|
|
19459
19459
|
}
|
|
@@ -19463,7 +19463,7 @@ function revokeApiKey(idOrPrefix, db) {
|
|
|
19463
19463
|
const row = d.query("SELECT * FROM api_keys WHERE id = ? OR prefix = ?").get(identifier, identifier);
|
|
19464
19464
|
if (!row)
|
|
19465
19465
|
return null;
|
|
19466
|
-
d.run("UPDATE api_keys SET revoked_at = ? WHERE id = ?", [
|
|
19466
|
+
d.run("UPDATE api_keys SET revoked_at = ? WHERE id = ?", [now2(), row.id]);
|
|
19467
19467
|
const updated = d.query("SELECT * FROM api_keys WHERE id = ?").get(row.id);
|
|
19468
19468
|
return rowToRecord(updated);
|
|
19469
19469
|
}
|
|
@@ -19478,7 +19478,7 @@ function rowToOrg(row) {
|
|
|
19478
19478
|
function createOrg(input, db) {
|
|
19479
19479
|
const d = db || getDatabase();
|
|
19480
19480
|
const id = uuid();
|
|
19481
|
-
const timestamp =
|
|
19481
|
+
const timestamp = now2();
|
|
19482
19482
|
d.run(`INSERT INTO orgs (id, name, description, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
19483
19483
|
return getOrg(id, d);
|
|
19484
19484
|
}
|
|
@@ -19497,7 +19497,7 @@ function updateOrg(id, input, db) {
|
|
|
19497
19497
|
if (!org)
|
|
19498
19498
|
throw new Error(`Org not found: ${id}`);
|
|
19499
19499
|
const sets = ["updated_at = ?"];
|
|
19500
|
-
const params = [
|
|
19500
|
+
const params = [now2()];
|
|
19501
19501
|
if (input.name !== undefined) {
|
|
19502
19502
|
sets.push("name = ?");
|
|
19503
19503
|
params.push(input.name);
|
|
@@ -19613,8 +19613,8 @@ var exports_doctor = {};
|
|
|
19613
19613
|
__export(exports_doctor, {
|
|
19614
19614
|
runTodosDoctor: () => runTodosDoctor
|
|
19615
19615
|
});
|
|
19616
|
-
import { chmodSync, copyFileSync, existsSync as
|
|
19617
|
-
import { basename as basename7, dirname as dirname7, join as
|
|
19616
|
+
import { chmodSync, copyFileSync, existsSync as existsSync14, mkdirSync as mkdirSync6, statSync as statSync6 } from "fs";
|
|
19617
|
+
import { basename as basename7, dirname as dirname7, join as join12 } from "path";
|
|
19618
19618
|
function tableExists(db, table) {
|
|
19619
19619
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
19620
19620
|
}
|
|
@@ -19708,7 +19708,7 @@ function findMissingProjectRoots(db) {
|
|
|
19708
19708
|
continue;
|
|
19709
19709
|
if (!row.path.startsWith("/"))
|
|
19710
19710
|
continue;
|
|
19711
|
-
if (!
|
|
19711
|
+
if (!existsSync14(row.path))
|
|
19712
19712
|
missing++;
|
|
19713
19713
|
}
|
|
19714
19714
|
return missing;
|
|
@@ -19768,16 +19768,16 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
19768
19768
|
function createBackup(dbPath) {
|
|
19769
19769
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
19770
19770
|
return;
|
|
19771
|
-
if (!
|
|
19771
|
+
if (!existsSync14(dbPath))
|
|
19772
19772
|
return;
|
|
19773
|
-
const stamp =
|
|
19774
|
-
const backupDir =
|
|
19773
|
+
const stamp = now2().replace(/[:.]/g, "-");
|
|
19774
|
+
const backupDir = join12(dirname7(dbPath), `${basename7(dbPath)}.backup-${stamp}`);
|
|
19775
19775
|
const files = [];
|
|
19776
19776
|
mkdirSync6(backupDir, { recursive: true });
|
|
19777
19777
|
for (const source2 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
19778
|
-
if (!
|
|
19778
|
+
if (!existsSync14(source2))
|
|
19779
19779
|
continue;
|
|
19780
|
-
const target =
|
|
19780
|
+
const target = join12(backupDir, basename7(source2));
|
|
19781
19781
|
copyFileSync(source2, target);
|
|
19782
19782
|
files.push(target);
|
|
19783
19783
|
}
|
|
@@ -20003,7 +20003,7 @@ var init_doctor = __esm(() => {
|
|
|
20003
20003
|
});
|
|
20004
20004
|
|
|
20005
20005
|
// src/server/routes.ts
|
|
20006
|
-
import { join as
|
|
20006
|
+
import { join as join13, resolve as resolve14, sep } from "path";
|
|
20007
20007
|
function parseFieldsParam(url) {
|
|
20008
20008
|
const fieldsParam = url.searchParams.get("fields");
|
|
20009
20009
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -20707,7 +20707,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
20707
20707
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
20708
20708
|
return null;
|
|
20709
20709
|
if (path !== "/") {
|
|
20710
|
-
const filePath =
|
|
20710
|
+
const filePath = join13(ctx.dashboardDir, path);
|
|
20711
20711
|
const resolvedFile = resolve14(filePath);
|
|
20712
20712
|
const resolvedBase = resolve14(ctx.dashboardDir);
|
|
20713
20713
|
if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {
|
|
@@ -20717,7 +20717,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
20717
20717
|
if (res2)
|
|
20718
20718
|
return res2;
|
|
20719
20719
|
}
|
|
20720
|
-
const indexPath =
|
|
20720
|
+
const indexPath = join13(ctx.dashboardDir, "index.html");
|
|
20721
20721
|
const res = serveStaticFile2(indexPath);
|
|
20722
20722
|
if (res)
|
|
20723
20723
|
return res;
|
|
@@ -24745,7 +24745,7 @@ function createDispatch(input, db) {
|
|
|
24745
24745
|
input.message ?? null,
|
|
24746
24746
|
input.delay_ms ?? null,
|
|
24747
24747
|
input.scheduled_at ?? null,
|
|
24748
|
-
|
|
24748
|
+
now2()
|
|
24749
24749
|
]);
|
|
24750
24750
|
const row = _db2.query("SELECT * FROM dispatches WHERE id = ?").get(id);
|
|
24751
24751
|
return rowToDispatch(row);
|
|
@@ -24788,7 +24788,7 @@ function updateDispatchStatus(id, status, opts = {}, db) {
|
|
|
24788
24788
|
function createDispatchLog(log, db) {
|
|
24789
24789
|
const _db2 = db ?? getDatabase();
|
|
24790
24790
|
const id = uuid();
|
|
24791
|
-
const created_at =
|
|
24791
|
+
const created_at = now2();
|
|
24792
24792
|
_db2.run(`INSERT INTO dispatch_logs (id, dispatch_id, target_window, message, delay_ms, status, error, created_at)
|
|
24793
24793
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, log.dispatch_id, log.target_window, log.message, log.delay_ms, log.status, log.error ?? null, created_at]);
|
|
24794
24794
|
return { ...log, id, created_at };
|
|
@@ -24802,7 +24802,7 @@ function getDueDispatches(db) {
|
|
|
24802
24802
|
const rows = _db2.query(`SELECT * FROM dispatches
|
|
24803
24803
|
WHERE status = 'pending'
|
|
24804
24804
|
AND (scheduled_at IS NULL OR scheduled_at <= ?)
|
|
24805
|
-
ORDER BY created_at ASC`).all(
|
|
24805
|
+
ORDER BY created_at ASC`).all(now2());
|
|
24806
24806
|
return rows.map(rowToDispatch);
|
|
24807
24807
|
}
|
|
24808
24808
|
var init_dispatches = __esm(() => {
|
|
@@ -24926,7 +24926,7 @@ async function executeDispatch(dispatch, opts = {}, db) {
|
|
|
24926
24926
|
error: null
|
|
24927
24927
|
}, _db2);
|
|
24928
24928
|
if (!opts.dryRun) {
|
|
24929
|
-
updateDispatchStatus(dispatch.id, "sent", { sent_at:
|
|
24929
|
+
updateDispatchStatus(dispatch.id, "sent", { sent_at: now2() }, _db2);
|
|
24930
24930
|
}
|
|
24931
24931
|
} catch (err) {
|
|
24932
24932
|
const error = err instanceof Error ? err.message : String(err);
|
|
@@ -25394,7 +25394,7 @@ function normalizeName3(name) {
|
|
|
25394
25394
|
function createLabel(input, db) {
|
|
25395
25395
|
const d = db || getDatabase();
|
|
25396
25396
|
const id = uuid();
|
|
25397
|
-
const ts =
|
|
25397
|
+
const ts = now2();
|
|
25398
25398
|
d.run(`INSERT INTO labels (id, project_id, name, color, description, created_at, updated_at)
|
|
25399
25399
|
VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id ?? null, input.name.trim(), input.color ?? null, input.description ?? null, ts, ts]);
|
|
25400
25400
|
return rowToLabel(d.query("SELECT * FROM labels WHERE id = ?").get(id));
|
|
@@ -25419,7 +25419,7 @@ function updateLabel(idOrName, input, db) {
|
|
|
25419
25419
|
const existing = getLabel(idOrName, d);
|
|
25420
25420
|
if (!existing)
|
|
25421
25421
|
throw new Error(`Label not found: ${idOrName}`);
|
|
25422
|
-
const ts =
|
|
25422
|
+
const ts = now2();
|
|
25423
25423
|
d.run(`UPDATE labels SET
|
|
25424
25424
|
name = COALESCE(?, name),
|
|
25425
25425
|
color = COALESCE(?, color),
|
|
@@ -25480,7 +25480,7 @@ function getTagRow(idOrName, db) {
|
|
|
25480
25480
|
function updateTaskTagJson(oldName, newName, db) {
|
|
25481
25481
|
const rows = db.query("SELECT id, tags FROM tasks WHERE tags IS NOT NULL AND tags != '[]'").all();
|
|
25482
25482
|
const update = db.prepare("UPDATE tasks SET tags = ?, updated_at = ? WHERE id = ?");
|
|
25483
|
-
const ts =
|
|
25483
|
+
const ts = now2();
|
|
25484
25484
|
for (const row of rows) {
|
|
25485
25485
|
if (!row.tags)
|
|
25486
25486
|
continue;
|
|
@@ -25509,7 +25509,7 @@ function createTag(input, db) {
|
|
|
25509
25509
|
if (!name)
|
|
25510
25510
|
throw new Error("Tag name is required");
|
|
25511
25511
|
const id = uuid();
|
|
25512
|
-
const ts =
|
|
25512
|
+
const ts = now2();
|
|
25513
25513
|
d.run(`INSERT INTO tags (id, name, color, description, created_at, updated_at)
|
|
25514
25514
|
VALUES (?, ?, ?, ?, ?, ?)`, [id, name, input.color ?? null, input.description ?? null, ts, ts]);
|
|
25515
25515
|
return rowToTag(d.query("SELECT * FROM tags WHERE id = ?").get(id), d);
|
|
@@ -25555,7 +25555,7 @@ function updateTag(idOrName, input, db) {
|
|
|
25555
25555
|
renameTaskTagRows(stored.name, nextName, d);
|
|
25556
25556
|
updateTaskTagJson(stored.name, nextName, d);
|
|
25557
25557
|
}
|
|
25558
|
-
d.run(`UPDATE tags SET name = ?, color = COALESCE(?, color), description = COALESCE(?, description), updated_at = ? WHERE id = ?`, [nextName, input.color ?? null, input.description ?? null,
|
|
25558
|
+
d.run(`UPDATE tags SET name = ?, color = COALESCE(?, color), description = COALESCE(?, description), updated_at = ? WHERE id = ?`, [nextName, input.color ?? null, input.description ?? null, now2(), stored.id]);
|
|
25559
25559
|
return getTag(stored.id, d);
|
|
25560
25560
|
}
|
|
25561
25561
|
function deleteTag(idOrName, db) {
|
|
@@ -25577,8 +25577,8 @@ var exports_mention_resolver = {};
|
|
|
25577
25577
|
__export(exports_mention_resolver, {
|
|
25578
25578
|
resolveMentions: () => resolveMentions
|
|
25579
25579
|
});
|
|
25580
|
-
import { existsSync as
|
|
25581
|
-
import { basename as basename8, isAbsolute, join as
|
|
25580
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync7 } from "fs";
|
|
25581
|
+
import { basename as basename8, isAbsolute, join as join14, relative as relative5, resolve as resolve15, sep as sep2 } from "path";
|
|
25582
25582
|
function blankResolution(parsed) {
|
|
25583
25583
|
return {
|
|
25584
25584
|
input: parsed.input,
|
|
@@ -25676,7 +25676,7 @@ function resolveFile(parsed, workspace) {
|
|
|
25676
25676
|
return resolution;
|
|
25677
25677
|
}
|
|
25678
25678
|
resolution.path = relPath;
|
|
25679
|
-
if (!
|
|
25679
|
+
if (!existsSync15(absolutePath)) {
|
|
25680
25680
|
resolution.warnings.push("file does not exist in the local workspace");
|
|
25681
25681
|
return resolution;
|
|
25682
25682
|
}
|
|
@@ -25709,7 +25709,7 @@ function walkSourceFiles(root, current = root, files = []) {
|
|
|
25709
25709
|
if (SKIP_DIRS2.has(entry.name))
|
|
25710
25710
|
continue;
|
|
25711
25711
|
}
|
|
25712
|
-
const absolutePath =
|
|
25712
|
+
const absolutePath = join14(current, entry.name);
|
|
25713
25713
|
if (entry.isDirectory()) {
|
|
25714
25714
|
if (!SKIP_DIRS2.has(entry.name))
|
|
25715
25715
|
walkSourceFiles(root, absolutePath, files);
|
|
@@ -26052,7 +26052,7 @@ function alertId(kind, id, suffix = "") {
|
|
|
26052
26052
|
return `${kind}:${id}${suffix ? `:${suffix}` : ""}`;
|
|
26053
26053
|
}
|
|
26054
26054
|
function taskPayload(task, extra = {}) {
|
|
26055
|
-
return
|
|
26055
|
+
return redactValue2({
|
|
26056
26056
|
id: task.id,
|
|
26057
26057
|
task_id: task.id,
|
|
26058
26058
|
title: task.title,
|
|
@@ -26098,7 +26098,7 @@ function calendarAlert(event, triggeredAt, quieted) {
|
|
|
26098
26098
|
due_at: event.starts_at,
|
|
26099
26099
|
triggered_at: triggeredAt,
|
|
26100
26100
|
quieted,
|
|
26101
|
-
payload:
|
|
26101
|
+
payload: redactValue2({ ...event, severity: event.kind === "milestone" ? "warning" : "info" })
|
|
26102
26102
|
};
|
|
26103
26103
|
}
|
|
26104
26104
|
function runAlerts(input, db, checkedAt, quieted) {
|
|
@@ -26126,7 +26126,7 @@ function runAlerts(input, db, checkedAt, quieted) {
|
|
|
26126
26126
|
due_at: null,
|
|
26127
26127
|
triggered_at: run.completed_at || checkedAt,
|
|
26128
26128
|
quieted,
|
|
26129
|
-
payload:
|
|
26129
|
+
payload: redactValue2({ ...run, task_title: task?.title, project_id: task?.project_id, severity: failed ? "critical" : "info" })
|
|
26130
26130
|
};
|
|
26131
26131
|
}).filter((alert) => alert !== null);
|
|
26132
26132
|
}
|
|
@@ -26146,7 +26146,7 @@ function countAlerts(alerts) {
|
|
|
26146
26146
|
}
|
|
26147
26147
|
async function checkLocalNotifications(input = {}, db) {
|
|
26148
26148
|
const d = db || getDatabase();
|
|
26149
|
-
const checkedAt = input.now ||
|
|
26149
|
+
const checkedAt = input.now || now2();
|
|
26150
26150
|
const checkedMs = Date.parse(checkedAt);
|
|
26151
26151
|
const dueWithin = minutes(input.due_within_minutes, 60);
|
|
26152
26152
|
const staleMinutes = minutes(input.stale_minutes, 30);
|
|
@@ -26265,7 +26265,7 @@ function toEntry(row) {
|
|
|
26265
26265
|
created_at: row.created_at,
|
|
26266
26266
|
title: redactEvidenceText(row.title),
|
|
26267
26267
|
message: row.message ? redactEvidenceText(row.message) : null,
|
|
26268
|
-
metadata:
|
|
26268
|
+
metadata: redactValue2(parseMetadata2(row.metadata_json))
|
|
26269
26269
|
};
|
|
26270
26270
|
}
|
|
26271
26271
|
function sourceRank(source2) {
|
|
@@ -27022,7 +27022,7 @@ function rowToRelationship(row) {
|
|
|
27022
27022
|
function addTaskRelationship(input, db) {
|
|
27023
27023
|
const d = db || getDatabase();
|
|
27024
27024
|
const id = uuid();
|
|
27025
|
-
const timestamp =
|
|
27025
|
+
const timestamp = now2();
|
|
27026
27026
|
if (input.source_task_id === input.target_task_id) {
|
|
27027
27027
|
throw new Error("Cannot create a relationship between a task and itself");
|
|
27028
27028
|
}
|
|
@@ -27345,10 +27345,10 @@ function moveFiles(db, primaryId, duplicateId) {
|
|
|
27345
27345
|
if (existing) {
|
|
27346
27346
|
const note = [existing.note, row.note].filter(Boolean).join(`
|
|
27347
27347
|
`);
|
|
27348
|
-
db.run("UPDATE task_files SET status = ?, agent_id = COALESCE(?, agent_id), note = ?, updated_at = ? WHERE id = ?", [row.status, row.agent_id, note || null,
|
|
27348
|
+
db.run("UPDATE task_files SET status = ?, agent_id = COALESCE(?, agent_id), note = ?, updated_at = ? WHERE id = ?", [row.status, row.agent_id, note || null, now2(), existing.id]);
|
|
27349
27349
|
db.run("DELETE FROM task_files WHERE id = ?", [row.id]);
|
|
27350
27350
|
} else {
|
|
27351
|
-
db.run("UPDATE task_files SET task_id = ?, updated_at = ? WHERE id = ?", [primaryId,
|
|
27351
|
+
db.run("UPDATE task_files SET task_id = ?, updated_at = ? WHERE id = ?", [primaryId, now2(), row.id]);
|
|
27352
27352
|
}
|
|
27353
27353
|
moved++;
|
|
27354
27354
|
}
|
|
@@ -27381,7 +27381,7 @@ function moveGitRefs(db, primaryId, duplicateId) {
|
|
|
27381
27381
|
const exists = db.query("SELECT id FROM task_git_refs WHERE task_id = ? AND ref_type = ? AND name = ?").get(primaryId, row.ref_type, row.name);
|
|
27382
27382
|
if (exists)
|
|
27383
27383
|
continue;
|
|
27384
|
-
db.run("UPDATE task_git_refs SET task_id = ?, updated_at = ? WHERE id = ?", [primaryId,
|
|
27384
|
+
db.run("UPDATE task_git_refs SET task_id = ?, updated_at = ? WHERE id = ?", [primaryId, now2(), row.id]);
|
|
27385
27385
|
moved++;
|
|
27386
27386
|
}
|
|
27387
27387
|
return moved;
|
|
@@ -27391,7 +27391,7 @@ function moveChecklists(db, primaryId, duplicateId) {
|
|
|
27391
27391
|
const rows = db.query("SELECT id, position FROM task_checklists WHERE task_id = ? ORDER BY position, created_at").all(duplicateId);
|
|
27392
27392
|
let moved = 0;
|
|
27393
27393
|
for (const [index, row] of rows.entries()) {
|
|
27394
|
-
db.run("UPDATE task_checklists SET task_id = ?, position = ?, updated_at = ? WHERE id = ?", [primaryId, max.position + index + 1,
|
|
27394
|
+
db.run("UPDATE task_checklists SET task_id = ?, position = ?, updated_at = ? WHERE id = ?", [primaryId, max.position + index + 1, now2(), row.id]);
|
|
27395
27395
|
moved++;
|
|
27396
27396
|
}
|
|
27397
27397
|
return moved;
|
|
@@ -27441,7 +27441,7 @@ function mergeDuplicateTask(input, db) {
|
|
|
27441
27441
|
throw new Error(`Primary task not found: ${input.primary_task_id}`);
|
|
27442
27442
|
if (!duplicate)
|
|
27443
27443
|
throw new Error(`Duplicate task not found: ${input.duplicate_task_id}`);
|
|
27444
|
-
const mergedAt =
|
|
27444
|
+
const mergedAt = now2();
|
|
27445
27445
|
const moved = {
|
|
27446
27446
|
comments: 0,
|
|
27447
27447
|
dependencies: 0,
|
|
@@ -27668,7 +27668,7 @@ function createRoadmap(input) {
|
|
|
27668
27668
|
if (!name)
|
|
27669
27669
|
throw new Error("Roadmap name is required");
|
|
27670
27670
|
const store = readStore();
|
|
27671
|
-
const
|
|
27671
|
+
const now4 = timestamp();
|
|
27672
27672
|
const roadmap = {
|
|
27673
27673
|
id: newId("roadmap"),
|
|
27674
27674
|
name,
|
|
@@ -27679,8 +27679,8 @@ function createRoadmap(input) {
|
|
|
27679
27679
|
agent_id: cleanString(input.agent_id),
|
|
27680
27680
|
release: cleanString(input.release),
|
|
27681
27681
|
milestone_ids: [],
|
|
27682
|
-
created_at:
|
|
27683
|
-
updated_at:
|
|
27682
|
+
created_at: now4,
|
|
27683
|
+
updated_at: now4
|
|
27684
27684
|
};
|
|
27685
27685
|
store.roadmaps[roadmap.id] = roadmap;
|
|
27686
27686
|
writeStore(store);
|
|
@@ -27740,7 +27740,7 @@ function createMilestone(input) {
|
|
|
27740
27740
|
const title = input.title.trim();
|
|
27741
27741
|
if (!title)
|
|
27742
27742
|
throw new Error("Milestone title is required");
|
|
27743
|
-
const
|
|
27743
|
+
const now4 = timestamp();
|
|
27744
27744
|
const milestone = {
|
|
27745
27745
|
id: newId("milestone"),
|
|
27746
27746
|
roadmap_id: roadmapId,
|
|
@@ -27755,11 +27755,11 @@ function createMilestone(input) {
|
|
|
27755
27755
|
run_ids: cleanList(input.run_ids),
|
|
27756
27756
|
release: cleanString(input.release ?? roadmap.release ?? undefined),
|
|
27757
27757
|
tags: cleanList(input.tags),
|
|
27758
|
-
created_at:
|
|
27759
|
-
updated_at:
|
|
27758
|
+
created_at: now4,
|
|
27759
|
+
updated_at: now4
|
|
27760
27760
|
};
|
|
27761
27761
|
store.milestones[milestone.id] = milestone;
|
|
27762
|
-
store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList([...roadmap.milestone_ids, milestone.id]), updated_at:
|
|
27762
|
+
store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList([...roadmap.milestone_ids, milestone.id]), updated_at: now4 };
|
|
27763
27763
|
writeStore(store);
|
|
27764
27764
|
return milestone;
|
|
27765
27765
|
}
|
|
@@ -27819,7 +27819,7 @@ function upsertReleaseGroup(input) {
|
|
|
27819
27819
|
throw new Error("Release group name is required");
|
|
27820
27820
|
const key = releaseKey(roadmapId, name);
|
|
27821
27821
|
const existing = store.releases[key];
|
|
27822
|
-
const
|
|
27822
|
+
const now4 = timestamp();
|
|
27823
27823
|
const release = {
|
|
27824
27824
|
name,
|
|
27825
27825
|
version: input.version === undefined ? existing?.version ?? null : cleanString(input.version),
|
|
@@ -27830,8 +27830,8 @@ function upsertReleaseGroup(input) {
|
|
|
27830
27830
|
plan_ids: input.plan_ids === undefined ? existing?.plan_ids ?? [] : cleanList(input.plan_ids),
|
|
27831
27831
|
run_ids: input.run_ids === undefined ? existing?.run_ids ?? [] : cleanList(input.run_ids),
|
|
27832
27832
|
notes: input.notes === undefined ? existing?.notes ?? null : cleanString(input.notes),
|
|
27833
|
-
created_at: existing?.created_at ??
|
|
27834
|
-
updated_at:
|
|
27833
|
+
created_at: existing?.created_at ?? now4,
|
|
27834
|
+
updated_at: now4
|
|
27835
27835
|
};
|
|
27836
27836
|
store.releases[key] = release;
|
|
27837
27837
|
writeStore(store);
|
|
@@ -28039,7 +28039,7 @@ function upsertCapacityProfile(input) {
|
|
|
28039
28039
|
const store = readStore2();
|
|
28040
28040
|
const key = profileKey(agentId, projectId);
|
|
28041
28041
|
const existing = store.profiles[key];
|
|
28042
|
-
const
|
|
28042
|
+
const now4 = timestamp2();
|
|
28043
28043
|
const profile = {
|
|
28044
28044
|
id: existing?.id ?? key,
|
|
28045
28045
|
agent_id: agentId,
|
|
@@ -28047,8 +28047,8 @@ function upsertCapacityProfile(input) {
|
|
|
28047
28047
|
minutes_per_day: assertMinutes(input.minutes_per_day),
|
|
28048
28048
|
working_days: normalizeWorkingDays(input.working_days),
|
|
28049
28049
|
effective_from: cleanString2(input.effective_from),
|
|
28050
|
-
created_at: existing?.created_at ??
|
|
28051
|
-
updated_at:
|
|
28050
|
+
created_at: existing?.created_at ?? now4,
|
|
28051
|
+
updated_at: now4
|
|
28052
28052
|
};
|
|
28053
28053
|
store.profiles[key] = profile;
|
|
28054
28054
|
writeStore2(store);
|
|
@@ -28201,7 +28201,7 @@ function parsePayload(value) {
|
|
|
28201
28201
|
return {};
|
|
28202
28202
|
try {
|
|
28203
28203
|
const parsed = JSON.parse(value);
|
|
28204
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ?
|
|
28204
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? redactValue2(parsed) : {};
|
|
28205
28205
|
} catch {
|
|
28206
28206
|
return {};
|
|
28207
28207
|
}
|
|
@@ -28372,7 +28372,7 @@ function getLocalAuditLedger(input = {}, db) {
|
|
|
28372
28372
|
first_entry_hash: entries[0]?.chain_hash ?? null,
|
|
28373
28373
|
last_entry_hash: entries.at(-1)?.chain_hash ?? null,
|
|
28374
28374
|
source_counts: sourceCounts,
|
|
28375
|
-
generated_at:
|
|
28375
|
+
generated_at: now2(),
|
|
28376
28376
|
...input.include_entries === false ? {} : { entries }
|
|
28377
28377
|
};
|
|
28378
28378
|
}
|
|
@@ -28393,7 +28393,7 @@ function sealLocalAuditLedger(input, db) {
|
|
|
28393
28393
|
first_entry_hash: ledger.first_entry_hash,
|
|
28394
28394
|
last_entry_hash: ledger.last_entry_hash,
|
|
28395
28395
|
source_counts: ledger.source_counts,
|
|
28396
|
-
created_at:
|
|
28396
|
+
created_at: now2()
|
|
28397
28397
|
};
|
|
28398
28398
|
saveConfig({
|
|
28399
28399
|
...config,
|
|
@@ -28474,7 +28474,7 @@ __export(exports_release_compatibility, {
|
|
|
28474
28474
|
LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
|
|
28475
28475
|
});
|
|
28476
28476
|
import { readFileSync as readFileSync9 } from "fs";
|
|
28477
|
-
import { join as
|
|
28477
|
+
import { join as join15, resolve as resolve16 } from "path";
|
|
28478
28478
|
import { Database as Database2 } from "bun:sqlite";
|
|
28479
28479
|
function pass(id, message, details) {
|
|
28480
28480
|
return { id, status: "passed", message, details };
|
|
@@ -28486,7 +28486,7 @@ function warn(id, message, details) {
|
|
|
28486
28486
|
return { id, status: "warning", message, details };
|
|
28487
28487
|
}
|
|
28488
28488
|
function readPackageJson2(root) {
|
|
28489
|
-
return JSON.parse(readFileSync9(
|
|
28489
|
+
return JSON.parse(readFileSync9(join15(root, "package.json"), "utf8"));
|
|
28490
28490
|
}
|
|
28491
28491
|
function sortedKeys(value) {
|
|
28492
28492
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -30942,7 +30942,7 @@ __export(exports_handoffs, {
|
|
|
30942
30942
|
function createHandoff(input, db) {
|
|
30943
30943
|
const d = db || getDatabase();
|
|
30944
30944
|
const id = uuid();
|
|
30945
|
-
const timestamp3 =
|
|
30945
|
+
const timestamp3 = now2();
|
|
30946
30946
|
d.run(`INSERT INTO handoffs (id, agent_id, project_id, session_id, summary, completed, in_progress, blockers, next_steps, task_ids, relevant_files, run_ids, created_at)
|
|
30947
30947
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
30948
30948
|
id,
|
|
@@ -31081,7 +31081,7 @@ function importHandoffBundle(bundle, options = {}, db) {
|
|
|
31081
31081
|
]);
|
|
31082
31082
|
}
|
|
31083
31083
|
for (const agentId of handoff.acknowledged_by ?? []) {
|
|
31084
|
-
d.run("INSERT OR REPLACE INTO handoff_acknowledgements (handoff_id, agent_id, acknowledged_at) VALUES (?, ?, ?)", [handoff.id, agentId,
|
|
31084
|
+
d.run("INSERT OR REPLACE INTO handoff_acknowledgements (handoff_id, agent_id, acknowledged_at) VALUES (?, ?, ?)", [handoff.id, agentId, now2()]);
|
|
31085
31085
|
}
|
|
31086
31086
|
return {
|
|
31087
31087
|
applied: true,
|
|
@@ -31157,7 +31157,7 @@ function acknowledgeHandoff(id, agentId, db) {
|
|
|
31157
31157
|
const handoff = getHandoff(id, d);
|
|
31158
31158
|
if (!handoff)
|
|
31159
31159
|
throw new Error(`Handoff not found: ${id}`);
|
|
31160
|
-
d.run("INSERT OR REPLACE INTO handoff_acknowledgements (handoff_id, agent_id, acknowledged_at) VALUES (?, ?, ?)", [handoff.id, agentId,
|
|
31160
|
+
d.run("INSERT OR REPLACE INTO handoff_acknowledgements (handoff_id, agent_id, acknowledged_at) VALUES (?, ?, ?)", [handoff.id, agentId, now2()]);
|
|
31161
31161
|
return getHandoff(handoff.id, d);
|
|
31162
31162
|
}
|
|
31163
31163
|
function getLatestHandoff(agentId, projectId, db) {
|
|
@@ -31467,7 +31467,7 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
31467
31467
|
const globalRole = n.agent.role ? ` [${n.agent.role}]` : "";
|
|
31468
31468
|
const lead = n.is_lead ? " \u2605" : "";
|
|
31469
31469
|
const lastSeen = new Date(n.agent.last_seen_at).getTime();
|
|
31470
|
-
const active =
|
|
31470
|
+
const active = now4 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
|
|
31471
31471
|
const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${lead}`;
|
|
31472
31472
|
const children = n.reports.length > 0 ? `
|
|
31473
31473
|
` + render(n.reports, indent + 1) : "";
|
|
@@ -31480,7 +31480,7 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
31480
31480
|
if (format === "json") {
|
|
31481
31481
|
return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
|
|
31482
31482
|
}
|
|
31483
|
-
const
|
|
31483
|
+
const now4 = Date.now();
|
|
31484
31484
|
const ACTIVE_MS = 30 * 60 * 1000;
|
|
31485
31485
|
const text = tree.length > 0 ? render(tree) : "No agents in org chart.";
|
|
31486
31486
|
return { content: [{ type: "text", text }] };
|
|
@@ -31676,14 +31676,14 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
31676
31676
|
return { content: [{ type: "text", text: "No agent_id provided and no agent focus is active." }], isError: true };
|
|
31677
31677
|
}
|
|
31678
31678
|
const assigned = listTasks3({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
|
|
31679
|
-
const
|
|
31680
|
-
const dueSoonCutoff =
|
|
31679
|
+
const now4 = Date.now();
|
|
31680
|
+
const dueSoonCutoff = now4 + 24 * 60 * 60 * 1000;
|
|
31681
31681
|
const blocked = getBlockedTasks2().filter((t) => t.assigned_to === effectiveAgentId);
|
|
31682
31682
|
const workload = {
|
|
31683
31683
|
in_progress: assigned.filter((t) => t.status === "in_progress").length,
|
|
31684
31684
|
pending: assigned.filter((t) => t.status === "pending").length,
|
|
31685
|
-
completed_recent: assigned.filter((t) => t.status === "completed" && t.completed_at &&
|
|
31686
|
-
due_soon: assigned.filter((t) => t.due_at && new Date(t.due_at).getTime() <= dueSoonCutoff && new Date(t.due_at).getTime() >=
|
|
31685
|
+
completed_recent: assigned.filter((t) => t.status === "completed" && t.completed_at && now4 - new Date(t.completed_at).getTime() <= 7 * 24 * 60 * 60 * 1000).length,
|
|
31686
|
+
due_soon: assigned.filter((t) => t.due_at && new Date(t.due_at).getTime() <= dueSoonCutoff && new Date(t.due_at).getTime() >= now4 && !["completed", "cancelled", "failed"].includes(t.status)).length,
|
|
31687
31687
|
blocked: blocked.length
|
|
31688
31688
|
};
|
|
31689
31689
|
const lines = [
|
|
@@ -31910,7 +31910,7 @@ function limits(input) {
|
|
|
31910
31910
|
stale_after_hours: clamp(input.stale_after_hours, DEFAULT_LIMITS.stale_after_hours, 24 * 365)
|
|
31911
31911
|
};
|
|
31912
31912
|
}
|
|
31913
|
-
function
|
|
31913
|
+
function truncate2(value, max) {
|
|
31914
31914
|
if (!value)
|
|
31915
31915
|
return value ?? null;
|
|
31916
31916
|
const redacted = redactEvidenceText(value);
|
|
@@ -31931,9 +31931,9 @@ function acceptanceCriteria(task, maxText) {
|
|
|
31931
31931
|
const metadata = task.metadata || {};
|
|
31932
31932
|
const raw = metadata["acceptance_criteria"] ?? metadata["acceptanceCriteria"] ?? metadata["criteria"];
|
|
31933
31933
|
if (Array.isArray(raw))
|
|
31934
|
-
return raw.map((item) =>
|
|
31934
|
+
return raw.map((item) => truncate2(String(item), maxText)).filter((item) => Boolean(item));
|
|
31935
31935
|
if (typeof raw === "string") {
|
|
31936
|
-
return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) =>
|
|
31936
|
+
return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) => truncate2(line, maxText)).filter((item) => Boolean(item));
|
|
31937
31937
|
}
|
|
31938
31938
|
return [];
|
|
31939
31939
|
}
|
|
@@ -31956,7 +31956,7 @@ function addFile(files, path, source2, base) {
|
|
|
31956
31956
|
path,
|
|
31957
31957
|
status: base?.status || "active",
|
|
31958
31958
|
agent_id: base?.agent_id ?? null,
|
|
31959
|
-
note:
|
|
31959
|
+
note: truncate2(base?.note, 240),
|
|
31960
31960
|
updated_at: base?.updated_at || "",
|
|
31961
31961
|
sources: [source2]
|
|
31962
31962
|
});
|
|
@@ -32014,7 +32014,7 @@ function estimateTokens(value) {
|
|
|
32014
32014
|
return Math.max(1, Math.ceil((text || "").length / 4));
|
|
32015
32015
|
}
|
|
32016
32016
|
function summarizeStrings(values, maxChars) {
|
|
32017
|
-
return
|
|
32017
|
+
return truncate2(values.filter(Boolean).join("; "), maxChars) || "No local details were available before this section was omitted.";
|
|
32018
32018
|
}
|
|
32019
32019
|
function summarizeSection(pack, section, maxChars) {
|
|
32020
32020
|
if (section === "project")
|
|
@@ -32205,8 +32205,8 @@ function createAgentContextPack(input, db) {
|
|
|
32205
32205
|
...taskFiles.map((file) => file.updated_at)
|
|
32206
32206
|
], task.updated_at);
|
|
32207
32207
|
const warnings = [];
|
|
32208
|
-
const
|
|
32209
|
-
if (Date.parse(task.updated_at) <
|
|
32208
|
+
const now4 = input.now ? new Date(input.now) : new Date;
|
|
32209
|
+
if (Date.parse(task.updated_at) < now4.getTime() - limit.stale_after_hours * 60 * 60 * 1000) {
|
|
32210
32210
|
warnings.push(`task state is older than ${limit.stale_after_hours} hours`);
|
|
32211
32211
|
}
|
|
32212
32212
|
if (comments.length > recentComments.length)
|
|
@@ -32221,7 +32221,7 @@ function createAgentContextPack(input, db) {
|
|
|
32221
32221
|
id: task.id,
|
|
32222
32222
|
short_id: task.short_id,
|
|
32223
32223
|
title: redactEvidenceText(task.title),
|
|
32224
|
-
description:
|
|
32224
|
+
description: truncate2(task.description, limit.max_text_chars),
|
|
32225
32225
|
status: task.status,
|
|
32226
32226
|
priority: task.priority,
|
|
32227
32227
|
assigned_to: task.assigned_to,
|
|
@@ -32245,7 +32245,7 @@ function createAgentContextPack(input, db) {
|
|
|
32245
32245
|
plan: plan ? {
|
|
32246
32246
|
id: plan.id,
|
|
32247
32247
|
name: plan.name,
|
|
32248
|
-
description:
|
|
32248
|
+
description: truncate2(plan.description, limit.max_text_chars),
|
|
32249
32249
|
status: plan.status,
|
|
32250
32250
|
agent_id: plan.agent_id,
|
|
32251
32251
|
tasks: planTasks.slice(0, limit.plan_task_limit).map(taskSummary).filter((item) => Boolean(item)),
|
|
@@ -32264,7 +32264,7 @@ function createAgentContextPack(input, db) {
|
|
|
32264
32264
|
type: comment.type,
|
|
32265
32265
|
progress_pct: comment.progress_pct,
|
|
32266
32266
|
created_at: comment.created_at,
|
|
32267
|
-
content:
|
|
32267
|
+
content: truncate2(comment.content, limit.max_text_chars) || ""
|
|
32268
32268
|
})),
|
|
32269
32269
|
omitted: Math.max(0, comments.length - recentComments.length)
|
|
32270
32270
|
},
|
|
@@ -32272,7 +32272,7 @@ function createAgentContextPack(input, db) {
|
|
|
32272
32272
|
traceability: {
|
|
32273
32273
|
commits: traceability.commits.map((commit) => ({
|
|
32274
32274
|
sha: commit.sha,
|
|
32275
|
-
message:
|
|
32275
|
+
message: truncate2(commit.message, 240),
|
|
32276
32276
|
files_changed: commit.files_changed,
|
|
32277
32277
|
committed_at: commit.committed_at
|
|
32278
32278
|
})),
|
|
@@ -32280,7 +32280,7 @@ function createAgentContextPack(input, db) {
|
|
|
32280
32280
|
verifications: verifications.map((verification) => ({
|
|
32281
32281
|
command: verification.command,
|
|
32282
32282
|
status: verification.status,
|
|
32283
|
-
output_summary:
|
|
32283
|
+
output_summary: truncate2(verification.output_summary, limit.max_text_chars),
|
|
32284
32284
|
artifact_path: verification.artifact_path,
|
|
32285
32285
|
run_at: verification.run_at
|
|
32286
32286
|
})),
|
|
@@ -32291,14 +32291,14 @@ function createAgentContextPack(input, db) {
|
|
|
32291
32291
|
id: ledger.run.id,
|
|
32292
32292
|
title: ledger.run.title,
|
|
32293
32293
|
status: ledger.run.status,
|
|
32294
|
-
summary:
|
|
32294
|
+
summary: truncate2(ledger.run.summary, limit.max_text_chars),
|
|
32295
32295
|
agent_id: ledger.run.agent_id,
|
|
32296
32296
|
started_at: ledger.run.started_at,
|
|
32297
32297
|
completed_at: ledger.run.completed_at,
|
|
32298
|
-
events: ledger.events.map((event) => ({ event_type: event.event_type, message:
|
|
32299
|
-
commands: ledger.commands.map((command) => ({ command: command.command, status: command.status, output_summary:
|
|
32300
|
-
files: ledger.files.map((file) => ({ path: file.path, status: file.status, note:
|
|
32301
|
-
artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description:
|
|
32298
|
+
events: ledger.events.map((event) => ({ event_type: event.event_type, message: truncate2(event.message, 500), created_at: event.created_at })),
|
|
32299
|
+
commands: ledger.commands.map((command) => ({ command: command.command, status: command.status, output_summary: truncate2(command.output_summary, limit.max_text_chars), artifact_path: command.artifact_path })),
|
|
32300
|
+
files: ledger.files.map((file) => ({ path: file.path, status: file.status, note: truncate2(file.note, 240) })),
|
|
32301
|
+
artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description: truncate2(artifact.description, 240), sha256: artifact.sha256 }))
|
|
32302
32302
|
})),
|
|
32303
32303
|
omitted: Math.max(0, runs.length - selectedRuns.length)
|
|
32304
32304
|
},
|
|
@@ -32330,7 +32330,7 @@ function createAgentContextPack(input, db) {
|
|
|
32330
32330
|
}))
|
|
32331
32331
|
};
|
|
32332
32332
|
const { context_budget: _contextBudget, ...withoutContextBudget } = budgeted;
|
|
32333
|
-
const redacted =
|
|
32333
|
+
const redacted = redactValue2(withoutContextBudget);
|
|
32334
32334
|
redacted.context_budget = contextBudget;
|
|
32335
32335
|
return redacted;
|
|
32336
32336
|
}
|
|
@@ -32403,7 +32403,7 @@ function renderAgentContextPackCompactMarkdown(pack) {
|
|
|
32403
32403
|
const lines = [
|
|
32404
32404
|
`# Context: ${pack.task.title}`,
|
|
32405
32405
|
`${pack.task.status} | ${pack.task.priority} | ${pack.task.short_id || pack.task.id.slice(0, 8)}`,
|
|
32406
|
-
pack.task.description ?
|
|
32406
|
+
pack.task.description ? truncate2(pack.task.description, Math.min(pack.limits.summary_char_limit, 700)) : null,
|
|
32407
32407
|
"",
|
|
32408
32408
|
"## Must Know",
|
|
32409
32409
|
bullet([
|
|
@@ -32518,7 +32518,7 @@ function emptyContract(taskId) {
|
|
|
32518
32518
|
relevant_files: [],
|
|
32519
32519
|
risk_level: null,
|
|
32520
32520
|
done_definition: [],
|
|
32521
|
-
updated_at:
|
|
32521
|
+
updated_at: now2()
|
|
32522
32522
|
};
|
|
32523
32523
|
}
|
|
32524
32524
|
function emptyReview(taskId) {
|
|
@@ -32544,7 +32544,7 @@ function readContract(taskId, metadata) {
|
|
|
32544
32544
|
relevant_files: cleanList2(raw.relevant_files),
|
|
32545
32545
|
risk_level: cleanRisk(raw.risk_level),
|
|
32546
32546
|
done_definition: cleanList2(raw.done_definition),
|
|
32547
|
-
updated_at: typeof raw.updated_at === "string" ? raw.updated_at :
|
|
32547
|
+
updated_at: typeof raw.updated_at === "string" ? raw.updated_at : now2()
|
|
32548
32548
|
};
|
|
32549
32549
|
}
|
|
32550
32550
|
function readReview(taskId, metadata) {
|
|
@@ -32557,7 +32557,7 @@ function readReview(taskId, metadata) {
|
|
|
32557
32557
|
actor: typeof item.actor === "string" ? item.actor : "unknown",
|
|
32558
32558
|
notes: typeof item.notes === "string" ? item.notes : null,
|
|
32559
32559
|
changes_requested: cleanList2(item.changes_requested),
|
|
32560
|
-
at: typeof item.at === "string" ? item.at :
|
|
32560
|
+
at: typeof item.at === "string" ? item.at : now2()
|
|
32561
32561
|
};
|
|
32562
32562
|
}) : [];
|
|
32563
32563
|
return {
|
|
@@ -32587,7 +32587,7 @@ function setTaskContract(input, db) {
|
|
|
32587
32587
|
relevant_files: input.relevant_files === undefined ? current.relevant_files : cleanList2(input.relevant_files),
|
|
32588
32588
|
risk_level: input.risk_level === undefined ? current.risk_level : cleanRisk(input.risk_level),
|
|
32589
32589
|
done_definition: input.done_definition === undefined ? current.done_definition : cleanList2(input.done_definition),
|
|
32590
|
-
updated_at:
|
|
32590
|
+
updated_at: now2()
|
|
32591
32591
|
};
|
|
32592
32592
|
const metadata = {
|
|
32593
32593
|
...task.metadata,
|
|
@@ -32606,7 +32606,7 @@ function getTaskContract(taskId, db) {
|
|
|
32606
32606
|
}
|
|
32607
32607
|
function requestTaskReview(input, db) {
|
|
32608
32608
|
const task = taskOrThrow(input.task_id, db);
|
|
32609
|
-
const timestamp3 =
|
|
32609
|
+
const timestamp3 = now2();
|
|
32610
32610
|
const previous = readReview(task.id, task.metadata);
|
|
32611
32611
|
const entry = {
|
|
32612
32612
|
state: "requested",
|
|
@@ -32631,7 +32631,7 @@ function requestTaskReview(input, db) {
|
|
|
32631
32631
|
}
|
|
32632
32632
|
function recordTaskReview(input, db) {
|
|
32633
32633
|
const task = taskOrThrow(input.task_id, db);
|
|
32634
|
-
const timestamp3 =
|
|
32634
|
+
const timestamp3 = now2();
|
|
32635
32635
|
const previous = readReview(task.id, task.metadata);
|
|
32636
32636
|
const changes = cleanList2(input.changes_requested);
|
|
32637
32637
|
const entry = {
|
|
@@ -33492,11 +33492,11 @@ function updateDispatcherMetadata(runId, dispatcher, db) {
|
|
|
33492
33492
|
const run = getTaskRun(resolveTaskRunId(runId, d), d);
|
|
33493
33493
|
if (!run)
|
|
33494
33494
|
throw new Error(`Run not found: ${runId}`);
|
|
33495
|
-
const metadata =
|
|
33495
|
+
const metadata = redactValue2({
|
|
33496
33496
|
...run.metadata,
|
|
33497
33497
|
agent_run_dispatcher: dispatcher
|
|
33498
33498
|
});
|
|
33499
|
-
d.run("UPDATE task_runs SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata),
|
|
33499
|
+
d.run("UPDATE task_runs SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata), now2(), run.id]);
|
|
33500
33500
|
return getTaskRun(run.id, d);
|
|
33501
33501
|
}
|
|
33502
33502
|
function resolveAdapter(adapter) {
|
|
@@ -33735,7 +33735,7 @@ function claimNextAgentRun(agentId, options = {}, db) {
|
|
|
33735
33735
|
const dispatcher = { ...next.dispatcher, state: "running", started_at: new Date().toISOString() };
|
|
33736
33736
|
const updated = updateDispatcherMetadata(next.run.id, dispatcher, d);
|
|
33737
33737
|
if (agentId)
|
|
33738
|
-
d.run("UPDATE task_runs SET agent_id = ?, updated_at = ? WHERE id = ?", [agentId,
|
|
33738
|
+
d.run("UPDATE task_runs SET agent_id = ?, updated_at = ? WHERE id = ?", [agentId, now2(), next.run.id]);
|
|
33739
33739
|
const reread = getTaskRun(next.run.id, d) ?? updated;
|
|
33740
33740
|
return toAgentRun({ run: reread, dispatcher });
|
|
33741
33741
|
}
|
|
@@ -33805,7 +33805,7 @@ __export(exports_verification_providers, {
|
|
|
33805
33805
|
getVerificationRecord: () => getVerificationRecord,
|
|
33806
33806
|
discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
|
|
33807
33807
|
});
|
|
33808
|
-
import { existsSync as
|
|
33808
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10 } from "fs";
|
|
33809
33809
|
function normalizeName6(name) {
|
|
33810
33810
|
const normalized = name.trim().toLowerCase();
|
|
33811
33811
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
@@ -33838,7 +33838,7 @@ function upsertVerificationProvider(input) {
|
|
|
33838
33838
|
kind: input.kind,
|
|
33839
33839
|
command: input.command ?? existing?.command,
|
|
33840
33840
|
cwd: input.cwd ?? existing?.cwd,
|
|
33841
|
-
env: input.env ?
|
|
33841
|
+
env: input.env ? redactValue2(input.env) : existing?.env,
|
|
33842
33842
|
capabilities: input.capabilities ?? existing?.capabilities,
|
|
33843
33843
|
retry: input.retry ? retryConfig(input.retry) : existing?.retry,
|
|
33844
33844
|
timeout_ms: timeoutMs(input.timeout_ms ?? existing?.timeout_ms),
|
|
@@ -33957,7 +33957,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
33957
33957
|
};
|
|
33958
33958
|
}
|
|
33959
33959
|
function runCiLogProvider(input) {
|
|
33960
|
-
const text = input.log_text ?? (input.log_path &&
|
|
33960
|
+
const text = input.log_text ?? (input.log_path && existsSync16(input.log_path) ? readFileSync10(input.log_path, "utf-8") : "");
|
|
33961
33961
|
return {
|
|
33962
33962
|
status: classifyLog(text),
|
|
33963
33963
|
attempts: 1,
|
|
@@ -33969,7 +33969,7 @@ function runBrowserProvider(input) {
|
|
|
33969
33969
|
if (!input.artifact_path) {
|
|
33970
33970
|
return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
|
|
33971
33971
|
}
|
|
33972
|
-
if (!
|
|
33972
|
+
if (!existsSync16(input.artifact_path)) {
|
|
33973
33973
|
return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
|
|
33974
33974
|
}
|
|
33975
33975
|
return {
|
|
@@ -33997,9 +33997,9 @@ async function runVerificationProvider(input, db) {
|
|
|
33997
33997
|
exit_code: partial.exit_code,
|
|
33998
33998
|
output_summary: partial.output_summary ? redactEvidenceText(partial.output_summary) : null,
|
|
33999
33999
|
artifact_path: input.artifact_path || null,
|
|
34000
|
-
run_at:
|
|
34000
|
+
run_at: now2(),
|
|
34001
34001
|
task_id: input.task_id || null,
|
|
34002
|
-
metadata:
|
|
34002
|
+
metadata: redactValue2({
|
|
34003
34003
|
...input.metadata || {},
|
|
34004
34004
|
provider_kind: provider.kind,
|
|
34005
34005
|
command_template: command,
|
|
@@ -34246,7 +34246,7 @@ function generateReleaseNotes(input = {}, db) {
|
|
|
34246
34246
|
warnings.push("no completed tasks matched the release-note scope");
|
|
34247
34247
|
return {
|
|
34248
34248
|
schema_version: 1,
|
|
34249
|
-
generated_at: input.generated_at ||
|
|
34249
|
+
generated_at: input.generated_at || now2(),
|
|
34250
34250
|
title: input.title || "Release Notes",
|
|
34251
34251
|
version: input.version || null,
|
|
34252
34252
|
local_only: true,
|
|
@@ -34341,7 +34341,7 @@ var init_release_notes = __esm(() => {
|
|
|
34341
34341
|
function saveSnapshot(input, db) {
|
|
34342
34342
|
const d = db || getDatabase();
|
|
34343
34343
|
const id = uuid();
|
|
34344
|
-
const timestamp3 =
|
|
34344
|
+
const timestamp3 = now2();
|
|
34345
34345
|
d.run(`INSERT INTO context_snapshots (id, agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps, metadata, created_at)
|
|
34346
34346
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
34347
34347
|
id,
|
|
@@ -34426,7 +34426,7 @@ function rowToKnowledgeRecord(row) {
|
|
|
34426
34426
|
agent_id: row.agent_id,
|
|
34427
34427
|
snapshot_id: row.snapshot_id,
|
|
34428
34428
|
tags: parseArray2(row.tags),
|
|
34429
|
-
metadata:
|
|
34429
|
+
metadata: redactValue2(parseObject2(row.metadata)),
|
|
34430
34430
|
created_at: row.created_at,
|
|
34431
34431
|
updated_at: row.updated_at
|
|
34432
34432
|
};
|
|
@@ -34442,7 +34442,7 @@ function createKnowledgeRecord(input, db) {
|
|
|
34442
34442
|
if (!title)
|
|
34443
34443
|
throw new Error("Knowledge record title is required");
|
|
34444
34444
|
const d = db || getDatabase();
|
|
34445
|
-
const timestamp3 =
|
|
34445
|
+
const timestamp3 = now2();
|
|
34446
34446
|
const id = uuid();
|
|
34447
34447
|
d.run(`INSERT INTO project_knowledge_records (
|
|
34448
34448
|
id, record_type, title, content, decision, rationale, alternatives,
|
|
@@ -34578,7 +34578,7 @@ function createKnowledgeExportReport(options, db) {
|
|
|
34578
34578
|
schema_version: 1,
|
|
34579
34579
|
local_only: true,
|
|
34580
34580
|
no_network: true,
|
|
34581
|
-
generated_at:
|
|
34581
|
+
generated_at: now2(),
|
|
34582
34582
|
filters: { ...filters, query: options.query || null },
|
|
34583
34583
|
count: records.length,
|
|
34584
34584
|
records
|
|
@@ -34694,7 +34694,7 @@ function rowToRisk(row) {
|
|
|
34694
34694
|
plan_id: row.plan_id,
|
|
34695
34695
|
task_id: row.task_id,
|
|
34696
34696
|
tags: parseArray3(row.tags),
|
|
34697
|
-
metadata:
|
|
34697
|
+
metadata: redactValue2(parseObject3(row.metadata)),
|
|
34698
34698
|
created_at: row.created_at,
|
|
34699
34699
|
updated_at: row.updated_at,
|
|
34700
34700
|
closed_at: row.closed_at
|
|
@@ -34716,7 +34716,7 @@ function createRisk(input, db) {
|
|
|
34716
34716
|
assertSeverity(severity);
|
|
34717
34717
|
assertProbability(probability);
|
|
34718
34718
|
const d = db || getDatabase();
|
|
34719
|
-
const timestamp3 =
|
|
34719
|
+
const timestamp3 = now2();
|
|
34720
34720
|
const id = uuid();
|
|
34721
34721
|
const closedAt = status === "resolved" || status === "accepted" ? timestamp3 : null;
|
|
34722
34722
|
d.run(`INSERT INTO project_risks (
|
|
@@ -34756,7 +34756,7 @@ function updateRisk(id, input, db) {
|
|
|
34756
34756
|
throw new Error(`Risk not found: ${id}`);
|
|
34757
34757
|
const resolved = current.id;
|
|
34758
34758
|
const sets = ["updated_at = ?"];
|
|
34759
|
-
const params = [
|
|
34759
|
+
const params = [now2()];
|
|
34760
34760
|
if (input.title !== undefined) {
|
|
34761
34761
|
const title = input.title.trim();
|
|
34762
34762
|
if (!title)
|
|
@@ -34773,7 +34773,7 @@ function updateRisk(id, input, db) {
|
|
|
34773
34773
|
sets.push("status = ?");
|
|
34774
34774
|
params.push(input.status);
|
|
34775
34775
|
sets.push("closed_at = ?");
|
|
34776
|
-
params.push(input.status === "resolved" || input.status === "accepted" ?
|
|
34776
|
+
params.push(input.status === "resolved" || input.status === "accepted" ? now2() : null);
|
|
34777
34777
|
}
|
|
34778
34778
|
if (input.severity !== undefined) {
|
|
34779
34779
|
assertSeverity(input.severity);
|
|
@@ -34911,7 +34911,7 @@ function calculateDependencyDepth(taskIds, db) {
|
|
|
34911
34911
|
}
|
|
34912
34912
|
function scoreHealth(scope, scopeId, db) {
|
|
34913
34913
|
const d = db || getDatabase();
|
|
34914
|
-
const generatedAt =
|
|
34914
|
+
const generatedAt = now2();
|
|
34915
34915
|
const scopeInfo = scopeCondition(scope, scopeId, d);
|
|
34916
34916
|
const tasks = d.query(`SELECT id, short_id, title, status, due_at FROM tasks WHERE ${scopeInfo.where}`).all(scopeInfo.id);
|
|
34917
34917
|
const taskIds = new Set(tasks.map((task) => task.id));
|
|
@@ -35008,7 +35008,7 @@ function createRiskRegisterExport(options = {}, db) {
|
|
|
35008
35008
|
schema_version: 1,
|
|
35009
35009
|
local_only: true,
|
|
35010
35010
|
no_network: true,
|
|
35011
|
-
generated_at:
|
|
35011
|
+
generated_at: now2(),
|
|
35012
35012
|
filters,
|
|
35013
35013
|
count: risks.length,
|
|
35014
35014
|
risks
|
|
@@ -35157,8 +35157,8 @@ function scoreTask(taskId, score, reviewerId, db) {
|
|
|
35157
35157
|
metadata._review_score = score;
|
|
35158
35158
|
if (reviewerId)
|
|
35159
35159
|
metadata._reviewed_by = reviewerId;
|
|
35160
|
-
metadata._reviewed_at =
|
|
35161
|
-
d.run("UPDATE tasks SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata),
|
|
35160
|
+
metadata._reviewed_at = now2();
|
|
35161
|
+
d.run("UPDATE tasks SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata), now2(), taskId]);
|
|
35162
35162
|
}
|
|
35163
35163
|
function clampScore(value) {
|
|
35164
35164
|
if (!Number.isFinite(value))
|
|
@@ -35238,7 +35238,7 @@ function getAgentReliabilityScorecard(agentId, options = {}, db) {
|
|
|
35238
35238
|
const agent = resolveAgent2(agentId, d);
|
|
35239
35239
|
if (!agent)
|
|
35240
35240
|
return null;
|
|
35241
|
-
const generatedAt =
|
|
35241
|
+
const generatedAt = now2();
|
|
35242
35242
|
const staleAfterHours = Number.isFinite(options.stale_after_hours) && options.stale_after_hours > 0 ? Math.min(Math.floor(options.stale_after_hours), 24 * 30) : 24;
|
|
35243
35243
|
const staleCutoff = new Date(Date.now() - staleAfterHours * 60 * 60 * 1000).toISOString();
|
|
35244
35244
|
const taskFilter = agentTaskWhere(agent, options, d);
|
|
@@ -35417,7 +35417,7 @@ function createAgentReliabilityExport(options = {}, db) {
|
|
|
35417
35417
|
schema_version: 1,
|
|
35418
35418
|
local_only: true,
|
|
35419
35419
|
no_network: true,
|
|
35420
|
-
generated_at:
|
|
35420
|
+
generated_at: now2(),
|
|
35421
35421
|
filters: {
|
|
35422
35422
|
agent_id: options.agent_id || null,
|
|
35423
35423
|
project_id: options.project_id || null,
|
|
@@ -35545,7 +35545,7 @@ function extractUsage(value) {
|
|
|
35545
35545
|
}
|
|
35546
35546
|
return own;
|
|
35547
35547
|
}
|
|
35548
|
-
function
|
|
35548
|
+
function parseJsonObject6(value) {
|
|
35549
35549
|
if (!value)
|
|
35550
35550
|
return {};
|
|
35551
35551
|
try {
|
|
@@ -35717,7 +35717,7 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
35717
35717
|
completedRunMs += millisBetween(run.started_at, run.completed_at);
|
|
35718
35718
|
else
|
|
35719
35719
|
openRunMs += millisBetween(run.started_at, generatedAt);
|
|
35720
|
-
const usage = extractUsage(
|
|
35720
|
+
const usage = extractUsage(parseJsonObject6(run.metadata));
|
|
35721
35721
|
metadataUsage.tokens += usage.tokens;
|
|
35722
35722
|
metadataUsage.cost_usd += usage.cost_usd;
|
|
35723
35723
|
metadataUsage.duration_ms += usage.duration_ms;
|
|
@@ -35729,7 +35729,7 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
35729
35729
|
JOIN tasks t ON t.id = e.task_id
|
|
35730
35730
|
${runClause}`, runParams);
|
|
35731
35731
|
for (const event of eventRows) {
|
|
35732
|
-
const usage = extractUsage(
|
|
35732
|
+
const usage = extractUsage(parseJsonObject6(event.data));
|
|
35733
35733
|
metadataUsage.tokens += usage.tokens;
|
|
35734
35734
|
metadataUsage.cost_usd += usage.cost_usd;
|
|
35735
35735
|
metadataUsage.duration_ms += usage.duration_ms;
|
|
@@ -35881,12 +35881,12 @@ function summarizeTask(task) {
|
|
|
35881
35881
|
};
|
|
35882
35882
|
}
|
|
35883
35883
|
function overdueTasks(tasks, nowIso) {
|
|
35884
|
-
const
|
|
35884
|
+
const now4 = Date.parse(nowIso);
|
|
35885
35885
|
return tasks.filter((task) => {
|
|
35886
35886
|
if (isTerminal(task) || !task.due_at)
|
|
35887
35887
|
return false;
|
|
35888
35888
|
const due = Date.parse(task.due_at);
|
|
35889
|
-
return Number.isFinite(due) && due <
|
|
35889
|
+
return Number.isFinite(due) && due < now4;
|
|
35890
35890
|
});
|
|
35891
35891
|
}
|
|
35892
35892
|
function isReady(task, db) {
|
|
@@ -36299,7 +36299,7 @@ function asRecord(value) {
|
|
|
36299
36299
|
}
|
|
36300
36300
|
function createLocalBackup(options = {}, db) {
|
|
36301
36301
|
const d = db || getDatabase();
|
|
36302
|
-
const createdAt = options.generated_at ??
|
|
36302
|
+
const createdAt = options.generated_at ?? now2();
|
|
36303
36303
|
const bridge = createLocalBridgeBundle({
|
|
36304
36304
|
project_id: options.project_id,
|
|
36305
36305
|
generatedAt: createdAt,
|
|
@@ -36364,7 +36364,7 @@ function readLocalBackupFile(path) {
|
|
|
36364
36364
|
return JSON.parse(readFileSync11(resolve17(path), "utf-8"));
|
|
36365
36365
|
}
|
|
36366
36366
|
function verifyLocalBackup(value, options = {}, db) {
|
|
36367
|
-
const verifiedAt = options.verified_at ??
|
|
36367
|
+
const verifiedAt = options.verified_at ?? now2();
|
|
36368
36368
|
const record = asRecord(value);
|
|
36369
36369
|
const issues = [];
|
|
36370
36370
|
const warnings = [];
|
|
@@ -36470,7 +36470,7 @@ function restoreLocalBackup(backup, options = {}, db) {
|
|
|
36470
36470
|
kind: "hasna.todos.local-backup-restore",
|
|
36471
36471
|
local_only: true,
|
|
36472
36472
|
no_network: true,
|
|
36473
|
-
restored_at: options.verified_at ??
|
|
36473
|
+
restored_at: options.verified_at ?? now2(),
|
|
36474
36474
|
dry_run: !options.apply,
|
|
36475
36475
|
ok: verification.ok && Boolean(importResult?.ok),
|
|
36476
36476
|
verification,
|
|
@@ -36528,7 +36528,7 @@ function checkLocalIntegrity(options = {}, db) {
|
|
|
36528
36528
|
kind: TODOS_LOCAL_INTEGRITY_KIND,
|
|
36529
36529
|
local_only: true,
|
|
36530
36530
|
no_network: true,
|
|
36531
|
-
generated_at: options.generated_at ??
|
|
36531
|
+
generated_at: options.generated_at ?? now2(),
|
|
36532
36532
|
database_path: getDatabasePath(),
|
|
36533
36533
|
sqlite,
|
|
36534
36534
|
bridge_validation: bridgeValidation,
|
|
@@ -36558,7 +36558,7 @@ __export(exports_onboarding_fixtures, {
|
|
|
36558
36558
|
TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION: () => TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION
|
|
36559
36559
|
});
|
|
36560
36560
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
36561
|
-
import { join as
|
|
36561
|
+
import { join as join16 } from "path";
|
|
36562
36562
|
function emptyData() {
|
|
36563
36563
|
return {
|
|
36564
36564
|
projects: [],
|
|
@@ -36891,7 +36891,7 @@ function writeOnboardingFixtureFiles(directory) {
|
|
|
36891
36891
|
mkdirSync8(directory, { recursive: true });
|
|
36892
36892
|
const files = [];
|
|
36893
36893
|
for (const fixture of allFixtures()) {
|
|
36894
|
-
const path =
|
|
36894
|
+
const path = join16(directory, `${fixture.summary.name}.bridge.json`);
|
|
36895
36895
|
writeFileSync6(path, `${JSON.stringify(fixture.bundle, null, 2)}
|
|
36896
36896
|
`, "utf-8");
|
|
36897
36897
|
files.push(path);
|
|
@@ -37121,7 +37121,7 @@ function getLocalSnapshot(options, db) {
|
|
|
37121
37121
|
if (!ALL_TYPES.includes(options.type)) {
|
|
37122
37122
|
throw new Error(`Unknown local snapshot type: ${options.type}`);
|
|
37123
37123
|
}
|
|
37124
|
-
const generatedAt = options.generatedAt ??
|
|
37124
|
+
const generatedAt = options.generatedAt ?? now2();
|
|
37125
37125
|
const limit = normalizeLimit2(options.limit);
|
|
37126
37126
|
const items = snapshotItems({ ...options, generatedAt, limit }, db);
|
|
37127
37127
|
const cursor = latestTimestamp2(items, generatedAt);
|
|
@@ -37157,7 +37157,7 @@ function getLocalSnapshot(options, db) {
|
|
|
37157
37157
|
};
|
|
37158
37158
|
}
|
|
37159
37159
|
function pollLocalSnapshots(options = {}, db) {
|
|
37160
|
-
const generatedAt = options.generatedAt ??
|
|
37160
|
+
const generatedAt = options.generatedAt ?? now2();
|
|
37161
37161
|
const types2 = options.types?.length ? options.types : ALL_TYPES;
|
|
37162
37162
|
const snapshots = types2.map((type) => getLocalSnapshot({
|
|
37163
37163
|
type,
|
|
@@ -37282,7 +37282,7 @@ function rowToRetrospective(row) {
|
|
|
37282
37282
|
project_id: row.project_id,
|
|
37283
37283
|
plan_id: row.plan_id,
|
|
37284
37284
|
agent_id: row.agent_id,
|
|
37285
|
-
report:
|
|
37285
|
+
report: redactValue2(report),
|
|
37286
37286
|
created_at: row.created_at,
|
|
37287
37287
|
updated_at: row.updated_at
|
|
37288
37288
|
};
|
|
@@ -37347,7 +37347,7 @@ function buildFollowUps(report) {
|
|
|
37347
37347
|
}
|
|
37348
37348
|
function buildReport2(input, db) {
|
|
37349
37349
|
const scope = scopeFromInput(input, db);
|
|
37350
|
-
const generatedAt =
|
|
37350
|
+
const generatedAt = now2();
|
|
37351
37351
|
const title = input.title?.trim() || `${scope.scope === "plan" ? "Plan" : "Project"} retrospective ${scope.scopeId.slice(0, 8)}`;
|
|
37352
37352
|
const tasks = db.query(`SELECT id, short_id, title, status, estimated_minutes, actual_minutes
|
|
37353
37353
|
FROM tasks WHERE ${scope.where}`).all(scope.scopeId);
|
|
@@ -37431,7 +37431,7 @@ function createRetrospective(input, db) {
|
|
|
37431
37431
|
const d = db || getDatabase();
|
|
37432
37432
|
const report = buildReport2(input, d);
|
|
37433
37433
|
const scope = scopeFromInput(input, d);
|
|
37434
|
-
const timestamp3 =
|
|
37434
|
+
const timestamp3 = now2();
|
|
37435
37435
|
if (input.create_followups) {
|
|
37436
37436
|
for (const followUp of report.follow_up_tasks) {
|
|
37437
37437
|
const task2 = createTask({
|
|
@@ -37485,7 +37485,7 @@ function createRetrospectiveExport(options = {}, db) {
|
|
|
37485
37485
|
schema_version: 1,
|
|
37486
37486
|
local_only: true,
|
|
37487
37487
|
no_network: true,
|
|
37488
|
-
generated_at:
|
|
37488
|
+
generated_at: now2(),
|
|
37489
37489
|
filters,
|
|
37490
37490
|
count: retrospectives.length,
|
|
37491
37491
|
retrospectives
|
|
@@ -37603,7 +37603,7 @@ function approvalsFromFixture(fixture, runs) {
|
|
|
37603
37603
|
return [...explicit, ...fromEvents];
|
|
37604
37604
|
}
|
|
37605
37605
|
function simulateAgentReplay(input, options = {}) {
|
|
37606
|
-
const fixture =
|
|
37606
|
+
const fixture = redactValue2(unpackFixture(input));
|
|
37607
37607
|
const task2 = isObject2(fixture["task"]) ? fixture["task"] : {};
|
|
37608
37608
|
const plan = isObject2(fixture["plan"]) ? fixture["plan"] : null;
|
|
37609
37609
|
const runsContainer = isObject2(fixture["runs"]) ? fixture["runs"] : {};
|
|
@@ -37953,7 +37953,7 @@ function createInboxItem(input, db) {
|
|
|
37953
37953
|
const item = rowToInboxItem(existing);
|
|
37954
37954
|
return { item, task: item.task_id ? getTask(item.task_id, d) : null, duplicate: true };
|
|
37955
37955
|
}
|
|
37956
|
-
const timestamp3 =
|
|
37956
|
+
const timestamp3 = now2();
|
|
37957
37957
|
let task2 = null;
|
|
37958
37958
|
const metadata = {
|
|
37959
37959
|
...input.metadata || {},
|
|
@@ -38127,7 +38127,7 @@ function githubRecord(input, fallback) {
|
|
|
38127
38127
|
labels,
|
|
38128
38128
|
assignee: asString3(asObject2(input["assignee"])["login"]) || asString3(input["assignee"]),
|
|
38129
38129
|
priority: priorityFromLabels(labels, fallback),
|
|
38130
|
-
metadata:
|
|
38130
|
+
metadata: redactValue2(input)
|
|
38131
38131
|
};
|
|
38132
38132
|
}
|
|
38133
38133
|
function linearRecord(input, fallback) {
|
|
@@ -38146,7 +38146,7 @@ function linearRecord(input, fallback) {
|
|
|
38146
38146
|
labels,
|
|
38147
38147
|
assignee: asString3(asObject2(input["assignee"])["name"]) || asString3(asObject2(input["assignee"])["displayName"]) || asString3(input["assignee"]),
|
|
38148
38148
|
priority,
|
|
38149
|
-
metadata:
|
|
38149
|
+
metadata: redactValue2(input)
|
|
38150
38150
|
};
|
|
38151
38151
|
}
|
|
38152
38152
|
function jiraDescription(value) {
|
|
@@ -38174,7 +38174,7 @@ function jiraRecord(input, fallback) {
|
|
|
38174
38174
|
labels,
|
|
38175
38175
|
assignee: asString3(asObject2(fields["assignee"])["displayName"]) || asString3(input["assignee"]),
|
|
38176
38176
|
priority,
|
|
38177
|
-
metadata:
|
|
38177
|
+
metadata: redactValue2(input)
|
|
38178
38178
|
};
|
|
38179
38179
|
}
|
|
38180
38180
|
function urlRecord(url, title, body, fallback = "medium") {
|
|
@@ -38357,7 +38357,7 @@ function createTaskFromIssue(issue, input, db) {
|
|
|
38357
38357
|
key: issue.key,
|
|
38358
38358
|
url: issue.url,
|
|
38359
38359
|
state: issue.state,
|
|
38360
|
-
imported_at:
|
|
38360
|
+
imported_at: now2()
|
|
38361
38361
|
},
|
|
38362
38362
|
...parsedGitHub ? {
|
|
38363
38363
|
github_url: issue.url,
|
|
@@ -38371,7 +38371,7 @@ function createTaskFromIssue(issue, input, db) {
|
|
|
38371
38371
|
description: taskDescription(issue).slice(0, 4000),
|
|
38372
38372
|
priority: issue.priority || input.default_priority || "medium",
|
|
38373
38373
|
tags: Array.from(new Set(["external-issue", issue.provider, ...issue.labels])).slice(0, 10),
|
|
38374
|
-
metadata:
|
|
38374
|
+
metadata: redactValue2(metadata),
|
|
38375
38375
|
project_id: input.project_id,
|
|
38376
38376
|
task_list_id: input.task_list_id,
|
|
38377
38377
|
agent_id: input.agent_id,
|
|
@@ -38421,7 +38421,7 @@ function importExternalIssues(input, db) {
|
|
|
38421
38421
|
external_id: stableId(issue.provider, issue.key, issue.url, issue.title),
|
|
38422
38422
|
body: issue.body ? redactEvidenceText(issue.body) : null,
|
|
38423
38423
|
title: redactEvidenceText(issue.title),
|
|
38424
|
-
metadata:
|
|
38424
|
+
metadata: redactValue2(issue.metadata)
|
|
38425
38425
|
}));
|
|
38426
38426
|
const createdTasks = [];
|
|
38427
38427
|
const inboxItems = [];
|
|
@@ -38467,7 +38467,7 @@ function importExternalIssues(input, db) {
|
|
|
38467
38467
|
local_only: true,
|
|
38468
38468
|
network_used: networkUsed,
|
|
38469
38469
|
dry_run: !input.apply,
|
|
38470
|
-
imported_at:
|
|
38470
|
+
imported_at: now2(),
|
|
38471
38471
|
source: {
|
|
38472
38472
|
provider,
|
|
38473
38473
|
source_url: input.source_url || null,
|
|
@@ -38512,14 +38512,14 @@ function expiresAt(ttlSeconds) {
|
|
|
38512
38512
|
}
|
|
38513
38513
|
function cleanExpiredFileLocks(db) {
|
|
38514
38514
|
const d = db || getDatabase();
|
|
38515
|
-
const result = d.run("DELETE FROM file_locks WHERE expires_at <= ?", [
|
|
38515
|
+
const result = d.run("DELETE FROM file_locks WHERE expires_at <= ?", [now2()]);
|
|
38516
38516
|
return result.changes;
|
|
38517
38517
|
}
|
|
38518
38518
|
function lockFile(input, db) {
|
|
38519
38519
|
const d = db || getDatabase();
|
|
38520
38520
|
const ttl = input.ttl_seconds ?? FILE_LOCK_DEFAULT_TTL_SECONDS;
|
|
38521
38521
|
const expiry = expiresAt(ttl);
|
|
38522
|
-
const timestamp3 =
|
|
38522
|
+
const timestamp3 = now2();
|
|
38523
38523
|
cleanExpiredFileLocks(d);
|
|
38524
38524
|
const existing = d.query("SELECT * FROM file_locks WHERE path = ?").get(input.path);
|
|
38525
38525
|
if (existing) {
|
|
@@ -40047,7 +40047,7 @@ function syncKgEdges(db) {
|
|
|
40047
40047
|
function upsertEdge(d, sourceId, sourceType, targetId, targetType, relationType, weight = 1) {
|
|
40048
40048
|
try {
|
|
40049
40049
|
d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
|
|
40050
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, '{}', ?)`, [uuid(), sourceId, sourceType, targetId, targetType, relationType, weight,
|
|
40050
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, '{}', ?)`, [uuid(), sourceId, sourceType, targetId, targetType, relationType, weight, now2()]);
|
|
40051
40051
|
return 1;
|
|
40052
40052
|
} catch {
|
|
40053
40053
|
return 0;
|
|
@@ -40202,7 +40202,7 @@ function getCriticalPath(opts, db) {
|
|
|
40202
40202
|
function addKgEdge(sourceId, sourceType, targetId, targetType, relationType, weight = 1, metadata, db) {
|
|
40203
40203
|
const d = db || getDatabase();
|
|
40204
40204
|
const id = uuid();
|
|
40205
|
-
const timestamp3 =
|
|
40205
|
+
const timestamp3 = now2();
|
|
40206
40206
|
d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
|
|
40207
40207
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, sourceId, sourceType, targetId, targetType, relationType, weight, JSON.stringify(metadata || {}), timestamp3]);
|
|
40208
40208
|
return { id, source_id: sourceId, source_type: sourceType, target_id: targetId, target_type: targetType, relation_type: relationType, weight, metadata: metadata || {}, created_at: timestamp3 };
|
|
@@ -40238,7 +40238,7 @@ function setProjectAgentRole(projectId, agentId, role, isLead = false, db) {
|
|
|
40238
40238
|
return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(existing.id));
|
|
40239
40239
|
}
|
|
40240
40240
|
const id = uuid();
|
|
40241
|
-
d.run("INSERT INTO project_agent_roles (id, project_id, agent_id, role, is_lead, created_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, agentId, role, isLead ? 1 : 0,
|
|
40241
|
+
d.run("INSERT INTO project_agent_roles (id, project_id, agent_id, role, is_lead, created_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, agentId, role, isLead ? 1 : 0, now2()]);
|
|
40242
40242
|
return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(id));
|
|
40243
40243
|
}
|
|
40244
40244
|
function removeProjectAgentRole(projectId, agentId, role, db) {
|
|
@@ -40442,7 +40442,7 @@ function taskOrThrow2(taskId, db) {
|
|
|
40442
40442
|
return task2;
|
|
40443
40443
|
}
|
|
40444
40444
|
function normalizeRule2(input, existing) {
|
|
40445
|
-
const timestamp3 =
|
|
40445
|
+
const timestamp3 = now2();
|
|
40446
40446
|
const name = input.name.trim();
|
|
40447
40447
|
if (!name)
|
|
40448
40448
|
throw new Error("Review routing rule name is required");
|
|
@@ -40477,7 +40477,7 @@ function readQueue(task2) {
|
|
|
40477
40477
|
requested_at: typeof raw.requested_at === "string" ? raw.requested_at : null,
|
|
40478
40478
|
claimed_at: typeof raw.claimed_at === "string" ? raw.claimed_at : null,
|
|
40479
40479
|
decided_at: typeof raw.decided_at === "string" ? raw.decided_at : null,
|
|
40480
|
-
updated_at: typeof raw.updated_at === "string" ? raw.updated_at :
|
|
40480
|
+
updated_at: typeof raw.updated_at === "string" ? raw.updated_at : now2(),
|
|
40481
40481
|
history: Array.isArray(raw.history) ? raw.history.map((entry) => {
|
|
40482
40482
|
const item = typeof entry === "object" && entry !== null ? entry : {};
|
|
40483
40483
|
const entryState = typeof item.state === "string" && STATES.has(item.state) ? item.state : state;
|
|
@@ -40485,7 +40485,7 @@ function readQueue(task2) {
|
|
|
40485
40485
|
state: entryState,
|
|
40486
40486
|
actor: typeof item.actor === "string" ? item.actor : "unknown",
|
|
40487
40487
|
note: typeof item.note === "string" ? item.note : null,
|
|
40488
|
-
at: typeof item.at === "string" ? item.at :
|
|
40488
|
+
at: typeof item.at === "string" ? item.at : now2()
|
|
40489
40489
|
};
|
|
40490
40490
|
}) : []
|
|
40491
40491
|
};
|
|
@@ -40527,7 +40527,7 @@ function writeQueue(task2, queue, actor, action, db) {
|
|
|
40527
40527
|
return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
|
|
40528
40528
|
}
|
|
40529
40529
|
function appendHistory(queue, state, actor, note) {
|
|
40530
|
-
return [...queue.history, { state, actor, note: note ?? null, at:
|
|
40530
|
+
return [...queue.history, { state, actor, note: note ?? null, at: now2() }];
|
|
40531
40531
|
}
|
|
40532
40532
|
function itemFromTask(task2, queue, db) {
|
|
40533
40533
|
return {
|
|
@@ -40662,7 +40662,7 @@ function requestReviewQueue(input, db) {
|
|
|
40662
40662
|
notes: input.notes ?? input.reason
|
|
40663
40663
|
}, d);
|
|
40664
40664
|
const updatedTask = taskOrThrow2(task2.id, d);
|
|
40665
|
-
const timestamp3 =
|
|
40665
|
+
const timestamp3 = now2();
|
|
40666
40666
|
const previous = readQueue(updatedTask);
|
|
40667
40667
|
const queue = {
|
|
40668
40668
|
schema_version: 1,
|
|
@@ -40705,7 +40705,7 @@ function claimReviewItem(input, db) {
|
|
|
40705
40705
|
const previous = derivedQueue(task2, d);
|
|
40706
40706
|
if (!previous)
|
|
40707
40707
|
throw new Error(`Task is not in a review queue: ${task2.id}`);
|
|
40708
|
-
const timestamp3 =
|
|
40708
|
+
const timestamp3 = now2();
|
|
40709
40709
|
const queue = {
|
|
40710
40710
|
...previous,
|
|
40711
40711
|
state: "claimed",
|
|
@@ -40725,7 +40725,7 @@ function approveReviewItem(input, db) {
|
|
|
40725
40725
|
throw new Error(`Task is not in a review queue: ${task2.id}`);
|
|
40726
40726
|
recordTaskReview({ task_id: task2.id, state: "approved", reviewer: input.reviewer, notes: input.note }, d);
|
|
40727
40727
|
const updatedTask = taskOrThrow2(task2.id, d);
|
|
40728
|
-
const timestamp3 =
|
|
40728
|
+
const timestamp3 = now2();
|
|
40729
40729
|
const queue = {
|
|
40730
40730
|
...previous,
|
|
40731
40731
|
state: "approved",
|
|
@@ -40753,7 +40753,7 @@ function returnReviewItem(input, db) {
|
|
|
40753
40753
|
changes_requested: changes
|
|
40754
40754
|
}, d);
|
|
40755
40755
|
const updatedTask = taskOrThrow2(task2.id, d);
|
|
40756
|
-
const timestamp3 =
|
|
40756
|
+
const timestamp3 = now2();
|
|
40757
40757
|
const queue = {
|
|
40758
40758
|
...previous,
|
|
40759
40759
|
state: "returned",
|
|
@@ -40774,7 +40774,7 @@ function reopenReviewItem(input, db) {
|
|
|
40774
40774
|
throw new Error(`Task is not in a review queue: ${task2.id}`);
|
|
40775
40775
|
recordTaskReview({ task_id: task2.id, state: "reopened", reviewer: input.reviewer, notes: input.note }, d);
|
|
40776
40776
|
const updatedTask = taskOrThrow2(task2.id, d);
|
|
40777
|
-
const timestamp3 =
|
|
40777
|
+
const timestamp3 = now2();
|
|
40778
40778
|
const queue = {
|
|
40779
40779
|
...previous,
|
|
40780
40780
|
state: "reopened",
|
|
@@ -41217,7 +41217,7 @@ ${lines.join(`
|
|
|
41217
41217
|
const projectRoles = n.project_roles.length > 0 ? ` <${n.project_roles.join(", ")}>` : "";
|
|
41218
41218
|
const lead = n.is_project_lead ? " \u2605" : "";
|
|
41219
41219
|
const lastSeen = new Date(n.agent.last_seen_at).getTime();
|
|
41220
|
-
const active =
|
|
41220
|
+
const active = now4 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
|
|
41221
41221
|
const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${projectRoles}${lead}`;
|
|
41222
41222
|
const children = n.reports.length > 0 ? `
|
|
41223
41223
|
` + render(n.reports, indent + 1) : "";
|
|
@@ -41231,7 +41231,7 @@ ${lines.join(`
|
|
|
41231
41231
|
if (format === "json") {
|
|
41232
41232
|
return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
|
|
41233
41233
|
}
|
|
41234
|
-
const
|
|
41234
|
+
const now4 = Date.now();
|
|
41235
41235
|
const ACTIVE_MS = 30 * 60 * 1000;
|
|
41236
41236
|
const text2 = tree.length > 0 ? render(tree) : "No agents in this project's org chart.";
|
|
41237
41237
|
return { content: [{ type: "text", text: text2 }] };
|
|
@@ -41781,10 +41781,10 @@ ${lines.join(`
|
|
|
41781
41781
|
});
|
|
41782
41782
|
}
|
|
41783
41783
|
if (shouldRegisterTool("get_idle_focus_prompts")) {
|
|
41784
|
-
server.tool("get_idle_focus_prompts", "Return local idle prompts for active focus sessions.", { agent_id: exports_external.string().optional(), now: exports_external.string().optional() }, async ({ agent_id, now:
|
|
41784
|
+
server.tool("get_idle_focus_prompts", "Return local idle prompts for active focus sessions.", { agent_id: exports_external.string().optional(), now: exports_external.string().optional() }, async ({ agent_id, now: now4 }) => {
|
|
41785
41785
|
try {
|
|
41786
41786
|
const { getIdleFocusSessionPrompts: getIdleFocusSessionPrompts2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
41787
|
-
return { content: [{ type: "text", text: JSON.stringify(getIdleFocusSessionPrompts2({ agent_id, now:
|
|
41787
|
+
return { content: [{ type: "text", text: JSON.stringify(getIdleFocusSessionPrompts2({ agent_id, now: now4 }), null, 2) }] };
|
|
41788
41788
|
} catch (e) {
|
|
41789
41789
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
41790
41790
|
}
|
|
@@ -42922,16 +42922,16 @@ __export(exports_environment_snapshots, {
|
|
|
42922
42922
|
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
42923
42923
|
});
|
|
42924
42924
|
import { createHash as createHash12 } from "crypto";
|
|
42925
|
-
import { existsSync as
|
|
42925
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
42926
42926
|
import { hostname as hostname2, platform, arch } from "os";
|
|
42927
|
-
import { dirname as dirname9, join as
|
|
42927
|
+
import { dirname as dirname9, join as join17, resolve as resolve18 } from "path";
|
|
42928
42928
|
import { tmpdir as tmpdir2 } from "os";
|
|
42929
42929
|
function sha2566(value) {
|
|
42930
42930
|
return createHash12("sha256").update(value).digest("hex");
|
|
42931
42931
|
}
|
|
42932
42932
|
function fileRecord(root, relativePath) {
|
|
42933
|
-
const path =
|
|
42934
|
-
if (!
|
|
42933
|
+
const path = join17(root, relativePath);
|
|
42934
|
+
if (!existsSync17(path))
|
|
42935
42935
|
return null;
|
|
42936
42936
|
const stat = statSync8(path);
|
|
42937
42937
|
if (!stat.isFile())
|
|
@@ -42943,10 +42943,10 @@ function manifestRecord(root, relativePath) {
|
|
|
42943
42943
|
const base = fileRecord(root, relativePath);
|
|
42944
42944
|
if (!base)
|
|
42945
42945
|
return null;
|
|
42946
|
-
const parsed = readJsonFile(
|
|
42946
|
+
const parsed = readJsonFile(join17(root, relativePath));
|
|
42947
42947
|
if (!parsed)
|
|
42948
42948
|
return { ...base, redacted: {} };
|
|
42949
|
-
const redacted =
|
|
42949
|
+
const redacted = redactValue2({
|
|
42950
42950
|
name: parsed["name"] ?? null,
|
|
42951
42951
|
version: parsed["version"] ?? null,
|
|
42952
42952
|
packageManager: parsed["packageManager"] ?? null,
|
|
@@ -43038,8 +43038,8 @@ function commandEnv(env, includeValues) {
|
|
|
43038
43038
|
function defaultSnapshotDir() {
|
|
43039
43039
|
const dbPath = getDatabasePath();
|
|
43040
43040
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
43041
|
-
return
|
|
43042
|
-
return
|
|
43041
|
+
return join17(tmpdir2(), "hasna-todos", "environment-snapshots");
|
|
43042
|
+
return join17(dirname9(resolve18(dbPath)), "environment-snapshots");
|
|
43043
43043
|
}
|
|
43044
43044
|
function snapshotWithId(snapshot) {
|
|
43045
43045
|
const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
|
|
@@ -43086,7 +43086,7 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
43086
43086
|
});
|
|
43087
43087
|
}
|
|
43088
43088
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
43089
|
-
const path = outputPath ? resolve18(outputPath) :
|
|
43089
|
+
const path = outputPath ? resolve18(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
43090
43090
|
ensureDir2(dirname9(path));
|
|
43091
43091
|
writeJsonFile(path, snapshot);
|
|
43092
43092
|
return path;
|
|
@@ -43617,27 +43617,27 @@ __export(exports_serve, {
|
|
|
43617
43617
|
SECURITY_HEADERS: () => SECURITY_HEADERS,
|
|
43618
43618
|
MIME_TYPES: () => MIME_TYPES
|
|
43619
43619
|
});
|
|
43620
|
-
import { existsSync as
|
|
43621
|
-
import { join as
|
|
43620
|
+
import { existsSync as existsSync18 } from "fs";
|
|
43621
|
+
import { join as join18, dirname as dirname10, extname } from "path";
|
|
43622
43622
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
43623
43623
|
function resolveDashboardDir() {
|
|
43624
43624
|
const candidates = [];
|
|
43625
43625
|
try {
|
|
43626
43626
|
const scriptDir = dirname10(fileURLToPath2(import.meta.url));
|
|
43627
|
-
candidates.push(
|
|
43628
|
-
candidates.push(
|
|
43627
|
+
candidates.push(join18(scriptDir, "..", "dashboard", "dist"));
|
|
43628
|
+
candidates.push(join18(scriptDir, "..", "..", "dashboard", "dist"));
|
|
43629
43629
|
} catch {}
|
|
43630
43630
|
if (process.argv[1]) {
|
|
43631
43631
|
const mainDir = dirname10(process.argv[1]);
|
|
43632
|
-
candidates.push(
|
|
43633
|
-
candidates.push(
|
|
43632
|
+
candidates.push(join18(mainDir, "..", "dashboard", "dist"));
|
|
43633
|
+
candidates.push(join18(mainDir, "..", "..", "dashboard", "dist"));
|
|
43634
43634
|
}
|
|
43635
|
-
candidates.push(
|
|
43635
|
+
candidates.push(join18(process.cwd(), "dashboard", "dist"));
|
|
43636
43636
|
for (const candidate of candidates) {
|
|
43637
|
-
if (
|
|
43637
|
+
if (existsSync18(candidate))
|
|
43638
43638
|
return candidate;
|
|
43639
43639
|
}
|
|
43640
|
-
return
|
|
43640
|
+
return join18(process.cwd(), "dashboard", "dist");
|
|
43641
43641
|
}
|
|
43642
43642
|
function getProvidedApiKey(req) {
|
|
43643
43643
|
const headerKey = req.headers.get("x-api-key");
|
|
@@ -43664,15 +43664,15 @@ function checkAuth(req, apiKey) {
|
|
|
43664
43664
|
return null;
|
|
43665
43665
|
}
|
|
43666
43666
|
function checkRateLimit(ip) {
|
|
43667
|
-
const
|
|
43667
|
+
const now4 = Date.now();
|
|
43668
43668
|
const entry = rateLimitMap.get(ip);
|
|
43669
|
-
if (!entry ||
|
|
43670
|
-
rateLimitMap.set(ip, { count: 1, resetAt:
|
|
43669
|
+
if (!entry || now4 > entry.resetAt) {
|
|
43670
|
+
rateLimitMap.set(ip, { count: 1, resetAt: now4 + RATE_LIMIT_WINDOW_MS });
|
|
43671
43671
|
return { allowed: true };
|
|
43672
43672
|
}
|
|
43673
43673
|
entry.count++;
|
|
43674
43674
|
if (entry.count > RATE_LIMIT_MAX) {
|
|
43675
|
-
return { allowed: false, retryAfter: Math.ceil((entry.resetAt -
|
|
43675
|
+
return { allowed: false, retryAfter: Math.ceil((entry.resetAt - now4) / 1000) };
|
|
43676
43676
|
}
|
|
43677
43677
|
return { allowed: true };
|
|
43678
43678
|
}
|
|
@@ -43687,7 +43687,7 @@ function json(data, status = 200, headers) {
|
|
|
43687
43687
|
});
|
|
43688
43688
|
}
|
|
43689
43689
|
function serveStaticFile(filePath) {
|
|
43690
|
-
if (!
|
|
43690
|
+
if (!existsSync18(filePath))
|
|
43691
43691
|
return null;
|
|
43692
43692
|
const ext = extname(filePath);
|
|
43693
43693
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -43766,7 +43766,7 @@ data: ${data}
|
|
|
43766
43766
|
filteredSseClients.delete(client);
|
|
43767
43767
|
}
|
|
43768
43768
|
const dashboardDir = resolveDashboardDir();
|
|
43769
|
-
const dashboardExists =
|
|
43769
|
+
const dashboardExists = existsSync18(dashboardDir);
|
|
43770
43770
|
if (!dashboardExists) {
|
|
43771
43771
|
console.error(`
|
|
43772
43772
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -45592,12 +45592,12 @@ __export(exports_config_serve_commands, {
|
|
|
45592
45592
|
registerConfigServeCommands: () => registerConfigServeCommands
|
|
45593
45593
|
});
|
|
45594
45594
|
import chalk6 from "chalk";
|
|
45595
|
-
import { existsSync as
|
|
45596
|
-
import { dirname as dirname11, join as
|
|
45595
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
45596
|
+
import { dirname as dirname11, join as join19 } from "path";
|
|
45597
45597
|
function registerConfigServeCommands(program2) {
|
|
45598
45598
|
program2.command("config").description("View or update configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value (e.g. completion_guard.enabled=true)").action((opts) => {
|
|
45599
45599
|
const globalOpts = program2.opts();
|
|
45600
|
-
const configPath =
|
|
45600
|
+
const configPath = join19(getTodosGlobalDir(), "config.json");
|
|
45601
45601
|
if (opts.get) {
|
|
45602
45602
|
const config2 = loadConfig();
|
|
45603
45603
|
const keys = opts.get.split(".");
|
|
@@ -45634,7 +45634,7 @@ function registerConfigServeCommands(program2) {
|
|
|
45634
45634
|
}
|
|
45635
45635
|
obj[keys[keys.length - 1]] = parsedValue;
|
|
45636
45636
|
const dir = dirname11(configPath);
|
|
45637
|
-
if (!
|
|
45637
|
+
if (!existsSync19(dir))
|
|
45638
45638
|
mkdirSync9(dir, { recursive: true });
|
|
45639
45639
|
writeFileSync7(configPath, JSON.stringify(config2, null, 2));
|
|
45640
45640
|
if (globalOpts.json) {
|
|
@@ -46496,8 +46496,8 @@ function registerConfigServeCommands(program2) {
|
|
|
46496
46496
|
for (const t of all)
|
|
46497
46497
|
counts[t.status] = (counts[t.status] || 0) + 1;
|
|
46498
46498
|
process.stdout.write("\x1B[2J\x1B[0f");
|
|
46499
|
-
const
|
|
46500
|
-
console.log(chalk6.bold(`todos watch`) + chalk6.dim(` \u2014 ${
|
|
46499
|
+
const now4 = new Date().toLocaleTimeString();
|
|
46500
|
+
console.log(chalk6.bold(`todos watch`) + chalk6.dim(` \u2014 ${now4} \u2014 refreshing every ${opts.interval}s \u2014 Ctrl+C to stop
|
|
46501
46501
|
`));
|
|
46502
46502
|
const parts = [
|
|
46503
46503
|
`total: ${chalk6.bold(String(all.length))}`,
|
|
@@ -47331,9 +47331,9 @@ Repairs`));
|
|
|
47331
47331
|
const db = getDatabase();
|
|
47332
47332
|
const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
|
|
47333
47333
|
const { statSync: statSync9 } = await import("fs");
|
|
47334
|
-
const { join:
|
|
47334
|
+
const { join: join20 } = await import("path");
|
|
47335
47335
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
47336
|
-
const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] ||
|
|
47336
|
+
const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join20(home, ".hasna", "todos", "todos.db");
|
|
47337
47337
|
let size = "unknown";
|
|
47338
47338
|
try {
|
|
47339
47339
|
size = `${(statSync9(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
@@ -47641,14 +47641,14 @@ Repairs`));
|
|
|
47641
47641
|
const globalOpts = program2.opts();
|
|
47642
47642
|
const db = getDatabase();
|
|
47643
47643
|
const { getTasksChangedSince: getTasksChangedSince2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
47644
|
-
const
|
|
47645
|
-
const start = new Date(
|
|
47644
|
+
const now4 = new Date;
|
|
47645
|
+
const start = new Date(now4);
|
|
47646
47646
|
start.setDate(start.getDate() - 7);
|
|
47647
47647
|
start.setHours(0, 0, 0, 0);
|
|
47648
47648
|
const tasks = getTasksChangedSince2(start.toISOString(), undefined, db);
|
|
47649
47649
|
const days = {};
|
|
47650
47650
|
for (let i = 0;i < 7; i++) {
|
|
47651
|
-
const d = new Date(
|
|
47651
|
+
const d = new Date(now4);
|
|
47652
47652
|
d.setDate(d.getDate() - i);
|
|
47653
47653
|
days[d.toISOString().slice(0, 10)] = { completed: [], started: [], other: [] };
|
|
47654
47654
|
}
|
|
@@ -47664,12 +47664,12 @@ Repairs`));
|
|
|
47664
47664
|
days[day].other.push(t);
|
|
47665
47665
|
}
|
|
47666
47666
|
if (opts.json || globalOpts.json) {
|
|
47667
|
-
console.log(JSON.stringify({ from: start.toISOString().slice(0, 10), to:
|
|
47667
|
+
console.log(JSON.stringify({ from: start.toISOString().slice(0, 10), to: now4.toISOString().slice(0, 10), days }));
|
|
47668
47668
|
return;
|
|
47669
47669
|
}
|
|
47670
47670
|
const totalCompleted = tasks.filter((t) => t.status === "completed").length;
|
|
47671
47671
|
const totalStarted = tasks.filter((t) => t.status === "in_progress").length;
|
|
47672
|
-
console.log(chalk7.bold(`Week \u2014 ${start.toISOString().slice(0, 10)} to ${
|
|
47672
|
+
console.log(chalk7.bold(`Week \u2014 ${start.toISOString().slice(0, 10)} to ${now4.toISOString().slice(0, 10)}`));
|
|
47673
47673
|
console.log(chalk7.dim(` ${totalCompleted} completed, ${totalStarted} in progress, ${tasks.length} total changes
|
|
47674
47674
|
`));
|
|
47675
47675
|
const sortedDays = Object.keys(days).sort().reverse();
|
|
@@ -47696,10 +47696,10 @@ Repairs`));
|
|
|
47696
47696
|
const { getRecentActivity: getRecentActivity2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
|
|
47697
47697
|
const numDays = parseInt(opts.days, 10);
|
|
47698
47698
|
const entries = getRecentActivity2(5000, db);
|
|
47699
|
-
const
|
|
47699
|
+
const now4 = new Date;
|
|
47700
47700
|
const dayStats = [];
|
|
47701
47701
|
for (let i = numDays - 1;i >= 0; i--) {
|
|
47702
|
-
const d = new Date(
|
|
47702
|
+
const d = new Date(now4);
|
|
47703
47703
|
d.setDate(d.getDate() - i);
|
|
47704
47704
|
const dateStr = d.toISOString().slice(0, 10);
|
|
47705
47705
|
const dayEntries = entries.filter((e) => e.created_at.slice(0, 10) === dateStr);
|
|
@@ -49090,21 +49090,21 @@ __export(exports_mcp_hooks_commands, {
|
|
|
49090
49090
|
});
|
|
49091
49091
|
import chalk8 from "chalk";
|
|
49092
49092
|
import { execSync as execSync3 } from "child_process";
|
|
49093
|
-
import { existsSync as
|
|
49094
|
-
import { dirname as dirname12, join as
|
|
49093
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10, chmodSync as chmodSync2 } from "fs";
|
|
49094
|
+
import { dirname as dirname12, join as join20 } from "path";
|
|
49095
49095
|
function getMcpBinaryPath() {
|
|
49096
49096
|
try {
|
|
49097
49097
|
const p = execSync3("which todos-mcp", { encoding: "utf-8" }).trim();
|
|
49098
49098
|
if (p)
|
|
49099
49099
|
return p;
|
|
49100
49100
|
} catch {}
|
|
49101
|
-
const bunBin =
|
|
49102
|
-
if (
|
|
49101
|
+
const bunBin = join20(HOME2, ".bun", "bin", "todos-mcp");
|
|
49102
|
+
if (existsSync20(bunBin))
|
|
49103
49103
|
return bunBin;
|
|
49104
49104
|
return "todos-mcp";
|
|
49105
49105
|
}
|
|
49106
49106
|
function readJsonFile2(path) {
|
|
49107
|
-
if (!
|
|
49107
|
+
if (!existsSync20(path))
|
|
49108
49108
|
return {};
|
|
49109
49109
|
try {
|
|
49110
49110
|
return JSON.parse(readFileSync16(path, "utf-8"));
|
|
@@ -49114,19 +49114,19 @@ function readJsonFile2(path) {
|
|
|
49114
49114
|
}
|
|
49115
49115
|
function writeJsonFile2(path, data) {
|
|
49116
49116
|
const dir = dirname12(path);
|
|
49117
|
-
if (!
|
|
49117
|
+
if (!existsSync20(dir))
|
|
49118
49118
|
mkdirSync10(dir, { recursive: true });
|
|
49119
49119
|
writeFileSync9(path, JSON.stringify(data, null, 2) + `
|
|
49120
49120
|
`);
|
|
49121
49121
|
}
|
|
49122
49122
|
function readTomlFile(path) {
|
|
49123
|
-
if (!
|
|
49123
|
+
if (!existsSync20(path))
|
|
49124
49124
|
return "";
|
|
49125
49125
|
return readFileSync16(path, "utf-8");
|
|
49126
49126
|
}
|
|
49127
49127
|
function writeTomlFile(path, content) {
|
|
49128
49128
|
const dir = dirname12(path);
|
|
49129
|
-
if (!
|
|
49129
|
+
if (!existsSync20(dir))
|
|
49130
49130
|
mkdirSync10(dir, { recursive: true });
|
|
49131
49131
|
writeFileSync9(path, content);
|
|
49132
49132
|
}
|
|
@@ -49192,7 +49192,7 @@ function unregisterClaude(_global) {
|
|
|
49192
49192
|
}
|
|
49193
49193
|
}
|
|
49194
49194
|
function registerCodex(binPath) {
|
|
49195
|
-
const configPath =
|
|
49195
|
+
const configPath = join20(HOME2, ".codex", "config.toml");
|
|
49196
49196
|
let content = readTomlFile(configPath);
|
|
49197
49197
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
49198
49198
|
const block = `
|
|
@@ -49206,7 +49206,7 @@ args = []
|
|
|
49206
49206
|
console.log(chalk8.green(`Codex CLI: registered in ${configPath}`));
|
|
49207
49207
|
}
|
|
49208
49208
|
function unregisterCodex() {
|
|
49209
|
-
const configPath =
|
|
49209
|
+
const configPath = join20(HOME2, ".codex", "config.toml");
|
|
49210
49210
|
let content = readTomlFile(configPath);
|
|
49211
49211
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
49212
49212
|
console.log(chalk8.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
@@ -49218,7 +49218,7 @@ function unregisterCodex() {
|
|
|
49218
49218
|
console.log(chalk8.green(`Codex CLI: unregistered from ${configPath}`));
|
|
49219
49219
|
}
|
|
49220
49220
|
function registerGemini(binPath) {
|
|
49221
|
-
const configPath =
|
|
49221
|
+
const configPath = join20(HOME2, ".gemini", "settings.json");
|
|
49222
49222
|
const config = readJsonFile2(configPath);
|
|
49223
49223
|
if (!config["mcpServers"]) {
|
|
49224
49224
|
config["mcpServers"] = {};
|
|
@@ -49232,7 +49232,7 @@ function registerGemini(binPath) {
|
|
|
49232
49232
|
console.log(chalk8.green(`Gemini CLI: registered in ${configPath}`));
|
|
49233
49233
|
}
|
|
49234
49234
|
function unregisterGemini() {
|
|
49235
|
-
const configPath =
|
|
49235
|
+
const configPath = join20(HOME2, ".gemini", "settings.json");
|
|
49236
49236
|
const config = readJsonFile2(configPath);
|
|
49237
49237
|
const servers = config["mcpServers"];
|
|
49238
49238
|
if (!servers || !("todos" in servers)) {
|
|
@@ -49289,8 +49289,8 @@ function registerMcpHooksCommands(program2) {
|
|
|
49289
49289
|
if (p)
|
|
49290
49290
|
todosBin = p;
|
|
49291
49291
|
} catch {}
|
|
49292
|
-
const hooksDir =
|
|
49293
|
-
if (!
|
|
49292
|
+
const hooksDir = join20(process.cwd(), ".claude", "hooks");
|
|
49293
|
+
if (!existsSync20(hooksDir))
|
|
49294
49294
|
mkdirSync10(hooksDir, { recursive: true });
|
|
49295
49295
|
const hookScript = `#!/usr/bin/env bash
|
|
49296
49296
|
# Auto-generated by: todos hooks install
|
|
@@ -49315,11 +49315,11 @@ esac
|
|
|
49315
49315
|
|
|
49316
49316
|
exit 0
|
|
49317
49317
|
`;
|
|
49318
|
-
const hookPath =
|
|
49318
|
+
const hookPath = join20(hooksDir, "todos-sync.sh");
|
|
49319
49319
|
writeFileSync9(hookPath, hookScript);
|
|
49320
49320
|
execSync3(`chmod +x "${hookPath}"`);
|
|
49321
49321
|
console.log(chalk8.green(`Hook script created: ${hookPath}`));
|
|
49322
|
-
const settingsPath =
|
|
49322
|
+
const settingsPath = join20(process.cwd(), ".claude", "settings.json");
|
|
49323
49323
|
const settings = readJsonFile2(settingsPath);
|
|
49324
49324
|
if (!settings["hooks"]) {
|
|
49325
49325
|
settings["hooks"] = {};
|
|
@@ -50122,7 +50122,7 @@ Artifacts:`));
|
|
|
50122
50122
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
50123
50123
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
50124
50124
|
const marker = "# todos-auto-link";
|
|
50125
|
-
if (
|
|
50125
|
+
if (existsSync20(hookPath)) {
|
|
50126
50126
|
const existing = readFileSync16(hookPath, "utf-8");
|
|
50127
50127
|
if (existing.includes(marker)) {
|
|
50128
50128
|
console.log(chalk8.yellow("Hook already installed."));
|
|
@@ -50150,7 +50150,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
50150
50150
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
50151
50151
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
50152
50152
|
const marker = "# todos-auto-link";
|
|
50153
|
-
if (!
|
|
50153
|
+
if (!existsSync20(hookPath)) {
|
|
50154
50154
|
console.log(chalk8.dim("No post-commit hook found."));
|
|
50155
50155
|
return;
|
|
50156
50156
|
}
|
|
@@ -50332,7 +50332,7 @@ import chalk10 from "chalk";
|
|
|
50332
50332
|
import { execSync as execSync4 } from "child_process";
|
|
50333
50333
|
import { readFileSync as readFileSync17, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
50334
50334
|
import { tmpdir as tmpdir3 } from "os";
|
|
50335
|
-
import { join as
|
|
50335
|
+
import { join as join21 } from "path";
|
|
50336
50336
|
function getOrCreateLocalMachineName() {
|
|
50337
50337
|
return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
|
|
50338
50338
|
}
|
|
@@ -50370,7 +50370,7 @@ function remoteTempPath(sshAddress) {
|
|
|
50370
50370
|
}
|
|
50371
50371
|
function readRemoteBridgeBundle(sshAddress) {
|
|
50372
50372
|
const remotePath = remoteTempPath(sshAddress);
|
|
50373
|
-
const localPath =
|
|
50373
|
+
const localPath = join21(tmpdir3(), `todos-bridge-pull-${uuid()}.json`);
|
|
50374
50374
|
try {
|
|
50375
50375
|
runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
|
|
50376
50376
|
scpFromRemote(sshAddress, remotePath, localPath);
|
|
@@ -50385,7 +50385,7 @@ function readRemoteBridgeBundle(sshAddress) {
|
|
|
50385
50385
|
}
|
|
50386
50386
|
}
|
|
50387
50387
|
function writeLocalBridgeBundle() {
|
|
50388
|
-
const localPath =
|
|
50388
|
+
const localPath = join21(tmpdir3(), `todos-bridge-push-${uuid()}.json`);
|
|
50389
50389
|
writeFileSync10(localPath, JSON.stringify(createLocalBridgeBundle(), null, 2));
|
|
50390
50390
|
return localPath;
|
|
50391
50391
|
}
|
|
@@ -50908,7 +50908,7 @@ function parseRecordType(value) {
|
|
|
50908
50908
|
console.error(chalk13.red(`type must be one of: ${RECORD_TYPES.join(", ")}`));
|
|
50909
50909
|
process.exit(1);
|
|
50910
50910
|
}
|
|
50911
|
-
function
|
|
50911
|
+
function parseJsonObject7(value, label) {
|
|
50912
50912
|
if (!value)
|
|
50913
50913
|
return;
|
|
50914
50914
|
try {
|
|
@@ -50968,7 +50968,7 @@ function registerKnowledgeCommands(program2) {
|
|
|
50968
50968
|
plan_id: opts.plan,
|
|
50969
50969
|
agent_id: opts.agent || globalOpts.agent,
|
|
50970
50970
|
tags: tagsFromOption(opts.tag),
|
|
50971
|
-
metadata:
|
|
50971
|
+
metadata: parseJsonObject7(opts.metadataJson, "--metadata-json")
|
|
50972
50972
|
}, getDatabase());
|
|
50973
50973
|
if (opts.json || globalOpts.json)
|
|
50974
50974
|
output(record, true);
|
|
@@ -50993,7 +50993,7 @@ function registerKnowledgeCommands(program2) {
|
|
|
50993
50993
|
blockers: opts.blocker,
|
|
50994
50994
|
next_steps: opts.next,
|
|
50995
50995
|
tags: tagsFromOption(opts.tag),
|
|
50996
|
-
metadata:
|
|
50996
|
+
metadata: parseJsonObject7(opts.metadataJson, "--metadata-json")
|
|
50997
50997
|
}, getDatabase());
|
|
50998
50998
|
if (opts.json || globalOpts.json)
|
|
50999
50999
|
output(result, true);
|
|
@@ -51079,7 +51079,7 @@ function parseChoice(value, choices, label) {
|
|
|
51079
51079
|
console.error(chalk14.red(`${label} must be one of: ${choices.join(", ")}`));
|
|
51080
51080
|
process.exit(1);
|
|
51081
51081
|
}
|
|
51082
|
-
function
|
|
51082
|
+
function parseJsonObject8(value, label) {
|
|
51083
51083
|
if (!value)
|
|
51084
51084
|
return;
|
|
51085
51085
|
try {
|
|
@@ -51144,7 +51144,7 @@ function registerRiskCommands(program2) {
|
|
|
51144
51144
|
plan_id: opts.plan,
|
|
51145
51145
|
task_id: opts.task,
|
|
51146
51146
|
tags: tagsFromOption2(opts.tag),
|
|
51147
|
-
metadata:
|
|
51147
|
+
metadata: parseJsonObject8(opts.metadataJson, "--metadata-json")
|
|
51148
51148
|
}, getDatabase());
|
|
51149
51149
|
if (opts.json || globalOpts.json)
|
|
51150
51150
|
output(risk, true);
|
|
@@ -51196,7 +51196,7 @@ function registerRiskCommands(program2) {
|
|
|
51196
51196
|
plan_id: opts.plan,
|
|
51197
51197
|
task_id: opts.task,
|
|
51198
51198
|
tags: opts.tag.length > 0 ? tagsFromOption2(opts.tag) : undefined,
|
|
51199
|
-
metadata:
|
|
51199
|
+
metadata: parseJsonObject8(opts.metadataJson, "--metadata-json")
|
|
51200
51200
|
}, getDatabase());
|
|
51201
51201
|
if (opts.json || globalOpts.json)
|
|
51202
51202
|
output(risk, true);
|
|
@@ -51360,7 +51360,7 @@ __export(exports_agent_reliability_commands, {
|
|
|
51360
51360
|
registerAgentReliabilityCommands: () => registerAgentReliabilityCommands
|
|
51361
51361
|
});
|
|
51362
51362
|
import chalk16 from "chalk";
|
|
51363
|
-
function
|
|
51363
|
+
function parseNumber2(value, fallback) {
|
|
51364
51364
|
if (!value)
|
|
51365
51365
|
return fallback;
|
|
51366
51366
|
const parsed = Number.parseInt(value, 10);
|
|
@@ -51370,7 +51370,7 @@ function commonOptions(opts) {
|
|
|
51370
51370
|
return {
|
|
51371
51371
|
project_id: opts.project,
|
|
51372
51372
|
since: opts.since,
|
|
51373
|
-
stale_after_hours:
|
|
51373
|
+
stale_after_hours: parseNumber2(opts.staleAfterHours, 24)
|
|
51374
51374
|
};
|
|
51375
51375
|
}
|
|
51376
51376
|
function printScorecard(scorecard) {
|
|
@@ -51407,7 +51407,7 @@ function registerAgentReliabilityCommands(program2) {
|
|
|
51407
51407
|
const report = createAgentReliabilityExport({
|
|
51408
51408
|
...commonOptions(opts),
|
|
51409
51409
|
agent_id: opts.agent,
|
|
51410
|
-
limit:
|
|
51410
|
+
limit: parseNumber2(opts.limit, 50)
|
|
51411
51411
|
}, getDatabase());
|
|
51412
51412
|
const globalOpts = program2.opts();
|
|
51413
51413
|
if (opts.json || globalOpts.json)
|
|
@@ -51423,7 +51423,7 @@ function registerAgentReliabilityCommands(program2) {
|
|
|
51423
51423
|
const report = createAgentReliabilityExport({
|
|
51424
51424
|
...commonOptions(opts),
|
|
51425
51425
|
agent_id: opts.agent,
|
|
51426
|
-
limit:
|
|
51426
|
+
limit: parseNumber2(opts.limit, 100)
|
|
51427
51427
|
}, getDatabase());
|
|
51428
51428
|
if (opts.format === "markdown") {
|
|
51429
51429
|
console.log(renderAgentReliabilityMarkdown(report));
|
|
@@ -54767,7 +54767,7 @@ __export(exports_sdk_integration_fixtures, {
|
|
|
54767
54767
|
TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT: () => TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT
|
|
54768
54768
|
});
|
|
54769
54769
|
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
54770
|
-
import { join as
|
|
54770
|
+
import { join as join22 } from "path";
|
|
54771
54771
|
function source5(version) {
|
|
54772
54772
|
return {
|
|
54773
54773
|
packageName: "@hasna/todos",
|
|
@@ -54874,7 +54874,7 @@ function writeSdkIntegrationFixtures(directory, options = {}) {
|
|
|
54874
54874
|
];
|
|
54875
54875
|
const written = [];
|
|
54876
54876
|
for (const [name, payload] of files) {
|
|
54877
|
-
const file =
|
|
54877
|
+
const file = join22(directory, name);
|
|
54878
54878
|
writeFileSync11(file, `${JSON.stringify(payload, null, 2)}
|
|
54879
54879
|
`, "utf-8");
|
|
54880
54880
|
written.push(file);
|
|
@@ -56596,7 +56596,7 @@ var init_hybrid = __esm(() => {
|
|
|
56596
56596
|
});
|
|
56597
56597
|
|
|
56598
56598
|
// src/storage/postgres-adapter.ts
|
|
56599
|
-
import { randomUUID as
|
|
56599
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
56600
56600
|
function createPostgresTodosStorageAdapter(options) {
|
|
56601
56601
|
const store = new PostgresJsonRecordStore(options);
|
|
56602
56602
|
const adapter = {
|
|
@@ -56779,7 +56779,7 @@ async function createTask3(input, store, context) {
|
|
|
56779
56779
|
const timestamp3 = new Date().toISOString();
|
|
56780
56780
|
const shortId = input.project_id ? await nextTaskShortId2(input.project_id, store, context) : null;
|
|
56781
56781
|
const task2 = {
|
|
56782
|
-
id:
|
|
56782
|
+
id: randomUUID5(),
|
|
56783
56783
|
short_id: shortId,
|
|
56784
56784
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
56785
56785
|
parent_id: input.parent_id ?? null,
|
|
@@ -56946,7 +56946,7 @@ async function getChangedSince(since, filters, store) {
|
|
|
56946
56946
|
async function createProject2(input, store, context) {
|
|
56947
56947
|
const timestamp3 = new Date().toISOString();
|
|
56948
56948
|
const project = {
|
|
56949
|
-
id:
|
|
56949
|
+
id: randomUUID5(),
|
|
56950
56950
|
name: input.name,
|
|
56951
56951
|
path: input.path,
|
|
56952
56952
|
description: input.description ?? null,
|
|
@@ -56966,7 +56966,7 @@ async function updateProject2(id, input, store) {
|
|
|
56966
56966
|
async function createPlan2(input, store, context) {
|
|
56967
56967
|
const timestamp3 = new Date().toISOString();
|
|
56968
56968
|
return store.upsert("plans", {
|
|
56969
|
-
id:
|
|
56969
|
+
id: randomUUID5(),
|
|
56970
56970
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
56971
56971
|
task_list_id: input.task_list_id ?? context?.taskListId ?? null,
|
|
56972
56972
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
@@ -56988,7 +56988,7 @@ async function registerAgent2(input, store, context) {
|
|
|
56988
56988
|
}
|
|
56989
56989
|
const timestamp3 = new Date().toISOString();
|
|
56990
56990
|
const agent = {
|
|
56991
|
-
id: existing?.id ??
|
|
56991
|
+
id: existing?.id ?? randomUUID5().slice(0, 8),
|
|
56992
56992
|
name: input.name,
|
|
56993
56993
|
description: input.description ?? existing?.description ?? null,
|
|
56994
56994
|
role: input.role ?? existing?.role ?? null,
|
|
@@ -57024,7 +57024,7 @@ async function updateAgent2(id, input, store) {
|
|
|
57024
57024
|
async function createTaskList2(input, store, context) {
|
|
57025
57025
|
const timestamp3 = new Date().toISOString();
|
|
57026
57026
|
return store.upsert("task_lists", {
|
|
57027
|
-
id:
|
|
57027
|
+
id: randomUUID5(),
|
|
57028
57028
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
57029
57029
|
slug: input.slug ?? slugify2(input.name),
|
|
57030
57030
|
name: input.name,
|
|
@@ -57046,7 +57046,7 @@ async function updateTaskList2(id, input, store) {
|
|
|
57046
57046
|
async function createTemplate2(input, store, context) {
|
|
57047
57047
|
const timestamp3 = new Date().toISOString();
|
|
57048
57048
|
return store.upsert("templates", {
|
|
57049
|
-
id:
|
|
57049
|
+
id: randomUUID5(),
|
|
57050
57050
|
name: input.name,
|
|
57051
57051
|
title_pattern: input.title_pattern,
|
|
57052
57052
|
description: input.description ?? null,
|
|
@@ -57075,7 +57075,7 @@ async function updateTemplate2(id, input, store) {
|
|
|
57075
57075
|
}
|
|
57076
57076
|
async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentId, store, context) {
|
|
57077
57077
|
const entry2 = {
|
|
57078
|
-
id:
|
|
57078
|
+
id: randomUUID5(),
|
|
57079
57079
|
task_id: taskId,
|
|
57080
57080
|
action,
|
|
57081
57081
|
field: field2 ?? null,
|
|
@@ -57088,7 +57088,7 @@ async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentI
|
|
|
57088
57088
|
}
|
|
57089
57089
|
async function addComment2(input, store, context) {
|
|
57090
57090
|
const comment = {
|
|
57091
|
-
id:
|
|
57091
|
+
id: randomUUID5(),
|
|
57092
57092
|
task_id: input.task_id,
|
|
57093
57093
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
57094
57094
|
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
@@ -57271,10 +57271,10 @@ var init_factory = __esm(() => {
|
|
|
57271
57271
|
});
|
|
57272
57272
|
|
|
57273
57273
|
// src/storage/s3-artifacts.ts
|
|
57274
|
-
import { createHash as createHash13, createHmac } from "crypto";
|
|
57274
|
+
import { createHash as createHash13, createHmac as createHmac2 } from "crypto";
|
|
57275
57275
|
function createTodosS3ArtifactStore(options) {
|
|
57276
57276
|
const requestFetch = options.fetch ?? fetch;
|
|
57277
|
-
const
|
|
57277
|
+
const now4 = options.now ?? (() => new Date);
|
|
57278
57278
|
return {
|
|
57279
57279
|
objectKey: (relativePath) => buildS3ObjectKey(options.config, relativePath),
|
|
57280
57280
|
objectUrl: (relativePath) => buildS3ObjectUrl(options.config, buildS3ObjectKey(options.config, relativePath)),
|
|
@@ -57296,7 +57296,7 @@ function createTodosS3ArtifactStore(options) {
|
|
|
57296
57296
|
headers,
|
|
57297
57297
|
body,
|
|
57298
57298
|
credentials: options.credentials,
|
|
57299
|
-
now:
|
|
57299
|
+
now: now4()
|
|
57300
57300
|
});
|
|
57301
57301
|
const response = await requestFetch(url, { method: "PUT", headers: signed.headers, body });
|
|
57302
57302
|
if (!response.ok)
|
|
@@ -57317,7 +57317,7 @@ function createTodosS3ArtifactStore(options) {
|
|
|
57317
57317
|
service: "s3",
|
|
57318
57318
|
headers: {},
|
|
57319
57319
|
credentials: options.credentials,
|
|
57320
|
-
now:
|
|
57320
|
+
now: now4()
|
|
57321
57321
|
});
|
|
57322
57322
|
const response = await requestFetch(url, { method: "GET", headers: signed.headers });
|
|
57323
57323
|
if (!response.ok)
|
|
@@ -57333,7 +57333,7 @@ function createTodosS3ArtifactStore(options) {
|
|
|
57333
57333
|
service: "s3",
|
|
57334
57334
|
headers: {},
|
|
57335
57335
|
credentials: options.credentials,
|
|
57336
|
-
now:
|
|
57336
|
+
now: now4()
|
|
57337
57337
|
});
|
|
57338
57338
|
const response = await requestFetch(url, { method: "DELETE", headers: signed.headers });
|
|
57339
57339
|
if (!response.ok && response.status !== 404)
|
|
@@ -57446,10 +57446,10 @@ function sha256Hex(value) {
|
|
|
57446
57446
|
return createHash13("sha256").update(value).digest("hex");
|
|
57447
57447
|
}
|
|
57448
57448
|
function hmac(key, value) {
|
|
57449
|
-
return
|
|
57449
|
+
return createHmac2("sha256", key).update(value).digest();
|
|
57450
57450
|
}
|
|
57451
57451
|
function hmacHex(key, value) {
|
|
57452
|
-
return
|
|
57452
|
+
return createHmac2("sha256", key).update(value).digest("hex");
|
|
57453
57453
|
}
|
|
57454
57454
|
function getSigningKey(secretAccessKey, dateStamp, region, service) {
|
|
57455
57455
|
const dateKey = hmac(`AWS4${secretAccessKey}`, dateStamp);
|
|
@@ -57462,7 +57462,7 @@ var init_s3_artifacts = () => {};
|
|
|
57462
57462
|
// src/storage/s3-artifact-sync.ts
|
|
57463
57463
|
async function uploadRunArtifactsToS3(options) {
|
|
57464
57464
|
const db = options.db ?? getDatabase();
|
|
57465
|
-
const
|
|
57465
|
+
const now4 = options.now ?? (() => new Date);
|
|
57466
57466
|
const result = emptyResult();
|
|
57467
57467
|
for (const artifact of listRunArtifacts(db, options.filter)) {
|
|
57468
57468
|
try {
|
|
@@ -57501,7 +57501,7 @@ async function uploadRunArtifactsToS3(options) {
|
|
|
57501
57501
|
url: ref.url,
|
|
57502
57502
|
sha256: content.sha256,
|
|
57503
57503
|
size_bytes: content.size_bytes,
|
|
57504
|
-
uploaded_at:
|
|
57504
|
+
uploaded_at: now4().toISOString()
|
|
57505
57505
|
};
|
|
57506
57506
|
updateArtifactMetadata(db, artifact.id, {
|
|
57507
57507
|
...metadata,
|
|
@@ -57576,7 +57576,7 @@ function planRunArtifactsS3Sync(options) {
|
|
|
57576
57576
|
}
|
|
57577
57577
|
async function downloadRunArtifactsFromS3(options) {
|
|
57578
57578
|
const db = options.db ?? getDatabase();
|
|
57579
|
-
const
|
|
57579
|
+
const now4 = options.now ?? (() => new Date);
|
|
57580
57580
|
const result = emptyResult();
|
|
57581
57581
|
for (const artifact of listRunArtifacts(db, options.filter)) {
|
|
57582
57582
|
try {
|
|
@@ -57612,7 +57612,7 @@ async function downloadRunArtifactsFromS3(options) {
|
|
|
57612
57612
|
...metadata,
|
|
57613
57613
|
remote_artifact_store: {
|
|
57614
57614
|
...remote,
|
|
57615
|
-
downloaded_at:
|
|
57615
|
+
downloaded_at: now4().toISOString()
|
|
57616
57616
|
}
|
|
57617
57617
|
});
|
|
57618
57618
|
result.downloaded += 1;
|
|
@@ -58675,6 +58675,686 @@ var init_help_commands = __esm(() => {
|
|
|
58675
58675
|
|
|
58676
58676
|
// src/cli/index.tsx
|
|
58677
58677
|
init_esm();
|
|
58678
|
+
|
|
58679
|
+
// node_modules/.bun/@hasna+events@0.1.3/node_modules/@hasna/events/dist/commander.js
|
|
58680
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
58681
|
+
import { existsSync } from "fs";
|
|
58682
|
+
import { homedir } from "os";
|
|
58683
|
+
import { join } from "path";
|
|
58684
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
58685
|
+
import { randomUUID } from "crypto";
|
|
58686
|
+
import { spawn } from "child_process";
|
|
58687
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
58688
|
+
function getPathValue(input, path) {
|
|
58689
|
+
return path.split(".").reduce((value, part) => {
|
|
58690
|
+
if (value && typeof value === "object" && part in value) {
|
|
58691
|
+
return value[part];
|
|
58692
|
+
}
|
|
58693
|
+
return;
|
|
58694
|
+
}, input);
|
|
58695
|
+
}
|
|
58696
|
+
function wildcardToRegExp(pattern) {
|
|
58697
|
+
const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
|
|
58698
|
+
return new RegExp(`^${escaped}$`);
|
|
58699
|
+
}
|
|
58700
|
+
function matchString(value, matcher) {
|
|
58701
|
+
if (matcher === undefined)
|
|
58702
|
+
return true;
|
|
58703
|
+
if (value === undefined)
|
|
58704
|
+
return false;
|
|
58705
|
+
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
58706
|
+
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
58707
|
+
}
|
|
58708
|
+
function matchRecord(input, matcher) {
|
|
58709
|
+
if (!matcher)
|
|
58710
|
+
return true;
|
|
58711
|
+
return Object.entries(matcher).every(([path, expected]) => {
|
|
58712
|
+
const actual = getPathValue(input, path);
|
|
58713
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
58714
|
+
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
58715
|
+
}
|
|
58716
|
+
return actual === expected;
|
|
58717
|
+
});
|
|
58718
|
+
}
|
|
58719
|
+
function eventMatchesFilter(event, filter) {
|
|
58720
|
+
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
58721
|
+
}
|
|
58722
|
+
function channelMatchesEvent(channel, event) {
|
|
58723
|
+
if (!channel.enabled)
|
|
58724
|
+
return false;
|
|
58725
|
+
if (!channel.filters || channel.filters.length === 0)
|
|
58726
|
+
return true;
|
|
58727
|
+
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
58728
|
+
}
|
|
58729
|
+
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
58730
|
+
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
58731
|
+
function getEventsDataDir(override) {
|
|
58732
|
+
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
58733
|
+
}
|
|
58734
|
+
|
|
58735
|
+
class JsonEventsStore {
|
|
58736
|
+
dataDir;
|
|
58737
|
+
channelsPath;
|
|
58738
|
+
eventsPath;
|
|
58739
|
+
deliveriesPath;
|
|
58740
|
+
constructor(dataDir = getEventsDataDir()) {
|
|
58741
|
+
this.dataDir = dataDir;
|
|
58742
|
+
this.channelsPath = join(dataDir, "channels.json");
|
|
58743
|
+
this.eventsPath = join(dataDir, "events.json");
|
|
58744
|
+
this.deliveriesPath = join(dataDir, "deliveries.json");
|
|
58745
|
+
}
|
|
58746
|
+
async init() {
|
|
58747
|
+
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
58748
|
+
await chmod(this.dataDir, 448).catch(() => {
|
|
58749
|
+
return;
|
|
58750
|
+
});
|
|
58751
|
+
await this.ensureArrayFile(this.channelsPath);
|
|
58752
|
+
await this.ensureArrayFile(this.eventsPath);
|
|
58753
|
+
await this.ensureArrayFile(this.deliveriesPath);
|
|
58754
|
+
}
|
|
58755
|
+
async addChannel(channel) {
|
|
58756
|
+
await this.init();
|
|
58757
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
58758
|
+
const index = channels.findIndex((item) => item.id === channel.id);
|
|
58759
|
+
if (index >= 0) {
|
|
58760
|
+
channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
|
|
58761
|
+
} else {
|
|
58762
|
+
channels.push(channel);
|
|
58763
|
+
}
|
|
58764
|
+
await this.writeJson(this.channelsPath, channels);
|
|
58765
|
+
return index >= 0 ? channels[index] : channel;
|
|
58766
|
+
}
|
|
58767
|
+
async listChannels() {
|
|
58768
|
+
await this.init();
|
|
58769
|
+
return this.readJson(this.channelsPath, []);
|
|
58770
|
+
}
|
|
58771
|
+
async getChannel(id) {
|
|
58772
|
+
const channels = await this.listChannels();
|
|
58773
|
+
return channels.find((channel) => channel.id === id);
|
|
58774
|
+
}
|
|
58775
|
+
async removeChannel(id) {
|
|
58776
|
+
await this.init();
|
|
58777
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
58778
|
+
const next = channels.filter((channel) => channel.id !== id);
|
|
58779
|
+
await this.writeJson(this.channelsPath, next);
|
|
58780
|
+
return next.length !== channels.length;
|
|
58781
|
+
}
|
|
58782
|
+
async appendEvent(event) {
|
|
58783
|
+
await this.init();
|
|
58784
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
58785
|
+
events.push(event);
|
|
58786
|
+
await this.writeJson(this.eventsPath, events);
|
|
58787
|
+
return event;
|
|
58788
|
+
}
|
|
58789
|
+
async listEvents() {
|
|
58790
|
+
await this.init();
|
|
58791
|
+
return this.readJson(this.eventsPath, []);
|
|
58792
|
+
}
|
|
58793
|
+
async findEventByIdentity(identity) {
|
|
58794
|
+
const events = await this.listEvents();
|
|
58795
|
+
return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
|
|
58796
|
+
}
|
|
58797
|
+
async appendDelivery(result) {
|
|
58798
|
+
await this.init();
|
|
58799
|
+
const deliveries = await this.readJson(this.deliveriesPath, []);
|
|
58800
|
+
deliveries.push(result);
|
|
58801
|
+
await this.writeJson(this.deliveriesPath, deliveries);
|
|
58802
|
+
return result;
|
|
58803
|
+
}
|
|
58804
|
+
async listDeliveries() {
|
|
58805
|
+
await this.init();
|
|
58806
|
+
return this.readJson(this.deliveriesPath, []);
|
|
58807
|
+
}
|
|
58808
|
+
async exportData() {
|
|
58809
|
+
return {
|
|
58810
|
+
channels: await this.listChannels(),
|
|
58811
|
+
events: await this.listEvents(),
|
|
58812
|
+
deliveries: await this.listDeliveries()
|
|
58813
|
+
};
|
|
58814
|
+
}
|
|
58815
|
+
async ensureArrayFile(path) {
|
|
58816
|
+
if (!existsSync(path)) {
|
|
58817
|
+
await writeFile(path, `[]
|
|
58818
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
58819
|
+
}
|
|
58820
|
+
await chmod(path, 384).catch(() => {
|
|
58821
|
+
return;
|
|
58822
|
+
});
|
|
58823
|
+
}
|
|
58824
|
+
async readJson(path, fallback) {
|
|
58825
|
+
try {
|
|
58826
|
+
const raw = await readFile(path, "utf-8");
|
|
58827
|
+
if (!raw.trim())
|
|
58828
|
+
return fallback;
|
|
58829
|
+
return JSON.parse(raw);
|
|
58830
|
+
} catch (error) {
|
|
58831
|
+
if (error.code === "ENOENT")
|
|
58832
|
+
return fallback;
|
|
58833
|
+
throw error;
|
|
58834
|
+
}
|
|
58835
|
+
}
|
|
58836
|
+
async writeJson(path, value) {
|
|
58837
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
58838
|
+
await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
|
|
58839
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
58840
|
+
await rename(tempPath, path);
|
|
58841
|
+
await chmod(path, 384).catch(() => {
|
|
58842
|
+
return;
|
|
58843
|
+
});
|
|
58844
|
+
}
|
|
58845
|
+
}
|
|
58846
|
+
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
58847
|
+
function buildSignatureBase(timestamp, body) {
|
|
58848
|
+
return `${timestamp}.${body}`;
|
|
58849
|
+
}
|
|
58850
|
+
function signPayload(secret, timestamp, body) {
|
|
58851
|
+
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
58852
|
+
return `sha256=${digest}`;
|
|
58853
|
+
}
|
|
58854
|
+
function now() {
|
|
58855
|
+
return new Date().toISOString();
|
|
58856
|
+
}
|
|
58857
|
+
function truncate(value, max = 4096) {
|
|
58858
|
+
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
58859
|
+
}
|
|
58860
|
+
function buildWebhookRequest(event, channel) {
|
|
58861
|
+
if (!channel.webhook)
|
|
58862
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
58863
|
+
const body = JSON.stringify(event);
|
|
58864
|
+
const timestamp = event.time;
|
|
58865
|
+
const headers = {
|
|
58866
|
+
"Content-Type": "application/json",
|
|
58867
|
+
"User-Agent": "@hasna/events",
|
|
58868
|
+
"X-Hasna-Event-Id": event.id,
|
|
58869
|
+
"X-Hasna-Event-Type": event.type,
|
|
58870
|
+
"X-Hasna-Timestamp": timestamp,
|
|
58871
|
+
...channel.webhook.headers
|
|
58872
|
+
};
|
|
58873
|
+
if (channel.webhook.secret) {
|
|
58874
|
+
headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
|
|
58875
|
+
}
|
|
58876
|
+
return { body, headers };
|
|
58877
|
+
}
|
|
58878
|
+
async function dispatchWebhook(event, channel, options = {}) {
|
|
58879
|
+
if (!channel.webhook)
|
|
58880
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
58881
|
+
const startedAt = now();
|
|
58882
|
+
const { body, headers } = buildWebhookRequest(event, channel);
|
|
58883
|
+
const controller = new AbortController;
|
|
58884
|
+
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
58885
|
+
try {
|
|
58886
|
+
const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
|
|
58887
|
+
method: "POST",
|
|
58888
|
+
headers,
|
|
58889
|
+
body,
|
|
58890
|
+
signal: controller.signal
|
|
58891
|
+
});
|
|
58892
|
+
const responseBody = truncate(await response.text());
|
|
58893
|
+
return {
|
|
58894
|
+
attempt: 1,
|
|
58895
|
+
status: response.ok ? "success" : "failed",
|
|
58896
|
+
startedAt,
|
|
58897
|
+
completedAt: now(),
|
|
58898
|
+
responseStatus: response.status,
|
|
58899
|
+
responseBody,
|
|
58900
|
+
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
58901
|
+
};
|
|
58902
|
+
} catch (error) {
|
|
58903
|
+
return {
|
|
58904
|
+
attempt: 1,
|
|
58905
|
+
status: "failed",
|
|
58906
|
+
startedAt,
|
|
58907
|
+
completedAt: now(),
|
|
58908
|
+
error: error instanceof Error ? error.message : String(error)
|
|
58909
|
+
};
|
|
58910
|
+
} finally {
|
|
58911
|
+
clearTimeout(timeout);
|
|
58912
|
+
}
|
|
58913
|
+
}
|
|
58914
|
+
async function dispatchCommand(event, channel) {
|
|
58915
|
+
if (!channel.command)
|
|
58916
|
+
throw new Error(`Channel ${channel.id} has no command config`);
|
|
58917
|
+
const startedAt = now();
|
|
58918
|
+
const eventJson = JSON.stringify(event);
|
|
58919
|
+
const env = {
|
|
58920
|
+
...process.env,
|
|
58921
|
+
...channel.command.env,
|
|
58922
|
+
HASNA_CHANNEL_ID: channel.id,
|
|
58923
|
+
HASNA_EVENT_ID: event.id,
|
|
58924
|
+
HASNA_EVENT_TYPE: event.type,
|
|
58925
|
+
HASNA_EVENT_SOURCE: event.source,
|
|
58926
|
+
HASNA_EVENT_SUBJECT: event.subject ?? "",
|
|
58927
|
+
HASNA_EVENT_SEVERITY: event.severity,
|
|
58928
|
+
HASNA_EVENT_TIME: event.time,
|
|
58929
|
+
HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
|
|
58930
|
+
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
58931
|
+
HASNA_EVENT_JSON: eventJson
|
|
58932
|
+
};
|
|
58933
|
+
return new Promise((resolve) => {
|
|
58934
|
+
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
58935
|
+
cwd: channel.command.cwd,
|
|
58936
|
+
env,
|
|
58937
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
58938
|
+
});
|
|
58939
|
+
let stdout = "";
|
|
58940
|
+
let stderr = "";
|
|
58941
|
+
const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
|
|
58942
|
+
child.stdin.end(eventJson);
|
|
58943
|
+
child.stdout.on("data", (chunk) => {
|
|
58944
|
+
stdout += chunk.toString();
|
|
58945
|
+
});
|
|
58946
|
+
child.stderr.on("data", (chunk) => {
|
|
58947
|
+
stderr += chunk.toString();
|
|
58948
|
+
});
|
|
58949
|
+
child.on("error", (error) => {
|
|
58950
|
+
clearTimeout(timeout);
|
|
58951
|
+
resolve({
|
|
58952
|
+
attempt: 1,
|
|
58953
|
+
status: "failed",
|
|
58954
|
+
startedAt,
|
|
58955
|
+
completedAt: now(),
|
|
58956
|
+
stdout: truncate(stdout),
|
|
58957
|
+
stderr: truncate(stderr),
|
|
58958
|
+
error: error.message
|
|
58959
|
+
});
|
|
58960
|
+
});
|
|
58961
|
+
child.on("close", (code, signal) => {
|
|
58962
|
+
clearTimeout(timeout);
|
|
58963
|
+
const success = code === 0;
|
|
58964
|
+
resolve({
|
|
58965
|
+
attempt: 1,
|
|
58966
|
+
status: success ? "success" : "failed",
|
|
58967
|
+
startedAt,
|
|
58968
|
+
completedAt: now(),
|
|
58969
|
+
stdout: truncate(stdout),
|
|
58970
|
+
stderr: truncate(stderr),
|
|
58971
|
+
error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
58972
|
+
});
|
|
58973
|
+
});
|
|
58974
|
+
});
|
|
58975
|
+
}
|
|
58976
|
+
async function dispatchChannel(event, channel, options = {}) {
|
|
58977
|
+
if (channel.transport === "webhook")
|
|
58978
|
+
return dispatchWebhook(event, channel, options);
|
|
58979
|
+
if (channel.transport === "command")
|
|
58980
|
+
return dispatchCommand(event, channel);
|
|
58981
|
+
return {
|
|
58982
|
+
attempt: 1,
|
|
58983
|
+
status: "skipped",
|
|
58984
|
+
startedAt: now(),
|
|
58985
|
+
completedAt: now(),
|
|
58986
|
+
error: `Unsupported transport: ${channel.transport}`
|
|
58987
|
+
};
|
|
58988
|
+
}
|
|
58989
|
+
function createDeliveryResult(event, channel, attempts) {
|
|
58990
|
+
const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
|
|
58991
|
+
return {
|
|
58992
|
+
id: randomUUID(),
|
|
58993
|
+
eventId: event.id,
|
|
58994
|
+
channelId: channel.id,
|
|
58995
|
+
transport: channel.transport,
|
|
58996
|
+
status,
|
|
58997
|
+
attempts,
|
|
58998
|
+
createdAt: attempts[0]?.startedAt ?? now(),
|
|
58999
|
+
completedAt: attempts.at(-1)?.completedAt ?? now()
|
|
59000
|
+
};
|
|
59001
|
+
}
|
|
59002
|
+
function createEvent(input) {
|
|
59003
|
+
return {
|
|
59004
|
+
id: input.id ?? randomUUID2(),
|
|
59005
|
+
source: input.source,
|
|
59006
|
+
type: input.type,
|
|
59007
|
+
time: normalizeTime(input.time),
|
|
59008
|
+
subject: input.subject,
|
|
59009
|
+
severity: input.severity ?? "info",
|
|
59010
|
+
data: input.data ?? {},
|
|
59011
|
+
message: input.message,
|
|
59012
|
+
dedupeKey: input.dedupeKey,
|
|
59013
|
+
schemaVersion: input.schemaVersion ?? "1.0",
|
|
59014
|
+
metadata: input.metadata ?? {}
|
|
59015
|
+
};
|
|
59016
|
+
}
|
|
59017
|
+
|
|
59018
|
+
class EventsClient {
|
|
59019
|
+
store;
|
|
59020
|
+
redactors;
|
|
59021
|
+
transportOptions;
|
|
59022
|
+
constructor(options = {}) {
|
|
59023
|
+
this.store = options.store ?? new JsonEventsStore(options.dataDir);
|
|
59024
|
+
this.redactors = options.redactors ?? [];
|
|
59025
|
+
this.transportOptions = { fetchImpl: options.fetchImpl };
|
|
59026
|
+
}
|
|
59027
|
+
async addChannel(input) {
|
|
59028
|
+
const timestamp = new Date().toISOString();
|
|
59029
|
+
return this.store.addChannel({
|
|
59030
|
+
...input,
|
|
59031
|
+
createdAt: input.createdAt ?? timestamp,
|
|
59032
|
+
updatedAt: input.updatedAt ?? timestamp
|
|
59033
|
+
});
|
|
59034
|
+
}
|
|
59035
|
+
async listChannels() {
|
|
59036
|
+
return this.store.listChannels();
|
|
59037
|
+
}
|
|
59038
|
+
async removeChannel(id) {
|
|
59039
|
+
return this.store.removeChannel(id);
|
|
59040
|
+
}
|
|
59041
|
+
async emit(input, options = {}) {
|
|
59042
|
+
const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
|
|
59043
|
+
if (options.dedupe !== false) {
|
|
59044
|
+
const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
|
|
59045
|
+
if (existing) {
|
|
59046
|
+
return { event: existing, deliveries: [], deduped: true };
|
|
59047
|
+
}
|
|
59048
|
+
}
|
|
59049
|
+
await this.store.appendEvent(event);
|
|
59050
|
+
const deliveries = options.deliver === false ? [] : await this.deliver(event);
|
|
59051
|
+
return { event, deliveries, deduped: false };
|
|
59052
|
+
}
|
|
59053
|
+
async listEvents() {
|
|
59054
|
+
return this.store.listEvents();
|
|
59055
|
+
}
|
|
59056
|
+
async listDeliveries() {
|
|
59057
|
+
return this.store.listDeliveries();
|
|
59058
|
+
}
|
|
59059
|
+
async deliver(event) {
|
|
59060
|
+
const channels = await this.store.listChannels();
|
|
59061
|
+
const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
|
|
59062
|
+
const deliveries = [];
|
|
59063
|
+
for (const channel of selected) {
|
|
59064
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
59065
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
59066
|
+
await this.store.appendDelivery(result);
|
|
59067
|
+
deliveries.push(result);
|
|
59068
|
+
}
|
|
59069
|
+
return deliveries;
|
|
59070
|
+
}
|
|
59071
|
+
async testChannel(id, input = {}) {
|
|
59072
|
+
const channel = await this.store.getChannel(id);
|
|
59073
|
+
if (!channel)
|
|
59074
|
+
throw new Error(`Channel not found: ${id}`);
|
|
59075
|
+
const event = createEvent({
|
|
59076
|
+
source: input.source ?? "hasna.events",
|
|
59077
|
+
type: input.type ?? "events.test",
|
|
59078
|
+
subject: input.subject ?? id,
|
|
59079
|
+
severity: input.severity ?? "info",
|
|
59080
|
+
data: input.data ?? { test: true },
|
|
59081
|
+
message: input.message ?? "Hasna events test delivery",
|
|
59082
|
+
dedupeKey: input.dedupeKey,
|
|
59083
|
+
schemaVersion: input.schemaVersion,
|
|
59084
|
+
metadata: input.metadata,
|
|
59085
|
+
time: input.time,
|
|
59086
|
+
id: input.id
|
|
59087
|
+
});
|
|
59088
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
59089
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
59090
|
+
await this.store.appendDelivery(result);
|
|
59091
|
+
return result;
|
|
59092
|
+
}
|
|
59093
|
+
async replay(options = {}) {
|
|
59094
|
+
const events = (await this.store.listEvents()).filter((event) => {
|
|
59095
|
+
if (options.eventId && event.id !== options.eventId)
|
|
59096
|
+
return false;
|
|
59097
|
+
if (options.source && event.source !== options.source)
|
|
59098
|
+
return false;
|
|
59099
|
+
if (options.type && event.type !== options.type)
|
|
59100
|
+
return false;
|
|
59101
|
+
return true;
|
|
59102
|
+
});
|
|
59103
|
+
if (options.dryRun)
|
|
59104
|
+
return { events, deliveries: [] };
|
|
59105
|
+
const deliveries = [];
|
|
59106
|
+
for (const event of events) {
|
|
59107
|
+
deliveries.push(...await this.deliver(event));
|
|
59108
|
+
}
|
|
59109
|
+
return { events, deliveries };
|
|
59110
|
+
}
|
|
59111
|
+
async applyRedaction(event, channel) {
|
|
59112
|
+
let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
|
|
59113
|
+
for (const redactor of this.redactors) {
|
|
59114
|
+
next = await redactor(next, channel);
|
|
59115
|
+
}
|
|
59116
|
+
return next;
|
|
59117
|
+
}
|
|
59118
|
+
async deliverWithRetry(event, channel) {
|
|
59119
|
+
const policy = normalizeRetryPolicy(channel.retry);
|
|
59120
|
+
const attempts = [];
|
|
59121
|
+
for (let index = 0;index < policy.maxAttempts; index += 1) {
|
|
59122
|
+
const attempt = await dispatchChannel(event, channel, this.transportOptions);
|
|
59123
|
+
attempt.attempt = index + 1;
|
|
59124
|
+
if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
|
|
59125
|
+
attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
|
|
59126
|
+
}
|
|
59127
|
+
attempts.push(attempt);
|
|
59128
|
+
if (attempt.status !== "failed")
|
|
59129
|
+
break;
|
|
59130
|
+
if (attempt.nextBackoffMs)
|
|
59131
|
+
await Bun.sleep(attempt.nextBackoffMs);
|
|
59132
|
+
}
|
|
59133
|
+
return createDeliveryResult(event, channel, attempts);
|
|
59134
|
+
}
|
|
59135
|
+
}
|
|
59136
|
+
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
59137
|
+
if (paths.length === 0)
|
|
59138
|
+
return event;
|
|
59139
|
+
const copy = structuredClone(event);
|
|
59140
|
+
for (const path of paths) {
|
|
59141
|
+
setPath(copy, path, replacement);
|
|
59142
|
+
}
|
|
59143
|
+
return copy;
|
|
59144
|
+
}
|
|
59145
|
+
function sanitizeChannelForOutput(channel) {
|
|
59146
|
+
const copy = structuredClone(channel);
|
|
59147
|
+
if (copy.webhook?.secret)
|
|
59148
|
+
copy.webhook.secret = "[REDACTED]";
|
|
59149
|
+
if (copy.command?.env) {
|
|
59150
|
+
copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
|
|
59151
|
+
}
|
|
59152
|
+
return copy;
|
|
59153
|
+
}
|
|
59154
|
+
function sanitizeChannelsForOutput(channels) {
|
|
59155
|
+
return channels.map(sanitizeChannelForOutput);
|
|
59156
|
+
}
|
|
59157
|
+
function redactSensitiveKeys(event, replacement = "[REDACTED]") {
|
|
59158
|
+
return redactValue(event, replacement);
|
|
59159
|
+
}
|
|
59160
|
+
function shouldRedactKey(key) {
|
|
59161
|
+
return /secret|token|password|api[_-]?key|authorization/i.test(key);
|
|
59162
|
+
}
|
|
59163
|
+
function redactValue(value, replacement) {
|
|
59164
|
+
if (Array.isArray(value))
|
|
59165
|
+
return value.map((item) => redactValue(item, replacement));
|
|
59166
|
+
if (!value || typeof value !== "object")
|
|
59167
|
+
return value;
|
|
59168
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
59169
|
+
key,
|
|
59170
|
+
shouldRedactKey(key) ? replacement : redactValue(item, replacement)
|
|
59171
|
+
]));
|
|
59172
|
+
}
|
|
59173
|
+
function setPath(input, path, replacement) {
|
|
59174
|
+
const parts = path.split(".");
|
|
59175
|
+
let cursor = input;
|
|
59176
|
+
for (const part of parts.slice(0, -1)) {
|
|
59177
|
+
const next = cursor[part];
|
|
59178
|
+
if (!next || typeof next !== "object")
|
|
59179
|
+
return;
|
|
59180
|
+
cursor = next;
|
|
59181
|
+
}
|
|
59182
|
+
const last = parts.at(-1);
|
|
59183
|
+
if (last && last in cursor)
|
|
59184
|
+
cursor[last] = replacement;
|
|
59185
|
+
}
|
|
59186
|
+
function normalizeTime(value) {
|
|
59187
|
+
if (!value)
|
|
59188
|
+
return new Date().toISOString();
|
|
59189
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
59190
|
+
}
|
|
59191
|
+
function normalizeRetryPolicy(policy) {
|
|
59192
|
+
return {
|
|
59193
|
+
maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
|
|
59194
|
+
backoffMs: Math.max(0, policy?.backoffMs ?? 250),
|
|
59195
|
+
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
59196
|
+
};
|
|
59197
|
+
}
|
|
59198
|
+
function parseJsonObject(value, fallback) {
|
|
59199
|
+
if (!value)
|
|
59200
|
+
return fallback;
|
|
59201
|
+
const parsed = JSON.parse(value);
|
|
59202
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
59203
|
+
throw new Error("Expected a JSON object");
|
|
59204
|
+
}
|
|
59205
|
+
return parsed;
|
|
59206
|
+
}
|
|
59207
|
+
function parseHeaders(values) {
|
|
59208
|
+
if (!values?.length)
|
|
59209
|
+
return;
|
|
59210
|
+
const headers = {};
|
|
59211
|
+
for (const value of values) {
|
|
59212
|
+
const separator = value.indexOf("=");
|
|
59213
|
+
if (separator === -1)
|
|
59214
|
+
throw new Error(`Invalid header, expected name=value: ${value}`);
|
|
59215
|
+
headers[value.slice(0, separator)] = value.slice(separator + 1);
|
|
59216
|
+
}
|
|
59217
|
+
return headers;
|
|
59218
|
+
}
|
|
59219
|
+
function parseFilter(options) {
|
|
59220
|
+
const filter2 = {};
|
|
59221
|
+
if (options.source)
|
|
59222
|
+
filter2.source = options.source;
|
|
59223
|
+
if (options.type)
|
|
59224
|
+
filter2.type = options.type;
|
|
59225
|
+
if (options.subject)
|
|
59226
|
+
filter2.subject = options.subject;
|
|
59227
|
+
if (options.severity)
|
|
59228
|
+
filter2.severity = options.severity;
|
|
59229
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
59230
|
+
}
|
|
59231
|
+
function createClient(options) {
|
|
59232
|
+
if (options.createClient)
|
|
59233
|
+
return options.createClient();
|
|
59234
|
+
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
59235
|
+
}
|
|
59236
|
+
function print(value, json, text) {
|
|
59237
|
+
if (json)
|
|
59238
|
+
console.log(JSON.stringify(value, null, 2));
|
|
59239
|
+
else
|
|
59240
|
+
console.log(text);
|
|
59241
|
+
}
|
|
59242
|
+
function registerWebhookCommands(program2, options) {
|
|
59243
|
+
const webhooks = program2.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
59244
|
+
webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions) => {
|
|
59245
|
+
const timestamp = new Date().toISOString();
|
|
59246
|
+
const channel = {
|
|
59247
|
+
id: actionOptions.id,
|
|
59248
|
+
name: actionOptions.name,
|
|
59249
|
+
enabled: !actionOptions.disabled,
|
|
59250
|
+
transport: actionOptions.transport,
|
|
59251
|
+
filters: parseFilter(actionOptions),
|
|
59252
|
+
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
59253
|
+
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
59254
|
+
createdAt: timestamp,
|
|
59255
|
+
updatedAt: timestamp
|
|
59256
|
+
};
|
|
59257
|
+
if (actionOptions.transport === "webhook") {
|
|
59258
|
+
channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
|
|
59259
|
+
} else if (actionOptions.transport === "command") {
|
|
59260
|
+
channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
|
|
59261
|
+
} else {
|
|
59262
|
+
throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
|
|
59263
|
+
}
|
|
59264
|
+
const saved = await createClient(options).addChannel(channel);
|
|
59265
|
+
print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
|
|
59266
|
+
});
|
|
59267
|
+
webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
59268
|
+
const channels = await createClient(options).listChannels();
|
|
59269
|
+
if (actionOptions.json) {
|
|
59270
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
|
|
59271
|
+
return;
|
|
59272
|
+
}
|
|
59273
|
+
if (!channels.length) {
|
|
59274
|
+
console.log("No channels configured.");
|
|
59275
|
+
return;
|
|
59276
|
+
}
|
|
59277
|
+
for (const channel of channels) {
|
|
59278
|
+
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
59279
|
+
}
|
|
59280
|
+
});
|
|
59281
|
+
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
59282
|
+
const removed = await createClient(options).removeChannel(id);
|
|
59283
|
+
print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
59284
|
+
});
|
|
59285
|
+
webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
59286
|
+
const result = await createClient(options).testChannel(id, {
|
|
59287
|
+
source: options.source,
|
|
59288
|
+
type: actionOptions.type,
|
|
59289
|
+
subject: actionOptions.subject ?? id,
|
|
59290
|
+
message: actionOptions.message,
|
|
59291
|
+
data: parseJsonObject(actionOptions.data, { test: true })
|
|
59292
|
+
});
|
|
59293
|
+
print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
|
|
59294
|
+
});
|
|
59295
|
+
return webhooks;
|
|
59296
|
+
}
|
|
59297
|
+
function registerEventCommands(program2, options) {
|
|
59298
|
+
const events = program2.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
59299
|
+
events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Print JSON output", false).action(async (type, actionOptions) => {
|
|
59300
|
+
const result = await createClient(options).emit({
|
|
59301
|
+
source: actionOptions.source ?? options.source,
|
|
59302
|
+
type,
|
|
59303
|
+
subject: actionOptions.subject,
|
|
59304
|
+
severity: actionOptions.severity,
|
|
59305
|
+
message: actionOptions.message,
|
|
59306
|
+
dedupeKey: actionOptions.dedupeKey,
|
|
59307
|
+
data: parseJsonObject(actionOptions.data, {}),
|
|
59308
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
59309
|
+
}, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
|
|
59310
|
+
print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
|
|
59311
|
+
});
|
|
59312
|
+
events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
59313
|
+
let rows = await createClient(options).listEvents();
|
|
59314
|
+
if (actionOptions.source)
|
|
59315
|
+
rows = rows.filter((event) => event.source === actionOptions.source);
|
|
59316
|
+
if (actionOptions.type)
|
|
59317
|
+
rows = rows.filter((event) => event.type === actionOptions.type);
|
|
59318
|
+
if (actionOptions.limit)
|
|
59319
|
+
rows = rows.slice(-actionOptions.limit);
|
|
59320
|
+
if (actionOptions.json) {
|
|
59321
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
59322
|
+
return;
|
|
59323
|
+
}
|
|
59324
|
+
if (!rows.length) {
|
|
59325
|
+
console.log("No events recorded.");
|
|
59326
|
+
return;
|
|
59327
|
+
}
|
|
59328
|
+
for (const event of rows)
|
|
59329
|
+
console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
|
|
59330
|
+
});
|
|
59331
|
+
events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
59332
|
+
const result = await createClient(options).replay({
|
|
59333
|
+
eventId: actionOptions.id,
|
|
59334
|
+
source: actionOptions.source,
|
|
59335
|
+
type: actionOptions.type,
|
|
59336
|
+
dryRun: actionOptions.dryRun
|
|
59337
|
+
});
|
|
59338
|
+
print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
|
|
59339
|
+
});
|
|
59340
|
+
return events;
|
|
59341
|
+
}
|
|
59342
|
+
function registerEventsCommands(program2, options) {
|
|
59343
|
+
registerWebhookCommands(program2, options);
|
|
59344
|
+
registerEventCommands(program2, options);
|
|
59345
|
+
}
|
|
59346
|
+
function parseNumber(value) {
|
|
59347
|
+
const parsed = Number(value);
|
|
59348
|
+
if (!Number.isFinite(parsed))
|
|
59349
|
+
throw new Error(`Expected a number, got ${value}`);
|
|
59350
|
+
return parsed;
|
|
59351
|
+
}
|
|
59352
|
+
function collectValues(value, previous) {
|
|
59353
|
+
previous.push(value);
|
|
59354
|
+
return previous;
|
|
59355
|
+
}
|
|
59356
|
+
|
|
59357
|
+
// src/cli/index.tsx
|
|
58678
59358
|
init_package_version();
|
|
58679
59359
|
var program2 = new Command;
|
|
58680
59360
|
program2.name("todos").description("Universal task management for AI coding agents").version(getPackageVersion()).option("--project <path>", "Project path").option("-j, --json", "Output as JSON").option("--agent <name>", "Agent name").option("--session <id>", "Session ID");
|
|
@@ -58764,5 +59444,6 @@ registerUsageLedgerCommands2(program2);
|
|
|
58764
59444
|
registerLocalBackupCommands2(program2);
|
|
58765
59445
|
registerStorageCommands2(program2);
|
|
58766
59446
|
registerScaleHardeningCommands2(program2);
|
|
59447
|
+
registerEventsCommands(program2, { source: "todos" });
|
|
58767
59448
|
registerHelpCommands2(program2);
|
|
58768
59449
|
program2.parse();
|