@ozaiya/openclaw-channel 0.10.51 → 0.10.53
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/actionConfirmation.d.ts +45 -0
- package/dist/src/actionConfirmation.js +117 -8
- package/dist/src/actionConfirmation.js.map +1 -1
- package/dist/src/botEmail.d.ts +32 -3
- package/dist/src/botEmail.js +388 -64
- package/dist/src/botEmail.js.map +1 -1
- package/dist/src/botMcp.d.ts +10 -3
- package/dist/src/botMcp.js +16 -4
- package/dist/src/botMcp.js.map +1 -1
- package/dist/src/channel.js +89 -11
- package/dist/src/channel.js.map +1 -1
- package/dist/src/mailDocuments.d.ts +3 -1
- package/dist/src/mailDocuments.js +72 -6
- package/dist/src/mailDocuments.js.map +1 -1
- package/dist/src/mailHtml.d.ts +8 -0
- package/dist/src/mailHtml.js +79 -0
- package/dist/src/mailHtml.js.map +1 -0
- package/dist/src/mailSchedule.d.ts +29 -0
- package/dist/src/mailSchedule.js +89 -0
- package/dist/src/mailSchedule.js.map +1 -0
- package/package.json +1 -1
package/dist/src/channel.js
CHANGED
|
@@ -30,8 +30,9 @@ import { summarizeWithYuanbao } from "./yuanbao.js";
|
|
|
30
30
|
import { summarizeWithDoubao } from "./doubao.js";
|
|
31
31
|
import { fetchXueqiuPost, searchXueqiuPosts } from "./xueqiu.js";
|
|
32
32
|
import { fetchSocialMediaPost, searchSocialMedia, extractSocialMediaContent } from "./socialMedia.js";
|
|
33
|
-
import { requestConfirmation, parseActionCallback, resolveConfirmation } from "./actionConfirmation.js";
|
|
33
|
+
import { requestConfirmation, requestLastingConfirmation, parseActionCallback, resolveConfirmation, restoreParkedActions, takeParkedAction } from "./actionConfirmation.js";
|
|
34
34
|
import { STAGED_NAME_SUFFIX, createBotEmailTool, emailAddonOf, newMailNotice, watchMailboxes } from "./botEmail.js";
|
|
35
|
+
import { cancelScheduledMail, restoreScheduledMails, scheduleMail, scheduledMails } from "./mailSchedule.js";
|
|
35
36
|
import { createBotMcpTool, mcpAddonOf } from "./botMcp.js";
|
|
36
37
|
import { lastChatFromSessionStore } from "./lastChat.js";
|
|
37
38
|
import { writeBotLocalSettings } from "./botLocalSettings.js";
|
|
@@ -109,6 +110,8 @@ function categorize(toolName) {
|
|
|
109
110
|
}
|
|
110
111
|
// Active dispatches keyed by accountId (one dispatch at a time per account)
|
|
111
112
|
const activeDispatches = new Map();
|
|
113
|
+
/** Bots whose parked approval cards were restored after this gateway started. */
|
|
114
|
+
const parkedRestored = new Set();
|
|
112
115
|
/** Bot id → the chat a new-mail turn is running in (the owner's chat with the bot). */
|
|
113
116
|
const mailTurns = new Map();
|
|
114
117
|
/**
|
|
@@ -1121,6 +1124,43 @@ async function sendEncryptedChatContent(params) {
|
|
|
1121
1124
|
recordState(account.accountId, { lastOutboundAt: Date.now() });
|
|
1122
1125
|
return result;
|
|
1123
1126
|
}
|
|
1127
|
+
/** Deps for a lasting card (mail, business-system writes): parked actions live in the gateway's state dir. */
|
|
1128
|
+
function makeLastingDeps(account) {
|
|
1129
|
+
return { ...makeConfirmationDeps(account), accountId: account.accountId, stateDir: getOzaiyaRuntime().state.resolveStateDir() };
|
|
1130
|
+
}
|
|
1131
|
+
/** A lasting card's answer as the tools take it: true / false now, or "later" (runs on a later Approve). */
|
|
1132
|
+
async function askOwnerLasting(account, groupId, kind, label, params, args) {
|
|
1133
|
+
if (!groupId)
|
|
1134
|
+
return false;
|
|
1135
|
+
const answer = await requestLastingConfirmation(makeLastingDeps(account), groupId, kind, params, label, { tool: kind, args });
|
|
1136
|
+
return answer === "later" ? "later" : answer === "approved";
|
|
1137
|
+
}
|
|
1138
|
+
/** The mcp tool for this bot; its writes ask on a lasting card in the owner's chat. */
|
|
1139
|
+
function createMcpToolFor(account, config) {
|
|
1140
|
+
return createBotMcpTool(account, config, {
|
|
1141
|
+
askOwner: (label, params, args) => askOwnerLasting(account, config.ownerGroupId, "mcp", label, params, args),
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* The owner approved a parked card after its first wait: do it now with the tool it came from,
|
|
1146
|
+
* approval already given, and post what came of it in that chat.
|
|
1147
|
+
*/
|
|
1148
|
+
async function runParkedAction(account, cfg, action) {
|
|
1149
|
+
const zh = /[\u3400-\u9fff]/.test(`${action.label} ${JSON.stringify(action.card.params)}`);
|
|
1150
|
+
let result;
|
|
1151
|
+
if (action.tool === "email") {
|
|
1152
|
+
const config = emailAddonOf(account);
|
|
1153
|
+
result = config ? await createEmailToolFor(account, config, cfg).runApproved(action.args) : zh ? "邮箱 add-on 已关闭,没有执行。" : "The email add-on is off; nothing was done.";
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
const config = mcpAddonOf(account);
|
|
1157
|
+
result = config ? await createMcpToolFor(account, config).runApproved(action.args) : zh ? "业务系统 add-on 已关闭,没有执行。" : "The business-system add-on is off; nothing was done.";
|
|
1158
|
+
}
|
|
1159
|
+
// What the tool tells the model, minus the data markers meant for it.
|
|
1160
|
+
const shown = result.replace(/^<<(end )?external [^>]*>>\n?/gm, "").trim();
|
|
1161
|
+
const text = `${zh ? "✅ 已按你的同意执行" : "✅ Done on your approval"}:${action.label}\n${shown.length > 1500 ? `${shown.slice(0, 1500)}…` : shown}`;
|
|
1162
|
+
await sendEncryptedChatContent({ account, groupId: action.groupId, content: { text } }).catch(() => { });
|
|
1163
|
+
}
|
|
1124
1164
|
/** Build ConfirmationDeps for the action confirmation module. */
|
|
1125
1165
|
function makeConfirmationDeps(account) {
|
|
1126
1166
|
return {
|
|
@@ -1571,11 +1611,8 @@ function buildChannelTools(account, cfg) {
|
|
|
1571
1611
|
tools.push(createEmailToolFor(account, emailConfig, cfg));
|
|
1572
1612
|
// The owner's business systems over MCP (mcp add-on): one tool, write operations behind a card.
|
|
1573
1613
|
const mcpConfig = mcpAddonOf(account);
|
|
1574
|
-
if (mcpConfig)
|
|
1575
|
-
tools.push(
|
|
1576
|
-
askOwner: async (label, params) => !!mcpConfig.ownerGroupId && requestConfirmation(makeConfirmationDeps(account), mcpConfig.ownerGroupId, "mcp", params, label),
|
|
1577
|
-
}));
|
|
1578
|
-
}
|
|
1614
|
+
if (mcpConfig)
|
|
1615
|
+
tools.push(createMcpToolFor(account, mcpConfig));
|
|
1579
1616
|
// Plan tool: lets the agent declare + maintain a high-level task plan that the
|
|
1580
1617
|
// app renders as a live "task progress N/M" checklist (distinct from tool steps).
|
|
1581
1618
|
tools.push({
|
|
@@ -2204,6 +2241,11 @@ export const ozaiyaPlugin = {
|
|
|
2204
2241
|
});
|
|
2205
2242
|
ctx.log?.info(`[${botAccount.accountId}] registered webhook at ${botAccount.webhookPath}`);
|
|
2206
2243
|
syncMailWatch(botAccount, ctx.cfg, stateDir, ctx.log);
|
|
2244
|
+
if (!parkedRestored.has(botAccount.accountId)) {
|
|
2245
|
+
parkedRestored.add(botAccount.accountId);
|
|
2246
|
+
void restoreParkedActions(makeLastingDeps(botAccount)).catch(() => { });
|
|
2247
|
+
void restoreScheduledMails(mailScheduleDeps(botAccount, ctx.cfg)).catch(() => { });
|
|
2248
|
+
}
|
|
2207
2249
|
// The app's model + add-on settings, for helpers beside the gateway (e.g. a 闲鱼 inbox watcher).
|
|
2208
2250
|
writeBotLocalSettings(stateDir, botAccount).catch((err) => {
|
|
2209
2251
|
ctx.log?.warn?.(`[${botAccount.accountId}] could not write local settings: ${String(err)}`);
|
|
@@ -3248,11 +3290,7 @@ function createEmailToolFor(account, config, cfg) {
|
|
|
3248
3290
|
const running = [activeDispatches.get(account.accountId)?.groupId, mailTurns.get(account.accountId)].filter(Boolean);
|
|
3249
3291
|
return running.length > 0 && running.every((groupId) => groupId === ownerGroupId);
|
|
3250
3292
|
},
|
|
3251
|
-
askOwner:
|
|
3252
|
-
if (!ownerGroupId)
|
|
3253
|
-
return false;
|
|
3254
|
-
return requestConfirmation(makeConfirmationDeps(account), ownerGroupId, "email", params, label);
|
|
3255
|
-
},
|
|
3293
|
+
askOwner: (label, params, args) => askOwnerLasting(account, ownerGroupId, "email", label, params, args),
|
|
3256
3294
|
// Files the owner sends in that chat are staged here (stageInboundAttachmentsForAgent).
|
|
3257
3295
|
ownerFiles: () => ownerGroupId
|
|
3258
3296
|
? path.join(getOzaiyaRuntime().state.resolveStateDir(), "media", "ozaiya", sanitizePathSegment(ownerGroupId, "group"))
|
|
@@ -3263,8 +3301,33 @@ function createEmailToolFor(account, config, cfg) {
|
|
|
3263
3301
|
const uploaded = await uploadFile(account.apiBaseUrl, account.botToken, ownerGroupId, file.name, resolveAttachmentMime(file.name, file.mime), file.data);
|
|
3264
3302
|
await sendEncryptedChatContent({ account, groupId: ownerGroupId, content: { files: [uploaded], ...(file.caption ? { text: file.caption } : {}) } });
|
|
3265
3303
|
},
|
|
3304
|
+
schedule: async (entry) => {
|
|
3305
|
+
if (!ownerGroupId)
|
|
3306
|
+
throw new Error("no chat with the owner");
|
|
3307
|
+
return (await scheduleMail(mailScheduleDeps(account, cfg), { ...entry, groupId: ownerGroupId })).id;
|
|
3308
|
+
},
|
|
3309
|
+
scheduledList: () => scheduledMails(account.accountId),
|
|
3310
|
+
cancelScheduled: (id) => cancelScheduledMail(mailScheduleDeps(account, cfg), id),
|
|
3266
3311
|
});
|
|
3267
3312
|
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Scheduled sending for a bot: at the time, the email tool — built then, from the bot's config as it
|
|
3315
|
+
* is by then — sends with approval already given, and the outcome goes to the owner's chat.
|
|
3316
|
+
*/
|
|
3317
|
+
function mailScheduleDeps(account, cfg) {
|
|
3318
|
+
return {
|
|
3319
|
+
accountId: account.accountId,
|
|
3320
|
+
stateDir: getOzaiyaRuntime().state.resolveStateDir(),
|
|
3321
|
+
send: async (args) => {
|
|
3322
|
+
const current = gatewayBotAccounts.get(account.accountId) ?? account;
|
|
3323
|
+
const config = emailAddonOf(current);
|
|
3324
|
+
return config ? createEmailToolFor(current, config, cfg).runApproved(args) : "Failed: the email add-on is off.";
|
|
3325
|
+
},
|
|
3326
|
+
notify: async (groupId, text) => {
|
|
3327
|
+
await sendEncryptedChatContent({ account: gatewayBotAccounts.get(account.accountId) ?? account, groupId, content: { text } });
|
|
3328
|
+
},
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3268
3331
|
/**
|
|
3269
3332
|
* New mail reached the bot's own mailbox: run a turn in the owner's chat with the bot, so it tells
|
|
3270
3333
|
* them and, where it may, answers. The mail's headers come in marked as outside data; the agent
|
|
@@ -3372,6 +3435,14 @@ function syncMailWatch(account, cfg, stateDir, log) {
|
|
|
3372
3435
|
const stop = watchMailboxes(account, {
|
|
3373
3436
|
stateDir,
|
|
3374
3437
|
log: { info: (msg) => log?.info?.(msg), warn: (msg) => log?.warn?.(msg) },
|
|
3438
|
+
onLoginRejected: async (mailbox, detail) => {
|
|
3439
|
+
const groupId = config?.ownerGroupId;
|
|
3440
|
+
if (!groupId)
|
|
3441
|
+
return;
|
|
3442
|
+
const text = `⚠️ 邮箱 ${mailbox.email} 登录被邮件服务器拒绝(${detail}),暂时收不到新邮件。\n`
|
|
3443
|
+
+ "如果改过这个邮箱的密码,请在 app 里把邮箱重新添加一次;如果没改过,可能是服务商暂时限制了登录,我会每 30 分钟重试一次,恢复后自动继续。";
|
|
3444
|
+
await sendEncryptedChatContent({ account: gatewayBotAccounts.get(botId) ?? account, groupId, content: { text } });
|
|
3445
|
+
},
|
|
3375
3446
|
onNewMail: (mailbox, mails) => {
|
|
3376
3447
|
// One new-mail turn at a time per bot, with the bot's config as it is by then.
|
|
3377
3448
|
const next = (mailTurnQueues.get(botId) ?? Promise.resolve())
|
|
@@ -4529,6 +4600,13 @@ ctx) {
|
|
|
4529
4600
|
const resolved = await resolveConfirmation(deps, actionCallback.actionId, actionCallback.approved);
|
|
4530
4601
|
if (resolved)
|
|
4531
4602
|
return; // intercepted — don't dispatch to agent
|
|
4603
|
+
// A lasting card answered after its first wait: run the parked action now.
|
|
4604
|
+
const late = await takeParkedAction(makeLastingDeps(account), actionCallback.actionId, actionCallback.approved);
|
|
4605
|
+
if (late.found) {
|
|
4606
|
+
if (late.run)
|
|
4607
|
+
await runParkedAction(account, ctx.cfg, late.run);
|
|
4608
|
+
return;
|
|
4609
|
+
}
|
|
4532
4610
|
}
|
|
4533
4611
|
// Intercept task-control callbacks from the chat task card.
|
|
4534
4612
|
// oz_stop:<taskId> → abort the running dispatch mid-flight (interrupts the LLM turn)
|