@basegrid_tech/mcp 0.18.12 → 0.18.14-beta.1

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 (2) hide show
  1. package/dist/index.js +371 -26
  2. package/package.json +12 -12
package/dist/index.js CHANGED
@@ -69,6 +69,8 @@ var DEFAULT_WORKSPACE = {
69
69
  iconColor: "#6b7280",
70
70
  order: 0
71
71
  };
72
+ var MCP_ASK_TIMEOUT_MS = 60 * 60 * 1e3;
73
+ var MCP_TOOL_TIMEOUT_MS = MCP_ASK_TIMEOUT_MS + 6e4;
72
74
  var QUICK_COMMAND_LIMITS = {
73
75
  maxCommands: 40,
74
76
  maxLabel: 80,
@@ -102,6 +104,29 @@ var DEFAULT_AGENT_COMMANDS = {
102
104
  }
103
105
  };
104
106
 
107
+ // ../shared/src/config-save-mutation.ts
108
+ function applyTaskConfigMutation(currentTasks, mutation) {
109
+ if (!mutation) return currentTasks;
110
+ if (mutation.kind === "create") {
111
+ if (currentTasks.some((task) => task.id === mutation.task.id)) return currentTasks;
112
+ return [...currentTasks, mutation.task];
113
+ }
114
+ if (mutation.kind === "delete") {
115
+ const doomed = new Set(mutation.ids);
116
+ return currentTasks.filter((task) => !doomed.has(task.id));
117
+ }
118
+ const patches = new Map(mutation.patches.map((patch) => [patch.id, patch]));
119
+ return currentTasks.map((task) => {
120
+ const patch = patches.get(task.id);
121
+ if (!patch) return task;
122
+ const next = { ...task, ...patch.updates };
123
+ for (const key of patch.clear ?? []) {
124
+ delete next[key];
125
+ }
126
+ return next;
127
+ });
128
+ }
129
+
105
130
  // ../server/src/database.ts
106
131
  import Database from "libsql";
107
132
  import path3 from "path";
@@ -162,6 +187,32 @@ var DEFAULT_NO_PROXY_HOSTS = [
162
187
  ];
163
188
  var DEFAULT_NO_PROXY = DEFAULT_NO_PROXY_HOSTS.join(",");
164
189
 
190
+ // ../shared/src/connector-identity.ts
191
+ var IDENTITY_FIELDS = [
192
+ "owner",
193
+ // github
194
+ "repo",
195
+ // github
196
+ "orgId",
197
+ // yandex tracker
198
+ "queue",
199
+ // yandex tracker
200
+ "teamKey"
201
+ // linear
202
+ ];
203
+ function connectionIdentityKey(connectorId, filters) {
204
+ if (!filters) return "";
205
+ const parts = [];
206
+ for (const key of IDENTITY_FIELDS) {
207
+ const value = filters[key];
208
+ if (typeof value !== "string") continue;
209
+ const normalized = value.trim().toLowerCase();
210
+ if (!normalized) continue;
211
+ parts.push(`${key}=${normalized}`);
212
+ }
213
+ return parts.length > 0 ? `${connectorId}:${parts.join("&")}` : "";
214
+ }
215
+
165
216
  // ../shared/src/quick-commands.ts
166
217
  function normalizeQuickCommands(input) {
167
218
  if (!Array.isArray(input)) return [];
@@ -185,8 +236,13 @@ function normalizeOne(raw, seenIds) {
185
236
  const label = clampString(obj.label, QUICK_COMMAND_LIMITS.maxLabel);
186
237
  if (!label) return null;
187
238
  const scope = normalizeScope(obj.scope);
188
- const kind = obj.kind === "terminal" ? "terminal" : obj.kind === "agent" ? "agent" : null;
239
+ const kind = obj.kind === "terminal" || obj.kind === "agent" || obj.kind === "snippet" ? obj.kind : null;
189
240
  if (!kind) return null;
241
+ if (kind === "snippet") {
242
+ const text2 = clampString(obj.text, QUICK_COMMAND_LIMITS.maxBody);
243
+ if (!text2) return null;
244
+ return { id, label, scope, kind, text: text2 };
245
+ }
190
246
  if (kind === "agent") {
191
247
  const prompt = clampString(obj.prompt, QUICK_COMMAND_LIMITS.maxBody);
192
248
  if (!prompt) return null;
@@ -485,7 +541,8 @@ function createSchema() {
485
541
  );
486
542
 
487
543
  -- Quick commands (\u043F\u043E\u0440\u0442 \u0444\u0438\u0447\u0438 \u0438\u0437 orca): \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0435 \u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u043A\u043E\u043C\u0430\u043D\u0434\u044B.
488
- -- kind='agent' \u2192 \u0437\u0430\u043F\u0443\u0441\u043A \u0430\u0433\u0435\u043D\u0442\u0430 \u0441 \u043F\u0440\u043E\u043C\u043F\u0442\u043E\u043C; kind='terminal' \u2192 \u0448\u0435\u043B\u043B-\u043A\u043E\u043C\u0430\u043D\u0434\u0430.
544
+ -- kind='agent' \u2192 \u0437\u0430\u043F\u0443\u0441\u043A \u0430\u0433\u0435\u043D\u0442\u0430 \u0441 \u043F\u0440\u043E\u043C\u043F\u0442\u043E\u043C; kind='terminal' \u2192 \u0448\u0435\u043B\u043B-\u043A\u043E\u043C\u0430\u043D\u0434\u0430;
545
+ -- kind='snippet' \u2192 \u0433\u043E\u0442\u043E\u0432\u044B\u0439 \u0442\u0435\u043A\u0441\u0442, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0432\u0441\u0442\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0432 \u0438\u043D\u043F\u0443\u0442 \u0441\u0435\u0441\u0441\u0438\u0438.
489
546
  -- scope \u0445\u0440\u0430\u043D\u0438\u0442\u0441\u044F JSON-\u0441\u0442\u0440\u043E\u043A\u043E\u0439 \u0434\u043B\u044F \u0431\u0443\u0434\u0443\u0449\u0435\u0439 \u043F\u0440\u0438\u0432\u044F\u0437\u043A\u0438 \u043A \u043F\u0440\u043E\u0435\u043A\u0442\u0443 (\u0441\u0435\u0439\u0447\u0430\u0441 global).
490
547
  CREATE TABLE IF NOT EXISTS quick_commands (
491
548
  id TEXT PRIMARY KEY,
@@ -498,7 +555,8 @@ function createSchema() {
498
555
  command TEXT,
499
556
  append_enter INTEGER NOT NULL DEFAULT 1,
500
557
  position INTEGER NOT NULL DEFAULT 0,
501
- shell TEXT
558
+ shell TEXT,
559
+ snippet_text TEXT
502
560
  );
503
561
 
504
562
  CREATE TABLE IF NOT EXISTS remote_hosts (
@@ -571,6 +629,7 @@ function createSchema() {
571
629
  agent_session_id TEXT,
572
630
  branch TEXT,
573
631
  use_worktree INTEGER DEFAULT 0,
632
+ worktree_path TEXT,
574
633
  created_at TEXT NOT NULL,
575
634
  updated_at TEXT NOT NULL,
576
635
  completed_at TEXT
@@ -756,12 +815,39 @@ function createSchema() {
756
815
  updated_at INTEGER NOT NULL
757
816
  );
758
817
 
818
+ -- \u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0438\u043D\u043F\u0443\u0442\u0430 \u0441\u0435\u0441\u0441\u0438\u0438: \u043D\u0430\u0431\u0440\u0430\u043D\u043D\u044B\u0439, \u043D\u043E \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0439 \u0442\u0435\u043A\u0441\u0442 (HTML
819
+ -- \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0430 PromptInput) \u0438 \u043C\u0435\u0442\u0430\u0434\u0430\u043D\u043D\u044B\u0435 \u0435\u0433\u043E \u0447\u0438\u043F\u043E\u0432. \u041A\u043B\u044E\u0447 \u2014 terminal_id,
820
+ -- \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043F\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F (issue #921). \u0421\u0442\u0440\u043E\u043A\u0430
821
+ -- \u0436\u0438\u0432\u0451\u0442 \u0440\u043E\u0432\u043D\u043E \u0441\u0442\u043E\u043B\u044C\u043A\u043E \u0436\u0435, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0442\u0440\u043E\u043A\u0430 \u0441\u0435\u0441\u0441\u0438\u0438: \u0435\u0451 \u0443\u0434\u0430\u043B\u044F\u044E\u0442 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441
822
+ -- \u0441\u0435\u0441\u0441\u0438\u0435\u0439 (deleteSessionById \u0438 \u0441\u043E\u0441\u0435\u0434\u0438) \u0438 \u043F\u043E\u0434\u0447\u0438\u0449\u0430\u044E\u0442 \u043D\u0430 \u0441\u0442\u0430\u0440\u0442\u0435
823
+ -- (pruneOrphanInputDrafts).
824
+ CREATE TABLE IF NOT EXISTS session_input_drafts (
825
+ terminal_id TEXT PRIMARY KEY,
826
+ html TEXT NOT NULL,
827
+ attachments TEXT NOT NULL DEFAULT '[]',
828
+ updated_at INTEGER NOT NULL
829
+ );
830
+
759
831
  CREATE TABLE IF NOT EXISTS worktree_notes (
760
832
  worktree_path TEXT PRIMARY KEY,
761
833
  body TEXT NOT NULL,
762
834
  updated_at INTEGER NOT NULL
763
835
  );
764
836
 
837
+ -- \u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0440\u0430\u0431\u043E\u0442\u044B \u0432 \u0432\u043E\u0440\u043A\u0442\u0440\u0438: \u0433\u0434\u0435 \u043C\u044B \u0432 \u0446\u0435\u043F\u043E\u0447\u043A\u0435 \xAB\u043E\u0431\u0441\u0443\u0436\u0434\u0435\u043D\u0438\u0435 \u2192 \u0440\u0435\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u044F \u2192
838
+ -- \u043F\u0440\u0438\u0451\u043C\u043A\u0430\xBB \u0438 \u0447\u0442\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044F \u0434\u0430\u043B\u044C\u0448\u0435. \u0416\u0438\u0432\u0451\u0442 \u043D\u0430 \u0432\u043E\u0440\u043A\u0442\u0440\u0438, \u0430 \u043D\u0435 \u043D\u0430 \u0441\u0435\u0441\u0441\u0438\u0438
839
+ -- (\u0438\u0445 \u0432 \u043E\u0434\u043D\u043E\u0439 \u0432\u043E\u0440\u043A\u0442\u0440\u0438 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E, \u0438 \u043E\u043D\u0438 \u0440\u0430\u0437\u043E\u0448\u043B\u0438\u0441\u044C \u0431\u044B) \u0438 \u043D\u0435 \u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0435
840
+ -- (\u0437\u0430\u0434\u0430\u0447 \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0438\u043B\u0438 \u043D\u0438 \u043E\u0434\u043D\u043E\u0439). \u0423\u043C\u0438\u0440\u0430\u0435\u0442 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0432\u043E\u0440\u043A\u0442\u0440\u0438 \u2014
841
+ -- \u0441\u043C. deleteWorktreeListEntry.
842
+ CREATE TABLE IF NOT EXISTS worktree_flow (
843
+ worktree_path TEXT PRIMARY KEY,
844
+ step TEXT NOT NULL,
845
+ criteria TEXT NOT NULL DEFAULT '[]',
846
+ work_session_id TEXT,
847
+ accept_session_id TEXT,
848
+ updated_at INTEGER NOT NULL
849
+ );
850
+
765
851
  CREATE TABLE IF NOT EXISTS worktree_view_modes (
766
852
  worktree_path TEXT PRIMARY KEY,
767
853
  view_mode TEXT NOT NULL,
@@ -852,6 +938,17 @@ function createSchema() {
852
938
  function migrateSchema(d) {
853
939
  const row = d.prepare("SELECT value FROM schema_meta WHERE key = 'schema_version'").get();
854
940
  const version = row ? parseInt(row.value, 10) : 0;
941
+ {
942
+ const flowCols = d.prepare("PRAGMA table_info(worktree_flow)").all();
943
+ if (flowCols.length > 0) {
944
+ if (!flowCols.some((c) => c.name === "work_session_id")) {
945
+ d.exec("ALTER TABLE worktree_flow ADD COLUMN work_session_id TEXT");
946
+ }
947
+ if (!flowCols.some((c) => c.name === "accept_session_id")) {
948
+ d.exec("ALTER TABLE worktree_flow ADD COLUMN accept_session_id TEXT");
949
+ }
950
+ }
951
+ }
855
952
  if (version < 1) {
856
953
  d.transaction(() => {
857
954
  const projectCols = d.prepare("PRAGMA table_info(projects)").all();
@@ -1608,6 +1705,120 @@ function migrateSchema(d) {
1608
1705
  })();
1609
1706
  logger_default.info("[database] migrated schema to version 37 (session_activity timestamp index)");
1610
1707
  }
1708
+ if (version < 38) {
1709
+ d.transaction(() => {
1710
+ const cols = d.prepare("PRAGMA table_info(quick_commands)").all();
1711
+ if (!cols.some((c) => c.name === "snippet_text")) {
1712
+ d.exec("ALTER TABLE quick_commands ADD COLUMN snippet_text TEXT");
1713
+ }
1714
+ d.prepare(
1715
+ "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '38')"
1716
+ ).run();
1717
+ })();
1718
+ logger_default.info("[database] migrated schema to version 38 (quick command snippets)");
1719
+ }
1720
+ if (version < 39) {
1721
+ d.transaction(() => {
1722
+ const cols = d.prepare("PRAGMA table_info(source_connections)").all();
1723
+ if (cols.length === 0) {
1724
+ d.prepare(
1725
+ "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '39')"
1726
+ ).run();
1727
+ return;
1728
+ }
1729
+ if (!cols.some((c) => c.name === "identity_key")) {
1730
+ d.exec("ALTER TABLE source_connections ADD COLUMN identity_key TEXT NOT NULL DEFAULT ''");
1731
+ }
1732
+ const rows = d.prepare(
1733
+ `SELECT c.id, c.connector_id, c.filters, c.created_at, c.sync_cursor,
1734
+ c.status_mapping, c.sync_interval_minutes, c.execution_project,
1735
+ (SELECT count(*) FROM task_source_links l WHERE l.connection_id = c.id) AS link_count
1736
+ FROM source_connections c`
1737
+ ).all();
1738
+ const setIdentity = d.prepare("UPDATE source_connections SET identity_key = ? WHERE id = ?");
1739
+ const setFilters = d.prepare("UPDATE source_connections SET filters = ? WHERE id = ?");
1740
+ const groups = /* @__PURE__ */ new Map();
1741
+ for (const row2 of rows) {
1742
+ let filters;
1743
+ try {
1744
+ filters = JSON.parse(row2.filters);
1745
+ } catch {
1746
+ filters = {};
1747
+ }
1748
+ if (row2.connector_id === "github" && "state" in filters) {
1749
+ delete filters.state;
1750
+ setFilters.run(JSON.stringify(filters), row2.id);
1751
+ }
1752
+ const key = connectionIdentityKey(row2.connector_id, filters);
1753
+ setIdentity.run(key, row2.id);
1754
+ if (!key) continue;
1755
+ const bucket = groups.get(key) ?? [];
1756
+ bucket.push({ ...row2, parsedFilters: filters });
1757
+ groups.set(key, bucket);
1758
+ }
1759
+ const relinkLinks = d.prepare(
1760
+ "UPDATE OR IGNORE task_source_links SET connection_id = ? WHERE connection_id = ?"
1761
+ );
1762
+ const relinkHistory = d.prepare(
1763
+ "UPDATE sync_history SET connection_id = ? WHERE connection_id = ?"
1764
+ );
1765
+ const adoptSettings = d.prepare(
1766
+ `UPDATE source_connections
1767
+ SET filters = ?, status_mapping = ?, sync_interval_minutes = ?,
1768
+ execution_project = ?, sync_cursor = ?
1769
+ WHERE id = ?`
1770
+ );
1771
+ const dropConn = d.prepare("DELETE FROM source_connections WHERE id = ?");
1772
+ let collapsed = 0;
1773
+ let strandedLinks = 0;
1774
+ for (const bucket of groups.values()) {
1775
+ if (bucket.length < 2) continue;
1776
+ const byWeight = [...bucket].sort(
1777
+ (a, b) => b.link_count - a.link_count || a.created_at.localeCompare(b.created_at)
1778
+ );
1779
+ const keep = byWeight[0];
1780
+ const dupes = byWeight.slice(1);
1781
+ const newest = [...bucket].sort((a, b) => b.created_at.localeCompare(a.created_at))[0];
1782
+ const cursors = bucket.map((c) => c.sync_cursor).filter((c) => !!c);
1783
+ const earliest = cursors.length > 0 ? cursors.sort()[0] : null;
1784
+ for (const dupe of dupes) {
1785
+ const moved = Number(relinkLinks.run(keep.id, dupe.id).changes);
1786
+ strandedLinks += dupe.link_count - moved;
1787
+ relinkHistory.run(keep.id, dupe.id);
1788
+ dropConn.run(dupe.id);
1789
+ collapsed++;
1790
+ }
1791
+ adoptSettings.run(
1792
+ JSON.stringify(newest.parsedFilters),
1793
+ newest.status_mapping,
1794
+ newest.sync_interval_minutes,
1795
+ newest.execution_project,
1796
+ earliest,
1797
+ keep.id
1798
+ );
1799
+ }
1800
+ const catchUpFrom = new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString();
1801
+ d.prepare(
1802
+ `UPDATE source_connections
1803
+ SET sync_cursor = CASE WHEN created_at > ? THEN created_at ELSE ? END
1804
+ WHERE sync_cursor IS NOT NULL AND sync_cursor > ?`
1805
+ ).run(catchUpFrom, catchUpFrom, catchUpFrom);
1806
+ d.exec(`
1807
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_source_connections_identity
1808
+ ON source_connections(identity_key)
1809
+ WHERE identity_key <> ''
1810
+ `);
1811
+ d.prepare(
1812
+ "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '39')"
1813
+ ).run();
1814
+ if (collapsed > 0) {
1815
+ logger_default.warn(
1816
+ `[database] collapsed ${collapsed} duplicate source connection(s)` + (strandedLinks > 0 ? `; ${strandedLinks} duplicate task link(s) dropped` : "")
1817
+ );
1818
+ }
1819
+ })();
1820
+ logger_default.info("[database] migrated schema to version 39 (source_connections.identity_key)");
1821
+ }
1611
1822
  }
1612
1823
  function verifySchema(d) {
1613
1824
  const expectedTables = [
@@ -1634,7 +1845,8 @@ function verifySchema(d) {
1634
1845
  last_sync_at TEXT,
1635
1846
  last_sync_error TEXT,
1636
1847
  sync_cursor TEXT,
1637
- created_at TEXT NOT NULL
1848
+ created_at TEXT NOT NULL,
1849
+ identity_key TEXT NOT NULL DEFAULT ''
1638
1850
  )`
1639
1851
  },
1640
1852
  {
@@ -1767,6 +1979,10 @@ function verifySchema(d) {
1767
1979
  {
1768
1980
  column: "source_external_id",
1769
1981
  ddl: "ALTER TABLE tasks ADD COLUMN source_external_id TEXT"
1982
+ },
1983
+ {
1984
+ column: "worktree_path",
1985
+ ddl: "ALTER TABLE tasks ADD COLUMN worktree_path TEXT"
1770
1986
  }
1771
1987
  ],
1772
1988
  task_source_links: [
@@ -1804,8 +2020,17 @@ function verifySchema(d) {
1804
2020
  ddl: "ALTER TABLE external_worktree_settings ADD COLUMN hidden_paths TEXT NOT NULL DEFAULT '[]'"
1805
2021
  }
1806
2022
  ],
1807
- quick_commands: [{ column: "shell", ddl: "ALTER TABLE quick_commands ADD COLUMN shell TEXT" }],
1808
- account: [{ column: "member_since", ddl: "ALTER TABLE account ADD COLUMN member_since TEXT" }]
2023
+ quick_commands: [
2024
+ { column: "shell", ddl: "ALTER TABLE quick_commands ADD COLUMN shell TEXT" },
2025
+ { column: "snippet_text", ddl: "ALTER TABLE quick_commands ADD COLUMN snippet_text TEXT" }
2026
+ ],
2027
+ account: [{ column: "member_since", ddl: "ALTER TABLE account ADD COLUMN member_since TEXT" }],
2028
+ source_connections: [
2029
+ {
2030
+ column: "identity_key",
2031
+ ddl: "ALTER TABLE source_connections ADD COLUMN identity_key TEXT NOT NULL DEFAULT ''"
2032
+ }
2033
+ ]
1809
2034
  };
1810
2035
  for (const [table, columns] of Object.entries(expectedByTable)) {
1811
2036
  const existing = new Set(
@@ -1821,6 +2046,15 @@ function verifySchema(d) {
1821
2046
  }
1822
2047
  }
1823
2048
  }
2049
+ try {
2050
+ d.exec(`
2051
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_source_connections_identity
2052
+ ON source_connections(identity_key)
2053
+ WHERE identity_key <> ''
2054
+ `);
2055
+ } catch (err) {
2056
+ logger_default.error({ err }, "[database] self-heal: failed to ensure source connection identity index");
2057
+ }
1824
2058
  }
1825
2059
  function loadConfig() {
1826
2060
  const d = getDb();
@@ -1876,6 +2110,7 @@ var DEFAULTS_KEYS_MAP = {
1876
2110
  claudeDefaultEffort: true,
1877
2111
  codexDefaultModel: true,
1878
2112
  codexDefaultEffort: true,
2113
+ geminiDefaultModel: true,
1879
2114
  language: true,
1880
2115
  hasSeededDefaultTaskWorkflow: true,
1881
2116
  defaultSpinnerVariant: true,
@@ -1892,6 +2127,7 @@ var DEFAULTS_KEYS_MAP = {
1892
2127
  reviewClaudeModel: true,
1893
2128
  reviewCodexModel: true,
1894
2129
  reviewCodexEffort: true,
2130
+ reviewGeminiModel: true,
1895
2131
  aiCommitModel: true,
1896
2132
  aiCommitThinkingLevel: true,
1897
2133
  aiCommitCustomPrompt: true,
@@ -1975,7 +2211,8 @@ function loadQuickCommands(d) {
1975
2211
  model: r.model ?? void 0,
1976
2212
  command: r.command ?? void 0,
1977
2213
  appendEnter: r.append_enter !== 0,
1978
- shell: r.shell ?? void 0
2214
+ shell: r.shell ?? void 0,
2215
+ text: r.snippet_text ?? void 0
1979
2216
  }));
1980
2217
  return normalizeQuickCommands(raw);
1981
2218
  }
@@ -2084,8 +2321,8 @@ function saveConfig(config) {
2084
2321
  }
2085
2322
  d.prepare("DELETE FROM quick_commands").run();
2086
2323
  const insertQuickCommand = d.prepare(
2087
- `INSERT INTO quick_commands (id, label, kind, scope, agent_type, prompt, model, command, append_enter, position, shell)
2088
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
2324
+ `INSERT INTO quick_commands (id, label, kind, scope, agent_type, prompt, model, command, append_enter, position, shell, snippet_text)
2325
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
2089
2326
  );
2090
2327
  const normalizedQuickCommands = normalizeQuickCommands(config.quickCommands ?? []);
2091
2328
  normalizedQuickCommands.forEach((q, index) => {
@@ -2100,7 +2337,8 @@ function saveConfig(config) {
2100
2337
  q.kind === "terminal" ? q.command : null,
2101
2338
  q.kind === "terminal" && q.appendEnter ? 1 : 0,
2102
2339
  index,
2103
- q.kind === "terminal" ? q.shell ?? null : null
2340
+ q.kind === "terminal" ? q.shell ?? null : null,
2341
+ q.kind === "snippet" ? q.text : null
2104
2342
  );
2105
2343
  });
2106
2344
  d.prepare("DELETE FROM agent_commands").run();
@@ -2139,17 +2377,17 @@ function saveConfig(config) {
2139
2377
  h.sshOptions ?? null
2140
2378
  );
2141
2379
  }
2142
- const nextTaskIds = new Set((config.tasks ?? []).map((t) => t.id));
2143
- const existingTaskIds = d.prepare("SELECT id FROM tasks").all().map(
2144
- (r) => r.id
2145
- );
2146
- const deleteTask = d.prepare("DELETE FROM tasks WHERE id = ?");
2147
- for (const id of existingTaskIds) {
2148
- if (!nextTaskIds.has(id)) deleteTask.run(id);
2380
+ if (config.tasks) {
2381
+ const nextTaskIds = new Set(config.tasks.map((t) => t.id));
2382
+ const existingTaskIds = d.prepare("SELECT id FROM tasks").all().map((r) => r.id);
2383
+ const deleteTask = d.prepare("DELETE FROM tasks WHERE id = ?");
2384
+ for (const id of existingTaskIds) {
2385
+ if (!nextTaskIds.has(id)) deleteTask.run(id);
2386
+ }
2149
2387
  }
2150
2388
  const insertTask = d.prepare(
2151
- `INSERT INTO tasks (id, project_name, title, description, status, "order", assigned_session_id, assigned_agent, agent_session_id, branch, use_worktree, created_at, updated_at, completed_at, source_connector_id, source_external_url, source_external_id)
2152
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2389
+ `INSERT INTO tasks (id, project_name, title, description, status, "order", assigned_session_id, assigned_agent, agent_session_id, branch, use_worktree, worktree_path, created_at, updated_at, completed_at, source_connector_id, source_external_url, source_external_id)
2390
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2153
2391
  ON CONFLICT(id) DO UPDATE SET
2154
2392
  project_name = excluded.project_name,
2155
2393
  title = excluded.title,
@@ -2161,6 +2399,7 @@ function saveConfig(config) {
2161
2399
  agent_session_id = excluded.agent_session_id,
2162
2400
  branch = excluded.branch,
2163
2401
  use_worktree = excluded.use_worktree,
2402
+ worktree_path = excluded.worktree_path,
2164
2403
  created_at = excluded.created_at,
2165
2404
  updated_at = excluded.updated_at,
2166
2405
  completed_at = excluded.completed_at,
@@ -2181,6 +2420,7 @@ function saveConfig(config) {
2181
2420
  t.agentSessionId ?? null,
2182
2421
  t.branch ?? null,
2183
2422
  t.useWorktree ? 1 : 0,
2423
+ t.worktreePath ?? null,
2184
2424
  t.createdAt,
2185
2425
  t.updatedAt,
2186
2426
  t.completedAt ?? null,
@@ -2223,8 +2463,8 @@ function dbGetTask(id) {
2223
2463
  }
2224
2464
  function dbInsertTask(task) {
2225
2465
  getDb().prepare(
2226
- `INSERT INTO tasks (id, project_name, title, description, status, "order", assigned_session_id, assigned_agent, agent_session_id, branch, use_worktree, created_at, updated_at, completed_at, source_connector_id, source_external_url, source_external_id)
2227
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
2466
+ `INSERT INTO tasks (id, project_name, title, description, status, "order", assigned_session_id, assigned_agent, agent_session_id, branch, use_worktree, worktree_path, created_at, updated_at, completed_at, source_connector_id, source_external_url, source_external_id)
2467
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
2228
2468
  ).run(
2229
2469
  task.id,
2230
2470
  task.projectName,
@@ -2237,6 +2477,7 @@ function dbInsertTask(task) {
2237
2477
  task.agentSessionId ?? null,
2238
2478
  task.branch ?? null,
2239
2479
  task.useWorktree ? 1 : 0,
2480
+ task.worktreePath ?? null,
2240
2481
  task.createdAt,
2241
2482
  task.updatedAt,
2242
2483
  task.completedAt ?? null,
@@ -2259,6 +2500,7 @@ function dbUpdateTask(id, updates) {
2259
2500
  addSet(sets, params, "status", updates.status);
2260
2501
  addSet(sets, params, '"order"', updates.order);
2261
2502
  addSet(sets, params, "branch", updates.branch);
2503
+ addSet(sets, params, "worktree_path", updates.worktreePath);
2262
2504
  if (updates.useWorktree !== void 0) {
2263
2505
  sets.push("use_worktree = ?");
2264
2506
  params.push(updates.useWorktree ? 1 : 0);
@@ -2473,6 +2715,7 @@ function rowToTask(r) {
2473
2715
  ...r.agent_session_id != null && { agentSessionId: r.agent_session_id },
2474
2716
  ...r.branch != null && { branch: r.branch },
2475
2717
  ...r.use_worktree != null && r.use_worktree !== 0 && { useWorktree: true },
2718
+ ...r.worktree_path != null && { worktreePath: r.worktree_path },
2476
2719
  createdAt: r.created_at,
2477
2720
  updatedAt: r.updated_at,
2478
2721
  ...r.completed_at != null && { completedAt: r.completed_at },
@@ -2749,9 +2992,40 @@ var ConfigManager = class {
2749
2992
  }
2750
2993
  return seeded;
2751
2994
  }
2995
+ /** Авторитетный список задач читаем прямо из БД, минуя `cachedConfig`:
2996
+ * задачи пишутся и мимо ConfigManager (backfill коннектора, MCP
2997
+ * stdio-процесс), а кэш инвалидируется только собственными
2998
+ * saveConfig/notifyChanged. Сохранение поверх тёплого кэша означало бы
2999
+ * «удалить всё, что появилось после последней записи».
3000
+ *
3001
+ * Возвращает null, если чтение упало: подставлять сюда fallback из
3002
+ * loadConfig() нельзя — он отдаёт `tasks: []`, что для dbSaveConfig
3003
+ * равносильно «удалить все задачи». */
3004
+ readPersistedTasks() {
3005
+ try {
3006
+ return loadConfig().tasks ?? [];
3007
+ } catch (err) {
3008
+ logger_default.error({ err }, "[config-manager] failed to read tasks before save");
3009
+ return null;
3010
+ }
3011
+ }
2752
3012
  saveConfig(config) {
2753
3013
  try {
2754
- saveConfig(config);
3014
+ const mutation = config.__taskMutation;
3015
+ const persisted = { ...config };
3016
+ delete persisted.__taskMutation;
3017
+ delete config.__taskMutation;
3018
+ const currentTasks = this.readPersistedTasks();
3019
+ if (currentTasks === null) {
3020
+ delete persisted.tasks;
3021
+ } else {
3022
+ const tasks = applyTaskConfigMutation(currentTasks, mutation);
3023
+ if (config.tasks !== void 0 || currentTasks.length || mutation) {
3024
+ persisted.tasks = tasks;
3025
+ config.tasks = tasks;
3026
+ }
3027
+ }
3028
+ saveConfig(persisted);
2755
3029
  this.cachedConfig = null;
2756
3030
  } catch (err) {
2757
3031
  logger_default.error({ err }, "[config-manager] saveConfig failed");
@@ -4081,10 +4355,12 @@ function registerWorkspaceTools(server) {
4081
4355
 
4082
4356
  // src/tools/ask-user-question.ts
4083
4357
  import { z as z7 } from "zod";
4084
- var ASK_TIMEOUT_MS = 60 * 60 * 1e3;
4085
4358
  var questionSchema = z7.object({
4086
4359
  question: z7.string().min(1).max(1e3).describe("The question to ask the user."),
4087
- header: z7.string().max(40).optional().describe("Very short label/chip shown above the question (max 40 chars)."),
4360
+ // 12 символов потолок «чипа» в карточке (как у встроенного
4361
+ // AskUserQuestion). Обрезаем, а не отвергаем: завалить схему из-за косметики
4362
+ // значит не задать вопрос вовсе, и модель узнает об этом только по ошибке.
4363
+ header: z7.string().max(200).optional().transform((value) => value?.slice(0, 12)).describe("Very short label/chip shown above the question (kept to 12 chars)."),
4088
4364
  multiSelect: z7.boolean().optional().describe("Allow multiple options to be selected. Defaults to single-select."),
4089
4365
  options: z7.array(z7.string().min(1).max(200)).min(2).max(4).describe(
4090
4366
  'Available choices (2\u20134). Do NOT include "Other" yourself \u2014 the UI adds it automatically when supported.'
@@ -4131,7 +4407,7 @@ function registerAskUserQuestionTool(server) {
4131
4407
  const res = await rpcCall(
4132
4408
  "mcp:askUserQuestion",
4133
4409
  { terminalId, questions: normalized },
4134
- { timeoutMs: ASK_TIMEOUT_MS }
4410
+ { timeoutMs: MCP_ASK_TIMEOUT_MS }
4135
4411
  );
4136
4412
  if (res.skipped || !res.answers) {
4137
4413
  return {
@@ -4564,6 +4840,74 @@ function registerEmulatorTools(server) {
4564
4840
  );
4565
4841
  }
4566
4842
 
4843
+ // src/tools/flow.ts
4844
+ import { z as z9 } from "zod";
4845
+ function registerFlowTools(server) {
4846
+ server.tool(
4847
+ "set_acceptance_criteria",
4848
+ "Record the acceptance criteria for the work in this worktree: what a person will do and what they should see. Call this at the end of a discussion, before any code is written \u2014 the criteria are handed to the session that implements the work and to the one that accepts it. Replaces the list you recorded before, but never the criteria the human added by hand. Requires the BaseGrid app to be running.",
4849
+ {
4850
+ worktree_path: V.absolutePath.describe(
4851
+ "Absolute path of the worktree you are working in (your cwd)"
4852
+ ),
4853
+ criteria: z9.array(
4854
+ z9.object({
4855
+ text: V.title.describe("One checkable criterion, phrased as a user would"),
4856
+ task_id: V.id.optional().describe("Task this criterion comes from, if any")
4857
+ })
4858
+ ).min(1).max(30).describe("The full list \u2014 it replaces whatever was recorded before")
4859
+ },
4860
+ async (args) => {
4861
+ const flow = await rpcCall("worktree:flow:setCriteria", {
4862
+ cwd: args.worktree_path,
4863
+ // Пункты, дописанные человеком в полосе, вызов не трогает.
4864
+ fromAgent: true,
4865
+ criteria: args.criteria.map((c) => ({
4866
+ text: c.text,
4867
+ ...c.task_id ? { taskId: c.task_id } : {}
4868
+ }))
4869
+ });
4870
+ return { content: [{ type: "text", text: JSON.stringify(flow, null, 2) }] };
4871
+ }
4872
+ );
4873
+ server.tool(
4874
+ "set_criterion_status",
4875
+ "Record the verdict for ONE acceptance criterion during an acceptance pass, by its 1-based number as shown in the list. Only the acceptance tab may record verdicts \u2014 from the work tab the call is rejected. Call it as soon as you close a criterion, not in a batch at the end: the bar above the tabs is the scoreboard the human watches while you work. A verdict left only in your reply dies with the session \u2014 this one survives it. Requires the BaseGrid app to be running.",
4876
+ {
4877
+ worktree_path: V.absolutePath.describe(
4878
+ "Absolute path of the worktree you are working in (your cwd)"
4879
+ ),
4880
+ index: z9.number().int().min(1).max(30).describe("1-based position of the criterion in the recorded list"),
4881
+ status: z9.enum(["passed", "failed"]).describe("passed only when you actually ran it \u2014 never for something you only read"),
4882
+ evidence: V.title.optional().describe("What closed it: the command you ran, the file, the behaviour you saw")
4883
+ },
4884
+ async (args) => {
4885
+ const terminalId = process.env.BASEGRID_TERMINAL_ID;
4886
+ const flow = await rpcCall("worktree:flow:setCriterionStatus", {
4887
+ cwd: args.worktree_path,
4888
+ index: args.index,
4889
+ status: args.status,
4890
+ ...args.evidence ? { evidence: args.evidence } : {},
4891
+ ...terminalId ? { terminalId } : {}
4892
+ });
4893
+ return { content: [{ type: "text", text: JSON.stringify(flow, null, 2) }] };
4894
+ }
4895
+ );
4896
+ server.tool(
4897
+ "get_acceptance_criteria",
4898
+ "Read the acceptance criteria and current step recorded for a worktree. Returns null when nothing has been recorded yet.",
4899
+ {
4900
+ worktree_path: V.absolutePath.describe("Absolute path of the worktree (your cwd)")
4901
+ },
4902
+ async (args) => {
4903
+ const flow = await rpcCall("worktree:flow:get", {
4904
+ cwd: args.worktree_path
4905
+ });
4906
+ return { content: [{ type: "text", text: JSON.stringify(flow, null, 2) }] };
4907
+ }
4908
+ );
4909
+ }
4910
+
4567
4911
  // src/server.ts
4568
4912
  function createMcpServer(version) {
4569
4913
  const server = new McpServer({ name: "basegrid", version }, { capabilities: { tools: {} } });
@@ -4575,6 +4919,7 @@ function createMcpServer(version) {
4575
4919
  registerWorkspaceTools(server);
4576
4920
  registerAskUserQuestionTool(server);
4577
4921
  registerEmulatorTools(server);
4922
+ registerFlowTools(server);
4578
4923
  return server;
4579
4924
  }
4580
4925
 
@@ -4587,7 +4932,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
4587
4932
  console.error = (...args) => _origError("[mcp:error]", ...args);
4588
4933
  async function main() {
4589
4934
  configManager.init();
4590
- const version = true ? "0.18.12" : createRequire(import.meta.url)("../package.json").version;
4935
+ const version = true ? "0.18.14-beta.1" : createRequire(import.meta.url)("../package.json").version;
4591
4936
  const server = createMcpServer(version);
4592
4937
  const transport = new StdioServerTransport();
4593
4938
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basegrid_tech/mcp",
3
- "version": "0.18.12",
3
+ "version": "0.18.14-beta.1",
4
4
  "description": "BaseGrid MCP server — task management, git, and workflow tools for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,17 +35,17 @@
35
35
  "dev": "tsx src/index.ts"
36
36
  },
37
37
  "dependencies": {
38
- "@modelcontextprotocol/sdk": "^1.29.0",
39
- "libsql": "^0.5.29",
40
- "pino": "^10.3.1",
41
- "ws": "^8.20.0",
42
- "zod": "^4.3.6"
38
+ "@modelcontextprotocol/sdk": "1.29.0",
39
+ "libsql": "0.5.29",
40
+ "pino": "10.3.1",
41
+ "ws": "8.21.0",
42
+ "zod": "4.3.6"
43
43
  },
44
44
  "devDependencies": {
45
- "@basegrid/server": "0.18.12",
46
- "@basegrid/shared": "0.18.12",
47
- "tsup": "^8.5.1",
48
- "tsx": "^4.21.0",
49
- "typescript": "^6.0.3"
45
+ "@basegrid/server": "workspace:*",
46
+ "@basegrid/shared": "workspace:*",
47
+ "tsup": "8.5.1",
48
+ "tsx": "4.21.0",
49
+ "typescript": "6.0.3"
50
50
  }
51
- }
51
+ }