@yeaft/webchat-agent 0.1.974 → 0.1.976
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/yeaft/debug-trace.js +15 -9
- package/yeaft/engine.js +15 -13
- package/yeaft/history-compact.js +23 -2
- package/yeaft/tools/registry.js +18 -18
- package/yeaft/vp/seed-defaults.js +113 -3
- package/yeaft/vp/seed-topup.js +11 -3
- package/yeaft/web-bridge.js +2 -0
package/package.json
CHANGED
package/yeaft/debug-trace.js
CHANGED
|
@@ -48,6 +48,7 @@ const SCHEMA = `
|
|
|
48
48
|
tool_name TEXT NOT NULL,
|
|
49
49
|
tool_input TEXT,
|
|
50
50
|
tool_output TEXT,
|
|
51
|
+
tool_call_id TEXT,
|
|
51
52
|
duration_ms INTEGER,
|
|
52
53
|
is_error INTEGER DEFAULT 0,
|
|
53
54
|
created_at INTEGER NOT NULL,
|
|
@@ -101,12 +102,12 @@ function migrateAddColumn(db, table, column, type) {
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
/** Max
|
|
105
|
-
const
|
|
105
|
+
/** Max tool input size stored inline. Tool output is persisted raw. */
|
|
106
|
+
const MAX_TOOL_INPUT = 10240;
|
|
106
107
|
|
|
107
108
|
/**
|
|
108
109
|
* Max per-loop payload (system prompt, messages JSON, raw request /
|
|
109
|
-
* response, response text) stored per row. Larger than
|
|
110
|
+
* response, response text) stored per row. Larger than MAX_TOOL_INPUT
|
|
110
111
|
* because real-world LLM exchanges (system prompt + 30K-token message
|
|
111
112
|
* trail + raw response) routinely cross 10KB. 256KB lets us replay the
|
|
112
113
|
* panel verbatim for the most recent traces without bloating the DB.
|
|
@@ -207,6 +208,7 @@ export class DebugTrace {
|
|
|
207
208
|
// snapshot — `messages.find(role==='user')` would return turn 1's
|
|
208
209
|
// text for every subsequent turn, mislabeling every Turn header.
|
|
209
210
|
migrateAddColumn(this.#db, 'trace_turns', 'user_prompt', 'TEXT');
|
|
211
|
+
migrateAddColumn(this.#db, 'trace_tools', 'tool_call_id', 'TEXT');
|
|
210
212
|
// Indexes on the just-added columns. Must run AFTER the ALTER TABLEs
|
|
211
213
|
// — running them inside SCHEMA's CREATE INDEX IF NOT EXISTS block
|
|
212
214
|
// would fail with "no such column: group_id" on a pre-bugfix DB.
|
|
@@ -308,11 +310,12 @@ export class DebugTrace {
|
|
|
308
310
|
/**
|
|
309
311
|
* Log a tool call within a turn.
|
|
310
312
|
* @param {string} turnId
|
|
311
|
-
* @param {{ toolName: string, toolInput?: string, toolOutput?: string, durationMs?: number, isError?: boolean }} info
|
|
313
|
+
* @param {{ toolName: string, toolCallId?: string|null, toolInput?: string, toolOutput?: string, durationMs?: number, isError?: boolean }} info
|
|
312
314
|
* @returns {string} — tool record id
|
|
313
315
|
*/
|
|
314
316
|
logTool(turnId, {
|
|
315
317
|
toolName,
|
|
318
|
+
toolCallId = null,
|
|
316
319
|
toolInput = null,
|
|
317
320
|
toolOutput = null,
|
|
318
321
|
durationMs = null,
|
|
@@ -321,12 +324,13 @@ export class DebugTrace {
|
|
|
321
324
|
const id = randomUUID();
|
|
322
325
|
const now = Date.now();
|
|
323
326
|
this.#prepare('insertTool', `
|
|
324
|
-
INSERT INTO trace_tools (id, turn_id, tool_name, tool_input, tool_output, duration_ms, is_error, created_at)
|
|
325
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
327
|
+
INSERT INTO trace_tools (id, turn_id, tool_name, tool_input, tool_output, tool_call_id, duration_ms, is_error, created_at)
|
|
328
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
326
329
|
`).run(
|
|
327
330
|
id, turnId, toolName,
|
|
328
|
-
truncate(toolInput,
|
|
329
|
-
|
|
331
|
+
truncate(toolInput, MAX_TOOL_INPUT),
|
|
332
|
+
toolOutput == null ? null : String(toolOutput),
|
|
333
|
+
toolCallId,
|
|
330
334
|
durationMs, isError ? 1 : 0, now,
|
|
331
335
|
);
|
|
332
336
|
return id;
|
|
@@ -518,8 +522,10 @@ export class DebugTrace {
|
|
|
518
522
|
if (!t) continue;
|
|
519
523
|
t.tools.push({
|
|
520
524
|
loopNumber: owner.turn_number || 0,
|
|
521
|
-
callId: tool.id,
|
|
525
|
+
callId: tool.tool_call_id || tool.id,
|
|
526
|
+
traceToolId: tool.id,
|
|
522
527
|
name: tool.tool_name,
|
|
528
|
+
toolOutput: tool.tool_output == null ? null : String(tool.tool_output),
|
|
523
529
|
durationMs: tool.duration_ms || 0,
|
|
524
530
|
isError: !!tool.is_error,
|
|
525
531
|
});
|
package/yeaft/engine.js
CHANGED
|
@@ -43,7 +43,7 @@ import { countTurns } from './turn-utils.js';
|
|
|
43
43
|
import { attachRouterPlan, extractPriorPlan, stripMetaForWire } from './router/continuity.js';
|
|
44
44
|
import { resolveThinking } from './router/thinking.js';
|
|
45
45
|
import { approxTokens } from './memory/budget.js';
|
|
46
|
-
import { COLLAB_TOOL_POLICY, truncateToolResultIfNeeded } from './tools/registry.js';
|
|
46
|
+
import { COLLAB_TOOL_POLICY, normalizeToolOutput, truncateToolResultIfNeeded } from './tools/registry.js';
|
|
47
47
|
import { acknowledgePendingNotifications, formatNotificationsForPrompt, peekPendingNotifications } from './sub-agent/notifications.js';
|
|
48
48
|
import {
|
|
49
49
|
TOOL_BATCH_SIZE,
|
|
@@ -2489,13 +2489,7 @@ export class Engine {
|
|
|
2489
2489
|
// is exercised by tests and a few standalone tools. Aligning
|
|
2490
2490
|
// both paths keeps `ctx.cwd` semantics consistent.
|
|
2491
2491
|
const rawOutput = await tool.execute(tc.input, toolCtx);
|
|
2492
|
-
|
|
2493
|
-
// ToolRegistry.execute. Otherwise a deployment using the legacy
|
|
2494
|
-
// registration path bypasses the defense entirely.
|
|
2495
|
-
output = truncateToolResultIfNeeded(rawOutput, {
|
|
2496
|
-
toolName: tc.name,
|
|
2497
|
-
language: this.#config?.language,
|
|
2498
|
-
});
|
|
2492
|
+
output = normalizeToolOutput(rawOutput);
|
|
2499
2493
|
}
|
|
2500
2494
|
yield { type: 'tool_end', id: tc.id, name: tc.name, output, isError: false, threadId: this.currentThreadId };
|
|
2501
2495
|
} catch (err) {
|
|
@@ -2508,9 +2502,8 @@ export class Engine {
|
|
|
2508
2502
|
const toolDurationMs = Date.now() - toolStartTime;
|
|
2509
2503
|
|
|
2510
2504
|
// feat-6af5f9f1 PR B: emit a structured `tool_exec` event for the
|
|
2511
|
-
// debug panel.
|
|
2512
|
-
//
|
|
2513
|
-
// duplicate them here — only the per-tool timing + status.
|
|
2505
|
+
// debug panel. Keep raw output here; the model-facing tool
|
|
2506
|
+
// message below is deliberately truncated for context budget.
|
|
2514
2507
|
yield {
|
|
2515
2508
|
type: 'tool_exec',
|
|
2516
2509
|
turnId: queryTurnId,
|
|
@@ -2520,6 +2513,7 @@ export class Engine {
|
|
|
2520
2513
|
name: tc.name,
|
|
2521
2514
|
durationMs: toolDurationMs,
|
|
2522
2515
|
isError,
|
|
2516
|
+
toolOutput: output,
|
|
2523
2517
|
};
|
|
2524
2518
|
|
|
2525
2519
|
// 2026-05-13: feed the per-tool counters. Stays best-effort — a
|
|
@@ -2539,17 +2533,25 @@ export class Engine {
|
|
|
2539
2533
|
// Log tool to debug trace
|
|
2540
2534
|
this.#trace.logTool(turnId, {
|
|
2541
2535
|
toolName: tc.name,
|
|
2536
|
+
toolCallId: tc.id,
|
|
2542
2537
|
toolInput: JSON.stringify(tc.input),
|
|
2543
2538
|
toolOutput: output,
|
|
2544
2539
|
durationMs: toolDurationMs,
|
|
2545
2540
|
isError,
|
|
2541
|
+
toolOutput: output,
|
|
2546
2542
|
});
|
|
2547
2543
|
|
|
2548
|
-
// Append
|
|
2544
|
+
// Append only the bounded copy to the model message history. Raw
|
|
2545
|
+
// `output` is still used for debug traces, UI events, exec-log, and
|
|
2546
|
+
// persistence so large tool results are not lost outside context.
|
|
2547
|
+
const contextOutput = truncateToolResultIfNeeded(output, {
|
|
2548
|
+
toolName: tc.name,
|
|
2549
|
+
language: this.#config?.language,
|
|
2550
|
+
});
|
|
2549
2551
|
conversationMessages.push({
|
|
2550
2552
|
role: 'tool',
|
|
2551
2553
|
toolCallId: tc.id,
|
|
2552
|
-
content:
|
|
2554
|
+
content: contextOutput,
|
|
2553
2555
|
isError,
|
|
2554
2556
|
});
|
|
2555
2557
|
|
package/yeaft/history-compact.js
CHANGED
|
@@ -54,12 +54,28 @@
|
|
|
54
54
|
|
|
55
55
|
import { estimateTokens } from './conversation/persist.js';
|
|
56
56
|
import { pairSanitize } from './pair-sanitize.js';
|
|
57
|
+
import { truncateToolResultIfNeeded } from './tools/registry.js';
|
|
57
58
|
import {
|
|
58
59
|
countTurns as countTurnsImpl,
|
|
59
60
|
indexOfNthTurnFromEnd,
|
|
60
61
|
sliceLastNTurns,
|
|
61
62
|
} from './turn-utils.js';
|
|
62
63
|
|
|
64
|
+
|
|
65
|
+
function truncateToolResultsForModel(messages, opts = {}) {
|
|
66
|
+
if (!Array.isArray(messages) || messages.length === 0) return [];
|
|
67
|
+
return messages.map((m) => {
|
|
68
|
+
if (!m || m.role !== 'tool' || typeof m.content !== 'string') return { ...m };
|
|
69
|
+
return {
|
|
70
|
+
...m,
|
|
71
|
+
content: truncateToolResultIfNeeded(m.content, {
|
|
72
|
+
toolName: m.name || m.toolName || 'tool_result',
|
|
73
|
+
language: opts.language,
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
63
79
|
/**
|
|
64
80
|
* Re-export `countTurns` so existing callers / tests that import it
|
|
65
81
|
* from this module continue to work. Implementation now lives in
|
|
@@ -706,7 +722,7 @@ export async function compactHistory(messages, options) {
|
|
|
706
722
|
* between trim (per-call) and compact (global) explicit.
|
|
707
723
|
*
|
|
708
724
|
* @param {Array<object>} snapshot
|
|
709
|
-
* @param {{ messageTokenBudget?: number, recentTurnCap?: number, keepToolTurns?: number }} [opts]
|
|
725
|
+
* @param {{ messageTokenBudget?: number, recentTurnCap?: number, keepToolTurns?: number, language?: string }} [opts]
|
|
710
726
|
* @returns {Array<object>}
|
|
711
727
|
*/
|
|
712
728
|
export function trimSnapshotForBudget(snapshot, opts = {}) {
|
|
@@ -738,6 +754,11 @@ export function trimSnapshotForBudget(snapshot, opts = {}) {
|
|
|
738
754
|
keepToolTurns: opts.keepToolTurns,
|
|
739
755
|
});
|
|
740
756
|
|
|
741
|
-
// Stage 4:
|
|
757
|
+
// Stage 4: bound the raw tool result copy that is fed back into the model.
|
|
758
|
+
// The in-memory/persisted transcript keeps the full content; this transform
|
|
759
|
+
// only affects the per-query snapshot passed to engine.query().
|
|
760
|
+
trimmed = truncateToolResultsForModel(trimmed, { language: opts.language });
|
|
761
|
+
|
|
762
|
+
// Stage 5: pair-sanitize to drop orphan tool_use/tool_result.
|
|
742
763
|
return pairSanitize(trimmed);
|
|
743
764
|
}
|
package/yeaft/tools/registry.js
CHANGED
|
@@ -33,19 +33,17 @@ export const SUB_AGENT_TOOL_NAMES = Object.freeze([
|
|
|
33
33
|
export const FORWARD_TOOL_NAMES = Object.freeze(['RouteForward']);
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Per-tool-result
|
|
36
|
+
* Per-tool-result model-context cap.
|
|
37
37
|
*
|
|
38
38
|
* A single tool can return megabytes (a grep over a large repo, a large file
|
|
39
39
|
* read, a paginated web fetch). If we forward that verbatim into the next LLM
|
|
40
|
-
* request,
|
|
41
|
-
*
|
|
40
|
+
* request, it bloats context and makes every replay expensive. Keep the
|
|
41
|
+
* boundary small and deterministic for LLM message history: one tool result
|
|
42
42
|
* gets at most 1 KiB before a visible truncation marker is appended.
|
|
43
43
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* the user sees the full 2 MB output but the model gets a stub —
|
|
48
|
-
* confusing.
|
|
44
|
+
* Do NOT apply this at tool execution time. Debug events, exec logs, and
|
|
45
|
+
* persisted transcripts need the raw result. The engine/history replay path
|
|
46
|
+
* applies this only when building messages for the model.
|
|
49
47
|
*/
|
|
50
48
|
export const TOOL_RESULT_MAX_BYTES = 1024;
|
|
51
49
|
|
|
@@ -170,7 +168,7 @@ export class ToolExecutionTimeoutError extends Error {
|
|
|
170
168
|
* @param {{ toolName: string, language?: string }} opts
|
|
171
169
|
* @returns {string}
|
|
172
170
|
*/
|
|
173
|
-
export function
|
|
171
|
+
export function normalizeToolOutput(output) {
|
|
174
172
|
let text;
|
|
175
173
|
if (typeof output === 'string') {
|
|
176
174
|
text = output;
|
|
@@ -182,6 +180,11 @@ export function truncateToolResultIfNeeded(output, { toolName, language } = {})
|
|
|
182
180
|
text = String(output);
|
|
183
181
|
}
|
|
184
182
|
}
|
|
183
|
+
return text;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function truncateToolResultIfNeeded(output, { toolName, language } = {}) {
|
|
187
|
+
const text = normalizeToolOutput(output);
|
|
185
188
|
const originalBytes = Buffer.byteLength(text, 'utf8');
|
|
186
189
|
if (originalBytes <= TOOL_RESULT_MAX_BYTES) return text;
|
|
187
190
|
|
|
@@ -195,8 +198,8 @@ export function truncateToolResultIfNeeded(output, { toolName, language } = {})
|
|
|
195
198
|
}
|
|
196
199
|
const head = chunks.join('');
|
|
197
200
|
const marker = normalizeLanguage(language) === 'zh'
|
|
198
|
-
? `\n\n[已截断:${toolName} 返回 ${formatSize(originalBytes)},上限为 ${formatSize(TOOL_RESULT_MAX_BYTES)};原因:单个 tool result 超过 1KB
|
|
199
|
-
: `\n\n[truncated: ${toolName} returned ${formatSize(originalBytes)}, capped at ${formatSize(TOOL_RESULT_MAX_BYTES)}; reason: single tool result exceeded 1KB, the model
|
|
201
|
+
? `\n\n[已截断:${toolName} 返回 ${formatSize(originalBytes)},上限为 ${formatSize(TOOL_RESULT_MAX_BYTES)};原因:单个 tool result 超过 1KB,模型消息历史不会看到剩余内容]`
|
|
202
|
+
: `\n\n[truncated: ${toolName} returned ${formatSize(originalBytes)}, capped at ${formatSize(TOOL_RESULT_MAX_BYTES)}; reason: single tool result exceeded 1KB, the model message history will not see the rest]`;
|
|
200
203
|
return head + marker;
|
|
201
204
|
}
|
|
202
205
|
|
|
@@ -372,9 +375,9 @@ export class ToolRegistry {
|
|
|
372
375
|
/**
|
|
373
376
|
* Execute a tool by name.
|
|
374
377
|
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
+
* Returns the raw tool result as text. Do not truncate here: debug display,
|
|
379
|
+
* exec logs, and persistence must retain the full result. The engine/history
|
|
380
|
+
* replay path truncates only the copy inserted into model message history.
|
|
378
381
|
*
|
|
379
382
|
* @param {string} name
|
|
380
383
|
* @param {object} input
|
|
@@ -398,10 +401,7 @@ export class ToolRegistry {
|
|
|
398
401
|
? await runWithTimeout(tool.execute(input, ctx), rawTimeout, name)
|
|
399
402
|
: await tool.execute(input, ctx);
|
|
400
403
|
|
|
401
|
-
return
|
|
402
|
-
toolName: name,
|
|
403
|
-
language: ctx.config?.language,
|
|
404
|
-
});
|
|
404
|
+
return normalizeToolOutput(output);
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
/** Number of registered tools. */
|
|
@@ -1312,15 +1312,125 @@ const DEFAULT_VP_PERSONA_ZH = Object.freeze({
|
|
|
1312
1312
|
}
|
|
1313
1313
|
});
|
|
1314
1314
|
|
|
1315
|
+
|
|
1316
|
+
function cleanDefaultPersonaFragment(value) {
|
|
1317
|
+
let text = String(value || '').trim();
|
|
1318
|
+
text = text.replace(/cross-VP/g, 'cross-member').replace(/Cross-VP/g, 'Cross-member');
|
|
1319
|
+
text = text.replace(/\bdevelopment VP\b/g, 'developer');
|
|
1320
|
+
text = text.replace(/\bVP\b/g, '');
|
|
1321
|
+
text = text.replace(/generic assistant/gi, 'generic helper');
|
|
1322
|
+
text = text.replace(/generic helper/g, 'generic coordinator');
|
|
1323
|
+
text = text.replace(/\s+,/g, ',').replace(/\s+\./g, '.').replace(/ {2,}/g, ' ');
|
|
1324
|
+
return text.trim();
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
function sentenceFromFragment(value) {
|
|
1328
|
+
const text = cleanDefaultPersonaFragment(value);
|
|
1329
|
+
if (!text) return '';
|
|
1330
|
+
const first = text[0].toUpperCase() + text.slice(1);
|
|
1331
|
+
return /[.!?"”]$/.test(first) ? first : `${first}.`;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function firstSection(value) {
|
|
1335
|
+
return String(value || '').split(/\n\n(?:Core capabilities|Traits|Strengths|Problem-solving style|Decision style|Catchphrases|Good for|Bad for|Expected from you|Answer style):/)[0].trim();
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function extractLabel(value, label) {
|
|
1339
|
+
const text = String(value || '');
|
|
1340
|
+
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1341
|
+
const match = text.match(new RegExp(`\\n\\n${escaped}: ([\\s\\S]*?)(?=\\n\\n[A-Z][A-Za-z -]+:|\\nBad for:|$)`));
|
|
1342
|
+
return match ? match[1].trim() : '';
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
function extractBulletDetails(value) {
|
|
1346
|
+
const details = [];
|
|
1347
|
+
for (const match of String(value || '').matchAll(/^- [^:\n]+: ([^\n]+)$/gm)) {
|
|
1348
|
+
details.push(sentenceFromFragment(match[1]));
|
|
1349
|
+
}
|
|
1350
|
+
return details;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
function cleanDefaultPersonaIdentityEn(value) {
|
|
1354
|
+
let text = String(value || '').trim();
|
|
1355
|
+
text = text.replace(/cross-VP/g, 'cross-member').replace(/Cross-VP/g, 'Cross-member');
|
|
1356
|
+
text = text.replace(/\bdevelopment VP\b/g, 'developer');
|
|
1357
|
+
text = text.replace(/You are Omni, a VP responsible for ([^\.]+)\./g, 'You are Omni. You are responsible for $1.');
|
|
1358
|
+
text = text.replace(/You are ([^\.\n]+?), a ([^\.\n]*?) VP\. /g, 'You are $1. You bring $2 judgment. ');
|
|
1359
|
+
text = text.replace(/\bVP\b/g, '');
|
|
1360
|
+
text = text.replace(/generic assistant/gi, 'generic helper');
|
|
1361
|
+
text = text.replace(/generic helper/g, 'generic coordinator');
|
|
1362
|
+
text = text.replace(/\s+,/g, ',').replace(/\s+\./g, '.').replace(/ {2,}/g, ' ');
|
|
1363
|
+
return text.trim();
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function naturalizeDefaultPersonaEn(value) {
|
|
1367
|
+
const raw = String(value || '').trim();
|
|
1368
|
+
const identity = cleanDefaultPersonaIdentityEn(firstSection(raw));
|
|
1369
|
+
const core = extractBulletDetails(raw);
|
|
1370
|
+
const traits = extractLabel(raw, 'Traits');
|
|
1371
|
+
const strengths = extractLabel(raw, 'Strengths');
|
|
1372
|
+
const problemSolving = extractLabel(raw, 'Problem-solving style');
|
|
1373
|
+
const decision = extractLabel(raw, 'Decision style');
|
|
1374
|
+
const catchphrases = extractLabel(raw, 'Catchphrases');
|
|
1375
|
+
const goodFor = extractLabel(raw, 'Good for');
|
|
1376
|
+
const badFor = extractLabel(raw, 'Bad for');
|
|
1377
|
+
const expected = extractLabel(raw, 'Expected from you');
|
|
1378
|
+
const answerStyle = extractLabel(raw, 'Answer style');
|
|
1379
|
+
|
|
1380
|
+
const craft = [];
|
|
1381
|
+
if (core.length) craft.push(`You look for the work that matters first: ${core.join(' ')}`);
|
|
1382
|
+
if (traits) craft.push(sentenceFromFragment(`You are ${traits}`));
|
|
1383
|
+
if (strengths) craft.push(sentenceFromFragment(`You are at your best in ${strengths}`));
|
|
1384
|
+
|
|
1385
|
+
const conduct = [];
|
|
1386
|
+
if (decision) conduct.push(sentenceFromFragment(decision));
|
|
1387
|
+
if (problemSolving) conduct.push(sentenceFromFragment(`You work by ${problemSolving}`));
|
|
1388
|
+
if (catchphrases) conduct.push(sentenceFromFragment(`Your familiar lines still matter: ${catchphrases}`));
|
|
1389
|
+
if (goodFor) conduct.push(sentenceFromFragment(`People come to you for ${goodFor}`));
|
|
1390
|
+
if (expected) conduct.push(sentenceFromFragment(`People come to you when they need ${expected}`));
|
|
1391
|
+
if (badFor) conduct.push(sentenceFromFragment(`You are the wrong voice for ${badFor}`));
|
|
1392
|
+
if (answerStyle) conduct.push(sentenceFromFragment(`You answer ${answerStyle}`));
|
|
1393
|
+
|
|
1394
|
+
return [identity, craft.join(' '), conduct.join(' ')].filter(Boolean).join('\n\n').trim();
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function naturalizeDefaultPersonaZh(value) {
|
|
1398
|
+
let text = String(value || '').trim();
|
|
1399
|
+
text = text.replace(/跨\s*VP\s*协作/g, '跨成员协作');
|
|
1400
|
+
text = text.replace(/开发\s*VP/g, '开发者');
|
|
1401
|
+
text = text.replace(/直接替开发者写代码/g, '直接替开发者写代码');
|
|
1402
|
+
text = text.replace(/安全 VP/g, '安全判断');
|
|
1403
|
+
text = text.replace(/AI 伙伴/g, '伙伴');
|
|
1404
|
+
text = text.replace(/泛用助手/g, '泛泛的协调者');
|
|
1405
|
+
text = text.replace(/你是([^,。]+),一个负责(.+?)的\s*VP。/g, '你是$1。你负责$2。');
|
|
1406
|
+
text = text.replace(/你是([^,。]+),一个以(.+?)为核心的设计\s*VP。/g, '你是$1。你以$2看设计问题。');
|
|
1407
|
+
text = text.replace(/你是([^,。]+),一个以(.+?)为核心的安全\s*VP。/g, '你是$1。你以$2做安全判断。');
|
|
1408
|
+
text = text.replace(/你是([^,。]+),一个以(.+?)为核心的\s*VP。/g, '你是$1。你以$2看问题。');
|
|
1409
|
+
text = text.replace(/\n\n人物特点:([^\n]+)/g, '\n\n你$1');
|
|
1410
|
+
text = text.replace(/\n\n擅长的事情:([^\n]+)/g, '\n\n你最擅长$1');
|
|
1411
|
+
text = text.replace(/\n\n解决问题的方式:([^\n]+)/g, '\n\n处理问题时,$1');
|
|
1412
|
+
text = text.replace(/\n\n用户通常期待你完成:([^\n]+)/g, '\n\n用户来找你,通常是为了$1');
|
|
1413
|
+
text = text.replace(/\n\n回答风格:([^\n]+)/g, '\n\n回答时,$1');
|
|
1414
|
+
text = text.replace(/\bVP\b/g, '');
|
|
1415
|
+
text = text.replace(/\s+,/g, ',').replace(/\s+。/g, '。');
|
|
1416
|
+
return text.trim();
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1315
1419
|
function localizeDefaultVpPersona(vp) {
|
|
1316
1420
|
const zh = DEFAULT_VP_PERSONA_ZH[vp.vpId];
|
|
1317
1421
|
if (!zh) return vp;
|
|
1422
|
+
const legacyPersonaEn = String(vp.persona || '').trim();
|
|
1423
|
+
const legacyPersonaZh = String(zh.persona || '').trim();
|
|
1424
|
+
const personaEn = naturalizeDefaultPersonaEn(legacyPersonaEn);
|
|
1425
|
+
const personaZh = naturalizeDefaultPersonaZh(legacyPersonaZh);
|
|
1318
1426
|
return {
|
|
1319
1427
|
...vp,
|
|
1320
1428
|
roleZh: zh.roleZh,
|
|
1321
|
-
persona: localizedPersonaSections(
|
|
1322
|
-
personaEn
|
|
1323
|
-
personaZh
|
|
1429
|
+
persona: localizedPersonaSections(personaEn, personaZh),
|
|
1430
|
+
personaEn,
|
|
1431
|
+
personaZh,
|
|
1432
|
+
legacyPersonaEn,
|
|
1433
|
+
legacyPersona: localizedPersonaSections(legacyPersonaEn, legacyPersonaZh),
|
|
1324
1434
|
};
|
|
1325
1435
|
}
|
|
1326
1436
|
|
package/yeaft/vp/seed-topup.js
CHANGED
|
@@ -234,9 +234,17 @@ function replaceRoleBody(source, body) {
|
|
|
234
234
|
function backfillLocalizedPersonaBody(source, vp) {
|
|
235
235
|
const body = roleBodyOf(source).trim();
|
|
236
236
|
const nextBody = typeof vp.persona === 'string' ? vp.persona.trim() : '';
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
237
|
+
if (!body || !nextBody || body === nextBody) return null;
|
|
238
|
+
|
|
239
|
+
const acceptedOldBodies = [
|
|
240
|
+
vp.legacyPersonaEn,
|
|
241
|
+
vp.legacyPersona,
|
|
242
|
+
vp.personaEn,
|
|
243
|
+
]
|
|
244
|
+
.filter(value => typeof value === 'string' && value.trim())
|
|
245
|
+
.map(value => value.trim());
|
|
246
|
+
|
|
247
|
+
if (!acceptedOldBodies.includes(body)) return null;
|
|
240
248
|
return replaceRoleBody(source, nextBody);
|
|
241
249
|
}
|
|
242
250
|
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -2276,6 +2276,7 @@ function handleEngineEvent(event, hctx) {
|
|
|
2276
2276
|
name: event.name,
|
|
2277
2277
|
durationMs: event.durationMs,
|
|
2278
2278
|
isError: event.isError,
|
|
2279
|
+
toolOutput: event.toolOutput,
|
|
2279
2280
|
}, envelope);
|
|
2280
2281
|
break;
|
|
2281
2282
|
|
|
@@ -3035,6 +3036,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3035
3036
|
// array). See `trimSnapshotForBudget` doc-block for policy.
|
|
3036
3037
|
const trimmedMessages = trimSnapshotForBudget(baseSnapshot, {
|
|
3037
3038
|
messageTokenBudget: session?.config?.messageTokenBudget,
|
|
3039
|
+
language: session?.config?.language,
|
|
3038
3040
|
});
|
|
3039
3041
|
for await (const event of vpEngine.query({
|
|
3040
3042
|
prompt,
|