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.
Files changed (39) hide show
  1. package/dist/src/app/remote-agent-tool.js +108 -10
  2. package/dist/src/auth/config.js +65 -10
  3. package/dist/src/channel.js +2 -1
  4. package/dist/src/plugin-ui/assets/bg.png +0 -0
  5. package/dist/src/plugin-ui/assets/icon.png +0 -0
  6. package/dist/src/plugin-ui/assets/openagent-override.js +1753 -774
  7. package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +53 -5
  8. package/dist/src/proxy/auth-proxy.d.ts +1 -0
  9. package/dist/src/proxy/auth-proxy.js +28 -9
  10. package/dist/src/transport/oasn/oasn-http.d.ts +12 -0
  11. package/dist/src/transport/oasn/oasn-http.js +45 -14
  12. package/dist/src/util/url-resolver.d.ts +6 -0
  13. package/dist/src/util/url-resolver.js +24 -8
  14. package/openclaw.plugin.json +16 -1
  15. package/package.json +3 -3
  16. package/src/app/remote-agent-tool.ts +126 -11
  17. package/src/auth/config.ts +79 -10
  18. package/src/channel.ts +2 -1
  19. package/src/plugin-ui/assets/bg.png +0 -0
  20. package/src/plugin-ui/assets/icon.png +0 -0
  21. package/src/plugin-ui/assets/openagent-override.js +1753 -774
  22. package/src/plugin-ui/build.cjs +80 -4
  23. package/src/plugin-ui/modules/agent-book/panel/agent-book.js +92 -9
  24. package/src/plugin-ui/modules/agent-book/panel/agent-card.js +26 -10
  25. package/src/plugin-ui/modules/agent-book/panel/agent-data.js +1 -1
  26. package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +64 -0
  27. package/src/plugin-ui/modules/agent-book/panel/styles.js +313 -178
  28. package/src/plugin-ui/modules/loader/bootstrap.js +1 -1
  29. package/src/plugin-ui/modules/loader/shared-state.js +54 -0
  30. package/src/plugin-ui/modules/remote-agent/execution-card.js +347 -124
  31. package/src/plugin-ui/modules/remote-agent/media-links.js +151 -0
  32. package/src/plugin-ui/modules/remote-agent/output-card.js +110 -33
  33. package/src/plugin-ui/modules/remote-agent/render-hooks.js +97 -18
  34. package/src/plugin-ui/modules/remote-agent/styles.js +488 -402
  35. package/src/plugin-ui/modules/remote-agent/tool-card-model.js +6 -0
  36. package/src/plugin-ui/ui-extension-loader/registry-regex.ts +50 -5
  37. package/src/proxy/auth-proxy.ts +30 -10
  38. package/src/transport/oasn/oasn-http.ts +60 -14
  39. 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 = artifacts.map((a) => a.contentUrl).filter(Boolean);
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 的非文本 artifacts 暂继续复用 ArtifactRef.contentUrl 作为 mediaUrls。
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 in text for Gateway MEDIA: parser + LLM echo
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
- ? `${reply.content}\n${mediaLines}`
414
- : mediaLines;
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
@@ -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 = (targetAcc['api_base'] || targetAcc['apiBase']
79
- || clCfg['api_base']
80
- || clCfg['apiBase']
81
- || envApiBase
82
- || fallbackApiBase);
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 = (targetAcc['access_api_base'] || targetAcc['accessApiBase']
87
- || clCfg['access_api_base']
88
- || clCfg['accessApiBase']
89
- || envAccessApiBase
90
- || apiBase);
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);
@@ -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(new URL('/api/agents/claim', apiBase), {
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