openclaw-openagent 1.0.11 → 1.0.13
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 +3 -1
- package/dist/src/app/remote-agent-tool.js +110 -14
- package/dist/src/app/types.d.ts +2 -2
- package/dist/src/config/config-schema.d.ts +9 -0
- package/dist/src/config/config-schema.js +6 -0
- package/dist/src/plugin-ui/adapters/adapters/oc-2026-04.js +103 -0
- package/dist/src/plugin-ui/adapters/adapters/oc-2026-05.js +125 -0
- package/dist/src/plugin-ui/adapters/adapters/oc-2026-06.js +125 -0
- package/dist/src/plugin-ui/adapters/adapters/oc-unknown.js +48 -0
- package/dist/src/plugin-ui/adapters/oc-2026-04.js +103 -0
- package/dist/src/plugin-ui/adapters/oc-2026-05.js +125 -0
- package/dist/src/plugin-ui/adapters/oc-2026-06.js +125 -0
- package/dist/src/plugin-ui/adapters/oc-unknown.js +48 -0
- package/dist/src/plugin-ui/assets/openagent-override.js +1129 -273
- package/dist/src/plugin-ui/index.d.ts +1 -1
- package/dist/src/plugin-ui/index.js +2 -2
- package/dist/src/plugin-ui/ui-extension-loader/index.d.ts +2 -1
- package/dist/src/plugin-ui/ui-extension-loader/index.js +5 -5
- package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +75 -8
- package/dist/src/plugin-ui/ui-extension-loader/types.d.ts +4 -1
- package/dist/src/proxy/auth-proxy.js +5 -0
- package/dist/src/runtime/update-checker.d.ts +18 -0
- package/dist/src/runtime/update-checker.js +253 -0
- package/dist/src/state/store.d.ts +21 -0
- package/dist/src/state/store.js +54 -0
- package/dist/src/transport/oasn/oasn-invocation.d.ts +3 -0
- package/dist/src/transport/oasn/oasn-invocation.js +28 -12
- package/dist/src/transport/oasn/oasn-types.d.ts +8 -4
- package/index.ts +4 -1
- package/package.json +4 -3
- package/src/app/remote-agent-tool.ts +131 -16
- package/src/app/types.ts +2 -2
- package/src/config/config-schema.ts +6 -0
- package/src/plugin-ui/adapters/oc-2026-04.js +103 -0
- package/src/plugin-ui/adapters/oc-2026-05.js +125 -0
- package/src/plugin-ui/adapters/oc-2026-06.js +125 -0
- package/src/plugin-ui/adapters/oc-unknown.js +48 -0
- package/src/plugin-ui/assets/openagent-override.js +1129 -273
- package/src/plugin-ui/build.cjs +249 -38
- package/src/plugin-ui/index.ts +2 -2
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +102 -12
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +105 -3
- package/src/plugin-ui/modules/agent-book/panel/styles.js +54 -48
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +4 -2
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +4 -1
- package/src/plugin-ui/modules/agent-book/scanner.js +17 -5
- package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +12 -1
- package/src/plugin-ui/modules/loader/bootstrap.js +95 -0
- package/src/plugin-ui/modules/loader/shared-state.js +244 -20
- package/src/plugin-ui/modules/remote-agent/execution-card.js +54 -16
- package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +5 -23
- package/src/plugin-ui/modules/remote-agent/output-card.js +13 -7
- package/src/plugin-ui/modules/remote-agent/render-hooks.js +53 -41
- package/src/plugin-ui/modules/remote-agent/styles.js +238 -89
- package/src/plugin-ui/modules/remote-agent/tool-card-model.js +72 -4
- package/src/plugin-ui/postinstall-deploy.cjs +52 -0
- package/src/plugin-ui/ui-extension-loader/index.ts +6 -6
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +81 -9
- package/src/plugin-ui/ui-extension-loader/types.ts +5 -1
- package/src/proxy/auth-proxy.ts +5 -0
- package/src/runtime/update-checker.ts +286 -0
- package/src/state/store.ts +80 -0
- package/src/transport/oasn/oasn-invocation.ts +47 -12
- package/src/transport/oasn/oasn-types.ts +6 -2
- package/src/types/openclaw-plugin-sdk-media-store.d.ts +9 -0
|
@@ -11,6 +11,6 @@ import { loadUIExtensions } from './ui-extension-loader/index.js';
|
|
|
11
11
|
* 由 index.ts register() 调用。内部通过 Manifest Reconciler
|
|
12
12
|
* 自动完成扩展点的 apply / revert / skip。
|
|
13
13
|
*/
|
|
14
|
-
export async function injectControlUiOverride() {
|
|
15
|
-
await loadUIExtensions();
|
|
14
|
+
export async function injectControlUiOverride(hostVersion) {
|
|
15
|
+
await loadUIExtensions({ hostVersion });
|
|
16
16
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* 每次 Gateway 启动时调用 loadUIExtensions()。
|
|
10
10
|
*/
|
|
11
|
+
import type { UIExtensionContext } from './types.js';
|
|
11
12
|
/**
|
|
12
13
|
* 加载全部 UI 扩展。
|
|
13
14
|
*
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
* 4. 共享文件扩展按组处理:任何一个变更 → 恢复 backup → 重新 apply 全组
|
|
19
20
|
* 5. 写入新 manifest
|
|
20
21
|
*/
|
|
21
|
-
export declare function loadUIExtensions(): Promise<void>;
|
|
22
|
+
export declare function loadUIExtensions(context?: UIExtensionContext): Promise<void>;
|
|
22
23
|
/**
|
|
23
24
|
* 卸载全部 UI 扩展(插件卸载时调用)。
|
|
24
25
|
*
|
|
@@ -46,7 +46,7 @@ oldVersion // null = manifest 中不存在
|
|
|
46
46
|
* 4. 共享文件扩展按组处理:任何一个变更 → 恢复 backup → 重新 apply 全组
|
|
47
47
|
* 5. 写入新 manifest
|
|
48
48
|
*/
|
|
49
|
-
export async function loadUIExtensions() {
|
|
49
|
+
export async function loadUIExtensions(context = {}) {
|
|
50
50
|
const controlUiDir = resolveControlUiDir();
|
|
51
51
|
if (!controlUiDir) {
|
|
52
52
|
logger.warn('[ui-ext] control-ui directory not found, skipping');
|
|
@@ -112,7 +112,7 @@ export async function loadUIExtensions() {
|
|
|
112
112
|
safeRevert(ext, controlUiDir);
|
|
113
113
|
}
|
|
114
114
|
logger.debug(`[ui-ext] ${ext.id}: apply v${ext.version}`);
|
|
115
|
-
if (safeApply(ext, controlUiDir)) {
|
|
115
|
+
if (safeApply(ext, controlUiDir, context)) {
|
|
116
116
|
newExtensions[ext.id] = { version: ext.version };
|
|
117
117
|
applied++;
|
|
118
118
|
}
|
|
@@ -171,7 +171,7 @@ export async function loadUIExtensions() {
|
|
|
171
171
|
// 重新 apply 全组中所有应保留的扩展
|
|
172
172
|
for (const ext of group) {
|
|
173
173
|
logger.debug(`[ui-ext] ${ext.id}: apply v${ext.version} (group rebuild)`);
|
|
174
|
-
if (safeApply(ext, controlUiDir)) {
|
|
174
|
+
if (safeApply(ext, controlUiDir, context)) {
|
|
175
175
|
newExtensions[ext.id] = { version: ext.version };
|
|
176
176
|
applied++;
|
|
177
177
|
}
|
|
@@ -212,9 +212,9 @@ export async function unloadUIExtensions() {
|
|
|
212
212
|
logger.info('[ui-ext] All UI extensions unloaded');
|
|
213
213
|
}
|
|
214
214
|
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
215
|
-
function safeApply(ext, controlUiDir) {
|
|
215
|
+
function safeApply(ext, controlUiDir, context) {
|
|
216
216
|
try {
|
|
217
|
-
return ext.apply(controlUiDir);
|
|
217
|
+
return ext.apply(controlUiDir, context);
|
|
218
218
|
}
|
|
219
219
|
catch (err) {
|
|
220
220
|
logger.error(`[ui-ext] Failed to apply '${ext.id}': ${err.message}`);
|
|
@@ -16,9 +16,74 @@ import { makeMarker, hasMarker } from './types.js';
|
|
|
16
16
|
const OVERRIDE_ASSET_URL = new URL('../assets/openagent-override.js', import.meta.url);
|
|
17
17
|
const ICON_ASSET_URL = new URL('../assets/icon.png', import.meta.url);
|
|
18
18
|
const BG_ASSET_URL = new URL('../assets/bg.png', import.meta.url);
|
|
19
|
-
|
|
19
|
+
const HOST_ADAPTER_URLS = {
|
|
20
|
+
'oc-2026-04': new URL('../adapters/oc-2026-04.js', import.meta.url),
|
|
21
|
+
'oc-2026-05': new URL('../adapters/oc-2026-05.js', import.meta.url),
|
|
22
|
+
'oc-2026-06': new URL('../adapters/oc-2026-06.js', import.meta.url),
|
|
23
|
+
'oc-unknown': new URL('../adapters/oc-unknown.js', import.meta.url),
|
|
24
|
+
};
|
|
25
|
+
const HOST_ADAPTER_START = '/*__OPENAGENT_HOST_ADAPTER_START__*/';
|
|
26
|
+
const HOST_ADAPTER_END = '/*__OPENAGENT_HOST_ADAPTER_END__*/';
|
|
27
|
+
const HOST_ADAPTER_BLOCK_RE = /\/\*__OPENAGENT_HOST_ADAPTER_START__\*\/[\s\S]*?\/\*__OPENAGENT_HOST_ADAPTER_END__\*\//;
|
|
28
|
+
const BUILD_ID_TOKEN = "buildId: '__OPENAGENT_BUILD_ID__'";
|
|
29
|
+
const BUILD_ID_ASSIGNMENT_RE = /buildId:\s*(?:"[^"]*"|'[^']*')/;
|
|
30
|
+
function normalizeHostVersion(hostVersion) {
|
|
31
|
+
const value = String(hostVersion || '').trim();
|
|
32
|
+
if (!value || value === 'unknown')
|
|
33
|
+
return '';
|
|
34
|
+
const match = value.match(/20\d{2}\.\d{1,2}\.\d{1,2}(?:-[A-Za-z0-9._-]+)?/);
|
|
35
|
+
return match ? match[0] : '';
|
|
36
|
+
}
|
|
37
|
+
function resolveHostAdapterId(hostVersion) {
|
|
38
|
+
const version = normalizeHostVersion(hostVersion);
|
|
39
|
+
const parts = version.split('.');
|
|
40
|
+
const year = Number.parseInt(parts[0], 10);
|
|
41
|
+
const month = Number.parseInt(parts[1], 10);
|
|
42
|
+
if (year === 2026 && month === 4)
|
|
43
|
+
return 'oc-2026-04';
|
|
44
|
+
if (year === 2026 && month === 5)
|
|
45
|
+
return 'oc-2026-05';
|
|
46
|
+
if (year === 2026 && month === 6)
|
|
47
|
+
return 'oc-2026-06';
|
|
48
|
+
return 'oc-unknown';
|
|
49
|
+
}
|
|
50
|
+
function getHostAdapterBlock(hostVersion) {
|
|
51
|
+
let adapterId = resolveHostAdapterId(hostVersion);
|
|
52
|
+
let adapterUrl = HOST_ADAPTER_URLS[adapterId];
|
|
53
|
+
if (!adapterUrl || !existsSync(adapterUrl)) {
|
|
54
|
+
adapterId = 'oc-unknown';
|
|
55
|
+
adapterUrl = HOST_ADAPTER_URLS[adapterId];
|
|
56
|
+
}
|
|
57
|
+
const adapterSource = readFileSync(adapterUrl, 'utf-8').trimEnd();
|
|
58
|
+
return [
|
|
59
|
+
HOST_ADAPTER_START,
|
|
60
|
+
`// HOST ADAPTER: ${adapterId}`,
|
|
61
|
+
adapterSource,
|
|
62
|
+
HOST_ADAPTER_END,
|
|
63
|
+
].join('\n');
|
|
64
|
+
}
|
|
65
|
+
function injectHostAdapter(source, hostVersion) {
|
|
66
|
+
const block = getHostAdapterBlock(hostVersion);
|
|
67
|
+
return HOST_ADAPTER_BLOCK_RE.test(source)
|
|
68
|
+
? source.replace(HOST_ADAPTER_BLOCK_RE, block)
|
|
69
|
+
: source;
|
|
70
|
+
}
|
|
71
|
+
function injectBuildId(source) {
|
|
72
|
+
const normalized = source.replace(BUILD_ID_ASSIGNMENT_RE, BUILD_ID_TOKEN);
|
|
73
|
+
const buildId = createHash('sha256').update(normalized).digest('hex').slice(0, 12);
|
|
74
|
+
return normalized.replace(BUILD_ID_TOKEN, `buildId: ${JSON.stringify(buildId)}`);
|
|
75
|
+
}
|
|
76
|
+
function getOverrideAssetContent(context) {
|
|
77
|
+
let source = readFileSync(OVERRIDE_ASSET_URL, 'utf-8');
|
|
78
|
+
const hostVersion = normalizeHostVersion(context?.hostVersion);
|
|
79
|
+
if (hostVersion) {
|
|
80
|
+
source = source.replace("hostVersion: '__OPENCLAW_HOST_VERSION__'", `hostVersion: ${JSON.stringify(hostVersion)}`);
|
|
81
|
+
}
|
|
82
|
+
return injectBuildId(injectHostAdapter(source, hostVersion));
|
|
83
|
+
}
|
|
84
|
+
function getOverrideAssetHash(context) {
|
|
20
85
|
return createHash('sha256')
|
|
21
|
-
.update(
|
|
86
|
+
.update(getOverrideAssetContent(context))
|
|
22
87
|
.digest('hex')
|
|
23
88
|
.slice(0, 12);
|
|
24
89
|
}
|
|
@@ -99,21 +164,21 @@ const overrideScript = {
|
|
|
99
164
|
id: 'override-script',
|
|
100
165
|
version: 1,
|
|
101
166
|
alwaysApply: true,
|
|
102
|
-
apply(controlUiDir) {
|
|
167
|
+
apply(controlUiDir, context) {
|
|
103
168
|
if (!existsSync(OVERRIDE_ASSET_URL)) {
|
|
104
169
|
logger.warn('[ui-ext:regex] override-script: asset missing');
|
|
105
170
|
return false;
|
|
106
171
|
}
|
|
107
172
|
const dest = join(controlUiDir, 'openagent-override.js');
|
|
173
|
+
const srcContent = getOverrideAssetContent(context);
|
|
108
174
|
if (existsSync(dest)) {
|
|
109
|
-
const srcContent = readFileSync(OVERRIDE_ASSET_URL, 'utf-8');
|
|
110
175
|
const destContent = readFileSync(dest, 'utf-8');
|
|
111
176
|
if (srcContent === destContent) {
|
|
112
177
|
logger.debug('[ui-ext:regex] override-script: content unchanged, skip');
|
|
113
178
|
return true;
|
|
114
179
|
}
|
|
115
180
|
}
|
|
116
|
-
|
|
181
|
+
writeFileSync(dest, srcContent, 'utf-8');
|
|
117
182
|
logger.info('[ui-ext:regex] override-script: deployed openagent-override.js');
|
|
118
183
|
// Deploy icon.png alongside
|
|
119
184
|
if (existsSync(ICON_ASSET_URL)) {
|
|
@@ -167,7 +232,7 @@ const htmlScriptTag = {
|
|
|
167
232
|
id: 'html-script-tag',
|
|
168
233
|
version: 5,
|
|
169
234
|
alwaysApply: true,
|
|
170
|
-
apply(controlUiDir) {
|
|
235
|
+
apply(controlUiDir, context) {
|
|
171
236
|
const indexPath = join(controlUiDir, 'index.html');
|
|
172
237
|
if (!existsSync(indexPath)) {
|
|
173
238
|
logger.warn('[ui-ext:regex] html-script-tag: index.html not found');
|
|
@@ -176,8 +241,10 @@ const htmlScriptTag = {
|
|
|
176
241
|
let html = readFileSync(indexPath, 'utf-8');
|
|
177
242
|
const htmlMarker = `<!--openagent:${this.id}-->`;
|
|
178
243
|
const markerPattern = /(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)/;
|
|
179
|
-
const overrideHash = getOverrideAssetHash();
|
|
180
|
-
const
|
|
244
|
+
const overrideHash = getOverrideAssetHash(context);
|
|
245
|
+
const hostVersion = normalizeHostVersion(context?.hostVersion);
|
|
246
|
+
const hostVersionAttr = hostVersion ? ` data-openagent-host-version="${hostVersion}"` : '';
|
|
247
|
+
const scriptTag = `${htmlMarker}<script src="./openagent-override.js?v=${overrideHash}"${hostVersionAttr}></script>`;
|
|
181
248
|
const markedScriptPattern = /(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
|
|
182
249
|
const oldScriptPattern = /<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
|
|
183
250
|
if (markerPattern.test(html)) {
|
|
@@ -32,7 +32,7 @@ export interface UIExtension {
|
|
|
32
32
|
* 接收 control-ui 目录路径,内部自行决定改什么文件、怎么改。
|
|
33
33
|
* 返回 true 表示成功,false 表示失败(不写入 manifest,下次重试)。
|
|
34
34
|
*/
|
|
35
|
-
apply: (controlUiDir: string) => boolean;
|
|
35
|
+
apply: (controlUiDir: string, context?: UIExtensionContext) => boolean;
|
|
36
36
|
/**
|
|
37
37
|
* 卸载扩展。
|
|
38
38
|
* 还原 apply 所做的全部修改。
|
|
@@ -40,6 +40,9 @@ export interface UIExtension {
|
|
|
40
40
|
*/
|
|
41
41
|
revert: (controlUiDir: string) => void;
|
|
42
42
|
}
|
|
43
|
+
export interface UIExtensionContext {
|
|
44
|
+
hostVersion?: string;
|
|
45
|
+
}
|
|
43
46
|
/**
|
|
44
47
|
* OpenAgent 标记前缀,注入的代码旁加此注释以标识修改来源。
|
|
45
48
|
* 格式:/*openagent:<extension-id>* /
|
|
@@ -17,6 +17,7 @@ import fs from 'node:fs';
|
|
|
17
17
|
import nodePath from 'node:path';
|
|
18
18
|
import { logger } from '../util/logger.js';
|
|
19
19
|
import { registry } from '../runtime/registry.js';
|
|
20
|
+
import { getOpenagentUpdateStatus } from '../runtime/update-checker.js';
|
|
20
21
|
import { isUnconfiguredOasnApiBase } from '../auth/config.js';
|
|
21
22
|
import { resolveOasnRelativeUrl } from '../util/url-resolver.js';
|
|
22
23
|
/** TIM 路径默认上游(兼容旧版) */
|
|
@@ -90,6 +91,10 @@ export function createAuthProxyHandler() {
|
|
|
90
91
|
if (targetPath.startsWith('/media/')) {
|
|
91
92
|
return serveLocalMedia(targetPath, res);
|
|
92
93
|
}
|
|
94
|
+
if (targetPath === '/update-status') {
|
|
95
|
+
writeJson(res, 200, getOpenagentUpdateStatus());
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
93
98
|
// v3.9+ 双轨抽象:上游按当前 transport 切换(OASN → api.oasn.ai;TIM → auth.ai-talk.live)
|
|
94
99
|
const method = req.method ?? 'GET';
|
|
95
100
|
const rt = registry.getDefault();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type OpenagentUpdatePolicy = 'notify' | 'auto' | 'off';
|
|
2
|
+
export type OpenagentUpdateStatus = {
|
|
3
|
+
checked: boolean;
|
|
4
|
+
checking: boolean;
|
|
5
|
+
currentVersion: string;
|
|
6
|
+
latestVersion: string;
|
|
7
|
+
distTag: string;
|
|
8
|
+
updateAvailable: boolean;
|
|
9
|
+
policy: OpenagentUpdatePolicy;
|
|
10
|
+
installCommand: string;
|
|
11
|
+
autoUpdateStarted: boolean;
|
|
12
|
+
lastCheckedAt?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function getOpenagentUpdateStatus(): OpenagentUpdateStatus;
|
|
16
|
+
export declare function startOpenagentUpdateChecker(): void;
|
|
17
|
+
export declare function runOpenagentUpdateCheck(): Promise<OpenagentUpdateStatus>;
|
|
18
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { logger } from '../util/logger.js';
|
|
7
|
+
const PLUGIN_PACKAGE = 'openclaw-openagent';
|
|
8
|
+
const CLI_PACKAGE = 'openclaw-openagent-cli';
|
|
9
|
+
const DEFAULT_DIST_TAG = 'latest';
|
|
10
|
+
const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
11
|
+
const CHECK_DELAY_MS = 3_000;
|
|
12
|
+
let checkerStarted = false;
|
|
13
|
+
const updateStatus = {
|
|
14
|
+
checked: false,
|
|
15
|
+
checking: false,
|
|
16
|
+
currentVersion: '',
|
|
17
|
+
latestVersion: '',
|
|
18
|
+
distTag: DEFAULT_DIST_TAG,
|
|
19
|
+
updateAvailable: false,
|
|
20
|
+
policy: 'notify',
|
|
21
|
+
installCommand: `npx -y ${CLI_PACKAGE}`,
|
|
22
|
+
autoUpdateStarted: false,
|
|
23
|
+
};
|
|
24
|
+
export function getOpenagentUpdateStatus() {
|
|
25
|
+
return { ...updateStatus };
|
|
26
|
+
}
|
|
27
|
+
export function startOpenagentUpdateChecker() {
|
|
28
|
+
if (checkerStarted)
|
|
29
|
+
return;
|
|
30
|
+
checkerStarted = true;
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
void runOpenagentUpdateCheck().catch((err) => {
|
|
33
|
+
updateStatus.checked = true;
|
|
34
|
+
updateStatus.checking = false;
|
|
35
|
+
updateStatus.error = err.message;
|
|
36
|
+
updateStatus.lastCheckedAt = new Date().toISOString();
|
|
37
|
+
logger.warn(`[update-check] failed: ${err.message}`);
|
|
38
|
+
});
|
|
39
|
+
}, CHECK_DELAY_MS).unref?.();
|
|
40
|
+
}
|
|
41
|
+
export async function runOpenagentUpdateCheck() {
|
|
42
|
+
const config = resolveUpdateConfig();
|
|
43
|
+
updateStatus.policy = config.policy;
|
|
44
|
+
updateStatus.distTag = config.distTag;
|
|
45
|
+
updateStatus.installCommand = buildInstallCommand(config);
|
|
46
|
+
updateStatus.currentVersion = resolveCurrentVersion();
|
|
47
|
+
if (config.policy === 'off') {
|
|
48
|
+
updateStatus.checked = true;
|
|
49
|
+
updateStatus.checking = false;
|
|
50
|
+
updateStatus.updateAvailable = false;
|
|
51
|
+
updateStatus.lastCheckedAt = new Date().toISOString();
|
|
52
|
+
logger.info('[update-check] disabled by policy');
|
|
53
|
+
return getOpenagentUpdateStatus();
|
|
54
|
+
}
|
|
55
|
+
updateStatus.checking = true;
|
|
56
|
+
updateStatus.error = undefined;
|
|
57
|
+
try {
|
|
58
|
+
const latestVersion = await fetchDistTagVersion(config.distTag);
|
|
59
|
+
updateStatus.latestVersion = latestVersion;
|
|
60
|
+
updateStatus.updateAvailable = compareVersions(latestVersion, updateStatus.currentVersion) > 0;
|
|
61
|
+
updateStatus.checked = true;
|
|
62
|
+
updateStatus.checking = false;
|
|
63
|
+
updateStatus.lastCheckedAt = new Date().toISOString();
|
|
64
|
+
if (!updateStatus.updateAvailable) {
|
|
65
|
+
logger.info(`[update-check] up to date: ${PLUGIN_PACKAGE}@${updateStatus.currentVersion}`);
|
|
66
|
+
return getOpenagentUpdateStatus();
|
|
67
|
+
}
|
|
68
|
+
logger.warn(`[update-check] new ${PLUGIN_PACKAGE} version available: `
|
|
69
|
+
+ `${updateStatus.currentVersion} -> ${latestVersion}. Run: ${updateStatus.installCommand}`);
|
|
70
|
+
if (config.policy === 'auto') {
|
|
71
|
+
startAutoUpdate(config);
|
|
72
|
+
}
|
|
73
|
+
return getOpenagentUpdateStatus();
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
updateStatus.checked = true;
|
|
77
|
+
updateStatus.checking = false;
|
|
78
|
+
updateStatus.error = err.message;
|
|
79
|
+
updateStatus.lastCheckedAt = new Date().toISOString();
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function resolveUpdateConfig() {
|
|
84
|
+
const fileConfig = readOpenagentConfig();
|
|
85
|
+
const envPolicy = normalizePolicy(process.env.OPENAGENT_UPDATE_POLICY);
|
|
86
|
+
const autoUpdateEnv = readBool(process.env.OPENAGENT_AUTO_UPDATE ?? process.env.OPENAGENT_PLUGIN_AUTO_UPDATE);
|
|
87
|
+
const checkEnabledEnv = readBool(process.env.OPENAGENT_UPDATE_CHECK);
|
|
88
|
+
const configPolicy = normalizePolicy(readString(fileConfig, 'update_policy', 'updatePolicy'));
|
|
89
|
+
const configAutoUpdate = readBool(readString(fileConfig, 'auto_update', 'autoUpdate'));
|
|
90
|
+
const configUpdateCheck = readBool(readString(fileConfig, 'update_check', 'updateCheck'));
|
|
91
|
+
let policy = configPolicy || 'notify';
|
|
92
|
+
if (configAutoUpdate === true)
|
|
93
|
+
policy = 'auto';
|
|
94
|
+
if (configUpdateCheck === false)
|
|
95
|
+
policy = 'off';
|
|
96
|
+
if (envPolicy)
|
|
97
|
+
policy = envPolicy;
|
|
98
|
+
if (autoUpdateEnv === true)
|
|
99
|
+
policy = 'auto';
|
|
100
|
+
if (checkEnabledEnv === false)
|
|
101
|
+
policy = 'off';
|
|
102
|
+
const distTag = normalizeDistTag(process.env.OPENAGENT_UPDATE_DIST_TAG
|
|
103
|
+
|| readString(fileConfig, 'update_dist_tag', 'updateDistTag')
|
|
104
|
+
|| process.env.OPENAGENT_PLUGIN_DIST_TAG
|
|
105
|
+
|| DEFAULT_DIST_TAG);
|
|
106
|
+
const channel = normalizeDistTag(process.env.OPENAGENT_UPDATE_CHANNEL
|
|
107
|
+
|| readString(fileConfig, 'update_channel', 'updateChannel')
|
|
108
|
+
|| process.env.OPENAGENT_PLUGIN_CHANNEL
|
|
109
|
+
|| '');
|
|
110
|
+
return {
|
|
111
|
+
policy,
|
|
112
|
+
distTag: channel || distTag,
|
|
113
|
+
channel,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function startAutoUpdate(config) {
|
|
117
|
+
if (updateStatus.autoUpdateStarted)
|
|
118
|
+
return;
|
|
119
|
+
updateStatus.autoUpdateStarted = true;
|
|
120
|
+
const npxBin = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
121
|
+
const args = ['-y', CLI_PACKAGE, 'update'];
|
|
122
|
+
if (config.channel) {
|
|
123
|
+
args.push('--channel', config.channel);
|
|
124
|
+
}
|
|
125
|
+
else if (config.distTag && config.distTag !== DEFAULT_DIST_TAG) {
|
|
126
|
+
args.push('--dist-tag', config.distTag);
|
|
127
|
+
}
|
|
128
|
+
logger.warn(`[update-check] auto update started: ${npxBin} ${args.join(' ')}`);
|
|
129
|
+
const child = spawn(npxBin, args, {
|
|
130
|
+
detached: true,
|
|
131
|
+
stdio: 'ignore',
|
|
132
|
+
env: process.env,
|
|
133
|
+
});
|
|
134
|
+
child.unref();
|
|
135
|
+
}
|
|
136
|
+
function buildInstallCommand(config) {
|
|
137
|
+
const parts = ['npx', '-y', CLI_PACKAGE, 'update'];
|
|
138
|
+
if (config.channel) {
|
|
139
|
+
parts.push('--channel', config.channel);
|
|
140
|
+
}
|
|
141
|
+
else if (config.distTag && config.distTag !== DEFAULT_DIST_TAG) {
|
|
142
|
+
parts.push('--dist-tag', config.distTag);
|
|
143
|
+
}
|
|
144
|
+
return parts.join(' ');
|
|
145
|
+
}
|
|
146
|
+
async function fetchDistTagVersion(distTag) {
|
|
147
|
+
if (process.env.OPENAGENT_UPDATE_TEST_VERSION) {
|
|
148
|
+
return process.env.OPENAGENT_UPDATE_TEST_VERSION;
|
|
149
|
+
}
|
|
150
|
+
const registry = (process.env.OPENAGENT_UPDATE_REGISTRY || DEFAULT_REGISTRY).replace(/\/+$/, '');
|
|
151
|
+
const url = `${registry}/${encodeURIComponent(PLUGIN_PACKAGE)}/${encodeURIComponent(distTag)}`;
|
|
152
|
+
const res = await fetch(url, {
|
|
153
|
+
headers: { Accept: 'application/json' },
|
|
154
|
+
signal: AbortSignal.timeout(5_000),
|
|
155
|
+
});
|
|
156
|
+
if (!res.ok) {
|
|
157
|
+
throw new Error(`npm registry ${res.status} for ${PLUGIN_PACKAGE}@${distTag}`);
|
|
158
|
+
}
|
|
159
|
+
const data = await res.json();
|
|
160
|
+
const version = typeof data.version === 'string' ? data.version : '';
|
|
161
|
+
if (!version)
|
|
162
|
+
throw new Error(`npm registry response missing version for ${PLUGIN_PACKAGE}@${distTag}`);
|
|
163
|
+
return version;
|
|
164
|
+
}
|
|
165
|
+
function resolveCurrentVersion() {
|
|
166
|
+
const packageJson = findPackageJson();
|
|
167
|
+
if (!packageJson)
|
|
168
|
+
return '0.0.0';
|
|
169
|
+
try {
|
|
170
|
+
const data = JSON.parse(fs.readFileSync(packageJson, 'utf8'));
|
|
171
|
+
return typeof data.version === 'string' ? data.version : '0.0.0';
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return '0.0.0';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function findPackageJson() {
|
|
178
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
179
|
+
for (let i = 0; i < 8; i += 1) {
|
|
180
|
+
const candidate = path.join(dir, 'package.json');
|
|
181
|
+
try {
|
|
182
|
+
const data = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
183
|
+
if (data.name === PLUGIN_PACKAGE)
|
|
184
|
+
return candidate;
|
|
185
|
+
}
|
|
186
|
+
catch { }
|
|
187
|
+
const parent = path.dirname(dir);
|
|
188
|
+
if (parent === dir)
|
|
189
|
+
break;
|
|
190
|
+
dir = parent;
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
function readOpenagentConfig() {
|
|
195
|
+
const stateDir = process.env.OPENCLAW_STATE_DIR || path.join(os.homedir(), '.openclaw');
|
|
196
|
+
const configPath = path.join(stateDir, 'openclaw.json');
|
|
197
|
+
try {
|
|
198
|
+
const root = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
199
|
+
return root.channels?.openagent || {};
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function readString(obj, ...keys) {
|
|
206
|
+
for (const key of keys) {
|
|
207
|
+
const value = obj[key];
|
|
208
|
+
if (typeof value === 'string' && value.trim())
|
|
209
|
+
return value.trim();
|
|
210
|
+
if (typeof value === 'boolean')
|
|
211
|
+
return value ? 'true' : 'false';
|
|
212
|
+
}
|
|
213
|
+
return '';
|
|
214
|
+
}
|
|
215
|
+
function readBool(value) {
|
|
216
|
+
if (typeof value === 'boolean')
|
|
217
|
+
return value;
|
|
218
|
+
const text = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
219
|
+
if (!text)
|
|
220
|
+
return null;
|
|
221
|
+
if (['1', 'true', 'yes', 'on', 'auto'].includes(text))
|
|
222
|
+
return true;
|
|
223
|
+
if (['0', 'false', 'no', 'off', 'notify'].includes(text))
|
|
224
|
+
return false;
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
function normalizePolicy(value) {
|
|
228
|
+
const text = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
229
|
+
if (text === 'auto' || text === 'notify' || text === 'off')
|
|
230
|
+
return text;
|
|
231
|
+
return '';
|
|
232
|
+
}
|
|
233
|
+
function normalizeDistTag(value) {
|
|
234
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
235
|
+
if (!text)
|
|
236
|
+
return '';
|
|
237
|
+
return /^[A-Za-z0-9._-]+$/.test(text) ? text : '';
|
|
238
|
+
}
|
|
239
|
+
export function compareVersions(a, b) {
|
|
240
|
+
const pa = parseVersion(a);
|
|
241
|
+
const pb = parseVersion(b);
|
|
242
|
+
for (let i = 0; i < 3; i += 1) {
|
|
243
|
+
if (pa[i] !== pb[i])
|
|
244
|
+
return pa[i] - pb[i];
|
|
245
|
+
}
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
function parseVersion(version) {
|
|
249
|
+
const match = String(version || '').match(/(\d+)\.(\d+)\.(\d+)/);
|
|
250
|
+
if (!match)
|
|
251
|
+
return [0, 0, 0];
|
|
252
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
253
|
+
}
|
|
@@ -53,6 +53,21 @@ export interface PendingInvocationRecord {
|
|
|
53
53
|
created_at: number;
|
|
54
54
|
updated_at: number;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* 远程 ServiceAgent 会话续传记录。
|
|
58
|
+
*
|
|
59
|
+
* 一个 OpenClaw 本地会话里,同一个远程 Agent 的第一次 invocation 会返回
|
|
60
|
+
* session_id;后续 mode='session' 的调用应带回这个 session_id,才能保留
|
|
61
|
+
* 远端上下文。account_id + openclaw_session_key + target_agent_id 共同隔离,
|
|
62
|
+
* 避免不同账号、不同本地聊天、不同远程 Agent 之间串 session。
|
|
63
|
+
*/
|
|
64
|
+
export interface RemoteAgentSessionRecord {
|
|
65
|
+
openclaw_session_key: string;
|
|
66
|
+
target_agent_id: string;
|
|
67
|
+
session_id: string;
|
|
68
|
+
created_at: number;
|
|
69
|
+
updated_at: number;
|
|
70
|
+
}
|
|
56
71
|
export declare class StateStore {
|
|
57
72
|
private db;
|
|
58
73
|
private accountId;
|
|
@@ -121,6 +136,12 @@ export declare class StateStore {
|
|
|
121
136
|
* Close the database connection
|
|
122
137
|
*/
|
|
123
138
|
close(): void;
|
|
139
|
+
getRemoteAgentSession(openclawSessionKey: string, targetAgentId: string): string | null;
|
|
140
|
+
upsertRemoteAgentSession(record: {
|
|
141
|
+
openclaw_session_key: string;
|
|
142
|
+
target_agent_id: string;
|
|
143
|
+
session_id: string;
|
|
144
|
+
}): void;
|
|
124
145
|
/**
|
|
125
146
|
* 查 tool_call_id 是否有未完成记录。命中 → 复用其 idempotency_key 防止重复创建 Invocation。
|
|
126
147
|
*
|
package/dist/src/state/store.js
CHANGED
|
@@ -211,6 +211,21 @@ export class StateStore {
|
|
|
211
211
|
this.db.run(`
|
|
212
212
|
CREATE INDEX IF NOT EXISTS idx_pending_invocations_invocation
|
|
213
213
|
ON pending_invocations (account_id, invocation_id)
|
|
214
|
+
`);
|
|
215
|
+
this.db.run(`
|
|
216
|
+
CREATE TABLE IF NOT EXISTS remote_agent_sessions (
|
|
217
|
+
account_id TEXT NOT NULL,
|
|
218
|
+
openclaw_session_key TEXT NOT NULL,
|
|
219
|
+
target_agent_id TEXT NOT NULL,
|
|
220
|
+
session_id TEXT NOT NULL,
|
|
221
|
+
created_at INTEGER NOT NULL DEFAULT 0,
|
|
222
|
+
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
223
|
+
PRIMARY KEY (account_id, openclaw_session_key, target_agent_id)
|
|
224
|
+
)
|
|
225
|
+
`);
|
|
226
|
+
this.db.run(`
|
|
227
|
+
CREATE INDEX IF NOT EXISTS idx_remote_agent_sessions_updated
|
|
228
|
+
ON remote_agent_sessions (account_id, updated_at)
|
|
214
229
|
`);
|
|
215
230
|
}
|
|
216
231
|
// ── Write to disk ──
|
|
@@ -408,6 +423,45 @@ export class StateStore {
|
|
|
408
423
|
}
|
|
409
424
|
}
|
|
410
425
|
// ═════════════════════════════════════════════════════════════════
|
|
426
|
+
// OASN remote_agent_sessions CRUD
|
|
427
|
+
//
|
|
428
|
+
// 保存远端 ServiceAgent 返回的 session_id,用于同一 OpenClaw 会话内后续
|
|
429
|
+
// mode='session' 调用续传。run 模式不读写这张表。
|
|
430
|
+
// ═════════════════════════════════════════════════════════════════
|
|
431
|
+
getRemoteAgentSession(openclawSessionKey, targetAgentId) {
|
|
432
|
+
if (!this.db)
|
|
433
|
+
return null;
|
|
434
|
+
const scopeKey = openclawSessionKey.trim();
|
|
435
|
+
const agentId = targetAgentId.trim();
|
|
436
|
+
if (!scopeKey || !agentId)
|
|
437
|
+
return null;
|
|
438
|
+
const stmt = this.db.prepare(`SELECT session_id
|
|
439
|
+
FROM remote_agent_sessions
|
|
440
|
+
WHERE account_id = ? AND openclaw_session_key = ? AND target_agent_id = ?`);
|
|
441
|
+
stmt.bind([this.accountId, scopeKey, agentId]);
|
|
442
|
+
const row = stmt.step() ? stmt.getAsObject() : null;
|
|
443
|
+
stmt.free();
|
|
444
|
+
const sessionId = row?.['session_id'];
|
|
445
|
+
return typeof sessionId === 'string' && sessionId.trim() ? sessionId : null;
|
|
446
|
+
}
|
|
447
|
+
upsertRemoteAgentSession(record) {
|
|
448
|
+
if (!this.db)
|
|
449
|
+
return;
|
|
450
|
+
const scopeKey = record.openclaw_session_key.trim();
|
|
451
|
+
const agentId = record.target_agent_id.trim();
|
|
452
|
+
const sessionId = record.session_id.trim();
|
|
453
|
+
if (!scopeKey || !agentId || !sessionId)
|
|
454
|
+
return;
|
|
455
|
+
const now = Date.now();
|
|
456
|
+
this.db.run(`INSERT INTO remote_agent_sessions
|
|
457
|
+
(account_id, openclaw_session_key, target_agent_id, session_id, created_at, updated_at)
|
|
458
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
459
|
+
ON CONFLICT(account_id, openclaw_session_key, target_agent_id) DO UPDATE SET
|
|
460
|
+
session_id = excluded.session_id,
|
|
461
|
+
updated_at = excluded.updated_at`, [this.accountId, scopeKey, agentId, sessionId, now, now]);
|
|
462
|
+
this.persist();
|
|
463
|
+
}
|
|
464
|
+
// ═════════════════════════════════════════════════════════════════
|
|
411
465
|
// §11.1 OASN pending_invocations CRUD(OasnInvocation.PendingInvocationStore 适配)
|
|
412
466
|
//
|
|
413
467
|
// 设计依据:docs/specs/2026-06-16-oasn-transport-abstraction-design.md §11.1
|
|
@@ -139,6 +139,9 @@ export declare class OasnInvocation {
|
|
|
139
139
|
private _toWebuiContinuation;
|
|
140
140
|
/** 终态错误专用 TransportError,便于上层 catch */
|
|
141
141
|
private _terminalError;
|
|
142
|
+
private _invocationFailureCode;
|
|
143
|
+
private _invocationFailureMessage;
|
|
144
|
+
private _firstText;
|
|
142
145
|
private _toArtifactRef;
|
|
143
146
|
private _normalizeCreateInvocation;
|
|
144
147
|
/** clamp 轮询间隔到合理范围 */
|