@theowlops/boombot 1.1.1 → 1.1.2
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/commands/gateway.js +4 -4
- package/dist/gateway/config.js +11 -3
- package/dist/gateway/turns/session-adapter.js +4 -0
- package/dist/node_modules/@boombot/core/agent-core/turn-loop.js +75 -7
- package/dist/node_modules/@boombot/core/agent-core/types.d.ts +3 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands.d.ts +185 -185
- package/dist/package.json +1 -1
- package/dist/web/assets/ChatView-CP16lYju.css +1 -0
- package/dist/web/assets/{ChatView-BZrSD-64.js → ChatView-DwrZXRTf.js} +15 -15
- package/dist/web/assets/{MemoryView-BfMq_6CD.js → MemoryView-GytJpeXY.js} +1 -1
- package/dist/web/assets/{SchedulesView-B22QmPR-.js → SchedulesView-CgZHa7Zc.js} +1 -1
- package/dist/web/assets/{SessionsView-DS0-aH2a.js → SessionsView-DIaw3vEm.js} +1 -1
- package/dist/web/assets/{TasksView-ey0FLsW8.js → TasksView-BIccQf95.js} +1 -1
- package/dist/web/assets/{Textarea-DN3Zybl1.js → Textarea-DjaaMDYP.js} +1 -1
- package/dist/web/assets/{Tooltip-MzV2Gqnc.js → Tooltip-DSXSVOvn.js} +1 -1
- package/dist/web/assets/{browser-Bnt73-Ub.js → browser-BfCCGa0f.js} +1 -1
- package/dist/web/assets/{index-CuPR73Tn.js → index-7FrWWPBR.js} +2 -2
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/assets/ChatView-Cahbjez6.css +0 -1
|
@@ -97,11 +97,11 @@ function gatewayHostPort() {
|
|
|
97
97
|
try {
|
|
98
98
|
const config = loadConfig(resolveConfigPath());
|
|
99
99
|
const host = process.env['BOOMBOT_HOST']?.trim() || config?.host || '127.0.0.1';
|
|
100
|
-
const port = Number.parseInt(process.env['BOOMBOT_PORT']?.trim() || String(config?.port ??
|
|
101
|
-
return { host, port: Number.isInteger(port) && port > 0 && port <= 65535 ? port :
|
|
100
|
+
const port = Number.parseInt(process.env['BOOMBOT_PORT']?.trim() || String(config?.port ?? 8788), 10);
|
|
101
|
+
return { host, port: Number.isInteger(port) && port > 0 && port <= 65535 ? port : 8788 };
|
|
102
102
|
}
|
|
103
103
|
catch {
|
|
104
|
-
return { host: '127.0.0.1', port:
|
|
104
|
+
return { host: '127.0.0.1', port: 8788 };
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
/** Public URL for the browser: 0.0.0.0/:: mean "this machine". Exported for tests. */
|
|
@@ -142,7 +142,7 @@ export function initializeGateway(configPath = resolveConfigPath()) {
|
|
|
142
142
|
ensureMasterKey(masterKeyPath);
|
|
143
143
|
}
|
|
144
144
|
const { apiKeyHash } = ensureApiKey(resolveApiKeyPath(configPath));
|
|
145
|
-
saveConfig({ host: '127.0.0.1', port:
|
|
145
|
+
saveConfig({ host: '127.0.0.1', port: 8788, dataDir, apiKeyHash }, configPath, 'wx');
|
|
146
146
|
return true;
|
|
147
147
|
}
|
|
148
148
|
/**
|
package/dist/gateway/config.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
1
2
|
import { join } from 'node:path';
|
|
2
3
|
import { hashApiKey } from './auth.js';
|
|
3
4
|
import { coerceChannelSection, defaultConfigDir, defaultConfigPath, readConfigObject, resolveConfigFilePath, } from './config-format.js';
|
|
4
5
|
const DEFAULT_HOST = '127.0.0.1';
|
|
5
|
-
const DEFAULT_PORT =
|
|
6
|
+
const DEFAULT_PORT = 8788;
|
|
6
7
|
/**
|
|
7
8
|
* Default config path (`~/.boombot/config.yml`). The real definition lives in
|
|
8
9
|
* `config-format.ts`, next to the file probe order; re-exported here so old
|
|
@@ -397,8 +398,15 @@ export function loadConfig(env = process.env) {
|
|
|
397
398
|
const fileHost = typeof file.host === 'string' && file.host.length > 0 ? file.host : undefined;
|
|
398
399
|
const host = get('BOOMBOT_HOST') ?? fileHost ?? DEFAULT_HOST;
|
|
399
400
|
const port = parsePort(get('BOOMBOT_PORT') ?? file.port);
|
|
400
|
-
const fileDataDir = typeof file.dataDir === 'string' && file.dataDir.length > 0
|
|
401
|
-
|
|
401
|
+
const fileDataDir = typeof file.dataDir === 'string' && file.dataDir.length > 0
|
|
402
|
+
? (file.dataDir.startsWith('~/') || file.dataDir.startsWith('~\\')
|
|
403
|
+
? join(homedir(), file.dataDir.slice(2))
|
|
404
|
+
: file.dataDir)
|
|
405
|
+
: undefined;
|
|
406
|
+
const rawDataDir = get('BOOMBOT_DATA_DIR') ?? fileDataDir ?? defaultDataDir(env);
|
|
407
|
+
const dataDir = rawDataDir.startsWith('~/') || rawDataDir.startsWith('~\\')
|
|
408
|
+
? join(homedir(), rawDataDir.slice(2))
|
|
409
|
+
: rawDataDir;
|
|
402
410
|
const apiKeyHash = resolveApiKeyHash(file.apiKeyHash, get('BOOMBOT_API_KEY'));
|
|
403
411
|
const channels = mergeChannelEnv(parseChannels(file.channels, configPath), get);
|
|
404
412
|
const reasoningGuard = {
|
|
@@ -84,6 +84,7 @@ function toChatMessage(m) {
|
|
|
84
84
|
...(metadata?.toolCalls !== undefined ? { toolCalls: metadata.toolCalls } : {}),
|
|
85
85
|
...(metadata?.toolCallId !== undefined ? { toolCallId: metadata.toolCallId } : {}),
|
|
86
86
|
...(metadata?.toolName !== undefined ? { name: metadata.toolName } : {}),
|
|
87
|
+
...(metadata?.reasoning !== undefined ? { reasoning: metadata.reasoning } : {}),
|
|
87
88
|
};
|
|
88
89
|
}
|
|
89
90
|
/**
|
|
@@ -105,6 +106,9 @@ function toSessionMetadata(message) {
|
|
|
105
106
|
if (message.name !== undefined && message.name !== '') {
|
|
106
107
|
metadata.toolName = message.name;
|
|
107
108
|
}
|
|
109
|
+
if (message.reasoning !== undefined && message.reasoning !== '') {
|
|
110
|
+
metadata.reasoning = message.reasoning;
|
|
111
|
+
}
|
|
108
112
|
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
109
113
|
}
|
|
110
114
|
function boundedContext(messages, alreadyOmitted = 0) {
|
|
@@ -174,6 +174,7 @@ async function modelRound(deps, messages, tools, emit, signal) {
|
|
|
174
174
|
checkCancelled(signal);
|
|
175
175
|
let text = '';
|
|
176
176
|
let reasoning = '';
|
|
177
|
+
let inThinkTag = false;
|
|
177
178
|
const usage = { inputTokens: 0, outputTokens: 0 };
|
|
178
179
|
const accumulators = new Map();
|
|
179
180
|
const order = [];
|
|
@@ -188,8 +189,43 @@ async function modelRound(deps, messages, tools, emit, signal) {
|
|
|
188
189
|
}
|
|
189
190
|
}
|
|
190
191
|
if (chunk.delta) {
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
let remaining = chunk.delta;
|
|
193
|
+
while (remaining.length > 0) {
|
|
194
|
+
if (inThinkTag) {
|
|
195
|
+
const closeIdx = remaining.indexOf('</think>');
|
|
196
|
+
if (closeIdx !== -1) {
|
|
197
|
+
const thinkPart = remaining.slice(0, closeIdx);
|
|
198
|
+
remaining = remaining.slice(closeIdx + '</think>'.length);
|
|
199
|
+
inThinkTag = false;
|
|
200
|
+
if (thinkPart) {
|
|
201
|
+
reasoning += thinkPart;
|
|
202
|
+
emit({ type: 'reasoning.delta', text: thinkPart });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
reasoning += remaining;
|
|
207
|
+
emit({ type: 'reasoning.delta', text: remaining });
|
|
208
|
+
remaining = '';
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const openIdx = remaining.indexOf('<think>');
|
|
213
|
+
if (openIdx !== -1) {
|
|
214
|
+
const textPart = remaining.slice(0, openIdx);
|
|
215
|
+
remaining = remaining.slice(openIdx + '<think>'.length);
|
|
216
|
+
inThinkTag = true;
|
|
217
|
+
if (textPart) {
|
|
218
|
+
text += textPart;
|
|
219
|
+
emit({ type: 'text.delta', text: textPart });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
text += remaining;
|
|
224
|
+
emit({ type: 'text.delta', text: remaining });
|
|
225
|
+
remaining = '';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
193
229
|
}
|
|
194
230
|
if (chunk.reasoningDelta) {
|
|
195
231
|
reasoning += chunk.reasoningDelta;
|
|
@@ -244,15 +280,15 @@ async function modelRound(deps, messages, tools, emit, signal) {
|
|
|
244
280
|
arguments: parsed,
|
|
245
281
|
});
|
|
246
282
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const finalText = text.trim() ? text : (calls.length ? '' : reasoning.trim());
|
|
283
|
+
const finalText = text.trim();
|
|
284
|
+
const trimmedReasoning = reasoning.trim();
|
|
250
285
|
const assistantMessage = {
|
|
251
286
|
role: 'assistant',
|
|
252
287
|
content: finalText,
|
|
288
|
+
...(trimmedReasoning ? { reasoning: trimmedReasoning } : {}),
|
|
253
289
|
...(calls.length > 0 ? { toolCalls: calls } : {}),
|
|
254
290
|
};
|
|
255
|
-
return { text: finalText, toolCalls: calls, assistantMessage, usage };
|
|
291
|
+
return { text: finalText, reasoning: trimmedReasoning || undefined, toolCalls: calls, assistantMessage, usage };
|
|
256
292
|
}
|
|
257
293
|
async function executeToolBatch(deps, batch, emit, signal) {
|
|
258
294
|
const results = [];
|
|
@@ -431,6 +467,7 @@ export async function runTurn(deps, context, onEvent, signal) {
|
|
|
431
467
|
let emptyRecoveryPending = false;
|
|
432
468
|
let emptyRecoveryUsed = false;
|
|
433
469
|
let contextRetryUsed = false;
|
|
470
|
+
let lastRoundReasoning;
|
|
434
471
|
while (rounds < maxRounds) {
|
|
435
472
|
checkCancelled(signal);
|
|
436
473
|
rounds++;
|
|
@@ -440,7 +477,10 @@ export async function runTurn(deps, context, onEvent, signal) {
|
|
|
440
477
|
const requestMessages = () => {
|
|
441
478
|
const prepared = [...modelMessages];
|
|
442
479
|
if (recoverEmpty) {
|
|
443
|
-
|
|
480
|
+
const nudge = lastRoundReasoning
|
|
481
|
+
? 'Bạn đã hoàn thành phần suy luận nhưng chưa sinh nội dung câu trả lời cho người dùng. Hãy đưa ra câu trả lời trực tiếp và chi tiết cho người dùng ngay bây giờ bằng tiếng Việt, không lặp lại phần suy luận và không gọi công cụ.'
|
|
482
|
+
: EMPTY_RESPONSE_NUDGE;
|
|
483
|
+
prepared.push({ role: 'user', content: `[Lưu ý hệ thống: ${nudge}]` });
|
|
444
484
|
}
|
|
445
485
|
if (answerOnly) {
|
|
446
486
|
prepared.push({
|
|
@@ -469,8 +509,36 @@ export async function runTurn(deps, context, onEvent, signal) {
|
|
|
469
509
|
if (!emptyRecoveryUsed && rounds < maxRounds) {
|
|
470
510
|
emptyRecoveryUsed = true;
|
|
471
511
|
emptyRecoveryPending = true;
|
|
512
|
+
lastRoundReasoning = model.reasoning;
|
|
472
513
|
continue;
|
|
473
514
|
}
|
|
515
|
+
// Force one final synthesis round without tools if reasoning exists
|
|
516
|
+
if (model.reasoning && rounds < maxRounds) {
|
|
517
|
+
try {
|
|
518
|
+
const forceMessages = [
|
|
519
|
+
...modelMessages,
|
|
520
|
+
{
|
|
521
|
+
role: 'user',
|
|
522
|
+
content: 'Hãy tổng hợp và đưa ra câu trả lời trực tiếp, đầy đủ cho người dùng ngay bây giờ dựa trên các suy luận ở trên bằng tiếng Việt. Không gọi thêm công cụ.',
|
|
523
|
+
},
|
|
524
|
+
];
|
|
525
|
+
const finalModel = await execPhase('modelRound', () => modelRound(deps, forceMessages, [], emit, signal));
|
|
526
|
+
const finalText = finalModel.text.trim();
|
|
527
|
+
if (finalText) {
|
|
528
|
+
const finalMsg = {
|
|
529
|
+
role: 'assistant',
|
|
530
|
+
content: finalText,
|
|
531
|
+
...(model.reasoning ? { reasoning: model.reasoning } : {}),
|
|
532
|
+
};
|
|
533
|
+
messages.push(finalMsg);
|
|
534
|
+
modelMessages.push(finalMsg);
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
// fall through to error
|
|
540
|
+
}
|
|
541
|
+
}
|
|
474
542
|
const result = await finish('error', 'model returned an empty response');
|
|
475
543
|
emit({ type: 'turn.end', result });
|
|
476
544
|
return result;
|
|
@@ -21,6 +21,8 @@ export type MessageRole = 'system' | 'user' | 'assistant' | 'tool';
|
|
|
21
21
|
export interface ChatMessage {
|
|
22
22
|
readonly role: MessageRole;
|
|
23
23
|
readonly content: string;
|
|
24
|
+
/** Optional reasoning/thinking output from reasoning models or thinking blocks. */
|
|
25
|
+
readonly reasoning?: string;
|
|
24
26
|
/** Optional image references for multimodal user messages. */
|
|
25
27
|
readonly images?: readonly {
|
|
26
28
|
readonly url: string;
|
|
@@ -123,6 +125,7 @@ export type EventHandler = (event: AgentEvent) => void;
|
|
|
123
125
|
/** Phase payloads consumed by gateway middleware after Python returns them. */
|
|
124
126
|
export interface ModelRoundResult {
|
|
125
127
|
readonly text: string;
|
|
128
|
+
readonly reasoning?: string;
|
|
126
129
|
readonly toolCalls: readonly ToolCall[];
|
|
127
130
|
readonly assistantMessage: ChatMessage;
|
|
128
131
|
}
|