@hasna/todos 0.11.41 → 0.11.42
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 +20 -0
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/index.js +741 -141
- package/dist/cli-mcp-parity.d.ts.map +1 -1
- package/dist/contracts.js +73 -6
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +519 -103
- package/dist/json-contracts.d.ts.map +1 -1
- package/dist/lib/local-bridge.d.ts +2 -0
- package/dist/lib/local-bridge.d.ts.map +1 -1
- package/dist/lib/saved-search-views.d.ts +60 -0
- package/dist/lib/saved-search-views.d.ts.map +1 -0
- package/dist/lib/todos-md.d.ts.map +1 -1
- package/dist/mcp/index.js +544 -96
- package/dist/mcp/token-utils.d.ts.map +1 -1
- package/dist/mcp/tools/task-meta-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
- package/dist/mcp.js +4 -0
- package/dist/registry.js +86 -7
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +24 -0
- package/dist/storage.js +24 -0
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -981,6 +981,19 @@ var init_migrations = __esm(() => {
|
|
|
981
981
|
);
|
|
982
982
|
CREATE INDEX IF NOT EXISTS idx_handoff_acks_agent ON handoff_acknowledgements(agent_id, acknowledged_at);
|
|
983
983
|
INSERT OR IGNORE INTO _migrations (id) VALUES (54);
|
|
984
|
+
`,
|
|
985
|
+
`
|
|
986
|
+
CREATE TABLE IF NOT EXISTS saved_search_views (
|
|
987
|
+
id TEXT PRIMARY KEY,
|
|
988
|
+
name TEXT NOT NULL UNIQUE,
|
|
989
|
+
description TEXT,
|
|
990
|
+
scope TEXT NOT NULL DEFAULT 'tasks' CHECK(scope IN ('all', 'tasks', 'projects', 'plans', 'runs', 'comments')),
|
|
991
|
+
filters TEXT NOT NULL DEFAULT '{}',
|
|
992
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
993
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
994
|
+
);
|
|
995
|
+
CREATE INDEX IF NOT EXISTS idx_saved_search_views_scope ON saved_search_views(scope);
|
|
996
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (55);
|
|
984
997
|
`
|
|
985
998
|
];
|
|
986
999
|
});
|
|
@@ -1157,6 +1170,17 @@ function ensureSchema(db) {
|
|
|
1157
1170
|
PRIMARY KEY (handoff_id, agent_id)
|
|
1158
1171
|
)`);
|
|
1159
1172
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_handoff_acks_agent ON handoff_acknowledgements(agent_id, acknowledged_at)");
|
|
1173
|
+
ensureTable("saved_search_views", `
|
|
1174
|
+
CREATE TABLE saved_search_views (
|
|
1175
|
+
id TEXT PRIMARY KEY,
|
|
1176
|
+
name TEXT NOT NULL UNIQUE,
|
|
1177
|
+
description TEXT,
|
|
1178
|
+
scope TEXT NOT NULL DEFAULT 'tasks' CHECK(scope IN ('all', 'tasks', 'projects', 'plans', 'runs', 'comments')),
|
|
1179
|
+
filters TEXT NOT NULL DEFAULT '{}',
|
|
1180
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1181
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1182
|
+
)`);
|
|
1183
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_saved_search_views_scope ON saved_search_views(scope)");
|
|
1160
1184
|
ensureTable("task_relationships", `
|
|
1161
1185
|
CREATE TABLE task_relationships (
|
|
1162
1186
|
id TEXT PRIMARY KEY,
|
|
@@ -6611,6 +6635,101 @@ var init_task_runs = __esm(() => {
|
|
|
6611
6635
|
init_tasks();
|
|
6612
6636
|
});
|
|
6613
6637
|
|
|
6638
|
+
// src/lib/local-fields.ts
|
|
6639
|
+
function normalizeList(values) {
|
|
6640
|
+
return [...new Set((values || []).map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
6641
|
+
}
|
|
6642
|
+
function metadataFields(task) {
|
|
6643
|
+
const value = task.metadata[LOCAL_FIELDS_KEY];
|
|
6644
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6645
|
+
}
|
|
6646
|
+
function sameCustomValue(actual, expected) {
|
|
6647
|
+
return JSON.stringify(actual) === JSON.stringify(expected);
|
|
6648
|
+
}
|
|
6649
|
+
function hasOwnField(fields, key) {
|
|
6650
|
+
return Object.prototype.hasOwnProperty.call(fields, key);
|
|
6651
|
+
}
|
|
6652
|
+
function getTaskLocalFields(taskId, db) {
|
|
6653
|
+
const d = db || getDatabase();
|
|
6654
|
+
const task = getTask(taskId, d);
|
|
6655
|
+
if (!task)
|
|
6656
|
+
throw new TaskNotFoundError(taskId);
|
|
6657
|
+
const fields = metadataFields(task);
|
|
6658
|
+
return {
|
|
6659
|
+
labels: normalizeList(fields.labels),
|
|
6660
|
+
priority: task.priority,
|
|
6661
|
+
severity: typeof fields.severity === "string" ? fields.severity : null,
|
|
6662
|
+
owner: hasOwnField(fields, "owner") ? typeof fields.owner === "string" ? fields.owner : null : task.assigned_to,
|
|
6663
|
+
area: typeof fields.area === "string" ? fields.area : null,
|
|
6664
|
+
custom: fields.custom && typeof fields.custom === "object" && !Array.isArray(fields.custom) ? fields.custom : {}
|
|
6665
|
+
};
|
|
6666
|
+
}
|
|
6667
|
+
function setTaskLocalFields(taskId, input, db) {
|
|
6668
|
+
const d = db || getDatabase();
|
|
6669
|
+
const task = getTask(taskId, d);
|
|
6670
|
+
if (!task)
|
|
6671
|
+
throw new TaskNotFoundError(taskId);
|
|
6672
|
+
const currentFields = getTaskLocalFields(taskId, d);
|
|
6673
|
+
const labels = input.labels !== undefined ? normalizeList(input.labels) : currentFields.labels;
|
|
6674
|
+
const custom2 = input.custom !== undefined ? redactValue(input.merge_custom === false ? input.custom : { ...currentFields.custom, ...input.custom }) : currentFields.custom;
|
|
6675
|
+
const nextFields = {
|
|
6676
|
+
labels,
|
|
6677
|
+
priority: input.priority || task.priority,
|
|
6678
|
+
severity: input.severity !== undefined ? input.severity : currentFields.severity,
|
|
6679
|
+
owner: input.owner !== undefined ? input.owner : currentFields.owner,
|
|
6680
|
+
area: input.area !== undefined ? input.area : currentFields.area,
|
|
6681
|
+
custom: custom2
|
|
6682
|
+
};
|
|
6683
|
+
const nextMetadata = {
|
|
6684
|
+
...task.metadata,
|
|
6685
|
+
[LOCAL_FIELDS_KEY]: nextFields
|
|
6686
|
+
};
|
|
6687
|
+
const previousLabels = new Set(currentFields.labels);
|
|
6688
|
+
const nextTags = normalizeList([...task.tags.filter((tag) => !previousLabels.has(tag)), ...labels]);
|
|
6689
|
+
const updates = {
|
|
6690
|
+
version: task.version,
|
|
6691
|
+
priority: input.priority,
|
|
6692
|
+
tags: nextTags,
|
|
6693
|
+
metadata: nextMetadata
|
|
6694
|
+
};
|
|
6695
|
+
if (input.owner !== undefined)
|
|
6696
|
+
updates.assigned_to = nextFields.owner;
|
|
6697
|
+
return updateTask(taskId, updates, d);
|
|
6698
|
+
}
|
|
6699
|
+
function queryTasksByLocalFields(query, db) {
|
|
6700
|
+
const d = db || getDatabase();
|
|
6701
|
+
const tasks = listTasks({
|
|
6702
|
+
priority: query.priority,
|
|
6703
|
+
tags: query.labels,
|
|
6704
|
+
limit: 1e4
|
|
6705
|
+
}, d);
|
|
6706
|
+
const matches = tasks.filter((task) => {
|
|
6707
|
+
const fields = getTaskLocalFields(task.id, d);
|
|
6708
|
+
if (query.labels && !query.labels.every((label) => fields.labels.includes(label)))
|
|
6709
|
+
return false;
|
|
6710
|
+
if (query.severity && fields.severity !== query.severity)
|
|
6711
|
+
return false;
|
|
6712
|
+
if (query.owner && fields.owner !== query.owner)
|
|
6713
|
+
return false;
|
|
6714
|
+
if (query.area && fields.area !== query.area)
|
|
6715
|
+
return false;
|
|
6716
|
+
if (query.custom) {
|
|
6717
|
+
for (const [key, expected] of Object.entries(query.custom)) {
|
|
6718
|
+
if (!sameCustomValue(fields.custom[key], expected))
|
|
6719
|
+
return false;
|
|
6720
|
+
}
|
|
6721
|
+
}
|
|
6722
|
+
return true;
|
|
6723
|
+
});
|
|
6724
|
+
return matches.slice(0, query.limit || 100);
|
|
6725
|
+
}
|
|
6726
|
+
var LOCAL_FIELDS_KEY = "local_fields";
|
|
6727
|
+
var init_local_fields = __esm(() => {
|
|
6728
|
+
init_database();
|
|
6729
|
+
init_tasks();
|
|
6730
|
+
init_types();
|
|
6731
|
+
});
|
|
6732
|
+
|
|
6614
6733
|
// src/db/task-relationships.ts
|
|
6615
6734
|
var exports_task_relationships = {};
|
|
6616
6735
|
__export(exports_task_relationships, {
|
|
@@ -6852,6 +6971,347 @@ var init_search = __esm(() => {
|
|
|
6852
6971
|
init_database();
|
|
6853
6972
|
});
|
|
6854
6973
|
|
|
6974
|
+
// src/lib/saved-search-views.ts
|
|
6975
|
+
var exports_saved_search_views = {};
|
|
6976
|
+
__export(exports_saved_search_views, {
|
|
6977
|
+
saveSearchView: () => saveSearchView,
|
|
6978
|
+
runSearchView: () => runSearchView,
|
|
6979
|
+
runSavedSearch: () => runSavedSearch,
|
|
6980
|
+
normalizeScope: () => normalizeScope,
|
|
6981
|
+
listSearchViews: () => listSearchViews,
|
|
6982
|
+
getSearchView: () => getSearchView,
|
|
6983
|
+
deleteSearchView: () => deleteSearchView
|
|
6984
|
+
});
|
|
6985
|
+
function parseFilters(value) {
|
|
6986
|
+
if (!value)
|
|
6987
|
+
return {};
|
|
6988
|
+
try {
|
|
6989
|
+
const parsed = JSON.parse(value);
|
|
6990
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
6991
|
+
} catch {
|
|
6992
|
+
return {};
|
|
6993
|
+
}
|
|
6994
|
+
}
|
|
6995
|
+
function rowToSavedSearchView(row) {
|
|
6996
|
+
return {
|
|
6997
|
+
...row,
|
|
6998
|
+
scope: normalizeScope(row.scope),
|
|
6999
|
+
filters: parseFilters(row.filters)
|
|
7000
|
+
};
|
|
7001
|
+
}
|
|
7002
|
+
function normalizeScope(scope) {
|
|
7003
|
+
if (scope === "all" || scope === "tasks" || scope === "projects" || scope === "plans" || scope === "runs" || scope === "comments") {
|
|
7004
|
+
return scope;
|
|
7005
|
+
}
|
|
7006
|
+
return "tasks";
|
|
7007
|
+
}
|
|
7008
|
+
function normalizeName(name) {
|
|
7009
|
+
const normalized = name.trim();
|
|
7010
|
+
if (!normalized)
|
|
7011
|
+
throw new Error("Saved view name is required");
|
|
7012
|
+
return normalized;
|
|
7013
|
+
}
|
|
7014
|
+
function normalizeLimit(limit) {
|
|
7015
|
+
if (!Number.isFinite(limit) || !limit || limit <= 0)
|
|
7016
|
+
return 100;
|
|
7017
|
+
return Math.min(Math.trunc(limit), 1000);
|
|
7018
|
+
}
|
|
7019
|
+
function valuesList(value) {
|
|
7020
|
+
if (value === undefined)
|
|
7021
|
+
return [];
|
|
7022
|
+
return (Array.isArray(value) ? value : [value]).map((item) => item.trim()).filter(Boolean);
|
|
7023
|
+
}
|
|
7024
|
+
function addStatusFilter(sql, params, column, value) {
|
|
7025
|
+
const values = valuesList(value);
|
|
7026
|
+
if (values.length === 0)
|
|
7027
|
+
return sql;
|
|
7028
|
+
params.push(...values);
|
|
7029
|
+
return `${sql} AND ${column} IN (${values.map(() => "?").join(",")})`;
|
|
7030
|
+
}
|
|
7031
|
+
function addDateFilter(sql, params, column, value) {
|
|
7032
|
+
if (!value)
|
|
7033
|
+
return sql;
|
|
7034
|
+
params.push(value);
|
|
7035
|
+
return `${sql} AND ${column} > ?`;
|
|
7036
|
+
}
|
|
7037
|
+
function likePattern(query) {
|
|
7038
|
+
const trimmed = query?.trim();
|
|
7039
|
+
if (!trimmed || trimmed === "*")
|
|
7040
|
+
return null;
|
|
7041
|
+
return `%${trimmed}%`;
|
|
7042
|
+
}
|
|
7043
|
+
function parseJsonObject(value) {
|
|
7044
|
+
if (!value)
|
|
7045
|
+
return {};
|
|
7046
|
+
if (typeof value === "object" && !Array.isArray(value))
|
|
7047
|
+
return value;
|
|
7048
|
+
if (typeof value !== "string")
|
|
7049
|
+
return {};
|
|
7050
|
+
try {
|
|
7051
|
+
const parsed = JSON.parse(value);
|
|
7052
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
7053
|
+
} catch {
|
|
7054
|
+
return {};
|
|
7055
|
+
}
|
|
7056
|
+
}
|
|
7057
|
+
function rowToTaskRun(row) {
|
|
7058
|
+
return { ...row, metadata: parseJsonObject(row.metadata) };
|
|
7059
|
+
}
|
|
7060
|
+
function taskMatchesSavedFilters(task, filters, db) {
|
|
7061
|
+
if (filters.plan_id && task.plan_id !== filters.plan_id)
|
|
7062
|
+
return false;
|
|
7063
|
+
if (filters.tags && !filters.tags.every((tag) => task.tags.includes(tag)))
|
|
7064
|
+
return false;
|
|
7065
|
+
if (filters.depends_on) {
|
|
7066
|
+
const row = db.query("SELECT 1 FROM task_dependencies WHERE task_id = ? AND depends_on = ?").get(task.id, filters.depends_on);
|
|
7067
|
+
if (!row)
|
|
7068
|
+
return false;
|
|
7069
|
+
}
|
|
7070
|
+
if (filters.blocks) {
|
|
7071
|
+
const row = db.query("SELECT 1 FROM task_dependencies WHERE task_id = ? AND depends_on = ?").get(filters.blocks, task.id);
|
|
7072
|
+
if (!row)
|
|
7073
|
+
return false;
|
|
7074
|
+
}
|
|
7075
|
+
return true;
|
|
7076
|
+
}
|
|
7077
|
+
function searchTaskEntities(filters, db) {
|
|
7078
|
+
let tasks;
|
|
7079
|
+
if (filters.local_fields) {
|
|
7080
|
+
const localMatches = queryTasksByLocalFields({ ...filters.local_fields, limit: 1e4 }, db);
|
|
7081
|
+
const allowedIds = new Set(localMatches.map((task) => task.id));
|
|
7082
|
+
tasks = searchTasks({
|
|
7083
|
+
query: filters.query,
|
|
7084
|
+
project_id: filters.project_id,
|
|
7085
|
+
task_list_id: filters.task_list_id,
|
|
7086
|
+
status: filters.status,
|
|
7087
|
+
priority: filters.priority,
|
|
7088
|
+
assigned_to: filters.assigned_to,
|
|
7089
|
+
agent_id: filters.agent_id,
|
|
7090
|
+
created_after: filters.created_after,
|
|
7091
|
+
updated_after: filters.updated_after,
|
|
7092
|
+
has_dependencies: filters.has_dependencies,
|
|
7093
|
+
is_blocked: filters.is_blocked
|
|
7094
|
+
}, undefined, undefined, db).filter((task) => allowedIds.has(task.id));
|
|
7095
|
+
} else {
|
|
7096
|
+
tasks = searchTasks({
|
|
7097
|
+
query: filters.query,
|
|
7098
|
+
project_id: filters.project_id,
|
|
7099
|
+
task_list_id: filters.task_list_id,
|
|
7100
|
+
status: filters.status,
|
|
7101
|
+
priority: filters.priority,
|
|
7102
|
+
assigned_to: filters.assigned_to,
|
|
7103
|
+
agent_id: filters.agent_id,
|
|
7104
|
+
created_after: filters.created_after,
|
|
7105
|
+
updated_after: filters.updated_after,
|
|
7106
|
+
has_dependencies: filters.has_dependencies,
|
|
7107
|
+
is_blocked: filters.is_blocked
|
|
7108
|
+
}, undefined, undefined, db);
|
|
7109
|
+
}
|
|
7110
|
+
return tasks.filter((task) => taskMatchesSavedFilters(task, filters, db)).slice(0, normalizeLimit(filters.limit));
|
|
7111
|
+
}
|
|
7112
|
+
function searchProjects(filters, db) {
|
|
7113
|
+
const params = [];
|
|
7114
|
+
let sql = "SELECT * FROM projects WHERE 1=1";
|
|
7115
|
+
if (filters.project_id) {
|
|
7116
|
+
sql += " AND id = ?";
|
|
7117
|
+
params.push(filters.project_id);
|
|
7118
|
+
}
|
|
7119
|
+
const pattern = likePattern(filters.query);
|
|
7120
|
+
if (pattern) {
|
|
7121
|
+
sql += " AND (name LIKE ? OR description LIKE ? OR path LIKE ?)";
|
|
7122
|
+
params.push(pattern, pattern, pattern);
|
|
7123
|
+
}
|
|
7124
|
+
sql = addDateFilter(sql, params, "created_at", filters.created_after);
|
|
7125
|
+
sql = addDateFilter(sql, params, "updated_at", filters.updated_after);
|
|
7126
|
+
sql += " ORDER BY name LIMIT ?";
|
|
7127
|
+
params.push(normalizeLimit(filters.limit));
|
|
7128
|
+
return db.query(sql).all(...params);
|
|
7129
|
+
}
|
|
7130
|
+
function searchPlans(filters, db) {
|
|
7131
|
+
const params = [];
|
|
7132
|
+
let sql = "SELECT * FROM plans WHERE 1=1";
|
|
7133
|
+
if (filters.project_id) {
|
|
7134
|
+
sql += " AND project_id = ?";
|
|
7135
|
+
params.push(filters.project_id);
|
|
7136
|
+
}
|
|
7137
|
+
if (filters.task_list_id) {
|
|
7138
|
+
sql += " AND task_list_id = ?";
|
|
7139
|
+
params.push(filters.task_list_id);
|
|
7140
|
+
}
|
|
7141
|
+
if (filters.agent_id) {
|
|
7142
|
+
sql += " AND agent_id = ?";
|
|
7143
|
+
params.push(filters.agent_id);
|
|
7144
|
+
}
|
|
7145
|
+
sql = addStatusFilter(sql, params, "status", filters.status);
|
|
7146
|
+
const pattern = likePattern(filters.query);
|
|
7147
|
+
if (pattern) {
|
|
7148
|
+
sql += " AND (name LIKE ? OR description LIKE ?)";
|
|
7149
|
+
params.push(pattern, pattern);
|
|
7150
|
+
}
|
|
7151
|
+
sql = addDateFilter(sql, params, "created_at", filters.created_after);
|
|
7152
|
+
sql = addDateFilter(sql, params, "updated_at", filters.updated_after);
|
|
7153
|
+
sql += " ORDER BY updated_at DESC, created_at DESC LIMIT ?";
|
|
7154
|
+
params.push(normalizeLimit(filters.limit));
|
|
7155
|
+
return db.query(sql).all(...params);
|
|
7156
|
+
}
|
|
7157
|
+
function searchRuns(filters, db) {
|
|
7158
|
+
const params = [];
|
|
7159
|
+
let sql = `SELECT r.* FROM task_runs r
|
|
7160
|
+
JOIN tasks t ON t.id = r.task_id
|
|
7161
|
+
WHERE 1=1`;
|
|
7162
|
+
if (filters.project_id) {
|
|
7163
|
+
sql += " AND t.project_id = ?";
|
|
7164
|
+
params.push(filters.project_id);
|
|
7165
|
+
}
|
|
7166
|
+
if (filters.task_list_id) {
|
|
7167
|
+
sql += " AND t.task_list_id = ?";
|
|
7168
|
+
params.push(filters.task_list_id);
|
|
7169
|
+
}
|
|
7170
|
+
if (filters.plan_id) {
|
|
7171
|
+
sql += " AND t.plan_id = ?";
|
|
7172
|
+
params.push(filters.plan_id);
|
|
7173
|
+
}
|
|
7174
|
+
if (filters.task_id) {
|
|
7175
|
+
sql += " AND r.task_id = ?";
|
|
7176
|
+
params.push(filters.task_id);
|
|
7177
|
+
}
|
|
7178
|
+
if (filters.agent_id) {
|
|
7179
|
+
sql += " AND r.agent_id = ?";
|
|
7180
|
+
params.push(filters.agent_id);
|
|
7181
|
+
}
|
|
7182
|
+
sql = addStatusFilter(sql, params, "r.status", filters.status);
|
|
7183
|
+
const pattern = likePattern(filters.query);
|
|
7184
|
+
if (pattern) {
|
|
7185
|
+
sql += " AND (r.title LIKE ? OR r.summary LIKE ? OR t.title LIKE ?)";
|
|
7186
|
+
params.push(pattern, pattern, pattern);
|
|
7187
|
+
}
|
|
7188
|
+
sql = addDateFilter(sql, params, "r.created_at", filters.created_after);
|
|
7189
|
+
sql = addDateFilter(sql, params, "r.updated_at", filters.updated_after);
|
|
7190
|
+
sql += " ORDER BY r.started_at DESC, r.created_at DESC LIMIT ?";
|
|
7191
|
+
params.push(normalizeLimit(filters.limit));
|
|
7192
|
+
return db.query(sql).all(...params).map(rowToTaskRun);
|
|
7193
|
+
}
|
|
7194
|
+
function searchComments(filters, db) {
|
|
7195
|
+
const params = [];
|
|
7196
|
+
let sql = `SELECT c.* FROM task_comments c
|
|
7197
|
+
JOIN tasks t ON t.id = c.task_id
|
|
7198
|
+
WHERE 1=1`;
|
|
7199
|
+
if (filters.project_id) {
|
|
7200
|
+
sql += " AND t.project_id = ?";
|
|
7201
|
+
params.push(filters.project_id);
|
|
7202
|
+
}
|
|
7203
|
+
if (filters.task_list_id) {
|
|
7204
|
+
sql += " AND t.task_list_id = ?";
|
|
7205
|
+
params.push(filters.task_list_id);
|
|
7206
|
+
}
|
|
7207
|
+
if (filters.plan_id) {
|
|
7208
|
+
sql += " AND t.plan_id = ?";
|
|
7209
|
+
params.push(filters.plan_id);
|
|
7210
|
+
}
|
|
7211
|
+
if (filters.task_id) {
|
|
7212
|
+
sql += " AND c.task_id = ?";
|
|
7213
|
+
params.push(filters.task_id);
|
|
7214
|
+
}
|
|
7215
|
+
if (filters.agent_id) {
|
|
7216
|
+
sql += " AND c.agent_id = ?";
|
|
7217
|
+
params.push(filters.agent_id);
|
|
7218
|
+
}
|
|
7219
|
+
const pattern = likePattern(filters.query);
|
|
7220
|
+
if (pattern) {
|
|
7221
|
+
sql += " AND (c.content LIKE ? OR t.title LIKE ?)";
|
|
7222
|
+
params.push(pattern, pattern);
|
|
7223
|
+
}
|
|
7224
|
+
sql = addDateFilter(sql, params, "c.created_at", filters.created_after);
|
|
7225
|
+
sql += " ORDER BY c.created_at DESC, c.id LIMIT ?";
|
|
7226
|
+
params.push(normalizeLimit(filters.limit));
|
|
7227
|
+
return db.query(sql).all(...params);
|
|
7228
|
+
}
|
|
7229
|
+
function toResults(entityType, rows) {
|
|
7230
|
+
return rows.map((entity) => ({ entity_type: entityType, entity }));
|
|
7231
|
+
}
|
|
7232
|
+
function runSavedSearch(filters = {}, scope = "tasks", db) {
|
|
7233
|
+
const d = db || getDatabase();
|
|
7234
|
+
const normalizedScope = normalizeScope(scope);
|
|
7235
|
+
const scopes = normalizedScope === "all" ? ["tasks", "projects", "plans", "runs", "comments"] : [normalizedScope];
|
|
7236
|
+
const results = [];
|
|
7237
|
+
for (const item of scopes) {
|
|
7238
|
+
if (item === "tasks")
|
|
7239
|
+
results.push(...toResults("tasks", searchTaskEntities(filters, d)));
|
|
7240
|
+
if (item === "projects")
|
|
7241
|
+
results.push(...toResults("projects", searchProjects(filters, d)));
|
|
7242
|
+
if (item === "plans")
|
|
7243
|
+
results.push(...toResults("plans", searchPlans(filters, d)));
|
|
7244
|
+
if (item === "runs")
|
|
7245
|
+
results.push(...toResults("runs", searchRuns(filters, d)));
|
|
7246
|
+
if (item === "comments")
|
|
7247
|
+
results.push(...toResults("comments", searchComments(filters, d)));
|
|
7248
|
+
}
|
|
7249
|
+
return {
|
|
7250
|
+
scope: normalizedScope,
|
|
7251
|
+
filters,
|
|
7252
|
+
count: results.length,
|
|
7253
|
+
results: results.slice(0, normalizeLimit(filters.limit))
|
|
7254
|
+
};
|
|
7255
|
+
}
|
|
7256
|
+
function saveSearchView(input, db) {
|
|
7257
|
+
const d = db || getDatabase();
|
|
7258
|
+
const name = normalizeName(input.name);
|
|
7259
|
+
const timestamp = now();
|
|
7260
|
+
const existing = getSearchView(name, d);
|
|
7261
|
+
if (existing) {
|
|
7262
|
+
d.run(`UPDATE saved_search_views
|
|
7263
|
+
SET description = ?, scope = ?, filters = ?, updated_at = ?
|
|
7264
|
+
WHERE id = ?`, [
|
|
7265
|
+
input.description ?? existing.description,
|
|
7266
|
+
normalizeScope(input.scope ?? existing.scope),
|
|
7267
|
+
JSON.stringify(input.filters ?? existing.filters),
|
|
7268
|
+
timestamp,
|
|
7269
|
+
existing.id
|
|
7270
|
+
]);
|
|
7271
|
+
return getSearchView(existing.id, d);
|
|
7272
|
+
}
|
|
7273
|
+
const id = uuid();
|
|
7274
|
+
d.run(`INSERT INTO saved_search_views (id, name, description, scope, filters, created_at, updated_at)
|
|
7275
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
7276
|
+
id,
|
|
7277
|
+
name,
|
|
7278
|
+
input.description ?? null,
|
|
7279
|
+
normalizeScope(input.scope),
|
|
7280
|
+
JSON.stringify(input.filters ?? {}),
|
|
7281
|
+
timestamp,
|
|
7282
|
+
timestamp
|
|
7283
|
+
]);
|
|
7284
|
+
return getSearchView(id, d);
|
|
7285
|
+
}
|
|
7286
|
+
function getSearchView(idOrName, db) {
|
|
7287
|
+
const d = db || getDatabase();
|
|
7288
|
+
const row = d.query("SELECT * FROM saved_search_views WHERE id = ? OR name = ?").get(idOrName, idOrName);
|
|
7289
|
+
return row ? rowToSavedSearchView(row) : null;
|
|
7290
|
+
}
|
|
7291
|
+
function listSearchViews(scope, db) {
|
|
7292
|
+
const d = db || getDatabase();
|
|
7293
|
+
const normalizedScope = scope ? normalizeScope(scope) : null;
|
|
7294
|
+
const rows = normalizedScope ? d.query("SELECT * FROM saved_search_views WHERE scope = ? ORDER BY name").all(normalizedScope) : d.query("SELECT * FROM saved_search_views ORDER BY name").all();
|
|
7295
|
+
return rows.map(rowToSavedSearchView);
|
|
7296
|
+
}
|
|
7297
|
+
function deleteSearchView(idOrName, db) {
|
|
7298
|
+
const d = db || getDatabase();
|
|
7299
|
+
const result = d.run("DELETE FROM saved_search_views WHERE id = ? OR name = ?", [idOrName, idOrName]);
|
|
7300
|
+
return result.changes > 0;
|
|
7301
|
+
}
|
|
7302
|
+
function runSearchView(idOrName, db) {
|
|
7303
|
+
const d = db || getDatabase();
|
|
7304
|
+
const view = getSearchView(idOrName, d);
|
|
7305
|
+
if (!view)
|
|
7306
|
+
throw new Error(`Saved search view not found: ${idOrName}`);
|
|
7307
|
+
return { ...runSavedSearch(view.filters, view.scope, d), view };
|
|
7308
|
+
}
|
|
7309
|
+
var init_saved_search_views = __esm(() => {
|
|
7310
|
+
init_database();
|
|
7311
|
+
init_local_fields();
|
|
7312
|
+
init_search();
|
|
7313
|
+
});
|
|
7314
|
+
|
|
6855
7315
|
// src/db/handoffs.ts
|
|
6856
7316
|
var exports_handoffs = {};
|
|
6857
7317
|
__export(exports_handoffs, {
|
|
@@ -13169,6 +13629,10 @@ var MCP_TOOL_GROUPS = {
|
|
|
13169
13629
|
"request_task_review",
|
|
13170
13630
|
"reschedule_task",
|
|
13171
13631
|
"search_tasks",
|
|
13632
|
+
"save_search_view",
|
|
13633
|
+
"list_search_views",
|
|
13634
|
+
"run_search_view",
|
|
13635
|
+
"delete_search_view",
|
|
13172
13636
|
"standup",
|
|
13173
13637
|
"set_task_contract",
|
|
13174
13638
|
"task_context",
|
|
@@ -14854,98 +15318,8 @@ function getLocalActivityTimeline(options = {}, db) {
|
|
|
14854
15318
|
};
|
|
14855
15319
|
}
|
|
14856
15320
|
|
|
14857
|
-
// src/
|
|
14858
|
-
|
|
14859
|
-
init_tasks();
|
|
14860
|
-
init_types();
|
|
14861
|
-
var LOCAL_FIELDS_KEY = "local_fields";
|
|
14862
|
-
function normalizeList(values) {
|
|
14863
|
-
return [...new Set((values || []).map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
14864
|
-
}
|
|
14865
|
-
function metadataFields(task) {
|
|
14866
|
-
const value = task.metadata[LOCAL_FIELDS_KEY];
|
|
14867
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
14868
|
-
}
|
|
14869
|
-
function sameCustomValue(actual, expected) {
|
|
14870
|
-
return JSON.stringify(actual) === JSON.stringify(expected);
|
|
14871
|
-
}
|
|
14872
|
-
function hasOwnField(fields, key) {
|
|
14873
|
-
return Object.prototype.hasOwnProperty.call(fields, key);
|
|
14874
|
-
}
|
|
14875
|
-
function getTaskLocalFields(taskId, db) {
|
|
14876
|
-
const d = db || getDatabase();
|
|
14877
|
-
const task = getTask(taskId, d);
|
|
14878
|
-
if (!task)
|
|
14879
|
-
throw new TaskNotFoundError(taskId);
|
|
14880
|
-
const fields = metadataFields(task);
|
|
14881
|
-
return {
|
|
14882
|
-
labels: normalizeList(fields.labels),
|
|
14883
|
-
priority: task.priority,
|
|
14884
|
-
severity: typeof fields.severity === "string" ? fields.severity : null,
|
|
14885
|
-
owner: hasOwnField(fields, "owner") ? typeof fields.owner === "string" ? fields.owner : null : task.assigned_to,
|
|
14886
|
-
area: typeof fields.area === "string" ? fields.area : null,
|
|
14887
|
-
custom: fields.custom && typeof fields.custom === "object" && !Array.isArray(fields.custom) ? fields.custom : {}
|
|
14888
|
-
};
|
|
14889
|
-
}
|
|
14890
|
-
function setTaskLocalFields(taskId, input, db) {
|
|
14891
|
-
const d = db || getDatabase();
|
|
14892
|
-
const task = getTask(taskId, d);
|
|
14893
|
-
if (!task)
|
|
14894
|
-
throw new TaskNotFoundError(taskId);
|
|
14895
|
-
const currentFields = getTaskLocalFields(taskId, d);
|
|
14896
|
-
const labels = input.labels !== undefined ? normalizeList(input.labels) : currentFields.labels;
|
|
14897
|
-
const custom2 = input.custom !== undefined ? redactValue(input.merge_custom === false ? input.custom : { ...currentFields.custom, ...input.custom }) : currentFields.custom;
|
|
14898
|
-
const nextFields = {
|
|
14899
|
-
labels,
|
|
14900
|
-
priority: input.priority || task.priority,
|
|
14901
|
-
severity: input.severity !== undefined ? input.severity : currentFields.severity,
|
|
14902
|
-
owner: input.owner !== undefined ? input.owner : currentFields.owner,
|
|
14903
|
-
area: input.area !== undefined ? input.area : currentFields.area,
|
|
14904
|
-
custom: custom2
|
|
14905
|
-
};
|
|
14906
|
-
const nextMetadata = {
|
|
14907
|
-
...task.metadata,
|
|
14908
|
-
[LOCAL_FIELDS_KEY]: nextFields
|
|
14909
|
-
};
|
|
14910
|
-
const previousLabels = new Set(currentFields.labels);
|
|
14911
|
-
const nextTags = normalizeList([...task.tags.filter((tag) => !previousLabels.has(tag)), ...labels]);
|
|
14912
|
-
const updates = {
|
|
14913
|
-
version: task.version,
|
|
14914
|
-
priority: input.priority,
|
|
14915
|
-
tags: nextTags,
|
|
14916
|
-
metadata: nextMetadata
|
|
14917
|
-
};
|
|
14918
|
-
if (input.owner !== undefined)
|
|
14919
|
-
updates.assigned_to = nextFields.owner;
|
|
14920
|
-
return updateTask(taskId, updates, d);
|
|
14921
|
-
}
|
|
14922
|
-
function queryTasksByLocalFields(query, db) {
|
|
14923
|
-
const d = db || getDatabase();
|
|
14924
|
-
const tasks = listTasks({
|
|
14925
|
-
priority: query.priority,
|
|
14926
|
-
tags: query.labels,
|
|
14927
|
-
limit: 1e4
|
|
14928
|
-
}, d);
|
|
14929
|
-
const matches = tasks.filter((task) => {
|
|
14930
|
-
const fields = getTaskLocalFields(task.id, d);
|
|
14931
|
-
if (query.labels && !query.labels.every((label) => fields.labels.includes(label)))
|
|
14932
|
-
return false;
|
|
14933
|
-
if (query.severity && fields.severity !== query.severity)
|
|
14934
|
-
return false;
|
|
14935
|
-
if (query.owner && fields.owner !== query.owner)
|
|
14936
|
-
return false;
|
|
14937
|
-
if (query.area && fields.area !== query.area)
|
|
14938
|
-
return false;
|
|
14939
|
-
if (query.custom) {
|
|
14940
|
-
for (const [key, expected] of Object.entries(query.custom)) {
|
|
14941
|
-
if (!sameCustomValue(fields.custom[key], expected))
|
|
14942
|
-
return false;
|
|
14943
|
-
}
|
|
14944
|
-
}
|
|
14945
|
-
return true;
|
|
14946
|
-
});
|
|
14947
|
-
return matches.slice(0, query.limit || 100);
|
|
14948
|
-
}
|
|
15321
|
+
// src/mcp/tools/task-project-tools.ts
|
|
15322
|
+
init_local_fields();
|
|
14949
15323
|
|
|
14950
15324
|
// src/lib/task-dedupe.ts
|
|
14951
15325
|
init_audit();
|
|
@@ -16737,6 +17111,76 @@ ${lines.join(`
|
|
|
16737
17111
|
}
|
|
16738
17112
|
});
|
|
16739
17113
|
}
|
|
17114
|
+
if (shouldRegisterTool("save_search_view")) {
|
|
17115
|
+
server.tool("save_search_view", "Save a local search view for tasks, projects, plans, runs, comments, or all records.", {
|
|
17116
|
+
name: exports_external.string().describe("Saved view name"),
|
|
17117
|
+
query: exports_external.string().optional().describe("Search query"),
|
|
17118
|
+
scope: exports_external.enum(["all", "tasks", "projects", "plans", "runs", "comments"]).optional(),
|
|
17119
|
+
description: exports_external.string().optional(),
|
|
17120
|
+
project_id: exports_external.string().optional(),
|
|
17121
|
+
status: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
|
|
17122
|
+
priority: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
|
|
17123
|
+
assigned_to: exports_external.string().optional(),
|
|
17124
|
+
agent_id: exports_external.string().optional(),
|
|
17125
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
17126
|
+
limit: exports_external.number().optional()
|
|
17127
|
+
}, async ({ name, scope, description, ...filters }) => {
|
|
17128
|
+
try {
|
|
17129
|
+
const { saveSearchView: saveSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
17130
|
+
const view = saveSearchView2({
|
|
17131
|
+
name,
|
|
17132
|
+
description,
|
|
17133
|
+
scope,
|
|
17134
|
+
filters: {
|
|
17135
|
+
...filters,
|
|
17136
|
+
project_id: filters.project_id ? resolveId(filters.project_id, "projects") : undefined
|
|
17137
|
+
}
|
|
17138
|
+
});
|
|
17139
|
+
return { content: [{ type: "text", text: JSON.stringify(view, null, 2) }] };
|
|
17140
|
+
} catch (e) {
|
|
17141
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17142
|
+
}
|
|
17143
|
+
});
|
|
17144
|
+
}
|
|
17145
|
+
if (shouldRegisterTool("list_search_views")) {
|
|
17146
|
+
server.tool("list_search_views", "List local saved search views.", {
|
|
17147
|
+
scope: exports_external.enum(["all", "tasks", "projects", "plans", "runs", "comments"]).optional()
|
|
17148
|
+
}, async ({ scope }) => {
|
|
17149
|
+
try {
|
|
17150
|
+
const { listSearchViews: listSearchViews2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
17151
|
+
const views = listSearchViews2(scope);
|
|
17152
|
+
return { content: [{ type: "text", text: JSON.stringify(views, null, 2) }] };
|
|
17153
|
+
} catch (e) {
|
|
17154
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17155
|
+
}
|
|
17156
|
+
});
|
|
17157
|
+
}
|
|
17158
|
+
if (shouldRegisterTool("run_search_view")) {
|
|
17159
|
+
server.tool("run_search_view", "Run a local saved search view and return stable JSON results.", {
|
|
17160
|
+
name: exports_external.string().describe("Saved view name or id")
|
|
17161
|
+
}, async ({ name }) => {
|
|
17162
|
+
try {
|
|
17163
|
+
const { runSearchView: runSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
17164
|
+
const result = runSearchView2(name);
|
|
17165
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
17166
|
+
} catch (e) {
|
|
17167
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17168
|
+
}
|
|
17169
|
+
});
|
|
17170
|
+
}
|
|
17171
|
+
if (shouldRegisterTool("delete_search_view")) {
|
|
17172
|
+
server.tool("delete_search_view", "Delete a local saved search view.", {
|
|
17173
|
+
name: exports_external.string().describe("Saved view name or id")
|
|
17174
|
+
}, async ({ name }) => {
|
|
17175
|
+
try {
|
|
17176
|
+
const { deleteSearchView: deleteSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
17177
|
+
const deleted = deleteSearchView2(name);
|
|
17178
|
+
return { content: [{ type: "text", text: JSON.stringify({ deleted }, null, 2) }] };
|
|
17179
|
+
} catch (e) {
|
|
17180
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17181
|
+
}
|
|
17182
|
+
});
|
|
17183
|
+
}
|
|
16740
17184
|
}
|
|
16741
17185
|
|
|
16742
17186
|
// src/mcp/tools/task-workflow-tools.ts
|
|
@@ -17828,6 +18272,10 @@ function registerTaskMetaTools(server, ctx) {
|
|
|
17828
18272
|
reschedule_task: "reschedule_task \u2014 Update deadline. Params: task_id, deadline, version",
|
|
17829
18273
|
prioritize_task: "prioritize_task \u2014 Set priority. Params: task_id, priority, version",
|
|
17830
18274
|
search_tasks: "search_tasks \u2014 Full-text search. Params: query, project_id, status, limit",
|
|
18275
|
+
save_search_view: "save_search_view \u2014 Save a local search view across tasks, projects, plans, runs, comments, or all records. Params: name, query, scope, description, project_id, status, priority, assigned_to, agent_id, tags, limit",
|
|
18276
|
+
list_search_views: "list_search_views \u2014 List local saved search views. Params: scope",
|
|
18277
|
+
run_search_view: "run_search_view \u2014 Run a local saved search view and return stable JSON results. Params: name",
|
|
18278
|
+
delete_search_view: "delete_search_view \u2014 Delete a local saved search view. Params: name",
|
|
17831
18279
|
get_my_tasks: "get_my_tasks \u2014 Get tasks for calling agent. Params: agent_id, status, project_id, limit",
|
|
17832
18280
|
get_next_task: "get_next_task \u2014 Get the next available task without claiming it. Params: agent_id, project_id, task_list_id, plan_id, tags",
|
|
17833
18281
|
claim_next_task: "claim_next_task \u2014 Atomically claim and start the next available task. Params: agent_id, project_id, task_list_id, plan_id, tags, steal_stale, stale_minutes",
|
|
@@ -18158,7 +18606,7 @@ var DEFAULT_CAPABILITIES = {
|
|
|
18158
18606
|
browser: ["browser", "screenshot", "artifact", "evidence"],
|
|
18159
18607
|
script: ["script", "command", "retry", "evidence"]
|
|
18160
18608
|
};
|
|
18161
|
-
function
|
|
18609
|
+
function normalizeName2(name) {
|
|
18162
18610
|
const normalized = name.trim().toLowerCase();
|
|
18163
18611
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
18164
18612
|
throw new Error("verification provider name must use lowercase letters, numbers, dashes, or underscores");
|
|
@@ -18177,10 +18625,10 @@ function timeoutMs(value) {
|
|
|
18177
18625
|
return Math.max(1, Math.min(24 * 60 * 60000, Math.floor(value)));
|
|
18178
18626
|
}
|
|
18179
18627
|
function getProvider(name) {
|
|
18180
|
-
return loadConfig().verification_providers?.[
|
|
18628
|
+
return loadConfig().verification_providers?.[normalizeName2(name)] || null;
|
|
18181
18629
|
}
|
|
18182
18630
|
function upsertVerificationProvider(input) {
|
|
18183
|
-
const name =
|
|
18631
|
+
const name = normalizeName2(input.name);
|
|
18184
18632
|
const config = loadConfig();
|
|
18185
18633
|
const existing = config.verification_providers?.[name];
|
|
18186
18634
|
const timestamp = new Date().toISOString();
|
|
@@ -18210,7 +18658,7 @@ function listVerificationProviders() {
|
|
|
18210
18658
|
return Object.values(loadConfig().verification_providers || {}).sort((a, b) => a.name.localeCompare(b.name));
|
|
18211
18659
|
}
|
|
18212
18660
|
function removeVerificationProvider(name) {
|
|
18213
|
-
const normalized =
|
|
18661
|
+
const normalized = normalizeName2(name);
|
|
18214
18662
|
const config = loadConfig();
|
|
18215
18663
|
if (!config.verification_providers?.[normalized])
|
|
18216
18664
|
return false;
|