@inline-chat/hermes-agent-adapter 0.0.8-alpha.0 → 0.0.9
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 +12 -6
- package/dist/install.js +88 -42
- package/package.json +3 -2
- package/plugin/inline/adapter.py +194 -21
- package/plugin/inline/cli.py +88 -27
- package/plugin/inline/message_actions.py +109 -0
- package/plugin/inline/plugin.yaml +2 -2
- package/plugin/inline/sidecar/index.mjs +1495 -54
- package/plugin/inline/tools.py +162 -12
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
|
|
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
|
|
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.
|
|
150
|
+
enabled user 0.0.9 inline-platform
|
|
150
151
|
```
|
|
151
152
|
|
|
152
153
|
## Update Or Reinstall
|
|
@@ -172,7 +173,7 @@ mismatch, rerun the same command after rebuilding or upgrading the package.
|
|
|
172
173
|
against Hermes Agent `0.20.0` from source commit `3c27eb6`.
|
|
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.
|
|
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
|
|
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
|
@@ -7044,6 +7044,8 @@ function redactUrl(value) {
|
|
|
7044
7044
|
var defaultPluginId = "inline";
|
|
7045
7045
|
var pluginEnableKey = "inline-platform";
|
|
7046
7046
|
var minNodeMajor = 20;
|
|
7047
|
+
var hermesMachineTimeoutMs = 15000;
|
|
7048
|
+
var hermesMachineMaxBytes = 256 * 1024;
|
|
7047
7049
|
async function main(argv = process.argv.slice(2)) {
|
|
7048
7050
|
const opts = parseArgs(argv);
|
|
7049
7051
|
if (opts.command === "help") {
|
|
@@ -7147,7 +7149,7 @@ async function inspectInstall(params) {
|
|
|
7147
7149
|
const sourceSidecar = await inspectSidecar(params.source);
|
|
7148
7150
|
const targetSidecar = targetExists ? await inspectSidecar(params.target) : null;
|
|
7149
7151
|
const node = await inspectNode();
|
|
7150
|
-
const activation = await inspectActivation(params.opts.hermesHome, params.target);
|
|
7152
|
+
const activation = await inspectActivation(params.opts.hermesHome, params.target, params.opts.command === "doctor");
|
|
7151
7153
|
const issues = [];
|
|
7152
7154
|
const warnings = [];
|
|
7153
7155
|
const sidecarMatches = Boolean(targetSidecar?.exists && sourceSidecar.sha256 && targetSidecar.sha256 && sourceSidecar.sha256 === targetSidecar.sha256);
|
|
@@ -7171,11 +7173,33 @@ async function inspectInstall(params) {
|
|
|
7171
7173
|
if (params.opts.command === "doctor" && !activation.pluginEnabled) {
|
|
7172
7174
|
issues.push(`Hermes plugin '${pluginEnableKey}' is not enabled. Run: hermes plugins enable ${pluginEnableKey}`);
|
|
7173
7175
|
}
|
|
7174
|
-
if (targetExists && activation.pluginEnabled && !activation.platformConfigured &&
|
|
7175
|
-
|
|
7176
|
+
if (targetExists && activation.pluginEnabled && !activation.platformConfigured && activation.credentialState === "not_configured") {
|
|
7177
|
+
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";
|
|
7178
|
+
if (params.opts.command === "doctor")
|
|
7179
|
+
issues.push(message);
|
|
7180
|
+
else
|
|
7181
|
+
warnings.push(message);
|
|
7182
|
+
}
|
|
7183
|
+
if (targetExists && activation.pluginEnabled && activation.credentialState === "not_configured") {
|
|
7184
|
+
const message = "Hermes reports that no Inline credential is configured; run `hermes inline setup`";
|
|
7185
|
+
if (params.opts.command === "doctor")
|
|
7186
|
+
issues.push(message);
|
|
7187
|
+
else
|
|
7188
|
+
warnings.push(message);
|
|
7176
7189
|
}
|
|
7177
|
-
if (targetExists && activation.pluginEnabled &&
|
|
7178
|
-
|
|
7190
|
+
if (targetExists && activation.pluginEnabled && activation.credentialState === "invalid") {
|
|
7191
|
+
const message = "Hermes found the Inline credential, but Inline rejected it; reconfigure the credential";
|
|
7192
|
+
if (params.opts.command === "doctor")
|
|
7193
|
+
issues.push(message);
|
|
7194
|
+
else
|
|
7195
|
+
warnings.push(message);
|
|
7196
|
+
}
|
|
7197
|
+
if (targetExists && activation.pluginEnabled && activation.credentialState === "unknown" && activation.hermesCredentialStoreChecked) {
|
|
7198
|
+
const message = "Hermes found the Inline credential, but the requested verification was inconclusive";
|
|
7199
|
+
if (params.opts.command === "doctor")
|
|
7200
|
+
issues.push(message);
|
|
7201
|
+
else
|
|
7202
|
+
warnings.push(message);
|
|
7179
7203
|
}
|
|
7180
7204
|
if (targetExists && !activation.installOwnershipAligned) {
|
|
7181
7205
|
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 +7498,7 @@ async function hasPluginFiles(dir) {
|
|
|
7474
7498
|
"plugin.yaml",
|
|
7475
7499
|
"__init__.py",
|
|
7476
7500
|
"adapter.py",
|
|
7501
|
+
"message_actions.py",
|
|
7477
7502
|
path.join("sidecar", "index.mjs")
|
|
7478
7503
|
];
|
|
7479
7504
|
const checks = files.map((file) => exists(path.join(dir, file)));
|
|
@@ -7538,7 +7563,7 @@ async function inspectNode() {
|
|
|
7538
7563
|
error
|
|
7539
7564
|
};
|
|
7540
7565
|
}
|
|
7541
|
-
async function inspectActivation(hermesHome, target) {
|
|
7566
|
+
async function inspectActivation(hermesHome, target, probeCredential) {
|
|
7542
7567
|
const configPath = path.join(hermesHome, "config.yaml");
|
|
7543
7568
|
let config = null;
|
|
7544
7569
|
let configExists = false;
|
|
@@ -7550,7 +7575,9 @@ async function inspectActivation(hermesHome, target) {
|
|
|
7550
7575
|
}
|
|
7551
7576
|
const tokenPresent = Boolean(process.env.INLINE_TOKEN || process.env.INLINE_BOT_TOKEN);
|
|
7552
7577
|
const configTokenConfigured = configTokenIsConfigured(config, ["platforms", defaultPluginId, "token"]) || configTokenIsConfigured(config, [defaultPluginId, "token"]);
|
|
7553
|
-
const
|
|
7578
|
+
const machineCredential = inspectHermesCredential(hermesHome, probeCredential);
|
|
7579
|
+
const fallbackConfigured = tokenPresent || configTokenConfigured;
|
|
7580
|
+
const credentialState = (machineCredential.state === "unknown" || machineCredential.state === "not_configured") && fallbackConfigured ? "configured_unverified" : machineCredential.state;
|
|
7554
7581
|
const installOwnershipAligned = await inspectInstallOwnership(hermesHome, target);
|
|
7555
7582
|
return {
|
|
7556
7583
|
configPath,
|
|
@@ -7558,10 +7585,13 @@ async function inspectActivation(hermesHome, target) {
|
|
|
7558
7585
|
pluginEnabled: readConfigList(config, ["plugins", "enabled"]).includes(pluginEnableKey),
|
|
7559
7586
|
platformConfigured: readConfigBoolean(config, ["platforms", defaultPluginId, "enabled"]) || readConfigBoolean(config, [defaultPluginId, "enabled"]),
|
|
7560
7587
|
configTokenConfigured,
|
|
7561
|
-
hermesCredentialStoreChecked:
|
|
7562
|
-
hermesCredentialStoreTokenConfigured:
|
|
7588
|
+
hermesCredentialStoreChecked: machineCredential.checked,
|
|
7589
|
+
hermesCredentialStoreTokenConfigured: machineCredential.configured === true,
|
|
7590
|
+
credentialState,
|
|
7591
|
+
...machineCredential.botUserId ? { credentialBotUserId: machineCredential.botUserId } : {},
|
|
7592
|
+
...machineCredential.botUsername ? { credentialBotUsername: machineCredential.botUsername } : {},
|
|
7563
7593
|
tokenPresent,
|
|
7564
|
-
tokenConfigured:
|
|
7594
|
+
tokenConfigured: machineCredential.configured === false ? fallbackConfigured : machineCredential.configured ?? fallbackConfigured,
|
|
7565
7595
|
installOwnershipAligned
|
|
7566
7596
|
};
|
|
7567
7597
|
}
|
|
@@ -7615,40 +7645,56 @@ async function chownTree(target, uid, gid) {
|
|
|
7615
7645
|
}
|
|
7616
7646
|
await chown(target, uid, gid);
|
|
7617
7647
|
}
|
|
7618
|
-
function
|
|
7619
|
-
const
|
|
7620
|
-
const
|
|
7621
|
-
const
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
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" };
|
|
7648
|
+
function inspectHermesCredential(hermesHome, probe) {
|
|
7649
|
+
const executable = normalizeToken(process.env.INLINE_HERMES_BIN) || "hermes";
|
|
7650
|
+
const args = ["inline", "status", "--json", ...probe ? ["--probe"] : []];
|
|
7651
|
+
const result = spawnSync(executable, args, {
|
|
7652
|
+
encoding: "utf8",
|
|
7653
|
+
timeout: hermesMachineTimeoutMs,
|
|
7654
|
+
maxBuffer: hermesMachineMaxBytes,
|
|
7655
|
+
env: { ...process.env, HERMES_HOME: hermesHome }
|
|
7656
|
+
});
|
|
7657
|
+
const raw = typeof result.stdout === "string" ? result.stdout.trim() : "";
|
|
7658
|
+
if (!raw || Buffer.byteLength(raw) > hermesMachineMaxBytes) {
|
|
7659
|
+
return { checked: false, configured: null, state: "unknown" };
|
|
7637
7660
|
}
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
if (
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7661
|
+
let payload;
|
|
7662
|
+
try {
|
|
7663
|
+
payload = JSON.parse(raw);
|
|
7664
|
+
} catch {
|
|
7665
|
+
return { checked: false, configured: null, state: "unknown" };
|
|
7666
|
+
}
|
|
7667
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
7668
|
+
return { checked: false, configured: null, state: "unknown" };
|
|
7669
|
+
}
|
|
7670
|
+
const record = payload;
|
|
7671
|
+
if (record.action !== "inline.status" || typeof record.setupProtocolVersion !== "number" || record.setupProtocolVersion < 1 || typeof record.configured !== "boolean") {
|
|
7672
|
+
return { checked: false, configured: null, state: "unknown" };
|
|
7673
|
+
}
|
|
7674
|
+
if (!record.configured) {
|
|
7675
|
+
return { checked: true, configured: false, state: "not_configured" };
|
|
7676
|
+
}
|
|
7677
|
+
if (!probe) {
|
|
7678
|
+
return { checked: true, configured: true, state: "configured_unverified" };
|
|
7679
|
+
}
|
|
7680
|
+
const probeResult = record.probe && typeof record.probe === "object" && !Array.isArray(record.probe) ? record.probe : null;
|
|
7681
|
+
if (probeResult?.ok === true) {
|
|
7682
|
+
const botUserId = typeof probeResult.botUserId === "string" && /^[1-9]\d*$/.test(probeResult.botUserId) ? probeResult.botUserId : undefined;
|
|
7683
|
+
if (!botUserId)
|
|
7684
|
+
return { checked: false, configured: true, state: "unknown" };
|
|
7685
|
+
const botUsername = typeof probeResult.botUsername === "string" && probeResult.botUsername.trim() ? probeResult.botUsername.trim() : undefined;
|
|
7686
|
+
return {
|
|
7687
|
+
checked: true,
|
|
7688
|
+
configured: true,
|
|
7689
|
+
state: "verified",
|
|
7690
|
+
botUserId,
|
|
7691
|
+
...botUsername ? { botUsername } : {}
|
|
7692
|
+
};
|
|
7693
|
+
}
|
|
7694
|
+
if (probeResult?.errorKind === "invalid_credential") {
|
|
7695
|
+
return { checked: true, configured: true, state: "invalid" };
|
|
7649
7696
|
}
|
|
7650
|
-
|
|
7651
|
-
return [...new Set(candidates)];
|
|
7697
|
+
return { checked: true, configured: true, state: "unknown" };
|
|
7652
7698
|
}
|
|
7653
7699
|
function readConfigList(config, pathParts) {
|
|
7654
7700
|
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.
|
|
3
|
+
"version": "0.0.9",
|
|
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.
|
|
64
|
+
"@inline-chat/realtime-sdk": "0.0.16",
|
|
64
65
|
"yaml": "2.9.0"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|