ErisPulse 2.7.1.dev0__tar.gz → 2.7.1.dev2__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 (112) hide show
  1. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/PKG-INFO +1 -1
  2. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/pyproject.toml +1 -1
  3. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/doctor.py +67 -10
  4. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/list.py +3 -0
  5. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/run.py +12 -0
  6. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/en.py +4 -0
  7. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/ja.py +4 -0
  8. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/ru.py +4 -0
  9. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +4 -0
  10. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +4 -0
  11. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/utils/display.py +4 -1
  12. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/base.py +9 -1
  13. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/adapter.py +25 -11
  14. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/config.py +29 -0
  15. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/en.py +38 -2
  16. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/ja.py +38 -2
  17. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/ru.py +38 -2
  18. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/zh_cn.py +38 -2
  19. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/zh_tw.py +38 -2
  20. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/logger.py +181 -30
  21. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/module.py +33 -4
  22. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/router.py +42 -0
  23. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/finders/bases/finder.py +46 -9
  24. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/adapter.py +8 -3
  25. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/bases/loader.py +18 -6
  26. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/module.py +98 -29
  27. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/strict.py +32 -2
  28. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/exceptions.py +58 -0
  29. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/frame_config.py +48 -5
  30. erispulse-2.7.1.dev2/src/ErisPulse/runtime/tasks.py +109 -0
  31. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/sdk.py +43 -11
  32. erispulse-2.7.1.dev0/src/ErisPulse/runtime/tasks.py +0 -63
  33. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/.gitignore +0 -0
  34. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/LICENSE +0 -0
  35. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/README.pypi.md +0 -0
  36. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/__init__.py +0 -0
  37. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/base.py +0 -0
  38. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/cli.py +0 -0
  39. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/__init__.py +0 -0
  40. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/create.py +0 -0
  41. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/init.py +0 -0
  42. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/install.py +0 -0
  43. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/language.py +0 -0
  44. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
  45. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/self_update.py +0 -0
  46. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/types.py +0 -0
  47. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
  48. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/upgrade.py +0 -0
  49. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/console.py +0 -0
  50. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/constants.py +0 -0
  51. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/hints.py +0 -0
  52. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
  53. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
  54. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/registry.py +0 -0
  55. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/utils/__init__.py +0 -0
  56. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/utils/file_watcher.py +0 -0
  57. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/utils/package_manager.py +0 -0
  58. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/utils/scaffold_text.py +0 -0
  59. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/__init__.py +0 -0
  60. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/adapter.py +0 -0
  61. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/client.py +0 -0
  62. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/config_schema.py +0 -0
  63. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/converter.py +0 -0
  64. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/errors.py +0 -0
  65. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/i18n_schema.py +0 -0
  66. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/kv_builder.py +0 -0
  67. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/manager.py +0 -0
  68. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/module.py +0 -0
  69. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/router.py +0 -0
  70. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
  71. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/send_rules.py +0 -0
  72. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/storage.py +0 -0
  73. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/websocket.py +0 -0
  74. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/__init__.py +0 -0
  75. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/command.py +0 -0
  76. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/message.py +0 -0
  77. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/message_builder.py +0 -0
  78. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/meta.py +0 -0
  79. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/notice.py +0 -0
  80. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/request.py +0 -0
  81. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/session_type.py +0 -0
  82. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/wrapper.py +0 -0
  83. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/__init__.py +0 -0
  84. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/assets/__init__.py +0 -0
  85. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/assets/error.css +0 -0
  86. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/assets/error.html +0 -0
  87. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/assets/root.css +0 -0
  88. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/assets/root.html +0 -0
  89. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/client.py +0 -0
  90. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/constants.py +0 -0
  91. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/__init__.py +0 -0
  92. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/constants.py +0 -0
  93. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
  94. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/lifecycle.py +0 -0
  95. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/master.py +0 -0
  96. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/storage.py +0 -0
  97. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/__init__.py +0 -0
  98. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/__main__.py +0 -0
  99. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/finders/__init__.py +0 -0
  100. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/finders/adapter.py +0 -0
  101. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/finders/bases/__init__.py +0 -0
  102. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/finders/module.py +0 -0
  103. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/__init__.py +0 -0
  104. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/bases/__init__.py +0 -0
  105. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/strategy.py +0 -0
  106. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/py.typed +0 -0
  107. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/__init__.py +0 -0
  108. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/config_schema.py +0 -0
  109. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/context.py +0 -0
  110. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/diagnostics.py +0 -0
  111. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/hints.py +0 -0
  112. {erispulse-2.7.1.dev0 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/memory.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ErisPulse
3
- Version: 2.7.1.dev0
3
+ Version: 2.7.1.dev2
4
4
  Summary: Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署
5
5
  Project-URL: Homepage, https://github.com/ErisPulse/ErisPulse
6
6
  Project-URL: Documentation, https://www.erisdev.com#docs
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ErisPulse"
7
- version = "2.7.1-dev.0"
7
+ version = "2.7.1-dev.2"
8
8
  description = "Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署"
9
9
  readme = "README.pypi.md"
10
10
  requires-python = ">=3.10"
@@ -43,13 +43,14 @@ class DoctorCommand(Command):
43
43
 
44
44
  def execute(self, args):
45
45
  pm = self.package_manager
46
- rows: list[tuple[str, str]] = []
46
+ rows: list[tuple[str, str, str]] = []
47
47
  failed = False
48
48
 
49
49
  # 1. Python 解释器
50
50
  py_ok = sys.version_info >= (3, 10)
51
51
  rows.append(
52
52
  (
53
+ self._status(py_ok),
53
54
  i18n.t("cli.doctor.python"),
54
55
  f"{platform.python_version()} ({sys.executable})",
55
56
  )
@@ -60,27 +61,43 @@ class DoctorCommand(Command):
60
61
  # 2. 安装后端(uv / pip)
61
62
  uv_cmd = pm._get_uv_command()
62
63
  if uv_cmd:
63
- rows.append((i18n.t("cli.doctor.backend"), f"uv ({uv_cmd[0]})"))
64
+ rows.append(
65
+ (self._status(True), i18n.t("cli.doctor.backend"), f"uv ({uv_cmd[0]})")
66
+ )
64
67
  else:
65
- rows.append((i18n.t("cli.doctor.backend"), i18n.t("cli.doctor.backend_pip")))
68
+ rows.append(
69
+ (
70
+ self._status(True),
71
+ i18n.t("cli.doctor.backend"),
72
+ i18n.t("cli.doctor.backend_pip"),
73
+ )
74
+ )
66
75
 
67
76
  # 3. 目标解释器(安装目标环境)
68
77
  target_python = pm._get_target_python()
69
- rows.append((i18n.t("cli.doctor.target_python"), target_python))
78
+ rows.append(
79
+ (self._status(True), i18n.t("cli.doctor.target_python"), target_python)
80
+ )
70
81
 
71
82
  # 4. 配置文件
72
83
  config_path = Path("config") / "config.toml"
73
84
  if config_path.exists():
74
85
  rows.append(
75
- (i18n.t("cli.doctor.config"), str(config_path.resolve()))
86
+ (
87
+ self._status(True),
88
+ i18n.t("cli.doctor.config"),
89
+ str(config_path.resolve()),
90
+ )
76
91
  )
77
92
  else:
78
93
  rows.append(
79
94
  (
95
+ self._status(False),
80
96
  i18n.t("cli.doctor.config"),
81
97
  f"{i18n.t('cli.doctor.config_missing')} ({config_path})",
82
98
  )
83
99
  )
100
+ failed = True
84
101
 
85
102
  # 5. PyPI 连通性
86
103
  try:
@@ -90,21 +107,40 @@ class DoctorCommand(Command):
90
107
  )
91
108
  rows.append(
92
109
  (
110
+ self._status(True),
93
111
  i18n.t("cli.doctor.pypi"),
94
112
  i18n.t("cli.doctor.pypi_ok", count=pkg_count),
95
113
  )
96
114
  )
97
115
  except Exception:
98
116
  failed = True
99
- rows.append((i18n.t("cli.doctor.pypi"), i18n.t("cli.doctor.pypi_fail")))
117
+ rows.append(
118
+ (
119
+ self._status(False),
120
+ i18n.t("cli.doctor.pypi"),
121
+ i18n.t("cli.doctor.pypi_fail"),
122
+ )
123
+ )
100
124
 
101
125
  # 6. 代理
102
126
  proxy = pm._get_system_proxy()
103
127
  if proxy:
104
128
  https_proxy = proxy.get("https") or proxy.get("http", "")
105
- rows.append((i18n.t("cli.doctor.proxy"), https_proxy))
129
+ rows.append(
130
+ (
131
+ self._status(True),
132
+ i18n.t("cli.doctor.proxy"),
133
+ https_proxy,
134
+ )
135
+ )
106
136
  else:
107
- rows.append((i18n.t("cli.doctor.proxy"), i18n.t("cli.doctor.proxy_none")))
137
+ rows.append(
138
+ (
139
+ self._status(True),
140
+ i18n.t("cli.doctor.proxy"),
141
+ i18n.t("cli.doctor.proxy_none"),
142
+ )
143
+ )
108
144
 
109
145
  # 输出诊断报告
110
146
  table = Table(
@@ -113,15 +149,36 @@ class DoctorCommand(Command):
113
149
  pad_edge=False,
114
150
  expand=True,
115
151
  )
116
- for label, detail in rows:
117
- table.add_row(f"[info]{label}[/]", detail)
152
+ for status, label, detail in rows:
153
+ table.add_row(status, f"[info]{label}[/]", detail)
118
154
 
119
155
  console.print(table)
120
156
 
121
157
  if failed:
122
158
  console.print(f"[error]{i18n.t('cli.doctor.failed')}[/]")
159
+ for status, label, _ in rows:
160
+ if status != self._status(True):
161
+ console.print(
162
+ f" [error]-[/] [info]{label}[/]"
163
+ )
123
164
  else:
124
165
  console.print(f"[success]{i18n.t('cli.doctor.all_ok')}[/]")
125
166
 
167
+ @staticmethod
168
+ def _status(ok: bool) -> str:
169
+ """
170
+ 生成诊断项的状态标记文本(OK / FAIL,不使用 emoji)
171
+
172
+ :param ok: bool 诊断项是否正常
173
+ :return: str 带样式的状态标记文本
174
+ """
175
+ from ..i18n import i18n
176
+
177
+ return (
178
+ f"[success]{i18n.t('cli.doctor.status_ok')}[/]"
179
+ if ok
180
+ else f"[error]{i18n.t('cli.doctor.status_fail')}[/]"
181
+ )
182
+
126
183
 
127
184
  __all__ = ["DoctorCommand"]
@@ -121,6 +121,7 @@ class ListCommand(Command):
121
121
  self._print_package_scripts(installed["modules"])
122
122
  else:
123
123
  console.print(f"[dim] {i18n.t('cli.list.no_modules')}[/]")
124
+ console.print(f"[dim] {i18n.t('cli.list.empty_hint')}[/]")
124
125
 
125
126
  elif pkg_type == "adapters" and installed["adapters"]:
126
127
  table = Table(
@@ -154,11 +155,13 @@ class ListCommand(Command):
154
155
  )
155
156
  else:
156
157
  console.print(f"[dim] {i18n.t('cli.list.no_adapters')}[/]")
158
+ console.print(f"[dim] {i18n.t('cli.list.empty_hint')}[/]")
157
159
 
158
160
  elif not installed.get(pkg_type, {}):
159
161
  console.print(
160
162
  f"[dim] {i18n.t('cli.list.no_packages', pkg_type=pkg_type)}[/]"
161
163
  )
164
+ console.print(f"[dim] {i18n.t('cli.list.empty_hint')}[/]")
162
165
 
163
166
  def _is_package_outdated(
164
167
  self,
@@ -183,6 +183,7 @@ class RunCommand(Command):
183
183
  ]
184
184
 
185
185
  crash_count = 0
186
+ process = None
186
187
  try:
187
188
  while True:
188
189
  process = subprocess.Popen(cmd)
@@ -208,6 +209,17 @@ class RunCommand(Command):
208
209
  # 继续循环,重新启动子进程
209
210
  except KeyboardInterrupt:
210
211
  pass
212
+ finally:
213
+ # 运行器退出(Ctrl+C 等)前必须终止子进程,否则孤儿进程会继续持有
214
+ # 端口等资源,导致下次启动时端口被占用(如监听 8000 的残留进程)。
215
+ if process is not None and process.poll() is None:
216
+ console.print(f"[info]{i18n.t('cli.run.terminating_child')}[/]")
217
+ process.terminate()
218
+ try:
219
+ process.wait(timeout=5)
220
+ except subprocess.TimeoutExpired:
221
+ process.kill()
222
+ process.wait()
211
223
 
212
224
  def _run_script(self, script_path: str, reload_mode: bool):
213
225
  """
@@ -205,6 +205,7 @@ TRANSLATIONS = {
205
205
  "cli.list.count_adapters": "{count} adapter(s)",
206
206
  "cli.list.no_adapters": "No matching adapters",
207
207
  "cli.list.no_packages": "No {pkg_type}",
208
+ "cli.list.empty_hint": "Run 'epsdk install' to browse and install components, or 'epsdk init' to create a project",
208
209
  # ==================== list-remote command ====================
209
210
  "cli.list_remote.description": "List remote available components",
210
211
  "cli.list_remote.type_help": "List type (default: all)",
@@ -276,6 +277,7 @@ TRANSLATIONS = {
276
277
  "cli.run.process_exited": "Process exited normally, waiting for file changes to restart",
277
278
  "cli.run.process_crashed": "Process crashed (exit code {code}), fix and save to restart",
278
279
  "cli.run.subprocess_crashed_retry": "Subprocess crashed, will auto-retry in {seconds}s...",
280
+ "cli.run.terminating_child": "Terminating the bot subprocess to release ports and resources...",
279
281
  # ==================== uninstall command ====================
280
282
  "cli.uninstall.description": "Uninstall module/adapter packages",
281
283
  "cli.uninstall.package_help": "Package name(s) to uninstall (multiple allowed)",
@@ -416,6 +418,8 @@ TRANSLATIONS = {
416
418
  "cli.doctor.proxy_none": "none detected",
417
419
  "cli.doctor.failed": "[done] Issues found, see output above",
418
420
  "cli.doctor.all_ok": "[done] Environment is healthy",
421
+ "cli.doctor.status_ok": "OK",
422
+ "cli.doctor.status_fail": "FAIL",
419
423
  # ==================== types command ====================
420
424
  "cli.types.description": "Generate type stub file to enable IDE completion",
421
425
  "cli.types.output_help": "Output file path (default: ./_ep_types.py)",
@@ -205,6 +205,7 @@ TRANSLATIONS = {
205
205
  "cli.list.count_adapters": "{count} 個のアダプター",
206
206
  "cli.list.no_adapters": "該当するアダプターはありません",
207
207
  "cli.list.no_packages": "{pkg_type} はありません",
208
+ "cli.list.empty_hint": "'epsdk install' でコンポーネントを閲覧・インストールするか、'epsdk init' でプロジェクトを作成できます",
208
209
  # ==================== list-remote コマンド ====================
209
210
  "cli.list_remote.description": "リモートで利用可能なコンポーネントを一覧表示",
210
211
  "cli.list_remote.type_help": "一覧タイプ (デフォルト: all)",
@@ -276,6 +277,7 @@ TRANSLATIONS = {
276
277
  "cli.run.process_exited": "プロセスが正常に終了しました。ファイルの変更を待機して再起動",
277
278
  "cli.run.process_crashed": "プロセスが異常終了しました(終了コード {code})。修正して保存すると自動再起動",
278
279
  "cli.run.subprocess_crashed_retry": "サブプロセスが異常終了しました。{seconds} 秒後に自動再試行します...",
280
+ "cli.run.terminating_child": "ポートなどのリソースを解放するため、ボットの子プロセスを終了しています...",
279
281
  # ==================== uninstall コマンド ====================
280
282
  "cli.uninstall.description": "モジュール/アダプターパッケージをアンインストール",
281
283
  "cli.uninstall.package_help": "アンインストールするパッケージ名(複数指定可)",
@@ -416,6 +418,8 @@ TRANSLATIONS = {
416
418
  "cli.doctor.proxy_none": "検出されません",
417
419
  "cli.doctor.failed": "[完了] 対応が必要な問題があります。上記を確認してください",
418
420
  "cli.doctor.all_ok": "[完了] 環境は正常です",
421
+ "cli.doctor.status_ok": "OK",
422
+ "cli.doctor.status_fail": "NG",
419
423
  # ==================== types コマンド ====================
420
424
  "cli.types.description": "型スタブファイルを生成して IDE 補完を有効化",
421
425
  "cli.types.output_help": "出力ファイルパス(デフォルト: ./_ep_types.py)",
@@ -205,6 +205,7 @@ TRANSLATIONS = {
205
205
  "cli.list.count_adapters": "{count} адаптера(ов)",
206
206
  "cli.list.no_adapters": "Нет подходящих адаптеров",
207
207
  "cli.list.no_packages": "Нет {pkg_type}",
208
+ "cli.list.empty_hint": "Выполните 'epsdk install' для просмотра и установки компонентов, или 'epsdk init' для создания проекта",
208
209
  # ==================== команда list-remote ====================
209
210
  "cli.list_remote.description": "Список удаленно доступных компонентов",
210
211
  "cli.list_remote.type_help": "Тип списка (по умолчанию: all)",
@@ -276,6 +277,7 @@ TRANSLATIONS = {
276
277
  "cli.run.process_exited": "Процесс завершен нормально, ожидание изменений файлов для перезапуска",
277
278
  "cli.run.process_crashed": "Процесс аварийно завершен (код {code}), исправьте и сохраните для перезапуска",
278
279
  "cli.run.subprocess_crashed_retry": "Подпроцесс аварийно завершен, автоматическая перезагрузка через {seconds}с...",
280
+ "cli.run.terminating_child": "Завершение дочернего процесса бота для освобождения портов и ресурсов...",
279
281
  # ==================== команда uninstall ====================
280
282
  "cli.uninstall.description": "Удалить пакеты модулей/адаптеров",
281
283
  "cli.uninstall.package_help": "Имя(ена) пакета(ов) для удаления (можно указать несколько)",
@@ -416,6 +418,8 @@ TRANSLATIONS = {
416
418
  "cli.doctor.proxy_none": "не обнаружен",
417
419
  "cli.doctor.failed": "[готово] Есть проблемы, см. вывод выше",
418
420
  "cli.doctor.all_ok": "[готово] Окружение в порядке",
421
+ "cli.doctor.status_ok": "OK",
422
+ "cli.doctor.status_fail": "FAIL",
419
423
  # ==================== команда types ====================
420
424
  "cli.types.description": "Сгенерировать файл заглушек типов для автодополнения IDE",
421
425
  "cli.types.output_help": "Путь вывода (по умолчанию: ./_ep_types.py)",
@@ -205,6 +205,7 @@ TRANSLATIONS = {
205
205
  "cli.list.count_adapters": "{count} 个适配器",
206
206
  "cli.list.no_adapters": "没有符合条件的适配器",
207
207
  "cli.list.no_packages": "没有{pkg_type}",
208
+ "cli.list.empty_hint": "可运行 'epsdk install' 浏览并安装组件,或运行 'epsdk init' 创建项目",
208
209
  # ==================== list-remote 命令 ====================
209
210
  "cli.list_remote.description": "列出远程可用的组件",
210
211
  "cli.list_remote.type_help": "列出类型 (默认: all)",
@@ -276,6 +277,7 @@ TRANSLATIONS = {
276
277
  "cli.run.process_exited": "进程已正常退出,等待文件变更后重启",
277
278
  "cli.run.process_crashed": "进程异常退出(退出码 {code}),修复后保存文件将自动重启",
278
279
  "cli.run.subprocess_crashed_retry": "子进程异常终止,将在 {seconds} 秒后自动重试...",
280
+ "cli.run.terminating_child": "正在终止机器人子进程,释放端口等资源...",
279
281
  # ==================== uninstall 命令 ====================
280
282
  "cli.uninstall.description": "卸载模块/适配器包",
281
283
  "cli.uninstall.package_help": "要卸载的包名(可指定多个)",
@@ -416,6 +418,8 @@ TRANSLATIONS = {
416
418
  "cli.doctor.proxy_none": "未检测到",
417
419
  "cli.doctor.failed": "[诊断完成] 存在需要处理的问题,请参考上方输出",
418
420
  "cli.doctor.all_ok": "[诊断完成] 环境正常",
421
+ "cli.doctor.status_ok": "正常",
422
+ "cli.doctor.status_fail": "异常",
419
423
  # ==================== types 命令 ====================
420
424
  "cli.types.description": "生成类型存根文件,启用 IDE 补全",
421
425
  "cli.types.output_help": "输出文件路径(默认: ./_ep_types.py)",
@@ -205,6 +205,7 @@ TRANSLATIONS = {
205
205
  "cli.list.count_adapters": "{count} 個適配器",
206
206
  "cli.list.no_adapters": "沒有符合條件的適配器",
207
207
  "cli.list.no_packages": "沒有{pkg_type}",
208
+ "cli.list.empty_hint": "可執行 'epsdk install' 瀏覽並安裝元件,或執行 'epsdk init' 建立專案",
208
209
  # ==================== list-remote 命令 ====================
209
210
  "cli.list_remote.description": "列出遠端可用的元件",
210
211
  "cli.list_remote.type_help": "列出型別 (預設: all)",
@@ -276,6 +277,7 @@ TRANSLATIONS = {
276
277
  "cli.run.process_exited": "程式已正常退出,等待檔案變更後重啟",
277
278
  "cli.run.process_crashed": "程式異常退出(退出碼 {code}),修復後儲存檔案將自動重啟",
278
279
  "cli.run.subprocess_crashed_retry": "子程序異常終止,將在 {seconds} 秒後自動重試...",
280
+ "cli.run.terminating_child": "正在終止機器人子程序,釋放連接埠等資源...",
279
281
  # ==================== uninstall 命令 ====================
280
282
  "cli.uninstall.description": "解除安裝模組/適配器包",
281
283
  "cli.uninstall.package_help": "要解除安裝的包名(可指定多個)",
@@ -416,6 +418,8 @@ TRANSLATIONS = {
416
418
  "cli.doctor.proxy_none": "未偵測到",
417
419
  "cli.doctor.failed": "[診斷完成] 存在需要處理的問題,請參考上方輸出",
418
420
  "cli.doctor.all_ok": "[診斷完成] 環境正常",
421
+ "cli.doctor.status_ok": "正常",
422
+ "cli.doctor.status_fail": "異常",
419
423
  # ==================== types 命令 ====================
420
424
  "cli.types.description": "生成類型存根檔案,啟用 IDE 補全",
421
425
  "cli.types.output_help": "輸出檔案路徑(預設: ./_ep_types.py)",
@@ -9,6 +9,7 @@ from collections.abc import Callable
9
9
  from typing import Any
10
10
 
11
11
  from rich.box import SIMPLE
12
+ from rich.markup import escape
12
13
  from rich.prompt import Prompt
13
14
  from rich.table import Table
14
15
  from rich.text import Text
@@ -45,7 +46,9 @@ def _input(prompt_label: str = ">") -> str:
45
46
  :param prompt_label: [str] 提示标签 (默认: ">")
46
47
  :return: [str] 用户输入内容(已去除首尾空白)
47
48
  """
48
- console.print(f" [dim]{prompt_label}[/] ", end="")
49
+ # 转义富文本标记
50
+ escaped = escape(prompt_label)
51
+ console.print(f" [dim]{escaped}[/] ", end="")
49
52
  try:
50
53
  return input().strip()
51
54
  except (EOFError, KeyboardInterrupt):
@@ -59,7 +59,15 @@ async def _invoke_handler(handler_info: dict, event: Event) -> None:
59
59
  else:
60
60
  handler(event)
61
61
  except Exception as e:
62
- logger.error(i18n.t("core.event.handler_error", error=e))
62
+ logger.error(
63
+ i18n.t(
64
+ "core.event.handler_error",
65
+ handler=_hname,
66
+ type=event.get("type", "") if isinstance(event, dict) else "",
67
+ owner=_owner or "<unknown>",
68
+ error=e,
69
+ )
70
+ )
63
71
  return
64
72
  finally:
65
73
  _elapsed = _time.monotonic() - _t
@@ -1052,7 +1052,9 @@ class AdapterManager(ManagerBase):
1052
1052
  logger.error(i18n.t("core.adapter.platform_not_exist", platform=platform))
1053
1053
  return False
1054
1054
 
1055
- config.setConfig(CONFIG_KEY_ADAPTER_STATUS_OF.format(platform), True)
1055
+ config.setConfig(
1056
+ CONFIG_KEY_ADAPTER_STATUS_OF.format(platform), True, immediate=True
1057
+ )
1056
1058
  logger.info(i18n.t("core.adapter.platform_enabled", platform=platform))
1057
1059
  return True
1058
1060
 
@@ -1075,7 +1077,9 @@ class AdapterManager(ManagerBase):
1075
1077
  logger.error(i18n.t("core.adapter.platform_not_exist", platform=platform))
1076
1078
  return False
1077
1079
 
1078
- config.setConfig(CONFIG_KEY_ADAPTER_STATUS_OF.format(platform), False)
1080
+ config.setConfig(
1081
+ CONFIG_KEY_ADAPTER_STATUS_OF.format(platform), False, immediate=True
1082
+ )
1079
1083
  logger.info(i18n.t("core.adapter.platform_disabled", platform=platform))
1080
1084
  return True
1081
1085
 
@@ -1514,18 +1518,28 @@ class AdapterManager(ManagerBase):
1514
1518
  # 调用过 wait_reply:纯等待属于交互白名单
1515
1519
  if _pure > HANDLER_SLOW_THRESHOLD_SECS:
1516
1520
  logger.warning(
1517
- f"事件处理器执行缓慢 [{_func_name}] "
1518
- f"耗时 {elapsed:.2f}s "
1519
- f"(wait_reply={_wait_total:.2f}s, pure={_pure:.2f}s)"
1520
- f" > {HANDLER_SLOW_THRESHOLD_SECS}s "
1521
- f"type={event_type} platform={platform}{_owner_tag}"
1521
+ i18n.t(
1522
+ "core.adapter.handler_slow",
1523
+ handler=_func_name,
1524
+ elapsed=f"{elapsed:.2f}",
1525
+ threshold=HANDLER_SLOW_THRESHOLD_SECS,
1526
+ type=event_type,
1527
+ platform=platform,
1528
+ tag=_owner_tag,
1529
+ )
1522
1530
  )
1523
1531
  else:
1524
1532
  logger.trace(
1525
- f"事件处理器 [{_func_name}] 耗时 {elapsed:.2f}s "
1526
- f"(wait_reply={_wait_total:.2f}s, pure={_pure:.2f}s) "
1527
- f"interactive-wait, suppressed slow-warning "
1528
- f"type={event_type} platform={platform}{_owner_tag}"
1533
+ i18n.t(
1534
+ "core.adapter.handler_slow_trace",
1535
+ handler=_func_name,
1536
+ elapsed=f"{elapsed:.2f}",
1537
+ wait=f"{_wait_total:.2f}",
1538
+ pure=f"{_pure:.2f}",
1539
+ type=event_type,
1540
+ platform=platform,
1541
+ tag=_owner_tag,
1542
+ )
1529
1543
  )
1530
1544
  elif elapsed > HANDLER_SLOW_THRESHOLD_SECS:
1531
1545
  logger.warning(
@@ -248,6 +248,9 @@ class ConfigManager:
248
248
  else:
249
249
  self._cache = config
250
250
  self._cache_timestamp = time.time()
251
+ # 外部修改重载后,丢弃与新文件一致的待写键,
252
+ # 避免陈旧的整棵/叶子脏键在下次 flush 时覆盖用户热更新
253
+ self._drop_redundant_dirty_keys()
251
254
  if not self._cache:
252
255
  self._log_config_error(
253
256
  i18n.t("core.config.loaded_empty", path=self.CONFIG_FILE),
@@ -255,6 +258,32 @@ class ConfigManager:
255
258
  )
256
259
  return True
257
260
 
261
+ def _drop_redundant_dirty_keys(self) -> None:
262
+ """
263
+ 丢弃与新配置文件内容一致的待写键
264
+
265
+ 配置重载(外部修改)后,部分待写键的值可能已与文件一致,
266
+ 继续保留会在下次 flush 时用陈旧快照覆盖用户热更新。
267
+ 仅当待写值已反映在缓存(即文件)中时才丢弃;真正未落盘的写入仍保留。
268
+
269
+ {!--< internal-use >!--}
270
+ {!--< /internal-use >!--}
271
+ """
272
+ if not self._dirty_keys:
273
+ return
274
+ for key in list(self._dirty_keys):
275
+ value = self._dirty_keys[key]
276
+ current = self._cache
277
+ found = True
278
+ for k in key.split("."):
279
+ if isinstance(current, dict) and k in current:
280
+ current = current[k]
281
+ else:
282
+ found = False
283
+ break
284
+ if found and current == value:
285
+ del self._dirty_keys[key]
286
+
258
287
  @staticmethod
259
288
  def _log_config_error(message: str, level: str = "error") -> None:
260
289
  """
@@ -33,6 +33,8 @@ TRANSLATIONS = {
33
33
  "core.sdk.init.disabled_modules": "Disabled modules: {names}",
34
34
  "core.sdk.init.strict_rejected": "Rejected by strict mode ({count}):",
35
35
  "core.sdk.init.strict_rejected_reason": "({reason})",
36
+ "core.sdk.init.strict_tolerated": "Lenient mode: {count} component(s) have compliance issues but were loaded:",
37
+ "core.sdk.init.strict_action_hint": "To load skipped components, set ErisPulse.framework.strict_mode = 0, or add exemptions under ErisPulse.framework.strict_mode_exceptions.",
36
38
  "core.sdk.init.success": "SDK initialized successfully (duration: {duration})",
37
39
  "core.sdk.init.module_init_complete": "Module initialization complete",
38
40
  "core.sdk.init.module_init_partial_failed": "Module initialization partially failed",
@@ -164,6 +166,7 @@ TRANSLATIONS = {
164
166
  "core.adapter.bot_online": "Bot {platform}/{bot_id} online",
165
167
  "core.adapter.handler_error": "Event handler execution error [{handler}] type={type} platform={platform}: {error}",
166
168
  "core.adapter.handler_slow": "Event handler execution slow [{handler}] elapsed {elapsed}s > {threshold}s type={type} platform={platform}{tag}",
169
+ "core.adapter.handler_slow_trace": "Event handler [{handler}] took {elapsed}s (wait_reply={wait}s, pure={pure}s) interactive-wait, suppressed slow-warning type={type} platform={platform}{tag}",
167
170
  "core.adapter.handler_drain_timeout": "{count} pending event handler(s) did not exit within {timeout}s, force cancelled",
168
171
  "core.adapter.lifecycle_hooks_cleaned": "Adapter {platform} cleaned {count} lifecycle hook(s)",
169
172
  "core.adapter.auto_discover_bot": "Auto-discovered Bot: {platform}/{bot_id}",
@@ -253,6 +256,7 @@ TRANSLATIONS = {
253
256
  "core.router.started": "Router server started",
254
257
  "core.router.start_failed_msg": "Router server startup failed",
255
258
  "core.router.start_failed": "Failed to start server: {error}",
259
+ "core.router.port_in_use": "Port {host}:{port} is already in use; the router server could not start. Check whether another ErisPulse instance or another program is using this port (run `netstat -ano` to find the process holding it)",
256
260
  "core.router.stopping": "Stopping router server...",
257
261
  "core.router.stopped_normal": "Router server stopped normally",
258
262
  "core.router.stopped_cancelled": "Router server was cancelled",
@@ -303,7 +307,7 @@ TRANSLATIONS = {
303
307
  "core.config.toml_malformed": "Configuration file {path} has a syntax error (line {line}, column {col}): {reason}",
304
308
  "core.config.flush_malformed": "Configuration file {path} is corrupted (syntax error, line {line}, column {col}: {reason}); cannot merge-write. Please fix the config file and restart. Pending changes have been preserved.",
305
309
  "core.config.permission_denied": "Permission denied when reading configuration file {path} (file may lack read access)",
306
- "core.config.using_defaults_warning": "Fell back to default configuration your custom settings were NOT applied. Please fix the issue and restart.",
310
+ "core.config.using_defaults_warning": "Failed to load the configuration file. Continuing with the last valid configuration; your recent edits were NOT applied. Fix the issue, then reload or restart.",
307
311
  "core.config.loaded_empty": "Configuration file {path} was loaded but is empty",
308
312
  "core.config.watcher_error": "Config watcher task error: {error}",
309
313
  "core.config.restart_required": "Configuration '{key}' changed; a process restart is required to take effect",
@@ -327,6 +331,7 @@ TRANSLATIONS = {
327
331
  # ==================== Loader ====================
328
332
  "loader.adapter.discovered": "Discovered {count} adapters",
329
333
  "loader.adapter.none": "No adapters found",
334
+ "loader.adapter.discovery_failed": "Adapter discovery failed, environment/dependency issue may be the cause: {error}",
330
335
  "loader.adapter.load_failed": "Failed to load {group} entry-points: {error}",
331
336
  "loader.adapter.systemexit": "SystemExit({code}) triggered while loading {group}, process exit blocked. Do not use sys.exit() or raise SystemExit",
332
337
  "loader.adapter.load_single_failed": "Failed to load adapter {name}, skipped: {error}",
@@ -340,6 +345,7 @@ TRANSLATIONS = {
340
345
  "loader.adapter.register_skipped": "Adapter {name} registration failed, skipped",
341
346
  "loader.module.discovered": "Discovered {count} modules",
342
347
  "loader.module.none": "No modules found",
348
+ "loader.module.discovery_failed": "Module discovery failed, environment/dependency issue may be the cause: {error}",
343
349
  "loader.module.load_failed": "Failed to load {group} entry-points: {error}",
344
350
  "loader.module.load_single_failed": "Failed to load module {name} from entry-point, skipped: {error}",
345
351
  "loader.module.diag_hint": " → Hint: raise the log level to DEBUG for the full traceback; check the implementation of module {name}",
@@ -359,8 +365,30 @@ TRANSLATIONS = {
359
365
  "loader.module.mount_lazy": "Mounting lazy-load module to sdk: {name}",
360
366
  "loader.module.mount_eager": "Mounting eager-load module to sdk: {name}",
361
367
  "loader.module.init_lazy_start": "Initializing lazy-load module {name}...",
368
+ "loader.module.systemexit_group": "SystemExit({code}) triggered while loading {group}, process exit blocked. Do not use sys.exit() or raise SystemExit",
369
+ "loader.module.systemexit_single": "Module {name} attempted to exit process (SystemExit({code})), skipped. Do not use sys.exit() or raise SystemExit",
370
+ "loader.module.strategy_failed": "Failed to call {method}() on module {name}: {error}",
371
+ "loader.module.lazy_init_failed": "Lazy-load module {name} initialization failed: {error}",
372
+ "loader.module.sync_init_failed": "Lazy-load module {name} synchronous initialization failed: {error}",
373
+ "loader.module.background_init_failed": "Lazy-load module {name} background initialization failed: {error}",
374
+ "loader.module.async_init_hint": "Module {name} requires async initialization, please call it in an async context",
375
+ "loader.module.lazy_init_done": "Lazy-load module {name} initialization complete",
376
+ "loader.module.sync_init_start": "Synchronously initializing lazy-load module {name}...",
377
+ "loader.module.sync_init_done": "Lazy-load module {name} synchronous initialization complete",
378
+ "loader.module.needs_async_load": "Module {name} requires async initialization, use 'await sdk.load_module(\"{name}\")' to initialize it",
379
+ "loader.module.init_failed_attr_get": "Module {name} initialization failed, cannot access attribute '{attr}'",
380
+ "loader.module.init_failed_attr_set": "Module {name} initialization failed, cannot set attribute '{attr}'",
381
+ # ==================== Base Loader ====================
382
+ "loader.base.load_start": "Loading {group} entry-points...",
383
+ "loader.base.load_done": "{group} loaded",
384
+ "loader.base.load_failed": "Failed to load {group} entry-points: {error}",
385
+ "loader.base.import_failed": "Failed to import {group}: {error}",
386
+ "loader.base.registered": "{prefix} {name} registered and {status}",
387
+ "loader.base.status_enabled": "enabled",
388
+ "loader.base.status_disabled": "disabled",
362
389
  # ==================== Strict Mode ====================
363
390
  "loader.strict.config_failed": "Failed to read strict_mode config, using default: {error}",
391
+ "loader.strict.invalid_level": "Invalid value '{value}' for ErisPulse.framework.strict_mode. Valid values are 0 (lenient), 1 (skip), 2 (fatal). Using default level ({level}).",
364
392
  "loader.strict.exempted": "{type} '{name}' is exempted from strict mode ({reason}), continuing to load",
365
393
  "loader.strict.tolerated": "Lenient mode: {type} '{name}' {reason}, attempting to load",
366
394
  "loader.strict.rejected": "Strict mode: skipped {type} '{name}' ({reason}). To load it anyway, add exemption in ErisPulse.framework.strict_mode_exceptions.{list_key}, or set ErisPulse.framework.strict_mode = 0",
@@ -368,6 +396,13 @@ TRANSLATIONS = {
368
396
  "loader.strict.fatal_report_header": "Strict mode (fatal) detected {count} violation(s), aborting startup:",
369
397
  "loader.strict.fatal_report_item": " [{index}] {type} '{name}': {reason} {detail}",
370
398
  "loader.strict.fatal_abort": "Strict mode (fatal): {count} violation(s) collected, startup aborted",
399
+ # ==================== Finder ====================
400
+ "finder.remote_load_unavailable": "_RemoteEntryPoint.load() is unavailable: cross-environment queries cannot load target-environment entry-point objects in the current process. Resolve via .value or call within the target environment.",
401
+ "finder.query_started": "Looking up {group} entry-points...",
402
+ "finder.found_count": "Found {count} {group} entry-points",
403
+ "finder.entry_points_query_failed": "Failed to find {group} entry-points: {error}",
404
+ "finder.remote_query_failed": "Failed to query {group} entry-points in target environment {python}: {stderr}",
405
+ "finder.remote_query_exception": "Failed to query entry-points in target environment {python}: {error}",
371
406
  # ==================== Storage Router Display ====================
372
407
  "core.router.lan_ipv4": "LAN IPv4",
373
408
  "core.router.lan_ipv6": "LAN IPv6",
@@ -377,13 +412,14 @@ TRANSLATIONS = {
377
412
  "core.logger.saved": "Logs have been saved to: {path}.",
378
413
  "core.logger.save_failed": "Failed to save logs to {path}: {error}.",
379
414
  "core.logger.invalid_level": "Invalid log level: {level}",
415
+ "core.logger.invalid_format": "Invalid log format: {fmt}. Valid values are rich, plain, json.",
380
416
  "core.logger.module_level_set": "Module {module} log level set to {level}",
381
417
  "core.logger.set_output_failed": "Failed to set log file {path}: {error}",
382
418
  "core.logger.no_output_set": "Failed to set any log file.",
383
419
  "core.logger.memory_limit_invalid": "Log memory limit must be greater than 0.",
384
420
  "core.logger.attr.did_you_mean": "Logger module name '{input}' is very similar to registered '{suggestion}', possibly a typo",
385
421
  # ==================== Event System ====================
386
- "core.event.handler_error": "Event handler execution error: {error}",
422
+ "core.event.handler_error": "Event handler execution error [{handler}] type={type} owner={owner}: {error}",
387
423
  "core.event.slow_handler_wait": "[EventHandler] Slow handler {handler} took {elapsed}s (wait_reply={wait}s, pure={pure}s, waits=[{waits}]){owner}",
388
424
  "core.event.trace_handler_wait": "[EventHandler] {handler} took {elapsed}s (wait_reply={wait}s, pure={pure}s) interactive-wait, suppressed slow-warning{owner}",
389
425
  "core.event.slow_handler": "[EventHandler] Slow handler {handler} took {elapsed}s{owner}",