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 +10 -1
- package/dist/src/app/remote-agent-tool.js +6 -1
- package/dist/src/plugin-ui/assets/openagent-override.js +1 -1
- package/dist/src/proxy/auth-proxy.js +33 -1
- package/index.ts +9 -1
- package/package.json +1 -1
- package/src/app/remote-agent-tool.ts +10 -1
- package/src/plugin-ui/assets/openagent-override.js +1 -1
- package/src/proxy/auth-proxy.ts +34 -1
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',
|
|
@@ -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
|
-
|
|
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
|
@@ -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',
|
package/src/proxy/auth-proxy.ts
CHANGED
|
@@ -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
|
-
|
|
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)`);
|