@hasna/todos 0.11.58 → 0.11.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/cli/commands/mcp-hooks-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1420 -187
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +502 -4
- package/dist/db/findings.d.ts +108 -0
- package/dist/db/findings.d.ts.map +1 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/task-crud.d.ts +3 -1
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-runs.d.ts +56 -0
- package/dist/db/task-runs.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +2 -2
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +870 -5
- package/dist/json-contracts.d.ts.map +1 -1
- package/dist/lib/access-profiles.d.ts.map +1 -1
- package/dist/lib/event-hooks.d.ts +1 -1
- package/dist/lib/event-hooks.d.ts.map +1 -1
- package/dist/lib/shared-events.d.ts +1 -1
- package/dist/lib/shared-events.d.ts.map +1 -1
- package/dist/mcp/index.js +984 -17
- package/dist/mcp/token-utils.d.ts.map +1 -1
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp/tools/task-resources.d.ts.map +1 -1
- package/dist/mcp.js +12 -1
- package/dist/registry.js +502 -4
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +984 -17
- package/dist/server/routes.d.ts +1 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/serve.d.ts +2 -0
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/storage.js +375 -1
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3223,6 +3223,49 @@ var init_migrations = __esm(() => {
|
|
|
3223
3223
|
CREATE INDEX IF NOT EXISTS idx_local_retrospectives_plan ON local_retrospectives(plan_id);
|
|
3224
3224
|
CREATE INDEX IF NOT EXISTS idx_local_retrospectives_agent ON local_retrospectives(agent_id);
|
|
3225
3225
|
INSERT OR IGNORE INTO _migrations (id) VALUES (61);
|
|
3226
|
+
`,
|
|
3227
|
+
`
|
|
3228
|
+
CREATE TABLE IF NOT EXISTS task_run_transactions (
|
|
3229
|
+
id TEXT PRIMARY KEY,
|
|
3230
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
3231
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
3232
|
+
key TEXT NOT NULL,
|
|
3233
|
+
loop_id TEXT,
|
|
3234
|
+
loop_run_id TEXT,
|
|
3235
|
+
metadata TEXT DEFAULT '{}',
|
|
3236
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3237
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3238
|
+
UNIQUE(task_id, key)
|
|
3239
|
+
);
|
|
3240
|
+
CREATE INDEX IF NOT EXISTS idx_task_run_transactions_task_key ON task_run_transactions(task_id, key);
|
|
3241
|
+
CREATE INDEX IF NOT EXISTS idx_task_run_transactions_key ON task_run_transactions(key);
|
|
3242
|
+
CREATE INDEX IF NOT EXISTS idx_task_run_transactions_run ON task_run_transactions(run_id);
|
|
3243
|
+
|
|
3244
|
+
CREATE TABLE IF NOT EXISTS task_findings (
|
|
3245
|
+
id TEXT PRIMARY KEY,
|
|
3246
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
3247
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
3248
|
+
fingerprint TEXT NOT NULL,
|
|
3249
|
+
title TEXT NOT NULL,
|
|
3250
|
+
severity TEXT NOT NULL DEFAULT 'medium' CHECK(severity IN ('low', 'medium', 'high', 'critical')),
|
|
3251
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'resolved', 'ignored')),
|
|
3252
|
+
source TEXT,
|
|
3253
|
+
summary TEXT,
|
|
3254
|
+
artifact_path TEXT,
|
|
3255
|
+
metadata TEXT DEFAULT '{}',
|
|
3256
|
+
first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3257
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3258
|
+
resolved_at TEXT,
|
|
3259
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3260
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3261
|
+
UNIQUE(task_id, fingerprint)
|
|
3262
|
+
);
|
|
3263
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_task ON task_findings(task_id);
|
|
3264
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_run ON task_findings(run_id);
|
|
3265
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_status ON task_findings(status);
|
|
3266
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source);
|
|
3267
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint);
|
|
3268
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (62);
|
|
3226
3269
|
`
|
|
3227
3270
|
];
|
|
3228
3271
|
});
|
|
@@ -3660,6 +3703,47 @@ function ensureSchema(db) {
|
|
|
3660
3703
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_run ON task_run_artifacts(run_id)");
|
|
3661
3704
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_task ON task_run_artifacts(task_id)");
|
|
3662
3705
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_path ON task_run_artifacts(path)");
|
|
3706
|
+
ensureTable("task_run_transactions", `
|
|
3707
|
+
CREATE TABLE task_run_transactions (
|
|
3708
|
+
id TEXT PRIMARY KEY,
|
|
3709
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
3710
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
3711
|
+
key TEXT NOT NULL,
|
|
3712
|
+
loop_id TEXT,
|
|
3713
|
+
loop_run_id TEXT,
|
|
3714
|
+
metadata TEXT DEFAULT '{}',
|
|
3715
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3716
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3717
|
+
UNIQUE(task_id, key)
|
|
3718
|
+
)`);
|
|
3719
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_transactions_task_key ON task_run_transactions(task_id, key)");
|
|
3720
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_transactions_key ON task_run_transactions(key)");
|
|
3721
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_transactions_run ON task_run_transactions(run_id)");
|
|
3722
|
+
ensureTable("task_findings", `
|
|
3723
|
+
CREATE TABLE task_findings (
|
|
3724
|
+
id TEXT PRIMARY KEY,
|
|
3725
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
3726
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
3727
|
+
fingerprint TEXT NOT NULL,
|
|
3728
|
+
title TEXT NOT NULL,
|
|
3729
|
+
severity TEXT NOT NULL DEFAULT 'medium' CHECK(severity IN ('low', 'medium', 'high', 'critical')),
|
|
3730
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'resolved', 'ignored')),
|
|
3731
|
+
source TEXT,
|
|
3732
|
+
summary TEXT,
|
|
3733
|
+
artifact_path TEXT,
|
|
3734
|
+
metadata TEXT DEFAULT '{}',
|
|
3735
|
+
first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3736
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3737
|
+
resolved_at TEXT,
|
|
3738
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3739
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3740
|
+
UNIQUE(task_id, fingerprint)
|
|
3741
|
+
)`);
|
|
3742
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_task ON task_findings(task_id)");
|
|
3743
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_run ON task_findings(run_id)");
|
|
3744
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_status ON task_findings(status)");
|
|
3745
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source)");
|
|
3746
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint)");
|
|
3663
3747
|
ensureTable("inbox_items", `
|
|
3664
3748
|
CREATE TABLE inbox_items (
|
|
3665
3749
|
id TEXT PRIMARY KEY,
|
|
@@ -6134,6 +6218,7 @@ var init_event_hooks = __esm(() => {
|
|
|
6134
6218
|
"task.blocked",
|
|
6135
6219
|
"task.started",
|
|
6136
6220
|
"task.completed",
|
|
6221
|
+
"task.updated",
|
|
6137
6222
|
"task.due",
|
|
6138
6223
|
"task.due_soon",
|
|
6139
6224
|
"task.failed",
|
|
@@ -7463,6 +7548,17 @@ function replaceTaskTags(taskId, tags, db) {
|
|
|
7463
7548
|
db.run("DELETE FROM task_tags WHERE task_id = ?", [taskId]);
|
|
7464
7549
|
insertTaskTags(taskId, tags, db);
|
|
7465
7550
|
}
|
|
7551
|
+
function addMetadataConditions(metadata, conditions, params) {
|
|
7552
|
+
if (!metadata)
|
|
7553
|
+
return;
|
|
7554
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
7555
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(key)) {
|
|
7556
|
+
throw new Error(`Invalid metadata filter key: ${key}`);
|
|
7557
|
+
}
|
|
7558
|
+
conditions.push(`json_extract(metadata, '$."${key}"') = ?`);
|
|
7559
|
+
params.push(value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : JSON.stringify(value));
|
|
7560
|
+
}
|
|
7561
|
+
}
|
|
7466
7562
|
function createTask(input, db) {
|
|
7467
7563
|
const d = db || getDatabase();
|
|
7468
7564
|
const timestamp = now();
|
|
@@ -7645,6 +7741,7 @@ function listTasks(filter = {}, db) {
|
|
|
7645
7741
|
params.push(filter.task_type);
|
|
7646
7742
|
}
|
|
7647
7743
|
}
|
|
7744
|
+
addMetadataConditions(filter.metadata, conditions, params);
|
|
7648
7745
|
const PRIORITY_RANK = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
|
|
7649
7746
|
if (filter.cursor) {
|
|
7650
7747
|
try {
|
|
@@ -7669,6 +7766,54 @@ function listTasks(filter = {}, db) {
|
|
|
7669
7766
|
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK}, created_at DESC${limitClause}`).all(...params);
|
|
7670
7767
|
return rows.map(rowToTask);
|
|
7671
7768
|
}
|
|
7769
|
+
function getTaskByFingerprint(fingerprint, db) {
|
|
7770
|
+
const tasks = listTasks({ metadata: { fingerprint }, limit: 1 }, db);
|
|
7771
|
+
return tasks[0] ?? null;
|
|
7772
|
+
}
|
|
7773
|
+
function mergeTaskMetadata(current, next, fingerprint) {
|
|
7774
|
+
return {
|
|
7775
|
+
...current,
|
|
7776
|
+
...next ?? {},
|
|
7777
|
+
fingerprint
|
|
7778
|
+
};
|
|
7779
|
+
}
|
|
7780
|
+
function upsertTaskByFingerprint(input, db) {
|
|
7781
|
+
const d = db || getDatabase();
|
|
7782
|
+
const fingerprint = input.fingerprint.trim();
|
|
7783
|
+
if (!fingerprint)
|
|
7784
|
+
throw new Error("fingerprint is required");
|
|
7785
|
+
const existing = getTaskByFingerprint(fingerprint, d);
|
|
7786
|
+
const metadata = mergeTaskMetadata(existing?.metadata ?? {}, input.metadata, fingerprint);
|
|
7787
|
+
if (!existing) {
|
|
7788
|
+
const task2 = createTask({ ...input, metadata }, d);
|
|
7789
|
+
return { task: task2, created: true };
|
|
7790
|
+
}
|
|
7791
|
+
const task = updateTask(existing.id, {
|
|
7792
|
+
version: existing.version,
|
|
7793
|
+
title: input.title,
|
|
7794
|
+
description: input.description,
|
|
7795
|
+
status: input.status,
|
|
7796
|
+
priority: input.priority,
|
|
7797
|
+
project_id: input.project_id,
|
|
7798
|
+
assigned_to: input.assigned_to,
|
|
7799
|
+
working_dir: input.working_dir,
|
|
7800
|
+
plan_id: input.plan_id,
|
|
7801
|
+
task_list_id: input.task_list_id,
|
|
7802
|
+
tags: input.tags,
|
|
7803
|
+
metadata,
|
|
7804
|
+
due_at: input.due_at,
|
|
7805
|
+
estimated_minutes: input.estimated_minutes,
|
|
7806
|
+
sla_minutes: input.sla_minutes,
|
|
7807
|
+
confidence: input.confidence,
|
|
7808
|
+
retry_count: input.retry_count,
|
|
7809
|
+
max_retries: input.max_retries,
|
|
7810
|
+
retry_after: input.retry_after,
|
|
7811
|
+
requires_approval: input.requires_approval,
|
|
7812
|
+
recurrence_rule: input.recurrence_rule,
|
|
7813
|
+
task_type: input.task_type
|
|
7814
|
+
}, d);
|
|
7815
|
+
return { task, created: false };
|
|
7816
|
+
}
|
|
7672
7817
|
function countTasks(filter = {}, db) {
|
|
7673
7818
|
const d = db || getDatabase();
|
|
7674
7819
|
const conditions = [];
|
|
@@ -7732,6 +7877,7 @@ function countTasks(filter = {}, db) {
|
|
|
7732
7877
|
conditions.push("task_list_id = ?");
|
|
7733
7878
|
params.push(filter.task_list_id);
|
|
7734
7879
|
}
|
|
7880
|
+
addMetadataConditions(filter.metadata, conditions, params);
|
|
7735
7881
|
if (!filter.include_archived) {
|
|
7736
7882
|
conditions.push("archived_at IS NULL");
|
|
7737
7883
|
}
|
|
@@ -7782,6 +7928,10 @@ function updateTask(id, input, db) {
|
|
|
7782
7928
|
sets.push("assigned_to = ?");
|
|
7783
7929
|
params.push(input.assigned_to);
|
|
7784
7930
|
}
|
|
7931
|
+
if (input.working_dir !== undefined) {
|
|
7932
|
+
sets.push("working_dir = ?");
|
|
7933
|
+
params.push(input.working_dir);
|
|
7934
|
+
}
|
|
7785
7935
|
if (input.tags !== undefined) {
|
|
7786
7936
|
sets.push("tags = ?");
|
|
7787
7937
|
params.push(JSON.stringify(input.tags));
|
|
@@ -7870,6 +8020,8 @@ function updateTask(id, input, db) {
|
|
|
7870
8020
|
logTaskChange(id, "update", "title", task.title, input.title, agentId, d);
|
|
7871
8021
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to)
|
|
7872
8022
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
8023
|
+
if (input.working_dir !== undefined && input.working_dir !== task.working_dir)
|
|
8024
|
+
logTaskChange(id, "update", "working_dir", task.working_dir, input.working_dir, agentId, d);
|
|
7873
8025
|
if (input.approved_by !== undefined)
|
|
7874
8026
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
7875
8027
|
const updatedTask = {
|
|
@@ -7905,6 +8057,10 @@ function updateTask(id, input, db) {
|
|
|
7905
8057
|
if (input.approved_by !== undefined) {
|
|
7906
8058
|
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
7907
8059
|
}
|
|
8060
|
+
const updatePayload = taskEventData(updatedTask);
|
|
8061
|
+
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
8062
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
|
|
8063
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
|
|
7908
8064
|
return updatedTask;
|
|
7909
8065
|
}
|
|
7910
8066
|
function deleteTask(id, db) {
|
|
@@ -10903,17 +11059,22 @@ var init_task_commits = __esm(() => {
|
|
|
10903
11059
|
var exports_task_runs = {};
|
|
10904
11060
|
__export(exports_task_runs, {
|
|
10905
11061
|
verifyTaskRunArtifacts: () => verifyTaskRunArtifacts,
|
|
11062
|
+
summarizeTaskRun: () => summarizeTaskRun,
|
|
10906
11063
|
startTaskRun: () => startTaskRun,
|
|
10907
11064
|
resolveTaskRunId: () => resolveTaskRunId,
|
|
10908
11065
|
redactEvidenceText: () => redactEvidenceText,
|
|
10909
11066
|
listTaskRuns: () => listTaskRuns,
|
|
10910
11067
|
getTaskRunLedger: () => getTaskRunLedger,
|
|
10911
11068
|
getTaskRun: () => getTaskRun,
|
|
11069
|
+
finishTaskRunTransaction: () => finishTaskRunTransaction,
|
|
10912
11070
|
finishTaskRun: () => finishTaskRun,
|
|
11071
|
+
findTaskRunByTransactionKey: () => findTaskRunByTransactionKey,
|
|
11072
|
+
beginTaskRunTransaction: () => beginTaskRunTransaction,
|
|
10913
11073
|
addTaskRunFile: () => addTaskRunFile,
|
|
10914
11074
|
addTaskRunEvent: () => addTaskRunEvent,
|
|
10915
11075
|
addTaskRunCommand: () => addTaskRunCommand,
|
|
10916
|
-
addTaskRunArtifact: () => addTaskRunArtifact
|
|
11076
|
+
addTaskRunArtifact: () => addTaskRunArtifact,
|
|
11077
|
+
LOOP_RUN_TRANSACTION_SCHEMA_VERSION: () => LOOP_RUN_TRANSACTION_SCHEMA_VERSION
|
|
10917
11078
|
});
|
|
10918
11079
|
function parseObject(value) {
|
|
10919
11080
|
if (!value)
|
|
@@ -10937,6 +11098,72 @@ function rowToArtifact(row) {
|
|
|
10937
11098
|
function getRunRow(runId, db) {
|
|
10938
11099
|
return db.query("SELECT * FROM task_runs WHERE id = ?").get(runId);
|
|
10939
11100
|
}
|
|
11101
|
+
function normalizeTransactionKey(input) {
|
|
11102
|
+
const key = (input.key || input.loop_run_id || input.loop_id || "").trim();
|
|
11103
|
+
if (!key)
|
|
11104
|
+
throw new Error("idempotent run transactions require --key, --loop-run-id, or --loop-id");
|
|
11105
|
+
return key.toLowerCase().replace(/[^a-z0-9._:/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 240);
|
|
11106
|
+
}
|
|
11107
|
+
function loopTransactionMetadata(record) {
|
|
11108
|
+
const value = record.metadata["loop_transaction"];
|
|
11109
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
11110
|
+
}
|
|
11111
|
+
function runKey(record) {
|
|
11112
|
+
const tx = loopTransactionMetadata(record);
|
|
11113
|
+
const key = tx["idempotency_key"] ?? record.metadata["idempotency_key"];
|
|
11114
|
+
return typeof key === "string" ? key : null;
|
|
11115
|
+
}
|
|
11116
|
+
function loopId(record) {
|
|
11117
|
+
const tx = loopTransactionMetadata(record);
|
|
11118
|
+
const value = tx["loop_id"] ?? record.metadata["loop_id"];
|
|
11119
|
+
return typeof value === "string" ? value : null;
|
|
11120
|
+
}
|
|
11121
|
+
function loopRunId(record) {
|
|
11122
|
+
const tx = loopTransactionMetadata(record);
|
|
11123
|
+
const value = tx["loop_run_id"] ?? record.metadata["loop_run_id"];
|
|
11124
|
+
return typeof value === "string" ? value : null;
|
|
11125
|
+
}
|
|
11126
|
+
function getTaskRunTransactionByKey(key, taskId, db) {
|
|
11127
|
+
if (taskId) {
|
|
11128
|
+
return db.query("SELECT * FROM task_run_transactions WHERE task_id = ? AND key = ?").get(taskId, key);
|
|
11129
|
+
}
|
|
11130
|
+
const rows = db.query("SELECT * FROM task_run_transactions WHERE key = ? ORDER BY created_at DESC LIMIT 2").all(key);
|
|
11131
|
+
if (rows.length > 1)
|
|
11132
|
+
throw new Error(`Run transaction key is ambiguous across tasks: ${key}. Pass task_id.`);
|
|
11133
|
+
return rows[0] ?? null;
|
|
11134
|
+
}
|
|
11135
|
+
function summarizeTaskRun(run) {
|
|
11136
|
+
return {
|
|
11137
|
+
id: run.id,
|
|
11138
|
+
task_id: run.task_id,
|
|
11139
|
+
agent_id: run.agent_id,
|
|
11140
|
+
title: run.title,
|
|
11141
|
+
status: run.status,
|
|
11142
|
+
summary: run.summary,
|
|
11143
|
+
idempotency_key: runKey(run),
|
|
11144
|
+
loop_id: loopId(run),
|
|
11145
|
+
loop_run_id: loopRunId(run),
|
|
11146
|
+
metadata_keys: Object.keys(run.metadata).sort(),
|
|
11147
|
+
started_at: run.started_at,
|
|
11148
|
+
completed_at: run.completed_at,
|
|
11149
|
+
updated_at: run.updated_at
|
|
11150
|
+
};
|
|
11151
|
+
}
|
|
11152
|
+
function findTaskRunByTransactionKey(key, taskId, db) {
|
|
11153
|
+
const d = db || getDatabase();
|
|
11154
|
+
const normalized = normalizeTransactionKey({ key });
|
|
11155
|
+
const transaction = getTaskRunTransactionByKey(normalized, taskId, d);
|
|
11156
|
+
if (transaction?.run_id)
|
|
11157
|
+
return getTaskRun(transaction.run_id, d);
|
|
11158
|
+
return null;
|
|
11159
|
+
}
|
|
11160
|
+
function loopRunCommands(run, key) {
|
|
11161
|
+
return [
|
|
11162
|
+
run ? `todos runs show ${run.id.slice(0, 8)}` : "todos runs list",
|
|
11163
|
+
run ? `todos findings list --task ${run.task_id.slice(0, 8)} --json` : "todos findings list --json",
|
|
11164
|
+
`todos runs begin <task-id> --key ${key} --apply --json`
|
|
11165
|
+
];
|
|
11166
|
+
}
|
|
10940
11167
|
function resolveTaskRunId(idOrPrefix, db) {
|
|
10941
11168
|
const d = db || getDatabase();
|
|
10942
11169
|
const rows = d.query("SELECT id FROM task_runs WHERE id = ? OR id LIKE ? ORDER BY created_at DESC LIMIT 2").all(idOrPrefix, `${idOrPrefix}%`);
|
|
@@ -10955,7 +11182,7 @@ function startTaskRun(input, db) {
|
|
|
10955
11182
|
const d = db || getDatabase();
|
|
10956
11183
|
if (!getTask(input.task_id, d))
|
|
10957
11184
|
throw new TaskNotFoundError(input.task_id);
|
|
10958
|
-
const id = uuid();
|
|
11185
|
+
const id = input.id ?? uuid();
|
|
10959
11186
|
const timestamp = input.started_at || now();
|
|
10960
11187
|
if (input.claim && input.agent_id) {
|
|
10961
11188
|
startTask(input.task_id, input.agent_id, d);
|
|
@@ -10993,6 +11220,97 @@ function startTaskRun(input, db) {
|
|
|
10993
11220
|
emitLocalEventHooksQuiet({ type: "run.started", payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title } });
|
|
10994
11221
|
return run;
|
|
10995
11222
|
}
|
|
11223
|
+
function beginTaskRunTransaction(input, db) {
|
|
11224
|
+
const d = db || getDatabase();
|
|
11225
|
+
if (!getTask(input.task_id, d))
|
|
11226
|
+
throw new TaskNotFoundError(input.task_id);
|
|
11227
|
+
const timestamp = input.started_at || now();
|
|
11228
|
+
const key = normalizeTransactionKey(input);
|
|
11229
|
+
const existing = findTaskRunByTransactionKey(key, input.task_id, d);
|
|
11230
|
+
const dryRun = !input.apply;
|
|
11231
|
+
if (existing) {
|
|
11232
|
+
return {
|
|
11233
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11234
|
+
local_only: true,
|
|
11235
|
+
dry_run: dryRun,
|
|
11236
|
+
processed_at: timestamp,
|
|
11237
|
+
action: "matched",
|
|
11238
|
+
key,
|
|
11239
|
+
run: summarizeTaskRun(existing),
|
|
11240
|
+
warnings: existing.status === "running" ? [] : [`matched ${existing.status} run`],
|
|
11241
|
+
commands: loopRunCommands(existing, key)
|
|
11242
|
+
};
|
|
11243
|
+
}
|
|
11244
|
+
if (dryRun) {
|
|
11245
|
+
return {
|
|
11246
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11247
|
+
local_only: true,
|
|
11248
|
+
dry_run: true,
|
|
11249
|
+
processed_at: timestamp,
|
|
11250
|
+
action: "preview",
|
|
11251
|
+
key,
|
|
11252
|
+
run: null,
|
|
11253
|
+
warnings: [],
|
|
11254
|
+
commands: loopRunCommands(null, key)
|
|
11255
|
+
};
|
|
11256
|
+
}
|
|
11257
|
+
const metadata = redactValue({
|
|
11258
|
+
...input.metadata || {},
|
|
11259
|
+
loop_transaction: {
|
|
11260
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11261
|
+
idempotency_key: key,
|
|
11262
|
+
loop_id: input.loop_id ?? null,
|
|
11263
|
+
loop_run_id: input.loop_run_id ?? null,
|
|
11264
|
+
first_seen_at: timestamp
|
|
11265
|
+
},
|
|
11266
|
+
idempotency_key: key
|
|
11267
|
+
});
|
|
11268
|
+
const created = d.transaction(() => {
|
|
11269
|
+
d.run(`INSERT OR IGNORE INTO task_run_transactions (
|
|
11270
|
+
id, task_id, run_id, key, loop_id, loop_run_id, metadata, created_at, updated_at
|
|
11271
|
+
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?)`, [
|
|
11272
|
+
uuid(),
|
|
11273
|
+
input.task_id,
|
|
11274
|
+
key,
|
|
11275
|
+
input.loop_id ?? null,
|
|
11276
|
+
input.loop_run_id ?? null,
|
|
11277
|
+
JSON.stringify(metadata),
|
|
11278
|
+
timestamp,
|
|
11279
|
+
timestamp
|
|
11280
|
+
]);
|
|
11281
|
+
const transaction = getTaskRunTransactionByKey(key, input.task_id, d);
|
|
11282
|
+
if (!transaction)
|
|
11283
|
+
throw new Error(`Could not create run transaction for key: ${key}`);
|
|
11284
|
+
if (transaction.run_id) {
|
|
11285
|
+
const existingRun = getTaskRun(transaction.run_id, d);
|
|
11286
|
+
if (existingRun)
|
|
11287
|
+
return { run: existingRun, action: "matched" };
|
|
11288
|
+
}
|
|
11289
|
+
const run = startTaskRun({
|
|
11290
|
+
id: uuid(),
|
|
11291
|
+
task_id: input.task_id,
|
|
11292
|
+
agent_id: input.agent_id,
|
|
11293
|
+
title: input.title,
|
|
11294
|
+
summary: input.summary,
|
|
11295
|
+
metadata,
|
|
11296
|
+
claim: input.claim,
|
|
11297
|
+
started_at: timestamp
|
|
11298
|
+
}, d);
|
|
11299
|
+
d.run("UPDATE task_run_transactions SET run_id = ?, loop_id = COALESCE(?, loop_id), loop_run_id = COALESCE(?, loop_run_id), metadata = ?, updated_at = ? WHERE id = ?", [run.id, input.loop_id ?? null, input.loop_run_id ?? null, JSON.stringify(metadata), timestamp, transaction.id]);
|
|
11300
|
+
return { run, action: "created" };
|
|
11301
|
+
})();
|
|
11302
|
+
return {
|
|
11303
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11304
|
+
local_only: true,
|
|
11305
|
+
dry_run: false,
|
|
11306
|
+
processed_at: timestamp,
|
|
11307
|
+
action: created.action,
|
|
11308
|
+
key,
|
|
11309
|
+
run: summarizeTaskRun(created.run),
|
|
11310
|
+
warnings: [],
|
|
11311
|
+
commands: loopRunCommands(created.run, key)
|
|
11312
|
+
};
|
|
11313
|
+
}
|
|
10996
11314
|
function addTaskRunEvent(input, db) {
|
|
10997
11315
|
const d = db || getDatabase();
|
|
10998
11316
|
const runId = resolveTaskRunId(input.run_id, d);
|
|
@@ -11172,6 +11490,66 @@ function finishTaskRun(input, db) {
|
|
|
11172
11490
|
});
|
|
11173
11491
|
return updated;
|
|
11174
11492
|
}
|
|
11493
|
+
function finishTaskRunTransaction(input, db) {
|
|
11494
|
+
const d = db || getDatabase();
|
|
11495
|
+
const timestamp = input.completed_at || now();
|
|
11496
|
+
const status = input.status || "completed";
|
|
11497
|
+
const key = input.key ? normalizeTransactionKey({ key: input.key }) : "";
|
|
11498
|
+
const run = input.run_id ? getTaskRun(resolveTaskRunId(input.run_id, d), d) : key ? findTaskRunByTransactionKey(key, input.task_id, d) : null;
|
|
11499
|
+
if (!run) {
|
|
11500
|
+
throw new Error(input.run_id ? `Run not found: ${input.run_id}` : "runs finish requires a run id or --key");
|
|
11501
|
+
}
|
|
11502
|
+
if (input.task_id && run.task_id !== input.task_id) {
|
|
11503
|
+
throw new Error(`Run ${run.id} belongs to task ${run.task_id}, not ${input.task_id}`);
|
|
11504
|
+
}
|
|
11505
|
+
const resolvedKey = key || runKey(run) || run.id;
|
|
11506
|
+
const dryRun = input.apply === false;
|
|
11507
|
+
if (run.status !== "running") {
|
|
11508
|
+
const conflict = run.status !== status;
|
|
11509
|
+
return {
|
|
11510
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11511
|
+
local_only: true,
|
|
11512
|
+
dry_run: dryRun,
|
|
11513
|
+
processed_at: timestamp,
|
|
11514
|
+
action: conflict ? "conflict" : "matched",
|
|
11515
|
+
key: resolvedKey,
|
|
11516
|
+
run: summarizeTaskRun(run),
|
|
11517
|
+
warnings: conflict ? [`run is already ${run.status}; requested ${status}`] : [],
|
|
11518
|
+
commands: loopRunCommands(run, resolvedKey)
|
|
11519
|
+
};
|
|
11520
|
+
}
|
|
11521
|
+
if (dryRun) {
|
|
11522
|
+
return {
|
|
11523
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11524
|
+
local_only: true,
|
|
11525
|
+
dry_run: true,
|
|
11526
|
+
processed_at: timestamp,
|
|
11527
|
+
action: "preview",
|
|
11528
|
+
key: resolvedKey,
|
|
11529
|
+
run: summarizeTaskRun({ ...run, status, summary: input.summary ?? run.summary, completed_at: timestamp, updated_at: timestamp }),
|
|
11530
|
+
warnings: [],
|
|
11531
|
+
commands: loopRunCommands(run, resolvedKey)
|
|
11532
|
+
};
|
|
11533
|
+
}
|
|
11534
|
+
const finished = finishTaskRun({
|
|
11535
|
+
run_id: run.id,
|
|
11536
|
+
status,
|
|
11537
|
+
summary: input.summary,
|
|
11538
|
+
agent_id: input.agent_id,
|
|
11539
|
+
completed_at: timestamp
|
|
11540
|
+
}, d);
|
|
11541
|
+
return {
|
|
11542
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11543
|
+
local_only: true,
|
|
11544
|
+
dry_run: false,
|
|
11545
|
+
processed_at: timestamp,
|
|
11546
|
+
action: "finished",
|
|
11547
|
+
key: resolvedKey,
|
|
11548
|
+
run: summarizeTaskRun(finished),
|
|
11549
|
+
warnings: [],
|
|
11550
|
+
commands: loopRunCommands(finished, resolvedKey)
|
|
11551
|
+
};
|
|
11552
|
+
}
|
|
11175
11553
|
function listTaskRuns(taskId, db) {
|
|
11176
11554
|
const d = db || getDatabase();
|
|
11177
11555
|
const rows = taskId ? d.query("SELECT * FROM task_runs WHERE task_id = ? ORDER BY started_at DESC, created_at DESC").all(taskId) : d.query("SELECT * FROM task_runs ORDER BY started_at DESC, created_at DESC LIMIT 100").all();
|
|
@@ -11189,6 +11567,7 @@ function getTaskRunLedger(runId, db) {
|
|
|
11189
11567
|
const files = d.query("SELECT * FROM task_files WHERE task_id = ? ORDER BY updated_at DESC, path").all(run.task_id);
|
|
11190
11568
|
return { run, events, commands, artifacts, files };
|
|
11191
11569
|
}
|
|
11570
|
+
var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
|
|
11192
11571
|
var init_task_runs = __esm(() => {
|
|
11193
11572
|
init_artifact_store();
|
|
11194
11573
|
init_event_hooks();
|
|
@@ -11577,6 +11956,7 @@ var init_calendar = __esm(() => {
|
|
|
11577
11956
|
var exports_tasks = {};
|
|
11578
11957
|
__export(exports_tasks, {
|
|
11579
11958
|
watchTask: () => watchTask,
|
|
11959
|
+
upsertTaskByFingerprint: () => upsertTaskByFingerprint,
|
|
11580
11960
|
updateTaskBoard: () => updateTaskBoard,
|
|
11581
11961
|
updateTask: () => updateTask,
|
|
11582
11962
|
unwatchTask: () => unwatchTask,
|
|
@@ -11620,6 +12000,7 @@ __export(exports_tasks, {
|
|
|
11620
12000
|
getTaskGraph: () => getTaskGraph,
|
|
11621
12001
|
getTaskDependents: () => getTaskDependents,
|
|
11622
12002
|
getTaskDependencies: () => getTaskDependencies,
|
|
12003
|
+
getTaskByFingerprint: () => getTaskByFingerprint,
|
|
11623
12004
|
getTaskBoard: () => getTaskBoard,
|
|
11624
12005
|
getTask: () => getTask,
|
|
11625
12006
|
getStatus: () => getStatus,
|
|
@@ -11860,6 +12241,62 @@ function parsePriority(value) {
|
|
|
11860
12241
|
}
|
|
11861
12242
|
return value;
|
|
11862
12243
|
}
|
|
12244
|
+
function parseJsonObject3(value, flag) {
|
|
12245
|
+
if (!value)
|
|
12246
|
+
return;
|
|
12247
|
+
let parsed;
|
|
12248
|
+
try {
|
|
12249
|
+
parsed = JSON.parse(value);
|
|
12250
|
+
} catch (error) {
|
|
12251
|
+
console.error(chalk2.red(`${flag} must be valid JSON: ${error instanceof Error ? error.message : String(error)}`));
|
|
12252
|
+
process.exit(1);
|
|
12253
|
+
}
|
|
12254
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
12255
|
+
console.error(chalk2.red(`${flag} must be a JSON object`));
|
|
12256
|
+
process.exit(1);
|
|
12257
|
+
}
|
|
12258
|
+
return parsed;
|
|
12259
|
+
}
|
|
12260
|
+
function parseJsonValue(value) {
|
|
12261
|
+
if (value === undefined)
|
|
12262
|
+
return;
|
|
12263
|
+
try {
|
|
12264
|
+
return JSON.parse(value);
|
|
12265
|
+
} catch {
|
|
12266
|
+
return value;
|
|
12267
|
+
}
|
|
12268
|
+
}
|
|
12269
|
+
function parseTags(value) {
|
|
12270
|
+
return value ? value.split(",").map((tag) => tag.trim()).filter(Boolean) : undefined;
|
|
12271
|
+
}
|
|
12272
|
+
function buildExpectationMetadata(opts) {
|
|
12273
|
+
const metadata = parseJsonObject3(opts["metadataJson"], "--metadata-json") ?? {};
|
|
12274
|
+
const expectationId = opts["expectationId"];
|
|
12275
|
+
const expectationFingerprint = opts["expectationFingerprint"];
|
|
12276
|
+
const evidencePaths = opts["evidencePaths"];
|
|
12277
|
+
const originLoopId = opts["originLoopId"];
|
|
12278
|
+
const originRunId = opts["originRunId"];
|
|
12279
|
+
const expected = opts["expected"];
|
|
12280
|
+
const observed = opts["observed"];
|
|
12281
|
+
const acceptance = opts["acceptance"];
|
|
12282
|
+
if (expectationId !== undefined)
|
|
12283
|
+
metadata["expectation_id"] = expectationId;
|
|
12284
|
+
if (expectationFingerprint !== undefined)
|
|
12285
|
+
metadata["expectation_fingerprint"] = expectationFingerprint;
|
|
12286
|
+
if (evidencePaths !== undefined)
|
|
12287
|
+
metadata["evidence_paths"] = String(evidencePaths).split(",").map((path) => path.trim()).filter(Boolean);
|
|
12288
|
+
if (originLoopId !== undefined)
|
|
12289
|
+
metadata["origin_loop_id"] = originLoopId;
|
|
12290
|
+
if (originRunId !== undefined)
|
|
12291
|
+
metadata["origin_run_id"] = originRunId;
|
|
12292
|
+
if (expected !== undefined)
|
|
12293
|
+
metadata["expected"] = parseJsonValue(String(expected));
|
|
12294
|
+
if (observed !== undefined)
|
|
12295
|
+
metadata["observed"] = parseJsonValue(String(observed));
|
|
12296
|
+
if (acceptance !== undefined)
|
|
12297
|
+
metadata["acceptance"] = parseJsonValue(String(acceptance));
|
|
12298
|
+
return metadata;
|
|
12299
|
+
}
|
|
11863
12300
|
function registerTaskCommands(program2) {
|
|
11864
12301
|
program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("--estimated <minutes>", "Estimated time in minutes").option("--sla-minutes <minutes>", "SLA minutes before unfinished work is escalated").option("--sla <minutes>", "Alias for --sla-minutes").option("--approval", "Require approval before completion").option("--recurrence <rule>", "Recurrence rule, e.g. 'every day', 'every weekday', 'every 2 weeks'").option("--due <date>", "Due date (ISO string or YYYY-MM-DD)").option("--reason <text>", "Why this task exists").option("--project <id>", "Assign to project by ID or slug (overrides auto-detect)").action((title, opts) => {
|
|
11865
12302
|
const globalOpts = program2.opts();
|
|
@@ -11876,7 +12313,7 @@ function registerTaskCommands(program2) {
|
|
|
11876
12313
|
}
|
|
11877
12314
|
return id;
|
|
11878
12315
|
})() : undefined;
|
|
11879
|
-
const
|
|
12316
|
+
const task2 = createTask({
|
|
11880
12317
|
title,
|
|
11881
12318
|
description: opts.description,
|
|
11882
12319
|
priority: parsePriority(opts.priority),
|
|
@@ -11898,10 +12335,53 @@ function registerTaskCommands(program2) {
|
|
|
11898
12335
|
reason: opts.reason
|
|
11899
12336
|
});
|
|
11900
12337
|
if (globalOpts.json) {
|
|
11901
|
-
output(
|
|
12338
|
+
output(task2, true);
|
|
11902
12339
|
} else {
|
|
11903
12340
|
console.log(chalk2.green("Task created:"));
|
|
11904
|
-
console.log(formatTaskLine(
|
|
12341
|
+
console.log(formatTaskLine(task2));
|
|
12342
|
+
}
|
|
12343
|
+
});
|
|
12344
|
+
const task = program2.command("task").description("Task subcommands for deterministic automation");
|
|
12345
|
+
task.command("upsert").description("Create or update a task by stable metadata fingerprint").requiredOption("--fingerprint <key>", "Stable dedupe fingerprint").requiredOption("--title <text>", "Task title").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("-s, --status <status>", "Task status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--metadata-json <json>", "JSON object merged into task metadata").option("--working-dir <path>", "Working directory to store on create/update").option("--project <id>", "Assign to project by ID, slug, or path").option("--assign <agent>", "Assign to agent").option("--expectation-id <id>", "Expectation metadata ID").option("--expectation-fingerprint <key>", "Expectation metadata fingerprint").option("--evidence-paths <paths>", "Comma-separated evidence paths").option("--origin-loop-id <id>", "Origin loop ID").option("--origin-run-id <id>", "Origin run ID").option("--expected <json-or-text>", "Expected value metadata").option("--observed <json-or-text>", "Observed value metadata").option("--acceptance <json-or-text>", "Acceptance metadata").action((opts) => {
|
|
12346
|
+
const globalOpts = program2.opts();
|
|
12347
|
+
opts.tags = opts.tags || opts.tag;
|
|
12348
|
+
opts.list = opts.list || opts.taskList;
|
|
12349
|
+
const explicitProject = opts.project || globalOpts.project;
|
|
12350
|
+
const projectId = explicitProject ? resolveProjectIdOrSlug(explicitProject) : autoProject(globalOpts);
|
|
12351
|
+
const taskListId = opts.list ? (() => {
|
|
12352
|
+
const db = getDatabase();
|
|
12353
|
+
const id = resolvePartialId(db, "task_lists", opts.list);
|
|
12354
|
+
if (!id) {
|
|
12355
|
+
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
12356
|
+
process.exit(1);
|
|
12357
|
+
}
|
|
12358
|
+
return id;
|
|
12359
|
+
})() : undefined;
|
|
12360
|
+
let result;
|
|
12361
|
+
try {
|
|
12362
|
+
result = upsertTaskByFingerprint({
|
|
12363
|
+
fingerprint: opts.fingerprint,
|
|
12364
|
+
title: opts.title,
|
|
12365
|
+
description: opts.description,
|
|
12366
|
+
priority: parsePriority(opts.priority),
|
|
12367
|
+
status: opts.status ? normalizeStatus(opts.status) : undefined,
|
|
12368
|
+
task_list_id: taskListId,
|
|
12369
|
+
tags: parseTags(opts.tags),
|
|
12370
|
+
metadata: buildExpectationMetadata(opts),
|
|
12371
|
+
working_dir: opts.workingDir ? resolve8(opts.workingDir) : process.cwd(),
|
|
12372
|
+
project_id: projectId,
|
|
12373
|
+
assigned_to: opts.assign,
|
|
12374
|
+
agent_id: globalOpts.agent,
|
|
12375
|
+
session_id: globalOpts.session
|
|
12376
|
+
});
|
|
12377
|
+
} catch (e) {
|
|
12378
|
+
handleError(e);
|
|
12379
|
+
}
|
|
12380
|
+
if (globalOpts.json) {
|
|
12381
|
+
output(result, true);
|
|
12382
|
+
} else {
|
|
12383
|
+
console.log(chalk2.green(result.created ? "Task created:" : "Task updated:"));
|
|
12384
|
+
console.log(formatTaskLine(result.task));
|
|
11905
12385
|
}
|
|
11906
12386
|
});
|
|
11907
12387
|
program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <id>", "Filter by task list ID").option("--task-list <id>", "Filter by task list ID (alias for --list)").option("--project-name <name>", "Filter by project name").option("--agent-name <name>", "Filter by agent name/assigned").option("--sort <field>", "Sort by: updated, created, priority, status").option("--format <fmt>", "Output format: table (default), compact, csv, json").option("--due-today", "Only tasks due today or earlier").option("--overdue", "Only overdue tasks (past due_at)").option("--recurring", "Only recurring tasks").option("--limit <n>", "Max tasks to return").action((opts) => {
|
|
@@ -12059,89 +12539,89 @@ function registerTaskCommands(program2) {
|
|
|
12059
12539
|
program2.command("show <id>").description("Show full task details").action((id) => {
|
|
12060
12540
|
const globalOpts = program2.opts();
|
|
12061
12541
|
const resolvedId = resolveTaskId(id);
|
|
12062
|
-
const
|
|
12063
|
-
if (!
|
|
12542
|
+
const task2 = getTaskWithRelations(resolvedId);
|
|
12543
|
+
if (!task2) {
|
|
12064
12544
|
console.error(chalk2.red(`Task not found: ${id}`));
|
|
12065
12545
|
process.exit(1);
|
|
12066
12546
|
}
|
|
12067
12547
|
if (globalOpts.json) {
|
|
12068
|
-
output(
|
|
12548
|
+
output(task2, true);
|
|
12069
12549
|
return;
|
|
12070
12550
|
}
|
|
12071
12551
|
console.log(chalk2.bold(`Task Details:
|
|
12072
12552
|
`));
|
|
12073
|
-
console.log(` ${chalk2.dim("ID:")} ${
|
|
12074
|
-
console.log(` ${chalk2.dim("Title:")} ${
|
|
12075
|
-
console.log(` ${chalk2.dim("Status:")} ${(statusColors[
|
|
12076
|
-
console.log(` ${chalk2.dim("Priority:")} ${(priorityColors[
|
|
12077
|
-
if (
|
|
12078
|
-
console.log(` ${chalk2.dim("Desc:")} ${
|
|
12079
|
-
if (
|
|
12080
|
-
console.log(` ${chalk2.dim("Assigned:")} ${
|
|
12081
|
-
if (
|
|
12082
|
-
console.log(` ${chalk2.dim("Agent:")} ${
|
|
12083
|
-
if (
|
|
12084
|
-
console.log(` ${chalk2.dim("Session:")} ${
|
|
12085
|
-
if (
|
|
12086
|
-
console.log(` ${chalk2.dim("Locked:")} ${
|
|
12087
|
-
if (
|
|
12088
|
-
const approvalStatus =
|
|
12553
|
+
console.log(` ${chalk2.dim("ID:")} ${task2.id}`);
|
|
12554
|
+
console.log(` ${chalk2.dim("Title:")} ${task2.title}`);
|
|
12555
|
+
console.log(` ${chalk2.dim("Status:")} ${(statusColors[task2.status] || chalk2.white)(task2.status)}`);
|
|
12556
|
+
console.log(` ${chalk2.dim("Priority:")} ${(priorityColors[task2.priority] || chalk2.white)(task2.priority)}`);
|
|
12557
|
+
if (task2.description)
|
|
12558
|
+
console.log(` ${chalk2.dim("Desc:")} ${task2.description}`);
|
|
12559
|
+
if (task2.assigned_to)
|
|
12560
|
+
console.log(` ${chalk2.dim("Assigned:")} ${task2.assigned_to}`);
|
|
12561
|
+
if (task2.agent_id)
|
|
12562
|
+
console.log(` ${chalk2.dim("Agent:")} ${task2.agent_id}`);
|
|
12563
|
+
if (task2.session_id)
|
|
12564
|
+
console.log(` ${chalk2.dim("Session:")} ${task2.session_id}`);
|
|
12565
|
+
if (task2.locked_by)
|
|
12566
|
+
console.log(` ${chalk2.dim("Locked:")} ${task2.locked_by} (at ${task2.locked_at})`);
|
|
12567
|
+
if (task2.requires_approval) {
|
|
12568
|
+
const approvalStatus = task2.approved_by ? chalk2.green(`approved by ${task2.approved_by}`) : chalk2.yellow("pending approval");
|
|
12089
12569
|
console.log(` ${chalk2.dim("Approval:")} ${approvalStatus}`);
|
|
12090
12570
|
}
|
|
12091
|
-
if (
|
|
12092
|
-
console.log(` ${chalk2.dim("Estimate:")} ${
|
|
12093
|
-
if (
|
|
12094
|
-
console.log(` ${chalk2.dim("SLA:")} ${
|
|
12095
|
-
if (
|
|
12096
|
-
console.log(` ${chalk2.dim("Due:")} ${
|
|
12097
|
-
if (
|
|
12098
|
-
console.log(` ${chalk2.dim("Repeats:")} ${
|
|
12099
|
-
if (
|
|
12100
|
-
console.log(` ${chalk2.dim("Project:")} ${
|
|
12101
|
-
if (
|
|
12102
|
-
console.log(` ${chalk2.dim("Plan:")} ${
|
|
12103
|
-
if (
|
|
12104
|
-
console.log(` ${chalk2.dim("WorkDir:")} ${
|
|
12105
|
-
if (
|
|
12106
|
-
console.log(` ${chalk2.dim("Parent:")} ${
|
|
12107
|
-
if (
|
|
12108
|
-
console.log(` ${chalk2.dim("Tags:")} ${
|
|
12109
|
-
console.log(` ${chalk2.dim("Version:")} ${
|
|
12110
|
-
console.log(` ${chalk2.dim("Created:")} ${
|
|
12111
|
-
if (
|
|
12112
|
-
console.log(` ${chalk2.dim("Started:")} ${
|
|
12113
|
-
if (
|
|
12114
|
-
console.log(` ${chalk2.dim("Done:")} ${
|
|
12115
|
-
if (
|
|
12116
|
-
const dur = Math.round((new Date(
|
|
12571
|
+
if (task2.estimated_minutes)
|
|
12572
|
+
console.log(` ${chalk2.dim("Estimate:")} ${task2.estimated_minutes} minutes`);
|
|
12573
|
+
if (task2.sla_minutes)
|
|
12574
|
+
console.log(` ${chalk2.dim("SLA:")} ${task2.sla_minutes} minutes`);
|
|
12575
|
+
if (task2.due_at)
|
|
12576
|
+
console.log(` ${chalk2.dim("Due:")} ${task2.due_at}`);
|
|
12577
|
+
if (task2.recurrence_rule)
|
|
12578
|
+
console.log(` ${chalk2.dim("Repeats:")} ${task2.recurrence_rule}`);
|
|
12579
|
+
if (task2.project_id)
|
|
12580
|
+
console.log(` ${chalk2.dim("Project:")} ${task2.project_id}`);
|
|
12581
|
+
if (task2.plan_id)
|
|
12582
|
+
console.log(` ${chalk2.dim("Plan:")} ${task2.plan_id}`);
|
|
12583
|
+
if (task2.working_dir)
|
|
12584
|
+
console.log(` ${chalk2.dim("WorkDir:")} ${task2.working_dir}`);
|
|
12585
|
+
if (task2.parent)
|
|
12586
|
+
console.log(` ${chalk2.dim("Parent:")} ${task2.parent.id.slice(0, 8)} | ${task2.parent.title}`);
|
|
12587
|
+
if (task2.tags.length > 0)
|
|
12588
|
+
console.log(` ${chalk2.dim("Tags:")} ${task2.tags.join(", ")}`);
|
|
12589
|
+
console.log(` ${chalk2.dim("Version:")} ${task2.version}`);
|
|
12590
|
+
console.log(` ${chalk2.dim("Created:")} ${task2.created_at}`);
|
|
12591
|
+
if (task2.started_at)
|
|
12592
|
+
console.log(` ${chalk2.dim("Started:")} ${task2.started_at}`);
|
|
12593
|
+
if (task2.completed_at) {
|
|
12594
|
+
console.log(` ${chalk2.dim("Done:")} ${task2.completed_at}`);
|
|
12595
|
+
if (task2.started_at) {
|
|
12596
|
+
const dur = Math.round((new Date(task2.completed_at).getTime() - new Date(task2.started_at).getTime()) / 60000);
|
|
12117
12597
|
console.log(` ${chalk2.dim("Duration:")} ${dur}m`);
|
|
12118
12598
|
}
|
|
12119
12599
|
}
|
|
12120
|
-
if (
|
|
12600
|
+
if (task2.subtasks.length > 0) {
|
|
12121
12601
|
console.log(chalk2.bold(`
|
|
12122
|
-
Subtasks (${
|
|
12123
|
-
for (const st of
|
|
12602
|
+
Subtasks (${task2.subtasks.length}):`));
|
|
12603
|
+
for (const st of task2.subtasks) {
|
|
12124
12604
|
console.log(` ${formatTaskLine(st)}`);
|
|
12125
12605
|
}
|
|
12126
12606
|
}
|
|
12127
|
-
if (
|
|
12607
|
+
if (task2.dependencies.length > 0) {
|
|
12128
12608
|
console.log(chalk2.bold(`
|
|
12129
|
-
Depends on (${
|
|
12130
|
-
for (const dep of
|
|
12609
|
+
Depends on (${task2.dependencies.length}):`));
|
|
12610
|
+
for (const dep of task2.dependencies) {
|
|
12131
12611
|
console.log(` ${formatTaskLine(dep)}`);
|
|
12132
12612
|
}
|
|
12133
12613
|
}
|
|
12134
|
-
if (
|
|
12614
|
+
if (task2.blocked_by.length > 0) {
|
|
12135
12615
|
console.log(chalk2.bold(`
|
|
12136
|
-
Blocks (${
|
|
12137
|
-
for (const b of
|
|
12616
|
+
Blocks (${task2.blocked_by.length}):`));
|
|
12617
|
+
for (const b of task2.blocked_by) {
|
|
12138
12618
|
console.log(` ${formatTaskLine(b)}`);
|
|
12139
12619
|
}
|
|
12140
12620
|
}
|
|
12141
|
-
if (
|
|
12621
|
+
if (task2.comments.length > 0) {
|
|
12142
12622
|
console.log(chalk2.bold(`
|
|
12143
|
-
Comments (${
|
|
12144
|
-
for (const c of
|
|
12623
|
+
Comments (${task2.comments.length}):`));
|
|
12624
|
+
for (const c of task2.comments) {
|
|
12145
12625
|
const agent = c.agent_id ? chalk2.cyan(`[${c.agent_id}] `) : "";
|
|
12146
12626
|
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
12147
12627
|
}
|
|
@@ -12160,8 +12640,8 @@ function registerTaskCommands(program2) {
|
|
|
12160
12640
|
console.error(chalk2.red("No task ID given and no active task found. Pass an ID or use --agent."));
|
|
12161
12641
|
process.exit(1);
|
|
12162
12642
|
}
|
|
12163
|
-
const
|
|
12164
|
-
if (!
|
|
12643
|
+
const task2 = getTaskWithRelations(resolvedId);
|
|
12644
|
+
if (!task2) {
|
|
12165
12645
|
console.error(chalk2.red(`Task not found: ${id || resolvedId}`));
|
|
12166
12646
|
process.exit(1);
|
|
12167
12647
|
}
|
|
@@ -12169,55 +12649,55 @@ function registerTaskCommands(program2) {
|
|
|
12169
12649
|
const { listTaskFiles: listTaskFiles2 } = await Promise.resolve().then(() => (init_task_files(), exports_task_files));
|
|
12170
12650
|
const { getTaskCommits: getTaskCommits2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
|
|
12171
12651
|
try {
|
|
12172
|
-
|
|
12652
|
+
task2.files = listTaskFiles2(task2.id);
|
|
12173
12653
|
} catch (e) {
|
|
12174
12654
|
console.error(chalk2.dim(`Warning: could not load task files: ${e instanceof Error ? e.message : String(e)}`));
|
|
12175
12655
|
}
|
|
12176
12656
|
try {
|
|
12177
|
-
|
|
12657
|
+
task2.commits = getTaskCommits2(task2.id);
|
|
12178
12658
|
} catch (e) {
|
|
12179
12659
|
console.error(chalk2.dim(`Warning: could not load task commits: ${e instanceof Error ? e.message : String(e)}`));
|
|
12180
12660
|
}
|
|
12181
|
-
output(
|
|
12661
|
+
output(task2, true);
|
|
12182
12662
|
return;
|
|
12183
12663
|
}
|
|
12184
|
-
const sid =
|
|
12185
|
-
const statusColor = statusColors[
|
|
12186
|
-
const prioColor = priorityColors[
|
|
12664
|
+
const sid = task2.short_id || task2.id.slice(0, 8);
|
|
12665
|
+
const statusColor = statusColors[task2.status] || chalk2.white;
|
|
12666
|
+
const prioColor = priorityColors[task2.priority] || chalk2.white;
|
|
12187
12667
|
console.log(chalk2.bold(`
|
|
12188
|
-
${chalk2.cyan(sid)} ${statusColor(
|
|
12668
|
+
${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${task2.title}
|
|
12189
12669
|
`));
|
|
12190
|
-
if (
|
|
12670
|
+
if (task2.description) {
|
|
12191
12671
|
console.log(chalk2.dim("Description:"));
|
|
12192
|
-
console.log(` ${
|
|
12672
|
+
console.log(` ${task2.description}
|
|
12193
12673
|
`);
|
|
12194
12674
|
}
|
|
12195
|
-
if (
|
|
12196
|
-
console.log(` ${chalk2.dim("Assigned:")} ${
|
|
12197
|
-
if (
|
|
12198
|
-
console.log(` ${chalk2.dim("Locked by:")} ${
|
|
12199
|
-
if (
|
|
12200
|
-
console.log(` ${chalk2.dim("Project:")} ${
|
|
12201
|
-
if (
|
|
12202
|
-
console.log(` ${chalk2.dim("Plan:")} ${
|
|
12203
|
-
if (
|
|
12204
|
-
console.log(` ${chalk2.dim("Started:")} ${
|
|
12205
|
-
if (
|
|
12206
|
-
console.log(` ${chalk2.dim("Completed:")} ${
|
|
12207
|
-
if (
|
|
12208
|
-
const dur = Math.round((new Date(
|
|
12675
|
+
if (task2.assigned_to)
|
|
12676
|
+
console.log(` ${chalk2.dim("Assigned:")} ${task2.assigned_to}`);
|
|
12677
|
+
if (task2.locked_by)
|
|
12678
|
+
console.log(` ${chalk2.dim("Locked by:")} ${task2.locked_by}`);
|
|
12679
|
+
if (task2.project_id)
|
|
12680
|
+
console.log(` ${chalk2.dim("Project:")} ${task2.project_id}`);
|
|
12681
|
+
if (task2.plan_id)
|
|
12682
|
+
console.log(` ${chalk2.dim("Plan:")} ${task2.plan_id}`);
|
|
12683
|
+
if (task2.started_at)
|
|
12684
|
+
console.log(` ${chalk2.dim("Started:")} ${task2.started_at}`);
|
|
12685
|
+
if (task2.completed_at) {
|
|
12686
|
+
console.log(` ${chalk2.dim("Completed:")} ${task2.completed_at}`);
|
|
12687
|
+
if (task2.started_at) {
|
|
12688
|
+
const dur = Math.round((new Date(task2.completed_at).getTime() - new Date(task2.started_at).getTime()) / 60000);
|
|
12209
12689
|
console.log(` ${chalk2.dim("Duration:")} ${dur}m`);
|
|
12210
12690
|
}
|
|
12211
12691
|
}
|
|
12212
|
-
if (
|
|
12213
|
-
console.log(` ${chalk2.dim("Estimate:")} ${
|
|
12214
|
-
if (
|
|
12215
|
-
console.log(` ${chalk2.dim("Tags:")} ${
|
|
12216
|
-
const unfinishedDeps =
|
|
12217
|
-
if (
|
|
12692
|
+
if (task2.estimated_minutes)
|
|
12693
|
+
console.log(` ${chalk2.dim("Estimate:")} ${task2.estimated_minutes}m`);
|
|
12694
|
+
if (task2.tags.length > 0)
|
|
12695
|
+
console.log(` ${chalk2.dim("Tags:")} ${task2.tags.join(", ")}`);
|
|
12696
|
+
const unfinishedDeps = task2.dependencies.filter((d) => d.status !== "completed" && d.status !== "cancelled");
|
|
12697
|
+
if (task2.dependencies.length > 0) {
|
|
12218
12698
|
console.log(chalk2.bold(`
|
|
12219
|
-
Depends on (${
|
|
12220
|
-
for (const dep of
|
|
12699
|
+
Depends on (${task2.dependencies.length}):`));
|
|
12700
|
+
for (const dep of task2.dependencies) {
|
|
12221
12701
|
const blocked = dep.status !== "completed" && dep.status !== "cancelled";
|
|
12222
12702
|
const icon = blocked ? chalk2.red("\u2717") : chalk2.green("\u2713");
|
|
12223
12703
|
console.log(` ${icon} ${formatTaskLine(dep)}`);
|
|
@@ -12227,21 +12707,21 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12227
12707
|
console.log(chalk2.red(`
|
|
12228
12708
|
BLOCKED by ${unfinishedDeps.length} unfinished dep(s)`));
|
|
12229
12709
|
}
|
|
12230
|
-
if (
|
|
12710
|
+
if (task2.blocked_by.length > 0) {
|
|
12231
12711
|
console.log(chalk2.bold(`
|
|
12232
|
-
Blocks (${
|
|
12233
|
-
for (const b of
|
|
12712
|
+
Blocks (${task2.blocked_by.length}):`));
|
|
12713
|
+
for (const b of task2.blocked_by)
|
|
12234
12714
|
console.log(` ${formatTaskLine(b)}`);
|
|
12235
12715
|
}
|
|
12236
|
-
if (
|
|
12716
|
+
if (task2.subtasks.length > 0) {
|
|
12237
12717
|
console.log(chalk2.bold(`
|
|
12238
|
-
Subtasks (${
|
|
12239
|
-
for (const st of
|
|
12718
|
+
Subtasks (${task2.subtasks.length}):`));
|
|
12719
|
+
for (const st of task2.subtasks)
|
|
12240
12720
|
console.log(` ${formatTaskLine(st)}`);
|
|
12241
12721
|
}
|
|
12242
12722
|
try {
|
|
12243
12723
|
const { listTaskFiles: listTaskFiles2 } = await Promise.resolve().then(() => (init_task_files(), exports_task_files));
|
|
12244
|
-
const files = listTaskFiles2(
|
|
12724
|
+
const files = listTaskFiles2(task2.id);
|
|
12245
12725
|
if (files.length > 0) {
|
|
12246
12726
|
console.log(chalk2.bold(`
|
|
12247
12727
|
Files (${files.length}):`));
|
|
@@ -12253,7 +12733,7 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12253
12733
|
}
|
|
12254
12734
|
try {
|
|
12255
12735
|
const { getTaskCommits: getTaskCommits2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
|
|
12256
|
-
const commits = getTaskCommits2(
|
|
12736
|
+
const commits = getTaskCommits2(task2.id);
|
|
12257
12737
|
if (commits.length > 0) {
|
|
12258
12738
|
console.log(chalk2.bold(`
|
|
12259
12739
|
Commits (${commits.length}):`));
|
|
@@ -12263,19 +12743,19 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12263
12743
|
} catch (e) {
|
|
12264
12744
|
console.error(chalk2.dim(`Warning: could not load task commits: ${e instanceof Error ? e.message : String(e)}`));
|
|
12265
12745
|
}
|
|
12266
|
-
if (
|
|
12746
|
+
if (task2.comments.length > 0) {
|
|
12267
12747
|
console.log(chalk2.bold(`
|
|
12268
|
-
Comments (${
|
|
12269
|
-
for (const c of
|
|
12748
|
+
Comments (${task2.comments.length}):`));
|
|
12749
|
+
for (const c of task2.comments) {
|
|
12270
12750
|
const agent = c.agent_id ? chalk2.cyan(`[${c.agent_id}] `) : "";
|
|
12271
12751
|
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
12272
12752
|
}
|
|
12273
12753
|
}
|
|
12274
|
-
if (
|
|
12275
|
-
const done =
|
|
12754
|
+
if (task2.checklist && task2.checklist.length > 0) {
|
|
12755
|
+
const done = task2.checklist.filter((c) => c.checked).length;
|
|
12276
12756
|
console.log(chalk2.bold(`
|
|
12277
|
-
Checklist (${done}/${
|
|
12278
|
-
for (const item of
|
|
12757
|
+
Checklist (${done}/${task2.checklist.length}):`));
|
|
12758
|
+
for (const item of task2.checklist) {
|
|
12279
12759
|
const icon = item.checked ? chalk2.green("\u2611") : chalk2.dim("\u2610");
|
|
12280
12760
|
console.log(` ${icon} ${item.text || item.title}`);
|
|
12281
12761
|
}
|
|
@@ -12328,9 +12808,9 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12328
12808
|
return resolved;
|
|
12329
12809
|
})() : undefined;
|
|
12330
12810
|
const planId = opts.plan ? resolvePlanId(opts.plan) : opts.clearPlan ? null : undefined;
|
|
12331
|
-
let
|
|
12811
|
+
let task2;
|
|
12332
12812
|
try {
|
|
12333
|
-
|
|
12813
|
+
task2 = updateTask(resolvedId, {
|
|
12334
12814
|
version: current.version,
|
|
12335
12815
|
title: opts.title,
|
|
12336
12816
|
description: opts.description,
|
|
@@ -12350,10 +12830,10 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12350
12830
|
handleError(e);
|
|
12351
12831
|
}
|
|
12352
12832
|
if (globalOpts.json) {
|
|
12353
|
-
output(
|
|
12833
|
+
output(task2, true);
|
|
12354
12834
|
} else {
|
|
12355
12835
|
console.log(chalk2.green("Task updated:"));
|
|
12356
|
-
console.log(formatTaskLine(
|
|
12836
|
+
console.log(formatTaskLine(task2));
|
|
12357
12837
|
}
|
|
12358
12838
|
});
|
|
12359
12839
|
program2.command("done <id>").description("Mark a task as completed").option("--attach-ids <ids>", "Comma-separated @hasna/attachments IDs to link as evidence").option("--files-changed <files>", "Comma-separated list of files changed").option("--test-results <results>", "Test results summary").option("--commit-hash <hash>", "Git commit hash").option("--notes <notes>", "Completion notes").option("--confidence <0-1>", "Agent's confidence 0.0-1.0 that the task is fully complete (default: 1.0, <0.7 flagged for review)").action((id, opts) => {
|
|
@@ -12363,37 +12843,37 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12363
12843
|
const filesChanged = opts.filesChanged ? opts.filesChanged.split(",").map((s) => s.trim()) : undefined;
|
|
12364
12844
|
const confidence = opts.confidence !== undefined ? parseFloat(opts.confidence) : undefined;
|
|
12365
12845
|
const evidence = attachmentIds || filesChanged || opts.testResults || opts.commitHash || opts.notes ? { attachment_ids: attachmentIds, files_changed: filesChanged, test_results: opts.testResults, commit_hash: opts.commitHash, notes: opts.notes } : undefined;
|
|
12366
|
-
let
|
|
12846
|
+
let task2;
|
|
12367
12847
|
try {
|
|
12368
|
-
|
|
12848
|
+
task2 = completeTask(resolvedId, globalOpts.agent, undefined, { ...evidence, confidence });
|
|
12369
12849
|
} catch (e) {
|
|
12370
12850
|
handleError(e);
|
|
12371
12851
|
}
|
|
12372
12852
|
if (globalOpts.json) {
|
|
12373
|
-
output(
|
|
12853
|
+
output(task2, true);
|
|
12374
12854
|
} else {
|
|
12375
12855
|
console.log(chalk2.green("Task completed:"));
|
|
12376
|
-
console.log(formatTaskLine(
|
|
12856
|
+
console.log(formatTaskLine(task2));
|
|
12377
12857
|
}
|
|
12378
12858
|
});
|
|
12379
12859
|
program2.command("approve <id>").description("Approve a task that requires approval").action((id) => {
|
|
12380
12860
|
const globalOpts = program2.opts();
|
|
12381
12861
|
const resolvedId = resolveTaskId(id);
|
|
12382
|
-
const
|
|
12383
|
-
if (!
|
|
12862
|
+
const task2 = getTask(resolvedId);
|
|
12863
|
+
if (!task2) {
|
|
12384
12864
|
console.error(chalk2.red(`Task not found: ${id}`));
|
|
12385
12865
|
process.exit(1);
|
|
12386
12866
|
}
|
|
12387
|
-
if (!
|
|
12867
|
+
if (!task2.requires_approval) {
|
|
12388
12868
|
console.log(chalk2.yellow("This task does not require approval."));
|
|
12389
12869
|
return;
|
|
12390
12870
|
}
|
|
12391
|
-
if (
|
|
12392
|
-
console.log(chalk2.yellow(`Already approved by ${
|
|
12871
|
+
if (task2.approved_by) {
|
|
12872
|
+
console.log(chalk2.yellow(`Already approved by ${task2.approved_by}.`));
|
|
12393
12873
|
return;
|
|
12394
12874
|
}
|
|
12395
12875
|
try {
|
|
12396
|
-
const updated = updateTask(resolvedId, { approved_by: globalOpts.agent || "cli", version:
|
|
12876
|
+
const updated = updateTask(resolvedId, { approved_by: globalOpts.agent || "cli", version: task2.version });
|
|
12397
12877
|
if (globalOpts.json) {
|
|
12398
12878
|
output(updated, true);
|
|
12399
12879
|
} else {
|
|
@@ -12408,17 +12888,17 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12408
12888
|
const globalOpts = program2.opts();
|
|
12409
12889
|
const agentId = globalOpts.agent || "cli";
|
|
12410
12890
|
const resolvedId = resolveTaskId(id);
|
|
12411
|
-
let
|
|
12891
|
+
let task2;
|
|
12412
12892
|
try {
|
|
12413
|
-
|
|
12893
|
+
task2 = startTask(resolvedId, agentId);
|
|
12414
12894
|
} catch (e) {
|
|
12415
12895
|
handleError(e);
|
|
12416
12896
|
}
|
|
12417
12897
|
if (globalOpts.json) {
|
|
12418
|
-
output(
|
|
12898
|
+
output(task2, true);
|
|
12419
12899
|
} else {
|
|
12420
12900
|
console.log(chalk2.green(`Task started by ${agentId}:`));
|
|
12421
|
-
console.log(formatTaskLine(
|
|
12901
|
+
console.log(formatTaskLine(task2));
|
|
12422
12902
|
}
|
|
12423
12903
|
});
|
|
12424
12904
|
program2.command("lock <id>").description("Acquire exclusive lock on a task").action((id) => {
|
|
@@ -13591,7 +14071,7 @@ function likePattern(query) {
|
|
|
13591
14071
|
return null;
|
|
13592
14072
|
return `%${trimmed}%`;
|
|
13593
14073
|
}
|
|
13594
|
-
function
|
|
14074
|
+
function parseJsonObject4(value) {
|
|
13595
14075
|
if (!value)
|
|
13596
14076
|
return {};
|
|
13597
14077
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -13606,7 +14086,7 @@ function parseJsonObject3(value) {
|
|
|
13606
14086
|
}
|
|
13607
14087
|
}
|
|
13608
14088
|
function rowToTaskRun(row) {
|
|
13609
|
-
return { ...row, metadata:
|
|
14089
|
+
return { ...row, metadata: parseJsonObject4(row.metadata) };
|
|
13610
14090
|
}
|
|
13611
14091
|
function taskMatchesSavedFilters(task, filters, db) {
|
|
13612
14092
|
if (filters.plan_id && task.plan_id !== filters.plan_id)
|
|
@@ -14969,7 +15449,7 @@ function packageSource(version) {
|
|
|
14969
15449
|
function emptyCounts() {
|
|
14970
15450
|
return Object.fromEntries(dataKeys.map((key) => [key, 0]));
|
|
14971
15451
|
}
|
|
14972
|
-
function
|
|
15452
|
+
function parseJsonObject5(value) {
|
|
14973
15453
|
if (!value)
|
|
14974
15454
|
return {};
|
|
14975
15455
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -15015,34 +15495,34 @@ function rowToTask3(row) {
|
|
|
15015
15495
|
return {
|
|
15016
15496
|
...row,
|
|
15017
15497
|
tags: parseJsonArray2(row.tags),
|
|
15018
|
-
metadata:
|
|
15498
|
+
metadata: parseJsonObject5(row.metadata),
|
|
15019
15499
|
requires_approval: Boolean(row.requires_approval)
|
|
15020
15500
|
};
|
|
15021
15501
|
}
|
|
15022
15502
|
function rowToTaskList2(row) {
|
|
15023
|
-
return { ...row, metadata:
|
|
15503
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
15024
15504
|
}
|
|
15025
15505
|
function rowWithMetadata(row) {
|
|
15026
|
-
return { ...row, metadata:
|
|
15506
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
15027
15507
|
}
|
|
15028
15508
|
function rowToRunEvent(row) {
|
|
15029
|
-
return { ...row, data:
|
|
15509
|
+
return { ...row, data: parseJsonObject5(row.data) };
|
|
15030
15510
|
}
|
|
15031
15511
|
function rowToCommit2(row) {
|
|
15032
15512
|
return { ...row, files_changed: row.files_changed ? parseJsonArray2(row.files_changed) : null };
|
|
15033
15513
|
}
|
|
15034
15514
|
function rowToSavedView(row) {
|
|
15035
|
-
return { ...row, filters:
|
|
15515
|
+
return { ...row, filters: parseJsonObject5(row.filters) };
|
|
15036
15516
|
}
|
|
15037
15517
|
function rowToTaskBoard2(row) {
|
|
15038
15518
|
return {
|
|
15039
15519
|
...row,
|
|
15040
15520
|
lanes: parseJsonArray2(row.lanes),
|
|
15041
|
-
filters:
|
|
15521
|
+
filters: parseJsonObject5(row.filters)
|
|
15042
15522
|
};
|
|
15043
15523
|
}
|
|
15044
15524
|
function rowToCalendarItem2(row) {
|
|
15045
|
-
return { ...row, metadata:
|
|
15525
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
15046
15526
|
}
|
|
15047
15527
|
function bridgeStats(data) {
|
|
15048
15528
|
return Object.fromEntries(dataKeys.map((key) => [key, data[key].length]));
|
|
@@ -18389,6 +18869,7 @@ var init_token_utils = __esm(() => {
|
|
|
18389
18869
|
"add_task_run_event",
|
|
18390
18870
|
"add_task_run_file",
|
|
18391
18871
|
"acknowledge_handoff",
|
|
18872
|
+
"begin_task_run_transaction",
|
|
18392
18873
|
"build_local_report",
|
|
18393
18874
|
"cancel_agent_run_dispatch",
|
|
18394
18875
|
"finish_task_run",
|
|
@@ -18436,6 +18917,7 @@ var init_token_utils = __esm(() => {
|
|
|
18436
18917
|
"list_local_snapshots",
|
|
18437
18918
|
"list_retrospectives",
|
|
18438
18919
|
"list_risks",
|
|
18920
|
+
"list_task_findings",
|
|
18439
18921
|
"list_task_runs",
|
|
18440
18922
|
"list_verification_providers",
|
|
18441
18923
|
"merge_duplicate_task",
|
|
@@ -18444,6 +18926,7 @@ var init_token_utils = __esm(() => {
|
|
|
18444
18926
|
"remove_review_routing_rule",
|
|
18445
18927
|
"restore_local_backup",
|
|
18446
18928
|
"retry_agent_run_dispatch",
|
|
18929
|
+
"resolve_missing_task_findings",
|
|
18447
18930
|
"resolve_mentions",
|
|
18448
18931
|
"run_next_agent_dispatch",
|
|
18449
18932
|
"search_knowledge_records",
|
|
@@ -18486,9 +18969,17 @@ var init_token_utils = __esm(() => {
|
|
|
18486
18969
|
"unlock_file",
|
|
18487
18970
|
"unwatch_task",
|
|
18488
18971
|
"update_comment",
|
|
18972
|
+
"upsert_task_finding",
|
|
18489
18973
|
"update_risk",
|
|
18490
18974
|
"watch_task"
|
|
18491
18975
|
],
|
|
18976
|
+
loops: [
|
|
18977
|
+
"begin_task_run_transaction",
|
|
18978
|
+
"finish_task_run",
|
|
18979
|
+
"list_task_findings",
|
|
18980
|
+
"resolve_missing_task_findings",
|
|
18981
|
+
"upsert_task_finding"
|
|
18982
|
+
],
|
|
18492
18983
|
agents: [
|
|
18493
18984
|
"auto_assign_task",
|
|
18494
18985
|
"delete_agent",
|
|
@@ -18570,7 +19061,7 @@ var init_token_utils = __esm(() => {
|
|
|
18570
19061
|
maintenance: ["extract_todos", "get_sla_breaches", "notify_upcoming_deadlines", "run_doctor", "score_task", "watch_source_todos"]
|
|
18571
19062
|
};
|
|
18572
19063
|
MCP_PROFILE_GROUPS = {
|
|
18573
|
-
minimal: ["core"],
|
|
19064
|
+
minimal: ["core", "loops"],
|
|
18574
19065
|
core: ["core"],
|
|
18575
19066
|
standard: ["core", "tasks", "projects", "resources", "agents", "metadata"],
|
|
18576
19067
|
agent: ["core", "tasks", "projects", "resources"],
|
|
@@ -20863,6 +21354,39 @@ async function handleCreateTask(req, ctx, json2, taskToSummary2) {
|
|
|
20863
21354
|
return json2({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
|
|
20864
21355
|
}
|
|
20865
21356
|
}
|
|
21357
|
+
async function handleUpsertTask(req, ctx, json2, taskToSummary2) {
|
|
21358
|
+
try {
|
|
21359
|
+
const body = await req.json();
|
|
21360
|
+
if (typeof body["fingerprint"] !== "string" || body["fingerprint"].trim() === "") {
|
|
21361
|
+
return json2({ error: "Missing 'fingerprint'" }, 400);
|
|
21362
|
+
}
|
|
21363
|
+
if (typeof body["title"] !== "string" || body["title"].trim() === "") {
|
|
21364
|
+
return json2({ error: "Missing 'title'" }, 400);
|
|
21365
|
+
}
|
|
21366
|
+
const metadata = body["metadata"] && typeof body["metadata"] === "object" && !Array.isArray(body["metadata"]) ? { ...body["metadata"] } : {};
|
|
21367
|
+
for (const key of ["expectation_id", "expectation_fingerprint", "evidence_paths", "origin_loop_id", "origin_run_id", "expected", "observed", "acceptance"]) {
|
|
21368
|
+
if (body[key] !== undefined)
|
|
21369
|
+
metadata[key] = body[key];
|
|
21370
|
+
}
|
|
21371
|
+
const result = upsertTaskByFingerprint({
|
|
21372
|
+
fingerprint: body["fingerprint"],
|
|
21373
|
+
title: body["title"],
|
|
21374
|
+
description: typeof body["description"] === "string" ? body["description"] : undefined,
|
|
21375
|
+
status: body["status"],
|
|
21376
|
+
priority: body["priority"],
|
|
21377
|
+
project_id: typeof body["project_id"] === "string" ? body["project_id"] : undefined,
|
|
21378
|
+
task_list_id: typeof body["task_list_id"] === "string" ? body["task_list_id"] : undefined,
|
|
21379
|
+
assigned_to: typeof body["assigned_to"] === "string" ? body["assigned_to"] : undefined,
|
|
21380
|
+
working_dir: typeof body["working_dir"] === "string" ? body["working_dir"] : undefined,
|
|
21381
|
+
tags: Array.isArray(body["tags"]) ? body["tags"].filter((tag) => typeof tag === "string") : undefined,
|
|
21382
|
+
metadata
|
|
21383
|
+
});
|
|
21384
|
+
ctx.broadcastEvent({ type: "task", task_id: result.task.id, action: result.created ? "created" : "updated", agent_id: result.task.agent_id, project_id: result.task.project_id });
|
|
21385
|
+
return json2({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
|
|
21386
|
+
} catch (e) {
|
|
21387
|
+
return json2({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
|
|
21388
|
+
}
|
|
21389
|
+
}
|
|
20866
21390
|
function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
20867
21391
|
const format = url.searchParams.get("format") || "json";
|
|
20868
21392
|
const status = url.searchParams.get("status") || undefined;
|
|
@@ -26005,6 +26529,61 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
26005
26529
|
}
|
|
26006
26530
|
});
|
|
26007
26531
|
}
|
|
26532
|
+
if (shouldRegisterTool("upsert_task")) {
|
|
26533
|
+
server.tool("upsert_task", "Create or update a task by stable metadata fingerprint. Metadata is shallow-merged on updates.", {
|
|
26534
|
+
fingerprint: exports_external.string().describe("Stable dedupe fingerprint stored as metadata.fingerprint"),
|
|
26535
|
+
title: exports_external.string().describe("Task title"),
|
|
26536
|
+
description: exports_external.string().optional().describe("Task description"),
|
|
26537
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
26538
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
26539
|
+
project_id: exports_external.string().optional().describe("Project ID"),
|
|
26540
|
+
task_list_id: exports_external.string().optional().describe("Task list ID"),
|
|
26541
|
+
assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
|
|
26542
|
+
tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
|
|
26543
|
+
working_dir: exports_external.string().optional().describe("Working directory associated with the task"),
|
|
26544
|
+
metadata: exports_external.record(exports_external.unknown()).optional().describe("Metadata object to shallow-merge"),
|
|
26545
|
+
expectation_id: exports_external.string().optional(),
|
|
26546
|
+
expectation_fingerprint: exports_external.string().optional(),
|
|
26547
|
+
evidence_paths: exports_external.array(exports_external.string()).optional(),
|
|
26548
|
+
origin_loop_id: exports_external.string().optional(),
|
|
26549
|
+
origin_run_id: exports_external.string().optional(),
|
|
26550
|
+
expected: exports_external.unknown().optional(),
|
|
26551
|
+
observed: exports_external.unknown().optional(),
|
|
26552
|
+
acceptance: exports_external.unknown().optional()
|
|
26553
|
+
}, async (params) => {
|
|
26554
|
+
try {
|
|
26555
|
+
const { assigned_to, project_id, task_list_id, metadata, expectation_id, expectation_fingerprint, evidence_paths, origin_loop_id, origin_run_id, expected, observed, acceptance, ...rest } = params;
|
|
26556
|
+
const mergedMetadata = { ...metadata ?? {} };
|
|
26557
|
+
if (expectation_id !== undefined)
|
|
26558
|
+
mergedMetadata["expectation_id"] = expectation_id;
|
|
26559
|
+
if (expectation_fingerprint !== undefined)
|
|
26560
|
+
mergedMetadata["expectation_fingerprint"] = expectation_fingerprint;
|
|
26561
|
+
if (evidence_paths !== undefined)
|
|
26562
|
+
mergedMetadata["evidence_paths"] = evidence_paths;
|
|
26563
|
+
if (origin_loop_id !== undefined)
|
|
26564
|
+
mergedMetadata["origin_loop_id"] = origin_loop_id;
|
|
26565
|
+
if (origin_run_id !== undefined)
|
|
26566
|
+
mergedMetadata["origin_run_id"] = origin_run_id;
|
|
26567
|
+
if (expected !== undefined)
|
|
26568
|
+
mergedMetadata["expected"] = expected;
|
|
26569
|
+
if (observed !== undefined)
|
|
26570
|
+
mergedMetadata["observed"] = observed;
|
|
26571
|
+
if (acceptance !== undefined)
|
|
26572
|
+
mergedMetadata["acceptance"] = acceptance;
|
|
26573
|
+
const resolved = { ...rest, metadata: mergedMetadata };
|
|
26574
|
+
if (assigned_to)
|
|
26575
|
+
resolved.assigned_to = resolveAssignee(assigned_to);
|
|
26576
|
+
if (project_id)
|
|
26577
|
+
resolved.project_id = resolveId(project_id, "projects");
|
|
26578
|
+
if (task_list_id)
|
|
26579
|
+
resolved.task_list_id = resolveId(task_list_id, "task_lists");
|
|
26580
|
+
const result = upsertTaskByFingerprint(resolved);
|
|
26581
|
+
return { content: [{ type: "text", text: compactJson({ created: result.created, task: JSON.parse(mutationTaskResponse(result.task)) }) }] };
|
|
26582
|
+
} catch (e) {
|
|
26583
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
26584
|
+
}
|
|
26585
|
+
});
|
|
26586
|
+
}
|
|
26008
26587
|
if (shouldRegisterTool("list_tasks")) {
|
|
26009
26588
|
server.tool("list_tasks", "List tasks with optional filters. Pass empty arrays for multi-value filters (e.g. status=[] shows all).", {
|
|
26010
26589
|
status: exports_external.union([exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]), exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]))]).optional().describe("Filter by status"),
|
|
@@ -26016,7 +26595,8 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
26016
26595
|
created_after: exports_external.string().optional().describe("ISO date \u2014 tasks created after this date"),
|
|
26017
26596
|
created_before: exports_external.string().optional().describe("ISO date \u2014 tasks created before this date"),
|
|
26018
26597
|
limit: exports_external.number().optional().describe("Max results (default: 50, max 500)"),
|
|
26019
|
-
offset: exports_external.number().optional().describe("Pagination offset")
|
|
26598
|
+
offset: exports_external.number().optional().describe("Pagination offset"),
|
|
26599
|
+
metadata: exports_external.record(exports_external.unknown()).optional().describe("Exact top-level metadata filters")
|
|
26020
26600
|
}, async (params) => {
|
|
26021
26601
|
try {
|
|
26022
26602
|
const resolved = { ...params };
|
|
@@ -27604,7 +28184,7 @@ function parseAssignee(line) {
|
|
|
27604
28184
|
return mention;
|
|
27605
28185
|
return line.match(/\bassign(?:ed)?\s+(?:to\s+)?([a-zA-Z0-9._-]+)/i)?.[1] || null;
|
|
27606
28186
|
}
|
|
27607
|
-
function
|
|
28187
|
+
function parseTags2(line) {
|
|
27608
28188
|
return Array.from(new Set(Array.from(line.matchAll(/#([a-zA-Z0-9._-]+)/g)).map((match) => match[1].toLowerCase()))).slice(0, 10);
|
|
27609
28189
|
}
|
|
27610
28190
|
function parseDependencies(line) {
|
|
@@ -27676,7 +28256,7 @@ function previewNaturalLanguageIntake(input, db) {
|
|
|
27676
28256
|
description: `Parsed from local natural-language intake:
|
|
27677
28257
|
${line}`,
|
|
27678
28258
|
priority: parsePriority2(line, fallbackPriority),
|
|
27679
|
-
tags:
|
|
28259
|
+
tags: parseTags2(line),
|
|
27680
28260
|
assigned_to: parseAssignee(line),
|
|
27681
28261
|
due_at: parseDue(line, referenceDate),
|
|
27682
28262
|
depends_on: parseDependencies(line),
|
|
@@ -28184,7 +28764,7 @@ function countMovedStandaloneVerifications(db, duplicateId) {
|
|
|
28184
28764
|
const rows = db.query("SELECT command FROM task_verifications WHERE task_id = ?").all(duplicateId);
|
|
28185
28765
|
return rows.filter((row) => !generatedCommands.has(row.command)).length;
|
|
28186
28766
|
}
|
|
28187
|
-
function
|
|
28767
|
+
function mergeTaskMetadata2(primary, duplicate, input, mergedAt) {
|
|
28188
28768
|
const mergedDuplicates = Array.isArray(primary.metadata["merged_duplicates"]) ? [...primary.metadata["merged_duplicates"]] : [];
|
|
28189
28769
|
mergedDuplicates.push({
|
|
28190
28770
|
id: duplicate.id,
|
|
@@ -28245,7 +28825,7 @@ function mergeDuplicateTask(input, db) {
|
|
|
28245
28825
|
updateTask(primary.id, {
|
|
28246
28826
|
version: primary.version,
|
|
28247
28827
|
tags: mergedTags,
|
|
28248
|
-
metadata:
|
|
28828
|
+
metadata: mergeTaskMetadata2(primary, duplicate, input, mergedAt),
|
|
28249
28829
|
description: mergeTaskDescription(primary, duplicate) ?? undefined
|
|
28250
28830
|
}, d);
|
|
28251
28831
|
moved.comments = updateRows(d, "task_comments", "task_id", duplicate.id, primary.id);
|
|
@@ -34239,6 +34819,366 @@ var init_task_meta_tools = __esm(() => {
|
|
|
34239
34819
|
init_zod();
|
|
34240
34820
|
});
|
|
34241
34821
|
|
|
34822
|
+
// src/db/findings.ts
|
|
34823
|
+
var exports_findings = {};
|
|
34824
|
+
__export(exports_findings, {
|
|
34825
|
+
upsertTaskFinding: () => upsertTaskFinding,
|
|
34826
|
+
resolveMissingTaskFindings: () => resolveMissingTaskFindings,
|
|
34827
|
+
listTaskFindings: () => listTaskFindings,
|
|
34828
|
+
listCompactTaskFindings: () => listCompactTaskFindings,
|
|
34829
|
+
TASK_FINDING_UPSERT_SCHEMA_VERSION: () => TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
34830
|
+
TASK_FINDING_SCHEMA_VERSION: () => TASK_FINDING_SCHEMA_VERSION,
|
|
34831
|
+
TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION: () => TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION
|
|
34832
|
+
});
|
|
34833
|
+
function parseObject2(value) {
|
|
34834
|
+
if (!value)
|
|
34835
|
+
return {};
|
|
34836
|
+
try {
|
|
34837
|
+
const parsed = JSON.parse(value);
|
|
34838
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
34839
|
+
} catch {
|
|
34840
|
+
return {};
|
|
34841
|
+
}
|
|
34842
|
+
}
|
|
34843
|
+
function normalizeKey(value) {
|
|
34844
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9._:/-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
34845
|
+
}
|
|
34846
|
+
function normalizeFingerprint(value) {
|
|
34847
|
+
const normalized = normalizeKey(value);
|
|
34848
|
+
if (!normalized)
|
|
34849
|
+
throw new Error("finding fingerprint is required");
|
|
34850
|
+
return normalized.slice(0, 240);
|
|
34851
|
+
}
|
|
34852
|
+
function normalizeSeverity(value) {
|
|
34853
|
+
const normalized = normalizeKey(value || "medium");
|
|
34854
|
+
if (SEVERITIES.has(normalized))
|
|
34855
|
+
return normalized;
|
|
34856
|
+
if (/^(p0|blocker|urgent|highest)$/.test(normalized))
|
|
34857
|
+
return "critical";
|
|
34858
|
+
if (/^(p1|major)$/.test(normalized))
|
|
34859
|
+
return "high";
|
|
34860
|
+
if (/^(p3|minor|info)$/.test(normalized))
|
|
34861
|
+
return "low";
|
|
34862
|
+
return "medium";
|
|
34863
|
+
}
|
|
34864
|
+
function normalizeStatus2(value) {
|
|
34865
|
+
const normalized = normalizeKey(value || "open");
|
|
34866
|
+
if (STATUSES.has(normalized))
|
|
34867
|
+
return normalized;
|
|
34868
|
+
if (normalized === "closed" || normalized === "fixed")
|
|
34869
|
+
return "resolved";
|
|
34870
|
+
return "open";
|
|
34871
|
+
}
|
|
34872
|
+
function normalizeResolutionStatus(value) {
|
|
34873
|
+
const status = normalizeStatus2(value || "resolved");
|
|
34874
|
+
if (status === "open")
|
|
34875
|
+
throw new Error("resolve-missing status must be resolved or ignored");
|
|
34876
|
+
return status;
|
|
34877
|
+
}
|
|
34878
|
+
function redactOptional(value, max = 2000) {
|
|
34879
|
+
if (!value)
|
|
34880
|
+
return null;
|
|
34881
|
+
const redacted = redactEvidenceText(value).trim();
|
|
34882
|
+
if (!redacted)
|
|
34883
|
+
return null;
|
|
34884
|
+
return redacted.length > max ? `${redacted.slice(0, max - 3)}...` : redacted;
|
|
34885
|
+
}
|
|
34886
|
+
function rowToFinding(row) {
|
|
34887
|
+
return {
|
|
34888
|
+
schema_version: TASK_FINDING_SCHEMA_VERSION,
|
|
34889
|
+
...row,
|
|
34890
|
+
severity: normalizeSeverity(row.severity),
|
|
34891
|
+
status: normalizeStatus2(row.status),
|
|
34892
|
+
metadata: parseObject2(row.metadata)
|
|
34893
|
+
};
|
|
34894
|
+
}
|
|
34895
|
+
function compactFinding(finding2) {
|
|
34896
|
+
return {
|
|
34897
|
+
schema_version: TASK_FINDING_SCHEMA_VERSION,
|
|
34898
|
+
id: finding2.id,
|
|
34899
|
+
task_id: finding2.task_id,
|
|
34900
|
+
run_id: finding2.run_id,
|
|
34901
|
+
fingerprint: finding2.fingerprint,
|
|
34902
|
+
title: finding2.title,
|
|
34903
|
+
severity: finding2.severity,
|
|
34904
|
+
status: finding2.status,
|
|
34905
|
+
source: finding2.source,
|
|
34906
|
+
summary: finding2.summary,
|
|
34907
|
+
artifact_path: finding2.artifact_path,
|
|
34908
|
+
first_seen_at: finding2.first_seen_at,
|
|
34909
|
+
last_seen_at: finding2.last_seen_at,
|
|
34910
|
+
resolved_at: finding2.resolved_at,
|
|
34911
|
+
metadata_keys: Object.keys(finding2.metadata).sort()
|
|
34912
|
+
};
|
|
34913
|
+
}
|
|
34914
|
+
function previewFinding(existing, next, timestamp3) {
|
|
34915
|
+
return {
|
|
34916
|
+
...existing,
|
|
34917
|
+
run_id: next.run_id,
|
|
34918
|
+
title: next.title,
|
|
34919
|
+
severity: next.severity,
|
|
34920
|
+
status: next.status,
|
|
34921
|
+
source: next.source,
|
|
34922
|
+
summary: next.summary,
|
|
34923
|
+
artifact_path: next.artifact_path,
|
|
34924
|
+
metadata: next.metadata,
|
|
34925
|
+
last_seen_at: timestamp3,
|
|
34926
|
+
resolved_at: next.status === "open" ? null : existing.resolved_at || timestamp3,
|
|
34927
|
+
updated_at: timestamp3
|
|
34928
|
+
};
|
|
34929
|
+
}
|
|
34930
|
+
function upsertAction(existing, next) {
|
|
34931
|
+
if (sameFinding(existing, next))
|
|
34932
|
+
return "matched";
|
|
34933
|
+
return existing.status !== "open" && next.status === "open" ? "reopened" : "updated";
|
|
34934
|
+
}
|
|
34935
|
+
function resolveRunForTask(runId, taskId, db) {
|
|
34936
|
+
if (!runId)
|
|
34937
|
+
return null;
|
|
34938
|
+
const resolved = resolveTaskRunId(runId, db);
|
|
34939
|
+
const run = getTaskRun(resolved, db);
|
|
34940
|
+
if (!run)
|
|
34941
|
+
throw new Error(`Run not found: ${runId}`);
|
|
34942
|
+
if (run.task_id !== taskId)
|
|
34943
|
+
throw new Error(`Run ${resolved} belongs to task ${run.task_id}, not ${taskId}`);
|
|
34944
|
+
return resolved;
|
|
34945
|
+
}
|
|
34946
|
+
function getFindingByFingerprint(taskId, fingerprint2, db) {
|
|
34947
|
+
const row = db.query("SELECT * FROM task_findings WHERE task_id = ? AND fingerprint = ?").get(taskId, fingerprint2);
|
|
34948
|
+
return row ? rowToFinding(row) : null;
|
|
34949
|
+
}
|
|
34950
|
+
function assertTask(taskId, db) {
|
|
34951
|
+
if (!getTask(taskId, db))
|
|
34952
|
+
throw new TaskNotFoundError(taskId);
|
|
34953
|
+
}
|
|
34954
|
+
function nextFinding(input, db) {
|
|
34955
|
+
const fingerprint2 = normalizeFingerprint(input.fingerprint);
|
|
34956
|
+
const title = redactOptional(input.title, 300);
|
|
34957
|
+
if (!title)
|
|
34958
|
+
throw new Error("finding title is required");
|
|
34959
|
+
return {
|
|
34960
|
+
fingerprint: fingerprint2,
|
|
34961
|
+
run_id: resolveRunForTask(input.run_id, input.task_id, db),
|
|
34962
|
+
title,
|
|
34963
|
+
severity: normalizeSeverity(input.severity),
|
|
34964
|
+
status: normalizeStatus2(input.status),
|
|
34965
|
+
source: redactOptional(input.source, 120),
|
|
34966
|
+
summary: redactOptional(input.summary, 2000),
|
|
34967
|
+
artifact_path: redactOptional(input.artifact_path, 1000),
|
|
34968
|
+
metadata: redactValue(input.metadata || {})
|
|
34969
|
+
};
|
|
34970
|
+
}
|
|
34971
|
+
function sameFinding(left, right) {
|
|
34972
|
+
return left.run_id === right.run_id && left.title === right.title && left.severity === right.severity && left.status === right.status && left.source === right.source && left.summary === right.summary && left.artifact_path === right.artifact_path && JSON.stringify(left.metadata) === JSON.stringify(right.metadata);
|
|
34973
|
+
}
|
|
34974
|
+
function upsertTaskFinding(input, db) {
|
|
34975
|
+
const d = db || getDatabase();
|
|
34976
|
+
assertTask(input.task_id, d);
|
|
34977
|
+
const timestamp3 = input.observed_at || now();
|
|
34978
|
+
const warnings = [];
|
|
34979
|
+
const next = nextFinding(input, d);
|
|
34980
|
+
const existing = getFindingByFingerprint(input.task_id, next.fingerprint, d);
|
|
34981
|
+
const dryRun = !input.apply;
|
|
34982
|
+
if (dryRun) {
|
|
34983
|
+
const action2 = existing ? upsertAction(existing, next) : "preview";
|
|
34984
|
+
return {
|
|
34985
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
34986
|
+
local_only: true,
|
|
34987
|
+
dry_run: true,
|
|
34988
|
+
processed_at: timestamp3,
|
|
34989
|
+
action: action2,
|
|
34990
|
+
fingerprint: next.fingerprint,
|
|
34991
|
+
finding: existing ? compactFinding(previewFinding(existing, next, timestamp3)) : null,
|
|
34992
|
+
warnings
|
|
34993
|
+
};
|
|
34994
|
+
}
|
|
34995
|
+
if (!existing) {
|
|
34996
|
+
const id = uuid();
|
|
34997
|
+
d.run(`INSERT INTO task_findings (
|
|
34998
|
+
id, task_id, run_id, fingerprint, title, severity, status, source, summary, artifact_path,
|
|
34999
|
+
metadata, first_seen_at, last_seen_at, resolved_at, created_at, updated_at
|
|
35000
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
35001
|
+
id,
|
|
35002
|
+
input.task_id,
|
|
35003
|
+
next.run_id,
|
|
35004
|
+
next.fingerprint,
|
|
35005
|
+
next.title,
|
|
35006
|
+
next.severity,
|
|
35007
|
+
next.status,
|
|
35008
|
+
next.source,
|
|
35009
|
+
next.summary,
|
|
35010
|
+
next.artifact_path,
|
|
35011
|
+
JSON.stringify(next.metadata),
|
|
35012
|
+
timestamp3,
|
|
35013
|
+
timestamp3,
|
|
35014
|
+
next.status === "open" ? null : timestamp3,
|
|
35015
|
+
timestamp3,
|
|
35016
|
+
timestamp3
|
|
35017
|
+
]);
|
|
35018
|
+
return {
|
|
35019
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
35020
|
+
local_only: true,
|
|
35021
|
+
dry_run: false,
|
|
35022
|
+
processed_at: timestamp3,
|
|
35023
|
+
action: "created",
|
|
35024
|
+
fingerprint: next.fingerprint,
|
|
35025
|
+
finding: compactFinding(getFindingByFingerprint(input.task_id, next.fingerprint, d)),
|
|
35026
|
+
warnings
|
|
35027
|
+
};
|
|
35028
|
+
}
|
|
35029
|
+
if (sameFinding(existing, next)) {
|
|
35030
|
+
return {
|
|
35031
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
35032
|
+
local_only: true,
|
|
35033
|
+
dry_run: false,
|
|
35034
|
+
processed_at: timestamp3,
|
|
35035
|
+
action: "matched",
|
|
35036
|
+
fingerprint: next.fingerprint,
|
|
35037
|
+
finding: compactFinding(existing),
|
|
35038
|
+
warnings
|
|
35039
|
+
};
|
|
35040
|
+
}
|
|
35041
|
+
const action = upsertAction(existing, next);
|
|
35042
|
+
d.run(`UPDATE task_findings SET
|
|
35043
|
+
run_id = ?, title = ?, severity = ?, status = ?, source = ?, summary = ?, artifact_path = ?,
|
|
35044
|
+
metadata = ?, last_seen_at = ?, resolved_at = ?, updated_at = ?
|
|
35045
|
+
WHERE id = ?`, [
|
|
35046
|
+
next.run_id,
|
|
35047
|
+
next.title,
|
|
35048
|
+
next.severity,
|
|
35049
|
+
next.status,
|
|
35050
|
+
next.source,
|
|
35051
|
+
next.summary,
|
|
35052
|
+
next.artifact_path,
|
|
35053
|
+
JSON.stringify(next.metadata),
|
|
35054
|
+
timestamp3,
|
|
35055
|
+
next.status === "open" ? null : existing.resolved_at || timestamp3,
|
|
35056
|
+
timestamp3,
|
|
35057
|
+
existing.id
|
|
35058
|
+
]);
|
|
35059
|
+
return {
|
|
35060
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
35061
|
+
local_only: true,
|
|
35062
|
+
dry_run: false,
|
|
35063
|
+
processed_at: timestamp3,
|
|
35064
|
+
action,
|
|
35065
|
+
fingerprint: next.fingerprint,
|
|
35066
|
+
finding: compactFinding(getFindingByFingerprint(input.task_id, next.fingerprint, d)),
|
|
35067
|
+
warnings
|
|
35068
|
+
};
|
|
35069
|
+
}
|
|
35070
|
+
function listTaskFindings(filter = {}, db) {
|
|
35071
|
+
const d = db || getDatabase();
|
|
35072
|
+
const conditions = ["1=1"];
|
|
35073
|
+
const params = [];
|
|
35074
|
+
if (filter.task_id) {
|
|
35075
|
+
conditions.push("task_id = ?");
|
|
35076
|
+
params.push(filter.task_id);
|
|
35077
|
+
}
|
|
35078
|
+
if (filter.run_id) {
|
|
35079
|
+
conditions.push("run_id = ?");
|
|
35080
|
+
params.push(resolveTaskRunId(filter.run_id, d));
|
|
35081
|
+
}
|
|
35082
|
+
if (filter.status) {
|
|
35083
|
+
conditions.push("status = ?");
|
|
35084
|
+
params.push(normalizeStatus2(filter.status));
|
|
35085
|
+
}
|
|
35086
|
+
if (filter.source) {
|
|
35087
|
+
conditions.push("source = ?");
|
|
35088
|
+
params.push(redactOptional(filter.source, 120));
|
|
35089
|
+
}
|
|
35090
|
+
const limit = Math.min(Math.max(Math.floor(filter.limit ?? 50), 1), 500);
|
|
35091
|
+
const rows = d.query(`SELECT * FROM task_findings WHERE ${conditions.join(" AND ")} ORDER BY updated_at DESC, created_at DESC LIMIT ?`).all(...[...params, limit]);
|
|
35092
|
+
return rows.map(rowToFinding);
|
|
35093
|
+
}
|
|
35094
|
+
function listCompactTaskFindings(filter = {}, db) {
|
|
35095
|
+
return listTaskFindings(filter, db).map(compactFinding);
|
|
35096
|
+
}
|
|
35097
|
+
function resolveMissingTaskFindings(input, db) {
|
|
35098
|
+
const d = db || getDatabase();
|
|
35099
|
+
assertTask(input.task_id, d);
|
|
35100
|
+
const timestamp3 = input.resolved_at || now();
|
|
35101
|
+
const status = normalizeResolutionStatus(input.status);
|
|
35102
|
+
const runId = resolveRunForTask(input.run_id, input.task_id, d);
|
|
35103
|
+
const present = new Set(input.fingerprints.map(normalizeFingerprint));
|
|
35104
|
+
const warnings = [];
|
|
35105
|
+
const conditions = ["task_id = ?", "status = 'open'"];
|
|
35106
|
+
const params = [input.task_id];
|
|
35107
|
+
if (input.source) {
|
|
35108
|
+
conditions.push("source = ?");
|
|
35109
|
+
params.push(redactOptional(input.source, 120));
|
|
35110
|
+
}
|
|
35111
|
+
const candidates = d.query(`SELECT * FROM task_findings WHERE ${conditions.join(" AND ")} ORDER BY updated_at DESC, created_at DESC`).all(...params).map(rowToFinding).filter((finding2) => !present.has(finding2.fingerprint));
|
|
35112
|
+
const limit = Math.min(Math.max(Math.floor(input.limit ?? 50), 1), 200);
|
|
35113
|
+
const display = candidates.slice(0, limit);
|
|
35114
|
+
const omittedCount = Math.max(0, candidates.length - display.length);
|
|
35115
|
+
if (!input.apply) {
|
|
35116
|
+
return {
|
|
35117
|
+
schema_version: TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
|
|
35118
|
+
local_only: true,
|
|
35119
|
+
dry_run: true,
|
|
35120
|
+
processed_at: timestamp3,
|
|
35121
|
+
action: candidates.length > 0 ? "preview" : "noop",
|
|
35122
|
+
task_id: input.task_id,
|
|
35123
|
+
source: input.source ? redactOptional(input.source, 120) : null,
|
|
35124
|
+
run_id: runId,
|
|
35125
|
+
present_fingerprint_count: present.size,
|
|
35126
|
+
candidate_count: candidates.length,
|
|
35127
|
+
changed_count: 0,
|
|
35128
|
+
omitted_count: omittedCount,
|
|
35129
|
+
findings: display.map(compactFinding),
|
|
35130
|
+
warnings
|
|
35131
|
+
};
|
|
35132
|
+
}
|
|
35133
|
+
const metadataPatch = redactValue({
|
|
35134
|
+
resolved_by: {
|
|
35135
|
+
agent_id: input.agent_id ?? null,
|
|
35136
|
+
run_id: runId,
|
|
35137
|
+
reason: input.reason ? redactEvidenceText(input.reason) : "missing from latest loop finding set"
|
|
35138
|
+
}
|
|
35139
|
+
});
|
|
35140
|
+
const tx = d.transaction(() => {
|
|
35141
|
+
for (const finding2 of candidates) {
|
|
35142
|
+
d.run("UPDATE task_findings SET status = ?, resolved_at = ?, metadata = ?, updated_at = ? WHERE id = ? AND status = 'open'", [
|
|
35143
|
+
status,
|
|
35144
|
+
timestamp3,
|
|
35145
|
+
JSON.stringify({ ...finding2.metadata, ...metadataPatch }),
|
|
35146
|
+
timestamp3,
|
|
35147
|
+
finding2.id
|
|
35148
|
+
]);
|
|
35149
|
+
}
|
|
35150
|
+
});
|
|
35151
|
+
tx();
|
|
35152
|
+
const updated = candidates.map((finding2) => d.query("SELECT * FROM task_findings WHERE id = ?").get(finding2.id)).filter((row) => Boolean(row)).map(rowToFinding);
|
|
35153
|
+
const visibleUpdated = updated.slice(0, limit);
|
|
35154
|
+
return {
|
|
35155
|
+
schema_version: TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
|
|
35156
|
+
local_only: true,
|
|
35157
|
+
dry_run: false,
|
|
35158
|
+
processed_at: timestamp3,
|
|
35159
|
+
action: updated.length > 0 ? status : "noop",
|
|
35160
|
+
task_id: input.task_id,
|
|
35161
|
+
source: input.source ? redactOptional(input.source, 120) : null,
|
|
35162
|
+
run_id: runId,
|
|
35163
|
+
present_fingerprint_count: present.size,
|
|
35164
|
+
candidate_count: candidates.length,
|
|
35165
|
+
changed_count: updated.length,
|
|
35166
|
+
omitted_count: omittedCount,
|
|
35167
|
+
findings: visibleUpdated.map(compactFinding),
|
|
35168
|
+
warnings
|
|
35169
|
+
};
|
|
35170
|
+
}
|
|
35171
|
+
var TASK_FINDING_SCHEMA_VERSION = "todos.task_finding.v1", TASK_FINDING_UPSERT_SCHEMA_VERSION = "todos.task_finding_upsert.v1", TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION = "todos.task_finding_resolve_missing.v1", SEVERITIES, STATUSES;
|
|
35172
|
+
var init_findings = __esm(() => {
|
|
35173
|
+
init_redaction();
|
|
35174
|
+
init_types();
|
|
35175
|
+
init_database();
|
|
35176
|
+
init_tasks();
|
|
35177
|
+
init_task_runs();
|
|
35178
|
+
SEVERITIES = new Set(["low", "medium", "high", "critical"]);
|
|
35179
|
+
STATUSES = new Set(["open", "resolved", "ignored"]);
|
|
35180
|
+
});
|
|
35181
|
+
|
|
34242
35182
|
// src/lib/agent-run-dispatcher.ts
|
|
34243
35183
|
var exports_agent_run_dispatcher = {};
|
|
34244
35184
|
__export(exports_agent_run_dispatcher, {
|
|
@@ -35167,7 +36107,7 @@ function parseArray2(value) {
|
|
|
35167
36107
|
return [];
|
|
35168
36108
|
}
|
|
35169
36109
|
}
|
|
35170
|
-
function
|
|
36110
|
+
function parseObject3(value) {
|
|
35171
36111
|
try {
|
|
35172
36112
|
const parsed = JSON.parse(value || "{}");
|
|
35173
36113
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
@@ -35208,7 +36148,7 @@ function rowToKnowledgeRecord(row) {
|
|
|
35208
36148
|
agent_id: row.agent_id,
|
|
35209
36149
|
snapshot_id: row.snapshot_id,
|
|
35210
36150
|
tags: parseArray2(row.tags),
|
|
35211
|
-
metadata: redactValue(
|
|
36151
|
+
metadata: redactValue(parseObject3(row.metadata)),
|
|
35212
36152
|
created_at: row.created_at,
|
|
35213
36153
|
updated_at: row.updated_at
|
|
35214
36154
|
};
|
|
@@ -35427,7 +36367,7 @@ function parseArray3(value) {
|
|
|
35427
36367
|
return [];
|
|
35428
36368
|
}
|
|
35429
36369
|
}
|
|
35430
|
-
function
|
|
36370
|
+
function parseObject4(value) {
|
|
35431
36371
|
try {
|
|
35432
36372
|
const parsed = JSON.parse(value || "{}");
|
|
35433
36373
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
@@ -35476,7 +36416,7 @@ function rowToRisk(row) {
|
|
|
35476
36416
|
plan_id: row.plan_id,
|
|
35477
36417
|
task_id: row.task_id,
|
|
35478
36418
|
tags: parseArray3(row.tags),
|
|
35479
|
-
metadata: redactValue(
|
|
36419
|
+
metadata: redactValue(parseObject4(row.metadata)),
|
|
35480
36420
|
created_at: row.created_at,
|
|
35481
36421
|
updated_at: row.updated_at,
|
|
35482
36422
|
closed_at: row.closed_at
|
|
@@ -36327,7 +37267,7 @@ function extractUsage(value) {
|
|
|
36327
37267
|
}
|
|
36328
37268
|
return own;
|
|
36329
37269
|
}
|
|
36330
|
-
function
|
|
37270
|
+
function parseJsonObject6(value) {
|
|
36331
37271
|
if (!value)
|
|
36332
37272
|
return {};
|
|
36333
37273
|
try {
|
|
@@ -36499,7 +37439,7 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
36499
37439
|
completedRunMs += millisBetween(run.started_at, run.completed_at);
|
|
36500
37440
|
else
|
|
36501
37441
|
openRunMs += millisBetween(run.started_at, generatedAt);
|
|
36502
|
-
const usage = extractUsage(
|
|
37442
|
+
const usage = extractUsage(parseJsonObject6(run.metadata));
|
|
36503
37443
|
metadataUsage.tokens += usage.tokens;
|
|
36504
37444
|
metadataUsage.cost_usd += usage.cost_usd;
|
|
36505
37445
|
metadataUsage.duration_ms += usage.duration_ms;
|
|
@@ -36511,7 +37451,7 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
36511
37451
|
JOIN tasks t ON t.id = e.task_id
|
|
36512
37452
|
${runClause}`, runParams);
|
|
36513
37453
|
for (const event of eventRows) {
|
|
36514
|
-
const usage = extractUsage(
|
|
37454
|
+
const usage = extractUsage(parseJsonObject6(event.data));
|
|
36515
37455
|
metadataUsage.tokens += usage.tokens;
|
|
36516
37456
|
metadataUsage.cost_usd += usage.cost_usd;
|
|
36517
37457
|
metadataUsage.duration_ms += usage.duration_ms;
|
|
@@ -40312,6 +41252,38 @@ ${lines.join(`
|
|
|
40312
41252
|
}
|
|
40313
41253
|
});
|
|
40314
41254
|
}
|
|
41255
|
+
if (shouldRegisterTool("begin_task_run_transaction")) {
|
|
41256
|
+
server.tool("begin_task_run_transaction", "Preview or apply an idempotent local loop run transaction keyed by a stable loop/run id.", {
|
|
41257
|
+
task_id: exports_external.string().describe("Task ID"),
|
|
41258
|
+
key: exports_external.string().optional().describe("Stable idempotency key"),
|
|
41259
|
+
loop_id: exports_external.string().optional().describe("Loop identifier; used as key fallback"),
|
|
41260
|
+
loop_run_id: exports_external.string().optional().describe("Loop run identifier; used as key fallback"),
|
|
41261
|
+
agent_id: exports_external.string().optional().describe("Agent starting the run"),
|
|
41262
|
+
title: exports_external.string().optional().describe("Run title"),
|
|
41263
|
+
summary: exports_external.string().optional().describe("Run summary"),
|
|
41264
|
+
metadata: exports_external.record(exports_external.unknown()).optional().describe("Additional local metadata"),
|
|
41265
|
+
claim: exports_external.boolean().optional().describe("Claim/start the task before recording the run"),
|
|
41266
|
+
apply: exports_external.boolean().optional().describe("Apply the transaction; false or omitted is dry-run")
|
|
41267
|
+
}, async ({ task_id, key, loop_id, loop_run_id, agent_id, title, summary, metadata, claim, apply }) => {
|
|
41268
|
+
try {
|
|
41269
|
+
const result = beginTaskRunTransaction({
|
|
41270
|
+
task_id: resolveId(task_id),
|
|
41271
|
+
key,
|
|
41272
|
+
loop_id,
|
|
41273
|
+
loop_run_id,
|
|
41274
|
+
agent_id,
|
|
41275
|
+
title,
|
|
41276
|
+
summary,
|
|
41277
|
+
metadata,
|
|
41278
|
+
claim,
|
|
41279
|
+
apply
|
|
41280
|
+
});
|
|
41281
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
41282
|
+
} catch (e) {
|
|
41283
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
41284
|
+
}
|
|
41285
|
+
});
|
|
41286
|
+
}
|
|
40315
41287
|
if (shouldRegisterTool("list_task_runs")) {
|
|
40316
41288
|
server.tool("list_task_runs", "List local run ledger entries, optionally scoped to a task.", { task_id: exports_external.string().optional().describe("Optional task ID") }, async ({ task_id }) => {
|
|
40317
41289
|
try {
|
|
@@ -40409,15 +41381,117 @@ ${lines.join(`
|
|
|
40409
41381
|
});
|
|
40410
41382
|
}
|
|
40411
41383
|
if (shouldRegisterTool("finish_task_run")) {
|
|
40412
|
-
server.tool("finish_task_run", "Finish a local run ledger entry as completed, failed, or cancelled.", {
|
|
40413
|
-
run_id: exports_external.string().describe("Run ID or prefix"),
|
|
40414
|
-
|
|
41384
|
+
server.tool("finish_task_run", "Finish a local run ledger entry as completed, failed, or cancelled. Supports idempotent lookup by key.", {
|
|
41385
|
+
run_id: exports_external.string().optional().describe("Run ID or prefix"),
|
|
41386
|
+
key: exports_external.string().optional().describe("Idempotency key when run_id is omitted"),
|
|
41387
|
+
task_id: exports_external.string().optional().describe("Task scope for key lookup"),
|
|
41388
|
+
status: exports_external.enum(["completed", "failed", "cancelled"]).optional().describe("Final run status"),
|
|
40415
41389
|
summary: exports_external.string().optional().describe("Final summary"),
|
|
40416
|
-
agent_id: exports_external.string().optional().describe("Agent finishing the run")
|
|
40417
|
-
|
|
41390
|
+
agent_id: exports_external.string().optional().describe("Agent finishing the run"),
|
|
41391
|
+
apply: exports_external.boolean().optional().describe("Apply the transaction; false previews without mutating")
|
|
41392
|
+
}, async ({ run_id, key, task_id, status, summary, agent_id, apply }) => {
|
|
40418
41393
|
try {
|
|
40419
|
-
|
|
40420
|
-
|
|
41394
|
+
if (run_id && !key && apply === undefined) {
|
|
41395
|
+
const result2 = finishTaskRunTransaction({ run_id, status: status || "completed", summary, agent_id, apply: true });
|
|
41396
|
+
const run = result2.run ? getTaskRunLedger(result2.run.id).run : null;
|
|
41397
|
+
return { content: [{ type: "text", text: JSON.stringify(run, null, 2) }] };
|
|
41398
|
+
}
|
|
41399
|
+
const result = finishTaskRunTransaction({
|
|
41400
|
+
run_id,
|
|
41401
|
+
key,
|
|
41402
|
+
task_id: task_id ? resolveId(task_id) : undefined,
|
|
41403
|
+
status: status || "completed",
|
|
41404
|
+
summary,
|
|
41405
|
+
agent_id,
|
|
41406
|
+
apply: apply !== false
|
|
41407
|
+
});
|
|
41408
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
41409
|
+
} catch (e) {
|
|
41410
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
41411
|
+
}
|
|
41412
|
+
});
|
|
41413
|
+
}
|
|
41414
|
+
if (shouldRegisterTool("upsert_task_finding")) {
|
|
41415
|
+
server.tool("upsert_task_finding", "Preview or apply an idempotent local finding upsert scoped by task and fingerprint.", {
|
|
41416
|
+
task_id: exports_external.string().describe("Task ID"),
|
|
41417
|
+
fingerprint: exports_external.string().describe("Stable finding fingerprint"),
|
|
41418
|
+
title: exports_external.string().describe("Finding title"),
|
|
41419
|
+
severity: exports_external.enum(["low", "medium", "high", "critical"]).optional().describe("Finding severity"),
|
|
41420
|
+
status: exports_external.enum(["open", "resolved", "ignored"]).optional().describe("Finding status"),
|
|
41421
|
+
source: exports_external.string().optional().describe("Loop/tool source name"),
|
|
41422
|
+
summary: exports_external.string().optional().describe("Bounded finding summary"),
|
|
41423
|
+
artifact_path: exports_external.string().optional().describe("Local artifact path/reference; content is not read"),
|
|
41424
|
+
run_id: exports_external.string().optional().describe("Run ledger ID or prefix"),
|
|
41425
|
+
metadata: exports_external.record(exports_external.unknown()).optional().describe("Additional local metadata"),
|
|
41426
|
+
apply: exports_external.boolean().optional().describe("Apply the upsert; false or omitted is dry-run")
|
|
41427
|
+
}, async ({ task_id, fingerprint: fingerprint3, title, severity, status, source: source3, summary, artifact_path, run_id, metadata, apply }) => {
|
|
41428
|
+
try {
|
|
41429
|
+
const result = upsertTaskFinding({
|
|
41430
|
+
task_id: resolveId(task_id),
|
|
41431
|
+
fingerprint: fingerprint3,
|
|
41432
|
+
title,
|
|
41433
|
+
severity,
|
|
41434
|
+
status,
|
|
41435
|
+
source: source3,
|
|
41436
|
+
summary,
|
|
41437
|
+
artifact_path,
|
|
41438
|
+
run_id,
|
|
41439
|
+
metadata,
|
|
41440
|
+
apply
|
|
41441
|
+
});
|
|
41442
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
41443
|
+
} catch (e) {
|
|
41444
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
41445
|
+
}
|
|
41446
|
+
});
|
|
41447
|
+
}
|
|
41448
|
+
if (shouldRegisterTool("list_task_findings")) {
|
|
41449
|
+
server.tool("list_task_findings", "List compact local findings with bounded output.", {
|
|
41450
|
+
task_id: exports_external.string().optional().describe("Filter by task"),
|
|
41451
|
+
run_id: exports_external.string().optional().describe("Filter by run"),
|
|
41452
|
+
status: exports_external.enum(["open", "resolved", "ignored"]).optional().describe("Filter by status"),
|
|
41453
|
+
source: exports_external.string().optional().describe("Filter by source"),
|
|
41454
|
+
limit: exports_external.number().optional().describe("Maximum findings to return")
|
|
41455
|
+
}, async ({ task_id, run_id, status, source: source3, limit }) => {
|
|
41456
|
+
try {
|
|
41457
|
+
const findings = listCompactTaskFindings({
|
|
41458
|
+
task_id: task_id ? resolveId(task_id) : undefined,
|
|
41459
|
+
run_id,
|
|
41460
|
+
status,
|
|
41461
|
+
source: source3,
|
|
41462
|
+
limit
|
|
41463
|
+
});
|
|
41464
|
+
return { content: [{ type: "text", text: JSON.stringify(findings) }] };
|
|
41465
|
+
} catch (e) {
|
|
41466
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
41467
|
+
}
|
|
41468
|
+
});
|
|
41469
|
+
}
|
|
41470
|
+
if (shouldRegisterTool("resolve_missing_task_findings")) {
|
|
41471
|
+
server.tool("resolve_missing_task_findings", "Resolve open findings absent from the latest loop finding set.", {
|
|
41472
|
+
task_id: exports_external.string().describe("Task ID"),
|
|
41473
|
+
fingerprints: exports_external.array(exports_external.string()).optional().describe("Fingerprints still present in the latest run"),
|
|
41474
|
+
source: exports_external.string().optional().describe("Only resolve findings from this source"),
|
|
41475
|
+
run_id: exports_external.string().optional().describe("Run ledger ID or prefix for audit metadata"),
|
|
41476
|
+
status: exports_external.enum(["resolved", "ignored"]).optional().describe("Resolution status"),
|
|
41477
|
+
agent_id: exports_external.string().optional().describe("Agent resolving findings"),
|
|
41478
|
+
reason: exports_external.string().optional().describe("Resolution reason"),
|
|
41479
|
+
limit: exports_external.number().optional().describe("Maximum findings returned"),
|
|
41480
|
+
apply: exports_external.boolean().optional().describe("Apply resolution; false or omitted is dry-run")
|
|
41481
|
+
}, async ({ task_id, fingerprints, source: source3, run_id, status, agent_id, reason, limit, apply }) => {
|
|
41482
|
+
try {
|
|
41483
|
+
const result = resolveMissingTaskFindings({
|
|
41484
|
+
task_id: resolveId(task_id),
|
|
41485
|
+
fingerprints: fingerprints || [],
|
|
41486
|
+
source: source3,
|
|
41487
|
+
run_id,
|
|
41488
|
+
status,
|
|
41489
|
+
agent_id,
|
|
41490
|
+
reason,
|
|
41491
|
+
limit,
|
|
41492
|
+
apply
|
|
41493
|
+
});
|
|
41494
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
40421
41495
|
} catch (e) {
|
|
40422
41496
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
40423
41497
|
}
|
|
@@ -40753,6 +41827,7 @@ var init_task_resources = __esm(() => {
|
|
|
40753
41827
|
init_agents();
|
|
40754
41828
|
init_task_commits();
|
|
40755
41829
|
init_task_runs();
|
|
41830
|
+
init_findings();
|
|
40756
41831
|
init_agent_run_dispatcher();
|
|
40757
41832
|
init_verification_providers();
|
|
40758
41833
|
init_release_notes();
|
|
@@ -44493,8 +45568,10 @@ function taskToSummary(task2, fields) {
|
|
|
44493
45568
|
task_list_id: task2.task_list_id,
|
|
44494
45569
|
agent_id: task2.agent_id,
|
|
44495
45570
|
assigned_to: task2.assigned_to,
|
|
45571
|
+
working_dir: task2.working_dir,
|
|
44496
45572
|
locked_by: task2.locked_by,
|
|
44497
45573
|
tags: task2.tags,
|
|
45574
|
+
metadata: task2.metadata,
|
|
44498
45575
|
version: task2.version,
|
|
44499
45576
|
created_at: task2.created_at,
|
|
44500
45577
|
updated_at: task2.updated_at,
|
|
@@ -44636,6 +45713,9 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
44636
45713
|
if (path === "/api/tasks" && method === "POST") {
|
|
44637
45714
|
return handleCreateTask(req, ctx, jsonWithCors, taskToSummary);
|
|
44638
45715
|
}
|
|
45716
|
+
if (path === "/api/tasks/upsert" && method === "POST") {
|
|
45717
|
+
return handleUpsertTask(req, ctx, jsonWithCors, taskToSummary);
|
|
45718
|
+
}
|
|
44639
45719
|
if (path === "/api/tasks/export" && method === "GET") {
|
|
44640
45720
|
return handleTasksExport(req, url, ctx, jsonWithCors, taskToSummary);
|
|
44641
45721
|
}
|
|
@@ -44831,7 +45911,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
44831
45911
|
} catch {}
|
|
44832
45912
|
}
|
|
44833
45913
|
}
|
|
44834
|
-
var MIME_TYPES, SECURITY_HEADERS, rateLimitMap, RATE_LIMIT_WINDOW_MS = 60000, RATE_LIMIT_MAX
|
|
45914
|
+
var MIME_TYPES, SECURITY_HEADERS, rateLimitMap, RATE_LIMIT_WINDOW_MS = 60000, RATE_LIMIT_MAX;
|
|
44835
45915
|
var init_serve = __esm(() => {
|
|
44836
45916
|
init_database();
|
|
44837
45917
|
init_api_keys();
|
|
@@ -44856,6 +45936,7 @@ var init_serve = __esm(() => {
|
|
|
44856
45936
|
"Permissions-Policy": "camera=, microphone=, geolocation="
|
|
44857
45937
|
};
|
|
44858
45938
|
rateLimitMap = new Map;
|
|
45939
|
+
RATE_LIMIT_MAX = Number.parseInt(process.env["TODOS_RATE_LIMIT_MAX"] || "120", 10);
|
|
44859
45940
|
});
|
|
44860
45941
|
|
|
44861
45942
|
// src/cli/components/Header.tsx
|
|
@@ -47509,9 +48590,9 @@ function normalizeKind(value) {
|
|
|
47509
48590
|
const raw = cleanKey(asString4(value) || "unknown").replace(/-/g, "_");
|
|
47510
48591
|
return KINDS.has(raw) ? raw : raw || "unknown";
|
|
47511
48592
|
}
|
|
47512
|
-
function
|
|
48593
|
+
function normalizeSeverity2(value, fallback) {
|
|
47513
48594
|
const raw = cleanKey(asString4(value) || fallback);
|
|
47514
|
-
if (
|
|
48595
|
+
if (SEVERITIES2.has(raw))
|
|
47515
48596
|
return raw;
|
|
47516
48597
|
if (/^(p0|blocker|urgent|highest)$/.test(raw))
|
|
47517
48598
|
return "critical";
|
|
@@ -47606,7 +48687,7 @@ function normalizeTesterIssueReport(value, fallbackPriority = "medium") {
|
|
|
47606
48687
|
title,
|
|
47607
48688
|
summary: truncate3(asString4(input["summary"]), 1000) ?? null,
|
|
47608
48689
|
kind: normalizeKind(input["kind"] ?? input["type"]),
|
|
47609
|
-
severity:
|
|
48690
|
+
severity: normalizeSeverity2(input["severity"] ?? input["priority"], fallbackPriority),
|
|
47610
48691
|
source: source3,
|
|
47611
48692
|
target,
|
|
47612
48693
|
failure,
|
|
@@ -47904,13 +48985,13 @@ function readTesterIssueReportsPayload(value) {
|
|
|
47904
48985
|
return record["issues"];
|
|
47905
48986
|
return [value];
|
|
47906
48987
|
}
|
|
47907
|
-
var TESTERS_ISSUE_REPORT_SCHEMA_VERSION = "testers.issue_report.v1", TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_result.v1", TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_batch_result.v1", PRIORITIES5,
|
|
48988
|
+
var TESTERS_ISSUE_REPORT_SCHEMA_VERSION = "testers.issue_report.v1", TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_result.v1", TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_batch_result.v1", PRIORITIES5, SEVERITIES2, KINDS;
|
|
47908
48989
|
var init_tester_issue_reports = __esm(() => {
|
|
47909
48990
|
init_database();
|
|
47910
48991
|
init_tasks();
|
|
47911
48992
|
init_redaction();
|
|
47912
48993
|
PRIORITIES5 = ["low", "medium", "high", "critical"];
|
|
47913
|
-
|
|
48994
|
+
SEVERITIES2 = new Set(PRIORITIES5);
|
|
47914
48995
|
KINDS = new Set([
|
|
47915
48996
|
"assertion_failure",
|
|
47916
48997
|
"runtime_error",
|
|
@@ -51092,6 +52173,23 @@ Verifications:`));
|
|
|
51092
52173
|
console.log(`${result.status} ${result.provider}: ${result.output_summary || ""}`);
|
|
51093
52174
|
});
|
|
51094
52175
|
const runs = program2.command("runs").description("Manage the local run ledger and evidence capture");
|
|
52176
|
+
runs.command("begin <task-id>").description("Preview or apply an idempotent loop run transaction").option("--key <key>", "Stable idempotency key for this loop transaction").option("--loop-id <id>", "Loop identifier; used as the key when --key/--loop-run-id are omitted").option("--loop-run-id <id>", "Loop run identifier; used as the key when --key is omitted").option("--agent <name>", "Agent starting the run").option("--title <text>", "Run title").option("--summary <text>", "Run summary").option("--metadata <json>", "Additional JSON metadata").option("--claim", "Claim/start the task for the agent before recording the run").option("--apply", "Apply the transaction; omitted means dry-run").action(async (taskId, opts) => {
|
|
52177
|
+
const globalOpts = program2.opts();
|
|
52178
|
+
const { beginTaskRunTransaction: beginTaskRunTransaction2 } = await Promise.resolve().then(() => (init_task_runs(), exports_task_runs));
|
|
52179
|
+
const result = beginTaskRunTransaction2({
|
|
52180
|
+
task_id: resolveTaskId(taskId),
|
|
52181
|
+
key: opts.key,
|
|
52182
|
+
loop_id: opts.loopId,
|
|
52183
|
+
loop_run_id: opts.loopRunId,
|
|
52184
|
+
agent_id: opts.agent || globalOpts.agent,
|
|
52185
|
+
title: opts.title,
|
|
52186
|
+
summary: opts.summary,
|
|
52187
|
+
metadata: parseJsonOption(opts.metadata, "--metadata"),
|
|
52188
|
+
claim: opts.claim,
|
|
52189
|
+
apply: opts.apply
|
|
52190
|
+
});
|
|
52191
|
+
output(result, true);
|
|
52192
|
+
});
|
|
51095
52193
|
runs.command("start <task-id>").description("Start a local run ledger entry for a task").option("--agent <name>", "Agent starting the run").option("--title <text>", "Run title").option("--summary <text>", "Run summary").option("--metadata <json>", "Additional JSON metadata").option("--claim", "Claim/start the task for the agent before recording the run").action(async (taskId, opts) => {
|
|
51096
52194
|
const globalOpts = program2.opts();
|
|
51097
52195
|
const { startTaskRun: startTaskRun2 } = await Promise.resolve().then(() => (init_task_runs(), exports_task_runs));
|
|
@@ -51303,19 +52401,68 @@ Artifacts:`));
|
|
|
51303
52401
|
process.exit(1);
|
|
51304
52402
|
}
|
|
51305
52403
|
});
|
|
51306
|
-
runs.command("finish
|
|
52404
|
+
runs.command("finish [run-id]").description("Finish a run ledger entry idempotently").option("--key <key>", "Resolve run by idempotency key when run-id is omitted").option("--task <task-id>", "Task scope for --key lookup").option("--status <status>", "completed, failed, or cancelled", "completed").option("--summary <text>", "Final summary").option("--agent <name>", "Agent finishing the run").option("--dry-run", "Preview without mutating").action(async (runId, opts) => {
|
|
51307
52405
|
const globalOpts = program2.opts();
|
|
51308
52406
|
if (opts.status !== "completed" && opts.status !== "failed" && opts.status !== "cancelled") {
|
|
51309
52407
|
console.error(chalk8.red("--status must be completed, failed, or cancelled"));
|
|
51310
52408
|
process.exit(1);
|
|
51311
52409
|
}
|
|
51312
|
-
const {
|
|
51313
|
-
const
|
|
51314
|
-
|
|
51315
|
-
|
|
51316
|
-
|
|
51317
|
-
|
|
51318
|
-
|
|
52410
|
+
const { finishTaskRunTransaction: finishTaskRunTransaction2 } = await Promise.resolve().then(() => (init_task_runs(), exports_task_runs));
|
|
52411
|
+
const result = finishTaskRunTransaction2({
|
|
52412
|
+
run_id: runId,
|
|
52413
|
+
key: opts.key,
|
|
52414
|
+
task_id: opts.task ? resolveTaskId(opts.task) : undefined,
|
|
52415
|
+
status: opts.status,
|
|
52416
|
+
summary: opts.summary,
|
|
52417
|
+
agent_id: opts.agent || globalOpts.agent,
|
|
52418
|
+
apply: !opts.dryRun
|
|
52419
|
+
});
|
|
52420
|
+
output(result, true);
|
|
52421
|
+
});
|
|
52422
|
+
const findings = program2.command("findings").description("Manage local task findings for loop dedupe and resolution");
|
|
52423
|
+
findings.command("upsert").description("Preview or apply an idempotent finding upsert").requiredOption("--task <task-id>", "Task ID").requiredOption("--fingerprint <value>", "Stable finding fingerprint").requiredOption("--title <text>", "Finding title").option("--severity <severity>", "low, medium, high, or critical", "medium").option("--status <status>", "open, resolved, or ignored", "open").option("--source <source>", "Loop/tool source name").option("--summary <text>", "Bounded finding summary").option("--artifact <path>", "Local artifact path/reference; content is not read").option("--run <run-id>", "Run ledger ID or prefix").option("--metadata <json>", "Additional JSON metadata").option("--apply", "Apply the upsert; omitted means dry-run").action(async (opts) => {
|
|
52424
|
+
const { upsertTaskFinding: upsertTaskFinding2 } = await Promise.resolve().then(() => (init_findings(), exports_findings));
|
|
52425
|
+
const result = upsertTaskFinding2({
|
|
52426
|
+
task_id: resolveTaskId(opts.task),
|
|
52427
|
+
fingerprint: opts.fingerprint,
|
|
52428
|
+
title: opts.title,
|
|
52429
|
+
severity: opts.severity,
|
|
52430
|
+
status: opts.status,
|
|
52431
|
+
source: opts.source,
|
|
52432
|
+
summary: opts.summary,
|
|
52433
|
+
artifact_path: opts.artifact,
|
|
52434
|
+
run_id: opts.run,
|
|
52435
|
+
metadata: parseJsonOption(opts.metadata, "--metadata"),
|
|
52436
|
+
apply: opts.apply
|
|
52437
|
+
});
|
|
52438
|
+
output(result, true);
|
|
52439
|
+
});
|
|
52440
|
+
findings.command("resolve-missing").description("Resolve open findings absent from the latest loop finding set").requiredOption("--task <task-id>", "Task ID").option("--fingerprints <list>", "Comma-separated fingerprints still present").option("--source <source>", "Only resolve findings from this source").option("--run <run-id>", "Run ledger ID or prefix for audit metadata").option("--status <status>", "resolved or ignored", "resolved").option("--agent <name>", "Agent resolving findings").option("--reason <text>", "Resolution reason").option("--limit <n>", "Maximum findings returned", "50").option("--apply", "Apply resolution; omitted means dry-run").action(async (opts) => {
|
|
52441
|
+
const globalOpts = program2.opts();
|
|
52442
|
+
const { resolveMissingTaskFindings: resolveMissingTaskFindings2 } = await Promise.resolve().then(() => (init_findings(), exports_findings));
|
|
52443
|
+
const result = resolveMissingTaskFindings2({
|
|
52444
|
+
task_id: resolveTaskId(opts.task),
|
|
52445
|
+
fingerprints: listOption(opts.fingerprints) || [],
|
|
52446
|
+
source: opts.source,
|
|
52447
|
+
run_id: opts.run,
|
|
52448
|
+
status: opts.status,
|
|
52449
|
+
agent_id: opts.agent || globalOpts.agent,
|
|
52450
|
+
reason: opts.reason,
|
|
52451
|
+
limit: opts.limit ? Number.parseInt(opts.limit, 10) : undefined,
|
|
52452
|
+
apply: opts.apply
|
|
52453
|
+
});
|
|
52454
|
+
output(result, true);
|
|
52455
|
+
});
|
|
52456
|
+
findings.command("list").description("List compact local findings").option("--task <task-id>", "Filter by task").option("--run <run-id>", "Filter by run").option("--status <status>", "Filter by open, resolved, or ignored").option("--source <source>", "Filter by source").option("--limit <n>", "Maximum findings returned", "50").action(async (opts) => {
|
|
52457
|
+
const { listCompactTaskFindings: listCompactTaskFindings2 } = await Promise.resolve().then(() => (init_findings(), exports_findings));
|
|
52458
|
+
const findings2 = listCompactTaskFindings2({
|
|
52459
|
+
task_id: opts.task ? resolveTaskId(opts.task) : undefined,
|
|
52460
|
+
run_id: opts.run,
|
|
52461
|
+
status: opts.status,
|
|
52462
|
+
source: opts.source,
|
|
52463
|
+
limit: opts.limit ? Number.parseInt(opts.limit, 10) : undefined
|
|
52464
|
+
});
|
|
52465
|
+
output(findings2, true);
|
|
51319
52466
|
});
|
|
51320
52467
|
const agentRuns = program2.command("agent-runs").description("Queue and dispatch local agent runs");
|
|
51321
52468
|
agentRuns.command("adapter-set <name>").description("Create or update a local agent run adapter").option("--command <command>", "Local command template. Supports {task_id}, {run_id}, and {agent_id}").option("--sandbox <name>", "Runner sandbox profile to check before launch").option("--cwd <path>", "Command working directory").option("--env <json>", "Static adapter environment as a JSON object").action(async (name, opts) => {
|
|
@@ -52229,7 +53376,7 @@ function parseRecordType(value) {
|
|
|
52229
53376
|
console.error(chalk13.red(`type must be one of: ${RECORD_TYPES.join(", ")}`));
|
|
52230
53377
|
process.exit(1);
|
|
52231
53378
|
}
|
|
52232
|
-
function
|
|
53379
|
+
function parseJsonObject7(value, label) {
|
|
52233
53380
|
if (!value)
|
|
52234
53381
|
return;
|
|
52235
53382
|
try {
|
|
@@ -52289,7 +53436,7 @@ function registerKnowledgeCommands(program2) {
|
|
|
52289
53436
|
plan_id: opts.plan,
|
|
52290
53437
|
agent_id: opts.agent || globalOpts.agent,
|
|
52291
53438
|
tags: tagsFromOption(opts.tag),
|
|
52292
|
-
metadata:
|
|
53439
|
+
metadata: parseJsonObject7(opts.metadataJson, "--metadata-json")
|
|
52293
53440
|
}, getDatabase());
|
|
52294
53441
|
if (opts.json || globalOpts.json)
|
|
52295
53442
|
output(record, true);
|
|
@@ -52314,7 +53461,7 @@ function registerKnowledgeCommands(program2) {
|
|
|
52314
53461
|
blockers: opts.blocker,
|
|
52315
53462
|
next_steps: opts.next,
|
|
52316
53463
|
tags: tagsFromOption(opts.tag),
|
|
52317
|
-
metadata:
|
|
53464
|
+
metadata: parseJsonObject7(opts.metadataJson, "--metadata-json")
|
|
52318
53465
|
}, getDatabase());
|
|
52319
53466
|
if (opts.json || globalOpts.json)
|
|
52320
53467
|
output(result, true);
|
|
@@ -52400,7 +53547,7 @@ function parseChoice(value, choices, label) {
|
|
|
52400
53547
|
console.error(chalk14.red(`${label} must be one of: ${choices.join(", ")}`));
|
|
52401
53548
|
process.exit(1);
|
|
52402
53549
|
}
|
|
52403
|
-
function
|
|
53550
|
+
function parseJsonObject8(value, label) {
|
|
52404
53551
|
if (!value)
|
|
52405
53552
|
return;
|
|
52406
53553
|
try {
|
|
@@ -52421,8 +53568,8 @@ function tagsFromOption2(value) {
|
|
|
52421
53568
|
}
|
|
52422
53569
|
function commonFilters2(opts) {
|
|
52423
53570
|
return {
|
|
52424
|
-
status: opts.status ? parseChoice(opts.status,
|
|
52425
|
-
severity: opts.severity ? parseChoice(opts.severity,
|
|
53571
|
+
status: opts.status ? parseChoice(opts.status, STATUSES2, "--status") : undefined,
|
|
53572
|
+
severity: opts.severity ? parseChoice(opts.severity, SEVERITIES3, "--severity") : undefined,
|
|
52426
53573
|
probability: opts.probability ? parseChoice(opts.probability, PROBABILITIES, "--probability") : undefined,
|
|
52427
53574
|
owner: opts.owner,
|
|
52428
53575
|
project_id: opts.project,
|
|
@@ -52455,8 +53602,8 @@ function registerRiskCommands(program2) {
|
|
|
52455
53602
|
const risk = createRisk({
|
|
52456
53603
|
title,
|
|
52457
53604
|
description: opts.description,
|
|
52458
|
-
status: parseChoice(opts.status,
|
|
52459
|
-
severity: parseChoice(opts.severity,
|
|
53605
|
+
status: parseChoice(opts.status, STATUSES2, "--status"),
|
|
53606
|
+
severity: parseChoice(opts.severity, SEVERITIES3, "--severity"),
|
|
52460
53607
|
probability: parseChoice(opts.probability, PROBABILITIES, "--probability"),
|
|
52461
53608
|
owner: opts.owner,
|
|
52462
53609
|
mitigation: opts.mitigation,
|
|
@@ -52465,7 +53612,7 @@ function registerRiskCommands(program2) {
|
|
|
52465
53612
|
plan_id: opts.plan,
|
|
52466
53613
|
task_id: opts.task,
|
|
52467
53614
|
tags: tagsFromOption2(opts.tag),
|
|
52468
|
-
metadata:
|
|
53615
|
+
metadata: parseJsonObject8(opts.metadataJson, "--metadata-json")
|
|
52469
53616
|
}, getDatabase());
|
|
52470
53617
|
if (opts.json || globalOpts.json)
|
|
52471
53618
|
output(risk, true);
|
|
@@ -52507,8 +53654,8 @@ function registerRiskCommands(program2) {
|
|
|
52507
53654
|
const risk = updateRisk(id, {
|
|
52508
53655
|
title: opts.title,
|
|
52509
53656
|
description: opts.description,
|
|
52510
|
-
status: opts.status ? parseChoice(opts.status,
|
|
52511
|
-
severity: opts.severity ? parseChoice(opts.severity,
|
|
53657
|
+
status: opts.status ? parseChoice(opts.status, STATUSES2, "--status") : undefined,
|
|
53658
|
+
severity: opts.severity ? parseChoice(opts.severity, SEVERITIES3, "--severity") : undefined,
|
|
52512
53659
|
probability: opts.probability ? parseChoice(opts.probability, PROBABILITIES, "--probability") : undefined,
|
|
52513
53660
|
owner: opts.owner,
|
|
52514
53661
|
mitigation: opts.mitigation,
|
|
@@ -52517,7 +53664,7 @@ function registerRiskCommands(program2) {
|
|
|
52517
53664
|
plan_id: opts.plan,
|
|
52518
53665
|
task_id: opts.task,
|
|
52519
53666
|
tags: opts.tag.length > 0 ? tagsFromOption2(opts.tag) : undefined,
|
|
52520
|
-
metadata:
|
|
53667
|
+
metadata: parseJsonObject8(opts.metadataJson, "--metadata-json")
|
|
52521
53668
|
}, getDatabase());
|
|
52522
53669
|
if (opts.json || globalOpts.json)
|
|
52523
53670
|
output(risk, true);
|
|
@@ -52575,13 +53722,13 @@ function registerRiskCommands(program2) {
|
|
|
52575
53722
|
}
|
|
52576
53723
|
});
|
|
52577
53724
|
}
|
|
52578
|
-
var
|
|
53725
|
+
var STATUSES2, SEVERITIES3, PROBABILITIES;
|
|
52579
53726
|
var init_risk_commands = __esm(() => {
|
|
52580
53727
|
init_database();
|
|
52581
53728
|
init_project_risks();
|
|
52582
53729
|
init_helpers();
|
|
52583
|
-
|
|
52584
|
-
|
|
53730
|
+
STATUSES2 = ["open", "mitigating", "resolved", "accepted"];
|
|
53731
|
+
SEVERITIES3 = ["low", "medium", "high", "critical"];
|
|
52585
53732
|
PROBABILITIES = ["low", "medium", "high"];
|
|
52586
53733
|
});
|
|
52587
53734
|
|
|
@@ -55166,6 +56313,92 @@ var init_json_contracts = __esm(() => {
|
|
|
55166
56313
|
},
|
|
55167
56314
|
optional: {}
|
|
55168
56315
|
}),
|
|
56316
|
+
contract({
|
|
56317
|
+
id: "loop_run_transaction",
|
|
56318
|
+
name: "Loop Run Transaction",
|
|
56319
|
+
description: "Compact local result for idempotent loop run begin/finish transactions.",
|
|
56320
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
56321
|
+
stability: "stable",
|
|
56322
|
+
required: {
|
|
56323
|
+
schema_version: field("string", "Result schema version."),
|
|
56324
|
+
local_only: field("boolean", "Always true; loop run transactions use local state."),
|
|
56325
|
+
dry_run: field("boolean", "True when no run ledger mutation was applied."),
|
|
56326
|
+
processed_at: isoDateField,
|
|
56327
|
+
action: field("string", "preview, created, matched, finished, or conflict."),
|
|
56328
|
+
key: field("string", "Stable idempotency key used to dedupe the transaction."),
|
|
56329
|
+
run: field(["object", "null"], "Compact run summary or null for create previews.", true),
|
|
56330
|
+
warnings: field("array", "Non-fatal warnings such as terminal-status conflicts."),
|
|
56331
|
+
commands: field("array", "Follow-up CLI commands for agents and operators.")
|
|
56332
|
+
},
|
|
56333
|
+
optional: {}
|
|
56334
|
+
}),
|
|
56335
|
+
contract({
|
|
56336
|
+
id: "task_finding",
|
|
56337
|
+
name: "Task Finding",
|
|
56338
|
+
description: "Compact local finding record deduped by task and fingerprint.",
|
|
56339
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
56340
|
+
stability: "stable",
|
|
56341
|
+
required: {
|
|
56342
|
+
schema_version: field("string", "Finding schema version."),
|
|
56343
|
+
id: idField,
|
|
56344
|
+
task_id: idField,
|
|
56345
|
+
run_id: field(["string", "null"], "Optional run ledger ID.", true),
|
|
56346
|
+
fingerprint: field("string", "Stable finding fingerprint scoped to the task."),
|
|
56347
|
+
title: field("string", "Short redacted finding title."),
|
|
56348
|
+
severity: field("string", "low, medium, high, or critical."),
|
|
56349
|
+
status: field("string", "open, resolved, or ignored."),
|
|
56350
|
+
source: field(["string", "null"], "Optional loop/tool source.", true),
|
|
56351
|
+
summary: field(["string", "null"], "Bounded redacted finding summary.", true),
|
|
56352
|
+
artifact_path: field(["string", "null"], "Local artifact path/reference; raw content is not included.", true),
|
|
56353
|
+
first_seen_at: isoDateField,
|
|
56354
|
+
last_seen_at: isoDateField,
|
|
56355
|
+
resolved_at: field(["string", "null"], "Resolution timestamp when closed.", true),
|
|
56356
|
+
metadata_keys: field("array", "Sorted metadata keys; metadata values are intentionally omitted in compact output.")
|
|
56357
|
+
},
|
|
56358
|
+
optional: {}
|
|
56359
|
+
}),
|
|
56360
|
+
contract({
|
|
56361
|
+
id: "task_finding_upsert",
|
|
56362
|
+
name: "Task Finding Upsert Result",
|
|
56363
|
+
description: "Local-only dry-run or applied result from idempotently upserting a task finding.",
|
|
56364
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
56365
|
+
stability: "stable",
|
|
56366
|
+
required: {
|
|
56367
|
+
schema_version: field("string", "Result schema version."),
|
|
56368
|
+
local_only: field("boolean", "Always true; finding upserts use local state."),
|
|
56369
|
+
dry_run: field("boolean", "True when no finding row was created or updated."),
|
|
56370
|
+
processed_at: isoDateField,
|
|
56371
|
+
action: field("string", "preview, created, matched, updated, or reopened."),
|
|
56372
|
+
fingerprint: field("string", "Normalized finding fingerprint."),
|
|
56373
|
+
finding: field(["object", "null"], "Compact finding summary or null for create previews.", true),
|
|
56374
|
+
warnings: field("array", "Non-fatal warnings.")
|
|
56375
|
+
},
|
|
56376
|
+
optional: {}
|
|
56377
|
+
}),
|
|
56378
|
+
contract({
|
|
56379
|
+
id: "task_finding_resolve_missing",
|
|
56380
|
+
name: "Task Finding Resolve Missing Result",
|
|
56381
|
+
description: "Local-only dry-run or applied result from resolving open findings absent from the latest loop finding set.",
|
|
56382
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
56383
|
+
stability: "stable",
|
|
56384
|
+
required: {
|
|
56385
|
+
schema_version: field("string", "Result schema version."),
|
|
56386
|
+
local_only: field("boolean", "Always true; finding resolution uses local state."),
|
|
56387
|
+
dry_run: field("boolean", "True when no finding rows were changed."),
|
|
56388
|
+
processed_at: isoDateField,
|
|
56389
|
+
action: field("string", "preview, resolved, ignored, or noop."),
|
|
56390
|
+
task_id: idField,
|
|
56391
|
+
source: field(["string", "null"], "Optional source scope.", true),
|
|
56392
|
+
run_id: field(["string", "null"], "Optional run ledger ID used for audit metadata.", true),
|
|
56393
|
+
present_fingerprint_count: field("integer", "Number of fingerprints supplied as still present."),
|
|
56394
|
+
candidate_count: field("integer", "Open findings missing from the supplied set."),
|
|
56395
|
+
changed_count: field("integer", "Rows resolved or ignored by the applied transaction."),
|
|
56396
|
+
omitted_count: field("integer", "Matching findings omitted from bounded output."),
|
|
56397
|
+
findings: field("array", "Bounded compact finding summaries."),
|
|
56398
|
+
warnings: field("array", "Non-fatal warnings.")
|
|
56399
|
+
},
|
|
56400
|
+
optional: {}
|
|
56401
|
+
}),
|
|
55169
56402
|
contract({
|
|
55170
56403
|
id: "verification_provider",
|
|
55171
56404
|
name: "Verification Provider",
|