@yeaft/webchat-agent 0.1.521 → 0.1.523

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.
@@ -0,0 +1,191 @@
1
+ /**
2
+ * system-prompt.js — assemble a VP's system prompt per architecture §8.
3
+ *
4
+ * Three sections:
5
+ * § STATIC
6
+ * - identity (yeaft base — short)
7
+ * - vp_persona (role.md persona body)
8
+ * - capabilities (tools) — 334d owns the real list; this slice leaves
9
+ * a stub that names the MVP tool surface ("route_forward" ...).
10
+ *
11
+ * § SEMI-DYNAMIC
12
+ * - group_roster (name + on-line status via Registry.activeCount)
13
+ * - (skills / mcp / user_profile — owned by other slices; 334c emits
14
+ * placeholder only if the caller provides them)
15
+ *
16
+ * § DYNAMIC
17
+ * - runtime_ctx { vpId, groupId, taskId?, isDream=false }
18
+ * - core_memory (recall top-K; vp single-dim per R3 §Δ2.3)
19
+ *
20
+ * Caching: the STATIC persona section is cached per RoleInstance via
21
+ * `ri._promptBuiltForMtime === vp.mtimeMs`. DYNAMIC is always rebuilt.
22
+ *
23
+ * Hard constraint (334c): this module does not touch 334f/334g memory
24
+ * internals. It calls `memoryStore.query({vp})` (duck-typed) and falls
25
+ * back to an empty top-K if the store is null.
26
+ */
27
+
28
+ import { recallCoreMemory } from './core-memory-recall.js';
29
+
30
+ const CORE_MEMORY_TOP_K = 7; // §8
31
+ const ROSTER_STATUS_ONLINE = 'online';
32
+ const ROSTER_STATUS_BUSY = 'busy';
33
+
34
+ /**
35
+ * Build one complete system prompt string.
36
+ *
37
+ * @param {import('./role-instance.js').RoleInstance} ri
38
+ * @param {{
39
+ * registry?: import('./registry.js').Registry,
40
+ * rosterMembers?: string[], // explicit override (334b GroupHandle.roster)
41
+ * runtimeCtx?: { taskId?: string|null, isDream?: boolean, recentChatSummary?: string },
42
+ * capabilitiesLine?: string, // 334d injects tool inventory
43
+ * userProfile?: string, // 334l injects top-5 user-memory
44
+ * recentGroupChat?: string, // 334h injects N recent msgs
45
+ * }} opts
46
+ * @returns {string}
47
+ */
48
+ export async function buildSystemPrompt(ri, opts = {}) {
49
+ if (!ri || !ri.vp) throw new Error('buildSystemPrompt: role instance required');
50
+ const vp = ri.vp;
51
+
52
+ // ─── § STATIC (cached per mtime) ────────────────────────────
53
+ let staticBlock = ri.systemPrompt;
54
+ if (!staticBlock || ri._promptBuiltForMtime !== vp.mtimeMs) {
55
+ staticBlock = buildStatic(vp, opts.capabilitiesLine);
56
+ ri.systemPrompt = staticBlock;
57
+ ri._promptBuiltForMtime = vp.mtimeMs;
58
+ }
59
+
60
+ // ─── § SEMI-DYNAMIC ─────────────────────────────────────────
61
+ const roster = buildRoster(ri, opts.registry, opts.rosterMembers);
62
+ const userProfile = opts.userProfile ? `\n## user_profile\n${opts.userProfile.trim()}\n` : '';
63
+
64
+ // ─── § DYNAMIC ──────────────────────────────────────────────
65
+ const ctx = opts.runtimeCtx || {};
66
+ const runtime = buildRuntimeCtx(ri, ctx);
67
+ const recent = opts.recentGroupChat
68
+ ? `\n## recent_group_chat\n${opts.recentGroupChat.trim()}\n`
69
+ : '';
70
+ const coreMem = await buildCoreMemoryBlock(ri, ctx);
71
+
72
+ return [
73
+ '# § STATIC',
74
+ staticBlock,
75
+ '',
76
+ '# § SEMI-DYNAMIC',
77
+ roster,
78
+ userProfile.trim() ? userProfile : '',
79
+ '',
80
+ '# § DYNAMIC',
81
+ runtime,
82
+ recent.trim() ? recent : '',
83
+ coreMem,
84
+ ]
85
+ .filter(Boolean)
86
+ .join('\n')
87
+ .replace(/\n{3,}/g, '\n\n')
88
+ .trim();
89
+ }
90
+
91
+ // ──────────────────────────────────────────────────────────────
92
+ // STATIC
93
+ // ──────────────────────────────────────────────────────────────
94
+
95
+ function buildStatic(vp, capabilitiesLine) {
96
+ const identity =
97
+ 'You are a Virtual Person (VP) in a yeaft multi-agent group.\n' +
98
+ 'Respect §6 triggers: text @-mentions do NOT route. Use the `route_forward` tool for explicit dispatch.';
99
+
100
+ const persona = vp.persona && vp.persona.trim()
101
+ ? vp.persona.trim()
102
+ : `(no persona body for ${vp.id})`;
103
+
104
+ const caps = (capabilitiesLine && capabilitiesLine.trim())
105
+ || 'Tools: route_forward, memory_search, memory_trace, task_summary_post (if initiator).';
106
+
107
+ // personaHash travels in the static block so downstream (334h live-diff)
108
+ // can detect changes without re-hashing.
109
+ return [
110
+ '## identity',
111
+ identity,
112
+ '',
113
+ `## vp_persona (id=${vp.id}, hash=${vp.personaHash || '-'})`,
114
+ `Name: ${vp.name}`,
115
+ vp.role ? `Role: ${vp.role}` : '',
116
+ vp.traits && vp.traits.length ? `Traits: ${vp.traits.join(', ')}` : '',
117
+ '',
118
+ persona,
119
+ '',
120
+ '## capabilities',
121
+ caps,
122
+ ].filter(Boolean).join('\n');
123
+ }
124
+
125
+ // ──────────────────────────────────────────────────────────────
126
+ // SEMI-DYNAMIC — Roster
127
+ // ──────────────────────────────────────────────────────────────
128
+
129
+ function buildRoster(ri, registry, rosterMembers) {
130
+ const members = Array.isArray(rosterMembers) && rosterMembers.length > 0
131
+ ? rosterMembers
132
+ : registry
133
+ ? Array.from(new Set(registry.listRoleInstances()
134
+ .filter((r) => r.groupId === ri.groupId)
135
+ .map((r) => r.vpId)))
136
+ : [ri.vpId];
137
+
138
+ const lines = [`## 群成员 (${members.length})`];
139
+ for (const vpId of members) {
140
+ if (vpId === ri.vpId) {
141
+ lines.push(`- 你自己:${vpId}`);
142
+ continue;
143
+ }
144
+ const status = memberStatus(vpId, registry);
145
+ const name = registry?.getVp?.(vpId)?.name || vpId;
146
+ lines.push(`- ${name} (${vpId}) · ${status}`);
147
+ }
148
+ return lines.join('\n');
149
+ }
150
+
151
+ function memberStatus(vpId, registry) {
152
+ if (!registry) return ROSTER_STATUS_ONLINE;
153
+ // §8.2: busy if any RoleInstance for this VP has state==='running' in any group.
154
+ // MVP: also 'running' while 'queued'? — spec says "running RoleInstance 数 > 0".
155
+ const ris = registry.listRoleInstances?.() || [];
156
+ const busy = ris.some((r) => r.vpId === vpId && r.state === 'running');
157
+ return busy ? ROSTER_STATUS_BUSY : ROSTER_STATUS_ONLINE;
158
+ }
159
+
160
+ // ──────────────────────────────────────────────────────────────
161
+ // DYNAMIC
162
+ // ──────────────────────────────────────────────────────────────
163
+
164
+ function buildRuntimeCtx(ri, ctx) {
165
+ return [
166
+ '## runtime_ctx',
167
+ `vpId: ${ri.vpId}`,
168
+ `groupId: ${ri.groupId}`,
169
+ ctx.taskId ? `taskId: ${ctx.taskId}` : null,
170
+ `isDream: ${Boolean(ctx.isDream)}`,
171
+ ].filter(Boolean).join('\n');
172
+ }
173
+
174
+ async function buildCoreMemoryBlock(ri, ctx) {
175
+ if (!ri.memoryStore) return '';
176
+ const entries = await recallCoreMemory(ri.memoryStore, {
177
+ vp: ri.vpId,
178
+ limit: CORE_MEMORY_TOP_K,
179
+ });
180
+ if (!entries || entries.length === 0) return '';
181
+ void ctx; // task_ctx injection is 334n's scope; reserved param.
182
+ const lines = ['## core_memory'];
183
+ for (const e of entries) {
184
+ const shard = e.shard || 'general';
185
+ const body = (e.body || '').trim();
186
+ if (!body) continue;
187
+ lines.push(`- [mem:${shard}] ${body}`);
188
+ }
189
+ if (lines.length === 1) return '';
190
+ return lines.join('\n');
191
+ }