@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.
Files changed (42) hide show
  1. package/LICENSE +5 -15
  2. package/README.md +14 -1
  3. package/bin/hook.js +94 -0
  4. package/bin/index.js +1878 -50
  5. package/bin/mcp.js +1828 -38
  6. package/dashboard/dist/assets/index-DhHQq3wL.css +1 -0
  7. package/dashboard/dist/index.html +2 -2
  8. package/dist/cli/brains.test.d.ts +1 -0
  9. package/dist/cli/commands/analytics.test.d.ts +1 -0
  10. package/dist/cli/commands/messaging.test.d.ts +1 -0
  11. package/dist/cli/commands/spaces.test.d.ts +1 -0
  12. package/dist/cli/commands/tmux.test.d.ts +1 -0
  13. package/dist/hooks/blocker-hook.test.d.ts +1 -0
  14. package/dist/index.d.ts +6 -2
  15. package/dist/index.js +916 -15
  16. package/dist/index.test.d.ts +1 -0
  17. package/dist/lib/gatherer.test.d.ts +1 -0
  18. package/dist/lib/model-config.test.d.ts +1 -0
  19. package/dist/lib/names.test.d.ts +1 -0
  20. package/dist/lib/pg-migrations.test.d.ts +1 -0
  21. package/dist/lib/tasks.d.ts +78 -0
  22. package/dist/lib/tasks.test.d.ts +1 -0
  23. package/dist/lib/terminal-markdown.test.d.ts +1 -0
  24. package/dist/lib/webhooks-management.test.d.ts +1 -0
  25. package/dist/lib/webhooks.d.ts +46 -1
  26. package/dist/mcp/http.d.ts +16 -0
  27. package/dist/mcp/http.test.d.ts +1 -0
  28. package/dist/mcp/index.d.ts +3 -1
  29. package/dist/mcp/telegram-channel.test.d.ts +1 -0
  30. package/dist/mcp/tools/advanced.test.d.ts +1 -0
  31. package/dist/mcp/tools/agents.test.d.ts +1 -0
  32. package/dist/mcp/tools/messaging.test.d.ts +1 -0
  33. package/dist/mcp/tools/projects.test.d.ts +1 -0
  34. package/dist/mcp/tools/spaces.test.d.ts +1 -0
  35. package/dist/mcp/tools/tasks.d.ts +6 -0
  36. package/dist/mcp/tools/tasks.test.d.ts +1 -0
  37. package/dist/mcp/tools/webhooks.d.ts +6 -0
  38. package/dist/mcp/tools/webhooks.test.d.ts +1 -0
  39. package/dist/types.d.ts +120 -0
  40. package/package.json +3 -2
  41. package/dashboard/dist/assets/index-CF_GDtNp.css +0 -1
  42. /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 mkdirSync7, copyFileSync as copyFileSync3, statSync as statSync2, existsSync as existsSync9, realpathSync } from "fs";
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 (!existsSync9(absolute)) {
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
- mkdirSync7(attachmentsDir, { recursive: true });
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.47",
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.24",
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.rds.host && config2.rds.username) {
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
- var import__package2, server, agentFocus, isDirectRun;
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
- registerMessagingTools(server, resolveProjectId);
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
- startMcpServer().catch((error48) => {
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 existsSync11 } from "fs";
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 = join14(import.meta.dir, "../../dashboard/dist");
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 (hasDist) {
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 mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
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 writeFileSync5, mkdirSync as mkdirSync8, existsSync as existsSync10 } from "fs";
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
- var CONFIG_DIR3 = getDataDir2();
49594
- var CONFIG_PATH2 = join12(CONFIG_DIR3, "config.json");
51417
+ function getConfigPath3() {
51418
+ return process.env.CONVERSATIONS_CONFIG_PATH || join12(getDataDir2(), "config.json");
51419
+ }
49595
51420
  function readConfig() {
49596
- if (!existsSync10(CONFIG_PATH2))
51421
+ const path = getConfigPath3();
51422
+ if (!existsSync11(path))
49597
51423
  return {};
49598
51424
  try {
49599
- return JSON.parse(readFileSync6(CONFIG_PATH2, "utf-8"));
51425
+ return JSON.parse(readFileSync6(path, "utf-8"));
49600
51426
  } catch {
49601
51427
  return {};
49602
51428
  }
49603
51429
  }
49604
51430
  function writeConfig(config) {
49605
- if (!existsSync10(CONFIG_DIR3)) {
49606
- mkdirSync8(CONFIG_DIR3, { recursive: true });
51431
+ const path = getConfigPath3();
51432
+ const dir = path.substring(0, path.lastIndexOf("/"));
51433
+ if (!existsSync11(dir)) {
51434
+ mkdirSync9(dir, { recursive: true });
49607
51435
  }
49608
- writeFileSync5(CONFIG_PATH2, JSON.stringify(config, null, 2), "utf-8");
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
- mkdirSync9(outputDir, { recursive: true });
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
- writeFileSync6(outputPath, jsonl, "utf-8");
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 {