@inline-chat/hermes-agent-adapter 1.0.1 → 1.2.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.1"
33
+ _PROBE_USER_AGENT = "inline-hermes-agent-adapter/1.2.0"
34
34
  _ENV_REFERENCE_RE = re.compile(r"^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$")
35
35
 
36
36
 
@@ -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,14 @@ label: Inline
3
3
  kind: platform
4
4
  provides_tools:
5
5
  - inline
6
- version: 1.0.1
6
+ provides_hooks:
7
+ - pre_tool_call
8
+ - post_tool_call
9
+ - subagent_start
10
+ - subagent_stop
11
+ - pre_approval_request
12
+ - post_approval_response
13
+ version: 1.2.0
7
14
  requires_hermes: ">=0.21.3"
8
15
  description: >
9
16
  Inline platform adapter for Hermes Agent. The adapter runs as a native
@@ -29,6 +36,10 @@ optional_env:
29
36
  description: "Parse supported outbound Inline Markdown (true by default; false preserves literal syntax)"
30
37
  prompt: "Parse outbound Markdown?"
31
38
  password: false
39
+ - name: INLINE_CRON_MENTION_PREFIX
40
+ description: "Inline Markdown mention prefixed to scheduled gateway text deliveries; empty disables it (requires Markdown parsing)"
41
+ prompt: "Scheduled delivery mention, e.g. [@You](inline://user?id=42)"
42
+ password: false
32
43
  - name: INLINE_SYNC_COMMANDS
33
44
  description: "Sync Hermes slash commands into Inline's native / command menu on gateway connect (true/false, default true)"
34
45
  prompt: "Sync native slash commands?"