@yeaft/webchat-agent 0.1.829 → 0.1.831
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/package.json +1 -1
- package/unify/config.js +2 -2
- package/unify/engine.js +5 -5
- package/unify/history-compact.js +3 -3
- package/unify/init.js +1 -1
- package/unify/memory/consolidate.js +2 -2
- package/unify/tools/registry.js +25 -33
package/package.json
CHANGED
package/unify/config.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* "language": "en",
|
|
14
14
|
* "debug": false,
|
|
15
15
|
* "maxContextTokens": 200000,
|
|
16
|
-
* "messageTokenBudget":
|
|
16
|
+
* "messageTokenBudget": 32768
|
|
17
17
|
* }
|
|
18
18
|
*
|
|
19
19
|
* Legacy: if config.json doesn't exist, falls back to old config.md + .env for migration.
|
|
@@ -31,7 +31,7 @@ const DEFAULTS = {
|
|
|
31
31
|
dir: DEFAULT_YEAFT_DIR,
|
|
32
32
|
maxContextTokens: 200000,
|
|
33
33
|
maxOutputTokens: 16384,
|
|
34
|
-
messageTokenBudget:
|
|
34
|
+
messageTokenBudget: 32768,
|
|
35
35
|
maxContinueTurns: 3,
|
|
36
36
|
// task-318: Unify-specific runtime caps. `maxConcurrentThreads` gates
|
|
37
37
|
// how many ThreadEngineRegistry instances may be live at once; dispatch
|
package/unify/engine.js
CHANGED
|
@@ -1041,7 +1041,7 @@ export class Engine {
|
|
|
1041
1041
|
if (!this.#conversationStore) return null;
|
|
1042
1042
|
if (this.#config._readOnly) return null;
|
|
1043
1043
|
|
|
1044
|
-
const budget = this.#config.messageTokenBudget ||
|
|
1044
|
+
const budget = this.#config.messageTokenBudget || 32768;
|
|
1045
1045
|
const compactCfg = (this.#config && this.#config.compact) || {};
|
|
1046
1046
|
return this.#runOrchestratorCompact(budget, compactCfg);
|
|
1047
1047
|
}
|
|
@@ -2255,12 +2255,12 @@ export class Engine {
|
|
|
2255
2255
|
} else {
|
|
2256
2256
|
const tool = this.#tools.get(tc.name);
|
|
2257
2257
|
const rawOutput = await tool.execute(tc.input, { signal });
|
|
2258
|
-
//
|
|
2259
|
-
//
|
|
2260
|
-
//
|
|
2258
|
+
// Legacy #tools branch must apply the same per-tool cap as
|
|
2259
|
+
// ToolRegistry.execute. Otherwise a deployment using the legacy
|
|
2260
|
+
// registration path bypasses the defense entirely.
|
|
2261
2261
|
output = truncateToolResultIfNeeded(rawOutput, {
|
|
2262
|
-
contextWindow: currentContextWindow,
|
|
2263
2262
|
toolName: tc.name,
|
|
2263
|
+
language: this.#config?.language,
|
|
2264
2264
|
});
|
|
2265
2265
|
}
|
|
2266
2266
|
yield { type: 'tool_end', id: tc.id, name: tc.name, output, isError: false, threadId: this.currentThreadId };
|
package/unify/history-compact.js
CHANGED
|
@@ -142,10 +142,10 @@ export const DEFAULT_RECENT_TURN_CAP = 25;
|
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
144
|
* Default per-query token budget for the snapshot (separate from the
|
|
145
|
-
* `tokenLimit` used by compact triggers). Mirrors the
|
|
145
|
+
* `tokenLimit` used by compact triggers). Mirrors the default
|
|
146
146
|
* carried in `~/.yeaft/config.json`'s `messageTokenBudget` field.
|
|
147
147
|
*/
|
|
148
|
-
export const DEFAULT_MESSAGE_TOKEN_BUDGET =
|
|
148
|
+
export const DEFAULT_MESSAGE_TOKEN_BUDGET = 32768;
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
151
|
* Estimate the token weight of a single message including role overhead
|
|
@@ -628,7 +628,7 @@ export async function compactHistory(messages, options) {
|
|
|
628
628
|
* boundary and walks forward through `@vp-X` variants of the
|
|
629
629
|
* cut turn so the slice is pair-safe.
|
|
630
630
|
* 2. **Token budget** — if the trimmed slice still exceeds
|
|
631
|
-
* `messageTokenBudget` tokens (default
|
|
631
|
+
* `messageTokenBudget` tokens (default 32768 from
|
|
632
632
|
* `~/.yeaft/config.json`), iteratively drop the oldest turn until
|
|
633
633
|
* we're under budget. We never drop below 1 turn — even a single
|
|
634
634
|
* huge turn is preferable to no context.
|
package/unify/init.js
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
// ─── Constants ──────────────────────────────────────────────────
|
|
18
18
|
|
|
19
|
-
/** Default MESSAGE_TOKEN_BUDGET
|
|
20
|
-
export const DEFAULT_MESSAGE_TOKEN_BUDGET =
|
|
19
|
+
/** Default MESSAGE_TOKEN_BUDGET for hot message compaction. */
|
|
20
|
+
export const DEFAULT_MESSAGE_TOKEN_BUDGET = 32768;
|
|
21
21
|
|
|
22
22
|
/** After compact, keep this fraction of the budget. */
|
|
23
23
|
export const COMPACT_KEEP_RATIO = 0.4;
|
package/unify/tools/registry.js
CHANGED
|
@@ -10,27 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { formatSize } from '../archive/tool-results.js';
|
|
13
|
-
import { DEFAULT_CONTEXT_WINDOW } from '../models.js';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Per-tool-result hard cap.
|
|
17
16
|
*
|
|
18
|
-
* A single tool can return megabytes (a grep over a large repo, a large
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* Cap = max(MIN_CAP, floor(contextWindow * RATIO))
|
|
25
|
-
* - RATIO = 10%: leaves 90% of context for system prompt, history, the
|
|
26
|
-
* model's own output, and other tools called this turn. Generous in
|
|
27
|
-
* absolute terms (25.6 KB on a 256 K window, ~20 K on a 200 K window),
|
|
28
|
-
* plenty for a real tool result; small enough that 5 such results
|
|
29
|
-
* still fit alongside everything else.
|
|
30
|
-
* - MIN_CAP = 8 KB: sanity floor for tiny test fixtures (4 K context
|
|
31
|
-
* models in tests would otherwise cap at 409 chars, defeating the
|
|
32
|
-
* test's purpose). Real production models all have ≥ 32 K context, so
|
|
33
|
-
* the floor never bites in practice.
|
|
17
|
+
* A single tool can return megabytes (a grep over a large repo, a large file
|
|
18
|
+
* read, a paginated web fetch). If we forward that verbatim into the next LLM
|
|
19
|
+
* request, persistence, or UI event, it bloats context and makes every replay
|
|
20
|
+
* expensive. Keep the hard boundary small and deterministic: one tool result
|
|
21
|
+
* gets at most 1 KiB before a visible truncation marker is appended.
|
|
34
22
|
*
|
|
35
23
|
* The truncation lands HERE (not in engine.js when pushing tool results
|
|
36
24
|
* into messages) so the UI's `tool_end` event, the exec log, AND the
|
|
@@ -38,8 +26,7 @@ import { DEFAULT_CONTEXT_WINDOW } from '../models.js';
|
|
|
38
26
|
* the user sees the full 2 MB output but the model gets a stub —
|
|
39
27
|
* confusing.
|
|
40
28
|
*/
|
|
41
|
-
const
|
|
42
|
-
const TOOL_RESULT_MIN_CAP = 8 * 1024;
|
|
29
|
+
export const TOOL_RESULT_MAX_BYTES = 1024;
|
|
43
30
|
|
|
44
31
|
function normalizeLanguage(language) {
|
|
45
32
|
return String(language || '').toLowerCase().startsWith('zh') ? 'zh' : 'en';
|
|
@@ -151,7 +138,7 @@ export class ToolExecutionTimeoutError extends Error {
|
|
|
151
138
|
/**
|
|
152
139
|
* Truncate a tool result if it exceeds the per-result cap. Non-string
|
|
153
140
|
* outputs are JSON-stringified first (matching what engine.js eventually
|
|
154
|
-
* pushes into `content`), then capped.
|
|
141
|
+
* pushes into `content`), then capped by UTF-8 byte length.
|
|
155
142
|
*
|
|
156
143
|
* Edge cases handled:
|
|
157
144
|
* - `undefined` → `JSON.stringify(undefined)` returns `undefined`, not
|
|
@@ -159,10 +146,10 @@ export class ToolExecutionTimeoutError extends Error {
|
|
|
159
146
|
* - circular refs / `JSON.stringify` throws → fall back to `String(...)`.
|
|
160
147
|
*
|
|
161
148
|
* @param {unknown} output
|
|
162
|
-
* @param {{
|
|
149
|
+
* @param {{ toolName: string, language?: string }} opts
|
|
163
150
|
* @returns {string}
|
|
164
151
|
*/
|
|
165
|
-
export function truncateToolResultIfNeeded(output, {
|
|
152
|
+
export function truncateToolResultIfNeeded(output, { toolName, language } = {}) {
|
|
166
153
|
let text;
|
|
167
154
|
if (typeof output === 'string') {
|
|
168
155
|
text = output;
|
|
@@ -174,14 +161,21 @@ export function truncateToolResultIfNeeded(output, { contextWindow, toolName, la
|
|
|
174
161
|
text = String(output);
|
|
175
162
|
}
|
|
176
163
|
}
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
164
|
+
const originalBytes = Buffer.byteLength(text, 'utf8');
|
|
165
|
+
if (originalBytes <= TOOL_RESULT_MAX_BYTES) return text;
|
|
166
|
+
|
|
167
|
+
const chunks = [];
|
|
168
|
+
let used = 0;
|
|
169
|
+
for (const ch of text) {
|
|
170
|
+
const n = Buffer.byteLength(ch, 'utf8');
|
|
171
|
+
if (used + n > TOOL_RESULT_MAX_BYTES) break;
|
|
172
|
+
chunks.push(ch);
|
|
173
|
+
used += n;
|
|
174
|
+
}
|
|
175
|
+
const head = chunks.join('');
|
|
182
176
|
const marker = normalizeLanguage(language) === 'zh'
|
|
183
|
-
? `\n\n[已截断:${toolName} 返回 ${formatSize(
|
|
184
|
-
: `\n\n[truncated: ${toolName} returned ${formatSize(
|
|
177
|
+
? `\n\n[已截断:${toolName} 返回 ${formatSize(originalBytes)},上限为 ${formatSize(TOOL_RESULT_MAX_BYTES)};原因:单个 tool result 超过 1KB,模型和持久化展示不会看到剩余内容]`
|
|
178
|
+
: `\n\n[truncated: ${toolName} returned ${formatSize(originalBytes)}, capped at ${formatSize(TOOL_RESULT_MAX_BYTES)}; reason: single tool result exceeded 1KB, the model and persisted display will not see the rest]`;
|
|
185
179
|
return head + marker;
|
|
186
180
|
}
|
|
187
181
|
|
|
@@ -325,9 +319,8 @@ export class ToolRegistry {
|
|
|
325
319
|
* Execute a tool by name.
|
|
326
320
|
*
|
|
327
321
|
* The result is passed through {@link truncateToolResultIfNeeded} so that
|
|
328
|
-
* a single tool
|
|
329
|
-
*
|
|
330
|
-
* fall back to a 200K default for callers that don't supply it.
|
|
322
|
+
* a single tool result never injects more than 1KB before the visible
|
|
323
|
+
* truncation marker.
|
|
331
324
|
*
|
|
332
325
|
* @param {string} name
|
|
333
326
|
* @param {object} input
|
|
@@ -352,7 +345,6 @@ export class ToolRegistry {
|
|
|
352
345
|
: await tool.execute(input, ctx);
|
|
353
346
|
|
|
354
347
|
return truncateToolResultIfNeeded(output, {
|
|
355
|
-
contextWindow: ctx.contextWindow,
|
|
356
348
|
toolName: name,
|
|
357
349
|
language: ctx.config?.language,
|
|
358
350
|
});
|