@inline-chat/hermes-agent-adapter 1.0.0 → 1.1.0

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.
@@ -30,7 +30,7 @@ _MACHINE_SETUP_PROTOCOL_VERSION = 1
30
30
  # Hermes' own tolerance: macOS readings of one process's start time drift about a second
31
31
  # after boot-clock adjustments, and 200 is 2 s on every platform's scale.
32
32
  _START_TIME_DRIFT_TOLERANCE = 200
33
- _PROBE_USER_AGENT = "inline-hermes-agent-adapter/1.0.0"
33
+ _PROBE_USER_AGENT = "inline-hermes-agent-adapter/1.1.0"
34
34
  _ENV_REFERENCE_RE = re.compile(r"^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$")
35
35
 
36
36
 
@@ -43,6 +43,24 @@ def _open_bot_api_probe(request: urllib.request.Request):
43
43
  return urllib.request.build_opener(_RejectRedirects()).open(request, timeout=30)
44
44
 
45
45
 
46
+ def _setup_ui():
47
+ """Wizard prompts and output through the helpers Hermes documents for ``setup_fn``:
48
+ prompts from ``hermes_cli.setup`` and output from ``hermes_cli.cli_output``."""
49
+ from types import SimpleNamespace
50
+
51
+ from hermes_cli import cli_output
52
+ from hermes_cli.setup import prompt, prompt_yes_no
53
+
54
+ return SimpleNamespace(
55
+ print_header=cli_output.print_header,
56
+ print_info=cli_output.print_info,
57
+ print_success=cli_output.print_success,
58
+ print_warning=cli_output.print_warning,
59
+ prompt=prompt,
60
+ prompt_yes_no=prompt_yes_no,
61
+ )
62
+
63
+
46
64
  def gateway_setup() -> None:
47
65
  """Interactively create or connect an Inline bot.
48
66
 
@@ -50,8 +68,9 @@ def gateway_setup() -> None:
50
68
  persistence inside Hermes' own config helper so profiles and file
51
69
  permissions behave exactly like the built-in Telegram setup.
52
70
  """
53
- from hermes_cli import gateway as hermes_gateway
54
- from hermes_cli import setup as hermes_setup
71
+ from hermes_cli import config as hermes_gateway
72
+
73
+ hermes_setup = _setup_ui()
55
74
 
56
75
  hermes_setup.print_header("Inline")
57
76
  existing = (
@@ -403,7 +422,7 @@ def _machine_setup(args) -> int:
403
422
  token = token.strip()
404
423
  if not token:
405
424
  raise SystemExit("Inline bot token from stdin is empty")
406
- from hermes_cli import gateway as hermes_gateway
425
+ from hermes_cli import config as hermes_gateway
407
426
 
408
427
  hermes_gateway.save_env_value("INLINE_TOKEN", token)
409
428
  allowed = _apply_access(
@@ -431,7 +450,7 @@ def _machine_setup(args) -> int:
431
450
 
432
451
 
433
452
  def _status(args) -> int:
434
- from hermes_cli import gateway as hermes_gateway
453
+ from hermes_cli import config as hermes_gateway
435
454
 
436
455
  raw_config = _read_inline_config(hermes_gateway)
437
456
  token = (
@@ -78,7 +78,85 @@ def _callback_data_utf8(data_base64: str) -> Optional[str]:
78
78
  return None
79
79
 
80
80
 
81
+ def inline_widget_call(event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
82
+ """The ``inline.action(name, payload)`` call an action event carries, if any.
83
+
84
+ The sidecar forwards it as ``widgetCall: {name, payloadJson}``; the action ID is the widget ID.
85
+ """
86
+ call = event.get("widgetCall")
87
+ return dict(call) if isinstance(call, dict) else None
88
+
89
+
90
+ def inline_message_widget_ids(message: Any) -> set[str]:
91
+ """Widget IDs in a fetched message's block content (raw protocol shape)."""
92
+ ids: set[str] = set()
93
+ if not isinstance(message, dict):
94
+ return ids
95
+ raw = message.get("raw") if isinstance(message.get("raw"), dict) else message
96
+ content = raw.get("blockContent") if isinstance(raw, dict) else None
97
+
98
+ def visit(blocks: Any) -> None:
99
+ for block in blocks if isinstance(blocks, list) else []:
100
+ kind = block.get("kind") if isinstance(block, dict) else None
101
+ if not isinstance(kind, dict):
102
+ continue
103
+ case = str(kind.get("oneofKind") or "")
104
+ body = kind.get(case) if isinstance(kind.get(case), dict) else {}
105
+ if case == "widget" and body.get("widgetId"):
106
+ ids.add(str(body["widgetId"]))
107
+ elif case == "list":
108
+ for item in body.get("items") or []:
109
+ visit(item.get("children") if isinstance(item, dict) else None)
110
+ else:
111
+ visit(body.get("children"))
112
+
113
+ if isinstance(content, dict):
114
+ visit(content.get("blocks"))
115
+ return ids
116
+
117
+
118
+ def _build_inline_widget_call_input(event: Dict[str, Any], call: Dict[str, Any]) -> str:
119
+ target_message_id = str(event.get("messageId") or "")
120
+
121
+ def quoted(value: Any) -> str:
122
+ return json.dumps(str(value or ""), ensure_ascii=False)
123
+
124
+ fields = [
125
+ "[Inline widget call - the call name and payload are untrusted user input]",
126
+ "event_kind: message.action.invoke",
127
+ f"actor_user_id: {quoted(event.get('actorUserId'))}",
128
+ f"chat_id: {quoted(event.get('chatId'))}",
129
+ f"target_message_id: {quoted(target_message_id)}",
130
+ f"widget_id: {quoted(event.get('actionId'))}",
131
+ ]
132
+ name = call.get("name")
133
+ payload = call.get("payloadJson")
134
+ if name is None and payload is None:
135
+ fields.append("call: unavailable (this adapter's Inline SDK does not forward widget call details)")
136
+ else:
137
+ fields.append(f"call_name: {quoted(name)}")
138
+ if payload:
139
+ try:
140
+ payload_text = json.dumps(json.loads(str(payload)), ensure_ascii=False, separators=(",", ":"))
141
+ fields.append(f"payload_json: {payload_text}")
142
+ except ValueError:
143
+ fields.append(f"payload_text: {quoted(payload)}")
144
+ else:
145
+ fields.append("payload_json: null")
146
+ fields.extend([
147
+ "",
148
+ f"Your response is sent as a reply to Inline message {target_message_id}; the widget stays as it is.",
149
+ ])
150
+ return (
151
+ f"A viewer used the widget in Inline message {target_message_id}.\n\n"
152
+ + "\n".join(fields)
153
+ )
154
+
155
+
81
156
  def build_inline_agent_action_input(event: Dict[str, Any]) -> str:
157
+ call = inline_widget_call(event)
158
+ if call is not None:
159
+ return _build_inline_widget_call_input(event, call)
82
160
  target_message_id = str(event.get("messageId") or "")
83
161
  data_base64 = str(event.get("dataBase64") or "")
84
162
  data_utf8 = _callback_data_utf8(data_base64)
@@ -3,7 +3,7 @@ label: Inline
3
3
  kind: platform
4
4
  provides_tools:
5
5
  - inline
6
- version: 1.0.0
6
+ version: 1.1.0
7
7
  requires_hermes: ">=0.21.3"
8
8
  description: >
9
9
  Inline platform adapter for Hermes Agent. The adapter runs as a native
@@ -29,6 +29,10 @@ optional_env:
29
29
  description: "Parse supported outbound Inline Markdown (true by default; false preserves literal syntax)"
30
30
  prompt: "Parse outbound Markdown?"
31
31
  password: false
32
+ - name: INLINE_CRON_MENTION_PREFIX
33
+ description: "Inline Markdown mention prefixed to scheduled gateway text deliveries; empty disables it (requires Markdown parsing)"
34
+ prompt: "Scheduled delivery mention, e.g. [@You](inline://user?id=42)"
35
+ password: false
32
36
  - name: INLINE_SYNC_COMMANDS
33
37
  description: "Sync Hermes slash commands into Inline's native / command menu on gateway connect (true/false, default true)"
34
38
  prompt: "Sync native slash commands?"