jarvis-ai-assistant 0.3.16__py3-none-any.whl → 0.3.18__py3-none-any.whl
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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +2 -0
- jarvis/jarvis_agent/jarvis.py +362 -182
- jarvis/jarvis_agent/methodology_share_manager.py +2 -2
- jarvis/jarvis_agent/session_manager.py +9 -1
- jarvis/jarvis_agent/task_manager.py +11 -6
- jarvis/jarvis_agent/tool_share_manager.py +2 -2
- jarvis/jarvis_data/config_schema.json +39 -0
- jarvis/jarvis_platform/base.py +8 -1
- jarvis/jarvis_platform_manager/main.py +12 -22
- jarvis/jarvis_tools/read_webpage.py +75 -14
- jarvis/jarvis_utils/config.py +56 -0
- jarvis/jarvis_utils/input.py +49 -3
- jarvis/jarvis_utils/utils.py +151 -0
- {jarvis_ai_assistant-0.3.16.dist-info → jarvis_ai_assistant-0.3.18.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.3.16.dist-info → jarvis_ai_assistant-0.3.18.dist-info}/RECORD +20 -20
- {jarvis_ai_assistant-0.3.16.dist-info → jarvis_ai_assistant-0.3.18.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.16.dist-info → jarvis_ai_assistant-0.3.18.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.16.dist-info → jarvis_ai_assistant-0.3.18.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.16.dist-info → jarvis_ai_assistant-0.3.18.dist-info}/top_level.txt +0 -0
@@ -235,6 +235,30 @@
|
|
235
235
|
},
|
236
236
|
"default": []
|
237
237
|
},
|
238
|
+
"JARVIS_AGENT_DEFINITION_DIRS": {
|
239
|
+
"type": "array",
|
240
|
+
"description": "agent 定义加载目录",
|
241
|
+
"items": {
|
242
|
+
"type": "string"
|
243
|
+
},
|
244
|
+
"default": []
|
245
|
+
},
|
246
|
+
"JARVIS_MULTI_AGENT_DIRS": {
|
247
|
+
"type": "array",
|
248
|
+
"description": "multi_agent 加载目录",
|
249
|
+
"items": {
|
250
|
+
"type": "string"
|
251
|
+
},
|
252
|
+
"default": []
|
253
|
+
},
|
254
|
+
"JARVIS_ROLES_DIRS": {
|
255
|
+
"type": "array",
|
256
|
+
"description": "roles 加载目录",
|
257
|
+
"items": {
|
258
|
+
"type": "string"
|
259
|
+
},
|
260
|
+
"default": []
|
261
|
+
},
|
238
262
|
"JARVIS_CENTRAL_METHODOLOGY_REPO": {
|
239
263
|
"type": "string",
|
240
264
|
"description": "中心方法论Git仓库地址,该仓库会自动添加到方法论加载路径中",
|
@@ -260,6 +284,21 @@
|
|
260
284
|
"description": "是否强制保存记忆",
|
261
285
|
"default": true
|
262
286
|
},
|
287
|
+
"JARVIS_ENABLE_GIT_JCA_SWITCH": {
|
288
|
+
"type": "boolean",
|
289
|
+
"description": "在初始化环境前检测Git仓库并提示可切换到代码开发模式(jca)",
|
290
|
+
"default": false
|
291
|
+
},
|
292
|
+
"JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR": {
|
293
|
+
"type": "boolean",
|
294
|
+
"description": "在进入默认通用代理前,列出可用配置(agent/multi_agent/roles)供选择",
|
295
|
+
"default": false
|
296
|
+
},
|
297
|
+
"JARVIS_IMMEDIATE_ABORT": {
|
298
|
+
"type": "boolean",
|
299
|
+
"description": "是否启用立即中断:在对话迭代中检测到中断信号时立即返回",
|
300
|
+
"default": false
|
301
|
+
},
|
263
302
|
"JARVIS_TOOL_GROUP": {
|
264
303
|
"type": "string",
|
265
304
|
"description": "选择一个预定义的工具配置组",
|
jarvis/jarvis_platform/base.py
CHANGED
@@ -15,9 +15,10 @@ from jarvis.jarvis_utils.config import (
|
|
15
15
|
get_max_input_token_count,
|
16
16
|
get_pretty_output,
|
17
17
|
is_print_prompt,
|
18
|
+
is_immediate_abort,
|
18
19
|
)
|
19
20
|
from jarvis.jarvis_utils.embedding import split_text_into_chunks
|
20
|
-
from jarvis.jarvis_utils.globals import set_in_chat
|
21
|
+
from jarvis.jarvis_utils.globals import set_in_chat, get_interrupt
|
21
22
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
22
23
|
from jarvis.jarvis_utils.tag import ct, ot
|
23
24
|
from jarvis.jarvis_utils.utils import get_context_token_count, while_success, while_true
|
@@ -137,6 +138,8 @@ class BasePlatform(ABC):
|
|
137
138
|
with Live(panel, refresh_per_second=10, transient=False) as live:
|
138
139
|
for s in self.chat(message):
|
139
140
|
response += s
|
141
|
+
if is_immediate_abort() and get_interrupt():
|
142
|
+
return response
|
140
143
|
text_content.append(s, style="bright_white")
|
141
144
|
panel.subtitle = "[yellow]正在回答...[/yellow]"
|
142
145
|
live.update(panel)
|
@@ -150,10 +153,14 @@ class BasePlatform(ABC):
|
|
150
153
|
for s in self.chat(message):
|
151
154
|
print(s, end="", flush=True)
|
152
155
|
response += s
|
156
|
+
if is_immediate_abort() and get_interrupt():
|
157
|
+
return response
|
153
158
|
print()
|
154
159
|
else:
|
155
160
|
for s in self.chat(message):
|
156
161
|
response += s
|
162
|
+
if is_immediate_abort() and get_interrupt():
|
163
|
+
return response
|
157
164
|
# Keep original think tag handling
|
158
165
|
response = re.sub(
|
159
166
|
ot("think") + r".*?" + ct("think"), "", response, flags=re.DOTALL
|
@@ -26,9 +26,7 @@ app = typer.Typer(help="Jarvis AI 平台")
|
|
26
26
|
|
27
27
|
@app.command("info")
|
28
28
|
def list_platforms(
|
29
|
-
platform: Optional[str] = typer.Option(
|
30
|
-
None, "--platform", "-p", help="指定要查看的平台"
|
31
|
-
)
|
29
|
+
platform: Optional[str] = typer.Option(None, "--platform", "-p", help="指定要查看的平台")
|
32
30
|
) -> None:
|
33
31
|
"""列出所有支持的平台和模型,或指定平台的详细信息。"""
|
34
32
|
registry = PlatformRegistry.get_global_platform_registry()
|
@@ -219,13 +217,9 @@ def chat_with_model(
|
|
219
217
|
for entry in conversation_history:
|
220
218
|
file_obj.write(f"{entry['role']}: {entry['content']}\n\n")
|
221
219
|
|
222
|
-
PrettyOutput.print(
|
223
|
-
f"所有对话已保存到 {file_path}", OutputType.SUCCESS
|
224
|
-
)
|
220
|
+
PrettyOutput.print(f"所有对话已保存到 {file_path}", OutputType.SUCCESS)
|
225
221
|
except Exception as exc:
|
226
|
-
PrettyOutput.print(
|
227
|
-
f"保存所有对话失败: {str(exc)}", OutputType.ERROR
|
228
|
-
)
|
222
|
+
PrettyOutput.print(f"保存所有对话失败: {str(exc)}", OutputType.ERROR)
|
229
223
|
continue
|
230
224
|
|
231
225
|
# Check if it is a save_session command
|
@@ -246,9 +240,7 @@ def chat_with_model(
|
|
246
240
|
file_path = file_path[1:-1]
|
247
241
|
|
248
242
|
if platform.save(file_path):
|
249
|
-
PrettyOutput.print(
|
250
|
-
f"会话已保存到 {file_path}", OutputType.SUCCESS
|
251
|
-
)
|
243
|
+
PrettyOutput.print(f"会话已保存到 {file_path}", OutputType.SUCCESS)
|
252
244
|
else:
|
253
245
|
PrettyOutput.print("保存会话失败", OutputType.ERROR)
|
254
246
|
except Exception as exc:
|
@@ -274,9 +266,7 @@ def chat_with_model(
|
|
274
266
|
|
275
267
|
if platform.restore(file_path):
|
276
268
|
conversation_history = [] # Clear local history after loading
|
277
|
-
PrettyOutput.print(
|
278
|
-
f"会话已从 {file_path} 加载", OutputType.SUCCESS
|
279
|
-
)
|
269
|
+
PrettyOutput.print(f"会话已从 {file_path} 加载", OutputType.SUCCESS)
|
280
270
|
else:
|
281
271
|
PrettyOutput.print("加载会话失败", OutputType.ERROR)
|
282
272
|
except Exception as exc:
|
@@ -356,9 +346,7 @@ def validate_platform_model(platform: Optional[str], model: Optional[str]) -> bo
|
|
356
346
|
|
357
347
|
@app.command("chat")
|
358
348
|
def chat_command(
|
359
|
-
platform: Optional[str] = typer.Option(
|
360
|
-
None, "--platform", "-p", help="指定要使用的平台"
|
361
|
-
),
|
349
|
+
platform: Optional[str] = typer.Option(None, "--platform", "-p", help="指定要使用的平台"),
|
362
350
|
model: Optional[str] = typer.Option(None, "--model", "-m", help="指定要使用的模型"),
|
363
351
|
llm_type: str = typer.Option(
|
364
352
|
"normal",
|
@@ -441,9 +429,7 @@ def role_command(
|
|
441
429
|
platform: Optional[str] = typer.Option(
|
442
430
|
None, "--platform", "-p", help="指定要使用的平台,覆盖角色配置"
|
443
431
|
),
|
444
|
-
model: Optional[str] = typer.Option(
|
445
|
-
None, "--model", "-m", help="指定要使用的模型,覆盖角色配置"
|
446
|
-
),
|
432
|
+
model: Optional[str] = typer.Option(None, "--model", "-m", help="指定要使用的模型,覆盖角色配置"),
|
447
433
|
llm_type: Optional[str] = typer.Option(
|
448
434
|
None,
|
449
435
|
"-t",
|
@@ -472,8 +458,12 @@ def role_command(
|
|
472
458
|
PrettyOutput.print(output_str, OutputType.INFO)
|
473
459
|
|
474
460
|
# 让用户选择角色
|
461
|
+
raw_choice = get_single_line_input("请选择角色(输入编号,直接回车退出): ")
|
462
|
+
if not raw_choice.strip():
|
463
|
+
PrettyOutput.print("已取消,退出程序", OutputType.INFO)
|
464
|
+
raise typer.Exit(code=0)
|
475
465
|
try:
|
476
|
-
choice = int(
|
466
|
+
choice = int(raw_choice)
|
477
467
|
selected_role = config["roles"][choice - 1]
|
478
468
|
except (ValueError, IndexError):
|
479
469
|
PrettyOutput.print("无效的选择", OutputType.ERROR)
|
@@ -3,6 +3,13 @@ from typing import Any, Dict
|
|
3
3
|
|
4
4
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
5
5
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
6
|
+
from jarvis.jarvis_utils.config import (
|
7
|
+
get_web_search_platform_name,
|
8
|
+
get_web_search_model_name,
|
9
|
+
)
|
10
|
+
from jarvis.jarvis_utils.http import get as http_get
|
11
|
+
from markdownify import markdownify as md # type: ignore
|
12
|
+
import requests
|
6
13
|
|
7
14
|
|
8
15
|
class WebpageTool:
|
@@ -22,28 +29,82 @@ class WebpageTool:
|
|
22
29
|
}
|
23
30
|
|
24
31
|
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
25
|
-
"""
|
32
|
+
"""
|
33
|
+
读取网页内容。
|
34
|
+
优先使用配置的 web_search_platform 与模型的原生web能力;若不支持,则使用requests抓取页面并调用模型进行分析。
|
35
|
+
"""
|
26
36
|
try:
|
27
|
-
url = args
|
28
|
-
want = args.get("want", "请总结这个网页的主要内容")
|
37
|
+
url = str(args.get("url", "")).strip()
|
38
|
+
want = str(args.get("want", "请总结这个网页的主要内容"))
|
29
39
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
40
|
+
if not url:
|
41
|
+
return {"success": False, "stdout": "", "stderr": "缺少必需参数:url"}
|
42
|
+
|
43
|
+
# 1) 优先使用配置的 Web 搜索平台与模型(若支持web)
|
44
|
+
web_search_platform = get_web_search_platform_name()
|
45
|
+
web_search_model = get_web_search_model_name()
|
46
|
+
if web_search_platform and web_search_model:
|
47
|
+
model = PlatformRegistry().create_platform(web_search_platform)
|
48
|
+
if model:
|
49
|
+
model.set_model_name(web_search_model)
|
50
|
+
if model.support_web():
|
51
|
+
PrettyOutput.print("▶️ 使用配置的 Web 搜索平台读取网页...", OutputType.INFO)
|
52
|
+
model.set_web(True)
|
53
|
+
model.set_suppress_output(False) # type: ignore
|
54
|
+
prompt = f"""请帮我处理这个网页:{url}
|
55
|
+
用户的具体需求是:{want}
|
56
|
+
请按照以下要求输出结果:
|
57
|
+
1. 使用Markdown格式
|
58
|
+
2. 包含网页标题
|
59
|
+
3. 根据用户需求提供准确、完整的信息"""
|
60
|
+
response = model.chat_until_success(prompt) # type: ignore
|
61
|
+
return {"success": True, "stdout": response, "stderr": ""}
|
34
62
|
|
35
|
-
#
|
36
|
-
|
63
|
+
# 2) 然后尝试使用默认平台(normal)的 web 能力
|
64
|
+
model = PlatformRegistry().get_normal_platform()
|
65
|
+
if model.support_web():
|
66
|
+
PrettyOutput.print("▶️ 使用默认平台的 Web 能力读取网页...", OutputType.INFO)
|
67
|
+
model.set_web(True)
|
68
|
+
model.set_suppress_output(False) # type: ignore
|
69
|
+
prompt = f"""请帮我处理这个网页:{url}
|
37
70
|
用户的具体需求是:{want}
|
38
71
|
请按照以下要求输出结果:
|
39
72
|
1. 使用Markdown格式
|
40
73
|
2. 包含网页标题
|
41
74
|
3. 根据用户需求提供准确、完整的信息"""
|
75
|
+
response = model.chat_until_success(prompt) # type: ignore
|
76
|
+
return {"success": True, "stdout": response, "stderr": ""}
|
77
|
+
|
78
|
+
# 3) 回退:使用 requests 抓取网页,再用模型分析
|
79
|
+
PrettyOutput.print("ℹ️ 当前模型不支持Web,使用requests抓取网页并进行分析。", OutputType.INFO)
|
80
|
+
try:
|
81
|
+
resp = http_get(url, timeout=10.0, allow_redirects=True)
|
82
|
+
content_md = md(resp.text, strip=["script", "style"])
|
83
|
+
except requests.exceptions.HTTPError as e:
|
84
|
+
PrettyOutput.print(f"⚠️ HTTP错误 {e.response.status_code} 访问 {url}", OutputType.WARNING)
|
85
|
+
return {"success": False, "stdout": "", "stderr": f"HTTP错误:{e.response.status_code}"}
|
86
|
+
except requests.exceptions.RequestException as e:
|
87
|
+
PrettyOutput.print(f"⚠️ 请求错误: {e}", OutputType.WARNING)
|
88
|
+
return {"success": False, "stdout": "", "stderr": f"请求错误:{e}"}
|
89
|
+
|
90
|
+
if not content_md or not content_md.strip():
|
91
|
+
return {"success": False, "stdout": "", "stderr": "无法从网页抓取有效内容。"}
|
42
92
|
|
43
|
-
|
44
|
-
|
93
|
+
PrettyOutput.print("🧠 正在分析抓取到的网页内容...", OutputType.INFO)
|
94
|
+
summary_prompt = f"""以下是网页 {url} 的内容(已转换为Markdown):
|
95
|
+
----------------
|
96
|
+
{content_md}
|
97
|
+
----------------
|
98
|
+
请根据用户的具体需求“{want}”进行总结与回答:
|
99
|
+
- 使用Markdown格式
|
100
|
+
- 包含网页标题(若可推断)
|
101
|
+
- 提供准确、完整的信息"""
|
102
|
+
|
103
|
+
model = PlatformRegistry().get_normal_platform()
|
104
|
+
model.set_suppress_output(False) # type: ignore
|
105
|
+
summary = model.chat_until_success(summary_prompt) # type: ignore
|
45
106
|
|
46
|
-
return {"success": True, "stdout":
|
107
|
+
return {"success": True, "stdout": summary, "stderr": ""}
|
47
108
|
|
48
109
|
except Exception as e:
|
49
110
|
PrettyOutput.print(f"读取网页失败: {str(e)}", OutputType.ERROR)
|
@@ -55,5 +116,5 @@ class WebpageTool:
|
|
55
116
|
|
56
117
|
@staticmethod
|
57
118
|
def check() -> bool:
|
58
|
-
"""
|
59
|
-
return
|
119
|
+
"""工具可用性检查:始终可用;若模型不支持web将回退到requests抓取。"""
|
120
|
+
return True
|
jarvis/jarvis_utils/config.py
CHANGED
@@ -309,6 +309,36 @@ def get_methodology_dirs() -> List[str]:
|
|
309
309
|
return GLOBAL_CONFIG_DATA.get("JARVIS_METHODOLOGY_DIRS", [])
|
310
310
|
|
311
311
|
|
312
|
+
def get_agent_definition_dirs() -> List[str]:
|
313
|
+
"""
|
314
|
+
获取 agent 定义的加载目录。
|
315
|
+
|
316
|
+
返回:
|
317
|
+
List[str]: agent 定义加载目录列表
|
318
|
+
"""
|
319
|
+
return GLOBAL_CONFIG_DATA.get("JARVIS_AGENT_DEFINITION_DIRS", [])
|
320
|
+
|
321
|
+
|
322
|
+
def get_multi_agent_dirs() -> List[str]:
|
323
|
+
"""
|
324
|
+
获取 multi_agent 的加载目录。
|
325
|
+
|
326
|
+
返回:
|
327
|
+
List[str]: multi_agent 加载目录列表
|
328
|
+
"""
|
329
|
+
return GLOBAL_CONFIG_DATA.get("JARVIS_MULTI_AGENT_DIRS", [])
|
330
|
+
|
331
|
+
|
332
|
+
def get_roles_dirs() -> List[str]:
|
333
|
+
"""
|
334
|
+
获取 roles 的加载目录。
|
335
|
+
|
336
|
+
返回:
|
337
|
+
List[str]: roles 加载目录列表
|
338
|
+
"""
|
339
|
+
return GLOBAL_CONFIG_DATA.get("JARVIS_ROLES_DIRS", [])
|
340
|
+
|
341
|
+
|
312
342
|
def get_central_methodology_repo() -> str:
|
313
343
|
"""
|
314
344
|
获取中心方法论Git仓库地址。
|
@@ -576,3 +606,29 @@ def get_tool_dont_use_list() -> List[str]:
|
|
576
606
|
"""
|
577
607
|
config = _get_resolved_tool_config()
|
578
608
|
return config.get("dont_use", [])
|
609
|
+
|
610
|
+
|
611
|
+
def is_enable_git_repo_jca_switch() -> bool:
|
612
|
+
"""
|
613
|
+
是否启用:在初始化环境前检测Git仓库并提示可切换到代码开发模式(jca)
|
614
|
+
默认关闭
|
615
|
+
"""
|
616
|
+
return GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_GIT_JCA_SWITCH", False) is True
|
617
|
+
|
618
|
+
|
619
|
+
def is_enable_builtin_config_selector() -> bool:
|
620
|
+
"""
|
621
|
+
是否启用:在进入默认通用代理前,列出可用配置(agent/multi_agent/roles)供选择
|
622
|
+
默认关闭
|
623
|
+
"""
|
624
|
+
return (
|
625
|
+
GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR", False) is True
|
626
|
+
)
|
627
|
+
|
628
|
+
|
629
|
+
def is_immediate_abort() -> bool:
|
630
|
+
"""
|
631
|
+
是否启用立即中断:当在对话过程中检测到用户中断信号时,立即停止输出并返回。
|
632
|
+
默认关闭
|
633
|
+
"""
|
634
|
+
return GLOBAL_CONFIG_DATA.get("JARVIS_IMMEDIATE_ABORT", False) is True
|
jarvis/jarvis_utils/input.py
CHANGED
@@ -15,7 +15,7 @@ from colorama import Fore
|
|
15
15
|
from colorama import Style as ColoramaStyle
|
16
16
|
from fuzzywuzzy import process
|
17
17
|
from prompt_toolkit import PromptSession
|
18
|
-
from prompt_toolkit.application import Application
|
18
|
+
from prompt_toolkit.application import Application, run_in_terminal
|
19
19
|
from prompt_toolkit.completion import CompleteEvent
|
20
20
|
from prompt_toolkit.completion import (
|
21
21
|
Completer,
|
@@ -40,6 +40,28 @@ from jarvis.jarvis_utils.tag import ot
|
|
40
40
|
# Sentinel value to indicate that Ctrl+O was pressed
|
41
41
|
CTRL_O_SENTINEL = "__CTRL_O_PRESSED__"
|
42
42
|
|
43
|
+
# Persistent hint marker for multiline input (shown only once across runs)
|
44
|
+
_MULTILINE_HINT_MARK_FILE = os.path.join(get_data_dir(), "multiline_enter_hint_shown")
|
45
|
+
|
46
|
+
|
47
|
+
def _multiline_hint_already_shown() -> bool:
|
48
|
+
"""Check if the multiline Enter hint has been shown before (persisted)."""
|
49
|
+
try:
|
50
|
+
return os.path.exists(_MULTILINE_HINT_MARK_FILE)
|
51
|
+
except Exception:
|
52
|
+
return False
|
53
|
+
|
54
|
+
|
55
|
+
def _mark_multiline_hint_shown() -> None:
|
56
|
+
"""Persist that the multiline Enter hint has been shown."""
|
57
|
+
try:
|
58
|
+
os.makedirs(os.path.dirname(_MULTILINE_HINT_MARK_FILE), exist_ok=True)
|
59
|
+
with open(_MULTILINE_HINT_MARK_FILE, "w", encoding="utf-8") as f:
|
60
|
+
f.write("1")
|
61
|
+
except Exception:
|
62
|
+
# Non-critical persistence failure; ignore to avoid breaking input flow
|
63
|
+
pass
|
64
|
+
|
43
65
|
|
44
66
|
def get_single_line_input(tip: str, default: str = "") -> str:
|
45
67
|
"""
|
@@ -315,8 +337,32 @@ def _get_multiline_input_internal(tip: str) -> str:
|
|
315
337
|
"""
|
316
338
|
bindings = KeyBindings()
|
317
339
|
|
340
|
+
# Show a one-time hint on the first Enter press in this invocation
|
341
|
+
first_enter_hint_shown = False
|
342
|
+
|
318
343
|
@bindings.add("enter")
|
319
344
|
def _(event):
|
345
|
+
nonlocal first_enter_hint_shown
|
346
|
+
if not first_enter_hint_shown and not _multiline_hint_already_shown():
|
347
|
+
first_enter_hint_shown = True
|
348
|
+
|
349
|
+
def _show_notice():
|
350
|
+
print(
|
351
|
+
f"{Fore.YELLOW}提示:当前支持多行输入。输入完成请使用 Ctrl+J 确认;Enter 仅用于换行。{ColoramaStyle.RESET_ALL}"
|
352
|
+
)
|
353
|
+
try:
|
354
|
+
input("按回车继续...")
|
355
|
+
except Exception:
|
356
|
+
pass
|
357
|
+
# Persist the hint so it won't be shown again in future runs
|
358
|
+
try:
|
359
|
+
_mark_multiline_hint_shown()
|
360
|
+
except Exception:
|
361
|
+
pass
|
362
|
+
|
363
|
+
run_in_terminal(_show_notice)
|
364
|
+
return
|
365
|
+
|
320
366
|
if event.current_buffer.complete_state:
|
321
367
|
completion = event.current_buffer.complete_state.current_completion
|
322
368
|
if completion:
|
@@ -363,7 +409,7 @@ def get_multiline_input(tip: str) -> str:
|
|
363
409
|
此函数处理控制流,允许在不破坏终端状态的情况下处理历史记录复制。
|
364
410
|
"""
|
365
411
|
PrettyOutput.section(
|
366
|
-
"用户输入 - 使用 @ 触发文件补全,Tab 选择补全项,Ctrl+J
|
412
|
+
"用户输入 - 使用 @ 触发文件补全,Tab 选择补全项,Ctrl+J 确认,Ctrl+O 从历史记录中选择消息复制,按 Ctrl+C/D 取消输入",
|
367
413
|
OutputType.USER,
|
368
414
|
)
|
369
415
|
|
@@ -372,7 +418,7 @@ def get_multiline_input(tip: str) -> str:
|
|
372
418
|
|
373
419
|
if user_input == CTRL_O_SENTINEL:
|
374
420
|
_show_history_and_copy()
|
375
|
-
tip = "请继续输入(或按Ctrl+J
|
421
|
+
tip = "请继续输入(或按Ctrl+J确认):"
|
376
422
|
continue
|
377
423
|
else:
|
378
424
|
if not user_input:
|
jarvis/jarvis_utils/utils.py
CHANGED
@@ -880,12 +880,163 @@ def _load_and_process_config(jarvis_dir: str, config_file: str) -> None:
|
|
880
880
|
config_file: 配置文件路径
|
881
881
|
"""
|
882
882
|
from jarvis.jarvis_utils.input import user_confirm as get_yes_no
|
883
|
+
from jarvis.jarvis_utils.input import get_single_line_input
|
883
884
|
|
884
885
|
try:
|
885
886
|
content, config_data = _load_config_file(config_file)
|
886
887
|
_ensure_schema_declaration(jarvis_dir, config_file, content, config_data)
|
887
888
|
set_global_env_data(config_data)
|
888
889
|
_process_env_variables(config_data)
|
890
|
+
|
891
|
+
# 首次运行提示:为新功能开关询问用户(默认值见各配置的getter)
|
892
|
+
def _ask_and_set(_key, _tip, _default, _type="bool"):
|
893
|
+
try:
|
894
|
+
if _key in config_data:
|
895
|
+
return False
|
896
|
+
if _type == "bool":
|
897
|
+
val = get_yes_no(_tip, default=bool(_default))
|
898
|
+
config_data[_key] = bool(val)
|
899
|
+
else:
|
900
|
+
val = get_single_line_input(f"{_tip}", default=str(_default or ""))
|
901
|
+
config_data[_key] = val.strip()
|
902
|
+
return True
|
903
|
+
except Exception:
|
904
|
+
# 出现异常时按默认值设置,避免中断流程
|
905
|
+
config_data[_key] = (
|
906
|
+
bool(_default) if _type == "bool" else str(_default or "")
|
907
|
+
)
|
908
|
+
return True
|
909
|
+
|
910
|
+
changed = False
|
911
|
+
# 现有两个开关
|
912
|
+
changed = (
|
913
|
+
_ask_and_set(
|
914
|
+
"JARVIS_ENABLE_GIT_JCA_SWITCH",
|
915
|
+
"是否在检测到Git仓库时,提示并可自动切换到代码开发模式(jca)?",
|
916
|
+
False,
|
917
|
+
"bool",
|
918
|
+
)
|
919
|
+
or changed
|
920
|
+
)
|
921
|
+
changed = (
|
922
|
+
_ask_and_set(
|
923
|
+
"JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR",
|
924
|
+
"在进入默认通用代理前,是否先列出可用配置(agent/multi_agent/roles)供选择?",
|
925
|
+
False,
|
926
|
+
"bool",
|
927
|
+
)
|
928
|
+
or changed
|
929
|
+
)
|
930
|
+
|
931
|
+
# 新增的配置项交互
|
932
|
+
changed = (
|
933
|
+
_ask_and_set(
|
934
|
+
"JARVIS_PRETTY_OUTPUT",
|
935
|
+
"是否启用更美观的终端输出(Pretty Output)?",
|
936
|
+
False,
|
937
|
+
"bool",
|
938
|
+
)
|
939
|
+
or changed
|
940
|
+
)
|
941
|
+
changed = (
|
942
|
+
_ask_and_set(
|
943
|
+
"JARVIS_PRINT_PROMPT",
|
944
|
+
"是否打印发送给模型的提示词(Prompt)?",
|
945
|
+
False,
|
946
|
+
"bool",
|
947
|
+
)
|
948
|
+
or changed
|
949
|
+
)
|
950
|
+
changed = (
|
951
|
+
_ask_and_set(
|
952
|
+
"JARVIS_IMMEDIATE_ABORT",
|
953
|
+
"是否启用立即中断?\n- 选择 是/true:在对话输出流的每次迭代中检测到用户中断(例如 Ctrl+C)时,立即返回当前已生成的内容并停止继续输出。\n- 选择 否/false:不会在输出过程中立刻返回,而是按既有流程处理(不中途打断输出)。",
|
954
|
+
False,
|
955
|
+
"bool",
|
956
|
+
)
|
957
|
+
or changed
|
958
|
+
)
|
959
|
+
changed = (
|
960
|
+
_ask_and_set(
|
961
|
+
"JARVIS_ENABLE_STATIC_ANALYSIS",
|
962
|
+
"是否启用静态代码分析(Static Analysis)?",
|
963
|
+
True,
|
964
|
+
"bool",
|
965
|
+
)
|
966
|
+
or changed
|
967
|
+
)
|
968
|
+
changed = (
|
969
|
+
_ask_and_set(
|
970
|
+
"JARVIS_USE_METHODOLOGY",
|
971
|
+
"是否启用方法论系统(Methodology)?",
|
972
|
+
True,
|
973
|
+
"bool",
|
974
|
+
)
|
975
|
+
or changed
|
976
|
+
)
|
977
|
+
changed = (
|
978
|
+
_ask_and_set(
|
979
|
+
"JARVIS_USE_ANALYSIS",
|
980
|
+
"是否启用分析流程(Analysis)?",
|
981
|
+
True,
|
982
|
+
"bool",
|
983
|
+
)
|
984
|
+
or changed
|
985
|
+
)
|
986
|
+
changed = (
|
987
|
+
_ask_and_set(
|
988
|
+
"JARVIS_FORCE_SAVE_MEMORY",
|
989
|
+
"是否强制保存会话记忆?",
|
990
|
+
True,
|
991
|
+
"bool",
|
992
|
+
)
|
993
|
+
or changed
|
994
|
+
)
|
995
|
+
changed = (
|
996
|
+
_ask_and_set(
|
997
|
+
"JARVIS_CENTRAL_METHODOLOGY_REPO",
|
998
|
+
"请输入中心方法论仓库地址(可留空跳过):",
|
999
|
+
"",
|
1000
|
+
"str",
|
1001
|
+
)
|
1002
|
+
or changed
|
1003
|
+
)
|
1004
|
+
changed = (
|
1005
|
+
_ask_and_set(
|
1006
|
+
"JARVIS_CENTRAL_TOOL_REPO",
|
1007
|
+
"请输入中心工具仓库地址(可留空跳过):",
|
1008
|
+
"",
|
1009
|
+
"str",
|
1010
|
+
)
|
1011
|
+
or changed
|
1012
|
+
)
|
1013
|
+
|
1014
|
+
if changed:
|
1015
|
+
# 保留schema声明,如无则自动补充
|
1016
|
+
header = ""
|
1017
|
+
try:
|
1018
|
+
with open(config_file, "r", encoding="utf-8") as rf:
|
1019
|
+
first_line = rf.readline()
|
1020
|
+
if first_line.startswith("# yaml-language-server: $schema="):
|
1021
|
+
header = first_line
|
1022
|
+
except Exception:
|
1023
|
+
header = ""
|
1024
|
+
yaml_str = yaml.dump(config_data, allow_unicode=True, sort_keys=False)
|
1025
|
+
if not header:
|
1026
|
+
schema_path = Path(
|
1027
|
+
os.path.relpath(
|
1028
|
+
Path(__file__).parent.parent
|
1029
|
+
/ "jarvis_data"
|
1030
|
+
/ "config_schema.json",
|
1031
|
+
start=jarvis_dir,
|
1032
|
+
)
|
1033
|
+
)
|
1034
|
+
header = f"# yaml-language-server: $schema={schema_path}\n"
|
1035
|
+
with open(config_file, "w", encoding="utf-8") as wf:
|
1036
|
+
wf.write(header)
|
1037
|
+
wf.write(yaml_str)
|
1038
|
+
# 更新全局配置
|
1039
|
+
set_global_env_data(config_data)
|
889
1040
|
except Exception:
|
890
1041
|
PrettyOutput.print("加载配置文件失败", OutputType.ERROR)
|
891
1042
|
if get_yes_no("配置文件格式错误,是否删除并重新配置?"):
|