@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.
- package/README.md +171 -215
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +17 -15
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +414 -62
- package/dist/components/MultilineInput.js +2 -2
- package/dist/config/dream.d.ts +10 -0
- package/dist/config/dream.js +60 -0
- package/dist/config/userProfile.d.ts +5 -1
- package/dist/config/userProfile.js +15 -2
- package/dist/core/QueryEngine.d.ts +9 -0
- package/dist/core/QueryEngine.js +83 -8
- package/dist/core/WorkerBridge.d.ts +3 -1
- package/dist/core/WorkerBridge.js +2 -2
- package/dist/core/query.d.ts +5 -1
- package/dist/core/query.js +4 -4
- package/dist/core/queryWorker.d.ts +3 -0
- package/dist/core/queryWorker.js +1 -1
- package/dist/hooks/useDoubleCtrlCExit.js +32 -15
- package/dist/hooks/useSlashMenu.d.ts +3 -1
- package/dist/hooks/useSlashMenu.js +58 -71
- package/dist/screens/repl.js +69 -36
- package/dist/screens/slashCommands.d.ts +1 -1
- package/dist/screens/slashCommands.js +2 -2
- package/dist/services/api/llm.d.ts +5 -2
- package/dist/services/api/llm.js +20 -7
- package/dist/services/api/mock.d.ts +3 -1
- package/dist/services/api/mock.js +1 -1
- package/dist/services/dream.d.ts +7 -0
- package/dist/services/dream.js +171 -0
- package/dist/services/userProfile.d.ts +1 -0
- package/dist/services/userProfile.js +15 -0
- package/dist/types/index.d.ts +3 -1
- package/package.json +3 -3
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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;
|