@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.
- package/package.json +1 -1
- package/unify/groups/coordinator.js +206 -0
- package/unify/groups/feature-flag.js +49 -0
- package/unify/groups/group-store.js +182 -0
- package/unify/groups/ids.js +71 -0
- package/unify/groups/index.js +47 -0
- package/unify/groups/roster.js +60 -0
- package/unify/groups/seed-default.js +47 -0
- package/unify/vp/core-memory-recall.js +75 -0
- package/unify/vp/engine-binding.js +96 -0
- package/unify/vp/index.js +6 -0
- package/unify/vp/registry.js +5 -0
- package/unify/vp/role-instance.js +143 -14
- package/unify/vp/run-turn.js +197 -0
- package/unify/vp/system-prompt.js +191 -0
|
@@ -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
|
+
}
|