openfox 2.0.13 → 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-VFRWE5X4.js → chunk-4U45A7B3.js} +386 -2075
- 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-FQIT7VIA.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-TJHQ326L.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,1975 +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
|
-
}).finally(() => {
|
|
3213
|
-
sessionManager.setRunning(sessionId, false);
|
|
3214
|
-
sendForSession(sessionId, createSessionRunningMessage(false));
|
|
3215
|
-
});
|
|
3216
|
-
break;
|
|
3217
|
-
}
|
|
3218
|
-
case "context.checkDynamic": {
|
|
3219
|
-
if (!client.activeSessionId) {
|
|
3220
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3221
|
-
return;
|
|
3222
|
-
}
|
|
3223
|
-
const sessionId = client.activeSessionId;
|
|
3224
|
-
const session = sessionManager.requireSession(sessionId);
|
|
3225
|
-
(async () => {
|
|
3226
|
-
try {
|
|
3227
|
-
const { content: instructionContent } = await getAllInstructions(session.workdir, session.projectId);
|
|
3228
|
-
const runtimeConfig = getRuntimeConfig();
|
|
3229
|
-
const configDir = getGlobalConfigDir(runtimeConfig.mode ?? "production");
|
|
3230
|
-
const skills = await getEnabledSkillMetadata(configDir, runtimeConfig.workdir);
|
|
3231
|
-
const currentHash = computeDynamicContextHash(instructionContent, skills);
|
|
3232
|
-
const cachedHash = sessionManager.getCachedPrompt(sessionId)?.hash;
|
|
3233
|
-
if (cachedHash) {
|
|
3234
|
-
if (currentHash !== cachedHash) {
|
|
3235
|
-
if (!sessionManager.getDynamicContextChanged(sessionId)) {
|
|
3236
|
-
sessionManager.setDynamicContextChanged(sessionId, true);
|
|
3237
|
-
const newContextState = sessionManager.getContextState(sessionId);
|
|
3238
|
-
sendForSession(sessionId, createContextStateMessage(newContextState));
|
|
3239
|
-
}
|
|
3240
|
-
} else if (sessionManager.getDynamicContextChanged(sessionId)) {
|
|
3241
|
-
sessionManager.setDynamicContextChanged(sessionId, false);
|
|
3242
|
-
const newContextState = sessionManager.getContextState(sessionId);
|
|
3243
|
-
sendForSession(sessionId, createContextStateMessage(newContextState));
|
|
3244
|
-
}
|
|
3245
|
-
}
|
|
3246
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3247
|
-
} catch (error) {
|
|
3248
|
-
logger.error("Failed to check dynamic context", { error, sessionId });
|
|
3249
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3250
|
-
}
|
|
3251
|
-
})();
|
|
3252
|
-
break;
|
|
3253
|
-
}
|
|
3254
|
-
case "context.applyDynamic": {
|
|
3255
|
-
if (!client.activeSessionId) {
|
|
3256
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3257
|
-
return;
|
|
3258
|
-
}
|
|
3259
|
-
const sessionId = client.activeSessionId;
|
|
3260
|
-
const session = sessionManager.requireSession(sessionId);
|
|
3261
|
-
if (session.isRunning) {
|
|
3262
|
-
send(createErrorMessage("SESSION_RUNNING", "Cannot apply dynamic context while session is running", message.id));
|
|
3263
|
-
return;
|
|
3264
|
-
}
|
|
3265
|
-
;
|
|
3266
|
-
(async () => {
|
|
3267
|
-
try {
|
|
3268
|
-
const { content: instructionContent } = await getAllInstructions(session.workdir, session.projectId);
|
|
3269
|
-
const runtimeConfig = getRuntimeConfig();
|
|
3270
|
-
const configDir = getGlobalConfigDir(runtimeConfig.mode ?? "production");
|
|
3271
|
-
const skills = await getEnabledSkillMetadata(configDir, runtimeConfig.workdir);
|
|
3272
|
-
const dynamicHash = computeDynamicContextHash(instructionContent, skills);
|
|
3273
|
-
const allAgents = await loadAllAgentsDefault();
|
|
3274
|
-
const subAgentDefs = getSubAgents(allAgents);
|
|
3275
|
-
const systemPrompt = buildTopLevelSystemPrompt(
|
|
3276
|
-
session.workdir,
|
|
3277
|
-
instructionContent || void 0,
|
|
3278
|
-
skills,
|
|
3279
|
-
subAgentDefs
|
|
3280
|
-
);
|
|
3281
|
-
sessionManager.setCachedPrompt(sessionId, systemPrompt, dynamicHash);
|
|
3282
|
-
sessionManager.setDynamicContextChanged(sessionId, false);
|
|
3283
|
-
const newContextState = sessionManager.getContextState(sessionId);
|
|
3284
|
-
sendForSession(sessionId, createContextStateMessage(newContextState));
|
|
3285
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3286
|
-
} catch (error) {
|
|
3287
|
-
logger.error("Failed to apply dynamic context", { error, sessionId });
|
|
3288
|
-
sendForSession(
|
|
3289
|
-
sessionId,
|
|
3290
|
-
createChatErrorMessage(
|
|
3291
|
-
`Failed to apply dynamic context: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
3292
|
-
true
|
|
3293
|
-
)
|
|
3294
|
-
);
|
|
3295
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3296
|
-
}
|
|
3297
|
-
})();
|
|
3298
|
-
break;
|
|
3299
|
-
}
|
|
3300
|
-
// =========================================================================
|
|
3301
|
-
// Runner (Auto-Loop)
|
|
3302
|
-
// =========================================================================
|
|
3303
|
-
case "runner.launch": {
|
|
3304
|
-
if (!client.activeSessionId) {
|
|
3305
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3306
|
-
return;
|
|
3307
|
-
}
|
|
3308
|
-
const session = sessionManager.requireSession(client.activeSessionId);
|
|
3309
|
-
if (session.isRunning) {
|
|
3310
|
-
const launchPayload2 = message.payload;
|
|
3311
|
-
const content = launchPayload2?.content ?? "";
|
|
3312
|
-
const attachments = launchPayload2?.attachments;
|
|
3313
|
-
const workflowId = launchPayload2?.workflowId;
|
|
3314
|
-
let fullContent = content;
|
|
3315
|
-
if (workflowId) {
|
|
3316
|
-
const workflowInfo = `// Workflow: ${workflowId}`;
|
|
3317
|
-
fullContent = content ? `${workflowInfo}
|
|
3318
|
-
|
|
3319
|
-
${content}` : workflowInfo;
|
|
3320
|
-
}
|
|
3321
|
-
sessionManager.queueMessage(client.activeSessionId, "asap", fullContent, attachments, "workflow-launch");
|
|
3322
|
-
const queueState = sessionManager.getQueueState(client.activeSessionId);
|
|
3323
|
-
send({
|
|
3324
|
-
type: "queue.state",
|
|
3325
|
-
payload: { success: true, queueState },
|
|
3326
|
-
id: message.id
|
|
3327
|
-
});
|
|
3328
|
-
return;
|
|
3329
|
-
}
|
|
3330
|
-
const launchPayloadEarly = message.payload;
|
|
3331
|
-
const pendingCriteria = session.criteria.filter((c) => c.status.type !== "passed");
|
|
3332
|
-
if (!launchPayloadEarly?.workflowId && pendingCriteria.length === 0) {
|
|
3333
|
-
send(createErrorMessage("NO_WORK", "No pending criteria to work on", message.id));
|
|
3334
|
-
return;
|
|
3335
|
-
}
|
|
3336
|
-
const sessionId = client.activeSessionId;
|
|
3337
|
-
if (session.phase === "blocked") {
|
|
3338
|
-
logger.info("User launched runner - resetting blocked state", { sessionId });
|
|
3339
|
-
sessionManager.setPhase(sessionId, "build");
|
|
3340
|
-
}
|
|
3341
|
-
const launchPayload = message.payload;
|
|
3342
|
-
const launchAttachments = launchPayload?.attachments;
|
|
3343
|
-
const hasUserContent = launchPayload?.content && typeof launchPayload.content === "string" && launchPayload.content.trim();
|
|
3344
|
-
const hasUserAttachments = launchAttachments && launchAttachments.length > 0;
|
|
3345
|
-
const hasUserMessage = hasUserContent || hasUserAttachments;
|
|
3346
|
-
sessionManager.setRunning(sessionId, true);
|
|
3347
|
-
sendForSession(sessionId, createSessionRunningMessage(true));
|
|
3348
|
-
const controller = new AbortController();
|
|
3349
|
-
const existingController = activeAgents.get(sessionId);
|
|
3350
|
-
if (existingController) {
|
|
3351
|
-
logger.warn("Aborting existing agent before starting new one", { sessionId });
|
|
3352
|
-
existingController.abort();
|
|
3353
|
-
}
|
|
3354
|
-
activeAgents.set(sessionId, controller);
|
|
3355
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3356
|
-
ensureEventStoreSubscription(sessionId);
|
|
3357
|
-
logger.info("Runner launching", { sessionId, pendingCriteria: pendingCriteria.length });
|
|
3358
|
-
runOrchestrator({
|
|
3359
|
-
sessionManager,
|
|
3360
|
-
sessionId,
|
|
3361
|
-
llmClient: llmForSession(sessionId),
|
|
3362
|
-
statsIdentity: statsForSession(sessionId),
|
|
3363
|
-
injectWorkflowKickoff: !hasUserMessage,
|
|
3364
|
-
...launchPayload?.workflowId ? { workflowId: launchPayload.workflowId } : {},
|
|
3365
|
-
...launchPayload?.subGroup ? { subGroup: launchPayload.subGroup } : {},
|
|
3366
|
-
...hasUserMessage ? {
|
|
3367
|
-
userMessage: {
|
|
3368
|
-
content: hasUserContent ? launchPayload.content : "",
|
|
3369
|
-
...hasUserAttachments ? { attachments: launchAttachments } : {}
|
|
3370
|
-
}
|
|
3371
|
-
} : {},
|
|
3372
|
-
signal: controller.signal,
|
|
3373
|
-
onMessage: (msg) => sendForSession(sessionId, msg)
|
|
3374
|
-
// For path confirmation dialogs
|
|
3375
|
-
}).catch((error) => {
|
|
3376
|
-
if (error instanceof Error && error.message === "Aborted") {
|
|
3377
|
-
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}`);
|
|
3378
1592
|
}
|
|
3379
|
-
logger.error("Runner error", { error, sessionId });
|
|
3380
|
-
}).finally(() => {
|
|
3381
|
-
sessionManager.setRunning(sessionId, false);
|
|
3382
|
-
sendForSession(sessionId, createSessionRunningMessage(false));
|
|
3383
|
-
cleanupAfterTurn(sessionId, controller, sendForSession, true);
|
|
3384
|
-
});
|
|
3385
|
-
break;
|
|
3386
|
-
}
|
|
3387
|
-
// =========================================================================
|
|
3388
|
-
// Path Confirmation
|
|
3389
|
-
// =========================================================================
|
|
3390
|
-
case "path.confirm": {
|
|
3391
|
-
send(
|
|
3392
|
-
createErrorMessage(
|
|
3393
|
-
"DEPRECATED",
|
|
3394
|
-
"path.confirm removed. Use REST API: POST /api/sessions/:id/confirm-path",
|
|
3395
|
-
message.id
|
|
3396
|
-
)
|
|
3397
|
-
);
|
|
3398
|
-
break;
|
|
3399
|
-
}
|
|
3400
|
-
// =========================================================================
|
|
3401
|
-
// Ask User
|
|
3402
|
-
// =========================================================================
|
|
3403
|
-
case "ask.answer": {
|
|
3404
|
-
if (!client.activeSessionId) {
|
|
3405
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3406
|
-
return;
|
|
3407
|
-
}
|
|
3408
|
-
if (!isAskAnswerPayload(message.payload)) {
|
|
3409
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid ask.answer payload", message.id));
|
|
3410
|
-
return;
|
|
3411
|
-
}
|
|
3412
|
-
const { callId, answer, skip } = message.payload;
|
|
3413
|
-
const found = provideAnswer(callId, answer, skip);
|
|
3414
|
-
if (!found) {
|
|
3415
|
-
send(createErrorMessage("NOT_FOUND", "No pending question with that ID", message.id));
|
|
3416
|
-
return;
|
|
3417
1593
|
}
|
|
3418
|
-
logger.debug("Ask user answer received", {
|
|
3419
|
-
sessionId: client.activeSessionId,
|
|
3420
|
-
callId,
|
|
3421
|
-
answerLength: answer.length
|
|
3422
|
-
});
|
|
3423
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
3424
|
-
break;
|
|
3425
|
-
}
|
|
3426
|
-
default: {
|
|
3427
|
-
send(createErrorMessage("UNKNOWN_MESSAGE", `Unknown message type: ${message.type}`, message.id));
|
|
3428
1594
|
}
|
|
1595
|
+
return parts.sort().join(",");
|
|
3429
1596
|
}
|
|
3430
|
-
}
|
|
1597
|
+
};
|
|
3431
1598
|
|
|
3432
1599
|
// src/server/lsp/server.ts
|
|
3433
|
-
import { spawn
|
|
1600
|
+
import { spawn } from "child_process";
|
|
3434
1601
|
import { extname } from "path";
|
|
3435
1602
|
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node.js";
|
|
3436
1603
|
var LSP = {
|
|
@@ -3492,7 +1659,7 @@ var LspServer = class {
|
|
|
3492
1659
|
}
|
|
3493
1660
|
this.state = "starting";
|
|
3494
1661
|
try {
|
|
3495
|
-
this.process =
|
|
1662
|
+
this.process = spawn(this.commandPath, this.config.serverArgs, {
|
|
3496
1663
|
cwd: this.workdir,
|
|
3497
1664
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3498
1665
|
env: {
|
|
@@ -3758,20 +1925,20 @@ var LspServer = class {
|
|
|
3758
1925
|
};
|
|
3759
1926
|
|
|
3760
1927
|
// src/server/utils/which.ts
|
|
3761
|
-
import { access
|
|
3762
|
-
import { join
|
|
3763
|
-
import { fileURLToPath
|
|
3764
|
-
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));
|
|
3765
1932
|
function getBundledBinPaths() {
|
|
3766
1933
|
const paths = [];
|
|
3767
|
-
paths.push(
|
|
3768
|
-
paths.push(
|
|
3769
|
-
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"));
|
|
3770
1937
|
return paths;
|
|
3771
1938
|
}
|
|
3772
1939
|
async function existsExecutable(path) {
|
|
3773
1940
|
try {
|
|
3774
|
-
await
|
|
1941
|
+
await access(path, constants.X_OK);
|
|
3775
1942
|
return true;
|
|
3776
1943
|
} catch {
|
|
3777
1944
|
return false;
|
|
@@ -3785,13 +1952,13 @@ async function which(command, workdir) {
|
|
|
3785
1952
|
return null;
|
|
3786
1953
|
}
|
|
3787
1954
|
for (const binDir of getBundledBinPaths()) {
|
|
3788
|
-
const fullPath =
|
|
1955
|
+
const fullPath = join(binDir, command);
|
|
3789
1956
|
if (await existsExecutable(fullPath)) {
|
|
3790
1957
|
return fullPath;
|
|
3791
1958
|
}
|
|
3792
1959
|
}
|
|
3793
1960
|
if (workdir) {
|
|
3794
|
-
const projectBin =
|
|
1961
|
+
const projectBin = join(workdir, "node_modules", ".bin", command);
|
|
3795
1962
|
if (await existsExecutable(projectBin)) {
|
|
3796
1963
|
return projectBin;
|
|
3797
1964
|
}
|
|
@@ -3800,7 +1967,7 @@ async function which(command, workdir) {
|
|
|
3800
1967
|
const paths = pathEnv.split(getPathSeparator());
|
|
3801
1968
|
for (const dir of paths) {
|
|
3802
1969
|
if (!dir) continue;
|
|
3803
|
-
const fullPath =
|
|
1970
|
+
const fullPath = join(dir, command);
|
|
3804
1971
|
if (await existsExecutable(fullPath)) {
|
|
3805
1972
|
return fullPath;
|
|
3806
1973
|
}
|
|
@@ -5021,8 +3188,8 @@ var SessionManager = class {
|
|
|
5021
3188
|
updateExecutionState(sessionId, updates) {
|
|
5022
3189
|
logger.debug("updateExecutionState called", { sessionId, updates });
|
|
5023
3190
|
}
|
|
5024
|
-
setCachedPrompt(sessionId, systemPrompt, hash) {
|
|
5025
|
-
this.cachedPromptStore.set(sessionId, { systemPrompt, hash });
|
|
3191
|
+
setCachedPrompt(sessionId, systemPrompt, tools, hash) {
|
|
3192
|
+
this.cachedPromptStore.set(sessionId, { systemPrompt, tools, hash });
|
|
5026
3193
|
}
|
|
5027
3194
|
getCachedPrompt(sessionId) {
|
|
5028
3195
|
return this.cachedPromptStore.get(sessionId);
|
|
@@ -5167,6 +3334,7 @@ var SessionManager = class {
|
|
|
5167
3334
|
if (!hasCachedPrompt && eventState.cachedSystemPrompt && eventState.dynamicContextHash) {
|
|
5168
3335
|
this.cachedPromptStore.set(dbSession.id, {
|
|
5169
3336
|
systemPrompt: eventState.cachedSystemPrompt,
|
|
3337
|
+
tools: [],
|
|
5170
3338
|
hash: eventState.dynamicContextHash
|
|
5171
3339
|
});
|
|
5172
3340
|
}
|
|
@@ -5193,7 +3361,7 @@ var SessionManager = class {
|
|
|
5193
3361
|
|
|
5194
3362
|
// src/server/routes/crud-helpers.ts
|
|
5195
3363
|
import { Router } from "express";
|
|
5196
|
-
import { join as
|
|
3364
|
+
import { join as join2 } from "path";
|
|
5197
3365
|
function computeOverrideIds(defaults, userItems) {
|
|
5198
3366
|
return userItems.filter((u) => defaults.some((d) => d.metadata.id === u.metadata.id)).map((u) => u.metadata.id);
|
|
5199
3367
|
}
|
|
@@ -5205,7 +3373,7 @@ function validateNameIdPrompt(body) {
|
|
|
5205
3373
|
return null;
|
|
5206
3374
|
}
|
|
5207
3375
|
function getProjectItemPath(projectDir, dirName, id, ext) {
|
|
5208
|
-
return
|
|
3376
|
+
return join2(projectDir, ".openfox", dirName, `${id}${ext}`);
|
|
5209
3377
|
}
|
|
5210
3378
|
async function isProjectItem(projectDir, dirName, id, ext) {
|
|
5211
3379
|
if (!projectDir) return false;
|
|
@@ -5369,27 +3537,27 @@ function createSkillRoutes(configDir, projectDir) {
|
|
|
5369
3537
|
}
|
|
5370
3538
|
|
|
5371
3539
|
// src/server/commands/registry.ts
|
|
5372
|
-
import { writeFile
|
|
5373
|
-
import { join as
|
|
5374
|
-
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";
|
|
5375
3543
|
import matter from "gray-matter";
|
|
5376
|
-
var
|
|
5377
|
-
var
|
|
5378
|
-
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");
|
|
5379
3547
|
var COMMAND_EXTENSION = ".command.md";
|
|
5380
3548
|
function getCommandsDir(configDir) {
|
|
5381
|
-
return
|
|
3549
|
+
return join3(configDir, "commands");
|
|
5382
3550
|
}
|
|
5383
3551
|
function getProjectCommandsDir(projectDir) {
|
|
5384
|
-
return
|
|
3552
|
+
return join3(projectDir, ".openfox", "commands");
|
|
5385
3553
|
}
|
|
5386
3554
|
async function loadDefaultCommands() {
|
|
5387
|
-
let defaults = await loadItemsFromDir(
|
|
3555
|
+
let defaults = await loadItemsFromDir(DEFAULTS_DIR, {
|
|
5388
3556
|
extension: COMMAND_EXTENSION,
|
|
5389
3557
|
logName: "command"
|
|
5390
3558
|
});
|
|
5391
3559
|
if (!defaults.length) {
|
|
5392
|
-
defaults = await loadItemsFromDir(
|
|
3560
|
+
defaults = await loadItemsFromDir(DEFAULTS_DIR_ALT, {
|
|
5393
3561
|
extension: COMMAND_EXTENSION,
|
|
5394
3562
|
logName: "command"
|
|
5395
3563
|
});
|
|
@@ -5426,9 +3594,9 @@ async function loadAllCommands(configDir, projectDir) {
|
|
|
5426
3594
|
return Array.from(commandMap.values());
|
|
5427
3595
|
}
|
|
5428
3596
|
async function getDefaultCommandIds() {
|
|
5429
|
-
const ids = await getDefaultIds(
|
|
3597
|
+
const ids = await getDefaultIds(DEFAULTS_DIR, COMMAND_EXTENSION);
|
|
5430
3598
|
if (ids.length) return ids;
|
|
5431
|
-
return getDefaultIds(
|
|
3599
|
+
return getDefaultIds(DEFAULTS_DIR_ALT, COMMAND_EXTENSION);
|
|
5432
3600
|
}
|
|
5433
3601
|
async function isDefaultCommand(commandId) {
|
|
5434
3602
|
const defaultIds = await getDefaultCommandIds();
|
|
@@ -5438,19 +3606,19 @@ function findCommandById(commandId, commands) {
|
|
|
5438
3606
|
return commands.find((c) => c.metadata.id === commandId);
|
|
5439
3607
|
}
|
|
5440
3608
|
async function commandExists(configDir, commandId, projectDir) {
|
|
5441
|
-
if (await pathExists(
|
|
5442
|
-
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}`)))
|
|
5443
3611
|
return true;
|
|
5444
3612
|
return false;
|
|
5445
3613
|
}
|
|
5446
3614
|
async function saveCommand(configDir, command) {
|
|
5447
3615
|
const commandsDir = getCommandsDir(configDir);
|
|
5448
3616
|
if (!await pathExists(commandsDir)) {
|
|
5449
|
-
await
|
|
3617
|
+
await mkdir(commandsDir, { recursive: true });
|
|
5450
3618
|
}
|
|
5451
|
-
const filePath =
|
|
3619
|
+
const filePath = join3(commandsDir, `${command.metadata.id}${COMMAND_EXTENSION}`);
|
|
5452
3620
|
const content = matter.stringify(command.prompt, command.metadata);
|
|
5453
|
-
await
|
|
3621
|
+
await writeFile(filePath, content, "utf-8");
|
|
5454
3622
|
}
|
|
5455
3623
|
async function saveCommandToProject(projectDir, command) {
|
|
5456
3624
|
await saveItemToDir(
|
|
@@ -5468,9 +3636,9 @@ async function deleteCommand(configDir, commandId) {
|
|
|
5468
3636
|
if (isDefault) {
|
|
5469
3637
|
return { success: false, reason: "Cannot delete built-in defaults" };
|
|
5470
3638
|
}
|
|
5471
|
-
const filePath =
|
|
3639
|
+
const filePath = join3(getCommandsDir(configDir), `${commandId}${COMMAND_EXTENSION}`);
|
|
5472
3640
|
try {
|
|
5473
|
-
await
|
|
3641
|
+
await unlink(filePath);
|
|
5474
3642
|
return { success: true };
|
|
5475
3643
|
} catch {
|
|
5476
3644
|
return { success: false };
|
|
@@ -5691,8 +3859,8 @@ function createTerminalRoutes() {
|
|
|
5691
3859
|
|
|
5692
3860
|
// src/server/routes/directories.ts
|
|
5693
3861
|
import { Router as Router5 } from "express";
|
|
5694
|
-
import { readdir
|
|
5695
|
-
import { resolve, join as
|
|
3862
|
+
import { readdir } from "fs/promises";
|
|
3863
|
+
import { resolve, join as join4, dirname as dirname3, basename as basename2 } from "path";
|
|
5696
3864
|
function createDirectoryRoutes() {
|
|
5697
3865
|
const router = Router5();
|
|
5698
3866
|
const DEFAULT_BASE_PATH = process.cwd();
|
|
@@ -5700,12 +3868,12 @@ function createDirectoryRoutes() {
|
|
|
5700
3868
|
const path = req.query["path"] || DEFAULT_BASE_PATH;
|
|
5701
3869
|
try {
|
|
5702
3870
|
const resolvedPath = resolve(path);
|
|
5703
|
-
const entries = await
|
|
3871
|
+
const entries = await readdir(resolvedPath, { withFileTypes: true });
|
|
5704
3872
|
const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
5705
3873
|
name: entry.name,
|
|
5706
|
-
path:
|
|
3874
|
+
path: join4(resolvedPath, entry.name)
|
|
5707
3875
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
5708
|
-
const parent =
|
|
3876
|
+
const parent = dirname3(resolvedPath);
|
|
5709
3877
|
const hasParent = parent !== resolvedPath;
|
|
5710
3878
|
res.json({
|
|
5711
3879
|
current: resolvedPath,
|
|
@@ -5728,10 +3896,10 @@ function createDirectoryRoutes() {
|
|
|
5728
3896
|
|
|
5729
3897
|
// src/server/routes/auto-update.ts
|
|
5730
3898
|
import { Router as Router6 } from "express";
|
|
5731
|
-
import { spawn as
|
|
3899
|
+
import { spawn as spawn2 } from "child_process";
|
|
5732
3900
|
|
|
5733
3901
|
// src/constants.ts
|
|
5734
|
-
var VERSION = "2.0.
|
|
3902
|
+
var VERSION = "2.0.14";
|
|
5735
3903
|
|
|
5736
3904
|
// src/server/routes/auto-update.ts
|
|
5737
3905
|
var updateInProgress = false;
|
|
@@ -5755,7 +3923,7 @@ function createAutoUpdateRoutes(options = {}) {
|
|
|
5755
3923
|
const current = VERSION;
|
|
5756
3924
|
try {
|
|
5757
3925
|
const latest = await new Promise((resolve3, reject) => {
|
|
5758
|
-
const child =
|
|
3926
|
+
const child = spawn2("npm", ["view", "openfox", "version"], {
|
|
5759
3927
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5760
3928
|
});
|
|
5761
3929
|
let stdout = "";
|
|
@@ -5793,7 +3961,7 @@ function createAutoUpdateRoutes(options = {}) {
|
|
|
5793
3961
|
updateInProgress = true;
|
|
5794
3962
|
try {
|
|
5795
3963
|
const isService = isRunningAsService();
|
|
5796
|
-
const child =
|
|
3964
|
+
const child = spawn2("bash", ["-c", "openfox update"], {
|
|
5797
3965
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5798
3966
|
});
|
|
5799
3967
|
let stdout = "";
|
|
@@ -5832,7 +4000,7 @@ function createAutoUpdateRoutes(options = {}) {
|
|
|
5832
4000
|
return;
|
|
5833
4001
|
}
|
|
5834
4002
|
try {
|
|
5835
|
-
const child =
|
|
4003
|
+
const child = spawn2("bash", ["-c", "openfox service restart"], {
|
|
5836
4004
|
detached: true,
|
|
5837
4005
|
stdio: "ignore"
|
|
5838
4006
|
});
|
|
@@ -5846,7 +4014,7 @@ function createAutoUpdateRoutes(options = {}) {
|
|
|
5846
4014
|
}
|
|
5847
4015
|
|
|
5848
4016
|
// src/server/index.ts
|
|
5849
|
-
var __dirname2 =
|
|
4017
|
+
var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
|
|
5850
4018
|
async function createServerHandle(config4) {
|
|
5851
4019
|
setRuntimeConfig(config4);
|
|
5852
4020
|
setLogLevel(config4.logging?.level ?? void 0, config4.mode);
|
|
@@ -5895,6 +4063,21 @@ async function createServerHandle(config4) {
|
|
|
5895
4063
|
(err) => logger.error("LLM initialization failed", { error: err instanceof Error ? err.message : String(err) })
|
|
5896
4064
|
);
|
|
5897
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
|
+
});
|
|
5898
4081
|
const app = express();
|
|
5899
4082
|
app.use(cors());
|
|
5900
4083
|
app.use(express.json({ limit: "10mb" }));
|
|
@@ -6083,7 +4266,7 @@ async function createServerHandle(config4) {
|
|
|
6083
4266
|
app.get("/api/sessions/:id", async (req, res) => {
|
|
6084
4267
|
const { getEventStore: getEventStore2 } = await import("./events-4K52FKPR.js");
|
|
6085
4268
|
const { buildMessagesFromStoredEvents } = await import("./folding-QIBNKYA6.js");
|
|
6086
|
-
const { getPendingQuestionsForSession } = await import("./tools-
|
|
4269
|
+
const { getPendingQuestionsForSession } = await import("./tools-DUC54GEH.js");
|
|
6087
4270
|
const session = sessionManager.getSession(req.params.id);
|
|
6088
4271
|
if (!session) {
|
|
6089
4272
|
return res.status(404).json({ error: "Session not found" });
|
|
@@ -6126,7 +4309,7 @@ async function createServerHandle(config4) {
|
|
|
6126
4309
|
return res.status(400).json({ error: "providerId is required" });
|
|
6127
4310
|
}
|
|
6128
4311
|
sessionManager.setSessionProvider(sessionId, providerId, model ?? "auto");
|
|
6129
|
-
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-
|
|
4312
|
+
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-YFHA5MWV.js");
|
|
6130
4313
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6131
4314
|
const updatedConfig = setDefaultModelSelection(globalConfig, providerId, model ?? "auto");
|
|
6132
4315
|
await saveGlobalConfig(config4.mode ?? "production", updatedConfig);
|
|
@@ -6214,7 +4397,7 @@ async function createServerHandle(config4) {
|
|
|
6214
4397
|
if (!callId || approved === void 0) {
|
|
6215
4398
|
return res.status(400).json({ error: "callId and approved are required" });
|
|
6216
4399
|
}
|
|
6217
|
-
const { providePathConfirmation } = await import("./tools-
|
|
4400
|
+
const { providePathConfirmation } = await import("./tools-DUC54GEH.js");
|
|
6218
4401
|
const result = providePathConfirmation(callId, approved, alwaysAllow);
|
|
6219
4402
|
if (!result.found) {
|
|
6220
4403
|
return res.status(404).json({ error: "No pending path confirmation with that ID" });
|
|
@@ -6222,7 +4405,7 @@ async function createServerHandle(config4) {
|
|
|
6222
4405
|
const { getEventStore: getEventStore2 } = await import("./events-4K52FKPR.js");
|
|
6223
4406
|
const { buildMessagesFromStoredEvents, foldPendingConfirmations } = await import("./folding-QIBNKYA6.js");
|
|
6224
4407
|
const { createSessionStateMessage } = await import("./protocol-YYWMFR35.js");
|
|
6225
|
-
const { getPendingQuestionsForSession } = await import("./tools-
|
|
4408
|
+
const { getPendingQuestionsForSession } = await import("./tools-DUC54GEH.js");
|
|
6226
4409
|
const eventStore = getEventStore2();
|
|
6227
4410
|
const events = eventStore.getEvents(sessionId);
|
|
6228
4411
|
const messages = buildMessagesFromStoredEvents(events);
|
|
@@ -6243,8 +4426,8 @@ async function createServerHandle(config4) {
|
|
|
6243
4426
|
if (!skip && typeof answer !== "string") {
|
|
6244
4427
|
return res.status(400).json({ error: "answer is required when not skipping" });
|
|
6245
4428
|
}
|
|
6246
|
-
const { provideAnswer
|
|
6247
|
-
const found =
|
|
4429
|
+
const { provideAnswer } = await import("./tools-DUC54GEH.js");
|
|
4430
|
+
const found = provideAnswer(callId, answer ?? "", skip ?? false);
|
|
6248
4431
|
if (!found) {
|
|
6249
4432
|
return res.status(404).json({ error: "No pending question with that ID" });
|
|
6250
4433
|
}
|
|
@@ -6279,8 +4462,8 @@ async function createServerHandle(config4) {
|
|
|
6279
4462
|
if (!session) {
|
|
6280
4463
|
return res.status(404).json({ error: "Session not found" });
|
|
6281
4464
|
}
|
|
6282
|
-
const { stopSessionExecution } = await import("./chat-handler-
|
|
6283
|
-
const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-
|
|
4465
|
+
const { stopSessionExecution } = await import("./chat-handler-ITXCBFJK.js");
|
|
4466
|
+
const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-DUC54GEH.js");
|
|
6284
4467
|
const queuedMessages = sessionManager.getQueueState(sessionId);
|
|
6285
4468
|
sessionManager.clearMessageQueue(sessionId);
|
|
6286
4469
|
stopSessionExecution(sessionId, sessionManager);
|
|
@@ -6391,7 +4574,7 @@ async function createServerHandle(config4) {
|
|
|
6391
4574
|
let visionFallback;
|
|
6392
4575
|
let globalWorkdir;
|
|
6393
4576
|
try {
|
|
6394
|
-
const { loadGlobalConfig, getVisionFallback } = await import("./config-
|
|
4577
|
+
const { loadGlobalConfig, getVisionFallback } = await import("./config-YFHA5MWV.js");
|
|
6395
4578
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6396
4579
|
const fallback = getVisionFallback(globalConfig);
|
|
6397
4580
|
if (fallback) {
|
|
@@ -6487,7 +4670,7 @@ async function createServerHandle(config4) {
|
|
|
6487
4670
|
const apiKey = req.query["apiKey"];
|
|
6488
4671
|
if (!url) return res.status(400).json({ error: "url is required" });
|
|
6489
4672
|
try {
|
|
6490
|
-
const { fetchModelsWithContext } = await import("./provider-manager-
|
|
4673
|
+
const { fetchModelsWithContext } = await import("./provider-manager-DETXLDKW.js");
|
|
6491
4674
|
const models = await fetchModelsWithContext(url, apiKey);
|
|
6492
4675
|
if (models.length === 0) {
|
|
6493
4676
|
return res.status(404).json({ error: `No models found at ${buildModelsUrl(url)}`, url });
|
|
@@ -6557,7 +4740,7 @@ async function createServerHandle(config4) {
|
|
|
6557
4740
|
return res.status(400).json({ error: "name, url, and backend are required" });
|
|
6558
4741
|
}
|
|
6559
4742
|
try {
|
|
6560
|
-
const { loadGlobalConfig, saveGlobalConfig, addProvider, setDefaultModelSelection } = await import("./config-
|
|
4743
|
+
const { loadGlobalConfig, saveGlobalConfig, addProvider, setDefaultModelSelection } = await import("./config-YFHA5MWV.js");
|
|
6561
4744
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6562
4745
|
const providerBackend = backend;
|
|
6563
4746
|
const configWithProvider = addProvider(globalConfig, {
|
|
@@ -6592,7 +4775,7 @@ async function createServerHandle(config4) {
|
|
|
6592
4775
|
app.post("/api/init/config", async (req, res) => {
|
|
6593
4776
|
const { workdir, visionFallback } = req.body;
|
|
6594
4777
|
try {
|
|
6595
|
-
const { loadGlobalConfig, saveGlobalConfig } = await import("./config-
|
|
4778
|
+
const { loadGlobalConfig, saveGlobalConfig } = await import("./config-YFHA5MWV.js");
|
|
6596
4779
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6597
4780
|
const updatedConfig = {
|
|
6598
4781
|
...globalConfig,
|
|
@@ -6620,7 +4803,7 @@ async function createServerHandle(config4) {
|
|
|
6620
4803
|
});
|
|
6621
4804
|
app.delete("/api/providers/:id", async (req, res) => {
|
|
6622
4805
|
const { id } = req.params;
|
|
6623
|
-
const { loadGlobalConfig, saveGlobalConfig, removeProvider } = await import("./config-
|
|
4806
|
+
const { loadGlobalConfig, saveGlobalConfig, removeProvider } = await import("./config-YFHA5MWV.js");
|
|
6624
4807
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6625
4808
|
const updatedConfig = removeProvider(globalConfig, id);
|
|
6626
4809
|
await saveGlobalConfig(config4.mode ?? "production", updatedConfig);
|
|
@@ -6631,7 +4814,7 @@ async function createServerHandle(config4) {
|
|
|
6631
4814
|
const { id } = req.params;
|
|
6632
4815
|
const { isLocal } = req.body;
|
|
6633
4816
|
try {
|
|
6634
|
-
const { loadGlobalConfig, saveGlobalConfig, updateProvider } = await import("./config-
|
|
4817
|
+
const { loadGlobalConfig, saveGlobalConfig, updateProvider } = await import("./config-YFHA5MWV.js");
|
|
6635
4818
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6636
4819
|
const provider = globalConfig.providers.find((p) => p.id === id);
|
|
6637
4820
|
if (!provider) {
|
|
@@ -6659,7 +4842,7 @@ async function createServerHandle(config4) {
|
|
|
6659
4842
|
models: modelConfigs
|
|
6660
4843
|
} = req.body;
|
|
6661
4844
|
try {
|
|
6662
|
-
const { loadGlobalConfig, saveGlobalConfig, updateProvider } = await import("./config-
|
|
4845
|
+
const { loadGlobalConfig, saveGlobalConfig, updateProvider } = await import("./config-YFHA5MWV.js");
|
|
6663
4846
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6664
4847
|
const provider = globalConfig.providers.find((p) => p.id === id);
|
|
6665
4848
|
if (!provider) {
|
|
@@ -6696,7 +4879,7 @@ async function createServerHandle(config4) {
|
|
|
6696
4879
|
return res.status(400).json({ error: result.error });
|
|
6697
4880
|
}
|
|
6698
4881
|
const llmClient = getLLMClient();
|
|
6699
|
-
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-
|
|
4882
|
+
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-YFHA5MWV.js");
|
|
6700
4883
|
const globalConfig = await loadGlobalConfig(config4.mode ?? "production");
|
|
6701
4884
|
const updatedConfig = setDefaultModelSelection(globalConfig, id, llmClient.getModel());
|
|
6702
4885
|
await saveGlobalConfig(config4.mode ?? "production", updatedConfig);
|
|
@@ -6721,6 +4904,131 @@ async function createServerHandle(config4) {
|
|
|
6721
4904
|
status: updatedProvider?.status ?? "unknown"
|
|
6722
4905
|
});
|
|
6723
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
|
+
});
|
|
6724
5032
|
const projectDir = config4.workdir;
|
|
6725
5033
|
app.use("/api/skills", createSkillRoutes(configDir, projectDir));
|
|
6726
5034
|
app.use("/api/commands", createCommandRoutes(configDir, projectDir));
|
|
@@ -6795,7 +5103,7 @@ async function createServerHandle(config4) {
|
|
|
6795
5103
|
}
|
|
6796
5104
|
});
|
|
6797
5105
|
app.get("/fox.svg", (_req, res) => {
|
|
6798
|
-
|
|
5106
|
+
readFile(join5(webDir, "fox.svg")).then((content) => {
|
|
6799
5107
|
res.set("Content-Type", "image/svg+xml");
|
|
6800
5108
|
res.send(content);
|
|
6801
5109
|
}).catch(() => {
|
|
@@ -6804,7 +5112,7 @@ async function createServerHandle(config4) {
|
|
|
6804
5112
|
});
|
|
6805
5113
|
app.use(
|
|
6806
5114
|
"/sounds",
|
|
6807
|
-
express.static(
|
|
5115
|
+
express.static(join5(webDir, "public", "sounds"), {
|
|
6808
5116
|
setHeaders: (res) => {
|
|
6809
5117
|
res.set("Content-Type", "audio/mpeg");
|
|
6810
5118
|
}
|
|
@@ -6813,13 +5121,13 @@ async function createServerHandle(config4) {
|
|
|
6813
5121
|
app.use("/__inspect__.js", (_req, res) => {
|
|
6814
5122
|
res.set("Access-Control-Allow-Origin", "*");
|
|
6815
5123
|
res.set("Content-Type", "application/javascript");
|
|
6816
|
-
|
|
5124
|
+
readFile(join5(webDir, "public", "__inspect__.js")).then((content) => res.send(content)).catch(() => res.status(404).send("Not found"));
|
|
6817
5125
|
});
|
|
6818
5126
|
app.get("/*path", (req, res) => {
|
|
6819
5127
|
if (req.path.startsWith("/api/")) {
|
|
6820
5128
|
return;
|
|
6821
5129
|
}
|
|
6822
|
-
|
|
5130
|
+
readFile(join5(webDir, "index.html"), "utf-8").then((indexHtml) => viteServer.transformIndexHtml(req.originalUrl, indexHtml)).then((transformed) => res.send(transformed)).catch((err) => {
|
|
6823
5131
|
logger.error("Error serving index.html", { error: err });
|
|
6824
5132
|
res.status(500).send("Server error");
|
|
6825
5133
|
});
|
|
@@ -6830,7 +5138,7 @@ async function createServerHandle(config4) {
|
|
|
6830
5138
|
const distWebDir = resolve2(__dirname2, "web");
|
|
6831
5139
|
app.use(
|
|
6832
5140
|
"/assets",
|
|
6833
|
-
express.static(
|
|
5141
|
+
express.static(join5(distWebDir, "assets"), {
|
|
6834
5142
|
setHeaders: (res, filepath) => {
|
|
6835
5143
|
if (filepath.endsWith(".css")) {
|
|
6836
5144
|
res.set("Content-Type", "text/css");
|
|
@@ -6838,11 +5146,11 @@ async function createServerHandle(config4) {
|
|
|
6838
5146
|
}
|
|
6839
5147
|
})
|
|
6840
5148
|
);
|
|
6841
|
-
app.use("/manifest.webmanifest", express.static(
|
|
6842
|
-
app.use("/registerSW.js", express.static(
|
|
6843
|
-
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")));
|
|
6844
5152
|
app.get("/fox.svg", (_req, res) => {
|
|
6845
|
-
|
|
5153
|
+
readFile(join5(distWebDir, "fox.svg")).then((content) => {
|
|
6846
5154
|
res.set("Content-Type", "image/svg+xml");
|
|
6847
5155
|
res.send(content);
|
|
6848
5156
|
}).catch(() => {
|
|
@@ -6851,7 +5159,7 @@ async function createServerHandle(config4) {
|
|
|
6851
5159
|
});
|
|
6852
5160
|
app.use(
|
|
6853
5161
|
"/sounds",
|
|
6854
|
-
express.static(
|
|
5162
|
+
express.static(join5(distWebDir, "sounds"), {
|
|
6855
5163
|
setHeaders: (res) => {
|
|
6856
5164
|
res.set("Content-Type", "audio/mpeg");
|
|
6857
5165
|
}
|
|
@@ -6860,15 +5168,15 @@ async function createServerHandle(config4) {
|
|
|
6860
5168
|
app.use("/__inspect__.js", (_req, res) => {
|
|
6861
5169
|
res.set("Access-Control-Allow-Origin", "*");
|
|
6862
5170
|
res.set("Content-Type", "application/javascript");
|
|
6863
|
-
|
|
5171
|
+
readFile(join5(distWebDir, "__inspect__.js")).then((content) => res.send(content)).catch(() => res.status(404).send("Not found"));
|
|
6864
5172
|
});
|
|
6865
5173
|
app.use("/__inspect__.js", (_req, res) => {
|
|
6866
5174
|
res.set("Access-Control-Allow-Origin", "*");
|
|
6867
5175
|
res.set("Content-Type", "application/javascript");
|
|
6868
|
-
|
|
5176
|
+
readFile(join5(distWebDir, "__inspect__.js")).then((content) => res.send(content)).catch(() => res.status(404).send("Not found"));
|
|
6869
5177
|
});
|
|
6870
5178
|
app.get("/", (_req, res) => {
|
|
6871
|
-
|
|
5179
|
+
readFile(join5(distWebDir, "index.html"), "utf-8").then((content) => res.send(content)).catch(() => {
|
|
6872
5180
|
res.status(404).send("Web UI not built. Run `npm run build:web`");
|
|
6873
5181
|
});
|
|
6874
5182
|
});
|
|
@@ -6876,12 +5184,14 @@ async function createServerHandle(config4) {
|
|
|
6876
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") {
|
|
6877
5185
|
return;
|
|
6878
5186
|
}
|
|
6879
|
-
|
|
5187
|
+
readFile(join5(distWebDir, "index.html"), "utf-8").then((content) => res.send(content)).catch(() => {
|
|
6880
5188
|
res.status(404).send("Web UI not built");
|
|
6881
5189
|
});
|
|
6882
5190
|
});
|
|
6883
5191
|
}
|
|
6884
5192
|
const httpServer = createHttpServer(app);
|
|
5193
|
+
const { setMcpManagerForWs } = await import("./server-EA6W7YIC.js");
|
|
5194
|
+
setMcpManagerForWs(mcpManager);
|
|
6885
5195
|
const wssExports = createWebSocketServer(
|
|
6886
5196
|
httpServer,
|
|
6887
5197
|
config4,
|
|
@@ -6891,7 +5201,7 @@ async function createServerHandle(config4) {
|
|
|
6891
5201
|
providerManager
|
|
6892
5202
|
);
|
|
6893
5203
|
const wss = wssExports.wss;
|
|
6894
|
-
const { QueueProcessor } = await import("./processor-
|
|
5204
|
+
const { QueueProcessor } = await import("./processor-CCXZYLX6.js");
|
|
6895
5205
|
const queueProcessor = new QueueProcessor({
|
|
6896
5206
|
sessionManager,
|
|
6897
5207
|
providerManager,
|
|
@@ -6931,6 +5241,7 @@ async function createServerHandle(config4) {
|
|
|
6931
5241
|
logger.info("Shutting down...");
|
|
6932
5242
|
void (async () => {
|
|
6933
5243
|
await devServerManager.stopAll();
|
|
5244
|
+
await mcpManager.disconnectAll();
|
|
6934
5245
|
const { stopAllInspectProxies } = await import("./inspect-proxy-42ZXL2R5.js");
|
|
6935
5246
|
stopAllInspectProxies();
|
|
6936
5247
|
const { cleanupAllProcesses } = await import("./store-OQ45KONN.js");
|
|
@@ -6965,4 +5276,4 @@ export {
|
|
|
6965
5276
|
createServerHandle,
|
|
6966
5277
|
createServer
|
|
6967
5278
|
};
|
|
6968
|
-
//# sourceMappingURL=chunk-
|
|
5279
|
+
//# sourceMappingURL=chunk-4U45A7B3.js.map
|