@inline-chat/hermes-agent-adapter 0.0.5-alpha.4 → 0.0.5-alpha.5
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 +12 -3
- package/package.json +1 -1
- package/plugin/inline/adapter.py +217 -33
- package/plugin/inline/plugin.yaml +1 -1
- package/plugin/inline/sidecar/index.mjs +21 -0
package/README.md
CHANGED
|
@@ -26,12 +26,12 @@ Supported:
|
|
|
26
26
|
- Supervised loopback Node sidecar using the Inline realtime SDK.
|
|
27
27
|
- Realtime inbound messages, catch-up, replies to bot messages, and action callbacks.
|
|
28
28
|
- Outbound text, Markdown parsing, opt-in edit-message streaming, long-message splitting, edits, deletes, typing, and presence.
|
|
29
|
-
- Inline reply-thread routing, explicit-request auto mode, `/threads` controls, parent chat metadata, parent/thread prompt fallback, and thread-specific skill bindings.
|
|
29
|
+
- Inline reply-thread routing, explicit-request auto mode, `/threads` controls, explicit `/follow` and `/unfollow` dialog relevance controls, parent chat metadata, parent/thread prompt fallback, and thread-specific skill bindings.
|
|
30
30
|
- Native Hermes `inline` tool for current-chat/thread reads, bounded history and search, exact message lookup, editing/deleting bot-owned messages, reactions, pin/unpin/list pins, reply-thread creation, and avatar presence/status.
|
|
31
31
|
- Per-turn Inline sender/chat/thread IDs, selective reply/thread/observed context, and parent-thread context, with prompt guidance for sender mentions and current chat/thread Markdown links.
|
|
32
32
|
- OpenClaw-style entity summaries for live turns and tool-fetched history, including mentions, text links, thread links, thread-title links, code/pre blocks, bot commands, and group mentions as untrusted Hermes context.
|
|
33
33
|
- DM and group policies, user allowlists, group sender allowlists, mention requirements, strict mention mode, allowed chats, and free-response chats.
|
|
34
|
-
- Native Inline `/` command-menu sync for Hermes slash commands, including `/threads`, `/inline_update`, and `/update`; typed slash commands continue to work even if menu sync is disabled or rejected.
|
|
34
|
+
- Native Inline `/` command-menu sync for Hermes slash commands, including `/threads`, `/follow`, `/unfollow`, `/inline_update`, and `/update`; typed slash commands continue to work even if menu sync is disabled or rejected.
|
|
35
35
|
- Inline-native buttons for clarify prompts, command approvals, slash confirmations, and model selection.
|
|
36
36
|
- Outbound local photo, video, voice, and document uploads with configurable size caps.
|
|
37
37
|
- Inbound photo, video, voice, and document summaries, with URL-backed media cached locally for Hermes when available.
|
|
@@ -319,7 +319,8 @@ model-picker callbacks. The stricter callback gate prevents group-visible
|
|
|
319
319
|
buttons from becoming a bypass when message intake is otherwise `open`.
|
|
320
320
|
|
|
321
321
|
On gateway startup, the adapter derives the Inline `/` menu from Hermes'
|
|
322
|
-
|
|
322
|
+
declarative local command registry plus Hermes' central slash-command registry,
|
|
323
|
+
normalizes names to Inline Bot API constraints
|
|
323
324
|
(`^[a-z0-9_]+$`, max 32 characters), and calls `setMyCommands`. If Inline
|
|
324
325
|
rejects the full list with `BOT_COMMANDS_TOO_MUCH`, the adapter retries with a
|
|
325
326
|
smaller prefix. Menu sync failures are logged as warnings and do not prevent
|
|
@@ -333,6 +334,14 @@ minimum Hermes version against the running agent and refuses incompatible or
|
|
|
333
334
|
unverifiable updates. The update runs in the background without exposing the
|
|
334
335
|
Inline token to npm, then asks you to run `/restart` so Hermes loads the
|
|
335
336
|
installed version. Development symlink installs are left untouched.
|
|
337
|
+
Unexpected precheck or installer failures write bounded, credential-redacted
|
|
338
|
+
diagnostics to the standard Hermes log stream under the `[inline-update]`
|
|
339
|
+
marker, so `hermes logs` and Hermes debug reports can surface the root cause.
|
|
340
|
+
|
|
341
|
+
Run `/follow` in an Inline DM, group, or reply thread to explicitly opt into
|
|
342
|
+
eligible unmentioned activity waking Hermes. Run `/unfollow` to explicitly opt
|
|
343
|
+
out; server auto-follow heuristics will not turn following back on, while
|
|
344
|
+
mentions and replies retain their normal relevance behavior.
|
|
336
345
|
|
|
337
346
|
The plugin id is `inline`, which is intentionally the same id an eventual
|
|
338
347
|
bundled Hermes adapter should use.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inline-chat/hermes-agent-adapter",
|
|
3
|
-
"version": "0.0.5-alpha.
|
|
3
|
+
"version": "0.0.5-alpha.5",
|
|
4
4
|
"description": "Hermes Agent platform adapter for Inline, with a native Python plugin and bundled Inline realtime sidecar.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
package/plugin/inline/adapter.py
CHANGED
|
@@ -25,7 +25,7 @@ import time
|
|
|
25
25
|
from collections import OrderedDict
|
|
26
26
|
from datetime import datetime, timezone
|
|
27
27
|
from pathlib import Path
|
|
28
|
-
from typing import Any, Dict, List, Optional
|
|
28
|
+
from typing import Any, Callable, Dict, List, NamedTuple, Optional
|
|
29
29
|
from urllib.parse import quote, urlparse
|
|
30
30
|
|
|
31
31
|
try:
|
|
@@ -82,18 +82,19 @@ _INLINE_COMMAND_RETRY_RATIO = 0.8
|
|
|
82
82
|
_INLINE_COMMAND_RE = re.compile(r"^[a-z0-9_]{1,32}$")
|
|
83
83
|
_INLINE_THREADS_COMMAND_DESCRIPTION = "Configure Inline reply-thread routing"
|
|
84
84
|
_INLINE_THREADS_COMMAND_ARGS = "[status|on|off|auto|reset]"
|
|
85
|
+
_INLINE_FOLLOW_COMMAND_DESCRIPTION = "Explicitly follow this Inline chat or thread"
|
|
86
|
+
_INLINE_UNFOLLOW_COMMAND_DESCRIPTION = "Explicitly unfollow this Inline chat or thread"
|
|
85
87
|
_INLINE_UPDATE_COMMAND_DESCRIPTION = "Update the Inline Hermes plugin"
|
|
86
88
|
_INLINE_UPDATE_PACKAGE_NAME = "@inline-chat/hermes-agent-adapter"
|
|
87
89
|
_INLINE_UPDATE_PRECHECK_TIMEOUT_SECONDS = 30
|
|
88
90
|
_INLINE_UPDATE_TIMEOUT_SECONDS = 5 * 60
|
|
91
|
+
_INLINE_UPDATE_LOG_MAX_LINES = 80
|
|
92
|
+
_INLINE_UPDATE_LOG_MAX_CHARS = 8_000
|
|
89
93
|
_INLINE_UPDATE_LOCK = threading.Lock()
|
|
90
94
|
_INLINE_THREADS_ACTION_PREFIX = "th:"
|
|
91
95
|
_INLINE_THREADS_ACTION_TTL_SECONDS = 15 * 60
|
|
92
|
-
_INLINE_LOCAL_COMMANDS = (
|
|
93
|
-
("threads", _INLINE_THREADS_COMMAND_DESCRIPTION),
|
|
94
|
-
("inline_update", _INLINE_UPDATE_COMMAND_DESCRIPTION),
|
|
95
|
-
)
|
|
96
96
|
_INLINE_THREAD_COMMAND_RE = re.compile(r"^/(?:thread|threads)(?:@[A-Za-z0-9_]+)?(?:\s+(.*))?$", re.IGNORECASE)
|
|
97
|
+
_INLINE_FOLLOW_COMMAND_RE = re.compile(r"^/(follow|unfollow)(?:@[A-Za-z0-9_]+)?(?:\s+(.*))?$", re.IGNORECASE)
|
|
97
98
|
_INLINE_REPLY_THREAD_NEGATION_RE = re.compile(
|
|
98
99
|
r"\b(?:do\s+not|don't|dont|please\s+don't|please\s+dont|no\s+need\s+to)\s+"
|
|
99
100
|
r"(?:create|start|open|make|use|move|take|reply|respond|answer|send|thread)\b[^.!?\n]*\bthread\b|"
|
|
@@ -116,6 +117,15 @@ _INLINE_REPLY_THREAD_INTENT_RE = re.compile(
|
|
|
116
117
|
_INLINE_SETTINGS_VERSION = 1
|
|
117
118
|
_INLINE_ENTITY_LIMIT = 12
|
|
118
119
|
_INLINE_ENTITY_TEXT_LIMIT = 120
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class _InlineCommandSpec(NamedTuple):
|
|
123
|
+
name: str
|
|
124
|
+
handler: Callable[[str], Any]
|
|
125
|
+
description: str
|
|
126
|
+
args_hint: str = ""
|
|
127
|
+
|
|
128
|
+
|
|
119
129
|
_INLINE_ENTITY_TYPE_NAMES = {
|
|
120
130
|
1: "mention",
|
|
121
131
|
2: "url",
|
|
@@ -419,7 +429,11 @@ def _normalize_inline_command_description(raw: str) -> str:
|
|
|
419
429
|
def _inline_menu_commands(max_commands: int = _INLINE_COMMAND_LIMIT) -> tuple[List[Dict[str, Any]], int]:
|
|
420
430
|
from hermes_cli.commands import telegram_menu_commands
|
|
421
431
|
|
|
422
|
-
|
|
432
|
+
command_specs = _inline_command_specs()
|
|
433
|
+
local_commands = [
|
|
434
|
+
(spec.name, spec.description)
|
|
435
|
+
for spec in command_specs[:max(0, max_commands)]
|
|
436
|
+
]
|
|
423
437
|
remaining = max(0, max_commands - len(local_commands))
|
|
424
438
|
menu_commands, hidden_count = telegram_menu_commands(max_commands=remaining)
|
|
425
439
|
commands: List[Dict[str, Any]] = []
|
|
@@ -441,7 +455,7 @@ def _inline_menu_commands(max_commands: int = _INLINE_COMMAND_LIMIT) -> tuple[Li
|
|
|
441
455
|
"sort_order": index,
|
|
442
456
|
})
|
|
443
457
|
|
|
444
|
-
hidden_local = max(0, len(
|
|
458
|
+
hidden_local = max(0, len(command_specs) - len(local_commands))
|
|
445
459
|
return commands, hidden_count + hidden_local + skipped
|
|
446
460
|
|
|
447
461
|
|
|
@@ -1104,6 +1118,54 @@ class InlineAdapter(BasePlatformAdapter):
|
|
|
1104
1118
|
)
|
|
1105
1119
|
return True
|
|
1106
1120
|
|
|
1121
|
+
async def _handle_follow_command(
|
|
1122
|
+
self,
|
|
1123
|
+
*,
|
|
1124
|
+
chat_id: str,
|
|
1125
|
+
msg_id: str,
|
|
1126
|
+
from_id: str,
|
|
1127
|
+
text: str,
|
|
1128
|
+
chat_type: str,
|
|
1129
|
+
thread_id: Optional[str],
|
|
1130
|
+
) -> bool:
|
|
1131
|
+
match = _INLINE_FOLLOW_COMMAND_RE.match(str(text or "").strip())
|
|
1132
|
+
if not match:
|
|
1133
|
+
return False
|
|
1134
|
+
command = match.group(1).lower()
|
|
1135
|
+
args = (match.group(2) or "").strip()
|
|
1136
|
+
metadata = {"thread_id": thread_id} if thread_id else None
|
|
1137
|
+
if args:
|
|
1138
|
+
await self.send(chat_id, f"Usage: `/{command}`", reply_to=msg_id, metadata=metadata)
|
|
1139
|
+
return True
|
|
1140
|
+
|
|
1141
|
+
mode = "following" if command == "follow" else "unfollowed"
|
|
1142
|
+
target = {"userId": from_id} if chat_type == "dm" else {"chatId": chat_id}
|
|
1143
|
+
try:
|
|
1144
|
+
await self._sidecar_call("/follow-mode", {"target": target, "mode": mode})
|
|
1145
|
+
except Exception as exc:
|
|
1146
|
+
logger.warning("[inline] /%s failed for chat %s: %s", command, chat_id, exc)
|
|
1147
|
+
await self.send(
|
|
1148
|
+
chat_id,
|
|
1149
|
+
f"Could not update Inline follow mode. Try `/{command}` again.",
|
|
1150
|
+
reply_to=msg_id,
|
|
1151
|
+
metadata=metadata,
|
|
1152
|
+
)
|
|
1153
|
+
return True
|
|
1154
|
+
|
|
1155
|
+
self._chat_info_cache.pop(self._chat_key(chat_id), None)
|
|
1156
|
+
if command == "follow":
|
|
1157
|
+
body = (
|
|
1158
|
+
"Now explicitly following this Inline chat or thread. "
|
|
1159
|
+
"Eligible activity can wake Hermes without an @mention."
|
|
1160
|
+
)
|
|
1161
|
+
else:
|
|
1162
|
+
body = (
|
|
1163
|
+
"Explicitly unfollowed this Inline chat or thread. Automatic follow heuristics "
|
|
1164
|
+
"will not turn following back on; mentions and replies can still wake Hermes."
|
|
1165
|
+
)
|
|
1166
|
+
await self.send(chat_id, body, reply_to=msg_id, metadata=metadata)
|
|
1167
|
+
return True
|
|
1168
|
+
|
|
1107
1169
|
@property
|
|
1108
1170
|
def enforces_own_access_policy(self) -> bool:
|
|
1109
1171
|
"""Inline gates DM/group access at intake via dm_policy/group_policy."""
|
|
@@ -1504,6 +1566,15 @@ class InlineAdapter(BasePlatformAdapter):
|
|
|
1504
1566
|
parent_chat_id=parent_chat_id,
|
|
1505
1567
|
):
|
|
1506
1568
|
return
|
|
1569
|
+
if await self._handle_follow_command(
|
|
1570
|
+
chat_id=chat_id,
|
|
1571
|
+
msg_id=msg_id,
|
|
1572
|
+
from_id=from_id,
|
|
1573
|
+
text=text,
|
|
1574
|
+
chat_type=chat_type,
|
|
1575
|
+
thread_id=thread_id,
|
|
1576
|
+
):
|
|
1577
|
+
return
|
|
1507
1578
|
text = _normalize_inline_plugin_command_text(text)
|
|
1508
1579
|
reply_to_is_own = False
|
|
1509
1580
|
reply_to_text = None
|
|
@@ -3691,6 +3762,23 @@ def _inline_threads_command_handler(raw_args: str) -> str:
|
|
|
3691
3762
|
)
|
|
3692
3763
|
|
|
3693
3764
|
|
|
3765
|
+
def _inline_follow_command_fallback(command: str, raw_args: str) -> str:
|
|
3766
|
+
if str(raw_args or "").strip():
|
|
3767
|
+
return f"Usage: `/{command}`"
|
|
3768
|
+
return (
|
|
3769
|
+
f"Inline follow mode is chat-scoped. Use `/{command}` inside the target "
|
|
3770
|
+
"Inline DM, group chat, or reply thread."
|
|
3771
|
+
)
|
|
3772
|
+
|
|
3773
|
+
|
|
3774
|
+
def _inline_follow_command_handler(raw_args: str = "") -> str:
|
|
3775
|
+
return _inline_follow_command_fallback("follow", raw_args)
|
|
3776
|
+
|
|
3777
|
+
|
|
3778
|
+
def _inline_unfollow_command_handler(raw_args: str = "") -> str:
|
|
3779
|
+
return _inline_follow_command_fallback("unfollow", raw_args)
|
|
3780
|
+
|
|
3781
|
+
|
|
3694
3782
|
def _inline_update_environment() -> Dict[str, str]:
|
|
3695
3783
|
try:
|
|
3696
3784
|
from tools.environments.local import _sanitize_subprocess_env
|
|
@@ -3705,6 +3793,51 @@ def _inline_update_environment() -> Dict[str, str]:
|
|
|
3705
3793
|
return env
|
|
3706
3794
|
|
|
3707
3795
|
|
|
3796
|
+
def _inline_update_log_text(raw: Any, hermes_home: Optional[Path] = None) -> str:
|
|
3797
|
+
text = str(raw or "")
|
|
3798
|
+
text = re.sub(r"\x1b\[[0-?]*[ -/]*[@-~]", "", text)
|
|
3799
|
+
text = re.sub(r"(?i)\b(Bearer\s+)[^\s]+", r"\1[REDACTED]", text)
|
|
3800
|
+
text = re.sub(r"(?i)(https?://)[^/\s:@]+:[^@\s/]+@", r"\1[REDACTED]@", text)
|
|
3801
|
+
text = re.sub(r"([?&][^=\s&]+)=([^&\s]+)", r"\1=[REDACTED]", text)
|
|
3802
|
+
text = re.sub(
|
|
3803
|
+
r"(?i)\b([A-Za-z0-9_-]*(?:token|secret|password|api[_-]?key|authorization)[A-Za-z0-9_-]*)\s*([=:])\s*[^\s]+",
|
|
3804
|
+
r"\1\2[REDACTED]",
|
|
3805
|
+
text,
|
|
3806
|
+
)
|
|
3807
|
+
sensitive_names = ("TOKEN", "SECRET", "PASSWORD", "API_KEY", "AUTHORIZATION")
|
|
3808
|
+
for key, value in os.environ.items():
|
|
3809
|
+
if len(value) >= 8 and any(name in key.upper() for name in sensitive_names):
|
|
3810
|
+
text = text.replace(value, "[REDACTED]")
|
|
3811
|
+
private_paths = []
|
|
3812
|
+
if hermes_home:
|
|
3813
|
+
private_paths.append((str(hermes_home), "$HERMES_HOME"))
|
|
3814
|
+
private_paths.append((str(Path.home()), "~"))
|
|
3815
|
+
for path_text, replacement in private_paths:
|
|
3816
|
+
if len(path_text) > 1:
|
|
3817
|
+
text = text.replace(path_text, replacement)
|
|
3818
|
+
text = "".join(char if char in "\n\t" or ord(char) >= 32 else "?" for char in text)
|
|
3819
|
+
lines = [line.rstrip() for line in text.splitlines() if line.strip()]
|
|
3820
|
+
text = "\n".join(lines[-_INLINE_UPDATE_LOG_MAX_LINES:])
|
|
3821
|
+
if len(text) > _INLINE_UPDATE_LOG_MAX_CHARS:
|
|
3822
|
+
text = "[earlier output truncated]\n" + text[-_INLINE_UPDATE_LOG_MAX_CHARS:]
|
|
3823
|
+
return text or "(no subprocess output)"
|
|
3824
|
+
|
|
3825
|
+
|
|
3826
|
+
def _log_inline_update_failure(
|
|
3827
|
+
stage: str,
|
|
3828
|
+
detail: str,
|
|
3829
|
+
*,
|
|
3830
|
+
output: Any = None,
|
|
3831
|
+
hermes_home: Optional[Path] = None,
|
|
3832
|
+
) -> None:
|
|
3833
|
+
diagnostic = f"{detail}\n{output or ''}".strip()
|
|
3834
|
+
logger.error(
|
|
3835
|
+
"[inline-update] stage=%s failed\n%s",
|
|
3836
|
+
stage,
|
|
3837
|
+
_inline_update_log_text(diagnostic, hermes_home),
|
|
3838
|
+
)
|
|
3839
|
+
|
|
3840
|
+
|
|
3708
3841
|
def _installed_inline_plugin_version(hermes_home: Path) -> Optional[str]:
|
|
3709
3842
|
manifest = hermes_home / "plugins" / "inline" / "plugin.yaml"
|
|
3710
3843
|
try:
|
|
@@ -3741,11 +3874,12 @@ def _run_inline_update() -> str:
|
|
|
3741
3874
|
|
|
3742
3875
|
|
|
3743
3876
|
def _run_inline_update_locked() -> str:
|
|
3877
|
+
hermes_home = Path(os.getenv("HERMES_HOME") or Path.home() / ".hermes").expanduser()
|
|
3744
3878
|
npm_bin = shutil.which("npm")
|
|
3745
3879
|
if not npm_bin:
|
|
3746
|
-
|
|
3880
|
+
_log_inline_update_failure("precheck", "npm was not found on PATH", hermes_home=hermes_home)
|
|
3881
|
+
return "Inline plugin update is unavailable because npm was not found on PATH. Details were written to Hermes logs under `[inline-update]`."
|
|
3747
3882
|
|
|
3748
|
-
hermes_home = Path(os.getenv("HERMES_HOME") or Path.home() / ".hermes").expanduser()
|
|
3749
3883
|
target = hermes_home / "plugins" / "inline"
|
|
3750
3884
|
if target.is_symlink():
|
|
3751
3885
|
return (
|
|
@@ -3772,16 +3906,27 @@ def _run_inline_update_locked() -> str:
|
|
|
3772
3906
|
check=False,
|
|
3773
3907
|
env=_inline_update_environment(),
|
|
3774
3908
|
)
|
|
3775
|
-
except subprocess.TimeoutExpired:
|
|
3776
|
-
|
|
3909
|
+
except subprocess.TimeoutExpired as exc:
|
|
3910
|
+
_log_inline_update_failure(
|
|
3911
|
+
"precheck",
|
|
3912
|
+
"npm view timed out",
|
|
3913
|
+
output=f"{exc.stdout or ''}\n{exc.stderr or ''}",
|
|
3914
|
+
hermes_home=hermes_home,
|
|
3915
|
+
)
|
|
3916
|
+
return "Inline plugin compatibility precheck timed out; no update was applied. Details were written to Hermes logs under `[inline-update]`."
|
|
3777
3917
|
except OSError as exc:
|
|
3778
|
-
|
|
3779
|
-
return "Inline plugin compatibility precheck could not start; no update was applied."
|
|
3918
|
+
_log_inline_update_failure("precheck", f"npm view could not start: {exc}", hermes_home=hermes_home)
|
|
3919
|
+
return "Inline plugin compatibility precheck could not start; no update was applied. Details were written to Hermes logs under `[inline-update]`."
|
|
3780
3920
|
if precheck.returncode != 0:
|
|
3781
|
-
|
|
3921
|
+
_log_inline_update_failure(
|
|
3922
|
+
"precheck",
|
|
3923
|
+
f"npm view exited with code {precheck.returncode} for {package_spec}",
|
|
3924
|
+
output=f"{precheck.stdout or ''}\n{precheck.stderr or ''}",
|
|
3925
|
+
hermes_home=hermes_home,
|
|
3926
|
+
)
|
|
3782
3927
|
return (
|
|
3783
3928
|
f"Inline plugin compatibility precheck failed with exit code {precheck.returncode}; "
|
|
3784
|
-
"no update was applied."
|
|
3929
|
+
"no update was applied. Details were written to Hermes logs under `[inline-update]`."
|
|
3785
3930
|
)
|
|
3786
3931
|
try:
|
|
3787
3932
|
package_metadata = json.loads(precheck.stdout or "{}")
|
|
@@ -3797,9 +3942,15 @@ def _run_inline_update_locked() -> str:
|
|
|
3797
3942
|
current_core = _semver_core(current_hermes)
|
|
3798
3943
|
minimum_core = _semver_core(minimum_hermes)
|
|
3799
3944
|
if not candidate_version or not current_core or not minimum_core:
|
|
3945
|
+
_log_inline_update_failure(
|
|
3946
|
+
"precheck",
|
|
3947
|
+
f"invalid compatibility metadata for {package_spec}; current Hermes version={current_hermes!r}",
|
|
3948
|
+
output=precheck.stdout,
|
|
3949
|
+
hermes_home=hermes_home,
|
|
3950
|
+
)
|
|
3800
3951
|
return (
|
|
3801
3952
|
"Inline plugin compatibility metadata could not be verified; "
|
|
3802
|
-
"no update was applied."
|
|
3953
|
+
"no update was applied. Details were written to Hermes logs under `[inline-update]`."
|
|
3803
3954
|
)
|
|
3804
3955
|
if current_core < minimum_core:
|
|
3805
3956
|
return (
|
|
@@ -3829,22 +3980,33 @@ def _run_inline_update_locked() -> str:
|
|
|
3829
3980
|
check=False,
|
|
3830
3981
|
env=_inline_update_environment(),
|
|
3831
3982
|
)
|
|
3832
|
-
except subprocess.TimeoutExpired:
|
|
3983
|
+
except subprocess.TimeoutExpired as exc:
|
|
3984
|
+
_log_inline_update_failure(
|
|
3985
|
+
"install",
|
|
3986
|
+
f"npm exec timed out for {package_spec}",
|
|
3987
|
+
output=exc.stdout,
|
|
3988
|
+
hermes_home=hermes_home,
|
|
3989
|
+
)
|
|
3833
3990
|
return (
|
|
3834
3991
|
"Inline plugin update timed out. Run "
|
|
3835
3992
|
f"`npm exec --yes --package={package_spec} -- "
|
|
3836
|
-
"inline-hermes install --force` on the Hermes host."
|
|
3993
|
+
"inline-hermes install --force` on the Hermes host. Details were written to Hermes logs under `[inline-update]`."
|
|
3837
3994
|
)
|
|
3838
3995
|
except OSError as exc:
|
|
3839
|
-
|
|
3840
|
-
return "Inline plugin update could not start. Check that npm is installed and usable on the Hermes host."
|
|
3996
|
+
_log_inline_update_failure("install", f"npm exec could not start: {exc}", hermes_home=hermes_home)
|
|
3997
|
+
return "Inline plugin update could not start. Check that npm is installed and usable on the Hermes host. Details were written to Hermes logs under `[inline-update]`."
|
|
3841
3998
|
|
|
3842
3999
|
if result.returncode != 0:
|
|
3843
|
-
|
|
4000
|
+
_log_inline_update_failure(
|
|
4001
|
+
"install",
|
|
4002
|
+
f"npm exec exited with code {result.returncode} for {package_spec}",
|
|
4003
|
+
output=result.stdout,
|
|
4004
|
+
hermes_home=hermes_home,
|
|
4005
|
+
)
|
|
3844
4006
|
return (
|
|
3845
4007
|
f"Inline plugin update failed with exit code {result.returncode}. Run "
|
|
3846
4008
|
f"`npm exec --yes --package={package_spec} -- "
|
|
3847
|
-
"inline-hermes install --force` on the Hermes host."
|
|
4009
|
+
"inline-hermes install --force` on the Hermes host. Details were written to Hermes logs under `[inline-update]`."
|
|
3848
4010
|
)
|
|
3849
4011
|
|
|
3850
4012
|
version = _installed_inline_plugin_version(hermes_home)
|
|
@@ -3858,6 +4020,32 @@ async def _inline_update_command_handler(raw_args: str = "") -> str:
|
|
|
3858
4020
|
return await asyncio.to_thread(_run_inline_update)
|
|
3859
4021
|
|
|
3860
4022
|
|
|
4023
|
+
def _inline_command_specs() -> tuple[_InlineCommandSpec, ...]:
|
|
4024
|
+
return (
|
|
4025
|
+
_InlineCommandSpec(
|
|
4026
|
+
name="threads",
|
|
4027
|
+
handler=_inline_threads_command_handler,
|
|
4028
|
+
description=_INLINE_THREADS_COMMAND_DESCRIPTION,
|
|
4029
|
+
args_hint=_INLINE_THREADS_COMMAND_ARGS,
|
|
4030
|
+
),
|
|
4031
|
+
_InlineCommandSpec(
|
|
4032
|
+
name="follow",
|
|
4033
|
+
handler=_inline_follow_command_handler,
|
|
4034
|
+
description=_INLINE_FOLLOW_COMMAND_DESCRIPTION,
|
|
4035
|
+
),
|
|
4036
|
+
_InlineCommandSpec(
|
|
4037
|
+
name="unfollow",
|
|
4038
|
+
handler=_inline_unfollow_command_handler,
|
|
4039
|
+
description=_INLINE_UNFOLLOW_COMMAND_DESCRIPTION,
|
|
4040
|
+
),
|
|
4041
|
+
_InlineCommandSpec(
|
|
4042
|
+
name="inline-update",
|
|
4043
|
+
handler=_inline_update_command_handler,
|
|
4044
|
+
description=_INLINE_UPDATE_COMMAND_DESCRIPTION,
|
|
4045
|
+
),
|
|
4046
|
+
)
|
|
4047
|
+
|
|
4048
|
+
|
|
3861
4049
|
def register(ctx) -> None:
|
|
3862
4050
|
from . import cli as _cli
|
|
3863
4051
|
from . import tools as _tools
|
|
@@ -3895,17 +4083,13 @@ def register(ctx) -> None:
|
|
|
3895
4083
|
)
|
|
3896
4084
|
register_command = getattr(ctx, "register_command", None)
|
|
3897
4085
|
if callable(register_command):
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
"inline-update",
|
|
3906
|
-
handler=_inline_update_command_handler,
|
|
3907
|
-
description=_INLINE_UPDATE_COMMAND_DESCRIPTION,
|
|
3908
|
-
)
|
|
4086
|
+
for spec in _inline_command_specs():
|
|
4087
|
+
register_command(
|
|
4088
|
+
spec.name,
|
|
4089
|
+
handler=spec.handler,
|
|
4090
|
+
description=spec.description,
|
|
4091
|
+
args_hint=spec.args_hint,
|
|
4092
|
+
)
|
|
3909
4093
|
ctx.register_cli_command(
|
|
3910
4094
|
name="inline",
|
|
3911
4095
|
help="Set up and inspect the Inline integration",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
name: inline-platform
|
|
2
2
|
label: Inline
|
|
3
3
|
kind: platform
|
|
4
|
-
version: 0.0.5-alpha.
|
|
4
|
+
version: 0.0.5-alpha.5
|
|
5
5
|
description: >
|
|
6
6
|
Inline platform adapter for Hermes Agent. The adapter runs as a native
|
|
7
7
|
Hermes Python platform plugin and supervises a local Node sidecar that uses
|
|
@@ -24160,6 +24160,7 @@ function defaultErrorText(error) {
|
|
|
24160
24160
|
}
|
|
24161
24161
|
|
|
24162
24162
|
// src/sidecar/index.ts
|
|
24163
|
+
var DIALOG_FOLLOW_MODE_UNFOLLOWED = 2;
|
|
24163
24164
|
var token = process.env.INLINE_TOKEN || process.env.INLINE_BOT_TOKEN || "";
|
|
24164
24165
|
var baseUrl = process.env.INLINE_BASE_URL || "https://api.inline.chat";
|
|
24165
24166
|
var sidecarToken = process.env.INLINE_SIDECAR_TOKEN || "";
|
|
@@ -24307,6 +24308,9 @@ async function handleRequest(req, res) {
|
|
|
24307
24308
|
case "/chat":
|
|
24308
24309
|
await endpointChat(res, body);
|
|
24309
24310
|
return;
|
|
24311
|
+
case "/follow-mode":
|
|
24312
|
+
await endpointFollowMode(res, body);
|
|
24313
|
+
return;
|
|
24310
24314
|
case "/messages":
|
|
24311
24315
|
await endpointMessages(res, body);
|
|
24312
24316
|
return;
|
|
@@ -24523,6 +24527,23 @@ async function endpointChat(res, body) {
|
|
|
24523
24527
|
}
|
|
24524
24528
|
});
|
|
24525
24529
|
}
|
|
24530
|
+
async function endpointFollowMode(res, body) {
|
|
24531
|
+
const record = asRecord(body);
|
|
24532
|
+
const target = parseTarget(record);
|
|
24533
|
+
const mode = readRequiredString(record, "mode");
|
|
24534
|
+
const followMode = mode === "following" ? DialogFollowMode.FOLLOWING : mode === "unfollowed" ? DIALOG_FOLLOW_MODE_UNFOLLOWED : null;
|
|
24535
|
+
if (followMode == null) {
|
|
24536
|
+
throw new SidecarError("mode must be following or unfollowed", "bad_format");
|
|
24537
|
+
}
|
|
24538
|
+
await client.invokeUncheckedRaw(Method.UPDATE_DIALOG_FOLLOW_MODE, {
|
|
24539
|
+
oneofKind: "updateDialogFollowMode",
|
|
24540
|
+
updateDialogFollowMode: {
|
|
24541
|
+
peerId: inputPeerFromTarget(target),
|
|
24542
|
+
followMode
|
|
24543
|
+
}
|
|
24544
|
+
});
|
|
24545
|
+
writeJson(res, 200, { ok: true, result: { mode } });
|
|
24546
|
+
}
|
|
24526
24547
|
async function endpointMessages(res, body) {
|
|
24527
24548
|
const record = asRecord(body);
|
|
24528
24549
|
const target = parseTarget(record);
|