@yeaft/webchat-agent 0.1.410 → 0.1.411

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.410",
3
+ "version": "0.1.411",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/unify/config.js CHANGED
@@ -267,3 +267,39 @@ export function loadConfig(overrides = {}) {
267
267
 
268
268
  return config;
269
269
  }
270
+
271
+ /**
272
+ * Load MCP server configuration from ~/.yeaft/mcp.json.
273
+ *
274
+ * JSON format (frontmatter parser can't handle nested objects):
275
+ * {
276
+ * "servers": [
277
+ * {
278
+ * "name": "github",
279
+ * "command": "npx",
280
+ * "args": ["@mcp/github"],
281
+ * "env": { "GITHUB_TOKEN": "ghp_..." }
282
+ * }
283
+ * ]
284
+ * }
285
+ *
286
+ * @param {string} yeaftDir — e.g. ~/.yeaft
287
+ * @returns {{ servers: object[] }}
288
+ */
289
+ export function loadMCPConfig(yeaftDir) {
290
+ const mcpPath = join(yeaftDir, 'mcp.json');
291
+ if (!existsSync(mcpPath)) return { servers: [] };
292
+
293
+ try {
294
+ const raw = readFileSync(mcpPath, 'utf8');
295
+ const parsed = JSON.parse(raw);
296
+ if (!parsed.servers || !Array.isArray(parsed.servers)) {
297
+ return { servers: [] };
298
+ }
299
+ // Each server must have at least name + command
300
+ const valid = parsed.servers.filter(s => s.name && s.command);
301
+ return { servers: valid };
302
+ } catch {
303
+ return { servers: [] };
304
+ }
305
+ }
package/unify/engine.js CHANGED
@@ -22,6 +22,7 @@ import { buildSystemPrompt } from './prompts.js';
22
22
  import { LLMContextError } from './llm/adapter.js';
23
23
  import { recall } from './memory/recall.js';
24
24
  import { shouldConsolidate, consolidate } from './memory/consolidate.js';
25
+ import { runStopHooks } from './stop-hooks.js';
25
26
 
26
27
  /** Maximum number of turns before the engine stops to prevent infinite loops. */
27
28
  const MAX_TURNS = 25;
@@ -67,16 +68,32 @@ export class Engine {
67
68
  /** @type {import('./memory/store.js').MemoryStore|null} */
68
69
  #memoryStore;
69
70
 
71
+ /** @type {import('./tools/registry.js').ToolRegistry|null} */
72
+ #toolRegistry;
73
+
74
+ /** @type {import('./skills.js').SkillManager|null} */
75
+ #skillManager;
76
+
77
+ /** @type {import('./mcp.js').MCPManager|null} */
78
+ #mcpManager;
79
+
80
+ /** @type {string|null} */
81
+ #yeaftDir;
82
+
70
83
  /**
71
84
  * @param {{
72
85
  * adapter: import('./llm/adapter.js').LLMAdapter,
73
86
  * trace: object,
74
87
  * config: object,
75
88
  * conversationStore?: import('./conversation/persist.js').ConversationStore,
76
- * memoryStore?: import('./memory/store.js').MemoryStore
89
+ * memoryStore?: import('./memory/store.js').MemoryStore,
90
+ * toolRegistry?: import('./tools/registry.js').ToolRegistry,
91
+ * skillManager?: import('./skills.js').SkillManager,
92
+ * mcpManager?: import('./mcp.js').MCPManager,
93
+ * yeaftDir?: string,
77
94
  * }} params
78
95
  */
79
- constructor({ adapter, trace, config, conversationStore, memoryStore }) {
96
+ constructor({ adapter, trace, config, conversationStore, memoryStore, toolRegistry, skillManager, mcpManager, yeaftDir }) {
80
97
  this.#adapter = adapter;
81
98
  this.#trace = trace;
82
99
  this.#config = config;
@@ -84,6 +101,10 @@ export class Engine {
84
101
  this.#traceId = randomUUID();
85
102
  this.#conversationStore = conversationStore || null;
86
103
  this.#memoryStore = memoryStore || null;
104
+ this.#toolRegistry = toolRegistry || null;
105
+ this.#skillManager = skillManager || null;
106
+ this.#mcpManager = mcpManager || null;
107
+ this.#yeaftDir = yeaftDir || null;
87
108
  }
88
109
 
89
110
  /**
@@ -106,10 +127,16 @@ export class Engine {
106
127
 
107
128
  /**
108
129
  * Get the list of registered tool definitions (for passing to the adapter).
130
+ * Prefers ToolRegistry (mode-aware) when available, falls back to legacy #tools Map.
109
131
  *
132
+ * @param {string} [mode]
110
133
  * @returns {import('./llm/adapter.js').UnifiedToolDef[]}
111
134
  */
112
- #getToolDefs() {
135
+ #getToolDefs(mode) {
136
+ if (this.#toolRegistry) {
137
+ return this.#toolRegistry.getToolDefs(mode || 'chat');
138
+ }
139
+ // Legacy path: no mode filtering
113
140
  const defs = [];
114
141
  for (const [, tool] of this.#tools) {
115
142
  defs.push({
@@ -122,23 +149,58 @@ export class Engine {
122
149
  }
123
150
 
124
151
  /**
125
- * Build the system prompt with memory and compact summary.
152
+ * Build the system prompt with memory, compact summary, and skill content.
126
153
  *
127
154
  * @param {string} mode
128
155
  * @param {{ profile?: string, entries?: object[] }} [memory]
129
156
  * @param {string} [compactSummary]
157
+ * @param {string} [prompt] — user prompt (for skill relevance matching)
130
158
  * @returns {string}
131
159
  */
132
- #buildSystemPrompt(mode, memory, compactSummary) {
160
+ #buildSystemPrompt(mode, memory, compactSummary, prompt) {
161
+ // Get relevant skill content if SkillManager is wired
162
+ let skillContent = '';
163
+ if (this.#skillManager && prompt) {
164
+ skillContent = this.#skillManager.getRelevantPromptContent(prompt, mode);
165
+ }
166
+
167
+ // Get tool names from the appropriate source
168
+ const toolNames = this.#toolRegistry
169
+ ? this.#toolRegistry.getToolNames(mode || 'chat')
170
+ : Array.from(this.#tools.keys());
171
+
133
172
  return buildSystemPrompt({
134
173
  language: this.#config.language || 'en',
135
174
  mode,
136
- toolNames: Array.from(this.#tools.keys()),
175
+ toolNames,
137
176
  memory,
138
177
  compactSummary,
178
+ skillContent,
139
179
  });
140
180
  }
141
181
 
182
+ /**
183
+ * Build the full tool context for Phase 5 tools.
184
+ *
185
+ * @param {AbortSignal} [signal]
186
+ * @param {string} [mode]
187
+ * @returns {object}
188
+ */
189
+ #buildToolContext(signal, mode) {
190
+ return {
191
+ signal,
192
+ yeaftDir: this.#yeaftDir,
193
+ cwd: process.cwd(),
194
+ mcpManager: this.#mcpManager,
195
+ skillManager: this.#skillManager,
196
+ memoryStore: this.#memoryStore,
197
+ conversationStore: this.#conversationStore,
198
+ adapter: this.#adapter,
199
+ config: this.#config,
200
+ mode,
201
+ };
202
+ }
203
+
142
204
  /**
143
205
  * Perform memory recall for a given prompt.
144
206
  *
@@ -262,7 +324,7 @@ export class Engine {
262
324
  }
263
325
 
264
326
  const compactSummary = this.#getCompactSummary();
265
- const systemPrompt = this.#buildSystemPrompt(mode, memory, compactSummary);
327
+ const systemPrompt = this.#buildSystemPrompt(mode, memory, compactSummary, prompt);
266
328
 
267
329
  // Build conversation: existing messages + new user message
268
330
  const conversationMessages = [
@@ -270,7 +332,7 @@ export class Engine {
270
332
  { role: 'user', content: prompt },
271
333
  ];
272
334
 
273
- const toolDefs = this.#getToolDefs();
335
+ const toolDefs = this.#getToolDefs(mode);
274
336
  let turnNumber = 0;
275
337
  let continueTurns = 0; // auto-continue counter
276
338
  let fullResponseText = '';
@@ -416,33 +478,66 @@ export class Engine {
416
478
  if (stopReason !== 'tool_use' || toolCalls.length === 0) {
417
479
  yield { type: 'turn_end', turnNumber, stopReason };
418
480
 
419
- // ─── Post-query: Persist + Consolidate ────────────
420
- this.#persistMessages(prompt, fullResponseText, mode, assistantMsg.toolCalls);
481
+ // ─── Post-query: StopHooks or Legacy ─────────────
482
+ if (this.#yeaftDir && this.#conversationStore) {
483
+ // Full pipeline: persist + consolidate + dream gate
484
+ const hookResult = await runStopHooks({
485
+ yeaftDir: this.#yeaftDir,
486
+ mode,
487
+ conversationStore: this.#conversationStore,
488
+ memoryStore: this.#memoryStore,
489
+ adapter: this.#adapter,
490
+ config: this.#config,
491
+ messages: conversationMessages,
492
+ trace: this.#trace,
493
+ });
494
+
495
+ if (hookResult.consolidated) {
496
+ yield { type: 'consolidate', archivedCount: 0, extractedCount: 0 };
497
+ }
498
+ if (hookResult.dreamTriggered) {
499
+ yield { type: 'dream_triggered' };
500
+ }
501
+ } else {
502
+ // Legacy path (no yeaftDir → use old behavior)
503
+ this.#persistMessages(prompt, fullResponseText, mode, assistantMsg.toolCalls);
421
504
 
422
- const consolidated = await this.#maybeConsolidate();
423
- if (consolidated && consolidated.archivedCount > 0) {
424
- yield { type: 'consolidate', archivedCount: consolidated.archivedCount, extractedCount: consolidated.extractedCount };
505
+ const consolidated = await this.#maybeConsolidate();
506
+ if (consolidated && consolidated.archivedCount > 0) {
507
+ yield { type: 'consolidate', archivedCount: consolidated.archivedCount, extractedCount: consolidated.extractedCount };
508
+ }
425
509
  }
426
510
 
427
511
  break;
428
512
  }
429
513
 
430
514
  // Execute tool calls and feed results back
515
+ const toolCtx = this.#buildToolContext(signal, mode);
516
+
431
517
  for (const tc of toolCalls) {
432
- const tool = this.#tools.get(tc.name);
433
518
  const toolStartTime = Date.now();
434
519
 
435
520
  let output;
436
521
  let isError = false;
437
522
 
438
- if (!tool) {
523
+ // Resolve tool: prefer ToolRegistry, fallback to legacy #tools Map
524
+ const hasTool = this.#toolRegistry
525
+ ? this.#toolRegistry.has(tc.name)
526
+ : this.#tools.has(tc.name);
527
+
528
+ if (!hasTool) {
439
529
  output = `Error: unknown tool "${tc.name}"`;
440
530
  isError = true;
441
531
  yield { type: 'tool_end', id: tc.id, name: tc.name, output, isError: true };
442
532
  } else {
443
533
  try {
444
534
  yield { type: 'tool_start', id: tc.id, name: tc.name, input: tc.input };
445
- output = await tool.execute(tc.input, { signal });
535
+ if (this.#toolRegistry) {
536
+ output = await this.#toolRegistry.execute(tc.name, tc.input, toolCtx);
537
+ } else {
538
+ const tool = this.#tools.get(tc.name);
539
+ output = await tool.execute(tc.input, { signal });
540
+ }
446
541
  yield { type: 'tool_end', id: tc.id, name: tc.name, output, isError: false };
447
542
  } catch (err) {
448
543
  output = `Error: ${err.message}`;
@@ -490,6 +585,7 @@ export class Engine {
490
585
  * @returns {string[]}
491
586
  */
492
587
  get toolNames() {
588
+ if (this.#toolRegistry) return this.#toolRegistry.names;
493
589
  return Array.from(this.#tools.keys());
494
590
  }
495
591
 
@@ -508,4 +604,16 @@ export class Engine {
508
604
  get memoryStore() {
509
605
  return this.#memoryStore;
510
606
  }
607
+
608
+ /** @returns {import('./tools/registry.js').ToolRegistry|null} */
609
+ get toolRegistry() { return this.#toolRegistry; }
610
+
611
+ /** @returns {import('./skills.js').SkillManager|null} */
612
+ get skillManager() { return this.#skillManager; }
613
+
614
+ /** @returns {import('./mcp.js').MCPManager|null} */
615
+ get mcpManager() { return this.#mcpManager; }
616
+
617
+ /** @returns {string|null} */
618
+ get yeaftDir() { return this.#yeaftDir; }
511
619
  }
package/unify/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  export { initYeaftDir, DEFAULT_YEAFT_DIR } from './init.js';
8
- export { loadConfig, parseFrontmatter } from './config.js';
8
+ export { loadConfig, parseFrontmatter, loadMCPConfig } from './config.js';
9
9
  export { DebugTrace, NullTrace, createTrace } from './debug-trace.js';
10
10
  export {
11
11
  LLMAdapter,
@@ -36,4 +36,5 @@ export { MCPManager, createMCPManager } from './mcp.js';
36
36
  export { SkillManager, createSkillManager, parseSkill, serializeSkill } from './skills.js';
37
37
  export { defineTool } from './tools/types.js';
38
38
  export { ToolRegistry, createEmptyRegistry } from './tools/registry.js';
39
+ export { loadSession } from './session.js';
39
40
 
package/unify/prompts.js CHANGED
@@ -61,6 +61,7 @@ export function buildSystemPrompt({
61
61
  toolNames = [],
62
62
  memory,
63
63
  compactSummary,
64
+ skillContent,
64
65
  } = {}) {
65
66
  // Fallback to English for unknown languages
66
67
  const lang = PROMPTS[language] || PROMPTS.en;
@@ -81,6 +82,11 @@ export function buildSystemPrompt({
81
82
  parts.push(lang.tools(toolNames.join(', ')));
82
83
  }
83
84
 
85
+ // ─── Skills Section ─────────────────────────────────────
86
+ if (skillContent) {
87
+ parts.push(skillContent);
88
+ }
89
+
84
90
  // ─── Memory Section ─────────────────────────────────────
85
91
  if (memory && (memory.profile || (memory.entries && memory.entries.length > 0))) {
86
92
  const memoryParts = [lang.memoryHeader];
@@ -0,0 +1,191 @@
1
+ /**
2
+ * session.js — Session orchestrator for Yeaft Unify
3
+ *
4
+ * Single entry point: loadSession(options?) → Session
5
+ *
6
+ * Wires all subsystems together:
7
+ * initYeaftDir → loadConfig → createTrace → createLLMAdapter →
8
+ * ConversationStore → MemoryStore → SkillManager → MCPManager →
9
+ * ToolRegistry → Engine → Session
10
+ *
11
+ * The ~/.yeaft/ directory is the agent's persistent workspace.
12
+ * loadSession() loads (or initializes) this workspace and returns
13
+ * a fully wired Session ready for queries.
14
+ */
15
+
16
+ import { initYeaftDir } from './init.js';
17
+ import { loadConfig, loadMCPConfig } from './config.js';
18
+ import { createTrace } from './debug-trace.js';
19
+ import { createLLMAdapter } from './llm/adapter.js';
20
+ import { ConversationStore } from './conversation/persist.js';
21
+ import { MemoryStore } from './memory/store.js';
22
+ import { SkillManager, createSkillManager } from './skills.js';
23
+ import { MCPManager } from './mcp.js';
24
+ import { createEmptyRegistry } from './tools/registry.js';
25
+ import { Engine } from './engine.js';
26
+ import { join } from 'path';
27
+
28
+ // Built-in tools
29
+ import mcpTools from './tools/mcp-tools.js';
30
+ import skillTool from './tools/skill.js';
31
+ import enterWorktree from './tools/enter-worktree.js';
32
+ import exitWorktree from './tools/exit-worktree.js';
33
+
34
+ /**
35
+ * @typedef {Object} SessionOptions
36
+ * @property {string} [dir] — Yeaft data directory override (default: ~/.yeaft)
37
+ * @property {string} [model] — Model override
38
+ * @property {string} [language] — Language override ('en' | 'zh')
39
+ * @property {boolean} [debug] — Debug mode override
40
+ * @property {boolean} [skipMCP] — Skip MCP server connections (faster startup)
41
+ * @property {boolean} [skipSkills] — Skip skill loading
42
+ * @property {object[]} [extraTools] — Additional ToolDef objects to register
43
+ * @property {object} [configOverrides] — Additional config overrides
44
+ */
45
+
46
+ /**
47
+ * @typedef {Object} Session
48
+ * @property {Engine} engine — The wired engine, ready for .query()
49
+ * @property {object} config — Resolved configuration
50
+ * @property {ConversationStore} conversationStore — Conversation persistence
51
+ * @property {MemoryStore} memoryStore — Memory persistence
52
+ * @property {SkillManager} skillManager — Skill manager
53
+ * @property {MCPManager} mcpManager — MCP manager
54
+ * @property {import('./tools/registry.js').ToolRegistry} toolRegistry — Tool registry
55
+ * @property {import('./debug-trace.js').DebugTrace|import('./debug-trace.js').NullTrace} trace
56
+ * @property {string} yeaftDir — Resolved data directory path
57
+ * @property {{ skills: number, mcpServers: string[], mcpFailed: object[], tools: number }} status
58
+ * @property {() => Promise<void>} shutdown — Graceful shutdown
59
+ */
60
+
61
+ /**
62
+ * Load (or initialize) a Yeaft session.
63
+ *
64
+ * This is the main entry point for using Yeaft programmatically.
65
+ * It creates the directory structure if needed, loads config, connects
66
+ * to services, registers tools, and returns a ready-to-use Session.
67
+ *
68
+ * @param {SessionOptions} [options={}]
69
+ * @returns {Promise<Session>}
70
+ */
71
+ export async function loadSession(options = {}) {
72
+ const {
73
+ dir,
74
+ model,
75
+ language,
76
+ debug,
77
+ skipMCP = false,
78
+ skipSkills = false,
79
+ extraTools = [],
80
+ configOverrides = {},
81
+ } = options;
82
+
83
+ // ─── 1. Load config (determines yeaftDir) ──────────────
84
+ const overrides = { ...configOverrides };
85
+ if (dir) overrides.dir = dir;
86
+ if (model) overrides.model = model;
87
+ if (language) overrides.language = language;
88
+ if (debug !== undefined) overrides.debug = debug;
89
+
90
+ const config = loadConfig(overrides);
91
+ const yeaftDir = config.dir;
92
+
93
+ // ─── 2. Ensure directory structure ─────────────────────
94
+ initYeaftDir(yeaftDir);
95
+
96
+ // ─── 3. Create debug trace ─────────────────────────────
97
+ const trace = createTrace({
98
+ enabled: config.debug,
99
+ dbPath: join(yeaftDir, 'debug.db'),
100
+ });
101
+
102
+ // ─── 4. Create LLM adapter ────────────────────────────
103
+ const adapter = await createLLMAdapter(config);
104
+
105
+ // ─── 5. Create stores ──────────────────────────────────
106
+ const conversationStore = new ConversationStore(yeaftDir);
107
+ const memoryStore = new MemoryStore(yeaftDir);
108
+
109
+ // ─── 6. Load skills ────────────────────────────────────
110
+ let skillManager;
111
+ if (skipSkills) {
112
+ skillManager = new SkillManager(yeaftDir);
113
+ // Don't call .load() — empty skill manager
114
+ } else {
115
+ skillManager = createSkillManager(yeaftDir);
116
+ }
117
+
118
+ // ─── 7. Connect MCP servers ────────────────────────────
119
+ const mcpConfig = loadMCPConfig(yeaftDir);
120
+ const mcpManager = new MCPManager();
121
+ let mcpStatus = { connected: [], failed: [] };
122
+
123
+ if (!skipMCP && mcpConfig.servers.length > 0) {
124
+ mcpStatus = await mcpManager.connectAll(mcpConfig.servers);
125
+ }
126
+
127
+ // ─── 8. Build tool registry ────────────────────────────
128
+ const toolRegistry = createEmptyRegistry();
129
+
130
+ // Register built-in tools
131
+ for (const tool of mcpTools) {
132
+ toolRegistry.register(tool);
133
+ }
134
+ toolRegistry.register(skillTool);
135
+ toolRegistry.register(enterWorktree);
136
+ toolRegistry.register(exitWorktree);
137
+
138
+ // Register any extra tools from caller
139
+ for (const tool of extraTools) {
140
+ toolRegistry.register(tool);
141
+ }
142
+
143
+ // ─── 9. Create engine (wires everything) ───────────────
144
+ const engine = new Engine({
145
+ adapter,
146
+ trace,
147
+ config,
148
+ conversationStore,
149
+ memoryStore,
150
+ toolRegistry,
151
+ skillManager,
152
+ mcpManager,
153
+ yeaftDir,
154
+ });
155
+
156
+ // ─── 10. Build session ─────────────────────────────────
157
+ const status = {
158
+ skills: skillManager.size,
159
+ mcpServers: mcpStatus.connected,
160
+ mcpFailed: mcpStatus.failed,
161
+ tools: toolRegistry.size,
162
+ };
163
+
164
+ /** Graceful shutdown: disconnect MCP, close trace DB. */
165
+ async function shutdown() {
166
+ try {
167
+ await mcpManager.disconnectAll();
168
+ } catch {
169
+ // Best-effort cleanup
170
+ }
171
+ try {
172
+ trace.close();
173
+ } catch {
174
+ // Trace might not have close() (NullTrace)
175
+ }
176
+ }
177
+
178
+ return {
179
+ engine,
180
+ config,
181
+ conversationStore,
182
+ memoryStore,
183
+ skillManager,
184
+ mcpManager,
185
+ toolRegistry,
186
+ trace,
187
+ yeaftDir,
188
+ status,
189
+ shutdown,
190
+ };
191
+ }