@yeaft/webchat-agent 0.1.854 → 0.1.857

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 (174) hide show
  1. package/bin/yeaft-stats.js +3 -3
  2. package/connection/message-router.js +73 -44
  3. package/context.js +4 -4
  4. package/expert-roles.js +1 -1
  5. package/index.js +1 -1
  6. package/package.json +4 -4
  7. package/service/index.js +1 -1
  8. package/{unify → yeaft}/attachments.js +7 -7
  9. package/{unify → yeaft}/cli.js +4 -4
  10. package/{unify → yeaft}/compact/compactor.js +10 -10
  11. package/{unify → yeaft}/compact/orchestrator.js +7 -13
  12. package/{unify → yeaft}/config-api.js +12 -12
  13. package/{unify → yeaft}/config.js +19 -19
  14. package/{unify → yeaft}/conversation/migrate-messages-threadid.js +1 -1
  15. package/{unify → yeaft}/conversation/persist.js +194 -65
  16. package/{unify → yeaft}/conversation/search.js +28 -23
  17. package/{unify → yeaft}/debug-trace.js +1 -1
  18. package/{unify → yeaft}/dream-v2/apply.js +28 -11
  19. package/{unify → yeaft}/dream-v2/limits.js +3 -3
  20. package/{unify → yeaft}/dream-v2/prompts/index.js +8 -1
  21. package/{unify → yeaft}/dream-v2/runner.js +13 -6
  22. package/{unify → yeaft}/dream-v2/session-wiring.js +2 -2
  23. package/{unify → yeaft}/dream-v2/triage.js +13 -8
  24. package/{unify → yeaft}/effort.js +2 -2
  25. package/{unify → yeaft}/engine.js +10 -10
  26. package/{unify → yeaft}/eval/run-eval.js +11 -11
  27. package/{unify → yeaft}/eval/runner.js +1 -1
  28. package/{unify → yeaft}/groups/feature-flag.js +2 -2
  29. package/{unify → yeaft}/groups/index.js +2 -2
  30. package/{unify → yeaft}/groups/pre-flow.js +15 -3
  31. package/{unify → yeaft}/groups/project-doc.js +1 -1
  32. package/{unify → yeaft}/history-compact.js +3 -3
  33. package/{unify → yeaft}/index.js +1 -1
  34. package/{unify → yeaft}/init.js +15 -9
  35. package/{unify → yeaft}/llm/adapter.js +1 -1
  36. package/{unify → yeaft}/llm/anthropic.js +2 -2
  37. package/{unify → yeaft}/llm/models-dev.js +10 -3
  38. package/{unify → yeaft}/llm/openai-responses.js +2 -2
  39. package/{unify → yeaft}/llm/router.js +1 -1
  40. package/{unify → yeaft}/mcp.js +1 -1
  41. package/{unify → yeaft}/memory/adjust.js +2 -4
  42. package/{unify → yeaft}/memory/ams-registry.js +1 -1
  43. package/{unify → yeaft}/memory/ams.js +2 -4
  44. package/{unify → yeaft}/memory/preflow.js +2 -6
  45. package/{unify → yeaft}/memory/seed-backfill.js +42 -2
  46. package/{unify → yeaft}/memory/segment.js +1 -1
  47. package/{unify → yeaft}/memory/store-v2.js +120 -73
  48. package/{unify → yeaft}/models.js +3 -3
  49. package/{unify → yeaft}/pair-sanitize.js +2 -2
  50. package/{unify → yeaft}/personas.js +1 -1
  51. package/{unify → yeaft}/prompts.js +1 -1
  52. package/{unify → yeaft}/routing/index.js +1 -1
  53. package/{unify → yeaft}/routing/router.js +1 -1
  54. package/{unify → yeaft}/session.js +19 -9
  55. package/{unify → yeaft}/skills.js +2 -2
  56. package/{unify → yeaft}/stats/format.js +1 -1
  57. package/{unify → yeaft}/stop-hooks.js +1 -1
  58. package/{unify → yeaft}/sub-agent/runner.js +1 -1
  59. package/{unify → yeaft}/tools/ask-user.js +2 -2
  60. package/{unify → yeaft}/tools/enter-worktree.js +1 -1
  61. package/{unify → yeaft}/tools/exit-worktree.js +1 -1
  62. package/{unify → yeaft}/tools/mcp-tools.js +1 -1
  63. package/{unify → yeaft}/tools/registry.js +2 -2
  64. package/{unify → yeaft}/tools/skill.js +1 -1
  65. package/{unify → yeaft}/tools/start-plan.js +1 -1
  66. package/{unify → yeaft}/tools/types.js +3 -3
  67. package/{unify → yeaft}/tools/web-search.js +1 -1
  68. package/{unify → yeaft}/turn-utils.js +1 -1
  69. package/{unify → yeaft}/vp/index.js +1 -1
  70. package/{unify → yeaft}/vp/registry.js +1 -1
  71. package/{unify → yeaft}/vp/vp-bridge.js +8 -8
  72. package/{unify → yeaft}/vp/vp-crud.js +7 -24
  73. package/{unify → yeaft}/web-bridge.js +230 -230
  74. /package/{unify → yeaft}/archive/tool-results.js +0 -0
  75. /package/{unify → yeaft}/archive/trace.js +0 -0
  76. /package/{unify → yeaft}/archive/turn-archive.js +0 -0
  77. /package/{unify → yeaft}/compact/decisions-log.js +0 -0
  78. /package/{unify → yeaft}/compact/triggers.js +0 -0
  79. /package/{unify → yeaft}/compact/turn-group.js +0 -0
  80. /package/{unify → yeaft}/dream-v2/merge.js +0 -0
  81. /package/{unify → yeaft}/dream-v2/prompts/create.md +0 -0
  82. /package/{unify → yeaft}/dream-v2/prompts/extract-group.md +0 -0
  83. /package/{unify → yeaft}/dream-v2/prompts/extract-topic.md +0 -0
  84. /package/{unify → yeaft}/dream-v2/prompts/extract-user.md +0 -0
  85. /package/{unify → yeaft}/dream-v2/prompts/extract-vp.md +0 -0
  86. /package/{unify → yeaft}/dream-v2/prompts/summarize-scope.md +0 -0
  87. /package/{unify → yeaft}/dream-v2/prompts/triage-pass1.md +0 -0
  88. /package/{unify → yeaft}/dream-v2/prompts/triage-pass2.md +0 -0
  89. /package/{unify → yeaft}/dream-v2/prompts/update.md +0 -0
  90. /package/{unify → yeaft}/dream-v2/schedule.js +0 -0
  91. /package/{unify → yeaft}/dream-v2/segment.js +0 -0
  92. /package/{unify → yeaft}/dream-v2/snapshot.js +0 -0
  93. /package/{unify → yeaft}/dream-v2/state.js +0 -0
  94. /package/{unify → yeaft}/eval/cases/e2e.js +0 -0
  95. /package/{unify → yeaft}/eval/cases/memory.js +0 -0
  96. /package/{unify → yeaft}/eval/cases/skills.js +0 -0
  97. /package/{unify → yeaft}/eval/cases/tool-use.js +0 -0
  98. /package/{unify → yeaft}/groups/coordinator.js +0 -0
  99. /package/{unify → yeaft}/groups/group-config.js +0 -0
  100. /package/{unify → yeaft}/groups/group-crud.js +0 -0
  101. /package/{unify → yeaft}/groups/group-store.js +0 -0
  102. /package/{unify → yeaft}/groups/ids.js +0 -0
  103. /package/{unify → yeaft}/groups/roster.js +0 -0
  104. /package/{unify → yeaft}/groups/seed-default.js +0 -0
  105. /package/{unify → yeaft}/llm/credentials/github-copilot.js +0 -0
  106. /package/{unify → yeaft}/llm/credentials/index.js +0 -0
  107. /package/{unify → yeaft}/memory/budget.js +0 -0
  108. /package/{unify → yeaft}/memory/consolidate.js +0 -0
  109. /package/{unify → yeaft}/memory/index-db.js +0 -0
  110. /package/{unify → yeaft}/memory/keywords.js +0 -0
  111. /package/{unify → yeaft}/memory/segment-store.js +0 -0
  112. /package/{unify → yeaft}/memory/segment-sync.js +0 -0
  113. /package/{unify → yeaft}/memory/summary-store.js +0 -0
  114. /package/{unify → yeaft}/router/continuity.js +0 -0
  115. /package/{unify → yeaft}/router/thinking.js +0 -0
  116. /package/{unify → yeaft}/routing/loop-guard.js +0 -0
  117. /package/{unify → yeaft}/stats/tool-usage.js +0 -0
  118. /package/{unify → yeaft}/storage/atomic.js +0 -0
  119. /package/{unify → yeaft}/storage/compact.js +0 -0
  120. /package/{unify → yeaft}/storage/index.js +0 -0
  121. /package/{unify → yeaft}/storage/jsonl-index.js +0 -0
  122. /package/{unify → yeaft}/storage/jsonl-log.js +0 -0
  123. /package/{unify → yeaft}/storage/shard-index.js +0 -0
  124. /package/{unify → yeaft}/storage/shard-store.js +0 -0
  125. /package/{unify → yeaft}/sub-agent/spawned-prompt.js +0 -0
  126. /package/{unify → yeaft}/templates/base.md +0 -0
  127. /package/{unify → yeaft}/templates/common-rules.md +0 -0
  128. /package/{unify → yeaft}/templates/harness/router-handoff.md +0 -0
  129. /package/{unify → yeaft}/templates/harness/router-shape.md +0 -0
  130. /package/{unify → yeaft}/templates/harness/worker-shape.md +0 -0
  131. /package/{unify → yeaft}/templates/identity-yeaft.md +0 -0
  132. /package/{unify → yeaft}/templates/mode-dream.md +0 -0
  133. /package/{unify → yeaft}/templates/mode-unified.md +0 -0
  134. /package/{unify → yeaft}/templates/personas/explorer.md +0 -0
  135. /package/{unify → yeaft}/templates/personas/implementer.md +0 -0
  136. /package/{unify → yeaft}/templates/personas/researcher.md +0 -0
  137. /package/{unify → yeaft}/templates/personas/reviewer.md +0 -0
  138. /package/{unify → yeaft}/templates/plan-instruction.md +0 -0
  139. /package/{unify → yeaft}/templates/tool-guidance.md +0 -0
  140. /package/{unify → yeaft}/tool-folding/exec-log.js +0 -0
  141. /package/{unify → yeaft}/tool-folding/fallback-stub.js +0 -0
  142. /package/{unify → yeaft}/tool-folding/index.js +0 -0
  143. /package/{unify → yeaft}/tool-folding/reflection-prompt.js +0 -0
  144. /package/{unify → yeaft}/tool-folding/t1-reflector.js +0 -0
  145. /package/{unify → yeaft}/tool-folding/t2-reflector.js +0 -0
  146. /package/{unify → yeaft}/tools/agent.js +0 -0
  147. /package/{unify → yeaft}/tools/apply-patch.js +0 -0
  148. /package/{unify → yeaft}/tools/bash.js +0 -0
  149. /package/{unify → yeaft}/tools/close-agent.js +0 -0
  150. /package/{unify → yeaft}/tools/file-edit.js +0 -0
  151. /package/{unify → yeaft}/tools/file-read.js +0 -0
  152. /package/{unify → yeaft}/tools/file-write.js +0 -0
  153. /package/{unify → yeaft}/tools/glob.js +0 -0
  154. /package/{unify → yeaft}/tools/grep.js +0 -0
  155. /package/{unify → yeaft}/tools/history-search.js +0 -0
  156. /package/{unify → yeaft}/tools/image-generation.js +0 -0
  157. /package/{unify → yeaft}/tools/index.js +0 -0
  158. /package/{unify → yeaft}/tools/js-repl.js +0 -0
  159. /package/{unify → yeaft}/tools/list-agents.js +0 -0
  160. /package/{unify → yeaft}/tools/list-dir.js +0 -0
  161. /package/{unify → yeaft}/tools/notebook-edit.js +0 -0
  162. /package/{unify → yeaft}/tools/route-forward.js +0 -0
  163. /package/{unify → yeaft}/tools/send-message.js +0 -0
  164. /package/{unify → yeaft}/tools/todo-write.js +0 -0
  165. /package/{unify → yeaft}/tools/view-image.js +0 -0
  166. /package/{unify → yeaft}/tools/wait-agent.js +0 -0
  167. /package/{unify → yeaft}/tools/web-fetch.js +0 -0
  168. /package/{unify → yeaft}/vp/seed-defaults.js +0 -0
  169. /package/{unify → yeaft}/vp/seed-topup.js +0 -0
  170. /package/{unify → yeaft}/vp/stock-ids.js +0 -0
  171. /package/{unify → yeaft}/vp/thread-classifier.js +0 -0
  172. /package/{unify → yeaft}/vp/vp-loader.js +0 -0
  173. /package/{unify → yeaft}/vp/vp-store.js +0 -0
  174. /package/{unify → yeaft}/vp-status-broker.js +0 -0
@@ -2,46 +2,48 @@
2
2
  * search.js — Conversation history search
3
3
  *
4
4
  * Simple keyword search across hot and cold messages.
5
- * Reference: yeaft-unify-core-systems.md §4.3
6
5
  */
7
6
 
8
7
  import { existsSync, readdirSync, readFileSync } from 'fs';
9
8
  import { join } from 'path';
10
- import { parseMessage } from './persist.js';
9
+ import { parseMessage, parseSeqFromId } from './persist.js';
11
10
 
12
11
  /**
13
12
  * Search messages in a directory for a keyword.
14
13
  *
15
14
  * @param {string} dir — messages directory
16
15
  * @param {string} keyword — search term (case-insensitive)
17
- * @param {number} limit — max results
18
16
  * @returns {object[]} — matching messages
19
17
  */
20
- function searchDir(dir, keyword, limit) {
18
+ function searchDir(dir, keyword) {
21
19
  if (!existsSync(dir)) return [];
22
20
 
23
21
  const lowerKeyword = keyword.toLowerCase();
24
22
  const files = readdirSync(dir)
25
23
  .filter(f => f.endsWith('.md'))
26
24
  .sort()
27
- .reverse(); // newest first
25
+ .reverse(); // newest first within one directory
28
26
 
29
27
  const results = [];
30
28
  for (const file of files) {
31
- if (results.length >= limit) break;
32
-
33
29
  const raw = readFileSync(join(dir, file), 'utf8');
34
- if (raw.toLowerCase().includes(lowerKeyword)) {
35
- const msg = parseMessage(raw);
36
- if (msg) results.push(msg);
37
- }
38
- }
30
+ if (!raw.toLowerCase().includes(lowerKeyword)) continue;
39
31
 
32
+ const msg = parseMessage(raw);
33
+ if (msg) results.push(msg);
34
+ }
40
35
  return results;
41
36
  }
42
37
 
38
+ function compareNewestFirst(a, b) {
39
+ const sa = parseSeqFromId(a?.id);
40
+ const sb = parseSeqFromId(b?.id);
41
+ if (Number.isFinite(sa) && Number.isFinite(sb) && sa !== sb) return sb - sa;
42
+ return String(b?.time || '').localeCompare(String(a?.time || ''));
43
+ }
44
+
43
45
  /**
44
- * Search conversation history (hot + cold) for a keyword.
46
+ * Search Yeaft history (chat + group + legacy conversation) for a keyword.
45
47
  *
46
48
  * @param {string} dir — Yeaft root directory (e.g. ~/.yeaft)
47
49
  * @param {string} keyword — search term
@@ -51,15 +53,18 @@ function searchDir(dir, keyword, limit) {
51
53
  export function searchMessages(dir, keyword, limit = 20) {
52
54
  if (!keyword || !keyword.trim()) return [];
53
55
 
54
- const msgDir = join(dir, 'conversation', 'messages');
55
- const coldDir = join(dir, 'conversation', 'cold');
56
-
57
- // Search hot first (more recent), then cold
58
- const hotResults = searchDir(msgDir, keyword, limit);
59
- const remaining = limit - hotResults.length;
60
- const coldResults = remaining > 0
61
- ? searchDir(coldDir, keyword, remaining)
62
- : [];
56
+ const dirs = [
57
+ join(dir, 'chat', 'messages'),
58
+ join(dir, 'chat', 'cold'),
59
+ join(dir, 'group', 'messages'),
60
+ join(dir, 'group', 'cold'),
61
+ // Compatibility for profiles created before chat/group split.
62
+ join(dir, 'conversation', 'messages'),
63
+ join(dir, 'conversation', 'cold'),
64
+ ];
63
65
 
64
- return [...hotResults, ...coldResults];
66
+ return dirs
67
+ .flatMap(d => searchDir(d, keyword))
68
+ .sort(compareNewestFirst)
69
+ .slice(0, limit);
65
70
  }
@@ -401,7 +401,7 @@ export class DebugTrace {
401
401
  }
402
402
 
403
403
  /**
404
- * Fetch the recent debug history for the UnifyDebugPanel. Returns one
404
+ * Fetch the recent debug history for the YeaftDebugPanel. Returns one
405
405
  * record per LLM loop (ordered oldest → newest) with the structured
406
406
  * fields the panel expects. JSON columns are parsed; truncated /
407
407
  * malformed payloads degrade to null instead of failing the call.
@@ -120,12 +120,28 @@ export function targetToScope(target) {
120
120
  if (!target || typeof target !== 'string') throw new Error('apply.targetToScope: target required');
121
121
  if (target === 'user') return { kind: 'user' };
122
122
  const segs = target.split('/').filter(Boolean);
123
- const head = segs[0];
124
- if (head === 'vp' && segs.length === 2) return { kind: 'vp', id: segs[1] };
125
- if (head === 'group' && segs.length === 2) return { kind: 'group', id: segs[1] };
126
- if (head === 'feature' && segs.length === 2) return { kind: 'feature', id: segs[1] };
127
- if (head === 'topic' && (segs.length === 2 || segs.length === 3)) {
128
- return { kind: 'topic', path: segs.slice(1) };
123
+ // Legacy scopes — explicitly rejected. Old data lives under .legacy/.
124
+ if (segs[0] === 'vp' || segs[0] === 'feature' || segs[0] === 'topic') {
125
+ throw new Error(`apply.targetToScope: legacy root scope ${JSON.stringify(target)} rejected use group/<g>/${segs[0]}/...`);
126
+ }
127
+ if (segs[0] === 'group') {
128
+ if (segs.length === 2) return { kind: 'group', id: segs[1] };
129
+ // group/<g>/user
130
+ if (segs.length === 3 && segs[2] === 'user') {
131
+ return { kind: 'group-user', groupId: segs[1] };
132
+ }
133
+ // group/<g>/vp/<v>
134
+ if (segs.length === 4 && segs[2] === 'vp') {
135
+ return { kind: 'group-vp', groupId: segs[1], id: segs[3] };
136
+ }
137
+ // group/<g>/feature/<f>
138
+ if (segs.length === 4 && segs[2] === 'feature') {
139
+ return { kind: 'group-feature', groupId: segs[1], id: segs[3] };
140
+ }
141
+ // group/<g>/topic/<l1>[/<l2>]
142
+ if (segs[2] === 'topic' && (segs.length === 4 || segs.length === 5)) {
143
+ return { kind: 'group-topic', groupId: segs[1], path: segs.slice(3) };
144
+ }
129
145
  }
130
146
  throw new Error(`apply.targetToScope: malformed target ${JSON.stringify(target)}`);
131
147
  }
@@ -252,11 +268,12 @@ export async function applyMergedTarget(merged, opts) {
252
268
 
253
269
  function scopeRelDir(scope) {
254
270
  switch (scope.kind) {
255
- case 'user': return 'user';
256
- case 'vp': return `vp/${scope.id}`;
257
- case 'group': return `group/${scope.id}`;
258
- case 'feature': return `feature/${scope.id}`;
259
- case 'topic': return `topic/${scope.path.join('/')}`;
271
+ case 'user': return 'user';
272
+ case 'group': return `group/${scope.id}`;
273
+ case 'group-user': return `group/${scope.groupId}/user`;
274
+ case 'group-vp': return `group/${scope.groupId}/vp/${scope.id}`;
275
+ case 'group-feature': return `group/${scope.groupId}/feature/${scope.id}`;
276
+ case 'group-topic': return `group/${scope.groupId}/topic/${scope.path.join('/')}`;
260
277
  default: throw new Error(`apply.scopeRelDir: unknown kind ${scope.kind}`);
261
278
  }
262
279
  }
@@ -6,7 +6,7 @@
6
6
  * callers that want to override one knob without restating the rest.
7
7
  *
8
8
  * `loadLimitsFromConfig(config)` merges a `~/.yeaft/config.json`
9
- * `unify.dream` block on top of defaults; unknown keys are ignored,
9
+ * `yeaft.dream` block on top of defaults; unknown keys are ignored,
10
10
  * malformed values fall back to default.
11
11
  */
12
12
 
@@ -35,12 +35,12 @@ export const DEFAULT_LIMITS = Object.freeze({
35
35
  });
36
36
 
37
37
  /**
38
- * Merge a config object's `unify.dream` block on top of defaults.
38
+ * Merge a config object's `yeaft.dream` block on top of defaults.
39
39
  * @param {object} [config]
40
40
  */
41
41
  export function loadLimitsFromConfig(config) {
42
42
  const out = { ...DEFAULT_LIMITS };
43
- const ud = config && config.unify && config.unify.dream;
43
+ const ud = config && config.yeaft && config.yeaft.dream;
44
44
  if (!ud || typeof ud !== 'object') return out;
45
45
  for (const k of Object.keys(out)) {
46
46
  if (Object.prototype.hasOwnProperty.call(ud, k)) {
@@ -41,8 +41,15 @@ const FILES = {
41
41
  export function extractTemplateForScope(scope) {
42
42
  if (!scope || typeof scope !== 'string') return 'extractTopic';
43
43
  if (scope === 'user') return 'extractUser';
44
- if (scope.startsWith('vp/')) return 'extractVp';
44
+ // Nested group-isolated scopes must be matched BEFORE the bare `group/<g>`
45
+ // branch so VPs/topics/features under a group don't get the group template.
46
+ if (/^group\/[^/]+\/vp\//.test(scope)) return 'extractVp';
47
+ if (/^group\/[^/]+\/topic\//.test(scope)) return 'extractTopic';
48
+ if (/^group\/[^/]+\/user(?:\/|$)/.test(scope)) return 'extractUser';
45
49
  if (scope.startsWith('group/')) return 'extractGroup';
50
+ // Legacy top-level vp/topic scopes (archived to .legacy/ on boot — kept
51
+ // here defensively in case something still constructs the old strings).
52
+ if (scope.startsWith('vp/')) return 'extractVp';
46
53
  if (scope.startsWith('topic/')) return 'extractTopic';
47
54
  return 'extractTopic';
48
55
  }
@@ -29,7 +29,7 @@
29
29
  * pruneOldSnapshots()
30
30
  *
31
31
  * onProgress emits dream_progress events that the web bridge forwards
32
- * as `unify_output` messages so the debug panel can render live state
32
+ * as `yeaft_output` messages so the debug panel can render live state
33
33
  * . All events flow through the same channel; no new
34
34
  * WebSocket message type is introduced.
35
35
  *
@@ -95,9 +95,14 @@ export async function runDream(opts) {
95
95
  const processedGroups = [];
96
96
 
97
97
  // 2. per-group: skip / segment / triage
98
- const topicSummaries = opts.listTopicSummaries
99
- ? await safeCall(opts.listTopicSummaries, [])
100
- : await defaultListTopicSummaries(opts.root, opts.language).catch(() => []);
98
+ // Topic summaries are now per-group (group/<g>/topic/...), so resolve
99
+ // them inside the per-group loop instead of once up front.
100
+ const resolveTopicSummaries = async (groupId) => {
101
+ if (opts.listTopicSummaries) {
102
+ return await safeCall(() => opts.listTopicSummaries(groupId), []);
103
+ }
104
+ return await defaultListTopicSummaries(opts.root, groupId, opts.language).catch(() => []);
105
+ };
101
106
 
102
107
  for (const groupId of groupIds) {
103
108
  // Current-group manual dream passes are the one case where scopeFilter
@@ -152,6 +157,7 @@ export async function runDream(opts) {
152
157
 
153
158
  let actions;
154
159
  try {
160
+ const topicSummaries = await resolveTopicSummaries(groupId);
155
161
  actions = await triageGroupSegments({
156
162
  groupId,
157
163
  segments,
@@ -304,11 +310,12 @@ async function safeCall(fn, fallback) {
304
310
  }
305
311
  }
306
312
 
307
- async function defaultListTopicSummaries(root, language) {
313
+ async function defaultListTopicSummaries(root, groupId, language) {
308
314
  const all = await listScopes({ root });
309
315
  const out = [];
310
316
  for (const sc of all) {
311
- if (sc.kind !== 'topic') continue;
317
+ if (sc.kind !== 'group-topic') continue;
318
+ if (sc.groupId !== groupId) continue;
312
319
  const summary = await readSummary(sc, { root, language });
313
320
  out.push({ path: sc.path.join('/'), summary });
314
321
  }
@@ -9,7 +9,7 @@
9
9
  * retired (task-710) — the wiring is unconditional.
10
10
  *
11
11
  * ─────────────────────────────────────────────────────────────────────
12
- * Wire contract — DreamEvent (consumed by web/components/UnifyDebugPanel.js)
12
+ * Wire contract — DreamEvent (consumed by web/components/YeaftDebugPanel.js)
13
13
  * ─────────────────────────────────────────────────────────────────────
14
14
  * The events persisted to trace_events with these event_type values are
15
15
  * load-bearing for the debug panel. Renaming a field on this side
@@ -282,7 +282,7 @@ function persistDreamTrace(session, eventType, eventData) {
282
282
  /**
283
283
  * Create a v2 dream scheduler bound to a session and wire its progress
284
284
  * events into the engine's sub-agent event sink (web-bridge translates
285
- * `dream_progress` into `unify_output` for the debug panel).
285
+ * `dream_progress` into `yeaft_output` for the debug panel).
286
286
  *
287
287
  * @param {Object} session
288
288
  * @returns {Object} — scheduler (start/stop/triggerNow)
@@ -62,20 +62,24 @@ export function applyHardRules({ groupId, messages }) {
62
62
  const out = new Map();
63
63
  const add = (scope) => { if (!out.has(scope)) out.set(scope, { kind: 'update', scope }); };
64
64
 
65
- // user is always in.
65
+ // global user is always in.
66
66
  add('user');
67
67
 
68
- // active group, except the virtual _no-group bucket.
69
- if (groupId && groupId !== '_no-group') add(`group/${groupId}`);
68
+ // active group + its per-group user layer, except the virtual _no-group bucket.
69
+ if (groupId && groupId !== '_no-group') {
70
+ add(`group/${groupId}`);
71
+ add(`group/${groupId}/user`);
72
+ }
70
73
 
71
74
  for (const m of (messages || [])) {
72
75
  if (!m || typeof m !== 'object') continue;
73
- // Active VP: any assistant message's vpId.
76
+ // Active VP: any assistant message's vpId — now group-internal.
74
77
  if (m.role === 'assistant') {
75
78
  const vp = m.vpId || (m.author && /^vp:(.+)$/.exec(m.author)?.[1]);
76
- if (vp && /^[A-Za-z0-9_\-.一-鿿]+$/.test(vp)) add(`vp/${vp}`);
79
+ if (vp && /^[A-Za-z0-9_\-.一-鿿]+$/.test(vp) && groupId && groupId !== '_no-group') {
80
+ add(`group/${groupId}/vp/${vp}`);
81
+ }
77
82
  }
78
- // (Active feature scope was dropped 2026-05-13 with the Feature system.)
79
83
  }
80
84
 
81
85
  return Array.from(out.values());
@@ -161,8 +165,9 @@ export async function classifySoft({ groupId, messages, topicSummaries, llm, lan
161
165
  const path = String(pass2.path || '').trim();
162
166
  if (!path) continue;
163
167
  const segs = path.split('/').filter(Boolean);
164
- if (!isValidTopic({ kind: 'topic', path: segs })) continue;
165
- const scope = `topic/${segs.join('/')}`;
168
+ if (!groupId || groupId === '_no-group') continue;
169
+ if (!isValidTopic({ kind: 'group-topic', groupId, path: segs })) continue;
170
+ const scope = `group/${groupId}/topic/${segs.join('/')}`;
166
171
  if (pass2.decision === 'match') {
167
172
  out.push({ kind: 'update', scope });
168
173
  } else if (pass2.decision === 'new') {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * effort.js — Scenario → effort decision tree for Unify
2
+ * effort.js — Scenario → effort decision tree for Yeaft
3
3
  *
4
4
  * task-327b: given a per-query context (scenario tag, tool-loop depth,
5
5
  * user override), pick the thinking/reasoning effort level that should be
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Red lines:
16
16
  * • Never error on unknown scenario — default to 'max'.
17
- * • Feature flag UNIFY_THINKING_V1 is enforced at the adapter/router
17
+ * • Feature flag YEAFT_THINKING_V1 is enforced at the adapter/router
18
18
  * layer; this module just computes the intended value. If the flag
19
19
  * is off, adapters drop it anyway.
20
20
  * • Unsupported models silently drop effort at the router — this
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Pattern derived from Claude Code's query loop (src/query.ts).
16
16
  *
17
- * Reference: yeaft-unify-implementation-plan.md §3.1, §4 (Phase 2)
17
+ * Reference: yeaft-yeaft-implementation-plan.md §3.1, §4 (Phase 2)
18
18
  */
19
19
 
20
20
  import { randomUUID } from 'crypto';
@@ -60,7 +60,7 @@ import {
60
60
  * task-324 — Turn cap removed.
61
61
  *
62
62
  * Previously MAX_TURNS=25 broke the query loop out of long tool-driven
63
- * conversations (user report: Unify loop errored at the cap). The engine
63
+ * conversations (user report: Yeaft loop errored at the cap). The engine
64
64
  * now runs until the LLM itself returns stopReason='end_turn' or a
65
65
  * non-retryable error surfaces. Real runaway loops are still bounded by:
66
66
  * • provider rate limits / context window (LLMContextError → compact)
@@ -81,7 +81,7 @@ const MAX_CONTINUE_TURNS = 3;
81
81
  * Content is passed through verbatim — never truncated. Debug traces must
82
82
  * mirror exactly what we sent to the LLM; a truncated copy is misleading.
83
83
  * If the resulting payload is too large for the client debug store the
84
- * bound is per-loop-count (see `MAX_UNIFY_DEBUG_LOOPS` in
84
+ * bound is per-loop-count (see `MAX_YEAFT_DEBUG_LOOPS` in
85
85
  * `web/stores/chat.js`), not per-payload mutilation here.
86
86
  *
87
87
  * Pure function — no side effects on the input message.
@@ -569,8 +569,8 @@ export class Engine {
569
569
  groupId
570
570
  ? readScopeSummary({ kind: 'group', id: groupId }, { root: memoryRoot, language }).catch(() => '')
571
571
  : Promise.resolve(''),
572
- vpId
573
- ? readScopeSummary({ kind: 'vp', id: vpId }, { root: memoryRoot, language }).catch(() => '')
572
+ vpId && groupId
573
+ ? readScopeSummary({ kind: 'group-vp', groupId, id: vpId }, { root: memoryRoot, language }).catch(() => '')
574
574
  : Promise.resolve(''),
575
575
  ];
576
576
  const [user, group, vp] = await Promise.all(tasks);
@@ -790,7 +790,7 @@ export class Engine {
790
790
  // Worker-shape harness is descriptive metadata for human inspection;
791
791
  // production prompts skip it to save tokens. Re-enable via env when
792
792
  // diagnosing prompt structure issues.
793
- includeShape: process.env.UNIFY_PROMPT_INCLUDE_SHAPE === '1',
793
+ includeShape: process.env.YEAFT_PROMPT_INCLUDE_SHAPE === '1',
794
794
  });
795
795
  }
796
796
 
@@ -888,9 +888,9 @@ export class Engine {
888
888
  // ViewImage (task-333b PR-B rev-3 P1-A): expose size cap + allowlist
889
889
  // via tool ctx so hosts can override via ~/.yeaft/config.json without
890
890
  // touching the tool impl.
891
- maxImageBytes: this.#config?.unify?.maxImageBytes,
892
- imageAllowlist: Array.isArray(this.#config?.unify?.imageAllowlist)
893
- ? this.#config.unify.imageAllowlist
891
+ maxImageBytes: this.#config?.yeaft?.maxImageBytes,
892
+ imageAllowlist: Array.isArray(this.#config?.yeaft?.imageAllowlist)
893
+ ? this.#config.yeaft.imageAllowlist
894
894
  : [],
895
895
  // Bug 4 fix — VP / routing context for RouteForward (and any other
896
896
  // VP-aware tool). Undefined when running in non-group / no-VP flows.
@@ -2735,7 +2735,7 @@ export class Engine {
2735
2735
  /**
2736
2736
  * Run a one-shot fast-model call to produce a compact summary.
2737
2737
  * Used by the web bridge's in-memory history compactor
2738
- * (`agent/unify/history-compact.js`) — kept on the engine so callers
2738
+ * (`agent/yeaft/history-compact.js`) — kept on the engine so callers
2739
2739
  * don't reach into the private adapter field.
2740
2740
  *
2741
2741
  * @param {{system: string, prompt: string, maxTokens?: number}} args
@@ -5,22 +5,22 @@
5
5
  *
6
6
  * Usage:
7
7
  * # Run all evals against default model (requires API key)
8
- * node agent/unify/eval/run-eval.js
8
+ * node agent/yeaft/eval/run-eval.js
9
9
  *
10
10
  * # Run specific suite
11
- * node agent/unify/eval/run-eval.js --suite tools
11
+ * node agent/yeaft/eval/run-eval.js --suite tools
12
12
  *
13
13
  * # Compare multiple models
14
- * node agent/unify/eval/run-eval.js --models claude-sonnet-4-20250514,gpt-5
14
+ * node agent/yeaft/eval/run-eval.js --models claude-sonnet-4-20250514,gpt-5
15
15
  *
16
16
  * # Save baseline
17
- * node agent/unify/eval/run-eval.js --save-baseline initial
17
+ * node agent/yeaft/eval/run-eval.js --save-baseline initial
18
18
  *
19
19
  * # Compare against baseline
20
- * node agent/unify/eval/run-eval.js --compare-baseline baselines/initial.json
20
+ * node agent/yeaft/eval/run-eval.js --compare-baseline baselines/initial.json
21
21
  *
22
22
  * # Dry run (MockAdapter, no API calls)
23
- * node agent/unify/eval/run-eval.js --dry-run
23
+ * node agent/yeaft/eval/run-eval.js --dry-run
24
24
  *
25
25
  * Environment:
26
26
  * YEAFT_API_KEY — Anthropic API key
@@ -62,7 +62,7 @@ if (args.help) {
62
62
  Yeaft Eval Runner
63
63
 
64
64
  Usage:
65
- node agent/unify/eval/run-eval.js [options]
65
+ node agent/yeaft/eval/run-eval.js [options]
66
66
 
67
67
  Options:
68
68
  -s, --suite <name> Run specific suite: tools, memory, skills, e2e, all (default: all)
@@ -78,16 +78,16 @@ Environment:
78
78
 
79
79
  Examples:
80
80
  # Quick dry run to verify harness works
81
- node agent/unify/eval/run-eval.js --dry-run
81
+ node agent/yeaft/eval/run-eval.js --dry-run
82
82
 
83
83
  # Run tool evals against Claude Sonnet
84
- node agent/unify/eval/run-eval.js --suite tools --models claude-sonnet-4-20250514
84
+ node agent/yeaft/eval/run-eval.js --suite tools --models claude-sonnet-4-20250514
85
85
 
86
86
  # Full eval, save baseline
87
- node agent/unify/eval/run-eval.js --save-baseline v0.1.411
87
+ node agent/yeaft/eval/run-eval.js --save-baseline v0.1.411
88
88
 
89
89
  # Check for regressions
90
- node agent/unify/eval/run-eval.js --compare-baseline baselines/v0.1.411.json
90
+ node agent/yeaft/eval/run-eval.js --compare-baseline baselines/v0.1.411.json
91
91
  `);
92
92
  process.exit(0);
93
93
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * eval/runner.js — Eval harness for Yeaft Unify
2
+ * eval/runner.js — Eval harness for Yeaft Yeaft
3
3
  *
4
4
  * Runs eval cases against one or more models and produces scored results.
5
5
  * Each eval case defines:
@@ -1,5 +1,5 @@
1
1
  /**
2
- * feature-flag.js — Reads `config.unify.multiVp.enabled` from ~/.yeaft/config.json.
2
+ * feature-flag.js — Reads `config.yeaft.multiVp.enabled` from ~/.yeaft/config.json.
3
3
  *
4
4
  * Per architecture §11: multi-VP group mode is opt-in for MVP. The flag
5
5
  * gates UI entry points and (later) migration. This module returns a plain
@@ -14,7 +14,7 @@ import { join } from 'path';
14
14
  import { writeAtomic } from '../storage/index.js';
15
15
 
16
16
  const CONFIG_FILE = 'config.json';
17
- const FLAG_PATH = ['unify', 'multiVp', 'enabled'];
17
+ const FLAG_PATH = ['yeaft', 'multiVp', 'enabled'];
18
18
 
19
19
  function readConfig(yeaftDir) {
20
20
  const path = join(yeaftDir, CONFIG_FILE);
@@ -6,10 +6,10 @@
6
6
  * - Roster mutation helpers
7
7
  * - Coordinator that parses @-mentions on USER messages and dispatches to
8
8
  * target RoleInstances via a caller-supplied `deliver(vpId, envelope)`.
9
- * - Feature flag reader for `unify.multiVp.enabled`
9
+ * - Feature flag reader for `yeaft.multiVp.enabled`
10
10
  * - First-boot default group seeder
11
11
  *
12
- * See agent/unify/groups/coordinator.js for the dispatch contract.
12
+ * See agent/yeaft/groups/coordinator.js for the dispatch contract.
13
13
  */
14
14
 
15
15
  export {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * groups/pre-flow.js — explicit pre-flow stage for Unify.
2
+ * groups/pre-flow.js — explicit pre-flow stage for Yeaft.
3
3
  *
4
4
  * Pre-flow is the "before any VP runs" stage. It owns:
5
5
  *
@@ -167,6 +167,15 @@ export function selectRespondingVps(input) {
167
167
  */
168
168
  function scopeHeading(scope) {
169
169
  if (scope === 'user') return '## Memory: User';
170
+ // Nested group scopes first.
171
+ let m = /^group\/([^/]+)\/vp\/(.+)$/.exec(scope);
172
+ if (m) return `## Memory: VP ${m[2]}`;
173
+ m = /^group\/([^/]+)\/user$/.exec(scope);
174
+ if (m) return `## Memory: Group ${m[1]} (user)`;
175
+ m = /^group\/([^/]+)\/feature\/(.+)$/.exec(scope);
176
+ if (m) return `## Memory: Feature ${m[2]}`;
177
+ m = /^group\/([^/]+)\/topic\/(.+)$/.exec(scope);
178
+ if (m) return `## Memory: Topic ${m[2]}`;
170
179
  if (scope.startsWith('group/')) return `## Memory: Group ${scope.slice(6)}`;
171
180
  if (scope.startsWith('vp/')) return `## Memory: VP ${scope.slice(3)}`;
172
181
  if (scope.startsWith('feature/')) return `## Memory: Feature ${scope.slice(8)}`;
@@ -238,8 +247,11 @@ export function formatPickedForInjection(picked) {
238
247
  */
239
248
  export function buildRelevantScopes({ groupId, vpId, extra } = {}) {
240
249
  const scopes = ['user'];
241
- if (groupId) scopes.push(`group/${groupId}`);
242
- if (vpId) scopes.push(`vp/${vpId}`);
250
+ if (groupId) {
251
+ scopes.push(`group/${groupId}`);
252
+ scopes.push(`group/${groupId}/user`);
253
+ if (vpId) scopes.push(`group/${groupId}/vp/${vpId}`);
254
+ }
243
255
  if (Array.isArray(extra)) {
244
256
  for (const s of extra) {
245
257
  if (s && !scopes.includes(s)) scopes.push(s);
@@ -140,7 +140,7 @@ export function readProjectDoc(workDir, opts = {}) {
140
140
  const text = buffer.toString('utf8', 0, useBytes).trim();
141
141
  if (truncated) {
142
142
  console.warn(
143
- `[unify/project-doc] ${picked.path} exceeds ${maxBytes} bytes — truncated.`,
143
+ `[yeaft/project-doc] ${picked.path} exceeds ${maxBytes} bytes — truncated.`,
144
144
  );
145
145
  }
146
146
  if (!text) return null;
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * history-compact.js — In-memory conversation history compaction for the
3
- * Unify group-chat fan-out path.
3
+ * Yeaft group-chat fan-out path.
4
4
  *
5
5
  * Problem this solves:
6
- * `agent/unify/web-bridge.js` keeps a flat module-level array
6
+ * `agent/yeaft/web-bridge.js` keeps a flat module-level array
7
7
  * `conversationMessages` that grows unbounded across the lifetime of the
8
8
  * agent process. Every fan-out turn snapshots the whole thing into
9
9
  * `baseSnapshot` and feeds it to `engine.query` for every VP. Without a
10
10
  * cap, prompt size and token cost grow linearly with conversation length.
11
11
  *
12
- * Existing infrastructure (`agent/unify/compact/orchestrator.js`,
12
+ * Existing infrastructure (`agent/yeaft/compact/orchestrator.js`,
13
13
  * `engine.js#runOrchestratorCompact`) compacts the on-disk
14
14
  * `conversationStore` — a different surface. This helper compacts the
15
15
  * in-memory array that actually gets passed to the LLM.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * index.js — Yeaft Unify module entry point
2
+ * index.js — Yeaft Yeaft module entry point
3
3
  *
4
4
  * Re-exports all public APIs for external consumption.
5
5
  */
@@ -2,7 +2,7 @@
2
2
  * init.js — Yeaft directory structure initialization
3
3
  *
4
4
  * Ensures ~/.yeaft/ and all required subdirectories exist.
5
- * Creates default config.md, MEMORY.md, and conversation/index.md if missing.
5
+ * Creates default config.md, MEMORY.md, chat/index.md, and group/index.md if missing.
6
6
  */
7
7
 
8
8
  import { existsSync, mkdirSync, writeFileSync, accessSync, constants } from 'fs';
@@ -74,9 +74,13 @@ export const DEFAULT_YEAFT_DIR = join(homedir(), '.yeaft');
74
74
 
75
75
  /** Subdirectories that must exist inside the Yeaft data directory. */
76
76
  const SUBDIRS = [
77
- 'conversation/messages',
78
- 'conversation/cold',
79
- 'conversation/blobs',
77
+ 'chat/messages',
78
+ 'chat/cold',
79
+ 'chat/blobs',
80
+ 'group/messages',
81
+ 'group/cold',
82
+ 'group/blobs',
83
+ 'group/compact',
80
84
  'memory/entries',
81
85
  'tasks',
82
86
  'skills',
@@ -144,7 +148,7 @@ totalMessages: 0
144
148
 
145
149
  # Conversation Index
146
150
 
147
- This file tracks the conversation state for the "one eternal conversation" model.
151
+ This file tracks one Yeaft message-history mode.
148
152
  `;
149
153
 
150
154
  /**
@@ -196,10 +200,12 @@ export function initYeaftDir(dir) {
196
200
  created.push(memoryPath);
197
201
  }
198
202
 
199
- const indexPath = join(root, 'conversation', 'index.md');
200
- if (!existsSync(indexPath)) {
201
- safeWriteFile(indexPath, DEFAULT_CONVERSATION_INDEX, warnings);
202
- created.push(indexPath);
203
+ for (const mode of ['chat', 'group']) {
204
+ const indexPath = join(root, mode, 'index.md');
205
+ if (!existsSync(indexPath)) {
206
+ safeWriteFile(indexPath, DEFAULT_CONVERSATION_INDEX, warnings);
207
+ created.push(indexPath);
208
+ }
203
209
  }
204
210
 
205
211
  // mcp.json.example — reference template for MCP server configuration
@@ -120,7 +120,7 @@ export class LLMAbortError extends Error {
120
120
  * sent to the LLM. A truncated copy is worse than useless — it lies about
121
121
  * what the model saw. If the resulting payload is too large for the debug
122
122
  * store, the fix is to bound retention (drop oldest turns), not to mutilate
123
- * individual payloads. See `MAX_UNIFY_DEBUG_LOOPS` in `web/stores/chat.js`.
123
+ * individual payloads. See `MAX_YEAFT_DEBUG_LOOPS` in `web/stores/chat.js`.
124
124
  *
125
125
  * @param {{ url: string, method: string, headers: object, body: any }} req
126
126
  * @returns {{ url: string, method: string, headers: object, body: any }}
@@ -24,10 +24,10 @@ import {
24
24
 
25
25
  /**
26
26
  * task-327a: feature-flag accessor. thinkingV1 is OFF by default; set
27
- * env UNIFY_THINKING_V1=1 to enable. Read lazily so tests can flip.
27
+ * env YEAFT_THINKING_V1=1 to enable. Read lazily so tests can flip.
28
28
  */
29
29
  function thinkingV1Enabled() {
30
- return process.env.UNIFY_THINKING_V1 === '1';
30
+ return process.env.YEAFT_THINKING_V1 === '1';
31
31
  }
32
32
 
33
33
  const DEFAULT_BASE_URL = 'https://api.anthropic.com';