@hasna/todos 0.11.33 → 0.11.35
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 +12 -0
- package/dist/cli/commands/agent-commands.d.ts.map +1 -1
- package/dist/cli/commands/api-key-commands.d.ts +3 -0
- package/dist/cli/commands/api-key-commands.d.ts.map +1 -0
- package/dist/cli/commands/config-serve-commands.d.ts.map +1 -1
- package/dist/cli/index.js +2343 -607
- package/dist/db/agent-names.d.ts +23 -0
- package/dist/db/agent-names.d.ts.map +1 -0
- package/dist/db/agents.d.ts +2 -0
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/api-keys.d.ts +28 -0
- package/dist/db/api-keys.d.ts.map +1 -0
- package/dist/db/comments.d.ts +3 -0
- package/dist/db/comments.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/db/task-lifecycle.d.ts +1 -0
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/task-relations.d.ts +24 -0
- package/dist/db/task-relations.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +1 -1
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +400 -13
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +2585 -536
- package/dist/mcp/tools/agents.d.ts.map +1 -1
- package/dist/mcp/tools/task-adv-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-auto-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-crud.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/tools/task-workflow-tools.d.ts.map +1 -1
- package/dist/server/index.js +352 -42
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2888,6 +2888,42 @@ var init_migrations = __esm(() => {
|
|
|
2888
2888
|
ALTER TABLE tasks ADD COLUMN current_step TEXT;
|
|
2889
2889
|
ALTER TABLE tasks ADD COLUMN total_steps INTEGER;
|
|
2890
2890
|
INSERT OR IGNORE INTO _migrations (id) VALUES (48);
|
|
2891
|
+
`,
|
|
2892
|
+
`
|
|
2893
|
+
CREATE TABLE IF NOT EXISTS cycles (
|
|
2894
|
+
id TEXT PRIMARY KEY,
|
|
2895
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
2896
|
+
number INTEGER NOT NULL,
|
|
2897
|
+
start_date TEXT NOT NULL,
|
|
2898
|
+
end_date TEXT NOT NULL,
|
|
2899
|
+
duration_weeks INTEGER NOT NULL DEFAULT 1,
|
|
2900
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
|
|
2901
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2902
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2903
|
+
);
|
|
2904
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_project ON cycles(project_id);
|
|
2905
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_number ON cycles(number);
|
|
2906
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_status ON cycles(status);
|
|
2907
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_dates ON cycles(start_date, end_date);
|
|
2908
|
+
ALTER TABLE tasks ADD COLUMN cycle_id TEXT REFERENCES cycles(id) ON DELETE SET NULL;
|
|
2909
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_cycle ON tasks(cycle_id) WHERE cycle_id IS NOT NULL;
|
|
2910
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (49);
|
|
2911
|
+
`,
|
|
2912
|
+
`
|
|
2913
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
2914
|
+
id TEXT PRIMARY KEY,
|
|
2915
|
+
name TEXT NOT NULL,
|
|
2916
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
2917
|
+
prefix TEXT NOT NULL UNIQUE,
|
|
2918
|
+
permissions TEXT NOT NULL DEFAULT '["*"]',
|
|
2919
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2920
|
+
last_used_at TEXT,
|
|
2921
|
+
expires_at TEXT,
|
|
2922
|
+
revoked_at TEXT
|
|
2923
|
+
);
|
|
2924
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(prefix);
|
|
2925
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(revoked_at, expires_at);
|
|
2926
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (50);
|
|
2891
2927
|
`
|
|
2892
2928
|
];
|
|
2893
2929
|
});
|
|
@@ -3286,6 +3322,20 @@ function ensureSchema(db) {
|
|
|
3286
3322
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_cycles_dates ON cycles(start_date, end_date)");
|
|
3287
3323
|
ensureColumn("tasks", "cycle_id", "TEXT REFERENCES cycles(id) ON DELETE SET NULL");
|
|
3288
3324
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_cycle ON tasks(cycle_id) WHERE cycle_id IS NOT NULL");
|
|
3325
|
+
ensureTable("api_keys", `
|
|
3326
|
+
CREATE TABLE api_keys (
|
|
3327
|
+
id TEXT PRIMARY KEY,
|
|
3328
|
+
name TEXT NOT NULL,
|
|
3329
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
3330
|
+
prefix TEXT NOT NULL UNIQUE,
|
|
3331
|
+
permissions TEXT NOT NULL DEFAULT '["*"]',
|
|
3332
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3333
|
+
last_used_at TEXT,
|
|
3334
|
+
expires_at TEXT,
|
|
3335
|
+
revoked_at TEXT
|
|
3336
|
+
)`);
|
|
3337
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(prefix)");
|
|
3338
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(revoked_at, expires_at)");
|
|
3289
3339
|
}
|
|
3290
3340
|
function backfillTaskTags(db) {
|
|
3291
3341
|
try {
|
|
@@ -4675,8 +4725,8 @@ function createTask(input, db) {
|
|
|
4675
4725
|
let id = uuid();
|
|
4676
4726
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
4677
4727
|
try {
|
|
4678
|
-
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
|
|
4679
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
4728
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
|
|
4729
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
4680
4730
|
id,
|
|
4681
4731
|
null,
|
|
4682
4732
|
input.project_id || null,
|
|
@@ -4698,6 +4748,10 @@ function createTask(input, db) {
|
|
|
4698
4748
|
timestamp,
|
|
4699
4749
|
input.due_at || null,
|
|
4700
4750
|
input.estimated_minutes || null,
|
|
4751
|
+
input.confidence ?? null,
|
|
4752
|
+
input.retry_count ?? 0,
|
|
4753
|
+
input.max_retries ?? 3,
|
|
4754
|
+
input.retry_after ?? null,
|
|
4701
4755
|
input.requires_approval ? 1 : 0,
|
|
4702
4756
|
null,
|
|
4703
4757
|
null,
|
|
@@ -4722,11 +4776,11 @@ function createTask(input, db) {
|
|
|
4722
4776
|
if (tags.length > 0) {
|
|
4723
4777
|
insertTaskTags(id, tags, d);
|
|
4724
4778
|
}
|
|
4725
|
-
const task =
|
|
4779
|
+
const task = getTask(id, d);
|
|
4726
4780
|
dispatchWebhook("task.created", { id: task.id, short_id: task.short_id, title: task.title, status: task.status, priority: task.priority, project_id: task.project_id, assigned_to: task.assigned_to }, d).catch(() => {});
|
|
4727
4781
|
return task;
|
|
4728
4782
|
}
|
|
4729
|
-
function
|
|
4783
|
+
function getTask(id, db) {
|
|
4730
4784
|
const d = db || getDatabase();
|
|
4731
4785
|
const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
4732
4786
|
if (!row)
|
|
@@ -4735,7 +4789,7 @@ function getTask2(id, db) {
|
|
|
4735
4789
|
}
|
|
4736
4790
|
function getTaskWithRelations(id, db) {
|
|
4737
4791
|
const d = db || getDatabase();
|
|
4738
|
-
const task =
|
|
4792
|
+
const task = getTask(id, d);
|
|
4739
4793
|
if (!task)
|
|
4740
4794
|
return null;
|
|
4741
4795
|
const subtaskRows = d.query("SELECT * FROM tasks WHERE parent_id = ? ORDER BY created_at").all(id);
|
|
@@ -4749,7 +4803,7 @@ function getTaskWithRelations(id, db) {
|
|
|
4749
4803
|
WHERE td.depends_on = ?`).all(id);
|
|
4750
4804
|
const blocked_by = blockedByRows.map(rowToTask);
|
|
4751
4805
|
const comments = d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(id);
|
|
4752
|
-
const parent = task.parent_id ?
|
|
4806
|
+
const parent = task.parent_id ? getTask(task.parent_id, d) : null;
|
|
4753
4807
|
const checklist = getChecklist(id, d);
|
|
4754
4808
|
return {
|
|
4755
4809
|
...task,
|
|
@@ -4936,14 +4990,16 @@ function countTasks(filter = {}, db) {
|
|
|
4936
4990
|
}
|
|
4937
4991
|
function updateTask(id, input, db) {
|
|
4938
4992
|
const d = db || getDatabase();
|
|
4939
|
-
const task =
|
|
4993
|
+
const task = getTask(id, d);
|
|
4940
4994
|
if (!task)
|
|
4941
4995
|
throw new TaskNotFoundError(id);
|
|
4942
4996
|
if (task.version !== input.version) {
|
|
4943
4997
|
throw new VersionConflictError(id, input.version, task.version);
|
|
4944
4998
|
}
|
|
4999
|
+
const timestamp = now();
|
|
5000
|
+
const completionTimestamp = input.completed_at ?? timestamp;
|
|
4945
5001
|
const sets = ["version = version + 1", "updated_at = ?"];
|
|
4946
|
-
const params = [
|
|
5002
|
+
const params = [timestamp];
|
|
4947
5003
|
if (input.title !== undefined) {
|
|
4948
5004
|
sets.push("title = ?");
|
|
4949
5005
|
params.push(input.title);
|
|
@@ -4960,13 +5016,17 @@ function updateTask(id, input, db) {
|
|
|
4960
5016
|
params.push(input.status);
|
|
4961
5017
|
if (input.status === "completed") {
|
|
4962
5018
|
sets.push("completed_at = ?");
|
|
4963
|
-
params.push(
|
|
5019
|
+
params.push(completionTimestamp);
|
|
4964
5020
|
}
|
|
4965
5021
|
}
|
|
4966
5022
|
if (input.priority !== undefined) {
|
|
4967
5023
|
sets.push("priority = ?");
|
|
4968
5024
|
params.push(input.priority);
|
|
4969
5025
|
}
|
|
5026
|
+
if (input.project_id !== undefined) {
|
|
5027
|
+
sets.push("project_id = ?");
|
|
5028
|
+
params.push(input.project_id);
|
|
5029
|
+
}
|
|
4970
5030
|
if (input.assigned_to !== undefined) {
|
|
4971
5031
|
sets.push("assigned_to = ?");
|
|
4972
5032
|
params.push(input.assigned_to);
|
|
@@ -4995,6 +5055,30 @@ function updateTask(id, input, db) {
|
|
|
4995
5055
|
sets.push("estimated_minutes = ?");
|
|
4996
5056
|
params.push(input.estimated_minutes);
|
|
4997
5057
|
}
|
|
5058
|
+
if (input.actual_minutes !== undefined) {
|
|
5059
|
+
sets.push("actual_minutes = ?");
|
|
5060
|
+
params.push(input.actual_minutes);
|
|
5061
|
+
}
|
|
5062
|
+
if (input.completed_at !== undefined && input.status !== "completed") {
|
|
5063
|
+
sets.push("completed_at = ?");
|
|
5064
|
+
params.push(input.completed_at);
|
|
5065
|
+
}
|
|
5066
|
+
if (input.confidence !== undefined) {
|
|
5067
|
+
sets.push("confidence = ?");
|
|
5068
|
+
params.push(input.confidence);
|
|
5069
|
+
}
|
|
5070
|
+
if (input.retry_count !== undefined) {
|
|
5071
|
+
sets.push("retry_count = ?");
|
|
5072
|
+
params.push(input.retry_count);
|
|
5073
|
+
}
|
|
5074
|
+
if (input.max_retries !== undefined) {
|
|
5075
|
+
sets.push("max_retries = ?");
|
|
5076
|
+
params.push(input.max_retries);
|
|
5077
|
+
}
|
|
5078
|
+
if (input.retry_after !== undefined) {
|
|
5079
|
+
sets.push("retry_after = ?");
|
|
5080
|
+
params.push(input.retry_after);
|
|
5081
|
+
}
|
|
4998
5082
|
if (input.requires_approval !== undefined) {
|
|
4999
5083
|
sets.push("requires_approval = ?");
|
|
5000
5084
|
params.push(input.requires_approval ? 1 : 0);
|
|
@@ -5016,7 +5100,7 @@ function updateTask(id, input, db) {
|
|
|
5016
5100
|
params.push(id, input.version);
|
|
5017
5101
|
const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
|
|
5018
5102
|
if (result.changes === 0) {
|
|
5019
|
-
const current =
|
|
5103
|
+
const current = getTask(id, d);
|
|
5020
5104
|
throw new VersionConflictError(id, input.version, current?.version ?? -1);
|
|
5021
5105
|
}
|
|
5022
5106
|
if (input.tags !== undefined) {
|
|
@@ -5045,11 +5129,16 @@ function updateTask(id, input, db) {
|
|
|
5045
5129
|
tags: input.tags ?? task.tags,
|
|
5046
5130
|
metadata: input.metadata ?? task.metadata,
|
|
5047
5131
|
version: task.version + 1,
|
|
5048
|
-
updated_at:
|
|
5049
|
-
completed_at: input.status === "completed" ?
|
|
5132
|
+
updated_at: timestamp,
|
|
5133
|
+
completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : task.completed_at,
|
|
5134
|
+
actual_minutes: input.actual_minutes ?? task.actual_minutes,
|
|
5135
|
+
confidence: input.confidence !== undefined ? input.confidence : task.confidence,
|
|
5136
|
+
retry_count: input.retry_count ?? task.retry_count,
|
|
5137
|
+
max_retries: input.max_retries ?? task.max_retries,
|
|
5138
|
+
retry_after: input.retry_after !== undefined ? input.retry_after : task.retry_after,
|
|
5050
5139
|
requires_approval: input.requires_approval !== undefined ? input.requires_approval : task.requires_approval,
|
|
5051
5140
|
approved_by: input.approved_by ?? task.approved_by,
|
|
5052
|
-
approved_at: input.approved_by ?
|
|
5141
|
+
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
5053
5142
|
};
|
|
5054
5143
|
}
|
|
5055
5144
|
function deleteTask(id, db) {
|
|
@@ -5622,9 +5711,9 @@ var init_templates = __esm(() => {
|
|
|
5622
5711
|
// src/db/task-graph.ts
|
|
5623
5712
|
function addDependency(taskId, dependsOn, db) {
|
|
5624
5713
|
const d = db || getDatabase();
|
|
5625
|
-
if (!
|
|
5714
|
+
if (!getTask(taskId, d))
|
|
5626
5715
|
throw new TaskNotFoundError(taskId);
|
|
5627
|
-
if (!
|
|
5716
|
+
if (!getTask(dependsOn, d))
|
|
5628
5717
|
throw new TaskNotFoundError(dependsOn);
|
|
5629
5718
|
if (wouldCreateCycle(taskId, dependsOn, d)) {
|
|
5630
5719
|
throw new DependencyCycleError(taskId, dependsOn);
|
|
@@ -5646,7 +5735,7 @@ function getTaskDependents(taskId, db) {
|
|
|
5646
5735
|
}
|
|
5647
5736
|
function cloneTask(taskId, overrides, db) {
|
|
5648
5737
|
const d = db || getDatabase();
|
|
5649
|
-
const source =
|
|
5738
|
+
const source = getTask(taskId, d);
|
|
5650
5739
|
if (!source)
|
|
5651
5740
|
throw new TaskNotFoundError(taskId);
|
|
5652
5741
|
const input = {
|
|
@@ -5669,13 +5758,13 @@ function cloneTask(taskId, overrides, db) {
|
|
|
5669
5758
|
}
|
|
5670
5759
|
function getTaskGraph(taskId, direction = "both", db) {
|
|
5671
5760
|
const d = db || getDatabase();
|
|
5672
|
-
const task =
|
|
5761
|
+
const task = getTask(taskId, d);
|
|
5673
5762
|
if (!task)
|
|
5674
5763
|
throw new TaskNotFoundError(taskId);
|
|
5675
5764
|
function toNode(t) {
|
|
5676
5765
|
const deps = getTaskDependencies(t.id, d);
|
|
5677
5766
|
const hasUnfinishedDeps = deps.some((dep) => {
|
|
5678
|
-
const depTask =
|
|
5767
|
+
const depTask = getTask(dep.depends_on, d);
|
|
5679
5768
|
return depTask && depTask.status !== "completed";
|
|
5680
5769
|
});
|
|
5681
5770
|
return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
|
|
@@ -5686,7 +5775,7 @@ function getTaskGraph(taskId, direction = "both", db) {
|
|
|
5686
5775
|
visited.add(id);
|
|
5687
5776
|
const deps = d.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(id);
|
|
5688
5777
|
return deps.map((dep) => {
|
|
5689
|
-
const depTask =
|
|
5778
|
+
const depTask = getTask(dep.depends_on, d);
|
|
5690
5779
|
if (!depTask)
|
|
5691
5780
|
return null;
|
|
5692
5781
|
return { task: toNode(depTask), depends_on: buildUp(dep.depends_on, visited), blocks: [] };
|
|
@@ -5698,7 +5787,7 @@ function getTaskGraph(taskId, direction = "both", db) {
|
|
|
5698
5787
|
visited.add(id);
|
|
5699
5788
|
const dependents = d.query("SELECT task_id FROM task_dependencies WHERE depends_on = ?").all(id);
|
|
5700
5789
|
return dependents.map((dep) => {
|
|
5701
|
-
const depTask =
|
|
5790
|
+
const depTask = getTask(dep.task_id, d);
|
|
5702
5791
|
if (!depTask)
|
|
5703
5792
|
return null;
|
|
5704
5793
|
return { task: toNode(depTask), depends_on: [], blocks: buildDown(dep.task_id, visited) };
|
|
@@ -5711,7 +5800,7 @@ function getTaskGraph(taskId, direction = "both", db) {
|
|
|
5711
5800
|
}
|
|
5712
5801
|
function moveTask(taskId, target, db) {
|
|
5713
5802
|
const d = db || getDatabase();
|
|
5714
|
-
const task =
|
|
5803
|
+
const task = getTask(taskId, d);
|
|
5715
5804
|
if (!task)
|
|
5716
5805
|
throw new TaskNotFoundError(taskId);
|
|
5717
5806
|
const sets = ["updated_at = ?", "version = version + 1"];
|
|
@@ -5730,7 +5819,7 @@ function moveTask(taskId, target, db) {
|
|
|
5730
5819
|
}
|
|
5731
5820
|
params.push(taskId);
|
|
5732
5821
|
d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
5733
|
-
return
|
|
5822
|
+
return getTask(taskId, d);
|
|
5734
5823
|
}
|
|
5735
5824
|
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
5736
5825
|
const visited = new Set;
|
|
@@ -5763,7 +5852,7 @@ function getBlockingDeps(id, db) {
|
|
|
5763
5852
|
return [];
|
|
5764
5853
|
const blocking = [];
|
|
5765
5854
|
for (const dep of deps) {
|
|
5766
|
-
const task =
|
|
5855
|
+
const task = getTask(dep.depends_on, d);
|
|
5767
5856
|
if (task && task.status !== "completed")
|
|
5768
5857
|
blocking.push(task);
|
|
5769
5858
|
}
|
|
@@ -5771,7 +5860,7 @@ function getBlockingDeps(id, db) {
|
|
|
5771
5860
|
}
|
|
5772
5861
|
function startTask(id, agentId, db) {
|
|
5773
5862
|
const d = db || getDatabase();
|
|
5774
|
-
const task =
|
|
5863
|
+
const task = getTask(id, d);
|
|
5775
5864
|
if (!task)
|
|
5776
5865
|
throw new TaskNotFoundError(id);
|
|
5777
5866
|
const blocking = getBlockingDeps(id, d);
|
|
@@ -5794,7 +5883,7 @@ function startTask(id, agentId, db) {
|
|
|
5794
5883
|
}
|
|
5795
5884
|
function completeTask(id, agentId, db, options) {
|
|
5796
5885
|
const d = db || getDatabase();
|
|
5797
|
-
const task =
|
|
5886
|
+
const task = getTask(id, d);
|
|
5798
5887
|
if (!task)
|
|
5799
5888
|
throw new TaskNotFoundError(id);
|
|
5800
5889
|
if (agentId && task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
|
|
@@ -5810,14 +5899,14 @@ function completeTask(id, agentId, db, options) {
|
|
|
5810
5899
|
completionMeta._completion = { confidence: options.confidence };
|
|
5811
5900
|
}
|
|
5812
5901
|
const hasMeta = Object.keys(completionMeta).length > 0;
|
|
5813
|
-
const timestamp = now();
|
|
5902
|
+
const timestamp = options?.completed_at || now();
|
|
5814
5903
|
const confidence = options?.confidence !== undefined ? options.confidence : null;
|
|
5815
5904
|
const tx = d.transaction(() => {
|
|
5816
5905
|
if (hasMeta) {
|
|
5817
5906
|
const meta2 = { ...task.metadata, ...completionMeta };
|
|
5818
5907
|
const metaResult = d.run("UPDATE tasks SET metadata = ?, version = version + 1, updated_at = ? WHERE id = ? AND version = ?", [JSON.stringify(meta2), timestamp, id, task.version]);
|
|
5819
5908
|
if (metaResult.changes === 0) {
|
|
5820
|
-
const current =
|
|
5909
|
+
const current = getTask(id, d);
|
|
5821
5910
|
throw new VersionConflictError(id, task.version, current?.version ?? -1);
|
|
5822
5911
|
}
|
|
5823
5912
|
}
|
|
@@ -5874,7 +5963,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
5874
5963
|
}
|
|
5875
5964
|
function lockTask(id, agentId, db) {
|
|
5876
5965
|
const d = db || getDatabase();
|
|
5877
|
-
const task =
|
|
5966
|
+
const task = getTask(id, d);
|
|
5878
5967
|
if (!task)
|
|
5879
5968
|
throw new TaskNotFoundError(id);
|
|
5880
5969
|
if (task.locked_by === agentId && !isLockExpired(task.locked_at)) {
|
|
@@ -5885,7 +5974,7 @@ function lockTask(id, agentId, db) {
|
|
|
5885
5974
|
const result = d.run(`UPDATE tasks SET locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
|
|
5886
5975
|
WHERE id = ? AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, timestamp, timestamp, id, agentId, cutoff]);
|
|
5887
5976
|
if (result.changes === 0) {
|
|
5888
|
-
const current =
|
|
5977
|
+
const current = getTask(id, d);
|
|
5889
5978
|
if (!current)
|
|
5890
5979
|
throw new TaskNotFoundError(id);
|
|
5891
5980
|
if (current.locked_by && !isLockExpired(current.locked_at)) {
|
|
@@ -5901,7 +5990,7 @@ function lockTask(id, agentId, db) {
|
|
|
5901
5990
|
}
|
|
5902
5991
|
function unlockTask(id, agentId, db) {
|
|
5903
5992
|
const d = db || getDatabase();
|
|
5904
|
-
const task =
|
|
5993
|
+
const task = getTask(id, d);
|
|
5905
5994
|
if (!task)
|
|
5906
5995
|
throw new TaskNotFoundError(id);
|
|
5907
5996
|
if (agentId && task.locked_by && task.locked_by !== agentId) {
|
|
@@ -6002,7 +6091,7 @@ function getTasksChangedSince(since, filters, db) {
|
|
|
6002
6091
|
}
|
|
6003
6092
|
function failTask(id, agentId, reason, options, db) {
|
|
6004
6093
|
const d = db || getDatabase();
|
|
6005
|
-
const task =
|
|
6094
|
+
const task = getTask(id, d);
|
|
6006
6095
|
if (!task)
|
|
6007
6096
|
throw new TaskNotFoundError(id);
|
|
6008
6097
|
const meta = {
|
|
@@ -6201,7 +6290,7 @@ function getStatus(filters, agentId, options, db) {
|
|
|
6201
6290
|
}
|
|
6202
6291
|
function decomposeTasks(parentId, subtasks, options, db) {
|
|
6203
6292
|
const d = db || getDatabase();
|
|
6204
|
-
const parent =
|
|
6293
|
+
const parent = getTask(parentId, d);
|
|
6205
6294
|
if (!parent)
|
|
6206
6295
|
throw new TaskNotFoundError(parentId);
|
|
6207
6296
|
const created = [];
|
|
@@ -6232,7 +6321,7 @@ function decomposeTasks(parentId, subtasks, options, db) {
|
|
|
6232
6321
|
function setTaskStatus(id, status, _agentId, db) {
|
|
6233
6322
|
const d = db || getDatabase();
|
|
6234
6323
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
6235
|
-
const task =
|
|
6324
|
+
const task = getTask(id, d);
|
|
6236
6325
|
if (!task)
|
|
6237
6326
|
throw new TaskNotFoundError(id);
|
|
6238
6327
|
if (task.status === status)
|
|
@@ -6250,7 +6339,7 @@ function setTaskStatus(id, status, _agentId, db) {
|
|
|
6250
6339
|
function setTaskPriority(id, priority, _agentId, db) {
|
|
6251
6340
|
const d = db || getDatabase();
|
|
6252
6341
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
6253
|
-
const task =
|
|
6342
|
+
const task = getTask(id, d);
|
|
6254
6343
|
if (!task)
|
|
6255
6344
|
throw new TaskNotFoundError(id);
|
|
6256
6345
|
if (task.priority === priority)
|
|
@@ -6358,7 +6447,7 @@ function bulkUpdateTasks(taskIds, updates, db) {
|
|
|
6358
6447
|
const tx = d.transaction(() => {
|
|
6359
6448
|
for (const id of taskIds) {
|
|
6360
6449
|
try {
|
|
6361
|
-
const task =
|
|
6450
|
+
const task = getTask(id, d);
|
|
6362
6451
|
if (!task) {
|
|
6363
6452
|
failed.push({ id, error: "Task not found" });
|
|
6364
6453
|
continue;
|
|
@@ -6373,6 +6462,30 @@ function bulkUpdateTasks(taskIds, updates, db) {
|
|
|
6373
6462
|
tx();
|
|
6374
6463
|
return { updated, failed };
|
|
6375
6464
|
}
|
|
6465
|
+
function bulkDeleteTasks(taskIds, force = false, db) {
|
|
6466
|
+
const d = db || getDatabase();
|
|
6467
|
+
let deleted = 0;
|
|
6468
|
+
let skipped = 0;
|
|
6469
|
+
const failed = [];
|
|
6470
|
+
const tx = d.transaction(() => {
|
|
6471
|
+
for (const id of taskIds) {
|
|
6472
|
+
try {
|
|
6473
|
+
const childCount = d.query("SELECT COUNT(*) as count FROM tasks WHERE parent_id = ?").get(id);
|
|
6474
|
+
if (!force && childCount.count > 0) {
|
|
6475
|
+
skipped++;
|
|
6476
|
+
continue;
|
|
6477
|
+
}
|
|
6478
|
+
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
6479
|
+
if (result.changes > 0)
|
|
6480
|
+
deleted++;
|
|
6481
|
+
} catch (e) {
|
|
6482
|
+
failed.push({ id, error: e instanceof Error ? e.message : String(e) });
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
});
|
|
6486
|
+
tx();
|
|
6487
|
+
return { deleted, skipped, failed };
|
|
6488
|
+
}
|
|
6376
6489
|
function archiveTasks(options, db) {
|
|
6377
6490
|
const d = db || getDatabase();
|
|
6378
6491
|
const conditions = ["archived_at IS NULL"];
|
|
@@ -6397,10 +6510,33 @@ function archiveTasks(options, db) {
|
|
|
6397
6510
|
const result = d.run(`UPDATE tasks SET archived_at = ? WHERE ${conditions.join(" AND ")}`, [ts, ...params]);
|
|
6398
6511
|
return { archived: result.changes };
|
|
6399
6512
|
}
|
|
6513
|
+
function archiveCompletedTasks(days = 7, projectId, db) {
|
|
6514
|
+
return archiveTasks({
|
|
6515
|
+
project_id: projectId,
|
|
6516
|
+
older_than_days: days,
|
|
6517
|
+
status: ["completed"]
|
|
6518
|
+
}, db).archived;
|
|
6519
|
+
}
|
|
6520
|
+
function getArchivedTasks(opts = {}, db) {
|
|
6521
|
+
const d = db || getDatabase();
|
|
6522
|
+
const conditions = ["archived_at IS NOT NULL"];
|
|
6523
|
+
const params = [];
|
|
6524
|
+
if (opts.project_id) {
|
|
6525
|
+
conditions.push("project_id = ?");
|
|
6526
|
+
params.push(opts.project_id);
|
|
6527
|
+
}
|
|
6528
|
+
let limitClause = "";
|
|
6529
|
+
if (opts.limit) {
|
|
6530
|
+
limitClause = " LIMIT ?";
|
|
6531
|
+
params.push(opts.limit);
|
|
6532
|
+
}
|
|
6533
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY archived_at DESC${limitClause}`).all(...params);
|
|
6534
|
+
return rows.map(rowToTask);
|
|
6535
|
+
}
|
|
6400
6536
|
function unarchiveTask(id, db) {
|
|
6401
6537
|
const d = db || getDatabase();
|
|
6402
6538
|
d.run("UPDATE tasks SET archived_at = NULL WHERE id = ?", [id]);
|
|
6403
|
-
return
|
|
6539
|
+
return getTask(id, d);
|
|
6404
6540
|
}
|
|
6405
6541
|
function getOverdueTasks(projectId, db) {
|
|
6406
6542
|
const d = db || getDatabase();
|
|
@@ -6415,6 +6551,80 @@ function getOverdueTasks(projectId, db) {
|
|
|
6415
6551
|
const rows = d.query(query).all(...params);
|
|
6416
6552
|
return rows.map(rowToTask);
|
|
6417
6553
|
}
|
|
6554
|
+
function notifyUpcomingDeadlines(opts = {}, db) {
|
|
6555
|
+
const d = db || getDatabase();
|
|
6556
|
+
const hours = opts.hours ?? 24;
|
|
6557
|
+
const start = new Date().toISOString();
|
|
6558
|
+
const end = new Date(Date.now() + hours * 60 * 60 * 1000).toISOString();
|
|
6559
|
+
const conditions = [
|
|
6560
|
+
"archived_at IS NULL",
|
|
6561
|
+
"due_at IS NOT NULL",
|
|
6562
|
+
"due_at >= ?",
|
|
6563
|
+
"due_at <= ?",
|
|
6564
|
+
"status NOT IN ('completed', 'cancelled', 'failed')"
|
|
6565
|
+
];
|
|
6566
|
+
const params = [start, end];
|
|
6567
|
+
if (opts.project_id) {
|
|
6568
|
+
conditions.push("project_id = ?");
|
|
6569
|
+
params.push(opts.project_id);
|
|
6570
|
+
}
|
|
6571
|
+
if (opts.agent_id) {
|
|
6572
|
+
conditions.push("assigned_to = ?");
|
|
6573
|
+
params.push(opts.agent_id);
|
|
6574
|
+
}
|
|
6575
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC`).all(...params);
|
|
6576
|
+
return rows.map(rowToTask);
|
|
6577
|
+
}
|
|
6578
|
+
function getBlockedTasks(projectId, db) {
|
|
6579
|
+
const d = db || getDatabase();
|
|
6580
|
+
const params = [];
|
|
6581
|
+
let projectClause = "";
|
|
6582
|
+
if (projectId) {
|
|
6583
|
+
projectClause = "AND t.project_id = ?";
|
|
6584
|
+
params.push(projectId);
|
|
6585
|
+
}
|
|
6586
|
+
const rows = d.query(`
|
|
6587
|
+
SELECT t.*, GROUP_CONCAT(dep.id) AS blocked_by_ids
|
|
6588
|
+
FROM tasks t
|
|
6589
|
+
JOIN task_dependencies td ON td.task_id = t.id
|
|
6590
|
+
JOIN tasks dep ON dep.id = td.depends_on
|
|
6591
|
+
WHERE t.archived_at IS NULL
|
|
6592
|
+
AND t.status NOT IN ('completed', 'cancelled', 'failed')
|
|
6593
|
+
AND dep.status NOT IN ('completed', 'cancelled')
|
|
6594
|
+
${projectClause}
|
|
6595
|
+
GROUP BY t.id
|
|
6596
|
+
ORDER BY t.priority DESC, t.created_at DESC
|
|
6597
|
+
`).all(...params);
|
|
6598
|
+
return rows.map((row) => ({
|
|
6599
|
+
...rowToTask(row),
|
|
6600
|
+
blocked_by: row.blocked_by_ids ? row.blocked_by_ids.split(",") : []
|
|
6601
|
+
}));
|
|
6602
|
+
}
|
|
6603
|
+
function getBlockingTasks(projectId, db) {
|
|
6604
|
+
const d = db || getDatabase();
|
|
6605
|
+
const params = [];
|
|
6606
|
+
let projectClause = "";
|
|
6607
|
+
if (projectId) {
|
|
6608
|
+
projectClause = "AND blocker.project_id = ?";
|
|
6609
|
+
params.push(projectId);
|
|
6610
|
+
}
|
|
6611
|
+
const rows = d.query(`
|
|
6612
|
+
SELECT blocker.*, COUNT(DISTINCT blocked.id) AS blocking_count
|
|
6613
|
+
FROM tasks blocker
|
|
6614
|
+
JOIN task_dependencies td ON td.depends_on = blocker.id
|
|
6615
|
+
JOIN tasks blocked ON blocked.id = td.task_id
|
|
6616
|
+
WHERE blocker.archived_at IS NULL
|
|
6617
|
+
AND blocker.status NOT IN ('completed', 'cancelled', 'failed')
|
|
6618
|
+
AND blocked.status NOT IN ('completed', 'cancelled', 'failed')
|
|
6619
|
+
${projectClause}
|
|
6620
|
+
GROUP BY blocker.id
|
|
6621
|
+
ORDER BY blocking_count DESC, blocker.created_at DESC
|
|
6622
|
+
`).all(...params);
|
|
6623
|
+
return rows.map((row) => ({
|
|
6624
|
+
...rowToTask(row),
|
|
6625
|
+
blocking_count: row.blocking_count
|
|
6626
|
+
}));
|
|
6627
|
+
}
|
|
6418
6628
|
function logTime(input, db) {
|
|
6419
6629
|
const d = db || getDatabase();
|
|
6420
6630
|
const id = uuid();
|
|
@@ -6495,6 +6705,7 @@ __export(exports_tasks, {
|
|
|
6495
6705
|
removeDependency: () => removeDependency,
|
|
6496
6706
|
redistributeStaleTasks: () => redistributeStaleTasks,
|
|
6497
6707
|
notifyWatchers: () => notifyWatchers,
|
|
6708
|
+
notifyUpcomingDeadlines: () => notifyUpcomingDeadlines,
|
|
6498
6709
|
moveTask: () => moveTask,
|
|
6499
6710
|
logTime: () => logTime,
|
|
6500
6711
|
logCost: () => logCost,
|
|
@@ -6510,12 +6721,15 @@ __export(exports_tasks, {
|
|
|
6510
6721
|
getTaskGraph: () => getTaskGraph,
|
|
6511
6722
|
getTaskDependents: () => getTaskDependents,
|
|
6512
6723
|
getTaskDependencies: () => getTaskDependencies,
|
|
6513
|
-
getTask: () =>
|
|
6724
|
+
getTask: () => getTask,
|
|
6514
6725
|
getStatus: () => getStatus,
|
|
6515
6726
|
getStaleTasks: () => getStaleTasks,
|
|
6516
6727
|
getOverdueTasks: () => getOverdueTasks,
|
|
6517
6728
|
getNextTask: () => getNextTask,
|
|
6729
|
+
getBlockingTasks: () => getBlockingTasks,
|
|
6518
6730
|
getBlockingDeps: () => getBlockingDeps,
|
|
6731
|
+
getBlockedTasks: () => getBlockedTasks,
|
|
6732
|
+
getArchivedTasks: () => getArchivedTasks,
|
|
6519
6733
|
getActiveWork: () => getActiveWork,
|
|
6520
6734
|
failTask: () => failTask,
|
|
6521
6735
|
deleteTask: () => deleteTask,
|
|
@@ -6527,8 +6741,10 @@ __export(exports_tasks, {
|
|
|
6527
6741
|
claimOrSteal: () => claimOrSteal,
|
|
6528
6742
|
claimNextTask: () => claimNextTask,
|
|
6529
6743
|
bulkUpdateTasks: () => bulkUpdateTasks,
|
|
6744
|
+
bulkDeleteTasks: () => bulkDeleteTasks,
|
|
6530
6745
|
bulkCreateTasks: () => bulkCreateTasks,
|
|
6531
6746
|
archiveTasks: () => archiveTasks,
|
|
6747
|
+
archiveCompletedTasks: () => archiveCompletedTasks,
|
|
6532
6748
|
addDependency: () => addDependency
|
|
6533
6749
|
});
|
|
6534
6750
|
var init_tasks = __esm(() => {
|
|
@@ -6934,9 +7150,18 @@ var init_builtin_templates = __esm(() => {
|
|
|
6934
7150
|
});
|
|
6935
7151
|
|
|
6936
7152
|
// src/db/comments.ts
|
|
7153
|
+
var exports_comments = {};
|
|
7154
|
+
__export(exports_comments, {
|
|
7155
|
+
updateComment: () => updateComment,
|
|
7156
|
+
logProgress: () => logProgress,
|
|
7157
|
+
listComments: () => listComments,
|
|
7158
|
+
getComment: () => getComment,
|
|
7159
|
+
deleteComment: () => deleteComment,
|
|
7160
|
+
addComment: () => addComment
|
|
7161
|
+
});
|
|
6937
7162
|
function addComment(input, db) {
|
|
6938
7163
|
const d = db || getDatabase();
|
|
6939
|
-
if (!
|
|
7164
|
+
if (!getTask(input.task_id, d)) {
|
|
6940
7165
|
throw new TaskNotFoundError(input.task_id);
|
|
6941
7166
|
}
|
|
6942
7167
|
const id = uuid();
|
|
@@ -6965,6 +7190,15 @@ function listComments(taskId, db) {
|
|
|
6965
7190
|
const d = db || getDatabase();
|
|
6966
7191
|
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
|
|
6967
7192
|
}
|
|
7193
|
+
function updateComment(id, input, db) {
|
|
7194
|
+
const d = db || getDatabase();
|
|
7195
|
+
d.run("UPDATE task_comments SET content = ? WHERE id = ?", [input.content, id]);
|
|
7196
|
+
const comment = getComment(id, d);
|
|
7197
|
+
if (!comment) {
|
|
7198
|
+
throw new Error(`Comment not found: ${id}`);
|
|
7199
|
+
}
|
|
7200
|
+
return comment;
|
|
7201
|
+
}
|
|
6968
7202
|
function deleteComment(id, db) {
|
|
6969
7203
|
const d = db || getDatabase();
|
|
6970
7204
|
const result = d.run("DELETE FROM task_comments WHERE id = ?", [id]);
|
|
@@ -6977,6 +7211,10 @@ var init_comments = __esm(() => {
|
|
|
6977
7211
|
});
|
|
6978
7212
|
|
|
6979
7213
|
// src/lib/search.ts
|
|
7214
|
+
var exports_search = {};
|
|
7215
|
+
__export(exports_search, {
|
|
7216
|
+
searchTasks: () => searchTasks
|
|
7217
|
+
});
|
|
6980
7218
|
function rowToTask2(row) {
|
|
6981
7219
|
return {
|
|
6982
7220
|
...row,
|
|
@@ -7293,14 +7531,224 @@ var init_extract = __esm(() => {
|
|
|
7293
7531
|
]);
|
|
7294
7532
|
});
|
|
7295
7533
|
|
|
7534
|
+
// src/db/agent-names.ts
|
|
7535
|
+
function normalizeAgentNameInput(name) {
|
|
7536
|
+
return name.trim().toLowerCase();
|
|
7537
|
+
}
|
|
7538
|
+
function hasGeneratedNumericSuffix(name) {
|
|
7539
|
+
return NUMERIC_SUFFIX_RE.test(normalizeAgentNameInput(name));
|
|
7540
|
+
}
|
|
7541
|
+
function isGenericAgentName(name) {
|
|
7542
|
+
const normalized = normalizeAgentNameInput(name);
|
|
7543
|
+
if (RESERVED_GENERIC_NAMES.has(normalized))
|
|
7544
|
+
return true;
|
|
7545
|
+
for (const generic of RESERVED_GENERIC_NAMES) {
|
|
7546
|
+
if (normalized === `${generic}s`)
|
|
7547
|
+
return true;
|
|
7548
|
+
if (normalized.match(new RegExp(`^${generic}\\d+$`)))
|
|
7549
|
+
return true;
|
|
7550
|
+
if (normalized.match(new RegExp(`^${generic}[-_]\\d+$`)))
|
|
7551
|
+
return true;
|
|
7552
|
+
}
|
|
7553
|
+
return false;
|
|
7554
|
+
}
|
|
7555
|
+
function isBlockedAgentName(name) {
|
|
7556
|
+
const normalized = normalizeAgentNameInput(name);
|
|
7557
|
+
return isGenericAgentName(normalized) || hasGeneratedNumericSuffix(normalized) || !ONE_WORD_NAME_RE.test(normalized);
|
|
7558
|
+
}
|
|
7559
|
+
function suggestAgentNames(existingNames = []) {
|
|
7560
|
+
const existing = new Set([...existingNames].map(normalizeAgentNameInput));
|
|
7561
|
+
return PREFERRED_AGENT_NAMES.filter((name) => !existing.has(name));
|
|
7562
|
+
}
|
|
7563
|
+
function validateAgentName(name, existingNames = []) {
|
|
7564
|
+
const normalized = normalizeAgentNameInput(name);
|
|
7565
|
+
const suggestions = suggestAgentNames(existingNames).slice(0, 5);
|
|
7566
|
+
if (!normalized) {
|
|
7567
|
+
throw new InvalidAgentNameError(name, "choose a real one-word name instead of an empty value", suggestions);
|
|
7568
|
+
}
|
|
7569
|
+
if (/\s/.test(normalized)) {
|
|
7570
|
+
throw new InvalidAgentNameError(name, "use a single word, preferably a Roman or Greek name", suggestions);
|
|
7571
|
+
}
|
|
7572
|
+
if (normalized.length < 3) {
|
|
7573
|
+
throw new InvalidAgentNameError(name, "use a more distinctive name with at least three characters", suggestions);
|
|
7574
|
+
}
|
|
7575
|
+
if (isGenericAgentName(normalized)) {
|
|
7576
|
+
throw new InvalidAgentNameError(name, "generic names like agent, agent-1, assistant, or worker-2 are reserved", suggestions);
|
|
7577
|
+
}
|
|
7578
|
+
if (hasGeneratedNumericSuffix(normalized)) {
|
|
7579
|
+
throw new InvalidAgentNameError(name, "numbered suffix names are not allowed; pick a distinct human-readable name", suggestions);
|
|
7580
|
+
}
|
|
7581
|
+
if (!ONE_WORD_NAME_RE.test(normalized)) {
|
|
7582
|
+
throw new InvalidAgentNameError(name, "use one word made of letters only, preferably a Roman or Greek name", suggestions);
|
|
7583
|
+
}
|
|
7584
|
+
return normalized;
|
|
7585
|
+
}
|
|
7586
|
+
function tableHasColumn(db, table, column) {
|
|
7587
|
+
try {
|
|
7588
|
+
return db.query(`PRAGMA table_info(${table})`).all().some((row) => row.name === column);
|
|
7589
|
+
} catch {
|
|
7590
|
+
return false;
|
|
7591
|
+
}
|
|
7592
|
+
}
|
|
7593
|
+
function updateReferences(db, oldName, newName) {
|
|
7594
|
+
const refs = [
|
|
7595
|
+
["tasks", "assigned_to"],
|
|
7596
|
+
["tasks", "agent_id"],
|
|
7597
|
+
["tasks", "locked_by"],
|
|
7598
|
+
["tasks", "assigned_by"],
|
|
7599
|
+
["plans", "agent_id"],
|
|
7600
|
+
["sessions", "agent_id"],
|
|
7601
|
+
["task_comments", "agent_id"],
|
|
7602
|
+
["task_history", "agent_id"],
|
|
7603
|
+
["webhooks", "agent_id"],
|
|
7604
|
+
["task_files", "agent_id"],
|
|
7605
|
+
["task_time_logs", "agent_id"],
|
|
7606
|
+
["task_watchers", "agent_id"],
|
|
7607
|
+
["task_checkpoints", "agent_id"],
|
|
7608
|
+
["task_heartbeats", "agent_id"],
|
|
7609
|
+
["project_agent_roles", "agent_id"]
|
|
7610
|
+
];
|
|
7611
|
+
let changed = 0;
|
|
7612
|
+
for (const [table, column] of refs) {
|
|
7613
|
+
if (!tableHasColumn(db, table, column))
|
|
7614
|
+
continue;
|
|
7615
|
+
try {
|
|
7616
|
+
changed += db.run(`UPDATE ${table} SET ${column} = ? WHERE LOWER(${column}) = ?`, [newName, oldName]).changes;
|
|
7617
|
+
} catch {}
|
|
7618
|
+
}
|
|
7619
|
+
return changed;
|
|
7620
|
+
}
|
|
7621
|
+
function normalizeGeneratedAgentNames(db) {
|
|
7622
|
+
const rows = db.query("SELECT * FROM agents ORDER BY created_at, id").all();
|
|
7623
|
+
const existing = new Set(rows.map((agent) => normalizeAgentNameInput(agent.name)));
|
|
7624
|
+
const renamed = [];
|
|
7625
|
+
for (const agent of rows) {
|
|
7626
|
+
const oldName = normalizeAgentNameInput(agent.name);
|
|
7627
|
+
if (!isBlockedAgentName(oldName))
|
|
7628
|
+
continue;
|
|
7629
|
+
const candidates = suggestAgentNames(existing);
|
|
7630
|
+
const replacement = candidates[0];
|
|
7631
|
+
if (!replacement) {
|
|
7632
|
+
throw new Error("No safe agent names are available for normalization");
|
|
7633
|
+
}
|
|
7634
|
+
existing.delete(oldName);
|
|
7635
|
+
existing.add(replacement);
|
|
7636
|
+
db.run("UPDATE agents SET name = ?, last_seen_at = ? WHERE id = ?", [replacement, now(), agent.id]);
|
|
7637
|
+
const referenceUpdates = updateReferences(db, oldName, replacement);
|
|
7638
|
+
renamed.push({
|
|
7639
|
+
id: agent.id,
|
|
7640
|
+
old_name: oldName,
|
|
7641
|
+
new_name: replacement,
|
|
7642
|
+
reference_updates: referenceUpdates
|
|
7643
|
+
});
|
|
7644
|
+
}
|
|
7645
|
+
return renamed;
|
|
7646
|
+
}
|
|
7647
|
+
var InvalidAgentNameError, ROMAN_AGENT_NAMES, GREEK_AGENT_NAMES, NICE_AGENT_NAMES, PREFERRED_AGENT_NAMES, RESERVED_GENERIC_NAMES, NUMERIC_SUFFIX_RE, ONE_WORD_NAME_RE;
|
|
7648
|
+
var init_agent_names = __esm(() => {
|
|
7649
|
+
init_database();
|
|
7650
|
+
InvalidAgentNameError = class InvalidAgentNameError extends Error {
|
|
7651
|
+
suggestions;
|
|
7652
|
+
constructor(name, reason, suggestions = []) {
|
|
7653
|
+
super(`Invalid agent name "${name}": ${reason}${suggestions.length > 0 ? `. Try: ${suggestions.join(", ")}` : ""}`);
|
|
7654
|
+
this.name = "InvalidAgentNameError";
|
|
7655
|
+
this.suggestions = suggestions;
|
|
7656
|
+
}
|
|
7657
|
+
};
|
|
7658
|
+
ROMAN_AGENT_NAMES = [
|
|
7659
|
+
"caesar",
|
|
7660
|
+
"augustus",
|
|
7661
|
+
"marcus",
|
|
7662
|
+
"brutus",
|
|
7663
|
+
"cicero",
|
|
7664
|
+
"cato",
|
|
7665
|
+
"nero",
|
|
7666
|
+
"claudius",
|
|
7667
|
+
"tiberius",
|
|
7668
|
+
"hadrian",
|
|
7669
|
+
"trajan",
|
|
7670
|
+
"vespasian",
|
|
7671
|
+
"domitian",
|
|
7672
|
+
"caligula",
|
|
7673
|
+
"commodus",
|
|
7674
|
+
"livia",
|
|
7675
|
+
"julia",
|
|
7676
|
+
"octavia",
|
|
7677
|
+
"claudia",
|
|
7678
|
+
"agrippina",
|
|
7679
|
+
"cornelia",
|
|
7680
|
+
"valeria",
|
|
7681
|
+
"fulvia",
|
|
7682
|
+
"hortensia",
|
|
7683
|
+
"fabia"
|
|
7684
|
+
];
|
|
7685
|
+
GREEK_AGENT_NAMES = [
|
|
7686
|
+
"athena",
|
|
7687
|
+
"apollo",
|
|
7688
|
+
"artemis",
|
|
7689
|
+
"hera",
|
|
7690
|
+
"iris",
|
|
7691
|
+
"hector",
|
|
7692
|
+
"achilles",
|
|
7693
|
+
"odysseus",
|
|
7694
|
+
"theseus",
|
|
7695
|
+
"pericles",
|
|
7696
|
+
"solon",
|
|
7697
|
+
"sophia",
|
|
7698
|
+
"thalia",
|
|
7699
|
+
"calliope",
|
|
7700
|
+
"clio",
|
|
7701
|
+
"phoebe",
|
|
7702
|
+
"daphne",
|
|
7703
|
+
"leonidas",
|
|
7704
|
+
"andromeda",
|
|
7705
|
+
"cassander"
|
|
7706
|
+
];
|
|
7707
|
+
NICE_AGENT_NAMES = [
|
|
7708
|
+
"atlas",
|
|
7709
|
+
"aurora",
|
|
7710
|
+
"ember",
|
|
7711
|
+
"nova",
|
|
7712
|
+
"orion",
|
|
7713
|
+
"rhea",
|
|
7714
|
+
"selene",
|
|
7715
|
+
"sirius",
|
|
7716
|
+
"vesper",
|
|
7717
|
+
"zephyr"
|
|
7718
|
+
];
|
|
7719
|
+
PREFERRED_AGENT_NAMES = [
|
|
7720
|
+
...ROMAN_AGENT_NAMES,
|
|
7721
|
+
...GREEK_AGENT_NAMES,
|
|
7722
|
+
...NICE_AGENT_NAMES
|
|
7723
|
+
];
|
|
7724
|
+
RESERVED_GENERIC_NAMES = new Set([
|
|
7725
|
+
"agent",
|
|
7726
|
+
"agents",
|
|
7727
|
+
"ai",
|
|
7728
|
+
"assistant",
|
|
7729
|
+
"bot",
|
|
7730
|
+
"coder",
|
|
7731
|
+
"default",
|
|
7732
|
+
"helper",
|
|
7733
|
+
"model",
|
|
7734
|
+
"system",
|
|
7735
|
+
"user",
|
|
7736
|
+
"worker"
|
|
7737
|
+
]);
|
|
7738
|
+
NUMERIC_SUFFIX_RE = /[-_]\d+$/;
|
|
7739
|
+
ONE_WORD_NAME_RE = /^[a-z]+$/;
|
|
7740
|
+
});
|
|
7741
|
+
|
|
7296
7742
|
// src/db/agents.ts
|
|
7297
7743
|
var exports_agents = {};
|
|
7298
7744
|
__export(exports_agents, {
|
|
7299
7745
|
updateAgentActivity: () => updateAgentActivity,
|
|
7300
7746
|
updateAgent: () => updateAgent,
|
|
7301
7747
|
unarchiveAgent: () => unarchiveAgent,
|
|
7748
|
+
suggestAgentNames: () => suggestAgentNames,
|
|
7302
7749
|
releaseAgent: () => releaseAgent,
|
|
7303
7750
|
registerAgent: () => registerAgent,
|
|
7751
|
+
normalizeGeneratedAgentNames: () => normalizeGeneratedAgentNames,
|
|
7304
7752
|
matchCapabilities: () => matchCapabilities,
|
|
7305
7753
|
listAgents: () => listAgents,
|
|
7306
7754
|
isAgentConflict: () => isAgentConflict,
|
|
@@ -7312,7 +7760,8 @@ __export(exports_agents, {
|
|
|
7312
7760
|
getAgent: () => getAgent,
|
|
7313
7761
|
deleteAgent: () => deleteAgent,
|
|
7314
7762
|
autoReleaseStaleAgents: () => autoReleaseStaleAgents,
|
|
7315
|
-
archiveAgent: () => archiveAgent
|
|
7763
|
+
archiveAgent: () => archiveAgent,
|
|
7764
|
+
InvalidAgentNameError: () => InvalidAgentNameError
|
|
7316
7765
|
});
|
|
7317
7766
|
function getActiveWindowMs() {
|
|
7318
7767
|
const env = process.env["TODOS_AGENT_TIMEOUT_MS"];
|
|
@@ -7335,7 +7784,7 @@ function getAvailableNamesFromPool(pool, db) {
|
|
|
7335
7784
|
autoReleaseStaleAgents(db);
|
|
7336
7785
|
const cutoff = new Date(Date.now() - getActiveWindowMs()).toISOString();
|
|
7337
7786
|
const activeNames = new Set(db.query("SELECT name FROM agents WHERE status = 'active' AND last_seen_at > ?").all(cutoff).map((r) => r.name.toLowerCase()));
|
|
7338
|
-
return pool.filter((name) => !activeNames.has(name
|
|
7787
|
+
return pool.map(normalizeAgentNameInput).filter((name) => !activeNames.has(name));
|
|
7339
7788
|
}
|
|
7340
7789
|
function shortUuid() {
|
|
7341
7790
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -7351,7 +7800,8 @@ function rowToAgent(row) {
|
|
|
7351
7800
|
}
|
|
7352
7801
|
function registerAgent(input, db) {
|
|
7353
7802
|
const d = db || getDatabase();
|
|
7354
|
-
const
|
|
7803
|
+
const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
|
|
7804
|
+
const normalizedName = validateAgentName(input.name, existingNames);
|
|
7355
7805
|
const existing = getAgentByName(normalizedName, d);
|
|
7356
7806
|
if (existing) {
|
|
7357
7807
|
const lastSeenMs = new Date(existing.last_seen_at).getTime();
|
|
@@ -7484,7 +7934,8 @@ function updateAgent(id, input, db) {
|
|
|
7484
7934
|
const sets = ["last_seen_at = ?"];
|
|
7485
7935
|
const params = [now()];
|
|
7486
7936
|
if (input.name !== undefined) {
|
|
7487
|
-
const
|
|
7937
|
+
const existingNames = d.query("SELECT name FROM agents WHERE id != ?").all(id).map((row) => row.name);
|
|
7938
|
+
const newName = validateAgentName(input.name, existingNames);
|
|
7488
7939
|
const holder = getAgentByName(newName, d);
|
|
7489
7940
|
if (holder && holder.id !== id) {
|
|
7490
7941
|
const lastSeenMs = new Date(holder.last_seen_at).getTime();
|
|
@@ -7595,6 +8046,7 @@ function getCapableAgents(capabilities, opts, db) {
|
|
|
7595
8046
|
}
|
|
7596
8047
|
var init_agents = __esm(() => {
|
|
7597
8048
|
init_database();
|
|
8049
|
+
init_agent_names();
|
|
7598
8050
|
});
|
|
7599
8051
|
|
|
7600
8052
|
// src/db/task-lists.ts
|
|
@@ -7664,6 +8116,90 @@ var init_task_lists = __esm(() => {
|
|
|
7664
8116
|
init_projects();
|
|
7665
8117
|
});
|
|
7666
8118
|
|
|
8119
|
+
// src/db/api-keys.ts
|
|
8120
|
+
import { createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
8121
|
+
function rowToRecord(row) {
|
|
8122
|
+
return {
|
|
8123
|
+
id: row.id,
|
|
8124
|
+
name: row.name,
|
|
8125
|
+
prefix: row.prefix,
|
|
8126
|
+
permissions: JSON.parse(row.permissions || '["*"]'),
|
|
8127
|
+
created_at: row.created_at,
|
|
8128
|
+
last_used_at: row.last_used_at,
|
|
8129
|
+
expires_at: row.expires_at,
|
|
8130
|
+
revoked_at: row.revoked_at
|
|
8131
|
+
};
|
|
8132
|
+
}
|
|
8133
|
+
function hashApiKey(key) {
|
|
8134
|
+
return createHash("sha256").update(key).digest("hex");
|
|
8135
|
+
}
|
|
8136
|
+
function safeEqualHex(a, b) {
|
|
8137
|
+
if (a.length !== b.length)
|
|
8138
|
+
return false;
|
|
8139
|
+
return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
8140
|
+
}
|
|
8141
|
+
function generatePlaintextKey() {
|
|
8142
|
+
return `tdos_${randomBytes(32).toString("base64url")}`;
|
|
8143
|
+
}
|
|
8144
|
+
function createApiKey(input, db) {
|
|
8145
|
+
const d = db || getDatabase();
|
|
8146
|
+
const name = input.name.trim();
|
|
8147
|
+
if (!name)
|
|
8148
|
+
throw new Error("API key name is required");
|
|
8149
|
+
const key = generatePlaintextKey();
|
|
8150
|
+
const timestamp = now();
|
|
8151
|
+
const id = uuid();
|
|
8152
|
+
const prefix = key.slice(0, 12);
|
|
8153
|
+
d.run(`INSERT INTO api_keys (id, name, key_hash, prefix, permissions, created_at, expires_at)
|
|
8154
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
8155
|
+
id,
|
|
8156
|
+
name,
|
|
8157
|
+
hashApiKey(key),
|
|
8158
|
+
prefix,
|
|
8159
|
+
JSON.stringify(input.permissions?.length ? input.permissions : ["*"]),
|
|
8160
|
+
timestamp,
|
|
8161
|
+
input.expires_at || null
|
|
8162
|
+
]);
|
|
8163
|
+
const row = d.query("SELECT * FROM api_keys WHERE id = ?").get(id);
|
|
8164
|
+
return { key, record: rowToRecord(row) };
|
|
8165
|
+
}
|
|
8166
|
+
function listApiKeys(opts, db) {
|
|
8167
|
+
const d = db || getDatabase();
|
|
8168
|
+
const includeRevoked = opts?.include_revoked ?? false;
|
|
8169
|
+
const sql = includeRevoked ? "SELECT * FROM api_keys ORDER BY created_at DESC" : "SELECT * FROM api_keys WHERE revoked_at IS NULL ORDER BY created_at DESC";
|
|
8170
|
+
return d.query(sql).all().map(rowToRecord);
|
|
8171
|
+
}
|
|
8172
|
+
function hasActiveApiKeys(db) {
|
|
8173
|
+
const d = db || getDatabase();
|
|
8174
|
+
const row = d.query("SELECT COUNT(*) AS count FROM api_keys WHERE revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)").get(now());
|
|
8175
|
+
return (row?.count ?? 0) > 0;
|
|
8176
|
+
}
|
|
8177
|
+
function verifyApiKey(key, db) {
|
|
8178
|
+
const d = db || getDatabase();
|
|
8179
|
+
const candidateHash = hashApiKey(key);
|
|
8180
|
+
const rows = d.query("SELECT * FROM api_keys WHERE revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)").all(now());
|
|
8181
|
+
for (const row of rows) {
|
|
8182
|
+
if (!safeEqualHex(candidateHash, row.key_hash))
|
|
8183
|
+
continue;
|
|
8184
|
+
d.run("UPDATE api_keys SET last_used_at = ? WHERE id = ?", [now(), row.id]);
|
|
8185
|
+
return rowToRecord({ ...row, last_used_at: now() });
|
|
8186
|
+
}
|
|
8187
|
+
return null;
|
|
8188
|
+
}
|
|
8189
|
+
function revokeApiKey(idOrPrefix, db) {
|
|
8190
|
+
const d = db || getDatabase();
|
|
8191
|
+
const identifier = idOrPrefix.trim();
|
|
8192
|
+
const row = d.query("SELECT * FROM api_keys WHERE id = ? OR prefix = ?").get(identifier, identifier);
|
|
8193
|
+
if (!row)
|
|
8194
|
+
return null;
|
|
8195
|
+
d.run("UPDATE api_keys SET revoked_at = ? WHERE id = ?", [now(), row.id]);
|
|
8196
|
+
const updated = d.query("SELECT * FROM api_keys WHERE id = ?").get(row.id);
|
|
8197
|
+
return rowToRecord(updated);
|
|
8198
|
+
}
|
|
8199
|
+
var init_api_keys = __esm(() => {
|
|
8200
|
+
init_database();
|
|
8201
|
+
});
|
|
8202
|
+
|
|
7667
8203
|
// src/db/orgs.ts
|
|
7668
8204
|
function rowToOrg(row) {
|
|
7669
8205
|
return { ...row, metadata: JSON.parse(row.metadata || "{}") };
|
|
@@ -7994,7 +8530,7 @@ function handleTasksContext(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
7994
8530
|
return new Response(text, { headers: { "Content-Type": "text/plain" } });
|
|
7995
8531
|
}
|
|
7996
8532
|
function handleTaskAttachments(id, _ctx, json2) {
|
|
7997
|
-
const task =
|
|
8533
|
+
const task = getTask(id);
|
|
7998
8534
|
if (!task)
|
|
7999
8535
|
return json2({ error: "Task not found" }, 404);
|
|
8000
8536
|
const evidence = task.metadata?._evidence || {};
|
|
@@ -8002,7 +8538,7 @@ function handleTaskAttachments(id, _ctx, json2) {
|
|
|
8002
8538
|
return json2({ task_id: id, short_id: task.short_id, attachment_ids: attachmentIds, count: attachmentIds.length, files_changed: evidence.files_changed, commit_hash: evidence.commit_hash, notes: evidence.notes });
|
|
8003
8539
|
}
|
|
8004
8540
|
async function handleTaskProgress(id, req, method, _ctx, json2) {
|
|
8005
|
-
const task =
|
|
8541
|
+
const task = getTask(id);
|
|
8006
8542
|
if (!task)
|
|
8007
8543
|
return json2({ error: "Task not found" }, 404);
|
|
8008
8544
|
if (method === "GET") {
|
|
@@ -8025,7 +8561,7 @@ async function handleTaskProgress(id, req, method, _ctx, json2) {
|
|
|
8025
8561
|
return null;
|
|
8026
8562
|
}
|
|
8027
8563
|
function handleGetTask(id, _ctx, json2, taskToSummary2) {
|
|
8028
|
-
const task =
|
|
8564
|
+
const task = getTask(id);
|
|
8029
8565
|
if (!task)
|
|
8030
8566
|
return json2({ error: "Task not found" }, 404);
|
|
8031
8567
|
return json2(taskToSummary2(task));
|
|
@@ -8033,10 +8569,10 @@ function handleGetTask(id, _ctx, json2, taskToSummary2) {
|
|
|
8033
8569
|
async function handlePatchTask(id, req, _ctx, json2, taskToSummary2) {
|
|
8034
8570
|
try {
|
|
8035
8571
|
const body = await req.json();
|
|
8036
|
-
const task =
|
|
8572
|
+
const task = getTask(id);
|
|
8037
8573
|
if (!task)
|
|
8038
8574
|
return json2({ error: "Task not found" }, 404);
|
|
8039
|
-
const ALLOWED = new Set(["title", "description", "status", "priority", "assigned_to", "plan_id", "task_list_id", "tags", "metadata", "due_at", "estimated_minutes", "task_type"]);
|
|
8575
|
+
const ALLOWED = new Set(["title", "description", "status", "priority", "assigned_to", "plan_id", "task_list_id", "tags", "metadata", "due_at", "estimated_minutes", "actual_minutes", "confidence", "retry_count", "max_retries", "retry_after", "task_type"]);
|
|
8040
8576
|
const safeBody = {};
|
|
8041
8577
|
for (const [key, value] of Object.entries(body)) {
|
|
8042
8578
|
if (ALLOWED.has(key))
|
|
@@ -8109,31 +8645,37 @@ function handleDeleteProject(id, _ctx, json2) {
|
|
|
8109
8645
|
return json2({ success: true });
|
|
8110
8646
|
}
|
|
8111
8647
|
async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8648
|
+
try {
|
|
8649
|
+
const name = url.searchParams.get("name");
|
|
8650
|
+
if (!name)
|
|
8651
|
+
return json2({ error: "Missing name param" }, 400);
|
|
8652
|
+
const agentResult = registerAgent({ name });
|
|
8653
|
+
if (isAgentConflict(agentResult))
|
|
8654
|
+
return json2({ error: agentResult.message, conflict: true }, 409);
|
|
8655
|
+
const agent = agentResult;
|
|
8656
|
+
const tasks = listTasks({ assigned_to: agent.name });
|
|
8657
|
+
const agentIdTasks = listTasks({ agent_id: agent.id });
|
|
8658
|
+
const allTasks = [...tasks, ...agentIdTasks.filter((t) => !tasks.some((tt) => tt.id === t.id))];
|
|
8659
|
+
const pending = allTasks.filter((t) => t.status === "pending");
|
|
8660
|
+
const inProgress = allTasks.filter((t) => t.status === "in_progress");
|
|
8661
|
+
const completed = allTasks.filter((t) => t.status === "completed");
|
|
8662
|
+
return json2({
|
|
8663
|
+
agent,
|
|
8664
|
+
pending_tasks: pending.map((t) => taskToSummary2(t)),
|
|
8665
|
+
in_progress_tasks: inProgress.map((t) => taskToSummary2(t)),
|
|
8666
|
+
stats: {
|
|
8667
|
+
total: allTasks.length,
|
|
8668
|
+
pending: pending.length,
|
|
8669
|
+
in_progress: inProgress.length,
|
|
8670
|
+
completed: completed.length,
|
|
8671
|
+
completion_rate: allTasks.length > 0 ? Math.round(completed.length / allTasks.length * 100) : 0
|
|
8672
|
+
}
|
|
8673
|
+
});
|
|
8674
|
+
} catch (e) {
|
|
8675
|
+
if (e instanceof InvalidAgentNameError)
|
|
8676
|
+
return json2({ error: e.message, suggestions: e.suggestions }, 400);
|
|
8677
|
+
return json2({ error: e instanceof Error ? e.message : "Failed to get agent profile" }, 500);
|
|
8678
|
+
}
|
|
8137
8679
|
}
|
|
8138
8680
|
function handleAgentQueue(agentId, _ctx, json2, taskToSummary2) {
|
|
8139
8681
|
const pending = listTasks({ status: "pending" });
|
|
@@ -8199,6 +8741,8 @@ async function handleRegisterAgent(req, _ctx, json2) {
|
|
|
8199
8741
|
return json2({ error: result.message, conflict: true }, 409);
|
|
8200
8742
|
return json2(result, 201);
|
|
8201
8743
|
} catch (e) {
|
|
8744
|
+
if (e instanceof InvalidAgentNameError)
|
|
8745
|
+
return json2({ error: e.message, suggestions: e.suggestions }, 400);
|
|
8202
8746
|
return json2({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500);
|
|
8203
8747
|
}
|
|
8204
8748
|
}
|
|
@@ -8208,6 +8752,8 @@ async function handleUpdateAgent(id, req, _ctx, json2) {
|
|
|
8208
8752
|
const agent = updateAgent(id, body);
|
|
8209
8753
|
return json2(agent);
|
|
8210
8754
|
} catch (e) {
|
|
8755
|
+
if (e instanceof InvalidAgentNameError)
|
|
8756
|
+
return json2({ error: e.message, suggestions: e.suggestions }, 400);
|
|
8211
8757
|
return json2({ error: e instanceof Error ? e.message : "Failed to update agent" }, 500);
|
|
8212
8758
|
}
|
|
8213
8759
|
}
|
|
@@ -8444,14 +8990,26 @@ function resolveDashboardDir() {
|
|
|
8444
8990
|
}
|
|
8445
8991
|
return join9(process.cwd(), "dashboard", "dist");
|
|
8446
8992
|
}
|
|
8993
|
+
function getProvidedApiKey(req) {
|
|
8994
|
+
const headerKey = req.headers.get("x-api-key");
|
|
8995
|
+
if (headerKey)
|
|
8996
|
+
return headerKey.trim();
|
|
8997
|
+
const auth = req.headers.get("authorization");
|
|
8998
|
+
if (!auth)
|
|
8999
|
+
return null;
|
|
9000
|
+
return auth.replace(/^Bearer\s+/i, "").trim() || null;
|
|
9001
|
+
}
|
|
8447
9002
|
function checkAuth(req, apiKey) {
|
|
8448
|
-
|
|
9003
|
+
const generatedKeysEnabled = hasActiveApiKeys();
|
|
9004
|
+
if (!apiKey && !generatedKeysEnabled)
|
|
8449
9005
|
return null;
|
|
8450
|
-
const provided =
|
|
8451
|
-
|
|
9006
|
+
const provided = getProvidedApiKey(req);
|
|
9007
|
+
const matchesEnvKey = Boolean(apiKey && provided && provided === apiKey);
|
|
9008
|
+
const matchesGeneratedKey = Boolean(provided && verifyApiKey(provided));
|
|
9009
|
+
if (!matchesEnvKey && !matchesGeneratedKey) {
|
|
8452
9010
|
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
8453
9011
|
status: 401,
|
|
8454
|
-
headers: { "Content-Type": "application/json", ...SECURITY_HEADERS }
|
|
9012
|
+
headers: { "Content-Type": "application/json", "WWW-Authenticate": "Bearer", ...SECURITY_HEADERS }
|
|
8455
9013
|
});
|
|
8456
9014
|
}
|
|
8457
9015
|
return null;
|
|
@@ -8833,6 +9391,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
8833
9391
|
var MIME_TYPES, SECURITY_HEADERS, rateLimitMap, RATE_LIMIT_WINDOW_MS = 60000, RATE_LIMIT_MAX = 120;
|
|
8834
9392
|
var init_serve = __esm(() => {
|
|
8835
9393
|
init_database();
|
|
9394
|
+
init_api_keys();
|
|
8836
9395
|
init_routes();
|
|
8837
9396
|
MIME_TYPES = {
|
|
8838
9397
|
".html": "text/html; charset=utf-8",
|
|
@@ -10199,6 +10758,77 @@ var init_Dashboard = __esm(() => {
|
|
|
10199
10758
|
init_audit();
|
|
10200
10759
|
});
|
|
10201
10760
|
|
|
10761
|
+
// src/db/handoffs.ts
|
|
10762
|
+
var exports_handoffs = {};
|
|
10763
|
+
__export(exports_handoffs, {
|
|
10764
|
+
listHandoffs: () => listHandoffs,
|
|
10765
|
+
getLatestHandoff: () => getLatestHandoff,
|
|
10766
|
+
createHandoff: () => createHandoff
|
|
10767
|
+
});
|
|
10768
|
+
function createHandoff(input, db) {
|
|
10769
|
+
const d = db || getDatabase();
|
|
10770
|
+
const id = uuid();
|
|
10771
|
+
const timestamp = now();
|
|
10772
|
+
d.run(`INSERT INTO handoffs (id, agent_id, project_id, summary, completed, in_progress, blockers, next_steps, created_at)
|
|
10773
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10774
|
+
id,
|
|
10775
|
+
input.agent_id || null,
|
|
10776
|
+
input.project_id || null,
|
|
10777
|
+
input.summary,
|
|
10778
|
+
input.completed ? JSON.stringify(input.completed) : null,
|
|
10779
|
+
input.in_progress ? JSON.stringify(input.in_progress) : null,
|
|
10780
|
+
input.blockers ? JSON.stringify(input.blockers) : null,
|
|
10781
|
+
input.next_steps ? JSON.stringify(input.next_steps) : null,
|
|
10782
|
+
timestamp
|
|
10783
|
+
]);
|
|
10784
|
+
return {
|
|
10785
|
+
id,
|
|
10786
|
+
agent_id: input.agent_id || null,
|
|
10787
|
+
project_id: input.project_id || null,
|
|
10788
|
+
summary: input.summary,
|
|
10789
|
+
completed: input.completed || null,
|
|
10790
|
+
in_progress: input.in_progress || null,
|
|
10791
|
+
blockers: input.blockers || null,
|
|
10792
|
+
next_steps: input.next_steps || null,
|
|
10793
|
+
created_at: timestamp
|
|
10794
|
+
};
|
|
10795
|
+
}
|
|
10796
|
+
function rowToHandoff(row) {
|
|
10797
|
+
return {
|
|
10798
|
+
...row,
|
|
10799
|
+
completed: row.completed ? JSON.parse(row.completed) : null,
|
|
10800
|
+
in_progress: row.in_progress ? JSON.parse(row.in_progress) : null,
|
|
10801
|
+
blockers: row.blockers ? JSON.parse(row.blockers) : null,
|
|
10802
|
+
next_steps: row.next_steps ? JSON.parse(row.next_steps) : null
|
|
10803
|
+
};
|
|
10804
|
+
}
|
|
10805
|
+
function listHandoffs(projectId, limit = 10, db) {
|
|
10806
|
+
const d = db || getDatabase();
|
|
10807
|
+
if (projectId) {
|
|
10808
|
+
return d.query("SELECT * FROM handoffs WHERE project_id = ? ORDER BY rowid DESC LIMIT ?").all(projectId, limit).map(rowToHandoff);
|
|
10809
|
+
}
|
|
10810
|
+
return d.query("SELECT * FROM handoffs ORDER BY rowid DESC LIMIT ?").all(limit).map(rowToHandoff);
|
|
10811
|
+
}
|
|
10812
|
+
function getLatestHandoff(agentId, projectId, db) {
|
|
10813
|
+
const d = db || getDatabase();
|
|
10814
|
+
let query = "SELECT * FROM handoffs WHERE 1=1";
|
|
10815
|
+
const params = [];
|
|
10816
|
+
if (agentId) {
|
|
10817
|
+
query += " AND agent_id = ?";
|
|
10818
|
+
params.push(agentId);
|
|
10819
|
+
}
|
|
10820
|
+
if (projectId) {
|
|
10821
|
+
query += " AND project_id = ?";
|
|
10822
|
+
params.push(projectId);
|
|
10823
|
+
}
|
|
10824
|
+
query += " ORDER BY rowid DESC LIMIT 1";
|
|
10825
|
+
const row = d.query(query).get(...params);
|
|
10826
|
+
return row ? rowToHandoff(row) : null;
|
|
10827
|
+
}
|
|
10828
|
+
var init_handoffs = __esm(() => {
|
|
10829
|
+
init_database();
|
|
10830
|
+
});
|
|
10831
|
+
|
|
10202
10832
|
// src/lib/auto-assign.ts
|
|
10203
10833
|
var exports_auto_assign = {};
|
|
10204
10834
|
__export(exports_auto_assign, {
|
|
@@ -10289,7 +10919,7 @@ async function callCerebras(prompt, apiKey) {
|
|
|
10289
10919
|
}
|
|
10290
10920
|
async function autoAssignTask(taskId, db) {
|
|
10291
10921
|
const d = db || getDatabase();
|
|
10292
|
-
const task =
|
|
10922
|
+
const task = getTask(taskId, d);
|
|
10293
10923
|
if (!task)
|
|
10294
10924
|
throw new Error(`Task ${taskId} not found`);
|
|
10295
10925
|
const agents = listAgents(d).filter((a) => a.status === "active");
|
|
@@ -14489,7 +15119,7 @@ var init_dist = __esm(() => {
|
|
|
14489
15119
|
var nodeCrypto = __require2("crypto");
|
|
14490
15120
|
module.exports = {
|
|
14491
15121
|
postgresMd5PasswordHash,
|
|
14492
|
-
randomBytes,
|
|
15122
|
+
randomBytes: randomBytes2,
|
|
14493
15123
|
deriveKey,
|
|
14494
15124
|
sha256,
|
|
14495
15125
|
hashByName,
|
|
@@ -14499,7 +15129,7 @@ var init_dist = __esm(() => {
|
|
|
14499
15129
|
var webCrypto = nodeCrypto.webcrypto || globalThis.crypto;
|
|
14500
15130
|
var subtleCrypto = webCrypto.subtle;
|
|
14501
15131
|
var textEncoder = new TextEncoder;
|
|
14502
|
-
function
|
|
15132
|
+
function randomBytes2(length) {
|
|
14503
15133
|
return webCrypto.getRandomValues(Buffer.alloc(length));
|
|
14504
15134
|
}
|
|
14505
15135
|
async function md5(string) {
|
|
@@ -26215,12 +26845,21 @@ var init_dispatch2 = __esm(() => {
|
|
|
26215
26845
|
// src/mcp/tools/task-crud.ts
|
|
26216
26846
|
function registerTaskCrudTools(server, ctx) {
|
|
26217
26847
|
const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
|
|
26848
|
+
function versionFor(taskId, version) {
|
|
26849
|
+
const current = getTask(taskId);
|
|
26850
|
+
if (!current)
|
|
26851
|
+
throw new TaskNotFoundError(taskId);
|
|
26852
|
+
if (version !== undefined && current.version !== version) {
|
|
26853
|
+
throw new VersionConflictError(taskId, version, current.version);
|
|
26854
|
+
}
|
|
26855
|
+
return current.version;
|
|
26856
|
+
}
|
|
26218
26857
|
if (shouldRegisterTool("create_task")) {
|
|
26219
26858
|
server.tool("create_task", "Create a new task in a project. Pass short_id=null to auto-generate.", {
|
|
26220
26859
|
title: exports_external2.string().describe("Task title"),
|
|
26221
26860
|
description: exports_external2.string().optional().describe("Task description (markdown)"),
|
|
26222
|
-
status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe("Initial status (default: pending)"),
|
|
26223
|
-
priority: exports_external2.enum(["low", "medium", "high", "
|
|
26861
|
+
status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional().describe("Initial status (default: pending)"),
|
|
26862
|
+
priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional().describe("Priority (default: medium)"),
|
|
26224
26863
|
project_id: exports_external2.string().optional().describe("Project ID"),
|
|
26225
26864
|
task_list_id: exports_external2.string().optional().describe("Task list ID"),
|
|
26226
26865
|
assigned_to: exports_external2.string().optional().describe("Agent ID or name to assign to"),
|
|
@@ -26250,9 +26889,9 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
26250
26889
|
if (confidence !== undefined)
|
|
26251
26890
|
resolved.confidence = confidence;
|
|
26252
26891
|
if (retry_count !== undefined)
|
|
26253
|
-
resolved.
|
|
26892
|
+
resolved.max_retries = retry_count;
|
|
26254
26893
|
if (deadline)
|
|
26255
|
-
resolved.
|
|
26894
|
+
resolved.due_at = deadline;
|
|
26256
26895
|
const task = createTask(resolved);
|
|
26257
26896
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
26258
26897
|
} catch (e) {
|
|
@@ -26262,8 +26901,8 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
26262
26901
|
}
|
|
26263
26902
|
if (shouldRegisterTool("list_tasks")) {
|
|
26264
26903
|
server.tool("list_tasks", "List tasks with optional filters. Pass empty arrays for multi-value filters (e.g. status=[] shows all).", {
|
|
26265
|
-
status: exports_external2.union([exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]), exports_external2.array(exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]))]).optional().describe("Filter by status"),
|
|
26266
|
-
priority: exports_external2.union([exports_external2.enum(["low", "medium", "high", "
|
|
26904
|
+
status: exports_external2.union([exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]), exports_external2.array(exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]))]).optional().describe("Filter by status"),
|
|
26905
|
+
priority: exports_external2.union([exports_external2.enum(["low", "medium", "high", "critical"]), exports_external2.array(exports_external2.enum(["low", "medium", "high", "critical"]))]).optional().describe("Filter by priority"),
|
|
26267
26906
|
project_id: exports_external2.string().optional().describe("Filter by project"),
|
|
26268
26907
|
task_list_id: exports_external2.string().optional().describe("Filter by task list"),
|
|
26269
26908
|
assigned_to: exports_external2.string().optional().describe("Filter by assignee (agent ID or name, empty string = unassigned)"),
|
|
@@ -26298,9 +26937,9 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
26298
26937
|
}, async ({ task_id }) => {
|
|
26299
26938
|
try {
|
|
26300
26939
|
const resolvedId = resolveId(task_id);
|
|
26301
|
-
const task =
|
|
26940
|
+
const task = getTask(resolvedId);
|
|
26302
26941
|
if (!task)
|
|
26303
|
-
throw new
|
|
26942
|
+
throw new TaskNotFoundError(task_id);
|
|
26304
26943
|
const focus = ctx.getAgentFocus(task.assigned_to || "");
|
|
26305
26944
|
const lines = [
|
|
26306
26945
|
`ID: ${task.id}`,
|
|
@@ -26314,7 +26953,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
26314
26953
|
task.estimated_minutes != null ? `Estimate: ${task.estimated_minutes} min` : null,
|
|
26315
26954
|
task.actual_minutes != null ? `Actual: ${task.actual_minutes} min` : null,
|
|
26316
26955
|
task.confidence != null ? `Confidence: ${task.confidence}` : null,
|
|
26317
|
-
task.
|
|
26956
|
+
task.due_at ? `Due: ${task.due_at}` : null,
|
|
26318
26957
|
task.completed_at ? `Completed: ${task.completed_at}` : null,
|
|
26319
26958
|
focus ? `Focus: agent=${focus.agent_id} project=${focus.project_id || "(global)"}` : null,
|
|
26320
26959
|
task.created_at ? `Created: ${task.created_at}` : null,
|
|
@@ -26336,8 +26975,8 @@ ${task.description}` : null
|
|
|
26336
26975
|
task_id: exports_external2.string().describe("Task ID"),
|
|
26337
26976
|
title: exports_external2.string().optional(),
|
|
26338
26977
|
description: exports_external2.string().optional(),
|
|
26339
|
-
status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
|
|
26340
|
-
priority: exports_external2.enum(["low", "medium", "high", "
|
|
26978
|
+
status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
26979
|
+
priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional(),
|
|
26341
26980
|
assigned_to: exports_external2.string().nullable().optional().describe("Agent ID or name, null to unassign"),
|
|
26342
26981
|
project_id: exports_external2.string().nullable().optional(),
|
|
26343
26982
|
task_list_id: exports_external2.string().nullable().optional(),
|
|
@@ -26366,9 +27005,15 @@ ${task.description}` : null
|
|
|
26366
27005
|
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
26367
27006
|
if (resolved.depends_on && Array.isArray(resolved.depends_on))
|
|
26368
27007
|
resolved.depends_on = resolved.depends_on.map(resolveId);
|
|
26369
|
-
if (resolved.estimate !== undefined)
|
|
27008
|
+
if (resolved.estimate !== undefined) {
|
|
26370
27009
|
resolved.estimated_minutes = resolved.estimate;
|
|
26371
|
-
|
|
27010
|
+
delete resolved.estimate;
|
|
27011
|
+
}
|
|
27012
|
+
if (resolved.deadline !== undefined) {
|
|
27013
|
+
resolved.due_at = resolved.deadline;
|
|
27014
|
+
delete resolved.deadline;
|
|
27015
|
+
}
|
|
27016
|
+
const task = updateTask(resolvedId, { ...resolved, version: versionFor(resolvedId, version) });
|
|
26372
27017
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
26373
27018
|
} catch (e) {
|
|
26374
27019
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -26393,11 +27038,24 @@ ${task.description}` : null
|
|
|
26393
27038
|
var init_task_crud2 = __esm(() => {
|
|
26394
27039
|
init_zod2();
|
|
26395
27040
|
init_tasks();
|
|
27041
|
+
init_types();
|
|
26396
27042
|
});
|
|
26397
27043
|
|
|
26398
27044
|
// src/mcp/tools/task-project-tools.ts
|
|
26399
27045
|
function registerTaskProjectTools(server, ctx) {
|
|
26400
27046
|
const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
|
|
27047
|
+
function versionFor(taskId, version) {
|
|
27048
|
+
const current = getTask(taskId);
|
|
27049
|
+
if (!current)
|
|
27050
|
+
throw new TaskNotFoundError(taskId);
|
|
27051
|
+
if (version !== undefined && current.version !== version) {
|
|
27052
|
+
throw new VersionConflictError(taskId, version, current.version);
|
|
27053
|
+
}
|
|
27054
|
+
return current.version;
|
|
27055
|
+
}
|
|
27056
|
+
function updateWithOptionalVersion(taskId, updates, version) {
|
|
27057
|
+
return updateTask(taskId, { ...updates, version: versionFor(taskId, version) });
|
|
27058
|
+
}
|
|
26401
27059
|
if (shouldRegisterTool("start_task")) {
|
|
26402
27060
|
server.tool("start_task", "Mark a task as in_progress. Uses optimistic locking via version if provided.", {
|
|
26403
27061
|
task_id: exports_external2.string().describe("Task ID"),
|
|
@@ -26405,7 +27063,12 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26405
27063
|
}, async ({ task_id, version }) => {
|
|
26406
27064
|
try {
|
|
26407
27065
|
const resolvedId = resolveId(task_id);
|
|
26408
|
-
|
|
27066
|
+
if (version !== undefined)
|
|
27067
|
+
versionFor(resolvedId, version);
|
|
27068
|
+
const current = getTask(resolvedId);
|
|
27069
|
+
if (!current)
|
|
27070
|
+
throw new TaskNotFoundError(resolvedId);
|
|
27071
|
+
const task = startTask(resolvedId, current.assigned_to || current.agent_id || "mcp");
|
|
26409
27072
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
26410
27073
|
} catch (e) {
|
|
26411
27074
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -26421,7 +27084,12 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26421
27084
|
}, async ({ task_id, confidence, completed_at, version }) => {
|
|
26422
27085
|
try {
|
|
26423
27086
|
const resolvedId = resolveId(task_id);
|
|
26424
|
-
|
|
27087
|
+
if (version !== undefined)
|
|
27088
|
+
versionFor(resolvedId, version);
|
|
27089
|
+
const current = getTask(resolvedId);
|
|
27090
|
+
if (!current)
|
|
27091
|
+
throw new TaskNotFoundError(resolvedId);
|
|
27092
|
+
const task = completeTask(resolvedId, current.assigned_to || current.agent_id || undefined, undefined, { confidence, completed_at });
|
|
26425
27093
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
26426
27094
|
} catch (e) {
|
|
26427
27095
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -26435,7 +27103,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26435
27103
|
}, async ({ task_id, version }) => {
|
|
26436
27104
|
try {
|
|
26437
27105
|
const resolvedId = resolveId(task_id);
|
|
26438
|
-
const task =
|
|
27106
|
+
const task = version === undefined ? setTaskStatus(resolvedId, "cancelled") : updateWithOptionalVersion(resolvedId, { status: "cancelled" }, version);
|
|
26439
27107
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
26440
27108
|
} catch (e) {
|
|
26441
27109
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -26451,7 +27119,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26451
27119
|
try {
|
|
26452
27120
|
const resolvedId = resolveId(task_id);
|
|
26453
27121
|
const resolvedAssignee = resolveId(new_assignee, "agents");
|
|
26454
|
-
const task =
|
|
27122
|
+
const task = updateWithOptionalVersion(resolvedId, { assigned_to: resolvedAssignee }, version);
|
|
26455
27123
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
26456
27124
|
} catch (e) {
|
|
26457
27125
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -26466,7 +27134,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26466
27134
|
}, async ({ task_id, deadline, version }) => {
|
|
26467
27135
|
try {
|
|
26468
27136
|
const resolvedId = resolveId(task_id);
|
|
26469
|
-
const task =
|
|
27137
|
+
const task = updateWithOptionalVersion(resolvedId, { due_at: deadline }, version);
|
|
26470
27138
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
26471
27139
|
} catch (e) {
|
|
26472
27140
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -26476,12 +27144,12 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26476
27144
|
if (shouldRegisterTool("prioritize_task")) {
|
|
26477
27145
|
server.tool("prioritize_task", "Set a task's priority.", {
|
|
26478
27146
|
task_id: exports_external2.string().describe("Task ID"),
|
|
26479
|
-
priority: exports_external2.enum(["low", "medium", "high", "
|
|
27147
|
+
priority: exports_external2.enum(["low", "medium", "high", "critical"]).describe("New priority"),
|
|
26480
27148
|
version: exports_external2.number().optional().describe("Expected version for optimistic locking")
|
|
26481
27149
|
}, async ({ task_id, priority, version }) => {
|
|
26482
27150
|
try {
|
|
26483
27151
|
const resolvedId = resolveId(task_id);
|
|
26484
|
-
const task =
|
|
27152
|
+
const task = version === undefined ? setTaskPriority(resolvedId, priority) : updateWithOptionalVersion(resolvedId, { priority }, version);
|
|
26485
27153
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
26486
27154
|
} catch (e) {
|
|
26487
27155
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -26537,18 +27205,18 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26537
27205
|
if (shouldRegisterTool("bulk_update_tasks")) {
|
|
26538
27206
|
server.tool("bulk_update_tasks", "Update multiple tasks at once. All tasks must pass the dependency check.", {
|
|
26539
27207
|
task_ids: exports_external2.array(exports_external2.string()).describe("Array of task IDs to update"),
|
|
26540
|
-
status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
|
|
26541
|
-
priority: exports_external2.enum(["low", "medium", "high", "
|
|
27208
|
+
status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
27209
|
+
priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional(),
|
|
26542
27210
|
assigned_to: exports_external2.string().nullable().optional().describe("Agent ID or name, null to unassign")
|
|
26543
27211
|
}, async ({ task_ids, status, priority, assigned_to }) => {
|
|
26544
27212
|
try {
|
|
26545
|
-
const { bulkUpdateTasks: bulkUpdateTasks2 } = (()
|
|
27213
|
+
const { bulkUpdateTasks: bulkUpdateTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
26546
27214
|
const resolved = task_ids.map(resolveId);
|
|
26547
27215
|
let resolvedAssignee = assigned_to;
|
|
26548
27216
|
if (resolvedAssignee && typeof resolvedAssignee === "string")
|
|
26549
27217
|
resolvedAssignee = resolveId(resolvedAssignee, "agents");
|
|
26550
27218
|
const result = bulkUpdateTasks2(resolved, { status, priority, assigned_to: resolvedAssignee });
|
|
26551
|
-
return { content: [{ type: "text", text: `${result.updated} task(s) updated, ${result.
|
|
27219
|
+
return { content: [{ type: "text", text: `${result.updated} task(s) updated, ${result.failed.length} failed.` }] };
|
|
26552
27220
|
} catch (e) {
|
|
26553
27221
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
26554
27222
|
}
|
|
@@ -26559,8 +27227,8 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26559
27227
|
tasks: exports_external2.array(exports_external2.object({
|
|
26560
27228
|
title: exports_external2.string(),
|
|
26561
27229
|
description: exports_external2.string().optional(),
|
|
26562
|
-
status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
|
|
26563
|
-
priority: exports_external2.enum(["low", "medium", "high", "
|
|
27230
|
+
status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
27231
|
+
priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional(),
|
|
26564
27232
|
project_id: exports_external2.string().optional(),
|
|
26565
27233
|
task_list_id: exports_external2.string().optional(),
|
|
26566
27234
|
assigned_to: exports_external2.string().optional(),
|
|
@@ -26571,7 +27239,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26571
27239
|
})).describe("Array of task objects")
|
|
26572
27240
|
}, async ({ tasks }) => {
|
|
26573
27241
|
try {
|
|
26574
|
-
const { bulkCreateTasks: bulkCreateTasks2 } = (()
|
|
27242
|
+
const { bulkCreateTasks: bulkCreateTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
26575
27243
|
const resolved = tasks.map((t) => {
|
|
26576
27244
|
const r = { ...t };
|
|
26577
27245
|
if (r.project_id)
|
|
@@ -26585,7 +27253,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26585
27253
|
return r;
|
|
26586
27254
|
});
|
|
26587
27255
|
const result = bulkCreateTasks2(resolved);
|
|
26588
|
-
return { content: [{ type: "text", text: `${result.created} task(s) created
|
|
27256
|
+
return { content: [{ type: "text", text: `${result.created.length} task(s) created.` }] };
|
|
26589
27257
|
} catch (e) {
|
|
26590
27258
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
26591
27259
|
}
|
|
@@ -26597,9 +27265,9 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26597
27265
|
force: exports_external2.boolean().optional().describe("Skip child check for all tasks (dangerous)")
|
|
26598
27266
|
}, async ({ task_ids, force }) => {
|
|
26599
27267
|
try {
|
|
26600
|
-
const { bulkDeleteTasks } = (()
|
|
27268
|
+
const { bulkDeleteTasks: bulkDeleteTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
26601
27269
|
const resolved = task_ids.map(resolveId);
|
|
26602
|
-
const result =
|
|
27270
|
+
const result = bulkDeleteTasks2(resolved, force);
|
|
26603
27271
|
return { content: [{ type: "text", text: `${result.deleted} task(s) deleted, ${result.skipped} skipped (has children).` }] };
|
|
26604
27272
|
} catch (e) {
|
|
26605
27273
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -26648,7 +27316,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26648
27316
|
const project = getProject(resolvedId);
|
|
26649
27317
|
if (!project)
|
|
26650
27318
|
throw new TaskNotFoundError(`Project not found: ${project_id}`);
|
|
26651
|
-
const { listTasks: listTasks3 } = (()
|
|
27319
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
26652
27320
|
const tasks = listTasks3({ project_id: resolvedId, limit: 100 }, undefined);
|
|
26653
27321
|
const lines = [
|
|
26654
27322
|
`ID: ${project.id}`,
|
|
@@ -26749,7 +27417,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
26749
27417
|
throw new TaskNotFoundError(`Task list not found: ${task_list_id}`);
|
|
26750
27418
|
let tasks = [];
|
|
26751
27419
|
if (include_tasks) {
|
|
26752
|
-
const { listTasks: listTasks3 } = (()
|
|
27420
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
26753
27421
|
tasks = listTasks3({ task_list_id: resolvedId, limit: 200 }, undefined);
|
|
26754
27422
|
}
|
|
26755
27423
|
const lines = [
|
|
@@ -26849,7 +27517,7 @@ Tasks:` : null,
|
|
|
26849
27517
|
throw new TaskNotFoundError(`Plan not found: ${plan_id}`);
|
|
26850
27518
|
let tasks = [];
|
|
26851
27519
|
if (include_tasks) {
|
|
26852
|
-
const { listTasks: listTasks3 } = (()
|
|
27520
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
26853
27521
|
tasks = listTasks3({ plan_id: resolvedId, limit: 200 }, undefined);
|
|
26854
27522
|
}
|
|
26855
27523
|
const lines = [
|
|
@@ -26938,7 +27606,7 @@ Tasks:` : null,
|
|
|
26938
27606
|
const tag = getTag(tag_id);
|
|
26939
27607
|
if (!tag)
|
|
26940
27608
|
throw new TaskNotFoundError(`Tag not found: ${tag_id}`);
|
|
26941
|
-
const { listTasks: listTasks3 } = (()
|
|
27609
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
26942
27610
|
const tasks = listTasks3({ tags: [tag.name], limit: 100 }, undefined);
|
|
26943
27611
|
const lines = [
|
|
26944
27612
|
`Tag: ${tag.name}${tag.color ? ` (${tag.color})` : ""}`,
|
|
@@ -27017,7 +27685,7 @@ Tasks:` : null,
|
|
|
27017
27685
|
const label = getLabel(label_id);
|
|
27018
27686
|
if (!label)
|
|
27019
27687
|
throw new TaskNotFoundError(`Label not found: ${label_id}`);
|
|
27020
|
-
const { listTasks: listTasks3 } = (()
|
|
27688
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27021
27689
|
const tasks = listTasks3({ tags: [label.name], limit: 100 }, undefined);
|
|
27022
27690
|
const lines = [
|
|
27023
27691
|
`Label: ${label.name}${label.color ? ` (${label.color})` : ""}`,
|
|
@@ -27068,7 +27736,7 @@ Tasks:` : null,
|
|
|
27068
27736
|
try {
|
|
27069
27737
|
const resolvedId = resolveId(task_id);
|
|
27070
27738
|
const resolvedAuthor = author ? resolveId(author, "agents") : undefined;
|
|
27071
|
-
const comment = addComment({ task_id: resolvedId, body,
|
|
27739
|
+
const comment = addComment({ task_id: resolvedId, content: body, agent_id: resolvedAuthor });
|
|
27072
27740
|
return { content: [{ type: "text", text: `Comment added to ${task_id.slice(0, 8)}: ${body.slice(0, 50)}${body.length > 50 ? "..." : ""}` }] };
|
|
27073
27741
|
} catch (e) {
|
|
27074
27742
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -27084,8 +27752,8 @@ Tasks:` : null,
|
|
|
27084
27752
|
const comments = listComments(resolvedId);
|
|
27085
27753
|
if (comments.length === 0)
|
|
27086
27754
|
return { content: [{ type: "text", text: "No comments." }] };
|
|
27087
|
-
const lines = comments.map((c) => `[${c.
|
|
27088
|
-
${c.
|
|
27755
|
+
const lines = comments.map((c) => `[${c.agent_id || "unknown"}] ${c.created_at?.slice(0, 16)}:
|
|
27756
|
+
${c.content}`);
|
|
27089
27757
|
return { content: [{ type: "text", text: lines.join(`
|
|
27090
27758
|
|
|
27091
27759
|
`) }] };
|
|
@@ -27100,7 +27768,7 @@ Tasks:` : null,
|
|
|
27100
27768
|
body: exports_external2.string().describe("New comment body")
|
|
27101
27769
|
}, async ({ comment_id, body }) => {
|
|
27102
27770
|
try {
|
|
27103
|
-
const comment = updateComment(comment_id, { body });
|
|
27771
|
+
const comment = updateComment(comment_id, { content: body });
|
|
27104
27772
|
return { content: [{ type: "text", text: `Comment ${comment_id.slice(0, 8)} updated.` }] };
|
|
27105
27773
|
} catch (e) {
|
|
27106
27774
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -27123,11 +27791,11 @@ Tasks:` : null,
|
|
|
27123
27791
|
server.tool("search_tasks", "Full-text search across task titles and descriptions.", {
|
|
27124
27792
|
query: exports_external2.string().describe("Search query"),
|
|
27125
27793
|
project_id: exports_external2.string().optional().describe("Filter by project"),
|
|
27126
|
-
status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
|
|
27794
|
+
status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
27127
27795
|
limit: exports_external2.number().optional().describe("Max results (default: 20)")
|
|
27128
27796
|
}, async ({ query, project_id, status, limit }) => {
|
|
27129
27797
|
try {
|
|
27130
|
-
const { searchTasks: searchTasks2 } = (()
|
|
27798
|
+
const { searchTasks: searchTasks2 } = (init_search(), __toCommonJS(exports_search));
|
|
27131
27799
|
const resolved = { query, limit };
|
|
27132
27800
|
if (project_id)
|
|
27133
27801
|
resolved.project_id = resolveId(project_id, "projects");
|
|
@@ -27145,29 +27813,6 @@ ${lines.join(`
|
|
|
27145
27813
|
}
|
|
27146
27814
|
});
|
|
27147
27815
|
}
|
|
27148
|
-
if (shouldRegisterTool("sync")) {
|
|
27149
|
-
server.tool("sync", "Sync tasks from a GitHub PR or external source into the project.", {
|
|
27150
|
-
source: exports_external2.enum(["github_pr", "linear", "asana"]).describe("Source type"),
|
|
27151
|
-
source_id: exports_external2.string().describe("PR number, Linear issue ID, or Asana task ID"),
|
|
27152
|
-
project_id: exports_external2.string().optional().describe("Project ID to import into"),
|
|
27153
|
-
options: exports_external2.record(exports_external2.unknown()).optional().describe("Source-specific options")
|
|
27154
|
-
}, async ({ source, source_id, project_id, options }) => {
|
|
27155
|
-
try {
|
|
27156
|
-
const { syncFromGithubPR, syncFromLinear, syncFromAsana } = (()=>{throw new Error("Cannot require module "+"../lib/sync.js");})();
|
|
27157
|
-
let result;
|
|
27158
|
-
if (source === "github_pr") {
|
|
27159
|
-
result = await syncFromGithubPR({ prNumber: parseInt(source_id), project_id: project_id ? resolveId(project_id, "projects") : undefined, ...options });
|
|
27160
|
-
} else if (source === "linear") {
|
|
27161
|
-
result = await syncFromLinear({ issueId: source_id, project_id: project_id ? resolveId(project_id, "projects") : undefined, ...options });
|
|
27162
|
-
} else {
|
|
27163
|
-
result = await syncFromAsana({ taskId: source_id, project_id: project_id ? resolveId(project_id, "projects") : undefined, ...options });
|
|
27164
|
-
}
|
|
27165
|
-
return { content: [{ type: "text", text: `Synced from ${source}: ${result.task?.title || source_id}` }] };
|
|
27166
|
-
} catch (e) {
|
|
27167
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27168
|
-
}
|
|
27169
|
-
});
|
|
27170
|
-
}
|
|
27171
27816
|
}
|
|
27172
27817
|
var init_task_project_tools = __esm(() => {
|
|
27173
27818
|
init_zod2();
|
|
@@ -27182,6 +27827,16 @@ var init_task_project_tools = __esm(() => {
|
|
|
27182
27827
|
// src/mcp/tools/task-workflow-tools.ts
|
|
27183
27828
|
function registerTaskWorkflowTools(server, ctx) {
|
|
27184
27829
|
const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
|
|
27830
|
+
function versionFor(taskId, version) {
|
|
27831
|
+
const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27832
|
+
const current = getTask2(taskId);
|
|
27833
|
+
if (!current)
|
|
27834
|
+
throw new TaskNotFoundError(taskId);
|
|
27835
|
+
if (version !== undefined && current.version !== version) {
|
|
27836
|
+
throw new VersionConflictError(taskId, version, current.version);
|
|
27837
|
+
}
|
|
27838
|
+
return current.version;
|
|
27839
|
+
}
|
|
27185
27840
|
if (shouldRegisterTool("approve_task")) {
|
|
27186
27841
|
server.tool("approve_task", "Approve a task that requires_approval. Records who approved it.", {
|
|
27187
27842
|
task_id: exports_external2.string().describe("Task ID"),
|
|
@@ -27190,13 +27845,14 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
27190
27845
|
version: exports_external2.number().optional().describe("Expected version for optimistic locking")
|
|
27191
27846
|
}, async ({ task_id, approved_by, notes, version }) => {
|
|
27192
27847
|
try {
|
|
27193
|
-
const { updateTask: updateTask2 } = (()
|
|
27848
|
+
const { updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27194
27849
|
const resolvedId = resolveId(task_id);
|
|
27195
27850
|
const resolvedApprover = approved_by ? resolveId(approved_by, "agents") : undefined;
|
|
27196
27851
|
const task = updateTask2(resolvedId, {
|
|
27197
27852
|
approved_by: resolvedApprover,
|
|
27198
|
-
metadata: notes ? { approval_notes: notes } : {}
|
|
27199
|
-
|
|
27853
|
+
metadata: notes ? { approval_notes: notes } : {},
|
|
27854
|
+
version: versionFor(resolvedId, version)
|
|
27855
|
+
});
|
|
27200
27856
|
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} approved by ${resolvedApprover || "unknown"}` }] };
|
|
27201
27857
|
} catch (e) {
|
|
27202
27858
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -27211,15 +27867,11 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
27211
27867
|
version: exports_external2.number().optional().describe("Expected version for optimistic locking")
|
|
27212
27868
|
}, async ({ task_id, reason, agent_id, version }) => {
|
|
27213
27869
|
try {
|
|
27214
|
-
const {
|
|
27870
|
+
const { failTask: failTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27215
27871
|
const resolvedId = resolveId(task_id);
|
|
27216
|
-
|
|
27217
|
-
|
|
27218
|
-
|
|
27219
|
-
back_on_board: false,
|
|
27220
|
-
metadata: reason ? { failure_reason: reason } : {}
|
|
27221
|
-
}, version);
|
|
27222
|
-
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} marked failed. Retry count: ${task.retry_count}` }] };
|
|
27872
|
+
versionFor(resolvedId, version);
|
|
27873
|
+
const result = failTask2(resolvedId, agent_id, reason);
|
|
27874
|
+
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} marked failed. Retry count: ${result.task.retry_count}` }] };
|
|
27223
27875
|
} catch (e) {
|
|
27224
27876
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27225
27877
|
}
|
|
@@ -27228,12 +27880,12 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
27228
27880
|
if (shouldRegisterTool("get_my_tasks")) {
|
|
27229
27881
|
server.tool("get_my_tasks", "Get tasks assigned to the calling agent. Supports focus mode scoping.", {
|
|
27230
27882
|
agent_id: exports_external2.string().optional().describe("Agent ID (defaults to context agent)"),
|
|
27231
|
-
status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe("Filter by status"),
|
|
27883
|
+
status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional().describe("Filter by status"),
|
|
27232
27884
|
project_id: exports_external2.string().optional().describe("Filter by project (respects focus mode)"),
|
|
27233
27885
|
limit: exports_external2.number().optional().describe("Max results (default: 50)")
|
|
27234
27886
|
}, async ({ agent_id, status, project_id, limit }) => {
|
|
27235
27887
|
try {
|
|
27236
|
-
const { listTasks: listTasks3 } = (()
|
|
27888
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27237
27889
|
const focus = ctx.getAgentFocus(agent_id || "");
|
|
27238
27890
|
const effectiveAgentId = focus ? focus.agent_id : agent_id || "";
|
|
27239
27891
|
const effectiveProjectId = focus?.project_id || project_id;
|
|
@@ -27253,6 +27905,122 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
27253
27905
|
}
|
|
27254
27906
|
});
|
|
27255
27907
|
}
|
|
27908
|
+
if (shouldRegisterTool("get_next_task")) {
|
|
27909
|
+
server.tool("get_next_task", "Get the best available pending task without claiming it.", {
|
|
27910
|
+
agent_id: exports_external2.string().optional().describe("Agent ID or name for assignment affinity"),
|
|
27911
|
+
project_id: exports_external2.string().optional().describe("Filter by project"),
|
|
27912
|
+
task_list_id: exports_external2.string().optional().describe("Filter by task list"),
|
|
27913
|
+
plan_id: exports_external2.string().optional().describe("Filter by plan"),
|
|
27914
|
+
tags: exports_external2.array(exports_external2.string()).optional().describe("Filter by tags")
|
|
27915
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
27916
|
+
try {
|
|
27917
|
+
const { getNextTask: getNextTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27918
|
+
const filters = {};
|
|
27919
|
+
if (project_id)
|
|
27920
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
27921
|
+
if (task_list_id)
|
|
27922
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
27923
|
+
if (plan_id)
|
|
27924
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
27925
|
+
if (tags)
|
|
27926
|
+
filters.tags = tags;
|
|
27927
|
+
const task = getNextTask2(agent_id, filters);
|
|
27928
|
+
return { content: [{ type: "text", text: task ? formatTask(task) : "No available task." }] };
|
|
27929
|
+
} catch (e) {
|
|
27930
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27931
|
+
}
|
|
27932
|
+
});
|
|
27933
|
+
}
|
|
27934
|
+
if (shouldRegisterTool("claim_next_task")) {
|
|
27935
|
+
server.tool("claim_next_task", "Atomically claim and start the best available pending task for an agent.", {
|
|
27936
|
+
agent_id: exports_external2.string().describe("Agent ID or name claiming the task"),
|
|
27937
|
+
project_id: exports_external2.string().optional().describe("Filter by project"),
|
|
27938
|
+
task_list_id: exports_external2.string().optional().describe("Filter by task list"),
|
|
27939
|
+
plan_id: exports_external2.string().optional().describe("Filter by plan"),
|
|
27940
|
+
tags: exports_external2.array(exports_external2.string()).optional().describe("Filter by tags")
|
|
27941
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
27942
|
+
try {
|
|
27943
|
+
const { claimNextTask: claimNextTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27944
|
+
const filters = {};
|
|
27945
|
+
if (project_id)
|
|
27946
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
27947
|
+
if (task_list_id)
|
|
27948
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
27949
|
+
if (plan_id)
|
|
27950
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
27951
|
+
if (tags)
|
|
27952
|
+
filters.tags = tags;
|
|
27953
|
+
const task = claimNextTask2(agent_id, filters);
|
|
27954
|
+
return { content: [{ type: "text", text: task ? formatTask(task) : "No available task to claim." }] };
|
|
27955
|
+
} catch (e) {
|
|
27956
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27957
|
+
}
|
|
27958
|
+
});
|
|
27959
|
+
}
|
|
27960
|
+
if (shouldRegisterTool("get_tasks_changed_since")) {
|
|
27961
|
+
server.tool("get_tasks_changed_since", "List tasks changed since an ISO timestamp.", {
|
|
27962
|
+
since: exports_external2.string().describe("ISO timestamp"),
|
|
27963
|
+
project_id: exports_external2.string().optional().describe("Filter by project"),
|
|
27964
|
+
task_list_id: exports_external2.string().optional().describe("Filter by task list"),
|
|
27965
|
+
limit: exports_external2.number().optional().describe("Maximum tasks to return")
|
|
27966
|
+
}, async ({ since, project_id, task_list_id, limit }) => {
|
|
27967
|
+
try {
|
|
27968
|
+
const { getTasksChangedSince: getTasksChangedSince2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27969
|
+
const filters = {};
|
|
27970
|
+
if (project_id)
|
|
27971
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
27972
|
+
if (task_list_id)
|
|
27973
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
27974
|
+
const tasks = getTasksChangedSince2(since, filters).slice(0, limit || 50);
|
|
27975
|
+
if (tasks.length === 0)
|
|
27976
|
+
return { content: [{ type: "text", text: "No changed tasks." }] };
|
|
27977
|
+
return { content: [{ type: "text", text: tasks.map(formatTask).join(`
|
|
27978
|
+
`) }] };
|
|
27979
|
+
} catch (e) {
|
|
27980
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27981
|
+
}
|
|
27982
|
+
});
|
|
27983
|
+
}
|
|
27984
|
+
function registerContextTool(name, description) {
|
|
27985
|
+
if (!shouldRegisterTool(name))
|
|
27986
|
+
return;
|
|
27987
|
+
server.tool(name, description, {
|
|
27988
|
+
agent_id: exports_external2.string().optional().describe("Agent ID or name"),
|
|
27989
|
+
project_id: exports_external2.string().optional().describe("Filter by project"),
|
|
27990
|
+
task_list_id: exports_external2.string().optional().describe("Filter by task list"),
|
|
27991
|
+
explain_blocked: exports_external2.boolean().optional().describe("Include blocked task details")
|
|
27992
|
+
}, async ({ agent_id, project_id, task_list_id, explain_blocked }) => {
|
|
27993
|
+
try {
|
|
27994
|
+
const { getStatus: getStatus2, getNextTask: getNextTask2, getOverdueTasks: getOverdueTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27995
|
+
const { getLatestHandoff: getLatestHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
27996
|
+
const filters = {};
|
|
27997
|
+
if (project_id)
|
|
27998
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
27999
|
+
if (task_list_id)
|
|
28000
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
28001
|
+
const status = getStatus2(filters, agent_id, { explain_blocked });
|
|
28002
|
+
const next_task = getNextTask2(agent_id, filters);
|
|
28003
|
+
const overdue = getOverdueTasks2(filters.project_id);
|
|
28004
|
+
const latest_handoff = getLatestHandoff2(agent_id, filters.project_id);
|
|
28005
|
+
return {
|
|
28006
|
+
content: [{
|
|
28007
|
+
type: "text",
|
|
28008
|
+
text: JSON.stringify({
|
|
28009
|
+
status,
|
|
28010
|
+
next_task,
|
|
28011
|
+
overdue_count: overdue.length,
|
|
28012
|
+
latest_handoff,
|
|
28013
|
+
as_of: new Date().toISOString()
|
|
28014
|
+
}, null, 2)
|
|
28015
|
+
}]
|
|
28016
|
+
};
|
|
28017
|
+
} catch (e) {
|
|
28018
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
28019
|
+
}
|
|
28020
|
+
});
|
|
28021
|
+
}
|
|
28022
|
+
registerContextTool("get_context", "Get session start context: queue status, next task, overdue count, and latest handoff.");
|
|
28023
|
+
registerContextTool("bootstrap", "Bootstrap an agent session with queue context and the next available task.");
|
|
27256
28024
|
if (shouldRegisterTool("get_org_chart")) {
|
|
27257
28025
|
server.tool("get_org_chart", "Get the global org chart (agent hierarchy + titles).", {
|
|
27258
28026
|
format: exports_external2.enum(["text", "json"]).optional().describe("Output format (default: text)")
|
|
@@ -27273,7 +28041,7 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
27273
28041
|
}).join(`
|
|
27274
28042
|
`);
|
|
27275
28043
|
};
|
|
27276
|
-
const { getOrgChart: getOrgChart2 } = (()
|
|
28044
|
+
const { getOrgChart: getOrgChart2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
27277
28045
|
const tree = getOrgChart2();
|
|
27278
28046
|
if (format === "json") {
|
|
27279
28047
|
return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
|
|
@@ -27293,10 +28061,10 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
27293
28061
|
reports_to: exports_external2.string().describe("Manager agent ID or name")
|
|
27294
28062
|
}, async ({ agent_id, reports_to }) => {
|
|
27295
28063
|
try {
|
|
27296
|
-
const {
|
|
28064
|
+
const { updateAgent: updateAgent2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
27297
28065
|
const resolvedAgent = resolveId(agent_id, "agents");
|
|
27298
28066
|
const resolvedManager = resolveId(reports_to, "agents");
|
|
27299
|
-
|
|
28067
|
+
updateAgent2(resolvedAgent, { reports_to: resolvedManager });
|
|
27300
28068
|
return { content: [{ type: "text", text: `${agent_id} now reports to ${reports_to}` }] };
|
|
27301
28069
|
} catch (e) {
|
|
27302
28070
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -27306,6 +28074,7 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
27306
28074
|
}
|
|
27307
28075
|
var init_task_workflow_tools = __esm(() => {
|
|
27308
28076
|
init_zod2();
|
|
28077
|
+
init_types();
|
|
27309
28078
|
});
|
|
27310
28079
|
|
|
27311
28080
|
// src/mcp/tools/task-auto-tools.ts
|
|
@@ -27317,9 +28086,9 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
27317
28086
|
project_id: exports_external2.string().optional().describe("Scope to a project")
|
|
27318
28087
|
}, async ({ days = 7, project_id }) => {
|
|
27319
28088
|
try {
|
|
27320
|
-
const { archiveCompletedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28089
|
+
const { archiveCompletedTasks: archiveCompletedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27321
28090
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
27322
|
-
const count =
|
|
28091
|
+
const count = archiveCompletedTasks2(days, resolvedProjectId);
|
|
27323
28092
|
return { content: [{ type: "text", text: `Archived ${count} completed task(s) older than ${days} days.` }] };
|
|
27324
28093
|
} catch (e) {
|
|
27325
28094
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -27345,9 +28114,9 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
27345
28114
|
limit: exports_external2.number().optional().describe("Max results (default: 50)")
|
|
27346
28115
|
}, async ({ project_id, limit }) => {
|
|
27347
28116
|
try {
|
|
27348
|
-
const { getArchivedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28117
|
+
const { getArchivedTasks: getArchivedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27349
28118
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
27350
|
-
const tasks =
|
|
28119
|
+
const tasks = getArchivedTasks2({ project_id: resolvedProjectId, limit: limit || 50 });
|
|
27351
28120
|
if (tasks.length === 0)
|
|
27352
28121
|
return { content: [{ type: "text", text: "No archived tasks." }] };
|
|
27353
28122
|
const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} ${t.title} archived ${t.archived_at}`);
|
|
@@ -27363,14 +28132,13 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
27363
28132
|
task_id: exports_external2.string().describe("Task ID")
|
|
27364
28133
|
}, async ({ task_id }) => {
|
|
27365
28134
|
try {
|
|
27366
|
-
const { autoAssign } = (init_agents(), __toCommonJS(exports_agents));
|
|
27367
28135
|
const resolvedId = resolveId(task_id);
|
|
27368
|
-
const
|
|
27369
|
-
|
|
28136
|
+
const { autoAssignTask: autoAssignTask2 } = (init_auto_assign(), __toCommonJS(exports_auto_assign));
|
|
28137
|
+
const assignment = await autoAssignTask2(resolvedId);
|
|
28138
|
+
if (!assignment.assigned_to)
|
|
27370
28139
|
return { content: [{ type: "text", text: "No suitable agent found for this task." }] };
|
|
27371
|
-
const
|
|
27372
|
-
|
|
27373
|
-
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} assigned to ${assignment.agent_name} (score: ${assignment.score.toFixed(2)})` }] };
|
|
28140
|
+
const reason = assignment.reason ? ` \u2014 ${assignment.reason}` : "";
|
|
28141
|
+
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} assigned to ${assignment.agent_name || assignment.assigned_to} via ${assignment.method}${reason}` }] };
|
|
27374
28142
|
} catch (e) {
|
|
27375
28143
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27376
28144
|
}
|
|
@@ -27381,10 +28149,23 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
27381
28149
|
agent_id: exports_external2.string().optional().describe("Agent ID (defaults to context agent)")
|
|
27382
28150
|
}, async ({ agent_id }) => {
|
|
27383
28151
|
try {
|
|
27384
|
-
const {
|
|
28152
|
+
const { listTasks: listTasks3, getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27385
28153
|
const focus = ctx.getAgentFocus(agent_id || "");
|
|
27386
28154
|
const effectiveAgentId = focus ? focus.agent_id : agent_id || "";
|
|
27387
|
-
|
|
28155
|
+
if (!effectiveAgentId) {
|
|
28156
|
+
return { content: [{ type: "text", text: "No agent_id provided and no agent focus is active." }], isError: true };
|
|
28157
|
+
}
|
|
28158
|
+
const assigned = listTasks3({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
|
|
28159
|
+
const now2 = Date.now();
|
|
28160
|
+
const dueSoonCutoff = now2 + 24 * 60 * 60 * 1000;
|
|
28161
|
+
const blocked = getBlockedTasks2().filter((t) => t.assigned_to === effectiveAgentId);
|
|
28162
|
+
const workload = {
|
|
28163
|
+
in_progress: assigned.filter((t) => t.status === "in_progress").length,
|
|
28164
|
+
pending: assigned.filter((t) => t.status === "pending").length,
|
|
28165
|
+
completed_recent: assigned.filter((t) => t.status === "completed" && t.completed_at && now2 - new Date(t.completed_at).getTime() <= 7 * 24 * 60 * 60 * 1000).length,
|
|
28166
|
+
due_soon: assigned.filter((t) => t.due_at && new Date(t.due_at).getTime() <= dueSoonCutoff && new Date(t.due_at).getTime() >= now2 && !["completed", "cancelled", "failed"].includes(t.status)).length,
|
|
28167
|
+
blocked: blocked.length
|
|
28168
|
+
};
|
|
27388
28169
|
const lines = [
|
|
27389
28170
|
`Agent: ${effectiveAgentId}`,
|
|
27390
28171
|
`In Progress: ${workload.in_progress}`,
|
|
@@ -27406,10 +28187,29 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
27406
28187
|
max_per_agent: exports_external2.number().optional().describe("Max tasks per agent (default: 5)")
|
|
27407
28188
|
}, async ({ project_id, max_per_agent }) => {
|
|
27408
28189
|
try {
|
|
27409
|
-
const {
|
|
28190
|
+
const { listAgents: listAgents2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
28191
|
+
const { listTasks: listTasks3, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27410
28192
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
27411
|
-
const
|
|
27412
|
-
|
|
28193
|
+
const limit = max_per_agent || 5;
|
|
28194
|
+
const agents = listAgents2().filter((agent) => agent.status === "active");
|
|
28195
|
+
if (agents.length === 0)
|
|
28196
|
+
return { content: [{ type: "text", text: "No active agents available for rebalancing." }] };
|
|
28197
|
+
const activeTasks = listTasks3({ project_id: resolvedProjectId, status: ["pending", "in_progress"], limit: 1000 }, undefined);
|
|
28198
|
+
const load = new Map(agents.map((agent) => [agent.id, activeTasks.filter((t) => t.assigned_to === agent.id).length]));
|
|
28199
|
+
let moved = 0;
|
|
28200
|
+
let skipped = 0;
|
|
28201
|
+
for (const task of activeTasks.filter((t) => t.status === "pending" && t.assigned_to && (load.get(t.assigned_to) ?? 0) > limit)) {
|
|
28202
|
+
const target = agents.filter((agent) => agent.id !== task.assigned_to).sort((a, b) => (load.get(a.id) ?? 0) - (load.get(b.id) ?? 0))[0];
|
|
28203
|
+
if (!target || (load.get(target.id) ?? 0) >= limit) {
|
|
28204
|
+
skipped++;
|
|
28205
|
+
continue;
|
|
28206
|
+
}
|
|
28207
|
+
updateTask2(task.id, { assigned_to: target.id, version: task.version });
|
|
28208
|
+
load.set(task.assigned_to, (load.get(task.assigned_to) ?? 1) - 1);
|
|
28209
|
+
load.set(target.id, (load.get(target.id) ?? 0) + 1);
|
|
28210
|
+
moved++;
|
|
28211
|
+
}
|
|
28212
|
+
return { content: [{ type: "text", text: `Rebalanced: moved ${moved} task(s), ${skipped} skipped.` }] };
|
|
27413
28213
|
} catch (e) {
|
|
27414
28214
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27415
28215
|
}
|
|
@@ -27422,13 +28222,13 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
27422
28222
|
agent_id: exports_external2.string().optional().describe("Filter by assignee")
|
|
27423
28223
|
}, async ({ hours = 24, project_id, agent_id }) => {
|
|
27424
28224
|
try {
|
|
27425
|
-
const { notifyUpcomingDeadlines } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28225
|
+
const { notifyUpcomingDeadlines: notifyUpcomingDeadlines2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27426
28226
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
27427
28227
|
const resolvedAgentId = agent_id ? resolveId(agent_id, "agents") : undefined;
|
|
27428
|
-
const tasks =
|
|
28228
|
+
const tasks = notifyUpcomingDeadlines2({ hours, project_id: resolvedProjectId, agent_id: resolvedAgentId });
|
|
27429
28229
|
if (tasks.length === 0)
|
|
27430
28230
|
return { content: [{ type: "text", text: "No deadlines approaching." }] };
|
|
27431
|
-
const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} ${t.title} due ${t.
|
|
28231
|
+
const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} ${t.title} due ${t.due_at}`);
|
|
27432
28232
|
return { content: [{ type: "text", text: `${tasks.length} task(s) due within ${hours}h:
|
|
27433
28233
|
${lines.join(`
|
|
27434
28234
|
`)}` }] };
|
|
@@ -27462,9 +28262,9 @@ ${lines.join(`
|
|
|
27462
28262
|
project_id: exports_external2.string().optional().describe("Filter by project")
|
|
27463
28263
|
}, async ({ project_id }) => {
|
|
27464
28264
|
try {
|
|
27465
|
-
const { getBlockedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28265
|
+
const { getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27466
28266
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
27467
|
-
const tasks =
|
|
28267
|
+
const tasks = getBlockedTasks2(resolvedProjectId);
|
|
27468
28268
|
if (tasks.length === 0)
|
|
27469
28269
|
return { content: [{ type: "text", text: "No blocked tasks." }] };
|
|
27470
28270
|
const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} [${t.status}] ${t.title} \u2014 blocked by ${(t.blocked_by || []).map((b) => b.slice(0, 8)).join(", ")}`);
|
|
@@ -27481,9 +28281,9 @@ ${lines.join(`
|
|
|
27481
28281
|
project_id: exports_external2.string().optional().describe("Filter by project")
|
|
27482
28282
|
}, async ({ project_id }) => {
|
|
27483
28283
|
try {
|
|
27484
|
-
const { getBlockingTasks } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28284
|
+
const { getBlockingTasks: getBlockingTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27485
28285
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
27486
|
-
const tasks =
|
|
28286
|
+
const tasks = getBlockingTasks2(resolvedProjectId);
|
|
27487
28287
|
if (tasks.length === 0)
|
|
27488
28288
|
return { content: [{ type: "text", text: "No tasks blocking others." }] };
|
|
27489
28289
|
const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} [${t.status}] ${t.title} \u2014 blocking ${t.blocking_count} task(s)`);
|
|
@@ -27498,20 +28298,18 @@ ${lines.join(`
|
|
|
27498
28298
|
if (shouldRegisterTool("get_health")) {
|
|
27499
28299
|
server.tool("get_health", "Get system health: task counts by status, active agents, project summary.", async () => {
|
|
27500
28300
|
try {
|
|
27501
|
-
const {
|
|
27502
|
-
const { listProjects: listProjects2 } = (
|
|
28301
|
+
const { countTasks: countTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28302
|
+
const { listProjects: listProjects2 } = (init_projects(), __toCommonJS(exports_projects));
|
|
27503
28303
|
const { listAgents: listAgents2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
27504
|
-
const
|
|
27505
|
-
|
|
27506
|
-
|
|
27507
|
-
|
|
27508
|
-
|
|
27509
|
-
|
|
27510
|
-
const projects = listProjects2({ limit: 100 });
|
|
27511
|
-
const agents = listAgents2({ limit: 100 });
|
|
28304
|
+
const pending = countTasks2({ status: "pending" });
|
|
28305
|
+
const inProgress = countTasks2({ status: "in_progress" });
|
|
28306
|
+
const completed = countTasks2({ status: "completed" });
|
|
28307
|
+
const cancelled = countTasks2({ status: "cancelled" });
|
|
28308
|
+
const projects = listProjects2();
|
|
28309
|
+
const agents = listAgents2();
|
|
27512
28310
|
const lines = [
|
|
27513
28311
|
`=== System Health ===`,
|
|
27514
|
-
`Tasks: ${pending
|
|
28312
|
+
`Tasks: ${pending} pending | ${inProgress} in progress | ${completed} completed | ${cancelled} cancelled`,
|
|
27515
28313
|
`Projects: ${projects.length} total`,
|
|
27516
28314
|
`Agents: ${agents.length} registered`
|
|
27517
28315
|
];
|
|
@@ -27657,17 +28455,31 @@ var init_task_relationships = __esm(() => {
|
|
|
27657
28455
|
function registerTaskAdvTools(server, ctx) {
|
|
27658
28456
|
const { shouldRegisterTool, resolveId, formatError, formatTask, formatTaskDetail } = ctx;
|
|
27659
28457
|
if (shouldRegisterTool("get_status")) {
|
|
27660
|
-
server.tool("get_status", "Get
|
|
27661
|
-
task_id: exports_external2.string().describe("Task ID")
|
|
27662
|
-
|
|
28458
|
+
server.tool("get_status", "Get queue status summary, or pass task_id for a task's detailed status.", {
|
|
28459
|
+
task_id: exports_external2.string().optional().describe("Task ID for task-specific status"),
|
|
28460
|
+
project_id: exports_external2.string().optional().describe("Filter summary by project"),
|
|
28461
|
+
task_list_id: exports_external2.string().optional().describe("Filter summary by task list"),
|
|
28462
|
+
agent_id: exports_external2.string().optional().describe("Agent for next-task affinity"),
|
|
28463
|
+
explain_blocked: exports_external2.boolean().optional().describe("Include blocked task explanations in summary")
|
|
28464
|
+
}, async ({ task_id, project_id, task_list_id, agent_id, explain_blocked }) => {
|
|
27663
28465
|
try {
|
|
28466
|
+
if (!task_id) {
|
|
28467
|
+
const { getStatus: getStatus2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28468
|
+
const filters = {};
|
|
28469
|
+
if (project_id)
|
|
28470
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
28471
|
+
if (task_list_id)
|
|
28472
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
28473
|
+
const status = getStatus2(filters, agent_id, { explain_blocked });
|
|
28474
|
+
return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
|
|
28475
|
+
}
|
|
27664
28476
|
const resolvedId = resolveId(task_id);
|
|
27665
|
-
const { getTask:
|
|
27666
|
-
const task =
|
|
28477
|
+
const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28478
|
+
const task = getTask2(resolvedId);
|
|
27667
28479
|
if (!task)
|
|
27668
28480
|
throw new Error(`Task not found: ${task_id}`);
|
|
27669
28481
|
const { getTaskDependencies: getTaskDependencies3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27670
|
-
const { listComments: listComments2 } = (
|
|
28482
|
+
const { listComments: listComments2 } = (init_comments(), __toCommonJS(exports_comments));
|
|
27671
28483
|
const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
27672
28484
|
const [deps, comments, files] = await Promise.all([
|
|
27673
28485
|
Promise.resolve(getTaskDependencies3(resolvedId, "both")),
|
|
@@ -27677,14 +28489,14 @@ function registerTaskAdvTools(server, ctx) {
|
|
|
27677
28489
|
const lines = [
|
|
27678
28490
|
`Status: ${task.status} | Priority: ${task.priority}`,
|
|
27679
28491
|
task.assigned_to ? `Assigned: ${task.assigned_to}` : "Unassigned",
|
|
27680
|
-
task.
|
|
28492
|
+
task.due_at ? `Due: ${task.due_at}` : null,
|
|
27681
28493
|
task.confidence != null ? `Confidence: ${task.confidence}` : null,
|
|
27682
28494
|
deps.length > 0 ? `
|
|
27683
28495
|
Dependencies (${deps.length}):` : null,
|
|
27684
28496
|
...deps.map((d) => ` [${d.direction}] ${d.task_id.slice(0, 8)} (${d.status})`),
|
|
27685
28497
|
comments.length > 0 ? `
|
|
27686
28498
|
Comments (${comments.length}):` : null,
|
|
27687
|
-
...comments.map((c) => ` [${c.
|
|
28499
|
+
...comments.map((c) => ` [${c.agent_id || "?"}] ${c.created_at?.slice(0, 16)}: ${c.content.slice(0, 80)}`),
|
|
27688
28500
|
files.length > 0 ? `
|
|
27689
28501
|
Files (${files.length}):` : null,
|
|
27690
28502
|
...files.map((f) => ` ${f.status} ${f.path}`)
|
|
@@ -27702,15 +28514,15 @@ Files (${files.length}):` : null,
|
|
|
27702
28514
|
}, async ({ task_id }) => {
|
|
27703
28515
|
try {
|
|
27704
28516
|
const resolvedId = resolveId(task_id);
|
|
27705
|
-
const { getTask:
|
|
27706
|
-
const task =
|
|
28517
|
+
const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28518
|
+
const task = getTask2(resolvedId);
|
|
27707
28519
|
if (!task)
|
|
27708
28520
|
throw new Error(`Task not found: ${task_id}`);
|
|
27709
28521
|
const { getTaskDependencies: getTaskDependencies3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27710
28522
|
const { getTaskRelationships: getTaskRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
|
|
27711
|
-
const { listComments: listComments2 } = (
|
|
28523
|
+
const { listComments: listComments2 } = (init_comments(), __toCommonJS(exports_comments));
|
|
27712
28524
|
const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
27713
|
-
const { getTaskCommits: getTaskCommits2 } = (
|
|
28525
|
+
const { getTaskCommits: getTaskCommits2 } = (init_task_commits(), __toCommonJS(exports_task_commits));
|
|
27714
28526
|
const { getTaskWatchers: getTaskWatchers2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27715
28527
|
const [deps, rels, comments, files, commits, watchers] = await Promise.all([
|
|
27716
28528
|
Promise.resolve(getTaskDependencies3(resolvedId, "both")),
|
|
@@ -27731,7 +28543,7 @@ Files (${files.length}):` : null,
|
|
|
27731
28543
|
task.tags?.length ? `Tags: ${task.tags.join(", ")}` : null,
|
|
27732
28544
|
task.created_at ? `Created: ${task.created_at}` : null,
|
|
27733
28545
|
task.updated_at ? `Updated: ${task.updated_at}` : null,
|
|
27734
|
-
task.
|
|
28546
|
+
task.due_at ? `Due: ${task.due_at}` : null,
|
|
27735
28547
|
task.completed_at ? `Completed: ${task.completed_at}` : null,
|
|
27736
28548
|
deps.length > 0 ? `
|
|
27737
28549
|
--- Dependencies (${deps.length}) ---` : null,
|
|
@@ -27741,7 +28553,7 @@ Files (${files.length}):` : null,
|
|
|
27741
28553
|
...rels.map((r) => ` ${r.source_task_id.slice(0, 8)} --[${r.relationship_type}]--> ${r.target_task_id.slice(0, 8)}`),
|
|
27742
28554
|
comments.length > 0 ? `
|
|
27743
28555
|
--- Comments (${comments.length}) ---` : null,
|
|
27744
|
-
...comments.map((c) => ` [${c.
|
|
28556
|
+
...comments.map((c) => ` [${c.agent_id || "?"}] ${c.created_at?.slice(0, 16)}: ${c.content.slice(0, 120)}`),
|
|
27745
28557
|
files.length > 0 ? `
|
|
27746
28558
|
--- Files (${files.length}) ---` : null,
|
|
27747
28559
|
...files.map((f) => ` [${f.status}] ${f.path}`),
|
|
@@ -27790,8 +28602,8 @@ ${JSON.stringify(task.metadata, null, 2)}` : null
|
|
|
27790
28602
|
limit: 20
|
|
27791
28603
|
}, undefined);
|
|
27792
28604
|
const completedYesterday = completed.filter((t) => t.completed_at && t.completed_at.startsWith(yesterdayStr));
|
|
27793
|
-
const { getBlockedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27794
|
-
const blocked =
|
|
28605
|
+
const { getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28606
|
+
const blocked = getBlockedTasks2(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => t.assigned_to === effectiveAgentId);
|
|
27795
28607
|
const lines = [
|
|
27796
28608
|
`Standup for ${effectiveAgentId} (${effectiveProjectId ? `project: ${effectiveProjectId.slice(0, 8)}` : "all projects"})`,
|
|
27797
28609
|
inProgress.length > 0 ? `
|
|
@@ -27820,11 +28632,11 @@ No blocked tasks.`,
|
|
|
27820
28632
|
agent_id: exports_external2.string().optional().describe("Agent claiming (defaults to context)")
|
|
27821
28633
|
}, async ({ task_id, agent_id }) => {
|
|
27822
28634
|
try {
|
|
27823
|
-
const {
|
|
28635
|
+
const { startTask: startTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27824
28636
|
const resolvedId = resolveId(task_id);
|
|
27825
28637
|
const focus = ctx.getAgentFocus(agent_id || "");
|
|
27826
|
-
const effectiveAgent = focus ? focus.agent_id : agent_id || "";
|
|
27827
|
-
const task =
|
|
28638
|
+
const effectiveAgent = focus ? focus.agent_id : agent_id || "mcp";
|
|
28639
|
+
const task = startTask2(resolvedId, effectiveAgent);
|
|
27828
28640
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
27829
28641
|
} catch (e) {
|
|
27830
28642
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -27836,8 +28648,12 @@ No blocked tasks.`,
|
|
|
27836
28648
|
task_id: exports_external2.string().describe("Task ID")
|
|
27837
28649
|
}, async ({ task_id }) => {
|
|
27838
28650
|
try {
|
|
27839
|
-
const { updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27840
|
-
const
|
|
28651
|
+
const { getTask: getTask2, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28652
|
+
const resolvedId = resolveId(task_id);
|
|
28653
|
+
const current = getTask2(resolvedId);
|
|
28654
|
+
if (!current)
|
|
28655
|
+
throw new Error(`Task not found: ${task_id}`);
|
|
28656
|
+
const task = updateTask2(resolvedId, { status: "pending", assigned_to: null, version: current.version });
|
|
27841
28657
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
27842
28658
|
} catch (e) {
|
|
27843
28659
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -27850,13 +28666,13 @@ No blocked tasks.`,
|
|
|
27850
28666
|
minutes: exports_external2.number().describe("Additional minutes to add to estimate")
|
|
27851
28667
|
}, async ({ task_id, minutes }) => {
|
|
27852
28668
|
try {
|
|
27853
|
-
const { getTask:
|
|
28669
|
+
const { getTask: getTask2, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27854
28670
|
const resolvedId = resolveId(task_id);
|
|
27855
|
-
const task =
|
|
28671
|
+
const task = getTask2(resolvedId);
|
|
27856
28672
|
if (!task)
|
|
27857
28673
|
throw new Error(`Task not found: ${task_id}`);
|
|
27858
28674
|
const currentEstimate = task.estimated_minutes || 0;
|
|
27859
|
-
const updated = updateTask2(resolvedId, { estimated_minutes: currentEstimate + minutes });
|
|
28675
|
+
const updated = updateTask2(resolvedId, { estimated_minutes: currentEstimate + minutes, version: task.version });
|
|
27860
28676
|
return { content: [{ type: "text", text: `Estimate updated: ${currentEstimate} \u2192 ${updated.estimated_minutes} min (+${minutes})` }] };
|
|
27861
28677
|
} catch (e) {
|
|
27862
28678
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -27870,10 +28686,10 @@ No blocked tasks.`,
|
|
|
27870
28686
|
author: exports_external2.string().optional().describe("Author agent ID or name")
|
|
27871
28687
|
}, async ({ task_id, body, author }) => {
|
|
27872
28688
|
try {
|
|
27873
|
-
const {
|
|
28689
|
+
const { addComment: addComment2 } = (init_comments(), __toCommonJS(exports_comments));
|
|
27874
28690
|
const resolvedId = resolveId(task_id);
|
|
27875
28691
|
const resolvedAuthor = author ? resolveId(author, "agents") : undefined;
|
|
27876
|
-
|
|
28692
|
+
addComment2({ task_id: resolvedId, content: body, agent_id: resolvedAuthor });
|
|
27877
28693
|
return { content: [{ type: "text", text: `Comment added to ${task_id.slice(0, 8)}` }] };
|
|
27878
28694
|
} catch (e) {
|
|
27879
28695
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -27885,11 +28701,11 @@ No blocked tasks.`,
|
|
|
27885
28701
|
task_id: exports_external2.string().describe("Task ID")
|
|
27886
28702
|
}, async ({ task_id }) => {
|
|
27887
28703
|
try {
|
|
27888
|
-
const { listComments: listComments2 } = (
|
|
28704
|
+
const { listComments: listComments2 } = (init_comments(), __toCommonJS(exports_comments));
|
|
27889
28705
|
const comments = listComments2(resolveId(task_id));
|
|
27890
28706
|
if (comments.length === 0)
|
|
27891
28707
|
return { content: [{ type: "text", text: "No comments." }] };
|
|
27892
|
-
const lines = comments.map((c) => `[${c.
|
|
28708
|
+
const lines = comments.map((c) => `[${c.agent_id || "?"}] ${c.created_at?.slice(0, 16)}: ${c.content}`);
|
|
27893
28709
|
return { content: [{ type: "text", text: lines.join(`
|
|
27894
28710
|
|
|
27895
28711
|
`) }] };
|
|
@@ -27901,7 +28717,7 @@ No blocked tasks.`,
|
|
|
27901
28717
|
if (shouldRegisterTool("list_my_tasks")) {
|
|
27902
28718
|
server.tool("list_my_tasks", "Alias for get_my_tasks.", {
|
|
27903
28719
|
agent_id: exports_external2.string().optional().describe("Agent ID (defaults to context agent)"),
|
|
27904
|
-
status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
|
|
28720
|
+
status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
27905
28721
|
project_id: exports_external2.string().optional().describe("Filter by project"),
|
|
27906
28722
|
limit: exports_external2.number().optional()
|
|
27907
28723
|
}, async ({ agent_id, status, project_id, limit }) => {
|
|
@@ -27926,126 +28742,6 @@ No blocked tasks.`,
|
|
|
27926
28742
|
}
|
|
27927
28743
|
});
|
|
27928
28744
|
}
|
|
27929
|
-
if (shouldRegisterTool("list_agents")) {
|
|
27930
|
-
server.tool("list_agents", "List all registered agents.", {
|
|
27931
|
-
project_id: exports_external2.string().optional().describe("Filter by project"),
|
|
27932
|
-
role: exports_external2.string().optional().describe("Filter by global role"),
|
|
27933
|
-
capabilities: exports_external2.array(exports_external2.string()).optional().describe("Filter by capabilities")
|
|
27934
|
-
}, async ({ project_id, role, capabilities }) => {
|
|
27935
|
-
try {
|
|
27936
|
-
const { listAgents: listAgents2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
27937
|
-
const resolved = {};
|
|
27938
|
-
if (project_id)
|
|
27939
|
-
resolved.project_id = resolveId(project_id, "projects");
|
|
27940
|
-
if (role)
|
|
27941
|
-
resolved.role = role;
|
|
27942
|
-
if (capabilities)
|
|
27943
|
-
resolved.capabilities = capabilities;
|
|
27944
|
-
const agents = listAgents2(resolved);
|
|
27945
|
-
if (agents.length === 0)
|
|
27946
|
-
return { content: [{ type: "text", text: "No agents found." }] };
|
|
27947
|
-
const lines = agents.map((a) => `\u25CF ${a.name} [${a.role || "no role"}]${a.capabilities?.length ? ` caps:[${a.capabilities.join(",")}]` : ""}`);
|
|
27948
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
27949
|
-
`) }] };
|
|
27950
|
-
} catch (e) {
|
|
27951
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27952
|
-
}
|
|
27953
|
-
});
|
|
27954
|
-
}
|
|
27955
|
-
if (shouldRegisterTool("get_agent")) {
|
|
27956
|
-
server.tool("get_agent", "Get full details for an agent.", {
|
|
27957
|
-
agent_id: exports_external2.string().describe("Agent ID or name")
|
|
27958
|
-
}, async ({ agent_id }) => {
|
|
27959
|
-
try {
|
|
27960
|
-
const { getAgentByName: getAgentByName2, getAgent: getAgent2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
27961
|
-
const resolvedId = resolveId(agent_id, "agents");
|
|
27962
|
-
const agent = agent_id.includes("@") || !resolvedId.startsWith("agent_") ? getAgentByName2(agent_id) : getAgent2(resolvedId);
|
|
27963
|
-
if (!agent)
|
|
27964
|
-
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
27965
|
-
const lines = [
|
|
27966
|
-
`ID: ${agent.id}`,
|
|
27967
|
-
`Name: ${agent.name}`,
|
|
27968
|
-
agent.email ? `Email: ${agent.email}` : null,
|
|
27969
|
-
agent.title ? `Title: ${agent.title}` : null,
|
|
27970
|
-
agent.role ? `Role: ${agent.role}` : null,
|
|
27971
|
-
agent.capabilities?.length ? `Capabilities: ${agent.capabilities.join(", ")}` : null,
|
|
27972
|
-
agent.last_seen_at ? `Last Seen: ${agent.last_seen_at}` : null
|
|
27973
|
-
].filter(Boolean);
|
|
27974
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
27975
|
-
`) }] };
|
|
27976
|
-
} catch (e) {
|
|
27977
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27978
|
-
}
|
|
27979
|
-
});
|
|
27980
|
-
}
|
|
27981
|
-
if (shouldRegisterTool("update_agent")) {
|
|
27982
|
-
server.tool("update_agent", "Update an agent's fields.", {
|
|
27983
|
-
agent_id: exports_external2.string().describe("Agent ID or name"),
|
|
27984
|
-
name: exports_external2.string().optional(),
|
|
27985
|
-
email: exports_external2.string().optional(),
|
|
27986
|
-
title: exports_external2.string().optional(),
|
|
27987
|
-
role: exports_external2.string().optional(),
|
|
27988
|
-
capabilities: exports_external2.array(exports_external2.string()).optional()
|
|
27989
|
-
}, async ({ agent_id, ...updates }) => {
|
|
27990
|
-
try {
|
|
27991
|
-
const { getAgentByName: getAgentByName2, updateAgent: updateAgent2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
27992
|
-
const resolvedId = resolveId(agent_id, "agents");
|
|
27993
|
-
const agent = agent_id.includes("@") || !resolvedId.startsWith("agent_") ? getAgentByName2(agent_id) : { id: resolvedId };
|
|
27994
|
-
updateAgent2(agent.id, updates);
|
|
27995
|
-
return { content: [{ type: "text", text: `Agent ${agent.id.slice(0, 8)} updated.` }] };
|
|
27996
|
-
} catch (e) {
|
|
27997
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27998
|
-
}
|
|
27999
|
-
});
|
|
28000
|
-
}
|
|
28001
|
-
if (shouldRegisterTool("delete_agent")) {
|
|
28002
|
-
server.tool("delete_agent", "Deregister an agent.", {
|
|
28003
|
-
agent_id: exports_external2.string().describe("Agent ID or name")
|
|
28004
|
-
}, async ({ agent_id }) => {
|
|
28005
|
-
try {
|
|
28006
|
-
const { getAgentByName: getAgentByName2, deleteAgent: deleteAgent2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
28007
|
-
const resolvedId = resolveId(agent_id, "agents");
|
|
28008
|
-
const agent = agent_id.includes("@") || !resolvedId.startsWith("agent_") ? getAgentByName2(agent_id) : { id: resolvedId };
|
|
28009
|
-
if (!agent)
|
|
28010
|
-
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
28011
|
-
deleteAgent2(agent.id);
|
|
28012
|
-
return { content: [{ type: "text", text: `Agent ${agent_id} deleted.` }] };
|
|
28013
|
-
} catch (e) {
|
|
28014
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
28015
|
-
}
|
|
28016
|
-
});
|
|
28017
|
-
}
|
|
28018
|
-
if (shouldRegisterTool("register_agent")) {
|
|
28019
|
-
server.tool("register_agent", "Register a new agent.", {
|
|
28020
|
-
name: exports_external2.string().describe("Agent name"),
|
|
28021
|
-
email: exports_external2.string().optional(),
|
|
28022
|
-
title: exports_external2.string().optional(),
|
|
28023
|
-
role: exports_external2.string().optional(),
|
|
28024
|
-
capabilities: exports_external2.array(exports_external2.string()).optional()
|
|
28025
|
-
}, async ({ name, email, title, role, capabilities }) => {
|
|
28026
|
-
try {
|
|
28027
|
-
const { registerAgent: registerAgent2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
28028
|
-
const agent = registerAgent2({ name, email, title, role, capabilities });
|
|
28029
|
-
return { content: [{ type: "text", text: `Agent registered: ${agent.name} (${agent.id})` }] };
|
|
28030
|
-
} catch (e) {
|
|
28031
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
28032
|
-
}
|
|
28033
|
-
});
|
|
28034
|
-
}
|
|
28035
|
-
if (shouldRegisterTool("heartbeat")) {
|
|
28036
|
-
server.tool("heartbeat", "Agent heartbeat \u2014 updates last_seen_at timestamp.", {
|
|
28037
|
-
agent_id: exports_external2.string().describe("Agent ID"),
|
|
28038
|
-
status: exports_external2.string().optional().describe("Current status message")
|
|
28039
|
-
}, async ({ agent_id, status }) => {
|
|
28040
|
-
try {
|
|
28041
|
-
const { heartbeat } = (init_agents(), __toCommonJS(exports_agents));
|
|
28042
|
-
heartbeat(resolveId(agent_id, "agents"), status);
|
|
28043
|
-
return { content: [{ type: "text", text: "Heartbeat received." }] };
|
|
28044
|
-
} catch (e) {
|
|
28045
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
28046
|
-
}
|
|
28047
|
-
});
|
|
28048
|
-
}
|
|
28049
28745
|
}
|
|
28050
28746
|
var init_task_adv_tools = __esm(() => {
|
|
28051
28747
|
init_zod2();
|
|
@@ -28090,6 +28786,11 @@ function registerTaskMetaTools(server, ctx) {
|
|
|
28090
28786
|
prioritize_task: "prioritize_task \u2014 Set priority. Params: task_id, priority, version",
|
|
28091
28787
|
search_tasks: "search_tasks \u2014 Full-text search. Params: query, project_id, status, limit",
|
|
28092
28788
|
get_my_tasks: "get_my_tasks \u2014 Get tasks for calling agent. Params: agent_id, status, project_id, limit",
|
|
28789
|
+
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",
|
|
28790
|
+
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",
|
|
28791
|
+
get_tasks_changed_since: "get_tasks_changed_since \u2014 List tasks changed since an ISO timestamp. Params: since, project_id, task_list_id, limit",
|
|
28792
|
+
get_context: "get_context \u2014 Get session start context. Params: agent_id, project_id, task_list_id, explain_blocked",
|
|
28793
|
+
bootstrap: "bootstrap \u2014 Bootstrap an agent session with queue context. Params: agent_id, project_id, task_list_id, explain_blocked",
|
|
28093
28794
|
standup: "standup \u2014 Get standup report. Params: agent_id, project_id",
|
|
28094
28795
|
patrol_tasks: "patrol_tasks \u2014 Scan for task issues. Params: stuck_minutes, confidence_threshold, project_id",
|
|
28095
28796
|
get_review_queue: "get_review_queue \u2014 Get tasks needing review. Params: project_id, limit",
|
|
@@ -28147,9 +28848,20 @@ function registerTaskMetaTools(server, ctx) {
|
|
|
28147
28848
|
get_health: "get_health \u2014 Get system health stats",
|
|
28148
28849
|
approve_task: "approve_task \u2014 Approve a task. Params: task_id, approved_by, notes, version",
|
|
28149
28850
|
fail_task: "fail_task \u2014 Mark task failed. Params: task_id, reason, agent_id, version",
|
|
28851
|
+
register_agent: "register_agent \u2014 Register an agent. Params: name, description, role, title, capabilities, session_id, working_dir, force",
|
|
28852
|
+
list_agents: "list_agents \u2014 List registered agents. Params: include_archived",
|
|
28853
|
+
get_agent: "get_agent \u2014 Get agent details. Params: agent_id, id, name",
|
|
28854
|
+
update_agent: "update_agent \u2014 Update an agent. Params: agent_id, id, name, description, role, title, level, capabilities, permissions, metadata",
|
|
28855
|
+
delete_agent: "delete_agent \u2014 Archive an agent. Params: agent_id, id, name",
|
|
28856
|
+
unarchive_agent: "unarchive_agent \u2014 Restore an archived agent. Params: agent_id, id, name",
|
|
28857
|
+
heartbeat: "heartbeat \u2014 Update an agent heartbeat. Params: agent_id",
|
|
28858
|
+
release_agent: "release_agent \u2014 Release an agent session/name. Params: agent_id, session_id",
|
|
28859
|
+
set_focus: "set_focus \u2014 Focus an agent on a project. Params: agent_id, project_id, task_list_id",
|
|
28860
|
+
get_focus: "get_focus \u2014 Get current agent focus. Params: agent_id",
|
|
28861
|
+
unfocus: "unfocus \u2014 Clear agent focus. Params: agent_id",
|
|
28862
|
+
suggest_agent_name: "suggest_agent_name \u2014 Suggest available agent names. Params: working_dir",
|
|
28150
28863
|
get_org_chart: "get_org_chart \u2014 Get global org chart. Params: format",
|
|
28151
28864
|
set_reports_to: "set_reports_to \u2014 Set org hierarchy. Params: agent_id, reports_to",
|
|
28152
|
-
sync: "sync \u2014 Sync from external source. Params: source, source_id, project_id, options",
|
|
28153
28865
|
extract_todos: "extract_todos \u2014 Scan code for TODO comments. Params: path, project_id, task_list_id, patterns, tags, assigned_to, agent_id, dry_run, extensions",
|
|
28154
28866
|
migrate_pg: "migrate_pg \u2014 Apply PostgreSQL migrations. Params: connection_string"
|
|
28155
28867
|
};
|
|
@@ -28167,6 +28879,74 @@ var init_task_meta_tools = __esm(() => {
|
|
|
28167
28879
|
init_zod2();
|
|
28168
28880
|
});
|
|
28169
28881
|
|
|
28882
|
+
// src/db/file-locks.ts
|
|
28883
|
+
var exports_file_locks = {};
|
|
28884
|
+
__export(exports_file_locks, {
|
|
28885
|
+
unlockFile: () => unlockFile,
|
|
28886
|
+
lockFile: () => lockFile,
|
|
28887
|
+
listFileLocks: () => listFileLocks,
|
|
28888
|
+
forceUnlockFile: () => forceUnlockFile,
|
|
28889
|
+
cleanExpiredFileLocks: () => cleanExpiredFileLocks,
|
|
28890
|
+
checkFileLock: () => checkFileLock,
|
|
28891
|
+
FILE_LOCK_DEFAULT_TTL_SECONDS: () => FILE_LOCK_DEFAULT_TTL_SECONDS
|
|
28892
|
+
});
|
|
28893
|
+
function expiresAt(ttlSeconds) {
|
|
28894
|
+
return new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
28895
|
+
}
|
|
28896
|
+
function cleanExpiredFileLocks(db) {
|
|
28897
|
+
const d = db || getDatabase();
|
|
28898
|
+
const result = d.run("DELETE FROM file_locks WHERE expires_at <= ?", [now()]);
|
|
28899
|
+
return result.changes;
|
|
28900
|
+
}
|
|
28901
|
+
function lockFile(input, db) {
|
|
28902
|
+
const d = db || getDatabase();
|
|
28903
|
+
const ttl = input.ttl_seconds ?? FILE_LOCK_DEFAULT_TTL_SECONDS;
|
|
28904
|
+
const expiry = expiresAt(ttl);
|
|
28905
|
+
const timestamp = now();
|
|
28906
|
+
cleanExpiredFileLocks(d);
|
|
28907
|
+
const existing = d.query("SELECT * FROM file_locks WHERE path = ?").get(input.path);
|
|
28908
|
+
if (existing) {
|
|
28909
|
+
if (existing.agent_id === input.agent_id) {
|
|
28910
|
+
d.run("UPDATE file_locks SET expires_at = ?, task_id = COALESCE(?, task_id) WHERE id = ?", [expiry, input.task_id ?? null, existing.id]);
|
|
28911
|
+
return d.query("SELECT * FROM file_locks WHERE id = ?").get(existing.id);
|
|
28912
|
+
}
|
|
28913
|
+
throw new LockError(input.path, existing.agent_id);
|
|
28914
|
+
}
|
|
28915
|
+
const id = uuid();
|
|
28916
|
+
d.run("INSERT INTO file_locks (id, path, agent_id, task_id, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?)", [id, input.path, input.agent_id, input.task_id ?? null, expiry, timestamp]);
|
|
28917
|
+
return d.query("SELECT * FROM file_locks WHERE id = ?").get(id);
|
|
28918
|
+
}
|
|
28919
|
+
function unlockFile(path, agentId, db) {
|
|
28920
|
+
const d = db || getDatabase();
|
|
28921
|
+
cleanExpiredFileLocks(d);
|
|
28922
|
+
const result = d.run("DELETE FROM file_locks WHERE path = ? AND agent_id = ?", [path, agentId]);
|
|
28923
|
+
return result.changes > 0;
|
|
28924
|
+
}
|
|
28925
|
+
function checkFileLock(path, db) {
|
|
28926
|
+
const d = db || getDatabase();
|
|
28927
|
+
cleanExpiredFileLocks(d);
|
|
28928
|
+
return d.query("SELECT * FROM file_locks WHERE path = ?").get(path);
|
|
28929
|
+
}
|
|
28930
|
+
function listFileLocks(agentId, db) {
|
|
28931
|
+
const d = db || getDatabase();
|
|
28932
|
+
cleanExpiredFileLocks(d);
|
|
28933
|
+
if (agentId) {
|
|
28934
|
+
return d.query("SELECT * FROM file_locks WHERE agent_id = ? ORDER BY created_at DESC").all(agentId);
|
|
28935
|
+
}
|
|
28936
|
+
return d.query("SELECT * FROM file_locks ORDER BY created_at DESC").all();
|
|
28937
|
+
}
|
|
28938
|
+
function forceUnlockFile(path, db) {
|
|
28939
|
+
const d = db || getDatabase();
|
|
28940
|
+
const result = d.run("DELETE FROM file_locks WHERE path = ?", [path]);
|
|
28941
|
+
return result.changes > 0;
|
|
28942
|
+
}
|
|
28943
|
+
var FILE_LOCK_DEFAULT_TTL_SECONDS;
|
|
28944
|
+
var init_file_locks = __esm(() => {
|
|
28945
|
+
init_database();
|
|
28946
|
+
init_types();
|
|
28947
|
+
FILE_LOCK_DEFAULT_TTL_SECONDS = 30 * 60;
|
|
28948
|
+
});
|
|
28949
|
+
|
|
28170
28950
|
// src/mcp/tools/task-resources.ts
|
|
28171
28951
|
function registerTaskResources(server, ctx) {
|
|
28172
28952
|
const { shouldRegisterTool, resolveId, formatError } = ctx;
|
|
@@ -28192,7 +28972,7 @@ function registerTaskResources(server, ctx) {
|
|
|
28192
28972
|
note: exports_external2.string().optional().describe("Note about why this file is linked")
|
|
28193
28973
|
}, async ({ task_id, path, paths: multiplePaths, status, agent_id, note }) => {
|
|
28194
28974
|
try {
|
|
28195
|
-
const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2, detectFileConflicts: detectFileConflicts2 } = (()
|
|
28975
|
+
const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2, detectFileConflicts: detectFileConflicts2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
28196
28976
|
const resolvedId = resolveId(task_id);
|
|
28197
28977
|
let addedFiles;
|
|
28198
28978
|
if (multiplePaths && multiplePaths.length > 0) {
|
|
@@ -28236,7 +29016,7 @@ function registerTaskResources(server, ctx) {
|
|
|
28236
29016
|
if (shouldRegisterTool("list_task_files")) {
|
|
28237
29017
|
server.tool("list_task_files", "List all files linked to a task.", { task_id: exports_external2.string().describe("Task ID") }, async ({ task_id }) => {
|
|
28238
29018
|
try {
|
|
28239
|
-
const { listTaskFiles: listTaskFiles2 } = (()
|
|
29019
|
+
const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
28240
29020
|
const resolvedId = resolveId(task_id);
|
|
28241
29021
|
const files = listTaskFiles2(resolvedId);
|
|
28242
29022
|
if (files.length === 0)
|
|
@@ -28253,7 +29033,7 @@ ${lines.join(`
|
|
|
28253
29033
|
if (shouldRegisterTool("find_tasks_by_file")) {
|
|
28254
29034
|
server.tool("find_tasks_by_file", "Find which tasks are linked to a specific file path. Shows who's working on what files.", { path: exports_external2.string().describe("File path to search for") }, async ({ path }) => {
|
|
28255
29035
|
try {
|
|
28256
|
-
const { findTasksByFile: findTasksByFile2 } = (()
|
|
29036
|
+
const { findTasksByFile: findTasksByFile2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
28257
29037
|
const files = findTasksByFile2(path);
|
|
28258
29038
|
if (files.length === 0)
|
|
28259
29039
|
return { content: [{ type: "text", text: `No tasks linked to ${path}` }] };
|
|
@@ -28273,7 +29053,7 @@ ${lines.join(`
|
|
|
28273
29053
|
min_edits: exports_external2.number().optional().describe("Minimum edit count to include (default: 1)")
|
|
28274
29054
|
}, async ({ limit, project_id, min_edits }) => {
|
|
28275
29055
|
try {
|
|
28276
|
-
const { getFileHeatMap: getFileHeatMap2 } = (()
|
|
29056
|
+
const { getFileHeatMap: getFileHeatMap2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
28277
29057
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
28278
29058
|
const results = getFileHeatMap2({ limit, project_id: resolvedProjectId, min_edits });
|
|
28279
29059
|
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
@@ -28287,7 +29067,7 @@ ${lines.join(`
|
|
|
28287
29067
|
paths: exports_external2.array(exports_external2.string()).describe("Array of file paths to check")
|
|
28288
29068
|
}, async ({ paths }) => {
|
|
28289
29069
|
try {
|
|
28290
|
-
const { bulkFindTasksByFiles: bulkFindTasksByFiles2 } = (()
|
|
29070
|
+
const { bulkFindTasksByFiles: bulkFindTasksByFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
28291
29071
|
const results = bulkFindTasksByFiles2(paths);
|
|
28292
29072
|
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
28293
29073
|
} catch (e) {
|
|
@@ -28300,11 +29080,11 @@ ${lines.join(`
|
|
|
28300
29080
|
project_id: exports_external2.string().optional().describe("Filter by project")
|
|
28301
29081
|
}, async ({ project_id }) => {
|
|
28302
29082
|
try {
|
|
28303
|
-
const { listActiveFiles: listActiveFiles2 } = (()
|
|
29083
|
+
const { listActiveFiles: listActiveFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
28304
29084
|
let files = listActiveFiles2();
|
|
28305
29085
|
if (project_id) {
|
|
28306
29086
|
const pid = resolveId(project_id, "projects");
|
|
28307
|
-
const db = (()
|
|
29087
|
+
const db = (init_database(), __toCommonJS(exports_database)).getDatabase();
|
|
28308
29088
|
files = db.query(`
|
|
28309
29089
|
SELECT
|
|
28310
29090
|
tf.path,
|
|
@@ -28386,8 +29166,8 @@ ${lines.join(`
|
|
|
28386
29166
|
ttl_seconds: exports_external2.number().optional().describe("Lock TTL in seconds (default: 1800 = 30 min)")
|
|
28387
29167
|
}, async ({ path, agent_id, task_id, ttl_seconds }) => {
|
|
28388
29168
|
try {
|
|
28389
|
-
const { lockFile } = (()
|
|
28390
|
-
const lock =
|
|
29169
|
+
const { lockFile: lockFile2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
|
|
29170
|
+
const lock = lockFile2({ path, agent_id, task_id, ttl_seconds });
|
|
28391
29171
|
return { content: [{ type: "text", text: JSON.stringify(lock, null, 2) }] };
|
|
28392
29172
|
} catch (e) {
|
|
28393
29173
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -28400,8 +29180,8 @@ ${lines.join(`
|
|
|
28400
29180
|
agent_id: exports_external2.string().describe("Agent releasing the lock (must be the lock holder)")
|
|
28401
29181
|
}, async ({ path, agent_id }) => {
|
|
28402
29182
|
try {
|
|
28403
|
-
const { unlockFile } = (()
|
|
28404
|
-
const released =
|
|
29183
|
+
const { unlockFile: unlockFile2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
|
|
29184
|
+
const released = unlockFile2(path, agent_id);
|
|
28405
29185
|
return { content: [{ type: "text", text: JSON.stringify({ released, path }) }] };
|
|
28406
29186
|
} catch (e) {
|
|
28407
29187
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -28413,8 +29193,8 @@ ${lines.join(`
|
|
|
28413
29193
|
path: exports_external2.string().describe("File path to check")
|
|
28414
29194
|
}, async ({ path }) => {
|
|
28415
29195
|
try {
|
|
28416
|
-
const { checkFileLock } = (()
|
|
28417
|
-
const lock =
|
|
29196
|
+
const { checkFileLock: checkFileLock2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
|
|
29197
|
+
const lock = checkFileLock2(path);
|
|
28418
29198
|
if (!lock)
|
|
28419
29199
|
return { content: [{ type: "text", text: JSON.stringify({ path, locked: false }) }] };
|
|
28420
29200
|
return { content: [{ type: "text", text: JSON.stringify({ path, locked: true, ...lock }) }] };
|
|
@@ -28428,8 +29208,8 @@ ${lines.join(`
|
|
|
28428
29208
|
agent_id: exports_external2.string().optional().describe("Filter locks by agent")
|
|
28429
29209
|
}, async ({ agent_id }) => {
|
|
28430
29210
|
try {
|
|
28431
|
-
const { listFileLocks } = (()
|
|
28432
|
-
const locks =
|
|
29211
|
+
const { listFileLocks: listFileLocks2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
|
|
29212
|
+
const locks = listFileLocks2(agent_id);
|
|
28433
29213
|
return { content: [{ type: "text", text: JSON.stringify(locks, null, 2) }] };
|
|
28434
29214
|
} catch (e) {
|
|
28435
29215
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -28445,49 +29225,576 @@ var init_task_resources = __esm(() => {
|
|
|
28445
29225
|
init_task_commits();
|
|
28446
29226
|
});
|
|
28447
29227
|
|
|
28448
|
-
// src/
|
|
28449
|
-
|
|
28450
|
-
|
|
28451
|
-
|
|
28452
|
-
|
|
28453
|
-
|
|
28454
|
-
|
|
28455
|
-
|
|
28456
|
-
|
|
28457
|
-
|
|
28458
|
-
|
|
28459
|
-
|
|
28460
|
-
|
|
28461
|
-
|
|
28462
|
-
|
|
28463
|
-
|
|
28464
|
-
|
|
28465
|
-
|
|
28466
|
-
|
|
28467
|
-
|
|
28468
|
-
|
|
28469
|
-
|
|
28470
|
-
|
|
28471
|
-
|
|
28472
|
-
|
|
28473
|
-
|
|
28474
|
-
|
|
29228
|
+
// src/db/kg.ts
|
|
29229
|
+
var exports_kg = {};
|
|
29230
|
+
__export(exports_kg, {
|
|
29231
|
+
syncKgEdges: () => syncKgEdges,
|
|
29232
|
+
removeKgEdges: () => removeKgEdges,
|
|
29233
|
+
getRelated: () => getRelated,
|
|
29234
|
+
getImpactAnalysis: () => getImpactAnalysis,
|
|
29235
|
+
getCriticalPath: () => getCriticalPath,
|
|
29236
|
+
findPath: () => findPath,
|
|
29237
|
+
addKgEdge: () => addKgEdge
|
|
29238
|
+
});
|
|
29239
|
+
function rowToEdge(row) {
|
|
29240
|
+
return {
|
|
29241
|
+
...row,
|
|
29242
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
29243
|
+
};
|
|
29244
|
+
}
|
|
29245
|
+
function syncKgEdges(db) {
|
|
29246
|
+
const d = db || getDatabase();
|
|
29247
|
+
let synced = 0;
|
|
29248
|
+
const tx = d.transaction(() => {
|
|
29249
|
+
const deps = d.query("SELECT task_id, depends_on FROM task_dependencies").all();
|
|
29250
|
+
for (const dep of deps) {
|
|
29251
|
+
synced += upsertEdge(d, dep.task_id, "task", dep.depends_on, "task", "depends_on");
|
|
29252
|
+
}
|
|
29253
|
+
const assignments = d.query("SELECT id, assigned_to FROM tasks WHERE assigned_to IS NOT NULL").all();
|
|
29254
|
+
for (const a of assignments) {
|
|
29255
|
+
synced += upsertEdge(d, a.id, "task", a.assigned_to, "agent", "assigned_to");
|
|
29256
|
+
}
|
|
29257
|
+
const agents = d.query("SELECT id, reports_to FROM agents WHERE reports_to IS NOT NULL").all();
|
|
29258
|
+
for (const a of agents) {
|
|
29259
|
+
synced += upsertEdge(d, a.id, "agent", a.reports_to, "agent", "reports_to");
|
|
29260
|
+
}
|
|
29261
|
+
const files = d.query("SELECT task_id, path FROM task_files WHERE status != 'removed'").all();
|
|
29262
|
+
for (const f of files) {
|
|
29263
|
+
synced += upsertEdge(d, f.task_id, "task", f.path, "file", "references_file");
|
|
29264
|
+
}
|
|
29265
|
+
const taskProjects = d.query("SELECT id, project_id FROM tasks WHERE project_id IS NOT NULL").all();
|
|
29266
|
+
for (const tp of taskProjects) {
|
|
29267
|
+
synced += upsertEdge(d, tp.id, "task", tp.project_id, "project", "in_project");
|
|
29268
|
+
}
|
|
29269
|
+
const taskPlans = d.query("SELECT id, plan_id FROM tasks WHERE plan_id IS NOT NULL").all();
|
|
29270
|
+
for (const tp of taskPlans) {
|
|
29271
|
+
synced += upsertEdge(d, tp.id, "task", tp.plan_id, "plan", "in_plan");
|
|
29272
|
+
}
|
|
29273
|
+
try {
|
|
29274
|
+
const rels = d.query("SELECT source_task_id, target_task_id, relationship_type FROM task_relationships").all();
|
|
29275
|
+
for (const r of rels) {
|
|
29276
|
+
synced += upsertEdge(d, r.source_task_id, "task", r.target_task_id, "task", r.relationship_type);
|
|
28475
29277
|
}
|
|
28476
|
-
}
|
|
29278
|
+
} catch {}
|
|
29279
|
+
});
|
|
29280
|
+
tx();
|
|
29281
|
+
return { synced };
|
|
29282
|
+
}
|
|
29283
|
+
function upsertEdge(d, sourceId, sourceType, targetId, targetType, relationType, weight = 1) {
|
|
29284
|
+
try {
|
|
29285
|
+
d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
|
|
29286
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, '{}', ?)`, [uuid(), sourceId, sourceType, targetId, targetType, relationType, weight, now()]);
|
|
29287
|
+
return 1;
|
|
29288
|
+
} catch {
|
|
29289
|
+
return 0;
|
|
28477
29290
|
}
|
|
28478
|
-
|
|
28479
|
-
|
|
28480
|
-
|
|
28481
|
-
|
|
28482
|
-
|
|
28483
|
-
|
|
28484
|
-
|
|
28485
|
-
|
|
28486
|
-
|
|
28487
|
-
|
|
28488
|
-
|
|
28489
|
-
|
|
28490
|
-
|
|
29291
|
+
}
|
|
29292
|
+
function getRelated(entityId, opts, db) {
|
|
29293
|
+
const d = db || getDatabase();
|
|
29294
|
+
const direction = opts?.direction || "both";
|
|
29295
|
+
const conditions = [];
|
|
29296
|
+
const params = [];
|
|
29297
|
+
if (direction === "outgoing" || direction === "both") {
|
|
29298
|
+
conditions.push("source_id = ?");
|
|
29299
|
+
params.push(entityId);
|
|
29300
|
+
}
|
|
29301
|
+
if (direction === "incoming" || direction === "both") {
|
|
29302
|
+
conditions.push("target_id = ?");
|
|
29303
|
+
params.push(entityId);
|
|
29304
|
+
}
|
|
29305
|
+
let sql = `SELECT * FROM kg_edges WHERE (${conditions.join(" OR ")})`;
|
|
29306
|
+
if (opts?.relation_type) {
|
|
29307
|
+
sql += " AND relation_type = ?";
|
|
29308
|
+
params.push(opts.relation_type);
|
|
29309
|
+
}
|
|
29310
|
+
if (opts?.entity_type) {
|
|
29311
|
+
sql += " AND (source_type = ? OR target_type = ?)";
|
|
29312
|
+
params.push(opts.entity_type, opts.entity_type);
|
|
29313
|
+
}
|
|
29314
|
+
sql += " ORDER BY weight DESC, created_at DESC";
|
|
29315
|
+
if (opts?.limit) {
|
|
29316
|
+
sql += " LIMIT ?";
|
|
29317
|
+
params.push(opts.limit);
|
|
29318
|
+
}
|
|
29319
|
+
return d.query(sql).all(...params).map(rowToEdge);
|
|
29320
|
+
}
|
|
29321
|
+
function findPath(sourceId, targetId, opts, db) {
|
|
29322
|
+
const d = db || getDatabase();
|
|
29323
|
+
const maxDepth = opts?.max_depth || 5;
|
|
29324
|
+
const visited = new Set;
|
|
29325
|
+
const queue = [{ id: sourceId, path: [] }];
|
|
29326
|
+
const results = [];
|
|
29327
|
+
visited.add(sourceId);
|
|
29328
|
+
while (queue.length > 0) {
|
|
29329
|
+
const current = queue.shift();
|
|
29330
|
+
if (current.path.length >= maxDepth)
|
|
29331
|
+
continue;
|
|
29332
|
+
let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
|
|
29333
|
+
const params = [current.id];
|
|
29334
|
+
if (opts?.relation_types && opts.relation_types.length > 0) {
|
|
29335
|
+
const placeholders = opts.relation_types.map(() => "?").join(",");
|
|
29336
|
+
sql += ` AND relation_type IN (${placeholders})`;
|
|
29337
|
+
params.push(...opts.relation_types);
|
|
29338
|
+
}
|
|
29339
|
+
const edges = d.query(sql).all(...params).map(rowToEdge);
|
|
29340
|
+
for (const edge of edges) {
|
|
29341
|
+
const nextId = edge.target_id;
|
|
29342
|
+
const newPath = [...current.path, edge];
|
|
29343
|
+
if (nextId === targetId) {
|
|
29344
|
+
results.push(newPath);
|
|
29345
|
+
if (results.length >= 3)
|
|
29346
|
+
return results;
|
|
29347
|
+
continue;
|
|
29348
|
+
}
|
|
29349
|
+
if (!visited.has(nextId)) {
|
|
29350
|
+
visited.add(nextId);
|
|
29351
|
+
queue.push({ id: nextId, path: newPath });
|
|
29352
|
+
}
|
|
29353
|
+
}
|
|
29354
|
+
}
|
|
29355
|
+
return results;
|
|
29356
|
+
}
|
|
29357
|
+
function getImpactAnalysis(entityId, opts, db) {
|
|
29358
|
+
const d = db || getDatabase();
|
|
29359
|
+
const maxDepth = opts?.max_depth || 3;
|
|
29360
|
+
const results = [];
|
|
29361
|
+
const visited = new Set;
|
|
29362
|
+
visited.add(entityId);
|
|
29363
|
+
const queue = [{ id: entityId, depth: 0 }];
|
|
29364
|
+
while (queue.length > 0) {
|
|
29365
|
+
const current = queue.shift();
|
|
29366
|
+
if (current.depth >= maxDepth)
|
|
29367
|
+
continue;
|
|
29368
|
+
let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
|
|
29369
|
+
const params = [current.id];
|
|
29370
|
+
if (opts?.relation_types && opts.relation_types.length > 0) {
|
|
29371
|
+
const placeholders = opts.relation_types.map(() => "?").join(",");
|
|
29372
|
+
sql += ` AND relation_type IN (${placeholders})`;
|
|
29373
|
+
params.push(...opts.relation_types);
|
|
29374
|
+
}
|
|
29375
|
+
const edges = d.query(sql).all(...params).map(rowToEdge);
|
|
29376
|
+
for (const edge of edges) {
|
|
29377
|
+
if (!visited.has(edge.target_id)) {
|
|
29378
|
+
visited.add(edge.target_id);
|
|
29379
|
+
results.push({
|
|
29380
|
+
entity_id: edge.target_id,
|
|
29381
|
+
entity_type: edge.target_type,
|
|
29382
|
+
depth: current.depth + 1,
|
|
29383
|
+
relation: edge.relation_type
|
|
29384
|
+
});
|
|
29385
|
+
queue.push({ id: edge.target_id, depth: current.depth + 1 });
|
|
29386
|
+
}
|
|
29387
|
+
}
|
|
29388
|
+
}
|
|
29389
|
+
return results;
|
|
29390
|
+
}
|
|
29391
|
+
function getCriticalPath(opts, db) {
|
|
29392
|
+
const d = db || getDatabase();
|
|
29393
|
+
let sql = `SELECT source_id, target_id FROM kg_edges WHERE relation_type = 'depends_on'`;
|
|
29394
|
+
const params = [];
|
|
29395
|
+
if (opts?.project_id) {
|
|
29396
|
+
sql += ` AND source_id IN (SELECT id FROM tasks WHERE project_id = ?)`;
|
|
29397
|
+
params.push(opts.project_id);
|
|
29398
|
+
}
|
|
29399
|
+
const edges = d.query(sql).all(...params);
|
|
29400
|
+
const blocks = new Map;
|
|
29401
|
+
for (const e of edges) {
|
|
29402
|
+
if (!blocks.has(e.target_id))
|
|
29403
|
+
blocks.set(e.target_id, new Set);
|
|
29404
|
+
blocks.get(e.target_id).add(e.source_id);
|
|
29405
|
+
}
|
|
29406
|
+
const results = [];
|
|
29407
|
+
for (const [taskId] of blocks) {
|
|
29408
|
+
const visited = new Set;
|
|
29409
|
+
const q = [taskId];
|
|
29410
|
+
let maxDepth = 0;
|
|
29411
|
+
let depth = 0;
|
|
29412
|
+
let levelSize = q.length;
|
|
29413
|
+
while (q.length > 0) {
|
|
29414
|
+
const node = q.shift();
|
|
29415
|
+
levelSize--;
|
|
29416
|
+
const downstream = blocks.get(node);
|
|
29417
|
+
if (downstream) {
|
|
29418
|
+
for (const d2 of downstream) {
|
|
29419
|
+
if (!visited.has(d2)) {
|
|
29420
|
+
visited.add(d2);
|
|
29421
|
+
q.push(d2);
|
|
29422
|
+
}
|
|
29423
|
+
}
|
|
29424
|
+
}
|
|
29425
|
+
if (levelSize === 0) {
|
|
29426
|
+
depth++;
|
|
29427
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
29428
|
+
levelSize = q.length;
|
|
29429
|
+
}
|
|
29430
|
+
}
|
|
29431
|
+
if (visited.size > 0) {
|
|
29432
|
+
results.push({ task_id: taskId, blocking_count: visited.size, depth: maxDepth });
|
|
29433
|
+
}
|
|
29434
|
+
}
|
|
29435
|
+
results.sort((a, b) => b.blocking_count - a.blocking_count);
|
|
29436
|
+
return results.slice(0, opts?.limit || 20);
|
|
29437
|
+
}
|
|
29438
|
+
function addKgEdge(sourceId, sourceType, targetId, targetType, relationType, weight = 1, metadata, db) {
|
|
29439
|
+
const d = db || getDatabase();
|
|
29440
|
+
const id = uuid();
|
|
29441
|
+
const timestamp = now();
|
|
29442
|
+
d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
|
|
29443
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, sourceId, sourceType, targetId, targetType, relationType, weight, JSON.stringify(metadata || {}), timestamp]);
|
|
29444
|
+
return { id, source_id: sourceId, source_type: sourceType, target_id: targetId, target_type: targetType, relation_type: relationType, weight, metadata: metadata || {}, created_at: timestamp };
|
|
29445
|
+
}
|
|
29446
|
+
function removeKgEdges(sourceId, targetId, relationType, db) {
|
|
29447
|
+
const d = db || getDatabase();
|
|
29448
|
+
if (relationType) {
|
|
29449
|
+
return d.run("DELETE FROM kg_edges WHERE source_id = ? AND target_id = ? AND relation_type = ?", [sourceId, targetId, relationType]).changes;
|
|
29450
|
+
}
|
|
29451
|
+
return d.run("DELETE FROM kg_edges WHERE source_id = ? AND target_id = ?", [sourceId, targetId]).changes;
|
|
29452
|
+
}
|
|
29453
|
+
var init_kg = __esm(() => {
|
|
29454
|
+
init_database();
|
|
29455
|
+
});
|
|
29456
|
+
|
|
29457
|
+
// src/db/project-agent-roles.ts
|
|
29458
|
+
var exports_project_agent_roles = {};
|
|
29459
|
+
__export(exports_project_agent_roles, {
|
|
29460
|
+
setProjectAgentRole: () => setProjectAgentRole,
|
|
29461
|
+
removeProjectAgentRole: () => removeProjectAgentRole,
|
|
29462
|
+
listProjectAgentRoles: () => listProjectAgentRoles,
|
|
29463
|
+
getProjectOrgChart: () => getProjectOrgChart,
|
|
29464
|
+
getAgentProjectRoles: () => getAgentProjectRoles
|
|
29465
|
+
});
|
|
29466
|
+
function rowToRole(row) {
|
|
29467
|
+
return { ...row, is_lead: row.is_lead === 1 };
|
|
29468
|
+
}
|
|
29469
|
+
function setProjectAgentRole(projectId, agentId, role, isLead = false, db) {
|
|
29470
|
+
const d = db || getDatabase();
|
|
29471
|
+
const existing = d.query("SELECT * FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?").get(projectId, agentId, role);
|
|
29472
|
+
if (existing) {
|
|
29473
|
+
d.run("UPDATE project_agent_roles SET is_lead = ? WHERE id = ?", [isLead ? 1 : 0, existing.id]);
|
|
29474
|
+
return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(existing.id));
|
|
29475
|
+
}
|
|
29476
|
+
const id = uuid();
|
|
29477
|
+
d.run("INSERT INTO project_agent_roles (id, project_id, agent_id, role, is_lead, created_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, agentId, role, isLead ? 1 : 0, now()]);
|
|
29478
|
+
return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(id));
|
|
29479
|
+
}
|
|
29480
|
+
function removeProjectAgentRole(projectId, agentId, role, db) {
|
|
29481
|
+
const d = db || getDatabase();
|
|
29482
|
+
if (role) {
|
|
29483
|
+
return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?", [projectId, agentId, role]).changes;
|
|
29484
|
+
}
|
|
29485
|
+
return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ?", [projectId, agentId]).changes;
|
|
29486
|
+
}
|
|
29487
|
+
function listProjectAgentRoles(projectId, db) {
|
|
29488
|
+
const d = db || getDatabase();
|
|
29489
|
+
return d.query("SELECT * FROM project_agent_roles WHERE project_id = ? ORDER BY role, created_at").all(projectId).map(rowToRole);
|
|
29490
|
+
}
|
|
29491
|
+
function getAgentProjectRoles(agentId, db) {
|
|
29492
|
+
const d = db || getDatabase();
|
|
29493
|
+
return d.query("SELECT * FROM project_agent_roles WHERE agent_id = ? ORDER BY project_id, role").all(agentId).map(rowToRole);
|
|
29494
|
+
}
|
|
29495
|
+
function getProjectOrgChart(projectId, opts, db) {
|
|
29496
|
+
const d = db || getDatabase();
|
|
29497
|
+
const globalTree = getOrgChart(d);
|
|
29498
|
+
const projectRoles = listProjectAgentRoles(projectId, d);
|
|
29499
|
+
const rolesByAgent = new Map;
|
|
29500
|
+
for (const pr of projectRoles) {
|
|
29501
|
+
if (!rolesByAgent.has(pr.agent_id))
|
|
29502
|
+
rolesByAgent.set(pr.agent_id, { roles: [], isLead: false });
|
|
29503
|
+
const entry = rolesByAgent.get(pr.agent_id);
|
|
29504
|
+
entry.roles.push(pr.role);
|
|
29505
|
+
if (pr.is_lead)
|
|
29506
|
+
entry.isLead = true;
|
|
29507
|
+
}
|
|
29508
|
+
function augmentTree(nodes) {
|
|
29509
|
+
return nodes.map((n) => {
|
|
29510
|
+
const override = rolesByAgent.get(n.agent.id);
|
|
29511
|
+
return {
|
|
29512
|
+
...n,
|
|
29513
|
+
reports: augmentTree(n.reports),
|
|
29514
|
+
project_roles: override?.roles ?? [],
|
|
29515
|
+
is_project_lead: override?.isLead ?? false
|
|
29516
|
+
};
|
|
29517
|
+
}).filter((n) => {
|
|
29518
|
+
if (!opts?.filter_to_project)
|
|
29519
|
+
return true;
|
|
29520
|
+
const hasRole = n.project_roles.length > 0;
|
|
29521
|
+
const hasDescendant = n.reports.length > 0;
|
|
29522
|
+
return hasRole || hasDescendant;
|
|
29523
|
+
});
|
|
29524
|
+
}
|
|
29525
|
+
return augmentTree(globalTree);
|
|
29526
|
+
}
|
|
29527
|
+
var init_project_agent_roles = __esm(() => {
|
|
29528
|
+
init_database();
|
|
29529
|
+
init_agents();
|
|
29530
|
+
});
|
|
29531
|
+
|
|
29532
|
+
// src/db/patrol.ts
|
|
29533
|
+
var exports_patrol = {};
|
|
29534
|
+
__export(exports_patrol, {
|
|
29535
|
+
patrolTasks: () => patrolTasks,
|
|
29536
|
+
getReviewQueue: () => getReviewQueue
|
|
29537
|
+
});
|
|
29538
|
+
function rowToTask3(row) {
|
|
29539
|
+
return {
|
|
29540
|
+
...row,
|
|
29541
|
+
status: row.status,
|
|
29542
|
+
priority: row.priority,
|
|
29543
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
29544
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
29545
|
+
requires_approval: Boolean(row.requires_approval)
|
|
29546
|
+
};
|
|
29547
|
+
}
|
|
29548
|
+
function patrolTasks(opts, db) {
|
|
29549
|
+
const d = db || getDatabase();
|
|
29550
|
+
const stuckMinutes = opts?.stuck_minutes || 60;
|
|
29551
|
+
const confidenceThreshold = opts?.confidence_threshold || 0.5;
|
|
29552
|
+
const issues = [];
|
|
29553
|
+
let projectFilter = "";
|
|
29554
|
+
const projectParams = [];
|
|
29555
|
+
if (opts?.project_id) {
|
|
29556
|
+
projectFilter = " AND project_id = ?";
|
|
29557
|
+
projectParams.push(opts.project_id);
|
|
29558
|
+
}
|
|
29559
|
+
const stuckCutoff = new Date(Date.now() - stuckMinutes * 60 * 1000).toISOString();
|
|
29560
|
+
const stuckRows = d.query(`SELECT * FROM tasks WHERE status = 'in_progress' AND updated_at < ?${projectFilter} ORDER BY updated_at ASC`).all(stuckCutoff, ...projectParams);
|
|
29561
|
+
for (const row of stuckRows) {
|
|
29562
|
+
const task = rowToTask3(row);
|
|
29563
|
+
const minutesStuck = Math.round((Date.now() - new Date(task.updated_at).getTime()) / 60000);
|
|
29564
|
+
issues.push({
|
|
29565
|
+
type: "stuck",
|
|
29566
|
+
task_id: task.id,
|
|
29567
|
+
task_title: task.title,
|
|
29568
|
+
severity: minutesStuck > 480 ? "critical" : minutesStuck > 120 ? "high" : "medium",
|
|
29569
|
+
detail: `In progress for ${minutesStuck} minutes without update`
|
|
29570
|
+
});
|
|
29571
|
+
}
|
|
29572
|
+
const lowConfRows = d.query(`SELECT * FROM tasks WHERE status = 'completed' AND confidence IS NOT NULL AND confidence < ?${projectFilter} ORDER BY confidence ASC`).all(confidenceThreshold, ...projectParams);
|
|
29573
|
+
for (const row of lowConfRows) {
|
|
29574
|
+
const task = rowToTask3(row);
|
|
29575
|
+
issues.push({
|
|
29576
|
+
type: "low_confidence",
|
|
29577
|
+
task_id: task.id,
|
|
29578
|
+
task_title: task.title,
|
|
29579
|
+
severity: (task.confidence ?? 0) < 0.3 ? "high" : "medium",
|
|
29580
|
+
detail: `Completed with confidence ${task.confidence} (threshold: ${confidenceThreshold})`
|
|
29581
|
+
});
|
|
29582
|
+
}
|
|
29583
|
+
const orphanedRows = d.query(`SELECT * FROM tasks WHERE status = 'pending' AND project_id IS NULL AND agent_id IS NULL AND assigned_to IS NULL ORDER BY created_at ASC`).all();
|
|
29584
|
+
for (const row of orphanedRows) {
|
|
29585
|
+
const task = rowToTask3(row);
|
|
29586
|
+
issues.push({
|
|
29587
|
+
type: "orphaned",
|
|
29588
|
+
task_id: task.id,
|
|
29589
|
+
task_title: task.title,
|
|
29590
|
+
severity: "low",
|
|
29591
|
+
detail: "Pending task with no project, no agent, and no assignee"
|
|
29592
|
+
});
|
|
29593
|
+
}
|
|
29594
|
+
const needsReviewRows = d.query(`SELECT * FROM tasks WHERE status = 'completed' AND requires_approval = 1 AND approved_by IS NULL${projectFilter} ORDER BY completed_at DESC`).all(...projectParams);
|
|
29595
|
+
for (const row of needsReviewRows) {
|
|
29596
|
+
const task = rowToTask3(row);
|
|
29597
|
+
issues.push({
|
|
29598
|
+
type: "needs_review",
|
|
29599
|
+
task_id: task.id,
|
|
29600
|
+
task_title: task.title,
|
|
29601
|
+
severity: "medium",
|
|
29602
|
+
detail: "Completed but requires approval \u2014 not yet reviewed"
|
|
29603
|
+
});
|
|
29604
|
+
}
|
|
29605
|
+
const pendingWithDeps = d.query(`SELECT t.* FROM tasks t
|
|
29606
|
+
WHERE t.status = 'pending'${projectFilter}
|
|
29607
|
+
AND t.id IN (SELECT task_id FROM task_dependencies)`).all(...projectParams);
|
|
29608
|
+
for (const row of pendingWithDeps) {
|
|
29609
|
+
const deps = d.query(`SELECT d.depends_on, t.status FROM task_dependencies d
|
|
29610
|
+
JOIN tasks t ON t.id = d.depends_on
|
|
29611
|
+
WHERE d.task_id = ?`).all(row.id);
|
|
29612
|
+
if (deps.length > 0 && deps.every((dep) => dep.status === "failed" || dep.status === "cancelled")) {
|
|
29613
|
+
const task = rowToTask3(row);
|
|
29614
|
+
issues.push({
|
|
29615
|
+
type: "zombie_blocked",
|
|
29616
|
+
task_id: task.id,
|
|
29617
|
+
task_title: task.title,
|
|
29618
|
+
severity: "high",
|
|
29619
|
+
detail: `Blocked by ${deps.length} task(s) that are all failed/cancelled \u2014 will never unblock`
|
|
29620
|
+
});
|
|
29621
|
+
}
|
|
29622
|
+
}
|
|
29623
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
29624
|
+
issues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
29625
|
+
return {
|
|
29626
|
+
issues,
|
|
29627
|
+
total_issues: issues.length,
|
|
29628
|
+
scanned_at: new Date().toISOString()
|
|
29629
|
+
};
|
|
29630
|
+
}
|
|
29631
|
+
function getReviewQueue(opts, db) {
|
|
29632
|
+
const d = db || getDatabase();
|
|
29633
|
+
let sql = `SELECT * FROM tasks WHERE status = 'completed' AND (
|
|
29634
|
+
(requires_approval = 1 AND approved_by IS NULL)
|
|
29635
|
+
OR (confidence IS NOT NULL AND confidence < 0.5)
|
|
29636
|
+
)`;
|
|
29637
|
+
const params = [];
|
|
29638
|
+
if (opts?.project_id) {
|
|
29639
|
+
sql += " AND project_id = ?";
|
|
29640
|
+
params.push(opts.project_id);
|
|
29641
|
+
}
|
|
29642
|
+
sql += " ORDER BY CASE WHEN requires_approval = 1 AND approved_by IS NULL THEN 0 ELSE 1 END, confidence ASC";
|
|
29643
|
+
if (opts?.limit) {
|
|
29644
|
+
sql += " LIMIT ?";
|
|
29645
|
+
params.push(opts.limit);
|
|
29646
|
+
}
|
|
29647
|
+
return d.query(sql).all(...params).map(rowToTask3);
|
|
29648
|
+
}
|
|
29649
|
+
var init_patrol = __esm(() => {
|
|
29650
|
+
init_database();
|
|
29651
|
+
});
|
|
29652
|
+
|
|
29653
|
+
// src/db/agent-metrics.ts
|
|
29654
|
+
var exports_agent_metrics = {};
|
|
29655
|
+
__export(exports_agent_metrics, {
|
|
29656
|
+
scoreTask: () => scoreTask,
|
|
29657
|
+
getLeaderboard: () => getLeaderboard,
|
|
29658
|
+
getAgentMetrics: () => getAgentMetrics
|
|
29659
|
+
});
|
|
29660
|
+
function getAgentMetrics(agentId, opts, db) {
|
|
29661
|
+
const d = db || getDatabase();
|
|
29662
|
+
const agent = d.query("SELECT id, name FROM agents WHERE id = ? OR LOWER(name) = LOWER(?)").get(agentId, agentId);
|
|
29663
|
+
if (!agent)
|
|
29664
|
+
return null;
|
|
29665
|
+
let projectFilter = "";
|
|
29666
|
+
const params = [agent.id, agent.id];
|
|
29667
|
+
if (opts?.project_id) {
|
|
29668
|
+
projectFilter = " AND project_id = ?";
|
|
29669
|
+
params.push(opts.project_id);
|
|
29670
|
+
}
|
|
29671
|
+
const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}`).get(...params).count;
|
|
29672
|
+
const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'failed'${projectFilter}`).get(...params).count;
|
|
29673
|
+
const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'in_progress'${projectFilter}`).get(...params).count;
|
|
29674
|
+
const total = completed + failed;
|
|
29675
|
+
const completionRate = total > 0 ? completed / total : 0;
|
|
29676
|
+
const avgTime = d.query(`SELECT AVG(
|
|
29677
|
+
(julianday(completed_at) - julianday(created_at)) * 24 * 60
|
|
29678
|
+
) as avg_minutes
|
|
29679
|
+
FROM tasks
|
|
29680
|
+
WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
|
|
29681
|
+
const avgConf = d.query(`SELECT AVG(confidence) as avg_confidence
|
|
29682
|
+
FROM tasks
|
|
29683
|
+
WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
|
|
29684
|
+
const reviewTasks = d.query(`SELECT metadata FROM tasks
|
|
29685
|
+
WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}
|
|
29686
|
+
AND metadata LIKE '%_review_score%'`).all(...params);
|
|
29687
|
+
let reviewScoreAvg = null;
|
|
29688
|
+
if (reviewTasks.length > 0) {
|
|
29689
|
+
let total2 = 0;
|
|
29690
|
+
let count = 0;
|
|
29691
|
+
for (const row of reviewTasks) {
|
|
29692
|
+
try {
|
|
29693
|
+
const meta = JSON.parse(row.metadata);
|
|
29694
|
+
if (typeof meta._review_score === "number") {
|
|
29695
|
+
total2 += meta._review_score;
|
|
29696
|
+
count++;
|
|
29697
|
+
}
|
|
29698
|
+
} catch {}
|
|
29699
|
+
}
|
|
29700
|
+
if (count > 0)
|
|
29701
|
+
reviewScoreAvg = total2 / count;
|
|
29702
|
+
}
|
|
29703
|
+
const speedScore = avgTime?.avg_minutes != null ? Math.max(0, 1 - avgTime.avg_minutes / (60 * 24)) : 0.5;
|
|
29704
|
+
const confidenceScore = avgConf?.avg_confidence ?? 0.5;
|
|
29705
|
+
const volumeScore = Math.min(1, completed / 50);
|
|
29706
|
+
const compositeScore = completionRate * 0.3 + speedScore * 0.2 + confidenceScore * 0.3 + volumeScore * 0.2;
|
|
29707
|
+
return {
|
|
29708
|
+
agent_id: agent.id,
|
|
29709
|
+
agent_name: agent.name,
|
|
29710
|
+
tasks_completed: completed,
|
|
29711
|
+
tasks_failed: failed,
|
|
29712
|
+
tasks_in_progress: inProgress,
|
|
29713
|
+
completion_rate: Math.round(completionRate * 1000) / 1000,
|
|
29714
|
+
avg_completion_minutes: avgTime?.avg_minutes != null ? Math.round(avgTime.avg_minutes * 10) / 10 : null,
|
|
29715
|
+
avg_confidence: avgConf?.avg_confidence != null ? Math.round(avgConf.avg_confidence * 1000) / 1000 : null,
|
|
29716
|
+
review_score_avg: reviewScoreAvg != null ? Math.round(reviewScoreAvg * 1000) / 1000 : null,
|
|
29717
|
+
composite_score: Math.round(compositeScore * 1000) / 1000
|
|
29718
|
+
};
|
|
29719
|
+
}
|
|
29720
|
+
function getLeaderboard(opts, db) {
|
|
29721
|
+
const d = db || getDatabase();
|
|
29722
|
+
const agents = d.query("SELECT id FROM agents ORDER BY name").all();
|
|
29723
|
+
const entries = [];
|
|
29724
|
+
for (const agent of agents) {
|
|
29725
|
+
const metrics = getAgentMetrics(agent.id, { project_id: opts?.project_id }, d);
|
|
29726
|
+
if (metrics && (metrics.tasks_completed > 0 || metrics.tasks_failed > 0 || metrics.tasks_in_progress > 0)) {
|
|
29727
|
+
entries.push(metrics);
|
|
29728
|
+
}
|
|
29729
|
+
}
|
|
29730
|
+
entries.sort((a, b) => b.composite_score - a.composite_score);
|
|
29731
|
+
const limit = opts?.limit || 20;
|
|
29732
|
+
return entries.slice(0, limit).map((entry, idx) => ({
|
|
29733
|
+
...entry,
|
|
29734
|
+
rank: idx + 1
|
|
29735
|
+
}));
|
|
29736
|
+
}
|
|
29737
|
+
function scoreTask(taskId, score, reviewerId, db) {
|
|
29738
|
+
const d = db || getDatabase();
|
|
29739
|
+
if (score < 0 || score > 1)
|
|
29740
|
+
throw new Error("Score must be between 0 and 1");
|
|
29741
|
+
const task = d.query("SELECT metadata FROM tasks WHERE id = ?").get(taskId);
|
|
29742
|
+
if (!task)
|
|
29743
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
29744
|
+
const metadata = JSON.parse(task.metadata || "{}");
|
|
29745
|
+
metadata._review_score = score;
|
|
29746
|
+
if (reviewerId)
|
|
29747
|
+
metadata._reviewed_by = reviewerId;
|
|
29748
|
+
metadata._reviewed_at = now();
|
|
29749
|
+
d.run("UPDATE tasks SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata), now(), taskId]);
|
|
29750
|
+
}
|
|
29751
|
+
var init_agent_metrics = __esm(() => {
|
|
29752
|
+
init_database();
|
|
29753
|
+
});
|
|
29754
|
+
|
|
29755
|
+
// src/mcp/tools/task-rel-tools.ts
|
|
29756
|
+
function registerTaskRelTools(server, ctx) {
|
|
29757
|
+
const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
|
|
29758
|
+
if (shouldRegisterTool("create_handoff")) {
|
|
29759
|
+
server.tool("create_handoff", "Create a session handoff note for agent coordination.", {
|
|
29760
|
+
agent_id: exports_external2.string().optional().describe("Agent creating the handoff"),
|
|
29761
|
+
project_id: exports_external2.string().optional().describe("Project ID"),
|
|
29762
|
+
summary: exports_external2.string().describe("What was accomplished this session"),
|
|
29763
|
+
completed: exports_external2.array(exports_external2.string()).optional().describe("Items completed"),
|
|
29764
|
+
in_progress: exports_external2.array(exports_external2.string()).optional().describe("Items still in progress"),
|
|
29765
|
+
blockers: exports_external2.array(exports_external2.string()).optional().describe("Blocking issues"),
|
|
29766
|
+
next_steps: exports_external2.array(exports_external2.string()).optional().describe("Recommended next actions")
|
|
29767
|
+
}, async ({ agent_id, project_id, summary, completed, in_progress, blockers, next_steps }) => {
|
|
29768
|
+
try {
|
|
29769
|
+
const { createHandoff: createHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
29770
|
+
const handoff = createHandoff2({
|
|
29771
|
+
agent_id,
|
|
29772
|
+
project_id: project_id ? resolveId(project_id, "projects") : undefined,
|
|
29773
|
+
summary,
|
|
29774
|
+
completed,
|
|
29775
|
+
in_progress,
|
|
29776
|
+
blockers,
|
|
29777
|
+
next_steps
|
|
29778
|
+
});
|
|
29779
|
+
return { content: [{ type: "text", text: `Handoff created: ${handoff.id.slice(0, 8)} by ${handoff.agent_id || "unknown"}` }] };
|
|
29780
|
+
} catch (e) {
|
|
29781
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
29782
|
+
}
|
|
29783
|
+
});
|
|
29784
|
+
}
|
|
29785
|
+
if (shouldRegisterTool("get_latest_handoff")) {
|
|
29786
|
+
server.tool("get_latest_handoff", "Get the most recent handoff for an agent or project.", {
|
|
29787
|
+
agent_id: exports_external2.string().optional().describe("Filter by agent"),
|
|
29788
|
+
project_id: exports_external2.string().optional().describe("Filter by project")
|
|
29789
|
+
}, async ({ agent_id, project_id }) => {
|
|
29790
|
+
try {
|
|
29791
|
+
const { getLatestHandoff: getLatestHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
29792
|
+
const handoff = getLatestHandoff2(agent_id, project_id ? resolveId(project_id, "projects") : undefined);
|
|
29793
|
+
if (!handoff)
|
|
29794
|
+
return { content: [{ type: "text", text: "No handoffs found." }] };
|
|
29795
|
+
const lines = [
|
|
29796
|
+
`${handoff.created_at.slice(0, 16)} ${handoff.agent_id || "unknown"}`,
|
|
29797
|
+
handoff.summary
|
|
28491
29798
|
];
|
|
28492
29799
|
if (handoff.completed?.length)
|
|
28493
29800
|
lines.push(`Done: ${handoff.completed.join(", ")}`);
|
|
@@ -28512,7 +29819,7 @@ function registerTaskRelTools(server, ctx) {
|
|
|
28512
29819
|
created_by: exports_external2.string().optional().describe("Agent ID who created this relationship")
|
|
28513
29820
|
}, async ({ source_task_id, target_task_id, relationship_type, created_by }) => {
|
|
28514
29821
|
try {
|
|
28515
|
-
const { addTaskRelationship: addTaskRelationship2 } = (()
|
|
29822
|
+
const { addTaskRelationship: addTaskRelationship2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
|
|
28516
29823
|
const rel = addTaskRelationship2({
|
|
28517
29824
|
source_task_id: resolveId(source_task_id),
|
|
28518
29825
|
target_task_id: resolveId(target_task_id),
|
|
@@ -28533,7 +29840,7 @@ function registerTaskRelTools(server, ctx) {
|
|
|
28533
29840
|
relationship_type: exports_external2.enum(["related_to", "conflicts_with", "similar_to", "duplicates", "supersedes", "modifies_same_file"]).optional()
|
|
28534
29841
|
}, async ({ id, source_task_id, target_task_id, relationship_type }) => {
|
|
28535
29842
|
try {
|
|
28536
|
-
const { removeTaskRelationship: removeTaskRelationship2, removeTaskRelationshipByPair: removeTaskRelationshipByPair2 } = (()
|
|
29843
|
+
const { removeTaskRelationship: removeTaskRelationship2, removeTaskRelationshipByPair: removeTaskRelationshipByPair2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
|
|
28537
29844
|
let removed = false;
|
|
28538
29845
|
if (id) {
|
|
28539
29846
|
removed = removeTaskRelationship2(id);
|
|
@@ -28554,7 +29861,7 @@ function registerTaskRelTools(server, ctx) {
|
|
|
28554
29861
|
relationship_type: exports_external2.enum(["related_to", "conflicts_with", "similar_to", "duplicates", "supersedes", "modifies_same_file"]).optional()
|
|
28555
29862
|
}, async ({ task_id, relationship_type }) => {
|
|
28556
29863
|
try {
|
|
28557
|
-
const { getTaskRelationships: getTaskRelationships2 } = (()
|
|
29864
|
+
const { getTaskRelationships: getTaskRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
|
|
28558
29865
|
const rels = getTaskRelationships2(resolveId(task_id), relationship_type);
|
|
28559
29866
|
if (rels.length === 0)
|
|
28560
29867
|
return { content: [{ type: "text", text: "No relationships found." }] };
|
|
@@ -28571,7 +29878,7 @@ function registerTaskRelTools(server, ctx) {
|
|
|
28571
29878
|
task_id: exports_external2.string().describe("Task ID to detect file relationships for")
|
|
28572
29879
|
}, async ({ task_id }) => {
|
|
28573
29880
|
try {
|
|
28574
|
-
const { autoDetectFileRelationships: autoDetectFileRelationships2 } = (()
|
|
29881
|
+
const { autoDetectFileRelationships: autoDetectFileRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
|
|
28575
29882
|
const created = autoDetectFileRelationships2(resolveId(task_id));
|
|
28576
29883
|
return { content: [{ type: "text", text: created.length > 0 ? `Created ${created.length} file relationship(s).` : "No file overlaps detected." }] };
|
|
28577
29884
|
} catch (e) {
|
|
@@ -28582,8 +29889,8 @@ function registerTaskRelTools(server, ctx) {
|
|
|
28582
29889
|
if (shouldRegisterTool("sync_kg")) {
|
|
28583
29890
|
server.tool("sync_kg", "Sync all existing relationships into the knowledge graph edges table. Idempotent.", {}, async () => {
|
|
28584
29891
|
try {
|
|
28585
|
-
const { syncKgEdges } = (()
|
|
28586
|
-
const result =
|
|
29892
|
+
const { syncKgEdges: syncKgEdges2 } = (init_kg(), __toCommonJS(exports_kg));
|
|
29893
|
+
const result = syncKgEdges2();
|
|
28587
29894
|
return { content: [{ type: "text", text: `Knowledge graph synced: ${result.synced} edge(s) processed.` }] };
|
|
28588
29895
|
} catch (e) {
|
|
28589
29896
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -28599,8 +29906,8 @@ function registerTaskRelTools(server, ctx) {
|
|
|
28599
29906
|
limit: exports_external2.number().optional().describe("Max results")
|
|
28600
29907
|
}, async ({ entity_id, relation_type, entity_type, direction, limit }) => {
|
|
28601
29908
|
try {
|
|
28602
|
-
const { getRelated } = (()
|
|
28603
|
-
const edges =
|
|
29909
|
+
const { getRelated: getRelated2 } = (init_kg(), __toCommonJS(exports_kg));
|
|
29910
|
+
const edges = getRelated2(entity_id, { relation_type, entity_type, direction, limit });
|
|
28604
29911
|
if (edges.length === 0)
|
|
28605
29912
|
return { content: [{ type: "text", text: "No related entities found." }] };
|
|
28606
29913
|
const lines = edges.map((e) => `${e.source_id.slice(0, 12)}(${e.source_type}) --[${e.relation_type}]--> ${e.target_id.slice(0, 12)}(${e.target_type})`);
|
|
@@ -28619,8 +29926,8 @@ function registerTaskRelTools(server, ctx) {
|
|
|
28619
29926
|
relation_types: exports_external2.array(exports_external2.string()).optional().describe("Filter by relation types")
|
|
28620
29927
|
}, async ({ source_id, target_id, max_depth, relation_types }) => {
|
|
28621
29928
|
try {
|
|
28622
|
-
const { findPath } = (()
|
|
28623
|
-
const paths =
|
|
29929
|
+
const { findPath: findPath2 } = (init_kg(), __toCommonJS(exports_kg));
|
|
29930
|
+
const paths = findPath2(source_id, target_id, { max_depth, relation_types });
|
|
28624
29931
|
if (paths.length === 0)
|
|
28625
29932
|
return { content: [{ type: "text", text: "No path found." }] };
|
|
28626
29933
|
const lines = paths.map((path, i) => {
|
|
@@ -28644,8 +29951,8 @@ function registerTaskRelTools(server, ctx) {
|
|
|
28644
29951
|
relation_types: exports_external2.array(exports_external2.string()).optional().describe("Filter by relation types")
|
|
28645
29952
|
}, async ({ entity_id, max_depth, relation_types }) => {
|
|
28646
29953
|
try {
|
|
28647
|
-
const { getImpactAnalysis } = (()
|
|
28648
|
-
const impact =
|
|
29954
|
+
const { getImpactAnalysis: getImpactAnalysis2 } = (init_kg(), __toCommonJS(exports_kg));
|
|
29955
|
+
const impact = getImpactAnalysis2(entity_id, { max_depth, relation_types });
|
|
28649
29956
|
if (impact.length === 0)
|
|
28650
29957
|
return { content: [{ type: "text", text: "No downstream impact detected." }] };
|
|
28651
29958
|
const byDepth = new Map;
|
|
@@ -28675,8 +29982,8 @@ Depth ${depth}:`);
|
|
|
28675
29982
|
limit: exports_external2.number().optional().describe("Max results (default: 20)")
|
|
28676
29983
|
}, async ({ project_id, limit }) => {
|
|
28677
29984
|
try {
|
|
28678
|
-
const { getCriticalPath } = (()
|
|
28679
|
-
const result =
|
|
29985
|
+
const { getCriticalPath: getCriticalPath2 } = (init_kg(), __toCommonJS(exports_kg));
|
|
29986
|
+
const result = getCriticalPath2({ project_id: project_id ? resolveId(project_id, "projects") : undefined, limit });
|
|
28680
29987
|
if (result.length === 0)
|
|
28681
29988
|
return { content: [{ type: "text", text: "No critical path data. Run sync_kg first to populate the knowledge graph." }] };
|
|
28682
29989
|
const lines = result.map((r, i) => `${i + 1}. ${r.task_id.slice(0, 8)} blocks ${r.blocking_count} task(s), max depth ${r.depth}`);
|
|
@@ -28696,13 +30003,13 @@ ${lines.join(`
|
|
|
28696
30003
|
is_lead: exports_external2.coerce.boolean().optional().describe("Whether this agent is the project lead for this role")
|
|
28697
30004
|
}, async ({ project_id, agent_name, role, is_lead }) => {
|
|
28698
30005
|
try {
|
|
28699
|
-
const { setProjectAgentRole } = (()
|
|
28700
|
-
const { getAgentByName: getAgentByName2 } = (()
|
|
30006
|
+
const { setProjectAgentRole: setProjectAgentRole2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
|
|
30007
|
+
const { getAgentByName: getAgentByName2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
28701
30008
|
const agent = getAgentByName2(agent_name);
|
|
28702
30009
|
if (!agent)
|
|
28703
30010
|
return { content: [{ type: "text", text: `Agent not found: ${agent_name}` }], isError: true };
|
|
28704
30011
|
const pid = resolveId(project_id, "projects");
|
|
28705
|
-
const result =
|
|
30012
|
+
const result = setProjectAgentRole2(pid, agent.id, role, is_lead ?? false);
|
|
28706
30013
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
28707
30014
|
} catch (e) {
|
|
28708
30015
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -28732,9 +30039,9 @@ ${lines.join(`
|
|
|
28732
30039
|
}).join(`
|
|
28733
30040
|
`);
|
|
28734
30041
|
};
|
|
28735
|
-
const { getProjectOrgChart } = (()
|
|
30042
|
+
const { getProjectOrgChart: getProjectOrgChart2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
|
|
28736
30043
|
const pid = resolveId(project_id, "projects");
|
|
28737
|
-
const tree =
|
|
30044
|
+
const tree = getProjectOrgChart2(pid, { filter_to_project });
|
|
28738
30045
|
if (format === "json") {
|
|
28739
30046
|
return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
|
|
28740
30047
|
}
|
|
@@ -28752,9 +30059,9 @@ ${lines.join(`
|
|
|
28752
30059
|
project_id: exports_external2.string().describe("Project ID")
|
|
28753
30060
|
}, async ({ project_id }) => {
|
|
28754
30061
|
try {
|
|
28755
|
-
const { listProjectAgentRoles } = (()
|
|
30062
|
+
const { listProjectAgentRoles: listProjectAgentRoles2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
|
|
28756
30063
|
const pid = resolveId(project_id, "projects");
|
|
28757
|
-
const roles =
|
|
30064
|
+
const roles = listProjectAgentRoles2(pid);
|
|
28758
30065
|
return { content: [{ type: "text", text: JSON.stringify(roles, null, 2) }] };
|
|
28759
30066
|
} catch (e) {
|
|
28760
30067
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -28768,7 +30075,7 @@ ${lines.join(`
|
|
|
28768
30075
|
limit: exports_external2.number().optional().describe("Max results")
|
|
28769
30076
|
}, async ({ capabilities, min_score, limit }) => {
|
|
28770
30077
|
try {
|
|
28771
|
-
const { getCapableAgents: getCapableAgents2 } = (()
|
|
30078
|
+
const { getCapableAgents: getCapableAgents2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
28772
30079
|
const results = getCapableAgents2(capabilities, { min_score, limit });
|
|
28773
30080
|
if (results.length === 0)
|
|
28774
30081
|
return { content: [{ type: "text", text: "No agents match the given capabilities." }] };
|
|
@@ -28787,8 +30094,8 @@ ${lines.join(`
|
|
|
28787
30094
|
project_id: exports_external2.string().optional().describe("Filter by project")
|
|
28788
30095
|
}, async ({ stuck_minutes, confidence_threshold, project_id }) => {
|
|
28789
30096
|
try {
|
|
28790
|
-
const { patrolTasks } = (()
|
|
28791
|
-
const result =
|
|
30097
|
+
const { patrolTasks: patrolTasks2 } = (init_patrol(), __toCommonJS(exports_patrol));
|
|
30098
|
+
const result = patrolTasks2({
|
|
28792
30099
|
stuck_minutes,
|
|
28793
30100
|
confidence_threshold,
|
|
28794
30101
|
project_id: project_id ? resolveId(project_id, "projects") : undefined
|
|
@@ -28814,8 +30121,8 @@ ${lines.join(`
|
|
|
28814
30121
|
limit: exports_external2.number().optional().describe("Max results (default: all)")
|
|
28815
30122
|
}, async ({ project_id, limit }) => {
|
|
28816
30123
|
try {
|
|
28817
|
-
const { getReviewQueue } = (()
|
|
28818
|
-
const tasks =
|
|
30124
|
+
const { getReviewQueue: getReviewQueue2 } = (init_patrol(), __toCommonJS(exports_patrol));
|
|
30125
|
+
const tasks = getReviewQueue2({
|
|
28819
30126
|
project_id: project_id ? resolveId(project_id, "projects") : undefined,
|
|
28820
30127
|
limit
|
|
28821
30128
|
});
|
|
@@ -28841,8 +30148,8 @@ ${lines.join(`
|
|
|
28841
30148
|
reviewer_id: exports_external2.string().optional().describe("Agent ID of reviewer")
|
|
28842
30149
|
}, async ({ task_id, score, reviewer_id }) => {
|
|
28843
30150
|
try {
|
|
28844
|
-
const { scoreTask } = (()
|
|
28845
|
-
|
|
30151
|
+
const { scoreTask: scoreTask2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
|
|
30152
|
+
scoreTask2(resolveId(task_id), score, reviewer_id);
|
|
28846
30153
|
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} scored: ${score}` }] };
|
|
28847
30154
|
} catch (e) {
|
|
28848
30155
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -28859,7 +30166,7 @@ ${lines.join(`
|
|
|
28859
30166
|
notes: exports_external2.string().optional().describe("Notes about what was done")
|
|
28860
30167
|
}, async ({ task_id, minutes, agent_id, started_at, ended_at, notes }) => {
|
|
28861
30168
|
try {
|
|
28862
|
-
const { logTime: logTime2 } = (()
|
|
30169
|
+
const { logTime: logTime2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28863
30170
|
logTime2({ task_id: resolveId(task_id), minutes, agent_id, started_at, ended_at, notes });
|
|
28864
30171
|
return { content: [{ type: "text", text: `Logged ${minutes} min on task ${task_id.slice(0, 8)}` }] };
|
|
28865
30172
|
} catch (e) {
|
|
@@ -28874,7 +30181,7 @@ ${lines.join(`
|
|
|
28874
30181
|
since: exports_external2.string().optional().describe("ISO date \u2014 only tasks completed after this date")
|
|
28875
30182
|
}, async ({ project_id, agent_id, since }) => {
|
|
28876
30183
|
try {
|
|
28877
|
-
const { getTimeReport: getTimeReport2 } = (()
|
|
30184
|
+
const { getTimeReport: getTimeReport2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28878
30185
|
const report = getTimeReport2({ project_id: project_id ? resolveId(project_id, "projects") : undefined, agent_id, since });
|
|
28879
30186
|
if (report.length === 0)
|
|
28880
30187
|
return { content: [{ type: "text", text: "No completed tasks found." }] };
|
|
@@ -28898,7 +30205,7 @@ ${lines.join(`
|
|
|
28898
30205
|
agent_id: exports_external2.string().optional().describe("Agent subscribing (defaults to context agent)")
|
|
28899
30206
|
}, async ({ task_id, agent_id }) => {
|
|
28900
30207
|
try {
|
|
28901
|
-
const { watchTask: watchTask2 } = (()
|
|
30208
|
+
const { watchTask: watchTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28902
30209
|
watchTask2(resolveId(task_id), agent_id || "");
|
|
28903
30210
|
return { content: [{ type: "text", text: `Now watching task ${task_id.slice(0, 8)}` }] };
|
|
28904
30211
|
} catch (e) {
|
|
@@ -28912,7 +30219,7 @@ ${lines.join(`
|
|
|
28912
30219
|
agent_id: exports_external2.string().optional().describe("Agent unsubscribing (defaults to context agent)")
|
|
28913
30220
|
}, async ({ task_id, agent_id }) => {
|
|
28914
30221
|
try {
|
|
28915
|
-
const { unwatchTask: unwatchTask2 } = (()
|
|
30222
|
+
const { unwatchTask: unwatchTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28916
30223
|
unwatchTask2(resolveId(task_id), agent_id || "");
|
|
28917
30224
|
return { content: [{ type: "text", text: `Stopped watching task ${task_id.slice(0, 8)}` }] };
|
|
28918
30225
|
} catch (e) {
|
|
@@ -28925,7 +30232,7 @@ ${lines.join(`
|
|
|
28925
30232
|
task_id: exports_external2.string().describe("Task ID")
|
|
28926
30233
|
}, async ({ task_id }) => {
|
|
28927
30234
|
try {
|
|
28928
|
-
const { getTaskWatchers: getTaskWatchers2 } = (()
|
|
30235
|
+
const { getTaskWatchers: getTaskWatchers2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28929
30236
|
const watchers = getTaskWatchers2(resolveId(task_id));
|
|
28930
30237
|
if (watchers.length === 0)
|
|
28931
30238
|
return { content: [{ type: "text", text: "No watchers." }] };
|
|
@@ -28944,15 +30251,15 @@ ${lines.join(`
|
|
|
28944
30251
|
agent_id: exports_external2.string().optional().describe("Filter by assignee")
|
|
28945
30252
|
}, async ({ project_id, plan_id, task_list_id, since, agent_id }) => {
|
|
28946
30253
|
try {
|
|
28947
|
-
const { listTasks: listTasks3 } = (()
|
|
28948
|
-
const { patrolTasks } = (()
|
|
30254
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
30255
|
+
const { patrolTasks: patrolTasks2 } = (init_patrol(), __toCommonJS(exports_patrol));
|
|
28949
30256
|
const completed = listTasks3({ status: "completed", project_id, plan_id, task_list_id, assigned_to: agent_id, limit: 500 }, undefined);
|
|
28950
30257
|
const filtered = since ? completed.filter((t) => t.completed_at && t.completed_at >= since) : completed;
|
|
28951
30258
|
const total = filtered.length;
|
|
28952
30259
|
const lowConf = filtered.filter((t) => t.confidence != null && t.confidence < 0.7).length;
|
|
28953
30260
|
const withEstimate = filtered.filter((t) => t.estimated_minutes != null && t.actual_minutes != null);
|
|
28954
30261
|
const avgDiff = withEstimate.length > 0 ? withEstimate.reduce((acc, t) => acc + (t.actual_minutes - t.estimated_minutes), 0) / withEstimate.length : 0;
|
|
28955
|
-
const patrolResult =
|
|
30262
|
+
const patrolResult = patrolTasks2({ project_id: project_id ? resolveId(project_id, "projects") : undefined });
|
|
28956
30263
|
const stuck = patrolResult.issues.filter((i) => i.type === "stuck").length;
|
|
28957
30264
|
const lines = [
|
|
28958
30265
|
`Retro (${total} completed tasks${since ? ` since ${since}` : ""})`,
|
|
@@ -28973,7 +30280,7 @@ ${lines.join(`
|
|
|
28973
30280
|
limit: exports_external2.number().optional().describe("Max results (default: 20)")
|
|
28974
30281
|
}, async ({ project_id, limit }) => {
|
|
28975
30282
|
try {
|
|
28976
|
-
const { listTasks: listTasks3 } = (()
|
|
30283
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
28977
30284
|
const tasks = listTasks3({ status: "pending", project_id, assigned_to: "", limit: limit || 20 }, undefined);
|
|
28978
30285
|
if (tasks.length === 0)
|
|
28979
30286
|
return { content: [{ type: "text", text: "Inbox is empty." }] };
|
|
@@ -28992,8 +30299,8 @@ ${lines.join(`
|
|
|
28992
30299
|
project_id: exports_external2.string().optional().describe("Filter by project")
|
|
28993
30300
|
}, async ({ agent_id, project_id }) => {
|
|
28994
30301
|
try {
|
|
28995
|
-
const { getAgentMetrics } = (()
|
|
28996
|
-
const metrics =
|
|
30302
|
+
const { getAgentMetrics: getAgentMetrics2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
|
|
30303
|
+
const metrics = getAgentMetrics2(agent_id, {
|
|
28997
30304
|
project_id: project_id ? resolveId(project_id, "projects") : undefined
|
|
28998
30305
|
});
|
|
28999
30306
|
if (!metrics)
|
|
@@ -29020,8 +30327,8 @@ ${lines.join(`
|
|
|
29020
30327
|
limit: exports_external2.number().optional().describe("Max entries (default: 20)")
|
|
29021
30328
|
}, async ({ project_id, limit }) => {
|
|
29022
30329
|
try {
|
|
29023
|
-
const { getLeaderboard } = (()
|
|
29024
|
-
const entries =
|
|
30330
|
+
const { getLeaderboard: getLeaderboard2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
|
|
30331
|
+
const entries = getLeaderboard2({
|
|
29025
30332
|
project_id: project_id ? resolveId(project_id, "projects") : undefined,
|
|
29026
30333
|
limit
|
|
29027
30334
|
});
|
|
@@ -29868,116 +31175,493 @@ SSH: ${machine.ssh_address ?? "(not set)"}`
|
|
|
29868
31175
|
}
|
|
29869
31176
|
]
|
|
29870
31177
|
};
|
|
29871
|
-
} catch (error) {
|
|
29872
|
-
return { content: [{ type: "text", text: ctx.formatError(error) }] };
|
|
31178
|
+
} catch (error) {
|
|
31179
|
+
return { content: [{ type: "text", text: ctx.formatError(error) }] };
|
|
31180
|
+
}
|
|
31181
|
+
});
|
|
31182
|
+
}
|
|
31183
|
+
if (ctx.shouldRegisterTool("machines_list")) {
|
|
31184
|
+
server.tool("machines_list", "List all registered machines", {
|
|
31185
|
+
include_archived: exports_external2.boolean().optional().describe("Include archived machines")
|
|
31186
|
+
}, async (params) => {
|
|
31187
|
+
try {
|
|
31188
|
+
const db = getDb();
|
|
31189
|
+
const machines = listMachines(db, params.include_archived);
|
|
31190
|
+
if (machines.length === 0) {
|
|
31191
|
+
return {
|
|
31192
|
+
content: [{ type: "text", text: "No machines registered. Use machines_register to add one." }]
|
|
31193
|
+
};
|
|
31194
|
+
}
|
|
31195
|
+
const lines = machines.map((m) => {
|
|
31196
|
+
const primaryTag = m.is_primary ? " [PRIMARY]" : "";
|
|
31197
|
+
const archivedTag = m.archived_at ? " [ARCHIVED]" : "";
|
|
31198
|
+
return `${m.name} (${m.id.slice(0, 8)}) | ${m.hostname ?? "unknown"} | ${m.platform ?? "unknown"} | last: ${m.last_seen_at}${primaryTag}${archivedTag}`;
|
|
31199
|
+
});
|
|
31200
|
+
return { content: [{ type: "text", text: `Machines:
|
|
31201
|
+
${lines.join(`
|
|
31202
|
+
`)}` }] };
|
|
31203
|
+
} catch (error) {
|
|
31204
|
+
return { content: [{ type: "text", text: ctx.formatError(error) }] };
|
|
31205
|
+
}
|
|
31206
|
+
});
|
|
31207
|
+
}
|
|
31208
|
+
if (ctx.shouldRegisterTool("machines_set_primary")) {
|
|
31209
|
+
server.tool("machines_set_primary", "Set the primary machine", {
|
|
31210
|
+
name: exports_external2.string().describe("Machine name to set as primary")
|
|
31211
|
+
}, async (params) => {
|
|
31212
|
+
try {
|
|
31213
|
+
const db = getDb();
|
|
31214
|
+
const machine = setPrimaryMachine(params.name, db);
|
|
31215
|
+
return {
|
|
31216
|
+
content: [
|
|
31217
|
+
{ type: "text", text: `Primary machine set to: ${machine.name} (${machine.id.slice(0, 8)})` }
|
|
31218
|
+
]
|
|
31219
|
+
};
|
|
31220
|
+
} catch (error) {
|
|
31221
|
+
return { content: [{ type: "text", text: ctx.formatError(error) }] };
|
|
31222
|
+
}
|
|
31223
|
+
});
|
|
31224
|
+
}
|
|
31225
|
+
if (ctx.shouldRegisterTool("machines_archive")) {
|
|
31226
|
+
server.tool("machines_archive", "Archive (soft-delete) a machine. Cannot archive primary or machines with active tasks.", {
|
|
31227
|
+
name: exports_external2.string().describe("Machine name to archive")
|
|
31228
|
+
}, async (params) => {
|
|
31229
|
+
try {
|
|
31230
|
+
const db = getDb();
|
|
31231
|
+
const machine = getMachineByName(params.name, db);
|
|
31232
|
+
if (!machine) {
|
|
31233
|
+
return { content: [{ type: "text", text: `Machine '${params.name}' not found` }] };
|
|
31234
|
+
}
|
|
31235
|
+
archiveMachine(machine.id, db);
|
|
31236
|
+
return { content: [{ type: "text", text: `Machine '${params.name}' archived` }] };
|
|
31237
|
+
} catch (error) {
|
|
31238
|
+
return { content: [{ type: "text", text: ctx.formatError(error) }] };
|
|
31239
|
+
}
|
|
31240
|
+
});
|
|
31241
|
+
}
|
|
31242
|
+
if (ctx.shouldRegisterTool("machines_unarchive")) {
|
|
31243
|
+
server.tool("machines_unarchive", "Unarchive a machine", {
|
|
31244
|
+
name: exports_external2.string().describe("Machine name to unarchive")
|
|
31245
|
+
}, async (params) => {
|
|
31246
|
+
try {
|
|
31247
|
+
const db = getDb();
|
|
31248
|
+
const machine = getMachineByName(params.name, db);
|
|
31249
|
+
if (!machine) {
|
|
31250
|
+
return { content: [{ type: "text", text: `Machine '${params.name}' not found` }] };
|
|
31251
|
+
}
|
|
31252
|
+
const result = unarchiveMachine(machine.id, db);
|
|
31253
|
+
return {
|
|
31254
|
+
content: [
|
|
31255
|
+
{ type: "text", text: `Machine '${params.name}' unarchived${result ? ` (${result.id.slice(0, 8)})` : ""}` }
|
|
31256
|
+
]
|
|
31257
|
+
};
|
|
31258
|
+
} catch (error) {
|
|
31259
|
+
return { content: [{ type: "text", text: ctx.formatError(error) }] };
|
|
31260
|
+
}
|
|
31261
|
+
});
|
|
31262
|
+
}
|
|
31263
|
+
if (ctx.shouldRegisterTool("machines_delete")) {
|
|
31264
|
+
server.tool("machines_delete", "Hard-delete a machine. Cannot delete primary or machines with active tasks.", {
|
|
31265
|
+
name: exports_external2.string().describe("Machine name to delete")
|
|
31266
|
+
}, async (params) => {
|
|
31267
|
+
try {
|
|
31268
|
+
const db = getDb();
|
|
31269
|
+
const machine = getMachineByName(params.name, db);
|
|
31270
|
+
if (!machine) {
|
|
31271
|
+
return { content: [{ type: "text", text: `Machine '${params.name}' not found` }] };
|
|
31272
|
+
}
|
|
31273
|
+
const result = deleteMachine(machine.id, db);
|
|
31274
|
+
return {
|
|
31275
|
+
content: [
|
|
31276
|
+
{ type: "text", text: result ? `Machine '${params.name}' deleted` : `Failed to delete machine '${params.name}'` }
|
|
31277
|
+
]
|
|
31278
|
+
};
|
|
31279
|
+
} catch (error) {
|
|
31280
|
+
return { content: [{ type: "text", text: ctx.formatError(error) }] };
|
|
31281
|
+
}
|
|
31282
|
+
});
|
|
31283
|
+
}
|
|
31284
|
+
}
|
|
31285
|
+
var init_machines2 = __esm(() => {
|
|
31286
|
+
init_zod2();
|
|
31287
|
+
init_machines();
|
|
31288
|
+
init_database();
|
|
31289
|
+
});
|
|
31290
|
+
|
|
31291
|
+
// src/mcp/tools/agents.ts
|
|
31292
|
+
function registerAgentTools(server, { shouldRegisterTool, resolveId, formatError, agentFocusMap, getAgentFocus }) {
|
|
31293
|
+
if (shouldRegisterTool("set_focus")) {
|
|
31294
|
+
server.tool("set_focus", "Focus this agent on a project. All list/search/status tools will default to this project.", {
|
|
31295
|
+
agent_id: exports_external2.string().describe("Agent ID or name"),
|
|
31296
|
+
project_id: exports_external2.string().optional().describe("Project to focus on. Omit to clear."),
|
|
31297
|
+
task_list_id: exports_external2.string().optional().describe("Task list to focus on")
|
|
31298
|
+
}, async ({ agent_id, project_id, task_list_id }) => {
|
|
31299
|
+
try {
|
|
31300
|
+
const resolvedProject = project_id ? resolveId(project_id, "projects") : undefined;
|
|
31301
|
+
const focus = { agent_id, project_id: resolvedProject, task_list_id };
|
|
31302
|
+
agentFocusMap.set(agent_id, focus);
|
|
31303
|
+
try {
|
|
31304
|
+
const agent = getAgentByName(agent_id) || getAgent(agent_id);
|
|
31305
|
+
if (agent) {
|
|
31306
|
+
const db = getDatabase();
|
|
31307
|
+
db.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [resolvedProject || null, agent.id]);
|
|
31308
|
+
}
|
|
31309
|
+
} catch {}
|
|
31310
|
+
const projectName = resolvedProject ? ` (${resolvedProject.slice(0, 8)})` : "";
|
|
31311
|
+
return { content: [{ type: "text", text: `Focused on project${projectName}. Read tools will default to this scope. Pass explicit project_id to override.` }] };
|
|
31312
|
+
} catch (e) {
|
|
31313
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
31314
|
+
}
|
|
31315
|
+
});
|
|
31316
|
+
}
|
|
31317
|
+
if (shouldRegisterTool("get_focus")) {
|
|
31318
|
+
server.tool("get_focus", "Get the current focus for an agent.", { agent_id: exports_external2.string().describe("Agent ID or name") }, async ({ agent_id }) => {
|
|
31319
|
+
const focus = getAgentFocus(agent_id);
|
|
31320
|
+
if (!focus?.project_id) {
|
|
31321
|
+
return { content: [{ type: "text", text: "No focus set. Showing all projects." }] };
|
|
31322
|
+
}
|
|
31323
|
+
return { content: [{ type: "text", text: `Focused on project: ${focus.project_id}${focus.task_list_id ? `, task list: ${focus.task_list_id}` : ""}` }] };
|
|
31324
|
+
});
|
|
31325
|
+
}
|
|
31326
|
+
if (shouldRegisterTool("unfocus")) {
|
|
31327
|
+
server.tool("unfocus", "Clear focus \u2014 show all projects and tasks.", { agent_id: exports_external2.string().describe("Agent ID or name") }, async ({ agent_id }) => {
|
|
31328
|
+
agentFocusMap.delete(agent_id);
|
|
31329
|
+
try {
|
|
31330
|
+
const agent = getAgentByName(agent_id) || getAgent(agent_id);
|
|
31331
|
+
if (agent) {
|
|
31332
|
+
const db = getDatabase();
|
|
31333
|
+
db.run("UPDATE agents SET active_project_id = NULL WHERE id = ?", [agent.id]);
|
|
31334
|
+
}
|
|
31335
|
+
} catch {}
|
|
31336
|
+
return { content: [{ type: "text", text: "Focus cleared. Showing all projects." }] };
|
|
31337
|
+
});
|
|
31338
|
+
}
|
|
31339
|
+
if (shouldRegisterTool("register_agent")) {
|
|
31340
|
+
server.tool("register_agent", "Register an agent with a distinctive one-word name. Generic/generated names like agent, agent-1, assistant, or worker-2 are rejected. Prefer Roman or Greek names from suggest_agent_name.", {
|
|
31341
|
+
name: exports_external2.string().describe("Distinctive one-word agent name. Use suggest_agent_name first; avoid agent, agent-1, and other numbered generated names."),
|
|
31342
|
+
description: exports_external2.string().optional(),
|
|
31343
|
+
role: exports_external2.string().optional(),
|
|
31344
|
+
title: exports_external2.string().optional(),
|
|
31345
|
+
level: exports_external2.string().optional(),
|
|
31346
|
+
permissions: exports_external2.array(exports_external2.string()).optional(),
|
|
31347
|
+
capabilities: exports_external2.array(exports_external2.string()).optional().describe("Agent capabilities/skills for task routing (e.g. ['typescript', 'testing', 'devops'])"),
|
|
31348
|
+
session_id: exports_external2.string().optional().describe("Unique ID for this coding session (e.g. process PID + timestamp, or env var). Used to detect name collisions across sessions. Store it and pass on every register_agent call."),
|
|
31349
|
+
working_dir: exports_external2.string().optional().describe("Working directory of this session \u2014 used to look up the project's agent pool and identify who holds the name in a conflict"),
|
|
31350
|
+
force: exports_external2.boolean().optional().describe("Force takeover of an active agent's name. Use with caution \u2014 only when you know the previous session is dead.")
|
|
31351
|
+
}, async ({ name, description, role, title, level, permissions, capabilities, session_id, working_dir, force }) => {
|
|
31352
|
+
try {
|
|
31353
|
+
const pool = getAgentPoolForProject(working_dir);
|
|
31354
|
+
const result = registerAgent({ name, description, role, title, level, permissions, capabilities, session_id, working_dir, force, pool: pool || undefined });
|
|
31355
|
+
if (isAgentConflict(result)) {
|
|
31356
|
+
const suggestLine = result.suggestions && result.suggestions.length > 0 ? `
|
|
31357
|
+
Available names: ${result.suggestions.join(", ")}` : "";
|
|
31358
|
+
const hint = `CONFLICT: ${result.message}${suggestLine}`;
|
|
31359
|
+
return {
|
|
31360
|
+
content: [{ type: "text", text: hint }],
|
|
31361
|
+
isError: true
|
|
31362
|
+
};
|
|
31363
|
+
}
|
|
31364
|
+
const agent = result;
|
|
31365
|
+
const poolLine = pool ? `
|
|
31366
|
+
Pool: [${pool.join(", ")}]` : "";
|
|
31367
|
+
return {
|
|
31368
|
+
content: [{
|
|
31369
|
+
type: "text",
|
|
31370
|
+
text: `Agent registered:
|
|
31371
|
+
ID: ${agent.id}
|
|
31372
|
+
Name: ${agent.name}${agent.description ? `
|
|
31373
|
+
Description: ${agent.description}` : ""}
|
|
31374
|
+
Session: ${agent.session_id ?? "unbound"}${poolLine}
|
|
31375
|
+
Created: ${agent.created_at}
|
|
31376
|
+
Last seen: ${agent.last_seen_at}`
|
|
31377
|
+
}]
|
|
31378
|
+
};
|
|
31379
|
+
} catch (e) {
|
|
31380
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
31381
|
+
}
|
|
31382
|
+
});
|
|
31383
|
+
}
|
|
31384
|
+
if (shouldRegisterTool("suggest_agent_name")) {
|
|
31385
|
+
server.tool("suggest_agent_name", "Get available Roman/Greek-style agent names for a project. Use these instead of generic generated names.", {
|
|
31386
|
+
working_dir: exports_external2.string().optional().describe("Your working directory \u2014 used to look up the project's allowed name pool from config")
|
|
31387
|
+
}, async ({ working_dir }) => {
|
|
31388
|
+
try {
|
|
31389
|
+
const pool = getAgentPoolForProject(working_dir);
|
|
31390
|
+
const cutoff = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
31391
|
+
const allActive = listAgents().filter((a) => a.last_seen_at > cutoff);
|
|
31392
|
+
if (!pool) {
|
|
31393
|
+
const suggestions = getAvailableNamesFromPool([
|
|
31394
|
+
"caesar",
|
|
31395
|
+
"augustus",
|
|
31396
|
+
"marcus",
|
|
31397
|
+
"brutus",
|
|
31398
|
+
"cicero",
|
|
31399
|
+
"cato",
|
|
31400
|
+
"nero",
|
|
31401
|
+
"claudius",
|
|
31402
|
+
"tiberius",
|
|
31403
|
+
"hadrian",
|
|
31404
|
+
"athena",
|
|
31405
|
+
"apollo",
|
|
31406
|
+
"artemis",
|
|
31407
|
+
"iris",
|
|
31408
|
+
"hector",
|
|
31409
|
+
"sophia",
|
|
31410
|
+
"thalia",
|
|
31411
|
+
"phoebe",
|
|
31412
|
+
"daphne"
|
|
31413
|
+
], getDatabase());
|
|
31414
|
+
const lines2 = [
|
|
31415
|
+
"No project pool configured. Use a distinctive one-word name; generic generated names are blocked.",
|
|
31416
|
+
`Suggested names: ${suggestions.slice(0, 8).join(", ")}`,
|
|
31417
|
+
allActive.length > 0 ? `Active agents (avoid these names): ${allActive.map((a) => `${a.name} (seen ${Math.round((Date.now() - new Date(a.last_seen_at).getTime()) / 60000)}m ago)`).join(", ")}` : "No active agents.",
|
|
31418
|
+
`
|
|
31419
|
+
To restrict names, configure agent_pool or project_pools in ~/.hasna/todos/config.json`
|
|
31420
|
+
];
|
|
31421
|
+
return { content: [{ type: "text", text: lines2.join(`
|
|
31422
|
+
`) }] };
|
|
31423
|
+
}
|
|
31424
|
+
const available = getAvailableNamesFromPool(pool, getDatabase());
|
|
31425
|
+
const activeInPool = allActive.filter((a) => pool.map((n) => n.toLowerCase()).includes(a.name));
|
|
31426
|
+
const lines = [
|
|
31427
|
+
`Project pool: ${pool.join(", ")}`,
|
|
31428
|
+
`Available now (${available.length}): ${available.length > 0 ? available.join(", ") : "none \u2014 all names in use"}`,
|
|
31429
|
+
activeInPool.length > 0 ? `Active agents: ${activeInPool.map((a) => `${a.name} (seen ${Math.round((Date.now() - new Date(a.last_seen_at).getTime()) / 60000)}m ago)`).join(", ")}` : "Active agents: none",
|
|
31430
|
+
available.length > 0 ? `
|
|
31431
|
+
Suggested: ${available[0]}` : `
|
|
31432
|
+
No names available. Wait for an active agent to go stale (30min timeout).`
|
|
31433
|
+
];
|
|
31434
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
31435
|
+
`) }] };
|
|
31436
|
+
} catch (e) {
|
|
31437
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
31438
|
+
}
|
|
31439
|
+
});
|
|
31440
|
+
}
|
|
31441
|
+
if (shouldRegisterTool("list_agents")) {
|
|
31442
|
+
server.tool("list_agents", "List all registered agents. By default shows only active agents \u2014 set include_archived to see archived ones too.", {
|
|
31443
|
+
include_archived: exports_external2.boolean().optional().describe("Include archived agents in the list (default: false)")
|
|
31444
|
+
}, async ({ include_archived }) => {
|
|
31445
|
+
try {
|
|
31446
|
+
const agents = listAgents({ include_archived: include_archived ?? false });
|
|
31447
|
+
if (agents.length === 0) {
|
|
31448
|
+
return { content: [{ type: "text", text: "No agents registered." }] };
|
|
31449
|
+
}
|
|
31450
|
+
const text = agents.map((a) => {
|
|
31451
|
+
const statusTag = a.status === "archived" ? " [archived]" : "";
|
|
31452
|
+
return `${a.id} | ${a.name}${statusTag}${a.description ? ` - ${a.description}` : ""} (last seen: ${a.last_seen_at})`;
|
|
31453
|
+
}).join(`
|
|
31454
|
+
`);
|
|
31455
|
+
return { content: [{ type: "text", text: `${agents.length} agent(s):
|
|
31456
|
+
${text}` }] };
|
|
31457
|
+
} catch (e) {
|
|
31458
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
31459
|
+
}
|
|
31460
|
+
});
|
|
31461
|
+
}
|
|
31462
|
+
if (shouldRegisterTool("get_agent")) {
|
|
31463
|
+
server.tool("get_agent", "Get agent details by ID or name. Provide one of id or name.", {
|
|
31464
|
+
agent_id: exports_external2.string().optional(),
|
|
31465
|
+
id: exports_external2.string().optional(),
|
|
31466
|
+
name: exports_external2.string().optional()
|
|
31467
|
+
}, async ({ agent_id, id, name }) => {
|
|
31468
|
+
try {
|
|
31469
|
+
const identifier = agent_id || id || name;
|
|
31470
|
+
if (!identifier) {
|
|
31471
|
+
return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
|
|
31472
|
+
}
|
|
31473
|
+
const agent = getAgent(identifier) || getAgentByName(identifier);
|
|
31474
|
+
if (!agent) {
|
|
31475
|
+
return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
|
|
31476
|
+
}
|
|
31477
|
+
const parts = [
|
|
31478
|
+
`ID: ${agent.id}`,
|
|
31479
|
+
`Name: ${agent.name}`
|
|
31480
|
+
];
|
|
31481
|
+
if (agent.description)
|
|
31482
|
+
parts.push(`Description: ${agent.description}`);
|
|
31483
|
+
if (Object.keys(agent.metadata).length > 0)
|
|
31484
|
+
parts.push(`Metadata: ${JSON.stringify(agent.metadata)}`);
|
|
31485
|
+
parts.push(`Created: ${agent.created_at}`);
|
|
31486
|
+
parts.push(`Last seen: ${agent.last_seen_at}`);
|
|
31487
|
+
return { content: [{ type: "text", text: parts.join(`
|
|
31488
|
+
`) }] };
|
|
31489
|
+
} catch (e) {
|
|
31490
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
31491
|
+
}
|
|
31492
|
+
});
|
|
31493
|
+
}
|
|
31494
|
+
if (shouldRegisterTool("rename_agent")) {
|
|
31495
|
+
server.tool("rename_agent", "Rename an agent. Resolve by id or current name.", {
|
|
31496
|
+
id: exports_external2.string().optional(),
|
|
31497
|
+
name: exports_external2.string().optional(),
|
|
31498
|
+
new_name: exports_external2.string()
|
|
31499
|
+
}, async ({ id, name, new_name }) => {
|
|
31500
|
+
try {
|
|
31501
|
+
if (!id && !name) {
|
|
31502
|
+
return { content: [{ type: "text", text: "Provide either id or name." }], isError: true };
|
|
31503
|
+
}
|
|
31504
|
+
const agent = id ? getAgent(id) : getAgentByName(name);
|
|
31505
|
+
if (!agent) {
|
|
31506
|
+
return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
|
|
31507
|
+
}
|
|
31508
|
+
const oldName = agent.name;
|
|
31509
|
+
const updated = updateAgent(agent.id, { name: new_name });
|
|
31510
|
+
const db = getDatabase();
|
|
31511
|
+
const tasksResult = db.run("UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?", [new_name, oldName]);
|
|
31512
|
+
const taskNote = tasksResult.changes > 0 ? `
|
|
31513
|
+
Updated assigned_to on ${tasksResult.changes} task(s).` : "";
|
|
31514
|
+
return {
|
|
31515
|
+
content: [{
|
|
31516
|
+
type: "text",
|
|
31517
|
+
text: `Agent renamed: ${oldName} -> ${updated.name}
|
|
31518
|
+
ID: ${updated.id}${taskNote}`
|
|
31519
|
+
}]
|
|
31520
|
+
};
|
|
31521
|
+
} catch (e) {
|
|
31522
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
29873
31523
|
}
|
|
29874
31524
|
});
|
|
29875
31525
|
}
|
|
29876
|
-
if (
|
|
29877
|
-
server.tool("
|
|
29878
|
-
|
|
29879
|
-
|
|
31526
|
+
if (shouldRegisterTool("update_agent")) {
|
|
31527
|
+
server.tool("update_agent", "Update an agent's description, role, title, or other metadata. Resolve by id or name.", {
|
|
31528
|
+
agent_id: exports_external2.string().optional(),
|
|
31529
|
+
id: exports_external2.string().optional(),
|
|
31530
|
+
name: exports_external2.string().optional(),
|
|
31531
|
+
description: exports_external2.string().optional(),
|
|
31532
|
+
role: exports_external2.string().optional(),
|
|
31533
|
+
title: exports_external2.string().optional(),
|
|
31534
|
+
level: exports_external2.string().optional(),
|
|
31535
|
+
capabilities: exports_external2.array(exports_external2.string()).optional(),
|
|
31536
|
+
permissions: exports_external2.array(exports_external2.string()).optional(),
|
|
31537
|
+
metadata: exports_external2.record(exports_external2.unknown()).optional()
|
|
31538
|
+
}, async ({ agent_id, id, name, ...updates }) => {
|
|
29880
31539
|
try {
|
|
29881
|
-
const
|
|
29882
|
-
|
|
29883
|
-
|
|
29884
|
-
return {
|
|
29885
|
-
content: [{ type: "text", text: "No machines registered. Use machines_register to add one." }]
|
|
29886
|
-
};
|
|
31540
|
+
const identifier = agent_id || id || name;
|
|
31541
|
+
if (!identifier) {
|
|
31542
|
+
return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
|
|
29887
31543
|
}
|
|
29888
|
-
const
|
|
29889
|
-
|
|
29890
|
-
|
|
29891
|
-
|
|
29892
|
-
|
|
29893
|
-
return { content: [{ type: "text", text: `
|
|
29894
|
-
|
|
29895
|
-
|
|
29896
|
-
} catch (error) {
|
|
29897
|
-
return { content: [{ type: "text", text: ctx.formatError(error) }] };
|
|
31544
|
+
const agent = getAgent(identifier) || getAgentByName(identifier);
|
|
31545
|
+
if (!agent) {
|
|
31546
|
+
return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
|
|
31547
|
+
}
|
|
31548
|
+
const updated = updateAgent(agent.id, updates);
|
|
31549
|
+
return { content: [{ type: "text", text: `Agent updated: ${updated.name} (${updated.id.slice(0, 8)})` }] };
|
|
31550
|
+
} catch (e) {
|
|
31551
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
29898
31552
|
}
|
|
29899
31553
|
});
|
|
29900
31554
|
}
|
|
29901
|
-
if (
|
|
29902
|
-
server.tool("
|
|
29903
|
-
|
|
29904
|
-
|
|
31555
|
+
if (shouldRegisterTool("delete_agent")) {
|
|
31556
|
+
server.tool("delete_agent", "Archive an agent (soft delete). The agent is hidden from list_agents but preserved for task history. Use unarchive_agent to restore. Resolve by id or name.", {
|
|
31557
|
+
agent_id: exports_external2.string().optional(),
|
|
31558
|
+
id: exports_external2.string().optional(),
|
|
31559
|
+
name: exports_external2.string().optional()
|
|
31560
|
+
}, async ({ agent_id, id, name }) => {
|
|
29905
31561
|
try {
|
|
29906
|
-
const
|
|
29907
|
-
|
|
31562
|
+
const identifier = agent_id || id || name;
|
|
31563
|
+
if (!identifier) {
|
|
31564
|
+
return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
|
|
31565
|
+
}
|
|
31566
|
+
const agent = getAgent(identifier) || getAgentByName(identifier);
|
|
31567
|
+
if (!agent) {
|
|
31568
|
+
return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
|
|
31569
|
+
}
|
|
31570
|
+
const archived = archiveAgent(agent.id);
|
|
29908
31571
|
return {
|
|
29909
|
-
content: [
|
|
29910
|
-
|
|
29911
|
-
|
|
31572
|
+
content: [{
|
|
31573
|
+
type: "text",
|
|
31574
|
+
text: archived ? `Agent archived: ${agent.name} (${agent.id}). Use unarchive_agent to restore.` : `Failed to archive agent: ${agent.name}`
|
|
31575
|
+
}],
|
|
31576
|
+
isError: !archived
|
|
29912
31577
|
};
|
|
29913
|
-
} catch (
|
|
29914
|
-
return { content: [{ type: "text", text:
|
|
31578
|
+
} catch (e) {
|
|
31579
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
29915
31580
|
}
|
|
29916
31581
|
});
|
|
29917
31582
|
}
|
|
29918
|
-
if (
|
|
29919
|
-
server.tool("
|
|
29920
|
-
|
|
29921
|
-
|
|
31583
|
+
if (shouldRegisterTool("unarchive_agent")) {
|
|
31584
|
+
server.tool("unarchive_agent", "Restore an archived agent back to active status. Resolve by id or name.", {
|
|
31585
|
+
agent_id: exports_external2.string().optional(),
|
|
31586
|
+
id: exports_external2.string().optional(),
|
|
31587
|
+
name: exports_external2.string().optional()
|
|
31588
|
+
}, async ({ agent_id, id, name }) => {
|
|
29922
31589
|
try {
|
|
29923
|
-
const
|
|
29924
|
-
|
|
29925
|
-
|
|
29926
|
-
return { content: [{ type: "text", text: `Machine '${params.name}' not found` }] };
|
|
31590
|
+
const identifier = agent_id || id || name;
|
|
31591
|
+
if (!identifier) {
|
|
31592
|
+
return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
|
|
29927
31593
|
}
|
|
29928
|
-
|
|
29929
|
-
|
|
29930
|
-
|
|
29931
|
-
|
|
31594
|
+
const agent = getAgent(identifier) || getAgentByName(identifier);
|
|
31595
|
+
if (!agent) {
|
|
31596
|
+
return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
|
|
31597
|
+
}
|
|
31598
|
+
if (agent.status === "active") {
|
|
31599
|
+
return { content: [{ type: "text", text: `Agent ${agent.name} is already active.` }] };
|
|
31600
|
+
}
|
|
31601
|
+
const restored = unarchiveAgent(agent.id);
|
|
31602
|
+
return {
|
|
31603
|
+
content: [{
|
|
31604
|
+
type: "text",
|
|
31605
|
+
text: restored ? `Agent restored: ${agent.name} (${agent.id}) is now active.` : `Failed to restore agent: ${agent.name}`
|
|
31606
|
+
}],
|
|
31607
|
+
isError: !restored
|
|
31608
|
+
};
|
|
31609
|
+
} catch (e) {
|
|
31610
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
29932
31611
|
}
|
|
29933
31612
|
});
|
|
29934
31613
|
}
|
|
29935
|
-
if (
|
|
29936
|
-
server.tool("
|
|
29937
|
-
|
|
29938
|
-
}, async (
|
|
31614
|
+
if (shouldRegisterTool("heartbeat")) {
|
|
31615
|
+
server.tool("heartbeat", "Update your last_seen_at timestamp to signal you're still active. Call periodically during long tasks to prevent being marked stale.", {
|
|
31616
|
+
agent_id: exports_external2.string().describe("Your agent ID or name.")
|
|
31617
|
+
}, async ({ agent_id }) => {
|
|
29939
31618
|
try {
|
|
29940
|
-
const
|
|
29941
|
-
|
|
29942
|
-
|
|
29943
|
-
return { content: [{ type: "text", text: `Machine '${params.name}' not found` }] };
|
|
31619
|
+
const agent = getAgent(agent_id) || getAgentByName(agent_id);
|
|
31620
|
+
if (!agent) {
|
|
31621
|
+
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
29944
31622
|
}
|
|
29945
|
-
|
|
31623
|
+
updateAgentActivity(agent.id);
|
|
29946
31624
|
return {
|
|
29947
|
-
content: [
|
|
29948
|
-
|
|
29949
|
-
|
|
31625
|
+
content: [{
|
|
31626
|
+
type: "text",
|
|
31627
|
+
text: `Heartbeat: ${agent.name} (${agent.id}) \u2014 last_seen_at updated to ${new Date().toISOString()}`
|
|
31628
|
+
}]
|
|
29950
31629
|
};
|
|
29951
|
-
} catch (
|
|
29952
|
-
return { content: [{ type: "text", text:
|
|
31630
|
+
} catch (e) {
|
|
31631
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
29953
31632
|
}
|
|
29954
31633
|
});
|
|
29955
31634
|
}
|
|
29956
|
-
if (
|
|
29957
|
-
server.tool("
|
|
29958
|
-
|
|
29959
|
-
|
|
31635
|
+
if (shouldRegisterTool("release_agent")) {
|
|
31636
|
+
server.tool("release_agent", "Explicitly release/logout an agent \u2014 clears session binding and makes the name immediately available. Call this when your session ends instead of waiting for the 30-minute stale timeout.", {
|
|
31637
|
+
agent_id: exports_external2.string().describe("Your agent ID or name."),
|
|
31638
|
+
session_id: exports_external2.string().optional().describe("Your session ID \u2014 if provided, release only succeeds if it matches (prevents other sessions from releasing your agent).")
|
|
31639
|
+
}, async ({ agent_id, session_id }) => {
|
|
29960
31640
|
try {
|
|
29961
|
-
const
|
|
29962
|
-
|
|
29963
|
-
|
|
29964
|
-
|
|
31641
|
+
const agent = getAgent(agent_id) || getAgentByName(agent_id);
|
|
31642
|
+
if (!agent) {
|
|
31643
|
+
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
31644
|
+
}
|
|
31645
|
+
const released = releaseAgent(agent.id, session_id);
|
|
31646
|
+
if (!released) {
|
|
31647
|
+
return { content: [{ type: "text", text: `Release denied: session_id does not match agent's current session.` }], isError: true };
|
|
29965
31648
|
}
|
|
29966
|
-
const result = deleteMachine(machine.id, db);
|
|
29967
31649
|
return {
|
|
29968
|
-
content: [
|
|
29969
|
-
|
|
29970
|
-
|
|
31650
|
+
content: [{
|
|
31651
|
+
type: "text",
|
|
31652
|
+
text: `Agent released: ${agent.name} (${agent.id}) \u2014 session cleared, name is now available.`
|
|
31653
|
+
}]
|
|
29971
31654
|
};
|
|
29972
|
-
} catch (
|
|
29973
|
-
return { content: [{ type: "text", text:
|
|
31655
|
+
} catch (e) {
|
|
31656
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
29974
31657
|
}
|
|
29975
31658
|
});
|
|
29976
31659
|
}
|
|
29977
31660
|
}
|
|
29978
|
-
var
|
|
31661
|
+
var init_agents2 = __esm(() => {
|
|
29979
31662
|
init_zod2();
|
|
29980
|
-
|
|
31663
|
+
init_agents();
|
|
31664
|
+
init_config();
|
|
29981
31665
|
init_database();
|
|
29982
31666
|
});
|
|
29983
31667
|
|
|
@@ -29988,17 +31672,23 @@ __export(exports_mcp, {
|
|
|
29988
31672
|
});
|
|
29989
31673
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
29990
31674
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
29991
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
31675
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
29992
31676
|
import { join as join12, dirname as dirname6 } from "path";
|
|
29993
31677
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
29994
31678
|
function getMcpVersion() {
|
|
29995
31679
|
try {
|
|
29996
|
-
|
|
29997
|
-
|
|
29998
|
-
|
|
31680
|
+
let dir = dirname6(fileURLToPath3(import.meta.url));
|
|
31681
|
+
for (let i = 0;i < 4; i++) {
|
|
31682
|
+
const pkgPath = join12(dir, "package.json");
|
|
31683
|
+
if (existsSync9(pkgPath)) {
|
|
31684
|
+
return JSON.parse(readFileSync7(pkgPath, "utf-8")).version || "0.0.0";
|
|
31685
|
+
}
|
|
31686
|
+
dir = dirname6(dir);
|
|
31687
|
+
}
|
|
29999
31688
|
} catch {
|
|
30000
31689
|
return "0.0.0";
|
|
30001
31690
|
}
|
|
31691
|
+
return "0.0.0";
|
|
30002
31692
|
}
|
|
30003
31693
|
function shouldRegisterTool(name) {
|
|
30004
31694
|
if (TODOS_PROFILE === "minimal")
|
|
@@ -30159,6 +31849,7 @@ var init_mcp = __esm(() => {
|
|
|
30159
31849
|
init_task_rel_tools();
|
|
30160
31850
|
init_code_tools();
|
|
30161
31851
|
init_machines2();
|
|
31852
|
+
init_agents2();
|
|
30162
31853
|
server = new McpServer({
|
|
30163
31854
|
name: "todos",
|
|
30164
31855
|
version: getMcpVersion()
|
|
@@ -30176,6 +31867,7 @@ var init_mcp = __esm(() => {
|
|
|
30176
31867
|
"get_next_task",
|
|
30177
31868
|
"bootstrap",
|
|
30178
31869
|
"get_tasks_changed_since",
|
|
31870
|
+
"get_health",
|
|
30179
31871
|
"heartbeat",
|
|
30180
31872
|
"release_agent"
|
|
30181
31873
|
]);
|
|
@@ -30216,6 +31908,7 @@ var init_mcp = __esm(() => {
|
|
|
30216
31908
|
registerTaskResources(server, toolContext);
|
|
30217
31909
|
registerTaskRelTools(server, toolContext);
|
|
30218
31910
|
registerCodeTools(server, toolContext);
|
|
31911
|
+
registerAgentTools(server, { ...toolContext, agentFocusMap });
|
|
30219
31912
|
registerMachineTools(server, { shouldRegisterTool, formatError });
|
|
30220
31913
|
registerDispatchTools(server, { shouldRegisterTool, resolveId, formatError });
|
|
30221
31914
|
registerCloudSyncTools(server, { shouldRegisterTool, formatError });
|
|
@@ -30766,7 +32459,7 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
30766
32459
|
opts.tags = opts.tags || opts.tag;
|
|
30767
32460
|
opts.list = opts.list || opts.taskList;
|
|
30768
32461
|
const resolvedId = resolveTaskId(id);
|
|
30769
|
-
const current =
|
|
32462
|
+
const current = getTask(resolvedId);
|
|
30770
32463
|
if (!current) {
|
|
30771
32464
|
console.error(chalk2.red(`Task not found: ${id}`));
|
|
30772
32465
|
process.exit(1);
|
|
@@ -30827,7 +32520,7 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
30827
32520
|
program2.command("approve <id>").description("Approve a task that requires approval").action((id) => {
|
|
30828
32521
|
const globalOpts = program2.opts();
|
|
30829
32522
|
const resolvedId = resolveTaskId(id);
|
|
30830
|
-
const task =
|
|
32523
|
+
const task = getTask(resolvedId);
|
|
30831
32524
|
if (!task) {
|
|
30832
32525
|
console.error(chalk2.red(`Task not found: ${id}`));
|
|
30833
32526
|
process.exit(1);
|
|
@@ -31429,7 +33122,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
31429
33122
|
updated.activeForm = existing.task.activeForm;
|
|
31430
33123
|
writeClaudeTask(dir, updated);
|
|
31431
33124
|
if (recordConflict) {
|
|
31432
|
-
const latest =
|
|
33125
|
+
const latest = getTask(task.id);
|
|
31433
33126
|
if (latest) {
|
|
31434
33127
|
const conflict = {
|
|
31435
33128
|
agent: "claude",
|
|
@@ -31452,7 +33145,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
31452
33145
|
ct.subject = formatPrefixedSubject(task.title, prefixConfig.prefix, prefixCounter);
|
|
31453
33146
|
}
|
|
31454
33147
|
writeClaudeTask(dir, ct);
|
|
31455
|
-
const current =
|
|
33148
|
+
const current = getTask(task.id);
|
|
31456
33149
|
if (current) {
|
|
31457
33150
|
const newMeta = { ...current.metadata, claude_task_id: claudeId };
|
|
31458
33151
|
updateTask(task.id, { version: current.version, metadata: newMeta });
|
|
@@ -31654,7 +33347,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
31654
33347
|
const updated = taskToAgentTask(task, existing.task.id, existing.task.metadata);
|
|
31655
33348
|
writeAgentTask(dir, updated);
|
|
31656
33349
|
if (recordConflict) {
|
|
31657
|
-
const latest =
|
|
33350
|
+
const latest = getTask(task.id);
|
|
31658
33351
|
if (latest) {
|
|
31659
33352
|
const conflict = {
|
|
31660
33353
|
agent,
|
|
@@ -31673,7 +33366,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
31673
33366
|
hwm++;
|
|
31674
33367
|
const at = taskToAgentTask(task, externalId);
|
|
31675
33368
|
writeAgentTask(dir, at);
|
|
31676
|
-
const current =
|
|
33369
|
+
const current = getTask(task.id);
|
|
31677
33370
|
if (current) {
|
|
31678
33371
|
const newMeta = { ...current.metadata, [metaKey]: externalId };
|
|
31679
33372
|
updateTask(task.id, { version: current.version, metadata: newMeta });
|
|
@@ -32324,6 +34017,27 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
|
|
|
32324
34017
|
handleError(e);
|
|
32325
34018
|
}
|
|
32326
34019
|
});
|
|
34020
|
+
program2.command("agents-normalize").alias("normalize-agents").description("Rename invalid/generated agent names (agent, agent-1, name-2, two-word names) to safe one-word names").action(async () => {
|
|
34021
|
+
const globalOpts = program2.opts();
|
|
34022
|
+
try {
|
|
34023
|
+
const db = getDatabase();
|
|
34024
|
+
const renamed = normalizeGeneratedAgentNames(db);
|
|
34025
|
+
if (globalOpts.json) {
|
|
34026
|
+
output({ renamed, suggestions: suggestAgentNames(listAgents().map((agent) => agent.name)).slice(0, 5) }, true);
|
|
34027
|
+
return;
|
|
34028
|
+
}
|
|
34029
|
+
if (renamed.length === 0) {
|
|
34030
|
+
console.log(chalk5.green("No invalid or generated agent names found."));
|
|
34031
|
+
return;
|
|
34032
|
+
}
|
|
34033
|
+
console.log(chalk5.green(`Normalized ${renamed.length} agent name(s):`));
|
|
34034
|
+
for (const item of renamed) {
|
|
34035
|
+
console.log(` ${chalk5.cyan(item.id)} ${chalk5.red(item.old_name)} ${chalk5.dim("->")} ${chalk5.bold(item.new_name)} ${chalk5.dim(`(${item.reference_updates} reference updates)`)}`);
|
|
34036
|
+
}
|
|
34037
|
+
} catch (e) {
|
|
34038
|
+
handleError(e);
|
|
34039
|
+
}
|
|
34040
|
+
});
|
|
32327
34041
|
program2.command("agent-update <name>").alias("agents-update").description("Update an agent's description, role, or other fields").option("--description <text>", "New description").option("--role <role>", "New role").option("--title <title>", "New title").action(async (name, opts) => {
|
|
32328
34042
|
const globalOpts = program2.opts();
|
|
32329
34043
|
try {
|
|
@@ -32624,7 +34338,7 @@ function registerConfigServeCommands(program2) {
|
|
|
32624
34338
|
console.log(JSON.stringify(config, null, 2));
|
|
32625
34339
|
}
|
|
32626
34340
|
});
|
|
32627
|
-
program2.command("serve").description("Start the web dashboard").option("--port <port>", "Port number", "19427").option("--host <host>", "Host to bind (default: 127.0.0.1 localhost only, use 0.0.0.0 for all interfaces)").option("--no-open", "Don't open browser automatically").action(async (opts) => {
|
|
34341
|
+
program2.command("serve").description("Start the web dashboard").option("--port <port>", "Port number", "19427").option("--host <host>", "Host to bind (default: 127.0.0.1 localhost only, use 0.0.0.0 for all interfaces)").option("--api-key <key>", "Require this API key for /api/* requests").option("--no-open", "Don't open browser automatically").action(async (opts) => {
|
|
32628
34342
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
32629
34343
|
const requestedPort = parseInt(opts.port, 10);
|
|
32630
34344
|
let port = requestedPort;
|
|
@@ -32639,7 +34353,7 @@ function registerConfigServeCommands(program2) {
|
|
|
32639
34353
|
if (port !== requestedPort) {
|
|
32640
34354
|
console.log(`Port ${requestedPort} in use, using ${port}`);
|
|
32641
34355
|
}
|
|
32642
|
-
await startServer2(port, { open: opts.open !== false, host: opts.host });
|
|
34356
|
+
await startServer2(port, { open: opts.open !== false, host: opts.host, apiKey: opts.apiKey });
|
|
32643
34357
|
});
|
|
32644
34358
|
program2.command("watch").description("Live-updating task list (refreshes every few seconds)").option("-s, --status <status>", "Filter by status (default: pending,in_progress)").option("-i, --interval <seconds>", "Refresh interval in seconds", "5").action(async (opts) => {
|
|
32645
34359
|
const globalOpts = program2.opts();
|
|
@@ -32756,7 +34470,7 @@ ${tasks.length} task(s) shown`));
|
|
|
32756
34470
|
program2.command("blame <file>").description("Show which tasks/agents touched a file and why \u2014 combines task_files + task_commits").action(async (filePath) => {
|
|
32757
34471
|
const globalOpts = program2.opts();
|
|
32758
34472
|
const { findTasksByFile: findTasksByFile2 } = await Promise.resolve().then(() => (init_task_files(), exports_task_files));
|
|
32759
|
-
const { getTask:
|
|
34473
|
+
const { getTask: getTask2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
32760
34474
|
const db = getDatabase();
|
|
32761
34475
|
const taskFiles = findTasksByFile2(filePath, db);
|
|
32762
34476
|
const commitRows = db.query("SELECT tc.*, t.title, t.short_id FROM task_commits tc JOIN tasks t ON t.id = tc.task_id WHERE tc.files_changed LIKE ? ORDER BY tc.committed_at DESC").all(`%${filePath}%`);
|
|
@@ -32770,7 +34484,7 @@ Blame: ${filePath}
|
|
|
32770
34484
|
if (taskFiles.length > 0) {
|
|
32771
34485
|
console.log(chalk6.bold("Task File Links:"));
|
|
32772
34486
|
for (const tf of taskFiles) {
|
|
32773
|
-
const task =
|
|
34487
|
+
const task = getTask2(tf.task_id, db);
|
|
32774
34488
|
const title = task ? task.title : "unknown";
|
|
32775
34489
|
const sid = task?.short_id || tf.task_id.slice(0, 8);
|
|
32776
34490
|
console.log(` ${chalk6.cyan(sid)} ${title} \u2014 ${chalk6.dim(tf.role || "file")} ${chalk6.dim(tf.updated_at)}`);
|
|
@@ -32804,74 +34518,10 @@ Commit Links (${commitRows.length}):`));
|
|
|
32804
34518
|
init_database();
|
|
32805
34519
|
init_tasks();
|
|
32806
34520
|
init_audit();
|
|
32807
|
-
|
|
32808
|
-
|
|
32809
|
-
// src/db/handoffs.ts
|
|
32810
|
-
init_database();
|
|
32811
|
-
function createHandoff(input, db) {
|
|
32812
|
-
const d = db || getDatabase();
|
|
32813
|
-
const id = uuid();
|
|
32814
|
-
const timestamp = now();
|
|
32815
|
-
d.run(`INSERT INTO handoffs (id, agent_id, project_id, summary, completed, in_progress, blockers, next_steps, created_at)
|
|
32816
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
32817
|
-
id,
|
|
32818
|
-
input.agent_id || null,
|
|
32819
|
-
input.project_id || null,
|
|
32820
|
-
input.summary,
|
|
32821
|
-
input.completed ? JSON.stringify(input.completed) : null,
|
|
32822
|
-
input.in_progress ? JSON.stringify(input.in_progress) : null,
|
|
32823
|
-
input.blockers ? JSON.stringify(input.blockers) : null,
|
|
32824
|
-
input.next_steps ? JSON.stringify(input.next_steps) : null,
|
|
32825
|
-
timestamp
|
|
32826
|
-
]);
|
|
32827
|
-
return {
|
|
32828
|
-
id,
|
|
32829
|
-
agent_id: input.agent_id || null,
|
|
32830
|
-
project_id: input.project_id || null,
|
|
32831
|
-
summary: input.summary,
|
|
32832
|
-
completed: input.completed || null,
|
|
32833
|
-
in_progress: input.in_progress || null,
|
|
32834
|
-
blockers: input.blockers || null,
|
|
32835
|
-
next_steps: input.next_steps || null,
|
|
32836
|
-
created_at: timestamp
|
|
32837
|
-
};
|
|
32838
|
-
}
|
|
32839
|
-
function rowToHandoff(row) {
|
|
32840
|
-
return {
|
|
32841
|
-
...row,
|
|
32842
|
-
completed: row.completed ? JSON.parse(row.completed) : null,
|
|
32843
|
-
in_progress: row.in_progress ? JSON.parse(row.in_progress) : null,
|
|
32844
|
-
blockers: row.blockers ? JSON.parse(row.blockers) : null,
|
|
32845
|
-
next_steps: row.next_steps ? JSON.parse(row.next_steps) : null
|
|
32846
|
-
};
|
|
32847
|
-
}
|
|
32848
|
-
function listHandoffs(projectId, limit = 10, db) {
|
|
32849
|
-
const d = db || getDatabase();
|
|
32850
|
-
if (projectId) {
|
|
32851
|
-
return d.query("SELECT * FROM handoffs WHERE project_id = ? ORDER BY rowid DESC LIMIT ?").all(projectId, limit).map(rowToHandoff);
|
|
32852
|
-
}
|
|
32853
|
-
return d.query("SELECT * FROM handoffs ORDER BY rowid DESC LIMIT ?").all(limit).map(rowToHandoff);
|
|
32854
|
-
}
|
|
32855
|
-
function getLatestHandoff(agentId, projectId, db) {
|
|
32856
|
-
const d = db || getDatabase();
|
|
32857
|
-
let query = "SELECT * FROM handoffs WHERE 1=1";
|
|
32858
|
-
const params = [];
|
|
32859
|
-
if (agentId) {
|
|
32860
|
-
query += " AND agent_id = ?";
|
|
32861
|
-
params.push(agentId);
|
|
32862
|
-
}
|
|
32863
|
-
if (projectId) {
|
|
32864
|
-
query += " AND project_id = ?";
|
|
32865
|
-
params.push(projectId);
|
|
32866
|
-
}
|
|
32867
|
-
query += " ORDER BY rowid DESC LIMIT 1";
|
|
32868
|
-
const row = d.query(query).get(...params);
|
|
32869
|
-
return row ? rowToHandoff(row) : null;
|
|
32870
|
-
}
|
|
32871
|
-
|
|
32872
|
-
// src/cli/commands/query-commands.ts
|
|
34521
|
+
init_handoffs();
|
|
32873
34522
|
init_recurrence();
|
|
32874
34523
|
init_helpers();
|
|
34524
|
+
import chalk7 from "chalk";
|
|
32875
34525
|
function registerQueryCommands(program2) {
|
|
32876
34526
|
program2.command("next").description("Show the best pending task to work on next").option("--agent <id>", "Prefer tasks assigned to this agent").option("--project <id>", "Filter to project").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
32877
34527
|
const globalOpts = program2.opts();
|
|
@@ -33160,7 +34810,7 @@ No task claimed (nothing available).`));
|
|
|
33160
34810
|
const globalOpts = program2.opts();
|
|
33161
34811
|
const resolvedId = resolveTaskId(id);
|
|
33162
34812
|
const db = getDatabase();
|
|
33163
|
-
const task =
|
|
34813
|
+
const task = getTask(resolvedId, db);
|
|
33164
34814
|
if (!task) {
|
|
33165
34815
|
console.error(chalk7.red(`Task not found: ${id}`));
|
|
33166
34816
|
process.exit(1);
|
|
@@ -33180,7 +34830,7 @@ No task claimed (nothing available).`));
|
|
|
33180
34830
|
const globalOpts = program2.opts();
|
|
33181
34831
|
const resolvedId = resolveTaskId(id);
|
|
33182
34832
|
const db = getDatabase();
|
|
33183
|
-
const task =
|
|
34833
|
+
const task = getTask(resolvedId, db);
|
|
33184
34834
|
if (!task) {
|
|
33185
34835
|
console.error(chalk7.red(`Task not found: ${id}`));
|
|
33186
34836
|
process.exit(1);
|
|
@@ -33200,7 +34850,7 @@ No task claimed (nothing available).`));
|
|
|
33200
34850
|
const globalOpts = program2.opts();
|
|
33201
34851
|
const resolvedId = resolveTaskId(id);
|
|
33202
34852
|
const db = getDatabase();
|
|
33203
|
-
const task =
|
|
34853
|
+
const task = getTask(resolvedId, db);
|
|
33204
34854
|
if (!task) {
|
|
33205
34855
|
console.error(chalk7.red(`Task not found: ${id}`));
|
|
33206
34856
|
process.exit(1);
|
|
@@ -33221,7 +34871,7 @@ No task claimed (nothing available).`));
|
|
|
33221
34871
|
const globalOpts = program2.opts();
|
|
33222
34872
|
const resolvedId = resolveTaskId(id);
|
|
33223
34873
|
const db = getDatabase();
|
|
33224
|
-
const task =
|
|
34874
|
+
const task = getTask(resolvedId, db);
|
|
33225
34875
|
if (!task) {
|
|
33226
34876
|
console.error(chalk7.red(`Task not found: ${id}`));
|
|
33227
34877
|
process.exit(1);
|
|
@@ -34384,7 +36034,7 @@ init_tasks();
|
|
|
34384
36034
|
init_helpers();
|
|
34385
36035
|
import chalk9 from "chalk";
|
|
34386
36036
|
import { execSync as execSync3 } from "child_process";
|
|
34387
|
-
import { existsSync as
|
|
36037
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, chmodSync } from "fs";
|
|
34388
36038
|
import { dirname as dirname7, join as join13 } from "path";
|
|
34389
36039
|
var HOME2 = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
34390
36040
|
function getMcpBinaryPath() {
|
|
@@ -34394,12 +36044,12 @@ function getMcpBinaryPath() {
|
|
|
34394
36044
|
return p;
|
|
34395
36045
|
} catch {}
|
|
34396
36046
|
const bunBin = join13(HOME2, ".bun", "bin", "todos-mcp");
|
|
34397
|
-
if (
|
|
36047
|
+
if (existsSync10(bunBin))
|
|
34398
36048
|
return bunBin;
|
|
34399
36049
|
return "todos-mcp";
|
|
34400
36050
|
}
|
|
34401
36051
|
function readJsonFile2(path) {
|
|
34402
|
-
if (!
|
|
36052
|
+
if (!existsSync10(path))
|
|
34403
36053
|
return {};
|
|
34404
36054
|
try {
|
|
34405
36055
|
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
@@ -34409,19 +36059,19 @@ function readJsonFile2(path) {
|
|
|
34409
36059
|
}
|
|
34410
36060
|
function writeJsonFile2(path, data) {
|
|
34411
36061
|
const dir = dirname7(path);
|
|
34412
|
-
if (!
|
|
36062
|
+
if (!existsSync10(dir))
|
|
34413
36063
|
mkdirSync5(dir, { recursive: true });
|
|
34414
36064
|
writeFileSync5(path, JSON.stringify(data, null, 2) + `
|
|
34415
36065
|
`);
|
|
34416
36066
|
}
|
|
34417
36067
|
function readTomlFile(path) {
|
|
34418
|
-
if (!
|
|
36068
|
+
if (!existsSync10(path))
|
|
34419
36069
|
return "";
|
|
34420
36070
|
return readFileSync8(path, "utf-8");
|
|
34421
36071
|
}
|
|
34422
36072
|
function writeTomlFile(path, content) {
|
|
34423
36073
|
const dir = dirname7(path);
|
|
34424
|
-
if (!
|
|
36074
|
+
if (!existsSync10(dir))
|
|
34425
36075
|
mkdirSync5(dir, { recursive: true });
|
|
34426
36076
|
writeFileSync5(path, content);
|
|
34427
36077
|
}
|
|
@@ -34565,7 +36215,7 @@ function registerMcpHooksCommands(program2) {
|
|
|
34565
36215
|
todosBin = p;
|
|
34566
36216
|
} catch {}
|
|
34567
36217
|
const hooksDir = join13(process.cwd(), ".claude", "hooks");
|
|
34568
|
-
if (!
|
|
36218
|
+
if (!existsSync10(hooksDir))
|
|
34569
36219
|
mkdirSync5(hooksDir, { recursive: true });
|
|
34570
36220
|
const hookScript = `#!/usr/bin/env bash
|
|
34571
36221
|
# Auto-generated by: todos hooks install
|
|
@@ -34687,7 +36337,7 @@ exit 0
|
|
|
34687
36337
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
34688
36338
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
34689
36339
|
const marker = "# todos-auto-link";
|
|
34690
|
-
if (
|
|
36340
|
+
if (existsSync10(hookPath)) {
|
|
34691
36341
|
const existing = readFileSync8(hookPath, "utf-8");
|
|
34692
36342
|
if (existing.includes(marker)) {
|
|
34693
36343
|
console.log(chalk9.yellow("Hook already installed."));
|
|
@@ -34715,7 +36365,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
34715
36365
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
34716
36366
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
34717
36367
|
const marker = "# todos-auto-link";
|
|
34718
|
-
if (!
|
|
36368
|
+
if (!existsSync10(hookPath)) {
|
|
34719
36369
|
console.log(chalk9.dim("No post-commit hook found."));
|
|
34720
36370
|
return;
|
|
34721
36371
|
}
|
|
@@ -35132,6 +36782,91 @@ Sync complete: ${totalPulled} task(s) pulled.`));
|
|
|
35132
36782
|
});
|
|
35133
36783
|
}
|
|
35134
36784
|
|
|
36785
|
+
// src/cli/commands/api-key-commands.ts
|
|
36786
|
+
init_api_keys();
|
|
36787
|
+
init_helpers();
|
|
36788
|
+
import chalk12 from "chalk";
|
|
36789
|
+
function registerApiKeyCommands(program2) {
|
|
36790
|
+
const apiKeys = program2.command("api-keys").alias("api-key").description("Generate, list, and revoke API keys for secured app/API access");
|
|
36791
|
+
apiKeys.command("create <name>").alias("generate").description("Generate a new API key. The plaintext key is shown once.").option("--expires-at <iso>", "Optional ISO timestamp when this key expires").option("--permissions <list>", "Comma-separated permissions (default: *)").action((name, opts) => {
|
|
36792
|
+
const globalOpts = program2.opts();
|
|
36793
|
+
try {
|
|
36794
|
+
const permissions = opts.permissions ? opts.permissions.split(",").map((item) => item.trim()).filter(Boolean) : undefined;
|
|
36795
|
+
const created = createApiKey({ name, permissions, expires_at: opts.expiresAt || null });
|
|
36796
|
+
if (globalOpts.json) {
|
|
36797
|
+
output(created, true);
|
|
36798
|
+
return;
|
|
36799
|
+
}
|
|
36800
|
+
console.log(chalk12.green("API key generated:"));
|
|
36801
|
+
console.log(` ${chalk12.dim("ID:")} ${created.record.id}`);
|
|
36802
|
+
console.log(` ${chalk12.dim("Name:")} ${created.record.name}`);
|
|
36803
|
+
console.log(` ${chalk12.dim("Prefix:")} ${created.record.prefix}`);
|
|
36804
|
+
console.log();
|
|
36805
|
+
console.log(chalk12.yellow("Copy this key now. It will not be shown again:"));
|
|
36806
|
+
console.log(created.key);
|
|
36807
|
+
} catch (e) {
|
|
36808
|
+
handleError(e);
|
|
36809
|
+
}
|
|
36810
|
+
});
|
|
36811
|
+
apiKeys.command("list").description("List API keys without showing plaintext secrets").option("--include-revoked", "Include revoked keys").action((opts) => {
|
|
36812
|
+
const globalOpts = program2.opts();
|
|
36813
|
+
try {
|
|
36814
|
+
const keys = listApiKeys({ include_revoked: opts.includeRevoked ?? false });
|
|
36815
|
+
if (globalOpts.json) {
|
|
36816
|
+
output(keys, true);
|
|
36817
|
+
return;
|
|
36818
|
+
}
|
|
36819
|
+
if (keys.length === 0) {
|
|
36820
|
+
console.log(chalk12.dim("No API keys found."));
|
|
36821
|
+
return;
|
|
36822
|
+
}
|
|
36823
|
+
for (const key of keys) {
|
|
36824
|
+
const state = key.revoked_at ? chalk12.red("revoked") : key.expires_at && key.expires_at < new Date().toISOString() ? chalk12.yellow("expired") : chalk12.green("active");
|
|
36825
|
+
console.log(`${chalk12.cyan(key.id)} ${chalk12.bold(key.name)} ${chalk12.dim(key.prefix)} ${state}`);
|
|
36826
|
+
if (key.last_used_at)
|
|
36827
|
+
console.log(chalk12.dim(` last used: ${key.last_used_at}`));
|
|
36828
|
+
if (key.expires_at)
|
|
36829
|
+
console.log(chalk12.dim(` expires: ${key.expires_at}`));
|
|
36830
|
+
}
|
|
36831
|
+
} catch (e) {
|
|
36832
|
+
handleError(e);
|
|
36833
|
+
}
|
|
36834
|
+
});
|
|
36835
|
+
apiKeys.command("revoke <id-or-prefix>").description("Revoke an API key by id or prefix").action((idOrPrefix) => {
|
|
36836
|
+
const globalOpts = program2.opts();
|
|
36837
|
+
try {
|
|
36838
|
+
const revoked = revokeApiKey(idOrPrefix);
|
|
36839
|
+
if (!revoked) {
|
|
36840
|
+
throw new Error(`API key not found: ${idOrPrefix}`);
|
|
36841
|
+
}
|
|
36842
|
+
if (globalOpts.json) {
|
|
36843
|
+
output(revoked, true);
|
|
36844
|
+
return;
|
|
36845
|
+
}
|
|
36846
|
+
console.log(chalk12.green(`Revoked API key: ${revoked.name} (${revoked.prefix})`));
|
|
36847
|
+
} catch (e) {
|
|
36848
|
+
handleError(e);
|
|
36849
|
+
}
|
|
36850
|
+
});
|
|
36851
|
+
apiKeys.command("verify <key>").description("Verify an API key locally without printing stored hashes").action((key) => {
|
|
36852
|
+
const globalOpts = program2.opts();
|
|
36853
|
+
try {
|
|
36854
|
+
const record = verifyApiKey(key);
|
|
36855
|
+
if (globalOpts.json) {
|
|
36856
|
+
output({ valid: Boolean(record), key: record }, true);
|
|
36857
|
+
return;
|
|
36858
|
+
}
|
|
36859
|
+
if (!record) {
|
|
36860
|
+
console.error(chalk12.red("API key is invalid, revoked, or expired."));
|
|
36861
|
+
process.exit(1);
|
|
36862
|
+
}
|
|
36863
|
+
console.log(chalk12.green(`API key valid: ${record.name} (${record.prefix})`));
|
|
36864
|
+
} catch (e) {
|
|
36865
|
+
handleError(e);
|
|
36866
|
+
}
|
|
36867
|
+
});
|
|
36868
|
+
}
|
|
36869
|
+
|
|
35135
36870
|
// src/cli/index.tsx
|
|
35136
36871
|
var program2 = new Command;
|
|
35137
36872
|
program2.name("todos").description("Universal task management for AI coding agents").version(getPackageVersion()).option("--project <path>", "Project path").option("-j, --json", "Output as JSON").option("--agent <name>", "Agent name").option("--session <id>", "Session ID");
|
|
@@ -35145,4 +36880,5 @@ registerCloudCommands2(program2);
|
|
|
35145
36880
|
registerMcpHooksCommands(program2);
|
|
35146
36881
|
registerDispatchCommands(program2);
|
|
35147
36882
|
registerMachineCommands(program2);
|
|
36883
|
+
registerApiKeyCommands(program2);
|
|
35148
36884
|
program2.parse();
|