@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/index.js
CHANGED
|
@@ -1139,6 +1139,49 @@ var init_migrations = __esm(() => {
|
|
|
1139
1139
|
CREATE INDEX IF NOT EXISTS idx_local_retrospectives_plan ON local_retrospectives(plan_id);
|
|
1140
1140
|
CREATE INDEX IF NOT EXISTS idx_local_retrospectives_agent ON local_retrospectives(agent_id);
|
|
1141
1141
|
INSERT OR IGNORE INTO _migrations (id) VALUES (61);
|
|
1142
|
+
`,
|
|
1143
|
+
`
|
|
1144
|
+
CREATE TABLE IF NOT EXISTS task_run_transactions (
|
|
1145
|
+
id TEXT PRIMARY KEY,
|
|
1146
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1147
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
1148
|
+
key TEXT NOT NULL,
|
|
1149
|
+
loop_id TEXT,
|
|
1150
|
+
loop_run_id TEXT,
|
|
1151
|
+
metadata TEXT DEFAULT '{}',
|
|
1152
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1153
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1154
|
+
UNIQUE(task_id, key)
|
|
1155
|
+
);
|
|
1156
|
+
CREATE INDEX IF NOT EXISTS idx_task_run_transactions_task_key ON task_run_transactions(task_id, key);
|
|
1157
|
+
CREATE INDEX IF NOT EXISTS idx_task_run_transactions_key ON task_run_transactions(key);
|
|
1158
|
+
CREATE INDEX IF NOT EXISTS idx_task_run_transactions_run ON task_run_transactions(run_id);
|
|
1159
|
+
|
|
1160
|
+
CREATE TABLE IF NOT EXISTS task_findings (
|
|
1161
|
+
id TEXT PRIMARY KEY,
|
|
1162
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1163
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
1164
|
+
fingerprint TEXT NOT NULL,
|
|
1165
|
+
title TEXT NOT NULL,
|
|
1166
|
+
severity TEXT NOT NULL DEFAULT 'medium' CHECK(severity IN ('low', 'medium', 'high', 'critical')),
|
|
1167
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'resolved', 'ignored')),
|
|
1168
|
+
source TEXT,
|
|
1169
|
+
summary TEXT,
|
|
1170
|
+
artifact_path TEXT,
|
|
1171
|
+
metadata TEXT DEFAULT '{}',
|
|
1172
|
+
first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1173
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1174
|
+
resolved_at TEXT,
|
|
1175
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1176
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1177
|
+
UNIQUE(task_id, fingerprint)
|
|
1178
|
+
);
|
|
1179
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_task ON task_findings(task_id);
|
|
1180
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_run ON task_findings(run_id);
|
|
1181
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_status ON task_findings(status);
|
|
1182
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source);
|
|
1183
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint);
|
|
1184
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (62);
|
|
1142
1185
|
`
|
|
1143
1186
|
];
|
|
1144
1187
|
});
|
|
@@ -1576,6 +1619,47 @@ function ensureSchema(db) {
|
|
|
1576
1619
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_run ON task_run_artifacts(run_id)");
|
|
1577
1620
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_task ON task_run_artifacts(task_id)");
|
|
1578
1621
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_path ON task_run_artifacts(path)");
|
|
1622
|
+
ensureTable("task_run_transactions", `
|
|
1623
|
+
CREATE TABLE task_run_transactions (
|
|
1624
|
+
id TEXT PRIMARY KEY,
|
|
1625
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1626
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
1627
|
+
key TEXT NOT NULL,
|
|
1628
|
+
loop_id TEXT,
|
|
1629
|
+
loop_run_id TEXT,
|
|
1630
|
+
metadata TEXT DEFAULT '{}',
|
|
1631
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1632
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1633
|
+
UNIQUE(task_id, key)
|
|
1634
|
+
)`);
|
|
1635
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_transactions_task_key ON task_run_transactions(task_id, key)");
|
|
1636
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_transactions_key ON task_run_transactions(key)");
|
|
1637
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_transactions_run ON task_run_transactions(run_id)");
|
|
1638
|
+
ensureTable("task_findings", `
|
|
1639
|
+
CREATE TABLE task_findings (
|
|
1640
|
+
id TEXT PRIMARY KEY,
|
|
1641
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1642
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
1643
|
+
fingerprint TEXT NOT NULL,
|
|
1644
|
+
title TEXT NOT NULL,
|
|
1645
|
+
severity TEXT NOT NULL DEFAULT 'medium' CHECK(severity IN ('low', 'medium', 'high', 'critical')),
|
|
1646
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'resolved', 'ignored')),
|
|
1647
|
+
source TEXT,
|
|
1648
|
+
summary TEXT,
|
|
1649
|
+
artifact_path TEXT,
|
|
1650
|
+
metadata TEXT DEFAULT '{}',
|
|
1651
|
+
first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1652
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1653
|
+
resolved_at TEXT,
|
|
1654
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1655
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1656
|
+
UNIQUE(task_id, fingerprint)
|
|
1657
|
+
)`);
|
|
1658
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_task ON task_findings(task_id)");
|
|
1659
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_run ON task_findings(run_id)");
|
|
1660
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_status ON task_findings(status)");
|
|
1661
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source)");
|
|
1662
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint)");
|
|
1579
1663
|
ensureTable("inbox_items", `
|
|
1580
1664
|
CREATE TABLE inbox_items (
|
|
1581
1665
|
id TEXT PRIMARY KEY,
|
|
@@ -3681,6 +3765,7 @@ var MCP_TOOL_GROUPS = {
|
|
|
3681
3765
|
"add_task_run_event",
|
|
3682
3766
|
"add_task_run_file",
|
|
3683
3767
|
"acknowledge_handoff",
|
|
3768
|
+
"begin_task_run_transaction",
|
|
3684
3769
|
"build_local_report",
|
|
3685
3770
|
"cancel_agent_run_dispatch",
|
|
3686
3771
|
"finish_task_run",
|
|
@@ -3728,6 +3813,7 @@ var MCP_TOOL_GROUPS = {
|
|
|
3728
3813
|
"list_local_snapshots",
|
|
3729
3814
|
"list_retrospectives",
|
|
3730
3815
|
"list_risks",
|
|
3816
|
+
"list_task_findings",
|
|
3731
3817
|
"list_task_runs",
|
|
3732
3818
|
"list_verification_providers",
|
|
3733
3819
|
"merge_duplicate_task",
|
|
@@ -3736,6 +3822,7 @@ var MCP_TOOL_GROUPS = {
|
|
|
3736
3822
|
"remove_review_routing_rule",
|
|
3737
3823
|
"restore_local_backup",
|
|
3738
3824
|
"retry_agent_run_dispatch",
|
|
3825
|
+
"resolve_missing_task_findings",
|
|
3739
3826
|
"resolve_mentions",
|
|
3740
3827
|
"run_next_agent_dispatch",
|
|
3741
3828
|
"search_knowledge_records",
|
|
@@ -3778,9 +3865,17 @@ var MCP_TOOL_GROUPS = {
|
|
|
3778
3865
|
"unlock_file",
|
|
3779
3866
|
"unwatch_task",
|
|
3780
3867
|
"update_comment",
|
|
3868
|
+
"upsert_task_finding",
|
|
3781
3869
|
"update_risk",
|
|
3782
3870
|
"watch_task"
|
|
3783
3871
|
],
|
|
3872
|
+
loops: [
|
|
3873
|
+
"begin_task_run_transaction",
|
|
3874
|
+
"finish_task_run",
|
|
3875
|
+
"list_task_findings",
|
|
3876
|
+
"resolve_missing_task_findings",
|
|
3877
|
+
"upsert_task_finding"
|
|
3878
|
+
],
|
|
3784
3879
|
agents: [
|
|
3785
3880
|
"auto_assign_task",
|
|
3786
3881
|
"delete_agent",
|
|
@@ -3862,7 +3957,7 @@ var MCP_TOOL_GROUPS = {
|
|
|
3862
3957
|
maintenance: ["extract_todos", "get_sla_breaches", "notify_upcoming_deadlines", "run_doctor", "score_task", "watch_source_todos"]
|
|
3863
3958
|
};
|
|
3864
3959
|
var MCP_PROFILE_GROUPS = {
|
|
3865
|
-
minimal: ["core"],
|
|
3960
|
+
minimal: ["core", "loops"],
|
|
3866
3961
|
core: ["core"],
|
|
3867
3962
|
standard: ["core", "tasks", "projects", "resources", "agents", "metadata"],
|
|
3868
3963
|
agent: ["core", "tasks", "projects", "resources"],
|
|
@@ -4935,6 +5030,92 @@ var TODOS_JSON_CONTRACTS = [
|
|
|
4935
5030
|
},
|
|
4936
5031
|
optional: {}
|
|
4937
5032
|
}),
|
|
5033
|
+
contract({
|
|
5034
|
+
id: "loop_run_transaction",
|
|
5035
|
+
name: "Loop Run Transaction",
|
|
5036
|
+
description: "Compact local result for idempotent loop run begin/finish transactions.",
|
|
5037
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
5038
|
+
stability: "stable",
|
|
5039
|
+
required: {
|
|
5040
|
+
schema_version: field("string", "Result schema version."),
|
|
5041
|
+
local_only: field("boolean", "Always true; loop run transactions use local state."),
|
|
5042
|
+
dry_run: field("boolean", "True when no run ledger mutation was applied."),
|
|
5043
|
+
processed_at: isoDateField,
|
|
5044
|
+
action: field("string", "preview, created, matched, finished, or conflict."),
|
|
5045
|
+
key: field("string", "Stable idempotency key used to dedupe the transaction."),
|
|
5046
|
+
run: field(["object", "null"], "Compact run summary or null for create previews.", true),
|
|
5047
|
+
warnings: field("array", "Non-fatal warnings such as terminal-status conflicts."),
|
|
5048
|
+
commands: field("array", "Follow-up CLI commands for agents and operators.")
|
|
5049
|
+
},
|
|
5050
|
+
optional: {}
|
|
5051
|
+
}),
|
|
5052
|
+
contract({
|
|
5053
|
+
id: "task_finding",
|
|
5054
|
+
name: "Task Finding",
|
|
5055
|
+
description: "Compact local finding record deduped by task and fingerprint.",
|
|
5056
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
5057
|
+
stability: "stable",
|
|
5058
|
+
required: {
|
|
5059
|
+
schema_version: field("string", "Finding schema version."),
|
|
5060
|
+
id: idField,
|
|
5061
|
+
task_id: idField,
|
|
5062
|
+
run_id: field(["string", "null"], "Optional run ledger ID.", true),
|
|
5063
|
+
fingerprint: field("string", "Stable finding fingerprint scoped to the task."),
|
|
5064
|
+
title: field("string", "Short redacted finding title."),
|
|
5065
|
+
severity: field("string", "low, medium, high, or critical."),
|
|
5066
|
+
status: field("string", "open, resolved, or ignored."),
|
|
5067
|
+
source: field(["string", "null"], "Optional loop/tool source.", true),
|
|
5068
|
+
summary: field(["string", "null"], "Bounded redacted finding summary.", true),
|
|
5069
|
+
artifact_path: field(["string", "null"], "Local artifact path/reference; raw content is not included.", true),
|
|
5070
|
+
first_seen_at: isoDateField,
|
|
5071
|
+
last_seen_at: isoDateField,
|
|
5072
|
+
resolved_at: field(["string", "null"], "Resolution timestamp when closed.", true),
|
|
5073
|
+
metadata_keys: field("array", "Sorted metadata keys; metadata values are intentionally omitted in compact output.")
|
|
5074
|
+
},
|
|
5075
|
+
optional: {}
|
|
5076
|
+
}),
|
|
5077
|
+
contract({
|
|
5078
|
+
id: "task_finding_upsert",
|
|
5079
|
+
name: "Task Finding Upsert Result",
|
|
5080
|
+
description: "Local-only dry-run or applied result from idempotently upserting a task finding.",
|
|
5081
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
5082
|
+
stability: "stable",
|
|
5083
|
+
required: {
|
|
5084
|
+
schema_version: field("string", "Result schema version."),
|
|
5085
|
+
local_only: field("boolean", "Always true; finding upserts use local state."),
|
|
5086
|
+
dry_run: field("boolean", "True when no finding row was created or updated."),
|
|
5087
|
+
processed_at: isoDateField,
|
|
5088
|
+
action: field("string", "preview, created, matched, updated, or reopened."),
|
|
5089
|
+
fingerprint: field("string", "Normalized finding fingerprint."),
|
|
5090
|
+
finding: field(["object", "null"], "Compact finding summary or null for create previews.", true),
|
|
5091
|
+
warnings: field("array", "Non-fatal warnings.")
|
|
5092
|
+
},
|
|
5093
|
+
optional: {}
|
|
5094
|
+
}),
|
|
5095
|
+
contract({
|
|
5096
|
+
id: "task_finding_resolve_missing",
|
|
5097
|
+
name: "Task Finding Resolve Missing Result",
|
|
5098
|
+
description: "Local-only dry-run or applied result from resolving open findings absent from the latest loop finding set.",
|
|
5099
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
5100
|
+
stability: "stable",
|
|
5101
|
+
required: {
|
|
5102
|
+
schema_version: field("string", "Result schema version."),
|
|
5103
|
+
local_only: field("boolean", "Always true; finding resolution uses local state."),
|
|
5104
|
+
dry_run: field("boolean", "True when no finding rows were changed."),
|
|
5105
|
+
processed_at: isoDateField,
|
|
5106
|
+
action: field("string", "preview, resolved, ignored, or noop."),
|
|
5107
|
+
task_id: idField,
|
|
5108
|
+
source: field(["string", "null"], "Optional source scope.", true),
|
|
5109
|
+
run_id: field(["string", "null"], "Optional run ledger ID used for audit metadata.", true),
|
|
5110
|
+
present_fingerprint_count: field("integer", "Number of fingerprints supplied as still present."),
|
|
5111
|
+
candidate_count: field("integer", "Open findings missing from the supplied set."),
|
|
5112
|
+
changed_count: field("integer", "Rows resolved or ignored by the applied transaction."),
|
|
5113
|
+
omitted_count: field("integer", "Matching findings omitted from bounded output."),
|
|
5114
|
+
findings: field("array", "Bounded compact finding summaries."),
|
|
5115
|
+
warnings: field("array", "Non-fatal warnings.")
|
|
5116
|
+
},
|
|
5117
|
+
optional: {}
|
|
5118
|
+
}),
|
|
4938
5119
|
contract({
|
|
4939
5120
|
id: "verification_provider",
|
|
4940
5121
|
name: "Verification Provider",
|
|
@@ -6640,6 +6821,7 @@ var LOCAL_EVENT_TYPES = [
|
|
|
6640
6821
|
"task.blocked",
|
|
6641
6822
|
"task.started",
|
|
6642
6823
|
"task.completed",
|
|
6824
|
+
"task.updated",
|
|
6643
6825
|
"task.due",
|
|
6644
6826
|
"task.due_soon",
|
|
6645
6827
|
"task.failed",
|
|
@@ -6878,7 +7060,7 @@ async function testLocalEventHook(name, input) {
|
|
|
6878
7060
|
return emitLocalEventHooks({ ...input, hooks: [hook] });
|
|
6879
7061
|
}
|
|
6880
7062
|
|
|
6881
|
-
// node_modules/.bun/@hasna+events@0.1.
|
|
7063
|
+
// node_modules/.bun/@hasna+events@0.1.9/node_modules/@hasna/events/dist/index.js
|
|
6882
7064
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
6883
7065
|
import { existsSync as existsSync6 } from "fs";
|
|
6884
7066
|
import { homedir } from "os";
|
|
@@ -6895,17 +7077,30 @@ function getPathValue(input, path) {
|
|
|
6895
7077
|
return;
|
|
6896
7078
|
}, input);
|
|
6897
7079
|
}
|
|
6898
|
-
function wildcardToRegExp(pattern) {
|
|
6899
|
-
|
|
6900
|
-
|
|
7080
|
+
function wildcardToRegExp(pattern, options = {}) {
|
|
7081
|
+
let body = "";
|
|
7082
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
7083
|
+
const char = pattern[index];
|
|
7084
|
+
if (char === "*") {
|
|
7085
|
+
if (pattern[index + 1] === "*") {
|
|
7086
|
+
body += ".*";
|
|
7087
|
+
index += 1;
|
|
7088
|
+
} else {
|
|
7089
|
+
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
7090
|
+
}
|
|
7091
|
+
} else {
|
|
7092
|
+
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
7093
|
+
}
|
|
7094
|
+
}
|
|
7095
|
+
return new RegExp(`^${body}$`);
|
|
6901
7096
|
}
|
|
6902
|
-
function matchString(value, matcher) {
|
|
7097
|
+
function matchString(value, matcher, options = {}) {
|
|
6903
7098
|
if (matcher === undefined)
|
|
6904
7099
|
return true;
|
|
6905
7100
|
if (value === undefined)
|
|
6906
7101
|
return false;
|
|
6907
7102
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
6908
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
7103
|
+
return matchers.some((item) => wildcardToRegExp(item, options).test(value));
|
|
6909
7104
|
}
|
|
6910
7105
|
function matchRecord(input, matcher) {
|
|
6911
7106
|
if (!matcher)
|
|
@@ -6913,7 +7108,9 @@ function matchRecord(input, matcher) {
|
|
|
6913
7108
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
6914
7109
|
const actual = getPathValue(input, path);
|
|
6915
7110
|
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
6916
|
-
return matchString(actual === undefined ? undefined : String(actual), expected
|
|
7111
|
+
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
7112
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
7113
|
+
});
|
|
6917
7114
|
}
|
|
6918
7115
|
return actual === expected;
|
|
6919
7116
|
});
|
|
@@ -6933,7 +7130,6 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
6933
7130
|
function getEventsDataDir(override) {
|
|
6934
7131
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join5(homedir(), ".hasna", "events");
|
|
6935
7132
|
}
|
|
6936
|
-
|
|
6937
7133
|
class JsonEventsStore {
|
|
6938
7134
|
dataDir;
|
|
6939
7135
|
channelsPath;
|
|
@@ -7270,7 +7466,7 @@ class EventsClient {
|
|
|
7270
7466
|
}
|
|
7271
7467
|
return deliveries;
|
|
7272
7468
|
}
|
|
7273
|
-
async
|
|
7469
|
+
async matchChannel(id, input = {}) {
|
|
7274
7470
|
const channel = await this.store.getChannel(id);
|
|
7275
7471
|
if (!channel)
|
|
7276
7472
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -7287,6 +7483,34 @@ class EventsClient {
|
|
|
7287
7483
|
time: input.time,
|
|
7288
7484
|
id: input.id
|
|
7289
7485
|
});
|
|
7486
|
+
const matched = channelMatchesEvent(channel, event);
|
|
7487
|
+
return {
|
|
7488
|
+
channelId: channel.id,
|
|
7489
|
+
matched,
|
|
7490
|
+
event,
|
|
7491
|
+
filters: channel.filters,
|
|
7492
|
+
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
7493
|
+
};
|
|
7494
|
+
}
|
|
7495
|
+
async testChannel(id, input = {}, options = {}) {
|
|
7496
|
+
const channel = await this.store.getChannel(id);
|
|
7497
|
+
if (!channel)
|
|
7498
|
+
throw new Error(`Channel not found: ${id}`);
|
|
7499
|
+
const match = await this.matchChannel(id, input);
|
|
7500
|
+
const event = match.event;
|
|
7501
|
+
if (options.honorFilters && !match.matched) {
|
|
7502
|
+
const timestamp = new Date().toISOString();
|
|
7503
|
+
const result2 = createDeliveryResult(event, channel, [{
|
|
7504
|
+
attempt: 1,
|
|
7505
|
+
status: "skipped",
|
|
7506
|
+
startedAt: timestamp,
|
|
7507
|
+
completedAt: timestamp,
|
|
7508
|
+
error: match.reason
|
|
7509
|
+
}]);
|
|
7510
|
+
result2.metadata = { reason: "filter_mismatch" };
|
|
7511
|
+
await this.store.appendDelivery(result2);
|
|
7512
|
+
return result2;
|
|
7513
|
+
}
|
|
7290
7514
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
7291
7515
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
7292
7516
|
await this.store.appendDelivery(result);
|
|
@@ -7386,6 +7610,90 @@ function normalizeRetryPolicy(policy) {
|
|
|
7386
7610
|
};
|
|
7387
7611
|
}
|
|
7388
7612
|
|
|
7613
|
+
// src/lib/shared-events.ts
|
|
7614
|
+
init_database();
|
|
7615
|
+
|
|
7616
|
+
// src/db/task-lists.ts
|
|
7617
|
+
init_types();
|
|
7618
|
+
init_database();
|
|
7619
|
+
function rowToTaskList(row) {
|
|
7620
|
+
return {
|
|
7621
|
+
...row,
|
|
7622
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
7623
|
+
};
|
|
7624
|
+
}
|
|
7625
|
+
function createTaskList(input, db) {
|
|
7626
|
+
const d = db || getDatabase();
|
|
7627
|
+
const id = uuid();
|
|
7628
|
+
const timestamp = now();
|
|
7629
|
+
const slug = input.slug || slugify(input.name);
|
|
7630
|
+
if (!input.project_id) {
|
|
7631
|
+
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
7632
|
+
if (existing) {
|
|
7633
|
+
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
7637
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
7638
|
+
return getTaskList(id, d);
|
|
7639
|
+
}
|
|
7640
|
+
function getTaskList(id, db) {
|
|
7641
|
+
const d = db || getDatabase();
|
|
7642
|
+
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
7643
|
+
return row ? rowToTaskList(row) : null;
|
|
7644
|
+
}
|
|
7645
|
+
function getTaskListBySlug(slug, projectId, db) {
|
|
7646
|
+
const d = db || getDatabase();
|
|
7647
|
+
let row;
|
|
7648
|
+
if (projectId) {
|
|
7649
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
7650
|
+
} else {
|
|
7651
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
7652
|
+
}
|
|
7653
|
+
return row ? rowToTaskList(row) : null;
|
|
7654
|
+
}
|
|
7655
|
+
function listTaskLists(projectId, db) {
|
|
7656
|
+
const d = db || getDatabase();
|
|
7657
|
+
if (projectId) {
|
|
7658
|
+
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
|
|
7659
|
+
}
|
|
7660
|
+
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
|
|
7661
|
+
}
|
|
7662
|
+
function updateTaskList(id, input, db) {
|
|
7663
|
+
const d = db || getDatabase();
|
|
7664
|
+
const existing = getTaskList(id, d);
|
|
7665
|
+
if (!existing)
|
|
7666
|
+
throw new TaskListNotFoundError(id);
|
|
7667
|
+
const sets = ["updated_at = ?"];
|
|
7668
|
+
const params = [now()];
|
|
7669
|
+
if (input.name !== undefined) {
|
|
7670
|
+
sets.push("name = ?");
|
|
7671
|
+
params.push(input.name);
|
|
7672
|
+
}
|
|
7673
|
+
if (input.description !== undefined) {
|
|
7674
|
+
sets.push("description = ?");
|
|
7675
|
+
params.push(input.description);
|
|
7676
|
+
}
|
|
7677
|
+
if (input.metadata !== undefined) {
|
|
7678
|
+
sets.push("metadata = ?");
|
|
7679
|
+
params.push(JSON.stringify(input.metadata));
|
|
7680
|
+
}
|
|
7681
|
+
params.push(id);
|
|
7682
|
+
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
7683
|
+
return getTaskList(id, d);
|
|
7684
|
+
}
|
|
7685
|
+
function deleteTaskList(id, db) {
|
|
7686
|
+
const d = db || getDatabase();
|
|
7687
|
+
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
7688
|
+
}
|
|
7689
|
+
function ensureTaskList(name, slug, projectId, db) {
|
|
7690
|
+
const d = db || getDatabase();
|
|
7691
|
+
const existing = getTaskListBySlug(slug, projectId, d);
|
|
7692
|
+
if (existing)
|
|
7693
|
+
return existing;
|
|
7694
|
+
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
7695
|
+
}
|
|
7696
|
+
|
|
7389
7697
|
// src/lib/shared-events.ts
|
|
7390
7698
|
var SOURCE = "todos";
|
|
7391
7699
|
function taskEventData(task, extra = {}) {
|
|
@@ -7416,6 +7724,69 @@ function taskEventData(task, extra = {}) {
|
|
|
7416
7724
|
...extra
|
|
7417
7725
|
};
|
|
7418
7726
|
}
|
|
7727
|
+
function taskEventMetadata(task) {
|
|
7728
|
+
const metadata = {
|
|
7729
|
+
package: "@hasna/todos",
|
|
7730
|
+
todos_event_schema_version: 1,
|
|
7731
|
+
task_id: task.id,
|
|
7732
|
+
task_short_id: task.short_id,
|
|
7733
|
+
project_id: task.project_id,
|
|
7734
|
+
task_list_id: task.task_list_id,
|
|
7735
|
+
working_dir: task.working_dir
|
|
7736
|
+
};
|
|
7737
|
+
try {
|
|
7738
|
+
const project = task.project_id ? getProject(task.project_id) : null;
|
|
7739
|
+
const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
|
|
7740
|
+
if (project) {
|
|
7741
|
+
metadata.project_id = project.id;
|
|
7742
|
+
metadata.project_name = project.name;
|
|
7743
|
+
metadata.project_path = projectPath;
|
|
7744
|
+
metadata.project_canonical_path = project.path;
|
|
7745
|
+
metadata.project_default_task_list_slug = project.task_list_id;
|
|
7746
|
+
metadata.root_project_id = inferRootProjectId(project);
|
|
7747
|
+
} else if (projectPath) {
|
|
7748
|
+
metadata.project_path = projectPath;
|
|
7749
|
+
metadata.project_canonical_path = projectPath;
|
|
7750
|
+
}
|
|
7751
|
+
if (projectPath) {
|
|
7752
|
+
metadata.project_kind = classifyProjectKind(projectPath);
|
|
7753
|
+
metadata.project_is_worktree = isWorktreePath(projectPath);
|
|
7754
|
+
if (typeof task.metadata.route_enabled === "boolean") {
|
|
7755
|
+
metadata.route_enabled = task.metadata.route_enabled;
|
|
7756
|
+
}
|
|
7757
|
+
metadata.working_dir = task.working_dir ?? projectPath;
|
|
7758
|
+
}
|
|
7759
|
+
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;
|
|
7760
|
+
if (taskList) {
|
|
7761
|
+
metadata.task_list_id = taskList.id;
|
|
7762
|
+
metadata.task_list_slug = taskList.slug;
|
|
7763
|
+
metadata.task_list_name = taskList.name;
|
|
7764
|
+
metadata.task_list_project_id = taskList.project_id;
|
|
7765
|
+
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
7766
|
+
}
|
|
7767
|
+
} catch {}
|
|
7768
|
+
return metadata;
|
|
7769
|
+
}
|
|
7770
|
+
function classifyProjectKind(path) {
|
|
7771
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
7772
|
+
}
|
|
7773
|
+
function isWorktreePath(path) {
|
|
7774
|
+
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
7775
|
+
}
|
|
7776
|
+
function inferRootProjectId(project) {
|
|
7777
|
+
return isWorktreePath(project.path) ? null : project.id;
|
|
7778
|
+
}
|
|
7779
|
+
function readMachineLocalPath(project) {
|
|
7780
|
+
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
7781
|
+
if (!machineId)
|
|
7782
|
+
return null;
|
|
7783
|
+
try {
|
|
7784
|
+
const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
|
|
7785
|
+
return row?.path ?? null;
|
|
7786
|
+
} catch {
|
|
7787
|
+
return null;
|
|
7788
|
+
}
|
|
7789
|
+
}
|
|
7419
7790
|
async function emitSharedTaskEvent(input) {
|
|
7420
7791
|
const data = taskEventData(input.task, input.data);
|
|
7421
7792
|
await new EventsClient().emit({
|
|
@@ -7426,12 +7797,7 @@ async function emitSharedTaskEvent(input) {
|
|
|
7426
7797
|
message: input.message ?? `${input.type}: ${input.task.title}`,
|
|
7427
7798
|
data,
|
|
7428
7799
|
dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
|
|
7429
|
-
metadata:
|
|
7430
|
-
package: "@hasna/todos",
|
|
7431
|
-
task_id: input.task.id,
|
|
7432
|
-
project_id: input.task.project_id,
|
|
7433
|
-
task_list_id: input.task.task_list_id
|
|
7434
|
-
}
|
|
7800
|
+
metadata: taskEventMetadata(input.task)
|
|
7435
7801
|
}, { deliver: true, dedupe: true });
|
|
7436
7802
|
}
|
|
7437
7803
|
function emitSharedTaskEventQuiet(input) {
|
|
@@ -7794,6 +8160,17 @@ function replaceTaskTags(taskId, tags, db) {
|
|
|
7794
8160
|
db.run("DELETE FROM task_tags WHERE task_id = ?", [taskId]);
|
|
7795
8161
|
insertTaskTags(taskId, tags, db);
|
|
7796
8162
|
}
|
|
8163
|
+
function addMetadataConditions(metadata, conditions, params) {
|
|
8164
|
+
if (!metadata)
|
|
8165
|
+
return;
|
|
8166
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
8167
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(key)) {
|
|
8168
|
+
throw new Error(`Invalid metadata filter key: ${key}`);
|
|
8169
|
+
}
|
|
8170
|
+
conditions.push(`json_extract(metadata, '$."${key}"') = ?`);
|
|
8171
|
+
params.push(value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : JSON.stringify(value));
|
|
8172
|
+
}
|
|
8173
|
+
}
|
|
7797
8174
|
function createTask(input, db) {
|
|
7798
8175
|
const d = db || getDatabase();
|
|
7799
8176
|
const timestamp = now();
|
|
@@ -7976,6 +8353,7 @@ function listTasks(filter = {}, db) {
|
|
|
7976
8353
|
params.push(filter.task_type);
|
|
7977
8354
|
}
|
|
7978
8355
|
}
|
|
8356
|
+
addMetadataConditions(filter.metadata, conditions, params);
|
|
7979
8357
|
const PRIORITY_RANK = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
|
|
7980
8358
|
if (filter.cursor) {
|
|
7981
8359
|
try {
|
|
@@ -8000,6 +8378,54 @@ function listTasks(filter = {}, db) {
|
|
|
8000
8378
|
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK}, created_at DESC${limitClause}`).all(...params);
|
|
8001
8379
|
return rows.map(rowToTask);
|
|
8002
8380
|
}
|
|
8381
|
+
function getTaskByFingerprint(fingerprint, db) {
|
|
8382
|
+
const tasks = listTasks({ metadata: { fingerprint }, limit: 1 }, db);
|
|
8383
|
+
return tasks[0] ?? null;
|
|
8384
|
+
}
|
|
8385
|
+
function mergeTaskMetadata(current, next, fingerprint) {
|
|
8386
|
+
return {
|
|
8387
|
+
...current,
|
|
8388
|
+
...next ?? {},
|
|
8389
|
+
fingerprint
|
|
8390
|
+
};
|
|
8391
|
+
}
|
|
8392
|
+
function upsertTaskByFingerprint(input, db) {
|
|
8393
|
+
const d = db || getDatabase();
|
|
8394
|
+
const fingerprint = input.fingerprint.trim();
|
|
8395
|
+
if (!fingerprint)
|
|
8396
|
+
throw new Error("fingerprint is required");
|
|
8397
|
+
const existing = getTaskByFingerprint(fingerprint, d);
|
|
8398
|
+
const metadata = mergeTaskMetadata(existing?.metadata ?? {}, input.metadata, fingerprint);
|
|
8399
|
+
if (!existing) {
|
|
8400
|
+
const task2 = createTask({ ...input, metadata }, d);
|
|
8401
|
+
return { task: task2, created: true };
|
|
8402
|
+
}
|
|
8403
|
+
const task = updateTask(existing.id, {
|
|
8404
|
+
version: existing.version,
|
|
8405
|
+
title: input.title,
|
|
8406
|
+
description: input.description,
|
|
8407
|
+
status: input.status,
|
|
8408
|
+
priority: input.priority,
|
|
8409
|
+
project_id: input.project_id,
|
|
8410
|
+
assigned_to: input.assigned_to,
|
|
8411
|
+
working_dir: input.working_dir,
|
|
8412
|
+
plan_id: input.plan_id,
|
|
8413
|
+
task_list_id: input.task_list_id,
|
|
8414
|
+
tags: input.tags,
|
|
8415
|
+
metadata,
|
|
8416
|
+
due_at: input.due_at,
|
|
8417
|
+
estimated_minutes: input.estimated_minutes,
|
|
8418
|
+
sla_minutes: input.sla_minutes,
|
|
8419
|
+
confidence: input.confidence,
|
|
8420
|
+
retry_count: input.retry_count,
|
|
8421
|
+
max_retries: input.max_retries,
|
|
8422
|
+
retry_after: input.retry_after,
|
|
8423
|
+
requires_approval: input.requires_approval,
|
|
8424
|
+
recurrence_rule: input.recurrence_rule,
|
|
8425
|
+
task_type: input.task_type
|
|
8426
|
+
}, d);
|
|
8427
|
+
return { task, created: false };
|
|
8428
|
+
}
|
|
8003
8429
|
function countTasks(filter = {}, db) {
|
|
8004
8430
|
const d = db || getDatabase();
|
|
8005
8431
|
const conditions = [];
|
|
@@ -8063,6 +8489,7 @@ function countTasks(filter = {}, db) {
|
|
|
8063
8489
|
conditions.push("task_list_id = ?");
|
|
8064
8490
|
params.push(filter.task_list_id);
|
|
8065
8491
|
}
|
|
8492
|
+
addMetadataConditions(filter.metadata, conditions, params);
|
|
8066
8493
|
if (!filter.include_archived) {
|
|
8067
8494
|
conditions.push("archived_at IS NULL");
|
|
8068
8495
|
}
|
|
@@ -8113,6 +8540,10 @@ function updateTask(id, input, db) {
|
|
|
8113
8540
|
sets.push("assigned_to = ?");
|
|
8114
8541
|
params.push(input.assigned_to);
|
|
8115
8542
|
}
|
|
8543
|
+
if (input.working_dir !== undefined) {
|
|
8544
|
+
sets.push("working_dir = ?");
|
|
8545
|
+
params.push(input.working_dir);
|
|
8546
|
+
}
|
|
8116
8547
|
if (input.tags !== undefined) {
|
|
8117
8548
|
sets.push("tags = ?");
|
|
8118
8549
|
params.push(JSON.stringify(input.tags));
|
|
@@ -8201,6 +8632,8 @@ function updateTask(id, input, db) {
|
|
|
8201
8632
|
logTaskChange(id, "update", "title", task.title, input.title, agentId, d);
|
|
8202
8633
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to)
|
|
8203
8634
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
8635
|
+
if (input.working_dir !== undefined && input.working_dir !== task.working_dir)
|
|
8636
|
+
logTaskChange(id, "update", "working_dir", task.working_dir, input.working_dir, agentId, d);
|
|
8204
8637
|
if (input.approved_by !== undefined)
|
|
8205
8638
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
8206
8639
|
const updatedTask = {
|
|
@@ -8236,6 +8669,10 @@ function updateTask(id, input, db) {
|
|
|
8236
8669
|
if (input.approved_by !== undefined) {
|
|
8237
8670
|
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
8238
8671
|
}
|
|
8672
|
+
const updatePayload = taskEventData(updatedTask);
|
|
8673
|
+
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
8674
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
|
|
8675
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
|
|
8239
8676
|
return updatedTask;
|
|
8240
8677
|
}
|
|
8241
8678
|
function deleteTask(id, db) {
|
|
@@ -10931,6 +11368,7 @@ function getTaskTraceability(taskId, db) {
|
|
|
10931
11368
|
|
|
10932
11369
|
// src/db/task-runs.ts
|
|
10933
11370
|
init_redaction();
|
|
11371
|
+
var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
|
|
10934
11372
|
function parseObject(value) {
|
|
10935
11373
|
if (!value)
|
|
10936
11374
|
return {};
|
|
@@ -10953,6 +11391,72 @@ function rowToArtifact(row) {
|
|
|
10953
11391
|
function getRunRow(runId, db) {
|
|
10954
11392
|
return db.query("SELECT * FROM task_runs WHERE id = ?").get(runId);
|
|
10955
11393
|
}
|
|
11394
|
+
function normalizeTransactionKey(input) {
|
|
11395
|
+
const key = (input.key || input.loop_run_id || input.loop_id || "").trim();
|
|
11396
|
+
if (!key)
|
|
11397
|
+
throw new Error("idempotent run transactions require --key, --loop-run-id, or --loop-id");
|
|
11398
|
+
return key.toLowerCase().replace(/[^a-z0-9._:/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 240);
|
|
11399
|
+
}
|
|
11400
|
+
function loopTransactionMetadata(record) {
|
|
11401
|
+
const value = record.metadata["loop_transaction"];
|
|
11402
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
11403
|
+
}
|
|
11404
|
+
function runKey(record) {
|
|
11405
|
+
const tx = loopTransactionMetadata(record);
|
|
11406
|
+
const key = tx["idempotency_key"] ?? record.metadata["idempotency_key"];
|
|
11407
|
+
return typeof key === "string" ? key : null;
|
|
11408
|
+
}
|
|
11409
|
+
function loopId(record) {
|
|
11410
|
+
const tx = loopTransactionMetadata(record);
|
|
11411
|
+
const value = tx["loop_id"] ?? record.metadata["loop_id"];
|
|
11412
|
+
return typeof value === "string" ? value : null;
|
|
11413
|
+
}
|
|
11414
|
+
function loopRunId(record) {
|
|
11415
|
+
const tx = loopTransactionMetadata(record);
|
|
11416
|
+
const value = tx["loop_run_id"] ?? record.metadata["loop_run_id"];
|
|
11417
|
+
return typeof value === "string" ? value : null;
|
|
11418
|
+
}
|
|
11419
|
+
function getTaskRunTransactionByKey(key, taskId, db) {
|
|
11420
|
+
if (taskId) {
|
|
11421
|
+
return db.query("SELECT * FROM task_run_transactions WHERE task_id = ? AND key = ?").get(taskId, key);
|
|
11422
|
+
}
|
|
11423
|
+
const rows = db.query("SELECT * FROM task_run_transactions WHERE key = ? ORDER BY created_at DESC LIMIT 2").all(key);
|
|
11424
|
+
if (rows.length > 1)
|
|
11425
|
+
throw new Error(`Run transaction key is ambiguous across tasks: ${key}. Pass task_id.`);
|
|
11426
|
+
return rows[0] ?? null;
|
|
11427
|
+
}
|
|
11428
|
+
function summarizeTaskRun(run) {
|
|
11429
|
+
return {
|
|
11430
|
+
id: run.id,
|
|
11431
|
+
task_id: run.task_id,
|
|
11432
|
+
agent_id: run.agent_id,
|
|
11433
|
+
title: run.title,
|
|
11434
|
+
status: run.status,
|
|
11435
|
+
summary: run.summary,
|
|
11436
|
+
idempotency_key: runKey(run),
|
|
11437
|
+
loop_id: loopId(run),
|
|
11438
|
+
loop_run_id: loopRunId(run),
|
|
11439
|
+
metadata_keys: Object.keys(run.metadata).sort(),
|
|
11440
|
+
started_at: run.started_at,
|
|
11441
|
+
completed_at: run.completed_at,
|
|
11442
|
+
updated_at: run.updated_at
|
|
11443
|
+
};
|
|
11444
|
+
}
|
|
11445
|
+
function findTaskRunByTransactionKey(key, taskId, db) {
|
|
11446
|
+
const d = db || getDatabase();
|
|
11447
|
+
const normalized = normalizeTransactionKey({ key });
|
|
11448
|
+
const transaction = getTaskRunTransactionByKey(normalized, taskId, d);
|
|
11449
|
+
if (transaction?.run_id)
|
|
11450
|
+
return getTaskRun(transaction.run_id, d);
|
|
11451
|
+
return null;
|
|
11452
|
+
}
|
|
11453
|
+
function loopRunCommands(run, key) {
|
|
11454
|
+
return [
|
|
11455
|
+
run ? `todos runs show ${run.id.slice(0, 8)}` : "todos runs list",
|
|
11456
|
+
run ? `todos findings list --task ${run.task_id.slice(0, 8)} --json` : "todos findings list --json",
|
|
11457
|
+
`todos runs begin <task-id> --key ${key} --apply --json`
|
|
11458
|
+
];
|
|
11459
|
+
}
|
|
10956
11460
|
function resolveTaskRunId(idOrPrefix, db) {
|
|
10957
11461
|
const d = db || getDatabase();
|
|
10958
11462
|
const rows = d.query("SELECT id FROM task_runs WHERE id = ? OR id LIKE ? ORDER BY created_at DESC LIMIT 2").all(idOrPrefix, `${idOrPrefix}%`);
|
|
@@ -10971,7 +11475,7 @@ function startTaskRun(input, db) {
|
|
|
10971
11475
|
const d = db || getDatabase();
|
|
10972
11476
|
if (!getTask(input.task_id, d))
|
|
10973
11477
|
throw new TaskNotFoundError(input.task_id);
|
|
10974
|
-
const id = uuid();
|
|
11478
|
+
const id = input.id ?? uuid();
|
|
10975
11479
|
const timestamp = input.started_at || now();
|
|
10976
11480
|
if (input.claim && input.agent_id) {
|
|
10977
11481
|
startTask(input.task_id, input.agent_id, d);
|
|
@@ -11009,6 +11513,97 @@ function startTaskRun(input, db) {
|
|
|
11009
11513
|
emitLocalEventHooksQuiet({ type: "run.started", payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title } });
|
|
11010
11514
|
return run;
|
|
11011
11515
|
}
|
|
11516
|
+
function beginTaskRunTransaction(input, db) {
|
|
11517
|
+
const d = db || getDatabase();
|
|
11518
|
+
if (!getTask(input.task_id, d))
|
|
11519
|
+
throw new TaskNotFoundError(input.task_id);
|
|
11520
|
+
const timestamp = input.started_at || now();
|
|
11521
|
+
const key = normalizeTransactionKey(input);
|
|
11522
|
+
const existing = findTaskRunByTransactionKey(key, input.task_id, d);
|
|
11523
|
+
const dryRun = !input.apply;
|
|
11524
|
+
if (existing) {
|
|
11525
|
+
return {
|
|
11526
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11527
|
+
local_only: true,
|
|
11528
|
+
dry_run: dryRun,
|
|
11529
|
+
processed_at: timestamp,
|
|
11530
|
+
action: "matched",
|
|
11531
|
+
key,
|
|
11532
|
+
run: summarizeTaskRun(existing),
|
|
11533
|
+
warnings: existing.status === "running" ? [] : [`matched ${existing.status} run`],
|
|
11534
|
+
commands: loopRunCommands(existing, key)
|
|
11535
|
+
};
|
|
11536
|
+
}
|
|
11537
|
+
if (dryRun) {
|
|
11538
|
+
return {
|
|
11539
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11540
|
+
local_only: true,
|
|
11541
|
+
dry_run: true,
|
|
11542
|
+
processed_at: timestamp,
|
|
11543
|
+
action: "preview",
|
|
11544
|
+
key,
|
|
11545
|
+
run: null,
|
|
11546
|
+
warnings: [],
|
|
11547
|
+
commands: loopRunCommands(null, key)
|
|
11548
|
+
};
|
|
11549
|
+
}
|
|
11550
|
+
const metadata = redactValue({
|
|
11551
|
+
...input.metadata || {},
|
|
11552
|
+
loop_transaction: {
|
|
11553
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11554
|
+
idempotency_key: key,
|
|
11555
|
+
loop_id: input.loop_id ?? null,
|
|
11556
|
+
loop_run_id: input.loop_run_id ?? null,
|
|
11557
|
+
first_seen_at: timestamp
|
|
11558
|
+
},
|
|
11559
|
+
idempotency_key: key
|
|
11560
|
+
});
|
|
11561
|
+
const created = d.transaction(() => {
|
|
11562
|
+
d.run(`INSERT OR IGNORE INTO task_run_transactions (
|
|
11563
|
+
id, task_id, run_id, key, loop_id, loop_run_id, metadata, created_at, updated_at
|
|
11564
|
+
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?)`, [
|
|
11565
|
+
uuid(),
|
|
11566
|
+
input.task_id,
|
|
11567
|
+
key,
|
|
11568
|
+
input.loop_id ?? null,
|
|
11569
|
+
input.loop_run_id ?? null,
|
|
11570
|
+
JSON.stringify(metadata),
|
|
11571
|
+
timestamp,
|
|
11572
|
+
timestamp
|
|
11573
|
+
]);
|
|
11574
|
+
const transaction = getTaskRunTransactionByKey(key, input.task_id, d);
|
|
11575
|
+
if (!transaction)
|
|
11576
|
+
throw new Error(`Could not create run transaction for key: ${key}`);
|
|
11577
|
+
if (transaction.run_id) {
|
|
11578
|
+
const existingRun = getTaskRun(transaction.run_id, d);
|
|
11579
|
+
if (existingRun)
|
|
11580
|
+
return { run: existingRun, action: "matched" };
|
|
11581
|
+
}
|
|
11582
|
+
const run = startTaskRun({
|
|
11583
|
+
id: uuid(),
|
|
11584
|
+
task_id: input.task_id,
|
|
11585
|
+
agent_id: input.agent_id,
|
|
11586
|
+
title: input.title,
|
|
11587
|
+
summary: input.summary,
|
|
11588
|
+
metadata,
|
|
11589
|
+
claim: input.claim,
|
|
11590
|
+
started_at: timestamp
|
|
11591
|
+
}, d);
|
|
11592
|
+
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]);
|
|
11593
|
+
return { run, action: "created" };
|
|
11594
|
+
})();
|
|
11595
|
+
return {
|
|
11596
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11597
|
+
local_only: true,
|
|
11598
|
+
dry_run: false,
|
|
11599
|
+
processed_at: timestamp,
|
|
11600
|
+
action: created.action,
|
|
11601
|
+
key,
|
|
11602
|
+
run: summarizeTaskRun(created.run),
|
|
11603
|
+
warnings: [],
|
|
11604
|
+
commands: loopRunCommands(created.run, key)
|
|
11605
|
+
};
|
|
11606
|
+
}
|
|
11012
11607
|
function addTaskRunEvent(input, db) {
|
|
11013
11608
|
const d = db || getDatabase();
|
|
11014
11609
|
const runId = resolveTaskRunId(input.run_id, d);
|
|
@@ -11188,6 +11783,66 @@ function finishTaskRun(input, db) {
|
|
|
11188
11783
|
});
|
|
11189
11784
|
return updated;
|
|
11190
11785
|
}
|
|
11786
|
+
function finishTaskRunTransaction(input, db) {
|
|
11787
|
+
const d = db || getDatabase();
|
|
11788
|
+
const timestamp = input.completed_at || now();
|
|
11789
|
+
const status = input.status || "completed";
|
|
11790
|
+
const key = input.key ? normalizeTransactionKey({ key: input.key }) : "";
|
|
11791
|
+
const run = input.run_id ? getTaskRun(resolveTaskRunId(input.run_id, d), d) : key ? findTaskRunByTransactionKey(key, input.task_id, d) : null;
|
|
11792
|
+
if (!run) {
|
|
11793
|
+
throw new Error(input.run_id ? `Run not found: ${input.run_id}` : "runs finish requires a run id or --key");
|
|
11794
|
+
}
|
|
11795
|
+
if (input.task_id && run.task_id !== input.task_id) {
|
|
11796
|
+
throw new Error(`Run ${run.id} belongs to task ${run.task_id}, not ${input.task_id}`);
|
|
11797
|
+
}
|
|
11798
|
+
const resolvedKey = key || runKey(run) || run.id;
|
|
11799
|
+
const dryRun = input.apply === false;
|
|
11800
|
+
if (run.status !== "running") {
|
|
11801
|
+
const conflict = run.status !== status;
|
|
11802
|
+
return {
|
|
11803
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11804
|
+
local_only: true,
|
|
11805
|
+
dry_run: dryRun,
|
|
11806
|
+
processed_at: timestamp,
|
|
11807
|
+
action: conflict ? "conflict" : "matched",
|
|
11808
|
+
key: resolvedKey,
|
|
11809
|
+
run: summarizeTaskRun(run),
|
|
11810
|
+
warnings: conflict ? [`run is already ${run.status}; requested ${status}`] : [],
|
|
11811
|
+
commands: loopRunCommands(run, resolvedKey)
|
|
11812
|
+
};
|
|
11813
|
+
}
|
|
11814
|
+
if (dryRun) {
|
|
11815
|
+
return {
|
|
11816
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11817
|
+
local_only: true,
|
|
11818
|
+
dry_run: true,
|
|
11819
|
+
processed_at: timestamp,
|
|
11820
|
+
action: "preview",
|
|
11821
|
+
key: resolvedKey,
|
|
11822
|
+
run: summarizeTaskRun({ ...run, status, summary: input.summary ?? run.summary, completed_at: timestamp, updated_at: timestamp }),
|
|
11823
|
+
warnings: [],
|
|
11824
|
+
commands: loopRunCommands(run, resolvedKey)
|
|
11825
|
+
};
|
|
11826
|
+
}
|
|
11827
|
+
const finished = finishTaskRun({
|
|
11828
|
+
run_id: run.id,
|
|
11829
|
+
status,
|
|
11830
|
+
summary: input.summary,
|
|
11831
|
+
agent_id: input.agent_id,
|
|
11832
|
+
completed_at: timestamp
|
|
11833
|
+
}, d);
|
|
11834
|
+
return {
|
|
11835
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11836
|
+
local_only: true,
|
|
11837
|
+
dry_run: false,
|
|
11838
|
+
processed_at: timestamp,
|
|
11839
|
+
action: "finished",
|
|
11840
|
+
key: resolvedKey,
|
|
11841
|
+
run: summarizeTaskRun(finished),
|
|
11842
|
+
warnings: [],
|
|
11843
|
+
commands: loopRunCommands(finished, resolvedKey)
|
|
11844
|
+
};
|
|
11845
|
+
}
|
|
11191
11846
|
function listTaskRuns(taskId, db) {
|
|
11192
11847
|
const d = db || getDatabase();
|
|
11193
11848
|
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();
|
|
@@ -11749,7 +12404,7 @@ function rowToTask2(row) {
|
|
|
11749
12404
|
requires_approval: Boolean(row.requires_approval)
|
|
11750
12405
|
};
|
|
11751
12406
|
}
|
|
11752
|
-
function
|
|
12407
|
+
function rowToTaskList2(row) {
|
|
11753
12408
|
return { ...row, metadata: parseJsonObject3(row.metadata) };
|
|
11754
12409
|
}
|
|
11755
12410
|
function rowWithMetadata(row) {
|
|
@@ -11785,7 +12440,7 @@ function createLocalBridgeBundle(options = {}, db) {
|
|
|
11785
12440
|
const project = options.project_id ? d.query("SELECT * FROM projects WHERE id = ?").get(options.project_id) : null;
|
|
11786
12441
|
const data = redactValue({
|
|
11787
12442
|
projects: options.project_id ? project ? [project] : [] : d.query("SELECT * FROM projects ORDER BY name").all(),
|
|
11788
|
-
task_lists: (options.project_id ? d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM task_lists ORDER BY name").all()).map(
|
|
12443
|
+
task_lists: (options.project_id ? d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM task_lists ORDER BY name").all()).map(rowToTaskList2),
|
|
11789
12444
|
plans: options.project_id ? d.query("SELECT * FROM plans WHERE project_id = ? ORDER BY created_at").all(options.project_id) : d.query("SELECT * FROM plans ORDER BY created_at").all(),
|
|
11790
12445
|
tasks: queryByTaskIds(d, "SELECT * FROM tasks WHERE id IN (__TASK_IDS__) ORDER BY created_at", taskIds).map(rowToTask2),
|
|
11791
12446
|
task_dependencies: queryByTaskIds(d, "SELECT task_id, depends_on, external_project_id, external_task_id FROM task_dependencies WHERE task_id IN (__TASK_IDS__) ORDER BY task_id, depends_on", taskIds),
|
|
@@ -17368,7 +18023,7 @@ function countMovedStandaloneVerifications(db, duplicateId) {
|
|
|
17368
18023
|
const rows = db.query("SELECT command FROM task_verifications WHERE task_id = ?").all(duplicateId);
|
|
17369
18024
|
return rows.filter((row) => !generatedCommands.has(row.command)).length;
|
|
17370
18025
|
}
|
|
17371
|
-
function
|
|
18026
|
+
function mergeTaskMetadata2(primary, duplicate, input, mergedAt) {
|
|
17372
18027
|
const mergedDuplicates = Array.isArray(primary.metadata["merged_duplicates"]) ? [...primary.metadata["merged_duplicates"]] : [];
|
|
17373
18028
|
mergedDuplicates.push({
|
|
17374
18029
|
id: duplicate.id,
|
|
@@ -17429,7 +18084,7 @@ function mergeDuplicateTask(input, db) {
|
|
|
17429
18084
|
updateTask(primary.id, {
|
|
17430
18085
|
version: primary.version,
|
|
17431
18086
|
tags: mergedTags,
|
|
17432
|
-
metadata:
|
|
18087
|
+
metadata: mergeTaskMetadata2(primary, duplicate, input, mergedAt),
|
|
17433
18088
|
description: mergeTaskDescription(primary, duplicate) ?? undefined
|
|
17434
18089
|
}, d);
|
|
17435
18090
|
moved.comments = updateRows(d, "task_comments", "task_id", duplicate.id, primary.id);
|
|
@@ -19900,6 +20555,33 @@ var TODOS_API_ROUTES = [
|
|
|
19900
20555
|
tags: ["tasks", "mutation"],
|
|
19901
20556
|
stability: "stable"
|
|
19902
20557
|
},
|
|
20558
|
+
{
|
|
20559
|
+
id: "tasks.upsert",
|
|
20560
|
+
method: "POST",
|
|
20561
|
+
path: "/api/tasks/upsert",
|
|
20562
|
+
description: "Create or update a task by stable metadata fingerprint, merging metadata on updates.",
|
|
20563
|
+
auth: "optional-api-key",
|
|
20564
|
+
requestSchema: {
|
|
20565
|
+
type: "object",
|
|
20566
|
+
properties: {
|
|
20567
|
+
fingerprint: { type: "string" },
|
|
20568
|
+
title: { type: "string" },
|
|
20569
|
+
description: { type: "string" },
|
|
20570
|
+
priority: { type: "string", enum: TASK_PRIORITIES },
|
|
20571
|
+
status: { type: "string", enum: TASK_STATUSES },
|
|
20572
|
+
project_id: { type: "string" },
|
|
20573
|
+
task_list_id: { type: "string" },
|
|
20574
|
+
working_dir: { type: "string" },
|
|
20575
|
+
tags: { type: "array", items: { type: "string" } },
|
|
20576
|
+
metadata: objectSchema
|
|
20577
|
+
},
|
|
20578
|
+
required: ["fingerprint", "title"],
|
|
20579
|
+
additionalProperties: true
|
|
20580
|
+
},
|
|
20581
|
+
responseSchema: objectSchema,
|
|
20582
|
+
tags: ["tasks", "mutation", "dedupe"],
|
|
20583
|
+
stability: "stable"
|
|
20584
|
+
},
|
|
19903
20585
|
{
|
|
19904
20586
|
id: "tasks.read",
|
|
19905
20587
|
method: "GET",
|
|
@@ -21174,87 +21856,6 @@ function getCapableAgents(capabilities, opts, db) {
|
|
|
21174
21856
|
return opts?.limit ? scored.slice(0, opts.limit) : scored;
|
|
21175
21857
|
}
|
|
21176
21858
|
|
|
21177
|
-
// src/db/task-lists.ts
|
|
21178
|
-
init_types();
|
|
21179
|
-
init_database();
|
|
21180
|
-
function rowToTaskList2(row) {
|
|
21181
|
-
return {
|
|
21182
|
-
...row,
|
|
21183
|
-
metadata: JSON.parse(row.metadata || "{}")
|
|
21184
|
-
};
|
|
21185
|
-
}
|
|
21186
|
-
function createTaskList(input, db) {
|
|
21187
|
-
const d = db || getDatabase();
|
|
21188
|
-
const id = uuid();
|
|
21189
|
-
const timestamp2 = now();
|
|
21190
|
-
const slug = input.slug || slugify(input.name);
|
|
21191
|
-
if (!input.project_id) {
|
|
21192
|
-
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
21193
|
-
if (existing) {
|
|
21194
|
-
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
21195
|
-
}
|
|
21196
|
-
}
|
|
21197
|
-
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
21198
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp2, timestamp2]);
|
|
21199
|
-
return getTaskList(id, d);
|
|
21200
|
-
}
|
|
21201
|
-
function getTaskList(id, db) {
|
|
21202
|
-
const d = db || getDatabase();
|
|
21203
|
-
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
21204
|
-
return row ? rowToTaskList2(row) : null;
|
|
21205
|
-
}
|
|
21206
|
-
function getTaskListBySlug(slug, projectId, db) {
|
|
21207
|
-
const d = db || getDatabase();
|
|
21208
|
-
let row;
|
|
21209
|
-
if (projectId) {
|
|
21210
|
-
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
21211
|
-
} else {
|
|
21212
|
-
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
21213
|
-
}
|
|
21214
|
-
return row ? rowToTaskList2(row) : null;
|
|
21215
|
-
}
|
|
21216
|
-
function listTaskLists(projectId, db) {
|
|
21217
|
-
const d = db || getDatabase();
|
|
21218
|
-
if (projectId) {
|
|
21219
|
-
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList2);
|
|
21220
|
-
}
|
|
21221
|
-
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList2);
|
|
21222
|
-
}
|
|
21223
|
-
function updateTaskList(id, input, db) {
|
|
21224
|
-
const d = db || getDatabase();
|
|
21225
|
-
const existing = getTaskList(id, d);
|
|
21226
|
-
if (!existing)
|
|
21227
|
-
throw new TaskListNotFoundError(id);
|
|
21228
|
-
const sets = ["updated_at = ?"];
|
|
21229
|
-
const params = [now()];
|
|
21230
|
-
if (input.name !== undefined) {
|
|
21231
|
-
sets.push("name = ?");
|
|
21232
|
-
params.push(input.name);
|
|
21233
|
-
}
|
|
21234
|
-
if (input.description !== undefined) {
|
|
21235
|
-
sets.push("description = ?");
|
|
21236
|
-
params.push(input.description);
|
|
21237
|
-
}
|
|
21238
|
-
if (input.metadata !== undefined) {
|
|
21239
|
-
sets.push("metadata = ?");
|
|
21240
|
-
params.push(JSON.stringify(input.metadata));
|
|
21241
|
-
}
|
|
21242
|
-
params.push(id);
|
|
21243
|
-
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
21244
|
-
return getTaskList(id, d);
|
|
21245
|
-
}
|
|
21246
|
-
function deleteTaskList(id, db) {
|
|
21247
|
-
const d = db || getDatabase();
|
|
21248
|
-
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
21249
|
-
}
|
|
21250
|
-
function ensureTaskList(name, slug, projectId, db) {
|
|
21251
|
-
const d = db || getDatabase();
|
|
21252
|
-
const existing = getTaskListBySlug(slug, projectId, d);
|
|
21253
|
-
if (existing)
|
|
21254
|
-
return existing;
|
|
21255
|
-
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
21256
|
-
}
|
|
21257
|
-
|
|
21258
21859
|
// src/storage/local-sqlite.ts
|
|
21259
21860
|
init_database();
|
|
21260
21861
|
|
|
@@ -28859,6 +29460,7 @@ var READ_ONLY_TOOLS = new Set([
|
|
|
28859
29460
|
"describe_tools",
|
|
28860
29461
|
"search_tools",
|
|
28861
29462
|
"inspect_git_commit",
|
|
29463
|
+
"list_task_findings",
|
|
28862
29464
|
"scan_text_for_secrets",
|
|
28863
29465
|
"check_workspace_permission",
|
|
28864
29466
|
"check_sandbox_command",
|
|
@@ -28884,7 +29486,12 @@ var MINIMAL_TOOLS = new Set([
|
|
|
28884
29486
|
"bootstrap",
|
|
28885
29487
|
"get_tasks_changed_since",
|
|
28886
29488
|
"heartbeat",
|
|
28887
|
-
"release_agent"
|
|
29489
|
+
"release_agent",
|
|
29490
|
+
"begin_task_run_transaction",
|
|
29491
|
+
"finish_task_run",
|
|
29492
|
+
"upsert_task_finding",
|
|
29493
|
+
"list_task_findings",
|
|
29494
|
+
"resolve_missing_task_findings"
|
|
28888
29495
|
]);
|
|
28889
29496
|
var AGENT_SAFE_TOOLS = new Set([
|
|
28890
29497
|
...READ_ONLY_TOOLS,
|
|
@@ -41722,6 +42329,353 @@ async function dispatchToMultiple(input, opts = {}, db) {
|
|
|
41722
42329
|
}
|
|
41723
42330
|
return dispatches;
|
|
41724
42331
|
}
|
|
42332
|
+
// src/db/findings.ts
|
|
42333
|
+
init_redaction();
|
|
42334
|
+
init_types();
|
|
42335
|
+
init_database();
|
|
42336
|
+
var TASK_FINDING_SCHEMA_VERSION = "todos.task_finding.v1";
|
|
42337
|
+
var TASK_FINDING_UPSERT_SCHEMA_VERSION = "todos.task_finding_upsert.v1";
|
|
42338
|
+
var TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION = "todos.task_finding_resolve_missing.v1";
|
|
42339
|
+
var SEVERITIES2 = new Set(["low", "medium", "high", "critical"]);
|
|
42340
|
+
var STATUSES = new Set(["open", "resolved", "ignored"]);
|
|
42341
|
+
function parseObject4(value) {
|
|
42342
|
+
if (!value)
|
|
42343
|
+
return {};
|
|
42344
|
+
try {
|
|
42345
|
+
const parsed = JSON.parse(value);
|
|
42346
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
42347
|
+
} catch {
|
|
42348
|
+
return {};
|
|
42349
|
+
}
|
|
42350
|
+
}
|
|
42351
|
+
function normalizeKey(value) {
|
|
42352
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9._:/-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
42353
|
+
}
|
|
42354
|
+
function normalizeFingerprint(value) {
|
|
42355
|
+
const normalized = normalizeKey(value);
|
|
42356
|
+
if (!normalized)
|
|
42357
|
+
throw new Error("finding fingerprint is required");
|
|
42358
|
+
return normalized.slice(0, 240);
|
|
42359
|
+
}
|
|
42360
|
+
function normalizeSeverity2(value) {
|
|
42361
|
+
const normalized = normalizeKey(value || "medium");
|
|
42362
|
+
if (SEVERITIES2.has(normalized))
|
|
42363
|
+
return normalized;
|
|
42364
|
+
if (/^(p0|blocker|urgent|highest)$/.test(normalized))
|
|
42365
|
+
return "critical";
|
|
42366
|
+
if (/^(p1|major)$/.test(normalized))
|
|
42367
|
+
return "high";
|
|
42368
|
+
if (/^(p3|minor|info)$/.test(normalized))
|
|
42369
|
+
return "low";
|
|
42370
|
+
return "medium";
|
|
42371
|
+
}
|
|
42372
|
+
function normalizeStatus(value) {
|
|
42373
|
+
const normalized = normalizeKey(value || "open");
|
|
42374
|
+
if (STATUSES.has(normalized))
|
|
42375
|
+
return normalized;
|
|
42376
|
+
if (normalized === "closed" || normalized === "fixed")
|
|
42377
|
+
return "resolved";
|
|
42378
|
+
return "open";
|
|
42379
|
+
}
|
|
42380
|
+
function normalizeResolutionStatus(value) {
|
|
42381
|
+
const status = normalizeStatus(value || "resolved");
|
|
42382
|
+
if (status === "open")
|
|
42383
|
+
throw new Error("resolve-missing status must be resolved or ignored");
|
|
42384
|
+
return status;
|
|
42385
|
+
}
|
|
42386
|
+
function redactOptional(value, max = 2000) {
|
|
42387
|
+
if (!value)
|
|
42388
|
+
return null;
|
|
42389
|
+
const redacted = redactEvidenceText(value).trim();
|
|
42390
|
+
if (!redacted)
|
|
42391
|
+
return null;
|
|
42392
|
+
return redacted.length > max ? `${redacted.slice(0, max - 3)}...` : redacted;
|
|
42393
|
+
}
|
|
42394
|
+
function rowToFinding(row) {
|
|
42395
|
+
return {
|
|
42396
|
+
schema_version: TASK_FINDING_SCHEMA_VERSION,
|
|
42397
|
+
...row,
|
|
42398
|
+
severity: normalizeSeverity2(row.severity),
|
|
42399
|
+
status: normalizeStatus(row.status),
|
|
42400
|
+
metadata: parseObject4(row.metadata)
|
|
42401
|
+
};
|
|
42402
|
+
}
|
|
42403
|
+
function compactFinding(finding2) {
|
|
42404
|
+
return {
|
|
42405
|
+
schema_version: TASK_FINDING_SCHEMA_VERSION,
|
|
42406
|
+
id: finding2.id,
|
|
42407
|
+
task_id: finding2.task_id,
|
|
42408
|
+
run_id: finding2.run_id,
|
|
42409
|
+
fingerprint: finding2.fingerprint,
|
|
42410
|
+
title: finding2.title,
|
|
42411
|
+
severity: finding2.severity,
|
|
42412
|
+
status: finding2.status,
|
|
42413
|
+
source: finding2.source,
|
|
42414
|
+
summary: finding2.summary,
|
|
42415
|
+
artifact_path: finding2.artifact_path,
|
|
42416
|
+
first_seen_at: finding2.first_seen_at,
|
|
42417
|
+
last_seen_at: finding2.last_seen_at,
|
|
42418
|
+
resolved_at: finding2.resolved_at,
|
|
42419
|
+
metadata_keys: Object.keys(finding2.metadata).sort()
|
|
42420
|
+
};
|
|
42421
|
+
}
|
|
42422
|
+
function previewFinding(existing, next, timestamp2) {
|
|
42423
|
+
return {
|
|
42424
|
+
...existing,
|
|
42425
|
+
run_id: next.run_id,
|
|
42426
|
+
title: next.title,
|
|
42427
|
+
severity: next.severity,
|
|
42428
|
+
status: next.status,
|
|
42429
|
+
source: next.source,
|
|
42430
|
+
summary: next.summary,
|
|
42431
|
+
artifact_path: next.artifact_path,
|
|
42432
|
+
metadata: next.metadata,
|
|
42433
|
+
last_seen_at: timestamp2,
|
|
42434
|
+
resolved_at: next.status === "open" ? null : existing.resolved_at || timestamp2,
|
|
42435
|
+
updated_at: timestamp2
|
|
42436
|
+
};
|
|
42437
|
+
}
|
|
42438
|
+
function upsertAction(existing, next) {
|
|
42439
|
+
if (sameFinding(existing, next))
|
|
42440
|
+
return "matched";
|
|
42441
|
+
return existing.status !== "open" && next.status === "open" ? "reopened" : "updated";
|
|
42442
|
+
}
|
|
42443
|
+
function resolveRunForTask(runId, taskId, db) {
|
|
42444
|
+
if (!runId)
|
|
42445
|
+
return null;
|
|
42446
|
+
const resolved = resolveTaskRunId(runId, db);
|
|
42447
|
+
const run = getTaskRun(resolved, db);
|
|
42448
|
+
if (!run)
|
|
42449
|
+
throw new Error(`Run not found: ${runId}`);
|
|
42450
|
+
if (run.task_id !== taskId)
|
|
42451
|
+
throw new Error(`Run ${resolved} belongs to task ${run.task_id}, not ${taskId}`);
|
|
42452
|
+
return resolved;
|
|
42453
|
+
}
|
|
42454
|
+
function getFindingByFingerprint(taskId, fingerprint4, db) {
|
|
42455
|
+
const row = db.query("SELECT * FROM task_findings WHERE task_id = ? AND fingerprint = ?").get(taskId, fingerprint4);
|
|
42456
|
+
return row ? rowToFinding(row) : null;
|
|
42457
|
+
}
|
|
42458
|
+
function assertTask(taskId, db) {
|
|
42459
|
+
if (!getTask(taskId, db))
|
|
42460
|
+
throw new TaskNotFoundError(taskId);
|
|
42461
|
+
}
|
|
42462
|
+
function nextFinding(input, db) {
|
|
42463
|
+
const fingerprint4 = normalizeFingerprint(input.fingerprint);
|
|
42464
|
+
const title = redactOptional(input.title, 300);
|
|
42465
|
+
if (!title)
|
|
42466
|
+
throw new Error("finding title is required");
|
|
42467
|
+
return {
|
|
42468
|
+
fingerprint: fingerprint4,
|
|
42469
|
+
run_id: resolveRunForTask(input.run_id, input.task_id, db),
|
|
42470
|
+
title,
|
|
42471
|
+
severity: normalizeSeverity2(input.severity),
|
|
42472
|
+
status: normalizeStatus(input.status),
|
|
42473
|
+
source: redactOptional(input.source, 120),
|
|
42474
|
+
summary: redactOptional(input.summary, 2000),
|
|
42475
|
+
artifact_path: redactOptional(input.artifact_path, 1000),
|
|
42476
|
+
metadata: redactValue(input.metadata || {})
|
|
42477
|
+
};
|
|
42478
|
+
}
|
|
42479
|
+
function sameFinding(left, right) {
|
|
42480
|
+
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);
|
|
42481
|
+
}
|
|
42482
|
+
function upsertTaskFinding(input, db) {
|
|
42483
|
+
const d = db || getDatabase();
|
|
42484
|
+
assertTask(input.task_id, d);
|
|
42485
|
+
const timestamp2 = input.observed_at || now();
|
|
42486
|
+
const warnings = [];
|
|
42487
|
+
const next = nextFinding(input, d);
|
|
42488
|
+
const existing = getFindingByFingerprint(input.task_id, next.fingerprint, d);
|
|
42489
|
+
const dryRun = !input.apply;
|
|
42490
|
+
if (dryRun) {
|
|
42491
|
+
const action2 = existing ? upsertAction(existing, next) : "preview";
|
|
42492
|
+
return {
|
|
42493
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
42494
|
+
local_only: true,
|
|
42495
|
+
dry_run: true,
|
|
42496
|
+
processed_at: timestamp2,
|
|
42497
|
+
action: action2,
|
|
42498
|
+
fingerprint: next.fingerprint,
|
|
42499
|
+
finding: existing ? compactFinding(previewFinding(existing, next, timestamp2)) : null,
|
|
42500
|
+
warnings
|
|
42501
|
+
};
|
|
42502
|
+
}
|
|
42503
|
+
if (!existing) {
|
|
42504
|
+
const id = uuid();
|
|
42505
|
+
d.run(`INSERT INTO task_findings (
|
|
42506
|
+
id, task_id, run_id, fingerprint, title, severity, status, source, summary, artifact_path,
|
|
42507
|
+
metadata, first_seen_at, last_seen_at, resolved_at, created_at, updated_at
|
|
42508
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
42509
|
+
id,
|
|
42510
|
+
input.task_id,
|
|
42511
|
+
next.run_id,
|
|
42512
|
+
next.fingerprint,
|
|
42513
|
+
next.title,
|
|
42514
|
+
next.severity,
|
|
42515
|
+
next.status,
|
|
42516
|
+
next.source,
|
|
42517
|
+
next.summary,
|
|
42518
|
+
next.artifact_path,
|
|
42519
|
+
JSON.stringify(next.metadata),
|
|
42520
|
+
timestamp2,
|
|
42521
|
+
timestamp2,
|
|
42522
|
+
next.status === "open" ? null : timestamp2,
|
|
42523
|
+
timestamp2,
|
|
42524
|
+
timestamp2
|
|
42525
|
+
]);
|
|
42526
|
+
return {
|
|
42527
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
42528
|
+
local_only: true,
|
|
42529
|
+
dry_run: false,
|
|
42530
|
+
processed_at: timestamp2,
|
|
42531
|
+
action: "created",
|
|
42532
|
+
fingerprint: next.fingerprint,
|
|
42533
|
+
finding: compactFinding(getFindingByFingerprint(input.task_id, next.fingerprint, d)),
|
|
42534
|
+
warnings
|
|
42535
|
+
};
|
|
42536
|
+
}
|
|
42537
|
+
if (sameFinding(existing, next)) {
|
|
42538
|
+
return {
|
|
42539
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
42540
|
+
local_only: true,
|
|
42541
|
+
dry_run: false,
|
|
42542
|
+
processed_at: timestamp2,
|
|
42543
|
+
action: "matched",
|
|
42544
|
+
fingerprint: next.fingerprint,
|
|
42545
|
+
finding: compactFinding(existing),
|
|
42546
|
+
warnings
|
|
42547
|
+
};
|
|
42548
|
+
}
|
|
42549
|
+
const action = upsertAction(existing, next);
|
|
42550
|
+
d.run(`UPDATE task_findings SET
|
|
42551
|
+
run_id = ?, title = ?, severity = ?, status = ?, source = ?, summary = ?, artifact_path = ?,
|
|
42552
|
+
metadata = ?, last_seen_at = ?, resolved_at = ?, updated_at = ?
|
|
42553
|
+
WHERE id = ?`, [
|
|
42554
|
+
next.run_id,
|
|
42555
|
+
next.title,
|
|
42556
|
+
next.severity,
|
|
42557
|
+
next.status,
|
|
42558
|
+
next.source,
|
|
42559
|
+
next.summary,
|
|
42560
|
+
next.artifact_path,
|
|
42561
|
+
JSON.stringify(next.metadata),
|
|
42562
|
+
timestamp2,
|
|
42563
|
+
next.status === "open" ? null : existing.resolved_at || timestamp2,
|
|
42564
|
+
timestamp2,
|
|
42565
|
+
existing.id
|
|
42566
|
+
]);
|
|
42567
|
+
return {
|
|
42568
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
42569
|
+
local_only: true,
|
|
42570
|
+
dry_run: false,
|
|
42571
|
+
processed_at: timestamp2,
|
|
42572
|
+
action,
|
|
42573
|
+
fingerprint: next.fingerprint,
|
|
42574
|
+
finding: compactFinding(getFindingByFingerprint(input.task_id, next.fingerprint, d)),
|
|
42575
|
+
warnings
|
|
42576
|
+
};
|
|
42577
|
+
}
|
|
42578
|
+
function listTaskFindings(filter = {}, db) {
|
|
42579
|
+
const d = db || getDatabase();
|
|
42580
|
+
const conditions = ["1=1"];
|
|
42581
|
+
const params = [];
|
|
42582
|
+
if (filter.task_id) {
|
|
42583
|
+
conditions.push("task_id = ?");
|
|
42584
|
+
params.push(filter.task_id);
|
|
42585
|
+
}
|
|
42586
|
+
if (filter.run_id) {
|
|
42587
|
+
conditions.push("run_id = ?");
|
|
42588
|
+
params.push(resolveTaskRunId(filter.run_id, d));
|
|
42589
|
+
}
|
|
42590
|
+
if (filter.status) {
|
|
42591
|
+
conditions.push("status = ?");
|
|
42592
|
+
params.push(normalizeStatus(filter.status));
|
|
42593
|
+
}
|
|
42594
|
+
if (filter.source) {
|
|
42595
|
+
conditions.push("source = ?");
|
|
42596
|
+
params.push(redactOptional(filter.source, 120));
|
|
42597
|
+
}
|
|
42598
|
+
const limit = Math.min(Math.max(Math.floor(filter.limit ?? 50), 1), 500);
|
|
42599
|
+
const rows = d.query(`SELECT * FROM task_findings WHERE ${conditions.join(" AND ")} ORDER BY updated_at DESC, created_at DESC LIMIT ?`).all(...[...params, limit]);
|
|
42600
|
+
return rows.map(rowToFinding);
|
|
42601
|
+
}
|
|
42602
|
+
function listCompactTaskFindings(filter = {}, db) {
|
|
42603
|
+
return listTaskFindings(filter, db).map(compactFinding);
|
|
42604
|
+
}
|
|
42605
|
+
function resolveMissingTaskFindings(input, db) {
|
|
42606
|
+
const d = db || getDatabase();
|
|
42607
|
+
assertTask(input.task_id, d);
|
|
42608
|
+
const timestamp2 = input.resolved_at || now();
|
|
42609
|
+
const status = normalizeResolutionStatus(input.status);
|
|
42610
|
+
const runId = resolveRunForTask(input.run_id, input.task_id, d);
|
|
42611
|
+
const present = new Set(input.fingerprints.map(normalizeFingerprint));
|
|
42612
|
+
const warnings = [];
|
|
42613
|
+
const conditions = ["task_id = ?", "status = 'open'"];
|
|
42614
|
+
const params = [input.task_id];
|
|
42615
|
+
if (input.source) {
|
|
42616
|
+
conditions.push("source = ?");
|
|
42617
|
+
params.push(redactOptional(input.source, 120));
|
|
42618
|
+
}
|
|
42619
|
+
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));
|
|
42620
|
+
const limit = Math.min(Math.max(Math.floor(input.limit ?? 50), 1), 200);
|
|
42621
|
+
const display = candidates.slice(0, limit);
|
|
42622
|
+
const omittedCount = Math.max(0, candidates.length - display.length);
|
|
42623
|
+
if (!input.apply) {
|
|
42624
|
+
return {
|
|
42625
|
+
schema_version: TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
|
|
42626
|
+
local_only: true,
|
|
42627
|
+
dry_run: true,
|
|
42628
|
+
processed_at: timestamp2,
|
|
42629
|
+
action: candidates.length > 0 ? "preview" : "noop",
|
|
42630
|
+
task_id: input.task_id,
|
|
42631
|
+
source: input.source ? redactOptional(input.source, 120) : null,
|
|
42632
|
+
run_id: runId,
|
|
42633
|
+
present_fingerprint_count: present.size,
|
|
42634
|
+
candidate_count: candidates.length,
|
|
42635
|
+
changed_count: 0,
|
|
42636
|
+
omitted_count: omittedCount,
|
|
42637
|
+
findings: display.map(compactFinding),
|
|
42638
|
+
warnings
|
|
42639
|
+
};
|
|
42640
|
+
}
|
|
42641
|
+
const metadataPatch = redactValue({
|
|
42642
|
+
resolved_by: {
|
|
42643
|
+
agent_id: input.agent_id ?? null,
|
|
42644
|
+
run_id: runId,
|
|
42645
|
+
reason: input.reason ? redactEvidenceText(input.reason) : "missing from latest loop finding set"
|
|
42646
|
+
}
|
|
42647
|
+
});
|
|
42648
|
+
const tx = d.transaction(() => {
|
|
42649
|
+
for (const finding2 of candidates) {
|
|
42650
|
+
d.run("UPDATE task_findings SET status = ?, resolved_at = ?, metadata = ?, updated_at = ? WHERE id = ? AND status = 'open'", [
|
|
42651
|
+
status,
|
|
42652
|
+
timestamp2,
|
|
42653
|
+
JSON.stringify({ ...finding2.metadata, ...metadataPatch }),
|
|
42654
|
+
timestamp2,
|
|
42655
|
+
finding2.id
|
|
42656
|
+
]);
|
|
42657
|
+
}
|
|
42658
|
+
});
|
|
42659
|
+
tx();
|
|
42660
|
+
const updated = candidates.map((finding2) => d.query("SELECT * FROM task_findings WHERE id = ?").get(finding2.id)).filter((row) => Boolean(row)).map(rowToFinding);
|
|
42661
|
+
const visibleUpdated = updated.slice(0, limit);
|
|
42662
|
+
return {
|
|
42663
|
+
schema_version: TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
|
|
42664
|
+
local_only: true,
|
|
42665
|
+
dry_run: false,
|
|
42666
|
+
processed_at: timestamp2,
|
|
42667
|
+
action: updated.length > 0 ? status : "noop",
|
|
42668
|
+
task_id: input.task_id,
|
|
42669
|
+
source: input.source ? redactOptional(input.source, 120) : null,
|
|
42670
|
+
run_id: runId,
|
|
42671
|
+
present_fingerprint_count: present.size,
|
|
42672
|
+
candidate_count: candidates.length,
|
|
42673
|
+
changed_count: updated.length,
|
|
42674
|
+
omitted_count: omittedCount,
|
|
42675
|
+
findings: visibleUpdated.map(compactFinding),
|
|
42676
|
+
warnings
|
|
42677
|
+
};
|
|
42678
|
+
}
|
|
41725
42679
|
|
|
41726
42680
|
// src/index.ts
|
|
41727
42681
|
init_redaction();
|
|
@@ -43052,6 +44006,8 @@ export {
|
|
|
43052
44006
|
upsertTesterIssueReports,
|
|
43053
44007
|
upsertTesterIssueReport,
|
|
43054
44008
|
upsertTerminalNotificationRule,
|
|
44009
|
+
upsertTaskFinding,
|
|
44010
|
+
upsertTaskByFingerprint,
|
|
43055
44011
|
upsertSecretSafetyConfig,
|
|
43056
44012
|
upsertRunnerSandboxProfile,
|
|
43057
44013
|
upsertReviewRoutingRule,
|
|
@@ -43101,6 +44057,7 @@ export {
|
|
|
43101
44057
|
syncWithAgent,
|
|
43102
44058
|
syncKgEdges,
|
|
43103
44059
|
supersedeDecisionRecord,
|
|
44060
|
+
summarizeTaskRun,
|
|
43104
44061
|
summarizeRoadmap,
|
|
43105
44062
|
summarizeMilestone,
|
|
43106
44063
|
suggestAgentNames,
|
|
@@ -43178,6 +44135,7 @@ export {
|
|
|
43178
44135
|
resolvePlanRef,
|
|
43179
44136
|
resolvePlanId,
|
|
43180
44137
|
resolvePartialId,
|
|
44138
|
+
resolveMissingTaskFindings,
|
|
43181
44139
|
resolveMentions,
|
|
43182
44140
|
resolveGitRoot,
|
|
43183
44141
|
resolveCommandQuery,
|
|
@@ -43352,6 +44310,7 @@ export {
|
|
|
43352
44310
|
listTasks,
|
|
43353
44311
|
listTaskRuns,
|
|
43354
44312
|
listTaskLists,
|
|
44313
|
+
listTaskFindings,
|
|
43355
44314
|
listTaskFiles,
|
|
43356
44315
|
listTaskBoards,
|
|
43357
44316
|
listSubscriptions,
|
|
@@ -43404,6 +44363,7 @@ export {
|
|
|
43404
44363
|
listCyclesWithStats,
|
|
43405
44364
|
listCycles,
|
|
43406
44365
|
listCustomFieldDefinitions,
|
|
44366
|
+
listCompactTaskFindings,
|
|
43407
44367
|
listComments,
|
|
43408
44368
|
listCommandAliases,
|
|
43409
44369
|
listCapacityProfiles,
|
|
@@ -43510,6 +44470,7 @@ export {
|
|
|
43510
44470
|
getTaskCustomFields,
|
|
43511
44471
|
getTaskContract,
|
|
43512
44472
|
getTaskCommits,
|
|
44473
|
+
getTaskByFingerprint,
|
|
43513
44474
|
getTaskBoard,
|
|
43514
44475
|
getTask,
|
|
43515
44476
|
getStoredHandoffAsPacket,
|
|
@@ -43689,10 +44650,12 @@ export {
|
|
|
43689
44650
|
formatDecisionRecordMarkdown,
|
|
43690
44651
|
formatAgentWorkflowDemoReport,
|
|
43691
44652
|
formatActivityRecordText,
|
|
44653
|
+
finishTaskRunTransaction,
|
|
43692
44654
|
finishTaskRun,
|
|
43693
44655
|
fingerprintTesterIssueReport,
|
|
43694
44656
|
fingerprintInboxInput,
|
|
43695
44657
|
findTasksByFile,
|
|
44658
|
+
findTaskRunByTransactionKey,
|
|
43696
44659
|
findTaskByCommit,
|
|
43697
44660
|
findRelatedTaskIds,
|
|
43698
44661
|
findPath,
|
|
@@ -43914,6 +44877,7 @@ export {
|
|
|
43914
44877
|
buildCodebaseIndex,
|
|
43915
44878
|
buildArtifactExportManifest,
|
|
43916
44879
|
bootstrapProject,
|
|
44880
|
+
beginTaskRunTransaction,
|
|
43917
44881
|
backupDatabase,
|
|
43918
44882
|
autoReleaseStaleAgents,
|
|
43919
44883
|
autoDetectFileRelationships,
|
|
@@ -43999,6 +44963,9 @@ export {
|
|
|
43999
44963
|
TASK_STATUSES,
|
|
44000
44964
|
TASK_SCHEDULING_SCHEMA,
|
|
44001
44965
|
TASK_PRIORITIES,
|
|
44966
|
+
TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
44967
|
+
TASK_FINDING_SCHEMA_VERSION,
|
|
44968
|
+
TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
|
|
44002
44969
|
STORAGE_TABLES,
|
|
44003
44970
|
SECRET_REDACTION_SCHEMA,
|
|
44004
44971
|
SCHEMA_SEMVER,
|
|
@@ -44035,6 +45002,7 @@ export {
|
|
|
44035
45002
|
MANPAGE_SCHEMA,
|
|
44036
45003
|
MACHINE_TOPOLOGY_SCHEMA,
|
|
44037
45004
|
LockError,
|
|
45005
|
+
LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
44038
45006
|
LOCAL_USAGE_LEDGER_SCHEMA_VERSION,
|
|
44039
45007
|
LOCAL_ROADMAP_SCHEMA_VERSION,
|
|
44040
45008
|
LOCAL_REPORT_TYPES,
|