@yeaft/webchat-agent 0.1.971 → 0.1.973
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/dream/apply.js +2 -2
- package/yeaft/dream/prompts/create.md +19 -2
- package/yeaft/dream/prompts/extract-session.md +49 -47
- package/yeaft/dream/prompts/extract-topic.md +46 -50
- package/yeaft/dream/prompts/extract-user.md +56 -45
- package/yeaft/dream/prompts/extract-vp.md +49 -47
- package/yeaft/dream/prompts/index.js +19 -1
- package/yeaft/dream/prompts/summarize-scope.md +34 -25
- package/yeaft/dream/prompts/triage-pass1.md +25 -2
- package/yeaft/dream/prompts/triage-pass2.md +18 -0
- package/yeaft/dream/prompts/update.md +38 -2
- package/yeaft/dream/triage.js +2 -2
- package/yeaft/prompts.js +255 -118
- package/yeaft/templates/base.md +44 -96
- package/yeaft/templates/common-rules.md +68 -82
- package/yeaft/templates/harness/router-shape.md +31 -32
- package/yeaft/templates/harness/worker-shape.md +23 -22
- package/yeaft/templates/identity-yeaft.md +4 -4
- package/yeaft/templates/mode-unified.md +10 -48
- package/yeaft/templates/personas/explorer.md +23 -4
- package/yeaft/templates/personas/implementer.md +23 -4
- package/yeaft/templates/personas/researcher.md +25 -6
- package/yeaft/templates/personas/reviewer.md +23 -4
- package/yeaft/templates/plan-instruction.md +40 -44
- package/yeaft/vp/seed-defaults.js +498 -24
- package/yeaft/vp/seed-topup.js +81 -5
- package/yeaft/web-bridge.js +101 -9
package/yeaft/vp/seed-topup.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* 1. **Top-up missing stock VPs**. If a vpId from `DEFAULT_VPS` is not
|
|
15
15
|
* on disk, `createVp()` it. This keeps product-owned defaults such as
|
|
16
|
-
* Omni and the expanded role roster visible in
|
|
16
|
+
* Omni and the expanded role roster visible in session/member pickers.
|
|
17
17
|
*
|
|
18
18
|
* 2. **Backfill the `area` frontmatter line** on existing seeded VPs whose
|
|
19
19
|
* role.md predates the area field. The body is left BYTE-IDENTICAL —
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* - **Never** overwrite a VP that is on disk. The user might have edited
|
|
26
26
|
* persona/role/traits; that is their truth, not ours.
|
|
27
27
|
* - **Keep stock defaults available.** If a shipped stock VP is missing,
|
|
28
|
-
* recreate it, but never overwrite an on-disk VP. The
|
|
28
|
+
* recreate it, but never overwrite an on-disk VP. The session/member picker
|
|
29
29
|
* depends on these product-owned defaults being present.
|
|
30
30
|
* - Best-effort: any failure is logged, never thrown.
|
|
31
31
|
*
|
|
@@ -209,6 +209,37 @@ export function insertNameZhLine(source, nameZh) {
|
|
|
209
209
|
return insertFrontmatterLine(source, 'nameZh', nameZh);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Backfill `roleZh:` into role.md frontmatter.
|
|
214
|
+
*
|
|
215
|
+
* @param {string} source
|
|
216
|
+
* @param {string} roleZh
|
|
217
|
+
* @returns {string|null}
|
|
218
|
+
*/
|
|
219
|
+
export function insertRoleZhLine(source, roleZh) {
|
|
220
|
+
return insertFrontmatterLine(source, 'roleZh', roleZh);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function roleBodyOf(source) {
|
|
224
|
+
const match = String(source || '').match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
225
|
+
return match ? match[1] : '';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function replaceRoleBody(source, body) {
|
|
229
|
+
const match = String(source || '').match(/^(---\r?\n[\s\S]*?\r?\n---\r?\n?)([\s\S]*)$/);
|
|
230
|
+
if (!match) return null;
|
|
231
|
+
return `${match[1]}${String(body || '').trim()}\n`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function backfillLocalizedPersonaBody(source, vp) {
|
|
235
|
+
const body = roleBodyOf(source).trim();
|
|
236
|
+
const nextBody = typeof vp.persona === 'string' ? vp.persona.trim() : '';
|
|
237
|
+
const oldBody = typeof vp.personaEn === 'string' ? vp.personaEn.trim() : '';
|
|
238
|
+
if (!body || !nextBody || body.includes('<!-- lang:')) return null;
|
|
239
|
+
if (!oldBody || body !== oldBody) return null;
|
|
240
|
+
return replaceRoleBody(source, nextBody);
|
|
241
|
+
}
|
|
242
|
+
|
|
212
243
|
/**
|
|
213
244
|
* Top-up the default VPs into an existing `libDir`.
|
|
214
245
|
*
|
|
@@ -217,6 +248,8 @@ export function insertNameZhLine(source, nameZh) {
|
|
|
217
248
|
* added: string[],
|
|
218
249
|
* areaBackfilled: string[],
|
|
219
250
|
* nameZhBackfilled: string[],
|
|
251
|
+
* roleZhBackfilled: string[],
|
|
252
|
+
* personaBackfilled: string[],
|
|
220
253
|
* respectedDeletes: string[],
|
|
221
254
|
* skippedExisting: string[],
|
|
222
255
|
* errors: Array<{vpId:string, code:string, message:string}>,
|
|
@@ -226,6 +259,8 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
226
259
|
const added = [];
|
|
227
260
|
const areaBackfilled = [];
|
|
228
261
|
const nameZhBackfilled = [];
|
|
262
|
+
const roleZhBackfilled = [];
|
|
263
|
+
const personaBackfilled = [];
|
|
229
264
|
const respectedDeletes = [];
|
|
230
265
|
const skippedExisting = [];
|
|
231
266
|
/** @type {Array<{vpId:string,code:string,message:string}>} */
|
|
@@ -235,7 +270,7 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
235
270
|
// that case there's nothing to top up — seedDefaultVps will populate
|
|
236
271
|
// everything. We still return cleanly.
|
|
237
272
|
if (!existsSync(libDir)) {
|
|
238
|
-
return { added, areaBackfilled, nameZhBackfilled, respectedDeletes, skippedExisting, errors };
|
|
273
|
+
return { added, areaBackfilled, nameZhBackfilled, roleZhBackfilled, personaBackfilled, respectedDeletes, skippedExisting, errors };
|
|
239
274
|
}
|
|
240
275
|
|
|
241
276
|
let versions = readSeedVersions(libDir);
|
|
@@ -314,6 +349,47 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
314
349
|
});
|
|
315
350
|
}
|
|
316
351
|
}
|
|
352
|
+
if (vp.roleZh) {
|
|
353
|
+
try {
|
|
354
|
+
const src = readRole();
|
|
355
|
+
if (src != null) {
|
|
356
|
+
const patched = insertRoleZhLine(src, vp.roleZh);
|
|
357
|
+
if (patched != null && patched !== src) {
|
|
358
|
+
writeFileSync(rolePath, patched, 'utf-8');
|
|
359
|
+
currentSrc = patched;
|
|
360
|
+
roleZhBackfilled.push(vpId);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} catch (err) {
|
|
364
|
+
errors.push({
|
|
365
|
+
vpId,
|
|
366
|
+
code: 'role_zh_backfill_failed',
|
|
367
|
+
message: String(err?.message || err),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// v0.1.967 — stock VP soul localization. Existing first-run stock VPs
|
|
372
|
+
// have an English-only body. Replace only the exact old stock body;
|
|
373
|
+
// any user-edited body is respected byte-for-byte.
|
|
374
|
+
if (vp.personaZh && vp.personaEn) {
|
|
375
|
+
try {
|
|
376
|
+
const src = readRole();
|
|
377
|
+
if (src != null) {
|
|
378
|
+
const patched = backfillLocalizedPersonaBody(src, vp);
|
|
379
|
+
if (patched != null && patched !== src) {
|
|
380
|
+
writeFileSync(rolePath, patched, 'utf-8');
|
|
381
|
+
currentSrc = patched;
|
|
382
|
+
personaBackfilled.push(vpId);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
errors.push({
|
|
387
|
+
vpId,
|
|
388
|
+
code: 'persona_localization_backfill_failed',
|
|
389
|
+
message: String(err?.message || err),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
317
393
|
// Make sure the ledger records it (e.g. user-authored VP whose id
|
|
318
394
|
// collides with a default — we still want to skip future creates).
|
|
319
395
|
if (!inLedger) versions.seeded[vpId] = 'legacy';
|
|
@@ -323,7 +399,7 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
323
399
|
if (inLedger && !STOCK_VP_IDS.has(vpId)) {
|
|
324
400
|
// We seeded this custom/default VP before, user has since deleted it — respect that.
|
|
325
401
|
// Stock/default personas are product-owned roster entries and must remain
|
|
326
|
-
// available in
|
|
402
|
+
// available in session creation/member pickers after migrations. Recreate
|
|
327
403
|
// them below without overwriting anything that exists on disk.
|
|
328
404
|
respectedDeletes.push(vpId);
|
|
329
405
|
continue;
|
|
@@ -351,5 +427,5 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
351
427
|
}
|
|
352
428
|
|
|
353
429
|
writeSeedVersions(libDir, versions);
|
|
354
|
-
return { added, areaBackfilled, nameZhBackfilled, respectedDeletes, skippedExisting, errors };
|
|
430
|
+
return { added, areaBackfilled, nameZhBackfilled, roleZhBackfilled, personaBackfilled, respectedDeletes, skippedExisting, errors };
|
|
355
431
|
}
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -150,7 +150,9 @@ const turnAbortCtrls = new Map();
|
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
152
|
* Per-turn runtime ownership. Targeted thread aborts use this instead of
|
|
153
|
-
* blindly aborting every turn controller in the process.
|
|
153
|
+
* blindly aborting every turn controller in the process. Queued VP turns are
|
|
154
|
+
* registered here before their AbortController exists so the VP-list Stop
|
|
155
|
+
* button can remove them from the inbox instead of becoming a no-op.
|
|
154
156
|
* @type {Map<string, { sessionId: string, vpId: string, threadId: string, key: string }>}
|
|
155
157
|
*/
|
|
156
158
|
const turnAbortMeta = new Map();
|
|
@@ -1062,6 +1064,7 @@ async function routeEnvelopeToVpThread(sessionId, vpId, envelope) {
|
|
|
1062
1064
|
vpInboxes.set(key, inbox);
|
|
1063
1065
|
}
|
|
1064
1066
|
inbox.push({ envelope, turnId, thread });
|
|
1067
|
+
turnAbortMeta.set(turnId, { sessionId, vpId, threadId: thread.threadId, key });
|
|
1065
1068
|
|
|
1066
1069
|
try {
|
|
1067
1070
|
sendSessionEvent({
|
|
@@ -3354,6 +3357,63 @@ const _persistedUserMsgIds = new Set();
|
|
|
3354
3357
|
*
|
|
3355
3358
|
* @param {string[]} aborted — output array, mutated in place
|
|
3356
3359
|
*/
|
|
3360
|
+
function removeQueuedVpTurn(turnId) {
|
|
3361
|
+
const meta = turnAbortMeta.get(turnId);
|
|
3362
|
+
const keys = meta?.key ? [meta.key] : Array.from(vpInboxes.keys());
|
|
3363
|
+
let removed = false;
|
|
3364
|
+
for (const key of keys) {
|
|
3365
|
+
const inbox = vpInboxes.get(key);
|
|
3366
|
+
if (!Array.isArray(inbox) || inbox.length === 0) continue;
|
|
3367
|
+
const before = inbox.length;
|
|
3368
|
+
const kept = inbox.filter((entry) => entry?.turnId !== turnId);
|
|
3369
|
+
if (kept.length === before) continue;
|
|
3370
|
+
removed = true;
|
|
3371
|
+
inbox.length = 0;
|
|
3372
|
+
inbox.push(...kept);
|
|
3373
|
+
}
|
|
3374
|
+
return removed;
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
function emitQueuedTurnAbort(meta, turnId) {
|
|
3378
|
+
if (!meta?.vpId) return;
|
|
3379
|
+
const sessionId = meta.sessionId || null;
|
|
3380
|
+
const vpId = meta.vpId;
|
|
3381
|
+
const threadId = meta.threadId || 'main';
|
|
3382
|
+
try {
|
|
3383
|
+
getVpStatusBroker().transition({
|
|
3384
|
+
sessionId,
|
|
3385
|
+
vpId,
|
|
3386
|
+
threadId,
|
|
3387
|
+
state: 'idle',
|
|
3388
|
+
turnId,
|
|
3389
|
+
messageCount: 0,
|
|
3390
|
+
});
|
|
3391
|
+
} catch (err) {
|
|
3392
|
+
console.warn('[Yeaft] queued VP abort status transition failed:', err?.message || err);
|
|
3393
|
+
}
|
|
3394
|
+
try {
|
|
3395
|
+
sendSessionEvent({
|
|
3396
|
+
type: 'vp_typing_end',
|
|
3397
|
+
sessionId,
|
|
3398
|
+
vpId,
|
|
3399
|
+
threadId,
|
|
3400
|
+
turnId,
|
|
3401
|
+
ts: Date.now(),
|
|
3402
|
+
}, { sessionId, vpId, threadId, turnId });
|
|
3403
|
+
} catch { /* never crash WS pipeline */ }
|
|
3404
|
+
try {
|
|
3405
|
+
sendSessionEvent({
|
|
3406
|
+
type: 'vp_turn_end',
|
|
3407
|
+
sessionId,
|
|
3408
|
+
vpId,
|
|
3409
|
+
threadId,
|
|
3410
|
+
turnId,
|
|
3411
|
+
reason: 'aborted',
|
|
3412
|
+
ts: Date.now(),
|
|
3413
|
+
}, { sessionId, vpId, threadId, turnId });
|
|
3414
|
+
} catch { /* never crash WS pipeline */ }
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3357
3417
|
function abortAllVpRuntime(aborted) {
|
|
3358
3418
|
for (const [key, ctrl] of vpAborts) {
|
|
3359
3419
|
try {
|
|
@@ -3450,17 +3510,21 @@ export function handleYeaftAbortTurn(msg = {}) {
|
|
|
3450
3510
|
sendSessionEvent({ type: 'yeaft_turn_aborted', turnId: null, success: false });
|
|
3451
3511
|
return;
|
|
3452
3512
|
}
|
|
3513
|
+
|
|
3514
|
+
const meta = turnAbortMeta.get(turnId);
|
|
3453
3515
|
const ctrl = turnAbortCtrls.get(turnId);
|
|
3516
|
+
let success = false;
|
|
3517
|
+
|
|
3454
3518
|
if (ctrl && !ctrl.signal.aborted) {
|
|
3455
|
-
try { ctrl.abort(); } catch { /* best-effort */ }
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
} else {
|
|
3460
|
-
turnAbortCtrls.delete(turnId);
|
|
3461
|
-
turnAbortMeta.delete(turnId);
|
|
3462
|
-
sendSessionEvent({ type: 'yeaft_turn_aborted', turnId, success: false });
|
|
3519
|
+
try { ctrl.abort(); success = true; } catch { /* best-effort */ }
|
|
3520
|
+
} else if (removeQueuedVpTurn(turnId)) {
|
|
3521
|
+
success = true;
|
|
3522
|
+
emitQueuedTurnAbort(meta, turnId);
|
|
3463
3523
|
}
|
|
3524
|
+
|
|
3525
|
+
turnAbortCtrls.delete(turnId);
|
|
3526
|
+
turnAbortMeta.delete(turnId);
|
|
3527
|
+
sendSessionEvent({ type: 'yeaft_turn_aborted', turnId, success });
|
|
3464
3528
|
}
|
|
3465
3529
|
|
|
3466
3530
|
/**
|
|
@@ -4460,3 +4524,31 @@ export async function handleYeaftMcpReload(msg = {}) {
|
|
|
4460
4524
|
});
|
|
4461
4525
|
broadcastMcpUpdated({ reason: 'reload', name: targetName, failures });
|
|
4462
4526
|
}
|
|
4527
|
+
|
|
4528
|
+
export const __testHooks = {
|
|
4529
|
+
resetAbortState() {
|
|
4530
|
+
turnAbortCtrls.clear();
|
|
4531
|
+
turnAbortMeta.clear();
|
|
4532
|
+
vpAborts.clear();
|
|
4533
|
+
vpInboxes.clear();
|
|
4534
|
+
},
|
|
4535
|
+
seedQueuedVpTurn({ sessionId = 'session-test', vpId = 'vp-test', threadId = 'main', turnId = 'turn-test' } = {}) {
|
|
4536
|
+
const key = threadKey(sessionId, vpId, threadId);
|
|
4537
|
+
const inbox = vpInboxes.get(key) || [];
|
|
4538
|
+
inbox.push({ envelope: { msg: { id: `${turnId}-msg` } }, turnId, thread: { threadId, title: '', messageIds: [] } });
|
|
4539
|
+
vpInboxes.set(key, inbox);
|
|
4540
|
+
turnAbortMeta.set(turnId, { sessionId, vpId, threadId, key });
|
|
4541
|
+
return { key, turnId };
|
|
4542
|
+
},
|
|
4543
|
+
seedRunningVpTurn({ sessionId = 'session-test', vpId = 'vp-test', threadId = 'main', turnId = 'turn-test' } = {}) {
|
|
4544
|
+
const key = threadKey(sessionId, vpId, threadId);
|
|
4545
|
+
const ctrl = new AbortController();
|
|
4546
|
+
vpAborts.set(key, ctrl);
|
|
4547
|
+
turnAbortCtrls.set(turnId, ctrl);
|
|
4548
|
+
turnAbortMeta.set(turnId, { sessionId, vpId, threadId, key });
|
|
4549
|
+
return { key, turnId, ctrl };
|
|
4550
|
+
},
|
|
4551
|
+
queuedTurnIds() {
|
|
4552
|
+
return Array.from(vpInboxes.values()).flatMap((inbox) => Array.isArray(inbox) ? inbox.map((entry) => entry.turnId) : []);
|
|
4553
|
+
},
|
|
4554
|
+
};
|