@otterware/openclaw-otter 0.0.0-stage → 0.1.1

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/index.js ADDED
@@ -0,0 +1,62 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ createChannelPluginBase,
4
+ createChatChannelPlugin,
5
+ defineChannelPluginEntry,
6
+ } from "openclaw/plugin-sdk/channel-core";
7
+ import { accountOf, inboxKey, OTTER_TARGET, pointerOf, tell } from "./otter.js";
8
+
9
+ async function deliver(ctx, payload) {
10
+ const account = accountOf(ctx.cfg);
11
+ const pointer = pointerOf({ sessionKey: ctx.sessionKey, payload });
12
+ await tell(account, pointer, { signal: ctx.signal });
13
+ return { channel: "otter", messageId: randomUUID(), timestamp: pointer.at };
14
+ }
15
+
16
+ export const otterChannel = createChatChannelPlugin({
17
+ base: {
18
+ ...createChannelPluginBase({
19
+ id: "otter",
20
+ meta: { label: "Otter", blurb: "Send what your agents want you to see to Otter." },
21
+ capabilities: { chatTypes: ["direct"] },
22
+ config: {
23
+ listAccountIds: (cfg) => (cfg?.channels?.otter ? ["default"] : []),
24
+ resolveAccount: (cfg) => accountOf(cfg),
25
+ inspectAccount: (cfg) => {
26
+ const account = accountOf(cfg);
27
+ return {
28
+ enabled: account.enabled,
29
+ configured: Boolean(cfg?.channels?.otter),
30
+ tokenStatus: account.token ? "available" : "missing",
31
+ };
32
+ },
33
+ },
34
+ }),
35
+ messaging: {
36
+ targetResolver: { looksLikeId: () => true, hint: `"${OTTER_TARGET}"` },
37
+ // Every delivery to Otter lands in the agent's Otter session, whatever the gateway's DM scope.
38
+ resolveOutboundSessionRoute: ({ agentId }) => ({
39
+ sessionKey: inboxKey(agentId),
40
+ baseSessionKey: inboxKey(agentId),
41
+ recipientSessionExact: true,
42
+ peer: { kind: "direct", id: OTTER_TARGET },
43
+ chatType: "direct",
44
+ from: "otter",
45
+ to: OTTER_TARGET,
46
+ }),
47
+ },
48
+ },
49
+ outbound: {
50
+ deliveryMode: "direct",
51
+ resolveTarget: () => ({ ok: true, to: OTTER_TARGET }),
52
+ sendText: (ctx) => deliver(ctx),
53
+ sendPayload: (ctx) => deliver(ctx, ctx.payload),
54
+ },
55
+ });
56
+
57
+ export default defineChannelPluginEntry({
58
+ id: "otter",
59
+ name: "Otter",
60
+ description: "Send what your agents want you to see to Otter.",
61
+ plugin: otterChannel,
62
+ });
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "otter",
3
+ "name": "Otter",
4
+ "description": "Send what your agents want you to see to Otter.",
5
+ "activation": { "onStartup": true },
6
+ "channels": ["otter"],
7
+ "configSchema": { "type": "object", "additionalProperties": false, "properties": {} },
8
+ "channelConfigs": {
9
+ "otter": {
10
+ "schema": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "properties": {
14
+ "enabled": { "type": "boolean" },
15
+ "relayUrl": { "type": "string" },
16
+ "token": { "type": "string" }
17
+ }
18
+ },
19
+ "uiHints": {
20
+ "relayUrl": { "label": "Otter relay" },
21
+ "token": { "label": "Otter channel token", "sensitive": true }
22
+ }
23
+ }
24
+ }
25
+ }
package/otter.js ADDED
@@ -0,0 +1,58 @@
1
+ // What the plugin does, apart from OpenClaw's SDK, so Otter's tests can run it.
2
+
3
+ /** Where an agent's deliveries to Otter are kept: one session per agent, read by Otter's apps. */
4
+ export const inboxKey = (agentId) => `agent:${agentId}:otter:inbox`;
5
+
6
+ export const OTTER_TARGET = "inbox";
7
+
8
+ export const agentOf = (sessionKey) =>
9
+ typeof sessionKey === "string" && sessionKey.startsWith("agent:")
10
+ ? sessionKey.split(":")[1]
11
+ : undefined;
12
+
13
+ /**
14
+ * What the relay is told of a delivery: never its text, only where to read it.
15
+ * Otter's apps read it from the gateway themselves.
16
+ */
17
+ export function pointerOf({ sessionKey, payload, at = Date.now() }) {
18
+ const data = payload?.channelData ?? {};
19
+ const approval = data.execApproval;
20
+ const question = data.askUser;
21
+ const session = approval?.sessionKey ?? sessionKey;
22
+ const agentId = approval?.agentId ?? agentOf(session) ?? "main";
23
+ return {
24
+ kind: approval ? "approval" : question ? "question" : "message",
25
+ agentId,
26
+ sessionKey: approval || question ? (session ?? null) : inboxKey(agentId),
27
+ ...(approval && {
28
+ approvalId: String(approval.approvalId),
29
+ approvalKind: approval.approvalKind === "plugin" ? "plugin" : "exec",
30
+ resolved: approval.state === "resolved",
31
+ }),
32
+ ...(question && { questionId: String(question.questionId) }),
33
+ at,
34
+ };
35
+ }
36
+
37
+ export function accountOf(cfg) {
38
+ const section = cfg?.channels?.otter ?? {};
39
+ return {
40
+ accountId: "default",
41
+ enabled: section.enabled !== false,
42
+ relayUrl: typeof section.relayUrl === "string" ? section.relayUrl.replace(/\/+$/, "") : "",
43
+ token: typeof section.token === "string" ? section.token : "",
44
+ };
45
+ }
46
+
47
+ /** Tells Otter's devices; the delivery itself is already kept on the gateway. */
48
+ export async function tell(account, pointer, { fetch = globalThis.fetch, signal } = {}) {
49
+ if (!account.relayUrl || !account.token) return false;
50
+ const response = await fetch(`${account.relayUrl}/channel/v1/pointers`, {
51
+ method: "POST",
52
+ signal,
53
+ headers: { "content-type": "application/json", authorization: `Bearer ${account.token}` },
54
+ body: JSON.stringify(pointer),
55
+ });
56
+ if (!response.ok) throw new Error(`Otter's relay answered ${response.status}`);
57
+ return true;
58
+ }
package/package.json CHANGED
@@ -1,6 +1,36 @@
1
1
  {
2
2
  "name": "@otterware/openclaw-otter",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Otter as an OpenClaw channel: what your agents send you waits in Otter, and Otter tells your devices.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/otterware-app/otter-mail.git",
9
+ "directory": "extensions/openclaw/gateway"
10
+ },
11
+ "files": [
12
+ "index.js",
13
+ "otter.js",
14
+ "openclaw.plugin.json"
15
+ ],
16
+ "type": "module",
17
+ "peerDependencies": {
18
+ "openclaw": ">=2026.11.1"
19
+ },
20
+ "openclaw": {
21
+ "extensions": [
22
+ "./index.js"
23
+ ],
24
+ "channel": {
25
+ "id": "otter",
26
+ "label": "Otter",
27
+ "blurb": "Send what your agents want you to see to Otter."
28
+ },
29
+ "compat": {
30
+ "pluginApi": ">=2026.11.1"
31
+ },
32
+ "build": {
33
+ "openclawVersion": "2026.11.1"
34
+ }
35
+ }
36
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.