@code4bug/jarvis-agent 1.3.2 → 1.3.4

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 @@
8
8
  const builtinCommands = [
9
9
  { name: 'init', description: '初始化项目信息,生成 JARVIS.md', category: 'builtin', submitMode: 'action' },
10
10
  { name: 'new', description: '开启新会话,重新初始化上下文', category: 'builtin', submitMode: 'action' },
11
+ { name: 'exit', description: '退出应用程序', category: 'builtin', submitMode: 'action' },
12
+ { name: 'quit', description: '退出应用程序', category: 'builtin', submitMode: 'action' },
13
+ { name: 'bye', description: '退出应用程序', category: 'builtin', submitMode: 'action' },
11
14
  { name: 'resume', description: '恢复历史会话上下文', category: 'builtin', submitMode: 'list' },
12
15
  { name: 'help', description: '显示帮助信息', category: 'builtin', submitMode: 'action' },
13
16
  { name: 'agent', description: '切换智能体', category: 'builtin', submitMode: 'list' },
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
3
  * 危险命令交互式确认组件
4
4
  *
@@ -7,21 +7,18 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
7
7
  * 2. 持久授权(写入 ~/.jarvis/.permissions.json)
8
8
  * 3. 取消执行
9
9
  */
10
- import React, { useState } from 'react';
10
+ import { useState } from 'react';
11
11
  import { Box, Text, useInput, useStdout } from 'ink';
12
12
  const OPTIONS = [
13
13
  { key: 'once', label: '临时执行(仅本次会话)', color: 'yellow' },
14
14
  { key: 'always', label: '持久授权(写入 ~/.jarvis/.permissions.json)', color: 'green' },
15
15
  { key: 'cancel', label: '取消执行', color: 'red' },
16
16
  ];
17
- // 圆角边框字符
18
- const BORDER = { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' };
19
17
  export default function DangerConfirm({ command, reason, ruleName, onSelect }) {
20
18
  const [selectedIndex, setSelectedIndex] = useState(2); // 默认选中「取消」,最安全
21
19
  const { stdout } = useStdout();
22
- const cols = Math.min(stdout?.columns ?? 80, 80);
23
- // 内容区宽度 = 总宽度 - 左右边框(2) - 左右 padding(2)
24
- const innerWidth = cols - 4;
20
+ const cols = Math.min(stdout?.columns ?? 80, 84);
21
+ const contentWidth = Math.max(32, cols - 8);
25
22
  useInput((_input, key) => {
26
23
  if (key.upArrow) {
27
24
  setSelectedIndex((prev) => (prev > 0 ? prev - 1 : OPTIONS.length - 1));
@@ -36,15 +33,14 @@ export default function DangerConfirm({ command, reason, ruleName, onSelect }) {
36
33
  onSelect('cancel');
37
34
  }
38
35
  });
39
- const hLine = BORDER.h.repeat(cols - 2);
40
- const topBorder = BORDER.tl + hLine + BORDER.tr;
41
- const bottomBorder = BORDER.bl + hLine + BORDER.br;
42
- /** 渲染一行带左右边框的内容 */
43
- const row = (children) => (_jsxs(Box, { children: [_jsxs(Text, { color: "yellow", children: [BORDER.v, " "] }), _jsx(Box, { width: innerWidth, children: children }), _jsxs(Text, { color: "yellow", children: [" ", BORDER.v] })] }));
44
- const emptyRow = row(_jsx(Text, { children: " " }));
45
- return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "yellow", children: topBorder }), row(_jsx(Text, { color: "yellow", bold: true, children: "\u5B89\u5168\u56F4\u680F\u62E6\u622A" })), emptyRow, row(_jsxs(Text, { color: "gray", children: ["\u547D\u4EE4: ", _jsx(Text, { color: "white", bold: true, children: command })] })), row(_jsxs(Text, { color: "gray", children: ["\u89C4\u5219: ", _jsx(Text, { color: "cyan", children: ruleName })] })), row(_jsx(Text, { color: "red", children: reason })), emptyRow, row(_jsx(Text, { color: "gray", dimColor: true, children: "\u4F7F\u7528 \u2191\u2193 \u9009\u62E9\uFF0CEnter \u786E\u8BA4\uFF0CESC \u53D6\u6D88:" })), emptyRow, OPTIONS.map((opt, i) => {
46
- const isSelected = i === selectedIndex;
47
- const prefix = isSelected ? '❯ ' : ' ';
48
- return (_jsx(React.Fragment, { children: row(_jsxs(Text, { color: isSelected ? opt.color : 'gray', bold: isSelected, children: [prefix, opt.label] })) }, opt.key));
49
- }), _jsx(Text, { color: "yellow", children: bottomBorder })] }));
36
+ const section = (label, value) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "gray", children: label }), _jsx(Box, { width: contentWidth, marginTop: 0, children: value })] }));
37
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, paddingX: 1, children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { backgroundColor: "yellow", color: "black", bold: true, children: ' 安全围栏 ' }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: "white", children: "\u68C0\u6D4B\u5230\u9AD8\u98CE\u9669\u547D\u4EE4" }), _jsx(Text, { color: "gray", children: "\u8BF7\u786E\u8BA4\u662F\u5426\u7EE7\u7EED\u6267\u884C" })] })] }), section('命令', _jsx(Text, { color: "white", bold: true, wrap: "truncate-end", children: command })), section('规则', _jsx(Text, { color: "cyan", children: ruleName })), section('原因', _jsx(Text, { color: "red", children: reason })), _jsx(Box, { marginTop: 2, children: _jsx(Text, { color: "gray", dimColor: true, children: "\u4F7F\u7528 \u2191\u2193 \u9009\u62E9\uFF0CEnter \u786E\u8BA4\uFF0CESC \u53D6\u6D88" }) }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: OPTIONS.map((opt, i) => {
38
+ const isSelected = i === selectedIndex;
39
+ const prefix = isSelected ? '›' : ' ';
40
+ const textColor = isSelected ? 'black' : opt.color;
41
+ const backgroundColor = isSelected
42
+ ? (opt.color === 'yellow' ? 'yellow' : opt.color === 'green' ? 'green' : 'red')
43
+ : undefined;
44
+ return (_jsx(Box, { marginTop: i === 0 ? 0 : 1, children: _jsx(Text, { color: textColor, backgroundColor: backgroundColor, bold: isSelected, children: ` ${prefix} ${opt.label} ` }) }, opt.key));
45
+ }) })] }));
50
46
  }
@@ -2,6 +2,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { PROJECT_NAME, getContextTokenLimit, getModelName, isThinkingModeToggleEnabled } from '../config/constants.js';
5
+ import { getToolStatsText } from '../tools/index.js';
6
+ function truncateText(text, maxChars) {
7
+ if (maxChars <= 0)
8
+ return '';
9
+ if (text.length <= maxChars)
10
+ return text;
11
+ if (maxChars === 1)
12
+ return '…';
13
+ return `${text.slice(0, maxChars - 1)}…`;
14
+ }
5
15
  /** 生成 token 用量进度条 */
6
16
  function tokenProgressBar(used, limit, barWidth) {
7
17
  const ratio = Math.min(used / limit, 1);
@@ -15,7 +25,8 @@ function StatusBar({ width, totalTokens, activeAgents = 0 }) {
15
25
  const modelName = getModelName();
16
26
  const contextTokenLimit = getContextTokenLimit();
17
27
  const thinkingModeToggleEnabled = isThinkingModeToggleEnabled();
18
- const left = ` ${modelName} │ ${PROJECT_NAME}`;
28
+ const toolStats = getToolStatsText();
29
+ const rawLeft = ` ${modelName} │ ${PROJECT_NAME} │ ${toolStats}`;
19
30
  // 右侧:智能体数量(有后台 Agent 时显示)+ token 进度条 + 思考模式切换(可选)
20
31
  const agentPart = activeAgents > 0 ? `⬡ ${activeAgents} agent${activeAgents > 1 ? 's' : ''} │ ` : '';
21
32
  const tokenLabel = `${totalTokens}/${contextTokenLimit}`;
@@ -24,6 +35,8 @@ function StatusBar({ width, totalTokens, activeAgents = 0 }) {
24
35
  const effortPart = thinkingModeToggleEnabled ? ' │ ● medium · /effort' : '';
25
36
  // 右侧完整文本长度(用于计算间距)
26
37
  const rightLen = agentPart.length + tokenLabel.length + 1 + barWidth + effortPart.length + 1;
38
+ const leftMaxWidth = Math.max(width - rightLen - 1, 1);
39
+ const left = truncateText(rawLeft, leftMaxWidth);
27
40
  const gap = Math.max(width - left.length - rightLen, 1);
28
41
  return (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: left }), _jsx(Text, { children: ' '.repeat(gap) }), activeAgents > 0 && _jsx(Text, { color: "cyan", children: agentPart }), _jsxs(Text, { color: "gray", children: [tokenLabel, " "] }), _jsx(Text, { color: color, children: bar }), thinkingModeToggleEnabled && _jsx(Text, { color: "gray", children: effortPart }), _jsx(Text, { children: " " })] }));
29
42
  }
@@ -208,6 +208,12 @@ export default function REPL() {
208
208
  const parts = trimmed.slice(1).split(/\s+/);
209
209
  const cmdName = parts[0].toLowerCase();
210
210
  const hasArgs = parts.length > 1 && parts.slice(1).join('').length > 0;
211
+ if (['exit', 'quit', 'bye'].includes(cmdName)) {
212
+ setInput('');
213
+ slashMenu.setSlashMenuVisible(false);
214
+ handleExit();
215
+ return;
216
+ }
211
217
  // 内置命令
212
218
  if (['new', 'help', 'init', 'session_clear', 'permissions', 'skills', 'version'].includes(cmdName)) {
213
219
  setInput('');
@@ -319,7 +325,7 @@ export default function REPL() {
319
325
  clearStream();
320
326
  abortRequestedRef.current = false;
321
327
  await engineRef.current.handleQuery(trimmed, callbacks);
322
- }, [isProcessing, pushHistory, clearStream, slashMenu, handleNewSession]);
328
+ }, [isProcessing, pushHistory, clearStream, slashMenu, handleNewSession, handleExit]);
323
329
  // ===== 输入处理 =====
324
330
  const handleUpArrow = useCallback(() => {
325
331
  const result = navigateUp(input);
@@ -26,6 +26,9 @@ export async function executeSlashCommand(cmdName) {
26
26
  '可用命令:',
27
27
  ' /init 初始化项目信息,生成 JARVIS.md',
28
28
  ' /new 开启新会话,重新初始化上下文',
29
+ ' /exit 退出应用程序',
30
+ ' /quit 退出应用程序',
31
+ ' /bye 退出应用程序',
29
32
  ' /resume 恢复历史会话(支持二级菜单选择)',
30
33
  ' /resume <ID> 直接恢复指定会话',
31
34
  ' /help 显示此帮助信息',
@@ -12,7 +12,7 @@
12
12
  import { exec } from 'child_process';
13
13
  import fs from 'fs';
14
14
  import path from 'path';
15
- import { scanExternalSkills, getExternalSkillsDir } from './loader.js';
15
+ import { scanExternalSkills } from './loader.js';
16
16
  import { allTools as builtinTools } from '../tools/index.js';
17
17
  // ===== 缓存 =====
18
18
  let _skillCache = null;
@@ -210,7 +210,6 @@ export function loadExternalSkills() {
210
210
  if (_skillCache)
211
211
  return _skillCache;
212
212
  _skillCache = scanExternalSkills();
213
- console.log(`[skills] 已加载 ${_skillCache.length} 个外部 skill (${getExternalSkillsDir()})`);
214
213
  return _skillCache;
215
214
  }
216
215
  /** 获取合并后的所有工具:内置 tools + 外部 skills */
@@ -22,3 +22,5 @@ export declare function findTool(name: string): Tool | undefined;
22
22
  export declare function getAllTools(): Tool[];
23
23
  /** 按名称查找工具(内置 + 外部 skills) */
24
24
  export declare function findToolMerged(name: string): Tool | undefined;
25
+ /** 获取状态栏用的工具统计摘要 */
26
+ export declare function getToolStatsText(): string;
@@ -27,6 +27,7 @@ export function findTool(name) {
27
27
  }
28
28
  // ===== 合并工具(内置 + 外部 Skills)=====
29
29
  import { getMergedTools, findMergedTool } from '../skills/index.js';
30
+ import { listSkills } from '../skills/index.js';
30
31
  /** 获取所有工具(内置 + 外部 skills),供 QueryEngine 使用 */
31
32
  export function getAllTools() {
32
33
  return getMergedTools();
@@ -35,3 +36,7 @@ export function getAllTools() {
35
36
  export function findToolMerged(name) {
36
37
  return findMergedTool(name);
37
38
  }
39
+ /** 获取状态栏用的工具统计摘要 */
40
+ export function getToolStatsText() {
41
+ return `skills(${allTools.length}/${listSkills().length})`;
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code4bug/jarvis-agent",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "基于 React + TypeScript + Ink 构建的命令行智能体交互界面",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",