@code4bug/jarvis-agent 1.1.4 → 1.1.6

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.
@@ -0,0 +1,127 @@
1
+ import { loadConfig, getActiveModel } from '../config/loader.js';
2
+ import { readUserProfile, writeUserProfile } from '../config/userProfile.js';
3
+ import { logError, logInfo, logWarn } from '../core/logger.js';
4
+ function extractMessageText(content) {
5
+ if (typeof content === 'string')
6
+ return content;
7
+ if (!Array.isArray(content))
8
+ return '';
9
+ return content
10
+ .map((item) => (item?.type === 'text' ? item.text ?? '' : ''))
11
+ .join('');
12
+ }
13
+ function buildProfilePrompt(userInput, existingProfile) {
14
+ return [
15
+ '请基于“已有用户画像”和“最新用户输入”,整理一份新的 USER.md。',
16
+ '',
17
+ '目标:让后续智能体在系统提示词中读取后,能够更了解用户。',
18
+ '',
19
+ '必须遵守:',
20
+ '1. 只保留对后续交互有帮助的信息。',
21
+ '2. 有明确依据的内容写成确定描述;只有弱信号时写成“可能/倾向于”。',
22
+ '3. 没有依据时明确写“未知”,不要编造职业、年龄、经历。',
23
+ '4. 输出必须是中文 Markdown,不要输出代码块围栏,不要解释你的推理过程。',
24
+ '5. 画像应简洁稳定,避免复述用户原话。',
25
+ '',
26
+ '请严格按下面结构输出:',
27
+ '# 用户画像',
28
+ '## 基本信息',
29
+ '- 职业:',
30
+ '- 年龄阶段:',
31
+ '- 所在地区:',
32
+ '- 语言偏好:',
33
+ '',
34
+ '## 思维与沟通',
35
+ '- 思维习惯:',
36
+ '- 沟通风格:',
37
+ '- 决策偏好:',
38
+ '',
39
+ '## 能力与背景',
40
+ '- 技术背景:',
41
+ '- 专业领域:',
42
+ '- 熟悉工具:',
43
+ '',
44
+ '## 当前关注点',
45
+ '- 长期目标:',
46
+ '- 近期任务倾向:',
47
+ '- 约束与偏好:',
48
+ '',
49
+ '## 交互建议',
50
+ '- 回答策略:',
51
+ '- 需要避免:',
52
+ '',
53
+ '## 置信说明',
54
+ '- 高置信信息:',
55
+ '- 低置信推断:',
56
+ '- 明显未知:',
57
+ '',
58
+ '---',
59
+ '已有用户画像:',
60
+ existingProfile || '(暂无)',
61
+ '',
62
+ '最新用户输入:',
63
+ userInput,
64
+ ].join('\n');
65
+ }
66
+ export async function updateUserProfileFromInput(userInput) {
67
+ const normalizedInput = userInput.trim();
68
+ if (!normalizedInput)
69
+ return false;
70
+ const config = loadConfig();
71
+ const activeModel = getActiveModel(config);
72
+ if (!activeModel) {
73
+ logWarn('user_profile.skip.no_active_model');
74
+ return false;
75
+ }
76
+ const prompt = buildProfilePrompt(normalizedInput, readUserProfile());
77
+ const body = {
78
+ model: activeModel.model,
79
+ messages: [
80
+ {
81
+ role: 'system',
82
+ content: '你是一个严谨的用户画像整理助手,负责维护 ~/.jarvis/USER.md。输出必须是可直接写入文件的 Markdown 正文。',
83
+ },
84
+ {
85
+ role: 'user',
86
+ content: prompt,
87
+ },
88
+ ],
89
+ max_tokens: Math.min(activeModel.max_tokens ?? 4096, 1200),
90
+ temperature: 0.2,
91
+ stream: false,
92
+ };
93
+ if (activeModel.extra_body) {
94
+ Object.assign(body, activeModel.extra_body);
95
+ }
96
+ try {
97
+ const response = await fetch(activeModel.api_url, {
98
+ method: 'POST',
99
+ headers: {
100
+ 'Content-Type': 'application/json',
101
+ Authorization: `Bearer ${activeModel.api_key}`,
102
+ },
103
+ body: JSON.stringify(body),
104
+ });
105
+ if (!response.ok) {
106
+ const errorText = await response.text().catch(() => '');
107
+ throw new Error(`API 错误 ${response.status}: ${errorText.slice(0, 300)}`);
108
+ }
109
+ const data = await response.json();
110
+ const content = extractMessageText(data.choices?.[0]?.message?.content).trim();
111
+ if (!content) {
112
+ throw new Error('用户画像生成结果为空');
113
+ }
114
+ writeUserProfile(content);
115
+ logInfo('user_profile.updated', {
116
+ inputLength: normalizedInput.length,
117
+ outputLength: content.length,
118
+ });
119
+ return true;
120
+ }
121
+ catch (error) {
122
+ logError('user_profile.update_failed', error, {
123
+ inputLength: normalizedInput.length,
124
+ });
125
+ return false;
126
+ }
127
+ }
@@ -12,7 +12,8 @@ import { sendToAgent } from './sendToAgent.js';
12
12
  import { publishMessage } from './publishMessage.js';
13
13
  import { subscribeMessage } from './subscribeMessage.js';
14
14
  import { readChannel } from './readChannel.js';
15
- export { readFile, writeFile, runCommand, listDirectory, searchFiles, semanticSearch, createSkill, runAgent, spawnAgent, sendToAgent, publishMessage, subscribeMessage, readChannel, };
15
+ import { manageMemory } from './manageMemory.js';
16
+ export { readFile, writeFile, runCommand, listDirectory, searchFiles, semanticSearch, createSkill, runAgent, spawnAgent, sendToAgent, publishMessage, subscribeMessage, readChannel, manageMemory, };
16
17
  /** 所有内置工具 */
17
18
  export declare const allTools: Tool[];
18
19
  /** 按名称查找内置工具 */
@@ -11,13 +11,15 @@ import { sendToAgent } from './sendToAgent.js';
11
11
  import { publishMessage } from './publishMessage.js';
12
12
  import { subscribeMessage } from './subscribeMessage.js';
13
13
  import { readChannel } from './readChannel.js';
14
- export { readFile, writeFile, runCommand, listDirectory, searchFiles, semanticSearch, createSkill, runAgent, spawnAgent, sendToAgent, publishMessage, subscribeMessage, readChannel, };
14
+ import { manageMemory } from './manageMemory.js';
15
+ export { readFile, writeFile, runCommand, listDirectory, searchFiles, semanticSearch, createSkill, runAgent, spawnAgent, sendToAgent, publishMessage, subscribeMessage, readChannel, manageMemory, };
15
16
  /** 所有内置工具 */
16
17
  export const allTools = [
17
18
  readFile, writeFile, runCommand, listDirectory, searchFiles,
18
19
  semanticSearch, createSkill,
19
20
  runAgent, spawnAgent, sendToAgent,
20
21
  publishMessage, subscribeMessage, readChannel,
22
+ manageMemory,
21
23
  ];
22
24
  /** 按名称查找内置工具 */
23
25
  export function findTool(name) {
@@ -0,0 +1,2 @@
1
+ import { Tool } from '../types/index.js';
2
+ export declare const manageMemory: Tool;
@@ -0,0 +1,46 @@
1
+ import { MEMORY_FILE_PATH, appendPersistentMemory, readPersistentMemory, replacePersistentMemory, ensureMemoryFile, } from '../config/memory.js';
2
+ export const manageMemory = {
3
+ name: 'manage_memory',
4
+ description: [
5
+ '管理智能体长期记忆文件 ~/.jarvis/MEMORY.md。',
6
+ '可读取、追加、覆盖记忆,也可返回记忆文件路径。',
7
+ '适合沉淀可复用的经验、技能、偏好、约束和稳定环境事实。',
8
+ ].join('\n'),
9
+ parameters: {
10
+ action: {
11
+ type: 'string',
12
+ description: '操作类型:read | append | replace | path',
13
+ required: true,
14
+ },
15
+ content: {
16
+ type: 'string',
17
+ description: 'append 或 replace 时要写入的 Markdown 内容',
18
+ required: false,
19
+ },
20
+ },
21
+ execute: async (args) => {
22
+ const action = String(args.action || '').trim();
23
+ ensureMemoryFile();
24
+ if (action === 'path') {
25
+ return MEMORY_FILE_PATH;
26
+ }
27
+ if (action === 'read') {
28
+ return readPersistentMemory() || '(MEMORY.md 为空)';
29
+ }
30
+ if (action === 'append') {
31
+ const content = String(args.content || '').trim();
32
+ if (!content)
33
+ throw new Error('append 操作需要提供 content');
34
+ appendPersistentMemory(content);
35
+ return `长期记忆已追加到 ${MEMORY_FILE_PATH}`;
36
+ }
37
+ if (action === 'replace') {
38
+ const content = String(args.content || '').trim();
39
+ if (!content)
40
+ throw new Error('replace 操作需要提供 content');
41
+ replacePersistentMemory(content);
42
+ return `长期记忆已覆盖写入 ${MEMORY_FILE_PATH}`;
43
+ }
44
+ throw new Error(`不支持的 action: ${action}`);
45
+ },
46
+ };
@@ -1,10 +1,12 @@
1
1
  import { exec } from 'child_process';
2
2
  import { sanitizeOutput } from '../core/safeguard.js';
3
+ import { logError, logInfo, logWarn } from '../core/logger.js';
3
4
  /**
4
5
  * 异步执行命令,支持通过 abortSignal 中断子进程
5
6
  */
6
7
  function execAsync(command, options, abortSignal) {
7
8
  return new Promise((resolve, reject) => {
9
+ logInfo('bash.exec.start', { command });
8
10
  const child = exec(command, options, (error, stdout, stderr) => {
9
11
  // 清理轮询
10
12
  if (pollTimer !== null)
@@ -12,6 +14,10 @@ function execAsync(command, options, abortSignal) {
12
14
  // 被中断时直接返回已有输出,不视为错误
13
15
  if (abortSignal?.aborted) {
14
16
  const partial = sanitizeOutput(String(stdout ?? '').trim());
17
+ logWarn('bash.exec.aborted', {
18
+ command,
19
+ stdoutLength: String(stdout ?? '').length,
20
+ });
15
21
  resolve(partial ? `(命令被中断)\n${partial}` : '(命令被中断)');
16
22
  return;
17
23
  }
@@ -25,9 +31,18 @@ function execAsync(command, options, abortSignal) {
25
31
  parts.push(`[exit code] ${error.code}`);
26
32
  if (parts.length === 0)
27
33
  parts.push(error.message);
34
+ logError('bash.exec.failed', error, {
35
+ command,
36
+ stdoutLength: String(stdout ?? '').length,
37
+ stderrLength: String(stderr ?? '').length,
38
+ });
28
39
  reject(new Error(`命令执行失败:\n${parts.join('\n')}`));
29
40
  return;
30
41
  }
42
+ logInfo('bash.exec.done', {
43
+ command,
44
+ stdoutLength: String(stdout ?? '').length,
45
+ });
31
46
  resolve(sanitizeOutput(String(stdout).trim()) || '(命令执行完成,无输出)');
32
47
  });
33
48
  // 轮询 abortSignal,检测到中断时 kill 子进程
@@ -38,6 +53,7 @@ function execAsync(command, options, abortSignal) {
38
53
  if (pollTimer !== null)
39
54
  clearInterval(pollTimer);
40
55
  pollTimer = null;
56
+ logWarn('bash.exec.kill_requested', { command, pid: child.pid });
41
57
  // 先尝试 SIGTERM,给进程优雅退出的机会
42
58
  try {
43
59
  child.kill('SIGTERM');
@@ -15,6 +15,7 @@ import { agentUIBus } from '../core/AgentRegistry.js';
15
15
  import { agentMessageBus } from '../core/AgentMessageBus.js';
16
16
  import { getBus } from '../core/busAccess.js';
17
17
  import { pendingSpawnRequests, incrementActiveAgents, decrementActiveAgents } from '../core/spawnRegistry.js';
18
+ import { logError, logInfo } from '../core/logger.js';
18
19
  /** 运行中的 task_id 集合(主线程侧,防止重复启动) */
19
20
  const runningAgents = new Set();
20
21
  function resolveAgentLabel(taskId, role) {
@@ -31,12 +32,18 @@ function resolveAgentLabel(taskId, role) {
31
32
  */
32
33
  export function spawnSubAgentInMainThread(taskId, instruction, agentLabel, allowedTools) {
33
34
  if (runningAgents.has(taskId)) {
35
+ logInfo('spawn_agent.duplicate', { taskId, agentLabel });
34
36
  return `子 Agent [${taskId}] 已在运行中,请使用 send_to_agent 向其发送消息,或使用不同的 task_id 启动新实例。`;
35
37
  }
36
38
  const replyChannel = `agent-reply:${taskId}`;
37
39
  const bridge = new SubAgentBridge();
38
40
  runningAgents.add(taskId);
39
41
  incrementActiveAgents();
42
+ logInfo('spawn_agent.started', {
43
+ taskId,
44
+ agentLabel,
45
+ allowedTools,
46
+ });
40
47
  bridge.run({ taskId, instruction, allowedTools }, {
41
48
  onMessage: (_tid, msg) => {
42
49
  const tagged = { ...msg, subAgentId: agentLabel };
@@ -51,10 +58,20 @@ export function spawnSubAgentInMainThread(taskId, instruction, agentLabel, allow
51
58
  }).then((result) => {
52
59
  runningAgents.delete(taskId);
53
60
  decrementActiveAgents();
61
+ logInfo('spawn_agent.done', {
62
+ taskId,
63
+ agentLabel,
64
+ status: result.status,
65
+ outputLength: result.output.length,
66
+ });
54
67
  agentMessageBus.publish(taskId, replyChannel, `[AGENT_DONE] ${result.output || '子 Agent 已完成'}`);
55
68
  }).catch((err) => {
56
69
  runningAgents.delete(taskId);
57
70
  decrementActiveAgents();
71
+ logError('spawn_agent.failed', err, {
72
+ taskId,
73
+ agentLabel,
74
+ });
58
75
  agentMessageBus.publish(taskId, replyChannel, `[AGENT_ERROR] ${err.message}`);
59
76
  });
60
77
  const inboxChannel = `agent-inbox:${taskId}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code4bug/jarvis-agent",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "基于 React + TypeScript + Ink 构建的命令行智能体交互界面",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",