@hasna/conversations 0.2.47 → 0.2.49
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/LICENSE +5 -15
- package/README.md +14 -1
- package/bin/hook.js +94 -0
- package/bin/index.js +1878 -50
- package/bin/mcp.js +1828 -38
- package/dashboard/dist/assets/index-DhHQq3wL.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/brains.test.d.ts +1 -0
- package/dist/cli/commands/analytics.test.d.ts +1 -0
- package/dist/cli/commands/messaging.test.d.ts +1 -0
- package/dist/cli/commands/spaces.test.d.ts +1 -0
- package/dist/cli/commands/tmux.test.d.ts +1 -0
- package/dist/hooks/blocker-hook.test.d.ts +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +916 -15
- package/dist/index.test.d.ts +1 -0
- package/dist/lib/gatherer.test.d.ts +1 -0
- package/dist/lib/model-config.test.d.ts +1 -0
- package/dist/lib/names.test.d.ts +1 -0
- package/dist/lib/pg-migrations.test.d.ts +1 -0
- package/dist/lib/tasks.d.ts +78 -0
- package/dist/lib/tasks.test.d.ts +1 -0
- package/dist/lib/terminal-markdown.test.d.ts +1 -0
- package/dist/lib/webhooks-management.test.d.ts +1 -0
- package/dist/lib/webhooks.d.ts +46 -1
- package/dist/mcp/http.d.ts +16 -0
- package/dist/mcp/http.test.d.ts +1 -0
- package/dist/mcp/index.d.ts +3 -1
- package/dist/mcp/telegram-channel.test.d.ts +1 -0
- package/dist/mcp/tools/advanced.test.d.ts +1 -0
- package/dist/mcp/tools/agents.test.d.ts +1 -0
- package/dist/mcp/tools/messaging.test.d.ts +1 -0
- package/dist/mcp/tools/projects.test.d.ts +1 -0
- package/dist/mcp/tools/spaces.test.d.ts +1 -0
- package/dist/mcp/tools/tasks.d.ts +6 -0
- package/dist/mcp/tools/tasks.test.d.ts +1 -0
- package/dist/mcp/tools/webhooks.d.ts +6 -0
- package/dist/mcp/tools/webhooks.test.d.ts +1 -0
- package/dist/types.d.ts +120 -0
- package/package.json +3 -2
- package/dashboard/dist/assets/index-CF_GDtNp.css +0 -1
- /package/dashboard/dist/assets/{index-Bw0wMcXE.js → index-UKgLYJ49.js} +0 -0
package/bin/index.js
CHANGED
|
@@ -3668,9 +3668,9 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
3668
3668
|
const batch = rows.slice(offset, offset + batchSize);
|
|
3669
3669
|
try {
|
|
3670
3670
|
if (isAsyncAdapter(target)) {
|
|
3671
|
-
await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
|
|
3671
|
+
await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch, columns.includes(conflictColumn) ? conflictColumn : undefined);
|
|
3672
3672
|
} else {
|
|
3673
|
-
batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
|
|
3673
|
+
batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch, columns.includes(conflictColumn) ? conflictColumn : undefined);
|
|
3674
3674
|
}
|
|
3675
3675
|
result.rowsWritten += batch.length;
|
|
3676
3676
|
} catch (err) {
|
|
@@ -3717,7 +3717,7 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
3717
3717
|
}
|
|
3718
3718
|
return results;
|
|
3719
3719
|
}
|
|
3720
|
-
async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch) {
|
|
3720
|
+
async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch, conflictColumn) {
|
|
3721
3721
|
if (batch.length === 0)
|
|
3722
3722
|
return;
|
|
3723
3723
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
@@ -3727,20 +3727,22 @@ async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, ba
|
|
|
3727
3727
|
}).join(", ");
|
|
3728
3728
|
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
3729
3729
|
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
3730
|
+
const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
|
|
3730
3731
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
3731
|
-
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
3732
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}${whereClause}`;
|
|
3732
3733
|
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
3733
3734
|
await target.run(sql, ...params);
|
|
3734
3735
|
}
|
|
3735
|
-
function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch) {
|
|
3736
|
+
function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch, conflictColumn) {
|
|
3736
3737
|
if (batch.length === 0)
|
|
3737
3738
|
return;
|
|
3738
3739
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
3739
3740
|
const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
|
|
3740
3741
|
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
3741
3742
|
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
3743
|
+
const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
|
|
3742
3744
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
3743
|
-
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
3745
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}${whereClause}`;
|
|
3744
3746
|
const params = batch.flatMap((row) => columns.map((c) => coerceForSqlite(row[c])));
|
|
3745
3747
|
target.run(sql, ...params);
|
|
3746
3748
|
}
|
|
@@ -4810,7 +4812,7 @@ async function ensureAllPgDatabases() {
|
|
|
4810
4812
|
}
|
|
4811
4813
|
return results;
|
|
4812
4814
|
}
|
|
4813
|
-
function registerCloudTools(server, serviceName) {
|
|
4815
|
+
function registerCloudTools(server, serviceName, opts = {}) {
|
|
4814
4816
|
server.tool(`${serviceName}_cloud_status`, "Show cloud configuration and connection health", {}, async () => {
|
|
4815
4817
|
const config = getCloudConfig();
|
|
4816
4818
|
const lines = [
|
|
@@ -4843,8 +4845,13 @@ function registerCloudTools(server, serviceName) {
|
|
|
4843
4845
|
isError: true
|
|
4844
4846
|
};
|
|
4845
4847
|
}
|
|
4846
|
-
const local = new SqliteAdapter(getDbPath(serviceName));
|
|
4848
|
+
const local = new SqliteAdapter(opts.dbPath ?? getDbPath(serviceName));
|
|
4847
4849
|
const cloud = new PgAdapterAsync(getConnectionString(serviceName));
|
|
4850
|
+
if (opts.migrations?.length) {
|
|
4851
|
+
for (const sql of opts.migrations) {
|
|
4852
|
+
await cloud.run(sql);
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4848
4855
|
const tableList = tablesStr ? tablesStr.split(",").map((t) => t.trim()) : listSqliteTables(local);
|
|
4849
4856
|
const results = await syncPush(local, cloud, { tables: tableList });
|
|
4850
4857
|
local.close();
|
|
@@ -4866,7 +4873,7 @@ function registerCloudTools(server, serviceName) {
|
|
|
4866
4873
|
isError: true
|
|
4867
4874
|
};
|
|
4868
4875
|
}
|
|
4869
|
-
const local = new SqliteAdapter(getDbPath(serviceName));
|
|
4876
|
+
const local = new SqliteAdapter(opts.dbPath ?? getDbPath(serviceName));
|
|
4870
4877
|
const cloud = new PgAdapterAsync(getConnectionString(serviceName));
|
|
4871
4878
|
let tableList;
|
|
4872
4879
|
if (tablesStr) {
|
|
@@ -13820,6 +13827,100 @@ function getDb() {
|
|
|
13820
13827
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
13821
13828
|
)
|
|
13822
13829
|
`);
|
|
13830
|
+
db.exec(`
|
|
13831
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
13832
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13833
|
+
uuid TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))),
|
|
13834
|
+
subject TEXT NOT NULL,
|
|
13835
|
+
description TEXT,
|
|
13836
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
13837
|
+
priority TEXT NOT NULL DEFAULT 'medium',
|
|
13838
|
+
assignee TEXT,
|
|
13839
|
+
reporter TEXT NOT NULL,
|
|
13840
|
+
project_id TEXT,
|
|
13841
|
+
space TEXT,
|
|
13842
|
+
parent_id INTEGER REFERENCES tasks(id),
|
|
13843
|
+
depends_on TEXT,
|
|
13844
|
+
tags TEXT,
|
|
13845
|
+
metadata TEXT,
|
|
13846
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
13847
|
+
started_at TEXT,
|
|
13848
|
+
completed_at TEXT,
|
|
13849
|
+
cancelled_at TEXT,
|
|
13850
|
+
due_at TEXT
|
|
13851
|
+
)
|
|
13852
|
+
`);
|
|
13853
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_uuid ON tasks(uuid)");
|
|
13854
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)");
|
|
13855
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee)");
|
|
13856
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_reporter ON tasks(reporter)");
|
|
13857
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id)");
|
|
13858
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_space ON tasks(space)");
|
|
13859
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_id)");
|
|
13860
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority)");
|
|
13861
|
+
db.exec(`
|
|
13862
|
+
CREATE TABLE IF NOT EXISTS task_comments (
|
|
13863
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13864
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
13865
|
+
agent TEXT NOT NULL,
|
|
13866
|
+
content TEXT NOT NULL,
|
|
13867
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))
|
|
13868
|
+
)
|
|
13869
|
+
`);
|
|
13870
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id)");
|
|
13871
|
+
db.exec(`
|
|
13872
|
+
CREATE TABLE IF NOT EXISTS task_activity (
|
|
13873
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13874
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
13875
|
+
agent TEXT NOT NULL,
|
|
13876
|
+
action TEXT NOT NULL,
|
|
13877
|
+
detail TEXT,
|
|
13878
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))
|
|
13879
|
+
)
|
|
13880
|
+
`);
|
|
13881
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_task_activity_task ON task_activity(task_id)");
|
|
13882
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_task_activity_agent ON task_activity(agent)");
|
|
13883
|
+
db.exec(`
|
|
13884
|
+
CREATE TABLE IF NOT EXISTS task_dependencies (
|
|
13885
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
13886
|
+
depends_on_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
13887
|
+
PRIMARY KEY (task_id, depends_on_id)
|
|
13888
|
+
)
|
|
13889
|
+
`);
|
|
13890
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_task_deps_depends ON task_dependencies(depends_on_id)");
|
|
13891
|
+
const hasTasksFts = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='tasks_fts'").get();
|
|
13892
|
+
if (!hasTasksFts) {
|
|
13893
|
+
db.exec(`
|
|
13894
|
+
CREATE VIRTUAL TABLE tasks_fts USING fts5(
|
|
13895
|
+
subject, description, tags
|
|
13896
|
+
)
|
|
13897
|
+
`);
|
|
13898
|
+
db.exec(`
|
|
13899
|
+
INSERT INTO tasks_fts(rowid, subject, description, tags)
|
|
13900
|
+
SELECT id, COALESCE(subject, ''), COALESCE(description, ''),
|
|
13901
|
+
COALESCE(REPLACE(REPLACE(REPLACE(tags, '[', ''), ']', ''), '"', ''), '')
|
|
13902
|
+
FROM tasks
|
|
13903
|
+
`);
|
|
13904
|
+
db.exec(`
|
|
13905
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_insert AFTER INSERT ON tasks BEGIN
|
|
13906
|
+
INSERT INTO tasks_fts(rowid, subject, description, tags)
|
|
13907
|
+
VALUES (new.id, COALESCE(new.subject, ''), COALESCE(new.description, ''),
|
|
13908
|
+
COALESCE(REPLACE(REPLACE(REPLACE(new.tags, '[', ''), ']', ''), '"', ''), ''));
|
|
13909
|
+
END
|
|
13910
|
+
`);
|
|
13911
|
+
db.exec(`
|
|
13912
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_delete AFTER DELETE ON tasks BEGIN
|
|
13913
|
+
DELETE FROM tasks_fts WHERE rowid = old.id;
|
|
13914
|
+
END
|
|
13915
|
+
`);
|
|
13916
|
+
db.exec(`
|
|
13917
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_update AFTER UPDATE ON tasks BEGIN
|
|
13918
|
+
INSERT OR REPLACE INTO tasks_fts(rowid, subject, description, tags)
|
|
13919
|
+
VALUES (new.id, COALESCE(new.subject, ''), COALESCE(new.description, ''),
|
|
13920
|
+
COALESCE(REPLACE(REPLACE(REPLACE(new.tags, '[', ''), ']', ''), '"', ''), ''));
|
|
13921
|
+
END
|
|
13922
|
+
`);
|
|
13923
|
+
}
|
|
13823
13924
|
return db;
|
|
13824
13925
|
}
|
|
13825
13926
|
function closeDb() {
|
|
@@ -14156,7 +14257,7 @@ var init_spaces = __esm(() => {
|
|
|
14156
14257
|
});
|
|
14157
14258
|
|
|
14158
14259
|
// src/lib/webhooks.ts
|
|
14159
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
14260
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync7 } from "fs";
|
|
14160
14261
|
import { join as join10 } from "path";
|
|
14161
14262
|
import dns from "dns";
|
|
14162
14263
|
import net from "net";
|
|
@@ -14258,6 +14359,27 @@ function fireWebhooks(msg) {
|
|
|
14258
14359
|
});
|
|
14259
14360
|
}
|
|
14260
14361
|
}
|
|
14362
|
+
function fireTaskWebhooks(event) {
|
|
14363
|
+
const config = loadConfig();
|
|
14364
|
+
if (!config.webhooks || config.webhooks.length === 0)
|
|
14365
|
+
return;
|
|
14366
|
+
const taskWebhooks = config.webhooks.filter((w) => w.events.includes("task"));
|
|
14367
|
+
if (taskWebhooks.length === 0)
|
|
14368
|
+
return;
|
|
14369
|
+
for (const webhook of taskWebhooks) {
|
|
14370
|
+
if (webhook.agent && event.agent !== webhook.agent)
|
|
14371
|
+
continue;
|
|
14372
|
+
validateWebhookUrl(webhook.url).then((valid) => {
|
|
14373
|
+
if (!valid)
|
|
14374
|
+
return;
|
|
14375
|
+
fetch(webhook.url, {
|
|
14376
|
+
method: "POST",
|
|
14377
|
+
headers: { "Content-Type": "application/json" },
|
|
14378
|
+
body: JSON.stringify(event)
|
|
14379
|
+
}).catch(() => {});
|
|
14380
|
+
});
|
|
14381
|
+
}
|
|
14382
|
+
}
|
|
14261
14383
|
var cachedConfig = null, configLoadedAt = 0, CONFIG_CACHE_MS = 1e4;
|
|
14262
14384
|
var init_webhooks = __esm(() => {
|
|
14263
14385
|
init_db();
|
|
@@ -14265,7 +14387,7 @@ var init_webhooks = __esm(() => {
|
|
|
14265
14387
|
|
|
14266
14388
|
// src/lib/messages.ts
|
|
14267
14389
|
import { randomUUID } from "crypto";
|
|
14268
|
-
import { mkdirSync as
|
|
14390
|
+
import { mkdirSync as mkdirSync8, copyFileSync as copyFileSync3, statSync as statSync2, existsSync as existsSync10, realpathSync } from "fs";
|
|
14269
14391
|
import { join as join11, basename, resolve } from "path";
|
|
14270
14392
|
function compactMessage(msg) {
|
|
14271
14393
|
const result = {};
|
|
@@ -14309,7 +14431,7 @@ function getAttachmentsDir() {
|
|
|
14309
14431
|
}
|
|
14310
14432
|
function validateAttachment(sourcePath, name) {
|
|
14311
14433
|
const absolute = resolve(sourcePath);
|
|
14312
|
-
if (!
|
|
14434
|
+
if (!existsSync10(absolute)) {
|
|
14313
14435
|
throw new Error(`Attachment source not found: ${sourcePath}`);
|
|
14314
14436
|
}
|
|
14315
14437
|
const real = realpathSync(absolute);
|
|
@@ -14387,7 +14509,7 @@ function sendMessage(opts) {
|
|
|
14387
14509
|
const message = parseMessage(row);
|
|
14388
14510
|
if (opts.attachments && opts.attachments.length > 0) {
|
|
14389
14511
|
const attachmentsDir = join11(getAttachmentsDir(), String(message.id));
|
|
14390
|
-
|
|
14512
|
+
mkdirSync8(attachmentsDir, { recursive: true });
|
|
14391
14513
|
const attachmentInfos = [];
|
|
14392
14514
|
for (const att of opts.attachments) {
|
|
14393
14515
|
const { safeSource, safeName } = validateAttachment(att.source_path, att.name);
|
|
@@ -15416,7 +15538,7 @@ var init_space_notifications = __esm(() => {
|
|
|
15416
15538
|
var require_package = __commonJS((exports, module) => {
|
|
15417
15539
|
module.exports = {
|
|
15418
15540
|
name: "@hasna/conversations",
|
|
15419
|
-
version: "0.2.
|
|
15541
|
+
version: "0.2.49",
|
|
15420
15542
|
description: "Real-time CLI messaging for AI agents",
|
|
15421
15543
|
type: "module",
|
|
15422
15544
|
bin: {
|
|
@@ -15467,7 +15589,7 @@ var require_package = __commonJS((exports, module) => {
|
|
|
15467
15589
|
typescript: "^5"
|
|
15468
15590
|
},
|
|
15469
15591
|
dependencies: {
|
|
15470
|
-
"@hasna/cloud": "^0.1.
|
|
15592
|
+
"@hasna/cloud": "^0.1.30",
|
|
15471
15593
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
15472
15594
|
chalk: "^5.3.0",
|
|
15473
15595
|
commander: "^12.1.0",
|
|
@@ -15475,6 +15597,7 @@ var require_package = __commonJS((exports, module) => {
|
|
|
15475
15597
|
"ink-select-input": "^6.0.0",
|
|
15476
15598
|
"ink-spinner": "^5.0.0",
|
|
15477
15599
|
"ink-text-input": "^6.0.0",
|
|
15600
|
+
pg: "^8.20.0",
|
|
15478
15601
|
react: "^18.2.0",
|
|
15479
15602
|
zod: "^4.3.6"
|
|
15480
15603
|
},
|
|
@@ -34261,7 +34384,7 @@ function assertCompleteRequestResourceTemplate(request) {
|
|
|
34261
34384
|
throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request.params.ref.type}`);
|
|
34262
34385
|
}
|
|
34263
34386
|
}
|
|
34264
|
-
var LATEST_PROTOCOL_VERSION = "2025-11-25", SUPPORTED_PROTOCOL_VERSIONS, RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task", JSONRPC_VERSION = "2.0", AssertObjectSchema, ProgressTokenSchema, CursorSchema, TaskCreationParamsSchema, TaskMetadataSchema, RelatedTaskMetadataSchema, RequestMetaSchema, BaseRequestParamsSchema, TaskAugmentedRequestParamsSchema, isTaskAugmentedRequestParams = (value) => TaskAugmentedRequestParamsSchema.safeParse(value).success, RequestSchema, NotificationsParamsSchema, NotificationSchema, ResultSchema, RequestIdSchema, JSONRPCRequestSchema, isJSONRPCRequest = (value) => JSONRPCRequestSchema.safeParse(value).success, JSONRPCNotificationSchema, isJSONRPCNotification = (value) => JSONRPCNotificationSchema.safeParse(value).success, JSONRPCResultResponseSchema, isJSONRPCResultResponse = (value) => JSONRPCResultResponseSchema.safeParse(value).success, ErrorCode, JSONRPCErrorResponseSchema, isJSONRPCErrorResponse = (value) => JSONRPCErrorResponseSchema.safeParse(value).success, JSONRPCMessageSchema, JSONRPCResponseSchema, EmptyResultSchema, CancelledNotificationParamsSchema, CancelledNotificationSchema, IconSchema, IconsSchema, BaseMetadataSchema, ImplementationSchema, FormElicitationCapabilitySchema, ElicitationCapabilitySchema, ClientTasksCapabilitySchema, ServerTasksCapabilitySchema, ClientCapabilitiesSchema, InitializeRequestParamsSchema, InitializeRequestSchema, ServerCapabilitiesSchema, InitializeResultSchema, InitializedNotificationSchema, PingRequestSchema, ProgressSchema, ProgressNotificationParamsSchema, ProgressNotificationSchema, PaginatedRequestParamsSchema, PaginatedRequestSchema, PaginatedResultSchema, TaskStatusSchema, TaskSchema, CreateTaskResultSchema, TaskStatusNotificationParamsSchema, TaskStatusNotificationSchema, GetTaskRequestSchema, GetTaskResultSchema, GetTaskPayloadRequestSchema, GetTaskPayloadResultSchema, ListTasksRequestSchema, ListTasksResultSchema, CancelTaskRequestSchema, CancelTaskResultSchema, ResourceContentsSchema, TextResourceContentsSchema, Base64Schema, BlobResourceContentsSchema, RoleSchema, AnnotationsSchema, ResourceSchema, ResourceTemplateSchema, ListResourcesRequestSchema, ListResourcesResultSchema, ListResourceTemplatesRequestSchema, ListResourceTemplatesResultSchema, ResourceRequestParamsSchema, ReadResourceRequestParamsSchema, ReadResourceRequestSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, SubscribeRequestParamsSchema, SubscribeRequestSchema, UnsubscribeRequestParamsSchema, UnsubscribeRequestSchema, ResourceUpdatedNotificationParamsSchema, ResourceUpdatedNotificationSchema, PromptArgumentSchema, PromptSchema, ListPromptsRequestSchema, ListPromptsResultSchema, GetPromptRequestParamsSchema, GetPromptRequestSchema, TextContentSchema, ImageContentSchema, AudioContentSchema, ToolUseContentSchema, EmbeddedResourceSchema, ResourceLinkSchema, ContentBlockSchema, PromptMessageSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ToolAnnotationsSchema, ToolExecutionSchema, ToolSchema, ListToolsRequestSchema, ListToolsResultSchema, CallToolResultSchema, CompatibilityCallToolResultSchema, CallToolRequestParamsSchema, CallToolRequestSchema, ToolListChangedNotificationSchema, ListChangedOptionsBaseSchema, LoggingLevelSchema, SetLevelRequestParamsSchema, SetLevelRequestSchema, LoggingMessageNotificationParamsSchema, LoggingMessageNotificationSchema, ModelHintSchema, ModelPreferencesSchema, ToolChoiceSchema, ToolResultContentSchema, SamplingContentSchema, SamplingMessageContentBlockSchema, SamplingMessageSchema, CreateMessageRequestParamsSchema, CreateMessageRequestSchema, CreateMessageResultSchema, CreateMessageResultWithToolsSchema, BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema, UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema, LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema, EnumSchemaSchema, PrimitiveSchemaDefinitionSchema, ElicitRequestFormParamsSchema, ElicitRequestURLParamsSchema, ElicitRequestParamsSchema, ElicitRequestSchema, ElicitationCompleteNotificationParamsSchema, ElicitationCompleteNotificationSchema, ElicitResultSchema, ResourceTemplateReferenceSchema, PromptReferenceSchema, CompleteRequestParamsSchema, CompleteRequestSchema, CompleteResultSchema, RootSchema, ListRootsRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, ClientRequestSchema, ClientNotificationSchema, ClientResultSchema, ServerRequestSchema, ServerNotificationSchema, ServerResultSchema, McpError, UrlElicitationRequiredError;
|
|
34387
|
+
var LATEST_PROTOCOL_VERSION = "2025-11-25", DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26", SUPPORTED_PROTOCOL_VERSIONS, RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task", JSONRPC_VERSION = "2.0", AssertObjectSchema, ProgressTokenSchema, CursorSchema, TaskCreationParamsSchema, TaskMetadataSchema, RelatedTaskMetadataSchema, RequestMetaSchema, BaseRequestParamsSchema, TaskAugmentedRequestParamsSchema, isTaskAugmentedRequestParams = (value) => TaskAugmentedRequestParamsSchema.safeParse(value).success, RequestSchema, NotificationsParamsSchema, NotificationSchema, ResultSchema, RequestIdSchema, JSONRPCRequestSchema, isJSONRPCRequest = (value) => JSONRPCRequestSchema.safeParse(value).success, JSONRPCNotificationSchema, isJSONRPCNotification = (value) => JSONRPCNotificationSchema.safeParse(value).success, JSONRPCResultResponseSchema, isJSONRPCResultResponse = (value) => JSONRPCResultResponseSchema.safeParse(value).success, ErrorCode, JSONRPCErrorResponseSchema, isJSONRPCErrorResponse = (value) => JSONRPCErrorResponseSchema.safeParse(value).success, JSONRPCMessageSchema, JSONRPCResponseSchema, EmptyResultSchema, CancelledNotificationParamsSchema, CancelledNotificationSchema, IconSchema, IconsSchema, BaseMetadataSchema, ImplementationSchema, FormElicitationCapabilitySchema, ElicitationCapabilitySchema, ClientTasksCapabilitySchema, ServerTasksCapabilitySchema, ClientCapabilitiesSchema, InitializeRequestParamsSchema, InitializeRequestSchema, isInitializeRequest = (value) => InitializeRequestSchema.safeParse(value).success, ServerCapabilitiesSchema, InitializeResultSchema, InitializedNotificationSchema, PingRequestSchema, ProgressSchema, ProgressNotificationParamsSchema, ProgressNotificationSchema, PaginatedRequestParamsSchema, PaginatedRequestSchema, PaginatedResultSchema, TaskStatusSchema, TaskSchema, CreateTaskResultSchema, TaskStatusNotificationParamsSchema, TaskStatusNotificationSchema, GetTaskRequestSchema, GetTaskResultSchema, GetTaskPayloadRequestSchema, GetTaskPayloadResultSchema, ListTasksRequestSchema, ListTasksResultSchema, CancelTaskRequestSchema, CancelTaskResultSchema, ResourceContentsSchema, TextResourceContentsSchema, Base64Schema, BlobResourceContentsSchema, RoleSchema, AnnotationsSchema, ResourceSchema, ResourceTemplateSchema, ListResourcesRequestSchema, ListResourcesResultSchema, ListResourceTemplatesRequestSchema, ListResourceTemplatesResultSchema, ResourceRequestParamsSchema, ReadResourceRequestParamsSchema, ReadResourceRequestSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, SubscribeRequestParamsSchema, SubscribeRequestSchema, UnsubscribeRequestParamsSchema, UnsubscribeRequestSchema, ResourceUpdatedNotificationParamsSchema, ResourceUpdatedNotificationSchema, PromptArgumentSchema, PromptSchema, ListPromptsRequestSchema, ListPromptsResultSchema, GetPromptRequestParamsSchema, GetPromptRequestSchema, TextContentSchema, ImageContentSchema, AudioContentSchema, ToolUseContentSchema, EmbeddedResourceSchema, ResourceLinkSchema, ContentBlockSchema, PromptMessageSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ToolAnnotationsSchema, ToolExecutionSchema, ToolSchema, ListToolsRequestSchema, ListToolsResultSchema, CallToolResultSchema, CompatibilityCallToolResultSchema, CallToolRequestParamsSchema, CallToolRequestSchema, ToolListChangedNotificationSchema, ListChangedOptionsBaseSchema, LoggingLevelSchema, SetLevelRequestParamsSchema, SetLevelRequestSchema, LoggingMessageNotificationParamsSchema, LoggingMessageNotificationSchema, ModelHintSchema, ModelPreferencesSchema, ToolChoiceSchema, ToolResultContentSchema, SamplingContentSchema, SamplingMessageContentBlockSchema, SamplingMessageSchema, CreateMessageRequestParamsSchema, CreateMessageRequestSchema, CreateMessageResultSchema, CreateMessageResultWithToolsSchema, BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema, UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema, LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema, EnumSchemaSchema, PrimitiveSchemaDefinitionSchema, ElicitRequestFormParamsSchema, ElicitRequestURLParamsSchema, ElicitRequestParamsSchema, ElicitRequestSchema, ElicitationCompleteNotificationParamsSchema, ElicitationCompleteNotificationSchema, ElicitResultSchema, ResourceTemplateReferenceSchema, PromptReferenceSchema, CompleteRequestParamsSchema, CompleteRequestSchema, CompleteResultSchema, RootSchema, ListRootsRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, ClientRequestSchema, ClientNotificationSchema, ClientResultSchema, ServerRequestSchema, ServerNotificationSchema, ServerResultSchema, McpError, UrlElicitationRequiredError;
|
|
34265
34388
|
var init_types3 = __esm(() => {
|
|
34266
34389
|
init_v4();
|
|
34267
34390
|
SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
|
|
@@ -47139,7 +47262,28 @@ function registerAdvancedTools(server, pkgVersion) {
|
|
|
47139
47262
|
"remove_agent",
|
|
47140
47263
|
"rename_agent",
|
|
47141
47264
|
"search_tools",
|
|
47142
|
-
"describe_tools"
|
|
47265
|
+
"describe_tools",
|
|
47266
|
+
"create_task",
|
|
47267
|
+
"get_task",
|
|
47268
|
+
"list_tasks",
|
|
47269
|
+
"start_task",
|
|
47270
|
+
"complete_task",
|
|
47271
|
+
"cancel_task",
|
|
47272
|
+
"block_task",
|
|
47273
|
+
"unblock_task",
|
|
47274
|
+
"reopen_task",
|
|
47275
|
+
"assign_task",
|
|
47276
|
+
"set_task_priority",
|
|
47277
|
+
"delete_task",
|
|
47278
|
+
"add_comment",
|
|
47279
|
+
"get_comments",
|
|
47280
|
+
"get_subtasks",
|
|
47281
|
+
"get_task_tree",
|
|
47282
|
+
"add_dependency",
|
|
47283
|
+
"remove_dependency",
|
|
47284
|
+
"get_dependencies",
|
|
47285
|
+
"get_dependents",
|
|
47286
|
+
"get_task_activity"
|
|
47143
47287
|
];
|
|
47144
47288
|
const q = query?.toLowerCase();
|
|
47145
47289
|
const matches = q ? all.filter((n) => n.includes(q)) : all;
|
|
@@ -47220,7 +47364,28 @@ function registerAdvancedTools(server, pkgVersion) {
|
|
|
47220
47364
|
remove_agent: "Remove agent from presence list. Optional: from?, agent?(defaults to self)",
|
|
47221
47365
|
rename_agent: "Rename agent in presence list. Required: new_name. Optional: from?",
|
|
47222
47366
|
search_tools: "Search tool names by keyword. Optional: query?",
|
|
47223
|
-
describe_tools: "Get full descriptions for tools. Required: names(array of tool names)"
|
|
47367
|
+
describe_tools: "Get full descriptions for tools. Required: names(array of tool names)",
|
|
47368
|
+
create_task: "Create a new task. Required: subject, reporter. Optional: description?, assignee?, priority?(low|medium|high|critical), project_id?, space?, parent_id?(subtask), depends_on?(array of task ids), tags?(array), metadata?(JSON), due_at?(ISO date)",
|
|
47369
|
+
get_task: "Get a task by id or uuid. Returns enriched TaskInfo with subtask_count, comment_count, dependency_count, blocker_info. Required: id? or uuid?",
|
|
47370
|
+
list_tasks: "List tasks with filters. Optional: status?(pending|in_progress|completed|cancelled|blocked), assignee?, reporter?, project_id?, space?, parent_id?(null for top-level), priority?, tag?, limit?(default 50), offset?, include_archived?",
|
|
47371
|
+
start_task: "Mark task in_progress. Fails if any dependency not completed. Required: id. Optional: agent?",
|
|
47372
|
+
complete_task: "Mark task completed. Auto-unblocks dependent tasks with all deps met. Required: id. Optional: agent?, evidence?",
|
|
47373
|
+
cancel_task: "Cancel a task with optional reason. Required: id. Optional: agent?, reason?",
|
|
47374
|
+
block_task: "Manually block a task. Required: id. Optional: agent?, reason?",
|
|
47375
|
+
unblock_task: "Unblock a task to pending if all deps completed, stays blocked otherwise. Required: id. Optional: agent?",
|
|
47376
|
+
reopen_task: "Reopen completed/cancelled task back to pending. Re-checks dependencies. Required: id. Optional: agent?",
|
|
47377
|
+
assign_task: "Assign a task to an agent. Required: id, assignee. Optional: agent?",
|
|
47378
|
+
set_task_priority: "Change task priority. Required: id, priority(low|medium|high|critical). Optional: agent?",
|
|
47379
|
+
delete_task: "Delete a task. Fails if subtasks exist. Required: id. Optional: agent?",
|
|
47380
|
+
add_comment: "Add a comment to a task. Required: task_id, content. Optional: agent?",
|
|
47381
|
+
get_comments: "Get all comments on a task ordered by creation time. Required: task_id",
|
|
47382
|
+
get_subtasks: "Get direct children (subtasks) of a parent task. Required: parent_id",
|
|
47383
|
+
get_task_tree: "Get task with full subtask tree (recursive, max depth 5). Required: parent_id. Optional: max_depth?",
|
|
47384
|
+
add_dependency: "Add dependency: task_id depends on depends_on_id. Prevents circular deps. Auto-blocks if dep not completed. Required: task_id, depends_on_id",
|
|
47385
|
+
remove_dependency: "Remove a dependency. Required: task_id, depends_on_id",
|
|
47386
|
+
get_dependencies: "Get tasks this task depends on (must complete first). Required: task_id",
|
|
47387
|
+
get_dependents: "Get tasks that depend on this task (blocked by this). Required: task_id",
|
|
47388
|
+
get_task_activity: "Get activity log: status changes, comments, dep changes. Required: task_id. Optional: limit?(default 50)"
|
|
47224
47389
|
};
|
|
47225
47390
|
const result = names.map((n) => `${n}: ${descriptions[n] || "See tool schema"}`).join(`
|
|
47226
47391
|
`);
|
|
@@ -47497,7 +47662,9 @@ function registerCloudSyncTools(server) {
|
|
|
47497
47662
|
`Service: conversations`,
|
|
47498
47663
|
`RDS Host: ${config2.rds.host || "(not configured)"}`
|
|
47499
47664
|
];
|
|
47500
|
-
if (config2.
|
|
47665
|
+
if (config2.mode === "local") {
|
|
47666
|
+
lines.push("PostgreSQL: skipped in local mode");
|
|
47667
|
+
} else if (config2.rds.host && config2.rds.username) {
|
|
47501
47668
|
try {
|
|
47502
47669
|
const pg = new PgAdapterAsync2(getConnectionString2("postgres"));
|
|
47503
47670
|
await pg.get("SELECT 1 as ok");
|
|
@@ -47944,11 +48111,1606 @@ var init_tmux2 = __esm(() => {
|
|
|
47944
48111
|
init_tmux();
|
|
47945
48112
|
});
|
|
47946
48113
|
|
|
48114
|
+
// src/lib/tasks.ts
|
|
48115
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
48116
|
+
function parseTask(row) {
|
|
48117
|
+
let dependsOn = null;
|
|
48118
|
+
if (row.depends_on) {
|
|
48119
|
+
try {
|
|
48120
|
+
dependsOn = JSON.parse(row.depends_on);
|
|
48121
|
+
} catch {
|
|
48122
|
+
dependsOn = null;
|
|
48123
|
+
}
|
|
48124
|
+
}
|
|
48125
|
+
let tags = null;
|
|
48126
|
+
if (row.tags) {
|
|
48127
|
+
try {
|
|
48128
|
+
tags = JSON.parse(row.tags);
|
|
48129
|
+
} catch {
|
|
48130
|
+
tags = null;
|
|
48131
|
+
}
|
|
48132
|
+
}
|
|
48133
|
+
let metadata = null;
|
|
48134
|
+
if (row.metadata) {
|
|
48135
|
+
try {
|
|
48136
|
+
metadata = JSON.parse(row.metadata);
|
|
48137
|
+
} catch {
|
|
48138
|
+
metadata = null;
|
|
48139
|
+
}
|
|
48140
|
+
}
|
|
48141
|
+
return {
|
|
48142
|
+
id: row.id,
|
|
48143
|
+
uuid: row.uuid,
|
|
48144
|
+
subject: row.subject,
|
|
48145
|
+
description: row.description || null,
|
|
48146
|
+
status: row.status,
|
|
48147
|
+
priority: row.priority,
|
|
48148
|
+
assignee: row.assignee || null,
|
|
48149
|
+
reporter: row.reporter,
|
|
48150
|
+
project_id: row.project_id || null,
|
|
48151
|
+
space: row.space || null,
|
|
48152
|
+
parent_id: row.parent_id || null,
|
|
48153
|
+
depends_on: dependsOn,
|
|
48154
|
+
tags,
|
|
48155
|
+
metadata,
|
|
48156
|
+
created_at: row.created_at,
|
|
48157
|
+
started_at: row.started_at || null,
|
|
48158
|
+
completed_at: row.completed_at || null,
|
|
48159
|
+
cancelled_at: row.cancelled_at || null,
|
|
48160
|
+
due_at: row.due_at || null
|
|
48161
|
+
};
|
|
48162
|
+
}
|
|
48163
|
+
function logActivity(taskId, agent, action, detail) {
|
|
48164
|
+
const db2 = getDb();
|
|
48165
|
+
db2.prepare("INSERT INTO task_activity (task_id, agent, action, detail) VALUES (?, ?, ?, ?)").run(taskId, agent, action, detail || null);
|
|
48166
|
+
}
|
|
48167
|
+
function emitTaskEvent(task, action, agent, oldStatus, detail) {
|
|
48168
|
+
fireTaskWebhooks({
|
|
48169
|
+
task_id: task.id,
|
|
48170
|
+
task_uuid: task.uuid,
|
|
48171
|
+
subject: task.subject,
|
|
48172
|
+
action,
|
|
48173
|
+
old_status: oldStatus,
|
|
48174
|
+
new_status: task.status,
|
|
48175
|
+
agent,
|
|
48176
|
+
detail,
|
|
48177
|
+
priority: task.priority,
|
|
48178
|
+
assignee: task.assignee,
|
|
48179
|
+
project_id: task.project_id,
|
|
48180
|
+
created_at: task.created_at
|
|
48181
|
+
});
|
|
48182
|
+
}
|
|
48183
|
+
function createTask(opts) {
|
|
48184
|
+
const db2 = getDb();
|
|
48185
|
+
const uuid3 = randomUUID3().replace(/-/g, "");
|
|
48186
|
+
const priority = opts.priority || "medium";
|
|
48187
|
+
const description = opts.description || null;
|
|
48188
|
+
const assignee = opts.assignee || null;
|
|
48189
|
+
const project_id = opts.project_id || null;
|
|
48190
|
+
const space = opts.space || null;
|
|
48191
|
+
const parent_id = opts.parent_id || null;
|
|
48192
|
+
const tags = opts.tags ? JSON.stringify(opts.tags) : null;
|
|
48193
|
+
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
48194
|
+
const due_at = opts.due_at || null;
|
|
48195
|
+
const row = db2.prepare(`
|
|
48196
|
+
INSERT INTO tasks (uuid, subject, description, reporter, assignee, priority, project_id, space, parent_id, tags, metadata, due_at)
|
|
48197
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
48198
|
+
RETURNING *
|
|
48199
|
+
`).get(uuid3, opts.subject, description, opts.reporter, assignee, priority, project_id, space, parent_id, tags, metadata, due_at);
|
|
48200
|
+
const task = parseTask(row);
|
|
48201
|
+
if (opts.depends_on && opts.depends_on.length > 0) {
|
|
48202
|
+
const depIds = opts.depends_on;
|
|
48203
|
+
const insertDep = db2.prepare("INSERT INTO task_dependencies (task_id, depends_on_id) VALUES (?, ?)");
|
|
48204
|
+
const depIdsResolved = [];
|
|
48205
|
+
for (const depId of depIds) {
|
|
48206
|
+
const exists = db2.prepare("SELECT id, status FROM tasks WHERE id = ?").get(depId);
|
|
48207
|
+
if (!exists)
|
|
48208
|
+
throw new Error(`Dependency task #${depId} not found`);
|
|
48209
|
+
insertDep.run(task.id, depId);
|
|
48210
|
+
depIdsResolved.push(depId);
|
|
48211
|
+
}
|
|
48212
|
+
db2.prepare("UPDATE tasks SET depends_on = ? WHERE id = ?").run(JSON.stringify(depIdsResolved), task.id);
|
|
48213
|
+
const incompleteDeps = db2.prepare("SELECT depends_on_id FROM task_dependencies WHERE task_id = ? AND depends_on_id IN (SELECT id FROM tasks WHERE status != 'completed')").all(task.id);
|
|
48214
|
+
if (incompleteDeps.length > 0) {
|
|
48215
|
+
db2.prepare("UPDATE tasks SET status = 'blocked' WHERE id = ?").run(task.id);
|
|
48216
|
+
}
|
|
48217
|
+
}
|
|
48218
|
+
logActivity(task.id, opts.reporter, "created");
|
|
48219
|
+
const created = parseTask(db2.prepare("SELECT * FROM tasks WHERE id = ?").get(task.id));
|
|
48220
|
+
fireTaskWebhooks({
|
|
48221
|
+
task_id: created.id,
|
|
48222
|
+
task_uuid: created.uuid,
|
|
48223
|
+
subject: created.subject,
|
|
48224
|
+
action: "created",
|
|
48225
|
+
new_status: created.status,
|
|
48226
|
+
agent: opts.reporter,
|
|
48227
|
+
priority: created.priority,
|
|
48228
|
+
assignee: created.assignee,
|
|
48229
|
+
project_id: created.project_id,
|
|
48230
|
+
created_at: created.created_at
|
|
48231
|
+
});
|
|
48232
|
+
return created;
|
|
48233
|
+
}
|
|
48234
|
+
function getTask(idOrUuid) {
|
|
48235
|
+
const db2 = getDb();
|
|
48236
|
+
let row = null;
|
|
48237
|
+
if (typeof idOrUuid === "number") {
|
|
48238
|
+
row = db2.prepare("SELECT * FROM tasks WHERE id = ?").get(idOrUuid);
|
|
48239
|
+
} else {
|
|
48240
|
+
row = db2.prepare("SELECT * FROM tasks WHERE uuid = ?").get(idOrUuid);
|
|
48241
|
+
}
|
|
48242
|
+
if (!row)
|
|
48243
|
+
return null;
|
|
48244
|
+
return enrichTask(row);
|
|
48245
|
+
}
|
|
48246
|
+
function listTasks(opts = {}) {
|
|
48247
|
+
const db2 = getDb();
|
|
48248
|
+
const conditions = [];
|
|
48249
|
+
const params = [];
|
|
48250
|
+
if (opts.status) {
|
|
48251
|
+
conditions.push("t.status = ?");
|
|
48252
|
+
params.push(opts.status);
|
|
48253
|
+
}
|
|
48254
|
+
if (opts.assignee) {
|
|
48255
|
+
conditions.push("t.assignee = ?");
|
|
48256
|
+
params.push(opts.assignee);
|
|
48257
|
+
}
|
|
48258
|
+
if (opts.reporter) {
|
|
48259
|
+
conditions.push("t.reporter = ?");
|
|
48260
|
+
params.push(opts.reporter);
|
|
48261
|
+
}
|
|
48262
|
+
if (opts.project_id) {
|
|
48263
|
+
conditions.push("t.project_id = ?");
|
|
48264
|
+
params.push(opts.project_id);
|
|
48265
|
+
}
|
|
48266
|
+
if (opts.space) {
|
|
48267
|
+
conditions.push("t.space = ?");
|
|
48268
|
+
params.push(opts.space);
|
|
48269
|
+
}
|
|
48270
|
+
if (opts.priority) {
|
|
48271
|
+
conditions.push("t.priority = ?");
|
|
48272
|
+
params.push(opts.priority);
|
|
48273
|
+
}
|
|
48274
|
+
if (opts.tag) {
|
|
48275
|
+
conditions.push("t.tags LIKE ?");
|
|
48276
|
+
params.push(`%"${opts.tag}"%`);
|
|
48277
|
+
}
|
|
48278
|
+
if (opts.tags && opts.tags.length > 0) {
|
|
48279
|
+
for (const tag of opts.tags) {
|
|
48280
|
+
conditions.push("t.tags LIKE ?");
|
|
48281
|
+
params.push(`%"${tag}"%`);
|
|
48282
|
+
}
|
|
48283
|
+
}
|
|
48284
|
+
if (opts.metadata && Object.keys(opts.metadata).length > 0) {
|
|
48285
|
+
for (const [key, value] of Object.entries(opts.metadata)) {
|
|
48286
|
+
if (typeof value === "string") {
|
|
48287
|
+
conditions.push(`t.metadata LIKE ?`);
|
|
48288
|
+
params.push(`%"${key}":"${value}"%`);
|
|
48289
|
+
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
48290
|
+
conditions.push(`t.metadata LIKE ?`);
|
|
48291
|
+
params.push(`%"${key}":${value}%`);
|
|
48292
|
+
} else {
|
|
48293
|
+
conditions.push(`t.metadata LIKE ?`);
|
|
48294
|
+
params.push(`%"${key}"%`);
|
|
48295
|
+
}
|
|
48296
|
+
}
|
|
48297
|
+
}
|
|
48298
|
+
if (opts.parent_id === null) {
|
|
48299
|
+
conditions.push("t.parent_id IS NULL");
|
|
48300
|
+
} else if (typeof opts.parent_id === "number") {
|
|
48301
|
+
conditions.push("t.parent_id = ?");
|
|
48302
|
+
params.push(opts.parent_id);
|
|
48303
|
+
}
|
|
48304
|
+
if (!opts.include_archived) {
|
|
48305
|
+
conditions.push("t.status != 'cancelled'");
|
|
48306
|
+
}
|
|
48307
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
48308
|
+
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 50;
|
|
48309
|
+
const offset = Number.isFinite(opts.offset) && opts.offset >= 0 ? Math.floor(opts.offset) : 0;
|
|
48310
|
+
const rows = db2.prepare(`
|
|
48311
|
+
SELECT t.* FROM tasks t
|
|
48312
|
+
${where}
|
|
48313
|
+
ORDER BY
|
|
48314
|
+
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
48315
|
+
t.created_at DESC
|
|
48316
|
+
LIMIT ? OFFSET ?
|
|
48317
|
+
`).all(...params, limit, offset);
|
|
48318
|
+
return rows.map(enrichTask);
|
|
48319
|
+
}
|
|
48320
|
+
function startTask(id, agent) {
|
|
48321
|
+
const db2 = getDb();
|
|
48322
|
+
const task = resolveTask(id);
|
|
48323
|
+
if (!task)
|
|
48324
|
+
return null;
|
|
48325
|
+
const incompleteDeps = db2.prepare(`
|
|
48326
|
+
SELECT td.depends_on_id, t.subject, t.status
|
|
48327
|
+
FROM task_dependencies td
|
|
48328
|
+
JOIN tasks t ON t.id = td.depends_on_id
|
|
48329
|
+
WHERE td.task_id = ? AND t.status != 'completed'
|
|
48330
|
+
`).all(task.id);
|
|
48331
|
+
if (incompleteDeps.length > 0) {
|
|
48332
|
+
throw new Error(`Cannot start: blocked by ${incompleteDeps.length} incomplete task(s): ${incompleteDeps.map((d) => `#${d.depends_on_id} "${d.subject}" (${d.status})`).join(", ")}`);
|
|
48333
|
+
}
|
|
48334
|
+
const now = new Date().toISOString();
|
|
48335
|
+
const oldStatus = task.status;
|
|
48336
|
+
db2.prepare("UPDATE tasks SET status = 'in_progress', started_at = ? WHERE id = ?").run(now, task.id);
|
|
48337
|
+
logActivity(task.id, agent || task.reporter, "started");
|
|
48338
|
+
const updated = getTaskById(task.id);
|
|
48339
|
+
if (updated)
|
|
48340
|
+
emitTaskEvent(updated, "started", agent || task.reporter, oldStatus);
|
|
48341
|
+
return updated;
|
|
48342
|
+
}
|
|
48343
|
+
function completeTask(id, agent, opts) {
|
|
48344
|
+
const db2 = getDb();
|
|
48345
|
+
const task = resolveTask(id);
|
|
48346
|
+
if (!task)
|
|
48347
|
+
return null;
|
|
48348
|
+
const now = new Date().toISOString();
|
|
48349
|
+
const oldStatus = task.status;
|
|
48350
|
+
db2.prepare("UPDATE tasks SET status = 'completed', completed_at = ? WHERE id = ?").run(now, task.id);
|
|
48351
|
+
logActivity(task.id, agent || task.reporter, "completed", opts?.evidence);
|
|
48352
|
+
unblockDependents(task.id);
|
|
48353
|
+
const updated = getTaskById(task.id);
|
|
48354
|
+
if (updated)
|
|
48355
|
+
emitTaskEvent(updated, "completed", agent || task.reporter, oldStatus, opts?.evidence);
|
|
48356
|
+
return updated;
|
|
48357
|
+
}
|
|
48358
|
+
function cancelTask(id, agent, opts) {
|
|
48359
|
+
const db2 = getDb();
|
|
48360
|
+
const task = resolveTask(id);
|
|
48361
|
+
if (!task)
|
|
48362
|
+
return null;
|
|
48363
|
+
const now = new Date().toISOString();
|
|
48364
|
+
const oldStatus = task.status;
|
|
48365
|
+
db2.prepare("UPDATE tasks SET status = 'cancelled', cancelled_at = ? WHERE id = ?").run(now, task.id);
|
|
48366
|
+
logActivity(task.id, agent || task.reporter, "cancelled", opts?.reason);
|
|
48367
|
+
const updated = getTaskById(task.id);
|
|
48368
|
+
if (updated)
|
|
48369
|
+
emitTaskEvent(updated, "cancelled", agent || task.reporter, oldStatus, opts?.reason);
|
|
48370
|
+
return updated;
|
|
48371
|
+
}
|
|
48372
|
+
function blockTask(id, agent, opts) {
|
|
48373
|
+
const db2 = getDb();
|
|
48374
|
+
const task = resolveTask(id);
|
|
48375
|
+
if (!task)
|
|
48376
|
+
return null;
|
|
48377
|
+
const oldStatus = task.status;
|
|
48378
|
+
db2.prepare("UPDATE tasks SET status = 'blocked' WHERE id = ?").run(task.id);
|
|
48379
|
+
logActivity(task.id, agent || task.reporter, "blocked", opts?.reason);
|
|
48380
|
+
const updated = getTaskById(task.id);
|
|
48381
|
+
if (updated)
|
|
48382
|
+
emitTaskEvent(updated, "blocked", agent || task.reporter, oldStatus, opts?.reason);
|
|
48383
|
+
return updated;
|
|
48384
|
+
}
|
|
48385
|
+
function unblockTask(id, agent) {
|
|
48386
|
+
const db2 = getDb();
|
|
48387
|
+
const task = resolveTask(id);
|
|
48388
|
+
if (!task)
|
|
48389
|
+
return null;
|
|
48390
|
+
const incompleteDeps = db2.prepare(`
|
|
48391
|
+
SELECT 1 FROM task_dependencies td
|
|
48392
|
+
JOIN tasks t ON t.id = td.depends_on_id
|
|
48393
|
+
WHERE td.task_id = ? AND t.status != 'completed'
|
|
48394
|
+
LIMIT 1
|
|
48395
|
+
`).get(task.id);
|
|
48396
|
+
const oldStatus = task.status;
|
|
48397
|
+
const newStatus = incompleteDeps ? "blocked" : "pending";
|
|
48398
|
+
db2.prepare("UPDATE tasks SET status = ? WHERE id = ?").run(newStatus, task.id);
|
|
48399
|
+
logActivity(task.id, agent || task.reporter, "unblocked");
|
|
48400
|
+
const updated = getTaskById(task.id);
|
|
48401
|
+
if (updated)
|
|
48402
|
+
emitTaskEvent(updated, "unblocked", agent || task.reporter, oldStatus);
|
|
48403
|
+
return updated;
|
|
48404
|
+
}
|
|
48405
|
+
function reopenTask(id, agent) {
|
|
48406
|
+
const db2 = getDb();
|
|
48407
|
+
const task = resolveTask(id);
|
|
48408
|
+
if (!task)
|
|
48409
|
+
return null;
|
|
48410
|
+
const oldStatus = task.status;
|
|
48411
|
+
db2.prepare("UPDATE tasks SET status = 'pending', completed_at = NULL, cancelled_at = NULL WHERE id = ?").run(task.id);
|
|
48412
|
+
logActivity(task.id, agent || task.reporter, "reopened");
|
|
48413
|
+
const incompleteDeps = db2.prepare(`
|
|
48414
|
+
SELECT 1 FROM task_dependencies td
|
|
48415
|
+
JOIN tasks t ON t.id = td.depends_on_id
|
|
48416
|
+
WHERE td.task_id = ? AND t.status != 'completed'
|
|
48417
|
+
LIMIT 1
|
|
48418
|
+
`).get(task.id);
|
|
48419
|
+
const updated = getTaskById(task.id);
|
|
48420
|
+
if (updated)
|
|
48421
|
+
emitTaskEvent(updated, "reopened", agent || task.reporter, oldStatus);
|
|
48422
|
+
return updated;
|
|
48423
|
+
}
|
|
48424
|
+
function assignTask(id, assignee, agent) {
|
|
48425
|
+
const db2 = getDb();
|
|
48426
|
+
const task = resolveTask(id);
|
|
48427
|
+
if (!task)
|
|
48428
|
+
return null;
|
|
48429
|
+
db2.prepare("UPDATE tasks SET assignee = ? WHERE id = ?").run(assignee, task.id);
|
|
48430
|
+
logActivity(task.id, agent || task.reporter, "assigned", assignee);
|
|
48431
|
+
const updated = getTaskById(task.id);
|
|
48432
|
+
if (updated)
|
|
48433
|
+
emitTaskEvent(updated, "assigned", agent || task.reporter, task.status);
|
|
48434
|
+
return updated;
|
|
48435
|
+
}
|
|
48436
|
+
function setTaskPriority(id, priority, agent) {
|
|
48437
|
+
const db2 = getDb();
|
|
48438
|
+
const task = resolveTask(id);
|
|
48439
|
+
if (!task)
|
|
48440
|
+
return null;
|
|
48441
|
+
const oldPriority = task.priority;
|
|
48442
|
+
db2.prepare("UPDATE tasks SET priority = ? WHERE id = ?").run(priority, task.id);
|
|
48443
|
+
logActivity(task.id, agent || task.reporter, "priority_changed", `${oldPriority} -> ${priority}`);
|
|
48444
|
+
const updated = getTaskById(task.id);
|
|
48445
|
+
if (updated)
|
|
48446
|
+
emitTaskEvent(updated, "priority_changed", agent || task.reporter, task.status, `${oldPriority} -> ${priority}`);
|
|
48447
|
+
return updated;
|
|
48448
|
+
}
|
|
48449
|
+
function addComment(taskId, agent, content) {
|
|
48450
|
+
const db2 = getDb();
|
|
48451
|
+
const task = resolveTask(taskId);
|
|
48452
|
+
if (!task)
|
|
48453
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
48454
|
+
const row = db2.prepare("INSERT INTO task_comments (task_id, agent, content) VALUES (?, ?, ?) RETURNING *").get(task.id, agent, content);
|
|
48455
|
+
logActivity(task.id, agent, "comment", content.length > 200 ? content.slice(0, 200) + "\u2026" : content);
|
|
48456
|
+
return {
|
|
48457
|
+
id: row.id,
|
|
48458
|
+
task_id: row.task_id,
|
|
48459
|
+
agent: row.agent,
|
|
48460
|
+
content: row.content,
|
|
48461
|
+
created_at: row.created_at
|
|
48462
|
+
};
|
|
48463
|
+
}
|
|
48464
|
+
function getComments(taskId) {
|
|
48465
|
+
const db2 = getDb();
|
|
48466
|
+
const task = resolveTask(taskId);
|
|
48467
|
+
if (!task)
|
|
48468
|
+
return [];
|
|
48469
|
+
return db2.prepare("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at ASC, id ASC").all(task.id);
|
|
48470
|
+
}
|
|
48471
|
+
function getSubtasks(parentId) {
|
|
48472
|
+
const db2 = getDb();
|
|
48473
|
+
const parent = resolveTask(parentId);
|
|
48474
|
+
if (!parent)
|
|
48475
|
+
return [];
|
|
48476
|
+
const rows = db2.prepare("SELECT * FROM tasks WHERE parent_id = ? ORDER BY created_at ASC, id ASC").all(parent.id);
|
|
48477
|
+
return rows.map(enrichTask);
|
|
48478
|
+
}
|
|
48479
|
+
function getTaskTree(parentId, maxDepth = 5) {
|
|
48480
|
+
const root = getTask(typeof parentId === "number" ? parentId : parentId);
|
|
48481
|
+
if (!root)
|
|
48482
|
+
throw new Error(`Task not found: ${parentId}`);
|
|
48483
|
+
const buildTree = (task, depth) => {
|
|
48484
|
+
if (depth >= maxDepth)
|
|
48485
|
+
return { ...task, children: [] };
|
|
48486
|
+
const children = getSubtasks(task.id);
|
|
48487
|
+
return { ...task, children: children.map((c) => buildTree(c, depth + 1)) };
|
|
48488
|
+
};
|
|
48489
|
+
return buildTree(root, 0);
|
|
48490
|
+
}
|
|
48491
|
+
function addDependency(taskId, dependsOnId) {
|
|
48492
|
+
const db2 = getDb();
|
|
48493
|
+
const task = resolveTask(taskId);
|
|
48494
|
+
const dep = resolveTask(dependsOnId);
|
|
48495
|
+
if (!task)
|
|
48496
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
48497
|
+
if (!dep)
|
|
48498
|
+
throw new Error(`Dependency task not found: ${dependsOnId}`);
|
|
48499
|
+
if (task.id === dep.id)
|
|
48500
|
+
throw new Error("A task cannot depend on itself");
|
|
48501
|
+
if (isCircularDependency(task.id, dep.id)) {
|
|
48502
|
+
throw new Error(`Circular dependency detected: task #${task.id} -> #${dep.id}`);
|
|
48503
|
+
}
|
|
48504
|
+
db2.prepare("INSERT OR IGNORE INTO task_dependencies (task_id, depends_on_id) VALUES (?, ?)").run(task.id, dep.id);
|
|
48505
|
+
const deps = db2.prepare("SELECT depends_on_id FROM task_dependencies WHERE task_id = ?").all(task.id);
|
|
48506
|
+
db2.prepare("UPDATE tasks SET depends_on = ? WHERE id = ?").run(JSON.stringify(deps.map((d) => d.depends_on_id)), task.id);
|
|
48507
|
+
if (dep.status !== "completed") {
|
|
48508
|
+
db2.prepare("UPDATE tasks SET status = 'blocked' WHERE id = ?").run(task.id);
|
|
48509
|
+
}
|
|
48510
|
+
logActivity(task.id, "", "dependency_added", `depends on #${dep.id}`);
|
|
48511
|
+
}
|
|
48512
|
+
function removeDependency(taskId, dependsOnId) {
|
|
48513
|
+
const db2 = getDb();
|
|
48514
|
+
const task = resolveTask(taskId);
|
|
48515
|
+
if (!task)
|
|
48516
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
48517
|
+
db2.prepare("DELETE FROM task_dependencies WHERE task_id = ? AND depends_on_id = ?").run(task.id, dependsOnId);
|
|
48518
|
+
const deps = db2.prepare("SELECT depends_on_id FROM task_dependencies WHERE task_id = ?").all(task.id);
|
|
48519
|
+
db2.prepare("UPDATE tasks SET depends_on = ? WHERE id = ?").run(JSON.stringify(deps.map((d) => d.depends_on_id)), task.id);
|
|
48520
|
+
logActivity(task.id, "", "dependency_removed", `no longer depends on #${dependsOnId}`);
|
|
48521
|
+
}
|
|
48522
|
+
function getDependencies(taskId) {
|
|
48523
|
+
const db2 = getDb();
|
|
48524
|
+
const task = resolveTask(taskId);
|
|
48525
|
+
if (!task)
|
|
48526
|
+
return [];
|
|
48527
|
+
return db2.prepare(`
|
|
48528
|
+
SELECT t.* FROM tasks t
|
|
48529
|
+
INNER JOIN task_dependencies td ON td.depends_on_id = t.id
|
|
48530
|
+
WHERE td.task_id = ?
|
|
48531
|
+
ORDER BY t.created_at ASC
|
|
48532
|
+
`).all(task.id).map(parseTask);
|
|
48533
|
+
}
|
|
48534
|
+
function getDependents(taskId) {
|
|
48535
|
+
const db2 = getDb();
|
|
48536
|
+
const task = resolveTask(taskId);
|
|
48537
|
+
if (!task)
|
|
48538
|
+
return [];
|
|
48539
|
+
return db2.prepare(`
|
|
48540
|
+
SELECT t.* FROM tasks t
|
|
48541
|
+
INNER JOIN task_dependencies td ON td.task_id = t.id
|
|
48542
|
+
WHERE td.depends_on_id = ?
|
|
48543
|
+
ORDER BY t.created_at ASC
|
|
48544
|
+
`).all(task.id).map(parseTask);
|
|
48545
|
+
}
|
|
48546
|
+
function getTaskActivity(taskId, limit = 50) {
|
|
48547
|
+
const db2 = getDb();
|
|
48548
|
+
const task = resolveTask(taskId);
|
|
48549
|
+
if (!task)
|
|
48550
|
+
return [];
|
|
48551
|
+
const safeLimit = Math.max(1, Math.min(Math.floor(limit), 1000));
|
|
48552
|
+
return db2.prepare(`SELECT * FROM task_activity WHERE task_id = ? ORDER BY created_at DESC LIMIT ${safeLimit}`).all(task.id);
|
|
48553
|
+
}
|
|
48554
|
+
function deleteTask(id, agent) {
|
|
48555
|
+
const db2 = getDb();
|
|
48556
|
+
const task = resolveTask(id);
|
|
48557
|
+
if (!task)
|
|
48558
|
+
return false;
|
|
48559
|
+
const subtaskCount = db2.prepare("SELECT COUNT(*) as c FROM tasks WHERE parent_id = ?").get(task.id).c;
|
|
48560
|
+
if (subtaskCount > 0) {
|
|
48561
|
+
throw new Error(`Cannot delete: ${subtaskCount} subtask(s) still reference this task`);
|
|
48562
|
+
}
|
|
48563
|
+
logActivity(task.id, agent || "", "deleted");
|
|
48564
|
+
db2.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
|
|
48565
|
+
return true;
|
|
48566
|
+
}
|
|
48567
|
+
function searchTasks(opts) {
|
|
48568
|
+
const db2 = getDb();
|
|
48569
|
+
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
|
|
48570
|
+
const sortByRelevance = opts.sort !== "recent";
|
|
48571
|
+
const query = opts.query.trim();
|
|
48572
|
+
const terms = query.split(/\s+/).filter(Boolean);
|
|
48573
|
+
const ftsAvailable = db2.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='tasks_fts'").get();
|
|
48574
|
+
if (ftsAvailable && terms.length > 0) {
|
|
48575
|
+
try {
|
|
48576
|
+
let ftsQuery;
|
|
48577
|
+
if (query.startsWith('"') && query.endsWith('"')) {
|
|
48578
|
+
ftsQuery = query;
|
|
48579
|
+
} else {
|
|
48580
|
+
ftsQuery = terms.map((w) => `"${w.replace(/"/g, '""')}"`).join(" ");
|
|
48581
|
+
}
|
|
48582
|
+
const ftsRows = db2.prepare(`SELECT rowid, rank, snippet(tasks_fts, 0, '**', '**', '...', 10) as snippet
|
|
48583
|
+
FROM tasks_fts WHERE tasks_fts MATCH ? ORDER BY rank LIMIT ${limit * 3}`).all(ftsQuery);
|
|
48584
|
+
if (ftsRows.length === 0) {} else {
|
|
48585
|
+
const ids = ftsRows.map((r) => r.rowid);
|
|
48586
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
48587
|
+
const rows2 = db2.prepare(`SELECT * FROM tasks WHERE id IN (${placeholders})`).all(...ids);
|
|
48588
|
+
const taskMap = new Map;
|
|
48589
|
+
for (const row of rows2)
|
|
48590
|
+
taskMap.set(row.id, row);
|
|
48591
|
+
const rankMap = new Map(ftsRows.map((r) => [r.rowid, { rank: r.rank, snippet: r.snippet }]));
|
|
48592
|
+
const sorted = sortByRelevance ? [...ftsRows].sort((a, b) => a.rank - b.rank) : [...ftsRows].sort((a, b) => {
|
|
48593
|
+
const aTask = taskMap.get(a.rowid);
|
|
48594
|
+
const bTask = taskMap.get(b.rowid);
|
|
48595
|
+
return (bTask?.created_at || "").localeCompare(aTask?.created_at || "");
|
|
48596
|
+
});
|
|
48597
|
+
const results = [];
|
|
48598
|
+
const maxRank = Math.abs(sorted[0].rank) || 1;
|
|
48599
|
+
for (const fts of sorted) {
|
|
48600
|
+
const row = taskMap.get(fts.rowid);
|
|
48601
|
+
if (!row)
|
|
48602
|
+
continue;
|
|
48603
|
+
const task = enrichTask(row);
|
|
48604
|
+
if (opts.status && task.status !== opts.status)
|
|
48605
|
+
continue;
|
|
48606
|
+
if (opts.assignee && task.assignee !== opts.assignee)
|
|
48607
|
+
continue;
|
|
48608
|
+
if (opts.project_id && task.project_id !== opts.project_id)
|
|
48609
|
+
continue;
|
|
48610
|
+
if (opts.space && task.space !== opts.space)
|
|
48611
|
+
continue;
|
|
48612
|
+
if (opts.priority && task.priority !== opts.priority)
|
|
48613
|
+
continue;
|
|
48614
|
+
if (!opts.include_archived && task.status === "cancelled")
|
|
48615
|
+
continue;
|
|
48616
|
+
results.push({
|
|
48617
|
+
...task,
|
|
48618
|
+
snippet: fts.snippet || null,
|
|
48619
|
+
relevance_score: Math.round((1 - Math.abs(fts.rank) / maxRank) * 100)
|
|
48620
|
+
});
|
|
48621
|
+
if (results.length >= limit)
|
|
48622
|
+
break;
|
|
48623
|
+
}
|
|
48624
|
+
return results;
|
|
48625
|
+
}
|
|
48626
|
+
} catch {}
|
|
48627
|
+
}
|
|
48628
|
+
if (terms.length === 0)
|
|
48629
|
+
return [];
|
|
48630
|
+
const params = [];
|
|
48631
|
+
const conditions = [];
|
|
48632
|
+
for (const term of terms) {
|
|
48633
|
+
conditions.push("(LOWER(t.subject) LIKE ? OR LOWER(t.description) LIKE ? OR LOWER(t.tags) LIKE ?)");
|
|
48634
|
+
const likeTerm = `%${term}%`;
|
|
48635
|
+
params.push(likeTerm, likeTerm, likeTerm);
|
|
48636
|
+
}
|
|
48637
|
+
if (opts.status) {
|
|
48638
|
+
conditions.push("t.status = ?");
|
|
48639
|
+
params.push(opts.status);
|
|
48640
|
+
}
|
|
48641
|
+
if (opts.assignee) {
|
|
48642
|
+
conditions.push("t.assignee = ?");
|
|
48643
|
+
params.push(opts.assignee);
|
|
48644
|
+
}
|
|
48645
|
+
if (opts.project_id) {
|
|
48646
|
+
conditions.push("t.project_id = ?");
|
|
48647
|
+
params.push(opts.project_id);
|
|
48648
|
+
}
|
|
48649
|
+
if (opts.space) {
|
|
48650
|
+
conditions.push("t.space = ?");
|
|
48651
|
+
params.push(opts.space);
|
|
48652
|
+
}
|
|
48653
|
+
if (opts.priority) {
|
|
48654
|
+
conditions.push("t.priority = ?");
|
|
48655
|
+
params.push(opts.priority);
|
|
48656
|
+
}
|
|
48657
|
+
if (!opts.include_archived) {
|
|
48658
|
+
conditions.push("t.status != 'cancelled'");
|
|
48659
|
+
}
|
|
48660
|
+
const orderClause = sortByRelevance ? "ORDER BY CASE t.priority WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 WHEN 'low' THEN 4 END, t.created_at DESC" : "ORDER BY t.created_at DESC";
|
|
48661
|
+
const rows = db2.prepare(`SELECT t.* FROM tasks t WHERE ${conditions.join(" AND ")} ${orderClause} LIMIT ${limit}`).all(...params);
|
|
48662
|
+
return rows.map((row) => {
|
|
48663
|
+
const task = enrichTask(row);
|
|
48664
|
+
const subject = row.subject.toLowerCase();
|
|
48665
|
+
const matchCount = terms.filter((t) => subject.includes(t)).length;
|
|
48666
|
+
return {
|
|
48667
|
+
...task,
|
|
48668
|
+
snippet: null,
|
|
48669
|
+
relevance_score: Math.round(matchCount / terms.length * 100)
|
|
48670
|
+
};
|
|
48671
|
+
});
|
|
48672
|
+
}
|
|
48673
|
+
function getDueTasks(opts = {}) {
|
|
48674
|
+
const db2 = getDb();
|
|
48675
|
+
const windowHours = opts.window_hours ?? 24;
|
|
48676
|
+
const now = new Date;
|
|
48677
|
+
const deadline = new Date(now.getTime() + windowHours * 60 * 60 * 1000);
|
|
48678
|
+
const rows = db2.prepare(`
|
|
48679
|
+
SELECT t.* FROM tasks t
|
|
48680
|
+
WHERE t.due_at IS NOT NULL
|
|
48681
|
+
AND t.due_at <= ?
|
|
48682
|
+
AND t.status NOT IN ('completed', 'cancelled')
|
|
48683
|
+
ORDER BY t.due_at ASC
|
|
48684
|
+
`).all(deadline.toISOString());
|
|
48685
|
+
return rows.map((row) => {
|
|
48686
|
+
const task = enrichTask(row);
|
|
48687
|
+
const dueAt = new Date(task.due_at);
|
|
48688
|
+
const hoursUntilDue = (dueAt.getTime() - now.getTime()) / (1000 * 60 * 60);
|
|
48689
|
+
let urgency;
|
|
48690
|
+
if (hoursUntilDue < 0)
|
|
48691
|
+
urgency = "overdue";
|
|
48692
|
+
else if (hoursUntilDue <= 24)
|
|
48693
|
+
urgency = "due_today";
|
|
48694
|
+
else
|
|
48695
|
+
urgency = "due_soon";
|
|
48696
|
+
return { task, due_in_hours: Math.round(hoursUntilDue * 10) / 10, urgency };
|
|
48697
|
+
});
|
|
48698
|
+
}
|
|
48699
|
+
function getTaskSummary(idOrUuid) {
|
|
48700
|
+
const db2 = getDb();
|
|
48701
|
+
const task = getTask(idOrUuid);
|
|
48702
|
+
if (!task)
|
|
48703
|
+
return null;
|
|
48704
|
+
const subtasks = db2.prepare("SELECT status FROM tasks WHERE parent_id = ?").all(task.id);
|
|
48705
|
+
const totalSubtasks = subtasks.length;
|
|
48706
|
+
const completedSubtasks = subtasks.filter((s) => s.status === "completed").length;
|
|
48707
|
+
const depRows = db2.prepare("SELECT td.depends_on_id, t.status FROM task_dependencies td JOIN tasks t ON t.id = td.depends_on_id WHERE td.task_id = ?").all(task.id);
|
|
48708
|
+
const totalDeps = depRows.length;
|
|
48709
|
+
const completedDeps = depRows.filter((d) => d.status === "completed").length;
|
|
48710
|
+
const commentCount = db2.prepare("SELECT COUNT(*) as c FROM task_comments WHERE task_id = ?").get(task.id).c;
|
|
48711
|
+
const items = totalSubtasks + totalDeps;
|
|
48712
|
+
const completed = completedSubtasks + completedDeps;
|
|
48713
|
+
const completionPct = items > 0 ? Math.round(completed / items * 100) : task.status === "completed" ? 100 : 0;
|
|
48714
|
+
const activity = db2.prepare("SELECT action, agent, detail, created_at FROM task_activity WHERE task_id = ? ORDER BY id DESC LIMIT 10").all(task.id);
|
|
48715
|
+
const blockerInfo = db2.prepare("SELECT td.depends_on_id as task_id, t.subject, t.status FROM task_dependencies td JOIN tasks t ON t.id = td.depends_on_id WHERE td.task_id = ? AND t.status != 'completed'").all(task.id);
|
|
48716
|
+
const dependentRows = db2.prepare("SELECT td.task_id, t.subject, t.status FROM task_dependencies td JOIN tasks t ON t.id = td.task_id WHERE td.depends_on_id = ?").all(task.id);
|
|
48717
|
+
return {
|
|
48718
|
+
task,
|
|
48719
|
+
progress: {
|
|
48720
|
+
total_subtasks: totalSubtasks,
|
|
48721
|
+
completed_subtasks: completedSubtasks,
|
|
48722
|
+
total_dependencies: totalDeps,
|
|
48723
|
+
completed_dependencies: completedDeps,
|
|
48724
|
+
comment_count: commentCount,
|
|
48725
|
+
completion_pct: completionPct
|
|
48726
|
+
},
|
|
48727
|
+
recent_activity: activity,
|
|
48728
|
+
blockers: blockerInfo,
|
|
48729
|
+
dependents: dependentRows
|
|
48730
|
+
};
|
|
48731
|
+
}
|
|
48732
|
+
function enrichTask(row) {
|
|
48733
|
+
const db2 = getDb();
|
|
48734
|
+
const task = parseTask(row);
|
|
48735
|
+
const subtaskCount = db2.prepare("SELECT COUNT(*) as c FROM tasks WHERE parent_id = ?").get(task.id).c;
|
|
48736
|
+
const commentCount = db2.prepare("SELECT COUNT(*) as c FROM task_comments WHERE task_id = ?").get(task.id).c;
|
|
48737
|
+
const depRows = db2.prepare("SELECT depends_on_id FROM task_dependencies WHERE task_id = ?").all(task.id);
|
|
48738
|
+
const depCount = depRows.length;
|
|
48739
|
+
let blockerInfo = [];
|
|
48740
|
+
if (depRows.length > 0) {
|
|
48741
|
+
blockerInfo = depRows.map((d) => {
|
|
48742
|
+
const dep = db2.prepare("SELECT id, subject, status FROM tasks WHERE id = ?").get(d.depends_on_id);
|
|
48743
|
+
return dep ? { task_id: dep.id, subject: dep.subject, status: dep.status } : null;
|
|
48744
|
+
}).filter(Boolean);
|
|
48745
|
+
}
|
|
48746
|
+
return { ...task, subtask_count: subtaskCount, comment_count: commentCount, dependency_count: depCount, blocker_info: blockerInfo };
|
|
48747
|
+
}
|
|
48748
|
+
function resolveTask(idOrUuid) {
|
|
48749
|
+
const db2 = getDb();
|
|
48750
|
+
if (typeof idOrUuid === "number") {
|
|
48751
|
+
const row2 = db2.prepare("SELECT * FROM tasks WHERE id = ?").get(idOrUuid);
|
|
48752
|
+
return row2 ? parseTask(row2) : null;
|
|
48753
|
+
}
|
|
48754
|
+
const row = db2.prepare("SELECT * FROM tasks WHERE uuid = ?").get(idOrUuid);
|
|
48755
|
+
return row ? parseTask(row) : null;
|
|
48756
|
+
}
|
|
48757
|
+
function getTaskById(id) {
|
|
48758
|
+
const db2 = getDb();
|
|
48759
|
+
const row = db2.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
48760
|
+
return row ? parseTask(row) : null;
|
|
48761
|
+
}
|
|
48762
|
+
function unblockDependents(completedTaskId) {
|
|
48763
|
+
const db2 = getDb();
|
|
48764
|
+
const dependents = db2.prepare(`
|
|
48765
|
+
SELECT td.task_id, t.status FROM task_dependencies td
|
|
48766
|
+
JOIN tasks t ON t.id = td.task_id
|
|
48767
|
+
WHERE td.depends_on_id = ?
|
|
48768
|
+
`).all(completedTaskId);
|
|
48769
|
+
for (const dep of dependents) {
|
|
48770
|
+
if (dep.status === "blocked") {
|
|
48771
|
+
const incompleteCount = db2.prepare(`
|
|
48772
|
+
SELECT COUNT(*) as c FROM task_dependencies td
|
|
48773
|
+
JOIN tasks t ON t.id = td.depends_on_id
|
|
48774
|
+
WHERE td.task_id = ? AND t.status != 'completed'
|
|
48775
|
+
`).get(dep.task_id).c;
|
|
48776
|
+
if (incompleteCount === 0) {
|
|
48777
|
+
db2.prepare("UPDATE tasks SET status = 'pending' WHERE id = ?").run(dep.task_id);
|
|
48778
|
+
logActivity(dep.task_id, "", "auto_unblocked", `dependency #${completedTaskId} completed`);
|
|
48779
|
+
const task = getTaskById(dep.task_id);
|
|
48780
|
+
if (task)
|
|
48781
|
+
emitTaskEvent(task, "auto_unblocked", "system", "blocked", `dependency #${completedTaskId} completed`);
|
|
48782
|
+
}
|
|
48783
|
+
}
|
|
48784
|
+
}
|
|
48785
|
+
}
|
|
48786
|
+
function isCircularDependency(taskId, dependsOnId) {
|
|
48787
|
+
const db2 = getDb();
|
|
48788
|
+
const visited = new Set;
|
|
48789
|
+
let current = dependsOnId;
|
|
48790
|
+
let depth = 0;
|
|
48791
|
+
while (current !== undefined && depth < 20) {
|
|
48792
|
+
if (current === taskId)
|
|
48793
|
+
return true;
|
|
48794
|
+
if (visited.has(current))
|
|
48795
|
+
break;
|
|
48796
|
+
visited.add(current);
|
|
48797
|
+
const parents = db2.prepare("SELECT depends_on_id FROM task_dependencies WHERE task_id = ?").all(current);
|
|
48798
|
+
current = parents.length > 0 ? parents[0].depends_on_id : undefined;
|
|
48799
|
+
depth++;
|
|
48800
|
+
}
|
|
48801
|
+
return false;
|
|
48802
|
+
}
|
|
48803
|
+
var init_tasks = __esm(() => {
|
|
48804
|
+
init_db();
|
|
48805
|
+
init_webhooks();
|
|
48806
|
+
});
|
|
48807
|
+
|
|
48808
|
+
// src/mcp/tools/tasks.ts
|
|
48809
|
+
function registerTaskTools(server) {
|
|
48810
|
+
server.registerTool("create_task", {
|
|
48811
|
+
description: "Create a new task with optional assignee, priority, parent (subtask), dependencies, tags, and metadata.",
|
|
48812
|
+
inputSchema: {
|
|
48813
|
+
subject: exports_external2.string(),
|
|
48814
|
+
description: exports_external2.string().optional(),
|
|
48815
|
+
reporter: exports_external2.string().optional(),
|
|
48816
|
+
assignee: exports_external2.string().optional(),
|
|
48817
|
+
priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional(),
|
|
48818
|
+
project_id: exports_external2.string().optional(),
|
|
48819
|
+
space: exports_external2.string().optional(),
|
|
48820
|
+
parent_id: exports_external2.coerce.number().optional(),
|
|
48821
|
+
depends_on: exports_external2.array(exports_external2.coerce.number()).optional(),
|
|
48822
|
+
tags: exports_external2.array(exports_external2.string()).optional(),
|
|
48823
|
+
metadata: exports_external2.record(exports_external2.string(), exports_external2.unknown()).optional(),
|
|
48824
|
+
due_at: exports_external2.string().optional()
|
|
48825
|
+
}
|
|
48826
|
+
}, async (args) => {
|
|
48827
|
+
if (!args.reporter) {
|
|
48828
|
+
try {
|
|
48829
|
+
args.reporter = resolveIdentity(undefined);
|
|
48830
|
+
} catch {
|
|
48831
|
+
args.reporter = "unknown";
|
|
48832
|
+
}
|
|
48833
|
+
}
|
|
48834
|
+
const task = createTask({
|
|
48835
|
+
subject: args.subject,
|
|
48836
|
+
description: args.description,
|
|
48837
|
+
reporter: args.reporter,
|
|
48838
|
+
assignee: args.assignee,
|
|
48839
|
+
priority: args.priority,
|
|
48840
|
+
project_id: args.project_id,
|
|
48841
|
+
space: args.space,
|
|
48842
|
+
parent_id: args.parent_id,
|
|
48843
|
+
depends_on: args.depends_on,
|
|
48844
|
+
tags: args.tags,
|
|
48845
|
+
metadata: args.metadata,
|
|
48846
|
+
due_at: args.due_at
|
|
48847
|
+
});
|
|
48848
|
+
return { content: [{ type: "text", text: JSON.stringify(task) }] };
|
|
48849
|
+
});
|
|
48850
|
+
server.registerTool("get_task", {
|
|
48851
|
+
description: "Get a task by id or uuid. Returns enriched TaskInfo with subtask count, comment count, dependency count, and blocker info.",
|
|
48852
|
+
inputSchema: {
|
|
48853
|
+
id: exports_external2.coerce.number().optional(),
|
|
48854
|
+
uuid: exports_external2.string().optional()
|
|
48855
|
+
}
|
|
48856
|
+
}, async (args) => {
|
|
48857
|
+
const lookup = args.id ?? args.uuid;
|
|
48858
|
+
if (!lookup)
|
|
48859
|
+
return { content: [{ type: "text", text: "id or uuid required" }], isError: true };
|
|
48860
|
+
const task = getTask(lookup);
|
|
48861
|
+
if (!task)
|
|
48862
|
+
return { content: [{ type: "text", text: `Task not found: ${lookup}` }], isError: true };
|
|
48863
|
+
return { content: [{ type: "text", text: JSON.stringify(task) }] };
|
|
48864
|
+
});
|
|
48865
|
+
server.registerTool("list_tasks", {
|
|
48866
|
+
description: "List tasks with optional filters. Default: 50 tasks, sorted by priority then date. Use 'tags' for AND-matching multiple tags. Use 'metadata' to filter by metadata key/value pairs.",
|
|
48867
|
+
inputSchema: {
|
|
48868
|
+
status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled", "blocked"]).optional(),
|
|
48869
|
+
assignee: exports_external2.string().optional(),
|
|
48870
|
+
reporter: exports_external2.string().optional(),
|
|
48871
|
+
project_id: exports_external2.string().optional(),
|
|
48872
|
+
space: exports_external2.string().optional(),
|
|
48873
|
+
parent_id: exports_external2.coerce.number().nullable().optional(),
|
|
48874
|
+
priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional(),
|
|
48875
|
+
tag: exports_external2.string().optional(),
|
|
48876
|
+
tags: exports_external2.array(exports_external2.string()).optional(),
|
|
48877
|
+
metadata: exports_external2.record(exports_external2.string(), exports_external2.unknown()).optional(),
|
|
48878
|
+
limit: exports_external2.coerce.number().optional(),
|
|
48879
|
+
offset: exports_external2.coerce.number().optional(),
|
|
48880
|
+
include_archived: exports_external2.coerce.boolean().optional()
|
|
48881
|
+
}
|
|
48882
|
+
}, async (args) => {
|
|
48883
|
+
const tasks = listTasks(args);
|
|
48884
|
+
return { content: [{ type: "text", text: JSON.stringify({ tasks, count: tasks.length }) }] };
|
|
48885
|
+
});
|
|
48886
|
+
server.registerTool("start_task", {
|
|
48887
|
+
description: "Mark a task as in_progress. Fails if any dependency is not completed.",
|
|
48888
|
+
inputSchema: {
|
|
48889
|
+
id: exports_external2.coerce.number(),
|
|
48890
|
+
agent: exports_external2.string().optional()
|
|
48891
|
+
}
|
|
48892
|
+
}, async (args) => {
|
|
48893
|
+
const agent = args.agent ? args.agent : resolveIdentity(undefined);
|
|
48894
|
+
const task = startTask(args.id, agent);
|
|
48895
|
+
if (!task)
|
|
48896
|
+
return { content: [{ type: "text", text: `Task not found: ${args.id}` }], isError: true };
|
|
48897
|
+
return { content: [{ type: "text", text: JSON.stringify(task) }] };
|
|
48898
|
+
});
|
|
48899
|
+
server.registerTool("complete_task", {
|
|
48900
|
+
description: "Mark a task as completed. Auto-unblocks any dependent tasks that now have all dependencies completed.",
|
|
48901
|
+
inputSchema: {
|
|
48902
|
+
id: exports_external2.coerce.number(),
|
|
48903
|
+
agent: exports_external2.string().optional(),
|
|
48904
|
+
evidence: exports_external2.string().optional()
|
|
48905
|
+
}
|
|
48906
|
+
}, async (args) => {
|
|
48907
|
+
const agent = args.agent ? args.agent : resolveIdentity(undefined);
|
|
48908
|
+
const task = completeTask(args.id, agent, args.evidence ? { evidence: args.evidence } : undefined);
|
|
48909
|
+
if (!task)
|
|
48910
|
+
return { content: [{ type: "text", text: `Task not found: ${args.id}` }], isError: true };
|
|
48911
|
+
return { content: [{ type: "text", text: JSON.stringify(task) }] };
|
|
48912
|
+
});
|
|
48913
|
+
server.registerTool("cancel_task", {
|
|
48914
|
+
description: "Cancel a task with optional reason.",
|
|
48915
|
+
inputSchema: {
|
|
48916
|
+
id: exports_external2.coerce.number(),
|
|
48917
|
+
agent: exports_external2.string().optional(),
|
|
48918
|
+
reason: exports_external2.string().optional()
|
|
48919
|
+
}
|
|
48920
|
+
}, async (args) => {
|
|
48921
|
+
const agent = args.agent ? args.agent : resolveIdentity(undefined);
|
|
48922
|
+
const task = cancelTask(args.id, agent, args.reason ? { reason: args.reason } : undefined);
|
|
48923
|
+
if (!task)
|
|
48924
|
+
return { content: [{ type: "text", text: `Task not found: ${args.id}` }], isError: true };
|
|
48925
|
+
return { content: [{ type: "text", text: JSON.stringify(task) }] };
|
|
48926
|
+
});
|
|
48927
|
+
server.registerTool("block_task", {
|
|
48928
|
+
description: "Manually block a task with optional reason.",
|
|
48929
|
+
inputSchema: {
|
|
48930
|
+
id: exports_external2.coerce.number(),
|
|
48931
|
+
agent: exports_external2.string().optional(),
|
|
48932
|
+
reason: exports_external2.string().optional()
|
|
48933
|
+
}
|
|
48934
|
+
}, async (args) => {
|
|
48935
|
+
const agent = args.agent ? args.agent : resolveIdentity(undefined);
|
|
48936
|
+
const task = blockTask(args.id, agent, args.reason ? { reason: args.reason } : undefined);
|
|
48937
|
+
if (!task)
|
|
48938
|
+
return { content: [{ type: "text", text: `Task not found: ${args.id}` }], isError: true };
|
|
48939
|
+
return { content: [{ type: "text", text: JSON.stringify(task) }] };
|
|
48940
|
+
});
|
|
48941
|
+
server.registerTool("unblock_task", {
|
|
48942
|
+
description: "Unblock a task. Sets to 'pending' if all dependencies are completed, otherwise stays 'blocked'.",
|
|
48943
|
+
inputSchema: {
|
|
48944
|
+
id: exports_external2.coerce.number(),
|
|
48945
|
+
agent: exports_external2.string().optional()
|
|
48946
|
+
}
|
|
48947
|
+
}, async (args) => {
|
|
48948
|
+
const agent = args.agent ? args.agent : resolveIdentity(undefined);
|
|
48949
|
+
const task = unblockTask(args.id, agent);
|
|
48950
|
+
if (!task)
|
|
48951
|
+
return { content: [{ type: "text", text: `Task not found: ${args.id}` }], isError: true };
|
|
48952
|
+
return { content: [{ type: "text", text: JSON.stringify(task) }] };
|
|
48953
|
+
});
|
|
48954
|
+
server.registerTool("reopen_task", {
|
|
48955
|
+
description: "Reopen a completed or cancelled task back to pending. Re-checks dependencies.",
|
|
48956
|
+
inputSchema: {
|
|
48957
|
+
id: exports_external2.coerce.number(),
|
|
48958
|
+
agent: exports_external2.string().optional()
|
|
48959
|
+
}
|
|
48960
|
+
}, async (args) => {
|
|
48961
|
+
const agent = args.agent ? args.agent : resolveIdentity(undefined);
|
|
48962
|
+
const task = reopenTask(args.id, agent);
|
|
48963
|
+
if (!task)
|
|
48964
|
+
return { content: [{ type: "text", text: `Task not found: ${args.id}` }], isError: true };
|
|
48965
|
+
return { content: [{ type: "text", text: JSON.stringify(task) }] };
|
|
48966
|
+
});
|
|
48967
|
+
server.registerTool("assign_task", {
|
|
48968
|
+
description: "Assign a task to an agent.",
|
|
48969
|
+
inputSchema: {
|
|
48970
|
+
id: exports_external2.coerce.number(),
|
|
48971
|
+
assignee: exports_external2.string(),
|
|
48972
|
+
agent: exports_external2.string().optional()
|
|
48973
|
+
}
|
|
48974
|
+
}, async (args) => {
|
|
48975
|
+
const agent = args.agent ? args.agent : resolveIdentity(undefined);
|
|
48976
|
+
const task = assignTask(args.id, args.assignee, agent);
|
|
48977
|
+
if (!task)
|
|
48978
|
+
return { content: [{ type: "text", text: `Task not found: ${args.id}` }], isError: true };
|
|
48979
|
+
return { content: [{ type: "text", text: JSON.stringify(task) }] };
|
|
48980
|
+
});
|
|
48981
|
+
server.registerTool("set_task_priority", {
|
|
48982
|
+
description: "Change a task's priority: low, medium, high, critical.",
|
|
48983
|
+
inputSchema: {
|
|
48984
|
+
id: exports_external2.coerce.number(),
|
|
48985
|
+
priority: exports_external2.enum(["low", "medium", "high", "critical"]),
|
|
48986
|
+
agent: exports_external2.string().optional()
|
|
48987
|
+
}
|
|
48988
|
+
}, async (args) => {
|
|
48989
|
+
const agent = args.agent ? args.agent : resolveIdentity(undefined);
|
|
48990
|
+
const task = setTaskPriority(args.id, args.priority, agent);
|
|
48991
|
+
if (!task)
|
|
48992
|
+
return { content: [{ type: "text", text: `Task not found: ${args.id}` }], isError: true };
|
|
48993
|
+
return { content: [{ type: "text", text: JSON.stringify(task) }] };
|
|
48994
|
+
});
|
|
48995
|
+
server.registerTool("delete_task", {
|
|
48996
|
+
description: "Delete a task. Fails if subtasks still reference it.",
|
|
48997
|
+
inputSchema: {
|
|
48998
|
+
id: exports_external2.coerce.number(),
|
|
48999
|
+
agent: exports_external2.string().optional()
|
|
49000
|
+
}
|
|
49001
|
+
}, async (args) => {
|
|
49002
|
+
const agent = args.agent ? args.agent : resolveIdentity(undefined);
|
|
49003
|
+
const deleted = deleteTask(args.id, agent);
|
|
49004
|
+
return { content: [{ type: "text", text: JSON.stringify({ deleted, id: args.id }) }] };
|
|
49005
|
+
});
|
|
49006
|
+
server.registerTool("add_comment", {
|
|
49007
|
+
description: "Add a comment to a task.",
|
|
49008
|
+
inputSchema: {
|
|
49009
|
+
task_id: exports_external2.coerce.number(),
|
|
49010
|
+
content: exports_external2.string(),
|
|
49011
|
+
agent: exports_external2.string().optional()
|
|
49012
|
+
}
|
|
49013
|
+
}, async (args) => {
|
|
49014
|
+
const agent = args.agent ? args.agent : resolveIdentity(undefined);
|
|
49015
|
+
const comment = addComment(args.task_id, agent, args.content);
|
|
49016
|
+
return { content: [{ type: "text", text: JSON.stringify(comment) }] };
|
|
49017
|
+
});
|
|
49018
|
+
server.registerTool("get_comments", {
|
|
49019
|
+
description: "Get all comments on a task, ordered by creation time.",
|
|
49020
|
+
inputSchema: {
|
|
49021
|
+
task_id: exports_external2.coerce.number()
|
|
49022
|
+
}
|
|
49023
|
+
}, async (args) => {
|
|
49024
|
+
const comments = getComments(args.task_id);
|
|
49025
|
+
return { content: [{ type: "text", text: JSON.stringify({ comments, count: comments.length }) }] };
|
|
49026
|
+
});
|
|
49027
|
+
server.registerTool("get_subtasks", {
|
|
49028
|
+
description: "Get direct children (subtasks) of a parent task.",
|
|
49029
|
+
inputSchema: {
|
|
49030
|
+
parent_id: exports_external2.coerce.number()
|
|
49031
|
+
}
|
|
49032
|
+
}, async (args) => {
|
|
49033
|
+
const subtasks = getSubtasks(args.parent_id);
|
|
49034
|
+
return { content: [{ type: "text", text: JSON.stringify({ subtasks, count: subtasks.length }) }] };
|
|
49035
|
+
});
|
|
49036
|
+
server.registerTool("get_task_tree", {
|
|
49037
|
+
description: "Get a task with its full subtask tree (recursive, max depth 5).",
|
|
49038
|
+
inputSchema: {
|
|
49039
|
+
parent_id: exports_external2.coerce.number(),
|
|
49040
|
+
max_depth: exports_external2.coerce.number().optional()
|
|
49041
|
+
}
|
|
49042
|
+
}, async (args) => {
|
|
49043
|
+
const tree = getTaskTree(args.parent_id, args.max_depth ?? 5);
|
|
49044
|
+
return { content: [{ type: "text", text: JSON.stringify(tree) }] };
|
|
49045
|
+
});
|
|
49046
|
+
server.registerTool("add_dependency", {
|
|
49047
|
+
description: "Add a dependency: task_id depends on depends_on_id. Prevents circular dependencies. Auto-blocks if dependency not completed.",
|
|
49048
|
+
inputSchema: {
|
|
49049
|
+
task_id: exports_external2.coerce.number(),
|
|
49050
|
+
depends_on_id: exports_external2.coerce.number()
|
|
49051
|
+
}
|
|
49052
|
+
}, async (args) => {
|
|
49053
|
+
addDependency(args.task_id, args.depends_on_id);
|
|
49054
|
+
return { content: [{ type: "text", text: `Task #${args.task_id} now depends on #${args.depends_on_id}` }] };
|
|
49055
|
+
});
|
|
49056
|
+
server.registerTool("remove_dependency", {
|
|
49057
|
+
description: "Remove a dependency between two tasks.",
|
|
49058
|
+
inputSchema: {
|
|
49059
|
+
task_id: exports_external2.coerce.number(),
|
|
49060
|
+
depends_on_id: exports_external2.coerce.number()
|
|
49061
|
+
}
|
|
49062
|
+
}, async (args) => {
|
|
49063
|
+
removeDependency(args.task_id, args.depends_on_id);
|
|
49064
|
+
return { content: [{ type: "text", text: `Removed dependency: #${args.task_id} no longer depends on #${args.depends_on_id}` }] };
|
|
49065
|
+
});
|
|
49066
|
+
server.registerTool("get_dependencies", {
|
|
49067
|
+
description: "Get tasks that this task depends on (what must be completed first).",
|
|
49068
|
+
inputSchema: {
|
|
49069
|
+
task_id: exports_external2.coerce.number()
|
|
49070
|
+
}
|
|
49071
|
+
}, async (args) => {
|
|
49072
|
+
const deps = getDependencies(args.task_id);
|
|
49073
|
+
return { content: [{ type: "text", text: JSON.stringify({ dependencies: deps, count: deps.length }) }] };
|
|
49074
|
+
});
|
|
49075
|
+
server.registerTool("get_dependents", {
|
|
49076
|
+
description: "Get tasks that depend on this task (what is blocked by this).",
|
|
49077
|
+
inputSchema: {
|
|
49078
|
+
task_id: exports_external2.coerce.number()
|
|
49079
|
+
}
|
|
49080
|
+
}, async (args) => {
|
|
49081
|
+
const deps = getDependents(args.task_id);
|
|
49082
|
+
return { content: [{ type: "text", text: JSON.stringify({ dependents: deps, count: deps.length }) }] };
|
|
49083
|
+
});
|
|
49084
|
+
server.registerTool("get_task_activity", {
|
|
49085
|
+
description: "Get activity log for a task: status changes, comments, dependency changes.",
|
|
49086
|
+
inputSchema: {
|
|
49087
|
+
task_id: exports_external2.coerce.number(),
|
|
49088
|
+
limit: exports_external2.coerce.number().optional()
|
|
49089
|
+
}
|
|
49090
|
+
}, async (args) => {
|
|
49091
|
+
const activity = getTaskActivity(args.task_id, args.limit ?? 50);
|
|
49092
|
+
return { content: [{ type: "text", text: JSON.stringify({ activity, count: activity.length }) }] };
|
|
49093
|
+
});
|
|
49094
|
+
server.registerTool("get_due_tasks", {
|
|
49095
|
+
description: "Get tasks with approaching or past due dates. Returns tasks that are overdue, due today, or due within the specified window (default 24h). Ordered by due_at ascending. Excludes completed and cancelled tasks.",
|
|
49096
|
+
inputSchema: {
|
|
49097
|
+
window_hours: exports_external2.coerce.number().optional()
|
|
49098
|
+
}
|
|
49099
|
+
}, async (args) => {
|
|
49100
|
+
const due = getDueTasks({ window_hours: args.window_hours });
|
|
49101
|
+
return { content: [{ type: "text", text: JSON.stringify({ tasks: due, count: due.length }) }] };
|
|
49102
|
+
});
|
|
49103
|
+
server.registerTool("get_task_summary", {
|
|
49104
|
+
description: "Get a structured summary of a task including progress metrics, recent activity, blockers, and dependents. Returns subtask progress, dependency progress, completion percentage, and recent activity log.",
|
|
49105
|
+
inputSchema: {
|
|
49106
|
+
id: exports_external2.coerce.number().optional(),
|
|
49107
|
+
uuid: exports_external2.string().optional()
|
|
49108
|
+
}
|
|
49109
|
+
}, async (args) => {
|
|
49110
|
+
const lookup = args.id ?? args.uuid;
|
|
49111
|
+
if (!lookup)
|
|
49112
|
+
return { content: [{ type: "text", text: "id or uuid required" }], isError: true };
|
|
49113
|
+
const summary = getTaskSummary(lookup);
|
|
49114
|
+
if (!summary)
|
|
49115
|
+
return { content: [{ type: "text", text: `Task not found: ${lookup}` }], isError: true };
|
|
49116
|
+
return { content: [{ type: "text", text: JSON.stringify(summary) }] };
|
|
49117
|
+
});
|
|
49118
|
+
server.registerTool("search_tasks", {
|
|
49119
|
+
description: "Search tasks using full-text search on subject, description, and tags. Supports phrase queries (quoted) and prefix matching. Optional filters: status, assignee, project_id, space, priority. Use sort='relevance' (default) or 'recent'.",
|
|
49120
|
+
inputSchema: {
|
|
49121
|
+
query: exports_external2.string(),
|
|
49122
|
+
status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled", "blocked"]).optional(),
|
|
49123
|
+
assignee: exports_external2.string().optional(),
|
|
49124
|
+
project_id: exports_external2.string().optional(),
|
|
49125
|
+
space: exports_external2.string().optional(),
|
|
49126
|
+
priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional(),
|
|
49127
|
+
limit: exports_external2.coerce.number().optional(),
|
|
49128
|
+
sort: exports_external2.enum(["relevance", "recent"]).optional(),
|
|
49129
|
+
include_archived: exports_external2.coerce.boolean().optional()
|
|
49130
|
+
}
|
|
49131
|
+
}, async (args) => {
|
|
49132
|
+
const results = searchTasks({ query: args.query, ...args });
|
|
49133
|
+
return { content: [{ type: "text", text: JSON.stringify({ tasks: results, count: results.length }) }] };
|
|
49134
|
+
});
|
|
49135
|
+
}
|
|
49136
|
+
var init_tasks2 = __esm(() => {
|
|
49137
|
+
init_zod2();
|
|
49138
|
+
init_tasks();
|
|
49139
|
+
init_identity();
|
|
49140
|
+
});
|
|
49141
|
+
|
|
49142
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
|
|
49143
|
+
class WebStandardStreamableHTTPServerTransport {
|
|
49144
|
+
constructor(options = {}) {
|
|
49145
|
+
this._started = false;
|
|
49146
|
+
this._hasHandledRequest = false;
|
|
49147
|
+
this._streamMapping = new Map;
|
|
49148
|
+
this._requestToStreamMapping = new Map;
|
|
49149
|
+
this._requestResponseMap = new Map;
|
|
49150
|
+
this._initialized = false;
|
|
49151
|
+
this._enableJsonResponse = false;
|
|
49152
|
+
this._standaloneSseStreamId = "_GET_stream";
|
|
49153
|
+
this.sessionIdGenerator = options.sessionIdGenerator;
|
|
49154
|
+
this._enableJsonResponse = options.enableJsonResponse ?? false;
|
|
49155
|
+
this._eventStore = options.eventStore;
|
|
49156
|
+
this._onsessioninitialized = options.onsessioninitialized;
|
|
49157
|
+
this._onsessionclosed = options.onsessionclosed;
|
|
49158
|
+
this._allowedHosts = options.allowedHosts;
|
|
49159
|
+
this._allowedOrigins = options.allowedOrigins;
|
|
49160
|
+
this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
|
|
49161
|
+
this._retryInterval = options.retryInterval;
|
|
49162
|
+
}
|
|
49163
|
+
async start() {
|
|
49164
|
+
if (this._started) {
|
|
49165
|
+
throw new Error("Transport already started");
|
|
49166
|
+
}
|
|
49167
|
+
this._started = true;
|
|
49168
|
+
}
|
|
49169
|
+
createJsonErrorResponse(status, code, message, options) {
|
|
49170
|
+
const error48 = { code, message };
|
|
49171
|
+
if (options?.data !== undefined) {
|
|
49172
|
+
error48.data = options.data;
|
|
49173
|
+
}
|
|
49174
|
+
return new Response(JSON.stringify({
|
|
49175
|
+
jsonrpc: "2.0",
|
|
49176
|
+
error: error48,
|
|
49177
|
+
id: null
|
|
49178
|
+
}), {
|
|
49179
|
+
status,
|
|
49180
|
+
headers: {
|
|
49181
|
+
"Content-Type": "application/json",
|
|
49182
|
+
...options?.headers
|
|
49183
|
+
}
|
|
49184
|
+
});
|
|
49185
|
+
}
|
|
49186
|
+
validateRequestHeaders(req) {
|
|
49187
|
+
if (!this._enableDnsRebindingProtection) {
|
|
49188
|
+
return;
|
|
49189
|
+
}
|
|
49190
|
+
if (this._allowedHosts && this._allowedHosts.length > 0) {
|
|
49191
|
+
const hostHeader = req.headers.get("host");
|
|
49192
|
+
if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
|
|
49193
|
+
const error48 = `Invalid Host header: ${hostHeader}`;
|
|
49194
|
+
this.onerror?.(new Error(error48));
|
|
49195
|
+
return this.createJsonErrorResponse(403, -32000, error48);
|
|
49196
|
+
}
|
|
49197
|
+
}
|
|
49198
|
+
if (this._allowedOrigins && this._allowedOrigins.length > 0) {
|
|
49199
|
+
const originHeader = req.headers.get("origin");
|
|
49200
|
+
if (originHeader && !this._allowedOrigins.includes(originHeader)) {
|
|
49201
|
+
const error48 = `Invalid Origin header: ${originHeader}`;
|
|
49202
|
+
this.onerror?.(new Error(error48));
|
|
49203
|
+
return this.createJsonErrorResponse(403, -32000, error48);
|
|
49204
|
+
}
|
|
49205
|
+
}
|
|
49206
|
+
return;
|
|
49207
|
+
}
|
|
49208
|
+
async handleRequest(req, options) {
|
|
49209
|
+
if (!this.sessionIdGenerator && this._hasHandledRequest) {
|
|
49210
|
+
throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");
|
|
49211
|
+
}
|
|
49212
|
+
this._hasHandledRequest = true;
|
|
49213
|
+
const validationError = this.validateRequestHeaders(req);
|
|
49214
|
+
if (validationError) {
|
|
49215
|
+
return validationError;
|
|
49216
|
+
}
|
|
49217
|
+
switch (req.method) {
|
|
49218
|
+
case "POST":
|
|
49219
|
+
return this.handlePostRequest(req, options);
|
|
49220
|
+
case "GET":
|
|
49221
|
+
return this.handleGetRequest(req);
|
|
49222
|
+
case "DELETE":
|
|
49223
|
+
return this.handleDeleteRequest(req);
|
|
49224
|
+
default:
|
|
49225
|
+
return this.handleUnsupportedRequest();
|
|
49226
|
+
}
|
|
49227
|
+
}
|
|
49228
|
+
async writePrimingEvent(controller, encoder, streamId, protocolVersion) {
|
|
49229
|
+
if (!this._eventStore) {
|
|
49230
|
+
return;
|
|
49231
|
+
}
|
|
49232
|
+
if (protocolVersion < "2025-11-25") {
|
|
49233
|
+
return;
|
|
49234
|
+
}
|
|
49235
|
+
const primingEventId = await this._eventStore.storeEvent(streamId, {});
|
|
49236
|
+
let primingEvent = `id: ${primingEventId}
|
|
49237
|
+
data:
|
|
49238
|
+
|
|
49239
|
+
`;
|
|
49240
|
+
if (this._retryInterval !== undefined) {
|
|
49241
|
+
primingEvent = `id: ${primingEventId}
|
|
49242
|
+
retry: ${this._retryInterval}
|
|
49243
|
+
data:
|
|
49244
|
+
|
|
49245
|
+
`;
|
|
49246
|
+
}
|
|
49247
|
+
controller.enqueue(encoder.encode(primingEvent));
|
|
49248
|
+
}
|
|
49249
|
+
async handleGetRequest(req) {
|
|
49250
|
+
const acceptHeader = req.headers.get("accept");
|
|
49251
|
+
if (!acceptHeader?.includes("text/event-stream")) {
|
|
49252
|
+
return this.createJsonErrorResponse(406, -32000, "Not Acceptable: Client must accept text/event-stream");
|
|
49253
|
+
}
|
|
49254
|
+
const sessionError = this.validateSession(req);
|
|
49255
|
+
if (sessionError) {
|
|
49256
|
+
return sessionError;
|
|
49257
|
+
}
|
|
49258
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
49259
|
+
if (protocolError) {
|
|
49260
|
+
return protocolError;
|
|
49261
|
+
}
|
|
49262
|
+
if (this._eventStore) {
|
|
49263
|
+
const lastEventId = req.headers.get("last-event-id");
|
|
49264
|
+
if (lastEventId) {
|
|
49265
|
+
return this.replayEvents(lastEventId);
|
|
49266
|
+
}
|
|
49267
|
+
}
|
|
49268
|
+
if (this._streamMapping.get(this._standaloneSseStreamId) !== undefined) {
|
|
49269
|
+
return this.createJsonErrorResponse(409, -32000, "Conflict: Only one SSE stream is allowed per session");
|
|
49270
|
+
}
|
|
49271
|
+
const encoder = new TextEncoder;
|
|
49272
|
+
let streamController;
|
|
49273
|
+
const readable = new ReadableStream({
|
|
49274
|
+
start: (controller) => {
|
|
49275
|
+
streamController = controller;
|
|
49276
|
+
},
|
|
49277
|
+
cancel: () => {
|
|
49278
|
+
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
49279
|
+
}
|
|
49280
|
+
});
|
|
49281
|
+
const headers = {
|
|
49282
|
+
"Content-Type": "text/event-stream",
|
|
49283
|
+
"Cache-Control": "no-cache, no-transform",
|
|
49284
|
+
Connection: "keep-alive"
|
|
49285
|
+
};
|
|
49286
|
+
if (this.sessionId !== undefined) {
|
|
49287
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
49288
|
+
}
|
|
49289
|
+
this._streamMapping.set(this._standaloneSseStreamId, {
|
|
49290
|
+
controller: streamController,
|
|
49291
|
+
encoder,
|
|
49292
|
+
cleanup: () => {
|
|
49293
|
+
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
49294
|
+
try {
|
|
49295
|
+
streamController.close();
|
|
49296
|
+
} catch {}
|
|
49297
|
+
}
|
|
49298
|
+
});
|
|
49299
|
+
return new Response(readable, { headers });
|
|
49300
|
+
}
|
|
49301
|
+
async replayEvents(lastEventId) {
|
|
49302
|
+
if (!this._eventStore) {
|
|
49303
|
+
return this.createJsonErrorResponse(400, -32000, "Event store not configured");
|
|
49304
|
+
}
|
|
49305
|
+
try {
|
|
49306
|
+
let streamId;
|
|
49307
|
+
if (this._eventStore.getStreamIdForEventId) {
|
|
49308
|
+
streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
|
|
49309
|
+
if (!streamId) {
|
|
49310
|
+
return this.createJsonErrorResponse(400, -32000, "Invalid event ID format");
|
|
49311
|
+
}
|
|
49312
|
+
if (this._streamMapping.get(streamId) !== undefined) {
|
|
49313
|
+
return this.createJsonErrorResponse(409, -32000, "Conflict: Stream already has an active connection");
|
|
49314
|
+
}
|
|
49315
|
+
}
|
|
49316
|
+
const headers = {
|
|
49317
|
+
"Content-Type": "text/event-stream",
|
|
49318
|
+
"Cache-Control": "no-cache, no-transform",
|
|
49319
|
+
Connection: "keep-alive"
|
|
49320
|
+
};
|
|
49321
|
+
if (this.sessionId !== undefined) {
|
|
49322
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
49323
|
+
}
|
|
49324
|
+
const encoder = new TextEncoder;
|
|
49325
|
+
let streamController;
|
|
49326
|
+
const readable = new ReadableStream({
|
|
49327
|
+
start: (controller) => {
|
|
49328
|
+
streamController = controller;
|
|
49329
|
+
},
|
|
49330
|
+
cancel: () => {}
|
|
49331
|
+
});
|
|
49332
|
+
const replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, {
|
|
49333
|
+
send: async (eventId, message) => {
|
|
49334
|
+
const success2 = this.writeSSEEvent(streamController, encoder, message, eventId);
|
|
49335
|
+
if (!success2) {
|
|
49336
|
+
this.onerror?.(new Error("Failed replay events"));
|
|
49337
|
+
try {
|
|
49338
|
+
streamController.close();
|
|
49339
|
+
} catch {}
|
|
49340
|
+
}
|
|
49341
|
+
}
|
|
49342
|
+
});
|
|
49343
|
+
this._streamMapping.set(replayedStreamId, {
|
|
49344
|
+
controller: streamController,
|
|
49345
|
+
encoder,
|
|
49346
|
+
cleanup: () => {
|
|
49347
|
+
this._streamMapping.delete(replayedStreamId);
|
|
49348
|
+
try {
|
|
49349
|
+
streamController.close();
|
|
49350
|
+
} catch {}
|
|
49351
|
+
}
|
|
49352
|
+
});
|
|
49353
|
+
return new Response(readable, { headers });
|
|
49354
|
+
} catch (error48) {
|
|
49355
|
+
this.onerror?.(error48);
|
|
49356
|
+
return this.createJsonErrorResponse(500, -32000, "Error replaying events");
|
|
49357
|
+
}
|
|
49358
|
+
}
|
|
49359
|
+
writeSSEEvent(controller, encoder, message, eventId) {
|
|
49360
|
+
try {
|
|
49361
|
+
let eventData = `event: message
|
|
49362
|
+
`;
|
|
49363
|
+
if (eventId) {
|
|
49364
|
+
eventData += `id: ${eventId}
|
|
49365
|
+
`;
|
|
49366
|
+
}
|
|
49367
|
+
eventData += `data: ${JSON.stringify(message)}
|
|
49368
|
+
|
|
49369
|
+
`;
|
|
49370
|
+
controller.enqueue(encoder.encode(eventData));
|
|
49371
|
+
return true;
|
|
49372
|
+
} catch {
|
|
49373
|
+
return false;
|
|
49374
|
+
}
|
|
49375
|
+
}
|
|
49376
|
+
handleUnsupportedRequest() {
|
|
49377
|
+
return new Response(JSON.stringify({
|
|
49378
|
+
jsonrpc: "2.0",
|
|
49379
|
+
error: {
|
|
49380
|
+
code: -32000,
|
|
49381
|
+
message: "Method not allowed."
|
|
49382
|
+
},
|
|
49383
|
+
id: null
|
|
49384
|
+
}), {
|
|
49385
|
+
status: 405,
|
|
49386
|
+
headers: {
|
|
49387
|
+
Allow: "GET, POST, DELETE",
|
|
49388
|
+
"Content-Type": "application/json"
|
|
49389
|
+
}
|
|
49390
|
+
});
|
|
49391
|
+
}
|
|
49392
|
+
async handlePostRequest(req, options) {
|
|
49393
|
+
try {
|
|
49394
|
+
const acceptHeader = req.headers.get("accept");
|
|
49395
|
+
if (!acceptHeader?.includes("application/json") || !acceptHeader.includes("text/event-stream")) {
|
|
49396
|
+
return this.createJsonErrorResponse(406, -32000, "Not Acceptable: Client must accept both application/json and text/event-stream");
|
|
49397
|
+
}
|
|
49398
|
+
const ct = req.headers.get("content-type");
|
|
49399
|
+
if (!ct || !ct.includes("application/json")) {
|
|
49400
|
+
return this.createJsonErrorResponse(415, -32000, "Unsupported Media Type: Content-Type must be application/json");
|
|
49401
|
+
}
|
|
49402
|
+
const requestInfo = {
|
|
49403
|
+
headers: Object.fromEntries(req.headers.entries())
|
|
49404
|
+
};
|
|
49405
|
+
let rawMessage;
|
|
49406
|
+
if (options?.parsedBody !== undefined) {
|
|
49407
|
+
rawMessage = options.parsedBody;
|
|
49408
|
+
} else {
|
|
49409
|
+
try {
|
|
49410
|
+
rawMessage = await req.json();
|
|
49411
|
+
} catch {
|
|
49412
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON");
|
|
49413
|
+
}
|
|
49414
|
+
}
|
|
49415
|
+
let messages;
|
|
49416
|
+
try {
|
|
49417
|
+
if (Array.isArray(rawMessage)) {
|
|
49418
|
+
messages = rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg));
|
|
49419
|
+
} else {
|
|
49420
|
+
messages = [JSONRPCMessageSchema.parse(rawMessage)];
|
|
49421
|
+
}
|
|
49422
|
+
} catch {
|
|
49423
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
|
|
49424
|
+
}
|
|
49425
|
+
const isInitializationRequest = messages.some(isInitializeRequest);
|
|
49426
|
+
if (isInitializationRequest) {
|
|
49427
|
+
if (this._initialized && this.sessionId !== undefined) {
|
|
49428
|
+
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Server already initialized");
|
|
49429
|
+
}
|
|
49430
|
+
if (messages.length > 1) {
|
|
49431
|
+
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Only one initialization request is allowed");
|
|
49432
|
+
}
|
|
49433
|
+
this.sessionId = this.sessionIdGenerator?.();
|
|
49434
|
+
this._initialized = true;
|
|
49435
|
+
if (this.sessionId && this._onsessioninitialized) {
|
|
49436
|
+
await Promise.resolve(this._onsessioninitialized(this.sessionId));
|
|
49437
|
+
}
|
|
49438
|
+
}
|
|
49439
|
+
if (!isInitializationRequest) {
|
|
49440
|
+
const sessionError = this.validateSession(req);
|
|
49441
|
+
if (sessionError) {
|
|
49442
|
+
return sessionError;
|
|
49443
|
+
}
|
|
49444
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
49445
|
+
if (protocolError) {
|
|
49446
|
+
return protocolError;
|
|
49447
|
+
}
|
|
49448
|
+
}
|
|
49449
|
+
const hasRequests = messages.some(isJSONRPCRequest);
|
|
49450
|
+
if (!hasRequests) {
|
|
49451
|
+
for (const message of messages) {
|
|
49452
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
|
|
49453
|
+
}
|
|
49454
|
+
return new Response(null, { status: 202 });
|
|
49455
|
+
}
|
|
49456
|
+
const streamId = crypto.randomUUID();
|
|
49457
|
+
const initRequest = messages.find((m) => isInitializeRequest(m));
|
|
49458
|
+
const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
|
|
49459
|
+
if (this._enableJsonResponse) {
|
|
49460
|
+
return new Promise((resolve2) => {
|
|
49461
|
+
this._streamMapping.set(streamId, {
|
|
49462
|
+
resolveJson: resolve2,
|
|
49463
|
+
cleanup: () => {
|
|
49464
|
+
this._streamMapping.delete(streamId);
|
|
49465
|
+
}
|
|
49466
|
+
});
|
|
49467
|
+
for (const message of messages) {
|
|
49468
|
+
if (isJSONRPCRequest(message)) {
|
|
49469
|
+
this._requestToStreamMapping.set(message.id, streamId);
|
|
49470
|
+
}
|
|
49471
|
+
}
|
|
49472
|
+
for (const message of messages) {
|
|
49473
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
|
|
49474
|
+
}
|
|
49475
|
+
});
|
|
49476
|
+
}
|
|
49477
|
+
const encoder = new TextEncoder;
|
|
49478
|
+
let streamController;
|
|
49479
|
+
const readable = new ReadableStream({
|
|
49480
|
+
start: (controller) => {
|
|
49481
|
+
streamController = controller;
|
|
49482
|
+
},
|
|
49483
|
+
cancel: () => {
|
|
49484
|
+
this._streamMapping.delete(streamId);
|
|
49485
|
+
}
|
|
49486
|
+
});
|
|
49487
|
+
const headers = {
|
|
49488
|
+
"Content-Type": "text/event-stream",
|
|
49489
|
+
"Cache-Control": "no-cache",
|
|
49490
|
+
Connection: "keep-alive"
|
|
49491
|
+
};
|
|
49492
|
+
if (this.sessionId !== undefined) {
|
|
49493
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
49494
|
+
}
|
|
49495
|
+
for (const message of messages) {
|
|
49496
|
+
if (isJSONRPCRequest(message)) {
|
|
49497
|
+
this._streamMapping.set(streamId, {
|
|
49498
|
+
controller: streamController,
|
|
49499
|
+
encoder,
|
|
49500
|
+
cleanup: () => {
|
|
49501
|
+
this._streamMapping.delete(streamId);
|
|
49502
|
+
try {
|
|
49503
|
+
streamController.close();
|
|
49504
|
+
} catch {}
|
|
49505
|
+
}
|
|
49506
|
+
});
|
|
49507
|
+
this._requestToStreamMapping.set(message.id, streamId);
|
|
49508
|
+
}
|
|
49509
|
+
}
|
|
49510
|
+
await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
|
|
49511
|
+
for (const message of messages) {
|
|
49512
|
+
let closeSSEStream;
|
|
49513
|
+
let closeStandaloneSSEStream;
|
|
49514
|
+
if (isJSONRPCRequest(message) && this._eventStore && clientProtocolVersion >= "2025-11-25") {
|
|
49515
|
+
closeSSEStream = () => {
|
|
49516
|
+
this.closeSSEStream(message.id);
|
|
49517
|
+
};
|
|
49518
|
+
closeStandaloneSSEStream = () => {
|
|
49519
|
+
this.closeStandaloneSSEStream();
|
|
49520
|
+
};
|
|
49521
|
+
}
|
|
49522
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo, closeSSEStream, closeStandaloneSSEStream });
|
|
49523
|
+
}
|
|
49524
|
+
return new Response(readable, { status: 200, headers });
|
|
49525
|
+
} catch (error48) {
|
|
49526
|
+
this.onerror?.(error48);
|
|
49527
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error", { data: String(error48) });
|
|
49528
|
+
}
|
|
49529
|
+
}
|
|
49530
|
+
async handleDeleteRequest(req) {
|
|
49531
|
+
const sessionError = this.validateSession(req);
|
|
49532
|
+
if (sessionError) {
|
|
49533
|
+
return sessionError;
|
|
49534
|
+
}
|
|
49535
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
49536
|
+
if (protocolError) {
|
|
49537
|
+
return protocolError;
|
|
49538
|
+
}
|
|
49539
|
+
await Promise.resolve(this._onsessionclosed?.(this.sessionId));
|
|
49540
|
+
await this.close();
|
|
49541
|
+
return new Response(null, { status: 200 });
|
|
49542
|
+
}
|
|
49543
|
+
validateSession(req) {
|
|
49544
|
+
if (this.sessionIdGenerator === undefined) {
|
|
49545
|
+
return;
|
|
49546
|
+
}
|
|
49547
|
+
if (!this._initialized) {
|
|
49548
|
+
return this.createJsonErrorResponse(400, -32000, "Bad Request: Server not initialized");
|
|
49549
|
+
}
|
|
49550
|
+
const sessionId = req.headers.get("mcp-session-id");
|
|
49551
|
+
if (!sessionId) {
|
|
49552
|
+
return this.createJsonErrorResponse(400, -32000, "Bad Request: Mcp-Session-Id header is required");
|
|
49553
|
+
}
|
|
49554
|
+
if (sessionId !== this.sessionId) {
|
|
49555
|
+
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
49556
|
+
}
|
|
49557
|
+
return;
|
|
49558
|
+
}
|
|
49559
|
+
validateProtocolVersion(req) {
|
|
49560
|
+
const protocolVersion = req.headers.get("mcp-protocol-version");
|
|
49561
|
+
if (protocolVersion !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
|
|
49562
|
+
return this.createJsonErrorResponse(400, -32000, `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`);
|
|
49563
|
+
}
|
|
49564
|
+
return;
|
|
49565
|
+
}
|
|
49566
|
+
async close() {
|
|
49567
|
+
this._streamMapping.forEach(({ cleanup }) => {
|
|
49568
|
+
cleanup();
|
|
49569
|
+
});
|
|
49570
|
+
this._streamMapping.clear();
|
|
49571
|
+
this._requestResponseMap.clear();
|
|
49572
|
+
this.onclose?.();
|
|
49573
|
+
}
|
|
49574
|
+
closeSSEStream(requestId) {
|
|
49575
|
+
const streamId = this._requestToStreamMapping.get(requestId);
|
|
49576
|
+
if (!streamId)
|
|
49577
|
+
return;
|
|
49578
|
+
const stream = this._streamMapping.get(streamId);
|
|
49579
|
+
if (stream) {
|
|
49580
|
+
stream.cleanup();
|
|
49581
|
+
}
|
|
49582
|
+
}
|
|
49583
|
+
closeStandaloneSSEStream() {
|
|
49584
|
+
const stream = this._streamMapping.get(this._standaloneSseStreamId);
|
|
49585
|
+
if (stream) {
|
|
49586
|
+
stream.cleanup();
|
|
49587
|
+
}
|
|
49588
|
+
}
|
|
49589
|
+
async send(message, options) {
|
|
49590
|
+
let requestId = options?.relatedRequestId;
|
|
49591
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
49592
|
+
requestId = message.id;
|
|
49593
|
+
}
|
|
49594
|
+
if (requestId === undefined) {
|
|
49595
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
49596
|
+
throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");
|
|
49597
|
+
}
|
|
49598
|
+
let eventId;
|
|
49599
|
+
if (this._eventStore) {
|
|
49600
|
+
eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
|
|
49601
|
+
}
|
|
49602
|
+
const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
|
|
49603
|
+
if (standaloneSse === undefined) {
|
|
49604
|
+
return;
|
|
49605
|
+
}
|
|
49606
|
+
if (standaloneSse.controller && standaloneSse.encoder) {
|
|
49607
|
+
this.writeSSEEvent(standaloneSse.controller, standaloneSse.encoder, message, eventId);
|
|
49608
|
+
}
|
|
49609
|
+
return;
|
|
49610
|
+
}
|
|
49611
|
+
const streamId = this._requestToStreamMapping.get(requestId);
|
|
49612
|
+
if (!streamId) {
|
|
49613
|
+
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
49614
|
+
}
|
|
49615
|
+
const stream = this._streamMapping.get(streamId);
|
|
49616
|
+
if (!this._enableJsonResponse && stream?.controller && stream?.encoder) {
|
|
49617
|
+
let eventId;
|
|
49618
|
+
if (this._eventStore) {
|
|
49619
|
+
eventId = await this._eventStore.storeEvent(streamId, message);
|
|
49620
|
+
}
|
|
49621
|
+
this.writeSSEEvent(stream.controller, stream.encoder, message, eventId);
|
|
49622
|
+
}
|
|
49623
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
49624
|
+
this._requestResponseMap.set(requestId, message);
|
|
49625
|
+
const relatedIds = Array.from(this._requestToStreamMapping.entries()).filter(([_, sid]) => sid === streamId).map(([id]) => id);
|
|
49626
|
+
const allResponsesReady = relatedIds.every((id) => this._requestResponseMap.has(id));
|
|
49627
|
+
if (allResponsesReady) {
|
|
49628
|
+
if (!stream) {
|
|
49629
|
+
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
49630
|
+
}
|
|
49631
|
+
if (this._enableJsonResponse && stream.resolveJson) {
|
|
49632
|
+
const headers = {
|
|
49633
|
+
"Content-Type": "application/json"
|
|
49634
|
+
};
|
|
49635
|
+
if (this.sessionId !== undefined) {
|
|
49636
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
49637
|
+
}
|
|
49638
|
+
const responses = relatedIds.map((id) => this._requestResponseMap.get(id));
|
|
49639
|
+
if (responses.length === 1) {
|
|
49640
|
+
stream.resolveJson(new Response(JSON.stringify(responses[0]), { status: 200, headers }));
|
|
49641
|
+
} else {
|
|
49642
|
+
stream.resolveJson(new Response(JSON.stringify(responses), { status: 200, headers }));
|
|
49643
|
+
}
|
|
49644
|
+
} else {
|
|
49645
|
+
stream.cleanup();
|
|
49646
|
+
}
|
|
49647
|
+
for (const id of relatedIds) {
|
|
49648
|
+
this._requestResponseMap.delete(id);
|
|
49649
|
+
this._requestToStreamMapping.delete(id);
|
|
49650
|
+
}
|
|
49651
|
+
}
|
|
49652
|
+
}
|
|
49653
|
+
}
|
|
49654
|
+
}
|
|
49655
|
+
var init_webStandardStreamableHttp = __esm(() => {
|
|
49656
|
+
init_types3();
|
|
49657
|
+
});
|
|
49658
|
+
|
|
49659
|
+
// src/mcp/http.ts
|
|
49660
|
+
function isHttpMode(args) {
|
|
49661
|
+
return args.includes("--http") || process.env.MCP_HTTP === "1";
|
|
49662
|
+
}
|
|
49663
|
+
function resolveMcpHttpPort(args) {
|
|
49664
|
+
const portIdx = args.indexOf("--port");
|
|
49665
|
+
if (portIdx >= 0 && args[portIdx + 1]) {
|
|
49666
|
+
return Number(args[portIdx + 1]);
|
|
49667
|
+
}
|
|
49668
|
+
const envPort = process.env.MCP_HTTP_PORT;
|
|
49669
|
+
if (envPort)
|
|
49670
|
+
return Number(envPort);
|
|
49671
|
+
return DEFAULT_MCP_HTTP_PORT;
|
|
49672
|
+
}
|
|
49673
|
+
function healthPayload(name = MCP_SERVICE_NAME) {
|
|
49674
|
+
return { status: "ok", name };
|
|
49675
|
+
}
|
|
49676
|
+
async function handleMcpRequest(req, buildServer) {
|
|
49677
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
49678
|
+
sessionIdGenerator: undefined
|
|
49679
|
+
});
|
|
49680
|
+
const server = buildServer();
|
|
49681
|
+
await server.connect(transport);
|
|
49682
|
+
return transport.handleRequest(req);
|
|
49683
|
+
}
|
|
49684
|
+
function startMcpHttpServer(options) {
|
|
49685
|
+
const { name, port, buildServer } = options;
|
|
49686
|
+
const server = Bun.serve({
|
|
49687
|
+
hostname: MCP_HTTP_HOST,
|
|
49688
|
+
port,
|
|
49689
|
+
async fetch(req) {
|
|
49690
|
+
const url2 = new URL(req.url);
|
|
49691
|
+
if (url2.pathname === "/health" && req.method === "GET") {
|
|
49692
|
+
return Response.json(healthPayload(name));
|
|
49693
|
+
}
|
|
49694
|
+
if (url2.pathname === "/mcp") {
|
|
49695
|
+
return handleMcpRequest(req, buildServer);
|
|
49696
|
+
}
|
|
49697
|
+
return new Response("Not Found", { status: 404 });
|
|
49698
|
+
}
|
|
49699
|
+
});
|
|
49700
|
+
console.error(`${name}-mcp HTTP listening on http://${MCP_HTTP_HOST}:${port}/mcp`);
|
|
49701
|
+
return server;
|
|
49702
|
+
}
|
|
49703
|
+
var DEFAULT_MCP_HTTP_PORT = 8811, MCP_HTTP_HOST = "127.0.0.1", MCP_SERVICE_NAME = "conversations";
|
|
49704
|
+
var init_http = __esm(() => {
|
|
49705
|
+
init_webStandardStreamableHttp();
|
|
49706
|
+
});
|
|
49707
|
+
|
|
47947
49708
|
// src/mcp/index.ts
|
|
47948
49709
|
var exports_mcp = {};
|
|
47949
49710
|
__export(exports_mcp, {
|
|
47950
49711
|
startMcpServer: () => startMcpServer,
|
|
47951
|
-
server: () => server
|
|
49712
|
+
server: () => server,
|
|
49713
|
+
buildServer: () => buildServer
|
|
47952
49714
|
});
|
|
47953
49715
|
function getAgentFocus(agentId) {
|
|
47954
49716
|
if (agentFocus.has(agentId))
|
|
@@ -47962,12 +49724,60 @@ function resolveProjectId(explicitProjectId, agentId) {
|
|
|
47962
49724
|
const focused = getAgentFocus(agentId);
|
|
47963
49725
|
return focused ?? undefined;
|
|
47964
49726
|
}
|
|
49727
|
+
function buildServer(forHttp = false) {
|
|
49728
|
+
const srv = new McpServer({
|
|
49729
|
+
name: "conversations",
|
|
49730
|
+
version: import__package2.default.version
|
|
49731
|
+
});
|
|
49732
|
+
registerMessagingTools(srv, resolveProjectId);
|
|
49733
|
+
registerSpaceTools(srv);
|
|
49734
|
+
registerProjectTools(srv);
|
|
49735
|
+
registerAgentTools(srv, agentFocus, getAgentFocus);
|
|
49736
|
+
registerAdvancedTools(srv, import__package2.default.version);
|
|
49737
|
+
registerTaskTools(srv);
|
|
49738
|
+
registerTmuxTools(srv);
|
|
49739
|
+
registerCloudSyncTools(srv);
|
|
49740
|
+
if (!forHttp) {
|
|
49741
|
+
registerChannelBridge(srv);
|
|
49742
|
+
registerTelegramChannel(srv);
|
|
49743
|
+
}
|
|
49744
|
+
return srv;
|
|
49745
|
+
}
|
|
47965
49746
|
async function startMcpServer() {
|
|
47966
49747
|
const transport = new StdioServerTransport;
|
|
47967
|
-
registerCloudSyncTools(server);
|
|
47968
49748
|
await server.connect(transport);
|
|
47969
49749
|
}
|
|
47970
|
-
|
|
49750
|
+
async function main() {
|
|
49751
|
+
const args = process.argv.slice(2);
|
|
49752
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
49753
|
+
console.log(`conversations-mcp \u2014 MCP server for @hasna/conversations v${import__package2.default.version}
|
|
49754
|
+
|
|
49755
|
+
Usage:
|
|
49756
|
+
conversations-mcp stdio transport (default)
|
|
49757
|
+
conversations-mcp --http Streamable HTTP on 127.0.0.1:8811
|
|
49758
|
+
conversations-mcp --http --port <n>
|
|
49759
|
+
|
|
49760
|
+
Environment:
|
|
49761
|
+
MCP_HTTP=1 Enable HTTP mode
|
|
49762
|
+
MCP_HTTP_PORT=<n> Override default port (8811)
|
|
49763
|
+
`);
|
|
49764
|
+
return;
|
|
49765
|
+
}
|
|
49766
|
+
if (args.includes("--version") || args.includes("-V")) {
|
|
49767
|
+
console.log(import__package2.default.version);
|
|
49768
|
+
return;
|
|
49769
|
+
}
|
|
49770
|
+
if (isHttpMode(args)) {
|
|
49771
|
+
startMcpHttpServer({
|
|
49772
|
+
name: "conversations",
|
|
49773
|
+
port: resolveMcpHttpPort(args),
|
|
49774
|
+
buildServer: () => buildServer(true)
|
|
49775
|
+
});
|
|
49776
|
+
return;
|
|
49777
|
+
}
|
|
49778
|
+
await startMcpServer();
|
|
49779
|
+
}
|
|
49780
|
+
var import__package2, agentFocus, server, isDirectRun;
|
|
47971
49781
|
var init_mcp2 = __esm(() => {
|
|
47972
49782
|
init_mcp();
|
|
47973
49783
|
init_stdio2();
|
|
@@ -47981,23 +49791,14 @@ var init_mcp2 = __esm(() => {
|
|
|
47981
49791
|
init_channel();
|
|
47982
49792
|
init_telegram_channel();
|
|
47983
49793
|
init_tmux2();
|
|
49794
|
+
init_tasks2();
|
|
49795
|
+
init_http();
|
|
47984
49796
|
import__package2 = __toESM(require_package(), 1);
|
|
47985
|
-
server = new McpServer({
|
|
47986
|
-
name: "conversations",
|
|
47987
|
-
version: import__package2.default.version
|
|
47988
|
-
});
|
|
47989
49797
|
agentFocus = new Map;
|
|
47990
|
-
|
|
47991
|
-
registerSpaceTools(server);
|
|
47992
|
-
registerProjectTools(server);
|
|
47993
|
-
registerAgentTools(server, agentFocus, getAgentFocus);
|
|
47994
|
-
registerAdvancedTools(server, import__package2.default.version);
|
|
47995
|
-
registerTmuxTools(server);
|
|
47996
|
-
registerChannelBridge(server);
|
|
47997
|
-
registerTelegramChannel(server);
|
|
49798
|
+
server = buildServer();
|
|
47998
49799
|
isDirectRun = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("mcp.js") || process.argv[1]?.endsWith("mcp.ts");
|
|
47999
49800
|
if (isDirectRun) {
|
|
48000
|
-
|
|
49801
|
+
main().catch((error48) => {
|
|
48001
49802
|
console.error("MCP server error:", error48);
|
|
48002
49803
|
process.exit(1);
|
|
48003
49804
|
});
|
|
@@ -48010,7 +49811,7 @@ __export(exports_serve, {
|
|
|
48010
49811
|
startDashboardServer: () => startDashboardServer
|
|
48011
49812
|
});
|
|
48012
49813
|
import { join as join14, resolve as resolve2, sep } from "path";
|
|
48013
|
-
import { existsSync as
|
|
49814
|
+
import { existsSync as existsSync12 } from "fs";
|
|
48014
49815
|
function securityHeaders(base) {
|
|
48015
49816
|
const headers = new Headers(base);
|
|
48016
49817
|
if (!headers.has("X-Content-Type-Options"))
|
|
@@ -48104,17 +49905,38 @@ function isSameOrigin(req) {
|
|
|
48104
49905
|
return false;
|
|
48105
49906
|
return origin === new URL(req.url).origin;
|
|
48106
49907
|
}
|
|
49908
|
+
function resolveDashboardDist() {
|
|
49909
|
+
const configuredDist = process.env.CONVERSATIONS_DASHBOARD_DIST?.trim();
|
|
49910
|
+
const candidates = [
|
|
49911
|
+
configuredDist,
|
|
49912
|
+
join14(import.meta.dir, "../../dashboard/dist"),
|
|
49913
|
+
join14(import.meta.dir, "../dashboard/dist"),
|
|
49914
|
+
join14(process.cwd(), "dashboard/dist")
|
|
49915
|
+
].filter((candidate) => Boolean(candidate));
|
|
49916
|
+
for (const candidate of candidates) {
|
|
49917
|
+
const resolved = resolve2(candidate);
|
|
49918
|
+
if (existsSync12(join14(resolved, "index.html"))) {
|
|
49919
|
+
return resolved;
|
|
49920
|
+
}
|
|
49921
|
+
}
|
|
49922
|
+
return null;
|
|
49923
|
+
}
|
|
48107
49924
|
function startDashboardServer(port = 0, host) {
|
|
48108
49925
|
const resolvedPort = normalizePort(port, 0);
|
|
48109
49926
|
const resolvedHost = normalizeHost(host ?? process.env.CONVERSATIONS_DASHBOARD_HOST);
|
|
48110
|
-
const dashboardDist =
|
|
48111
|
-
const hasDist = existsSync11(dashboardDist);
|
|
49927
|
+
const dashboardDist = resolveDashboardDist();
|
|
48112
49928
|
const server2 = Bun.serve({
|
|
48113
49929
|
port: resolvedPort,
|
|
48114
49930
|
hostname: resolvedHost,
|
|
48115
49931
|
async fetch(req) {
|
|
48116
49932
|
const url2 = new URL(req.url);
|
|
48117
49933
|
const path = url2.pathname;
|
|
49934
|
+
if (path === "/health" && req.method === "GET") {
|
|
49935
|
+
return jsonResponse(healthPayload("conversations"));
|
|
49936
|
+
}
|
|
49937
|
+
if (path === "/mcp") {
|
|
49938
|
+
return handleMcpRequest(req, () => buildServer(true));
|
|
49939
|
+
}
|
|
48118
49940
|
if (path === "/api/status") {
|
|
48119
49941
|
return jsonResponse(getStatus());
|
|
48120
49942
|
}
|
|
@@ -48515,7 +50337,7 @@ function startDashboardServer(port = 0, host) {
|
|
|
48515
50337
|
return jsonResponse({ error: e.message }, 500);
|
|
48516
50338
|
}
|
|
48517
50339
|
}
|
|
48518
|
-
if (
|
|
50340
|
+
if (dashboardDist) {
|
|
48519
50341
|
const baseDir = resolve2(dashboardDist);
|
|
48520
50342
|
const safePath = path === "/" ? "index.html" : path.replace(/^\/+/, "");
|
|
48521
50343
|
const filePath = resolve2(baseDir, safePath);
|
|
@@ -48558,6 +50380,8 @@ var init_serve = __esm(() => {
|
|
|
48558
50380
|
init_hot();
|
|
48559
50381
|
init_graph();
|
|
48560
50382
|
init_locks();
|
|
50383
|
+
init_http();
|
|
50384
|
+
init_mcp2();
|
|
48561
50385
|
isDirectRun2 = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("serve.ts") || process.argv[1]?.endsWith("serve.js");
|
|
48562
50386
|
if (isDirectRun2) {
|
|
48563
50387
|
const port = normalizePort(process.env.PORT, 0);
|
|
@@ -49522,7 +51346,7 @@ function App({ agent }) {
|
|
|
49522
51346
|
|
|
49523
51347
|
// src/cli/brains.ts
|
|
49524
51348
|
import chalk2 from "chalk";
|
|
49525
|
-
import { mkdirSync as
|
|
51349
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
49526
51350
|
import { join as join13 } from "path";
|
|
49527
51351
|
import { spawnSync } from "child_process";
|
|
49528
51352
|
|
|
@@ -49587,25 +51411,29 @@ var gatherTrainingData = async (options = {}) => {
|
|
|
49587
51411
|
|
|
49588
51412
|
// src/lib/model-config.ts
|
|
49589
51413
|
init_db();
|
|
49590
|
-
import { readFileSync as readFileSync6, writeFileSync as
|
|
51414
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync9, existsSync as existsSync11 } from "fs";
|
|
49591
51415
|
import { join as join12 } from "path";
|
|
49592
51416
|
var DEFAULT_MODEL = "gpt-4o-mini";
|
|
49593
|
-
|
|
49594
|
-
|
|
51417
|
+
function getConfigPath3() {
|
|
51418
|
+
return process.env.CONVERSATIONS_CONFIG_PATH || join12(getDataDir2(), "config.json");
|
|
51419
|
+
}
|
|
49595
51420
|
function readConfig() {
|
|
49596
|
-
|
|
51421
|
+
const path = getConfigPath3();
|
|
51422
|
+
if (!existsSync11(path))
|
|
49597
51423
|
return {};
|
|
49598
51424
|
try {
|
|
49599
|
-
return JSON.parse(readFileSync6(
|
|
51425
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
49600
51426
|
} catch {
|
|
49601
51427
|
return {};
|
|
49602
51428
|
}
|
|
49603
51429
|
}
|
|
49604
51430
|
function writeConfig(config) {
|
|
49605
|
-
|
|
49606
|
-
|
|
51431
|
+
const path = getConfigPath3();
|
|
51432
|
+
const dir = path.substring(0, path.lastIndexOf("/"));
|
|
51433
|
+
if (!existsSync11(dir)) {
|
|
51434
|
+
mkdirSync9(dir, { recursive: true });
|
|
49607
51435
|
}
|
|
49608
|
-
|
|
51436
|
+
writeFileSync6(path, JSON.stringify(config, null, 2), "utf-8");
|
|
49609
51437
|
}
|
|
49610
51438
|
function getActiveModel() {
|
|
49611
51439
|
const config = readConfig();
|
|
@@ -49631,12 +51459,12 @@ function registerBrainsCommand(program2) {
|
|
|
49631
51459
|
const since = opts.since ? new Date(opts.since) : undefined;
|
|
49632
51460
|
const result = await gatherTrainingData({ limit: opts.limit, since });
|
|
49633
51461
|
const outputDir = join13(getDataDir2(), "training");
|
|
49634
|
-
|
|
51462
|
+
mkdirSync10(outputDir, { recursive: true });
|
|
49635
51463
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
49636
51464
|
const outputPath = opts.output ?? join13(outputDir, `training-${timestamp}.jsonl`);
|
|
49637
51465
|
const jsonl = result.examples.map((ex) => JSON.stringify(ex)).join(`
|
|
49638
51466
|
`);
|
|
49639
|
-
|
|
51467
|
+
writeFileSync7(outputPath, jsonl, "utf-8");
|
|
49640
51468
|
if (opts.json) {
|
|
49641
51469
|
console.log(JSON.stringify({ path: outputPath, count: result.count, source: result.source }));
|
|
49642
51470
|
} else {
|