@ozaiya/openclaw-channel 0.10.37 → 0.10.39
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/dist/src/botEmail.d.ts +85 -0
- package/dist/src/botEmail.js +713 -0
- package/dist/src/botEmail.js.map +1 -0
- package/dist/src/botMcp.d.ts +48 -0
- package/dist/src/botMcp.js +308 -0
- package/dist/src/botMcp.js.map +1 -0
- package/dist/src/channel.js +167 -0
- package/dist/src/channel.js.map +1 -1
- package/dist/src/desktopContainer.d.ts +0 -1
- package/dist/src/gateway.js +38 -6
- package/dist/src/gateway.js.map +1 -1
- package/dist/src/mailCompose.d.ts +40 -0
- package/dist/src/mailCompose.js +50 -0
- package/dist/src/mailCompose.js.map +1 -0
- package/package.json +7 -2
package/dist/src/channel.js
CHANGED
|
@@ -31,6 +31,8 @@ import { summarizeWithDoubao } from "./doubao.js";
|
|
|
31
31
|
import { fetchXueqiuPost, searchXueqiuPosts } from "./xueqiu.js";
|
|
32
32
|
import { fetchSocialMediaPost, searchSocialMedia, extractSocialMediaContent } from "./socialMedia.js";
|
|
33
33
|
import { requestConfirmation, parseActionCallback, resolveConfirmation } from "./actionConfirmation.js";
|
|
34
|
+
import { createBotEmailTool, emailAddonOf, newMailNotice, watchMailboxes } from "./botEmail.js";
|
|
35
|
+
import { createBotMcpTool, mcpAddonOf } from "./botMcp.js";
|
|
34
36
|
const DEFAULT_API_BASE_URL = "https://api.ozaiya.com";
|
|
35
37
|
const DEFAULT_WEBHOOK_PATH = "/ozaiya/webhook";
|
|
36
38
|
const DEFAULT_ACCOUNT_ID = "default";
|
|
@@ -104,6 +106,15 @@ function categorize(toolName) {
|
|
|
104
106
|
}
|
|
105
107
|
// Active dispatches keyed by accountId (one dispatch at a time per account)
|
|
106
108
|
const activeDispatches = new Map();
|
|
109
|
+
/** Bot id → the chat a new-mail turn is running in (the owner's chat with the bot). */
|
|
110
|
+
const mailTurns = new Map();
|
|
111
|
+
/** Bot id → its running mailbox watch, and the config it was started with. */
|
|
112
|
+
const mailWatches = new Map();
|
|
113
|
+
/** Bot id → new-mail turns run one after another. */
|
|
114
|
+
const mailTurnQueues = new Map();
|
|
115
|
+
/** Bot id → when its recent new-mail turns started (a flood of mail doesn't become a flood of turns). */
|
|
116
|
+
const mailTurnTimes = new Map();
|
|
117
|
+
const MAIL_TURNS_PER_HOUR = 20;
|
|
107
118
|
function buildTaskProgressContent(dispatch, completed) {
|
|
108
119
|
return {
|
|
109
120
|
taskProgress: {
|
|
@@ -1509,6 +1520,17 @@ function buildChannelTools(account, cfg) {
|
|
|
1509
1520
|
const summarizeTool = addonOn("web_research") ? createSummarizeUrlTool(cfg) : null;
|
|
1510
1521
|
if (summarizeTool)
|
|
1511
1522
|
tools.push(summarizeTool);
|
|
1523
|
+
// The bot's own mailbox (email add-on): there only once the owner has added one.
|
|
1524
|
+
const emailConfig = emailAddonOf(account);
|
|
1525
|
+
if (emailConfig)
|
|
1526
|
+
tools.push(createEmailToolFor(account, emailConfig));
|
|
1527
|
+
// The owner's business systems over MCP (mcp add-on): one tool, write operations behind a card.
|
|
1528
|
+
const mcpConfig = mcpAddonOf(account);
|
|
1529
|
+
if (mcpConfig) {
|
|
1530
|
+
tools.push(createBotMcpTool(account, mcpConfig, {
|
|
1531
|
+
askOwner: async (label, params) => !!mcpConfig.ownerGroupId && requestConfirmation(makeConfirmationDeps(account), mcpConfig.ownerGroupId, "mcp", params, label),
|
|
1532
|
+
}));
|
|
1533
|
+
}
|
|
1512
1534
|
// Plan tool: lets the agent declare + maintain a high-level task plan that the
|
|
1513
1535
|
// app renders as a live "task progress N/M" checklist (distinct from tool steps).
|
|
1514
1536
|
tools.push({
|
|
@@ -2090,10 +2112,12 @@ export const ozaiyaPlugin = {
|
|
|
2090
2112
|
recordState(botAccount.accountId, { running: false, lastStopAt: Date.now() });
|
|
2091
2113
|
});
|
|
2092
2114
|
ctx.log?.info(`[${botAccount.accountId}] registered webhook at ${botAccount.webhookPath}`);
|
|
2115
|
+
syncMailWatch(botAccount, ctx.cfg, stateDir, ctx.log);
|
|
2093
2116
|
};
|
|
2094
2117
|
const stopBotHandler = (botId) => {
|
|
2095
2118
|
botUnregisters.get(botId)?.();
|
|
2096
2119
|
botUnregisters.delete(botId);
|
|
2120
|
+
stopMailWatch(botId);
|
|
2097
2121
|
gatewayBotAccounts.delete(botId);
|
|
2098
2122
|
};
|
|
2099
2123
|
await startGatewayMode({
|
|
@@ -2170,6 +2194,7 @@ export const ozaiyaPlugin = {
|
|
|
2170
2194
|
groupToActivePhoneCall.clear();
|
|
2171
2195
|
for (const id of gatewayBotAccounts.keys()) {
|
|
2172
2196
|
recordState(id, { running: false, lastStopAt: Date.now() });
|
|
2197
|
+
stopMailWatch(id);
|
|
2173
2198
|
}
|
|
2174
2199
|
gatewayBotAccounts.clear();
|
|
2175
2200
|
recordState(account.accountId, { running: false, lastStopAt: Date.now() });
|
|
@@ -3106,6 +3131,148 @@ function createRequestUserApprovalTool(account) {
|
|
|
3106
3131
|
},
|
|
3107
3132
|
};
|
|
3108
3133
|
}
|
|
3134
|
+
// ─── The bot's own mailbox (email add-on) ───────────────────────────────────
|
|
3135
|
+
/**
|
|
3136
|
+
* The email tool for this bot. Its mailbox may only be looked into from the owner's own chat with
|
|
3137
|
+
* the bot — the chat a message or new-mail turn is running in, when that is the only one running —
|
|
3138
|
+
* and every approval card goes to that chat, whoever asked.
|
|
3139
|
+
*/
|
|
3140
|
+
function createEmailToolFor(account, config) {
|
|
3141
|
+
const ownerGroupId = config.ownerGroupId;
|
|
3142
|
+
return createBotEmailTool(account, config, {
|
|
3143
|
+
inOwnerChat: () => {
|
|
3144
|
+
const running = [activeDispatches.get(account.accountId)?.groupId, mailTurns.get(account.accountId)].filter(Boolean);
|
|
3145
|
+
return !!ownerGroupId && running.length > 0 && running.every((groupId) => groupId === ownerGroupId);
|
|
3146
|
+
},
|
|
3147
|
+
askOwner: async (label, params) => {
|
|
3148
|
+
if (!ownerGroupId)
|
|
3149
|
+
return false;
|
|
3150
|
+
return requestConfirmation(makeConfirmationDeps(account), ownerGroupId, "email", params, label);
|
|
3151
|
+
},
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
/**
|
|
3155
|
+
* New mail reached the bot's own mailbox: run a turn in the owner's chat with the bot, so it tells
|
|
3156
|
+
* them and, where it may, answers. The mail's headers come in marked as outside data; the agent
|
|
3157
|
+
* reads the rest with its email tool.
|
|
3158
|
+
*/
|
|
3159
|
+
async function handleNewMail(account, cfg, log, mailbox, mails) {
|
|
3160
|
+
const config = emailAddonOf(account);
|
|
3161
|
+
const groupId = config?.ownerGroupId;
|
|
3162
|
+
if (!config || !groupId)
|
|
3163
|
+
return;
|
|
3164
|
+
const now = Date.now();
|
|
3165
|
+
const recent = (mailTurnTimes.get(account.accountId) ?? []).filter((t) => now - t < 3_600_000);
|
|
3166
|
+
if (recent.length >= MAIL_TURNS_PER_HOUR) {
|
|
3167
|
+
log?.warn?.(`ozaiya: [${account.accountId}] ${mails.length} new email(s) in ${mailbox.email} not announced: over ${MAIL_TURNS_PER_HOUR} new-mail turns this hour`);
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
mailTurnTimes.set(account.accountId, [...recent, now]);
|
|
3171
|
+
recordState(account.accountId, { lastInboundAt: now });
|
|
3172
|
+
await getGroupKeyOrThrow(account, groupId);
|
|
3173
|
+
const ch = getOzaiyaRuntime().channel;
|
|
3174
|
+
const route = ch.routing.resolveAgentRoute({
|
|
3175
|
+
cfg,
|
|
3176
|
+
channel: "ozaiya",
|
|
3177
|
+
accountId: account.accountId,
|
|
3178
|
+
peer: { kind: "group", id: groupId },
|
|
3179
|
+
});
|
|
3180
|
+
const fromAddress = `ozaiya:group:${groupId}`;
|
|
3181
|
+
const conversationLabel = `group:${groupId}`;
|
|
3182
|
+
const storePath = ch.session.resolveStorePath(undefined, { agentId: route.agentId });
|
|
3183
|
+
const notice = newMailNotice(mailbox, mails, config.replyWithoutAsking !== false);
|
|
3184
|
+
const body = ch.reply.formatAgentEnvelope({
|
|
3185
|
+
channel: "Ozaiya",
|
|
3186
|
+
from: `Email to ${mailbox.email} (${conversationLabel})`,
|
|
3187
|
+
timestamp: now,
|
|
3188
|
+
previousTimestamp: ch.session.readSessionUpdatedAt({ storePath, sessionKey: route.sessionKey }),
|
|
3189
|
+
envelope: ch.reply.resolveEnvelopeFormatOptions(cfg),
|
|
3190
|
+
body: notice,
|
|
3191
|
+
});
|
|
3192
|
+
const msgCtx = ch.reply.finalizeInboundContext({
|
|
3193
|
+
GroupSystemPrompt: account.persona,
|
|
3194
|
+
Body: body,
|
|
3195
|
+
BodyForAgent: notice,
|
|
3196
|
+
RawBody: notice,
|
|
3197
|
+
// Mail text never runs as a command.
|
|
3198
|
+
CommandBody: "",
|
|
3199
|
+
From: fromAddress,
|
|
3200
|
+
To: fromAddress,
|
|
3201
|
+
SessionKey: route.sessionKey,
|
|
3202
|
+
AccountId: route.accountId,
|
|
3203
|
+
ChatType: "direct",
|
|
3204
|
+
ConversationLabel: conversationLabel,
|
|
3205
|
+
GroupSubject: groupId,
|
|
3206
|
+
SenderId: "ozaiya-email",
|
|
3207
|
+
SenderName: "Email",
|
|
3208
|
+
Provider: "ozaiya",
|
|
3209
|
+
Surface: "ozaiya",
|
|
3210
|
+
MessageSid: `email-${mailbox.id}-${mails[mails.length - 1]?.uid ?? 0}-${now}`,
|
|
3211
|
+
Timestamp: now,
|
|
3212
|
+
MessageText: notice,
|
|
3213
|
+
CommandAuthorized: false,
|
|
3214
|
+
OriginatingChannel: "ozaiya",
|
|
3215
|
+
OriginatingTo: fromAddress,
|
|
3216
|
+
});
|
|
3217
|
+
void ch.session.recordInboundSession({
|
|
3218
|
+
storePath,
|
|
3219
|
+
sessionKey: route.sessionKey,
|
|
3220
|
+
ctx: msgCtx,
|
|
3221
|
+
onRecordError: (err) => log?.warn?.(`ozaiya: failed recording new-mail session: ${String(err)}`),
|
|
3222
|
+
}).catch((err) => log?.warn?.(`ozaiya: failed recording new-mail session: ${String(err)}`));
|
|
3223
|
+
mailTurns.set(account.accountId, groupId);
|
|
3224
|
+
try {
|
|
3225
|
+
await ch.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
3226
|
+
ctx: msgCtx,
|
|
3227
|
+
cfg,
|
|
3228
|
+
dispatcherOptions: {
|
|
3229
|
+
deliver: async (replyPayload, _info) => {
|
|
3230
|
+
const replyText = replyPayload.text;
|
|
3231
|
+
if (!replyText?.trim())
|
|
3232
|
+
return;
|
|
3233
|
+
await sendEncryptedChatContent({ account, groupId, content: { text: replyText }, log });
|
|
3234
|
+
},
|
|
3235
|
+
onError: (err) => log?.warn?.(`ozaiya: new-mail turn error: ${String(err)}`),
|
|
3236
|
+
},
|
|
3237
|
+
replyOptions: account.model ? { isHeartbeat: true, heartbeatModelOverride: account.model } : undefined,
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
finally {
|
|
3241
|
+
mailTurns.delete(account.accountId);
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
/**
|
|
3245
|
+
* Start, restart or stop a bot's mailbox watch to match its email add-on (gateway mode). A reload
|
|
3246
|
+
* that leaves the mailboxes and the owner's chat as they were keeps the running watch.
|
|
3247
|
+
*/
|
|
3248
|
+
function syncMailWatch(account, cfg, stateDir, log) {
|
|
3249
|
+
const config = emailAddonOf(account);
|
|
3250
|
+
const signature = config?.ownerGroupId ? JSON.stringify([config.accounts, config.ownerGroupId]) : "";
|
|
3251
|
+
const running = mailWatches.get(account.accountId);
|
|
3252
|
+
if (running?.signature === signature)
|
|
3253
|
+
return;
|
|
3254
|
+
stopMailWatch(account.accountId);
|
|
3255
|
+
if (!signature)
|
|
3256
|
+
return;
|
|
3257
|
+
const botId = account.accountId;
|
|
3258
|
+
const stop = watchMailboxes(account, {
|
|
3259
|
+
stateDir,
|
|
3260
|
+
log: { info: (msg) => log?.info?.(msg), warn: (msg) => log?.warn?.(msg) },
|
|
3261
|
+
onNewMail: (mailbox, mails) => {
|
|
3262
|
+
// One new-mail turn at a time per bot, with the bot's config as it is by then.
|
|
3263
|
+
const next = (mailTurnQueues.get(botId) ?? Promise.resolve())
|
|
3264
|
+
.then(() => handleNewMail(gatewayBotAccounts.get(botId) ?? account, cfg, log, mailbox, mails))
|
|
3265
|
+
.catch((err) => log?.warn?.(`ozaiya: [${botId}] new-mail turn failed: ${String(err)}`));
|
|
3266
|
+
mailTurnQueues.set(botId, next);
|
|
3267
|
+
return next;
|
|
3268
|
+
},
|
|
3269
|
+
});
|
|
3270
|
+
mailWatches.set(botId, { signature, stop });
|
|
3271
|
+
}
|
|
3272
|
+
function stopMailWatch(botId) {
|
|
3273
|
+
mailWatches.get(botId)?.stop();
|
|
3274
|
+
mailWatches.delete(botId);
|
|
3275
|
+
}
|
|
3109
3276
|
function createMakePhoneCallTool(account, cfg) {
|
|
3110
3277
|
return {
|
|
3111
3278
|
label: "Make Phone Call",
|