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
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Discovery Tools — Agent 搜索 / Agent 详情
|
|
3
|
-
*
|
|
4
|
-
* 封装 OASN DiscoveryTransport 能力为 LLM 可用工具。
|
|
5
|
-
* 当前仅 OASN 模式可用;TIM 模式下工具返回明确错误。
|
|
6
|
-
*
|
|
7
|
-
* 设计依据:docs/specs/2026-06-21-oasn-agent-search-tool-wiring.md
|
|
8
|
-
*
|
|
9
|
-
* 工具列表:
|
|
10
|
-
* - openagent_search_agents — 按关键词 / 分类 / 标签搜索 Agent
|
|
11
|
-
* - openagent_get_agent_detail — 查看单个 Agent 详情
|
|
12
|
-
*
|
|
13
|
-
* 注:getCategories() 传输层能力保留给 Agent Book UI 使用,不暴露为 LLM 工具。
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { registry } from '../runtime/registry.js';
|
|
17
|
-
import { logger } from '../util/logger.js';
|
|
18
|
-
import {
|
|
19
|
-
toolResult,
|
|
20
|
-
type ToolDescriptor,
|
|
21
|
-
type ToolFactory,
|
|
22
|
-
type ToolParams,
|
|
23
|
-
type ToolResult,
|
|
24
|
-
} from './types.js';
|
|
25
|
-
import type { SearchQuery, AgentCard, OasnTransport } from '../transport/types.js';
|
|
26
|
-
|
|
27
|
-
// ── Helpers ──
|
|
28
|
-
|
|
29
|
-
function fail(error: string): ToolResult {
|
|
30
|
-
return toolResult(JSON.stringify({ success: false, error }));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 从 AccountRuntime 获取 OASN transport 并 narrow 为 DiscoveryTransport。
|
|
35
|
-
* 返回 { error: string } 表示不可用(未连接 / TIM 模式 / transport 未就绪)。
|
|
36
|
-
*/
|
|
37
|
-
function getOasnDiscovery(): { error: string } | { transport: OasnTransport } {
|
|
38
|
-
const rt = registry.getDefault();
|
|
39
|
-
if (!rt || !rt.isRunning) return { error: 'OpenAgent runtime not running' };
|
|
40
|
-
if (rt.transportType !== 'oasn') {
|
|
41
|
-
return { error: 'Agent discovery is only available via OASN transport' };
|
|
42
|
-
}
|
|
43
|
-
const transport = rt.transport;
|
|
44
|
-
if (!transport || transport.transportType !== 'oasn') {
|
|
45
|
-
return { error: 'OASN transport not ready' };
|
|
46
|
-
}
|
|
47
|
-
// transportType check above guarantees OASN transport
|
|
48
|
-
return { transport: transport as OasnTransport };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 将 AgentCard 转换为 LLM 友好的精简视图。
|
|
53
|
-
* 去除内部字段(agent_type / visibility),保留用户关心的信息。
|
|
54
|
-
*/
|
|
55
|
-
function agentCardToSummary(card: AgentCard) {
|
|
56
|
-
return {
|
|
57
|
-
agent_id: card.agent_id,
|
|
58
|
-
name: card.name,
|
|
59
|
-
description: card.description,
|
|
60
|
-
tags: card.tags?.map((t) => t.name) ?? [],
|
|
61
|
-
category: card.category,
|
|
62
|
-
avatar_url: card.avatar_url,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ═══════════════════════════════════════════════════════════════
|
|
67
|
-
// 工具 1: openagent_search_agents
|
|
68
|
-
// ═══════════════════════════════════════════════════════════════
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 搜索可用 Agent。
|
|
72
|
-
*
|
|
73
|
-
* 三种搜索模式(互斥,按优先级):
|
|
74
|
-
* 1. query — 自然语言搜索(POST /api/agent-cards/search/by-query)
|
|
75
|
-
* 2. category — 分类浏览(POST /api/agent-cards/search/by-category)
|
|
76
|
-
* 3. tags — 标签筛选(POST /api/agent-cards/search/by-tags)
|
|
77
|
-
*
|
|
78
|
-
* LLM 使用场景:
|
|
79
|
-
* - 用户说"我想去旅行" → search_agents({ query: "旅行" })
|
|
80
|
-
* - 用户说"找个机票助手" → search_agents({ query: "机票" })
|
|
81
|
-
* - 用户说"看看有哪些旅行类 Agent" → search_agents({ category: ["travel"] })
|
|
82
|
-
*/
|
|
83
|
-
export const createSearchAgentsTool: ToolFactory = () => ({
|
|
84
|
-
name: 'openagent_search_agents',
|
|
85
|
-
label: 'Search Available Agents',
|
|
86
|
-
description: [
|
|
87
|
-
'Search for available service agents by natural language query, category, or tags.',
|
|
88
|
-
'Use this tool when the user expresses an intent that might be handled by a specialist agent.',
|
|
89
|
-
'Examples:',
|
|
90
|
-
' - "我想去旅行" → search_agents({ query: "旅行" })',
|
|
91
|
-
' - "帮我查航班" → search_agents({ query: "机票" })',
|
|
92
|
-
' - "有什么翻译助手" → search_agents({ query: "翻译" })',
|
|
93
|
-
'After finding agents, pick the most relevant one and call openagent_call_remote_agent with its agent_id.',
|
|
94
|
-
'At least one of query, category, or tags must be provided.',
|
|
95
|
-
].join(' '),
|
|
96
|
-
parameters: {
|
|
97
|
-
type: 'object',
|
|
98
|
-
properties: {
|
|
99
|
-
query: {
|
|
100
|
-
type: 'string',
|
|
101
|
-
description: 'Natural language search query, e.g. "旅行" or "机票预订"',
|
|
102
|
-
},
|
|
103
|
-
category: {
|
|
104
|
-
type: 'string',
|
|
105
|
-
description: 'Category path to browse, e.g. "travel" or ["travel", "flights"]',
|
|
106
|
-
},
|
|
107
|
-
tags: {
|
|
108
|
-
type: 'string',
|
|
109
|
-
description: 'Comma-separated tag names to filter by, e.g. "机票,酒店"',
|
|
110
|
-
},
|
|
111
|
-
page_size: {
|
|
112
|
-
type: 'number',
|
|
113
|
-
description: 'Results per page (default 10, max 50)',
|
|
114
|
-
default: 10,
|
|
115
|
-
},
|
|
116
|
-
page_num: {
|
|
117
|
-
type: 'number',
|
|
118
|
-
description: 'Page number, 1-indexed (default 1)',
|
|
119
|
-
default: 1,
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
required: [], // 至少提供一个: query / category / tags(运行时校验)
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
async execute(_id: unknown, params: ToolParams): Promise<ToolResult> {
|
|
126
|
-
logger.info(`[discovery-tools] search_agents called: ${JSON.stringify(params)}`);
|
|
127
|
-
|
|
128
|
-
// ① 获取 OASN transport
|
|
129
|
-
const discovery = getOasnDiscovery();
|
|
130
|
-
if ('error' in discovery) {
|
|
131
|
-
logger.warn(`[discovery-tools] search_agents unavailable: ${discovery.error}`);
|
|
132
|
-
return fail(discovery.error);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ② 构造 SearchQuery
|
|
136
|
-
const searchQuery = buildSearchQuery(params);
|
|
137
|
-
if (!searchQuery.query && !searchQuery.category?.length && !searchQuery.tags?.length) {
|
|
138
|
-
return fail('At least one of query, category, or tags must be provided');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// ③ 调用 transport.searchAgents()
|
|
142
|
-
try {
|
|
143
|
-
const result = await discovery.transport.searchAgents(searchQuery);
|
|
144
|
-
logger.info(
|
|
145
|
-
`[discovery-tools] search_agents returned: searchId=${result.searchId} ` +
|
|
146
|
-
`count=${result.agentCards.length} hasNext=${result.hasNext}`,
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
return toolResult(JSON.stringify({
|
|
150
|
-
success: true,
|
|
151
|
-
search_id: result.searchId,
|
|
152
|
-
agents: result.agentCards.map(agentCardToSummary),
|
|
153
|
-
page: {
|
|
154
|
-
num: result.pageNum,
|
|
155
|
-
size: result.pageSize,
|
|
156
|
-
has_next: result.hasNext,
|
|
157
|
-
},
|
|
158
|
-
}, null, 2));
|
|
159
|
-
} catch (err) {
|
|
160
|
-
const msg = (err as Error).message;
|
|
161
|
-
logger.error(`[discovery-tools] search_agents failed: ${msg}`);
|
|
162
|
-
return fail(`Agent search failed: ${msg}`);
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* 将工具参数转换为 SearchQuery。
|
|
169
|
-
* 处理类型转换(string → string[])和默认值。
|
|
170
|
-
*/
|
|
171
|
-
function buildSearchQuery(params: ToolParams): SearchQuery {
|
|
172
|
-
const query: SearchQuery = {
|
|
173
|
-
agentTypes: ['service'], // 只搜索 service 类型
|
|
174
|
-
pageSize: params.page_size ? Number(params.page_size) : 10,
|
|
175
|
-
pageNum: params.page_num ? Number(params.page_num) : 1,
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// query: 自然语言搜索
|
|
179
|
-
if (params.query && typeof params.query === 'string') {
|
|
180
|
-
query.query = params.query.trim();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// category: 可能是 string 或 string[]
|
|
184
|
-
if (params.category) {
|
|
185
|
-
if (Array.isArray(params.category)) {
|
|
186
|
-
query.category = params.category.map(String);
|
|
187
|
-
} else if (typeof params.category === 'string') {
|
|
188
|
-
// 支持逗号分隔或单值
|
|
189
|
-
query.category = params.category.split(',').map((s) => s.trim()).filter(Boolean);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// tags: 可能是 string 或 string[]
|
|
194
|
-
if (params.tags) {
|
|
195
|
-
if (Array.isArray(params.tags)) {
|
|
196
|
-
query.tags = params.tags.map(String);
|
|
197
|
-
} else if (typeof params.tags === 'string') {
|
|
198
|
-
query.tags = params.tags.split(',').map((s) => s.trim()).filter(Boolean);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return query;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ═══════════════════════════════════════════════════════════════
|
|
206
|
-
// 工具 2: openagent_get_agent_detail
|
|
207
|
-
// ═══════════════════════════════════════════════════════════════
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* 查看单个 Agent 详情。
|
|
211
|
-
*
|
|
212
|
-
* 在搜索结果中选择了一个 Agent 后,可以调用此工具获取完整信息(包括 MOM)。
|
|
213
|
-
*/
|
|
214
|
-
export const createGetAgentDetailTool: ToolFactory = () => ({
|
|
215
|
-
name: 'openagent_get_agent_detail',
|
|
216
|
-
label: 'Get Agent Details',
|
|
217
|
-
description: [
|
|
218
|
-
'Get detailed information about a specific agent by its agent_id.',
|
|
219
|
-
'Use this after search_agents to see the full agent card including description and MOM (Model Operation Manual).',
|
|
220
|
-
'The MOM contains usage instructions for the agent.',
|
|
221
|
-
].join(' '),
|
|
222
|
-
parameters: {
|
|
223
|
-
type: 'object',
|
|
224
|
-
properties: {
|
|
225
|
-
agent_id: {
|
|
226
|
-
type: 'string',
|
|
227
|
-
description: 'The agent_id to look up (from search_agents results)',
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
required: ['agent_id'],
|
|
231
|
-
},
|
|
232
|
-
|
|
233
|
-
async execute(_id: unknown, params: ToolParams): Promise<ToolResult> {
|
|
234
|
-
const agentId = String(params.agent_id || '').trim();
|
|
235
|
-
if (!agentId) {
|
|
236
|
-
return fail('agent_id is required');
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
logger.info(`[discovery-tools] get_agent_detail called: agent_id=${agentId}`);
|
|
240
|
-
|
|
241
|
-
const discovery = getOasnDiscovery();
|
|
242
|
-
if ('error' in discovery) {
|
|
243
|
-
return fail(discovery.error);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
const card = await discovery.transport.getAgent(agentId);
|
|
248
|
-
|
|
249
|
-
if (!card) {
|
|
250
|
-
return fail(`Agent not found: ${agentId}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
logger.info(`[discovery-tools] get_agent_detail returned: name=${card.name}`);
|
|
254
|
-
|
|
255
|
-
return toolResult(JSON.stringify({
|
|
256
|
-
success: true,
|
|
257
|
-
agent: {
|
|
258
|
-
agent_id: card.agent_id,
|
|
259
|
-
name: card.name,
|
|
260
|
-
description: card.description,
|
|
261
|
-
MOM: card.MOM,
|
|
262
|
-
tags: card.tags?.map((t: { tag_id: string; name: string }) => t.name) ?? [],
|
|
263
|
-
category: card.category,
|
|
264
|
-
avatar_url: card.avatar_url,
|
|
265
|
-
},
|
|
266
|
-
}, null, 2));
|
|
267
|
-
} catch (err) {
|
|
268
|
-
const msg = (err as Error).message;
|
|
269
|
-
logger.error(`[discovery-tools] get_agent_detail failed: ${msg}`);
|
|
270
|
-
return fail(`Failed to get agent details: ${msg}`);
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
});
|
|
@@ -1,133 +0,0 @@
|
|
|
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 || rt.config?.apiBase;
|
|
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
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hooks — Scene + Purpose injection + Inline Image Offload
|
|
3
|
-
*
|
|
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
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { registry } from '../runtime/registry.js';
|
|
11
|
-
import { logger } from '../util/logger.js';
|
|
12
|
-
|
|
13
|
-
interface HookEvent {
|
|
14
|
-
[key: string]: unknown;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface HookContext {
|
|
18
|
-
sessionId?: string;
|
|
19
|
-
[key: string]: unknown;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
-
type AnyApi = any;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Register all OpenAgent hooks with OpenClaw API.
|
|
27
|
-
*/
|
|
28
|
-
export function registerHooks(api: AnyApi): void {
|
|
29
|
-
// ── Hook 1: Scene + Purpose injection ──
|
|
30
|
-
api.registerHook('before_prompt_build', async (_event: HookEvent, ctx: HookContext) => {
|
|
31
|
-
// Only fire for openagent channel sessions
|
|
32
|
-
if (!ctx.sessionId?.startsWith('openagent:')) return {};
|
|
33
|
-
|
|
34
|
-
const channelId = ctx.sessionId.replace('openagent:', '');
|
|
35
|
-
const rt = registry.getDefault();
|
|
36
|
-
if (!rt) return {};
|
|
37
|
-
|
|
38
|
-
const parts: string[] = [];
|
|
39
|
-
|
|
40
|
-
// Inject channel rules (skill/scene)
|
|
41
|
-
try {
|
|
42
|
-
const rules = await rt.getChannelSkill(channelId);
|
|
43
|
-
if (rules) parts.push(`[SCENE: ${channelId}]\n${rules}\n[/SCENE]`);
|
|
44
|
-
} catch (err) {
|
|
45
|
-
logger.warn(`[hooks] rules fetch failed: ${(err as Error).message}`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Inject agent purpose for this channel
|
|
49
|
-
try {
|
|
50
|
-
const purpose = rt.store?.getPurpose(channelId);
|
|
51
|
-
if (purpose) parts.push(`[PURPOSE]\n${purpose}\n[/PURPOSE]`);
|
|
52
|
-
} catch (err) {
|
|
53
|
-
logger.warn(`[hooks] purpose fetch failed: ${(err as Error).message}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (parts.length === 0) return {};
|
|
57
|
-
|
|
58
|
-
logger.debug(`[hooks] Injected Scene+Purpose for ${channelId}`);
|
|
59
|
-
return { appendSystemContext: parts.join('\n\n') };
|
|
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' });
|
|
144
|
-
}
|
package/src/app/index.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* App — Tool + Hook registration aggregator
|
|
3
|
-
*
|
|
4
|
-
* Registers all tools and hooks with the OpenClaw API.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
createListChannelsTool,
|
|
9
|
-
createSearchChannelsTool,
|
|
10
|
-
createJoinChannelTool,
|
|
11
|
-
createLeaveChannelTool,
|
|
12
|
-
createCreateChannelTool,
|
|
13
|
-
createGetMembersTool,
|
|
14
|
-
createSetChannelSkillTool,
|
|
15
|
-
createGetChannelSkillTool,
|
|
16
|
-
} from './channel-tools.js';
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
createSendMessageTool,
|
|
20
|
-
createGetMessagesTool,
|
|
21
|
-
} from './messaging-tools.js';
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
createSetStrategyTool,
|
|
25
|
-
createGetStatusTool,
|
|
26
|
-
createSetPurposeTool,
|
|
27
|
-
createGetPurposeTool,
|
|
28
|
-
} from './ops-tools.js';
|
|
29
|
-
|
|
30
|
-
import { createCallRemoteAgentTool } from './remote-agent-tool.js';
|
|
31
|
-
import {
|
|
32
|
-
createSearchAgentsTool,
|
|
33
|
-
createGetAgentDetailTool,
|
|
34
|
-
} from './discovery-tools.js';
|
|
35
|
-
import { createUploadFileTool } from './upload-file-tool.js';
|
|
36
|
-
import { createDownloadFileTool } from './download-file-tool.js';
|
|
37
|
-
|
|
38
|
-
import { registerHooks } from './hooks.js';
|
|
39
|
-
|
|
40
|
-
import type { ToolFactory } from './types.js';
|
|
41
|
-
|
|
42
|
-
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk/plugin-entry';
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Register all tools with OpenClaw API.
|
|
46
|
-
*/
|
|
47
|
-
export function registerTools(api: OpenClawPluginApi): void {
|
|
48
|
-
// ── Channel management (8) — DORMANT: code preserved, not registered ──
|
|
49
|
-
// Uncomment to re-enable channel-based social networking features.
|
|
50
|
-
// api.registerTool(createListChannelsTool, { optional: false });
|
|
51
|
-
// api.registerTool(createSearchChannelsTool, { optional: false });
|
|
52
|
-
// api.registerTool(createJoinChannelTool, { optional: false });
|
|
53
|
-
// api.registerTool(createLeaveChannelTool, { optional: false });
|
|
54
|
-
// api.registerTool(createCreateChannelTool, { optional: false });
|
|
55
|
-
// api.registerTool(createGetMembersTool, { optional: false });
|
|
56
|
-
// api.registerTool(createSetChannelSkillTool, { optional: false });
|
|
57
|
-
// api.registerTool(createGetChannelSkillTool, { optional: false });
|
|
58
|
-
|
|
59
|
-
// ── Messaging (2) — DORMANT ──
|
|
60
|
-
// api.registerTool(createSendMessageTool, { optional: false });
|
|
61
|
-
// api.registerTool(createGetMessagesTool, { optional: false });
|
|
62
|
-
|
|
63
|
-
// ── Ops (4) — DORMANT ──
|
|
64
|
-
// api.registerTool(createSetStrategyTool, { optional: false });
|
|
65
|
-
// api.registerTool(createGetStatusTool, { optional: false });
|
|
66
|
-
// api.registerTool(createSetPurposeTool, { optional: false });
|
|
67
|
-
// api.registerTool(createGetPurposeTool, { optional: false });
|
|
68
|
-
|
|
69
|
-
// ── Remote Agent Orchestration (1) — ACTIVE ──
|
|
70
|
-
api.registerTool(createCallRemoteAgentTool, { optional: false });
|
|
71
|
-
|
|
72
|
-
// ── Agent Discovery (2) — ACTIVE (OASN-only) ──
|
|
73
|
-
// 搜索 / Agent 详情。TIM 模式下工具存在但返回明确错误。
|
|
74
|
-
// 注:getCategories() 传输层能力保留给 Agent Book UI 使用,不暴露为 LLM 工具。
|
|
75
|
-
api.registerTool(createSearchAgentsTool, { optional: false });
|
|
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 });
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export { registerHooks };
|
|
84
|
-
export type { ToolDescriptor, ToolFactory } from './types.js';
|