openclaw-openagent 1.0.5 → 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
|
|
@@ -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
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)`);
|