@vornrun/mcp 0.7.0-beta.13 → 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 +428 -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,9 +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
|
-
{ column: "shell_cwd", ddl: "ALTER TABLE sessions ADD COLUMN shell_cwd TEXT" }
|
|
1054
|
+
{ column: "shell_cwd", ddl: "ALTER TABLE sessions ADD COLUMN shell_cwd 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
|
+
}
|
|
984
1060
|
],
|
|
985
1061
|
agent_commands: [
|
|
986
1062
|
{
|
|
@@ -1093,6 +1169,7 @@ function loadConfig() {
|
|
|
1093
1169
|
const remoteHosts = loadRemoteHosts(d);
|
|
1094
1170
|
const tasks = loadTasks(d);
|
|
1095
1171
|
const workspaces = loadWorkspaces(d);
|
|
1172
|
+
const sessionGroups = loadSessionGroups(d);
|
|
1096
1173
|
return {
|
|
1097
1174
|
version: 1,
|
|
1098
1175
|
revision: readConfigRevision(d),
|
|
@@ -1102,7 +1179,8 @@ function loadConfig() {
|
|
|
1102
1179
|
workflows,
|
|
1103
1180
|
remoteHosts,
|
|
1104
1181
|
tasks,
|
|
1105
|
-
workspaces
|
|
1182
|
+
workspaces,
|
|
1183
|
+
sessionGroups
|
|
1106
1184
|
};
|
|
1107
1185
|
}
|
|
1108
1186
|
function loadDefaults(d) {
|
|
@@ -1135,6 +1213,8 @@ function loadDefaults(d) {
|
|
|
1135
1213
|
// terminal drew and waits. There is nothing to ask, and leaving it off made
|
|
1136
1214
|
// the whole thing invisible unless somebody went looking for a toggle.
|
|
1137
1215
|
reopenSessions: map.reopenSessions ?? true,
|
|
1216
|
+
// Off by default: nothing starts itself because someone installed an app.
|
|
1217
|
+
startAtLogin: map.startAtLogin ?? false,
|
|
1138
1218
|
// Saving iterates over every key in defaults, but loading is this explicit
|
|
1139
1219
|
// list — so a key missing here round-trips to nothing and its feature is
|
|
1140
1220
|
// silently inert.
|
|
@@ -1187,6 +1267,9 @@ function loadDefaults(d) {
|
|
|
1187
1267
|
...map.headlessRetentionMinutes !== void 0 && {
|
|
1188
1268
|
headlessRetentionMinutes: map.headlessRetentionMinutes
|
|
1189
1269
|
},
|
|
1270
|
+
...map.hasSeededDevServerWorkflow !== void 0 && {
|
|
1271
|
+
hasSeededDevServerWorkflow: map.hasSeededDevServerWorkflow
|
|
1272
|
+
},
|
|
1190
1273
|
...map.hasSeededDefaultTaskWorkflow !== void 0 && {
|
|
1191
1274
|
hasSeededDefaultTaskWorkflow: map.hasSeededDefaultTaskWorkflow
|
|
1192
1275
|
},
|
|
@@ -1250,6 +1333,10 @@ function loadTasks(d) {
|
|
|
1250
1333
|
const rows = d.prepare('SELECT * FROM tasks ORDER BY "order"').all();
|
|
1251
1334
|
return rows.map(rowToTask);
|
|
1252
1335
|
}
|
|
1336
|
+
function loadSessionGroups(d) {
|
|
1337
|
+
const rows = d.prepare('SELECT * FROM session_groups ORDER BY "order"').all();
|
|
1338
|
+
return rows.map(rowToSessionGroup);
|
|
1339
|
+
}
|
|
1253
1340
|
function loadWorkspaces(d) {
|
|
1254
1341
|
const rows = d.prepare('SELECT * FROM workspaces ORDER BY "order"').all();
|
|
1255
1342
|
return rows.map(rowToWorkspace);
|
|
@@ -1489,6 +1576,36 @@ function saveConfig(config) {
|
|
|
1489
1576
|
for (const ws of workspaces) {
|
|
1490
1577
|
insertWorkspace.run(ws.id, ws.name, ws.icon ?? null, ws.iconColor ?? null, ws.order, revision);
|
|
1491
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
|
+
}
|
|
1492
1609
|
d.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES (?, ?)").run(
|
|
1493
1610
|
CONFIG_REVISION_KEY,
|
|
1494
1611
|
String(revision)
|
|
@@ -1546,6 +1663,16 @@ function rowToWorkflow(r) {
|
|
|
1546
1663
|
workspaceId: r.workspace_id ?? "personal"
|
|
1547
1664
|
};
|
|
1548
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
|
+
}
|
|
1549
1676
|
function rowToWorkspace(r) {
|
|
1550
1677
|
return {
|
|
1551
1678
|
id: r.id,
|
|
@@ -1562,8 +1689,8 @@ var ConfigManager = class {
|
|
|
1562
1689
|
dbWatcher = null;
|
|
1563
1690
|
debounceTimer = null;
|
|
1564
1691
|
cachedConfig = null;
|
|
1565
|
-
init(
|
|
1566
|
-
initDatabase(
|
|
1692
|
+
init(dataDir2) {
|
|
1693
|
+
initDatabase(dataDir2);
|
|
1567
1694
|
}
|
|
1568
1695
|
close() {
|
|
1569
1696
|
this.stopWatchingDb();
|
|
@@ -1681,33 +1808,47 @@ var V = {
|
|
|
1681
1808
|
url: safeUrl
|
|
1682
1809
|
};
|
|
1683
1810
|
|
|
1684
|
-
// src/
|
|
1811
|
+
// ../server/src/rpc-client.ts
|
|
1685
1812
|
import fs4 from "fs";
|
|
1686
1813
|
import path3 from "path";
|
|
1687
1814
|
import os2 from "os";
|
|
1688
1815
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1689
1816
|
import { WebSocket } from "ws";
|
|
1690
|
-
var
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
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()}).
|
|
1694
1832
|
The server writes it on startup and removes it on shutdown, so this usually means
|
|
1695
|
-
Vorn is not running. Start Vorn (or \`vorn
|
|
1696
|
-
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
|
+
}
|
|
1697
1838
|
function readLocalToken() {
|
|
1698
1839
|
try {
|
|
1699
|
-
const token = fs4.readFileSync(
|
|
1840
|
+
const token = fs4.readFileSync(localTokenFile(), "utf-8").trim();
|
|
1700
1841
|
if (!token) throw new Error("empty");
|
|
1701
1842
|
return token;
|
|
1702
1843
|
} catch {
|
|
1703
|
-
throw new Error(
|
|
1844
|
+
throw new Error(tokenFileMissingMessage());
|
|
1704
1845
|
}
|
|
1705
1846
|
}
|
|
1706
1847
|
function connection() {
|
|
1707
1848
|
const result = readPort();
|
|
1708
1849
|
if (!result.port) {
|
|
1709
1850
|
const reason = "reason" in result ? result.reason : "missing";
|
|
1710
|
-
throw new Error(reason === "invalid" ?
|
|
1851
|
+
throw new Error(reason === "invalid" ? portFileInvalidMessage() : portFileMissingMessage());
|
|
1711
1852
|
}
|
|
1712
1853
|
return {
|
|
1713
1854
|
url: `ws://127.0.0.1:${result.port}/ws`,
|
|
@@ -1716,21 +1857,42 @@ function connection() {
|
|
|
1716
1857
|
}
|
|
1717
1858
|
var TIMEOUT_MS = 1e4;
|
|
1718
1859
|
var IS_WIN = process.platform === "win32";
|
|
1719
|
-
|
|
1860
|
+
function portFileMissingMessage() {
|
|
1861
|
+
const file = portFile();
|
|
1862
|
+
return IS_WIN ? `Vorn port file not found (${file}).
|
|
1720
1863
|
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
1721
|
-
To fix, find the Vorn process and its listening port:
|
|
1722
|
-
|
|
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
|
|
1723
1866
|
Then write the WS port to the file:
|
|
1724
|
-
|
|
1725
|
-
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}).
|
|
1726
1869
|
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
1727
1870
|
To fix, run: lsof -iTCP -sTCP:LISTEN -P | grep Vorn
|
|
1728
1871
|
Then write the WS port (the one on *:<port>) to the file:
|
|
1729
|
-
echo '{"port":<PORT>,"pid":<PID>}' >
|
|
1872
|
+
echo '{"port":<PORT>,"pid":<PID>}' > "${file}"
|
|
1730
1873
|
Or restart Vorn to regenerate it.`;
|
|
1731
|
-
|
|
1874
|
+
}
|
|
1875
|
+
function portFileInvalidMessage() {
|
|
1876
|
+
const file = portFile();
|
|
1877
|
+
return `Vorn port file exists but contains invalid data (${file}).
|
|
1732
1878
|
Delete it and restart Vorn, or overwrite it with the correct port:
|
|
1733
|
-
${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
|
+
}
|
|
1734
1896
|
var rpcId = 0;
|
|
1735
1897
|
var cachedPort = null;
|
|
1736
1898
|
var cacheTimestamp = 0;
|
|
@@ -1783,7 +1945,11 @@ function discoverPort() {
|
|
|
1783
1945
|
}
|
|
1784
1946
|
return null;
|
|
1785
1947
|
}
|
|
1948
|
+
function discoveryAllowed() {
|
|
1949
|
+
return dataDirOverride === void 0 && named(process.env.VORN_DATA_DIR) === void 0;
|
|
1950
|
+
}
|
|
1786
1951
|
function discoverAndHeal() {
|
|
1952
|
+
if (!discoveryAllowed()) return { port: null, reason: "missing" };
|
|
1787
1953
|
const now = Date.now();
|
|
1788
1954
|
if (cachedPort && now - cacheTimestamp < CACHE_TTL_MS) return { port: cachedPort };
|
|
1789
1955
|
const discovered = discoverPort();
|
|
@@ -1791,8 +1957,8 @@ function discoverAndHeal() {
|
|
|
1791
1957
|
cacheTimestamp = now;
|
|
1792
1958
|
if (discovered) {
|
|
1793
1959
|
try {
|
|
1794
|
-
fs4.mkdirSync(
|
|
1795
|
-
fs4.writeFileSync(
|
|
1960
|
+
fs4.mkdirSync(dataDir(), { recursive: true });
|
|
1961
|
+
fs4.writeFileSync(portFile(), JSON.stringify({ port: discovered }), "utf-8");
|
|
1796
1962
|
} catch {
|
|
1797
1963
|
}
|
|
1798
1964
|
return { port: discovered };
|
|
@@ -1801,7 +1967,7 @@ function discoverAndHeal() {
|
|
|
1801
1967
|
}
|
|
1802
1968
|
function readPort() {
|
|
1803
1969
|
try {
|
|
1804
|
-
const raw = fs4.readFileSync(
|
|
1970
|
+
const raw = fs4.readFileSync(portFile(), "utf-8").trim();
|
|
1805
1971
|
if (!raw) return { port: null, reason: "invalid" };
|
|
1806
1972
|
if (raw.startsWith("{")) {
|
|
1807
1973
|
const parsed = JSON.parse(raw);
|
|
@@ -1843,15 +2009,19 @@ async function rpcCall(method, params, timeoutMs = TIMEOUT_MS) {
|
|
|
1843
2009
|
const msg = JSON.parse(raw.toString());
|
|
1844
2010
|
if (msg.id !== id) return;
|
|
1845
2011
|
clearTimeout(timer);
|
|
1846
|
-
ws.close();
|
|
1847
2012
|
if (msg.error) {
|
|
1848
|
-
reject(new Error(msg.error.message));
|
|
2013
|
+
reject(new Error(explain(msg.error.message)));
|
|
1849
2014
|
} else {
|
|
1850
2015
|
resolve(msg.result);
|
|
1851
2016
|
}
|
|
2017
|
+
ws.close();
|
|
1852
2018
|
} catch {
|
|
1853
2019
|
}
|
|
1854
2020
|
});
|
|
2021
|
+
ws.on("close", (code) => {
|
|
2022
|
+
clearTimeout(timer);
|
|
2023
|
+
reject(new Error(closedBeforeAnswering(code)));
|
|
2024
|
+
});
|
|
1855
2025
|
ws.on("error", (err) => {
|
|
1856
2026
|
clearTimeout(timer);
|
|
1857
2027
|
reject(new Error(`Cannot connect to Vorn server: ${err.message}. Is the app running?`));
|
|
@@ -1980,6 +2150,9 @@ async function listWorkflowRunsByTask(taskId, limit = 20) {
|
|
|
1980
2150
|
limit
|
|
1981
2151
|
});
|
|
1982
2152
|
}
|
|
2153
|
+
async function listRunsWithWaitingGates() {
|
|
2154
|
+
return rpcCall("workflowRun:listWaiting");
|
|
2155
|
+
}
|
|
1983
2156
|
async function listAllWorkflowRuns(workspaceId, limit = 50) {
|
|
1984
2157
|
return rpcCall("workflowRun:listAll", {
|
|
1985
2158
|
workspaceId,
|
|
@@ -2790,8 +2963,8 @@ function resolveRequirement(requirement, connections) {
|
|
|
2790
2963
|
);
|
|
2791
2964
|
if (candidates.length === 0) return void 0;
|
|
2792
2965
|
if (requirement.name !== "") {
|
|
2793
|
-
const
|
|
2794
|
-
if (
|
|
2966
|
+
const named2 = candidates.filter((connection2) => connection2.name === requirement.name);
|
|
2967
|
+
if (named2.length === 1) return named2[0].id;
|
|
2795
2968
|
}
|
|
2796
2969
|
return candidates.length === 1 ? candidates[0].id : void 0;
|
|
2797
2970
|
}
|
|
@@ -3205,6 +3378,46 @@ function resolveWorkflowId(args) {
|
|
|
3205
3378
|
}
|
|
3206
3379
|
return { id };
|
|
3207
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
|
+
}
|
|
3208
3421
|
async function listPortableConnections() {
|
|
3209
3422
|
try {
|
|
3210
3423
|
return await rpcCall("connection:list", { connectorId: void 0 });
|
|
@@ -3358,7 +3571,7 @@ function registerWorkflowTools(server) {
|
|
|
3358
3571
|
);
|
|
3359
3572
|
server.tool(
|
|
3360
3573
|
"list_workflow_runs",
|
|
3361
|
-
"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.",
|
|
3362
3575
|
{
|
|
3363
3576
|
workflow_id: V.id.optional().describe("Filter by workflow ID"),
|
|
3364
3577
|
task_id: V.id.optional().describe("Filter by task ID (runs triggered by this task)"),
|
|
@@ -3371,28 +3584,28 @@ function registerWorkflowTools(server) {
|
|
|
3371
3584
|
isError: true
|
|
3372
3585
|
};
|
|
3373
3586
|
}
|
|
3587
|
+
const withGates = async (runs) => runs.some((r) => r.nodeStates.some((n) => n.status === "waiting")) ? annotateWaitingGates(runs, await dbListWorkflows()) : runs;
|
|
3374
3588
|
if (args.task_id) {
|
|
3375
|
-
const runs = await listWorkflowRunsByTask(args.task_id, args.limit ?? 20);
|
|
3589
|
+
const runs = await withGates(await listWorkflowRunsByTask(args.task_id, args.limit ?? 20));
|
|
3376
3590
|
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
3377
3591
|
}
|
|
3378
3592
|
if (args.workflow_id) {
|
|
3379
|
-
const runs = await listWorkflowRuns(args.workflow_id, args.limit ?? 20);
|
|
3593
|
+
const runs = await withGates(await listWorkflowRuns(args.workflow_id, args.limit ?? 20));
|
|
3380
3594
|
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
3381
3595
|
}
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
};
|
|
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) }] };
|
|
3386
3599
|
}
|
|
3387
3600
|
);
|
|
3388
3601
|
server.tool(
|
|
3389
3602
|
"stop_workflow_run",
|
|
3390
|
-
"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.",
|
|
3391
3604
|
{
|
|
3392
3605
|
run_id: V.id.describe("Run ID (from list_workflow_runs)")
|
|
3393
3606
|
},
|
|
3394
3607
|
async (args) => {
|
|
3395
|
-
const run =
|
|
3608
|
+
const run = await runById(args.run_id);
|
|
3396
3609
|
if (!run) {
|
|
3397
3610
|
return {
|
|
3398
3611
|
content: [
|
|
@@ -3437,6 +3650,74 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3437
3650
|
};
|
|
3438
3651
|
}
|
|
3439
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
|
+
);
|
|
3440
3721
|
server.tool(
|
|
3441
3722
|
"get_workflow_schedule",
|
|
3442
3723
|
"Get scheduler info for a workflow: execution log or next scheduled run. Requires the Vorn app to be running.",
|
|
@@ -3477,7 +3758,7 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3477
3758
|
);
|
|
3478
3759
|
server.tool(
|
|
3479
3760
|
"execute_workflow",
|
|
3480
|
-
"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.",
|
|
3481
3762
|
{
|
|
3482
3763
|
workflow_id: V.id.describe("Workflow ID (from list_workflows)"),
|
|
3483
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>}})")
|
|
@@ -3796,27 +4077,43 @@ var failure = (message) => ({
|
|
|
3796
4077
|
function summarize(entry) {
|
|
3797
4078
|
return { type: entry.type, label: entry.label };
|
|
3798
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
|
+
}
|
|
3799
4092
|
function registerConnectorTools(server) {
|
|
3800
4093
|
server.tool(
|
|
3801
4094
|
"list_connectors",
|
|
3802
|
-
"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.",
|
|
3803
4096
|
{
|
|
3804
|
-
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")
|
|
3805
4099
|
},
|
|
3806
4100
|
async (args) => {
|
|
3807
|
-
const [builtIns, snapshot, connections, statuses] = await Promise.all([
|
|
4101
|
+
const [builtIns, snapshot, connections, statuses, packs] = await Promise.all([
|
|
3808
4102
|
rpcCall("connector:list"),
|
|
3809
4103
|
rpcCall("connector:catalog"),
|
|
3810
4104
|
rpcCall("connection:list", { connectorId: void 0 }),
|
|
3811
|
-
rpcCall("connector:status")
|
|
4105
|
+
rpcCall("connector:status"),
|
|
4106
|
+
rpcCall("connector:listPacks")
|
|
3812
4107
|
]);
|
|
3813
4108
|
const countFor = (id) => connections.filter((conn) => connectionConnectorId(conn) === id).length;
|
|
3814
4109
|
const statusFor = (id) => statuses.find((s) => s.connectorId === id);
|
|
4110
|
+
const packFor = (id) => packs.find((pack) => pack.id === id);
|
|
3815
4111
|
const entries = [
|
|
3816
4112
|
...builtIns.map((c) => ({
|
|
3817
4113
|
id: c.id,
|
|
3818
4114
|
name: c.name,
|
|
3819
4115
|
source: "built-in",
|
|
4116
|
+
kind: "connector",
|
|
3820
4117
|
capabilities: c.capabilities,
|
|
3821
4118
|
connections: countFor(c.id),
|
|
3822
4119
|
// Only meaningful for connectors that authenticate up front; the
|
|
@@ -3826,25 +4123,49 @@ function registerConnectorTools(server) {
|
|
|
3826
4123
|
...statusFor(c.id).message && { authMessage: statusFor(c.id).message }
|
|
3827
4124
|
}
|
|
3828
4125
|
})),
|
|
3829
|
-
...snapshot.items.map((entry) =>
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
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
|
+
})
|
|
3846
4162
|
];
|
|
3847
|
-
|
|
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
|
+
);
|
|
3848
4169
|
}
|
|
3849
4170
|
);
|
|
3850
4171
|
server.tool(
|
|
@@ -3894,7 +4215,7 @@ function registerConnectorTools(server) {
|
|
|
3894
4215
|
);
|
|
3895
4216
|
server.tool(
|
|
3896
4217
|
"inspect_connector_package",
|
|
3897
|
-
"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.",
|
|
3898
4219
|
{
|
|
3899
4220
|
package: V.shortText.describe(
|
|
3900
4221
|
'npm package name, or a command to run a local build (e.g. "node /path/to/dist/index.js")'
|
|
@@ -3908,10 +4229,10 @@ function registerConnectorTools(server) {
|
|
|
3908
4229
|
);
|
|
3909
4230
|
server.tool(
|
|
3910
4231
|
"install_connector",
|
|
3911
|
-
"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.",
|
|
3912
4233
|
{
|
|
3913
4234
|
connector_id: V.id.optional().describe("Catalog connector id (from list_connectors). Use this or package."),
|
|
3914
|
-
package: V.shortText.optional().describe("
|
|
4235
|
+
package: V.shortText.optional().describe("Launch command the connection runs, or a package name to run with npx"),
|
|
3915
4236
|
pack_path: V.shortText.optional().describe(
|
|
3916
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."
|
|
3917
4238
|
),
|
|
@@ -3937,6 +4258,38 @@ function registerConnectorTools(server) {
|
|
|
3937
4258
|
});
|
|
3938
4259
|
if (!outcome.ok) return failure(`The pack was refused: ${outcome.error}`);
|
|
3939
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
|
+
});
|
|
3940
4293
|
}
|
|
3941
4294
|
const target = installed ? packLaunch(installed) : entry?.launch ?? args.package;
|
|
3942
4295
|
if (!target) return failure("Provide either connector_id, package, or pack_path.");
|
|
@@ -4529,7 +4882,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
4529
4882
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
4530
4883
|
async function main() {
|
|
4531
4884
|
configManager.init();
|
|
4532
|
-
const version = true ? "0.7.0-beta.
|
|
4885
|
+
const version = true ? "0.7.0-beta.15" : createRequire(import.meta.url)("../package.json").version;
|
|
4533
4886
|
const server = createMcpServer(version);
|
|
4534
4887
|
const transport = new StdioServerTransport();
|
|
4535
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"
|