@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 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
- central slash-command registry, normalizes names to Inline Bot API constraints
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.4",
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",
@@ -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
- local_commands = list(_INLINE_LOCAL_COMMANDS[:max(0, max_commands)])
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(_INLINE_LOCAL_COMMANDS) - len(local_commands))
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
- return "Inline plugin update is unavailable because npm was not found on PATH."
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
- return "Inline plugin compatibility precheck timed out; no update was applied."
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
- logger.warning("[inline] plugin compatibility precheck failed to start: %s", exc)
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
- logger.warning("[inline] plugin compatibility precheck exited with code %s", precheck.returncode)
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
- logger.warning("[inline] plugin update failed to start: %s", exc)
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
- logger.warning("[inline] plugin update exited with code %s", result.returncode)
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
- register_command(
3899
- "threads",
3900
- handler=_inline_threads_command_handler,
3901
- description=_INLINE_THREADS_COMMAND_DESCRIPTION,
3902
- args_hint=_INLINE_THREADS_COMMAND_ARGS,
3903
- )
3904
- register_command(
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
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);