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
package/src/app/types.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
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
|
-
|
|
9
|
-
export interface ToolParams {
|
|
10
|
-
[key: string]: unknown;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** SDK-compatible tool result (matches AgentToolResult structure) */
|
|
14
|
-
export interface ToolResult {
|
|
15
|
-
content: Array<{ type: 'text'; text: string }>;
|
|
16
|
-
/** Optional details for rich media. Matches OpenClaw AgentToolResult<TDetails>. */
|
|
17
|
-
details?: {
|
|
18
|
-
media?: { mediaUrls: string[] };
|
|
19
|
-
[key: string]: unknown;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export type ToolUpdateCallback = (partialResult: ToolResult) => void;
|
|
24
|
-
|
|
25
|
-
export interface ToolDescriptor {
|
|
26
|
-
name: string;
|
|
27
|
-
label: string;
|
|
28
|
-
description: string;
|
|
29
|
-
parameters: {
|
|
30
|
-
type: 'object';
|
|
31
|
-
properties: Record<string, {
|
|
32
|
-
type: string;
|
|
33
|
-
description: string;
|
|
34
|
-
enum?: string[];
|
|
35
|
-
default?: unknown;
|
|
36
|
-
items?: { type: string };
|
|
37
|
-
}>;
|
|
38
|
-
required: string[];
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* Execute the tool.
|
|
42
|
-
*
|
|
43
|
-
* OpenClaw runtime usually calls:
|
|
44
|
-
* tool.execute(toolCallId, params, signal, onUpdate, ctx)
|
|
45
|
-
*
|
|
46
|
-
* Older adapters used:
|
|
47
|
-
* tool.execute(toolCallId, params, onUpdate, ctx, signal)
|
|
48
|
-
*
|
|
49
|
-
* The wider signature lets streaming tools normalize both shapes while
|
|
50
|
-
* existing 2-param tools continue to work unchanged.
|
|
51
|
-
*
|
|
52
|
-
* @see docs/reference/openclaw/v2026.3.23/dist/pi-embedded-CswW9luA.js L169823
|
|
53
|
-
*/
|
|
54
|
-
execute: (
|
|
55
|
-
_id: unknown,
|
|
56
|
-
params: ToolParams,
|
|
57
|
-
signalOrUpdate?: AbortSignal | ToolUpdateCallback,
|
|
58
|
-
onUpdateOrContext?: ToolUpdateCallback | unknown,
|
|
59
|
-
contextOrSignal?: unknown,
|
|
60
|
-
) => Promise<ToolResult>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export type ToolFactory = () => ToolDescriptor;
|
|
64
|
-
|
|
65
|
-
/** Helper: create a successful text result in SDK format */
|
|
66
|
-
export function toolResult(text: string): ToolResult {
|
|
67
|
-
return { content: [{ type: 'text', text }] };
|
|
68
|
-
}
|
|
@@ -1,411 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk/plugin-entry';
|
|
2
|
-
import { logger } from '../util/logger.js';
|
|
3
|
-
|
|
4
|
-
type AgentEvent = {
|
|
5
|
-
runId?: unknown;
|
|
6
|
-
stream?: unknown;
|
|
7
|
-
data?: unknown;
|
|
8
|
-
[key: string]: unknown;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type AgentRunContext = {
|
|
12
|
-
verboseLevel?: string;
|
|
13
|
-
[key: string]: unknown;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
type AgentEventState = {
|
|
17
|
-
listeners?: Set<(evt: AgentEvent) => void>;
|
|
18
|
-
runContextById?: Map<string, AgentRunContext>;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const AGENT_EVENT_STATE_KEY = Symbol.for('openclaw.agentEvents.state');
|
|
22
|
-
const REMOTE_TOOL_CALL_IDS = new Set<string>();
|
|
23
|
-
const REMOTE_TOOL_CALL_IDS_BY_RUN = new Map<string, Set<string>>();
|
|
24
|
-
const PROMOTED_RUNS = new Set<string>();
|
|
25
|
-
|
|
26
|
-
let installed = false;
|
|
27
|
-
let missingStateWarned = false;
|
|
28
|
-
let missingRuntimeWarned = false;
|
|
29
|
-
|
|
30
|
-
function asRecord(value: unknown): Record<string, unknown> {
|
|
31
|
-
return value && typeof value === 'object' ? value as Record<string, unknown> : {};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function readString(value: unknown): string {
|
|
35
|
-
return typeof value === 'string' ? value : '';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function readToolName(data: Record<string, unknown>): string {
|
|
39
|
-
const call = asRecord(data.call);
|
|
40
|
-
return (
|
|
41
|
-
readString(data.name) ||
|
|
42
|
-
readString(data.toolName) ||
|
|
43
|
-
readString(data.tool_name) ||
|
|
44
|
-
readString(call.name)
|
|
45
|
-
).toLowerCase();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function readToolCallId(data: Record<string, unknown>): string {
|
|
49
|
-
const call = asRecord(data.call);
|
|
50
|
-
return (
|
|
51
|
-
readString(data.toolCallId) ||
|
|
52
|
-
readString(data.tool_call_id) ||
|
|
53
|
-
readString(data.id) ||
|
|
54
|
-
readString(call.id)
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function isOpenagentRemoteToolName(name: string): boolean {
|
|
59
|
-
return name.includes('call_remote_agent') || name.includes('openagent_delegate');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function rememberRemoteToolCall(runId: string, toolCallId: string): void {
|
|
63
|
-
REMOTE_TOOL_CALL_IDS.add(toolCallId);
|
|
64
|
-
let runToolCallIds = REMOTE_TOOL_CALL_IDS_BY_RUN.get(runId);
|
|
65
|
-
if (!runToolCallIds) {
|
|
66
|
-
runToolCallIds = new Set<string>();
|
|
67
|
-
REMOTE_TOOL_CALL_IDS_BY_RUN.set(runId, runToolCallIds);
|
|
68
|
-
}
|
|
69
|
-
runToolCallIds.add(toolCallId);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function forgetRemoteToolCall(runId: string, toolCallId: string): void {
|
|
73
|
-
REMOTE_TOOL_CALL_IDS.delete(toolCallId);
|
|
74
|
-
const runToolCallIds = REMOTE_TOOL_CALL_IDS_BY_RUN.get(runId);
|
|
75
|
-
if (!runToolCallIds) return;
|
|
76
|
-
runToolCallIds.delete(toolCallId);
|
|
77
|
-
if (runToolCallIds.size === 0) {
|
|
78
|
-
REMOTE_TOOL_CALL_IDS_BY_RUN.delete(runId);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function forgetRun(runId: string): void {
|
|
83
|
-
PROMOTED_RUNS.delete(runId);
|
|
84
|
-
const runToolCallIds = REMOTE_TOOL_CALL_IDS_BY_RUN.get(runId);
|
|
85
|
-
if (!runToolCallIds) return;
|
|
86
|
-
for (const toolCallId of runToolCallIds) {
|
|
87
|
-
REMOTE_TOOL_CALL_IDS.delete(toolCallId);
|
|
88
|
-
}
|
|
89
|
-
REMOTE_TOOL_CALL_IDS_BY_RUN.delete(runId);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function getAgentEventState(): AgentEventState | null {
|
|
93
|
-
const state = (globalThis as Record<PropertyKey, unknown>)[AGENT_EVENT_STATE_KEY];
|
|
94
|
-
if (!state || typeof state !== 'object') return null;
|
|
95
|
-
return state as AgentEventState;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function promoteRunVerboseLevel(runId: string, toolCallId: string, toolName: string): void {
|
|
99
|
-
const state = getAgentEventState();
|
|
100
|
-
const runContextById = state?.runContextById;
|
|
101
|
-
|
|
102
|
-
if (!(runContextById instanceof Map)) {
|
|
103
|
-
if (!missingStateWarned) {
|
|
104
|
-
missingStateWarned = true;
|
|
105
|
-
logger.warn('[verbose-preflight] OpenClaw agent event state is unavailable; OpenAgent SSE payload may be stripped');
|
|
106
|
-
}
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
let ctx = runContextById.get(runId);
|
|
111
|
-
if (!ctx) {
|
|
112
|
-
ctx = {};
|
|
113
|
-
runContextById.set(runId, ctx);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (ctx.verboseLevel !== 'full') {
|
|
117
|
-
ctx.verboseLevel = 'full';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!PROMOTED_RUNS.has(runId)) {
|
|
121
|
-
PROMOTED_RUNS.add(runId);
|
|
122
|
-
logger.info(`[verbose-preflight] promoted run verboseLevel=full (runId=${runId}, toolCallId=${toolCallId || 'unknown'}, tool=${toolName || 'unknown'})`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function handleAgentEvent(evt: AgentEvent): void {
|
|
127
|
-
const runId = readString(evt.runId);
|
|
128
|
-
if (!runId) return;
|
|
129
|
-
|
|
130
|
-
const data = asRecord(evt.data);
|
|
131
|
-
|
|
132
|
-
if (evt.stream === 'lifecycle') {
|
|
133
|
-
const phase = readString(data.phase);
|
|
134
|
-
if (phase === 'end' || phase === 'error') {
|
|
135
|
-
forgetRun(runId);
|
|
136
|
-
}
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (evt.stream !== 'tool') return;
|
|
141
|
-
const toolName = readToolName(data);
|
|
142
|
-
const toolCallId = readToolCallId(data);
|
|
143
|
-
|
|
144
|
-
if (toolCallId && isOpenagentRemoteToolName(toolName)) {
|
|
145
|
-
rememberRemoteToolCall(runId, toolCallId);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const shouldKeepPayload =
|
|
149
|
-
isOpenagentRemoteToolName(toolName) ||
|
|
150
|
-
(toolCallId !== '' && REMOTE_TOOL_CALL_IDS.has(toolCallId));
|
|
151
|
-
|
|
152
|
-
if (!shouldKeepPayload) return;
|
|
153
|
-
|
|
154
|
-
promoteRunVerboseLevel(runId, toolCallId, toolName);
|
|
155
|
-
|
|
156
|
-
const phase = readString(data.phase);
|
|
157
|
-
if (toolCallId && (phase === 'result' || phase === 'error' || phase === 'end')) {
|
|
158
|
-
forgetRemoteToolCall(runId, toolCallId);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function prioritizePreflightListener(listener: (evt: AgentEvent) => void): void {
|
|
163
|
-
const listeners = getAgentEventState()?.listeners;
|
|
164
|
-
if (!(listeners instanceof Set) || !listeners.has(listener)) return;
|
|
165
|
-
|
|
166
|
-
const rest = Array.from(listeners).filter(item => item !== listener);
|
|
167
|
-
listeners.clear();
|
|
168
|
-
listeners.add(listener);
|
|
169
|
-
for (const item of rest) listeners.add(item);
|
|
170
|
-
|
|
171
|
-
logger.debug('[verbose-preflight] listener moved before gateway subscribers');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function installOpenagentVerbosePreflight(api: OpenClawPluginApi): void {
|
|
175
|
-
if (installed) return;
|
|
176
|
-
|
|
177
|
-
const onAgentEvent = api.runtime?.events?.onAgentEvent;
|
|
178
|
-
if (typeof onAgentEvent !== 'function') {
|
|
179
|
-
if (!missingRuntimeWarned) {
|
|
180
|
-
missingRuntimeWarned = true;
|
|
181
|
-
logger.warn('[verbose-preflight] api.runtime.events.onAgentEvent is unavailable; OpenAgent SSE payload preflight disabled');
|
|
182
|
-
}
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
onAgentEvent(handleAgentEvent);
|
|
187
|
-
prioritizePreflightListener(handleAgentEvent);
|
|
188
|
-
installed = true;
|
|
189
|
-
logger.info('[verbose-preflight] installed');
|
|
190
|
-
}
|