@coralai/sps-cli 0.49.15 → 0.50.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.
Files changed (175) hide show
  1. package/dist/commands/cardAdd.d.ts.map +1 -1
  2. package/dist/commands/cardAdd.js +47 -67
  3. package/dist/commands/cardAdd.js.map +1 -1
  4. package/dist/commands/cardMarkComplete.js +1 -1
  5. package/dist/commands/cardMarkComplete.js.map +1 -1
  6. package/dist/commands/cardMarkStarted.js +1 -1
  7. package/dist/commands/cardMarkStarted.js.map +1 -1
  8. package/dist/commands/consoleCommand.js +3 -3
  9. package/dist/commands/consoleCommand.js.map +1 -1
  10. package/dist/commands/hookCommand.d.ts +25 -0
  11. package/dist/commands/hookCommand.d.ts.map +1 -1
  12. package/dist/commands/hookCommand.js +2 -2
  13. package/dist/commands/hookCommand.js.map +1 -1
  14. package/dist/console/index.d.ts +15 -0
  15. package/dist/console/index.d.ts.map +1 -0
  16. package/dist/console/index.js +177 -0
  17. package/dist/console/index.js.map +1 -0
  18. package/dist/console/lib/lockFile.d.ts +17 -0
  19. package/dist/console/lib/lockFile.d.ts.map +1 -0
  20. package/dist/console/lib/lockFile.js +61 -0
  21. package/dist/console/lib/lockFile.js.map +1 -0
  22. package/dist/console/lib/portPicker.d.ts +3 -0
  23. package/dist/console/lib/portPicker.d.ts.map +1 -0
  24. package/dist/console/lib/portPicker.js +25 -0
  25. package/dist/console/lib/portPicker.js.map +1 -0
  26. package/dist/console/lib/resultToJson.d.ts +18 -0
  27. package/dist/console/lib/resultToJson.d.ts.map +1 -0
  28. package/dist/console/lib/resultToJson.js +19 -0
  29. package/dist/console/lib/resultToJson.js.map +1 -0
  30. package/dist/console/routes/cards.d.ts +26 -0
  31. package/dist/console/routes/cards.d.ts.map +1 -0
  32. package/dist/console/routes/cards.js +85 -0
  33. package/dist/console/routes/cards.js.map +1 -0
  34. package/dist/console/routes/chat.d.ts +36 -0
  35. package/dist/console/routes/chat.d.ts.map +1 -0
  36. package/dist/console/routes/chat.js +458 -0
  37. package/dist/console/routes/chat.js.map +1 -0
  38. package/dist/console/routes/logs.d.ts +17 -0
  39. package/dist/console/routes/logs.d.ts.map +1 -0
  40. package/dist/console/routes/logs.js +159 -0
  41. package/dist/console/routes/logs.js.map +1 -0
  42. package/dist/console/routes/pipeline.d.ts +10 -0
  43. package/dist/console/routes/pipeline.d.ts.map +1 -0
  44. package/dist/console/routes/pipeline.js +39 -0
  45. package/dist/console/routes/pipeline.js.map +1 -0
  46. package/dist/console/routes/projects.d.ts +15 -0
  47. package/dist/console/routes/projects.d.ts.map +1 -0
  48. package/dist/console/routes/projects.js +132 -0
  49. package/dist/console/routes/projects.js.map +1 -0
  50. package/dist/console/routes/skills.d.ts +4 -0
  51. package/dist/console/routes/skills.d.ts.map +1 -0
  52. package/dist/console/routes/skills.js +91 -0
  53. package/dist/console/routes/skills.js.map +1 -0
  54. package/dist/console/routes/system.d.ts +7 -0
  55. package/dist/console/routes/system.d.ts.map +1 -0
  56. package/dist/console/routes/system.js +288 -0
  57. package/dist/console/routes/system.js.map +1 -0
  58. package/dist/console/routes/workers.d.ts +5 -0
  59. package/dist/console/routes/workers.d.ts.map +1 -0
  60. package/dist/console/routes/workers.js +149 -0
  61. package/dist/console/routes/workers.js.map +1 -0
  62. package/dist/console/sse/eventBus.d.ts +25 -0
  63. package/dist/console/sse/eventBus.d.ts.map +1 -0
  64. package/dist/console/sse/eventBus.js +32 -0
  65. package/dist/console/sse/eventBus.js.map +1 -0
  66. package/dist/console/sse/projectStream.d.ts +13 -0
  67. package/dist/console/sse/projectStream.d.ts.map +1 -0
  68. package/dist/console/sse/projectStream.js +84 -0
  69. package/dist/console/sse/projectStream.js.map +1 -0
  70. package/dist/console-server/routes/projects.d.ts.map +1 -1
  71. package/dist/console-server/routes/projects.js +2 -1
  72. package/dist/console-server/routes/projects.js.map +1 -1
  73. package/dist/console-server/routes/system.js +1 -1
  74. package/dist/console-server/routes/system.js.map +1 -1
  75. package/dist/console-server/routes/workers.d.ts.map +1 -1
  76. package/dist/console-server/routes/workers.js +14 -5
  77. package/dist/console-server/routes/workers.js.map +1 -1
  78. package/dist/console-server/watchers/markerWatcher.d.ts.map +1 -1
  79. package/dist/console-server/watchers/markerWatcher.js +4 -2
  80. package/dist/console-server/watchers/markerWatcher.js.map +1 -1
  81. package/dist/infra/chokidarWatchers.d.ts +38 -0
  82. package/dist/infra/chokidarWatchers.d.ts.map +1 -0
  83. package/dist/infra/chokidarWatchers.js +213 -0
  84. package/dist/infra/chokidarWatchers.js.map +1 -0
  85. package/dist/infra/clock.d.ts +35 -0
  86. package/dist/infra/clock.d.ts.map +1 -0
  87. package/dist/infra/clock.js +45 -0
  88. package/dist/infra/clock.js.map +1 -0
  89. package/dist/infra/filesystem.d.ts +89 -0
  90. package/dist/infra/filesystem.d.ts.map +1 -0
  91. package/dist/infra/filesystem.js +247 -0
  92. package/dist/infra/filesystem.js.map +1 -0
  93. package/dist/infra/spawn.d.ts +67 -0
  94. package/dist/infra/spawn.d.ts.map +1 -0
  95. package/dist/infra/spawn.js +116 -0
  96. package/dist/infra/spawn.js.map +1 -0
  97. package/dist/infra/sseBus.d.ts +36 -0
  98. package/dist/infra/sseBus.d.ts.map +1 -0
  99. package/dist/infra/sseBus.js +72 -0
  100. package/dist/infra/sseBus.js.map +1 -0
  101. package/dist/interfaces/ACPClient.d.ts +1 -1
  102. package/dist/interfaces/ACPClient.d.ts.map +1 -1
  103. package/dist/main.js +1 -1
  104. package/dist/main.js.map +1 -1
  105. package/dist/providers/LocalACPClient.d.ts +1 -1
  106. package/dist/providers/LocalACPClient.d.ts.map +1 -1
  107. package/dist/services/CardService.d.ts +86 -0
  108. package/dist/services/CardService.d.ts.map +1 -0
  109. package/dist/services/CardService.js +313 -0
  110. package/dist/services/CardService.js.map +1 -0
  111. package/dist/services/ChatService.d.ts +62 -0
  112. package/dist/services/ChatService.d.ts.map +1 -0
  113. package/dist/services/ChatService.js +157 -0
  114. package/dist/services/ChatService.js.map +1 -0
  115. package/dist/services/LogService.d.ts +46 -0
  116. package/dist/services/LogService.d.ts.map +1 -0
  117. package/dist/services/LogService.js +185 -0
  118. package/dist/services/LogService.js.map +1 -0
  119. package/dist/services/PipelineService.d.ts +71 -0
  120. package/dist/services/PipelineService.d.ts.map +1 -0
  121. package/dist/services/PipelineService.js +349 -0
  122. package/dist/services/PipelineService.js.map +1 -0
  123. package/dist/services/ProjectService.d.ts +105 -0
  124. package/dist/services/ProjectService.d.ts.map +1 -0
  125. package/dist/services/ProjectService.js +346 -0
  126. package/dist/services/ProjectService.js.map +1 -0
  127. package/dist/services/SkillService.d.ts +38 -0
  128. package/dist/services/SkillService.d.ts.map +1 -0
  129. package/dist/services/SkillService.js +301 -0
  130. package/dist/services/SkillService.js.map +1 -0
  131. package/dist/services/WorkerService.d.ts +83 -0
  132. package/dist/services/WorkerService.d.ts.map +1 -0
  133. package/dist/services/WorkerService.js +262 -0
  134. package/dist/services/WorkerService.js.map +1 -0
  135. package/dist/services/container.d.ts +47 -0
  136. package/dist/services/container.d.ts.map +1 -0
  137. package/dist/services/container.js +77 -0
  138. package/dist/services/container.js.map +1 -0
  139. package/dist/services/defaults.d.ts +15 -0
  140. package/dist/services/defaults.d.ts.map +1 -0
  141. package/dist/services/defaults.js +11 -0
  142. package/dist/services/defaults.js.map +1 -0
  143. package/dist/services/executors.d.ts +38 -0
  144. package/dist/services/executors.d.ts.map +1 -0
  145. package/dist/services/executors.js +157 -0
  146. package/dist/services/executors.js.map +1 -0
  147. package/dist/services/ports.d.ts +17 -0
  148. package/dist/services/ports.d.ts.map +1 -0
  149. package/dist/services/ports.js +2 -0
  150. package/dist/services/ports.js.map +1 -0
  151. package/dist/shared/domainEvents.d.ts +118 -0
  152. package/dist/shared/domainEvents.d.ts.map +1 -0
  153. package/dist/shared/domainEvents.js +40 -0
  154. package/dist/shared/domainEvents.js.map +1 -0
  155. package/dist/shared/errors.d.ts +59 -0
  156. package/dist/shared/errors.d.ts.map +1 -0
  157. package/dist/shared/errors.js +79 -0
  158. package/dist/shared/errors.js.map +1 -0
  159. package/dist/shared/result.d.ts +51 -0
  160. package/dist/shared/result.d.ts.map +1 -0
  161. package/dist/shared/result.js +48 -0
  162. package/dist/shared/result.js.map +1 -0
  163. package/dist/shared/runtimePaths.d.ts +78 -0
  164. package/dist/shared/runtimePaths.d.ts.map +1 -0
  165. package/dist/shared/runtimePaths.js +179 -0
  166. package/dist/shared/runtimePaths.js.map +1 -0
  167. package/dist/shared/runtimeSchemas.d.ts +324 -0
  168. package/dist/shared/runtimeSchemas.d.ts.map +1 -0
  169. package/dist/shared/runtimeSchemas.js +124 -0
  170. package/dist/shared/runtimeSchemas.js.map +1 -0
  171. package/dist/shared/types.d.ts +12 -0
  172. package/dist/shared/types.d.ts.map +1 -0
  173. package/dist/shared/types.js +2 -0
  174. package/dist/shared/types.js.map +1 -0
  175. package/package.json +1 -1
@@ -0,0 +1,458 @@
1
+ /**
2
+ * @module console/routes/chat
3
+ * @description Chat sessions via daemon — structured streaming with tool events
4
+ *
5
+ * v0.46 重写:
6
+ * - 放弃 spawn sps agent + stdout 纯文本解析
7
+ * - 改用 DaemonClient.ensureSession + startRun + subscribeRun 拿结构化 AccumulatorEvent
8
+ * - 每个 chat session 映射到 daemon slot `session-chat-<sessionId>`,多轮对话有上下文
9
+ * - SSE 事件按 block 类型分开推(text / tool_use / tool_update / complete)
10
+ */
11
+ import { randomUUID } from 'node:crypto';
12
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
13
+ import { resolve } from 'node:path';
14
+ import { Hono } from 'hono';
15
+ import { ensureDaemon } from '../../commands/agentDaemon.js';
16
+ import { DaemonClient } from '../../daemon/daemonClient.js';
17
+ import { eventBus } from '../sse/eventBus.js';
18
+ const HOME = process.env.HOME || '/home/coral';
19
+ const SESSIONS_DIR = resolve(HOME, '.coral', 'chat-sessions');
20
+ // v0.47.1 保护:并发上限 + 单消息大小上限
21
+ const MAX_CONCURRENT_SESSIONS = 5;
22
+ const MAX_ASSISTANT_CONTENT_BYTES = 10 * 1024 * 1024; // 10MB
23
+ const activeSessions = new Set(); // chatSlot strings currently streaming
24
+ function ensureDir() {
25
+ if (!existsSync(SESSIONS_DIR))
26
+ mkdirSync(SESSIONS_DIR, { recursive: true });
27
+ }
28
+ function sessionPath(id) {
29
+ return resolve(SESSIONS_DIR, `${id}.json`);
30
+ }
31
+ function readSession(id) {
32
+ const p = sessionPath(id);
33
+ if (!existsSync(p))
34
+ return null;
35
+ try {
36
+ return JSON.parse(readFileSync(p, 'utf-8'));
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ function writeSession(s) {
43
+ ensureDir();
44
+ writeFileSync(sessionPath(s.id), JSON.stringify(s, null, 2));
45
+ }
46
+ function listSessions() {
47
+ if (!existsSync(SESSIONS_DIR))
48
+ return [];
49
+ return readdirSync(SESSIONS_DIR)
50
+ .filter((f) => f.endsWith('.json'))
51
+ .map((f) => {
52
+ try {
53
+ return JSON.parse(readFileSync(resolve(SESSIONS_DIR, f), 'utf-8'));
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ })
59
+ .filter((s) => s !== null)
60
+ .sort((a, b) => {
61
+ const ta = a.lastMessageAt ?? a.createdAt;
62
+ const tb = b.lastMessageAt ?? b.createdAt;
63
+ return tb.localeCompare(ta);
64
+ });
65
+ }
66
+ function summarize(s) {
67
+ const { messages, ...rest } = s;
68
+ void messages;
69
+ return rest;
70
+ }
71
+ function formatTitle(firstMessage) {
72
+ const clean = firstMessage.replace(/\s+/g, ' ').trim();
73
+ return clean.length > 60 ? `${clean.slice(0, 57)}...` : clean;
74
+ }
75
+ function chatSlot(sessionId) {
76
+ return `session-chat-${sessionId}`;
77
+ }
78
+ async function streamAssistantResponse(sessionId, assistantId, userContent, log) {
79
+ // 1. Ensure daemon is running
80
+ if (!(await ensureDaemon())) {
81
+ const errMsg = {
82
+ id: assistantId,
83
+ role: 'error',
84
+ content: 'Failed to start agent daemon',
85
+ ts: new Date().toISOString(),
86
+ status: 'error',
87
+ };
88
+ persistAndEmitComplete(sessionId, errMsg, log);
89
+ return;
90
+ }
91
+ const client = new DaemonClient();
92
+ const slot = chatSlot(sessionId);
93
+ activeSessions.add(slot);
94
+ // 2. Ensure session exists (reuses if present → multi-turn context)
95
+ try {
96
+ await client.ensureSession(slot, 'claude');
97
+ }
98
+ catch (err) {
99
+ activeSessions.delete(slot);
100
+ const errMsg = {
101
+ id: assistantId,
102
+ role: 'error',
103
+ content: `ensureSession failed: ${err instanceof Error ? err.message : String(err)}`,
104
+ ts: new Date().toISOString(),
105
+ status: 'error',
106
+ };
107
+ persistAndEmitComplete(sessionId, errMsg, log);
108
+ return;
109
+ }
110
+ // 3. Subscribe FIRST (before startRun) so we don't miss early events.
111
+ // Run the subscription loop in background.
112
+ const subscription = client.subscribeRun(slot);
113
+ const blocks = [];
114
+ let accumulatedText = '';
115
+ let truncated = false;
116
+ const consume = (async () => {
117
+ for await (const evt of subscription) {
118
+ if (evt.event === 'text') {
119
+ if (truncated)
120
+ continue; // drop further text after cap
121
+ const remainingBudget = MAX_ASSISTANT_CONTENT_BYTES - Buffer.byteLength(accumulatedText, 'utf-8');
122
+ if (remainingBudget <= 0) {
123
+ truncated = true;
124
+ log.warn(`chat[${sessionId}]: assistant output exceeded ${MAX_ASSISTANT_CONTENT_BYTES} bytes — truncating`);
125
+ continue;
126
+ }
127
+ const piece = Buffer.byteLength(evt.text, 'utf-8') <= remainingBudget
128
+ ? evt.text
129
+ : Buffer.from(evt.text, 'utf-8').subarray(0, remainingBudget).toString('utf-8');
130
+ accumulatedText += piece;
131
+ const last = blocks[blocks.length - 1];
132
+ if (last?.type === 'text')
133
+ last.text += piece;
134
+ else
135
+ blocks.push({ type: 'text', text: piece });
136
+ eventBus.publish('chat.message.chunk.text', {
137
+ sessionId,
138
+ assistantId,
139
+ text: piece,
140
+ });
141
+ if (piece.length < evt.text.length) {
142
+ truncated = true;
143
+ log.warn(`chat[${sessionId}]: truncated mid-chunk at ${MAX_ASSISTANT_CONTENT_BYTES} bytes`);
144
+ }
145
+ }
146
+ else if (evt.event === 'tool_use') {
147
+ blocks.push({
148
+ type: 'tool_use',
149
+ id: evt.id,
150
+ title: evt.title,
151
+ kind: evt.kind,
152
+ status: evt.status,
153
+ });
154
+ eventBus.publish('chat.message.chunk.tool_use', {
155
+ sessionId,
156
+ assistantId,
157
+ id: evt.id,
158
+ title: evt.title,
159
+ kind: evt.kind,
160
+ status: evt.status,
161
+ });
162
+ }
163
+ else if (evt.event === 'tool_update') {
164
+ const tool = blocks.find((b) => b.type === 'tool_use' && b.id === evt.id);
165
+ if (tool && tool.type === 'tool_use')
166
+ tool.status = evt.status;
167
+ eventBus.publish('chat.message.chunk.tool_update', {
168
+ sessionId,
169
+ assistantId,
170
+ id: evt.id,
171
+ status: evt.status,
172
+ });
173
+ }
174
+ else if (evt.event === 'complete') {
175
+ const ts = new Date().toISOString();
176
+ const isError = evt.stopReason === 'failed';
177
+ const isCancelled = evt.stopReason === 'cancelled';
178
+ const msg = {
179
+ id: assistantId,
180
+ role: isError ? 'error' : 'assistant',
181
+ content: accumulatedText.trim() ||
182
+ (isError ? `stopped: ${evt.stopReason}` : isCancelled ? '(已中断)' : ''),
183
+ blocks: blocks.length > 0 ? blocks : undefined,
184
+ truncated: truncated || undefined,
185
+ ts,
186
+ status: isError ? 'error' : 'complete',
187
+ };
188
+ persistAndEmitComplete(sessionId, msg, log);
189
+ break;
190
+ }
191
+ }
192
+ })();
193
+ // 4. Start the run (fire and forget — events flow through subscription)
194
+ try {
195
+ await client.startRun(slot, userContent, 'claude');
196
+ }
197
+ catch (err) {
198
+ subscription.cancel();
199
+ activeSessions.delete(slot);
200
+ const errMsg = {
201
+ id: assistantId,
202
+ role: 'error',
203
+ content: `startRun failed: ${err instanceof Error ? err.message : String(err)}`,
204
+ ts: new Date().toISOString(),
205
+ status: 'error',
206
+ };
207
+ persistAndEmitComplete(sessionId, errMsg, log);
208
+ return;
209
+ }
210
+ try {
211
+ // Await subscription loop (background — but chat POST already returned 202)
212
+ await consume;
213
+ // 5. Clear run so slot is reusable for next turn
214
+ try {
215
+ await client.clearRun(slot);
216
+ }
217
+ catch {
218
+ /* best effort */
219
+ }
220
+ }
221
+ finally {
222
+ activeSessions.delete(slot);
223
+ }
224
+ }
225
+ function persistAndEmitComplete(sessionId, msg, log) {
226
+ const session = readSession(sessionId);
227
+ if (!session)
228
+ return;
229
+ session.messages.push(msg);
230
+ session.lastMessageAt = msg.ts;
231
+ session.messageCount = session.messages.length;
232
+ writeSession(session);
233
+ eventBus.publish('chat.message.complete', {
234
+ sessionId,
235
+ assistantId: msg.id,
236
+ message: msg,
237
+ });
238
+ if (msg.role === 'error')
239
+ log.warn(`chat[${sessionId}]: ${msg.content}`);
240
+ else
241
+ log.ok(`chat[${sessionId}]: assistant complete (${msg.content.length} chars, ${msg.blocks?.length ?? 0} blocks)`);
242
+ }
243
+ export function createChatRoute(log) {
244
+ const app = new Hono();
245
+ app.get('/sessions', (c) => {
246
+ return c.json({ data: listSessions().map(summarize) });
247
+ });
248
+ app.post('/sessions', async (c) => {
249
+ const body = await c.req.json().catch(() => ({}));
250
+ const session = {
251
+ id: randomUUID(),
252
+ createdAt: new Date().toISOString(),
253
+ lastMessageAt: null,
254
+ title: typeof body?.title === 'string' && body.title ? body.title : '新对话',
255
+ project: typeof body?.project === 'string' ? body.project : null,
256
+ messageCount: 0,
257
+ messages: [],
258
+ };
259
+ writeSession(session);
260
+ eventBus.publish('chat.session.created', { sessionId: session.id });
261
+ return c.json(summarize(session), 201);
262
+ });
263
+ app.get('/sessions/:id', (c) => {
264
+ const id = c.req.param('id');
265
+ const s = readSession(id);
266
+ if (!s)
267
+ return c.json({ type: 'not-found', title: 'Session not found', status: 404 }, 404);
268
+ return c.json(s);
269
+ });
270
+ /**
271
+ * GET /sessions/:id/messages?since=<iso-ts> — Diff fetch for reconnect.
272
+ * Returns only messages with ts > since. Frontend calls this after SSE
273
+ * reconnect to catch up on anything persisted during the gap.
274
+ */
275
+ app.get('/sessions/:id/messages', (c) => {
276
+ const id = c.req.param('id');
277
+ const s = readSession(id);
278
+ if (!s)
279
+ return c.json({ type: 'not-found', title: 'Session not found', status: 404 }, 404);
280
+ const since = c.req.query('since');
281
+ const messages = since
282
+ ? s.messages.filter((m) => m.ts > since)
283
+ : s.messages;
284
+ return c.json({ data: messages, total: s.messages.length });
285
+ });
286
+ app.delete('/sessions/:id', async (c) => {
287
+ const id = c.req.param('id');
288
+ const p = sessionPath(id);
289
+ if (existsSync(p))
290
+ rmSync(p);
291
+ // Also stop the daemon session so ACP child process is freed
292
+ try {
293
+ const client = new DaemonClient();
294
+ if (await client.isRunning()) {
295
+ await client.stopSession(chatSlot(id)).catch(() => { });
296
+ }
297
+ }
298
+ catch { /* best effort */ }
299
+ eventBus.publish('chat.session.deleted', { sessionId: id });
300
+ return c.body(null, 204);
301
+ });
302
+ /**
303
+ * POST /sessions/:id/interrupt — Cancel the in-flight assistant turn.
304
+ * Session stays alive so user can send another message with context preserved.
305
+ * Accumulator emits 'complete' event with stopReason='cancelled' → subscription loop exits.
306
+ */
307
+ app.post('/sessions/:id/interrupt', async (c) => {
308
+ const id = c.req.param('id');
309
+ const client = new DaemonClient();
310
+ if (!(await client.isRunning())) {
311
+ return c.json({ type: 'not-found', title: 'daemon not running', status: 404 }, 404);
312
+ }
313
+ try {
314
+ await client.cancelRun(chatSlot(id));
315
+ }
316
+ catch (err) {
317
+ return c.json({
318
+ type: 'internal',
319
+ title: 'cancel failed',
320
+ status: 500,
321
+ detail: err instanceof Error ? err.message : String(err),
322
+ }, 500);
323
+ }
324
+ return c.body(null, 204);
325
+ });
326
+ /**
327
+ * POST messages: 非阻塞
328
+ * 1. 持久化 user msg → 立即返回 user + assistantId (202)
329
+ * 2. 后台走 daemon,SSE 推 chunk.text / chunk.tool_use / chunk.tool_update / complete
330
+ */
331
+ app.post('/sessions/:id/messages', async (c) => {
332
+ const id = c.req.param('id');
333
+ const s = readSession(id);
334
+ if (!s)
335
+ return c.json({ type: 'not-found', title: 'Session not found', status: 404 }, 404);
336
+ const body = (await c.req.json().catch(() => null));
337
+ if (!body?.content || typeof body.content !== 'string') {
338
+ return c.json({ type: 'validation', title: 'content required', status: 422 }, 422);
339
+ }
340
+ // v0.47.1 并发上限:资源保护。当前 session 若已经在跑,也拒绝(避免重复 startRun)
341
+ if (activeSessions.has(chatSlot(id))) {
342
+ return c.json({
343
+ type: 'conflict',
344
+ title: 'session busy',
345
+ status: 409,
346
+ detail: '上一条消息还在生成中,先等完成或点中断',
347
+ }, 409);
348
+ }
349
+ if (activeSessions.size >= MAX_CONCURRENT_SESSIONS) {
350
+ return c.json({
351
+ type: 'too-many-requests',
352
+ title: 'concurrency limit',
353
+ status: 429,
354
+ detail: `最多同时 ${MAX_CONCURRENT_SESSIONS} 个 chat 会话在生成。先等其它完成或中断。`,
355
+ }, 429);
356
+ }
357
+ const userMsg = {
358
+ id: randomUUID(),
359
+ role: 'user',
360
+ content: body.content,
361
+ ts: new Date().toISOString(),
362
+ status: 'complete',
363
+ };
364
+ s.messages.push(userMsg);
365
+ s.lastMessageAt = userMsg.ts;
366
+ s.messageCount = s.messages.length;
367
+ if (s.messages.length === 1)
368
+ s.title = formatTitle(body.content);
369
+ writeSession(s);
370
+ eventBus.publish('chat.message', { sessionId: id, message: userMsg });
371
+ const assistantId = randomUUID();
372
+ eventBus.publish('chat.message.pending', {
373
+ sessionId: id,
374
+ assistantId,
375
+ ts: new Date().toISOString(),
376
+ });
377
+ // Fire and forget — events stream via SSE
378
+ streamAssistantResponse(id, assistantId, body.content, log).catch((err) => {
379
+ log.error(`chat[${id}] stream error: ${err instanceof Error ? err.message : String(err)}`);
380
+ });
381
+ return c.json({ user: userMsg, assistantId }, 202);
382
+ });
383
+ return app;
384
+ }
385
+ // ── SSE: /stream/chat/:id ───────────────────────────────────────────────
386
+ export function createChatStreamRoute() {
387
+ const app = new Hono();
388
+ app.get('/:id', (c) => {
389
+ const sessionId = c.req.param('id');
390
+ const stream = new ReadableStream({
391
+ start(controller) {
392
+ const enc = new TextEncoder();
393
+ let closed = false;
394
+ const send = (event, data) => {
395
+ if (closed)
396
+ return;
397
+ try {
398
+ controller.enqueue(enc.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
399
+ }
400
+ catch {
401
+ closed = true;
402
+ }
403
+ };
404
+ const eventTypes = [
405
+ 'chat.message',
406
+ 'chat.message.pending',
407
+ 'chat.message.chunk.text',
408
+ 'chat.message.chunk.tool_use',
409
+ 'chat.message.chunk.tool_update',
410
+ 'chat.message.complete',
411
+ ];
412
+ const handlers = [];
413
+ for (const event of eventTypes) {
414
+ const fn = (data) => {
415
+ if (typeof data !== 'object' ||
416
+ data === null ||
417
+ data.sessionId !== sessionId)
418
+ return;
419
+ send(event, data);
420
+ };
421
+ eventBus.on(event, fn);
422
+ handlers.push({ event, fn });
423
+ }
424
+ const heartbeat = setInterval(() => {
425
+ if (closed)
426
+ return;
427
+ try {
428
+ controller.enqueue(enc.encode(`: heartbeat ${Date.now()}\n\n`));
429
+ }
430
+ catch {
431
+ closed = true;
432
+ }
433
+ }, 15_000);
434
+ c.req.raw.signal?.addEventListener('abort', () => {
435
+ closed = true;
436
+ for (const h of handlers)
437
+ eventBus.off(h.event, h.fn);
438
+ clearInterval(heartbeat);
439
+ try {
440
+ controller.close();
441
+ }
442
+ catch {
443
+ /* ignore */
444
+ }
445
+ });
446
+ },
447
+ });
448
+ return new Response(stream, {
449
+ headers: {
450
+ 'Content-Type': 'text/event-stream',
451
+ 'Cache-Control': 'no-cache, no-transform',
452
+ Connection: 'keep-alive',
453
+ },
454
+ });
455
+ });
456
+ return app;
457
+ }
458
+ //# sourceMappingURL=chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../../../src/console/routes/chat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @module console/routes/logs
3
+ * @description 日志 REST + SSE stream
4
+ *
5
+ * @layer console
6
+ *
7
+ * v0.50 重构:query 路径走 LogService;SSE stream 仍用 fs.watch(Delivery 专属)。
8
+ */
9
+ import { Hono } from 'hono';
10
+ import type { Logger } from '../../core/logger.js';
11
+ import { type LogService } from '../../services/LogService.js';
12
+ export declare function createLogsRoute(logs: LogService): Hono;
13
+ /**
14
+ * SSE stream —— 保留原 fs.watch 逻辑,属于 Delivery 专属(service 不做实时流)。
15
+ */
16
+ export declare function createLogsStreamRoute(log: Logger): Hono;
17
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../../src/console/routes/logs.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,8BAA8B,CAAC;AAM7E,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAiBtD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAsGvD"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * @module console/routes/logs
3
+ * @description 日志 REST + SSE stream
4
+ *
5
+ * @layer console
6
+ *
7
+ * v0.50 重构:query 路径走 LogService;SSE stream 仍用 fs.watch(Delivery 专属)。
8
+ */
9
+ import { createReadStream, existsSync, watch as fsWatch, readdirSync, statSync, } from 'node:fs';
10
+ import { resolve } from 'node:path';
11
+ import { createInterface } from 'node:readline';
12
+ import { Hono } from 'hono';
13
+ import { parseLogLine } from '../../services/LogService.js';
14
+ import { home, logsDir, workerLogLineTag } from '../../shared/runtimePaths.js';
15
+ import { sendResult } from '../lib/resultToJson.js';
16
+ const SSE_HEARTBEAT_MS = 15_000;
17
+ export function createLogsRoute(logs) {
18
+ const app = new Hono();
19
+ app.get('/', async (c) => {
20
+ const project = c.req.query('project');
21
+ const workerStr = c.req.query('worker');
22
+ const limit = Number.parseInt(c.req.query('limit') ?? '500', 10) || undefined;
23
+ const since = c.req.query('since');
24
+ const worker = workerStr ? Number.parseInt(workerStr, 10) : undefined;
25
+ if (!project) {
26
+ return sendResult(c, await logs.aggregate({ worker, limit, since }));
27
+ }
28
+ return sendResult(c, await logs.tail({ project, worker, limit, since }));
29
+ });
30
+ return app;
31
+ }
32
+ /**
33
+ * SSE stream —— 保留原 fs.watch 逻辑,属于 Delivery 专属(service 不做实时流)。
34
+ */
35
+ export function createLogsStreamRoute(log) {
36
+ const app = new Hono();
37
+ app.get('/', (c) => {
38
+ const project = c.req.query('project');
39
+ if (!project) {
40
+ return c.text('project required', 422);
41
+ }
42
+ const workerStr = c.req.query('worker');
43
+ const worker = workerStr ? Number.parseInt(workerStr, 10) : undefined;
44
+ const files = findLogFilesRaw(project, worker);
45
+ const file = files[0];
46
+ const stream = new ReadableStream({
47
+ start(controller) {
48
+ const enc = new TextEncoder();
49
+ let closed = false;
50
+ const send = (event, data) => {
51
+ if (closed)
52
+ return;
53
+ try {
54
+ controller.enqueue(enc.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
55
+ }
56
+ catch {
57
+ closed = true;
58
+ }
59
+ };
60
+ let watcher = null;
61
+ let lastSize = 0;
62
+ if (file && existsSync(file)) {
63
+ try {
64
+ lastSize = statSync(file).size;
65
+ }
66
+ catch {
67
+ lastSize = 0;
68
+ }
69
+ try {
70
+ watcher = fsWatch(file, async () => {
71
+ if (closed)
72
+ return;
73
+ try {
74
+ const stat = statSync(file);
75
+ if (stat.size <= lastSize) {
76
+ lastSize = stat.size;
77
+ return;
78
+ }
79
+ const newChunk = [];
80
+ await new Promise((done) => {
81
+ const s = createReadStream(file, {
82
+ start: lastSize,
83
+ end: stat.size,
84
+ encoding: 'utf-8',
85
+ });
86
+ const rl = createInterface({ input: s });
87
+ rl.on('line', (l) => newChunk.push(l));
88
+ rl.on('close', () => done());
89
+ });
90
+ lastSize = stat.size;
91
+ for (const l of newChunk) {
92
+ if (!l.trim())
93
+ continue;
94
+ send('log.line', parseLogLine(l));
95
+ }
96
+ }
97
+ catch (err) {
98
+ log.warn(`log watch failed: ${err instanceof Error ? err.message : String(err)}`);
99
+ }
100
+ });
101
+ }
102
+ catch (err) {
103
+ log.warn(`fs.watch failed: ${err instanceof Error ? err.message : String(err)}`);
104
+ }
105
+ }
106
+ const heartbeat = setInterval(() => {
107
+ if (closed)
108
+ return;
109
+ try {
110
+ controller.enqueue(enc.encode(`: heartbeat ${Date.now()}\n\n`));
111
+ }
112
+ catch {
113
+ closed = true;
114
+ }
115
+ }, SSE_HEARTBEAT_MS);
116
+ send('log.init', { file: file?.replace(home(), '~') ?? null });
117
+ c.req.raw.signal?.addEventListener('abort', () => {
118
+ closed = true;
119
+ watcher?.close();
120
+ clearInterval(heartbeat);
121
+ try {
122
+ controller.close();
123
+ }
124
+ catch {
125
+ /* ignore */
126
+ }
127
+ });
128
+ },
129
+ });
130
+ return new Response(stream, {
131
+ headers: {
132
+ 'Content-Type': 'text/event-stream',
133
+ 'Cache-Control': 'no-cache, no-transform',
134
+ Connection: 'keep-alive',
135
+ },
136
+ });
137
+ });
138
+ return app;
139
+ }
140
+ /** SSE 专属:扫 logs 目录找最新文件(LogService.tail 内部一样逻辑,这里 Delivery 层需要路径)。 */
141
+ function findLogFilesRaw(project, worker) {
142
+ const dir = logsDir(project);
143
+ if (!existsSync(dir))
144
+ return [];
145
+ const tag = worker !== undefined ? workerLogLineTag(worker) : null;
146
+ return readdirSync(dir)
147
+ .filter((f) => f.endsWith('.log'))
148
+ .filter((f) => (tag ? f.includes(tag) || f.includes(`-${worker}-`) : true))
149
+ .map((f) => resolve(dir, f))
150
+ .sort((a, b) => {
151
+ try {
152
+ return statSync(b).mtimeMs - statSync(a).mtimeMs;
153
+ }
154
+ catch {
155
+ return 0;
156
+ }
157
+ });
158
+ }
159
+ //# sourceMappingURL=logs.js.map