openfox 0.2.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-defaults/builder.agent.md +3 -7
- package/dist/agent-defaults/code-reviewer.agent.md +1 -2
- package/dist/agent-defaults/debugger.agent.md +1 -2
- package/dist/agent-defaults/planner.agent.md +2 -9
- package/dist/agent-defaults/test-generator.agent.md +1 -1
- package/dist/agent-defaults/verifier.agent.md +4 -5
- package/dist/ask-3RK5YJZE.js +19 -0
- package/dist/auto-compaction-7LNZ5J3W.js +23 -0
- package/dist/{branch.api-WDHKZJS6.js → branch.api-XADFZZAA.js} +2 -2
- package/dist/chat-handler-T7SLWZ4E.js +201 -0
- package/dist/chunk-22CTURMH.js +128 -0
- package/dist/chunk-2KSCZSXQ.js +680 -0
- package/dist/{chunk-YMUIRZUI.js → chunk-2TBNG7CS.js} +12 -12
- package/dist/{chunk-4Z2OZ6NX.js → chunk-3EHGGBWE.js} +29 -2
- package/dist/{chunk-L475ND2Z.js → chunk-3UQACISJ.js} +162 -52
- package/dist/{chunk-AF7XS4OI.js → chunk-5YQAXUKO.js} +4 -1
- package/dist/{chunk-MTX5Y3V3.js → chunk-7IOZFJBW.js} +2 -2
- package/dist/chunk-BU2ZYOEA.js +1067 -0
- package/dist/chunk-M2C5GEQR.js +137 -0
- package/dist/{chunk-U62ZECSQ.js → chunk-PNBH3RAX.js} +2 -2
- package/dist/chunk-QDEKU5RL.js +75 -0
- package/dist/chunk-QYP5HQV4.js +438 -0
- package/dist/{chunk-3WOUSUW7.js → chunk-RAZQTQK2.js} +1353 -1134
- package/dist/chunk-S4QKTRGJ.js +343 -0
- package/dist/{chunk-ARFK3RZW.js → chunk-TPT6HP4H.js} +1 -1
- package/dist/chunk-TVQOONDR.js +75 -0
- package/dist/{chunk-5ZUU23XF.js → chunk-U6BUT37A.js} +286 -33
- package/dist/chunk-VZXXJKSQ.js +314 -0
- package/dist/{chunk-26NRL3HX.js → chunk-XFXOSPYH.js} +3 -2
- package/dist/{chunk-N2XYX4ZA.js → chunk-XKFPU2FA.js} +13 -2
- package/dist/{chunk-FUUYYDVG.js → chunk-YB2574C6.js} +2841 -4507
- package/dist/cli/dev.js +4 -3
- package/dist/cli/index.js +4 -3
- package/dist/{config-BMXSDOZB.js → config-GIFCEJDU.js} +11 -6
- package/dist/events-G6CKHDNZ.js +114 -0
- package/dist/folding-O52L65VN.js +37 -0
- package/dist/{init-PLRTRSDC.js → init-MKTUIGZU.js} +46 -13
- package/dist/orchestrator-UGXX7JRQ.js +39 -0
- package/dist/package.json +9 -2
- package/dist/{paths-4BPXWB2W.js → paths-MM76AWYB.js} +2 -2
- package/dist/processor-KLHKDILM.js +236 -0
- package/dist/{project-creator-CWPKRIKI.js → project-creator-KAQXMZCT.js} +9 -8
- package/dist/projects-GSMMEVSO.js +19 -0
- package/dist/{protocol-BFFz3WM0.d.ts → protocol-BZ6OIASP.d.ts} +71 -50
- package/dist/protocol-WQENDK72.js +88 -0
- package/dist/{provider-ETVY64A2.js → provider-4KTWU6IV.js} +41 -9
- package/dist/runtime-config-2XJJIMSC.js +9 -0
- package/dist/{serve-TL34LE77.js → serve-UK6B6G2O.js} +38 -18
- package/dist/server/index.d.ts +81 -27
- package/dist/server/index.js +19 -11
- package/dist/{settings-D5O3TMLD.js → settings-NCLN4JDZ.js} +4 -4
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +1 -1
- package/dist/tools-SGBQE736.js +48 -0
- package/dist/vision-fallback-63VPBSEN.js +22 -0
- package/dist/web/assets/{index-DHmZmhnR.css → index-BsaaC0X0.css} +32 -1
- package/dist/web/assets/index-CvO7QkGh.js +141 -0
- package/dist/web/index.html +2 -2
- package/dist/web/sounds/typing.mp3 +0 -0
- package/dist/workflow-defaults/default.workflow.json +1 -1
- package/package.json +9 -2
- package/dist/chunk-L4XDA3V7.js +0 -218
- package/dist/tools-QMIIDJTV.js +0 -48
- package/dist/web/assets/index-CAD7Iv_d.js +0 -122
|
@@ -1,126 +1,121 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
createVerifierNudgeConfig,
|
|
3
|
+
runBuilderTurn,
|
|
4
|
+
runChatTurn
|
|
5
|
+
} from "./chunk-VZXXJKSQ.js";
|
|
6
|
+
import {
|
|
6
7
|
TurnMetrics,
|
|
7
8
|
agentExists,
|
|
8
9
|
assembleAgentRequest,
|
|
9
|
-
buildContextMessagesFromEventHistory,
|
|
10
|
-
buildMessagesFromStoredEvents,
|
|
11
|
-
buildSnapshotFromSessionState,
|
|
12
10
|
cancelPathConfirmationsForSession,
|
|
13
|
-
cancelQuestionsForSession,
|
|
14
11
|
createChatDoneEvent,
|
|
15
|
-
createChatDoneMessage,
|
|
16
|
-
createChatErrorMessage,
|
|
17
|
-
createChatMessageMessage,
|
|
18
|
-
createContextStateMessage,
|
|
19
|
-
createCriteriaUpdatedMessage,
|
|
20
|
-
createErrorMessage,
|
|
21
12
|
createMessageStartEvent,
|
|
22
|
-
createModeChangedMessage,
|
|
23
|
-
createPhaseChangedMessage,
|
|
24
|
-
createProjectListMessage,
|
|
25
|
-
createProjectStateMessage,
|
|
26
|
-
createQueueStateMessage,
|
|
27
|
-
createSessionListMessage,
|
|
28
|
-
createSessionRunningMessage,
|
|
29
|
-
createSessionStateMessage,
|
|
30
|
-
createSettingsValueMessage,
|
|
31
13
|
createToolRegistry,
|
|
32
14
|
deleteAgent,
|
|
33
15
|
deleteSkill,
|
|
34
16
|
devServerManager,
|
|
35
|
-
emitAssistantMessageStart,
|
|
36
|
-
emitContextCompacted,
|
|
37
|
-
emitContextState,
|
|
38
|
-
emitCriteriaSet,
|
|
39
|
-
emitCriterionUpdated,
|
|
40
|
-
emitModeChanged,
|
|
41
|
-
emitPhaseChanged,
|
|
42
|
-
emitRunningChanged,
|
|
43
|
-
emitSessionInitialized,
|
|
44
|
-
emitUserMessage,
|
|
45
17
|
ensureDefaultAgents,
|
|
46
18
|
ensureDefaultSkills,
|
|
47
19
|
executeSubAgent,
|
|
48
20
|
findAgentById,
|
|
49
21
|
findSkillById,
|
|
50
|
-
|
|
22
|
+
getAllInstructions,
|
|
51
23
|
getDefaultAgentIds,
|
|
52
24
|
getDefaultSkillIds,
|
|
53
|
-
|
|
25
|
+
getEnabledSkillMetadata,
|
|
54
26
|
getModifiedDefaultAgentIds,
|
|
55
27
|
getModifiedDefaultSkillIds,
|
|
56
|
-
getRecentUserPromptsForSession,
|
|
57
|
-
getRuntimeConfig,
|
|
58
|
-
getSessionState,
|
|
59
|
-
getSubAgents,
|
|
60
28
|
getToolRegistryForAgent,
|
|
61
|
-
initEventStore,
|
|
62
|
-
isChatSendPayload,
|
|
63
|
-
isCriteriaEditPayload,
|
|
64
|
-
isModeSwitchPayload,
|
|
65
|
-
isPathConfirmPayload,
|
|
66
|
-
isProjectCreatePayload,
|
|
67
|
-
isProjectCreateWithDirPayload,
|
|
68
|
-
isProjectDeletePayload,
|
|
69
|
-
isProjectLoadPayload,
|
|
70
|
-
isProjectUpdatePayload,
|
|
71
|
-
isQueueAsapPayload,
|
|
72
|
-
isQueueCancelPayload,
|
|
73
|
-
isQueueCompletionPayload,
|
|
74
|
-
isSessionCreatePayload,
|
|
75
|
-
isSessionLoadPayload,
|
|
76
|
-
isSessionSetProviderPayload,
|
|
77
|
-
isSettingsGetPayload,
|
|
78
|
-
isSettingsSetPayload,
|
|
79
29
|
isSkillEnabled,
|
|
80
30
|
loadAllAgents,
|
|
81
31
|
loadAllAgentsDefault,
|
|
82
32
|
loadAllSkills,
|
|
83
33
|
maybeAutoCompactContext,
|
|
84
|
-
parseClientMessage,
|
|
85
34
|
performManualContextCompaction,
|
|
86
|
-
providePathConfirmation,
|
|
87
35
|
restoreAllDefaultAgents,
|
|
88
36
|
restoreAllDefaultSkills,
|
|
89
37
|
restoreDefaultAgent,
|
|
90
38
|
restoreDefaultSkill,
|
|
91
|
-
runTopLevelAgentLoop,
|
|
92
39
|
saveAgent,
|
|
93
40
|
saveSkill,
|
|
94
|
-
serializeServerMessage,
|
|
95
|
-
setRuntimeConfig,
|
|
96
41
|
setSkillEnabled,
|
|
97
|
-
skillExists
|
|
42
|
+
skillExists
|
|
43
|
+
} from "./chunk-YB2574C6.js";
|
|
44
|
+
import {
|
|
45
|
+
generateSessionName,
|
|
46
|
+
needsNameGeneration
|
|
47
|
+
} from "./chunk-QDEKU5RL.js";
|
|
48
|
+
import {
|
|
49
|
+
createProviderManager,
|
|
50
|
+
parseDefaultModelSelection
|
|
51
|
+
} from "./chunk-QYP5HQV4.js";
|
|
52
|
+
import {
|
|
53
|
+
createSession,
|
|
54
|
+
deleteSession,
|
|
55
|
+
emitAssistantMessageStart,
|
|
56
|
+
emitContextCompacted,
|
|
57
|
+
emitContextState,
|
|
58
|
+
emitCriteriaSet,
|
|
59
|
+
emitCriterionUpdated,
|
|
60
|
+
emitModeChanged,
|
|
61
|
+
emitPhaseChanged,
|
|
62
|
+
emitRunningChanged,
|
|
63
|
+
emitSessionInitialized,
|
|
64
|
+
emitUserMessage,
|
|
65
|
+
getCurrentContextWindowId,
|
|
66
|
+
getEventStore,
|
|
67
|
+
getSession,
|
|
68
|
+
getSessionState,
|
|
69
|
+
initEventStore,
|
|
70
|
+
listSessions,
|
|
71
|
+
listSessionsByProject,
|
|
72
|
+
updateSessionDangerLevel,
|
|
73
|
+
updateSessionMetadata,
|
|
74
|
+
updateSessionProvider,
|
|
75
|
+
updateSessionSummary
|
|
76
|
+
} from "./chunk-BU2ZYOEA.js";
|
|
77
|
+
import {
|
|
78
|
+
buildContextMessagesFromEventHistory,
|
|
79
|
+
buildMessagesFromStoredEvents,
|
|
80
|
+
foldPendingConfirmations
|
|
81
|
+
} from "./chunk-2KSCZSXQ.js";
|
|
82
|
+
import {
|
|
83
|
+
createChatErrorMessage,
|
|
84
|
+
createChatMessageMessage,
|
|
85
|
+
createContextStateMessage,
|
|
86
|
+
createErrorMessage,
|
|
87
|
+
createPhaseChangedMessage,
|
|
88
|
+
createQueueStateMessage,
|
|
89
|
+
createSessionRunningMessage,
|
|
90
|
+
createSessionStateMessage,
|
|
91
|
+
isAskAnswerPayload,
|
|
92
|
+
isChatSendPayload,
|
|
93
|
+
isSessionLoadPayload,
|
|
94
|
+
parseClientMessage,
|
|
95
|
+
serializeServerMessage,
|
|
98
96
|
storedEventToServerMessage
|
|
99
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-S4QKTRGJ.js";
|
|
100
98
|
import {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
EventEmitter,
|
|
100
|
+
cancelQuestionsForSession,
|
|
101
|
+
provideAnswer
|
|
102
|
+
} from "./chunk-22CTURMH.js";
|
|
104
103
|
import {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
getProject,
|
|
108
|
-
listProjects,
|
|
109
|
-
updateProject
|
|
110
|
-
} from "./chunk-N2XYX4ZA.js";
|
|
104
|
+
getProject
|
|
105
|
+
} from "./chunk-XKFPU2FA.js";
|
|
111
106
|
import {
|
|
112
|
-
getDatabase,
|
|
113
107
|
initDatabase
|
|
114
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-3EHGGBWE.js";
|
|
115
109
|
import {
|
|
116
110
|
computeSessionStats
|
|
117
111
|
} from "./chunk-QY7BMXWT.js";
|
|
118
112
|
import {
|
|
119
113
|
createServerMessage
|
|
120
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-TPT6HP4H.js";
|
|
121
115
|
import {
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
getRuntimeConfig,
|
|
117
|
+
setRuntimeConfig
|
|
118
|
+
} from "./chunk-TVQOONDR.js";
|
|
124
119
|
import {
|
|
125
120
|
SessionNotFoundError,
|
|
126
121
|
createLLMClient,
|
|
@@ -129,14 +124,14 @@ import {
|
|
|
129
124
|
getBackendDisplayName,
|
|
130
125
|
getLlmStatus,
|
|
131
126
|
getModelProfile
|
|
132
|
-
} from "./chunk-
|
|
127
|
+
} from "./chunk-U6BUT37A.js";
|
|
128
|
+
import {
|
|
129
|
+
getGlobalConfigDir
|
|
130
|
+
} from "./chunk-5YQAXUKO.js";
|
|
133
131
|
import {
|
|
134
132
|
logger,
|
|
135
133
|
setLogLevel
|
|
136
|
-
} from "./chunk-
|
|
137
|
-
import {
|
|
138
|
-
getGlobalConfigDir
|
|
139
|
-
} from "./chunk-AF7XS4OI.js";
|
|
134
|
+
} from "./chunk-PNBH3RAX.js";
|
|
140
135
|
|
|
141
136
|
// src/server/index.ts
|
|
142
137
|
import express from "express";
|
|
@@ -157,92 +152,157 @@ var RULES = [
|
|
|
157
152
|
{
|
|
158
153
|
match: /ID\s*["']([a-z0-9-]+)["']:\s*["']([^"']+)["'][\s\S]*ID\s*["']([a-z0-9-]+)["']:\s*["']([^"']+)["']/i,
|
|
159
154
|
tools: [
|
|
160
|
-
{ name: "
|
|
161
|
-
{ name: "
|
|
155
|
+
{ name: "criterion", arguments: { action: "add", id: "$1", description: "$2" } },
|
|
156
|
+
{ name: "criterion", arguments: { action: "add", id: "$3", description: "$4" } }
|
|
162
157
|
],
|
|
163
158
|
response: "Added both criteria."
|
|
164
159
|
},
|
|
160
|
+
// Single criterion: "Add criterion ID "test-1" with description "The tests pass""
|
|
161
|
+
{
|
|
162
|
+
match: /Add criterion ID\s*["']([a-z0-9-]+)["']\s*with description\s*["']([^"']+)["'][\s\S]*/i,
|
|
163
|
+
tools: [{ name: "criterion", arguments: { action: "add", id: "$1", description: "$2" } }],
|
|
164
|
+
response: "Added the criterion."
|
|
165
|
+
},
|
|
165
166
|
// Single criterion: ID "test-1" with/: description "The tests pass"
|
|
166
167
|
{
|
|
167
168
|
match: /ID\s*["']([a-z0-9-]+)["'].*description\s*["']([^"']+)["']/i,
|
|
168
|
-
tools: [{ name: "
|
|
169
|
+
tools: [{ name: "criterion", arguments: { action: "add", id: "$1", description: "$2" } }],
|
|
169
170
|
response: "Added the criterion."
|
|
170
171
|
},
|
|
171
172
|
{
|
|
172
|
-
match: /Add these two acceptance criteria:\s*1\.\s*([^\n]+)\s*2\.\s*([^\n]+)\s*Use
|
|
173
|
+
match: /Add these two acceptance criteria:\s*1\.\s*([^\n]+)\s*2\.\s*([^\n]+)\s*Use criterion for each\./i,
|
|
173
174
|
tools: [
|
|
174
|
-
{ name: "
|
|
175
|
-
{ name: "
|
|
175
|
+
{ name: "criterion", arguments: { action: "add", id: "criterion-1", description: "$1" } },
|
|
176
|
+
{ name: "criterion", arguments: { action: "add", id: "criterion-2", description: "$2" } }
|
|
176
177
|
],
|
|
177
178
|
response: "Added both criteria."
|
|
178
179
|
},
|
|
179
180
|
{
|
|
180
|
-
match: /Add these two acceptance criteria:[\s\S]*?1\.\s*([^\n]+)[\s\S]*?2\.\s*([^\n]+)[\s\S]*?Use
|
|
181
|
+
match: /Add these two acceptance criteria:[\s\S]*?1\.\s*([^\n]+)[\s\S]*?2\.\s*([^\n]+)[\s\S]*?Use (?:add_)?criterion for each one\./i,
|
|
181
182
|
tools: [
|
|
182
|
-
{ name: "
|
|
183
|
-
{ name: "
|
|
183
|
+
{ name: "criterion", arguments: { action: "add", id: "criterion-1", description: "$1" } },
|
|
184
|
+
{ name: "criterion", arguments: { action: "add", id: "criterion-2", description: "$2" } }
|
|
184
185
|
],
|
|
185
186
|
response: "Added both criteria."
|
|
186
187
|
},
|
|
187
188
|
{
|
|
188
|
-
match: /Add
|
|
189
|
-
tools: [
|
|
189
|
+
match: /Add these two acceptance criteria:[\s\S]*?1\.\s*([^\n]+)[\s\S]*?2\.\s*([^\n]+)/i,
|
|
190
|
+
tools: [
|
|
191
|
+
{ name: "criterion", arguments: { action: "add", id: "criterion-1", description: "$1" } },
|
|
192
|
+
{ name: "criterion", arguments: { action: "add", id: "criterion-2", description: "$2" } }
|
|
193
|
+
],
|
|
194
|
+
response: "Added both criteria."
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
match: /Add criterion:\s*([\s\S]+?)\s*Use criterion\.?/i,
|
|
198
|
+
tools: [{ name: "criterion", arguments: { action: "add", id: "$auto", description: "$1" } }],
|
|
190
199
|
response: "Added the criterion."
|
|
191
200
|
},
|
|
192
201
|
// Single criterion: ID "test-1": "The tests pass" (colon format)
|
|
193
202
|
{
|
|
194
203
|
match: /ID\s*["']([a-z0-9-]+)["']\s*:\s*["']([^"']+)["']/i,
|
|
195
|
-
tools: [{ name: "
|
|
204
|
+
tools: [{ name: "criterion", arguments: { action: "add", id: "$1", description: "$2" } }],
|
|
196
205
|
response: "Added the criterion."
|
|
197
206
|
},
|
|
198
207
|
// complete_criterion to mark "id" as done
|
|
199
208
|
{
|
|
200
209
|
match: /complete_criterion.*mark\s*["']([a-z0-9-]+)["']/i,
|
|
201
|
-
tools: [{ name: "
|
|
210
|
+
tools: [{ name: "criterion", arguments: { action: "complete", id: "$1", reason: "Completed successfully" } }],
|
|
202
211
|
response: "Marked criterion as complete."
|
|
203
212
|
},
|
|
204
213
|
// complete_criterion with ID
|
|
205
214
|
{
|
|
206
215
|
match: /complete_criterion.*["']([a-z0-9-]+)["']/i,
|
|
207
|
-
tools: [{ name: "
|
|
216
|
+
tools: [{ name: "criterion", arguments: { action: "complete", id: "$1", reason: "Completed successfully" } }],
|
|
217
|
+
response: "Marked criterion as complete."
|
|
218
|
+
},
|
|
219
|
+
// criterion with action "complete" for "id"
|
|
220
|
+
{
|
|
221
|
+
match: /criterion.*action\s*["']complete["'].*["']([a-z0-9-]+)["']/i,
|
|
222
|
+
tools: [{ name: "criterion", arguments: { action: "complete", id: "$1", reason: "Completed successfully" } }],
|
|
208
223
|
response: "Marked criterion as complete."
|
|
209
224
|
},
|
|
210
225
|
// remove_criterion to remove "id"
|
|
211
226
|
{
|
|
212
227
|
match: /remove_criterion.*remove\s*["']([a-z0-9-]+)["']/i,
|
|
213
|
-
tools: [{ name: "
|
|
228
|
+
tools: [{ name: "criterion", arguments: { action: "remove", id: "$1" } }],
|
|
214
229
|
response: "Removed the criterion."
|
|
215
230
|
},
|
|
216
231
|
// remove_criterion with ID
|
|
217
232
|
{
|
|
218
233
|
match: /remove_criterion.*["']([a-z0-9-]+)["']/i,
|
|
219
|
-
tools: [{ name: "
|
|
234
|
+
tools: [{ name: "criterion", arguments: { action: "remove", id: "$1" } }],
|
|
220
235
|
response: "Removed the criterion."
|
|
221
236
|
},
|
|
222
237
|
// update_criterion to change "id" description to "new"
|
|
223
238
|
{
|
|
224
239
|
match: /update_criterion.*change\s*["']([a-z0-9-]+)["'].*to\s*["']([^"']+)["']/i,
|
|
225
|
-
tools: [{ name: "
|
|
240
|
+
tools: [{ name: "criterion", arguments: { action: "update", id: "$1", description: "$2" } }],
|
|
226
241
|
response: "Updated the criterion."
|
|
227
242
|
},
|
|
228
243
|
// update_criterion with ID and description
|
|
229
244
|
{
|
|
230
245
|
match: /update_criterion.*["']([a-z0-9-]+)["'].*["']([^"']+)["']/i,
|
|
231
|
-
tools: [{ name: "
|
|
246
|
+
tools: [{ name: "criterion", arguments: { action: "update", id: "$1", description: "$2" } }],
|
|
232
247
|
response: "Updated the criterion."
|
|
233
248
|
},
|
|
234
249
|
// get_criteria
|
|
235
250
|
{
|
|
236
|
-
match: /get_criteria/i,
|
|
237
|
-
tools: [{ name: "
|
|
251
|
+
match: /get_criteria|show.*criteria|list.*criteria/i,
|
|
252
|
+
tools: [{ name: "criterion", arguments: { action: "get" } }],
|
|
238
253
|
response: "Here are the current criteria."
|
|
239
254
|
},
|
|
255
|
+
// pass_criterion
|
|
256
|
+
{
|
|
257
|
+
match: /pass_criterion.*["']([a-z0-9-]+)["']/i,
|
|
258
|
+
tools: [{ name: "criterion", arguments: { action: "pass", id: "$1", reason: "Verified successfully" } }],
|
|
259
|
+
response: "Criterion passed."
|
|
260
|
+
},
|
|
261
|
+
// fail_criterion
|
|
262
|
+
{
|
|
263
|
+
match: /fail_criterion.*["']([a-z0-9-]+)["']/i,
|
|
264
|
+
tools: [{ name: "criterion", arguments: { action: "fail", id: "$1", reason: "Verification failed" } }],
|
|
265
|
+
response: "Criterion failed."
|
|
266
|
+
},
|
|
240
267
|
// Generic add criterion (fallback)
|
|
241
268
|
{
|
|
242
269
|
match: /add.*criterion/i,
|
|
243
|
-
tools: [{ name: "
|
|
270
|
+
tools: [{ name: "criterion", arguments: { action: "add", description: "Test criterion" } }],
|
|
244
271
|
response: "Added the criterion."
|
|
245
272
|
},
|
|
273
|
+
// Verifier nudge with fail criterion (check for "fail" in ID) - MUST come before general pass rule
|
|
274
|
+
{
|
|
275
|
+
match: /Use criterion with action ["']pass["'] or ["']fail["'].*criterion.*:\s*([a-z0-9-]*fail[a-z0-9-]*)/i,
|
|
276
|
+
tools: [{ name: "criterion", arguments: { action: "fail", id: "$1", reason: "Verification failed" } }],
|
|
277
|
+
response: "Criterion failed."
|
|
278
|
+
},
|
|
279
|
+
// Verifier nudge: Use criterion with action "pass" or "fail" for criterion-id-1, criterion-id-2
|
|
280
|
+
{
|
|
281
|
+
match: /Use criterion with action ["']pass["'] or ["']fail["'].*criterion:\s*([a-z0-9-]+)/i,
|
|
282
|
+
tools: [{ name: "criterion", arguments: { action: "pass", id: "$1", reason: "Verified successfully" } }],
|
|
283
|
+
response: "Criterion passed."
|
|
284
|
+
},
|
|
285
|
+
// Verifier nudge with multiple criteria
|
|
286
|
+
{
|
|
287
|
+
match: /Use criterion with action ["']pass["'] or ["']fail["'].*criterion:\s*([a-z0-9-]+),\s*([a-z0-9-]+)/i,
|
|
288
|
+
tools: [
|
|
289
|
+
{ name: "criterion", arguments: { action: "pass", id: "$1", reason: "Verified successfully" } },
|
|
290
|
+
{ name: "criterion", arguments: { action: "pass", id: "$2", reason: "Verified successfully" } }
|
|
291
|
+
],
|
|
292
|
+
response: "Criteria passed."
|
|
293
|
+
},
|
|
294
|
+
// Verifier should fail a criterion
|
|
295
|
+
{
|
|
296
|
+
match: /Verifier should fail.*criterion.*ID\s*["']([a-z0-9-]+)["']/i,
|
|
297
|
+
tools: [{ name: "criterion", arguments: { action: "fail", id: "$1", reason: "Verification failed" } }],
|
|
298
|
+
response: "Criterion failed."
|
|
299
|
+
},
|
|
300
|
+
// Verifier fail_criterion command
|
|
301
|
+
{
|
|
302
|
+
match: /fail_criterion.*ID\s*["']([a-z0-9-]+)["']/i,
|
|
303
|
+
tools: [{ name: "criterion", arguments: { action: "fail", id: "$1", reason: "Verification failed" } }],
|
|
304
|
+
response: "Criterion failed."
|
|
305
|
+
},
|
|
246
306
|
// -------------------------------------------------------------------------
|
|
247
307
|
// File Read Tools
|
|
248
308
|
// -------------------------------------------------------------------------
|
|
@@ -533,11 +593,12 @@ var RULES = [
|
|
|
533
593
|
{
|
|
534
594
|
match: /todo_write.*Read files.*Make changes/i,
|
|
535
595
|
tools: [{
|
|
536
|
-
name: "
|
|
596
|
+
name: "todo",
|
|
537
597
|
arguments: {
|
|
598
|
+
action: "write",
|
|
538
599
|
todos: [
|
|
539
|
-
{ content: "Read files", status: "in_progress"
|
|
540
|
-
{ content: "Make changes", status: "pending"
|
|
600
|
+
{ content: "Read files", status: "in_progress" },
|
|
601
|
+
{ content: "Make changes", status: "pending" }
|
|
541
602
|
]
|
|
542
603
|
}
|
|
543
604
|
}],
|
|
@@ -546,9 +607,10 @@ var RULES = [
|
|
|
546
607
|
{
|
|
547
608
|
match: /todo_write|todo.*list/i,
|
|
548
609
|
tools: [{
|
|
549
|
-
name: "
|
|
610
|
+
name: "todo",
|
|
550
611
|
arguments: {
|
|
551
|
-
|
|
612
|
+
action: "write",
|
|
613
|
+
todos: [{ content: "Test task", status: "pending" }]
|
|
552
614
|
}
|
|
553
615
|
}],
|
|
554
616
|
response: "Created todo list."
|
|
@@ -567,6 +629,19 @@ var RULES = [
|
|
|
567
629
|
response: "Here is a detailed explanation of the topic. TypeScript is a statically typed superset of JavaScript that adds optional type annotations. It provides better tooling, catches errors at compile time, and makes code more maintainable."
|
|
568
630
|
},
|
|
569
631
|
// -------------------------------------------------------------------------
|
|
632
|
+
// Ask User Tool
|
|
633
|
+
// -------------------------------------------------------------------------
|
|
634
|
+
{
|
|
635
|
+
match: /ask.*user|ask.*question|clarif/i,
|
|
636
|
+
tools: [{ name: "ask_user", arguments: { question: "What would you like me to do?" } }],
|
|
637
|
+
response: "I asked the user."
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
match: /confirm.*with.*user/i,
|
|
641
|
+
tools: [{ name: "ask_user", arguments: { question: "Should I proceed with this action?" } }],
|
|
642
|
+
response: "I asked for confirmation."
|
|
643
|
+
},
|
|
644
|
+
// -------------------------------------------------------------------------
|
|
570
645
|
// Default Responses (no tools)
|
|
571
646
|
// -------------------------------------------------------------------------
|
|
572
647
|
{
|
|
@@ -737,11 +812,12 @@ function getPromptAwareToolResponse(prompt) {
|
|
|
737
812
|
if (/Use the todo_write tool to create a todo list with 2 items/i.test(prompt)) {
|
|
738
813
|
return {
|
|
739
814
|
tools: [{
|
|
740
|
-
name: "
|
|
815
|
+
name: "todo",
|
|
741
816
|
arguments: {
|
|
817
|
+
action: "write",
|
|
742
818
|
todos: [
|
|
743
|
-
{ content: "Read files", status: "in_progress"
|
|
744
|
-
{ content: "Make changes", status: "pending"
|
|
819
|
+
{ content: "Read files", status: "in_progress" },
|
|
820
|
+
{ content: "Make changes", status: "pending" }
|
|
745
821
|
]
|
|
746
822
|
}
|
|
747
823
|
}],
|
|
@@ -751,9 +827,9 @@ function getPromptAwareToolResponse(prompt) {
|
|
|
751
827
|
if (/First call get_criteria to see what needs to be done, then create src\/test\.ts and call complete_criterion for ["']test-file["']\./i.test(prompt)) {
|
|
752
828
|
return {
|
|
753
829
|
tools: [
|
|
754
|
-
{ name: "
|
|
830
|
+
{ name: "criterion", arguments: { action: "get" } },
|
|
755
831
|
{ name: "write_file", arguments: { path: "src/test.ts", content: "export const created = true" } },
|
|
756
|
-
{ name: "
|
|
832
|
+
{ name: "criterion", arguments: { action: "complete", id: "test-file", reason: "Created the requested file" } },
|
|
757
833
|
{ name: "step_done", arguments: {} }
|
|
758
834
|
],
|
|
759
835
|
response: "Reviewed the criteria, created the file, and completed the criterion."
|
|
@@ -763,7 +839,7 @@ function getPromptAwareToolResponse(prompt) {
|
|
|
763
839
|
return {
|
|
764
840
|
tools: [
|
|
765
841
|
{ name: "write_file", arguments: { path: "src/utils.ts", content: "export const created = true" } },
|
|
766
|
-
{ name: "
|
|
842
|
+
{ name: "criterion", arguments: { action: "complete", id: "file-created", reason: "Created the requested file" } },
|
|
767
843
|
{ name: "step_done", arguments: {} }
|
|
768
844
|
],
|
|
769
845
|
response: "Created the file and completed the criterion."
|
|
@@ -852,27 +928,27 @@ function getConversationAwareToolResponse(request) {
|
|
|
852
928
|
if (conversationText.includes("inspect-src")) {
|
|
853
929
|
tools.push(
|
|
854
930
|
{ name: "read_file", arguments: { path: "src" } },
|
|
855
|
-
{ name: "
|
|
931
|
+
{ name: "criterion", arguments: { action: "complete", id: "inspect-src", reason: "Inspected the src directory and reported what exists" } }
|
|
856
932
|
);
|
|
857
933
|
completedCriteria.push("inspect-src");
|
|
858
934
|
}
|
|
859
935
|
if (conversationText.includes("trivial-pass")) {
|
|
860
|
-
tools.push({ name: "
|
|
936
|
+
tools.push({ name: "criterion", arguments: { action: "complete", id: "trivial-pass", reason: "Trivial criterion passes immediately" } });
|
|
861
937
|
completedCriteria.push("trivial-pass");
|
|
862
938
|
}
|
|
863
939
|
if (conversationText.includes("verify-fail")) {
|
|
864
|
-
tools.push({ name: "
|
|
940
|
+
tools.push({ name: "criterion", arguments: { action: "complete", id: "verify-fail", reason: "Prepared criterion for verification" } });
|
|
865
941
|
completedCriteria.push("verify-fail");
|
|
866
942
|
}
|
|
867
943
|
if (conversationText.includes("file-created")) {
|
|
868
944
|
tools.push(
|
|
869
945
|
{ name: "write_file", arguments: { path: "src/utils.ts", content: "export const created = true" } },
|
|
870
|
-
{ name: "
|
|
946
|
+
{ name: "criterion", arguments: { action: "complete", id: "file-created", reason: "Created the requested file" } }
|
|
871
947
|
);
|
|
872
948
|
completedCriteria.push("file-created");
|
|
873
949
|
}
|
|
874
950
|
if (tools.length === 0) {
|
|
875
|
-
tools.push({ name: "
|
|
951
|
+
tools.push({ name: "criterion", arguments: { action: "complete", id: "mock-crit", reason: "Completed for testing" } });
|
|
876
952
|
completedCriteria.push("mock-crit");
|
|
877
953
|
}
|
|
878
954
|
tools.push({ name: "step_done", arguments: {} });
|
|
@@ -884,7 +960,7 @@ function getConversationAwareToolResponse(request) {
|
|
|
884
960
|
if (/fulfil the \d+ criteria/i.test(prompt)) {
|
|
885
961
|
return {
|
|
886
962
|
tools: [
|
|
887
|
-
{ name: "
|
|
963
|
+
{ name: "criterion", arguments: { action: "complete", id: "mock-crit", reason: "Completed for testing" } },
|
|
888
964
|
{ name: "step_done", arguments: {} }
|
|
889
965
|
],
|
|
890
966
|
response: "Completed criterion and finished step."
|
|
@@ -895,17 +971,17 @@ function getConversationAwareToolResponse(request) {
|
|
|
895
971
|
if (conversationText.includes("file-created")) {
|
|
896
972
|
tools.push(
|
|
897
973
|
{ name: "write_file", arguments: { path: "src/utils.ts", content: "export const created = true" } },
|
|
898
|
-
{ name: "
|
|
974
|
+
{ name: "criterion", arguments: { action: "complete", id: "file-created", reason: "Created the requested file" } }
|
|
899
975
|
);
|
|
900
976
|
} else if (conversationText.includes("trivial-pass")) {
|
|
901
|
-
tools.push({ name: "
|
|
977
|
+
tools.push({ name: "criterion", arguments: { action: "complete", id: "trivial-pass", reason: "Trivial criterion passes immediately" } });
|
|
902
978
|
} else if (conversationText.includes("verify-fail")) {
|
|
903
|
-
tools.push({ name: "
|
|
979
|
+
tools.push({ name: "criterion", arguments: { action: "complete", id: "verify-fail", reason: "Prepared criterion for verification" } });
|
|
904
980
|
} else {
|
|
905
981
|
if (/1 criteria remaining/i.test(prompt)) {
|
|
906
|
-
tools.push({ name: "
|
|
982
|
+
tools.push({ name: "criterion", arguments: { action: "complete", id: "trivial-pass", reason: "Trivial criterion passes immediately" } });
|
|
907
983
|
} else {
|
|
908
|
-
tools.push({ name: "
|
|
984
|
+
tools.push({ name: "criterion", arguments: { action: "complete", id: "mock-crit", reason: "Completed for testing" } });
|
|
909
985
|
}
|
|
910
986
|
}
|
|
911
987
|
tools.push({ name: "step_done", arguments: {} });
|
|
@@ -917,7 +993,7 @@ function getConversationAwareToolResponse(request) {
|
|
|
917
993
|
if (/Verify each criterion marked \[NEEDS VERIFICATION\]\./i.test(prompt)) {
|
|
918
994
|
const alreadyVerified = request.messages.some(
|
|
919
995
|
(m) => m.role === "assistant" && m.toolCalls?.some(
|
|
920
|
-
(tc) => tc.name === "
|
|
996
|
+
(tc) => tc.name === "criterion" && (tc.arguments["action"] === "pass" || tc.arguments["action"] === "fail")
|
|
921
997
|
)
|
|
922
998
|
);
|
|
923
999
|
if (alreadyVerified) {
|
|
@@ -926,23 +1002,23 @@ function getConversationAwareToolResponse(request) {
|
|
|
926
1002
|
const tools = [];
|
|
927
1003
|
const terminalizedCriteria = [];
|
|
928
1004
|
if (conversationText.includes("trivial-pass")) {
|
|
929
|
-
tools.push({ name: "
|
|
1005
|
+
tools.push({ name: "criterion", arguments: { action: "pass", id: "trivial-pass", reason: "Verified successfully" } });
|
|
930
1006
|
terminalizedCriteria.push("trivial-pass");
|
|
931
1007
|
}
|
|
932
1008
|
if (conversationText.includes("inspect-src")) {
|
|
933
|
-
tools.push({ name: "
|
|
1009
|
+
tools.push({ name: "criterion", arguments: { action: "pass", id: "inspect-src", reason: "Verified the src directory was inspected successfully" } });
|
|
934
1010
|
terminalizedCriteria.push("inspect-src");
|
|
935
1011
|
}
|
|
936
1012
|
if (conversationText.includes("verify-fail")) {
|
|
937
|
-
tools.push({ name: "
|
|
1013
|
+
tools.push({ name: "criterion", arguments: { action: "complete", id: "verify-fail", reason: "Verification fails intentionally for this criterion" } });
|
|
938
1014
|
terminalizedCriteria.push("verify-fail");
|
|
939
1015
|
}
|
|
940
1016
|
if (conversationText.includes("file-created")) {
|
|
941
|
-
tools.push({ name: "
|
|
1017
|
+
tools.push({ name: "criterion", arguments: { action: "pass", id: "file-created", reason: "Verified the file was created successfully" } });
|
|
942
1018
|
terminalizedCriteria.push("file-created");
|
|
943
1019
|
}
|
|
944
1020
|
if (conversationText.includes("impossible/path")) {
|
|
945
|
-
tools.push({ name: "
|
|
1021
|
+
tools.push({ name: "criterion", arguments: { action: "complete", id: "auto-impossible", reason: "Impossible path does not exist" } });
|
|
946
1022
|
terminalizedCriteria.push("auto-impossible");
|
|
947
1023
|
}
|
|
948
1024
|
if (tools.length > 0) {
|
|
@@ -1101,211 +1177,189 @@ function createMockLLMClient() {
|
|
|
1101
1177
|
}
|
|
1102
1178
|
|
|
1103
1179
|
// src/server/ws/server.ts
|
|
1104
|
-
import { WebSocketServer, WebSocket } from "ws";
|
|
1180
|
+
import { WebSocketServer, WebSocket as WebSocket2 } from "ws";
|
|
1105
1181
|
|
|
1106
|
-
// src/server/
|
|
1107
|
-
|
|
1108
|
-
var VERIFIER_STALL_REASON = "Verifier stopped repeatedly before terminalizing verification after repeated nudges.";
|
|
1109
|
-
function getCriteriaAwaitingVerification(criteria) {
|
|
1110
|
-
return criteria.filter((criterion) => criterion.status.type === "completed");
|
|
1111
|
-
}
|
|
1112
|
-
function buildVerifierNudgeContent(criteria) {
|
|
1113
|
-
const ids = criteria.map((criterion) => criterion.id).join(", ");
|
|
1114
|
-
return `You stopped before finalizing verification. ${criteria.length} criteria still need a terminal verification result. Use pass_criterion or fail_criterion for each remaining criterion: ${ids}.`;
|
|
1115
|
-
}
|
|
1116
|
-
function buildVerifierRestartContent(criteria) {
|
|
1117
|
-
const ids = criteria.map((criterion) => criterion.id).join(", ");
|
|
1118
|
-
return `${VERIFIER_STALL_REASON} Leaving remaining criteria unchanged so verification can restart in a fresh window: ${ids}.`;
|
|
1119
|
-
}
|
|
1120
|
-
function createVerifierNudgeConfig() {
|
|
1121
|
-
return {
|
|
1122
|
-
maxConsecutiveNudges: MAX_CONSECUTIVE_VERIFIER_NUDGES,
|
|
1123
|
-
getCriteriaAwaiting: getCriteriaAwaitingVerification,
|
|
1124
|
-
buildNudgeContent: buildVerifierNudgeContent,
|
|
1125
|
-
buildRestartContent: buildVerifierRestartContent
|
|
1126
|
-
};
|
|
1127
|
-
}
|
|
1182
|
+
// src/server/ws/terminal.ts
|
|
1183
|
+
import WebSocket from "ws";
|
|
1128
1184
|
|
|
1129
|
-
// src/server/
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
return {
|
|
1138
|
-
|
|
1139
|
-
|
|
1185
|
+
// src/server/terminal/manager.ts
|
|
1186
|
+
import * as pty from "node-pty";
|
|
1187
|
+
import os from "os";
|
|
1188
|
+
var TerminalManager = class {
|
|
1189
|
+
sessions = /* @__PURE__ */ new Map();
|
|
1190
|
+
outputCallbacks = /* @__PURE__ */ new Set();
|
|
1191
|
+
outputHistory = /* @__PURE__ */ new Map();
|
|
1192
|
+
generateId() {
|
|
1193
|
+
return `term_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
1194
|
+
}
|
|
1195
|
+
getShell() {
|
|
1196
|
+
const shell = process.env["SHELL"] || "/bin/bash";
|
|
1197
|
+
return shell;
|
|
1198
|
+
}
|
|
1199
|
+
resolveWorkdir(workdir) {
|
|
1200
|
+
if (workdir && workdir.length > 0) {
|
|
1201
|
+
return workdir;
|
|
1202
|
+
}
|
|
1203
|
+
return os.homedir();
|
|
1204
|
+
}
|
|
1205
|
+
create(workdir) {
|
|
1206
|
+
const id = this.generateId();
|
|
1207
|
+
const shell = this.getShell();
|
|
1208
|
+
const cwd = this.resolveWorkdir(workdir);
|
|
1209
|
+
const ptyProcess = pty.spawn(shell, [], {
|
|
1210
|
+
name: "xterm-256color",
|
|
1211
|
+
cols: 80,
|
|
1212
|
+
rows: 24,
|
|
1213
|
+
cwd,
|
|
1214
|
+
env: process.env
|
|
1215
|
+
});
|
|
1216
|
+
const session = {
|
|
1217
|
+
id,
|
|
1218
|
+
pty: ptyProcess,
|
|
1219
|
+
workdir: cwd
|
|
1140
1220
|
};
|
|
1221
|
+
ptyProcess.onData((data) => {
|
|
1222
|
+
const output = { sessionId: id, data };
|
|
1223
|
+
for (const cb of this.outputCallbacks) {
|
|
1224
|
+
cb(output);
|
|
1225
|
+
}
|
|
1226
|
+
const history = this.outputHistory.get(id) || [];
|
|
1227
|
+
history.push(data);
|
|
1228
|
+
if (history.length > 1e4) history.shift();
|
|
1229
|
+
this.outputHistory.set(id, history);
|
|
1230
|
+
});
|
|
1231
|
+
this.sessions.set(id, session);
|
|
1232
|
+
logger.info("Terminal session created", { id, shell, cwd });
|
|
1233
|
+
return session;
|
|
1141
1234
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
model
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
async function runChatTurn(options) {
|
|
1150
|
-
const { sessionManager: sessionManager2, sessionId } = options;
|
|
1151
|
-
const eventStore = getEventStore();
|
|
1152
|
-
const statsIdentity = resolveStatsIdentity(options);
|
|
1153
|
-
const session = sessionManager2.requireSession(sessionId);
|
|
1154
|
-
const mode = session.mode;
|
|
1155
|
-
logger.debug("Starting chat turn", { sessionId, mode });
|
|
1156
|
-
const turnMetrics = new TurnMetrics();
|
|
1157
|
-
try {
|
|
1158
|
-
if (mode === "builder") {
|
|
1159
|
-
await runBuilderTurn(options, turnMetrics);
|
|
1160
|
-
} else {
|
|
1161
|
-
await runGenericAgentTurn(options, turnMetrics, mode);
|
|
1162
|
-
}
|
|
1163
|
-
const snapshot = buildSnapshot(sessionManager2, sessionId, turnMetrics.buildStats(statsIdentity, mode));
|
|
1164
|
-
const snapshotEvent = eventStore.append(sessionId, { type: "turn.snapshot", data: snapshot });
|
|
1165
|
-
const deletedCount = eventStore.cleanupOldEvents(sessionId);
|
|
1166
|
-
if (deletedCount > 0) {
|
|
1167
|
-
logger.debug("Cleaned up old events after snapshot", { sessionId, deletedCount, snapshotSeq: snapshotEvent.seq });
|
|
1235
|
+
write(sessionId, data) {
|
|
1236
|
+
const session = this.sessions.get(sessionId);
|
|
1237
|
+
if (!session) {
|
|
1238
|
+
return false;
|
|
1168
1239
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
}));
|
|
1177
|
-
eventStore.append(sessionId, createChatDoneEvent(waitMsgId, "waiting_for_user"));
|
|
1178
|
-
return;
|
|
1240
|
+
session.pty.write(data);
|
|
1241
|
+
return true;
|
|
1242
|
+
}
|
|
1243
|
+
resize(sessionId, cols, rows) {
|
|
1244
|
+
const session = this.sessions.get(sessionId);
|
|
1245
|
+
if (!session) {
|
|
1246
|
+
return false;
|
|
1179
1247
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
recoverable: false
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
eventStore.append(sessionId, createMessageStartEvent(errorMsgId2, "user", `Access denied: ${error.paths.join(", ")}. If you need this file, explain why and ask the user for permission.`, {
|
|
1191
|
-
...getCurrentWindowMessageOptions(sessionId) ?? {},
|
|
1192
|
-
isSystemGenerated: true,
|
|
1193
|
-
messageKind: "correction"
|
|
1194
|
-
}));
|
|
1195
|
-
eventStore.append(sessionId, createChatDoneEvent(errorMsgId2, "error"));
|
|
1196
|
-
return;
|
|
1248
|
+
session.pty.resize(cols, rows);
|
|
1249
|
+
return true;
|
|
1250
|
+
}
|
|
1251
|
+
kill(sessionId) {
|
|
1252
|
+
const session = this.sessions.get(sessionId);
|
|
1253
|
+
if (!session) {
|
|
1254
|
+
return false;
|
|
1197
1255
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1256
|
+
session.pty.kill();
|
|
1257
|
+
this.sessions.delete(sessionId);
|
|
1258
|
+
logger.info("Terminal session killed", { id: sessionId });
|
|
1259
|
+
return true;
|
|
1260
|
+
}
|
|
1261
|
+
get(sessionId) {
|
|
1262
|
+
return this.sessions.get(sessionId);
|
|
1263
|
+
}
|
|
1264
|
+
getAll() {
|
|
1265
|
+
return Array.from(this.sessions.values());
|
|
1266
|
+
}
|
|
1267
|
+
getOutputHistory(sessionId) {
|
|
1268
|
+
return (this.outputHistory.get(sessionId) || []).join("");
|
|
1269
|
+
}
|
|
1270
|
+
onOutput(callback) {
|
|
1271
|
+
this.outputCallbacks.add(callback);
|
|
1272
|
+
return () => {
|
|
1273
|
+
this.outputCallbacks.delete(callback);
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
killAll() {
|
|
1277
|
+
for (const session of this.sessions.values()) {
|
|
1278
|
+
session.pty.kill();
|
|
1200
1279
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
eventStore.append(sessionId, {
|
|
1204
|
-
type: "chat.error",
|
|
1205
|
-
data: {
|
|
1206
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1207
|
-
recoverable: false
|
|
1208
|
-
}
|
|
1209
|
-
});
|
|
1210
|
-
eventStore.append(sessionId, createMessageStartEvent(errorMsgId, "user", `Error: ${error instanceof Error ? error.message : "Unknown error"}`, {
|
|
1211
|
-
...getCurrentWindowMessageOptions(sessionId) ?? {},
|
|
1212
|
-
isSystemGenerated: true,
|
|
1213
|
-
messageKind: "correction"
|
|
1214
|
-
}));
|
|
1215
|
-
eventStore.append(sessionId, createChatDoneEvent(errorMsgId, "error"));
|
|
1216
|
-
} finally {
|
|
1217
|
-
eventStore.append(sessionId, { type: "running.changed", data: { isRunning: false } });
|
|
1280
|
+
this.sessions.clear();
|
|
1281
|
+
logger.info("All terminal sessions killed");
|
|
1218
1282
|
}
|
|
1283
|
+
};
|
|
1284
|
+
var terminalManager = new TerminalManager();
|
|
1285
|
+
|
|
1286
|
+
// src/server/ws/terminal.ts
|
|
1287
|
+
var subscriptions = /* @__PURE__ */ new Set();
|
|
1288
|
+
var outputHandlerRegistered = false;
|
|
1289
|
+
function registerOutputHandler() {
|
|
1290
|
+
if (outputHandlerRegistered) return;
|
|
1291
|
+
outputHandlerRegistered = true;
|
|
1292
|
+
terminalManager.onOutput((output) => {
|
|
1293
|
+
const subs = Array.from(subscriptions).filter(
|
|
1294
|
+
(s) => s.sessionId === output.sessionId && s.ws.readyState === WebSocket.OPEN
|
|
1295
|
+
);
|
|
1296
|
+
for (const sub of subs) {
|
|
1297
|
+
sub.ws.send(JSON.stringify({
|
|
1298
|
+
type: "terminal.output",
|
|
1299
|
+
payload: { sessionId: output.sessionId, data: output.data }
|
|
1300
|
+
}));
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1219
1303
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
llmClient: options.llmClient,
|
|
1230
|
-
statsIdentity,
|
|
1231
|
-
signal: options.signal,
|
|
1232
|
-
onMessage: options.onMessage,
|
|
1233
|
-
assembleRequest: (input) => assembleAgentRequest({ ...input, agentDef, subAgentDefs }),
|
|
1234
|
-
getToolRegistry: () => getToolRegistryForAgent(agentDef)
|
|
1235
|
-
}, turnMetrics);
|
|
1236
|
-
}
|
|
1237
|
-
async function runBuilderTurn(options, turnMetrics) {
|
|
1238
|
-
const { sessionManager: sessionManager2, sessionId } = options;
|
|
1239
|
-
const statsIdentity = resolveStatsIdentity(options);
|
|
1240
|
-
const eventStore = getEventStore();
|
|
1241
|
-
const allAgents = await loadAllAgentsDefault();
|
|
1242
|
-
const builderDef = findAgentById("builder", allAgents);
|
|
1243
|
-
const subAgentDefs = getSubAgents(allAgents);
|
|
1244
|
-
let stepDoneCalled = false;
|
|
1245
|
-
return {
|
|
1246
|
-
...await runTopLevelAgentLoop({
|
|
1247
|
-
mode: "builder",
|
|
1248
|
-
sessionManager: sessionManager2,
|
|
1249
|
-
sessionId,
|
|
1250
|
-
llmClient: options.llmClient,
|
|
1251
|
-
statsIdentity,
|
|
1252
|
-
signal: options.signal,
|
|
1253
|
-
onMessage: options.onMessage,
|
|
1254
|
-
assembleRequest: (input) => assembleAgentRequest({ ...input, agentDef: builderDef, subAgentDefs }),
|
|
1255
|
-
getToolRegistry: () => {
|
|
1256
|
-
const baseRegistry = getToolRegistryForAgent(builderDef);
|
|
1257
|
-
if (options.injectStepDone === true) {
|
|
1258
|
-
return baseRegistry;
|
|
1259
|
-
}
|
|
1260
|
-
return {
|
|
1261
|
-
tools: baseRegistry.tools.filter((t) => t.name !== "step_done"),
|
|
1262
|
-
definitions: baseRegistry.definitions.filter((d) => d.type === "function" && d.function.name !== "step_done"),
|
|
1263
|
-
execute: baseRegistry.execute
|
|
1264
|
-
};
|
|
1265
|
-
},
|
|
1266
|
-
onToolExecuted: (toolCall, toolResult) => {
|
|
1267
|
-
if (toolCall.name === "step_done" && toolResult.success) {
|
|
1268
|
-
stepDoneCalled = true;
|
|
1304
|
+
function handleTerminalMessage(ws, message) {
|
|
1305
|
+
switch (message.type) {
|
|
1306
|
+
case "terminal.subscribe": {
|
|
1307
|
+
if (message.payload.sessionId) {
|
|
1308
|
+
const exists = Array.from(subscriptions).some(
|
|
1309
|
+
(sub) => sub.ws === ws && sub.sessionId === message.payload.sessionId
|
|
1310
|
+
);
|
|
1311
|
+
if (!exists) {
|
|
1312
|
+
subscriptions.add({ ws, sessionId: message.payload.sessionId });
|
|
1269
1313
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1314
|
+
registerOutputHandler();
|
|
1315
|
+
const sessionId = message.payload.sessionId;
|
|
1316
|
+
const history = terminalManager.getOutputHistory(sessionId);
|
|
1317
|
+
if (history) {
|
|
1318
|
+
setTimeout(() => {
|
|
1319
|
+
ws.send(JSON.stringify({
|
|
1320
|
+
type: "terminal.output",
|
|
1321
|
+
payload: { sessionId, data: history }
|
|
1322
|
+
}));
|
|
1323
|
+
}, 100);
|
|
1273
1324
|
}
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1325
|
+
}
|
|
1326
|
+
break;
|
|
1327
|
+
}
|
|
1328
|
+
case "terminal.write": {
|
|
1329
|
+
if (message.payload.sessionId && message.payload.data) {
|
|
1330
|
+
terminalManager.write(message.payload.sessionId, message.payload.data);
|
|
1331
|
+
}
|
|
1332
|
+
break;
|
|
1333
|
+
}
|
|
1334
|
+
case "terminal.resize": {
|
|
1335
|
+
if (message.payload.sessionId && typeof message.payload.cols === "number" && typeof message.payload.rows === "number") {
|
|
1336
|
+
terminalManager.resize(message.payload.sessionId, message.payload.cols, message.payload.rows);
|
|
1337
|
+
}
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
case "terminal.kill": {
|
|
1341
|
+
if (message.payload.sessionId) {
|
|
1342
|
+
terminalManager.kill(message.payload.sessionId);
|
|
1343
|
+
for (const sub of subscriptions) {
|
|
1344
|
+
if (sub.sessionId === message.payload.sessionId) {
|
|
1345
|
+
subscriptions.delete(sub);
|
|
1346
|
+
}
|
|
1293
1347
|
}
|
|
1348
|
+
ws.send(JSON.stringify({
|
|
1349
|
+
type: "terminal.killed",
|
|
1350
|
+
payload: { sessionId: message.payload.sessionId }
|
|
1351
|
+
}));
|
|
1294
1352
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
}
|
|
1353
|
+
break;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1298
1356
|
}
|
|
1299
|
-
function
|
|
1300
|
-
const
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
session,
|
|
1306
|
-
events,
|
|
1307
|
-
latestSeq
|
|
1308
|
-
});
|
|
1357
|
+
function unsubscribeAllFromTerminal(ws) {
|
|
1358
|
+
for (const sub of subscriptions) {
|
|
1359
|
+
if (sub.ws === ws) {
|
|
1360
|
+
subscriptions.delete(sub);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1309
1363
|
}
|
|
1310
1364
|
|
|
1311
1365
|
// src/server/workflows/registry.ts
|
|
@@ -1465,7 +1519,7 @@ async function deleteWorkflow(configDir, workflowId) {
|
|
|
1465
1519
|
|
|
1466
1520
|
// src/server/runner/types.ts
|
|
1467
1521
|
var RUNNER_CONFIG = {
|
|
1468
|
-
maxVerifyRetries:
|
|
1522
|
+
maxVerifyRetries: 10
|
|
1469
1523
|
// Max times to retry a failing criterion
|
|
1470
1524
|
};
|
|
1471
1525
|
|
|
@@ -1474,14 +1528,14 @@ var TERMINAL_DONE = "$done";
|
|
|
1474
1528
|
var TERMINAL_BLOCKED = "$blocked";
|
|
1475
1529
|
|
|
1476
1530
|
// src/server/workflows/shell.ts
|
|
1477
|
-
import { spawn } from "child_process";
|
|
1531
|
+
import { spawn as spawn2 } from "child_process";
|
|
1478
1532
|
function executeShellCommand(command, cwd, timeout, signal) {
|
|
1479
1533
|
return new Promise((resolve2, reject) => {
|
|
1480
1534
|
if (signal?.aborted) {
|
|
1481
1535
|
reject(new Error("Aborted"));
|
|
1482
1536
|
return;
|
|
1483
1537
|
}
|
|
1484
|
-
const proc =
|
|
1538
|
+
const proc = spawn2("sh", ["-c", command], {
|
|
1485
1539
|
cwd,
|
|
1486
1540
|
env: { ...process.env, FORCE_COLOR: "0" },
|
|
1487
1541
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -1588,7 +1642,7 @@ function evaluateTransitions(transitions, criteria, stepOutcome) {
|
|
|
1588
1642
|
}
|
|
1589
1643
|
return TERMINAL_BLOCKED;
|
|
1590
1644
|
}
|
|
1591
|
-
function
|
|
1645
|
+
function getCurrentWindowMessageOptions(sessionId) {
|
|
1592
1646
|
const contextWindowId = getCurrentContextWindowId(sessionId);
|
|
1593
1647
|
return contextWindowId ? { contextWindowId } : void 0;
|
|
1594
1648
|
}
|
|
@@ -1597,21 +1651,21 @@ function buildReason(criteria) {
|
|
|
1597
1651
|
return `${remaining.length} criteria remaining`;
|
|
1598
1652
|
}
|
|
1599
1653
|
async function executeWorkflow(workflow, options) {
|
|
1600
|
-
const { sessionManager
|
|
1654
|
+
const { sessionManager, sessionId, llmClient, signal, onMessage } = options;
|
|
1601
1655
|
const eventStore = getEventStore();
|
|
1602
1656
|
const startTime = performance.now();
|
|
1603
1657
|
let iterations = 0;
|
|
1604
1658
|
let currentStepId = workflow.entryStep;
|
|
1605
1659
|
let lastStepOutput = {};
|
|
1606
1660
|
let isFirstBuilderEntry = true;
|
|
1607
|
-
const messagesBeforeWorkflow =
|
|
1661
|
+
const messagesBeforeWorkflow = sessionManager.requireSession(sessionId).messages.length;
|
|
1608
1662
|
const stepsById = /* @__PURE__ */ new Map();
|
|
1609
1663
|
for (const step of workflow.steps) {
|
|
1610
1664
|
stepsById.set(step.id, step);
|
|
1611
1665
|
}
|
|
1612
1666
|
logger.debug("Workflow executor starting", { sessionId, workflow: workflow.metadata.id });
|
|
1613
1667
|
if (workflow.startCondition && workflow.startCondition.type !== "always") {
|
|
1614
|
-
const session =
|
|
1668
|
+
const session = sessionManager.requireSession(sessionId);
|
|
1615
1669
|
const criteria = session.criteria;
|
|
1616
1670
|
const conditionMet = evaluateCondition(
|
|
1617
1671
|
workflow.startCondition,
|
|
@@ -1628,7 +1682,7 @@ async function executeWorkflow(workflow, options) {
|
|
|
1628
1682
|
}
|
|
1629
1683
|
}
|
|
1630
1684
|
const startMsgId = crypto.randomUUID();
|
|
1631
|
-
const startWindowOpts =
|
|
1685
|
+
const startWindowOpts = getCurrentWindowMessageOptions(sessionId);
|
|
1632
1686
|
eventStore.append(sessionId, createMessageStartEvent(
|
|
1633
1687
|
startMsgId,
|
|
1634
1688
|
"user",
|
|
@@ -1637,7 +1691,7 @@ async function executeWorkflow(workflow, options) {
|
|
|
1637
1691
|
));
|
|
1638
1692
|
eventStore.append(sessionId, { type: "message.done", data: { messageId: startMsgId } });
|
|
1639
1693
|
if (options.userMessage) {
|
|
1640
|
-
|
|
1694
|
+
sessionManager.addMessage(sessionId, {
|
|
1641
1695
|
role: "user",
|
|
1642
1696
|
content: options.userMessage.content,
|
|
1643
1697
|
...options.userMessage.attachments ? { attachments: options.userMessage.attachments } : {}
|
|
@@ -1662,8 +1716,8 @@ async function executeWorkflow(workflow, options) {
|
|
|
1662
1716
|
totalTime: (performance.now() - startTime) / 1e3
|
|
1663
1717
|
};
|
|
1664
1718
|
}
|
|
1665
|
-
const session =
|
|
1666
|
-
const currentWindowMessageOptions =
|
|
1719
|
+
const session = sessionManager.requireSession(sessionId);
|
|
1720
|
+
const currentWindowMessageOptions = getCurrentWindowMessageOptions(sessionId);
|
|
1667
1721
|
const criteria = session.criteria;
|
|
1668
1722
|
const templateCtx = {
|
|
1669
1723
|
workdir: session.workdir,
|
|
@@ -1677,7 +1731,7 @@ async function executeWorkflow(workflow, options) {
|
|
|
1677
1731
|
modifiedFiles: formatModifiedFiles(session),
|
|
1678
1732
|
stepOutput: lastStepOutput
|
|
1679
1733
|
};
|
|
1680
|
-
|
|
1734
|
+
sessionManager.setPhase(sessionId, step.phase);
|
|
1681
1735
|
logger.debug("Workflow step executing", { sessionId, iteration: iterations, stepId: step.id, stepType: step.type });
|
|
1682
1736
|
let stepOutcome = null;
|
|
1683
1737
|
switch (step.type) {
|
|
@@ -1694,9 +1748,21 @@ async function executeWorkflow(workflow, options) {
|
|
|
1694
1748
|
eventStore.append(sessionId, createMessageStartEvent(promptMsgId, "user", promptContent, {
|
|
1695
1749
|
...currentWindowMessageOptions ?? {},
|
|
1696
1750
|
isSystemGenerated: true,
|
|
1697
|
-
messageKind: "auto-prompt"
|
|
1751
|
+
messageKind: "auto-prompt",
|
|
1752
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
1698
1753
|
}));
|
|
1699
1754
|
eventStore.append(sessionId, { type: "message.done", data: { messageId: promptMsgId } });
|
|
1755
|
+
if (onMessage) {
|
|
1756
|
+
onMessage(createChatMessageMessage({
|
|
1757
|
+
id: promptMsgId,
|
|
1758
|
+
role: "user",
|
|
1759
|
+
content: promptContent,
|
|
1760
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1761
|
+
isSystemGenerated: true,
|
|
1762
|
+
messageKind: "auto-prompt",
|
|
1763
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
1764
|
+
}));
|
|
1765
|
+
}
|
|
1700
1766
|
} else if (!isFirstBuilderEntry) {
|
|
1701
1767
|
const parts = [];
|
|
1702
1768
|
if (agentStep.nudgePrompt) {
|
|
@@ -1709,13 +1775,25 @@ async function executeWorkflow(workflow, options) {
|
|
|
1709
1775
|
eventStore.append(sessionId, createMessageStartEvent(nudgeMsgId, "user", nudgeContent, {
|
|
1710
1776
|
...currentWindowMessageOptions ?? {},
|
|
1711
1777
|
isSystemGenerated: true,
|
|
1712
|
-
messageKind: "correction"
|
|
1778
|
+
messageKind: "correction",
|
|
1779
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
1713
1780
|
}));
|
|
1714
1781
|
eventStore.append(sessionId, { type: "message.done", data: { messageId: nudgeMsgId } });
|
|
1782
|
+
if (onMessage) {
|
|
1783
|
+
onMessage(createChatMessageMessage({
|
|
1784
|
+
id: nudgeMsgId,
|
|
1785
|
+
role: "user",
|
|
1786
|
+
content: nudgeContent,
|
|
1787
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1788
|
+
isSystemGenerated: true,
|
|
1789
|
+
messageKind: "correction",
|
|
1790
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
1791
|
+
}));
|
|
1792
|
+
}
|
|
1715
1793
|
}
|
|
1716
1794
|
const turnMetrics = new TurnMetrics();
|
|
1717
1795
|
const agentResult = await runBuilderTurn({
|
|
1718
|
-
sessionManager
|
|
1796
|
+
sessionManager,
|
|
1719
1797
|
sessionId,
|
|
1720
1798
|
llmClient,
|
|
1721
1799
|
injectStepDone: true,
|
|
@@ -1757,7 +1835,7 @@ async function executeWorkflow(workflow, options) {
|
|
|
1757
1835
|
const result = await executeSubAgent({
|
|
1758
1836
|
subAgentType: subStep.subAgentType,
|
|
1759
1837
|
prompt: resolvedPrompt,
|
|
1760
|
-
sessionManager
|
|
1838
|
+
sessionManager,
|
|
1761
1839
|
sessionId,
|
|
1762
1840
|
llmClient,
|
|
1763
1841
|
toolRegistry: filteredToolRegistry,
|
|
@@ -1780,8 +1858,20 @@ async function executeWorkflow(workflow, options) {
|
|
|
1780
1858
|
eventStore.append(sessionId, createMessageStartEvent(shellMsgId, "user", `Running: \`${command}\``, {
|
|
1781
1859
|
...currentWindowMessageOptions ?? {},
|
|
1782
1860
|
isSystemGenerated: true,
|
|
1783
|
-
messageKind: "auto-prompt"
|
|
1861
|
+
messageKind: "auto-prompt",
|
|
1862
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
1784
1863
|
}));
|
|
1864
|
+
if (onMessage) {
|
|
1865
|
+
onMessage(createChatMessageMessage({
|
|
1866
|
+
id: shellMsgId,
|
|
1867
|
+
role: "user",
|
|
1868
|
+
content: `Running: \`${command}\``,
|
|
1869
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1870
|
+
isSystemGenerated: true,
|
|
1871
|
+
messageKind: "auto-prompt",
|
|
1872
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
1873
|
+
}));
|
|
1874
|
+
}
|
|
1785
1875
|
const result = await executeShellCommand(command, session.workdir, timeout, signal);
|
|
1786
1876
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
1787
1877
|
lastStepOutput = { stdout: result.stdout ?? "", stderr: result.stderr ?? "", exitCode: String(result.exitCode) };
|
|
@@ -1797,20 +1887,30 @@ ${output.slice(0, 1e4)}
|
|
|
1797
1887
|
messageKind: "correction"
|
|
1798
1888
|
}));
|
|
1799
1889
|
eventStore.append(sessionId, { type: "message.done", data: { messageId: outputMsgId } });
|
|
1890
|
+
if (onMessage) {
|
|
1891
|
+
onMessage(createChatMessageMessage({
|
|
1892
|
+
id: outputMsgId,
|
|
1893
|
+
role: "user",
|
|
1894
|
+
content: outputContent,
|
|
1895
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1896
|
+
isSystemGenerated: true,
|
|
1897
|
+
messageKind: "correction"
|
|
1898
|
+
}));
|
|
1899
|
+
}
|
|
1800
1900
|
stepOutcome = { result: successCodes.includes(result.exitCode) ? "success" : "failure", output: lastStepOutput };
|
|
1801
1901
|
break;
|
|
1802
1902
|
}
|
|
1803
1903
|
}
|
|
1804
|
-
const refreshedSession =
|
|
1904
|
+
const refreshedSession = sessionManager.requireSession(sessionId);
|
|
1805
1905
|
const nextStepId = evaluateTransitions(
|
|
1806
1906
|
step.transitions,
|
|
1807
1907
|
refreshedSession.criteria,
|
|
1808
1908
|
stepOutcome
|
|
1809
1909
|
);
|
|
1810
1910
|
if (nextStepId === TERMINAL_DONE) {
|
|
1811
|
-
|
|
1911
|
+
sessionManager.setPhase(sessionId, "done");
|
|
1812
1912
|
const totalTimeSeconds = Math.round((performance.now() - startTime) / 100) / 10;
|
|
1813
|
-
const completedSession =
|
|
1913
|
+
const completedSession = sessionManager.requireSession(sessionId);
|
|
1814
1914
|
const workflowMessages = completedSession.messages.slice(messagesBeforeWorkflow);
|
|
1815
1915
|
const workflowStats = computeSessionStats(workflowMessages);
|
|
1816
1916
|
const totalToolCalls = workflowMessages.reduce(
|
|
@@ -1848,16 +1948,28 @@ ${output.slice(0, 1e4)}
|
|
|
1848
1948
|
};
|
|
1849
1949
|
}
|
|
1850
1950
|
if (nextStepId === TERMINAL_BLOCKED) {
|
|
1851
|
-
|
|
1951
|
+
sessionManager.setPhase(sessionId, "blocked");
|
|
1852
1952
|
const blockedCriteria = refreshedSession.criteria.filter((c) => c.status.type === "failed" && c.attempts.filter((a) => a.status === "failed").length >= RUNNER_CONFIG.maxVerifyRetries).map((c) => c.id);
|
|
1853
1953
|
const reason = blockedCriteria.length > 0 ? `Retry limit reached for: ${blockedCriteria.join(", ")}` : "No matching transition";
|
|
1854
1954
|
const blockedMsgId = crypto.randomUUID();
|
|
1855
1955
|
eventStore.append(sessionId, createMessageStartEvent(blockedMsgId, "user", `Runner blocked: ${reason}`, {
|
|
1856
1956
|
...currentWindowMessageOptions ?? {},
|
|
1857
1957
|
isSystemGenerated: true,
|
|
1858
|
-
messageKind: "correction"
|
|
1958
|
+
messageKind: "correction",
|
|
1959
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
1859
1960
|
}));
|
|
1860
1961
|
eventStore.append(sessionId, { type: "message.done", data: { messageId: blockedMsgId } });
|
|
1962
|
+
if (onMessage) {
|
|
1963
|
+
onMessage(createChatMessageMessage({
|
|
1964
|
+
id: blockedMsgId,
|
|
1965
|
+
role: "user",
|
|
1966
|
+
content: `Runner blocked: ${reason}`,
|
|
1967
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1968
|
+
isSystemGenerated: true,
|
|
1969
|
+
messageKind: "correction",
|
|
1970
|
+
metadata: { type: "workflow", name: "Workflow", color: "#f59e0b" }
|
|
1971
|
+
}));
|
|
1972
|
+
}
|
|
1861
1973
|
logger.warn("Workflow executor blocked", { sessionId, iterations, reason });
|
|
1862
1974
|
const blockedAction = { type: "BLOCKED", reason, blockedCriteria };
|
|
1863
1975
|
return {
|
|
@@ -1890,302 +2002,50 @@ async function runOrchestrator(options) {
|
|
|
1890
2002
|
return executeWorkflow(workflow, options);
|
|
1891
2003
|
}
|
|
1892
2004
|
|
|
1893
|
-
// src/server/db/sessions.ts
|
|
1894
|
-
function createSession(projectId, workdir, title, providerId, providerModel) {
|
|
1895
|
-
const db = getDatabase();
|
|
1896
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1897
|
-
const id = crypto.randomUUID();
|
|
1898
|
-
db.prepare(`
|
|
1899
|
-
INSERT INTO sessions (id, project_id, workdir, phase, mode, workflow_phase, is_running, created_at, updated_at, title, provider_id, provider_model)
|
|
1900
|
-
VALUES (?, ?, ?, 'idle', 'planner', 'plan', 0, ?, ?, ?, ?, ?)
|
|
1901
|
-
`).run(id, projectId, workdir, now, now, title ?? null, providerId ?? null, providerModel ?? null);
|
|
1902
|
-
return {
|
|
1903
|
-
id,
|
|
1904
|
-
projectId,
|
|
1905
|
-
workdir,
|
|
1906
|
-
mode: "planner",
|
|
1907
|
-
phase: "plan",
|
|
1908
|
-
isRunning: false,
|
|
1909
|
-
summary: null,
|
|
1910
|
-
providerId: providerId ?? null,
|
|
1911
|
-
providerModel: providerModel ?? null,
|
|
1912
|
-
createdAt: now,
|
|
1913
|
-
updatedAt: now,
|
|
1914
|
-
messages: [],
|
|
1915
|
-
criteria: [],
|
|
1916
|
-
contextWindows: [],
|
|
1917
|
-
executionState: null,
|
|
1918
|
-
metadata: {
|
|
1919
|
-
...title ? { title } : {},
|
|
1920
|
-
totalTokensUsed: 0,
|
|
1921
|
-
totalToolCalls: 0,
|
|
1922
|
-
iterationCount: 0
|
|
1923
|
-
}
|
|
1924
|
-
};
|
|
1925
|
-
}
|
|
1926
|
-
function getSession(id) {
|
|
1927
|
-
const db = getDatabase();
|
|
1928
|
-
const row = db.prepare(`
|
|
1929
|
-
SELECT * FROM sessions WHERE id = ?
|
|
1930
|
-
`).get(id);
|
|
1931
|
-
if (!row) {
|
|
1932
|
-
return null;
|
|
1933
|
-
}
|
|
1934
|
-
return {
|
|
1935
|
-
id: row.id,
|
|
1936
|
-
projectId: row.project_id,
|
|
1937
|
-
workdir: row.workdir,
|
|
1938
|
-
mode: row.mode ?? "planner",
|
|
1939
|
-
phase: row.workflow_phase ?? "plan",
|
|
1940
|
-
isRunning: Boolean(row.is_running),
|
|
1941
|
-
summary: row.summary ?? null,
|
|
1942
|
-
providerId: row.provider_id ?? null,
|
|
1943
|
-
providerModel: row.provider_model ?? null,
|
|
1944
|
-
createdAt: row.created_at,
|
|
1945
|
-
updatedAt: row.updated_at,
|
|
1946
|
-
messages: [],
|
|
1947
|
-
criteria: [],
|
|
1948
|
-
contextWindows: [],
|
|
1949
|
-
executionState: null,
|
|
1950
|
-
metadata: {
|
|
1951
|
-
...row.title ? { title: row.title } : {},
|
|
1952
|
-
totalTokensUsed: row.total_tokens_used,
|
|
1953
|
-
totalToolCalls: row.total_tool_calls,
|
|
1954
|
-
iterationCount: row.iteration_count
|
|
1955
|
-
}
|
|
1956
|
-
};
|
|
1957
|
-
}
|
|
1958
|
-
function updateSessionProvider(id, providerId, providerModel) {
|
|
1959
|
-
const db = getDatabase();
|
|
1960
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1961
|
-
db.prepare(`
|
|
1962
|
-
UPDATE sessions SET provider_id = ?, provider_model = ?, updated_at = ? WHERE id = ?
|
|
1963
|
-
`).run(providerId, providerModel, now, id);
|
|
1964
|
-
}
|
|
1965
|
-
function updateSessionSummary(id, summary) {
|
|
1966
|
-
const db = getDatabase();
|
|
1967
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1968
|
-
db.prepare(`
|
|
1969
|
-
UPDATE sessions SET summary = ?, updated_at = ? WHERE id = ?
|
|
1970
|
-
`).run(summary, now, id);
|
|
1971
|
-
}
|
|
1972
|
-
function updateSessionMetadata(id, metadata) {
|
|
1973
|
-
const db = getDatabase();
|
|
1974
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1975
|
-
const updates = ["updated_at = ?"];
|
|
1976
|
-
const values = [now];
|
|
1977
|
-
if (metadata.title !== void 0) {
|
|
1978
|
-
updates.push("title = ?");
|
|
1979
|
-
values.push(metadata.title ?? "");
|
|
1980
|
-
}
|
|
1981
|
-
if (metadata.totalTokensUsed !== void 0) {
|
|
1982
|
-
updates.push("total_tokens_used = ?");
|
|
1983
|
-
values.push(metadata.totalTokensUsed);
|
|
1984
|
-
}
|
|
1985
|
-
if (metadata.totalToolCalls !== void 0) {
|
|
1986
|
-
updates.push("total_tool_calls = ?");
|
|
1987
|
-
values.push(metadata.totalToolCalls);
|
|
1988
|
-
}
|
|
1989
|
-
if (metadata.iterationCount !== void 0) {
|
|
1990
|
-
updates.push("iteration_count = ?");
|
|
1991
|
-
values.push(metadata.iterationCount);
|
|
1992
|
-
}
|
|
1993
|
-
values.push(id);
|
|
1994
|
-
db.prepare(`
|
|
1995
|
-
UPDATE sessions SET ${updates.join(", ")} WHERE id = ?
|
|
1996
|
-
`).run(...values);
|
|
1997
|
-
}
|
|
1998
|
-
function listSessions() {
|
|
1999
|
-
const db = getDatabase();
|
|
2000
|
-
const rows = db.prepare(`
|
|
2001
|
-
SELECT
|
|
2002
|
-
s.id,
|
|
2003
|
-
s.project_id,
|
|
2004
|
-
s.workdir,
|
|
2005
|
-
s.mode,
|
|
2006
|
-
s.workflow_phase,
|
|
2007
|
-
s.is_running,
|
|
2008
|
-
s.created_at,
|
|
2009
|
-
s.updated_at,
|
|
2010
|
-
s.title,
|
|
2011
|
-
s.provider_id,
|
|
2012
|
-
s.provider_model,
|
|
2013
|
-
COALESCE(
|
|
2014
|
-
(SELECT COUNT(*) FROM events e
|
|
2015
|
-
WHERE e.session_id = s.id
|
|
2016
|
-
AND e.event_type = 'message.start'
|
|
2017
|
-
AND json_extract(e.payload, '$.role') IN ('user', 'assistant'))
|
|
2018
|
-
+
|
|
2019
|
-
COALESCE(
|
|
2020
|
-
(SELECT SUM(json_array_length(json_extract(e.payload, '$.messages'))) FROM events e
|
|
2021
|
-
WHERE e.session_id = s.id
|
|
2022
|
-
AND e.event_type = 'turn.snapshot'
|
|
2023
|
-
AND json_extract(e.payload, '$.messages') IS NOT NULL),
|
|
2024
|
-
0
|
|
2025
|
-
),
|
|
2026
|
-
0
|
|
2027
|
-
) as message_count
|
|
2028
|
-
FROM sessions s
|
|
2029
|
-
ORDER BY s.updated_at DESC
|
|
2030
|
-
`).all();
|
|
2031
|
-
return rows.map((row) => ({
|
|
2032
|
-
id: row.id,
|
|
2033
|
-
projectId: row.project_id,
|
|
2034
|
-
...row.title ? { title: row.title } : {},
|
|
2035
|
-
workdir: row.workdir,
|
|
2036
|
-
mode: row.mode ?? "planner",
|
|
2037
|
-
phase: row.workflow_phase ?? "plan",
|
|
2038
|
-
isRunning: Boolean(row.is_running),
|
|
2039
|
-
providerId: row.provider_id ?? null,
|
|
2040
|
-
providerModel: row.provider_model ?? null,
|
|
2041
|
-
createdAt: row.created_at,
|
|
2042
|
-
updatedAt: row.updated_at,
|
|
2043
|
-
criteriaCount: 0,
|
|
2044
|
-
// Derived from events
|
|
2045
|
-
criteriaCompleted: 0,
|
|
2046
|
-
// Derived from events
|
|
2047
|
-
messageCount: row.message_count
|
|
2048
|
-
}));
|
|
2049
|
-
}
|
|
2050
|
-
function listSessionsByProject(projectId) {
|
|
2051
|
-
const db = getDatabase();
|
|
2052
|
-
const rows = db.prepare(`
|
|
2053
|
-
SELECT
|
|
2054
|
-
s.id,
|
|
2055
|
-
s.project_id,
|
|
2056
|
-
s.workdir,
|
|
2057
|
-
s.mode,
|
|
2058
|
-
s.workflow_phase,
|
|
2059
|
-
s.is_running,
|
|
2060
|
-
s.created_at,
|
|
2061
|
-
s.updated_at,
|
|
2062
|
-
s.title,
|
|
2063
|
-
s.provider_id,
|
|
2064
|
-
s.provider_model,
|
|
2065
|
-
COALESCE(
|
|
2066
|
-
(SELECT COUNT(*) FROM events e
|
|
2067
|
-
WHERE e.session_id = s.id
|
|
2068
|
-
AND e.event_type = 'message.start'
|
|
2069
|
-
AND json_extract(e.payload, '$.role') IN ('user', 'assistant'))
|
|
2070
|
-
+
|
|
2071
|
-
COALESCE(
|
|
2072
|
-
(SELECT SUM(json_array_length(json_extract(e.payload, '$.messages'))) FROM events e
|
|
2073
|
-
WHERE e.session_id = s.id
|
|
2074
|
-
AND e.event_type = 'turn.snapshot'
|
|
2075
|
-
AND json_extract(e.payload, '$.messages') IS NOT NULL),
|
|
2076
|
-
0
|
|
2077
|
-
),
|
|
2078
|
-
0
|
|
2079
|
-
) as message_count
|
|
2080
|
-
FROM sessions s
|
|
2081
|
-
WHERE s.project_id = ?
|
|
2082
|
-
ORDER BY s.updated_at DESC
|
|
2083
|
-
`).all(projectId);
|
|
2084
|
-
return rows.map((row) => ({
|
|
2085
|
-
id: row.id,
|
|
2086
|
-
projectId: row.project_id,
|
|
2087
|
-
...row.title ? { title: row.title } : {},
|
|
2088
|
-
workdir: row.workdir,
|
|
2089
|
-
mode: row.mode ?? "planner",
|
|
2090
|
-
phase: row.workflow_phase ?? "plan",
|
|
2091
|
-
isRunning: Boolean(row.is_running),
|
|
2092
|
-
providerId: row.provider_id ?? null,
|
|
2093
|
-
providerModel: row.provider_model ?? null,
|
|
2094
|
-
createdAt: row.created_at,
|
|
2095
|
-
updatedAt: row.updated_at,
|
|
2096
|
-
criteriaCount: 0,
|
|
2097
|
-
// Derived from events
|
|
2098
|
-
criteriaCompleted: 0,
|
|
2099
|
-
// Derived from events
|
|
2100
|
-
messageCount: row.message_count
|
|
2101
|
-
}));
|
|
2102
|
-
}
|
|
2103
|
-
function deleteSession(id) {
|
|
2104
|
-
const db = getDatabase();
|
|
2105
|
-
db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
// src/server/session/name-generator.ts
|
|
2109
|
-
var SESSION_NAME_PROMPT = `Generate a concise, descriptive session name (max 50 characters) based on the user's message.
|
|
2110
|
-
Return ONLY the name, nothing else.
|
|
2111
|
-
|
|
2112
|
-
Example inputs and outputs:
|
|
2113
|
-
- "How do I set up React?" \u2192 "React setup"
|
|
2114
|
-
- "Fix the authentication bug" \u2192 "Fix authentication bug"
|
|
2115
|
-
- "Add unit tests for the API" \u2192 "Add API unit tests"
|
|
2116
|
-
|
|
2117
|
-
User message: {message}`;
|
|
2118
|
-
async function generateSessionName(options) {
|
|
2119
|
-
const { userMessage, llmClient, signal } = options;
|
|
2120
|
-
try {
|
|
2121
|
-
const prompt = SESSION_NAME_PROMPT.replace("{message}", userMessage);
|
|
2122
|
-
const messages = [
|
|
2123
|
-
{
|
|
2124
|
-
role: "user",
|
|
2125
|
-
content: prompt
|
|
2126
|
-
}
|
|
2127
|
-
];
|
|
2128
|
-
const timeoutSignal = AbortSignal.timeout(6e4);
|
|
2129
|
-
const composedSignal = signal ? AbortSignal.any([timeoutSignal, signal]) : timeoutSignal;
|
|
2130
|
-
const response = await llmClient.complete({
|
|
2131
|
-
messages,
|
|
2132
|
-
tools: [],
|
|
2133
|
-
signal: composedSignal,
|
|
2134
|
-
disableThinking: true
|
|
2135
|
-
});
|
|
2136
|
-
let name = response.content.trim();
|
|
2137
|
-
if (name.length > 50) {
|
|
2138
|
-
name = name.substring(0, 47) + "...";
|
|
2139
|
-
}
|
|
2140
|
-
if (!name || name.length < 3) {
|
|
2141
|
-
return {
|
|
2142
|
-
success: false,
|
|
2143
|
-
error: "Generated name is too short or empty"
|
|
2144
|
-
};
|
|
2145
|
-
}
|
|
2146
|
-
return {
|
|
2147
|
-
success: true,
|
|
2148
|
-
name
|
|
2149
|
-
};
|
|
2150
|
-
} catch (error) {
|
|
2151
|
-
return {
|
|
2152
|
-
success: false,
|
|
2153
|
-
error: error instanceof Error ? error.message : "Unknown error generating session name"
|
|
2154
|
-
};
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
function needsNameGeneration(sessionTitle, messageCount) {
|
|
2158
|
-
if (messageCount > 1) {
|
|
2159
|
-
return false;
|
|
2160
|
-
}
|
|
2161
|
-
if (!sessionTitle || sessionTitle.trim() === "") {
|
|
2162
|
-
return true;
|
|
2163
|
-
}
|
|
2164
|
-
if (/^Session \d+$/.test(sessionTitle)) {
|
|
2165
|
-
return true;
|
|
2166
|
-
}
|
|
2167
|
-
return false;
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
2005
|
// src/server/session/summary-generator.ts
|
|
2171
|
-
var
|
|
2172
|
-
|
|
2173
|
-
## Conversation History
|
|
2174
|
-
{messages}
|
|
2175
|
-
|
|
2176
|
-
## Summary
|
|
2177
|
-
`;
|
|
2006
|
+
var SUMMARY_INSTRUCTION = "Write a 2-3 sentence summary of what the user wants to accomplish. Focus on WHAT and WHY, not HOW. Output only the summary, no preamble.";
|
|
2178
2007
|
async function generateSessionSummary(options) {
|
|
2179
|
-
const { messages, llmClient } = options;
|
|
2008
|
+
const { messages, llmClient, workdir, customInstructions, skills } = options;
|
|
2180
2009
|
try {
|
|
2181
2010
|
const messagesText = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
2182
|
-
const
|
|
2183
|
-
|
|
2011
|
+
const userPrompt = `${SUMMARY_INSTRUCTION}
|
|
2012
|
+
|
|
2013
|
+
## Conversation History
|
|
2014
|
+
${messagesText}
|
|
2015
|
+
|
|
2016
|
+
## Summary`;
|
|
2017
|
+
const contextMessages = [
|
|
2184
2018
|
{
|
|
2185
2019
|
role: "user",
|
|
2186
|
-
content:
|
|
2020
|
+
content: userPrompt,
|
|
2021
|
+
source: "history"
|
|
2187
2022
|
}
|
|
2188
2023
|
];
|
|
2024
|
+
const mockAgentDef = {
|
|
2025
|
+
metadata: {
|
|
2026
|
+
id: "summary",
|
|
2027
|
+
name: "Summary",
|
|
2028
|
+
description: "Session summary generator",
|
|
2029
|
+
subagent: false,
|
|
2030
|
+
allowedTools: []
|
|
2031
|
+
},
|
|
2032
|
+
prompt: SUMMARY_INSTRUCTION
|
|
2033
|
+
};
|
|
2034
|
+
const assembledRequest = assembleAgentRequest({
|
|
2035
|
+
agentDef: mockAgentDef,
|
|
2036
|
+
workdir,
|
|
2037
|
+
messages: contextMessages,
|
|
2038
|
+
injectedFiles: [],
|
|
2039
|
+
...customInstructions ? { customInstructions } : {},
|
|
2040
|
+
...skills ? { skills } : {},
|
|
2041
|
+
promptTools: [],
|
|
2042
|
+
toolChoice: "none",
|
|
2043
|
+
disableThinking: true
|
|
2044
|
+
});
|
|
2045
|
+
const llmMessages = assembledRequest.messages.map((msg) => ({
|
|
2046
|
+
role: msg.role,
|
|
2047
|
+
content: msg.content
|
|
2048
|
+
}));
|
|
2189
2049
|
const response = await llmClient.complete({
|
|
2190
2050
|
messages: llmMessages,
|
|
2191
2051
|
tools: [],
|
|
@@ -2218,7 +2078,7 @@ function needsSummaryGeneration(sessionSummary) {
|
|
|
2218
2078
|
}
|
|
2219
2079
|
|
|
2220
2080
|
// src/server/ws/server.ts
|
|
2221
|
-
function
|
|
2081
|
+
function getCurrentWindowMessageOptions2(sessionId) {
|
|
2222
2082
|
const contextWindowId = getCurrentContextWindowId(sessionId);
|
|
2223
2083
|
return contextWindowId ? { contextWindowId } : void 0;
|
|
2224
2084
|
}
|
|
@@ -2236,7 +2096,7 @@ function getSessionMessageCount(sessionId) {
|
|
|
2236
2096
|
}
|
|
2237
2097
|
return count;
|
|
2238
2098
|
}
|
|
2239
|
-
function
|
|
2099
|
+
function resolveStatsIdentity(llmClient, getActiveProvider) {
|
|
2240
2100
|
const provider = getActiveProvider?.();
|
|
2241
2101
|
const model = llmClient.getModel();
|
|
2242
2102
|
const backend = provider?.backend ?? (llmClient.getBackend() === "unknown" ? "unknown" : llmClient.getBackend());
|
|
@@ -2248,12 +2108,12 @@ function resolveStatsIdentity2(llmClient, getActiveProvider) {
|
|
|
2248
2108
|
};
|
|
2249
2109
|
}
|
|
2250
2110
|
var activeAgents = /* @__PURE__ */ new Map();
|
|
2251
|
-
function createWebSocketServer(httpServer, config, getLLMClient, getActiveProvider,
|
|
2111
|
+
function createWebSocketServer(httpServer, config, getLLMClient, getActiveProvider, sessionManager, providerManager) {
|
|
2252
2112
|
const wss = new WebSocketServer({ server: httpServer, path: "/ws" });
|
|
2253
2113
|
const clients = /* @__PURE__ */ new Map();
|
|
2254
2114
|
const sessionLLMClients = /* @__PURE__ */ new Map();
|
|
2255
2115
|
function getSessionLLMClient(sessionId) {
|
|
2256
|
-
const session =
|
|
2116
|
+
const session = sessionManager.getSession(sessionId);
|
|
2257
2117
|
if (!session?.providerId || !session?.providerModel || !providerManager) {
|
|
2258
2118
|
return getLLMClient();
|
|
2259
2119
|
}
|
|
@@ -2268,7 +2128,7 @@ function createWebSocketServer(httpServer, config, getLLMClient, getActiveProvid
|
|
|
2268
2128
|
sessionId,
|
|
2269
2129
|
providerId: session.providerId
|
|
2270
2130
|
});
|
|
2271
|
-
|
|
2131
|
+
sessionManager.setSessionProvider(sessionId, null, null);
|
|
2272
2132
|
sessionLLMClients.delete(sessionId);
|
|
2273
2133
|
return getLLMClient();
|
|
2274
2134
|
}
|
|
@@ -2286,9 +2146,9 @@ function createWebSocketServer(httpServer, config, getLLMClient, getActiveProvid
|
|
|
2286
2146
|
return client;
|
|
2287
2147
|
}
|
|
2288
2148
|
function getSessionStatsIdentity(sessionId) {
|
|
2289
|
-
const session =
|
|
2149
|
+
const session = sessionManager.getSession(sessionId);
|
|
2290
2150
|
if (!session?.providerId || !providerManager) {
|
|
2291
|
-
return
|
|
2151
|
+
return resolveStatsIdentity(getLLMClient(), getActiveProvider);
|
|
2292
2152
|
}
|
|
2293
2153
|
const provider = providerManager.getProviders().find((p) => p.id === session.providerId);
|
|
2294
2154
|
const client = getSessionLLMClient(sessionId);
|
|
@@ -2307,23 +2167,128 @@ function createWebSocketServer(httpServer, config, getLLMClient, getActiveProvid
|
|
|
2307
2167
|
};
|
|
2308
2168
|
const broadcastForSession = (sessionId, msg) => {
|
|
2309
2169
|
for (const [clientWs, client] of clients) {
|
|
2310
|
-
if (isSubscribedToSession(client, sessionId) && clientWs.readyState ===
|
|
2170
|
+
if (isSubscribedToSession(client, sessionId) && clientWs.readyState === WebSocket2.OPEN) {
|
|
2311
2171
|
clientWs.send(serializeServerMessage({ ...msg, sessionId }));
|
|
2312
2172
|
}
|
|
2313
2173
|
}
|
|
2314
2174
|
};
|
|
2315
|
-
|
|
2175
|
+
const llmForSession = (sessionId) => getSessionLLMClient?.(sessionId) ?? getLLMClient();
|
|
2176
|
+
const statsForSession = (sessionId) => getSessionStatsIdentity?.(sessionId) ?? resolveStatsIdentity(getLLMClient(), getActiveProvider);
|
|
2177
|
+
function startTurnWithCompletionChain(sessionId, controller) {
|
|
2178
|
+
runChatTurn({
|
|
2179
|
+
sessionManager,
|
|
2180
|
+
sessionId,
|
|
2181
|
+
llmClient: llmForSession(sessionId),
|
|
2182
|
+
statsIdentity: statsForSession(sessionId),
|
|
2183
|
+
signal: controller.signal,
|
|
2184
|
+
onMessage: (msg) => broadcastForSession(sessionId, msg)
|
|
2185
|
+
}).catch((error) => {
|
|
2186
|
+
if (error instanceof Error && error.message === "Aborted") {
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
logger.error("Chat turn error", { error });
|
|
2190
|
+
}).finally(() => {
|
|
2191
|
+
if (activeAgents.get(sessionId) !== controller) {
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
activeAgents.delete(sessionId);
|
|
2195
|
+
const asapMsgs = sessionManager.drainAsapMessages(sessionId);
|
|
2196
|
+
const nextAsap = asapMsgs[0];
|
|
2197
|
+
if (nextAsap) {
|
|
2198
|
+
for (const remaining of asapMsgs.slice(1)) {
|
|
2199
|
+
sessionManager.queueMessage(sessionId, "asap", remaining.content, remaining.attachments, remaining.messageKind);
|
|
2200
|
+
}
|
|
2201
|
+
broadcastForSession(sessionId, createQueueStateMessage(sessionManager.getQueueState(sessionId)));
|
|
2202
|
+
const userMessagePayload = {
|
|
2203
|
+
role: "user",
|
|
2204
|
+
content: nextAsap.content,
|
|
2205
|
+
...nextAsap.attachments ? { attachments: nextAsap.attachments } : {}
|
|
2206
|
+
};
|
|
2207
|
+
const userMessage = sessionManager.addMessage(sessionId, userMessagePayload);
|
|
2208
|
+
broadcastForSession(sessionId, createChatMessageMessage(userMessage));
|
|
2209
|
+
const newController = new AbortController();
|
|
2210
|
+
activeAgents.set(sessionId, newController);
|
|
2211
|
+
startTurnWithCompletionChain(sessionId, newController);
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
const completionMsgs = sessionManager.drainCompletionMessages(sessionId);
|
|
2215
|
+
const next = completionMsgs[0];
|
|
2216
|
+
if (next) {
|
|
2217
|
+
for (const remaining of completionMsgs.slice(1)) {
|
|
2218
|
+
sessionManager.queueMessage(sessionId, "completion", remaining.content, remaining.attachments);
|
|
2219
|
+
}
|
|
2220
|
+
broadcastForSession(sessionId, createQueueStateMessage(sessionManager.getQueueState(sessionId)));
|
|
2221
|
+
const userMessage = sessionManager.addMessage(sessionId, {
|
|
2222
|
+
role: "user",
|
|
2223
|
+
content: next.content,
|
|
2224
|
+
...next.attachments ? { attachments: next.attachments } : {}
|
|
2225
|
+
});
|
|
2226
|
+
broadcastForSession(sessionId, createChatMessageMessage(userMessage));
|
|
2227
|
+
const newController = new AbortController();
|
|
2228
|
+
activeAgents.set(sessionId, newController);
|
|
2229
|
+
startTurnWithCompletionChain(sessionId, newController);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
sessionManager.clearMessageQueue(sessionId);
|
|
2233
|
+
sessionManager.setRunning(sessionId, false);
|
|
2234
|
+
const contextState = sessionManager.getContextState(sessionId);
|
|
2235
|
+
broadcastForSession(sessionId, createContextStateMessage(contextState));
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
function triggerQueueProcessing(sessionId) {
|
|
2239
|
+
const currentSession = sessionManager.getSession(sessionId);
|
|
2240
|
+
if (!currentSession || currentSession.isRunning) {
|
|
2241
|
+
return false;
|
|
2242
|
+
}
|
|
2243
|
+
const queue = sessionManager.getQueueState(sessionId);
|
|
2244
|
+
if (queue.length === 0) {
|
|
2245
|
+
return false;
|
|
2246
|
+
}
|
|
2247
|
+
const controller = new AbortController();
|
|
2248
|
+
activeAgents.set(sessionId, controller);
|
|
2249
|
+
sessionManager.setRunning(sessionId, true);
|
|
2250
|
+
const eventStore = getEventStore();
|
|
2251
|
+
eventStore.append(sessionId, { type: "running.changed", data: { isRunning: true } });
|
|
2252
|
+
broadcastForSession(sessionId, createSessionRunningMessage(true));
|
|
2253
|
+
const nextAsap = queue.find((m) => m.mode === "asap") ?? queue[0];
|
|
2254
|
+
if (nextAsap) {
|
|
2255
|
+
sessionManager.cancelQueuedMessage(sessionId, nextAsap.queueId);
|
|
2256
|
+
const userMessage = sessionManager.addMessage(sessionId, {
|
|
2257
|
+
role: "user",
|
|
2258
|
+
content: nextAsap.content,
|
|
2259
|
+
...nextAsap.attachments ? { attachments: nextAsap.attachments } : {},
|
|
2260
|
+
...nextAsap.messageKind ? { messageKind: nextAsap.messageKind } : {}
|
|
2261
|
+
});
|
|
2262
|
+
broadcastForSession(sessionId, createChatMessageMessage(userMessage));
|
|
2263
|
+
}
|
|
2264
|
+
startTurnWithCompletionChain(sessionId, controller);
|
|
2265
|
+
return true;
|
|
2266
|
+
}
|
|
2267
|
+
sessionManager.subscribe((event) => {
|
|
2316
2268
|
const sessionId = "sessionId" in event ? event.sessionId : "session" in event ? event.session.id : null;
|
|
2317
2269
|
if (!sessionId) return;
|
|
2318
2270
|
for (const [ws, client] of clients) {
|
|
2319
|
-
if (
|
|
2271
|
+
if (ws.readyState !== WebSocket2.OPEN) continue;
|
|
2272
|
+
if (event.type === "session_created") {
|
|
2273
|
+
const session = sessionManager.getSession(sessionId);
|
|
2274
|
+
if (session) {
|
|
2275
|
+
const eventStore = getEventStore();
|
|
2276
|
+
const events = eventStore.getEvents(sessionId);
|
|
2277
|
+
const messages = events.length > 0 ? buildMessagesFromStoredEvents(events) : [];
|
|
2278
|
+
const pendingConfirmations = foldPendingConfirmations(events);
|
|
2279
|
+
ws.send(serializeServerMessage({ ...createSessionStateMessage(session, messages, pendingConfirmations), sessionId }));
|
|
2280
|
+
}
|
|
2281
|
+
continue;
|
|
2282
|
+
}
|
|
2283
|
+
if (isSubscribedToSession(client, sessionId)) {
|
|
2320
2284
|
if (event.type === "session_updated") {
|
|
2321
|
-
const session =
|
|
2285
|
+
const session = sessionManager.getSession(sessionId);
|
|
2322
2286
|
if (session) {
|
|
2323
2287
|
const eventStore = getEventStore();
|
|
2324
2288
|
const events = eventStore.getEvents(sessionId);
|
|
2325
2289
|
const messages = events.length > 0 ? buildMessagesFromStoredEvents(events) : [];
|
|
2326
|
-
|
|
2290
|
+
const pendingConfirmations = foldPendingConfirmations(events);
|
|
2291
|
+
ws.send(serializeServerMessage({ ...createSessionStateMessage(session, messages, pendingConfirmations), sessionId }));
|
|
2327
2292
|
}
|
|
2328
2293
|
}
|
|
2329
2294
|
if (event.type === "running_changed") {
|
|
@@ -2335,7 +2300,7 @@ function createWebSocketServer(httpServer, config, getLLMClient, getActiveProvid
|
|
|
2335
2300
|
const broadcastAll = (msg) => {
|
|
2336
2301
|
const serialized = serializeServerMessage(msg);
|
|
2337
2302
|
for (const [clientWs] of clients) {
|
|
2338
|
-
if (clientWs.readyState ===
|
|
2303
|
+
if (clientWs.readyState === WebSocket2.OPEN) {
|
|
2339
2304
|
clientWs.send(serialized);
|
|
2340
2305
|
}
|
|
2341
2306
|
}
|
|
@@ -2363,9 +2328,13 @@ function createWebSocketServer(httpServer, config, getLLMClient, getActiveProvid
|
|
|
2363
2328
|
ws.send(serializeServerMessage(createErrorMessage("INVALID_MESSAGE", "Invalid message format")));
|
|
2364
2329
|
return;
|
|
2365
2330
|
}
|
|
2331
|
+
if (message.type.startsWith("terminal.")) {
|
|
2332
|
+
handleTerminalMessage(ws, message);
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2366
2335
|
const client = clients.get(ws);
|
|
2367
2336
|
try {
|
|
2368
|
-
await handleClientMessage(ws, client, message,
|
|
2337
|
+
await handleClientMessage(ws, client, message, getLLMClient, getActiveProvider, sessionManager, broadcastForSession, providerManager, getSessionLLMClient, getSessionStatsIdentity, llmForSession, statsForSession, startTurnWithCompletionChain);
|
|
2369
2338
|
} catch (error) {
|
|
2370
2339
|
logger.error("Error handling client message", { error, type: message.type });
|
|
2371
2340
|
ws.send(serializeServerMessage(
|
|
@@ -2388,17 +2357,31 @@ function createWebSocketServer(httpServer, config, getLLMClient, getActiveProvid
|
|
|
2388
2357
|
unsubscribe();
|
|
2389
2358
|
}
|
|
2390
2359
|
}
|
|
2360
|
+
unsubscribeAllFromTerminal(ws);
|
|
2391
2361
|
clients.delete(ws);
|
|
2392
2362
|
});
|
|
2393
2363
|
ws.on("error", (error) => {
|
|
2394
2364
|
logger.error("WebSocket error", { error });
|
|
2395
2365
|
});
|
|
2396
2366
|
});
|
|
2397
|
-
return
|
|
2367
|
+
return {
|
|
2368
|
+
wss,
|
|
2369
|
+
abortSession: (sessionId) => {
|
|
2370
|
+
const controller = activeAgents.get(sessionId);
|
|
2371
|
+
if (controller) {
|
|
2372
|
+
activeAgents.delete(sessionId);
|
|
2373
|
+
controller.abort();
|
|
2374
|
+
return true;
|
|
2375
|
+
}
|
|
2376
|
+
return false;
|
|
2377
|
+
},
|
|
2378
|
+
close: (cb) => wss.close(cb),
|
|
2379
|
+
broadcastForSession
|
|
2380
|
+
};
|
|
2398
2381
|
}
|
|
2399
|
-
async function handleClientMessage(ws, client, message,
|
|
2382
|
+
async function handleClientMessage(ws, client, message, getLLMClient, getActiveProvider, sessionManager, broadcastForSession, providerManager, getSessionLLMClient, getSessionStatsIdentity, llmForSession, statsForSession, startTurnWithCompletionChain) {
|
|
2400
2383
|
const send = (msg) => {
|
|
2401
|
-
if (ws.readyState ===
|
|
2384
|
+
if (ws.readyState === WebSocket2.OPEN) {
|
|
2402
2385
|
ws.send(serializeServerMessage(msg));
|
|
2403
2386
|
}
|
|
2404
2387
|
};
|
|
@@ -2414,57 +2397,13 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2414
2397
|
controller.abort();
|
|
2415
2398
|
cancelQuestionsForSession(sessionId, reason);
|
|
2416
2399
|
cancelPathConfirmationsForSession(sessionId, reason);
|
|
2417
|
-
|
|
2418
|
-
|
|
2400
|
+
sessionManager.clearMessageQueue(sessionId);
|
|
2401
|
+
sessionManager.setRunning(sessionId, false);
|
|
2419
2402
|
sendForSession(sessionId, createSessionRunningMessage(false));
|
|
2420
|
-
const contextState =
|
|
2403
|
+
const contextState = sessionManager.getContextState(sessionId);
|
|
2421
2404
|
sendForSession(sessionId, createContextStateMessage(contextState));
|
|
2422
2405
|
return true;
|
|
2423
2406
|
};
|
|
2424
|
-
const llmForSession = (sessionId) => getSessionLLMClient?.(sessionId) ?? getLLMClient();
|
|
2425
|
-
const statsForSession = (sessionId) => getSessionStatsIdentity?.(sessionId) ?? resolveStatsIdentity2(getLLMClient(), getActiveProvider);
|
|
2426
|
-
const startTurnWithCompletionChain = (sessionId, controller) => {
|
|
2427
|
-
runChatTurn({
|
|
2428
|
-
sessionManager: sessionManager2,
|
|
2429
|
-
sessionId,
|
|
2430
|
-
llmClient: llmForSession(sessionId),
|
|
2431
|
-
statsIdentity: statsForSession(sessionId),
|
|
2432
|
-
signal: controller.signal,
|
|
2433
|
-
onMessage: (msg) => sendForSession(sessionId, msg)
|
|
2434
|
-
}).catch((error) => {
|
|
2435
|
-
if (error instanceof Error && error.message === "Aborted") {
|
|
2436
|
-
return;
|
|
2437
|
-
}
|
|
2438
|
-
logger.error("Chat turn error", { error });
|
|
2439
|
-
}).finally(() => {
|
|
2440
|
-
if (activeAgents.get(sessionId) !== controller) {
|
|
2441
|
-
return;
|
|
2442
|
-
}
|
|
2443
|
-
activeAgents.delete(sessionId);
|
|
2444
|
-
const completionMsgs = sessionManager2.drainCompletionMessages(sessionId);
|
|
2445
|
-
const next = completionMsgs[0];
|
|
2446
|
-
if (next) {
|
|
2447
|
-
for (const remaining of completionMsgs.slice(1)) {
|
|
2448
|
-
sessionManager2.queueMessage(sessionId, "completion", remaining.content, remaining.attachments);
|
|
2449
|
-
}
|
|
2450
|
-
sendForSession(sessionId, createQueueStateMessage(sessionManager2.getQueueState(sessionId)));
|
|
2451
|
-
const userMessage = sessionManager2.addMessage(sessionId, {
|
|
2452
|
-
role: "user",
|
|
2453
|
-
content: next.content,
|
|
2454
|
-
...next.attachments ? { attachments: next.attachments } : {}
|
|
2455
|
-
});
|
|
2456
|
-
sendForSession(sessionId, createChatMessageMessage(userMessage));
|
|
2457
|
-
const newController = new AbortController();
|
|
2458
|
-
activeAgents.set(sessionId, newController);
|
|
2459
|
-
startTurnWithCompletionChain(sessionId, newController);
|
|
2460
|
-
return;
|
|
2461
|
-
}
|
|
2462
|
-
sessionManager2.clearMessageQueue(sessionId);
|
|
2463
|
-
sessionManager2.setRunning(sessionId, false);
|
|
2464
|
-
const contextState = sessionManager2.getContextState(sessionId);
|
|
2465
|
-
sendForSession(sessionId, createContextStateMessage(contextState));
|
|
2466
|
-
});
|
|
2467
|
-
};
|
|
2468
2407
|
const ensureEventStoreSubscription = (sessionId) => {
|
|
2469
2408
|
if (client.eventStoreSubscriptions.has(sessionId)) {
|
|
2470
2409
|
return;
|
|
@@ -2477,7 +2416,7 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2477
2416
|
try {
|
|
2478
2417
|
for await (const storedEvent of iterator) {
|
|
2479
2418
|
const serverMsg = storedEventToServerMessage(storedEvent);
|
|
2480
|
-
if (serverMsg && ws.readyState ===
|
|
2419
|
+
if (serverMsg && ws.readyState === WebSocket2.OPEN) {
|
|
2481
2420
|
ws.send(serializeServerMessage({ ...serverMsg, seq: storedEvent.seq, sessionId: sid }));
|
|
2482
2421
|
}
|
|
2483
2422
|
}
|
|
@@ -2489,138 +2428,36 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2489
2428
|
};
|
|
2490
2429
|
switch (message.type) {
|
|
2491
2430
|
// =========================================================================
|
|
2492
|
-
//
|
|
2493
|
-
//
|
|
2494
|
-
|
|
2495
|
-
if (!isProjectCreatePayload(message.payload)) {
|
|
2496
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid project.create payload", message.id));
|
|
2497
|
-
return;
|
|
2498
|
-
}
|
|
2499
|
-
const project = createProject(message.payload.name, message.payload.workdir);
|
|
2500
|
-
send(createProjectStateMessage(project, message.id));
|
|
2501
|
-
break;
|
|
2502
|
-
}
|
|
2503
|
-
case "project.create-with-dir": {
|
|
2504
|
-
logger.debug("WS project.create-with-dir received", { payload: message.payload });
|
|
2505
|
-
if (!isProjectCreateWithDirPayload(message.payload)) {
|
|
2506
|
-
logger.error("WS project.create-with-dir invalid payload");
|
|
2507
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid project.create-with-dir payload", message.id));
|
|
2508
|
-
return;
|
|
2509
|
-
}
|
|
2510
|
-
try {
|
|
2511
|
-
const workdir = config.workdir;
|
|
2512
|
-
logger.debug("WS creating project directory", { name: message.payload.name, workdir });
|
|
2513
|
-
const { createDirectoryWithGit } = await import("./project-creator-CWPKRIKI.js");
|
|
2514
|
-
const project = await createDirectoryWithGit(message.payload.name, workdir);
|
|
2515
|
-
logger.debug("WS project created", { id: project.id, name: project.name });
|
|
2516
|
-
send(createProjectStateMessage(project, message.id));
|
|
2517
|
-
logger.debug("WS sent project.state", { messageId: message.id });
|
|
2518
|
-
} catch (error) {
|
|
2519
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
2520
|
-
logger.error("WS project creation failed", { error: errorMessage });
|
|
2521
|
-
send(createErrorMessage("PROJECT_CREATION_FAILED", errorMessage, message.id));
|
|
2522
|
-
}
|
|
2523
|
-
break;
|
|
2524
|
-
}
|
|
2525
|
-
case "project.list": {
|
|
2526
|
-
logger.debug("WS project.list received", { messageId: message.id });
|
|
2527
|
-
const projects = listProjects();
|
|
2528
|
-
logger.debug("WS projects found", { count: projects.length, projects: projects.map((p) => ({ id: p.id, name: p.name })) });
|
|
2529
|
-
send(createProjectListMessage(projects, message.id));
|
|
2530
|
-
logger.debug("WS sent project.list", { messageId: message.id });
|
|
2531
|
-
break;
|
|
2532
|
-
}
|
|
2533
|
-
case "project.load": {
|
|
2534
|
-
if (!isProjectLoadPayload(message.payload)) {
|
|
2535
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid project.load payload", message.id));
|
|
2536
|
-
return;
|
|
2537
|
-
}
|
|
2538
|
-
const project = getProject(message.payload.projectId);
|
|
2539
|
-
if (!project) {
|
|
2540
|
-
send(createErrorMessage("NOT_FOUND", "Project not found", message.id));
|
|
2541
|
-
return;
|
|
2542
|
-
}
|
|
2543
|
-
send(createProjectStateMessage(project, message.id));
|
|
2544
|
-
break;
|
|
2545
|
-
}
|
|
2546
|
-
case "project.update": {
|
|
2547
|
-
if (!isProjectUpdatePayload(message.payload)) {
|
|
2548
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid project.update payload", message.id));
|
|
2549
|
-
return;
|
|
2550
|
-
}
|
|
2551
|
-
const updates = {};
|
|
2552
|
-
if (message.payload.name !== void 0) {
|
|
2553
|
-
updates.name = message.payload.name;
|
|
2554
|
-
}
|
|
2555
|
-
if (message.payload.customInstructions !== void 0) {
|
|
2556
|
-
updates.customInstructions = message.payload.customInstructions;
|
|
2557
|
-
}
|
|
2558
|
-
const updated = updateProject(message.payload.projectId, updates);
|
|
2559
|
-
if (!updated) {
|
|
2560
|
-
send(createErrorMessage("NOT_FOUND", "Project not found", message.id));
|
|
2561
|
-
return;
|
|
2562
|
-
}
|
|
2563
|
-
send(createProjectStateMessage(updated, message.id));
|
|
2564
|
-
break;
|
|
2565
|
-
}
|
|
2566
|
-
case "project.delete": {
|
|
2567
|
-
if (!isProjectDeletePayload(message.payload)) {
|
|
2568
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid project.delete payload", message.id));
|
|
2569
|
-
return;
|
|
2570
|
-
}
|
|
2571
|
-
deleteProject(message.payload.projectId);
|
|
2572
|
-
send({ type: "project.deleted", payload: { projectId: message.payload.projectId }, id: message.id });
|
|
2573
|
-
break;
|
|
2574
|
-
}
|
|
2575
|
-
// =========================================================================
|
|
2576
|
-
// Settings Management
|
|
2431
|
+
// DEPRECATED: All CRUD operations moved to REST API
|
|
2432
|
+
// If you see this error, update your code to use REST endpoints instead.
|
|
2433
|
+
// See docs/REST-API.md for details.
|
|
2577
2434
|
// =========================================================================
|
|
2578
|
-
case "
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
case "
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
send(createSettingsValueMessage(message.payload.key, message.payload.value, message.id));
|
|
2594
|
-
break;
|
|
2595
|
-
}
|
|
2435
|
+
case "project.create":
|
|
2436
|
+
case "project.create-with-dir":
|
|
2437
|
+
case "project.list":
|
|
2438
|
+
case "project.load":
|
|
2439
|
+
case "project.update":
|
|
2440
|
+
case "project.delete":
|
|
2441
|
+
case "settings.get":
|
|
2442
|
+
case "settings.set":
|
|
2443
|
+
case "session.create":
|
|
2444
|
+
case "session.list":
|
|
2445
|
+
case "session.delete":
|
|
2446
|
+
case "session.deleteAll":
|
|
2447
|
+
case "session.setProvider":
|
|
2448
|
+
send(createErrorMessage("DEPRECATED_MESSAGE_TYPE", `${message.type} removed. Use REST API instead. See docs/REST-API.md`, message.id));
|
|
2449
|
+
return;
|
|
2596
2450
|
// =========================================================================
|
|
2597
|
-
// Session
|
|
2451
|
+
// Session Load - Required for WS subscription mechanism
|
|
2452
|
+
// Note: This is kept ONLY to set activeSessionId for event routing.
|
|
2453
|
+
// For actual data loading, use REST API: GET /api/sessions/:id
|
|
2598
2454
|
// =========================================================================
|
|
2599
|
-
case "session.create": {
|
|
2600
|
-
if (!isSessionCreatePayload(message.payload)) {
|
|
2601
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid session.create payload", message.id));
|
|
2602
|
-
return;
|
|
2603
|
-
}
|
|
2604
|
-
const currentProvider = providerManager?.getActiveProvider();
|
|
2605
|
-
const currentModel = currentProvider ? getLLMClient().getModel() : null;
|
|
2606
|
-
const session = sessionManager2.createSession(
|
|
2607
|
-
message.payload.projectId,
|
|
2608
|
-
message.payload.title,
|
|
2609
|
-
currentProvider?.id ?? null,
|
|
2610
|
-
currentModel
|
|
2611
|
-
);
|
|
2612
|
-
client.activeSessionId = session.id;
|
|
2613
|
-
sendForSession(session.id, createSessionStateMessage(session, [], message.id));
|
|
2614
|
-
const contextState = sessionManager2.getContextState(session.id);
|
|
2615
|
-
sendForSession(session.id, createContextStateMessage(contextState));
|
|
2616
|
-
break;
|
|
2617
|
-
}
|
|
2618
2455
|
case "session.load": {
|
|
2619
2456
|
if (!isSessionLoadPayload(message.payload)) {
|
|
2620
2457
|
send(createErrorMessage("INVALID_PAYLOAD", "Invalid session.load payload", message.id));
|
|
2621
2458
|
return;
|
|
2622
2459
|
}
|
|
2623
|
-
const session =
|
|
2460
|
+
const session = sessionManager.getSession(message.payload.sessionId);
|
|
2624
2461
|
if (!session) {
|
|
2625
2462
|
send(createErrorMessage("NOT_FOUND", "Session not found", message.id));
|
|
2626
2463
|
return;
|
|
@@ -2630,77 +2467,15 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2630
2467
|
const eventStore = getEventStore();
|
|
2631
2468
|
const events = eventStore.getEvents(session.id);
|
|
2632
2469
|
const messages = buildMessagesFromStoredEvents(events);
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2470
|
+
const pendingConfirmations = foldPendingConfirmations(events);
|
|
2471
|
+
logger.debug("Loaded messages from EventStore", { sessionId: session.id, eventCount: events.length, messageCount: messages.length, pendingConfirmationsCount: pendingConfirmations.length });
|
|
2472
|
+
sendForSession(session.id, createSessionStateMessage(session, messages, pendingConfirmations, message.id));
|
|
2473
|
+
const contextState = sessionManager.getContextState(session.id);
|
|
2636
2474
|
sendForSession(session.id, createContextStateMessage(contextState));
|
|
2637
2475
|
break;
|
|
2638
2476
|
}
|
|
2639
|
-
case "session.list": {
|
|
2640
|
-
const sessions = sessionManager2.listSessions();
|
|
2641
|
-
const sessionsWithPrompts = sessions.map((session) => ({
|
|
2642
|
-
...session,
|
|
2643
|
-
recentUserPrompts: getRecentUserPromptsForSession(session.id, 10)
|
|
2644
|
-
}));
|
|
2645
|
-
send(createSessionListMessage(sessionsWithPrompts, message.id));
|
|
2646
|
-
break;
|
|
2647
|
-
}
|
|
2648
|
-
case "session.delete": {
|
|
2649
|
-
if (!isSessionLoadPayload(message.payload)) {
|
|
2650
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid session.delete payload", message.id));
|
|
2651
|
-
return;
|
|
2652
|
-
}
|
|
2653
|
-
sessionManager2.deleteSession(message.payload.sessionId);
|
|
2654
|
-
send({ type: "session.deleted", payload: { sessionId: message.payload.sessionId }, id: message.id });
|
|
2655
|
-
break;
|
|
2656
|
-
}
|
|
2657
|
-
case "session.deleteAll": {
|
|
2658
|
-
if (!message.payload || typeof message.payload !== "object" || !("projectId" in message.payload)) {
|
|
2659
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid session.deleteAll payload", message.id));
|
|
2660
|
-
return;
|
|
2661
|
-
}
|
|
2662
|
-
const payload = message.payload;
|
|
2663
|
-
const project = sessionManager2.getProject(payload.projectId);
|
|
2664
|
-
if (!project) {
|
|
2665
|
-
send(createErrorMessage("PROJECT_NOT_FOUND", "Project not found", message.id));
|
|
2666
|
-
return;
|
|
2667
|
-
}
|
|
2668
|
-
sessionManager2.deleteAllSessions(payload.projectId, project.workdir);
|
|
2669
|
-
send({ type: "session.deletedAll", payload: { projectId: payload.projectId }, id: message.id });
|
|
2670
|
-
break;
|
|
2671
|
-
}
|
|
2672
|
-
case "session.setProvider": {
|
|
2673
|
-
if (!client.activeSessionId) {
|
|
2674
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
2675
|
-
return;
|
|
2676
|
-
}
|
|
2677
|
-
if (!isSessionSetProviderPayload(message.payload)) {
|
|
2678
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid session.setProvider payload", message.id));
|
|
2679
|
-
return;
|
|
2680
|
-
}
|
|
2681
|
-
if (!providerManager) {
|
|
2682
|
-
send(createErrorMessage("NO_PROVIDERS", "Provider management not available", message.id));
|
|
2683
|
-
return;
|
|
2684
|
-
}
|
|
2685
|
-
const { providerId, model: requestedModel } = message.payload;
|
|
2686
|
-
const provider = providerManager.getProviders().find((p) => p.id === providerId);
|
|
2687
|
-
if (!provider) {
|
|
2688
|
-
send(createErrorMessage("PROVIDER_NOT_FOUND", "Provider not found", message.id));
|
|
2689
|
-
return;
|
|
2690
|
-
}
|
|
2691
|
-
const resolvedModel = requestedModel ?? "auto";
|
|
2692
|
-
const sessionId = client.activeSessionId;
|
|
2693
|
-
sessionManager2.setSessionProvider(sessionId, providerId, resolvedModel);
|
|
2694
|
-
invalidateSessionLLMClient?.(sessionId);
|
|
2695
|
-
const eventStore = getEventStore();
|
|
2696
|
-
const updatedSession = sessionManager2.requireSession(sessionId);
|
|
2697
|
-
const events = eventStore.getEvents(sessionId);
|
|
2698
|
-
const messages = buildMessagesFromStoredEvents(events);
|
|
2699
|
-
sendForSession(sessionId, createSessionStateMessage(updatedSession, messages, message.id));
|
|
2700
|
-
break;
|
|
2701
|
-
}
|
|
2702
2477
|
// =========================================================================
|
|
2703
|
-
// Unified Chat (replaces plan.message, agent.start, etc.)
|
|
2478
|
+
// Unified Chat (replaces plan.message, agent.start, etc.) - WS REQUIRED
|
|
2704
2479
|
// =========================================================================
|
|
2705
2480
|
case "chat.send": {
|
|
2706
2481
|
if (!client.activeSessionId) {
|
|
@@ -2711,15 +2486,15 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2711
2486
|
send(createErrorMessage("INVALID_PAYLOAD", "Invalid chat.send payload", message.id));
|
|
2712
2487
|
return;
|
|
2713
2488
|
}
|
|
2714
|
-
const currentSession =
|
|
2489
|
+
const currentSession = sessionManager.requireSession(client.activeSessionId);
|
|
2715
2490
|
if (currentSession.isRunning) {
|
|
2716
2491
|
send(createErrorMessage("ALREADY_RUNNING", "Session is already running", message.id));
|
|
2717
2492
|
return;
|
|
2718
2493
|
}
|
|
2719
2494
|
if (currentSession.phase === "blocked") {
|
|
2720
2495
|
logger.info("User intervention - resetting blocked state", { sessionId: client.activeSessionId });
|
|
2721
|
-
|
|
2722
|
-
|
|
2496
|
+
sessionManager.setPhase(client.activeSessionId, "build");
|
|
2497
|
+
sessionManager.resetAllCriteriaAttempts(client.activeSessionId);
|
|
2723
2498
|
sendForSession(client.activeSessionId, createPhaseChangedMessage("build"));
|
|
2724
2499
|
}
|
|
2725
2500
|
const sessionId = client.activeSessionId;
|
|
@@ -2732,12 +2507,12 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2732
2507
|
existingController.abort();
|
|
2733
2508
|
}
|
|
2734
2509
|
activeAgents.set(sessionId, controller);
|
|
2735
|
-
|
|
2510
|
+
sessionManager.setRunning(sessionId, true);
|
|
2736
2511
|
sendForSession(sessionId, createSessionRunningMessage(true));
|
|
2737
2512
|
send({ type: "ack", payload: {}, id: message.id });
|
|
2738
2513
|
try {
|
|
2739
2514
|
await maybeAutoCompactContext({
|
|
2740
|
-
sessionManager
|
|
2515
|
+
sessionManager,
|
|
2741
2516
|
sessionId,
|
|
2742
2517
|
llmClient: llmForSession(sessionId),
|
|
2743
2518
|
statsIdentity: statsForSession(sessionId),
|
|
@@ -2746,7 +2521,7 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2746
2521
|
if (controller.signal.aborted) {
|
|
2747
2522
|
break;
|
|
2748
2523
|
}
|
|
2749
|
-
const userMessage =
|
|
2524
|
+
const userMessage = sessionManager.addMessage(sessionId, {
|
|
2750
2525
|
role: "user",
|
|
2751
2526
|
content: message.payload.content,
|
|
2752
2527
|
...message.payload.attachments && { attachments: message.payload.attachments },
|
|
@@ -2767,11 +2542,12 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2767
2542
|
type: "session.name_generated",
|
|
2768
2543
|
data: { name: result.name }
|
|
2769
2544
|
});
|
|
2770
|
-
const updatedSession =
|
|
2545
|
+
const updatedSession = sessionManager.getSession(sessionId);
|
|
2771
2546
|
if (updatedSession) {
|
|
2772
2547
|
const events = eventStore.getEvents(sessionId);
|
|
2773
2548
|
const messages = buildMessagesFromStoredEvents(events);
|
|
2774
|
-
|
|
2549
|
+
const pendingConfirmations = foldPendingConfirmations(events);
|
|
2550
|
+
broadcastForSession(sessionId, createSessionStateMessage(updatedSession, messages, pendingConfirmations));
|
|
2775
2551
|
}
|
|
2776
2552
|
logger.info("Session name generated", { sessionId, name: result.name });
|
|
2777
2553
|
}
|
|
@@ -2784,9 +2560,9 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2784
2560
|
if (activeAgents.get(sessionId) === controller) {
|
|
2785
2561
|
activeAgents.delete(sessionId);
|
|
2786
2562
|
}
|
|
2787
|
-
|
|
2563
|
+
sessionManager.setRunning(sessionId, false);
|
|
2788
2564
|
sendForSession(sessionId, createSessionRunningMessage(false));
|
|
2789
|
-
const contextState =
|
|
2565
|
+
const contextState = sessionManager.getContextState(sessionId);
|
|
2790
2566
|
sendForSession(sessionId, createContextStateMessage(contextState));
|
|
2791
2567
|
if (!(error instanceof Error && error.message === "Aborted")) {
|
|
2792
2568
|
logger.error("Chat send pre-turn error", { sessionId, error });
|
|
@@ -2795,134 +2571,33 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2795
2571
|
break;
|
|
2796
2572
|
}
|
|
2797
2573
|
case "chat.stop": {
|
|
2798
|
-
|
|
2799
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
2800
|
-
return;
|
|
2801
|
-
}
|
|
2802
|
-
abortSessionExecution(client.activeSessionId, "Session aborted by user");
|
|
2803
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
2574
|
+
send(createErrorMessage("DEPRECATED", "chat.stop removed. Use REST API: POST /api/sessions/:id/stop", message.id));
|
|
2804
2575
|
break;
|
|
2805
2576
|
}
|
|
2806
2577
|
// =========================================================================
|
|
2807
2578
|
// Message Queue
|
|
2808
|
-
// =========================================================================
|
|
2809
|
-
case "queue.asap": {
|
|
2810
|
-
|
|
2811
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
2812
|
-
return;
|
|
2813
|
-
}
|
|
2814
|
-
if (!isQueueAsapPayload(message.payload)) {
|
|
2815
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid queue.asap payload", message.id));
|
|
2816
|
-
return;
|
|
2817
|
-
}
|
|
2818
|
-
const { content, attachments } = message.payload;
|
|
2819
|
-
sessionManager2.queueMessage(client.activeSessionId, "asap", content, attachments);
|
|
2820
|
-
sendForSession(client.activeSessionId, createQueueStateMessage(sessionManager2.getQueueState(client.activeSessionId)));
|
|
2821
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
2579
|
+
// =========================================================================
|
|
2580
|
+
case "queue.asap": {
|
|
2581
|
+
send(createErrorMessage("DEPRECATED", "queue.asap removed. Use REST API: POST /api/sessions/:id/queue/asap", message.id));
|
|
2822
2582
|
break;
|
|
2823
2583
|
}
|
|
2824
2584
|
case "queue.completion": {
|
|
2825
|
-
|
|
2826
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
2827
|
-
return;
|
|
2828
|
-
}
|
|
2829
|
-
if (!isQueueCompletionPayload(message.payload)) {
|
|
2830
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid queue.completion payload", message.id));
|
|
2831
|
-
return;
|
|
2832
|
-
}
|
|
2833
|
-
const { content, attachments } = message.payload;
|
|
2834
|
-
sessionManager2.queueMessage(client.activeSessionId, "completion", content, attachments);
|
|
2835
|
-
sendForSession(client.activeSessionId, createQueueStateMessage(sessionManager2.getQueueState(client.activeSessionId)));
|
|
2836
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
2585
|
+
send(createErrorMessage("DEPRECATED", "queue.completion removed. Use REST API: POST /api/sessions/:id/queue/completion", message.id));
|
|
2837
2586
|
break;
|
|
2838
2587
|
}
|
|
2839
2588
|
case "queue.cancel": {
|
|
2840
|
-
|
|
2841
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
2842
|
-
return;
|
|
2843
|
-
}
|
|
2844
|
-
if (!isQueueCancelPayload(message.payload)) {
|
|
2845
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid queue.cancel payload", message.id));
|
|
2846
|
-
return;
|
|
2847
|
-
}
|
|
2848
|
-
sessionManager2.cancelQueuedMessage(client.activeSessionId, message.payload.queueId);
|
|
2849
|
-
sendForSession(client.activeSessionId, createQueueStateMessage(sessionManager2.getQueueState(client.activeSessionId)));
|
|
2850
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
2589
|
+
send(createErrorMessage("DEPRECATED", "queue.cancel removed. Use REST API: DELETE /api/sessions/:id/queue/:queueId", message.id));
|
|
2851
2590
|
break;
|
|
2852
2591
|
}
|
|
2853
2592
|
case "chat.continue": {
|
|
2854
|
-
|
|
2855
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
2856
|
-
return;
|
|
2857
|
-
}
|
|
2858
|
-
const session = sessionManager2.requireSession(client.activeSessionId);
|
|
2859
|
-
if (session.isRunning) {
|
|
2860
|
-
send(createErrorMessage("ALREADY_RUNNING", "Session is already running", message.id));
|
|
2861
|
-
return;
|
|
2862
|
-
}
|
|
2863
|
-
const continueEventStore = getEventStore();
|
|
2864
|
-
const events = continueEventStore.getEvents(session.id);
|
|
2865
|
-
const messages = buildMessagesFromStoredEvents(events);
|
|
2866
|
-
const lastAssistantMessage = [...messages].reverse().find((msg) => msg.role === "assistant");
|
|
2867
|
-
const fallbackMessageId = lastAssistantMessage?.id ?? [...messages].reverse().find((msg) => msg.role === "user")?.id ?? crypto.randomUUID();
|
|
2868
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
2869
|
-
sendForSession(session.id, createChatDoneMessage(fallbackMessageId, "complete", lastAssistantMessage?.stats));
|
|
2593
|
+
send(createErrorMessage("DEPRECATED", "chat.continue removed. Use REST API: POST /api/sessions/:id/continue", message.id));
|
|
2870
2594
|
break;
|
|
2871
2595
|
}
|
|
2872
2596
|
// =========================================================================
|
|
2873
2597
|
// Mode Switching
|
|
2874
2598
|
// =========================================================================
|
|
2875
2599
|
case "mode.switch": {
|
|
2876
|
-
|
|
2877
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
2878
|
-
return;
|
|
2879
|
-
}
|
|
2880
|
-
if (!isModeSwitchPayload(message.payload)) {
|
|
2881
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid mode.switch payload", message.id));
|
|
2882
|
-
return;
|
|
2883
|
-
}
|
|
2884
|
-
const allAgents = await loadAllAgentsDefault();
|
|
2885
|
-
const targetAgent = findAgentById(message.payload.mode, allAgents);
|
|
2886
|
-
if (!targetAgent || targetAgent.metadata.subagent) {
|
|
2887
|
-
send(createErrorMessage("INVALID_AGENT", `Agent '${message.payload.mode}' not found or is a sub-agent`, message.id));
|
|
2888
|
-
return;
|
|
2889
|
-
}
|
|
2890
|
-
const sessionId = client.activeSessionId;
|
|
2891
|
-
const session = sessionManager2.requireSession(sessionId);
|
|
2892
|
-
const eventStore = getEventStore();
|
|
2893
|
-
if (message.payload.mode === "builder" && needsSummaryGeneration(session.summary)) {
|
|
2894
|
-
const events = eventStore.getEvents(sessionId);
|
|
2895
|
-
const nonSystemEvents = events.filter((event) => {
|
|
2896
|
-
if (event.type !== "message.start") return true;
|
|
2897
|
-
return event.data.isSystemGenerated !== true;
|
|
2898
|
-
});
|
|
2899
|
-
const contextMessages = buildContextMessagesFromEventHistory(nonSystemEvents);
|
|
2900
|
-
const summaryMessages = contextMessages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => ({ role: m.role, content: m.content }));
|
|
2901
|
-
if (summaryMessages.length > 0) {
|
|
2902
|
-
generateSessionSummary({
|
|
2903
|
-
messages: summaryMessages,
|
|
2904
|
-
llmClient: llmForSession(sessionId)
|
|
2905
|
-
}).then(async (result) => {
|
|
2906
|
-
if (result.success && result.summary) {
|
|
2907
|
-
sessionManager2.setSummary(sessionId, result.summary);
|
|
2908
|
-
const updatedSession = sessionManager2.getSession(sessionId);
|
|
2909
|
-
if (updatedSession) {
|
|
2910
|
-
const events2 = eventStore.getEvents(sessionId);
|
|
2911
|
-
const messages = buildMessagesFromStoredEvents(events2);
|
|
2912
|
-
broadcastForSession(sessionId, createSessionStateMessage(updatedSession, messages));
|
|
2913
|
-
}
|
|
2914
|
-
logger.info("Session summary generated", { sessionId, summaryLength: result.summary.length });
|
|
2915
|
-
}
|
|
2916
|
-
}).catch((error) => {
|
|
2917
|
-
logger.warn("Session summary generation failed", { sessionId, error: error instanceof Error ? error.message : error });
|
|
2918
|
-
});
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
sessionManager2.setMode(sessionId, message.payload.mode);
|
|
2922
|
-
sendForSession(sessionId, createModeChangedMessage(message.payload.mode, false));
|
|
2923
|
-
const modeEvents = eventStore.getEvents(sessionId);
|
|
2924
|
-
const modeMessages = buildMessagesFromStoredEvents(modeEvents);
|
|
2925
|
-
sendForSession(sessionId, createSessionStateMessage(sessionManager2.getSession(sessionId), modeMessages, message.id));
|
|
2600
|
+
send(createErrorMessage("DEPRECATED", "mode.switch removed. Use REST API: PUT /api/sessions/:id/mode", message.id));
|
|
2926
2601
|
break;
|
|
2927
2602
|
}
|
|
2928
2603
|
case "mode.accept": {
|
|
@@ -2930,9 +2605,26 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2930
2605
|
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
2931
2606
|
return;
|
|
2932
2607
|
}
|
|
2933
|
-
const session =
|
|
2608
|
+
const session = sessionManager.requireSession(client.activeSessionId);
|
|
2609
|
+
const acceptPayload = message.payload;
|
|
2934
2610
|
if (session.isRunning) {
|
|
2935
|
-
|
|
2611
|
+
const content = acceptPayload?.content ?? "";
|
|
2612
|
+
const attachments = acceptPayload?.attachments;
|
|
2613
|
+
const workflowId = acceptPayload?.workflowId;
|
|
2614
|
+
let fullContent = content;
|
|
2615
|
+
if (workflowId) {
|
|
2616
|
+
const workflowInfo = `// Workflow: ${workflowId}`;
|
|
2617
|
+
fullContent = content ? `${workflowInfo}
|
|
2618
|
+
|
|
2619
|
+
${content}` : workflowInfo;
|
|
2620
|
+
}
|
|
2621
|
+
sessionManager.queueMessage(client.activeSessionId, "asap", fullContent, attachments, "workflow-accept");
|
|
2622
|
+
const queueState = sessionManager.getQueueState(client.activeSessionId);
|
|
2623
|
+
send({
|
|
2624
|
+
type: "queue.state",
|
|
2625
|
+
payload: { success: true, queueState },
|
|
2626
|
+
id: message.id
|
|
2627
|
+
});
|
|
2936
2628
|
return;
|
|
2937
2629
|
}
|
|
2938
2630
|
const acceptPayloadEarly = message.payload;
|
|
@@ -2951,7 +2643,7 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2951
2643
|
try {
|
|
2952
2644
|
for await (const storedEvent of iterator) {
|
|
2953
2645
|
const serverMsg = storedEventToServerMessage(storedEvent);
|
|
2954
|
-
if (serverMsg && ws.readyState ===
|
|
2646
|
+
if (serverMsg && ws.readyState === WebSocket2.OPEN) {
|
|
2955
2647
|
ws.send(serializeServerMessage({ ...serverMsg, seq: storedEvent.seq, sessionId: sid }));
|
|
2956
2648
|
}
|
|
2957
2649
|
}
|
|
@@ -2960,10 +2652,10 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2960
2652
|
}
|
|
2961
2653
|
})();
|
|
2962
2654
|
}
|
|
2963
|
-
|
|
2655
|
+
sessionManager.setRunning(sessionId, true);
|
|
2964
2656
|
eventStore.append(sessionId, { type: "running.changed", data: { isRunning: true } });
|
|
2965
2657
|
sendForSession(sessionId, createSessionRunningMessage(true));
|
|
2966
|
-
const currentSession =
|
|
2658
|
+
const currentSession = sessionManager.requireSession(sessionId);
|
|
2967
2659
|
if (needsSummaryGeneration(currentSession.summary)) {
|
|
2968
2660
|
const events = eventStore.getEvents(sessionId);
|
|
2969
2661
|
const nonSystemEvents = events.filter((event) => {
|
|
@@ -2973,12 +2665,19 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2973
2665
|
const contextMessages = buildContextMessagesFromEventHistory(nonSystemEvents);
|
|
2974
2666
|
const summaryMessages = contextMessages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => ({ role: m.role, content: m.content }));
|
|
2975
2667
|
if (summaryMessages.length > 0) {
|
|
2668
|
+
const config = getRuntimeConfig();
|
|
2669
|
+
const configDir = getGlobalConfigDir(config.mode ?? "production");
|
|
2670
|
+
const skills = await getEnabledSkillMetadata(configDir);
|
|
2671
|
+
const { content: instructions } = await getAllInstructions(currentSession.workdir, currentSession.projectId);
|
|
2976
2672
|
generateSessionSummary({
|
|
2977
2673
|
messages: summaryMessages,
|
|
2978
|
-
llmClient: llmForSession(sessionId)
|
|
2674
|
+
llmClient: llmForSession(sessionId),
|
|
2675
|
+
workdir: currentSession.workdir,
|
|
2676
|
+
customInstructions: instructions || void 0,
|
|
2677
|
+
skills
|
|
2979
2678
|
}).then(async (result) => {
|
|
2980
2679
|
if (result.success && result.summary) {
|
|
2981
|
-
|
|
2680
|
+
sessionManager.setSummary(sessionId, result.summary);
|
|
2982
2681
|
}
|
|
2983
2682
|
}).catch((error) => {
|
|
2984
2683
|
logger.warn("Session summary generation failed", { sessionId, error: error instanceof Error ? error.message : error });
|
|
@@ -2989,8 +2688,8 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
2989
2688
|
(async () => {
|
|
2990
2689
|
let controller = null;
|
|
2991
2690
|
try {
|
|
2992
|
-
|
|
2993
|
-
|
|
2691
|
+
sessionManager.setMode(sessionId, "builder");
|
|
2692
|
+
sessionManager.setPhase(sessionId, "build");
|
|
2994
2693
|
eventStore.append(sessionId, { type: "mode.changed", data: { mode: "builder", auto: false, reason: "Criteria accepted" } });
|
|
2995
2694
|
eventStore.append(sessionId, { type: "phase.changed", data: { phase: "build" } });
|
|
2996
2695
|
controller = new AbortController();
|
|
@@ -3000,19 +2699,19 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3000
2699
|
existingController.abort();
|
|
3001
2700
|
}
|
|
3002
2701
|
activeAgents.set(sessionId, controller);
|
|
3003
|
-
const
|
|
3004
|
-
const acceptAttachments =
|
|
3005
|
-
const hasAcceptContent =
|
|
2702
|
+
const acceptPayload2 = message.payload;
|
|
2703
|
+
const acceptAttachments = acceptPayload2?.attachments;
|
|
2704
|
+
const hasAcceptContent = acceptPayload2?.content && typeof acceptPayload2.content === "string" && acceptPayload2.content.trim();
|
|
3006
2705
|
const hasAcceptAttachments = acceptAttachments && acceptAttachments.length > 0;
|
|
3007
2706
|
const hasAcceptMessage = hasAcceptContent || hasAcceptAttachments;
|
|
3008
2707
|
await runOrchestrator({
|
|
3009
|
-
sessionManager
|
|
2708
|
+
sessionManager,
|
|
3010
2709
|
sessionId,
|
|
3011
2710
|
llmClient: llmForSession(sessionId),
|
|
3012
2711
|
statsIdentity: statsForSession(sessionId),
|
|
3013
2712
|
injectBuilderKickoff: !hasAcceptMessage,
|
|
3014
|
-
...
|
|
3015
|
-
...hasAcceptMessage ? { userMessage: { content: hasAcceptContent ?
|
|
2713
|
+
...acceptPayload2?.workflowId ? { workflowId: acceptPayload2.workflowId } : {},
|
|
2714
|
+
...hasAcceptMessage ? { userMessage: { content: hasAcceptContent ? acceptPayload2.content : "", ...hasAcceptAttachments ? { attachments: acceptAttachments } : {} } } : {},
|
|
3016
2715
|
signal: controller.signal,
|
|
3017
2716
|
onMessage: (msg) => sendForSession(sessionId, msg)
|
|
3018
2717
|
// For path confirmation dialogs
|
|
@@ -3031,7 +2730,7 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3031
2730
|
});
|
|
3032
2731
|
const errorMsgId = crypto.randomUUID();
|
|
3033
2732
|
eventStore.append(sessionId, createMessageStartEvent(errorMsgId, "user", `Error: ${error instanceof Error ? error.message : "Unknown error"}`, {
|
|
3034
|
-
...
|
|
2733
|
+
...getCurrentWindowMessageOptions2(sessionId) ?? {},
|
|
3035
2734
|
isSystemGenerated: true,
|
|
3036
2735
|
messageKind: "correction"
|
|
3037
2736
|
}));
|
|
@@ -3042,14 +2741,14 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3042
2741
|
return;
|
|
3043
2742
|
}
|
|
3044
2743
|
activeAgents.delete(sessionId);
|
|
3045
|
-
const completionMsgs =
|
|
2744
|
+
const completionMsgs = sessionManager.drainCompletionMessages(sessionId);
|
|
3046
2745
|
const nextCompletion = completionMsgs[0];
|
|
3047
2746
|
if (nextCompletion) {
|
|
3048
2747
|
for (const remaining of completionMsgs.slice(1)) {
|
|
3049
|
-
|
|
2748
|
+
sessionManager.queueMessage(sessionId, "completion", remaining.content, remaining.attachments);
|
|
3050
2749
|
}
|
|
3051
|
-
sendForSession(sessionId, createQueueStateMessage(
|
|
3052
|
-
const userMessage =
|
|
2750
|
+
sendForSession(sessionId, createQueueStateMessage(sessionManager.getQueueState(sessionId)));
|
|
2751
|
+
const userMessage = sessionManager.addMessage(sessionId, {
|
|
3053
2752
|
role: "user",
|
|
3054
2753
|
content: nextCompletion.content,
|
|
3055
2754
|
...nextCompletion.attachments ? { attachments: nextCompletion.attachments } : {}
|
|
@@ -3060,10 +2759,28 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3060
2759
|
startTurnWithCompletionChain(sessionId, newController);
|
|
3061
2760
|
return;
|
|
3062
2761
|
}
|
|
3063
|
-
|
|
3064
|
-
|
|
2762
|
+
const asapMsgs = sessionManager.drainAsapMessages(sessionId);
|
|
2763
|
+
const nextAsap = asapMsgs[0];
|
|
2764
|
+
if (nextAsap) {
|
|
2765
|
+
for (const remaining of asapMsgs.slice(1)) {
|
|
2766
|
+
sessionManager.queueMessage(sessionId, "asap", remaining.content, remaining.attachments, remaining.messageKind);
|
|
2767
|
+
}
|
|
2768
|
+
sendForSession(sessionId, createQueueStateMessage(sessionManager.getQueueState(sessionId)));
|
|
2769
|
+
const userMessage = sessionManager.addMessage(sessionId, {
|
|
2770
|
+
role: "user",
|
|
2771
|
+
content: nextAsap.content,
|
|
2772
|
+
...nextAsap.attachments ? { attachments: nextAsap.attachments } : {}
|
|
2773
|
+
});
|
|
2774
|
+
sendForSession(sessionId, createChatMessageMessage(userMessage));
|
|
2775
|
+
const newerController = new AbortController();
|
|
2776
|
+
activeAgents.set(sessionId, newerController);
|
|
2777
|
+
startTurnWithCompletionChain(sessionId, newerController);
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
sessionManager.clearMessageQueue(sessionId);
|
|
2781
|
+
sessionManager.setRunning(sessionId, false);
|
|
3065
2782
|
eventStore.append(sessionId, { type: "running.changed", data: { isRunning: false } });
|
|
3066
|
-
const contextState =
|
|
2783
|
+
const contextState = sessionManager.getContextState(sessionId);
|
|
3067
2784
|
sendForSession(sessionId, createContextStateMessage(contextState));
|
|
3068
2785
|
}
|
|
3069
2786
|
})();
|
|
@@ -3073,17 +2790,7 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3073
2790
|
// Criteria Editing
|
|
3074
2791
|
// =========================================================================
|
|
3075
2792
|
case "criteria.edit": {
|
|
3076
|
-
|
|
3077
|
-
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3078
|
-
return;
|
|
3079
|
-
}
|
|
3080
|
-
if (!isCriteriaEditPayload(message.payload)) {
|
|
3081
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid criteria.edit payload", message.id));
|
|
3082
|
-
return;
|
|
3083
|
-
}
|
|
3084
|
-
sessionManager2.setCriteria(client.activeSessionId, message.payload.criteria);
|
|
3085
|
-
sendForSession(client.activeSessionId, createCriteriaUpdatedMessage(message.payload.criteria));
|
|
3086
|
-
send({ type: "ack", payload: {}, id: message.id });
|
|
2793
|
+
send(createErrorMessage("DEPRECATED", "criteria.edit removed. Use REST API: PUT /api/sessions/:id/criteria", message.id));
|
|
3087
2794
|
break;
|
|
3088
2795
|
}
|
|
3089
2796
|
// =========================================================================
|
|
@@ -3094,31 +2801,32 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3094
2801
|
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3095
2802
|
return;
|
|
3096
2803
|
}
|
|
3097
|
-
const session =
|
|
2804
|
+
const session = sessionManager.requireSession(client.activeSessionId);
|
|
3098
2805
|
const sessionId = client.activeSessionId;
|
|
3099
2806
|
if (session.isRunning) {
|
|
3100
2807
|
send(createErrorMessage("SESSION_RUNNING", "Cannot compact while session is running", message.id));
|
|
3101
2808
|
return;
|
|
3102
2809
|
}
|
|
3103
|
-
const contextState =
|
|
2810
|
+
const contextState = sessionManager.getContextState(sessionId);
|
|
3104
2811
|
const tokensBefore = contextState.currentTokens;
|
|
3105
2812
|
send({ type: "ack", payload: {}, id: message.id });
|
|
3106
2813
|
(async () => {
|
|
3107
2814
|
try {
|
|
3108
2815
|
await performManualContextCompaction({
|
|
3109
|
-
sessionManager
|
|
2816
|
+
sessionManager,
|
|
3110
2817
|
sessionId,
|
|
3111
2818
|
llmClient: llmForSession(sessionId),
|
|
3112
2819
|
statsIdentity: statsForSession(sessionId),
|
|
3113
2820
|
tokenCountAtClose: tokensBefore
|
|
3114
2821
|
});
|
|
3115
|
-
const newContextState =
|
|
2822
|
+
const newContextState = sessionManager.getContextState(sessionId);
|
|
3116
2823
|
sendForSession(sessionId, createContextStateMessage(newContextState));
|
|
3117
|
-
const updatedSession =
|
|
2824
|
+
const updatedSession = sessionManager.requireSession(sessionId);
|
|
3118
2825
|
const compactEventStore = getEventStore();
|
|
3119
2826
|
const compactEvents = compactEventStore.getEvents(sessionId);
|
|
3120
2827
|
const compactMessages = buildMessagesFromStoredEvents(compactEvents);
|
|
3121
|
-
|
|
2828
|
+
const pendingConfirmations = foldPendingConfirmations(compactEvents);
|
|
2829
|
+
sendForSession(sessionId, createSessionStateMessage(updatedSession, compactMessages, pendingConfirmations));
|
|
3122
2830
|
} catch (error) {
|
|
3123
2831
|
logger.error("Compaction failed", { error, sessionId });
|
|
3124
2832
|
sendForSession(sessionId, createChatErrorMessage(
|
|
@@ -3137,9 +2845,26 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3137
2845
|
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3138
2846
|
return;
|
|
3139
2847
|
}
|
|
3140
|
-
const session =
|
|
2848
|
+
const session = sessionManager.requireSession(client.activeSessionId);
|
|
3141
2849
|
if (session.isRunning) {
|
|
3142
|
-
|
|
2850
|
+
const launchPayload2 = message.payload;
|
|
2851
|
+
const content = launchPayload2?.content ?? "";
|
|
2852
|
+
const attachments = launchPayload2?.attachments;
|
|
2853
|
+
const workflowId = launchPayload2?.workflowId;
|
|
2854
|
+
let fullContent = content;
|
|
2855
|
+
if (workflowId) {
|
|
2856
|
+
const workflowInfo = `// Workflow: ${workflowId}`;
|
|
2857
|
+
fullContent = content ? `${workflowInfo}
|
|
2858
|
+
|
|
2859
|
+
${content}` : workflowInfo;
|
|
2860
|
+
}
|
|
2861
|
+
sessionManager.queueMessage(client.activeSessionId, "asap", fullContent, attachments, "workflow-launch");
|
|
2862
|
+
const queueState = sessionManager.getQueueState(client.activeSessionId);
|
|
2863
|
+
send({
|
|
2864
|
+
type: "queue.state",
|
|
2865
|
+
payload: { success: true, queueState },
|
|
2866
|
+
id: message.id
|
|
2867
|
+
});
|
|
3143
2868
|
return;
|
|
3144
2869
|
}
|
|
3145
2870
|
if (session.mode !== "builder") {
|
|
@@ -3155,15 +2880,15 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3155
2880
|
const sessionId = client.activeSessionId;
|
|
3156
2881
|
if (session.phase === "blocked") {
|
|
3157
2882
|
logger.info("User launched runner - resetting blocked state", { sessionId });
|
|
3158
|
-
|
|
3159
|
-
|
|
2883
|
+
sessionManager.setPhase(sessionId, "build");
|
|
2884
|
+
sessionManager.resetAllCriteriaAttempts(sessionId);
|
|
3160
2885
|
}
|
|
3161
2886
|
const launchPayload = message.payload;
|
|
3162
2887
|
const launchAttachments = launchPayload?.attachments;
|
|
3163
2888
|
const hasUserContent = launchPayload?.content && typeof launchPayload.content === "string" && launchPayload.content.trim();
|
|
3164
2889
|
const hasUserAttachments = launchAttachments && launchAttachments.length > 0;
|
|
3165
2890
|
const hasUserMessage = hasUserContent || hasUserAttachments;
|
|
3166
|
-
|
|
2891
|
+
sessionManager.setRunning(sessionId, true);
|
|
3167
2892
|
sendForSession(sessionId, createSessionRunningMessage(true));
|
|
3168
2893
|
const controller = new AbortController();
|
|
3169
2894
|
const existingController = activeAgents.get(sessionId);
|
|
@@ -3176,7 +2901,7 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3176
2901
|
ensureEventStoreSubscription(sessionId);
|
|
3177
2902
|
logger.info("Runner launching", { sessionId, pendingCriteria: pendingCriteria.length });
|
|
3178
2903
|
runOrchestrator({
|
|
3179
|
-
sessionManager
|
|
2904
|
+
sessionManager,
|
|
3180
2905
|
sessionId,
|
|
3181
2906
|
llmClient: llmForSession(sessionId),
|
|
3182
2907
|
statsIdentity: statsForSession(sessionId),
|
|
@@ -3196,14 +2921,32 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3196
2921
|
return;
|
|
3197
2922
|
}
|
|
3198
2923
|
activeAgents.delete(sessionId);
|
|
3199
|
-
const
|
|
2924
|
+
const asapMsgs = sessionManager.drainAsapMessages(sessionId);
|
|
2925
|
+
const nextAsap = asapMsgs[0];
|
|
2926
|
+
if (nextAsap) {
|
|
2927
|
+
for (const remaining of asapMsgs.slice(1)) {
|
|
2928
|
+
sessionManager.queueMessage(sessionId, "asap", remaining.content, remaining.attachments, remaining.messageKind);
|
|
2929
|
+
}
|
|
2930
|
+
sendForSession(sessionId, createQueueStateMessage(sessionManager.getQueueState(sessionId)));
|
|
2931
|
+
const userMessage = sessionManager.addMessage(sessionId, {
|
|
2932
|
+
role: "user",
|
|
2933
|
+
content: nextAsap.content,
|
|
2934
|
+
...nextAsap.attachments ? { attachments: nextAsap.attachments } : {}
|
|
2935
|
+
});
|
|
2936
|
+
sendForSession(sessionId, createChatMessageMessage(userMessage));
|
|
2937
|
+
const newerController = new AbortController();
|
|
2938
|
+
activeAgents.set(sessionId, newerController);
|
|
2939
|
+
startTurnWithCompletionChain(sessionId, newerController);
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
const completionMsgs = sessionManager.drainCompletionMessages(sessionId);
|
|
3200
2943
|
const nextCompletion = completionMsgs[0];
|
|
3201
2944
|
if (nextCompletion) {
|
|
3202
2945
|
for (const remaining of completionMsgs.slice(1)) {
|
|
3203
|
-
|
|
2946
|
+
sessionManager.queueMessage(sessionId, "completion", remaining.content, remaining.attachments);
|
|
3204
2947
|
}
|
|
3205
|
-
sendForSession(sessionId, createQueueStateMessage(
|
|
3206
|
-
const userMessage =
|
|
2948
|
+
sendForSession(sessionId, createQueueStateMessage(sessionManager.getQueueState(sessionId)));
|
|
2949
|
+
const userMessage = sessionManager.addMessage(sessionId, {
|
|
3207
2950
|
role: "user",
|
|
3208
2951
|
content: nextCompletion.content,
|
|
3209
2952
|
...nextCompletion.attachments ? { attachments: nextCompletion.attachments } : {}
|
|
@@ -3214,8 +2957,8 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3214
2957
|
startTurnWithCompletionChain(sessionId, newController);
|
|
3215
2958
|
return;
|
|
3216
2959
|
}
|
|
3217
|
-
|
|
3218
|
-
|
|
2960
|
+
sessionManager.clearMessageQueue(sessionId);
|
|
2961
|
+
sessionManager.setRunning(sessionId, false);
|
|
3219
2962
|
});
|
|
3220
2963
|
break;
|
|
3221
2964
|
}
|
|
@@ -3223,24 +2966,31 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3223
2966
|
// Path Confirmation
|
|
3224
2967
|
// =========================================================================
|
|
3225
2968
|
case "path.confirm": {
|
|
2969
|
+
send(createErrorMessage("DEPRECATED", "path.confirm removed. Use REST API: POST /api/sessions/:id/confirm-path", message.id));
|
|
2970
|
+
break;
|
|
2971
|
+
}
|
|
2972
|
+
// =========================================================================
|
|
2973
|
+
// Ask User
|
|
2974
|
+
// =========================================================================
|
|
2975
|
+
case "ask.answer": {
|
|
3226
2976
|
if (!client.activeSessionId) {
|
|
3227
2977
|
send(createErrorMessage("NO_SESSION", "No active session", message.id));
|
|
3228
2978
|
return;
|
|
3229
2979
|
}
|
|
3230
|
-
if (!
|
|
3231
|
-
send(createErrorMessage("INVALID_PAYLOAD", "Invalid
|
|
2980
|
+
if (!isAskAnswerPayload(message.payload)) {
|
|
2981
|
+
send(createErrorMessage("INVALID_PAYLOAD", "Invalid ask.answer payload", message.id));
|
|
3232
2982
|
return;
|
|
3233
2983
|
}
|
|
3234
|
-
const { callId,
|
|
3235
|
-
const
|
|
3236
|
-
if (!
|
|
3237
|
-
send(createErrorMessage("NOT_FOUND", "No pending
|
|
2984
|
+
const { callId, answer } = message.payload;
|
|
2985
|
+
const found = provideAnswer(callId, answer);
|
|
2986
|
+
if (!found) {
|
|
2987
|
+
send(createErrorMessage("NOT_FOUND", "No pending question with that ID", message.id));
|
|
3238
2988
|
return;
|
|
3239
2989
|
}
|
|
3240
|
-
logger.debug("
|
|
2990
|
+
logger.debug("Ask user answer received", {
|
|
3241
2991
|
sessionId: client.activeSessionId,
|
|
3242
2992
|
callId,
|
|
3243
|
-
|
|
2993
|
+
answerLength: answer.length
|
|
3244
2994
|
});
|
|
3245
2995
|
send({ type: "ack", payload: {}, id: message.id });
|
|
3246
2996
|
break;
|
|
@@ -3252,7 +3002,7 @@ async function handleClientMessage(ws, client, message, config, getLLMClient, ge
|
|
|
3252
3002
|
}
|
|
3253
3003
|
|
|
3254
3004
|
// src/server/lsp/server.ts
|
|
3255
|
-
import { spawn as
|
|
3005
|
+
import { spawn as spawn3 } from "child_process";
|
|
3256
3006
|
import { extname } from "path";
|
|
3257
3007
|
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node.js";
|
|
3258
3008
|
var LSP = {
|
|
@@ -3314,7 +3064,7 @@ var LspServer = class {
|
|
|
3314
3064
|
}
|
|
3315
3065
|
this.state = "starting";
|
|
3316
3066
|
try {
|
|
3317
|
-
this.process =
|
|
3067
|
+
this.process = spawn3(this.commandPath, this.config.serverArgs, {
|
|
3318
3068
|
cwd: this.workdir,
|
|
3319
3069
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3320
3070
|
env: {
|
|
@@ -4299,11 +4049,16 @@ function canCompact(currentTokens, maxTokens) {
|
|
|
4299
4049
|
var SessionManager = class {
|
|
4300
4050
|
events = new EventEmitter();
|
|
4301
4051
|
activeSessionId = null;
|
|
4052
|
+
providerManager;
|
|
4053
|
+
constructor(providerManager) {
|
|
4054
|
+
this.providerManager = providerManager;
|
|
4055
|
+
}
|
|
4302
4056
|
// ============================================================================
|
|
4303
4057
|
// Session Lifecycle
|
|
4304
4058
|
// ============================================================================
|
|
4305
4059
|
/**
|
|
4306
4060
|
* Create a new session. Emits session.initialized event.
|
|
4061
|
+
* Note: maxTokens is no longer stored in the session - it comes from the current model config
|
|
4307
4062
|
*/
|
|
4308
4063
|
createSession(projectId, title, providerId, providerModel) {
|
|
4309
4064
|
const project = getProject(projectId);
|
|
@@ -4312,8 +4067,8 @@ var SessionManager = class {
|
|
|
4312
4067
|
}
|
|
4313
4068
|
let sessionTitle = title;
|
|
4314
4069
|
if (!sessionTitle) {
|
|
4315
|
-
const existingSessions = listSessionsByProject(projectId);
|
|
4316
|
-
sessionTitle = `Session ${existingSessions.length + 1}`;
|
|
4070
|
+
const existingSessions = listSessionsByProject(projectId, 1e3, 0);
|
|
4071
|
+
sessionTitle = `Session ${existingSessions.sessions.length + 1}`;
|
|
4317
4072
|
}
|
|
4318
4073
|
logger.debug("Creating session", { projectId, workdir: project.workdir, title: sessionTitle });
|
|
4319
4074
|
const dbSession = createSession(projectId, project.workdir, sessionTitle, providerId, providerModel);
|
|
@@ -4351,14 +4106,14 @@ var SessionManager = class {
|
|
|
4351
4106
|
return listSessions();
|
|
4352
4107
|
}
|
|
4353
4108
|
/**
|
|
4354
|
-
* List sessions for a project.
|
|
4109
|
+
* List sessions for a project with pagination.
|
|
4355
4110
|
*/
|
|
4356
|
-
listSessionsByProject(projectId) {
|
|
4111
|
+
listSessionsByProject(projectId, limit = 20, offset = 0) {
|
|
4357
4112
|
const project = getProject(projectId);
|
|
4358
4113
|
if (!project) {
|
|
4359
|
-
return [];
|
|
4114
|
+
return { sessions: [], hasMore: false };
|
|
4360
4115
|
}
|
|
4361
|
-
return listSessionsByProject(projectId);
|
|
4116
|
+
return listSessionsByProject(projectId, limit, offset);
|
|
4362
4117
|
}
|
|
4363
4118
|
/**
|
|
4364
4119
|
* Delete a session and all its events.
|
|
@@ -4387,8 +4142,8 @@ var SessionManager = class {
|
|
|
4387
4142
|
*/
|
|
4388
4143
|
deleteAllSessions(projectId, workdir) {
|
|
4389
4144
|
logger.debug("Deleting all sessions for project", { projectId, workdir });
|
|
4390
|
-
const
|
|
4391
|
-
sessions.forEach((session) => {
|
|
4145
|
+
const result = listSessionsByProject(projectId, 1e4, 0);
|
|
4146
|
+
result.sessions.forEach((session) => {
|
|
4392
4147
|
this.deleteSession(session.id);
|
|
4393
4148
|
});
|
|
4394
4149
|
}
|
|
@@ -4425,6 +4180,16 @@ var SessionManager = class {
|
|
|
4425
4180
|
this.emit({ type: "phase_changed", sessionId, phase });
|
|
4426
4181
|
return updatedSession;
|
|
4427
4182
|
}
|
|
4183
|
+
/**
|
|
4184
|
+
* Set danger level. Does NOT emit event - danger level is not part of session state.
|
|
4185
|
+
* Just updates DB and returns updated session.
|
|
4186
|
+
*/
|
|
4187
|
+
setDangerLevel(sessionId, dangerLevel) {
|
|
4188
|
+
this.requireSession(sessionId);
|
|
4189
|
+
logger.debug("Setting danger level", { sessionId, dangerLevel });
|
|
4190
|
+
updateSessionDangerLevel(sessionId, dangerLevel);
|
|
4191
|
+
return this.requireSession(sessionId);
|
|
4192
|
+
}
|
|
4428
4193
|
/**
|
|
4429
4194
|
* Set session running state. Emits running.changed event.
|
|
4430
4195
|
*/
|
|
@@ -4598,17 +4363,18 @@ ${summary}`, {
|
|
|
4598
4363
|
contextWindowId: newWindowId,
|
|
4599
4364
|
isSystemGenerated: true,
|
|
4600
4365
|
messageKind: "auto-prompt",
|
|
4601
|
-
isCompactionSummary: true
|
|
4366
|
+
isCompactionSummary: true,
|
|
4367
|
+
metadata: { type: "compaction", name: "Compaction", color: "#64748b" }
|
|
4602
4368
|
});
|
|
4603
4369
|
}
|
|
4604
4370
|
/**
|
|
4605
4371
|
* Set current context size (for token tracking).
|
|
4606
4372
|
* Emits a context.state event with the real promptTokens from the LLM.
|
|
4373
|
+
* maxTokens comes from providerManager.getCurrentModelContext() - the currently selected model's limit.
|
|
4607
4374
|
*/
|
|
4608
4375
|
setCurrentContextSize(sessionId, promptTokens) {
|
|
4609
|
-
const
|
|
4610
|
-
const maxTokens =
|
|
4611
|
-
const state = getSessionState(sessionId);
|
|
4376
|
+
const state = getSessionState(sessionId, this.providerManager.getCurrentModelContext());
|
|
4377
|
+
const maxTokens = this.providerManager.getCurrentModelContext();
|
|
4612
4378
|
const compactionCount = state?.contextState.compactionCount ?? 0;
|
|
4613
4379
|
emitContextState(
|
|
4614
4380
|
sessionId,
|
|
@@ -4682,14 +4448,7 @@ ${summary}`, {
|
|
|
4682
4448
|
if (!state) {
|
|
4683
4449
|
return { error: "Session not found" };
|
|
4684
4450
|
}
|
|
4685
|
-
|
|
4686
|
-
if (state.criteria.some((c) => c.id === criterion.id)) {
|
|
4687
|
-
let suffix = 1;
|
|
4688
|
-
while (state.criteria.some((c) => c.id === `${criterion.id}-${suffix}`)) {
|
|
4689
|
-
suffix++;
|
|
4690
|
-
}
|
|
4691
|
-
actualId = `${criterion.id}-${suffix}`;
|
|
4692
|
-
}
|
|
4451
|
+
const actualId = criterion.id || state.criteria.length.toString();
|
|
4693
4452
|
const updatedCriteria = [...state.criteria, { ...criterion, id: actualId }];
|
|
4694
4453
|
emitCriteriaSet(sessionId, updatedCriteria);
|
|
4695
4454
|
return { criteria: updatedCriteria, actualId };
|
|
@@ -4730,17 +4489,19 @@ ${summary}`, {
|
|
|
4730
4489
|
// Message Queue (runtime state, transient while agent is running)
|
|
4731
4490
|
// ============================================================================
|
|
4732
4491
|
messageQueues = /* @__PURE__ */ new Map();
|
|
4733
|
-
queueMessage(sessionId, mode, content, attachments) {
|
|
4492
|
+
queueMessage(sessionId, mode, content, attachments, messageKind) {
|
|
4734
4493
|
const queue = this.messageQueues.get(sessionId) ?? [];
|
|
4735
4494
|
const msg = {
|
|
4736
4495
|
queueId: crypto.randomUUID(),
|
|
4737
4496
|
mode,
|
|
4738
4497
|
content,
|
|
4739
4498
|
...attachments ? { attachments } : {},
|
|
4499
|
+
...messageKind ? { messageKind } : {},
|
|
4740
4500
|
queuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4741
4501
|
};
|
|
4742
4502
|
queue.push(msg);
|
|
4743
4503
|
this.messageQueues.set(sessionId, queue);
|
|
4504
|
+
this.emit({ type: "queue_added", sessionId, queueId: msg.queueId, mode, content });
|
|
4744
4505
|
return msg;
|
|
4745
4506
|
}
|
|
4746
4507
|
cancelQueuedMessage(sessionId, queueId) {
|
|
@@ -4749,6 +4510,7 @@ ${summary}`, {
|
|
|
4749
4510
|
const idx = queue.findIndex((m) => m.queueId === queueId);
|
|
4750
4511
|
if (idx === -1) return false;
|
|
4751
4512
|
queue.splice(idx, 1);
|
|
4513
|
+
this.emit({ type: "queue_cancelled", sessionId, queueId });
|
|
4752
4514
|
return true;
|
|
4753
4515
|
}
|
|
4754
4516
|
drainAsapMessages(sessionId) {
|
|
@@ -4756,6 +4518,9 @@ ${summary}`, {
|
|
|
4756
4518
|
if (!queue) return [];
|
|
4757
4519
|
const asap = queue.filter((m) => m.mode === "asap");
|
|
4758
4520
|
this.messageQueues.set(sessionId, queue.filter((m) => m.mode !== "asap"));
|
|
4521
|
+
for (const msg of asap) {
|
|
4522
|
+
this.emit({ type: "queue_drained", sessionId, queueId: msg.queueId });
|
|
4523
|
+
}
|
|
4759
4524
|
return asap;
|
|
4760
4525
|
}
|
|
4761
4526
|
drainCompletionMessages(sessionId) {
|
|
@@ -4763,11 +4528,18 @@ ${summary}`, {
|
|
|
4763
4528
|
if (!queue) return [];
|
|
4764
4529
|
const completion = queue.filter((m) => m.mode === "completion");
|
|
4765
4530
|
this.messageQueues.set(sessionId, queue.filter((m) => m.mode !== "completion"));
|
|
4531
|
+
for (const msg of completion) {
|
|
4532
|
+
this.emit({ type: "queue_drained", sessionId, queueId: msg.queueId });
|
|
4533
|
+
}
|
|
4766
4534
|
return completion;
|
|
4767
4535
|
}
|
|
4768
4536
|
getQueueState(sessionId) {
|
|
4769
4537
|
return this.messageQueues.get(sessionId) ?? [];
|
|
4770
4538
|
}
|
|
4539
|
+
hasQueuedMessages(sessionId) {
|
|
4540
|
+
const queue = this.messageQueues.get(sessionId);
|
|
4541
|
+
return queue !== void 0 && queue.length > 0;
|
|
4542
|
+
}
|
|
4771
4543
|
clearMessageQueue(sessionId) {
|
|
4772
4544
|
this.messageQueues.delete(sessionId);
|
|
4773
4545
|
}
|
|
@@ -4849,12 +4621,34 @@ ${summary}`,
|
|
|
4849
4621
|
* Get the current context state for a session.
|
|
4850
4622
|
*/
|
|
4851
4623
|
getContextState(sessionId) {
|
|
4852
|
-
const
|
|
4624
|
+
const session = this.getSession(sessionId);
|
|
4625
|
+
const providerManager = this.providerManager;
|
|
4626
|
+
let maxTokens;
|
|
4627
|
+
if (session?.providerId && session.providerModel) {
|
|
4628
|
+
const providers = providerManager.getProviders();
|
|
4629
|
+
const provider = providers.find((p) => p.id === session.providerId);
|
|
4630
|
+
if (provider) {
|
|
4631
|
+
let modelConfig = provider.models.find((m) => m.id === session.providerModel);
|
|
4632
|
+
if (!modelConfig && session.providerModel) {
|
|
4633
|
+
const normalize = (s) => s.toLowerCase().replace(/[-_\s]+/g, "");
|
|
4634
|
+
const sessionModelNormalized = normalize(session.providerModel);
|
|
4635
|
+
modelConfig = provider.models.find((m) => {
|
|
4636
|
+
const modelIdNormalized = normalize(m.id);
|
|
4637
|
+
return modelIdNormalized === sessionModelNormalized || modelIdNormalized.includes(sessionModelNormalized) || sessionModelNormalized.includes(modelIdNormalized);
|
|
4638
|
+
});
|
|
4639
|
+
}
|
|
4640
|
+
maxTokens = modelConfig?.contextWindow ?? providerManager.getCurrentModelContext();
|
|
4641
|
+
} else {
|
|
4642
|
+
maxTokens = providerManager.getCurrentModelContext();
|
|
4643
|
+
}
|
|
4644
|
+
} else {
|
|
4645
|
+
maxTokens = providerManager.getCurrentModelContext();
|
|
4646
|
+
}
|
|
4647
|
+
const state = getSessionState(sessionId, maxTokens);
|
|
4853
4648
|
if (!state) {
|
|
4854
|
-
const config = getRuntimeConfig();
|
|
4855
4649
|
return {
|
|
4856
4650
|
currentTokens: 0,
|
|
4857
|
-
maxTokens
|
|
4651
|
+
maxTokens,
|
|
4858
4652
|
compactionCount: 0,
|
|
4859
4653
|
dangerZone: false,
|
|
4860
4654
|
canCompact: false
|
|
@@ -4905,7 +4699,8 @@ ${summary}`,
|
|
|
4905
4699
|
* Build a full Session object from DB session + EventStore state
|
|
4906
4700
|
*/
|
|
4907
4701
|
buildSessionFromDb(dbSession) {
|
|
4908
|
-
const
|
|
4702
|
+
const maxTokens = this.providerManager.getCurrentModelContext();
|
|
4703
|
+
const eventState = getSessionState(dbSession.id, maxTokens);
|
|
4909
4704
|
if (!eventState) {
|
|
4910
4705
|
return {
|
|
4911
4706
|
...dbSession,
|
|
@@ -4949,12 +4744,10 @@ ${summary}`,
|
|
|
4949
4744
|
criteria: eventState.criteria,
|
|
4950
4745
|
contextWindows: [],
|
|
4951
4746
|
// Derived from events, not stored separately
|
|
4952
|
-
executionState: null
|
|
4953
|
-
// No longer using execution state
|
|
4747
|
+
executionState: eventState.lastModeWithReminder ? { lastModeWithReminder: eventState.lastModeWithReminder } : null
|
|
4954
4748
|
};
|
|
4955
4749
|
}
|
|
4956
4750
|
};
|
|
4957
|
-
var sessionManager = new SessionManager();
|
|
4958
4751
|
|
|
4959
4752
|
// src/server/commands/registry.ts
|
|
4960
4753
|
import { readdir as readdir2, readFile as readFile2, writeFile as writeFile2, copyFile as copyFile2, mkdir as mkdir2, access as access3, unlink as unlink2 } from "fs/promises";
|
|
@@ -5509,8 +5302,10 @@ function createDevServerRoutes() {
|
|
|
5509
5302
|
router.get("/logs", (req, res) => {
|
|
5510
5303
|
const workdir = req.query["workdir"];
|
|
5511
5304
|
if (!workdir) return res.status(400).json({ error: "workdir required" });
|
|
5512
|
-
const
|
|
5513
|
-
|
|
5305
|
+
const offset = Math.max(0, parseInt(req.query["offset"]) || 0);
|
|
5306
|
+
const limit = Math.max(1, parseInt(req.query["limit"]) || Infinity);
|
|
5307
|
+
const result = devServerManager.getLogsSlice(workdir, offset, limit);
|
|
5308
|
+
res.json({ logs: result.logs, total: result.total, offset, limit });
|
|
5514
5309
|
});
|
|
5515
5310
|
router.get("/config", async (req, res) => {
|
|
5516
5311
|
const workdir = req.query["workdir"];
|
|
@@ -5536,6 +5331,44 @@ function createDevServerRoutes() {
|
|
|
5536
5331
|
return router;
|
|
5537
5332
|
}
|
|
5538
5333
|
|
|
5334
|
+
// src/server/routes/terminals.ts
|
|
5335
|
+
import { Router as Router6 } from "express";
|
|
5336
|
+
function createTerminalRoutes() {
|
|
5337
|
+
const router = Router6();
|
|
5338
|
+
router.get("/", (_req, res) => {
|
|
5339
|
+
const sessions = terminalManager.getAll();
|
|
5340
|
+
const response = sessions.map((s) => ({
|
|
5341
|
+
id: s.id,
|
|
5342
|
+
workdir: s.workdir
|
|
5343
|
+
}));
|
|
5344
|
+
res.json(response);
|
|
5345
|
+
});
|
|
5346
|
+
router.post("/", (req, res) => {
|
|
5347
|
+
const workdir = req.body?.workdir;
|
|
5348
|
+
const session = terminalManager.create(workdir);
|
|
5349
|
+
res.status(201).json({ id: session.id, workdir: session.workdir });
|
|
5350
|
+
});
|
|
5351
|
+
router.delete("/:id", (req, res) => {
|
|
5352
|
+
const { id } = req.params;
|
|
5353
|
+
const success = terminalManager.kill(id);
|
|
5354
|
+
if (success) {
|
|
5355
|
+
res.status(204).send();
|
|
5356
|
+
} else {
|
|
5357
|
+
res.status(404).json({ error: "Terminal not found" });
|
|
5358
|
+
}
|
|
5359
|
+
});
|
|
5360
|
+
router.get("/:id", (req, res) => {
|
|
5361
|
+
const { id } = req.params;
|
|
5362
|
+
const session = terminalManager.get(id);
|
|
5363
|
+
if (session) {
|
|
5364
|
+
res.json({ id: session.id, workdir: session.workdir });
|
|
5365
|
+
} else {
|
|
5366
|
+
res.status(404).json({ error: "Terminal not found" });
|
|
5367
|
+
}
|
|
5368
|
+
});
|
|
5369
|
+
return router;
|
|
5370
|
+
}
|
|
5371
|
+
|
|
5539
5372
|
// src/server/index.ts
|
|
5540
5373
|
var __dirname2 = dirname4(fileURLToPath4(import.meta.url));
|
|
5541
5374
|
async function createServerHandle(config) {
|
|
@@ -5548,8 +5381,8 @@ async function createServerHandle(config) {
|
|
|
5548
5381
|
await ensureDefaultCommands(configDir);
|
|
5549
5382
|
await ensureDefaultAgents(configDir);
|
|
5550
5383
|
await ensureDefaultWorkflows(configDir);
|
|
5551
|
-
const sessionManager2 = new SessionManager();
|
|
5552
5384
|
const providerManager = createProviderManager(config);
|
|
5385
|
+
const sessionManager = new SessionManager(providerManager);
|
|
5553
5386
|
const useMock = process.env["OPENFOX_MOCK_LLM"] === "true";
|
|
5554
5387
|
const getMockClient = useMock ? createMockLLMClient : null;
|
|
5555
5388
|
const getLLMClient = () => getMockClient ? getMockClient() : providerManager.getLLMClient();
|
|
@@ -5584,8 +5417,19 @@ async function createServerHandle(config) {
|
|
|
5584
5417
|
logger.warn("Could not auto-detect model, using config", { model: config.llm.model });
|
|
5585
5418
|
}
|
|
5586
5419
|
}
|
|
5420
|
+
const activeProvider = providerManager.getActiveProvider();
|
|
5421
|
+
if (activeProvider) {
|
|
5422
|
+
await providerManager.refreshProviderModels(activeProvider.id).catch((err) => {
|
|
5423
|
+
logger.debug("Startup model refetch failed", {
|
|
5424
|
+
providerId: activeProvider.id,
|
|
5425
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5426
|
+
});
|
|
5427
|
+
});
|
|
5428
|
+
}
|
|
5587
5429
|
}
|
|
5588
|
-
initLLM().catch(
|
|
5430
|
+
initLLM().catch(
|
|
5431
|
+
(err) => logger.error("LLM initialization failed", { error: err instanceof Error ? err.message : String(err) })
|
|
5432
|
+
);
|
|
5589
5433
|
const toolRegistry = createToolRegistry();
|
|
5590
5434
|
const app = express();
|
|
5591
5435
|
app.use(cors());
|
|
@@ -5594,39 +5438,317 @@ async function createServerHandle(config) {
|
|
|
5594
5438
|
res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5595
5439
|
});
|
|
5596
5440
|
app.get("/api/tools", (_req, res) => {
|
|
5597
|
-
|
|
5441
|
+
const tools = toolRegistry.tools.map((t) => ({
|
|
5442
|
+
name: t.name,
|
|
5443
|
+
actions: t.permittedActions || []
|
|
5444
|
+
}));
|
|
5445
|
+
res.json({ tools });
|
|
5446
|
+
});
|
|
5447
|
+
app.get("/api/projects", async (_req, res) => {
|
|
5448
|
+
const { listProjects } = await import("./projects-GSMMEVSO.js");
|
|
5449
|
+
const projects = listProjects();
|
|
5450
|
+
res.json({ projects });
|
|
5451
|
+
});
|
|
5452
|
+
app.post("/api/projects", async (req, res) => {
|
|
5453
|
+
const { name, workdir } = req.body;
|
|
5454
|
+
if (!name || !workdir) {
|
|
5455
|
+
return res.status(400).json({ error: "name and workdir are required" });
|
|
5456
|
+
}
|
|
5457
|
+
const { createDirectoryWithGit } = await import("./project-creator-KAQXMZCT.js");
|
|
5458
|
+
const project = await createDirectoryWithGit(name, workdir);
|
|
5459
|
+
res.status(201).json({ project });
|
|
5460
|
+
});
|
|
5461
|
+
app.get("/api/projects/:id", async (req, res) => {
|
|
5462
|
+
const { getProject: getProject2 } = await import("./projects-GSMMEVSO.js");
|
|
5463
|
+
const project = getProject2(req.params.id);
|
|
5464
|
+
if (!project) {
|
|
5465
|
+
return res.status(404).json({ error: "Project not found" });
|
|
5466
|
+
}
|
|
5467
|
+
res.json({ project });
|
|
5468
|
+
});
|
|
5469
|
+
app.put("/api/projects/:id", async (req, res) => {
|
|
5470
|
+
const { updateProject } = await import("./projects-GSMMEVSO.js");
|
|
5471
|
+
const { name, customInstructions } = req.body;
|
|
5472
|
+
const updates = {};
|
|
5473
|
+
if (name !== void 0) updates.name = name;
|
|
5474
|
+
if (customInstructions !== void 0) updates.customInstructions = customInstructions;
|
|
5475
|
+
const updated = updateProject(req.params.id, updates);
|
|
5476
|
+
if (!updated) {
|
|
5477
|
+
return res.status(404).json({ error: "Project not found" });
|
|
5478
|
+
}
|
|
5479
|
+
res.json({ project: updated });
|
|
5480
|
+
});
|
|
5481
|
+
app.delete("/api/projects/:id", async (req, res) => {
|
|
5482
|
+
const { getProject: getProject2, deleteProject } = await import("./projects-GSMMEVSO.js");
|
|
5483
|
+
const project = getProject2(req.params.id);
|
|
5484
|
+
if (!project) {
|
|
5485
|
+
return res.status(404).json({ error: "Project not found" });
|
|
5486
|
+
}
|
|
5487
|
+
deleteProject(req.params.id);
|
|
5488
|
+
res.json({ success: true });
|
|
5489
|
+
});
|
|
5490
|
+
app.get("/api/sessions", async (req, res) => {
|
|
5491
|
+
const { getRecentUserPromptsForSession } = await import("./events-G6CKHDNZ.js");
|
|
5492
|
+
const projectId = req.query["projectId"];
|
|
5493
|
+
const limit = Math.min(parseInt(req.query["limit"]) || 20, 100);
|
|
5494
|
+
const offset = parseInt(req.query["offset"]) || 0;
|
|
5495
|
+
let sessions;
|
|
5496
|
+
let hasMore = false;
|
|
5497
|
+
if (projectId) {
|
|
5498
|
+
const result = sessionManager.listSessionsByProject(projectId, limit, offset);
|
|
5499
|
+
sessions = result.sessions;
|
|
5500
|
+
hasMore = result.hasMore;
|
|
5501
|
+
} else {
|
|
5502
|
+
sessions = sessionManager.listSessions();
|
|
5503
|
+
}
|
|
5504
|
+
const sessionsWithPrompts = sessions.map((session) => ({
|
|
5505
|
+
...session,
|
|
5506
|
+
recentUserPrompts: getRecentUserPromptsForSession(session.id, 10)
|
|
5507
|
+
}));
|
|
5508
|
+
res.json({ sessions: sessionsWithPrompts, hasMore });
|
|
5598
5509
|
});
|
|
5599
5510
|
app.post("/api/sessions", async (req, res) => {
|
|
5600
|
-
const {
|
|
5601
|
-
|
|
5511
|
+
const { projectId, title } = req.body;
|
|
5512
|
+
if (!projectId) {
|
|
5513
|
+
return res.status(400).json({ error: "projectId is required" });
|
|
5514
|
+
}
|
|
5515
|
+
const project = sessionManager.getProject(projectId);
|
|
5516
|
+
if (!project) {
|
|
5517
|
+
return res.status(404).json({ error: "Project not found" });
|
|
5518
|
+
}
|
|
5519
|
+
const { providerId, model } = parseDefaultModelSelection(config.defaultModelSelection);
|
|
5520
|
+
const session = sessionManager.createSession(projectId, title, providerId ?? null, model ?? null);
|
|
5602
5521
|
res.status(201).json({ session });
|
|
5603
5522
|
});
|
|
5604
|
-
app.get("/api/sessions/:id", (req, res) => {
|
|
5605
|
-
const
|
|
5523
|
+
app.get("/api/sessions/:id", async (req, res) => {
|
|
5524
|
+
const { getEventStore: getEventStore2 } = await import("./events-G6CKHDNZ.js");
|
|
5525
|
+
const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-O52L65VN.js");
|
|
5526
|
+
const session = sessionManager.getSession(req.params.id);
|
|
5606
5527
|
if (!session) {
|
|
5607
5528
|
return res.status(404).json({ error: "Session not found" });
|
|
5608
5529
|
}
|
|
5609
|
-
|
|
5530
|
+
const eventStore = getEventStore2();
|
|
5531
|
+
const events = eventStore.getEvents(req.params.id);
|
|
5532
|
+
const messages = buildMessagesFromStoredEvents2(events);
|
|
5533
|
+
const contextState = sessionManager.getContextState(req.params.id);
|
|
5534
|
+
const queueState = sessionManager.getQueueState(req.params.id);
|
|
5535
|
+
res.json({ session, messages, contextState, queueState });
|
|
5610
5536
|
});
|
|
5611
5537
|
app.delete("/api/sessions/:id", (req, res) => {
|
|
5612
|
-
|
|
5538
|
+
const session = sessionManager.getSession(req.params["id"]);
|
|
5539
|
+
if (!session) {
|
|
5540
|
+
return res.status(404).json({ error: "Session not found" });
|
|
5541
|
+
}
|
|
5542
|
+
sessionManager.deleteSession(req.params["id"]);
|
|
5613
5543
|
res.json({ success: true });
|
|
5614
5544
|
});
|
|
5615
5545
|
app.delete("/api/projects/:projectId/sessions", (req, res) => {
|
|
5616
5546
|
const projectId = req.params["projectId"];
|
|
5617
|
-
const project =
|
|
5547
|
+
const project = sessionManager.getProject(projectId);
|
|
5618
5548
|
if (!project) {
|
|
5619
5549
|
return res.status(404).json({ error: "Project not found" });
|
|
5620
5550
|
}
|
|
5621
|
-
|
|
5551
|
+
sessionManager.deleteAllSessions(projectId, project.workdir);
|
|
5552
|
+
res.json({ success: true });
|
|
5553
|
+
});
|
|
5554
|
+
app.post("/api/sessions/:id/provider", async (req, res) => {
|
|
5555
|
+
const { getEventStore: getEventStore2 } = await import("./events-G6CKHDNZ.js");
|
|
5556
|
+
const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-O52L65VN.js");
|
|
5557
|
+
const sessionId = req.params.id;
|
|
5558
|
+
const session = sessionManager.getSession(sessionId);
|
|
5559
|
+
if (!session) {
|
|
5560
|
+
return res.status(404).json({ error: "Session not found" });
|
|
5561
|
+
}
|
|
5562
|
+
const { providerId, model } = req.body;
|
|
5563
|
+
if (!providerId) {
|
|
5564
|
+
return res.status(400).json({ error: "providerId is required" });
|
|
5565
|
+
}
|
|
5566
|
+
sessionManager.setSessionProvider(sessionId, providerId, model ?? "auto");
|
|
5567
|
+
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-GIFCEJDU.js");
|
|
5568
|
+
const globalConfig = await loadGlobalConfig(config.mode ?? "production");
|
|
5569
|
+
const updatedConfig = setDefaultModelSelection(globalConfig, providerId, model ?? "auto");
|
|
5570
|
+
await saveGlobalConfig(config.mode ?? "production", updatedConfig);
|
|
5571
|
+
config.defaultModelSelection = updatedConfig.defaultModelSelection;
|
|
5572
|
+
const contextState = sessionManager.getContextState(sessionId);
|
|
5573
|
+
const eventStore = getEventStore2();
|
|
5574
|
+
const events = eventStore.getEvents(sessionId);
|
|
5575
|
+
const messages = buildMessagesFromStoredEvents2(events);
|
|
5576
|
+
const updatedSession = sessionManager.getSession(sessionId);
|
|
5577
|
+
res.json({ session: updatedSession, messages, contextState });
|
|
5578
|
+
});
|
|
5579
|
+
app.put("/api/sessions/:id/criteria", async (req, res) => {
|
|
5580
|
+
const sessionId = req.params.id;
|
|
5581
|
+
const session = sessionManager.getSession(sessionId);
|
|
5582
|
+
if (!session) {
|
|
5583
|
+
return res.status(404).json({ error: "Session not found" });
|
|
5584
|
+
}
|
|
5585
|
+
const { criteria } = req.body;
|
|
5586
|
+
if (!Array.isArray(criteria)) {
|
|
5587
|
+
return res.status(400).json({ error: "criteria is required and must be an array" });
|
|
5588
|
+
}
|
|
5589
|
+
sessionManager.setCriteria(sessionId, criteria);
|
|
5590
|
+
res.json({ success: true });
|
|
5591
|
+
});
|
|
5592
|
+
app.put("/api/sessions/:id/mode", async (req, res) => {
|
|
5593
|
+
const { getEventStore: getEventStore2 } = await import("./events-G6CKHDNZ.js");
|
|
5594
|
+
const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-O52L65VN.js");
|
|
5595
|
+
const sessionId = req.params.id;
|
|
5596
|
+
const session = sessionManager.getSession(sessionId);
|
|
5597
|
+
if (!session) {
|
|
5598
|
+
return res.status(404).json({ error: "Session not found" });
|
|
5599
|
+
}
|
|
5600
|
+
const { mode } = req.body;
|
|
5601
|
+
if (!mode || !["planner", "builder"].includes(mode)) {
|
|
5602
|
+
return res.status(400).json({ error: 'mode is required and must be "planner" or "builder"' });
|
|
5603
|
+
}
|
|
5604
|
+
sessionManager.setMode(sessionId, mode);
|
|
5605
|
+
const eventStore = getEventStore2();
|
|
5606
|
+
eventStore.append(sessionId, { type: "mode.changed", data: { mode, auto: false } });
|
|
5607
|
+
const events = eventStore.getEvents(sessionId);
|
|
5608
|
+
const messages = buildMessagesFromStoredEvents2(events);
|
|
5609
|
+
const updatedSession = sessionManager.getSession(sessionId);
|
|
5610
|
+
res.json({ session: updatedSession, messages });
|
|
5611
|
+
});
|
|
5612
|
+
app.put("/api/sessions/:id/danger-level", async (req, res) => {
|
|
5613
|
+
const sessionId = req.params.id;
|
|
5614
|
+
const session = sessionManager.getSession(sessionId);
|
|
5615
|
+
if (!session) {
|
|
5616
|
+
return res.status(404).json({ error: "Session not found" });
|
|
5617
|
+
}
|
|
5618
|
+
const { dangerLevel } = req.body;
|
|
5619
|
+
if (!dangerLevel || !["normal", "dangerous"].includes(dangerLevel)) {
|
|
5620
|
+
return res.status(400).json({ error: 'dangerLevel is required and must be "normal" or "dangerous"' });
|
|
5621
|
+
}
|
|
5622
|
+
sessionManager.setDangerLevel(sessionId, dangerLevel);
|
|
5623
|
+
const updatedSession = sessionManager.getSession(sessionId);
|
|
5624
|
+
res.json({ session: updatedSession });
|
|
5625
|
+
});
|
|
5626
|
+
app.post("/api/sessions/:id/confirm-path", async (req, res) => {
|
|
5627
|
+
const sessionId = req.params.id;
|
|
5628
|
+
const { callId, approved, alwaysAllow } = req.body;
|
|
5629
|
+
if (!callId || approved === void 0) {
|
|
5630
|
+
return res.status(400).json({ error: "callId and approved are required" });
|
|
5631
|
+
}
|
|
5632
|
+
const { providePathConfirmation: providePathConfirmation2 } = await import("./tools-SGBQE736.js");
|
|
5633
|
+
const result = providePathConfirmation2(callId, approved, alwaysAllow);
|
|
5634
|
+
if (!result.found) {
|
|
5635
|
+
return res.status(404).json({ error: "No pending path confirmation with that ID" });
|
|
5636
|
+
}
|
|
5637
|
+
const { getEventStore: getEventStore2 } = await import("./events-G6CKHDNZ.js");
|
|
5638
|
+
const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2, foldPendingConfirmations: foldPendingConfirmations2 } = await import("./folding-O52L65VN.js");
|
|
5639
|
+
const { createSessionStateMessage: createSessionStateMessage2 } = await import("./protocol-WQENDK72.js");
|
|
5640
|
+
const eventStore = getEventStore2();
|
|
5641
|
+
const events = eventStore.getEvents(sessionId);
|
|
5642
|
+
const messages = buildMessagesFromStoredEvents2(events);
|
|
5643
|
+
const pendingConfirmations = foldPendingConfirmations2(events);
|
|
5644
|
+
const session = sessionManager.getSession(sessionId);
|
|
5645
|
+
if (session) {
|
|
5646
|
+
const stateMsg = createSessionStateMessage2(session, messages, pendingConfirmations);
|
|
5647
|
+
wssExports.broadcastForSession(sessionId, { ...stateMsg, sessionId });
|
|
5648
|
+
}
|
|
5649
|
+
res.json({ success: true });
|
|
5650
|
+
});
|
|
5651
|
+
app.post("/api/sessions/:id/answer", async (req, res) => {
|
|
5652
|
+
const sessionId = req.params.id;
|
|
5653
|
+
const { callId, answer } = req.body;
|
|
5654
|
+
if (!callId || !answer) {
|
|
5655
|
+
return res.status(400).json({ error: "callId and answer are required" });
|
|
5656
|
+
}
|
|
5657
|
+
const { provideAnswer: provideAnswer2 } = await import("./tools-SGBQE736.js");
|
|
5658
|
+
const found = provideAnswer2(callId, answer);
|
|
5659
|
+
if (!found) {
|
|
5660
|
+
return res.status(404).json({ error: "No pending question with that ID" });
|
|
5661
|
+
}
|
|
5662
|
+
res.json({ success: true });
|
|
5663
|
+
});
|
|
5664
|
+
app.post("/api/sessions/:id/message", (req, res) => {
|
|
5665
|
+
const sessionId = req.params.id;
|
|
5666
|
+
const session = sessionManager.getSession(sessionId);
|
|
5667
|
+
if (!session) {
|
|
5668
|
+
return res.status(404).json({ error: "Session not found" });
|
|
5669
|
+
}
|
|
5670
|
+
const { content, attachments, messageKind } = req.body;
|
|
5671
|
+
if (!content?.trim()) {
|
|
5672
|
+
return res.status(400).json({ error: "content is required" });
|
|
5673
|
+
}
|
|
5674
|
+
sessionManager.queueMessage(sessionId, "asap", content, attachments, messageKind);
|
|
5675
|
+
res.json({ success: true, queueState: sessionManager.getQueueState(sessionId) });
|
|
5676
|
+
});
|
|
5677
|
+
app.delete("/api/sessions/:id/queue/:queueId", (req, res) => {
|
|
5678
|
+
const sessionId = req.params.id;
|
|
5679
|
+
const { queueId } = req.params;
|
|
5680
|
+
const session = sessionManager.getSession(sessionId);
|
|
5681
|
+
if (!session) {
|
|
5682
|
+
return res.status(404).json({ error: "Session not found" });
|
|
5683
|
+
}
|
|
5684
|
+
sessionManager.cancelQueuedMessage(sessionId, queueId);
|
|
5685
|
+
res.json({ success: true, queueState: sessionManager.getQueueState(sessionId) });
|
|
5686
|
+
});
|
|
5687
|
+
app.post("/api/sessions/:id/stop", async (req, res) => {
|
|
5688
|
+
const sessionId = req.params.id;
|
|
5689
|
+
const session = sessionManager.getSession(sessionId);
|
|
5690
|
+
if (!session) {
|
|
5691
|
+
return res.status(404).json({ error: "Session not found" });
|
|
5692
|
+
}
|
|
5693
|
+
const { stopSessionExecution } = await import("./chat-handler-T7SLWZ4E.js");
|
|
5694
|
+
const { cancelQuestionsForSession: cancelQuestionsForSession2, cancelPathConfirmationsForSession: cancelPathConfirmationsForSession2 } = await import("./tools-SGBQE736.js");
|
|
5695
|
+
stopSessionExecution(sessionId, sessionManager);
|
|
5696
|
+
abortSession(sessionId);
|
|
5697
|
+
cancelQuestionsForSession2(sessionId, "Session stopped by user");
|
|
5698
|
+
cancelPathConfirmationsForSession2(sessionId, "Session stopped by user");
|
|
5699
|
+
sessionManager.clearMessageQueue(sessionId);
|
|
5700
|
+
const eventStore = (await import("./events-G6CKHDNZ.js")).getEventStore();
|
|
5701
|
+
eventStore.append(sessionId, { type: "running.changed", data: { isRunning: false } });
|
|
5622
5702
|
res.json({ success: true });
|
|
5623
5703
|
});
|
|
5704
|
+
app.post("/api/sessions/:id/chat", async (req, res) => {
|
|
5705
|
+
const sessionId = req.params.id;
|
|
5706
|
+
const session = sessionManager.getSession(sessionId);
|
|
5707
|
+
if (!session) {
|
|
5708
|
+
return res.status(404).json({ error: "Session not found" });
|
|
5709
|
+
}
|
|
5710
|
+
const { content, attachments, messageKind, isSystemGenerated } = req.body;
|
|
5711
|
+
if (!content) {
|
|
5712
|
+
return res.status(400).json({ error: "content is required" });
|
|
5713
|
+
}
|
|
5714
|
+
if (session.isRunning) {
|
|
5715
|
+
return res.status(409).json({ error: "Session is already running" });
|
|
5716
|
+
}
|
|
5717
|
+
res.json({ accepted: true, sessionId });
|
|
5718
|
+
});
|
|
5719
|
+
app.post("/api/sessions/:id/continue", async (req, res) => {
|
|
5720
|
+
const sessionId = req.params.id;
|
|
5721
|
+
const session = sessionManager.getSession(sessionId);
|
|
5722
|
+
if (!session) {
|
|
5723
|
+
return res.status(404).json({ error: "Session not found" });
|
|
5724
|
+
}
|
|
5725
|
+
if (session.isRunning) {
|
|
5726
|
+
return res.status(409).json({ error: "Session is already running" });
|
|
5727
|
+
}
|
|
5728
|
+
res.json({ accepted: true });
|
|
5729
|
+
});
|
|
5730
|
+
app.get("/api/settings/:key", async (req, res) => {
|
|
5731
|
+
const { getSetting } = await import("./settings-NCLN4JDZ.js");
|
|
5732
|
+
const key = req.params.key;
|
|
5733
|
+
const value = getSetting(key);
|
|
5734
|
+
res.json({ key, value });
|
|
5735
|
+
});
|
|
5736
|
+
app.put("/api/settings/:key", async (req, res) => {
|
|
5737
|
+
const { setSetting } = await import("./settings-NCLN4JDZ.js");
|
|
5738
|
+
const key = req.params.key;
|
|
5739
|
+
const { value } = req.body;
|
|
5740
|
+
if (value === void 0) {
|
|
5741
|
+
return res.status(400).json({ error: "value is required" });
|
|
5742
|
+
}
|
|
5743
|
+
setSetting(key, value);
|
|
5744
|
+
res.json({ key, value });
|
|
5745
|
+
});
|
|
5624
5746
|
app.get("/api/config", (_req, res) => {
|
|
5625
5747
|
const llmClient = getLLMClient();
|
|
5626
5748
|
const activeProvider = providerManager.getActiveProvider();
|
|
5627
5749
|
res.json({
|
|
5628
5750
|
model: llmClient.getModel(),
|
|
5629
|
-
maxContext:
|
|
5751
|
+
maxContext: providerManager.getCurrentModelContext(),
|
|
5630
5752
|
llmUrl: activeProvider?.url ?? config.llm.baseUrl,
|
|
5631
5753
|
llmStatus: getLlmStatus(),
|
|
5632
5754
|
backend: llmClient.getBackend(),
|
|
@@ -5646,10 +5768,20 @@ async function createServerHandle(config) {
|
|
|
5646
5768
|
const detected = await detectModel(baseUrl);
|
|
5647
5769
|
if (detected) {
|
|
5648
5770
|
llmClient.setModel(detected);
|
|
5649
|
-
return res.json({
|
|
5771
|
+
return res.json({
|
|
5772
|
+
model: detected,
|
|
5773
|
+
source: "detected",
|
|
5774
|
+
llmStatus: getLlmStatus(),
|
|
5775
|
+
backend: llmClient.getBackend()
|
|
5776
|
+
});
|
|
5650
5777
|
}
|
|
5651
5778
|
}
|
|
5652
|
-
res.json({
|
|
5779
|
+
res.json({
|
|
5780
|
+
model: llmClient.getModel(),
|
|
5781
|
+
source: "cached",
|
|
5782
|
+
llmStatus: getLlmStatus(),
|
|
5783
|
+
backend: llmClient.getBackend()
|
|
5784
|
+
});
|
|
5653
5785
|
});
|
|
5654
5786
|
app.get("/api/providers", (_req, res) => {
|
|
5655
5787
|
const providers = providerManager.getProviders().map((p) => ({
|
|
@@ -5674,7 +5806,7 @@ async function createServerHandle(config) {
|
|
|
5674
5806
|
return res.status(400).json({ error: result.error });
|
|
5675
5807
|
}
|
|
5676
5808
|
const llmClient = getLLMClient();
|
|
5677
|
-
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-
|
|
5809
|
+
const { loadGlobalConfig, saveGlobalConfig, setDefaultModelSelection } = await import("./config-GIFCEJDU.js");
|
|
5678
5810
|
const globalConfig = await loadGlobalConfig(config.mode ?? "production");
|
|
5679
5811
|
const updatedConfig = setDefaultModelSelection(globalConfig, id, llmClient.getModel());
|
|
5680
5812
|
await saveGlobalConfig(config.mode ?? "production", updatedConfig);
|
|
@@ -5685,12 +5817,65 @@ async function createServerHandle(config) {
|
|
|
5685
5817
|
backend: llmClient.getBackend()
|
|
5686
5818
|
});
|
|
5687
5819
|
});
|
|
5820
|
+
app.post("/api/providers/:id/models/:modelId", async (req, res) => {
|
|
5821
|
+
const { id, modelId } = req.params;
|
|
5822
|
+
const body = req.body;
|
|
5823
|
+
if (!body.contextWindow) {
|
|
5824
|
+
return res.status(400).json({ error: "contextWindow is required" });
|
|
5825
|
+
}
|
|
5826
|
+
const result = await providerManager.updateModelContext(id, modelId, body.contextWindow);
|
|
5827
|
+
if (!result.success) {
|
|
5828
|
+
return res.status(400).json({ error: result.error });
|
|
5829
|
+
}
|
|
5830
|
+
const { loadGlobalConfig, saveGlobalConfig } = await import("./config-GIFCEJDU.js");
|
|
5831
|
+
const globalConfig = await loadGlobalConfig(config.mode ?? "production");
|
|
5832
|
+
const updatedProviders = providerManager.getProviders();
|
|
5833
|
+
const updatedConfig = {
|
|
5834
|
+
...globalConfig,
|
|
5835
|
+
providers: updatedProviders,
|
|
5836
|
+
activeProviderId: providerManager.getActiveProviderId(),
|
|
5837
|
+
defaultModelSelection: providerManager.getActiveProviderId() ? `${providerManager.getActiveProviderId()}/${providerManager.getCurrentModel()}` : void 0
|
|
5838
|
+
};
|
|
5839
|
+
await saveGlobalConfig(config.mode ?? "production", updatedConfig);
|
|
5840
|
+
let contextState = null;
|
|
5841
|
+
const sessions = sessionManager.listSessions();
|
|
5842
|
+
if (sessions.length > 0) {
|
|
5843
|
+
for (const session of sessions) {
|
|
5844
|
+
const sessionProviderId = session.providerId || providerManager.getActiveProviderId();
|
|
5845
|
+
if (sessionProviderId === id) {
|
|
5846
|
+
contextState = sessionManager.getContextState(session.id);
|
|
5847
|
+
break;
|
|
5848
|
+
}
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
res.json({
|
|
5852
|
+
success: true,
|
|
5853
|
+
providerId: id,
|
|
5854
|
+
modelId,
|
|
5855
|
+
contextWindow: body.contextWindow,
|
|
5856
|
+
contextState
|
|
5857
|
+
});
|
|
5858
|
+
});
|
|
5859
|
+
app.post("/api/providers/:id/refresh", async (req, res) => {
|
|
5860
|
+
const { id } = req.params;
|
|
5861
|
+
const result = await providerManager.refreshProviderModels(id);
|
|
5862
|
+
if (!result.success) {
|
|
5863
|
+
return res.status(400).json({ error: result.error });
|
|
5864
|
+
}
|
|
5865
|
+
const updatedProvider = providerManager.getProviders().find((p) => p.id === id);
|
|
5866
|
+
res.json({
|
|
5867
|
+
success: true,
|
|
5868
|
+
providerId: id,
|
|
5869
|
+
models: updatedProvider?.models ?? []
|
|
5870
|
+
});
|
|
5871
|
+
});
|
|
5688
5872
|
app.use("/api/skills", createSkillRoutes(configDir));
|
|
5689
5873
|
app.use("/api/commands", createCommandRoutes(configDir));
|
|
5690
5874
|
app.use("/api/agents", createAgentRoutes(configDir));
|
|
5691
5875
|
app.use("/api/workflows", createWorkflowRoutes(configDir, config));
|
|
5692
5876
|
app.use("/api/dev-server", createDevServerRoutes());
|
|
5693
|
-
|
|
5877
|
+
app.use("/api/terminals", createTerminalRoutes());
|
|
5878
|
+
const { getCurrentBranch } = await import("./branch.api-XADFZZAA.js");
|
|
5694
5879
|
app.get("/api/branch", async (req, res) => {
|
|
5695
5880
|
await getCurrentBranch(req, res);
|
|
5696
5881
|
});
|
|
@@ -5756,11 +5941,14 @@ async function createServerHandle(config) {
|
|
|
5756
5941
|
res.status(404).send("Not found");
|
|
5757
5942
|
});
|
|
5758
5943
|
});
|
|
5759
|
-
app.use(
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5944
|
+
app.use(
|
|
5945
|
+
"/sounds",
|
|
5946
|
+
express.static(join4(webDir, "public", "sounds"), {
|
|
5947
|
+
setHeaders: (res) => {
|
|
5948
|
+
res.set("Content-Type", "audio/mpeg");
|
|
5949
|
+
}
|
|
5950
|
+
})
|
|
5951
|
+
);
|
|
5764
5952
|
app.get("/*path", (req, res) => {
|
|
5765
5953
|
if (req.path.startsWith("/api/")) {
|
|
5766
5954
|
return;
|
|
@@ -5774,13 +5962,16 @@ async function createServerHandle(config) {
|
|
|
5774
5962
|
}
|
|
5775
5963
|
if (!isDev) {
|
|
5776
5964
|
const distWebDir = resolve(__dirname2, "web");
|
|
5777
|
-
app.use(
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5965
|
+
app.use(
|
|
5966
|
+
"/assets",
|
|
5967
|
+
express.static(join4(distWebDir, "assets"), {
|
|
5968
|
+
setHeaders: (res, filepath) => {
|
|
5969
|
+
if (filepath.endsWith(".css")) {
|
|
5970
|
+
res.set("Content-Type", "text/css");
|
|
5971
|
+
}
|
|
5781
5972
|
}
|
|
5782
|
-
}
|
|
5783
|
-
|
|
5973
|
+
})
|
|
5974
|
+
);
|
|
5784
5975
|
app.get("/fox.svg", (_req, res) => {
|
|
5785
5976
|
readFile3(join4(distWebDir, "fox.svg")).then((content) => {
|
|
5786
5977
|
res.set("Content-Type", "image/svg+xml");
|
|
@@ -5789,11 +5980,14 @@ async function createServerHandle(config) {
|
|
|
5789
5980
|
res.status(404).send("Not found");
|
|
5790
5981
|
});
|
|
5791
5982
|
});
|
|
5792
|
-
app.use(
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5983
|
+
app.use(
|
|
5984
|
+
"/sounds",
|
|
5985
|
+
express.static(join4(distWebDir, "sounds"), {
|
|
5986
|
+
setHeaders: (res) => {
|
|
5987
|
+
res.set("Content-Type", "audio/mpeg");
|
|
5988
|
+
}
|
|
5989
|
+
})
|
|
5990
|
+
);
|
|
5797
5991
|
app.get("/", (_req, res) => {
|
|
5798
5992
|
readFile3(join4(distWebDir, "index.html"), "utf-8").then((content) => res.send(content)).catch(() => {
|
|
5799
5993
|
res.status(404).send("Web UI not built. Run `npm run build:web`");
|
|
@@ -5809,10 +6003,35 @@ async function createServerHandle(config) {
|
|
|
5809
6003
|
});
|
|
5810
6004
|
}
|
|
5811
6005
|
const httpServer = createHttpServer(app);
|
|
5812
|
-
const
|
|
6006
|
+
const wssExports = createWebSocketServer(
|
|
6007
|
+
httpServer,
|
|
6008
|
+
config,
|
|
6009
|
+
getLLMClient,
|
|
6010
|
+
() => providerManager.getActiveProvider(),
|
|
6011
|
+
sessionManager,
|
|
6012
|
+
providerManager
|
|
6013
|
+
);
|
|
6014
|
+
const wss = wssExports.wss;
|
|
6015
|
+
const { QueueProcessor } = await import("./processor-KLHKDILM.js");
|
|
6016
|
+
const queueProcessor = new QueueProcessor({
|
|
6017
|
+
sessionManager,
|
|
6018
|
+
providerManager,
|
|
6019
|
+
getLLMClient,
|
|
6020
|
+
getActiveProvider: () => providerManager.getActiveProvider(),
|
|
6021
|
+
broadcastForSession: wssExports.broadcastForSession
|
|
6022
|
+
});
|
|
6023
|
+
queueProcessor.start();
|
|
6024
|
+
const abortSession = (sessionId) => {
|
|
6025
|
+
const aborted = wssExports.abortSession(sessionId) || queueProcessor.abortSession(sessionId);
|
|
6026
|
+
if (aborted) {
|
|
6027
|
+
sessionManager.setRunning(sessionId, false);
|
|
6028
|
+
wssExports.broadcastForSession(sessionId, { type: "session.running", payload: { isRunning: false } });
|
|
6029
|
+
}
|
|
6030
|
+
return aborted;
|
|
6031
|
+
};
|
|
5813
6032
|
return {
|
|
5814
6033
|
httpServer,
|
|
5815
|
-
ctx: { config, sessionManager
|
|
6034
|
+
ctx: { config, sessionManager, llmClient: getLLMClient(), toolRegistry, providerManager },
|
|
5816
6035
|
start: (port) => new Promise((resolve2, reject) => {
|
|
5817
6036
|
const listenPort = port ?? config.server.port;
|
|
5818
6037
|
const host = config.server.host;
|
|
@@ -5863,4 +6082,4 @@ export {
|
|
|
5863
6082
|
createServerHandle,
|
|
5864
6083
|
createServer
|
|
5865
6084
|
};
|
|
5866
|
-
//# sourceMappingURL=chunk-
|
|
6085
|
+
//# sourceMappingURL=chunk-RAZQTQK2.js.map
|