@vornrun/mcp 0.7.0-beta.8 → 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 +568 -108
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -50,7 +50,34 @@ 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;
|
|
55
|
+
var BOOTSTRAP_ENV_VAR = "SECRET_VORN_BOOTSTRAP_TOKEN";
|
|
53
56
|
var LOCAL_TOKEN_FILENAME = "local-token";
|
|
57
|
+
var WS_PORT_FILENAME = "ws-port";
|
|
58
|
+
|
|
59
|
+
// ../shared/src/types.ts
|
|
60
|
+
var DEFAULT_WORKSPACE = {
|
|
61
|
+
id: "personal",
|
|
62
|
+
name: "Personal",
|
|
63
|
+
icon: "User",
|
|
64
|
+
iconColor: "#6b7280",
|
|
65
|
+
order: 0
|
|
66
|
+
};
|
|
67
|
+
function isTerminalTaskStatus(status) {
|
|
68
|
+
return status === "done" || status === "cancelled";
|
|
69
|
+
}
|
|
70
|
+
var SDK_FILTER_KEYS = {
|
|
71
|
+
connectorId: "sdkConnectorId",
|
|
72
|
+
version: "sdkVersion",
|
|
73
|
+
icon: "sdkIcon",
|
|
74
|
+
implicit: "implicit"
|
|
75
|
+
};
|
|
76
|
+
var NEVER_BORROWED_ENV = { keys: ["CLAUDECODE"], prefixes: ["CLAUDE_CODE_"] };
|
|
77
|
+
function connectionConnectorId(connection2) {
|
|
78
|
+
const packaged = connection2.filters?.[SDK_FILTER_KEYS.connectorId];
|
|
79
|
+
return typeof packaged === "string" && packaged !== "" ? packaged : connection2.connectorId;
|
|
80
|
+
}
|
|
54
81
|
|
|
55
82
|
// ../server/src/process-utils.ts
|
|
56
83
|
function getDefaultShell(configured) {
|
|
@@ -76,32 +103,13 @@ function findWindowsShell() {
|
|
|
76
103
|
if (fs.existsSync(windowsPowerShell)) return windowsPowerShell;
|
|
77
104
|
return process.env.COMSPEC || "cmd.exe";
|
|
78
105
|
}
|
|
79
|
-
var STRIP_ENV_KEYS =
|
|
106
|
+
var STRIP_ENV_KEYS = NEVER_BORROWED_ENV.keys;
|
|
107
|
+
var STRIP_ENV_PREFIXES = [...NEVER_BORROWED_ENV.prefixes, BOOTSTRAP_ENV_VAR];
|
|
80
108
|
var STRIP_ENV_KEYS_UPPER = STRIP_ENV_KEYS.map((k) => k.toUpperCase());
|
|
81
109
|
|
|
82
|
-
// ../shared/src/types.ts
|
|
83
|
-
var DEFAULT_WORKSPACE = {
|
|
84
|
-
id: "personal",
|
|
85
|
-
name: "Personal",
|
|
86
|
-
icon: "User",
|
|
87
|
-
iconColor: "#6b7280",
|
|
88
|
-
order: 0
|
|
89
|
-
};
|
|
90
|
-
function isTerminalTaskStatus(status) {
|
|
91
|
-
return status === "done" || status === "cancelled";
|
|
92
|
-
}
|
|
93
|
-
var SDK_FILTER_KEYS = {
|
|
94
|
-
connectorId: "sdkConnectorId",
|
|
95
|
-
version: "sdkVersion",
|
|
96
|
-
icon: "sdkIcon"
|
|
97
|
-
};
|
|
98
|
-
function connectionConnectorId(connection2) {
|
|
99
|
-
const packaged = connection2.filters?.[SDK_FILTER_KEYS.connectorId];
|
|
100
|
-
return typeof packaged === "string" && packaged !== "" ? packaged : connection2.connectorId;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
110
|
// ../server/src/default-workflows.ts
|
|
104
111
|
var DEFAULT_TASK_WORKFLOW_ID = "system:default-task-workflow";
|
|
112
|
+
var DEV_SERVER_WORKFLOW_ID = "system:dev-server-on-restore";
|
|
105
113
|
function buildDefaultTaskWorkflow() {
|
|
106
114
|
const triggerConfig = {
|
|
107
115
|
triggerType: "taskStatusChanged",
|
|
@@ -141,6 +149,39 @@ function buildDefaultTaskWorkflow() {
|
|
|
141
149
|
edges: [{ id: "e1", source: "trigger-1", target: "launch-1" }]
|
|
142
150
|
};
|
|
143
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
|
+
}
|
|
144
185
|
|
|
145
186
|
// ../server/src/database.ts
|
|
146
187
|
var DEFAULT_DATA_DIR = path2.join(os.homedir(), ".vorn");
|
|
@@ -159,8 +200,8 @@ function getDataDir() {
|
|
|
159
200
|
}
|
|
160
201
|
return resolvedDataDir;
|
|
161
202
|
}
|
|
162
|
-
function initDatabase(
|
|
163
|
-
resolvedDataDir =
|
|
203
|
+
function initDatabase(dataDir2) {
|
|
204
|
+
resolvedDataDir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
164
205
|
if (!fs2.existsSync(getDataDir())) {
|
|
165
206
|
fs2.mkdirSync(getDataDir(), { recursive: true, mode: 448 });
|
|
166
207
|
}
|
|
@@ -185,17 +226,25 @@ function initDatabase(dataDir) {
|
|
|
185
226
|
}
|
|
186
227
|
}
|
|
187
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) {
|
|
188
237
|
const d = getDb();
|
|
189
|
-
const flagRow = d.prepare("SELECT value FROM defaults WHERE key =
|
|
238
|
+
const flagRow = d.prepare("SELECT value FROM defaults WHERE key = ?").get(flag);
|
|
190
239
|
if (flagRow) {
|
|
191
240
|
try {
|
|
192
241
|
if (JSON.parse(flagRow.value) === true) return;
|
|
193
242
|
} catch {
|
|
194
243
|
}
|
|
195
244
|
}
|
|
196
|
-
const existing = d.prepare("SELECT id FROM workflows WHERE id = ?").get(
|
|
245
|
+
const existing = d.prepare("SELECT id FROM workflows WHERE id = ?").get(id);
|
|
197
246
|
if (!existing) {
|
|
198
|
-
const w =
|
|
247
|
+
const w = build();
|
|
199
248
|
d.prepare(
|
|
200
249
|
`INSERT INTO workflows (id, name, icon, icon_color, nodes, edges, enabled, last_run_at, last_run_status, stagger_delay_ms, workspace_id)
|
|
201
250
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
@@ -212,11 +261,12 @@ function seedSystemDefaults() {
|
|
|
212
261
|
w.staggerDelayMs ?? null,
|
|
213
262
|
w.workspaceId ?? "personal"
|
|
214
263
|
);
|
|
215
|
-
logger_default.info(`[database] Seeded
|
|
264
|
+
logger_default.info(`[database] Seeded workflow ${id}`);
|
|
216
265
|
}
|
|
217
|
-
d.prepare(
|
|
218
|
-
|
|
219
|
-
|
|
266
|
+
d.prepare("INSERT OR REPLACE INTO defaults (key, value) VALUES (?, ?)").run(
|
|
267
|
+
flag,
|
|
268
|
+
JSON.stringify(true)
|
|
269
|
+
);
|
|
220
270
|
}
|
|
221
271
|
function recoverCorruptDatabase() {
|
|
222
272
|
try {
|
|
@@ -389,7 +439,8 @@ function createSchema() {
|
|
|
389
439
|
saved_at INTEGER,
|
|
390
440
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
391
441
|
worktree_name TEXT,
|
|
392
|
-
agent_session_id TEXT
|
|
442
|
+
agent_session_id TEXT,
|
|
443
|
+
renamed_by_person INTEGER
|
|
393
444
|
);
|
|
394
445
|
|
|
395
446
|
CREATE TABLE IF NOT EXISTS schedule_log (
|
|
@@ -413,6 +464,16 @@ function createSchema() {
|
|
|
413
464
|
"order" INTEGER NOT NULL DEFAULT 0
|
|
414
465
|
);
|
|
415
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
|
+
|
|
416
477
|
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
417
478
|
id TEXT PRIMARY KEY,
|
|
418
479
|
workflow_id TEXT NOT NULL,
|
|
@@ -447,6 +508,10 @@ function createSchema() {
|
|
|
447
508
|
output TEXT,
|
|
448
509
|
structured_output TEXT,
|
|
449
510
|
iteration INTEGER,
|
|
511
|
+
worktree_path TEXT,
|
|
512
|
+
worktree_name TEXT,
|
|
513
|
+
worktree_origin TEXT,
|
|
514
|
+
waiting_for TEXT,
|
|
450
515
|
FOREIGN KEY (run_id) REFERENCES workflow_runs(id) ON DELETE CASCADE
|
|
451
516
|
);
|
|
452
517
|
|
|
@@ -687,7 +752,9 @@ function migrateSchema(d) {
|
|
|
687
752
|
last_sync_at TEXT,
|
|
688
753
|
last_sync_error TEXT,
|
|
689
754
|
sync_cursor TEXT,
|
|
690
|
-
created_at TEXT NOT NULL
|
|
755
|
+
created_at TEXT NOT NULL,
|
|
756
|
+
signed_in_as TEXT,
|
|
757
|
+
signed_in_at TEXT
|
|
691
758
|
)
|
|
692
759
|
`);
|
|
693
760
|
d.exec(`
|
|
@@ -900,11 +967,85 @@ function migrateSchema(d) {
|
|
|
900
967
|
})();
|
|
901
968
|
logger_default.info("[database] migrated schema to version 16 (shell working directory)");
|
|
902
969
|
}
|
|
970
|
+
if (version < 17) {
|
|
971
|
+
d.transaction(() => {
|
|
972
|
+
const derived = `(
|
|
973
|
+
SELECT json_extract(sc.filters, '$.sdkConnectorId')
|
|
974
|
+
FROM task_source_links tsl
|
|
975
|
+
JOIN source_connections sc ON sc.id = tsl.connection_id
|
|
976
|
+
WHERE tsl.task_id = tasks.id
|
|
977
|
+
)`;
|
|
978
|
+
d.exec(`
|
|
979
|
+
UPDATE tasks
|
|
980
|
+
SET source_connector_id = ${derived}
|
|
981
|
+
WHERE source_connector_id = 'mcp' AND ${derived} IS NOT NULL
|
|
982
|
+
`);
|
|
983
|
+
d.exec(`
|
|
984
|
+
UPDATE task_source_links
|
|
985
|
+
SET connector_id = (
|
|
986
|
+
SELECT json_extract(sc.filters, '$.sdkConnectorId')
|
|
987
|
+
FROM source_connections sc
|
|
988
|
+
WHERE sc.id = task_source_links.connection_id
|
|
989
|
+
)
|
|
990
|
+
WHERE connector_id = 'mcp'
|
|
991
|
+
AND (
|
|
992
|
+
SELECT json_extract(sc.filters, '$.sdkConnectorId')
|
|
993
|
+
FROM source_connections sc
|
|
994
|
+
WHERE sc.id = task_source_links.connection_id
|
|
995
|
+
) IS NOT NULL
|
|
996
|
+
`);
|
|
997
|
+
d.prepare(
|
|
998
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '17')"
|
|
999
|
+
).run();
|
|
1000
|
+
})();
|
|
1001
|
+
logger_default.info("[database] migrated schema to version 17 (packaged connector task ids)");
|
|
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
|
+
}
|
|
903
1043
|
}
|
|
904
1044
|
var REVISIONED_TABLES = [
|
|
905
1045
|
"projects",
|
|
906
1046
|
"tasks",
|
|
907
1047
|
"workspaces",
|
|
1048
|
+
"session_groups",
|
|
908
1049
|
"remote_hosts",
|
|
909
1050
|
"agent_commands"
|
|
910
1051
|
];
|
|
@@ -941,9 +1082,15 @@ function verifySchema(d) {
|
|
|
941
1082
|
column: "sort_order",
|
|
942
1083
|
ddl: "ALTER TABLE sessions ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"
|
|
943
1084
|
},
|
|
1085
|
+
{ column: "group_id", ddl: "ALTER TABLE sessions ADD COLUMN group_id TEXT" },
|
|
944
1086
|
{ column: "worktree_name", ddl: "ALTER TABLE sessions ADD COLUMN worktree_name TEXT" },
|
|
945
1087
|
{ column: "agent_session_id", ddl: "ALTER TABLE sessions ADD COLUMN agent_session_id TEXT" },
|
|
946
|
-
{ 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
|
+
}
|
|
947
1094
|
],
|
|
948
1095
|
agent_commands: [
|
|
949
1096
|
{
|
|
@@ -977,6 +1124,7 @@ function verifySchema(d) {
|
|
|
977
1124
|
}
|
|
978
1125
|
],
|
|
979
1126
|
workflow_run_nodes: [
|
|
1127
|
+
{ column: "waiting_for", ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN waiting_for TEXT" },
|
|
980
1128
|
{ column: "agent_type", ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN agent_type TEXT" },
|
|
981
1129
|
{
|
|
982
1130
|
column: "project_name",
|
|
@@ -1056,6 +1204,7 @@ function loadConfig() {
|
|
|
1056
1204
|
const remoteHosts = loadRemoteHosts(d);
|
|
1057
1205
|
const tasks = loadTasks(d);
|
|
1058
1206
|
const workspaces = loadWorkspaces(d);
|
|
1207
|
+
const sessionGroups = loadSessionGroups(d);
|
|
1059
1208
|
return {
|
|
1060
1209
|
version: 1,
|
|
1061
1210
|
revision: readConfigRevision(d),
|
|
@@ -1065,7 +1214,8 @@ function loadConfig() {
|
|
|
1065
1214
|
workflows,
|
|
1066
1215
|
remoteHosts,
|
|
1067
1216
|
tasks,
|
|
1068
|
-
workspaces
|
|
1217
|
+
workspaces,
|
|
1218
|
+
sessionGroups
|
|
1069
1219
|
};
|
|
1070
1220
|
}
|
|
1071
1221
|
function loadDefaults(d) {
|
|
@@ -1098,6 +1248,8 @@ function loadDefaults(d) {
|
|
|
1098
1248
|
// terminal drew and waits. There is nothing to ask, and leaving it off made
|
|
1099
1249
|
// the whole thing invisible unless somebody went looking for a toggle.
|
|
1100
1250
|
reopenSessions: map.reopenSessions ?? true,
|
|
1251
|
+
// Off by default: nothing starts itself because someone installed an app.
|
|
1252
|
+
startAtLogin: map.startAtLogin ?? false,
|
|
1101
1253
|
// Saving iterates over every key in defaults, but loading is this explicit
|
|
1102
1254
|
// list — so a key missing here round-trips to nothing and its feature is
|
|
1103
1255
|
// silently inert.
|
|
@@ -1150,6 +1302,9 @@ function loadDefaults(d) {
|
|
|
1150
1302
|
...map.headlessRetentionMinutes !== void 0 && {
|
|
1151
1303
|
headlessRetentionMinutes: map.headlessRetentionMinutes
|
|
1152
1304
|
},
|
|
1305
|
+
...map.hasSeededDevServerWorkflow !== void 0 && {
|
|
1306
|
+
hasSeededDevServerWorkflow: map.hasSeededDevServerWorkflow
|
|
1307
|
+
},
|
|
1153
1308
|
...map.hasSeededDefaultTaskWorkflow !== void 0 && {
|
|
1154
1309
|
hasSeededDefaultTaskWorkflow: map.hasSeededDefaultTaskWorkflow
|
|
1155
1310
|
},
|
|
@@ -1213,6 +1368,10 @@ function loadTasks(d) {
|
|
|
1213
1368
|
const rows = d.prepare('SELECT * FROM tasks ORDER BY "order"').all();
|
|
1214
1369
|
return rows.map(rowToTask);
|
|
1215
1370
|
}
|
|
1371
|
+
function loadSessionGroups(d) {
|
|
1372
|
+
const rows = d.prepare('SELECT * FROM session_groups ORDER BY "order"').all();
|
|
1373
|
+
return rows.map(rowToSessionGroup);
|
|
1374
|
+
}
|
|
1216
1375
|
function loadWorkspaces(d) {
|
|
1217
1376
|
const rows = d.prepare('SELECT * FROM workspaces ORDER BY "order"').all();
|
|
1218
1377
|
return rows.map(rowToWorkspace);
|
|
@@ -1452,6 +1611,36 @@ function saveConfig(config) {
|
|
|
1452
1611
|
for (const ws of workspaces) {
|
|
1453
1612
|
insertWorkspace.run(ws.id, ws.name, ws.icon ?? null, ws.iconColor ?? null, ws.order, revision);
|
|
1454
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
|
+
}
|
|
1455
1644
|
d.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES (?, ?)").run(
|
|
1456
1645
|
CONFIG_REVISION_KEY,
|
|
1457
1646
|
String(revision)
|
|
@@ -1509,6 +1698,16 @@ function rowToWorkflow(r) {
|
|
|
1509
1698
|
workspaceId: r.workspace_id ?? "personal"
|
|
1510
1699
|
};
|
|
1511
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
|
+
}
|
|
1512
1711
|
function rowToWorkspace(r) {
|
|
1513
1712
|
return {
|
|
1514
1713
|
id: r.id,
|
|
@@ -1525,8 +1724,8 @@ var ConfigManager = class {
|
|
|
1525
1724
|
dbWatcher = null;
|
|
1526
1725
|
debounceTimer = null;
|
|
1527
1726
|
cachedConfig = null;
|
|
1528
|
-
init(
|
|
1529
|
-
initDatabase(
|
|
1727
|
+
init(dataDir2) {
|
|
1728
|
+
initDatabase(dataDir2);
|
|
1530
1729
|
}
|
|
1531
1730
|
close() {
|
|
1532
1731
|
this.stopWatchingDb();
|
|
@@ -1644,33 +1843,47 @@ var V = {
|
|
|
1644
1843
|
url: safeUrl
|
|
1645
1844
|
};
|
|
1646
1845
|
|
|
1647
|
-
// src/
|
|
1846
|
+
// ../server/src/rpc-client.ts
|
|
1648
1847
|
import fs4 from "fs";
|
|
1649
1848
|
import path3 from "path";
|
|
1650
1849
|
import os2 from "os";
|
|
1651
1850
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1652
1851
|
import { WebSocket } from "ws";
|
|
1653
|
-
var
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
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()}).
|
|
1657
1867
|
The server writes it on startup and removes it on shutdown, so this usually means
|
|
1658
|
-
Vorn is not running. Start Vorn (or \`vorn
|
|
1659
|
-
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
|
+
}
|
|
1660
1873
|
function readLocalToken() {
|
|
1661
1874
|
try {
|
|
1662
|
-
const token = fs4.readFileSync(
|
|
1875
|
+
const token = fs4.readFileSync(localTokenFile(), "utf-8").trim();
|
|
1663
1876
|
if (!token) throw new Error("empty");
|
|
1664
1877
|
return token;
|
|
1665
1878
|
} catch {
|
|
1666
|
-
throw new Error(
|
|
1879
|
+
throw new Error(tokenFileMissingMessage());
|
|
1667
1880
|
}
|
|
1668
1881
|
}
|
|
1669
1882
|
function connection() {
|
|
1670
1883
|
const result = readPort();
|
|
1671
1884
|
if (!result.port) {
|
|
1672
1885
|
const reason = "reason" in result ? result.reason : "missing";
|
|
1673
|
-
throw new Error(reason === "invalid" ?
|
|
1886
|
+
throw new Error(reason === "invalid" ? portFileInvalidMessage() : portFileMissingMessage());
|
|
1674
1887
|
}
|
|
1675
1888
|
return {
|
|
1676
1889
|
url: `ws://127.0.0.1:${result.port}/ws`,
|
|
@@ -1679,21 +1892,42 @@ function connection() {
|
|
|
1679
1892
|
}
|
|
1680
1893
|
var TIMEOUT_MS = 1e4;
|
|
1681
1894
|
var IS_WIN = process.platform === "win32";
|
|
1682
|
-
|
|
1895
|
+
function portFileMissingMessage() {
|
|
1896
|
+
const file = portFile();
|
|
1897
|
+
return IS_WIN ? `Vorn port file not found (${file}).
|
|
1683
1898
|
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
1684
|
-
To fix, find the Vorn process and its listening port:
|
|
1685
|
-
|
|
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
|
|
1686
1901
|
Then write the WS port to the file:
|
|
1687
|
-
|
|
1688
|
-
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}).
|
|
1689
1904
|
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
1690
1905
|
To fix, run: lsof -iTCP -sTCP:LISTEN -P | grep Vorn
|
|
1691
1906
|
Then write the WS port (the one on *:<port>) to the file:
|
|
1692
|
-
echo '{"port":<PORT>,"pid":<PID>}' >
|
|
1907
|
+
echo '{"port":<PORT>,"pid":<PID>}' > "${file}"
|
|
1693
1908
|
Or restart Vorn to regenerate it.`;
|
|
1694
|
-
|
|
1909
|
+
}
|
|
1910
|
+
function portFileInvalidMessage() {
|
|
1911
|
+
const file = portFile();
|
|
1912
|
+
return `Vorn port file exists but contains invalid data (${file}).
|
|
1695
1913
|
Delete it and restart Vorn, or overwrite it with the correct port:
|
|
1696
|
-
${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
|
+
}
|
|
1697
1931
|
var rpcId = 0;
|
|
1698
1932
|
var cachedPort = null;
|
|
1699
1933
|
var cacheTimestamp = 0;
|
|
@@ -1746,7 +1980,11 @@ function discoverPort() {
|
|
|
1746
1980
|
}
|
|
1747
1981
|
return null;
|
|
1748
1982
|
}
|
|
1983
|
+
function discoveryAllowed() {
|
|
1984
|
+
return dataDirOverride === void 0 && named(process.env.VORN_DATA_DIR) === void 0;
|
|
1985
|
+
}
|
|
1749
1986
|
function discoverAndHeal() {
|
|
1987
|
+
if (!discoveryAllowed()) return { port: null, reason: "missing" };
|
|
1750
1988
|
const now = Date.now();
|
|
1751
1989
|
if (cachedPort && now - cacheTimestamp < CACHE_TTL_MS) return { port: cachedPort };
|
|
1752
1990
|
const discovered = discoverPort();
|
|
@@ -1754,8 +1992,8 @@ function discoverAndHeal() {
|
|
|
1754
1992
|
cacheTimestamp = now;
|
|
1755
1993
|
if (discovered) {
|
|
1756
1994
|
try {
|
|
1757
|
-
fs4.mkdirSync(
|
|
1758
|
-
fs4.writeFileSync(
|
|
1995
|
+
fs4.mkdirSync(dataDir(), { recursive: true });
|
|
1996
|
+
fs4.writeFileSync(portFile(), JSON.stringify({ port: discovered }), "utf-8");
|
|
1759
1997
|
} catch {
|
|
1760
1998
|
}
|
|
1761
1999
|
return { port: discovered };
|
|
@@ -1764,7 +2002,7 @@ function discoverAndHeal() {
|
|
|
1764
2002
|
}
|
|
1765
2003
|
function readPort() {
|
|
1766
2004
|
try {
|
|
1767
|
-
const raw = fs4.readFileSync(
|
|
2005
|
+
const raw = fs4.readFileSync(portFile(), "utf-8").trim();
|
|
1768
2006
|
if (!raw) return { port: null, reason: "invalid" };
|
|
1769
2007
|
if (raw.startsWith("{")) {
|
|
1770
2008
|
const parsed = JSON.parse(raw);
|
|
@@ -1806,15 +2044,19 @@ async function rpcCall(method, params, timeoutMs = TIMEOUT_MS) {
|
|
|
1806
2044
|
const msg = JSON.parse(raw.toString());
|
|
1807
2045
|
if (msg.id !== id) return;
|
|
1808
2046
|
clearTimeout(timer);
|
|
1809
|
-
ws.close();
|
|
1810
2047
|
if (msg.error) {
|
|
1811
|
-
reject(new Error(msg.error.message));
|
|
2048
|
+
reject(new Error(explain(msg.error.message)));
|
|
1812
2049
|
} else {
|
|
1813
2050
|
resolve(msg.result);
|
|
1814
2051
|
}
|
|
2052
|
+
ws.close();
|
|
1815
2053
|
} catch {
|
|
1816
2054
|
}
|
|
1817
2055
|
});
|
|
2056
|
+
ws.on("close", (code) => {
|
|
2057
|
+
clearTimeout(timer);
|
|
2058
|
+
reject(new Error(closedBeforeAnswering(code)));
|
|
2059
|
+
});
|
|
1818
2060
|
ws.on("error", (err) => {
|
|
1819
2061
|
clearTimeout(timer);
|
|
1820
2062
|
reject(new Error(`Cannot connect to Vorn server: ${err.message}. Is the app running?`));
|
|
@@ -1943,6 +2185,9 @@ async function listWorkflowRunsByTask(taskId, limit = 20) {
|
|
|
1943
2185
|
limit
|
|
1944
2186
|
});
|
|
1945
2187
|
}
|
|
2188
|
+
async function listRunsWithWaitingGates() {
|
|
2189
|
+
return rpcCall("workflowRun:listWaiting");
|
|
2190
|
+
}
|
|
1946
2191
|
async function listAllWorkflowRuns(workspaceId, limit = 50) {
|
|
1947
2192
|
return rpcCall("workflowRun:listAll", {
|
|
1948
2193
|
workspaceId,
|
|
@@ -2713,6 +2958,13 @@ function registerSessionTools(server) {
|
|
|
2713
2958
|
|
|
2714
2959
|
// src/tools/workflows.ts
|
|
2715
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
|
|
2716
2968
|
import { z as z5 } from "zod";
|
|
2717
2969
|
|
|
2718
2970
|
// ../shared/src/workflow-portability.ts
|
|
@@ -2743,16 +2995,18 @@ function boundConnectionKey(node, config) {
|
|
|
2743
2995
|
if (node.type === "trigger" && config.triggerType === "connectorPoll") return "connectionId";
|
|
2744
2996
|
if (node.type === "callConnectorAction") return "connectionId";
|
|
2745
2997
|
if (node.type === "httpRequest") return "profileConnectionId";
|
|
2998
|
+
if (node.type === "script") return "secretsFrom";
|
|
2746
2999
|
return null;
|
|
2747
3000
|
}
|
|
3001
|
+
var OPTIONAL_CONNECTION_KEYS = /* @__PURE__ */ new Set(["profileConnectionId", "secretsFrom"]);
|
|
2748
3002
|
function resolveRequirement(requirement, connections) {
|
|
2749
3003
|
const candidates = connections.filter(
|
|
2750
3004
|
(connection2) => requirement.kind === "httpProfile" ? connectorOf(connection2) === HTTP_PROFILE_CONNECTOR : requirement.connectorId !== "" && connectorOf(connection2) === requirement.connectorId
|
|
2751
3005
|
);
|
|
2752
3006
|
if (candidates.length === 0) return void 0;
|
|
2753
3007
|
if (requirement.name !== "") {
|
|
2754
|
-
const
|
|
2755
|
-
if (
|
|
3008
|
+
const named2 = candidates.filter((connection2) => connection2.name === requirement.name);
|
|
3009
|
+
if (named2.length === 1) return named2[0].id;
|
|
2756
3010
|
}
|
|
2757
3011
|
return candidates.length === 1 ? candidates[0].id : void 0;
|
|
2758
3012
|
}
|
|
@@ -2776,19 +3030,22 @@ function toPortable(workflow, projectPath, connections = []) {
|
|
|
2776
3030
|
}
|
|
2777
3031
|
const key = boundConnectionKey(node, config);
|
|
2778
3032
|
const bound = key === null ? "" : config[key];
|
|
2779
|
-
|
|
3033
|
+
const unbound = key !== null && !OPTIONAL_CONNECTION_KEYS.has(key) && bound === "";
|
|
3034
|
+
if (key !== null && (typeof bound === "string" && bound !== "" || unbound)) {
|
|
2780
3035
|
const source2 = connections.find((connection2) => connection2.id === bound);
|
|
2781
3036
|
const event = config.event;
|
|
3037
|
+
const declared = config.connectorId;
|
|
2782
3038
|
requires.push(
|
|
2783
3039
|
key === "profileConnectionId" ? { kind: "httpProfile", nodeId: node.id, name: source2?.name ?? "" } : {
|
|
2784
3040
|
kind: "connection",
|
|
2785
3041
|
nodeId: node.id,
|
|
2786
|
-
connectorId: source2 ? connectorOf(source2) : "",
|
|
3042
|
+
connectorId: source2 ? connectorOf(source2) : typeof declared === "string" ? declared : "",
|
|
2787
3043
|
name: source2?.name ?? "",
|
|
2788
|
-
...typeof event === "string" && event !== "" && { event }
|
|
3044
|
+
...typeof event === "string" && event !== "" && { event },
|
|
3045
|
+
...key === "secretsFrom" && { key }
|
|
2789
3046
|
}
|
|
2790
3047
|
);
|
|
2791
|
-
if (key
|
|
3048
|
+
if (OPTIONAL_CONNECTION_KEYS.has(key)) delete config[key];
|
|
2792
3049
|
else config[key] = "";
|
|
2793
3050
|
}
|
|
2794
3051
|
return { ...node, config };
|
|
@@ -2819,9 +3076,12 @@ function replacePath(value, projectPath) {
|
|
|
2819
3076
|
function unresolvedRequirements(portable, connections) {
|
|
2820
3077
|
const present = new Set(portable.nodes.map((node) => node.id));
|
|
2821
3078
|
return (portable.requires ?? []).filter(
|
|
2822
|
-
(requirement) => present.has(requirement.nodeId) && resolveRequirement(requirement, connections) === void 0
|
|
3079
|
+
(requirement) => present.has(requirement.nodeId) && (bindsOnlyByHand(requirement) || resolveRequirement(requirement, connections) === void 0)
|
|
2823
3080
|
);
|
|
2824
3081
|
}
|
|
3082
|
+
function bindsOnlyByHand(requirement) {
|
|
3083
|
+
return requirement.kind === "connection" && requirement.key === "secretsFrom";
|
|
3084
|
+
}
|
|
2825
3085
|
function fromPortable(portable, bundle, project, connections = [], mintToken = () => crypto.randomUUID()) {
|
|
2826
3086
|
const bindings = /* @__PURE__ */ new Map();
|
|
2827
3087
|
for (const requirement of portable.requires ?? []) {
|
|
@@ -2829,15 +3089,16 @@ function fromPortable(portable, bundle, project, connections = [], mintToken = (
|
|
|
2829
3089
|
}
|
|
2830
3090
|
const nodes = portable.nodes.map((node) => {
|
|
2831
3091
|
const config = { ...node.config };
|
|
2832
|
-
|
|
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)) {
|
|
2833
3095
|
if (typeof value !== "string") continue;
|
|
2834
|
-
config[
|
|
3096
|
+
config[key2] = value.split(PROJECT_PATH_TOKEN).join(project.path.replace(/[/\\]+$/, "")).split(PROJECT_NAME_TOKEN).join(project.name);
|
|
2835
3097
|
}
|
|
2836
3098
|
for (const requirement of bindings.get(node.id) ?? []) {
|
|
3099
|
+
if (bindsOnlyByHand(requirement) || key === null) continue;
|
|
2837
3100
|
const resolved = resolveRequirement(requirement, connections);
|
|
2838
|
-
if (resolved
|
|
2839
|
-
if (requirement.kind === "httpProfile") config.profileConnectionId = resolved;
|
|
2840
|
-
else config.connectionId = resolved;
|
|
3101
|
+
if (resolved !== void 0) config[key] = resolved;
|
|
2841
3102
|
}
|
|
2842
3103
|
if (node.type === "trigger" && config.triggerType === "webhook" && !config.token) {
|
|
2843
3104
|
config.token = mintToken();
|
|
@@ -3159,6 +3420,64 @@ function resolveWorkflowId(args) {
|
|
|
3159
3420
|
}
|
|
3160
3421
|
return { id };
|
|
3161
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
|
+
}
|
|
3162
3481
|
async function listPortableConnections() {
|
|
3163
3482
|
try {
|
|
3164
3483
|
return await rpcCall("connection:list", { connectorId: void 0 });
|
|
@@ -3312,7 +3631,7 @@ function registerWorkflowTools(server) {
|
|
|
3312
3631
|
);
|
|
3313
3632
|
server.tool(
|
|
3314
3633
|
"list_workflow_runs",
|
|
3315
|
-
"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.",
|
|
3316
3635
|
{
|
|
3317
3636
|
workflow_id: V.id.optional().describe("Filter by workflow ID"),
|
|
3318
3637
|
task_id: V.id.optional().describe("Filter by task ID (runs triggered by this task)"),
|
|
@@ -3325,28 +3644,28 @@ function registerWorkflowTools(server) {
|
|
|
3325
3644
|
isError: true
|
|
3326
3645
|
};
|
|
3327
3646
|
}
|
|
3647
|
+
const withGates = async (runs) => runs.some((r) => r.nodeStates.some((n) => n.status === "waiting")) ? annotateWaitingGates(runs, await dbListWorkflows()) : runs;
|
|
3328
3648
|
if (args.task_id) {
|
|
3329
|
-
const runs = await listWorkflowRunsByTask(args.task_id, args.limit ?? 20);
|
|
3649
|
+
const runs = await withGates(await listWorkflowRunsByTask(args.task_id, args.limit ?? 20));
|
|
3330
3650
|
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
3331
3651
|
}
|
|
3332
3652
|
if (args.workflow_id) {
|
|
3333
|
-
const runs = await listWorkflowRuns(args.workflow_id, args.limit ?? 20);
|
|
3653
|
+
const runs = await withGates(await listWorkflowRuns(args.workflow_id, args.limit ?? 20));
|
|
3334
3654
|
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
3335
3655
|
}
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
};
|
|
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) }] };
|
|
3340
3659
|
}
|
|
3341
3660
|
);
|
|
3342
3661
|
server.tool(
|
|
3343
3662
|
"stop_workflow_run",
|
|
3344
|
-
"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.",
|
|
3345
3664
|
{
|
|
3346
3665
|
run_id: V.id.describe("Run ID (from list_workflow_runs)")
|
|
3347
3666
|
},
|
|
3348
3667
|
async (args) => {
|
|
3349
|
-
const run =
|
|
3668
|
+
const run = await runById(args.run_id);
|
|
3350
3669
|
if (!run) {
|
|
3351
3670
|
return {
|
|
3352
3671
|
content: [
|
|
@@ -3391,6 +3710,74 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3391
3710
|
};
|
|
3392
3711
|
}
|
|
3393
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
|
+
);
|
|
3394
3781
|
server.tool(
|
|
3395
3782
|
"get_workflow_schedule",
|
|
3396
3783
|
"Get scheduler info for a workflow: execution log or next scheduled run. Requires the Vorn app to be running.",
|
|
@@ -3431,7 +3818,7 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3431
3818
|
);
|
|
3432
3819
|
server.tool(
|
|
3433
3820
|
"execute_workflow",
|
|
3434
|
-
"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.",
|
|
3435
3822
|
{
|
|
3436
3823
|
workflow_id: V.id.describe("Workflow ID (from list_workflows)"),
|
|
3437
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>}})")
|
|
@@ -3750,27 +4137,43 @@ var failure = (message) => ({
|
|
|
3750
4137
|
function summarize(entry) {
|
|
3751
4138
|
return { type: entry.type, label: entry.label };
|
|
3752
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
|
+
}
|
|
3753
4152
|
function registerConnectorTools(server) {
|
|
3754
4153
|
server.tool(
|
|
3755
4154
|
"list_connectors",
|
|
3756
|
-
"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.",
|
|
3757
4156
|
{
|
|
3758
|
-
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")
|
|
3759
4159
|
},
|
|
3760
4160
|
async (args) => {
|
|
3761
|
-
const [builtIns, snapshot, connections, statuses] = await Promise.all([
|
|
4161
|
+
const [builtIns, snapshot, connections, statuses, packs] = await Promise.all([
|
|
3762
4162
|
rpcCall("connector:list"),
|
|
3763
4163
|
rpcCall("connector:catalog"),
|
|
3764
4164
|
rpcCall("connection:list", { connectorId: void 0 }),
|
|
3765
|
-
rpcCall("connector:status")
|
|
4165
|
+
rpcCall("connector:status"),
|
|
4166
|
+
rpcCall("connector:listPacks")
|
|
3766
4167
|
]);
|
|
3767
4168
|
const countFor = (id) => connections.filter((conn) => connectionConnectorId(conn) === id).length;
|
|
3768
4169
|
const statusFor = (id) => statuses.find((s) => s.connectorId === id);
|
|
4170
|
+
const packFor = (id) => packs.find((pack) => pack.id === id);
|
|
3769
4171
|
const entries = [
|
|
3770
4172
|
...builtIns.map((c) => ({
|
|
3771
4173
|
id: c.id,
|
|
3772
4174
|
name: c.name,
|
|
3773
4175
|
source: "built-in",
|
|
4176
|
+
kind: "connector",
|
|
3774
4177
|
capabilities: c.capabilities,
|
|
3775
4178
|
connections: countFor(c.id),
|
|
3776
4179
|
// Only meaningful for connectors that authenticate up front; the
|
|
@@ -3780,25 +4183,49 @@ function registerConnectorTools(server) {
|
|
|
3780
4183
|
...statusFor(c.id).message && { authMessage: statusFor(c.id).message }
|
|
3781
4184
|
}
|
|
3782
4185
|
})),
|
|
3783
|
-
...snapshot.items.map((entry) =>
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
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
|
+
})
|
|
3800
4222
|
];
|
|
3801
|
-
|
|
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
|
+
);
|
|
3802
4229
|
}
|
|
3803
4230
|
);
|
|
3804
4231
|
server.tool(
|
|
@@ -3848,7 +4275,7 @@ function registerConnectorTools(server) {
|
|
|
3848
4275
|
);
|
|
3849
4276
|
server.tool(
|
|
3850
4277
|
"inspect_connector_package",
|
|
3851
|
-
"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.",
|
|
3852
4279
|
{
|
|
3853
4280
|
package: V.shortText.describe(
|
|
3854
4281
|
'npm package name, or a command to run a local build (e.g. "node /path/to/dist/index.js")'
|
|
@@ -3862,10 +4289,10 @@ function registerConnectorTools(server) {
|
|
|
3862
4289
|
);
|
|
3863
4290
|
server.tool(
|
|
3864
4291
|
"install_connector",
|
|
3865
|
-
"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.",
|
|
3866
4293
|
{
|
|
3867
4294
|
connector_id: V.id.optional().describe("Catalog connector id (from list_connectors). Use this or package."),
|
|
3868
|
-
package: V.shortText.optional().describe("
|
|
4295
|
+
package: V.shortText.optional().describe("Launch command the connection runs, or a package name to run with npx"),
|
|
3869
4296
|
pack_path: V.shortText.optional().describe(
|
|
3870
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."
|
|
3871
4298
|
),
|
|
@@ -3891,6 +4318,38 @@ function registerConnectorTools(server) {
|
|
|
3891
4318
|
});
|
|
3892
4319
|
if (!outcome.ok) return failure(`The pack was refused: ${outcome.error}`);
|
|
3893
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
|
+
});
|
|
3894
4353
|
}
|
|
3895
4354
|
const target = installed ? packLaunch(installed) : entry?.launch ?? args.package;
|
|
3896
4355
|
if (!target) return failure("Provide either connector_id, package, or pack_path.");
|
|
@@ -4289,6 +4748,7 @@ function registerDeviceTools(server) {
|
|
|
4289
4748
|
sessionId: id,
|
|
4290
4749
|
udid: args.udid
|
|
4291
4750
|
});
|
|
4751
|
+
if (!r.ok) throw new Error(r.message);
|
|
4292
4752
|
return {
|
|
4293
4753
|
content: [{ type: "text", text: `Claimed ${r.name} (${r.udid}).` }]
|
|
4294
4754
|
};
|
|
@@ -4482,7 +4942,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
4482
4942
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
4483
4943
|
async function main() {
|
|
4484
4944
|
configManager.init();
|
|
4485
|
-
const version = true ? "0.7.0
|
|
4945
|
+
const version = true ? "0.7.0" : createRequire(import.meta.url)("../package.json").version;
|
|
4486
4946
|
const server = createMcpServer(version);
|
|
4487
4947
|
const transport = new StdioServerTransport();
|
|
4488
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"
|