chainlesschain 0.40.2 → 0.40.3
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/README.md +22 -15
- package/package.json +1 -1
- package/src/lib/agent-core.js +861 -0
- package/src/lib/chat-core.js +177 -0
- package/src/lib/interaction-adapter.js +177 -0
- package/src/lib/interactive-planner.js +524 -0
- package/src/lib/llm-providers.js +9 -1
- package/src/lib/slot-filler.js +465 -0
- package/src/lib/task-model-selector.js +5 -5
- package/src/lib/ws-agent-handler.js +403 -0
- package/src/lib/ws-chat-handler.js +145 -0
- package/src/lib/ws-server.js +280 -1
- package/src/lib/ws-session-manager.js +363 -0
- package/src/repl/agent-repl.js +159 -11
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Agent Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles agent session messages over WebSocket. Consumes agent-core's
|
|
5
|
+
* agentLoop generator and routes events to the client via the interaction
|
|
6
|
+
* adapter.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { agentLoop, formatToolArgs } from "./agent-core.js";
|
|
10
|
+
import { detectTaskType, selectModelForTask } from "./task-model-selector.js";
|
|
11
|
+
import { PlanState } from "./plan-mode.js";
|
|
12
|
+
|
|
13
|
+
export class WSAgentHandler {
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} options
|
|
16
|
+
* @param {import("./ws-session-manager.js").Session} options.session
|
|
17
|
+
* @param {import("./interaction-adapter.js").WebSocketInteractionAdapter} options.interaction
|
|
18
|
+
* @param {object} [options.db]
|
|
19
|
+
*/
|
|
20
|
+
constructor({ session, interaction, db }) {
|
|
21
|
+
this.session = session;
|
|
22
|
+
this.interaction = interaction;
|
|
23
|
+
this.db = db || null;
|
|
24
|
+
this._processing = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Handle a user message — one turn of the agentic loop.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} userMessage
|
|
31
|
+
* @param {string} [requestId] - id from ws message for response correlation
|
|
32
|
+
*/
|
|
33
|
+
async handleMessage(userMessage, requestId) {
|
|
34
|
+
if (this._processing) {
|
|
35
|
+
this.interaction.emit("error", {
|
|
36
|
+
requestId,
|
|
37
|
+
code: "BUSY",
|
|
38
|
+
message: "Session is currently processing a message",
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this._processing = true;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const { session } = this;
|
|
47
|
+
|
|
48
|
+
// Add user message
|
|
49
|
+
session.messages.push({ role: "user", content: userMessage });
|
|
50
|
+
|
|
51
|
+
// Auto-select model based on task type
|
|
52
|
+
let activeModel = session.model;
|
|
53
|
+
const taskDetection = detectTaskType(userMessage);
|
|
54
|
+
if (taskDetection.confidence > 0.3) {
|
|
55
|
+
const recommended = selectModelForTask(
|
|
56
|
+
session.provider,
|
|
57
|
+
taskDetection.taskType,
|
|
58
|
+
);
|
|
59
|
+
if (recommended && recommended !== activeModel) {
|
|
60
|
+
activeModel = recommended;
|
|
61
|
+
this.interaction.emit("model-switch", {
|
|
62
|
+
requestId,
|
|
63
|
+
from: session.model,
|
|
64
|
+
to: activeModel,
|
|
65
|
+
reason: taskDetection.name,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Run agent loop
|
|
71
|
+
const loopOptions = {
|
|
72
|
+
provider: session.provider,
|
|
73
|
+
model: activeModel,
|
|
74
|
+
baseUrl: session.baseUrl || "http://localhost:11434",
|
|
75
|
+
apiKey: session.apiKey,
|
|
76
|
+
contextEngine: session.contextEngine,
|
|
77
|
+
hookDb: this.db,
|
|
78
|
+
cwd: session.projectRoot,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
for await (const event of agentLoop(session.messages, loopOptions)) {
|
|
82
|
+
switch (event.type) {
|
|
83
|
+
case "tool-executing":
|
|
84
|
+
this.interaction.emit("tool-executing", {
|
|
85
|
+
requestId,
|
|
86
|
+
tool: event.tool,
|
|
87
|
+
args: event.args,
|
|
88
|
+
display: formatToolArgs(event.tool, event.args),
|
|
89
|
+
});
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
case "tool-result":
|
|
93
|
+
this.interaction.emit("tool-result", {
|
|
94
|
+
requestId,
|
|
95
|
+
tool: event.tool,
|
|
96
|
+
result: event.result,
|
|
97
|
+
error: event.error,
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case "response-complete":
|
|
102
|
+
if (event.content) {
|
|
103
|
+
session.messages.push({
|
|
104
|
+
role: "assistant",
|
|
105
|
+
content: event.content,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
this.interaction.emit("response-complete", {
|
|
109
|
+
requestId,
|
|
110
|
+
content: event.content,
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Update last activity
|
|
117
|
+
session.lastActivity = new Date().toISOString();
|
|
118
|
+
} catch (err) {
|
|
119
|
+
this.interaction.emit("error", {
|
|
120
|
+
requestId,
|
|
121
|
+
code: "AGENT_ERROR",
|
|
122
|
+
message: err.message,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Record error in context engine
|
|
126
|
+
if (this.session.contextEngine) {
|
|
127
|
+
this.session.contextEngine.recordError({
|
|
128
|
+
step: "ws-agent-loop",
|
|
129
|
+
message: err.message,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
this._processing = false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Handle slash commands within the session.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} command - e.g. "/plan enter", "/model qwen2:7b"
|
|
141
|
+
* @param {string} [requestId]
|
|
142
|
+
*/
|
|
143
|
+
async handleSlashCommand(command, requestId) {
|
|
144
|
+
const [cmd, ...args] = command.trim().split(/\s+/);
|
|
145
|
+
const arg = args.join(" ").trim();
|
|
146
|
+
const { session } = this;
|
|
147
|
+
|
|
148
|
+
switch (cmd) {
|
|
149
|
+
case "/model":
|
|
150
|
+
if (arg) {
|
|
151
|
+
session.model = arg;
|
|
152
|
+
this.interaction.emit("command-response", {
|
|
153
|
+
requestId,
|
|
154
|
+
command: cmd,
|
|
155
|
+
result: { model: arg },
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
this.interaction.emit("command-response", {
|
|
159
|
+
requestId,
|
|
160
|
+
command: cmd,
|
|
161
|
+
result: { model: session.model },
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
case "/provider": {
|
|
167
|
+
const supported = [
|
|
168
|
+
"ollama",
|
|
169
|
+
"anthropic",
|
|
170
|
+
"openai",
|
|
171
|
+
"deepseek",
|
|
172
|
+
"dashscope",
|
|
173
|
+
"mistral",
|
|
174
|
+
"gemini",
|
|
175
|
+
"volcengine",
|
|
176
|
+
];
|
|
177
|
+
if (arg && supported.includes(arg)) {
|
|
178
|
+
session.provider = arg;
|
|
179
|
+
this.interaction.emit("command-response", {
|
|
180
|
+
requestId,
|
|
181
|
+
command: cmd,
|
|
182
|
+
result: { provider: arg },
|
|
183
|
+
});
|
|
184
|
+
} else {
|
|
185
|
+
this.interaction.emit("command-response", {
|
|
186
|
+
requestId,
|
|
187
|
+
command: cmd,
|
|
188
|
+
result: { provider: session.provider, available: supported },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
case "/clear":
|
|
195
|
+
session.messages.length = 1; // Keep system prompt
|
|
196
|
+
this.interaction.emit("command-response", {
|
|
197
|
+
requestId,
|
|
198
|
+
command: cmd,
|
|
199
|
+
result: { cleared: true },
|
|
200
|
+
});
|
|
201
|
+
break;
|
|
202
|
+
|
|
203
|
+
case "/compact":
|
|
204
|
+
if (session.contextEngine && session.messages.length > 5) {
|
|
205
|
+
const compacted = session.contextEngine.smartCompact(
|
|
206
|
+
session.messages,
|
|
207
|
+
);
|
|
208
|
+
session.messages.length = 0;
|
|
209
|
+
session.messages.push(...compacted);
|
|
210
|
+
} else if (session.messages.length > 5) {
|
|
211
|
+
const systemMsg = session.messages[0];
|
|
212
|
+
const recent = session.messages.slice(-4);
|
|
213
|
+
session.messages.length = 0;
|
|
214
|
+
session.messages.push(systemMsg, ...recent);
|
|
215
|
+
}
|
|
216
|
+
this.interaction.emit("command-response", {
|
|
217
|
+
requestId,
|
|
218
|
+
command: cmd,
|
|
219
|
+
result: { messageCount: session.messages.length },
|
|
220
|
+
});
|
|
221
|
+
break;
|
|
222
|
+
|
|
223
|
+
case "/task":
|
|
224
|
+
if (arg === "clear") {
|
|
225
|
+
if (session.contextEngine) session.contextEngine.clearTask();
|
|
226
|
+
this.interaction.emit("command-response", {
|
|
227
|
+
requestId,
|
|
228
|
+
command: cmd,
|
|
229
|
+
result: { cleared: true },
|
|
230
|
+
});
|
|
231
|
+
} else if (arg) {
|
|
232
|
+
if (session.contextEngine) session.contextEngine.setTask(arg);
|
|
233
|
+
this.interaction.emit("command-response", {
|
|
234
|
+
requestId,
|
|
235
|
+
command: cmd,
|
|
236
|
+
result: { task: arg },
|
|
237
|
+
});
|
|
238
|
+
} else {
|
|
239
|
+
this.interaction.emit("command-response", {
|
|
240
|
+
requestId,
|
|
241
|
+
command: cmd,
|
|
242
|
+
result: {
|
|
243
|
+
task: session.contextEngine?.taskContext?.objective || null,
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
|
|
249
|
+
case "/stats":
|
|
250
|
+
if (session.contextEngine) {
|
|
251
|
+
const stats = session.contextEngine.getStats();
|
|
252
|
+
this.interaction.emit("command-response", {
|
|
253
|
+
requestId,
|
|
254
|
+
command: cmd,
|
|
255
|
+
result: stats,
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
this.interaction.emit("command-response", {
|
|
259
|
+
requestId,
|
|
260
|
+
command: cmd,
|
|
261
|
+
result: { error: "Context engine not available" },
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
case "/session":
|
|
267
|
+
this.interaction.emit("command-response", {
|
|
268
|
+
requestId,
|
|
269
|
+
command: cmd,
|
|
270
|
+
result: {
|
|
271
|
+
id: session.id,
|
|
272
|
+
type: session.type,
|
|
273
|
+
provider: session.provider,
|
|
274
|
+
model: session.model,
|
|
275
|
+
messageCount: session.messages.length,
|
|
276
|
+
projectRoot: session.projectRoot,
|
|
277
|
+
createdAt: session.createdAt,
|
|
278
|
+
lastActivity: session.lastActivity,
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
break;
|
|
282
|
+
|
|
283
|
+
case "/plan":
|
|
284
|
+
this._handlePlanCommand(arg, requestId);
|
|
285
|
+
break;
|
|
286
|
+
|
|
287
|
+
default:
|
|
288
|
+
this.interaction.emit("command-response", {
|
|
289
|
+
requestId,
|
|
290
|
+
command: cmd,
|
|
291
|
+
result: { error: `Unknown command: ${cmd}` },
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Handle /plan sub-commands.
|
|
298
|
+
*/
|
|
299
|
+
_handlePlanCommand(subCmd, requestId) {
|
|
300
|
+
const planManager = this.session.planManager;
|
|
301
|
+
|
|
302
|
+
if (!subCmd || subCmd === "enter") {
|
|
303
|
+
if (planManager.isActive()) {
|
|
304
|
+
this.interaction.emit("command-response", {
|
|
305
|
+
requestId,
|
|
306
|
+
command: "/plan",
|
|
307
|
+
result: { error: "Already in plan mode" },
|
|
308
|
+
});
|
|
309
|
+
} else {
|
|
310
|
+
planManager.enterPlanMode({ title: "Agent Plan" });
|
|
311
|
+
this.session.messages.push({
|
|
312
|
+
role: "system",
|
|
313
|
+
content:
|
|
314
|
+
"[PLAN MODE ACTIVE] You are now in plan mode. You can read files, search, and analyze — but write/execute tools are blocked. Any blocked tool calls will be recorded as plan items. Analyze the task thoroughly, then the user will approve your plan.",
|
|
315
|
+
});
|
|
316
|
+
this.interaction.emit("command-response", {
|
|
317
|
+
requestId,
|
|
318
|
+
command: "/plan",
|
|
319
|
+
result: { state: "analyzing", message: "Entered plan mode" },
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
} else if (subCmd === "show") {
|
|
323
|
+
if (!planManager.isActive()) {
|
|
324
|
+
this.interaction.emit("command-response", {
|
|
325
|
+
requestId,
|
|
326
|
+
command: "/plan show",
|
|
327
|
+
result: { error: "Not in plan mode" },
|
|
328
|
+
});
|
|
329
|
+
} else {
|
|
330
|
+
this.interaction.emit("plan-ready", {
|
|
331
|
+
requestId,
|
|
332
|
+
summary: planManager.generatePlanSummary(),
|
|
333
|
+
risk: planManager.getRiskAssessment(),
|
|
334
|
+
items: planManager.currentPlan?.items || [],
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
} else if (subCmd === "approve" || subCmd === "yes") {
|
|
338
|
+
if (!planManager.isActive()) {
|
|
339
|
+
this.interaction.emit("command-response", {
|
|
340
|
+
requestId,
|
|
341
|
+
command: "/plan approve",
|
|
342
|
+
result: { error: "No plan to approve" },
|
|
343
|
+
});
|
|
344
|
+
} else if (
|
|
345
|
+
!planManager.currentPlan ||
|
|
346
|
+
planManager.currentPlan.items.length === 0
|
|
347
|
+
) {
|
|
348
|
+
this.interaction.emit("command-response", {
|
|
349
|
+
requestId,
|
|
350
|
+
command: "/plan approve",
|
|
351
|
+
result: { error: "Plan has no items" },
|
|
352
|
+
});
|
|
353
|
+
} else {
|
|
354
|
+
planManager.approvePlan();
|
|
355
|
+
this.session.messages.push({
|
|
356
|
+
role: "system",
|
|
357
|
+
content: `[PLAN APPROVED] The user has approved your plan with ${planManager.currentPlan.items.length} items. You can now use all tools including write_file, edit_file, run_shell, and run_skill. Execute the plan items in order.`,
|
|
358
|
+
});
|
|
359
|
+
this.interaction.emit("command-response", {
|
|
360
|
+
requestId,
|
|
361
|
+
command: "/plan approve",
|
|
362
|
+
result: {
|
|
363
|
+
state: PlanState.APPROVED,
|
|
364
|
+
itemCount: planManager.currentPlan.items.length,
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
} else if (subCmd === "reject" || subCmd === "no") {
|
|
369
|
+
if (planManager.isActive()) {
|
|
370
|
+
planManager.rejectPlan("User rejected");
|
|
371
|
+
this.interaction.emit("command-response", {
|
|
372
|
+
requestId,
|
|
373
|
+
command: "/plan reject",
|
|
374
|
+
result: { state: PlanState.REJECTED },
|
|
375
|
+
});
|
|
376
|
+
} else {
|
|
377
|
+
this.interaction.emit("command-response", {
|
|
378
|
+
requestId,
|
|
379
|
+
command: "/plan reject",
|
|
380
|
+
result: { error: "No plan to reject" },
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
} else if (subCmd === "exit") {
|
|
384
|
+
if (planManager.isActive()) {
|
|
385
|
+
planManager.exitPlanMode({ savePlan: true });
|
|
386
|
+
}
|
|
387
|
+
this.interaction.emit("command-response", {
|
|
388
|
+
requestId,
|
|
389
|
+
command: "/plan exit",
|
|
390
|
+
result: { state: PlanState.INACTIVE },
|
|
391
|
+
});
|
|
392
|
+
} else {
|
|
393
|
+
this.interaction.emit("command-response", {
|
|
394
|
+
requestId,
|
|
395
|
+
command: "/plan",
|
|
396
|
+
result: {
|
|
397
|
+
error: `Unknown /plan subcommand: ${subCmd}`,
|
|
398
|
+
available: ["enter", "show", "approve", "reject", "exit"],
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Chat Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles simple streaming chat sessions over WebSocket.
|
|
5
|
+
* Consumes chat-core's chatStream generator.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { chatWithStreaming } from "./chat-core.js";
|
|
9
|
+
|
|
10
|
+
export class WSChatHandler {
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} options
|
|
13
|
+
* @param {import("./ws-session-manager.js").Session} options.session
|
|
14
|
+
* @param {import("./interaction-adapter.js").WebSocketInteractionAdapter} options.interaction
|
|
15
|
+
*/
|
|
16
|
+
constructor({ session, interaction }) {
|
|
17
|
+
this.session = session;
|
|
18
|
+
this.interaction = interaction;
|
|
19
|
+
this._processing = false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Handle a user message — stream the response.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} userMessage
|
|
26
|
+
* @param {string} [requestId]
|
|
27
|
+
*/
|
|
28
|
+
async handleMessage(userMessage, requestId) {
|
|
29
|
+
if (this._processing) {
|
|
30
|
+
this.interaction.emit("error", {
|
|
31
|
+
requestId,
|
|
32
|
+
code: "BUSY",
|
|
33
|
+
message: "Session is currently processing a message",
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this._processing = true;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const { session } = this;
|
|
42
|
+
session.messages.push({ role: "user", content: userMessage });
|
|
43
|
+
|
|
44
|
+
const options = {
|
|
45
|
+
provider: session.provider,
|
|
46
|
+
model: session.model,
|
|
47
|
+
baseUrl: session.baseUrl || "http://localhost:11434",
|
|
48
|
+
apiKey: session.apiKey,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const fullContent = await chatWithStreaming(
|
|
52
|
+
session.messages,
|
|
53
|
+
options,
|
|
54
|
+
(event) => {
|
|
55
|
+
if (event.type === "response-token") {
|
|
56
|
+
this.interaction.emit("response-token", {
|
|
57
|
+
requestId,
|
|
58
|
+
token: event.token,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
session.messages.push({ role: "assistant", content: fullContent });
|
|
65
|
+
this.interaction.emit("response-complete", {
|
|
66
|
+
requestId,
|
|
67
|
+
content: fullContent,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
session.lastActivity = new Date().toISOString();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
this.interaction.emit("error", {
|
|
73
|
+
requestId,
|
|
74
|
+
code: "CHAT_ERROR",
|
|
75
|
+
message: err.message,
|
|
76
|
+
});
|
|
77
|
+
} finally {
|
|
78
|
+
this._processing = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Handle slash commands.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} command
|
|
86
|
+
* @param {string} [requestId]
|
|
87
|
+
*/
|
|
88
|
+
handleSlashCommand(command, requestId) {
|
|
89
|
+
const [cmd, ...args] = command.trim().split(/\s+/);
|
|
90
|
+
const arg = args.join(" ").trim();
|
|
91
|
+
const { session } = this;
|
|
92
|
+
|
|
93
|
+
switch (cmd) {
|
|
94
|
+
case "/model":
|
|
95
|
+
if (arg) session.model = arg;
|
|
96
|
+
this.interaction.emit("command-response", {
|
|
97
|
+
requestId,
|
|
98
|
+
command: cmd,
|
|
99
|
+
result: { model: session.model },
|
|
100
|
+
});
|
|
101
|
+
break;
|
|
102
|
+
|
|
103
|
+
case "/provider":
|
|
104
|
+
if (arg) session.provider = arg;
|
|
105
|
+
this.interaction.emit("command-response", {
|
|
106
|
+
requestId,
|
|
107
|
+
command: cmd,
|
|
108
|
+
result: { provider: session.provider },
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case "/clear":
|
|
113
|
+
session.messages.length = 0;
|
|
114
|
+
this.interaction.emit("command-response", {
|
|
115
|
+
requestId,
|
|
116
|
+
command: cmd,
|
|
117
|
+
result: { cleared: true },
|
|
118
|
+
});
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
case "/history":
|
|
122
|
+
this.interaction.emit("command-response", {
|
|
123
|
+
requestId,
|
|
124
|
+
command: cmd,
|
|
125
|
+
result: {
|
|
126
|
+
messages: session.messages.map((m) => ({
|
|
127
|
+
role: m.role,
|
|
128
|
+
content:
|
|
129
|
+
m.content.length > 200
|
|
130
|
+
? m.content.substring(0, 200) + "..."
|
|
131
|
+
: m.content,
|
|
132
|
+
})),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
default:
|
|
138
|
+
this.interaction.emit("command-response", {
|
|
139
|
+
requestId,
|
|
140
|
+
command: cmd,
|
|
141
|
+
result: { error: `Unknown command: ${cmd}` },
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|