@aspalagin/openclaw-max 0.7.2
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/LICENSE +21 -0
- package/README.md +424 -0
- package/SKILL.md +108 -0
- package/dist/index.js +17 -0
- package/dist/setup-entry.js +8 -0
- package/dist/src/accounts.js +148 -0
- package/dist/src/actions.js +421 -0
- package/dist/src/api.js +553 -0
- package/dist/src/callbacks.js +111 -0
- package/dist/src/channel-agent-prompt.js +38 -0
- package/dist/src/channel-config.js +103 -0
- package/dist/src/channel-directory.js +84 -0
- package/dist/src/channel-lifecycle.js +219 -0
- package/dist/src/channel-outbound.js +133 -0
- package/dist/src/channel-policy.js +130 -0
- package/dist/src/channel.js +76 -0
- package/dist/src/config-schema.js +133 -0
- package/dist/src/deliver.js +126 -0
- package/dist/src/dispatch.js +186 -0
- package/dist/src/format.js +31 -0
- package/dist/src/inbound-attachments.js +125 -0
- package/dist/src/inbound.js +362 -0
- package/dist/src/media-temp.js +92 -0
- package/dist/src/model-buttons.js +96 -0
- package/dist/src/monitor-types.js +27 -0
- package/dist/src/monitor.js +40 -0
- package/dist/src/onboarding.js +180 -0
- package/dist/src/polling.js +98 -0
- package/dist/src/presentation.js +374 -0
- package/dist/src/runtime.js +36 -0
- package/dist/src/russian-trusted-ca.js +84 -0
- package/dist/src/send.js +612 -0
- package/dist/src/state.js +111 -0
- package/dist/src/sticker-cache.js +26 -0
- package/dist/src/stream-draft.js +102 -0
- package/dist/src/types.js +5 -0
- package/dist/src/webhook-queue.js +215 -0
- package/dist/src/webhook-runner.js +233 -0
- package/dist/src/webhook.js +196 -0
- package/openclaw.plugin.json +263 -0
- package/package.json +100 -0
- package/scripts/check-cycles.mjs +38 -0
- package/scripts/test-api.mjs +169 -0
- package/scripts/test-send.mjs +162 -0
- package/scripts/update-schema.mjs +48 -0
- package/src/accounts.ts +193 -0
- package/src/actions.ts +501 -0
- package/src/api.ts +758 -0
- package/src/callbacks.ts +140 -0
- package/src/channel-agent-prompt.ts +47 -0
- package/src/channel-config.ts +138 -0
- package/src/channel-directory.ts +90 -0
- package/src/channel-lifecycle.ts +256 -0
- package/src/channel-outbound.ts +159 -0
- package/src/channel-policy.ts +185 -0
- package/src/channel.ts +99 -0
- package/src/config-schema.ts +154 -0
- package/src/deliver.ts +168 -0
- package/src/dispatch.ts +224 -0
- package/src/format.ts +33 -0
- package/src/inbound-attachments.ts +152 -0
- package/src/inbound.ts +412 -0
- package/src/media-temp.ts +103 -0
- package/src/model-buttons.ts +150 -0
- package/src/monitor-types.ts +61 -0
- package/src/monitor.ts +49 -0
- package/src/onboarding.ts +215 -0
- package/src/polling.ts +125 -0
- package/src/presentation.ts +452 -0
- package/src/runtime.ts +56 -0
- package/src/russian-trusted-ca.ts +86 -0
- package/src/send.ts +774 -0
- package/src/state.ts +149 -0
- package/src/sticker-cache.ts +28 -0
- package/src/stream-draft.ts +125 -0
- package/src/types.ts +263 -0
- package/src/webhook-queue.ts +248 -0
- package/src/webhook-runner.ts +296 -0
- package/src/webhook.ts +268 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MessagePresentation → MAX message (text in MAX markdown + inline_keyboard).
|
|
3
|
+
*
|
|
4
|
+
* Contract: docs/plugins/message-presentation.md. Core adapts a presentation to
|
|
5
|
+
* `MAX_PRESENTATION_CAPABILITIES` and calls `renderMaxPresentation`; the result
|
|
6
|
+
* is an ordinary payload with `channelData.max.buttons`, sent by sendPayload.
|
|
7
|
+
* Channel-local funnels (agent replies, the message tool) go through the same
|
|
8
|
+
* `renderPresentationForDelivery` so they behave identically.
|
|
9
|
+
*
|
|
10
|
+
* Presentation actions become transport-private callback payloads (≤1024
|
|
11
|
+
* bytes) that `decodeMaxPresentationCallback` maps back on `message_callback`:
|
|
12
|
+
* - command → the command text itself ("/status"): it re-enters as a user
|
|
13
|
+
* message and takes core's native command path;
|
|
14
|
+
* - callback → "mxcb1:<value>": opaque, delivered to the agent as
|
|
15
|
+
* "callback_data: <value>" and never parsed as a slash command;
|
|
16
|
+
* - approval → "mxa1:<e|p|s>:<o|a|d>:<approvalId>": resolved through the
|
|
17
|
+
* canonical approval service (approval-gateway-runtime);
|
|
18
|
+
* - question → "mxq1:<questionId>:<optionValue>": resolved through
|
|
19
|
+
* question-gateway-runtime;
|
|
20
|
+
* - url / URL-backed web-app → a MAX `link` button.
|
|
21
|
+
*/
|
|
22
|
+
import { renderMessagePresentationFallbackText, renderPresentationForDelivery, resolveMessagePresentationButtonAction, resolveMessagePresentationOptionAction, } from 'openclaw/plugin-sdk/interactive-runtime';
|
|
23
|
+
/** MAX Bot API limits (schema.yaml: Button.text, CallbackButton.payload, InlineKeyboard). */
|
|
24
|
+
export const MAX_BUTTON_TEXT_LIMIT = 128;
|
|
25
|
+
export const MAX_CALLBACK_PAYLOAD_BYTES = 1024;
|
|
26
|
+
export const MAX_KEYBOARD_ROWS = 30;
|
|
27
|
+
export const MAX_KEYBOARD_BUTTONS = 210;
|
|
28
|
+
export const MAX_LINK_URL_LENGTH = 2048;
|
|
29
|
+
export const MAX_TEXT_LIMIT = 4000;
|
|
30
|
+
/** Buttons per row we render: within MAX's 7 (3 for link/open_app/request_*). */
|
|
31
|
+
const BUTTONS_PER_ROW = 3;
|
|
32
|
+
const SELECT_OPTIONS_PER_ROW = 2;
|
|
33
|
+
export const MAX_PRESENTATION_CAPABILITIES = {
|
|
34
|
+
supported: true,
|
|
35
|
+
buttons: true,
|
|
36
|
+
selects: true,
|
|
37
|
+
context: true,
|
|
38
|
+
divider: true,
|
|
39
|
+
// Rendered as monospace text blocks (MAX has no native charts/tables).
|
|
40
|
+
charts: true,
|
|
41
|
+
tables: true,
|
|
42
|
+
limits: {
|
|
43
|
+
actions: {
|
|
44
|
+
maxActions: MAX_KEYBOARD_BUTTONS,
|
|
45
|
+
maxActionsPerRow: BUTTONS_PER_ROW,
|
|
46
|
+
maxRows: MAX_KEYBOARD_ROWS,
|
|
47
|
+
maxLabelLength: MAX_BUTTON_TEXT_LIMIT,
|
|
48
|
+
// Leave room for the private envelope prefix inside the 1024-byte payload.
|
|
49
|
+
maxValueBytes: 960,
|
|
50
|
+
supportsStyles: false,
|
|
51
|
+
supportsDisabled: false,
|
|
52
|
+
},
|
|
53
|
+
selects: {
|
|
54
|
+
maxOptions: 20,
|
|
55
|
+
maxLabelLength: MAX_BUTTON_TEXT_LIMIT,
|
|
56
|
+
maxValueBytes: 960,
|
|
57
|
+
},
|
|
58
|
+
text: {
|
|
59
|
+
maxLength: MAX_TEXT_LIMIT,
|
|
60
|
+
encoding: 'characters',
|
|
61
|
+
markdownDialect: 'markdown',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
// ── Callback envelopes ──
|
|
66
|
+
const CALLBACK_PREFIX = 'mxcb1:';
|
|
67
|
+
const APPROVAL_PREFIX = 'mxa1:';
|
|
68
|
+
const QUESTION_PREFIX = 'mxq1:';
|
|
69
|
+
const APPROVAL_KIND_CODES = {
|
|
70
|
+
exec: 'e',
|
|
71
|
+
plugin: 'p',
|
|
72
|
+
'system-agent': 's',
|
|
73
|
+
};
|
|
74
|
+
const APPROVAL_DECISION_CODES = {
|
|
75
|
+
'allow-once': 'o',
|
|
76
|
+
'allow-always': 'a',
|
|
77
|
+
deny: 'd',
|
|
78
|
+
};
|
|
79
|
+
function fitsPayload(payload) {
|
|
80
|
+
return payload.length > 0 && Buffer.byteLength(payload, 'utf8') <= MAX_CALLBACK_PAYLOAD_BYTES;
|
|
81
|
+
}
|
|
82
|
+
/** Decode a payload produced by this renderer; null for anything else (legacy buttons). */
|
|
83
|
+
export function decodeMaxPresentationCallback(payload) {
|
|
84
|
+
if (!payload)
|
|
85
|
+
return null;
|
|
86
|
+
if (payload.startsWith(CALLBACK_PREFIX)) {
|
|
87
|
+
const value = payload.slice(CALLBACK_PREFIX.length);
|
|
88
|
+
return value ? { kind: 'callback', value } : null;
|
|
89
|
+
}
|
|
90
|
+
if (payload.startsWith(APPROVAL_PREFIX)) {
|
|
91
|
+
const match = /^mxa1:([eps]):([oad]):(.+)$/su.exec(payload);
|
|
92
|
+
if (!match)
|
|
93
|
+
return null;
|
|
94
|
+
const approvalKind = Object.keys(APPROVAL_KIND_CODES).find((kind) => APPROVAL_KIND_CODES[kind] === match[1]);
|
|
95
|
+
const decision = Object.keys(APPROVAL_DECISION_CODES).find((code) => APPROVAL_DECISION_CODES[code] === match[2]);
|
|
96
|
+
return approvalKind && decision
|
|
97
|
+
? { kind: 'approval', approvalKind, decision, approvalId: match[3] }
|
|
98
|
+
: null;
|
|
99
|
+
}
|
|
100
|
+
if (payload.startsWith(QUESTION_PREFIX)) {
|
|
101
|
+
const rest = payload.slice(QUESTION_PREFIX.length);
|
|
102
|
+
const separator = rest.indexOf(':');
|
|
103
|
+
if (separator <= 0 || separator === rest.length - 1)
|
|
104
|
+
return null;
|
|
105
|
+
return {
|
|
106
|
+
kind: 'question',
|
|
107
|
+
questionId: rest.slice(0, separator),
|
|
108
|
+
optionValue: rest.slice(separator + 1),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function clipLabel(label) {
|
|
114
|
+
const chars = Array.from(label.trim());
|
|
115
|
+
return chars.length <= MAX_BUTTON_TEXT_LIMIT
|
|
116
|
+
? chars.join('')
|
|
117
|
+
: `${chars.slice(0, MAX_BUTTON_TEXT_LIMIT - 1).join('')}…`;
|
|
118
|
+
}
|
|
119
|
+
function callbackButton(label, payload) {
|
|
120
|
+
return fitsPayload(payload)
|
|
121
|
+
? { button: { text: clipLabel(label), type: 'callback', payload } }
|
|
122
|
+
: { dropped: label };
|
|
123
|
+
}
|
|
124
|
+
function linkButton(label, url) {
|
|
125
|
+
const trimmed = url.trim();
|
|
126
|
+
if (!/^https?:\/\//iu.test(trimmed) || trimmed.length > MAX_LINK_URL_LENGTH)
|
|
127
|
+
return { dropped: label };
|
|
128
|
+
return { button: { text: clipLabel(label), type: 'link', url: trimmed } };
|
|
129
|
+
}
|
|
130
|
+
function commandPayload(command) {
|
|
131
|
+
const trimmed = command.trim();
|
|
132
|
+
return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
133
|
+
}
|
|
134
|
+
function toControl(button) {
|
|
135
|
+
const label = button.label;
|
|
136
|
+
const action = resolveMessagePresentationButtonAction(button);
|
|
137
|
+
if (!action || button.disabled)
|
|
138
|
+
return { dropped: label };
|
|
139
|
+
switch (action.type) {
|
|
140
|
+
case 'url':
|
|
141
|
+
return linkButton(label, action.url);
|
|
142
|
+
case 'web-app':
|
|
143
|
+
// MAX open_app addresses a bot's mini app by name, not by URL.
|
|
144
|
+
return action.url ? linkButton(label, action.url) : { dropped: label };
|
|
145
|
+
case 'command':
|
|
146
|
+
return action.command.trim()
|
|
147
|
+
? callbackButton(label, commandPayload(action.command))
|
|
148
|
+
: { dropped: label };
|
|
149
|
+
case 'callback':
|
|
150
|
+
return callbackButton(label, `${CALLBACK_PREFIX}${action.value}`);
|
|
151
|
+
case 'approval': {
|
|
152
|
+
const kind = APPROVAL_KIND_CODES[action.approvalKind];
|
|
153
|
+
const decision = APPROVAL_DECISION_CODES[action.decision];
|
|
154
|
+
if (!kind || !decision || !action.approvalId)
|
|
155
|
+
return { dropped: label };
|
|
156
|
+
return callbackButton(label, `${APPROVAL_PREFIX}${kind}:${decision}:${action.approvalId}`);
|
|
157
|
+
}
|
|
158
|
+
case 'question':
|
|
159
|
+
// custom-input needs a free-text composer target MAX cannot address; the
|
|
160
|
+
// producer states the text route in the message, so the control is omitted.
|
|
161
|
+
if ('intent' in action || action.questionId.includes(':'))
|
|
162
|
+
return { dropped: label };
|
|
163
|
+
return callbackButton(label, `${QUESTION_PREFIX}${action.questionId}:${action.optionValue}`);
|
|
164
|
+
default:
|
|
165
|
+
return { dropped: label };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function toOptionControl(option) {
|
|
169
|
+
const action = resolveMessagePresentationOptionAction(option);
|
|
170
|
+
if (!action)
|
|
171
|
+
return { dropped: option.label };
|
|
172
|
+
return action.type === 'command'
|
|
173
|
+
? callbackButton(option.label, commandPayload(action.command))
|
|
174
|
+
: callbackButton(option.label, `${CALLBACK_PREFIX}${action.value}`);
|
|
175
|
+
}
|
|
176
|
+
// ── Text ──
|
|
177
|
+
const TONE_PREFIX = {
|
|
178
|
+
info: 'ℹ️',
|
|
179
|
+
success: '✅',
|
|
180
|
+
warning: '⚠️',
|
|
181
|
+
danger: '⛔',
|
|
182
|
+
};
|
|
183
|
+
function collapse(value) {
|
|
184
|
+
return String(value).replace(/\s+/gu, ' ').replace(/`/gu, "'").trim();
|
|
185
|
+
}
|
|
186
|
+
function monospaceTable(headers, rows) {
|
|
187
|
+
const widths = headers.map((header, column) => Math.max(Array.from(header).length, ...rows.map((row) => Array.from(row[column] ?? '').length)));
|
|
188
|
+
const pad = (cells) => cells
|
|
189
|
+
.map((cell, column) => cell + ' '.repeat(Math.max(0, widths[column] - Array.from(cell).length)))
|
|
190
|
+
.join(' | ')
|
|
191
|
+
.trimEnd();
|
|
192
|
+
const lines = [
|
|
193
|
+
pad(headers),
|
|
194
|
+
widths.map((width) => '-'.repeat(Math.max(1, width))).join('-+-'),
|
|
195
|
+
...rows.map(pad),
|
|
196
|
+
];
|
|
197
|
+
return ['```', ...lines, '```'].join('\n');
|
|
198
|
+
}
|
|
199
|
+
function renderDataBlock(block) {
|
|
200
|
+
if (block.type === 'table') {
|
|
201
|
+
return [
|
|
202
|
+
`**${collapse(block.caption)}**`,
|
|
203
|
+
monospaceTable(block.headers.map(collapse), block.rows.map((row) => row.map(collapse))),
|
|
204
|
+
].join('\n');
|
|
205
|
+
}
|
|
206
|
+
if (block.chartType === 'pie') {
|
|
207
|
+
const total = block.segments.reduce((sum, segment) => sum + segment.value, 0);
|
|
208
|
+
const rows = block.segments.map((segment) => [
|
|
209
|
+
collapse(segment.label),
|
|
210
|
+
collapse(segment.value),
|
|
211
|
+
total > 0 ? `${((segment.value / total) * 100).toFixed(1)}%` : '',
|
|
212
|
+
]);
|
|
213
|
+
return [`**${collapse(block.title)}** (pie)`, monospaceTable(['', '', '%'], rows)].join('\n');
|
|
214
|
+
}
|
|
215
|
+
const headers = [
|
|
216
|
+
collapse(block.xLabel ?? ''),
|
|
217
|
+
...block.series.map((series) => collapse(series.name)),
|
|
218
|
+
];
|
|
219
|
+
const rows = block.categories.map((category, index) => [
|
|
220
|
+
collapse(category),
|
|
221
|
+
...block.series.map((series) => collapse(series.values[index] ?? '')),
|
|
222
|
+
]);
|
|
223
|
+
const axis = block.yLabel ? `, ${collapse(block.yLabel)}` : '';
|
|
224
|
+
return [
|
|
225
|
+
`**${collapse(block.title)}** (${block.chartType}${axis})`,
|
|
226
|
+
monospaceTable(headers, rows),
|
|
227
|
+
].join('\n');
|
|
228
|
+
}
|
|
229
|
+
function renderContext(text) {
|
|
230
|
+
const trimmed = text.trim();
|
|
231
|
+
return /[_*\n]/u.test(trimmed) ? trimmed : `_${trimmed}_`;
|
|
232
|
+
}
|
|
233
|
+
function chunk(items, size) {
|
|
234
|
+
const rows = [];
|
|
235
|
+
for (let index = 0; index < items.length; index += size)
|
|
236
|
+
rows.push(items.slice(index, index + size));
|
|
237
|
+
return rows;
|
|
238
|
+
}
|
|
239
|
+
/** Pure mapping of an (already adapted) presentation to MAX text + keyboard rows. */
|
|
240
|
+
export function renderMaxPresentationParts(params) {
|
|
241
|
+
const { presentation } = params;
|
|
242
|
+
const baseText = params.text?.trim() ?? '';
|
|
243
|
+
const parts = [];
|
|
244
|
+
const title = presentation.title?.trim();
|
|
245
|
+
if (title && !baseText.startsWith(title)) {
|
|
246
|
+
const tone = presentation.tone ? TONE_PREFIX[presentation.tone] : undefined;
|
|
247
|
+
parts.push(tone ? `${tone} **${title}**` : `**${title}**`);
|
|
248
|
+
}
|
|
249
|
+
if (baseText)
|
|
250
|
+
parts.push(baseText);
|
|
251
|
+
const rows = [];
|
|
252
|
+
const dropped = [];
|
|
253
|
+
let buttonCount = 0;
|
|
254
|
+
const pushRows = (candidates) => {
|
|
255
|
+
for (const row of candidates) {
|
|
256
|
+
const room = Math.min(row.length, MAX_KEYBOARD_BUTTONS - buttonCount);
|
|
257
|
+
if (rows.length >= MAX_KEYBOARD_ROWS || room <= 0) {
|
|
258
|
+
dropped.push(...row.map((button) => button.text));
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
rows.push(row.slice(0, room));
|
|
262
|
+
dropped.push(...row.slice(room).map((button) => button.text));
|
|
263
|
+
buttonCount += room;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
for (const block of presentation.blocks) {
|
|
267
|
+
switch (block.type) {
|
|
268
|
+
case 'text':
|
|
269
|
+
if (block.text.trim())
|
|
270
|
+
parts.push(block.text.trim());
|
|
271
|
+
break;
|
|
272
|
+
case 'context':
|
|
273
|
+
if (block.text.trim())
|
|
274
|
+
parts.push(renderContext(block.text));
|
|
275
|
+
break;
|
|
276
|
+
case 'divider':
|
|
277
|
+
parts.push('———');
|
|
278
|
+
break;
|
|
279
|
+
case 'table':
|
|
280
|
+
case 'chart':
|
|
281
|
+
parts.push(renderDataBlock(block));
|
|
282
|
+
break;
|
|
283
|
+
case 'buttons': {
|
|
284
|
+
const buttons = [];
|
|
285
|
+
for (const control of block.buttons.map(toControl)) {
|
|
286
|
+
if ('button' in control)
|
|
287
|
+
buttons.push(control.button);
|
|
288
|
+
else
|
|
289
|
+
dropped.push(control.dropped);
|
|
290
|
+
}
|
|
291
|
+
pushRows(chunk(buttons, BUTTONS_PER_ROW));
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case 'select': {
|
|
295
|
+
const options = [];
|
|
296
|
+
for (const control of block.options.map(toOptionControl)) {
|
|
297
|
+
if ('button' in control)
|
|
298
|
+
options.push(control.button);
|
|
299
|
+
else
|
|
300
|
+
dropped.push(control.dropped);
|
|
301
|
+
}
|
|
302
|
+
pushRows(chunk(options, SELECT_OPTIONS_PER_ROW));
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (dropped.length > 0) {
|
|
308
|
+
// Label-only fallback, as core renders controls a channel cannot carry.
|
|
309
|
+
const fallback = renderMessagePresentationFallbackText({
|
|
310
|
+
presentation: {
|
|
311
|
+
blocks: [
|
|
312
|
+
{ type: 'buttons', buttons: dropped.map((label) => ({ label, value: 'unavailable' })) },
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
if (fallback)
|
|
317
|
+
parts.push(fallback);
|
|
318
|
+
}
|
|
319
|
+
return { text: parts.join('\n\n'), buttons: rows };
|
|
320
|
+
}
|
|
321
|
+
function readChannelDataMax(channelData) {
|
|
322
|
+
if (!channelData || typeof channelData !== 'object' || Array.isArray(channelData))
|
|
323
|
+
return {};
|
|
324
|
+
const max = channelData.max;
|
|
325
|
+
return max && typeof max === 'object' && !Array.isArray(max)
|
|
326
|
+
? max
|
|
327
|
+
: {};
|
|
328
|
+
}
|
|
329
|
+
/** `renderPresentation` for the outbound adapter: presentation → channelData.max.buttons. */
|
|
330
|
+
export function renderMaxPresentation(payload, presentation) {
|
|
331
|
+
const rendered = renderMaxPresentationParts({ presentation, text: payload.text });
|
|
332
|
+
const existingMax = readChannelDataMax(payload.channelData);
|
|
333
|
+
const existingButtons = Array.isArray(existingMax.buttons)
|
|
334
|
+
? existingMax.buttons
|
|
335
|
+
: [];
|
|
336
|
+
const buttons = [...existingButtons, ...rendered.buttons];
|
|
337
|
+
const rest = { ...payload };
|
|
338
|
+
delete rest.presentation;
|
|
339
|
+
return {
|
|
340
|
+
...rest,
|
|
341
|
+
text: rendered.text,
|
|
342
|
+
...(buttons.length > 0 || payload.channelData
|
|
343
|
+
? {
|
|
344
|
+
channelData: {
|
|
345
|
+
...(payload.channelData ?? {}),
|
|
346
|
+
max: { ...existingMax, ...(buttons.length > 0 ? { buttons } : {}) },
|
|
347
|
+
},
|
|
348
|
+
}
|
|
349
|
+
: {}),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
/** Same adaptation + render + fallback policy core applies on its outbound path. */
|
|
353
|
+
export async function materializeMaxPresentation(payload) {
|
|
354
|
+
if (!payload.presentation)
|
|
355
|
+
return payload;
|
|
356
|
+
return renderPresentationForDelivery({
|
|
357
|
+
presentationCapabilities: MAX_PRESENTATION_CAPABILITIES,
|
|
358
|
+
renderPresentation: (adapted) => renderMaxPresentation(adapted, adapted.presentation),
|
|
359
|
+
}, payload);
|
|
360
|
+
}
|
|
361
|
+
/** Normalize `delivery.pin` (true | {enabled, notify, required}) or the tool's `pin: true`. */
|
|
362
|
+
export function readMaxDeliveryPin(delivery, pinFlag) {
|
|
363
|
+
const raw = delivery && typeof delivery === 'object' && !Array.isArray(delivery)
|
|
364
|
+
? delivery.pin
|
|
365
|
+
: undefined;
|
|
366
|
+
if (raw === true)
|
|
367
|
+
return { enabled: true };
|
|
368
|
+
if (raw && typeof raw === 'object' && raw.enabled) {
|
|
369
|
+
return { enabled: true, notify: raw.notify === true, required: raw.required === true };
|
|
370
|
+
}
|
|
371
|
+
if (pinFlag === true)
|
|
372
|
+
return { enabled: true };
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
let runtime = null;
|
|
2
|
+
export function setMaxRuntime(next) {
|
|
3
|
+
runtime = next;
|
|
4
|
+
}
|
|
5
|
+
export function getMaxRuntime() {
|
|
6
|
+
if (!runtime) {
|
|
7
|
+
throw new Error('MAX runtime not initialized');
|
|
8
|
+
}
|
|
9
|
+
return runtime;
|
|
10
|
+
}
|
|
11
|
+
/** Read the current config snapshot through the plugin runtime, on any supported OpenClaw version. */
|
|
12
|
+
export async function loadMaxConfig() {
|
|
13
|
+
const config = getMaxRuntime().config;
|
|
14
|
+
if (typeof config.current === 'function') {
|
|
15
|
+
return config.current();
|
|
16
|
+
}
|
|
17
|
+
const legacy = config;
|
|
18
|
+
if (typeof legacy.loadConfig === 'function') {
|
|
19
|
+
return await legacy.loadConfig();
|
|
20
|
+
}
|
|
21
|
+
throw new Error('MAX runtime config API unavailable: neither config.current() nor config.loadConfig()');
|
|
22
|
+
}
|
|
23
|
+
/** Persist a full config replacement through the plugin runtime, on any supported OpenClaw version. */
|
|
24
|
+
export async function writeMaxConfig(nextConfig) {
|
|
25
|
+
const config = getMaxRuntime().config;
|
|
26
|
+
if (typeof config.replaceConfigFile === 'function') {
|
|
27
|
+
await config.replaceConfigFile({ nextConfig, afterWrite: { mode: 'auto' } });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const legacy = config;
|
|
31
|
+
if (typeof legacy.writeConfigFile === 'function') {
|
|
32
|
+
await legacy.writeConfigFile(nextConfig);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
throw new Error('MAX runtime config API unavailable: neither config.replaceConfigFile() nor config.writeConfigFile()');
|
|
36
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Russian Trusted Root/Sub CA (Минцифры РФ) — required for TLS to platform-api2.max.ru.
|
|
3
|
+
*
|
|
4
|
+
* Source: https://gu-st.ru/content/lending/russian_trusted_root_ca_pem.crt
|
|
5
|
+
* https://gu-st.ru/content/lending/russian_trusted_sub_ca_pem.crt
|
|
6
|
+
* Root CA valid until 2032-02-27, Sub CA until 2027-03-06.
|
|
7
|
+
* These are used ONLY for MAX API connections (per-dispatcher ca), never installed process-wide.
|
|
8
|
+
*/
|
|
9
|
+
export const RUSSIAN_TRUSTED_ROOT_CA = `
|
|
10
|
+
-----BEGIN CERTIFICATE-----
|
|
11
|
+
MIIFwjCCA6qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
|
|
12
|
+
PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
|
|
13
|
+
ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
|
|
14
|
+
Q0EwHhcNMjIwMzAxMjEwNDE1WhcNMzIwMjI3MjEwNDE1WjBwMQswCQYDVQQGEwJS
|
|
15
|
+
VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
|
|
16
|
+
YW5kIENvbW11bmljYXRpb25zMSAwHgYDVQQDDBdSdXNzaWFuIFRydXN0ZWQgUm9v
|
|
17
|
+
dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMfFOZ8pUAL3+r2n
|
|
18
|
+
qqE0Zp52selXsKGFYoG0GM5bwz1bSFtCt+AZQMhkWQheI3poZAToYJu69pHLKS6Q
|
|
19
|
+
XBiwBC1cvzYmUYKMYZC7jE5YhEU2bSL0mX7NaMxMDmH2/NwuOVRj8OImVa5s1F4U
|
|
20
|
+
zn4Kv3PFlDBjjSjXKVY9kmjUBsXQrIHeaqmUIsPIlNWUnimXS0I0abExqkbdrXbX
|
|
21
|
+
YwCOXhOO2pDUx3ckmJlCMUGacUTnylyQW2VsJIyIGA8V0xzdaeUXg0VZ6ZmNUr5Y
|
|
22
|
+
Ber/EAOLPb8NYpsAhJe2mXjMB/J9HNsoFMBFJ0lLOT/+dQvjbdRZoOT8eqJpWnVD
|
|
23
|
+
U+QL/qEZnz57N88OWM3rabJkRNdU/Z7x5SFIM9FrqtN8xewsiBWBI0K6XFuOBOTD
|
|
24
|
+
4V08o4TzJ8+Ccq5XlCUW2L48pZNCYuBDfBh7FxkB7qDgGDiaftEkZZfApRg2E+M9
|
|
25
|
+
G8wkNKTPLDc4wH0FDTijhgxR3Y4PiS1HL2Zhw7bD3CbslmEGgfnnZojNkJtcLeBH
|
|
26
|
+
BLa52/dSwNU4WWLubaYSiAmA9IUMX1/RpfpxOxd4Ykmhz97oFbUaDJFipIggx5sX
|
|
27
|
+
ePAlkTdWnv+RWBxlJwMQ25oEHmRguNYf4Zr/Rxr9cS93Y+mdXIZaBEE0KS2iLRqa
|
|
28
|
+
OiWBki9IMQU4phqPOBAaG7A+eP8PAgMBAAGjZjBkMB0GA1UdDgQWBBTh0YHlzlpf
|
|
29
|
+
BKrS6badZrHF+qwshzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzAS
|
|
30
|
+
BgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
|
|
31
|
+
AAOCAgEAALIY1wkilt/urfEVM5vKzr6utOeDWCUczmWX/RX4ljpRdgF+5fAIS4vH
|
|
32
|
+
tmXkqpSCOVeWUrJV9QvZn6L227ZwuE15cWi8DCDal3Ue90WgAJJZMfTshN4OI8cq
|
|
33
|
+
W9E4EG9wglbEtMnObHlms8F3CHmrw3k6KmUkWGoa+/ENmcVl68u/cMRl1JbW2bM+
|
|
34
|
+
/3A+SAg2c6iPDlehczKx2oa95QW0SkPPWGuNA/CE8CpyANIhu9XFrj3RQ3EqeRcS
|
|
35
|
+
AQQod1RNuHpfETLU/A2gMmvn/w/sx7TB3W5BPs6rprOA37tutPq9u6FTZOcG1Oqj
|
|
36
|
+
C/B7yTqgI7rbyvox7DEXoX7rIiEqyNNUguTk/u3SZ4VXE2kmxdmSh3TQvybfbnXV
|
|
37
|
+
4JbCZVaqiZraqc7oZMnRoWrXRG3ztbnbes/9qhRGI7PqXqeKJBztxRTEVj8ONs1d
|
|
38
|
+
WN5szTwaPIvhkhO3CO5ErU2rVdUr89wKpNXbBODFKRtgxUT70YpmJ46VVaqdAhOZ
|
|
39
|
+
D9EUUn4YaeLaS8AjSF/h7UkjOibNc4qVDiPP+rkehFWM66PVnP1Msh93tc+taIfC
|
|
40
|
+
EYVMxjh8zNbFuoc7fzvvrFILLe7ifvEIUqSVIC/AzplM/Jxw7buXFeGP1qVCBEHq
|
|
41
|
+
391d/9RAfaZ12zkwFsl+IKwE/OZxW8AHa9i1p4GO0YSNuczzEm4=
|
|
42
|
+
-----END CERTIFICATE-----`;
|
|
43
|
+
export const RUSSIAN_TRUSTED_SUB_CA = `
|
|
44
|
+
-----BEGIN CERTIFICATE-----
|
|
45
|
+
MIIHQjCCBSqgAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
|
|
46
|
+
PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
|
|
47
|
+
ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
|
|
48
|
+
Q0EwHhcNMjIwMzAyMTEyNTE5WhcNMjcwMzA2MTEyNTE5WjBvMQswCQYDVQQGEwJS
|
|
49
|
+
VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
|
|
50
|
+
YW5kIENvbW11bmljYXRpb25zMR8wHQYDVQQDDBZSdXNzaWFuIFRydXN0ZWQgU3Vi
|
|
51
|
+
IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9YPqBKOk19NFymrE
|
|
52
|
+
wehzrhBEgT2atLezpduB24mQ7CiOa/HVpFCDRZzdxqlh8drku408/tTmWzlNH/br
|
|
53
|
+
HuQhZ/miWKOf35lpKzjyBd6TPM23uAfJvEOQ2/dnKGGJbsUo1/udKSvxQwVHpVv3
|
|
54
|
+
S80OlluKfhWPDEXQpgyFqIzPoxIQTLZ0deirZwMVHarZ5u8HqHetRuAtmO2ZDGQn
|
|
55
|
+
vVOJYAjls+Hiueq7Lj7Oce7CQsTwVZeP+XQx28PAaEZ3y6sQEt6rL06ddpSdoTMp
|
|
56
|
+
BnCqTbxW+eWMyjkIn6t9GBtUV45yB1EkHNnj2Ex4GwCiN9T84QQjKSr+8f0psGrZ
|
|
57
|
+
vPbCbQAwNFJjisLixnjlGPLKa5vOmNwIh/LAyUW5DjpkCx004LPDuqPpFsKXNKpa
|
|
58
|
+
L2Dm6uc0x4Jo5m+gUTVORB6hOSzWnWDj2GWfomLzzyjG81DRGFBpco/O93zecsIN
|
|
59
|
+
3SL2Ysjpq1zdoS01CMYxie//9zWvYwzI25/OZigtnpCIrcd2j1Y6dMUFQAzAtHE+
|
|
60
|
+
qsXflSL8HIS+IJEFIQobLlYhHkoE3avgNx5jlu+OLYe0dF0Ykx1PGNjbwqvTX37R
|
|
61
|
+
Cn32NMjlotW2QcGEZhDKj+3urZizp5xdTPZitA+aEjZM/Ni71VOdiOP0igbw6asZ
|
|
62
|
+
2fxdozZ1TnSSYNYvNATwthNmZysCAwEAAaOCAeUwggHhMBIGA1UdEwEB/wQIMAYB
|
|
63
|
+
Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTR4XENCy2BTm6KSo9MI7NM
|
|
64
|
+
XqtpCzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzCBxwYIKwYBBQUH
|
|
65
|
+
AQEEgbowgbcwOwYIKwYBBQUHMAKGL2h0dHA6Ly9yb3N0ZWxlY29tLnJ1L2NkcC9y
|
|
66
|
+
b290Y2Ffc3NsX3JzYTIwMjIuY3J0MDsGCCsGAQUFBzAChi9odHRwOi8vY29tcGFu
|
|
67
|
+
eS5ydC5ydS9jZHAvcm9vdGNhX3NzbF9yc2EyMDIyLmNydDA7BggrBgEFBQcwAoYv
|
|
68
|
+
aHR0cDovL3JlZXN0ci1wa2kucnUvY2RwL3Jvb3RjYV9zc2xfcnNhMjAyMi5jcnQw
|
|
69
|
+
gbAGA1UdHwSBqDCBpTA1oDOgMYYvaHR0cDovL3Jvc3RlbGVjb20ucnUvY2RwL3Jv
|
|
70
|
+
b3RjYV9zc2xfcnNhMjAyMi5jcmwwNaAzoDGGL2h0dHA6Ly9jb21wYW55LnJ0LnJ1
|
|
71
|
+
L2NkcC9yb290Y2Ffc3NsX3JzYTIwMjIuY3JsMDWgM6Axhi9odHRwOi8vcmVlc3Ry
|
|
72
|
+
LXBraS5ydS9jZHAvcm9vdGNhX3NzbF9yc2EyMDIyLmNybDANBgkqhkiG9w0BAQsF
|
|
73
|
+
AAOCAgEARBVzZls79AdiSCpar15dA5Hr/rrT4WbrOfzlpI+xrLeRPrUG6eUWIW4v
|
|
74
|
+
Sui1yx3iqGLCjPcKb+HOTwoRMbI6ytP/ndp3TlYua2advYBEhSvjs+4vDZNwXr/D
|
|
75
|
+
anbwIWdurZmViQRBDFebpkvnIvru/RpWud/5r624Wp8voZMRtj/cm6aI9LtvBfT9
|
|
76
|
+
cfzhOaexI/99c14dyiuk1+6QhdwKaCRTc1mdfNQmnfWNRbfWhWBlK3h4GGE9JK33
|
|
77
|
+
Gk8ZS8DMrkdAh0xby4xAQ/mSWAfWrBmfzlOqGyoB1U47WTOeqNbWkkoAP2ys94+s
|
|
78
|
+
Jg4NTkiDVtXRF6nr6fYi0bSOvOFg0IQrMXO2Y8gyg9ARdPJwKtvWX8VPADCYMiWH
|
|
79
|
+
h4n8bZokIrImVKLDQKHY4jCsND2HHdJfnrdL2YJw1qFskNO4cSNmZydw0Wkgjv9k
|
|
80
|
+
F+KxqrDKlB8MZu2Hclph6v/CZ0fQ9YuE8/lsHZ0Qc2HyiSMnvjgK5fDc3TD4fa8F
|
|
81
|
+
E8gMNurM+kV8PT8LNIM+4Zs+LKEV8nqRWBaxkIVJGekkVKO8xDBOG/aN62AZKHOe
|
|
82
|
+
GcyIdu7yNMMRihGVZCYr8rYiJoKiOzDqOkPkLOPdhtVlgnhowzHDxMHND/E2WA5p
|
|
83
|
+
ZHuNM/m0TXt2wTTPL7JH2YC0gPz/BvvSzjksgzU5rLbRyUKQkgU=
|
|
84
|
+
-----END CERTIFICATE-----`;
|