@researai/deepscientist 1.5.2 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/bin/ds.js +399 -175
- package/docs/en/00_QUICK_START.md +22 -0
- package/docs/en/01_SETTINGS_REFERENCE.md +13 -4
- package/docs/en/99_ACKNOWLEDGEMENTS.md +1 -0
- package/docs/images/connectors/discord-setup-overview.svg +52 -0
- package/docs/images/connectors/feishu-setup-overview.svg +53 -0
- package/docs/images/connectors/slack-setup-overview.svg +51 -0
- package/docs/images/connectors/telegram-setup-overview.svg +55 -0
- package/docs/images/connectors/whatsapp-setup-overview.svg +51 -0
- package/docs/images/lingzhu/lingzhu-openclaw-config.svg +17 -0
- package/docs/images/lingzhu/lingzhu-platform-values.svg +16 -0
- package/docs/images/lingzhu/lingzhu-settings-overview.svg +30 -0
- package/docs/images/qq/tencent-cloud-qq-chat.png +0 -0
- package/docs/images/qq/tencent-cloud-qq-register.png +0 -0
- package/docs/images/quickstart/00-home.png +0 -0
- package/docs/images/quickstart/01-start-research.png +0 -0
- package/docs/images/quickstart/02-list-quest.png +0 -0
- package/docs/zh/00_QUICK_START.md +22 -0
- package/docs/zh/01_SETTINGS_REFERENCE.md +14 -5
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +1 -0
- package/install.sh +120 -4
- package/package.json +8 -4
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/artifact/service.py +1 -1
- package/src/deepscientist/bash_exec/monitor.py +23 -4
- package/src/deepscientist/bash_exec/runtime.py +3 -0
- package/src/deepscientist/bash_exec/service.py +132 -4
- package/src/deepscientist/bridges/base.py +12 -20
- package/src/deepscientist/bridges/connectors.py +2 -1
- package/src/deepscientist/channels/discord_gateway.py +27 -4
- package/src/deepscientist/channels/feishu_long_connection.py +41 -3
- package/src/deepscientist/channels/qq.py +524 -64
- package/src/deepscientist/channels/qq_gateway.py +24 -5
- package/src/deepscientist/channels/relay.py +429 -90
- package/src/deepscientist/channels/slack_socket.py +31 -7
- package/src/deepscientist/channels/telegram_polling.py +27 -3
- package/src/deepscientist/channels/whatsapp_local_session.py +32 -4
- package/src/deepscientist/cli.py +31 -1
- package/src/deepscientist/config/models.py +13 -43
- package/src/deepscientist/config/service.py +216 -157
- package/src/deepscientist/connector_profiles.py +346 -0
- package/src/deepscientist/connector_runtime.py +88 -43
- package/src/deepscientist/daemon/api/handlers.py +53 -16
- package/src/deepscientist/daemon/api/router.py +2 -2
- package/src/deepscientist/daemon/app.py +747 -228
- package/src/deepscientist/mcp/server.py +60 -7
- package/src/deepscientist/migration.py +114 -0
- package/src/deepscientist/network.py +78 -0
- package/src/deepscientist/prompts/builder.py +50 -4
- package/src/deepscientist/qq_profiles.py +186 -0
- package/src/deepscientist/quest/service.py +1 -1
- package/src/deepscientist/skills/installer.py +77 -1
- package/src/prompts/connectors/qq.md +42 -2
- package/src/prompts/system.md +162 -6
- package/src/skills/analysis-campaign/SKILL.md +19 -5
- package/src/skills/baseline/SKILL.md +66 -31
- package/src/skills/decision/SKILL.md +1 -1
- package/src/skills/experiment/SKILL.md +11 -5
- package/src/skills/finalize/SKILL.md +1 -1
- package/src/skills/idea/SKILL.md +246 -4
- package/src/skills/intake-audit/SKILL.md +1 -1
- package/src/skills/rebuttal/SKILL.md +1 -1
- package/src/skills/review/SKILL.md +1 -1
- package/src/skills/scout/SKILL.md +1 -1
- package/src/skills/write/SKILL.md +152 -2
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-CZpg376x.js → AiManusChatView-BGLArZRn.js} +14 -37
- package/src/ui/dist/assets/{AnalysisPlugin-CtHA22g3.js → AnalysisPlugin-BgDGSigG.js} +1 -1
- package/src/ui/dist/assets/{AutoFigurePlugin-BSWmLMmF.js → AutoFigurePlugin-B65HD7L4.js} +5 -5
- package/src/ui/dist/assets/{CliPlugin-CJ7jdm_s.js → CliPlugin-CUqgsFHC.js} +17 -110
- package/src/ui/dist/assets/{CodeEditorPlugin-DhInVGFf.js → CodeEditorPlugin-CF5EdvaS.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-D1n8S9r5.js → CodeViewerPlugin-DEeU063D.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-C4XM_kqk.js → DocViewerPlugin-Df-FuDlZ.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-W6kS9r6v.js → GitDiffViewerPlugin-RAnNaRxM.js} +1 -1
- package/src/ui/dist/assets/{ImageViewerPlugin-DPeUx_Oz.js → ImageViewerPlugin-DXJ0ZJGg.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-eAelUaub.js → LabCopilotPanel-BlO-sKsj.js} +10 -10
- package/src/ui/dist/assets/{LabPlugin-BbOrBxKY.js → LabPlugin-BajPZW5v.js} +1 -1
- package/src/ui/dist/assets/{LatexPlugin-C-HhkVXY.js → LatexPlugin-F1OEol8D.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-BDIzIBfh.js → MarkdownViewerPlugin-MhUupqwT.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DAOJphwr.js → MarketplacePlugin-DxhIEsv0.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-BsoMvDoU.js → NotebookEditor-q7TkhewC.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-fiC7RtHf.js → PdfLoader-B8ZOTKFc.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-C5OxZBFK.js → PdfMarkdownPlugin-xFPvzvWh.js} +3 -3
- package/src/ui/dist/assets/{PdfViewerPlugin-CAbxQebk.js → PdfViewerPlugin-EjEcsIB8.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-SE33Lb9B.js → SearchPlugin-ixY-1lgW.js} +1 -1
- package/src/ui/dist/assets/{Stepper-0Av7GfV7.js → Stepper-gYFK2Pgz.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-Daf2gJDI.js → TextViewerPlugin-Cym6pv_n.js} +4 -4
- package/src/ui/dist/assets/{VNCViewer-BKrMUIOX.js → VNCViewer-BPmIHcmK.js} +9 -9
- package/src/ui/dist/assets/{bibtex-JBdOEe45.js → bibtex-Btv6Wi7f.js} +1 -1
- package/src/ui/dist/assets/{code-B0TDFCZz.js → code-BlG7g85c.js} +1 -1
- package/src/ui/dist/assets/{file-content-3YtrSacz.js → file-content-DBT5OfTZ.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-CJEg5OG1.js → file-diff-panel-BWXYzqHk.js} +1 -1
- package/src/ui/dist/assets/{file-socket-CYQYdmB1.js → file-socket-wDlx6byM.js} +1 -1
- package/src/ui/dist/assets/{file-utils-Cd1C9Ppl.js → file-utils-Ba3nJmH0.js} +1 -1
- package/src/ui/dist/assets/{image-B33ctrvC.js → image-BwtCyguk.js} +1 -1
- package/src/ui/dist/assets/{index-BNQWqmJ2.js → index-B-2scqCJ.js} +11 -11
- package/src/ui/dist/assets/{index-BVXsmS7V.js → index-Bz5AaWL7.js} +52383 -51440
- package/src/ui/dist/assets/{index-Buw_N1VQ.js → index-CfRpE209.js} +2 -2
- package/src/ui/dist/assets/{index-9CLPVeZh.js → index-DcqvKzeJ.js} +1 -1
- package/src/ui/dist/assets/{index-SwmFAld3.css → index-DpMZw8aM.css} +49 -2
- package/src/ui/dist/assets/{message-square-D0cUJ9yU.js → message-square-BnlyWVH0.js} +1 -1
- package/src/ui/dist/assets/{monaco-UZLYkp2n.js → monaco-CXe0pAVe.js} +1 -1
- package/src/ui/dist/assets/{popover-CTeiY-dK.js → popover-BCHmVhHj.js} +1 -1
- package/src/ui/dist/assets/{project-sync-Dbs01Xky.js → project-sync-Brk6kaOD.js} +1 -1
- package/src/ui/dist/assets/{sigma-CM08S-xT.js → sigma-D72eSUep.js} +1 -1
- package/src/ui/dist/assets/{tooltip-pDtzvU9p.js → tooltip-BMWd0dqX.js} +1 -1
- package/src/ui/dist/assets/{trash-YvPCP-da.js → trash-BIt_eWIS.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-Bavi74Ac.js → useCliAccess-N1hkTRrR.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-CVXY6oeg.js → useFileDiffOverlay-DPRPv6rv.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-Cf4flRW7.js → wrap-text-E5-UheyP.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-Hb0Z1YpT.js → zoom-out-D4TR-ZZ_.js} +1 -1
- package/src/ui/dist/index.html +2 -2
|
@@ -4,16 +4,22 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any
|
|
7
|
-
from urllib.error import URLError
|
|
8
|
-
from urllib.request import Request, urlopen
|
|
9
7
|
|
|
10
8
|
from ..connector_runtime import (
|
|
11
9
|
build_discovered_target,
|
|
12
10
|
conversation_identity_key,
|
|
11
|
+
format_conversation_id,
|
|
13
12
|
infer_connector_transport,
|
|
14
13
|
merge_discovered_targets,
|
|
15
14
|
parse_conversation_id,
|
|
16
15
|
)
|
|
16
|
+
from ..connector_profiles import (
|
|
17
|
+
PROFILEABLE_CONNECTOR_NAMES,
|
|
18
|
+
connector_profile_label,
|
|
19
|
+
find_connector_profile,
|
|
20
|
+
list_connector_profiles,
|
|
21
|
+
merge_connector_profile_config,
|
|
22
|
+
)
|
|
17
23
|
from ..bridges import get_connector_bridge
|
|
18
24
|
from ..shared import append_jsonl, ensure_dir, generate_id, read_json, read_jsonl, utc_now, write_json
|
|
19
25
|
from .base import BaseChannel
|
|
@@ -35,6 +41,45 @@ class GenericRelayChannel(BaseChannel):
|
|
|
35
41
|
self.bindings_path = self.root / "bindings.json"
|
|
36
42
|
self.state_path = self.root / "state.json"
|
|
37
43
|
|
|
44
|
+
def _profiles(self) -> list[dict[str, Any]]:
|
|
45
|
+
if self.name not in PROFILEABLE_CONNECTOR_NAMES:
|
|
46
|
+
return []
|
|
47
|
+
return list_connector_profiles(self.name, self.config)
|
|
48
|
+
|
|
49
|
+
def _should_encode_profile_id(self) -> bool:
|
|
50
|
+
return len(self._profiles()) > 1
|
|
51
|
+
|
|
52
|
+
def _conversation_id(self, chat_type: str, chat_id: str, *, profile_id: str | None = None) -> str:
|
|
53
|
+
return format_conversation_id(
|
|
54
|
+
self.name,
|
|
55
|
+
chat_type,
|
|
56
|
+
chat_id,
|
|
57
|
+
profile_id=profile_id if self._should_encode_profile_id() else None,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def _profile(self, profile_id: str | None) -> dict[str, Any] | None:
|
|
61
|
+
normalized = str(profile_id or "").strip() or None
|
|
62
|
+
if normalized:
|
|
63
|
+
return find_connector_profile(self.name, self.config, profile_id=normalized)
|
|
64
|
+
profiles = self._profiles()
|
|
65
|
+
if len(profiles) == 1:
|
|
66
|
+
return profiles[0]
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
def _profile_label(self, profile_id: str | None) -> str | None:
|
|
70
|
+
profile = self._profile(profile_id)
|
|
71
|
+
if profile is None:
|
|
72
|
+
return None
|
|
73
|
+
return connector_profile_label(self.name, profile)
|
|
74
|
+
|
|
75
|
+
def _profile_runtime_state(self, profile_id: str | None = None) -> dict[str, Any]:
|
|
76
|
+
runtime_path = self.root / "runtime.json"
|
|
77
|
+
normalized = str(profile_id or "").strip()
|
|
78
|
+
if normalized:
|
|
79
|
+
runtime_path = self.root / "profiles" / normalized / "runtime.json"
|
|
80
|
+
payload = read_json(runtime_path, {})
|
|
81
|
+
return payload if isinstance(payload, dict) else {}
|
|
82
|
+
|
|
38
83
|
def send(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
39
84
|
formatted = self._format_outbound(payload)
|
|
40
85
|
record = {"sent_at": utc_now(), **formatted}
|
|
@@ -88,18 +133,66 @@ class GenericRelayChannel(BaseChannel):
|
|
|
88
133
|
def status(self) -> dict[str, Any]:
|
|
89
134
|
bindings = self.list_bindings()
|
|
90
135
|
state = self._read_state()
|
|
91
|
-
runtime_state =
|
|
92
|
-
|
|
93
|
-
|
|
136
|
+
runtime_state = self._profile_runtime_state()
|
|
137
|
+
profiles = self._profiles()
|
|
138
|
+
profile_states: dict[str, dict[str, Any]] = {}
|
|
139
|
+
for profile in profiles:
|
|
140
|
+
profile_id = str(profile.get("profile_id") or "").strip()
|
|
141
|
+
profile_state = self._profile_runtime_state(profile_id)
|
|
142
|
+
if not profile_state and len(profiles) == 1 and isinstance(runtime_state, dict):
|
|
143
|
+
profile_state = runtime_state
|
|
144
|
+
profile_states[profile_id] = profile_state
|
|
145
|
+
runtime_last_conversation_candidates = [
|
|
146
|
+
str((runtime_state or {}).get("last_conversation_id") or "").strip()
|
|
94
147
|
if isinstance(runtime_state, dict)
|
|
95
|
-
else
|
|
96
|
-
|
|
148
|
+
else "",
|
|
149
|
+
*[
|
|
150
|
+
str((profile_state or {}).get("last_conversation_id") or "").strip()
|
|
151
|
+
for profile_state in profile_states.values()
|
|
152
|
+
if isinstance(profile_state, dict)
|
|
153
|
+
],
|
|
154
|
+
]
|
|
155
|
+
runtime_last_conversation_id = next((item for item in runtime_last_conversation_candidates if item), None)
|
|
97
156
|
last_conversation_id = str((state or {}).get("last_conversation_id") or runtime_last_conversation_id or "").strip() or None
|
|
98
|
-
|
|
157
|
+
profile_transports = [
|
|
158
|
+
infer_connector_transport(
|
|
159
|
+
self.name,
|
|
160
|
+
merge_connector_profile_config(self.name, self.config, profile),
|
|
161
|
+
)
|
|
162
|
+
for profile in profiles
|
|
163
|
+
]
|
|
164
|
+
if len(set(item for item in profile_transports if item)) == 1 and profile_transports:
|
|
165
|
+
transport = profile_transports[0]
|
|
166
|
+
elif profile_transports:
|
|
167
|
+
transport = "mixed"
|
|
168
|
+
else:
|
|
169
|
+
transport = infer_connector_transport(self.name, self.config)
|
|
99
170
|
default_conversation_id = last_conversation_id or (bindings[0]["conversation_id"] if bindings else None)
|
|
100
171
|
recent_conversations = self._recent_conversations(state)
|
|
172
|
+
known_targets = self._known_targets(state)
|
|
101
173
|
discovered_targets = merge_discovered_targets(
|
|
102
174
|
[
|
|
175
|
+
*[
|
|
176
|
+
{
|
|
177
|
+
**(
|
|
178
|
+
build_discovered_target(
|
|
179
|
+
item.get("conversation_id"),
|
|
180
|
+
source=str(item.get("source") or "known_target"),
|
|
181
|
+
is_default=item.get("conversation_id") == default_conversation_id,
|
|
182
|
+
label=str(item.get("label") or "").strip() or None,
|
|
183
|
+
quest_id=str(item.get("quest_id") or "").strip() or None,
|
|
184
|
+
updated_at=str(item.get("updated_at") or "").strip() or None,
|
|
185
|
+
profile_id=str(item.get("profile_id") or "").strip() or None,
|
|
186
|
+
profile_label=str(item.get("profile_label") or "").strip() or self._profile_label(
|
|
187
|
+
str(item.get("profile_id") or "").strip() or None
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
or {}
|
|
191
|
+
),
|
|
192
|
+
"first_seen_at": str(item.get("first_seen_at") or "").strip() or None,
|
|
193
|
+
}
|
|
194
|
+
for item in known_targets
|
|
195
|
+
],
|
|
103
196
|
*[
|
|
104
197
|
build_discovered_target(
|
|
105
198
|
item.get("conversation_id"),
|
|
@@ -108,15 +201,36 @@ class GenericRelayChannel(BaseChannel):
|
|
|
108
201
|
label=str(item.get("label") or "").strip() or None,
|
|
109
202
|
quest_id=str(item.get("quest_id") or "").strip() or None,
|
|
110
203
|
updated_at=str(item.get("updated_at") or "").strip() or None,
|
|
204
|
+
profile_id=str(item.get("profile_id") or "").strip() or None,
|
|
205
|
+
profile_label=str(item.get("profile_label") or "").strip() or self._profile_label(
|
|
206
|
+
str(item.get("profile_id") or "").strip() or None
|
|
207
|
+
),
|
|
111
208
|
)
|
|
112
209
|
for item in recent_conversations
|
|
113
210
|
],
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
211
|
+
*(
|
|
212
|
+
[
|
|
213
|
+
build_discovered_target(
|
|
214
|
+
runtime_last_conversation_id,
|
|
215
|
+
source="recent_runtime_activity",
|
|
216
|
+
is_default=runtime_last_conversation_id == default_conversation_id,
|
|
217
|
+
updated_at=str((runtime_state or {}).get("updated_at") or "").strip() or None,
|
|
218
|
+
)
|
|
219
|
+
]
|
|
220
|
+
if runtime_last_conversation_id
|
|
221
|
+
else []
|
|
119
222
|
),
|
|
223
|
+
*[
|
|
224
|
+
build_discovered_target(
|
|
225
|
+
str((profile_state or {}).get("last_conversation_id") or "").strip() or None,
|
|
226
|
+
source="recent_runtime_activity",
|
|
227
|
+
is_default=str((profile_state or {}).get("last_conversation_id") or "").strip() == default_conversation_id,
|
|
228
|
+
updated_at=str((profile_state or {}).get("updated_at") or "").strip() or None,
|
|
229
|
+
profile_id=profile_id or None,
|
|
230
|
+
profile_label=self._profile_label(profile_id),
|
|
231
|
+
)
|
|
232
|
+
for profile_id, profile_state in profile_states.items()
|
|
233
|
+
],
|
|
120
234
|
*[
|
|
121
235
|
build_discovered_target(
|
|
122
236
|
item.get("conversation_id"),
|
|
@@ -124,54 +238,176 @@ class GenericRelayChannel(BaseChannel):
|
|
|
124
238
|
is_default=item.get("conversation_id") == default_conversation_id,
|
|
125
239
|
quest_id=str(item.get("quest_id") or "").strip() or None,
|
|
126
240
|
updated_at=str(item.get("updated_at") or "").strip() or None,
|
|
241
|
+
profile_id=str(item.get("profile_id") or "").strip() or None,
|
|
242
|
+
profile_label=str(item.get("profile_label") or "").strip() or self._profile_label(
|
|
243
|
+
str(item.get("profile_id") or "").strip() or None
|
|
244
|
+
),
|
|
127
245
|
)
|
|
128
246
|
for item in bindings
|
|
129
247
|
],
|
|
130
248
|
]
|
|
131
249
|
)
|
|
132
250
|
default_target = next((item for item in discovered_targets if item.get("is_default")), None)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
):
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
251
|
+
def runtime_connection_state(
|
|
252
|
+
*,
|
|
253
|
+
config: dict[str, Any],
|
|
254
|
+
runtime_snapshot: dict[str, Any],
|
|
255
|
+
runtime_transport: str,
|
|
256
|
+
last_seen_conversation_id: str | None,
|
|
257
|
+
) -> tuple[str, str]:
|
|
258
|
+
connection_state = self._connection_state(runtime_transport, last_seen_conversation_id, config=config)
|
|
259
|
+
auth_state = self._auth_state(runtime_transport, config=config)
|
|
260
|
+
if (
|
|
261
|
+
bool(config.get("enabled", False))
|
|
262
|
+
and isinstance(runtime_snapshot, dict)
|
|
263
|
+
and str(runtime_snapshot.get("transport") or "").strip() == runtime_transport
|
|
264
|
+
):
|
|
265
|
+
snapshot_connection_state = str(runtime_snapshot.get("connection_state") or "").strip()
|
|
266
|
+
snapshot_auth_state = str(runtime_snapshot.get("auth_state") or "").strip()
|
|
267
|
+
if snapshot_connection_state:
|
|
268
|
+
connection_state = snapshot_connection_state
|
|
269
|
+
elif runtime_snapshot.get("connected") is True:
|
|
270
|
+
connection_state = "connected"
|
|
271
|
+
elif runtime_snapshot.get("last_error"):
|
|
272
|
+
connection_state = "error"
|
|
273
|
+
if snapshot_auth_state:
|
|
274
|
+
auth_state = snapshot_auth_state
|
|
275
|
+
elif runtime_snapshot.get("auth_ok") is True:
|
|
276
|
+
auth_state = "ready"
|
|
277
|
+
elif runtime_snapshot.get("auth_ok") is False:
|
|
278
|
+
auth_state = "error"
|
|
279
|
+
return connection_state, auth_state
|
|
280
|
+
|
|
281
|
+
def matches_profile(item: dict[str, Any], profile_id: str) -> bool:
|
|
282
|
+
item_profile_id = str(item.get("profile_id") or "").strip()
|
|
283
|
+
return item_profile_id == profile_id or (not item_profile_id and len(profiles) == 1)
|
|
284
|
+
|
|
285
|
+
profile_snapshots = []
|
|
286
|
+
for profile in profiles:
|
|
287
|
+
profile_id = str(profile.get("profile_id") or "").strip()
|
|
288
|
+
profile_config = merge_connector_profile_config(self.name, self.config, profile)
|
|
289
|
+
profile_transport = infer_connector_transport(self.name, profile_config)
|
|
290
|
+
profile_runtime_state = profile_states.get(profile_id, {})
|
|
291
|
+
profile_last_conversation_id = str(profile_runtime_state.get("last_conversation_id") or "").strip() or None
|
|
292
|
+
profile_connection_state, profile_auth_state = runtime_connection_state(
|
|
293
|
+
config=profile_config,
|
|
294
|
+
runtime_snapshot=profile_runtime_state,
|
|
295
|
+
runtime_transport=profile_transport,
|
|
296
|
+
last_seen_conversation_id=profile_last_conversation_id,
|
|
297
|
+
)
|
|
298
|
+
profile_targets = [
|
|
299
|
+
dict(item)
|
|
300
|
+
for item in discovered_targets
|
|
301
|
+
if matches_profile(item, profile_id)
|
|
302
|
+
]
|
|
303
|
+
profile_recent_conversations = [
|
|
304
|
+
dict(item)
|
|
305
|
+
for item in recent_conversations
|
|
306
|
+
if matches_profile(item, profile_id)
|
|
307
|
+
]
|
|
308
|
+
profile_bindings = [
|
|
309
|
+
dict(item)
|
|
310
|
+
for item in bindings
|
|
311
|
+
if matches_profile(item, profile_id)
|
|
312
|
+
]
|
|
313
|
+
profile_snapshots.append(
|
|
314
|
+
{
|
|
315
|
+
"profile_id": profile_id,
|
|
316
|
+
"label": connector_profile_label(self.name, profile),
|
|
317
|
+
"bot_name": str(profile.get("bot_name") or "").strip() or None,
|
|
318
|
+
"transport": profile_transport,
|
|
319
|
+
"last_conversation_id": profile_last_conversation_id,
|
|
320
|
+
"connection_state": profile_connection_state,
|
|
321
|
+
"auth_state": profile_auth_state,
|
|
322
|
+
"discovered_targets": profile_targets,
|
|
323
|
+
"recent_conversations": profile_recent_conversations,
|
|
324
|
+
"bindings": profile_bindings,
|
|
325
|
+
"target_count": len(profile_targets),
|
|
326
|
+
"binding_count": len(profile_bindings),
|
|
327
|
+
"last_error": profile_runtime_state.get("last_error") if isinstance(profile_runtime_state, dict) else None,
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
if profile_snapshots:
|
|
332
|
+
aggregate_connection_candidates = [
|
|
333
|
+
str(item.get("connection_state") or "").strip()
|
|
334
|
+
for item in profile_snapshots
|
|
335
|
+
if str(item.get("connection_state") or "").strip()
|
|
336
|
+
]
|
|
337
|
+
aggregate_auth_candidates = [
|
|
338
|
+
str(item.get("auth_state") or "").strip()
|
|
339
|
+
for item in profile_snapshots
|
|
340
|
+
if str(item.get("auth_state") or "").strip()
|
|
341
|
+
]
|
|
342
|
+
connection_state = next(
|
|
343
|
+
(
|
|
344
|
+
candidate
|
|
345
|
+
for candidate in (
|
|
346
|
+
"connected",
|
|
347
|
+
"connecting",
|
|
348
|
+
"starting",
|
|
349
|
+
"configured",
|
|
350
|
+
"ready",
|
|
351
|
+
"awaiting_first_message",
|
|
352
|
+
"error",
|
|
353
|
+
"needs_credentials",
|
|
354
|
+
"disabled",
|
|
355
|
+
)
|
|
356
|
+
if candidate in aggregate_connection_candidates
|
|
357
|
+
),
|
|
358
|
+
"disabled" if not bool(self.config.get("enabled", False)) else "configured",
|
|
359
|
+
)
|
|
360
|
+
auth_state = next(
|
|
361
|
+
(
|
|
362
|
+
candidate
|
|
363
|
+
for candidate in (
|
|
364
|
+
"ready",
|
|
365
|
+
"configured",
|
|
366
|
+
"error",
|
|
367
|
+
"missing_credentials",
|
|
368
|
+
"missing_configuration",
|
|
369
|
+
"disabled",
|
|
370
|
+
)
|
|
371
|
+
if candidate in aggregate_auth_candidates
|
|
372
|
+
),
|
|
373
|
+
"disabled" if not bool(self.config.get("enabled", False)) else "configured",
|
|
374
|
+
)
|
|
375
|
+
else:
|
|
376
|
+
connection_state, auth_state = runtime_connection_state(
|
|
377
|
+
config=self.config,
|
|
378
|
+
runtime_snapshot=runtime_state,
|
|
379
|
+
runtime_transport=transport,
|
|
380
|
+
last_seen_conversation_id=last_conversation_id,
|
|
381
|
+
)
|
|
154
382
|
return {
|
|
155
383
|
"name": self.name,
|
|
156
384
|
"display_mode": self.display_mode,
|
|
157
|
-
"mode": self.config.get("mode",
|
|
385
|
+
"mode": self.config.get("mode", transport),
|
|
158
386
|
"transport": transport,
|
|
159
|
-
"relay_url": self.config.get("relay_url"),
|
|
160
387
|
"enabled": bool(self.config.get("enabled", False)),
|
|
161
388
|
"connection_state": connection_state,
|
|
162
389
|
"auth_state": auth_state,
|
|
163
390
|
"last_conversation_id": last_conversation_id,
|
|
164
|
-
"last_error":
|
|
391
|
+
"last_error": next(
|
|
392
|
+
(
|
|
393
|
+
str(item.get("last_error") or "").strip()
|
|
394
|
+
for item in profile_snapshots
|
|
395
|
+
if str(item.get("last_error") or "").strip()
|
|
396
|
+
),
|
|
397
|
+
str(runtime_state.get("last_error") or "").strip() or None if isinstance(runtime_state, dict) else None,
|
|
398
|
+
),
|
|
165
399
|
"inbox_count": len(read_jsonl(self.inbox_path)),
|
|
166
400
|
"outbox_count": len(read_jsonl(self.outbox_path)),
|
|
167
401
|
"ignored_count": len(read_jsonl(self.ignored_path)),
|
|
168
402
|
"binding_count": len(bindings),
|
|
169
403
|
"bindings": bindings,
|
|
404
|
+
"known_targets": known_targets,
|
|
170
405
|
"recent_conversations": recent_conversations,
|
|
171
406
|
"recent_events": self._recent_events(),
|
|
172
407
|
"target_count": len(discovered_targets),
|
|
173
408
|
"default_target": default_target,
|
|
174
409
|
"discovered_targets": discovered_targets,
|
|
410
|
+
"profiles": profile_snapshots,
|
|
175
411
|
}
|
|
176
412
|
|
|
177
413
|
def ingest(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -188,6 +424,8 @@ class GenericRelayChannel(BaseChannel):
|
|
|
188
424
|
sender_name=str(normalized.get("sender_name") or "").strip() or None,
|
|
189
425
|
quest_id=str(normalized.get("quest_id") or "").strip() or None,
|
|
190
426
|
message_id=str(normalized.get("message_id") or "").strip() or None,
|
|
427
|
+
profile_id=str(normalized.get("profile_id") or "").strip() or None,
|
|
428
|
+
profile_label=str(normalized.get("profile_label") or "").strip() or None,
|
|
191
429
|
)
|
|
192
430
|
return {"ok": True, "accepted": True, "normalized": normalized}
|
|
193
431
|
|
|
@@ -201,10 +439,20 @@ class GenericRelayChannel(BaseChannel):
|
|
|
201
439
|
if chat_type not in {"group", "direct"}:
|
|
202
440
|
chat_type = "group" if group_id else "direct"
|
|
203
441
|
chat_key = group_id if chat_type == "group" else direct_id
|
|
204
|
-
|
|
442
|
+
parsed_conversation = parse_conversation_id(payload.get("conversation_id"))
|
|
443
|
+
profile_id = str(payload.get("profile_id") or (parsed_conversation or {}).get("profile_id") or "").strip() or None
|
|
444
|
+
profiles = self._profiles()
|
|
445
|
+
if profile_id is None and len(profiles) == 1:
|
|
446
|
+
profile_id = str(profiles[0].get("profile_id") or "").strip() or None
|
|
447
|
+
profile_config = self._profile(profile_id)
|
|
448
|
+
profile_label = self._profile_label(profile_id)
|
|
449
|
+
conversation_id = str(
|
|
450
|
+
payload.get("conversation_id")
|
|
451
|
+
or self._conversation_id(chat_type, chat_key or "unknown", profile_id=profile_id)
|
|
452
|
+
)
|
|
205
453
|
message_id = str(payload.get("message_id") or payload.get("event_id") or generate_id(self.name))
|
|
206
|
-
mentioned = bool(payload.get("mentioned")) or self._looks_like_mention(text)
|
|
207
|
-
normalized_text = self._strip_mention_prefix(text)
|
|
454
|
+
mentioned = bool(payload.get("mentioned")) or self._looks_like_mention(text, profile=profile_config)
|
|
455
|
+
normalized_text = self._strip_mention_prefix(text, profile=profile_config)
|
|
208
456
|
is_command = normalized_text.startswith(self.command_prefix())
|
|
209
457
|
|
|
210
458
|
group_access = self._check_group_access(group_id=group_id, sender_id=sender_id)
|
|
@@ -219,6 +467,8 @@ class GenericRelayChannel(BaseChannel):
|
|
|
219
467
|
"sender_id": sender_id,
|
|
220
468
|
"sender_name": sender_name,
|
|
221
469
|
"group_id": group_id,
|
|
470
|
+
"profile_id": profile_id,
|
|
471
|
+
"profile_label": profile_label,
|
|
222
472
|
}
|
|
223
473
|
|
|
224
474
|
dm_access = self._check_dm_access(sender_id=sender_id)
|
|
@@ -232,6 +482,8 @@ class GenericRelayChannel(BaseChannel):
|
|
|
232
482
|
"text": text,
|
|
233
483
|
"sender_id": sender_id,
|
|
234
484
|
"sender_name": sender_name,
|
|
485
|
+
"profile_id": profile_id,
|
|
486
|
+
"profile_label": profile_label,
|
|
235
487
|
}
|
|
236
488
|
|
|
237
489
|
if chat_type == "group" and self.config.get("require_mention_in_groups", True) and not (mentioned or is_command):
|
|
@@ -244,6 +496,8 @@ class GenericRelayChannel(BaseChannel):
|
|
|
244
496
|
"text": text,
|
|
245
497
|
"sender_id": sender_id,
|
|
246
498
|
"sender_name": sender_name,
|
|
499
|
+
"profile_id": profile_id,
|
|
500
|
+
"profile_label": profile_label,
|
|
247
501
|
}
|
|
248
502
|
|
|
249
503
|
return {
|
|
@@ -260,6 +514,8 @@ class GenericRelayChannel(BaseChannel):
|
|
|
260
514
|
"direct_id": direct_id or None,
|
|
261
515
|
"mentioned": mentioned,
|
|
262
516
|
"is_command": is_command,
|
|
517
|
+
"profile_id": profile_id,
|
|
518
|
+
"profile_label": profile_label,
|
|
263
519
|
"created_at": utc_now(),
|
|
264
520
|
"raw_event": payload,
|
|
265
521
|
}
|
|
@@ -267,9 +523,13 @@ class GenericRelayChannel(BaseChannel):
|
|
|
267
523
|
def bind_conversation(self, conversation_id: str, quest_id: str) -> dict[str, Any]:
|
|
268
524
|
bindings = read_json(self.bindings_path, {"bindings": {}})
|
|
269
525
|
binding_map = dict(bindings.get("bindings") or {})
|
|
526
|
+
parsed = parse_conversation_id(conversation_id)
|
|
527
|
+
profile_id = str((parsed or {}).get("profile_id") or "").strip() or None
|
|
270
528
|
binding_map[conversation_id] = {
|
|
271
529
|
"quest_id": quest_id,
|
|
272
530
|
"updated_at": utc_now(),
|
|
531
|
+
"profile_id": profile_id,
|
|
532
|
+
"profile_label": self._profile_label(profile_id),
|
|
273
533
|
}
|
|
274
534
|
bindings["bindings"] = binding_map
|
|
275
535
|
write_json(self.bindings_path, bindings)
|
|
@@ -278,6 +538,8 @@ class GenericRelayChannel(BaseChannel):
|
|
|
278
538
|
updated_at=str(binding_map[conversation_id].get("updated_at") or utc_now()),
|
|
279
539
|
source="quest_binding",
|
|
280
540
|
quest_id=quest_id,
|
|
541
|
+
profile_id=profile_id,
|
|
542
|
+
profile_label=str(binding_map[conversation_id].get("profile_label") or "").strip() or None,
|
|
281
543
|
)
|
|
282
544
|
return binding_map[conversation_id]
|
|
283
545
|
|
|
@@ -308,20 +570,31 @@ class GenericRelayChannel(BaseChannel):
|
|
|
308
570
|
for conversation_id, payload in sorted((bindings.get("bindings") or {}).items()):
|
|
309
571
|
if not isinstance(payload, dict):
|
|
310
572
|
continue
|
|
311
|
-
|
|
573
|
+
parsed = parse_conversation_id(conversation_id)
|
|
574
|
+
profile_id = str((parsed or {}).get("profile_id") or payload.get("profile_id") or "").strip() or None
|
|
575
|
+
items.append(
|
|
576
|
+
{
|
|
577
|
+
"conversation_id": conversation_id,
|
|
578
|
+
"profile_id": profile_id,
|
|
579
|
+
"profile_label": str(payload.get("profile_label") or "").strip() or self._profile_label(profile_id),
|
|
580
|
+
**payload,
|
|
581
|
+
}
|
|
582
|
+
)
|
|
312
583
|
return items
|
|
313
584
|
|
|
314
585
|
def command_prefix(self) -> str:
|
|
315
586
|
return str(self.config.get("command_prefix") or "/").strip() or "/"
|
|
316
587
|
|
|
317
|
-
def _looks_like_mention(self, text: str) -> bool:
|
|
588
|
+
def _looks_like_mention(self, text: str, *, profile: dict[str, Any] | None = None) -> bool:
|
|
318
589
|
lowered = (text or "").lower()
|
|
319
|
-
|
|
590
|
+
profile_config = profile or {}
|
|
591
|
+
bot_name = str(profile_config.get("bot_name") or self.config.get("bot_name") or "DeepScientist").strip().lower()
|
|
320
592
|
return f"@{bot_name}" in lowered
|
|
321
593
|
|
|
322
|
-
def _strip_mention_prefix(self, text: str) -> str:
|
|
594
|
+
def _strip_mention_prefix(self, text: str, *, profile: dict[str, Any] | None = None) -> str:
|
|
323
595
|
cleaned = str(text or "").strip()
|
|
324
|
-
|
|
596
|
+
profile_config = profile or {}
|
|
597
|
+
bot_name = str(profile_config.get("bot_name") or self.config.get("bot_name") or "DeepScientist").strip()
|
|
325
598
|
prefix = f"@{bot_name}"
|
|
326
599
|
if cleaned.startswith(prefix):
|
|
327
600
|
return cleaned[len(prefix):].strip()
|
|
@@ -404,34 +677,18 @@ class GenericRelayChannel(BaseChannel):
|
|
|
404
677
|
return normalized_sender in allow_from
|
|
405
678
|
|
|
406
679
|
def _deliver(self, record: dict[str, Any]) -> dict[str, Any] | None:
|
|
680
|
+
delivery_config = self.config
|
|
681
|
+
parsed = parse_conversation_id(record.get("conversation_id"))
|
|
682
|
+
profile_id = str((parsed or {}).get("profile_id") or "").strip() or None
|
|
683
|
+
profile = self._profile(profile_id)
|
|
684
|
+
if profile is not None:
|
|
685
|
+
delivery_config = merge_connector_profile_config(self.name, self.config, profile)
|
|
407
686
|
bridge = get_connector_bridge(self.name)
|
|
408
687
|
if bridge is not None:
|
|
409
|
-
delivery = bridge.deliver(record,
|
|
688
|
+
delivery = bridge.deliver(record, delivery_config)
|
|
410
689
|
if delivery is not None:
|
|
411
690
|
return delivery
|
|
412
|
-
|
|
413
|
-
if not relay_url or self.config.get("mode", "relay") != "relay":
|
|
414
|
-
return None
|
|
415
|
-
body = json.dumps(record, ensure_ascii=False).encode("utf-8")
|
|
416
|
-
request = Request(relay_url, data=body, method="POST")
|
|
417
|
-
request.add_header("Content-Type", "application/json; charset=utf-8")
|
|
418
|
-
token = str(self.config.get("relay_auth_token") or "").strip()
|
|
419
|
-
if token:
|
|
420
|
-
request.add_header("Authorization", f"Bearer {token}")
|
|
421
|
-
try:
|
|
422
|
-
with urlopen(request, timeout=5) as response: # noqa: S310
|
|
423
|
-
response_text = response.read().decode("utf-8", errors="replace")
|
|
424
|
-
return {
|
|
425
|
-
"ok": 200 <= response.status < 300,
|
|
426
|
-
"status_code": response.status,
|
|
427
|
-
"response": response_text[:500],
|
|
428
|
-
}
|
|
429
|
-
except URLError as exc:
|
|
430
|
-
return {
|
|
431
|
-
"ok": False,
|
|
432
|
-
"status_code": None,
|
|
433
|
-
"error": str(exc),
|
|
434
|
-
}
|
|
691
|
+
return None
|
|
435
692
|
|
|
436
693
|
def _read_state(self) -> dict[str, Any]:
|
|
437
694
|
payload = read_json(self.state_path, {})
|
|
@@ -478,6 +735,8 @@ class GenericRelayChannel(BaseChannel):
|
|
|
478
735
|
sender_name: str | None = None,
|
|
479
736
|
quest_id: str | None = None,
|
|
480
737
|
message_id: str | None = None,
|
|
738
|
+
profile_id: str | None = None,
|
|
739
|
+
profile_label: str | None = None,
|
|
481
740
|
) -> None:
|
|
482
741
|
entry = self._build_recent_conversation_entry(
|
|
483
742
|
conversation_id=conversation_id,
|
|
@@ -487,6 +746,8 @@ class GenericRelayChannel(BaseChannel):
|
|
|
487
746
|
sender_name=sender_name,
|
|
488
747
|
quest_id=quest_id,
|
|
489
748
|
message_id=message_id,
|
|
749
|
+
profile_id=profile_id,
|
|
750
|
+
profile_label=profile_label,
|
|
490
751
|
)
|
|
491
752
|
if entry is None:
|
|
492
753
|
return
|
|
@@ -500,6 +761,7 @@ class GenericRelayChannel(BaseChannel):
|
|
|
500
761
|
"recent_conversations": [entry, *list(state.get("recent_conversations") or [])],
|
|
501
762
|
}
|
|
502
763
|
)
|
|
764
|
+
state["known_targets"] = self._upsert_known_targets(state, entry)
|
|
503
765
|
self._write_state(state)
|
|
504
766
|
|
|
505
767
|
def _build_recent_conversation_entry(
|
|
@@ -512,6 +774,8 @@ class GenericRelayChannel(BaseChannel):
|
|
|
512
774
|
sender_name: str | None = None,
|
|
513
775
|
quest_id: str | None = None,
|
|
514
776
|
message_id: str | None = None,
|
|
777
|
+
profile_id: str | None = None,
|
|
778
|
+
profile_label: str | None = None,
|
|
515
779
|
) -> dict[str, Any] | None:
|
|
516
780
|
parsed = parse_conversation_id(conversation_id)
|
|
517
781
|
if parsed is None:
|
|
@@ -526,6 +790,12 @@ class GenericRelayChannel(BaseChannel):
|
|
|
526
790
|
"updated_at": updated_at,
|
|
527
791
|
"source": source,
|
|
528
792
|
}
|
|
793
|
+
resolved_profile_id = str(profile_id or parsed.get("profile_id") or "").strip() or None
|
|
794
|
+
resolved_profile_label = str(profile_label or "").strip() or self._profile_label(resolved_profile_id)
|
|
795
|
+
if resolved_profile_id:
|
|
796
|
+
payload["profile_id"] = resolved_profile_id
|
|
797
|
+
if resolved_profile_label:
|
|
798
|
+
payload["profile_label"] = resolved_profile_label
|
|
529
799
|
if sender_id:
|
|
530
800
|
payload["sender_id"] = sender_id
|
|
531
801
|
if sender_name:
|
|
@@ -536,6 +806,69 @@ class GenericRelayChannel(BaseChannel):
|
|
|
536
806
|
payload["message_id"] = message_id
|
|
537
807
|
return payload
|
|
538
808
|
|
|
809
|
+
def _known_targets(self, state: dict[str, Any]) -> list[dict[str, Any]]:
|
|
810
|
+
items = state.get("known_targets")
|
|
811
|
+
if not isinstance(items, list):
|
|
812
|
+
return []
|
|
813
|
+
merged: dict[str, dict[str, Any]] = {}
|
|
814
|
+
for raw in items:
|
|
815
|
+
if not isinstance(raw, dict):
|
|
816
|
+
continue
|
|
817
|
+
conversation_id = str(raw.get("conversation_id") or "").strip()
|
|
818
|
+
if not conversation_id:
|
|
819
|
+
continue
|
|
820
|
+
identity = conversation_identity_key(conversation_id)
|
|
821
|
+
current = dict(raw)
|
|
822
|
+
current["conversation_id"] = conversation_id
|
|
823
|
+
existing = merged.get(identity)
|
|
824
|
+
if existing is None:
|
|
825
|
+
merged[identity] = current
|
|
826
|
+
continue
|
|
827
|
+
merged_entry = {**existing, **current}
|
|
828
|
+
merged_entry["first_seen_at"] = (
|
|
829
|
+
str(existing.get("first_seen_at") or "").strip()
|
|
830
|
+
or str(current.get("first_seen_at") or "").strip()
|
|
831
|
+
or str(existing.get("updated_at") or "").strip()
|
|
832
|
+
or str(current.get("updated_at") or "").strip()
|
|
833
|
+
)
|
|
834
|
+
if str(existing.get("updated_at") or "") > str(current.get("updated_at") or ""):
|
|
835
|
+
merged_entry["updated_at"] = existing.get("updated_at")
|
|
836
|
+
merged[identity] = merged_entry
|
|
837
|
+
return sorted(
|
|
838
|
+
merged.values(),
|
|
839
|
+
key=lambda item: (str(item.get("updated_at") or ""), str(item.get("conversation_id") or "")),
|
|
840
|
+
reverse=True,
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
def _upsert_known_targets(self, state: dict[str, Any], entry: dict[str, Any]) -> list[dict[str, Any]]:
|
|
844
|
+
identity = conversation_identity_key(entry.get("conversation_id"))
|
|
845
|
+
items = list(state.get("known_targets") or [])
|
|
846
|
+
next_items: list[dict[str, Any]] = []
|
|
847
|
+
replaced = False
|
|
848
|
+
for raw in items:
|
|
849
|
+
if not isinstance(raw, dict):
|
|
850
|
+
continue
|
|
851
|
+
if conversation_identity_key(raw.get("conversation_id")) != identity:
|
|
852
|
+
next_items.append(dict(raw))
|
|
853
|
+
continue
|
|
854
|
+
merged = {**raw, **entry}
|
|
855
|
+
merged["first_seen_at"] = (
|
|
856
|
+
str(raw.get("first_seen_at") or "").strip()
|
|
857
|
+
or str(entry.get("first_seen_at") or "").strip()
|
|
858
|
+
or str(raw.get("updated_at") or "").strip()
|
|
859
|
+
or str(entry.get("updated_at") or "").strip()
|
|
860
|
+
)
|
|
861
|
+
next_items.append(merged)
|
|
862
|
+
replaced = True
|
|
863
|
+
if not replaced:
|
|
864
|
+
next_items.append(
|
|
865
|
+
{
|
|
866
|
+
**entry,
|
|
867
|
+
"first_seen_at": str(entry.get("updated_at") or "").strip() or utc_now(),
|
|
868
|
+
}
|
|
869
|
+
)
|
|
870
|
+
return self._known_targets({"known_targets": next_items})
|
|
871
|
+
|
|
539
872
|
@staticmethod
|
|
540
873
|
def _conversation_label(*, chat_type: str, chat_id: str, sender_name: str | None = None) -> str:
|
|
541
874
|
if sender_name and str(chat_type or "").strip().lower() == "direct":
|
|
@@ -596,44 +929,50 @@ class GenericRelayChannel(BaseChannel):
|
|
|
596
929
|
return normalized
|
|
597
930
|
return f"{normalized[: max(limit - 1, 0)].rstrip()}…"
|
|
598
931
|
|
|
599
|
-
def _connection_state(self, transport: str, last_conversation_id: str | None) -> str:
|
|
600
|
-
|
|
932
|
+
def _connection_state(self, transport: str, last_conversation_id: str | None, *, config: dict[str, Any] | None = None) -> str:
|
|
933
|
+
payload = config or self.config
|
|
934
|
+
if not bool(payload.get("enabled", False)):
|
|
601
935
|
return "disabled"
|
|
602
|
-
if transport
|
|
603
|
-
return "relay_configured" if str(self.config.get("relay_url") or "").strip() else "awaiting_relay"
|
|
604
|
-
if self._has_runtime_credentials(transport):
|
|
936
|
+
if self._has_runtime_credentials(transport, config=payload):
|
|
605
937
|
return "ready" if last_conversation_id else "configured"
|
|
606
938
|
return "needs_credentials"
|
|
607
939
|
|
|
608
|
-
def _auth_state(self, transport: str) -> str:
|
|
609
|
-
|
|
940
|
+
def _auth_state(self, transport: str, *, config: dict[str, Any] | None = None) -> str:
|
|
941
|
+
payload = config or self.config
|
|
942
|
+
if not bool(payload.get("enabled", False)):
|
|
610
943
|
return "disabled"
|
|
611
|
-
if transport
|
|
612
|
-
return "ready" if str(self.config.get("relay_url") or "").strip() else "missing_configuration"
|
|
613
|
-
return "ready" if self._has_runtime_credentials(transport) else "missing_credentials"
|
|
944
|
+
return "ready" if self._has_runtime_credentials(transport, config=payload) else "missing_credentials"
|
|
614
945
|
|
|
615
|
-
def _has_runtime_credentials(self, transport: str) -> bool:
|
|
946
|
+
def _has_runtime_credentials(self, transport: str, *, config: dict[str, Any] | None = None) -> bool:
|
|
947
|
+
payload = config or self.config
|
|
616
948
|
if self.name == "telegram":
|
|
617
|
-
return bool(self._secret("bot_token", "bot_token_env"))
|
|
949
|
+
return bool(self._secret("bot_token", "bot_token_env", config=payload))
|
|
618
950
|
if self.name == "discord":
|
|
619
|
-
return bool(self._secret("bot_token", "bot_token_env"))
|
|
951
|
+
return bool(self._secret("bot_token", "bot_token_env", config=payload))
|
|
620
952
|
if self.name == "slack":
|
|
621
953
|
if transport == "socket_mode":
|
|
622
|
-
return bool(
|
|
623
|
-
|
|
954
|
+
return bool(
|
|
955
|
+
self._secret("bot_token", "bot_token_env", config=payload)
|
|
956
|
+
and self._secret("app_token", "app_token_env", config=payload)
|
|
957
|
+
)
|
|
958
|
+
return bool(self._secret("bot_token", "bot_token_env", config=payload))
|
|
624
959
|
if self.name == "feishu":
|
|
625
|
-
return bool(str(
|
|
960
|
+
return bool(str(payload.get("app_id") or "").strip() and self._secret("app_secret", "app_secret_env", config=payload))
|
|
626
961
|
if self.name == "whatsapp":
|
|
627
962
|
if transport == "local_session":
|
|
628
|
-
return bool(str(
|
|
629
|
-
return bool(
|
|
963
|
+
return bool(str(payload.get("session_dir") or "").strip())
|
|
964
|
+
return bool(
|
|
965
|
+
self._secret("access_token", "access_token_env", config=payload)
|
|
966
|
+
and str(payload.get("phone_number_id") or "").strip()
|
|
967
|
+
)
|
|
630
968
|
return False
|
|
631
969
|
|
|
632
|
-
def _secret(self, key: str, env_key: str) -> str:
|
|
633
|
-
|
|
970
|
+
def _secret(self, key: str, env_key: str, *, config: dict[str, Any] | None = None) -> str:
|
|
971
|
+
payload = config or self.config
|
|
972
|
+
direct = str(payload.get(key) or "").strip()
|
|
634
973
|
if direct:
|
|
635
974
|
return direct
|
|
636
|
-
env_name = str(
|
|
975
|
+
env_name = str(payload.get(env_key) or "").strip()
|
|
637
976
|
if not env_name:
|
|
638
977
|
return ""
|
|
639
978
|
return str(os.environ.get(env_name) or "").strip()
|