@yeaft/webchat-agent 1.0.514 → 1.0.515

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.514",
3
+ "version": "1.0.515",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/yeaft/engine.js CHANGED
@@ -1250,8 +1250,8 @@ export class Engine {
1250
1250
  }
1251
1251
 
1252
1252
  /**
1253
- * Build the system prompt with the AMS-rendered Memory block, the
1254
- * Active Scope block, and skill content. The legacy multi-path
1253
+ * Build the system prompt with the AMS-rendered Memory block, routing
1254
+ * metadata, and skill content. The legacy multi-path
1255
1255
  * Memory injection (FTS-formatted + AMS snapshot + Layer-A summaries +
1256
1256
  * userProfile + coreMemory) was retired in DESIGN-PROMPT v1; callers
1257
1257
  * now thread a single `memoryInjection` string composed upstream from
@@ -1260,8 +1260,7 @@ export class Engine {
1260
1260
  * Routes through `buildWorkerPrompt`, which:
1261
1261
  * - Lays in the persona-as-identity block (or Yeaft identity fallback)
1262
1262
  * - Adds the Memory section (passed in as `memoryInjection`)
1263
- * - Adds the structured Active Scope block (`activeScope`)
1264
- * - Forwards optional `taskCtx` for the legacy task-context sub-block
1263
+ * - Adds multi-VP routing metadata from `activeScope` when applicable
1265
1264
  *
1266
1265
  * @param {object} args
1267
1266
  * @param {string} args.prompt — user prompt (for skill relevance matching)
@@ -1273,11 +1272,10 @@ export class Engine {
1273
1272
  * @param {string} [args.projectLabel] — current Project name and id for prompt attribution
1274
1273
  * @param {string} [args.workCenterInstructions] — frozen Agent-level Work Center policy
1275
1274
  * @param {string} [args.projectDoc] — resolved CLAUDE.md / AGENTS.md text (already truncated)
1276
- * @param {object} [args.taskCtx] — legacy task-context sub-block (optional)
1277
1275
  * @param {string} [args.explicitSkillName] — leading /skill:<name> command, if present
1278
1276
  * @returns {string}
1279
1277
  */
1280
- #buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectInstruction, projectLabel, workCenterInstructions, projectDoc, taskCtx, activeTasks, collabToolPolicy = null, activeToolNames = null, promptNotices = [], explicitSkillName, resolvedSkillContent = null } = {}) {
1278
+ #buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectInstruction, projectLabel, workCenterInstructions, projectDoc, promptNotices = [], explicitSkillName, resolvedSkillContent = null } = {}) {
1281
1279
  // #runQuery resolves Skill selection at each provider-request boundary so
1282
1280
  // a live Plugin policy change cannot leave stale content in the next prompt.
1283
1281
  // Keep the local fallback for internal callers that do not need selection
@@ -1292,21 +1290,8 @@ export class Engine {
1292
1290
  }
1293
1291
  }
1294
1292
 
1295
- // Prompt guidance must describe the same canonical capability
1296
- // intersection that reaches provider schemas and execution.
1297
- const registeredToolNames = this.#toolRegistry
1298
- ? this.#toolRegistry.getToolNames({
1299
- plugins: this.#config?.plugins,
1300
- collabToolPolicy,
1301
- })
1302
- : Array.from(this.#tools.keys());
1303
- const toolNames = activeToolNames instanceof Set
1304
- ? registeredToolNames.filter(name => activeToolNames.has(name))
1305
- : registeredToolNames;
1306
-
1307
1293
  return buildWorkerPrompt({
1308
1294
  language: this.#config.language || 'en',
1309
- toolNames,
1310
1295
  memoryInjection,
1311
1296
  skillContent,
1312
1297
  vpPersona,
@@ -1317,8 +1302,6 @@ export class Engine {
1317
1302
  workCenterInstructions,
1318
1303
  projectDoc,
1319
1304
  runtimePlatform: getRuntimePlatformInfo(),
1320
- taskCtx,
1321
- activeTasks,
1322
1305
  promptNotices,
1323
1306
  // Worker-shape harness is descriptive metadata for human inspection;
1324
1307
  // production prompts skip it to save tokens. Re-enable via env when
@@ -2306,18 +2289,13 @@ export class Engine {
2306
2289
  }))
2307
2290
  : [];
2308
2291
 
2309
- // ─── Active Scope (DESIGN-PROMPT §3 ④) ──────────────────────
2310
- // Structured per-turn scope summary: session + vp + members + envelope routing
2311
- // info. Long-form scope content lives in AMS — this block carries
2312
- // only IDs + tiny labels. (Feature scope retired 2026-05-13.)
2313
- const activeSessionTopics = topicScopesForResident
2314
- .map(scope => scope.replace(/^sessions\/[^/]+\/topic\//, ''));
2292
+ // ─── Multi-VP routing scope ──────────────────────────────────
2293
+ // Only identity and peer membership affect the routing contract rendered in
2294
+ // the system prompt. Session bookkeeping and inferred topics remain in their
2295
+ // owning runtime/memory paths instead of being mirrored into prompt metadata.
2315
2296
  const activeScope = {
2316
- sessionId: sessionId || '',
2317
- sessionMember: ownVpIdForAms || '',
2297
+ vpId: ownVpIdForAms || '',
2318
2298
  sessionMembers: Array.isArray(sessionMembers) ? sessionMembers : [],
2319
- sessionTopics: activeSessionTopics,
2320
- envelope: inboundEnvelope || null,
2321
2299
  };
2322
2300
 
2323
2301
  let projectDocSource = this.#getProjectDocBlock(workDir);
@@ -2332,11 +2310,6 @@ export class Engine {
2332
2310
  && typeof this.#taskManager.listActiveTasks === 'function'
2333
2311
  ? this.#taskManager.listActiveTasks(runtimeSessionId)
2334
2312
  : [];
2335
- let activeTasks = this.#taskManager
2336
- ? this.#taskManager.renderActiveTasksForPrompt(runtimeSessionId, {
2337
- language: this.#config.language || 'en',
2338
- })
2339
- : '';
2340
2313
  const registeredToolNames = this.#toolRegistry
2341
2314
  ? this.#toolRegistry.getToolNames({
2342
2315
  collabToolPolicy: effectiveCollabToolPolicy,
@@ -2407,9 +2380,6 @@ export class Engine {
2407
2380
  projectLabel,
2408
2381
  workCenterInstructions,
2409
2382
  projectDoc: projectDocContext.text,
2410
- activeTasks,
2411
- collabToolPolicy: effectiveCollabToolPolicy,
2412
- activeToolNames,
2413
2383
  promptNotices,
2414
2384
  explicitSkillName,
2415
2385
  resolvedSkillContent,
@@ -2825,11 +2795,6 @@ export class Engine {
2825
2795
  && typeof this.#taskManager.listActiveTasks === 'function'
2826
2796
  ? this.#taskManager.listActiveTasks(runtimeSessionId)
2827
2797
  : [];
2828
- activeTasks = this.#taskManager
2829
- ? this.#taskManager.renderActiveTasksForPrompt(runtimeSessionId, {
2830
- language: this.#config.language || 'en',
2831
- })
2832
- : '';
2833
2798
  activeToolNames = resolveCurrentActiveToolNames();
2834
2799
  for (const name of [...discoveredToolNames]) {
2835
2800
  if (this.#toolRegistry?.has(name)) activeToolNames?.add(name);
package/yeaft/prompts.js CHANGED
@@ -9,12 +9,10 @@
9
9
  *
10
10
  * Concept layering (DESIGN-PROMPT §3):
11
11
  * ① Identity — VP persona body (or Yeaft fallback)
12
- * ② Rules — session announcement, date, mode template, tools,
13
- * tool-guidance, skills, common rules
12
+ * ② Rules — Session/Project instructions, date, runtime, and skills
14
13
  * ③ Memory — single block produced upstream by the AMS render
15
14
  * outlet and threaded through here as `memoryInjection`
16
- * ④ Active Scope — structured per-turn scope summary
17
- * (session / vp / members / envelope IDs)
15
+ * ④ Routing — multi-VP identity and handoff metadata when applicable
18
16
  *
19
17
  * Long-term semantic context comes only from the AMS Memory outlet. The
20
18
  * conversation transcript stays in the messages timeline and is bounded by
@@ -142,7 +140,6 @@ const RAW_TEMPLATES = {
142
140
  commonRules: readTemplate('common-rules.md', { required: false }),
143
141
  modeUnified: readTemplate('mode-unified.md'),
144
142
  modeDream: readTemplate('mode-dream.md'),
145
- toolGuidance: readTemplate('tool-guidance.md'),
146
143
  // Phase 1 — DESIGN.md "Migration Plan" harness fragments. Optional so
147
144
  // older deployments without the templates still boot; buildWorkerPrompt /
148
145
  // buildRouterPrompt callers will simply omit the section.
@@ -192,12 +189,6 @@ const PROMPTS = {
192
189
  identity: 'No VP soul is active for this turn. Participate in the current session with grounded, evidence-based answers and preserve the user\'s context.',
193
190
  date: (d) => `Date: ${d}`,
194
191
  dream: 'You are in dream mode. Reflect on past conversations and consolidate memories.',
195
- // DESIGN-PROMPT §3 ④ — current session context block.
196
- activeScopeHeader: '## Current session context',
197
- activeScopeSessionIdLabel: 'Session ID',
198
- activeScopeMembersLabel: 'Session members',
199
- activeScopeTopicsLabel: 'Current focus',
200
- activeScopeEnvelopeLabel: 'Handoff',
201
192
  multiVpRoutingHeader: '## multi_vp_routing',
202
193
  sessionAnnouncementHeader: '[Session Announcement]',
203
194
  projectInstructionHeader: '[Project Instruction]',
@@ -218,12 +209,6 @@ const PROMPTS = {
218
209
  identity: '你正在当前会话中参与协作。保持用户上下文,回答要基于证据;需要工具时使用工具,但不要把自己没有实际执行过的事说成已经执行。',
219
210
  date: (d) => `日期:${d}`,
220
211
  dream: '你处于梦境模式。回顾过去的对话,整理和巩固记忆。',
221
- // DESIGN-PROMPT §3 ④ — 当前会话上下文。
222
- activeScopeHeader: '## 当前会话上下文',
223
- activeScopeSessionIdLabel: '会话 ID',
224
- activeScopeMembersLabel: '会话成员',
225
- activeScopeTopicsLabel: '当前讨论',
226
- activeScopeEnvelopeLabel: '转交消息',
227
212
  multiVpRoutingHeader: '## multi_vp_routing',
228
213
  sessionAnnouncementHeader: '[会话公告]',
229
214
  projectInstructionHeader: '[Project 指令]',
@@ -275,28 +260,22 @@ export function normalizePromptLanguage(language) {
275
260
  *
276
261
  * Prompt structure (DESIGN-PROMPT §3):
277
262
  * ① Identity — Core identity (persona or Yeaft fallback)
278
- * ② Rules — Session announcement, date, mode, tools, guidance, skills
263
+ * ② Rules — Session/Project instructions, date, runtime, and skills
279
264
  * ③ Memory — Single block produced by the AMS render outlet
280
265
  * (callers pass it as `memoryInjection`).
281
- * ④ Active Scope — Structured per-turn scope summary
282
- * (session / vp / members / envelope IDs).
283
- * (The previous standalone user_profile / core_memory blocks are
284
- * gone — those signals now arrive through AMS Resident. Task
285
- * context (`taskCtx`) was wired into Active Scope by task-334e
286
- * but never actually populated by the engine; removed 2026-05-13.)
266
+ * ④ Routing — Multi-VP identity and handoff metadata when applicable
287
267
  *
288
- * Active Scope params (DESIGN-PROMPT §3 ④):
289
- * @param {object} [activeScope] — structured scope summary for this turn
290
- * @param {string} [activeScope.sessionId]
291
- * @param {string} [activeScope.sessionMember]
292
- * @param {string[]} [activeScope.sessionMembers] current session roster
293
- * @param {string[]} [activeScope.sessionTopics] bounded topic labels for this session
294
- * @param {object} [activeScope.envelope] inbound routing info (sender, intent)
268
+ * Generic Session IDs, inferred topics, active-task labels, and repeated tool
269
+ * guidance are intentionally omitted from the system prompt.
270
+ *
271
+ * Multi-VP routing params:
272
+ * @param {object} [activeScope] — routing scope for this turn
273
+ * @param {string} [activeScope.vpId] current VP identity
274
+ * @param {string[]} [activeScope.sessionMembers] current Session roster
295
275
  *
296
276
  * @param {{
297
277
  * language?: string,
298
278
  * mode?: string,
299
- * toolNames?: string[],
300
279
  * memoryInjection?: string,
301
280
  * skillContent?: string,
302
281
  * activeScope?: object,
@@ -313,7 +292,6 @@ export function normalizePromptLanguage(language) {
313
292
  export function buildSystemPrompt({
314
293
  language = 'en',
315
294
  mode,
316
- toolNames = [],
317
295
  memoryInjection,
318
296
  skillContent,
319
297
  activeScope,
@@ -324,7 +302,6 @@ export function buildSystemPrompt({
324
302
  workCenterInstructions = '',
325
303
  projectDoc = '',
326
304
  runtimePlatform,
327
- activeTasks = '',
328
305
  promptNotices = [],
329
306
  } = {}) {
330
307
  // Normalize app locales like `zh-CN` to prompt dictionary/template keys.
@@ -405,20 +382,15 @@ export function buildSystemPrompt({
405
382
  parts.push(dreamTemplate || lang.dream);
406
383
  }
407
384
 
408
- // ─── 4. Runtime Platform + Tools + Tool Guidance ──────
385
+ // ─── 4. Runtime Platform ─────────────────────────────
386
+ // Tool schemas already carry their operational guidance. Repeating selected
387
+ // tool instructions in the system prompt wastes tokens and can drift from
388
+ // the executable schema, so only platform facts are rendered here.
409
389
  const runtimePlatformBlock = renderRuntimePlatformPrompt(runtimePlatform || getRuntimePlatformInfo(), effectiveLang);
410
390
  if (runtimePlatformBlock) {
411
391
  parts.push(runtimePlatformBlock);
412
392
  }
413
393
 
414
- if (toolNames.length > 0) {
415
- const guidance = renderActiveToolGuidance(toolNames, effectiveLang);
416
- const toolGuidanceTemplate = getTemplate('toolGuidance', effectiveLang);
417
- if (guidance && toolGuidanceTemplate) {
418
- parts.push(toolGuidanceTemplate.replace('{{guidance}}', guidance));
419
- }
420
- }
421
-
422
394
  // ─── 5. Skills Section ─────────────────────────────────
423
395
  if (skillContent) {
424
396
  parts.push(skillContent);
@@ -436,16 +408,10 @@ export function buildSystemPrompt({
436
408
  parts.push(memoryInjection.trim());
437
409
  }
438
410
 
439
- // ─── 7. Active Scope (DESIGN-PROMPT §3 ④) ──────────────
440
- // Structured per-turn scope summary. The group/vp/envelope identifiers
441
- // are rendered as a leading line. (Per-task taskCtx sub-block was
442
- // never wired and is removed 2026-05-13.)
443
- const activeScopeBlock = renderActiveScope(activeScope, lang);
444
- if (activeScopeBlock) parts.push(activeScopeBlock);
445
-
446
- const activeTaskText = typeof activeTasks === 'string' ? activeTasks.trim() : '';
447
- if (activeTaskText) parts.push(activeTaskText);
448
-
411
+ // ─── 7. Multi-VP routing ────────────────────────────────
412
+ // Generic Session IDs, inferred topics, and live task labels are runtime
413
+ // bookkeeping rather than model instructions. Keep only the routing block
414
+ // whose peer identities and RouteForward contract affect model behaviour.
449
415
  const multiVpRoutingBlock = renderMultiVpRouting(activeScope, lang);
450
416
  if (multiVpRoutingBlock) parts.push(multiVpRoutingBlock);
451
417
 
@@ -454,64 +420,6 @@ export function buildSystemPrompt({
454
420
 
455
421
  // ─── helpers ─────────────────────────────────────────────────────
456
422
 
457
- const TOOL_GUIDANCE_GROUPS = Object.freeze([
458
- {
459
- tools: ['DiscoverTools'],
460
- en: 'If the visible tools do not clearly cover the request, use `DiscoverTools` with the user goal before concluding that a capability is unavailable. If the target is absent from a page, follow `next_cursor` until found or the hidden directory is exhausted. If `restart_required` is true, restart without a cursor because the registered directory changed.',
461
- zh: '如果可见工具不能明确覆盖请求,应先按用户目标调用 `DiscoverTools`。若当前页没有目标,应按 `next_cursor` 继续翻页,直到找到或隐藏目录耗尽后,才能判断某项能力不可用。如果 `restart_required` 为 true,说明注册目录已变化,应丢弃游标重新开始。',
462
- },
463
- {
464
- tools: ['FileRead', 'FileWrite', 'FileEdit', 'Glob', 'Grep', 'ListDir', 'ApplyPatch', 'NotebookEdit'],
465
- en: 'Read existing files before editing. Use dedicated file/search tools instead of shell search or `sed -i`; make small, reviewable edits. Parallelize only proven-independent reads under the accuracy-first rule above.',
466
- zh: '编辑前先读现有文件。文件搜索和修改优先使用专用工具,不用 shell 搜索或 `sed -i`;改动保持小而可审查。只有明确满足上述准确性优先判据的读取才可并行。',
467
- },
468
- {
469
- tools: ['Bash'],
470
- en: 'Use non-interactive, deterministic shell commands, set reasonable timeouts, quote paths with spaces, and do not run destructive operations without authorization.',
471
- zh: 'Shell 命令保持非交互、确定性并设置合理 timeout;包含空格的路径要引用,未经授权不要执行破坏性操作。',
472
- },
473
- {
474
- tools: ['TodoWrite'],
475
- en: 'For non-trivial multi-step work, write a brief visible plan and call `TodoWrite` in the same assistant response as the first necessary work-tool call only when its arguments and safety do not depend on another result. Start with the smallest such call; do not speculative-batch the investigation or stop after planning unless user input genuinely blocks the first step.',
476
- zh: '非平凡多步骤任务先写简短可见计划。只有第一个工作工具调用已经确定有必要,且其参数和安全性都不依赖其他结果时,才在同一个 assistant response 中把它与 `TodoWrite` 一起发出;先执行满足条件的最小调用,不要推测性批量展开调查。只有用户信息确实阻塞第一步时才在规划后停下。',
477
- },
478
- {
479
- tools: ['SpawnAgent', 'PromptAgent', 'WaitAgent', 'CloseAgent', 'ListAgents'],
480
- en: 'Delegate only independent, bounded work. Keep ownership in the parent. After PromptAgent queues follow-up work, call WaitAgent in the same parent turn and collect the reply before ending. If a bounded wait times out, wait again with a larger bound unless the agent is stale/stalled. Relay the reply or continue the dependent work, then close the sub-agent when it is no longer needed.',
481
- zh: '只委派边界清晰且独立的工作。父级保留任务所有权。PromptAgent 排队后续工作后,必须在同一个父级 turn 调用 WaitAgent 并拿到回复再结束;有界等待超时后,除非 Agent 已 stale/stalled,否则使用更大上限继续等待;随后转述结果或继续依赖该结果的工作,不再需要时关闭子 Agent。',
482
- },
483
- {
484
- tools: ['ListTasks', 'ReadTaskLog', 'CancelTask'],
485
- en: 'Treat background tasks as live execution state, not memory facts. Inspect status or logs before retrying or cancelling work.',
486
- zh: '后台任务是实时执行状态,不是记忆事实。重试或取消前先检查状态或日志。',
487
- },
488
- {
489
- tools: ['RouteForward'],
490
- en: 'Use `RouteForward` for explicit VP-to-VP handoff; writing an @mention in ordinary text does not dispatch another VP.',
491
- zh: '显式 VP 转交必须使用 `RouteForward`;普通文本中的 @mention 不会调度另一个 VP。',
492
- },
493
- {
494
- tools: ['CreateWorkItem'],
495
- en: 'Use `CreateWorkItem` only for goals that need durable cross-turn coordination, recovery, review, waiting, or retry.',
496
- zh: '只有目标需要跨 turn 持久协调、恢复、评审、等待或重试时才使用 `CreateWorkItem`。',
497
- },
498
- ]);
499
-
500
- function renderActiveToolGuidance(toolNames, language) {
501
- const active = new Set(Array.isArray(toolNames) ? toolNames : []);
502
- const lines = [];
503
- if (active.size > 0) {
504
- lines.push(language === 'zh'
505
- ? '- 准确性优先:先用能解决当前未知的最小定向调用。只有每个调用都已经确定有必要,且其参数和安全性都不依赖同批其他结果时,才在一个响应中发出多个工具调用;否则串行执行。不要推测性扇出、重复成功的读取/搜索,也不要默认抓取多个来源;先检查证据,再决定是否扩展。'
506
- : '- Accuracy first: start with the smallest targeted call that can resolve the current uncertainty. Issue multiple tool calls in one response only when every call is already necessary and its arguments and safety do not depend on another call\'s result. Otherwise run them sequentially. Do not fan out speculatively, repeat a successful read/search, or fetch multiple sources by default; inspect evidence before expanding.');
507
- }
508
- for (const group of TOOL_GUIDANCE_GROUPS) {
509
- if (!group.tools.some(name => active.has(name))) continue;
510
- lines.push(`- ${language === 'zh' ? group.zh : group.en}`);
511
- }
512
- return lines.join('\n');
513
- }
514
-
515
423
  /**
516
424
  * Render the VP identity block when the engine is running on behalf of an
517
425
  * addressed VP. The `persona` body from role.md is the only soul source;
@@ -570,32 +478,6 @@ function selectVpPersonaBody(vpPersona, effectiveLang) {
570
478
 
571
479
 
572
480
 
573
- function renderActiveScope(activeScope, lang) {
574
- if (!activeScope || typeof activeScope !== 'object') return '';
575
-
576
- const isZh = lang === PROMPTS.zh;
577
- const separator = isZh ? ':' : ': ';
578
- const lines = [];
579
- const session = typeof activeScope.sessionId === 'string' && activeScope.sessionId.trim()
580
- ? activeScope.sessionId.trim()
581
- : '';
582
- if (session) lines.push(`${lang.activeScopeSessionIdLabel}${separator}${session}`);
583
-
584
- const membersLine = renderSessionMembersLine(activeScope.sessionMembers || activeScope.members, isZh);
585
- if (membersLine) lines.push(`${lang.activeScopeMembersLabel}${separator}${membersLine}`);
586
-
587
- const topicsLine = renderSessionTopicsLine(activeScope.sessionTopics, isZh);
588
- if (topicsLine) lines.push(`${lang.activeScopeTopicsLabel}${separator}${topicsLine}`);
589
-
590
- const envLine = renderEnvelopeLine(activeScope.envelope, isZh);
591
- if (envLine) lines.push(`${lang.activeScopeEnvelopeLabel}${separator}${envLine}`);
592
-
593
- if (lines.length === 0) return '';
594
-
595
- return `${lang.activeScopeHeader}\n${lines.join('\n')}`;
596
- }
597
-
598
-
599
481
  function firstNonEmptyString(...values) {
600
482
  for (const value of values) {
601
483
  if (typeof value === 'string' && value.trim()) return value.trim();
@@ -603,151 +485,6 @@ function firstNonEmptyString(...values) {
603
485
  return '';
604
486
  }
605
487
 
606
- function renderSessionMembersLine(members, useChineseSeparator = false) {
607
- return normalizeSessionMemberIds(members).join(useChineseSeparator ? '、' : ', ');
608
- }
609
-
610
- function renderSessionTopicsLine(topics, isZh = false) {
611
- const descriptions = [];
612
- const seen = new Set();
613
- for (const topic of normalizeSessionTopicIds(topics)) {
614
- const description = describeSessionTopic(topic, isZh);
615
- if (!description || seen.has(description)) continue;
616
- seen.add(description);
617
- descriptions.push(description);
618
- }
619
- return descriptions.join(isZh ? ';' : '; ');
620
- }
621
-
622
- function normalizeSessionTopicIds(topics) {
623
- if (!Array.isArray(topics)) return [];
624
- const clean = [];
625
- const seen = new Set();
626
- for (const topic of topics) {
627
- if (typeof topic !== 'string') continue;
628
- const id = topic.trim();
629
- if (!id || seen.has(id)) continue;
630
- seen.add(id);
631
- clean.push(id);
632
- }
633
- return clean;
634
- }
635
-
636
- function describeSessionTopic(topic, isZh = false) {
637
- const normalized = topic.toLowerCase().replace(/[_\s]+/g, '-');
638
-
639
- if (normalized.includes('dream') && normalized.includes('segments')) {
640
- return isZh
641
- ? '梦境记忆片段的抽取与整理'
642
- : 'Dream memory segment extraction and organization';
643
- }
644
- if (normalized.includes('dream') && normalized.includes('session') && normalized.includes('extraction')) {
645
- return isZh
646
- ? '梦境会话记忆的抽取质量'
647
- : 'Dream session-memory extraction quality';
648
- }
649
- if (normalized.includes('system-prompt') && normalized.includes('localization')) {
650
- return isZh
651
- ? '系统提示词的中英文一致性'
652
- : 'system prompt language localization';
653
- }
654
- if (normalized.includes('default-character') && normalized.includes('prompt-soul')) {
655
- return isZh
656
- ? '默认角色灵魂提示词的表达方式'
657
- : 'default persona soul prompt wording';
658
- }
659
- if (normalized.includes('prompt') && normalized.includes('soul')) {
660
- return isZh
661
- ? '角色灵魂提示词的表达方式'
662
- : 'persona soul prompt wording';
663
- }
664
- if (normalized.includes('openai-responses')) {
665
- return isZh
666
- ? 'Yeaft 的 OpenAI Responses 适配与模型配置'
667
- : 'Yeaft OpenAI Responses adapter and model configuration';
668
- }
669
- if (normalized.includes('model-config-isolation')) {
670
- return isZh
671
- ? 'Yeaft 的模型配置隔离'
672
- : 'Yeaft model configuration isolation';
673
- }
674
- if (normalized.includes('route-forward') || normalized.includes('handoff')) {
675
- return isZh
676
- ? 'Yeaft 的会话路由交接与可见性'
677
- : 'Yeaft session routing handoff and visibility';
678
- }
679
- if (normalized.includes('copilot-cli') || normalized.includes('chat-session')) {
680
- return isZh
681
- ? 'Copilot CLI 的聊天会话行为'
682
- : 'Copilot CLI chat session behavior';
683
- }
684
- if (normalized.includes('claude-opus')) {
685
- return isZh
686
- ? 'Yeaft 的 Claude Opus 模型接入'
687
- : 'Yeaft Claude Opus model integration';
688
- }
689
- if (normalized.includes('pr-workflow')) {
690
- return isZh
691
- ? '项目的 PR review、merge 和 tag 发布流程'
692
- : 'project PR review, merge, and tag release workflow';
693
- }
694
- if (/\bpr[-/]?\d+\b/.test(normalized) || normalized.includes('release') || /v\d+\.\d+\.\d+/.test(normalized)) {
695
- return isZh
696
- ? '最近的 PR 修复、review、merge 和 tag 发布流程'
697
- : 'recent PR fixes, review, merge, and tag release work';
698
- }
699
- if (normalized.includes('active-scope')) {
700
- return isZh
701
- ? '当前会话上下文的提示词呈现'
702
- : 'current session context prompt rendering';
703
- }
704
- if (normalized.includes('prompt') || normalized.includes('system')) {
705
- return isZh
706
- ? '近期的系统提示词调整'
707
- : humanizeTopicSlug(topic, false) || 'recent system prompt work';
708
- }
709
- if (normalized.includes('dream')) {
710
- return isZh
711
- ? '近期的 Dream 记忆维护工作'
712
- : humanizeTopicSlug(topic, false) || 'recent Dream memory work';
713
- }
714
- if (normalized.includes('session')) {
715
- return isZh
716
- ? '近期的会话上下文调整'
717
- : humanizeTopicSlug(topic, false) || 'recent session context work';
718
- }
719
-
720
- if (isZh) return '近期的项目协作事项';
721
- return humanizeTopicSlug(topic, false) || 'recent session collaboration topics';
722
- }
723
-
724
- function humanizeTopicSlug(topic, isZh = false) {
725
- if (isZh) return '';
726
- const words = topic
727
- .replace(/[\/_-]+/g, ' ')
728
- .replace(/\bv\d+(?:\.\d+)+\b/gi, '')
729
- .replace(/\bpr\s*\d+\b/gi, '')
730
- .trim()
731
- .split(/\s+/)
732
- .filter(Boolean);
733
- if (words.length === 0) return '';
734
-
735
- const normalizedWords = words.map(word => {
736
- const lower = word.toLowerCase();
737
- if (lower === 'yeaft') return 'Yeaft';
738
- if (lower === 'project') return 'project';
739
- if (lower === 'config') return 'configuration';
740
- if (lower === 'isolation') return 'isolation';
741
- if (lower === 'rendering') return 'rendering';
742
- if (lower === 'workflow') return 'workflow';
743
- if (lower === 'session') return 'session';
744
- if (lower === 'context') return 'context';
745
- return word;
746
- });
747
-
748
- return normalizedWords.join(' ');
749
- }
750
-
751
488
  function normalizeSessionMemberIds(members) {
752
489
  if (!Array.isArray(members)) return [];
753
490
  const clean = [];
@@ -764,7 +501,7 @@ function normalizeSessionMemberIds(members) {
764
501
 
765
502
  function renderMultiVpRouting(activeScope, lang) {
766
503
  if (!activeScope || typeof activeScope !== 'object') return '';
767
- const ownId = firstNonEmptyString(activeScope.sessionMember, activeScope.vpId);
504
+ const ownId = firstNonEmptyString(activeScope.vpId);
768
505
  const members = normalizeSessionMemberIds(activeScope.sessionMembers || activeScope.members);
769
506
  const peers = ownId ? members.filter((member) => member !== ownId) : members;
770
507
  if (peers.length === 0) return '';
@@ -793,33 +530,6 @@ function renderMultiVpRouting(activeScope, lang) {
793
530
  ].join('\n');
794
531
  }
795
532
 
796
- /**
797
- * Render a one-line envelope summary. Pulls the small set of routing
798
- * fields we surface to the LLM (sender, intent, originating user) and
799
- * leaves the rest in AMS. Returns '' when the envelope carries no
800
- * useful signal.
801
- *
802
- * @param {object|null|undefined} envelope
803
- * @returns {string}
804
- */
805
- function renderEnvelopeLine(envelope, isZh = false) {
806
- if (!envelope || typeof envelope !== 'object') return '';
807
- const segments = [];
808
- const fromVp = typeof envelope.fromVpId === 'string' && envelope.fromVpId.trim()
809
- ? envelope.fromVpId.trim()
810
- : (typeof envelope.senderVpId === 'string' ? envelope.senderVpId.trim() : '');
811
- if (fromVp) segments.push(`${isZh ? '来自' : 'from'}=${fromVp}`);
812
- const fromUser = typeof envelope.fromUserId === 'string' && envelope.fromUserId.trim()
813
- ? envelope.fromUserId.trim()
814
- : '';
815
- if (fromUser) segments.push(`${isZh ? '用户' : 'user'}=${fromUser}`);
816
- const intent = typeof envelope.intent === 'string' && envelope.intent.trim()
817
- ? envelope.intent.trim()
818
- : '';
819
- if (intent) segments.push(`${isZh ? '意图' : 'intent'}=${intent}`);
820
- return segments.join(' ');
821
- }
822
-
823
533
  // ─── Phase 1: Worker / Router prompt splits ──────────────────────
824
534
  //
825
535
  // DESIGN.md (multi-VP redesign) describes two distinct prompt shapes:
@@ -830,7 +540,7 @@ function renderEnvelopeLine(envelope, isZh = false) {
830
540
  // carries Layer-A summaries + UserProfile + CoreMemory, AMS OnDemand
831
541
  // carries the per-turn FTS hits. The worker shape that survives is:
832
542
  // harness/worker-shape — optional descriptive metadata
833
- // buildSystemPrompt(...) — ① Identity ② Rules ③ Memory ④ Active Scope
543
+ // buildSystemPrompt(...) — ① Identity ② Rules ③ Memory ④ Routing
834
544
  // optional taskScope/turnScope — caller-provided pass-through strings
835
545
  // `renderLayerASummaries` is no longer called inside the worker prompt
836
546
  // because AMS already renders the same summaries — calling both was
@@ -891,15 +601,12 @@ export function renderLayerASummaries(summaries, language = 'en') {
891
601
  *
892
602
  * Output sections (DESIGN-PROMPT §3 layered concepts):
893
603
  * harness/worker-shape (optional) — descriptive metadata
894
- * buildSystemPrompt(...) — ① Identity ② Rules ③ Memory ④ Active Scope
604
+ * buildSystemPrompt(...) — ① Identity ② Rules ③ Memory ④ Routing
895
605
  *
896
- * Earlier task-322 / task-334e variants accepted `taskScope` and
897
- * `turnScope` pass-through strings so callers could append their own
898
- * scope blocks. DESIGN-PROMPT v1 retired that surface — Active Scope is
899
- * now structured (`activeScope: { sessionId, vpId, envelope }`) and
900
- * rendered by `buildSystemPrompt` itself. Both pass-through params
901
- * had zero remaining callers when v1 landed; removing them prevents the
902
- * "two ways to describe scope" drift §1 set out to eliminate.
606
+ * Earlier variants accepted `taskScope` and `turnScope` pass-through
607
+ * strings so callers could append their own scope blocks. That surface is
608
+ * retired. `activeScope` now carries only the VP identity and Session roster
609
+ * needed to render the deterministic multi-VP routing contract.
903
610
  *
904
611
  * @param {{
905
612
  * language?: 'en'|'zh',
@@ -924,7 +631,7 @@ export function buildWorkerPrompt(params = {}) {
924
631
  if (shape) parts.push(shape);
925
632
  }
926
633
 
927
- // Identity + Rules + Memory + Active Scope (DESIGN-PROMPT §3).
634
+ // Identity + Rules + Memory + Routing (DESIGN-PROMPT §3).
928
635
  const baseBlock = buildSystemPrompt({ ...rest, language: effectiveLang });
929
636
  if (baseBlock) parts.push(baseBlock);
930
637