@wrongstack/desktop 0.280.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/LICENSE +21 -0
- package/bin/wrongstack-desktop.js +38 -0
- package/dist/main/agent-bridge.js +7 -0
- package/dist/main/agent-bridge.js.map +1 -0
- package/dist/main/chunk-F3LGFYUK.js +239 -0
- package/dist/main/chunk-F3LGFYUK.js.map +1 -0
- package/dist/main/main.js +2293 -0
- package/dist/main/main.js.map +1 -0
- package/dist/preload/preload.cjs +90 -0
- package/dist/preload/preload.cjs.map +1 -0
- package/dist/preload/webui-preload.cjs +84 -0
- package/dist/preload/webui-preload.cjs.map +1 -0
- package/dist/renderer/assets/index-BAP2wfHP.css +1 -0
- package/dist/renderer/assets/index-Cq-Fc_6i.js +395 -0
- package/dist/renderer/assets/index-Cq-Fc_6i.js.map +1 -0
- package/dist/renderer/index.html +13 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ECOSTACK TECHNOLOGY OÜ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import electron from 'electron';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
10
|
+
const mainEntry = path.join(packageRoot, 'dist', 'main', 'main.js');
|
|
11
|
+
|
|
12
|
+
if (!existsSync(mainEntry)) {
|
|
13
|
+
console.error(
|
|
14
|
+
[
|
|
15
|
+
'WrongStack Desktop is not built.',
|
|
16
|
+
'',
|
|
17
|
+
'From the repository, run:',
|
|
18
|
+
' pnpm --filter @wrongstack/desktop build',
|
|
19
|
+
'',
|
|
20
|
+
].join('\n'),
|
|
21
|
+
);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const child = spawn(electron, [packageRoot, ...process.argv.slice(2)], {
|
|
26
|
+
stdio: 'inherit',
|
|
27
|
+
env: process.env,
|
|
28
|
+
windowsHide: false,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
child.once('error', (err) => {
|
|
32
|
+
console.error(`Failed to start WrongStack Desktop: ${err.message}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
child.once('exit', (code, signal) => {
|
|
37
|
+
process.exitCode = typeof code === 'number' ? code : signal ? 1 : 0;
|
|
38
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// src/main/agent-bridge.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { EventEmitter } from "events";
|
|
4
|
+
import WebSocket from "ws";
|
|
5
|
+
var MAX_MESSAGES = 300;
|
|
6
|
+
var DesktopAgentBridge = class extends EventEmitter {
|
|
7
|
+
conversations = /* @__PURE__ */ new Map();
|
|
8
|
+
snapshot(runtimeId) {
|
|
9
|
+
return publicConversation(this.getOrCreate(runtimeId));
|
|
10
|
+
}
|
|
11
|
+
async ensureConnected(runtimeId, wsUrl) {
|
|
12
|
+
const conversation = this.getOrCreate(runtimeId);
|
|
13
|
+
if (conversation.ws?.readyState === WebSocket.OPEN) return publicConversation(conversation);
|
|
14
|
+
if (conversation.connectPromise) {
|
|
15
|
+
await conversation.connectPromise;
|
|
16
|
+
return publicConversation(conversation);
|
|
17
|
+
}
|
|
18
|
+
conversation.status = "connecting";
|
|
19
|
+
conversation.error = void 0;
|
|
20
|
+
this.emitChanged(conversation);
|
|
21
|
+
conversation.connectPromise = new Promise((resolve, reject) => {
|
|
22
|
+
const ws = new WebSocket(wsUrl);
|
|
23
|
+
conversation.ws = ws;
|
|
24
|
+
ws.once("open", () => {
|
|
25
|
+
conversation.status = "connected";
|
|
26
|
+
conversation.error = void 0;
|
|
27
|
+
conversation.connectPromise = null;
|
|
28
|
+
this.emitChanged(conversation);
|
|
29
|
+
resolve();
|
|
30
|
+
});
|
|
31
|
+
ws.on("message", (data) => {
|
|
32
|
+
this.handleServerMessage(conversation, data.toString());
|
|
33
|
+
});
|
|
34
|
+
ws.once("error", (err) => {
|
|
35
|
+
conversation.status = "error";
|
|
36
|
+
conversation.error = err instanceof Error ? err.message : String(err);
|
|
37
|
+
conversation.connectPromise = null;
|
|
38
|
+
this.appendMessage(conversation, {
|
|
39
|
+
role: "system",
|
|
40
|
+
text: `Connection error: ${conversation.error}`
|
|
41
|
+
});
|
|
42
|
+
reject(err);
|
|
43
|
+
});
|
|
44
|
+
ws.once("close", () => {
|
|
45
|
+
if (conversation.ws === ws) conversation.ws = null;
|
|
46
|
+
conversation.connectPromise = null;
|
|
47
|
+
if (conversation.status !== "error") {
|
|
48
|
+
conversation.status = "disconnected";
|
|
49
|
+
}
|
|
50
|
+
conversation.activeAssistantMessageId = null;
|
|
51
|
+
this.emitChanged(conversation);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
await conversation.connectPromise;
|
|
55
|
+
return publicConversation(conversation);
|
|
56
|
+
}
|
|
57
|
+
async sendMessage(runtimeId, wsUrl, content) {
|
|
58
|
+
const trimmed = content.trim();
|
|
59
|
+
if (!trimmed) return this.snapshot(runtimeId);
|
|
60
|
+
await this.ensureConnected(runtimeId, wsUrl);
|
|
61
|
+
const conversation = this.getOrCreate(runtimeId);
|
|
62
|
+
this.appendMessage(conversation, {
|
|
63
|
+
id: `user_${randomUUID()}`,
|
|
64
|
+
role: "user",
|
|
65
|
+
text: trimmed
|
|
66
|
+
});
|
|
67
|
+
conversation.status = "running";
|
|
68
|
+
conversation.activeAssistantMessageId = null;
|
|
69
|
+
this.emitChanged(conversation);
|
|
70
|
+
this.send(conversation, {
|
|
71
|
+
type: "user_message",
|
|
72
|
+
payload: {
|
|
73
|
+
id: `msg_${Date.now()}_${randomUUID().slice(0, 8)}`,
|
|
74
|
+
content: trimmed,
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
...conversation.sessionId ? { sessionId: conversation.sessionId } : {}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return publicConversation(conversation);
|
|
80
|
+
}
|
|
81
|
+
async abort(runtimeId, wsUrl) {
|
|
82
|
+
await this.ensureConnected(runtimeId, wsUrl);
|
|
83
|
+
const conversation = this.getOrCreate(runtimeId);
|
|
84
|
+
this.send(conversation, {
|
|
85
|
+
type: "abort",
|
|
86
|
+
payload: conversation.sessionId ? { sessionId: conversation.sessionId } : {}
|
|
87
|
+
});
|
|
88
|
+
conversation.status = "connected";
|
|
89
|
+
this.appendMessage(conversation, { role: "system", text: "Abort requested." });
|
|
90
|
+
return publicConversation(conversation);
|
|
91
|
+
}
|
|
92
|
+
close(runtimeId) {
|
|
93
|
+
const conversation = this.conversations.get(runtimeId);
|
|
94
|
+
if (!conversation) return;
|
|
95
|
+
conversation.ws?.close();
|
|
96
|
+
conversation.ws = null;
|
|
97
|
+
conversation.connectPromise = null;
|
|
98
|
+
conversation.status = "disconnected";
|
|
99
|
+
conversation.activeAssistantMessageId = null;
|
|
100
|
+
this.emitChanged(conversation);
|
|
101
|
+
}
|
|
102
|
+
closeAll() {
|
|
103
|
+
for (const runtimeId of this.conversations.keys()) {
|
|
104
|
+
this.close(runtimeId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
handleServerMessage(conversation, raw) {
|
|
108
|
+
let message;
|
|
109
|
+
try {
|
|
110
|
+
message = JSON.parse(raw);
|
|
111
|
+
} catch {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const payload = message.payload ?? {};
|
|
115
|
+
switch (message.type) {
|
|
116
|
+
case "session.start": {
|
|
117
|
+
const sessionId = stringValue(payload["sessionId"]);
|
|
118
|
+
if (sessionId) conversation.sessionId = sessionId;
|
|
119
|
+
conversation.status = conversation.status === "running" ? "running" : "connected";
|
|
120
|
+
this.emitChanged(conversation);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "provider.text_delta": {
|
|
124
|
+
conversation.status = "running";
|
|
125
|
+
this.appendAssistantDelta(conversation, stringValue(payload["text"]) ?? "");
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "tool.started": {
|
|
129
|
+
conversation.status = "running";
|
|
130
|
+
const name = stringValue(payload["name"]) ?? "tool";
|
|
131
|
+
this.appendMessage(conversation, { role: "tool", text: `Started ${name}` });
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case "tool.executed": {
|
|
135
|
+
const name = stringValue(payload["name"]) ?? "tool";
|
|
136
|
+
const ok = payload["ok"] === true;
|
|
137
|
+
this.appendMessage(conversation, {
|
|
138
|
+
role: "tool",
|
|
139
|
+
text: `${name} ${ok ? "completed" : "failed"}`
|
|
140
|
+
});
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "provider.error":
|
|
144
|
+
case "provider.stream_error":
|
|
145
|
+
case "error": {
|
|
146
|
+
const text = stringValue(payload["message"]) ?? stringValue(payload["description"]) ?? `${message.type} received`;
|
|
147
|
+
conversation.status = "error";
|
|
148
|
+
conversation.error = text;
|
|
149
|
+
this.appendMessage(conversation, { role: "system", text });
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "run.result": {
|
|
153
|
+
const finalText = stringValue(payload["finalText"]);
|
|
154
|
+
if (finalText && !this.lastAssistantHasText(conversation)) {
|
|
155
|
+
this.appendMessage(conversation, { role: "assistant", text: finalText });
|
|
156
|
+
}
|
|
157
|
+
conversation.status = payload["status"] === "failed" ? "error" : "connected";
|
|
158
|
+
conversation.activeAssistantMessageId = null;
|
|
159
|
+
this.emitChanged(conversation);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
send(conversation, message) {
|
|
165
|
+
if (conversation.ws?.readyState !== WebSocket.OPEN) {
|
|
166
|
+
throw new Error("Runtime socket is not connected");
|
|
167
|
+
}
|
|
168
|
+
conversation.ws.send(JSON.stringify(message));
|
|
169
|
+
}
|
|
170
|
+
appendAssistantDelta(conversation, text) {
|
|
171
|
+
if (!text) return;
|
|
172
|
+
let message = conversation.messages.find((m) => m.id === conversation.activeAssistantMessageId);
|
|
173
|
+
if (!message) {
|
|
174
|
+
message = {
|
|
175
|
+
id: `assistant_${randomUUID()}`,
|
|
176
|
+
role: "assistant",
|
|
177
|
+
text: "",
|
|
178
|
+
timestamp: Date.now()
|
|
179
|
+
};
|
|
180
|
+
conversation.activeAssistantMessageId = message.id;
|
|
181
|
+
conversation.messages.push(message);
|
|
182
|
+
}
|
|
183
|
+
message.text += text;
|
|
184
|
+
this.trimMessages(conversation);
|
|
185
|
+
this.emitChanged(conversation);
|
|
186
|
+
}
|
|
187
|
+
appendMessage(conversation, input) {
|
|
188
|
+
conversation.messages.push({
|
|
189
|
+
id: input.id ?? `${input.role}_${randomUUID()}`,
|
|
190
|
+
role: input.role,
|
|
191
|
+
text: input.text,
|
|
192
|
+
timestamp: input.timestamp ?? Date.now()
|
|
193
|
+
});
|
|
194
|
+
this.trimMessages(conversation);
|
|
195
|
+
this.emitChanged(conversation);
|
|
196
|
+
}
|
|
197
|
+
lastAssistantHasText(conversation) {
|
|
198
|
+
const lastAssistant = [...conversation.messages].reverse().find((m) => m.role === "assistant");
|
|
199
|
+
return Boolean(lastAssistant?.text.trim());
|
|
200
|
+
}
|
|
201
|
+
trimMessages(conversation) {
|
|
202
|
+
if (conversation.messages.length <= MAX_MESSAGES) return;
|
|
203
|
+
conversation.messages.splice(0, conversation.messages.length - MAX_MESSAGES);
|
|
204
|
+
}
|
|
205
|
+
getOrCreate(runtimeId) {
|
|
206
|
+
let conversation = this.conversations.get(runtimeId);
|
|
207
|
+
if (conversation) return conversation;
|
|
208
|
+
conversation = {
|
|
209
|
+
runtimeId,
|
|
210
|
+
status: "disconnected",
|
|
211
|
+
messages: [],
|
|
212
|
+
ws: null,
|
|
213
|
+
connectPromise: null,
|
|
214
|
+
activeAssistantMessageId: null
|
|
215
|
+
};
|
|
216
|
+
this.conversations.set(runtimeId, conversation);
|
|
217
|
+
return conversation;
|
|
218
|
+
}
|
|
219
|
+
emitChanged(conversation) {
|
|
220
|
+
this.emit("changed", publicConversation(conversation));
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
function publicConversation(conversation) {
|
|
224
|
+
return {
|
|
225
|
+
runtimeId: conversation.runtimeId,
|
|
226
|
+
status: conversation.status,
|
|
227
|
+
sessionId: conversation.sessionId,
|
|
228
|
+
error: conversation.error,
|
|
229
|
+
messages: conversation.messages.map((message) => ({ ...message }))
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function stringValue(value) {
|
|
233
|
+
return typeof value === "string" ? value : void 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export {
|
|
237
|
+
DesktopAgentBridge
|
|
238
|
+
};
|
|
239
|
+
//# sourceMappingURL=chunk-F3LGFYUK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/main/agent-bridge.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { EventEmitter } from 'node:events';\nimport WebSocket from 'ws';\nimport type {\n DesktopConversationMessage,\n DesktopConversationSnapshot,\n DesktopConversationStatus,\n} from '../shared/types.js';\n\ninterface ConversationInternal {\n runtimeId: string;\n status: DesktopConversationStatus;\n sessionId?: string | undefined;\n error?: string | undefined;\n messages: DesktopConversationMessage[];\n ws: WebSocket | null;\n connectPromise: Promise<void> | null;\n activeAssistantMessageId: string | null;\n}\n\ninterface ServerMessage {\n type: string;\n payload?: Record<string, unknown> | undefined;\n}\n\nconst MAX_MESSAGES = 300;\n\nexport class DesktopAgentBridge extends EventEmitter {\n private readonly conversations = new Map<string, ConversationInternal>();\n\n snapshot(runtimeId: string): DesktopConversationSnapshot {\n return publicConversation(this.getOrCreate(runtimeId));\n }\n\n async ensureConnected(runtimeId: string, wsUrl: string): Promise<DesktopConversationSnapshot> {\n const conversation = this.getOrCreate(runtimeId);\n if (conversation.ws?.readyState === WebSocket.OPEN) return publicConversation(conversation);\n if (conversation.connectPromise) {\n await conversation.connectPromise;\n return publicConversation(conversation);\n }\n\n conversation.status = 'connecting';\n conversation.error = undefined;\n this.emitChanged(conversation);\n\n conversation.connectPromise = new Promise((resolve, reject) => {\n const ws = new WebSocket(wsUrl);\n conversation.ws = ws;\n\n ws.once('open', () => {\n conversation.status = 'connected';\n conversation.error = undefined;\n conversation.connectPromise = null;\n this.emitChanged(conversation);\n resolve();\n });\n\n ws.on('message', (data) => {\n this.handleServerMessage(conversation, data.toString());\n });\n\n ws.once('error', (err) => {\n conversation.status = 'error';\n conversation.error = err instanceof Error ? err.message : String(err);\n conversation.connectPromise = null;\n this.appendMessage(conversation, {\n role: 'system',\n text: `Connection error: ${conversation.error}`,\n });\n reject(err);\n });\n\n ws.once('close', () => {\n if (conversation.ws === ws) conversation.ws = null;\n conversation.connectPromise = null;\n if (conversation.status !== 'error') {\n conversation.status = 'disconnected';\n }\n conversation.activeAssistantMessageId = null;\n this.emitChanged(conversation);\n });\n });\n\n await conversation.connectPromise;\n return publicConversation(conversation);\n }\n\n async sendMessage(\n runtimeId: string,\n wsUrl: string,\n content: string,\n ): Promise<DesktopConversationSnapshot> {\n const trimmed = content.trim();\n if (!trimmed) return this.snapshot(runtimeId);\n await this.ensureConnected(runtimeId, wsUrl);\n const conversation = this.getOrCreate(runtimeId);\n this.appendMessage(conversation, {\n id: `user_${randomUUID()}`,\n role: 'user',\n text: trimmed,\n });\n conversation.status = 'running';\n conversation.activeAssistantMessageId = null;\n this.emitChanged(conversation);\n this.send(conversation, {\n type: 'user_message',\n payload: {\n id: `msg_${Date.now()}_${randomUUID().slice(0, 8)}`,\n content: trimmed,\n timestamp: Date.now(),\n ...(conversation.sessionId ? { sessionId: conversation.sessionId } : {}),\n },\n });\n return publicConversation(conversation);\n }\n\n async abort(runtimeId: string, wsUrl: string): Promise<DesktopConversationSnapshot> {\n await this.ensureConnected(runtimeId, wsUrl);\n const conversation = this.getOrCreate(runtimeId);\n this.send(conversation, {\n type: 'abort',\n payload: conversation.sessionId ? { sessionId: conversation.sessionId } : {},\n });\n conversation.status = 'connected';\n this.appendMessage(conversation, { role: 'system', text: 'Abort requested.' });\n return publicConversation(conversation);\n }\n\n close(runtimeId: string): void {\n const conversation = this.conversations.get(runtimeId);\n if (!conversation) return;\n conversation.ws?.close();\n conversation.ws = null;\n conversation.connectPromise = null;\n conversation.status = 'disconnected';\n conversation.activeAssistantMessageId = null;\n this.emitChanged(conversation);\n }\n\n closeAll(): void {\n for (const runtimeId of this.conversations.keys()) {\n this.close(runtimeId);\n }\n }\n\n private handleServerMessage(conversation: ConversationInternal, raw: string): void {\n let message: ServerMessage;\n try {\n message = JSON.parse(raw) as ServerMessage;\n } catch {\n return;\n }\n const payload = message.payload ?? {};\n switch (message.type) {\n case 'session.start': {\n const sessionId = stringValue(payload['sessionId']);\n if (sessionId) conversation.sessionId = sessionId;\n conversation.status = conversation.status === 'running' ? 'running' : 'connected';\n this.emitChanged(conversation);\n break;\n }\n case 'provider.text_delta': {\n conversation.status = 'running';\n this.appendAssistantDelta(conversation, stringValue(payload['text']) ?? '');\n break;\n }\n case 'tool.started': {\n conversation.status = 'running';\n const name = stringValue(payload['name']) ?? 'tool';\n this.appendMessage(conversation, { role: 'tool', text: `Started ${name}` });\n break;\n }\n case 'tool.executed': {\n const name = stringValue(payload['name']) ?? 'tool';\n const ok = payload['ok'] === true;\n this.appendMessage(conversation, {\n role: 'tool',\n text: `${name} ${ok ? 'completed' : 'failed'}`,\n });\n break;\n }\n case 'provider.error':\n case 'provider.stream_error':\n case 'error': {\n const text =\n stringValue(payload['message']) ??\n stringValue(payload['description']) ??\n `${message.type} received`;\n conversation.status = 'error';\n conversation.error = text;\n this.appendMessage(conversation, { role: 'system', text });\n break;\n }\n case 'run.result': {\n const finalText = stringValue(payload['finalText']);\n if (finalText && !this.lastAssistantHasText(conversation)) {\n this.appendMessage(conversation, { role: 'assistant', text: finalText });\n }\n conversation.status = payload['status'] === 'failed' ? 'error' : 'connected';\n conversation.activeAssistantMessageId = null;\n this.emitChanged(conversation);\n break;\n }\n }\n }\n\n private send(conversation: ConversationInternal, message: Record<string, unknown>): void {\n if (conversation.ws?.readyState !== WebSocket.OPEN) {\n throw new Error('Runtime socket is not connected');\n }\n conversation.ws.send(JSON.stringify(message));\n }\n\n private appendAssistantDelta(conversation: ConversationInternal, text: string): void {\n if (!text) return;\n let message = conversation.messages.find((m) => m.id === conversation.activeAssistantMessageId);\n if (!message) {\n message = {\n id: `assistant_${randomUUID()}`,\n role: 'assistant',\n text: '',\n timestamp: Date.now(),\n };\n conversation.activeAssistantMessageId = message.id;\n conversation.messages.push(message);\n }\n message.text += text;\n this.trimMessages(conversation);\n this.emitChanged(conversation);\n }\n\n private appendMessage(\n conversation: ConversationInternal,\n input: Partial<DesktopConversationMessage> & Pick<DesktopConversationMessage, 'role' | 'text'>,\n ): void {\n conversation.messages.push({\n id: input.id ?? `${input.role}_${randomUUID()}`,\n role: input.role,\n text: input.text,\n timestamp: input.timestamp ?? Date.now(),\n });\n this.trimMessages(conversation);\n this.emitChanged(conversation);\n }\n\n private lastAssistantHasText(conversation: ConversationInternal): boolean {\n const lastAssistant = [...conversation.messages].reverse().find((m) => m.role === 'assistant');\n return Boolean(lastAssistant?.text.trim());\n }\n\n private trimMessages(conversation: ConversationInternal): void {\n if (conversation.messages.length <= MAX_MESSAGES) return;\n conversation.messages.splice(0, conversation.messages.length - MAX_MESSAGES);\n }\n\n private getOrCreate(runtimeId: string): ConversationInternal {\n let conversation = this.conversations.get(runtimeId);\n if (conversation) return conversation;\n conversation = {\n runtimeId,\n status: 'disconnected',\n messages: [],\n ws: null,\n connectPromise: null,\n activeAssistantMessageId: null,\n };\n this.conversations.set(runtimeId, conversation);\n return conversation;\n }\n\n private emitChanged(conversation: ConversationInternal): void {\n this.emit('changed', publicConversation(conversation));\n }\n}\n\nfunction publicConversation(conversation: ConversationInternal): DesktopConversationSnapshot {\n return {\n runtimeId: conversation.runtimeId,\n status: conversation.status,\n sessionId: conversation.sessionId,\n error: conversation.error,\n messages: conversation.messages.map((message) => ({ ...message })),\n };\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n"],"mappings":"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","names":[]}
|