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.
Files changed (142) hide show
  1. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/PKG-INFO +2 -2
  2. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/__init__.py +1 -1
  3. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/app.py +30 -0
  4. langchain_agentx_cli-0.3.0/langchain_agentx_cli/bridge/session_bridge.py +465 -0
  5. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/cli.py +15 -3
  6. langchain_agentx_cli-0.3.0/langchain_agentx_cli/observation/__init__.py +44 -0
  7. langchain_agentx_cli-0.3.0/langchain_agentx_cli/observation/config.py +35 -0
  8. langchain_agentx_cli-0.3.0/langchain_agentx_cli/observation/observer.py +173 -0
  9. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/screens/repl.py +0 -3
  10. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/state/task_store.py +63 -1
  11. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/consumer.py +4 -2
  12. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/message_events.py +122 -4
  13. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/message_list.py +353 -51
  14. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/messages/__init__.py +12 -0
  15. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/messages/assistant_message.py +16 -0
  16. langchain_agentx_cli-0.3.0/langchain_agentx_cli/widgets/messages/hook_message.py +72 -0
  17. langchain_agentx_cli-0.3.0/langchain_agentx_cli/widgets/messages/stop_hook_summary.py +70 -0
  18. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/__init__.py +15 -8
  19. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/agent/widget.py +16 -1
  20. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/ask_user_question/widget.py +34 -2
  21. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/base.py +52 -3
  22. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/bash/widget.py +30 -1
  23. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/batch_edit/widget.py +19 -9
  24. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/edit/widget.py +6 -2
  25. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/glob/widget.py +6 -2
  26. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/grep/widget.py +6 -2
  27. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/helpers.py +44 -0
  28. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/read/widget.py +39 -2
  29. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/skill/widget.py +5 -1
  30. langchain_agentx_cli-0.3.0/langchain_agentx_cli/widgets/tools/user_message/widget.py +83 -0
  31. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/webfetch/widget.py +16 -2
  32. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/websearch/widget.py +11 -2
  33. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/write/widget.py +8 -2
  34. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/PKG-INFO +2 -2
  35. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/SOURCES.txt +6 -0
  36. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/requires.txt +1 -1
  37. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/pyproject.toml +2 -2
  38. langchain_agentx_cli-0.3.0/tests/test_llm_config_chain.py +182 -0
  39. langchain_agentx_cli-0.2.1/langchain_agentx_cli/bridge/session_bridge.py +0 -276
  40. langchain_agentx_cli-0.2.1/langchain_agentx_cli/widgets/tools/user_message/widget.py +0 -88
  41. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/LICENSE +0 -0
  42. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/README.md +0 -0
  43. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/__main__.py +0 -0
  44. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/bootstrap.py +0 -0
  45. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/bridge/__init__.py +0 -0
  46. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/__init__.py +0 -0
  47. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/__init__.py +0 -0
  48. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/clear.py +0 -0
  49. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/compact.py +0 -0
  50. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/help.py +0 -0
  51. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/model.py +0 -0
  52. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/quit.py +0 -0
  53. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/builtin/theme.py +0 -0
  54. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/parser.py +0 -0
  55. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/providers/__init__.py +0 -0
  56. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/providers/sdk_commands.py +0 -0
  57. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/commands/registry.py +0 -0
  58. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/completion/__init__.py +0 -0
  59. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/completion/base.py +0 -0
  60. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/completion/command_source.py +0 -0
  61. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/completion/history_source.py +0 -0
  62. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/config.py +0 -0
  63. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/history/__init__.py +0 -0
  64. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/history/store.py +0 -0
  65. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/keybindings/__init__.py +0 -0
  66. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/keybindings/bindings.py +0 -0
  67. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/llm_config.py +0 -0
  68. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/permissions/__init__.py +0 -0
  69. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/permissions/factory.py +0 -0
  70. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/permissions/policy.py +0 -0
  71. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/prompts/__init__.py +0 -0
  72. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/prompts/assembler.py +0 -0
  73. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/prompts/sections.py +0 -0
  74. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/prompts/system_prompt.py +0 -0
  75. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/screens/__init__.py +0 -0
  76. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/session_factory.py +0 -0
  77. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/session_options.py +0 -0
  78. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/state/__init__.py +0 -0
  79. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/__init__.py +0 -0
  80. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/colors.py +0 -0
  81. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/detection.py +0 -0
  82. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/manager.py +0 -0
  83. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/settings.py +0 -0
  84. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/system_theme.py +0 -0
  85. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/themes.py +0 -0
  86. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/theme/watcher.py +0 -0
  87. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tools/__init__.py +0 -0
  88. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tools/registry.py +0 -0
  89. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/__init__.py +0 -0
  90. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/clipboard.py +0 -0
  91. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/message_selection.py +0 -0
  92. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/__init__.py +0 -0
  93. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/cache.py +0 -0
  94. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/component_manager.py +0 -0
  95. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/config.py +0 -0
  96. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/dialog.py +0 -0
  97. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/inject.py +0 -0
  98. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/models.py +0 -0
  99. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/presenters/__init__.py +0 -0
  100. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/presenters/base.py +0 -0
  101. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/presenters/bash.py +0 -0
  102. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/presenters/fallback.py +0 -0
  103. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/presenters/file.py +0 -0
  104. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/permissions/queue.py +0 -0
  105. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/tui/safe_screen.py +0 -0
  106. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/welcome.py +0 -0
  107. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/__init__.py +0 -0
  108. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/compact_progress.py +0 -0
  109. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/completion_overlay.py +0 -0
  110. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/input_area.py +0 -0
  111. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/messages/error_message.py +0 -0
  112. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/messages/rendering.py +0 -0
  113. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/messages/thinking_message.py +0 -0
  114. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/pending_permission.py +0 -0
  115. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/permission_inline.py +0 -0
  116. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/permission_keybindings.py +0 -0
  117. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/spinner.py +0 -0
  118. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/task_panel.py +0 -0
  119. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/agent/__init__.py +0 -0
  120. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/ask_user_question/__init__.py +0 -0
  121. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/bash/__init__.py +0 -0
  122. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/batch_edit/__init__.py +0 -0
  123. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/edit/__init__.py +0 -0
  124. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/glob/__init__.py +0 -0
  125. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/grep/__init__.py +0 -0
  126. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/read/__init__.py +0 -0
  127. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/skill/__init__.py +0 -0
  128. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/user_message/__init__.py +0 -0
  129. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/webfetch/__init__.py +0 -0
  130. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/websearch/__init__.py +0 -0
  131. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli/widgets/tools/write/__init__.py +0 -0
  132. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/dependency_links.txt +0 -0
  133. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/entry_points.txt +0 -0
  134. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/not-zip-safe +0 -0
  135. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/langchain_agentx_cli.egg-info/top_level.txt +0 -0
  136. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/setup.cfg +0 -0
  137. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_app_interrupt.py +0 -0
  138. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_repl_commands.py +0 -0
  139. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_repl_submit_flow.py +0 -0
  140. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_repl_ui.py +0 -0
  141. {langchain_agentx_cli-0.2.1 → langchain_agentx_cli-0.3.0}/tests/test_smoke.py +0 -0
  142. {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.2.1
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.4.0,>=0.3.4
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
@@ -1,3 +1,3 @@
1
1
  """langchain_agentx_cli:依托 langchain_agentx_python SDK 的终端 CLI/TUI(CC Ink 迁移)。"""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
@@ -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(self, launch_config: ReplLaunchConfig, session) -> int: # noqa: ANN001
142
- app = ReplApp(launch_config=launch_config, session=session)
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())