@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
@@ -25,6 +25,7 @@ const NETWORK_TIMEOUT_MS = 15000;
25
25
  const STALE_GRACE_MS = 5 * 60 * 1000; // 5 minutes when serving stale-after-failure
26
26
 
27
27
  let _memCache = null;
28
+ let _memCachePath = null;
28
29
  let _memCacheTime = 0;
29
30
 
30
31
  function getCachePath(yeaftDir) {
@@ -71,8 +72,10 @@ async function saveDiskCache(yeaftDir, data) {
71
72
  * @returns {Promise<object>} provider-id → provider entry; {} on total failure.
72
73
  */
73
74
  export async function fetchModelsDev({ forceRefresh = false, yeaftDir = null } = {}) {
75
+ const cachePath = getCachePath(yeaftDir);
76
+
74
77
  // Stage 1: in-memory cache.
75
- if (!forceRefresh && _memCache && Date.now() - _memCacheTime < CACHE_TTL_MS) {
78
+ if (!forceRefresh && _memCache && _memCachePath === cachePath && Date.now() - _memCacheTime < CACHE_TTL_MS) {
76
79
  return _memCache;
77
80
  }
78
81
 
@@ -83,6 +86,7 @@ export async function fetchModelsDev({ forceRefresh = false, yeaftDir = null } =
83
86
  const data = await loadDiskCache(yeaftDir);
84
87
  if (data && typeof data === 'object') {
85
88
  _memCache = data;
89
+ _memCachePath = cachePath;
86
90
  _memCacheTime = Date.now() - age;
87
91
  return _memCache;
88
92
  }
@@ -99,6 +103,7 @@ export async function fetchModelsDev({ forceRefresh = false, yeaftDir = null } =
99
103
  const data = await res.json();
100
104
  if (data && typeof data === 'object') {
101
105
  _memCache = data;
106
+ _memCachePath = cachePath;
102
107
  _memCacheTime = Date.now();
103
108
  await saveDiskCache(yeaftDir, data);
104
109
  return data;
@@ -111,15 +116,16 @@ export async function fetchModelsDev({ forceRefresh = false, yeaftDir = null } =
111
116
  }
112
117
 
113
118
  // Stage 4: stale disk cache fallback with short grace TTL so we retry soon.
114
- if (!_memCache) {
119
+ if (!_memCache || _memCachePath !== cachePath) {
115
120
  const data = await loadDiskCache(yeaftDir);
116
121
  if (data && typeof data === 'object') {
117
122
  _memCache = data;
123
+ _memCachePath = cachePath;
118
124
  _memCacheTime = Date.now() - CACHE_TTL_MS + STALE_GRACE_MS;
119
125
  }
120
126
  }
121
127
 
122
- return _memCache || {};
128
+ return _memCachePath === cachePath ? (_memCache || {}) : {};
123
129
  }
124
130
 
125
131
  /**
@@ -156,5 +162,6 @@ export async function listProviderModels(providerId, { yeaftDir = null } = {}) {
156
162
  */
157
163
  export function _resetMemCache() {
158
164
  _memCache = null;
165
+ _memCachePath = null;
159
166
  _memCacheTime = 0;
160
167
  }
@@ -43,12 +43,12 @@ import {
43
43
  const DEFAULT_BASE_URL = 'https://api.openai.com/v1';
44
44
 
45
45
  /**
46
- * Feature-flag accessor mirroring anthropic.js. UNIFY_THINKING_V1 is OFF by
46
+ * Feature-flag accessor mirroring anthropic.js. YEAFT_THINKING_V1 is OFF by
47
47
  * default; set env to '1' to enable thinking-mode field translation. Read
48
48
  * lazily so tests can flip the flag between calls.
49
49
  */
50
50
  function thinkingV1Enabled() {
51
- return process.env.UNIFY_THINKING_V1 === '1';
51
+ return process.env.YEAFT_THINKING_V1 === '1';
52
52
  }
53
53
 
54
54
  /**
@@ -78,7 +78,7 @@ export function inferProtocolFromModelId(modelId) {
78
78
  * task-327a: feature-flag accessor. Read lazily so tests can flip.
79
79
  */
80
80
  function thinkingV1Enabled() {
81
- return process.env.UNIFY_THINKING_V1 === '1';
81
+ return process.env.YEAFT_THINKING_V1 === '1';
82
82
  }
83
83
 
84
84
  /**
@@ -19,7 +19,7 @@
19
19
  *
20
20
  * Each MCP server communicates via stdio JSON-RPC (MCP protocol).
21
21
  *
22
- * Reference: yeaft-unify-design.md §8, yeaft-unify-core-systems.md §3.2
22
+ * Reference: yeaft-yeaft-design.md §8, yeaft-yeaft-core-systems.md §3.2
23
23
  */
24
24
 
25
25
  import { spawn } from 'child_process';
@@ -28,6 +28,7 @@
28
28
  */
29
29
 
30
30
  import { approxTokens } from './budget.js';
31
+ import { isVpForeign } from './store-v2.js';
31
32
 
32
33
  /**
33
34
  * @typedef {object} AdjustTriggerInput
@@ -104,10 +105,7 @@ function firstSentence(body) {
104
105
  }
105
106
 
106
107
  function isOwnOrNonVp(scope, ownVpId) {
107
- if (!scope.startsWith('vp/')) return true;
108
- if (!ownVpId) return true;
109
- const other = scope.slice(3).split('/')[0];
110
- return other === ownVpId;
108
+ return !isVpForeign(scope, ownVpId);
111
109
  }
112
110
 
113
111
  /**
@@ -24,7 +24,7 @@
24
24
  * the next time the group is opened. Resident layer is derived state
25
25
  * (rebuilt every turn from `<scope>/summary.md`) — never persisted.
26
26
  *
27
- * For the single-VP Unify path (no group), the registry uses the literal
27
+ * For the single-VP Yeaft path (no group), the registry uses the literal
28
28
  * key `"default"` so there's still a stable home for AMS state.
29
29
  */
30
30
 
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import { approxTokens, packWithinBudget } from './budget.js';
20
+ import { isVpForeign } from './store-v2.js';
20
21
 
21
22
  const RECENT_DEFAULT_CAPACITY = 64;
22
23
 
@@ -189,9 +190,6 @@ export class ActiveMemorySet {
189
190
  // ────────────────────────── privacy ──────────────────────────
190
191
 
191
192
  _isForeignVp(scope) {
192
- if (!scope || !scope.startsWith('vp/')) return false;
193
- if (!this.ownVpId) return false; // no own id → no filtering
194
- const other = scope.slice(3).split('/')[0];
195
- return other !== this.ownVpId;
193
+ return isVpForeign(scope, this.ownVpId);
196
194
  }
197
195
  }
@@ -17,6 +17,7 @@
17
17
 
18
18
  import { extractKeywords } from './keywords.js';
19
19
  import { approxTokens } from './budget.js';
20
+ import { isVpForeign } from './store-v2.js';
20
21
 
21
22
  /**
22
23
  * @typedef {object} PreflowOptions
@@ -118,12 +119,7 @@ export function buildFtsQuery(keywords) {
118
119
  * @returns {string[]}
119
120
  */
120
121
  export function filterScopes(scopes, ownVpId) {
121
- return scopes.filter(s => {
122
- if (!s.startsWith('vp/')) return true;
123
- if (!ownVpId) return true;
124
- const other = s.slice(3).split('/')[0];
125
- return other === ownVpId;
126
- });
122
+ return scopes.filter(s => !isVpForeign(s, ownVpId));
127
123
  }
128
124
 
129
125
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * memory/seed-backfill.js — Run-once backfill of `summary.md` for VPs and
3
3
  * groups that were created BEFORE the create-time seed was added (PR
4
- * "fix-unify-context-and-memory"). Without backfill, an existing user's
4
+ * "fix-yeaft-context-and-memory"). Without backfill, an existing user's
5
5
  * `grp_claude` group and `steve` VP never get a Layer-A resident summary —
6
6
  * `engine.#prepareAms` then renders an empty memory section every turn,
7
7
  * which is the user-visible Bug #2.
@@ -15,7 +15,7 @@
15
15
  * a permission error must NEVER prevent the session from loading.
16
16
  */
17
17
 
18
- import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync, renameSync } from 'fs';
19
19
  import { join } from 'path';
20
20
  import { homedir } from 'os';
21
21
  import { parseRoleMd } from '../vp/vp-store.js';
@@ -292,3 +292,43 @@ export function runSummaryBackfill({ yeaftDir, libDir, root = DEFAULT_MEMORY_ROO
292
292
  }
293
293
  return { migrate, vp, group };
294
294
  }
295
+
296
+ /**
297
+ * archiveLegacyScopes(root) — one-shot migration for the group-isolated
298
+ * memory refactor. The legacy flat layout had `vp/<id>/`, `feature/<id>/`,
299
+ * and `topic/<l1>[/<l2>]/` directories at the memory root; the new layout
300
+ * tucks each into `group/<g>/{vp,feature,topic}/...`. Per user directive
301
+ * "硬切,老的就不要了" — we do NOT migrate per-record, we just move the
302
+ * top-level dirs to `<root>/.legacy/<kind>/` once. They are never read
303
+ * again; this is forensics-only.
304
+ *
305
+ * Idempotent: a second invocation is a no-op when no legacy dirs remain at
306
+ * the root. If `.legacy/<kind>/` already exists, the new move is suffixed
307
+ * with a timestamp so re-attempts after a partial first run don't clobber.
308
+ *
309
+ * @param {string} root memory root (typically <yeaftDir>/memory)
310
+ * @returns {{moved: string[]}}
311
+ */
312
+ export function archiveLegacyScopes(root) {
313
+ const moved = [];
314
+ if (!root || !existsSync(root)) return { moved };
315
+ const legacyRoot = join(root, '.legacy');
316
+ for (const kind of ['vp', 'feature', 'topic']) {
317
+ const src = join(root, kind);
318
+ if (!existsSync(src)) continue;
319
+ try {
320
+ mkdirSync(legacyRoot, { recursive: true });
321
+ let dst = join(legacyRoot, kind);
322
+ if (existsSync(dst)) {
323
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
324
+ dst = `${dst}.${ts}`;
325
+ }
326
+ // eslint-disable-next-line global-require
327
+ renameSync(src, dst);
328
+ moved.push(kind);
329
+ } catch (err) {
330
+ console.warn(`[seed-backfill] archiveLegacyScopes(${kind}) failed:`, err?.message || err);
331
+ }
332
+ }
333
+ return { moved };
334
+ }
@@ -51,7 +51,7 @@ export const KIND_VALUES = new Set([
51
51
  'fact', 'preference', 'decision', 'lesson', 'relation', 'goal', 'context',
52
52
  ]);
53
53
 
54
- const SCOPE_RE = /^(user|vp\/[\w-]+|group\/[\w-]+|feature\/[\w-]+|topic\/[\w-]+(?:\/[\w-]+)?)$/;
54
+ const SCOPE_RE = /^(user|group\/[\w-]+(?:\/(?:user|vp\/[\w-]+|feature\/[\w-]+|topic\/[\w-]+(?:\/[\w-]+)?))?)$/;
55
55
 
56
56
  /**
57
57
  * Compute a stable id from segment content. Same body + scope + kind →
@@ -37,7 +37,7 @@
37
37
  * by `dream-v2/state.js`, not here.
38
38
  * - No LLM calls, no extraction, no summarisation. Pure I/O.
39
39
  *
40
- * Reference: agent/unify/memory/DESIGN-H2-AMS.md.
40
+ * Reference: agent/yeaft/memory/DESIGN-H2-AMS.md.
41
41
  */
42
42
 
43
43
  import {
@@ -54,16 +54,24 @@ import { homedir } from 'os';
54
54
  /** Default memory root. Tests override via `opts.root`. */
55
55
  export const DEFAULT_MEMORY_ROOT = join(homedir(), '.yeaft', 'memory');
56
56
 
57
- /** Scope kinds recognised by v2. */
58
- export const SCOPE_KINDS = Object.freeze(['user', 'vp', 'group', 'feature', 'topic']);
57
+ /** Scope kinds recognised by v2 (group-isolated layout). */
58
+ export const SCOPE_KINDS = Object.freeze([
59
+ 'user',
60
+ 'group',
61
+ 'group-user',
62
+ 'group-vp',
63
+ 'group-feature',
64
+ 'group-topic',
65
+ ]);
59
66
 
60
- /** @typedef {'user'|'vp'|'group'|'feature'|'topic'} ScopeKind */
67
+ /** @typedef {'user'|'group'|'group-user'|'group-vp'|'group-feature'|'group-topic'} ScopeKind */
61
68
 
62
69
  /**
63
70
  * @typedef {Object} Scope
64
71
  * @property {ScopeKind} kind
65
- * @property {string} [id] — required for vp / group / feature
66
- * @property {string[]} [path] — required for topic; 1–2 segments
72
+ * @property {string} [id] — required for group; for group-vp / group-feature the per-kind id
73
+ * @property {string} [groupId] — required for every group-* kind
74
+ * @property {string[]} [path] — required for group-topic; 1–2 segments
67
75
  */
68
76
 
69
77
  /**
@@ -81,25 +89,38 @@ export function scopeDir(scope) {
81
89
  switch (scope.kind) {
82
90
  case 'user':
83
91
  return 'user';
84
- case 'vp':
85
- if (!scope.id) throw new Error('scopeDir: vp scope requires id');
86
- assertSafeSegment(scope.id, 'vp.id');
87
- return `vp/${scope.id}`;
88
92
  case 'group':
89
93
  if (!scope.id) throw new Error('scopeDir: group scope requires id');
90
94
  assertSafeSegment(scope.id, 'group.id');
91
95
  return `group/${scope.id}`;
92
- case 'feature':
93
- if (!scope.id) throw new Error('scopeDir: feature scope requires id');
94
- assertSafeSegment(scope.id, 'feature.id');
95
- return `feature/${scope.id}`;
96
- case 'topic': {
96
+ case 'group-user': {
97
+ if (!scope.groupId) throw new Error('scopeDir: group-user scope requires groupId');
98
+ assertSafeSegment(scope.groupId, 'group-user.groupId');
99
+ return `group/${scope.groupId}/user`;
100
+ }
101
+ case 'group-vp': {
102
+ if (!scope.groupId) throw new Error('scopeDir: group-vp scope requires groupId');
103
+ if (!scope.id) throw new Error('scopeDir: group-vp scope requires id');
104
+ assertSafeSegment(scope.groupId, 'group-vp.groupId');
105
+ assertSafeSegment(scope.id, 'group-vp.id');
106
+ return `group/${scope.groupId}/vp/${scope.id}`;
107
+ }
108
+ case 'group-feature': {
109
+ if (!scope.groupId) throw new Error('scopeDir: group-feature scope requires groupId');
110
+ if (!scope.id) throw new Error('scopeDir: group-feature scope requires id');
111
+ assertSafeSegment(scope.groupId, 'group-feature.groupId');
112
+ assertSafeSegment(scope.id, 'group-feature.id');
113
+ return `group/${scope.groupId}/feature/${scope.id}`;
114
+ }
115
+ case 'group-topic': {
116
+ if (!scope.groupId) throw new Error('scopeDir: group-topic scope requires groupId');
117
+ assertSafeSegment(scope.groupId, 'group-topic.groupId');
97
118
  const segs = Array.isArray(scope.path) ? scope.path : [];
98
119
  if (segs.length === 0 || segs.length > 2) {
99
- throw new Error('scopeDir: topic.path must have 1 or 2 segments');
120
+ throw new Error('scopeDir: group-topic.path must have 1 or 2 segments');
100
121
  }
101
- for (const s of segs) assertSafeSegment(s, 'topic.path');
102
- return `topic/${segs.join('/')}`;
122
+ for (const s of segs) assertSafeSegment(s, 'group-topic.path');
123
+ return `group/${scope.groupId}/topic/${segs.join('/')}`;
103
124
  }
104
125
  default:
105
126
  throw new Error(`scopeDir: unknown kind ${JSON.stringify(scope.kind)}`);
@@ -140,7 +161,8 @@ function assertSafeSegment(s, ctx) {
140
161
  * @returns {boolean}
141
162
  */
142
163
  export function isValidTopic(scope) {
143
- if (!scope || scope.kind !== 'topic') return false;
164
+ if (!scope || scope.kind !== 'group-topic') return false;
165
+ if (!scope.groupId || typeof scope.groupId !== 'string') return false;
144
166
  if (!Array.isArray(scope.path)) return false;
145
167
  if (scope.path.length < 1 || scope.path.length > 2) return false;
146
168
  for (const s of scope.path) {
@@ -155,7 +177,9 @@ export function isValidTopic(scope) {
155
177
  // ─── ACL ───────────────────────────────────────────────────────
156
178
 
157
179
  /**
158
- * The single ACL: `vp/<other>` is foreign when `currentVpId` is given.
180
+ * The single ACL: `group/<g>/vp/<other>` is foreign when `currentVpId` is given.
181
+ * Across groups, every `group/<g>/vp/...` path is foreign by construction
182
+ * (the calling VP only runs inside its own group dir).
159
183
  *
160
184
  * @param {string} relPath
161
185
  * @param {string} currentVpId
@@ -163,7 +187,7 @@ export function isValidTopic(scope) {
163
187
  */
164
188
  export function isVpForeign(relPath, currentVpId) {
165
189
  if (!relPath || !currentVpId) return false;
166
- const m = /^vp\/([^/]+)(?:\/|$)/.exec(relPath);
190
+ const m = /^group\/[^/]+\/vp\/([^/]+)(?:\/|$)/.exec(relPath);
167
191
  if (!m) return false;
168
192
  return m[1] !== currentVpId;
169
193
  }
@@ -403,18 +427,17 @@ export async function ensureScope(scope, opts = {}) {
403
427
 
404
428
  /**
405
429
  * Enumerate all scopes present on disk. Returns Scope shapes that round-trip
406
- * back through `scopeDir`. Used by Triage to list candidate
407
- * scopes for a group's diff.
430
+ * back through `scopeDir`. Used by Triage to list candidate scopes.
408
431
  *
409
432
  * Walks shallowly:
410
- * user/ → { kind: 'user' }
411
- * vp/<id>/ → { kind: 'vp', id }
412
- * group/<id>/ → { kind: 'group', id }
413
- * feature/<id>/ → { kind: 'feature', id }
414
- * topic/<l1>/[<l2>/] → { kind: 'topic', path: [...] }
433
+ * user/ → { kind: 'user' }
434
+ * group/<g>/ → { kind: 'group', id: g }
435
+ * group/<g>/user/ → { kind: 'group-user', groupId: g }
436
+ * group/<g>/vp/<v>/ → { kind: 'group-vp', groupId: g, id: v }
437
+ * group/<g>/feature/<f>/ → { kind: 'group-feature', groupId: g, id: f }
438
+ * group/<g>/topic/<l1>[/<l2>]/ → { kind: 'group-topic', groupId: g, path: [...] }
415
439
  *
416
- * Skips entries that are not directories, and any name that fails segment
417
- * validation (e.g. accidental `.tmp.*` files at scope root, dotfiles).
440
+ * Skips `.legacy/` and any dotfile / unsafe segment.
418
441
  *
419
442
  * @param {{ root?: string }} [opts]
420
443
  * @returns {Promise<Scope[]>}
@@ -427,53 +450,77 @@ export async function listScopes(opts = {}) {
427
450
  // user/
428
451
  if (existsSync(join(root, 'user'))) out.push({ kind: 'user' });
429
452
 
430
- // vp/, group/, feature/ — single-level ids
431
- for (const kind of ['vp', 'group', 'feature']) {
432
- const dir = join(root, kind);
433
- let names;
434
- try { names = await fsp.readdir(dir, { withFileTypes: true }); }
435
- catch (err) {
436
- if (err && err.code === 'ENOENT') continue;
437
- throw err;
453
+ // group/<g>/...
454
+ const groupRoot = join(root, 'group');
455
+ let groups;
456
+ try { groups = await fsp.readdir(groupRoot, { withFileTypes: true }); }
457
+ catch (err) {
458
+ if (err && err.code === 'ENOENT') return out;
459
+ throw err;
460
+ }
461
+
462
+ for (const gent of groups) {
463
+ if (!gent.isDirectory()) continue;
464
+ if (gent.name.startsWith('.')) continue;
465
+ if (!isSafeId(gent.name)) continue;
466
+ const g = gent.name;
467
+ out.push({ kind: 'group', id: g });
468
+ const gAbs = join(groupRoot, g);
469
+
470
+ // group/<g>/user/
471
+ if (existsSync(join(gAbs, 'user'))) {
472
+ out.push({ kind: 'group-user', groupId: g });
438
473
  }
439
- for (const ent of names) {
440
- if (!ent.isDirectory()) continue;
441
- const id = ent.name;
442
- if (!isSafeId(id)) continue;
443
- out.push({ kind, id });
474
+
475
+ // group/<g>/vp/<v>/ and group/<g>/feature/<f>/
476
+ for (const kind of ['vp', 'feature']) {
477
+ const dir = join(gAbs, kind);
478
+ let names;
479
+ try { names = await fsp.readdir(dir, { withFileTypes: true }); }
480
+ catch (err) {
481
+ if (err && err.code === 'ENOENT') continue;
482
+ throw err;
483
+ }
484
+ for (const ent of names) {
485
+ if (!ent.isDirectory()) continue;
486
+ if (!isSafeId(ent.name)) continue;
487
+ out.push({
488
+ kind: kind === 'vp' ? 'group-vp' : 'group-feature',
489
+ groupId: g,
490
+ id: ent.name,
491
+ });
492
+ }
444
493
  }
445
- }
446
494
 
447
- // topic/<l1>/[<l2>/]
448
- const topicDir = join(root, 'topic');
449
- let l1s;
450
- try { l1s = await fsp.readdir(topicDir, { withFileTypes: true }); }
451
- catch (err) {
452
- if (err && err.code === 'ENOENT') l1s = [];
453
- else throw err;
454
- }
455
- for (const l1ent of l1s) {
456
- if (!l1ent.isDirectory()) continue;
457
- if (!isSafeId(l1ent.name)) continue;
458
- const l1 = l1ent.name;
459
- // Read l2 entries; if l1 itself contains memory.md, treat as 1-level topic
460
- const l1abs = join(topicDir, l1);
461
- let l2s;
462
- try { l2s = await fsp.readdir(l1abs, { withFileTypes: true }); }
463
- catch { l2s = []; }
464
- let hasL2 = false;
465
- for (const l2ent of l2s) {
466
- if (!l2ent.isDirectory()) continue;
467
- if (!isSafeId(l2ent.name)) continue;
468
- out.push({ kind: 'topic', path: [l1, l2ent.name] });
469
- hasL2 = true;
495
+ // group/<g>/topic/<l1>/[<l2>/]
496
+ const topicDir = join(gAbs, 'topic');
497
+ let l1s;
498
+ try { l1s = await fsp.readdir(topicDir, { withFileTypes: true }); }
499
+ catch (err) {
500
+ if (err && err.code === 'ENOENT') l1s = [];
501
+ else throw err;
470
502
  }
471
- // 1-level topic: present iff l1 has memory.md or summary.md directly
472
- if (!hasL2) {
473
- const hasMemory = existsSync(join(l1abs, 'memory.md'));
474
- const hasSummary = existsSync(join(l1abs, 'summary.md'));
475
- if (hasMemory || hasSummary) {
476
- out.push({ kind: 'topic', path: [l1] });
503
+ for (const l1ent of l1s) {
504
+ if (!l1ent.isDirectory()) continue;
505
+ if (!isSafeId(l1ent.name)) continue;
506
+ const l1 = l1ent.name;
507
+ const l1abs = join(topicDir, l1);
508
+ let l2s;
509
+ try { l2s = await fsp.readdir(l1abs, { withFileTypes: true }); }
510
+ catch { l2s = []; }
511
+ let hasL2 = false;
512
+ for (const l2ent of l2s) {
513
+ if (!l2ent.isDirectory()) continue;
514
+ if (!isSafeId(l2ent.name)) continue;
515
+ out.push({ kind: 'group-topic', groupId: g, path: [l1, l2ent.name] });
516
+ hasL2 = true;
517
+ }
518
+ if (!hasL2) {
519
+ const hasMemory = existsSync(join(l1abs, 'memory.md'));
520
+ const hasSummary = existsSync(join(l1abs, 'summary.md'));
521
+ if (hasMemory || hasSummary) {
522
+ out.push({ kind: 'group-topic', groupId: g, path: [l1] });
523
+ }
477
524
  }
478
525
  }
479
526
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * models.js — Model ID registry for Yeaft Unify
2
+ * models.js — Model ID registry for Yeaft Yeaft
3
3
  *
4
4
  * Maps model IDs (e.g. "gpt-5", "claude-sonnet-4-20250514") to their
5
5
  * adapter type, API base URL, and capabilities.
@@ -305,7 +305,7 @@ export function parseModelRef(ref) {
305
305
  // ─── task-327a: thinking / reasoning capability ─────────────────
306
306
 
307
307
  /**
308
- * Valid effort levels accepted by Unify adapters.
308
+ * Valid effort levels accepted by Yeaft adapters.
309
309
  * @typedef {'low' | 'medium' | 'high' | 'max'} Effort
310
310
  */
311
311
 
@@ -325,7 +325,7 @@ export const ANTHROPIC_THINKING_BUDGETS = {
325
325
  };
326
326
 
327
327
  /**
328
- * Map a Unify effort level to the OpenAI reasoning.effort enum. OpenAI does
328
+ * Map a Yeaft effort level to the OpenAI reasoning.effort enum. OpenAI does
329
329
  * not expose a 'max' level — callers that pass 'max' get 'high' (the highest
330
330
  * available on that protocol). The router/engine should log this downgrade
331
331
  * but the adapter MUST NOT error.
@@ -3,8 +3,8 @@
3
3
  * slice so it can be safely fed to the LLM adapter.
4
4
  *
5
5
  * Why this exists:
6
- * `agent/unify/conversation/persist.js#loadRecentByGroup` and
7
- * `agent/unify/history-compact.js#compactHistory` both produce
6
+ * `agent/yeaft/conversation/persist.js#loadRecentByGroup` and
7
+ * `agent/yeaft/history-compact.js#compactHistory` both produce
8
8
  * sub-slices of a longer message stream. Both paths can — depending on
9
9
  * where the cut lands — produce one of two illegal shapes:
10
10
  * 1. A `role: 'tool'` message whose owning assistant `tool_use` is
@@ -5,7 +5,7 @@
5
5
  * model tier it runs on, and what system prompt it carries.
6
6
  *
7
7
  * Presets live as markdown files with YAML frontmatter in
8
- * `agent/unify/templates/personas/*.md`.
8
+ * `agent/yeaft/templates/personas/*.md`.
9
9
  */
10
10
 
11
11
  import { readFileSync, readdirSync } from 'fs';
@@ -4,7 +4,7 @@
4
4
  * Single source of truth for system prompts. Both engine.js and cli.js
5
5
  * import buildSystemPrompt() from here. Supports 'en' and 'zh'.
6
6
  *
7
- * Template files from agent/unify/templates/ are loaded once at startup
7
+ * Template files from agent/yeaft/templates/ are loaded once at startup
8
8
  * and used to enrich the system prompt beyond the hardcoded fallbacks.
9
9
  *
10
10
  * Concept layering (DESIGN-PROMPT §3):
@@ -6,7 +6,7 @@
6
6
  * - router: route_forward → coordinator.ingest wrapper with guard,
7
7
  * self-reject, task.members forwarding, and causedBy chain.
8
8
  *
9
- * See agent/unify/tools/route-forward.js for the VP-facing tool.
9
+ * See agent/yeaft/tools/route-forward.js for the VP-facing tool.
10
10
  */
11
11
 
12
12
  export {
@@ -22,7 +22,7 @@
22
22
  * (a) Does NOT touch RoleInstance state machine internals (that's 334c).
23
23
  * (b) Does NOT touch live-diff (334h).
24
24
  * (c) Persistence routes through group.appendMessage via Coordinator.
25
- * (d) Tool schema uses defineTool (agent/unify/tools/types.js).
25
+ * (d) Tool schema uses defineTool (agent/yeaft/tools/types.js).
26
26
  */
27
27
 
28
28
  import { resolveMemberId } from '../groups/roster.js';