@hasna/todos 0.11.57 → 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 +1622 -285
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +703 -21
- 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 +1071 -103
- 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 +1186 -115
- 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 +703 -21
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +1186 -115
- 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 +574 -97
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
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",
|
|
@@ -6154,7 +6239,7 @@ var init_event_hooks = __esm(() => {
|
|
|
6154
6239
|
VALID_TARGETS = new Set(["stdout", "file", "socket", "script"]);
|
|
6155
6240
|
});
|
|
6156
6241
|
|
|
6157
|
-
// node_modules/.bun/@hasna+events@0.1.
|
|
6242
|
+
// node_modules/.bun/@hasna+events@0.1.9/node_modules/@hasna/events/dist/index.js
|
|
6158
6243
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
6159
6244
|
import { existsSync as existsSync6 } from "fs";
|
|
6160
6245
|
import { homedir } from "os";
|
|
@@ -6171,17 +6256,30 @@ function getPathValue(input, path) {
|
|
|
6171
6256
|
return;
|
|
6172
6257
|
}, input);
|
|
6173
6258
|
}
|
|
6174
|
-
function wildcardToRegExp(pattern) {
|
|
6175
|
-
|
|
6176
|
-
|
|
6259
|
+
function wildcardToRegExp(pattern, options = {}) {
|
|
6260
|
+
let body = "";
|
|
6261
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
6262
|
+
const char = pattern[index];
|
|
6263
|
+
if (char === "*") {
|
|
6264
|
+
if (pattern[index + 1] === "*") {
|
|
6265
|
+
body += ".*";
|
|
6266
|
+
index += 1;
|
|
6267
|
+
} else {
|
|
6268
|
+
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
6269
|
+
}
|
|
6270
|
+
} else {
|
|
6271
|
+
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
6272
|
+
}
|
|
6273
|
+
}
|
|
6274
|
+
return new RegExp(`^${body}$`);
|
|
6177
6275
|
}
|
|
6178
|
-
function matchString(value, matcher) {
|
|
6276
|
+
function matchString(value, matcher, options = {}) {
|
|
6179
6277
|
if (matcher === undefined)
|
|
6180
6278
|
return true;
|
|
6181
6279
|
if (value === undefined)
|
|
6182
6280
|
return false;
|
|
6183
6281
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
6184
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
6282
|
+
return matchers.some((item) => wildcardToRegExp(item, options).test(value));
|
|
6185
6283
|
}
|
|
6186
6284
|
function matchRecord(input, matcher) {
|
|
6187
6285
|
if (!matcher)
|
|
@@ -6189,7 +6287,9 @@ function matchRecord(input, matcher) {
|
|
|
6189
6287
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
6190
6288
|
const actual = getPathValue(input, path);
|
|
6191
6289
|
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
6192
|
-
return matchString(actual === undefined ? undefined : String(actual), expected
|
|
6290
|
+
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
6291
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
6292
|
+
});
|
|
6193
6293
|
}
|
|
6194
6294
|
return actual === expected;
|
|
6195
6295
|
});
|
|
@@ -6543,7 +6643,7 @@ class EventsClient {
|
|
|
6543
6643
|
}
|
|
6544
6644
|
return deliveries;
|
|
6545
6645
|
}
|
|
6546
|
-
async
|
|
6646
|
+
async matchChannel(id, input = {}) {
|
|
6547
6647
|
const channel = await this.store.getChannel(id);
|
|
6548
6648
|
if (!channel)
|
|
6549
6649
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -6560,6 +6660,34 @@ class EventsClient {
|
|
|
6560
6660
|
time: input.time,
|
|
6561
6661
|
id: input.id
|
|
6562
6662
|
});
|
|
6663
|
+
const matched = channelMatchesEvent(channel, event);
|
|
6664
|
+
return {
|
|
6665
|
+
channelId: channel.id,
|
|
6666
|
+
matched,
|
|
6667
|
+
event,
|
|
6668
|
+
filters: channel.filters,
|
|
6669
|
+
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
6670
|
+
};
|
|
6671
|
+
}
|
|
6672
|
+
async testChannel(id, input = {}, options = {}) {
|
|
6673
|
+
const channel = await this.store.getChannel(id);
|
|
6674
|
+
if (!channel)
|
|
6675
|
+
throw new Error(`Channel not found: ${id}`);
|
|
6676
|
+
const match = await this.matchChannel(id, input);
|
|
6677
|
+
const event = match.event;
|
|
6678
|
+
if (options.honorFilters && !match.matched) {
|
|
6679
|
+
const timestamp = new Date().toISOString();
|
|
6680
|
+
const result2 = createDeliveryResult(event, channel, [{
|
|
6681
|
+
attempt: 1,
|
|
6682
|
+
status: "skipped",
|
|
6683
|
+
startedAt: timestamp,
|
|
6684
|
+
completedAt: timestamp,
|
|
6685
|
+
error: match.reason
|
|
6686
|
+
}]);
|
|
6687
|
+
result2.metadata = { reason: "filter_mismatch" };
|
|
6688
|
+
await this.store.appendDelivery(result2);
|
|
6689
|
+
return result2;
|
|
6690
|
+
}
|
|
6563
6691
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
6564
6692
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
6565
6693
|
await this.store.appendDelivery(result);
|
|
@@ -6663,6 +6791,90 @@ var init_dist = __esm(() => {
|
|
|
6663
6791
|
DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
6664
6792
|
});
|
|
6665
6793
|
|
|
6794
|
+
// src/db/task-lists.ts
|
|
6795
|
+
function rowToTaskList(row) {
|
|
6796
|
+
return {
|
|
6797
|
+
...row,
|
|
6798
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
6799
|
+
};
|
|
6800
|
+
}
|
|
6801
|
+
function createTaskList(input, db) {
|
|
6802
|
+
const d = db || getDatabase();
|
|
6803
|
+
const id = uuid();
|
|
6804
|
+
const timestamp = now();
|
|
6805
|
+
const slug = input.slug || slugify(input.name);
|
|
6806
|
+
if (!input.project_id) {
|
|
6807
|
+
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
6808
|
+
if (existing) {
|
|
6809
|
+
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
6810
|
+
}
|
|
6811
|
+
}
|
|
6812
|
+
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
6813
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
6814
|
+
return getTaskList(id, d);
|
|
6815
|
+
}
|
|
6816
|
+
function getTaskList(id, db) {
|
|
6817
|
+
const d = db || getDatabase();
|
|
6818
|
+
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
6819
|
+
return row ? rowToTaskList(row) : null;
|
|
6820
|
+
}
|
|
6821
|
+
function getTaskListBySlug(slug, projectId, db) {
|
|
6822
|
+
const d = db || getDatabase();
|
|
6823
|
+
let row;
|
|
6824
|
+
if (projectId) {
|
|
6825
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
6826
|
+
} else {
|
|
6827
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
6828
|
+
}
|
|
6829
|
+
return row ? rowToTaskList(row) : null;
|
|
6830
|
+
}
|
|
6831
|
+
function listTaskLists(projectId, db) {
|
|
6832
|
+
const d = db || getDatabase();
|
|
6833
|
+
if (projectId) {
|
|
6834
|
+
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
|
|
6835
|
+
}
|
|
6836
|
+
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
|
|
6837
|
+
}
|
|
6838
|
+
function updateTaskList(id, input, db) {
|
|
6839
|
+
const d = db || getDatabase();
|
|
6840
|
+
const existing = getTaskList(id, d);
|
|
6841
|
+
if (!existing)
|
|
6842
|
+
throw new TaskListNotFoundError(id);
|
|
6843
|
+
const sets = ["updated_at = ?"];
|
|
6844
|
+
const params = [now()];
|
|
6845
|
+
if (input.name !== undefined) {
|
|
6846
|
+
sets.push("name = ?");
|
|
6847
|
+
params.push(input.name);
|
|
6848
|
+
}
|
|
6849
|
+
if (input.description !== undefined) {
|
|
6850
|
+
sets.push("description = ?");
|
|
6851
|
+
params.push(input.description);
|
|
6852
|
+
}
|
|
6853
|
+
if (input.metadata !== undefined) {
|
|
6854
|
+
sets.push("metadata = ?");
|
|
6855
|
+
params.push(JSON.stringify(input.metadata));
|
|
6856
|
+
}
|
|
6857
|
+
params.push(id);
|
|
6858
|
+
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
6859
|
+
return getTaskList(id, d);
|
|
6860
|
+
}
|
|
6861
|
+
function deleteTaskList(id, db) {
|
|
6862
|
+
const d = db || getDatabase();
|
|
6863
|
+
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
6864
|
+
}
|
|
6865
|
+
function ensureTaskList(name, slug, projectId, db) {
|
|
6866
|
+
const d = db || getDatabase();
|
|
6867
|
+
const existing = getTaskListBySlug(slug, projectId, d);
|
|
6868
|
+
if (existing)
|
|
6869
|
+
return existing;
|
|
6870
|
+
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
6871
|
+
}
|
|
6872
|
+
var init_task_lists = __esm(() => {
|
|
6873
|
+
init_types();
|
|
6874
|
+
init_database();
|
|
6875
|
+
init_projects();
|
|
6876
|
+
});
|
|
6877
|
+
|
|
6666
6878
|
// src/lib/shared-events.ts
|
|
6667
6879
|
function taskEventData(task, extra = {}) {
|
|
6668
6880
|
return {
|
|
@@ -6692,6 +6904,69 @@ function taskEventData(task, extra = {}) {
|
|
|
6692
6904
|
...extra
|
|
6693
6905
|
};
|
|
6694
6906
|
}
|
|
6907
|
+
function taskEventMetadata(task) {
|
|
6908
|
+
const metadata = {
|
|
6909
|
+
package: "@hasna/todos",
|
|
6910
|
+
todos_event_schema_version: 1,
|
|
6911
|
+
task_id: task.id,
|
|
6912
|
+
task_short_id: task.short_id,
|
|
6913
|
+
project_id: task.project_id,
|
|
6914
|
+
task_list_id: task.task_list_id,
|
|
6915
|
+
working_dir: task.working_dir
|
|
6916
|
+
};
|
|
6917
|
+
try {
|
|
6918
|
+
const project = task.project_id ? getProject(task.project_id) : null;
|
|
6919
|
+
const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
|
|
6920
|
+
if (project) {
|
|
6921
|
+
metadata.project_id = project.id;
|
|
6922
|
+
metadata.project_name = project.name;
|
|
6923
|
+
metadata.project_path = projectPath;
|
|
6924
|
+
metadata.project_canonical_path = project.path;
|
|
6925
|
+
metadata.project_default_task_list_slug = project.task_list_id;
|
|
6926
|
+
metadata.root_project_id = inferRootProjectId(project);
|
|
6927
|
+
} else if (projectPath) {
|
|
6928
|
+
metadata.project_path = projectPath;
|
|
6929
|
+
metadata.project_canonical_path = projectPath;
|
|
6930
|
+
}
|
|
6931
|
+
if (projectPath) {
|
|
6932
|
+
metadata.project_kind = classifyProjectKind(projectPath);
|
|
6933
|
+
metadata.project_is_worktree = isWorktreePath(projectPath);
|
|
6934
|
+
if (typeof task.metadata.route_enabled === "boolean") {
|
|
6935
|
+
metadata.route_enabled = task.metadata.route_enabled;
|
|
6936
|
+
}
|
|
6937
|
+
metadata.working_dir = task.working_dir ?? projectPath;
|
|
6938
|
+
}
|
|
6939
|
+
const taskList = task.task_list_id ? getTaskList(task.task_list_id) ?? (project ? getTaskListBySlug(task.task_list_id, project.id) : null) : project?.task_list_id ? getTaskListBySlug(project.task_list_id, project.id) : null;
|
|
6940
|
+
if (taskList) {
|
|
6941
|
+
metadata.task_list_id = taskList.id;
|
|
6942
|
+
metadata.task_list_slug = taskList.slug;
|
|
6943
|
+
metadata.task_list_name = taskList.name;
|
|
6944
|
+
metadata.task_list_project_id = taskList.project_id;
|
|
6945
|
+
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
6946
|
+
}
|
|
6947
|
+
} catch {}
|
|
6948
|
+
return metadata;
|
|
6949
|
+
}
|
|
6950
|
+
function classifyProjectKind(path) {
|
|
6951
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
6952
|
+
}
|
|
6953
|
+
function isWorktreePath(path) {
|
|
6954
|
+
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
6955
|
+
}
|
|
6956
|
+
function inferRootProjectId(project) {
|
|
6957
|
+
return isWorktreePath(project.path) ? null : project.id;
|
|
6958
|
+
}
|
|
6959
|
+
function readMachineLocalPath(project) {
|
|
6960
|
+
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
6961
|
+
if (!machineId)
|
|
6962
|
+
return null;
|
|
6963
|
+
try {
|
|
6964
|
+
const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
|
|
6965
|
+
return row?.path ?? null;
|
|
6966
|
+
} catch {
|
|
6967
|
+
return null;
|
|
6968
|
+
}
|
|
6969
|
+
}
|
|
6695
6970
|
async function emitSharedTaskEvent(input) {
|
|
6696
6971
|
const data = taskEventData(input.task, input.data);
|
|
6697
6972
|
await new EventsClient().emit({
|
|
@@ -6702,12 +6977,7 @@ async function emitSharedTaskEvent(input) {
|
|
|
6702
6977
|
message: input.message ?? `${input.type}: ${input.task.title}`,
|
|
6703
6978
|
data,
|
|
6704
6979
|
dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
|
|
6705
|
-
metadata:
|
|
6706
|
-
package: "@hasna/todos",
|
|
6707
|
-
task_id: input.task.id,
|
|
6708
|
-
project_id: input.task.project_id,
|
|
6709
|
-
task_list_id: input.task.task_list_id
|
|
6710
|
-
}
|
|
6980
|
+
metadata: taskEventMetadata(input.task)
|
|
6711
6981
|
}, { deliver: true, dedupe: true });
|
|
6712
6982
|
}
|
|
6713
6983
|
function emitSharedTaskEventQuiet(input) {
|
|
@@ -6718,6 +6988,9 @@ function emitSharedTaskEventQuiet(input) {
|
|
|
6718
6988
|
var SOURCE = "todos";
|
|
6719
6989
|
var init_shared_events = __esm(() => {
|
|
6720
6990
|
init_dist();
|
|
6991
|
+
init_database();
|
|
6992
|
+
init_projects();
|
|
6993
|
+
init_task_lists();
|
|
6721
6994
|
});
|
|
6722
6995
|
|
|
6723
6996
|
// src/lib/secret-redaction.ts
|
|
@@ -7275,6 +7548,17 @@ function replaceTaskTags(taskId, tags, db) {
|
|
|
7275
7548
|
db.run("DELETE FROM task_tags WHERE task_id = ?", [taskId]);
|
|
7276
7549
|
insertTaskTags(taskId, tags, db);
|
|
7277
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
|
+
}
|
|
7278
7562
|
function createTask(input, db) {
|
|
7279
7563
|
const d = db || getDatabase();
|
|
7280
7564
|
const timestamp = now();
|
|
@@ -7457,6 +7741,7 @@ function listTasks(filter = {}, db) {
|
|
|
7457
7741
|
params.push(filter.task_type);
|
|
7458
7742
|
}
|
|
7459
7743
|
}
|
|
7744
|
+
addMetadataConditions(filter.metadata, conditions, params);
|
|
7460
7745
|
const PRIORITY_RANK = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
|
|
7461
7746
|
if (filter.cursor) {
|
|
7462
7747
|
try {
|
|
@@ -7481,6 +7766,54 @@ function listTasks(filter = {}, db) {
|
|
|
7481
7766
|
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK}, created_at DESC${limitClause}`).all(...params);
|
|
7482
7767
|
return rows.map(rowToTask);
|
|
7483
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
|
+
}
|
|
7484
7817
|
function countTasks(filter = {}, db) {
|
|
7485
7818
|
const d = db || getDatabase();
|
|
7486
7819
|
const conditions = [];
|
|
@@ -7544,6 +7877,7 @@ function countTasks(filter = {}, db) {
|
|
|
7544
7877
|
conditions.push("task_list_id = ?");
|
|
7545
7878
|
params.push(filter.task_list_id);
|
|
7546
7879
|
}
|
|
7880
|
+
addMetadataConditions(filter.metadata, conditions, params);
|
|
7547
7881
|
if (!filter.include_archived) {
|
|
7548
7882
|
conditions.push("archived_at IS NULL");
|
|
7549
7883
|
}
|
|
@@ -7594,6 +7928,10 @@ function updateTask(id, input, db) {
|
|
|
7594
7928
|
sets.push("assigned_to = ?");
|
|
7595
7929
|
params.push(input.assigned_to);
|
|
7596
7930
|
}
|
|
7931
|
+
if (input.working_dir !== undefined) {
|
|
7932
|
+
sets.push("working_dir = ?");
|
|
7933
|
+
params.push(input.working_dir);
|
|
7934
|
+
}
|
|
7597
7935
|
if (input.tags !== undefined) {
|
|
7598
7936
|
sets.push("tags = ?");
|
|
7599
7937
|
params.push(JSON.stringify(input.tags));
|
|
@@ -7682,6 +8020,8 @@ function updateTask(id, input, db) {
|
|
|
7682
8020
|
logTaskChange(id, "update", "title", task.title, input.title, agentId, d);
|
|
7683
8021
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to)
|
|
7684
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);
|
|
7685
8025
|
if (input.approved_by !== undefined)
|
|
7686
8026
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
7687
8027
|
const updatedTask = {
|
|
@@ -7717,6 +8057,10 @@ function updateTask(id, input, db) {
|
|
|
7717
8057
|
if (input.approved_by !== undefined) {
|
|
7718
8058
|
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
7719
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 });
|
|
7720
8064
|
return updatedTask;
|
|
7721
8065
|
}
|
|
7722
8066
|
function deleteTask(id, db) {
|
|
@@ -10715,17 +11059,22 @@ var init_task_commits = __esm(() => {
|
|
|
10715
11059
|
var exports_task_runs = {};
|
|
10716
11060
|
__export(exports_task_runs, {
|
|
10717
11061
|
verifyTaskRunArtifacts: () => verifyTaskRunArtifacts,
|
|
11062
|
+
summarizeTaskRun: () => summarizeTaskRun,
|
|
10718
11063
|
startTaskRun: () => startTaskRun,
|
|
10719
11064
|
resolveTaskRunId: () => resolveTaskRunId,
|
|
10720
11065
|
redactEvidenceText: () => redactEvidenceText,
|
|
10721
11066
|
listTaskRuns: () => listTaskRuns,
|
|
10722
11067
|
getTaskRunLedger: () => getTaskRunLedger,
|
|
10723
11068
|
getTaskRun: () => getTaskRun,
|
|
11069
|
+
finishTaskRunTransaction: () => finishTaskRunTransaction,
|
|
10724
11070
|
finishTaskRun: () => finishTaskRun,
|
|
11071
|
+
findTaskRunByTransactionKey: () => findTaskRunByTransactionKey,
|
|
11072
|
+
beginTaskRunTransaction: () => beginTaskRunTransaction,
|
|
10725
11073
|
addTaskRunFile: () => addTaskRunFile,
|
|
10726
11074
|
addTaskRunEvent: () => addTaskRunEvent,
|
|
10727
11075
|
addTaskRunCommand: () => addTaskRunCommand,
|
|
10728
|
-
addTaskRunArtifact: () => addTaskRunArtifact
|
|
11076
|
+
addTaskRunArtifact: () => addTaskRunArtifact,
|
|
11077
|
+
LOOP_RUN_TRANSACTION_SCHEMA_VERSION: () => LOOP_RUN_TRANSACTION_SCHEMA_VERSION
|
|
10729
11078
|
});
|
|
10730
11079
|
function parseObject(value) {
|
|
10731
11080
|
if (!value)
|
|
@@ -10749,6 +11098,72 @@ function rowToArtifact(row) {
|
|
|
10749
11098
|
function getRunRow(runId, db) {
|
|
10750
11099
|
return db.query("SELECT * FROM task_runs WHERE id = ?").get(runId);
|
|
10751
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
|
+
}
|
|
10752
11167
|
function resolveTaskRunId(idOrPrefix, db) {
|
|
10753
11168
|
const d = db || getDatabase();
|
|
10754
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}%`);
|
|
@@ -10767,7 +11182,7 @@ function startTaskRun(input, db) {
|
|
|
10767
11182
|
const d = db || getDatabase();
|
|
10768
11183
|
if (!getTask(input.task_id, d))
|
|
10769
11184
|
throw new TaskNotFoundError(input.task_id);
|
|
10770
|
-
const id = uuid();
|
|
11185
|
+
const id = input.id ?? uuid();
|
|
10771
11186
|
const timestamp = input.started_at || now();
|
|
10772
11187
|
if (input.claim && input.agent_id) {
|
|
10773
11188
|
startTask(input.task_id, input.agent_id, d);
|
|
@@ -10805,6 +11220,97 @@ function startTaskRun(input, db) {
|
|
|
10805
11220
|
emitLocalEventHooksQuiet({ type: "run.started", payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title } });
|
|
10806
11221
|
return run;
|
|
10807
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
|
+
}
|
|
10808
11314
|
function addTaskRunEvent(input, db) {
|
|
10809
11315
|
const d = db || getDatabase();
|
|
10810
11316
|
const runId = resolveTaskRunId(input.run_id, d);
|
|
@@ -10984,6 +11490,66 @@ function finishTaskRun(input, db) {
|
|
|
10984
11490
|
});
|
|
10985
11491
|
return updated;
|
|
10986
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
|
+
}
|
|
10987
11553
|
function listTaskRuns(taskId, db) {
|
|
10988
11554
|
const d = db || getDatabase();
|
|
10989
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();
|
|
@@ -11001,6 +11567,7 @@ function getTaskRunLedger(runId, db) {
|
|
|
11001
11567
|
const files = d.query("SELECT * FROM task_files WHERE task_id = ? ORDER BY updated_at DESC, path").all(run.task_id);
|
|
11002
11568
|
return { run, events, commands, artifacts, files };
|
|
11003
11569
|
}
|
|
11570
|
+
var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
|
|
11004
11571
|
var init_task_runs = __esm(() => {
|
|
11005
11572
|
init_artifact_store();
|
|
11006
11573
|
init_event_hooks();
|
|
@@ -11389,6 +11956,7 @@ var init_calendar = __esm(() => {
|
|
|
11389
11956
|
var exports_tasks = {};
|
|
11390
11957
|
__export(exports_tasks, {
|
|
11391
11958
|
watchTask: () => watchTask,
|
|
11959
|
+
upsertTaskByFingerprint: () => upsertTaskByFingerprint,
|
|
11392
11960
|
updateTaskBoard: () => updateTaskBoard,
|
|
11393
11961
|
updateTask: () => updateTask,
|
|
11394
11962
|
unwatchTask: () => unwatchTask,
|
|
@@ -11432,6 +12000,7 @@ __export(exports_tasks, {
|
|
|
11432
12000
|
getTaskGraph: () => getTaskGraph,
|
|
11433
12001
|
getTaskDependents: () => getTaskDependents,
|
|
11434
12002
|
getTaskDependencies: () => getTaskDependencies,
|
|
12003
|
+
getTaskByFingerprint: () => getTaskByFingerprint,
|
|
11435
12004
|
getTaskBoard: () => getTaskBoard,
|
|
11436
12005
|
getTask: () => getTask,
|
|
11437
12006
|
getStatus: () => getStatus,
|
|
@@ -11672,6 +12241,62 @@ function parsePriority(value) {
|
|
|
11672
12241
|
}
|
|
11673
12242
|
return value;
|
|
11674
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
|
+
}
|
|
11675
12300
|
function registerTaskCommands(program2) {
|
|
11676
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) => {
|
|
11677
12302
|
const globalOpts = program2.opts();
|
|
@@ -11688,7 +12313,7 @@ function registerTaskCommands(program2) {
|
|
|
11688
12313
|
}
|
|
11689
12314
|
return id;
|
|
11690
12315
|
})() : undefined;
|
|
11691
|
-
const
|
|
12316
|
+
const task2 = createTask({
|
|
11692
12317
|
title,
|
|
11693
12318
|
description: opts.description,
|
|
11694
12319
|
priority: parsePriority(opts.priority),
|
|
@@ -11710,10 +12335,53 @@ function registerTaskCommands(program2) {
|
|
|
11710
12335
|
reason: opts.reason
|
|
11711
12336
|
});
|
|
11712
12337
|
if (globalOpts.json) {
|
|
11713
|
-
output(
|
|
12338
|
+
output(task2, true);
|
|
11714
12339
|
} else {
|
|
11715
12340
|
console.log(chalk2.green("Task created:"));
|
|
11716
|
-
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));
|
|
11717
12385
|
}
|
|
11718
12386
|
});
|
|
11719
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) => {
|
|
@@ -11871,89 +12539,89 @@ function registerTaskCommands(program2) {
|
|
|
11871
12539
|
program2.command("show <id>").description("Show full task details").action((id) => {
|
|
11872
12540
|
const globalOpts = program2.opts();
|
|
11873
12541
|
const resolvedId = resolveTaskId(id);
|
|
11874
|
-
const
|
|
11875
|
-
if (!
|
|
12542
|
+
const task2 = getTaskWithRelations(resolvedId);
|
|
12543
|
+
if (!task2) {
|
|
11876
12544
|
console.error(chalk2.red(`Task not found: ${id}`));
|
|
11877
12545
|
process.exit(1);
|
|
11878
12546
|
}
|
|
11879
12547
|
if (globalOpts.json) {
|
|
11880
|
-
output(
|
|
12548
|
+
output(task2, true);
|
|
11881
12549
|
return;
|
|
11882
12550
|
}
|
|
11883
12551
|
console.log(chalk2.bold(`Task Details:
|
|
11884
12552
|
`));
|
|
11885
|
-
console.log(` ${chalk2.dim("ID:")} ${
|
|
11886
|
-
console.log(` ${chalk2.dim("Title:")} ${
|
|
11887
|
-
console.log(` ${chalk2.dim("Status:")} ${(statusColors[
|
|
11888
|
-
console.log(` ${chalk2.dim("Priority:")} ${(priorityColors[
|
|
11889
|
-
if (
|
|
11890
|
-
console.log(` ${chalk2.dim("Desc:")} ${
|
|
11891
|
-
if (
|
|
11892
|
-
console.log(` ${chalk2.dim("Assigned:")} ${
|
|
11893
|
-
if (
|
|
11894
|
-
console.log(` ${chalk2.dim("Agent:")} ${
|
|
11895
|
-
if (
|
|
11896
|
-
console.log(` ${chalk2.dim("Session:")} ${
|
|
11897
|
-
if (
|
|
11898
|
-
console.log(` ${chalk2.dim("Locked:")} ${
|
|
11899
|
-
if (
|
|
11900
|
-
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");
|
|
11901
12569
|
console.log(` ${chalk2.dim("Approval:")} ${approvalStatus}`);
|
|
11902
12570
|
}
|
|
11903
|
-
if (
|
|
11904
|
-
console.log(` ${chalk2.dim("Estimate:")} ${
|
|
11905
|
-
if (
|
|
11906
|
-
console.log(` ${chalk2.dim("SLA:")} ${
|
|
11907
|
-
if (
|
|
11908
|
-
console.log(` ${chalk2.dim("Due:")} ${
|
|
11909
|
-
if (
|
|
11910
|
-
console.log(` ${chalk2.dim("Repeats:")} ${
|
|
11911
|
-
if (
|
|
11912
|
-
console.log(` ${chalk2.dim("Project:")} ${
|
|
11913
|
-
if (
|
|
11914
|
-
console.log(` ${chalk2.dim("Plan:")} ${
|
|
11915
|
-
if (
|
|
11916
|
-
console.log(` ${chalk2.dim("WorkDir:")} ${
|
|
11917
|
-
if (
|
|
11918
|
-
console.log(` ${chalk2.dim("Parent:")} ${
|
|
11919
|
-
if (
|
|
11920
|
-
console.log(` ${chalk2.dim("Tags:")} ${
|
|
11921
|
-
console.log(` ${chalk2.dim("Version:")} ${
|
|
11922
|
-
console.log(` ${chalk2.dim("Created:")} ${
|
|
11923
|
-
if (
|
|
11924
|
-
console.log(` ${chalk2.dim("Started:")} ${
|
|
11925
|
-
if (
|
|
11926
|
-
console.log(` ${chalk2.dim("Done:")} ${
|
|
11927
|
-
if (
|
|
11928
|
-
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);
|
|
11929
12597
|
console.log(` ${chalk2.dim("Duration:")} ${dur}m`);
|
|
11930
12598
|
}
|
|
11931
12599
|
}
|
|
11932
|
-
if (
|
|
12600
|
+
if (task2.subtasks.length > 0) {
|
|
11933
12601
|
console.log(chalk2.bold(`
|
|
11934
|
-
Subtasks (${
|
|
11935
|
-
for (const st of
|
|
12602
|
+
Subtasks (${task2.subtasks.length}):`));
|
|
12603
|
+
for (const st of task2.subtasks) {
|
|
11936
12604
|
console.log(` ${formatTaskLine(st)}`);
|
|
11937
12605
|
}
|
|
11938
12606
|
}
|
|
11939
|
-
if (
|
|
12607
|
+
if (task2.dependencies.length > 0) {
|
|
11940
12608
|
console.log(chalk2.bold(`
|
|
11941
|
-
Depends on (${
|
|
11942
|
-
for (const dep of
|
|
12609
|
+
Depends on (${task2.dependencies.length}):`));
|
|
12610
|
+
for (const dep of task2.dependencies) {
|
|
11943
12611
|
console.log(` ${formatTaskLine(dep)}`);
|
|
11944
12612
|
}
|
|
11945
12613
|
}
|
|
11946
|
-
if (
|
|
12614
|
+
if (task2.blocked_by.length > 0) {
|
|
11947
12615
|
console.log(chalk2.bold(`
|
|
11948
|
-
Blocks (${
|
|
11949
|
-
for (const b of
|
|
12616
|
+
Blocks (${task2.blocked_by.length}):`));
|
|
12617
|
+
for (const b of task2.blocked_by) {
|
|
11950
12618
|
console.log(` ${formatTaskLine(b)}`);
|
|
11951
12619
|
}
|
|
11952
12620
|
}
|
|
11953
|
-
if (
|
|
12621
|
+
if (task2.comments.length > 0) {
|
|
11954
12622
|
console.log(chalk2.bold(`
|
|
11955
|
-
Comments (${
|
|
11956
|
-
for (const c of
|
|
12623
|
+
Comments (${task2.comments.length}):`));
|
|
12624
|
+
for (const c of task2.comments) {
|
|
11957
12625
|
const agent = c.agent_id ? chalk2.cyan(`[${c.agent_id}] `) : "";
|
|
11958
12626
|
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
11959
12627
|
}
|
|
@@ -11972,8 +12640,8 @@ function registerTaskCommands(program2) {
|
|
|
11972
12640
|
console.error(chalk2.red("No task ID given and no active task found. Pass an ID or use --agent."));
|
|
11973
12641
|
process.exit(1);
|
|
11974
12642
|
}
|
|
11975
|
-
const
|
|
11976
|
-
if (!
|
|
12643
|
+
const task2 = getTaskWithRelations(resolvedId);
|
|
12644
|
+
if (!task2) {
|
|
11977
12645
|
console.error(chalk2.red(`Task not found: ${id || resolvedId}`));
|
|
11978
12646
|
process.exit(1);
|
|
11979
12647
|
}
|
|
@@ -11981,55 +12649,55 @@ function registerTaskCommands(program2) {
|
|
|
11981
12649
|
const { listTaskFiles: listTaskFiles2 } = await Promise.resolve().then(() => (init_task_files(), exports_task_files));
|
|
11982
12650
|
const { getTaskCommits: getTaskCommits2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
|
|
11983
12651
|
try {
|
|
11984
|
-
|
|
12652
|
+
task2.files = listTaskFiles2(task2.id);
|
|
11985
12653
|
} catch (e) {
|
|
11986
12654
|
console.error(chalk2.dim(`Warning: could not load task files: ${e instanceof Error ? e.message : String(e)}`));
|
|
11987
12655
|
}
|
|
11988
12656
|
try {
|
|
11989
|
-
|
|
12657
|
+
task2.commits = getTaskCommits2(task2.id);
|
|
11990
12658
|
} catch (e) {
|
|
11991
12659
|
console.error(chalk2.dim(`Warning: could not load task commits: ${e instanceof Error ? e.message : String(e)}`));
|
|
11992
12660
|
}
|
|
11993
|
-
output(
|
|
12661
|
+
output(task2, true);
|
|
11994
12662
|
return;
|
|
11995
12663
|
}
|
|
11996
|
-
const sid =
|
|
11997
|
-
const statusColor = statusColors[
|
|
11998
|
-
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;
|
|
11999
12667
|
console.log(chalk2.bold(`
|
|
12000
|
-
${chalk2.cyan(sid)} ${statusColor(
|
|
12668
|
+
${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${task2.title}
|
|
12001
12669
|
`));
|
|
12002
|
-
if (
|
|
12670
|
+
if (task2.description) {
|
|
12003
12671
|
console.log(chalk2.dim("Description:"));
|
|
12004
|
-
console.log(` ${
|
|
12672
|
+
console.log(` ${task2.description}
|
|
12005
12673
|
`);
|
|
12006
12674
|
}
|
|
12007
|
-
if (
|
|
12008
|
-
console.log(` ${chalk2.dim("Assigned:")} ${
|
|
12009
|
-
if (
|
|
12010
|
-
console.log(` ${chalk2.dim("Locked by:")} ${
|
|
12011
|
-
if (
|
|
12012
|
-
console.log(` ${chalk2.dim("Project:")} ${
|
|
12013
|
-
if (
|
|
12014
|
-
console.log(` ${chalk2.dim("Plan:")} ${
|
|
12015
|
-
if (
|
|
12016
|
-
console.log(` ${chalk2.dim("Started:")} ${
|
|
12017
|
-
if (
|
|
12018
|
-
console.log(` ${chalk2.dim("Completed:")} ${
|
|
12019
|
-
if (
|
|
12020
|
-
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);
|
|
12021
12689
|
console.log(` ${chalk2.dim("Duration:")} ${dur}m`);
|
|
12022
12690
|
}
|
|
12023
12691
|
}
|
|
12024
|
-
if (
|
|
12025
|
-
console.log(` ${chalk2.dim("Estimate:")} ${
|
|
12026
|
-
if (
|
|
12027
|
-
console.log(` ${chalk2.dim("Tags:")} ${
|
|
12028
|
-
const unfinishedDeps =
|
|
12029
|
-
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) {
|
|
12030
12698
|
console.log(chalk2.bold(`
|
|
12031
|
-
Depends on (${
|
|
12032
|
-
for (const dep of
|
|
12699
|
+
Depends on (${task2.dependencies.length}):`));
|
|
12700
|
+
for (const dep of task2.dependencies) {
|
|
12033
12701
|
const blocked = dep.status !== "completed" && dep.status !== "cancelled";
|
|
12034
12702
|
const icon = blocked ? chalk2.red("\u2717") : chalk2.green("\u2713");
|
|
12035
12703
|
console.log(` ${icon} ${formatTaskLine(dep)}`);
|
|
@@ -12039,21 +12707,21 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12039
12707
|
console.log(chalk2.red(`
|
|
12040
12708
|
BLOCKED by ${unfinishedDeps.length} unfinished dep(s)`));
|
|
12041
12709
|
}
|
|
12042
|
-
if (
|
|
12710
|
+
if (task2.blocked_by.length > 0) {
|
|
12043
12711
|
console.log(chalk2.bold(`
|
|
12044
|
-
Blocks (${
|
|
12045
|
-
for (const b of
|
|
12712
|
+
Blocks (${task2.blocked_by.length}):`));
|
|
12713
|
+
for (const b of task2.blocked_by)
|
|
12046
12714
|
console.log(` ${formatTaskLine(b)}`);
|
|
12047
12715
|
}
|
|
12048
|
-
if (
|
|
12716
|
+
if (task2.subtasks.length > 0) {
|
|
12049
12717
|
console.log(chalk2.bold(`
|
|
12050
|
-
Subtasks (${
|
|
12051
|
-
for (const st of
|
|
12718
|
+
Subtasks (${task2.subtasks.length}):`));
|
|
12719
|
+
for (const st of task2.subtasks)
|
|
12052
12720
|
console.log(` ${formatTaskLine(st)}`);
|
|
12053
12721
|
}
|
|
12054
12722
|
try {
|
|
12055
12723
|
const { listTaskFiles: listTaskFiles2 } = await Promise.resolve().then(() => (init_task_files(), exports_task_files));
|
|
12056
|
-
const files = listTaskFiles2(
|
|
12724
|
+
const files = listTaskFiles2(task2.id);
|
|
12057
12725
|
if (files.length > 0) {
|
|
12058
12726
|
console.log(chalk2.bold(`
|
|
12059
12727
|
Files (${files.length}):`));
|
|
@@ -12065,7 +12733,7 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12065
12733
|
}
|
|
12066
12734
|
try {
|
|
12067
12735
|
const { getTaskCommits: getTaskCommits2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
|
|
12068
|
-
const commits = getTaskCommits2(
|
|
12736
|
+
const commits = getTaskCommits2(task2.id);
|
|
12069
12737
|
if (commits.length > 0) {
|
|
12070
12738
|
console.log(chalk2.bold(`
|
|
12071
12739
|
Commits (${commits.length}):`));
|
|
@@ -12075,19 +12743,19 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12075
12743
|
} catch (e) {
|
|
12076
12744
|
console.error(chalk2.dim(`Warning: could not load task commits: ${e instanceof Error ? e.message : String(e)}`));
|
|
12077
12745
|
}
|
|
12078
|
-
if (
|
|
12746
|
+
if (task2.comments.length > 0) {
|
|
12079
12747
|
console.log(chalk2.bold(`
|
|
12080
|
-
Comments (${
|
|
12081
|
-
for (const c of
|
|
12748
|
+
Comments (${task2.comments.length}):`));
|
|
12749
|
+
for (const c of task2.comments) {
|
|
12082
12750
|
const agent = c.agent_id ? chalk2.cyan(`[${c.agent_id}] `) : "";
|
|
12083
12751
|
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
12084
12752
|
}
|
|
12085
12753
|
}
|
|
12086
|
-
if (
|
|
12087
|
-
const done =
|
|
12754
|
+
if (task2.checklist && task2.checklist.length > 0) {
|
|
12755
|
+
const done = task2.checklist.filter((c) => c.checked).length;
|
|
12088
12756
|
console.log(chalk2.bold(`
|
|
12089
|
-
Checklist (${done}/${
|
|
12090
|
-
for (const item of
|
|
12757
|
+
Checklist (${done}/${task2.checklist.length}):`));
|
|
12758
|
+
for (const item of task2.checklist) {
|
|
12091
12759
|
const icon = item.checked ? chalk2.green("\u2611") : chalk2.dim("\u2610");
|
|
12092
12760
|
console.log(` ${icon} ${item.text || item.title}`);
|
|
12093
12761
|
}
|
|
@@ -12140,9 +12808,9 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12140
12808
|
return resolved;
|
|
12141
12809
|
})() : undefined;
|
|
12142
12810
|
const planId = opts.plan ? resolvePlanId(opts.plan) : opts.clearPlan ? null : undefined;
|
|
12143
|
-
let
|
|
12811
|
+
let task2;
|
|
12144
12812
|
try {
|
|
12145
|
-
|
|
12813
|
+
task2 = updateTask(resolvedId, {
|
|
12146
12814
|
version: current.version,
|
|
12147
12815
|
title: opts.title,
|
|
12148
12816
|
description: opts.description,
|
|
@@ -12162,10 +12830,10 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12162
12830
|
handleError(e);
|
|
12163
12831
|
}
|
|
12164
12832
|
if (globalOpts.json) {
|
|
12165
|
-
output(
|
|
12833
|
+
output(task2, true);
|
|
12166
12834
|
} else {
|
|
12167
12835
|
console.log(chalk2.green("Task updated:"));
|
|
12168
|
-
console.log(formatTaskLine(
|
|
12836
|
+
console.log(formatTaskLine(task2));
|
|
12169
12837
|
}
|
|
12170
12838
|
});
|
|
12171
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) => {
|
|
@@ -12175,37 +12843,37 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12175
12843
|
const filesChanged = opts.filesChanged ? opts.filesChanged.split(",").map((s) => s.trim()) : undefined;
|
|
12176
12844
|
const confidence = opts.confidence !== undefined ? parseFloat(opts.confidence) : undefined;
|
|
12177
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;
|
|
12178
|
-
let
|
|
12846
|
+
let task2;
|
|
12179
12847
|
try {
|
|
12180
|
-
|
|
12848
|
+
task2 = completeTask(resolvedId, globalOpts.agent, undefined, { ...evidence, confidence });
|
|
12181
12849
|
} catch (e) {
|
|
12182
12850
|
handleError(e);
|
|
12183
12851
|
}
|
|
12184
12852
|
if (globalOpts.json) {
|
|
12185
|
-
output(
|
|
12853
|
+
output(task2, true);
|
|
12186
12854
|
} else {
|
|
12187
12855
|
console.log(chalk2.green("Task completed:"));
|
|
12188
|
-
console.log(formatTaskLine(
|
|
12856
|
+
console.log(formatTaskLine(task2));
|
|
12189
12857
|
}
|
|
12190
12858
|
});
|
|
12191
12859
|
program2.command("approve <id>").description("Approve a task that requires approval").action((id) => {
|
|
12192
12860
|
const globalOpts = program2.opts();
|
|
12193
12861
|
const resolvedId = resolveTaskId(id);
|
|
12194
|
-
const
|
|
12195
|
-
if (!
|
|
12862
|
+
const task2 = getTask(resolvedId);
|
|
12863
|
+
if (!task2) {
|
|
12196
12864
|
console.error(chalk2.red(`Task not found: ${id}`));
|
|
12197
12865
|
process.exit(1);
|
|
12198
12866
|
}
|
|
12199
|
-
if (!
|
|
12867
|
+
if (!task2.requires_approval) {
|
|
12200
12868
|
console.log(chalk2.yellow("This task does not require approval."));
|
|
12201
12869
|
return;
|
|
12202
12870
|
}
|
|
12203
|
-
if (
|
|
12204
|
-
console.log(chalk2.yellow(`Already approved by ${
|
|
12871
|
+
if (task2.approved_by) {
|
|
12872
|
+
console.log(chalk2.yellow(`Already approved by ${task2.approved_by}.`));
|
|
12205
12873
|
return;
|
|
12206
12874
|
}
|
|
12207
12875
|
try {
|
|
12208
|
-
const updated = updateTask(resolvedId, { approved_by: globalOpts.agent || "cli", version:
|
|
12876
|
+
const updated = updateTask(resolvedId, { approved_by: globalOpts.agent || "cli", version: task2.version });
|
|
12209
12877
|
if (globalOpts.json) {
|
|
12210
12878
|
output(updated, true);
|
|
12211
12879
|
} else {
|
|
@@ -12220,17 +12888,17 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12220
12888
|
const globalOpts = program2.opts();
|
|
12221
12889
|
const agentId = globalOpts.agent || "cli";
|
|
12222
12890
|
const resolvedId = resolveTaskId(id);
|
|
12223
|
-
let
|
|
12891
|
+
let task2;
|
|
12224
12892
|
try {
|
|
12225
|
-
|
|
12893
|
+
task2 = startTask(resolvedId, agentId);
|
|
12226
12894
|
} catch (e) {
|
|
12227
12895
|
handleError(e);
|
|
12228
12896
|
}
|
|
12229
12897
|
if (globalOpts.json) {
|
|
12230
|
-
output(
|
|
12898
|
+
output(task2, true);
|
|
12231
12899
|
} else {
|
|
12232
12900
|
console.log(chalk2.green(`Task started by ${agentId}:`));
|
|
12233
|
-
console.log(formatTaskLine(
|
|
12901
|
+
console.log(formatTaskLine(task2));
|
|
12234
12902
|
}
|
|
12235
12903
|
});
|
|
12236
12904
|
program2.command("lock <id>").description("Acquire exclusive lock on a task").action((id) => {
|
|
@@ -13403,7 +14071,7 @@ function likePattern(query) {
|
|
|
13403
14071
|
return null;
|
|
13404
14072
|
return `%${trimmed}%`;
|
|
13405
14073
|
}
|
|
13406
|
-
function
|
|
14074
|
+
function parseJsonObject4(value) {
|
|
13407
14075
|
if (!value)
|
|
13408
14076
|
return {};
|
|
13409
14077
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -13418,7 +14086,7 @@ function parseJsonObject3(value) {
|
|
|
13418
14086
|
}
|
|
13419
14087
|
}
|
|
13420
14088
|
function rowToTaskRun(row) {
|
|
13421
|
-
return { ...row, metadata:
|
|
14089
|
+
return { ...row, metadata: parseJsonObject4(row.metadata) };
|
|
13422
14090
|
}
|
|
13423
14091
|
function taskMatchesSavedFilters(task, filters, db) {
|
|
13424
14092
|
if (filters.plan_id && task.plan_id !== filters.plan_id)
|
|
@@ -14207,90 +14875,6 @@ var init_sync = __esm(() => {
|
|
|
14207
14875
|
init_config();
|
|
14208
14876
|
});
|
|
14209
14877
|
|
|
14210
|
-
// src/db/task-lists.ts
|
|
14211
|
-
function rowToTaskList(row) {
|
|
14212
|
-
return {
|
|
14213
|
-
...row,
|
|
14214
|
-
metadata: JSON.parse(row.metadata || "{}")
|
|
14215
|
-
};
|
|
14216
|
-
}
|
|
14217
|
-
function createTaskList(input, db) {
|
|
14218
|
-
const d = db || getDatabase();
|
|
14219
|
-
const id = uuid();
|
|
14220
|
-
const timestamp = now();
|
|
14221
|
-
const slug = input.slug || slugify(input.name);
|
|
14222
|
-
if (!input.project_id) {
|
|
14223
|
-
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
14224
|
-
if (existing) {
|
|
14225
|
-
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
14226
|
-
}
|
|
14227
|
-
}
|
|
14228
|
-
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
14229
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
14230
|
-
return getTaskList(id, d);
|
|
14231
|
-
}
|
|
14232
|
-
function getTaskList(id, db) {
|
|
14233
|
-
const d = db || getDatabase();
|
|
14234
|
-
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
14235
|
-
return row ? rowToTaskList(row) : null;
|
|
14236
|
-
}
|
|
14237
|
-
function getTaskListBySlug(slug, projectId, db) {
|
|
14238
|
-
const d = db || getDatabase();
|
|
14239
|
-
let row;
|
|
14240
|
-
if (projectId) {
|
|
14241
|
-
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
14242
|
-
} else {
|
|
14243
|
-
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
14244
|
-
}
|
|
14245
|
-
return row ? rowToTaskList(row) : null;
|
|
14246
|
-
}
|
|
14247
|
-
function listTaskLists(projectId, db) {
|
|
14248
|
-
const d = db || getDatabase();
|
|
14249
|
-
if (projectId) {
|
|
14250
|
-
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
|
|
14251
|
-
}
|
|
14252
|
-
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
|
|
14253
|
-
}
|
|
14254
|
-
function updateTaskList(id, input, db) {
|
|
14255
|
-
const d = db || getDatabase();
|
|
14256
|
-
const existing = getTaskList(id, d);
|
|
14257
|
-
if (!existing)
|
|
14258
|
-
throw new TaskListNotFoundError(id);
|
|
14259
|
-
const sets = ["updated_at = ?"];
|
|
14260
|
-
const params = [now()];
|
|
14261
|
-
if (input.name !== undefined) {
|
|
14262
|
-
sets.push("name = ?");
|
|
14263
|
-
params.push(input.name);
|
|
14264
|
-
}
|
|
14265
|
-
if (input.description !== undefined) {
|
|
14266
|
-
sets.push("description = ?");
|
|
14267
|
-
params.push(input.description);
|
|
14268
|
-
}
|
|
14269
|
-
if (input.metadata !== undefined) {
|
|
14270
|
-
sets.push("metadata = ?");
|
|
14271
|
-
params.push(JSON.stringify(input.metadata));
|
|
14272
|
-
}
|
|
14273
|
-
params.push(id);
|
|
14274
|
-
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
14275
|
-
return getTaskList(id, d);
|
|
14276
|
-
}
|
|
14277
|
-
function deleteTaskList(id, db) {
|
|
14278
|
-
const d = db || getDatabase();
|
|
14279
|
-
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
14280
|
-
}
|
|
14281
|
-
function ensureTaskList(name, slug, projectId, db) {
|
|
14282
|
-
const d = db || getDatabase();
|
|
14283
|
-
const existing = getTaskListBySlug(slug, projectId, d);
|
|
14284
|
-
if (existing)
|
|
14285
|
-
return existing;
|
|
14286
|
-
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
14287
|
-
}
|
|
14288
|
-
var init_task_lists = __esm(() => {
|
|
14289
|
-
init_types();
|
|
14290
|
-
init_database();
|
|
14291
|
-
init_projects();
|
|
14292
|
-
});
|
|
14293
|
-
|
|
14294
14878
|
// src/lib/project-bootstrap.ts
|
|
14295
14879
|
var exports_project_bootstrap = {};
|
|
14296
14880
|
__export(exports_project_bootstrap, {
|
|
@@ -14865,7 +15449,7 @@ function packageSource(version) {
|
|
|
14865
15449
|
function emptyCounts() {
|
|
14866
15450
|
return Object.fromEntries(dataKeys.map((key) => [key, 0]));
|
|
14867
15451
|
}
|
|
14868
|
-
function
|
|
15452
|
+
function parseJsonObject5(value) {
|
|
14869
15453
|
if (!value)
|
|
14870
15454
|
return {};
|
|
14871
15455
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -14911,34 +15495,34 @@ function rowToTask3(row) {
|
|
|
14911
15495
|
return {
|
|
14912
15496
|
...row,
|
|
14913
15497
|
tags: parseJsonArray2(row.tags),
|
|
14914
|
-
metadata:
|
|
15498
|
+
metadata: parseJsonObject5(row.metadata),
|
|
14915
15499
|
requires_approval: Boolean(row.requires_approval)
|
|
14916
15500
|
};
|
|
14917
15501
|
}
|
|
14918
15502
|
function rowToTaskList2(row) {
|
|
14919
|
-
return { ...row, metadata:
|
|
15503
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
14920
15504
|
}
|
|
14921
15505
|
function rowWithMetadata(row) {
|
|
14922
|
-
return { ...row, metadata:
|
|
15506
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
14923
15507
|
}
|
|
14924
15508
|
function rowToRunEvent(row) {
|
|
14925
|
-
return { ...row, data:
|
|
15509
|
+
return { ...row, data: parseJsonObject5(row.data) };
|
|
14926
15510
|
}
|
|
14927
15511
|
function rowToCommit2(row) {
|
|
14928
15512
|
return { ...row, files_changed: row.files_changed ? parseJsonArray2(row.files_changed) : null };
|
|
14929
15513
|
}
|
|
14930
15514
|
function rowToSavedView(row) {
|
|
14931
|
-
return { ...row, filters:
|
|
15515
|
+
return { ...row, filters: parseJsonObject5(row.filters) };
|
|
14932
15516
|
}
|
|
14933
15517
|
function rowToTaskBoard2(row) {
|
|
14934
15518
|
return {
|
|
14935
15519
|
...row,
|
|
14936
15520
|
lanes: parseJsonArray2(row.lanes),
|
|
14937
|
-
filters:
|
|
15521
|
+
filters: parseJsonObject5(row.filters)
|
|
14938
15522
|
};
|
|
14939
15523
|
}
|
|
14940
15524
|
function rowToCalendarItem2(row) {
|
|
14941
|
-
return { ...row, metadata:
|
|
15525
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
14942
15526
|
}
|
|
14943
15527
|
function bridgeStats(data) {
|
|
14944
15528
|
return Object.fromEntries(dataKeys.map((key) => [key, data[key].length]));
|
|
@@ -18285,6 +18869,7 @@ var init_token_utils = __esm(() => {
|
|
|
18285
18869
|
"add_task_run_event",
|
|
18286
18870
|
"add_task_run_file",
|
|
18287
18871
|
"acknowledge_handoff",
|
|
18872
|
+
"begin_task_run_transaction",
|
|
18288
18873
|
"build_local_report",
|
|
18289
18874
|
"cancel_agent_run_dispatch",
|
|
18290
18875
|
"finish_task_run",
|
|
@@ -18332,6 +18917,7 @@ var init_token_utils = __esm(() => {
|
|
|
18332
18917
|
"list_local_snapshots",
|
|
18333
18918
|
"list_retrospectives",
|
|
18334
18919
|
"list_risks",
|
|
18920
|
+
"list_task_findings",
|
|
18335
18921
|
"list_task_runs",
|
|
18336
18922
|
"list_verification_providers",
|
|
18337
18923
|
"merge_duplicate_task",
|
|
@@ -18340,6 +18926,7 @@ var init_token_utils = __esm(() => {
|
|
|
18340
18926
|
"remove_review_routing_rule",
|
|
18341
18927
|
"restore_local_backup",
|
|
18342
18928
|
"retry_agent_run_dispatch",
|
|
18929
|
+
"resolve_missing_task_findings",
|
|
18343
18930
|
"resolve_mentions",
|
|
18344
18931
|
"run_next_agent_dispatch",
|
|
18345
18932
|
"search_knowledge_records",
|
|
@@ -18382,9 +18969,17 @@ var init_token_utils = __esm(() => {
|
|
|
18382
18969
|
"unlock_file",
|
|
18383
18970
|
"unwatch_task",
|
|
18384
18971
|
"update_comment",
|
|
18972
|
+
"upsert_task_finding",
|
|
18385
18973
|
"update_risk",
|
|
18386
18974
|
"watch_task"
|
|
18387
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
|
+
],
|
|
18388
18983
|
agents: [
|
|
18389
18984
|
"auto_assign_task",
|
|
18390
18985
|
"delete_agent",
|
|
@@ -18466,7 +19061,7 @@ var init_token_utils = __esm(() => {
|
|
|
18466
19061
|
maintenance: ["extract_todos", "get_sla_breaches", "notify_upcoming_deadlines", "run_doctor", "score_task", "watch_source_todos"]
|
|
18467
19062
|
};
|
|
18468
19063
|
MCP_PROFILE_GROUPS = {
|
|
18469
|
-
minimal: ["core"],
|
|
19064
|
+
minimal: ["core", "loops"],
|
|
18470
19065
|
core: ["core"],
|
|
18471
19066
|
standard: ["core", "tasks", "projects", "resources", "agents", "metadata"],
|
|
18472
19067
|
agent: ["core", "tasks", "projects", "resources"],
|
|
@@ -20759,6 +21354,39 @@ async function handleCreateTask(req, ctx, json2, taskToSummary2) {
|
|
|
20759
21354
|
return json2({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
|
|
20760
21355
|
}
|
|
20761
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
|
+
}
|
|
20762
21390
|
function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
20763
21391
|
const format = url.searchParams.get("format") || "json";
|
|
20764
21392
|
const status = url.searchParams.get("status") || undefined;
|
|
@@ -25901,6 +26529,61 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
25901
26529
|
}
|
|
25902
26530
|
});
|
|
25903
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
|
+
}
|
|
25904
26587
|
if (shouldRegisterTool("list_tasks")) {
|
|
25905
26588
|
server.tool("list_tasks", "List tasks with optional filters. Pass empty arrays for multi-value filters (e.g. status=[] shows all).", {
|
|
25906
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"),
|
|
@@ -25912,7 +26595,8 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
25912
26595
|
created_after: exports_external.string().optional().describe("ISO date \u2014 tasks created after this date"),
|
|
25913
26596
|
created_before: exports_external.string().optional().describe("ISO date \u2014 tasks created before this date"),
|
|
25914
26597
|
limit: exports_external.number().optional().describe("Max results (default: 50, max 500)"),
|
|
25915
|
-
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")
|
|
25916
26600
|
}, async (params) => {
|
|
25917
26601
|
try {
|
|
25918
26602
|
const resolved = { ...params };
|
|
@@ -27500,7 +28184,7 @@ function parseAssignee(line) {
|
|
|
27500
28184
|
return mention;
|
|
27501
28185
|
return line.match(/\bassign(?:ed)?\s+(?:to\s+)?([a-zA-Z0-9._-]+)/i)?.[1] || null;
|
|
27502
28186
|
}
|
|
27503
|
-
function
|
|
28187
|
+
function parseTags2(line) {
|
|
27504
28188
|
return Array.from(new Set(Array.from(line.matchAll(/#([a-zA-Z0-9._-]+)/g)).map((match) => match[1].toLowerCase()))).slice(0, 10);
|
|
27505
28189
|
}
|
|
27506
28190
|
function parseDependencies(line) {
|
|
@@ -27572,7 +28256,7 @@ function previewNaturalLanguageIntake(input, db) {
|
|
|
27572
28256
|
description: `Parsed from local natural-language intake:
|
|
27573
28257
|
${line}`,
|
|
27574
28258
|
priority: parsePriority2(line, fallbackPriority),
|
|
27575
|
-
tags:
|
|
28259
|
+
tags: parseTags2(line),
|
|
27576
28260
|
assigned_to: parseAssignee(line),
|
|
27577
28261
|
due_at: parseDue(line, referenceDate),
|
|
27578
28262
|
depends_on: parseDependencies(line),
|
|
@@ -28080,7 +28764,7 @@ function countMovedStandaloneVerifications(db, duplicateId) {
|
|
|
28080
28764
|
const rows = db.query("SELECT command FROM task_verifications WHERE task_id = ?").all(duplicateId);
|
|
28081
28765
|
return rows.filter((row) => !generatedCommands.has(row.command)).length;
|
|
28082
28766
|
}
|
|
28083
|
-
function
|
|
28767
|
+
function mergeTaskMetadata2(primary, duplicate, input, mergedAt) {
|
|
28084
28768
|
const mergedDuplicates = Array.isArray(primary.metadata["merged_duplicates"]) ? [...primary.metadata["merged_duplicates"]] : [];
|
|
28085
28769
|
mergedDuplicates.push({
|
|
28086
28770
|
id: duplicate.id,
|
|
@@ -28141,7 +28825,7 @@ function mergeDuplicateTask(input, db) {
|
|
|
28141
28825
|
updateTask(primary.id, {
|
|
28142
28826
|
version: primary.version,
|
|
28143
28827
|
tags: mergedTags,
|
|
28144
|
-
metadata:
|
|
28828
|
+
metadata: mergeTaskMetadata2(primary, duplicate, input, mergedAt),
|
|
28145
28829
|
description: mergeTaskDescription(primary, duplicate) ?? undefined
|
|
28146
28830
|
}, d);
|
|
28147
28831
|
moved.comments = updateRows(d, "task_comments", "task_id", duplicate.id, primary.id);
|
|
@@ -34135,6 +34819,366 @@ var init_task_meta_tools = __esm(() => {
|
|
|
34135
34819
|
init_zod();
|
|
34136
34820
|
});
|
|
34137
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
|
+
|
|
34138
35182
|
// src/lib/agent-run-dispatcher.ts
|
|
34139
35183
|
var exports_agent_run_dispatcher = {};
|
|
34140
35184
|
__export(exports_agent_run_dispatcher, {
|
|
@@ -35063,7 +36107,7 @@ function parseArray2(value) {
|
|
|
35063
36107
|
return [];
|
|
35064
36108
|
}
|
|
35065
36109
|
}
|
|
35066
|
-
function
|
|
36110
|
+
function parseObject3(value) {
|
|
35067
36111
|
try {
|
|
35068
36112
|
const parsed = JSON.parse(value || "{}");
|
|
35069
36113
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
@@ -35104,7 +36148,7 @@ function rowToKnowledgeRecord(row) {
|
|
|
35104
36148
|
agent_id: row.agent_id,
|
|
35105
36149
|
snapshot_id: row.snapshot_id,
|
|
35106
36150
|
tags: parseArray2(row.tags),
|
|
35107
|
-
metadata: redactValue(
|
|
36151
|
+
metadata: redactValue(parseObject3(row.metadata)),
|
|
35108
36152
|
created_at: row.created_at,
|
|
35109
36153
|
updated_at: row.updated_at
|
|
35110
36154
|
};
|
|
@@ -35323,7 +36367,7 @@ function parseArray3(value) {
|
|
|
35323
36367
|
return [];
|
|
35324
36368
|
}
|
|
35325
36369
|
}
|
|
35326
|
-
function
|
|
36370
|
+
function parseObject4(value) {
|
|
35327
36371
|
try {
|
|
35328
36372
|
const parsed = JSON.parse(value || "{}");
|
|
35329
36373
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
@@ -35372,7 +36416,7 @@ function rowToRisk(row) {
|
|
|
35372
36416
|
plan_id: row.plan_id,
|
|
35373
36417
|
task_id: row.task_id,
|
|
35374
36418
|
tags: parseArray3(row.tags),
|
|
35375
|
-
metadata: redactValue(
|
|
36419
|
+
metadata: redactValue(parseObject4(row.metadata)),
|
|
35376
36420
|
created_at: row.created_at,
|
|
35377
36421
|
updated_at: row.updated_at,
|
|
35378
36422
|
closed_at: row.closed_at
|
|
@@ -36223,7 +37267,7 @@ function extractUsage(value) {
|
|
|
36223
37267
|
}
|
|
36224
37268
|
return own;
|
|
36225
37269
|
}
|
|
36226
|
-
function
|
|
37270
|
+
function parseJsonObject6(value) {
|
|
36227
37271
|
if (!value)
|
|
36228
37272
|
return {};
|
|
36229
37273
|
try {
|
|
@@ -36395,7 +37439,7 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
36395
37439
|
completedRunMs += millisBetween(run.started_at, run.completed_at);
|
|
36396
37440
|
else
|
|
36397
37441
|
openRunMs += millisBetween(run.started_at, generatedAt);
|
|
36398
|
-
const usage = extractUsage(
|
|
37442
|
+
const usage = extractUsage(parseJsonObject6(run.metadata));
|
|
36399
37443
|
metadataUsage.tokens += usage.tokens;
|
|
36400
37444
|
metadataUsage.cost_usd += usage.cost_usd;
|
|
36401
37445
|
metadataUsage.duration_ms += usage.duration_ms;
|
|
@@ -36407,7 +37451,7 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
36407
37451
|
JOIN tasks t ON t.id = e.task_id
|
|
36408
37452
|
${runClause}`, runParams);
|
|
36409
37453
|
for (const event of eventRows) {
|
|
36410
|
-
const usage = extractUsage(
|
|
37454
|
+
const usage = extractUsage(parseJsonObject6(event.data));
|
|
36411
37455
|
metadataUsage.tokens += usage.tokens;
|
|
36412
37456
|
metadataUsage.cost_usd += usage.cost_usd;
|
|
36413
37457
|
metadataUsage.duration_ms += usage.duration_ms;
|
|
@@ -40208,6 +41252,38 @@ ${lines.join(`
|
|
|
40208
41252
|
}
|
|
40209
41253
|
});
|
|
40210
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
|
+
}
|
|
40211
41287
|
if (shouldRegisterTool("list_task_runs")) {
|
|
40212
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 }) => {
|
|
40213
41289
|
try {
|
|
@@ -40305,15 +41381,117 @@ ${lines.join(`
|
|
|
40305
41381
|
});
|
|
40306
41382
|
}
|
|
40307
41383
|
if (shouldRegisterTool("finish_task_run")) {
|
|
40308
|
-
server.tool("finish_task_run", "Finish a local run ledger entry as completed, failed, or cancelled.", {
|
|
40309
|
-
run_id: exports_external.string().describe("Run ID or prefix"),
|
|
40310
|
-
|
|
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"),
|
|
40311
41389
|
summary: exports_external.string().optional().describe("Final summary"),
|
|
40312
|
-
agent_id: exports_external.string().optional().describe("Agent finishing the run")
|
|
40313
|
-
|
|
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 }) => {
|
|
40314
41393
|
try {
|
|
40315
|
-
|
|
40316
|
-
|
|
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) }] };
|
|
40317
41495
|
} catch (e) {
|
|
40318
41496
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
40319
41497
|
}
|
|
@@ -40649,6 +41827,7 @@ var init_task_resources = __esm(() => {
|
|
|
40649
41827
|
init_agents();
|
|
40650
41828
|
init_task_commits();
|
|
40651
41829
|
init_task_runs();
|
|
41830
|
+
init_findings();
|
|
40652
41831
|
init_agent_run_dispatcher();
|
|
40653
41832
|
init_verification_providers();
|
|
40654
41833
|
init_release_notes();
|
|
@@ -44389,8 +45568,10 @@ function taskToSummary(task2, fields) {
|
|
|
44389
45568
|
task_list_id: task2.task_list_id,
|
|
44390
45569
|
agent_id: task2.agent_id,
|
|
44391
45570
|
assigned_to: task2.assigned_to,
|
|
45571
|
+
working_dir: task2.working_dir,
|
|
44392
45572
|
locked_by: task2.locked_by,
|
|
44393
45573
|
tags: task2.tags,
|
|
45574
|
+
metadata: task2.metadata,
|
|
44394
45575
|
version: task2.version,
|
|
44395
45576
|
created_at: task2.created_at,
|
|
44396
45577
|
updated_at: task2.updated_at,
|
|
@@ -44532,6 +45713,9 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
44532
45713
|
if (path === "/api/tasks" && method === "POST") {
|
|
44533
45714
|
return handleCreateTask(req, ctx, jsonWithCors, taskToSummary);
|
|
44534
45715
|
}
|
|
45716
|
+
if (path === "/api/tasks/upsert" && method === "POST") {
|
|
45717
|
+
return handleUpsertTask(req, ctx, jsonWithCors, taskToSummary);
|
|
45718
|
+
}
|
|
44535
45719
|
if (path === "/api/tasks/export" && method === "GET") {
|
|
44536
45720
|
return handleTasksExport(req, url, ctx, jsonWithCors, taskToSummary);
|
|
44537
45721
|
}
|
|
@@ -44727,7 +45911,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
44727
45911
|
} catch {}
|
|
44728
45912
|
}
|
|
44729
45913
|
}
|
|
44730
|
-
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;
|
|
44731
45915
|
var init_serve = __esm(() => {
|
|
44732
45916
|
init_database();
|
|
44733
45917
|
init_api_keys();
|
|
@@ -44752,6 +45936,7 @@ var init_serve = __esm(() => {
|
|
|
44752
45936
|
"Permissions-Policy": "camera=, microphone=, geolocation="
|
|
44753
45937
|
};
|
|
44754
45938
|
rateLimitMap = new Map;
|
|
45939
|
+
RATE_LIMIT_MAX = Number.parseInt(process.env["TODOS_RATE_LIMIT_MAX"] || "120", 10);
|
|
44755
45940
|
});
|
|
44756
45941
|
|
|
44757
45942
|
// src/cli/components/Header.tsx
|
|
@@ -47405,9 +48590,9 @@ function normalizeKind(value) {
|
|
|
47405
48590
|
const raw = cleanKey(asString4(value) || "unknown").replace(/-/g, "_");
|
|
47406
48591
|
return KINDS.has(raw) ? raw : raw || "unknown";
|
|
47407
48592
|
}
|
|
47408
|
-
function
|
|
48593
|
+
function normalizeSeverity2(value, fallback) {
|
|
47409
48594
|
const raw = cleanKey(asString4(value) || fallback);
|
|
47410
|
-
if (
|
|
48595
|
+
if (SEVERITIES2.has(raw))
|
|
47411
48596
|
return raw;
|
|
47412
48597
|
if (/^(p0|blocker|urgent|highest)$/.test(raw))
|
|
47413
48598
|
return "critical";
|
|
@@ -47502,7 +48687,7 @@ function normalizeTesterIssueReport(value, fallbackPriority = "medium") {
|
|
|
47502
48687
|
title,
|
|
47503
48688
|
summary: truncate3(asString4(input["summary"]), 1000) ?? null,
|
|
47504
48689
|
kind: normalizeKind(input["kind"] ?? input["type"]),
|
|
47505
|
-
severity:
|
|
48690
|
+
severity: normalizeSeverity2(input["severity"] ?? input["priority"], fallbackPriority),
|
|
47506
48691
|
source: source3,
|
|
47507
48692
|
target,
|
|
47508
48693
|
failure,
|
|
@@ -47800,13 +48985,13 @@ function readTesterIssueReportsPayload(value) {
|
|
|
47800
48985
|
return record["issues"];
|
|
47801
48986
|
return [value];
|
|
47802
48987
|
}
|
|
47803
|
-
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;
|
|
47804
48989
|
var init_tester_issue_reports = __esm(() => {
|
|
47805
48990
|
init_database();
|
|
47806
48991
|
init_tasks();
|
|
47807
48992
|
init_redaction();
|
|
47808
48993
|
PRIORITIES5 = ["low", "medium", "high", "critical"];
|
|
47809
|
-
|
|
48994
|
+
SEVERITIES2 = new Set(PRIORITIES5);
|
|
47810
48995
|
KINDS = new Set([
|
|
47811
48996
|
"assertion_failure",
|
|
47812
48997
|
"runtime_error",
|
|
@@ -50988,6 +52173,23 @@ Verifications:`));
|
|
|
50988
52173
|
console.log(`${result.status} ${result.provider}: ${result.output_summary || ""}`);
|
|
50989
52174
|
});
|
|
50990
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
|
+
});
|
|
50991
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) => {
|
|
50992
52194
|
const globalOpts = program2.opts();
|
|
50993
52195
|
const { startTaskRun: startTaskRun2 } = await Promise.resolve().then(() => (init_task_runs(), exports_task_runs));
|
|
@@ -51199,19 +52401,68 @@ Artifacts:`));
|
|
|
51199
52401
|
process.exit(1);
|
|
51200
52402
|
}
|
|
51201
52403
|
});
|
|
51202
|
-
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) => {
|
|
51203
52405
|
const globalOpts = program2.opts();
|
|
51204
52406
|
if (opts.status !== "completed" && opts.status !== "failed" && opts.status !== "cancelled") {
|
|
51205
52407
|
console.error(chalk8.red("--status must be completed, failed, or cancelled"));
|
|
51206
52408
|
process.exit(1);
|
|
51207
52409
|
}
|
|
51208
|
-
const {
|
|
51209
|
-
const
|
|
51210
|
-
|
|
51211
|
-
|
|
51212
|
-
|
|
51213
|
-
|
|
51214
|
-
|
|
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);
|
|
51215
52466
|
});
|
|
51216
52467
|
const agentRuns = program2.command("agent-runs").description("Queue and dispatch local agent runs");
|
|
51217
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) => {
|
|
@@ -52125,7 +53376,7 @@ function parseRecordType(value) {
|
|
|
52125
53376
|
console.error(chalk13.red(`type must be one of: ${RECORD_TYPES.join(", ")}`));
|
|
52126
53377
|
process.exit(1);
|
|
52127
53378
|
}
|
|
52128
|
-
function
|
|
53379
|
+
function parseJsonObject7(value, label) {
|
|
52129
53380
|
if (!value)
|
|
52130
53381
|
return;
|
|
52131
53382
|
try {
|
|
@@ -52185,7 +53436,7 @@ function registerKnowledgeCommands(program2) {
|
|
|
52185
53436
|
plan_id: opts.plan,
|
|
52186
53437
|
agent_id: opts.agent || globalOpts.agent,
|
|
52187
53438
|
tags: tagsFromOption(opts.tag),
|
|
52188
|
-
metadata:
|
|
53439
|
+
metadata: parseJsonObject7(opts.metadataJson, "--metadata-json")
|
|
52189
53440
|
}, getDatabase());
|
|
52190
53441
|
if (opts.json || globalOpts.json)
|
|
52191
53442
|
output(record, true);
|
|
@@ -52210,7 +53461,7 @@ function registerKnowledgeCommands(program2) {
|
|
|
52210
53461
|
blockers: opts.blocker,
|
|
52211
53462
|
next_steps: opts.next,
|
|
52212
53463
|
tags: tagsFromOption(opts.tag),
|
|
52213
|
-
metadata:
|
|
53464
|
+
metadata: parseJsonObject7(opts.metadataJson, "--metadata-json")
|
|
52214
53465
|
}, getDatabase());
|
|
52215
53466
|
if (opts.json || globalOpts.json)
|
|
52216
53467
|
output(result, true);
|
|
@@ -52296,7 +53547,7 @@ function parseChoice(value, choices, label) {
|
|
|
52296
53547
|
console.error(chalk14.red(`${label} must be one of: ${choices.join(", ")}`));
|
|
52297
53548
|
process.exit(1);
|
|
52298
53549
|
}
|
|
52299
|
-
function
|
|
53550
|
+
function parseJsonObject8(value, label) {
|
|
52300
53551
|
if (!value)
|
|
52301
53552
|
return;
|
|
52302
53553
|
try {
|
|
@@ -52317,8 +53568,8 @@ function tagsFromOption2(value) {
|
|
|
52317
53568
|
}
|
|
52318
53569
|
function commonFilters2(opts) {
|
|
52319
53570
|
return {
|
|
52320
|
-
status: opts.status ? parseChoice(opts.status,
|
|
52321
|
-
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,
|
|
52322
53573
|
probability: opts.probability ? parseChoice(opts.probability, PROBABILITIES, "--probability") : undefined,
|
|
52323
53574
|
owner: opts.owner,
|
|
52324
53575
|
project_id: opts.project,
|
|
@@ -52351,8 +53602,8 @@ function registerRiskCommands(program2) {
|
|
|
52351
53602
|
const risk = createRisk({
|
|
52352
53603
|
title,
|
|
52353
53604
|
description: opts.description,
|
|
52354
|
-
status: parseChoice(opts.status,
|
|
52355
|
-
severity: parseChoice(opts.severity,
|
|
53605
|
+
status: parseChoice(opts.status, STATUSES2, "--status"),
|
|
53606
|
+
severity: parseChoice(opts.severity, SEVERITIES3, "--severity"),
|
|
52356
53607
|
probability: parseChoice(opts.probability, PROBABILITIES, "--probability"),
|
|
52357
53608
|
owner: opts.owner,
|
|
52358
53609
|
mitigation: opts.mitigation,
|
|
@@ -52361,7 +53612,7 @@ function registerRiskCommands(program2) {
|
|
|
52361
53612
|
plan_id: opts.plan,
|
|
52362
53613
|
task_id: opts.task,
|
|
52363
53614
|
tags: tagsFromOption2(opts.tag),
|
|
52364
|
-
metadata:
|
|
53615
|
+
metadata: parseJsonObject8(opts.metadataJson, "--metadata-json")
|
|
52365
53616
|
}, getDatabase());
|
|
52366
53617
|
if (opts.json || globalOpts.json)
|
|
52367
53618
|
output(risk, true);
|
|
@@ -52403,8 +53654,8 @@ function registerRiskCommands(program2) {
|
|
|
52403
53654
|
const risk = updateRisk(id, {
|
|
52404
53655
|
title: opts.title,
|
|
52405
53656
|
description: opts.description,
|
|
52406
|
-
status: opts.status ? parseChoice(opts.status,
|
|
52407
|
-
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,
|
|
52408
53659
|
probability: opts.probability ? parseChoice(opts.probability, PROBABILITIES, "--probability") : undefined,
|
|
52409
53660
|
owner: opts.owner,
|
|
52410
53661
|
mitigation: opts.mitigation,
|
|
@@ -52413,7 +53664,7 @@ function registerRiskCommands(program2) {
|
|
|
52413
53664
|
plan_id: opts.plan,
|
|
52414
53665
|
task_id: opts.task,
|
|
52415
53666
|
tags: opts.tag.length > 0 ? tagsFromOption2(opts.tag) : undefined,
|
|
52416
|
-
metadata:
|
|
53667
|
+
metadata: parseJsonObject8(opts.metadataJson, "--metadata-json")
|
|
52417
53668
|
}, getDatabase());
|
|
52418
53669
|
if (opts.json || globalOpts.json)
|
|
52419
53670
|
output(risk, true);
|
|
@@ -52471,13 +53722,13 @@ function registerRiskCommands(program2) {
|
|
|
52471
53722
|
}
|
|
52472
53723
|
});
|
|
52473
53724
|
}
|
|
52474
|
-
var
|
|
53725
|
+
var STATUSES2, SEVERITIES3, PROBABILITIES;
|
|
52475
53726
|
var init_risk_commands = __esm(() => {
|
|
52476
53727
|
init_database();
|
|
52477
53728
|
init_project_risks();
|
|
52478
53729
|
init_helpers();
|
|
52479
|
-
|
|
52480
|
-
|
|
53730
|
+
STATUSES2 = ["open", "mitigating", "resolved", "accepted"];
|
|
53731
|
+
SEVERITIES3 = ["low", "medium", "high", "critical"];
|
|
52481
53732
|
PROBABILITIES = ["low", "medium", "high"];
|
|
52482
53733
|
});
|
|
52483
53734
|
|
|
@@ -55062,6 +56313,92 @@ var init_json_contracts = __esm(() => {
|
|
|
55062
56313
|
},
|
|
55063
56314
|
optional: {}
|
|
55064
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
|
+
}),
|
|
55065
56402
|
contract({
|
|
55066
56403
|
id: "verification_provider",
|
|
55067
56404
|
name: "Verification Provider",
|