openfox 2.0.12 → 2.0.14
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/{chat-handler-6NUNVXIU.js → chat-handler-ITXCBFJK.js} +4 -4
- package/dist/{chunk-A52FXWJX.js → chunk-3SGGOBCL.js} +10 -387
- package/dist/{chunk-WF3MA65W.js → chunk-4U45A7B3.js} +386 -2072
- package/dist/chunk-4W2Z4B5U.js +2077 -0
- package/dist/{chunk-2VDLXCLO.js → chunk-4ZTQO5JP.js} +31 -25
- package/dist/{chunk-EZJUU54W.js → chunk-7GJ6XYAF.js} +10 -8
- package/dist/{chunk-HYYFVYDV.js → chunk-LNEBVOH6.js} +6 -6
- package/dist/chunk-NWO6GRYE.js +39 -0
- package/dist/{chunk-HPCGVAS4.js → chunk-PP6VQXQW.js} +17 -1
- package/dist/chunk-PQ56PX7L.js +397 -0
- package/dist/cli/dev.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/{config-BU66P4KX.js → config-YFHA5MWV.js} +2 -2
- package/dist/{orchestrator-WXEZHUVQ.js → orchestrator-VP74KOXI.js} +4 -4
- package/dist/package.json +3 -2
- package/dist/{processor-GA5NG3T7.js → processor-CCXZYLX6.js} +4 -4
- package/dist/{protocol-CaLuIetw.d.ts → protocol-BE5dRtYw.d.ts} +9 -0
- package/dist/{provider-QPECLUZ7.js → provider-QUJQLVOM.js} +7 -5
- package/dist/{provider-manager-YA2WALTF.js → provider-manager-DETXLDKW.js} +3 -2
- package/dist/{serve-5BSOEK54.js → serve-GA2UIHHB.js} +11 -7
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.js +8 -5
- package/dist/server-EA6W7YIC.js +29 -0
- package/dist/shared/index.d.ts +2 -2
- package/dist/tool-adapter-B7QP6NLA.js +7 -0
- package/dist/{tools-THXBQJ7A.js → tools-DUC54GEH.js} +5 -3
- package/dist/web/assets/{index-CQw-9GC9.js → index-D0qVcBva.js} +76 -75
- package/dist/web/assets/{index-Db3yUKBI.css → index-Ho_tVUbw.css} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +3 -2
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
TEMPLATE_VARIABLES,
|
|
3
|
+
createWebSocketServer,
|
|
4
|
+
deleteProjectWorkflow,
|
|
5
|
+
deleteWorkflow,
|
|
6
|
+
findWorkflowById,
|
|
7
|
+
getAuthConfig,
|
|
8
|
+
getDefaultWorkflowIds,
|
|
9
|
+
hasPassword,
|
|
10
|
+
isDefaultWorkflow,
|
|
11
|
+
isValidToken,
|
|
12
|
+
loadAllWorkflows,
|
|
13
|
+
loadDefaultWorkflows,
|
|
14
|
+
loadProjectWorkflows,
|
|
15
|
+
loadServerAuthConfig,
|
|
16
|
+
loadUserWorkflows,
|
|
17
|
+
requiresAuth,
|
|
18
|
+
saveWorkflow,
|
|
19
|
+
saveWorkflowToProject,
|
|
20
|
+
terminalManager,
|
|
21
|
+
tokenFromPassword,
|
|
22
|
+
verifyPassword,
|
|
23
|
+
workflowExists
|
|
24
|
+
} from "./chunk-4W2Z4B5U.js";
|
|
4
25
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
runAgentTurn,
|
|
8
|
-
runChatTurn
|
|
9
|
-
} from "./chunk-EZJUU54W.js";
|
|
26
|
+
createMcpTools
|
|
27
|
+
} from "./chunk-NWO6GRYE.js";
|
|
10
28
|
import {
|
|
11
29
|
agentExists,
|
|
12
|
-
checkAborted,
|
|
13
30
|
createToolRegistry,
|
|
14
31
|
deleteAgent,
|
|
15
32
|
deleteItemFromDir,
|
|
@@ -17,21 +34,15 @@ import {
|
|
|
17
34
|
deleteProjectSkill,
|
|
18
35
|
deleteSkill,
|
|
19
36
|
devServerManager,
|
|
20
|
-
executeSubAgent,
|
|
21
37
|
findAgentById,
|
|
22
38
|
findSkillById,
|
|
23
|
-
getAllInstructions,
|
|
24
39
|
getDefaultAgentIds,
|
|
25
40
|
getDefaultIds,
|
|
26
41
|
getDefaultSkillIds,
|
|
27
|
-
getEnabledSkillMetadata,
|
|
28
|
-
getSubAgents,
|
|
29
|
-
getToolRegistryForAgent,
|
|
30
42
|
getTopLevelAgents,
|
|
31
43
|
isDefaultAgent,
|
|
32
44
|
isDefaultSkill,
|
|
33
45
|
isSkillEnabled,
|
|
34
|
-
jsonSerializer,
|
|
35
46
|
loadAllAgents,
|
|
36
47
|
loadAllAgentsDefault,
|
|
37
48
|
loadAllSkills,
|
|
@@ -48,23 +59,16 @@ import {
|
|
|
48
59
|
saveItemToDir,
|
|
49
60
|
saveSkill,
|
|
50
61
|
saveSkillToProject,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
} from "./chunk-
|
|
62
|
+
setMcpTools,
|
|
63
|
+
skillExists
|
|
64
|
+
} from "./chunk-4ZTQO5JP.js";
|
|
54
65
|
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
createMessageStartEvent
|
|
58
|
-
} from "./chunk-L7TDUIQY.js";
|
|
66
|
+
getProject
|
|
67
|
+
} from "./chunk-XAMAYRDA.js";
|
|
59
68
|
import {
|
|
60
69
|
getPathSeparator,
|
|
61
|
-
|
|
62
|
-
isAbsolutePath,
|
|
63
|
-
onProcessEvent
|
|
70
|
+
isAbsolutePath
|
|
64
71
|
} from "./chunk-PBGOZMVY.js";
|
|
65
|
-
import {
|
|
66
|
-
getProject
|
|
67
|
-
} from "./chunk-XAMAYRDA.js";
|
|
68
72
|
import {
|
|
69
73
|
createSession,
|
|
70
74
|
deleteSession,
|
|
@@ -78,9 +82,7 @@ import {
|
|
|
78
82
|
emitRunningChanged,
|
|
79
83
|
emitSessionInitialized,
|
|
80
84
|
emitUserMessage,
|
|
81
|
-
getCurrentContextWindowId,
|
|
82
85
|
getEventStore,
|
|
83
|
-
getRuntimeConfig,
|
|
84
86
|
getSession,
|
|
85
87
|
getSessionState,
|
|
86
88
|
initEventStore,
|
|
@@ -93,45 +95,25 @@ import {
|
|
|
93
95
|
updateSessionRunning
|
|
94
96
|
} from "./chunk-SYG2ENUQ.js";
|
|
95
97
|
import {
|
|
96
|
-
|
|
97
|
-
createChatMessageMessage,
|
|
98
|
-
createContextStateMessage,
|
|
99
|
-
createErrorMessage,
|
|
100
|
-
createGitStatusMessage,
|
|
101
|
-
createQueueStateMessage,
|
|
102
|
-
createSessionRunningMessage,
|
|
103
|
-
isAskAnswerPayload,
|
|
104
|
-
isSessionLoadPayload,
|
|
105
|
-
parseClientMessage,
|
|
106
|
-
serializeServerMessage,
|
|
107
|
-
storedEventToServerMessage
|
|
108
|
-
} from "./chunk-3QB2RMX2.js";
|
|
109
|
-
import {
|
|
110
|
-
EventEmitter,
|
|
111
|
-
provideAnswer
|
|
98
|
+
EventEmitter
|
|
112
99
|
} from "./chunk-EU3WWTFH.js";
|
|
113
100
|
import {
|
|
114
101
|
initDatabase
|
|
115
102
|
} from "./chunk-FBGWG4N6.js";
|
|
116
|
-
import {
|
|
117
|
-
computeSessionStats
|
|
118
|
-
} from "./chunk-VUQCQXXJ.js";
|
|
119
|
-
import {
|
|
120
|
-
createServerMessage
|
|
121
|
-
} from "./chunk-5BDVM6YI.js";
|
|
122
103
|
import {
|
|
123
104
|
getGlobalConfigDir
|
|
124
105
|
} from "./chunk-CQGTEGKL.js";
|
|
106
|
+
import {
|
|
107
|
+
createProviderManager,
|
|
108
|
+
parseDefaultModelSelection
|
|
109
|
+
} from "./chunk-3SGGOBCL.js";
|
|
125
110
|
import {
|
|
126
111
|
SessionNotFoundError,
|
|
127
112
|
buildModelsUrl,
|
|
128
|
-
createLLMClient,
|
|
129
|
-
createProviderManager,
|
|
130
113
|
detectModel,
|
|
131
114
|
ensureVersionPrefix,
|
|
132
|
-
getLlmStatus
|
|
133
|
-
|
|
134
|
-
} from "./chunk-A52FXWJX.js";
|
|
115
|
+
getLlmStatus
|
|
116
|
+
} from "./chunk-PQ56PX7L.js";
|
|
135
117
|
import {
|
|
136
118
|
getBackendDisplayName,
|
|
137
119
|
getModelProfile
|
|
@@ -145,9 +127,9 @@ import {
|
|
|
145
127
|
import express from "express";
|
|
146
128
|
import cors from "cors";
|
|
147
129
|
import { createServer as createHttpServer } from "http";
|
|
148
|
-
import { fileURLToPath as
|
|
149
|
-
import { dirname as
|
|
150
|
-
import { readFile
|
|
130
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
131
|
+
import { dirname as dirname4, resolve as resolve2, join as join5 } from "path";
|
|
132
|
+
import { readFile } from "fs/promises";
|
|
151
133
|
import { createServer as createViteServer } from "vite";
|
|
152
134
|
|
|
153
135
|
// src/server/llm/mock.ts
|
|
@@ -1462,1972 +1444,160 @@ function createMockLLMClient() {
|
|
|
1462
1444
|
};
|
|
1463
1445
|
}
|
|
1464
1446
|
|
|
1465
|
-
// src/server/
|
|
1466
|
-
import {
|
|
1467
|
-
import {
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
import * as pty from "node-pty";
|
|
1475
|
-
import os from "os";
|
|
1476
|
-
var TerminalManager = class {
|
|
1477
|
-
sessions = /* @__PURE__ */ new Map();
|
|
1478
|
-
outputCallbacks = /* @__PURE__ */ new Set();
|
|
1479
|
-
exitCallbacks = /* @__PURE__ */ new Set();
|
|
1480
|
-
outputHistory = /* @__PURE__ */ new Map();
|
|
1481
|
-
generateId() {
|
|
1482
|
-
return `term_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
1483
|
-
}
|
|
1484
|
-
getShell() {
|
|
1485
|
-
return getPlatformShell().command;
|
|
1486
|
-
}
|
|
1487
|
-
resolveWorkdir(workdir) {
|
|
1488
|
-
if (workdir && workdir.length > 0) {
|
|
1489
|
-
return workdir;
|
|
1490
|
-
}
|
|
1491
|
-
return os.homedir();
|
|
1492
|
-
}
|
|
1493
|
-
create(workdir, projectId) {
|
|
1494
|
-
const id = this.generateId();
|
|
1495
|
-
const shell = this.getShell();
|
|
1496
|
-
const cwd = this.resolveWorkdir(workdir);
|
|
1497
|
-
const ptyProcess = pty.spawn(shell, [], {
|
|
1498
|
-
name: "xterm-256color",
|
|
1499
|
-
cols: 80,
|
|
1500
|
-
rows: 24,
|
|
1501
|
-
cwd,
|
|
1502
|
-
env: process.env
|
|
1503
|
-
});
|
|
1504
|
-
const session = {
|
|
1505
|
-
id,
|
|
1506
|
-
pty: ptyProcess,
|
|
1507
|
-
workdir: cwd,
|
|
1508
|
-
projectId: projectId ?? ""
|
|
1509
|
-
};
|
|
1510
|
-
ptyProcess.onData((data) => {
|
|
1511
|
-
const output = { sessionId: id, data };
|
|
1512
|
-
for (const cb of this.outputCallbacks) {
|
|
1513
|
-
cb(output);
|
|
1514
|
-
}
|
|
1515
|
-
const history = this.outputHistory.get(id) || [];
|
|
1516
|
-
history.push(data);
|
|
1517
|
-
if (history.length > 1e4) history.shift();
|
|
1518
|
-
this.outputHistory.set(id, history);
|
|
1519
|
-
});
|
|
1520
|
-
ptyProcess.onExit(({ exitCode }) => {
|
|
1521
|
-
logger.info("Terminal session exited", { id, exitCode });
|
|
1522
|
-
this.sessions.delete(id);
|
|
1523
|
-
this.outputHistory.delete(id);
|
|
1524
|
-
for (const cb of this.exitCallbacks) {
|
|
1525
|
-
cb(id, exitCode);
|
|
1526
|
-
}
|
|
1527
|
-
});
|
|
1528
|
-
this.sessions.set(id, session);
|
|
1529
|
-
logger.info("Terminal session created", { id, shell, cwd });
|
|
1530
|
-
return session;
|
|
1531
|
-
}
|
|
1532
|
-
write(sessionId, data) {
|
|
1533
|
-
const session = this.sessions.get(sessionId);
|
|
1534
|
-
if (!session) {
|
|
1535
|
-
return false;
|
|
1536
|
-
}
|
|
1537
|
-
try {
|
|
1538
|
-
session.pty.write(data);
|
|
1539
|
-
return true;
|
|
1540
|
-
} catch {
|
|
1541
|
-
return false;
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
resize(sessionId, cols, rows) {
|
|
1545
|
-
const session = this.sessions.get(sessionId);
|
|
1546
|
-
if (!session) {
|
|
1547
|
-
return false;
|
|
1548
|
-
}
|
|
1549
|
-
try {
|
|
1550
|
-
session.pty.resize(cols, rows);
|
|
1551
|
-
return true;
|
|
1552
|
-
} catch {
|
|
1553
|
-
return false;
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
kill(sessionId) {
|
|
1557
|
-
const session = this.sessions.get(sessionId);
|
|
1558
|
-
if (!session) {
|
|
1559
|
-
return false;
|
|
1560
|
-
}
|
|
1561
|
-
session.pty.kill();
|
|
1562
|
-
this.sessions.delete(sessionId);
|
|
1563
|
-
logger.info("Terminal session killed", { id: sessionId });
|
|
1564
|
-
return true;
|
|
1565
|
-
}
|
|
1566
|
-
get(sessionId) {
|
|
1567
|
-
return this.sessions.get(sessionId);
|
|
1568
|
-
}
|
|
1569
|
-
getAll() {
|
|
1570
|
-
return Array.from(this.sessions.values());
|
|
1571
|
-
}
|
|
1572
|
-
getByProject(projectId) {
|
|
1573
|
-
if (!projectId) return [];
|
|
1574
|
-
return Array.from(this.sessions.values()).filter((s) => s.projectId === projectId);
|
|
1575
|
-
}
|
|
1576
|
-
getOutputHistory(sessionId) {
|
|
1577
|
-
return (this.outputHistory.get(sessionId) || []).join("");
|
|
1578
|
-
}
|
|
1579
|
-
onOutput(callback) {
|
|
1580
|
-
this.outputCallbacks.add(callback);
|
|
1581
|
-
return () => {
|
|
1582
|
-
this.outputCallbacks.delete(callback);
|
|
1583
|
-
};
|
|
1584
|
-
}
|
|
1585
|
-
onExit(callback) {
|
|
1586
|
-
this.exitCallbacks.add(callback);
|
|
1587
|
-
return () => {
|
|
1588
|
-
this.exitCallbacks.delete(callback);
|
|
1589
|
-
};
|
|
1590
|
-
}
|
|
1591
|
-
killAll() {
|
|
1592
|
-
for (const session of this.sessions.values()) {
|
|
1593
|
-
session.pty.kill();
|
|
1594
|
-
}
|
|
1595
|
-
this.sessions.clear();
|
|
1596
|
-
logger.info("All terminal sessions killed");
|
|
1597
|
-
}
|
|
1598
|
-
};
|
|
1599
|
-
var terminalManager = new TerminalManager();
|
|
1600
|
-
|
|
1601
|
-
// src/server/ws/terminal.ts
|
|
1602
|
-
var subscriptions = /* @__PURE__ */ new Set();
|
|
1603
|
-
var outputHandlerRegistered = false;
|
|
1604
|
-
var exitHandlerRegistered = false;
|
|
1605
|
-
function registerOutputHandler() {
|
|
1606
|
-
if (outputHandlerRegistered) return;
|
|
1607
|
-
outputHandlerRegistered = true;
|
|
1608
|
-
terminalManager.onOutput((output) => {
|
|
1609
|
-
const subs = Array.from(subscriptions).filter(
|
|
1610
|
-
(s) => s.sessionId === output.sessionId && s.ws.readyState === WebSocket.OPEN
|
|
1611
|
-
);
|
|
1612
|
-
for (const sub of subs) {
|
|
1613
|
-
sub.ws.send(
|
|
1614
|
-
JSON.stringify({
|
|
1615
|
-
type: "terminal.output",
|
|
1616
|
-
payload: { sessionId: output.sessionId, data: output.data }
|
|
1617
|
-
})
|
|
1618
|
-
);
|
|
1619
|
-
}
|
|
1620
|
-
});
|
|
1621
|
-
}
|
|
1622
|
-
function registerExitHandler() {
|
|
1623
|
-
if (exitHandlerRegistered) return;
|
|
1624
|
-
exitHandlerRegistered = true;
|
|
1625
|
-
terminalManager.onExit((sessionId, exitCode) => {
|
|
1626
|
-
const subs = Array.from(subscriptions).filter(
|
|
1627
|
-
(s) => s.sessionId === sessionId && s.ws.readyState === WebSocket.OPEN
|
|
1628
|
-
);
|
|
1629
|
-
for (const sub of subs) {
|
|
1630
|
-
sub.ws.send(
|
|
1631
|
-
JSON.stringify({
|
|
1632
|
-
type: "terminal.exit",
|
|
1633
|
-
payload: { sessionId, exitCode }
|
|
1634
|
-
})
|
|
1635
|
-
);
|
|
1636
|
-
}
|
|
1637
|
-
for (const sub of subscriptions) {
|
|
1638
|
-
if (sub.sessionId === sessionId) {
|
|
1639
|
-
subscriptions.delete(sub);
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
});
|
|
1447
|
+
// src/server/mcp/manager.ts
|
|
1448
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1449
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1450
|
+
function estimateToolTokens(toolName, description, inputSchema) {
|
|
1451
|
+
const def = {
|
|
1452
|
+
type: "function",
|
|
1453
|
+
function: { name: toolName, description: description ?? "", parameters: inputSchema }
|
|
1454
|
+
};
|
|
1455
|
+
return Math.ceil(JSON.stringify(def).length / 4);
|
|
1643
1456
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
(sub) => sub.ws === ws && sub.sessionId === message.payload.sessionId
|
|
1650
|
-
);
|
|
1651
|
-
if (!exists) {
|
|
1652
|
-
subscriptions.add({ ws, sessionId: message.payload.sessionId });
|
|
1653
|
-
}
|
|
1654
|
-
registerOutputHandler();
|
|
1655
|
-
registerExitHandler();
|
|
1656
|
-
const sessionId = message.payload.sessionId;
|
|
1657
|
-
const history = terminalManager.getOutputHistory(sessionId);
|
|
1658
|
-
if (history) {
|
|
1659
|
-
setTimeout(() => {
|
|
1660
|
-
ws.send(
|
|
1661
|
-
JSON.stringify({
|
|
1662
|
-
type: "terminal.output",
|
|
1663
|
-
payload: { sessionId, data: history }
|
|
1664
|
-
})
|
|
1665
|
-
);
|
|
1666
|
-
}, 100);
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
break;
|
|
1670
|
-
}
|
|
1671
|
-
case "terminal.write": {
|
|
1672
|
-
if (message.payload.sessionId && message.payload.data) {
|
|
1673
|
-
terminalManager.write(message.payload.sessionId, message.payload.data);
|
|
1674
|
-
}
|
|
1675
|
-
break;
|
|
1676
|
-
}
|
|
1677
|
-
case "terminal.resize": {
|
|
1678
|
-
if (message.payload.sessionId && typeof message.payload.cols === "number" && typeof message.payload.rows === "number") {
|
|
1679
|
-
terminalManager.resize(message.payload.sessionId, message.payload.cols, message.payload.rows);
|
|
1680
|
-
}
|
|
1681
|
-
break;
|
|
1682
|
-
}
|
|
1683
|
-
case "terminal.kill": {
|
|
1684
|
-
if (message.payload.sessionId) {
|
|
1685
|
-
terminalManager.kill(message.payload.sessionId);
|
|
1686
|
-
for (const sub of subscriptions) {
|
|
1687
|
-
if (sub.sessionId === message.payload.sessionId) {
|
|
1688
|
-
subscriptions.delete(sub);
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
ws.send(
|
|
1692
|
-
JSON.stringify({
|
|
1693
|
-
type: "terminal.killed",
|
|
1694
|
-
payload: { sessionId: message.payload.sessionId }
|
|
1695
|
-
})
|
|
1696
|
-
);
|
|
1697
|
-
}
|
|
1698
|
-
break;
|
|
1699
|
-
}
|
|
1457
|
+
var McpManager = class {
|
|
1458
|
+
servers = /* @__PURE__ */ new Map();
|
|
1459
|
+
onServersChanged;
|
|
1460
|
+
constructor(options) {
|
|
1461
|
+
this.onServersChanged = options?.onServersChanged;
|
|
1700
1462
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
if (sub.ws === ws) {
|
|
1705
|
-
subscriptions.delete(sub);
|
|
1463
|
+
async addServer(name, config4) {
|
|
1464
|
+
if (this.servers.has(name)) {
|
|
1465
|
+
throw new Error(`MCP server '${name}' already exists`);
|
|
1706
1466
|
}
|
|
1467
|
+
const state = { name, config: config4, status: "disconnected", tools: [], estimatedTokens: 0 };
|
|
1468
|
+
this.servers.set(name, { config: config4, client: void 0, transport: null, state });
|
|
1469
|
+
await this.connectServer(name);
|
|
1707
1470
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
import { fileURLToPath } from "url";
|
|
1715
|
-
var __bundleDir = dirname(fileURLToPath(import.meta.url));
|
|
1716
|
-
var DEFAULTS_DIR = join(__bundleDir, "defaults");
|
|
1717
|
-
var DEFAULTS_DIR_ALT = join(__bundleDir, "workflow-defaults");
|
|
1718
|
-
var WORKFLOW_EXTENSION = ".workflow.json";
|
|
1719
|
-
function getWorkflowsDir(configDir) {
|
|
1720
|
-
return join(configDir, "workflows");
|
|
1721
|
-
}
|
|
1722
|
-
function getProjectWorkflowsDir(projectDir) {
|
|
1723
|
-
return join(projectDir, ".openfox", "workflows");
|
|
1724
|
-
}
|
|
1725
|
-
async function pathExists2(path) {
|
|
1726
|
-
try {
|
|
1727
|
-
await access(path, constants.R_OK);
|
|
1728
|
-
return true;
|
|
1729
|
-
} catch {
|
|
1730
|
-
return false;
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
async function loadWorkflowsFromDir(dir) {
|
|
1734
|
-
if (!await pathExists2(dir)) {
|
|
1735
|
-
return [];
|
|
1736
|
-
}
|
|
1737
|
-
let files;
|
|
1738
|
-
try {
|
|
1739
|
-
files = (await readdir(dir)).filter((f) => f.endsWith(WORKFLOW_EXTENSION));
|
|
1740
|
-
} catch {
|
|
1741
|
-
return [];
|
|
1471
|
+
removeServer(name) {
|
|
1472
|
+
const entry = this.servers.get(name);
|
|
1473
|
+
if (entry) {
|
|
1474
|
+
this.disconnectServer(name);
|
|
1475
|
+
this.servers.delete(name);
|
|
1476
|
+
}
|
|
1742
1477
|
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1478
|
+
async connectServer(name) {
|
|
1479
|
+
const entry = this.servers.get(name);
|
|
1480
|
+
if (!entry) return;
|
|
1745
1481
|
try {
|
|
1746
|
-
|
|
1747
|
-
const
|
|
1748
|
-
|
|
1749
|
-
|
|
1482
|
+
await this.disconnectServer(name);
|
|
1483
|
+
const client = new Client({ name: "openfox-mcp", version: "2.0.0" });
|
|
1484
|
+
let transport = null;
|
|
1485
|
+
if (entry.config.transport === "stdio") {
|
|
1486
|
+
if (!entry.config.command) throw new Error("command is required for stdio transport");
|
|
1487
|
+
transport = new StdioClientTransport({
|
|
1488
|
+
command: entry.config.command,
|
|
1489
|
+
...entry.config.args ? { args: entry.config.args } : {},
|
|
1490
|
+
...entry.config.env ? { env: entry.config.env } : {},
|
|
1491
|
+
stderr: "pipe"
|
|
1492
|
+
});
|
|
1493
|
+
await client.connect(transport);
|
|
1750
1494
|
} else {
|
|
1751
|
-
|
|
1495
|
+
throw new Error("Only stdio transport is supported in this version");
|
|
1752
1496
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
workflowMap.set(workflow.metadata.id, workflow);
|
|
1777
|
-
}
|
|
1778
|
-
for (const workflow of userWorkflows) {
|
|
1779
|
-
workflowMap.set(workflow.metadata.id, workflow);
|
|
1780
|
-
}
|
|
1781
|
-
if (projectDir) {
|
|
1782
|
-
const projectWorkflows = await loadProjectWorkflows(projectDir);
|
|
1783
|
-
for (const workflow of projectWorkflows) {
|
|
1784
|
-
workflowMap.set(workflow.metadata.id, workflow);
|
|
1497
|
+
const { tools: mcpTools } = await client.listTools();
|
|
1498
|
+
const disabledSet = new Set(entry.config.disabledTools ?? []);
|
|
1499
|
+
const tools = mcpTools.map((t) => {
|
|
1500
|
+
const inputSchema = t.inputSchema;
|
|
1501
|
+
return {
|
|
1502
|
+
name: t.name,
|
|
1503
|
+
description: t.description ?? "",
|
|
1504
|
+
inputSchema,
|
|
1505
|
+
enabled: !disabledSet.has(t.name),
|
|
1506
|
+
estimatedTokens: estimateToolTokens(t.name, t.description, inputSchema)
|
|
1507
|
+
};
|
|
1508
|
+
});
|
|
1509
|
+
const totalTokens = tools.filter((t) => t.enabled).reduce((sum, t) => sum + t.estimatedTokens, 0);
|
|
1510
|
+
entry.client = client;
|
|
1511
|
+
entry.transport = transport;
|
|
1512
|
+
entry.state = { name, config: entry.config, status: "connected", tools, estimatedTokens: totalTokens };
|
|
1513
|
+
logger.info("Connected to MCP server", { name, toolCount: tools.length });
|
|
1514
|
+
this.onServersChanged?.();
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1517
|
+
logger.error("Failed to connect MCP server", { name, error: msg });
|
|
1518
|
+
entry.state = { name, config: entry.config, status: "error", tools: [], estimatedTokens: 0, error: msg };
|
|
1519
|
+
this.onServersChanged?.();
|
|
1785
1520
|
}
|
|
1786
1521
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
await saveItemToDir(getProjectWorkflowsDir(projectDir), workflow, WORKFLOW_EXTENSION, jsonSerializer);
|
|
1791
|
-
}
|
|
1792
|
-
async function deleteProjectWorkflow(projectDir, workflowId) {
|
|
1793
|
-
return deleteItemFromDir(getProjectWorkflowsDir(projectDir), workflowId, WORKFLOW_EXTENSION);
|
|
1794
|
-
}
|
|
1795
|
-
async function getDefaultWorkflowIds() {
|
|
1796
|
-
for (const dir of [DEFAULTS_DIR, DEFAULTS_DIR_ALT]) {
|
|
1522
|
+
async disconnectServer(name) {
|
|
1523
|
+
const entry = this.servers.get(name);
|
|
1524
|
+
if (!entry) return;
|
|
1797
1525
|
try {
|
|
1798
|
-
|
|
1799
|
-
return files.map((f) => f.replace(WORKFLOW_EXTENSION, ""));
|
|
1526
|
+
await entry.client?.close();
|
|
1800
1527
|
} catch {
|
|
1801
1528
|
}
|
|
1529
|
+
entry.client = void 0;
|
|
1530
|
+
entry.transport = null;
|
|
1531
|
+
entry.state = { name, config: entry.config, status: "disconnected", tools: [], estimatedTokens: 0 };
|
|
1802
1532
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
const defaultIds = await getDefaultWorkflowIds();
|
|
1807
|
-
return defaultIds.includes(workflowId);
|
|
1808
|
-
}
|
|
1809
|
-
function findWorkflowById(workflowId, workflows) {
|
|
1810
|
-
return workflows.find((p) => p.metadata.id === workflowId);
|
|
1811
|
-
}
|
|
1812
|
-
async function workflowExists(configDir, workflowId, projectDir) {
|
|
1813
|
-
if (await pathExists2(join(getWorkflowsDir(configDir), `${workflowId}${WORKFLOW_EXTENSION}`))) return true;
|
|
1814
|
-
if (projectDir && await pathExists2(join(getProjectWorkflowsDir(projectDir), `${workflowId}${WORKFLOW_EXTENSION}`)))
|
|
1815
|
-
return true;
|
|
1816
|
-
return false;
|
|
1817
|
-
}
|
|
1818
|
-
async function saveWorkflow(configDir, workflow) {
|
|
1819
|
-
const workflowsDir = getWorkflowsDir(configDir);
|
|
1820
|
-
if (!await pathExists2(workflowsDir)) {
|
|
1821
|
-
await mkdir(workflowsDir, { recursive: true });
|
|
1822
|
-
}
|
|
1823
|
-
const filePath = join(workflowsDir, `${workflow.metadata.id}${WORKFLOW_EXTENSION}`);
|
|
1824
|
-
await writeFile(filePath, JSON.stringify(workflow, null, 2) + "\n", "utf-8");
|
|
1825
|
-
}
|
|
1826
|
-
async function deleteWorkflow(configDir, workflowId) {
|
|
1827
|
-
const isDefault = await isDefaultWorkflow(workflowId);
|
|
1828
|
-
if (isDefault) {
|
|
1829
|
-
return { success: false, reason: "Cannot delete built-in defaults" };
|
|
1830
|
-
}
|
|
1831
|
-
const filePath = join(getWorkflowsDir(configDir), `${workflowId}${WORKFLOW_EXTENSION}`);
|
|
1832
|
-
try {
|
|
1833
|
-
await unlink(filePath);
|
|
1834
|
-
return { success: true };
|
|
1835
|
-
} catch {
|
|
1836
|
-
return { success: false };
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
// src/server/workflows/types.ts
|
|
1841
|
-
var TERMINAL_DONE = "$done";
|
|
1842
|
-
var TERMINAL_BLOCKED = "$blocked";
|
|
1843
|
-
|
|
1844
|
-
// src/server/git/diff.ts
|
|
1845
|
-
import { spawn as spawn2 } from "child_process";
|
|
1846
|
-
function getGitDiffFiles(cwd) {
|
|
1847
|
-
return new Promise((resolve3) => {
|
|
1848
|
-
const diffProc = spawn2("git", ["diff", "--name-status", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
1849
|
-
const statusProc = spawn2("git", ["status", "--porcelain"], { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
1850
|
-
let diffStdout = "";
|
|
1851
|
-
let statusStdout = "";
|
|
1852
|
-
let diffExited = false;
|
|
1853
|
-
let statusExited = false;
|
|
1854
|
-
let diffCode = null;
|
|
1855
|
-
let statusCode = null;
|
|
1856
|
-
const collect = () => {
|
|
1857
|
-
if (!diffExited || !statusExited) return;
|
|
1858
|
-
const files = [];
|
|
1859
|
-
if (diffCode === 0) {
|
|
1860
|
-
for (const line of diffStdout.split("\n")) {
|
|
1861
|
-
if (!line.trim()) continue;
|
|
1862
|
-
const [statusChar, ...pathParts] = line.split(" ");
|
|
1863
|
-
const path = pathParts.join(" ") || statusChar || "";
|
|
1864
|
-
if (!path) continue;
|
|
1865
|
-
const status = statusChar === "A" ? "added" : statusChar === "D" ? "deleted" : "modified";
|
|
1866
|
-
files.push({ path, status, additions: 0, deletions: 0 });
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
if (statusCode === 0) {
|
|
1870
|
-
for (const line of statusStdout.split("\n")) {
|
|
1871
|
-
if (!line.startsWith("?? ")) continue;
|
|
1872
|
-
const path = line.slice(3).trim();
|
|
1873
|
-
if (!path) continue;
|
|
1874
|
-
files.push({ path, status: "added", additions: 0, deletions: 0 });
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
resolve3(files);
|
|
1878
|
-
};
|
|
1879
|
-
diffProc.stdout.on("data", (data) => {
|
|
1880
|
-
diffStdout += data.toString();
|
|
1881
|
-
});
|
|
1882
|
-
statusProc.stdout.on("data", (data) => {
|
|
1883
|
-
statusStdout += data.toString();
|
|
1884
|
-
});
|
|
1885
|
-
diffProc.on("close", (code) => {
|
|
1886
|
-
diffExited = true;
|
|
1887
|
-
diffCode = code;
|
|
1888
|
-
collect();
|
|
1889
|
-
});
|
|
1890
|
-
statusProc.on("close", (code) => {
|
|
1891
|
-
statusExited = true;
|
|
1892
|
-
statusCode = code;
|
|
1893
|
-
collect();
|
|
1894
|
-
});
|
|
1895
|
-
diffProc.on("error", () => {
|
|
1896
|
-
diffExited = true;
|
|
1897
|
-
diffCode = 1;
|
|
1898
|
-
collect();
|
|
1899
|
-
});
|
|
1900
|
-
statusProc.on("error", () => {
|
|
1901
|
-
statusExited = true;
|
|
1902
|
-
statusCode = 1;
|
|
1903
|
-
collect();
|
|
1904
|
-
});
|
|
1905
|
-
});
|
|
1906
|
-
}
|
|
1907
|
-
async function formatGitDiffFiles(cwd) {
|
|
1908
|
-
const files = await getGitDiffFiles(cwd);
|
|
1909
|
-
if (files.length === 0) return "(none)";
|
|
1910
|
-
return files.map((f) => `- ${f.path} (${f.status})`).join("\n");
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
// src/server/workflows/shell.ts
|
|
1914
|
-
function executeShellCommand(command, cwd, timeout, signal) {
|
|
1915
|
-
return new Promise((resolve3, reject) => {
|
|
1916
|
-
if (checkAborted(signal)) {
|
|
1917
|
-
reject(new Error("Aborted"));
|
|
1918
|
-
return;
|
|
1919
|
-
}
|
|
1920
|
-
const proc = spawnShellProcess(command, cwd, signal);
|
|
1921
|
-
let stdout = "";
|
|
1922
|
-
let stderr = "";
|
|
1923
|
-
let killed = false;
|
|
1924
|
-
const timer = setTimeout(() => {
|
|
1925
|
-
killed = true;
|
|
1926
|
-
proc.kill("SIGTERM");
|
|
1927
|
-
resolve3({ exitCode: 1, stdout, stderr: stderr + "\nCommand timed out", success: false });
|
|
1928
|
-
}, timeout);
|
|
1929
|
-
const onAbort = () => {
|
|
1930
|
-
if (!killed) {
|
|
1931
|
-
killed = true;
|
|
1932
|
-
proc.kill("SIGTERM");
|
|
1933
|
-
clearTimeout(timer);
|
|
1934
|
-
reject(new Error("Aborted"));
|
|
1935
|
-
}
|
|
1936
|
-
};
|
|
1937
|
-
signal?.addEventListener("abort", onAbort);
|
|
1938
|
-
proc.stdout?.on("data", (data) => {
|
|
1939
|
-
stdout += data.toString();
|
|
1940
|
-
});
|
|
1941
|
-
proc.stderr?.on("data", (data) => {
|
|
1942
|
-
stderr += data.toString();
|
|
1943
|
-
});
|
|
1944
|
-
proc.on("close", (code) => {
|
|
1945
|
-
clearTimeout(timer);
|
|
1946
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1947
|
-
if (killed) return;
|
|
1948
|
-
const exitCode = code ?? 1;
|
|
1949
|
-
resolve3({ exitCode, stdout, stderr, success: exitCode === 0 });
|
|
1950
|
-
});
|
|
1951
|
-
proc.on("error", (err) => {
|
|
1952
|
-
clearTimeout(timer);
|
|
1953
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1954
|
-
if (killed) return;
|
|
1955
|
-
resolve3({ exitCode: 1, stdout, stderr: err.message, success: false });
|
|
1956
|
-
});
|
|
1957
|
-
});
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
|
-
// src/server/workflows/executor.ts
|
|
1961
|
-
var TEMPLATE_VARIABLES = [
|
|
1962
|
-
{ name: "workdir", description: "Working directory of the session" },
|
|
1963
|
-
{ name: "reason", description: 'Human-readable reason (e.g. "2 criteria remaining")' },
|
|
1964
|
-
{
|
|
1965
|
-
name: "stepOutput",
|
|
1966
|
-
description: "Structured output from the previous step (content, stdout, stderr, exitCode, etc.)"
|
|
1967
|
-
},
|
|
1968
|
-
{
|
|
1969
|
-
name: "verifierFindings",
|
|
1970
|
-
description: "@deprecated Use stepOutput.content instead. Output from the last sub-agent step"
|
|
1971
|
-
},
|
|
1972
|
-
{
|
|
1973
|
-
name: "previousStepOutput",
|
|
1974
|
-
description: "@deprecated Use stepOutput.stdout instead. Output from the last shell step"
|
|
1975
|
-
},
|
|
1976
|
-
{ name: "criteriaCount", description: "Total number of criteria" },
|
|
1977
|
-
{ name: "pendingCount", description: "Number of pending/failed criteria" },
|
|
1978
|
-
{ name: "criteriaList", description: "Formatted list of all criteria with status" },
|
|
1979
|
-
{ name: "modifiedFiles", description: "List of modified files" }
|
|
1980
|
-
];
|
|
1981
|
-
function formatCriteriaList(entries) {
|
|
1982
|
-
if (entries.length === 0) return "(none)";
|
|
1983
|
-
return entries.map((e) => {
|
|
1984
|
-
const status = e.status === "passed" ? "[PASSED]" : e.status === "completed" ? "[NEEDS VERIFICATION]" : e.status === "failed" ? "[FAILED]" : "[NOT COMPLETED]";
|
|
1985
|
-
return `- **${e.id}** ${status}: ${e.description}`;
|
|
1986
|
-
}).join("\n");
|
|
1987
|
-
}
|
|
1988
|
-
async function formatModifiedFiles(session) {
|
|
1989
|
-
return formatGitDiffFiles(session.workdir);
|
|
1990
|
-
}
|
|
1991
|
-
function resolveTemplate(template, ctx) {
|
|
1992
|
-
let result = template;
|
|
1993
|
-
for (const { name } of TEMPLATE_VARIABLES) {
|
|
1994
|
-
if (name === "stepOutput" || name === "verifierFindings" || name === "previousStepOutput") continue;
|
|
1995
|
-
const value = String(ctx[name]);
|
|
1996
|
-
result = result.replace(new RegExp(`\\{\\{${name}\\}\\}`, "g"), value);
|
|
1997
|
-
}
|
|
1998
|
-
result = result.replace(/\{\{stepOutput\.(\w+)\}\}/g, (_, key) => ctx.stepOutput[key] ?? "");
|
|
1999
|
-
result = result.replace(/\{\{verifierFindings\}\}/g, ctx.stepOutput["content"] ?? "");
|
|
2000
|
-
result = result.replace(/\{\{previousStepOutput\}\}/g, ctx.stepOutput["stdout"] ?? "");
|
|
2001
|
-
return result;
|
|
2002
|
-
}
|
|
2003
|
-
function evaluateCondition(condition, stepOutcome, metadataEntries) {
|
|
2004
|
-
switch (condition.type) {
|
|
2005
|
-
case "step_result":
|
|
2006
|
-
if (!stepOutcome) return false;
|
|
2007
|
-
return stepOutcome.result === condition.result;
|
|
2008
|
-
case "metadata_all_match": {
|
|
2009
|
-
if (!metadataEntries) return false;
|
|
2010
|
-
const entries = metadataEntries[condition.key];
|
|
2011
|
-
if (!entries || entries.length === 0) return true;
|
|
2012
|
-
return entries.every((e) => e[condition.field] === condition.value);
|
|
2013
|
-
}
|
|
2014
|
-
case "metadata_all_in": {
|
|
2015
|
-
if (!metadataEntries) return false;
|
|
2016
|
-
const entries = metadataEntries[condition.key];
|
|
2017
|
-
if (!entries || entries.length === 0) return true;
|
|
2018
|
-
return entries.every((e) => condition.values.includes(e[condition.field]));
|
|
2019
|
-
}
|
|
2020
|
-
case "always":
|
|
2021
|
-
return true;
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
function evaluateTransitions(transitions, stepOutcome, metadataEntries) {
|
|
2025
|
-
for (const transition of transitions) {
|
|
2026
|
-
if (evaluateCondition(transition.when, stepOutcome, metadataEntries)) {
|
|
2027
|
-
return transition.goto;
|
|
1533
|
+
async disconnectAll() {
|
|
1534
|
+
for (const name of this.servers.keys()) {
|
|
1535
|
+
await this.disconnectServer(name);
|
|
2028
1536
|
}
|
|
2029
1537
|
}
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
function emitWorkflowMessage(eventStore, sessionId, content, windowOptions, onMessage) {
|
|
2033
|
-
const msgId = crypto.randomUUID();
|
|
2034
|
-
eventStore.append(
|
|
2035
|
-
sessionId,
|
|
2036
|
-
createMessageStartEvent(msgId, "user", content, {
|
|
2037
|
-
...windowOptions ?? {},
|
|
2038
|
-
isSystemGenerated: true,
|
|
2039
|
-
messageKind: "correction",
|
|
2040
|
-
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
2041
|
-
})
|
|
2042
|
-
);
|
|
2043
|
-
eventStore.append(sessionId, { type: "message.done", data: { messageId: msgId } });
|
|
2044
|
-
if (onMessage) {
|
|
2045
|
-
onMessage(
|
|
2046
|
-
createChatMessageMessage({
|
|
2047
|
-
id: msgId,
|
|
2048
|
-
role: "user",
|
|
2049
|
-
content,
|
|
2050
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2051
|
-
isSystemGenerated: true,
|
|
2052
|
-
messageKind: "correction",
|
|
2053
|
-
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
2054
|
-
})
|
|
2055
|
-
);
|
|
1538
|
+
async reconnectServer(name) {
|
|
1539
|
+
await this.connectServer(name);
|
|
2056
1540
|
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
function getCurrentWindowMessageOptions(sessionId) {
|
|
2060
|
-
const contextWindowId = getCurrentContextWindowId(sessionId);
|
|
2061
|
-
return contextWindowId ? { contextWindowId } : void 0;
|
|
2062
|
-
}
|
|
2063
|
-
function buildReason(metadataEntries) {
|
|
2064
|
-
const entries = metadataEntries?.["criteria"] ?? [];
|
|
2065
|
-
const remaining = entries.filter((e) => e.status !== "passed");
|
|
2066
|
-
return `${remaining.length} criteria remaining`;
|
|
2067
|
-
}
|
|
2068
|
-
async function executeWorkflow(workflow, options, subGroup) {
|
|
2069
|
-
const { sessionManager, sessionId, llmClient, signal, onMessage } = options;
|
|
2070
|
-
const eventStore = getEventStore();
|
|
2071
|
-
const startTime = performance.now();
|
|
2072
|
-
let iterations = 0;
|
|
2073
|
-
const activeSteps = subGroup ? workflow.steps.filter((s) => s.subGroup === subGroup) : workflow.steps;
|
|
2074
|
-
let currentStepId = subGroup ? activeSteps[0]?.id ?? workflow.entryStep : workflow.entryStep;
|
|
2075
|
-
let lastStepOutput = {};
|
|
2076
|
-
const firstEntryForStep = /* @__PURE__ */ new Set();
|
|
2077
|
-
const messagesBeforeWorkflow = sessionManager.requireSession(sessionId).messages.length;
|
|
2078
|
-
const activeStepIds = new Set(activeSteps.map((s) => s.id));
|
|
2079
|
-
const stepsById = /* @__PURE__ */ new Map();
|
|
2080
|
-
for (const step of activeSteps) {
|
|
2081
|
-
stepsById.set(step.id, step);
|
|
2082
|
-
}
|
|
2083
|
-
logger.debug("Workflow executor starting", { sessionId, workflow: workflow.metadata.id });
|
|
2084
|
-
if (workflow.startCondition && workflow.startCondition.type !== "always") {
|
|
2085
|
-
const session = sessionManager.requireSession(sessionId);
|
|
2086
|
-
const conditionMet = evaluateCondition(
|
|
2087
|
-
workflow.startCondition,
|
|
2088
|
-
null,
|
|
2089
|
-
session.metadataEntries
|
|
2090
|
-
);
|
|
2091
|
-
if (!conditionMet) {
|
|
2092
|
-
logger.debug("Workflow start condition not met", { sessionId, condition: workflow.startCondition.type });
|
|
2093
|
-
return {
|
|
2094
|
-
finalAction: {
|
|
2095
|
-
type: "BLOCKED",
|
|
2096
|
-
reason: `Start condition not met: ${workflow.startCondition.type}`,
|
|
2097
|
-
blockedCriteria: []
|
|
2098
|
-
},
|
|
2099
|
-
iterations: 0,
|
|
2100
|
-
totalTime: (performance.now() - startTime) / 1e3
|
|
2101
|
-
};
|
|
2102
|
-
}
|
|
1541
|
+
getServer(name) {
|
|
1542
|
+
return this.servers.get(name)?.state;
|
|
2103
1543
|
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
eventStore.append(
|
|
2107
|
-
sessionId,
|
|
2108
|
-
createMessageStartEvent(
|
|
2109
|
-
startMsgId,
|
|
2110
|
-
"user",
|
|
2111
|
-
JSON.stringify({
|
|
2112
|
-
workflowName: workflow.metadata.name,
|
|
2113
|
-
workflowId: workflow.metadata.id,
|
|
2114
|
-
workflowColor: workflow.metadata.color
|
|
2115
|
-
}),
|
|
2116
|
-
{ ...startWindowOpts ?? {}, isSystemGenerated: true, messageKind: "workflow-started" }
|
|
2117
|
-
)
|
|
2118
|
-
);
|
|
2119
|
-
eventStore.append(sessionId, { type: "message.done", data: { messageId: startMsgId } });
|
|
2120
|
-
if (options.userMessage) {
|
|
2121
|
-
sessionManager.addMessage(sessionId, {
|
|
2122
|
-
role: "user",
|
|
2123
|
-
content: options.userMessage.content,
|
|
2124
|
-
...options.userMessage.attachments ? { attachments: options.userMessage.attachments } : {}
|
|
2125
|
-
});
|
|
1544
|
+
getAllServers() {
|
|
1545
|
+
return Array.from(this.servers.values()).map((e) => e.state);
|
|
2126
1546
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
if (!step) {
|
|
2139
|
-
logger.error("Workflow step not found", { sessionId, stepId: currentStepId });
|
|
2140
|
-
return {
|
|
2141
|
-
finalAction: { type: "BLOCKED", reason: `Step "${currentStepId}" not found in workflow`, blockedCriteria: [] },
|
|
2142
|
-
iterations,
|
|
2143
|
-
totalTime: (performance.now() - startTime) / 1e3
|
|
2144
|
-
};
|
|
2145
|
-
}
|
|
2146
|
-
const session = sessionManager.requireSession(sessionId);
|
|
2147
|
-
const currentWindowMessageOptions = getCurrentWindowMessageOptions(sessionId);
|
|
2148
|
-
const criteriaEntries = session.metadataEntries["criteria"] ?? [];
|
|
2149
|
-
const templateCtx = {
|
|
2150
|
-
workdir: session.workdir,
|
|
2151
|
-
reason: buildReason(session.metadataEntries),
|
|
2152
|
-
verifierFindings: lastStepOutput["content"] ?? "",
|
|
2153
|
-
previousStepOutput: lastStepOutput["stdout"] ?? "",
|
|
2154
|
-
criteriaCount: criteriaEntries.length,
|
|
2155
|
-
pendingCount: criteriaEntries.filter((e) => e.status !== "passed").length,
|
|
2156
|
-
criteriaList: formatCriteriaList(criteriaEntries),
|
|
2157
|
-
modifiedFiles: await formatModifiedFiles(session),
|
|
2158
|
-
stepOutput: lastStepOutput
|
|
2159
|
-
};
|
|
2160
|
-
sessionManager.setPhase(sessionId, step.phase);
|
|
2161
|
-
logger.debug("Workflow step executing", { sessionId, iteration: iterations, stepId: step.id, stepType: step.type });
|
|
2162
|
-
let stepOutcome = null;
|
|
2163
|
-
switch (step.type) {
|
|
2164
|
-
case "agent": {
|
|
2165
|
-
const agentStep = step;
|
|
2166
|
-
const STEP_DONE_PROMPT = "\n\nOnce you're done, call step_done()";
|
|
2167
|
-
const STEP_DONE_NUDGE = "You haven't called step_done(). If you haven't finished the task, continue and when you're finished call step_done()";
|
|
2168
|
-
let promptContent;
|
|
2169
|
-
let nudgeContent;
|
|
2170
|
-
if (!firstEntryForStep.has(step.id) && agentStep.prompt) {
|
|
2171
|
-
const resolvedPrompt = resolveTemplate(agentStep.prompt, templateCtx);
|
|
2172
|
-
promptContent = resolvedPrompt + STEP_DONE_PROMPT;
|
|
2173
|
-
const promptMsgId = crypto.randomUUID();
|
|
2174
|
-
const msgMetadata = { type: "workflow", name: "Workflow", color: "#f59e0b" };
|
|
2175
|
-
eventStore.append(
|
|
2176
|
-
sessionId,
|
|
2177
|
-
createMessageStartEvent(promptMsgId, "user", promptContent, {
|
|
2178
|
-
...currentWindowMessageOptions ?? {},
|
|
2179
|
-
isSystemGenerated: true,
|
|
2180
|
-
messageKind: "auto-prompt",
|
|
2181
|
-
metadata: msgMetadata
|
|
2182
|
-
})
|
|
2183
|
-
);
|
|
2184
|
-
eventStore.append(sessionId, { type: "message.done", data: { messageId: promptMsgId } });
|
|
2185
|
-
if (onMessage) {
|
|
2186
|
-
onMessage(
|
|
2187
|
-
createChatMessageMessage({
|
|
2188
|
-
id: promptMsgId,
|
|
2189
|
-
role: "user",
|
|
2190
|
-
content: promptContent,
|
|
2191
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2192
|
-
isSystemGenerated: true,
|
|
2193
|
-
messageKind: "auto-prompt",
|
|
2194
|
-
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
2195
|
-
})
|
|
2196
|
-
);
|
|
2197
|
-
}
|
|
2198
|
-
} else if (firstEntryForStep.has(step.id)) {
|
|
2199
|
-
const parts = [];
|
|
2200
|
-
if (agentStep.nudgePrompt) {
|
|
2201
|
-
const resolvedNudge = resolveTemplate(agentStep.nudgePrompt, templateCtx);
|
|
2202
|
-
parts.push(resolvedNudge);
|
|
2203
|
-
}
|
|
2204
|
-
parts.push(STEP_DONE_NUDGE);
|
|
2205
|
-
nudgeContent = parts.join("\n\n");
|
|
2206
|
-
emitWorkflowMessage(eventStore, sessionId, nudgeContent, currentWindowMessageOptions, onMessage);
|
|
2207
|
-
}
|
|
2208
|
-
const turnMetrics = new TurnMetrics();
|
|
2209
|
-
const es = getEventStore();
|
|
2210
|
-
const append = (event) => es.append(sessionId, event);
|
|
2211
|
-
let stepDoneCalled = false;
|
|
2212
|
-
const agentResult = await runAgentTurn(
|
|
2213
|
-
{
|
|
2214
|
-
sessionManager,
|
|
2215
|
-
sessionId,
|
|
2216
|
-
llmClient,
|
|
2217
|
-
...options.statsIdentity ? { statsIdentity: options.statsIdentity } : {},
|
|
2218
|
-
...signal ? { signal } : {},
|
|
2219
|
-
...onMessage ? { onMessage } : {}
|
|
2220
|
-
},
|
|
2221
|
-
turnMetrics,
|
|
2222
|
-
agentStep.agentId ?? "planner",
|
|
2223
|
-
append,
|
|
2224
|
-
{
|
|
2225
|
-
...!firstEntryForStep.has(step.id) && !agentStep.prompt && options.injectWorkflowKickoff === true ? { injectKickoff: () => injectWorkflowKickoffIfNeeded(sessionManager, sessionId, es) } : {},
|
|
2226
|
-
onToolExecuted: (toolCall, toolResult) => {
|
|
2227
|
-
if (toolCall.name === "step_done" && toolResult.success) {
|
|
2228
|
-
stepDoneCalled = true;
|
|
2229
|
-
}
|
|
2230
|
-
if (toolResult.success && ["write_file", "edit_file"].includes(toolCall.name)) {
|
|
2231
|
-
const path = toolCall.arguments["path"];
|
|
2232
|
-
sessionManager.addModifiedFile(sessionId, path);
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
1547
|
+
getToolDefinitions() {
|
|
1548
|
+
const defs = [];
|
|
1549
|
+
for (const [, entry] of this.servers) {
|
|
1550
|
+
for (const tool of entry.state.tools) {
|
|
1551
|
+
if (!tool.enabled) continue;
|
|
1552
|
+
defs.push({
|
|
1553
|
+
type: "function",
|
|
1554
|
+
function: {
|
|
1555
|
+
name: `${entry.state.name}_${tool.name}`,
|
|
1556
|
+
description: tool.description ?? "",
|
|
1557
|
+
parameters: tool.inputSchema
|
|
2235
1558
|
}
|
|
2236
|
-
);
|
|
2237
|
-
firstEntryForStep.add(step.id);
|
|
2238
|
-
const agentReturnValue = agentResult.returnValueResult ?? "completed";
|
|
2239
|
-
lastStepOutput = {
|
|
2240
|
-
...agentResult.returnValueContent ? { content: agentResult.returnValueContent } : {},
|
|
2241
|
-
...agentResult.returnValueResult ? { result: agentResult.returnValueResult } : {},
|
|
2242
|
-
stepDoneCalled: String(stepDoneCalled)
|
|
2243
|
-
};
|
|
2244
|
-
stepOutcome = { result: agentReturnValue, output: lastStepOutput };
|
|
2245
|
-
if (!stepDoneCalled) {
|
|
2246
|
-
logger.debug("step_done not called, looping agent step", {
|
|
2247
|
-
sessionId,
|
|
2248
|
-
stepId: step.id,
|
|
2249
|
-
iteration: iterations
|
|
2250
|
-
});
|
|
2251
|
-
continue;
|
|
2252
|
-
}
|
|
2253
|
-
break;
|
|
2254
|
-
}
|
|
2255
|
-
case "sub_agent": {
|
|
2256
|
-
const subStep = step;
|
|
2257
|
-
const turnMetrics = new TurnMetrics();
|
|
2258
|
-
const promptTemplate = subStep.prompt ?? "Perform your task.";
|
|
2259
|
-
const resolvedPrompt = resolveTemplate(promptTemplate, templateCtx);
|
|
2260
|
-
const allAgents = await loadAllAgentsDefault();
|
|
2261
|
-
const agentDef = findAgentById(subStep.subAgentType, allAgents);
|
|
2262
|
-
if (!agentDef) {
|
|
2263
|
-
logger.error("Sub-agent definition not found", { subAgentType: subStep.subAgentType });
|
|
2264
|
-
stepOutcome = { result: "error", output: {} };
|
|
2265
|
-
break;
|
|
2266
|
-
}
|
|
2267
|
-
const toolRegistry = getToolRegistryForAgent(agentDef);
|
|
2268
|
-
const filteredToolRegistry = {
|
|
2269
|
-
tools: toolRegistry.tools.filter((t) => t.name !== "step_done"),
|
|
2270
|
-
definitions: toolRegistry.definitions.filter((d) => d.type === "function" && d.function.name !== "step_done"),
|
|
2271
|
-
execute: toolRegistry.execute
|
|
2272
|
-
};
|
|
2273
|
-
const result = await executeSubAgent({
|
|
2274
|
-
subAgentType: subStep.subAgentType,
|
|
2275
|
-
prompt: resolvedPrompt,
|
|
2276
|
-
sessionManager,
|
|
2277
|
-
sessionId,
|
|
2278
|
-
llmClient,
|
|
2279
|
-
toolRegistry: filteredToolRegistry,
|
|
2280
|
-
turnMetrics,
|
|
2281
|
-
statsIdentity: options.statsIdentity ?? {
|
|
2282
|
-
providerId: "",
|
|
2283
|
-
providerName: "",
|
|
2284
|
-
backend: "unknown",
|
|
2285
|
-
model: llmClient.getModel()
|
|
2286
|
-
},
|
|
2287
|
-
...signal ? { signal } : {},
|
|
2288
|
-
...onMessage ? { onMessage } : {}
|
|
2289
1559
|
});
|
|
2290
|
-
lastStepOutput = { content: result.content ?? "", ...result.result ? { result: result.result } : {} };
|
|
2291
|
-
stepOutcome = { result: result.result ?? "success", output: lastStepOutput };
|
|
2292
|
-
break;
|
|
2293
|
-
}
|
|
2294
|
-
case "shell": {
|
|
2295
|
-
const shellStep = step;
|
|
2296
|
-
const command = resolveTemplate(shellStep.command, templateCtx);
|
|
2297
|
-
const timeout = shellStep.timeout ?? 6e4;
|
|
2298
|
-
const successCodes = shellStep.successExitCodes ?? [0];
|
|
2299
|
-
const shellMsgId = crypto.randomUUID();
|
|
2300
|
-
eventStore.append(
|
|
2301
|
-
sessionId,
|
|
2302
|
-
createMessageStartEvent(shellMsgId, "user", `Running: \`${command}\``, {
|
|
2303
|
-
...currentWindowMessageOptions ?? {},
|
|
2304
|
-
isSystemGenerated: true,
|
|
2305
|
-
messageKind: "auto-prompt",
|
|
2306
|
-
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
2307
|
-
})
|
|
2308
|
-
);
|
|
2309
|
-
if (onMessage) {
|
|
2310
|
-
onMessage(
|
|
2311
|
-
createChatMessageMessage({
|
|
2312
|
-
id: shellMsgId,
|
|
2313
|
-
role: "user",
|
|
2314
|
-
content: `Running: \`${command}\``,
|
|
2315
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2316
|
-
isSystemGenerated: true,
|
|
2317
|
-
messageKind: "auto-prompt",
|
|
2318
|
-
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
2319
|
-
})
|
|
2320
|
-
);
|
|
2321
|
-
}
|
|
2322
|
-
const result = await executeShellCommand(command, session.workdir, timeout, signal);
|
|
2323
|
-
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
2324
|
-
lastStepOutput = { stdout: result.stdout ?? "", stderr: result.stderr ?? "", exitCode: String(result.exitCode) };
|
|
2325
|
-
const outputContent = output ? `Exit code: ${result.exitCode}
|
|
2326
|
-
\`\`\`
|
|
2327
|
-
${output.slice(0, 1e4)}
|
|
2328
|
-
\`\`\`` : `Exit code: ${result.exitCode}`;
|
|
2329
|
-
eventStore.append(sessionId, { type: "message.done", data: { messageId: shellMsgId } });
|
|
2330
|
-
const outputMsgId = crypto.randomUUID();
|
|
2331
|
-
eventStore.append(
|
|
2332
|
-
sessionId,
|
|
2333
|
-
createMessageStartEvent(outputMsgId, "user", outputContent, {
|
|
2334
|
-
...currentWindowMessageOptions ?? {},
|
|
2335
|
-
isSystemGenerated: true,
|
|
2336
|
-
messageKind: "correction"
|
|
2337
|
-
})
|
|
2338
|
-
);
|
|
2339
|
-
eventStore.append(sessionId, { type: "message.done", data: { messageId: outputMsgId } });
|
|
2340
|
-
if (onMessage) {
|
|
2341
|
-
onMessage(
|
|
2342
|
-
createChatMessageMessage({
|
|
2343
|
-
id: outputMsgId,
|
|
2344
|
-
role: "user",
|
|
2345
|
-
content: outputContent,
|
|
2346
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2347
|
-
isSystemGenerated: true,
|
|
2348
|
-
messageKind: "correction"
|
|
2349
|
-
})
|
|
2350
|
-
);
|
|
2351
|
-
}
|
|
2352
|
-
stepOutcome = { result: successCodes.includes(result.exitCode) ? "success" : "failure", output: lastStepOutput };
|
|
2353
|
-
break;
|
|
2354
1560
|
}
|
|
2355
1561
|
}
|
|
2356
|
-
|
|
2357
|
-
const effectiveTransitions = subGroup ? step.transitions.filter((t) => !t.subGroup || t.subGroup === subGroup) : step.transitions;
|
|
2358
|
-
const rawNextStepId = evaluateTransitions(effectiveTransitions, stepOutcome, refreshedSession.metadataEntries);
|
|
2359
|
-
const nextStepId = subGroup && rawNextStepId !== TERMINAL_DONE && rawNextStepId !== TERMINAL_BLOCKED && !activeStepIds.has(rawNextStepId) ? TERMINAL_DONE : rawNextStepId;
|
|
2360
|
-
if (nextStepId === TERMINAL_DONE) {
|
|
2361
|
-
sessionManager.setPhase(sessionId, "done");
|
|
2362
|
-
const totalTimeSeconds = Math.round((performance.now() - startTime) / 100) / 10;
|
|
2363
|
-
const completedSession = sessionManager.requireSession(sessionId);
|
|
2364
|
-
const workflowMessages = completedSession.messages.slice(messagesBeforeWorkflow);
|
|
2365
|
-
const workflowStats = computeSessionStats(workflowMessages);
|
|
2366
|
-
const totalToolCalls = workflowMessages.reduce((sum, m) => sum + (m.toolCalls?.length ?? 0), 0);
|
|
2367
|
-
const taskCompletedData = {
|
|
2368
|
-
summary: null,
|
|
2369
|
-
iterations,
|
|
2370
|
-
totalTimeSeconds,
|
|
2371
|
-
totalToolCalls,
|
|
2372
|
-
totalTokensGenerated: workflowStats?.generationTokens ?? 0,
|
|
2373
|
-
avgGenerationSpeed: workflowStats?.avgGenerationSpeed ?? 0,
|
|
2374
|
-
responseCount: workflowStats?.responseCount ?? 0,
|
|
2375
|
-
llmCallCount: workflowStats?.llmCallCount ?? 0,
|
|
2376
|
-
criteria: [],
|
|
2377
|
-
workflowName: workflow.metadata.name,
|
|
2378
|
-
workflowId: workflow.metadata.id,
|
|
2379
|
-
...workflow.metadata.color ? { workflowColor: workflow.metadata.color } : {}
|
|
2380
|
-
};
|
|
2381
|
-
eventStore.append(sessionId, { type: "task.completed", data: taskCompletedData });
|
|
2382
|
-
const markerMsgId = crypto.randomUUID();
|
|
2383
|
-
eventStore.append(
|
|
2384
|
-
sessionId,
|
|
2385
|
-
createMessageStartEvent(markerMsgId, "user", JSON.stringify(taskCompletedData), {
|
|
2386
|
-
...currentWindowMessageOptions ?? {},
|
|
2387
|
-
isSystemGenerated: true,
|
|
2388
|
-
messageKind: "task-completed"
|
|
2389
|
-
})
|
|
2390
|
-
);
|
|
2391
|
-
eventStore.append(sessionId, { type: "message.done", data: { messageId: markerMsgId } });
|
|
2392
|
-
logger.debug("Workflow executor complete", { sessionId, iterations });
|
|
2393
|
-
const doneAction = { type: "DONE" };
|
|
2394
|
-
return {
|
|
2395
|
-
finalAction: doneAction,
|
|
2396
|
-
iterations,
|
|
2397
|
-
totalTime: totalTimeSeconds
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
if (nextStepId === TERMINAL_BLOCKED) {
|
|
2401
|
-
sessionManager.setPhase(sessionId, "blocked");
|
|
2402
|
-
const reason = "No matching transition";
|
|
2403
|
-
emitWorkflowMessage(eventStore, sessionId, `Runner blocked: ${reason}`, currentWindowMessageOptions, onMessage);
|
|
2404
|
-
logger.warn("Workflow executor blocked", { sessionId, iterations, reason });
|
|
2405
|
-
const blockedAction = { type: "BLOCKED", reason, blockedCriteria: [] };
|
|
2406
|
-
return {
|
|
2407
|
-
finalAction: blockedAction,
|
|
2408
|
-
iterations,
|
|
2409
|
-
totalTime: (performance.now() - startTime) / 1e3
|
|
2410
|
-
};
|
|
2411
|
-
}
|
|
2412
|
-
currentStepId = nextStepId;
|
|
2413
|
-
}
|
|
2414
|
-
logger.warn("Workflow executor max iterations reached", { sessionId, iterations });
|
|
2415
|
-
return {
|
|
2416
|
-
finalAction: {
|
|
2417
|
-
type: "BLOCKED",
|
|
2418
|
-
reason: `Max iterations (${workflow.settings.maxIterations}) reached`,
|
|
2419
|
-
blockedCriteria: []
|
|
2420
|
-
},
|
|
2421
|
-
iterations,
|
|
2422
|
-
totalTime: (performance.now() - startTime) / 1e3
|
|
2423
|
-
};
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
// src/server/runner/orchestrator.ts
|
|
2427
|
-
async function runOrchestrator(options) {
|
|
2428
|
-
const runtimeConfig = getRuntimeConfig();
|
|
2429
|
-
const workflowId = options.workflowId ?? runtimeConfig.activeWorkflowId ?? "default";
|
|
2430
|
-
const configDir = getGlobalConfigDir(runtimeConfig.mode ?? "production");
|
|
2431
|
-
const workflows = await loadAllWorkflows(configDir);
|
|
2432
|
-
const workflow = findWorkflowById(workflowId, workflows);
|
|
2433
|
-
if (!workflow) {
|
|
2434
|
-
throw new Error(`Workflow "${workflowId}" not found`);
|
|
2435
|
-
}
|
|
2436
|
-
logger.debug("Using workflow executor", {
|
|
2437
|
-
sessionId: options.sessionId,
|
|
2438
|
-
workflow: workflow.metadata.id,
|
|
2439
|
-
subGroup: options.subGroup
|
|
2440
|
-
});
|
|
2441
|
-
return executeWorkflow(workflow, options, options.subGroup);
|
|
2442
|
-
}
|
|
2443
|
-
|
|
2444
|
-
// src/server/auth.ts
|
|
2445
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
2446
|
-
import { join as join2, dirname as dirname2 } from "path";
|
|
2447
|
-
import { createHash, privateDecrypt, createPublicKey } from "crypto";
|
|
2448
|
-
function getAuthConfigPath() {
|
|
2449
|
-
const configDir = getRuntimeConfig();
|
|
2450
|
-
const mode = configDir.mode === "development" ? "development" : configDir.mode === "test" ? "test" : "production";
|
|
2451
|
-
if (mode === "test") {
|
|
2452
|
-
const cwd = process.cwd();
|
|
2453
|
-
const base = cwd.endsWith("/e2e") ? cwd : join2(cwd, "e2e");
|
|
2454
|
-
const testAuthPath = join2(base, ".openfox-test", "auth.json");
|
|
2455
|
-
return testAuthPath;
|
|
2456
|
-
}
|
|
2457
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] || "";
|
|
2458
|
-
const basePath = process.env["XDG_CONFIG_HOME"] || `${home}/.config`;
|
|
2459
|
-
const suffix = mode === "development" ? "-dev" : "";
|
|
2460
|
-
return `${basePath}/openfox${suffix}/auth.json`;
|
|
2461
|
-
}
|
|
2462
|
-
function getKeyPath() {
|
|
2463
|
-
const authPath = getAuthConfigPath();
|
|
2464
|
-
const dir = dirname2(authPath);
|
|
2465
|
-
return join2(dir, "auth.key");
|
|
2466
|
-
}
|
|
2467
|
-
var cachedAuth = null;
|
|
2468
|
-
var cachedPrivateKey = null;
|
|
2469
|
-
async function loadPrivateKey() {
|
|
2470
|
-
if (cachedPrivateKey) {
|
|
2471
|
-
return cachedPrivateKey;
|
|
2472
|
-
}
|
|
2473
|
-
const keyPath = getKeyPath();
|
|
2474
|
-
const keyDir = dirname2(keyPath);
|
|
2475
|
-
try {
|
|
2476
|
-
cachedPrivateKey = await readFile2(keyPath, "utf-8");
|
|
2477
|
-
return cachedPrivateKey;
|
|
2478
|
-
} catch {
|
|
2479
|
-
const { privateKey } = await import("crypto").then(
|
|
2480
|
-
(c) => c.generateKeyPairSync("rsa", {
|
|
2481
|
-
modulusLength: 2048,
|
|
2482
|
-
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
2483
|
-
publicKeyEncoding: { type: "spki", format: "pem" }
|
|
2484
|
-
})
|
|
2485
|
-
);
|
|
2486
|
-
await mkdir2(keyDir, { recursive: true });
|
|
2487
|
-
await writeFile2(keyPath, privateKey, { mode: 384 });
|
|
2488
|
-
cachedPrivateKey = privateKey;
|
|
2489
|
-
return privateKey;
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
async function loadServerAuthConfig() {
|
|
2493
|
-
const configDir = getRuntimeConfig();
|
|
2494
|
-
const isTestMode = configDir.mode === "test";
|
|
2495
|
-
if (!isTestMode) {
|
|
2496
|
-
if (cachedAuth) {
|
|
2497
|
-
return cachedAuth;
|
|
2498
|
-
}
|
|
2499
|
-
}
|
|
2500
|
-
try {
|
|
2501
|
-
const authPath = getAuthConfigPath();
|
|
2502
|
-
const data = await readFile2(authPath, "utf-8");
|
|
2503
|
-
const authConfig = JSON.parse(data);
|
|
2504
|
-
cachedAuth = authConfig;
|
|
2505
|
-
return authConfig;
|
|
2506
|
-
} catch {
|
|
2507
|
-
return null;
|
|
1562
|
+
return defs;
|
|
2508
1563
|
}
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
}
|
|
2513
|
-
function hashPassword(password) {
|
|
2514
|
-
return createHash("sha256").update(password).digest("hex");
|
|
2515
|
-
}
|
|
2516
|
-
function requiresAuth() {
|
|
2517
|
-
return cachedAuth?.strategy === "network";
|
|
2518
|
-
}
|
|
2519
|
-
function hasPassword() {
|
|
2520
|
-
return cachedAuth?.encryptedPassword != null && cachedAuth.encryptedPassword.length > 0;
|
|
2521
|
-
}
|
|
2522
|
-
async function verifyPassword(password) {
|
|
2523
|
-
const encryptedPassword = cachedAuth?.encryptedPassword;
|
|
2524
|
-
if (!encryptedPassword) return false;
|
|
2525
|
-
const privateKey = await loadPrivateKey();
|
|
2526
|
-
try {
|
|
2527
|
-
const decrypted = privateDecrypt({ key: privateKey, padding: 1 }, Buffer.from(encryptedPassword, "base64"));
|
|
2528
|
-
return decrypted.toString() === password;
|
|
2529
|
-
} catch {
|
|
2530
|
-
return false;
|
|
2531
|
-
}
|
|
2532
|
-
}
|
|
2533
|
-
async function tokenFromPassword(password) {
|
|
2534
|
-
const privateKey = await loadPrivateKey();
|
|
2535
|
-
const passwordHash = hashPassword(password);
|
|
2536
|
-
const sign = await import("crypto").then((c) => {
|
|
2537
|
-
const s = c.createSign("SHA256");
|
|
2538
|
-
s.update(passwordHash);
|
|
2539
|
-
s.end();
|
|
2540
|
-
return s.sign(privateKey, "base64");
|
|
2541
|
-
});
|
|
2542
|
-
return sign;
|
|
2543
|
-
}
|
|
2544
|
-
async function isValidToken(token) {
|
|
2545
|
-
if (!cachedAuth?.encryptedPassword) return false;
|
|
2546
|
-
const privateKey = await loadPrivateKey();
|
|
2547
|
-
try {
|
|
2548
|
-
const decrypted = privateDecrypt(
|
|
2549
|
-
{ key: privateKey, padding: 1 },
|
|
2550
|
-
Buffer.from(cachedAuth.encryptedPassword, "base64")
|
|
2551
|
-
);
|
|
2552
|
-
const storedPassword = decrypted.toString();
|
|
2553
|
-
const storedHash = hashPassword(storedPassword);
|
|
2554
|
-
const verify = await import("crypto").then((c) => {
|
|
2555
|
-
const v = c.createVerify("SHA256");
|
|
2556
|
-
v.update(storedHash);
|
|
2557
|
-
v.end();
|
|
2558
|
-
return v;
|
|
2559
|
-
});
|
|
2560
|
-
const publicKey = createPublicKey(privateKey).export({ type: "spki", format: "pem" });
|
|
2561
|
-
return verify.verify(publicKey, token, "base64");
|
|
2562
|
-
} catch {
|
|
2563
|
-
return false;
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
|
|
2567
|
-
// src/server/ws/server.ts
|
|
2568
|
-
function moduleGitBranch(cwd) {
|
|
2569
|
-
return new Promise((resolve3) => {
|
|
2570
|
-
const proc = spawn3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
2571
|
-
cwd,
|
|
2572
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2573
|
-
});
|
|
2574
|
-
let stdout = "";
|
|
2575
|
-
proc.stdout.on("data", (data) => {
|
|
2576
|
-
stdout += data.toString();
|
|
2577
|
-
});
|
|
2578
|
-
proc.on("close", (code) => {
|
|
2579
|
-
if (code === 0 && stdout.trim()) {
|
|
2580
|
-
resolve3(stdout.trim());
|
|
2581
|
-
} else {
|
|
2582
|
-
resolve3(null);
|
|
2583
|
-
}
|
|
2584
|
-
});
|
|
2585
|
-
proc.on("error", () => resolve3(null));
|
|
2586
|
-
});
|
|
2587
|
-
}
|
|
2588
|
-
function hashContent(content) {
|
|
2589
|
-
return createHash2("sha256").update(content).digest("hex");
|
|
2590
|
-
}
|
|
2591
|
-
function moduleGitDiff(cwd) {
|
|
2592
|
-
return new Promise((resolve3) => {
|
|
2593
|
-
const diffProc = spawn3("git", ["diff", "--name-status", "HEAD"], {
|
|
2594
|
-
cwd,
|
|
2595
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2596
|
-
});
|
|
2597
|
-
const statusProc = spawn3("git", ["status", "--porcelain"], {
|
|
2598
|
-
cwd,
|
|
2599
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2600
|
-
});
|
|
2601
|
-
let diffStdout = "";
|
|
2602
|
-
let statusStdout = "";
|
|
2603
|
-
let diffExited = false;
|
|
2604
|
-
let statusExited = false;
|
|
2605
|
-
let diffCode = null;
|
|
2606
|
-
let statusCode = null;
|
|
2607
|
-
const processResults = () => {
|
|
2608
|
-
if (!diffExited || !statusExited) return;
|
|
2609
|
-
const raw = diffStdout + statusStdout;
|
|
2610
|
-
const hash = raw ? hashContent(raw) : "";
|
|
2611
|
-
const files = [];
|
|
2612
|
-
if (diffCode === 0) {
|
|
2613
|
-
for (const line of diffStdout.split("\n")) {
|
|
2614
|
-
if (!line.trim()) continue;
|
|
2615
|
-
const [statusChar, ...pathParts] = line.split(" ");
|
|
2616
|
-
const path = pathParts.join(" ") || statusChar || "";
|
|
2617
|
-
if (!path) continue;
|
|
2618
|
-
const status = statusChar === "A" ? "added" : statusChar === "D" ? "deleted" : "modified";
|
|
2619
|
-
files.push({ path, status, additions: 0, deletions: 0 });
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
if (statusCode === 0) {
|
|
2623
|
-
for (const line of statusStdout.split("\n")) {
|
|
2624
|
-
if (!line.startsWith("?? ")) continue;
|
|
2625
|
-
const path = line.slice(3).trim();
|
|
2626
|
-
if (!path) continue;
|
|
2627
|
-
files.push({ path, status: "added", additions: 0, deletions: 0 });
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
resolve3({ hash, files });
|
|
2631
|
-
};
|
|
2632
|
-
diffProc.stdout.on("data", (data) => {
|
|
2633
|
-
diffStdout += data.toString();
|
|
2634
|
-
});
|
|
2635
|
-
statusProc.stdout.on("data", (data) => {
|
|
2636
|
-
statusStdout += data.toString();
|
|
2637
|
-
});
|
|
2638
|
-
diffProc.on("close", (code) => {
|
|
2639
|
-
diffExited = true;
|
|
2640
|
-
diffCode = code;
|
|
2641
|
-
processResults();
|
|
2642
|
-
});
|
|
2643
|
-
statusProc.on("close", (code) => {
|
|
2644
|
-
statusExited = true;
|
|
2645
|
-
statusCode = code;
|
|
2646
|
-
processResults();
|
|
2647
|
-
});
|
|
2648
|
-
diffProc.on("error", () => {
|
|
2649
|
-
diffExited = true;
|
|
2650
|
-
diffCode = 1;
|
|
2651
|
-
processResults();
|
|
2652
|
-
});
|
|
2653
|
-
statusProc.on("error", () => {
|
|
2654
|
-
statusExited = true;
|
|
2655
|
-
statusCode = 1;
|
|
2656
|
-
processResults();
|
|
2657
|
-
});
|
|
2658
|
-
});
|
|
2659
|
-
}
|
|
2660
|
-
var moduleWorkdirLastHash = /* @__PURE__ */ new Map();
|
|
2661
|
-
var moduleWorkdirInterval = /* @__PURE__ */ new Map();
|
|
2662
|
-
var gitPollInterval = parseInt(process.env["OPENFOX_GIT_POLL_INTERVAL"] ?? "", 10) || 1e4;
|
|
2663
|
-
var moduleClients = null;
|
|
2664
|
-
var moduleEnqueueSend = null;
|
|
2665
|
-
function moduleGitPoll(workdir) {
|
|
2666
|
-
;
|
|
2667
|
-
(async () => {
|
|
1564
|
+
async callTool(serverName, toolName, args) {
|
|
1565
|
+
const entry = this.servers.get(serverName);
|
|
1566
|
+
if (!entry) return { success: false, error: `MCP server '${serverName}' not found` };
|
|
1567
|
+
if (!entry.client) return { success: false, error: `MCP server '${serverName}' is not connected` };
|
|
2668
1568
|
try {
|
|
2669
|
-
const
|
|
2670
|
-
const
|
|
2671
|
-
const
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
moduleWorkdirInterval.set(workdir, interval);
|
|
2693
|
-
}
|
|
2694
|
-
function moduleStopGitPolling(workdir) {
|
|
2695
|
-
const interval = moduleWorkdirInterval.get(workdir);
|
|
2696
|
-
if (interval !== void 0) {
|
|
2697
|
-
clearInterval(interval);
|
|
2698
|
-
moduleWorkdirInterval.delete(workdir);
|
|
2699
|
-
moduleWorkdirLastHash.delete(workdir);
|
|
2700
|
-
}
|
|
2701
|
-
}
|
|
2702
|
-
function resolveStatsIdentity(llmClient, getActiveProvider) {
|
|
2703
|
-
const provider = getActiveProvider?.();
|
|
2704
|
-
const model = llmClient.getModel();
|
|
2705
|
-
const backend = provider?.backend ?? (llmClient.getBackend() === "unknown" ? "unknown" : llmClient.getBackend());
|
|
2706
|
-
return {
|
|
2707
|
-
providerId: provider?.id ?? `provider:${model}`,
|
|
2708
|
-
providerName: provider?.name ?? "Unknown Provider",
|
|
2709
|
-
backend,
|
|
2710
|
-
model
|
|
2711
|
-
};
|
|
2712
|
-
}
|
|
2713
|
-
function addUserMessageAndBroadcast(sessionManager, sessionId, message, broadcastFn) {
|
|
2714
|
-
const userMessage = sessionManager.addMessage(sessionId, {
|
|
2715
|
-
role: "user",
|
|
2716
|
-
content: message.content,
|
|
2717
|
-
...message.attachments ? { attachments: message.attachments } : {},
|
|
2718
|
-
...message.messageKind ? { messageKind: message.messageKind } : {}
|
|
2719
|
-
});
|
|
2720
|
-
broadcastFn(sessionId, createChatMessageMessage(userMessage));
|
|
2721
|
-
return userMessage;
|
|
2722
|
-
}
|
|
2723
|
-
function processQueueAndRestartTurn(sessionManager, sessionId, drainFn, queueMode, broadcastFn, activeAgents2, startTurnFn, queueMessageFn) {
|
|
2724
|
-
const messages = drainFn(sessionId);
|
|
2725
|
-
const next = messages[0];
|
|
2726
|
-
if (!next) return false;
|
|
2727
|
-
for (const remaining of messages.slice(1)) {
|
|
2728
|
-
if (queueMessageFn) {
|
|
2729
|
-
queueMessageFn(
|
|
2730
|
-
sessionId,
|
|
2731
|
-
queueMode,
|
|
2732
|
-
remaining.content,
|
|
2733
|
-
remaining.attachments,
|
|
2734
|
-
remaining.messageKind
|
|
2735
|
-
);
|
|
2736
|
-
} else {
|
|
2737
|
-
sessionManager.queueMessage(sessionId, queueMode, remaining.content, remaining.attachments);
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
broadcastFn(sessionId, createQueueStateMessage(sessionManager.getQueueState(sessionId)));
|
|
2741
|
-
addUserMessageAndBroadcast(
|
|
2742
|
-
sessionManager,
|
|
2743
|
-
sessionId,
|
|
2744
|
-
{
|
|
2745
|
-
content: next.content,
|
|
2746
|
-
...next.attachments ? { attachments: next.attachments } : {},
|
|
2747
|
-
...next.messageKind ? { messageKind: next.messageKind } : {}
|
|
2748
|
-
},
|
|
2749
|
-
broadcastFn
|
|
2750
|
-
);
|
|
2751
|
-
const newController = new AbortController();
|
|
2752
|
-
activeAgents2.set(sessionId, newController);
|
|
2753
|
-
startTurnFn(sessionId, newController);
|
|
2754
|
-
return true;
|
|
2755
|
-
}
|
|
2756
|
-
var activeAgents = /* @__PURE__ */ new Map();
|
|
2757
|
-
var MAX_SEND_QUEUE_SIZE = 1e3;
|
|
2758
|
-
function createWebSocketServer(httpServer, config4, getLLMClient, getActiveProvider, sessionManager, providerManager) {
|
|
2759
|
-
const wss = new WebSocketServer({ server: httpServer });
|
|
2760
|
-
const clients = /* @__PURE__ */ new Map();
|
|
2761
|
-
moduleClients = clients;
|
|
2762
|
-
const sessionLLMClients = /* @__PURE__ */ new Map();
|
|
2763
|
-
function getSessionLLMClient(sessionId) {
|
|
2764
|
-
const session = sessionManager.getSession(sessionId);
|
|
2765
|
-
if (!session?.providerId || !session?.providerModel || !providerManager) {
|
|
2766
|
-
return getLLMClient();
|
|
2767
|
-
}
|
|
2768
|
-
const cacheKey = `${session.providerId}:${session.providerModel}`;
|
|
2769
|
-
const cached = sessionLLMClients.get(sessionId);
|
|
2770
|
-
if (cached && cached.key === cacheKey) {
|
|
2771
|
-
return cached.client;
|
|
2772
|
-
}
|
|
2773
|
-
const provider = providerManager.getProviders().find((p) => p.id === session.providerId);
|
|
2774
|
-
if (!provider) {
|
|
2775
|
-
logger.warn("Session references deleted provider, falling back to global", {
|
|
2776
|
-
sessionId,
|
|
2777
|
-
providerId: session.providerId
|
|
2778
|
-
});
|
|
2779
|
-
sessionManager.setSessionProvider(sessionId, null, null);
|
|
2780
|
-
sessionLLMClients.delete(sessionId);
|
|
2781
|
-
return getLLMClient();
|
|
2782
|
-
}
|
|
2783
|
-
const modelConfig = provider.models.find((m) => m.id === session.providerModel);
|
|
2784
|
-
const reasoningEffort = modelConfig?.thinkingEnabled && modelConfig?.thinkingLevel ? modelConfig.thinkingLevel : void 0;
|
|
2785
|
-
const baseUrl = ensureVersionPrefix(provider.url);
|
|
2786
|
-
const sessionConfig = {
|
|
2787
|
-
...config4,
|
|
2788
|
-
llm: {
|
|
2789
|
-
...config4.llm,
|
|
2790
|
-
baseUrl,
|
|
2791
|
-
model: session.providerModel,
|
|
2792
|
-
...provider.apiKey && { apiKey: provider.apiKey },
|
|
2793
|
-
...provider.thinkingField && { thinkingField: provider.thinkingField },
|
|
2794
|
-
...reasoningEffort && { reasoningEffort }
|
|
2795
|
-
}
|
|
2796
|
-
};
|
|
2797
|
-
const client = createLLMClient(sessionConfig);
|
|
2798
|
-
if (provider.backend !== "unknown") {
|
|
2799
|
-
client.setBackend(provider.backend);
|
|
2800
|
-
}
|
|
2801
|
-
client.setModel(session.providerModel);
|
|
2802
|
-
sessionLLMClients.set(sessionId, { key: cacheKey, client });
|
|
2803
|
-
return client;
|
|
2804
|
-
}
|
|
2805
|
-
function getSessionStatsIdentity(sessionId) {
|
|
2806
|
-
const session = sessionManager.getSession(sessionId);
|
|
2807
|
-
if (!session?.providerId || !providerManager) {
|
|
2808
|
-
return resolveStatsIdentity(getLLMClient(), getActiveProvider);
|
|
2809
|
-
}
|
|
2810
|
-
const provider = providerManager.getProviders().find((p) => p.id === session.providerId);
|
|
2811
|
-
const client = getSessionLLMClient(sessionId);
|
|
2812
|
-
return {
|
|
2813
|
-
providerId: provider?.id ?? session.providerId,
|
|
2814
|
-
providerName: provider?.name ?? "Unknown Provider",
|
|
2815
|
-
backend: provider?.backend ?? client.getBackend(),
|
|
2816
|
-
model: client.getModel()
|
|
2817
|
-
};
|
|
2818
|
-
}
|
|
2819
|
-
const isSubscribedToSession = (client, sessionId) => {
|
|
2820
|
-
return client.activeSessionId === sessionId;
|
|
2821
|
-
};
|
|
2822
|
-
const broadcastForSession = (sessionId, msg) => {
|
|
2823
|
-
for (const [clientWs, client] of clients) {
|
|
2824
|
-
if (isSubscribedToSession(client, sessionId) && clientWs.readyState === WebSocket2.OPEN) {
|
|
2825
|
-
clientWs.send(serializeServerMessage({ ...msg, sessionId }));
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
};
|
|
2829
|
-
function enqueueSend(client, data, seq) {
|
|
2830
|
-
if (client.sendQueue.length >= MAX_SEND_QUEUE_SIZE) {
|
|
2831
|
-
logger.warn("WebSocket send queue full, dropping message", {
|
|
2832
|
-
queueSize: client.sendQueue.length,
|
|
2833
|
-
sessionId: client.activeSessionId
|
|
2834
|
-
});
|
|
2835
|
-
return;
|
|
2836
|
-
}
|
|
2837
|
-
client.sendQueue.push({ data, seq });
|
|
2838
|
-
processSendQueue(client);
|
|
2839
|
-
}
|
|
2840
|
-
moduleEnqueueSend = enqueueSend;
|
|
2841
|
-
function processSendQueue(client) {
|
|
2842
|
-
if (client.isSending || client.sendQueue.length === 0) {
|
|
2843
|
-
return;
|
|
2844
|
-
}
|
|
2845
|
-
client.isSending = true;
|
|
2846
|
-
const item = client.sendQueue.shift();
|
|
2847
|
-
if (client.ws.readyState === WebSocket2.OPEN) {
|
|
2848
|
-
client.ws.send(item.data, (err) => {
|
|
2849
|
-
if (err) {
|
|
2850
|
-
logger.debug("WebSocket send error", { error: err });
|
|
2851
|
-
}
|
|
2852
|
-
client.isSending = false;
|
|
2853
|
-
client.lastSentSeq = item.seq;
|
|
2854
|
-
processSendQueue(client);
|
|
2855
|
-
});
|
|
2856
|
-
} else {
|
|
2857
|
-
client.isSending = false;
|
|
2858
|
-
processSendQueue(client);
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
const llmForSession = (sessionId) => getSessionLLMClient?.(sessionId) ?? getLLMClient();
|
|
2862
|
-
const statsForSession = (sessionId) => getSessionStatsIdentity?.(sessionId) ?? resolveStatsIdentity(getLLMClient(), getActiveProvider);
|
|
2863
|
-
function cleanupAfterTurn(sessionId, controller, sendFn, setRunningOnEarlyReturn) {
|
|
2864
|
-
if (activeAgents.get(sessionId) !== controller) {
|
|
2865
|
-
return;
|
|
2866
|
-
}
|
|
2867
|
-
activeAgents.delete(sessionId);
|
|
2868
|
-
const processed = processQueueAndRestartTurn(
|
|
2869
|
-
sessionManager,
|
|
2870
|
-
sessionId,
|
|
2871
|
-
(sid) => sessionManager.drainAsapMessages(sid),
|
|
2872
|
-
"asap",
|
|
2873
|
-
sendFn,
|
|
2874
|
-
activeAgents,
|
|
2875
|
-
startTurnWithCompletionChain,
|
|
2876
|
-
(sid, mode, content, attachments, messageKind) => sessionManager.queueMessage(sid, mode, content, attachments, messageKind)
|
|
2877
|
-
);
|
|
2878
|
-
if (processed) {
|
|
2879
|
-
if (setRunningOnEarlyReturn) sessionManager.setRunning(sessionId, false);
|
|
2880
|
-
return;
|
|
2881
|
-
}
|
|
2882
|
-
const processedCompletion = processQueueAndRestartTurn(
|
|
2883
|
-
sessionManager,
|
|
2884
|
-
sessionId,
|
|
2885
|
-
(sid) => sessionManager.drainCompletionMessages(sid),
|
|
2886
|
-
"completion",
|
|
2887
|
-
sendFn,
|
|
2888
|
-
activeAgents,
|
|
2889
|
-
startTurnWithCompletionChain
|
|
2890
|
-
);
|
|
2891
|
-
if (processedCompletion) {
|
|
2892
|
-
if (setRunningOnEarlyReturn) sessionManager.setRunning(sessionId, false);
|
|
2893
|
-
return;
|
|
2894
|
-
}
|
|
2895
|
-
sessionManager.clearMessageQueue(sessionId);
|
|
2896
|
-
const contextState = sessionManager.getContextState(sessionId);
|
|
2897
|
-
sendFn(sessionId, createContextStateMessage(contextState));
|
|
2898
|
-
}
|
|
2899
|
-
function startTurnWithCompletionChain(sessionId, controller) {
|
|
2900
|
-
runChatTurn({
|
|
2901
|
-
sessionManager,
|
|
2902
|
-
sessionId,
|
|
2903
|
-
llmClient: llmForSession(sessionId),
|
|
2904
|
-
statsIdentity: statsForSession(sessionId),
|
|
2905
|
-
signal: controller.signal,
|
|
2906
|
-
onMessage: (msg) => broadcastForSession(sessionId, msg)
|
|
2907
|
-
}).catch((error) => {
|
|
2908
|
-
if (error instanceof Error && error.message === "Aborted") {
|
|
2909
|
-
return;
|
|
2910
|
-
}
|
|
2911
|
-
logger.error("Chat turn error", { error });
|
|
2912
|
-
}).finally(() => {
|
|
2913
|
-
cleanupAfterTurn(sessionId, controller, broadcastForSession, false);
|
|
2914
|
-
});
|
|
2915
|
-
}
|
|
2916
|
-
const broadcastAll = (msg) => {
|
|
2917
|
-
const serialized = serializeServerMessage(msg);
|
|
2918
|
-
for (const [clientWs, client] of clients) {
|
|
2919
|
-
if (clientWs.readyState === WebSocket2.OPEN) {
|
|
2920
|
-
const seq = client.lastSentSeq + 1;
|
|
2921
|
-
enqueueSend(client, serialized, seq);
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2924
|
-
};
|
|
2925
|
-
devServerManager.onOutput((workdir, chunk) => {
|
|
2926
|
-
broadcastAll(
|
|
2927
|
-
createServerMessage("devServer.output", {
|
|
2928
|
-
workdir,
|
|
2929
|
-
stream: chunk.stream,
|
|
2930
|
-
content: chunk.content
|
|
2931
|
-
})
|
|
2932
|
-
);
|
|
2933
|
-
});
|
|
2934
|
-
devServerManager.onStateChange((workdir, state, errorMessage) => {
|
|
2935
|
-
broadcastAll(
|
|
2936
|
-
createServerMessage("devServer.state", {
|
|
2937
|
-
workdir,
|
|
2938
|
-
state,
|
|
2939
|
-
errorMessage
|
|
2940
|
-
})
|
|
2941
|
-
);
|
|
2942
|
-
});
|
|
2943
|
-
onProcessEvent((_processId, msg) => {
|
|
2944
|
-
const sessionId = msg.sessionId;
|
|
2945
|
-
if (!sessionId) return;
|
|
2946
|
-
for (const [clientWs, client] of clients) {
|
|
2947
|
-
if (client.activeSessionId === sessionId) {
|
|
2948
|
-
if (clientWs.readyState === WebSocket2.OPEN) {
|
|
2949
|
-
const seq = client.lastSentSeq + 1;
|
|
2950
|
-
enqueueSend(client, serializeServerMessage(msg), seq);
|
|
2951
|
-
}
|
|
2952
|
-
}
|
|
2953
|
-
}
|
|
2954
|
-
});
|
|
2955
|
-
wss.on("connection", async (ws, req) => {
|
|
2956
|
-
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
2957
|
-
const token = url.searchParams.get("token");
|
|
2958
|
-
const authConfig = getAuthConfig();
|
|
2959
|
-
if (authConfig?.strategy === "network" && authConfig.encryptedPassword) {
|
|
2960
|
-
if (!token || !await isValidToken(token)) {
|
|
2961
|
-
setTimeout(() => {
|
|
2962
|
-
ws.close(4e3, "Unauthorized");
|
|
2963
|
-
}, 100);
|
|
2964
|
-
return;
|
|
2965
|
-
}
|
|
2966
|
-
}
|
|
2967
|
-
logger.debug("WebSocket client connected");
|
|
2968
|
-
clients.set(ws, {
|
|
2969
|
-
ws,
|
|
2970
|
-
activeSessionId: null,
|
|
2971
|
-
activeWorkdir: null,
|
|
2972
|
-
globalSubscription: null,
|
|
2973
|
-
sendQueue: [],
|
|
2974
|
-
isSending: false,
|
|
2975
|
-
lastSentSeq: 0
|
|
2976
|
-
});
|
|
2977
|
-
const eventStore = getEventStore();
|
|
2978
|
-
const { iterator: globalIterator, unsubscribe: globalUnsubscribe } = eventStore.subscribeAll();
|
|
2979
|
-
clients.get(ws).globalSubscription = globalUnsubscribe;
|
|
2980
|
-
(async () => {
|
|
2981
|
-
try {
|
|
2982
|
-
for await (const storedEvent of globalIterator) {
|
|
2983
|
-
if (ws.readyState !== WebSocket2.OPEN) break;
|
|
2984
|
-
const serverMsg = storedEventToServerMessage(storedEvent);
|
|
2985
|
-
if (serverMsg) {
|
|
2986
|
-
const client = clients.get(ws);
|
|
2987
|
-
enqueueSend(
|
|
2988
|
-
client,
|
|
2989
|
-
serializeServerMessage({ ...serverMsg, seq: storedEvent.seq, sessionId: storedEvent.sessionId }),
|
|
2990
|
-
storedEvent.seq
|
|
2991
|
-
);
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
} catch (error) {
|
|
2995
|
-
logger.debug("Global event subscription ended", { error });
|
|
2996
|
-
}
|
|
2997
|
-
})();
|
|
2998
|
-
ws.on("message", async (data) => {
|
|
2999
|
-
const message = parseClientMessage(data.toString());
|
|
3000
|
-
if (!message) {
|
|
3001
|
-
const client2 = clients.get(ws);
|
|
3002
|
-
const seq = client2.lastSentSeq + 1;
|
|
3003
|
-
enqueueSend(
|
|
3004
|
-
client2,
|
|
3005
|
-
serializeServerMessage(createErrorMessage("INVALID_MESSAGE", "Invalid message format")),
|
|
3006
|
-
seq
|
|
3007
|
-
);
|
|
3008
|
-
return;
|
|
3009
|
-
}
|
|
3010
|
-
if (message.type.startsWith("terminal.")) {
|
|
3011
|
-
handleTerminalMessage(ws, message);
|
|
3012
|
-
return;
|
|
3013
|
-
}
|
|
3014
|
-
const client = clients.get(ws);
|
|
3015
|
-
try {
|
|
3016
|
-
await handleClientMessage(
|
|
3017
|
-
ws,
|
|
3018
|
-
client,
|
|
3019
|
-
message,
|
|
3020
|
-
getLLMClient,
|
|
3021
|
-
getActiveProvider,
|
|
3022
|
-
sessionManager,
|
|
3023
|
-
broadcastForSession,
|
|
3024
|
-
providerManager,
|
|
3025
|
-
getSessionLLMClient,
|
|
3026
|
-
getSessionStatsIdentity,
|
|
3027
|
-
llmForSession,
|
|
3028
|
-
statsForSession,
|
|
3029
|
-
startTurnWithCompletionChain,
|
|
3030
|
-
cleanupAfterTurn,
|
|
3031
|
-
enqueueSend
|
|
3032
|
-
);
|
|
3033
|
-
} catch (error) {
|
|
3034
|
-
logger.error("Error handling client message", { error, type: message.type });
|
|
3035
|
-
const client2 = clients.get(ws);
|
|
3036
|
-
const seq = client2.lastSentSeq + 1;
|
|
3037
|
-
enqueueSend(
|
|
3038
|
-
client2,
|
|
3039
|
-
serializeServerMessage(
|
|
3040
|
-
createErrorMessage("INTERNAL_ERROR", error instanceof Error ? error.message : "Unknown error", message.id)
|
|
3041
|
-
),
|
|
3042
|
-
seq
|
|
3043
|
-
);
|
|
3044
|
-
}
|
|
3045
|
-
});
|
|
3046
|
-
ws.on("close", () => {
|
|
3047
|
-
logger.debug("WebSocket client disconnected");
|
|
3048
|
-
const client = clients.get(ws);
|
|
3049
|
-
const disconnectedWorkdir = client?.activeWorkdir ?? null;
|
|
3050
|
-
if (client?.globalSubscription) {
|
|
3051
|
-
client.globalSubscription();
|
|
3052
|
-
}
|
|
3053
|
-
unsubscribeAllFromTerminal(ws);
|
|
3054
|
-
clients.delete(ws);
|
|
3055
|
-
if (disconnectedWorkdir) {
|
|
3056
|
-
const hasRemaining = [...clients.values()].some((c) => c.activeWorkdir === disconnectedWorkdir);
|
|
3057
|
-
if (!hasRemaining) {
|
|
3058
|
-
moduleStopGitPolling(disconnectedWorkdir);
|
|
3059
|
-
}
|
|
3060
|
-
}
|
|
3061
|
-
});
|
|
3062
|
-
ws.on("error", (error) => {
|
|
3063
|
-
logger.error("WebSocket error", { error });
|
|
3064
|
-
});
|
|
3065
|
-
});
|
|
3066
|
-
return {
|
|
3067
|
-
wss,
|
|
3068
|
-
abortSession: (sessionId) => {
|
|
3069
|
-
const controller = activeAgents.get(sessionId);
|
|
3070
|
-
if (controller) {
|
|
3071
|
-
activeAgents.delete(sessionId);
|
|
3072
|
-
controller.abort();
|
|
3073
|
-
return true;
|
|
3074
|
-
}
|
|
3075
|
-
return false;
|
|
3076
|
-
},
|
|
3077
|
-
close: (cb) => wss.close(cb),
|
|
3078
|
-
broadcastForSession
|
|
3079
|
-
};
|
|
3080
|
-
}
|
|
3081
|
-
async function handleClientMessage(ws, client, message, _getLLMClient, _getActiveProvider, sessionManager, _broadcastForSession, _providerManager, _getSessionLLMClient, _getSessionStatsIdentity, llmForSession, statsForSession, _startTurnWithCompletionChain, cleanupAfterTurn, enqueueSendFn) {
|
|
3082
|
-
const send = (msg) => {
|
|
3083
|
-
if (ws.readyState === WebSocket2.OPEN) {
|
|
3084
|
-
const seq = client.lastSentSeq + 1;
|
|
3085
|
-
enqueueSendFn(client, serializeServerMessage(msg), seq);
|
|
3086
|
-
}
|
|
3087
|
-
};
|
|
3088
|
-
const sendForSession = (sessionId, msg) => {
|
|
3089
|
-
send({ ...msg, sessionId });
|
|
3090
|
-
};
|
|
3091
|
-
const ensureEventStoreSubscription = (_sessionId) => {
|
|
3092
|
-
};
|
|
3093
|
-
switch (message.type) {
|
|
3094
|
-
// =========================================================================
|
|
3095
|
-
// DEPRECATED: All CRUD operations moved to REST API
|
|
3096
|
-
// If you see this error, update your code to use REST endpoints instead.
|
|
3097
|
-
// See docs/REST-API.md for details.
|
|
3098
|
-
// =========================================================================
|
|
3099
|
-
case "project.create":
|
|
3100
|
-
case "project.create-with-dir":
|
|
3101
|
-
case "project.list":
|
|
3102
|
-
case "project.load":
|
|
3103
|
-
case "project.update":
|
|
3104
|
-
case "project.delete":
|
|
3105
|
-
case "settings.get":
|
|
3106
|
-
case "settings.set":
|
|
3107
|
-
case "session.create":
|
|
3108
|
-
case "session.list":
|
|
3109
|
-
case "session.delete":
|
|
3110
|
-
case "session.deleteAll":
|
|
3111
|
-
case "session.setProvider":
|
|
3112
|
-
send(
|
|
3113
|
-
createErrorMessage(
|
|
3114
|
-
"DEPRECATED_MESSAGE_TYPE",
|
|
3115
|
-
`${message.type} removed. Use REST API instead. See docs/REST-API.md`,
|
|
3116
|
-
message.id
|
|
3117
|
-
)
|
|
3118
|
-
);
|
|
3119
|
-
return;
|
|
3120
|
-
// =========================================================================
|
|
3121
|
-
// Session Load - Sets active session for event routing
|
|
3122
|
-
// Note: Data loading is done via REST API: GET /api/sessions/:id
|
|
3123
|
-
// This WebSocket message is ONLY to tell the server which session is active
|
|
3124
|
-
// so it can route real-time events correctly.
|
|
3125
|
-
// =========================================================================
|
|
3126
|
-
case "session.load": {
|
|
3127
|
-
if (!isSessionLoadPayload(message.payload)) {
|
|
3128
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid session.load payload", message.id));
|
|
3129
|
-
return;
|
|
3130
|
-
}
|
|
3131
|
-
const session = sessionManager.getSession(message.payload.sessionId);
|
|
3132
|
-
if (!session) {
|
|
3133
|
-
send(createErrorMessage("NOT_FOUND", "Session not found", message.id));
|
|
3134
|
-
return;
|
|
3135
|
-
}
|
|
3136
|
-
client.activeSessionId = session.id;
|
|
3137
|
-
client.activeWorkdir = session.workdir;
|
|
3138
|
-
if (session.workdir) {
|
|
3139
|
-
const branch = await moduleGitBranch(session.workdir);
|
|
3140
|
-
const { files } = await moduleGitDiff(session.workdir);
|
|
3141
|
-
const msg = createGitStatusMessage(branch, files);
|
|
3142
|
-
send(msg);
|
|
3143
|
-
if (branch) moduleStartGitPolling(session.workdir);
|
|
3144
|
-
}
|
|
3145
|
-
ensureEventStoreSubscription(session.id);
|
|
3146
|
-
send({ type: "ack", payload: { sessionId: session.id }, id: message.id });
|
|
3147
|
-
const sendContextState = () => {
|
|
3148
|
-
const contextState = sessionManager.getContextState(session.id);
|
|
3149
|
-
send(createContextStateMessage(contextState));
|
|
3150
|
-
};
|
|
3151
|
-
sendContextState();
|
|
3152
|
-
const cachedHash = sessionManager.getCachedPrompt(session.id)?.hash;
|
|
3153
|
-
if (cachedHash) {
|
|
3154
|
-
;
|
|
3155
|
-
(async () => {
|
|
3156
|
-
try {
|
|
3157
|
-
const { content: instructionContent } = await getAllInstructions(session.workdir, session.projectId);
|
|
3158
|
-
const runtimeConfig = getRuntimeConfig();
|
|
3159
|
-
const configDir = getGlobalConfigDir(runtimeConfig.mode ?? "production");
|
|
3160
|
-
const skills = await getEnabledSkillMetadata(configDir, runtimeConfig.workdir);
|
|
3161
|
-
const dynamicInputs = JSON.stringify({
|
|
3162
|
-
instructions: instructionContent,
|
|
3163
|
-
skills: skills.map((s) => s.id).sort()
|
|
3164
|
-
});
|
|
3165
|
-
const currentHash = createHash2("sha256").update(dynamicInputs).digest("hex");
|
|
3166
|
-
if (currentHash !== cachedHash) {
|
|
3167
|
-
sessionManager.setDynamicContextChanged(session.id, true);
|
|
3168
|
-
sendContextState();
|
|
3169
|
-
}
|
|
3170
|
-
} catch {
|
|
3171
|
-
}
|
|
3172
|
-
})();
|
|
3173
|
-
}
|
|
3174
|
-
break;
|
|
3175
|
-
}
|
|
3176
|
-
// =========================================================================
|
|
3177
|
-
// Context Management
|
|
3178
|
-
// =========================================================================
|
|
3179
|
-
case "context.compact": {
|
|
3180
|
-
if (!client.activeSessionId) {
|
|
3181
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3182
|
-
return;
|
|
3183
|
-
}
|
|
3184
|
-
const session = sessionManager.requireSession(client.activeSessionId);
|
|
3185
|
-
const sessionId = client.activeSessionId;
|
|
3186
|
-
if (session.isRunning) {
|
|
3187
|
-
send(createErrorMessage("SESSION_RUNNING", "Cannot compact while session is running", message.id));
|
|
3188
|
-
return;
|
|
3189
|
-
}
|
|
3190
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3191
|
-
appendCompactionPrompt(sessionId, (event) => getEventStore().append(sessionId, event));
|
|
3192
|
-
runChatTurn({
|
|
3193
|
-
sessionManager,
|
|
3194
|
-
sessionId,
|
|
3195
|
-
llmClient: llmForSession(sessionId),
|
|
3196
|
-
statsIdentity: statsForSession(sessionId),
|
|
3197
|
-
onMessage: (msg) => _broadcastForSession(sessionId, msg),
|
|
3198
|
-
initialCompacting: true
|
|
3199
|
-
}).then(() => {
|
|
3200
|
-
const newContextState = sessionManager.getContextState(sessionId);
|
|
3201
|
-
sendForSession(sessionId, createContextStateMessage(newContextState));
|
|
3202
|
-
}).catch((error) => {
|
|
3203
|
-
if (error instanceof Error && error.message === "Aborted") return;
|
|
3204
|
-
logger.error("Compaction failed", { error, sessionId });
|
|
3205
|
-
sendForSession(
|
|
3206
|
-
sessionId,
|
|
3207
|
-
createChatErrorMessage(
|
|
3208
|
-
`Compaction failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
3209
|
-
true
|
|
3210
|
-
)
|
|
3211
|
-
);
|
|
3212
|
-
});
|
|
3213
|
-
break;
|
|
3214
|
-
}
|
|
3215
|
-
case "context.checkDynamic": {
|
|
3216
|
-
if (!client.activeSessionId) {
|
|
3217
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3218
|
-
return;
|
|
3219
|
-
}
|
|
3220
|
-
const sessionId = client.activeSessionId;
|
|
3221
|
-
const session = sessionManager.requireSession(sessionId);
|
|
3222
|
-
(async () => {
|
|
3223
|
-
try {
|
|
3224
|
-
const { content: instructionContent } = await getAllInstructions(session.workdir, session.projectId);
|
|
3225
|
-
const runtimeConfig = getRuntimeConfig();
|
|
3226
|
-
const configDir = getGlobalConfigDir(runtimeConfig.mode ?? "production");
|
|
3227
|
-
const skills = await getEnabledSkillMetadata(configDir, runtimeConfig.workdir);
|
|
3228
|
-
const currentHash = computeDynamicContextHash(instructionContent, skills);
|
|
3229
|
-
const cachedHash = sessionManager.getCachedPrompt(sessionId)?.hash;
|
|
3230
|
-
if (cachedHash) {
|
|
3231
|
-
if (currentHash !== cachedHash) {
|
|
3232
|
-
if (!sessionManager.getDynamicContextChanged(sessionId)) {
|
|
3233
|
-
sessionManager.setDynamicContextChanged(sessionId, true);
|
|
3234
|
-
const newContextState = sessionManager.getContextState(sessionId);
|
|
3235
|
-
sendForSession(sessionId, createContextStateMessage(newContextState));
|
|
3236
|
-
}
|
|
3237
|
-
} else if (sessionManager.getDynamicContextChanged(sessionId)) {
|
|
3238
|
-
sessionManager.setDynamicContextChanged(sessionId, false);
|
|
3239
|
-
const newContextState = sessionManager.getContextState(sessionId);
|
|
3240
|
-
sendForSession(sessionId, createContextStateMessage(newContextState));
|
|
3241
|
-
}
|
|
3242
|
-
}
|
|
3243
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3244
|
-
} catch (error) {
|
|
3245
|
-
logger.error("Failed to check dynamic context", { error, sessionId });
|
|
3246
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3247
|
-
}
|
|
3248
|
-
})();
|
|
3249
|
-
break;
|
|
3250
|
-
}
|
|
3251
|
-
case "context.applyDynamic": {
|
|
3252
|
-
if (!client.activeSessionId) {
|
|
3253
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3254
|
-
return;
|
|
3255
|
-
}
|
|
3256
|
-
const sessionId = client.activeSessionId;
|
|
3257
|
-
const session = sessionManager.requireSession(sessionId);
|
|
3258
|
-
if (session.isRunning) {
|
|
3259
|
-
send(createErrorMessage("SESSION_RUNNING", "Cannot apply dynamic context while session is running", message.id));
|
|
3260
|
-
return;
|
|
3261
|
-
}
|
|
3262
|
-
;
|
|
3263
|
-
(async () => {
|
|
3264
|
-
try {
|
|
3265
|
-
const { content: instructionContent } = await getAllInstructions(session.workdir, session.projectId);
|
|
3266
|
-
const runtimeConfig = getRuntimeConfig();
|
|
3267
|
-
const configDir = getGlobalConfigDir(runtimeConfig.mode ?? "production");
|
|
3268
|
-
const skills = await getEnabledSkillMetadata(configDir, runtimeConfig.workdir);
|
|
3269
|
-
const dynamicHash = computeDynamicContextHash(instructionContent, skills);
|
|
3270
|
-
const allAgents = await loadAllAgentsDefault();
|
|
3271
|
-
const subAgentDefs = getSubAgents(allAgents);
|
|
3272
|
-
const systemPrompt = buildTopLevelSystemPrompt(
|
|
3273
|
-
session.workdir,
|
|
3274
|
-
instructionContent || void 0,
|
|
3275
|
-
skills,
|
|
3276
|
-
subAgentDefs
|
|
3277
|
-
);
|
|
3278
|
-
sessionManager.setCachedPrompt(sessionId, systemPrompt, dynamicHash);
|
|
3279
|
-
sessionManager.setDynamicContextChanged(sessionId, false);
|
|
3280
|
-
const newContextState = sessionManager.getContextState(sessionId);
|
|
3281
|
-
sendForSession(sessionId, createContextStateMessage(newContextState));
|
|
3282
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3283
|
-
} catch (error) {
|
|
3284
|
-
logger.error("Failed to apply dynamic context", { error, sessionId });
|
|
3285
|
-
sendForSession(
|
|
3286
|
-
sessionId,
|
|
3287
|
-
createChatErrorMessage(
|
|
3288
|
-
`Failed to apply dynamic context: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
3289
|
-
true
|
|
3290
|
-
)
|
|
3291
|
-
);
|
|
3292
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3293
|
-
}
|
|
3294
|
-
})();
|
|
3295
|
-
break;
|
|
3296
|
-
}
|
|
3297
|
-
// =========================================================================
|
|
3298
|
-
// Runner (Auto-Loop)
|
|
3299
|
-
// =========================================================================
|
|
3300
|
-
case "runner.launch": {
|
|
3301
|
-
if (!client.activeSessionId) {
|
|
3302
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3303
|
-
return;
|
|
3304
|
-
}
|
|
3305
|
-
const session = sessionManager.requireSession(client.activeSessionId);
|
|
3306
|
-
if (session.isRunning) {
|
|
3307
|
-
const launchPayload2 = message.payload;
|
|
3308
|
-
const content = launchPayload2?.content ?? "";
|
|
3309
|
-
const attachments = launchPayload2?.attachments;
|
|
3310
|
-
const workflowId = launchPayload2?.workflowId;
|
|
3311
|
-
let fullContent = content;
|
|
3312
|
-
if (workflowId) {
|
|
3313
|
-
const workflowInfo = `// Workflow: ${workflowId}`;
|
|
3314
|
-
fullContent = content ? `${workflowInfo}
|
|
3315
|
-
|
|
3316
|
-
${content}` : workflowInfo;
|
|
3317
|
-
}
|
|
3318
|
-
sessionManager.queueMessage(client.activeSessionId, "asap", fullContent, attachments, "workflow-launch");
|
|
3319
|
-
const queueState = sessionManager.getQueueState(client.activeSessionId);
|
|
3320
|
-
send({
|
|
3321
|
-
type: "queue.state",
|
|
3322
|
-
payload: { success: true, queueState },
|
|
3323
|
-
id: message.id
|
|
3324
|
-
});
|
|
3325
|
-
return;
|
|
3326
|
-
}
|
|
3327
|
-
const launchPayloadEarly = message.payload;
|
|
3328
|
-
const pendingCriteria = session.criteria.filter((c) => c.status.type !== "passed");
|
|
3329
|
-
if (!launchPayloadEarly?.workflowId && pendingCriteria.length === 0) {
|
|
3330
|
-
send(createErrorMessage("NO_WORK", "No pending criteria to work on", message.id));
|
|
3331
|
-
return;
|
|
3332
|
-
}
|
|
3333
|
-
const sessionId = client.activeSessionId;
|
|
3334
|
-
if (session.phase === "blocked") {
|
|
3335
|
-
logger.info("User launched runner - resetting blocked state", { sessionId });
|
|
3336
|
-
sessionManager.setPhase(sessionId, "build");
|
|
3337
|
-
}
|
|
3338
|
-
const launchPayload = message.payload;
|
|
3339
|
-
const launchAttachments = launchPayload?.attachments;
|
|
3340
|
-
const hasUserContent = launchPayload?.content && typeof launchPayload.content === "string" && launchPayload.content.trim();
|
|
3341
|
-
const hasUserAttachments = launchAttachments && launchAttachments.length > 0;
|
|
3342
|
-
const hasUserMessage = hasUserContent || hasUserAttachments;
|
|
3343
|
-
sessionManager.setRunning(sessionId, true);
|
|
3344
|
-
sendForSession(sessionId, createSessionRunningMessage(true));
|
|
3345
|
-
const controller = new AbortController();
|
|
3346
|
-
const existingController = activeAgents.get(sessionId);
|
|
3347
|
-
if (existingController) {
|
|
3348
|
-
logger.warn("Aborting existing agent before starting new one", { sessionId });
|
|
3349
|
-
existingController.abort();
|
|
3350
|
-
}
|
|
3351
|
-
activeAgents.set(sessionId, controller);
|
|
3352
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3353
|
-
ensureEventStoreSubscription(sessionId);
|
|
3354
|
-
logger.info("Runner launching", { sessionId, pendingCriteria: pendingCriteria.length });
|
|
3355
|
-
runOrchestrator({
|
|
3356
|
-
sessionManager,
|
|
3357
|
-
sessionId,
|
|
3358
|
-
llmClient: llmForSession(sessionId),
|
|
3359
|
-
statsIdentity: statsForSession(sessionId),
|
|
3360
|
-
injectWorkflowKickoff: !hasUserMessage,
|
|
3361
|
-
...launchPayload?.workflowId ? { workflowId: launchPayload.workflowId } : {},
|
|
3362
|
-
...launchPayload?.subGroup ? { subGroup: launchPayload.subGroup } : {},
|
|
3363
|
-
...hasUserMessage ? {
|
|
3364
|
-
userMessage: {
|
|
3365
|
-
content: hasUserContent ? launchPayload.content : "",
|
|
3366
|
-
...hasUserAttachments ? { attachments: launchAttachments } : {}
|
|
3367
|
-
}
|
|
3368
|
-
} : {},
|
|
3369
|
-
signal: controller.signal,
|
|
3370
|
-
onMessage: (msg) => sendForSession(sessionId, msg)
|
|
3371
|
-
// For path confirmation dialogs
|
|
3372
|
-
}).catch((error) => {
|
|
3373
|
-
if (error instanceof Error && error.message === "Aborted") {
|
|
3374
|
-
return;
|
|
1569
|
+
const result = await entry.client.callTool({ name: toolName, arguments: args });
|
|
1570
|
+
const content = result.content;
|
|
1571
|
+
const textParts = content.filter((c) => c.type === "text").map((c) => c.text);
|
|
1572
|
+
return { success: !result.isError, output: textParts.join("\n") };
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
async setToolEnabled(serverName, toolName, enabled) {
|
|
1578
|
+
const entry = this.servers.get(serverName);
|
|
1579
|
+
if (!entry) throw new Error(`MCP server '${serverName}' not found`);
|
|
1580
|
+
const tool = entry.state.tools.find((t) => t.name === toolName);
|
|
1581
|
+
if (!tool) throw new Error(`Tool '${toolName}' not found on server '${serverName}'`);
|
|
1582
|
+
tool.enabled = enabled;
|
|
1583
|
+
entry.state.estimatedTokens = entry.state.tools.filter((t) => t.enabled).reduce((sum, t) => sum + t.estimatedTokens, 0);
|
|
1584
|
+
this.onServersChanged?.();
|
|
1585
|
+
}
|
|
1586
|
+
getToolFingerprint() {
|
|
1587
|
+
const parts = [];
|
|
1588
|
+
for (const [, entry] of this.servers) {
|
|
1589
|
+
for (const tool of entry.state.tools) {
|
|
1590
|
+
if (tool.enabled) {
|
|
1591
|
+
parts.push(`${entry.state.name}:${tool.name}`);
|
|
3375
1592
|
}
|
|
3376
|
-
logger.error("Runner error", { error, sessionId });
|
|
3377
|
-
}).finally(() => {
|
|
3378
|
-
sessionManager.setRunning(sessionId, false);
|
|
3379
|
-
sendForSession(sessionId, createSessionRunningMessage(false));
|
|
3380
|
-
cleanupAfterTurn(sessionId, controller, sendForSession, true);
|
|
3381
|
-
});
|
|
3382
|
-
break;
|
|
3383
|
-
}
|
|
3384
|
-
// =========================================================================
|
|
3385
|
-
// Path Confirmation
|
|
3386
|
-
// =========================================================================
|
|
3387
|
-
case "path.confirm": {
|
|
3388
|
-
send(
|
|
3389
|
-
createErrorMessage(
|
|
3390
|
-
"DEPRECATED",
|
|
3391
|
-
"path.confirm removed. Use REST API: POST /api/sessions/:id/confirm-path",
|
|
3392
|
-
message.id
|
|
3393
|
-
)
|
|
3394
|
-
);
|
|
3395
|
-
break;
|
|
3396
|
-
}
|
|
3397
|
-
// =========================================================================
|
|
3398
|
-
// Ask User
|
|
3399
|
-
// =========================================================================
|
|
3400
|
-
case "ask.answer": {
|
|
3401
|
-
if (!client.activeSessionId) {
|
|
3402
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3403
|
-
return;
|
|
3404
|
-
}
|
|
3405
|
-
if (!isAskAnswerPayload(message.payload)) {
|
|
3406
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid ask.answer payload", message.id));
|
|
3407
|
-
return;
|
|
3408
|
-
}
|
|
3409
|
-
const { callId, answer, skip } = message.payload;
|
|
3410
|
-
const found = provideAnswer(callId, answer, skip);
|
|
3411
|
-
if (!found) {
|
|
3412
|
-
send(createErrorMessage("NOT_FOUND", "No pending question with that ID", message.id));
|
|
3413
|
-
return;
|
|
3414
1593
|
}
|
|
3415
|
-
logger.debug("Ask user answer received", {
|
|
3416
|
-
sessionId: client.activeSessionId,
|
|
3417
|
-
callId,
|
|
3418
|
-
answerLength: answer.length
|
|
3419
|
-
});
|
|
3420
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3421
|
-
break;
|
|
3422
|
-
}
|
|
3423
|
-
default: {
|
|
3424
|
-
send(createErrorMessage("UNKNOWN_MESSAGE", `Unknown message type: ${message.type}`, message.id));
|
|
3425
1594
|
}
|
|
1595
|
+
return parts.sort().join(",");
|
|
3426
1596
|
}
|
|
3427
|
-
}
|
|
1597
|
+
};
|
|
3428
1598
|
|
|
3429
1599
|
// src/server/lsp/server.ts
|
|
3430
|
-
import { spawn
|
|
1600
|
+
import { spawn } from "child_process";
|
|
3431
1601
|
import { extname } from "path";
|
|
3432
1602
|
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node.js";
|
|
3433
1603
|
var LSP = {
|
|
@@ -3489,7 +1659,7 @@ var LspServer = class {
|
|
|
3489
1659
|
}
|
|
3490
1660
|
this.state = "starting";
|
|
3491
1661
|
try {
|
|
3492
|
-
this.process =
|
|
1662
|
+
this.process = spawn(this.commandPath, this.config.serverArgs, {
|
|
3493
1663
|
cwd: this.workdir,
|
|
3494
1664
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3495
1665
|
env: {
|
|
@@ -3755,20 +1925,20 @@ var LspServer = class {
|
|
|
3755
1925
|
};
|
|
3756
1926
|
|
|
3757
1927
|
// src/server/utils/which.ts
|
|
3758
|
-
import { access
|
|
3759
|
-
import { join
|
|
3760
|
-
import { fileURLToPath
|
|
3761
|
-
var __dirname =
|
|
1928
|
+
import { access, constants } from "fs/promises";
|
|
1929
|
+
import { join, dirname } from "path";
|
|
1930
|
+
import { fileURLToPath } from "url";
|
|
1931
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3762
1932
|
function getBundledBinPaths() {
|
|
3763
1933
|
const paths = [];
|
|
3764
|
-
paths.push(
|
|
3765
|
-
paths.push(
|
|
3766
|
-
paths.push(
|
|
1934
|
+
paths.push(join(process.cwd(), "node_modules", ".bin"));
|
|
1935
|
+
paths.push(join(__dirname, "..", "..", "..", "node_modules", ".bin"));
|
|
1936
|
+
paths.push(join(__dirname, "..", "..", "..", "..", "node_modules", ".bin"));
|
|
3767
1937
|
return paths;
|
|
3768
1938
|
}
|
|
3769
1939
|
async function existsExecutable(path) {
|
|
3770
1940
|
try {
|
|
3771
|
-
await
|
|
1941
|
+
await access(path, constants.X_OK);
|
|
3772
1942
|
return true;
|
|
3773
1943
|
} catch {
|
|
3774
1944
|
return false;
|
|
@@ -3782,13 +1952,13 @@ async function which(command, workdir) {
|
|
|
3782
1952
|
return null;
|
|
3783
1953
|
}
|
|
3784
1954
|
for (const binDir of getBundledBinPaths()) {
|
|
3785
|
-
const fullPath =
|
|
1955
|
+
const fullPath = join(binDir, command);
|
|
3786
1956
|
if (await existsExecutable(fullPath)) {
|
|
3787
1957
|
return fullPath;
|
|
3788
1958
|
}
|
|
3789
1959
|
}
|
|
3790
1960
|
if (workdir) {
|
|
3791
|
-
const projectBin =
|
|
1961
|
+
const projectBin = join(workdir, "node_modules", ".bin", command);
|
|
3792
1962
|
if (await existsExecutable(projectBin)) {
|
|
3793
1963
|
return projectBin;
|
|
3794
1964
|
}
|
|
@@ -3797,7 +1967,7 @@ async function which(command, workdir) {
|
|
|
3797
1967
|
const paths = pathEnv.split(getPathSeparator());
|
|
3798
1968
|
for (const dir of paths) {
|
|
3799
1969
|
if (!dir) continue;
|
|
3800
|
-
const fullPath =
|
|
1970
|
+
const fullPath = join(dir, command);
|
|
3801
1971
|
if (await existsExecutable(fullPath)) {
|
|
3802
1972
|
return fullPath;
|
|
3803
1973
|
}
|
|
@@ -5018,8 +3188,8 @@ var SessionManager = class {
|
|
|
5018
3188
|
updateExecutionState(sessionId, updates) {
|
|
5019
3189
|
logger.debug("updateExecutionState called", { sessionId, updates });
|
|
5020
3190
|
}
|
|
5021
|
-
setCachedPrompt(sessionId, systemPrompt, hash) {
|
|
5022
|
-
this.cachedPromptStore.set(sessionId, { systemPrompt, hash });
|
|
3191
|
+
setCachedPrompt(sessionId, systemPrompt, tools, hash) {
|
|
3192
|
+
this.cachedPromptStore.set(sessionId, { systemPrompt, tools, hash });
|
|
5023
3193
|
}
|
|
5024
3194
|
getCachedPrompt(sessionId) {
|
|
5025
3195
|
return this.cachedPromptStore.get(sessionId);
|
|
@@ -5164,6 +3334,7 @@ var SessionManager = class {
|
|
|
5164
3334
|
if (!hasCachedPrompt && eventState.cachedSystemPrompt && eventState.dynamicContextHash) {
|
|
5165
3335
|
this.cachedPromptStore.set(dbSession.id, {
|
|
5166
3336
|
systemPrompt: eventState.cachedSystemPrompt,
|
|
3337
|
+
tools: [],
|
|
5167
3338
|
hash: eventState.dynamicContextHash
|
|
5168
3339
|
});
|
|
5169
3340
|
}
|
|
@@ -5190,7 +3361,7 @@ var SessionManager = class {
|
|
|
5190
3361
|
|
|
5191
3362
|
// src/server/routes/crud-helpers.ts
|
|
5192
3363
|
import { Router } from "express";
|
|
5193
|
-
import { join as
|
|
3364
|
+
import { join as join2 } from "path";
|
|
5194
3365
|
function computeOverrideIds(defaults, userItems) {
|
|
5195
3366
|
return userItems.filter((u) => defaults.some((d) => d.metadata.id === u.metadata.id)).map((u) => u.metadata.id);
|
|
5196
3367
|
}
|
|
@@ -5202,7 +3373,7 @@ function validateNameIdPrompt(body) {
|
|
|
5202
3373
|
return null;
|
|
5203
3374
|
}
|
|
5204
3375
|
function getProjectItemPath(projectDir, dirName, id, ext) {
|
|
5205
|
-
return
|
|
3376
|
+
return join2(projectDir, ".openfox", dirName, `${id}${ext}`);
|
|
5206
3377
|
}
|
|
5207
3378
|
async function isProjectItem(projectDir, dirName, id, ext) {
|
|
5208
3379
|
if (!projectDir) return false;
|
|
@@ -5366,27 +3537,27 @@ function createSkillRoutes(configDir, projectDir) {
|
|
|
5366
3537
|
}
|
|
5367
3538
|
|
|
5368
3539
|
// src/server/commands/registry.ts
|
|
5369
|
-
import { writeFile
|
|
5370
|
-
import { join as
|
|
5371
|
-
import { fileURLToPath as
|
|
3540
|
+
import { writeFile, mkdir, unlink } from "fs/promises";
|
|
3541
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
3542
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5372
3543
|
import matter from "gray-matter";
|
|
5373
|
-
var
|
|
5374
|
-
var
|
|
5375
|
-
var
|
|
3544
|
+
var __bundleDir = dirname2(fileURLToPath2(import.meta.url));
|
|
3545
|
+
var DEFAULTS_DIR = join3(__bundleDir, "defaults");
|
|
3546
|
+
var DEFAULTS_DIR_ALT = join3(__bundleDir, "command-defaults");
|
|
5376
3547
|
var COMMAND_EXTENSION = ".command.md";
|
|
5377
3548
|
function getCommandsDir(configDir) {
|
|
5378
|
-
return
|
|
3549
|
+
return join3(configDir, "commands");
|
|
5379
3550
|
}
|
|
5380
3551
|
function getProjectCommandsDir(projectDir) {
|
|
5381
|
-
return
|
|
3552
|
+
return join3(projectDir, ".openfox", "commands");
|
|
5382
3553
|
}
|
|
5383
3554
|
async function loadDefaultCommands() {
|
|
5384
|
-
let defaults = await loadItemsFromDir(
|
|
3555
|
+
let defaults = await loadItemsFromDir(DEFAULTS_DIR, {
|
|
5385
3556
|
extension: COMMAND_EXTENSION,
|
|
5386
3557
|
logName: "command"
|
|
5387
3558
|
});
|
|
5388
3559
|
if (!defaults.length) {
|
|
5389
|
-
defaults = await loadItemsFromDir(
|
|
3560
|
+
defaults = await loadItemsFromDir(DEFAULTS_DIR_ALT, {
|
|
5390
3561
|
extension: COMMAND_EXTENSION,
|
|
5391
3562
|
logName: "command"
|
|
5392
3563
|
});
|
|
@@ -5423,9 +3594,9 @@ async function loadAllCommands(configDir, projectDir) {
|
|
|
5423
3594
|
return Array.from(commandMap.values());
|
|
5424
3595
|
}
|
|
5425
3596
|
async function getDefaultCommandIds() {
|
|
5426
|
-
const ids = await getDefaultIds(
|
|
3597
|
+
const ids = await getDefaultIds(DEFAULTS_DIR, COMMAND_EXTENSION);
|
|
5427
3598
|
if (ids.length) return ids;
|
|
5428
|
-
return getDefaultIds(
|
|
3599
|
+
return getDefaultIds(DEFAULTS_DIR_ALT, COMMAND_EXTENSION);
|
|
5429
3600
|
}
|
|
5430
3601
|
async function isDefaultCommand(commandId) {
|
|
5431
3602
|
const defaultIds = await getDefaultCommandIds();
|
|
@@ -5435,19 +3606,19 @@ function findCommandById(commandId, commands) {
|
|
|
5435
3606
|
return commands.find((c) => c.metadata.id === commandId);
|
|
5436
3607
|
}
|
|
5437
3608
|
async function commandExists(configDir, commandId, projectDir) {
|
|
5438
|
-
if (await pathExists(
|
|
5439
|
-
if (projectDir && await pathExists(
|
|
3609
|
+
if (await pathExists(join3(getCommandsDir(configDir), `${commandId}${COMMAND_EXTENSION}`))) return true;
|
|
3610
|
+
if (projectDir && await pathExists(join3(getProjectCommandsDir(projectDir), `${commandId}${COMMAND_EXTENSION}`)))
|
|
5440
3611
|
return true;
|
|
5441
3612
|
return false;
|
|
5442
3613
|
}
|
|
5443
3614
|
async function saveCommand(configDir, command) {
|
|
5444
3615
|
const commandsDir = getCommandsDir(configDir);
|
|
5445
3616
|
if (!await pathExists(commandsDir)) {
|
|
5446
|
-
await
|
|
3617
|
+
await mkdir(commandsDir, { recursive: true });
|
|
5447
3618
|
}
|
|
5448
|
-
const filePath =
|
|
3619
|
+
const filePath = join3(commandsDir, `${command.metadata.id}${COMMAND_EXTENSION}`);
|
|
5449
3620
|
const content = matter.stringify(command.prompt, command.metadata);
|
|
5450
|
-
await
|
|
3621
|
+
await writeFile(filePath, content, "utf-8");
|
|
5451
3622
|
}
|
|
5452
3623
|
async function saveCommandToProject(projectDir, command) {
|
|
5453
3624
|
await saveItemToDir(
|
|
@@ -5465,9 +3636,9 @@ async function deleteCommand(configDir, commandId) {
|
|
|
5465
3636
|
if (isDefault) {
|
|
5466
3637
|
return { success: false, reason: "Cannot delete built-in defaults" };
|
|
5467
3638
|
}
|
|
5468
|
-
const filePath =
|
|
3639
|
+
const filePath = join3(getCommandsDir(configDir), `${commandId}${COMMAND_EXTENSION}`);
|
|
5469
3640
|
try {
|
|
5470
|
-
await
|
|
3641
|
+
await unlink(filePath);
|
|
5471
3642
|
return { success: true };
|
|
5472
3643
|
} catch {
|
|
5473
3644
|
return { success: false };
|
|
@@ -5688,8 +3859,8 @@ function createTerminalRoutes() {
|
|
|
5688
3859
|
|
|
5689
3860
|
// src/server/routes/directories.ts
|
|
5690
3861
|
import { Router as Router5 } from "express";
|
|
5691
|
-
import { readdir
|
|
5692
|
-
import { resolve, join as
|
|
3862
|
+
import { readdir } from "fs/promises";
|
|
3863
|
+
import { resolve, join as join4, dirname as dirname3, basename as basename2 } from "path";
|
|
5693
3864
|
function createDirectoryRoutes() {
|
|
5694
3865
|
const router = Router5();
|
|
5695
3866
|
const DEFAULT_BASE_PATH = process.cwd();
|
|
@@ -5697,12 +3868,12 @@ function createDirectoryRoutes() {
|
|
|
5697
3868
|
const path = req.query["path"] || DEFAULT_BASE_PATH;
|
|
5698
3869
|
try {
|
|
5699
3870
|
const resolvedPath = resolve(path);
|
|
5700
|
-
const entries = await
|
|
3871
|
+
const entries = await readdir(resolvedPath, { withFileTypes: true });
|
|
5701
3872
|
const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
5702
3873
|
name: entry.name,
|
|
5703
|
-
path:
|
|
3874
|
+
path: join4(resolvedPath, entry.name)
|
|
5704
3875
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
5705
|
-
const parent =
|
|
3876
|
+
const parent = dirname3(resolvedPath);
|
|
5706
3877
|
const hasParent = parent !== resolvedPath;
|
|
5707
3878
|
res.json({
|
|
5708
3879
|
current: resolvedPath,
|
|
@@ -5725,10 +3896,10 @@ function createDirectoryRoutes() {
|
|
|
5725
3896
|
|
|
5726
3897
|
// src/server/routes/auto-update.ts
|
|
5727
3898
|
import { Router as Router6 } from "express";
|
|
5728
|
-
import { spawn as
|
|
3899
|
+
import { spawn as spawn2 } from "child_process";
|
|
5729
3900
|
|
|
5730
3901
|
// src/constants.ts
|
|
5731
|
-
var VERSION = "2.0.
|
|
3902
|
+
var VERSION = "2.0.14";
|
|
5732
3903
|
|
|
5733
3904
|
// src/server/routes/auto-update.ts
|
|
5734
3905
|
var updateInProgress = false;
|
|
@@ -5752,7 +3923,7 @@ function createAutoUpdateRoutes(options = {}) {
|
|
|
5752
3923
|
const current = VERSION;
|
|
5753
3924
|
try {
|
|
5754
3925
|
const latest = await new Promise((resolve3, reject) => {
|
|
5755
|
-
const child =
|
|
3926
|
+
const child = spawn2("npm", ["view", "openfox", "version"], {
|
|
5756
3927
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5757
3928
|
});
|
|
5758
3929
|
let stdout = "";
|
|
@@ -5790,7 +3961,7 @@ function createAutoUpdateRoutes(options = {}) {
|
|
|
5790
3961
|
updateInProgress = true;
|
|
5791
3962
|
try {
|
|
5792
3963
|
const isService = isRunningAsService();
|
|
5793
|
-
const child =
|
|
3964
|
+
const child = spawn2("bash", ["-c", "openfox update"], {
|
|
5794
3965
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5795
3966
|
});
|
|
5796
3967
|
let stdout = "";
|
|
@@ -5829,7 +4000,7 @@ function createAutoUpdateRoutes(options = {}) {
|
|
|
5829
4000
|
return;
|
|
5830
4001
|
}
|
|
5831
4002
|
try {
|
|
5832
|
-
const child =
|
|
4003
|
+
const child = spawn2("bash", ["-c", "openfox service restart"], {
|
|
5833
4004
|
detached: true,
|
|
5834
4005
|
stdio: "ignore"
|
|
5835
4006
|
});
|
|
@@ -5843,7 +4014,7 @@ function createAutoUpdateRoutes(options = {}) {
|
|
|
5843
4014
|
}
|
|
5844
4015
|
|
|
5845
4016
|
// src/server/index.ts
|
|
5846
|
-
var __dirname2 =
|
|
4017
|
+
var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
|
|
5847
4018
|
async function createServerHandle(config4) {
|
|
5848
4019
|
setRuntimeConfig(config4);
|
|
5849
4020
|
setLogLevel(config4.logging?.level ?? void 0, config4.mode);
|
|
@@ -5892,6 +4063,21 @@ async function createServerHandle(config4) {
|
|
|
5892
4063
|
(err) => logger.error("LLM initialization failed", { error: err instanceof Error ? err.message : String(err) })
|
|
5893
4064
|
);
|
|
5894
4065
|
const toolRegistry = createToolRegistry();
|
|
4066
|
+
const mcpManager = new McpManager();
|
|
4067
|
+
const mcpServers = config4.mcpServers ?? {};
|
|
4068
|
+
Promise.all(
|
|
4069
|
+
Object.entries(mcpServers).map(
|
|
4070
|
+
([name, serverConfig]) => mcpManager.addServer(name, serverConfig).catch((err) => {
|
|
4071
|
+
logger.warn("Failed to connect MCP server on startup", { name, error: String(err) });
|
|
4072
|
+
})
|
|
4073
|
+
)
|
|
4074
|
+
).then(() => {
|
|
4075
|
+
const mcpTools = createMcpTools(mcpManager);
|
|
4076
|
+
if (mcpTools.length > 0) {
|
|
4077
|
+
setMcpTools(mcpTools);
|
|
4078
|
+
logger.info("MCP tools registered", { count: mcpTools.length });
|
|
4079
|
+
}
|
|
4080
|
+
});
|
|
5895
4081
|
const app = express();
|
|
5896
4082
|
app.use(cors());
|
|
5897
4083
|
app.use(express.json({ limit: "10mb" }));
|
|
@@ -6080,7 +4266,7 @@ async function createServerHandle(config4) {
|
|
|
6080
4266
|
app.get("/api/sessions/:id", async (req, res) => {
|
|
6081
4267
|
const { getEventStore: getEventStore2 } = await import("./events-4K52FKPR.js");
|
|
6082
4268
|
const { buildMessagesFromStoredEvents } = await import("./folding-QIBNKYA6.js");
|
|
6083
|
-
const { getPendingQuestionsForSession } = await import("./tools-
|
|
4269
|
+
const { getPendingQuestionsForSession } = await import("./tools-DUC54GEH.js");
|
|
6084
4270
|
const session = sessionManager.getSession(req.params.id);
|
|
6085
4271
|
if (!session) {
|
|
6086
4272
|
return res.status(404).json({ error: "Session not found" });
|
|
@@ -6123,7 +4309,7 @@ async function createServerHandle(config4) {
|
|
|
6123
4309
|
return res.status(400).json({ error: "providerId is required" });
|
|
6124
4310
|
}
|
|
6125
4311
|
sessionManager.setSessionProvider(sessionId, providerId, model ?? "auto");
|
|
6126
|
-
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-
|
|
4312
|
+
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-YFHA5MWV.js");
|
|
6127
4313
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6128
4314
|
const updatedConfig = setDefaultModelSelection(globalConfig, providerId, model ?? "auto");
|
|
6129
4315
|
await saveGlobalConfig(config4.mode ?? "production", updatedConfig);
|
|
@@ -6211,7 +4397,7 @@ async function createServerHandle(config4) {
|
|
|
6211
4397
|
if (!callId || approved === void 0) {
|
|
6212
4398
|
return res.status(400).json({ error: "callId and approved are required" });
|
|
6213
4399
|
}
|
|
6214
|
-
const { providePathConfirmation } = await import("./tools-
|
|
4400
|
+
const { providePathConfirmation } = await import("./tools-DUC54GEH.js");
|
|
6215
4401
|
const result = providePathConfirmation(callId, approved, alwaysAllow);
|
|
6216
4402
|
if (!result.found) {
|
|
6217
4403
|
return res.status(404).json({ error: "No pending path confirmation with that ID" });
|
|
@@ -6219,7 +4405,7 @@ async function createServerHandle(config4) {
|
|
|
6219
4405
|
const { getEventStore: getEventStore2 } = await import("./events-4K52FKPR.js");
|
|
6220
4406
|
const { buildMessagesFromStoredEvents, foldPendingConfirmations } = await import("./folding-QIBNKYA6.js");
|
|
6221
4407
|
const { createSessionStateMessage } = await import("./protocol-YYWMFR35.js");
|
|
6222
|
-
const { getPendingQuestionsForSession } = await import("./tools-
|
|
4408
|
+
const { getPendingQuestionsForSession } = await import("./tools-DUC54GEH.js");
|
|
6223
4409
|
const eventStore = getEventStore2();
|
|
6224
4410
|
const events = eventStore.getEvents(sessionId);
|
|
6225
4411
|
const messages = buildMessagesFromStoredEvents(events);
|
|
@@ -6240,8 +4426,8 @@ async function createServerHandle(config4) {
|
|
|
6240
4426
|
if (!skip && typeof answer !== "string") {
|
|
6241
4427
|
return res.status(400).json({ error: "answer is required when not skipping" });
|
|
6242
4428
|
}
|
|
6243
|
-
const { provideAnswer
|
|
6244
|
-
const found =
|
|
4429
|
+
const { provideAnswer } = await import("./tools-DUC54GEH.js");
|
|
4430
|
+
const found = provideAnswer(callId, answer ?? "", skip ?? false);
|
|
6245
4431
|
if (!found) {
|
|
6246
4432
|
return res.status(404).json({ error: "No pending question with that ID" });
|
|
6247
4433
|
}
|
|
@@ -6276,8 +4462,8 @@ async function createServerHandle(config4) {
|
|
|
6276
4462
|
if (!session) {
|
|
6277
4463
|
return res.status(404).json({ error: "Session not found" });
|
|
6278
4464
|
}
|
|
6279
|
-
const { stopSessionExecution } = await import("./chat-handler-
|
|
6280
|
-
const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-
|
|
4465
|
+
const { stopSessionExecution } = await import("./chat-handler-ITXCBFJK.js");
|
|
4466
|
+
const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-DUC54GEH.js");
|
|
6281
4467
|
const queuedMessages = sessionManager.getQueueState(sessionId);
|
|
6282
4468
|
sessionManager.clearMessageQueue(sessionId);
|
|
6283
4469
|
stopSessionExecution(sessionId, sessionManager);
|
|
@@ -6388,7 +4574,7 @@ async function createServerHandle(config4) {
|
|
|
6388
4574
|
let visionFallback;
|
|
6389
4575
|
let globalWorkdir;
|
|
6390
4576
|
try {
|
|
6391
|
-
const { loadGlobalConfig, getVisionFallback } = await import("./config-
|
|
4577
|
+
const { loadGlobalConfig, getVisionFallback } = await import("./config-YFHA5MWV.js");
|
|
6392
4578
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6393
4579
|
const fallback = getVisionFallback(globalConfig);
|
|
6394
4580
|
if (fallback) {
|
|
@@ -6484,7 +4670,7 @@ async function createServerHandle(config4) {
|
|
|
6484
4670
|
const apiKey = req.query["apiKey"];
|
|
6485
4671
|
if (!url) return res.status(400).json({ error: "url is required" });
|
|
6486
4672
|
try {
|
|
6487
|
-
const { fetchModelsWithContext } = await import("./provider-manager-
|
|
4673
|
+
const { fetchModelsWithContext } = await import("./provider-manager-DETXLDKW.js");
|
|
6488
4674
|
const models = await fetchModelsWithContext(url, apiKey);
|
|
6489
4675
|
if (models.length === 0) {
|
|
6490
4676
|
return res.status(404).json({ error: `No models found at ${buildModelsUrl(url)}`, url });
|
|
@@ -6554,7 +4740,7 @@ async function createServerHandle(config4) {
|
|
|
6554
4740
|
return res.status(400).json({ error: "name, url, and backend are required" });
|
|
6555
4741
|
}
|
|
6556
4742
|
try {
|
|
6557
|
-
const { loadGlobalConfig, saveGlobalConfig, addProvider, setDefaultModelSelection } = await import("./config-
|
|
4743
|
+
const { loadGlobalConfig, saveGlobalConfig, addProvider, setDefaultModelSelection } = await import("./config-YFHA5MWV.js");
|
|
6558
4744
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6559
4745
|
const providerBackend = backend;
|
|
6560
4746
|
const configWithProvider = addProvider(globalConfig, {
|
|
@@ -6589,7 +4775,7 @@ async function createServerHandle(config4) {
|
|
|
6589
4775
|
app.post("/api/init/config", async (req, res) => {
|
|
6590
4776
|
const { workdir, visionFallback } = req.body;
|
|
6591
4777
|
try {
|
|
6592
|
-
const { loadGlobalConfig, saveGlobalConfig } = await import("./config-
|
|
4778
|
+
const { loadGlobalConfig, saveGlobalConfig } = await import("./config-YFHA5MWV.js");
|
|
6593
4779
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6594
4780
|
const updatedConfig = {
|
|
6595
4781
|
...globalConfig,
|
|
@@ -6617,7 +4803,7 @@ async function createServerHandle(config4) {
|
|
|
6617
4803
|
});
|
|
6618
4804
|
app.delete("/api/providers/:id", async (req, res) => {
|
|
6619
4805
|
const { id } = req.params;
|
|
6620
|
-
const { loadGlobalConfig, saveGlobalConfig, removeProvider } = await import("./config-
|
|
4806
|
+
const { loadGlobalConfig, saveGlobalConfig, removeProvider } = await import("./config-YFHA5MWV.js");
|
|
6621
4807
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6622
4808
|
const updatedConfig = removeProvider(globalConfig, id);
|
|
6623
4809
|
await saveGlobalConfig(config4.mode ?? "production", updatedConfig);
|
|
@@ -6628,7 +4814,7 @@ async function createServerHandle(config4) {
|
|
|
6628
4814
|
const { id } = req.params;
|
|
6629
4815
|
const { isLocal } = req.body;
|
|
6630
4816
|
try {
|
|
6631
|
-
const { loadGlobalConfig, saveGlobalConfig, updateProvider } = await import("./config-
|
|
4817
|
+
const { loadGlobalConfig, saveGlobalConfig, updateProvider } = await import("./config-YFHA5MWV.js");
|
|
6632
4818
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6633
4819
|
const provider = globalConfig.providers.find((p) => p.id === id);
|
|
6634
4820
|
if (!provider) {
|
|
@@ -6656,7 +4842,7 @@ async function createServerHandle(config4) {
|
|
|
6656
4842
|
models: modelConfigs
|
|
6657
4843
|
} = req.body;
|
|
6658
4844
|
try {
|
|
6659
|
-
const { loadGlobalConfig, saveGlobalConfig, updateProvider } = await import("./config-
|
|
4845
|
+
const { loadGlobalConfig, saveGlobalConfig, updateProvider } = await import("./config-YFHA5MWV.js");
|
|
6660
4846
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6661
4847
|
const provider = globalConfig.providers.find((p) => p.id === id);
|
|
6662
4848
|
if (!provider) {
|
|
@@ -6693,7 +4879,7 @@ async function createServerHandle(config4) {
|
|
|
6693
4879
|
return res.status(400).json({ error: result.error });
|
|
6694
4880
|
}
|
|
6695
4881
|
const llmClient = getLLMClient();
|
|
6696
|
-
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-
|
|
4882
|
+
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-YFHA5MWV.js");
|
|
6697
4883
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6698
4884
|
const updatedConfig = setDefaultModelSelection(globalConfig, id, llmClient.getModel());
|
|
6699
4885
|
await saveGlobalConfig(config4.mode ?? "production", updatedConfig);
|
|
@@ -6718,6 +4904,131 @@ async function createServerHandle(config4) {
|
|
|
6718
4904
|
status: updatedProvider?.status ?? "unknown"
|
|
6719
4905
|
});
|
|
6720
4906
|
});
|
|
4907
|
+
async function rebuildMcpTools() {
|
|
4908
|
+
const { createMcpTools: createMcpTools2 } = await import("./tool-adapter-B7QP6NLA.js");
|
|
4909
|
+
const { setMcpTools: setMcpTools2 } = await import("./tools-DUC54GEH.js");
|
|
4910
|
+
const mcpTools = createMcpTools2(mcpManager);
|
|
4911
|
+
setMcpTools2(mcpTools);
|
|
4912
|
+
}
|
|
4913
|
+
app.get("/api/mcp/servers", (_req, res) => {
|
|
4914
|
+
const servers = mcpManager.getAllServers();
|
|
4915
|
+
res.json({ servers });
|
|
4916
|
+
});
|
|
4917
|
+
app.post("/api/mcp/servers/test", async (req, res) => {
|
|
4918
|
+
const { name, command, args, env } = req.body;
|
|
4919
|
+
if (!command) {
|
|
4920
|
+
return res.status(400).json({ error: "command is required" });
|
|
4921
|
+
}
|
|
4922
|
+
try {
|
|
4923
|
+
const testManager = new McpManager();
|
|
4924
|
+
const testConfig = {
|
|
4925
|
+
transport: "stdio",
|
|
4926
|
+
command,
|
|
4927
|
+
...args && args.length > 0 ? { args } : {},
|
|
4928
|
+
...env && Object.keys(env).length > 0 ? { env } : {}
|
|
4929
|
+
};
|
|
4930
|
+
await testManager.addServer(name ?? "test", testConfig);
|
|
4931
|
+
const server = testManager.getServer(name ?? "test");
|
|
4932
|
+
await testManager.disconnectAll();
|
|
4933
|
+
if (server?.status === "connected") {
|
|
4934
|
+
res.json({ success: true, tools: server.tools.map((t) => t.name) });
|
|
4935
|
+
} else {
|
|
4936
|
+
res.json({ success: false, error: server?.error ?? "Connection failed" });
|
|
4937
|
+
}
|
|
4938
|
+
} catch (error) {
|
|
4939
|
+
res.status(400).json({ success: false, error: error instanceof Error ? error.message : String(error) });
|
|
4940
|
+
}
|
|
4941
|
+
});
|
|
4942
|
+
app.post("/api/mcp/servers", async (req, res) => {
|
|
4943
|
+
const { name, transport, command, args, env, url } = req.body;
|
|
4944
|
+
if (!name) {
|
|
4945
|
+
return res.status(400).json({ error: "name is required" });
|
|
4946
|
+
}
|
|
4947
|
+
try {
|
|
4948
|
+
const serverCfg = {
|
|
4949
|
+
transport: transport ?? "stdio",
|
|
4950
|
+
...command ? { command } : {},
|
|
4951
|
+
...args && args.length > 0 ? { args } : {},
|
|
4952
|
+
...env && Object.keys(env).length > 0 ? { env } : {},
|
|
4953
|
+
...url ? { url } : {}
|
|
4954
|
+
};
|
|
4955
|
+
await mcpManager.addServer(name, serverCfg);
|
|
4956
|
+
const server = mcpManager.getServer(name);
|
|
4957
|
+
const { loadGlobalConfig, saveGlobalConfig } = await import("./config-YFHA5MWV.js");
|
|
4958
|
+
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
4959
|
+
const updatedMcpServers = { ...globalConfig.mcpServers ?? {}, [name]: serverCfg };
|
|
4960
|
+
await saveGlobalConfig(config4.mode ?? "production", {
|
|
4961
|
+
...globalConfig,
|
|
4962
|
+
mcpServers: updatedMcpServers
|
|
4963
|
+
});
|
|
4964
|
+
await rebuildMcpTools();
|
|
4965
|
+
if (server) {
|
|
4966
|
+
const sessions = sessionManager.listSessions();
|
|
4967
|
+
for (const s of sessions) {
|
|
4968
|
+
sessionManager.setDynamicContextChanged(s.id, true);
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
res.status(201).json({ server });
|
|
4972
|
+
} catch (error) {
|
|
4973
|
+
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
|
|
4974
|
+
}
|
|
4975
|
+
});
|
|
4976
|
+
app.delete("/api/mcp/servers/:name", async (req, res) => {
|
|
4977
|
+
const { name } = req.params;
|
|
4978
|
+
const server = mcpManager.getServer(name);
|
|
4979
|
+
if (!server) {
|
|
4980
|
+
return res.status(404).json({ error: `MCP server '${name}' not found` });
|
|
4981
|
+
}
|
|
4982
|
+
mcpManager.removeServer(name);
|
|
4983
|
+
const { loadGlobalConfig, saveGlobalConfig } = await import("./config-YFHA5MWV.js");
|
|
4984
|
+
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
4985
|
+
const updatedMcpServers = { ...globalConfig.mcpServers ?? {} };
|
|
4986
|
+
delete updatedMcpServers[name];
|
|
4987
|
+
await saveGlobalConfig(config4.mode ?? "production", {
|
|
4988
|
+
...globalConfig,
|
|
4989
|
+
mcpServers: updatedMcpServers
|
|
4990
|
+
});
|
|
4991
|
+
await rebuildMcpTools();
|
|
4992
|
+
const sessions = sessionManager.listSessions();
|
|
4993
|
+
for (const s of sessions) {
|
|
4994
|
+
sessionManager.setDynamicContextChanged(s.id, true);
|
|
4995
|
+
}
|
|
4996
|
+
res.json({ success: true });
|
|
4997
|
+
});
|
|
4998
|
+
app.put("/api/mcp/servers/:name/tools/:toolName", async (req, res) => {
|
|
4999
|
+
const { name, toolName } = req.params;
|
|
5000
|
+
const { enabled } = req.body;
|
|
5001
|
+
if (enabled === void 0) {
|
|
5002
|
+
return res.status(400).json({ error: "enabled is required" });
|
|
5003
|
+
}
|
|
5004
|
+
try {
|
|
5005
|
+
await mcpManager.setToolEnabled(name, toolName, enabled);
|
|
5006
|
+
const server = mcpManager.getServer(name);
|
|
5007
|
+
if (server) {
|
|
5008
|
+
const { loadGlobalConfig, saveGlobalConfig } = await import("./config-YFHA5MWV.js");
|
|
5009
|
+
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
5010
|
+
const mcpServers2 = { ...globalConfig.mcpServers ?? {} };
|
|
5011
|
+
const serverCfg = mcpServers2[name];
|
|
5012
|
+
if (serverCfg) {
|
|
5013
|
+
const disabledTools = server.tools.filter((t) => !t.enabled).map((t) => t.name);
|
|
5014
|
+
const cfg = { ...serverCfg, ...disabledTools.length > 0 ? { disabledTools } : { disabledTools: void 0 } };
|
|
5015
|
+
mcpServers2[name] = cfg;
|
|
5016
|
+
await saveGlobalConfig(config4.mode ?? "production", {
|
|
5017
|
+
...globalConfig,
|
|
5018
|
+
mcpServers: mcpServers2
|
|
5019
|
+
});
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
5022
|
+
await rebuildMcpTools();
|
|
5023
|
+
const sessions = sessionManager.listSessions();
|
|
5024
|
+
for (const s of sessions) {
|
|
5025
|
+
sessionManager.setDynamicContextChanged(s.id, true);
|
|
5026
|
+
}
|
|
5027
|
+
res.json({ success: true });
|
|
5028
|
+
} catch (error) {
|
|
5029
|
+
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
|
|
5030
|
+
}
|
|
5031
|
+
});
|
|
6721
5032
|
const projectDir = config4.workdir;
|
|
6722
5033
|
app.use("/api/skills", createSkillRoutes(configDir, projectDir));
|
|
6723
5034
|
app.use("/api/commands", createCommandRoutes(configDir, projectDir));
|
|
@@ -6792,7 +5103,7 @@ async function createServerHandle(config4) {
|
|
|
6792
5103
|
}
|
|
6793
5104
|
});
|
|
6794
5105
|
app.get("/fox.svg", (_req, res) => {
|
|
6795
|
-
|
|
5106
|
+
readFile(join5(webDir, "fox.svg")).then((content) => {
|
|
6796
5107
|
res.set("Content-Type", "image/svg+xml");
|
|
6797
5108
|
res.send(content);
|
|
6798
5109
|
}).catch(() => {
|
|
@@ -6801,7 +5112,7 @@ async function createServerHandle(config4) {
|
|
|
6801
5112
|
});
|
|
6802
5113
|
app.use(
|
|
6803
5114
|
"/sounds",
|
|
6804
|
-
express.static(
|
|
5115
|
+
express.static(join5(webDir, "public", "sounds"), {
|
|
6805
5116
|
setHeaders: (res) => {
|
|
6806
5117
|
res.set("Content-Type", "audio/mpeg");
|
|
6807
5118
|
}
|
|
@@ -6810,13 +5121,13 @@ async function createServerHandle(config4) {
|
|
|
6810
5121
|
app.use("/__inspect__.js", (_req, res) => {
|
|
6811
5122
|
res.set("Access-Control-Allow-Origin", "*");
|
|
6812
5123
|
res.set("Content-Type", "application/javascript");
|
|
6813
|
-
|
|
5124
|
+
readFile(join5(webDir, "public", "__inspect__.js")).then((content) => res.send(content)).catch(() => res.status(404).send("Not found"));
|
|
6814
5125
|
});
|
|
6815
5126
|
app.get("/*path", (req, res) => {
|
|
6816
5127
|
if (req.path.startsWith("/api/")) {
|
|
6817
5128
|
return;
|
|
6818
5129
|
}
|
|
6819
|
-
|
|
5130
|
+
readFile(join5(webDir, "index.html"), "utf-8").then((indexHtml) => viteServer.transformIndexHtml(req.originalUrl, indexHtml)).then((transformed) => res.send(transformed)).catch((err) => {
|
|
6820
5131
|
logger.error("Error serving index.html", { error: err });
|
|
6821
5132
|
res.status(500).send("Server error");
|
|
6822
5133
|
});
|
|
@@ -6827,7 +5138,7 @@ async function createServerHandle(config4) {
|
|
|
6827
5138
|
const distWebDir = resolve2(__dirname2, "web");
|
|
6828
5139
|
app.use(
|
|
6829
5140
|
"/assets",
|
|
6830
|
-
express.static(
|
|
5141
|
+
express.static(join5(distWebDir, "assets"), {
|
|
6831
5142
|
setHeaders: (res, filepath) => {
|
|
6832
5143
|
if (filepath.endsWith(".css")) {
|
|
6833
5144
|
res.set("Content-Type", "text/css");
|
|
@@ -6835,11 +5146,11 @@ async function createServerHandle(config4) {
|
|
|
6835
5146
|
}
|
|
6836
5147
|
})
|
|
6837
5148
|
);
|
|
6838
|
-
app.use("/manifest.webmanifest", express.static(
|
|
6839
|
-
app.use("/registerSW.js", express.static(
|
|
6840
|
-
app.use("/sw.js", express.static(
|
|
5149
|
+
app.use("/manifest.webmanifest", express.static(join5(distWebDir, "manifest.webmanifest")));
|
|
5150
|
+
app.use("/registerSW.js", express.static(join5(distWebDir, "registerSW.js")));
|
|
5151
|
+
app.use("/sw.js", express.static(join5(distWebDir, "sw.js")));
|
|
6841
5152
|
app.get("/fox.svg", (_req, res) => {
|
|
6842
|
-
|
|
5153
|
+
readFile(join5(distWebDir, "fox.svg")).then((content) => {
|
|
6843
5154
|
res.set("Content-Type", "image/svg+xml");
|
|
6844
5155
|
res.send(content);
|
|
6845
5156
|
}).catch(() => {
|
|
@@ -6848,7 +5159,7 @@ async function createServerHandle(config4) {
|
|
|
6848
5159
|
});
|
|
6849
5160
|
app.use(
|
|
6850
5161
|
"/sounds",
|
|
6851
|
-
express.static(
|
|
5162
|
+
express.static(join5(distWebDir, "sounds"), {
|
|
6852
5163
|
setHeaders: (res) => {
|
|
6853
5164
|
res.set("Content-Type", "audio/mpeg");
|
|
6854
5165
|
}
|
|
@@ -6857,15 +5168,15 @@ async function createServerHandle(config4) {
|
|
|
6857
5168
|
app.use("/__inspect__.js", (_req, res) => {
|
|
6858
5169
|
res.set("Access-Control-Allow-Origin", "*");
|
|
6859
5170
|
res.set("Content-Type", "application/javascript");
|
|
6860
|
-
|
|
5171
|
+
readFile(join5(distWebDir, "__inspect__.js")).then((content) => res.send(content)).catch(() => res.status(404).send("Not found"));
|
|
6861
5172
|
});
|
|
6862
5173
|
app.use("/__inspect__.js", (_req, res) => {
|
|
6863
5174
|
res.set("Access-Control-Allow-Origin", "*");
|
|
6864
5175
|
res.set("Content-Type", "application/javascript");
|
|
6865
|
-
|
|
5176
|
+
readFile(join5(distWebDir, "__inspect__.js")).then((content) => res.send(content)).catch(() => res.status(404).send("Not found"));
|
|
6866
5177
|
});
|
|
6867
5178
|
app.get("/", (_req, res) => {
|
|
6868
|
-
|
|
5179
|
+
readFile(join5(distWebDir, "index.html"), "utf-8").then((content) => res.send(content)).catch(() => {
|
|
6869
5180
|
res.status(404).send("Web UI not built. Run `npm run build:web`");
|
|
6870
5181
|
});
|
|
6871
5182
|
});
|
|
@@ -6873,12 +5184,14 @@ async function createServerHandle(config4) {
|
|
|
6873
5184
|
if (req.path.startsWith("/api/") || req.path.startsWith("/assets/") || req.path.startsWith("/sounds/") || req.path.startsWith("/manifest.webmanifest") || req.path.startsWith("/registerSW.js") || req.path.startsWith("/sw.js") || req.path === "/fox.svg") {
|
|
6874
5185
|
return;
|
|
6875
5186
|
}
|
|
6876
|
-
|
|
5187
|
+
readFile(join5(distWebDir, "index.html"), "utf-8").then((content) => res.send(content)).catch(() => {
|
|
6877
5188
|
res.status(404).send("Web UI not built");
|
|
6878
5189
|
});
|
|
6879
5190
|
});
|
|
6880
5191
|
}
|
|
6881
5192
|
const httpServer = createHttpServer(app);
|
|
5193
|
+
const { setMcpManagerForWs } = await import("./server-EA6W7YIC.js");
|
|
5194
|
+
setMcpManagerForWs(mcpManager);
|
|
6882
5195
|
const wssExports = createWebSocketServer(
|
|
6883
5196
|
httpServer,
|
|
6884
5197
|
config4,
|
|
@@ -6888,7 +5201,7 @@ async function createServerHandle(config4) {
|
|
|
6888
5201
|
providerManager
|
|
6889
5202
|
);
|
|
6890
5203
|
const wss = wssExports.wss;
|
|
6891
|
-
const { QueueProcessor } = await import("./processor-
|
|
5204
|
+
const { QueueProcessor } = await import("./processor-CCXZYLX6.js");
|
|
6892
5205
|
const queueProcessor = new QueueProcessor({
|
|
6893
5206
|
sessionManager,
|
|
6894
5207
|
providerManager,
|
|
@@ -6928,6 +5241,7 @@ async function createServerHandle(config4) {
|
|
|
6928
5241
|
logger.info("Shutting down...");
|
|
6929
5242
|
void (async () => {
|
|
6930
5243
|
await devServerManager.stopAll();
|
|
5244
|
+
await mcpManager.disconnectAll();
|
|
6931
5245
|
const { stopAllInspectProxies } = await import("./inspect-proxy-42ZXL2R5.js");
|
|
6932
5246
|
stopAllInspectProxies();
|
|
6933
5247
|
const { cleanupAllProcesses } = await import("./store-OQ45KONN.js");
|
|
@@ -6962,4 +5276,4 @@ export {
|
|
|
6962
5276
|
createServerHandle,
|
|
6963
5277
|
createServer
|
|
6964
5278
|
};
|
|
6965
|
-
//# sourceMappingURL=chunk-
|
|
5279
|
+
//# sourceMappingURL=chunk-4U45A7B3.js.map
|