@inline-chat/hermes-agent-adapter 0.0.8 → 0.0.10

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
@@ -10,7 +10,7 @@ Implemented Hermes-native surfaces:
10
10
 
11
11
  - realtime inbound messages, replies, and action callbacks
12
12
  - outbound text, markdown, opt-in edit-message streaming, deletes, typing, media uploads, and reply threads
13
- - Discord-style native Hermes `inline` tool for bounded chat/message/history/search/thread, reaction, and pin actions
13
+ - Discord-style native Hermes `inline` tool for bounded chat/message/history/search/thread, reaction, pin, and agent-authored button-message actions
14
14
  - clarify prompts, command approvals, slash confirmations, and model picker buttons
15
15
  - Hermes slash commands synced into Inline's native `/` bot command menu
16
16
  - DM/group access policy controls compatible with Hermes gateway allowlists
@@ -27,12 +27,13 @@ Supported:
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
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
- - 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, top-level thread/chat creation outside the current conversation, and avatar presence/status.
30
+ - Native Hermes `inline` tool for current-chat/thread reads, bounded history and search, exact message lookup, button-message sends, editing/deleting bot-owned messages, reactions, pin/unpin/list pins, reply-thread creation, top-level thread/chat creation outside the current conversation, and avatar presence/status.
31
31
  - Cached, privacy-safe sender names/usernames plus chat/thread IDs, selective reply/thread/observed context, and parent-thread context, with first-name/username Markdown mention guidance and current chat/thread 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
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
+ - Agent-created `send_message`/`edit_message` button rows with opaque callback data. A callback is acknowledged immediately and becomes a normal Hermes turn naming the source message and exact action fields. The normal response edits that source message and clears omitted buttons; the agent can instead call `edit_message` with replacement buttons and finish with `NO_REPLY` so the explicit edit remains authoritative.
36
37
  - Outbound local photo, video, voice, and document uploads with configurable size caps.
37
38
  - Inbound photo, video, voice, and document summaries, with URL-backed media cached locally for Hermes when available.
38
39
  - Reactions on bot messages, plus opt-in lifecycle/system events as synthetic Hermes messages.
@@ -43,7 +44,7 @@ Unsupported or intentionally limited:
43
44
 
44
45
  - Multiple Inline accounts in one Hermes process. Run separate Hermes instances for separate tokens.
45
46
  - Inline member, space, and admin tools beyond bounded current-chat/thread message access.
46
- - Full Inline rich-text span conversion. Outbound formatting uses Inline Markdown parsing; rich entities are summarized for the agent instead of converted into Hermes-specific spans.
47
+ - Full Inline rich-text span conversion. Outbound formatting uses [Inline's bounded Markdown surface](../docs/markdown.md); rich entities are summarized for the agent instead of converted into Hermes-specific spans. Parsing is enabled by default, while disabling it preserves the supplied syntax literally.
47
48
  - Native animated draft streaming. Inline can stream by sending one preview message and editing it, but this edit-based streaming path is off by default like Discord and Slack.
48
49
  - Ephemeral in-channel private replies. Private notices are sent as DMs when a user id is available.
49
50
  - Live voice sessions or calls. Voice file messages are supported, but realtime audio is not.
@@ -146,7 +147,7 @@ uv run ./hermes plugins list --plain --no-bundled
146
147
  Expected local output includes:
147
148
 
148
149
  ```text
149
- enabled user 0.0.8 inline-platform
150
+ enabled user 0.0.10 inline-platform
150
151
  ```
151
152
 
152
153
  ## Update Or Reinstall
@@ -169,10 +170,10 @@ mismatch, rerun the same command after rebuilding or upgrading the package.
169
170
 
170
171
  - Hermes Agent: requires the external user plugin registry and native platform
171
172
  plugin loader available in Hermes Agent `0.17.x`. This package was validated
172
- against Hermes Agent `0.20.0` from source commit `3c27eb6`.
173
+ against Hermes Agent `0.20.6` from source commit `31e41eed`.
173
174
  - Node.js: `>=20` is required for the bundled sidecar. Hermes-managed Node 22,
174
175
  system Node, or an explicit `INLINE_NODE_BIN` path all work.
175
- - Inline transport: the sidecar uses `@inline-chat/realtime-sdk@0.0.15` and is
176
+ - Inline transport: the sidecar uses `@inline-chat/realtime-sdk@0.0.16` and is
176
177
  bundled into the npm package, so Hermes startup does not run `npm install`.
177
178
  - Live sends require a valid Inline user or bot token in `INLINE_TOKEN`,
178
179
  `INLINE_BOT_TOKEN`, `platforms.inline.token`, or `inline.token`.
@@ -293,7 +294,7 @@ Access control follows Hermes' native platform model:
293
294
  | `INLINE_SETTINGS_PATH` | JSON settings file for per-chat `/threads` overrides. `/threads` shows native Auto/On/Off buttons; `reset` clears the chat override back to the global default. Defaults next to `INLINE_STATE_PATH`; `.env`-like paths are refused. |
294
295
  | `INLINE_SYSTEM_EVENTS` | Delivers Inline lifecycle events such as edits, deletes, and participant changes as synthetic messages. Defaults to `false`. Reactions on bot messages are always delivered. |
295
296
  | `INLINE_MENTION_PATTERNS` | JSON list, comma-separated, or newline-separated regex patterns for group wake words. |
296
- | `INLINE_PARSE_MARKDOWN` | Controls whether outbound Hermes Markdown is parsed by Inline. Defaults to `true`. |
297
+ | `INLINE_PARSE_MARKDOWN` | Controls whether supported outbound Inline Markdown is parsed. Defaults to `true`; `false` preserves the supplied syntax literally. |
297
298
  | `INLINE_SYNC_COMMANDS` | Syncs Hermes slash commands into Inline's native `/` bot command menu on gateway connect. Defaults to `true`. |
298
299
  | `INLINE_COMMAND_LIMIT` | Caps native Inline bot command sync. Must be `1` through `100`; defaults to `100`. |
299
300
  | `INLINE_MEDIA_MAX_MB` | Maximum inbound media download size for URL-backed Inline attachments. Defaults to `25`. |
@@ -342,6 +343,11 @@ the clicking actor to pass an explicit Inline or global Hermes allowlist, or
342
343
  `INLINE_ALLOW_ALL_USERS=true` / `GATEWAY_ALLOW_ALL_USERS=true`. This includes
343
344
  model-picker callbacks. The stricter callback gate prevents group-visible
344
345
  buttons from becoming a bypass when message intake is otherwise `open`.
346
+ Adapter-owned controls use `system:` action IDs and stay in these deterministic
347
+ handlers. Agent-authored callbacks use `agent:` IDs and follow the ordinary
348
+ message intake policy because they are conversational input, not approval or
349
+ gateway-control input. Callback data is always presented to the model as
350
+ untrusted data and cannot become a Hermes slash command.
345
351
 
346
352
  On gateway startup, the adapter derives the Inline `/` menu from Hermes'
347
353
  declarative local command registry plus Hermes' central slash-command registry,
package/dist/install.js CHANGED
@@ -7009,6 +7009,10 @@ var sensitiveUrlParams = new Set([
7009
7009
  "key",
7010
7010
  "token"
7011
7011
  ]);
7012
+ var botSettingsEventKinds = new Set([
7013
+ "bot.chatSettings.request",
7014
+ "bot.chatSettings.item.invoke"
7015
+ ]);
7012
7016
  function redactText(value, secrets) {
7013
7017
  let text = value instanceof Error ? value.message : String(value);
7014
7018
  for (const secret of secrets) {
@@ -7044,6 +7048,8 @@ function redactUrl(value) {
7044
7048
  var defaultPluginId = "inline";
7045
7049
  var pluginEnableKey = "inline-platform";
7046
7050
  var minNodeMajor = 20;
7051
+ var hermesMachineTimeoutMs = 15000;
7052
+ var hermesMachineMaxBytes = 256 * 1024;
7047
7053
  async function main(argv = process.argv.slice(2)) {
7048
7054
  const opts = parseArgs(argv);
7049
7055
  if (opts.command === "help") {
@@ -7147,7 +7153,7 @@ async function inspectInstall(params) {
7147
7153
  const sourceSidecar = await inspectSidecar(params.source);
7148
7154
  const targetSidecar = targetExists ? await inspectSidecar(params.target) : null;
7149
7155
  const node = await inspectNode();
7150
- const activation = await inspectActivation(params.opts.hermesHome, params.target);
7156
+ const activation = await inspectActivation(params.opts.hermesHome, params.target, params.opts.command === "doctor");
7151
7157
  const issues = [];
7152
7158
  const warnings = [];
7153
7159
  const sidecarMatches = Boolean(targetSidecar?.exists && sourceSidecar.sha256 && targetSidecar.sha256 && sourceSidecar.sha256 === targetSidecar.sha256);
@@ -7171,11 +7177,33 @@ async function inspectInstall(params) {
7171
7177
  if (params.opts.command === "doctor" && !activation.pluginEnabled) {
7172
7178
  issues.push(`Hermes plugin '${pluginEnableKey}' is not enabled. Run: hermes plugins enable ${pluginEnableKey}`);
7173
7179
  }
7174
- if (targetExists && activation.pluginEnabled && !activation.platformConfigured && !activation.tokenConfigured) {
7175
- warnings.push("Inline platform config is not enabled. Add platforms.inline.enabled: true and set INLINE_TOKEN/INLINE_BOT_TOKEN in the gateway environment, or set platforms.inline.token/inline.token in Hermes config");
7180
+ if (targetExists && activation.pluginEnabled && !activation.platformConfigured && activation.credentialState === "not_configured") {
7181
+ const message = "Inline platform config is not enabled. Add platforms.inline.enabled: true and set INLINE_TOKEN/INLINE_BOT_TOKEN in the gateway environment, or set platforms.inline.token/inline.token in Hermes config";
7182
+ if (params.opts.command === "doctor")
7183
+ issues.push(message);
7184
+ else
7185
+ warnings.push(message);
7186
+ }
7187
+ if (targetExists && activation.pluginEnabled && activation.credentialState === "not_configured") {
7188
+ const message = "Hermes reports that no Inline credential is configured; run `hermes inline setup`";
7189
+ if (params.opts.command === "doctor")
7190
+ issues.push(message);
7191
+ else
7192
+ warnings.push(message);
7193
+ }
7194
+ if (targetExists && activation.pluginEnabled && activation.credentialState === "invalid") {
7195
+ const message = "Hermes found the Inline credential, but Inline rejected it; reconfigure the credential";
7196
+ if (params.opts.command === "doctor")
7197
+ issues.push(message);
7198
+ else
7199
+ warnings.push(message);
7176
7200
  }
7177
- if (targetExists && activation.pluginEnabled && !activation.tokenConfigured) {
7178
- warnings.push("No Inline token was detected in INLINE_TOKEN, INLINE_BOT_TOKEN, the Hermes credential store, or Hermes Inline config; live Inline realtime checks will not connect");
7201
+ if (targetExists && activation.pluginEnabled && activation.credentialState === "unknown" && activation.hermesCredentialStoreChecked) {
7202
+ const message = "Hermes found the Inline credential, but the requested verification was inconclusive";
7203
+ if (params.opts.command === "doctor")
7204
+ issues.push(message);
7205
+ else
7206
+ warnings.push(message);
7179
7207
  }
7180
7208
  if (targetExists && !activation.installOwnershipAligned) {
7181
7209
  warnings.push("Installed plugin ownership differs from the Hermes home owner; internal /inline_update may fail until inline-hermes install --force repairs it");
@@ -7474,6 +7502,7 @@ async function hasPluginFiles(dir) {
7474
7502
  "plugin.yaml",
7475
7503
  "__init__.py",
7476
7504
  "adapter.py",
7505
+ "message_actions.py",
7477
7506
  path.join("sidecar", "index.mjs")
7478
7507
  ];
7479
7508
  const checks = files.map((file) => exists(path.join(dir, file)));
@@ -7538,7 +7567,7 @@ async function inspectNode() {
7538
7567
  error
7539
7568
  };
7540
7569
  }
7541
- async function inspectActivation(hermesHome, target) {
7570
+ async function inspectActivation(hermesHome, target, probeCredential) {
7542
7571
  const configPath = path.join(hermesHome, "config.yaml");
7543
7572
  let config = null;
7544
7573
  let configExists = false;
@@ -7550,7 +7579,9 @@ async function inspectActivation(hermesHome, target) {
7550
7579
  }
7551
7580
  const tokenPresent = Boolean(process.env.INLINE_TOKEN || process.env.INLINE_BOT_TOKEN);
7552
7581
  const configTokenConfigured = configTokenIsConfigured(config, ["platforms", defaultPluginId, "token"]) || configTokenIsConfigured(config, [defaultPluginId, "token"]);
7553
- const hermesCredentialStore = inspectHermesCredentialStore();
7582
+ const machineCredential = inspectHermesCredential(hermesHome, probeCredential);
7583
+ const fallbackConfigured = tokenPresent || configTokenConfigured;
7584
+ const credentialState = (machineCredential.state === "unknown" || machineCredential.state === "not_configured") && fallbackConfigured ? "configured_unverified" : machineCredential.state;
7554
7585
  const installOwnershipAligned = await inspectInstallOwnership(hermesHome, target);
7555
7586
  return {
7556
7587
  configPath,
@@ -7558,10 +7589,13 @@ async function inspectActivation(hermesHome, target) {
7558
7589
  pluginEnabled: readConfigList(config, ["plugins", "enabled"]).includes(pluginEnableKey),
7559
7590
  platformConfigured: readConfigBoolean(config, ["platforms", defaultPluginId, "enabled"]) || readConfigBoolean(config, [defaultPluginId, "enabled"]),
7560
7591
  configTokenConfigured,
7561
- hermesCredentialStoreChecked: hermesCredentialStore.checked,
7562
- hermesCredentialStoreTokenConfigured: hermesCredentialStore.tokenConfigured,
7592
+ hermesCredentialStoreChecked: machineCredential.checked,
7593
+ hermesCredentialStoreTokenConfigured: machineCredential.configured === true,
7594
+ credentialState,
7595
+ ...machineCredential.botUserId ? { credentialBotUserId: machineCredential.botUserId } : {},
7596
+ ...machineCredential.botUsername ? { credentialBotUsername: machineCredential.botUsername } : {},
7563
7597
  tokenPresent,
7564
- tokenConfigured: tokenPresent || configTokenConfigured || hermesCredentialStore.tokenConfigured,
7598
+ tokenConfigured: machineCredential.configured === false ? fallbackConfigured : machineCredential.configured ?? fallbackConfigured,
7565
7599
  installOwnershipAligned
7566
7600
  };
7567
7601
  }
@@ -7615,40 +7649,56 @@ async function chownTree(target, uid, gid) {
7615
7649
  }
7616
7650
  await chown(target, uid, gid);
7617
7651
  }
7618
- function inspectHermesCredentialStore() {
7619
- const configuredPython = normalizeToken(process.env.INLINE_HERMES_PYTHON_BIN);
7620
- const candidates = configuredPython ? [configuredPython] : hermesPythonCandidates();
7621
- const probe = [
7622
- "from hermes_cli import gateway",
7623
- "configured = bool(gateway.get_env_value('INLINE_TOKEN') or gateway.get_env_value('INLINE_BOT_TOKEN'))",
7624
- "print('__INLINE_HERMES_TOKEN_CONFIGURED__=' + ('1' if configured else '0'))"
7625
- ].join("; ");
7626
- for (const candidate of candidates) {
7627
- const result = spawnSync(candidate, ["-c", probe], {
7628
- encoding: "utf8",
7629
- timeout: 2000,
7630
- maxBuffer: 64 * 1024
7631
- });
7632
- if (result.status !== 0)
7633
- continue;
7634
- const match = /^__INLINE_HERMES_TOKEN_CONFIGURED__=([01])$/m.exec(result.stdout || "");
7635
- if (match)
7636
- return { checked: true, tokenConfigured: match[1] === "1" };
7652
+ function inspectHermesCredential(hermesHome, probe) {
7653
+ const executable = normalizeToken(process.env.INLINE_HERMES_BIN) || "hermes";
7654
+ const args = ["inline", "status", "--json", ...probe ? ["--probe"] : []];
7655
+ const result = spawnSync(executable, args, {
7656
+ encoding: "utf8",
7657
+ timeout: hermesMachineTimeoutMs,
7658
+ maxBuffer: hermesMachineMaxBytes,
7659
+ env: { ...process.env, HERMES_HOME: hermesHome }
7660
+ });
7661
+ const raw = typeof result.stdout === "string" ? result.stdout.trim() : "";
7662
+ if (!raw || Buffer.byteLength(raw) > hermesMachineMaxBytes) {
7663
+ return { checked: false, configured: null, state: "unknown" };
7637
7664
  }
7638
- return { checked: false, tokenConfigured: false };
7639
- }
7640
- function hermesPythonCandidates() {
7641
- const candidates = ["/opt/hermes/.venv/bin/python"];
7642
- const hermesCommand = spawnSync("which", ["hermes"], { encoding: "utf8", timeout: 1000 });
7643
- const hermesPath = normalizeToken(hermesCommand.stdout);
7644
- if (hermesCommand.status === 0 && hermesPath) {
7645
- try {
7646
- const binDir = path.dirname(realpathSync(hermesPath));
7647
- candidates.push(path.join(binDir, "python"), path.join(binDir, "python3"));
7648
- } catch {}
7665
+ let payload;
7666
+ try {
7667
+ payload = JSON.parse(raw);
7668
+ } catch {
7669
+ return { checked: false, configured: null, state: "unknown" };
7670
+ }
7671
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
7672
+ return { checked: false, configured: null, state: "unknown" };
7673
+ }
7674
+ const record = payload;
7675
+ if (record.action !== "inline.status" || typeof record.setupProtocolVersion !== "number" || record.setupProtocolVersion < 1 || typeof record.configured !== "boolean") {
7676
+ return { checked: false, configured: null, state: "unknown" };
7677
+ }
7678
+ if (!record.configured) {
7679
+ return { checked: true, configured: false, state: "not_configured" };
7680
+ }
7681
+ if (!probe) {
7682
+ return { checked: true, configured: true, state: "configured_unverified" };
7683
+ }
7684
+ const probeResult = record.probe && typeof record.probe === "object" && !Array.isArray(record.probe) ? record.probe : null;
7685
+ if (probeResult?.ok === true) {
7686
+ const botUserId = typeof probeResult.botUserId === "string" && /^[1-9]\d*$/.test(probeResult.botUserId) ? probeResult.botUserId : undefined;
7687
+ if (!botUserId)
7688
+ return { checked: false, configured: true, state: "unknown" };
7689
+ const botUsername = typeof probeResult.botUsername === "string" && probeResult.botUsername.trim() ? probeResult.botUsername.trim() : undefined;
7690
+ return {
7691
+ checked: true,
7692
+ configured: true,
7693
+ state: "verified",
7694
+ botUserId,
7695
+ ...botUsername ? { botUsername } : {}
7696
+ };
7697
+ }
7698
+ if (probeResult?.errorKind === "invalid_credential") {
7699
+ return { checked: true, configured: true, state: "invalid" };
7649
7700
  }
7650
- candidates.push("python3", "python");
7651
- return [...new Set(candidates)];
7701
+ return { checked: true, configured: true, state: "unknown" };
7652
7702
  }
7653
7703
  function readConfigList(config, pathParts) {
7654
7704
  const value = readConfigValue(config, pathParts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inline-chat/hermes-agent-adapter",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
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",
@@ -31,6 +31,7 @@
31
31
  "plugin/inline/__init__.py",
32
32
  "plugin/inline/adapter.py",
33
33
  "plugin/inline/cli.py",
34
+ "plugin/inline/message_actions.py",
34
35
  "plugin/inline/plugin.yaml",
35
36
  "plugin/inline/tools.py",
36
37
  "plugin/inline/sidecar/index.mjs"
@@ -60,7 +61,7 @@
60
61
  "check": "bun run typecheck && bun run lint && bun run test"
61
62
  },
62
63
  "dependencies": {
63
- "@inline-chat/realtime-sdk": "0.0.15",
64
+ "@inline-chat/realtime-sdk": "0.0.16",
64
65
  "yaml": "2.9.0"
65
66
  },
66
67
  "devDependencies": {
@@ -80,7 +81,7 @@
80
81
  },
81
82
  "machineSetupProtocol": 1,
82
83
  "minHermesVersion": "0.17.0",
83
- "testedHermesVersion": "0.20.0",
84
- "testedHermesCommit": "3c27eb6"
84
+ "testedHermesVersion": "0.20.6",
85
+ "testedHermesCommit": "31e41eed"
85
86
  }
86
87
  }