@memo-code/memo 0.8.5 → 0.8.6

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.
Files changed (97) hide show
  1. package/dist/web/server/main.cjs +145360 -0
  2. package/package.json +2 -1
  3. package/dist/web/server/app.controller.d.ts +0 -6
  4. package/dist/web/server/app.controller.js +0 -33
  5. package/dist/web/server/app.module.d.ts +0 -4
  6. package/dist/web/server/app.module.js +0 -61
  7. package/dist/web/server/auth/access-token.guard.d.ts +0 -9
  8. package/dist/web/server/auth/access-token.guard.js +0 -53
  9. package/dist/web/server/auth/auth.controller.d.ts +0 -18
  10. package/dist/web/server/auth/auth.controller.js +0 -75
  11. package/dist/web/server/auth/auth.module.d.ts +0 -2
  12. package/dist/web/server/auth/auth.module.js +0 -24
  13. package/dist/web/server/auth/auth.service.d.ts +0 -15
  14. package/dist/web/server/auth/auth.service.js +0 -146
  15. package/dist/web/server/auth/auth.types.d.ts +0 -26
  16. package/dist/web/server/auth/auth.types.js +0 -2
  17. package/dist/web/server/auth/public.decorator.d.ts +0 -2
  18. package/dist/web/server/auth/public.decorator.js +0 -7
  19. package/dist/web/server/chat/chat.controller.d.ts +0 -30
  20. package/dist/web/server/chat/chat.controller.js +0 -150
  21. package/dist/web/server/chat/chat.module.d.ts +0 -2
  22. package/dist/web/server/chat/chat.module.js +0 -26
  23. package/dist/web/server/chat/chat.service.d.ts +0 -61
  24. package/dist/web/server/chat/chat.service.js +0 -847
  25. package/dist/web/server/chat/chat.types.d.ts +0 -38
  26. package/dist/web/server/chat/chat.types.js +0 -2
  27. package/dist/web/server/common/constants.d.ts +0 -1
  28. package/dist/web/server/common/constants.js +0 -4
  29. package/dist/web/server/common/filters/api-error.filter.d.ts +0 -7
  30. package/dist/web/server/common/filters/api-error.filter.js +0 -95
  31. package/dist/web/server/common/interceptors/api-response.interceptor.d.ts +0 -15
  32. package/dist/web/server/common/interceptors/api-response.interceptor.js +0 -51
  33. package/dist/web/server/common/middleware/request-logging.middleware.d.ts +0 -7
  34. package/dist/web/server/common/middleware/request-logging.middleware.js +0 -42
  35. package/dist/web/server/config/memo-config.service.d.ts +0 -7
  36. package/dist/web/server/config/memo-config.service.js +0 -106
  37. package/dist/web/server/config/memo-config.types.d.ts +0 -6
  38. package/dist/web/server/config/memo-config.types.js +0 -2
  39. package/dist/web/server/config/server-config.module.d.ts +0 -2
  40. package/dist/web/server/config/server-config.module.js +0 -22
  41. package/dist/web/server/config/server-config.service.d.ts +0 -14
  42. package/dist/web/server/config/server-config.service.js +0 -326
  43. package/dist/web/server/config/server-config.service.test.d.ts +0 -1
  44. package/dist/web/server/config/server-config.service.test.js +0 -193
  45. package/dist/web/server/config/server-config.types.d.ts +0 -27
  46. package/dist/web/server/config/server-config.types.js +0 -2
  47. package/dist/web/server/main.d.ts +0 -1
  48. package/dist/web/server/main.js +0 -19
  49. package/dist/web/server/mcp/mcp.controller.d.ts +0 -38
  50. package/dist/web/server/mcp/mcp.controller.js +0 -126
  51. package/dist/web/server/mcp/mcp.module.d.ts +0 -2
  52. package/dist/web/server/mcp/mcp.module.js +0 -22
  53. package/dist/web/server/mcp/mcp.service.d.ts +0 -25
  54. package/dist/web/server/mcp/mcp.service.js +0 -56
  55. package/dist/web/server/server.d.ts +0 -18
  56. package/dist/web/server/server.js +0 -142
  57. package/dist/web/server/sessions/sessions.controller.d.ts +0 -8
  58. package/dist/web/server/sessions/sessions.controller.js +0 -59
  59. package/dist/web/server/sessions/sessions.module.d.ts +0 -2
  60. package/dist/web/server/sessions/sessions.module.js +0 -24
  61. package/dist/web/server/sessions/sessions.service.d.ts +0 -22
  62. package/dist/web/server/sessions/sessions.service.js +0 -217
  63. package/dist/web/server/sessions/sessions.types.d.ts +0 -18
  64. package/dist/web/server/sessions/sessions.types.js +0 -2
  65. package/dist/web/server/skills/skills.controller.d.ts +0 -31
  66. package/dist/web/server/skills/skills.controller.js +0 -86
  67. package/dist/web/server/skills/skills.module.d.ts +0 -2
  68. package/dist/web/server/skills/skills.module.js +0 -24
  69. package/dist/web/server/skills/skills.service.d.ts +0 -38
  70. package/dist/web/server/skills/skills.service.js +0 -97
  71. package/dist/web/server/stream/stream.module.d.ts +0 -2
  72. package/dist/web/server/stream/stream.module.js +0 -20
  73. package/dist/web/server/stream/stream.service.d.ts +0 -26
  74. package/dist/web/server/stream/stream.service.js +0 -166
  75. package/dist/web/server/tsconfig.build.tsbuildinfo +0 -1
  76. package/dist/web/server/workspaces/workspaces.module.d.ts +0 -2
  77. package/dist/web/server/workspaces/workspaces.module.js +0 -20
  78. package/dist/web/server/workspaces/workspaces.service.d.ts +0 -38
  79. package/dist/web/server/workspaces/workspaces.service.js +0 -378
  80. package/dist/web/server/workspaces/workspaces.types.d.ts +0 -1
  81. package/dist/web/server/workspaces/workspaces.types.js +0 -2
  82. package/dist/web/server/workspaces/workspaces.utils.d.ts +0 -1
  83. package/dist/web/server/workspaces/workspaces.utils.js +0 -9
  84. package/dist/web/server/ws/rpc-router.service.d.ts +0 -20
  85. package/dist/web/server/ws/rpc-router.service.js +0 -275
  86. package/dist/web/server/ws/session-runtime-registry.service.d.ts +0 -37
  87. package/dist/web/server/ws/session-runtime-registry.service.js +0 -118
  88. package/dist/web/server/ws/ws-event-bus.service.d.ts +0 -5
  89. package/dist/web/server/ws/ws-event-bus.service.js +0 -27
  90. package/dist/web/server/ws/ws-gateway.module.d.ts +0 -2
  91. package/dist/web/server/ws/ws-gateway.module.js +0 -42
  92. package/dist/web/server/ws/ws-gateway.service.d.ts +0 -42
  93. package/dist/web/server/ws/ws-gateway.service.js +0 -473
  94. package/dist/web/server/ws/ws.errors.d.ts +0 -8
  95. package/dist/web/server/ws/ws.errors.js +0 -16
  96. package/dist/web/server/ws/ws.types.d.ts +0 -34
  97. package/dist/web/server/ws/ws.types.js +0 -2
@@ -1,847 +0,0 @@
1
- "use strict";
2
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
- return c > 3 && r && Object.defineProperty(target, key, r), r;
7
- };
8
- var __metadata = (this && this.__metadata) || function (k, v) {
9
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.ChatService = void 0;
13
- const node_crypto_1 = require("node:crypto");
14
- const promises_1 = require("node:fs/promises");
15
- const node_path_1 = require("node:path");
16
- const common_1 = require("@nestjs/common");
17
- const core_1 = require("@memo-code/core");
18
- const memo_config_service_1 = require("../config/memo-config.service");
19
- const sessions_service_1 = require("../sessions/sessions.service");
20
- const stream_service_1 = require("../stream/stream.service");
21
- const workspaces_service_1 = require("../workspaces/workspaces.service");
22
- const MAX_LIVE_SESSIONS = 20;
23
- function normalizeMode(value) {
24
- if (value === 'none' || value === 'once' || value === 'full')
25
- return value;
26
- return 'once';
27
- }
28
- function toAssistantText(turn) {
29
- const finalText = turn.finalText?.trim();
30
- if (finalText)
31
- return finalText;
32
- return turn.steps
33
- .map((step) => step.assistantText ?? '')
34
- .join('')
35
- .trim();
36
- }
37
- function cloneTurnStep(step) {
38
- return {
39
- step: step.step,
40
- assistantText: step.assistantText,
41
- thinking: step.thinking,
42
- action: step.action,
43
- parallelActions: step.parallelActions,
44
- observation: step.observation,
45
- resultStatus: step.resultStatus,
46
- };
47
- }
48
- function cloneTurnSteps(steps) {
49
- if (!steps || steps.length === 0)
50
- return [];
51
- return steps.map(cloneTurnStep);
52
- }
53
- async function findTaskPromptTemplate(templateName) {
54
- const candidates = [
55
- process.env.MEMO_TASK_PROMPTS_DIR
56
- ? (0, node_path_1.join)(process.env.MEMO_TASK_PROMPTS_DIR, `${templateName}.md`)
57
- : null,
58
- (0, node_path_1.join)(process.cwd(), 'dist', 'task-prompts', `${templateName}.md`),
59
- (0, node_path_1.join)(process.cwd(), 'packages', 'tui', 'src', 'task-prompts', `${templateName}.md`),
60
- ].filter((item) => Boolean(item));
61
- for (const filePath of candidates) {
62
- const resolved = (0, node_path_1.resolve)(filePath);
63
- try {
64
- await (0, promises_1.access)(resolved);
65
- return (0, promises_1.readFile)(resolved, 'utf8');
66
- }
67
- catch {
68
- }
69
- }
70
- throw new Error(`Task prompt not found: ${templateName}`);
71
- }
72
- function renderTemplate(template, vars) {
73
- return template.replace(/{{\s*([\w.-]+)\s*}}/g, (_match, key) => {
74
- return vars[key] ?? '';
75
- });
76
- }
77
- let ChatService = class ChatService {
78
- streamService;
79
- memoConfigService;
80
- sessionsService;
81
- workspacesService;
82
- sessions = new Map();
83
- constructor(streamService, memoConfigService, sessionsService, workspacesService) {
84
- this.streamService = streamService;
85
- this.memoConfigService = memoConfigService;
86
- this.sessionsService = sessionsService;
87
- this.workspacesService = workspacesService;
88
- }
89
- async createSession(input) {
90
- if (this.sessions.size >= MAX_LIVE_SESSIONS) {
91
- await this.evictOneIdleSession();
92
- }
93
- const workspace = await this.workspacesService.resolveWorkspace({
94
- workspaceId: input.workspaceId,
95
- cwd: input.cwd,
96
- });
97
- await this.workspacesService.touchLastUsed(workspace.id);
98
- const config = await this.memoConfigService.load();
99
- const provider = this.selectProvider(config.providers, input.providerName, config.current_provider);
100
- const sessionId = (0, node_crypto_1.randomUUID)();
101
- const runtime = await this.createRuntime({
102
- id: sessionId,
103
- title: 'New Session',
104
- workspaceId: workspace.id,
105
- projectName: workspace.name,
106
- providerName: provider.name,
107
- model: provider.model,
108
- cwd: workspace.cwd,
109
- startedAt: new Date().toISOString(),
110
- activeMcpServers: input.activeMcpServers && input.activeMcpServers.length > 0
111
- ? input.activeMcpServers
112
- : config.active_mcp_servers,
113
- toolPermissionMode: normalizeMode(input.toolPermissionMode),
114
- });
115
- this.sessions.set(runtime.id, runtime);
116
- this.touchSession(runtime);
117
- this.streamService.broadcast(runtime.id, {
118
- type: 'session.snapshot',
119
- payload: this.toState(runtime),
120
- });
121
- return this.toState(runtime);
122
- }
123
- async attachSession(sessionId) {
124
- const existing = this.sessions.get(sessionId);
125
- if (existing) {
126
- return this.toSnapshot(existing);
127
- }
128
- const detail = await this.sessionsService.getSessionDetail(sessionId);
129
- const workspace = await this.workspacesService.ensureByCwd(detail.cwd || process.cwd(), detail.project, { validateReadable: false });
130
- await this.workspacesService.touchLastUsed(workspace.id);
131
- const config = await this.memoConfigService.load();
132
- const provider = this.selectProvider(config.providers, undefined, config.current_provider);
133
- const historyMessages = [];
134
- const turns = [];
135
- for (const turn of detail.turns) {
136
- const input = (turn.input ?? '').trim();
137
- const assistant = toAssistantText(turn);
138
- if (input) {
139
- historyMessages.push({ role: 'user', content: input });
140
- }
141
- if (assistant) {
142
- historyMessages.push({ role: 'assistant', content: assistant });
143
- }
144
- turns.push({
145
- turn: turn.turn,
146
- input,
147
- assistant,
148
- status: turn.status ?? 'ok',
149
- errorMessage: turn.errorMessage,
150
- steps: cloneTurnSteps(turn.steps),
151
- });
152
- }
153
- const maxTurn = turns.reduce((max, item) => Math.max(max, item.turn), 0);
154
- const runtime = await this.createRuntime({
155
- id: detail.sessionId,
156
- title: detail.title,
157
- workspaceId: workspace.id,
158
- projectName: workspace.name,
159
- providerName: provider.name,
160
- model: provider.model,
161
- cwd: workspace.cwd,
162
- startedAt: detail.date.startedAt,
163
- activeMcpServers: config.active_mcp_servers,
164
- toolPermissionMode: 'once',
165
- historyFilePath: detail.filePath,
166
- });
167
- const system = runtime.agentSession.history[0];
168
- runtime.agentSession.history = system
169
- ? [system, ...historyMessages]
170
- : [...historyMessages];
171
- runtime.agentSession.turnIndex =
172
- maxTurn;
173
- runtime.agentSession.sessionStartEmitted = true;
174
- runtime.agentSession.title = detail.title;
175
- runtime.turn = maxTurn;
176
- runtime.turns = turns;
177
- this.sessions.set(runtime.id, runtime);
178
- this.touchSession(runtime);
179
- const snapshot = this.toSnapshot(runtime);
180
- this.streamService.broadcast(runtime.id, {
181
- type: 'session.snapshot',
182
- payload: snapshot.state,
183
- });
184
- return snapshot;
185
- }
186
- getSessionState(sessionId) {
187
- const session = this.requireSession(sessionId);
188
- return this.toState(session);
189
- }
190
- getSessionSnapshot(sessionId) {
191
- const session = this.requireSession(sessionId);
192
- return this.toSnapshot(session);
193
- }
194
- async listProviders() {
195
- const config = await this.memoConfigService.load();
196
- const current = config.current_provider || config.providers[0]?.name || '';
197
- return config.providers.map((provider) => ({
198
- name: provider.name,
199
- model: provider.model,
200
- isCurrent: provider.name === current,
201
- }));
202
- }
203
- async listRuntimeBadges(input) {
204
- const workspaceId = input.workspaceId?.trim();
205
- const items = Array.from(this.sessions.values())
206
- .filter((session) => !workspaceId || session.workspaceId === workspaceId)
207
- .map((session) => ({
208
- sessionId: session.id,
209
- workspaceId: session.workspaceId,
210
- status: session.status,
211
- updatedAt: session.updatedAt,
212
- }))
213
- .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
214
- return { items };
215
- }
216
- async suggestFiles(input) {
217
- const sessionId = input.sessionId?.trim();
218
- const workspaceId = input.workspaceId?.trim();
219
- if (!sessionId && !workspaceId) {
220
- throw new common_1.BadRequestException('sessionId or workspaceId is required');
221
- }
222
- let cwd = '';
223
- if (sessionId) {
224
- const active = this.sessions.get(sessionId);
225
- if (active) {
226
- cwd = active.cwd;
227
- }
228
- else {
229
- const detail = await this.sessionsService.getSessionDetail(sessionId);
230
- cwd = detail.cwd || process.cwd();
231
- }
232
- }
233
- else if (workspaceId) {
234
- const workspace = await this.workspacesService.resolveWorkspace({
235
- workspaceId,
236
- });
237
- cwd = workspace.cwd;
238
- }
239
- const limit = typeof input.limit === 'number'
240
- ? Math.max(1, Math.min(20, Math.floor(input.limit)))
241
- : 8;
242
- const items = await (0, core_1.getFileSuggestions)({
243
- cwd,
244
- query: input.query ?? '',
245
- limit,
246
- respectGitIgnore: true,
247
- });
248
- return {
249
- items: items.map((item) => ({
250
- id: item.id,
251
- path: item.path,
252
- name: item.name,
253
- parent: item.parent,
254
- isDir: item.isDir,
255
- })),
256
- };
257
- }
258
- hasSession(sessionId) {
259
- return this.sessions.has(sessionId);
260
- }
261
- async deleteSession(sessionId) {
262
- const target = sessionId.trim();
263
- if (!target) {
264
- throw new common_1.BadRequestException('sessionId is required');
265
- }
266
- if (this.sessions.has(target)) {
267
- await this.closeSession(target);
268
- }
269
- try {
270
- await this.sessionsService.removeSession(target);
271
- }
272
- catch (error) {
273
- if (!(error instanceof common_1.NotFoundException)) {
274
- throw error;
275
- }
276
- }
277
- return { deleted: true };
278
- }
279
- async closeSession(sessionId) {
280
- const session = this.requireSession(sessionId);
281
- this.resolvePendingApprovals(session, 'deny');
282
- session.status = 'closed';
283
- this.touchSession(session);
284
- this.streamService.broadcast(session.id, {
285
- type: 'session.status',
286
- payload: {
287
- status: 'closed',
288
- workspaceId: session.workspaceId,
289
- updatedAt: session.updatedAt,
290
- },
291
- });
292
- await session.agentSession.close();
293
- this.sessions.delete(sessionId);
294
- this.streamService.disconnectSession(sessionId);
295
- return { closed: true };
296
- }
297
- async submitInput(sessionId, input) {
298
- const session = this.requireSession(sessionId);
299
- if (session.status === 'running') {
300
- return {
301
- accepted: false,
302
- kind: 'turn',
303
- status: 'error',
304
- message: 'Session is busy.',
305
- };
306
- }
307
- const trimmed = input.trim();
308
- if (!trimmed) {
309
- throw new common_1.BadRequestException('input is required');
310
- }
311
- if (trimmed.startsWith('/')) {
312
- return this.runSlashCommand(session, trimmed);
313
- }
314
- return this.runCoreTurn(session, trimmed, trimmed);
315
- }
316
- cancelCurrentTurn(sessionId) {
317
- const session = this.requireSession(sessionId);
318
- if (session.status === 'running') {
319
- session.agentSession.cancelCurrentTurn?.('cancelled by user');
320
- return { cancelled: true };
321
- }
322
- return { cancelled: false };
323
- }
324
- async compactSession(sessionId) {
325
- const session = this.requireSession(sessionId);
326
- const result = await session.agentSession.compactHistory('manual');
327
- this.streamService.broadcast(session.id, {
328
- type: 'system.message',
329
- payload: {
330
- title: 'Compact',
331
- content: result.status === 'success'
332
- ? `Compacted history to ${result.afterTokens} tokens.`
333
- : result.status === 'skipped'
334
- ? 'Skipped compaction: no history to compact.'
335
- : `Compaction failed: ${result.errorMessage ?? 'unknown error'}`,
336
- },
337
- });
338
- return {
339
- compacted: result.status === 'success',
340
- keptMessages: session.agentSession.history.length,
341
- };
342
- }
343
- applyApprovalDecision(sessionId, fingerprint, decision) {
344
- const session = this.requireSession(sessionId);
345
- const resolver = session.pendingApprovals.get(fingerprint);
346
- if (!resolver) {
347
- return { recorded: false };
348
- }
349
- session.pendingApprovals.delete(fingerprint);
350
- if (session.pendingApproval?.fingerprint === fingerprint) {
351
- session.pendingApproval = undefined;
352
- }
353
- resolver(decision);
354
- this.touchSession(session);
355
- this.streamService.broadcast(session.id, {
356
- type: 'session.snapshot',
357
- payload: this.toState(session),
358
- });
359
- return { recorded: true };
360
- }
361
- async runSlashCommand(session, input) {
362
- const config = await this.memoConfigService.load();
363
- const result = (0, core_1.resolveSlashCommand)(input, {
364
- configPath: this.memoConfigService.getConfigPath(),
365
- providerName: session.providerName,
366
- model: session.model,
367
- mcpServers: config.mcp_servers,
368
- providers: config.providers,
369
- toolPermissionMode: session.toolPermissionMode,
370
- });
371
- if (result.kind === 'message') {
372
- this.sendSystemMessage(session, result.title, result.content);
373
- return { accepted: true, kind: 'command', status: 'ok' };
374
- }
375
- if (result.kind === 'exit') {
376
- await this.closeSession(session.id);
377
- return {
378
- accepted: true,
379
- kind: 'command',
380
- status: 'ok',
381
- message: 'session closed',
382
- };
383
- }
384
- if (result.kind === 'new') {
385
- await this.recreateSessionRuntime(session, {
386
- title: 'New Session',
387
- resetTurns: true,
388
- });
389
- this.sendSystemMessage(session, 'New Session', 'Started a fresh session.');
390
- return { accepted: true, kind: 'command', status: 'ok' };
391
- }
392
- if (result.kind === 'switch_model') {
393
- await this.recreateSessionRuntime(session, {
394
- title: session.title,
395
- providerName: result.provider.name,
396
- model: result.provider.model,
397
- });
398
- this.sendSystemMessage(session, 'Models', `Switched to ${result.provider.name} (${result.provider.model}).`);
399
- this.streamService.broadcast(session.id, {
400
- type: 'session.snapshot',
401
- payload: this.toState(session),
402
- });
403
- return { accepted: true, kind: 'command', status: 'ok' };
404
- }
405
- if (result.kind === 'set_tool_permission') {
406
- await this.recreateSessionRuntime(session, {
407
- title: session.title,
408
- toolPermissionMode: result.mode,
409
- });
410
- this.sendSystemMessage(session, 'Tools', `Tool permission set to ${result.mode}.`);
411
- this.streamService.broadcast(session.id, {
412
- type: 'session.snapshot',
413
- payload: this.toState(session),
414
- });
415
- return { accepted: true, kind: 'command', status: 'ok' };
416
- }
417
- if (result.kind === 'compact') {
418
- await this.compactSession(session.id);
419
- return { accepted: true, kind: 'command', status: 'ok' };
420
- }
421
- if (result.kind === 'init_agents_md') {
422
- const template = await findTaskPromptTemplate('init_agents');
423
- const prompt = renderTemplate(template, {});
424
- return this.runCoreTurn(session, prompt, '/init');
425
- }
426
- if (result.kind === 'review_pr') {
427
- const prNumber = String(result.prNumber);
428
- const template = await findTaskPromptTemplate('review_pull_request');
429
- const prompt = renderTemplate(template, {
430
- pr_number: prNumber,
431
- backend_strategy: 'web_server',
432
- backend_details: 'Running from memo web server',
433
- mcp_server_prefix: 'github',
434
- });
435
- return this.runCoreTurn(session, prompt, `/review ${prNumber}`);
436
- }
437
- return { accepted: true, kind: 'command', status: 'ok' };
438
- }
439
- async runCoreTurn(session, prompt, displayInput) {
440
- session.nextInputDisplay = displayInput;
441
- try {
442
- const result = await session.agentSession.runTurn(prompt);
443
- return {
444
- accepted: true,
445
- kind: 'turn',
446
- status: result.status === 'ok'
447
- ? 'ok'
448
- : result.status === 'cancelled'
449
- ? 'cancelled'
450
- : 'error',
451
- message: result.errorMessage,
452
- };
453
- }
454
- catch (error) {
455
- const message = error instanceof Error ? error.message : String(error);
456
- session.status = 'idle';
457
- this.touchSession(session);
458
- this.streamService.broadcast(session.id, {
459
- type: 'error',
460
- payload: {
461
- code: 'turn_error',
462
- message,
463
- },
464
- });
465
- this.streamService.broadcast(session.id, {
466
- type: 'session.status',
467
- payload: {
468
- status: 'idle',
469
- workspaceId: session.workspaceId,
470
- updatedAt: session.updatedAt,
471
- },
472
- });
473
- return {
474
- accepted: true,
475
- kind: 'turn',
476
- status: 'error',
477
- message,
478
- };
479
- }
480
- }
481
- async recreateSessionRuntime(session, options) {
482
- this.resolvePendingApprovals(session, 'deny');
483
- await session.agentSession.close();
484
- if (options.providerName)
485
- session.providerName = options.providerName;
486
- if (options.model)
487
- session.model = options.model;
488
- if (options.toolPermissionMode) {
489
- session.toolPermissionMode = options.toolPermissionMode;
490
- }
491
- if (options.activeMcpServers) {
492
- session.activeMcpServers = options.activeMcpServers;
493
- }
494
- if (options.title) {
495
- session.title = options.title;
496
- }
497
- if (options.resetTurns) {
498
- session.turn = 0;
499
- session.turns = [];
500
- }
501
- session.agentSession = await this.createCoreSession(session);
502
- if (!options.resetTurns) {
503
- this.restoreAgentHistoryFromTurns(session);
504
- }
505
- session.status = 'idle';
506
- this.touchSession(session);
507
- }
508
- restoreAgentHistoryFromTurns(session) {
509
- if (!session.turns.length)
510
- return;
511
- const historyMessages = [];
512
- let maxTurn = 0;
513
- const sorted = [...session.turns].sort((a, b) => a.turn - b.turn);
514
- for (const turn of sorted) {
515
- maxTurn = Math.max(maxTurn, turn.turn);
516
- const input = turn.input.trim();
517
- const assistant = turn.assistant.trim();
518
- if (input) {
519
- historyMessages.push({ role: 'user', content: input });
520
- }
521
- if (assistant) {
522
- historyMessages.push({ role: 'assistant', content: assistant });
523
- }
524
- }
525
- const system = session.agentSession.history[0];
526
- session.agentSession.history = system
527
- ? [system, ...historyMessages]
528
- : [...historyMessages];
529
- session.agentSession.turnIndex =
530
- maxTurn;
531
- session.agentSession.sessionStartEmitted = true;
532
- session.agentSession.title = session.title;
533
- session.turn = maxTurn;
534
- }
535
- async createRuntime(input) {
536
- const runtime = {
537
- id: input.id,
538
- title: input.title,
539
- workspaceId: input.workspaceId,
540
- projectName: input.projectName,
541
- providerName: input.providerName,
542
- model: input.model,
543
- cwd: input.cwd,
544
- startedAt: input.startedAt,
545
- updatedAt: new Date().toISOString(),
546
- status: 'idle',
547
- activeMcpServers: input.activeMcpServers,
548
- toolPermissionMode: input.toolPermissionMode,
549
- turn: 0,
550
- historyFilePath: input.historyFilePath,
551
- turns: [],
552
- pendingApprovals: new Map(),
553
- agentSession: null,
554
- };
555
- runtime.agentSession = await this.createCoreSession(runtime);
556
- runtime.historyFilePath =
557
- runtime.historyFilePath ?? runtime.agentSession.historyFilePath;
558
- return runtime;
559
- }
560
- async createCoreSession(runtime) {
561
- const deps = {
562
- historySinks: runtime.historyFilePath
563
- ? [new core_1.JsonlHistorySink(runtime.historyFilePath)]
564
- : undefined,
565
- onAssistantStep: (chunk, step) => {
566
- if (!chunk)
567
- return;
568
- const turn = runtime.activeTurn;
569
- if (!turn)
570
- return;
571
- const record = this.getOrCreateTurnRecord(runtime, turn);
572
- const stepRecord = this.getOrCreateTurnStep(record, step);
573
- record.assistant = `${record.assistant}${chunk}`;
574
- stepRecord.assistantText = `${stepRecord.assistantText ?? ''}${chunk}`;
575
- this.streamService.broadcast(runtime.id, {
576
- type: 'assistant.chunk',
577
- payload: {
578
- turn,
579
- step,
580
- chunk,
581
- },
582
- });
583
- },
584
- requestApproval: async (request) => {
585
- runtime.pendingApproval = request;
586
- this.touchSession(runtime);
587
- this.streamService.broadcast(runtime.id, {
588
- type: 'approval.request',
589
- payload: {
590
- fingerprint: request.fingerprint,
591
- toolName: request.toolName,
592
- reason: request.reason,
593
- riskLevel: request.riskLevel,
594
- params: request.params,
595
- },
596
- });
597
- this.streamService.broadcast(runtime.id, {
598
- type: 'session.snapshot',
599
- payload: this.toState(runtime),
600
- });
601
- return new Promise((resolve) => {
602
- runtime.pendingApprovals.set(request.fingerprint, resolve);
603
- });
604
- },
605
- hooks: {
606
- onTurnStart: ({ turn, input }) => {
607
- runtime.status = 'running';
608
- runtime.activeTurn = turn;
609
- runtime.turn = Math.max(runtime.turn, turn);
610
- const displayInput = runtime.nextInputDisplay ?? input;
611
- runtime.nextInputDisplay = undefined;
612
- const record = this.getOrCreateTurnRecord(runtime, turn);
613
- record.input = displayInput;
614
- record.assistant = '';
615
- record.status = 'running';
616
- record.errorMessage = undefined;
617
- record.steps = [];
618
- this.touchSession(runtime);
619
- this.streamService.broadcast(runtime.id, {
620
- type: 'session.status',
621
- payload: {
622
- status: 'running',
623
- workspaceId: runtime.workspaceId,
624
- updatedAt: runtime.updatedAt,
625
- },
626
- });
627
- this.streamService.broadcast(runtime.id, {
628
- type: 'turn.start',
629
- payload: {
630
- turn,
631
- input: displayInput,
632
- },
633
- });
634
- },
635
- onAction: ({ turn, step, action, parallelActions, thinking }) => {
636
- const record = this.getOrCreateTurnRecord(runtime, turn);
637
- const stepRecord = this.getOrCreateTurnStep(record, step);
638
- stepRecord.action = action;
639
- stepRecord.parallelActions =
640
- parallelActions && parallelActions.length > 1
641
- ? parallelActions
642
- : undefined;
643
- stepRecord.thinking = thinking;
644
- this.streamService.broadcast(runtime.id, {
645
- type: 'tool.action',
646
- payload: {
647
- turn,
648
- step,
649
- action,
650
- parallelActions: parallelActions && parallelActions.length > 0
651
- ? parallelActions
652
- : undefined,
653
- thinking,
654
- },
655
- });
656
- },
657
- onObservation: ({ turn, step, observation, resultStatus, parallelResultStatuses, }) => {
658
- const record = this.getOrCreateTurnRecord(runtime, turn);
659
- const stepRecord = this.getOrCreateTurnStep(record, step);
660
- stepRecord.observation = observation;
661
- stepRecord.resultStatus =
662
- typeof resultStatus === 'string'
663
- ? resultStatus
664
- : parallelResultStatuses?.[0];
665
- this.streamService.broadcast(runtime.id, {
666
- type: 'tool.observation',
667
- payload: {
668
- turn,
669
- step,
670
- observation,
671
- resultStatus,
672
- parallelResultStatuses,
673
- },
674
- });
675
- },
676
- onFinal: ({ turn, finalText, status, errorMessage }) => {
677
- runtime.activeTurn = undefined;
678
- runtime.status = 'idle';
679
- const record = this.getOrCreateTurnRecord(runtime, turn);
680
- record.assistant = finalText || record.assistant;
681
- record.status = status;
682
- record.errorMessage = errorMessage;
683
- this.touchSession(runtime);
684
- this.streamService.broadcast(runtime.id, {
685
- type: 'turn.final',
686
- payload: {
687
- turn,
688
- finalText,
689
- status,
690
- errorMessage,
691
- },
692
- });
693
- this.streamService.broadcast(runtime.id, {
694
- type: 'session.status',
695
- payload: {
696
- status: 'idle',
697
- workspaceId: runtime.workspaceId,
698
- updatedAt: runtime.updatedAt,
699
- },
700
- });
701
- this.streamService.broadcast(runtime.id, {
702
- type: 'session.snapshot',
703
- payload: this.toState(runtime),
704
- });
705
- },
706
- onTitleGenerated: ({ title }) => {
707
- runtime.title = title;
708
- this.touchSession(runtime);
709
- this.streamService.broadcast(runtime.id, {
710
- type: 'session.snapshot',
711
- payload: this.toState(runtime),
712
- });
713
- },
714
- onApprovalResponse: ({ fingerprint }) => {
715
- runtime.pendingApprovals.delete(fingerprint);
716
- if (runtime.pendingApproval?.fingerprint === fingerprint) {
717
- runtime.pendingApproval = undefined;
718
- }
719
- this.touchSession(runtime);
720
- this.streamService.broadcast(runtime.id, {
721
- type: 'session.snapshot',
722
- payload: this.toState(runtime),
723
- });
724
- },
725
- },
726
- };
727
- return (0, core_1.createAgentSession)(deps, {
728
- sessionId: runtime.id,
729
- mode: 'interactive',
730
- providerName: runtime.providerName,
731
- activeMcpServers: runtime.activeMcpServers,
732
- toolPermissionMode: runtime.toolPermissionMode,
733
- dangerous: runtime.toolPermissionMode === 'full',
734
- cwd: runtime.cwd,
735
- });
736
- }
737
- getOrCreateTurnRecord(runtime, turn) {
738
- let record = runtime.turns.find((item) => item.turn === turn);
739
- if (record)
740
- return record;
741
- record = {
742
- turn,
743
- input: '',
744
- assistant: '',
745
- status: 'running',
746
- steps: [],
747
- };
748
- runtime.turns.push(record);
749
- return record;
750
- }
751
- getOrCreateTurnStep(record, step) {
752
- let entry = record.steps.find((item) => item.step === step);
753
- if (entry)
754
- return entry;
755
- entry = { step };
756
- record.steps.push(entry);
757
- record.steps.sort((a, b) => a.step - b.step);
758
- return entry;
759
- }
760
- resolvePendingApprovals(session, decision) {
761
- for (const resolver of session.pendingApprovals.values()) {
762
- resolver(decision);
763
- }
764
- session.pendingApprovals.clear();
765
- session.pendingApproval = undefined;
766
- }
767
- sendSystemMessage(session, title, content) {
768
- this.streamService.broadcast(session.id, {
769
- type: 'system.message',
770
- payload: {
771
- title,
772
- content,
773
- },
774
- });
775
- }
776
- touchSession(session) {
777
- session.updatedAt = new Date().toISOString();
778
- }
779
- requireSession(sessionId) {
780
- const session = this.sessions.get(sessionId);
781
- if (!session) {
782
- throw new common_1.NotFoundException('Live session not found');
783
- }
784
- return session;
785
- }
786
- toState(session) {
787
- return {
788
- id: session.id,
789
- title: session.title,
790
- workspaceId: session.workspaceId,
791
- projectName: session.projectName,
792
- providerName: session.providerName,
793
- model: session.model,
794
- cwd: session.cwd,
795
- startedAt: session.startedAt,
796
- status: session.status,
797
- pendingApproval: session.pendingApproval
798
- ? {
799
- fingerprint: session.pendingApproval.fingerprint,
800
- toolName: session.pendingApproval.toolName,
801
- reason: session.pendingApproval.reason,
802
- riskLevel: session.pendingApproval.riskLevel,
803
- params: session.pendingApproval.params,
804
- }
805
- : undefined,
806
- activeMcpServers: session.activeMcpServers,
807
- toolPermissionMode: session.toolPermissionMode,
808
- };
809
- }
810
- toSnapshot(session) {
811
- return {
812
- state: this.toState(session),
813
- turns: session.turns.map((turn) => ({
814
- turn: turn.turn,
815
- input: turn.input,
816
- assistant: turn.assistant,
817
- status: turn.status,
818
- errorMessage: turn.errorMessage,
819
- steps: cloneTurnSteps(turn.steps),
820
- })),
821
- };
822
- }
823
- selectProvider(providers, preferredName, fallbackName) {
824
- const candidate = preferredName || fallbackName;
825
- const found = providers.find((provider) => provider.name === candidate);
826
- if (found)
827
- return found;
828
- if (providers.length > 0)
829
- return providers[0];
830
- throw new common_1.ServiceUnavailableException('No providers configured in memo config.');
831
- }
832
- async evictOneIdleSession() {
833
- const idle = Array.from(this.sessions.values()).find((session) => session.status === 'idle');
834
- if (!idle) {
835
- throw new common_1.ServiceUnavailableException(`Too many live sessions (max=${MAX_LIVE_SESSIONS}).`);
836
- }
837
- await this.closeSession(idle.id);
838
- }
839
- };
840
- exports.ChatService = ChatService;
841
- exports.ChatService = ChatService = __decorate([
842
- (0, common_1.Injectable)(),
843
- __metadata("design:paramtypes", [stream_service_1.StreamService,
844
- memo_config_service_1.MemoConfigService,
845
- sessions_service_1.SessionsService,
846
- workspaces_service_1.WorkspacesService])
847
- ], ChatService);