@code4bug/jarvis-agent 1.3.8 → 1.3.10

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 CHANGED
@@ -128,6 +128,7 @@ jarvis --version
128
128
  | 命令 | 说明 |
129
129
  | --- | --- |
130
130
  | `/init` | 扫描当前项目并生成 `JARVIS.md` |
131
+ | `/about` | 查看 Jarvis 的详细特性、功能与信息 |
131
132
  | `/new` | 开启新会话 |
132
133
  | `/resume` | 恢复历史会话 |
133
134
  | `/resume <ID>` | 直接恢复指定历史会话 |
package/dist/cli.js CHANGED
@@ -2,10 +2,19 @@
2
2
  import { APP_VERSION } from './config/constants.js';
3
3
  import { ensureLoggerReady, logError, logInfo } from './core/logger.js';
4
4
  import { startJarvis } from './index.js';
5
- const arg = process.argv[2];
5
+ const args = process.argv.slice(2);
6
+ const arg = args[0];
7
+ function printCliUsage() {
8
+ console.log([
9
+ '用法:',
10
+ ' jarvis',
11
+ ' jarvis --version',
12
+ ' jarvis --resume <sessionId>',
13
+ ].join('\n'));
14
+ }
6
15
  ensureLoggerReady();
7
16
  logInfo('cli.launch', {
8
- argv: process.argv.slice(2),
17
+ argv: args,
9
18
  version: APP_VERSION,
10
19
  });
11
20
  if (arg === '--version' || arg === '-v' || arg === 'version') {
@@ -13,10 +22,22 @@ if (arg === '--version' || arg === '-v' || arg === 'version') {
13
22
  console.log(APP_VERSION);
14
23
  process.exit(0);
15
24
  }
25
+ if (arg === '--resume') {
26
+ const sessionId = args[1]?.trim();
27
+ if (!sessionId) {
28
+ logError('cli.resume.missing_session_id', new Error('missing session id'));
29
+ printCliUsage();
30
+ process.exit(1);
31
+ }
32
+ logInfo('cli.resume', { sessionId });
33
+ startJarvis({ initialResumeSessionId: sessionId });
34
+ }
35
+ else {
36
+ startJarvis();
37
+ }
16
38
  process.on('uncaughtException', (error) => {
17
39
  logError('process.uncaught_exception', error);
18
40
  });
19
41
  process.on('unhandledRejection', (reason) => {
20
42
  logError('process.unhandled_rejection', reason);
21
43
  });
22
- startJarvis();
@@ -7,6 +7,7 @@
7
7
  /** 内置命令 */
8
8
  const builtinCommands = [
9
9
  { name: 'init', description: '初始化项目信息,生成 JARVIS.md', category: 'builtin', submitMode: 'action' },
10
+ { name: 'about', description: '查看 Jarvis 的详细特性、功能与信息', category: 'builtin', submitMode: 'action' },
10
11
  { name: 'new', description: '开启新会话,重新初始化上下文', category: 'builtin', submitMode: 'action' },
11
12
  { name: 'exit', description: '退出应用程序', category: 'builtin', submitMode: 'action' },
12
13
  { name: 'quit', description: '退出应用程序', category: 'builtin', submitMode: 'action' },
package/dist/index.d.ts CHANGED
@@ -1 +1,4 @@
1
- export declare function startJarvis(): void;
1
+ export interface StartJarvisOptions {
2
+ initialResumeSessionId?: string;
3
+ }
4
+ export declare function startJarvis(options?: StartJarvisOptions): void;
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { render } from 'ink';
3
3
  import { logInfo } from './core/logger.js';
4
4
  import AppBootstrap from './screens/AppBootstrap.js';
5
- export function startJarvis() {
6
- logInfo('app.render.start');
7
- render(_jsx(AppBootstrap, {}), { exitOnCtrlC: false });
5
+ export function startJarvis(options = {}) {
6
+ logInfo('app.render.start', { initialResumeSessionId: options.initialResumeSessionId });
7
+ render(_jsx(AppBootstrap, { initialResumeSessionId: options.initialResumeSessionId }), { exitOnCtrlC: false });
8
8
  }
@@ -1 +1,5 @@
1
- export default function AppBootstrap(): import("react/jsx-runtime").JSX.Element;
1
+ interface AppBootstrapProps {
2
+ initialResumeSessionId?: string;
3
+ }
4
+ export default function AppBootstrap({ initialResumeSessionId }: AppBootstrapProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -3,10 +3,10 @@ import { useState } from 'react';
3
3
  import REPL from './repl.js';
4
4
  import SetupWizard from './setup/SetupWizard.js';
5
5
  import { checkBootstrapStatus } from '../config/bootstrap.js';
6
- export default function AppBootstrap() {
6
+ export default function AppBootstrap({ initialResumeSessionId }) {
7
7
  const [status, setStatus] = useState(() => checkBootstrapStatus());
8
8
  if (status.ok) {
9
- return _jsx(REPL, {});
9
+ return _jsx(REPL, { initialResumeSessionId: initialResumeSessionId });
10
10
  }
11
11
  return (_jsx(SetupWizard, { initialStatus: status, onCompleted: () => {
12
12
  setStatus(checkBootstrapStatus());
@@ -1 +1,5 @@
1
- export default function REPL(): import("react/jsx-runtime").JSX.Element;
1
+ interface REPLProps {
2
+ initialResumeSessionId?: string;
3
+ }
4
+ export default function REPL({ initialResumeSessionId }: REPLProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -21,14 +21,28 @@ import { getAgentSubCommands } from '../commands/index.js';
21
21
  import { setActiveAgent } from '../config/agentState.js';
22
22
  import { buildShortcutHelpText } from '../config/shortcuts.js';
23
23
  import { hideTerminalCursor, showTerminalCursor } from '../terminal/cursor.js';
24
- export default function REPL() {
24
+ export default function REPL({ initialResumeSessionId }) {
25
25
  const { exit } = useApp();
26
26
  const width = useTerminalWidth();
27
27
  const windowFocused = useWindowFocus();
28
+ const buildExitHint = useCallback(() => {
29
+ const sessionId = sessionRef.current.id?.trim();
30
+ if (!sessionId)
31
+ return '';
32
+ const separatorWidth = Math.max(process.stdout.columns ?? 0, 80);
33
+ const separator = '─'.repeat(separatorWidth);
34
+ return `\n${separator}\n\nResume this session with:\njarvis --resume ${sessionId}\n\n`;
35
+ }, []);
28
36
  const handleExit = useCallback(() => {
37
+ const exitHint = buildExitHint();
29
38
  exit();
30
- setTimeout(() => process.exit(0), 50);
31
- }, [exit]);
39
+ setTimeout(() => {
40
+ showTerminalCursor();
41
+ if (exitHint)
42
+ process.stdout.write(exitHint);
43
+ process.exit(0);
44
+ }, 50);
45
+ }, [buildExitHint, exit]);
32
46
  const { countdown, handleCtrlC } = useDoubleCtrlCExit(handleExit);
33
47
  const { pushHistory, navigateUp, navigateDown, resetNavigation } = useInputHistory();
34
48
  const [messages, setMessages] = useState([]);
@@ -145,6 +159,43 @@ export default function REPL() {
145
159
  logInfo('ui.repl.unmounted');
146
160
  };
147
161
  }, []);
162
+ useEffect(() => {
163
+ if (!initialResumeSessionId || !engineRef.current)
164
+ return;
165
+ const result = engineRef.current.loadSession(initialResumeSessionId);
166
+ if (result) {
167
+ stopAll();
168
+ setMessages([
169
+ ...result.messages,
170
+ {
171
+ id: `resume-cli-${Date.now()}`,
172
+ type: 'system',
173
+ status: 'success',
174
+ content: `已通过启动参数恢复会话 ${initialResumeSessionId.slice(0, 8)}...(${result.messages.length} 条消息)`,
175
+ timestamp: Date.now(),
176
+ },
177
+ ]);
178
+ sessionRef.current = result.session;
179
+ tokenCountRef.current = result.session.totalTokens;
180
+ syncTokenDisplay(result.session.totalTokens);
181
+ setLoopState(null);
182
+ setIsProcessing(false);
183
+ setShowWelcome(false);
184
+ logInfo('ui.resume_from_cli.success', {
185
+ sessionId: initialResumeSessionId,
186
+ messageCount: result.messages.length,
187
+ });
188
+ return;
189
+ }
190
+ setMessages((prev) => [...prev, {
191
+ id: `resume-cli-error-${Date.now()}`,
192
+ type: 'error',
193
+ status: 'error',
194
+ content: `启动时恢复会话失败:${initialResumeSessionId} 不存在或已损坏`,
195
+ timestamp: Date.now(),
196
+ }]);
197
+ logWarn('ui.resume_from_cli.failed', { sessionId: initialResumeSessionId });
198
+ }, [initialResumeSessionId, stopAll, syncTokenDisplay]);
148
199
  // 订阅后台 SubAgent 计数变化
149
200
  useEffect(() => {
150
201
  return subscribeAgentCount((count) => setActiveAgents(count));
@@ -3,6 +3,7 @@ import { APP_VERSION } from '../config/constants.js';
3
3
  import { allTools } from '../tools/index.js';
4
4
  import { listSkills } from '../skills/index.js';
5
5
  import { getExternalSkillsDir } from '../skills/loader.js';
6
+ import { loadAllAgents } from '../agents/index.js';
6
7
  import { listPermanentAuthorizations, DANGER_RULES, } from '../core/safeguard.js';
7
8
  /**
8
9
  * 斜杠命令执行器
@@ -25,6 +26,7 @@ export async function executeSlashCommand(cmdName) {
25
26
  const helpText = [
26
27
  '可用命令:',
27
28
  ' /init 初始化项目信息,生成 JARVIS.md',
29
+ ' /about 查看 Jarvis 的详细特性、功能与信息',
28
30
  ' /new 开启新会话,重新初始化上下文',
29
31
  ' /exit 退出应用程序',
30
32
  ' /quit 退出应用程序',
@@ -59,6 +61,62 @@ export async function executeSlashCommand(cmdName) {
59
61
  timestamp: Date.now(),
60
62
  };
61
63
  }
64
+ case 'about': {
65
+ const skills = listSkills();
66
+ const agents = Array.from(loadAllAgents().values());
67
+ const parts = [
68
+ 'Jarvis 详细信息',
69
+ '',
70
+ `- 版本:${APP_VERSION}`,
71
+ '- 定位:运行在终端里的轻量级 AI Agent,面向代码、命令行与多步骤任务协作',
72
+ '- 技术栈:React + TypeScript + Ink',
73
+ '',
74
+ '核心能力:',
75
+ '- Agentic Loop:可连续推理、调用工具、读取结果并继续决策',
76
+ '- 流式终端交互:在终端里实时展示回复、状态与工具执行过程',
77
+ '- 文件与目录操作:支持读写文件、列目录、搜索内容、语义检索',
78
+ '- 命令执行:通过 Bash 工具执行命令,并统一经过安全围栏',
79
+ '- 多智能体协作:支持同步子 Agent、后台子 Agent 与消息总线通信',
80
+ '- 外部扩展:支持从 ~/.jarvis/skills/ 动态加载外部 Skill',
81
+ '- 会话与记忆:支持会话持久化、摘要、Token 统计与长期记忆',
82
+ '',
83
+ '当前实例概况:',
84
+ `- 内置工具:${allTools.length} 个`,
85
+ `- 外部 Skills:${skills.length} 个`,
86
+ `- 可切换 Agents:${agents.length} 个`,
87
+ '',
88
+ '内置工具:',
89
+ ...allTools.map((tool, index) => ` ${index + 1}. ${tool.name} - ${tool.description.split('\n')[0]}`),
90
+ '',
91
+ '可切换 Agents:',
92
+ ...agents.map((agent, index) => ` ${index + 1}. ${agent.meta.name} - ${agent.meta.description}`),
93
+ '',
94
+ '安全与交互:',
95
+ '- 危险命令会被安全围栏识别,并支持交互式确认与持久化授权',
96
+ '- 支持斜杠菜单、历史会话恢复、会话回退与快捷键操作',
97
+ '- 支持多个只读工具并行执行,提高响应效率',
98
+ '',
99
+ '常用入口:',
100
+ '- /help:查看所有斜杠命令',
101
+ '- /skills:查看当前 tools 和 skills',
102
+ '- /permissions:查看持久化授权列表',
103
+ '- /version:查看当前版本号',
104
+ ];
105
+ if (skills.length > 0) {
106
+ parts.push('');
107
+ parts.push('已加载 Skills:');
108
+ skills.forEach((skill, index) => {
109
+ parts.push(` ${index + 1}. ${skill.meta.name} - ${skill.meta.description}`);
110
+ });
111
+ }
112
+ return {
113
+ id: `about-${Date.now()}`,
114
+ type: 'system',
115
+ status: 'success',
116
+ content: parts.join('\n'),
117
+ timestamp: Date.now(),
118
+ };
119
+ }
62
120
  case 'permissions': {
63
121
  const perms = listPermanentAuthorizations();
64
122
  const lines = ['持久化授权列表 (~/.jarvis/.permissions.json)', ''];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code4bug/jarvis-agent",
3
- "version": "1.3.8",
3
+ "version": "1.3.10",
4
4
  "description": "基于 React + TypeScript + Ink 构建的命令行智能体交互界面",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",