@vornrun/mcp 0.7.0-beta.14 → 0.7.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +425 -75
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -50,8 +50,11 @@ import fs from "fs";
|
|
|
50
50
|
import path from "path";
|
|
51
51
|
|
|
52
52
|
// ../shared/src/protocol.ts
|
|
53
|
+
var CLOSE_UNAUTHENTICATED = 4001;
|
|
54
|
+
var CLOSE_CREDENTIAL_REJECTED = 4002;
|
|
53
55
|
var BOOTSTRAP_ENV_VAR = "SECRET_VORN_BOOTSTRAP_TOKEN";
|
|
54
56
|
var LOCAL_TOKEN_FILENAME = "local-token";
|
|
57
|
+
var WS_PORT_FILENAME = "ws-port";
|
|
55
58
|
|
|
56
59
|
// ../shared/src/types.ts
|
|
57
60
|
var DEFAULT_WORKSPACE = {
|
|
@@ -106,6 +109,7 @@ var STRIP_ENV_KEYS_UPPER = STRIP_ENV_KEYS.map((k) => k.toUpperCase());
|
|
|
106
109
|
|
|
107
110
|
// ../server/src/default-workflows.ts
|
|
108
111
|
var DEFAULT_TASK_WORKFLOW_ID = "system:default-task-workflow";
|
|
112
|
+
var DEV_SERVER_WORKFLOW_ID = "system:dev-server-on-restore";
|
|
109
113
|
function buildDefaultTaskWorkflow() {
|
|
110
114
|
const triggerConfig = {
|
|
111
115
|
triggerType: "taskStatusChanged",
|
|
@@ -145,6 +149,39 @@ function buildDefaultTaskWorkflow() {
|
|
|
145
149
|
edges: [{ id: "e1", source: "trigger-1", target: "launch-1" }]
|
|
146
150
|
};
|
|
147
151
|
}
|
|
152
|
+
function buildDevServerWorkflow() {
|
|
153
|
+
const triggerConfig = { triggerType: "sessionRestored" };
|
|
154
|
+
const scriptConfig = {
|
|
155
|
+
scriptType: "bash",
|
|
156
|
+
scriptContent: "yarn dev",
|
|
157
|
+
cwd: "{{context.projectPath}}"
|
|
158
|
+
};
|
|
159
|
+
return {
|
|
160
|
+
id: DEV_SERVER_WORKFLOW_ID,
|
|
161
|
+
name: "Bring the dev server back",
|
|
162
|
+
icon: "RotateCcw",
|
|
163
|
+
iconColor: "#c9972a",
|
|
164
|
+
enabled: false,
|
|
165
|
+
workspaceId: "personal",
|
|
166
|
+
nodes: [
|
|
167
|
+
{
|
|
168
|
+
id: "trigger-1",
|
|
169
|
+
type: "trigger",
|
|
170
|
+
label: "When a session is restored",
|
|
171
|
+
position: { x: 0, y: 0 },
|
|
172
|
+
config: triggerConfig
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "script-1",
|
|
176
|
+
type: "script",
|
|
177
|
+
label: "Start the dev server",
|
|
178
|
+
position: { x: 0, y: 120 },
|
|
179
|
+
config: scriptConfig
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
edges: [{ id: "e1", source: "trigger-1", target: "script-1" }]
|
|
183
|
+
};
|
|
184
|
+
}
|
|
148
185
|
|
|
149
186
|
// ../server/src/database.ts
|
|
150
187
|
var DEFAULT_DATA_DIR = path2.join(os.homedir(), ".vorn");
|
|
@@ -163,8 +200,8 @@ function getDataDir() {
|
|
|
163
200
|
}
|
|
164
201
|
return resolvedDataDir;
|
|
165
202
|
}
|
|
166
|
-
function initDatabase(
|
|
167
|
-
resolvedDataDir =
|
|
203
|
+
function initDatabase(dataDir2) {
|
|
204
|
+
resolvedDataDir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
168
205
|
if (!fs2.existsSync(getDataDir())) {
|
|
169
206
|
fs2.mkdirSync(getDataDir(), { recursive: true, mode: 448 });
|
|
170
207
|
}
|
|
@@ -189,17 +226,25 @@ function initDatabase(dataDir) {
|
|
|
189
226
|
}
|
|
190
227
|
}
|
|
191
228
|
function seedSystemDefaults() {
|
|
229
|
+
seedWorkflowOnce(
|
|
230
|
+
"hasSeededDefaultTaskWorkflow",
|
|
231
|
+
DEFAULT_TASK_WORKFLOW_ID,
|
|
232
|
+
buildDefaultTaskWorkflow
|
|
233
|
+
);
|
|
234
|
+
seedWorkflowOnce("hasSeededDevServerWorkflow", DEV_SERVER_WORKFLOW_ID, buildDevServerWorkflow);
|
|
235
|
+
}
|
|
236
|
+
function seedWorkflowOnce(flag, id, build) {
|
|
192
237
|
const d = getDb();
|
|
193
|
-
const flagRow = d.prepare("SELECT value FROM defaults WHERE key =
|
|
238
|
+
const flagRow = d.prepare("SELECT value FROM defaults WHERE key = ?").get(flag);
|
|
194
239
|
if (flagRow) {
|
|
195
240
|
try {
|
|
196
241
|
if (JSON.parse(flagRow.value) === true) return;
|
|
197
242
|
} catch {
|
|
198
243
|
}
|
|
199
244
|
}
|
|
200
|
-
const existing = d.prepare("SELECT id FROM workflows WHERE id = ?").get(
|
|
245
|
+
const existing = d.prepare("SELECT id FROM workflows WHERE id = ?").get(id);
|
|
201
246
|
if (!existing) {
|
|
202
|
-
const w =
|
|
247
|
+
const w = build();
|
|
203
248
|
d.prepare(
|
|
204
249
|
`INSERT INTO workflows (id, name, icon, icon_color, nodes, edges, enabled, last_run_at, last_run_status, stagger_delay_ms, workspace_id)
|
|
205
250
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
@@ -216,11 +261,12 @@ function seedSystemDefaults() {
|
|
|
216
261
|
w.staggerDelayMs ?? null,
|
|
217
262
|
w.workspaceId ?? "personal"
|
|
218
263
|
);
|
|
219
|
-
logger_default.info(`[database] Seeded
|
|
264
|
+
logger_default.info(`[database] Seeded workflow ${id}`);
|
|
220
265
|
}
|
|
221
|
-
d.prepare(
|
|
222
|
-
|
|
223
|
-
|
|
266
|
+
d.prepare("INSERT OR REPLACE INTO defaults (key, value) VALUES (?, ?)").run(
|
|
267
|
+
flag,
|
|
268
|
+
JSON.stringify(true)
|
|
269
|
+
);
|
|
224
270
|
}
|
|
225
271
|
function recoverCorruptDatabase() {
|
|
226
272
|
try {
|
|
@@ -393,7 +439,8 @@ function createSchema() {
|
|
|
393
439
|
saved_at INTEGER,
|
|
394
440
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
395
441
|
worktree_name TEXT,
|
|
396
|
-
agent_session_id TEXT
|
|
442
|
+
agent_session_id TEXT,
|
|
443
|
+
renamed_by_person INTEGER
|
|
397
444
|
);
|
|
398
445
|
|
|
399
446
|
CREATE TABLE IF NOT EXISTS schedule_log (
|
|
@@ -417,6 +464,16 @@ function createSchema() {
|
|
|
417
464
|
"order" INTEGER NOT NULL DEFAULT 0
|
|
418
465
|
);
|
|
419
466
|
|
|
467
|
+
CREATE TABLE IF NOT EXISTS session_groups (
|
|
468
|
+
id TEXT PRIMARY KEY,
|
|
469
|
+
name TEXT NOT NULL,
|
|
470
|
+
icon TEXT,
|
|
471
|
+
icon_color TEXT,
|
|
472
|
+
"order" INTEGER NOT NULL DEFAULT 0,
|
|
473
|
+
workspace_id TEXT NOT NULL DEFAULT 'personal',
|
|
474
|
+
row_revision INTEGER NOT NULL DEFAULT 0
|
|
475
|
+
);
|
|
476
|
+
|
|
420
477
|
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
421
478
|
id TEXT PRIMARY KEY,
|
|
422
479
|
workflow_id TEXT NOT NULL,
|
|
@@ -937,11 +994,24 @@ function migrateSchema(d) {
|
|
|
937
994
|
})();
|
|
938
995
|
logger_default.info("[database] migrated schema to version 17 (packaged connector task ids)");
|
|
939
996
|
}
|
|
997
|
+
if (version < 18) {
|
|
998
|
+
d.transaction(() => {
|
|
999
|
+
const sessionCols = d.prepare("PRAGMA table_info(sessions)").all();
|
|
1000
|
+
if (!sessionCols.some((c) => c.name === "group_id")) {
|
|
1001
|
+
d.exec("ALTER TABLE sessions ADD COLUMN group_id TEXT");
|
|
1002
|
+
}
|
|
1003
|
+
d.prepare(
|
|
1004
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '18')"
|
|
1005
|
+
).run();
|
|
1006
|
+
})();
|
|
1007
|
+
logger_default.info("[database] migrated schema to version 18 (session groups)");
|
|
1008
|
+
}
|
|
940
1009
|
}
|
|
941
1010
|
var REVISIONED_TABLES = [
|
|
942
1011
|
"projects",
|
|
943
1012
|
"tasks",
|
|
944
1013
|
"workspaces",
|
|
1014
|
+
"session_groups",
|
|
945
1015
|
"remote_hosts",
|
|
946
1016
|
"agent_commands"
|
|
947
1017
|
];
|
|
@@ -978,10 +1048,15 @@ function verifySchema(d) {
|
|
|
978
1048
|
column: "sort_order",
|
|
979
1049
|
ddl: "ALTER TABLE sessions ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"
|
|
980
1050
|
},
|
|
1051
|
+
{ column: "group_id", ddl: "ALTER TABLE sessions ADD COLUMN group_id TEXT" },
|
|
981
1052
|
{ column: "worktree_name", ddl: "ALTER TABLE sessions ADD COLUMN worktree_name TEXT" },
|
|
982
1053
|
{ column: "agent_session_id", ddl: "ALTER TABLE sessions ADD COLUMN agent_session_id TEXT" },
|
|
983
1054
|
{ column: "shell_cwd", ddl: "ALTER TABLE sessions ADD COLUMN shell_cwd TEXT" },
|
|
984
|
-
{ column: "head_commit", ddl: "ALTER TABLE sessions ADD COLUMN head_commit TEXT" }
|
|
1055
|
+
{ column: "head_commit", ddl: "ALTER TABLE sessions ADD COLUMN head_commit TEXT" },
|
|
1056
|
+
{
|
|
1057
|
+
column: "renamed_by_person",
|
|
1058
|
+
ddl: "ALTER TABLE sessions ADD COLUMN renamed_by_person INTEGER"
|
|
1059
|
+
}
|
|
985
1060
|
],
|
|
986
1061
|
agent_commands: [
|
|
987
1062
|
{
|
|
@@ -1094,6 +1169,7 @@ function loadConfig() {
|
|
|
1094
1169
|
const remoteHosts = loadRemoteHosts(d);
|
|
1095
1170
|
const tasks = loadTasks(d);
|
|
1096
1171
|
const workspaces = loadWorkspaces(d);
|
|
1172
|
+
const sessionGroups = loadSessionGroups(d);
|
|
1097
1173
|
return {
|
|
1098
1174
|
version: 1,
|
|
1099
1175
|
revision: readConfigRevision(d),
|
|
@@ -1103,7 +1179,8 @@ function loadConfig() {
|
|
|
1103
1179
|
workflows,
|
|
1104
1180
|
remoteHosts,
|
|
1105
1181
|
tasks,
|
|
1106
|
-
workspaces
|
|
1182
|
+
workspaces,
|
|
1183
|
+
sessionGroups
|
|
1107
1184
|
};
|
|
1108
1185
|
}
|
|
1109
1186
|
function loadDefaults(d) {
|
|
@@ -1190,6 +1267,9 @@ function loadDefaults(d) {
|
|
|
1190
1267
|
...map.headlessRetentionMinutes !== void 0 && {
|
|
1191
1268
|
headlessRetentionMinutes: map.headlessRetentionMinutes
|
|
1192
1269
|
},
|
|
1270
|
+
...map.hasSeededDevServerWorkflow !== void 0 && {
|
|
1271
|
+
hasSeededDevServerWorkflow: map.hasSeededDevServerWorkflow
|
|
1272
|
+
},
|
|
1193
1273
|
...map.hasSeededDefaultTaskWorkflow !== void 0 && {
|
|
1194
1274
|
hasSeededDefaultTaskWorkflow: map.hasSeededDefaultTaskWorkflow
|
|
1195
1275
|
},
|
|
@@ -1253,6 +1333,10 @@ function loadTasks(d) {
|
|
|
1253
1333
|
const rows = d.prepare('SELECT * FROM tasks ORDER BY "order"').all();
|
|
1254
1334
|
return rows.map(rowToTask);
|
|
1255
1335
|
}
|
|
1336
|
+
function loadSessionGroups(d) {
|
|
1337
|
+
const rows = d.prepare('SELECT * FROM session_groups ORDER BY "order"').all();
|
|
1338
|
+
return rows.map(rowToSessionGroup);
|
|
1339
|
+
}
|
|
1256
1340
|
function loadWorkspaces(d) {
|
|
1257
1341
|
const rows = d.prepare('SELECT * FROM workspaces ORDER BY "order"').all();
|
|
1258
1342
|
return rows.map(rowToWorkspace);
|
|
@@ -1492,6 +1576,36 @@ function saveConfig(config) {
|
|
|
1492
1576
|
for (const ws of workspaces) {
|
|
1493
1577
|
insertWorkspace.run(ws.id, ws.name, ws.icon ?? null, ws.iconColor ?? null, ws.order, revision);
|
|
1494
1578
|
}
|
|
1579
|
+
const sessionGroups = config.sessionGroups ?? [];
|
|
1580
|
+
pruneMissing(
|
|
1581
|
+
d,
|
|
1582
|
+
"session_groups",
|
|
1583
|
+
"id",
|
|
1584
|
+
sessionGroups.map((g) => g.id),
|
|
1585
|
+
baseRevision
|
|
1586
|
+
);
|
|
1587
|
+
const insertSessionGroup = d.prepare(
|
|
1588
|
+
`INSERT INTO session_groups (id, name, icon, icon_color, "order", workspace_id, row_revision)
|
|
1589
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1590
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1591
|
+
row_revision = excluded.row_revision,
|
|
1592
|
+
name = excluded.name,
|
|
1593
|
+
icon = excluded.icon,
|
|
1594
|
+
icon_color = excluded.icon_color,
|
|
1595
|
+
"order" = excluded."order",
|
|
1596
|
+
workspace_id = excluded.workspace_id`
|
|
1597
|
+
);
|
|
1598
|
+
for (const g of sessionGroups) {
|
|
1599
|
+
insertSessionGroup.run(
|
|
1600
|
+
g.id,
|
|
1601
|
+
g.name,
|
|
1602
|
+
g.icon ?? null,
|
|
1603
|
+
g.iconColor ?? null,
|
|
1604
|
+
g.order,
|
|
1605
|
+
g.workspaceId,
|
|
1606
|
+
revision
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1495
1609
|
d.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES (?, ?)").run(
|
|
1496
1610
|
CONFIG_REVISION_KEY,
|
|
1497
1611
|
String(revision)
|
|
@@ -1549,6 +1663,16 @@ function rowToWorkflow(r) {
|
|
|
1549
1663
|
workspaceId: r.workspace_id ?? "personal"
|
|
1550
1664
|
};
|
|
1551
1665
|
}
|
|
1666
|
+
function rowToSessionGroup(r) {
|
|
1667
|
+
return {
|
|
1668
|
+
id: r.id,
|
|
1669
|
+
name: r.name,
|
|
1670
|
+
...r.icon != null && { icon: r.icon },
|
|
1671
|
+
...r.icon_color != null && { iconColor: r.icon_color },
|
|
1672
|
+
order: r.order,
|
|
1673
|
+
workspaceId: r.workspace_id ?? "personal"
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1552
1676
|
function rowToWorkspace(r) {
|
|
1553
1677
|
return {
|
|
1554
1678
|
id: r.id,
|
|
@@ -1565,8 +1689,8 @@ var ConfigManager = class {
|
|
|
1565
1689
|
dbWatcher = null;
|
|
1566
1690
|
debounceTimer = null;
|
|
1567
1691
|
cachedConfig = null;
|
|
1568
|
-
init(
|
|
1569
|
-
initDatabase(
|
|
1692
|
+
init(dataDir2) {
|
|
1693
|
+
initDatabase(dataDir2);
|
|
1570
1694
|
}
|
|
1571
1695
|
close() {
|
|
1572
1696
|
this.stopWatchingDb();
|
|
@@ -1684,33 +1808,47 @@ var V = {
|
|
|
1684
1808
|
url: safeUrl
|
|
1685
1809
|
};
|
|
1686
1810
|
|
|
1687
|
-
// src/
|
|
1811
|
+
// ../server/src/rpc-client.ts
|
|
1688
1812
|
import fs4 from "fs";
|
|
1689
1813
|
import path3 from "path";
|
|
1690
1814
|
import os2 from "os";
|
|
1691
1815
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1692
1816
|
import { WebSocket } from "ws";
|
|
1693
|
-
var
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1817
|
+
var dataDirOverride;
|
|
1818
|
+
function named(value) {
|
|
1819
|
+
return value?.trim() ? value : void 0;
|
|
1820
|
+
}
|
|
1821
|
+
function dataDir() {
|
|
1822
|
+
return dataDirOverride ?? named(process.env.VORN_DATA_DIR) ?? path3.join(os2.homedir(), ".vorn");
|
|
1823
|
+
}
|
|
1824
|
+
function portFile() {
|
|
1825
|
+
return path3.join(dataDir(), WS_PORT_FILENAME);
|
|
1826
|
+
}
|
|
1827
|
+
function localTokenFile() {
|
|
1828
|
+
return path3.join(dataDir(), LOCAL_TOKEN_FILENAME);
|
|
1829
|
+
}
|
|
1830
|
+
function tokenFileMissingMessage() {
|
|
1831
|
+
return `Vorn local credential not found (${localTokenFile()}).
|
|
1697
1832
|
The server writes it on startup and removes it on shutdown, so this usually means
|
|
1698
|
-
Vorn is not running. Start Vorn (or \`vorn
|
|
1699
|
-
If the server runs with --data-dir,
|
|
1833
|
+
Vorn is not running. Start Vorn (or \`vorn server serve\`) and try again.
|
|
1834
|
+
If the server runs with --data-dir, pass the same --data-dir here, or set
|
|
1835
|
+
VORN_DATA_DIR to that directory -- which is how anything that is not the CLI,
|
|
1836
|
+
MCP included, reaches a server that moved.`;
|
|
1837
|
+
}
|
|
1700
1838
|
function readLocalToken() {
|
|
1701
1839
|
try {
|
|
1702
|
-
const token = fs4.readFileSync(
|
|
1840
|
+
const token = fs4.readFileSync(localTokenFile(), "utf-8").trim();
|
|
1703
1841
|
if (!token) throw new Error("empty");
|
|
1704
1842
|
return token;
|
|
1705
1843
|
} catch {
|
|
1706
|
-
throw new Error(
|
|
1844
|
+
throw new Error(tokenFileMissingMessage());
|
|
1707
1845
|
}
|
|
1708
1846
|
}
|
|
1709
1847
|
function connection() {
|
|
1710
1848
|
const result = readPort();
|
|
1711
1849
|
if (!result.port) {
|
|
1712
1850
|
const reason = "reason" in result ? result.reason : "missing";
|
|
1713
|
-
throw new Error(reason === "invalid" ?
|
|
1851
|
+
throw new Error(reason === "invalid" ? portFileInvalidMessage() : portFileMissingMessage());
|
|
1714
1852
|
}
|
|
1715
1853
|
return {
|
|
1716
1854
|
url: `ws://127.0.0.1:${result.port}/ws`,
|
|
@@ -1719,21 +1857,42 @@ function connection() {
|
|
|
1719
1857
|
}
|
|
1720
1858
|
var TIMEOUT_MS = 1e4;
|
|
1721
1859
|
var IS_WIN = process.platform === "win32";
|
|
1722
|
-
|
|
1860
|
+
function portFileMissingMessage() {
|
|
1861
|
+
const file = portFile();
|
|
1862
|
+
return IS_WIN ? `Vorn port file not found (${file}).
|
|
1723
1863
|
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
1724
|
-
To fix, find the Vorn process and its listening port:
|
|
1725
|
-
|
|
1864
|
+
To fix, in PowerShell, find the Vorn process and its listening port:
|
|
1865
|
+
Get-NetTCPConnection -State Listen -OwningProcess (Get-Process Vorn).Id | Select LocalPort
|
|
1726
1866
|
Then write the WS port to the file:
|
|
1727
|
-
|
|
1728
|
-
Or restart Vorn to regenerate it.` : `Vorn port file not found (
|
|
1867
|
+
'{"port":<PORT>,"pid":<PID>}' | Set-Content -Path "${file}"
|
|
1868
|
+
Or restart Vorn to regenerate it.` : `Vorn port file not found (${file}).
|
|
1729
1869
|
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
1730
1870
|
To fix, run: lsof -iTCP -sTCP:LISTEN -P | grep Vorn
|
|
1731
1871
|
Then write the WS port (the one on *:<port>) to the file:
|
|
1732
|
-
echo '{"port":<PORT>,"pid":<PID>}' >
|
|
1872
|
+
echo '{"port":<PORT>,"pid":<PID>}' > "${file}"
|
|
1733
1873
|
Or restart Vorn to regenerate it.`;
|
|
1734
|
-
|
|
1874
|
+
}
|
|
1875
|
+
function portFileInvalidMessage() {
|
|
1876
|
+
const file = portFile();
|
|
1877
|
+
return `Vorn port file exists but contains invalid data (${file}).
|
|
1735
1878
|
Delete it and restart Vorn, or overwrite it with the correct port:
|
|
1736
|
-
${IS_WIN ?
|
|
1879
|
+
${IS_WIN ? `Remove-Item "${file}"` : `rm "${file}"`}`;
|
|
1880
|
+
}
|
|
1881
|
+
function closedBeforeAnswering(code) {
|
|
1882
|
+
if (code === CLOSE_UNAUTHENTICATED || code === CLOSE_CREDENTIAL_REJECTED) {
|
|
1883
|
+
return `A Vorn server on this port refused the credential in ${localTokenFile()}.
|
|
1884
|
+
Another server is listening on it with its own data directory, which a dev build
|
|
1885
|
+
running beside the app does. Point at that one with --data-dir (or VORN_DATA_DIR),
|
|
1886
|
+
or stop it.`;
|
|
1887
|
+
}
|
|
1888
|
+
return `The server closed the connection before answering (code ${code}).`;
|
|
1889
|
+
}
|
|
1890
|
+
function explain(message) {
|
|
1891
|
+
if (!message.startsWith("Method not found:")) return message;
|
|
1892
|
+
const method = message.slice("Method not found:".length).trim();
|
|
1893
|
+
return `This server does not have ${method}, so it is older than the vorn command asking for it.
|
|
1894
|
+
Restart Vorn to pick up the newer server, or run this against the matching build.`;
|
|
1895
|
+
}
|
|
1737
1896
|
var rpcId = 0;
|
|
1738
1897
|
var cachedPort = null;
|
|
1739
1898
|
var cacheTimestamp = 0;
|
|
@@ -1786,7 +1945,11 @@ function discoverPort() {
|
|
|
1786
1945
|
}
|
|
1787
1946
|
return null;
|
|
1788
1947
|
}
|
|
1948
|
+
function discoveryAllowed() {
|
|
1949
|
+
return dataDirOverride === void 0 && named(process.env.VORN_DATA_DIR) === void 0;
|
|
1950
|
+
}
|
|
1789
1951
|
function discoverAndHeal() {
|
|
1952
|
+
if (!discoveryAllowed()) return { port: null, reason: "missing" };
|
|
1790
1953
|
const now = Date.now();
|
|
1791
1954
|
if (cachedPort && now - cacheTimestamp < CACHE_TTL_MS) return { port: cachedPort };
|
|
1792
1955
|
const discovered = discoverPort();
|
|
@@ -1794,8 +1957,8 @@ function discoverAndHeal() {
|
|
|
1794
1957
|
cacheTimestamp = now;
|
|
1795
1958
|
if (discovered) {
|
|
1796
1959
|
try {
|
|
1797
|
-
fs4.mkdirSync(
|
|
1798
|
-
fs4.writeFileSync(
|
|
1960
|
+
fs4.mkdirSync(dataDir(), { recursive: true });
|
|
1961
|
+
fs4.writeFileSync(portFile(), JSON.stringify({ port: discovered }), "utf-8");
|
|
1799
1962
|
} catch {
|
|
1800
1963
|
}
|
|
1801
1964
|
return { port: discovered };
|
|
@@ -1804,7 +1967,7 @@ function discoverAndHeal() {
|
|
|
1804
1967
|
}
|
|
1805
1968
|
function readPort() {
|
|
1806
1969
|
try {
|
|
1807
|
-
const raw = fs4.readFileSync(
|
|
1970
|
+
const raw = fs4.readFileSync(portFile(), "utf-8").trim();
|
|
1808
1971
|
if (!raw) return { port: null, reason: "invalid" };
|
|
1809
1972
|
if (raw.startsWith("{")) {
|
|
1810
1973
|
const parsed = JSON.parse(raw);
|
|
@@ -1846,15 +2009,19 @@ async function rpcCall(method, params, timeoutMs = TIMEOUT_MS) {
|
|
|
1846
2009
|
const msg = JSON.parse(raw.toString());
|
|
1847
2010
|
if (msg.id !== id) return;
|
|
1848
2011
|
clearTimeout(timer);
|
|
1849
|
-
ws.close();
|
|
1850
2012
|
if (msg.error) {
|
|
1851
|
-
reject(new Error(msg.error.message));
|
|
2013
|
+
reject(new Error(explain(msg.error.message)));
|
|
1852
2014
|
} else {
|
|
1853
2015
|
resolve(msg.result);
|
|
1854
2016
|
}
|
|
2017
|
+
ws.close();
|
|
1855
2018
|
} catch {
|
|
1856
2019
|
}
|
|
1857
2020
|
});
|
|
2021
|
+
ws.on("close", (code) => {
|
|
2022
|
+
clearTimeout(timer);
|
|
2023
|
+
reject(new Error(closedBeforeAnswering(code)));
|
|
2024
|
+
});
|
|
1858
2025
|
ws.on("error", (err) => {
|
|
1859
2026
|
clearTimeout(timer);
|
|
1860
2027
|
reject(new Error(`Cannot connect to Vorn server: ${err.message}. Is the app running?`));
|
|
@@ -1983,6 +2150,9 @@ async function listWorkflowRunsByTask(taskId, limit = 20) {
|
|
|
1983
2150
|
limit
|
|
1984
2151
|
});
|
|
1985
2152
|
}
|
|
2153
|
+
async function listRunsWithWaitingGates() {
|
|
2154
|
+
return rpcCall("workflowRun:listWaiting");
|
|
2155
|
+
}
|
|
1986
2156
|
async function listAllWorkflowRuns(workspaceId, limit = 50) {
|
|
1987
2157
|
return rpcCall("workflowRun:listAll", {
|
|
1988
2158
|
workspaceId,
|
|
@@ -2793,8 +2963,8 @@ function resolveRequirement(requirement, connections) {
|
|
|
2793
2963
|
);
|
|
2794
2964
|
if (candidates.length === 0) return void 0;
|
|
2795
2965
|
if (requirement.name !== "") {
|
|
2796
|
-
const
|
|
2797
|
-
if (
|
|
2966
|
+
const named2 = candidates.filter((connection2) => connection2.name === requirement.name);
|
|
2967
|
+
if (named2.length === 1) return named2[0].id;
|
|
2798
2968
|
}
|
|
2799
2969
|
return candidates.length === 1 ? candidates[0].id : void 0;
|
|
2800
2970
|
}
|
|
@@ -3208,6 +3378,46 @@ function resolveWorkflowId(args) {
|
|
|
3208
3378
|
}
|
|
3209
3379
|
return { id };
|
|
3210
3380
|
}
|
|
3381
|
+
function approvalNode(workflow, nodeId) {
|
|
3382
|
+
return workflow?.nodes.find((n) => n.id === nodeId && n.type === "approval");
|
|
3383
|
+
}
|
|
3384
|
+
function askedBy(node) {
|
|
3385
|
+
return node?.config?.message?.trim() || void 0;
|
|
3386
|
+
}
|
|
3387
|
+
function gateMessage(workflow, nodeId) {
|
|
3388
|
+
return askedBy(approvalNode(workflow, nodeId));
|
|
3389
|
+
}
|
|
3390
|
+
function annotateWaitingGates(runs, workflows) {
|
|
3391
|
+
return runs.map((run) => {
|
|
3392
|
+
if (!run.nodeStates.some((n) => n.status === "waiting")) return run;
|
|
3393
|
+
const workflow = workflows.find((w) => w.id === run.workflowId);
|
|
3394
|
+
return {
|
|
3395
|
+
...run,
|
|
3396
|
+
nodeStates: run.nodeStates.map((state) => {
|
|
3397
|
+
if (state.status !== "waiting") return state;
|
|
3398
|
+
const asks = gateMessage(workflow, state.nodeId);
|
|
3399
|
+
return asks ? { ...state, asks } : state;
|
|
3400
|
+
})
|
|
3401
|
+
};
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
async function runById(runId) {
|
|
3405
|
+
const recent = (await listAllWorkflowRuns(void 0, 500)).find((r) => r.runId === runId);
|
|
3406
|
+
if (recent) return recent;
|
|
3407
|
+
return (await listRunsWithWaitingGates()).find((r) => r.runId === runId);
|
|
3408
|
+
}
|
|
3409
|
+
function resolveGateTarget(run, nodeId) {
|
|
3410
|
+
const waiting = run.nodeStates.filter((n) => n.status === "waiting").map((n) => n.nodeId);
|
|
3411
|
+
if (nodeId) {
|
|
3412
|
+
if (waiting.includes(nodeId)) return { nodeId };
|
|
3413
|
+
return {
|
|
3414
|
+
error: waiting.length ? `node "${nodeId}" is not waiting. Waiting: ${waiting.join(", ")}` : `node "${nodeId}" is not waiting, and neither is any other node in this run`
|
|
3415
|
+
};
|
|
3416
|
+
}
|
|
3417
|
+
if (waiting.length === 1) return { nodeId: waiting[0] };
|
|
3418
|
+
if (waiting.length === 0) return { error: "no node in this run is waiting on a gate" };
|
|
3419
|
+
return { error: `${waiting.length} nodes are waiting \u2014 pass node_id: ${waiting.join(", ")}` };
|
|
3420
|
+
}
|
|
3211
3421
|
async function listPortableConnections() {
|
|
3212
3422
|
try {
|
|
3213
3423
|
return await rpcCall("connection:list", { connectorId: void 0 });
|
|
@@ -3361,7 +3571,7 @@ function registerWorkflowTools(server) {
|
|
|
3361
3571
|
);
|
|
3362
3572
|
server.tool(
|
|
3363
3573
|
"list_workflow_runs",
|
|
3364
|
-
"List workflow execution history. Filter by workflow_id or task_id.",
|
|
3574
|
+
"List workflow execution history. Filter by workflow_id or task_id; with neither, lists the runs parked on an approval gate, each waiting node saying what it asks.",
|
|
3365
3575
|
{
|
|
3366
3576
|
workflow_id: V.id.optional().describe("Filter by workflow ID"),
|
|
3367
3577
|
task_id: V.id.optional().describe("Filter by task ID (runs triggered by this task)"),
|
|
@@ -3374,28 +3584,28 @@ function registerWorkflowTools(server) {
|
|
|
3374
3584
|
isError: true
|
|
3375
3585
|
};
|
|
3376
3586
|
}
|
|
3587
|
+
const withGates = async (runs) => runs.some((r) => r.nodeStates.some((n) => n.status === "waiting")) ? annotateWaitingGates(runs, await dbListWorkflows()) : runs;
|
|
3377
3588
|
if (args.task_id) {
|
|
3378
|
-
const runs = await listWorkflowRunsByTask(args.task_id, args.limit ?? 20);
|
|
3589
|
+
const runs = await withGates(await listWorkflowRunsByTask(args.task_id, args.limit ?? 20));
|
|
3379
3590
|
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
3380
3591
|
}
|
|
3381
3592
|
if (args.workflow_id) {
|
|
3382
|
-
const runs = await listWorkflowRuns(args.workflow_id, args.limit ?? 20);
|
|
3593
|
+
const runs = await withGates(await listWorkflowRuns(args.workflow_id, args.limit ?? 20));
|
|
3383
3594
|
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
3384
3595
|
}
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
};
|
|
3596
|
+
const parked = (await listRunsWithWaitingGates()).slice(0, args.limit ?? 20);
|
|
3597
|
+
const waiting = await withGates(parked);
|
|
3598
|
+
return { content: [{ type: "text", text: JSON.stringify(waiting, null, 2) }] };
|
|
3389
3599
|
}
|
|
3390
3600
|
);
|
|
3391
3601
|
server.tool(
|
|
3392
3602
|
"stop_workflow_run",
|
|
3393
|
-
"Stop a workflow run that is still going, including one parked on an approval gate. Kills the agents it started, marks its unfinished nodes, and closes the run as cancelled. Requires the Vorn app to be running.
|
|
3603
|
+
"Stop a workflow run that is still going, including one parked on an approval gate. Kills the agents it started, marks its unfinished nodes, and closes the run as cancelled. Requires the Vorn app to be running.",
|
|
3394
3604
|
{
|
|
3395
3605
|
run_id: V.id.describe("Run ID (from list_workflow_runs)")
|
|
3396
3606
|
},
|
|
3397
3607
|
async (args) => {
|
|
3398
|
-
const run =
|
|
3608
|
+
const run = await runById(args.run_id);
|
|
3399
3609
|
if (!run) {
|
|
3400
3610
|
return {
|
|
3401
3611
|
content: [
|
|
@@ -3440,6 +3650,74 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3440
3650
|
};
|
|
3441
3651
|
}
|
|
3442
3652
|
);
|
|
3653
|
+
server.tool(
|
|
3654
|
+
"resolve_gate",
|
|
3655
|
+
"Approve or reject the approval gate a workflow run is parked on, the way the Vorn app does. Requires the Vorn app to be running: the decision is broadcast, and the instance holding the run is what resumes it. Read what is being approved first \u2014 list_workflow_runs names the waiting node and what it asks.",
|
|
3656
|
+
{
|
|
3657
|
+
run_id: V.id.describe("Run ID (from list_workflow_runs)"),
|
|
3658
|
+
decision: z5.enum(["approve", "reject"]).describe("approve lets the run go on; reject ends it"),
|
|
3659
|
+
node_id: V.id.optional().describe("The waiting node, when a run has more than one gate open")
|
|
3660
|
+
},
|
|
3661
|
+
async (args) => {
|
|
3662
|
+
const run = await runById(args.run_id);
|
|
3663
|
+
if (!run) {
|
|
3664
|
+
return {
|
|
3665
|
+
content: [
|
|
3666
|
+
{
|
|
3667
|
+
type: "text",
|
|
3668
|
+
text: `Error: no run "${args.run_id}" in the recent history, and none parked on a gate. Check list_workflow_runs.`
|
|
3669
|
+
}
|
|
3670
|
+
],
|
|
3671
|
+
isError: true
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3674
|
+
if (run.status !== "running") {
|
|
3675
|
+
return {
|
|
3676
|
+
content: [
|
|
3677
|
+
{
|
|
3678
|
+
type: "text",
|
|
3679
|
+
text: `Run ${args.run_id} already finished (${run.status}) \u2014 no gate to answer.`
|
|
3680
|
+
}
|
|
3681
|
+
]
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
const target = resolveGateTarget(run, args.node_id);
|
|
3685
|
+
if ("error" in target) {
|
|
3686
|
+
return {
|
|
3687
|
+
content: [{ type: "text", text: `Error: ${target.error}` }],
|
|
3688
|
+
isError: true
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
const workflow = (await dbListWorkflows()).find((w) => w.id === run.workflowId);
|
|
3692
|
+
const gateNode = approvalNode(workflow, target.nodeId);
|
|
3693
|
+
const asked = askedBy(gateNode);
|
|
3694
|
+
try {
|
|
3695
|
+
await rpcCall("workflow:resolveGate", {
|
|
3696
|
+
runId: args.run_id,
|
|
3697
|
+
nodeId: target.nodeId,
|
|
3698
|
+
decision: args.decision
|
|
3699
|
+
});
|
|
3700
|
+
} catch (err) {
|
|
3701
|
+
return {
|
|
3702
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
|
|
3703
|
+
isError: true
|
|
3704
|
+
};
|
|
3705
|
+
}
|
|
3706
|
+
const gate = gateNode?.label ?? target.nodeId;
|
|
3707
|
+
return {
|
|
3708
|
+
content: [
|
|
3709
|
+
{
|
|
3710
|
+
type: "text",
|
|
3711
|
+
text: `${args.decision === "approve" ? "Approved" : "Rejected"} "${gate}" on run ${args.run_id}${run.workflowName ? ` of "${run.workflowName}"` : ""}.${asked ? `
|
|
3712
|
+
|
|
3713
|
+
What it asked: ${asked}` : ""}
|
|
3714
|
+
|
|
3715
|
+
The decision went out; the instance holding the run acts on it, so a desktop has to be open. Confirm with list_workflow_runs.`
|
|
3716
|
+
}
|
|
3717
|
+
]
|
|
3718
|
+
};
|
|
3719
|
+
}
|
|
3720
|
+
);
|
|
3443
3721
|
server.tool(
|
|
3444
3722
|
"get_workflow_schedule",
|
|
3445
3723
|
"Get scheduler info for a workflow: execution log or next scheduled run. Requires the Vorn app to be running.",
|
|
@@ -3480,7 +3758,7 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3480
3758
|
);
|
|
3481
3759
|
server.tool(
|
|
3482
3760
|
"execute_workflow",
|
|
3483
|
-
"Run a workflow now, as if triggered manually. Supply values for any parameters the workflow declares (see the trigger node's inputs); declared defaults fill in anything omitted. Requires the Vorn app to be running. Returns as soon as the run is queued \u2014 poll list_workflow_runs for the outcome.",
|
|
3761
|
+
"Run a workflow now, as if triggered manually. Supply values for any parameters the workflow declares (see the trigger node's inputs); declared defaults fill in anything omitted. Requires the Vorn app to be running. Returns as soon as the run is queued \u2014 poll list_workflow_runs for the outcome. Runs of one workflow go side by side; only a run repeating one started in the last ten seconds with the same inputs is refused as a duplicate.",
|
|
3484
3762
|
{
|
|
3485
3763
|
workflow_id: V.id.describe("Workflow ID (from list_workflows)"),
|
|
3486
3764
|
inputs: z5.record(z5.string(), z5.union([z5.string(), z5.number(), z5.boolean()])).optional().describe("Values for the declared parameters, keyed by input key ({{inputs.<key>}})")
|
|
@@ -3799,27 +4077,43 @@ var failure = (message) => ({
|
|
|
3799
4077
|
function summarize(entry) {
|
|
3800
4078
|
return { type: entry.type, label: entry.label };
|
|
3801
4079
|
}
|
|
4080
|
+
function andList(values) {
|
|
4081
|
+
if (values.length <= 1) return values[0] ?? "";
|
|
4082
|
+
return `${values.slice(0, -1).join(", ")} and ${values[values.length - 1]}`;
|
|
4083
|
+
}
|
|
4084
|
+
function contributionSummary(contributes) {
|
|
4085
|
+
const named2 = (entries) => (entries ?? []).map((entry) => ({ id: entry.id, title: entry.title }));
|
|
4086
|
+
return {
|
|
4087
|
+
panes: named2(contributes?.panes),
|
|
4088
|
+
footers: named2(contributes?.footers),
|
|
4089
|
+
linkHandlers: named2(contributes?.linkHandlers)
|
|
4090
|
+
};
|
|
4091
|
+
}
|
|
3802
4092
|
function registerConnectorTools(server) {
|
|
3803
4093
|
server.tool(
|
|
3804
4094
|
"list_connectors",
|
|
3805
|
-
"List every connector: the ones built into Vorn, the ones installable from a package, and how many connections each already has. Use this before creating a workflow that calls a connector action, or to find
|
|
4095
|
+
"List every connector and extension: the ones built into Vorn, the ones installable from a package, and how many connections each already has. A connector polls a service; an extension adds footers and panes to a session card and says what it may touch. Use this before creating a workflow that calls a connector action, or to find an id to install.",
|
|
3806
4096
|
{
|
|
3807
|
-
installable_only: z7.boolean().optional().describe("Only connectors that are not set up yet")
|
|
4097
|
+
installable_only: z7.boolean().optional().describe("Only connectors that are not set up yet"),
|
|
4098
|
+
kind: z7.enum(["connector", "extension"]).optional().describe("Only one kind: what polls a service, or what shows on a card")
|
|
3808
4099
|
},
|
|
3809
4100
|
async (args) => {
|
|
3810
|
-
const [builtIns, snapshot, connections, statuses] = await Promise.all([
|
|
4101
|
+
const [builtIns, snapshot, connections, statuses, packs] = await Promise.all([
|
|
3811
4102
|
rpcCall("connector:list"),
|
|
3812
4103
|
rpcCall("connector:catalog"),
|
|
3813
4104
|
rpcCall("connection:list", { connectorId: void 0 }),
|
|
3814
|
-
rpcCall("connector:status")
|
|
4105
|
+
rpcCall("connector:status"),
|
|
4106
|
+
rpcCall("connector:listPacks")
|
|
3815
4107
|
]);
|
|
3816
4108
|
const countFor = (id) => connections.filter((conn) => connectionConnectorId(conn) === id).length;
|
|
3817
4109
|
const statusFor = (id) => statuses.find((s) => s.connectorId === id);
|
|
4110
|
+
const packFor = (id) => packs.find((pack) => pack.id === id);
|
|
3818
4111
|
const entries = [
|
|
3819
4112
|
...builtIns.map((c) => ({
|
|
3820
4113
|
id: c.id,
|
|
3821
4114
|
name: c.name,
|
|
3822
4115
|
source: "built-in",
|
|
4116
|
+
kind: "connector",
|
|
3823
4117
|
capabilities: c.capabilities,
|
|
3824
4118
|
connections: countFor(c.id),
|
|
3825
4119
|
// Only meaningful for connectors that authenticate up front; the
|
|
@@ -3829,25 +4123,49 @@ function registerConnectorTools(server) {
|
|
|
3829
4123
|
...statusFor(c.id).message && { authMessage: statusFor(c.id).message }
|
|
3830
4124
|
}
|
|
3831
4125
|
})),
|
|
3832
|
-
...snapshot.items.map((entry) =>
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
4126
|
+
...snapshot.items.map((entry) => {
|
|
4127
|
+
const pack = packFor(entry.id);
|
|
4128
|
+
const kind = pack?.kind ?? entry.kind ?? "connector";
|
|
4129
|
+
return {
|
|
4130
|
+
id: entry.id,
|
|
4131
|
+
name: entry.name,
|
|
4132
|
+
source: "package",
|
|
4133
|
+
kind,
|
|
4134
|
+
description: entry.description,
|
|
4135
|
+
package: entry.packageName,
|
|
4136
|
+
...entry.version && { version: entry.version },
|
|
4137
|
+
capabilities: entry.capabilities,
|
|
4138
|
+
connections: countFor(entry.id),
|
|
4139
|
+
...pack && { installed: pack.version },
|
|
4140
|
+
...entry.auth && { auth: entry.auth },
|
|
4141
|
+
// Generated upstream from the connector's own manifest, so an agent
|
|
4142
|
+
// can tell whether a connector is worth installing without launching
|
|
4143
|
+
// it — which for a list of twenty would be twenty npx processes.
|
|
4144
|
+
...entry.triggers && { triggers: entry.triggers.map(summarize) },
|
|
4145
|
+
...entry.actions && { actions: entry.actions.map(summarize) },
|
|
4146
|
+
...entry.env && { env: entry.env.map((e) => e.name) },
|
|
4147
|
+
// Only what was stated: an empty list would read as "adds nothing",
|
|
4148
|
+
// which is a claim an older catalog never made.
|
|
4149
|
+
...kind === "extension" && {
|
|
4150
|
+
...(pack?.contributes ?? entry.contributes) && {
|
|
4151
|
+
contributes: contributionSummary(pack?.contributes ?? entry.contributes)
|
|
4152
|
+
},
|
|
4153
|
+
...(pack?.permissions ?? entry.permissions) && {
|
|
4154
|
+
permissions: pack?.permissions ?? entry.permissions
|
|
4155
|
+
},
|
|
4156
|
+
...(pack?.activates ?? entry.activates) && {
|
|
4157
|
+
activates: pack?.activates ?? entry.activates
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
};
|
|
4161
|
+
})
|
|
3849
4162
|
];
|
|
3850
|
-
|
|
4163
|
+
const ofKind = args.kind ? entries.filter((e) => e.kind === args.kind) : entries;
|
|
4164
|
+
return json(
|
|
4165
|
+
args.installable_only ? ofKind.filter(
|
|
4166
|
+
(e) => e.kind === "extension" ? !("installed" in e && e.installed) : e.connections === 0
|
|
4167
|
+
) : ofKind
|
|
4168
|
+
);
|
|
3851
4169
|
}
|
|
3852
4170
|
);
|
|
3853
4171
|
server.tool(
|
|
@@ -3897,7 +4215,7 @@ function registerConnectorTools(server) {
|
|
|
3897
4215
|
);
|
|
3898
4216
|
server.tool(
|
|
3899
4217
|
"inspect_connector_package",
|
|
3900
|
-
"Start a connector package and read what it offers \u2014 its triggers, actions and required environment variables \u2014 without installing it. Use this to review
|
|
4218
|
+
"Start a connector package and read what it offers \u2014 its triggers, actions and required environment variables, or for an extension what it contributes to a card and what it asks to touch \u2014 without installing it. Use this to review one before install_connector, or to check a local build.",
|
|
3901
4219
|
{
|
|
3902
4220
|
package: V.shortText.describe(
|
|
3903
4221
|
'npm package name, or a command to run a local build (e.g. "node /path/to/dist/index.js")'
|
|
@@ -3911,10 +4229,10 @@ function registerConnectorTools(server) {
|
|
|
3911
4229
|
);
|
|
3912
4230
|
server.tool(
|
|
3913
4231
|
"install_connector",
|
|
3914
|
-
"Install a connector from a pack file, from the catalog, or
|
|
4232
|
+
"Install a connector from a pack file, from the catalog, or by a launch command, creating a connection ready to poll. Installing an extension is the whole of setting it up: it takes no trigger and makes no connection, and shows on the cards its activation names. Call list_connectors for catalog ids and inspect_connector_package to see which environment variables are needed. Secrets cannot be set this way \u2014 see the error it returns if the connector requires one.",
|
|
3915
4233
|
{
|
|
3916
4234
|
connector_id: V.id.optional().describe("Catalog connector id (from list_connectors). Use this or package."),
|
|
3917
|
-
package: V.shortText.optional().describe("
|
|
4235
|
+
package: V.shortText.optional().describe("Launch command the connection runs, or a package name to run with npx"),
|
|
3918
4236
|
pack_path: V.shortText.optional().describe(
|
|
3919
4237
|
"Path to a .vorn.tgz pack to install first. It is verified and copied to disk, and the connection then launches those files rather than resolving a package."
|
|
3920
4238
|
),
|
|
@@ -3940,6 +4258,38 @@ function registerConnectorTools(server) {
|
|
|
3940
4258
|
});
|
|
3941
4259
|
if (!outcome.ok) return failure(`The pack was refused: ${outcome.error}`);
|
|
3942
4260
|
installed = outcome.pack;
|
|
4261
|
+
} else if (entry?.kind === "extension") {
|
|
4262
|
+
if (!entry.packUrl) {
|
|
4263
|
+
return failure(
|
|
4264
|
+
`${entry.name} is in the catalog but no release has published a pack for it yet.`
|
|
4265
|
+
);
|
|
4266
|
+
}
|
|
4267
|
+
const outcome = await rpcCall("connector:installPack", {
|
|
4268
|
+
kind: "url",
|
|
4269
|
+
url: entry.packUrl,
|
|
4270
|
+
...entry.sha256 && { sha256: entry.sha256 }
|
|
4271
|
+
});
|
|
4272
|
+
if (!outcome.ok) return failure(`The pack was refused: ${outcome.error}`);
|
|
4273
|
+
installed = outcome.pack;
|
|
4274
|
+
}
|
|
4275
|
+
if (installed?.kind === "extension") {
|
|
4276
|
+
const ignored = [
|
|
4277
|
+
args.trigger !== void 0 && "trigger",
|
|
4278
|
+
args.sync_interval_minutes !== void 0 && "sync_interval_minutes",
|
|
4279
|
+
args.env !== void 0 && "env",
|
|
4280
|
+
args.name !== void 0 && "name",
|
|
4281
|
+
args.project !== void 0 && "project"
|
|
4282
|
+
].filter(Boolean);
|
|
4283
|
+
return json({
|
|
4284
|
+
installed: installed.name,
|
|
4285
|
+
kind: "extension",
|
|
4286
|
+
version: installed.version,
|
|
4287
|
+
path: installed.path,
|
|
4288
|
+
contributes: installed.contributes ?? {},
|
|
4289
|
+
permissions: installed.permissions ?? [],
|
|
4290
|
+
...installed.activates && { activates: installed.activates },
|
|
4291
|
+
note: "Extensions have no connection: they show on the cards their activation names." + (ignored.length > 0 ? ` Ignored ${andList(ignored)}, which only a connection uses.` : "")
|
|
4292
|
+
});
|
|
3943
4293
|
}
|
|
3944
4294
|
const target = installed ? packLaunch(installed) : entry?.launch ?? args.package;
|
|
3945
4295
|
if (!target) return failure("Provide either connector_id, package, or pack_path.");
|
|
@@ -4532,7 +4882,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
4532
4882
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
4533
4883
|
async function main() {
|
|
4534
4884
|
configManager.init();
|
|
4535
|
-
const version = true ? "0.7.0-beta.
|
|
4885
|
+
const version = true ? "0.7.0-beta.15" : createRequire(import.meta.url)("../package.json").version;
|
|
4536
4886
|
const server = createMcpServer(version);
|
|
4537
4887
|
const transport = new StdioServerTransport();
|
|
4538
4888
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vornrun/mcp",
|
|
3
|
-
"version": "0.7.0-beta.
|
|
3
|
+
"version": "0.7.0-beta.15",
|
|
4
4
|
"description": "Vorn MCP server — task management, git, and workflow tools for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"libsql": "^0.5.29",
|
|
36
36
|
"pino": "^10.3.1",
|
|
37
37
|
"ws": "^8.21.1",
|
|
38
|
-
"zod": "^4.4
|
|
38
|
+
"zod": "^4.5.4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@vornrun/server": "0.7.0-beta.
|
|
42
|
-
"@vornrun/shared": "0.7.0-beta.
|
|
41
|
+
"@vornrun/server": "0.7.0-beta.15",
|
|
42
|
+
"@vornrun/shared": "0.7.0-beta.15",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
44
|
"tsx": "^4.23.1",
|
|
45
45
|
"typescript": "^6.0.3"
|