openclaw-openagent 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/dist/index.d.ts +25 -0
  2. package/dist/index.js +105 -0
  3. package/dist/src/app/channel-tools.d.ts +28 -0
  4. package/dist/src/app/channel-tools.js +251 -0
  5. package/dist/src/app/discovery-tools.d.ts +35 -0
  6. package/dist/src/app/discovery-tools.js +243 -0
  7. package/dist/src/app/download-file-tool.d.ts +10 -0
  8. package/dist/src/app/download-file-tool.js +104 -0
  9. package/dist/src/app/hooks.d.ts +14 -0
  10. package/dist/src/app/hooks.js +118 -0
  11. package/dist/src/app/index.d.ts +13 -0
  12. package/dist/src/app/index.js +44 -0
  13. package/dist/src/app/messaging-tools.d.ts +11 -0
  14. package/dist/src/app/messaging-tools.js +79 -0
  15. package/dist/src/app/ops-tools.d.ts +21 -0
  16. package/dist/src/app/ops-tools.js +158 -0
  17. package/dist/src/app/remote-agent-tool.d.ts +27 -0
  18. package/dist/src/app/remote-agent-tool.js +461 -0
  19. package/dist/src/app/types.d.ts +61 -0
  20. package/dist/src/app/types.js +11 -0
  21. package/dist/src/app/upload-file-tool.d.ts +16 -0
  22. package/dist/src/app/upload-file-tool.js +353 -0
  23. package/dist/src/app/verbose-preflight.d.ts +2 -0
  24. package/dist/src/app/verbose-preflight.js +145 -0
  25. package/dist/src/auth/config.d.ts +79 -0
  26. package/dist/src/auth/config.js +133 -0
  27. package/dist/src/auth/credential-manager.d.ts +65 -0
  28. package/dist/src/auth/credential-manager.js +122 -0
  29. package/dist/src/auth/index.d.ts +6 -0
  30. package/dist/src/auth/index.js +6 -0
  31. package/dist/src/auth/verify.d.ts +42 -0
  32. package/dist/src/auth/verify.js +60 -0
  33. package/dist/src/channel.d.ts +269 -0
  34. package/dist/src/channel.js +488 -0
  35. package/dist/src/compat.d.ts +37 -0
  36. package/dist/src/compat.js +70 -0
  37. package/dist/src/config/config-schema.d.ts +56 -0
  38. package/dist/src/config/config-schema.js +34 -0
  39. package/dist/src/messaging/aggregator.d.ts +25 -0
  40. package/dist/src/messaging/aggregator.js +90 -0
  41. package/dist/src/messaging/collector.d.ts +27 -0
  42. package/dist/src/messaging/collector.js +76 -0
  43. package/dist/src/messaging/executor.d.ts +14 -0
  44. package/dist/src/messaging/executor.js +59 -0
  45. package/dist/src/messaging/inbound.d.ts +97 -0
  46. package/dist/src/messaging/inbound.js +63 -0
  47. package/dist/src/messaging/index.d.ts +10 -0
  48. package/dist/src/messaging/index.js +9 -0
  49. package/dist/src/messaging/mention-protocol.d.ts +42 -0
  50. package/dist/src/messaging/mention-protocol.js +74 -0
  51. package/dist/src/messaging/process-c2c-request.d.ts +55 -0
  52. package/dist/src/messaging/process-c2c-request.js +445 -0
  53. package/dist/src/messaging/process-message.d.ts +62 -0
  54. package/dist/src/messaging/process-message.js +282 -0
  55. package/dist/src/messaging/scheduler.d.ts +17 -0
  56. package/dist/src/messaging/scheduler.js +47 -0
  57. package/dist/src/messaging/types.d.ts +34 -0
  58. package/dist/src/messaging/types.js +4 -0
  59. package/dist/src/plugin-ui/assets/openagent-override.js +9267 -0
  60. package/dist/src/plugin-ui/index.d.ts +13 -0
  61. package/dist/src/plugin-ui/index.js +16 -0
  62. package/dist/src/plugin-ui/ui-extension-loader/backup.d.ts +23 -0
  63. package/dist/src/plugin-ui/ui-extension-loader/backup.js +82 -0
  64. package/dist/src/plugin-ui/ui-extension-loader/index.d.ts +28 -0
  65. package/dist/src/plugin-ui/ui-extension-loader/index.js +240 -0
  66. package/dist/src/plugin-ui/ui-extension-loader/locator.d.ts +35 -0
  67. package/dist/src/plugin-ui/ui-extension-loader/locator.js +129 -0
  68. package/dist/src/plugin-ui/ui-extension-loader/manifest.d.ts +26 -0
  69. package/dist/src/plugin-ui/ui-extension-loader/manifest.js +45 -0
  70. package/dist/src/plugin-ui/ui-extension-loader/registry-regex.d.ts +9 -0
  71. package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +701 -0
  72. package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.d.ts +8 -0
  73. package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.js +58 -0
  74. package/dist/src/plugin-ui/ui-extension-loader/types.d.ts +55 -0
  75. package/dist/src/plugin-ui/ui-extension-loader/types.js +23 -0
  76. package/dist/src/proxy/auth-proxy.d.ts +20 -0
  77. package/dist/src/proxy/auth-proxy.js +337 -0
  78. package/dist/src/runtime/account.d.ts +156 -0
  79. package/dist/src/runtime/account.js +491 -0
  80. package/dist/src/runtime/index.d.ts +5 -0
  81. package/dist/src/runtime/index.js +6 -0
  82. package/dist/src/runtime/plugin-runtime.d.ts +40 -0
  83. package/dist/src/runtime/plugin-runtime.js +72 -0
  84. package/dist/src/runtime/registry.d.ts +41 -0
  85. package/dist/src/runtime/registry.js +60 -0
  86. package/dist/src/sdk/CLASS_MAP.md +143 -0
  87. package/dist/src/sdk/index.d.ts +126 -0
  88. package/dist/src/sdk/index.js +23990 -0
  89. package/dist/src/sdk/modules/cloud-search-module.js +1117 -0
  90. package/dist/src/sdk/modules/follow-module.js +1069 -0
  91. package/dist/src/sdk/modules/group-module.js +7397 -0
  92. package/dist/src/sdk/modules/relationship-module.js +2269 -0
  93. package/dist/src/sdk/modules/signaling-module.js +1468 -0
  94. package/dist/src/sdk/modules/tim-upload-plugin.js +730 -0
  95. package/dist/src/sdk/node-env/http-request.js +90 -0
  96. package/dist/src/sdk/node-env/index.js +57 -0
  97. package/dist/src/sdk/node-env/storage.js +114 -0
  98. package/dist/src/sdk/package.json +10 -0
  99. package/dist/src/sdk/sdk/CLASS_MAP.md +143 -0
  100. package/dist/src/sdk/sdk/index.d.ts +126 -0
  101. package/dist/src/sdk/sdk/index.js +23990 -0
  102. package/dist/src/sdk/sdk/modules/cloud-search-module.js +1117 -0
  103. package/dist/src/sdk/sdk/modules/follow-module.js +1069 -0
  104. package/dist/src/sdk/sdk/modules/group-module.js +7397 -0
  105. package/dist/src/sdk/sdk/modules/relationship-module.js +2269 -0
  106. package/dist/src/sdk/sdk/modules/signaling-module.js +1468 -0
  107. package/dist/src/sdk/sdk/modules/tim-upload-plugin.js +730 -0
  108. package/dist/src/sdk/sdk/node-env/http-request.js +90 -0
  109. package/dist/src/sdk/sdk/node-env/index.js +57 -0
  110. package/dist/src/sdk/sdk/node-env/storage.js +114 -0
  111. package/dist/src/sdk/sdk/package.json +10 -0
  112. package/dist/src/sdk/sdk/tsconfig.json +16 -0
  113. package/dist/src/sdk/tsconfig.json +16 -0
  114. package/dist/src/state/pending-invocation-store.d.ts +26 -0
  115. package/dist/src/state/pending-invocation-store.js +39 -0
  116. package/dist/src/state/store.d.ts +165 -0
  117. package/dist/src/state/store.js +535 -0
  118. package/dist/src/tim/c2c.d.ts +83 -0
  119. package/dist/src/tim/c2c.js +318 -0
  120. package/dist/src/tim/channels.d.ts +95 -0
  121. package/dist/src/tim/channels.js +279 -0
  122. package/dist/src/tim/client.d.ts +54 -0
  123. package/dist/src/tim/client.js +268 -0
  124. package/dist/src/tim/index.d.ts +6 -0
  125. package/dist/src/tim/index.js +6 -0
  126. package/dist/src/tim/messages.d.ts +50 -0
  127. package/dist/src/tim/messages.js +104 -0
  128. package/dist/src/tim/sdk-logger-init.d.ts +13 -0
  129. package/dist/src/tim/sdk-logger-init.js +46 -0
  130. package/dist/src/tools.d.ts +9 -0
  131. package/dist/src/tools.js +8 -0
  132. package/dist/src/transport/factory.d.ts +63 -0
  133. package/dist/src/transport/factory.js +54 -0
  134. package/dist/src/transport/oasn/index.d.ts +12 -0
  135. package/dist/src/transport/oasn/index.js +9 -0
  136. package/dist/src/transport/oasn/oasn-agent-card.d.ts +38 -0
  137. package/dist/src/transport/oasn/oasn-agent-card.js +102 -0
  138. package/dist/src/transport/oasn/oasn-discovery.d.ts +33 -0
  139. package/dist/src/transport/oasn/oasn-discovery.js +97 -0
  140. package/dist/src/transport/oasn/oasn-files.d.ts +64 -0
  141. package/dist/src/transport/oasn/oasn-files.js +174 -0
  142. package/dist/src/transport/oasn/oasn-http.d.ts +98 -0
  143. package/dist/src/transport/oasn/oasn-http.js +362 -0
  144. package/dist/src/transport/oasn/oasn-invocation.d.ts +154 -0
  145. package/dist/src/transport/oasn/oasn-invocation.js +432 -0
  146. package/dist/src/transport/oasn/oasn-normalize.d.ts +6 -0
  147. package/dist/src/transport/oasn/oasn-normalize.js +112 -0
  148. package/dist/src/transport/oasn/oasn-register.d.ts +19 -0
  149. package/dist/src/transport/oasn/oasn-register.js +24 -0
  150. package/dist/src/transport/oasn/oasn-transport.d.ts +114 -0
  151. package/dist/src/transport/oasn/oasn-transport.js +230 -0
  152. package/dist/src/transport/oasn/oasn-types.d.ts +331 -0
  153. package/dist/src/transport/oasn/oasn-types.js +44 -0
  154. package/dist/src/transport/tim/index.d.ts +7 -0
  155. package/dist/src/transport/tim/index.js +6 -0
  156. package/dist/src/transport/tim/tim-transport.d.ts +122 -0
  157. package/dist/src/transport/tim/tim-transport.js +402 -0
  158. package/dist/src/transport/types.d.ts +450 -0
  159. package/dist/src/transport/types.js +38 -0
  160. package/dist/src/util/http.d.ts +21 -0
  161. package/dist/src/util/http.js +93 -0
  162. package/dist/src/util/logger.d.ts +20 -0
  163. package/dist/src/util/logger.js +100 -0
  164. package/dist/src/util/url-resolver.d.ts +7 -0
  165. package/dist/src/util/url-resolver.js +20 -0
  166. package/index.ts +11 -0
  167. package/openclaw.plugin.json +9 -0
  168. package/package.json +7 -4
  169. package/src/app/download-file-tool.ts +133 -0
  170. package/src/app/hooks.ts +89 -5
  171. package/src/app/index.ts +6 -0
  172. package/src/app/remote-agent-tool.ts +46 -0
  173. package/src/app/types.ts +1 -0
  174. package/src/app/upload-file-tool.ts +411 -0
  175. package/src/plugin-ui/assets/openagent-override.js +1 -63
  176. package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -61
  177. package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -1
  178. package/src/plugin-ui/ui-extension-loader/registry-regex.ts +30 -13
  179. package/src/runtime/account.ts +0 -23
  180. package/src/transport/oasn/oasn-files.ts +5 -5
  181. package/src/transport/oasn/oasn-invocation.ts +44 -2
  182. package/src/transport/oasn/oasn-normalize.ts +0 -26
  183. package/src/transport/oasn/oasn-register.ts +8 -81
  184. package/src/transport/oasn/oasn-transport.ts +9 -32
  185. package/src/transport/oasn/oasn-types.ts +53 -6
  186. package/src/transport/types.ts +33 -36
  187. package/src/util/url-resolver.ts +17 -0
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Upload File Tool — openagent_upload_file
3
+ *
4
+ * Uploads a local file to the OASN agent network, returning a fileRef
5
+ * that can be passed to openagent_call_remote_agent via input_file_refs.
6
+ *
7
+ * Supports three input modes:
8
+ * - file_path: absolute path to a local file
9
+ * - media_ref: OpenClaw media://inbound/<id> reference (from web UI attachments)
10
+ * - filename_hint: original filename to scan & match from ~/.openclaw/media/inbound/
11
+ *
12
+ * API: POST /api/files (access layer)
13
+ * Ref: access-upload-download-api.md §2
14
+ */
15
+
16
+ import fs from 'node:fs';
17
+ import os from 'node:os';
18
+ import nodePath from 'node:path';
19
+
20
+ import { registry } from '../runtime/registry.js';
21
+ import { logger } from '../util/logger.js';
22
+ import {
23
+ toolResult,
24
+ type ToolDescriptor,
25
+ type ToolParams,
26
+ type ToolResult,
27
+ } from './types.js';
28
+ import type { OasnTransport, UploadFileParams } from '../transport/types.js';
29
+
30
+ // ── Helpers ──
31
+
32
+ const MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024; // 100 MB
33
+
34
+ function fail(error: string): ToolResult {
35
+ return toolResult(JSON.stringify({ success: false, error }));
36
+ }
37
+
38
+ function success(result: Record<string, unknown>): ToolResult {
39
+ return toolResult(JSON.stringify({ success: true, ...result }));
40
+ }
41
+
42
+ /** 根据文件扩展名推断 MIME 类型 */
43
+ function inferMimeType(filePath: string): string {
44
+ const ext = nodePath.extname(filePath).toLowerCase();
45
+ const map: Record<string, string> = {
46
+ '.pdf': 'application/pdf',
47
+ '.png': 'image/png',
48
+ '.jpg': 'image/jpeg',
49
+ '.jpeg': 'image/jpeg',
50
+ '.gif': 'image/gif',
51
+ '.svg': 'image/svg+xml',
52
+ '.webp': 'image/webp',
53
+ '.txt': 'text/plain',
54
+ '.md': 'text/markdown',
55
+ '.json': 'application/json',
56
+ '.csv': 'text/csv',
57
+ '.html': 'text/html',
58
+ '.css': 'text/css',
59
+ '.js': 'text/javascript',
60
+ '.ts': 'text/typescript',
61
+ '.py': 'text/x-python',
62
+ '.xml': 'application/xml',
63
+ '.zip': 'application/zip',
64
+ '.tar': 'application/x-tar',
65
+ '.gz': 'application/gzip',
66
+ '.mp3': 'audio/mpeg',
67
+ '.mp4': 'video/mp4',
68
+ '.doc': 'application/msword',
69
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
70
+ '.xls': 'application/vnd.ms-excel',
71
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
72
+ '.ppt': 'application/vnd.ms-powerpoint',
73
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
74
+ };
75
+ return map[ext] || 'application/octet-stream';
76
+ }
77
+
78
+ /**
79
+ * Resolve a media://inbound/<id> reference to an absolute file path.
80
+ * Uses OpenClaw's resolveMediaBufferPath when available, else falls back
81
+ * to computing the path from ~/.openclaw/media/inbound/<id>.
82
+ */
83
+ async function resolveMediaRef(mediaRef: string): Promise<string> {
84
+ // Parse media://inbound/<id>
85
+ const match = mediaRef.match(/^media:\/\/inbound\/(.+)$/);
86
+ if (!match) {
87
+ throw new Error(`Unsupported media reference format: ${mediaRef}. Expected media://inbound/<id>`);
88
+ }
89
+ const id = match[1]!;
90
+
91
+ // Try OpenClaw SDK's resolveMediaBufferPath first
92
+ try {
93
+ const { resolveMediaBufferPath } = await import('openclaw/plugin-sdk/media-runtime');
94
+ return await resolveMediaBufferPath(id, 'inbound');
95
+ } catch {
96
+ // Fallback: compute path manually
97
+ const mediaDir = nodePath.join(os.homedir(), '.openclaw', 'media', 'inbound');
98
+ const resolved = nodePath.join(mediaDir, id);
99
+ // Security: ensure resolved path stays within mediaDir
100
+ if (!resolved.startsWith(mediaDir + nodePath.sep) && resolved !== mediaDir) {
101
+ throw new Error(`Unsafe media ID: ${id}`);
102
+ }
103
+ if (!fs.existsSync(resolved)) {
104
+ throw new Error(`Media file not found: ${id}. The attachment may have been sent inline (not saved to disk). Use file_path to upload a local file instead.`);
105
+ }
106
+ return resolved;
107
+ }
108
+ }
109
+
110
+ /** Recency window for filename_hint matching (milliseconds) */
111
+ const FILENAME_HINT_RECENCY_MS = 5 * 60 * 1000;
112
+
113
+ /**
114
+ * Scan ~/.openclaw/media/inbound/ for recently modified files,
115
+ * optionally filtered by a predicate. Returns candidates sorted by
116
+ * mtime descending (most recent first).
117
+ */
118
+ function findRecentInboundFiles(
119
+ mediaDir: string,
120
+ filter?: (name: string) => boolean,
121
+ ): { name: string; path: string; mtimeMs: number }[] {
122
+ if (!fs.existsSync(mediaDir)) return [];
123
+
124
+ let entries: string[];
125
+ try {
126
+ entries = fs.readdirSync(mediaDir);
127
+ } catch {
128
+ return [];
129
+ }
130
+
131
+ const now = Date.now();
132
+ return entries
133
+ .filter((name) => {
134
+ if (name.startsWith('.')) return false;
135
+ if (filter && !filter(name)) return false;
136
+ try {
137
+ const stat = fs.statSync(nodePath.join(mediaDir, name));
138
+ return stat.isFile() && (now - stat.mtimeMs) <= FILENAME_HINT_RECENCY_MS;
139
+ } catch {
140
+ return false;
141
+ }
142
+ })
143
+ .map((name) => {
144
+ const fullPath = nodePath.join(mediaDir, name);
145
+ const stat = fs.statSync(fullPath);
146
+ return { name, path: fullPath, mtimeMs: stat.mtimeMs };
147
+ })
148
+ .sort((a, b) => b.mtimeMs - a.mtimeMs);
149
+ }
150
+
151
+ /**
152
+ * Resolve a filename hint to an absolute file path by scanning
153
+ * ~/.openclaw/media/inbound/ for recently modified files whose
154
+ * names contain the hint (case-insensitive substring match).
155
+ *
156
+ * Handles the disk naming convention: <original-base>---<uuid>.<ext>
157
+ * A hint of "photo.png" matches "photo---abc123.png" by extracting
158
+ * the base name "photo" and matching it against the stored filename.
159
+ */
160
+ function resolveByFilenameHint(filenameHint: string): string {
161
+ const mediaDir = nodePath.join(os.homedir(), '.openclaw', 'media', 'inbound');
162
+
163
+ if (!fs.existsSync(mediaDir)) {
164
+ throw new Error(
165
+ `Media inbound directory not found: ${mediaDir}. ` +
166
+ `Ensure a file has been uploaded via the Web UI first.`,
167
+ );
168
+ }
169
+
170
+ // Normalize hint for matching against <base>---<uuid>.<ext> filenames
171
+ const hintLower = filenameHint.toLowerCase();
172
+ const lastDot = filenameHint.lastIndexOf('.');
173
+ const hintBaseLower = lastDot > 0
174
+ ? filenameHint.substring(0, lastDot).toLowerCase()
175
+ : hintLower;
176
+
177
+ const candidates = findRecentInboundFiles(mediaDir, (name) => {
178
+ const nameLower = name.toLowerCase();
179
+ return nameLower.includes(hintLower) || nameLower.includes(hintBaseLower);
180
+ });
181
+
182
+ if (candidates.length === 0) {
183
+ throw new Error(
184
+ `No recently modified file matching "${filenameHint}" found in the media inbound directory. ` +
185
+ `The file may have been renamed, deleted, or uploaded more than 5 minutes ago. ` +
186
+ `Try uploading the file again via the Web UI, or use file_path to specify an absolute path.`,
187
+ );
188
+ }
189
+
190
+ // Security: ensure resolved path stays within mediaDir
191
+ const resolved = candidates[0]!.path;
192
+ const mediaSep = mediaDir + nodePath.sep;
193
+ if (!resolved.startsWith(mediaSep) && resolved !== mediaDir) {
194
+ throw new Error(`Unsafe path resolved from filename hint: ${filenameHint}`);
195
+ }
196
+
197
+ return resolved;
198
+ }
199
+
200
+ /**
201
+ * Resolve the most recently uploaded file from ~/.openclaw/media/inbound/.
202
+ * Used as a last-resort fallback when the model sees an inline image
203
+ * but has no filename or path information at all.
204
+ */
205
+ function resolveLatestInboundFile(): string {
206
+ const mediaDir = nodePath.join(os.homedir(), '.openclaw', 'media', 'inbound');
207
+
208
+ if (!fs.existsSync(mediaDir)) {
209
+ throw new Error(
210
+ `Media inbound directory not found: ${mediaDir}. ` +
211
+ `Ensure a file has been uploaded via the Web UI first.`,
212
+ );
213
+ }
214
+
215
+ const candidates = findRecentInboundFiles(mediaDir);
216
+
217
+ if (candidates.length === 0) {
218
+ throw new Error(
219
+ `No recently uploaded files found in the media inbound directory. ` +
220
+ `Upload a file via the Web UI first, then retry. ` +
221
+ `If you know the file path, use the file_path parameter instead.`,
222
+ );
223
+ }
224
+
225
+ if (candidates.length > 1) {
226
+ // Log a warning so the user/operator can audit unexpected matches
227
+ logger.warn(
228
+ `[upload-tool] auto-resolved latest of ${candidates.length} inbound files: ` +
229
+ `"${candidates[0]!.name}" (others: ${candidates.slice(1, 4).map((c) => c.name).join(', ')})`,
230
+ );
231
+ }
232
+
233
+ const resolved = candidates[0]!.path;
234
+ const mediaSep = mediaDir + nodePath.sep;
235
+ if (!resolved.startsWith(mediaSep) && resolved !== mediaDir) {
236
+ throw new Error('Unsafe path resolved from latest inbound file');
237
+ }
238
+
239
+ return resolved;
240
+ }
241
+
242
+ // ── Tool Factory ──
243
+
244
+ export function createUploadFileTool(): ToolDescriptor {
245
+ return {
246
+ name: 'openagent_upload_file',
247
+ label: 'Upload File',
248
+ description:
249
+ 'Upload a local file to the OASN agent network before calling a remote agent. ' +
250
+ 'Use this when the user wants to send a file (document, image, code, etc.) ' +
251
+ 'to a remote specialist agent for processing. ' +
252
+ 'Returns a fileRef string. Pass this fileRef in the ' +
253
+ 'openagent_call_remote_agent input_file_refs parameter so the remote agent can access the file. ' +
254
+ 'Three input modes (use exactly one, or none to auto-detect): ' +
255
+ '(1) file_path: absolute local path (e.g., /Users/name/Downloads/photo.png); ' +
256
+ '(2) media_ref: a media://inbound/<id> URL visible in the message text; ' +
257
+ '(3) filename_hint: the filename as shown in the upload UI (e.g., "photo.png") — ' +
258
+ 'use this when you see an inline image in the chat but NO media://inbound/ URL. ' +
259
+ 'The tool will automatically scan recently uploaded files to find the match on disk. ' +
260
+ 'If NO parameters are provided, the tool auto-resolves the most recently uploaded file ' +
261
+ 'from the Web UI — this is the easiest option when the user just attached a file. ' +
262
+ 'Prefer media_ref when you see a media://inbound/ URL, ' +
263
+ 'filename_hint when you only see the inline content without a URL, ' +
264
+ 'and file_path when the user explicitly tells you a specific path.',
265
+ parameters: {
266
+ type: 'object',
267
+ properties: {
268
+ file_path: {
269
+ type: 'string',
270
+ description: 'Absolute path to a local file (e.g., /Users/.../report.pdf). Use this for files on disk.',
271
+ },
272
+ media_ref: {
273
+ type: 'string',
274
+ description: 'OpenClaw media reference from web UI attachments (e.g., media://inbound/photo---uuid.png). Use this when the user attached a file in the chat and you see a media://inbound/ URL.',
275
+ },
276
+ filename_hint: {
277
+ type: 'string',
278
+ description:
279
+ 'The original filename as shown in the upload UI (e.g., "photo.png", "report.pdf"). ' +
280
+ 'Use this when you can see the file content inline but do NOT have a media://inbound/ URL. ' +
281
+ 'The tool will scan recently uploaded files to find a match.',
282
+ },
283
+ display_name: {
284
+ type: 'string',
285
+ description: 'Optional display name for the file (defaults to filename)',
286
+ },
287
+ },
288
+ required: [],
289
+ },
290
+
291
+ async execute(
292
+ _id: unknown,
293
+ params: ToolParams,
294
+ ): Promise<ToolResult> {
295
+ const filePathRaw = String(params.file_path || '').trim();
296
+ const mediaRefRaw = String(params.media_ref || '').trim();
297
+ const filenameHint = String(params.filename_hint || '').trim();
298
+
299
+ // ① Resolve input source
300
+ let filePath: string;
301
+ let displayName: string;
302
+
303
+ if (mediaRefRaw) {
304
+ // media://inbound/<id> reference
305
+ try {
306
+ filePath = await resolveMediaRef(mediaRefRaw);
307
+ displayName = String(params.display_name || '').trim()
308
+ || nodePath.basename(filePath);
309
+ } catch (err) {
310
+ return fail((err as Error).message);
311
+ }
312
+ } else if (filePathRaw) {
313
+ // traditional file path
314
+ filePath = filePathRaw;
315
+ displayName = String(params.display_name || '').trim()
316
+ || nodePath.basename(filePath);
317
+ } else if (filenameHint) {
318
+ // filename hint → scan ~/.openclaw/media/inbound/ for matching file
319
+ try {
320
+ filePath = resolveByFilenameHint(filenameHint);
321
+ displayName = String(params.display_name || '').trim()
322
+ || nodePath.basename(filePath);
323
+ } catch (err) {
324
+ return fail((err as Error).message);
325
+ }
326
+ } else {
327
+ // fallback: auto-resolve the most recently uploaded file
328
+ try {
329
+ filePath = resolveLatestInboundFile();
330
+ displayName = String(params.display_name || '').trim()
331
+ || nodePath.basename(filePath);
332
+ logger.info(
333
+ `[upload-tool] Auto-resolved latest inbound file: ${filePath}`,
334
+ );
335
+ } catch (err) {
336
+ return fail((err as Error).message);
337
+ }
338
+ }
339
+
340
+ // ② 检查运行态
341
+ const rt = registry.getDefault();
342
+ if (!rt || !rt.isRunning) {
343
+ return fail('OpenAgent runtime not running');
344
+ }
345
+ if (rt.transportType !== 'oasn') {
346
+ return fail('File upload is only available via OASN transport');
347
+ }
348
+ const transport = rt.transport;
349
+ if (!transport || transport.transportType !== 'oasn') {
350
+ return fail('OASN transport not ready');
351
+ }
352
+
353
+ // ③ 读取文件
354
+ let stat: fs.Stats;
355
+ try {
356
+ stat = fs.statSync(filePath);
357
+ } catch {
358
+ return fail(`File not found: ${filePath}`);
359
+ }
360
+ if (!stat.isFile()) {
361
+ return fail(`Not a file: ${filePath}`);
362
+ }
363
+ if (stat.size > MAX_FILE_SIZE_BYTES) {
364
+ return fail(
365
+ `File too large: ${(stat.size / (1024 * 1024)).toFixed(1)} MB (max 100 MB)`,
366
+ );
367
+ }
368
+
369
+ let buffer: Buffer;
370
+ try {
371
+ buffer = fs.readFileSync(filePath);
372
+ } catch (err) {
373
+ return fail(`Cannot read file: ${(err as Error).message}`);
374
+ }
375
+
376
+ // ④ 上传
377
+ const mimeType = inferMimeType(filePath);
378
+
379
+ const oasnTransport = transport as OasnTransport;
380
+ const uploadParams: UploadFileParams = {
381
+ content: buffer,
382
+ displayName,
383
+ mimeType,
384
+ };
385
+
386
+ logger.info(
387
+ `[upload-tool] Uploading file: name=${displayName} size=${buffer.byteLength} mime=${mimeType}`,
388
+ );
389
+
390
+ let result;
391
+ try {
392
+ result = await oasnTransport.uploadFile(uploadParams);
393
+ } catch (err) {
394
+ logger.error(`[upload-tool] Upload failed: ${(err as Error).message}`);
395
+ return fail(`Upload failed: ${(err as Error).message}`);
396
+ }
397
+
398
+ logger.info(
399
+ `[upload-tool] Upload success: fileRef=${result.fileRef} size=${result.sizeBytes}`,
400
+ );
401
+
402
+ return success({
403
+ fileRef: result.fileRef,
404
+ displayName: result.displayName,
405
+ mimeType: result.mimeType,
406
+ sizeBytes: result.sizeBytes,
407
+ expiresAt: result.expiresAt,
408
+ });
409
+ },
410
+ };
411
+ }
@@ -2,7 +2,7 @@
2
2
  * OpenAgent UI Override — Auto-generated, do not edit directly!
3
3
  *
4
4
  * Source: src/plugin-ui/modules/
5
- * Built: 2026-06-23T07:31:34.439Z
5
+ * Built: 2026-06-25T03:47:26.820Z
6
6
  *
7
7
  * To modify, edit the source modules and run:
8
8
  * npm run build:override
@@ -797,7 +797,6 @@ CL.fetchTripCardDataOnce = function(source, options) {
797
797
  const SEARCH_API = CL.AUTH_API + '/api/agent-cards/search/by-query';
798
798
  const SEARCH_BY_CATEGORY_API = CL.AUTH_API + '/api/agent-cards/search/by-category';
799
799
  const CATEGORIES_API = CL.AUTH_API + '/api/agent-card-categories';
800
- const TAGLINE_API = CL.AUTH_API + '/api/conversations/generate-title';
801
800
 
802
801
  // ── Mock 数据(仅显式调试时使用)──
803
802
  // 格式对齐 OASN AgentCardResponse。生产路径不在 API 失败或空结果时自动回退。
@@ -2591,9 +2590,6 @@ list.innerHTML = '<div class="openagent-loading">加载中...</div>';
2591
2590
  const query = extractSearchQuery(textarea);
2592
2591
  console.log('[openagent] openPanel with search query:', JSON.stringify(query));
2593
2592
 
2594
- // 动态生成面板标题 tagline
2595
- _updatePanelTagline(query);
2596
-
2597
2593
  if (query) {
2598
2594
  // 有输入文本 → 语义搜索
2599
2595
  searchAgents(query).then(agents => {
@@ -2608,62 +2604,6 @@ if (query) {
2608
2604
  });
2609
2605
  }
2610
2606
  }
2611
-
2612
- /** 更新面板标题:优先 API,降级到客户端生成 */
2613
- let _authSigCache = null; // { userSig, expiresAt }
2614
-
2615
- async function _getAuthSig() {
2616
- if (_authSigCache && Date.now() < _authSigCache.expiresAt) {
2617
- return _authSigCache.userSig;
2618
- }
2619
- try {
2620
- const res = await fetch('./openagent-auth.json?t=' + Date.now());
2621
- if (!res.ok) return null;
2622
- const data = await res.json();
2623
- if (data?.userSig) {
2624
- _authSigCache = data;
2625
- return data.userSig;
2626
- }
2627
- } catch { }
2628
- return null;
2629
- }
2630
-
2631
- function _updatePanelTagline(query) {
2632
- if (!panel) return;
2633
- const titleEl = panel.querySelector('.panel-title-text');
2634
- if (!titleEl) return;
2635
-
2636
- // 客户端即时文案(无延迟)
2637
- const localTagline = query
2638
- ? `匹配${query.length > 12 ? query.slice(0, 12) + '…' : query}AGENT`
2639
- : '在线AGENT | 2.4M';
2640
- titleEl.textContent = localTagline;
2641
- console.log('[openagent] tagline: query=', JSON.stringify(query), 'local=', localTagline);
2642
-
2643
- // 异步获取鉴权 + 调用 tagline API
2644
- _getAuthSig().then(sig => {
2645
- console.log('[openagent] tagline: authSig=', sig ? `${sig.slice(0,20)}...` : 'null');
2646
- if (!sig) return; // 无鉴权,保持客户端文案
2647
- return fetch(TAGLINE_API, {
2648
- method: 'POST',
2649
- headers: {
2650
- 'Content-Type': 'application/json',
2651
- 'Authorization': `Bearer ${sig}`,
2652
- },
2653
- body: JSON.stringify({ type: 'tagline', query: query || '' }),
2654
- });
2655
- })
2656
- .then(r => {
2657
- console.log('[openagent] tagline: response status=', r?.status);
2658
- return r?.ok ? r.json() : null;
2659
- })
2660
- .then(data => {
2661
- console.log('[openagent] tagline: API result=', JSON.stringify(data));
2662
- // 标题固定为 Agent Book,不再被 API 覆盖
2663
- })
2664
- .catch(err => { console.warn('[openagent] tagline error:', err); });
2665
- }
2666
-
2667
2607
  /**
2668
2608
  * 实时搜索更新(debounce 300ms,对齐 MentionPickerPopup)
2669
2609
  * 当面板打开时,textarea 每次输入都会触发
@@ -2684,8 +2624,6 @@ searchDebounceId = setTimeout(() => {
2684
2624
 
2685
2625
  list.innerHTML = '<div class="openagent-loading">搜索中...</div>';
2686
2626
 
2687
- // 同步更新面板标题
2688
- _updatePanelTagline(query);
2689
2627
 
2690
2628
  const doSearch = query
2691
2629
  ? searchAgents(query, signal)
@@ -111,9 +111,6 @@ list.innerHTML = '<div class="openagent-loading">加载中...</div>';
111
111
  const query = extractSearchQuery(textarea);
112
112
  console.log('[openagent] openPanel with search query:', JSON.stringify(query));
113
113
 
114
- // 动态生成面板标题 tagline
115
- _updatePanelTagline(query);
116
-
117
114
  if (query) {
118
115
  // 有输入文本 → 语义搜索
119
116
  searchAgents(query).then(agents => {
@@ -128,62 +125,6 @@ if (query) {
128
125
  });
129
126
  }
130
127
  }
131
-
132
- /** 更新面板标题:优先 API,降级到客户端生成 */
133
- let _authSigCache = null; // { userSig, expiresAt }
134
-
135
- async function _getAuthSig() {
136
- if (_authSigCache && Date.now() < _authSigCache.expiresAt) {
137
- return _authSigCache.userSig;
138
- }
139
- try {
140
- const res = await fetch('./openagent-auth.json?t=' + Date.now());
141
- if (!res.ok) return null;
142
- const data = await res.json();
143
- if (data?.userSig) {
144
- _authSigCache = data;
145
- return data.userSig;
146
- }
147
- } catch { }
148
- return null;
149
- }
150
-
151
- function _updatePanelTagline(query) {
152
- if (!panel) return;
153
- const titleEl = panel.querySelector('.panel-title-text');
154
- if (!titleEl) return;
155
-
156
- // 客户端即时文案(无延迟)
157
- const localTagline = query
158
- ? `匹配${query.length > 12 ? query.slice(0, 12) + '…' : query}AGENT`
159
- : '在线AGENT | 2.4M';
160
- titleEl.textContent = localTagline;
161
- console.log('[openagent] tagline: query=', JSON.stringify(query), 'local=', localTagline);
162
-
163
- // 异步获取鉴权 + 调用 tagline API
164
- _getAuthSig().then(sig => {
165
- console.log('[openagent] tagline: authSig=', sig ? `${sig.slice(0,20)}...` : 'null');
166
- if (!sig) return; // 无鉴权,保持客户端文案
167
- return fetch(TAGLINE_API, {
168
- method: 'POST',
169
- headers: {
170
- 'Content-Type': 'application/json',
171
- 'Authorization': `Bearer ${sig}`,
172
- },
173
- body: JSON.stringify({ type: 'tagline', query: query || '' }),
174
- });
175
- })
176
- .then(r => {
177
- console.log('[openagent] tagline: response status=', r?.status);
178
- return r?.ok ? r.json() : null;
179
- })
180
- .then(data => {
181
- console.log('[openagent] tagline: API result=', JSON.stringify(data));
182
- // 标题固定为 Agent Book,不再被 API 覆盖
183
- })
184
- .catch(err => { console.warn('[openagent] tagline error:', err); });
185
- }
186
-
187
128
  /**
188
129
  * 实时搜索更新(debounce 300ms,对齐 MentionPickerPopup)
189
130
  * 当面板打开时,textarea 每次输入都会触发
@@ -204,8 +145,6 @@ searchDebounceId = setTimeout(() => {
204
145
 
205
146
  list.innerHTML = '<div class="openagent-loading">搜索中...</div>';
206
147
 
207
- // 同步更新面板标题
208
- _updatePanelTagline(query);
209
148
 
210
149
  const doSearch = query
211
150
  ? searchAgents(query, signal)
@@ -11,7 +11,6 @@
11
11
  const SEARCH_API = CL.AUTH_API + '/api/agent-cards/search/by-query';
12
12
  const SEARCH_BY_CATEGORY_API = CL.AUTH_API + '/api/agent-cards/search/by-category';
13
13
  const CATEGORIES_API = CL.AUTH_API + '/api/agent-card-categories';
14
- const TAGLINE_API = CL.AUTH_API + '/api/conversations/generate-title';
15
14
 
16
15
  // ── Mock 数据(仅显式调试时使用)──
17
16
  // 格式对齐 OASN AgentCardResponse。生产路径不在 API 失败或空结果时自动回退。