@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/index.js
CHANGED
|
@@ -808,6 +808,42 @@ var init_migrations = __esm(() => {
|
|
|
808
808
|
ALTER TABLE tasks ADD COLUMN current_step TEXT;
|
|
809
809
|
ALTER TABLE tasks ADD COLUMN total_steps INTEGER;
|
|
810
810
|
INSERT OR IGNORE INTO _migrations (id) VALUES (48);
|
|
811
|
+
`,
|
|
812
|
+
`
|
|
813
|
+
CREATE TABLE IF NOT EXISTS cycles (
|
|
814
|
+
id TEXT PRIMARY KEY,
|
|
815
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
816
|
+
number INTEGER NOT NULL,
|
|
817
|
+
start_date TEXT NOT NULL,
|
|
818
|
+
end_date TEXT NOT NULL,
|
|
819
|
+
duration_weeks INTEGER NOT NULL DEFAULT 1,
|
|
820
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
|
|
821
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
822
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
823
|
+
);
|
|
824
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_project ON cycles(project_id);
|
|
825
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_number ON cycles(number);
|
|
826
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_status ON cycles(status);
|
|
827
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_dates ON cycles(start_date, end_date);
|
|
828
|
+
ALTER TABLE tasks ADD COLUMN cycle_id TEXT REFERENCES cycles(id) ON DELETE SET NULL;
|
|
829
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_cycle ON tasks(cycle_id) WHERE cycle_id IS NOT NULL;
|
|
830
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (49);
|
|
831
|
+
`,
|
|
832
|
+
`
|
|
833
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
834
|
+
id TEXT PRIMARY KEY,
|
|
835
|
+
name TEXT NOT NULL,
|
|
836
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
837
|
+
prefix TEXT NOT NULL UNIQUE,
|
|
838
|
+
permissions TEXT NOT NULL DEFAULT '["*"]',
|
|
839
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
840
|
+
last_used_at TEXT,
|
|
841
|
+
expires_at TEXT,
|
|
842
|
+
revoked_at TEXT
|
|
843
|
+
);
|
|
844
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(prefix);
|
|
845
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(revoked_at, expires_at);
|
|
846
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (50);
|
|
811
847
|
`
|
|
812
848
|
];
|
|
813
849
|
});
|
|
@@ -1206,6 +1242,20 @@ function ensureSchema(db) {
|
|
|
1206
1242
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_cycles_dates ON cycles(start_date, end_date)");
|
|
1207
1243
|
ensureColumn("tasks", "cycle_id", "TEXT REFERENCES cycles(id) ON DELETE SET NULL");
|
|
1208
1244
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_cycle ON tasks(cycle_id) WHERE cycle_id IS NOT NULL");
|
|
1245
|
+
ensureTable("api_keys", `
|
|
1246
|
+
CREATE TABLE api_keys (
|
|
1247
|
+
id TEXT PRIMARY KEY,
|
|
1248
|
+
name TEXT NOT NULL,
|
|
1249
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
1250
|
+
prefix TEXT NOT NULL UNIQUE,
|
|
1251
|
+
permissions TEXT NOT NULL DEFAULT '["*"]',
|
|
1252
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1253
|
+
last_used_at TEXT,
|
|
1254
|
+
expires_at TEXT,
|
|
1255
|
+
revoked_at TEXT
|
|
1256
|
+
)`);
|
|
1257
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(prefix)");
|
|
1258
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(revoked_at, expires_at)");
|
|
1209
1259
|
}
|
|
1210
1260
|
function backfillTaskTags(db) {
|
|
1211
1261
|
try {
|
|
@@ -3043,8 +3093,8 @@ function createTask(input, db) {
|
|
|
3043
3093
|
let id = uuid();
|
|
3044
3094
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
3045
3095
|
try {
|
|
3046
|
-
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)
|
|
3047
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3096
|
+
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)
|
|
3097
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3048
3098
|
id,
|
|
3049
3099
|
null,
|
|
3050
3100
|
input.project_id || null,
|
|
@@ -3066,6 +3116,10 @@ function createTask(input, db) {
|
|
|
3066
3116
|
timestamp,
|
|
3067
3117
|
input.due_at || null,
|
|
3068
3118
|
input.estimated_minutes || null,
|
|
3119
|
+
input.confidence ?? null,
|
|
3120
|
+
input.retry_count ?? 0,
|
|
3121
|
+
input.max_retries ?? 3,
|
|
3122
|
+
input.retry_after ?? null,
|
|
3069
3123
|
input.requires_approval ? 1 : 0,
|
|
3070
3124
|
null,
|
|
3071
3125
|
null,
|
|
@@ -3310,8 +3364,10 @@ function updateTask(id, input, db) {
|
|
|
3310
3364
|
if (task.version !== input.version) {
|
|
3311
3365
|
throw new VersionConflictError(id, input.version, task.version);
|
|
3312
3366
|
}
|
|
3367
|
+
const timestamp = now();
|
|
3368
|
+
const completionTimestamp = input.completed_at ?? timestamp;
|
|
3313
3369
|
const sets = ["version = version + 1", "updated_at = ?"];
|
|
3314
|
-
const params = [
|
|
3370
|
+
const params = [timestamp];
|
|
3315
3371
|
if (input.title !== undefined) {
|
|
3316
3372
|
sets.push("title = ?");
|
|
3317
3373
|
params.push(input.title);
|
|
@@ -3328,13 +3384,17 @@ function updateTask(id, input, db) {
|
|
|
3328
3384
|
params.push(input.status);
|
|
3329
3385
|
if (input.status === "completed") {
|
|
3330
3386
|
sets.push("completed_at = ?");
|
|
3331
|
-
params.push(
|
|
3387
|
+
params.push(completionTimestamp);
|
|
3332
3388
|
}
|
|
3333
3389
|
}
|
|
3334
3390
|
if (input.priority !== undefined) {
|
|
3335
3391
|
sets.push("priority = ?");
|
|
3336
3392
|
params.push(input.priority);
|
|
3337
3393
|
}
|
|
3394
|
+
if (input.project_id !== undefined) {
|
|
3395
|
+
sets.push("project_id = ?");
|
|
3396
|
+
params.push(input.project_id);
|
|
3397
|
+
}
|
|
3338
3398
|
if (input.assigned_to !== undefined) {
|
|
3339
3399
|
sets.push("assigned_to = ?");
|
|
3340
3400
|
params.push(input.assigned_to);
|
|
@@ -3363,6 +3423,30 @@ function updateTask(id, input, db) {
|
|
|
3363
3423
|
sets.push("estimated_minutes = ?");
|
|
3364
3424
|
params.push(input.estimated_minutes);
|
|
3365
3425
|
}
|
|
3426
|
+
if (input.actual_minutes !== undefined) {
|
|
3427
|
+
sets.push("actual_minutes = ?");
|
|
3428
|
+
params.push(input.actual_minutes);
|
|
3429
|
+
}
|
|
3430
|
+
if (input.completed_at !== undefined && input.status !== "completed") {
|
|
3431
|
+
sets.push("completed_at = ?");
|
|
3432
|
+
params.push(input.completed_at);
|
|
3433
|
+
}
|
|
3434
|
+
if (input.confidence !== undefined) {
|
|
3435
|
+
sets.push("confidence = ?");
|
|
3436
|
+
params.push(input.confidence);
|
|
3437
|
+
}
|
|
3438
|
+
if (input.retry_count !== undefined) {
|
|
3439
|
+
sets.push("retry_count = ?");
|
|
3440
|
+
params.push(input.retry_count);
|
|
3441
|
+
}
|
|
3442
|
+
if (input.max_retries !== undefined) {
|
|
3443
|
+
sets.push("max_retries = ?");
|
|
3444
|
+
params.push(input.max_retries);
|
|
3445
|
+
}
|
|
3446
|
+
if (input.retry_after !== undefined) {
|
|
3447
|
+
sets.push("retry_after = ?");
|
|
3448
|
+
params.push(input.retry_after);
|
|
3449
|
+
}
|
|
3366
3450
|
if (input.requires_approval !== undefined) {
|
|
3367
3451
|
sets.push("requires_approval = ?");
|
|
3368
3452
|
params.push(input.requires_approval ? 1 : 0);
|
|
@@ -3413,11 +3497,16 @@ function updateTask(id, input, db) {
|
|
|
3413
3497
|
tags: input.tags ?? task.tags,
|
|
3414
3498
|
metadata: input.metadata ?? task.metadata,
|
|
3415
3499
|
version: task.version + 1,
|
|
3416
|
-
updated_at:
|
|
3417
|
-
completed_at: input.status === "completed" ?
|
|
3500
|
+
updated_at: timestamp,
|
|
3501
|
+
completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : task.completed_at,
|
|
3502
|
+
actual_minutes: input.actual_minutes ?? task.actual_minutes,
|
|
3503
|
+
confidence: input.confidence !== undefined ? input.confidence : task.confidence,
|
|
3504
|
+
retry_count: input.retry_count ?? task.retry_count,
|
|
3505
|
+
max_retries: input.max_retries ?? task.max_retries,
|
|
3506
|
+
retry_after: input.retry_after !== undefined ? input.retry_after : task.retry_after,
|
|
3418
3507
|
requires_approval: input.requires_approval !== undefined ? input.requires_approval : task.requires_approval,
|
|
3419
3508
|
approved_by: input.approved_by ?? task.approved_by,
|
|
3420
|
-
approved_at: input.approved_by ?
|
|
3509
|
+
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
3421
3510
|
};
|
|
3422
3511
|
}
|
|
3423
3512
|
function deleteTask(id, db) {
|
|
@@ -4145,7 +4234,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
4145
4234
|
completionMeta._completion = { confidence: options.confidence };
|
|
4146
4235
|
}
|
|
4147
4236
|
const hasMeta = Object.keys(completionMeta).length > 0;
|
|
4148
|
-
const timestamp = now();
|
|
4237
|
+
const timestamp = options?.completed_at || now();
|
|
4149
4238
|
const confidence = options?.confidence !== undefined ? options.confidence : null;
|
|
4150
4239
|
const tx = d.transaction(() => {
|
|
4151
4240
|
if (hasMeta) {
|
|
@@ -4977,6 +5066,214 @@ function deleteComment(id, db) {
|
|
|
4977
5066
|
}
|
|
4978
5067
|
// src/db/agents.ts
|
|
4979
5068
|
init_database();
|
|
5069
|
+
|
|
5070
|
+
// src/db/agent-names.ts
|
|
5071
|
+
init_database();
|
|
5072
|
+
|
|
5073
|
+
class InvalidAgentNameError extends Error {
|
|
5074
|
+
suggestions;
|
|
5075
|
+
constructor(name, reason, suggestions = []) {
|
|
5076
|
+
super(`Invalid agent name "${name}": ${reason}${suggestions.length > 0 ? `. Try: ${suggestions.join(", ")}` : ""}`);
|
|
5077
|
+
this.name = "InvalidAgentNameError";
|
|
5078
|
+
this.suggestions = suggestions;
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
var ROMAN_AGENT_NAMES = [
|
|
5082
|
+
"caesar",
|
|
5083
|
+
"augustus",
|
|
5084
|
+
"marcus",
|
|
5085
|
+
"brutus",
|
|
5086
|
+
"cicero",
|
|
5087
|
+
"cato",
|
|
5088
|
+
"nero",
|
|
5089
|
+
"claudius",
|
|
5090
|
+
"tiberius",
|
|
5091
|
+
"hadrian",
|
|
5092
|
+
"trajan",
|
|
5093
|
+
"vespasian",
|
|
5094
|
+
"domitian",
|
|
5095
|
+
"caligula",
|
|
5096
|
+
"commodus",
|
|
5097
|
+
"livia",
|
|
5098
|
+
"julia",
|
|
5099
|
+
"octavia",
|
|
5100
|
+
"claudia",
|
|
5101
|
+
"agrippina",
|
|
5102
|
+
"cornelia",
|
|
5103
|
+
"valeria",
|
|
5104
|
+
"fulvia",
|
|
5105
|
+
"hortensia",
|
|
5106
|
+
"fabia"
|
|
5107
|
+
];
|
|
5108
|
+
var GREEK_AGENT_NAMES = [
|
|
5109
|
+
"athena",
|
|
5110
|
+
"apollo",
|
|
5111
|
+
"artemis",
|
|
5112
|
+
"hera",
|
|
5113
|
+
"iris",
|
|
5114
|
+
"hector",
|
|
5115
|
+
"achilles",
|
|
5116
|
+
"odysseus",
|
|
5117
|
+
"theseus",
|
|
5118
|
+
"pericles",
|
|
5119
|
+
"solon",
|
|
5120
|
+
"sophia",
|
|
5121
|
+
"thalia",
|
|
5122
|
+
"calliope",
|
|
5123
|
+
"clio",
|
|
5124
|
+
"phoebe",
|
|
5125
|
+
"daphne",
|
|
5126
|
+
"leonidas",
|
|
5127
|
+
"andromeda",
|
|
5128
|
+
"cassander"
|
|
5129
|
+
];
|
|
5130
|
+
var NICE_AGENT_NAMES = [
|
|
5131
|
+
"atlas",
|
|
5132
|
+
"aurora",
|
|
5133
|
+
"ember",
|
|
5134
|
+
"nova",
|
|
5135
|
+
"orion",
|
|
5136
|
+
"rhea",
|
|
5137
|
+
"selene",
|
|
5138
|
+
"sirius",
|
|
5139
|
+
"vesper",
|
|
5140
|
+
"zephyr"
|
|
5141
|
+
];
|
|
5142
|
+
var PREFERRED_AGENT_NAMES = [
|
|
5143
|
+
...ROMAN_AGENT_NAMES,
|
|
5144
|
+
...GREEK_AGENT_NAMES,
|
|
5145
|
+
...NICE_AGENT_NAMES
|
|
5146
|
+
];
|
|
5147
|
+
var RESERVED_GENERIC_NAMES = new Set([
|
|
5148
|
+
"agent",
|
|
5149
|
+
"agents",
|
|
5150
|
+
"ai",
|
|
5151
|
+
"assistant",
|
|
5152
|
+
"bot",
|
|
5153
|
+
"coder",
|
|
5154
|
+
"default",
|
|
5155
|
+
"helper",
|
|
5156
|
+
"model",
|
|
5157
|
+
"system",
|
|
5158
|
+
"user",
|
|
5159
|
+
"worker"
|
|
5160
|
+
]);
|
|
5161
|
+
var NUMERIC_SUFFIX_RE = /[-_]\d+$/;
|
|
5162
|
+
var ONE_WORD_NAME_RE = /^[a-z]+$/;
|
|
5163
|
+
function normalizeAgentNameInput(name) {
|
|
5164
|
+
return name.trim().toLowerCase();
|
|
5165
|
+
}
|
|
5166
|
+
function hasGeneratedNumericSuffix(name) {
|
|
5167
|
+
return NUMERIC_SUFFIX_RE.test(normalizeAgentNameInput(name));
|
|
5168
|
+
}
|
|
5169
|
+
function isGenericAgentName(name) {
|
|
5170
|
+
const normalized = normalizeAgentNameInput(name);
|
|
5171
|
+
if (RESERVED_GENERIC_NAMES.has(normalized))
|
|
5172
|
+
return true;
|
|
5173
|
+
for (const generic of RESERVED_GENERIC_NAMES) {
|
|
5174
|
+
if (normalized === `${generic}s`)
|
|
5175
|
+
return true;
|
|
5176
|
+
if (normalized.match(new RegExp(`^${generic}\\d+$`)))
|
|
5177
|
+
return true;
|
|
5178
|
+
if (normalized.match(new RegExp(`^${generic}[-_]\\d+$`)))
|
|
5179
|
+
return true;
|
|
5180
|
+
}
|
|
5181
|
+
return false;
|
|
5182
|
+
}
|
|
5183
|
+
function isBlockedAgentName(name) {
|
|
5184
|
+
const normalized = normalizeAgentNameInput(name);
|
|
5185
|
+
return isGenericAgentName(normalized) || hasGeneratedNumericSuffix(normalized) || !ONE_WORD_NAME_RE.test(normalized);
|
|
5186
|
+
}
|
|
5187
|
+
function suggestAgentNames(existingNames = []) {
|
|
5188
|
+
const existing = new Set([...existingNames].map(normalizeAgentNameInput));
|
|
5189
|
+
return PREFERRED_AGENT_NAMES.filter((name) => !existing.has(name));
|
|
5190
|
+
}
|
|
5191
|
+
function validateAgentName(name, existingNames = []) {
|
|
5192
|
+
const normalized = normalizeAgentNameInput(name);
|
|
5193
|
+
const suggestions = suggestAgentNames(existingNames).slice(0, 5);
|
|
5194
|
+
if (!normalized) {
|
|
5195
|
+
throw new InvalidAgentNameError(name, "choose a real one-word name instead of an empty value", suggestions);
|
|
5196
|
+
}
|
|
5197
|
+
if (/\s/.test(normalized)) {
|
|
5198
|
+
throw new InvalidAgentNameError(name, "use a single word, preferably a Roman or Greek name", suggestions);
|
|
5199
|
+
}
|
|
5200
|
+
if (normalized.length < 3) {
|
|
5201
|
+
throw new InvalidAgentNameError(name, "use a more distinctive name with at least three characters", suggestions);
|
|
5202
|
+
}
|
|
5203
|
+
if (isGenericAgentName(normalized)) {
|
|
5204
|
+
throw new InvalidAgentNameError(name, "generic names like agent, agent-1, assistant, or worker-2 are reserved", suggestions);
|
|
5205
|
+
}
|
|
5206
|
+
if (hasGeneratedNumericSuffix(normalized)) {
|
|
5207
|
+
throw new InvalidAgentNameError(name, "numbered suffix names are not allowed; pick a distinct human-readable name", suggestions);
|
|
5208
|
+
}
|
|
5209
|
+
if (!ONE_WORD_NAME_RE.test(normalized)) {
|
|
5210
|
+
throw new InvalidAgentNameError(name, "use one word made of letters only, preferably a Roman or Greek name", suggestions);
|
|
5211
|
+
}
|
|
5212
|
+
return normalized;
|
|
5213
|
+
}
|
|
5214
|
+
function tableHasColumn(db, table, column) {
|
|
5215
|
+
try {
|
|
5216
|
+
return db.query(`PRAGMA table_info(${table})`).all().some((row) => row.name === column);
|
|
5217
|
+
} catch {
|
|
5218
|
+
return false;
|
|
5219
|
+
}
|
|
5220
|
+
}
|
|
5221
|
+
function updateReferences(db, oldName, newName) {
|
|
5222
|
+
const refs = [
|
|
5223
|
+
["tasks", "assigned_to"],
|
|
5224
|
+
["tasks", "agent_id"],
|
|
5225
|
+
["tasks", "locked_by"],
|
|
5226
|
+
["tasks", "assigned_by"],
|
|
5227
|
+
["plans", "agent_id"],
|
|
5228
|
+
["sessions", "agent_id"],
|
|
5229
|
+
["task_comments", "agent_id"],
|
|
5230
|
+
["task_history", "agent_id"],
|
|
5231
|
+
["webhooks", "agent_id"],
|
|
5232
|
+
["task_files", "agent_id"],
|
|
5233
|
+
["task_time_logs", "agent_id"],
|
|
5234
|
+
["task_watchers", "agent_id"],
|
|
5235
|
+
["task_checkpoints", "agent_id"],
|
|
5236
|
+
["task_heartbeats", "agent_id"],
|
|
5237
|
+
["project_agent_roles", "agent_id"]
|
|
5238
|
+
];
|
|
5239
|
+
let changed = 0;
|
|
5240
|
+
for (const [table, column] of refs) {
|
|
5241
|
+
if (!tableHasColumn(db, table, column))
|
|
5242
|
+
continue;
|
|
5243
|
+
try {
|
|
5244
|
+
changed += db.run(`UPDATE ${table} SET ${column} = ? WHERE LOWER(${column}) = ?`, [newName, oldName]).changes;
|
|
5245
|
+
} catch {}
|
|
5246
|
+
}
|
|
5247
|
+
return changed;
|
|
5248
|
+
}
|
|
5249
|
+
function normalizeGeneratedAgentNames(db) {
|
|
5250
|
+
const rows = db.query("SELECT * FROM agents ORDER BY created_at, id").all();
|
|
5251
|
+
const existing = new Set(rows.map((agent) => normalizeAgentNameInput(agent.name)));
|
|
5252
|
+
const renamed = [];
|
|
5253
|
+
for (const agent of rows) {
|
|
5254
|
+
const oldName = normalizeAgentNameInput(agent.name);
|
|
5255
|
+
if (!isBlockedAgentName(oldName))
|
|
5256
|
+
continue;
|
|
5257
|
+
const candidates = suggestAgentNames(existing);
|
|
5258
|
+
const replacement = candidates[0];
|
|
5259
|
+
if (!replacement) {
|
|
5260
|
+
throw new Error("No safe agent names are available for normalization");
|
|
5261
|
+
}
|
|
5262
|
+
existing.delete(oldName);
|
|
5263
|
+
existing.add(replacement);
|
|
5264
|
+
db.run("UPDATE agents SET name = ?, last_seen_at = ? WHERE id = ?", [replacement, now(), agent.id]);
|
|
5265
|
+
const referenceUpdates = updateReferences(db, oldName, replacement);
|
|
5266
|
+
renamed.push({
|
|
5267
|
+
id: agent.id,
|
|
5268
|
+
old_name: oldName,
|
|
5269
|
+
new_name: replacement,
|
|
5270
|
+
reference_updates: referenceUpdates
|
|
5271
|
+
});
|
|
5272
|
+
}
|
|
5273
|
+
return renamed;
|
|
5274
|
+
}
|
|
5275
|
+
|
|
5276
|
+
// src/db/agents.ts
|
|
4980
5277
|
function getActiveWindowMs() {
|
|
4981
5278
|
const env = process.env["TODOS_AGENT_TIMEOUT_MS"];
|
|
4982
5279
|
if (env) {
|
|
@@ -4998,7 +5295,7 @@ function getAvailableNamesFromPool(pool, db) {
|
|
|
4998
5295
|
autoReleaseStaleAgents(db);
|
|
4999
5296
|
const cutoff = new Date(Date.now() - getActiveWindowMs()).toISOString();
|
|
5000
5297
|
const activeNames = new Set(db.query("SELECT name FROM agents WHERE status = 'active' AND last_seen_at > ?").all(cutoff).map((r) => r.name.toLowerCase()));
|
|
5001
|
-
return pool.filter((name) => !activeNames.has(name
|
|
5298
|
+
return pool.map(normalizeAgentNameInput).filter((name) => !activeNames.has(name));
|
|
5002
5299
|
}
|
|
5003
5300
|
function shortUuid() {
|
|
5004
5301
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -5014,7 +5311,8 @@ function rowToAgent(row) {
|
|
|
5014
5311
|
}
|
|
5015
5312
|
function registerAgent(input, db) {
|
|
5016
5313
|
const d = db || getDatabase();
|
|
5017
|
-
const
|
|
5314
|
+
const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
|
|
5315
|
+
const normalizedName = validateAgentName(input.name, existingNames);
|
|
5018
5316
|
const existing = getAgentByName(normalizedName, d);
|
|
5019
5317
|
if (existing) {
|
|
5020
5318
|
const lastSeenMs = new Date(existing.last_seen_at).getTime();
|
|
@@ -5147,7 +5445,8 @@ function updateAgent(id, input, db) {
|
|
|
5147
5445
|
const sets = ["last_seen_at = ?"];
|
|
5148
5446
|
const params = [now()];
|
|
5149
5447
|
if (input.name !== undefined) {
|
|
5150
|
-
const
|
|
5448
|
+
const existingNames = d.query("SELECT name FROM agents WHERE id != ?").all(id).map((row) => row.name);
|
|
5449
|
+
const newName = validateAgentName(input.name, existingNames);
|
|
5151
5450
|
const holder = getAgentByName(newName, d);
|
|
5152
5451
|
if (holder && holder.id !== id) {
|
|
5153
5452
|
const lastSeenMs = new Date(holder.last_seen_at).getTime();
|
|
@@ -5256,6 +5555,87 @@ function getCapableAgents(capabilities, opts, db) {
|
|
|
5256
5555
|
})).filter((entry) => entry.score >= minScore).sort((a, b) => b.score - a.score);
|
|
5257
5556
|
return opts?.limit ? scored.slice(0, opts.limit) : scored;
|
|
5258
5557
|
}
|
|
5558
|
+
// src/db/api-keys.ts
|
|
5559
|
+
init_database();
|
|
5560
|
+
import { createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
5561
|
+
function rowToRecord(row) {
|
|
5562
|
+
return {
|
|
5563
|
+
id: row.id,
|
|
5564
|
+
name: row.name,
|
|
5565
|
+
prefix: row.prefix,
|
|
5566
|
+
permissions: JSON.parse(row.permissions || '["*"]'),
|
|
5567
|
+
created_at: row.created_at,
|
|
5568
|
+
last_used_at: row.last_used_at,
|
|
5569
|
+
expires_at: row.expires_at,
|
|
5570
|
+
revoked_at: row.revoked_at
|
|
5571
|
+
};
|
|
5572
|
+
}
|
|
5573
|
+
function hashApiKey(key) {
|
|
5574
|
+
return createHash("sha256").update(key).digest("hex");
|
|
5575
|
+
}
|
|
5576
|
+
function safeEqualHex(a, b) {
|
|
5577
|
+
if (a.length !== b.length)
|
|
5578
|
+
return false;
|
|
5579
|
+
return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
5580
|
+
}
|
|
5581
|
+
function generatePlaintextKey() {
|
|
5582
|
+
return `tdos_${randomBytes(32).toString("base64url")}`;
|
|
5583
|
+
}
|
|
5584
|
+
function createApiKey(input, db) {
|
|
5585
|
+
const d = db || getDatabase();
|
|
5586
|
+
const name = input.name.trim();
|
|
5587
|
+
if (!name)
|
|
5588
|
+
throw new Error("API key name is required");
|
|
5589
|
+
const key = generatePlaintextKey();
|
|
5590
|
+
const timestamp = now();
|
|
5591
|
+
const id = uuid();
|
|
5592
|
+
const prefix = key.slice(0, 12);
|
|
5593
|
+
d.run(`INSERT INTO api_keys (id, name, key_hash, prefix, permissions, created_at, expires_at)
|
|
5594
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
5595
|
+
id,
|
|
5596
|
+
name,
|
|
5597
|
+
hashApiKey(key),
|
|
5598
|
+
prefix,
|
|
5599
|
+
JSON.stringify(input.permissions?.length ? input.permissions : ["*"]),
|
|
5600
|
+
timestamp,
|
|
5601
|
+
input.expires_at || null
|
|
5602
|
+
]);
|
|
5603
|
+
const row = d.query("SELECT * FROM api_keys WHERE id = ?").get(id);
|
|
5604
|
+
return { key, record: rowToRecord(row) };
|
|
5605
|
+
}
|
|
5606
|
+
function listApiKeys(opts, db) {
|
|
5607
|
+
const d = db || getDatabase();
|
|
5608
|
+
const includeRevoked = opts?.include_revoked ?? false;
|
|
5609
|
+
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";
|
|
5610
|
+
return d.query(sql).all().map(rowToRecord);
|
|
5611
|
+
}
|
|
5612
|
+
function hasActiveApiKeys(db) {
|
|
5613
|
+
const d = db || getDatabase();
|
|
5614
|
+
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());
|
|
5615
|
+
return (row?.count ?? 0) > 0;
|
|
5616
|
+
}
|
|
5617
|
+
function verifyApiKey(key, db) {
|
|
5618
|
+
const d = db || getDatabase();
|
|
5619
|
+
const candidateHash = hashApiKey(key);
|
|
5620
|
+
const rows = d.query("SELECT * FROM api_keys WHERE revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)").all(now());
|
|
5621
|
+
for (const row of rows) {
|
|
5622
|
+
if (!safeEqualHex(candidateHash, row.key_hash))
|
|
5623
|
+
continue;
|
|
5624
|
+
d.run("UPDATE api_keys SET last_used_at = ? WHERE id = ?", [now(), row.id]);
|
|
5625
|
+
return rowToRecord({ ...row, last_used_at: now() });
|
|
5626
|
+
}
|
|
5627
|
+
return null;
|
|
5628
|
+
}
|
|
5629
|
+
function revokeApiKey(idOrPrefix, db) {
|
|
5630
|
+
const d = db || getDatabase();
|
|
5631
|
+
const identifier = idOrPrefix.trim();
|
|
5632
|
+
const row = d.query("SELECT * FROM api_keys WHERE id = ? OR prefix = ?").get(identifier, identifier);
|
|
5633
|
+
if (!row)
|
|
5634
|
+
return null;
|
|
5635
|
+
d.run("UPDATE api_keys SET revoked_at = ? WHERE id = ?", [now(), row.id]);
|
|
5636
|
+
const updated = d.query("SELECT * FROM api_keys WHERE id = ?").get(row.id);
|
|
5637
|
+
return rowToRecord(updated);
|
|
5638
|
+
}
|
|
5259
5639
|
// src/db/task-lists.ts
|
|
5260
5640
|
init_types();
|
|
5261
5641
|
init_database();
|
|
@@ -8176,7 +8556,7 @@ var require_utils_webcrypto = __commonJS((exports, module) => {
|
|
|
8176
8556
|
var nodeCrypto = __require("crypto");
|
|
8177
8557
|
module.exports = {
|
|
8178
8558
|
postgresMd5PasswordHash,
|
|
8179
|
-
randomBytes,
|
|
8559
|
+
randomBytes: randomBytes2,
|
|
8180
8560
|
deriveKey,
|
|
8181
8561
|
sha256,
|
|
8182
8562
|
hashByName,
|
|
@@ -8186,7 +8566,7 @@ var require_utils_webcrypto = __commonJS((exports, module) => {
|
|
|
8186
8566
|
var webCrypto = nodeCrypto.webcrypto || globalThis.crypto;
|
|
8187
8567
|
var subtleCrypto = webCrypto.subtle;
|
|
8188
8568
|
var textEncoder = new TextEncoder;
|
|
8189
|
-
function
|
|
8569
|
+
function randomBytes2(length) {
|
|
8190
8570
|
return webCrypto.getRandomValues(Buffer.alloc(length));
|
|
8191
8571
|
}
|
|
8192
8572
|
async function md5(string) {
|
|
@@ -17985,6 +18365,7 @@ async function dispatchToMultiple(input, opts = {}, db) {
|
|
|
17985
18365
|
return dispatches;
|
|
17986
18366
|
}
|
|
17987
18367
|
export {
|
|
18368
|
+
verifyApiKey,
|
|
17988
18369
|
validateTmuxTarget,
|
|
17989
18370
|
uuid,
|
|
17990
18371
|
updateTemplate,
|
|
@@ -18009,6 +18390,7 @@ export {
|
|
|
18009
18390
|
syncWithAgents,
|
|
18010
18391
|
syncWithAgent,
|
|
18011
18392
|
syncKgEdges,
|
|
18393
|
+
suggestAgentNames,
|
|
18012
18394
|
stealTask,
|
|
18013
18395
|
startTask,
|
|
18014
18396
|
slugify,
|
|
@@ -18022,6 +18404,7 @@ export {
|
|
|
18022
18404
|
scoreTask,
|
|
18023
18405
|
saveSnapshot,
|
|
18024
18406
|
runDueDispatches,
|
|
18407
|
+
revokeApiKey,
|
|
18025
18408
|
resolveVariables,
|
|
18026
18409
|
resolvePartialId,
|
|
18027
18410
|
resetMachineId,
|
|
@@ -18045,6 +18428,7 @@ export {
|
|
|
18045
18428
|
parseRecurrenceRule,
|
|
18046
18429
|
parseGitHubUrl,
|
|
18047
18430
|
now,
|
|
18431
|
+
normalizeGeneratedAgentNames,
|
|
18048
18432
|
nextTaskShortId,
|
|
18049
18433
|
nextOccurrence,
|
|
18050
18434
|
moveTask,
|
|
@@ -18076,12 +18460,14 @@ export {
|
|
|
18076
18460
|
listCyclesWithStats,
|
|
18077
18461
|
listCycles,
|
|
18078
18462
|
listComments,
|
|
18463
|
+
listApiKeys,
|
|
18079
18464
|
listAgents,
|
|
18080
18465
|
issueToTask,
|
|
18081
18466
|
isValidRecurrenceRule,
|
|
18082
18467
|
isAgentConflict,
|
|
18083
18468
|
initBuiltinTemplates,
|
|
18084
18469
|
importTemplate,
|
|
18470
|
+
hasActiveApiKeys,
|
|
18085
18471
|
getWebhook,
|
|
18086
18472
|
getTraceStats,
|
|
18087
18473
|
getTemplateWithTasks,
|
|
@@ -18196,6 +18582,7 @@ export {
|
|
|
18196
18582
|
createDispatch,
|
|
18197
18583
|
createCycle,
|
|
18198
18584
|
createClient,
|
|
18585
|
+
createApiKey,
|
|
18199
18586
|
countTasks,
|
|
18200
18587
|
completeTask,
|
|
18201
18588
|
closeDatabase,
|
package/dist/mcp/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";AAuGA,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAO9E"}
|