adp-openclaw 0.0.72 → 0.0.74

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/index.ts ADDED
@@ -0,0 +1,264 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
3
+
4
+ // ============================================================================
5
+ // PERFORMANCE NOTE: This file is the plugin entry point loaded by openclaw at
6
+ // startup. Everything imported at the top level is evaluated **synchronously**
7
+ // during plugin discovery, blocking the entire startup sequence.
8
+ //
9
+ // Heavy modules (session-history, adp-upload-tool, tool-result-message-blocks)
10
+ // are therefore NOT imported at the top level. Instead they are:
11
+ // 1. Lazily re-exported via getter helpers (for external consumers).
12
+ // 2. Dynamically imported inside register() / tool-execute callbacks.
13
+ //
14
+ // This keeps register() fast (< 50 ms) and avoids blocking the scan phase.
15
+ // ============================================================================
16
+
17
+ // ---- Lightweight imports (tiny modules, no heavy deps) ----
18
+ import { adpOpenclawPlugin, type AdpOpenclawChannelConfig } from "./src/channel.js";
19
+ import { setAdpOpenclawRuntime } from "./src/runtime.js";
20
+ // Type-only imports are erased at runtime — zero overhead
21
+ import type { AdpUploadToolResult, UploadedFileInfo } from "./src/adp-upload-tool.js";
22
+
23
+ // ---- Tool name / schema constants (inlined to avoid loading full upload module) ----
24
+ const ADP_UPLOAD_TOOL_NAME = "adp_upload_file";
25
+ const ADP_UPLOAD_TOOL_SCHEMA = {
26
+ type: "object" as const,
27
+ properties: {
28
+ paths: {
29
+ type: "array" as const,
30
+ items: { type: "string" as const },
31
+ description: "Array of 1-10 local file paths to upload",
32
+ minItems: 1,
33
+ maxItems: 10,
34
+ },
35
+ fileType: {
36
+ type: "string" as const,
37
+ description: "Optional MIME type hint for uploaded files",
38
+ },
39
+ },
40
+ required: ["paths"] as const,
41
+ };
42
+
43
+ // Track whether register() has been called at least once (for log dedup)
44
+ let _registerCallCount = 0;
45
+
46
+ // ============================================================================
47
+ // Lazy re-exports for external consumers
48
+ // These are loaded on first access, not at plugin startup.
49
+ // ============================================================================
50
+
51
+ // Session history (heavy: node:child_process, node:fs, 1100+ lines)
52
+ export async function getSessionHistoryModule() {
53
+ return import("./src/session-history.js");
54
+ }
55
+
56
+ // Re-export types (type-only imports are free at runtime)
57
+ export type {
58
+ OpenClawSession,
59
+ OpenClawMessage,
60
+ ChatHistoryResponse,
61
+ SessionsListResponse,
62
+ SessionFileConfig,
63
+ } from "./src/session-history.js";
64
+
65
+ // ADP upload tool
66
+ export async function getAdpUploadToolModule() {
67
+ return import("./src/adp-upload-tool.js");
68
+ }
69
+
70
+ export type {
71
+ UploadResult,
72
+ AdpUploadToolParams,
73
+ AdpUploadToolResult,
74
+ UploadedFileInfo,
75
+ AdpUploadOptions,
76
+ DescribeRemoteBotStorageCredentialReq,
77
+ DescribeRemoteBotStorageCredentialRsp,
78
+ Credentials,
79
+ } from "./src/adp-upload-tool.js";
80
+
81
+ // Tool result message blocks
82
+ export async function getToolResultMessageBlocksModule() {
83
+ return import("./src/tool-result-message-blocks.js");
84
+ }
85
+
86
+ export type {
87
+ ResourceLinkBlock,
88
+ TextBlock,
89
+ ContentBlock,
90
+ MessageBlock,
91
+ } from "./src/tool-result-message-blocks.js";
92
+
93
+ // Helper to format tool result as JSON string
94
+ const formatToolResultJson = (result: unknown): string =>
95
+ JSON.stringify(result, null, 2) ?? "upload failed";
96
+
97
+ const plugin = {
98
+ id: "adp-openclaw",
99
+ name: "ADP OpenClaw",
100
+ description: "ADP channel plugin backed by a Go WebSocket server",
101
+ configSchema: emptyPluginConfigSchema(),
102
+ register(api: OpenClawPluginApi) {
103
+ _registerCallCount++;
104
+
105
+ // Only log on first registration to avoid flooding logs
106
+ if (_registerCallCount === 1) {
107
+ api.logger.info?.("[adp-openclaw] Plugin register() called");
108
+ }
109
+
110
+ setAdpOpenclawRuntime(api.runtime);
111
+
112
+ api.registerTool((ctx: { messageChannel?: string; [key: string]: unknown }) => {
113
+ // Only expose this tool when the message comes from the adp-openclaw channel
114
+ const channel = ctx.messageChannel ?? "";
115
+ if (channel && channel !== "adp-openclaw") {
116
+ return null;
117
+ }
118
+
119
+ return {
120
+ name: ADP_UPLOAD_TOOL_NAME,
121
+ description:
122
+ "If you can see this tool description, you are connected to the ADP server. " +
123
+ "Use this tool to send 1-10 local files of any type (including images and documents) to the user. " +
124
+ "A successful call means the files were uploaded and download URLs are returned. " +
125
+ "IMPORTANT: You MUST include the download URLs in your response to the user, " +
126
+ "and remind the user that the download links are valid for 24 hours. " +
127
+ "Parameters must be local filesystem paths only.",
128
+ parameters: ADP_UPLOAD_TOOL_SCHEMA,
129
+ async execute(toolCallId: string, params: unknown) {
130
+ // Lazy-load the heavy upload module only when the tool is actually invoked
131
+ const {
132
+ parseAdpUploadToolParams,
133
+ uploadFilesToAdpEndpoint,
134
+ uploadResultEmitter,
135
+ UPLOAD_RESULT_EVENT,
136
+ } = await import("./src/adp-upload-tool.js");
137
+
138
+ // Get bot token from channel config
139
+ const getClientToken = (): string | undefined => {
140
+ try {
141
+ const cfg = api.runtime?.config?.loadConfig?.();
142
+ const channelCfg = cfg?.channels?.["adp-openclaw"] as AdpOpenclawChannelConfig | undefined;
143
+ return channelCfg?.clientToken?.trim() || process.env.ADP_OPENCLAW_CLIENT_TOKEN;
144
+ } catch {
145
+ return process.env.ADP_OPENCLAW_CLIENT_TOKEN;
146
+ }
147
+ };
148
+
149
+ // Parse and validate parameters
150
+ const parsed = parseAdpUploadToolParams(params);
151
+ if (!parsed.ok) {
152
+ const errorResult = {
153
+ ok: false,
154
+ error: formatToolResultJson(parsed.error),
155
+ };
156
+ api.logger.debug?.(`[${ADP_UPLOAD_TOOL_NAME}] validation failed toolCallId=${toolCallId} error=${errorResult.error}`);
157
+ return {
158
+ output: errorResult,
159
+ result: errorResult,
160
+ details: errorResult,
161
+ content: [{ type: "text", text: formatToolResultJson(parsed.error) }],
162
+ isError: true,
163
+ };
164
+ }
165
+
166
+ // Get bot token
167
+ const botToken = getClientToken();
168
+ if (!botToken) {
169
+ const errorResult = {
170
+ ok: false,
171
+ error: "missing bot token for file upload - please configure clientToken in adp-openclaw channel settings",
172
+ };
173
+ return {
174
+ output: errorResult,
175
+ result: errorResult,
176
+ details: errorResult,
177
+ content: [{ type: "text", text: errorResult.error }],
178
+ isError: true,
179
+ };
180
+ }
181
+
182
+ // Execute upload
183
+ const uploadResult = await uploadFilesToAdpEndpoint(parsed.value.paths, {
184
+ botToken,
185
+ fileType: parsed.value.fileType,
186
+ });
187
+
188
+ if (!uploadResult.ok) {
189
+ const errorResult = {
190
+ ok: false,
191
+ error: formatToolResultJson(uploadResult.error),
192
+ };
193
+ api.logger.debug?.(`[${ADP_UPLOAD_TOOL_NAME}] upload failed toolCallId=${toolCallId} error=${errorResult.error}`);
194
+ return {
195
+ output: errorResult,
196
+ result: errorResult,
197
+ details: errorResult,
198
+ content: [{ type: "text", text: formatToolResultJson(uploadResult.error) }],
199
+ isError: true,
200
+ };
201
+ }
202
+
203
+ // Success - format result with download URLs
204
+ const successResult: AdpUploadToolResult = {
205
+ ok: true,
206
+ files: uploadResult.files,
207
+ };
208
+
209
+ api.logger.debug?.(`[${ADP_UPLOAD_TOOL_NAME}] upload success toolCallId=${toolCallId} count=${successResult.files?.length ?? 0}`);
210
+
211
+ // 发射上传结果事件,让 monitor.ts 能够直接获取完整的下载链接
212
+ uploadResultEmitter.emit(UPLOAD_RESULT_EVENT, {
213
+ toolCallId,
214
+ result: successResult,
215
+ });
216
+
217
+ // Build content with resource links and download URLs
218
+ const content: Array<{ type: string; uri?: string; name?: string; mimeType?: string; text?: string; downloadUrl?: string }> = [];
219
+
220
+ // Add resource links for each file
221
+ for (const file of (successResult.files || [])) {
222
+ content.push({
223
+ type: "resource_link",
224
+ uri: file.downloadUrl || file.uri,
225
+ name: file.name,
226
+ mimeType: file.mimeType,
227
+ downloadUrl: file.downloadUrl,
228
+ });
229
+ }
230
+
231
+ // Add a text summary with download URLs for AI to include in response
232
+ const urlSummary = (successResult.files || [])
233
+ .map((f: UploadedFileInfo) => {
234
+ const url = f.downloadUrl || f.uri;
235
+ return `- **${f.name}**: \`${url}\``;
236
+ })
237
+ .join("\n");
238
+
239
+ content.push({
240
+ type: "text",
241
+ text: `Files uploaded successfully:\n${urlSummary}\n\n⚠️ IMPORTANT: The URLs above contain authentication signatures. You MUST copy the ENTIRE URL exactly as shown (including all query parameters after the "?"). Do NOT truncate or modify the URLs in any way. The links are valid for 24 hours.`,
242
+ });
243
+
244
+ return {
245
+ output: successResult,
246
+ result: successResult,
247
+ details: successResult,
248
+ content,
249
+ isError: false,
250
+ };
251
+ },
252
+ }; // end of tool object
253
+ }); // end of factory function passed to registerTool
254
+
255
+ // Register the channel plugin (channel.ts + onboarding.ts are lightweight config-only modules)
256
+ api.registerChannel({ plugin: adpOpenclawPlugin });
257
+
258
+ if (_registerCallCount === 1) {
259
+ api.logger.info?.("[adp-openclaw] Plugin registration complete");
260
+ }
261
+ },
262
+ };
263
+
264
+ export default plugin;
package/package.json CHANGED
@@ -1,31 +1,25 @@
1
1
  {
2
2
  "name": "adp-openclaw",
3
- "version": "0.0.72",
3
+ "version": "0.0.74",
4
4
  "description": "ADP-OpenClaw demo channel plugin (Go WebSocket backend)",
5
5
  "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "files": [
9
- "dist/**/*.js",
10
- "dist/**/*.d.ts",
11
- "openclaw.plugin.json"
12
- ],
13
- "scripts": {
14
- "build": "tsc",
15
- "prepublish": "tsc"
16
- },
6
+ "main": "index.ts",
17
7
  "dependencies": {
18
8
  "ws": "^8.16.0",
19
9
  "zod": "^3.22.4"
20
10
  },
11
+ "peerDependencies": {
12
+ "openclaw": ">=2026.3.22"
13
+ },
21
14
  "devDependencies": {
22
15
  "@types/node": "^20.11.0",
23
16
  "@types/ws": "^8.5.10",
17
+ "openclaw": ">=2026.3.22",
24
18
  "typescript": "^5.9.3"
25
19
  },
26
20
  "openclaw": {
27
21
  "extensions": [
28
- "./dist/index.js"
22
+ "./index.ts"
29
23
  ],
30
24
  "channel": {
31
25
  "id": "adp-openclaw",
@@ -0,0 +1,122 @@
1
+ /**
2
+ * ADP Upload Tool 测试文件
3
+ *
4
+ * 使用方式:
5
+ *
6
+ * 1. 使用环境变量(推荐,与插件配置一致):
7
+ * ADP_OPENCLAW_CLIENT_TOKEN=your-token npx tsx src/adp-upload-tool.test.ts <filePath>
8
+ *
9
+ * 2. 使用命令行参数:
10
+ * npx tsx src/adp-upload-tool.test.ts --token <botToken> <filePath>
11
+ */
12
+
13
+ import { AdpUploader, getStorageCredential } from "./adp-upload-tool.js";
14
+
15
+ async function main() {
16
+ const args = process.argv.slice(2);
17
+
18
+ let botToken: string | undefined;
19
+ let filePaths: string[] = [];
20
+
21
+ // 解析参数
22
+ for (let i = 0; i < args.length; i++) {
23
+ if (args[i] === "--token" && i + 1 < args.length) {
24
+ botToken = args[i + 1];
25
+ i++; // 跳过下一个参数
26
+ } else if (!args[i].startsWith("--")) {
27
+ filePaths.push(args[i]);
28
+ }
29
+ }
30
+
31
+ // 如果没有通过参数传入 token,尝试从环境变量读取
32
+ if (!botToken) {
33
+ botToken = process.env.ADP_OPENCLAW_CLIENT_TOKEN;
34
+ }
35
+
36
+ if (filePaths.length === 0) {
37
+ console.log("ADP Upload Tool 测试");
38
+ console.log("");
39
+ console.log("使用方式:");
40
+ console.log(" 1. 使用环境变量(推荐,与插件配置一致):");
41
+ console.log(" ADP_OPENCLAW_CLIENT_TOKEN=your-token npx tsx src/adp-upload-tool.test.ts <filePath>");
42
+ console.log("");
43
+ console.log(" 2. 使用命令行参数:");
44
+ console.log(" npx tsx src/adp-upload-tool.test.ts --token <botToken> <filePath>");
45
+ console.log("");
46
+ console.log("示例:");
47
+ console.log(" npx tsx src/adp-upload-tool.test.ts --token my-bot-token ./test.txt");
48
+ console.log(" ADP_OPENCLAW_CLIENT_TOKEN=my-token npx tsx src/adp-upload-tool.test.ts ./file1.txt ./file2.pdf");
49
+ process.exit(1);
50
+ }
51
+
52
+ if (!botToken) {
53
+ console.error("错误: 未提供 botToken");
54
+ console.error("请通过 --token 参数或 ADP_OPENCLAW_CLIENT_TOKEN 环境变量设置");
55
+ process.exit(1);
56
+ }
57
+
58
+ console.log("=".repeat(60));
59
+ console.log("ADP Upload Tool 测试");
60
+ console.log("=".repeat(60));
61
+ console.log(`Bot Token: ${botToken.substring(0, 10)}...`);
62
+ console.log(`文件列表: ${filePaths.join(", ")}`);
63
+ console.log("");
64
+
65
+ // 测试获取临时密钥
66
+ console.log("1. 测试获取临时密钥...");
67
+ try {
68
+ const credential = await getStorageCredential(botToken);
69
+ console.log(" ✓ 获取密钥成功");
70
+ console.log(` - Bucket: ${credential.bucket}`);
71
+ console.log(` - Region: ${credential.region}`);
72
+ console.log(` - File Path: ${credential.file_path}`);
73
+ console.log(` - Secret ID: ${credential.credentials.tmp_secret_id.substring(0, 10)}...`);
74
+ console.log(` - 有效期至: ${new Date(credential.expired_time * 1000).toLocaleString()}`);
75
+ console.log("");
76
+ } catch (error) {
77
+ console.log(" ✗ 获取密钥失败");
78
+ console.log(` - 错误: ${error instanceof Error ? error.message : error}`);
79
+ process.exit(1);
80
+ }
81
+
82
+ // 使用 AdpUploader 类测试上传
83
+ console.log("2. 使用 AdpUploader 测试上传...");
84
+ const uploader = new AdpUploader({ clientToken: botToken });
85
+ console.log(` - 配置状态: ${uploader.isConfigured() ? "已配置" : "未配置"}`);
86
+ console.log(` - Token 预览: ${uploader.getTokenPreview()}`);
87
+ console.log("");
88
+
89
+ if (filePaths.length === 1) {
90
+ // 单文件上传
91
+ console.log("3. 测试单文件上传...");
92
+ const result = await uploader.upload(filePaths[0]);
93
+ if (result.ok) {
94
+ console.log(" ✓ 上传成功");
95
+ console.log(` - 下载链接: ${result.fileUrl}`);
96
+ } else {
97
+ console.log(" ✗ 上传失败");
98
+ console.log(` - 错误: ${result.error}`);
99
+ }
100
+ } else {
101
+ // 批量上传
102
+ console.log("3. 测试批量上传...");
103
+ const results = await uploader.uploadMultiple(filePaths);
104
+ results.forEach((result, index) => {
105
+ const filePath = filePaths[index];
106
+ if (result.ok) {
107
+ console.log(` ✓ [${filePath}] 上传成功`);
108
+ console.log(` 下载链接: ${result.fileUrl}`);
109
+ } else {
110
+ console.log(` ✗ [${filePath}] 上传失败`);
111
+ console.log(` 错误: ${result.error}`);
112
+ }
113
+ });
114
+ }
115
+
116
+ console.log("");
117
+ console.log("=".repeat(60));
118
+ console.log("测试完成");
119
+ console.log("=".repeat(60));
120
+ }
121
+
122
+ main().catch(console.error);