dexto 1.6.16 → 1.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cloud-chat.d.ts +18 -0
- package/dist/cli/cloud-chat.d.ts.map +1 -0
- package/dist/cli/cloud-chat.js +697 -0
- package/dist/cli/commands/deploy/client.d.ts +92 -0
- package/dist/cli/commands/deploy/client.d.ts.map +1 -1
- package/dist/cli/commands/deploy/client.js +260 -4
- package/dist/cli/commands/deploy/index.d.ts +1 -0
- package/dist/cli/commands/deploy/index.d.ts.map +1 -1
- package/dist/cli/commands/deploy/index.js +73 -0
- package/dist/cli/commands/deploy/register.d.ts.map +1 -1
- package/dist/cli/commands/deploy/register.js +19 -0
- package/dist/index-main.js +26 -2
- package/dist/utils/graceful-shutdown.d.ts +5 -2
- package/dist/utils/graceful-shutdown.d.ts.map +1 -1
- package/dist/utils/graceful-shutdown.js +9 -3
- package/dist/webui/assets/{index-UDAdxmci.js → index-COTVlYsv.js} +1 -1
- package/dist/webui/index.html +1 -1
- package/package.json +15 -14
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import { AgentEventBus, logger, } from '@dexto/core';
|
|
2
|
+
import { startInkCliRefactored } from '@dexto/tui';
|
|
3
|
+
import { createDeployClient } from './commands/deploy/client.js';
|
|
4
|
+
import { loadWorkspaceDeployLink } from './commands/deploy/state.js';
|
|
5
|
+
const CLOUD_SUPPORTED_COMMANDS = [
|
|
6
|
+
'help',
|
|
7
|
+
'h',
|
|
8
|
+
'?',
|
|
9
|
+
'exit',
|
|
10
|
+
'quit',
|
|
11
|
+
'q',
|
|
12
|
+
'new',
|
|
13
|
+
'clear',
|
|
14
|
+
'reset',
|
|
15
|
+
'resume',
|
|
16
|
+
'r',
|
|
17
|
+
'search',
|
|
18
|
+
'find',
|
|
19
|
+
'export',
|
|
20
|
+
'copy',
|
|
21
|
+
'cp',
|
|
22
|
+
'shortcuts',
|
|
23
|
+
'docs',
|
|
24
|
+
'doc',
|
|
25
|
+
'login',
|
|
26
|
+
'logout',
|
|
27
|
+
'stream',
|
|
28
|
+
'sounds',
|
|
29
|
+
];
|
|
30
|
+
const KNOWN_PROVIDERS = new Set([
|
|
31
|
+
'openai',
|
|
32
|
+
'openai-compatible',
|
|
33
|
+
'anthropic',
|
|
34
|
+
'google',
|
|
35
|
+
'groq',
|
|
36
|
+
'xai',
|
|
37
|
+
'cohere',
|
|
38
|
+
'minimax',
|
|
39
|
+
'glm',
|
|
40
|
+
'openrouter',
|
|
41
|
+
'litellm',
|
|
42
|
+
'glama',
|
|
43
|
+
'vertex',
|
|
44
|
+
'bedrock',
|
|
45
|
+
'local',
|
|
46
|
+
'ollama',
|
|
47
|
+
'dexto-nova',
|
|
48
|
+
]);
|
|
49
|
+
function getErrorMessage(error) {
|
|
50
|
+
return error instanceof Error ? error.message : String(error);
|
|
51
|
+
}
|
|
52
|
+
function unsupportedCloudFeature(feature) {
|
|
53
|
+
return new Error(`${feature} is not available while connected to a cloud agent.`);
|
|
54
|
+
}
|
|
55
|
+
function normalizeProvider(provider) {
|
|
56
|
+
if (!provider || !KNOWN_PROVIDERS.has(provider)) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return provider;
|
|
60
|
+
}
|
|
61
|
+
function normalizeCloudParts(parts) {
|
|
62
|
+
return parts.map((part) => {
|
|
63
|
+
if (part.type === 'text') {
|
|
64
|
+
return {
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: part.text,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (part.type === 'image') {
|
|
70
|
+
return {
|
|
71
|
+
type: 'image',
|
|
72
|
+
image: part.image,
|
|
73
|
+
...(part.mimeType !== undefined ? { mimeType: part.mimeType } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
type: 'file',
|
|
78
|
+
data: part.data,
|
|
79
|
+
mimeType: part.mimeType,
|
|
80
|
+
...(part.filename !== undefined ? { filename: part.filename } : {}),
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function normalizeHistoryEntry(entry, index) {
|
|
85
|
+
const content = normalizeCloudParts(entry.content);
|
|
86
|
+
if (entry.role === 'assistant') {
|
|
87
|
+
const provider = normalizeProvider(entry.provider);
|
|
88
|
+
return {
|
|
89
|
+
role: 'assistant',
|
|
90
|
+
content: content.length > 0 ? content : null,
|
|
91
|
+
...(entry.reasoning !== undefined ? { reasoning: entry.reasoning } : {}),
|
|
92
|
+
...(entry.model !== undefined ? { model: entry.model } : {}),
|
|
93
|
+
...(provider ? { provider } : {}),
|
|
94
|
+
...(entry.timestamp !== undefined ? { timestamp: entry.timestamp } : {}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (entry.role === 'tool') {
|
|
98
|
+
return {
|
|
99
|
+
role: 'tool',
|
|
100
|
+
content,
|
|
101
|
+
toolCallId: entry.toolCallId ?? `cloud-tool-${index}`,
|
|
102
|
+
name: entry.name ?? `tool-${index + 1}`,
|
|
103
|
+
...(entry.success !== undefined ? { success: entry.success } : {}),
|
|
104
|
+
...(entry.timestamp !== undefined ? { timestamp: entry.timestamp } : {}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
role: entry.role,
|
|
109
|
+
content,
|
|
110
|
+
...(entry.timestamp !== undefined ? { timestamp: entry.timestamp } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function buildSessionMetadata(session) {
|
|
114
|
+
return {
|
|
115
|
+
createdAt: session.createdAt ?? Date.now(),
|
|
116
|
+
lastActivity: session.lastActivity ?? session.createdAt ?? Date.now(),
|
|
117
|
+
messageCount: session.messageCount,
|
|
118
|
+
...(session.title ? { title: session.title } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function extractTextContent(parts) {
|
|
122
|
+
return parts
|
|
123
|
+
.filter((part) => part.type === 'text')
|
|
124
|
+
.map((part) => part.text)
|
|
125
|
+
.join('\n')
|
|
126
|
+
.trim();
|
|
127
|
+
}
|
|
128
|
+
function contentInputToParts(input) {
|
|
129
|
+
return typeof input === 'string' ? [{ type: 'text', text: input }] : [...input];
|
|
130
|
+
}
|
|
131
|
+
function contentInputToCloudText(input) {
|
|
132
|
+
const parts = contentInputToParts(input);
|
|
133
|
+
const unsupportedParts = parts.filter((part) => part.type !== 'text');
|
|
134
|
+
if (unsupportedParts.length > 0) {
|
|
135
|
+
throw unsupportedCloudFeature('Image and file attachments');
|
|
136
|
+
}
|
|
137
|
+
const text = extractTextContent(parts);
|
|
138
|
+
if (!text) {
|
|
139
|
+
throw new Error('Cloud chat messages must include text content.');
|
|
140
|
+
}
|
|
141
|
+
return text;
|
|
142
|
+
}
|
|
143
|
+
function contentPartsToCloudText(parts) {
|
|
144
|
+
return contentInputToCloudText(parts);
|
|
145
|
+
}
|
|
146
|
+
function combineQueuedContent(messages) {
|
|
147
|
+
return messages.flatMap((message) => message.content);
|
|
148
|
+
}
|
|
149
|
+
function buildQueuedMessage(id, content) {
|
|
150
|
+
return {
|
|
151
|
+
id,
|
|
152
|
+
content,
|
|
153
|
+
queuedAt: Date.now(),
|
|
154
|
+
kind: 'default',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function extractMessageSearchText(message) {
|
|
158
|
+
const content = message.role === 'assistant' ? (message.content ?? []) : message.content;
|
|
159
|
+
const text = extractTextContent(content);
|
|
160
|
+
if (message.role === 'assistant' && message.reasoning) {
|
|
161
|
+
return [text, message.reasoning].filter(Boolean).join('\n');
|
|
162
|
+
}
|
|
163
|
+
return text;
|
|
164
|
+
}
|
|
165
|
+
function buildSearchContext(text, query) {
|
|
166
|
+
const lowerText = text.toLowerCase();
|
|
167
|
+
const lowerQuery = query.toLowerCase();
|
|
168
|
+
const matchIndex = lowerText.indexOf(lowerQuery);
|
|
169
|
+
if (matchIndex === -1) {
|
|
170
|
+
return {
|
|
171
|
+
matchedText: query,
|
|
172
|
+
context: text.slice(0, 160),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const start = Math.max(0, matchIndex - 60);
|
|
176
|
+
const end = Math.min(text.length, matchIndex + query.length + 100);
|
|
177
|
+
return {
|
|
178
|
+
matchedText: text.slice(matchIndex, matchIndex + query.length),
|
|
179
|
+
context: text.slice(start, end).trim(),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function emitStreamingEvent(eventBus, event) {
|
|
183
|
+
const { name, ...payload } = event;
|
|
184
|
+
eventBus.emit(name, payload);
|
|
185
|
+
}
|
|
186
|
+
export function buildCloudApprovalDecision(approval) {
|
|
187
|
+
if (typeof approval.approvalId !== 'string' || typeof approval.status !== 'string') {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const approvalData = typeof approval.data === 'object' && approval.data !== null
|
|
191
|
+
? approval.data
|
|
192
|
+
: undefined;
|
|
193
|
+
return {
|
|
194
|
+
status: approval.status,
|
|
195
|
+
...(approvalData?.formData !== undefined ? { formData: approvalData.formData } : {}),
|
|
196
|
+
...(approvalData?.rememberChoice !== undefined
|
|
197
|
+
? { rememberChoice: approvalData.rememberChoice }
|
|
198
|
+
: {}),
|
|
199
|
+
...(approvalData?.rememberPattern !== undefined
|
|
200
|
+
? { rememberPattern: approvalData.rememberPattern }
|
|
201
|
+
: {}),
|
|
202
|
+
...(approvalData?.rememberDirectory !== undefined
|
|
203
|
+
? { rememberDirectory: approvalData.rememberDirectory }
|
|
204
|
+
: {}),
|
|
205
|
+
...(approval.reason !== undefined ? { reason: approval.reason } : {}),
|
|
206
|
+
...(approval.message !== undefined ? { message: approval.message } : {}),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function resolveCloudAgentId(options) {
|
|
210
|
+
const explicitCloudAgentId = options.cloudAgentId?.trim();
|
|
211
|
+
if (explicitCloudAgentId) {
|
|
212
|
+
return explicitCloudAgentId;
|
|
213
|
+
}
|
|
214
|
+
const workspaceRoot = options.workspaceRoot ?? process.cwd();
|
|
215
|
+
const link = await loadWorkspaceDeployLink(workspaceRoot);
|
|
216
|
+
if (link?.cloudAgentId) {
|
|
217
|
+
return link.cloudAgentId;
|
|
218
|
+
}
|
|
219
|
+
throw new Error('No cloud agent ID provided and this workspace is not linked to a deployment. Run `dexto deploy` first or pass a cloud agent ID.');
|
|
220
|
+
}
|
|
221
|
+
async function resolveInitialSessionId(client, cloudAgentId, options) {
|
|
222
|
+
const sessions = await client.listCloudAgentSessions(cloudAgentId);
|
|
223
|
+
if (options.resume) {
|
|
224
|
+
const requestedSession = sessions.find((session) => session.id === options.resume);
|
|
225
|
+
if (!requestedSession) {
|
|
226
|
+
throw new Error(`Cloud session '${options.resume}' not found for ${cloudAgentId}.`);
|
|
227
|
+
}
|
|
228
|
+
return requestedSession.id;
|
|
229
|
+
}
|
|
230
|
+
if (options.continueMostRecent) {
|
|
231
|
+
const [mostRecentSession] = sessions
|
|
232
|
+
.slice()
|
|
233
|
+
.sort((left, right) => (right.lastActivity ?? 0) - (left.lastActivity ?? 0));
|
|
234
|
+
return mostRecentSession?.id ?? null;
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
export function createCloudAgentBackend(client, cloudAgentId) {
|
|
239
|
+
const eventBus = new AgentEventBus();
|
|
240
|
+
const sessionMetadataCache = new Map();
|
|
241
|
+
const sessionTitleCache = new Map();
|
|
242
|
+
const sessionModelCache = new Map();
|
|
243
|
+
const queueBySession = new Map();
|
|
244
|
+
const globalDisabledTools = [];
|
|
245
|
+
const sessionDisabledTools = new Map();
|
|
246
|
+
const sessionAutoApproveTools = new Map();
|
|
247
|
+
const defaultLogFilePath = () => null;
|
|
248
|
+
const defaultLogLevel = () => {
|
|
249
|
+
return logger.getLevel();
|
|
250
|
+
};
|
|
251
|
+
const backendLogger = {
|
|
252
|
+
debug: (message, context) => {
|
|
253
|
+
logger.debug(message, context);
|
|
254
|
+
},
|
|
255
|
+
info: (message, context) => {
|
|
256
|
+
logger.info(message, context);
|
|
257
|
+
},
|
|
258
|
+
warn: (message, context) => {
|
|
259
|
+
logger.warn(message, context);
|
|
260
|
+
},
|
|
261
|
+
error: (message, context) => {
|
|
262
|
+
logger.error(message, context);
|
|
263
|
+
},
|
|
264
|
+
getLevel: defaultLogLevel,
|
|
265
|
+
getLogFilePath: defaultLogFilePath,
|
|
266
|
+
};
|
|
267
|
+
const ensureSessionMetadata = async (sessionId) => {
|
|
268
|
+
const cached = sessionMetadataCache.get(sessionId);
|
|
269
|
+
if (cached) {
|
|
270
|
+
return cached;
|
|
271
|
+
}
|
|
272
|
+
const sessions = await client.listCloudAgentSessions(cloudAgentId);
|
|
273
|
+
for (const session of sessions) {
|
|
274
|
+
const metadata = buildSessionMetadata(session);
|
|
275
|
+
sessionMetadataCache.set(session.id, metadata);
|
|
276
|
+
sessionTitleCache.set(session.id, metadata.title);
|
|
277
|
+
}
|
|
278
|
+
return sessionMetadataCache.get(sessionId);
|
|
279
|
+
};
|
|
280
|
+
const hydrateSessionHistory = async (sessionId) => {
|
|
281
|
+
const historyResult = await client.getCloudAgentSessionHistory(cloudAgentId, sessionId);
|
|
282
|
+
const history = historyResult.history.map(normalizeHistoryEntry);
|
|
283
|
+
const latestAssistant = [...history]
|
|
284
|
+
.reverse()
|
|
285
|
+
.find((message) => {
|
|
286
|
+
return message.role === 'assistant' && typeof message.model === 'string';
|
|
287
|
+
});
|
|
288
|
+
if (latestAssistant?.model) {
|
|
289
|
+
sessionModelCache.set(sessionId, {
|
|
290
|
+
model: latestAssistant.model,
|
|
291
|
+
...(latestAssistant.provider ? { provider: latestAssistant.provider } : {}),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return history;
|
|
295
|
+
};
|
|
296
|
+
const dequeueQueuedMessages = (sessionId) => {
|
|
297
|
+
const queued = queueBySession.get(sessionId) ?? [];
|
|
298
|
+
queueBySession.delete(sessionId);
|
|
299
|
+
return queued;
|
|
300
|
+
};
|
|
301
|
+
const appendQueuedMessage = (sessionId, message) => {
|
|
302
|
+
const current = queueBySession.get(sessionId) ?? [];
|
|
303
|
+
const next = [...current, message];
|
|
304
|
+
queueBySession.set(sessionId, next);
|
|
305
|
+
return next.length;
|
|
306
|
+
};
|
|
307
|
+
const currentLLMConfig = (sessionId) => {
|
|
308
|
+
const cachedModel = sessionId ? sessionModelCache.get(sessionId) : undefined;
|
|
309
|
+
return {
|
|
310
|
+
provider: cachedModel?.provider ?? 'dexto-nova',
|
|
311
|
+
model: cachedModel?.model ?? 'cloud-agent',
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
const sessionLogger = {
|
|
315
|
+
getLogFilePath: defaultLogFilePath,
|
|
316
|
+
getLevel: defaultLogLevel,
|
|
317
|
+
};
|
|
318
|
+
const backend = {
|
|
319
|
+
logger: backendLogger,
|
|
320
|
+
config: {
|
|
321
|
+
agentId: cloudAgentId,
|
|
322
|
+
},
|
|
323
|
+
capabilities: {
|
|
324
|
+
supportedCommands: [...CLOUD_SUPPORTED_COMMANDS],
|
|
325
|
+
prompts: false,
|
|
326
|
+
resources: false,
|
|
327
|
+
attachments: false,
|
|
328
|
+
reasoningCycle: false,
|
|
329
|
+
contextStats: false,
|
|
330
|
+
startupInfo: false,
|
|
331
|
+
},
|
|
332
|
+
sessionManager: {
|
|
333
|
+
getSessionMetadata: async (sessionId) => await ensureSessionMetadata(sessionId),
|
|
334
|
+
getSessionStats: async () => {
|
|
335
|
+
const sessions = await client.listCloudAgentSessions(cloudAgentId);
|
|
336
|
+
return {
|
|
337
|
+
totalSessions: sessions.length,
|
|
338
|
+
inMemorySessions: sessions.length,
|
|
339
|
+
maxSessions: Number.MAX_SAFE_INTEGER,
|
|
340
|
+
};
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
mcpManager: {
|
|
344
|
+
getClients: () => new Map(),
|
|
345
|
+
getFailedConnections: () => ({}),
|
|
346
|
+
},
|
|
347
|
+
toolManager: {
|
|
348
|
+
addSessionAutoApproveTools: async () => { },
|
|
349
|
+
},
|
|
350
|
+
services: {
|
|
351
|
+
hookManager: {
|
|
352
|
+
getHookNames: () => [],
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
on: ((eventName, listener, options) => {
|
|
356
|
+
eventBus.on(eventName, listener, options);
|
|
357
|
+
}),
|
|
358
|
+
emit: ((eventName, ...args) => {
|
|
359
|
+
const payload = args[0];
|
|
360
|
+
if (eventName === 'approval:response') {
|
|
361
|
+
const approval = payload;
|
|
362
|
+
const decision = buildCloudApprovalDecision(approval);
|
|
363
|
+
if (decision) {
|
|
364
|
+
void client
|
|
365
|
+
.submitCloudAgentApproval(cloudAgentId, approval.approvalId, decision)
|
|
366
|
+
.catch((error) => {
|
|
367
|
+
backendLogger.error('Failed to submit cloud approval response', {
|
|
368
|
+
cloudAgentId,
|
|
369
|
+
approvalId: approval.approvalId,
|
|
370
|
+
status: approval.status,
|
|
371
|
+
error: getErrorMessage(error),
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return eventBus.emit(eventName, payload);
|
|
377
|
+
}),
|
|
378
|
+
stream: (async (content, sessionId, options) => {
|
|
379
|
+
const initialText = contentInputToCloudText(content);
|
|
380
|
+
const signal = options?.signal;
|
|
381
|
+
const iterator = async function* () {
|
|
382
|
+
let currentText = initialText;
|
|
383
|
+
for (;;) {
|
|
384
|
+
const upstream = await client.streamCloudAgentMessage(cloudAgentId, {
|
|
385
|
+
sessionId,
|
|
386
|
+
content: currentText,
|
|
387
|
+
...(signal ? { signal } : {}),
|
|
388
|
+
});
|
|
389
|
+
let pendingRunComplete = null;
|
|
390
|
+
for await (const event of upstream) {
|
|
391
|
+
if (event.name === 'llm:response' && event.sessionId === sessionId) {
|
|
392
|
+
if (typeof event.model === 'string') {
|
|
393
|
+
sessionModelCache.set(sessionId, {
|
|
394
|
+
model: event.model,
|
|
395
|
+
...(event.provider ? { provider: event.provider } : {}),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (event.name === 'session:title-updated' &&
|
|
400
|
+
event.sessionId === sessionId) {
|
|
401
|
+
sessionTitleCache.set(sessionId, event.title);
|
|
402
|
+
const metadata = await ensureSessionMetadata(sessionId);
|
|
403
|
+
if (metadata) {
|
|
404
|
+
sessionMetadataCache.set(sessionId, {
|
|
405
|
+
...metadata,
|
|
406
|
+
title: event.title,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (event.name === 'run:complete') {
|
|
411
|
+
pendingRunComplete = event;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
emitStreamingEvent(eventBus, event);
|
|
415
|
+
yield event;
|
|
416
|
+
}
|
|
417
|
+
const queuedMessages = dequeueQueuedMessages(sessionId);
|
|
418
|
+
if (queuedMessages.length === 0) {
|
|
419
|
+
if (pendingRunComplete) {
|
|
420
|
+
emitStreamingEvent(eventBus, pendingRunComplete);
|
|
421
|
+
yield pendingRunComplete;
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
const combinedContent = combineQueuedContent(queuedMessages);
|
|
426
|
+
const dequeuedEvent = {
|
|
427
|
+
name: 'message:dequeued',
|
|
428
|
+
sessionId,
|
|
429
|
+
count: queuedMessages.length,
|
|
430
|
+
ids: queuedMessages.map((message) => message.id),
|
|
431
|
+
coalesced: queuedMessages.length > 1,
|
|
432
|
+
content: combinedContent,
|
|
433
|
+
messages: queuedMessages,
|
|
434
|
+
};
|
|
435
|
+
emitStreamingEvent(eventBus, dequeuedEvent);
|
|
436
|
+
yield dequeuedEvent;
|
|
437
|
+
currentText = contentPartsToCloudText(combinedContent);
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
return iterator();
|
|
441
|
+
}),
|
|
442
|
+
stop: (async () => { }),
|
|
443
|
+
run: (async () => {
|
|
444
|
+
throw unsupportedCloudFeature('Prompt execution');
|
|
445
|
+
}),
|
|
446
|
+
createSession: (async (sessionId) => {
|
|
447
|
+
const session = await client.createCloudAgentSession(cloudAgentId, sessionId ? { sessionId } : undefined);
|
|
448
|
+
const metadata = buildSessionMetadata(session);
|
|
449
|
+
sessionMetadataCache.set(session.id, metadata);
|
|
450
|
+
sessionTitleCache.set(session.id, metadata.title);
|
|
451
|
+
return {
|
|
452
|
+
id: session.id,
|
|
453
|
+
logger: sessionLogger,
|
|
454
|
+
};
|
|
455
|
+
}),
|
|
456
|
+
listSessions: (async () => {
|
|
457
|
+
const sessions = await client.listCloudAgentSessions(cloudAgentId);
|
|
458
|
+
for (const session of sessions) {
|
|
459
|
+
const metadata = buildSessionMetadata(session);
|
|
460
|
+
sessionMetadataCache.set(session.id, metadata);
|
|
461
|
+
sessionTitleCache.set(session.id, metadata.title);
|
|
462
|
+
}
|
|
463
|
+
return sessions.map((session) => session.id);
|
|
464
|
+
}),
|
|
465
|
+
getSession: (async (sessionId) => {
|
|
466
|
+
await ensureSessionMetadata(sessionId);
|
|
467
|
+
return {
|
|
468
|
+
id: sessionId,
|
|
469
|
+
logger: sessionLogger,
|
|
470
|
+
};
|
|
471
|
+
}),
|
|
472
|
+
getSessionMetadata: (async (sessionId) => await ensureSessionMetadata(sessionId)),
|
|
473
|
+
getSessionHistory: (async (sessionId) => await hydrateSessionHistory(sessionId)),
|
|
474
|
+
getSessionTitle: (async (sessionId) => {
|
|
475
|
+
const cachedTitle = sessionTitleCache.get(sessionId);
|
|
476
|
+
if (cachedTitle !== undefined) {
|
|
477
|
+
return cachedTitle;
|
|
478
|
+
}
|
|
479
|
+
return (await ensureSessionMetadata(sessionId))?.title;
|
|
480
|
+
}),
|
|
481
|
+
setSessionTitle: (async () => {
|
|
482
|
+
throw unsupportedCloudFeature('Session renaming');
|
|
483
|
+
}),
|
|
484
|
+
generateSessionTitle: (async (sessionId) => {
|
|
485
|
+
const result = await client.generateCloudAgentSessionTitle(cloudAgentId, sessionId);
|
|
486
|
+
const title = result?.title ?? undefined;
|
|
487
|
+
sessionTitleCache.set(sessionId, title);
|
|
488
|
+
const metadata = await ensureSessionMetadata(sessionId);
|
|
489
|
+
if (metadata) {
|
|
490
|
+
sessionMetadataCache.set(sessionId, {
|
|
491
|
+
...metadata,
|
|
492
|
+
...(title ? { title } : {}),
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
if (title) {
|
|
496
|
+
eventBus.emit('session:title-updated', {
|
|
497
|
+
sessionId,
|
|
498
|
+
title,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
return title;
|
|
502
|
+
}),
|
|
503
|
+
forkSession: (async () => {
|
|
504
|
+
throw unsupportedCloudFeature('Session forking');
|
|
505
|
+
}),
|
|
506
|
+
getCurrentLLMConfig: ((sessionId) => currentLLMConfig(sessionId)),
|
|
507
|
+
switchLLM: (async () => {
|
|
508
|
+
throw unsupportedCloudFeature('Model switching');
|
|
509
|
+
}),
|
|
510
|
+
getSupportedProviders: (() => []),
|
|
511
|
+
getSupportedModels: (() => ({})),
|
|
512
|
+
getContextStats: (async () => {
|
|
513
|
+
throw unsupportedCloudFeature('Context statistics');
|
|
514
|
+
}),
|
|
515
|
+
clearContext: (async (sessionId) => {
|
|
516
|
+
await client.clearCloudAgentSessionContext(cloudAgentId, sessionId);
|
|
517
|
+
queueBySession.delete(sessionId);
|
|
518
|
+
eventBus.emit('context:cleared', { sessionId });
|
|
519
|
+
}),
|
|
520
|
+
compactContext: (async () => {
|
|
521
|
+
throw unsupportedCloudFeature('Context compaction');
|
|
522
|
+
}),
|
|
523
|
+
queueMessage: (async (sessionId, input) => {
|
|
524
|
+
const messageId = `queued-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
525
|
+
const message = buildQueuedMessage(messageId, [...input.content]);
|
|
526
|
+
const position = appendQueuedMessage(sessionId, message);
|
|
527
|
+
eventBus.emit('message:queued', {
|
|
528
|
+
id: message.id,
|
|
529
|
+
position,
|
|
530
|
+
sessionId,
|
|
531
|
+
});
|
|
532
|
+
return {
|
|
533
|
+
queued: true,
|
|
534
|
+
position,
|
|
535
|
+
id: message.id,
|
|
536
|
+
};
|
|
537
|
+
}),
|
|
538
|
+
getQueuedMessages: (async (sessionId) => queueBySession.get(sessionId) ?? []),
|
|
539
|
+
removeQueuedMessage: (async (sessionId, messageId) => {
|
|
540
|
+
const current = queueBySession.get(sessionId) ?? [];
|
|
541
|
+
const next = current.filter((message) => message.id !== messageId);
|
|
542
|
+
queueBySession.set(sessionId, next);
|
|
543
|
+
eventBus.emit('message:removed', {
|
|
544
|
+
id: messageId,
|
|
545
|
+
sessionId,
|
|
546
|
+
});
|
|
547
|
+
return true;
|
|
548
|
+
}),
|
|
549
|
+
clearMessageQueue: (async (sessionId) => {
|
|
550
|
+
if (typeof sessionId === 'string') {
|
|
551
|
+
const clearedCount = (queueBySession.get(sessionId) ?? []).length;
|
|
552
|
+
queueBySession.delete(sessionId);
|
|
553
|
+
return clearedCount;
|
|
554
|
+
}
|
|
555
|
+
const clearedCount = Array.from(queueBySession.values()).reduce((total, queuedMessages) => total + queuedMessages.length, 0);
|
|
556
|
+
queueBySession.clear();
|
|
557
|
+
return clearedCount;
|
|
558
|
+
}),
|
|
559
|
+
cancel: (async (sessionId) => {
|
|
560
|
+
await client.cancelCloudAgentSessionRun(cloudAgentId, sessionId);
|
|
561
|
+
return true;
|
|
562
|
+
}),
|
|
563
|
+
searchMessages: (async (query, options = {}) => {
|
|
564
|
+
const normalizedQuery = query.trim();
|
|
565
|
+
if (!normalizedQuery) {
|
|
566
|
+
return {
|
|
567
|
+
results: [],
|
|
568
|
+
total: 0,
|
|
569
|
+
hasMore: false,
|
|
570
|
+
query,
|
|
571
|
+
options,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
const sessionIds = options.sessionId
|
|
575
|
+
? [options.sessionId]
|
|
576
|
+
: await backend.listSessions();
|
|
577
|
+
const results = [];
|
|
578
|
+
for (const sessionId of sessionIds) {
|
|
579
|
+
const history = await backend.getSessionHistory(sessionId);
|
|
580
|
+
history.forEach((message, messageIndex) => {
|
|
581
|
+
if (options.role && message.role !== options.role) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const searchableText = extractMessageSearchText(message);
|
|
585
|
+
if (!searchableText.toLowerCase().includes(normalizedQuery.toLowerCase())) {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const { matchedText, context } = buildSearchContext(searchableText, normalizedQuery);
|
|
589
|
+
results.push({
|
|
590
|
+
sessionId,
|
|
591
|
+
message,
|
|
592
|
+
matchedText,
|
|
593
|
+
context,
|
|
594
|
+
messageIndex,
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
results.sort((left, right) => {
|
|
599
|
+
const leftTimestamp = left.message.timestamp ?? 0;
|
|
600
|
+
const rightTimestamp = right.message.timestamp ?? 0;
|
|
601
|
+
return rightTimestamp - leftTimestamp;
|
|
602
|
+
});
|
|
603
|
+
const offset = options.offset ?? 0;
|
|
604
|
+
const limit = options.limit ?? 20;
|
|
605
|
+
const pagedResults = results.slice(offset, offset + limit);
|
|
606
|
+
return {
|
|
607
|
+
results: pagedResults,
|
|
608
|
+
total: results.length,
|
|
609
|
+
hasMore: offset + limit < results.length,
|
|
610
|
+
query,
|
|
611
|
+
options,
|
|
612
|
+
};
|
|
613
|
+
}),
|
|
614
|
+
listPrompts: (async () => ({})),
|
|
615
|
+
refreshPrompts: (async () => { }),
|
|
616
|
+
resolvePrompt: (async () => {
|
|
617
|
+
throw unsupportedCloudFeature('Prompt resolution');
|
|
618
|
+
}),
|
|
619
|
+
getSystemPrompt: (async () => ''),
|
|
620
|
+
loadToolkits: (async (toolkits) => ({
|
|
621
|
+
loaded: [...toolkits],
|
|
622
|
+
skipped: [],
|
|
623
|
+
})),
|
|
624
|
+
listResources: (async () => ({})),
|
|
625
|
+
setLogLevel: (async () => { }),
|
|
626
|
+
getAllTools: (async () => ({})),
|
|
627
|
+
getEnabledTools: (async () => ({})),
|
|
628
|
+
getAllMcpTools: (async () => ({})),
|
|
629
|
+
setGlobalDisabledTools: ((toolNames) => {
|
|
630
|
+
globalDisabledTools.splice(0, globalDisabledTools.length, ...toolNames);
|
|
631
|
+
}),
|
|
632
|
+
setSessionDisabledTools: ((sessionId, toolNames) => {
|
|
633
|
+
sessionDisabledTools.set(sessionId, [...toolNames]);
|
|
634
|
+
}),
|
|
635
|
+
setSessionAutoApproveTools: ((sessionId, toolNames) => {
|
|
636
|
+
sessionAutoApproveTools.set(sessionId, [...toolNames]);
|
|
637
|
+
}),
|
|
638
|
+
getSessionAutoApproveTools: ((sessionId) => {
|
|
639
|
+
return sessionAutoApproveTools.get(sessionId) ?? [];
|
|
640
|
+
}),
|
|
641
|
+
getMcpServersWithStatus: (() => []),
|
|
642
|
+
addMcpServer: (async () => {
|
|
643
|
+
throw unsupportedCloudFeature('MCP management');
|
|
644
|
+
}),
|
|
645
|
+
enableMcpServer: (async () => {
|
|
646
|
+
throw unsupportedCloudFeature('MCP management');
|
|
647
|
+
}),
|
|
648
|
+
disableMcpServer: (async () => {
|
|
649
|
+
throw unsupportedCloudFeature('MCP management');
|
|
650
|
+
}),
|
|
651
|
+
removeMcpServer: (async () => {
|
|
652
|
+
throw unsupportedCloudFeature('MCP management');
|
|
653
|
+
}),
|
|
654
|
+
restartMcpServer: (async () => {
|
|
655
|
+
throw unsupportedCloudFeature('MCP management');
|
|
656
|
+
}),
|
|
657
|
+
getMcpClients: (() => new Map()),
|
|
658
|
+
getMcpFailedConnections: (() => ({})),
|
|
659
|
+
getEffectiveConfig: ((sessionId) => {
|
|
660
|
+
const llmConfig = currentLLMConfig(sessionId);
|
|
661
|
+
return {
|
|
662
|
+
runtimeTarget: 'cloud',
|
|
663
|
+
cloudAgentId,
|
|
664
|
+
llm: {
|
|
665
|
+
provider: llmConfig.provider,
|
|
666
|
+
model: llmConfig.model,
|
|
667
|
+
maxOutputTokens: null,
|
|
668
|
+
temperature: null,
|
|
669
|
+
},
|
|
670
|
+
permissions: {
|
|
671
|
+
mode: 'cloud',
|
|
672
|
+
},
|
|
673
|
+
sessions: {},
|
|
674
|
+
mcpServers: {},
|
|
675
|
+
prompts: [],
|
|
676
|
+
};
|
|
677
|
+
}),
|
|
678
|
+
};
|
|
679
|
+
return backend;
|
|
680
|
+
}
|
|
681
|
+
export async function startCloudChatCli(options) {
|
|
682
|
+
if (!process.stdin.isTTY) {
|
|
683
|
+
throw new Error('Interactive cloud chat requires a TTY.');
|
|
684
|
+
}
|
|
685
|
+
const deployClient = createDeployClient();
|
|
686
|
+
const cloudAgentId = await resolveCloudAgentId(options);
|
|
687
|
+
const initialSessionId = await resolveInitialSessionId(deployClient, cloudAgentId, options);
|
|
688
|
+
const backend = createCloudAgentBackend(deployClient, cloudAgentId);
|
|
689
|
+
try {
|
|
690
|
+
await startInkCliRefactored(backend, initialSessionId, {
|
|
691
|
+
...(options.initialPrompt ? { initialPrompt: options.initialPrompt } : {}),
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
catch (error) {
|
|
695
|
+
throw new Error(`Cloud chat failed: ${getErrorMessage(error)}`);
|
|
696
|
+
}
|
|
697
|
+
}
|