@code4bug/jarvis-agent 1.1.6 → 1.1.8

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.
@@ -8,6 +8,9 @@ import fs from 'fs';
8
8
  import path from 'path';
9
9
  import { execSync } from 'child_process';
10
10
  import { APP_NAME, APP_VERSION } from '../config/constants.js';
11
+ import { LLMServiceImpl, getDefaultConfig } from '../services/api/llm.js';
12
+ import { allTools } from '../tools/index.js';
13
+ import { loadAllAgents } from '../agents/index.js';
11
14
  // ===== 辅助函数 =====
12
15
  /** 安全执行命令 */
13
16
  function safeExec(cmd) {
@@ -123,7 +126,393 @@ function countSourceFiles(dir) {
123
126
  walk(dir);
124
127
  return { total, byExt };
125
128
  }
126
- export function executeInit() {
129
+ /** 读取文本文件,超长时截断 */
130
+ function readTextFile(filePath, maxChars = 6000) {
131
+ try {
132
+ const content = fs.readFileSync(filePath, 'utf-8');
133
+ if (content.length <= maxChars)
134
+ return content;
135
+ return `${content.slice(0, maxChars)}\n\n[...已截断,共 ${content.length} 字符]`;
136
+ }
137
+ catch {
138
+ return '';
139
+ }
140
+ }
141
+ function shouldIgnoreEntry(name) {
142
+ return name.startsWith('.')
143
+ || name === 'node_modules'
144
+ || name === 'dist'
145
+ || name === 'build'
146
+ || name === 'target'
147
+ || name === '__pycache__'
148
+ || name === '.git';
149
+ }
150
+ function normalizePath(relativePath) {
151
+ return relativePath.split(path.sep).join('/');
152
+ }
153
+ function isTextLikeFile(fileName) {
154
+ const ext = path.extname(fileName).toLowerCase();
155
+ return [
156
+ '.md', '.txt', '.json', '.jsonc', '.yaml', '.yml', '.toml', '.ini', '.conf',
157
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
158
+ '.py', '.java', '.go', '.rs', '.kt', '.kts',
159
+ '.c', '.cc', '.cpp', '.h', '.hpp',
160
+ '.sh', '.bash', '.zsh', '.sql', '.xml', '.gradle', '.properties',
161
+ ].includes(ext) || fileName === 'Dockerfile' || fileName === 'Makefile';
162
+ }
163
+ function collectRootContextFiles(cwd) {
164
+ const preferredPatterns = [
165
+ /^README(\..+)?$/i,
166
+ /^package\.json$/i,
167
+ /^pnpm-lock\.ya?ml$/i,
168
+ /^package-lock\.json$/i,
169
+ /^yarn\.lock$/i,
170
+ /^tsconfig.*\.json$/i,
171
+ /^vite\.config\./i,
172
+ /^webpack\.config\./i,
173
+ /^next\.config\./i,
174
+ /^nuxt\.config\./i,
175
+ /^pom\.xml$/i,
176
+ /^build\.gradle(\.kts)?$/i,
177
+ /^settings\.gradle(\.kts)?$/i,
178
+ /^pyproject\.toml$/i,
179
+ /^requirements.*\.txt$/i,
180
+ /^go\.mod$/i,
181
+ /^Cargo\.toml$/i,
182
+ /^composer\.json$/i,
183
+ /^Gemfile$/i,
184
+ /^Dockerfile$/i,
185
+ /^docker-compose\.ya?ml$/i,
186
+ /^Makefile$/i,
187
+ /^AGENT.*\.md$/i,
188
+ /^SKILL.*\.md$/i,
189
+ ];
190
+ try {
191
+ const entries = fs.readdirSync(cwd, { withFileTypes: true });
192
+ return entries
193
+ .filter((entry) => entry.isFile() && !shouldIgnoreEntry(entry.name))
194
+ .map((entry) => entry.name)
195
+ .filter((name) => preferredPatterns.some((pattern) => pattern.test(name)))
196
+ .sort((a, b) => a.localeCompare(b))
197
+ .slice(0, 12);
198
+ }
199
+ catch {
200
+ return [];
201
+ }
202
+ }
203
+ function chooseRepresentativeFiles(dirPath, relativeDir, maxFiles = 3) {
204
+ const preferredNames = [
205
+ 'index', 'main', 'app', 'cli', 'server', 'client', 'api',
206
+ 'router', 'routes', 'controller', 'service', 'model',
207
+ 'commands', 'tools', 'query', 'engine',
208
+ ];
209
+ try {
210
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true })
211
+ .filter((entry) => entry.isFile() && !shouldIgnoreEntry(entry.name) && isTextLikeFile(entry.name))
212
+ .sort((a, b) => {
213
+ const aBase = path.parse(a.name).name.toLowerCase();
214
+ const bBase = path.parse(b.name).name.toLowerCase();
215
+ const aScore = preferredNames.findIndex((name) => name === aBase);
216
+ const bScore = preferredNames.findIndex((name) => name === bBase);
217
+ const normalizedAScore = aScore === -1 ? preferredNames.length : aScore;
218
+ const normalizedBScore = bScore === -1 ? preferredNames.length : bScore;
219
+ if (normalizedAScore !== normalizedBScore)
220
+ return normalizedAScore - normalizedBScore;
221
+ return a.name.localeCompare(b.name);
222
+ });
223
+ return entries
224
+ .slice(0, maxFiles)
225
+ .map((entry) => normalizePath(path.join(relativeDir, entry.name)));
226
+ }
227
+ catch {
228
+ return [];
229
+ }
230
+ }
231
+ function collectSourceContextFiles(cwd) {
232
+ const candidateDirs = ['src', 'app', 'lib', 'cmd', 'internal', 'pkg', 'server', 'client', 'backend', 'frontend'];
233
+ const result = [];
234
+ for (const dirName of candidateDirs) {
235
+ const dirPath = path.join(cwd, dirName);
236
+ if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory())
237
+ continue;
238
+ result.push(...chooseRepresentativeFiles(dirPath, dirName, 3));
239
+ try {
240
+ const subDirs = fs.readdirSync(dirPath, { withFileTypes: true })
241
+ .filter((entry) => entry.isDirectory() && !shouldIgnoreEntry(entry.name))
242
+ .sort((a, b) => a.name.localeCompare(b.name))
243
+ .slice(0, 4);
244
+ for (const subDir of subDirs) {
245
+ result.push(...chooseRepresentativeFiles(path.join(dirPath, subDir.name), path.join(dirName, subDir.name), 2));
246
+ }
247
+ }
248
+ catch {
249
+ // ignore
250
+ }
251
+ }
252
+ return Array.from(new Set(result)).slice(0, 18);
253
+ }
254
+ /** 采样项目关键文件,提供给大模型做总结 */
255
+ function collectProjectContext(cwd) {
256
+ const candidates = [
257
+ ...collectRootContextFiles(cwd),
258
+ ...collectSourceContextFiles(cwd),
259
+ ];
260
+ const parts = [];
261
+ for (const relativePath of candidates) {
262
+ const fullPath = path.join(cwd, relativePath);
263
+ if (!fs.existsSync(fullPath))
264
+ continue;
265
+ const content = readTextFile(fullPath);
266
+ if (!content)
267
+ continue;
268
+ parts.push(`## 文件: ${normalizePath(relativePath)}\n\n\`\`\`\n${content}\n\`\`\``);
269
+ }
270
+ return parts.join('\n\n');
271
+ }
272
+ function stripMarkdownCodeFence(text) {
273
+ const trimmed = text.trim();
274
+ const match = trimmed.match(/^```(?:md|markdown)?\s*([\s\S]*?)\s*```$/i);
275
+ return match ? match[1].trim() : trimmed;
276
+ }
277
+ function stripJsonCodeFence(text) {
278
+ const trimmed = text.trim();
279
+ const match = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
280
+ return match ? match[1].trim() : trimmed;
281
+ }
282
+ function listTopLevelDirectories(cwd) {
283
+ try {
284
+ return fs.readdirSync(cwd, { withFileTypes: true })
285
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules' && entry.name !== 'dist')
286
+ .map((entry) => entry.name)
287
+ .sort((a, b) => a.localeCompare(b));
288
+ }
289
+ catch {
290
+ return [];
291
+ }
292
+ }
293
+ function buildRealCommandSuggestions(cwd, pkg) {
294
+ const commands = [];
295
+ if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {
296
+ commands.push('pnpm install');
297
+ }
298
+ else if (fs.existsSync(path.join(cwd, 'package-lock.json'))) {
299
+ commands.push('npm install');
300
+ }
301
+ if (pkg?.scripts?.dev)
302
+ commands.push('npm run dev');
303
+ if (pkg?.scripts?.start)
304
+ commands.push('npm run start');
305
+ if (pkg?.scripts?.build)
306
+ commands.push('npm run build');
307
+ if (pkg?.scripts?.test)
308
+ commands.push('npm test');
309
+ return commands;
310
+ }
311
+ function renderJarvisMd(input) {
312
+ const { projectName, packageJson: pkg, projectTypes, gitInfo, dirTree, devCommands, summary } = input;
313
+ const md = [];
314
+ md.push(`# ${pkg?.name || projectName}`);
315
+ md.push('');
316
+ md.push('## 项目概览');
317
+ md.push('');
318
+ md.push(summary.overview || '待补充');
319
+ md.push('');
320
+ md.push('## 技术栈');
321
+ md.push('');
322
+ md.push(`- 项目类型:${projectTypes.join(', ')}`);
323
+ md.push(`- 运行时:${pkg?.type || '未发现'}`);
324
+ md.push(`- 版本:${pkg?.version || '未发现'}`);
325
+ md.push(`- Git 分支:${gitInfo?.branch || '未发现'}`);
326
+ md.push('');
327
+ md.push('## 目录与模块');
328
+ md.push('');
329
+ for (const line of summary.architecture) {
330
+ md.push(`- ${line}`);
331
+ }
332
+ md.push('');
333
+ md.push('```');
334
+ md.push(`${projectName}/`);
335
+ md.push(...dirTree);
336
+ md.push('```');
337
+ md.push('');
338
+ md.push('## 当前能力');
339
+ md.push('');
340
+ for (const line of summary.capabilities) {
341
+ md.push(`- ${line}`);
342
+ }
343
+ md.push('');
344
+ md.push('## 开发与构建');
345
+ md.push('');
346
+ if (devCommands.length > 0) {
347
+ md.push('```bash');
348
+ md.push(...devCommands);
349
+ md.push('```');
350
+ }
351
+ else {
352
+ md.push('待补充');
353
+ }
354
+ md.push('');
355
+ md.push('## 协作约定');
356
+ md.push('');
357
+ for (const line of summary.collaborationNotes) {
358
+ md.push(`- ${line}`);
359
+ }
360
+ md.push('');
361
+ md.push(`> 由 ${APP_NAME} /init 自动生成`);
362
+ md.push('');
363
+ return md.join('\n');
364
+ }
365
+ async function generateJarvisMdWithLLM(input) {
366
+ const service = new LLMServiceImpl({
367
+ ...getDefaultConfig(),
368
+ });
369
+ const projectContext = collectProjectContext(input.cwd);
370
+ const topLevelDirectories = listTopLevelDirectories(input.cwd);
371
+ const devCommands = buildRealCommandSuggestions(input.cwd, input.packageJson);
372
+ const toolNames = allTools.map((tool) => tool.name);
373
+ const agentNames = Array.from(loadAllAgents().values()).map((agent) => agent.meta.name);
374
+ const extStats = Object.entries(input.fileStats.byExt)
375
+ .sort((a, b) => b[1] - a[1])
376
+ .map(([ext, count]) => `${ext}: ${count}`)
377
+ .join(', ');
378
+ const prompt = [
379
+ '你是项目初始化助手。请基于提供的真实项目信息,为 JARVIS.md 先生成结构化摘要。',
380
+ '',
381
+ '要求:',
382
+ '1. 只能依据给定信息总结,不要编造不存在的模块、流程、命令、目录或能力',
383
+ '2. 全文使用中文',
384
+ '3. 必须只输出合法 JSON,不要附加解释,不要输出 Markdown',
385
+ '4. 内容要有总结性,但必须可被事实支撑',
386
+ '5. 如果信息不足,明确写“待补充”或“未发现”,不要猜测',
387
+ '6. 禁止使用 emoji、营销文案、夸张措辞',
388
+ '',
389
+ 'JSON 结构如下:',
390
+ '{',
391
+ ' "overview": "1 段中文概述,80-160 字",',
392
+ ' "architecture": ["3 到 6 条,每条一句,描述目录或模块职责"],',
393
+ ' "capabilities": ["4 到 8 条,每条一句,描述当前已实现能力"],',
394
+ ' "collaborationNotes": ["3 到 6 条,每条一句,描述开发协作约定或注意事项"]',
395
+ '}',
396
+ '',
397
+ '以下是项目事实:',
398
+ `- 项目名称: ${input.packageJson?.name || input.projectName}`,
399
+ `- 项目目录名: ${input.projectName}`,
400
+ `- 项目版本: ${input.packageJson?.version || '未发现'}`,
401
+ `- 项目描述: ${input.packageJson?.description || '未发现'}`,
402
+ `- 项目类型: ${input.projectTypes.join(', ')}`,
403
+ `- Git 分支: ${input.gitInfo?.branch || '未发现'}`,
404
+ `- Git 远程: ${input.gitInfo?.remote || '未发现'}`,
405
+ `- 最近提交: ${input.gitInfo?.lastCommit || '未发现'}`,
406
+ `- 源文件总数: ${input.fileStats.total}`,
407
+ `- 扩展名统计: ${extStats || '未发现'}`,
408
+ `- 顶层目录: ${topLevelDirectories.join(', ') || '未发现'}`,
409
+ `- 内置工具: ${toolNames.join(', ') || '未发现'}`,
410
+ `- 内置智能体: ${agentNames.join(', ') || '未发现'}`,
411
+ `- 可确认开发命令: ${devCommands.join(' | ') || '未发现'}`,
412
+ '',
413
+ '目录树(浅层):',
414
+ `${input.projectName}/`,
415
+ ...input.dirTree,
416
+ '',
417
+ '关键文件内容:',
418
+ projectContext || '(未读取到关键文件)',
419
+ ].join('\n');
420
+ let result = '';
421
+ const transcript = [
422
+ { role: 'user', content: prompt },
423
+ ];
424
+ await new Promise((resolve, reject) => {
425
+ service.streamMessage(transcript, [], {
426
+ onText: (text) => { result += text; },
427
+ onToolUse: () => { },
428
+ onComplete: () => resolve(),
429
+ onError: (error) => reject(error),
430
+ }, undefined, { includeUserProfile: false }).catch(reject);
431
+ });
432
+ const cleaned = stripJsonCodeFence(stripMarkdownCodeFence(result));
433
+ if (!cleaned) {
434
+ throw new Error('大模型未返回有效内容');
435
+ }
436
+ let summary;
437
+ try {
438
+ const parsed = JSON.parse(cleaned);
439
+ summary = {
440
+ overview: String(parsed.overview || '').trim() || '待补充',
441
+ architecture: Array.isArray(parsed.architecture) ? parsed.architecture.map((item) => String(item).trim()).filter(Boolean) : [],
442
+ capabilities: Array.isArray(parsed.capabilities) ? parsed.capabilities.map((item) => String(item).trim()).filter(Boolean) : [],
443
+ collaborationNotes: Array.isArray(parsed.collaborationNotes) ? parsed.collaborationNotes.map((item) => String(item).trim()).filter(Boolean) : [],
444
+ };
445
+ }
446
+ catch (error) {
447
+ throw new Error(`解析大模型总结失败: ${error.message}`);
448
+ }
449
+ if (summary.architecture.length === 0)
450
+ summary.architecture = ['待补充'];
451
+ if (summary.capabilities.length === 0)
452
+ summary.capabilities = ['待补充'];
453
+ if (summary.collaborationNotes.length === 0)
454
+ summary.collaborationNotes = ['待补充'];
455
+ return renderJarvisMd({
456
+ projectName: input.projectName,
457
+ packageJson: input.packageJson,
458
+ projectTypes: input.projectTypes,
459
+ gitInfo: input.gitInfo,
460
+ dirTree: input.dirTree,
461
+ devCommands,
462
+ summary,
463
+ });
464
+ }
465
+ function generateBasicJarvisMd(input) {
466
+ const { cwd, projectName, packageJson: pkg, projectTypes, gitInfo, dirTree } = input;
467
+ const md = [];
468
+ md.push(`# ${pkg?.name || projectName}`);
469
+ md.push('');
470
+ if (pkg?.description) {
471
+ md.push(pkg.description);
472
+ md.push('');
473
+ }
474
+ md.push('## 项目概览');
475
+ md.push('');
476
+ md.push(`- 项目类型:${projectTypes.join(', ')}`);
477
+ md.push(`- 当前目录:\`${cwd}\``);
478
+ md.push(`- 版本:${pkg?.version || '未发现'}`);
479
+ md.push(`- Git 分支:${gitInfo?.branch || '未发现'}`);
480
+ md.push('');
481
+ md.push('## 目录结构');
482
+ md.push('');
483
+ md.push('```');
484
+ md.push(`${projectName}/`);
485
+ md.push(...dirTree);
486
+ md.push('```');
487
+ md.push('');
488
+ if (pkg?.scripts) {
489
+ md.push('## 开发命令');
490
+ md.push('');
491
+ md.push('```bash');
492
+ if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {
493
+ md.push('pnpm install');
494
+ }
495
+ else if (fs.existsSync(path.join(cwd, 'package-lock.json'))) {
496
+ md.push('npm install');
497
+ }
498
+ if (pkg.scripts.dev)
499
+ md.push('npm run dev');
500
+ if (pkg.scripts.build)
501
+ md.push('npm run build');
502
+ if (pkg.scripts.test)
503
+ md.push('npm test');
504
+ md.push('```');
505
+ md.push('');
506
+ }
507
+ md.push('## 协作约定');
508
+ md.push('');
509
+ md.push('- 本文件为自动生成结果;若需更准确的业务背景,请补充 README 或项目文档。');
510
+ md.push('');
511
+ md.push(`> 由 ${APP_NAME} /init 自动生成`);
512
+ md.push('');
513
+ return md.join('\n');
514
+ }
515
+ export async function executeInit() {
127
516
  const cwd = process.cwd();
128
517
  const projectName = path.basename(cwd);
129
518
  const pkg = readPackageJson();
@@ -207,73 +596,36 @@ export function executeInit() {
207
596
  display.push('');
208
597
  }
209
598
  }
210
- // ===== 生成 JARVIS.md =====
211
- const md = [];
212
- md.push(`# ${pkg?.name || projectName}`);
213
- md.push('');
214
- if (pkg?.description) {
215
- md.push(pkg.description);
216
- md.push('');
217
- }
218
- md.push('---');
219
- md.push('');
220
- md.push('## 项目信息');
221
- md.push('');
222
- md.push(`| 项目 | 值 |`);
223
- md.push(`|------|-----|`);
224
- md.push(`| 名称 | ${pkg?.name || projectName} |`);
225
- if (pkg?.version)
226
- md.push(`| 版本 | ${pkg.version} |`);
227
- md.push(`| 类型 | ${projectTypes.join(', ')} |`);
228
- if (gitInfo?.branch)
229
- md.push(`| Git 分支 | ${gitInfo.branch} |`);
230
- if (gitInfo?.remote)
231
- md.push(`| Git 远程 | ${gitInfo.remote} |`);
232
- md.push('');
233
- md.push('## 目录结构');
234
- md.push('');
235
- md.push('```');
236
- md.push(`${projectName}/`);
237
- for (const line of dirTree) {
238
- md.push(line);
599
+ let jarvisMdContent = '';
600
+ let generationMode = '基础扫描';
601
+ try {
602
+ jarvisMdContent = await generateJarvisMdWithLLM({
603
+ projectName,
604
+ cwd,
605
+ packageJson: pkg,
606
+ projectTypes,
607
+ gitInfo,
608
+ fileStats,
609
+ dirTree,
610
+ });
611
+ generationMode = '大模型总结';
239
612
  }
240
- md.push('```');
241
- md.push('');
242
- // 快速开始
243
- if (pkg?.scripts) {
244
- md.push('## 快速开始');
245
- md.push('');
246
- md.push('```bash');
247
- if (pkg.scripts.install || fs.existsSync(path.join(cwd, 'package-lock.json'))) {
248
- md.push('# 安装依赖');
249
- md.push('npm install');
250
- md.push('');
251
- }
252
- if (pkg.scripts.dev) {
253
- md.push('# 开发模式');
254
- md.push(`npm run dev`);
255
- }
256
- else if (pkg.scripts.start) {
257
- md.push('# 启动');
258
- md.push(`npm run start`);
259
- }
260
- if (pkg.scripts.build) {
261
- md.push('');
262
- md.push('# 构建');
263
- md.push('npm run build');
264
- }
265
- md.push('```');
266
- md.push('');
613
+ catch {
614
+ jarvisMdContent = generateBasicJarvisMd({
615
+ cwd,
616
+ projectName,
617
+ packageJson: pkg,
618
+ projectTypes,
619
+ gitInfo,
620
+ dirTree,
621
+ });
267
622
  }
268
- md.push('---');
269
- md.push('');
270
- md.push(`> 由 ${APP_NAME} /init 自动生成`);
271
- md.push('');
272
- const jarvisMdContent = md.join('\n');
273
623
  const jarvisMdPath = path.join(cwd, 'JARVIS.md');
274
624
  const isNew = !fs.existsSync(jarvisMdPath);
275
625
  // 写入文件
276
626
  fs.writeFileSync(jarvisMdPath, jarvisMdContent, 'utf-8');
627
+ display.push(`[ 输出 ]`);
628
+ display.push(` 生成方式: ${generationMode}`);
277
629
  display.push(isNew ? '已生成 JARVIS.md' : '已更新 JARVIS.md');
278
630
  return {
279
631
  displayText: display.join('\n'),
@@ -174,7 +174,7 @@ export default function MultilineInput({ value, onChange, onSubmit, onUpArrow, o
174
174
  if (raw === '\r' || raw === '\n') {
175
175
  // 斜杠菜单激活时,回车 = 选中当前项
176
176
  if (slashMenuActiveRef.current) {
177
- onSlashMenuSelectRef.current?.();
177
+ onSubmitRef.current(v);
178
178
  return;
179
179
  }
180
180
  const expanded = expandPlaceholders(v);
@@ -256,7 +256,7 @@ export default function MultilineInput({ value, onChange, onSubmit, onUpArrow, o
256
256
  return;
257
257
  // 忽略控制字符和转义序列
258
258
  if (raw === '\t') {
259
- // 斜杠菜单激活时,Tab = 选中当前项
259
+ // 斜杠菜单激活时,Tab = 补全当前项到输入框
260
260
  if (slashMenuActiveRef.current) {
261
261
  onSlashMenuSelectRef.current?.();
262
262
  }
@@ -0,0 +1,10 @@
1
+ export declare const DREAM_FILE_PATH: string;
2
+ export declare function ensureDreamHomeDir(): string;
3
+ export declare function ensureDreamFile(): string;
4
+ export declare function readDream(): string;
5
+ export declare function readDreamForPrompt(maxChars?: number): string;
6
+ export declare function initializeDreamCache(): string;
7
+ export declare function getCachedDream(): string;
8
+ export declare function replaceDream(content: string, options?: {
9
+ updateCache?: boolean;
10
+ }): void;
@@ -0,0 +1,60 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ export const DREAM_FILE_PATH = path.join(os.homedir(), '.jarvis', 'DREAM.md');
5
+ const DREAM_FILE_HEADER = [
6
+ '# Jarvis 梦境',
7
+ '',
8
+ '> 这里记录 Jarvis 在闲置时对当前会话、用户画像与长期记忆的回顾与发散。',
9
+ '> 内容用于塑造更稳定的表达气质、关注点与人格倾向,不应包含敏感信息或伪造事实。',
10
+ '',
11
+ ].join('\n');
12
+ let cachedDream = '';
13
+ export function ensureDreamHomeDir() {
14
+ const dir = path.dirname(DREAM_FILE_PATH);
15
+ if (!fs.existsSync(dir)) {
16
+ fs.mkdirSync(dir, { recursive: true });
17
+ }
18
+ return dir;
19
+ }
20
+ export function ensureDreamFile() {
21
+ ensureDreamHomeDir();
22
+ if (!fs.existsSync(DREAM_FILE_PATH)) {
23
+ fs.writeFileSync(DREAM_FILE_PATH, DREAM_FILE_HEADER, 'utf-8');
24
+ }
25
+ return DREAM_FILE_PATH;
26
+ }
27
+ export function readDream() {
28
+ try {
29
+ ensureDreamFile();
30
+ return fs.readFileSync(DREAM_FILE_PATH, 'utf-8').trim();
31
+ }
32
+ catch {
33
+ return '';
34
+ }
35
+ }
36
+ export function readDreamForPrompt(maxChars = 8000) {
37
+ const content = readDream();
38
+ if (!content)
39
+ return '';
40
+ if (content.length <= maxChars)
41
+ return content;
42
+ return `...[已截断,仅保留最近 ${maxChars} 字符]\n${content.slice(-maxChars)}`;
43
+ }
44
+ export function initializeDreamCache() {
45
+ cachedDream = readDreamForPrompt();
46
+ return cachedDream;
47
+ }
48
+ export function getCachedDream() {
49
+ return cachedDream;
50
+ }
51
+ export function replaceDream(content, options) {
52
+ ensureDreamFile();
53
+ const normalized = content.trim();
54
+ const finalContent = normalized || DREAM_FILE_HEADER.trimEnd();
55
+ fs.writeFileSync(DREAM_FILE_PATH, `${finalContent}\n`, 'utf-8');
56
+ if (options?.updateCache) {
57
+ cachedDream = readDreamForPrompt();
58
+ }
59
+ }
60
+ initializeDreamCache();
@@ -1,4 +1,8 @@
1
1
  export declare const USER_PROFILE_PATH: string;
2
2
  export declare function ensureJarvisHomeDir(): string;
3
3
  export declare function readUserProfile(): string;
4
- export declare function writeUserProfile(content: string): void;
4
+ export declare function initializeUserProfileCache(): string;
5
+ export declare function getCachedUserProfile(): string;
6
+ export declare function writeUserProfile(content: string, options?: {
7
+ updateCache?: boolean;
8
+ }): void;
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import os from 'os';
3
3
  import path from 'path';
4
4
  export const USER_PROFILE_PATH = path.join(os.homedir(), '.jarvis', 'USER.md');
5
+ let cachedUserProfile = '';
5
6
  export function ensureJarvisHomeDir() {
6
7
  const dir = path.dirname(USER_PROFILE_PATH);
7
8
  if (!fs.existsSync(dir)) {
@@ -19,7 +20,19 @@ export function readUserProfile() {
19
20
  return '';
20
21
  }
21
22
  }
22
- export function writeUserProfile(content) {
23
+ export function initializeUserProfileCache() {
24
+ cachedUserProfile = readUserProfile();
25
+ return cachedUserProfile;
26
+ }
27
+ export function getCachedUserProfile() {
28
+ return cachedUserProfile;
29
+ }
30
+ export function writeUserProfile(content, options) {
23
31
  ensureJarvisHomeDir();
24
- fs.writeFileSync(USER_PROFILE_PATH, content.trimEnd() + '\n', 'utf-8');
32
+ const normalizedContent = content.trimEnd();
33
+ fs.writeFileSync(USER_PROFILE_PATH, normalizedContent + '\n', 'utf-8');
34
+ if (options?.updateCache) {
35
+ cachedUserProfile = normalizedContent;
36
+ }
25
37
  }
38
+ initializeUserProfileCache();
@@ -21,6 +21,11 @@ export declare class QueryEngine {
21
21
  private transcript;
22
22
  private workerBridge;
23
23
  private memoryUpdateQueue;
24
+ private userProfileUpdateQueue;
25
+ private dreamUpdateQueue;
26
+ private dreamTimer;
27
+ private lastActivityAt;
28
+ private isQueryRunning;
24
29
  constructor();
25
30
  /** 注册持久 UI 回调,供后台 spawn_agent 子 Agent 推送消息 */
26
31
  registerUIBus(onMessage: (msg: Message) => void, onUpdateMessage: (id: string, updates: Partial<Message>) => void): void;
@@ -40,6 +45,10 @@ export declare class QueryEngine {
40
45
  /** 保存会话到文件 */
41
46
  private saveSession;
42
47
  private schedulePersistentMemoryUpdate;
48
+ private scheduleUserProfileUpdate;
49
+ private touchActivity;
50
+ private scheduleDreamTimer;
51
+ private runDreamCycle;
43
52
  /** 列出所有历史会话(按更新时间倒序),返回摘要信息 */
44
53
  static listSessions(): {
45
54
  id: string;