@yeaft/webchat-agent 0.1.585 → 0.1.587
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/engine.js +15 -7
- package/unify/pipeline/dispatcher.js +3 -1
- package/unify/prompts.js +45 -0
- package/unify/threads/engine-instance.js +2 -2
- package/unify/web-bridge.js +48 -0
package/package.json
CHANGED
package/unify/engine.js
CHANGED
|
@@ -297,7 +297,7 @@ export class Engine {
|
|
|
297
297
|
* @param {string} [userProfile] — user profile from user-memory shard store
|
|
298
298
|
* @returns {string}
|
|
299
299
|
*/
|
|
300
|
-
#buildSystemPrompt(memory, compactSummary, prompt, memoryInjection, userProfile) {
|
|
300
|
+
#buildSystemPrompt(memory, compactSummary, prompt, memoryInjection, userProfile, vpPersona) {
|
|
301
301
|
// Get relevant skill content if SkillManager is wired
|
|
302
302
|
let skillContent = '';
|
|
303
303
|
if (this.#skillManager && prompt) {
|
|
@@ -317,6 +317,7 @@ export class Engine {
|
|
|
317
317
|
compactSummary,
|
|
318
318
|
skillContent,
|
|
319
319
|
userProfile,
|
|
320
|
+
vpPersona,
|
|
320
321
|
// task-334f: memory_trace tool is now registered (49 → 51 tools), so
|
|
321
322
|
// unlock the core_memory meta-line behind 334e's feature flag.
|
|
322
323
|
memoryTraceAvailable: true,
|
|
@@ -329,7 +330,7 @@ export class Engine {
|
|
|
329
330
|
* @param {AbortSignal} [signal]
|
|
330
331
|
* @returns {object}
|
|
331
332
|
*/
|
|
332
|
-
#buildToolContext(signal) {
|
|
333
|
+
#buildToolContext(signal, vpCtx) {
|
|
333
334
|
return {
|
|
334
335
|
signal,
|
|
335
336
|
yeaftDir: this.#yeaftDir,
|
|
@@ -348,6 +349,13 @@ export class Engine {
|
|
|
348
349
|
imageAllowlist: Array.isArray(this.#config?.unify?.imageAllowlist)
|
|
349
350
|
? this.#config.unify.imageAllowlist
|
|
350
351
|
: [],
|
|
352
|
+
// Bug 4 fix — VP / routing context for RouteForward (and any other
|
|
353
|
+
// VP-aware tool). Undefined when running in non-group / no-VP flows.
|
|
354
|
+
router: vpCtx?.router,
|
|
355
|
+
senderVpId: vpCtx?.senderVpId,
|
|
356
|
+
inboundEnvelope: vpCtx?.inboundEnvelope,
|
|
357
|
+
taskId: vpCtx?.taskId,
|
|
358
|
+
taskMembers: vpCtx?.taskMembers,
|
|
351
359
|
};
|
|
352
360
|
}
|
|
353
361
|
|
|
@@ -510,7 +518,7 @@ export class Engine {
|
|
|
510
518
|
* SCENARIO_EFFORT. Unknown values fall through to 'high'.
|
|
511
519
|
* @yields {EngineEvent}
|
|
512
520
|
*/
|
|
513
|
-
async *query({ prompt, messages = [], signal, userEffort = null, scenario = 'chat' }) {
|
|
521
|
+
async *query({ prompt, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers } = {}) {
|
|
514
522
|
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
|
515
523
|
yield {
|
|
516
524
|
type: 'error',
|
|
@@ -561,7 +569,7 @@ export class Engine {
|
|
|
561
569
|
const runSignal = abortCtrl.signal;
|
|
562
570
|
|
|
563
571
|
try {
|
|
564
|
-
yield* this.#runQuery({ prompt: effectivePrompt, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario });
|
|
572
|
+
yield* this.#runQuery({ prompt: effectivePrompt, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers });
|
|
565
573
|
} finally {
|
|
566
574
|
if (signal) {
|
|
567
575
|
try { signal.removeEventListener('abort', onExternalAbort); } catch { /* ignore */ }
|
|
@@ -579,7 +587,7 @@ export class Engine {
|
|
|
579
587
|
* in a try/finally without indenting the whole loop.
|
|
580
588
|
* @private
|
|
581
589
|
*/
|
|
582
|
-
async *#runQuery({ prompt, messages, signal, userEffort = null, scenario = 'chat' }) {
|
|
590
|
+
async *#runQuery({ prompt, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers }) {
|
|
583
591
|
|
|
584
592
|
// ─── Pre-query: Memory Injection (task-287) + Compact Summary ──
|
|
585
593
|
// Two-layer recall:
|
|
@@ -618,7 +626,7 @@ export class Engine {
|
|
|
618
626
|
|
|
619
627
|
const compactSummary = this.#getCompactSummary();
|
|
620
628
|
const userProfile = recallResult?.profile || '';
|
|
621
|
-
const systemPrompt = this.#buildSystemPrompt(undefined, compactSummary, prompt, memoryInjection, userProfile);
|
|
629
|
+
const systemPrompt = this.#buildSystemPrompt(undefined, compactSummary, prompt, memoryInjection, userProfile, vpPersona);
|
|
622
630
|
|
|
623
631
|
// Build conversation: existing messages + new user message
|
|
624
632
|
const conversationMessages = [
|
|
@@ -883,7 +891,7 @@ export class Engine {
|
|
|
883
891
|
}
|
|
884
892
|
|
|
885
893
|
// Execute tool calls and feed results back
|
|
886
|
-
const toolCtx = this.#buildToolContext(signal);
|
|
894
|
+
const toolCtx = this.#buildToolContext(signal, { router, senderVpId, inboundEnvelope, taskId, taskMembers });
|
|
887
895
|
|
|
888
896
|
// task-325a: track whether we aborted mid tool-loop so we can
|
|
889
897
|
// break out of the outer while-loop cleanly once the current
|
|
@@ -164,6 +164,7 @@ export class Dispatcher {
|
|
|
164
164
|
transientMeta.set(entry, {
|
|
165
165
|
messageId: opts.messageId || undefined,
|
|
166
166
|
override: opts.override || undefined,
|
|
167
|
+
queryOpts: opts.queryOpts || undefined,
|
|
167
168
|
});
|
|
168
169
|
const snapshot = this.#queueSnapshot();
|
|
169
170
|
return { entry, snapshot };
|
|
@@ -299,7 +300,8 @@ export class Dispatcher {
|
|
|
299
300
|
}
|
|
300
301
|
|
|
301
302
|
try {
|
|
302
|
-
|
|
303
|
+
const queryOpts = (transientMeta.get(claimed) || {}).queryOpts || {};
|
|
304
|
+
for await (const event of instance.query({ prompt: claimed.text, signal, ...queryOpts })) {
|
|
303
305
|
yield { type: 'engine_event', threadId: targetThreadId, event };
|
|
304
306
|
}
|
|
305
307
|
inputQueue.markRouted(claimed.id, targetThreadId);
|
package/unify/prompts.js
CHANGED
|
@@ -192,6 +192,9 @@ const PROMPTS = {
|
|
|
192
192
|
userProfileHeader: '## user_profile',
|
|
193
193
|
coreMemoryHeader: '## core_memory',
|
|
194
194
|
coreMemoryMeta: 'To open the original message behind any entry above, call `memory_trace`.',
|
|
195
|
+
vpPersonaHeader: '## active_persona',
|
|
196
|
+
vpPersonaIntro: (name, role) =>
|
|
197
|
+
`For this turn you are speaking as **${name}**${role ? ` (${role})` : ''}. Stay in character; the persona below overrides the generic Yeaft identity for tone, expertise, and decision style.`,
|
|
195
198
|
},
|
|
196
199
|
zh: {
|
|
197
200
|
identity: '你是 Yeaft,一个有用的 AI 助手。',
|
|
@@ -210,6 +213,9 @@ const PROMPTS = {
|
|
|
210
213
|
userProfileHeader: '## user_profile',
|
|
211
214
|
coreMemoryHeader: '## core_memory',
|
|
212
215
|
coreMemoryMeta: '如需原始 message,调 `memory_trace`。',
|
|
216
|
+
vpPersonaHeader: '## active_persona',
|
|
217
|
+
vpPersonaIntro: (name, role) =>
|
|
218
|
+
`本轮你以 **${name}**${role ? `(${role})` : ''} 的身份说话。请保持人设:以下 persona 在语气、专业方向与判断风格上覆盖默认的 Yeaft 身份。`,
|
|
213
219
|
},
|
|
214
220
|
};
|
|
215
221
|
|
|
@@ -289,6 +295,7 @@ export function buildSystemPrompt({
|
|
|
289
295
|
userProfile,
|
|
290
296
|
coreMemory,
|
|
291
297
|
memoryTraceAvailable = false,
|
|
298
|
+
vpPersona,
|
|
292
299
|
} = {}) {
|
|
293
300
|
// Fallback to English for unknown languages
|
|
294
301
|
const lang = PROMPTS[language] || PROMPTS.en;
|
|
@@ -308,6 +315,14 @@ export function buildSystemPrompt({
|
|
|
308
315
|
// ─── 2. Date Metadata ──────────────────────────────────
|
|
309
316
|
parts.push(lang.date(new Date().toISOString().split('T')[0]));
|
|
310
317
|
|
|
318
|
+
// ─── 2.5 VP Persona Override (Bug 3 fix) ───────────────
|
|
319
|
+
// When the caller (web-bridge / dispatcher) addressed a specific VP via
|
|
320
|
+
// @-mention, inject that VP's persona body so the LLM stops speaking as
|
|
321
|
+
// generic Yeaft and adopts the VP's voice. Placed AFTER base identity so
|
|
322
|
+
// the persona section's directive ("override generic Yeaft") wins.
|
|
323
|
+
const vpBlock = renderVpPersona(vpPersona, lang);
|
|
324
|
+
if (vpBlock) parts.push(vpBlock);
|
|
325
|
+
|
|
311
326
|
// ─── 3. Mode-Specific Instructions ─────────────────────
|
|
312
327
|
// task-297: single unified mode for all normal operation.
|
|
313
328
|
// `dream` is retained for background memory maintenance.
|
|
@@ -382,6 +397,36 @@ export function buildSystemPrompt({
|
|
|
382
397
|
|
|
383
398
|
// ─── task-334e helpers ───────────────────────────────────────────
|
|
384
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Render the `## active_persona` block when the engine is running on
|
|
402
|
+
* behalf of an addressed VP. Accepts `{ displayName, role?, persona }` —
|
|
403
|
+
* `persona` is the body text from the VP's role.md (loaded by the engine
|
|
404
|
+
* via readVp). When `persona` is empty we still emit the intro line so
|
|
405
|
+
* the LLM at least knows whose voice to adopt; if even displayName is
|
|
406
|
+
* missing we omit the whole block (no useful signal).
|
|
407
|
+
*
|
|
408
|
+
* @param {object} vpPersona
|
|
409
|
+
* @param {string} vpPersona.displayName
|
|
410
|
+
* @param {string} [vpPersona.role]
|
|
411
|
+
* @param {string} [vpPersona.persona]
|
|
412
|
+
* @param {object} lang
|
|
413
|
+
* @returns {string}
|
|
414
|
+
*/
|
|
415
|
+
function renderVpPersona(vpPersona, lang) {
|
|
416
|
+
if (!vpPersona || typeof vpPersona !== 'object') return '';
|
|
417
|
+
const name = typeof vpPersona.displayName === 'string'
|
|
418
|
+
? vpPersona.displayName.trim() : '';
|
|
419
|
+
if (!name) return '';
|
|
420
|
+
const role = typeof vpPersona.role === 'string' ? vpPersona.role.trim() : '';
|
|
421
|
+
const body = typeof vpPersona.persona === 'string' ? vpPersona.persona.trim() : '';
|
|
422
|
+
const lines = [
|
|
423
|
+
lang.vpPersonaHeader,
|
|
424
|
+
lang.vpPersonaIntro(name, role),
|
|
425
|
+
];
|
|
426
|
+
if (body) lines.push('', body);
|
|
427
|
+
return lines.join('\n');
|
|
428
|
+
}
|
|
429
|
+
|
|
385
430
|
const DEFAULT_TASK_MEMORY_TOP = 5;
|
|
386
431
|
const DEFAULT_RELATED_TASK_TOP = 3;
|
|
387
432
|
const DEFAULT_RELATED_TASK_MEMORY_TOP = 2;
|
|
@@ -97,7 +97,7 @@ export class EngineInstance {
|
|
|
97
97
|
* @param {AbortSignal} [params.signal]
|
|
98
98
|
* @yields {object} EngineEvent with { ...event, threadId }
|
|
99
99
|
*/
|
|
100
|
-
async *query({ prompt, mode, signal }) {
|
|
100
|
+
async *query({ prompt, mode, signal, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers } = {}) {
|
|
101
101
|
if (this.#terminated) {
|
|
102
102
|
yield {
|
|
103
103
|
type: 'error',
|
|
@@ -173,7 +173,7 @@ export class EngineInstance {
|
|
|
173
173
|
curToolResults = [];
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
for await (const event of this.#engine.query({ prompt, mode, messages: snapshot, signal })) {
|
|
176
|
+
for await (const event of this.#engine.query({ prompt, mode, messages: snapshot, signal, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers })) {
|
|
177
177
|
// Re-tag every event with the bound threadId. Non-object events
|
|
178
178
|
// (shouldn't happen — all engine events are objects) are passed
|
|
179
179
|
// through untouched.
|
package/unify/web-bridge.js
CHANGED
|
@@ -28,6 +28,7 @@ import ctx from '../context.js';
|
|
|
28
28
|
import { getThreadStore, MAIN_THREAD_ID } from './threads/store.js';
|
|
29
29
|
import { handleVpSubscribe } from './vp/vp-bridge.js';
|
|
30
30
|
import { createVp, updateVp, deleteVp, readVp, VpCrudError } from './vp/vp-crud.js';
|
|
31
|
+
import { createRouter } from './routing/router.js';
|
|
31
32
|
import { handleUnifyTaskMessage as _handleUnifyTaskMessage } from './task-message.js';
|
|
32
33
|
import {
|
|
33
34
|
handleUnifyUserMemoryWrite as _handleUnifyUserMemoryWrite,
|
|
@@ -1165,6 +1166,10 @@ export async function handleUnifyGroupChat(msg) {
|
|
|
1165
1166
|
groupId,
|
|
1166
1167
|
vpId,
|
|
1167
1168
|
speakerVpId: vpId,
|
|
1169
|
+
// Bug 4: hand the coordinator down so handleUnifyChat can build a
|
|
1170
|
+
// Router for the RouteForward tool. Field is namespaced with `_`
|
|
1171
|
+
// to mark it as an internal hop, never sent over WS.
|
|
1172
|
+
_groupCoordinator: coord,
|
|
1168
1173
|
});
|
|
1169
1174
|
} catch (err) {
|
|
1170
1175
|
console.warn('[Unify] unify_group_chat: per-vp dispatch failed', vpId, err?.message || err);
|
|
@@ -1181,6 +1186,42 @@ export async function handleUnifyGroupChat(msg) {
|
|
|
1181
1186
|
}
|
|
1182
1187
|
}
|
|
1183
1188
|
|
|
1189
|
+
/**
|
|
1190
|
+
* Build the per-query VP context for the Engine.
|
|
1191
|
+
*
|
|
1192
|
+
* - Loads the addressed VP's persona via readVp() so the system prompt
|
|
1193
|
+
* speaks in that VP's voice (Bug 3 fix).
|
|
1194
|
+
* - When a GroupCoordinator handle is supplied, wraps it in a Router so
|
|
1195
|
+
* the RouteForward tool actually forwards instead of returning
|
|
1196
|
+
* `router_unavailable` (Bug 4 fix).
|
|
1197
|
+
*
|
|
1198
|
+
* Returns `undefined` when we have nothing to inject — keeps the legacy
|
|
1199
|
+
* single-agent path identical to before.
|
|
1200
|
+
*/
|
|
1201
|
+
function buildVpQueryOpts({ vpId, groupCoordinator }) {
|
|
1202
|
+
if (!vpId) return undefined;
|
|
1203
|
+
const out = { senderVpId: vpId };
|
|
1204
|
+
try {
|
|
1205
|
+
const vp = readVp(vpId);
|
|
1206
|
+
if (vp) {
|
|
1207
|
+
out.vpPersona = {
|
|
1208
|
+
displayName: vp.displayName || vpId,
|
|
1209
|
+
role: vp.role || '',
|
|
1210
|
+
persona: vp.persona || '',
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
} catch { /* persona load is best-effort */ }
|
|
1214
|
+
if (groupCoordinator && typeof groupCoordinator.ingest === 'function') {
|
|
1215
|
+
try {
|
|
1216
|
+
out.router = createRouter({ coordinator: groupCoordinator });
|
|
1217
|
+
} catch {
|
|
1218
|
+
// Router build failure is non-fatal — RouteForward will report
|
|
1219
|
+
// router_unavailable and the VP can pivot.
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return out;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1184
1225
|
/**
|
|
1185
1226
|
* Handle a unify_chat message from the web UI.
|
|
1186
1227
|
*
|
|
@@ -1191,6 +1232,12 @@ export async function handleUnifyGroupChat(msg) {
|
|
|
1191
1232
|
export async function handleUnifyChat(msg) {
|
|
1192
1233
|
const { prompt, mode } = msg;
|
|
1193
1234
|
if (!prompt?.trim()) return;
|
|
1235
|
+
// Bug 3 / Bug 4 — when the upstream group dispatcher addresses a specific
|
|
1236
|
+
// VP, it stamps `vpId` (and optionally a coordinator handle in
|
|
1237
|
+
// `_groupCoordinator`). Hoist them now so they survive the lazy-init
|
|
1238
|
+
// branch and reach the dispatcher.submit() queryOpts.
|
|
1239
|
+
const vpId = typeof msg.vpId === 'string' && msg.vpId.trim() ? msg.vpId.trim() : null;
|
|
1240
|
+
const groupCoordinator = msg._groupCoordinator || null;
|
|
1194
1241
|
|
|
1195
1242
|
// Deprecation warning — task-297 removed chat/work mode distinction
|
|
1196
1243
|
if (mode !== undefined && mode !== null) {
|
|
@@ -1302,6 +1349,7 @@ export async function handleUnifyChat(msg) {
|
|
|
1302
1349
|
const { entry } = session.dispatcher.submit(cleanedPrompt, {
|
|
1303
1350
|
messageId: msg.messageId,
|
|
1304
1351
|
override: override || undefined,
|
|
1352
|
+
queryOpts: buildVpQueryOpts({ vpId, groupCoordinator }),
|
|
1305
1353
|
});
|
|
1306
1354
|
sendUnifyEvent({
|
|
1307
1355
|
type: 'input_queue_updated',
|