@baimingtao/lbs-agent 1.5.1 → 1.6.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 +2 -5
- package/interfaces/cli/_impl/cli_theme.py +156 -176
- package/interfaces/cli/_impl/commands/chat_repl.py +62 -68
- package/package.json +1 -1
package/bin/lbs.js
CHANGED
|
@@ -96,14 +96,11 @@ function gradient(text) {
|
|
|
96
96
|
return out;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// ── Banner —
|
|
99
|
+
// ── Banner — 单行品牌 ─────────────────────────────────────────
|
|
100
100
|
|
|
101
101
|
function printBanner() {
|
|
102
102
|
console.log();
|
|
103
|
-
console.log('
|
|
104
|
-
console.log();
|
|
105
|
-
console.log(' ' + gradient('LBS Agent'));
|
|
106
|
-
console.log(' ' + paint('Autonomous AI Runtime', C.subtle));
|
|
103
|
+
console.log(' ' + gradient('LBS Agent') + ' ' + paint('Autonomous AI Runtime', C.subtle));
|
|
107
104
|
console.log();
|
|
108
105
|
}
|
|
109
106
|
// ── 初始化检查 ─────────────────────────────────────────────────
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
"""LBS CLI
|
|
1
|
+
"""LBS CLI Terminal Design System.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
对标: Claude Code, Gemini CLI, Warp Terminal, Charm BubbleTea
|
|
3
|
+
原则:
|
|
4
|
+
终端 = Canvas, 不是 stdout
|
|
5
|
+
所有输出走 Layout, 有版心、间距、层级
|
|
6
|
+
一套图标, 一种节奏
|
|
9
7
|
"""
|
|
10
8
|
|
|
11
9
|
from __future__ import annotations
|
|
@@ -13,6 +11,33 @@ from __future__ import annotations
|
|
|
13
11
|
import os
|
|
14
12
|
import sys
|
|
15
13
|
|
|
14
|
+
# ── 版心 ───────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
MARGIN = 2 # 左缩进
|
|
17
|
+
MAX_W = 110 # 内容宽度上限
|
|
18
|
+
|
|
19
|
+
def _term_width() -> int:
|
|
20
|
+
try:
|
|
21
|
+
import shutil
|
|
22
|
+
return shutil.get_terminal_size().columns
|
|
23
|
+
except Exception:
|
|
24
|
+
return 80
|
|
25
|
+
|
|
26
|
+
def content_width() -> int:
|
|
27
|
+
return min(_term_width() - MARGIN * 2, MAX_W)
|
|
28
|
+
|
|
29
|
+
def pad(text: str, level: int = 0) -> str:
|
|
30
|
+
"""给每行加左缩进。level=0 用 MARGIN, 更深层级递加。"""
|
|
31
|
+
indent = " " * (MARGIN + level * 2)
|
|
32
|
+
lines = text.split("\n")
|
|
33
|
+
return "\n".join(indent + ln if ln.strip() else ln for ln in lines)
|
|
34
|
+
|
|
35
|
+
def out(text: str = "", level: int = 0) -> None:
|
|
36
|
+
"""统一输出 — 所有 CLI 输出的唯一出口。"""
|
|
37
|
+
if text == "":
|
|
38
|
+
print()
|
|
39
|
+
else:
|
|
40
|
+
print(pad(text, level))
|
|
16
41
|
|
|
17
42
|
# ── 色彩 ───────────────────────────────────────────────────────
|
|
18
43
|
|
|
@@ -22,212 +47,167 @@ class C:
|
|
|
22
47
|
DIM = "\033[2m"
|
|
23
48
|
ITALIC = "\033[3m"
|
|
24
49
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# 文字
|
|
42
|
-
TEXT = "\033[38;2;235;235;245m" # #EBEBF5 (正文)
|
|
43
|
-
SUBTLE = "\033[38;2;138;138;150m" # #8A8A96 (辅助)
|
|
44
|
-
FAINT = "\033[38;2;90;90;102m" # #5A5A66 (最弱)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _ok() -> bool:
|
|
50
|
+
PINK = "\033[38;2;255;79;164m"
|
|
51
|
+
PINK_L = "\033[38;2;255;167;213m"
|
|
52
|
+
PURPLE = "\033[38;2;194;107;255m"
|
|
53
|
+
BLUE = "\033[38;2;110;139;255m"
|
|
54
|
+
MINT = "\033[38;2;111;196;176m"
|
|
55
|
+
MINT_L = "\033[38;2;152;216;200m"
|
|
56
|
+
OK = "\033[38;2;62;229;138m"
|
|
57
|
+
WARN = "\033[38;2;255;179;71m"
|
|
58
|
+
ERR = "\033[38;2;255;107;129m"
|
|
59
|
+
TEXT = "\033[38;2;235;235;245m"
|
|
60
|
+
SUBTLE = "\033[38;2;138;138;150m"
|
|
61
|
+
FAINT = "\033[38;2;90;90;102m"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _color_ok() -> bool:
|
|
48
65
|
if os.environ.get("NO_COLOR"):
|
|
49
66
|
return False
|
|
50
67
|
if os.environ.get("TERM") == "dumb":
|
|
51
68
|
return False
|
|
52
69
|
return sys.stdout.isatty()
|
|
53
70
|
|
|
54
|
-
|
|
55
71
|
def p(text: str, *codes: str) -> str:
|
|
56
|
-
|
|
57
|
-
if not _ok():
|
|
72
|
+
if not _color_ok():
|
|
58
73
|
return text
|
|
59
74
|
return "".join(codes) + text + C.RESET
|
|
60
75
|
|
|
76
|
+
def dim(t: str) -> str:
|
|
77
|
+
return p(t, C.SUBTLE)
|
|
61
78
|
|
|
62
|
-
def
|
|
63
|
-
return p(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def faint(text: str) -> str:
|
|
67
|
-
return p(text, C.FAINT)
|
|
79
|
+
def faint(t: str) -> str:
|
|
80
|
+
return p(t, C.FAINT)
|
|
68
81
|
|
|
82
|
+
def pink(t: str) -> str:
|
|
83
|
+
return p(t, C.PINK)
|
|
69
84
|
|
|
70
|
-
def
|
|
71
|
-
return p(
|
|
85
|
+
def purple(t: str) -> str:
|
|
86
|
+
return p(t, C.PURPLE)
|
|
72
87
|
|
|
88
|
+
def mint(t: str) -> str:
|
|
89
|
+
return p(t, C.MINT)
|
|
73
90
|
|
|
74
|
-
def
|
|
75
|
-
return p(
|
|
91
|
+
def ok(t: str) -> str:
|
|
92
|
+
return p(t, C.OK)
|
|
76
93
|
|
|
94
|
+
def err(t: str) -> str:
|
|
95
|
+
return p(t, C.ERR)
|
|
77
96
|
|
|
78
|
-
def
|
|
79
|
-
return p(
|
|
97
|
+
def bold(t: str) -> str:
|
|
98
|
+
return p(t, C.BOLD)
|
|
80
99
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def err(text: str) -> str:
|
|
87
|
-
return p(text, C.ERR)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# ── 组件 ───────────────────────────────────────────────────────
|
|
91
|
-
|
|
92
|
-
def gradient_text(text: str) -> str:
|
|
93
|
-
"""Pink → Purple → Blue 逐字符渐变。"""
|
|
94
|
-
if not _ok():
|
|
100
|
+
def gradient(text: str) -> str:
|
|
101
|
+
"""Pink → Purple → Blue 渐变"""
|
|
102
|
+
if not _color_ok():
|
|
95
103
|
return text
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
stops = [
|
|
105
|
+
"\033[38;2;255;79;164m",
|
|
106
|
+
"\033[38;2;224;119;220m",
|
|
107
|
+
"\033[38;2;194;107;255m",
|
|
108
|
+
"\033[38;2;160;123;240m",
|
|
109
|
+
"\033[38;2;110;139;255m",
|
|
110
|
+
]
|
|
99
111
|
n = len(text)
|
|
100
112
|
result = []
|
|
101
113
|
for i, ch in enumerate(text):
|
|
102
114
|
if ch == " ":
|
|
103
115
|
result.append(ch)
|
|
104
116
|
continue
|
|
105
|
-
idx = min(int(i / max(n, 1) * len(
|
|
106
|
-
result.append(
|
|
117
|
+
idx = min(int(i / max(n, 1) * len(stops)), len(stops) - 1)
|
|
118
|
+
result.append(stops[idx] + ch + C.RESET)
|
|
107
119
|
return "".join(result)
|
|
108
120
|
|
|
109
121
|
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
# ── 统一图标体系 ───────────────────────────────────────────────
|
|
123
|
+
#
|
|
124
|
+
# 唯一一套符号, 不混用:
|
|
125
|
+
# ✦ 品牌/模型
|
|
126
|
+
# ◐ Agent
|
|
127
|
+
# ◇ Session
|
|
128
|
+
# ▸ 工具
|
|
129
|
+
# ✓ 成功
|
|
130
|
+
# ✕ 失败
|
|
131
|
+
# ❯ 输入
|
|
132
|
+
# · 分隔
|
|
133
|
+
#
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ── 组件 ───────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
def render_banner(version: str = "") -> str:
|
|
139
|
+
"""极简 Header — 品牌名一行, 副标题一行, 结束。"""
|
|
112
140
|
lines = [
|
|
113
141
|
"",
|
|
114
|
-
f"
|
|
115
|
-
"",
|
|
116
|
-
f" {gradient_text('LBS Agent')}",
|
|
117
|
-
f" {dim('Autonomous AI Runtime')}",
|
|
142
|
+
f"{gradient('LBS Agent')}" + (f" {faint(version)}" if version else ""),
|
|
143
|
+
faint("Autonomous AI Runtime"),
|
|
118
144
|
"",
|
|
119
145
|
]
|
|
120
146
|
return "\n".join(lines)
|
|
121
147
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
"
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return "
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def prompt_symbol() -> str:
|
|
149
|
-
"""输入提示符"""
|
|
150
|
-
return p("❯", C.PINK)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def footer() -> str:
|
|
154
|
-
"""底部快捷键栏"""
|
|
155
|
-
keys = [
|
|
156
|
-
("↵", "Send"),
|
|
157
|
-
("↑↓", "History"),
|
|
158
|
-
("Tab", "Complete"),
|
|
159
|
-
("/help", "Commands"),
|
|
160
|
-
("Ctrl+C", "Exit"),
|
|
161
|
-
]
|
|
162
|
-
parts = []
|
|
163
|
-
for key, label in keys:
|
|
164
|
-
parts.append(f"{p(key, C.PURPLE)} {faint(label)}")
|
|
165
|
-
return faint(" ") + faint(" · ".join(parts))
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def usage_line(seconds: float, iterations: int, tools: int) -> str:
|
|
169
|
-
"""用量条"""
|
|
170
|
-
parts = [
|
|
171
|
-
p(f"{seconds:.1f}s", C.MINT_D),
|
|
172
|
-
faint(f"iter {iterations}"),
|
|
173
|
-
faint(f"tools {tools}"),
|
|
174
|
-
]
|
|
175
|
-
return " " + faint(" · ").join(parts)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def result_text(content: str) -> str:
|
|
179
|
-
"""最终结果"""
|
|
180
|
-
return p(content, C.TEXT)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def error_text(content: str) -> str:
|
|
184
|
-
"""错误"""
|
|
185
|
-
return p(f"✕ {content}", C.ERR)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def agent_tag(name: str) -> str:
|
|
189
|
-
"""Agent 标签"""
|
|
190
|
-
return p(f"◐ {name}", C.PURPLE)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def model_tag(model: str) -> str:
|
|
194
|
-
"""模型标签"""
|
|
195
|
-
return p(f"✦ {model}", C.PINK)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def react_header(agent_display: str, model_chain: list[str], tools: list[str]) -> str:
|
|
199
|
-
"""ReAct 任务头 — 极简留白"""
|
|
148
|
+
def render_session_line(agent: str, model: str = "", provider: str = "", session_id: str = "") -> str:
|
|
149
|
+
"""Session 信息压成一行。"""
|
|
150
|
+
parts = [f"{p('◐', C.PURPLE)} {pink(agent)}"]
|
|
151
|
+
if model:
|
|
152
|
+
parts.append(f"{p('✦', C.PINK)} {pink(model)}")
|
|
153
|
+
if provider:
|
|
154
|
+
parts.append(f"{faint(provider)}")
|
|
155
|
+
if session_id:
|
|
156
|
+
parts.append(faint(session_id[:12]))
|
|
157
|
+
return faint(" · ").join(parts)
|
|
158
|
+
|
|
159
|
+
def render_prompt() -> str:
|
|
160
|
+
"""输入提示符。"""
|
|
161
|
+
return f"{p('❯', C.PINK)} "
|
|
162
|
+
|
|
163
|
+
def render_footer() -> str:
|
|
164
|
+
"""快捷键栏 — 一行, 最弱色。"""
|
|
165
|
+
keys = [("↵", "send"), ("/help", "commands"), ("Ctrl+C", "exit")]
|
|
166
|
+
return faint(" ") + faint(" · ").join(
|
|
167
|
+
f"{p(k, C.SUBTLE)} {faint(label)}" for k, label in keys
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def render_react_header(agent: str, model_chain: list[str], tools: list[str]) -> str:
|
|
171
|
+
"""ReAct 任务头 — 无边框, 纯留白。"""
|
|
172
|
+
lines = ["", f" {p('◐', C.PURPLE)} {bold(agent)}"]
|
|
200
173
|
model_str = faint(" → ").join(pink(m) for m in model_chain)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
f" {faint('tools')} {tools_str}" if tools_str else "",
|
|
207
|
-
separator(),
|
|
208
|
-
])
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def react_iter(iteration: int) -> str:
|
|
212
|
-
"""迭代标记"""
|
|
213
|
-
return f"\n {p('⟡', C.PINK)} {p(f'iter {iteration}', C.BOLD, C.PINK)}"
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def thinking_block(content: str) -> str:
|
|
217
|
-
"""思考块"""
|
|
218
|
-
return p(content, C.ITALIC, C.MINT_D)
|
|
174
|
+
lines.append(f" {model_str}")
|
|
175
|
+
if tools:
|
|
176
|
+
lines.append(f" {faint(', ').join(mint(t) for t in tools)}")
|
|
177
|
+
lines.append("")
|
|
178
|
+
return "\n".join(lines)
|
|
219
179
|
|
|
180
|
+
def render_iter(iteration: int, max_iter: int = 0) -> str:
|
|
181
|
+
"""迭代标记。"""
|
|
182
|
+
suffix = faint(f"/{max_iter}") if max_iter else ""
|
|
183
|
+
return f" {p('✦', C.PINK)} {p(f'iter {iteration}', C.BOLD)}{suffix}"
|
|
220
184
|
|
|
221
|
-
def
|
|
222
|
-
"""
|
|
223
|
-
|
|
224
|
-
icon = "✓" if status == "ok" else "✕"
|
|
225
|
-
dur = f" {faint(duration)}" if duration else ""
|
|
226
|
-
return f" {p(icon, status_color)} {faint(name)}{dur}"
|
|
185
|
+
def render_thinking(content: str) -> str:
|
|
186
|
+
"""思考块 — 薄荷绿斜体。"""
|
|
187
|
+
return f" {p(content, C.ITALIC, C.MINT)}"
|
|
227
188
|
|
|
189
|
+
def render_tool_call(name: str, args_preview: str = "") -> str:
|
|
190
|
+
"""工具调用。"""
|
|
191
|
+
suffix = faint(f"({args_preview})") if args_preview else ""
|
|
192
|
+
return f" {p('▸', C.PURPLE)} {mint(name)}{suffix}"
|
|
228
193
|
|
|
229
|
-
def
|
|
230
|
-
"""
|
|
194
|
+
def render_tool_result(name: str, success: bool, preview: str = "") -> str:
|
|
195
|
+
"""工具结果。"""
|
|
231
196
|
icon = "✓" if success else "✕"
|
|
232
|
-
|
|
233
|
-
|
|
197
|
+
color_fn = ok if success else err
|
|
198
|
+
suffix = f" {faint(preview)}" if preview else ""
|
|
199
|
+
return f" {color_fn(icon)} {faint(name)}{suffix}"
|
|
200
|
+
|
|
201
|
+
def render_result(agent: str, success: bool, iterations: int, tools: int, duration: float, error: str = "") -> str:
|
|
202
|
+
"""最终结果摘要 — 两行, 无框。"""
|
|
203
|
+
icon = ok("✓") if success else err("✕")
|
|
204
|
+
parts = [faint(f"iter {iterations}"), faint(f"tools {tools}"), mint(f"{duration:.1f}s")]
|
|
205
|
+
lines = [
|
|
206
|
+
"",
|
|
207
|
+
f" {icon} {bold(agent)}",
|
|
208
|
+
f" {faint(' · ').join(parts)}",
|
|
209
|
+
]
|
|
210
|
+
if error:
|
|
211
|
+
lines.append(f" {err(error[:200])}")
|
|
212
|
+
lines.append("")
|
|
213
|
+
return "\n".join(lines)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
"""LBS Agent REPL —
|
|
1
|
+
"""LBS Agent REPL — Terminal Product v3。
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
终端 = Canvas, 不是 stdout。
|
|
4
|
+
所有输出走 TDS (Terminal Design System)。
|
|
4
5
|
"""
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
@@ -13,11 +14,9 @@ from application.container import AgentSystemContainer
|
|
|
13
14
|
from config.config import AppConfig
|
|
14
15
|
from interfaces.cli._impl.commands.common import project_root
|
|
15
16
|
from interfaces.cli._impl.cli_theme import (
|
|
16
|
-
C, p, dim, faint, pink, purple, mint, ok, err,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
error_text, agent_tag, model_tag, react_header,
|
|
20
|
-
react_iter, thinking_block, tool_card, timeline_item,
|
|
17
|
+
C, p, dim, faint, pink, purple, mint, ok, err, bold,
|
|
18
|
+
gradient, out, pad, render_banner, render_session_line,
|
|
19
|
+
render_prompt, render_footer, render_result,
|
|
21
20
|
)
|
|
22
21
|
|
|
23
22
|
|
|
@@ -46,34 +45,33 @@ def _resolve_agent(config: AppConfig) -> str:
|
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
def _print_banner(state: ChatState) -> None:
|
|
49
|
-
"""
|
|
48
|
+
"""极简启动 — 版心内, 大留白"""
|
|
50
49
|
print()
|
|
51
|
-
print(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
print(pad(gradient("LBS Agent")))
|
|
51
|
+
print(pad(faint("Autonomous AI Runtime")))
|
|
52
|
+
print()
|
|
53
|
+
# session 信息一行
|
|
54
|
+
info = render_session_line(
|
|
55
|
+
agent=state.agent_name,
|
|
56
|
+
model=state.model_name,
|
|
57
|
+
provider=state.provider_name,
|
|
58
|
+
session_id=state.session_id,
|
|
59
|
+
)
|
|
60
|
+
print(pad(info))
|
|
61
61
|
print()
|
|
62
|
-
print(
|
|
63
|
-
print(f" {faint('Ask anything...')}")
|
|
64
|
-
print(separator())
|
|
62
|
+
print(pad(render_footer()))
|
|
65
63
|
print()
|
|
66
64
|
|
|
67
65
|
|
|
68
66
|
def _print_turn_header(state: ChatState) -> None:
|
|
69
|
-
"""输入提示符 —
|
|
67
|
+
"""输入提示符 — 版心内, 无横线"""
|
|
70
68
|
print()
|
|
71
|
-
print(
|
|
69
|
+
print(pad(render_prompt()), end="", flush=True)
|
|
72
70
|
|
|
73
71
|
|
|
74
72
|
def _print_help() -> None:
|
|
75
73
|
print()
|
|
76
|
-
|
|
74
|
+
out(gradient("Commands"))
|
|
77
75
|
print()
|
|
78
76
|
cmds = [
|
|
79
77
|
("/help", "显示帮助"),
|
|
@@ -86,7 +84,7 @@ def _print_help() -> None:
|
|
|
86
84
|
("/clear", "清屏"),
|
|
87
85
|
]
|
|
88
86
|
for cmd, desc in cmds:
|
|
89
|
-
|
|
87
|
+
out(f"{pink(cmd.ljust(12))} {faint(desc)}", level=1)
|
|
90
88
|
print()
|
|
91
89
|
|
|
92
90
|
|
|
@@ -94,8 +92,8 @@ def _cmd_list_agents(state: ChatState) -> None:
|
|
|
94
92
|
agents = state.container.config.agents.agents
|
|
95
93
|
print()
|
|
96
94
|
for name, a in agents.items():
|
|
97
|
-
current = f" {ok('←
|
|
98
|
-
|
|
95
|
+
current = f" {ok('←')}" if name == state.agent_name else ""
|
|
96
|
+
out(f"{p('◐', C.PURPLE)} {pink(name)} {faint(a.display_name)}{current}")
|
|
99
97
|
print()
|
|
100
98
|
|
|
101
99
|
|
|
@@ -103,21 +101,20 @@ def _cmd_list_models(state: ChatState) -> None:
|
|
|
103
101
|
config = state.container.config
|
|
104
102
|
provider_name = state.provider_name or config.models.default_provider
|
|
105
103
|
if provider_name not in config.models.providers:
|
|
106
|
-
|
|
107
|
-
print(f" {error_text(msg)}")
|
|
104
|
+
print(f" {err(f'无 provider {provider_name!r}')}")
|
|
108
105
|
return
|
|
109
106
|
prov = config.models.providers[provider_name]
|
|
110
107
|
print()
|
|
111
|
-
|
|
108
|
+
out(f"{faint('provider')} {purple(provider_name)}")
|
|
112
109
|
for m in prov.models:
|
|
113
110
|
marker = f" {ok('←')}" if m == state.model_name else ""
|
|
114
|
-
|
|
111
|
+
out(f"{p('✦', C.PINK)} {pink(m)}{marker}", level=1)
|
|
115
112
|
print()
|
|
116
113
|
|
|
117
114
|
|
|
118
115
|
def _cmd_history(state: ChatState, n: int = 5) -> None:
|
|
119
116
|
if not state.session_id:
|
|
120
|
-
|
|
117
|
+
out(faint("无 session"))
|
|
121
118
|
return
|
|
122
119
|
try:
|
|
123
120
|
messages = list(state.session_service.session_store.load_session_messages(state.session_id))
|
|
@@ -129,26 +126,24 @@ def _cmd_history(state: ChatState, n: int = 5) -> None:
|
|
|
129
126
|
recent.append((role, content))
|
|
130
127
|
print()
|
|
131
128
|
for role, content in recent:
|
|
132
|
-
rc = C.PINK if role == "user" else C.
|
|
133
|
-
|
|
129
|
+
rc = C.PINK if role == "user" else C.MINT
|
|
130
|
+
out(f"{p(role.ljust(9), rc)} {faint(content)}", level=1)
|
|
134
131
|
print()
|
|
135
132
|
except Exception as e:
|
|
136
|
-
|
|
137
|
-
print(f" {error_text(msg)}")
|
|
133
|
+
out(err(f"读取失败: {e}"))
|
|
138
134
|
|
|
139
135
|
|
|
140
136
|
def _cmd_status(state: ChatState) -> None:
|
|
141
137
|
elapsed = int(time.time() - state.started_at)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
("elapsed", faint(f"{elapsed}s")),
|
|
149
|
-
]
|
|
138
|
+
info = render_session_line(
|
|
139
|
+
agent=state.agent_name,
|
|
140
|
+
model=state.model_name,
|
|
141
|
+
provider=state.provider_name,
|
|
142
|
+
session_id=state.session_id,
|
|
143
|
+
)
|
|
150
144
|
print()
|
|
151
|
-
|
|
145
|
+
out(info)
|
|
146
|
+
out(faint(f"turns {state.turn_count} · {elapsed}s"))
|
|
152
147
|
print()
|
|
153
148
|
|
|
154
149
|
|
|
@@ -165,14 +160,14 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
165
160
|
state.session_id = ""
|
|
166
161
|
state.turn_count = 0
|
|
167
162
|
state.started_at = time.time()
|
|
168
|
-
|
|
163
|
+
out(f"{ok('◇')} {faint('new session')}")
|
|
169
164
|
elif cmd == "/session":
|
|
170
165
|
if arg:
|
|
171
166
|
state.session_id = arg
|
|
172
|
-
|
|
167
|
+
out(f"{ok('◇')} {faint('→')} {pink(arg)}")
|
|
173
168
|
else:
|
|
174
169
|
sid = state.session_id or "(none)"
|
|
175
|
-
|
|
170
|
+
out(f"{faint('session')} {sid}")
|
|
176
171
|
elif cmd == "/status":
|
|
177
172
|
_cmd_status(state)
|
|
178
173
|
elif cmd == "/agent":
|
|
@@ -182,16 +177,15 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
182
177
|
agents = state.container.config.agents.agents
|
|
183
178
|
if arg in agents:
|
|
184
179
|
state.agent_name = arg
|
|
185
|
-
|
|
180
|
+
out(f"{ok('◇')} {faint('→')} {p('◐', C.PURPLE)} {pink(arg)}")
|
|
186
181
|
else:
|
|
187
|
-
|
|
188
|
-
print(f" {error_text(msg)}")
|
|
182
|
+
out(err(f"无 agent '{arg}'"))
|
|
189
183
|
elif cmd == "/model":
|
|
190
184
|
if not arg:
|
|
191
185
|
_cmd_list_models(state)
|
|
192
186
|
else:
|
|
193
187
|
state.model_name = arg
|
|
194
|
-
|
|
188
|
+
out(f"{ok('◇')} {faint('→')} {p('✦', C.PINK)} {pink(arg)}")
|
|
195
189
|
elif cmd == "/clear":
|
|
196
190
|
print("\033[2J\033[H", end="")
|
|
197
191
|
elif cmd == "/history":
|
|
@@ -199,16 +193,15 @@ def _handle_command(state: ChatState, line: str) -> bool:
|
|
|
199
193
|
n = int(arg) if arg else 5
|
|
200
194
|
_cmd_history(state, n)
|
|
201
195
|
except ValueError:
|
|
202
|
-
|
|
196
|
+
out(err("/history [n] — n 须为整数"))
|
|
203
197
|
elif cmd == "/save":
|
|
204
|
-
|
|
198
|
+
out(faint("session 每次 turn 自动保存"))
|
|
205
199
|
else:
|
|
206
|
-
|
|
200
|
+
out(err(f"未知命令: {cmd}"))
|
|
207
201
|
return True
|
|
208
202
|
|
|
209
203
|
|
|
210
204
|
def _run_turn(state: ChatState, user_input: str) -> None:
|
|
211
|
-
"""跑一轮对话"""
|
|
212
205
|
t0 = time.time()
|
|
213
206
|
try:
|
|
214
207
|
result = state.orchestrator.run_agent_sync(
|
|
@@ -220,21 +213,25 @@ def _run_turn(state: ChatState, user_input: str) -> None:
|
|
|
220
213
|
elapsed = time.time() - t0
|
|
221
214
|
if result.success:
|
|
222
215
|
content = (result.content or "").strip()
|
|
223
|
-
print(
|
|
216
|
+
print()
|
|
217
|
+
for line in content.split("\n"):
|
|
218
|
+
out(p(line, C.TEXT))
|
|
224
219
|
else:
|
|
225
220
|
e = result.error or "(无错误信息)"
|
|
226
|
-
|
|
227
|
-
|
|
221
|
+
out(err(e[:300]))
|
|
222
|
+
# 用量 — 缩进, 最弱色
|
|
223
|
+
print()
|
|
224
|
+
out(faint(f" {elapsed:.1f}s · iter {result.iterations} · tools {result.total_tool_calls}"))
|
|
228
225
|
if not state.session_id:
|
|
229
226
|
sid = result.session_id if hasattr(result, "session_id") and result.session_id else ""
|
|
230
227
|
state.session_id = sid or f"chat-{int(state.started_at)}"
|
|
231
228
|
except KeyboardInterrupt:
|
|
232
|
-
print(
|
|
229
|
+
print()
|
|
230
|
+
out(f"{p('⏸', C.WARN)} {faint('已中断')}")
|
|
233
231
|
except Exception as e:
|
|
234
232
|
elapsed = time.time() - t0
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
print(f" {faint(f'{elapsed:.1f}s')}")
|
|
233
|
+
out(err(f"{type(e).__name__}: {str(e)[:200]}"))
|
|
234
|
+
out(faint(f" {elapsed:.1f}s"))
|
|
238
235
|
|
|
239
236
|
|
|
240
237
|
def cmd_chat(
|
|
@@ -245,7 +242,6 @@ def cmd_chat(
|
|
|
245
242
|
query: str = "",
|
|
246
243
|
session_id: str = "",
|
|
247
244
|
) -> int:
|
|
248
|
-
"""进入 chat REPL 或单次 query。"""
|
|
249
245
|
container = AgentSystemContainer(config=config, project_root=project_root)
|
|
250
246
|
|
|
251
247
|
if not agent_name:
|
|
@@ -253,7 +249,7 @@ def cmd_chat(
|
|
|
253
249
|
|
|
254
250
|
if agent_name not in config.agents.agents:
|
|
255
251
|
msg = f"Agent '{agent_name}' 不存在"
|
|
256
|
-
|
|
252
|
+
out(err(msg))
|
|
257
253
|
return 1
|
|
258
254
|
|
|
259
255
|
state = ChatState(
|
|
@@ -275,8 +271,6 @@ def cmd_chat(
|
|
|
275
271
|
|
|
276
272
|
# REPL
|
|
277
273
|
_print_banner(state)
|
|
278
|
-
print(footer())
|
|
279
|
-
print()
|
|
280
274
|
|
|
281
275
|
while True:
|
|
282
276
|
try:
|
|
@@ -299,5 +293,5 @@ def cmd_chat(
|
|
|
299
293
|
state.turn_count += 1
|
|
300
294
|
_run_turn(state, line)
|
|
301
295
|
|
|
302
|
-
|
|
296
|
+
out(f"{ok('◇')} {faint('Session saved. Bye.')}")
|
|
303
297
|
return 0
|