@inline-chat/hermes-agent-adapter 0.0.18-alpha.0 → 0.0.19
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 +24 -20
- package/dist/install.js +26 -8
- package/package.json +7 -5
- package/plugin/inline/LICENSE +201 -0
- package/plugin/inline/README.md +56 -0
- package/plugin/inline/adapter.py +18 -272
- package/plugin/inline/cli.py +48 -3
- package/plugin/inline/plugin.yaml +4 -1
- package/plugin/inline/sidecar/index.mjs +84 -1325
package/plugin/inline/adapter.py
CHANGED
|
@@ -22,7 +22,6 @@ import signal
|
|
|
22
22
|
import socket
|
|
23
23
|
import subprocess
|
|
24
24
|
import sys
|
|
25
|
-
import threading
|
|
26
25
|
import time
|
|
27
26
|
from collections import OrderedDict
|
|
28
27
|
from datetime import datetime, timezone
|
|
@@ -45,6 +44,7 @@ from gateway.platforms.base import (
|
|
|
45
44
|
SendResult,
|
|
46
45
|
cache_audio_from_url,
|
|
47
46
|
cache_image_from_url,
|
|
47
|
+
safe_url_for_log,
|
|
48
48
|
)
|
|
49
49
|
from gateway.platforms.helpers import strip_markdown
|
|
50
50
|
|
|
@@ -106,15 +106,8 @@ _INLINE_THREADS_COMMAND_DESCRIPTION = "Configure Inline reply-thread routing"
|
|
|
106
106
|
_INLINE_THREADS_COMMAND_ARGS = "[status|on|off|auto|reset]"
|
|
107
107
|
_INLINE_FOLLOW_COMMAND_DESCRIPTION = "Explicitly follow this Inline chat or thread"
|
|
108
108
|
_INLINE_UNFOLLOW_COMMAND_DESCRIPTION = "Explicitly unfollow this Inline chat or thread"
|
|
109
|
-
_INLINE_UPDATE_COMMAND_DESCRIPTION = "Update the Inline Hermes plugin"
|
|
110
109
|
_INLINE_SYNC_COMMAND_DESCRIPTION = "Resync Inline commands and skills"
|
|
111
110
|
_INLINE_VERSION_COMMAND_DESCRIPTION = "Show Inline plugin and sync information"
|
|
112
|
-
_INLINE_UPDATE_PACKAGE_NAME = "@inline-chat/hermes-agent-adapter"
|
|
113
|
-
_INLINE_UPDATE_PRECHECK_TIMEOUT_SECONDS = 30
|
|
114
|
-
_INLINE_UPDATE_TIMEOUT_SECONDS = 5 * 60
|
|
115
|
-
_INLINE_UPDATE_LOG_MAX_LINES = 80
|
|
116
|
-
_INLINE_UPDATE_LOG_MAX_CHARS = 8_000
|
|
117
|
-
_INLINE_UPDATE_LOCK = threading.Lock()
|
|
118
111
|
_INLINE_THREADS_ACTION_PREFIX = "th:"
|
|
119
112
|
_INLINE_THREADS_ACTION_TTL_SECONDS = 15 * 60
|
|
120
113
|
_INLINE_THREAD_COMMAND_RE = re.compile(r"^/(?:thread|threads)(?:@[A-Za-z0-9_]+)?(?:\s+(.*))?$", re.IGNORECASE)
|
|
@@ -575,7 +568,7 @@ def _normalize_inline_command_description(raw: str) -> str:
|
|
|
575
568
|
|
|
576
569
|
|
|
577
570
|
def _inline_menu_commands(max_commands: int = _INLINE_COMMAND_LIMIT) -> tuple[List[Dict[str, Any]], int]:
|
|
578
|
-
from hermes_cli.
|
|
571
|
+
from hermes_cli.commands_platforms import telegram_menu_commands
|
|
579
572
|
|
|
580
573
|
command_specs = _inline_command_specs()
|
|
581
574
|
local_commands = [
|
|
@@ -1209,7 +1202,7 @@ class InlineAdapter(BasePlatformAdapter):
|
|
|
1209
1202
|
|
|
1210
1203
|
try:
|
|
1211
1204
|
from gateway.run import _load_gateway_config
|
|
1212
|
-
from hermes_cli.
|
|
1205
|
+
from hermes_cli.model_switch_providers import list_picker_providers
|
|
1213
1206
|
|
|
1214
1207
|
cfg = _load_gateway_config() or {}
|
|
1215
1208
|
model_cfg = cfg.get("model") if isinstance(cfg, dict) else {}
|
|
@@ -3001,16 +2994,15 @@ class InlineAdapter(BasePlatformAdapter):
|
|
|
3001
2994
|
return await cache_audio_from_url(url, ext=_extension_for_media(mime, file_name, ".ogg"))
|
|
3002
2995
|
return await self._download_inline_media_url(url, mime=mime, file_name=file_name)
|
|
3003
2996
|
except Exception as exc:
|
|
3004
|
-
logger.warning("[inline] failed to cache %s attachment
|
|
3005
|
-
|
|
2997
|
+
logger.warning("[inline] failed to cache %s attachment (%s)", kind, type(exc).__name__)
|
|
2998
|
+
# Never pass an unvalidated URL on to Hermes for another fetch.
|
|
2999
|
+
return None
|
|
3006
3000
|
|
|
3007
3001
|
async def _download_inline_media_url(self, url: str, *, mime: str, file_name: Optional[str]) -> str:
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
except ImportError:
|
|
3013
|
-
pass
|
|
3002
|
+
# A missing host safety helper must stop the download, never bypass it.
|
|
3003
|
+
from tools.url_safety import is_safe_url
|
|
3004
|
+
if not is_safe_url(url):
|
|
3005
|
+
raise ValueError(f"blocked unsafe media URL: {safe_url_for_log(url)}")
|
|
3014
3006
|
_MEDIA_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
3015
3007
|
name = _safe_media_file_name(url=url, mime=mime, file_name=file_name)
|
|
3016
3008
|
path = _MEDIA_CACHE_DIR / name
|
|
@@ -4230,7 +4222,7 @@ class InlineAdapter(BasePlatformAdapter):
|
|
|
4230
4222
|
label = group_id
|
|
4231
4223
|
member_slugs: list[str] = []
|
|
4232
4224
|
try:
|
|
4233
|
-
from hermes_cli.
|
|
4225
|
+
from hermes_cli.models_catalog_static import PROVIDER_GROUPS
|
|
4234
4226
|
label, _desc, members = PROVIDER_GROUPS.get(group_id, (group_id, "", []))
|
|
4235
4227
|
member_slugs = [str(member) for member in members]
|
|
4236
4228
|
except Exception:
|
|
@@ -4686,7 +4678,6 @@ class InlineAdapter(BasePlatformAdapter):
|
|
|
4686
4678
|
|
|
4687
4679
|
async def send_image(self, chat_id: str, image_url: str, caption: Optional[str] = None, reply_to: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> SendResult:
|
|
4688
4680
|
try:
|
|
4689
|
-
from gateway.platforms.base import cache_image_from_url
|
|
4690
4681
|
local_path = await cache_image_from_url(image_url)
|
|
4691
4682
|
return await self.send_image_file(chat_id, local_path, caption, reply_to, metadata)
|
|
4692
4683
|
except Exception:
|
|
@@ -4694,7 +4685,6 @@ class InlineAdapter(BasePlatformAdapter):
|
|
|
4694
4685
|
|
|
4695
4686
|
async def send_animation(self, chat_id: str, animation_url: str, caption: Optional[str] = None, reply_to: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> SendResult:
|
|
4696
4687
|
try:
|
|
4697
|
-
from gateway.platforms.base import cache_image_from_url
|
|
4698
4688
|
local_path = await cache_image_from_url(animation_url, ext=".gif")
|
|
4699
4689
|
return await self.send_document(chat_id, local_path, caption, file_name=Path(local_path).name, reply_to=reply_to, metadata=metadata)
|
|
4700
4690
|
except Exception:
|
|
@@ -4982,7 +4972,7 @@ class InlineAdapter(BasePlatformAdapter):
|
|
|
4982
4972
|
}
|
|
4983
4973
|
actions: list[Dict[str, str]] = []
|
|
4984
4974
|
try:
|
|
4985
|
-
from hermes_cli.
|
|
4975
|
+
from hermes_cli.models_catalog_static import group_providers
|
|
4986
4976
|
grouped = group_providers(list(by_slug.keys()))
|
|
4987
4977
|
except Exception:
|
|
4988
4978
|
grouped = [{"kind": "single", "slug": slug} for slug in by_slug.keys()]
|
|
@@ -5319,67 +5309,8 @@ def _inline_unfollow_command_handler(raw_args: str = "") -> str:
|
|
|
5319
5309
|
return _inline_follow_command_fallback("unfollow", raw_args)
|
|
5320
5310
|
|
|
5321
5311
|
|
|
5322
|
-
def
|
|
5323
|
-
|
|
5324
|
-
from tools.environments.local import _sanitize_subprocess_env
|
|
5325
|
-
env = _sanitize_subprocess_env(os.environ.copy())
|
|
5326
|
-
except Exception:
|
|
5327
|
-
env = os.environ.copy()
|
|
5328
|
-
# Hermes cannot know the secret names introduced by every external plugin.
|
|
5329
|
-
sensitive_names = ("TOKEN", "SECRET", "PASSWORD", "API_KEY", "AUTHORIZATION")
|
|
5330
|
-
for key in list(env):
|
|
5331
|
-
if any(name in key.upper() for name in sensitive_names):
|
|
5332
|
-
env.pop(key, None)
|
|
5333
|
-
return env
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
def _inline_update_log_text(raw: Any, hermes_home: Optional[Path] = None) -> str:
|
|
5337
|
-
text = str(raw or "")
|
|
5338
|
-
text = re.sub(r"\x1b\[[0-?]*[ -/]*[@-~]", "", text)
|
|
5339
|
-
text = re.sub(r"(?i)\b(Bearer\s+)[^\s]+", r"\1[REDACTED]", text)
|
|
5340
|
-
text = re.sub(r"(?i)(https?://)[^/\s:@]+:[^@\s/]+@", r"\1[REDACTED]@", text)
|
|
5341
|
-
text = re.sub(r"([?&][^=\s&]+)=([^&\s]+)", r"\1=[REDACTED]", text)
|
|
5342
|
-
text = re.sub(
|
|
5343
|
-
r"(?i)\b([A-Za-z0-9_-]*(?:token|secret|password|api[_-]?key|authorization)[A-Za-z0-9_-]*)\s*([=:])\s*[^\s]+",
|
|
5344
|
-
r"\1\2[REDACTED]",
|
|
5345
|
-
text,
|
|
5346
|
-
)
|
|
5347
|
-
sensitive_names = ("TOKEN", "SECRET", "PASSWORD", "API_KEY", "AUTHORIZATION")
|
|
5348
|
-
for key, value in os.environ.items():
|
|
5349
|
-
if len(value) >= 8 and any(name in key.upper() for name in sensitive_names):
|
|
5350
|
-
text = text.replace(value, "[REDACTED]")
|
|
5351
|
-
private_paths = []
|
|
5352
|
-
if hermes_home:
|
|
5353
|
-
private_paths.append((str(hermes_home), "$HERMES_HOME"))
|
|
5354
|
-
private_paths.append((str(Path.home()), "~"))
|
|
5355
|
-
for path_text, replacement in private_paths:
|
|
5356
|
-
if len(path_text) > 1:
|
|
5357
|
-
text = text.replace(path_text, replacement)
|
|
5358
|
-
text = "".join(char if char in "\n\t" or ord(char) >= 32 else "?" for char in text)
|
|
5359
|
-
lines = [line.rstrip() for line in text.splitlines() if line.strip()]
|
|
5360
|
-
text = "\n".join(lines[-_INLINE_UPDATE_LOG_MAX_LINES:])
|
|
5361
|
-
if len(text) > _INLINE_UPDATE_LOG_MAX_CHARS:
|
|
5362
|
-
text = "[earlier output truncated]\n" + text[-_INLINE_UPDATE_LOG_MAX_CHARS:]
|
|
5363
|
-
return text or "(no subprocess output)"
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
def _log_inline_update_failure(
|
|
5367
|
-
stage: str,
|
|
5368
|
-
detail: str,
|
|
5369
|
-
*,
|
|
5370
|
-
output: Any = None,
|
|
5371
|
-
hermes_home: Optional[Path] = None,
|
|
5372
|
-
) -> None:
|
|
5373
|
-
diagnostic = f"{detail}\n{output or ''}".strip()
|
|
5374
|
-
logger.error(
|
|
5375
|
-
"[inline-update] stage=%s failed\n%s",
|
|
5376
|
-
stage,
|
|
5377
|
-
_inline_update_log_text(diagnostic, hermes_home),
|
|
5378
|
-
)
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
def _installed_inline_plugin_version(hermes_home: Path) -> Optional[str]:
|
|
5382
|
-
manifest = hermes_home / "plugins" / "inline" / "plugin.yaml"
|
|
5312
|
+
def _installed_inline_plugin_version() -> Optional[str]:
|
|
5313
|
+
manifest = Path(__file__).resolve().with_name("plugin.yaml")
|
|
5383
5314
|
try:
|
|
5384
5315
|
text = manifest.read_text(encoding="utf-8")
|
|
5385
5316
|
except OSError:
|
|
@@ -5388,8 +5319,8 @@ def _installed_inline_plugin_version(hermes_home: Path) -> Optional[str]:
|
|
|
5388
5319
|
return match.group(1) if match else None
|
|
5389
5320
|
|
|
5390
5321
|
|
|
5391
|
-
def _inline_install_timestamp(
|
|
5392
|
-
target =
|
|
5322
|
+
def _inline_install_timestamp() -> Optional[str]:
|
|
5323
|
+
target = Path(__file__).resolve().parent
|
|
5393
5324
|
try:
|
|
5394
5325
|
timestamp = target.lstat().st_ctime
|
|
5395
5326
|
except OSError:
|
|
@@ -5406,21 +5337,13 @@ def _inline_catalog_part_summary(part: Dict[str, Any]) -> str:
|
|
|
5406
5337
|
|
|
5407
5338
|
|
|
5408
5339
|
def _inline_version_text(last_sync: Optional[Dict[str, Any]] = None) -> str:
|
|
5409
|
-
|
|
5410
|
-
version = _installed_inline_plugin_version(hermes_home)
|
|
5411
|
-
if not version:
|
|
5412
|
-
try:
|
|
5413
|
-
manifest = Path(__file__).with_name("plugin.yaml").read_text(encoding="utf-8")
|
|
5414
|
-
except OSError:
|
|
5415
|
-
manifest = ""
|
|
5416
|
-
match = re.search(r"(?m)^version:\s*['\"]?([^\s'\"]+)", manifest)
|
|
5417
|
-
version = match.group(1) if match else None
|
|
5340
|
+
version = _installed_inline_plugin_version()
|
|
5418
5341
|
try:
|
|
5419
5342
|
from hermes_cli import __version__ as hermes_version
|
|
5420
5343
|
except Exception:
|
|
5421
5344
|
hermes_version = None
|
|
5422
5345
|
|
|
5423
|
-
installed_at = _inline_install_timestamp(
|
|
5346
|
+
installed_at = _inline_install_timestamp()
|
|
5424
5347
|
if last_sync:
|
|
5425
5348
|
sync_text = (
|
|
5426
5349
|
f"{last_sync.get('completed_at') or 'unknown'} "
|
|
@@ -5439,178 +5362,6 @@ def _inline_version_text(last_sync: Optional[Dict[str, Any]] = None) -> str:
|
|
|
5439
5362
|
])
|
|
5440
5363
|
|
|
5441
5364
|
|
|
5442
|
-
def _inline_update_lane(version: Optional[str]) -> Optional[str]:
|
|
5443
|
-
if not version:
|
|
5444
|
-
return None
|
|
5445
|
-
match = re.fullmatch(r"\d+\.\d+\.\d+(?:-([A-Za-z][A-Za-z0-9-]*)(?:\.[0-9A-Za-z-]+)*)?", version)
|
|
5446
|
-
if not match:
|
|
5447
|
-
return None
|
|
5448
|
-
return match.group(1) or "latest"
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
def _semver_core(version: Any) -> Optional[tuple[int, int, int]]:
|
|
5452
|
-
match = re.match(r"^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$", str(version or "").strip())
|
|
5453
|
-
if not match:
|
|
5454
|
-
return None
|
|
5455
|
-
return tuple(int(part) for part in match.groups())
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
def _run_inline_update() -> str:
|
|
5459
|
-
if not _INLINE_UPDATE_LOCK.acquire(blocking=False):
|
|
5460
|
-
return "An Inline plugin update is already running."
|
|
5461
|
-
try:
|
|
5462
|
-
return _run_inline_update_locked()
|
|
5463
|
-
finally:
|
|
5464
|
-
_INLINE_UPDATE_LOCK.release()
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
def _run_inline_update_locked() -> str:
|
|
5468
|
-
hermes_home = Path(os.getenv("HERMES_HOME") or Path.home() / ".hermes").expanduser()
|
|
5469
|
-
npm_bin = shutil.which("npm")
|
|
5470
|
-
if not npm_bin:
|
|
5471
|
-
_log_inline_update_failure("precheck", "npm was not found on PATH", hermes_home=hermes_home)
|
|
5472
|
-
return "Inline plugin update is unavailable because npm was not found on PATH. Details were written to Hermes logs under `[inline-update]`."
|
|
5473
|
-
|
|
5474
|
-
target = hermes_home / "plugins" / "inline"
|
|
5475
|
-
if target.is_symlink():
|
|
5476
|
-
return (
|
|
5477
|
-
"Inline is installed as a development symlink, so automatic update was skipped. "
|
|
5478
|
-
"Update the linked source checkout and restart Hermes instead."
|
|
5479
|
-
)
|
|
5480
|
-
|
|
5481
|
-
installed_version = _installed_inline_plugin_version(hermes_home)
|
|
5482
|
-
lane = _inline_update_lane(installed_version)
|
|
5483
|
-
if not lane:
|
|
5484
|
-
return (
|
|
5485
|
-
"Inline plugin update could not determine the installed release channel. "
|
|
5486
|
-
"Update it manually with the package version or npm dist-tag you want to follow."
|
|
5487
|
-
)
|
|
5488
|
-
package_spec = f"{_INLINE_UPDATE_PACKAGE_NAME}@{lane}"
|
|
5489
|
-
precheck_command = [npm_bin, "view", package_spec, "--json"]
|
|
5490
|
-
try:
|
|
5491
|
-
precheck = subprocess.run(
|
|
5492
|
-
precheck_command,
|
|
5493
|
-
stdout=subprocess.PIPE,
|
|
5494
|
-
stderr=subprocess.PIPE,
|
|
5495
|
-
text=True,
|
|
5496
|
-
timeout=_INLINE_UPDATE_PRECHECK_TIMEOUT_SECONDS,
|
|
5497
|
-
check=False,
|
|
5498
|
-
env=_inline_update_environment(),
|
|
5499
|
-
)
|
|
5500
|
-
except subprocess.TimeoutExpired as exc:
|
|
5501
|
-
_log_inline_update_failure(
|
|
5502
|
-
"precheck",
|
|
5503
|
-
"npm view timed out",
|
|
5504
|
-
output=f"{exc.stdout or ''}\n{exc.stderr or ''}",
|
|
5505
|
-
hermes_home=hermes_home,
|
|
5506
|
-
)
|
|
5507
|
-
return "Inline plugin compatibility precheck timed out; no update was applied. Details were written to Hermes logs under `[inline-update]`."
|
|
5508
|
-
except OSError as exc:
|
|
5509
|
-
_log_inline_update_failure("precheck", f"npm view could not start: {exc}", hermes_home=hermes_home)
|
|
5510
|
-
return "Inline plugin compatibility precheck could not start; no update was applied. Details were written to Hermes logs under `[inline-update]`."
|
|
5511
|
-
if precheck.returncode != 0:
|
|
5512
|
-
_log_inline_update_failure(
|
|
5513
|
-
"precheck",
|
|
5514
|
-
f"npm view exited with code {precheck.returncode} for {package_spec}",
|
|
5515
|
-
output=f"{precheck.stdout or ''}\n{precheck.stderr or ''}",
|
|
5516
|
-
hermes_home=hermes_home,
|
|
5517
|
-
)
|
|
5518
|
-
return (
|
|
5519
|
-
f"Inline plugin compatibility precheck failed with exit code {precheck.returncode}; "
|
|
5520
|
-
"no update was applied. Details were written to Hermes logs under `[inline-update]`."
|
|
5521
|
-
)
|
|
5522
|
-
try:
|
|
5523
|
-
package_metadata = json.loads(precheck.stdout or "{}")
|
|
5524
|
-
except (TypeError, json.JSONDecodeError):
|
|
5525
|
-
package_metadata = None
|
|
5526
|
-
inline_metadata = package_metadata.get("inlineHermes") if isinstance(package_metadata, dict) else None
|
|
5527
|
-
candidate_version = package_metadata.get("version") if isinstance(package_metadata, dict) else None
|
|
5528
|
-
minimum_hermes = inline_metadata.get("minHermesVersion") if isinstance(inline_metadata, dict) else None
|
|
5529
|
-
try:
|
|
5530
|
-
from hermes_cli import __version__ as current_hermes
|
|
5531
|
-
except Exception:
|
|
5532
|
-
current_hermes = None
|
|
5533
|
-
current_core = _semver_core(current_hermes)
|
|
5534
|
-
minimum_core = _semver_core(minimum_hermes)
|
|
5535
|
-
if not candidate_version or not current_core or not minimum_core:
|
|
5536
|
-
_log_inline_update_failure(
|
|
5537
|
-
"precheck",
|
|
5538
|
-
f"invalid compatibility metadata for {package_spec}; current Hermes version={current_hermes!r}",
|
|
5539
|
-
output=precheck.stdout,
|
|
5540
|
-
hermes_home=hermes_home,
|
|
5541
|
-
)
|
|
5542
|
-
return (
|
|
5543
|
-
"Inline plugin compatibility metadata could not be verified; "
|
|
5544
|
-
"no update was applied. Details were written to Hermes logs under `[inline-update]`."
|
|
5545
|
-
)
|
|
5546
|
-
if current_core < minimum_core:
|
|
5547
|
-
return (
|
|
5548
|
-
f"Inline plugin `{candidate_version}` requires Hermes `{minimum_hermes}` or newer, "
|
|
5549
|
-
f"but this agent is running `{current_hermes}`. Update Hermes first; no plugin update was applied."
|
|
5550
|
-
)
|
|
5551
|
-
|
|
5552
|
-
command = [
|
|
5553
|
-
npm_bin,
|
|
5554
|
-
"exec",
|
|
5555
|
-
"--yes",
|
|
5556
|
-
f"--package={package_spec}",
|
|
5557
|
-
"--",
|
|
5558
|
-
"inline-hermes",
|
|
5559
|
-
"install",
|
|
5560
|
-
"--force",
|
|
5561
|
-
"--hermes-home",
|
|
5562
|
-
str(hermes_home),
|
|
5563
|
-
]
|
|
5564
|
-
try:
|
|
5565
|
-
result = subprocess.run(
|
|
5566
|
-
command,
|
|
5567
|
-
stdout=subprocess.PIPE,
|
|
5568
|
-
stderr=subprocess.STDOUT,
|
|
5569
|
-
text=True,
|
|
5570
|
-
timeout=_INLINE_UPDATE_TIMEOUT_SECONDS,
|
|
5571
|
-
check=False,
|
|
5572
|
-
env=_inline_update_environment(),
|
|
5573
|
-
)
|
|
5574
|
-
except subprocess.TimeoutExpired as exc:
|
|
5575
|
-
_log_inline_update_failure(
|
|
5576
|
-
"install",
|
|
5577
|
-
f"npm exec timed out for {package_spec}",
|
|
5578
|
-
output=exc.stdout,
|
|
5579
|
-
hermes_home=hermes_home,
|
|
5580
|
-
)
|
|
5581
|
-
return (
|
|
5582
|
-
"Inline plugin update timed out. Run "
|
|
5583
|
-
f"`npm exec --yes --package={package_spec} -- "
|
|
5584
|
-
"inline-hermes install --force` on the Hermes host. Details were written to Hermes logs under `[inline-update]`."
|
|
5585
|
-
)
|
|
5586
|
-
except OSError as exc:
|
|
5587
|
-
_log_inline_update_failure("install", f"npm exec could not start: {exc}", hermes_home=hermes_home)
|
|
5588
|
-
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]`."
|
|
5589
|
-
|
|
5590
|
-
if result.returncode != 0:
|
|
5591
|
-
_log_inline_update_failure(
|
|
5592
|
-
"install",
|
|
5593
|
-
f"npm exec exited with code {result.returncode} for {package_spec}",
|
|
5594
|
-
output=result.stdout,
|
|
5595
|
-
hermes_home=hermes_home,
|
|
5596
|
-
)
|
|
5597
|
-
return (
|
|
5598
|
-
f"Inline plugin update failed with exit code {result.returncode}. Run "
|
|
5599
|
-
f"`npm exec --yes --package={package_spec} -- "
|
|
5600
|
-
"inline-hermes install --force` on the Hermes host. Details were written to Hermes logs under `[inline-update]`."
|
|
5601
|
-
)
|
|
5602
|
-
|
|
5603
|
-
version = _installed_inline_plugin_version(hermes_home)
|
|
5604
|
-
version_text = f" to `{version}`" if version else ""
|
|
5605
|
-
return f"Inline plugin updated{version_text} from the `{lane}` channel. Run `/restart` to load the new version."
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
async def _inline_update_command_handler(raw_args: str = "") -> str:
|
|
5609
|
-
if str(raw_args or "").strip():
|
|
5610
|
-
return "Usage: `/inline_update`"
|
|
5611
|
-
return await asyncio.to_thread(_run_inline_update)
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
5365
|
async def _inline_sync_command_handler(raw_args: str = "") -> str:
|
|
5615
5366
|
if str(raw_args or "").strip():
|
|
5616
5367
|
return "Usage: `/inline_sync`"
|
|
@@ -5641,11 +5392,6 @@ def _inline_command_specs() -> tuple[_InlineCommandSpec, ...]:
|
|
|
5641
5392
|
handler=_inline_unfollow_command_handler,
|
|
5642
5393
|
description=_INLINE_UNFOLLOW_COMMAND_DESCRIPTION,
|
|
5643
5394
|
),
|
|
5644
|
-
_InlineCommandSpec(
|
|
5645
|
-
name="inline-update",
|
|
5646
|
-
handler=_inline_update_command_handler,
|
|
5647
|
-
description=_INLINE_UPDATE_COMMAND_DESCRIPTION,
|
|
5648
|
-
),
|
|
5649
5395
|
_InlineCommandSpec(
|
|
5650
5396
|
name="inline-sync",
|
|
5651
5397
|
handler=_inline_sync_command_handler,
|
package/plugin/inline/cli.py
CHANGED
|
@@ -27,7 +27,7 @@ _CLI_INSTALL_URL = "https://inline.chat/cli/install.sh"
|
|
|
27
27
|
_MAX_TOKEN_BYTES = 16 * 1024
|
|
28
28
|
_MAX_PROBE_RESPONSE_BYTES = 64 * 1024
|
|
29
29
|
_MACHINE_SETUP_PROTOCOL_VERSION = 1
|
|
30
|
-
_PROBE_USER_AGENT = "inline-hermes-agent-adapter/0.0.
|
|
30
|
+
_PROBE_USER_AGENT = "inline-hermes-agent-adapter/0.0.19"
|
|
31
31
|
_ENV_REFERENCE_RE = re.compile(r"^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$")
|
|
32
32
|
|
|
33
33
|
|
|
@@ -362,6 +362,7 @@ def register_cli(parser: argparse.ArgumentParser) -> None:
|
|
|
362
362
|
setup.add_argument("--json", action="store_true", help="Print compact machine-readable setup output.")
|
|
363
363
|
status = subs.add_parser("status", help="Show Inline adapter status", description="Check Inline configuration, sidecar, Node runtime, and optional credential identity.")
|
|
364
364
|
status.add_argument("--json", action="store_true", help="Print compact machine-readable status output.")
|
|
365
|
+
status.add_argument("--check-compatibility", action="store_true", help="Validate the installed plugin with the Hermes loader.")
|
|
365
366
|
status.add_argument("--probe", action="store_true", help="Verify the configured Inline credential and bot identity.")
|
|
366
367
|
parser.set_defaults(func=dispatch)
|
|
367
368
|
|
|
@@ -453,7 +454,9 @@ def _status(args) -> int:
|
|
|
453
454
|
and sidecar["size"] > 0
|
|
454
455
|
)
|
|
455
456
|
runtime_usable = bool(sidecar["ok"] and node["ok"])
|
|
456
|
-
|
|
457
|
+
compatibility = _compatibility_status() if getattr(args, "check_compatibility", False) else None
|
|
458
|
+
compatible = compatibility is None or compatibility["ok"]
|
|
459
|
+
ready = compatible and runtime_usable and configured and (not probe_requested or bool(probe and probe.get("ok")))
|
|
457
460
|
result = {
|
|
458
461
|
"ok": ready,
|
|
459
462
|
"ready": ready,
|
|
@@ -467,11 +470,14 @@ def _status(args) -> int:
|
|
|
467
470
|
"node": node,
|
|
468
471
|
"probeRequested": probe_requested,
|
|
469
472
|
"gateway": _gateway_status(),
|
|
473
|
+
**({"compatibility": compatibility} if compatibility is not None else {}),
|
|
470
474
|
**({"probe": probe} if probe is not None else {}),
|
|
471
475
|
}
|
|
472
476
|
if getattr(args, "json", False):
|
|
473
477
|
print(json.dumps(result, separators=(",", ":")))
|
|
474
478
|
else:
|
|
479
|
+
if compatibility is not None:
|
|
480
|
+
print(f"Hermes plugin compatible: {'yes' if compatibility['ok'] else 'no'} ({compatibility['reason']})")
|
|
475
481
|
print(f"Inline configured: {'yes' if configured else 'no'}")
|
|
476
482
|
print(f"Inline sidecar usable: {'yes' if sidecar['ok'] else 'no'}")
|
|
477
483
|
print(f"Node available: {_node_status_text(node)}")
|
|
@@ -481,7 +487,46 @@ def _status(args) -> int:
|
|
|
481
487
|
elif probe_requested:
|
|
482
488
|
print(f"Inline credential probe: {'ready' if ready else 'failed'}")
|
|
483
489
|
print("Advanced diagnostics: inline-hermes doctor --json")
|
|
484
|
-
return 0 if runtime_usable and (not probe_requested or ready) else 1
|
|
490
|
+
return 0 if compatible and runtime_usable and (not probe_requested or ready) else 1
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _compatibility_status() -> dict:
|
|
494
|
+
"""Attest only a successful host load of this exact installed plugin.
|
|
495
|
+
|
|
496
|
+
Never return host exception text: configuration or import errors may contain
|
|
497
|
+
credentials. A missing diagnostic API is an inconclusive check, not success.
|
|
498
|
+
"""
|
|
499
|
+
unavailable = {"ok": False, "reason": "loader_unavailable"}
|
|
500
|
+
try:
|
|
501
|
+
from hermes_cli.plugins import get_plugin_manager
|
|
502
|
+
|
|
503
|
+
manager = get_plugin_manager()
|
|
504
|
+
manager.discover_and_load()
|
|
505
|
+
plugin_dir = Path(__file__).resolve().parent
|
|
506
|
+
loaded = next((
|
|
507
|
+
item for item in manager._plugins.values()
|
|
508
|
+
if item.manifest.name == "inline-platform"
|
|
509
|
+
and Path(item.manifest.path).resolve() == plugin_dir
|
|
510
|
+
), None)
|
|
511
|
+
if loaded is None:
|
|
512
|
+
return {"ok": False, "reason": "plugin_not_loaded"}
|
|
513
|
+
if not loaded.enabled or loaded.error or loaded.module is None:
|
|
514
|
+
return {"ok": False, "reason": "plugin_load_failed"}
|
|
515
|
+
if "inline" not in loaded.tools_registered:
|
|
516
|
+
return {"ok": False, "reason": "tool_not_registered"}
|
|
517
|
+
# Older supported Hermes releases predate the moved-import scanner.
|
|
518
|
+
# Their loader still checks real imports and plugin registration above.
|
|
519
|
+
try:
|
|
520
|
+
from hermes_cli.plugin_compat import plugin_hits
|
|
521
|
+
except ModuleNotFoundError as error:
|
|
522
|
+
if error.name != "hermes_cli.plugin_compat":
|
|
523
|
+
raise
|
|
524
|
+
else:
|
|
525
|
+
if plugin_hits(loaded.manifest):
|
|
526
|
+
return {"ok": False, "reason": "deprecated_imports"}
|
|
527
|
+
return {"ok": True, "reason": "loaded", "pluginPath": str(plugin_dir)}
|
|
528
|
+
except Exception:
|
|
529
|
+
return unavailable
|
|
485
530
|
|
|
486
531
|
|
|
487
532
|
def _gateway_status() -> dict:
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
name: inline-platform
|
|
2
2
|
label: Inline
|
|
3
3
|
kind: platform
|
|
4
|
-
|
|
4
|
+
provides_tools:
|
|
5
|
+
- inline
|
|
6
|
+
version: 0.0.19
|
|
7
|
+
requires_hermes: ">=0.21.4"
|
|
5
8
|
description: >
|
|
6
9
|
Inline platform adapter for Hermes Agent. The adapter runs as a native
|
|
7
10
|
Hermes Python platform plugin and supervises a local Node sidecar that uses
|