@ozaiya/openclaw-channel 0.10.35 → 0.10.37
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/channel.js +122 -10
- package/dist/src/channel.js.map +1 -1
- package/dist/src/gateway.js +2 -0
- package/dist/src/gateway.js.map +1 -1
- package/dist/src/types.d.ts +4 -0
- package/package.json +1 -1
- package/types/openclaw-plugin-sdk.d.ts +2 -0
package/dist/src/channel.js
CHANGED
|
@@ -117,6 +117,7 @@ function buildTaskProgressContent(dispatch, completed) {
|
|
|
117
117
|
completedAt: s.completedAt,
|
|
118
118
|
})),
|
|
119
119
|
completed,
|
|
120
|
+
stopped: dispatch.stopped,
|
|
120
121
|
plan: dispatch.plan,
|
|
121
122
|
deliverables: dispatch.deliverables,
|
|
122
123
|
live: !completed,
|
|
@@ -1486,21 +1487,26 @@ function buildChannelTools(account, cfg) {
|
|
|
1486
1487
|
createMakePhoneCallTool(account, cfg),
|
|
1487
1488
|
createHangUpCallTool(account),
|
|
1488
1489
|
createStartInAppCallTool(account, cfg),
|
|
1490
|
+
createRequestUserApprovalTool(account),
|
|
1489
1491
|
];
|
|
1490
|
-
|
|
1492
|
+
// A bot hosted on Ozaiya pays per addon, so its addon-backed tools appear only once the
|
|
1493
|
+
// addon is on; a self-hosted gateway keeps every tool its own config enables.
|
|
1494
|
+
const addonOn = (key) => !account.hosted || !!account.addons?.[key];
|
|
1495
|
+
const imageTool = addonOn("image_gen") ? createGenerateImageTool(account, cfg, resolveForGroup) : null;
|
|
1491
1496
|
if (imageTool)
|
|
1492
1497
|
tools.push(imageTool);
|
|
1493
|
-
|
|
1494
|
-
|
|
1498
|
+
if (addonOn("wechat"))
|
|
1499
|
+
tools.push(createDesktopTool());
|
|
1500
|
+
const xueqiuTool = addonOn("xueqiu") ? createFetchXueqiuTool(cfg) : null;
|
|
1495
1501
|
if (xueqiuTool)
|
|
1496
1502
|
tools.push(xueqiuTool);
|
|
1497
|
-
const xueqiuSearchTool = createSearchXueqiuTool(cfg);
|
|
1503
|
+
const xueqiuSearchTool = addonOn("xueqiu") ? createSearchXueqiuTool(cfg) : null;
|
|
1498
1504
|
if (xueqiuSearchTool)
|
|
1499
1505
|
tools.push(xueqiuSearchTool);
|
|
1500
|
-
const scrapeTool = createScrapeSocialMediaTool(cfg);
|
|
1506
|
+
const scrapeTool = addonOn("social_media") ? createScrapeSocialMediaTool(cfg) : null;
|
|
1501
1507
|
if (scrapeTool)
|
|
1502
1508
|
tools.push(scrapeTool);
|
|
1503
|
-
const summarizeTool = createSummarizeUrlTool(cfg);
|
|
1509
|
+
const summarizeTool = addonOn("web_research") ? createSummarizeUrlTool(cfg) : null;
|
|
1504
1510
|
if (summarizeTool)
|
|
1505
1511
|
tools.push(summarizeTool);
|
|
1506
1512
|
// Plan tool: lets the agent declare + maintain a high-level task plan that the
|
|
@@ -1781,9 +1787,10 @@ export const ozaiyaPlugin = {
|
|
|
1781
1787
|
resolveAccount: (cfg, accountId) => resolveAccount(cfg, accountId),
|
|
1782
1788
|
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
1783
1789
|
isConfigured: (account) => {
|
|
1784
|
-
//
|
|
1790
|
+
// The gateway sentinel is only listed when a userToken (user gateway) or a provisionSecret
|
|
1791
|
+
// (fleet node, whose account has no botToken) is set, so being listed means configured.
|
|
1785
1792
|
if (account.accountId === GATEWAY_ACCOUNT_ID) {
|
|
1786
|
-
return
|
|
1793
|
+
return true;
|
|
1787
1794
|
}
|
|
1788
1795
|
// Gateway-loaded bot accounts are always configured
|
|
1789
1796
|
if (gatewayBotAccounts.has(account.accountId)) {
|
|
@@ -1795,7 +1802,7 @@ export const ozaiyaPlugin = {
|
|
|
1795
1802
|
accountId: account.accountId,
|
|
1796
1803
|
enabled: account.enabled,
|
|
1797
1804
|
configured: account.accountId === GATEWAY_ACCOUNT_ID
|
|
1798
|
-
?
|
|
1805
|
+
? true
|
|
1799
1806
|
: Boolean(account.botToken?.trim() && account.botPrivateKey?.trim()),
|
|
1800
1807
|
webhookPath: account.webhookPath,
|
|
1801
1808
|
baseUrl: account.apiBaseUrl,
|
|
@@ -3060,6 +3067,45 @@ function createStartInAppCallTool(account, cfg) {
|
|
|
3060
3067
|
},
|
|
3061
3068
|
};
|
|
3062
3069
|
}
|
|
3070
|
+
/**
|
|
3071
|
+
* Ask the user before an action that can't be undone — paying, placing an order, submitting a
|
|
3072
|
+
* form with their details, posting or sending something in their name. Shows an Approve/Reject
|
|
3073
|
+
* card in the chat and waits for the answer (or the confirmation timeout).
|
|
3074
|
+
*/
|
|
3075
|
+
function createRequestUserApprovalTool(account) {
|
|
3076
|
+
return {
|
|
3077
|
+
label: "Request User Approval",
|
|
3078
|
+
name: "request_user_approval",
|
|
3079
|
+
ownerOnly: false,
|
|
3080
|
+
description: "Ask the user to approve an action BEFORE doing it. Required before paying or placing an order, " +
|
|
3081
|
+
"submitting a form with their personal details, or sending a message, email or post on their behalf. " +
|
|
3082
|
+
"Shows Approve/Reject buttons in the chat and waits up to 5 minutes. Returns APPROVED or REJECTED; " +
|
|
3083
|
+
"only go ahead on APPROVED.",
|
|
3084
|
+
parameters: {
|
|
3085
|
+
type: "object",
|
|
3086
|
+
properties: {
|
|
3087
|
+
groupId: { type: "string", description: "The chat to ask in (the current conversation)." },
|
|
3088
|
+
action: { type: "string", description: "Short name of the action, e.g. \"Pay ¥128 for 2 movie tickets\"." },
|
|
3089
|
+
details: {
|
|
3090
|
+
type: "object",
|
|
3091
|
+
description: "Key facts the user should check, as short label → value pairs (amount, recipient, item, date…).",
|
|
3092
|
+
additionalProperties: { type: "string" },
|
|
3093
|
+
},
|
|
3094
|
+
},
|
|
3095
|
+
required: ["groupId", "action"],
|
|
3096
|
+
},
|
|
3097
|
+
execute: async (_toolCallId, rawArgs) => {
|
|
3098
|
+
const args = rawArgs;
|
|
3099
|
+
const params = {};
|
|
3100
|
+
for (const [key, value] of Object.entries(args.details ?? {})) {
|
|
3101
|
+
if (value !== undefined && value !== null && value !== "")
|
|
3102
|
+
params[key] = String(value).slice(0, 200);
|
|
3103
|
+
}
|
|
3104
|
+
const approved = await requestConfirmation(makeConfirmationDeps(account), args.groupId, "request_user_approval", params, String(args.action ?? "").slice(0, 120) || undefined);
|
|
3105
|
+
return { content: [{ type: "text", text: approved ? "APPROVED" : "REJECTED (or no answer in time) — do not do it." }] };
|
|
3106
|
+
},
|
|
3107
|
+
};
|
|
3108
|
+
}
|
|
3063
3109
|
function createMakePhoneCallTool(account, cfg) {
|
|
3064
3110
|
return {
|
|
3065
3111
|
label: "Make Phone Call",
|
|
@@ -3406,6 +3452,7 @@ async function handlePhoneAutoTranscript(text, session, account, cfg, groupId) {
|
|
|
3406
3452
|
const bodyForAgent = `${voicePrompt}\n\n${text}`;
|
|
3407
3453
|
const body = `[ozaiya] from: phone caller | at: ${new Date().toISOString()}\n---\n${text}`;
|
|
3408
3454
|
const msgCtx = ch.reply.finalizeInboundContext({
|
|
3455
|
+
GroupSystemPrompt: account.persona,
|
|
3409
3456
|
Body: body,
|
|
3410
3457
|
BodyForAgent: bodyForAgent,
|
|
3411
3458
|
RawBody: text,
|
|
@@ -3819,6 +3866,7 @@ ctx) {
|
|
|
3819
3866
|
});
|
|
3820
3867
|
// Build finalized context for dispatch
|
|
3821
3868
|
const msgCtx = ch.reply.finalizeInboundContext({
|
|
3869
|
+
GroupSystemPrompt: account.persona,
|
|
3822
3870
|
Body: body,
|
|
3823
3871
|
BodyForAgent: effectiveAgentInput,
|
|
3824
3872
|
RawBody: agentInput,
|
|
@@ -3880,6 +3928,7 @@ ctx) {
|
|
|
3880
3928
|
agentId: route.agentId,
|
|
3881
3929
|
title: deriveTaskTitle(effectiveMessageText ?? agentInput),
|
|
3882
3930
|
replyTexts: [],
|
|
3931
|
+
abortController: new AbortController(),
|
|
3883
3932
|
};
|
|
3884
3933
|
activeDispatches.set(account.accountId, dispatch);
|
|
3885
3934
|
// Start screen recording for replay (best-effort; null if nothing recordable).
|
|
@@ -4033,9 +4082,23 @@ ctx) {
|
|
|
4033
4082
|
ctx.log?.warn?.(`ozaiya: reply dispatch error: ${String(err)}`);
|
|
4034
4083
|
},
|
|
4035
4084
|
},
|
|
4036
|
-
|
|
4085
|
+
// The abortSignal lets a "stop" callback interrupt the running turn mid-flight.
|
|
4086
|
+
replyOptions: {
|
|
4087
|
+
abortSignal: dispatch.abortController.signal,
|
|
4088
|
+
...(account.model ? { isHeartbeat: true, heartbeatModelOverride: account.model } : {}),
|
|
4089
|
+
},
|
|
4037
4090
|
});
|
|
4038
4091
|
}
|
|
4092
|
+
catch (err) {
|
|
4093
|
+
// A user-initiated stop aborts the running turn — that rejection is expected,
|
|
4094
|
+
// so don't surface it as a dispatch error (the card is finalized as "stopped" below).
|
|
4095
|
+
if (dispatch.stopped || dispatch.abortController.signal.aborted) {
|
|
4096
|
+
ctx.log?.info?.(`ozaiya: task ${dispatch.taskId} stopped by user`);
|
|
4097
|
+
}
|
|
4098
|
+
else {
|
|
4099
|
+
ctx.log?.warn?.(`ozaiya: reply dispatch threw: ${String(err)}`);
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4039
4102
|
finally {
|
|
4040
4103
|
// Recover an inline table the model forgot to publish_artifact, BEFORE finalizing
|
|
4041
4104
|
// so the final progress card includes the deliverable.
|
|
@@ -4183,6 +4246,52 @@ ctx) {
|
|
|
4183
4246
|
if (resolved)
|
|
4184
4247
|
return; // intercepted — don't dispatch to agent
|
|
4185
4248
|
}
|
|
4249
|
+
// Intercept task-control callbacks from the chat task card.
|
|
4250
|
+
// oz_stop:<taskId> → abort the running dispatch mid-flight (interrupts the LLM turn)
|
|
4251
|
+
// oz_continue:<taskId> → resume the task with a fresh, fully-tracked dispatch
|
|
4252
|
+
if (callbackData.startsWith("oz_stop:")) {
|
|
4253
|
+
const taskId = callbackData.slice("oz_stop:".length);
|
|
4254
|
+
const dispatch = activeDispatches.get(account.accountId);
|
|
4255
|
+
if (dispatch && dispatch.taskId === taskId && !dispatch.abortController.signal.aborted) {
|
|
4256
|
+
dispatch.stopped = true; // makes finalizeTaskProgress emit a "stopped" card
|
|
4257
|
+
ctx.log?.info?.(`ozaiya: stop requested for task ${taskId} — aborting dispatch`);
|
|
4258
|
+
dispatch.abortController.abort();
|
|
4259
|
+
}
|
|
4260
|
+
else {
|
|
4261
|
+
ctx.log?.info?.(`ozaiya: stop requested for task ${taskId} but no matching live dispatch`);
|
|
4262
|
+
}
|
|
4263
|
+
return; // intercepted — don't dispatch to agent
|
|
4264
|
+
}
|
|
4265
|
+
if (callbackData.startsWith("oz_continue:")) {
|
|
4266
|
+
// Resume by feeding a synthetic "continue" message through the normal inbound
|
|
4267
|
+
// path, so it gets full task-progress tracking + a fresh live card. The agent's
|
|
4268
|
+
// session persists across dispatches, so it picks up with the task's full context.
|
|
4269
|
+
const continueText = "继续之前的任务,从你上次停下的地方接着做。\nContinue the previous task from where you stopped.";
|
|
4270
|
+
try {
|
|
4271
|
+
const groupKey = await getGroupKeyOrThrow(account, groupId);
|
|
4272
|
+
const synthetic = {
|
|
4273
|
+
event: "message.new",
|
|
4274
|
+
groupId,
|
|
4275
|
+
groupType: "group",
|
|
4276
|
+
replyAllowed: true,
|
|
4277
|
+
message: {
|
|
4278
|
+
id: `continue-${Date.now()}`,
|
|
4279
|
+
senderId: from,
|
|
4280
|
+
senderName: from,
|
|
4281
|
+
seq: 0,
|
|
4282
|
+
content: encryptMessage({ text: continueText }, groupKey),
|
|
4283
|
+
replyToId: null,
|
|
4284
|
+
createdAt: Date.now(),
|
|
4285
|
+
},
|
|
4286
|
+
};
|
|
4287
|
+
ctx.log?.info?.(`ozaiya: continue requested for task ${callbackData.slice("oz_continue:".length)} — re-dispatching`);
|
|
4288
|
+
await handleInboundMessage(synthetic, ctx);
|
|
4289
|
+
}
|
|
4290
|
+
catch (err) {
|
|
4291
|
+
ctx.log?.warn?.(`ozaiya: continue dispatch failed: ${String(err)}`);
|
|
4292
|
+
}
|
|
4293
|
+
return; // intercepted — don't run the lightweight callback dispatch
|
|
4294
|
+
}
|
|
4186
4295
|
const runtime = getOzaiyaRuntime();
|
|
4187
4296
|
const ch = runtime.channel;
|
|
4188
4297
|
const { callbackBody, callbackCommand, callbackLabel, callbackOptionIndex } = normalizeCallbackQueryPayload({
|
|
@@ -4225,6 +4334,7 @@ ctx) {
|
|
|
4225
4334
|
body: callbackBody,
|
|
4226
4335
|
});
|
|
4227
4336
|
const msgCtx = ch.reply.finalizeInboundContext({
|
|
4337
|
+
GroupSystemPrompt: account.persona,
|
|
4228
4338
|
Body: body,
|
|
4229
4339
|
BodyForAgent: callbackBody,
|
|
4230
4340
|
RawBody: callbackBody,
|
|
@@ -4401,6 +4511,7 @@ ctx) {
|
|
|
4401
4511
|
body: bodyForAgent,
|
|
4402
4512
|
});
|
|
4403
4513
|
const msgCtx = ch.reply.finalizeInboundContext({
|
|
4514
|
+
GroupSystemPrompt: account.persona,
|
|
4404
4515
|
Body: body,
|
|
4405
4516
|
BodyForAgent: bodyForAgent,
|
|
4406
4517
|
RawBody: text,
|
|
@@ -4485,6 +4596,7 @@ ctx) {
|
|
|
4485
4596
|
body: bodyForAgent,
|
|
4486
4597
|
});
|
|
4487
4598
|
const msgCtx = ch.reply.finalizeInboundContext({
|
|
4599
|
+
GroupSystemPrompt: account.persona,
|
|
4488
4600
|
Body: body,
|
|
4489
4601
|
BodyForAgent: bodyForAgent,
|
|
4490
4602
|
RawBody: lastUserMessage,
|