openclaw-openagent 1.0.8 → 1.0.11
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/app/remote-agent-tool.js +108 -10
- package/dist/src/auth/config.js +65 -10
- package/dist/src/channel.js +2 -1
- package/dist/src/plugin-ui/assets/bg.png +0 -0
- package/dist/src/plugin-ui/assets/icon.png +0 -0
- package/dist/src/plugin-ui/assets/openagent-override.js +1753 -774
- package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +53 -5
- package/dist/src/proxy/auth-proxy.d.ts +1 -0
- package/dist/src/proxy/auth-proxy.js +28 -9
- package/dist/src/transport/oasn/oasn-http.d.ts +12 -0
- package/dist/src/transport/oasn/oasn-http.js +45 -14
- package/dist/src/util/url-resolver.d.ts +6 -0
- package/dist/src/util/url-resolver.js +24 -8
- package/openclaw.plugin.json +16 -1
- package/package.json +3 -3
- package/src/app/remote-agent-tool.ts +126 -11
- package/src/auth/config.ts +79 -10
- package/src/channel.ts +2 -1
- package/src/plugin-ui/assets/bg.png +0 -0
- package/src/plugin-ui/assets/icon.png +0 -0
- package/src/plugin-ui/assets/openagent-override.js +1753 -774
- package/src/plugin-ui/build.cjs +80 -4
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +92 -9
- package/src/plugin-ui/modules/agent-book/panel/agent-card.js +26 -10
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +1 -1
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +64 -0
- package/src/plugin-ui/modules/agent-book/panel/styles.js +313 -178
- package/src/plugin-ui/modules/loader/bootstrap.js +1 -1
- package/src/plugin-ui/modules/loader/shared-state.js +54 -0
- package/src/plugin-ui/modules/remote-agent/execution-card.js +347 -124
- package/src/plugin-ui/modules/remote-agent/media-links.js +151 -0
- package/src/plugin-ui/modules/remote-agent/output-card.js +110 -33
- package/src/plugin-ui/modules/remote-agent/render-hooks.js +97 -18
- package/src/plugin-ui/modules/remote-agent/styles.js +488 -402
- package/src/plugin-ui/modules/remote-agent/tool-card-model.js +6 -0
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +50 -5
- package/src/proxy/auth-proxy.ts +30 -10
- package/src/transport/oasn/oasn-http.ts +60 -14
- package/src/util/url-resolver.ts +24 -8
|
@@ -77,6 +77,102 @@ function isTextArtifact(candidate) {
|
|
|
77
77
|
function artifactContentToText(content) {
|
|
78
78
|
return Buffer.from(content).toString('utf8').trim();
|
|
79
79
|
}
|
|
80
|
+
const OPENAGENT_MEDIA_DIR = '/tmp/openclaw/openagent-media';
|
|
81
|
+
const EXT_BY_MIME = {
|
|
82
|
+
'application/json': '.json',
|
|
83
|
+
'text/markdown': '.md',
|
|
84
|
+
'text/plain': '.txt',
|
|
85
|
+
'application/pdf': '.pdf',
|
|
86
|
+
'image/png': '.png',
|
|
87
|
+
'image/jpeg': '.jpg',
|
|
88
|
+
'image/gif': '.gif',
|
|
89
|
+
'image/webp': '.webp',
|
|
90
|
+
'application/zip': '.zip',
|
|
91
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': '.pptx',
|
|
92
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
|
|
93
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
|
|
94
|
+
};
|
|
95
|
+
function sanitizeMediaFileName(name) {
|
|
96
|
+
const base = nodePath.basename(name || 'artifact');
|
|
97
|
+
return base
|
|
98
|
+
.replace(/[\0\r\n]/g, '')
|
|
99
|
+
.replace(/[/:\\]/g, '_')
|
|
100
|
+
.replace(/^\.+$/, 'artifact')
|
|
101
|
+
.slice(0, 180) || 'artifact';
|
|
102
|
+
}
|
|
103
|
+
function inferExtension(buffer, mimeType) {
|
|
104
|
+
const mime = mimeType.split(';')[0]?.trim().toLowerCase() || '';
|
|
105
|
+
const byMime = EXT_BY_MIME[mime];
|
|
106
|
+
if (byMime)
|
|
107
|
+
return byMime;
|
|
108
|
+
if (buffer.subarray(0, 2).toString('latin1') === 'PK') {
|
|
109
|
+
const zipNames = buffer.toString('latin1');
|
|
110
|
+
if (zipNames.includes('[Content_Types].xml') && zipNames.includes('ppt/'))
|
|
111
|
+
return '.pptx';
|
|
112
|
+
if (zipNames.includes('[Content_Types].xml') && zipNames.includes('word/'))
|
|
113
|
+
return '.docx';
|
|
114
|
+
if (zipNames.includes('[Content_Types].xml') && zipNames.includes('xl/'))
|
|
115
|
+
return '.xlsx';
|
|
116
|
+
return '.zip';
|
|
117
|
+
}
|
|
118
|
+
if (buffer.subarray(0, 4).toString('latin1') === '%PDF')
|
|
119
|
+
return '.pdf';
|
|
120
|
+
if (buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])))
|
|
121
|
+
return '.png';
|
|
122
|
+
if (buffer.subarray(0, 3).equals(Buffer.from([0xff, 0xd8, 0xff])))
|
|
123
|
+
return '.jpg';
|
|
124
|
+
return '.bin';
|
|
125
|
+
}
|
|
126
|
+
function buildOasnMediaFileName(artifact, buffer, downloadedName, downloadedMime) {
|
|
127
|
+
const preferredName = downloadedName && downloadedName !== 'artifact'
|
|
128
|
+
? downloadedName
|
|
129
|
+
: artifact.displayName && artifact.displayName !== 'artifact'
|
|
130
|
+
? artifact.displayName
|
|
131
|
+
: artifact.id || 'artifact';
|
|
132
|
+
let safeName = sanitizeMediaFileName(preferredName);
|
|
133
|
+
if (!nodePath.extname(safeName)) {
|
|
134
|
+
safeName += inferExtension(buffer, downloadedMime || artifact.mimeType);
|
|
135
|
+
}
|
|
136
|
+
return safeName;
|
|
137
|
+
}
|
|
138
|
+
async function cacheOasnArtifactsForMedia(oasnTransport, artifacts) {
|
|
139
|
+
const localPaths = [];
|
|
140
|
+
for (const artifact of artifacts) {
|
|
141
|
+
const ref = artifact.contentUrl || artifact.id;
|
|
142
|
+
if (!ref)
|
|
143
|
+
continue;
|
|
144
|
+
try {
|
|
145
|
+
const downloaded = await oasnTransport.downloadArtifact(ref);
|
|
146
|
+
const buffer = Buffer.from(downloaded.content);
|
|
147
|
+
const fileName = buildOasnMediaFileName(artifact, buffer, downloaded.displayName, downloaded.mimeType);
|
|
148
|
+
const filePath = nodePath.join(OPENAGENT_MEDIA_DIR, fileName);
|
|
149
|
+
fs.mkdirSync(nodePath.dirname(filePath), { recursive: true });
|
|
150
|
+
fs.writeFileSync(filePath, buffer);
|
|
151
|
+
localPaths.push(filePath);
|
|
152
|
+
logger.info(`[remote-tool] Cached OASN artifact for media: id=${artifact.id} file=${filePath} size=${buffer.byteLength}`);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
logger.warn(`[remote-tool] Failed to cache OASN artifact ${artifact.id || ref}: ${err.message}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return localPaths;
|
|
159
|
+
}
|
|
160
|
+
function mediaDownloadLabel(url) {
|
|
161
|
+
let fileName = 'artifact';
|
|
162
|
+
try {
|
|
163
|
+
fileName = decodeURIComponent(new URL(url).pathname.split('/').pop() || fileName);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
fileName = decodeURIComponent(url.split('?')[0]?.split('/').pop() || fileName);
|
|
167
|
+
}
|
|
168
|
+
return sanitizeMediaFileName(fileName).replace(/[[\]\r\n]/g, ' ') || 'artifact';
|
|
169
|
+
}
|
|
170
|
+
function formatMediaDownloadLines(urls) {
|
|
171
|
+
const lines = urls
|
|
172
|
+
.filter((url) => typeof url === 'string' && url.trim())
|
|
173
|
+
.map((url) => `- [${mediaDownloadLabel(url)}](${url})`);
|
|
174
|
+
return lines.length ? `下载链接:\n${lines.join('\n')}` : '';
|
|
175
|
+
}
|
|
80
176
|
function normalizeRemoteToolExecuteArgs(signalOrUpdate, onUpdateOrContext, legacySignal) {
|
|
81
177
|
if (typeof signalOrUpdate === 'function') {
|
|
82
178
|
return {
|
|
@@ -265,7 +361,7 @@ export function createCallRemoteAgentTool() {
|
|
|
265
361
|
});
|
|
266
362
|
const artifacts = result.artifacts ?? [];
|
|
267
363
|
let content = result.content;
|
|
268
|
-
let mediaUrls =
|
|
364
|
+
let mediaUrls = [];
|
|
269
365
|
if (!content.trim()) {
|
|
270
366
|
for (const textArtifact of artifacts) {
|
|
271
367
|
if (!textArtifact.contentUrl && !textArtifact.contentAvailable)
|
|
@@ -274,10 +370,6 @@ export function createCallRemoteAgentTool() {
|
|
|
274
370
|
const downloaded = await oasnTransport.downloadArtifact(textArtifact.contentUrl || textArtifact.id);
|
|
275
371
|
if (isTextArtifact(downloaded)) {
|
|
276
372
|
content = artifactContentToText(downloaded.content);
|
|
277
|
-
mediaUrls = artifacts
|
|
278
|
-
.filter((a) => a.id !== textArtifact.id)
|
|
279
|
-
.map((a) => a.contentUrl)
|
|
280
|
-
.filter(Boolean);
|
|
281
373
|
break;
|
|
282
374
|
}
|
|
283
375
|
}
|
|
@@ -316,12 +408,15 @@ export function createCallRemoteAgentTool() {
|
|
|
316
408
|
? `${content}\n\nFiles from remote agent (use openagent_download_file to save):\n${artifactLines.join('\n')}`
|
|
317
409
|
: `Files from remote agent:\n${artifactLines.join('\n')}`;
|
|
318
410
|
}
|
|
411
|
+
if (artifacts.length > 0) {
|
|
412
|
+
mediaUrls = await cacheOasnArtifactsForMedia(oasnTransport, artifacts);
|
|
413
|
+
}
|
|
319
414
|
// Resolve OASN-relative URLs in content (remote agent may have embedded
|
|
320
415
|
// /webui/continuations/... or /api/artifacts/... in its output text).
|
|
321
416
|
if (content && accessBase) {
|
|
322
417
|
content = content.replace(/(?<![a-zA-Z0-9])(\/webui\/[^\s"')\]]+|\/api\/[^\s"')\]]+)/g, (match) => resolveOasnAccessUrl(accessBase, match));
|
|
323
418
|
}
|
|
324
|
-
// OASN
|
|
419
|
+
// OASN artifacts are cached locally before being handed to Gateway media rendering.
|
|
325
420
|
reply = {
|
|
326
421
|
status: 'complete',
|
|
327
422
|
content,
|
|
@@ -405,13 +500,16 @@ export function createCallRemoteAgentTool() {
|
|
|
405
500
|
return `${baseUrl}/plugins/openagent/media/${encodeURIComponent(fileName)}`;
|
|
406
501
|
});
|
|
407
502
|
}
|
|
408
|
-
// Path 1: MEDIA: lines
|
|
503
|
+
// Path 1: explicit markdown links + MEDIA: lines for Gateway parser.
|
|
504
|
+
// The markdown block makes the link visible even when the host does not
|
|
505
|
+
// render MEDIA: attachments in assistant/tool-result bubbles.
|
|
506
|
+
const downloadLines = formatMediaDownloadLines(httpMediaUrls);
|
|
409
507
|
const mediaLines = httpMediaUrls
|
|
410
508
|
.map((url) => `MEDIA:${url}`)
|
|
411
509
|
.join('\n');
|
|
412
|
-
const textWithMedia = reply.content
|
|
413
|
-
|
|
414
|
-
|
|
510
|
+
const textWithMedia = [reply.content.trim(), downloadLines, mediaLines]
|
|
511
|
+
.filter(Boolean)
|
|
512
|
+
.join('\n\n');
|
|
415
513
|
return {
|
|
416
514
|
content: [{ type: 'text', text: textWithMedia }],
|
|
417
515
|
// Path 2: details.media for v2026.4.11+ native attachment rendering
|
package/dist/src/auth/config.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* See §0.1 of refactoring-plan.md.
|
|
10
10
|
*/
|
|
11
11
|
const DEFAULT_API_BASE = 'https://api.openagent.club';
|
|
12
|
+
const DEFAULT_OASN_PUBLIC_ROOT = 'https://oasn-test.haimawan.com';
|
|
13
|
+
const OASN_ENV_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
12
14
|
/**
|
|
13
15
|
* OASN 默认 API base。
|
|
14
16
|
*
|
|
@@ -19,6 +21,41 @@ export const DEFAULT_OASN_API_BASE = 'https://oasn.invalid/CONFIGURE_ME';
|
|
|
19
21
|
export function isUnconfiguredOasnApiBase(value) {
|
|
20
22
|
return !value || value === DEFAULT_OASN_API_BASE;
|
|
21
23
|
}
|
|
24
|
+
function normalizeOasnApiBase(value) {
|
|
25
|
+
const normalized = String(value || '')
|
|
26
|
+
.trim()
|
|
27
|
+
.replace(/\/+$/, '')
|
|
28
|
+
.replace(/\/api$/, '');
|
|
29
|
+
return normalized === DEFAULT_OASN_API_BASE ? '' : normalized;
|
|
30
|
+
}
|
|
31
|
+
function normalizeOasnEnvId(value) {
|
|
32
|
+
const envId = String(value || '').trim();
|
|
33
|
+
if (!envId)
|
|
34
|
+
return '';
|
|
35
|
+
return OASN_ENV_ID_RE.test(envId) ? envId : '';
|
|
36
|
+
}
|
|
37
|
+
function oasnPublicBaseFromEnvId(envId) {
|
|
38
|
+
const normalized = normalizeOasnEnvId(envId);
|
|
39
|
+
if (!normalized)
|
|
40
|
+
return '';
|
|
41
|
+
const root = normalizeOasnApiBase(process.env.OASN_PUBLIC_ROOT || process.env.OPENAGENT_OASN_PUBLIC_ROOT || DEFAULT_OASN_PUBLIC_ROOT);
|
|
42
|
+
return `${root}/e/${normalized}`;
|
|
43
|
+
}
|
|
44
|
+
function resolveOasnBase({ accountBase, channelBase, envBase, accountEnvId, channelEnvId, envEnvId, fallbackBase, }) {
|
|
45
|
+
const configuredBase = normalizeOasnApiBase(accountBase) || normalizeOasnApiBase(channelBase);
|
|
46
|
+
if (configuredBase)
|
|
47
|
+
return configuredBase;
|
|
48
|
+
const configuredEnvId = normalizeOasnEnvId(accountEnvId) || normalizeOasnEnvId(channelEnvId);
|
|
49
|
+
if (configuredEnvId)
|
|
50
|
+
return oasnPublicBaseFromEnvId(configuredEnvId);
|
|
51
|
+
const ambientBase = normalizeOasnApiBase(envBase);
|
|
52
|
+
if (ambientBase)
|
|
53
|
+
return ambientBase;
|
|
54
|
+
const ambientEnvId = normalizeOasnEnvId(envEnvId);
|
|
55
|
+
if (ambientEnvId)
|
|
56
|
+
return oasnPublicBaseFromEnvId(ambientEnvId);
|
|
57
|
+
return fallbackBase;
|
|
58
|
+
}
|
|
22
59
|
/**
|
|
23
60
|
* Extract the OpenAgent-specific config block from the full OpenClaw config.
|
|
24
61
|
* Handles multiple config key locations that have drifted across versions.
|
|
@@ -75,19 +112,37 @@ export function resolveOpenagentAccount(cfg, accountId) {
|
|
|
75
112
|
const envApiBase = transport === 'oasn'
|
|
76
113
|
? (process.env.OASN_API_BASE || process.env.OPENAGENT_OASN_API_BASE || '')
|
|
77
114
|
: '';
|
|
78
|
-
const apiBase =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
115
|
+
const apiBase = transport === 'oasn'
|
|
116
|
+
? resolveOasnBase({
|
|
117
|
+
accountBase: targetAcc['api_base'] || targetAcc['apiBase'],
|
|
118
|
+
channelBase: clCfg['api_base'] || clCfg['apiBase'],
|
|
119
|
+
envBase: envApiBase,
|
|
120
|
+
accountEnvId: targetAcc['oasn_env_id'] || targetAcc['oasnEnvId'] || targetAcc['env_id'] || targetAcc['envId'],
|
|
121
|
+
channelEnvId: clCfg['oasn_env_id'] || clCfg['oasnEnvId'] || clCfg['env_id'] || clCfg['envId'],
|
|
122
|
+
envEnvId: process.env.OASN_ENV_ID || process.env.OPENAGENT_OASN_ENV_ID,
|
|
123
|
+
fallbackBase: fallbackApiBase,
|
|
124
|
+
})
|
|
125
|
+
: (targetAcc['api_base'] || targetAcc['apiBase']
|
|
126
|
+
|| clCfg['api_base']
|
|
127
|
+
|| clCfg['apiBase']
|
|
128
|
+
|| fallbackApiBase);
|
|
83
129
|
const envAccessApiBase = transport === 'oasn'
|
|
84
130
|
? (process.env.OASN_ACCESS_API_BASE || process.env.OPENAGENT_OASN_ACCESS_API_BASE || '')
|
|
85
131
|
: '';
|
|
86
|
-
const accessApiBase =
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
132
|
+
const accessApiBase = transport === 'oasn'
|
|
133
|
+
? resolveOasnBase({
|
|
134
|
+
accountBase: targetAcc['access_api_base'] || targetAcc['accessApiBase'],
|
|
135
|
+
channelBase: clCfg['access_api_base'] || clCfg['accessApiBase'],
|
|
136
|
+
envBase: envAccessApiBase,
|
|
137
|
+
accountEnvId: targetAcc['oasn_env_id'] || targetAcc['oasnEnvId'] || targetAcc['env_id'] || targetAcc['envId'],
|
|
138
|
+
channelEnvId: clCfg['oasn_env_id'] || clCfg['oasnEnvId'] || clCfg['env_id'] || clCfg['envId'],
|
|
139
|
+
envEnvId: process.env.OASN_ENV_ID || process.env.OPENAGENT_OASN_ENV_ID,
|
|
140
|
+
fallbackBase: apiBase,
|
|
141
|
+
})
|
|
142
|
+
: (targetAcc['access_api_base'] || targetAcc['accessApiBase']
|
|
143
|
+
|| clCfg['access_api_base']
|
|
144
|
+
|| clCfg['accessApiBase']
|
|
145
|
+
|| apiBase);
|
|
91
146
|
const claimUrl = (targetAcc['claim_url'] || targetAcc['claimUrl'] || clCfg['claim_url'] || clCfg['claimUrl'] || '');
|
|
92
147
|
const rawStatus = (targetAcc['status'] || targetAcc['claim_status'] || targetAcc['claimStatus'] || clCfg['status'] || '');
|
|
93
148
|
const normalizedStatus = normalizeClaimStatus(rawStatus, agentId, apiKey);
|
package/dist/src/channel.js
CHANGED
|
@@ -11,6 +11,7 @@ import { isUnconfiguredOasnApiBase, resolveOpenagentAccount, getOpenagentConfig
|
|
|
11
11
|
import { registry } from './runtime/registry.js';
|
|
12
12
|
import { AccountRuntime } from './runtime/account.js';
|
|
13
13
|
import { logger } from './util/logger.js';
|
|
14
|
+
import { resolveOasnRelativeUrl } from './util/url-resolver.js';
|
|
14
15
|
const API_BASE = 'https://api.clawlink.club';
|
|
15
16
|
const OASN_TRANSPORT = 'oasn';
|
|
16
17
|
// ── Channel adapter meta ──
|
|
@@ -467,7 +468,7 @@ function compactConfig(input) {
|
|
|
467
468
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined && value !== ''));
|
|
468
469
|
}
|
|
469
470
|
async function claimOasnAgent(apiBase, apiKey, agentId, ownerEmail) {
|
|
470
|
-
const res = await fetch(
|
|
471
|
+
const res = await fetch(resolveOasnRelativeUrl(apiBase, '/api/agents/claim'), {
|
|
471
472
|
method: 'POST',
|
|
472
473
|
headers: {
|
|
473
474
|
'Content-Type': 'application/json',
|
|
Binary file
|
|
Binary file
|