@yeaft/webchat-agent 0.1.691 → 0.1.697
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 +103 -54
- package/unify/memory/budget.js +19 -12
- package/unify/prompts.js +165 -188
package/package.json
CHANGED
package/unify/engine.js
CHANGED
|
@@ -545,25 +545,29 @@ export class Engine {
|
|
|
545
545
|
}
|
|
546
546
|
|
|
547
547
|
/**
|
|
548
|
-
* Build the system prompt with
|
|
549
|
-
* and
|
|
548
|
+
* Build the system prompt with the AMS-rendered Memory block, the
|
|
549
|
+
* Active Scope block, and skill content. The legacy multi-path
|
|
550
|
+
* Memory injection (FTS-formatted + AMS snapshot + Layer-A summaries +
|
|
551
|
+
* userProfile + coreMemory) was retired in DESIGN-PROMPT v1; callers
|
|
552
|
+
* now thread a single `memoryInjection` string composed upstream from
|
|
553
|
+
* the AMS snapshot.
|
|
550
554
|
*
|
|
551
555
|
* Routes through `buildWorkerPrompt`, which:
|
|
552
556
|
* - Lays in the persona-as-identity block (or Yeaft identity fallback)
|
|
553
|
-
* -
|
|
554
|
-
* -
|
|
555
|
-
*
|
|
557
|
+
* - Adds the Memory section (passed in as `memoryInjection`)
|
|
558
|
+
* - Adds the structured Active Scope block (`activeScope`)
|
|
559
|
+
* - Forwards optional `taskCtx` for the legacy task-context sub-block
|
|
556
560
|
*
|
|
557
|
-
* @param {
|
|
558
|
-
* @param {string}
|
|
559
|
-
* @param {string}
|
|
560
|
-
* @param {
|
|
561
|
-
* @param {
|
|
562
|
-
* @param {
|
|
563
|
-
* @param {
|
|
561
|
+
* @param {object} args
|
|
562
|
+
* @param {string} args.prompt — user prompt (for skill relevance matching)
|
|
563
|
+
* @param {string} args.memoryInjection — prebuilt Memory block from AMS
|
|
564
|
+
* @param {object} [args.vpPersona]
|
|
565
|
+
* @param {object} [args.activeScope] — DESIGN-PROMPT §3 ④ structured scope summary
|
|
566
|
+
* @param {string} [args.groupAnnouncement]
|
|
567
|
+
* @param {object} [args.taskCtx] — legacy task-context sub-block (optional)
|
|
564
568
|
* @returns {string}
|
|
565
569
|
*/
|
|
566
|
-
#buildSystemPrompt(
|
|
570
|
+
#buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, groupAnnouncement, taskCtx } = {}) {
|
|
567
571
|
// Get relevant skill content if SkillManager is wired
|
|
568
572
|
let skillContent = '';
|
|
569
573
|
if (this.#skillManager && prompt) {
|
|
@@ -578,21 +582,16 @@ export class Engine {
|
|
|
578
582
|
return buildWorkerPrompt({
|
|
579
583
|
language: this.#config.language || 'en',
|
|
580
584
|
toolNames,
|
|
581
|
-
memory,
|
|
582
585
|
memoryInjection,
|
|
583
|
-
compactSummary,
|
|
584
586
|
skillContent,
|
|
585
|
-
userProfile,
|
|
586
587
|
vpPersona,
|
|
587
|
-
|
|
588
|
+
activeScope,
|
|
588
589
|
groupAnnouncement,
|
|
590
|
+
taskCtx,
|
|
589
591
|
// Worker-shape harness is descriptive metadata for human inspection;
|
|
590
592
|
// production prompts skip it to save tokens. Re-enable via env when
|
|
591
593
|
// diagnosing prompt structure issues.
|
|
592
594
|
includeShape: process.env.UNIFY_PROMPT_INCLUDE_SHAPE === '1',
|
|
593
|
-
// task-334f: memory_trace tool is now registered (49 → 51 tools), so
|
|
594
|
-
// unlock the core_memory meta-line behind 334e's feature flag.
|
|
595
|
-
memoryTraceAvailable: true,
|
|
596
595
|
});
|
|
597
596
|
}
|
|
598
597
|
|
|
@@ -931,16 +930,18 @@ export class Engine {
|
|
|
931
930
|
*/
|
|
932
931
|
async *#runQuery({ prompt, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, groupId, vpPlan, groupAnnouncement }) {
|
|
933
932
|
|
|
934
|
-
// ─── Pre-query: FTS5 Memory Recall +
|
|
935
|
-
// Memory
|
|
936
|
-
//
|
|
937
|
-
//
|
|
938
|
-
//
|
|
939
|
-
//
|
|
933
|
+
// ─── Pre-query: FTS5 Memory Recall + AMS snapshot ─────
|
|
934
|
+
// Memory has a SINGLE render outlet now (DESIGN-PROMPT §3 ③):
|
|
935
|
+
// 1. FTS5 pre-flow recall produces a list of segments;
|
|
936
|
+
// 2. those segments are pushed into AMS OnDemand;
|
|
937
|
+
// 3. AMS renders a budget-aware snapshot (Resident + Recent +
|
|
938
|
+
// OnDemand) — that snapshot IS `memoryInjection`.
|
|
939
|
+
// The legacy second path (`recallResult.formatted` concatenated
|
|
940
|
+
// directly into `memoryInjection`) was a duplicate render of the
|
|
941
|
+
// same segments AMS would also surface, so it's gone.
|
|
940
942
|
let memoryInjection = '';
|
|
941
943
|
let recallEntryCount = 0;
|
|
942
944
|
|
|
943
|
-
// FTS5 recall: append per-turn scoped hits to memory injection
|
|
944
945
|
const recallResult = await this.#recallMemory(prompt, {
|
|
945
946
|
groupId,
|
|
946
947
|
vpId: vpPersona && typeof vpPersona === 'object' && typeof vpPersona.vpId === 'string'
|
|
@@ -950,26 +951,16 @@ export class Engine {
|
|
|
950
951
|
? inboundEnvelope.featureId
|
|
951
952
|
: undefined,
|
|
952
953
|
});
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
recallEntryCount = recallResult.entries.length;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
if (memoryInjection) {
|
|
954
|
+
recallEntryCount = recallResult && Array.isArray(recallResult.entries)
|
|
955
|
+
? recallResult.entries.length
|
|
956
|
+
: 0;
|
|
957
|
+
if (recallEntryCount > 0) {
|
|
961
958
|
yield { type: 'recall', entryCount: recallEntryCount, cached: false };
|
|
962
959
|
}
|
|
963
960
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
// Phase 8 wire-up — Layer A scope summaries
|
|
968
|
-
// Load `summary.md` for the user / addressed group / addressed VP from
|
|
969
|
-
// the scoped memory tree (DESIGN.md §2). This is the rolling synopsis a
|
|
970
|
-
// dream tick maintains; we surface it to the worker prompt so the LLM
|
|
971
|
-
// has cheap, persistent context without paying the recall cost on every
|
|
972
|
-
// turn. Failures are non-fatal (cold-start / no memory dir).
|
|
961
|
+
// Layer-A summaries — same scopes AMS Resident will surface, loaded
|
|
962
|
+
// here so we can pass them into #prepareAms. (Rolling per-scope
|
|
963
|
+
// synopsis maintained by the dream tick.) Failures are non-fatal.
|
|
973
964
|
const summaries = await this.#loadLayerASummaries({
|
|
974
965
|
groupId,
|
|
975
966
|
vpId: vpPersona && typeof vpPersona === 'object' && typeof vpPersona.vpId === 'string'
|
|
@@ -979,12 +970,11 @@ export class Engine {
|
|
|
979
970
|
|
|
980
971
|
// ─── AMS: populate + snapshot ───────────────────────────────
|
|
981
972
|
// Group-keyed and persisted across session deactivation. Each turn:
|
|
982
|
-
// (a) resident layer is rebuilt from <scope>/summary.md
|
|
983
|
-
// summaries already loaded above are the same scopes, so
|
|
984
|
-
// reuse them);
|
|
973
|
+
// (a) resident layer is rebuilt from <scope>/summary.md;
|
|
985
974
|
// (b) onDemand is replaced with this turn's FTS hits;
|
|
986
|
-
// (c) we render a budget-aware snapshot block
|
|
987
|
-
//
|
|
975
|
+
// (c) we render a budget-aware snapshot block — this is the SOLE
|
|
976
|
+
// Memory section in the system prompt. Adjust runs post-turn
|
|
977
|
+
// (see end_turn below).
|
|
988
978
|
const ownVpIdForAms = vpPersona && typeof vpPersona === 'object'
|
|
989
979
|
&& typeof vpPersona.vpId === 'string'
|
|
990
980
|
? vpPersona.vpId
|
|
@@ -1000,15 +990,74 @@ export class Engine {
|
|
|
1000
990
|
recallEntries: recallResult ? (recallResult.entries || []) : [],
|
|
1001
991
|
});
|
|
1002
992
|
if (amsContext && amsContext.snapshotBlock) {
|
|
1003
|
-
memoryInjection =
|
|
1004
|
-
? memoryInjection + '\n\n' + amsContext.snapshotBlock
|
|
1005
|
-
: amsContext.snapshotBlock;
|
|
993
|
+
memoryInjection = amsContext.snapshotBlock;
|
|
1006
994
|
}
|
|
1007
995
|
|
|
1008
|
-
|
|
996
|
+
// ─── Active Scope (DESIGN-PROMPT §3 ④) ──────────────────────
|
|
997
|
+
// Structured per-turn scope summary: feature + group + vp + envelope
|
|
998
|
+
// routing info. Long-form scope content lives in AMS — this block
|
|
999
|
+
// carries only IDs + tiny labels. featureId is allowed to be null
|
|
1000
|
+
// (T4 Scope Tagging is a placeholder; not every turn lives in a
|
|
1001
|
+
// feature — DESIGN-PROMPT §5.1).
|
|
1002
|
+
const activeScope = {
|
|
1003
|
+
featureId: featureIdForAms || null,
|
|
1004
|
+
featureTitle: typeof inboundEnvelope === 'object' && inboundEnvelope
|
|
1005
|
+
&& typeof inboundEnvelope.featureTitle === 'string'
|
|
1006
|
+
? inboundEnvelope.featureTitle
|
|
1007
|
+
: '',
|
|
1008
|
+
groupId: groupId || '',
|
|
1009
|
+
vpId: ownVpIdForAms || '',
|
|
1010
|
+
envelope: inboundEnvelope || null,
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
const systemPrompt = this.#buildSystemPrompt({
|
|
1014
|
+
prompt,
|
|
1015
|
+
memoryInjection,
|
|
1016
|
+
vpPersona,
|
|
1017
|
+
activeScope,
|
|
1018
|
+
groupAnnouncement,
|
|
1019
|
+
// taskCtx is not currently wired by the query loop. The legacy
|
|
1020
|
+
// task-context sub-block (renderTaskCtx, task-334e/334n contract) is
|
|
1021
|
+
// retained behind a feature flag for callers that still build it
|
|
1022
|
+
// upstream — it'll be folded into Active Scope or retired in a
|
|
1023
|
+
// dedicated PR alongside the task-334n cleanup.
|
|
1024
|
+
taskCtx: undefined,
|
|
1025
|
+
});
|
|
1009
1026
|
|
|
1010
|
-
//
|
|
1027
|
+
// ─── Compact summary as messages-array head (DESIGN-PROMPT §4.3) ─
|
|
1028
|
+
// The previous code placed the compact summary inside the system
|
|
1029
|
+
// prompt; that broke prompt-cache hit-rate (any compact update
|
|
1030
|
+
// invalidated the entire system) and conflated identity/rules with
|
|
1031
|
+
// dialogue history. The compact summary is the product of compressing
|
|
1032
|
+
// older turns, so it belongs at the head of the messages array.
|
|
1033
|
+
//
|
|
1034
|
+
// Note: this is a separate mechanism from `history-compact.js`'s
|
|
1035
|
+
// `_compactSummary`-tagged user message. They never collide:
|
|
1036
|
+
// • THIS path injects a `<conversation_summary>` pair on every
|
|
1037
|
+
// query when conversationStore.readCompactSummary() returns text
|
|
1038
|
+
// (i.e. when a previous T1 run wrote one to disk). Engine reads,
|
|
1039
|
+
// does not produce.
|
|
1040
|
+
// • history-compact.js#compactHistory rewrites the in-memory
|
|
1041
|
+
// `messages` array, replacing cold messages with a single
|
|
1042
|
+
// `_compactSummary`-tagged user message. That path runs at a
|
|
1043
|
+
// different layer (web-bridge during a manual /compact) and never
|
|
1044
|
+
// touches `compactMessages` here.
|
|
1045
|
+
// The two would only overlap if a tagged `_compactSummary` user
|
|
1046
|
+
// message also matched the `<conversation_summary>` template — they
|
|
1047
|
+
// don't, so duplication is impossible by construction.
|
|
1048
|
+
const compactSummaryRaw = this.#getCompactSummary();
|
|
1049
|
+
const compactSummary = typeof compactSummaryRaw === 'string'
|
|
1050
|
+
? compactSummaryRaw.trim() : '';
|
|
1051
|
+
const compactMessages = compactSummary
|
|
1052
|
+
? [
|
|
1053
|
+
{ role: 'user', content: `<conversation_summary>\n${compactSummary}\n</conversation_summary>` },
|
|
1054
|
+
{ role: 'assistant', content: 'Acknowledged.' },
|
|
1055
|
+
]
|
|
1056
|
+
: [];
|
|
1057
|
+
|
|
1058
|
+
// Build conversation: optional compact head + existing messages + new user message
|
|
1011
1059
|
const conversationMessages = [
|
|
1060
|
+
...compactMessages,
|
|
1012
1061
|
...messages,
|
|
1013
1062
|
{ role: 'user', content: prompt },
|
|
1014
1063
|
];
|
package/unify/memory/budget.js
CHANGED
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* memory/budget.js — DESIGN-
|
|
2
|
+
* memory/budget.js — DESIGN-PROMPT §3 ③ Memory.
|
|
3
3
|
*
|
|
4
|
-
* Memory budget = `min(
|
|
4
|
+
* Memory budget = `min(100_000, modelMaxContext * 0.20)`.
|
|
5
5
|
*
|
|
6
6
|
* Then split across the three AMS layers (resident / recent / onDemand)
|
|
7
|
-
* with a configurable ratio. The defaults are tuned
|
|
8
|
-
*
|
|
7
|
+
* with a configurable ratio. The defaults are tuned per DESIGN-PROMPT §3
|
|
8
|
+
* (Resident gets the largest share because UserProfile + CoreMemory
|
|
9
|
+
* collapse into Resident now):
|
|
9
10
|
*
|
|
10
|
-
* resident
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* resident 60% → 24k of a 40k pool (Layer-A summaries +
|
|
12
|
+
* UserProfile + CoreMemory pinned)
|
|
13
|
+
* recent 15% → 6k (LRU of recently-used segments)
|
|
14
|
+
* onDemand 25% → 10k (this turn's FTS recall)
|
|
15
|
+
*
|
|
16
|
+
* Concrete budgets for common models:
|
|
17
|
+
* 200K context → 40K total (20% × 200K)
|
|
18
|
+
* 1M context → 100K total (capped)
|
|
19
|
+
* 128K context → 25.6K total
|
|
13
20
|
*
|
|
14
21
|
* Token counting here is approximate (chars / 4) — accurate enough for
|
|
15
22
|
* budget enforcement. The engine has a real tokenizer for prompt
|
|
16
23
|
* assembly; budget here is a guard rail, not the source of truth.
|
|
17
24
|
*/
|
|
18
25
|
|
|
19
|
-
export const ABSOLUTE_CAP =
|
|
20
|
-
export const MODEL_FRACTION = 0.
|
|
26
|
+
export const ABSOLUTE_CAP = 100_000;
|
|
27
|
+
export const MODEL_FRACTION = 0.20;
|
|
21
28
|
|
|
22
29
|
export const DEFAULT_RATIO = {
|
|
23
|
-
resident: 0.
|
|
24
|
-
recent: 0.
|
|
25
|
-
onDemand: 0.
|
|
30
|
+
resident: 0.60,
|
|
31
|
+
recent: 0.15,
|
|
32
|
+
onDemand: 0.25,
|
|
26
33
|
};
|
|
27
34
|
|
|
28
35
|
/**
|
package/unify/prompts.js
CHANGED
|
@@ -7,22 +7,24 @@
|
|
|
7
7
|
* Template files from agent/unify/templates/ are loaded once at startup
|
|
8
8
|
* and used to enrich the system prompt beyond the hardcoded fallbacks.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Concept layering (DESIGN-PROMPT §3):
|
|
11
|
+
* ① Identity — VP persona body (or Yeaft fallback)
|
|
12
|
+
* ② Rules — group announcement, date, mode template, tools,
|
|
13
|
+
* tool-guidance, skills, common rules
|
|
14
|
+
* ③ Memory — single block produced upstream by the AMS render
|
|
15
|
+
* outlet and threaded through here as `memoryInjection`
|
|
16
|
+
* ④ Active Scope — structured per-turn scope summary
|
|
17
|
+
* (feature / group / vp / envelope IDs)
|
|
13
18
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* Reference: yeaft-unify-system-prompt-budget.md — Static + Dynamic + Context layers
|
|
19
|
+
* The compact summary, user_profile, and core_memory blocks that used to
|
|
20
|
+
* live inside the system prompt are GONE. Compact summary is now part of
|
|
21
|
+
* the messages timeline; user_profile + core_memory have been folded into
|
|
22
|
+
* AMS Resident.
|
|
20
23
|
*/
|
|
21
24
|
|
|
22
25
|
import { readFileSync, existsSync } from 'fs';
|
|
23
26
|
import { join, dirname } from 'path';
|
|
24
27
|
import { fileURLToPath } from 'url';
|
|
25
|
-
import { homedir } from 'os';
|
|
26
28
|
|
|
27
29
|
// ─── Template Loading (one-time at startup) ──────────────────────
|
|
28
30
|
|
|
@@ -169,18 +171,13 @@ const PROMPTS = {
|
|
|
169
171
|
date: (d) => `Date: ${d}`,
|
|
170
172
|
dream: 'You are in dream mode. Reflect on past conversations and consolidate memories.',
|
|
171
173
|
tools: (names) => `Available tools: ${names}`,
|
|
172
|
-
|
|
173
|
-
profileHeader: '### User Profile',
|
|
174
|
-
recalledHeader: '### Recalled Memories',
|
|
175
|
-
compactHeader: '## Conversation History Summary',
|
|
176
|
-
// task-334e — new section headers
|
|
174
|
+
// task-334e — task-context section header (sub-block of Active Scope)
|
|
177
175
|
taskCtxHeader: '## task_ctx',
|
|
178
176
|
taskCtxRelatedHeader: '### related tasks',
|
|
179
177
|
taskCtxSummaryReminder: (min, count) =>
|
|
180
178
|
`💡 ${min}min since last summary (+${count} new messages). Consider calling \`task_summary_post\`.`,
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
coreMemoryMeta: 'To open the original message behind any entry above, call `memory_trace`.',
|
|
179
|
+
// DESIGN-PROMPT §3 ④ — Active Scope header
|
|
180
|
+
activeScopeHeader: '## active_scope',
|
|
184
181
|
vpPersonaIntro: (name, role) =>
|
|
185
182
|
`You ARE **${name}**${role ? ` (${role})` : ''}. Speak in the first person as ${name}; do not refer to yourself as "Yeaft" or as a generic AI assistant. The text below is your identity, expertise, and decision style.`,
|
|
186
183
|
},
|
|
@@ -189,18 +186,13 @@ const PROMPTS = {
|
|
|
189
186
|
date: (d) => `日期:${d}`,
|
|
190
187
|
dream: '你处于梦境模式。回顾过去的对话,整理和巩固记忆。',
|
|
191
188
|
tools: (names) => `可用工具:${names}`,
|
|
192
|
-
|
|
193
|
-
profileHeader: '### 用户画像',
|
|
194
|
-
recalledHeader: '### 相关记忆',
|
|
195
|
-
compactHeader: '## 对话历史摘要',
|
|
196
|
-
// task-334e — new section headers
|
|
189
|
+
// task-334e — task-context section header (sub-block of Active Scope)
|
|
197
190
|
taskCtxHeader: '## task_ctx',
|
|
198
191
|
taskCtxRelatedHeader: '### 相关任务',
|
|
199
192
|
taskCtxSummaryReminder: (min, count) =>
|
|
200
193
|
`💡 距上次 summary 已过 ${min}min,新增 ${count} 条消息,建议调用 \`task_summary_post\`。`,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
coreMemoryMeta: '如需原始 message,调 `memory_trace`。',
|
|
194
|
+
// DESIGN-PROMPT §3 ④ — Active Scope header
|
|
195
|
+
activeScopeHeader: '## active_scope',
|
|
204
196
|
vpPersonaIntro: (name, role) =>
|
|
205
197
|
`你就是 **${name}**${role ? `(${role})` : ''}。请以 ${name} 的第一人称发言;不要自称 "Yeaft" 或泛指的 AI 助手。下面的文字是你的身份、专业方向与判断风格。`,
|
|
206
198
|
},
|
|
@@ -217,19 +209,18 @@ export const SUPPORTED_LANGUAGES = Object.keys(PROMPTS);
|
|
|
217
209
|
* — only `mode === 'dream'` triggers the dream-mode template (used by background
|
|
218
210
|
* memory maintenance); all other values fall through to unified mode.
|
|
219
211
|
*
|
|
220
|
-
* Prompt structure:
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* 10. Core memory section (task-334e §Δ24.5)
|
|
212
|
+
* Prompt structure (DESIGN-PROMPT §3):
|
|
213
|
+
* ① Identity — Core identity (persona or Yeaft fallback)
|
|
214
|
+
* ② Rules — Group announcement, date, mode, tools, guidance, skills
|
|
215
|
+
* ③ Memory — Single block produced by the AMS render outlet
|
|
216
|
+
* (callers pass it as `memoryInjection`).
|
|
217
|
+
* ④ Active Scope — Structured per-turn scope summary
|
|
218
|
+
* (feature / group / vp / envelope IDs).
|
|
219
|
+
* (Task context lives inside Active Scope; the previous standalone
|
|
220
|
+
* user_profile / core_memory blocks are gone — those signals now
|
|
221
|
+
* arrive through AMS Resident.)
|
|
231
222
|
*
|
|
232
|
-
* task-334e
|
|
223
|
+
* task-334e taskCtx is preserved as a sub-block of Active Scope:
|
|
233
224
|
* @param {object} [taskCtx] — per-task context
|
|
234
225
|
* @param {string} [taskCtx.taskId]
|
|
235
226
|
* @param {string} [taskCtx.currentVpId] — used for ACL + initiator check
|
|
@@ -244,29 +235,24 @@ export const SUPPORTED_LANGUAGES = Object.keys(PROMPTS);
|
|
|
244
235
|
* @param {number} [taskCtx.summaryReminder.lastSummaryAt] — epoch ms (0/missing = never)
|
|
245
236
|
* @param {number} [taskCtx.summaryReminder.now] — override clock (tests), default Date.now()
|
|
246
237
|
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* @param {
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* @param {
|
|
253
|
-
*
|
|
254
|
-
* false so we don't point VPs at an unimplemented tool (prev-3 Nit-2 /
|
|
255
|
-
* PM-approved Option A). 334f will flip this to `true` from session.js
|
|
256
|
-
* once `memory_trace` ships; this slice stays decoupled from session.js.
|
|
238
|
+
* Active Scope params (DESIGN-PROMPT §3 ④):
|
|
239
|
+
* @param {object} [activeScope] — structured scope summary for this turn
|
|
240
|
+
* @param {string|null} [activeScope.featureId] currently active feature, or null
|
|
241
|
+
* @param {string} [activeScope.featureTitle] short title for human display
|
|
242
|
+
* @param {string} [activeScope.groupId]
|
|
243
|
+
* @param {string} [activeScope.vpId]
|
|
244
|
+
* @param {object} [activeScope.envelope] inbound routing info (sender, intent)
|
|
257
245
|
*
|
|
258
246
|
* @param {{
|
|
259
247
|
* language?: string,
|
|
260
248
|
* mode?: string,
|
|
261
249
|
* toolNames?: string[],
|
|
262
|
-
* memory?: { profile?: string, entries?: object[] },
|
|
263
250
|
* memoryInjection?: string,
|
|
264
|
-
* compactSummary?: string,
|
|
265
251
|
* skillContent?: string,
|
|
266
252
|
* taskCtx?: object,
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
253
|
+
* activeScope?: object,
|
|
254
|
+
* vpPersona?: object,
|
|
255
|
+
* groupAnnouncement?: string,
|
|
270
256
|
* }} params
|
|
271
257
|
* @returns {string}
|
|
272
258
|
*/
|
|
@@ -274,14 +260,10 @@ export function buildSystemPrompt({
|
|
|
274
260
|
language = 'en',
|
|
275
261
|
mode,
|
|
276
262
|
toolNames = [],
|
|
277
|
-
memory,
|
|
278
263
|
memoryInjection,
|
|
279
|
-
compactSummary,
|
|
280
264
|
skillContent,
|
|
281
265
|
taskCtx,
|
|
282
|
-
|
|
283
|
-
coreMemory,
|
|
284
|
-
memoryTraceAvailable = false,
|
|
266
|
+
activeScope,
|
|
285
267
|
vpPersona,
|
|
286
268
|
groupAnnouncement = '',
|
|
287
269
|
} = {}) {
|
|
@@ -351,31 +333,29 @@ export function buildSystemPrompt({
|
|
|
351
333
|
parts.push(skillContent);
|
|
352
334
|
}
|
|
353
335
|
|
|
354
|
-
// ─── 6. Memory Section
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
336
|
+
// ─── 6. Memory Section (DESIGN-PROMPT §3 ③) ────────────
|
|
337
|
+
// The Memory section has a SINGLE render outlet. Callers compose the
|
|
338
|
+
// block upstream by rendering the AMS snapshot (Resident + Recent +
|
|
339
|
+
// OnDemand) and passing the result here as `memoryInjection`. The
|
|
340
|
+
// legacy multi-path injection (FTS-formatted + AMS snapshot +
|
|
341
|
+
// renderLayerASummaries + renderUserProfile + renderCoreMemory) was
|
|
342
|
+
// retired in DESIGN-PROMPT v1: it produced 2-3× duplicated content
|
|
343
|
+
// for the same `summary.md` payload.
|
|
358
344
|
if (memoryInjection && memoryInjection.trim()) {
|
|
359
345
|
parts.push(memoryInjection.trim());
|
|
360
346
|
}
|
|
361
347
|
|
|
362
|
-
// ─── 7.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
348
|
+
// ─── 7. Active Scope (DESIGN-PROMPT §3 ④) ──────────────
|
|
349
|
+
// Structured per-turn scope summary. taskCtx is rendered as a
|
|
350
|
+
// sub-block of Active Scope (when supplied), and the new
|
|
351
|
+
// feature/group/vp/envelope identifiers are rendered as a leading
|
|
352
|
+
// line.
|
|
353
|
+
const activeScopeBlock = renderActiveScope(activeScope, lang);
|
|
354
|
+
if (activeScopeBlock) parts.push(activeScopeBlock);
|
|
366
355
|
|
|
367
|
-
// ─── 8. Task Context Section (task-334e §Δ24.5 / §Δ27.3 / §Δ31.4) ─
|
|
368
356
|
const taskCtxBlock = renderTaskCtx(taskCtx, lang);
|
|
369
357
|
if (taskCtxBlock) parts.push(taskCtxBlock);
|
|
370
358
|
|
|
371
|
-
// ─── 9. User Profile Section (task-334e §Δ29.3 stub) ───
|
|
372
|
-
const profileBlock = renderUserProfile(userProfile, lang);
|
|
373
|
-
if (profileBlock) parts.push(profileBlock);
|
|
374
|
-
|
|
375
|
-
// ─── 10. Core Memory Section (task-334e §Δ24.5) ────────
|
|
376
|
-
const coreMemBlock = renderCoreMemory(coreMemory, lang, memoryTraceAvailable);
|
|
377
|
-
if (coreMemBlock) parts.push(coreMemBlock);
|
|
378
|
-
|
|
379
359
|
return parts.join('\n\n');
|
|
380
360
|
}
|
|
381
361
|
|
|
@@ -418,7 +398,6 @@ function renderVpPersona(vpPersona, lang) {
|
|
|
418
398
|
const DEFAULT_TASK_MEMORY_TOP = 5;
|
|
419
399
|
const DEFAULT_RELATED_TASK_TOP = 3;
|
|
420
400
|
const DEFAULT_RELATED_TASK_MEMORY_TOP = 2;
|
|
421
|
-
const DEFAULT_CORE_MEMORY_TOP = 7;
|
|
422
401
|
// task-334n §Δ31.4 — tightened reminder gate:
|
|
423
402
|
// (a) currentVpId === initiatorVpId
|
|
424
403
|
// (b) task.members.length >= 2 (multi-VP only)
|
|
@@ -559,94 +538,114 @@ function renderSummaryReminder(taskCtx, lang) {
|
|
|
559
538
|
}
|
|
560
539
|
|
|
561
540
|
/**
|
|
562
|
-
* Render `##
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
541
|
+
* Render `## active_scope` block (DESIGN-PROMPT §3 ④).
|
|
542
|
+
*
|
|
543
|
+
* Active Scope is a structured, deterministic, bounded block telling the
|
|
544
|
+
* LLM what scope the current turn lives in. It is NOT memory; long-form
|
|
545
|
+
* scope content (decisions, history) flows through AMS — Active Scope
|
|
546
|
+
* carries only IDs + tiny labels.
|
|
547
|
+
*
|
|
548
|
+
* Schema:
|
|
549
|
+
* ## active_scope
|
|
550
|
+
* feature: <featureId> "<title>" (omitted when null/empty)
|
|
551
|
+
* group: <groupId> (omitted when missing)
|
|
552
|
+
* vp: <vpId> (omitted when missing)
|
|
553
|
+
* envelope: from=<sender> intent=<intent> (omitted when no envelope)
|
|
554
|
+
*
|
|
555
|
+
* Returns '' when the input has no useful field — we don't emit an empty
|
|
556
|
+
* header. featureId is allowed to be `null` (DESIGN-PROMPT §5.1 — T4
|
|
557
|
+
* Scope Tagging is a placeholder; not every turn lives in a feature).
|
|
558
|
+
*
|
|
559
|
+
* @param {object} [activeScope]
|
|
560
|
+
* @param {string|null} [activeScope.featureId]
|
|
561
|
+
* @param {string} [activeScope.featureTitle]
|
|
562
|
+
* @param {string} [activeScope.groupId]
|
|
563
|
+
* @param {string} [activeScope.vpId]
|
|
564
|
+
* @param {object} [activeScope.envelope] inbound routing summary
|
|
565
|
+
* @param {object} lang
|
|
566
|
+
* @returns {string}
|
|
566
567
|
*/
|
|
567
|
-
function
|
|
568
|
-
|
|
569
|
-
if (typeof userProfile === 'string' && userProfile.trim()) {
|
|
570
|
-
content = userProfile.trim();
|
|
571
|
-
} else if (userProfile == null) {
|
|
572
|
-
content = readUserProfileStub();
|
|
573
|
-
}
|
|
574
|
-
if (!content) return '';
|
|
575
|
-
return `${lang.userProfileHeader}\n${content}`;
|
|
576
|
-
}
|
|
568
|
+
function renderActiveScope(activeScope, lang) {
|
|
569
|
+
if (!activeScope || typeof activeScope !== 'object') return '';
|
|
577
570
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
571
|
+
const lines = [];
|
|
572
|
+
const feature = typeof activeScope.featureId === 'string' && activeScope.featureId.trim()
|
|
573
|
+
? activeScope.featureId.trim()
|
|
574
|
+
: null;
|
|
575
|
+
if (feature) {
|
|
576
|
+
// Escape embedded `"` in featureTitle so a title like `Onboard "v2"` does
|
|
577
|
+
// not produce a malformed `feature: f1 "Onboard "v2""` line. Titles come
|
|
578
|
+
// from user / agent input — assume nothing.
|
|
579
|
+
const title = typeof activeScope.featureTitle === 'string' && activeScope.featureTitle.trim()
|
|
580
|
+
? ` "${activeScope.featureTitle.trim().replace(/"/g, '\\"')}"`
|
|
581
|
+
: '';
|
|
582
|
+
lines.push(`feature: ${feature}${title}`);
|
|
590
583
|
}
|
|
584
|
+
const group = typeof activeScope.groupId === 'string' && activeScope.groupId.trim()
|
|
585
|
+
? activeScope.groupId.trim()
|
|
586
|
+
: '';
|
|
587
|
+
if (group) lines.push(`group: ${group}`);
|
|
588
|
+
|
|
589
|
+
const vp = typeof activeScope.vpId === 'string' && activeScope.vpId.trim()
|
|
590
|
+
? activeScope.vpId.trim()
|
|
591
|
+
: '';
|
|
592
|
+
if (vp) lines.push(`vp: ${vp}`);
|
|
593
|
+
|
|
594
|
+
const envLine = renderEnvelopeLine(activeScope.envelope);
|
|
595
|
+
if (envLine) lines.push(`envelope: ${envLine}`);
|
|
596
|
+
|
|
597
|
+
if (lines.length === 0) return '';
|
|
598
|
+
|
|
599
|
+
return `${lang.activeScopeHeader}\n${lines.join('\n')}`;
|
|
591
600
|
}
|
|
592
601
|
|
|
593
602
|
/**
|
|
594
|
-
* Render
|
|
603
|
+
* Render a one-line envelope summary. Pulls the small set of routing
|
|
604
|
+
* fields we surface to the LLM (sender, intent, originating user) and
|
|
605
|
+
* leaves the rest in AMS. Returns '' when the envelope carries no
|
|
606
|
+
* useful signal.
|
|
595
607
|
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
* `memoryTraceAvailable` (prev-3 Nit-2 / PM-approved Option A): when the
|
|
599
|
-
* `memory_trace` tool is not yet implemented (334f), we omit the meta line
|
|
600
|
-
* entirely so the LLM does not try to call a non-existent tool. 334f will
|
|
601
|
-
* flip the flag to `true` when it wires session.js.
|
|
608
|
+
* @param {object|null|undefined} envelope
|
|
609
|
+
* @returns {string}
|
|
602
610
|
*/
|
|
603
|
-
function
|
|
604
|
-
if (!
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
?
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
shown += 1;
|
|
620
|
-
}
|
|
621
|
-
if (shown === 0) return '';
|
|
622
|
-
if (memoryTraceAvailable) {
|
|
623
|
-
lines.push('');
|
|
624
|
-
lines.push(lang.coreMemoryMeta);
|
|
625
|
-
}
|
|
626
|
-
return lines.join('\n');
|
|
611
|
+
function renderEnvelopeLine(envelope) {
|
|
612
|
+
if (!envelope || typeof envelope !== 'object') return '';
|
|
613
|
+
const segments = [];
|
|
614
|
+
const fromVp = typeof envelope.fromVpId === 'string' && envelope.fromVpId.trim()
|
|
615
|
+
? envelope.fromVpId.trim()
|
|
616
|
+
: (typeof envelope.senderVpId === 'string' ? envelope.senderVpId.trim() : '');
|
|
617
|
+
if (fromVp) segments.push(`from=${fromVp}`);
|
|
618
|
+
const fromUser = typeof envelope.fromUserId === 'string' && envelope.fromUserId.trim()
|
|
619
|
+
? envelope.fromUserId.trim()
|
|
620
|
+
: '';
|
|
621
|
+
if (fromUser) segments.push(`user=${fromUser}`);
|
|
622
|
+
const intent = typeof envelope.intent === 'string' && envelope.intent.trim()
|
|
623
|
+
? envelope.intent.trim()
|
|
624
|
+
: '';
|
|
625
|
+
if (intent) segments.push(`intent=${intent}`);
|
|
626
|
+
return segments.join(' ');
|
|
627
627
|
}
|
|
628
628
|
|
|
629
629
|
// ─── Phase 1: Worker / Router prompt splits ──────────────────────
|
|
630
630
|
//
|
|
631
631
|
// DESIGN.md (multi-VP redesign) describes two distinct prompt shapes:
|
|
632
632
|
//
|
|
633
|
-
// • Worker prompt — what a VP sees when it executes a turn.
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
//
|
|
637
|
-
//
|
|
638
|
-
//
|
|
633
|
+
// • Worker prompt — what a VP sees when it executes a turn. The
|
|
634
|
+
// DESIGN-PROMPT v1 refactor collapsed the previous A/B/C/D layered
|
|
635
|
+
// shape into a single AMS-driven Memory block: AMS Resident now
|
|
636
|
+
// carries Layer-A summaries + UserProfile + CoreMemory, AMS OnDemand
|
|
637
|
+
// carries the per-turn FTS hits. The worker shape that survives is:
|
|
638
|
+
// harness/worker-shape — optional descriptive metadata
|
|
639
|
+
// buildSystemPrompt(...) — ① Identity ② Rules ③ Memory ④ Active Scope
|
|
640
|
+
// optional taskScope/turnScope — caller-provided pass-through strings
|
|
641
|
+
// `renderLayerASummaries` is no longer called inside the worker prompt
|
|
642
|
+
// because AMS already renders the same summaries — calling both was
|
|
643
|
+
// the duplicate-render bug DESIGN-PROMPT §6.1 #2 set out to fix.
|
|
639
644
|
//
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
// headed sections, and
|
|
645
|
-
// 2) prepend the matching harness/*-shape.md fragment when present.
|
|
646
|
-
//
|
|
647
|
-
// Subsequent phases will migrate engine.js / router.js to these entry
|
|
648
|
-
// points and start filling Layers B / C with the new memory tree. For
|
|
649
|
-
// now they exist primarily so tests can pin the contract.
|
|
645
|
+
// • Router prompt — what the per-VP Router sees before it decides
|
|
646
|
+
// plans[]. This is a separate, smaller LLM call that does not run
|
|
647
|
+
// AMS, so it still uses `renderLayerASummaries` directly to surface
|
|
648
|
+
// the three Layer-A summaries inline.
|
|
650
649
|
|
|
651
650
|
const LAYER_A_HEADERS = {
|
|
652
651
|
en: {
|
|
@@ -663,8 +662,11 @@ const LAYER_A_HEADERS = {
|
|
|
663
662
|
|
|
664
663
|
/**
|
|
665
664
|
* Render Layer A's three rolling summaries (user / group / vp). Each is
|
|
666
|
-
* optional; missing or empty strings are skipped.
|
|
667
|
-
*
|
|
665
|
+
* optional; missing or empty strings are skipped.
|
|
666
|
+
*
|
|
667
|
+
* Used by the Router prompt path only — the Worker prompt path receives
|
|
668
|
+
* the same summaries through AMS Resident (see DESIGN-PROMPT §3 ③) and
|
|
669
|
+
* MUST NOT call this in addition.
|
|
668
670
|
*
|
|
669
671
|
* @param {{user?: string, group?: string, vp?: string}} summaries
|
|
670
672
|
* @param {'en'|'zh'} language
|
|
@@ -683,26 +685,22 @@ export function renderLayerASummaries(summaries, language = 'en') {
|
|
|
683
685
|
}
|
|
684
686
|
|
|
685
687
|
/**
|
|
686
|
-
* Worker prompt entry point
|
|
688
|
+
* Worker prompt entry point.
|
|
687
689
|
*
|
|
688
|
-
*
|
|
689
|
-
* harness/worker-shape —
|
|
690
|
-
*
|
|
691
|
-
* summaries via `summaries`)
|
|
692
|
-
* Layer B — `preselectedMemory` block (router-supplied)
|
|
693
|
-
* Layer C — `taskScope` block (active task summary + related-task window)
|
|
694
|
-
* Layer D — `turnScope` block (inbound envelope, in-flight turn notes)
|
|
690
|
+
* Output sections (DESIGN-PROMPT §3 layered concepts):
|
|
691
|
+
* harness/worker-shape (optional) — descriptive metadata
|
|
692
|
+
* buildSystemPrompt(...) — ① Identity ② Rules ③ Memory ④ Active Scope
|
|
695
693
|
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
694
|
+
* Earlier task-322 / task-334e variants accepted `taskScope` and
|
|
695
|
+
* `turnScope` pass-through strings so callers could append their own
|
|
696
|
+
* scope blocks. DESIGN-PROMPT v1 retired that surface — Active Scope is
|
|
697
|
+
* now structured (`activeScope: { featureId, groupId, vpId, envelope }`)
|
|
698
|
+
* and rendered by `buildSystemPrompt` itself. Both pass-through params
|
|
699
|
+
* had zero remaining callers when v1 landed; removing them prevents the
|
|
700
|
+
* "two ways to describe scope" drift §1 set out to eliminate.
|
|
699
701
|
*
|
|
700
702
|
* @param {{
|
|
701
703
|
* language?: 'en'|'zh',
|
|
702
|
-
* summaries?: {user?: string, group?: string, vp?: string},
|
|
703
|
-
* preselectedMemory?: string,
|
|
704
|
-
* taskScope?: string,
|
|
705
|
-
* turnScope?: string,
|
|
706
704
|
* includeShape?: boolean,
|
|
707
705
|
* ...rest: import('./prompts.js').buildSystemPrompt
|
|
708
706
|
* }} params
|
|
@@ -711,10 +709,6 @@ export function renderLayerASummaries(summaries, language = 'en') {
|
|
|
711
709
|
export function buildWorkerPrompt(params = {}) {
|
|
712
710
|
const {
|
|
713
711
|
language = 'en',
|
|
714
|
-
summaries,
|
|
715
|
-
preselectedMemory,
|
|
716
|
-
taskScope,
|
|
717
|
-
turnScope,
|
|
718
712
|
includeShape = true,
|
|
719
713
|
...rest
|
|
720
714
|
} = params;
|
|
@@ -727,26 +721,9 @@ export function buildWorkerPrompt(params = {}) {
|
|
|
727
721
|
if (shape) parts.push(shape);
|
|
728
722
|
}
|
|
729
723
|
|
|
730
|
-
//
|
|
724
|
+
// Identity + Rules + Memory + Active Scope (DESIGN-PROMPT §3).
|
|
731
725
|
const baseBlock = buildSystemPrompt({ ...rest, language });
|
|
732
726
|
if (baseBlock) parts.push(baseBlock);
|
|
733
|
-
const summaryBlock = renderLayerASummaries(summaries, language);
|
|
734
|
-
if (summaryBlock) parts.push(summaryBlock);
|
|
735
|
-
|
|
736
|
-
// Layer B — router-preselected memory entries (rendered upstream).
|
|
737
|
-
if (typeof preselectedMemory === 'string' && preselectedMemory.trim()) {
|
|
738
|
-
parts.push(preselectedMemory.trim());
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// Layer C — task scope.
|
|
742
|
-
if (typeof taskScope === 'string' && taskScope.trim()) {
|
|
743
|
-
parts.push(taskScope.trim());
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Layer D — turn scope (inbound envelope, in-flight turn notes).
|
|
747
|
-
if (typeof turnScope === 'string' && turnScope.trim()) {
|
|
748
|
-
parts.push(turnScope.trim());
|
|
749
|
-
}
|
|
750
727
|
|
|
751
728
|
return parts.join('\n\n');
|
|
752
729
|
}
|