@yeaft/webchat-agent 0.1.445 → 0.1.446

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 (2) hide show
  1. package/package.json +1 -1
  2. package/unify/prompts.js +142 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.445",
3
+ "version": "0.1.446",
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/prompts.js CHANGED
@@ -4,6 +4,9 @@
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
8
+ * and used to enrich the system prompt beyond the hardcoded fallbacks.
9
+ *
7
10
  * Phase 2 additions:
8
11
  * - Memory section (user profile + recalled entries)
9
12
  * - Compact summary section (conversation history summary)
@@ -11,7 +14,99 @@
11
14
  * Reference: yeaft-unify-system-prompt-budget.md — Static + Dynamic + Context layers
12
15
  */
13
16
 
14
- // ─── Prompt Templates ─────────────────────────────────────────
17
+ import { readFileSync, existsSync } from 'fs';
18
+ import { join, dirname } from 'path';
19
+ import { fileURLToPath } from 'url';
20
+
21
+ // ─── Template Loading (one-time at startup) ──────────────────────
22
+
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
24
+ const TEMPLATES_DIR = join(__dirname, 'templates');
25
+
26
+ /**
27
+ * Read a template file from the templates/ directory.
28
+ * Returns empty string if file doesn't exist or can't be read.
29
+ * @param {string} name — filename (e.g. 'base.md')
30
+ * @returns {string}
31
+ */
32
+ function readTemplate(name) {
33
+ const path = join(TEMPLATES_DIR, name);
34
+ if (!existsSync(path)) return '';
35
+ try {
36
+ return readFileSync(path, 'utf8').trim();
37
+ } catch {
38
+ return '';
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Extract the section for a given language from a bilingual template.
44
+ * Templates use `<!-- lang:en -->` / `<!-- lang:zh -->` HTML comment markers
45
+ * to delimit language sections. Returns the content between the matching
46
+ * marker and the next marker (or EOF).
47
+ *
48
+ * If no markers exist, returns the full content regardless of language.
49
+ *
50
+ * @param {string} content — full template content
51
+ * @param {string} language — 'en' or 'zh'
52
+ * @returns {string}
53
+ */
54
+ function extractLangSection(content, language) {
55
+ if (!content) return '';
56
+
57
+ const marker = `<!-- lang:${language} -->`;
58
+ const markerIdx = content.indexOf(marker);
59
+
60
+ if (markerIdx === -1) {
61
+ // No marker for this language — if language is 'zh', try 'en' fallback
62
+ if (language === 'zh') {
63
+ const enMarker = '<!-- lang:en -->';
64
+ const enIdx = content.indexOf(enMarker);
65
+ if (enIdx !== -1) {
66
+ // Has en marker but no zh — return en section as fallback
67
+ return extractLangSection(content, 'en');
68
+ }
69
+ }
70
+ // No markers at all — return full content
71
+ if (!content.includes('<!-- lang:')) return content;
72
+ // Has markers but not for this language — fallback to en
73
+ return extractLangSection(content, 'en');
74
+ }
75
+
76
+ // Extract from after the marker to the next <!-- lang: marker or EOF
77
+ const sectionStart = markerIdx + marker.length;
78
+ const nextMarkerIdx = content.indexOf('<!-- lang:', sectionStart);
79
+
80
+ if (nextMarkerIdx === -1) {
81
+ // This is the last section — take everything after the marker
82
+ return content.slice(sectionStart).trim();
83
+ }
84
+
85
+ return content.slice(sectionStart, nextMarkerIdx).trim();
86
+ }
87
+
88
+ /** Loaded templates — read once at module load time. */
89
+ const RAW_TEMPLATES = {
90
+ base: readTemplate('base.md'),
91
+ modeChat: readTemplate('mode-chat.md'),
92
+ modeWorker: readTemplate('mode-worker.md'),
93
+ modeDream: readTemplate('mode-dream.md'),
94
+ toolGuidance: readTemplate('tool-guidance.md'),
95
+ };
96
+
97
+ /**
98
+ * Get a template section for the given language.
99
+ * @param {string} key — template key (e.g. 'base', 'modeChat')
100
+ * @param {string} language — 'en' or 'zh'
101
+ * @returns {string}
102
+ */
103
+ function getTemplate(key, language) {
104
+ const raw = RAW_TEMPLATES[key];
105
+ if (!raw) return '';
106
+ return extractLangSection(raw, language);
107
+ }
108
+
109
+ // ─── Prompt Templates (hardcoded fallbacks) ──────────────────────
15
110
 
16
111
  const PROMPTS = {
17
112
  en: {
@@ -46,12 +141,22 @@ export const SUPPORTED_LANGUAGES = Object.keys(PROMPTS);
46
141
  /**
47
142
  * Build the system prompt for a given language and mode.
48
143
  *
144
+ * Prompt structure:
145
+ * 1. Core identity (from template or fallback)
146
+ * 2. Mode + date metadata
147
+ * 3. Mode-specific behavioral instructions (from template or fallback)
148
+ * 4. Tool list + tool guidance (from template)
149
+ * 5. Skills section
150
+ * 6. Memory section
151
+ * 7. Compact summary section
152
+ *
49
153
  * @param {{
50
154
  * language?: string,
51
155
  * mode?: string,
52
156
  * toolNames?: string[],
53
157
  * memory?: { profile?: string, entries?: object[] },
54
- * compactSummary?: string
158
+ * compactSummary?: string,
159
+ * skillContent?: string,
55
160
  * }} params
56
161
  * @returns {string}
57
162
  */
@@ -65,29 +170,54 @@ export function buildSystemPrompt({
65
170
  } = {}) {
66
171
  // Fallback to English for unknown languages
67
172
  const lang = PROMPTS[language] || PROMPTS.en;
173
+ const effectiveLang = PROMPTS[language] ? language : 'en';
68
174
 
69
- const parts = [
70
- lang.identity,
71
- lang.mode(mode),
72
- lang.date(new Date().toISOString().split('T')[0]),
73
- ];
175
+ const parts = [];
176
+
177
+ // ─── 1. Core Identity ──────────────────────────────────
178
+ // Use template if available, otherwise fallback to hardcoded one-liner
179
+ const baseTemplate = getTemplate('base', effectiveLang);
180
+ if (baseTemplate) {
181
+ parts.push(baseTemplate);
182
+ } else {
183
+ parts.push(lang.identity);
184
+ }
74
185
 
186
+ // ─── 2. Mode + Date Metadata ───────────────────────────
187
+ parts.push(lang.mode(mode));
188
+ parts.push(lang.date(new Date().toISOString().split('T')[0]));
189
+
190
+ // ─── 3. Mode-Specific Instructions ─────────────────────
75
191
  if (mode === 'work') {
76
- parts.push(lang.work);
192
+ const workerTemplate = getTemplate('modeWorker', effectiveLang);
193
+ parts.push(workerTemplate || lang.work);
77
194
  } else if (mode === 'dream') {
78
- parts.push(lang.dream);
195
+ const dreamTemplate = getTemplate('modeDream', effectiveLang);
196
+ parts.push(dreamTemplate || lang.dream);
197
+ } else if (mode === 'chat') {
198
+ const chatTemplate = getTemplate('modeChat', effectiveLang);
199
+ if (chatTemplate) {
200
+ parts.push(chatTemplate);
201
+ }
202
+ // No fallback needed — chat mode previously had no instructions
79
203
  }
80
204
 
205
+ // ─── 4. Tools + Tool Guidance ──────────────────────────
81
206
  if (toolNames.length > 0) {
82
207
  parts.push(lang.tools(toolNames.join(', ')));
208
+
209
+ const toolGuidanceTemplate = getTemplate('toolGuidance', effectiveLang);
210
+ if (toolGuidanceTemplate) {
211
+ parts.push(toolGuidanceTemplate);
212
+ }
83
213
  }
84
214
 
85
- // ─── Skills Section ─────────────────────────────────────
215
+ // ─── 5. Skills Section ─────────────────────────────────
86
216
  if (skillContent) {
87
217
  parts.push(skillContent);
88
218
  }
89
219
 
90
- // ─── Memory Section ─────────────────────────────────────
220
+ // ─── 6. Memory Section ─────────────────────────────────
91
221
  if (memory && (memory.profile || (memory.entries && memory.entries.length > 0))) {
92
222
  const memoryParts = [lang.memoryHeader];
93
223
 
@@ -106,7 +236,7 @@ export function buildSystemPrompt({
106
236
  parts.push(memoryParts.join('\n\n'));
107
237
  }
108
238
 
109
- // ─── Compact Summary Section ────────────────────────────
239
+ // ─── 7. Compact Summary Section ────────────────────────
110
240
  if (compactSummary) {
111
241
  parts.push(`${lang.compactHeader}\n${compactSummary}`);
112
242
  }