@inline-chat/hermes-agent-adapter 0.0.5 → 0.0.6

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
@@ -27,7 +27,7 @@ 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, and avatar presence/status.
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.
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.
@@ -58,6 +58,12 @@ hermes plugins enable inline-platform
58
58
  hermes gateway setup
59
59
  ```
60
60
 
61
+ When installation runs as root but the Hermes home belongs to its service user,
62
+ the installer automatically aligns the plugin directory ownership with that
63
+ service user. Subsequent `/inline_update` commands replace the durable plugin
64
+ directly; users do not need to maintain a second npm prefix or repeat the
65
+ host-side install commands for routine updates.
66
+
61
67
  Select Inline in the messaging-platform picker. The default path is: go to
62
68
  **Inline → Settings → Bots → Create a new bot**, then paste its token. See the
63
69
  [Inline bot creation guide](https://inline.chat/docs/creating-a-bot). The
@@ -106,10 +112,12 @@ inline-hermes --version
106
112
 
107
113
  `doctor` verifies required plugin files, the Node executable used for the
108
114
  sidecar, the installed sidecar bundle hash, and whether Hermes config enables
109
- the external plugin with `plugins.enabled: [inline-platform]`. It honors
110
- `INLINE_NODE_BIN` and fails if that explicit path is missing, not executable,
111
- older than Node 20, or cannot report a Node version. It also fails if a copied
112
- plugin has drifted from the package bundle.
115
+ the external plugin with `plugins.enabled: [inline-platform]`. It detects
116
+ Inline credentials from the current environment, Hermes config, or the managed
117
+ credential store used by `hermes gateway setup`, without printing token values.
118
+ It honors `INLINE_NODE_BIN` and fails if that explicit path is missing, not
119
+ executable, older than Node 20, or cannot report a Node version. It also fails
120
+ if a copied plugin has drifted from the package bundle.
113
121
 
114
122
  Confirm Hermes sees the plugin:
115
123
 
@@ -121,7 +129,7 @@ uv run ./hermes plugins list --plain --no-bundled
121
129
  Expected local output includes:
122
130
 
123
131
  ```text
124
- enabled user 0.0.5 inline-platform
132
+ enabled user 0.0.6 inline-platform
125
133
  ```
126
134
 
127
135
  ## Update Or Reinstall
@@ -144,10 +152,10 @@ mismatch, rerun the same command after rebuilding or upgrading the package.
144
152
 
145
153
  - Hermes Agent: requires the external user plugin registry and native platform
146
154
  plugin loader available in Hermes Agent `0.17.x`. This package was validated
147
- against Hermes Agent `0.18.2` from source commit `9de9c25`.
155
+ against Hermes Agent `0.19.1` from source commit `cc4cab2`.
148
156
  - Node.js: `>=20` is required for the bundled sidecar. Hermes-managed Node 22,
149
157
  system Node, or an explicit `INLINE_NODE_BIN` path all work.
150
- - Inline transport: the sidecar uses `@inline-chat/realtime-sdk@0.0.13` and is
158
+ - Inline transport: the sidecar uses `@inline-chat/realtime-sdk@0.0.14` and is
151
159
  bundled into the npm package, so Hermes startup does not run `npm install`.
152
160
  - Live sends require a valid Inline user or bot token in `INLINE_TOKEN`,
153
161
  `INLINE_BOT_TOKEN`, `platforms.inline.token`, or `inline.token`.
@@ -254,7 +262,7 @@ Access control follows Hermes' native platform model:
254
262
  | `INLINE_DM_POLICY=open|allowlist|disabled` | Controls direct-message intake. `allowlist` requires `INLINE_ALLOWED_USERS` or config `allow_from`. |
255
263
  | `INLINE_GROUP_POLICY=open|allowlist|disabled` | Controls group intake. `allowlist` requires `INLINE_GROUP_ALLOW_FROM`. |
256
264
  | `INLINE_GROUP_ALLOW_FROM` | Comma-separated Inline user ids allowed to invoke the bot from group chats. |
257
- | `INLINE_REQUIRE_MENTION` | Requires a mention or wake word in groups by default. Replies to the bot and followed eligible reply/fresh threads are accepted without the wake word. |
265
+ | `INLINE_REQUIRE_MENTION` | Requires a mention or wake word in groups by default. Replies to the bot and dialogs already marked `FOLLOWING` are accepted without the wake word. |
258
266
  | `INLINE_STRICT_MENTION` | Requires a mention or wake word on every group turn, including replies to the bot and followed threads. Defaults to `false`. |
259
267
  | `INLINE_ALLOWED_CHATS` | Comma-separated group/thread chat ids where the bot may respond. Parent chat ids also match their Inline reply threads. DMs are not filtered. Empty means no chat restriction. |
260
268
  | `INLINE_FREE_RESPONSE_CHATS` | Comma-separated group/thread chat ids where no mention is required. Parent chat ids also match their Inline reply threads. Useful for dedicated agent rooms. |
@@ -324,7 +332,11 @@ normalizes names to Inline Bot API constraints
324
332
  (`^[a-z0-9_]+$`, max 32 characters), and calls `setMyCommands`. If Inline
325
333
  rejects the full list with `BOT_COMMANDS_TOO_MUCH`, the adapter retries with a
326
334
  smaller prefix. Menu sync failures are logged as warnings and do not prevent
327
- message transport; `/commands` remains the full fallback list.
335
+ message transport; `/commands` remains the full fallback list. In chats with
336
+ multiple bots, Inline may insert `/command@botusername` to disambiguate a menu
337
+ choice. Hermes accepts that form only when the suffix matches its own username,
338
+ then removes the suffix before command dispatch; commands addressed to another
339
+ bot are ignored.
328
340
 
329
341
  Run `/inline_update` to install the newest adapter from the plugin's current
330
342
  npm release channel. Stable installs continue following `latest`, while
@@ -341,7 +353,18 @@ marker, so `hermes logs` and Hermes debug reports can surface the root cause.
341
353
  Run `/follow` in an Inline DM, group, or reply thread to explicitly opt into
342
354
  eligible unmentioned activity waking Hermes. Run `/unfollow` to explicitly opt
343
355
  out; server auto-follow heuristics will not turn following back on, while
344
- mentions and replies retain their normal relevance behavior.
356
+ mentions and replies retain their normal relevance behavior. If an otherwise
357
+ implicit follow/reply wake begins with a concrete mention of another person,
358
+ Hermes treats that explicit address as higher priority and does not respond
359
+ unless the bot is also explicitly mentioned.
360
+
361
+ The model-callable `inline` tool keeps reply and top-level creation separate:
362
+ `create_thread` creates a reply subthread under the current or explicit parent
363
+ chat, while `create_chat` creates a new top-level destination. `create_chat`
364
+ requires a title, defaults to private, accepts optional participant user IDs or
365
+ a parent space ID, and requires both `space_id` and explicit `is_public: true`
366
+ for space-wide visibility. It returns the new chat ID so Hermes can link it in
367
+ the normal reply.
345
368
 
346
369
  The plugin id is `inline`, which is intentionally the same id an eventual
347
370
  bundled Hermes adapter should use.
@@ -349,7 +372,8 @@ bundled Hermes adapter should use.
349
372
  ## Troubleshooting
350
373
 
351
374
  Run `inline-hermes doctor --json` first. It checks the installed plugin path,
352
- required files, Node executable, and source/installed sidecar bundle hashes.
375
+ required files, Node executable, source/installed sidecar bundle hashes, and
376
+ whether an Inline token is available to Hermes.
353
377
 
354
378
  - `plugin is not installed`: run `inline-hermes install`.
355
379
  - `Hermes plugin 'inline-platform' is not enabled`: run
package/dist/install.js CHANGED
@@ -6946,7 +6946,7 @@ var require_public_api = __commonJS((exports) => {
6946
6946
 
6947
6947
  // src/install.ts
6948
6948
  import { constants as fsConstants, existsSync, realpathSync } from "node:fs";
6949
- import { access, cp, lstat, mkdir, readFile, realpath, rm, stat, symlink } from "node:fs/promises";
6949
+ import { access, chown, cp, lchown, lstat, mkdir, readFile, readdir, realpath, rm, stat, symlink } from "node:fs/promises";
6950
6950
  import http from "node:http";
6951
6951
  import { createHash, randomBytes } from "node:crypto";
6952
6952
  import { spawn, spawnSync } from "node:child_process";
@@ -7114,6 +7114,7 @@ async function installPlugin(params) {
7114
7114
  filter: shouldCopyPluginFile
7115
7115
  });
7116
7116
  }
7117
+ await alignInstallOwnership(params.opts.hermesHome, params.target, params.opts.link);
7117
7118
  const after = await inspectInstall(params);
7118
7119
  return {
7119
7120
  ...after,
@@ -7139,7 +7140,7 @@ async function inspectInstall(params) {
7139
7140
  const sourceSidecar = await inspectSidecar(params.source);
7140
7141
  const targetSidecar = targetExists ? await inspectSidecar(params.target) : null;
7141
7142
  const node = await inspectNode();
7142
- const activation = await inspectActivation(params.opts.hermesHome);
7143
+ const activation = await inspectActivation(params.opts.hermesHome, params.target);
7143
7144
  const issues = [];
7144
7145
  const warnings = [];
7145
7146
  if (!sourceValid) {
@@ -7164,7 +7165,10 @@ async function inspectInstall(params) {
7164
7165
  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");
7165
7166
  }
7166
7167
  if (targetExists && activation.pluginEnabled && !activation.tokenConfigured) {
7167
- warnings.push("No Inline token was detected in INLINE_TOKEN, INLINE_BOT_TOKEN, or Hermes Inline config; live Inline realtime checks will not connect");
7168
+ 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");
7169
+ }
7170
+ if (targetExists && !activation.installOwnershipAligned) {
7171
+ warnings.push("Installed plugin ownership differs from the Hermes home owner; internal /inline_update may fail until inline-hermes install --force repairs it");
7168
7172
  }
7169
7173
  return {
7170
7174
  ok: sourceValid && (!targetExists || targetValid) && node.ok && issues.length === 0,
@@ -7488,7 +7492,7 @@ async function inspectNode() {
7488
7492
  error
7489
7493
  };
7490
7494
  }
7491
- async function inspectActivation(hermesHome) {
7495
+ async function inspectActivation(hermesHome, target) {
7492
7496
  const configPath = path.join(hermesHome, "config.yaml");
7493
7497
  let config = null;
7494
7498
  let configExists = false;
@@ -7500,16 +7504,106 @@ async function inspectActivation(hermesHome) {
7500
7504
  }
7501
7505
  const tokenPresent = Boolean(process.env.INLINE_TOKEN || process.env.INLINE_BOT_TOKEN);
7502
7506
  const configTokenConfigured = configTokenIsConfigured(config, ["platforms", defaultPluginId, "token"]) || configTokenIsConfigured(config, [defaultPluginId, "token"]);
7507
+ const hermesCredentialStore = inspectHermesCredentialStore();
7508
+ const installOwnershipAligned = await inspectInstallOwnership(hermesHome, target);
7503
7509
  return {
7504
7510
  configPath,
7505
7511
  configExists,
7506
7512
  pluginEnabled: readConfigList(config, ["plugins", "enabled"]).includes(pluginEnableKey),
7507
7513
  platformConfigured: readConfigBoolean(config, ["platforms", defaultPluginId, "enabled"]) || readConfigBoolean(config, [defaultPluginId, "enabled"]),
7508
7514
  configTokenConfigured,
7515
+ hermesCredentialStoreChecked: hermesCredentialStore.checked,
7516
+ hermesCredentialStoreTokenConfigured: hermesCredentialStore.tokenConfigured,
7509
7517
  tokenPresent,
7510
- tokenConfigured: tokenPresent || configTokenConfigured
7518
+ tokenConfigured: tokenPresent || configTokenConfigured || hermesCredentialStore.tokenConfigured,
7519
+ installOwnershipAligned
7511
7520
  };
7512
7521
  }
7522
+ async function inspectInstallOwnership(hermesHome, target) {
7523
+ if (!await exists(target))
7524
+ return true;
7525
+ try {
7526
+ const homeInfo = await stat(hermesHome);
7527
+ const pluginsInfo = await stat(path.dirname(target));
7528
+ return pluginsInfo.uid === homeInfo.uid && pluginsInfo.gid === homeInfo.gid && await treeOwnershipMatches(target, homeInfo.uid, homeInfo.gid);
7529
+ } catch {
7530
+ return false;
7531
+ }
7532
+ }
7533
+ async function treeOwnershipMatches(target, uid, gid) {
7534
+ const info = await lstat(target);
7535
+ if (info.uid !== uid || info.gid !== gid)
7536
+ return false;
7537
+ if (!info.isDirectory())
7538
+ return true;
7539
+ const entries = await readdir(target);
7540
+ for (const entry of entries) {
7541
+ if (!await treeOwnershipMatches(path.join(target, entry), uid, gid))
7542
+ return false;
7543
+ }
7544
+ return true;
7545
+ }
7546
+ async function alignInstallOwnership(hermesHome, target, linked) {
7547
+ if (typeof process.getuid !== "function" || process.getuid() !== 0)
7548
+ return;
7549
+ const homeInfo = await stat(hermesHome);
7550
+ if (homeInfo.uid === 0)
7551
+ return;
7552
+ await chown(path.dirname(target), homeInfo.uid, homeInfo.gid);
7553
+ if (linked) {
7554
+ await lchown(target, homeInfo.uid, homeInfo.gid);
7555
+ return;
7556
+ }
7557
+ await chownTree(target, homeInfo.uid, homeInfo.gid);
7558
+ }
7559
+ async function chownTree(target, uid, gid) {
7560
+ const info = await lstat(target);
7561
+ if (info.isSymbolicLink()) {
7562
+ await lchown(target, uid, gid);
7563
+ return;
7564
+ }
7565
+ if (info.isDirectory()) {
7566
+ const entries = await readdir(target);
7567
+ for (const entry of entries)
7568
+ await chownTree(path.join(target, entry), uid, gid);
7569
+ }
7570
+ await chown(target, uid, gid);
7571
+ }
7572
+ function inspectHermesCredentialStore() {
7573
+ const configuredPython = normalizeToken(process.env.INLINE_HERMES_PYTHON_BIN);
7574
+ const candidates = configuredPython ? [configuredPython] : hermesPythonCandidates();
7575
+ const probe = [
7576
+ "from hermes_cli import gateway",
7577
+ "configured = bool(gateway.get_env_value('INLINE_TOKEN') or gateway.get_env_value('INLINE_BOT_TOKEN'))",
7578
+ "print('__INLINE_HERMES_TOKEN_CONFIGURED__=' + ('1' if configured else '0'))"
7579
+ ].join("; ");
7580
+ for (const candidate of candidates) {
7581
+ const result = spawnSync(candidate, ["-c", probe], {
7582
+ encoding: "utf8",
7583
+ timeout: 2000,
7584
+ maxBuffer: 64 * 1024
7585
+ });
7586
+ if (result.status !== 0)
7587
+ continue;
7588
+ const match = /^__INLINE_HERMES_TOKEN_CONFIGURED__=([01])$/m.exec(result.stdout || "");
7589
+ if (match)
7590
+ return { checked: true, tokenConfigured: match[1] === "1" };
7591
+ }
7592
+ return { checked: false, tokenConfigured: false };
7593
+ }
7594
+ function hermesPythonCandidates() {
7595
+ const candidates = ["/opt/hermes/.venv/bin/python"];
7596
+ const hermesCommand = spawnSync("which", ["hermes"], { encoding: "utf8", timeout: 1000 });
7597
+ const hermesPath = normalizeToken(hermesCommand.stdout);
7598
+ if (hermesCommand.status === 0 && hermesPath) {
7599
+ try {
7600
+ const binDir = path.dirname(realpathSync(hermesPath));
7601
+ candidates.push(path.join(binDir, "python"), path.join(binDir, "python3"));
7602
+ } catch {}
7603
+ }
7604
+ candidates.push("python3", "python");
7605
+ return [...new Set(candidates)];
7606
+ }
7513
7607
  function readConfigList(config, pathParts) {
7514
7608
  const value = readConfigValue(config, pathParts);
7515
7609
  if (Array.isArray(value))
@@ -7840,8 +7934,14 @@ function printInstallResult(result) {
7840
7934
  printStatus(result);
7841
7935
  if (result.installed || result.dryRun) {
7842
7936
  console.log("");
7843
- console.log(`Enable the Hermes plugin with \`hermes plugins enable ${pluginEnableKey}\`.`);
7844
- console.log("Then run `hermes gateway setup`, select Inline, and follow the guided bot setup.");
7937
+ if (!result.activation.pluginEnabled) {
7938
+ console.log(`Enable the Hermes plugin with \`hermes plugins enable ${pluginEnableKey}\`.`);
7939
+ }
7940
+ if (!result.activation.platformConfigured || !result.activation.tokenConfigured) {
7941
+ console.log("Run `hermes gateway setup`, select Inline, and follow the guided bot setup.");
7942
+ } else if (result.installed) {
7943
+ console.log("Restart the Hermes gateway to load the updated Inline plugin files.");
7944
+ }
7845
7945
  }
7846
7946
  }
7847
7947
  function printTestSendResult(result) {
@@ -7879,6 +7979,7 @@ function printStatus(status) {
7879
7979
  console.log(`node: ${formatNodeInfo(status.node)}`);
7880
7980
  console.log(`hermes plugin enabled: ${status.activation.pluginEnabled ? "yes" : "no"}`);
7881
7981
  console.log(`inline platform configured: ${status.activation.platformConfigured || status.activation.tokenConfigured ? "yes" : "no"}`);
7982
+ console.log(`inline token configured: ${status.activation.tokenConfigured ? "yes" : "no"}`);
7882
7983
  if (status.warnings.length > 0) {
7883
7984
  console.log("warnings:");
7884
7985
  for (const warning of status.warnings)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inline-chat/hermes-agent-adapter",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
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",
@@ -60,7 +60,7 @@
60
60
  "check": "bun run typecheck && bun run lint && bun run test"
61
61
  },
62
62
  "dependencies": {
63
- "@inline-chat/realtime-sdk": "0.0.13",
63
+ "@inline-chat/realtime-sdk": "0.0.14",
64
64
  "yaml": "2.9.0"
65
65
  },
66
66
  "devDependencies": {
@@ -76,7 +76,7 @@
76
76
  "pluginId": "inline",
77
77
  "pluginPath": "plugin/inline",
78
78
  "minHermesVersion": "0.17.0",
79
- "testedHermesVersion": "0.18.2",
80
- "testedHermesCommit": "9de9c25"
79
+ "testedHermesVersion": "0.19.1",
80
+ "testedHermesCommit": "cc4cab2"
81
81
  }
82
82
  }