openclaw-openagent 1.0.13 → 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/src/messaging/executor.d.ts +2 -3
- package/dist/src/messaging/executor.js +4 -31
- package/dist/src/plugin-ui/assets/openagent-override.js +4 -5
- package/dist/src/runtime/update-checker.d.ts +1 -1
- package/dist/src/runtime/update-checker.js +9 -46
- 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 -45
- 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 -11263
- 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 -832
- 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 -262
- package/src/plugin-ui/modules/loader/bootstrap.js +0 -133
- 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 -903
- 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 -414
- 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/runtime/update-checker.ts +0 -286
- 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
|
@@ -1,564 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Process C2C Request — Remote agent task request handler (T3)
|
|
3
|
-
*
|
|
4
|
-
* ⚠️ TIM-only 路径(v3.9+ 双轨抽象)
|
|
5
|
-
* ---------------------------------------------------------
|
|
6
|
-
* 本文件只在 transport='tim' 下被 AccountRuntime._connectTIM() 通过
|
|
7
|
-
* `this.client.on('c2c_request', ...)` 注册。OASN 路径下 AccountRuntime
|
|
8
|
-
* 不会监听 c2c_request 事件 —— OASN ClientAgent 是消费侧,不接收外来 Invocation,
|
|
9
|
-
* 远程 Agent 调用统一由发起方走 transport.sendTask / waitForTaskResult 完成。
|
|
10
|
-
*
|
|
11
|
-
* Handles incoming `c2c_request` events from c2c.ts (T1).
|
|
12
|
-
* Mirrors the process-message.ts pattern but for C2C task requests:
|
|
13
|
-
*
|
|
14
|
-
* 1. Parse request parameters (from, requestId, task, sessionMode)
|
|
15
|
-
* 2. Compute sessionKey (aligned with OpenClaw buildAgentPeerSessionKey)
|
|
16
|
-
* 3. Register onAgentEvent listener ({openagent_progress} prefix filter)
|
|
17
|
-
* 4. Build MsgContext (adapted for C2C, not group)
|
|
18
|
-
* 5. channelRuntime pipeline: resolveAgentRoute → finalize → record → dispatch
|
|
19
|
-
* 6. deliver callback routes kind="block" → progress, kind="final" → result
|
|
20
|
-
* 7. Unsubscribe onAgentEvent listener
|
|
21
|
-
*
|
|
22
|
-
* @see docs/specs/2026-06-16-oasn-transport-abstraction-design.md §4.2
|
|
23
|
-
* @see docs/products/orchestrator/specs/T3-remote-handler.md
|
|
24
|
-
* @see docs/products/orchestrator/specs/T1-c2c-infra.md §6.2 (c2c_request emit)
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
import { randomUUID } from 'node:crypto';
|
|
28
|
-
import fs from 'node:fs';
|
|
29
|
-
import nodePath from 'node:path';
|
|
30
|
-
import { ZipFile } from 'yazl';
|
|
31
|
-
import type { PluginRuntime } from 'openclaw/plugin-sdk/core';
|
|
32
|
-
|
|
33
|
-
import { sendC2CCustomMessage, type C2CMessagePayload } from '../tim/c2c.js';
|
|
34
|
-
import { getOpenagentPluginRuntime } from '../runtime/plugin-runtime.js';
|
|
35
|
-
import { registry } from '../runtime/registry.js';
|
|
36
|
-
import { logger } from '../util/logger.js';
|
|
37
|
-
|
|
38
|
-
// ── Types ──
|
|
39
|
-
|
|
40
|
-
/** Parsed task request payload (from c2c.ts L267) */
|
|
41
|
-
interface C2CTaskRequest {
|
|
42
|
-
type: 'openagent_task_request';
|
|
43
|
-
request_id: string;
|
|
44
|
-
task: string;
|
|
45
|
-
session_mode: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Incoming c2c_request event shape (emitted by c2c.ts initC2CHandler) */
|
|
49
|
-
export interface C2CRequestEvent {
|
|
50
|
-
from: string;
|
|
51
|
-
payload: C2CTaskRequest;
|
|
52
|
-
time: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Dependencies injected by AccountRuntime._connectTIM() */
|
|
56
|
-
export interface ProcessC2CRequestDeps {
|
|
57
|
-
accountId: string;
|
|
58
|
-
selfUserId: string;
|
|
59
|
-
config: Record<string, unknown>;
|
|
60
|
-
channelRuntime: PluginRuntime['channel'];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ── Agent event type (local mirror of SDK enriched event shape) ──
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Local type for the enriched agent event received by onAgentEvent listeners.
|
|
67
|
-
* Mirrors the shape from agent-events.d.ts — only the fields we use.
|
|
68
|
-
*
|
|
69
|
-
* @see docs/reference/openclaw/v2026.3.23/dist/plugin-sdk/src/infra/agent-events.d.ts L23
|
|
70
|
-
* @see pi-embedded-CswW9luA.js L94723-94736 (emitAgentEvent enrichment)
|
|
71
|
-
*/
|
|
72
|
-
interface AgentEventPayload {
|
|
73
|
-
runId?: string;
|
|
74
|
-
sessionKey?: string;
|
|
75
|
-
stream?: string;
|
|
76
|
-
data: {
|
|
77
|
-
phase?: string;
|
|
78
|
-
partialResult?: unknown;
|
|
79
|
-
[key: string]: unknown;
|
|
80
|
-
};
|
|
81
|
-
[key: string]: unknown;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ── Progress prefix constant ──
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Marker prefix for tool progress messages.
|
|
88
|
-
* Remote developers add this prefix to onUpdate() calls to opt-in
|
|
89
|
-
* to progress forwarding via OpenAgent C2C.
|
|
90
|
-
*
|
|
91
|
-
* @see T3-remote-handler.md §9.1.2
|
|
92
|
-
*/
|
|
93
|
-
const PROGRESS_PREFIX = '{openagent_progress}';
|
|
94
|
-
|
|
95
|
-
// ── Directory → ZIP helper ──
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Recursively zip a directory into a .zip file.
|
|
99
|
-
* Returns the path to the created zip file in /tmp/openclaw/openagent-media/.
|
|
100
|
-
*
|
|
101
|
-
* Uses yazl (pure JS, ~75KB) for cross-platform zip creation.
|
|
102
|
-
* The zip preserves directory structure with relative paths.
|
|
103
|
-
*/
|
|
104
|
-
function zipDirectory(dirPath: string): Promise<string> {
|
|
105
|
-
return new Promise((resolve, reject) => {
|
|
106
|
-
const dirName = nodePath.basename(dirPath);
|
|
107
|
-
const tmpDir = '/tmp/openclaw/openagent-media';
|
|
108
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
109
|
-
const zipPath = nodePath.join(tmpDir, `${Date.now()}-${dirName}.zip`);
|
|
110
|
-
|
|
111
|
-
const zipfile = new ZipFile();
|
|
112
|
-
|
|
113
|
-
// Recursively add all files
|
|
114
|
-
const addDir = (currentPath: string, prefix: string) => {
|
|
115
|
-
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
116
|
-
for (const entry of entries) {
|
|
117
|
-
const fullPath = nodePath.join(currentPath, entry.name);
|
|
118
|
-
const arcPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
119
|
-
if (entry.isDirectory()) {
|
|
120
|
-
addDir(fullPath, arcPath);
|
|
121
|
-
} else if (entry.isFile()) {
|
|
122
|
-
zipfile.addFile(fullPath, arcPath);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
addDir(dirPath, dirName);
|
|
128
|
-
zipfile.end();
|
|
129
|
-
|
|
130
|
-
const output = fs.createWriteStream(zipPath);
|
|
131
|
-
zipfile.outputStream.pipe(output);
|
|
132
|
-
output.on('close', () => {
|
|
133
|
-
logger.info(`[c2c-request] T4: zipped directory ${dirName}/ → ${zipPath} (${fs.statSync(zipPath).size} bytes)`);
|
|
134
|
-
resolve(zipPath);
|
|
135
|
-
});
|
|
136
|
-
output.on('error', reject);
|
|
137
|
-
zipfile.outputStream.on('error', reject);
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// ── Core processing ──
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Process a single incoming C2C task request through the standard
|
|
145
|
-
* OpenClaw channelRuntime pipeline.
|
|
146
|
-
*
|
|
147
|
-
* This is the T3 equivalent of processOneMessage (process-message.ts).
|
|
148
|
-
* Fire-and-forget safe — errors are caught and result in an error C2C reply.
|
|
149
|
-
*/
|
|
150
|
-
export async function processC2CRequest(
|
|
151
|
-
request: C2CRequestEvent,
|
|
152
|
-
deps: ProcessC2CRequestDeps,
|
|
153
|
-
): Promise<void> {
|
|
154
|
-
const { from: fromUserId, payload, time } = request;
|
|
155
|
-
const { request_id: requestId, task, session_mode: sessionMode } = payload;
|
|
156
|
-
|
|
157
|
-
logger.info(
|
|
158
|
-
`[c2c-request] Incoming task: from=${fromUserId} requestId=${requestId} mode=${sessionMode} taskLen=${task.length}`,
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
// ① Validate
|
|
162
|
-
if (!fromUserId || !requestId || !task) {
|
|
163
|
-
logger.error(`[c2c-request] Invalid request: missing required fields`);
|
|
164
|
-
await sendErrorResult(fromUserId, requestId, 'Invalid request: missing required fields');
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// ② Compute sessionKey — aligned with OpenClaw buildAgentPeerSessionKey
|
|
169
|
-
// Format: agent:{agentId}:{channel}:{peerKind}:{peerId}
|
|
170
|
-
// @see T1-c2c-infra.md §3.1 sessionKey naming convention
|
|
171
|
-
const agentId = 'main'; // Default agent ID; multi-agent is P1 (T3 §9.2)
|
|
172
|
-
// buildAgentPeerSessionKey (session-key-DAhnzjyr.js L211) lowercases peerId.
|
|
173
|
-
// Must match exactly for onAgentEvent sessionKey filtering to work.
|
|
174
|
-
const normalizedFrom = fromUserId.toLowerCase();
|
|
175
|
-
const sessionKey = sessionMode === 'run'
|
|
176
|
-
? `agent:${agentId}:openagent:remote:${normalizedFrom}:run:${requestId}`
|
|
177
|
-
: `agent:${agentId}:openagent:remote:${normalizedFrom}`;
|
|
178
|
-
|
|
179
|
-
logger.debug(`[c2c-request] sessionKey=${sessionKey}`);
|
|
180
|
-
|
|
181
|
-
// ③ Register onAgentEvent listener for {openagent_progress} prefix
|
|
182
|
-
// Must be BEFORE dispatchReplyFromConfig — otherwise tool events are missed.
|
|
183
|
-
//
|
|
184
|
-
// Filter by runId (NOT sessionKey). OpenClaw sets isControlUiVisible=false
|
|
185
|
-
// for external channels like OpenAgent, which causes emitAgentEvent to strip
|
|
186
|
-
// sessionKey to undefined. runId is always preserved.
|
|
187
|
-
// @see audit/021-progress-pipeline-v1.md §4 (root cause)
|
|
188
|
-
const runId = randomUUID();
|
|
189
|
-
logger.debug(`[c2c-request] generated runId=${runId} for requestId=${requestId}`);
|
|
190
|
-
|
|
191
|
-
let unsubscribe: (() => void) | null = null;
|
|
192
|
-
try {
|
|
193
|
-
const runtime = getOpenagentPluginRuntime();
|
|
194
|
-
unsubscribe = runtime.events.onAgentEvent((evt: AgentEventPayload) => {
|
|
195
|
-
if (evt.runId !== runId) return;
|
|
196
|
-
if (evt.stream !== 'tool' || evt.data.phase !== 'update') return;
|
|
197
|
-
|
|
198
|
-
// partialResult is AgentToolResult object: { content: [{ type: "text", text: "..." }] }
|
|
199
|
-
// Source: sanitizeToolResult (L173042-173071) returns same structure.
|
|
200
|
-
// OpenAgent toolResult() (types.ts L51-53): { content: [{ type: 'text', text }] }
|
|
201
|
-
const pr = evt.data.partialResult as
|
|
202
|
-
| { content?: Array<{ type?: string; text?: string }> }
|
|
203
|
-
| string
|
|
204
|
-
| null
|
|
205
|
-
| undefined;
|
|
206
|
-
|
|
207
|
-
let raw: string;
|
|
208
|
-
if (pr && typeof pr === 'object' && Array.isArray(pr.content)) {
|
|
209
|
-
raw = pr.content.find((c) => c?.type === 'text')?.text ?? '';
|
|
210
|
-
} else {
|
|
211
|
-
raw = String(pr ?? '');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (!raw.startsWith(PROGRESS_PREFIX)) return; // No prefix → discard
|
|
215
|
-
|
|
216
|
-
const content = raw.slice(PROGRESS_PREFIX.length).trim();
|
|
217
|
-
if (!content) return;
|
|
218
|
-
|
|
219
|
-
logger.info(`[c2c-request] Tool progress: requestId=${requestId} runId=${runId} content=${content.slice(0, 80)}`);
|
|
220
|
-
|
|
221
|
-
// Send via same TIM C2C path as deliver callback
|
|
222
|
-
void sendC2CCustomMessage(fromUserId, {
|
|
223
|
-
type: 'openagent_task_progress',
|
|
224
|
-
request_id: requestId,
|
|
225
|
-
content,
|
|
226
|
-
} as C2CMessagePayload).catch((err: unknown) => {
|
|
227
|
-
logger.error(`[c2c-request] Failed to send tool progress: ${(err as Error).message}`);
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
} catch (err) {
|
|
231
|
-
// If PluginRuntime isn't available, continue without progress monitoring
|
|
232
|
-
logger.warn(`[c2c-request] Could not register onAgentEvent: ${(err as Error).message}`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
try {
|
|
236
|
-
const { channelRuntime, config, accountId } = deps;
|
|
237
|
-
|
|
238
|
-
// ④ Build MsgContext for C2C request
|
|
239
|
-
// NOT using timMessageToMsgContext — that's for group messages (ChatType='group').
|
|
240
|
-
// C2C requests are a different chat type.
|
|
241
|
-
const msgContext = {
|
|
242
|
-
// Message body
|
|
243
|
-
Body: task,
|
|
244
|
-
BodyForAgent: task,
|
|
245
|
-
RawBody: task,
|
|
246
|
-
CommandBody: task,
|
|
247
|
-
|
|
248
|
-
// Routing
|
|
249
|
-
From: fromUserId,
|
|
250
|
-
To: fromUserId, // C2C: destination is the sender (reply goes back to them)
|
|
251
|
-
AccountId: accountId,
|
|
252
|
-
|
|
253
|
-
// Sender info
|
|
254
|
-
SenderId: fromUserId,
|
|
255
|
-
SenderName: fromUserId,
|
|
256
|
-
|
|
257
|
-
// Conversation label
|
|
258
|
-
ConversationLabel: `openagent:remote:${fromUserId}`,
|
|
259
|
-
GroupSubject: '',
|
|
260
|
-
|
|
261
|
-
// Channel metadata
|
|
262
|
-
OriginatingChannel: 'openagent',
|
|
263
|
-
OriginatingTo: fromUserId,
|
|
264
|
-
MessageSid: `c2c:${requestId}`,
|
|
265
|
-
Timestamp: time * 1000, // TIM time is seconds, OpenClaw expects ms
|
|
266
|
-
Provider: 'openagent',
|
|
267
|
-
Surface: 'openagent',
|
|
268
|
-
ChatType: 'direct', // NOT 'group' — this is a C2C request
|
|
269
|
-
|
|
270
|
-
// Session
|
|
271
|
-
SessionKey: sessionKey,
|
|
272
|
-
|
|
273
|
-
// No mention, no history for C2C requests
|
|
274
|
-
WasMentioned: true, // Always process (no mention gating for C2C)
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
// ⑤ Agent routing
|
|
278
|
-
const route = channelRuntime.routing.resolveAgentRoute({
|
|
279
|
-
cfg: config,
|
|
280
|
-
channel: 'openagent',
|
|
281
|
-
accountId,
|
|
282
|
-
peer: { kind: 'remote', id: fromUserId },
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
logger.debug(
|
|
286
|
-
`[c2c-request] route: agentId=${route.agentId ?? '(none)'} sessionKey=${route.sessionKey ?? '(none)'}`,
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
// Use our computed sessionKey, not the route's
|
|
290
|
-
// (route may return a different key for group-based routing)
|
|
291
|
-
msgContext.SessionKey = sessionKey;
|
|
292
|
-
|
|
293
|
-
// ⑤ Finalize inbound context
|
|
294
|
-
const finalized = channelRuntime.reply.finalizeInboundContext(
|
|
295
|
-
msgContext as Parameters<typeof channelRuntime.reply.finalizeInboundContext>[0],
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
// ⑤ Record inbound session
|
|
299
|
-
const storePath = channelRuntime.session.resolveStorePath(
|
|
300
|
-
(config as { session?: { store?: unknown } }).session?.store,
|
|
301
|
-
{ agentId: route.agentId || agentId },
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
await channelRuntime.session.recordInboundSession({
|
|
305
|
-
storePath,
|
|
306
|
-
sessionKey,
|
|
307
|
-
ctx: finalized as Parameters<typeof channelRuntime.session.recordInboundSession>[0]['ctx'],
|
|
308
|
-
updateLastRoute: {
|
|
309
|
-
sessionKey: route.mainSessionKey || sessionKey,
|
|
310
|
-
channel: 'openagent',
|
|
311
|
-
to: fromUserId,
|
|
312
|
-
accountId,
|
|
313
|
-
},
|
|
314
|
-
onRecordError: (err: unknown) =>
|
|
315
|
-
logger.error(`[c2c-request] recordInboundSession error: ${String(err)}`),
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// ⑥ Create reply dispatcher + dispatch AI reply
|
|
319
|
-
const { dispatcher, replyOptions, markDispatchIdle } =
|
|
320
|
-
channelRuntime.reply.createReplyDispatcherWithTyping({
|
|
321
|
-
deliver: async (
|
|
322
|
-
deliverPayload: { text?: string; mediaUrl?: string; mediaUrls?: string[] },
|
|
323
|
-
deliverMeta?: { kind?: string },
|
|
324
|
-
) => {
|
|
325
|
-
const text = deliverPayload.text ?? '';
|
|
326
|
-
const kind = deliverMeta?.kind;
|
|
327
|
-
|
|
328
|
-
if (kind === 'block') {
|
|
329
|
-
// LLM streaming text → C2C progress (unchanged)
|
|
330
|
-
if (!text) return;
|
|
331
|
-
logger.debug(`[c2c-request] deliver block: requestId=${requestId} textLen=${text.length}`);
|
|
332
|
-
await sendC2CCustomMessage(fromUserId, {
|
|
333
|
-
type: 'openagent_task_progress',
|
|
334
|
-
request_id: requestId,
|
|
335
|
-
content: text,
|
|
336
|
-
} as C2CMessagePayload);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// kind="tool" → skip. Remote LLM will summarize tool data into natural language.
|
|
340
|
-
|
|
341
|
-
if (kind === 'final') {
|
|
342
|
-
// ── T4: File detection + upload ──
|
|
343
|
-
// Collect file paths from two sources:
|
|
344
|
-
// Path A: deliverPayload.mediaUrls (tool results, e.g. image_generate)
|
|
345
|
-
// Path B: {openagent_file} text markers (skill scripts)
|
|
346
|
-
const filePaths: string[] = [];
|
|
347
|
-
|
|
348
|
-
// Path A: structured mediaUrls from OpenClaw delivery pipeline
|
|
349
|
-
if (deliverPayload.mediaUrls) {
|
|
350
|
-
for (const u of deliverPayload.mediaUrls) {
|
|
351
|
-
if (u) filePaths.push(u);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
if (deliverPayload.mediaUrl) {
|
|
355
|
-
filePaths.push(deliverPayload.mediaUrl);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Path B: {openagent_file} text markers
|
|
359
|
-
let cleanText = text;
|
|
360
|
-
const fileMarkerRegex = /\{openagent_file:([^}]+)\}/g;
|
|
361
|
-
let match: RegExpExecArray | null;
|
|
362
|
-
while ((match = fileMarkerRegex.exec(text)) !== null) {
|
|
363
|
-
const markerPath = match[1];
|
|
364
|
-
filePaths.push(markerPath);
|
|
365
|
-
cleanText = cleanText.replace(match[0], '').trim();
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const tripDataPath = findTripCardDataFile(text);
|
|
369
|
-
if (tripDataPath && !filePaths.includes(tripDataPath)) {
|
|
370
|
-
filePaths.push(tripDataPath);
|
|
371
|
-
logger.info(`[c2c-request] T4: attaching trip card data ${tripDataPath}`);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Upload files via TIM C2C (TIMFileElem) — before sending text result
|
|
375
|
-
let fileCount = 0;
|
|
376
|
-
if (filePaths.length > 0) {
|
|
377
|
-
logger.info(`[c2c-request] T4: ${filePaths.length} file(s) detected for requestId=${requestId}`);
|
|
378
|
-
const rt = registry.getDefault();
|
|
379
|
-
const chat = rt?.client?._chat;
|
|
380
|
-
const types = rt?.client?._types;
|
|
381
|
-
|
|
382
|
-
if (chat && types && rt?.client?.isReady) {
|
|
383
|
-
const fs = await import('node:fs');
|
|
384
|
-
const nodePath = await import('node:path');
|
|
385
|
-
|
|
386
|
-
for (let filePath of filePaths) {
|
|
387
|
-
try {
|
|
388
|
-
if (!fs.existsSync(filePath)) {
|
|
389
|
-
logger.warn(`[c2c-request] T4: file not found, skip: ${filePath}`);
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// T4: If path is a directory, zip it first
|
|
394
|
-
if (fs.statSync(filePath).isDirectory()) {
|
|
395
|
-
logger.info(`[c2c-request] T4: path is directory, zipping: ${filePath}`);
|
|
396
|
-
filePath = await zipDirectory(filePath);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const buffer = fs.readFileSync(filePath);
|
|
400
|
-
const fileName = nodePath.basename(filePath);
|
|
401
|
-
const file = new File([buffer], fileName);
|
|
402
|
-
|
|
403
|
-
// TIM SDK expects payload.file to behave like an HTMLInputElement
|
|
404
|
-
// with a .files property (FileList). Node.js File doesn't have this.
|
|
405
|
-
// Wrap in a shim: { files: [File] } to satisfy SDK's internal check
|
|
406
|
-
// at createFileMessage → _isEmptyFileList(s.file.files).
|
|
407
|
-
const fileShim = { files: [file] };
|
|
408
|
-
|
|
409
|
-
logger.info(`[c2c-request] T4: uploading ${fileName} (${buffer.byteLength} bytes) to ${fromUserId}`);
|
|
410
|
-
|
|
411
|
-
const fileMsg = chat.createFileMessage({
|
|
412
|
-
to: fromUserId,
|
|
413
|
-
conversationType: types.CONV_C2C,
|
|
414
|
-
payload: { file: fileShim as unknown as File },
|
|
415
|
-
});
|
|
416
|
-
await chat.sendMessage(fileMsg);
|
|
417
|
-
fileCount++;
|
|
418
|
-
logger.info(`[c2c-request] T4: file sent OK: ${fileName}`);
|
|
419
|
-
} catch (fileErr) {
|
|
420
|
-
logger.error(`[c2c-request] T4: file upload failed: ${(fileErr as Error).message} path=${filePath}`);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
} else {
|
|
424
|
-
logger.warn(`[c2c-request] T4: TIM SDK not ready, skipping file upload`);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Send text result (with file_count for receiver coordination)
|
|
429
|
-
const finalText = filePaths.length > 0 ? cleanText : text;
|
|
430
|
-
if (finalText || fileCount > 0) {
|
|
431
|
-
logger.info(`[c2c-request] deliver final: requestId=${requestId} textLen=${finalText.length} files=${fileCount}`);
|
|
432
|
-
await sendC2CCustomMessage(fromUserId, {
|
|
433
|
-
type: 'openagent_task_result',
|
|
434
|
-
request_id: requestId,
|
|
435
|
-
status: 'complete',
|
|
436
|
-
content: finalText || '(file transfer)',
|
|
437
|
-
file_count: fileCount,
|
|
438
|
-
} as C2CMessagePayload);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
},
|
|
442
|
-
onError: (err: unknown, info: { kind: string }) => {
|
|
443
|
-
logger.error(`[c2c-request] reply ${info.kind} error: ${String(err)}`);
|
|
444
|
-
},
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
logger.debug(`[c2c-request] dispatching AI turn for requestId=${requestId}`);
|
|
448
|
-
|
|
449
|
-
try {
|
|
450
|
-
await channelRuntime.reply.withReplyDispatcher({
|
|
451
|
-
dispatcher,
|
|
452
|
-
run: () =>
|
|
453
|
-
channelRuntime.reply.dispatchReplyFromConfig({
|
|
454
|
-
ctx: finalized,
|
|
455
|
-
cfg: config,
|
|
456
|
-
dispatcher,
|
|
457
|
-
replyOptions: { ...replyOptions, runId },
|
|
458
|
-
}),
|
|
459
|
-
});
|
|
460
|
-
logger.info(`[c2c-request] AI turn completed for requestId=${requestId}`);
|
|
461
|
-
} catch (err) {
|
|
462
|
-
logger.error(`[c2c-request] dispatchReplyFromConfig error: ${(err as Error).message}`);
|
|
463
|
-
await sendErrorResult(fromUserId, requestId, (err as Error).message);
|
|
464
|
-
} finally {
|
|
465
|
-
markDispatchIdle();
|
|
466
|
-
}
|
|
467
|
-
} catch (err) {
|
|
468
|
-
// Top-level error → send error result to requester
|
|
469
|
-
logger.error(`[c2c-request] Fatal error processing request: ${(err as Error).message}`);
|
|
470
|
-
await sendErrorResult(fromUserId, requestId, (err as Error).message);
|
|
471
|
-
} finally {
|
|
472
|
-
// ⑦ Always unsubscribe onAgentEvent listener
|
|
473
|
-
if (unsubscribe) {
|
|
474
|
-
unsubscribe();
|
|
475
|
-
logger.debug(`[c2c-request] onAgentEvent listener unsubscribed for requestId=${requestId}`);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// ── Helpers ──
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Send an error result back to the requesting agent.
|
|
484
|
-
* Best-effort — if this fails, we just log.
|
|
485
|
-
*/
|
|
486
|
-
async function sendErrorResult(
|
|
487
|
-
toUserId: string,
|
|
488
|
-
requestId: string,
|
|
489
|
-
errorMessage: string,
|
|
490
|
-
): Promise<void> {
|
|
491
|
-
try {
|
|
492
|
-
await sendC2CCustomMessage(toUserId, {
|
|
493
|
-
type: 'openagent_task_result',
|
|
494
|
-
request_id: requestId,
|
|
495
|
-
status: 'error',
|
|
496
|
-
content: errorMessage,
|
|
497
|
-
} as C2CMessagePayload);
|
|
498
|
-
} catch (err) {
|
|
499
|
-
logger.error(`[c2c-request] Failed to send error result: ${(err as Error).message}`);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function findTripCardDataFile(text: string): string | null {
|
|
504
|
-
if (!text || !hasTripCardTags(text)) return null;
|
|
505
|
-
|
|
506
|
-
const candidates = getControlUiCandidates().flatMap((dir) => [
|
|
507
|
-
nodePath.join(dir, 'trip-card-data.json'),
|
|
508
|
-
nodePath.join(dir, 'ctrip-card-data.json'),
|
|
509
|
-
]);
|
|
510
|
-
const cardIds = extractTripCardIds(text);
|
|
511
|
-
|
|
512
|
-
for (const candidate of candidates) {
|
|
513
|
-
if (!fs.existsSync(candidate)) continue;
|
|
514
|
-
if (cardIds.length === 0 || tripDataMatches(candidate, cardIds)) {
|
|
515
|
-
return candidate;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
logger.warn(`[c2c-request] Trip card tags found but no matching trip-card-data.json was available`);
|
|
520
|
-
return null;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
function hasTripCardTags(text: string): boolean {
|
|
524
|
-
return /<(?:sh_card|tr_card|i_card|la_card|bg_card|fr_card|time_data_card)>/i.test(text)
|
|
525
|
-
|| /\[TRIP_CARDS_READY\]|\[CTRIP_CARDS_READY\]/.test(text);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function extractTripCardIds(text: string): string[] {
|
|
529
|
-
const ids = new Set<string>();
|
|
530
|
-
const re = /<(?:tr_card|i_card|la_card)>([\s\S]*?)<\/(?:tr_card|i_card|la_card)>/gi;
|
|
531
|
-
let match: RegExpExecArray | null;
|
|
532
|
-
while ((match = re.exec(text)) !== null) {
|
|
533
|
-
const id = match[1]?.trim();
|
|
534
|
-
if (id) ids.add(id);
|
|
535
|
-
}
|
|
536
|
-
return Array.from(ids);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function tripDataMatches(filePath: string, ids: string[]): boolean {
|
|
540
|
-
try {
|
|
541
|
-
const raw = fs.readFileSync(filePath, 'utf8');
|
|
542
|
-
return ids.some((id) => raw.includes(id));
|
|
543
|
-
} catch {
|
|
544
|
-
return false;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
function getControlUiCandidates(): string[] {
|
|
549
|
-
const dirs = new Set<string>();
|
|
550
|
-
const argvEntry = process.argv[1] || '';
|
|
551
|
-
if (argvEntry) {
|
|
552
|
-
dirs.add(nodePath.join(nodePath.dirname(argvEntry), 'control-ui'));
|
|
553
|
-
}
|
|
554
|
-
dirs.add(nodePath.join(process.cwd(), 'dist', 'control-ui'));
|
|
555
|
-
dirs.add('/app/dist/control-ui');
|
|
556
|
-
|
|
557
|
-
return Array.from(dirs).filter((dir) => {
|
|
558
|
-
try {
|
|
559
|
-
return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
|
|
560
|
-
} catch {
|
|
561
|
-
return false;
|
|
562
|
-
}
|
|
563
|
-
});
|
|
564
|
-
}
|