@otterware/openclaw-otter 0.2.2 → 0.2.4
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 +23 -9
- package/otter.js +38 -11
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -17,12 +17,15 @@ import {
|
|
|
17
17
|
accountOf,
|
|
18
18
|
chatOf,
|
|
19
19
|
chatTarget,
|
|
20
|
+
isNewChat,
|
|
20
21
|
isOwnerConfigured,
|
|
21
22
|
messageOf,
|
|
22
|
-
|
|
23
|
+
newChatKey,
|
|
24
|
+
otterTargetsNote,
|
|
23
25
|
OWNER,
|
|
24
26
|
pointerOf,
|
|
25
|
-
|
|
27
|
+
targetOf,
|
|
28
|
+
TARGETS_HINT,
|
|
26
29
|
tell,
|
|
27
30
|
} from "./otter.js";
|
|
28
31
|
|
|
@@ -61,13 +64,14 @@ export const otterChannel = createChatChannelPlugin({
|
|
|
61
64
|
messaging: {
|
|
62
65
|
targetResolver: {
|
|
63
66
|
looksLikeId: () => true,
|
|
64
|
-
hint:
|
|
67
|
+
hint: TARGETS_HINT,
|
|
65
68
|
},
|
|
66
|
-
// A delivery to a chat is kept in its session;
|
|
69
|
+
// A delivery to a chat is kept in its session; to "chat:new", in a chat it starts (the gateway
|
|
70
|
+
// routes each delivery once, and sends it to that session).
|
|
67
71
|
resolveOutboundSessionRoute: ({ agentId, target }) => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
const
|
|
72
|
+
const to = targetOf(target);
|
|
73
|
+
if (!to) return null;
|
|
74
|
+
const sessionKey = isNewChat(to) ? newChatKey(agentId, randomUUID()) : chatOf(to);
|
|
71
75
|
return {
|
|
72
76
|
sessionKey,
|
|
73
77
|
baseSessionKey: sessionKey,
|
|
@@ -83,8 +87,10 @@ export const otterChannel = createChatChannelPlugin({
|
|
|
83
87
|
outbound: {
|
|
84
88
|
deliveryMode: "direct",
|
|
85
89
|
resolveTarget: (params) => {
|
|
86
|
-
const
|
|
87
|
-
return
|
|
90
|
+
const to = targetOf(params?.to);
|
|
91
|
+
return to
|
|
92
|
+
? { ok: true, to }
|
|
93
|
+
: { ok: false, error: new Error(`Otter's targets are ${TARGETS_HINT}.`) };
|
|
88
94
|
},
|
|
89
95
|
sendText: (ctx) => deliver(ctx),
|
|
90
96
|
sendPayload: (ctx) => deliver(ctx, ctx.payload),
|
|
@@ -155,6 +161,14 @@ export default defineChannelPluginEntry({
|
|
|
155
161
|
description: "Send what your agents want you to see to Otter.",
|
|
156
162
|
plugin: otterChannel,
|
|
157
163
|
registerFull(api) {
|
|
164
|
+
// What Otter's targets are, for a turn in an Otter chat: the Codex harness leaves channels'
|
|
165
|
+
// message-tool hints out of its instructions, but every harness runs this hook.
|
|
166
|
+
api.on("before_prompt_build", (_event, ctx) =>
|
|
167
|
+
ctx?.channel === "otter" || ctx?.messageProvider === "otter"
|
|
168
|
+
? { appendSystemContext: otterTargetsNote(ctx.sessionKey) }
|
|
169
|
+
: undefined,
|
|
170
|
+
);
|
|
171
|
+
|
|
158
172
|
// What you send in an Otter chat, as the gateway's owner: `chat.send`'s run, with the authority
|
|
159
173
|
// a configured channel owner has (an automation another chat made, for one).
|
|
160
174
|
api.registerGatewayMethod(
|
package/otter.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
// What the plugin does, apart from OpenClaw's SDK, so Otter's tests can run it.
|
|
2
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
3
|
/** Who sends what you type in Otter: the gateway's owner once `commands.ownerAllowFrom` lists `otter:owner`. */
|
|
9
4
|
export const OWNER = "owner";
|
|
10
5
|
export const OWNER_ENTRY = `otter:${OWNER}`;
|
|
@@ -12,6 +7,40 @@ export const OWNER_ENTRY = `otter:${OWNER}`;
|
|
|
12
7
|
/** A chat in Otter is a target of its own: what's sent to it is kept in its session. */
|
|
13
8
|
export const chatTarget = (sessionKey) => `chat:${sessionKey}`;
|
|
14
9
|
|
|
10
|
+
/** Like posting to a Slack channel rather than in a thread: each delivery starts a chat of its own. */
|
|
11
|
+
export const NEW_CHAT_TARGET = "chat:new";
|
|
12
|
+
|
|
13
|
+
const bare = (to) => (typeof to === "string" ? to.trim().replace(/^otter:/, "") : "");
|
|
14
|
+
|
|
15
|
+
/** What's sent to the gateway's owner (`otter:owner`) starts a chat, as it names none. */
|
|
16
|
+
export const isNewChat = (to) => bare(to) === NEW_CHAT_TARGET || bare(to) === OWNER;
|
|
17
|
+
|
|
18
|
+
/** A target as the channel keeps it: one of Otter's chats, or a new one each time; else none. */
|
|
19
|
+
export function targetOf(to) {
|
|
20
|
+
const chat = chatOf(to);
|
|
21
|
+
if (chat) return chatTarget(chat);
|
|
22
|
+
return isNewChat(to) ? NEW_CHAT_TARGET : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const TARGETS_HINT =
|
|
26
|
+
'"chat:<session key>" for one of Otter\'s chats, or "chat:new" for a new one';
|
|
27
|
+
|
|
28
|
+
/** A session for a new chat, an Otter chat like one you start. */
|
|
29
|
+
export const newChatKey = (agentId, id) => `agent:${agentId}:otter-mail:${id}`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* What an agent in an Otter chat is told about sending to Otter (the message tool, an automation's
|
|
33
|
+
* delivery), with this chat's own target.
|
|
34
|
+
*/
|
|
35
|
+
export function otterTargetsNote(sessionKey) {
|
|
36
|
+
const here = agentOf(sessionKey) ? chatTarget(sessionKey) : "chat:<session key>";
|
|
37
|
+
return [
|
|
38
|
+
"Otter delivery targets (channel `otter`), like replying in a thread or a new post in Slack:",
|
|
39
|
+
`- "${here}": this chat. Any Otter chat is "chat:<its session key>".`,
|
|
40
|
+
'- "chat:new": a new Otter chat for each delivery (an automation that should post a fresh chat every run, such as a morning brief or an alert).',
|
|
41
|
+
].join("\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
15
44
|
export const agentOf = (sessionKey) =>
|
|
16
45
|
typeof sessionKey === "string" && sessionKey.startsWith("agent:")
|
|
17
46
|
? sessionKey.split(":")[1]
|
|
@@ -19,15 +48,12 @@ export const agentOf = (sessionKey) =>
|
|
|
19
48
|
|
|
20
49
|
/** The chat a target names, if it names one. */
|
|
21
50
|
export function chatOf(to) {
|
|
22
|
-
const target =
|
|
51
|
+
const target = bare(to);
|
|
23
52
|
if (!target.startsWith("chat:")) return undefined;
|
|
24
53
|
const sessionKey = target.slice("chat:".length);
|
|
25
54
|
return agentOf(sessionKey) ? sessionKey : undefined;
|
|
26
55
|
}
|
|
27
56
|
|
|
28
|
-
/** The session a delivery to this target is kept in. */
|
|
29
|
-
export const sessionOf = (to, agentId) => chatOf(to) ?? inboxKey(agentId);
|
|
30
|
-
|
|
31
57
|
export const isOwnerConfigured = (cfg) =>
|
|
32
58
|
Array.isArray(cfg?.commands?.ownerAllowFrom) &&
|
|
33
59
|
cfg.commands.ownerAllowFrom.some((entry) => String(entry).trim() === OWNER_ENTRY);
|
|
@@ -41,12 +67,13 @@ export function pointerOf({ sessionKey, to, payload, at = Date.now() }) {
|
|
|
41
67
|
const approval = data.execApproval;
|
|
42
68
|
const question = data.askUser;
|
|
43
69
|
const session = approval?.sessionKey ?? sessionKey;
|
|
44
|
-
|
|
70
|
+
// A new chat's session is the one its delivery was routed to.
|
|
71
|
+
const chat = chatOf(to) ?? (isNewChat(to) && agentOf(sessionKey) ? sessionKey : undefined);
|
|
45
72
|
const agentId = approval?.agentId ?? agentOf(chat ?? session) ?? "main";
|
|
46
73
|
return {
|
|
47
74
|
kind: approval ? "approval" : question ? "question" : "message",
|
|
48
75
|
agentId,
|
|
49
|
-
sessionKey: approval || question ? (session ?? null) : (chat ??
|
|
76
|
+
sessionKey: approval || question ? (session ?? null) : (chat ?? session ?? null),
|
|
50
77
|
...(approval && {
|
|
51
78
|
approvalId: String(approval.approvalId),
|
|
52
79
|
approvalKind: approval.approvalKind === "plugin" ? "plugin" : "exec",
|
package/package.json
CHANGED