openclaw-openagent 1.0.12 → 1.0.14
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/index.js +2 -0
- package/dist/src/config/config-schema.d.ts +9 -0
- package/dist/src/config/config-schema.js +6 -0
- package/dist/src/messaging/executor.d.ts +2 -3
- package/dist/src/messaging/executor.js +4 -31
- package/dist/src/plugin-ui/assets/openagent-override.js +124 -18
- package/dist/src/proxy/auth-proxy.js +5 -0
- package/dist/src/runtime/update-checker.d.ts +18 -0
- package/dist/src/runtime/update-checker.js +216 -0
- package/index.ts +3 -0
- package/package.json +1 -3
- package/src/app/channel-tools.ts +0 -249
- package/src/app/discovery-tools.ts +0 -273
- package/src/app/download-file-tool.ts +0 -133
- package/src/app/hooks.ts +0 -144
- package/src/app/index.ts +0 -84
- package/src/app/messaging-tools.ts +0 -79
- package/src/app/ops-tools.ts +0 -155
- package/src/app/remote-agent-tool.ts +0 -762
- package/src/app/types.ts +0 -68
- package/src/app/upload-file-tool.ts +0 -411
- package/src/app/verbose-preflight.ts +0 -190
- package/src/auth/config.ts +0 -266
- package/src/auth/credential-manager.ts +0 -146
- package/src/auth/index.ts +0 -24
- package/src/auth/verify.ts +0 -99
- package/src/channel.ts +0 -566
- package/src/compat.ts +0 -94
- package/src/config/config-schema.ts +0 -39
- package/src/messaging/aggregator.ts +0 -120
- package/src/messaging/collector.ts +0 -89
- package/src/messaging/executor.ts +0 -72
- package/src/messaging/inbound.ts +0 -150
- package/src/messaging/index.ts +0 -11
- package/src/messaging/mention-protocol.ts +0 -94
- package/src/messaging/process-c2c-request.ts +0 -564
- package/src/messaging/process-message.ts +0 -373
- package/src/messaging/scheduler.ts +0 -55
- package/src/messaging/types.ts +0 -38
- package/src/plugin-ui/adapters/oc-2026-04.js +0 -103
- package/src/plugin-ui/adapters/oc-2026-05.js +0 -125
- package/src/plugin-ui/adapters/oc-2026-06.js +0 -125
- package/src/plugin-ui/adapters/oc-unknown.js +0 -48
- package/src/plugin-ui/assets/agentbook-icon.svg +0 -5
- package/src/plugin-ui/assets/bg.png +0 -0
- package/src/plugin-ui/assets/icon.png +0 -0
- package/src/plugin-ui/assets/magic.svg +0 -5
- package/src/plugin-ui/assets/openagent-override.js +0 -11156
- package/src/plugin-ui/build.cjs +0 -463
- package/src/plugin-ui/index.ts +0 -18
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -570
- package/src/plugin-ui/modules/agent-book/panel/agent-card.js +0 -170
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -643
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +0 -665
- package/src/plugin-ui/modules/agent-book/panel/mention-state.js +0 -206
- package/src/plugin-ui/modules/agent-book/panel/styles.js +0 -833
- package/src/plugin-ui/modules/agent-book/panel/theme-adapter.js +0 -86
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +0 -295
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +0 -211
- package/src/plugin-ui/modules/agent-book/scanner.js +0 -131
- package/src/plugin-ui/modules/agent-book/travelcard/travel-cards.js +0 -500
- package/src/plugin-ui/modules/agent-book/travelcard/travel-engine.js +0 -652
- package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +0 -256
- package/src/plugin-ui/modules/loader/bootstrap.js +0 -38
- package/src/plugin-ui/modules/loader/shared-state.js +0 -838
- package/src/plugin-ui/modules/loader/ws-intercept.js +0 -199
- package/src/plugin-ui/modules/remote-agent/chunk-parser.js +0 -161
- package/src/plugin-ui/modules/remote-agent/execution-card.js +0 -530
- package/src/plugin-ui/modules/remote-agent/markdown-renderer.js +0 -256
- package/src/plugin-ui/modules/remote-agent/media-links.js +0 -151
- package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +0 -134
- package/src/plugin-ui/modules/remote-agent/output-card.js +0 -342
- package/src/plugin-ui/modules/remote-agent/progress-store.js +0 -363
- package/src/plugin-ui/modules/remote-agent/render-hooks.js +0 -1700
- package/src/plugin-ui/modules/remote-agent/styles.js +0 -896
- package/src/plugin-ui/modules/remote-agent/tool-card-model.js +0 -130
- package/src/plugin-ui/postinstall-deploy.cjs +0 -52
- package/src/plugin-ui/ui-extension-loader/backup.ts +0 -92
- package/src/plugin-ui/ui-extension-loader/index.ts +0 -276
- package/src/plugin-ui/ui-extension-loader/locator.ts +0 -152
- package/src/plugin-ui/ui-extension-loader/manifest.ts +0 -57
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +0 -944
- package/src/plugin-ui/ui-extension-loader/removed-extensions.ts +0 -70
- package/src/plugin-ui/ui-extension-loader/types.ts +0 -72
- package/src/proxy/auth-proxy.ts +0 -409
- package/src/runtime/account.ts +0 -549
- package/src/runtime/index.ts +0 -7
- package/src/runtime/plugin-runtime.ts +0 -94
- package/src/runtime/registry.ts +0 -71
- package/src/sdk/CLASS_MAP.md +0 -143
- package/src/sdk/index.d.ts +0 -126
- package/src/sdk/index.js +0 -23990
- package/src/sdk/modules/cloud-search-module.js +0 -1117
- package/src/sdk/modules/follow-module.js +0 -1069
- package/src/sdk/modules/group-module.js +0 -7397
- package/src/sdk/modules/relationship-module.js +0 -2269
- package/src/sdk/modules/signaling-module.js +0 -1468
- package/src/sdk/modules/tim-upload-plugin.js +0 -730
- package/src/sdk/node-env/http-request.js +0 -90
- package/src/sdk/node-env/index.js +0 -57
- package/src/sdk/node-env/storage.js +0 -114
- package/src/sdk/package.json +0 -10
- package/src/sdk/tsconfig.json +0 -16
- package/src/state/pending-invocation-store.ts +0 -43
- package/src/state/store.ts +0 -756
- package/src/tim/c2c.ts +0 -451
- package/src/tim/channels.ts +0 -364
- package/src/tim/client.ts +0 -330
- package/src/tim/index.ts +0 -18
- package/src/tim/messages.ts +0 -166
- package/src/tim/sdk-logger-init.ts +0 -50
- package/src/tools.ts +0 -10
- package/src/transport/factory.ts +0 -95
- package/src/transport/oasn/index.ts +0 -17
- package/src/transport/oasn/oasn-agent-card.ts +0 -111
- package/src/transport/oasn/oasn-discovery.ts +0 -108
- package/src/transport/oasn/oasn-files.ts +0 -210
- package/src/transport/oasn/oasn-http.ts +0 -529
- package/src/transport/oasn/oasn-invocation.ts +0 -604
- package/src/transport/oasn/oasn-normalize.ts +0 -133
- package/src/transport/oasn/oasn-register.ts +0 -33
- package/src/transport/oasn/oasn-transport.ts +0 -318
- package/src/transport/oasn/oasn-types.ts +0 -404
- package/src/transport/tim/index.ts +0 -8
- package/src/transport/tim/tim-transport.ts +0 -515
- package/src/transport/types.ts +0 -538
- package/src/types/openclaw-plugin-sdk-media-store.d.ts +0 -9
- package/src/types/openclaw.d.ts +0 -97
- package/src/types/tencentcloud-chat.d.ts +0 -15
- package/src/util/http.ts +0 -113
- package/src/util/logger.ts +0 -131
- package/src/util/url-resolver.ts +0 -77
package/src/tim/c2c.ts
DELETED
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TIM C2C — Agent-to-Agent direct messaging
|
|
3
|
-
*
|
|
4
|
-
* Provides C2C (peer-to-peer) communication for remote agent orchestration.
|
|
5
|
-
* Used by openagent_call_remote_agent tool (T2) to send requests and await replies.
|
|
6
|
-
*
|
|
7
|
-
* Architecture:
|
|
8
|
-
* - sendC2CCustomMessage: sends a TIM CustomMessage via CONV_C2C
|
|
9
|
-
* - waitForC2CReply: registers a pending request, resolves when reply arrives
|
|
10
|
-
* - initC2CHandler: listens for client 'c2c_message' events, dispatches to pending requests
|
|
11
|
-
*
|
|
12
|
-
* @see docs/products/orchestrator/specs/T1-c2c-infra.md
|
|
13
|
-
* @see docs/products/orchestrator/openagent-teams-architecture.md §3.3.3
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { logger } from '../util/logger.js';
|
|
17
|
-
import { registry } from '../runtime/registry.js';
|
|
18
|
-
import type { TIMClient } from './client.js';
|
|
19
|
-
|
|
20
|
-
// ── Types ──
|
|
21
|
-
|
|
22
|
-
/** C2C message payload types for the OpenAgent orchestration protocol */
|
|
23
|
-
export type C2CMessagePayload =
|
|
24
|
-
| {
|
|
25
|
-
type: 'openagent_task_request';
|
|
26
|
-
request_id: string;
|
|
27
|
-
task: string;
|
|
28
|
-
session_mode: string;
|
|
29
|
-
}
|
|
30
|
-
| {
|
|
31
|
-
type: 'openagent_task_progress';
|
|
32
|
-
request_id: string;
|
|
33
|
-
content: string;
|
|
34
|
-
}
|
|
35
|
-
| {
|
|
36
|
-
type: 'openagent_task_result';
|
|
37
|
-
request_id: string;
|
|
38
|
-
status: 'complete' | 'error';
|
|
39
|
-
content: string;
|
|
40
|
-
/** T4: number of TIMFileElem messages sent before this result. 0 or absent = no files. */
|
|
41
|
-
file_count?: number;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/** Result returned by waitForC2CReply when the remote agent completes */
|
|
45
|
-
export interface C2CReplyResult {
|
|
46
|
-
status: 'complete' | 'error';
|
|
47
|
-
content: string;
|
|
48
|
-
/** T4: local file paths after saveMediaBuffer (populated from pending TIMFileElem messages) */
|
|
49
|
-
mediaUrls?: string[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Options for waitForC2CReply */
|
|
53
|
-
export interface WaitForC2CReplyOptions {
|
|
54
|
-
timeoutMs: number;
|
|
55
|
-
signal?: AbortSignal;
|
|
56
|
-
onProgress?: (content: string) => void;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Raw C2C message emitted by client.ts */
|
|
60
|
-
interface RawC2CMessage {
|
|
61
|
-
from: string;
|
|
62
|
-
payload: unknown;
|
|
63
|
-
type: string;
|
|
64
|
-
time: number;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ── Pending Request Registry ──
|
|
68
|
-
|
|
69
|
-
interface PendingRequest {
|
|
70
|
-
onProgress?: (content: string) => void;
|
|
71
|
-
resolve: (result: C2CReplyResult) => void;
|
|
72
|
-
reject: (error: Error) => void;
|
|
73
|
-
timeoutHandle: ReturnType<typeof setTimeout>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* All pending requests waiting for C2C replies.
|
|
78
|
-
* Key: requestId (UUID), Value: callbacks + timeout handle.
|
|
79
|
-
*
|
|
80
|
-
* Memory safety: every entry is guaranteed to be deleted via one of three paths:
|
|
81
|
-
* 1. Normal completion (task_result received) → clearTimeout + delete + resolve
|
|
82
|
-
* 2. Timeout (timeoutMs expired) → delete + reject
|
|
83
|
-
* 3. Abort (signal.abort) → clearTimeout + delete + reject
|
|
84
|
-
*/
|
|
85
|
-
const pendingRequests = new Map<string, PendingRequest>();
|
|
86
|
-
|
|
87
|
-
// ── T4: Pending File Registry ──
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Pending TIMFileElem messages received from remote agents.
|
|
91
|
-
* Key: senderUserId, Value: array of file metadata.
|
|
92
|
-
*
|
|
93
|
-
* Files may arrive before OR after the openagent_task_result message.
|
|
94
|
-
* When result arrives with file_count > 0, we drain this queue.
|
|
95
|
-
* If files haven't arrived yet, a fileWaiter is registered to resolve later.
|
|
96
|
-
* @see docs/audit/023-tim-upload-plugin-v1.md §3.2 (方案 α)
|
|
97
|
-
*/
|
|
98
|
-
interface PendingFileInfo {
|
|
99
|
-
fileUrl: string;
|
|
100
|
-
fileName: string;
|
|
101
|
-
fileSize: number;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const pendingFiles = new Map<string, PendingFileInfo[]>();
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Waiters for files that haven't arrived yet when task_result comes first.
|
|
108
|
-
* Key: senderUserId. When TIMFileElem arrives and file count is met, the
|
|
109
|
-
* waiter's callback is invoked to drain files and resolve the pending request.
|
|
110
|
-
*
|
|
111
|
-
* Each waiter has a timeout (FILE_WAIT_TIMEOUT_MS) to prevent indefinite blocking.
|
|
112
|
-
*/
|
|
113
|
-
interface FileWaiter {
|
|
114
|
-
expectedCount: number;
|
|
115
|
-
onFilesReady: (files: PendingFileInfo[]) => void;
|
|
116
|
-
timeoutHandle: ReturnType<typeof setTimeout>;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const fileWaiters = new Map<string, FileWaiter>();
|
|
120
|
-
|
|
121
|
-
/** Max time to wait for late-arriving TIMFileElem messages (10 min). */
|
|
122
|
-
const FILE_WAIT_TIMEOUT_MS = 10 * 60_000;
|
|
123
|
-
|
|
124
|
-
// ── sendC2CCustomMessage ──
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Send a C2C custom message to a remote agent.
|
|
128
|
-
*
|
|
129
|
-
* Uses TIM SDK createCustomMessage + CONV_C2C.
|
|
130
|
-
* Protocol payload is JSON-serialized into the custom message `data` field.
|
|
131
|
-
*
|
|
132
|
-
* Timeout pattern matches messages.ts sendMessage (10s SDK-call guard).
|
|
133
|
-
* @see src/tim/messages.ts L61-87
|
|
134
|
-
*
|
|
135
|
-
* @param userId - Target agent's TIM userId
|
|
136
|
-
* @param payload - Structured message payload (request/progress/result)
|
|
137
|
-
*/
|
|
138
|
-
export async function sendC2CCustomMessage(
|
|
139
|
-
userId: string,
|
|
140
|
-
payload: C2CMessagePayload,
|
|
141
|
-
): Promise<void> {
|
|
142
|
-
const rt = registry.getDefault();
|
|
143
|
-
if (!rt || !rt.isRunning || !rt.client) {
|
|
144
|
-
throw new Error('OpenAgent not connected');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const chat = rt.client._chat;
|
|
148
|
-
const types = rt.client._types;
|
|
149
|
-
if (!chat || !rt.client.isReady) {
|
|
150
|
-
throw new Error('TIM SDK not initialized');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const message = chat.createCustomMessage({
|
|
154
|
-
to: userId,
|
|
155
|
-
conversationType: types.CONV_C2C,
|
|
156
|
-
payload: {
|
|
157
|
-
data: JSON.stringify(payload),
|
|
158
|
-
description: payload.type,
|
|
159
|
-
extension: '',
|
|
160
|
-
},
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
logger.info(`[tim/c2c] Sending C2C: to=${userId} type=${payload.type}`);
|
|
164
|
-
|
|
165
|
-
// 10s SDK-call guard — same pattern as messages.ts L64-69
|
|
166
|
-
// If SDK hangs (dead WebSocket), this prevents the Tool from blocking forever.
|
|
167
|
-
let timeoutHandle: ReturnType<typeof setTimeout>;
|
|
168
|
-
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
169
|
-
timeoutHandle = setTimeout(
|
|
170
|
-
() => reject(new Error(`sendC2CCustomMessage timeout (10s) to=${userId}`)),
|
|
171
|
-
10_000,
|
|
172
|
-
);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
await Promise.race([chat.sendMessage(message), timeoutPromise]);
|
|
177
|
-
logger.info(`[tim/c2c] C2C sent OK: to=${userId} type=${payload.type}`);
|
|
178
|
-
} catch (err) {
|
|
179
|
-
const code = (err as { code?: number })?.code;
|
|
180
|
-
logger.error(`[tim/c2c] C2C send failed: code=${code} msg=${(err as Error).message} to=${userId}`);
|
|
181
|
-
throw err;
|
|
182
|
-
} finally {
|
|
183
|
-
clearTimeout(timeoutHandle!);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ── waitForC2CReply ──
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Wait for a C2C reply matching a specific requestId.
|
|
191
|
-
*
|
|
192
|
-
* Registers a pending entry in the registry. When c2cMessageHandler receives
|
|
193
|
-
* a matching message, it dispatches to the entry's callbacks:
|
|
194
|
-
* - task_progress → onProgress(content)
|
|
195
|
-
* - task_result → resolve({ status, content })
|
|
196
|
-
*
|
|
197
|
-
* @param requestId - UUID to match replies against
|
|
198
|
-
* @param options - Timeout, abort signal, and progress callback
|
|
199
|
-
*/
|
|
200
|
-
export function waitForC2CReply(
|
|
201
|
-
requestId: string,
|
|
202
|
-
options: WaitForC2CReplyOptions,
|
|
203
|
-
): Promise<C2CReplyResult> {
|
|
204
|
-
return new Promise<C2CReplyResult>((resolve, reject) => {
|
|
205
|
-
// Timeout: auto-reject + cleanup after timeoutMs
|
|
206
|
-
const timeoutHandle = setTimeout(() => {
|
|
207
|
-
pendingRequests.delete(requestId);
|
|
208
|
-
reject(new Error(`C2C reply timeout (${options.timeoutMs}ms) requestId=${requestId}`));
|
|
209
|
-
}, options.timeoutMs);
|
|
210
|
-
|
|
211
|
-
// Abort signal: reject + cleanup on user cancel
|
|
212
|
-
if (options.signal) {
|
|
213
|
-
options.signal.addEventListener('abort', () => {
|
|
214
|
-
clearTimeout(timeoutHandle);
|
|
215
|
-
pendingRequests.delete(requestId);
|
|
216
|
-
reject(new Error(`C2C reply aborted requestId=${requestId}`));
|
|
217
|
-
}, { once: true });
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Register pending entry
|
|
221
|
-
pendingRequests.set(requestId, {
|
|
222
|
-
onProgress: options.onProgress,
|
|
223
|
-
resolve,
|
|
224
|
-
reject,
|
|
225
|
-
timeoutHandle,
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
logger.info(`[tim/c2c] Registered pending request: requestId=${requestId} timeout=${options.timeoutMs}ms`);
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ── C2C Message Handler ──
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Parse a raw C2C message payload into a OpenAgent protocol message.
|
|
236
|
-
* Returns null if the message is not a OpenAgent protocol message.
|
|
237
|
-
*/
|
|
238
|
-
function parseC2CPayload(raw: RawC2CMessage): C2CMessagePayload | null {
|
|
239
|
-
// TIM CustomMessage payload: { data: string, description: string, extension: string }
|
|
240
|
-
const p = raw.payload as { data?: string } | undefined;
|
|
241
|
-
if (!p?.data) return null;
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
const parsed = JSON.parse(p.data);
|
|
245
|
-
// Validate: must be a OpenAgent protocol message with request_id
|
|
246
|
-
if (typeof parsed.type !== 'string' || !parsed.type.startsWith('openagent_task_')) {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
if (typeof parsed.request_id !== 'string') {
|
|
250
|
-
logger.warn(`[tim/c2c] C2C message missing request_id: type=${parsed.type}`);
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
return parsed as C2CMessagePayload;
|
|
254
|
-
} catch {
|
|
255
|
-
// Not a JSON payload or parse error → not a OpenAgent message
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Initialize the C2C message handler.
|
|
262
|
-
*
|
|
263
|
-
* Listens for 'c2c_message' events from TIMClient and dispatches to:
|
|
264
|
-
* - pendingRequests registry (for progress/result → T2's waitForC2CReply)
|
|
265
|
-
* - 'c2c_request' event (for incoming task requests → T3's handler)
|
|
266
|
-
*
|
|
267
|
-
* Must be called after TIMClient.connect() succeeds.
|
|
268
|
-
*
|
|
269
|
-
* @param client - Connected TIMClient instance
|
|
270
|
-
*/
|
|
271
|
-
export function initC2CHandler(client: TIMClient): void {
|
|
272
|
-
client.on('c2c_message', async (raw: RawC2CMessage) => {
|
|
273
|
-
try {
|
|
274
|
-
// T4: Check for TIMFileElem before parsing as CustomMessage
|
|
275
|
-
// MSG_FILE messages have a different payload structure (fileUrl, fileName, fileSize)
|
|
276
|
-
// and won't pass parseC2CPayload. We accumulate them for later retrieval.
|
|
277
|
-
if (raw.type === 'TIMFileElem') {
|
|
278
|
-
handleC2CFileMessage(raw);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const payload = parseC2CPayload(raw);
|
|
283
|
-
if (!payload) return; // Not a OpenAgent protocol message
|
|
284
|
-
|
|
285
|
-
const requestId = payload.request_id;
|
|
286
|
-
|
|
287
|
-
switch (payload.type) {
|
|
288
|
-
case 'openagent_task_progress': {
|
|
289
|
-
const pending = pendingRequests.get(requestId);
|
|
290
|
-
if (!pending) {
|
|
291
|
-
logger.debug(`[tim/c2c] No pending request for progress requestId=${requestId}`);
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
logger.debug(`[tim/c2c] Progress for requestId=${requestId}: ${payload.content.slice(0, 80)}`);
|
|
295
|
-
pending.onProgress?.(payload.content);
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
case 'openagent_task_result': {
|
|
300
|
-
const pending = pendingRequests.get(requestId);
|
|
301
|
-
if (!pending) {
|
|
302
|
-
logger.debug(`[tim/c2c] No pending request for result requestId=${requestId}`);
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
logger.info(`[tim/c2c] Result for requestId=${requestId}: status=${payload.status}`);
|
|
306
|
-
clearTimeout(pending.timeoutHandle);
|
|
307
|
-
pendingRequests.delete(requestId);
|
|
308
|
-
|
|
309
|
-
// T4: Collect pending files from this sender.
|
|
310
|
-
// Files may arrive before or after task_result (race condition).
|
|
311
|
-
// If files are already here → drain immediately.
|
|
312
|
-
// If not → register a fileWaiter and wait up to FILE_WAIT_TIMEOUT_MS.
|
|
313
|
-
const fileCount = (payload as { file_count?: number }).file_count ?? 0;
|
|
314
|
-
let mediaUrls: string[] | undefined;
|
|
315
|
-
|
|
316
|
-
if (fileCount > 0) {
|
|
317
|
-
const senderFiles = pendingFiles.get(raw.from) ?? [];
|
|
318
|
-
logger.info(`[tim/c2c] Result has file_count=${fileCount}, pending files from sender: ${senderFiles.length}`);
|
|
319
|
-
|
|
320
|
-
let filesToDrain: PendingFileInfo[];
|
|
321
|
-
|
|
322
|
-
if (senderFiles.length >= fileCount) {
|
|
323
|
-
// Files already arrived → drain immediately
|
|
324
|
-
filesToDrain = senderFiles;
|
|
325
|
-
pendingFiles.delete(raw.from);
|
|
326
|
-
} else {
|
|
327
|
-
// Files haven't arrived yet → wait for them
|
|
328
|
-
logger.info(`[tim/c2c] Waiting for ${fileCount} file(s) from ${raw.from} (have ${senderFiles.length})...`);
|
|
329
|
-
filesToDrain = await new Promise<PendingFileInfo[]>((resolveFiles) => {
|
|
330
|
-
const timeoutHandle = setTimeout(() => {
|
|
331
|
-
fileWaiters.delete(raw.from);
|
|
332
|
-
const partial = pendingFiles.get(raw.from) ?? [];
|
|
333
|
-
pendingFiles.delete(raw.from);
|
|
334
|
-
logger.warn(`[tim/c2c] File wait timeout (${FILE_WAIT_TIMEOUT_MS}ms) for ${raw.from}, got ${partial.length}/${fileCount} file(s)`);
|
|
335
|
-
resolveFiles(partial);
|
|
336
|
-
}, FILE_WAIT_TIMEOUT_MS);
|
|
337
|
-
|
|
338
|
-
fileWaiters.set(raw.from, {
|
|
339
|
-
expectedCount: fileCount,
|
|
340
|
-
onFilesReady: (files) => {
|
|
341
|
-
clearTimeout(timeoutHandle);
|
|
342
|
-
fileWaiters.delete(raw.from);
|
|
343
|
-
resolveFiles(files);
|
|
344
|
-
},
|
|
345
|
-
timeoutHandle,
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (filesToDrain.length > 0) {
|
|
351
|
-
mediaUrls = [];
|
|
352
|
-
for (const fileInfo of filesToDrain) {
|
|
353
|
-
try {
|
|
354
|
-
logger.info(`[tim/c2c] Downloading file: ${fileInfo.fileName} (${fileInfo.fileSize} bytes)`);
|
|
355
|
-
const resp = await fetch(fileInfo.fileUrl);
|
|
356
|
-
if (!resp.ok) {
|
|
357
|
-
logger.error(`[tim/c2c] File download failed: HTTP ${resp.status} for ${fileInfo.fileName}`);
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
const buffer = Buffer.from(await resp.arrayBuffer());
|
|
361
|
-
|
|
362
|
-
// Save to temp dir with original filename preserved
|
|
363
|
-
// Use /tmp/openclaw/ prefix — framework's default localRoots whitelist.
|
|
364
|
-
const fs = await import('node:fs');
|
|
365
|
-
const nodePath = await import('node:path');
|
|
366
|
-
const tmpDir = nodePath.join('/tmp/openclaw', 'openagent-media');
|
|
367
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
368
|
-
const tmpPath = nodePath.join(tmpDir, `${Date.now()}-${fileInfo.fileName}`);
|
|
369
|
-
fs.writeFileSync(tmpPath, buffer);
|
|
370
|
-
mediaUrls.push(tmpPath);
|
|
371
|
-
logger.info(`[tim/c2c] File saved: ${fileInfo.fileName} → ${tmpPath} (${buffer.byteLength} bytes)`);
|
|
372
|
-
} catch (err) {
|
|
373
|
-
logger.error(`[tim/c2c] File download/save error: ${(err as Error).message} for ${fileInfo.fileName}`);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
if (mediaUrls.length === 0) mediaUrls = undefined;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
pending.resolve({ status: payload.status, content: payload.content, mediaUrls });
|
|
381
|
-
break;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
case 'openagent_task_request': {
|
|
385
|
-
// Incoming task request from another agent → forward to T3 handler
|
|
386
|
-
// T1 does not process requests, only replies (progress + result)
|
|
387
|
-
logger.info(`[tim/c2c] Incoming task request from=${raw.from} requestId=${requestId}`);
|
|
388
|
-
client.emit('c2c_request', {
|
|
389
|
-
from: raw.from,
|
|
390
|
-
payload,
|
|
391
|
-
time: raw.time,
|
|
392
|
-
});
|
|
393
|
-
break;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
default:
|
|
397
|
-
logger.debug(`[tim/c2c] Unknown C2C payload type: ${(payload as { type: string }).type}`);
|
|
398
|
-
}
|
|
399
|
-
} catch (err) {
|
|
400
|
-
logger.error(`[tim/c2c] Error handling C2C message: ${(err as Error).message}`);
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
logger.info('[tim/c2c] C2C message handler initialized');
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Handle a raw C2C message that might be a TIMFileElem.
|
|
409
|
-
* Called from initC2CHandler for non-CustomMessage types.
|
|
410
|
-
* Accumulates file metadata in pendingFiles for later retrieval.
|
|
411
|
-
*
|
|
412
|
-
* @see docs/audit/023-tim-upload-plugin-v1.md §4.4
|
|
413
|
-
*/
|
|
414
|
-
function handleC2CFileMessage(raw: RawC2CMessage): void {
|
|
415
|
-
// TIMFileElem payload structure (from TIM SDK):
|
|
416
|
-
// { fileUrl: string, fileName: string, fileSize: number, uuid: string }
|
|
417
|
-
const payload = raw.payload as {
|
|
418
|
-
fileUrl?: string;
|
|
419
|
-
fileName?: string;
|
|
420
|
-
fileSize?: number;
|
|
421
|
-
} | undefined;
|
|
422
|
-
|
|
423
|
-
if (!payload?.fileUrl || !payload?.fileName) {
|
|
424
|
-
logger.debug(`[tim/c2c] MSG_FILE from=${raw.from} but missing fileUrl/fileName, skip`);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const fileInfo: PendingFileInfo = {
|
|
429
|
-
fileUrl: payload.fileUrl,
|
|
430
|
-
fileName: payload.fileName,
|
|
431
|
-
fileSize: payload.fileSize ?? 0,
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
if (!pendingFiles.has(raw.from)) {
|
|
435
|
-
pendingFiles.set(raw.from, []);
|
|
436
|
-
}
|
|
437
|
-
pendingFiles.get(raw.from)!.push(fileInfo);
|
|
438
|
-
|
|
439
|
-
const totalFiles = pendingFiles.get(raw.from)!.length;
|
|
440
|
-
logger.info(`[tim/c2c] Pending file from=${raw.from}: ${fileInfo.fileName} (${fileInfo.fileSize} bytes) total=${totalFiles}`);
|
|
441
|
-
|
|
442
|
-
// Check if a fileWaiter is waiting for files from this sender.
|
|
443
|
-
// This handles the race condition where task_result arrived before the file.
|
|
444
|
-
const waiter = fileWaiters.get(raw.from);
|
|
445
|
-
if (waiter && totalFiles >= waiter.expectedCount) {
|
|
446
|
-
logger.info(`[tim/c2c] File waiter satisfied for ${raw.from}: ${totalFiles}/${waiter.expectedCount} file(s)`);
|
|
447
|
-
const files = pendingFiles.get(raw.from) ?? [];
|
|
448
|
-
pendingFiles.delete(raw.from);
|
|
449
|
-
waiter.onFilesReady(files);
|
|
450
|
-
}
|
|
451
|
-
}
|