@baimingtao/lbs-agent 1.1.0 → 1.3.0
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/bin/lbs.js +28 -25
- package/interfaces/cli/_impl/cli_theme.py +200 -0
- package/interfaces/cli/_impl/commands/chat_repl.py +91 -70
- package/interfaces/cli/_impl/commands/setup.py +15 -15
- package/package.json +1 -1
- package/scripts/init.js +25 -20
package/bin/lbs.js
CHANGED
|
@@ -46,17 +46,21 @@ const INIT_SCRIPT = path.join(PKG_ROOT, 'scripts', 'init.js');
|
|
|
46
46
|
|
|
47
47
|
// ── 颜色与图标 (对标 Hermes banner/colors) ─────────────────────
|
|
48
48
|
|
|
49
|
+
// ── Sakura × Mint 配色 (对标 Web 端 tailwind.config.js) ───────
|
|
50
|
+
|
|
49
51
|
const C = {
|
|
50
|
-
reset: '\x1b[0m',
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
|
|
53
|
+
// 品牌色
|
|
54
|
+
sakura: '\x1b[38;2;255;183;197m', // #FFB7C5
|
|
55
|
+
sakuraDark: '\x1b[38;2;232;154;168m', // #E89AA8
|
|
56
|
+
mint: '\x1b[38;2;152;216;200m', // #98D8C8
|
|
57
|
+
mintDark: '\x1b[38;2;111;196;176m', // #6FC4B0
|
|
58
|
+
// 功能色
|
|
59
|
+
violet: '\x1b[38;2;110;90;168m', // #6E5AA8
|
|
60
|
+
honey: '\x1b[38;2;255;184;107m', // #FFB86B
|
|
61
|
+
green: '\x1b[38;2;69;214;163m', // #45D6A3
|
|
62
|
+
red: '\x1b[38;2;255;107;129m', // #FF6B81
|
|
63
|
+
blue: '\x1b[38;2;101;168;255m', // #65A8FF
|
|
60
64
|
};
|
|
61
65
|
|
|
62
66
|
function supportsColor() {
|
|
@@ -70,26 +74,25 @@ function paint(text, ...colors) {
|
|
|
70
74
|
return colors.join('') + text + C.reset;
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
// ── ASCII Logo
|
|
77
|
+
// ── ASCII Logo — Sakura 主题 ───────────────────────────────────
|
|
74
78
|
|
|
75
|
-
const LOGO =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
const LOGO = [
|
|
80
|
+
'',
|
|
81
|
+
' ' + paint('╔══╗', C.sakura) + ' ' + paint('╔═══╗', C.mint) + ' ' + paint('╔═════╗', C.mint),
|
|
82
|
+
' ' + paint('║ ║', C.sakura) + ' ' + paint('║ ║', C.mint) + ' ' + paint('║ ╔═╝', C.mint),
|
|
83
|
+
' ' + paint('╠══╣', C.sakura) + ' ' + paint('╠═══╣', C.mint) + ' ' + paint('║ ╚═╗', C.mint),
|
|
84
|
+
' ' + paint('║ ║', C.sakura) + ' ' + paint('║ ║', C.mint) + ' ' + paint('╚═════╝', C.mint),
|
|
85
|
+
' ' + paint('╚══╝', C.sakura) + ' ' + paint('╚═══╝', C.mint) + ' ' + paint('AGENT', C.dim),
|
|
86
|
+
].join('\n');
|
|
83
87
|
|
|
84
88
|
function printBanner() {
|
|
85
89
|
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf-8'));
|
|
86
|
-
console.log();
|
|
87
90
|
console.log(LOGO);
|
|
88
|
-
console.log(paint(
|
|
89
|
-
|
|
91
|
+
console.log(paint(' v' + pkg.version, C.bold, C.sakuraDark) +
|
|
92
|
+
paint(' · ', C.sakura) +
|
|
93
|
+
paint('Leaving Blank Space Agent', C.dim));
|
|
90
94
|
console.log();
|
|
91
95
|
}
|
|
92
|
-
|
|
93
96
|
// ── 初始化检查 ─────────────────────────────────────────────────
|
|
94
97
|
|
|
95
98
|
function isInitialized() {
|
|
@@ -99,8 +102,8 @@ function isInitialized() {
|
|
|
99
102
|
function ensureInitialized() {
|
|
100
103
|
if (isInitialized()) return true;
|
|
101
104
|
|
|
102
|
-
console.log(paint('⚙ ', C.
|
|
103
|
-
console.log(paint(' 状态目录: ', C.dim) + paint(STATE_ROOT, C.
|
|
105
|
+
console.log(paint('⚙ ', C.sakura) + paint('首次运行,正在初始化 LBS Agent...', C.bold));
|
|
106
|
+
console.log(paint(' 状态目录: ', C.dim) + paint(STATE_ROOT, C.mint));
|
|
104
107
|
console.log(paint(' 创建 Python 虚拟环境 + 安装依赖(约 1-2 分钟)', C.dim));
|
|
105
108
|
console.log();
|
|
106
109
|
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""LBS CLI 主题系统 — Sakura × Mint (对标 Web 端 tailwind.config.js)。
|
|
2
|
+
|
|
3
|
+
Web 端配色 → ANSI True Color 映射:
|
|
4
|
+
sakura #FFB7C5 → 粉色 (品牌主色)
|
|
5
|
+
mint #98D8C8 → 薄荷绿 (辅助色)
|
|
6
|
+
berry #2B1828 → 深莓 (正文)
|
|
7
|
+
violet #6E5AA8 → 紫色 (Agent)
|
|
8
|
+
honey #FFB86B → 蜂蜜 (警告)
|
|
9
|
+
green #45D6A3 → 翠绿 (成功)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ── ANSI True Color 码 (直接用 Web 端 hex) ─────────────────────
|
|
19
|
+
|
|
20
|
+
class Theme:
|
|
21
|
+
"""LBS CLI 色彩主题,映射 Web 端 tailwind config。"""
|
|
22
|
+
|
|
23
|
+
# 基础
|
|
24
|
+
RESET = "\033[0m"
|
|
25
|
+
BOLD = "\033[1m"
|
|
26
|
+
DIM = "\033[2m"
|
|
27
|
+
ITALIC = "\033[3m"
|
|
28
|
+
UNDERLINE = "\033[4m"
|
|
29
|
+
|
|
30
|
+
# 品牌色 (对标 tailwind)
|
|
31
|
+
SAKURA = "\033[38;2;255;183;197m" # #FFB7C5
|
|
32
|
+
SAKURA_LIGHT = "\033[38;2;255;212;222m" # #FFD4DE
|
|
33
|
+
SAKURA_DARK = "\033[38;2;232;154;168m" # #E89AA8
|
|
34
|
+
MINT = "\033[38;2;152;216;200m" # #98D8C8
|
|
35
|
+
MINT_LIGHT = "\033[38;2;184;232;220m" # #B8E8DC
|
|
36
|
+
MINT_DARK = "\033[38;2;111;196;176m" # #6FC4B0
|
|
37
|
+
|
|
38
|
+
# 功能色
|
|
39
|
+
BERRY = "\033[38;2;43;24;40m" # #2B1828 (深莓正文)
|
|
40
|
+
BERRY_LIGHT = "\033[38;2;74;47;69m" # #4A2F45
|
|
41
|
+
VIOLET = "\033[38;2;110;90;168m" # #6E5AA8
|
|
42
|
+
VIOLET_LIGHT = "\033[38;2;144;128;200m" # #9080C8
|
|
43
|
+
HONEY = "\033[38;2;255;184;107m" # #FFB86B
|
|
44
|
+
GREEN = "\033[38;2;69;214;163m" # #45D6A3
|
|
45
|
+
RED = "\033[38;2;255;107;129m" # #FF6B81
|
|
46
|
+
BLUE = "\033[38;2;101;168;255m" # #65A8FF
|
|
47
|
+
|
|
48
|
+
# 状态色 (终端兼容)
|
|
49
|
+
OK = "\033[1;38;2;69;214;163m" # green bold
|
|
50
|
+
ERR = "\033[1;38;2;255;107;129m" # red bold
|
|
51
|
+
WARN = "\033[1;38;2;255;184;107m" # honey bold
|
|
52
|
+
INFO = "\033[38;2;101;168;255m" # blue
|
|
53
|
+
DIM_TEXT = "\033[2m"
|
|
54
|
+
|
|
55
|
+
# 渐变 (用多段 ANSI 近似模拟)
|
|
56
|
+
GRADIENT_PINK = "\033[1;38;2;255;183;197m"
|
|
57
|
+
GRADIENT_MINT = "\033[1;38;2;152;216;200m"
|
|
58
|
+
GRADIENT_VIOLET = "\033[1;38;2;110;90;168m"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _use_color() -> bool:
|
|
62
|
+
"""检测是否使用彩色输出。"""
|
|
63
|
+
if os.environ.get("NO_COLOR"):
|
|
64
|
+
return False
|
|
65
|
+
if os.environ.get("TERM") == "dumb":
|
|
66
|
+
return False
|
|
67
|
+
return sys.stdout.isatty()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def paint(text: str, *codes: str) -> str:
|
|
71
|
+
"""给文本上色 (无 TTY 时自动降级)。"""
|
|
72
|
+
if not _use_color():
|
|
73
|
+
return text
|
|
74
|
+
return "".join(codes) + text + Theme.RESET
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ── 高级组件 ───────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
def sakura_label(text: str) -> str:
|
|
80
|
+
"""粉色标签 (用于品牌名)。"""
|
|
81
|
+
return paint(text, Theme.BOLD, Theme.SAKURA_DARK)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def mint_label(text: str) -> str:
|
|
85
|
+
"""薄荷标签 (用于成功状态)。"""
|
|
86
|
+
return paint(text, Theme.BOLD, Theme.MINT_DARK)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def dim(text: str) -> str:
|
|
90
|
+
"""灰色辅助文字。"""
|
|
91
|
+
return paint(text, Theme.DIM)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def agent_badge(name: str) -> str:
|
|
95
|
+
"""Agent 名称紫色胶囊。"""
|
|
96
|
+
return paint(f" {name} ", Theme.BOLD, Theme.VIOLET)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def model_pill(model: str) -> str:
|
|
100
|
+
"""模型名粉色文字。"""
|
|
101
|
+
return paint(model, Theme.SAKURA_DARK)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def status_dot(ok: bool = True) -> str:
|
|
105
|
+
"""状态圆点。"""
|
|
106
|
+
color = Theme.GREEN if ok else Theme.RED
|
|
107
|
+
return paint("●", color)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def divider(width: int = 56) -> str:
|
|
111
|
+
"""分隔线 — 樱花粉渐变薄荷。"""
|
|
112
|
+
return paint("─" * width, Theme.SAKURA, Theme.DIM)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def header_box(title: str, subtitle: str = "") -> str:
|
|
116
|
+
"""带圆角的标题框 (对标 Web 端 glass card)。"""
|
|
117
|
+
w = max(len(title) + 4, 44)
|
|
118
|
+
top = paint("╭" + "─" * (w - 2) + "╮", Theme.SAKURA)
|
|
119
|
+
bottom = paint("╰" + "─" * (w - 2) + "╯", Theme.SAKURA)
|
|
120
|
+
mid_title = paint(f"│ {title}", Theme.BOLD, Theme.SAKURA_DARK)
|
|
121
|
+
mid_title += " " * (w - 2 - len(title) - 1) + paint("│", Theme.SAKURA)
|
|
122
|
+
lines = [top, mid_title]
|
|
123
|
+
if subtitle:
|
|
124
|
+
mid_sub = paint(f"│ ", Theme.SAKURA) + paint(subtitle, Theme.DIM)
|
|
125
|
+
mid_sub += " " * (w - 2 - len(subtitle) - 1) + paint("│", Theme.SAKURA)
|
|
126
|
+
lines.append(mid_sub)
|
|
127
|
+
lines.append(bottom)
|
|
128
|
+
return "\n".join(lines)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def kv_row(label: str, value: str, label_width: int = 9) -> str:
|
|
132
|
+
"""键值行 (对标 Web 端 status dock)。"""
|
|
133
|
+
return f" {paint(label.ljust(label_width), Theme.DIM)} {paint('│', Theme.SAKURA, Theme.DIM)} {value}"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def turn_prompt(agent: str, turn: int) -> str:
|
|
137
|
+
"""Chat turn 提示符。"""
|
|
138
|
+
return f"{agent_badge(agent)} {dim(f'turn {turn}')} {paint('›', Theme.SAKURA)} "
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def usage_bar(seconds: float, iterations: int, tools: int) -> str:
|
|
142
|
+
"""用量状态条 (对标 Web 端 status dock)。"""
|
|
143
|
+
parts = [
|
|
144
|
+
paint(f"{seconds:.1f}s", Theme.MINT_DARK),
|
|
145
|
+
paint(f"iter {iterations}", Theme.DIM),
|
|
146
|
+
paint(f"tools {tools}", Theme.DIM),
|
|
147
|
+
]
|
|
148
|
+
return " " + paint(" │ ", Theme.SAKURA, Theme.DIM).join(parts)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def react_header(agent_display: str, model_chain: list[str], tools: list[str]) -> str:
|
|
152
|
+
"""ReAct 迭代头 (对标 Web 端 ChatFlow 的任务头)。"""
|
|
153
|
+
model_str = paint(" → ", Theme.SAKURA).join(
|
|
154
|
+
paint(m, Theme.SAKURA_DARK) for m in model_chain
|
|
155
|
+
)
|
|
156
|
+
tools_str = paint(", ", Theme.DIM).join(
|
|
157
|
+
paint(t, Theme.MINT_DARK) for t in tools
|
|
158
|
+
)
|
|
159
|
+
lines = [
|
|
160
|
+
paint("━" * 56, Theme.SAKURA, Theme.DIM),
|
|
161
|
+
f" {paint('Agent', Theme.DIM)} {agent_badge(agent_display)}",
|
|
162
|
+
f" {paint('Model', Theme.DIM)} {model_str}",
|
|
163
|
+
f" {paint('Tools', Theme.DIM)} {tools_str}",
|
|
164
|
+
paint("━" * 56, Theme.SAKURA, Theme.DIM),
|
|
165
|
+
]
|
|
166
|
+
return "\n".join(lines)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def react_iteration(iteration: int, max_iterations: int, phase: str = "") -> str:
|
|
170
|
+
"""ReAct 迭代标记。"""
|
|
171
|
+
phase_str = f" {paint(phase, Theme.VIOLET)}" if phase else ""
|
|
172
|
+
return f"\n {paint('⟡', Theme.SAKURA)} {paint(f'iter {iteration}', Theme.BOLD, Theme.SAKURA_DARK)}{paint(f'/{max_iterations}', Theme.DIM)}{phase_str}"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def thinking_block(content: str) -> str:
|
|
176
|
+
"""思考过程块 (薄荷绿斜体)。"""
|
|
177
|
+
return paint(content, Theme.ITALIC, Theme.MINT_DARK)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def result_block(content: str) -> str:
|
|
181
|
+
"""最终结果块。"""
|
|
182
|
+
return paint(content, Theme.BOLD)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def error_block(content: str) -> str:
|
|
186
|
+
"""错误块。"""
|
|
187
|
+
return paint(f"✗ {content}", Theme.BOLD, Theme.RED)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def logo() -> str:
|
|
191
|
+
"""LBS Agent ASCII Logo — Sakura 主题。"""
|
|
192
|
+
lines = [
|
|
193
|
+
"",
|
|
194
|
+
f" {paint('╔══╗', Theme.SAKURA)} {paint('╔═══╗', Theme.MINT)} {paint('╔═════╗', Theme.MINT)}",
|
|
195
|
+
f" {paint('║ ║', Theme.SAKURA)} {paint('║ ║', Theme.MINT)} {paint('║ ╔═╝', Theme.MINT)}",
|
|
196
|
+
f" {paint('╠══╣', Theme.SAKURA)} {paint('╠═══╣', Theme.MINT)} {paint('║ ╚═╗', Theme.MINT)}",
|
|
197
|
+
f" {paint('║ ║', Theme.SAKURA)} {paint('║ ║', Theme.MINT)} {paint('╚═════╝', Theme.MINT)}",
|
|
198
|
+
f" {paint('╚══╝', Theme.SAKURA)} {paint('╚═══╝', Theme.MINT)} {paint('AGENT', Theme.DIM)}",
|
|
199
|
+
]
|
|
200
|
+
return "\n".join(lines)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""LBS Agent REPL
|
|
1
|
+
"""LBS Agent REPL 交互模式。
|
|
2
2
|
|
|
3
3
|
设计:
|
|
4
4
|
- 用户进入交互循环,每次输入一行
|
|
@@ -23,14 +23,12 @@ from typing import Optional
|
|
|
23
23
|
from application.container import AgentSystemContainer
|
|
24
24
|
from config.config import AppConfig
|
|
25
25
|
from interfaces.cli._impl.commands.common import project_root
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
║ 输入 / 开头是命令;其他内容发送给当前 agent ║
|
|
33
|
-
╚══════════════════════════════════════════════════════════════╝\033[0m"""
|
|
26
|
+
from interfaces.cli._impl.cli_theme import (
|
|
27
|
+
Theme, paint, dim, logo, agent_badge, model_pill, status_dot,
|
|
28
|
+
divider, header_box, kv_row, turn_prompt, usage_bar,
|
|
29
|
+
react_header, react_iteration, thinking_block, result_block,
|
|
30
|
+
error_block, sakura_label, mint_label,
|
|
31
|
+
)
|
|
34
32
|
|
|
35
33
|
|
|
36
34
|
@dataclass
|
|
@@ -60,81 +58,105 @@ def _resolve_agent(config: AppConfig) -> str:
|
|
|
60
58
|
return names[0]
|
|
61
59
|
|
|
62
60
|
|
|
61
|
+
def _print_banner(state: ChatState) -> None:
|
|
62
|
+
"""启动 banner — Sakura 主题。"""
|
|
63
|
+
print()
|
|
64
|
+
print(logo())
|
|
65
|
+
print(f" {paint('LBS', Theme.BOLD, Theme.SAKURA_DARK)} {dim('Agent')} "
|
|
66
|
+
f"{paint('·', Theme.SAKURA)} {dim('Interactive Chat')}")
|
|
67
|
+
print()
|
|
68
|
+
print(f" {dim('Type a message, or')} {sakura_label('/help')} {dim('for commands')}")
|
|
69
|
+
print()
|
|
70
|
+
|
|
71
|
+
|
|
63
72
|
def _print_turn_header(state: ChatState) -> None:
|
|
64
73
|
"""每次 turn 的 header(agent + turn 编号)。"""
|
|
65
|
-
print(
|
|
74
|
+
print()
|
|
75
|
+
print(turn_prompt(state.agent_name, state.turn_count), end="", flush=True)
|
|
66
76
|
|
|
67
77
|
|
|
68
78
|
def _print_help() -> None:
|
|
69
|
-
print(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
""
|
|
79
|
+
print()
|
|
80
|
+
print(f" {header_box('Commands', '/ prefix to run')}")
|
|
81
|
+
print()
|
|
82
|
+
cmds = [
|
|
83
|
+
("/help", "显示本帮助"),
|
|
84
|
+
("/exit", "退出 chat"),
|
|
85
|
+
("/new", "开始新 session"),
|
|
86
|
+
("/status", "显示 session 状态"),
|
|
87
|
+
("/agent", "列出 / 切换 agent"),
|
|
88
|
+
("/model", "列出 / 切换 model"),
|
|
89
|
+
("/history", "显示最近对话"),
|
|
90
|
+
("/clear", "清屏"),
|
|
91
|
+
]
|
|
92
|
+
for cmd, desc in cmds:
|
|
93
|
+
print(f" {sakura_label(cmd.ljust(12))} {dim(desc)}")
|
|
94
|
+
print()
|
|
84
95
|
|
|
85
96
|
|
|
86
97
|
def _cmd_list_agents(state: ChatState) -> None:
|
|
87
98
|
agents = state.container.config.agents.agents
|
|
88
|
-
print(
|
|
99
|
+
print()
|
|
89
100
|
for name, a in agents.items():
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
if name == state.agent_name:
|
|
102
|
+
marker = f" {status_dot(True)} {dim('current')}"
|
|
103
|
+
else:
|
|
104
|
+
marker = ""
|
|
105
|
+
print(f" {agent_badge(name)} {dim(a.display_name)}{marker}")
|
|
106
|
+
print()
|
|
92
107
|
|
|
93
108
|
|
|
94
109
|
def _cmd_list_models(state: ChatState) -> None:
|
|
95
110
|
config = state.container.config
|
|
96
111
|
provider_name = state.provider_name or config.models.default_provider
|
|
97
112
|
if provider_name not in config.models.providers:
|
|
98
|
-
print(f" 无 provider
|
|
113
|
+
print(f" {error_block(f'无 provider {provider_name!r}')}")
|
|
99
114
|
return
|
|
100
115
|
p = config.models.providers[provider_name]
|
|
101
|
-
print(
|
|
116
|
+
print()
|
|
117
|
+
print(f" {dim('provider')} {paint(provider_name, Theme.VIOLET)} {dim(f'({p.display_name or provider_name})')}")
|
|
102
118
|
for m in p.models:
|
|
103
|
-
marker = "
|
|
104
|
-
print(f"
|
|
119
|
+
marker = f" {status_dot(True)}" if m == state.model_name else ""
|
|
120
|
+
print(f" {model_pill(m)}{marker}")
|
|
121
|
+
print()
|
|
105
122
|
|
|
106
123
|
|
|
107
124
|
def _cmd_history(state: ChatState, n: int = 5) -> None:
|
|
108
125
|
if not state.session_id:
|
|
109
|
-
print(" 无 session")
|
|
126
|
+
print(f" {dim('无 session')}")
|
|
110
127
|
return
|
|
111
128
|
try:
|
|
112
129
|
messages = list(state.session_service.session_store.load_session_messages(state.session_id))
|
|
113
|
-
# 显示 user/assistant 配对
|
|
114
130
|
recent = []
|
|
115
131
|
for msg in messages[-n*2:]:
|
|
116
132
|
role = getattr(msg, "role", "?")
|
|
117
133
|
content = getattr(msg, "content", "")[:120]
|
|
118
134
|
if role in ("user", "assistant"):
|
|
119
135
|
recent.append((role, content))
|
|
120
|
-
print(
|
|
136
|
+
print()
|
|
137
|
+
print(f" {dim(f'最近 {len(recent)} 条消息')}")
|
|
121
138
|
for role, content in recent:
|
|
122
|
-
|
|
139
|
+
role_color = Theme.SAKURA_DARK if role == "user" else Theme.MINT_DARK
|
|
140
|
+
print(f" {paint(role.ljust(9), role_color)} {paint('│', Theme.SAKURA, Theme.DIM)} {dim(content)}")
|
|
141
|
+
print()
|
|
123
142
|
except Exception as e:
|
|
124
|
-
print(f"
|
|
143
|
+
print(f" {error_block(f'读取失败: {e}')}")
|
|
125
144
|
|
|
126
145
|
|
|
127
146
|
def _cmd_status(state: ChatState) -> None:
|
|
128
147
|
elapsed = int(time.time() - state.started_at)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
rows = [
|
|
149
|
+
("session", state.session_id or dim("(unsaved)")),
|
|
150
|
+
("agent", agent_badge(state.agent_name)),
|
|
151
|
+
("provider", state.provider_name or dim("(default)")),
|
|
152
|
+
("model", model_pill(state.model_name) if state.model_name else dim("(default)")),
|
|
153
|
+
("turns", str(state.turn_count)),
|
|
154
|
+
("elapsed", f"{elapsed}s"),
|
|
155
|
+
]
|
|
156
|
+
print()
|
|
157
|
+
for label, val in rows:
|
|
158
|
+
print(kv_row(label, val))
|
|
159
|
+
print()
|
|
138
160
|
|
|
139
161
|
|
|
140
162
|
def _handle_command(state: ChatState, line: str) -> bool:
|
|
@@ -151,13 +173,13 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
151
173
|
state.session_id = ""
|
|
152
174
|
state.turn_count = 0
|
|
153
175
|
state.started_at = time.time()
|
|
154
|
-
print("
|
|
176
|
+
print(f" {mint_label('→')} {dim('new session')}")
|
|
155
177
|
elif cmd == "/session":
|
|
156
178
|
if arg:
|
|
157
179
|
state.session_id = arg
|
|
158
|
-
print(f"
|
|
180
|
+
print(f" {mint_label('→')} {dim('switched to')} {paint(arg, Theme.SAKURA_DARK)}")
|
|
159
181
|
else:
|
|
160
|
-
print(f"
|
|
182
|
+
print(f" {dim('session:')} {state.session_id or dim('(none)')}")
|
|
161
183
|
elif cmd == "/status":
|
|
162
184
|
_cmd_status(state)
|
|
163
185
|
elif cmd == "/agent":
|
|
@@ -167,15 +189,15 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
167
189
|
agents = state.container.config.agents.agents
|
|
168
190
|
if arg in agents:
|
|
169
191
|
state.agent_name = arg
|
|
170
|
-
print(f"
|
|
192
|
+
print(f" {mint_label('→')} {dim('agent')} {agent_badge(arg)}")
|
|
171
193
|
else:
|
|
172
|
-
print(f" 无 agent
|
|
194
|
+
print(f" {error_block(f'无 agent {arg!r}')}")
|
|
173
195
|
elif cmd == "/model":
|
|
174
196
|
if not arg:
|
|
175
197
|
_cmd_list_models(state)
|
|
176
198
|
else:
|
|
177
199
|
state.model_name = arg
|
|
178
|
-
print(f"
|
|
200
|
+
print(f" {mint_label('→')} {dim('model')} {model_pill(arg)}")
|
|
179
201
|
elif cmd == "/clear":
|
|
180
202
|
print("\033[2J\033[H", end="")
|
|
181
203
|
elif cmd == "/history":
|
|
@@ -183,12 +205,11 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
183
205
|
n = int(arg) if arg else 5
|
|
184
206
|
_cmd_history(state, n)
|
|
185
207
|
except ValueError:
|
|
186
|
-
print(" /history [n]
|
|
208
|
+
print(f" {error_block('/history [n] — n 必须是整数')}")
|
|
187
209
|
elif cmd == "/save":
|
|
188
|
-
|
|
189
|
-
print(" session 已在每次 turn 自动保存")
|
|
210
|
+
print(f" {dim('session 自动保存在每次 turn')}")
|
|
190
211
|
else:
|
|
191
|
-
print(f"
|
|
212
|
+
print(f" {error_block(f'未知命令: {cmd} — 输入 /help')}")
|
|
192
213
|
return True
|
|
193
214
|
|
|
194
215
|
|
|
@@ -196,7 +217,6 @@ def _run_turn(state: ChatState, user_input: str) -> None:
|
|
|
196
217
|
"""跑一轮对话(同步阻塞)。"""
|
|
197
218
|
t0 = time.time()
|
|
198
219
|
try:
|
|
199
|
-
# 把 user_input 作为 task
|
|
200
220
|
result = state.orchestrator.run_agent_sync(
|
|
201
221
|
state.agent_name,
|
|
202
222
|
user_input,
|
|
@@ -207,20 +227,21 @@ def _run_turn(state: ChatState, user_input: str) -> None:
|
|
|
207
227
|
# 显示结果
|
|
208
228
|
if result.success:
|
|
209
229
|
content = (result.content or "").strip()
|
|
210
|
-
print(f"\n
|
|
230
|
+
print(f"\n{result_block(content)}")
|
|
211
231
|
else:
|
|
212
232
|
err = result.error or "(无错误信息)"
|
|
213
|
-
print(f"\n
|
|
214
|
-
|
|
215
|
-
|
|
233
|
+
print(f"\n {error_block(err[:300])}")
|
|
234
|
+
# 用量状态条
|
|
235
|
+
print(usage_bar(elapsed, result.iterations, result.total_tool_calls))
|
|
236
|
+
# 保存 session_id
|
|
216
237
|
if not state.session_id:
|
|
217
238
|
state.session_id = result.session_id if hasattr(result, "session_id") and result.session_id else f"chat-{int(state.started_at)}"
|
|
218
239
|
except KeyboardInterrupt:
|
|
219
|
-
print("\n
|
|
240
|
+
print(f"\n {paint('⏸', Theme.HONEY)} {dim('已中断')}")
|
|
220
241
|
except Exception as e:
|
|
221
242
|
elapsed = time.time() - t0
|
|
222
|
-
print(f"\n
|
|
223
|
-
print(f"
|
|
243
|
+
print(f"\n {error_block(f'{type(e).__name__}: {str(e)[:200]}')}")
|
|
244
|
+
print(f" {dim(f'{elapsed:.1f}s')}")
|
|
224
245
|
|
|
225
246
|
|
|
226
247
|
def cmd_chat(
|
|
@@ -240,7 +261,8 @@ def cmd_chat(
|
|
|
240
261
|
|
|
241
262
|
# 验证 agent 存在
|
|
242
263
|
if agent_name not in config.agents.agents:
|
|
243
|
-
|
|
264
|
+
msg = f"Agent '{agent_name}' 不存在"
|
|
265
|
+
print(f" {error_block(msg)}")
|
|
244
266
|
return 1
|
|
245
267
|
|
|
246
268
|
state = ChatState(
|
|
@@ -253,7 +275,7 @@ def cmd_chat(
|
|
|
253
275
|
session_id=session_id,
|
|
254
276
|
)
|
|
255
277
|
|
|
256
|
-
# 单次 query
|
|
278
|
+
# 单次 query 模式
|
|
257
279
|
if query:
|
|
258
280
|
state.turn_count = 1
|
|
259
281
|
_print_turn_header(state)
|
|
@@ -262,9 +284,8 @@ def cmd_chat(
|
|
|
262
284
|
return 0
|
|
263
285
|
|
|
264
286
|
# REPL 模式
|
|
265
|
-
|
|
287
|
+
_print_banner(state)
|
|
266
288
|
_cmd_status(state)
|
|
267
|
-
print("\n输入 /help 查看命令。Ctrl+C 中断当前 turn,/exit 退出。\n")
|
|
268
289
|
|
|
269
290
|
while True:
|
|
270
291
|
try:
|
|
@@ -274,7 +295,7 @@ def cmd_chat(
|
|
|
274
295
|
print("\n")
|
|
275
296
|
break
|
|
276
297
|
except KeyboardInterrupt:
|
|
277
|
-
print("\n
|
|
298
|
+
print(f"\n {paint('用 /exit 退出', Theme.HONEY)}")
|
|
278
299
|
continue
|
|
279
300
|
|
|
280
301
|
line = line.strip()
|
|
@@ -287,5 +308,5 @@ def cmd_chat(
|
|
|
287
308
|
state.turn_count += 1
|
|
288
309
|
_run_turn(state, line)
|
|
289
310
|
|
|
290
|
-
print("\n
|
|
311
|
+
print(f"\n {mint_label('✓')} {dim('Session saved. Bye.')}")
|
|
291
312
|
return 0
|
|
@@ -73,15 +73,15 @@ def _paint(text: str, *codes: str) -> str:
|
|
|
73
73
|
def _header(title: str) -> None:
|
|
74
74
|
"""打印章节标题 (带边框)。"""
|
|
75
75
|
print()
|
|
76
|
-
print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.
|
|
77
|
-
print(_paint(" │ ", _C.
|
|
78
|
-
+ _paint(" " * max(0, 47 - len(title) - 14) + "│", _C.
|
|
79
|
-
print(_paint(" └─────────────────────────────────────────────────────────┘", _C.
|
|
76
|
+
print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.SAKURA))
|
|
77
|
+
print(_paint(" │ ", _C.SAKURA) + _paint(f"⚙ LBS Setup — {title}", _C.BOLD, _C.SAKURA)
|
|
78
|
+
+ _paint(" " * max(0, 47 - len(title) - 14) + "│", _C.SAKURA))
|
|
79
|
+
print(_paint(" └─────────────────────────────────────────────────────────┘", _C.SAKURA))
|
|
80
80
|
print()
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def _info(msg: str) -> None:
|
|
84
|
-
print(_paint(" ℹ ", _C.
|
|
84
|
+
print(_paint(" ℹ ", _C.VIOLET) + msg)
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def _success(msg: str) -> None:
|
|
@@ -89,7 +89,7 @@ def _success(msg: str) -> None:
|
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def _warn(msg: str) -> None:
|
|
92
|
-
print(_paint(" ⚠ ", _C.
|
|
92
|
+
print(_paint(" ⚠ ", _C.HONEY) + _paint(msg, _C.HONEY))
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
def _error(msg: str) -> None:
|
|
@@ -100,7 +100,7 @@ def _prompt(label: str, default: str = "") -> str:
|
|
|
100
100
|
"""带默认值的输入提示。"""
|
|
101
101
|
suffix = f" [{default}]" if default else ""
|
|
102
102
|
try:
|
|
103
|
-
val = input(_paint(f" ▸ {label}{suffix}: ", _C.
|
|
103
|
+
val = input(_paint(f" ▸ {label}{suffix}: ", _C.MINT)).strip()
|
|
104
104
|
except (EOFError, KeyboardInterrupt):
|
|
105
105
|
print()
|
|
106
106
|
return default
|
|
@@ -109,7 +109,7 @@ def _prompt(label: str, default: str = "") -> str:
|
|
|
109
109
|
|
|
110
110
|
def _prompt_choice(label: str, options: list[str], default: str = "") -> str:
|
|
111
111
|
"""带选项列表的输入提示。"""
|
|
112
|
-
print(_paint(f" ▸ {label}", _C.
|
|
112
|
+
print(_paint(f" ▸ {label}", _C.MINT))
|
|
113
113
|
for i, opt in enumerate(options, 1):
|
|
114
114
|
marker = _paint("→", _C.GREEN) if opt == default else " "
|
|
115
115
|
print(f" {marker} {i}. {opt}")
|
|
@@ -125,7 +125,7 @@ def _prompt_password(label: str) -> str:
|
|
|
125
125
|
"""密码输入(不回显)。"""
|
|
126
126
|
import getpass
|
|
127
127
|
try:
|
|
128
|
-
val = getpass.getpass(_paint(f" ▸ {label}: ", _C.
|
|
128
|
+
val = getpass.getpass(_paint(f" ▸ {label}: ", _C.MINT))
|
|
129
129
|
except (EOFError, KeyboardInterrupt):
|
|
130
130
|
print()
|
|
131
131
|
return ""
|
|
@@ -467,10 +467,10 @@ def run_setup(section: str | None = None) -> int:
|
|
|
467
467
|
root.mkdir(parents=True, exist_ok=True)
|
|
468
468
|
|
|
469
469
|
print()
|
|
470
|
-
print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.
|
|
471
|
-
print(_paint(" │ ", _C.
|
|
472
|
-
+ _paint(" │", _C.
|
|
473
|
-
print(_paint(" └─────────────────────────────────────────────────────────┘", _C.
|
|
470
|
+
print(_paint(" ┌─────────────────────────────────────────────────────────┐", _C.SAKURA))
|
|
471
|
+
print(_paint(" │ ", _C.SAKURA) + _paint("⚙ LBS Agent Setup Wizard", _C.BOLD, _C.SAKURA)
|
|
472
|
+
+ _paint(" │", _C.SAKURA))
|
|
473
|
+
print(_paint(" └─────────────────────────────────────────────────────────┘", _C.SAKURA))
|
|
474
474
|
print()
|
|
475
475
|
_info(f"配置目录: {root}")
|
|
476
476
|
print()
|
|
@@ -512,8 +512,8 @@ def paint_path(p: Path) -> str:
|
|
|
512
512
|
s = str(p)
|
|
513
513
|
if len(s) > 40:
|
|
514
514
|
s = "..." + s[-37:]
|
|
515
|
-
return _paint(s, _C.
|
|
515
|
+
return _paint(s, _C.MINT)
|
|
516
516
|
|
|
517
517
|
|
|
518
518
|
def paint_cmd(cmd: str) -> str:
|
|
519
|
-
return _paint(cmd, _C.
|
|
519
|
+
return _paint(cmd, _C.MINT, _C.BOLD)
|
package/package.json
CHANGED
package/scripts/init.js
CHANGED
|
@@ -29,10 +29,15 @@ const PIP = isWin ? path.join(VENV_DIR, 'Scripts', 'pip.exe') : path.join(VENV_D
|
|
|
29
29
|
// ── 颜色 ───────────────────────────────────────────────────────
|
|
30
30
|
|
|
31
31
|
const C = {
|
|
32
|
-
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
|
|
33
|
+
sakura: '\x1b[38;2;255;183;197m', // #FFB7C5
|
|
34
|
+
sakuraDark: '\x1b[38;2;232;154;168m', // #E89AA8
|
|
35
|
+
mint: '\x1b[38;2;152;216;200m', // #98D8C8
|
|
36
|
+
mintDark: '\x1b[38;2;111;196;176m', // #6FC4B0
|
|
37
|
+
violet: '\x1b[38;2;110;90;168m', // #6E5AA8
|
|
38
|
+
honey: '\x1b[38;2;255;184;107m', // #FFB86B
|
|
39
|
+
green: '\x1b[38;2;69;214;163m', // #45D6A3
|
|
40
|
+
red: '\x1b[38;2;255;107;129m', // #FF6B81
|
|
36
41
|
};
|
|
37
42
|
|
|
38
43
|
function paint(text, ...colors) {
|
|
@@ -58,7 +63,7 @@ function ensureDir(dir) {
|
|
|
58
63
|
// ── Step 1: 检测系统 Python ────────────────────────────────────
|
|
59
64
|
|
|
60
65
|
function findSystemPython() {
|
|
61
|
-
console.log(paint(' 🔍 ', C.
|
|
66
|
+
console.log(paint(' 🔍 ', C.sakura) + '检测系统 Python...');
|
|
62
67
|
|
|
63
68
|
const candidates = isWin
|
|
64
69
|
? ['python', 'python3', 'py -3']
|
|
@@ -76,7 +81,7 @@ function findSystemPython() {
|
|
|
76
81
|
const minor = parseInt(match[2]);
|
|
77
82
|
if (major >= 3 && minor >= 10) return cmd;
|
|
78
83
|
}
|
|
79
|
-
console.log(paint(' ⚠ ', C.
|
|
84
|
+
console.log(paint(' ⚠ ', C.honey) + '版本低于 3.10,继续寻找...');
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
|
|
@@ -97,7 +102,7 @@ function createVenv(sysPython) {
|
|
|
97
102
|
|
|
98
103
|
// 确保 state root 存在
|
|
99
104
|
ensureDir(STATE_ROOT);
|
|
100
|
-
console.log(paint(' 📦 ', C.
|
|
105
|
+
console.log(paint(' 📦 ', C.sakura) + `创建 Python 虚拟环境 → ${paint(STATE_ROOT + '/venv', C.mint)}`);
|
|
101
106
|
|
|
102
107
|
const result = run(sysPython, ['-m', 'venv', VENV_DIR]);
|
|
103
108
|
if (result.status !== 0 || !fs.existsSync(PYTHON)) {
|
|
@@ -116,7 +121,7 @@ function installDeps() {
|
|
|
116
121
|
return false;
|
|
117
122
|
}
|
|
118
123
|
|
|
119
|
-
console.log(paint(' 📥 ', C.
|
|
124
|
+
console.log(paint(' 📥 ', C.sakura) + '安装 Python 依赖 (可能需要 1-2 分钟)...');
|
|
120
125
|
const result = run(PIP, ['install', '-r', REQUIREMENTS, '--disable-pip-version-check']);
|
|
121
126
|
if (result.status !== 0) {
|
|
122
127
|
console.error(paint(' ✖ ', C.red) + '依赖安装失败');
|
|
@@ -162,7 +167,7 @@ function createDefaultDirs() {
|
|
|
162
167
|
// ── Step 5: 验证 ───────────────────────────────────────────────
|
|
163
168
|
|
|
164
169
|
function verifyInstall() {
|
|
165
|
-
console.log(paint(' 🔍 ', C.
|
|
170
|
+
console.log(paint(' 🔍 ', C.sakura) + '验证安装...');
|
|
166
171
|
const result = run(PYTHON, [path.join(PKG_ROOT, 'lbs.py'), 'status'], {
|
|
167
172
|
silent: true,
|
|
168
173
|
cwd: PKG_ROOT,
|
|
@@ -171,7 +176,7 @@ function verifyInstall() {
|
|
|
171
176
|
if (result.status === 0) {
|
|
172
177
|
console.log(paint(' ✓ ', C.green) + 'lbs status 运行正常');
|
|
173
178
|
} else {
|
|
174
|
-
console.log(paint(' ⚠ ', C.
|
|
179
|
+
console.log(paint(' ⚠ ', C.honey) + 'lbs status 运行异常(可能是配置问题,不影响初始化)');
|
|
175
180
|
}
|
|
176
181
|
return true;
|
|
177
182
|
}
|
|
@@ -193,12 +198,12 @@ function writeFlag() {
|
|
|
193
198
|
|
|
194
199
|
function main() {
|
|
195
200
|
console.log();
|
|
196
|
-
console.log(paint(' ┌─────────────────────────────────────────────────────────┐', C.
|
|
197
|
-
console.log(paint(' │ ', C.
|
|
198
|
-
console.log(paint(' └─────────────────────────────────────────────────────────┘', C.
|
|
201
|
+
console.log(paint(' ┌─────────────────────────────────────────────────────────┐', C.sakura));
|
|
202
|
+
console.log(paint(' │ ', C.sakura) + paint('⚙ LBS Agent — 初始化', C.bold, C.sakura) + paint(' │', C.sakura));
|
|
203
|
+
console.log(paint(' └─────────────────────────────────────────────────────────┘', C.sakura));
|
|
199
204
|
console.log();
|
|
200
|
-
console.log(paint(' 包目录 (只读): ', C.dim) + paint(PKG_ROOT, C.
|
|
201
|
-
console.log(paint(' 状态目录 (可写): ', C.dim) + paint(STATE_ROOT, C.
|
|
205
|
+
console.log(paint(' 包目录 (只读): ', C.dim) + paint(PKG_ROOT, C.mint));
|
|
206
|
+
console.log(paint(' 状态目录 (可写): ', C.dim) + paint(STATE_ROOT, C.mint));
|
|
202
207
|
console.log();
|
|
203
208
|
|
|
204
209
|
// Step 1: Python
|
|
@@ -225,12 +230,12 @@ function main() {
|
|
|
225
230
|
console.log(paint(' │ ', C.green) + paint('✅ 初始化完成!', C.bold, C.green) + paint(' │', C.green));
|
|
226
231
|
console.log(paint(' │ │', C.green));
|
|
227
232
|
console.log(paint(' │ ', C.green) + paint('下一步:', C.bold) + paint(' │', C.green));
|
|
228
|
-
console.log(paint(' │ ', C.green) + paint(' lbs setup 配置模型/供应商/Agent', C.
|
|
229
|
-
console.log(paint(' │ ', C.green) + paint(' lbs status 查看系统状态', C.
|
|
230
|
-
console.log(paint(' │ ', C.green) + paint(' lbs help 查看所有命令', C.
|
|
231
|
-
console.log(paint(' │ ', C.green) + paint(' lbs web 启动 Web 界面', C.
|
|
233
|
+
console.log(paint(' │ ', C.green) + paint(' lbs setup 配置模型/供应商/Agent', C.mint) + paint(' │', C.green));
|
|
234
|
+
console.log(paint(' │ ', C.green) + paint(' lbs status 查看系统状态', C.mint) + paint(' │', C.green));
|
|
235
|
+
console.log(paint(' │ ', C.green) + paint(' lbs help 查看所有命令', C.mint) + paint(' │', C.green));
|
|
236
|
+
console.log(paint(' │ ', C.green) + paint(' lbs web 启动 Web 界面', C.mint) + paint(' │', C.green));
|
|
232
237
|
console.log(paint(' │ │', C.green));
|
|
233
|
-
console.log(paint(' │ ', C.green) + paint('状态目录: ', C.dim) + paint(STATE_ROOT, C.
|
|
238
|
+
console.log(paint(' │ ', C.green) + paint('状态目录: ', C.dim) + paint(STATE_ROOT, C.mint) + paint(' │', C.green));
|
|
234
239
|
console.log(paint(' └─────────────────────────────────────────────────────────┘', C.green));
|
|
235
240
|
console.log();
|
|
236
241
|
}
|