@yeaft/webchat-agent 0.1.665 → 0.1.667

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.
Files changed (41) hide show
  1. package/connection/message-router.js +1 -24
  2. package/package.json +1 -1
  3. package/unify/cli.js +5 -84
  4. package/unify/config.js +2 -2
  5. package/unify/dream-v2/apply.js +1 -1
  6. package/unify/dream-v2/limits.js +1 -1
  7. package/unify/dream-v2/merge.js +1 -1
  8. package/unify/dream-v2/runner.js +2 -2
  9. package/unify/dream-v2/schedule.js +2 -2
  10. package/unify/dream-v2/segment.js +1 -1
  11. package/unify/dream-v2/session-wiring.js +2 -3
  12. package/unify/dream-v2/snapshot.js +1 -1
  13. package/unify/dream-v2/state.js +1 -1
  14. package/unify/dream-v2/triage.js +1 -1
  15. package/unify/engine.js +23 -133
  16. package/unify/eval/cases/memory.js +9 -142
  17. package/unify/features/summary.js +15 -98
  18. package/unify/index.js +0 -2
  19. package/unify/memory/ams.js +1 -1
  20. package/unify/memory/consolidate.js +10 -125
  21. package/unify/memory/segment-store.js +1 -1
  22. package/unify/memory/store-v2.js +6 -9
  23. package/unify/prompts.js +8 -50
  24. package/unify/session.js +2 -22
  25. package/unify/stop-hooks.js +8 -44
  26. package/unify/tools/index.js +0 -11
  27. package/unify/web-bridge.js +0 -98
  28. package/unify/memory/dream-shard.js +0 -722
  29. package/unify/memory/extract.js +0 -101
  30. package/unify/memory/layout.js +0 -358
  31. package/unify/memory/schema.js +0 -166
  32. package/unify/memory/shard-store.js +0 -373
  33. package/unify/memory/store.js +0 -578
  34. package/unify/memory/types.js +0 -139
  35. package/unify/memory/user-memory-store.js +0 -452
  36. package/unify/tools/memory-query.js +0 -134
  37. package/unify/tools/memory-read.js +0 -90
  38. package/unify/tools/memory-search.js +0 -140
  39. package/unify/tools/memory-trace.js +0 -135
  40. package/unify/tools/memory-write.js +0 -113
  41. package/unify/user-memory.js +0 -107
@@ -1,140 +0,0 @@
1
- /**
2
- * memory-search.js → memory_load (task-333b rename).
3
- *
4
- * This tool loads memory classification files by path (it is NOT a search).
5
- * task-333b renamed it from `memory_search` → `memory_load` so the name
6
- * actually reflects behaviour; `memory_query` remains the fuzzy search tool.
7
- *
8
- * Backwards compatibility: a thin alias tool named `memory_search` is also
9
- * exported (see bottom of file) so older transcripts/prompts still resolve.
10
- *
11
- * The system prompt injects `index.md` every turn, which lists all available
12
- * classification files under `~/.yeaft/memory/` (single files + by-project /
13
- * by-topic / timeline categories). When the LLM sees a path it wants,
14
- * it calls this tool with `paths: [...]` to load one or more of those files
15
- * in full.
16
- *
17
- * Only paths under `memory/` are accepted. `..` segments are rejected.
18
- */
19
-
20
- import { defineTool } from './types.js';
21
- import { readMemoryFile, listClassificationFiles } from '../memory/layout.js';
22
-
23
- const MAX_FILES_PER_CALL = 5;
24
- const MAX_BYTES_PER_FILE = 32000;
25
-
26
- const DESCRIPTION = `Load one or more memory classification files in full.
27
-
28
- Paths are relative to ~/.yeaft/memory/. Allowed targets:
29
- - user-preferences.md — merged user preferences
30
- - by-project/<slug>.md — per-project narrative summary
31
- - by-topic/<slug>.md — per-topic narrative summary
32
- - timeline/<YYYY-MM>.md — monthly narrative digest
33
-
34
- See the "Memory Index" section of the system prompt for the current list
35
- of available files. Use this tool when the index suggests a file is relevant
36
- to the user's current request. For fuzzy search over atomic memory entries
37
- (facts, lessons, preferences), use the memory_query tool instead.
38
-
39
- Up to ${MAX_FILES_PER_CALL} files per call. Each file is capped at ${MAX_BYTES_PER_FILE} bytes.`;
40
-
41
- const PARAMETERS = {
42
- type: 'object',
43
- properties: {
44
- paths: {
45
- type: 'array',
46
- items: { type: 'string' },
47
- description: 'Relative paths under memory/. Example: ["by-project/claude-web-chat.md", "user-preferences.md"]',
48
- },
49
- },
50
- required: ['paths'],
51
- };
52
-
53
- async function executeLoad(input, ctx) {
54
- const yeaftDir = ctx?.yeaftDir;
55
- if (!yeaftDir) {
56
- return JSON.stringify({ error: 'Memory system not initialized (no yeaftDir in context)' });
57
- }
58
-
59
- const paths = Array.isArray(input?.paths) ? input.paths : [];
60
- if (paths.length === 0) {
61
- return JSON.stringify({
62
- error: 'paths is required and must be a non-empty string array',
63
- availablePaths: listClassificationFiles(yeaftDir).map(f => f.path),
64
- });
65
- }
66
-
67
- const results = [];
68
- const errors = [];
69
-
70
- for (const rel of paths.slice(0, MAX_FILES_PER_CALL)) {
71
- if (typeof rel !== 'string' || !rel.trim()) {
72
- errors.push({ path: rel, error: 'not a non-empty string' });
73
- continue;
74
- }
75
- if (rel.includes('..') || rel.startsWith('/')) {
76
- errors.push({ path: rel, error: 'path must be relative and must not contain ..' });
77
- continue;
78
- }
79
- if (!rel.endsWith('.md')) {
80
- errors.push({ path: rel, error: 'only .md files are supported' });
81
- continue;
82
- }
83
-
84
- const text = readMemoryFile(yeaftDir, rel);
85
- if (!text) {
86
- errors.push({ path: rel, error: 'file not found or empty' });
87
- continue;
88
- }
89
-
90
- const truncated = text.length > MAX_BYTES_PER_FILE;
91
- results.push({
92
- path: rel,
93
- content: truncated ? text.slice(0, MAX_BYTES_PER_FILE) : text,
94
- truncated,
95
- size: text.length,
96
- });
97
- }
98
-
99
- return JSON.stringify({ results, errors }, null, 2);
100
- }
101
-
102
- /**
103
- * Canonical tool — `memory_load`. This is the default export so existing
104
- * import sites (`import memorySearch from './memory-search.js'`) keep
105
- * working; the renamed identity is expressed via the tool's `name`.
106
- */
107
- const memoryLoad = defineTool({
108
- name: 'memory_load',
109
- description: DESCRIPTION,
110
- parameters: PARAMETERS,
111
- isConcurrencySafe: () => true,
112
- isReadOnly: () => true,
113
- execute: executeLoad,
114
- });
115
-
116
- /**
117
- * Deprecated alias — `memory_search`. Kept so older prompts / saved tool
118
- * calls still resolve. Delegates to the same executor. Do not use for new
119
- * call sites. Emits a one-time console.warn on first invocation.
120
- */
121
- const _memSearchWarned = { v: false };
122
- async function executeLoadWithWarn(input, ctx) {
123
- if (!_memSearchWarned.v) {
124
- _memSearchWarned.v = true;
125
- // eslint-disable-next-line no-console
126
- console.warn('[deprecated] memory_search → memory_load. Use memory_load for path-based file loading; use memory_query for fuzzy keyword search.');
127
- }
128
- return executeLoad(input, ctx);
129
- }
130
-
131
- export const memorySearchAlias = defineTool({
132
- name: 'memory_search',
133
- description: 'DEPRECATED — use memory_load. Same params. Removal target: v0.2.0.',
134
- parameters: PARAMETERS,
135
- isConcurrencySafe: () => true,
136
- isReadOnly: () => true,
137
- execute: executeLoadWithWarn,
138
- });
139
-
140
- export default memoryLoad;
@@ -1,135 +0,0 @@
1
- /**
2
- * memory-trace.js — task-334f R6 §Δ24.3.
3
- *
4
- * Given a memory id, return the full entry (including sourceRef) plus the
5
- * original source messages referenced by sourceRef.msgIds / timeWindow.
6
- *
7
- * Hard guardrails (task-334f):
8
- * - Results are returned to the current turn ONLY. Nothing is written back
9
- * to memory; the extraction lane sees its own copy.
10
- * - Does not do cross-group fan-out. A trace is anchored to one groupId.
11
- */
12
-
13
- import { defineTool } from './types.js';
14
-
15
- const MAX_BYTES = 64 * 1024;
16
-
17
- export default defineTool({
18
- name: 'memory_trace',
19
- description: `Trace a memory entry back to its original source messages.
20
-
21
- Use this when a recalled memory body is insufficient and you need the raw
22
- discussion. Returns the full memory entry (with sourceRef) plus the source
23
- messages from the group jsonl log.
24
-
25
- Parameters:
26
- - memId (required): the memory id (from recall)
27
- - expand: "full" (default, exact msgIds) | "window" (expand around timeWindow)
28
-
29
- Returns JSON: { memory, messages[], truncated? }.
30
- The result is NOT written back to memory — it is context for the current turn
31
- only.`,
32
- parameters: {
33
- type: 'object',
34
- properties: {
35
- memId: { type: 'string', description: 'Memory entry id' },
36
- expand: { type: 'string', enum: ['full', 'window'], default: 'full' },
37
- },
38
- required: ['memId'],
39
- },
40
- isConcurrencySafe: () => true,
41
- isReadOnly: () => true,
42
- async execute(input, ctx) {
43
- const memId = input?.memId;
44
- if (!memId || typeof memId !== 'string') {
45
- return JSON.stringify({ error: 'memId required (string)' });
46
- }
47
- const expand = input?.expand === 'window' ? 'window' : 'full';
48
-
49
- const store = ctx?.memoryShardStore;
50
- if (!store) {
51
- return JSON.stringify({ error: 'R6 memory shard store not initialised' });
52
- }
53
- const entry = store.get(memId);
54
- if (!entry) {
55
- return JSON.stringify({ error: `memory entry not found: ${memId}` });
56
- }
57
-
58
- const sourceRef = entry.sourceRef || null;
59
- if (!sourceRef) {
60
- return JSON.stringify({
61
- memory: entry,
62
- messages: [],
63
- note: 'entry has no sourceRef (pure declaration)',
64
- });
65
- }
66
-
67
- const coordinator = ctx?.coordinator;
68
- const groupId = sourceRef.groupId;
69
- if (!coordinator || !groupId) {
70
- return JSON.stringify({
71
- memory: entry,
72
- messages: [],
73
- note: 'no group coordinator available',
74
- });
75
- }
76
-
77
- const group = typeof coordinator.openGroup === 'function'
78
- ? coordinator.openGroup(groupId)
79
- : null;
80
- if (!group) {
81
- return JSON.stringify({
82
- memory: entry,
83
- messages: [],
84
- note: `group ${groupId} not resolvable`,
85
- });
86
- }
87
-
88
- const messages = [];
89
- let bytes = 0;
90
- let truncated = false;
91
-
92
- if (expand === 'full' && Array.isArray(sourceRef.msgIds) && sourceRef.msgIds.length) {
93
- const targetSet = new Set(sourceRef.msgIds);
94
- // Walk only the smallest overlapping range instead of streaming all.
95
- const first = sourceRef.msgIds[0];
96
- const last = sourceRef.msgIds[sourceRef.msgIds.length - 1];
97
- const iter = typeof group.readMessageRange === 'function'
98
- ? group.readMessageRange(first, last)
99
- : group.streamMessages();
100
- for (const msg of iter) {
101
- if (!targetSet.has(msg.id)) continue;
102
- const chunk = estimateBytes(msg);
103
- if (bytes + chunk > MAX_BYTES) { truncated = true; break; }
104
- messages.push(msg);
105
- bytes += chunk;
106
- }
107
- } else if (expand === 'window' && sourceRef.timeWindow) {
108
- // timeWindow is "ISO..ISO"; best-effort textual compare works for ULIDs/ISO.
109
- const [t0, t1] = String(sourceRef.timeWindow).split('..');
110
- for (const msg of group.streamMessages()) {
111
- const ts = msg.ts || '';
112
- if (t0 && ts < t0) continue;
113
- if (t1 && ts > t1) break;
114
- const chunk = estimateBytes(msg);
115
- if (bytes + chunk > MAX_BYTES) { truncated = true; break; }
116
- messages.push(msg);
117
- bytes += chunk;
118
- }
119
- }
120
-
121
- return JSON.stringify({
122
- memory: entry,
123
- messages,
124
- ...(truncated ? { truncated: true } : {}),
125
- });
126
- },
127
- });
128
-
129
- function estimateBytes(msg) {
130
- try {
131
- return Buffer.byteLength(JSON.stringify(msg), 'utf8');
132
- } catch {
133
- return 512;
134
- }
135
- }
@@ -1,113 +0,0 @@
1
- /**
2
- * memory-write.js — Write memory entries to the Yeaft memory store.
3
- *
4
- * Creates, updates, or deletes memory entries. Also supports
5
- * appending lines to MEMORY.md sections and overwriting the profile.
6
- */
7
-
8
- import { defineTool } from './types.js';
9
-
10
- export default defineTool({
11
- name: 'MemoryWrite',
12
- description: `Write to Yeaft's persistent memory system.
13
-
14
- Actions:
15
- - "write_entry" — create or update a memory entry (entries/*.md)
16
- - "delete_entry" — delete a memory entry by name
17
- - "write_profile" — overwrite the full MEMORY.md profile
18
- - "add_to_section" — append a line to a section in MEMORY.md
19
-
20
- Memory kinds: fact, preference, skill, lesson, context, relation
21
- Importance levels: low, normal, high, critical`,
22
- parameters: {
23
- type: 'object',
24
- properties: {
25
- action: {
26
- type: 'string',
27
- enum: ['write_entry', 'delete_entry', 'write_profile', 'add_to_section'],
28
- description: 'What memory operation to perform',
29
- },
30
- entry: {
31
- type: 'object',
32
- description: 'Memory entry data (for "write_entry")',
33
- properties: {
34
- name: { type: 'string', description: 'Entry name (will be slugified for filename)' },
35
- kind: { type: 'string', enum: ['fact', 'preference', 'skill', 'lesson', 'context', 'relation'] },
36
- scope: { type: 'string', description: 'Scope path, e.g. "global", "work/my-project"' },
37
- tags: { type: 'array', items: { type: 'string' } },
38
- importance: { type: 'string', enum: ['low', 'normal', 'high', 'critical'] },
39
- content: { type: 'string', description: 'The memory content (markdown body)' },
40
- },
41
- required: ['name', 'content'],
42
- },
43
- name: {
44
- type: 'string',
45
- description: 'Entry name slug (for "delete_entry") or section name (for "add_to_section")',
46
- },
47
- content: {
48
- type: 'string',
49
- description: 'Content for "write_profile" or line to add for "add_to_section"',
50
- },
51
- },
52
- required: ['action'],
53
- },
54
- isConcurrencySafe: () => false,
55
- isReadOnly: () => false,
56
- async execute(input, ctx) {
57
- const memoryStore = ctx?.memoryStore;
58
- if (!memoryStore) {
59
- return JSON.stringify({ error: 'Memory system not initialized' });
60
- }
61
-
62
- try {
63
- switch (input.action) {
64
- case 'write_entry': {
65
- if (!input.entry) return JSON.stringify({ error: 'entry is required for "write_entry"' });
66
- if (!input.entry.name) return JSON.stringify({ error: 'entry.name is required' });
67
- if (!input.entry.content) return JSON.stringify({ error: 'entry.content is required' });
68
-
69
- const slug = memoryStore.writeEntry(input.entry);
70
- return JSON.stringify({
71
- success: true,
72
- slug,
73
- message: `Memory entry "${input.entry.name}" saved as ${slug}.md`,
74
- });
75
- }
76
-
77
- case 'delete_entry': {
78
- if (!input.name) return JSON.stringify({ error: 'name is required for "delete_entry"' });
79
- const deleted = memoryStore.deleteEntry(input.name);
80
- return JSON.stringify({
81
- success: deleted,
82
- message: deleted
83
- ? `Deleted memory entry "${input.name}"`
84
- : `Entry "${input.name}" not found`,
85
- });
86
- }
87
-
88
- case 'write_profile': {
89
- if (!input.content && input.content !== '') {
90
- return JSON.stringify({ error: 'content is required for "write_profile"' });
91
- }
92
- memoryStore.writeProfile(input.content);
93
- return JSON.stringify({ success: true, message: 'MEMORY.md updated' });
94
- }
95
-
96
- case 'add_to_section': {
97
- if (!input.name) return JSON.stringify({ error: 'name (section) is required for "add_to_section"' });
98
- if (!input.content) return JSON.stringify({ error: 'content (line) is required for "add_to_section"' });
99
- memoryStore.addToSection(input.name, input.content);
100
- return JSON.stringify({
101
- success: true,
102
- message: `Added to section "${input.name}" in MEMORY.md`,
103
- });
104
- }
105
-
106
- default:
107
- return JSON.stringify({ error: `Unknown action: ${input.action}` });
108
- }
109
- } catch (err) {
110
- return JSON.stringify({ error: `Memory write failed: ${err.message}` });
111
- }
112
- },
113
- });
@@ -1,107 +0,0 @@
1
- /**
2
- * user-memory.js — R6 §Δ29 user-memory WS event handlers.
3
- *
4
- * Replaces the stub (task-334h) with real ingestion backed by the R6
5
- * shard-store. Writes land immediately in `~/.yeaft/user/memory/` with
6
- * a real entryId; the ack carries `reason: 'accepted'`.
7
- *
8
- * Wire shapes (frozen by R6 §Δ31.6 table; additive fields only):
9
- *
10
- * inbound (web → agent): `unify_user_memory_write`
11
- * { type, text, tags?, sourceRef?, requestId? }
12
- *
13
- * outbound (agent → web): `user_memory_updated`
14
- * { type, entryId?, reason: 'accepted'|'noop',
15
- * requestId?, pending?: boolean }
16
- *
17
- * outbound (agent → web): `user_memory_removed`
18
- * { type, entryId, requestId? }
19
- */
20
-
21
- import {
22
- getUserMemoryStore,
23
- writeUserMemory,
24
- removeUserMemory,
25
- } from './memory/user-memory-store.js';
26
-
27
- /** @type {(event:object)=>void | null} */
28
- let _sendUnifyEvent = null;
29
-
30
- /**
31
- * Install a send fn. Called once during session init from web-bridge.js.
32
- * Exposed so tests can swap in a collector without spinning up a session.
33
- */
34
- export function setUserMemorySender(fn) {
35
- _sendUnifyEvent = (typeof fn === 'function') ? fn : null;
36
- }
37
-
38
- /**
39
- * WS handler: `unify_user_memory_write`.
40
- *
41
- * Validates the minimum shape (non-empty string `text`), writes to the
42
- * user-memory shard store, and replies with a `user_memory_updated` ack
43
- * carrying the real entryId. Never throws.
44
- *
45
- * @param {any} msg
46
- * @param {(event:object)=>void} [sendUnifyEvent] — optional override
47
- */
48
- export function handleUnifyUserMemoryWrite(msg, sendUnifyEvent) {
49
- const send = sendUnifyEvent || _sendUnifyEvent;
50
- if (!send) return;
51
-
52
- const requestId = msg && typeof msg.requestId === 'string' ? msg.requestId : undefined;
53
- const text = msg && typeof msg.text === 'string' ? msg.text : '';
54
-
55
- if (!text || text.length === 0) {
56
- try {
57
- send({
58
- type: 'user_memory_updated',
59
- reason: 'noop',
60
- pending: false,
61
- ...(requestId ? { requestId } : {}),
62
- });
63
- } catch { /* best-effort */ }
64
- return;
65
- }
66
-
67
- // Real ingestion via shard store.
68
- const store = getUserMemoryStore();
69
- const tags = Array.isArray(msg.tags) ? msg.tags : [];
70
- const sourceRef = msg.sourceRef && typeof msg.sourceRef === 'object' ? msg.sourceRef : undefined;
71
- const entryId = store ? writeUserMemory(store, { text, tags, sourceRef }) : null;
72
-
73
- try {
74
- send({
75
- type: 'user_memory_updated',
76
- reason: entryId ? 'accepted' : 'deferred',
77
- pending: !entryId,
78
- entryId: entryId || undefined,
79
- ...(requestId ? { requestId } : {}),
80
- });
81
- } catch { /* best-effort */ }
82
- }
83
-
84
- /**
85
- * WS handler: `unify_user_memory_remove`.
86
- *
87
- * Removes the entry from the user-memory shard store and acks.
88
- */
89
- export function handleUnifyUserMemoryRemove(msg, sendUnifyEvent) {
90
- const send = sendUnifyEvent || _sendUnifyEvent;
91
- if (!send) return;
92
-
93
- const requestId = msg && typeof msg.requestId === 'string' ? msg.requestId : undefined;
94
- const entryId = msg && typeof msg.entryId === 'string' ? msg.entryId : null;
95
-
96
- const store = getUserMemoryStore();
97
- const removed = entryId && store ? removeUserMemory(store, entryId) : false;
98
-
99
- try {
100
- send({
101
- type: 'user_memory_removed',
102
- entryId,
103
- pending: !removed,
104
- ...(requestId ? { requestId } : {}),
105
- });
106
- } catch { /* best-effort */ }
107
- }