openclaw-openagent 1.0.1 → 1.0.3

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 (188) 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 +9278 -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 +724 -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 +15 -66
  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/modules/agent-book/panel/inject-ui.js +14 -3
  179. package/src/plugin-ui/ui-extension-loader/registry-regex.ts +102 -63
  180. package/src/runtime/account.ts +0 -23
  181. package/src/transport/oasn/oasn-files.ts +5 -5
  182. package/src/transport/oasn/oasn-invocation.ts +44 -2
  183. package/src/transport/oasn/oasn-normalize.ts +0 -26
  184. package/src/transport/oasn/oasn-register.ts +8 -81
  185. package/src/transport/oasn/oasn-transport.ts +9 -32
  186. package/src/transport/oasn/oasn-types.ts +53 -6
  187. package/src/transport/types.ts +33 -36
  188. package/src/util/url-resolver.ts +17 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared tool types
3
+ *
4
+ * Return format aligned with OpenClaw SDK's AgentToolResult:
5
+ * @see https://docs.openclaw.ai (Plugin SDK → Building Plugins)
6
+ * @see docs/reference/openclaw/v2026.3.23/dist/plugin-sdk/src/agents/tools/common.d.ts
7
+ */
8
+ export interface ToolParams {
9
+ [key: string]: unknown;
10
+ }
11
+ /** SDK-compatible tool result (matches AgentToolResult structure) */
12
+ export interface ToolResult {
13
+ content: Array<{
14
+ type: 'text';
15
+ text: string;
16
+ }>;
17
+ /** Optional details for rich media. Matches OpenClaw AgentToolResult<TDetails>. */
18
+ details?: {
19
+ media?: {
20
+ mediaUrls: string[];
21
+ };
22
+ [key: string]: unknown;
23
+ };
24
+ }
25
+ export type ToolUpdateCallback = (partialResult: ToolResult) => void;
26
+ export interface ToolDescriptor {
27
+ name: string;
28
+ label: string;
29
+ description: string;
30
+ parameters: {
31
+ type: 'object';
32
+ properties: Record<string, {
33
+ type: string;
34
+ description: string;
35
+ enum?: string[];
36
+ default?: unknown;
37
+ items?: {
38
+ type: string;
39
+ };
40
+ }>;
41
+ required: string[];
42
+ };
43
+ /**
44
+ * Execute the tool.
45
+ *
46
+ * OpenClaw runtime usually calls:
47
+ * tool.execute(toolCallId, params, signal, onUpdate)
48
+ *
49
+ * Older adapters used:
50
+ * tool.execute(toolCallId, params, onUpdate, ctx, signal)
51
+ *
52
+ * The wider signature lets streaming tools normalize both shapes while
53
+ * existing 2-param tools continue to work unchanged.
54
+ *
55
+ * @see docs/reference/openclaw/v2026.3.23/dist/pi-embedded-CswW9luA.js L169823
56
+ */
57
+ execute: (_id: unknown, params: ToolParams, signalOrUpdate?: AbortSignal | ToolUpdateCallback, onUpdateOrContext?: ToolUpdateCallback | unknown, legacySignal?: AbortSignal) => Promise<ToolResult>;
58
+ }
59
+ export type ToolFactory = () => ToolDescriptor;
60
+ /** Helper: create a successful text result in SDK format */
61
+ export declare function toolResult(text: string): ToolResult;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared tool types
3
+ *
4
+ * Return format aligned with OpenClaw SDK's AgentToolResult:
5
+ * @see https://docs.openclaw.ai (Plugin SDK → Building Plugins)
6
+ * @see docs/reference/openclaw/v2026.3.23/dist/plugin-sdk/src/agents/tools/common.d.ts
7
+ */
8
+ /** Helper: create a successful text result in SDK format */
9
+ export function toolResult(text) {
10
+ return { content: [{ type: 'text', text }] };
11
+ }
@@ -0,0 +1,16 @@
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
+ import { type ToolDescriptor } from './types.js';
16
+ export declare function createUploadFileTool(): ToolDescriptor;
@@ -0,0 +1,353 @@
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
+ import fs from 'node:fs';
16
+ import os from 'node:os';
17
+ import nodePath from 'node:path';
18
+ import { registry } from '../runtime/registry.js';
19
+ import { logger } from '../util/logger.js';
20
+ import { toolResult, } from './types.js';
21
+ // ── Helpers ──
22
+ const MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024; // 100 MB
23
+ function fail(error) {
24
+ return toolResult(JSON.stringify({ success: false, error }));
25
+ }
26
+ function success(result) {
27
+ return toolResult(JSON.stringify({ success: true, ...result }));
28
+ }
29
+ /** 根据文件扩展名推断 MIME 类型 */
30
+ function inferMimeType(filePath) {
31
+ const ext = nodePath.extname(filePath).toLowerCase();
32
+ const map = {
33
+ '.pdf': 'application/pdf',
34
+ '.png': 'image/png',
35
+ '.jpg': 'image/jpeg',
36
+ '.jpeg': 'image/jpeg',
37
+ '.gif': 'image/gif',
38
+ '.svg': 'image/svg+xml',
39
+ '.webp': 'image/webp',
40
+ '.txt': 'text/plain',
41
+ '.md': 'text/markdown',
42
+ '.json': 'application/json',
43
+ '.csv': 'text/csv',
44
+ '.html': 'text/html',
45
+ '.css': 'text/css',
46
+ '.js': 'text/javascript',
47
+ '.ts': 'text/typescript',
48
+ '.py': 'text/x-python',
49
+ '.xml': 'application/xml',
50
+ '.zip': 'application/zip',
51
+ '.tar': 'application/x-tar',
52
+ '.gz': 'application/gzip',
53
+ '.mp3': 'audio/mpeg',
54
+ '.mp4': 'video/mp4',
55
+ '.doc': 'application/msword',
56
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
57
+ '.xls': 'application/vnd.ms-excel',
58
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
59
+ '.ppt': 'application/vnd.ms-powerpoint',
60
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
61
+ };
62
+ return map[ext] || 'application/octet-stream';
63
+ }
64
+ /**
65
+ * Resolve a media://inbound/<id> reference to an absolute file path.
66
+ * Uses OpenClaw's resolveMediaBufferPath when available, else falls back
67
+ * to computing the path from ~/.openclaw/media/inbound/<id>.
68
+ */
69
+ async function resolveMediaRef(mediaRef) {
70
+ // Parse media://inbound/<id>
71
+ const match = mediaRef.match(/^media:\/\/inbound\/(.+)$/);
72
+ if (!match) {
73
+ throw new Error(`Unsupported media reference format: ${mediaRef}. Expected media://inbound/<id>`);
74
+ }
75
+ const id = match[1];
76
+ // Try OpenClaw SDK's resolveMediaBufferPath first
77
+ try {
78
+ const { resolveMediaBufferPath } = await import('openclaw/plugin-sdk/media-runtime');
79
+ return await resolveMediaBufferPath(id, 'inbound');
80
+ }
81
+ catch {
82
+ // Fallback: compute path manually
83
+ const mediaDir = nodePath.join(os.homedir(), '.openclaw', 'media', 'inbound');
84
+ const resolved = nodePath.join(mediaDir, id);
85
+ // Security: ensure resolved path stays within mediaDir
86
+ if (!resolved.startsWith(mediaDir + nodePath.sep) && resolved !== mediaDir) {
87
+ throw new Error(`Unsafe media ID: ${id}`);
88
+ }
89
+ if (!fs.existsSync(resolved)) {
90
+ 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.`);
91
+ }
92
+ return resolved;
93
+ }
94
+ }
95
+ /** Recency window for filename_hint matching (milliseconds) */
96
+ const FILENAME_HINT_RECENCY_MS = 5 * 60 * 1000;
97
+ /**
98
+ * Scan ~/.openclaw/media/inbound/ for recently modified files,
99
+ * optionally filtered by a predicate. Returns candidates sorted by
100
+ * mtime descending (most recent first).
101
+ */
102
+ function findRecentInboundFiles(mediaDir, filter) {
103
+ if (!fs.existsSync(mediaDir))
104
+ return [];
105
+ let entries;
106
+ try {
107
+ entries = fs.readdirSync(mediaDir);
108
+ }
109
+ catch {
110
+ return [];
111
+ }
112
+ const now = Date.now();
113
+ return entries
114
+ .filter((name) => {
115
+ if (name.startsWith('.'))
116
+ return false;
117
+ if (filter && !filter(name))
118
+ return false;
119
+ try {
120
+ const stat = fs.statSync(nodePath.join(mediaDir, name));
121
+ return stat.isFile() && (now - stat.mtimeMs) <= FILENAME_HINT_RECENCY_MS;
122
+ }
123
+ catch {
124
+ return false;
125
+ }
126
+ })
127
+ .map((name) => {
128
+ const fullPath = nodePath.join(mediaDir, name);
129
+ const stat = fs.statSync(fullPath);
130
+ return { name, path: fullPath, mtimeMs: stat.mtimeMs };
131
+ })
132
+ .sort((a, b) => b.mtimeMs - a.mtimeMs);
133
+ }
134
+ /**
135
+ * Resolve a filename hint to an absolute file path by scanning
136
+ * ~/.openclaw/media/inbound/ for recently modified files whose
137
+ * names contain the hint (case-insensitive substring match).
138
+ *
139
+ * Handles the disk naming convention: <original-base>---<uuid>.<ext>
140
+ * A hint of "photo.png" matches "photo---abc123.png" by extracting
141
+ * the base name "photo" and matching it against the stored filename.
142
+ */
143
+ function resolveByFilenameHint(filenameHint) {
144
+ const mediaDir = nodePath.join(os.homedir(), '.openclaw', 'media', 'inbound');
145
+ if (!fs.existsSync(mediaDir)) {
146
+ throw new Error(`Media inbound directory not found: ${mediaDir}. ` +
147
+ `Ensure a file has been uploaded via the Web UI first.`);
148
+ }
149
+ // Normalize hint for matching against <base>---<uuid>.<ext> filenames
150
+ const hintLower = filenameHint.toLowerCase();
151
+ const lastDot = filenameHint.lastIndexOf('.');
152
+ const hintBaseLower = lastDot > 0
153
+ ? filenameHint.substring(0, lastDot).toLowerCase()
154
+ : hintLower;
155
+ const candidates = findRecentInboundFiles(mediaDir, (name) => {
156
+ const nameLower = name.toLowerCase();
157
+ return nameLower.includes(hintLower) || nameLower.includes(hintBaseLower);
158
+ });
159
+ if (candidates.length === 0) {
160
+ throw new Error(`No recently modified file matching "${filenameHint}" found in the media inbound directory. ` +
161
+ `The file may have been renamed, deleted, or uploaded more than 5 minutes ago. ` +
162
+ `Try uploading the file again via the Web UI, or use file_path to specify an absolute path.`);
163
+ }
164
+ // Security: ensure resolved path stays within mediaDir
165
+ const resolved = candidates[0].path;
166
+ const mediaSep = mediaDir + nodePath.sep;
167
+ if (!resolved.startsWith(mediaSep) && resolved !== mediaDir) {
168
+ throw new Error(`Unsafe path resolved from filename hint: ${filenameHint}`);
169
+ }
170
+ return resolved;
171
+ }
172
+ /**
173
+ * Resolve the most recently uploaded file from ~/.openclaw/media/inbound/.
174
+ * Used as a last-resort fallback when the model sees an inline image
175
+ * but has no filename or path information at all.
176
+ */
177
+ function resolveLatestInboundFile() {
178
+ const mediaDir = nodePath.join(os.homedir(), '.openclaw', 'media', 'inbound');
179
+ if (!fs.existsSync(mediaDir)) {
180
+ throw new Error(`Media inbound directory not found: ${mediaDir}. ` +
181
+ `Ensure a file has been uploaded via the Web UI first.`);
182
+ }
183
+ const candidates = findRecentInboundFiles(mediaDir);
184
+ if (candidates.length === 0) {
185
+ throw new Error(`No recently uploaded files found in the media inbound directory. ` +
186
+ `Upload a file via the Web UI first, then retry. ` +
187
+ `If you know the file path, use the file_path parameter instead.`);
188
+ }
189
+ if (candidates.length > 1) {
190
+ // Log a warning so the user/operator can audit unexpected matches
191
+ logger.warn(`[upload-tool] auto-resolved latest of ${candidates.length} inbound files: ` +
192
+ `"${candidates[0].name}" (others: ${candidates.slice(1, 4).map((c) => c.name).join(', ')})`);
193
+ }
194
+ const resolved = candidates[0].path;
195
+ const mediaSep = mediaDir + nodePath.sep;
196
+ if (!resolved.startsWith(mediaSep) && resolved !== mediaDir) {
197
+ throw new Error('Unsafe path resolved from latest inbound file');
198
+ }
199
+ return resolved;
200
+ }
201
+ // ── Tool Factory ──
202
+ export function createUploadFileTool() {
203
+ return {
204
+ name: 'openagent_upload_file',
205
+ label: 'Upload File',
206
+ description: 'Upload a local file to the OASN agent network before calling a remote agent. ' +
207
+ 'Use this when the user wants to send a file (document, image, code, etc.) ' +
208
+ 'to a remote specialist agent for processing. ' +
209
+ 'Returns a fileRef string. Pass this fileRef in the ' +
210
+ 'openagent_call_remote_agent input_file_refs parameter so the remote agent can access the file. ' +
211
+ 'Three input modes (use exactly one, or none to auto-detect): ' +
212
+ '(1) file_path: absolute local path (e.g., /Users/name/Downloads/photo.png); ' +
213
+ '(2) media_ref: a media://inbound/<id> URL visible in the message text; ' +
214
+ '(3) filename_hint: the filename as shown in the upload UI (e.g., "photo.png") — ' +
215
+ 'use this when you see an inline image in the chat but NO media://inbound/ URL. ' +
216
+ 'The tool will automatically scan recently uploaded files to find the match on disk. ' +
217
+ 'If NO parameters are provided, the tool auto-resolves the most recently uploaded file ' +
218
+ 'from the Web UI — this is the easiest option when the user just attached a file. ' +
219
+ 'Prefer media_ref when you see a media://inbound/ URL, ' +
220
+ 'filename_hint when you only see the inline content without a URL, ' +
221
+ 'and file_path when the user explicitly tells you a specific path.',
222
+ parameters: {
223
+ type: 'object',
224
+ properties: {
225
+ file_path: {
226
+ type: 'string',
227
+ description: 'Absolute path to a local file (e.g., /Users/.../report.pdf). Use this for files on disk.',
228
+ },
229
+ media_ref: {
230
+ type: 'string',
231
+ 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.',
232
+ },
233
+ filename_hint: {
234
+ type: 'string',
235
+ description: 'The original filename as shown in the upload UI (e.g., "photo.png", "report.pdf"). ' +
236
+ 'Use this when you can see the file content inline but do NOT have a media://inbound/ URL. ' +
237
+ 'The tool will scan recently uploaded files to find a match.',
238
+ },
239
+ display_name: {
240
+ type: 'string',
241
+ description: 'Optional display name for the file (defaults to filename)',
242
+ },
243
+ },
244
+ required: [],
245
+ },
246
+ async execute(_id, params) {
247
+ const filePathRaw = String(params.file_path || '').trim();
248
+ const mediaRefRaw = String(params.media_ref || '').trim();
249
+ const filenameHint = String(params.filename_hint || '').trim();
250
+ // ① Resolve input source
251
+ let filePath;
252
+ let displayName;
253
+ if (mediaRefRaw) {
254
+ // media://inbound/<id> reference
255
+ try {
256
+ filePath = await resolveMediaRef(mediaRefRaw);
257
+ displayName = String(params.display_name || '').trim()
258
+ || nodePath.basename(filePath);
259
+ }
260
+ catch (err) {
261
+ return fail(err.message);
262
+ }
263
+ }
264
+ else if (filePathRaw) {
265
+ // traditional file path
266
+ filePath = filePathRaw;
267
+ displayName = String(params.display_name || '').trim()
268
+ || nodePath.basename(filePath);
269
+ }
270
+ else if (filenameHint) {
271
+ // filename hint → scan ~/.openclaw/media/inbound/ for matching file
272
+ try {
273
+ filePath = resolveByFilenameHint(filenameHint);
274
+ displayName = String(params.display_name || '').trim()
275
+ || nodePath.basename(filePath);
276
+ }
277
+ catch (err) {
278
+ return fail(err.message);
279
+ }
280
+ }
281
+ else {
282
+ // fallback: auto-resolve the most recently uploaded file
283
+ try {
284
+ filePath = resolveLatestInboundFile();
285
+ displayName = String(params.display_name || '').trim()
286
+ || nodePath.basename(filePath);
287
+ logger.info(`[upload-tool] Auto-resolved latest inbound file: ${filePath}`);
288
+ }
289
+ catch (err) {
290
+ return fail(err.message);
291
+ }
292
+ }
293
+ // ② 检查运行态
294
+ const rt = registry.getDefault();
295
+ if (!rt || !rt.isRunning) {
296
+ return fail('OpenAgent runtime not running');
297
+ }
298
+ if (rt.transportType !== 'oasn') {
299
+ return fail('File upload is only available via OASN transport');
300
+ }
301
+ const transport = rt.transport;
302
+ if (!transport || transport.transportType !== 'oasn') {
303
+ return fail('OASN transport not ready');
304
+ }
305
+ // ③ 读取文件
306
+ let stat;
307
+ try {
308
+ stat = fs.statSync(filePath);
309
+ }
310
+ catch {
311
+ return fail(`File not found: ${filePath}`);
312
+ }
313
+ if (!stat.isFile()) {
314
+ return fail(`Not a file: ${filePath}`);
315
+ }
316
+ if (stat.size > MAX_FILE_SIZE_BYTES) {
317
+ return fail(`File too large: ${(stat.size / (1024 * 1024)).toFixed(1)} MB (max 100 MB)`);
318
+ }
319
+ let buffer;
320
+ try {
321
+ buffer = fs.readFileSync(filePath);
322
+ }
323
+ catch (err) {
324
+ return fail(`Cannot read file: ${err.message}`);
325
+ }
326
+ // ④ 上传
327
+ const mimeType = inferMimeType(filePath);
328
+ const oasnTransport = transport;
329
+ const uploadParams = {
330
+ content: buffer,
331
+ displayName,
332
+ mimeType,
333
+ };
334
+ logger.info(`[upload-tool] Uploading file: name=${displayName} size=${buffer.byteLength} mime=${mimeType}`);
335
+ let result;
336
+ try {
337
+ result = await oasnTransport.uploadFile(uploadParams);
338
+ }
339
+ catch (err) {
340
+ logger.error(`[upload-tool] Upload failed: ${err.message}`);
341
+ return fail(`Upload failed: ${err.message}`);
342
+ }
343
+ logger.info(`[upload-tool] Upload success: fileRef=${result.fileRef} size=${result.sizeBytes}`);
344
+ return success({
345
+ fileRef: result.fileRef,
346
+ displayName: result.displayName,
347
+ mimeType: result.mimeType,
348
+ sizeBytes: result.sizeBytes,
349
+ expiresAt: result.expiresAt,
350
+ });
351
+ },
352
+ };
353
+ }
@@ -0,0 +1,2 @@
1
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk/plugin-entry';
2
+ export declare function installOpenagentVerbosePreflight(api: OpenClawPluginApi): void;
@@ -0,0 +1,145 @@
1
+ import { logger } from '../util/logger.js';
2
+ const AGENT_EVENT_STATE_KEY = Symbol.for('openclaw.agentEvents.state');
3
+ const REMOTE_TOOL_CALL_IDS = new Set();
4
+ const REMOTE_TOOL_CALL_IDS_BY_RUN = new Map();
5
+ const PROMOTED_RUNS = new Set();
6
+ let installed = false;
7
+ let missingStateWarned = false;
8
+ let missingRuntimeWarned = false;
9
+ function asRecord(value) {
10
+ return value && typeof value === 'object' ? value : {};
11
+ }
12
+ function readString(value) {
13
+ return typeof value === 'string' ? value : '';
14
+ }
15
+ function readToolName(data) {
16
+ const call = asRecord(data.call);
17
+ return (readString(data.name) ||
18
+ readString(data.toolName) ||
19
+ readString(data.tool_name) ||
20
+ readString(call.name)).toLowerCase();
21
+ }
22
+ function readToolCallId(data) {
23
+ const call = asRecord(data.call);
24
+ return (readString(data.toolCallId) ||
25
+ readString(data.tool_call_id) ||
26
+ readString(data.id) ||
27
+ readString(call.id));
28
+ }
29
+ function isOpenagentRemoteToolName(name) {
30
+ return name.includes('call_remote_agent') || name.includes('openagent_delegate');
31
+ }
32
+ function rememberRemoteToolCall(runId, toolCallId) {
33
+ REMOTE_TOOL_CALL_IDS.add(toolCallId);
34
+ let runToolCallIds = REMOTE_TOOL_CALL_IDS_BY_RUN.get(runId);
35
+ if (!runToolCallIds) {
36
+ runToolCallIds = new Set();
37
+ REMOTE_TOOL_CALL_IDS_BY_RUN.set(runId, runToolCallIds);
38
+ }
39
+ runToolCallIds.add(toolCallId);
40
+ }
41
+ function forgetRemoteToolCall(runId, toolCallId) {
42
+ REMOTE_TOOL_CALL_IDS.delete(toolCallId);
43
+ const runToolCallIds = REMOTE_TOOL_CALL_IDS_BY_RUN.get(runId);
44
+ if (!runToolCallIds)
45
+ return;
46
+ runToolCallIds.delete(toolCallId);
47
+ if (runToolCallIds.size === 0) {
48
+ REMOTE_TOOL_CALL_IDS_BY_RUN.delete(runId);
49
+ }
50
+ }
51
+ function forgetRun(runId) {
52
+ PROMOTED_RUNS.delete(runId);
53
+ const runToolCallIds = REMOTE_TOOL_CALL_IDS_BY_RUN.get(runId);
54
+ if (!runToolCallIds)
55
+ return;
56
+ for (const toolCallId of runToolCallIds) {
57
+ REMOTE_TOOL_CALL_IDS.delete(toolCallId);
58
+ }
59
+ REMOTE_TOOL_CALL_IDS_BY_RUN.delete(runId);
60
+ }
61
+ function getAgentEventState() {
62
+ const state = globalThis[AGENT_EVENT_STATE_KEY];
63
+ if (!state || typeof state !== 'object')
64
+ return null;
65
+ return state;
66
+ }
67
+ function promoteRunVerboseLevel(runId, toolCallId, toolName) {
68
+ const state = getAgentEventState();
69
+ const runContextById = state?.runContextById;
70
+ if (!(runContextById instanceof Map)) {
71
+ if (!missingStateWarned) {
72
+ missingStateWarned = true;
73
+ logger.warn('[verbose-preflight] OpenClaw agent event state is unavailable; OpenAgent SSE payload may be stripped');
74
+ }
75
+ return;
76
+ }
77
+ let ctx = runContextById.get(runId);
78
+ if (!ctx) {
79
+ ctx = {};
80
+ runContextById.set(runId, ctx);
81
+ }
82
+ if (ctx.verboseLevel !== 'full') {
83
+ ctx.verboseLevel = 'full';
84
+ }
85
+ if (!PROMOTED_RUNS.has(runId)) {
86
+ PROMOTED_RUNS.add(runId);
87
+ logger.info(`[verbose-preflight] promoted run verboseLevel=full (runId=${runId}, toolCallId=${toolCallId || 'unknown'}, tool=${toolName || 'unknown'})`);
88
+ }
89
+ }
90
+ function handleAgentEvent(evt) {
91
+ const runId = readString(evt.runId);
92
+ if (!runId)
93
+ return;
94
+ const data = asRecord(evt.data);
95
+ if (evt.stream === 'lifecycle') {
96
+ const phase = readString(data.phase);
97
+ if (phase === 'end' || phase === 'error') {
98
+ forgetRun(runId);
99
+ }
100
+ return;
101
+ }
102
+ if (evt.stream !== 'tool')
103
+ return;
104
+ const toolName = readToolName(data);
105
+ const toolCallId = readToolCallId(data);
106
+ if (toolCallId && isOpenagentRemoteToolName(toolName)) {
107
+ rememberRemoteToolCall(runId, toolCallId);
108
+ }
109
+ const shouldKeepPayload = isOpenagentRemoteToolName(toolName) ||
110
+ (toolCallId !== '' && REMOTE_TOOL_CALL_IDS.has(toolCallId));
111
+ if (!shouldKeepPayload)
112
+ return;
113
+ promoteRunVerboseLevel(runId, toolCallId, toolName);
114
+ const phase = readString(data.phase);
115
+ if (toolCallId && (phase === 'result' || phase === 'error' || phase === 'end')) {
116
+ forgetRemoteToolCall(runId, toolCallId);
117
+ }
118
+ }
119
+ function prioritizePreflightListener(listener) {
120
+ const listeners = getAgentEventState()?.listeners;
121
+ if (!(listeners instanceof Set) || !listeners.has(listener))
122
+ return;
123
+ const rest = Array.from(listeners).filter(item => item !== listener);
124
+ listeners.clear();
125
+ listeners.add(listener);
126
+ for (const item of rest)
127
+ listeners.add(item);
128
+ logger.debug('[verbose-preflight] listener moved before gateway subscribers');
129
+ }
130
+ export function installOpenagentVerbosePreflight(api) {
131
+ if (installed)
132
+ return;
133
+ const onAgentEvent = api.runtime?.events?.onAgentEvent;
134
+ if (typeof onAgentEvent !== 'function') {
135
+ if (!missingRuntimeWarned) {
136
+ missingRuntimeWarned = true;
137
+ logger.warn('[verbose-preflight] api.runtime.events.onAgentEvent is unavailable; OpenAgent SSE payload preflight disabled');
138
+ }
139
+ return;
140
+ }
141
+ onAgentEvent(handleAgentEvent);
142
+ prioritizePreflightListener(handleAgentEvent);
143
+ installed = true;
144
+ logger.info('[verbose-preflight] installed');
145
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Auth — Configuration resolution
3
+ *
4
+ * Resolves OpenAgent account configuration from the OpenClaw config object.
5
+ * Supports two configuration layouts:
6
+ * 1. Nested: cfg.channels.openagent.accounts.{accountId}.*
7
+ * 2. Fallback (F1 compat): cfg.channels.openagent.agentId / .apiKey (top-level shorthand)
8
+ *
9
+ * See §0.1 of refactoring-plan.md.
10
+ */
11
+ /**
12
+ * OASN 默认 API base。
13
+ *
14
+ * 未配置时故意使用不可达占位,避免把本地或生产请求误打到 REL 私有地址。
15
+ * 解析优先级:账号级 api_base > channel 级 api_base > 环境变量 > 该占位。
16
+ */
17
+ export declare const DEFAULT_OASN_API_BASE = "https://oasn.invalid/CONFIGURE_ME";
18
+ export declare function isUnconfiguredOasnApiBase(value: string | undefined): boolean;
19
+ /**
20
+ * Transport 类型 —— v3.9+ 双轨抽象。
21
+ *
22
+ * 默认 'tim' 保留向后兼容(旧 openagent 配置无此字段,按 TIM 路径走)。
23
+ * 新 OASN 部署在配置中显式设置 transport='oasn'。
24
+ */
25
+ export type TransportType = 'oasn' | 'tim';
26
+ export interface OpenagentAccountConfig {
27
+ /** Resolved account ID */
28
+ accountId: string;
29
+ /** Display name */
30
+ name: string;
31
+ /** Whether the channel is enabled */
32
+ enabled: boolean;
33
+ /** Whether required credentials (agentId + apiKey) are present */
34
+ configured: boolean;
35
+ /** Agent identity on OpenAgent network */
36
+ agentId: string;
37
+ /** API key for auth verification */
38
+ apiKey: string;
39
+ /** API base URL */
40
+ apiBase: string;
41
+ /** Optional Access-layer API base URL for invocations/files when REL splits services */
42
+ accessApiBase: string;
43
+ /** Claim URL returned by OASN onboarding */
44
+ claimUrl: string;
45
+ /** Current onboarding claim status */
46
+ status: 'unregistered' | 'unclaimed' | 'claim_pending' | 'claimed' | 'credential_expired' | 'unknown';
47
+ /** Registration timestamp stored by the plugin */
48
+ registeredAt: string;
49
+ /**
50
+ * Transport 选型(v3.9+)。
51
+ *
52
+ * - 'oasn': 直接走 OASN HTTP API(Bearer api_key)
53
+ * - 'tim': 走腾讯云 IM(默认,向后兼容)
54
+ *
55
+ * 未在 config 中显式声明时默认 'tim',确保旧版插件配置无需迁移即可工作。
56
+ */
57
+ transport: TransportType;
58
+ /** Raw merged config object */
59
+ config: Record<string, unknown>;
60
+ }
61
+ /**
62
+ * Extract the OpenAgent-specific config block from the full OpenClaw config.
63
+ * Handles multiple config key locations that have drifted across versions.
64
+ *
65
+ * v3.9+ 兼容:同时支持 `channels.oasn` / `entries.openclaw-oasn` / `entries.openclaw-plugin-oasn`,
66
+ * 与原有 openagent 键名并列查找。优先 oasn → openagent,便于过渡期同时挂双配置。
67
+ */
68
+ export declare function getOpenagentConfig(cfg: Record<string, unknown>): Record<string, unknown>;
69
+ /**
70
+ * Resolve a specific account's configuration from OpenClaw config.
71
+ *
72
+ * @param cfg - Full OpenClaw configuration object
73
+ * @param accountId - Target account ID, or 'default' to pick the first available
74
+ */
75
+ export declare function resolveOpenagentAccount(cfg: Record<string, unknown>, accountId?: string): OpenagentAccountConfig;
76
+ /**
77
+ * List available account IDs from the config.
78
+ */
79
+ export declare function listAccountIds(cfg: Record<string, unknown>): string[];