@vornrun/mcp 0.7.0-beta.9 → 0.7.0
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 +506 -87
- 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,
|
|
@@ -451,6 +508,10 @@ function createSchema() {
|
|
|
451
508
|
output TEXT,
|
|
452
509
|
structured_output TEXT,
|
|
453
510
|
iteration INTEGER,
|
|
511
|
+
worktree_path TEXT,
|
|
512
|
+
worktree_name TEXT,
|
|
513
|
+
worktree_origin TEXT,
|
|
514
|
+
waiting_for TEXT,
|
|
454
515
|
FOREIGN KEY (run_id) REFERENCES workflow_runs(id) ON DELETE CASCADE
|
|
455
516
|
);
|
|
456
517
|
|
|
@@ -691,7 +752,9 @@ function migrateSchema(d) {
|
|
|
691
752
|
last_sync_at TEXT,
|
|
692
753
|
last_sync_error TEXT,
|
|
693
754
|
sync_cursor TEXT,
|
|
694
|
-
created_at TEXT NOT NULL
|
|
755
|
+
created_at TEXT NOT NULL,
|
|
756
|
+
signed_in_as TEXT,
|
|
757
|
+
signed_in_at TEXT
|
|
695
758
|
)
|
|
696
759
|
`);
|
|
697
760
|
d.exec(`
|
|
@@ -937,11 +1000,52 @@ function migrateSchema(d) {
|
|
|
937
1000
|
})();
|
|
938
1001
|
logger_default.info("[database] migrated schema to version 17 (packaged connector task ids)");
|
|
939
1002
|
}
|
|
1003
|
+
if (version < 18) {
|
|
1004
|
+
d.transaction(() => {
|
|
1005
|
+
const sessionCols = d.prepare("PRAGMA table_info(sessions)").all();
|
|
1006
|
+
if (!sessionCols.some((c) => c.name === "group_id")) {
|
|
1007
|
+
d.exec("ALTER TABLE sessions ADD COLUMN group_id TEXT");
|
|
1008
|
+
}
|
|
1009
|
+
d.prepare(
|
|
1010
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '18')"
|
|
1011
|
+
).run();
|
|
1012
|
+
})();
|
|
1013
|
+
logger_default.info("[database] migrated schema to version 18 (session groups)");
|
|
1014
|
+
}
|
|
1015
|
+
if (version < 19) {
|
|
1016
|
+
d.transaction(() => {
|
|
1017
|
+
const cols = d.prepare("PRAGMA table_info(workflow_run_nodes)").all();
|
|
1018
|
+
for (const column of ["worktree_path", "worktree_name", "worktree_origin"]) {
|
|
1019
|
+
if (!cols.some((c) => c.name === column)) {
|
|
1020
|
+
d.exec(`ALTER TABLE workflow_run_nodes ADD COLUMN ${column} TEXT`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
d.prepare(
|
|
1024
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '19')"
|
|
1025
|
+
).run();
|
|
1026
|
+
})();
|
|
1027
|
+
logger_default.info("[database] migrated schema to version 19 (worktrees on run steps)");
|
|
1028
|
+
}
|
|
1029
|
+
if (version < 20) {
|
|
1030
|
+
d.transaction(() => {
|
|
1031
|
+
const cols = d.prepare("PRAGMA table_info(source_connections)").all();
|
|
1032
|
+
for (const column of ["signed_in_as", "signed_in_at"]) {
|
|
1033
|
+
if (!cols.some((c) => c.name === column)) {
|
|
1034
|
+
d.exec(`ALTER TABLE source_connections ADD COLUMN ${column} TEXT`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
d.prepare(
|
|
1038
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '20')"
|
|
1039
|
+
).run();
|
|
1040
|
+
})();
|
|
1041
|
+
logger_default.info("[database] migrated schema to version 20 (who a connection is signed in as)");
|
|
1042
|
+
}
|
|
940
1043
|
}
|
|
941
1044
|
var REVISIONED_TABLES = [
|
|
942
1045
|
"projects",
|
|
943
1046
|
"tasks",
|
|
944
1047
|
"workspaces",
|
|
1048
|
+
"session_groups",
|
|
945
1049
|
"remote_hosts",
|
|
946
1050
|
"agent_commands"
|
|
947
1051
|
];
|
|
@@ -978,9 +1082,15 @@ function verifySchema(d) {
|
|
|
978
1082
|
column: "sort_order",
|
|
979
1083
|
ddl: "ALTER TABLE sessions ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"
|
|
980
1084
|
},
|
|
1085
|
+
{ column: "group_id", ddl: "ALTER TABLE sessions ADD COLUMN group_id TEXT" },
|
|
981
1086
|
{ column: "worktree_name", ddl: "ALTER TABLE sessions ADD COLUMN worktree_name TEXT" },
|
|
982
1087
|
{ 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" }
|
|
1088
|
+
{ column: "shell_cwd", ddl: "ALTER TABLE sessions ADD COLUMN shell_cwd TEXT" },
|
|
1089
|
+
{ column: "head_commit", ddl: "ALTER TABLE sessions ADD COLUMN head_commit TEXT" },
|
|
1090
|
+
{
|
|
1091
|
+
column: "renamed_by_person",
|
|
1092
|
+
ddl: "ALTER TABLE sessions ADD COLUMN renamed_by_person INTEGER"
|
|
1093
|
+
}
|
|
984
1094
|
],
|
|
985
1095
|
agent_commands: [
|
|
986
1096
|
{
|
|
@@ -1014,6 +1124,7 @@ function verifySchema(d) {
|
|
|
1014
1124
|
}
|
|
1015
1125
|
],
|
|
1016
1126
|
workflow_run_nodes: [
|
|
1127
|
+
{ column: "waiting_for", ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN waiting_for TEXT" },
|
|
1017
1128
|
{ column: "agent_type", ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN agent_type TEXT" },
|
|
1018
1129
|
{
|
|
1019
1130
|
column: "project_name",
|
|
@@ -1093,6 +1204,7 @@ function loadConfig() {
|
|
|
1093
1204
|
const remoteHosts = loadRemoteHosts(d);
|
|
1094
1205
|
const tasks = loadTasks(d);
|
|
1095
1206
|
const workspaces = loadWorkspaces(d);
|
|
1207
|
+
const sessionGroups = loadSessionGroups(d);
|
|
1096
1208
|
return {
|
|
1097
1209
|
version: 1,
|
|
1098
1210
|
revision: readConfigRevision(d),
|
|
@@ -1102,7 +1214,8 @@ function loadConfig() {
|
|
|
1102
1214
|
workflows,
|
|
1103
1215
|
remoteHosts,
|
|
1104
1216
|
tasks,
|
|
1105
|
-
workspaces
|
|
1217
|
+
workspaces,
|
|
1218
|
+
sessionGroups
|
|
1106
1219
|
};
|
|
1107
1220
|
}
|
|
1108
1221
|
function loadDefaults(d) {
|
|
@@ -1135,6 +1248,8 @@ function loadDefaults(d) {
|
|
|
1135
1248
|
// terminal drew and waits. There is nothing to ask, and leaving it off made
|
|
1136
1249
|
// the whole thing invisible unless somebody went looking for a toggle.
|
|
1137
1250
|
reopenSessions: map.reopenSessions ?? true,
|
|
1251
|
+
// Off by default: nothing starts itself because someone installed an app.
|
|
1252
|
+
startAtLogin: map.startAtLogin ?? false,
|
|
1138
1253
|
// Saving iterates over every key in defaults, but loading is this explicit
|
|
1139
1254
|
// list — so a key missing here round-trips to nothing and its feature is
|
|
1140
1255
|
// silently inert.
|
|
@@ -1187,6 +1302,9 @@ function loadDefaults(d) {
|
|
|
1187
1302
|
...map.headlessRetentionMinutes !== void 0 && {
|
|
1188
1303
|
headlessRetentionMinutes: map.headlessRetentionMinutes
|
|
1189
1304
|
},
|
|
1305
|
+
...map.hasSeededDevServerWorkflow !== void 0 && {
|
|
1306
|
+
hasSeededDevServerWorkflow: map.hasSeededDevServerWorkflow
|
|
1307
|
+
},
|
|
1190
1308
|
...map.hasSeededDefaultTaskWorkflow !== void 0 && {
|
|
1191
1309
|
hasSeededDefaultTaskWorkflow: map.hasSeededDefaultTaskWorkflow
|
|
1192
1310
|
},
|
|
@@ -1250,6 +1368,10 @@ function loadTasks(d) {
|
|
|
1250
1368
|
const rows = d.prepare('SELECT * FROM tasks ORDER BY "order"').all();
|
|
1251
1369
|
return rows.map(rowToTask);
|
|
1252
1370
|
}
|
|
1371
|
+
function loadSessionGroups(d) {
|
|
1372
|
+
const rows = d.prepare('SELECT * FROM session_groups ORDER BY "order"').all();
|
|
1373
|
+
return rows.map(rowToSessionGroup);
|
|
1374
|
+
}
|
|
1253
1375
|
function loadWorkspaces(d) {
|
|
1254
1376
|
const rows = d.prepare('SELECT * FROM workspaces ORDER BY "order"').all();
|
|
1255
1377
|
return rows.map(rowToWorkspace);
|
|
@@ -1489,6 +1611,36 @@ function saveConfig(config) {
|
|
|
1489
1611
|
for (const ws of workspaces) {
|
|
1490
1612
|
insertWorkspace.run(ws.id, ws.name, ws.icon ?? null, ws.iconColor ?? null, ws.order, revision);
|
|
1491
1613
|
}
|
|
1614
|
+
const sessionGroups = config.sessionGroups ?? [];
|
|
1615
|
+
pruneMissing(
|
|
1616
|
+
d,
|
|
1617
|
+
"session_groups",
|
|
1618
|
+
"id",
|
|
1619
|
+
sessionGroups.map((g) => g.id),
|
|
1620
|
+
baseRevision
|
|
1621
|
+
);
|
|
1622
|
+
const insertSessionGroup = d.prepare(
|
|
1623
|
+
`INSERT INTO session_groups (id, name, icon, icon_color, "order", workspace_id, row_revision)
|
|
1624
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1625
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1626
|
+
row_revision = excluded.row_revision,
|
|
1627
|
+
name = excluded.name,
|
|
1628
|
+
icon = excluded.icon,
|
|
1629
|
+
icon_color = excluded.icon_color,
|
|
1630
|
+
"order" = excluded."order",
|
|
1631
|
+
workspace_id = excluded.workspace_id`
|
|
1632
|
+
);
|
|
1633
|
+
for (const g of sessionGroups) {
|
|
1634
|
+
insertSessionGroup.run(
|
|
1635
|
+
g.id,
|
|
1636
|
+
g.name,
|
|
1637
|
+
g.icon ?? null,
|
|
1638
|
+
g.iconColor ?? null,
|
|
1639
|
+
g.order,
|
|
1640
|
+
g.workspaceId,
|
|
1641
|
+
revision
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1492
1644
|
d.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES (?, ?)").run(
|
|
1493
1645
|
CONFIG_REVISION_KEY,
|
|
1494
1646
|
String(revision)
|
|
@@ -1546,6 +1698,16 @@ function rowToWorkflow(r) {
|
|
|
1546
1698
|
workspaceId: r.workspace_id ?? "personal"
|
|
1547
1699
|
};
|
|
1548
1700
|
}
|
|
1701
|
+
function rowToSessionGroup(r) {
|
|
1702
|
+
return {
|
|
1703
|
+
id: r.id,
|
|
1704
|
+
name: r.name,
|
|
1705
|
+
...r.icon != null && { icon: r.icon },
|
|
1706
|
+
...r.icon_color != null && { iconColor: r.icon_color },
|
|
1707
|
+
order: r.order,
|
|
1708
|
+
workspaceId: r.workspace_id ?? "personal"
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1549
1711
|
function rowToWorkspace(r) {
|
|
1550
1712
|
return {
|
|
1551
1713
|
id: r.id,
|
|
@@ -1562,8 +1724,8 @@ var ConfigManager = class {
|
|
|
1562
1724
|
dbWatcher = null;
|
|
1563
1725
|
debounceTimer = null;
|
|
1564
1726
|
cachedConfig = null;
|
|
1565
|
-
init(
|
|
1566
|
-
initDatabase(
|
|
1727
|
+
init(dataDir2) {
|
|
1728
|
+
initDatabase(dataDir2);
|
|
1567
1729
|
}
|
|
1568
1730
|
close() {
|
|
1569
1731
|
this.stopWatchingDb();
|
|
@@ -1681,33 +1843,47 @@ var V = {
|
|
|
1681
1843
|
url: safeUrl
|
|
1682
1844
|
};
|
|
1683
1845
|
|
|
1684
|
-
// src/
|
|
1846
|
+
// ../server/src/rpc-client.ts
|
|
1685
1847
|
import fs4 from "fs";
|
|
1686
1848
|
import path3 from "path";
|
|
1687
1849
|
import os2 from "os";
|
|
1688
1850
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1689
1851
|
import { WebSocket } from "ws";
|
|
1690
|
-
var
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1852
|
+
var dataDirOverride;
|
|
1853
|
+
function named(value) {
|
|
1854
|
+
return value?.trim() ? value : void 0;
|
|
1855
|
+
}
|
|
1856
|
+
function dataDir() {
|
|
1857
|
+
return dataDirOverride ?? named(process.env.VORN_DATA_DIR) ?? path3.join(os2.homedir(), ".vorn");
|
|
1858
|
+
}
|
|
1859
|
+
function portFile() {
|
|
1860
|
+
return path3.join(dataDir(), WS_PORT_FILENAME);
|
|
1861
|
+
}
|
|
1862
|
+
function localTokenFile() {
|
|
1863
|
+
return path3.join(dataDir(), LOCAL_TOKEN_FILENAME);
|
|
1864
|
+
}
|
|
1865
|
+
function tokenFileMissingMessage() {
|
|
1866
|
+
return `Vorn local credential not found (${localTokenFile()}).
|
|
1694
1867
|
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,
|
|
1868
|
+
Vorn is not running. Start Vorn (or \`vorn server serve\`) and try again.
|
|
1869
|
+
If the server runs with --data-dir, pass the same --data-dir here, or set
|
|
1870
|
+
VORN_DATA_DIR to that directory -- which is how anything that is not the CLI,
|
|
1871
|
+
MCP included, reaches a server that moved.`;
|
|
1872
|
+
}
|
|
1697
1873
|
function readLocalToken() {
|
|
1698
1874
|
try {
|
|
1699
|
-
const token = fs4.readFileSync(
|
|
1875
|
+
const token = fs4.readFileSync(localTokenFile(), "utf-8").trim();
|
|
1700
1876
|
if (!token) throw new Error("empty");
|
|
1701
1877
|
return token;
|
|
1702
1878
|
} catch {
|
|
1703
|
-
throw new Error(
|
|
1879
|
+
throw new Error(tokenFileMissingMessage());
|
|
1704
1880
|
}
|
|
1705
1881
|
}
|
|
1706
1882
|
function connection() {
|
|
1707
1883
|
const result = readPort();
|
|
1708
1884
|
if (!result.port) {
|
|
1709
1885
|
const reason = "reason" in result ? result.reason : "missing";
|
|
1710
|
-
throw new Error(reason === "invalid" ?
|
|
1886
|
+
throw new Error(reason === "invalid" ? portFileInvalidMessage() : portFileMissingMessage());
|
|
1711
1887
|
}
|
|
1712
1888
|
return {
|
|
1713
1889
|
url: `ws://127.0.0.1:${result.port}/ws`,
|
|
@@ -1716,21 +1892,42 @@ function connection() {
|
|
|
1716
1892
|
}
|
|
1717
1893
|
var TIMEOUT_MS = 1e4;
|
|
1718
1894
|
var IS_WIN = process.platform === "win32";
|
|
1719
|
-
|
|
1895
|
+
function portFileMissingMessage() {
|
|
1896
|
+
const file = portFile();
|
|
1897
|
+
return IS_WIN ? `Vorn port file not found (${file}).
|
|
1720
1898
|
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
|
-
|
|
1899
|
+
To fix, in PowerShell, find the Vorn process and its listening port:
|
|
1900
|
+
Get-NetTCPConnection -State Listen -OwningProcess (Get-Process Vorn).Id | Select LocalPort
|
|
1723
1901
|
Then write the WS port to the file:
|
|
1724
|
-
|
|
1725
|
-
Or restart Vorn to regenerate it.` : `Vorn port file not found (
|
|
1902
|
+
'{"port":<PORT>,"pid":<PID>}' | Set-Content -Path "${file}"
|
|
1903
|
+
Or restart Vorn to regenerate it.` : `Vorn port file not found (${file}).
|
|
1726
1904
|
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
1727
1905
|
To fix, run: lsof -iTCP -sTCP:LISTEN -P | grep Vorn
|
|
1728
1906
|
Then write the WS port (the one on *:<port>) to the file:
|
|
1729
|
-
echo '{"port":<PORT>,"pid":<PID>}' >
|
|
1907
|
+
echo '{"port":<PORT>,"pid":<PID>}' > "${file}"
|
|
1730
1908
|
Or restart Vorn to regenerate it.`;
|
|
1731
|
-
|
|
1909
|
+
}
|
|
1910
|
+
function portFileInvalidMessage() {
|
|
1911
|
+
const file = portFile();
|
|
1912
|
+
return `Vorn port file exists but contains invalid data (${file}).
|
|
1732
1913
|
Delete it and restart Vorn, or overwrite it with the correct port:
|
|
1733
|
-
${IS_WIN ?
|
|
1914
|
+
${IS_WIN ? `Remove-Item "${file}"` : `rm "${file}"`}`;
|
|
1915
|
+
}
|
|
1916
|
+
function closedBeforeAnswering(code) {
|
|
1917
|
+
if (code === CLOSE_UNAUTHENTICATED || code === CLOSE_CREDENTIAL_REJECTED) {
|
|
1918
|
+
return `A Vorn server on this port refused the credential in ${localTokenFile()}.
|
|
1919
|
+
Another server is listening on it with its own data directory, which a dev build
|
|
1920
|
+
running beside the app does. Point at that one with --data-dir (or VORN_DATA_DIR),
|
|
1921
|
+
or stop it.`;
|
|
1922
|
+
}
|
|
1923
|
+
return `The server closed the connection before answering (code ${code}).`;
|
|
1924
|
+
}
|
|
1925
|
+
function explain(message) {
|
|
1926
|
+
if (!message.startsWith("Method not found:")) return message;
|
|
1927
|
+
const method = message.slice("Method not found:".length).trim();
|
|
1928
|
+
return `This server does not have ${method}, so it is older than the vorn command asking for it.
|
|
1929
|
+
Restart Vorn to pick up the newer server, or run this against the matching build.`;
|
|
1930
|
+
}
|
|
1734
1931
|
var rpcId = 0;
|
|
1735
1932
|
var cachedPort = null;
|
|
1736
1933
|
var cacheTimestamp = 0;
|
|
@@ -1783,7 +1980,11 @@ function discoverPort() {
|
|
|
1783
1980
|
}
|
|
1784
1981
|
return null;
|
|
1785
1982
|
}
|
|
1983
|
+
function discoveryAllowed() {
|
|
1984
|
+
return dataDirOverride === void 0 && named(process.env.VORN_DATA_DIR) === void 0;
|
|
1985
|
+
}
|
|
1786
1986
|
function discoverAndHeal() {
|
|
1987
|
+
if (!discoveryAllowed()) return { port: null, reason: "missing" };
|
|
1787
1988
|
const now = Date.now();
|
|
1788
1989
|
if (cachedPort && now - cacheTimestamp < CACHE_TTL_MS) return { port: cachedPort };
|
|
1789
1990
|
const discovered = discoverPort();
|
|
@@ -1791,8 +1992,8 @@ function discoverAndHeal() {
|
|
|
1791
1992
|
cacheTimestamp = now;
|
|
1792
1993
|
if (discovered) {
|
|
1793
1994
|
try {
|
|
1794
|
-
fs4.mkdirSync(
|
|
1795
|
-
fs4.writeFileSync(
|
|
1995
|
+
fs4.mkdirSync(dataDir(), { recursive: true });
|
|
1996
|
+
fs4.writeFileSync(portFile(), JSON.stringify({ port: discovered }), "utf-8");
|
|
1796
1997
|
} catch {
|
|
1797
1998
|
}
|
|
1798
1999
|
return { port: discovered };
|
|
@@ -1801,7 +2002,7 @@ function discoverAndHeal() {
|
|
|
1801
2002
|
}
|
|
1802
2003
|
function readPort() {
|
|
1803
2004
|
try {
|
|
1804
|
-
const raw = fs4.readFileSync(
|
|
2005
|
+
const raw = fs4.readFileSync(portFile(), "utf-8").trim();
|
|
1805
2006
|
if (!raw) return { port: null, reason: "invalid" };
|
|
1806
2007
|
if (raw.startsWith("{")) {
|
|
1807
2008
|
const parsed = JSON.parse(raw);
|
|
@@ -1843,15 +2044,19 @@ async function rpcCall(method, params, timeoutMs = TIMEOUT_MS) {
|
|
|
1843
2044
|
const msg = JSON.parse(raw.toString());
|
|
1844
2045
|
if (msg.id !== id) return;
|
|
1845
2046
|
clearTimeout(timer);
|
|
1846
|
-
ws.close();
|
|
1847
2047
|
if (msg.error) {
|
|
1848
|
-
reject(new Error(msg.error.message));
|
|
2048
|
+
reject(new Error(explain(msg.error.message)));
|
|
1849
2049
|
} else {
|
|
1850
2050
|
resolve(msg.result);
|
|
1851
2051
|
}
|
|
2052
|
+
ws.close();
|
|
1852
2053
|
} catch {
|
|
1853
2054
|
}
|
|
1854
2055
|
});
|
|
2056
|
+
ws.on("close", (code) => {
|
|
2057
|
+
clearTimeout(timer);
|
|
2058
|
+
reject(new Error(closedBeforeAnswering(code)));
|
|
2059
|
+
});
|
|
1855
2060
|
ws.on("error", (err) => {
|
|
1856
2061
|
clearTimeout(timer);
|
|
1857
2062
|
reject(new Error(`Cannot connect to Vorn server: ${err.message}. Is the app running?`));
|
|
@@ -1980,6 +2185,9 @@ async function listWorkflowRunsByTask(taskId, limit = 20) {
|
|
|
1980
2185
|
limit
|
|
1981
2186
|
});
|
|
1982
2187
|
}
|
|
2188
|
+
async function listRunsWithWaitingGates() {
|
|
2189
|
+
return rpcCall("workflowRun:listWaiting");
|
|
2190
|
+
}
|
|
1983
2191
|
async function listAllWorkflowRuns(workspaceId, limit = 50) {
|
|
1984
2192
|
return rpcCall("workflowRun:listAll", {
|
|
1985
2193
|
workspaceId,
|
|
@@ -2750,6 +2958,13 @@ function registerSessionTools(server) {
|
|
|
2750
2958
|
|
|
2751
2959
|
// src/tools/workflows.ts
|
|
2752
2960
|
import crypto3 from "crypto";
|
|
2961
|
+
|
|
2962
|
+
// ../shared/src/workflow-graph.ts
|
|
2963
|
+
function isSignInWait(state) {
|
|
2964
|
+
return state.status === "waiting" && state.waitingFor === "signIn";
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
// src/tools/workflows.ts
|
|
2753
2968
|
import { z as z5 } from "zod";
|
|
2754
2969
|
|
|
2755
2970
|
// ../shared/src/workflow-portability.ts
|
|
@@ -2780,16 +2995,18 @@ function boundConnectionKey(node, config) {
|
|
|
2780
2995
|
if (node.type === "trigger" && config.triggerType === "connectorPoll") return "connectionId";
|
|
2781
2996
|
if (node.type === "callConnectorAction") return "connectionId";
|
|
2782
2997
|
if (node.type === "httpRequest") return "profileConnectionId";
|
|
2998
|
+
if (node.type === "script") return "secretsFrom";
|
|
2783
2999
|
return null;
|
|
2784
3000
|
}
|
|
3001
|
+
var OPTIONAL_CONNECTION_KEYS = /* @__PURE__ */ new Set(["profileConnectionId", "secretsFrom"]);
|
|
2785
3002
|
function resolveRequirement(requirement, connections) {
|
|
2786
3003
|
const candidates = connections.filter(
|
|
2787
3004
|
(connection2) => requirement.kind === "httpProfile" ? connectorOf(connection2) === HTTP_PROFILE_CONNECTOR : requirement.connectorId !== "" && connectorOf(connection2) === requirement.connectorId
|
|
2788
3005
|
);
|
|
2789
3006
|
if (candidates.length === 0) return void 0;
|
|
2790
3007
|
if (requirement.name !== "") {
|
|
2791
|
-
const
|
|
2792
|
-
if (
|
|
3008
|
+
const named2 = candidates.filter((connection2) => connection2.name === requirement.name);
|
|
3009
|
+
if (named2.length === 1) return named2[0].id;
|
|
2793
3010
|
}
|
|
2794
3011
|
return candidates.length === 1 ? candidates[0].id : void 0;
|
|
2795
3012
|
}
|
|
@@ -2810,11 +3027,10 @@ function toPortable(workflow, projectPath, connections = []) {
|
|
|
2810
3027
|
config[`projectName`] = PROJECT_NAME_TOKEN;
|
|
2811
3028
|
}
|
|
2812
3029
|
delete config.remoteHostId;
|
|
2813
|
-
delete config.secretsFrom;
|
|
2814
3030
|
}
|
|
2815
3031
|
const key = boundConnectionKey(node, config);
|
|
2816
3032
|
const bound = key === null ? "" : config[key];
|
|
2817
|
-
const unbound = key !== null && key
|
|
3033
|
+
const unbound = key !== null && !OPTIONAL_CONNECTION_KEYS.has(key) && bound === "";
|
|
2818
3034
|
if (key !== null && (typeof bound === "string" && bound !== "" || unbound)) {
|
|
2819
3035
|
const source2 = connections.find((connection2) => connection2.id === bound);
|
|
2820
3036
|
const event = config.event;
|
|
@@ -2825,10 +3041,11 @@ function toPortable(workflow, projectPath, connections = []) {
|
|
|
2825
3041
|
nodeId: node.id,
|
|
2826
3042
|
connectorId: source2 ? connectorOf(source2) : typeof declared === "string" ? declared : "",
|
|
2827
3043
|
name: source2?.name ?? "",
|
|
2828
|
-
...typeof event === "string" && event !== "" && { event }
|
|
3044
|
+
...typeof event === "string" && event !== "" && { event },
|
|
3045
|
+
...key === "secretsFrom" && { key }
|
|
2829
3046
|
}
|
|
2830
3047
|
);
|
|
2831
|
-
if (key
|
|
3048
|
+
if (OPTIONAL_CONNECTION_KEYS.has(key)) delete config[key];
|
|
2832
3049
|
else config[key] = "";
|
|
2833
3050
|
}
|
|
2834
3051
|
return { ...node, config };
|
|
@@ -2859,9 +3076,12 @@ function replacePath(value, projectPath) {
|
|
|
2859
3076
|
function unresolvedRequirements(portable, connections) {
|
|
2860
3077
|
const present = new Set(portable.nodes.map((node) => node.id));
|
|
2861
3078
|
return (portable.requires ?? []).filter(
|
|
2862
|
-
(requirement) => present.has(requirement.nodeId) && resolveRequirement(requirement, connections) === void 0
|
|
3079
|
+
(requirement) => present.has(requirement.nodeId) && (bindsOnlyByHand(requirement) || resolveRequirement(requirement, connections) === void 0)
|
|
2863
3080
|
);
|
|
2864
3081
|
}
|
|
3082
|
+
function bindsOnlyByHand(requirement) {
|
|
3083
|
+
return requirement.kind === "connection" && requirement.key === "secretsFrom";
|
|
3084
|
+
}
|
|
2865
3085
|
function fromPortable(portable, bundle, project, connections = [], mintToken = () => crypto.randomUUID()) {
|
|
2866
3086
|
const bindings = /* @__PURE__ */ new Map();
|
|
2867
3087
|
for (const requirement of portable.requires ?? []) {
|
|
@@ -2869,16 +3089,16 @@ function fromPortable(portable, bundle, project, connections = [], mintToken = (
|
|
|
2869
3089
|
}
|
|
2870
3090
|
const nodes = portable.nodes.map((node) => {
|
|
2871
3091
|
const config = { ...node.config };
|
|
2872
|
-
|
|
2873
|
-
|
|
3092
|
+
const key = boundConnectionKey(node, config);
|
|
3093
|
+
if (key !== null && OPTIONAL_CONNECTION_KEYS.has(key)) delete config[key];
|
|
3094
|
+
for (const [key2, value] of Object.entries(config)) {
|
|
2874
3095
|
if (typeof value !== "string") continue;
|
|
2875
|
-
config[
|
|
3096
|
+
config[key2] = value.split(PROJECT_PATH_TOKEN).join(project.path.replace(/[/\\]+$/, "")).split(PROJECT_NAME_TOKEN).join(project.name);
|
|
2876
3097
|
}
|
|
2877
3098
|
for (const requirement of bindings.get(node.id) ?? []) {
|
|
3099
|
+
if (bindsOnlyByHand(requirement) || key === null) continue;
|
|
2878
3100
|
const resolved = resolveRequirement(requirement, connections);
|
|
2879
|
-
if (resolved
|
|
2880
|
-
if (requirement.kind === "httpProfile") config.profileConnectionId = resolved;
|
|
2881
|
-
else config.connectionId = resolved;
|
|
3101
|
+
if (resolved !== void 0) config[key] = resolved;
|
|
2882
3102
|
}
|
|
2883
3103
|
if (node.type === "trigger" && config.triggerType === "webhook" && !config.token) {
|
|
2884
3104
|
config.token = mintToken();
|
|
@@ -3200,6 +3420,64 @@ function resolveWorkflowId(args) {
|
|
|
3200
3420
|
}
|
|
3201
3421
|
return { id };
|
|
3202
3422
|
}
|
|
3423
|
+
function approvalNode(workflow, nodeId) {
|
|
3424
|
+
return workflow?.nodes.find((n) => n.id === nodeId && n.type === "approval");
|
|
3425
|
+
}
|
|
3426
|
+
function askedBy(node) {
|
|
3427
|
+
return node?.config?.message?.trim() || void 0;
|
|
3428
|
+
}
|
|
3429
|
+
function gateMessage(workflow, nodeId) {
|
|
3430
|
+
return askedBy(approvalNode(workflow, nodeId));
|
|
3431
|
+
}
|
|
3432
|
+
function annotateWaitingGates(runs, workflows) {
|
|
3433
|
+
return runs.map((run) => {
|
|
3434
|
+
if (!run.nodeStates.some((n) => n.status === "waiting")) return run;
|
|
3435
|
+
const workflow = workflows.find((w) => w.id === run.workflowId);
|
|
3436
|
+
return {
|
|
3437
|
+
...run,
|
|
3438
|
+
nodeStates: run.nodeStates.map((state) => {
|
|
3439
|
+
if (state.status !== "waiting") return state;
|
|
3440
|
+
if (isSignInWait(state)) {
|
|
3441
|
+
return {
|
|
3442
|
+
...state,
|
|
3443
|
+
asks: "Sign in to its connection in the Vorn app, and this step runs again"
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
const asks = gateMessage(workflow, state.nodeId);
|
|
3447
|
+
return asks ? { ...state, asks } : state;
|
|
3448
|
+
})
|
|
3449
|
+
};
|
|
3450
|
+
});
|
|
3451
|
+
}
|
|
3452
|
+
async function runById(runId) {
|
|
3453
|
+
const recent = (await listAllWorkflowRuns(void 0, 500)).find((r) => r.runId === runId);
|
|
3454
|
+
if (recent) return recent;
|
|
3455
|
+
return (await listRunsWithWaitingGates()).find((r) => r.runId === runId);
|
|
3456
|
+
}
|
|
3457
|
+
function resolveGateTarget(run, nodeId, decision = "approve") {
|
|
3458
|
+
const parked = run.nodeStates.filter((n) => n.status === "waiting");
|
|
3459
|
+
const answerable = parked.filter((n) => decision === "reject" || !isSignInWait(n));
|
|
3460
|
+
const waiting = answerable.map((n) => n.nodeId);
|
|
3461
|
+
const signIns = parked.filter((n) => !answerable.includes(n)).map((n) => n.nodeId);
|
|
3462
|
+
if (nodeId) {
|
|
3463
|
+
if (signIns.includes(nodeId)) {
|
|
3464
|
+
return {
|
|
3465
|
+
error: `node "${nodeId}" is waiting for a sign-in in the Vorn app, not for an approval`
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
if (waiting.includes(nodeId)) return { nodeId };
|
|
3469
|
+
return {
|
|
3470
|
+
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`
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
if (waiting.length === 1) return { nodeId: waiting[0] };
|
|
3474
|
+
if (waiting.length === 0) {
|
|
3475
|
+
return {
|
|
3476
|
+
error: signIns.length ? "this run is waiting for a sign-in in the Vorn app, not for an approval" : "no node in this run is waiting on a gate"
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
return { error: `${waiting.length} nodes are waiting \u2014 pass node_id: ${waiting.join(", ")}` };
|
|
3480
|
+
}
|
|
3203
3481
|
async function listPortableConnections() {
|
|
3204
3482
|
try {
|
|
3205
3483
|
return await rpcCall("connection:list", { connectorId: void 0 });
|
|
@@ -3353,7 +3631,7 @@ function registerWorkflowTools(server) {
|
|
|
3353
3631
|
);
|
|
3354
3632
|
server.tool(
|
|
3355
3633
|
"list_workflow_runs",
|
|
3356
|
-
"List workflow execution history. Filter by workflow_id or task_id.",
|
|
3634
|
+
"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.",
|
|
3357
3635
|
{
|
|
3358
3636
|
workflow_id: V.id.optional().describe("Filter by workflow ID"),
|
|
3359
3637
|
task_id: V.id.optional().describe("Filter by task ID (runs triggered by this task)"),
|
|
@@ -3366,28 +3644,28 @@ function registerWorkflowTools(server) {
|
|
|
3366
3644
|
isError: true
|
|
3367
3645
|
};
|
|
3368
3646
|
}
|
|
3647
|
+
const withGates = async (runs) => runs.some((r) => r.nodeStates.some((n) => n.status === "waiting")) ? annotateWaitingGates(runs, await dbListWorkflows()) : runs;
|
|
3369
3648
|
if (args.task_id) {
|
|
3370
|
-
const runs = await listWorkflowRunsByTask(args.task_id, args.limit ?? 20);
|
|
3649
|
+
const runs = await withGates(await listWorkflowRunsByTask(args.task_id, args.limit ?? 20));
|
|
3371
3650
|
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
3372
3651
|
}
|
|
3373
3652
|
if (args.workflow_id) {
|
|
3374
|
-
const runs = await listWorkflowRuns(args.workflow_id, args.limit ?? 20);
|
|
3653
|
+
const runs = await withGates(await listWorkflowRuns(args.workflow_id, args.limit ?? 20));
|
|
3375
3654
|
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
3376
3655
|
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
};
|
|
3656
|
+
const parked = (await listRunsWithWaitingGates()).slice(0, args.limit ?? 20);
|
|
3657
|
+
const waiting = await withGates(parked);
|
|
3658
|
+
return { content: [{ type: "text", text: JSON.stringify(waiting, null, 2) }] };
|
|
3381
3659
|
}
|
|
3382
3660
|
);
|
|
3383
3661
|
server.tool(
|
|
3384
3662
|
"stop_workflow_run",
|
|
3385
|
-
"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.
|
|
3663
|
+
"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.",
|
|
3386
3664
|
{
|
|
3387
3665
|
run_id: V.id.describe("Run ID (from list_workflow_runs)")
|
|
3388
3666
|
},
|
|
3389
3667
|
async (args) => {
|
|
3390
|
-
const run =
|
|
3668
|
+
const run = await runById(args.run_id);
|
|
3391
3669
|
if (!run) {
|
|
3392
3670
|
return {
|
|
3393
3671
|
content: [
|
|
@@ -3432,6 +3710,74 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3432
3710
|
};
|
|
3433
3711
|
}
|
|
3434
3712
|
);
|
|
3713
|
+
server.tool(
|
|
3714
|
+
"resolve_gate",
|
|
3715
|
+
"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.",
|
|
3716
|
+
{
|
|
3717
|
+
run_id: V.id.describe("Run ID (from list_workflow_runs)"),
|
|
3718
|
+
decision: z5.enum(["approve", "reject"]).describe("approve lets the run go on; reject ends it"),
|
|
3719
|
+
node_id: V.id.optional().describe("The waiting node, when a run has more than one gate open")
|
|
3720
|
+
},
|
|
3721
|
+
async (args) => {
|
|
3722
|
+
const run = await runById(args.run_id);
|
|
3723
|
+
if (!run) {
|
|
3724
|
+
return {
|
|
3725
|
+
content: [
|
|
3726
|
+
{
|
|
3727
|
+
type: "text",
|
|
3728
|
+
text: `Error: no run "${args.run_id}" in the recent history, and none parked on a gate. Check list_workflow_runs.`
|
|
3729
|
+
}
|
|
3730
|
+
],
|
|
3731
|
+
isError: true
|
|
3732
|
+
};
|
|
3733
|
+
}
|
|
3734
|
+
if (run.status !== "running") {
|
|
3735
|
+
return {
|
|
3736
|
+
content: [
|
|
3737
|
+
{
|
|
3738
|
+
type: "text",
|
|
3739
|
+
text: `Run ${args.run_id} already finished (${run.status}) \u2014 no gate to answer.`
|
|
3740
|
+
}
|
|
3741
|
+
]
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
const target = resolveGateTarget(run, args.node_id, args.decision);
|
|
3745
|
+
if ("error" in target) {
|
|
3746
|
+
return {
|
|
3747
|
+
content: [{ type: "text", text: `Error: ${target.error}` }],
|
|
3748
|
+
isError: true
|
|
3749
|
+
};
|
|
3750
|
+
}
|
|
3751
|
+
const workflow = (await dbListWorkflows()).find((w) => w.id === run.workflowId);
|
|
3752
|
+
const gateNode = approvalNode(workflow, target.nodeId);
|
|
3753
|
+
const asked = askedBy(gateNode);
|
|
3754
|
+
try {
|
|
3755
|
+
await rpcCall("workflow:resolveGate", {
|
|
3756
|
+
runId: args.run_id,
|
|
3757
|
+
nodeId: target.nodeId,
|
|
3758
|
+
decision: args.decision
|
|
3759
|
+
});
|
|
3760
|
+
} catch (err) {
|
|
3761
|
+
return {
|
|
3762
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
|
|
3763
|
+
isError: true
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
const gate = gateNode?.label ?? target.nodeId;
|
|
3767
|
+
return {
|
|
3768
|
+
content: [
|
|
3769
|
+
{
|
|
3770
|
+
type: "text",
|
|
3771
|
+
text: `${args.decision === "approve" ? "Approved" : "Rejected"} "${gate}" on run ${args.run_id}${run.workflowName ? ` of "${run.workflowName}"` : ""}.${asked ? `
|
|
3772
|
+
|
|
3773
|
+
What it asked: ${asked}` : ""}
|
|
3774
|
+
|
|
3775
|
+
The decision went out; the instance holding the run acts on it, so a desktop has to be open. Confirm with list_workflow_runs.`
|
|
3776
|
+
}
|
|
3777
|
+
]
|
|
3778
|
+
};
|
|
3779
|
+
}
|
|
3780
|
+
);
|
|
3435
3781
|
server.tool(
|
|
3436
3782
|
"get_workflow_schedule",
|
|
3437
3783
|
"Get scheduler info for a workflow: execution log or next scheduled run. Requires the Vorn app to be running.",
|
|
@@ -3472,7 +3818,7 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3472
3818
|
);
|
|
3473
3819
|
server.tool(
|
|
3474
3820
|
"execute_workflow",
|
|
3475
|
-
"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.",
|
|
3821
|
+
"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.",
|
|
3476
3822
|
{
|
|
3477
3823
|
workflow_id: V.id.describe("Workflow ID (from list_workflows)"),
|
|
3478
3824
|
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>}})")
|
|
@@ -3791,27 +4137,43 @@ var failure = (message) => ({
|
|
|
3791
4137
|
function summarize(entry) {
|
|
3792
4138
|
return { type: entry.type, label: entry.label };
|
|
3793
4139
|
}
|
|
4140
|
+
function andList(values) {
|
|
4141
|
+
if (values.length <= 1) return values[0] ?? "";
|
|
4142
|
+
return `${values.slice(0, -1).join(", ")} and ${values[values.length - 1]}`;
|
|
4143
|
+
}
|
|
4144
|
+
function contributionSummary(contributes) {
|
|
4145
|
+
const named2 = (entries) => (entries ?? []).map((entry) => ({ id: entry.id, title: entry.title }));
|
|
4146
|
+
return {
|
|
4147
|
+
panes: named2(contributes?.panes),
|
|
4148
|
+
footers: named2(contributes?.footers),
|
|
4149
|
+
linkHandlers: named2(contributes?.linkHandlers)
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
3794
4152
|
function registerConnectorTools(server) {
|
|
3795
4153
|
server.tool(
|
|
3796
4154
|
"list_connectors",
|
|
3797
|
-
"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
|
|
4155
|
+
"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.",
|
|
3798
4156
|
{
|
|
3799
|
-
installable_only: z7.boolean().optional().describe("Only connectors that are not set up yet")
|
|
4157
|
+
installable_only: z7.boolean().optional().describe("Only connectors that are not set up yet"),
|
|
4158
|
+
kind: z7.enum(["connector", "extension"]).optional().describe("Only one kind: what polls a service, or what shows on a card")
|
|
3800
4159
|
},
|
|
3801
4160
|
async (args) => {
|
|
3802
|
-
const [builtIns, snapshot, connections, statuses] = await Promise.all([
|
|
4161
|
+
const [builtIns, snapshot, connections, statuses, packs] = await Promise.all([
|
|
3803
4162
|
rpcCall("connector:list"),
|
|
3804
4163
|
rpcCall("connector:catalog"),
|
|
3805
4164
|
rpcCall("connection:list", { connectorId: void 0 }),
|
|
3806
|
-
rpcCall("connector:status")
|
|
4165
|
+
rpcCall("connector:status"),
|
|
4166
|
+
rpcCall("connector:listPacks")
|
|
3807
4167
|
]);
|
|
3808
4168
|
const countFor = (id) => connections.filter((conn) => connectionConnectorId(conn) === id).length;
|
|
3809
4169
|
const statusFor = (id) => statuses.find((s) => s.connectorId === id);
|
|
4170
|
+
const packFor = (id) => packs.find((pack) => pack.id === id);
|
|
3810
4171
|
const entries = [
|
|
3811
4172
|
...builtIns.map((c) => ({
|
|
3812
4173
|
id: c.id,
|
|
3813
4174
|
name: c.name,
|
|
3814
4175
|
source: "built-in",
|
|
4176
|
+
kind: "connector",
|
|
3815
4177
|
capabilities: c.capabilities,
|
|
3816
4178
|
connections: countFor(c.id),
|
|
3817
4179
|
// Only meaningful for connectors that authenticate up front; the
|
|
@@ -3821,25 +4183,49 @@ function registerConnectorTools(server) {
|
|
|
3821
4183
|
...statusFor(c.id).message && { authMessage: statusFor(c.id).message }
|
|
3822
4184
|
}
|
|
3823
4185
|
})),
|
|
3824
|
-
...snapshot.items.map((entry) =>
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
4186
|
+
...snapshot.items.map((entry) => {
|
|
4187
|
+
const pack = packFor(entry.id);
|
|
4188
|
+
const kind = pack?.kind ?? entry.kind ?? "connector";
|
|
4189
|
+
return {
|
|
4190
|
+
id: entry.id,
|
|
4191
|
+
name: entry.name,
|
|
4192
|
+
source: "package",
|
|
4193
|
+
kind,
|
|
4194
|
+
description: entry.description,
|
|
4195
|
+
package: entry.packageName,
|
|
4196
|
+
...entry.version && { version: entry.version },
|
|
4197
|
+
capabilities: entry.capabilities,
|
|
4198
|
+
connections: countFor(entry.id),
|
|
4199
|
+
...pack && { installed: pack.version },
|
|
4200
|
+
...entry.auth && { auth: entry.auth },
|
|
4201
|
+
// Generated upstream from the connector's own manifest, so an agent
|
|
4202
|
+
// can tell whether a connector is worth installing without launching
|
|
4203
|
+
// it — which for a list of twenty would be twenty npx processes.
|
|
4204
|
+
...entry.triggers && { triggers: entry.triggers.map(summarize) },
|
|
4205
|
+
...entry.actions && { actions: entry.actions.map(summarize) },
|
|
4206
|
+
...entry.env && { env: entry.env.map((e) => e.name) },
|
|
4207
|
+
// Only what was stated: an empty list would read as "adds nothing",
|
|
4208
|
+
// which is a claim an older catalog never made.
|
|
4209
|
+
...kind === "extension" && {
|
|
4210
|
+
...(pack?.contributes ?? entry.contributes) && {
|
|
4211
|
+
contributes: contributionSummary(pack?.contributes ?? entry.contributes)
|
|
4212
|
+
},
|
|
4213
|
+
...(pack?.permissions ?? entry.permissions) && {
|
|
4214
|
+
permissions: pack?.permissions ?? entry.permissions
|
|
4215
|
+
},
|
|
4216
|
+
...(pack?.activates ?? entry.activates) && {
|
|
4217
|
+
activates: pack?.activates ?? entry.activates
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
};
|
|
4221
|
+
})
|
|
3841
4222
|
];
|
|
3842
|
-
|
|
4223
|
+
const ofKind = args.kind ? entries.filter((e) => e.kind === args.kind) : entries;
|
|
4224
|
+
return json(
|
|
4225
|
+
args.installable_only ? ofKind.filter(
|
|
4226
|
+
(e) => e.kind === "extension" ? !("installed" in e && e.installed) : e.connections === 0
|
|
4227
|
+
) : ofKind
|
|
4228
|
+
);
|
|
3843
4229
|
}
|
|
3844
4230
|
);
|
|
3845
4231
|
server.tool(
|
|
@@ -3889,7 +4275,7 @@ function registerConnectorTools(server) {
|
|
|
3889
4275
|
);
|
|
3890
4276
|
server.tool(
|
|
3891
4277
|
"inspect_connector_package",
|
|
3892
|
-
"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
|
|
4278
|
+
"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.",
|
|
3893
4279
|
{
|
|
3894
4280
|
package: V.shortText.describe(
|
|
3895
4281
|
'npm package name, or a command to run a local build (e.g. "node /path/to/dist/index.js")'
|
|
@@ -3903,10 +4289,10 @@ function registerConnectorTools(server) {
|
|
|
3903
4289
|
);
|
|
3904
4290
|
server.tool(
|
|
3905
4291
|
"install_connector",
|
|
3906
|
-
"Install a connector from a pack file, from the catalog, or
|
|
4292
|
+
"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.",
|
|
3907
4293
|
{
|
|
3908
4294
|
connector_id: V.id.optional().describe("Catalog connector id (from list_connectors). Use this or package."),
|
|
3909
|
-
package: V.shortText.optional().describe("
|
|
4295
|
+
package: V.shortText.optional().describe("Launch command the connection runs, or a package name to run with npx"),
|
|
3910
4296
|
pack_path: V.shortText.optional().describe(
|
|
3911
4297
|
"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."
|
|
3912
4298
|
),
|
|
@@ -3932,6 +4318,38 @@ function registerConnectorTools(server) {
|
|
|
3932
4318
|
});
|
|
3933
4319
|
if (!outcome.ok) return failure(`The pack was refused: ${outcome.error}`);
|
|
3934
4320
|
installed = outcome.pack;
|
|
4321
|
+
} else if (entry?.kind === "extension") {
|
|
4322
|
+
if (!entry.packUrl) {
|
|
4323
|
+
return failure(
|
|
4324
|
+
`${entry.name} is in the catalog but no release has published a pack for it yet.`
|
|
4325
|
+
);
|
|
4326
|
+
}
|
|
4327
|
+
const outcome = await rpcCall("connector:installPack", {
|
|
4328
|
+
kind: "url",
|
|
4329
|
+
url: entry.packUrl,
|
|
4330
|
+
...entry.sha256 && { sha256: entry.sha256 }
|
|
4331
|
+
});
|
|
4332
|
+
if (!outcome.ok) return failure(`The pack was refused: ${outcome.error}`);
|
|
4333
|
+
installed = outcome.pack;
|
|
4334
|
+
}
|
|
4335
|
+
if (installed?.kind === "extension") {
|
|
4336
|
+
const ignored = [
|
|
4337
|
+
args.trigger !== void 0 && "trigger",
|
|
4338
|
+
args.sync_interval_minutes !== void 0 && "sync_interval_minutes",
|
|
4339
|
+
args.env !== void 0 && "env",
|
|
4340
|
+
args.name !== void 0 && "name",
|
|
4341
|
+
args.project !== void 0 && "project"
|
|
4342
|
+
].filter(Boolean);
|
|
4343
|
+
return json({
|
|
4344
|
+
installed: installed.name,
|
|
4345
|
+
kind: "extension",
|
|
4346
|
+
version: installed.version,
|
|
4347
|
+
path: installed.path,
|
|
4348
|
+
contributes: installed.contributes ?? {},
|
|
4349
|
+
permissions: installed.permissions ?? [],
|
|
4350
|
+
...installed.activates && { activates: installed.activates },
|
|
4351
|
+
note: "Extensions have no connection: they show on the cards their activation names." + (ignored.length > 0 ? ` Ignored ${andList(ignored)}, which only a connection uses.` : "")
|
|
4352
|
+
});
|
|
3935
4353
|
}
|
|
3936
4354
|
const target = installed ? packLaunch(installed) : entry?.launch ?? args.package;
|
|
3937
4355
|
if (!target) return failure("Provide either connector_id, package, or pack_path.");
|
|
@@ -4330,6 +4748,7 @@ function registerDeviceTools(server) {
|
|
|
4330
4748
|
sessionId: id,
|
|
4331
4749
|
udid: args.udid
|
|
4332
4750
|
});
|
|
4751
|
+
if (!r.ok) throw new Error(r.message);
|
|
4333
4752
|
return {
|
|
4334
4753
|
content: [{ type: "text", text: `Claimed ${r.name} (${r.udid}).` }]
|
|
4335
4754
|
};
|
|
@@ -4523,7 +4942,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
4523
4942
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
4524
4943
|
async function main() {
|
|
4525
4944
|
configManager.init();
|
|
4526
|
-
const version = true ? "0.7.0
|
|
4945
|
+
const version = true ? "0.7.0" : createRequire(import.meta.url)("../package.json").version;
|
|
4527
4946
|
const server = createMcpServer(version);
|
|
4528
4947
|
const transport = new StdioServerTransport();
|
|
4529
4948
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vornrun/mcp",
|
|
3
|
-
"version": "0.7.0
|
|
3
|
+
"version": "0.7.0",
|
|
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
|
|
42
|
-
"@vornrun/shared": "0.7.0
|
|
41
|
+
"@vornrun/server": "0.7.0",
|
|
42
|
+
"@vornrun/shared": "0.7.0",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
44
|
"tsx": "^4.23.1",
|
|
45
45
|
"typescript": "^6.0.3"
|