@sider-ai/chrome-openclaw-sider 1.0.31
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/README.md +75 -0
- package/README.zh_CN.md +108 -0
- package/dist/account-cW9SLuNu.d.ts +75 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +87 -0
- package/dist/setup-entry.d.ts +6 -0
- package/dist/setup-entry.js +11 -0
- package/dist/setup-plugin-api.d.ts +7 -0
- package/dist/setup-plugin-api.js +4 -0
- package/dist/src/account.d.ts +6 -0
- package/dist/src/account.js +260 -0
- package/dist/src/auth.d.ts +30 -0
- package/dist/src/auth.js +225 -0
- package/dist/src/channel-auto-title.d.ts +6 -0
- package/dist/src/channel-auto-title.js +102 -0
- package/dist/src/channel-builders.d.ts +105 -0
- package/dist/src/channel-builders.js +238 -0
- package/dist/src/channel-hooks.d.ts +30 -0
- package/dist/src/channel-hooks.js +380 -0
- package/dist/src/channel-monitor.d.ts +34 -0
- package/dist/src/channel-monitor.js +335 -0
- package/dist/src/channel-parts.d.ts +26 -0
- package/dist/src/channel-parts.js +32 -0
- package/dist/src/channel-relay.d.ts +117 -0
- package/dist/src/channel-relay.js +574 -0
- package/dist/src/channel-runtime.d.ts +33 -0
- package/dist/src/channel-runtime.js +138 -0
- package/dist/src/channel-send-result.d.ts +19 -0
- package/dist/src/channel-send-result.js +126 -0
- package/dist/src/channel-send.d.ts +73 -0
- package/dist/src/channel-send.js +291 -0
- package/dist/src/channel-session-model.d.ts +19 -0
- package/dist/src/channel-session-model.js +244 -0
- package/dist/src/channel-shared.d.ts +153 -0
- package/dist/src/channel-shared.js +96 -0
- package/dist/src/channel-state.d.ts +93 -0
- package/dist/src/channel-state.js +475 -0
- package/dist/src/channel-streaming.d.ts +117 -0
- package/dist/src/channel-streaming.js +681 -0
- package/dist/src/channel-types.d.ts +209 -0
- package/dist/src/channel-types.js +40 -0
- package/dist/src/channel-typing.d.ts +17 -0
- package/dist/src/channel-typing.js +79 -0
- package/dist/src/channel-util.d.ts +78 -0
- package/dist/src/channel-util.js +604 -0
- package/dist/src/channel.d.ts +14 -0
- package/dist/src/channel.js +834 -0
- package/dist/src/channel.setup.d.ts +10 -0
- package/dist/src/channel.setup.js +9 -0
- package/dist/src/config.d.ts +18 -0
- package/dist/src/config.js +38 -0
- package/dist/src/inbound-media.d.ts +37 -0
- package/dist/src/inbound-media.js +148 -0
- package/dist/src/media-upload.d.ts +87 -0
- package/dist/src/media-upload.js +1308 -0
- package/dist/src/setup-core.d.ts +72 -0
- package/dist/src/setup-core.js +343 -0
- package/dist/src/user-agent.d.ts +4 -0
- package/dist/src/user-agent.js +6 -0
- package/openclaw.plugin.json +96 -0
- package/package.json +47 -0
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
|
2
|
+
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/agent-runtime";
|
|
3
|
+
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
|
|
4
|
+
import {
|
|
5
|
+
loadConfig,
|
|
6
|
+
writeConfigFile
|
|
7
|
+
} from "openclaw/plugin-sdk/config-runtime";
|
|
8
|
+
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
|
9
|
+
import {
|
|
10
|
+
formatTextWithAttachmentLinks,
|
|
11
|
+
resolveOutboundMediaUrls
|
|
12
|
+
} from "openclaw/plugin-sdk/reply-payload";
|
|
13
|
+
import {
|
|
14
|
+
describeSiderAccountConfigurationError,
|
|
15
|
+
extractSiderToolSend,
|
|
16
|
+
isSiderAccountBootstrappable,
|
|
17
|
+
listSiderAccountIds,
|
|
18
|
+
looksLikeSiderTargetId,
|
|
19
|
+
normalizeSiderMessagingTarget,
|
|
20
|
+
resolveManagedSiderAccount,
|
|
21
|
+
resolveOutboundSessionId,
|
|
22
|
+
resolveSiderAccount
|
|
23
|
+
} from "./account.js";
|
|
24
|
+
import {
|
|
25
|
+
isFirstTurnInSiderSession,
|
|
26
|
+
resolveAutoTitleConfig,
|
|
27
|
+
scheduleSiderAutoTitleUpdate
|
|
28
|
+
} from "./channel-auto-title.js";
|
|
29
|
+
import {
|
|
30
|
+
maybeResetSiderSessionToCurrentDefaultModel
|
|
31
|
+
} from "./channel-session-model.js";
|
|
32
|
+
import {
|
|
33
|
+
SIDER_CHANNEL_ID,
|
|
34
|
+
SIDER_PUSH_TOOL_ERROR_WARNINGS,
|
|
35
|
+
SIDER_TOOL_ERROR_WARNING_TEXT_RE
|
|
36
|
+
} from "./config.js";
|
|
37
|
+
import { resolveInboundSiderMedia } from "./inbound-media.js";
|
|
38
|
+
import {
|
|
39
|
+
buildOptimisticSiderPartFromInlineAttachment
|
|
40
|
+
} from "./media-upload.js";
|
|
41
|
+
import {
|
|
42
|
+
applySiderSetupAccountConfig,
|
|
43
|
+
createSiderPairingPendingUpdateReporter,
|
|
44
|
+
formatSiderPairingInstructions,
|
|
45
|
+
requestSiderPairing,
|
|
46
|
+
SiderPairingExpiredError,
|
|
47
|
+
waitForSiderPairing
|
|
48
|
+
} from "./setup-core.js";
|
|
49
|
+
import { siderChannelPluginCommon } from "./channel-shared.js";
|
|
50
|
+
import {
|
|
51
|
+
getSiderRuntime,
|
|
52
|
+
logDebug,
|
|
53
|
+
logError,
|
|
54
|
+
logInfo,
|
|
55
|
+
logWarn,
|
|
56
|
+
siderOutboundLogger
|
|
57
|
+
} from "./channel-runtime.js";
|
|
58
|
+
import {
|
|
59
|
+
parseTextFromPart,
|
|
60
|
+
parseMediaFromPart,
|
|
61
|
+
describeInboundMediaReference,
|
|
62
|
+
decodeBase64AttachmentBuffer,
|
|
63
|
+
persistInlineAttachmentBuffer
|
|
64
|
+
} from "./channel-util.js";
|
|
65
|
+
import {
|
|
66
|
+
rememberSiderSessionBinding,
|
|
67
|
+
resolveSiderSessionBinding,
|
|
68
|
+
buildPendingMessageContextForRun,
|
|
69
|
+
consumePendingMessageContextForRun,
|
|
70
|
+
resolvePendingFinalTextForRun,
|
|
71
|
+
updateSiderRunStateContext
|
|
72
|
+
} from "./channel-state.js";
|
|
73
|
+
import { buildTextPart } from "./channel-builders.js";
|
|
74
|
+
import {
|
|
75
|
+
dispatchSiderParts,
|
|
76
|
+
handleSiderSendAction,
|
|
77
|
+
sendToSider
|
|
78
|
+
} from "./channel-send.js";
|
|
79
|
+
import { buildSendMediaResult } from "./channel-send-result.js";
|
|
80
|
+
import { acquireSiderTyping, releaseSiderTyping } from "./channel-typing.js";
|
|
81
|
+
import {
|
|
82
|
+
deliverReplyPayloadToSider,
|
|
83
|
+
persistDeferredFinalReplyPayloadToSider,
|
|
84
|
+
persistStandaloneReplyPayloadToSider,
|
|
85
|
+
enqueueStreamEventTask,
|
|
86
|
+
flushStreamEventQueue,
|
|
87
|
+
handleStreamingPartialSnapshot,
|
|
88
|
+
handleReasoningSnapshot,
|
|
89
|
+
closeStreamingSessionIfActive,
|
|
90
|
+
closeReasoningSessionIfActive,
|
|
91
|
+
commitCurrentStreamTextToFallbackPrefix,
|
|
92
|
+
resetCurrentStreamTextState,
|
|
93
|
+
resetReasoningStreamState,
|
|
94
|
+
rotateAssistantMessageFallbackState,
|
|
95
|
+
resolveFallbackFinalText,
|
|
96
|
+
cleanupSiderRunAfterReply,
|
|
97
|
+
finalizeQueuedRunMessages
|
|
98
|
+
} from "./channel-streaming.js";
|
|
99
|
+
import { runManagedSiderRelayMonitor } from "./channel-monitor.js";
|
|
100
|
+
import { setSiderRuntime } from "./channel-runtime.js";
|
|
101
|
+
import {
|
|
102
|
+
recordSiderPersistedAgentMessage,
|
|
103
|
+
recordSiderLlmOutputUsage,
|
|
104
|
+
recordSiderAgentEnd,
|
|
105
|
+
emitSiderToolHookEvent
|
|
106
|
+
} from "./channel-hooks.js";
|
|
107
|
+
async function handleInboundRealtimeMessage(params) {
|
|
108
|
+
const { cfg, account, event } = params;
|
|
109
|
+
const core = getSiderRuntime();
|
|
110
|
+
const runtimeConfigApi = core.config;
|
|
111
|
+
const currentCfg = runtimeConfigApi.current?.() ?? runtimeConfigApi.loadConfig?.() ?? loadConfig() ?? cfg;
|
|
112
|
+
const sessionId = typeof event.session_id === "string" ? event.session_id.trim() : "";
|
|
113
|
+
if (!sessionId) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if ((event.source_role ?? "").toLowerCase() === "relay") {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const parts = Array.isArray(event.parts) ? event.parts : [];
|
|
120
|
+
const textChunks = [];
|
|
121
|
+
const mediaItems = [];
|
|
122
|
+
for (const part of parts) {
|
|
123
|
+
const text = parseTextFromPart(part);
|
|
124
|
+
if (text) {
|
|
125
|
+
textChunks.push(text);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const media = parseMediaFromPart(part);
|
|
129
|
+
if (media) {
|
|
130
|
+
mediaItems.push(media);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const rawText = textChunks.join("\n").trim();
|
|
134
|
+
const mediaUrls = mediaItems.map((item) => item.url).filter((item) => Boolean(item));
|
|
135
|
+
const parsedMediaTypes = mediaItems.map((item) => item.mimeType).filter((item) => Boolean(item));
|
|
136
|
+
const hasControlCommand = rawText ? core.channel.text.hasControlCommand(rawText, currentCfg) : false;
|
|
137
|
+
const shouldComputeCommandAuthorized = rawText ? core.channel.commands.shouldComputeCommandAuthorized(rawText, currentCfg) : false;
|
|
138
|
+
const commandAuthorized = shouldComputeCommandAuthorized ? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({
|
|
139
|
+
useAccessGroups: currentCfg.commands?.useAccessGroups !== false,
|
|
140
|
+
authorizers: [{ configured: true, allowed: true }]
|
|
141
|
+
}) : void 0;
|
|
142
|
+
if (!rawText && mediaItems.length === 0) {
|
|
143
|
+
logDebug("drop inbound sider message: no text/media", {
|
|
144
|
+
accountId: account.accountId,
|
|
145
|
+
sessionId,
|
|
146
|
+
messageId: event.message_id,
|
|
147
|
+
partTypes: parts.map((part) => part.type)
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (hasControlCommand) {
|
|
152
|
+
logInfo("sider control command inbound", {
|
|
153
|
+
accountId: account.accountId,
|
|
154
|
+
sessionId,
|
|
155
|
+
messageId: event.message_id,
|
|
156
|
+
commandAuthorized
|
|
157
|
+
});
|
|
158
|
+
logDebug("detected sider control command", {
|
|
159
|
+
accountId: account.accountId,
|
|
160
|
+
sessionId,
|
|
161
|
+
messageId: event.message_id,
|
|
162
|
+
commandBody: rawText,
|
|
163
|
+
shouldComputeCommandAuthorized,
|
|
164
|
+
commandAuthorized
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const resolvedInboundMedia = await resolveInboundSiderMedia({
|
|
168
|
+
runtime: core,
|
|
169
|
+
cfg: currentCfg,
|
|
170
|
+
accountId: account.accountId,
|
|
171
|
+
mediaItems,
|
|
172
|
+
gatewayUrl: account.gatewayUrl,
|
|
173
|
+
token: account.token,
|
|
174
|
+
requestTimeoutMs: account.sendTimeoutMs,
|
|
175
|
+
logger: siderOutboundLogger
|
|
176
|
+
});
|
|
177
|
+
const unresolvedMediaUrls = resolvedInboundMedia.unresolvedMedia.map((item) => item.url).filter((item) => Boolean(item));
|
|
178
|
+
const unresolvedMediaRefs = resolvedInboundMedia.unresolvedMedia.map((item) => describeInboundMediaReference(item)).filter((item) => Boolean(item));
|
|
179
|
+
if (resolvedInboundMedia.unresolvedMedia.length > 0) {
|
|
180
|
+
logWarn("sider inbound media fallback to attachment url", {
|
|
181
|
+
accountId: account.accountId,
|
|
182
|
+
sessionId,
|
|
183
|
+
unresolvedCount: resolvedInboundMedia.unresolvedMedia.length,
|
|
184
|
+
unresolvedMediaRefs
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const bodyForAgent = formatTextWithAttachmentLinks(rawText, unresolvedMediaRefs);
|
|
188
|
+
const mediaPayload = resolvedInboundMedia.mediaPayload;
|
|
189
|
+
const route = core.channel.routing.resolveAgentRoute({
|
|
190
|
+
cfg: currentCfg,
|
|
191
|
+
channel: SIDER_CHANNEL_ID,
|
|
192
|
+
accountId: account.accountId,
|
|
193
|
+
peer: {
|
|
194
|
+
kind: "direct",
|
|
195
|
+
id: sessionId
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
const to = `session:${sessionId}`;
|
|
199
|
+
const mediaLocalRoots = getAgentScopedMediaLocalRoots(currentCfg, route.agentId);
|
|
200
|
+
const timestamp = typeof event.created_at === "number" && Number.isFinite(event.created_at) ? event.created_at : Date.now();
|
|
201
|
+
const envelopeBody = core.channel.reply.formatAgentEnvelope({
|
|
202
|
+
channel: "Sider",
|
|
203
|
+
from: sessionId,
|
|
204
|
+
timestamp,
|
|
205
|
+
envelope: core.channel.reply.resolveEnvelopeFormatOptions(currentCfg),
|
|
206
|
+
body: bodyForAgent
|
|
207
|
+
});
|
|
208
|
+
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
209
|
+
Body: envelopeBody,
|
|
210
|
+
BodyForAgent: bodyForAgent,
|
|
211
|
+
RawBody: bodyForAgent,
|
|
212
|
+
CommandBody: rawText,
|
|
213
|
+
BodyForCommands: rawText,
|
|
214
|
+
From: `${SIDER_CHANNEL_ID}:client:${sessionId}`,
|
|
215
|
+
To: to,
|
|
216
|
+
SessionKey: route.sessionKey,
|
|
217
|
+
AccountId: route.accountId,
|
|
218
|
+
ChatType: "direct",
|
|
219
|
+
ConversationLabel: sessionId,
|
|
220
|
+
SenderId: "client",
|
|
221
|
+
SenderName: "client",
|
|
222
|
+
Provider: SIDER_CHANNEL_ID,
|
|
223
|
+
Surface: SIDER_CHANNEL_ID,
|
|
224
|
+
MessageSid: typeof event.message_id === "string" ? event.message_id : void 0,
|
|
225
|
+
Timestamp: timestamp,
|
|
226
|
+
WasMentioned: true,
|
|
227
|
+
OriginatingChannel: SIDER_CHANNEL_ID,
|
|
228
|
+
OriginatingTo: to,
|
|
229
|
+
...mediaPayload,
|
|
230
|
+
...event.meta || {},
|
|
231
|
+
MediaUrl: mediaPayload.MediaUrl || (unresolvedMediaUrls.length > 0 ? unresolvedMediaUrls[0] : void 0),
|
|
232
|
+
MediaUrls: mediaPayload.MediaUrls || (unresolvedMediaUrls.length > 0 ? unresolvedMediaUrls : void 0),
|
|
233
|
+
MediaType: mediaPayload.MediaType || parsedMediaTypes[0],
|
|
234
|
+
MediaTypes: mediaPayload.MediaTypes || (parsedMediaTypes.length > 0 ? parsedMediaTypes : void 0),
|
|
235
|
+
CommandAuthorized: commandAuthorized
|
|
236
|
+
});
|
|
237
|
+
rememberSiderSessionBinding({
|
|
238
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
239
|
+
account,
|
|
240
|
+
sessionId
|
|
241
|
+
});
|
|
242
|
+
const storePath = core.channel.session.resolveStorePath(currentCfg.session?.store, {
|
|
243
|
+
agentId: route.agentId
|
|
244
|
+
});
|
|
245
|
+
const autoTitleConfig = resolveAutoTitleConfig(account.config.autoTitle);
|
|
246
|
+
const autoTitleUserMessage = rawText.slice(0, 500).trim();
|
|
247
|
+
let shouldAutoTitleAfterReply = false;
|
|
248
|
+
if (autoTitleConfig && autoTitleUserMessage) {
|
|
249
|
+
try {
|
|
250
|
+
shouldAutoTitleAfterReply = isFirstTurnInSiderSession({
|
|
251
|
+
cfg: currentCfg,
|
|
252
|
+
agentId: route.agentId,
|
|
253
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey
|
|
254
|
+
});
|
|
255
|
+
} catch (error) {
|
|
256
|
+
logWarn("sider auto-title first-turn detection failed", {
|
|
257
|
+
accountId: account.accountId,
|
|
258
|
+
sessionId,
|
|
259
|
+
error: String(error)
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
const modelReset = await maybeResetSiderSessionToCurrentDefaultModel({
|
|
265
|
+
runtime: core,
|
|
266
|
+
cfg: currentCfg,
|
|
267
|
+
agentId: route.agentId,
|
|
268
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey
|
|
269
|
+
});
|
|
270
|
+
if (modelReset.changed) {
|
|
271
|
+
logInfo("sider session model reset to current default", {
|
|
272
|
+
accountId: account.accountId,
|
|
273
|
+
sessionId,
|
|
274
|
+
agentId: route.agentId,
|
|
275
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
276
|
+
reason: modelReset.reason,
|
|
277
|
+
previousModel: modelReset.previousModel,
|
|
278
|
+
defaultModel: modelReset.defaultModel
|
|
279
|
+
});
|
|
280
|
+
} else {
|
|
281
|
+
logDebug("sider session model reset skipped", {
|
|
282
|
+
accountId: account.accountId,
|
|
283
|
+
sessionId,
|
|
284
|
+
agentId: route.agentId,
|
|
285
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
286
|
+
reason: modelReset.reason
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
logWarn("sider session model reset failed", {
|
|
291
|
+
accountId: account.accountId,
|
|
292
|
+
sessionId,
|
|
293
|
+
agentId: route.agentId,
|
|
294
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
295
|
+
error: String(error)
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
await core.channel.session.recordInboundSession({
|
|
299
|
+
storePath,
|
|
300
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
301
|
+
ctx: ctxPayload,
|
|
302
|
+
onRecordError: (err) => {
|
|
303
|
+
logWarn("sider failed to record session", {
|
|
304
|
+
error: String(err)
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
const { onModelSelected, ...prefixOptions } = createChannelReplyPipeline({
|
|
309
|
+
cfg: currentCfg,
|
|
310
|
+
agentId: route.agentId,
|
|
311
|
+
channel: SIDER_CHANNEL_ID,
|
|
312
|
+
accountId: account.accountId
|
|
313
|
+
});
|
|
314
|
+
logDebug("dispatching sider inbound message", {
|
|
315
|
+
accountId: account.accountId,
|
|
316
|
+
sessionId,
|
|
317
|
+
messageId: event.message_id,
|
|
318
|
+
routeSessionKey: route.sessionKey,
|
|
319
|
+
hasControlCommand,
|
|
320
|
+
commandAuthorized,
|
|
321
|
+
textLength: rawText.length,
|
|
322
|
+
mediaCount: mediaUrls.length,
|
|
323
|
+
mediaDownloadedCount: mediaPayload.MediaPaths?.length ?? 0,
|
|
324
|
+
mediaDownloadFailedCount: resolvedInboundMedia.unresolvedMedia.length
|
|
325
|
+
});
|
|
326
|
+
const streamState = {
|
|
327
|
+
answer: {
|
|
328
|
+
active: false,
|
|
329
|
+
streamId: void 0,
|
|
330
|
+
seq: 0,
|
|
331
|
+
blockDeltaCount: 0,
|
|
332
|
+
partialDeltaCount: 0,
|
|
333
|
+
streamDeltaMode: "undecided",
|
|
334
|
+
partialSnapshot: "",
|
|
335
|
+
accumulatedBlockText: "",
|
|
336
|
+
assistantSeq: 0,
|
|
337
|
+
fallbackAssistantSeq: 0,
|
|
338
|
+
fallbackTextSource: "none",
|
|
339
|
+
fallbackCurrentText: "",
|
|
340
|
+
fallbackTextPrefix: ""
|
|
341
|
+
},
|
|
342
|
+
reasoning: {
|
|
343
|
+
active: false,
|
|
344
|
+
streamId: void 0,
|
|
345
|
+
seq: 0,
|
|
346
|
+
snapshot: "",
|
|
347
|
+
deltaCount: 0
|
|
348
|
+
},
|
|
349
|
+
persistedFinalText: false,
|
|
350
|
+
pendingFinalPayloads: [],
|
|
351
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
352
|
+
currentRunId: void 0,
|
|
353
|
+
streamEventQueue: Promise.resolve()
|
|
354
|
+
};
|
|
355
|
+
acquireSiderTyping({ account, sessionId });
|
|
356
|
+
let dispatchCompleted = false;
|
|
357
|
+
let dispatchError;
|
|
358
|
+
try {
|
|
359
|
+
try {
|
|
360
|
+
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
361
|
+
ctx: ctxPayload,
|
|
362
|
+
cfg: currentCfg,
|
|
363
|
+
dispatcherOptions: {
|
|
364
|
+
...prefixOptions,
|
|
365
|
+
deliver: async (payload, info) => {
|
|
366
|
+
logDebug("-> deliver: ", payload, info);
|
|
367
|
+
if (payload.isCompactionNotice === true) {
|
|
368
|
+
logDebug("skip sider deliver: compaction notice", { accountId: account.accountId, sessionId, kind: info.kind });
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (payload.isError === true && !SIDER_PUSH_TOOL_ERROR_WARNINGS && typeof payload.text === "string" && SIDER_TOOL_ERROR_WARNING_TEXT_RE.test(payload.text)) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
logDebug("sider dispatch deliver payload", {
|
|
375
|
+
accountId: account.accountId,
|
|
376
|
+
sessionId,
|
|
377
|
+
kind: info.kind,
|
|
378
|
+
hasText: typeof payload.text === "string" && payload.text.length > 0,
|
|
379
|
+
mediaCount: resolveOutboundMediaUrls(payload).length
|
|
380
|
+
});
|
|
381
|
+
await deliverReplyPayloadToSider({ account, sessionId, streamState, payload, kind: info.kind, mediaLocalRoots });
|
|
382
|
+
},
|
|
383
|
+
onError: (err, info) => {
|
|
384
|
+
logError(`sider ${info.kind} reply failed`, { error: String(err) });
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
replyOptions: {
|
|
388
|
+
disableBlockStreaming: false,
|
|
389
|
+
onAgentRunStart: (runId) => {
|
|
390
|
+
streamState.currentRunId = runId;
|
|
391
|
+
const binding = resolveSiderSessionBinding(streamState.sessionKey);
|
|
392
|
+
if (binding) {
|
|
393
|
+
binding.currentRunId = runId;
|
|
394
|
+
}
|
|
395
|
+
updateSiderRunStateContext({
|
|
396
|
+
runId,
|
|
397
|
+
sessionKey: streamState.sessionKey,
|
|
398
|
+
sessionId,
|
|
399
|
+
accountId: account.accountId,
|
|
400
|
+
managedByReplyPipeline: true
|
|
401
|
+
});
|
|
402
|
+
},
|
|
403
|
+
onAssistantMessageStart: () => {
|
|
404
|
+
rotateAssistantMessageFallbackState(streamState);
|
|
405
|
+
},
|
|
406
|
+
onPartialReply: async (payload) => {
|
|
407
|
+
const snapshot = typeof payload.text === "string" ? payload.text : "";
|
|
408
|
+
if (!snapshot || streamState.answer.streamDeltaMode === "block") {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (streamState.answer.streamDeltaMode === "undecided") {
|
|
412
|
+
streamState.answer.streamDeltaMode = "partial";
|
|
413
|
+
}
|
|
414
|
+
await enqueueStreamEventTask({
|
|
415
|
+
streamState,
|
|
416
|
+
task: () => handleStreamingPartialSnapshot({ account, sessionId, streamState, snapshot })
|
|
417
|
+
});
|
|
418
|
+
},
|
|
419
|
+
onReasoningStream: async (payload) => {
|
|
420
|
+
const snapshot = typeof payload.text === "string" ? payload.text : "";
|
|
421
|
+
if (!snapshot.trim()) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
await enqueueStreamEventTask({
|
|
425
|
+
streamState,
|
|
426
|
+
task: () => handleReasoningSnapshot({ account, sessionId, streamState, snapshot })
|
|
427
|
+
});
|
|
428
|
+
},
|
|
429
|
+
onReasoningEnd: async () => {
|
|
430
|
+
await enqueueStreamEventTask({
|
|
431
|
+
streamState,
|
|
432
|
+
task: async () => {
|
|
433
|
+
await closeReasoningSessionIfActive({ account, sessionId, streamState, reason: "final", context: "reasoning.done" });
|
|
434
|
+
resetReasoningStreamState(streamState);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
},
|
|
438
|
+
onCompactionStart: async () => {
|
|
439
|
+
await flushStreamEventQueue(streamState);
|
|
440
|
+
commitCurrentStreamTextToFallbackPrefix(streamState);
|
|
441
|
+
await closeStreamingSessionIfActive({ account, sessionId, streamState, reason: "interrupted", context: "stream.done.compaction-start" });
|
|
442
|
+
resetCurrentStreamTextState(streamState);
|
|
443
|
+
await closeReasoningSessionIfActive({ account, sessionId, streamState, reason: "interrupted", context: "reasoning.done.compaction-start" });
|
|
444
|
+
resetReasoningStreamState(streamState);
|
|
445
|
+
logDebug("sider stream state reset for compaction", {
|
|
446
|
+
accountId: account.accountId,
|
|
447
|
+
sessionId,
|
|
448
|
+
fallbackPrefixLength: streamState.answer.fallbackTextPrefix.length,
|
|
449
|
+
reasoningTextLength: streamState.reasoning.snapshot.length
|
|
450
|
+
});
|
|
451
|
+
},
|
|
452
|
+
onModelSelected
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
dispatchCompleted = true;
|
|
456
|
+
logDebug("completed sider inbound dispatch", { accountId: account.accountId, sessionId, messageId: event.message_id });
|
|
457
|
+
} catch (err) {
|
|
458
|
+
dispatchError = err;
|
|
459
|
+
} finally {
|
|
460
|
+
await flushStreamEventQueue(streamState);
|
|
461
|
+
const streamEndedNormally = dispatchCompleted;
|
|
462
|
+
await closeStreamingSessionIfActive({
|
|
463
|
+
account,
|
|
464
|
+
sessionId,
|
|
465
|
+
streamState,
|
|
466
|
+
reason: streamEndedNormally ? "final" : "interrupted",
|
|
467
|
+
context: streamEndedNormally ? "stream.done.dispatch-end" : "stream.done.interrupted"
|
|
468
|
+
});
|
|
469
|
+
await closeReasoningSessionIfActive({
|
|
470
|
+
account,
|
|
471
|
+
sessionId,
|
|
472
|
+
streamState,
|
|
473
|
+
reason: streamEndedNormally ? "final" : "interrupted",
|
|
474
|
+
context: streamEndedNormally ? "reasoning.done.dispatch-end" : "reasoning.done.interrupted"
|
|
475
|
+
});
|
|
476
|
+
streamState.answer.blockDeltaCount = 0;
|
|
477
|
+
resetReasoningStreamState(streamState);
|
|
478
|
+
}
|
|
479
|
+
if (!dispatchCompleted) {
|
|
480
|
+
const selectedFinalText = resolvePendingFinalTextForRun(streamState.currentRunId) || resolveFallbackFinalText(streamState).trim();
|
|
481
|
+
const messageContext = buildPendingMessageContextForRun({
|
|
482
|
+
accountId: account.accountId,
|
|
483
|
+
runId: streamState.currentRunId,
|
|
484
|
+
includeFinalThinking: true
|
|
485
|
+
});
|
|
486
|
+
const cleanupRunId = messageContext.runId ?? streamState.currentRunId;
|
|
487
|
+
const finalParts = [
|
|
488
|
+
...messageContext.parts,
|
|
489
|
+
...selectedFinalText ? [buildTextPart(selectedFinalText)] : []
|
|
490
|
+
];
|
|
491
|
+
if (finalParts.length > 0 || cleanupRunId) {
|
|
492
|
+
logDebug("dispatch ended with interruption; finalizing current assistant text", {
|
|
493
|
+
accountId: account.accountId,
|
|
494
|
+
sessionId,
|
|
495
|
+
runId: cleanupRunId,
|
|
496
|
+
finalTextLength: selectedFinalText.length,
|
|
497
|
+
partTypes: finalParts.map((part) => part.type)
|
|
498
|
+
});
|
|
499
|
+
await finalizeQueuedRunMessages({
|
|
500
|
+
account,
|
|
501
|
+
sessionId,
|
|
502
|
+
streamState,
|
|
503
|
+
parts: finalParts,
|
|
504
|
+
stopReason: messageContext.stopReason,
|
|
505
|
+
context: "final.interrupted"
|
|
506
|
+
});
|
|
507
|
+
consumePendingMessageContextForRun({
|
|
508
|
+
runId: cleanupRunId,
|
|
509
|
+
consumeFinalThinking: true
|
|
510
|
+
});
|
|
511
|
+
cleanupSiderRunAfterReply({ streamState, runId: cleanupRunId });
|
|
512
|
+
} else {
|
|
513
|
+
cleanupSiderRunAfterReply({ streamState, runId: streamState.currentRunId });
|
|
514
|
+
}
|
|
515
|
+
if (dispatchError) {
|
|
516
|
+
throw dispatchError;
|
|
517
|
+
}
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const pendingFinalPayloads = [...streamState.pendingFinalPayloads];
|
|
521
|
+
streamState.pendingFinalPayloads = [];
|
|
522
|
+
if (pendingFinalPayloads.length > 0) {
|
|
523
|
+
const pendingRunFinalText = resolvePendingFinalTextForRun(streamState.currentRunId).trim();
|
|
524
|
+
const fallbackFinalText = (pendingRunFinalText || resolveFallbackFinalText(streamState)).trim();
|
|
525
|
+
let primaryFinalPayloadIndex = pendingFinalPayloads.findIndex(
|
|
526
|
+
(payload) => payload.isError !== true
|
|
527
|
+
);
|
|
528
|
+
const fallbackMatchingPayloadIndex = primaryFinalPayloadIndex < 0 && fallbackFinalText.length > 0 ? pendingFinalPayloads.findIndex((payload) => {
|
|
529
|
+
const payloadText = typeof payload.text === "string" ? payload.text.trim() : "";
|
|
530
|
+
return payloadText.length > 0 && payloadText === fallbackFinalText;
|
|
531
|
+
}) : -1;
|
|
532
|
+
if (primaryFinalPayloadIndex < 0 && fallbackMatchingPayloadIndex >= 0) {
|
|
533
|
+
primaryFinalPayloadIndex = fallbackMatchingPayloadIndex;
|
|
534
|
+
logDebug("promoting matching final payload as primary sider message", {
|
|
535
|
+
accountId: account.accountId,
|
|
536
|
+
sessionId,
|
|
537
|
+
runId: streamState.currentRunId,
|
|
538
|
+
primaryFinalPayloadIndex,
|
|
539
|
+
isError: pendingFinalPayloads[primaryFinalPayloadIndex]?.isError === true,
|
|
540
|
+
textLength: fallbackFinalText.length
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
const shouldSynthesizePrimaryFinal = primaryFinalPayloadIndex < 0 && fallbackFinalText.length > 0 && (pendingRunFinalText.length > 0 || streamState.answer.fallbackTextSource !== "final_payload");
|
|
544
|
+
if (!shouldSynthesizePrimaryFinal && primaryFinalPayloadIndex < 0) {
|
|
545
|
+
logDebug("skipping synthetic primary final message for payload-derived fallback", {
|
|
546
|
+
accountId: account.accountId,
|
|
547
|
+
sessionId,
|
|
548
|
+
runId: streamState.currentRunId,
|
|
549
|
+
fallbackTextSource: streamState.answer.fallbackTextSource,
|
|
550
|
+
fallbackTextLength: fallbackFinalText.length,
|
|
551
|
+
pendingFinalPayloadCount: pendingFinalPayloads.length
|
|
552
|
+
});
|
|
553
|
+
primaryFinalPayloadIndex = 0;
|
|
554
|
+
}
|
|
555
|
+
if (shouldSynthesizePrimaryFinal) {
|
|
556
|
+
const messageContext = buildPendingMessageContextForRun({
|
|
557
|
+
accountId: account.accountId,
|
|
558
|
+
runId: streamState.currentRunId,
|
|
559
|
+
includeFinalThinking: true
|
|
560
|
+
});
|
|
561
|
+
const cleanupRunId = messageContext.runId ?? streamState.currentRunId;
|
|
562
|
+
const finalParts = [
|
|
563
|
+
...messageContext.parts,
|
|
564
|
+
...fallbackFinalText ? [buildTextPart(fallbackFinalText)] : []
|
|
565
|
+
];
|
|
566
|
+
if (finalParts.length > 0 || cleanupRunId) {
|
|
567
|
+
logDebug("synthesizing primary final message before standalone error payloads", {
|
|
568
|
+
accountId: account.accountId,
|
|
569
|
+
sessionId,
|
|
570
|
+
runId: cleanupRunId,
|
|
571
|
+
finalTextLength: fallbackFinalText.length,
|
|
572
|
+
extraFinalPayloadCount: pendingFinalPayloads.length
|
|
573
|
+
});
|
|
574
|
+
await finalizeQueuedRunMessages({
|
|
575
|
+
account,
|
|
576
|
+
sessionId,
|
|
577
|
+
streamState,
|
|
578
|
+
parts: finalParts,
|
|
579
|
+
stopReason: messageContext.stopReason,
|
|
580
|
+
context: "final.synthetic_primary"
|
|
581
|
+
});
|
|
582
|
+
consumePendingMessageContextForRun({
|
|
583
|
+
runId: cleanupRunId,
|
|
584
|
+
consumeFinalThinking: true
|
|
585
|
+
});
|
|
586
|
+
cleanupSiderRunAfterReply({ streamState, runId: cleanupRunId });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
for (let index = 0; index < pendingFinalPayloads.length; index += 1) {
|
|
590
|
+
const payload = pendingFinalPayloads[index];
|
|
591
|
+
if (!shouldSynthesizePrimaryFinal && index === primaryFinalPayloadIndex) {
|
|
592
|
+
await persistDeferredFinalReplyPayloadToSider({
|
|
593
|
+
account,
|
|
594
|
+
sessionId,
|
|
595
|
+
streamState,
|
|
596
|
+
payload,
|
|
597
|
+
mediaLocalRoots
|
|
598
|
+
});
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
await persistStandaloneReplyPayloadToSider({
|
|
602
|
+
account,
|
|
603
|
+
sessionId,
|
|
604
|
+
payload,
|
|
605
|
+
mediaLocalRoots
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (!streamState.persistedFinalText) {
|
|
610
|
+
const selectedFinalText = resolvePendingFinalTextForRun(streamState.currentRunId) || resolveFallbackFinalText(streamState).trim();
|
|
611
|
+
const messageContext = buildPendingMessageContextForRun({
|
|
612
|
+
accountId: account.accountId,
|
|
613
|
+
runId: streamState.currentRunId,
|
|
614
|
+
includeFinalThinking: true
|
|
615
|
+
});
|
|
616
|
+
const cleanupRunId = messageContext.runId ?? streamState.currentRunId;
|
|
617
|
+
const finalParts = [
|
|
618
|
+
...messageContext.parts,
|
|
619
|
+
...selectedFinalText ? [buildTextPart(selectedFinalText)] : []
|
|
620
|
+
];
|
|
621
|
+
if (finalParts.length > 0 || cleanupRunId) {
|
|
622
|
+
logDebug(
|
|
623
|
+
selectedFinalText ? "no final payload observed; finalizing current assistant text" : "no final payload observed; flushing queued tail as final message",
|
|
624
|
+
{
|
|
625
|
+
accountId: account.accountId,
|
|
626
|
+
sessionId,
|
|
627
|
+
runId: cleanupRunId,
|
|
628
|
+
finalTextLength: selectedFinalText.length,
|
|
629
|
+
partTypes: finalParts.map((part) => part.type)
|
|
630
|
+
}
|
|
631
|
+
);
|
|
632
|
+
await finalizeQueuedRunMessages({
|
|
633
|
+
account,
|
|
634
|
+
sessionId,
|
|
635
|
+
streamState,
|
|
636
|
+
parts: finalParts,
|
|
637
|
+
stopReason: messageContext.stopReason,
|
|
638
|
+
context: selectedFinalText ? "final.text_fallback" : "final.queued_tail"
|
|
639
|
+
});
|
|
640
|
+
consumePendingMessageContextForRun({
|
|
641
|
+
runId: cleanupRunId,
|
|
642
|
+
consumeFinalThinking: true
|
|
643
|
+
});
|
|
644
|
+
cleanupSiderRunAfterReply({ streamState, runId: cleanupRunId });
|
|
645
|
+
} else {
|
|
646
|
+
cleanupSiderRunAfterReply({ streamState, runId: streamState.currentRunId });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (streamState.persistedFinalText && shouldAutoTitleAfterReply && autoTitleConfig) {
|
|
650
|
+
scheduleSiderAutoTitleUpdate({
|
|
651
|
+
cfg: currentCfg,
|
|
652
|
+
account,
|
|
653
|
+
sessionId,
|
|
654
|
+
userMessage: autoTitleUserMessage,
|
|
655
|
+
prompt: autoTitleConfig.prompt,
|
|
656
|
+
agentId: route.agentId
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
} finally {
|
|
660
|
+
releaseSiderTyping({ account, sessionId });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const siderPlugin = {
|
|
664
|
+
id: SIDER_CHANNEL_ID,
|
|
665
|
+
...siderChannelPluginCommon,
|
|
666
|
+
agentPrompt: {
|
|
667
|
+
messageToolHints: () => [
|
|
668
|
+
"- Sider targeting: omit `to` to reply in the current conversation (auto-inferred).",
|
|
669
|
+
"- To send an existing image or file to the current user, prefer the message tool with action='send' and set 'media' to a local file path or a remote HTTPS URL. You do not need to specify 'to' \u2014 the current conversation recipient is used automatically.",
|
|
670
|
+
"- IMPORTANT: Use action='send' for files/images. action='sendAttachment' is limited.",
|
|
671
|
+
"- When the user asks you to find an image from the web, use a web search or browser tool to find a suitable image URL, then send it using the message tool with 'media' set to that HTTPS image URL \u2014 do NOT download the image first.",
|
|
672
|
+
"- If the user asks you to download, export, or otherwise produce a file, send the resulting file to the user with the message tool. Do not stop at describing the file or giving its path.",
|
|
673
|
+
// "- If you call image_generate, the generated image is sent to the user automatically. Do not send the same image again with the message tool unless the user explicitly asks for a separate resend.",
|
|
674
|
+
"- IMPORTANT: When generating or saving a file to send, always use an absolute path (e.g. /tmp/photo.png), never a relative path like ./photo.png. Relative paths cannot be resolved and the file will not be delivered."
|
|
675
|
+
]
|
|
676
|
+
},
|
|
677
|
+
streaming: {
|
|
678
|
+
blockStreamingCoalesceDefaults: { minChars: 1200, idleMs: 900 }
|
|
679
|
+
},
|
|
680
|
+
actions: {
|
|
681
|
+
describeMessageTool: ({ cfg }) => {
|
|
682
|
+
const hasConfigured = listSiderAccountIds(cfg).some((accountId) => {
|
|
683
|
+
const account = resolveSiderAccount(cfg, accountId);
|
|
684
|
+
return account.configured || isSiderAccountBootstrappable(account);
|
|
685
|
+
});
|
|
686
|
+
return hasConfigured ? { actions: ["send"] } : null;
|
|
687
|
+
},
|
|
688
|
+
supportsAction: ({ action }) => action === "send",
|
|
689
|
+
extractToolSend: ({ args }) => extractSiderToolSend(args),
|
|
690
|
+
handleAction: async ({ action, params, cfg, accountId, mediaLocalRoots }) => {
|
|
691
|
+
logDebug(">> handleAction:", action, params);
|
|
692
|
+
if (action === "send") {
|
|
693
|
+
return jsonResult(
|
|
694
|
+
await handleSiderSendAction({
|
|
695
|
+
cfg,
|
|
696
|
+
accountId,
|
|
697
|
+
params,
|
|
698
|
+
mediaLocalRoots
|
|
699
|
+
})
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
if (action !== "sendAttachment") {
|
|
703
|
+
throw new Error(`sider action ${action} is not supported`);
|
|
704
|
+
}
|
|
705
|
+
const account = await resolveManagedSiderAccount({ cfg, accountId });
|
|
706
|
+
if (!account.configured) {
|
|
707
|
+
throw new Error(describeSiderAccountConfigurationError(account));
|
|
708
|
+
}
|
|
709
|
+
const to = readStringParam(params, "to", { required: true });
|
|
710
|
+
const rawBuffer = readStringParam(params, "buffer", { trim: false, required: true });
|
|
711
|
+
const contentType = readStringParam(params, "contentType", { trim: false }) ?? readStringParam(params, "mimeType", { trim: false });
|
|
712
|
+
const fileName = readStringParam(params, "filename", { trim: false }) ?? void 0;
|
|
713
|
+
const sessionId = resolveOutboundSessionId({ account, to });
|
|
714
|
+
const buffer = decodeBase64AttachmentBuffer(rawBuffer);
|
|
715
|
+
const savedAttachment = await persistInlineAttachmentBuffer({
|
|
716
|
+
cfg,
|
|
717
|
+
accountId,
|
|
718
|
+
buffer,
|
|
719
|
+
contentType: contentType ?? void 0,
|
|
720
|
+
fileName
|
|
721
|
+
});
|
|
722
|
+
const resolvedContentType = contentType ?? savedAttachment.contentType ?? void 0;
|
|
723
|
+
const resolvedFileName = fileName ?? savedAttachment.path;
|
|
724
|
+
const preparedAttachment = await buildOptimisticSiderPartFromInlineAttachment({
|
|
725
|
+
account,
|
|
726
|
+
sessionId,
|
|
727
|
+
buffer,
|
|
728
|
+
contentType: resolvedContentType,
|
|
729
|
+
fileName: resolvedFileName,
|
|
730
|
+
source: resolvedFileName
|
|
731
|
+
});
|
|
732
|
+
const sendParts = [preparedAttachment.part];
|
|
733
|
+
const attachmentResult = await buildSendMediaResult({
|
|
734
|
+
account,
|
|
735
|
+
sessionId,
|
|
736
|
+
part: preparedAttachment.part,
|
|
737
|
+
source: savedAttachment.path,
|
|
738
|
+
path: savedAttachment.path
|
|
739
|
+
});
|
|
740
|
+
await dispatchSiderParts({
|
|
741
|
+
account,
|
|
742
|
+
sessionId,
|
|
743
|
+
parts: sendParts,
|
|
744
|
+
deferredUploads: preparedAttachment.deferredUpload ? [preparedAttachment.deferredUpload] : [],
|
|
745
|
+
allowMergeActiveRun: true,
|
|
746
|
+
context: "action.sendAttachment"
|
|
747
|
+
});
|
|
748
|
+
return jsonResult(attachmentResult);
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
outbound: {
|
|
752
|
+
deliveryMode: "direct",
|
|
753
|
+
sendText: async ({ cfg, accountId, to, text, mediaLocalRoots }) => {
|
|
754
|
+
logDebug("-> sendText:", text);
|
|
755
|
+
const result = await sendToSider({ cfg, accountId, to, text, mediaLocalRoots, errorHint: "sider sendText requires non-empty text" });
|
|
756
|
+
return { channel: SIDER_CHANNEL_ID, messageId: result.messageId, conversationId: result.conversationId };
|
|
757
|
+
},
|
|
758
|
+
sendMedia: async ({ cfg, accountId, to, text, mediaUrl, mediaLocalRoots }) => {
|
|
759
|
+
logDebug("-> sendMedia:", text, mediaUrl);
|
|
760
|
+
const result = await sendToSider({ cfg, accountId, to, text, mediaUrl, mediaLocalRoots, errorHint: "sider sendMedia requires text and/or mediaUrl" });
|
|
761
|
+
return { channel: SIDER_CHANNEL_ID, messageId: result.messageId, conversationId: result.conversationId };
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
gateway: {
|
|
765
|
+
startAccount: async (ctx) => {
|
|
766
|
+
const account = await resolveManagedSiderAccount({ cfg: ctx.cfg, accountId: ctx.accountId });
|
|
767
|
+
if (!account.configured) {
|
|
768
|
+
throw new Error(describeSiderAccountConfigurationError(account));
|
|
769
|
+
}
|
|
770
|
+
ctx.log?.info(`[${account.accountId}] starting sider relay monitor (${account.gatewayUrl}, relayId=${account.relayId})`);
|
|
771
|
+
await runManagedSiderRelayMonitor({
|
|
772
|
+
cfg: ctx.cfg,
|
|
773
|
+
account,
|
|
774
|
+
abortSignal: ctx.abortSignal,
|
|
775
|
+
log: ctx.log,
|
|
776
|
+
onInboundMessage: handleInboundRealtimeMessage
|
|
777
|
+
});
|
|
778
|
+
ctx.log?.info(`[${account.accountId}] sider relay monitor watcher stopped`);
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
auth: {
|
|
782
|
+
login: async ({ accountId, runtime }) => {
|
|
783
|
+
const resolvedAccountId = normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID;
|
|
784
|
+
for (; ; ) {
|
|
785
|
+
const pairing = await requestSiderPairing();
|
|
786
|
+
runtime.log(
|
|
787
|
+
formatSiderPairingInstructions({ pairingCode: pairing.pairingCode, leadLine: "One more step to connect:" })
|
|
788
|
+
);
|
|
789
|
+
const reportPendingUpdate = createSiderPairingPendingUpdateReporter({
|
|
790
|
+
pairingCode: pairing.pairingCode,
|
|
791
|
+
report: (message) => {
|
|
792
|
+
runtime.log(message);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
try {
|
|
796
|
+
const paired = await waitForSiderPairing({
|
|
797
|
+
pairing,
|
|
798
|
+
onPending: reportPendingUpdate,
|
|
799
|
+
onRetryableError: (message) => {
|
|
800
|
+
runtime.log(message);
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
const latestCfg = loadConfig();
|
|
804
|
+
const nextCfg = applySiderSetupAccountConfig({ cfg: latestCfg, accountId: resolvedAccountId, input: { token: paired.token } });
|
|
805
|
+
await writeConfigFile(nextCfg);
|
|
806
|
+
runtime.log("Connected! You can now chat with me in the browser Side Panel.");
|
|
807
|
+
return;
|
|
808
|
+
} catch (error) {
|
|
809
|
+
if (error instanceof SiderPairingExpiredError) {
|
|
810
|
+
runtime.log("Pairing code expired.");
|
|
811
|
+
runtime.log("Generating a new code...");
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
throw error;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
messaging: {
|
|
820
|
+
normalizeTarget: normalizeSiderMessagingTarget,
|
|
821
|
+
targetResolver: {
|
|
822
|
+
looksLikeId: looksLikeSiderTargetId,
|
|
823
|
+
hint: "session:<sessionId> (or raw <sessionId>)"
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
export {
|
|
828
|
+
emitSiderToolHookEvent,
|
|
829
|
+
recordSiderAgentEnd,
|
|
830
|
+
recordSiderLlmOutputUsage,
|
|
831
|
+
recordSiderPersistedAgentMessage,
|
|
832
|
+
setSiderRuntime,
|
|
833
|
+
siderPlugin
|
|
834
|
+
};
|