openclaw-openagent 1.0.4 → 1.0.6

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.js CHANGED
@@ -20,6 +20,7 @@ import { logger } from './src/util/logger.js';
20
20
  // ── Runtime reference ─────────────────────────────────────────────────────
21
21
  // Saved from api.runtime during register(), accessible by other modules.
22
22
  let _runtime;
23
+ let _fullModeRegistered = false;
23
24
  export function getOpenagentRuntime() {
24
25
  return _runtime;
25
26
  }
@@ -63,8 +64,15 @@ export default {
63
64
  logger.info('[entry] Channel adapter registered');
64
65
  // P1-2: registrationMode exists in 2026.3.22+; skip heavy registrations in
65
66
  // setup-only mode (e.g. `openclaw channels list`).
67
+ // IMPORTANT: once registered in full mode, a subsequent hot-reload may
68
+ // re-invoke register() with mode='tool-discovery'. In that case we must
69
+ // still re-register tools/hooks so the gateway doesn't drop them.
66
70
  const mode = api.registrationMode;
67
- if (mode && mode !== 'full') {
71
+ if (mode && mode !== 'full' && _fullModeRegistered) {
72
+ logger.info(`[entry] Re-registering in ${mode} mode (full mode preserved)`);
73
+ // Fall through — re-register tools to avoid gateway dropping them.
74
+ }
75
+ else if (mode && mode !== 'full') {
68
76
  logger.info(`[entry] Setup-only mode (registrationMode=${mode}), skipping tools/hooks`);
69
77
  return;
70
78
  }
@@ -97,6 +105,7 @@ export default {
97
105
  logger.warn(`[entry] Control UI injection failed: ${err.message}`);
98
106
  });
99
107
  logger.info('[entry] Tools + hooks registered (full mode)');
108
+ _fullModeRegistered = true;
100
109
  },
101
110
  };
102
111
  // Named exports for direct imports
@@ -288,9 +288,9 @@ export function createCallRemoteAgentTool() {
288
288
  }
289
289
  // WebUI continuation:声明了 webui 能力的 ServiceAgent 在 succeeded 后会
290
290
  // 在 visible_result.webui_continuation 里返回可继续交互的入口。
291
+ const accessBase = rt.config?.accessApiBase || rt.config?.apiBase;
291
292
  if (result.webuiContinuation?.launchUrl) {
292
293
  const rawLink = result.webuiContinuation.launchUrl;
293
- const accessBase = rt.config?.accessApiBase || rt.config?.apiBase;
294
294
  const link = resolveOasnAccessUrl(accessBase, rawLink);
295
295
  const label = result.webuiContinuation.displayName || 'Open WebUI';
296
296
  content = content.trim()
@@ -315,6 +315,11 @@ export function createCallRemoteAgentTool() {
315
315
  ? `${content}\n\nFiles from remote agent (use openagent_download_file to save):\n${artifactLines.join('\n')}`
316
316
  : `Files from remote agent:\n${artifactLines.join('\n')}`;
317
317
  }
318
+ // Resolve OASN-relative URLs in content (remote agent may have embedded
319
+ // /webui/continuations/... or /api/artifacts/... in its output text).
320
+ if (content && accessBase) {
321
+ content = content.replace(/(?<![a-zA-Z0-9])(\/webui\/[^\s"')\]]+|\/api\/[^\s"')\]]+)/g, (match) => resolveOasnAccessUrl(accessBase, match));
322
+ }
318
323
  // OASN 的非文本 artifacts 暂继续复用 ArtifactRef.contentUrl 作为 mediaUrls。
319
324
  reply = {
320
325
  status: 'complete',
@@ -2,7 +2,7 @@
2
2
  * OpenAgent UI Override — Auto-generated, do not edit directly!
3
3
  *
4
4
  * Source: src/plugin-ui/modules/
5
- * Built: 2026-06-25T07:10:51.968Z
5
+ * Built: 2026-06-25T08:37:18.204Z
6
6
  *
7
7
  * To modify, edit the source modules and run:
8
8
  * npm run build:override
@@ -22,6 +22,15 @@ import { isUnconfiguredOasnApiBase } from '../auth/config.js';
22
22
  const AUTH_UPSTREAM_TIM = 'https://auth.ai-talk.live';
23
23
  const PROXY_TIMEOUT_MS = 15_000;
24
24
  const MAX_BODY_BYTES = 64 * 1024; // 64KB — tagline API body is ~100 bytes
25
+ /**
26
+ * 获取当前 OASN access API base URL,用于解析响应中的相对路径。
27
+ */
28
+ function getOasnAccessBase() {
29
+ const rt = registry.getDefault();
30
+ if (rt?.transportType !== 'oasn')
31
+ return '';
32
+ return (rt.config?.accessApiBase || rt.config?.apiBase || '').replace(/\/+$/, '');
33
+ }
25
34
  /**
26
35
  * 根据当前 active runtime 的 transport 类型决定上游。
27
36
  *
@@ -127,7 +136,30 @@ export function createAuthProxyHandler() {
127
136
  const contentType = upstream.headers.get('content-type');
128
137
  if (contentType)
129
138
  res.setHeader('Content-Type', contentType);
130
- const buf = Buffer.from(await upstream.arrayBuffer());
139
+ let buf = Buffer.from(await upstream.arrayBuffer());
140
+ // Resolve OASN-relative URLs in GET invocation responses so the
141
+ // frontend receives absolute URLs (launch_url, content_url, etc.).
142
+ if (method === 'GET' && /^\/api\/invocations\/[^/]+$/.test(targetPath) && buf.length > 0) {
143
+ try {
144
+ const isJson = contentType?.includes('json');
145
+ if (isJson) {
146
+ const oasnBase = getOasnAccessBase();
147
+ if (oasnBase) {
148
+ const text = buf.toString('utf-8');
149
+ const resolved = text.replace(/"(launch_url|content_url)":"(\/[^"]+)"/g, (_m, key, path) => {
150
+ try {
151
+ return `"${key}":"${new URL(path, oasnBase).toString()}"`;
152
+ }
153
+ catch {
154
+ return _m;
155
+ }
156
+ });
157
+ buf = Buffer.from(resolved, 'utf-8');
158
+ }
159
+ }
160
+ }
161
+ catch { /* best-effort; don't break proxy on parse errors */ }
162
+ }
131
163
  res.end(buf);
132
164
  logger.info(`[proxy] ${method} ${url.pathname} → ${upstream.status} (${buf.length} bytes)`);
133
165
  }
package/index.ts CHANGED
@@ -25,6 +25,7 @@ import { logger } from './src/util/logger.js';
25
25
  // Saved from api.runtime during register(), accessible by other modules.
26
26
 
27
27
  let _runtime: OpenClawPluginApi['runtime'] | undefined;
28
+ let _fullModeRegistered = false;
28
29
 
29
30
  export function getOpenagentRuntime(): OpenClawPluginApi['runtime'] | undefined {
30
31
  return _runtime;
@@ -80,8 +81,14 @@ export default {
80
81
 
81
82
  // P1-2: registrationMode exists in 2026.3.22+; skip heavy registrations in
82
83
  // setup-only mode (e.g. `openclaw channels list`).
84
+ // IMPORTANT: once registered in full mode, a subsequent hot-reload may
85
+ // re-invoke register() with mode='tool-discovery'. In that case we must
86
+ // still re-register tools/hooks so the gateway doesn't drop them.
83
87
  const mode = (api as { registrationMode?: string }).registrationMode;
84
- if (mode && mode !== 'full') {
88
+ if (mode && mode !== 'full' && _fullModeRegistered) {
89
+ logger.info(`[entry] Re-registering in ${mode} mode (full mode preserved)`);
90
+ // Fall through — re-register tools to avoid gateway dropping them.
91
+ } else if (mode && mode !== 'full') {
85
92
  logger.info(`[entry] Setup-only mode (registrationMode=${mode}), skipping tools/hooks`);
86
93
  return;
87
94
  }
@@ -116,6 +123,7 @@ export default {
116
123
  });
117
124
 
118
125
  logger.info('[entry] Tools + hooks registered (full mode)');
126
+ _fullModeRegistered = true;
119
127
  },
120
128
  };
121
129
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-openagent",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "OpenAgent channel plugin for OpenClaw",
6
6
  "license": "MIT",
@@ -347,9 +347,9 @@ export function createCallRemoteAgentTool(): ToolDescriptor {
347
347
 
348
348
  // WebUI continuation:声明了 webui 能力的 ServiceAgent 在 succeeded 后会
349
349
  // 在 visible_result.webui_continuation 里返回可继续交互的入口。
350
+ const accessBase = rt.config?.accessApiBase || rt.config?.apiBase;
350
351
  if (result.webuiContinuation?.launchUrl) {
351
352
  const rawLink = result.webuiContinuation.launchUrl;
352
- const accessBase = rt.config?.accessApiBase || rt.config?.apiBase;
353
353
  const link = resolveOasnAccessUrl(accessBase, rawLink);
354
354
  const label = result.webuiContinuation.displayName || 'Open WebUI';
355
355
  content = content.trim()
@@ -378,6 +378,15 @@ export function createCallRemoteAgentTool(): ToolDescriptor {
378
378
  : `Files from remote agent:\n${artifactLines.join('\n')}`;
379
379
  }
380
380
 
381
+ // Resolve OASN-relative URLs in content (remote agent may have embedded
382
+ // /webui/continuations/... or /api/artifacts/... in its output text).
383
+ if (content && accessBase) {
384
+ content = content.replace(
385
+ /(?<![a-zA-Z0-9])(\/webui\/[^\s"')\]]+|\/api\/[^\s"')\]]+)/g,
386
+ (match) => resolveOasnAccessUrl(accessBase, match),
387
+ );
388
+ }
389
+
381
390
  // OASN 的非文本 artifacts 暂继续复用 ArtifactRef.contentUrl 作为 mediaUrls。
382
391
  reply = {
383
392
  status: 'complete',
@@ -2,7 +2,7 @@
2
2
  * OpenAgent UI Override — Auto-generated, do not edit directly!
3
3
  *
4
4
  * Source: src/plugin-ui/modules/
5
- * Built: 2026-06-25T07:10:51.968Z
5
+ * Built: 2026-06-25T08:37:18.204Z
6
6
  *
7
7
  * To modify, edit the source modules and run:
8
8
  * npm run build:override
@@ -26,6 +26,15 @@ const AUTH_UPSTREAM_TIM = 'https://auth.ai-talk.live';
26
26
  const PROXY_TIMEOUT_MS = 15_000;
27
27
  const MAX_BODY_BYTES = 64 * 1024; // 64KB — tagline API body is ~100 bytes
28
28
 
29
+ /**
30
+ * 获取当前 OASN access API base URL,用于解析响应中的相对路径。
31
+ */
32
+ function getOasnAccessBase(): string {
33
+ const rt = registry.getDefault();
34
+ if (rt?.transportType !== 'oasn') return '';
35
+ return (rt.config?.accessApiBase || rt.config?.apiBase || '').replace(/\/+$/, '');
36
+ }
37
+
29
38
  /**
30
39
  * 根据当前 active runtime 的 transport 类型决定上游。
31
40
  *
@@ -143,7 +152,31 @@ export function createAuthProxyHandler() {
143
152
  const contentType = upstream.headers.get('content-type');
144
153
  if (contentType) res.setHeader('Content-Type', contentType);
145
154
 
146
- const buf = Buffer.from(await upstream.arrayBuffer());
155
+ let buf = Buffer.from(await upstream.arrayBuffer());
156
+
157
+ // Resolve OASN-relative URLs in GET invocation responses so the
158
+ // frontend receives absolute URLs (launch_url, content_url, etc.).
159
+ if (method === 'GET' && /^\/api\/invocations\/[^/]+$/.test(targetPath) && buf.length > 0) {
160
+ try {
161
+ const isJson = contentType?.includes('json');
162
+ if (isJson) {
163
+ const oasnBase = getOasnAccessBase();
164
+ if (oasnBase) {
165
+ const text = buf.toString('utf-8');
166
+ const resolved = text.replace(
167
+ /"(launch_url|content_url)":"(\/[^"]+)"/g,
168
+ (_m: string, key: string, path: string) => {
169
+ try {
170
+ return `"${key}":"${new URL(path, oasnBase).toString()}"`;
171
+ } catch { return _m; }
172
+ },
173
+ );
174
+ buf = Buffer.from(resolved, 'utf-8');
175
+ }
176
+ }
177
+ } catch { /* best-effort; don't break proxy on parse errors */ }
178
+ }
179
+
147
180
  res.end(buf);
148
181
 
149
182
  logger.info(`[proxy] ${method} ${url.pathname} → ${upstream.status} (${buf.length} bytes)`);