@vornrun/mcp 0.6.0 → 0.6.1-beta.1
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 +688 -735
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -6,8 +6,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
|
|
7
7
|
// ../server/src/config-manager.ts
|
|
8
8
|
import fs3 from "fs";
|
|
9
|
-
import path3 from "path";
|
|
10
|
-
import os2 from "os";
|
|
11
9
|
|
|
12
10
|
// ../shared/src/agent-defaults.ts
|
|
13
11
|
var DEFAULT_AGENT_COMMANDS = {
|
|
@@ -50,6 +48,11 @@ var logger_default = log;
|
|
|
50
48
|
import { execFileSync, execFile } from "child_process";
|
|
51
49
|
import fs from "fs";
|
|
52
50
|
import path from "path";
|
|
51
|
+
|
|
52
|
+
// ../shared/src/protocol.ts
|
|
53
|
+
var LOCAL_TOKEN_FILENAME = "local-token";
|
|
54
|
+
|
|
55
|
+
// ../server/src/process-utils.ts
|
|
53
56
|
function getDefaultShell(configured) {
|
|
54
57
|
const chosen = configured?.trim();
|
|
55
58
|
if (chosen) return chosen;
|
|
@@ -92,9 +95,9 @@ var SDK_FILTER_KEYS = {
|
|
|
92
95
|
version: "sdkVersion",
|
|
93
96
|
icon: "sdkIcon"
|
|
94
97
|
};
|
|
95
|
-
function connectionConnectorId(
|
|
96
|
-
const packaged =
|
|
97
|
-
return typeof packaged === "string" && packaged !== "" ? packaged :
|
|
98
|
+
function connectionConnectorId(connection2) {
|
|
99
|
+
const packaged = connection2.filters?.[SDK_FILTER_KEYS.connectorId];
|
|
100
|
+
return typeof packaged === "string" && packaged !== "" ? packaged : connection2.connectorId;
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
// ../server/src/default-workflows.ts
|
|
@@ -140,25 +143,35 @@ function buildDefaultTaskWorkflow() {
|
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
// ../server/src/database.ts
|
|
143
|
-
var
|
|
144
|
-
var
|
|
146
|
+
var DEFAULT_DATA_DIR = path2.join(os.homedir(), ".vorn");
|
|
147
|
+
var resolvedDataDir = null;
|
|
148
|
+
function dbPath() {
|
|
149
|
+
return path2.join(getDataDir(), "vorn.db");
|
|
150
|
+
}
|
|
145
151
|
var db = null;
|
|
146
152
|
function getDb() {
|
|
147
153
|
if (!db) throw new Error("Database not initialized. Call initDatabase() first.");
|
|
148
154
|
return db;
|
|
149
155
|
}
|
|
150
|
-
function
|
|
151
|
-
if (!
|
|
152
|
-
|
|
156
|
+
function getDataDir() {
|
|
157
|
+
if (!resolvedDataDir) {
|
|
158
|
+
throw new Error("Data directory not resolved. Call initDatabase() first.");
|
|
159
|
+
}
|
|
160
|
+
return resolvedDataDir;
|
|
161
|
+
}
|
|
162
|
+
function initDatabase(dataDir) {
|
|
163
|
+
resolvedDataDir = dataDir ?? DEFAULT_DATA_DIR;
|
|
164
|
+
if (!fs2.existsSync(getDataDir())) {
|
|
165
|
+
fs2.mkdirSync(getDataDir(), { recursive: true, mode: 448 });
|
|
153
166
|
}
|
|
154
167
|
try {
|
|
155
|
-
db = new Database(
|
|
168
|
+
db = new Database(dbPath());
|
|
156
169
|
db.pragma("journal_mode = WAL");
|
|
157
170
|
db.pragma("foreign_keys = ON");
|
|
158
171
|
createSchema();
|
|
159
172
|
seedSystemDefaults();
|
|
160
173
|
} catch (err) {
|
|
161
|
-
logger_default.error("[database] Failed to open database:"
|
|
174
|
+
logger_default.error({ err }, "[database] Failed to open database:");
|
|
162
175
|
const message = err instanceof Error ? err.message : String(err);
|
|
163
176
|
const isCorrupt = /corrupt|notadb|malformed|not a database|file is not a database/i.test(
|
|
164
177
|
message
|
|
@@ -212,39 +225,32 @@ function recoverCorruptDatabase() {
|
|
|
212
225
|
}
|
|
213
226
|
db = null;
|
|
214
227
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
215
|
-
const backupPath = `${
|
|
228
|
+
const backupPath = `${dbPath()}.corrupt-${timestamp}`;
|
|
216
229
|
try {
|
|
217
|
-
if (fs2.existsSync(
|
|
218
|
-
fs2.copyFileSync(
|
|
230
|
+
if (fs2.existsSync(dbPath())) {
|
|
231
|
+
fs2.copyFileSync(dbPath(), backupPath);
|
|
219
232
|
logger_default.info(`[database] Backed up corrupt database to ${backupPath}`);
|
|
220
233
|
}
|
|
221
234
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
222
|
-
const file =
|
|
235
|
+
const file = dbPath() + suffix;
|
|
223
236
|
if (fs2.existsSync(file)) fs2.unlinkSync(file);
|
|
224
237
|
}
|
|
225
238
|
} catch (backupErr) {
|
|
226
|
-
logger_default.error("[database] Failed to back up corrupt database:"
|
|
239
|
+
logger_default.error({ backupErr }, "[database] Failed to back up corrupt database:");
|
|
227
240
|
}
|
|
228
241
|
try {
|
|
229
|
-
db = new Database(
|
|
242
|
+
db = new Database(dbPath());
|
|
230
243
|
db.pragma("journal_mode = WAL");
|
|
231
244
|
db.pragma("foreign_keys = ON");
|
|
232
245
|
createSchema();
|
|
233
246
|
seedSystemDefaults();
|
|
234
247
|
logger_default.info("[database] Successfully created fresh database after corruption recovery");
|
|
235
248
|
} catch (freshErr) {
|
|
236
|
-
logger_default.error("[database] Failed to create fresh database after corruption:"
|
|
249
|
+
logger_default.error({ freshErr }, "[database] Failed to create fresh database after corruption:");
|
|
237
250
|
throw freshErr;
|
|
238
251
|
}
|
|
239
252
|
logger_default.warn(`[database] Database was corrupted and has been reset. Backup saved to: ${backupPath}`);
|
|
240
253
|
}
|
|
241
|
-
function dbSignalChange() {
|
|
242
|
-
try {
|
|
243
|
-
const signalPath = path2.join(CONFIG_DIR, ".db-signal");
|
|
244
|
-
fs2.writeFileSync(signalPath, Date.now().toString());
|
|
245
|
-
} catch {
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
254
|
function closeDatabase() {
|
|
249
255
|
if (db) {
|
|
250
256
|
db.close();
|
|
@@ -269,6 +275,29 @@ function createSchema() {
|
|
|
269
275
|
value TEXT NOT NULL
|
|
270
276
|
);
|
|
271
277
|
|
|
278
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
279
|
+
id TEXT PRIMARY KEY,
|
|
280
|
+
name TEXT NOT NULL,
|
|
281
|
+
role TEXT NOT NULL DEFAULT 'owner',
|
|
282
|
+
created_at TEXT NOT NULL
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
-- Only the hash is stored. The plaintext is shown once at creation and is
|
|
286
|
+
-- not recoverable afterwards, so a leaked database yields no usable
|
|
287
|
+
-- credential.
|
|
288
|
+
CREATE TABLE IF NOT EXISTS device_tokens (
|
|
289
|
+
id TEXT PRIMARY KEY,
|
|
290
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
291
|
+
name TEXT NOT NULL,
|
|
292
|
+
token_hash TEXT NOT NULL,
|
|
293
|
+
created_at TEXT NOT NULL,
|
|
294
|
+
last_seen_at TEXT,
|
|
295
|
+
revoked_at TEXT
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
CREATE INDEX IF NOT EXISTS idx_device_tokens_user
|
|
299
|
+
ON device_tokens(user_id);
|
|
300
|
+
|
|
272
301
|
CREATE TABLE IF NOT EXISTS projects (
|
|
273
302
|
name TEXT PRIMARY KEY,
|
|
274
303
|
path TEXT NOT NULL,
|
|
@@ -496,9 +525,9 @@ function seedLegacyConnectorPollState(d) {
|
|
|
496
525
|
);
|
|
497
526
|
const connectionId = trigger?.config?.connectionId;
|
|
498
527
|
if (!connectionId) continue;
|
|
499
|
-
const
|
|
500
|
-
if (!
|
|
501
|
-
insertState.run(workflow.id, connectionId,
|
|
528
|
+
const connection2 = readConnection.get(connectionId);
|
|
529
|
+
if (!connection2) continue;
|
|
530
|
+
insertState.run(workflow.id, connectionId, connection2.sync_cursor, connection2.last_sync_at);
|
|
502
531
|
}
|
|
503
532
|
}
|
|
504
533
|
function migrateSchema(d) {
|
|
@@ -821,6 +850,57 @@ function migrateSchema(d) {
|
|
|
821
850
|
})();
|
|
822
851
|
logger_default.info("[database] migrated schema to version 13 (connection-scoped inbox)");
|
|
823
852
|
}
|
|
853
|
+
if (version < 14) {
|
|
854
|
+
d.transaction(() => {
|
|
855
|
+
const existing = d.prepare("SELECT COUNT(*) AS n FROM users").get();
|
|
856
|
+
if (existing.n === 0) {
|
|
857
|
+
let name = "owner";
|
|
858
|
+
try {
|
|
859
|
+
name = os.userInfo().username || name;
|
|
860
|
+
} catch {
|
|
861
|
+
}
|
|
862
|
+
d.prepare("INSERT INTO users (id, name, role, created_at) VALUES (?, ?, 'owner', ?)").run(
|
|
863
|
+
randomUUID(),
|
|
864
|
+
name,
|
|
865
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
d.prepare(
|
|
869
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '14')"
|
|
870
|
+
).run();
|
|
871
|
+
})();
|
|
872
|
+
logger_default.info("[database] migrated schema to version 14 (identity and device tokens)");
|
|
873
|
+
}
|
|
874
|
+
if (version < 15) {
|
|
875
|
+
d.transaction(() => {
|
|
876
|
+
for (const table of REVISIONED_TABLES) {
|
|
877
|
+
const columns = d.prepare(`PRAGMA table_info(${table})`).all();
|
|
878
|
+
if (columns.some((c) => c.name === "row_revision")) continue;
|
|
879
|
+
d.exec(`ALTER TABLE ${table} ADD COLUMN row_revision INTEGER NOT NULL DEFAULT 0`);
|
|
880
|
+
}
|
|
881
|
+
d.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES (?, ?)").run(
|
|
882
|
+
CONFIG_REVISION_KEY,
|
|
883
|
+
"0"
|
|
884
|
+
);
|
|
885
|
+
d.prepare(
|
|
886
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '15')"
|
|
887
|
+
).run();
|
|
888
|
+
})();
|
|
889
|
+
logger_default.info("[database] migrated schema to version 15 (config row revisions)");
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
var REVISIONED_TABLES = [
|
|
893
|
+
"projects",
|
|
894
|
+
"tasks",
|
|
895
|
+
"workspaces",
|
|
896
|
+
"remote_hosts",
|
|
897
|
+
"agent_commands"
|
|
898
|
+
];
|
|
899
|
+
var CONFIG_REVISION_KEY = "config_revision";
|
|
900
|
+
function readConfigRevision(d) {
|
|
901
|
+
const row = d.prepare("SELECT value FROM schema_meta WHERE key = ?").get(CONFIG_REVISION_KEY);
|
|
902
|
+
const parsed = Number(row?.value);
|
|
903
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
824
904
|
}
|
|
825
905
|
function verifySchema(d) {
|
|
826
906
|
const expectedByTable = {
|
|
@@ -930,6 +1010,15 @@ function verifySchema(d) {
|
|
|
930
1010
|
}
|
|
931
1011
|
]
|
|
932
1012
|
};
|
|
1013
|
+
for (const table of REVISIONED_TABLES) {
|
|
1014
|
+
expectedByTable[table] = [
|
|
1015
|
+
...expectedByTable[table] ?? [],
|
|
1016
|
+
{
|
|
1017
|
+
column: "row_revision",
|
|
1018
|
+
ddl: `ALTER TABLE ${table} ADD COLUMN row_revision INTEGER NOT NULL DEFAULT 0`
|
|
1019
|
+
}
|
|
1020
|
+
];
|
|
1021
|
+
}
|
|
933
1022
|
for (const [table, columns] of Object.entries(expectedByTable)) {
|
|
934
1023
|
const existing = new Set(
|
|
935
1024
|
d.prepare(`PRAGMA table_info(${table})`).all().map((c) => c.name)
|
|
@@ -940,7 +1029,7 @@ function verifySchema(d) {
|
|
|
940
1029
|
d.exec(ddl);
|
|
941
1030
|
logger_default.warn(`[database] self-heal: added missing column ${table}.${column}`);
|
|
942
1031
|
} catch (err) {
|
|
943
|
-
logger_default.error(`[database] self-heal: failed to add ${table}.${column}
|
|
1032
|
+
logger_default.error({ err }, `[database] self-heal: failed to add ${table}.${column}:`);
|
|
944
1033
|
}
|
|
945
1034
|
}
|
|
946
1035
|
}
|
|
@@ -956,6 +1045,7 @@ function loadConfig() {
|
|
|
956
1045
|
const workspaces = loadWorkspaces(d);
|
|
957
1046
|
return {
|
|
958
1047
|
version: 1,
|
|
1048
|
+
revision: readConfigRevision(d),
|
|
959
1049
|
defaults,
|
|
960
1050
|
projects,
|
|
961
1051
|
agentCommands: Object.keys(agentCommands).length > 0 ? agentCommands : { ...DEFAULT_AGENT_COMMANDS },
|
|
@@ -1027,6 +1117,7 @@ function loadDefaults(d) {
|
|
|
1027
1117
|
...map.mobileAccessEnabled !== void 0 && {
|
|
1028
1118
|
mobileAccessEnabled: map.mobileAccessEnabled
|
|
1029
1119
|
},
|
|
1120
|
+
...map.serverPort !== void 0 && { serverPort: map.serverPort },
|
|
1030
1121
|
...map.networkAccessEnabled !== void 0 && {
|
|
1031
1122
|
networkAccessEnabled: map.networkAccessEnabled
|
|
1032
1123
|
},
|
|
@@ -1038,6 +1129,23 @@ function loadDefaults(d) {
|
|
|
1038
1129
|
},
|
|
1039
1130
|
...map.hasSeededDefaultTaskWorkflow !== void 0 && {
|
|
1040
1131
|
hasSeededDefaultTaskWorkflow: map.hasSeededDefaultTaskWorkflow
|
|
1132
|
+
},
|
|
1133
|
+
// The four below were declared in AppConfig and consumed, but never listed
|
|
1134
|
+
// here — exactly the failure the comment above describes. Each was written on
|
|
1135
|
+
// save and dropped on the next load, so the setting appeared to work until a
|
|
1136
|
+
// reload. `worktreeRetention` is the worst of them: it is read server-side
|
|
1137
|
+
// (register-methods.ts) and so always resolved to undefined.
|
|
1138
|
+
...map.updateAutoDownload !== void 0 && {
|
|
1139
|
+
updateAutoDownload: map.updateAutoDownload
|
|
1140
|
+
},
|
|
1141
|
+
...map.headlessStepTimeoutMinutes !== void 0 && {
|
|
1142
|
+
headlessStepTimeoutMinutes: map.headlessStepTimeoutMinutes
|
|
1143
|
+
},
|
|
1144
|
+
...map.enableHoverPreview !== void 0 && {
|
|
1145
|
+
enableHoverPreview: map.enableHoverPreview
|
|
1146
|
+
},
|
|
1147
|
+
...map.worktreeRetention !== void 0 && {
|
|
1148
|
+
worktreeRetention: map.worktreeRetention
|
|
1041
1149
|
}
|
|
1042
1150
|
};
|
|
1043
1151
|
}
|
|
@@ -1086,19 +1194,48 @@ function loadWorkspaces(d) {
|
|
|
1086
1194
|
const rows = d.prepare('SELECT * FROM workspaces ORDER BY "order"').all();
|
|
1087
1195
|
return rows.map(rowToWorkspace);
|
|
1088
1196
|
}
|
|
1197
|
+
function pruneMissing(d, table, keyColumn, keep, baseRevision) {
|
|
1198
|
+
const wanted = new Set(keep.filter((k) => typeof k === "string"));
|
|
1199
|
+
const existing = d.prepare(`SELECT ${keyColumn} AS key, row_revision AS revision FROM ${table}`).all();
|
|
1200
|
+
const remove = d.prepare(`DELETE FROM ${table} WHERE ${keyColumn} = ?`);
|
|
1201
|
+
for (const { key, revision } of existing) {
|
|
1202
|
+
if (wanted.has(key)) continue;
|
|
1203
|
+
if (revision > baseRevision) continue;
|
|
1204
|
+
remove.run(key);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1089
1207
|
function saveConfig(config) {
|
|
1090
1208
|
const d = getDb();
|
|
1091
1209
|
const run = d.transaction(() => {
|
|
1092
|
-
|
|
1093
|
-
const
|
|
1210
|
+
const baseRevision = config.revision ?? Number.MAX_SAFE_INTEGER;
|
|
1211
|
+
const revision = readConfigRevision(d) + 1;
|
|
1212
|
+
const upsertDefault = d.prepare(
|
|
1213
|
+
`INSERT INTO defaults (key, value) VALUES (?, ?)
|
|
1214
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
1215
|
+
);
|
|
1216
|
+
const deleteDefault = d.prepare("DELETE FROM defaults WHERE key = ?");
|
|
1094
1217
|
for (const [key, value] of Object.entries(config.defaults)) {
|
|
1095
|
-
if (value
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1218
|
+
if (value === void 0) deleteDefault.run(key);
|
|
1219
|
+
else upsertDefault.run(key, JSON.stringify(value));
|
|
1098
1220
|
}
|
|
1099
|
-
|
|
1221
|
+
pruneMissing(
|
|
1222
|
+
d,
|
|
1223
|
+
"projects",
|
|
1224
|
+
"name",
|
|
1225
|
+
config.projects.map((p) => p.name),
|
|
1226
|
+
baseRevision
|
|
1227
|
+
);
|
|
1100
1228
|
const insertProject = d.prepare(
|
|
1101
|
-
|
|
1229
|
+
`INSERT INTO projects (name, path, preferred_agents, icon, icon_color, host_ids, workspace_id, row_revision)
|
|
1230
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1231
|
+
ON CONFLICT(name) DO UPDATE SET
|
|
1232
|
+
row_revision = excluded.row_revision,
|
|
1233
|
+
path = excluded.path,
|
|
1234
|
+
preferred_agents = excluded.preferred_agents,
|
|
1235
|
+
icon = excluded.icon,
|
|
1236
|
+
icon_color = excluded.icon_color,
|
|
1237
|
+
host_ids = excluded.host_ids,
|
|
1238
|
+
workspace_id = excluded.workspace_id`
|
|
1102
1239
|
);
|
|
1103
1240
|
for (const p of config.projects) {
|
|
1104
1241
|
insertProject.run(
|
|
@@ -1108,7 +1245,8 @@ function saveConfig(config) {
|
|
|
1108
1245
|
p.icon ?? null,
|
|
1109
1246
|
p.iconColor ?? null,
|
|
1110
1247
|
p.hostIds ? JSON.stringify(p.hostIds) : null,
|
|
1111
|
-
p.workspaceId ?? "personal"
|
|
1248
|
+
p.workspaceId ?? "personal",
|
|
1249
|
+
revision
|
|
1112
1250
|
);
|
|
1113
1251
|
}
|
|
1114
1252
|
const workflows = config.workflows ?? [];
|
|
@@ -1148,9 +1286,23 @@ function saveConfig(config) {
|
|
|
1148
1286
|
w.workspaceId ?? "personal"
|
|
1149
1287
|
);
|
|
1150
1288
|
}
|
|
1151
|
-
|
|
1289
|
+
pruneMissing(
|
|
1290
|
+
d,
|
|
1291
|
+
"agent_commands",
|
|
1292
|
+
"agent_type",
|
|
1293
|
+
Object.keys(config.agentCommands ?? {}),
|
|
1294
|
+
baseRevision
|
|
1295
|
+
);
|
|
1152
1296
|
const insertAgent = d.prepare(
|
|
1153
|
-
|
|
1297
|
+
`INSERT INTO agent_commands (agent_type, command, args, headless_args, fallback_command, fallback_args, row_revision)
|
|
1298
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1299
|
+
ON CONFLICT(agent_type) DO UPDATE SET
|
|
1300
|
+
row_revision = excluded.row_revision,
|
|
1301
|
+
command = excluded.command,
|
|
1302
|
+
args = excluded.args,
|
|
1303
|
+
headless_args = excluded.headless_args,
|
|
1304
|
+
fallback_command = excluded.fallback_command,
|
|
1305
|
+
fallback_args = excluded.fallback_args`
|
|
1154
1306
|
);
|
|
1155
1307
|
if (config.agentCommands) {
|
|
1156
1308
|
for (const [agentType, cmd] of Object.entries(config.agentCommands)) {
|
|
@@ -1161,14 +1313,33 @@ function saveConfig(config) {
|
|
|
1161
1313
|
JSON.stringify(cmd.args),
|
|
1162
1314
|
cmd.headlessArgs ? JSON.stringify(cmd.headlessArgs) : null,
|
|
1163
1315
|
cmd.fallbackCommand ?? null,
|
|
1164
|
-
cmd.fallbackArgs ? JSON.stringify(cmd.fallbackArgs) : null
|
|
1316
|
+
cmd.fallbackArgs ? JSON.stringify(cmd.fallbackArgs) : null,
|
|
1317
|
+
revision
|
|
1165
1318
|
);
|
|
1166
1319
|
}
|
|
1167
1320
|
}
|
|
1168
1321
|
}
|
|
1169
|
-
|
|
1322
|
+
pruneMissing(
|
|
1323
|
+
d,
|
|
1324
|
+
"remote_hosts",
|
|
1325
|
+
"id",
|
|
1326
|
+
(config.remoteHosts ?? []).map((h) => h.id),
|
|
1327
|
+
baseRevision
|
|
1328
|
+
);
|
|
1170
1329
|
const insertHost = d.prepare(
|
|
1171
|
-
|
|
1330
|
+
`INSERT INTO remote_hosts (id, label, hostname, user, port, auth_method, ssh_key_path, credential_id, encrypted_password, ssh_options, row_revision)
|
|
1331
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1332
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1333
|
+
row_revision = excluded.row_revision,
|
|
1334
|
+
label = excluded.label,
|
|
1335
|
+
hostname = excluded.hostname,
|
|
1336
|
+
user = excluded.user,
|
|
1337
|
+
port = excluded.port,
|
|
1338
|
+
auth_method = excluded.auth_method,
|
|
1339
|
+
ssh_key_path = excluded.ssh_key_path,
|
|
1340
|
+
credential_id = excluded.credential_id,
|
|
1341
|
+
encrypted_password = excluded.encrypted_password,
|
|
1342
|
+
ssh_options = excluded.ssh_options`
|
|
1172
1343
|
);
|
|
1173
1344
|
for (const h of config.remoteHosts ?? []) {
|
|
1174
1345
|
insertHost.run(
|
|
@@ -1181,13 +1352,39 @@ function saveConfig(config) {
|
|
|
1181
1352
|
h.sshKeyPath ?? null,
|
|
1182
1353
|
h.credentialId ?? null,
|
|
1183
1354
|
h.encryptedPassword ?? null,
|
|
1184
|
-
h.sshOptions ?? null
|
|
1355
|
+
h.sshOptions ?? null,
|
|
1356
|
+
revision
|
|
1185
1357
|
);
|
|
1186
1358
|
}
|
|
1187
|
-
|
|
1359
|
+
pruneMissing(
|
|
1360
|
+
d,
|
|
1361
|
+
"tasks",
|
|
1362
|
+
"id",
|
|
1363
|
+
(config.tasks ?? []).map((t) => t.id),
|
|
1364
|
+
baseRevision
|
|
1365
|
+
);
|
|
1188
1366
|
const insertTask = d.prepare(
|
|
1189
|
-
`INSERT INTO tasks (id, project_name, title, description, status, "order", assigned_session_id, assigned_agent, agent_session_id, branch, use_worktree, created_at, updated_at, completed_at, archived_at, source_connector_id, source_external_url, source_external_id)
|
|
1190
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1367
|
+
`INSERT INTO tasks (id, project_name, title, description, status, "order", assigned_session_id, assigned_agent, agent_session_id, branch, use_worktree, created_at, updated_at, completed_at, archived_at, source_connector_id, source_external_url, source_external_id, row_revision)
|
|
1368
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1369
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1370
|
+
row_revision = excluded.row_revision,
|
|
1371
|
+
project_name = excluded.project_name,
|
|
1372
|
+
title = excluded.title,
|
|
1373
|
+
description = excluded.description,
|
|
1374
|
+
status = excluded.status,
|
|
1375
|
+
"order" = excluded."order",
|
|
1376
|
+
assigned_session_id = excluded.assigned_session_id,
|
|
1377
|
+
assigned_agent = excluded.assigned_agent,
|
|
1378
|
+
agent_session_id = excluded.agent_session_id,
|
|
1379
|
+
branch = excluded.branch,
|
|
1380
|
+
use_worktree = excluded.use_worktree,
|
|
1381
|
+
created_at = excluded.created_at,
|
|
1382
|
+
updated_at = excluded.updated_at,
|
|
1383
|
+
completed_at = excluded.completed_at,
|
|
1384
|
+
archived_at = excluded.archived_at,
|
|
1385
|
+
source_connector_id = excluded.source_connector_id,
|
|
1386
|
+
source_external_url = excluded.source_external_url,
|
|
1387
|
+
source_external_id = excluded.source_external_id`
|
|
1191
1388
|
);
|
|
1192
1389
|
for (const t of config.tasks ?? []) {
|
|
1193
1390
|
insertTask.run(
|
|
@@ -1208,307 +1405,37 @@ function saveConfig(config) {
|
|
|
1208
1405
|
t.archivedAt ?? null,
|
|
1209
1406
|
t.sourceConnectorId ?? null,
|
|
1210
1407
|
t.sourceExternalUrl ?? null,
|
|
1211
|
-
t.sourceExternalId ?? null
|
|
1408
|
+
t.sourceExternalId ?? null,
|
|
1409
|
+
revision
|
|
1212
1410
|
);
|
|
1213
1411
|
}
|
|
1214
|
-
|
|
1412
|
+
const workspaces = config.workspaces ?? [DEFAULT_WORKSPACE];
|
|
1413
|
+
pruneMissing(
|
|
1414
|
+
d,
|
|
1415
|
+
"workspaces",
|
|
1416
|
+
"id",
|
|
1417
|
+
workspaces.map((ws) => ws.id),
|
|
1418
|
+
baseRevision
|
|
1419
|
+
);
|
|
1215
1420
|
const insertWorkspace = d.prepare(
|
|
1216
|
-
`INSERT INTO workspaces (id, name, icon, icon_color, "order") VALUES (?, ?, ?, ?, ?)
|
|
1421
|
+
`INSERT INTO workspaces (id, name, icon, icon_color, "order", row_revision) VALUES (?, ?, ?, ?, ?, ?)
|
|
1422
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1423
|
+
row_revision = excluded.row_revision,
|
|
1424
|
+
name = excluded.name,
|
|
1425
|
+
icon = excluded.icon,
|
|
1426
|
+
icon_color = excluded.icon_color,
|
|
1427
|
+
"order" = excluded."order"`
|
|
1217
1428
|
);
|
|
1218
|
-
for (const ws of
|
|
1219
|
-
insertWorkspace.run(ws.id, ws.name, ws.icon ?? null, ws.iconColor ?? null, ws.order);
|
|
1429
|
+
for (const ws of workspaces) {
|
|
1430
|
+
insertWorkspace.run(ws.id, ws.name, ws.icon ?? null, ws.iconColor ?? null, ws.order, revision);
|
|
1220
1431
|
}
|
|
1432
|
+
d.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES (?, ?)").run(
|
|
1433
|
+
CONFIG_REVISION_KEY,
|
|
1434
|
+
String(revision)
|
|
1435
|
+
);
|
|
1221
1436
|
});
|
|
1222
1437
|
run();
|
|
1223
1438
|
}
|
|
1224
|
-
function dbListTasks(projectName, status) {
|
|
1225
|
-
const d = getDb();
|
|
1226
|
-
let sql = "SELECT * FROM tasks";
|
|
1227
|
-
const params = [];
|
|
1228
|
-
const clauses = [];
|
|
1229
|
-
if (projectName) {
|
|
1230
|
-
clauses.push("project_name = ?");
|
|
1231
|
-
params.push(projectName);
|
|
1232
|
-
}
|
|
1233
|
-
if (status) {
|
|
1234
|
-
clauses.push("status = ?");
|
|
1235
|
-
params.push(status);
|
|
1236
|
-
}
|
|
1237
|
-
if (clauses.length) sql += " WHERE " + clauses.join(" AND ");
|
|
1238
|
-
sql += ' ORDER BY "order"';
|
|
1239
|
-
const rows = d.prepare(sql).all(...params);
|
|
1240
|
-
return rows.map(rowToTask);
|
|
1241
|
-
}
|
|
1242
|
-
function dbGetTask(id) {
|
|
1243
|
-
const row = getDb().prepare("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
1244
|
-
return row ? rowToTask(row) : null;
|
|
1245
|
-
}
|
|
1246
|
-
function dbInsertTask(task) {
|
|
1247
|
-
getDb().prepare(
|
|
1248
|
-
`INSERT INTO tasks (id, project_name, title, description, status, "order", assigned_session_id, assigned_agent, agent_session_id, branch, use_worktree, created_at, updated_at, completed_at, archived_at, source_connector_id, source_external_url, source_external_id)
|
|
1249
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1250
|
-
).run(
|
|
1251
|
-
task.id,
|
|
1252
|
-
task.projectName,
|
|
1253
|
-
task.title,
|
|
1254
|
-
task.description,
|
|
1255
|
-
task.status,
|
|
1256
|
-
task.order,
|
|
1257
|
-
task.assignedSessionId ?? null,
|
|
1258
|
-
task.assignedAgent ?? null,
|
|
1259
|
-
task.agentSessionId ?? null,
|
|
1260
|
-
task.branch ?? null,
|
|
1261
|
-
task.useWorktree ? 1 : 0,
|
|
1262
|
-
task.createdAt,
|
|
1263
|
-
task.updatedAt,
|
|
1264
|
-
task.completedAt ?? null,
|
|
1265
|
-
task.archivedAt ?? null,
|
|
1266
|
-
task.sourceConnectorId ?? null,
|
|
1267
|
-
task.sourceExternalUrl ?? null,
|
|
1268
|
-
task.sourceExternalId ?? null
|
|
1269
|
-
);
|
|
1270
|
-
}
|
|
1271
|
-
function dbUpdateTask(id, updates) {
|
|
1272
|
-
const sets = [];
|
|
1273
|
-
const params = [];
|
|
1274
|
-
if (updates.title !== void 0) {
|
|
1275
|
-
sets.push("title = ?");
|
|
1276
|
-
params.push(updates.title);
|
|
1277
|
-
}
|
|
1278
|
-
if (updates.description !== void 0) {
|
|
1279
|
-
sets.push("description = ?");
|
|
1280
|
-
params.push(updates.description);
|
|
1281
|
-
}
|
|
1282
|
-
if (updates.status !== void 0) {
|
|
1283
|
-
sets.push("status = ?");
|
|
1284
|
-
params.push(updates.status);
|
|
1285
|
-
}
|
|
1286
|
-
if (updates.order !== void 0) {
|
|
1287
|
-
sets.push('"order" = ?');
|
|
1288
|
-
params.push(updates.order);
|
|
1289
|
-
}
|
|
1290
|
-
if (updates.branch !== void 0) {
|
|
1291
|
-
sets.push("branch = ?");
|
|
1292
|
-
params.push(updates.branch);
|
|
1293
|
-
}
|
|
1294
|
-
if (updates.useWorktree !== void 0) {
|
|
1295
|
-
sets.push("use_worktree = ?");
|
|
1296
|
-
params.push(updates.useWorktree ? 1 : 0);
|
|
1297
|
-
}
|
|
1298
|
-
if (updates.assignedAgent !== void 0) {
|
|
1299
|
-
sets.push("assigned_agent = ?");
|
|
1300
|
-
params.push(updates.assignedAgent);
|
|
1301
|
-
}
|
|
1302
|
-
if (updates.assignedSessionId !== void 0) {
|
|
1303
|
-
sets.push("assigned_session_id = ?");
|
|
1304
|
-
params.push(updates.assignedSessionId);
|
|
1305
|
-
}
|
|
1306
|
-
if (updates.agentSessionId !== void 0) {
|
|
1307
|
-
sets.push("agent_session_id = ?");
|
|
1308
|
-
params.push(updates.agentSessionId);
|
|
1309
|
-
}
|
|
1310
|
-
if (updates.updatedAt !== void 0) {
|
|
1311
|
-
sets.push("updated_at = ?");
|
|
1312
|
-
params.push(updates.updatedAt);
|
|
1313
|
-
}
|
|
1314
|
-
if ("completedAt" in updates) {
|
|
1315
|
-
sets.push("completed_at = ?");
|
|
1316
|
-
params.push(updates.completedAt ?? null);
|
|
1317
|
-
}
|
|
1318
|
-
if ("archivedAt" in updates) {
|
|
1319
|
-
sets.push("archived_at = ?");
|
|
1320
|
-
params.push(updates.archivedAt ?? null);
|
|
1321
|
-
}
|
|
1322
|
-
if (updates.sourceConnectorId !== void 0) {
|
|
1323
|
-
sets.push("source_connector_id = ?");
|
|
1324
|
-
params.push(updates.sourceConnectorId);
|
|
1325
|
-
}
|
|
1326
|
-
if (updates.sourceExternalUrl !== void 0) {
|
|
1327
|
-
sets.push("source_external_url = ?");
|
|
1328
|
-
params.push(updates.sourceExternalUrl);
|
|
1329
|
-
}
|
|
1330
|
-
if (updates.sourceExternalId !== void 0) {
|
|
1331
|
-
sets.push("source_external_id = ?");
|
|
1332
|
-
params.push(updates.sourceExternalId);
|
|
1333
|
-
}
|
|
1334
|
-
if (sets.length === 0) return;
|
|
1335
|
-
params.push(id);
|
|
1336
|
-
getDb().prepare(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
1337
|
-
}
|
|
1338
|
-
function dbDeleteTask(id) {
|
|
1339
|
-
getDb().prepare("DELETE FROM tasks WHERE id = ?").run(id);
|
|
1340
|
-
}
|
|
1341
|
-
function dbGetMaxTaskOrder(projectName) {
|
|
1342
|
-
const row = getDb().prepare('SELECT MAX("order") as m FROM tasks WHERE project_name = ?').get(projectName);
|
|
1343
|
-
return row.m ?? -1;
|
|
1344
|
-
}
|
|
1345
|
-
function dbListProjects() {
|
|
1346
|
-
const rows = getDb().prepare("SELECT * FROM projects").all();
|
|
1347
|
-
return rows.map(rowToProject);
|
|
1348
|
-
}
|
|
1349
|
-
function dbGetProject(name) {
|
|
1350
|
-
const row = getDb().prepare("SELECT * FROM projects WHERE name = ?").get(name);
|
|
1351
|
-
return row ? rowToProject(row) : null;
|
|
1352
|
-
}
|
|
1353
|
-
function dbInsertProject(project) {
|
|
1354
|
-
getDb().prepare(
|
|
1355
|
-
"INSERT INTO projects (name, path, preferred_agents, icon, icon_color, host_ids, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
1356
|
-
).run(
|
|
1357
|
-
project.name,
|
|
1358
|
-
project.path,
|
|
1359
|
-
JSON.stringify(project.preferredAgents),
|
|
1360
|
-
project.icon ?? null,
|
|
1361
|
-
project.iconColor ?? null,
|
|
1362
|
-
project.hostIds ? JSON.stringify(project.hostIds) : null,
|
|
1363
|
-
project.workspaceId ?? "personal"
|
|
1364
|
-
);
|
|
1365
|
-
}
|
|
1366
|
-
function dbUpdateProject(name, updates) {
|
|
1367
|
-
const sets = [];
|
|
1368
|
-
const params = [];
|
|
1369
|
-
if (updates.path !== void 0) {
|
|
1370
|
-
sets.push("path = ?");
|
|
1371
|
-
params.push(updates.path);
|
|
1372
|
-
}
|
|
1373
|
-
if (updates.preferredAgents !== void 0) {
|
|
1374
|
-
sets.push("preferred_agents = ?");
|
|
1375
|
-
params.push(JSON.stringify(updates.preferredAgents));
|
|
1376
|
-
}
|
|
1377
|
-
if (updates.icon !== void 0) {
|
|
1378
|
-
sets.push("icon = ?");
|
|
1379
|
-
params.push(updates.icon);
|
|
1380
|
-
}
|
|
1381
|
-
if (updates.iconColor !== void 0) {
|
|
1382
|
-
sets.push("icon_color = ?");
|
|
1383
|
-
params.push(updates.iconColor);
|
|
1384
|
-
}
|
|
1385
|
-
if (updates.hostIds !== void 0) {
|
|
1386
|
-
sets.push("host_ids = ?");
|
|
1387
|
-
params.push(JSON.stringify(updates.hostIds));
|
|
1388
|
-
}
|
|
1389
|
-
if (updates.workspaceId !== void 0) {
|
|
1390
|
-
sets.push("workspace_id = ?");
|
|
1391
|
-
params.push(updates.workspaceId);
|
|
1392
|
-
}
|
|
1393
|
-
if (sets.length === 0) return;
|
|
1394
|
-
params.push(name);
|
|
1395
|
-
getDb().prepare(`UPDATE projects SET ${sets.join(", ")} WHERE name = ?`).run(...params);
|
|
1396
|
-
}
|
|
1397
|
-
function dbDeleteProject(name) {
|
|
1398
|
-
const d = getDb();
|
|
1399
|
-
d.transaction(() => {
|
|
1400
|
-
d.prepare("DELETE FROM tasks WHERE project_name = ?").run(name);
|
|
1401
|
-
d.prepare("DELETE FROM projects WHERE name = ?").run(name);
|
|
1402
|
-
})();
|
|
1403
|
-
}
|
|
1404
|
-
function dbListWorkflows() {
|
|
1405
|
-
const rows = getDb().prepare("SELECT * FROM workflows").all();
|
|
1406
|
-
return rows.map(rowToWorkflow);
|
|
1407
|
-
}
|
|
1408
|
-
function dbInsertWorkflow(workflow) {
|
|
1409
|
-
getDb().prepare(
|
|
1410
|
-
`INSERT INTO workflows (id, name, icon, icon_color, nodes, edges, enabled, last_run_at, last_run_status, stagger_delay_ms, workspace_id)
|
|
1411
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1412
|
-
).run(
|
|
1413
|
-
workflow.id,
|
|
1414
|
-
workflow.name,
|
|
1415
|
-
workflow.icon,
|
|
1416
|
-
workflow.iconColor,
|
|
1417
|
-
JSON.stringify(workflow.nodes),
|
|
1418
|
-
JSON.stringify(workflow.edges),
|
|
1419
|
-
workflow.enabled ? 1 : 0,
|
|
1420
|
-
workflow.lastRunAt ?? null,
|
|
1421
|
-
workflow.lastRunStatus ?? null,
|
|
1422
|
-
workflow.staggerDelayMs ?? null,
|
|
1423
|
-
workflow.workspaceId ?? "personal"
|
|
1424
|
-
);
|
|
1425
|
-
}
|
|
1426
|
-
function dbUpdateWorkflow(id, updates) {
|
|
1427
|
-
const sets = [];
|
|
1428
|
-
const params = [];
|
|
1429
|
-
if (updates.name !== void 0) {
|
|
1430
|
-
sets.push("name = ?");
|
|
1431
|
-
params.push(updates.name);
|
|
1432
|
-
}
|
|
1433
|
-
if (updates.nodes !== void 0) {
|
|
1434
|
-
sets.push("nodes = ?");
|
|
1435
|
-
params.push(JSON.stringify(updates.nodes));
|
|
1436
|
-
}
|
|
1437
|
-
if (updates.edges !== void 0) {
|
|
1438
|
-
sets.push("edges = ?");
|
|
1439
|
-
params.push(JSON.stringify(updates.edges));
|
|
1440
|
-
}
|
|
1441
|
-
if (updates.icon !== void 0) {
|
|
1442
|
-
sets.push("icon = ?");
|
|
1443
|
-
params.push(updates.icon);
|
|
1444
|
-
}
|
|
1445
|
-
if (updates.iconColor !== void 0) {
|
|
1446
|
-
sets.push("icon_color = ?");
|
|
1447
|
-
params.push(updates.iconColor);
|
|
1448
|
-
}
|
|
1449
|
-
if (updates.enabled !== void 0) {
|
|
1450
|
-
sets.push("enabled = ?");
|
|
1451
|
-
params.push(updates.enabled ? 1 : 0);
|
|
1452
|
-
}
|
|
1453
|
-
if (updates.staggerDelayMs !== void 0) {
|
|
1454
|
-
sets.push("stagger_delay_ms = ?");
|
|
1455
|
-
params.push(updates.staggerDelayMs);
|
|
1456
|
-
}
|
|
1457
|
-
if (updates.workspaceId !== void 0) {
|
|
1458
|
-
sets.push("workspace_id = ?");
|
|
1459
|
-
params.push(updates.workspaceId);
|
|
1460
|
-
}
|
|
1461
|
-
if (sets.length === 0) return;
|
|
1462
|
-
params.push(id);
|
|
1463
|
-
getDb().prepare(`UPDATE workflows SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
1464
|
-
}
|
|
1465
|
-
function dbDeleteWorkflow(id) {
|
|
1466
|
-
getDb().prepare("DELETE FROM workflows WHERE id = ?").run(id);
|
|
1467
|
-
}
|
|
1468
|
-
function dbListWorkspaces() {
|
|
1469
|
-
const rows = getDb().prepare('SELECT * FROM workspaces ORDER BY "order"').all();
|
|
1470
|
-
return rows.map(rowToWorkspace);
|
|
1471
|
-
}
|
|
1472
|
-
function dbInsertWorkspace(workspace) {
|
|
1473
|
-
getDb().prepare(`INSERT INTO workspaces (id, name, icon, icon_color, "order") VALUES (?, ?, ?, ?, ?)`).run(
|
|
1474
|
-
workspace.id,
|
|
1475
|
-
workspace.name,
|
|
1476
|
-
workspace.icon ?? null,
|
|
1477
|
-
workspace.iconColor ?? null,
|
|
1478
|
-
workspace.order
|
|
1479
|
-
);
|
|
1480
|
-
}
|
|
1481
|
-
function dbUpdateWorkspace(id, updates) {
|
|
1482
|
-
const sets = [];
|
|
1483
|
-
const params = [];
|
|
1484
|
-
if (updates.name !== void 0) {
|
|
1485
|
-
sets.push("name = ?");
|
|
1486
|
-
params.push(updates.name);
|
|
1487
|
-
}
|
|
1488
|
-
if (updates.icon !== void 0) {
|
|
1489
|
-
sets.push("icon = ?");
|
|
1490
|
-
params.push(updates.icon);
|
|
1491
|
-
}
|
|
1492
|
-
if (updates.iconColor !== void 0) {
|
|
1493
|
-
sets.push("icon_color = ?");
|
|
1494
|
-
params.push(updates.iconColor);
|
|
1495
|
-
}
|
|
1496
|
-
if (updates.order !== void 0) {
|
|
1497
|
-
sets.push('"order" = ?');
|
|
1498
|
-
params.push(updates.order);
|
|
1499
|
-
}
|
|
1500
|
-
if (sets.length === 0) return;
|
|
1501
|
-
params.push(id);
|
|
1502
|
-
getDb().prepare(`UPDATE workspaces SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
1503
|
-
}
|
|
1504
|
-
function dbDeleteWorkspace(id) {
|
|
1505
|
-
const d = getDb();
|
|
1506
|
-
d.transaction(() => {
|
|
1507
|
-
d.prepare("UPDATE projects SET workspace_id = 'personal' WHERE workspace_id = ?").run(id);
|
|
1508
|
-
d.prepare("UPDATE workflows SET workspace_id = 'personal' WHERE workspace_id = ?").run(id);
|
|
1509
|
-
d.prepare("DELETE FROM workspaces WHERE id = ?").run(id);
|
|
1510
|
-
})();
|
|
1511
|
-
}
|
|
1512
1439
|
function rowToTask(r) {
|
|
1513
1440
|
return {
|
|
1514
1441
|
id: r.id,
|
|
@@ -1568,141 +1495,15 @@ function rowToWorkspace(r) {
|
|
|
1568
1495
|
order: r.order
|
|
1569
1496
|
};
|
|
1570
1497
|
}
|
|
1571
|
-
function mapNodeRow(n) {
|
|
1572
|
-
const structured = n.structured_output != null ? parseStructuredOutput(n.structured_output) : void 0;
|
|
1573
|
-
return {
|
|
1574
|
-
nodeId: n.node_id,
|
|
1575
|
-
status: n.status,
|
|
1576
|
-
...n.started_at != null && { startedAt: n.started_at },
|
|
1577
|
-
...n.completed_at != null && { completedAt: n.completed_at },
|
|
1578
|
-
...n.session_id != null && { sessionId: n.session_id },
|
|
1579
|
-
...n.error != null && { error: n.error },
|
|
1580
|
-
...n.logs != null && { logs: n.logs },
|
|
1581
|
-
...n.task_id != null && { taskId: n.task_id },
|
|
1582
|
-
...n.agent_session_id != null && { agentSessionId: n.agent_session_id },
|
|
1583
|
-
...n.agent_type != null && { agentType: n.agent_type },
|
|
1584
|
-
...n.project_name != null && { projectName: n.project_name },
|
|
1585
|
-
...n.project_path != null && { projectPath: n.project_path },
|
|
1586
|
-
...n.approved_at != null && { approvedAt: n.approved_at },
|
|
1587
|
-
...n.diagnostics != null && { diagnostics: n.diagnostics },
|
|
1588
|
-
...n.output != null && { output: n.output },
|
|
1589
|
-
...structured !== void 0 && { structuredOutput: structured },
|
|
1590
|
-
...n.iteration != null && { iteration: n.iteration }
|
|
1591
|
-
};
|
|
1592
|
-
}
|
|
1593
|
-
function parseStructuredOutput(raw) {
|
|
1594
|
-
try {
|
|
1595
|
-
const parsed = JSON.parse(raw);
|
|
1596
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
1597
|
-
} catch {
|
|
1598
|
-
return void 0;
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
function fetchNodesByRunIds(d, runIds) {
|
|
1602
|
-
if (runIds.length === 0) return /* @__PURE__ */ new Map();
|
|
1603
|
-
const placeholders = runIds.map(() => "?").join(",");
|
|
1604
|
-
const rows = d.prepare(`SELECT * FROM workflow_run_nodes WHERE run_id IN (${placeholders})`).all(...runIds);
|
|
1605
|
-
const out = /* @__PURE__ */ new Map();
|
|
1606
|
-
for (const r of rows) {
|
|
1607
|
-
const bucket = out.get(r.run_id);
|
|
1608
|
-
const node = mapNodeRow(r);
|
|
1609
|
-
if (bucket) bucket.push(node);
|
|
1610
|
-
else out.set(r.run_id, [node]);
|
|
1611
|
-
}
|
|
1612
|
-
return out;
|
|
1613
|
-
}
|
|
1614
|
-
function parseRunInputs(raw) {
|
|
1615
|
-
if (!raw) return void 0;
|
|
1616
|
-
try {
|
|
1617
|
-
const parsed = JSON.parse(raw);
|
|
1618
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
1619
|
-
} catch {
|
|
1620
|
-
return void 0;
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
function mapRunRows(rows, nodesByRun) {
|
|
1624
|
-
return rows.map((r) => {
|
|
1625
|
-
const inputs = parseRunInputs(r.inputs);
|
|
1626
|
-
const connectorItem = parseRunInputs(r.connector_item);
|
|
1627
|
-
return {
|
|
1628
|
-
runId: r.id,
|
|
1629
|
-
workflowId: r.workflow_id,
|
|
1630
|
-
startedAt: r.started_at,
|
|
1631
|
-
...r.completed_at != null && { completedAt: r.completed_at },
|
|
1632
|
-
status: r.status,
|
|
1633
|
-
...r.trigger_task_id != null && { triggerTaskId: r.trigger_task_id },
|
|
1634
|
-
...inputs && { inputs },
|
|
1635
|
-
...connectorItem && { connectorItem },
|
|
1636
|
-
...r.connector_inbox_id != null && { connectorInboxId: r.connector_inbox_id },
|
|
1637
|
-
...r.connector_inbox_lease_token != null && {
|
|
1638
|
-
connectorInboxLeaseToken: r.connector_inbox_lease_token
|
|
1639
|
-
},
|
|
1640
|
-
...r.connector_inbox_disposition === "processed" || r.connector_inbox_disposition === "retry" ? { connectorInboxDisposition: r.connector_inbox_disposition } : {},
|
|
1641
|
-
...r.workflow_name != null && { workflowName: r.workflow_name },
|
|
1642
|
-
nodeStates: nodesByRun.get(r.id) ?? []
|
|
1643
|
-
};
|
|
1644
|
-
});
|
|
1645
|
-
}
|
|
1646
|
-
function listWorkflowRuns(workflowId, limit = 20) {
|
|
1647
|
-
const d = getDb();
|
|
1648
|
-
const rows = d.prepare("SELECT * FROM workflow_runs WHERE workflow_id = ? ORDER BY started_at DESC LIMIT ?").all(workflowId, limit);
|
|
1649
|
-
return mapRunRows(
|
|
1650
|
-
rows,
|
|
1651
|
-
fetchNodesByRunIds(
|
|
1652
|
-
d,
|
|
1653
|
-
rows.map((r) => r.id)
|
|
1654
|
-
)
|
|
1655
|
-
);
|
|
1656
|
-
}
|
|
1657
|
-
function listWorkflowRunsByTask(taskId, limit = 20) {
|
|
1658
|
-
const d = getDb();
|
|
1659
|
-
const rows = d.prepare(
|
|
1660
|
-
`SELECT DISTINCT wr.*, w.name as workflow_name
|
|
1661
|
-
FROM workflow_runs wr
|
|
1662
|
-
LEFT JOIN workflows w ON w.id = wr.workflow_id
|
|
1663
|
-
WHERE wr.trigger_task_id = ?
|
|
1664
|
-
OR wr.id IN (SELECT run_id FROM workflow_run_nodes WHERE task_id = ?)
|
|
1665
|
-
ORDER BY wr.started_at DESC
|
|
1666
|
-
LIMIT ?`
|
|
1667
|
-
).all(taskId, taskId, limit);
|
|
1668
|
-
return mapRunRows(
|
|
1669
|
-
rows,
|
|
1670
|
-
fetchNodesByRunIds(
|
|
1671
|
-
d,
|
|
1672
|
-
rows.map((r) => r.id)
|
|
1673
|
-
)
|
|
1674
|
-
);
|
|
1675
|
-
}
|
|
1676
|
-
function listAllWorkflowRuns(workspaceId, limit = 50) {
|
|
1677
|
-
const d = getDb();
|
|
1678
|
-
const cappedLimit = Math.max(1, Math.min(limit, 500));
|
|
1679
|
-
const where = workspaceId ? `WHERE w.id IS NOT NULL AND COALESCE(w.workspace_id, 'personal') = ?` : "";
|
|
1680
|
-
const sql = `SELECT wr.*, w.name as workflow_name
|
|
1681
|
-
FROM workflow_runs wr
|
|
1682
|
-
LEFT JOIN workflows w ON w.id = wr.workflow_id
|
|
1683
|
-
${where}
|
|
1684
|
-
ORDER BY wr.started_at DESC
|
|
1685
|
-
LIMIT ?`;
|
|
1686
|
-
const params = workspaceId ? [workspaceId, cappedLimit] : [cappedLimit];
|
|
1687
|
-
const rows = d.prepare(sql).all(...params);
|
|
1688
|
-
return mapRunRows(
|
|
1689
|
-
rows,
|
|
1690
|
-
fetchNodesByRunIds(
|
|
1691
|
-
d,
|
|
1692
|
-
rows.map((r) => r.id)
|
|
1693
|
-
)
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1696
1498
|
|
|
1697
1499
|
// ../server/src/config-manager.ts
|
|
1698
|
-
var DB_DIR = path3.join(os2.homedir(), ".vorn");
|
|
1699
1500
|
var ConfigManager = class {
|
|
1700
1501
|
changeCallbacks = [];
|
|
1701
1502
|
dbWatcher = null;
|
|
1702
1503
|
debounceTimer = null;
|
|
1703
1504
|
cachedConfig = null;
|
|
1704
|
-
init() {
|
|
1705
|
-
initDatabase();
|
|
1505
|
+
init(dataDir) {
|
|
1506
|
+
initDatabase(dataDir);
|
|
1706
1507
|
}
|
|
1707
1508
|
close() {
|
|
1708
1509
|
this.stopWatchingDb();
|
|
@@ -1715,7 +1516,7 @@ var ConfigManager = class {
|
|
|
1715
1516
|
this.cachedConfig = config;
|
|
1716
1517
|
return config;
|
|
1717
1518
|
} catch (err) {
|
|
1718
|
-
logger_default.error("[config-manager] loadConfig failed, returning defaults:"
|
|
1519
|
+
logger_default.error({ err }, "[config-manager] loadConfig failed, returning defaults:");
|
|
1719
1520
|
return {
|
|
1720
1521
|
version: 1,
|
|
1721
1522
|
defaults: {
|
|
@@ -1735,7 +1536,7 @@ var ConfigManager = class {
|
|
|
1735
1536
|
saveConfig(config);
|
|
1736
1537
|
this.cachedConfig = null;
|
|
1737
1538
|
} catch (err) {
|
|
1738
|
-
logger_default.error("[config-manager] saveConfig failed:"
|
|
1539
|
+
logger_default.error({ err }, "[config-manager] saveConfig failed:");
|
|
1739
1540
|
throw err;
|
|
1740
1541
|
}
|
|
1741
1542
|
}
|
|
@@ -1759,7 +1560,7 @@ var ConfigManager = class {
|
|
|
1759
1560
|
if (this.dbWatcher) return;
|
|
1760
1561
|
const WATCH_SUFFIXES = [".db-signal", ".db-wal", ".db"];
|
|
1761
1562
|
try {
|
|
1762
|
-
this.dbWatcher = fs3.watch(
|
|
1563
|
+
this.dbWatcher = fs3.watch(getDataDir(), (eventType, filename) => {
|
|
1763
1564
|
if (!filename || !WATCH_SUFFIXES.some((s) => filename.endsWith(s))) return;
|
|
1764
1565
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
1765
1566
|
this.debounceTimer = setTimeout(() => {
|
|
@@ -1795,30 +1596,336 @@ import crypto from "crypto";
|
|
|
1795
1596
|
import path4 from "path";
|
|
1796
1597
|
import { z as z2 } from "zod";
|
|
1797
1598
|
|
|
1798
|
-
// src/validation.ts
|
|
1799
|
-
import { z } from "zod";
|
|
1800
|
-
var safeName = z.string().min(1, "Name must not be empty").max(200, "Name must be 200 characters or less").refine((s) => !s.includes("..") && !s.includes("/") && !s.includes("\\"), {
|
|
1801
|
-
message: "Name must not contain path traversal characters (.. / \\)"
|
|
1802
|
-
});
|
|
1803
|
-
var safeId = z.string().min(1, "ID must not be empty").max(100, "ID must be 100 characters or less");
|
|
1804
|
-
var safeTitle = z.string().min(1, "Title must not be empty").max(500, "Title must be 500 characters or less");
|
|
1805
|
-
var safeDescription = z.string().max(5e3, "Description must be 5000 characters or less");
|
|
1806
|
-
var safeShortText = z.string().max(200, "Value must be 200 characters or less");
|
|
1807
|
-
var safeUrl = z.string().min(1, "URL must not be empty").max(2048, "URL is too long");
|
|
1808
|
-
var safePrompt = z.string().max(1e4, "Prompt must be 10000 characters or less");
|
|
1809
|
-
var safeAbsolutePath = z.string().min(1, "Path must not be empty").max(1e3, "Path must be 1000 characters or less").refine((s) => s.startsWith("/"), { message: "Path must be absolute (start with /)" });
|
|
1810
|
-
var safeHexColor = z.string().regex(/^#[0-9a-fA-F]{3,8}$/, "Must be a valid hex color (e.g. #6366f1)");
|
|
1811
|
-
var V = {
|
|
1812
|
-
name: safeName,
|
|
1813
|
-
id: safeId,
|
|
1814
|
-
title: safeTitle,
|
|
1815
|
-
description: safeDescription,
|
|
1816
|
-
shortText: safeShortText,
|
|
1817
|
-
prompt: safePrompt,
|
|
1818
|
-
absolutePath: safeAbsolutePath,
|
|
1819
|
-
hexColor: safeHexColor,
|
|
1820
|
-
url: safeUrl
|
|
1821
|
-
};
|
|
1599
|
+
// src/validation.ts
|
|
1600
|
+
import { z } from "zod";
|
|
1601
|
+
var safeName = z.string().min(1, "Name must not be empty").max(200, "Name must be 200 characters or less").refine((s) => !s.includes("..") && !s.includes("/") && !s.includes("\\"), {
|
|
1602
|
+
message: "Name must not contain path traversal characters (.. / \\)"
|
|
1603
|
+
});
|
|
1604
|
+
var safeId = z.string().min(1, "ID must not be empty").max(100, "ID must be 100 characters or less");
|
|
1605
|
+
var safeTitle = z.string().min(1, "Title must not be empty").max(500, "Title must be 500 characters or less");
|
|
1606
|
+
var safeDescription = z.string().max(5e3, "Description must be 5000 characters or less");
|
|
1607
|
+
var safeShortText = z.string().max(200, "Value must be 200 characters or less");
|
|
1608
|
+
var safeUrl = z.string().min(1, "URL must not be empty").max(2048, "URL is too long");
|
|
1609
|
+
var safePrompt = z.string().max(1e4, "Prompt must be 10000 characters or less");
|
|
1610
|
+
var safeAbsolutePath = z.string().min(1, "Path must not be empty").max(1e3, "Path must be 1000 characters or less").refine((s) => s.startsWith("/"), { message: "Path must be absolute (start with /)" });
|
|
1611
|
+
var safeHexColor = z.string().regex(/^#[0-9a-fA-F]{3,8}$/, "Must be a valid hex color (e.g. #6366f1)");
|
|
1612
|
+
var V = {
|
|
1613
|
+
name: safeName,
|
|
1614
|
+
id: safeId,
|
|
1615
|
+
title: safeTitle,
|
|
1616
|
+
description: safeDescription,
|
|
1617
|
+
shortText: safeShortText,
|
|
1618
|
+
prompt: safePrompt,
|
|
1619
|
+
absolutePath: safeAbsolutePath,
|
|
1620
|
+
hexColor: safeHexColor,
|
|
1621
|
+
url: safeUrl
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
// src/ws-client.ts
|
|
1625
|
+
import fs4 from "fs";
|
|
1626
|
+
import path3 from "path";
|
|
1627
|
+
import os2 from "os";
|
|
1628
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1629
|
+
import { WebSocket } from "ws";
|
|
1630
|
+
var DATA_DIR = process.env.VORN_DATA_DIR || path3.join(os2.homedir(), ".vorn");
|
|
1631
|
+
var PORT_FILE = path3.join(DATA_DIR, "ws-port");
|
|
1632
|
+
var LOCAL_TOKEN_FILE = path3.join(DATA_DIR, LOCAL_TOKEN_FILENAME);
|
|
1633
|
+
var TOKEN_FILE_MISSING_MSG = `Vorn local credential not found (${LOCAL_TOKEN_FILE}).
|
|
1634
|
+
The server writes it on startup and removes it on shutdown, so this usually means
|
|
1635
|
+
Vorn is not running. Start Vorn (or \`vorn-server serve\`) and try again.
|
|
1636
|
+
If the server runs with --data-dir, set VORN_DATA_DIR to the same directory.`;
|
|
1637
|
+
function readLocalToken() {
|
|
1638
|
+
try {
|
|
1639
|
+
const token = fs4.readFileSync(LOCAL_TOKEN_FILE, "utf-8").trim();
|
|
1640
|
+
if (!token) throw new Error("empty");
|
|
1641
|
+
return token;
|
|
1642
|
+
} catch {
|
|
1643
|
+
throw new Error(TOKEN_FILE_MISSING_MSG);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
function connection() {
|
|
1647
|
+
const result = readPort();
|
|
1648
|
+
if (!result.port) {
|
|
1649
|
+
const reason = "reason" in result ? result.reason : "missing";
|
|
1650
|
+
throw new Error(reason === "invalid" ? PORT_FILE_INVALID_MSG : PORT_FILE_MISSING_MSG);
|
|
1651
|
+
}
|
|
1652
|
+
return {
|
|
1653
|
+
url: `ws://127.0.0.1:${result.port}/ws`,
|
|
1654
|
+
options: { headers: { Authorization: `Bearer ${readLocalToken()}` } }
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
var TIMEOUT_MS = 1e4;
|
|
1658
|
+
var IS_WIN = process.platform === "win32";
|
|
1659
|
+
var PORT_FILE_MISSING_MSG = IS_WIN ? `Vorn port file not found (~/.vorn/ws-port).
|
|
1660
|
+
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
1661
|
+
To fix, find the Vorn process and its listening port:
|
|
1662
|
+
powershell -c "Get-NetTCPConnection -State Listen -OwningProcess (Get-Process Vorn).Id | Select LocalPort"
|
|
1663
|
+
Then write the WS port to the file:
|
|
1664
|
+
echo {"port":<PORT>,"pid":<PID>} > %USERPROFILE%\\.vorn\\ws-port
|
|
1665
|
+
Or restart Vorn to regenerate it.` : `Vorn port file not found (~/.vorn/ws-port).
|
|
1666
|
+
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
1667
|
+
To fix, run: lsof -iTCP -sTCP:LISTEN -P | grep Vorn
|
|
1668
|
+
Then write the WS port (the one on *:<port>) to the file:
|
|
1669
|
+
echo '{"port":<PORT>,"pid":<PID>}' > ~/.vorn/ws-port
|
|
1670
|
+
Or restart Vorn to regenerate it.`;
|
|
1671
|
+
var PORT_FILE_INVALID_MSG = `Vorn port file exists but contains invalid data (~/.vorn/ws-port).
|
|
1672
|
+
Delete it and restart Vorn, or overwrite it with the correct port:
|
|
1673
|
+
${IS_WIN ? "del %USERPROFILE%\\.vorn\\ws-port" : "rm ~/.vorn/ws-port"}`;
|
|
1674
|
+
var rpcId = 0;
|
|
1675
|
+
var cachedPort = null;
|
|
1676
|
+
var cacheTimestamp = 0;
|
|
1677
|
+
var CACHE_TTL_MS = 5e3;
|
|
1678
|
+
var EXEC_OPTS = {
|
|
1679
|
+
encoding: "utf-8",
|
|
1680
|
+
timeout: 5e3,
|
|
1681
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1682
|
+
};
|
|
1683
|
+
function discoverPort() {
|
|
1684
|
+
try {
|
|
1685
|
+
if (IS_WIN) {
|
|
1686
|
+
const taskOut = execFileSync2(
|
|
1687
|
+
"tasklist",
|
|
1688
|
+
["/FI", "IMAGENAME eq Vorn.exe", "/FO", "CSV", "/NH"],
|
|
1689
|
+
EXEC_OPTS
|
|
1690
|
+
);
|
|
1691
|
+
const pidMatch = taskOut.match(/"Vorn\.exe","(\d+)"/);
|
|
1692
|
+
if (!pidMatch) return null;
|
|
1693
|
+
const pid = pidMatch[1];
|
|
1694
|
+
const lines = execFileSync2("netstat", ["-ano"], EXEC_OPTS).split("\n");
|
|
1695
|
+
let fallback = null;
|
|
1696
|
+
for (const line of lines) {
|
|
1697
|
+
if (!line.includes("LISTENING") || !line.trim().endsWith(pid)) continue;
|
|
1698
|
+
const m = line.match(/(?:0\.0\.0\.0|127\.0\.0\.1):(\d+)/);
|
|
1699
|
+
if (!m) continue;
|
|
1700
|
+
if (line.includes("0.0.0.0")) return parseInt(m[1], 10);
|
|
1701
|
+
fallback ??= parseInt(m[1], 10);
|
|
1702
|
+
}
|
|
1703
|
+
return fallback;
|
|
1704
|
+
} else {
|
|
1705
|
+
const lines = execFileSync2("lsof", ["-iTCP", "-sTCP:LISTEN", "-P", "-n"], EXEC_OPTS).split(
|
|
1706
|
+
"\n"
|
|
1707
|
+
);
|
|
1708
|
+
let fallback = null;
|
|
1709
|
+
for (const line of lines) {
|
|
1710
|
+
if (!line.includes("Vorn")) continue;
|
|
1711
|
+
if (line.includes("*:")) {
|
|
1712
|
+
const m = line.match(/\*:(\d+)/);
|
|
1713
|
+
if (m) return parseInt(m[1], 10);
|
|
1714
|
+
}
|
|
1715
|
+
if (!fallback) {
|
|
1716
|
+
const m = line.match(/:(\d+)\s/);
|
|
1717
|
+
if (m) fallback = parseInt(m[1], 10);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
return fallback;
|
|
1721
|
+
}
|
|
1722
|
+
} catch {
|
|
1723
|
+
}
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
function discoverAndHeal() {
|
|
1727
|
+
const now = Date.now();
|
|
1728
|
+
if (cachedPort && now - cacheTimestamp < CACHE_TTL_MS) return { port: cachedPort };
|
|
1729
|
+
const discovered = discoverPort();
|
|
1730
|
+
cachedPort = discovered;
|
|
1731
|
+
cacheTimestamp = now;
|
|
1732
|
+
if (discovered) {
|
|
1733
|
+
try {
|
|
1734
|
+
fs4.mkdirSync(path3.dirname(PORT_FILE), { recursive: true });
|
|
1735
|
+
fs4.writeFileSync(PORT_FILE, JSON.stringify({ port: discovered }), "utf-8");
|
|
1736
|
+
} catch {
|
|
1737
|
+
}
|
|
1738
|
+
return { port: discovered };
|
|
1739
|
+
}
|
|
1740
|
+
return { port: null, reason: "missing" };
|
|
1741
|
+
}
|
|
1742
|
+
function readPort() {
|
|
1743
|
+
try {
|
|
1744
|
+
const raw = fs4.readFileSync(PORT_FILE, "utf-8").trim();
|
|
1745
|
+
if (!raw) return { port: null, reason: "invalid" };
|
|
1746
|
+
if (raw.startsWith("{")) {
|
|
1747
|
+
const parsed = JSON.parse(raw);
|
|
1748
|
+
const p2 = parsed?.port;
|
|
1749
|
+
const pid = parsed?.pid;
|
|
1750
|
+
if (typeof p2 !== "number" || !Number.isFinite(p2) || p2 <= 0) {
|
|
1751
|
+
return { port: null, reason: "invalid" };
|
|
1752
|
+
}
|
|
1753
|
+
if (typeof pid === "number" && Number.isInteger(pid) && pid > 0) {
|
|
1754
|
+
try {
|
|
1755
|
+
process.kill(pid, 0);
|
|
1756
|
+
} catch (err) {
|
|
1757
|
+
if (err.code === "EPERM") return { port: p2 };
|
|
1758
|
+
return discoverAndHeal();
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
return { port: p2 };
|
|
1762
|
+
}
|
|
1763
|
+
const p = parseInt(raw, 10);
|
|
1764
|
+
return Number.isFinite(p) && p > 0 ? { port: p } : { port: null, reason: "invalid" };
|
|
1765
|
+
} catch {
|
|
1766
|
+
return discoverAndHeal();
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
async function rpcCall(method, params, timeoutMs = TIMEOUT_MS) {
|
|
1770
|
+
const { url, options } = connection();
|
|
1771
|
+
return new Promise((resolve, reject) => {
|
|
1772
|
+
const ws = new WebSocket(url, options);
|
|
1773
|
+
const id = ++rpcId;
|
|
1774
|
+
const timer = setTimeout(() => {
|
|
1775
|
+
ws.close();
|
|
1776
|
+
reject(new Error(`RPC call "${method}" timed out after ${timeoutMs}ms`));
|
|
1777
|
+
}, timeoutMs);
|
|
1778
|
+
ws.on("open", () => {
|
|
1779
|
+
ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
1780
|
+
});
|
|
1781
|
+
ws.on("message", (raw) => {
|
|
1782
|
+
try {
|
|
1783
|
+
const msg = JSON.parse(raw.toString());
|
|
1784
|
+
if (msg.id !== id) return;
|
|
1785
|
+
clearTimeout(timer);
|
|
1786
|
+
ws.close();
|
|
1787
|
+
if (msg.error) {
|
|
1788
|
+
reject(new Error(msg.error.message));
|
|
1789
|
+
} else {
|
|
1790
|
+
resolve(msg.result);
|
|
1791
|
+
}
|
|
1792
|
+
} catch {
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
ws.on("error", (err) => {
|
|
1796
|
+
clearTimeout(timer);
|
|
1797
|
+
reject(new Error(`Cannot connect to Vorn server: ${err.message}. Is the app running?`));
|
|
1798
|
+
});
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
async function rpcNotify(method, params) {
|
|
1802
|
+
const { url, options } = connection();
|
|
1803
|
+
return new Promise((resolve, reject) => {
|
|
1804
|
+
const ws = new WebSocket(url, options);
|
|
1805
|
+
ws.on("open", () => {
|
|
1806
|
+
ws.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
|
|
1807
|
+
ws.close();
|
|
1808
|
+
resolve();
|
|
1809
|
+
});
|
|
1810
|
+
ws.on("error", (err) => {
|
|
1811
|
+
reject(new Error(`Cannot connect to Vorn server: ${err.message}. Is the app running?`));
|
|
1812
|
+
});
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// src/data-access.ts
|
|
1817
|
+
async function loadConfig2() {
|
|
1818
|
+
return rpcCall("config:load");
|
|
1819
|
+
}
|
|
1820
|
+
async function saveConfig2(config) {
|
|
1821
|
+
await rpcCall("config:save", config);
|
|
1822
|
+
}
|
|
1823
|
+
async function mutate(change) {
|
|
1824
|
+
const config = await loadConfig2();
|
|
1825
|
+
await saveConfig2(change(config));
|
|
1826
|
+
}
|
|
1827
|
+
async function dbListProjects() {
|
|
1828
|
+
return (await loadConfig2()).projects ?? [];
|
|
1829
|
+
}
|
|
1830
|
+
async function dbGetProject(name) {
|
|
1831
|
+
return (await dbListProjects()).find((p) => p.name === name) ?? null;
|
|
1832
|
+
}
|
|
1833
|
+
async function dbInsertProject(project) {
|
|
1834
|
+
await mutate((config) => ({ ...config, projects: [...config.projects ?? [], project] }));
|
|
1835
|
+
}
|
|
1836
|
+
async function dbUpdateProject(name, updates) {
|
|
1837
|
+
await mutate((config) => ({
|
|
1838
|
+
...config,
|
|
1839
|
+
projects: (config.projects ?? []).map((p) => p.name === name ? { ...p, ...updates } : p)
|
|
1840
|
+
}));
|
|
1841
|
+
}
|
|
1842
|
+
async function dbDeleteProject(name) {
|
|
1843
|
+
await mutate((config) => ({
|
|
1844
|
+
...config,
|
|
1845
|
+
projects: (config.projects ?? []).filter((p) => p.name !== name)
|
|
1846
|
+
}));
|
|
1847
|
+
}
|
|
1848
|
+
async function dbListWorkspaces() {
|
|
1849
|
+
return (await loadConfig2()).workspaces ?? [];
|
|
1850
|
+
}
|
|
1851
|
+
async function dbInsertWorkspace(workspace) {
|
|
1852
|
+
await mutate((config) => ({ ...config, workspaces: [...config.workspaces ?? [], workspace] }));
|
|
1853
|
+
}
|
|
1854
|
+
async function dbUpdateWorkspace(id, updates) {
|
|
1855
|
+
await mutate((config) => ({
|
|
1856
|
+
...config,
|
|
1857
|
+
workspaces: (config.workspaces ?? []).map((w) => w.id === id ? { ...w, ...updates } : w)
|
|
1858
|
+
}));
|
|
1859
|
+
}
|
|
1860
|
+
async function dbDeleteWorkspace(id) {
|
|
1861
|
+
await mutate((config) => ({
|
|
1862
|
+
...config,
|
|
1863
|
+
workspaces: (config.workspaces ?? []).filter((w) => w.id !== id)
|
|
1864
|
+
}));
|
|
1865
|
+
}
|
|
1866
|
+
async function dbListWorkflows() {
|
|
1867
|
+
return (await loadConfig2()).workflows ?? [];
|
|
1868
|
+
}
|
|
1869
|
+
async function dbInsertWorkflow(workflow) {
|
|
1870
|
+
await mutate((config) => ({ ...config, workflows: [...config.workflows ?? [], workflow] }));
|
|
1871
|
+
}
|
|
1872
|
+
async function dbUpdateWorkflow(id, updates) {
|
|
1873
|
+
await mutate((config) => ({
|
|
1874
|
+
...config,
|
|
1875
|
+
workflows: (config.workflows ?? []).map((w) => w.id === id ? { ...w, ...updates } : w)
|
|
1876
|
+
}));
|
|
1877
|
+
}
|
|
1878
|
+
async function dbDeleteWorkflow(id) {
|
|
1879
|
+
await mutate((config) => ({
|
|
1880
|
+
...config,
|
|
1881
|
+
workflows: (config.workflows ?? []).filter((w) => w.id !== id)
|
|
1882
|
+
}));
|
|
1883
|
+
}
|
|
1884
|
+
async function dbListTasks(projectName, status) {
|
|
1885
|
+
const tasks = (await loadConfig2()).tasks ?? [];
|
|
1886
|
+
return tasks.filter(
|
|
1887
|
+
(t) => (projectName === void 0 || t.projectName === projectName) && (status === void 0 || t.status === status)
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
async function dbGetTask(id) {
|
|
1891
|
+
return ((await loadConfig2()).tasks ?? []).find((t) => t.id === id) ?? null;
|
|
1892
|
+
}
|
|
1893
|
+
async function dbInsertTask(task) {
|
|
1894
|
+
await mutate((config) => ({ ...config, tasks: [...config.tasks ?? [], task] }));
|
|
1895
|
+
}
|
|
1896
|
+
async function dbUpdateTask(id, updates) {
|
|
1897
|
+
await mutate((config) => ({
|
|
1898
|
+
...config,
|
|
1899
|
+
tasks: (config.tasks ?? []).map((t) => t.id === id ? { ...t, ...updates } : t)
|
|
1900
|
+
}));
|
|
1901
|
+
}
|
|
1902
|
+
async function dbDeleteTask(id) {
|
|
1903
|
+
await mutate((config) => ({
|
|
1904
|
+
...config,
|
|
1905
|
+
tasks: (config.tasks ?? []).filter((t) => t.id !== id)
|
|
1906
|
+
}));
|
|
1907
|
+
}
|
|
1908
|
+
async function dbGetMaxTaskOrder(projectName) {
|
|
1909
|
+
const tasks = await dbListTasks(projectName);
|
|
1910
|
+
return tasks.reduce((max, t) => Math.max(max, t.order ?? 0), 0);
|
|
1911
|
+
}
|
|
1912
|
+
function dbSignalChange() {
|
|
1913
|
+
}
|
|
1914
|
+
async function listWorkflowRuns(workflowId, limit = 20) {
|
|
1915
|
+
return rpcCall("workflowRun:list", { workflowId, limit });
|
|
1916
|
+
}
|
|
1917
|
+
async function listWorkflowRunsByTask(taskId, limit = 20) {
|
|
1918
|
+
return rpcCall("workflowRun:listByTask", {
|
|
1919
|
+
taskId,
|
|
1920
|
+
limit
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
async function listAllWorkflowRuns(workspaceId, limit = 50) {
|
|
1924
|
+
return rpcCall("workflowRun:listAll", {
|
|
1925
|
+
workspaceId,
|
|
1926
|
+
limit
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1822
1929
|
|
|
1823
1930
|
// src/tools/tasks.ts
|
|
1824
1931
|
var TASK_STATUSES = ["todo", "in_progress", "in_review", "done", "cancelled"];
|
|
@@ -1841,12 +1948,12 @@ function registerTaskTools(server) {
|
|
|
1841
1948
|
include_archived: z2.boolean().optional().describe("Include archived tasks in the result (default: false)")
|
|
1842
1949
|
},
|
|
1843
1950
|
async (args) => {
|
|
1844
|
-
let tasks = dbListTasks(args.project_name, args.status);
|
|
1951
|
+
let tasks = await dbListTasks(args.project_name, args.status);
|
|
1845
1952
|
if (!args.include_archived) {
|
|
1846
1953
|
tasks = tasks.filter((t) => !t.archivedAt);
|
|
1847
1954
|
}
|
|
1848
1955
|
if (args.workspace_id) {
|
|
1849
|
-
const projects = dbListProjects();
|
|
1956
|
+
const projects = await dbListProjects();
|
|
1850
1957
|
const wsProjectNames = new Set(
|
|
1851
1958
|
projects.filter((p) => (p.workspaceId ?? "personal") === args.workspace_id).map((p) => p.name)
|
|
1852
1959
|
);
|
|
@@ -1871,14 +1978,14 @@ function registerTaskTools(server) {
|
|
|
1871
1978
|
assigned_agent: z2.enum(AGENT_TYPES).optional().describe("Assign to an agent type")
|
|
1872
1979
|
},
|
|
1873
1980
|
async (args) => {
|
|
1874
|
-
const project = dbGetProject(args.project_name);
|
|
1981
|
+
const project = await dbGetProject(args.project_name);
|
|
1875
1982
|
if (!project) {
|
|
1876
1983
|
return {
|
|
1877
1984
|
content: [{ type: "text", text: `Error: project "${args.project_name}" not found` }],
|
|
1878
1985
|
isError: true
|
|
1879
1986
|
};
|
|
1880
1987
|
}
|
|
1881
|
-
const maxOrder = dbGetMaxTaskOrder(args.project_name);
|
|
1988
|
+
const maxOrder = await dbGetMaxTaskOrder(args.project_name);
|
|
1882
1989
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1883
1990
|
const status = args.status ?? "todo";
|
|
1884
1991
|
const task = {
|
|
@@ -1895,13 +2002,13 @@ function registerTaskTools(server) {
|
|
|
1895
2002
|
...args.assigned_agent && { assignedAgent: args.assigned_agent },
|
|
1896
2003
|
...(status === "done" || status === "cancelled") && { completedAt: now }
|
|
1897
2004
|
};
|
|
1898
|
-
dbInsertTask(task);
|
|
2005
|
+
await dbInsertTask(task);
|
|
1899
2006
|
dbSignalChange();
|
|
1900
2007
|
return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
|
|
1901
2008
|
}
|
|
1902
2009
|
);
|
|
1903
2010
|
server.tool("get_task", "Get a task by ID", { id: V.id.describe("Task ID") }, async (args) => {
|
|
1904
|
-
const task = dbGetTask(args.id);
|
|
2011
|
+
const task = await dbGetTask(args.id);
|
|
1905
2012
|
if (!task) {
|
|
1906
2013
|
return {
|
|
1907
2014
|
content: [{ type: "text", text: `Error: task "${args.id}" not found` }],
|
|
@@ -1924,7 +2031,7 @@ function registerTaskTools(server) {
|
|
|
1924
2031
|
order: z2.number().optional().describe("Queue order")
|
|
1925
2032
|
},
|
|
1926
2033
|
async (args) => {
|
|
1927
|
-
const task = dbGetTask(args.id);
|
|
2034
|
+
const task = await dbGetTask(args.id);
|
|
1928
2035
|
if (!task) {
|
|
1929
2036
|
return {
|
|
1930
2037
|
content: [{ type: "text", text: `Error: task "${args.id}" not found` }],
|
|
@@ -1950,9 +2057,9 @@ function registerTaskTools(server) {
|
|
|
1950
2057
|
updates.archivedAt = void 0;
|
|
1951
2058
|
}
|
|
1952
2059
|
}
|
|
1953
|
-
dbUpdateTask(args.id, updates);
|
|
2060
|
+
await dbUpdateTask(args.id, updates);
|
|
1954
2061
|
dbSignalChange();
|
|
1955
|
-
const updated = dbGetTask(args.id);
|
|
2062
|
+
const updated = await dbGetTask(args.id);
|
|
1956
2063
|
return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
|
|
1957
2064
|
}
|
|
1958
2065
|
);
|
|
@@ -1961,14 +2068,14 @@ function registerTaskTools(server) {
|
|
|
1961
2068
|
"Delete a task by ID",
|
|
1962
2069
|
{ id: V.id.describe("Task ID") },
|
|
1963
2070
|
async (args) => {
|
|
1964
|
-
const task = dbGetTask(args.id);
|
|
2071
|
+
const task = await dbGetTask(args.id);
|
|
1965
2072
|
if (!task) {
|
|
1966
2073
|
return {
|
|
1967
2074
|
content: [{ type: "text", text: `Error: task "${args.id}" not found` }],
|
|
1968
2075
|
isError: true
|
|
1969
2076
|
};
|
|
1970
2077
|
}
|
|
1971
|
-
dbDeleteTask(args.id);
|
|
2078
|
+
await dbDeleteTask(args.id);
|
|
1972
2079
|
dbSignalChange();
|
|
1973
2080
|
return { content: [{ type: "text", text: `Deleted task: ${task.title}` }] };
|
|
1974
2081
|
}
|
|
@@ -1978,7 +2085,7 @@ function registerTaskTools(server) {
|
|
|
1978
2085
|
"Archive a finished task (status must be done or cancelled). Archived tasks are hidden from default views but preserved and restorable.",
|
|
1979
2086
|
{ id: V.id.describe("Task ID") },
|
|
1980
2087
|
async (args) => {
|
|
1981
|
-
const task = dbGetTask(args.id);
|
|
2088
|
+
const task = await dbGetTask(args.id);
|
|
1982
2089
|
if (!task) {
|
|
1983
2090
|
return {
|
|
1984
2091
|
content: [{ type: "text", text: `Error: task "${args.id}" not found` }],
|
|
@@ -1997,9 +2104,9 @@ function registerTaskTools(server) {
|
|
|
1997
2104
|
};
|
|
1998
2105
|
}
|
|
1999
2106
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2000
|
-
dbUpdateTask(args.id, { archivedAt: now, updatedAt: now });
|
|
2107
|
+
await dbUpdateTask(args.id, { archivedAt: now, updatedAt: now });
|
|
2001
2108
|
dbSignalChange();
|
|
2002
|
-
const updated = dbGetTask(args.id);
|
|
2109
|
+
const updated = await dbGetTask(args.id);
|
|
2003
2110
|
return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
|
|
2004
2111
|
}
|
|
2005
2112
|
);
|
|
@@ -2008,7 +2115,7 @@ function registerTaskTools(server) {
|
|
|
2008
2115
|
"Restore an archived task so it shows in default views again.",
|
|
2009
2116
|
{ id: V.id.describe("Task ID") },
|
|
2010
2117
|
async (args) => {
|
|
2011
|
-
const task = dbGetTask(args.id);
|
|
2118
|
+
const task = await dbGetTask(args.id);
|
|
2012
2119
|
if (!task) {
|
|
2013
2120
|
return {
|
|
2014
2121
|
content: [{ type: "text", text: `Error: task "${args.id}" not found` }],
|
|
@@ -2016,9 +2123,9 @@ function registerTaskTools(server) {
|
|
|
2016
2123
|
};
|
|
2017
2124
|
}
|
|
2018
2125
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2019
|
-
dbUpdateTask(args.id, { archivedAt: void 0, updatedAt: now });
|
|
2126
|
+
await dbUpdateTask(args.id, { archivedAt: void 0, updatedAt: now });
|
|
2020
2127
|
dbSignalChange();
|
|
2021
|
-
const updated = dbGetTask(args.id);
|
|
2128
|
+
const updated = await dbGetTask(args.id);
|
|
2022
2129
|
return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
|
|
2023
2130
|
}
|
|
2024
2131
|
);
|
|
@@ -2033,15 +2140,15 @@ function registerTaskTools(server) {
|
|
|
2033
2140
|
},
|
|
2034
2141
|
async (args) => {
|
|
2035
2142
|
if (args.task_id) {
|
|
2036
|
-
const task = dbGetTask(args.task_id);
|
|
2143
|
+
const task = await dbGetTask(args.task_id);
|
|
2037
2144
|
if (!task) {
|
|
2038
2145
|
return {
|
|
2039
2146
|
content: [{ type: "text", text: `Error: task "${args.task_id}" not found` }],
|
|
2040
2147
|
isError: true
|
|
2041
2148
|
};
|
|
2042
2149
|
}
|
|
2043
|
-
const project = dbGetProject(task.projectName);
|
|
2044
|
-
const siblingTasks = dbListTasks(task.projectName);
|
|
2150
|
+
const project = await dbGetProject(task.projectName);
|
|
2151
|
+
const siblingTasks = await dbListTasks(task.projectName);
|
|
2045
2152
|
return {
|
|
2046
2153
|
content: [
|
|
2047
2154
|
{
|
|
@@ -2066,7 +2173,7 @@ function registerTaskTools(server) {
|
|
|
2066
2173
|
}
|
|
2067
2174
|
const cwd = args.cwd || process.cwd();
|
|
2068
2175
|
const normalizedCwd = path4.resolve(cwd);
|
|
2069
|
-
const projects = dbListProjects();
|
|
2176
|
+
const projects = await dbListProjects();
|
|
2070
2177
|
let matchedProject = null;
|
|
2071
2178
|
let matchLen = 0;
|
|
2072
2179
|
for (const p of projects) {
|
|
@@ -2094,7 +2201,7 @@ function registerTaskTools(server) {
|
|
|
2094
2201
|
]
|
|
2095
2202
|
};
|
|
2096
2203
|
}
|
|
2097
|
-
const projectTasks = dbListTasks(matchedProject.name);
|
|
2204
|
+
const projectTasks = await dbListTasks(matchedProject.name);
|
|
2098
2205
|
let matchedTask = null;
|
|
2099
2206
|
for (const t of projectTasks) {
|
|
2100
2207
|
if (t.worktreePath) {
|
|
@@ -2150,7 +2257,7 @@ function registerProjectTools(server) {
|
|
|
2150
2257
|
workspace_id: V.id.optional().describe('Filter by workspace ID (e.g. "personal")')
|
|
2151
2258
|
},
|
|
2152
2259
|
async (args) => {
|
|
2153
|
-
let projects = dbListProjects();
|
|
2260
|
+
let projects = await dbListProjects();
|
|
2154
2261
|
if (args.workspace_id) {
|
|
2155
2262
|
projects = projects.filter((p) => (p.workspaceId ?? "personal") === args.workspace_id);
|
|
2156
2263
|
}
|
|
@@ -2168,7 +2275,7 @@ function registerProjectTools(server) {
|
|
|
2168
2275
|
icon_color: V.hexColor.optional().describe("Hex color for icon")
|
|
2169
2276
|
},
|
|
2170
2277
|
async (args) => {
|
|
2171
|
-
if (dbGetProject(args.name)) {
|
|
2278
|
+
if (await dbGetProject(args.name)) {
|
|
2172
2279
|
return {
|
|
2173
2280
|
content: [{ type: "text", text: `Error: project "${args.name}" already exists` }],
|
|
2174
2281
|
isError: true
|
|
@@ -2181,7 +2288,7 @@ function registerProjectTools(server) {
|
|
|
2181
2288
|
...args.icon && { icon: args.icon },
|
|
2182
2289
|
...args.icon_color && { iconColor: args.icon_color }
|
|
2183
2290
|
};
|
|
2184
|
-
dbInsertProject(project);
|
|
2291
|
+
await dbInsertProject(project);
|
|
2185
2292
|
dbSignalChange();
|
|
2186
2293
|
return { content: [{ type: "text", text: JSON.stringify(project, null, 2) }] };
|
|
2187
2294
|
}
|
|
@@ -2197,7 +2304,7 @@ function registerProjectTools(server) {
|
|
|
2197
2304
|
icon_color: V.hexColor.optional().describe("Hex color for icon")
|
|
2198
2305
|
},
|
|
2199
2306
|
async (args) => {
|
|
2200
|
-
if (!dbGetProject(args.name)) {
|
|
2307
|
+
if (!await dbGetProject(args.name)) {
|
|
2201
2308
|
return {
|
|
2202
2309
|
content: [{ type: "text", text: `Error: project "${args.name}" not found` }],
|
|
2203
2310
|
isError: true
|
|
@@ -2209,9 +2316,9 @@ function registerProjectTools(server) {
|
|
|
2209
2316
|
updates.preferredAgents = args.preferred_agents;
|
|
2210
2317
|
if (args.icon !== void 0) updates.icon = args.icon;
|
|
2211
2318
|
if (args.icon_color !== void 0) updates.iconColor = args.icon_color;
|
|
2212
|
-
dbUpdateProject(args.name, updates);
|
|
2319
|
+
await dbUpdateProject(args.name, updates);
|
|
2213
2320
|
dbSignalChange();
|
|
2214
|
-
const updated = dbGetProject(args.name);
|
|
2321
|
+
const updated = await dbGetProject(args.name);
|
|
2215
2322
|
return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
|
|
2216
2323
|
}
|
|
2217
2324
|
);
|
|
@@ -2220,13 +2327,13 @@ function registerProjectTools(server) {
|
|
|
2220
2327
|
"Delete a project and all its tasks",
|
|
2221
2328
|
{ name: V.name.describe("Project name") },
|
|
2222
2329
|
async (args) => {
|
|
2223
|
-
if (!dbGetProject(args.name)) {
|
|
2330
|
+
if (!await dbGetProject(args.name)) {
|
|
2224
2331
|
return {
|
|
2225
2332
|
content: [{ type: "text", text: `Error: project "${args.name}" not found` }],
|
|
2226
2333
|
isError: true
|
|
2227
2334
|
};
|
|
2228
2335
|
}
|
|
2229
|
-
dbDeleteProject(args.name);
|
|
2336
|
+
await dbDeleteProject(args.name);
|
|
2230
2337
|
dbSignalChange();
|
|
2231
2338
|
return { content: [{ type: "text", text: `Deleted project: ${args.name}` }] };
|
|
2232
2339
|
}
|
|
@@ -2235,180 +2342,6 @@ function registerProjectTools(server) {
|
|
|
2235
2342
|
|
|
2236
2343
|
// src/tools/sessions.ts
|
|
2237
2344
|
import { z as z4 } from "zod";
|
|
2238
|
-
|
|
2239
|
-
// src/ws-client.ts
|
|
2240
|
-
import fs4 from "fs";
|
|
2241
|
-
import path5 from "path";
|
|
2242
|
-
import os3 from "os";
|
|
2243
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
2244
|
-
import { WebSocket } from "ws";
|
|
2245
|
-
var PORT_FILE = path5.join(os3.homedir(), ".vorn", "ws-port");
|
|
2246
|
-
var TIMEOUT_MS = 1e4;
|
|
2247
|
-
var IS_WIN = process.platform === "win32";
|
|
2248
|
-
var PORT_FILE_MISSING_MSG = IS_WIN ? `Vorn port file not found (~/.vorn/ws-port).
|
|
2249
|
-
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
2250
|
-
To fix, find the Vorn process and its listening port:
|
|
2251
|
-
powershell -c "Get-NetTCPConnection -State Listen -OwningProcess (Get-Process Vorn).Id | Select LocalPort"
|
|
2252
|
-
Then write the WS port to the file:
|
|
2253
|
-
echo {"port":<PORT>,"pid":<PID>} > %USERPROFILE%\\.vorn\\ws-port
|
|
2254
|
-
Or restart Vorn to regenerate it.` : `Vorn port file not found (~/.vorn/ws-port).
|
|
2255
|
-
The app may be running but the port file was deleted (e.g. by another instance shutting down).
|
|
2256
|
-
To fix, run: lsof -iTCP -sTCP:LISTEN -P | grep Vorn
|
|
2257
|
-
Then write the WS port (the one on *:<port>) to the file:
|
|
2258
|
-
echo '{"port":<PORT>,"pid":<PID>}' > ~/.vorn/ws-port
|
|
2259
|
-
Or restart Vorn to regenerate it.`;
|
|
2260
|
-
var PORT_FILE_INVALID_MSG = `Vorn port file exists but contains invalid data (~/.vorn/ws-port).
|
|
2261
|
-
Delete it and restart Vorn, or overwrite it with the correct port:
|
|
2262
|
-
${IS_WIN ? "del %USERPROFILE%\\.vorn\\ws-port" : "rm ~/.vorn/ws-port"}`;
|
|
2263
|
-
var rpcId = 0;
|
|
2264
|
-
var cachedPort = null;
|
|
2265
|
-
var cacheTimestamp = 0;
|
|
2266
|
-
var CACHE_TTL_MS = 5e3;
|
|
2267
|
-
var EXEC_OPTS = {
|
|
2268
|
-
encoding: "utf-8",
|
|
2269
|
-
timeout: 5e3,
|
|
2270
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
2271
|
-
};
|
|
2272
|
-
function discoverPort() {
|
|
2273
|
-
try {
|
|
2274
|
-
if (IS_WIN) {
|
|
2275
|
-
const taskOut = execFileSync2(
|
|
2276
|
-
"tasklist",
|
|
2277
|
-
["/FI", "IMAGENAME eq Vorn.exe", "/FO", "CSV", "/NH"],
|
|
2278
|
-
EXEC_OPTS
|
|
2279
|
-
);
|
|
2280
|
-
const pidMatch = taskOut.match(/"Vorn\.exe","(\d+)"/);
|
|
2281
|
-
if (!pidMatch) return null;
|
|
2282
|
-
const pid = pidMatch[1];
|
|
2283
|
-
const lines = execFileSync2("netstat", ["-ano"], EXEC_OPTS).split("\n");
|
|
2284
|
-
let fallback = null;
|
|
2285
|
-
for (const line of lines) {
|
|
2286
|
-
if (!line.includes("LISTENING") || !line.trim().endsWith(pid)) continue;
|
|
2287
|
-
const m = line.match(/(?:0\.0\.0\.0|127\.0\.0\.1):(\d+)/);
|
|
2288
|
-
if (!m) continue;
|
|
2289
|
-
if (line.includes("0.0.0.0")) return parseInt(m[1], 10);
|
|
2290
|
-
fallback ??= parseInt(m[1], 10);
|
|
2291
|
-
}
|
|
2292
|
-
return fallback;
|
|
2293
|
-
} else {
|
|
2294
|
-
const lines = execFileSync2("lsof", ["-iTCP", "-sTCP:LISTEN", "-P", "-n"], EXEC_OPTS).split(
|
|
2295
|
-
"\n"
|
|
2296
|
-
);
|
|
2297
|
-
let fallback = null;
|
|
2298
|
-
for (const line of lines) {
|
|
2299
|
-
if (!line.includes("Vorn")) continue;
|
|
2300
|
-
if (line.includes("*:")) {
|
|
2301
|
-
const m = line.match(/\*:(\d+)/);
|
|
2302
|
-
if (m) return parseInt(m[1], 10);
|
|
2303
|
-
}
|
|
2304
|
-
if (!fallback) {
|
|
2305
|
-
const m = line.match(/:(\d+)\s/);
|
|
2306
|
-
if (m) fallback = parseInt(m[1], 10);
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
return fallback;
|
|
2310
|
-
}
|
|
2311
|
-
} catch {
|
|
2312
|
-
}
|
|
2313
|
-
return null;
|
|
2314
|
-
}
|
|
2315
|
-
function discoverAndHeal() {
|
|
2316
|
-
const now = Date.now();
|
|
2317
|
-
if (cachedPort && now - cacheTimestamp < CACHE_TTL_MS) return { port: cachedPort };
|
|
2318
|
-
const discovered = discoverPort();
|
|
2319
|
-
cachedPort = discovered;
|
|
2320
|
-
cacheTimestamp = now;
|
|
2321
|
-
if (discovered) {
|
|
2322
|
-
try {
|
|
2323
|
-
fs4.mkdirSync(path5.dirname(PORT_FILE), { recursive: true });
|
|
2324
|
-
fs4.writeFileSync(PORT_FILE, JSON.stringify({ port: discovered }), "utf-8");
|
|
2325
|
-
} catch {
|
|
2326
|
-
}
|
|
2327
|
-
return { port: discovered };
|
|
2328
|
-
}
|
|
2329
|
-
return { port: null, reason: "missing" };
|
|
2330
|
-
}
|
|
2331
|
-
function readPort() {
|
|
2332
|
-
try {
|
|
2333
|
-
const raw = fs4.readFileSync(PORT_FILE, "utf-8").trim();
|
|
2334
|
-
if (!raw) return { port: null, reason: "invalid" };
|
|
2335
|
-
if (raw.startsWith("{")) {
|
|
2336
|
-
const parsed = JSON.parse(raw);
|
|
2337
|
-
const p2 = parsed?.port;
|
|
2338
|
-
const pid = parsed?.pid;
|
|
2339
|
-
if (typeof p2 !== "number" || !Number.isFinite(p2) || p2 <= 0) {
|
|
2340
|
-
return { port: null, reason: "invalid" };
|
|
2341
|
-
}
|
|
2342
|
-
if (typeof pid === "number" && Number.isInteger(pid) && pid > 0) {
|
|
2343
|
-
try {
|
|
2344
|
-
process.kill(pid, 0);
|
|
2345
|
-
} catch (err) {
|
|
2346
|
-
if (err.code === "EPERM") return { port: p2 };
|
|
2347
|
-
return discoverAndHeal();
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2350
|
-
return { port: p2 };
|
|
2351
|
-
}
|
|
2352
|
-
const p = parseInt(raw, 10);
|
|
2353
|
-
return Number.isFinite(p) && p > 0 ? { port: p } : { port: null, reason: "invalid" };
|
|
2354
|
-
} catch {
|
|
2355
|
-
return discoverAndHeal();
|
|
2356
|
-
}
|
|
2357
|
-
}
|
|
2358
|
-
async function rpcCall(method, params, timeoutMs = TIMEOUT_MS) {
|
|
2359
|
-
const result = readPort();
|
|
2360
|
-
if (!result.port) {
|
|
2361
|
-
throw new Error(result.reason === "invalid" ? PORT_FILE_INVALID_MSG : PORT_FILE_MISSING_MSG);
|
|
2362
|
-
}
|
|
2363
|
-
return new Promise((resolve, reject) => {
|
|
2364
|
-
const ws = new WebSocket(`ws://127.0.0.1:${result.port}/ws`);
|
|
2365
|
-
const id = ++rpcId;
|
|
2366
|
-
const timer = setTimeout(() => {
|
|
2367
|
-
ws.close();
|
|
2368
|
-
reject(new Error(`RPC call "${method}" timed out after ${timeoutMs}ms`));
|
|
2369
|
-
}, timeoutMs);
|
|
2370
|
-
ws.on("open", () => {
|
|
2371
|
-
ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
2372
|
-
});
|
|
2373
|
-
ws.on("message", (raw) => {
|
|
2374
|
-
try {
|
|
2375
|
-
const msg = JSON.parse(raw.toString());
|
|
2376
|
-
if (msg.id !== id) return;
|
|
2377
|
-
clearTimeout(timer);
|
|
2378
|
-
ws.close();
|
|
2379
|
-
if (msg.error) {
|
|
2380
|
-
reject(new Error(msg.error.message));
|
|
2381
|
-
} else {
|
|
2382
|
-
resolve(msg.result);
|
|
2383
|
-
}
|
|
2384
|
-
} catch {
|
|
2385
|
-
}
|
|
2386
|
-
});
|
|
2387
|
-
ws.on("error", (err) => {
|
|
2388
|
-
clearTimeout(timer);
|
|
2389
|
-
reject(new Error(`Cannot connect to Vorn server: ${err.message}. Is the app running?`));
|
|
2390
|
-
});
|
|
2391
|
-
});
|
|
2392
|
-
}
|
|
2393
|
-
async function rpcNotify(method, params) {
|
|
2394
|
-
const result = readPort();
|
|
2395
|
-
if (!result.port) {
|
|
2396
|
-
throw new Error(result.reason === "invalid" ? PORT_FILE_INVALID_MSG : PORT_FILE_MISSING_MSG);
|
|
2397
|
-
}
|
|
2398
|
-
return new Promise((resolve, reject) => {
|
|
2399
|
-
const ws = new WebSocket(`ws://127.0.0.1:${result.port}/ws`);
|
|
2400
|
-
ws.on("open", () => {
|
|
2401
|
-
ws.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
|
|
2402
|
-
ws.close();
|
|
2403
|
-
resolve();
|
|
2404
|
-
});
|
|
2405
|
-
ws.on("error", (err) => {
|
|
2406
|
-
reject(new Error(`Cannot connect to Vorn server: ${err.message}. Is the app running?`));
|
|
2407
|
-
});
|
|
2408
|
-
});
|
|
2409
|
-
}
|
|
2410
|
-
|
|
2411
|
-
// src/tools/sessions.ts
|
|
2412
2345
|
var AGENT_TYPES3 = [
|
|
2413
2346
|
"claude",
|
|
2414
2347
|
"copilot",
|
|
@@ -3138,7 +3071,7 @@ function registerWorkflowTools(server) {
|
|
|
3138
3071
|
workspace_id: V.id.optional().describe("Filter by workspace ID")
|
|
3139
3072
|
},
|
|
3140
3073
|
async (args) => {
|
|
3141
|
-
let workflows = dbListWorkflows();
|
|
3074
|
+
let workflows = await dbListWorkflows();
|
|
3142
3075
|
if (args.workspace_id) {
|
|
3143
3076
|
workflows = workflows.filter((w) => (w.workspaceId ?? "personal") === args.workspace_id);
|
|
3144
3077
|
}
|
|
@@ -3191,7 +3124,7 @@ function registerWorkflowTools(server) {
|
|
|
3191
3124
|
enabled: args.enabled ?? true,
|
|
3192
3125
|
...args.stagger_delay_ms && { staggerDelayMs: args.stagger_delay_ms }
|
|
3193
3126
|
};
|
|
3194
|
-
dbInsertWorkflow(workflow);
|
|
3127
|
+
await dbInsertWorkflow(workflow);
|
|
3195
3128
|
dbSignalChange();
|
|
3196
3129
|
return { content: [{ type: "text", text: JSON.stringify(workflow, null, 2) }] };
|
|
3197
3130
|
}
|
|
@@ -3215,7 +3148,7 @@ function registerWorkflowTools(server) {
|
|
|
3215
3148
|
if ("error" in resolved) {
|
|
3216
3149
|
return { content: [{ type: "text", text: `Error: ${resolved.error}` }], isError: true };
|
|
3217
3150
|
}
|
|
3218
|
-
const workflows = dbListWorkflows();
|
|
3151
|
+
const workflows = await dbListWorkflows();
|
|
3219
3152
|
const workflow = workflows.find((w) => w.id === resolved.id);
|
|
3220
3153
|
if (!workflow) {
|
|
3221
3154
|
return {
|
|
@@ -3242,7 +3175,7 @@ function registerWorkflowTools(server) {
|
|
|
3242
3175
|
if (args.icon_color !== void 0) updates.iconColor = args.icon_color;
|
|
3243
3176
|
if (args.enabled !== void 0) updates.enabled = args.enabled;
|
|
3244
3177
|
if (args.stagger_delay_ms !== void 0) updates.staggerDelayMs = args.stagger_delay_ms;
|
|
3245
|
-
dbUpdateWorkflow(resolved.id, updates);
|
|
3178
|
+
await dbUpdateWorkflow(resolved.id, updates);
|
|
3246
3179
|
dbSignalChange();
|
|
3247
3180
|
return {
|
|
3248
3181
|
content: [{ type: "text", text: JSON.stringify({ ...workflow, ...updates }, null, 2) }]
|
|
@@ -3261,7 +3194,7 @@ function registerWorkflowTools(server) {
|
|
|
3261
3194
|
if ("error" in resolved) {
|
|
3262
3195
|
return { content: [{ type: "text", text: `Error: ${resolved.error}` }], isError: true };
|
|
3263
3196
|
}
|
|
3264
|
-
const workflows = dbListWorkflows();
|
|
3197
|
+
const workflows = await dbListWorkflows();
|
|
3265
3198
|
const workflow = workflows.find((w) => w.id === resolved.id);
|
|
3266
3199
|
if (!workflow) {
|
|
3267
3200
|
return {
|
|
@@ -3269,7 +3202,7 @@ function registerWorkflowTools(server) {
|
|
|
3269
3202
|
isError: true
|
|
3270
3203
|
};
|
|
3271
3204
|
}
|
|
3272
|
-
dbDeleteWorkflow(resolved.id);
|
|
3205
|
+
await dbDeleteWorkflow(resolved.id);
|
|
3273
3206
|
dbSignalChange();
|
|
3274
3207
|
return { content: [{ type: "text", text: `Deleted workflow: ${workflow.name}` }] };
|
|
3275
3208
|
}
|
|
@@ -3290,11 +3223,11 @@ function registerWorkflowTools(server) {
|
|
|
3290
3223
|
};
|
|
3291
3224
|
}
|
|
3292
3225
|
if (args.task_id) {
|
|
3293
|
-
const runs = listWorkflowRunsByTask(args.task_id, args.limit ?? 20);
|
|
3226
|
+
const runs = await listWorkflowRunsByTask(args.task_id, args.limit ?? 20);
|
|
3294
3227
|
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
3295
3228
|
}
|
|
3296
3229
|
if (args.workflow_id) {
|
|
3297
|
-
const runs = listWorkflowRuns(args.workflow_id, args.limit ?? 20);
|
|
3230
|
+
const runs = await listWorkflowRuns(args.workflow_id, args.limit ?? 20);
|
|
3298
3231
|
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
3299
3232
|
}
|
|
3300
3233
|
return {
|
|
@@ -3310,7 +3243,7 @@ function registerWorkflowTools(server) {
|
|
|
3310
3243
|
run_id: V.id.describe("Run ID (from list_workflow_runs)")
|
|
3311
3244
|
},
|
|
3312
3245
|
async (args) => {
|
|
3313
|
-
const run = listAllWorkflowRuns(void 0, 500).find((r) => r.runId === args.run_id);
|
|
3246
|
+
const run = (await listAllWorkflowRuns(void 0, 500)).find((r) => r.runId === args.run_id);
|
|
3314
3247
|
if (!run) {
|
|
3315
3248
|
return {
|
|
3316
3249
|
content: [
|
|
@@ -3401,7 +3334,7 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3401
3334
|
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>}})")
|
|
3402
3335
|
},
|
|
3403
3336
|
async (args) => {
|
|
3404
|
-
const workflow = dbListWorkflows().find((w) => w.id === args.workflow_id);
|
|
3337
|
+
const workflow = (await dbListWorkflows()).find((w) => w.id === args.workflow_id);
|
|
3405
3338
|
if (!workflow) {
|
|
3406
3339
|
return {
|
|
3407
3340
|
content: [{ type: "text", text: `Error: workflow "${args.workflow_id}" not found` }],
|
|
@@ -3462,7 +3395,7 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
|
|
|
3462
3395
|
if ("error" in resolved) {
|
|
3463
3396
|
return { content: [{ type: "text", text: `Error: ${resolved.error}` }], isError: true };
|
|
3464
3397
|
}
|
|
3465
|
-
const workflow = dbListWorkflows().find((w) => w.id === resolved.id);
|
|
3398
|
+
const workflow = (await dbListWorkflows()).find((w) => w.id === resolved.id);
|
|
3466
3399
|
if (!workflow) {
|
|
3467
3400
|
return {
|
|
3468
3401
|
content: [{ type: "text", text: `Error: workflow "${resolved.id}" not found` }],
|
|
@@ -3481,7 +3414,7 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
|
|
|
3481
3414
|
isError: true
|
|
3482
3415
|
};
|
|
3483
3416
|
}
|
|
3484
|
-
const projects = dbListProjects();
|
|
3417
|
+
const projects = await dbListProjects();
|
|
3485
3418
|
const projectName = workflow.nodes.map((n) => n.config.projectName).find((name) => typeof name === "string" && name.length > 0);
|
|
3486
3419
|
const project = projects.find((p) => p.name === projectName);
|
|
3487
3420
|
if (!project) {
|
|
@@ -3549,7 +3482,7 @@ Warning: these still hold a machine-specific path and will not travel: ${residua
|
|
|
3549
3482
|
isError: true
|
|
3550
3483
|
};
|
|
3551
3484
|
}
|
|
3552
|
-
const project = dbListProjects().find((p) => p.name === args.project_name);
|
|
3485
|
+
const project = (await dbListProjects()).find((p) => p.name === args.project_name);
|
|
3553
3486
|
if (!project) {
|
|
3554
3487
|
return {
|
|
3555
3488
|
content: [
|
|
@@ -3591,11 +3524,11 @@ Warning: these still hold a machine-specific path and will not travel: ${residua
|
|
|
3591
3524
|
isError: true
|
|
3592
3525
|
};
|
|
3593
3526
|
}
|
|
3594
|
-
const existing = dbListWorkflows().find((w) => w.id === definition.id);
|
|
3527
|
+
const existing = (await dbListWorkflows()).find((w) => w.id === definition.id);
|
|
3595
3528
|
if (existing) {
|
|
3596
|
-
dbUpdateWorkflow(definition.id, definition);
|
|
3529
|
+
await dbUpdateWorkflow(definition.id, definition);
|
|
3597
3530
|
} else {
|
|
3598
|
-
dbInsertWorkflow(definition);
|
|
3531
|
+
await dbInsertWorkflow(definition);
|
|
3599
3532
|
}
|
|
3600
3533
|
dbSignalChange();
|
|
3601
3534
|
return {
|
|
@@ -3627,7 +3560,7 @@ import crypto3 from "crypto";
|
|
|
3627
3560
|
import { z as z6 } from "zod";
|
|
3628
3561
|
function registerWorkspaceTools(server) {
|
|
3629
3562
|
server.tool("list_workspaces", "List all workspaces", async () => {
|
|
3630
|
-
const workspaces = dbListWorkspaces();
|
|
3563
|
+
const workspaces = await dbListWorkspaces();
|
|
3631
3564
|
return { content: [{ type: "text", text: JSON.stringify(workspaces, null, 2) }] };
|
|
3632
3565
|
});
|
|
3633
3566
|
server.tool(
|
|
@@ -3639,7 +3572,7 @@ function registerWorkspaceTools(server) {
|
|
|
3639
3572
|
icon_color: V.hexColor.optional().describe("Hex color for icon")
|
|
3640
3573
|
},
|
|
3641
3574
|
async (args) => {
|
|
3642
|
-
const existing = dbListWorkspaces();
|
|
3575
|
+
const existing = await dbListWorkspaces();
|
|
3643
3576
|
const maxOrder = existing.reduce((max, w) => Math.max(max, w.order), 0);
|
|
3644
3577
|
const workspace = {
|
|
3645
3578
|
id: crypto3.randomUUID(),
|
|
@@ -3648,7 +3581,7 @@ function registerWorkspaceTools(server) {
|
|
|
3648
3581
|
...args.icon && { icon: args.icon },
|
|
3649
3582
|
...args.icon_color && { iconColor: args.icon_color }
|
|
3650
3583
|
};
|
|
3651
|
-
dbInsertWorkspace(workspace);
|
|
3584
|
+
await dbInsertWorkspace(workspace);
|
|
3652
3585
|
dbSignalChange();
|
|
3653
3586
|
return { content: [{ type: "text", text: JSON.stringify(workspace, null, 2) }] };
|
|
3654
3587
|
}
|
|
@@ -3664,7 +3597,7 @@ function registerWorkspaceTools(server) {
|
|
|
3664
3597
|
order: z6.number().int().min(0).optional().describe("Sort order")
|
|
3665
3598
|
},
|
|
3666
3599
|
async (args) => {
|
|
3667
|
-
const existing = dbListWorkspaces();
|
|
3600
|
+
const existing = await dbListWorkspaces();
|
|
3668
3601
|
if (!existing.find((w) => w.id === args.id)) {
|
|
3669
3602
|
return {
|
|
3670
3603
|
content: [{ type: "text", text: `Error: workspace "${args.id}" not found` }],
|
|
@@ -3676,9 +3609,9 @@ function registerWorkspaceTools(server) {
|
|
|
3676
3609
|
if (args.icon !== void 0) updates.icon = args.icon;
|
|
3677
3610
|
if (args.icon_color !== void 0) updates.iconColor = args.icon_color;
|
|
3678
3611
|
if (args.order !== void 0) updates.order = args.order;
|
|
3679
|
-
dbUpdateWorkspace(args.id, updates);
|
|
3612
|
+
await dbUpdateWorkspace(args.id, updates);
|
|
3680
3613
|
dbSignalChange();
|
|
3681
|
-
const updated = dbListWorkspaces().find((w) => w.id === args.id);
|
|
3614
|
+
const updated = (await dbListWorkspaces()).find((w) => w.id === args.id);
|
|
3682
3615
|
return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
|
|
3683
3616
|
}
|
|
3684
3617
|
);
|
|
@@ -3693,7 +3626,7 @@ function registerWorkspaceTools(server) {
|
|
|
3693
3626
|
isError: true
|
|
3694
3627
|
};
|
|
3695
3628
|
}
|
|
3696
|
-
const existing = dbListWorkspaces();
|
|
3629
|
+
const existing = await dbListWorkspaces();
|
|
3697
3630
|
const workspace = existing.find((w) => w.id === args.id);
|
|
3698
3631
|
if (!workspace) {
|
|
3699
3632
|
return {
|
|
@@ -3701,7 +3634,7 @@ function registerWorkspaceTools(server) {
|
|
|
3701
3634
|
isError: true
|
|
3702
3635
|
};
|
|
3703
3636
|
}
|
|
3704
|
-
dbDeleteWorkspace(args.id);
|
|
3637
|
+
await dbDeleteWorkspace(args.id);
|
|
3705
3638
|
dbSignalChange();
|
|
3706
3639
|
return { content: [{ type: "text", text: `Deleted workspace: ${workspace.name}` }] };
|
|
3707
3640
|
}
|
|
@@ -3884,7 +3817,7 @@ function registerConnectorTools(server) {
|
|
|
3884
3817
|
);
|
|
3885
3818
|
}
|
|
3886
3819
|
const launch = typeof target === "string" ? parseLaunch(target) : target;
|
|
3887
|
-
const
|
|
3820
|
+
const connection2 = await rpcCall("connection:create", {
|
|
3888
3821
|
connectorId: "mcp",
|
|
3889
3822
|
name: args.name ?? (trigger ? `${manifest.name}: ${trigger.label}` : manifest.name),
|
|
3890
3823
|
filters: {
|
|
@@ -3902,7 +3835,7 @@ function registerConnectorTools(server) {
|
|
|
3902
3835
|
});
|
|
3903
3836
|
return json({
|
|
3904
3837
|
installed: manifest.name,
|
|
3905
|
-
connectionId:
|
|
3838
|
+
connectionId: connection2.id,
|
|
3906
3839
|
trigger: trigger?.type,
|
|
3907
3840
|
note: "Poll it now with backfill_connection, or reference it from a workflow."
|
|
3908
3841
|
});
|
|
@@ -3986,7 +3919,7 @@ function errorResult(err) {
|
|
|
3986
3919
|
};
|
|
3987
3920
|
}
|
|
3988
3921
|
function source(label) {
|
|
3989
|
-
return label.includes("WEB PAGE") ? "page" : "device";
|
|
3922
|
+
return label.includes("WEB PAGE") || label.includes("BROWSER") ? "page" : "device";
|
|
3990
3923
|
}
|
|
3991
3924
|
function pageResult(data, label = "WEB PAGE CONTENT") {
|
|
3992
3925
|
const nonce = crypto4.randomUUID();
|
|
@@ -4116,7 +4049,7 @@ function registerBrowserTools(server) {
|
|
|
4116
4049
|
);
|
|
4117
4050
|
server.tool(
|
|
4118
4051
|
"browser_interact",
|
|
4119
|
-
'Act on your session browser pane: click, hover, type, press a key, or scroll. Address the target by "ref" from read_page where possible \u2014 refs survive reflow, coordinates do not. A ref from before a navigation is refused rather than guessed at; re-read the page.',
|
|
4052
|
+
'Act on your session browser pane: click, hover, type, press a key, or scroll. Address the target by "ref" from read_page where possible \u2014 refs survive reflow, coordinates do not. A ref from before a navigation is refused rather than guessed at; re-read the page. A ref target is scrolled into view first, so it does not need to be on screen already. "ok" means the input was dispatched, not that the page did what you expected \u2014 read the state back after anything that matters.',
|
|
4120
4053
|
{
|
|
4121
4054
|
action: z8.enum(["click", "hover", "type", "key", "scroll"]).describe('What to do. "type" clicks the target first when one is given.'),
|
|
4122
4055
|
ref: V.shortText.optional().describe("Element ref from read_page"),
|
|
@@ -4147,13 +4080,19 @@ function registerBrowserTools(server) {
|
|
|
4147
4080
|
);
|
|
4148
4081
|
server.tool(
|
|
4149
4082
|
"browser_tabs",
|
|
4150
|
-
'
|
|
4083
|
+
'List, add, close, or switch tabs in your session browser pane. "close" and "select" take a zero-based index \u2014 call "list" first to see what those indices name, since a tab that redirected or followed a link is no longer on the page it was opened with. Closing the last remaining tab closes the pane.',
|
|
4151
4084
|
{
|
|
4152
|
-
action: z8.enum(["add", "close", "select"]).describe("What to do with tabs"),
|
|
4085
|
+
action: z8.enum(["list", "add", "close", "select"]).describe("What to do with tabs"),
|
|
4153
4086
|
url: V.url.optional().describe('URL for "add"'),
|
|
4154
4087
|
index: z8.number().int().min(0).optional().describe("Zero-based tab index for close/select")
|
|
4155
4088
|
},
|
|
4156
4089
|
async (args) => withSession(async (id) => {
|
|
4090
|
+
if (args.action === "list") {
|
|
4091
|
+
const result = await rpcCall("browser:listTabs", {
|
|
4092
|
+
sessionId: id
|
|
4093
|
+
});
|
|
4094
|
+
return pageResult(result.tabs, "BROWSER TAB LIST");
|
|
4095
|
+
}
|
|
4157
4096
|
await rpcCall("browser:tabs", {
|
|
4158
4097
|
sessionId: id,
|
|
4159
4098
|
action: args.action,
|
|
@@ -4165,7 +4104,7 @@ function registerBrowserTools(server) {
|
|
|
4165
4104
|
);
|
|
4166
4105
|
server.tool(
|
|
4167
4106
|
"browser_navigate",
|
|
4168
|
-
"Navigate your session browser pane to a URL. Opens the pane first if none is open.
|
|
4107
|
+
"Navigate your session browser pane to a URL. Opens the pane first if none is open. http and https, plus file: urls inside your session's own project or worktree \u2014 anything outside it is refused, so serve files from elsewhere over http instead. The pane carries no claude.ai login, so artifact URLs will not load in it.",
|
|
4169
4108
|
{ url: V.url.describe("URL to open") },
|
|
4170
4109
|
async (args) => withSession(async (id) => {
|
|
4171
4110
|
const result = await rpcCall("browser:navigate", {
|
|
@@ -4175,6 +4114,20 @@ function registerBrowserTools(server) {
|
|
|
4175
4114
|
return { content: [{ type: "text", text: `Navigated to ${result.url}` }] };
|
|
4176
4115
|
})
|
|
4177
4116
|
);
|
|
4117
|
+
server.tool(
|
|
4118
|
+
"browser_history",
|
|
4119
|
+
"Step back or forward through your session browser pane's own history \u2014 the same thing the pane's back and forward buttons do. Fails when there is no page to move to, rather than quietly staying put.",
|
|
4120
|
+
{ direction: z8.enum(["back", "forward"]).describe("Which way to step") },
|
|
4121
|
+
async (args) => withSession(async (id) => {
|
|
4122
|
+
const result = await rpcCall("browser:history", {
|
|
4123
|
+
sessionId: id,
|
|
4124
|
+
direction: args.direction
|
|
4125
|
+
});
|
|
4126
|
+
return {
|
|
4127
|
+
content: [{ type: "text", text: `Went ${args.direction} to ${result.url}` }]
|
|
4128
|
+
};
|
|
4129
|
+
})
|
|
4130
|
+
);
|
|
4178
4131
|
}
|
|
4179
4132
|
|
|
4180
4133
|
// src/tools/device.ts
|
|
@@ -4397,14 +4350,14 @@ Screen is ${r.screen.width}x${r.screen.height} points. Image pixels are ${r.scal
|
|
|
4397
4350
|
function createMcpServer(version) {
|
|
4398
4351
|
const server = new McpServer({ name: "vorn", version }, { capabilities: { tools: {} } });
|
|
4399
4352
|
registerConfigTools(server);
|
|
4400
|
-
registerProjectTools(server);
|
|
4401
|
-
registerTaskTools(server);
|
|
4402
4353
|
registerSessionTools(server);
|
|
4403
|
-
registerWorkflowTools(server);
|
|
4404
|
-
registerWorkspaceTools(server);
|
|
4405
4354
|
registerConnectorTools(server);
|
|
4406
4355
|
registerBrowserTools(server);
|
|
4407
4356
|
registerDeviceTools(server);
|
|
4357
|
+
registerProjectTools(server);
|
|
4358
|
+
registerTaskTools(server);
|
|
4359
|
+
registerWorkflowTools(server);
|
|
4360
|
+
registerWorkspaceTools(server);
|
|
4408
4361
|
return server;
|
|
4409
4362
|
}
|
|
4410
4363
|
|
|
@@ -4417,7 +4370,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
4417
4370
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
4418
4371
|
async function main() {
|
|
4419
4372
|
configManager.init();
|
|
4420
|
-
const version = true ? "0.6.
|
|
4373
|
+
const version = true ? "0.6.1-beta.1" : createRequire(import.meta.url)("../package.json").version;
|
|
4421
4374
|
const server = createMcpServer(version);
|
|
4422
4375
|
const transport = new StdioServerTransport();
|
|
4423
4376
|
await server.connect(transport);
|