@inline-chat/hermes-agent-adapter 0.0.17 → 0.0.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inline-chat/hermes-agent-adapter",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
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",
@@ -14,14 +14,14 @@
14
14
  "adapter",
15
15
  "realtime"
16
16
  ],
17
- "homepage": "https://github.com/inline-chat/inline/tree/main/hermes-agent#readme",
17
+ "homepage": "https://github.com/inline-chat/inline/tree/main/plugins/hermes-agent#readme",
18
18
  "bugs": {
19
19
  "url": "https://github.com/inline-chat/inline/issues"
20
20
  },
21
21
  "repository": {
22
22
  "type": "git",
23
23
  "url": "git+https://github.com/inline-chat/inline.git",
24
- "directory": "hermes-agent"
24
+ "directory": "plugins/hermes-agent"
25
25
  },
26
26
  "files": [
27
27
  "LICENSE",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "scripts": {
53
53
  "build": "mkdir -p plugin/inline/sidecar && bun build ./src/install.ts --outdir dist --entry-naming '[name].js' --target=node --format=esm --packages=bundle && bun build ./src/sidecar/index.ts --outdir plugin/inline/sidecar --entry-naming 'index.mjs' --target=node --format=esm --packages=bundle && tsc -p tsconfig.json --emitDeclarationOnly",
54
- "lint": "bunx oxlint --ignore-path ../.oxlintignore scripts/release-stage.mjs src/install.ts src/sidecar/contract.ts src/sidecar/index.ts src/sidecar/telemetry.ts src/sidecar/telemetry.test.ts src/sidecar/user-directory.ts src/sidecar/user-directory.test.ts src/sidecar/inbound-delivery.ts src/sidecar/inbound-delivery.test.ts src/sidecar/inbound-stream.ts src/sidecar/inbound-stream.test.ts tests/inbound-flow.test.ts tests/adapter-python.test.ts tests/install.test.ts tests/package-artifact.test.ts tests/sidecar-contract.test.ts tests/sidecar-runtime.test.ts tests/telemetry-python.test.ts vitest.config.ts",
54
+ "lint": "bunx oxlint --ignore-path ../../.oxlintignore scripts/release-stage.mjs src/install.ts src/sidecar/contract.ts src/sidecar/index.ts src/sidecar/telemetry.ts src/sidecar/telemetry.test.ts src/sidecar/user-directory.ts src/sidecar/user-directory.test.ts src/sidecar/inbound-delivery.ts src/sidecar/inbound-delivery.test.ts src/sidecar/inbound-stream.ts src/sidecar/inbound-stream.test.ts tests/inbound-flow.test.ts tests/adapter-python.test.ts tests/install.test.ts tests/package-artifact.test.ts tests/sidecar-contract.test.ts tests/sidecar-runtime.test.ts tests/telemetry-python.test.ts vitest.config.ts",
55
55
  "pretest": "bun run build",
56
56
  "prepack": "bun run build",
57
57
  "prepublishOnly": "bun run check",
@@ -81,8 +81,8 @@
81
81
  "npmSpec": "@inline-chat/hermes-agent-adapter"
82
82
  },
83
83
  "machineSetupProtocol": 1,
84
- "minHermesVersion": "0.17.0",
85
- "testedHermesVersion": "0.21.0",
86
- "testedHermesCommit": "29112bef"
84
+ "minHermesVersion": "0.21.4",
85
+ "testedHermesVersion": "0.21.5",
86
+ "testedHermesCommit": "f97608f178d1ffeca59860195ab7da295f7c8e5f"
87
87
  }
88
88
  }
@@ -45,6 +45,7 @@ from gateway.platforms.base import (
45
45
  SendResult,
46
46
  cache_audio_from_url,
47
47
  cache_image_from_url,
48
+ safe_url_for_log,
48
49
  )
49
50
  from gateway.platforms.helpers import strip_markdown
50
51
 
@@ -575,7 +576,7 @@ def _normalize_inline_command_description(raw: str) -> str:
575
576
 
576
577
 
577
578
  def _inline_menu_commands(max_commands: int = _INLINE_COMMAND_LIMIT) -> tuple[List[Dict[str, Any]], int]:
578
- from hermes_cli.commands import telegram_menu_commands
579
+ from hermes_cli.commands_platforms import telegram_menu_commands
579
580
 
580
581
  command_specs = _inline_command_specs()
581
582
  local_commands = [
@@ -1209,7 +1210,7 @@ class InlineAdapter(BasePlatformAdapter):
1209
1210
 
1210
1211
  try:
1211
1212
  from gateway.run import _load_gateway_config
1212
- from hermes_cli.model_switch import list_picker_providers
1213
+ from hermes_cli.model_switch_providers import list_picker_providers
1213
1214
 
1214
1215
  cfg = _load_gateway_config() or {}
1215
1216
  model_cfg = cfg.get("model") if isinstance(cfg, dict) else {}
@@ -3001,16 +3002,15 @@ class InlineAdapter(BasePlatformAdapter):
3001
3002
  return await cache_audio_from_url(url, ext=_extension_for_media(mime, file_name, ".ogg"))
3002
3003
  return await self._download_inline_media_url(url, mime=mime, file_name=file_name)
3003
3004
  except Exception as exc:
3004
- logger.warning("[inline] failed to cache %s attachment: %s", kind, exc)
3005
- return url
3005
+ logger.warning("[inline] failed to cache %s attachment (%s)", kind, type(exc).__name__)
3006
+ # Never pass an unvalidated URL on to Hermes for another fetch.
3007
+ return None
3006
3008
 
3007
3009
  async def _download_inline_media_url(self, url: str, *, mime: str, file_name: Optional[str]) -> str:
3008
- try:
3009
- from tools.url_safety import is_safe_url, safe_url_for_log
3010
- if not is_safe_url(url):
3011
- raise ValueError(f"blocked unsafe media URL: {safe_url_for_log(url)}")
3012
- except ImportError:
3013
- pass
3010
+ # A missing host safety helper must stop the download, never bypass it.
3011
+ from tools.url_safety import is_safe_url
3012
+ if not is_safe_url(url):
3013
+ raise ValueError(f"blocked unsafe media URL: {safe_url_for_log(url)}")
3014
3014
  _MEDIA_CACHE_DIR.mkdir(parents=True, exist_ok=True)
3015
3015
  name = _safe_media_file_name(url=url, mime=mime, file_name=file_name)
3016
3016
  path = _MEDIA_CACHE_DIR / name
@@ -4230,7 +4230,7 @@ class InlineAdapter(BasePlatformAdapter):
4230
4230
  label = group_id
4231
4231
  member_slugs: list[str] = []
4232
4232
  try:
4233
- from hermes_cli.models import PROVIDER_GROUPS
4233
+ from hermes_cli.models_catalog_static import PROVIDER_GROUPS
4234
4234
  label, _desc, members = PROVIDER_GROUPS.get(group_id, (group_id, "", []))
4235
4235
  member_slugs = [str(member) for member in members]
4236
4236
  except Exception:
@@ -4686,7 +4686,6 @@ class InlineAdapter(BasePlatformAdapter):
4686
4686
 
4687
4687
  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
4688
  try:
4689
- from gateway.platforms.base import cache_image_from_url
4690
4689
  local_path = await cache_image_from_url(image_url)
4691
4690
  return await self.send_image_file(chat_id, local_path, caption, reply_to, metadata)
4692
4691
  except Exception:
@@ -4694,7 +4693,6 @@ class InlineAdapter(BasePlatformAdapter):
4694
4693
 
4695
4694
  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
4695
  try:
4697
- from gateway.platforms.base import cache_image_from_url
4698
4696
  local_path = await cache_image_from_url(animation_url, ext=".gif")
4699
4697
  return await self.send_document(chat_id, local_path, caption, file_name=Path(local_path).name, reply_to=reply_to, metadata=metadata)
4700
4698
  except Exception:
@@ -4982,7 +4980,7 @@ class InlineAdapter(BasePlatformAdapter):
4982
4980
  }
4983
4981
  actions: list[Dict[str, str]] = []
4984
4982
  try:
4985
- from hermes_cli.models import group_providers
4983
+ from hermes_cli.models_catalog_static import group_providers
4986
4984
  grouped = group_providers(list(by_slug.keys()))
4987
4985
  except Exception:
4988
4986
  grouped = [{"kind": "single", "slug": slug} for slug in by_slug.keys()]
@@ -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.17"
30
+ _PROBE_USER_AGENT = "inline-hermes-agent-adapter/0.0.18"
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
- ready = runtime_usable and configured and (not probe_requested or bool(probe and probe.get("ok")))
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
- version: 0.0.17
4
+ provides_tools:
5
+ - inline
6
+ version: 0.0.18
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