@yeaft/webchat-agent 0.1.903 → 0.1.905
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/connection/message-router.js +10 -9
- package/package.json +1 -1
- package/yeaft/compact/compactor.js +2 -2
- package/yeaft/config-api.js +16 -6
- package/yeaft/config.js +15 -2
- package/yeaft/conversation/persist.js +173 -60
- package/yeaft/engine.js +3 -3
- package/yeaft/llm/router.js +1 -1
- package/yeaft/memory/ams-registry.js +18 -19
- package/yeaft/pair-sanitize.js +1 -1
- package/yeaft/session.js +10 -1
- package/yeaft/snapshot-filter.js +88 -0
- package/yeaft/web-bridge.js +169 -140
package/yeaft/session.js
CHANGED
|
@@ -17,7 +17,7 @@ import { initYeaftDir, DEFAULT_YEAFT_DIR, isWritable } from './init.js';
|
|
|
17
17
|
import { loadConfig, loadMCPConfig } from './config.js';
|
|
18
18
|
import { createTrace } from './debug-trace.js';
|
|
19
19
|
import { createLLMAdapter } from './llm/adapter.js';
|
|
20
|
-
import { ConversationStore } from './conversation/persist.js';
|
|
20
|
+
import { ConversationStore, setDefaultRecentTurnsLimit } from './conversation/persist.js';
|
|
21
21
|
import { SkillManager, createSkillManager } from './skills.js';
|
|
22
22
|
import { MCPManager } from './mcp.js';
|
|
23
23
|
import { createFullRegistry } from './tools/index.js';
|
|
@@ -156,6 +156,15 @@ export async function loadSession(options = {}) {
|
|
|
156
156
|
// unref it (CLI / tests). Non-persisted — set per-session by caller.
|
|
157
157
|
if (serverMode) config.serverMode = true;
|
|
158
158
|
|
|
159
|
+
// Propagate the (clamped) cold-start replay window to the conversation
|
|
160
|
+
// store. The default is 20 turns; a user wanting more recall after a
|
|
161
|
+
// fresh boot sets `yeaft.recentTurnsLimit` in ~/.yeaft/config.json.
|
|
162
|
+
// Called once per session boot — subsequent boots overwrite the
|
|
163
|
+
// module-level default safely (single-process model).
|
|
164
|
+
if (config?.yeaft?.recentTurnsLimit) {
|
|
165
|
+
setDefaultRecentTurnsLimit(config.yeaft.recentTurnsLimit);
|
|
166
|
+
}
|
|
167
|
+
|
|
159
168
|
// ─── 2.1 Migration state check (task-334i) ────────────
|
|
160
169
|
// If the group-chat feature flag is on but migration has not
|
|
161
170
|
// completed, warn the user. Do NOT auto-run migration: that is
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* snapshot-filter.js — VP-isolated view of the in-memory group history.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the disk-replay rules in
|
|
5
|
+
* `agent/yeaft/conversation/persist.js#loadSessionHistoryForVp`:
|
|
6
|
+
*
|
|
7
|
+
* - User rows (no `speakerVpId`): KEEP — every VP sees the prompt.
|
|
8
|
+
* - This VP's own assistant rows + their paired tool rows: KEEP.
|
|
9
|
+
* - OTHER VPs' assistant rows: KEEP TEXT ONLY (strip `toolCalls` AND
|
|
10
|
+
* `thinkingBlocks` — thinking is VP-private per Anthropic's signed-
|
|
11
|
+
* block contract; tool_use ids belong to that VP's own tool arc).
|
|
12
|
+
* - OTHER VPs' tool result rows (role:'tool'): DROP — they pair with
|
|
13
|
+
* stripped tool_use ids and would orphan on the LLM request.
|
|
14
|
+
* - Rows with `_reflection` / `internal` / `systemOnly`: DROP — engine-
|
|
15
|
+
* private; never enter another VP's context.
|
|
16
|
+
*
|
|
17
|
+
* Why this exists separately from the disk replay path:
|
|
18
|
+
* `web-bridge.js` builds an in-memory `baseSnapshot` for every running
|
|
19
|
+
* VP turn. Before this filter the snapshot was only `threadId`-scoped
|
|
20
|
+
* and leaked other VPs' tool calls + thinking blocks into the next
|
|
21
|
+
* turn's messages, producing orphan `tool_use` ids and Anthropic 422s.
|
|
22
|
+
* The disk path uses `ConversationStore.loadSessionHistoryForVp` —
|
|
23
|
+
* the same rules implemented here, but reading from `messages/*.md`
|
|
24
|
+
* instead of the in-memory tape. We intentionally duplicate the rule
|
|
25
|
+
* set rather than calling into persist.js so both call sites stay
|
|
26
|
+
* independently audit-able.
|
|
27
|
+
*
|
|
28
|
+
* Pure function; does not mutate inputs.
|
|
29
|
+
*
|
|
30
|
+
* @param {object[]} snapshot — entries from getOrCreateSessionHistory(sessionId)
|
|
31
|
+
* (already threadId-filtered by the caller)
|
|
32
|
+
* @param {string} vpId — the VP we're about to send a turn for
|
|
33
|
+
* @returns {object[]}
|
|
34
|
+
*/
|
|
35
|
+
export function filterSnapshotForVp(snapshot, vpId) {
|
|
36
|
+
if (!Array.isArray(snapshot) || snapshot.length === 0) return [];
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const m of snapshot) {
|
|
39
|
+
if (!m || typeof m !== 'object') continue;
|
|
40
|
+
// Reflection / engine-private rows are dropped regardless of vpId.
|
|
41
|
+
// Even the "no vpId, give me everything" code path must not leak
|
|
42
|
+
// these into a snapshot the caller will hand to an LLM — they were
|
|
43
|
+
// never meant to enter another VP's context, nor any VP's.
|
|
44
|
+
if (m._reflection || m.internal || m.systemOnly || m.systemOnlyMessage) continue;
|
|
45
|
+
if (!vpId) {
|
|
46
|
+
// No VP scope known: treat every row as "own". This is the
|
|
47
|
+
// fallback used by callers that don't (yet) have a vpId; the
|
|
48
|
+
// reflection filter above still applies.
|
|
49
|
+
out.push(m);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (m.role === 'user') {
|
|
53
|
+
out.push(m);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (m.role === 'assistant') {
|
|
57
|
+
if (!m.speakerVpId || m.speakerVpId === vpId) {
|
|
58
|
+
// Own assistant turn OR un-attributed (pre-rename) row — keep
|
|
59
|
+
// intact so this VP's tool arcs survive untouched.
|
|
60
|
+
out.push(m);
|
|
61
|
+
} else {
|
|
62
|
+
// Other VP's assistant turn — keep the visible text only.
|
|
63
|
+
// Stripping toolCalls is what makes the paired 'tool' rows
|
|
64
|
+
// below safe to drop without leaving orphan tool_use ids in
|
|
65
|
+
// the LLM payload. Stripping thinkingBlocks is required by
|
|
66
|
+
// Anthropic — signatures are VP-private and would fail
|
|
67
|
+
// server-side verification if echoed by a different VP.
|
|
68
|
+
const copy = { ...m };
|
|
69
|
+
delete copy.toolCalls;
|
|
70
|
+
delete copy.thinkingBlocks;
|
|
71
|
+
out.push(copy);
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (m.role === 'tool') {
|
|
76
|
+
// Tool results belong to the assistant turn that emitted the
|
|
77
|
+
// tool_use. We only keep ours; the other VPs' results paired
|
|
78
|
+
// with `toolCalls` we just stripped above, so they'd be
|
|
79
|
+
// orphans now anyway.
|
|
80
|
+
if (!m.speakerVpId || m.speakerVpId === vpId) out.push(m);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Unknown role — keep as-is so future schema additions don't get
|
|
84
|
+
// silently dropped.
|
|
85
|
+
out.push(m);
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|