@hasna/todos 0.10.1 → 0.10.3
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/dist/cli/index.js +143 -73
- package/dist/db/agents.d.ts +8 -1
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -24
- package/dist/lib/config.d.ts +2 -7
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/mcp/index.js +141 -70
- package/dist/server/index.js +38 -41
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAC1D,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC1C;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAC1D,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC1C;AAWD,wBAAgB,UAAU,IAAI,WAAW,CAYxC;AAED,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,GAAG,IAAI,CAKzD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM7D;AAED,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,IAAI,CAG7D;AAUD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAmB3E;AAED,wBAAgB,wBAAwB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,QAAQ,CAAC,qBAAqB,CAAC,CASrG"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -150,6 +150,7 @@ function ensureSchema(db) {
|
|
|
150
150
|
CREATE TABLE agents (
|
|
151
151
|
id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT,
|
|
152
152
|
role TEXT DEFAULT 'agent', permissions TEXT DEFAULT '["*"]',
|
|
153
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived')),
|
|
153
154
|
metadata TEXT DEFAULT '{}',
|
|
154
155
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
155
156
|
last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
@@ -799,6 +800,11 @@ var init_database = __esm(() => {
|
|
|
799
800
|
`
|
|
800
801
|
ALTER TABLE agents ADD COLUMN capabilities TEXT DEFAULT '[]';
|
|
801
802
|
INSERT OR IGNORE INTO _migrations (id) VALUES (29);
|
|
803
|
+
`,
|
|
804
|
+
`
|
|
805
|
+
ALTER TABLE agents ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived'));
|
|
806
|
+
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
|
|
807
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (30);
|
|
802
808
|
`
|
|
803
809
|
];
|
|
804
810
|
});
|
|
@@ -953,6 +959,7 @@ var exports_agents = {};
|
|
|
953
959
|
__export(exports_agents, {
|
|
954
960
|
updateAgentActivity: () => updateAgentActivity,
|
|
955
961
|
updateAgent: () => updateAgent,
|
|
962
|
+
unarchiveAgent: () => unarchiveAgent,
|
|
956
963
|
registerAgent: () => registerAgent,
|
|
957
964
|
matchCapabilities: () => matchCapabilities,
|
|
958
965
|
listAgents: () => listAgents,
|
|
@@ -963,11 +970,12 @@ __export(exports_agents, {
|
|
|
963
970
|
getAvailableNamesFromPool: () => getAvailableNamesFromPool,
|
|
964
971
|
getAgentByName: () => getAgentByName,
|
|
965
972
|
getAgent: () => getAgent,
|
|
966
|
-
deleteAgent: () => deleteAgent
|
|
973
|
+
deleteAgent: () => deleteAgent,
|
|
974
|
+
archiveAgent: () => archiveAgent
|
|
967
975
|
});
|
|
968
976
|
function getAvailableNamesFromPool(pool, db) {
|
|
969
977
|
const cutoff = new Date(Date.now() - AGENT_ACTIVE_WINDOW_MS).toISOString();
|
|
970
|
-
const activeNames = new Set(db.query("SELECT name FROM agents WHERE last_seen_at > ?").all(cutoff).map((r) => r.name.toLowerCase()));
|
|
978
|
+
const activeNames = new Set(db.query("SELECT name FROM agents WHERE status = 'active' AND last_seen_at > ?").all(cutoff).map((r) => r.name.toLowerCase()));
|
|
971
979
|
return pool.filter((name) => !activeNames.has(name.toLowerCase()));
|
|
972
980
|
}
|
|
973
981
|
function shortUuid() {
|
|
@@ -978,30 +986,13 @@ function rowToAgent(row) {
|
|
|
978
986
|
...row,
|
|
979
987
|
permissions: JSON.parse(row.permissions || '["*"]'),
|
|
980
988
|
capabilities: JSON.parse(row.capabilities || "[]"),
|
|
989
|
+
status: row.status || "active",
|
|
981
990
|
metadata: JSON.parse(row.metadata || "{}")
|
|
982
991
|
};
|
|
983
992
|
}
|
|
984
993
|
function registerAgent(input, db) {
|
|
985
994
|
const d = db || getDatabase();
|
|
986
995
|
const normalizedName = input.name.trim().toLowerCase();
|
|
987
|
-
if (input.pool && input.pool.length > 0) {
|
|
988
|
-
const poolLower = input.pool.map((n) => n.toLowerCase());
|
|
989
|
-
if (!poolLower.includes(normalizedName)) {
|
|
990
|
-
const available = getAvailableNamesFromPool(input.pool, d);
|
|
991
|
-
const suggestion = available.length > 0 ? available[0] : null;
|
|
992
|
-
return {
|
|
993
|
-
conflict: true,
|
|
994
|
-
pool_violation: true,
|
|
995
|
-
existing_id: "",
|
|
996
|
-
existing_name: normalizedName,
|
|
997
|
-
last_seen_at: "",
|
|
998
|
-
session_hint: null,
|
|
999
|
-
working_dir: input.working_dir || null,
|
|
1000
|
-
suggestions: available.slice(0, 5),
|
|
1001
|
-
message: `"${normalizedName}" is not in this project's agent pool [${input.pool.join(", ")}]. ${available.length > 0 ? `Try: ${available.slice(0, 3).join(", ")}` : "No names are currently available \u2014 wait for an active agent to go stale."}${suggestion ? ` Suggested: ${suggestion}` : ""}`
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
996
|
const existing = getAgentByName(normalizedName, d);
|
|
1006
997
|
if (existing) {
|
|
1007
998
|
const lastSeenMs = new Date(existing.last_seen_at).getTime();
|
|
@@ -1022,7 +1013,7 @@ function registerAgent(input, db) {
|
|
|
1022
1013
|
message: `Agent "${normalizedName}" is already active (last seen ${minutesAgo}m ago, session ${existing.session_id?.slice(0, 8)}\u2026, dir: ${existing.working_dir ?? "unknown"}). Are you that agent? If so, pass session_id="${existing.session_id}" to reclaim it. Otherwise choose a different name.${suggestions.length > 0 ? ` Available: ${suggestions.slice(0, 3).join(", ")}` : ""}`
|
|
1023
1014
|
};
|
|
1024
1015
|
}
|
|
1025
|
-
const updates = ["last_seen_at = ?"];
|
|
1016
|
+
const updates = ["last_seen_at = ?", "status = 'active'"];
|
|
1026
1017
|
const params = [now()];
|
|
1027
1018
|
if (input.session_id && !sameSession) {
|
|
1028
1019
|
updates.push("session_id = ?");
|
|
@@ -1076,9 +1067,19 @@ function getAgentByName(name, db) {
|
|
|
1076
1067
|
const row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
|
|
1077
1068
|
return row ? rowToAgent(row) : null;
|
|
1078
1069
|
}
|
|
1079
|
-
function listAgents(db) {
|
|
1080
|
-
|
|
1081
|
-
|
|
1070
|
+
function listAgents(opts, db) {
|
|
1071
|
+
let d;
|
|
1072
|
+
let includeArchived = false;
|
|
1073
|
+
if (opts && typeof opts === "object" && "query" in opts) {
|
|
1074
|
+
d = opts;
|
|
1075
|
+
} else {
|
|
1076
|
+
includeArchived = opts?.include_archived ?? false;
|
|
1077
|
+
d = db || getDatabase();
|
|
1078
|
+
}
|
|
1079
|
+
if (includeArchived) {
|
|
1080
|
+
return d.query("SELECT * FROM agents ORDER BY name").all().map(rowToAgent);
|
|
1081
|
+
}
|
|
1082
|
+
return d.query("SELECT * FROM agents WHERE status = 'active' ORDER BY name").all().map(rowToAgent);
|
|
1082
1083
|
}
|
|
1083
1084
|
function updateAgentActivity(id, db) {
|
|
1084
1085
|
const d = db || getDatabase();
|
|
@@ -1137,7 +1138,17 @@ function updateAgent(id, input, db) {
|
|
|
1137
1138
|
}
|
|
1138
1139
|
function deleteAgent(id, db) {
|
|
1139
1140
|
const d = db || getDatabase();
|
|
1140
|
-
return d.run("
|
|
1141
|
+
return d.run("UPDATE agents SET status = 'archived', last_seen_at = ? WHERE id = ?", [now(), id]).changes > 0;
|
|
1142
|
+
}
|
|
1143
|
+
function archiveAgent(id, db) {
|
|
1144
|
+
const d = db || getDatabase();
|
|
1145
|
+
d.run("UPDATE agents SET status = 'archived', last_seen_at = ? WHERE id = ?", [now(), id]);
|
|
1146
|
+
return getAgent(id, d);
|
|
1147
|
+
}
|
|
1148
|
+
function unarchiveAgent(id, db) {
|
|
1149
|
+
const d = db || getDatabase();
|
|
1150
|
+
d.run("UPDATE agents SET status = 'active', last_seen_at = ? WHERE id = ?", [now(), id]);
|
|
1151
|
+
return getAgent(id, d);
|
|
1141
1152
|
}
|
|
1142
1153
|
function getDirectReports(agentId, db) {
|
|
1143
1154
|
const d = db || getDatabase();
|
|
@@ -6135,23 +6146,6 @@ function appendSyncConflict(metadata, conflict, limit = 5) {
|
|
|
6135
6146
|
}
|
|
6136
6147
|
|
|
6137
6148
|
// src/lib/config.ts
|
|
6138
|
-
var DEFAULT_AGENT_POOL = [
|
|
6139
|
-
"maximus",
|
|
6140
|
-
"cassius",
|
|
6141
|
-
"aurelius",
|
|
6142
|
-
"brutus",
|
|
6143
|
-
"titus",
|
|
6144
|
-
"nero",
|
|
6145
|
-
"cicero",
|
|
6146
|
-
"seneca",
|
|
6147
|
-
"cato",
|
|
6148
|
-
"julius",
|
|
6149
|
-
"marcus",
|
|
6150
|
-
"lucius",
|
|
6151
|
-
"quintus",
|
|
6152
|
-
"gaius",
|
|
6153
|
-
"publius"
|
|
6154
|
-
];
|
|
6155
6149
|
function getConfigPath() {
|
|
6156
6150
|
return join3(process.env["HOME"] || HOME, ".todos", "config.json");
|
|
6157
6151
|
}
|
|
@@ -6216,7 +6210,7 @@ function getAgentPoolForProject(workingDir) {
|
|
|
6216
6210
|
return config.project_pools[bestKey];
|
|
6217
6211
|
}
|
|
6218
6212
|
}
|
|
6219
|
-
return config.agent_pool ||
|
|
6213
|
+
return config.agent_pool || null;
|
|
6220
6214
|
}
|
|
6221
6215
|
function getCompletionGuardConfig(projectPath) {
|
|
6222
6216
|
const config = loadConfig();
|
|
@@ -8253,13 +8247,15 @@ var MINIMAL_TOOLS = new Set([
|
|
|
8253
8247
|
"add_comment",
|
|
8254
8248
|
"get_next_task",
|
|
8255
8249
|
"bootstrap",
|
|
8256
|
-
"get_tasks_changed_since"
|
|
8250
|
+
"get_tasks_changed_since",
|
|
8251
|
+
"heartbeat"
|
|
8257
8252
|
]);
|
|
8258
8253
|
var STANDARD_EXCLUDED = new Set([
|
|
8259
8254
|
"get_org_chart",
|
|
8260
8255
|
"set_reports_to",
|
|
8261
8256
|
"rename_agent",
|
|
8262
8257
|
"delete_agent",
|
|
8258
|
+
"unarchive_agent",
|
|
8263
8259
|
"create_webhook",
|
|
8264
8260
|
"list_webhooks",
|
|
8265
8261
|
"delete_webhook",
|
|
@@ -9228,8 +9224,8 @@ if (shouldRegisterTool("unfocus")) {
|
|
|
9228
9224
|
});
|
|
9229
9225
|
}
|
|
9230
9226
|
if (shouldRegisterTool("register_agent")) {
|
|
9231
|
-
server.tool("register_agent", "Register an agent.
|
|
9232
|
-
name: exports_external.string().describe("Agent name \u2014
|
|
9227
|
+
server.tool("register_agent", "Register an agent. Any name is allowed \u2014 the configured pool is advisory, not enforced. Returns a conflict error if the name is held by a recently-active agent.", {
|
|
9228
|
+
name: exports_external.string().describe("Agent name \u2014 any name is allowed. Use suggest_agent_name to see pool suggestions and avoid conflicts."),
|
|
9233
9229
|
description: exports_external.string().optional(),
|
|
9234
9230
|
capabilities: exports_external.array(exports_external.string()).optional().describe("Agent capabilities/skills for task routing (e.g. ['typescript', 'testing', 'devops'])"),
|
|
9235
9231
|
session_id: exports_external.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."),
|
|
@@ -9237,17 +9233,19 @@ if (shouldRegisterTool("register_agent")) {
|
|
|
9237
9233
|
}, async ({ name, description, capabilities, session_id, working_dir }) => {
|
|
9238
9234
|
try {
|
|
9239
9235
|
const pool = getAgentPoolForProject(working_dir);
|
|
9240
|
-
const result = registerAgent({ name, description, capabilities, session_id, working_dir, pool });
|
|
9236
|
+
const result = registerAgent({ name, description, capabilities, session_id, working_dir, pool: pool || undefined });
|
|
9241
9237
|
if (isAgentConflict(result)) {
|
|
9242
9238
|
const suggestLine = result.suggestions && result.suggestions.length > 0 ? `
|
|
9243
9239
|
Available names: ${result.suggestions.join(", ")}` : "";
|
|
9244
|
-
const hint =
|
|
9240
|
+
const hint = `CONFLICT: ${result.message}${suggestLine}`;
|
|
9245
9241
|
return {
|
|
9246
9242
|
content: [{ type: "text", text: hint }],
|
|
9247
9243
|
isError: true
|
|
9248
9244
|
};
|
|
9249
9245
|
}
|
|
9250
9246
|
const agent = result;
|
|
9247
|
+
const poolLine = pool ? `
|
|
9248
|
+
Pool: [${pool.join(", ")}]` : "";
|
|
9251
9249
|
return {
|
|
9252
9250
|
content: [{
|
|
9253
9251
|
type: "text",
|
|
@@ -9255,8 +9253,7 @@ Available names: ${result.suggestions.join(", ")}` : "";
|
|
|
9255
9253
|
ID: ${agent.id}
|
|
9256
9254
|
Name: ${agent.name}${agent.description ? `
|
|
9257
9255
|
Description: ${agent.description}` : ""}
|
|
9258
|
-
Session: ${agent.session_id ?? "unbound"}
|
|
9259
|
-
Pool: [${pool.join(", ")}]
|
|
9256
|
+
Session: ${agent.session_id ?? "unbound"}${poolLine}
|
|
9260
9257
|
Created: ${agent.created_at}
|
|
9261
9258
|
Last seen: ${agent.last_seen_at}`
|
|
9262
9259
|
}]
|
|
@@ -9267,21 +9264,32 @@ Last seen: ${agent.last_seen_at}`
|
|
|
9267
9264
|
});
|
|
9268
9265
|
}
|
|
9269
9266
|
if (shouldRegisterTool("suggest_agent_name")) {
|
|
9270
|
-
server.tool("suggest_agent_name", "Get available agent names for a project.
|
|
9271
|
-
working_dir: exports_external.string().optional().describe("Your working directory \u2014 used to look up the project's allowed name pool")
|
|
9267
|
+
server.tool("suggest_agent_name", "Get available agent names for a project. Shows configured pool, active agents, and suggestions. If no pool is configured, any name is allowed.", {
|
|
9268
|
+
working_dir: exports_external.string().optional().describe("Your working directory \u2014 used to look up the project's allowed name pool from config")
|
|
9272
9269
|
}, async ({ working_dir }) => {
|
|
9273
9270
|
try {
|
|
9274
9271
|
const pool = getAgentPoolForProject(working_dir);
|
|
9275
|
-
const available = getAvailableNamesFromPool(pool, getDatabase());
|
|
9276
9272
|
const cutoff = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
9277
|
-
const
|
|
9273
|
+
const allActive = listAgents().filter((a) => a.last_seen_at > cutoff);
|
|
9274
|
+
if (!pool) {
|
|
9275
|
+
const lines2 = [
|
|
9276
|
+
"No agent pool configured \u2014 any name is allowed.",
|
|
9277
|
+
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.",
|
|
9278
|
+
`
|
|
9279
|
+
To restrict names, configure agent_pool or project_pools in ~/.todos/config.json`
|
|
9280
|
+
];
|
|
9281
|
+
return { content: [{ type: "text", text: lines2.join(`
|
|
9282
|
+
`) }] };
|
|
9283
|
+
}
|
|
9284
|
+
const available = getAvailableNamesFromPool(pool, getDatabase());
|
|
9285
|
+
const activeInPool = allActive.filter((a) => pool.map((n) => n.toLowerCase()).includes(a.name));
|
|
9278
9286
|
const lines = [
|
|
9279
9287
|
`Project pool: ${pool.join(", ")}`,
|
|
9280
9288
|
`Available now (${available.length}): ${available.length > 0 ? available.join(", ") : "none \u2014 all names in use"}`,
|
|
9281
|
-
|
|
9289
|
+
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",
|
|
9282
9290
|
available.length > 0 ? `
|
|
9283
9291
|
Suggested: ${available[0]}` : `
|
|
9284
|
-
No names available
|
|
9292
|
+
No names available. Wait for an active agent to go stale (30min timeout).`
|
|
9285
9293
|
];
|
|
9286
9294
|
return { content: [{ type: "text", text: lines.join(`
|
|
9287
9295
|
`) }] };
|
|
@@ -9291,14 +9299,17 @@ No names available yet. Wait for an active agent to go stale (30min timeout).`
|
|
|
9291
9299
|
});
|
|
9292
9300
|
}
|
|
9293
9301
|
if (shouldRegisterTool("list_agents")) {
|
|
9294
|
-
server.tool("list_agents", "List all registered agents
|
|
9302
|
+
server.tool("list_agents", "List all registered agents. By default shows only active agents \u2014 set include_archived to see archived ones too.", {
|
|
9303
|
+
include_archived: exports_external.boolean().optional().describe("Include archived agents in the list (default: false)")
|
|
9304
|
+
}, async ({ include_archived }) => {
|
|
9295
9305
|
try {
|
|
9296
|
-
const agents = listAgents();
|
|
9306
|
+
const agents = listAgents({ include_archived: include_archived ?? false });
|
|
9297
9307
|
if (agents.length === 0) {
|
|
9298
9308
|
return { content: [{ type: "text", text: "No agents registered." }] };
|
|
9299
9309
|
}
|
|
9300
9310
|
const text = agents.map((a) => {
|
|
9301
|
-
|
|
9311
|
+
const statusTag = a.status === "archived" ? " [archived]" : "";
|
|
9312
|
+
return `${a.id} | ${a.name}${statusTag}${a.description ? ` - ${a.description}` : ""} (last seen: ${a.last_seen_at})`;
|
|
9302
9313
|
}).join(`
|
|
9303
9314
|
`);
|
|
9304
9315
|
return { content: [{ type: "text", text: `${agents.length} agent(s):
|
|
@@ -9366,7 +9377,7 @@ ID: ${updated.id}`
|
|
|
9366
9377
|
});
|
|
9367
9378
|
}
|
|
9368
9379
|
if (shouldRegisterTool("delete_agent")) {
|
|
9369
|
-
server.tool("delete_agent", "
|
|
9380
|
+
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.", {
|
|
9370
9381
|
id: exports_external.string().optional(),
|
|
9371
9382
|
name: exports_external.string().optional()
|
|
9372
9383
|
}, async ({ id, name }) => {
|
|
@@ -9378,13 +9389,63 @@ if (shouldRegisterTool("delete_agent")) {
|
|
|
9378
9389
|
if (!agent) {
|
|
9379
9390
|
return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
|
|
9380
9391
|
}
|
|
9381
|
-
const
|
|
9392
|
+
const archived = archiveAgent(agent.id);
|
|
9382
9393
|
return {
|
|
9383
9394
|
content: [{
|
|
9384
9395
|
type: "text",
|
|
9385
|
-
text:
|
|
9396
|
+
text: archived ? `Agent archived: ${agent.name} (${agent.id}). Use unarchive_agent to restore.` : `Failed to archive agent: ${agent.name}`
|
|
9386
9397
|
}],
|
|
9387
|
-
isError: !
|
|
9398
|
+
isError: !archived
|
|
9399
|
+
};
|
|
9400
|
+
} catch (e) {
|
|
9401
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9402
|
+
}
|
|
9403
|
+
});
|
|
9404
|
+
}
|
|
9405
|
+
if (shouldRegisterTool("unarchive_agent")) {
|
|
9406
|
+
server.tool("unarchive_agent", "Restore an archived agent back to active status. Resolve by id or name.", {
|
|
9407
|
+
id: exports_external.string().optional(),
|
|
9408
|
+
name: exports_external.string().optional()
|
|
9409
|
+
}, async ({ id, name }) => {
|
|
9410
|
+
try {
|
|
9411
|
+
if (!id && !name) {
|
|
9412
|
+
return { content: [{ type: "text", text: "Provide either id or name." }], isError: true };
|
|
9413
|
+
}
|
|
9414
|
+
const agent = id ? getAgent(id) : getAgentByName(name);
|
|
9415
|
+
if (!agent) {
|
|
9416
|
+
return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
|
|
9417
|
+
}
|
|
9418
|
+
if (agent.status === "active") {
|
|
9419
|
+
return { content: [{ type: "text", text: `Agent ${agent.name} is already active.` }] };
|
|
9420
|
+
}
|
|
9421
|
+
const restored = unarchiveAgent(agent.id);
|
|
9422
|
+
return {
|
|
9423
|
+
content: [{
|
|
9424
|
+
type: "text",
|
|
9425
|
+
text: restored ? `Agent restored: ${agent.name} (${agent.id}) is now active.` : `Failed to restore agent: ${agent.name}`
|
|
9426
|
+
}],
|
|
9427
|
+
isError: !restored
|
|
9428
|
+
};
|
|
9429
|
+
} catch (e) {
|
|
9430
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9431
|
+
}
|
|
9432
|
+
});
|
|
9433
|
+
}
|
|
9434
|
+
if (shouldRegisterTool("heartbeat")) {
|
|
9435
|
+
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.", {
|
|
9436
|
+
agent_id: exports_external.string().describe("Your agent ID or name.")
|
|
9437
|
+
}, async ({ agent_id }) => {
|
|
9438
|
+
try {
|
|
9439
|
+
const agent = getAgent(agent_id) || getAgentByName(agent_id);
|
|
9440
|
+
if (!agent) {
|
|
9441
|
+
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
9442
|
+
}
|
|
9443
|
+
updateAgentActivity(agent.id);
|
|
9444
|
+
return {
|
|
9445
|
+
content: [{
|
|
9446
|
+
type: "text",
|
|
9447
|
+
text: `Heartbeat: ${agent.name} (${agent.id}) \u2014 last_seen_at updated to ${new Date().toISOString()}`
|
|
9448
|
+
}]
|
|
9388
9449
|
};
|
|
9389
9450
|
} catch (e) {
|
|
9390
9451
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -10487,6 +10548,8 @@ if (shouldRegisterTool("search_tools")) {
|
|
|
10487
10548
|
"get_agent",
|
|
10488
10549
|
"rename_agent",
|
|
10489
10550
|
"delete_agent",
|
|
10551
|
+
"unarchive_agent",
|
|
10552
|
+
"heartbeat",
|
|
10490
10553
|
"get_my_tasks",
|
|
10491
10554
|
"get_org_chart",
|
|
10492
10555
|
"set_reports_to",
|
|
@@ -10624,22 +10687,30 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
10624
10687
|
delete_plan: `Delete a plan. Tasks in the plan are orphaned, not deleted.
|
|
10625
10688
|
Params: id(string, req)
|
|
10626
10689
|
Example: {id: 'a1b2c3d4'}`,
|
|
10627
|
-
suggest_agent_name: `
|
|
10628
|
-
Params: working_dir(string \u2014 your working directory, used to look up project pool)
|
|
10690
|
+
suggest_agent_name: `Check available agent names before registering. Shows active agents and, if a pool is configured, which pool names are free.
|
|
10691
|
+
Params: working_dir(string \u2014 your working directory, used to look up project pool from config)
|
|
10629
10692
|
Example: {working_dir: '/workspace/platform'}`,
|
|
10630
|
-
register_agent: `Register an agent.
|
|
10631
|
-
Params: name(string, req), description(string), session_id(string \u2014 unique per session
|
|
10632
|
-
Example: {name: '
|
|
10633
|
-
list_agents:
|
|
10693
|
+
register_agent: `Register an agent. Any name is allowed \u2014 pool is advisory. Returns CONFLICT if name is held by a recently-active agent.
|
|
10694
|
+
Params: name(string, req), description(string), capabilities(string[]), session_id(string \u2014 unique per session), working_dir(string \u2014 used to determine project pool)
|
|
10695
|
+
Example: {name: 'my-agent', session_id: 'abc123-1741952000', working_dir: '/workspace/platform'}`,
|
|
10696
|
+
list_agents: `List all registered agents (active by default). Set include_archived: true to see archived agents.
|
|
10697
|
+
Params: include_archived(boolean, optional)
|
|
10698
|
+
Example: {include_archived: true}`,
|
|
10634
10699
|
get_agent: `Get agent details by ID or name. Provide one of id or name.
|
|
10635
10700
|
Params: id(string), name(string)
|
|
10636
10701
|
Example: {name: 'maximus'}`,
|
|
10637
10702
|
rename_agent: `Rename an agent. Resolve by id or current name.
|
|
10638
10703
|
Params: id(string), name(string \u2014 current name), new_name(string, req)
|
|
10639
10704
|
Example: {name: 'old-name', new_name: 'new-name'}`,
|
|
10640
|
-
delete_agent: `
|
|
10705
|
+
delete_agent: `Archive an agent (soft delete). Agent is preserved for task history but hidden from list_agents. Use unarchive_agent to restore.
|
|
10706
|
+
Params: id(string), name(string)
|
|
10707
|
+
Example: {name: 'maximus'}`,
|
|
10708
|
+
unarchive_agent: `Restore an archived agent back to active status.
|
|
10641
10709
|
Params: id(string), name(string)
|
|
10642
10710
|
Example: {name: 'maximus'}`,
|
|
10711
|
+
heartbeat: `Update last_seen_at timestamp to signal you're still active. Call periodically during long tasks.
|
|
10712
|
+
Params: agent_id(string, req \u2014 your agent ID or name)
|
|
10713
|
+
Example: {agent_id: 'maximus'}`,
|
|
10643
10714
|
get_my_tasks: `Get all tasks assigned to/created by an agent, with stats (pending/active/done/rate).
|
|
10644
10715
|
Params: agent_name(string, req)
|
|
10645
10716
|
Example: {agent_name: 'maximus'}`,
|
package/dist/server/index.js
CHANGED
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
5
3
|
var __defProp = Object.defineProperty;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
-
for (let key of __getOwnPropNames(mod))
|
|
12
|
-
if (!__hasOwnProp.call(to, key))
|
|
13
|
-
__defProp(to, key, {
|
|
14
|
-
get: () => mod[key],
|
|
15
|
-
enumerable: true
|
|
16
|
-
});
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
4
|
var __export = (target, all) => {
|
|
20
5
|
for (var name in all)
|
|
21
6
|
__defProp(target, name, {
|
|
@@ -234,6 +219,7 @@ function ensureSchema(db) {
|
|
|
234
219
|
CREATE TABLE agents (
|
|
235
220
|
id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT,
|
|
236
221
|
role TEXT DEFAULT 'agent', permissions TEXT DEFAULT '["*"]',
|
|
222
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived')),
|
|
237
223
|
metadata TEXT DEFAULT '{}',
|
|
238
224
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
239
225
|
last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
@@ -863,6 +849,11 @@ var init_database = __esm(() => {
|
|
|
863
849
|
`
|
|
864
850
|
ALTER TABLE agents ADD COLUMN capabilities TEXT DEFAULT '[]';
|
|
865
851
|
INSERT OR IGNORE INTO _migrations (id) VALUES (29);
|
|
852
|
+
`,
|
|
853
|
+
`
|
|
854
|
+
ALTER TABLE agents ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived'));
|
|
855
|
+
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
|
|
856
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (30);
|
|
866
857
|
`
|
|
867
858
|
];
|
|
868
859
|
});
|
|
@@ -2477,6 +2468,7 @@ var exports_agents = {};
|
|
|
2477
2468
|
__export(exports_agents, {
|
|
2478
2469
|
updateAgentActivity: () => updateAgentActivity,
|
|
2479
2470
|
updateAgent: () => updateAgent,
|
|
2471
|
+
unarchiveAgent: () => unarchiveAgent,
|
|
2480
2472
|
registerAgent: () => registerAgent,
|
|
2481
2473
|
matchCapabilities: () => matchCapabilities,
|
|
2482
2474
|
listAgents: () => listAgents,
|
|
@@ -2487,11 +2479,12 @@ __export(exports_agents, {
|
|
|
2487
2479
|
getAvailableNamesFromPool: () => getAvailableNamesFromPool,
|
|
2488
2480
|
getAgentByName: () => getAgentByName,
|
|
2489
2481
|
getAgent: () => getAgent,
|
|
2490
|
-
deleteAgent: () => deleteAgent
|
|
2482
|
+
deleteAgent: () => deleteAgent,
|
|
2483
|
+
archiveAgent: () => archiveAgent
|
|
2491
2484
|
});
|
|
2492
2485
|
function getAvailableNamesFromPool(pool, db) {
|
|
2493
2486
|
const cutoff = new Date(Date.now() - AGENT_ACTIVE_WINDOW_MS).toISOString();
|
|
2494
|
-
const activeNames = new Set(db.query("SELECT name FROM agents WHERE last_seen_at > ?").all(cutoff).map((r) => r.name.toLowerCase()));
|
|
2487
|
+
const activeNames = new Set(db.query("SELECT name FROM agents WHERE status = 'active' AND last_seen_at > ?").all(cutoff).map((r) => r.name.toLowerCase()));
|
|
2495
2488
|
return pool.filter((name) => !activeNames.has(name.toLowerCase()));
|
|
2496
2489
|
}
|
|
2497
2490
|
function shortUuid() {
|
|
@@ -2502,30 +2495,13 @@ function rowToAgent(row) {
|
|
|
2502
2495
|
...row,
|
|
2503
2496
|
permissions: JSON.parse(row.permissions || '["*"]'),
|
|
2504
2497
|
capabilities: JSON.parse(row.capabilities || "[]"),
|
|
2498
|
+
status: row.status || "active",
|
|
2505
2499
|
metadata: JSON.parse(row.metadata || "{}")
|
|
2506
2500
|
};
|
|
2507
2501
|
}
|
|
2508
2502
|
function registerAgent(input, db) {
|
|
2509
2503
|
const d = db || getDatabase();
|
|
2510
2504
|
const normalizedName = input.name.trim().toLowerCase();
|
|
2511
|
-
if (input.pool && input.pool.length > 0) {
|
|
2512
|
-
const poolLower = input.pool.map((n) => n.toLowerCase());
|
|
2513
|
-
if (!poolLower.includes(normalizedName)) {
|
|
2514
|
-
const available = getAvailableNamesFromPool(input.pool, d);
|
|
2515
|
-
const suggestion = available.length > 0 ? available[0] : null;
|
|
2516
|
-
return {
|
|
2517
|
-
conflict: true,
|
|
2518
|
-
pool_violation: true,
|
|
2519
|
-
existing_id: "",
|
|
2520
|
-
existing_name: normalizedName,
|
|
2521
|
-
last_seen_at: "",
|
|
2522
|
-
session_hint: null,
|
|
2523
|
-
working_dir: input.working_dir || null,
|
|
2524
|
-
suggestions: available.slice(0, 5),
|
|
2525
|
-
message: `"${normalizedName}" is not in this project's agent pool [${input.pool.join(", ")}]. ${available.length > 0 ? `Try: ${available.slice(0, 3).join(", ")}` : "No names are currently available \u2014 wait for an active agent to go stale."}${suggestion ? ` Suggested: ${suggestion}` : ""}`
|
|
2526
|
-
};
|
|
2527
|
-
}
|
|
2528
|
-
}
|
|
2529
2505
|
const existing = getAgentByName(normalizedName, d);
|
|
2530
2506
|
if (existing) {
|
|
2531
2507
|
const lastSeenMs = new Date(existing.last_seen_at).getTime();
|
|
@@ -2546,7 +2522,7 @@ function registerAgent(input, db) {
|
|
|
2546
2522
|
message: `Agent "${normalizedName}" is already active (last seen ${minutesAgo}m ago, session ${existing.session_id?.slice(0, 8)}\u2026, dir: ${existing.working_dir ?? "unknown"}). Are you that agent? If so, pass session_id="${existing.session_id}" to reclaim it. Otherwise choose a different name.${suggestions.length > 0 ? ` Available: ${suggestions.slice(0, 3).join(", ")}` : ""}`
|
|
2547
2523
|
};
|
|
2548
2524
|
}
|
|
2549
|
-
const updates = ["last_seen_at = ?"];
|
|
2525
|
+
const updates = ["last_seen_at = ?", "status = 'active'"];
|
|
2550
2526
|
const params = [now()];
|
|
2551
2527
|
if (input.session_id && !sameSession) {
|
|
2552
2528
|
updates.push("session_id = ?");
|
|
@@ -2600,9 +2576,19 @@ function getAgentByName(name, db) {
|
|
|
2600
2576
|
const row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
|
|
2601
2577
|
return row ? rowToAgent(row) : null;
|
|
2602
2578
|
}
|
|
2603
|
-
function listAgents(db) {
|
|
2604
|
-
|
|
2605
|
-
|
|
2579
|
+
function listAgents(opts, db) {
|
|
2580
|
+
let d;
|
|
2581
|
+
let includeArchived = false;
|
|
2582
|
+
if (opts && typeof opts === "object" && "query" in opts) {
|
|
2583
|
+
d = opts;
|
|
2584
|
+
} else {
|
|
2585
|
+
includeArchived = opts?.include_archived ?? false;
|
|
2586
|
+
d = db || getDatabase();
|
|
2587
|
+
}
|
|
2588
|
+
if (includeArchived) {
|
|
2589
|
+
return d.query("SELECT * FROM agents ORDER BY name").all().map(rowToAgent);
|
|
2590
|
+
}
|
|
2591
|
+
return d.query("SELECT * FROM agents WHERE status = 'active' ORDER BY name").all().map(rowToAgent);
|
|
2606
2592
|
}
|
|
2607
2593
|
function updateAgentActivity(id, db) {
|
|
2608
2594
|
const d = db || getDatabase();
|
|
@@ -2661,7 +2647,17 @@ function updateAgent(id, input, db) {
|
|
|
2661
2647
|
}
|
|
2662
2648
|
function deleteAgent(id, db) {
|
|
2663
2649
|
const d = db || getDatabase();
|
|
2664
|
-
return d.run("
|
|
2650
|
+
return d.run("UPDATE agents SET status = 'archived', last_seen_at = ? WHERE id = ?", [now(), id]).changes > 0;
|
|
2651
|
+
}
|
|
2652
|
+
function archiveAgent(id, db) {
|
|
2653
|
+
const d = db || getDatabase();
|
|
2654
|
+
d.run("UPDATE agents SET status = 'archived', last_seen_at = ? WHERE id = ?", [now(), id]);
|
|
2655
|
+
return getAgent(id, d);
|
|
2656
|
+
}
|
|
2657
|
+
function unarchiveAgent(id, db) {
|
|
2658
|
+
const d = db || getDatabase();
|
|
2659
|
+
d.run("UPDATE agents SET status = 'active', last_seen_at = ? WHERE id = ?", [now(), id]);
|
|
2660
|
+
return getAgent(id, d);
|
|
2665
2661
|
}
|
|
2666
2662
|
function getDirectReports(agentId, db) {
|
|
2667
2663
|
const d = db || getDatabase();
|
|
@@ -3807,6 +3803,7 @@ async function main() {
|
|
|
3807
3803
|
if (port !== requestedPort) {
|
|
3808
3804
|
console.log(`Port ${requestedPort} in use, using ${port}`);
|
|
3809
3805
|
}
|
|
3810
|
-
|
|
3806
|
+
const noOpen = process.argv.includes("--no-open") || process.env["TODOS_NO_OPEN"] === "true";
|
|
3807
|
+
startServer(port, { open: !noOpen });
|
|
3811
3808
|
}
|
|
3812
3809
|
main();
|
package/dist/types/index.d.ts
CHANGED
|
@@ -92,6 +92,7 @@ export interface UpdatePlanInput {
|
|
|
92
92
|
task_list_id?: string;
|
|
93
93
|
agent_id?: string;
|
|
94
94
|
}
|
|
95
|
+
export type AgentStatus = "active" | "archived";
|
|
95
96
|
export interface Agent {
|
|
96
97
|
id: string;
|
|
97
98
|
name: string;
|
|
@@ -103,6 +104,7 @@ export interface Agent {
|
|
|
103
104
|
reports_to: string | null;
|
|
104
105
|
org_id: string | null;
|
|
105
106
|
capabilities: string[];
|
|
107
|
+
status: AgentStatus;
|
|
106
108
|
metadata: Record<string, unknown>;
|
|
107
109
|
created_at: string;
|
|
108
110
|
last_seen_at: string;
|
|
@@ -120,6 +122,7 @@ export interface AgentRow {
|
|
|
120
122
|
capabilities: string | null;
|
|
121
123
|
reports_to: string | null;
|
|
122
124
|
org_id: string | null;
|
|
125
|
+
status: string;
|
|
123
126
|
metadata: string | null;
|
|
124
127
|
created_at: string;
|
|
125
128
|
last_seen_at: string;
|
|
@@ -151,7 +154,6 @@ export interface AgentConflictError {
|
|
|
151
154
|
working_dir: string | null;
|
|
152
155
|
message: string;
|
|
153
156
|
suggestions?: string[];
|
|
154
|
-
pool_violation?: true;
|
|
155
157
|
}
|
|
156
158
|
export interface TaskList {
|
|
157
159
|
id: string;
|