@vornrun/mcp 0.7.1-beta.4 → 0.7.1-beta.6
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 +213 -61
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import "module";
|
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
|
|
7
7
|
// ../server/src/config-manager.ts
|
|
8
|
-
import
|
|
8
|
+
import fs4 from "fs";
|
|
9
9
|
|
|
10
10
|
// ../shared/src/agent-defaults.ts
|
|
11
11
|
var DEFAULT_AGENT_COMMANDS = {
|
|
@@ -34,9 +34,9 @@ var DEFAULT_AGENT_COMMANDS = {
|
|
|
34
34
|
|
|
35
35
|
// ../server/src/database.ts
|
|
36
36
|
import Database from "libsql";
|
|
37
|
-
import
|
|
37
|
+
import path3 from "path";
|
|
38
38
|
import os from "os";
|
|
39
|
-
import
|
|
39
|
+
import fs3 from "fs";
|
|
40
40
|
import { randomUUID } from "crypto";
|
|
41
41
|
|
|
42
42
|
// ../server/src/logger.ts
|
|
@@ -118,6 +118,12 @@ var STRIP_ENV_KEYS = NEVER_BORROWED_ENV.keys;
|
|
|
118
118
|
var STRIP_ENV_PREFIXES = [...NEVER_BORROWED_ENV.prefixes, BOOTSTRAP_ENV_VAR];
|
|
119
119
|
var STRIP_ENV_KEYS_UPPER = STRIP_ENV_KEYS.map((k) => k.toUpperCase());
|
|
120
120
|
|
|
121
|
+
// ../server/src/workflows/gate-views.ts
|
|
122
|
+
import fs2 from "fs";
|
|
123
|
+
import path2 from "path";
|
|
124
|
+
import crypto2 from "crypto";
|
|
125
|
+
var GATE_VIEW_MAX_BYTES = 5 * 1024 * 1024;
|
|
126
|
+
|
|
121
127
|
// ../server/src/default-workflows.ts
|
|
122
128
|
var DEFAULT_TASK_WORKFLOW_ID = "system:default-task-workflow";
|
|
123
129
|
var DEV_SERVER_WORKFLOW_ID = "system:dev-server-on-restore";
|
|
@@ -195,10 +201,10 @@ function buildDevServerWorkflow() {
|
|
|
195
201
|
}
|
|
196
202
|
|
|
197
203
|
// ../server/src/database.ts
|
|
198
|
-
var DEFAULT_DATA_DIR =
|
|
204
|
+
var DEFAULT_DATA_DIR = path3.join(os.homedir(), ".vorn");
|
|
199
205
|
var resolvedDataDir = null;
|
|
200
206
|
function dbPath() {
|
|
201
|
-
return
|
|
207
|
+
return path3.join(getDataDir(), "vorn.db");
|
|
202
208
|
}
|
|
203
209
|
var db = null;
|
|
204
210
|
function getDb() {
|
|
@@ -213,8 +219,8 @@ function getDataDir() {
|
|
|
213
219
|
}
|
|
214
220
|
function initDatabase(dataDir2) {
|
|
215
221
|
resolvedDataDir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
216
|
-
if (!
|
|
217
|
-
|
|
222
|
+
if (!fs3.existsSync(getDataDir())) {
|
|
223
|
+
fs3.mkdirSync(getDataDir(), { recursive: true, mode: 448 });
|
|
218
224
|
}
|
|
219
225
|
try {
|
|
220
226
|
db = new Database(dbPath());
|
|
@@ -288,13 +294,13 @@ function recoverCorruptDatabase() {
|
|
|
288
294
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
289
295
|
const backupPath = `${dbPath()}.corrupt-${timestamp}`;
|
|
290
296
|
try {
|
|
291
|
-
if (
|
|
292
|
-
|
|
297
|
+
if (fs3.existsSync(dbPath())) {
|
|
298
|
+
fs3.copyFileSync(dbPath(), backupPath);
|
|
293
299
|
logger_default.info(`[database] Backed up corrupt database to ${backupPath}`);
|
|
294
300
|
}
|
|
295
301
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
296
302
|
const file = dbPath() + suffix;
|
|
297
|
-
if (
|
|
303
|
+
if (fs3.existsSync(file)) fs3.unlinkSync(file);
|
|
298
304
|
}
|
|
299
305
|
} catch (backupErr) {
|
|
300
306
|
logger_default.error({ backupErr }, "[database] Failed to back up corrupt database:");
|
|
@@ -496,7 +502,8 @@ function createSchema() {
|
|
|
496
502
|
connector_item TEXT,
|
|
497
503
|
connector_inbox_id INTEGER,
|
|
498
504
|
connector_inbox_lease_token TEXT,
|
|
499
|
-
connector_inbox_disposition TEXT
|
|
505
|
+
connector_inbox_disposition TEXT,
|
|
506
|
+
definition TEXT
|
|
500
507
|
);
|
|
501
508
|
|
|
502
509
|
CREATE TABLE IF NOT EXISTS workflow_run_nodes (
|
|
@@ -523,6 +530,13 @@ function createSchema() {
|
|
|
523
530
|
worktree_name TEXT,
|
|
524
531
|
worktree_origin TEXT,
|
|
525
532
|
waiting_for TEXT,
|
|
533
|
+
message TEXT,
|
|
534
|
+
view_token TEXT,
|
|
535
|
+
round INTEGER,
|
|
536
|
+
feedback TEXT,
|
|
537
|
+
rejected_at TEXT,
|
|
538
|
+
editable_text TEXT,
|
|
539
|
+
edited_text TEXT,
|
|
526
540
|
FOREIGN KEY (run_id) REFERENCES workflow_runs(id) ON DELETE CASCADE
|
|
527
541
|
);
|
|
528
542
|
|
|
@@ -1081,7 +1095,58 @@ function migrateSchema(d) {
|
|
|
1081
1095
|
})();
|
|
1082
1096
|
logger_default.info("[database] migrated schema to version 21 (package connections belong to sdk)");
|
|
1083
1097
|
}
|
|
1098
|
+
if (version < 22) {
|
|
1099
|
+
d.transaction(() => {
|
|
1100
|
+
const runCols = d.prepare("PRAGMA table_info(workflow_runs)").all();
|
|
1101
|
+
if (!runCols.some((c) => c.name === "definition")) {
|
|
1102
|
+
d.exec("ALTER TABLE workflow_runs ADD COLUMN definition TEXT");
|
|
1103
|
+
}
|
|
1104
|
+
d.prepare(
|
|
1105
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '22')"
|
|
1106
|
+
).run();
|
|
1107
|
+
})();
|
|
1108
|
+
logger_default.info("[database] migrated schema to version 22 (the definition a run started with)");
|
|
1109
|
+
}
|
|
1110
|
+
if (version < 23) {
|
|
1111
|
+
d.transaction(() => {
|
|
1112
|
+
const nodeCols = d.prepare("PRAGMA table_info(workflow_run_nodes)").all();
|
|
1113
|
+
for (const [column, type] of GATE_COLUMNS) {
|
|
1114
|
+
if (!nodeCols.some((c) => c.name === column)) {
|
|
1115
|
+
d.exec(`ALTER TABLE workflow_run_nodes ADD COLUMN ${column} ${type}`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
d.prepare(
|
|
1119
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '23')"
|
|
1120
|
+
).run();
|
|
1121
|
+
})();
|
|
1122
|
+
logger_default.info("[database] migrated schema to version 23 (what an approval gate asked and heard)");
|
|
1123
|
+
}
|
|
1124
|
+
if (version < 24) {
|
|
1125
|
+
d.transaction(() => {
|
|
1126
|
+
const nodeCols = d.prepare("PRAGMA table_info(workflow_run_nodes)").all();
|
|
1127
|
+
for (const [column, type] of GATE_EDIT_COLUMNS) {
|
|
1128
|
+
if (!nodeCols.some((c) => c.name === column)) {
|
|
1129
|
+
d.exec(`ALTER TABLE workflow_run_nodes ADD COLUMN ${column} ${type}`);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
d.prepare(
|
|
1133
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '24')"
|
|
1134
|
+
).run();
|
|
1135
|
+
})();
|
|
1136
|
+
logger_default.info("[database] migrated schema to version 24 (the text a reviewer edited at a gate)");
|
|
1137
|
+
}
|
|
1084
1138
|
}
|
|
1139
|
+
var GATE_COLUMNS = [
|
|
1140
|
+
["message", "TEXT"],
|
|
1141
|
+
["view_token", "TEXT"],
|
|
1142
|
+
["round", "INTEGER"],
|
|
1143
|
+
["feedback", "TEXT"],
|
|
1144
|
+
["rejected_at", "TEXT"]
|
|
1145
|
+
];
|
|
1146
|
+
var GATE_EDIT_COLUMNS = [
|
|
1147
|
+
["editable_text", "TEXT"],
|
|
1148
|
+
["edited_text", "TEXT"]
|
|
1149
|
+
];
|
|
1085
1150
|
var REVISIONED_TABLES = [
|
|
1086
1151
|
"projects",
|
|
1087
1152
|
"tasks",
|
|
@@ -1156,7 +1221,8 @@ function verifySchema(d) {
|
|
|
1156
1221
|
{
|
|
1157
1222
|
column: "connector_inbox_disposition",
|
|
1158
1223
|
ddl: "ALTER TABLE workflow_runs ADD COLUMN connector_inbox_disposition TEXT"
|
|
1159
|
-
}
|
|
1224
|
+
},
|
|
1225
|
+
{ column: "definition", ddl: "ALTER TABLE workflow_runs ADD COLUMN definition TEXT" }
|
|
1160
1226
|
],
|
|
1161
1227
|
connector_inbox: [
|
|
1162
1228
|
{
|
|
@@ -1191,7 +1257,11 @@ function verifySchema(d) {
|
|
|
1191
1257
|
ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN structured_output TEXT"
|
|
1192
1258
|
},
|
|
1193
1259
|
// Which pass of a loop produced this row.
|
|
1194
|
-
{ column: "iteration", ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN iteration INTEGER" }
|
|
1260
|
+
{ column: "iteration", ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN iteration INTEGER" },
|
|
1261
|
+
...GATE_COLUMNS.map(([column, type]) => ({
|
|
1262
|
+
column,
|
|
1263
|
+
ddl: `ALTER TABLE workflow_run_nodes ADD COLUMN ${column} ${type}`
|
|
1264
|
+
}))
|
|
1195
1265
|
],
|
|
1196
1266
|
tasks: [
|
|
1197
1267
|
{
|
|
@@ -1815,16 +1885,12 @@ var ConfigManager = class {
|
|
|
1815
1885
|
cb(config);
|
|
1816
1886
|
}
|
|
1817
1887
|
}
|
|
1818
|
-
/**
|
|
1819
|
-
* Watch for external DB writes (e.g. MCP stdio process).
|
|
1820
|
-
* Detects: .db-signal (explicit), .db-wal changes, and .db changes (post-checkpoint).
|
|
1821
|
-
*/
|
|
1888
|
+
/** Reload when a writer touches `.db-signal` (`dbSignalChange`) after changing the configuration. */
|
|
1822
1889
|
watchDb() {
|
|
1823
1890
|
if (this.dbWatcher) return;
|
|
1824
|
-
const WATCH_SUFFIXES = [".db-signal", ".db-wal", ".db"];
|
|
1825
1891
|
try {
|
|
1826
|
-
this.dbWatcher =
|
|
1827
|
-
if (
|
|
1892
|
+
this.dbWatcher = fs4.watch(getDataDir(), (_eventType, filename) => {
|
|
1893
|
+
if (filename !== ".db-signal") return;
|
|
1828
1894
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
1829
1895
|
this.debounceTimer = setTimeout(() => {
|
|
1830
1896
|
this.notifyChanged();
|
|
@@ -1855,8 +1921,8 @@ var configManager = new ConfigManager();
|
|
|
1855
1921
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1856
1922
|
|
|
1857
1923
|
// src/tools/tasks.ts
|
|
1858
|
-
import
|
|
1859
|
-
import
|
|
1924
|
+
import crypto3 from "crypto";
|
|
1925
|
+
import path5 from "path";
|
|
1860
1926
|
import { z as z2 } from "zod";
|
|
1861
1927
|
|
|
1862
1928
|
// src/validation.ts
|
|
@@ -1885,8 +1951,8 @@ var V = {
|
|
|
1885
1951
|
};
|
|
1886
1952
|
|
|
1887
1953
|
// ../server/src/rpc-client.ts
|
|
1888
|
-
import
|
|
1889
|
-
import
|
|
1954
|
+
import fs5 from "fs";
|
|
1955
|
+
import path4 from "path";
|
|
1890
1956
|
import os2 from "os";
|
|
1891
1957
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1892
1958
|
import { WebSocket } from "ws";
|
|
@@ -1895,13 +1961,13 @@ function named(value) {
|
|
|
1895
1961
|
return value?.trim() ? value : void 0;
|
|
1896
1962
|
}
|
|
1897
1963
|
function dataDir() {
|
|
1898
|
-
return dataDirOverride ?? named(process.env.VORN_DATA_DIR) ??
|
|
1964
|
+
return dataDirOverride ?? named(process.env.VORN_DATA_DIR) ?? path4.join(os2.homedir(), ".vorn");
|
|
1899
1965
|
}
|
|
1900
1966
|
function portFile() {
|
|
1901
|
-
return
|
|
1967
|
+
return path4.join(dataDir(), WS_PORT_FILENAME);
|
|
1902
1968
|
}
|
|
1903
1969
|
function localTokenFile() {
|
|
1904
|
-
return
|
|
1970
|
+
return path4.join(dataDir(), LOCAL_TOKEN_FILENAME);
|
|
1905
1971
|
}
|
|
1906
1972
|
function tokenFileMissingMessage() {
|
|
1907
1973
|
return `Vorn local credential not found (${localTokenFile()}).
|
|
@@ -1913,7 +1979,7 @@ MCP included, reaches a server that moved.`;
|
|
|
1913
1979
|
}
|
|
1914
1980
|
function readLocalToken() {
|
|
1915
1981
|
try {
|
|
1916
|
-
const token =
|
|
1982
|
+
const token = fs5.readFileSync(localTokenFile(), "utf-8").trim();
|
|
1917
1983
|
if (!token) throw new Error("empty");
|
|
1918
1984
|
return token;
|
|
1919
1985
|
} catch {
|
|
@@ -2033,8 +2099,8 @@ function discoverAndHeal() {
|
|
|
2033
2099
|
cacheTimestamp = now;
|
|
2034
2100
|
if (discovered) {
|
|
2035
2101
|
try {
|
|
2036
|
-
|
|
2037
|
-
|
|
2102
|
+
fs5.mkdirSync(dataDir(), { recursive: true });
|
|
2103
|
+
fs5.writeFileSync(portFile(), JSON.stringify({ port: discovered }), "utf-8");
|
|
2038
2104
|
} catch {
|
|
2039
2105
|
}
|
|
2040
2106
|
return { port: discovered };
|
|
@@ -2043,7 +2109,7 @@ function discoverAndHeal() {
|
|
|
2043
2109
|
}
|
|
2044
2110
|
function readPort() {
|
|
2045
2111
|
try {
|
|
2046
|
-
const raw =
|
|
2112
|
+
const raw = fs5.readFileSync(portFile(), "utf-8").trim();
|
|
2047
2113
|
if (!raw) return { port: null, reason: "invalid" };
|
|
2048
2114
|
if (raw.startsWith("{")) {
|
|
2049
2115
|
const parsed = JSON.parse(raw);
|
|
@@ -2298,7 +2364,7 @@ function registerTaskTools(server) {
|
|
|
2298
2364
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2299
2365
|
const status = args.status ?? "todo";
|
|
2300
2366
|
const task = {
|
|
2301
|
-
id:
|
|
2367
|
+
id: crypto3.randomUUID(),
|
|
2302
2368
|
projectName: args.project_name,
|
|
2303
2369
|
title: args.title,
|
|
2304
2370
|
description: args.description ?? "",
|
|
@@ -2481,12 +2547,12 @@ function registerTaskTools(server) {
|
|
|
2481
2547
|
};
|
|
2482
2548
|
}
|
|
2483
2549
|
const cwd = args.cwd || process.cwd();
|
|
2484
|
-
const normalizedCwd =
|
|
2550
|
+
const normalizedCwd = path5.resolve(cwd);
|
|
2485
2551
|
const projects = await dbListProjects();
|
|
2486
2552
|
let matchedProject = null;
|
|
2487
2553
|
let matchLen = 0;
|
|
2488
2554
|
for (const p of projects) {
|
|
2489
|
-
const normalizedPath =
|
|
2555
|
+
const normalizedPath = path5.resolve(p.path);
|
|
2490
2556
|
if (normalizedCwd.startsWith(normalizedPath) && normalizedPath.length > matchLen) {
|
|
2491
2557
|
matchedProject = p;
|
|
2492
2558
|
matchLen = normalizedPath.length;
|
|
@@ -2514,7 +2580,7 @@ function registerTaskTools(server) {
|
|
|
2514
2580
|
let matchedTask = null;
|
|
2515
2581
|
for (const t of projectTasks) {
|
|
2516
2582
|
if (t.worktreePath) {
|
|
2517
|
-
const normalizedWorktree =
|
|
2583
|
+
const normalizedWorktree = path5.resolve(t.worktreePath);
|
|
2518
2584
|
if (normalizedCwd.startsWith(normalizedWorktree)) {
|
|
2519
2585
|
matchedTask = t;
|
|
2520
2586
|
break;
|
|
@@ -2998,12 +3064,43 @@ function registerSessionTools(server) {
|
|
|
2998
3064
|
}
|
|
2999
3065
|
|
|
3000
3066
|
// src/tools/workflows.ts
|
|
3001
|
-
import
|
|
3067
|
+
import crypto4 from "crypto";
|
|
3002
3068
|
|
|
3003
3069
|
// ../shared/src/workflow-graph.ts
|
|
3070
|
+
var MAX_GATE_ROUNDS = 10;
|
|
3071
|
+
function nodesBetween(from, to, edges) {
|
|
3072
|
+
if (from === to) return /* @__PURE__ */ new Set();
|
|
3073
|
+
const { successors, predecessors } = buildGraph(edges);
|
|
3074
|
+
const reach = (start, next) => {
|
|
3075
|
+
const seen = /* @__PURE__ */ new Set([start]);
|
|
3076
|
+
const queue = [start];
|
|
3077
|
+
while (queue.length > 0) {
|
|
3078
|
+
for (const id of next.get(queue.shift()) ?? []) {
|
|
3079
|
+
if (!seen.has(id)) {
|
|
3080
|
+
seen.add(id);
|
|
3081
|
+
queue.push(id);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
return seen;
|
|
3086
|
+
};
|
|
3087
|
+
const below = reach(from, successors);
|
|
3088
|
+
if (!below.has(to)) return /* @__PURE__ */ new Set();
|
|
3089
|
+
const above = reach(to, predecessors);
|
|
3090
|
+
return new Set([...below].filter((id) => above.has(id)));
|
|
3091
|
+
}
|
|
3004
3092
|
function isSignInWait(state) {
|
|
3005
3093
|
return state.status === "waiting" && state.waitingFor === "signIn";
|
|
3006
3094
|
}
|
|
3095
|
+
function buildGraph(edges) {
|
|
3096
|
+
const successors = /* @__PURE__ */ new Map();
|
|
3097
|
+
const predecessors = /* @__PURE__ */ new Map();
|
|
3098
|
+
for (const e of edges) {
|
|
3099
|
+
successors.set(e.source, [...successors.get(e.source) || [], e.target]);
|
|
3100
|
+
predecessors.set(e.target, [...predecessors.get(e.target) || [], e.source]);
|
|
3101
|
+
}
|
|
3102
|
+
return { successors, predecessors };
|
|
3103
|
+
}
|
|
3007
3104
|
|
|
3008
3105
|
// src/tools/workflows.ts
|
|
3009
3106
|
import { z as z5 } from "zod";
|
|
@@ -3364,7 +3461,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
3364
3461
|
const nodes = [];
|
|
3365
3462
|
const edges = [];
|
|
3366
3463
|
const triggerNode = {
|
|
3367
|
-
id:
|
|
3464
|
+
id: crypto4.randomUUID(),
|
|
3368
3465
|
type: "trigger",
|
|
3369
3466
|
label: trigger.triggerType === "manual" ? "Manual Trigger" : trigger.triggerType === "once" ? "Schedule (Once)" : trigger.triggerType === "recurring" ? "Schedule (Recurring)" : trigger.triggerType === "taskCreated" ? "When Task Created" : trigger.triggerType === "taskStatusChanged" ? "When Task Status Changes" : "Trigger",
|
|
3370
3467
|
config: trigger,
|
|
@@ -3375,7 +3472,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
3375
3472
|
const NODE_GAP = 140;
|
|
3376
3473
|
for (let i = 0; i < actions.length; i++) {
|
|
3377
3474
|
const action = actions[i];
|
|
3378
|
-
const nodeId =
|
|
3475
|
+
const nodeId = crypto4.randomUUID();
|
|
3379
3476
|
nodes.push({
|
|
3380
3477
|
id: nodeId,
|
|
3381
3478
|
type: "launchAgent",
|
|
@@ -3384,7 +3481,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
3384
3481
|
position: { x: 0, y: (i + 1) * NODE_GAP }
|
|
3385
3482
|
});
|
|
3386
3483
|
edges.push({
|
|
3387
|
-
id:
|
|
3484
|
+
id: crypto4.randomUUID(),
|
|
3388
3485
|
source: prevId,
|
|
3389
3486
|
target: nodeId
|
|
3390
3487
|
});
|
|
@@ -3484,12 +3581,37 @@ function annotateWaitingGates(runs, workflows) {
|
|
|
3484
3581
|
asks: "Sign in to its connection in the Vorn app, and this step runs again"
|
|
3485
3582
|
};
|
|
3486
3583
|
}
|
|
3487
|
-
const asks = gateMessage(workflow, state.nodeId);
|
|
3584
|
+
const asks = state.message ?? gateMessage(workflow, state.nodeId);
|
|
3488
3585
|
return asks ? { ...state, asks } : state;
|
|
3489
3586
|
})
|
|
3490
3587
|
};
|
|
3491
3588
|
});
|
|
3492
3589
|
}
|
|
3590
|
+
var ANSWERED = {
|
|
3591
|
+
approve: "Approved",
|
|
3592
|
+
reject: "Rejected",
|
|
3593
|
+
changes: "Sent back"
|
|
3594
|
+
};
|
|
3595
|
+
function validateGateFeedback(nodes, edges) {
|
|
3596
|
+
const errors = [];
|
|
3597
|
+
for (const node of nodes) {
|
|
3598
|
+
if (node.type !== "approval" || node.config?.feedback === void 0) continue;
|
|
3599
|
+
const feedback = node.config.feedback;
|
|
3600
|
+
const name = node.label || node.id;
|
|
3601
|
+
const from = typeof feedback?.from === "string" ? feedback.from : "";
|
|
3602
|
+
const source2 = nodes.find((n) => n.id === from);
|
|
3603
|
+
if (!source2) errors.push(`gate "${name}" redoes from unknown step "${from}"`);
|
|
3604
|
+
else if (source2.type === "trigger") errors.push(`gate "${name}" cannot redo from its trigger`);
|
|
3605
|
+
else if (nodesBetween(from, node.id, edges).size === 0) {
|
|
3606
|
+
errors.push(`gate "${name}" redoes from "${source2.label || from}", which does not lead to it`);
|
|
3607
|
+
}
|
|
3608
|
+
const rounds = Number(feedback?.maxRounds);
|
|
3609
|
+
if (!Number.isInteger(rounds) || rounds < 2 || rounds > MAX_GATE_ROUNDS) {
|
|
3610
|
+
errors.push(`gate "${name}" needs maxRounds between 2 and ${MAX_GATE_ROUNDS}`);
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
return errors;
|
|
3614
|
+
}
|
|
3493
3615
|
async function runById(runId) {
|
|
3494
3616
|
const recent = (await listAllWorkflowRuns(void 0, 500)).find((r) => r.runId === runId);
|
|
3495
3617
|
if (recent) return recent;
|
|
@@ -3561,9 +3683,8 @@ function registerWorkflowTools(server) {
|
|
|
3561
3683
|
if (args.nodes && args.edges) {
|
|
3562
3684
|
nodes = args.nodes;
|
|
3563
3685
|
edges = args.edges;
|
|
3564
|
-
const
|
|
3565
|
-
|
|
3566
|
-
);
|
|
3686
|
+
const loose = nodes;
|
|
3687
|
+
const loopErrors = [...validateLoopBodies(loose), ...validateGateFeedback(loose, edges)];
|
|
3567
3688
|
if (loopErrors.length > 0) {
|
|
3568
3689
|
return {
|
|
3569
3690
|
content: [{ type: "text", text: `Error: ${loopErrors.join("; ")}` }],
|
|
@@ -3578,7 +3699,7 @@ function registerWorkflowTools(server) {
|
|
|
3578
3699
|
edges = graph.edges;
|
|
3579
3700
|
}
|
|
3580
3701
|
const workflow = {
|
|
3581
|
-
id:
|
|
3702
|
+
id: crypto4.randomUUID(),
|
|
3582
3703
|
name: args.name,
|
|
3583
3704
|
icon: args.icon ?? "Zap",
|
|
3584
3705
|
iconColor: args.icon_color ?? "#6366f1",
|
|
@@ -3622,9 +3743,9 @@ function registerWorkflowTools(server) {
|
|
|
3622
3743
|
const updates = {};
|
|
3623
3744
|
if (args.name !== void 0) updates.name = args.name;
|
|
3624
3745
|
if (args.nodes !== void 0) {
|
|
3625
|
-
const
|
|
3626
|
-
|
|
3627
|
-
);
|
|
3746
|
+
const loose = args.nodes;
|
|
3747
|
+
const edges = args.edges ?? workflow.edges;
|
|
3748
|
+
const loopErrors = [...validateLoopBodies(loose), ...validateGateFeedback(loose, edges)];
|
|
3628
3749
|
if (loopErrors.length > 0) {
|
|
3629
3750
|
return {
|
|
3630
3751
|
content: [{ type: "text", text: `Error: ${loopErrors.join("; ")}` }],
|
|
@@ -3753,10 +3874,18 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3753
3874
|
);
|
|
3754
3875
|
server.tool(
|
|
3755
3876
|
"resolve_gate",
|
|
3756
|
-
"Approve or
|
|
3877
|
+
"Approve, reject, or send back 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.",
|
|
3757
3878
|
{
|
|
3758
3879
|
run_id: V.id.describe("Run ID (from list_workflow_runs)"),
|
|
3759
|
-
decision: z5.enum(["approve", "reject"]).describe(
|
|
3880
|
+
decision: z5.enum(["approve", "reject", "changes"]).describe(
|
|
3881
|
+
"approve lets the run go on; reject ends it; changes sends the work back to the step the gate redoes from, with your comment, and the gate asks again"
|
|
3882
|
+
),
|
|
3883
|
+
comment: z5.string().max(2e4).optional().describe(
|
|
3884
|
+
"Required for changes: what to change. Optional on approve and reject; kept with the run."
|
|
3885
|
+
),
|
|
3886
|
+
edited: z5.string().max(2e5).optional().describe(
|
|
3887
|
+
"Your rewrite of the gate's editable text, when it has one. Kept beside the original, and read by later steps as {{steps.<gate>.text}}. Ignored on reject."
|
|
3888
|
+
),
|
|
3760
3889
|
node_id: V.id.optional().describe("The waiting node, when a run has more than one gate open")
|
|
3761
3890
|
},
|
|
3762
3891
|
async (args) => {
|
|
@@ -3789,15 +3918,36 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3789
3918
|
isError: true
|
|
3790
3919
|
};
|
|
3791
3920
|
}
|
|
3921
|
+
if (args.decision === "changes" && !args.comment?.trim()) {
|
|
3922
|
+
return {
|
|
3923
|
+
content: [
|
|
3924
|
+
{ type: "text", text: "Error: changes needs a comment saying what to change." }
|
|
3925
|
+
],
|
|
3926
|
+
isError: true
|
|
3927
|
+
};
|
|
3928
|
+
}
|
|
3792
3929
|
const workflow = (await dbListWorkflows()).find((w) => w.id === run.workflowId);
|
|
3793
3930
|
const gateNode = approvalNode(workflow, target.nodeId);
|
|
3794
|
-
const asked = askedBy(gateNode);
|
|
3931
|
+
const asked = run.nodeStates.find((n) => n.nodeId === target.nodeId)?.message ?? askedBy(gateNode);
|
|
3795
3932
|
try {
|
|
3796
|
-
await rpcCall("workflow:resolveGate", {
|
|
3933
|
+
const answer = await rpcCall("workflow:resolveGate", {
|
|
3797
3934
|
runId: args.run_id,
|
|
3798
3935
|
nodeId: target.nodeId,
|
|
3799
|
-
decision: args.decision
|
|
3936
|
+
decision: args.decision,
|
|
3937
|
+
...args.comment?.trim() && { comment: args.comment.trim() },
|
|
3938
|
+
...args.edited?.trim() && { edited: args.edited.trim() }
|
|
3800
3939
|
});
|
|
3940
|
+
if (answer?.accepted === false) {
|
|
3941
|
+
return {
|
|
3942
|
+
content: [
|
|
3943
|
+
{
|
|
3944
|
+
type: "text",
|
|
3945
|
+
text: args.decision === "changes" ? "Error: this gate takes no changes now: it has no step to redo from, or its rounds are used up. Approve or reject it instead." : "Error: the gate did not take that answer. Check list_workflow_runs."
|
|
3946
|
+
}
|
|
3947
|
+
],
|
|
3948
|
+
isError: true
|
|
3949
|
+
};
|
|
3950
|
+
}
|
|
3801
3951
|
} catch (err) {
|
|
3802
3952
|
return {
|
|
3803
3953
|
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
|
|
@@ -3809,7 +3959,7 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3809
3959
|
content: [
|
|
3810
3960
|
{
|
|
3811
3961
|
type: "text",
|
|
3812
|
-
text: `${args.decision
|
|
3962
|
+
text: `${ANSWERED[args.decision]} "${gate}" on run ${args.run_id}${run.workflowName ? ` of "${run.workflowName}"` : ""}.${asked ? `
|
|
3813
3963
|
|
|
3814
3964
|
What it asked: ${asked}` : ""}
|
|
3815
3965
|
|
|
@@ -4018,9 +4168,11 @@ Warning: ${unnamed.length} step(s) point at a connection this install could not
|
|
|
4018
4168
|
isError: true
|
|
4019
4169
|
};
|
|
4020
4170
|
}
|
|
4021
|
-
const
|
|
4022
|
-
|
|
4023
|
-
|
|
4171
|
+
const loose = parsed.nodes;
|
|
4172
|
+
const loopErrors = [
|
|
4173
|
+
...validateLoopBodies(loose),
|
|
4174
|
+
...validateGateFeedback(loose, parsed.edges)
|
|
4175
|
+
];
|
|
4024
4176
|
if (loopErrors.length > 0) {
|
|
4025
4177
|
return {
|
|
4026
4178
|
content: [{ type: "text", text: `Error: ${loopErrors.join("; ")}` }],
|
|
@@ -4080,7 +4232,7 @@ function registerConfigTools(server) {
|
|
|
4080
4232
|
}
|
|
4081
4233
|
|
|
4082
4234
|
// src/tools/workspaces.ts
|
|
4083
|
-
import
|
|
4235
|
+
import crypto5 from "crypto";
|
|
4084
4236
|
import { z as z6 } from "zod";
|
|
4085
4237
|
function registerWorkspaceTools(server) {
|
|
4086
4238
|
server.tool("list_workspaces", "List all workspaces", async () => {
|
|
@@ -4099,7 +4251,7 @@ function registerWorkspaceTools(server) {
|
|
|
4099
4251
|
const existing = await dbListWorkspaces();
|
|
4100
4252
|
const maxOrder = existing.reduce((max, w) => Math.max(max, w.order), 0);
|
|
4101
4253
|
const workspace = {
|
|
4102
|
-
id:
|
|
4254
|
+
id: crypto5.randomUUID(),
|
|
4103
4255
|
name: args.name,
|
|
4104
4256
|
order: maxOrder + 1,
|
|
4105
4257
|
...args.icon && { icon: args.icon },
|
|
@@ -4507,7 +4659,7 @@ function plural(count, word) {
|
|
|
4507
4659
|
}
|
|
4508
4660
|
|
|
4509
4661
|
// src/tools/browser.ts
|
|
4510
|
-
import
|
|
4662
|
+
import crypto6 from "crypto";
|
|
4511
4663
|
import { z as z8 } from "zod";
|
|
4512
4664
|
function sessionId(env = process.env) {
|
|
4513
4665
|
const id = env.VORN_SESSION_ID;
|
|
@@ -4534,7 +4686,7 @@ function source(label) {
|
|
|
4534
4686
|
return label.includes("WEB PAGE") || label.includes("BROWSER") ? "page" : "device";
|
|
4535
4687
|
}
|
|
4536
4688
|
function pageResult(data, label = "WEB PAGE CONTENT") {
|
|
4537
|
-
const nonce =
|
|
4689
|
+
const nonce = crypto6.randomUUID();
|
|
4538
4690
|
return {
|
|
4539
4691
|
content: [
|
|
4540
4692
|
{
|
|
@@ -4983,7 +5135,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
4983
5135
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
4984
5136
|
async function main() {
|
|
4985
5137
|
configManager.init();
|
|
4986
|
-
const version = true ? "0.7.1-beta.
|
|
5138
|
+
const version = true ? "0.7.1-beta.6" : createRequire(import.meta.url)("../package.json").version;
|
|
4987
5139
|
const server = createMcpServer(version);
|
|
4988
5140
|
const transport = new StdioServerTransport();
|
|
4989
5141
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vornrun/mcp",
|
|
3
|
-
"version": "0.7.1-beta.
|
|
3
|
+
"version": "0.7.1-beta.6",
|
|
4
4
|
"description": "Vorn MCP server — task management, git, and workflow tools for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"zod": "^4.5.4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@vornrun/server": "0.7.1-beta.
|
|
42
|
-
"@vornrun/shared": "0.7.1-beta.
|
|
41
|
+
"@vornrun/server": "0.7.1-beta.6",
|
|
42
|
+
"@vornrun/shared": "0.7.1-beta.6",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
44
|
"tsx": "^4.23.1",
|
|
45
45
|
"typescript": "^6.0.3"
|