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.
- package/dist/index.d.ts +25 -0
- package/dist/index.js +105 -0
- package/dist/src/app/channel-tools.d.ts +28 -0
- package/dist/src/app/channel-tools.js +251 -0
- package/dist/src/app/discovery-tools.d.ts +35 -0
- package/dist/src/app/discovery-tools.js +243 -0
- package/dist/src/app/download-file-tool.d.ts +10 -0
- package/dist/src/app/download-file-tool.js +104 -0
- package/dist/src/app/hooks.d.ts +14 -0
- package/dist/src/app/hooks.js +118 -0
- package/dist/src/app/index.d.ts +13 -0
- package/dist/src/app/index.js +44 -0
- package/dist/src/app/messaging-tools.d.ts +11 -0
- package/dist/src/app/messaging-tools.js +79 -0
- package/dist/src/app/ops-tools.d.ts +21 -0
- package/dist/src/app/ops-tools.js +158 -0
- package/dist/src/app/remote-agent-tool.d.ts +27 -0
- package/dist/src/app/remote-agent-tool.js +461 -0
- package/dist/src/app/types.d.ts +61 -0
- package/dist/src/app/types.js +11 -0
- package/dist/src/app/upload-file-tool.d.ts +16 -0
- package/dist/src/app/upload-file-tool.js +353 -0
- package/dist/src/app/verbose-preflight.d.ts +2 -0
- package/dist/src/app/verbose-preflight.js +145 -0
- package/dist/src/auth/config.d.ts +79 -0
- package/dist/src/auth/config.js +133 -0
- package/dist/src/auth/credential-manager.d.ts +65 -0
- package/dist/src/auth/credential-manager.js +122 -0
- package/dist/src/auth/index.d.ts +6 -0
- package/dist/src/auth/index.js +6 -0
- package/dist/src/auth/verify.d.ts +42 -0
- package/dist/src/auth/verify.js +60 -0
- package/dist/src/channel.d.ts +269 -0
- package/dist/src/channel.js +488 -0
- package/dist/src/compat.d.ts +37 -0
- package/dist/src/compat.js +70 -0
- package/dist/src/config/config-schema.d.ts +56 -0
- package/dist/src/config/config-schema.js +34 -0
- package/dist/src/messaging/aggregator.d.ts +25 -0
- package/dist/src/messaging/aggregator.js +90 -0
- package/dist/src/messaging/collector.d.ts +27 -0
- package/dist/src/messaging/collector.js +76 -0
- package/dist/src/messaging/executor.d.ts +14 -0
- package/dist/src/messaging/executor.js +59 -0
- package/dist/src/messaging/inbound.d.ts +97 -0
- package/dist/src/messaging/inbound.js +63 -0
- package/dist/src/messaging/index.d.ts +10 -0
- package/dist/src/messaging/index.js +9 -0
- package/dist/src/messaging/mention-protocol.d.ts +42 -0
- package/dist/src/messaging/mention-protocol.js +74 -0
- package/dist/src/messaging/process-c2c-request.d.ts +55 -0
- package/dist/src/messaging/process-c2c-request.js +445 -0
- package/dist/src/messaging/process-message.d.ts +62 -0
- package/dist/src/messaging/process-message.js +282 -0
- package/dist/src/messaging/scheduler.d.ts +17 -0
- package/dist/src/messaging/scheduler.js +47 -0
- package/dist/src/messaging/types.d.ts +34 -0
- package/dist/src/messaging/types.js +4 -0
- package/dist/src/plugin-ui/assets/openagent-override.js +9278 -0
- package/dist/src/plugin-ui/index.d.ts +13 -0
- package/dist/src/plugin-ui/index.js +16 -0
- package/dist/src/plugin-ui/ui-extension-loader/backup.d.ts +23 -0
- package/dist/src/plugin-ui/ui-extension-loader/backup.js +82 -0
- package/dist/src/plugin-ui/ui-extension-loader/index.d.ts +28 -0
- package/dist/src/plugin-ui/ui-extension-loader/index.js +240 -0
- package/dist/src/plugin-ui/ui-extension-loader/locator.d.ts +35 -0
- package/dist/src/plugin-ui/ui-extension-loader/locator.js +129 -0
- package/dist/src/plugin-ui/ui-extension-loader/manifest.d.ts +26 -0
- package/dist/src/plugin-ui/ui-extension-loader/manifest.js +45 -0
- package/dist/src/plugin-ui/ui-extension-loader/registry-regex.d.ts +9 -0
- package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +724 -0
- package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.d.ts +8 -0
- package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.js +58 -0
- package/dist/src/plugin-ui/ui-extension-loader/types.d.ts +55 -0
- package/dist/src/plugin-ui/ui-extension-loader/types.js +23 -0
- package/dist/src/proxy/auth-proxy.d.ts +20 -0
- package/dist/src/proxy/auth-proxy.js +337 -0
- package/dist/src/runtime/account.d.ts +156 -0
- package/dist/src/runtime/account.js +491 -0
- package/dist/src/runtime/index.d.ts +5 -0
- package/dist/src/runtime/index.js +6 -0
- package/dist/src/runtime/plugin-runtime.d.ts +40 -0
- package/dist/src/runtime/plugin-runtime.js +72 -0
- package/dist/src/runtime/registry.d.ts +41 -0
- package/dist/src/runtime/registry.js +60 -0
- package/dist/src/sdk/CLASS_MAP.md +143 -0
- package/dist/src/sdk/index.d.ts +126 -0
- package/dist/src/sdk/index.js +23990 -0
- package/dist/src/sdk/modules/cloud-search-module.js +1117 -0
- package/dist/src/sdk/modules/follow-module.js +1069 -0
- package/dist/src/sdk/modules/group-module.js +7397 -0
- package/dist/src/sdk/modules/relationship-module.js +2269 -0
- package/dist/src/sdk/modules/signaling-module.js +1468 -0
- package/dist/src/sdk/modules/tim-upload-plugin.js +730 -0
- package/dist/src/sdk/node-env/http-request.js +90 -0
- package/dist/src/sdk/node-env/index.js +57 -0
- package/dist/src/sdk/node-env/storage.js +114 -0
- package/dist/src/sdk/package.json +10 -0
- package/dist/src/sdk/sdk/CLASS_MAP.md +143 -0
- package/dist/src/sdk/sdk/index.d.ts +126 -0
- package/dist/src/sdk/sdk/index.js +23990 -0
- package/dist/src/sdk/sdk/modules/cloud-search-module.js +1117 -0
- package/dist/src/sdk/sdk/modules/follow-module.js +1069 -0
- package/dist/src/sdk/sdk/modules/group-module.js +7397 -0
- package/dist/src/sdk/sdk/modules/relationship-module.js +2269 -0
- package/dist/src/sdk/sdk/modules/signaling-module.js +1468 -0
- package/dist/src/sdk/sdk/modules/tim-upload-plugin.js +730 -0
- package/dist/src/sdk/sdk/node-env/http-request.js +90 -0
- package/dist/src/sdk/sdk/node-env/index.js +57 -0
- package/dist/src/sdk/sdk/node-env/storage.js +114 -0
- package/dist/src/sdk/sdk/package.json +10 -0
- package/dist/src/sdk/sdk/tsconfig.json +16 -0
- package/dist/src/sdk/tsconfig.json +16 -0
- package/dist/src/state/pending-invocation-store.d.ts +26 -0
- package/dist/src/state/pending-invocation-store.js +39 -0
- package/dist/src/state/store.d.ts +165 -0
- package/dist/src/state/store.js +535 -0
- package/dist/src/tim/c2c.d.ts +83 -0
- package/dist/src/tim/c2c.js +318 -0
- package/dist/src/tim/channels.d.ts +95 -0
- package/dist/src/tim/channels.js +279 -0
- package/dist/src/tim/client.d.ts +54 -0
- package/dist/src/tim/client.js +268 -0
- package/dist/src/tim/index.d.ts +6 -0
- package/dist/src/tim/index.js +6 -0
- package/dist/src/tim/messages.d.ts +50 -0
- package/dist/src/tim/messages.js +104 -0
- package/dist/src/tim/sdk-logger-init.d.ts +13 -0
- package/dist/src/tim/sdk-logger-init.js +46 -0
- package/dist/src/tools.d.ts +9 -0
- package/dist/src/tools.js +8 -0
- package/dist/src/transport/factory.d.ts +63 -0
- package/dist/src/transport/factory.js +54 -0
- package/dist/src/transport/oasn/index.d.ts +12 -0
- package/dist/src/transport/oasn/index.js +9 -0
- package/dist/src/transport/oasn/oasn-agent-card.d.ts +38 -0
- package/dist/src/transport/oasn/oasn-agent-card.js +102 -0
- package/dist/src/transport/oasn/oasn-discovery.d.ts +33 -0
- package/dist/src/transport/oasn/oasn-discovery.js +97 -0
- package/dist/src/transport/oasn/oasn-files.d.ts +64 -0
- package/dist/src/transport/oasn/oasn-files.js +174 -0
- package/dist/src/transport/oasn/oasn-http.d.ts +98 -0
- package/dist/src/transport/oasn/oasn-http.js +362 -0
- package/dist/src/transport/oasn/oasn-invocation.d.ts +154 -0
- package/dist/src/transport/oasn/oasn-invocation.js +432 -0
- package/dist/src/transport/oasn/oasn-normalize.d.ts +6 -0
- package/dist/src/transport/oasn/oasn-normalize.js +112 -0
- package/dist/src/transport/oasn/oasn-register.d.ts +19 -0
- package/dist/src/transport/oasn/oasn-register.js +24 -0
- package/dist/src/transport/oasn/oasn-transport.d.ts +114 -0
- package/dist/src/transport/oasn/oasn-transport.js +230 -0
- package/dist/src/transport/oasn/oasn-types.d.ts +331 -0
- package/dist/src/transport/oasn/oasn-types.js +44 -0
- package/dist/src/transport/tim/index.d.ts +7 -0
- package/dist/src/transport/tim/index.js +6 -0
- package/dist/src/transport/tim/tim-transport.d.ts +122 -0
- package/dist/src/transport/tim/tim-transport.js +402 -0
- package/dist/src/transport/types.d.ts +450 -0
- package/dist/src/transport/types.js +38 -0
- package/dist/src/util/http.d.ts +21 -0
- package/dist/src/util/http.js +93 -0
- package/dist/src/util/logger.d.ts +20 -0
- package/dist/src/util/logger.js +100 -0
- package/dist/src/util/url-resolver.d.ts +7 -0
- package/dist/src/util/url-resolver.js +20 -0
- package/index.ts +11 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +7 -4
- package/src/app/download-file-tool.ts +133 -0
- package/src/app/hooks.ts +89 -5
- package/src/app/index.ts +6 -0
- package/src/app/remote-agent-tool.ts +46 -0
- package/src/app/types.ts +1 -0
- package/src/app/upload-file-tool.ts +411 -0
- package/src/plugin-ui/assets/openagent-override.js +15 -66
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -61
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -1
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +14 -3
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +102 -63
- package/src/runtime/account.ts +0 -23
- package/src/transport/oasn/oasn-files.ts +5 -5
- package/src/transport/oasn/oasn-invocation.ts +44 -2
- package/src/transport/oasn/oasn-normalize.ts +0 -26
- package/src/transport/oasn/oasn-register.ts +8 -81
- package/src/transport/oasn/oasn-transport.ts +9 -32
- package/src/transport/oasn/oasn-types.ts +53 -6
- package/src/transport/types.ts +33 -36
- package/src/util/url-resolver.ts +17 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAgent Plugin — Logger
|
|
3
|
+
*
|
|
4
|
+
* 统一日志模块,写入 OpenClaw 标准日志目录。
|
|
5
|
+
* 禁止在 Gateway 运行时使用任何 console 输出。
|
|
6
|
+
*
|
|
7
|
+
* 级别控制优先级:环境变量 > 默认 DEBUG(产品发布后改为 INFO)
|
|
8
|
+
*/
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/infra-runtime";
|
|
13
|
+
// ── 常量 ──
|
|
14
|
+
const LOG_DIR = resolvePreferredOpenClawTmpDir();
|
|
15
|
+
const SUBSYSTEM = "gateway/channels/openagent";
|
|
16
|
+
const HOSTNAME = os.hostname() || "unknown";
|
|
17
|
+
const LEVEL_IDS = {
|
|
18
|
+
TRACE: 1,
|
|
19
|
+
DEBUG: 2,
|
|
20
|
+
INFO: 3,
|
|
21
|
+
WARN: 4,
|
|
22
|
+
ERROR: 5,
|
|
23
|
+
FATAL: 6,
|
|
24
|
+
};
|
|
25
|
+
const DEFAULT_LEVEL = "DEBUG";
|
|
26
|
+
// ── 级别控制 ──
|
|
27
|
+
/**
|
|
28
|
+
* 解析最终日志级别。
|
|
29
|
+
* 优先级:环境变量 > 默认 DEBUG
|
|
30
|
+
*/
|
|
31
|
+
function resolveMinLevel() {
|
|
32
|
+
const env = process.env.OPENCLAW_LOG_LEVEL?.toUpperCase();
|
|
33
|
+
if (env && env in LEVEL_IDS)
|
|
34
|
+
return LEVEL_IDS[env];
|
|
35
|
+
return LEVEL_IDS[DEFAULT_LEVEL];
|
|
36
|
+
}
|
|
37
|
+
let minLevelId = resolveMinLevel();
|
|
38
|
+
/** 运行时动态修改日志级别 */
|
|
39
|
+
export function setLogLevel(level) {
|
|
40
|
+
const upper = level.toUpperCase();
|
|
41
|
+
if (!(upper in LEVEL_IDS)) {
|
|
42
|
+
throw new Error(`Invalid log level: ${level}. Valid: ${Object.keys(LEVEL_IDS).join(", ")}`);
|
|
43
|
+
}
|
|
44
|
+
minLevelId = LEVEL_IDS[upper];
|
|
45
|
+
}
|
|
46
|
+
// ── 时间格式化 ──
|
|
47
|
+
function toLocalISO(now) {
|
|
48
|
+
const offsetMs = -now.getTimezoneOffset() * 60_000;
|
|
49
|
+
const sign = offsetMs >= 0 ? "+" : "-";
|
|
50
|
+
const abs = Math.abs(now.getTimezoneOffset());
|
|
51
|
+
const hh = String(Math.floor(abs / 60)).padStart(2, "0");
|
|
52
|
+
const mm = String(abs % 60).padStart(2, "0");
|
|
53
|
+
return new Date(now.getTime() + offsetMs).toISOString().replace("Z", `${sign}${hh}:${mm}`);
|
|
54
|
+
}
|
|
55
|
+
function resolveLogPath() {
|
|
56
|
+
const dateKey = toLocalISO(new Date()).slice(0, 10);
|
|
57
|
+
return path.join(LOG_DIR, `openclaw-${dateKey}.log`);
|
|
58
|
+
}
|
|
59
|
+
// ── 写入 ──
|
|
60
|
+
let logDirEnsured = false;
|
|
61
|
+
function writeLog(level, message) {
|
|
62
|
+
const levelId = LEVEL_IDS[level] ?? LEVEL_IDS.INFO;
|
|
63
|
+
if (levelId < minLevelId)
|
|
64
|
+
return;
|
|
65
|
+
const now = new Date();
|
|
66
|
+
const entry = JSON.stringify({
|
|
67
|
+
"0": SUBSYSTEM,
|
|
68
|
+
"1": message,
|
|
69
|
+
_meta: {
|
|
70
|
+
runtime: "node",
|
|
71
|
+
runtimeVersion: process.versions.node,
|
|
72
|
+
hostname: HOSTNAME,
|
|
73
|
+
name: SUBSYSTEM,
|
|
74
|
+
parentNames: ["openclaw"],
|
|
75
|
+
date: now.toISOString(),
|
|
76
|
+
logLevelId: levelId,
|
|
77
|
+
logLevelName: level,
|
|
78
|
+
},
|
|
79
|
+
time: toLocalISO(now),
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
if (!logDirEnsured) {
|
|
83
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
84
|
+
logDirEnsured = true;
|
|
85
|
+
}
|
|
86
|
+
fs.appendFileSync(resolveLogPath(), `${entry}\n`, "utf-8");
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// 写日志失败不应阻塞业务
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export const logger = {
|
|
93
|
+
trace(message) { writeLog("TRACE", message); },
|
|
94
|
+
debug(message) { writeLog("DEBUG", message); },
|
|
95
|
+
info(message) { writeLog("INFO", message); },
|
|
96
|
+
warn(message) { writeLog("WARN", message); },
|
|
97
|
+
error(message) { writeLog("ERROR", message); },
|
|
98
|
+
fatal(message) { writeLog("FATAL", message); },
|
|
99
|
+
getLogFilePath() { return resolveLogPath(); },
|
|
100
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared URL resolution helper.
|
|
3
|
+
*
|
|
4
|
+
* Resolves a possibly-relative URL against an optional base URL.
|
|
5
|
+
* Used by remote-agent-tool (WebUI continuation) and download-file-tool (artifact download).
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveOasnAccessUrl(accessApiBase: string | undefined, url: string): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared URL resolution helper.
|
|
3
|
+
*
|
|
4
|
+
* Resolves a possibly-relative URL against an optional base URL.
|
|
5
|
+
* Used by remote-agent-tool (WebUI continuation) and download-file-tool (artifact download).
|
|
6
|
+
*/
|
|
7
|
+
export function resolveOasnAccessUrl(accessApiBase, url) {
|
|
8
|
+
if (!url)
|
|
9
|
+
return url;
|
|
10
|
+
if (/^https?:\/\//i.test(url))
|
|
11
|
+
return url;
|
|
12
|
+
if (!accessApiBase)
|
|
13
|
+
return url;
|
|
14
|
+
try {
|
|
15
|
+
return new URL(url, accessApiBase).toString();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return url;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/index.ts
CHANGED
|
@@ -46,6 +46,17 @@ export default {
|
|
|
46
46
|
'(transport=oasn) or Tencent IM C2C (transport=tim, default).',
|
|
47
47
|
configSchema: buildChannelConfigSchema(OpenagentConfigSchema),
|
|
48
48
|
|
|
49
|
+
// 2026.6.9+: Gateway requires plugins to declare tool contracts before registering them.
|
|
50
|
+
contracts: {
|
|
51
|
+
tools: [
|
|
52
|
+
'openagent_call_remote_agent',
|
|
53
|
+
'openagent_search_agents',
|
|
54
|
+
'openagent_get_agent_detail',
|
|
55
|
+
'openagent_upload_file',
|
|
56
|
+
'openagent_download_file',
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
|
|
49
60
|
register(api: OpenClawPluginApi): void {
|
|
50
61
|
logger.info('[entry] register() called');
|
|
51
62
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
"name": "OpenAgent",
|
|
4
4
|
"description": "OpenAgent channel plugin — real-time messaging between AI agents via OASN",
|
|
5
5
|
"version": "1.0.0",
|
|
6
|
+
"contracts": {
|
|
7
|
+
"tools": [
|
|
8
|
+
"openagent_call_remote_agent",
|
|
9
|
+
"openagent_search_agents",
|
|
10
|
+
"openagent_get_agent_detail",
|
|
11
|
+
"openagent_upload_file",
|
|
12
|
+
"openagent_download_file"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
6
15
|
"channels": [
|
|
7
16
|
"openagent"
|
|
8
17
|
],
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-openagent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenAgent channel plugin for OpenClaw",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
7
8
|
"files": [
|
|
9
|
+
"dist/",
|
|
8
10
|
"src/",
|
|
9
11
|
"skills/",
|
|
10
12
|
"index.ts",
|
|
@@ -19,7 +21,7 @@
|
|
|
19
21
|
],
|
|
20
22
|
"openclaw": {
|
|
21
23
|
"extensions": [
|
|
22
|
-
"./index.
|
|
24
|
+
"./dist/index.js"
|
|
23
25
|
],
|
|
24
26
|
"channel": {
|
|
25
27
|
"id": "openagent",
|
|
@@ -45,9 +47,10 @@
|
|
|
45
47
|
"node": ">=22"
|
|
46
48
|
},
|
|
47
49
|
"scripts": {
|
|
50
|
+
"build": "tsc -p tsconfig.build.json && cp -r src/sdk dist/src/sdk && mkdir -p dist/src/plugin-ui/assets && cp src/plugin-ui/assets/openagent-override.js dist/src/plugin-ui/assets/",
|
|
48
51
|
"build:override": "node ./src/plugin-ui/build.cjs",
|
|
49
52
|
"watch:override": "node ./src/plugin-ui/build.cjs --watch",
|
|
50
|
-
"prepack": "npm run build:override && npm run typecheck",
|
|
53
|
+
"prepack": "npm run build:override && npm run build && npm run typecheck",
|
|
51
54
|
"typecheck": "tsc --noEmit",
|
|
52
55
|
"prepublishOnly": "npm run typecheck",
|
|
53
56
|
"test:contract": "node tests/contract.test.mjs && node tests/agent-book-batch-category.test.mjs",
|
|
@@ -72,7 +75,7 @@
|
|
|
72
75
|
"@types/node": "^25.5.0",
|
|
73
76
|
"@types/sql.js": "^1.4.11",
|
|
74
77
|
"@types/yazl": "^3.3.1",
|
|
75
|
-
"openclaw": "^2026.
|
|
78
|
+
"openclaw": "^2026.6.10",
|
|
76
79
|
"tsx": "^4.21.0",
|
|
77
80
|
"typescript": "^6.0.2"
|
|
78
81
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download File Tool — openagent_download_file
|
|
3
|
+
*
|
|
4
|
+
* Downloads an artifact returned by a remote OASN agent and saves it to local disk.
|
|
5
|
+
*
|
|
6
|
+
* API: GET /api/artifacts/{id}/content (access layer)
|
|
7
|
+
* Ref: access-upload-download-api.md §3
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import nodePath from 'node:path';
|
|
12
|
+
import { resolveOasnAccessUrl } from '../util/url-resolver.js';
|
|
13
|
+
|
|
14
|
+
import { registry } from '../runtime/registry.js';
|
|
15
|
+
import { logger } from '../util/logger.js';
|
|
16
|
+
import {
|
|
17
|
+
toolResult,
|
|
18
|
+
type ToolDescriptor,
|
|
19
|
+
type ToolParams,
|
|
20
|
+
type ToolResult,
|
|
21
|
+
} from './types.js';
|
|
22
|
+
import type { OasnTransport } from '../transport/types.js';
|
|
23
|
+
|
|
24
|
+
// ── Helpers ──
|
|
25
|
+
|
|
26
|
+
function fail(error: string): ToolResult {
|
|
27
|
+
return toolResult(JSON.stringify({ success: false, error }));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function success(result: Record<string, unknown>): ToolResult {
|
|
31
|
+
return toolResult(JSON.stringify({ success: true, ...result }));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── Tool Factory ──
|
|
35
|
+
|
|
36
|
+
export function createDownloadFileTool(): ToolDescriptor {
|
|
37
|
+
return {
|
|
38
|
+
name: 'openagent_download_file',
|
|
39
|
+
label: 'Download File',
|
|
40
|
+
description:
|
|
41
|
+
'Download an artifact file returned by a remote OASN agent and save it to disk. ' +
|
|
42
|
+
'Use this when a remote agent returns a file (PDF, image, document, etc.) ' +
|
|
43
|
+
'that the user wants to save locally. ' +
|
|
44
|
+
'The content_url comes from the remote agent\'s response artifacts. ' +
|
|
45
|
+
'Look for artifact URLs in the openagent_call_remote_agent result. ' +
|
|
46
|
+
'If the save_path parent directory does not exist it will be created automatically. ' +
|
|
47
|
+
'Existing files at save_path will be overwritten.',
|
|
48
|
+
parameters: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
content_url: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description:
|
|
54
|
+
'The artifact content URL from the remote agent result ' +
|
|
55
|
+
'(e.g., /api/artifacts/art_1/content?session_id=...&invocation_id=...)',
|
|
56
|
+
},
|
|
57
|
+
save_path: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
description: 'Absolute local path where the file should be saved',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ['content_url', 'save_path'],
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async execute(
|
|
66
|
+
_id: unknown,
|
|
67
|
+
params: ToolParams,
|
|
68
|
+
): Promise<ToolResult> {
|
|
69
|
+
const contentUrl = String(params.content_url || '').trim();
|
|
70
|
+
const savePath = String(params.save_path || '').trim();
|
|
71
|
+
|
|
72
|
+
if (!contentUrl) {
|
|
73
|
+
return fail('content_url is required');
|
|
74
|
+
}
|
|
75
|
+
if (!savePath) {
|
|
76
|
+
return fail('save_path is required');
|
|
77
|
+
}
|
|
78
|
+
if (!nodePath.isAbsolute(savePath)) {
|
|
79
|
+
return fail('save_path must be an absolute path');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ① Check runtime state
|
|
83
|
+
const rt = registry.getDefault();
|
|
84
|
+
if (!rt || !rt.isRunning) {
|
|
85
|
+
return fail('OpenAgent runtime not running');
|
|
86
|
+
}
|
|
87
|
+
if (rt.transportType !== 'oasn') {
|
|
88
|
+
return fail('File download is only available via OASN transport');
|
|
89
|
+
}
|
|
90
|
+
const transport = rt.transport;
|
|
91
|
+
if (!transport || transport.transportType !== 'oasn') {
|
|
92
|
+
return fail('OASN transport not ready');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ② Resolve download URL
|
|
96
|
+
const accessBase = rt.config?.accessApiBase;
|
|
97
|
+
const downloadUrl = resolveOasnAccessUrl(accessBase, contentUrl);
|
|
98
|
+
|
|
99
|
+
logger.info(
|
|
100
|
+
`[download-tool] Downloading artifact: url=${downloadUrl} saveTo=${savePath}`,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// ③ Download
|
|
104
|
+
const oasnTransport = transport as OasnTransport;
|
|
105
|
+
let downloaded;
|
|
106
|
+
try {
|
|
107
|
+
downloaded = await oasnTransport.downloadArtifact(downloadUrl);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
logger.error(`[download-tool] Download failed: ${(err as Error).message}`);
|
|
110
|
+
return fail(`Download failed: ${(err as Error).message}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ④ Save to disk
|
|
114
|
+
try {
|
|
115
|
+
fs.mkdirSync(nodePath.dirname(savePath), { recursive: true });
|
|
116
|
+
fs.writeFileSync(savePath, Buffer.from(downloaded.content));
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return fail(`Cannot write to path: ${(err as Error).message}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
logger.info(
|
|
122
|
+
`[download-tool] Download success: path=${savePath} size=${downloaded.content.byteLength}`,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return success({
|
|
126
|
+
savedPath: savePath,
|
|
127
|
+
sizeBytes: downloaded.content.byteLength,
|
|
128
|
+
mimeType: downloaded.mimeType,
|
|
129
|
+
displayName: downloaded.displayName,
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
package/src/app/hooks.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hooks — Scene + Purpose injection
|
|
2
|
+
* Hooks — Scene + Purpose injection + Inline Image Offload
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
5
|
-
* rules (skill) and agent purpose
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Registers hooks with the OpenClaw API:
|
|
5
|
+
* 1. before_prompt_build — injects channel-specific rules (skill) and agent purpose
|
|
6
|
+
* 2. agent_turn_prepare — offloads inline base64 images to ~/.openclaw/media/inbound/
|
|
7
|
+
* so the model can reference them via media://inbound/<id>
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { registry } from '../runtime/registry.js';
|
|
@@ -26,6 +26,7 @@ type AnyApi = any;
|
|
|
26
26
|
* Register all OpenAgent hooks with OpenClaw API.
|
|
27
27
|
*/
|
|
28
28
|
export function registerHooks(api: AnyApi): void {
|
|
29
|
+
// ── Hook 1: Scene + Purpose injection ──
|
|
29
30
|
api.registerHook('before_prompt_build', async (_event: HookEvent, ctx: HookContext) => {
|
|
30
31
|
// Only fire for openagent channel sessions
|
|
31
32
|
if (!ctx.sessionId?.startsWith('openagent:')) return {};
|
|
@@ -57,4 +58,87 @@ export function registerHooks(api: AnyApi): void {
|
|
|
57
58
|
logger.debug(`[hooks] Injected Scene+Purpose for ${channelId}`);
|
|
58
59
|
return { appendSystemContext: parts.join('\n\n') };
|
|
59
60
|
}, { name: 'openagent-scene-purpose' });
|
|
61
|
+
|
|
62
|
+
// ── Hook 2: Inline image offload ──
|
|
63
|
+
//
|
|
64
|
+
// When a user uploads a file via Web UI and the model supports vision,
|
|
65
|
+
// images < 2MB are sent inline (base64 in the message content) and never
|
|
66
|
+
// saved to disk. This hook intercepts those inline images via the llm_input
|
|
67
|
+
// event (fires for ALL agent sessions including embedded), extracts the
|
|
68
|
+
// base64 data, and writes it to ~/.openclaw/media/inbound/ via saveMediaBuffer.
|
|
69
|
+
//
|
|
70
|
+
// The model won't see a media://inbound/ URL in its prompt, but
|
|
71
|
+
// openagent_upload_file's auto-fallback (resolveLatestInboundFile) will
|
|
72
|
+
// pick up the most recently saved file automatically.
|
|
73
|
+
//
|
|
74
|
+
api.registerHook('llm_input', async (event: HookEvent) => {
|
|
75
|
+
const historyMessages = event.historyMessages as unknown[] | undefined;
|
|
76
|
+
if (!Array.isArray(historyMessages) || historyMessages.length === 0) return;
|
|
77
|
+
|
|
78
|
+
logger.info(
|
|
79
|
+
`[hooks] llm_input FIRED imagesCount=${event.imagesCount} historyMsgs=${historyMessages.length}`,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Scan ALL messages (not just the last user message) for inline images
|
|
83
|
+
const MAX_BYTES = 20 * 1024 * 1024;
|
|
84
|
+
let savedCount = 0;
|
|
85
|
+
|
|
86
|
+
for (const msg of historyMessages as Record<string, unknown>[]) {
|
|
87
|
+
if (!msg || typeof msg !== 'object') continue;
|
|
88
|
+
const contentRaw = msg.content;
|
|
89
|
+
const contentBlocks: Record<string, unknown>[] = Array.isArray(contentRaw)
|
|
90
|
+
? contentRaw as Record<string, unknown>[]
|
|
91
|
+
: [];
|
|
92
|
+
|
|
93
|
+
for (const block of contentBlocks) {
|
|
94
|
+
let base64: string | undefined;
|
|
95
|
+
let mimeType: string | undefined;
|
|
96
|
+
|
|
97
|
+
// Anthropic-style: { type: "image", source: { type: "base64", media_type: "...", data: "..." } }
|
|
98
|
+
if (block?.type === 'image') {
|
|
99
|
+
const source = block.source as Record<string, unknown> | undefined;
|
|
100
|
+
if (source?.type === 'base64' && typeof source.data === 'string') {
|
|
101
|
+
base64 = source.data;
|
|
102
|
+
mimeType = (source.media_type as string) || 'image/png';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// OpenAI-style: { type: "image_url", image_url: { url: "data:image/png;base64,..." } }
|
|
107
|
+
if (!base64 && block?.type === 'image_url') {
|
|
108
|
+
const imageUrl = (block.image_url as Record<string, unknown> | undefined)?.url;
|
|
109
|
+
if (typeof imageUrl === 'string') {
|
|
110
|
+
const match = imageUrl.match(/^data:([^;]+);base64,(.+)$/);
|
|
111
|
+
if (match) {
|
|
112
|
+
mimeType = match[1]!;
|
|
113
|
+
base64 = match[2]!;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!base64) continue;
|
|
119
|
+
|
|
120
|
+
const buffer = Buffer.from(base64, 'base64');
|
|
121
|
+
const mime = mimeType || 'application/octet-stream';
|
|
122
|
+
const ext = mime.split('/')[1] || 'bin';
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const { saveMediaBuffer } = await import('openclaw/plugin-sdk/media-store');
|
|
126
|
+
const saved = await saveMediaBuffer(buffer, mime, 'inbound', MAX_BYTES, `webui-inline.${ext}`);
|
|
127
|
+
savedCount++;
|
|
128
|
+
logger.info(
|
|
129
|
+
`[hooks] Offloaded inline image: media://inbound/${saved.id} ` +
|
|
130
|
+
`size=${buffer.byteLength} mime=${mime}`,
|
|
131
|
+
);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
logger.warn(
|
|
134
|
+
`[hooks] Failed to save inline image: ${(err as Error).message}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (savedCount > 0) {
|
|
141
|
+
logger.info(`[hooks] llm_input saved ${savedCount} inline image(s) to media store`);
|
|
142
|
+
}
|
|
143
|
+
}, { name: 'openagent-inline-image-offload' });
|
|
60
144
|
}
|
package/src/app/index.ts
CHANGED
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
createSearchAgentsTool,
|
|
33
33
|
createGetAgentDetailTool,
|
|
34
34
|
} from './discovery-tools.js';
|
|
35
|
+
import { createUploadFileTool } from './upload-file-tool.js';
|
|
36
|
+
import { createDownloadFileTool } from './download-file-tool.js';
|
|
35
37
|
|
|
36
38
|
import { registerHooks } from './hooks.js';
|
|
37
39
|
|
|
@@ -72,6 +74,10 @@ export function registerTools(api: OpenClawPluginApi): void {
|
|
|
72
74
|
// 注:getCategories() 传输层能力保留给 Agent Book UI 使用,不暴露为 LLM 工具。
|
|
73
75
|
api.registerTool(createSearchAgentsTool, { optional: false });
|
|
74
76
|
api.registerTool(createGetAgentDetailTool, { optional: false });
|
|
77
|
+
|
|
78
|
+
// ── File Upload / Download (2) — ACTIVE (OASN-only) ──
|
|
79
|
+
api.registerTool(createUploadFileTool, { optional: false });
|
|
80
|
+
api.registerTool(createDownloadFileTool, { optional: false });
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
export { registerHooks };
|
|
@@ -29,6 +29,7 @@ import { sendC2CCustomMessage, waitForC2CReply } from '../tim/c2c.js';
|
|
|
29
29
|
import type { OasnTransport, TaskHandle, TaskRequest } from '../transport/types.js';
|
|
30
30
|
import fs from 'node:fs';
|
|
31
31
|
import nodePath from 'node:path';
|
|
32
|
+
import { resolveOasnAccessUrl } from '../util/url-resolver.js';
|
|
32
33
|
|
|
33
34
|
// ── Helpers ──
|
|
34
35
|
|
|
@@ -174,6 +175,13 @@ export function createCallRemoteAgentTool(): ToolDescriptor {
|
|
|
174
175
|
'"run": stateless — task must contain full context.',
|
|
175
176
|
default: 'session',
|
|
176
177
|
},
|
|
178
|
+
input_file_refs: {
|
|
179
|
+
type: 'array',
|
|
180
|
+
items: { type: 'string' },
|
|
181
|
+
description:
|
|
182
|
+
'Optional OASN file_ref strings from openagent_upload_file. ' +
|
|
183
|
+
'The remote agent will receive these files as inputs.',
|
|
184
|
+
},
|
|
177
185
|
},
|
|
178
186
|
required: ['agent_id', 'agent_name', 'task'],
|
|
179
187
|
},
|
|
@@ -279,9 +287,14 @@ export function createCallRemoteAgentTool(): ToolDescriptor {
|
|
|
279
287
|
return fail((err as Error).message);
|
|
280
288
|
}
|
|
281
289
|
|
|
290
|
+
const inputFileRefs: string[] | undefined = Array.isArray(params.input_file_refs)
|
|
291
|
+
? (params.input_file_refs as string[]).filter((r: unknown) => typeof r === 'string' && r.trim())
|
|
292
|
+
: undefined;
|
|
293
|
+
|
|
282
294
|
const taskReq: TaskRequest = {
|
|
283
295
|
requestId,
|
|
284
296
|
task,
|
|
297
|
+
inputFileRefs: inputFileRefs?.length ? inputFileRefs : undefined,
|
|
285
298
|
timeoutMs: OASN_INVOCATION_TIMEOUT_MS,
|
|
286
299
|
// mode='session' 时不在协议层 join 已有 session(OASN 默认每次起新 Invocation)。
|
|
287
300
|
// 上游若需要保留对话历史,请在 task 文本里显式带 session_id;
|
|
@@ -332,6 +345,39 @@ export function createCallRemoteAgentTool(): ToolDescriptor {
|
|
|
332
345
|
}
|
|
333
346
|
}
|
|
334
347
|
|
|
348
|
+
// WebUI continuation:声明了 webui 能力的 ServiceAgent 在 succeeded 后会
|
|
349
|
+
// 在 visible_result.webui_continuation 里返回可继续交互的入口。
|
|
350
|
+
if (result.webuiContinuation?.launchUrl) {
|
|
351
|
+
const rawLink = result.webuiContinuation.launchUrl;
|
|
352
|
+
const accessBase = rt.config?.accessApiBase;
|
|
353
|
+
const link = resolveOasnAccessUrl(accessBase, rawLink);
|
|
354
|
+
const label = result.webuiContinuation.displayName || 'Open WebUI';
|
|
355
|
+
content = content.trim()
|
|
356
|
+
? `${content}\n\n${label}: ${link}`
|
|
357
|
+
: `${label}: ${link}`;
|
|
358
|
+
logger.info(
|
|
359
|
+
`[remote-tool] OASN webui continuation: id=${result.webuiContinuation.id} ` +
|
|
360
|
+
`state=${result.webuiContinuation.visibleState} link=${link}`,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Non-text artifacts: expose content_url so the LLM can pass them to
|
|
365
|
+
// openagent_download_file to save files locally.
|
|
366
|
+
const nonTextArtifacts = artifacts.filter((a) => {
|
|
367
|
+
if (!a.contentUrl) return false;
|
|
368
|
+
// already inlined as text → skip
|
|
369
|
+
if (!content.includes(a.contentUrl)) return true;
|
|
370
|
+
return false;
|
|
371
|
+
});
|
|
372
|
+
if (nonTextArtifacts.length > 0) {
|
|
373
|
+
const artifactLines = nonTextArtifacts.map((a) =>
|
|
374
|
+
`- ${a.displayName || 'file'}: ${a.contentUrl}`,
|
|
375
|
+
);
|
|
376
|
+
content = content.trim()
|
|
377
|
+
? `${content}\n\nFiles from remote agent (use openagent_download_file to save):\n${artifactLines.join('\n')}`
|
|
378
|
+
: `Files from remote agent:\n${artifactLines.join('\n')}`;
|
|
379
|
+
}
|
|
380
|
+
|
|
335
381
|
// OASN 的非文本 artifacts 暂继续复用 ArtifactRef.contentUrl 作为 mediaUrls。
|
|
336
382
|
reply = {
|
|
337
383
|
status: 'complete',
|