langchain-agentx-cli 0.2.1__tar.gz → 0.3.0__tar.gz
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.
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/PKG-INFO +2 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/__init__.py +1 -1
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/app.py +30 -0
- langchain_agentx_cli-0.3.0/langchain_agentx_cli/bridge/session_bridge.py +465 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/cli.py +15 -3
- langchain_agentx_cli-0.3.0/langchain_agentx_cli/observation/__init__.py +44 -0
- langchain_agentx_cli-0.3.0/langchain_agentx_cli/observation/config.py +35 -0
- langchain_agentx_cli-0.3.0/langchain_agentx_cli/observation/observer.py +173 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/screens/repl.py +0 -3
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/state/task_store.py +63 -1
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/consumer.py +4 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/message_events.py +122 -4
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/message_list.py +353 -51
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/messages/__init__.py +12 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/messages/assistant_message.py +16 -0
- langchain_agentx_cli-0.3.0/langchain_agentx_cli/widgets/messages/hook_message.py +72 -0
- langchain_agentx_cli-0.3.0/langchain_agentx_cli/widgets/messages/stop_hook_summary.py +70 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/__init__.py +15 -8
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/agent/widget.py +16 -1
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/ask_user_question/widget.py +34 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/base.py +52 -3
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/bash/widget.py +30 -1
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/batch_edit/widget.py +19 -9
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/edit/widget.py +6 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/glob/widget.py +6 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/grep/widget.py +6 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/helpers.py +44 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/read/widget.py +39 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/skill/widget.py +5 -1
- langchain_agentx_cli-0.3.0/langchain_agentx_cli/widgets/tools/user_message/widget.py +83 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/webfetch/widget.py +16 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/websearch/widget.py +11 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/write/widget.py +8 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/PKG-INFO +2 -2
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/SOURCES.txt +6 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/requires.txt +1 -1
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/pyproject.toml +2 -2
- langchain_agentx_cli-0.3.0/tests/test_llm_config_chain.py +182 -0
- langchain_agentx_cli-0.2.1/langchain_agentx_cli/bridge/session_bridge.py +0 -276
- langchain_agentx_cli-0.2.1/langchain_agentx_cli/widgets/tools/user_message/widget.py +0 -88
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/LICENSE +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/README.md +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/__main__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/bootstrap.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/bridge/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/clear.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/compact.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/help.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/model.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/quit.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/theme.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/parser.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/providers/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/providers/sdk_commands.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/registry.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/completion/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/completion/base.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/completion/command_source.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/completion/history_source.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/config.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/history/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/history/store.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/keybindings/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/keybindings/bindings.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/llm_config.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/permissions/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/permissions/factory.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/permissions/policy.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/prompts/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/prompts/assembler.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/prompts/sections.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/prompts/system_prompt.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/screens/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/session_factory.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/session_options.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/state/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/colors.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/detection.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/manager.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/settings.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/system_theme.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/themes.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/watcher.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tools/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tools/registry.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/clipboard.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/message_selection.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/cache.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/component_manager.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/config.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/dialog.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/inject.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/models.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/presenters/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/presenters/base.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/presenters/bash.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/presenters/fallback.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/presenters/file.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/queue.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/safe_screen.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/welcome.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/compact_progress.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/completion_overlay.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/input_area.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/messages/error_message.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/messages/rendering.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/messages/thinking_message.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/pending_permission.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/permission_inline.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/permission_keybindings.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/spinner.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/task_panel.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/agent/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/ask_user_question/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/bash/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/batch_edit/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/edit/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/glob/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/grep/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/read/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/skill/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/user_message/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/webfetch/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/websearch/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/write/__init__.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/dependency_links.txt +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/entry_points.txt +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/not-zip-safe +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/top_level.txt +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/setup.cfg +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_app_interrupt.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_repl_commands.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_repl_submit_flow.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_repl_ui.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_smoke.py +0 -0
- {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_welcome.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-agentx-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Terminal CLI/TUI for AgentX: migrate Claude Code Ink UI, backed by langchain-agentx-python SDK.
|
|
5
5
|
Author: GoodMood2008
|
|
6
6
|
License: Apache-2.0
|
|
@@ -22,7 +22,7 @@ Classifier: Topic :: Software Development
|
|
|
22
22
|
Requires-Python: >=3.11
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
-
Requires-Dist: langchain-agentx-python<0.
|
|
25
|
+
Requires-Dist: langchain-agentx-python<0.6.0,>=0.4.3
|
|
26
26
|
Requires-Dist: click>=8.1
|
|
27
27
|
Requires-Dist: textual>=0.79.0
|
|
28
28
|
Provides-Extra: dev
|
|
@@ -10,6 +10,7 @@ app.py — Textual ReplApp 容器。
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
from pathlib import Path
|
|
13
14
|
from typing import TYPE_CHECKING
|
|
14
15
|
|
|
15
16
|
from textual import on
|
|
@@ -55,12 +56,15 @@ class ReplApp(App[None]):
|
|
|
55
56
|
show=False,
|
|
56
57
|
priority=True,
|
|
57
58
|
),
|
|
59
|
+
# 对齐 CC: Ctrl+O 切换 Transcript 模式
|
|
60
|
+
Binding("ctrl+o", "toggle_transcript", "Transcript", show=False, priority=True),
|
|
58
61
|
]
|
|
59
62
|
|
|
60
63
|
def __init__(
|
|
61
64
|
self,
|
|
62
65
|
launch_config: ReplLaunchConfig,
|
|
63
66
|
session: AgentSession | None = None,
|
|
67
|
+
debug: bool = False,
|
|
64
68
|
) -> None:
|
|
65
69
|
super().__init__()
|
|
66
70
|
self._launch_config = launch_config
|
|
@@ -74,6 +78,7 @@ class ReplApp(App[None]):
|
|
|
74
78
|
self._screen_messenger = ReplScreenMessenger(self)
|
|
75
79
|
# Phase 3: 加载用户偏好(供 MessageListWidget 和工具 Widget 使用)
|
|
76
80
|
self.preferences: AppUserPreferences = AppPreferencesStore().load()
|
|
81
|
+
self._debug = debug
|
|
77
82
|
|
|
78
83
|
@property
|
|
79
84
|
def screen_messenger(self) -> ReplScreenMessenger:
|
|
@@ -92,6 +97,13 @@ class ReplApp(App[None]):
|
|
|
92
97
|
return self._bridge
|
|
93
98
|
|
|
94
99
|
def on_mount(self) -> None:
|
|
100
|
+
# DEBUG: 启用观测日志
|
|
101
|
+
if self._debug:
|
|
102
|
+
from langchain_agentx_cli.observation import observer
|
|
103
|
+
|
|
104
|
+
observer.enable(self.workspace_root)
|
|
105
|
+
observer.info("[APP] ReplApp mounted, workspace=%s", self.workspace_root)
|
|
106
|
+
|
|
95
107
|
prefs = AppPreferencesStore().load()
|
|
96
108
|
initial_setting = self._launch_config.theme or prefs.theme
|
|
97
109
|
self.theme_manager = ThemeManager(self)
|
|
@@ -149,3 +161,21 @@ class ReplApp(App[None]):
|
|
|
149
161
|
if self._bridge is not None:
|
|
150
162
|
self._bridge.cancel_stream()
|
|
151
163
|
self.exit(0)
|
|
164
|
+
|
|
165
|
+
def action_toggle_transcript(self) -> None:
|
|
166
|
+
"""切换 Transcript 模式(对齐 CC Ctrl+O)。"""
|
|
167
|
+
screen = self.screen
|
|
168
|
+
if isinstance(screen, ReplScreen):
|
|
169
|
+
message_list = screen._message_list()
|
|
170
|
+
if message_list is not None:
|
|
171
|
+
from langchain_agentx_cli.observation import info
|
|
172
|
+
old_mode = message_list.display_mode
|
|
173
|
+
message_list.toggle_transcript_mode()
|
|
174
|
+
new_mode = message_list.display_mode
|
|
175
|
+
info("[APP] transcript toggled: %s → %s", old_mode, new_mode)
|
|
176
|
+
|
|
177
|
+
# 显示通知
|
|
178
|
+
if new_mode.value == "transcript":
|
|
179
|
+
self.notify("Transcript 模式已启用(显示所有内容)", severity="information", timeout=3)
|
|
180
|
+
else:
|
|
181
|
+
self.notify("已返回 Normal 模式", severity="information", timeout=2)
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
"""
|
|
2
|
+
bridge/session_bridge.py — SessionBridge(SDK 事件 → Textual Message)。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
消费 LangchainAgentEvent,经 Worker 异步调用 SDK,向 App 投递 UI Message;
|
|
6
|
+
创建权限 Queue/Resolver 并注入 AgentSession。
|
|
7
|
+
|
|
8
|
+
链路位置:
|
|
9
|
+
ReplApp → SessionBridge → AgentSession.stream_loop_events
|
|
10
|
+
→ LangGraphToLangchainAgentEventAdapter.adapt();
|
|
11
|
+
权限:AgentSessionPermissionResolver ← PermissionQueueConsumer ← ConfirmQueue。
|
|
12
|
+
|
|
13
|
+
当前裁剪范围:
|
|
14
|
+
不解析 LangGraph 原始事件名;Resolver 超时 300s(见 tui/permissions/config.py)。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
|
+
|
|
24
|
+
from langchain_agentx.tool_runtime.resolvers import (
|
|
25
|
+
AgentSessionPermissionResolver,
|
|
26
|
+
PermissionRequest,
|
|
27
|
+
)
|
|
28
|
+
from langchain_agentx_cli.observation import debug, info
|
|
29
|
+
from langchain_agentx_cli.tui.permissions import (
|
|
30
|
+
PermissionQueueConsumer,
|
|
31
|
+
SessionPermissionCache,
|
|
32
|
+
inject_permission_resolver,
|
|
33
|
+
)
|
|
34
|
+
from langchain_agentx_cli.tui.permissions.config import DEFAULT_PERMISSION_TIMEOUT_SECONDS
|
|
35
|
+
from langchain_agentx_cli.tui.permissions.dialog import DialogPermissionDecisionPort
|
|
36
|
+
from langchain_agentx_cli.tui.permissions.queue import ConfirmQueue
|
|
37
|
+
|
|
38
|
+
from langchain_agentx import (
|
|
39
|
+
LangGraphToLangchainAgentEventAdapter,
|
|
40
|
+
LangchainAgentEvent,
|
|
41
|
+
LangchainAgentEventType,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
from langchain_agentx_cli.widgets.messages import (
|
|
45
|
+
AssistantMessageChunk,
|
|
46
|
+
ContextCompactedMessage,
|
|
47
|
+
ContextCompactingMessage,
|
|
48
|
+
ErrorMessageOccurred,
|
|
49
|
+
HookFinished,
|
|
50
|
+
HookStarted,
|
|
51
|
+
TaskProgress,
|
|
52
|
+
TaskStarted,
|
|
53
|
+
ThinkingContentDelta,
|
|
54
|
+
ThinkingEnded,
|
|
55
|
+
ThinkingStarted,
|
|
56
|
+
ToolUseCompleted,
|
|
57
|
+
ToolUseStarted,
|
|
58
|
+
TurnInProgress,
|
|
59
|
+
UserMessage,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if TYPE_CHECKING:
|
|
63
|
+
from langchain_agentx import AgentSession
|
|
64
|
+
|
|
65
|
+
from langchain_agentx_cli.app import ReplApp
|
|
66
|
+
|
|
67
|
+
MVP_ADAPTER_CONFIG: dict[str, bool] = {
|
|
68
|
+
"enable_reasoning_events": True,
|
|
69
|
+
"enable_step_events": False,
|
|
70
|
+
"enable_hook_events": True, # 启用 Hook 事件
|
|
71
|
+
"enable_compact_events": True,
|
|
72
|
+
"enable_subagent_events": True, # Phase 3: 启用子 agent 事件
|
|
73
|
+
"synthesize_tool_call": True,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_LLM_FAILED_PREFIX = "[LLM request failed]"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class _TurnSegmentState:
|
|
81
|
+
"""一个 compaction 段内的流式状态;在 turn 开始和 COMPACT_END 时整体重置。"""
|
|
82
|
+
|
|
83
|
+
streamed_text: bool = False
|
|
84
|
+
|
|
85
|
+
def reset(self) -> None:
|
|
86
|
+
self.streamed_text = False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class SessionBridge:
|
|
90
|
+
"""TUI 与 SDK 之间的桥接层(消费 LangchainAgentEvent)。"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, app: ReplApp, session: AgentSession) -> None:
|
|
93
|
+
self._app = app
|
|
94
|
+
self._screen_messenger = app.screen_messenger
|
|
95
|
+
self._session = session
|
|
96
|
+
self._adapter = LangGraphToLangchainAgentEventAdapter(config=dict(MVP_ADAPTER_CONFIG))
|
|
97
|
+
self._current_worker: Any = None
|
|
98
|
+
self._seg = _TurnSegmentState()
|
|
99
|
+
# 对齐 CC: 集中式权限队列状态管理
|
|
100
|
+
self._permission_queue: asyncio.Queue[PermissionRequest] = asyncio.Queue()
|
|
101
|
+
self._permission_cache = SessionPermissionCache()
|
|
102
|
+
self._confirm_queue = ConfirmQueue(max_size=10) # 对齐 CC confirmQueue
|
|
103
|
+
self._permission_resolver = AgentSessionPermissionResolver(
|
|
104
|
+
request_queue=self._permission_queue,
|
|
105
|
+
timeout=DEFAULT_PERMISSION_TIMEOUT_SECONDS,
|
|
106
|
+
)
|
|
107
|
+
inject_permission_resolver(session, self._permission_resolver)
|
|
108
|
+
self._permission_consumer = PermissionQueueConsumer(
|
|
109
|
+
queue=self._permission_queue,
|
|
110
|
+
cache=self._permission_cache,
|
|
111
|
+
confirm_queue=self._confirm_queue, # 传入 confirm_queue
|
|
112
|
+
app=app,
|
|
113
|
+
decision_port=DialogPermissionDecisionPort(app),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def _post_ui(self, message: object) -> None:
|
|
117
|
+
self._screen_messenger.post(message)
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def _build_fallback_text(data: dict[str, Any]) -> str:
|
|
121
|
+
"""为未升级的 widget 提供兜底渲染文本:summary + payload。
|
|
122
|
+
|
|
123
|
+
与 SDK envelope_to_tool_output 的语义一致(summary\\npayload),
|
|
124
|
+
让仍按 event.output 字符串渲染的 widget 不需要改动即可工作。
|
|
125
|
+
"""
|
|
126
|
+
summary = str(data.get("summary", ""))
|
|
127
|
+
payload = data.get("payload")
|
|
128
|
+
if payload is None or payload == "":
|
|
129
|
+
return summary
|
|
130
|
+
if isinstance(payload, str):
|
|
131
|
+
payload_str = payload
|
|
132
|
+
else:
|
|
133
|
+
import json
|
|
134
|
+
try:
|
|
135
|
+
payload_str = json.dumps(payload, ensure_ascii=False)
|
|
136
|
+
except (TypeError, ValueError):
|
|
137
|
+
payload_str = str(payload)
|
|
138
|
+
return f"{summary}\n{payload_str}" if summary else payload_str
|
|
139
|
+
|
|
140
|
+
def submit_input(self, text: str) -> None:
|
|
141
|
+
self._post_ui(UserMessage(content=text))
|
|
142
|
+
self._post_ui(TurnInProgress(in_progress=True))
|
|
143
|
+
self._current_worker = self._app.run_worker(
|
|
144
|
+
self._stream_turn(text),
|
|
145
|
+
name="sdk-stream",
|
|
146
|
+
exit_on_error=False,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
async def _stream_turn(self, user_input: str) -> None:
|
|
150
|
+
self._adapter._reset_state()
|
|
151
|
+
self._seg.reset()
|
|
152
|
+
raw_events = self._session.stream_loop_events(user_input)
|
|
153
|
+
try:
|
|
154
|
+
async for event in self._adapter.adapt(raw_events):
|
|
155
|
+
self._dispatch_event(event)
|
|
156
|
+
except (KeyboardInterrupt, SystemExit):
|
|
157
|
+
raise
|
|
158
|
+
except Exception as exc:
|
|
159
|
+
debug("stream_turn error", exc_info=True)
|
|
160
|
+
self._post_ui(ErrorMessageOccurred(error=str(exc)))
|
|
161
|
+
finally:
|
|
162
|
+
self._post_ui(TurnInProgress(in_progress=False))
|
|
163
|
+
self._current_worker = None
|
|
164
|
+
|
|
165
|
+
def _dispatch_event(self, event: LangchainAgentEvent) -> None:
|
|
166
|
+
data = event.data or {}
|
|
167
|
+
# DEBUG: 追踪所有事件
|
|
168
|
+
info(
|
|
169
|
+
"[BRIDGE] event=%s data_keys=%s",
|
|
170
|
+
event.event_type,
|
|
171
|
+
list(data.keys()) if data else [],
|
|
172
|
+
)
|
|
173
|
+
match event.event_type:
|
|
174
|
+
case LangchainAgentEventType.TEXT_START:
|
|
175
|
+
self._post_ui(
|
|
176
|
+
AssistantMessageChunk(delta="", is_complete=False)
|
|
177
|
+
)
|
|
178
|
+
case LangchainAgentEventType.TEXT_DELTA:
|
|
179
|
+
delta = str(data.get("text", ""))
|
|
180
|
+
info("[BRIDGE] TEXT_DELTA len=%d first50=%r", len(delta), delta[:50])
|
|
181
|
+
if delta:
|
|
182
|
+
self._seg.streamed_text = True
|
|
183
|
+
self._post_ui(
|
|
184
|
+
AssistantMessageChunk(
|
|
185
|
+
delta=delta,
|
|
186
|
+
is_complete=False,
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
case LangchainAgentEventType.TEXT_END:
|
|
190
|
+
info("[BRIDGE] TEXT_END — finalizing assistant")
|
|
191
|
+
self._post_ui(
|
|
192
|
+
AssistantMessageChunk(delta="", is_complete=True)
|
|
193
|
+
)
|
|
194
|
+
case LangchainAgentEventType.TOOL_INPUT:
|
|
195
|
+
tool_input = data.get("input")
|
|
196
|
+
tool_name = str(data.get("tool_name", ""))
|
|
197
|
+
if not isinstance(tool_input, dict):
|
|
198
|
+
tool_input = data.get("params") if isinstance(data.get("params"), dict) else {}
|
|
199
|
+
info("[BRIDGE] TOOL_INPUT tool_name=%s", tool_name)
|
|
200
|
+
self._post_ui(
|
|
201
|
+
ToolUseStarted(
|
|
202
|
+
tool_name=tool_name,
|
|
203
|
+
tool_input=tool_input,
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
case LangchainAgentEventType.TOOL_CALL:
|
|
207
|
+
tool_name = str(data.get("tool_name", ""))
|
|
208
|
+
info("[BRIDGE] TOOL_CALL tool_name=%s", tool_name)
|
|
209
|
+
pass
|
|
210
|
+
case LangchainAgentEventType.TOOL_RESULT:
|
|
211
|
+
tool_name = str(data.get("tool_name", ""))
|
|
212
|
+
fallback_output = self._build_fallback_text(data)
|
|
213
|
+
info(
|
|
214
|
+
"[BRIDGE] TOOL_RESULT tool_name=%s summary_len=%d payload_type=%s has_display=%s",
|
|
215
|
+
tool_name,
|
|
216
|
+
len(str(data.get("summary", ""))),
|
|
217
|
+
type(data.get("payload")).__name__,
|
|
218
|
+
bool(data.get("display")),
|
|
219
|
+
)
|
|
220
|
+
self._post_ui(
|
|
221
|
+
ToolUseCompleted(
|
|
222
|
+
tool_name=tool_name,
|
|
223
|
+
output=fallback_output,
|
|
224
|
+
summary=str(data.get("summary", "")),
|
|
225
|
+
payload=data.get("payload"),
|
|
226
|
+
status=str(data.get("status", "ok")),
|
|
227
|
+
meta=data.get("meta") if isinstance(data.get("meta"), dict) else None,
|
|
228
|
+
display=data.get("display") if isinstance(data.get("display"), dict) else None,
|
|
229
|
+
truncated=bool(data.get("truncated", False)),
|
|
230
|
+
overflow_file=data.get("overflow_file"),
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
case LangchainAgentEventType.TOOL_ERROR:
|
|
234
|
+
tool_name = str(data.get("tool_name", ""))
|
|
235
|
+
error_text = str(data.get("error", ""))
|
|
236
|
+
self._post_ui(
|
|
237
|
+
ToolUseCompleted(
|
|
238
|
+
tool_name=tool_name,
|
|
239
|
+
output=f"Error: {error_text}",
|
|
240
|
+
error=error_text or None,
|
|
241
|
+
summary=str(data.get("summary", "")),
|
|
242
|
+
payload=data.get("payload"),
|
|
243
|
+
status=str(data.get("status", "error")),
|
|
244
|
+
meta=data.get("meta") if isinstance(data.get("meta"), dict) else None,
|
|
245
|
+
display=data.get("display") if isinstance(data.get("display"), dict) else None,
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
case LangchainAgentEventType.ERROR:
|
|
249
|
+
self._post_ui(
|
|
250
|
+
ErrorMessageOccurred(
|
|
251
|
+
error=str(data.get("error", "Unknown error")),
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
case LangchainAgentEventType.REASONING_START:
|
|
255
|
+
self._post_ui(ThinkingStarted())
|
|
256
|
+
case LangchainAgentEventType.REASONING_DELTA:
|
|
257
|
+
delta = str(data.get("text", ""))
|
|
258
|
+
if delta:
|
|
259
|
+
self._post_ui(ThinkingContentDelta(delta=delta))
|
|
260
|
+
case LangchainAgentEventType.REASONING_END:
|
|
261
|
+
self._post_ui(ThinkingEnded())
|
|
262
|
+
case LangchainAgentEventType.START:
|
|
263
|
+
self._post_ui(TurnInProgress(in_progress=True))
|
|
264
|
+
case LangchainAgentEventType.FINISH:
|
|
265
|
+
self._emit_finish_answer(str(data.get("answer", "")))
|
|
266
|
+
self._post_ui(TurnInProgress(in_progress=False))
|
|
267
|
+
case LangchainAgentEventType.COMPACT_END:
|
|
268
|
+
self._seg.reset()
|
|
269
|
+
compact_type = data.get("compact_type", "autocompact") # 向后兼容
|
|
270
|
+
tokens_freed = data.get("tokens_freed") or 0
|
|
271
|
+
info("[BRIDGE] COMPACT_END type=%s tokens=%s", compact_type, tokens_freed)
|
|
272
|
+
# 只显示 autocompact(LLM 摘要),忽略 microcompact(工具清理)
|
|
273
|
+
if compact_type == "autocompact":
|
|
274
|
+
self._post_ui(ContextCompactedMessage())
|
|
275
|
+
case LangchainAgentEventType.COMPACT_START:
|
|
276
|
+
info("[BRIDGE] COMPACT_START — compacting...")
|
|
277
|
+
pass
|
|
278
|
+
# Phase 3: 子 agent 工具事件(通过 adispatch_custom_event 冒泡)
|
|
279
|
+
case LangchainAgentEventType.SUBAGENT_TOOL_CALL:
|
|
280
|
+
tool_name = str(data.get("tool_name", ""))
|
|
281
|
+
tool_input = data.get("tool_input")
|
|
282
|
+
parent_tool_use_id = data.get("parent_tool_use_id")
|
|
283
|
+
info(
|
|
284
|
+
"[BRIDGE] SUBAGENT_TOOL_CALL tool=%s parent=%s",
|
|
285
|
+
tool_name,
|
|
286
|
+
parent_tool_use_id,
|
|
287
|
+
)
|
|
288
|
+
self._post_ui(
|
|
289
|
+
ToolUseStarted(
|
|
290
|
+
tool_name=tool_name,
|
|
291
|
+
tool_input=tool_input if isinstance(tool_input, dict) else {},
|
|
292
|
+
parent_tool_use_id=parent_tool_use_id,
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
case LangchainAgentEventType.SUBAGENT_TOOL_RESULT:
|
|
296
|
+
tool_name = str(data.get("tool_name", ""))
|
|
297
|
+
parent_tool_use_id = data.get("parent_tool_use_id")
|
|
298
|
+
fallback_output = self._build_fallback_text(data)
|
|
299
|
+
info(
|
|
300
|
+
"[BRIDGE] SUBAGENT_TOOL_RESULT tool=%s parent=%s summary_len=%d",
|
|
301
|
+
tool_name,
|
|
302
|
+
parent_tool_use_id,
|
|
303
|
+
len(str(data.get("summary", ""))),
|
|
304
|
+
)
|
|
305
|
+
self._post_ui(
|
|
306
|
+
ToolUseCompleted(
|
|
307
|
+
tool_name=tool_name,
|
|
308
|
+
output=fallback_output,
|
|
309
|
+
summary=str(data.get("summary", "")),
|
|
310
|
+
payload=data.get("payload"),
|
|
311
|
+
status=str(data.get("status", "ok")),
|
|
312
|
+
meta=data.get("meta") if isinstance(data.get("meta"), dict) else None,
|
|
313
|
+
display=data.get("display") if isinstance(data.get("display"), dict) else None,
|
|
314
|
+
truncated=bool(data.get("truncated", False)),
|
|
315
|
+
overflow_file=data.get("overflow_file"),
|
|
316
|
+
parent_tool_use_id=parent_tool_use_id,
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
case LangchainAgentEventType.HOOK_STARTED:
|
|
320
|
+
hook_event = str(data.get("hook_event", ""))
|
|
321
|
+
hook_name = str(data.get("hook_name", ""))
|
|
322
|
+
info("[BRIDGE] HOOK_STARTED event=%s name=%s", hook_event, hook_name)
|
|
323
|
+
self._post_ui(
|
|
324
|
+
HookStarted(
|
|
325
|
+
hook_event=hook_event,
|
|
326
|
+
hook_name=hook_name,
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
case LangchainAgentEventType.HOOK_FINISHED:
|
|
330
|
+
hook_event = str(data.get("hook_event", ""))
|
|
331
|
+
hook_name = str(data.get("hook_name", ""))
|
|
332
|
+
outcome = str(data.get("outcome", ""))
|
|
333
|
+
info(
|
|
334
|
+
"[BRIDGE] HOOK_FINISHED event=%s name=%s outcome=%s stdout_len=%d stderr_len=%d",
|
|
335
|
+
hook_event,
|
|
336
|
+
hook_name,
|
|
337
|
+
outcome,
|
|
338
|
+
len(str(data.get("combined_stdout", ""))),
|
|
339
|
+
len(str(data.get("combined_stderr", ""))),
|
|
340
|
+
)
|
|
341
|
+
self._post_ui(
|
|
342
|
+
HookFinished(
|
|
343
|
+
hook_event=hook_event,
|
|
344
|
+
hook_name=hook_name,
|
|
345
|
+
outcome=outcome,
|
|
346
|
+
stdout=data.get("combined_stdout"),
|
|
347
|
+
stderr=data.get("combined_stderr"),
|
|
348
|
+
exit_codes=data.get("exit_codes"),
|
|
349
|
+
combined_stdout=data.get("combined_stdout"),
|
|
350
|
+
combined_stderr=data.get("combined_stderr"),
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
case LangchainAgentEventType.TASK_STARTED:
|
|
354
|
+
task_id = str(data.get("task_id", ""))
|
|
355
|
+
task_type = str(data.get("task_type", ""))
|
|
356
|
+
description = str(data.get("description", ""))
|
|
357
|
+
tool_use_id = data.get("tool_use_id")
|
|
358
|
+
info("[BRIDGE] TASK_STARTED id=%s type=%s", task_id, task_type)
|
|
359
|
+
self._post_ui(
|
|
360
|
+
TaskStarted(
|
|
361
|
+
task_id=task_id,
|
|
362
|
+
task_type=task_type,
|
|
363
|
+
description=description,
|
|
364
|
+
tool_use_id=tool_use_id,
|
|
365
|
+
prompt=data.get("prompt"),
|
|
366
|
+
workflow_name=data.get("workflow_name"),
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
case LangchainAgentEventType.TASK_PROGRESS:
|
|
370
|
+
task_id = str(data.get("task_id", ""))
|
|
371
|
+
description = str(data.get("description", ""))
|
|
372
|
+
usage = data.get("usage")
|
|
373
|
+
last_tool_name = data.get("last_tool_name")
|
|
374
|
+
summary = data.get("summary")
|
|
375
|
+
info("[BRIDGE] TASK_PROGRESS id=%s summary=%s", task_id, summary)
|
|
376
|
+
self._post_ui(
|
|
377
|
+
TaskProgress(
|
|
378
|
+
task_id=task_id,
|
|
379
|
+
description=description,
|
|
380
|
+
usage=usage if isinstance(usage, dict) else None,
|
|
381
|
+
last_tool_name=last_tool_name,
|
|
382
|
+
summary=summary,
|
|
383
|
+
tool_use_id=data.get("tool_use_id"),
|
|
384
|
+
)
|
|
385
|
+
)
|
|
386
|
+
case _:
|
|
387
|
+
pass
|
|
388
|
+
|
|
389
|
+
def _emit_finish_answer(self, answer: str) -> None:
|
|
390
|
+
"""非流式路径(如 API 失败合成 AIMessage)在 FINISH.answer 带回文案。"""
|
|
391
|
+
text = answer.strip()
|
|
392
|
+
if not text:
|
|
393
|
+
return
|
|
394
|
+
if text.startswith(_LLM_FAILED_PREFIX):
|
|
395
|
+
self._post_ui(ErrorMessageOccurred(error=text))
|
|
396
|
+
return
|
|
397
|
+
if not self._seg.streamed_text:
|
|
398
|
+
self._post_ui(
|
|
399
|
+
AssistantMessageChunk(delta=text, is_complete=True)
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
def is_streaming(self) -> bool:
|
|
403
|
+
return self._current_worker is not None
|
|
404
|
+
|
|
405
|
+
def is_session_ready(self) -> bool:
|
|
406
|
+
return self._session is not None and getattr(self._session, "_graph", None) is not None
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
def session(self) -> AgentSession:
|
|
410
|
+
return self._session
|
|
411
|
+
|
|
412
|
+
@property
|
|
413
|
+
def permission_queue(self) -> asyncio.Queue[PermissionRequest]:
|
|
414
|
+
return self._permission_queue
|
|
415
|
+
|
|
416
|
+
@property
|
|
417
|
+
def permission_cache(self) -> SessionPermissionCache:
|
|
418
|
+
return self._permission_cache
|
|
419
|
+
|
|
420
|
+
@property
|
|
421
|
+
def confirm_queue(self) -> ConfirmQueue:
|
|
422
|
+
"""对齐 CC: confirmQueue 状态访问。"""
|
|
423
|
+
return self._confirm_queue
|
|
424
|
+
|
|
425
|
+
@property
|
|
426
|
+
def permission_resolver(self) -> AgentSessionPermissionResolver:
|
|
427
|
+
return self._permission_resolver
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def permission_consumer(self) -> PermissionQueueConsumer:
|
|
431
|
+
return self._permission_consumer
|
|
432
|
+
|
|
433
|
+
async def dispatch_command(
|
|
434
|
+
self,
|
|
435
|
+
raw_input: str,
|
|
436
|
+
*,
|
|
437
|
+
output: Callable[[str], None] | None = None,
|
|
438
|
+
) -> str | None:
|
|
439
|
+
if not self.is_session_ready():
|
|
440
|
+
message = "错误:会话未建立,无法执行该命令"
|
|
441
|
+
if output is not None:
|
|
442
|
+
output(message)
|
|
443
|
+
return message
|
|
444
|
+
result = await self._session.dispatch_command(raw_input)
|
|
445
|
+
if result is None:
|
|
446
|
+
return None
|
|
447
|
+
if result.error:
|
|
448
|
+
message = str(result.error)
|
|
449
|
+
if output is not None:
|
|
450
|
+
output(message)
|
|
451
|
+
return message
|
|
452
|
+
if result.output:
|
|
453
|
+
message = str(result.output)
|
|
454
|
+
if output is not None:
|
|
455
|
+
output(message)
|
|
456
|
+
return message
|
|
457
|
+
return None
|
|
458
|
+
|
|
459
|
+
def cancel_stream(self) -> None:
|
|
460
|
+
if self._current_worker is not None:
|
|
461
|
+
cancel = getattr(self._current_worker, "cancel", None)
|
|
462
|
+
if callable(cancel):
|
|
463
|
+
cancel()
|
|
464
|
+
self._current_worker = None
|
|
465
|
+
self._post_ui(TurnInProgress(in_progress=False))
|
|
@@ -50,6 +50,7 @@ class CliEntry:
|
|
|
50
50
|
show_config: bool,
|
|
51
51
|
enable_git_snapshot: bool | None,
|
|
52
52
|
skip_permissions: bool = False,
|
|
53
|
+
debug: bool = False,
|
|
53
54
|
) -> int:
|
|
54
55
|
# Mode 到 agent_home 的映射(优先级高于 --agent-home)
|
|
55
56
|
if mode:
|
|
@@ -88,7 +89,7 @@ class CliEntry:
|
|
|
88
89
|
return 2
|
|
89
90
|
|
|
90
91
|
try:
|
|
91
|
-
return self._run_tui(launch_config, session)
|
|
92
|
+
return self._run_tui(launch_config, session, debug=debug)
|
|
92
93
|
finally:
|
|
93
94
|
asyncio.run(close_agent_session(session))
|
|
94
95
|
|
|
@@ -138,8 +139,10 @@ class CliEntry:
|
|
|
138
139
|
}
|
|
139
140
|
return mode_map.get(mode.lower(), ".langchain_agentx")
|
|
140
141
|
|
|
141
|
-
def _run_tui(
|
|
142
|
-
|
|
142
|
+
def _run_tui(
|
|
143
|
+
self, launch_config: ReplLaunchConfig, session, debug: bool = False # noqa: ANN001
|
|
144
|
+
) -> int:
|
|
145
|
+
app = ReplApp(launch_config=launch_config, session=session, debug=debug)
|
|
143
146
|
app.run()
|
|
144
147
|
return 0
|
|
145
148
|
|
|
@@ -203,6 +206,13 @@ _CONFIG_PATH = user_config_path()
|
|
|
203
206
|
default=False,
|
|
204
207
|
help="跳过工具权限确认(对齐 CC --dangerously-skip-permissions)",
|
|
205
208
|
)
|
|
209
|
+
@click.option(
|
|
210
|
+
"--debug",
|
|
211
|
+
"-d",
|
|
212
|
+
is_flag=True,
|
|
213
|
+
default=False,
|
|
214
|
+
help="启用观察日志(写入 workspace/log/agentx-{pid}.log)",
|
|
215
|
+
)
|
|
206
216
|
def main(
|
|
207
217
|
workspace_root: Path | None,
|
|
208
218
|
mode: str | None,
|
|
@@ -212,6 +222,7 @@ def main(
|
|
|
212
222
|
show_config: bool,
|
|
213
223
|
enable_git_snapshot: bool | None,
|
|
214
224
|
skip_permissions: bool,
|
|
225
|
+
debug: bool,
|
|
215
226
|
) -> None:
|
|
216
227
|
"""langchain-agentx 终端 REPL(MVP)。"""
|
|
217
228
|
code = CliEntry().run(
|
|
@@ -223,5 +234,6 @@ def main(
|
|
|
223
234
|
show_config=show_config,
|
|
224
235
|
enable_git_snapshot=enable_git_snapshot,
|
|
225
236
|
skip_permissions=skip_permissions,
|
|
237
|
+
debug=debug,
|
|
226
238
|
)
|
|
227
239
|
raise SystemExit(code)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
observation — 统一日志观测模块。
|
|
3
|
+
|
|
4
|
+
提供启用/禁用控制的日志接口,默认禁用(零开销)。
|
|
5
|
+
|
|
6
|
+
使用方式:
|
|
7
|
+
from langchain_agentx_cli.observation import observer, info
|
|
8
|
+
|
|
9
|
+
# 模块直接调用
|
|
10
|
+
info("[MODULE] some_event value=%s", value)
|
|
11
|
+
|
|
12
|
+
# 或使用 observer 实例
|
|
13
|
+
observer.info("[MODULE] some_event value=%s", value)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from langchain_agentx_cli.observation.observer import (
|
|
17
|
+
Observer,
|
|
18
|
+
critical,
|
|
19
|
+
debug,
|
|
20
|
+
disable,
|
|
21
|
+
enable,
|
|
22
|
+
error,
|
|
23
|
+
get_observer,
|
|
24
|
+
info,
|
|
25
|
+
is_enabled,
|
|
26
|
+
warning,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# 全局 observer 实例(常用别名)
|
|
30
|
+
observer = get_observer()
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"Observer",
|
|
34
|
+
"observer",
|
|
35
|
+
"get_observer",
|
|
36
|
+
"enable",
|
|
37
|
+
"disable",
|
|
38
|
+
"is_enabled",
|
|
39
|
+
"debug",
|
|
40
|
+
"info",
|
|
41
|
+
"warning",
|
|
42
|
+
"error",
|
|
43
|
+
"critical",
|
|
44
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
observation/config.py — 日志配置常量。
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# 日志文件名模板
|
|
11
|
+
LOG_FILENAME_TEMPLATE = "agentx-{pid}.log"
|
|
12
|
+
|
|
13
|
+
# 默认日志格式
|
|
14
|
+
LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(message)s"
|
|
15
|
+
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
16
|
+
|
|
17
|
+
# 默认日志级别
|
|
18
|
+
DEFAULT_LOG_LEVEL = "INFO"
|
|
19
|
+
|
|
20
|
+
# 日志目录名(相对 workspace root)
|
|
21
|
+
LOG_DIR_NAME = "log"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_log_dir(workspace_root: Path | None) -> Path:
|
|
25
|
+
"""获取日志目录路径。"""
|
|
26
|
+
if workspace_root:
|
|
27
|
+
return workspace_root / LOG_DIR_NAME
|
|
28
|
+
# 回退到临时目录(无 workspace 时)
|
|
29
|
+
return Path(os.getenv("TMPDIR", "/tmp")) / "agentx-logs"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_log_path(workspace_root: Path | None) -> Path:
|
|
33
|
+
"""获取日志文件完整路径。"""
|
|
34
|
+
log_dir = get_log_dir(workspace_root)
|
|
35
|
+
return log_dir / LOG_FILENAME_TEMPLATE.format(pid=os.getpid())
|