openclaw-openagent 1.0.9 → 1.0.12
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 +1 -1
- package/dist/src/app/remote-agent-tool.js +110 -14
- package/dist/src/app/types.d.ts +2 -2
- 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/bg.png +0 -0
- package/dist/src/plugin-ui/assets/icon.png +0 -0
- package/dist/src/plugin-ui/assets/openagent-override.js +2480 -1004
- 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 +128 -13
- package/dist/src/plugin-ui/ui-extension-loader/types.d.ts +4 -1
- 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 +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +5 -4
- package/src/app/remote-agent-tool.ts +131 -16
- package/src/app/types.ts +2 -2
- 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/bg.png +0 -0
- package/src/plugin-ui/assets/icon.png +0 -0
- package/src/plugin-ui/assets/openagent-override.js +2480 -1004
- package/src/plugin-ui/build.cjs +309 -23
- package/src/plugin-ui/index.ts +2 -2
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +187 -14
- package/src/plugin-ui/modules/agent-book/panel/agent-card.js +26 -10
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +1 -1
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +149 -1
- package/src/plugin-ui/modules/agent-book/panel/styles.js +352 -210
- 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 +5 -0
- package/src/plugin-ui/modules/loader/bootstrap.js +1 -1
- package/src/plugin-ui/modules/loader/shared-state.js +278 -0
- package/src/plugin-ui/modules/remote-agent/execution-card.js +385 -124
- package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +5 -23
- package/src/plugin-ui/modules/remote-agent/output-card.js +97 -31
- package/src/plugin-ui/modules/remote-agent/render-hooks.js +149 -58
- package/src/plugin-ui/modules/remote-agent/styles.js +690 -523
- package/src/plugin-ui/modules/remote-agent/tool-card-model.js +77 -3
- 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 +131 -14
- package/src/plugin-ui/ui-extension-loader/types.ts +5 -1
- 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
|
@@ -15,6 +15,73 @@ function _clInferToolCardKind(card) {
|
|
|
15
15
|
return 'call';
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function _clPickAgentDescription(source) {
|
|
19
|
+
if (!source) return '';
|
|
20
|
+
var keys = [
|
|
21
|
+
'agent_bio',
|
|
22
|
+
'agentBio',
|
|
23
|
+
'bio',
|
|
24
|
+
'description',
|
|
25
|
+
'desc',
|
|
26
|
+
'agent_description',
|
|
27
|
+
'agentDescription',
|
|
28
|
+
'MOM',
|
|
29
|
+
'mom',
|
|
30
|
+
'introduction',
|
|
31
|
+
'summary'
|
|
32
|
+
];
|
|
33
|
+
for (var i = 0; i < keys.length; i++) {
|
|
34
|
+
var value = source[keys[i]];
|
|
35
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
36
|
+
}
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _clIsFailureStatusValue(value) {
|
|
41
|
+
var normalized = String(value || '').trim().toLowerCase();
|
|
42
|
+
return normalized === 'failed'
|
|
43
|
+
|| normalized === 'failure'
|
|
44
|
+
|| normalized === 'error'
|
|
45
|
+
|| normalized === 'errored'
|
|
46
|
+
|| normalized === 'rejected'
|
|
47
|
+
|| normalized === 'cancelled'
|
|
48
|
+
|| normalized === 'canceled';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function _clLooksLikeRemoteFailureText(text) {
|
|
52
|
+
var value = String(text || '').trim();
|
|
53
|
+
if (!value || value.length > 300) return false;
|
|
54
|
+
return /(^|[,。;\s])(调用失败|执行失败|任务失败|远端服务目前连不上|远端服务.*不可用|两次都返回了调用失败)([,。;\s]|$)/.test(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function _clInferToolCardError(card, chunks, rawText) {
|
|
58
|
+
if (chunks && chunks.some(function(c) { return c && c.type === 'error'; })) return true;
|
|
59
|
+
if (!card) return _clLooksLikeRemoteFailureText(rawText);
|
|
60
|
+
if (card.isError === true || card.error === true) return true;
|
|
61
|
+
if (_clIsFailureStatusValue(card.status)
|
|
62
|
+
|| _clIsFailureStatusValue(card.state)
|
|
63
|
+
|| _clIsFailureStatusValue(card.resultStatus)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (card.error && typeof card.error === 'object') return true;
|
|
67
|
+
return _clLooksLikeRemoteFailureText(rawText);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function _clApplyAgentIdentityToModel(model) {
|
|
71
|
+
if (!model || typeof CL === 'undefined' || !CL || typeof CL.getAgentIdentity !== 'function') return model;
|
|
72
|
+
var identity = CL.getAgentIdentity(model.agentId, model.agentName);
|
|
73
|
+
if (!identity) return model;
|
|
74
|
+
model.agentId = model.agentId || identity.id || identity.agent_id;
|
|
75
|
+
if (!model.agentName || model.agentName === 'Remote Agent') {
|
|
76
|
+
model.agentName = identity.name || model.agentName;
|
|
77
|
+
}
|
|
78
|
+
model.agentAvatar = model.agentAvatar || identity.avatar || identity.avatar_url || identity.avatarUrl || null;
|
|
79
|
+
if (!model.agentBio) {
|
|
80
|
+
model.agentBio = _clPickAgentDescription(identity);
|
|
81
|
+
}
|
|
82
|
+
return model;
|
|
83
|
+
}
|
|
84
|
+
|
|
18
85
|
function _clParseToolCardModel(card) {
|
|
19
86
|
var inferredKind = _clInferToolCardKind(card);
|
|
20
87
|
|
|
@@ -23,6 +90,8 @@ function _clParseToolCardModel(card) {
|
|
|
23
90
|
var agentId = args.agent_id || args.agentId || null;
|
|
24
91
|
var agentName = args.agent_name || args.agentName || agentId || 'Remote Agent';
|
|
25
92
|
var agentAvatar = args.agent_avatar || args.agentAvatar || args.avatar || args.avatar_url || args.avatarUrl || null;
|
|
93
|
+
var agentBio = _clPickAgentDescription(args);
|
|
94
|
+
var agentStars = args.agent_stars || args.agentStars || args.stars || args.claws || '3.2万';
|
|
26
95
|
var taskText = args.task || args.message || args.instruction || '';
|
|
27
96
|
|
|
28
97
|
// 从 text 解析思考步骤 / 最终文本
|
|
@@ -32,22 +101,27 @@ function _clParseToolCardModel(card) {
|
|
|
32
101
|
rawText: rawText
|
|
33
102
|
});
|
|
34
103
|
var hasOutput = inferredKind === 'result' && rawText.trim().length > 0;
|
|
35
|
-
var isError =
|
|
104
|
+
var isError = _clInferToolCardError(card, chunks, rawText);
|
|
36
105
|
|
|
37
106
|
var model = {
|
|
38
107
|
agentId: agentId,
|
|
39
108
|
agentName: agentName,
|
|
40
109
|
agentAvatar: agentAvatar,
|
|
110
|
+
agentBio: agentBio,
|
|
111
|
+
agentStars: agentStars,
|
|
41
112
|
taskText: taskText,
|
|
42
113
|
toolCallId: card.toolCallId || '',
|
|
43
|
-
status: hasOutput ? 'completed' : 'executing',
|
|
114
|
+
status: isError ? 'failed' : (hasOutput ? 'completed' : 'executing'),
|
|
44
115
|
rawText: rawText,
|
|
116
|
+
text: rawText,
|
|
117
|
+
outputText: rawText,
|
|
45
118
|
chunks: chunks,
|
|
46
119
|
isError: isError,
|
|
47
120
|
inferredKind: inferredKind,
|
|
48
121
|
};
|
|
122
|
+
_clApplyAgentIdentityToModel(model);
|
|
49
123
|
|
|
50
|
-
console.log('[cl-model] parsed:', 'agent=' + agentName,
|
|
124
|
+
console.log('[cl-model] parsed:', 'agent=' + model.agentName,
|
|
51
125
|
'status=' + model.status,
|
|
52
126
|
'chunks=' + chunks.length,
|
|
53
127
|
'error=' + isError);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Best-effort deploy for npm install/upgrade.
|
|
4
|
+
*
|
|
5
|
+
* The browser cache strategy depends on Control UI index.html carrying the
|
|
6
|
+
* latest openagent-override.js?v=<hash>. Running the override builder here
|
|
7
|
+
* updates reachable OpenClaw Control UI directories immediately after the npm
|
|
8
|
+
* package changes, without making npm install fail if OpenClaw is not present.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { spawnSync } = require('child_process');
|
|
14
|
+
|
|
15
|
+
function log(message) {
|
|
16
|
+
console.log('[openagent postinstall] ' + message);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
if (process.env.OPENAGENT_SKIP_POSTINSTALL_DEPLOY === '1') {
|
|
21
|
+
log('skipped by OPENAGENT_SKIP_POSTINSTALL_DEPLOY=1');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const buildScript = path.join(__dirname, 'build.cjs');
|
|
26
|
+
if (!fs.existsSync(buildScript)) {
|
|
27
|
+
log('build script not found, skipped');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const result = spawnSync(process.execPath, [buildScript], {
|
|
32
|
+
cwd: path.join(__dirname, '..', '..'),
|
|
33
|
+
env: {
|
|
34
|
+
...process.env,
|
|
35
|
+
OPENAGENT_POSTINSTALL_DEPLOY: '1',
|
|
36
|
+
},
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (result.error) {
|
|
41
|
+
log('deploy skipped: ' + result.error.message);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (result.status !== 0) {
|
|
45
|
+
log('deploy exited with code ' + result.status + ' (ignored)');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
log('deployed latest Control UI override where reachable');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
main();
|
|
@@ -17,7 +17,7 @@ import { restoreFile } from './backup.js';
|
|
|
17
17
|
import { UI_EXTENSIONS } from './registry-regex.js';
|
|
18
18
|
import { REMOVED_UI_EXTENSIONS } from './removed-extensions.js';
|
|
19
19
|
import type { ManifestData } from './manifest.js';
|
|
20
|
-
import type { UIExtension } from './types.js';
|
|
20
|
+
import type { UIExtension, UIExtensionContext } from './types.js';
|
|
21
21
|
import type { RemovedUIExtension, RemovedRevertResult } from './removed-extensions.js';
|
|
22
22
|
|
|
23
23
|
// ── 注入策略:正则模式链(无需 .map,21 版本全仿真验证通过)──────────────────
|
|
@@ -53,7 +53,7 @@ function needsChange(
|
|
|
53
53
|
* 4. 共享文件扩展按组处理:任何一个变更 → 恢复 backup → 重新 apply 全组
|
|
54
54
|
* 5. 写入新 manifest
|
|
55
55
|
*/
|
|
56
|
-
export async function loadUIExtensions(): Promise<void> {
|
|
56
|
+
export async function loadUIExtensions(context: UIExtensionContext = {}): Promise<void> {
|
|
57
57
|
const controlUiDir = resolveControlUiDir();
|
|
58
58
|
|
|
59
59
|
if (!controlUiDir) {
|
|
@@ -134,7 +134,7 @@ export async function loadUIExtensions(): Promise<void> {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
logger.debug(`[ui-ext] ${ext.id}: apply v${ext.version}`);
|
|
137
|
-
if (safeApply(ext, controlUiDir)) {
|
|
137
|
+
if (safeApply(ext, controlUiDir, context)) {
|
|
138
138
|
newExtensions[ext.id] = { version: ext.version };
|
|
139
139
|
applied++;
|
|
140
140
|
}
|
|
@@ -200,7 +200,7 @@ export async function loadUIExtensions(): Promise<void> {
|
|
|
200
200
|
// 重新 apply 全组中所有应保留的扩展
|
|
201
201
|
for (const ext of group) {
|
|
202
202
|
logger.debug(`[ui-ext] ${ext.id}: apply v${ext.version} (group rebuild)`);
|
|
203
|
-
if (safeApply(ext, controlUiDir)) {
|
|
203
|
+
if (safeApply(ext, controlUiDir, context)) {
|
|
204
204
|
newExtensions[ext.id] = { version: ext.version };
|
|
205
205
|
applied++;
|
|
206
206
|
}
|
|
@@ -249,9 +249,9 @@ export async function unloadUIExtensions(): Promise<void> {
|
|
|
249
249
|
|
|
250
250
|
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
251
251
|
|
|
252
|
-
function safeApply(ext: UIExtension, controlUiDir: string): boolean {
|
|
252
|
+
function safeApply(ext: UIExtension, controlUiDir: string, context: UIExtensionContext): boolean {
|
|
253
253
|
try {
|
|
254
|
-
return ext.apply(controlUiDir);
|
|
254
|
+
return ext.apply(controlUiDir, context);
|
|
255
255
|
} catch (err) {
|
|
256
256
|
logger.error(`[ui-ext] Failed to apply '${ext.id}': ${(err as Error).message}`);
|
|
257
257
|
return false;
|
|
@@ -13,15 +13,87 @@ import { logger } from '../../util/logger.js';
|
|
|
13
13
|
import { findFileByPrefix } from './locator.js';
|
|
14
14
|
import { backupFile, restoreFile } from './backup.js';
|
|
15
15
|
import { makeMarker, hasMarker } from './types.js';
|
|
16
|
-
import type { UIExtension } from './types.js';
|
|
16
|
+
import type { UIExtension, UIExtensionContext } from './types.js';
|
|
17
17
|
|
|
18
18
|
// ── 常量 ──────────────────────────────────────────────────────────────────
|
|
19
19
|
|
|
20
20
|
const OVERRIDE_ASSET_URL = new URL('../assets/openagent-override.js', import.meta.url);
|
|
21
|
+
const ICON_ASSET_URL = new URL('../assets/icon.png', import.meta.url);
|
|
22
|
+
const BG_ASSET_URL = new URL('../assets/bg.png', import.meta.url);
|
|
23
|
+
const HOST_ADAPTER_URLS: Record<string, URL> = {
|
|
24
|
+
'oc-2026-04': new URL('../adapters/oc-2026-04.js', import.meta.url),
|
|
25
|
+
'oc-2026-05': new URL('../adapters/oc-2026-05.js', import.meta.url),
|
|
26
|
+
'oc-2026-06': new URL('../adapters/oc-2026-06.js', import.meta.url),
|
|
27
|
+
'oc-unknown': new URL('../adapters/oc-unknown.js', import.meta.url),
|
|
28
|
+
};
|
|
29
|
+
const HOST_ADAPTER_START = '/*__OPENAGENT_HOST_ADAPTER_START__*/';
|
|
30
|
+
const HOST_ADAPTER_END = '/*__OPENAGENT_HOST_ADAPTER_END__*/';
|
|
31
|
+
const HOST_ADAPTER_BLOCK_RE = /\/\*__OPENAGENT_HOST_ADAPTER_START__\*\/[\s\S]*?\/\*__OPENAGENT_HOST_ADAPTER_END__\*\//;
|
|
32
|
+
const BUILD_ID_TOKEN = "buildId: '__OPENAGENT_BUILD_ID__'";
|
|
33
|
+
const BUILD_ID_ASSIGNMENT_RE = /buildId:\s*(?:"[^"]*"|'[^']*')/;
|
|
34
|
+
|
|
35
|
+
function normalizeHostVersion(hostVersion: string | undefined): string {
|
|
36
|
+
const value = String(hostVersion || '').trim();
|
|
37
|
+
if (!value || value === 'unknown') return '';
|
|
38
|
+
const match = value.match(/20\d{2}\.\d{1,2}\.\d{1,2}(?:-[A-Za-z0-9._-]+)?/);
|
|
39
|
+
return match ? match[0] : '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveHostAdapterId(hostVersion: string | undefined): string {
|
|
43
|
+
const version = normalizeHostVersion(hostVersion);
|
|
44
|
+
const parts = version.split('.');
|
|
45
|
+
const year = Number.parseInt(parts[0], 10);
|
|
46
|
+
const month = Number.parseInt(parts[1], 10);
|
|
47
|
+
if (year === 2026 && month === 4) return 'oc-2026-04';
|
|
48
|
+
if (year === 2026 && month === 5) return 'oc-2026-05';
|
|
49
|
+
if (year === 2026 && month === 6) return 'oc-2026-06';
|
|
50
|
+
return 'oc-unknown';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getHostAdapterBlock(hostVersion: string | undefined): string {
|
|
54
|
+
let adapterId = resolveHostAdapterId(hostVersion);
|
|
55
|
+
let adapterUrl = HOST_ADAPTER_URLS[adapterId];
|
|
56
|
+
if (!adapterUrl || !existsSync(adapterUrl)) {
|
|
57
|
+
adapterId = 'oc-unknown';
|
|
58
|
+
adapterUrl = HOST_ADAPTER_URLS[adapterId];
|
|
59
|
+
}
|
|
60
|
+
const adapterSource = readFileSync(adapterUrl, 'utf-8').trimEnd();
|
|
61
|
+
return [
|
|
62
|
+
HOST_ADAPTER_START,
|
|
63
|
+
`// HOST ADAPTER: ${adapterId}`,
|
|
64
|
+
adapterSource,
|
|
65
|
+
HOST_ADAPTER_END,
|
|
66
|
+
].join('\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function injectHostAdapter(source: string, hostVersion: string | undefined): string {
|
|
70
|
+
const block = getHostAdapterBlock(hostVersion);
|
|
71
|
+
return HOST_ADAPTER_BLOCK_RE.test(source)
|
|
72
|
+
? source.replace(HOST_ADAPTER_BLOCK_RE, block)
|
|
73
|
+
: source;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function injectBuildId(source: string): string {
|
|
77
|
+
const normalized = source.replace(BUILD_ID_ASSIGNMENT_RE, BUILD_ID_TOKEN);
|
|
78
|
+
const buildId = createHash('sha256').update(normalized).digest('hex').slice(0, 12);
|
|
79
|
+
return normalized.replace(BUILD_ID_TOKEN, `buildId: ${JSON.stringify(buildId)}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getOverrideAssetContent(context?: UIExtensionContext): string {
|
|
83
|
+
let source = readFileSync(OVERRIDE_ASSET_URL, 'utf-8');
|
|
84
|
+
const hostVersion = normalizeHostVersion(context?.hostVersion);
|
|
85
|
+
if (hostVersion) {
|
|
86
|
+
source = source.replace(
|
|
87
|
+
"hostVersion: '__OPENCLAW_HOST_VERSION__'",
|
|
88
|
+
`hostVersion: ${JSON.stringify(hostVersion)}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return injectBuildId(injectHostAdapter(source, hostVersion));
|
|
92
|
+
}
|
|
21
93
|
|
|
22
|
-
function getOverrideAssetHash(): string {
|
|
94
|
+
function getOverrideAssetHash(context?: UIExtensionContext): string {
|
|
23
95
|
return createHash('sha256')
|
|
24
|
-
.update(
|
|
96
|
+
.update(getOverrideAssetContent(context))
|
|
25
97
|
.digest('hex')
|
|
26
98
|
.slice(0, 12);
|
|
27
99
|
}
|
|
@@ -107,22 +179,43 @@ const overrideScript: UIExtension = {
|
|
|
107
179
|
version: 1,
|
|
108
180
|
alwaysApply: true,
|
|
109
181
|
|
|
110
|
-
apply(controlUiDir: string): boolean {
|
|
182
|
+
apply(controlUiDir: string, context?: UIExtensionContext): boolean {
|
|
111
183
|
if (!existsSync(OVERRIDE_ASSET_URL)) {
|
|
112
184
|
logger.warn('[ui-ext:regex] override-script: asset missing');
|
|
113
185
|
return false;
|
|
114
186
|
}
|
|
115
187
|
const dest = join(controlUiDir, 'openagent-override.js');
|
|
188
|
+
const srcContent = getOverrideAssetContent(context);
|
|
116
189
|
if (existsSync(dest)) {
|
|
117
|
-
const srcContent = readFileSync(OVERRIDE_ASSET_URL, 'utf-8');
|
|
118
190
|
const destContent = readFileSync(dest, 'utf-8');
|
|
119
191
|
if (srcContent === destContent) {
|
|
120
192
|
logger.debug('[ui-ext:regex] override-script: content unchanged, skip');
|
|
121
193
|
return true;
|
|
122
194
|
}
|
|
123
195
|
}
|
|
124
|
-
|
|
196
|
+
writeFileSync(dest, srcContent, 'utf-8');
|
|
125
197
|
logger.info('[ui-ext:regex] override-script: deployed openagent-override.js');
|
|
198
|
+
|
|
199
|
+
// Deploy icon.png alongside
|
|
200
|
+
if (existsSync(ICON_ASSET_URL)) {
|
|
201
|
+
const iconDest = join(controlUiDir, 'icon.png');
|
|
202
|
+
try {
|
|
203
|
+
copyFileSync(ICON_ASSET_URL, iconDest);
|
|
204
|
+
logger.info('[ui-ext:regex] override-script: deployed icon.png');
|
|
205
|
+
} catch (err) {
|
|
206
|
+
logger.warn('[ui-ext:regex] override-script: failed to deploy icon.png');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (existsSync(BG_ASSET_URL)) {
|
|
210
|
+
const bgDest = join(controlUiDir, 'bg.png');
|
|
211
|
+
try {
|
|
212
|
+
copyFileSync(BG_ASSET_URL, bgDest);
|
|
213
|
+
logger.info('[ui-ext:regex] override-script: deployed bg.png');
|
|
214
|
+
} catch (err) {
|
|
215
|
+
logger.warn('[ui-ext:regex] override-script: failed to deploy bg.png');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
126
219
|
return true;
|
|
127
220
|
},
|
|
128
221
|
|
|
@@ -132,6 +225,20 @@ const overrideScript: UIExtension = {
|
|
|
132
225
|
unlinkSync(dest);
|
|
133
226
|
logger.info('[ui-ext:regex] override-script: removed');
|
|
134
227
|
}
|
|
228
|
+
const iconDest = join(controlUiDir, 'icon.png');
|
|
229
|
+
if (existsSync(iconDest)) {
|
|
230
|
+
try {
|
|
231
|
+
unlinkSync(iconDest);
|
|
232
|
+
logger.info('[ui-ext:regex] override-script: removed icon.png');
|
|
233
|
+
} catch {}
|
|
234
|
+
}
|
|
235
|
+
const bgDest = join(controlUiDir, 'bg.png');
|
|
236
|
+
if (existsSync(bgDest)) {
|
|
237
|
+
try {
|
|
238
|
+
unlinkSync(bgDest);
|
|
239
|
+
logger.info('[ui-ext:regex] override-script: removed bg.png');
|
|
240
|
+
} catch {}
|
|
241
|
+
}
|
|
135
242
|
},
|
|
136
243
|
};
|
|
137
244
|
|
|
@@ -139,10 +246,10 @@ const overrideScript: UIExtension = {
|
|
|
139
246
|
|
|
140
247
|
const htmlScriptTag: UIExtension = {
|
|
141
248
|
id: 'html-script-tag',
|
|
142
|
-
version:
|
|
249
|
+
version: 5,
|
|
143
250
|
alwaysApply: true,
|
|
144
251
|
|
|
145
|
-
apply(controlUiDir: string): boolean {
|
|
252
|
+
apply(controlUiDir: string, context?: UIExtensionContext): boolean {
|
|
146
253
|
const indexPath = join(controlUiDir, 'index.html');
|
|
147
254
|
if (!existsSync(indexPath)) {
|
|
148
255
|
logger.warn('[ui-ext:regex] html-script-tag: index.html not found');
|
|
@@ -150,12 +257,23 @@ const htmlScriptTag: UIExtension = {
|
|
|
150
257
|
}
|
|
151
258
|
let html = readFileSync(indexPath, 'utf-8');
|
|
152
259
|
const htmlMarker = `<!--openagent:${this.id}-->`;
|
|
153
|
-
const
|
|
154
|
-
const
|
|
260
|
+
const markerPattern = /(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)/;
|
|
261
|
+
const overrideHash = getOverrideAssetHash(context);
|
|
262
|
+
const hostVersion = normalizeHostVersion(context?.hostVersion);
|
|
263
|
+
const hostVersionAttr = hostVersion ? ` data-openagent-host-version="${hostVersion}"` : '';
|
|
264
|
+
const scriptTag = `${htmlMarker}<script src="./openagent-override.js?v=${overrideHash}"${hostVersionAttr}></script>`;
|
|
155
265
|
const markedScriptPattern = /(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
|
|
266
|
+
const oldScriptPattern = /<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
|
|
156
267
|
|
|
157
|
-
if (
|
|
158
|
-
|
|
268
|
+
if (markerPattern.test(html)) {
|
|
269
|
+
let nextHtml = html.replace(markedScriptPattern, `${scriptTag}\n `);
|
|
270
|
+
if (nextHtml === html && !nextHtml.includes('openagent-override.js')) {
|
|
271
|
+
nextHtml = nextHtml.replace(markerPattern, `${scriptTag}\n `);
|
|
272
|
+
} else if (nextHtml === html && nextHtml.includes('openagent-override.js')) {
|
|
273
|
+
nextHtml = nextHtml
|
|
274
|
+
.replace(oldScriptPattern, '')
|
|
275
|
+
.replace(markerPattern, `${scriptTag}\n `);
|
|
276
|
+
}
|
|
159
277
|
if (nextHtml === html) {
|
|
160
278
|
logger.debug('[ui-ext:regex] html-script-tag: already applied');
|
|
161
279
|
} else {
|
|
@@ -164,7 +282,6 @@ const htmlScriptTag: UIExtension = {
|
|
|
164
282
|
}
|
|
165
283
|
return true;
|
|
166
284
|
}
|
|
167
|
-
const oldScriptPattern = /<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
|
|
168
285
|
if (oldScriptPattern.test(html)) {
|
|
169
286
|
html = html.replace(oldScriptPattern, '');
|
|
170
287
|
}
|
|
@@ -354,7 +471,7 @@ const toolcardRender: UIExtension = {
|
|
|
354
471
|
}
|
|
355
472
|
if (toolMessageOk) {
|
|
356
473
|
const toolMessageHook =
|
|
357
|
-
`${marker}/* openagent-media-aware-tool-message */let __openagentToolMsgCard=null;
|
|
474
|
+
`${marker}/* openagent-media-aware-tool-message */let __openagentToolMsgCard=null;if(window.__openagentRenderToolCard){for(let __openagentI=${cardsVar}.length-1;__openagentI>=0&&!__openagentToolMsgCard;__openagentI--){let __openagentCandidate=${cardsVar}[__openagentI];if(__openagentCandidate&&(__openagentCandidate.outputText||__openagentCandidate.text))__openagentToolMsgCard=window.__openagentRenderToolCard(__openagentCandidate,${toolMessageSidebarArg})}for(let __openagentI=0;__openagentI<${cardsVar}.length&&!__openagentToolMsgCard;__openagentI++){__openagentToolMsgCard=window.__openagentRenderToolCard(${cardsVar}[__openagentI],${toolMessageSidebarArg})}}if(__openagentToolMsgCard)return ${toolMessageHtmlVar}\`<div class="\${${chatBubbleClassVar}}">\${__openagentToolMsgCard}</div>\`;`;
|
|
358
475
|
const toolMessageInsertAt = toolMessageFn!.bodyStart + toolMessageReturnIdx;
|
|
359
476
|
content = content.substring(0, toolMessageInsertAt) + toolMessageHook + content.substring(toolMessageInsertAt);
|
|
360
477
|
patchedToolMessageWrapper = true;
|
|
@@ -37,7 +37,7 @@ export interface UIExtension {
|
|
|
37
37
|
* 接收 control-ui 目录路径,内部自行决定改什么文件、怎么改。
|
|
38
38
|
* 返回 true 表示成功,false 表示失败(不写入 manifest,下次重试)。
|
|
39
39
|
*/
|
|
40
|
-
apply: (controlUiDir: string) => boolean;
|
|
40
|
+
apply: (controlUiDir: string, context?: UIExtensionContext) => boolean;
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* 卸载扩展。
|
|
@@ -47,6 +47,10 @@ export interface UIExtension {
|
|
|
47
47
|
revert: (controlUiDir: string) => void;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export interface UIExtensionContext {
|
|
51
|
+
hostVersion?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
/**
|
|
51
55
|
* OpenAgent 标记前缀,注入的代码旁加此注释以标识修改来源。
|
|
52
56
|
* 格式:/*openagent:<extension-id>* /
|
package/src/state/store.ts
CHANGED
|
@@ -67,6 +67,22 @@ export interface PendingInvocationRecord {
|
|
|
67
67
|
updated_at: number;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* 远程 ServiceAgent 会话续传记录。
|
|
72
|
+
*
|
|
73
|
+
* 一个 OpenClaw 本地会话里,同一个远程 Agent 的第一次 invocation 会返回
|
|
74
|
+
* session_id;后续 mode='session' 的调用应带回这个 session_id,才能保留
|
|
75
|
+
* 远端上下文。account_id + openclaw_session_key + target_agent_id 共同隔离,
|
|
76
|
+
* 避免不同账号、不同本地聊天、不同远程 Agent 之间串 session。
|
|
77
|
+
*/
|
|
78
|
+
export interface RemoteAgentSessionRecord {
|
|
79
|
+
openclaw_session_key: string;
|
|
80
|
+
target_agent_id: string;
|
|
81
|
+
session_id: string;
|
|
82
|
+
created_at: number;
|
|
83
|
+
updated_at: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
interface SqlJsDatabase {
|
|
71
87
|
run(sql: string, params?: unknown[]): void;
|
|
72
88
|
prepare(sql: string): SqlJsStatement;
|
|
@@ -291,6 +307,21 @@ export class StateStore {
|
|
|
291
307
|
CREATE INDEX IF NOT EXISTS idx_pending_invocations_invocation
|
|
292
308
|
ON pending_invocations (account_id, invocation_id)
|
|
293
309
|
`);
|
|
310
|
+
this.db.run(`
|
|
311
|
+
CREATE TABLE IF NOT EXISTS remote_agent_sessions (
|
|
312
|
+
account_id TEXT NOT NULL,
|
|
313
|
+
openclaw_session_key TEXT NOT NULL,
|
|
314
|
+
target_agent_id TEXT NOT NULL,
|
|
315
|
+
session_id TEXT NOT NULL,
|
|
316
|
+
created_at INTEGER NOT NULL DEFAULT 0,
|
|
317
|
+
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
318
|
+
PRIMARY KEY (account_id, openclaw_session_key, target_agent_id)
|
|
319
|
+
)
|
|
320
|
+
`);
|
|
321
|
+
this.db.run(`
|
|
322
|
+
CREATE INDEX IF NOT EXISTS idx_remote_agent_sessions_updated
|
|
323
|
+
ON remote_agent_sessions (account_id, updated_at)
|
|
324
|
+
`);
|
|
294
325
|
}
|
|
295
326
|
|
|
296
327
|
// ── Write to disk ──
|
|
@@ -533,6 +564,55 @@ export class StateStore {
|
|
|
533
564
|
}
|
|
534
565
|
}
|
|
535
566
|
|
|
567
|
+
// ═════════════════════════════════════════════════════════════════
|
|
568
|
+
// OASN remote_agent_sessions CRUD
|
|
569
|
+
//
|
|
570
|
+
// 保存远端 ServiceAgent 返回的 session_id,用于同一 OpenClaw 会话内后续
|
|
571
|
+
// mode='session' 调用续传。run 模式不读写这张表。
|
|
572
|
+
// ═════════════════════════════════════════════════════════════════
|
|
573
|
+
|
|
574
|
+
getRemoteAgentSession(openclawSessionKey: string, targetAgentId: string): string | null {
|
|
575
|
+
if (!this.db) return null;
|
|
576
|
+
const scopeKey = openclawSessionKey.trim();
|
|
577
|
+
const agentId = targetAgentId.trim();
|
|
578
|
+
if (!scopeKey || !agentId) return null;
|
|
579
|
+
|
|
580
|
+
const stmt = this.db.prepare(
|
|
581
|
+
`SELECT session_id
|
|
582
|
+
FROM remote_agent_sessions
|
|
583
|
+
WHERE account_id = ? AND openclaw_session_key = ? AND target_agent_id = ?`,
|
|
584
|
+
);
|
|
585
|
+
stmt.bind([this.accountId, scopeKey, agentId]);
|
|
586
|
+
const row = stmt.step() ? stmt.getAsObject() : null;
|
|
587
|
+
stmt.free();
|
|
588
|
+
const sessionId = row?.['session_id'];
|
|
589
|
+
return typeof sessionId === 'string' && sessionId.trim() ? sessionId : null;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
upsertRemoteAgentSession(record: {
|
|
593
|
+
openclaw_session_key: string;
|
|
594
|
+
target_agent_id: string;
|
|
595
|
+
session_id: string;
|
|
596
|
+
}): void {
|
|
597
|
+
if (!this.db) return;
|
|
598
|
+
const scopeKey = record.openclaw_session_key.trim();
|
|
599
|
+
const agentId = record.target_agent_id.trim();
|
|
600
|
+
const sessionId = record.session_id.trim();
|
|
601
|
+
if (!scopeKey || !agentId || !sessionId) return;
|
|
602
|
+
|
|
603
|
+
const now = Date.now();
|
|
604
|
+
this.db.run(
|
|
605
|
+
`INSERT INTO remote_agent_sessions
|
|
606
|
+
(account_id, openclaw_session_key, target_agent_id, session_id, created_at, updated_at)
|
|
607
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
608
|
+
ON CONFLICT(account_id, openclaw_session_key, target_agent_id) DO UPDATE SET
|
|
609
|
+
session_id = excluded.session_id,
|
|
610
|
+
updated_at = excluded.updated_at`,
|
|
611
|
+
[this.accountId, scopeKey, agentId, sessionId, now, now],
|
|
612
|
+
);
|
|
613
|
+
this.persist();
|
|
614
|
+
}
|
|
615
|
+
|
|
536
616
|
// ═════════════════════════════════════════════════════════════════
|
|
537
617
|
// §11.1 OASN pending_invocations CRUD(OasnInvocation.PendingInvocationStore 适配)
|
|
538
618
|
//
|
|
@@ -407,15 +407,10 @@ export class OasnInvocation {
|
|
|
407
407
|
};
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const message = userError?.message
|
|
415
|
-
?? inlineResult?.summary
|
|
416
|
-
?? visible?.inlineResultSummary
|
|
417
|
-
?? visible?.inline_result_summary
|
|
418
|
-
?? `Invocation ended with status=${final.status}`;
|
|
410
|
+
const message = this._invocationFailureMessage(
|
|
411
|
+
final,
|
|
412
|
+
`Invocation ended with status=${final.status}`,
|
|
413
|
+
);
|
|
419
414
|
return {
|
|
420
415
|
requestId: invocationId,
|
|
421
416
|
status: 'error',
|
|
@@ -450,15 +445,55 @@ export class OasnInvocation {
|
|
|
450
445
|
|
|
451
446
|
/** 终态错误专用 TransportError,便于上层 catch */
|
|
452
447
|
private _terminalError(final: GetInvocationResponse, handle: TaskHandle): TransportError {
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
const msg = userError?.message ?? `Invocation ${final.status}`;
|
|
448
|
+
const code = this._invocationFailureCode(final) ?? `INVOCATION_${final.status.toUpperCase()}`;
|
|
449
|
+
const msg = this._invocationFailureMessage(final, `Invocation ${final.status}`);
|
|
456
450
|
return makeTransportError(code, msg, {
|
|
457
451
|
invocationId: handle.invocationId,
|
|
458
452
|
sessionId: handle.sessionId,
|
|
459
453
|
});
|
|
460
454
|
}
|
|
461
455
|
|
|
456
|
+
private _invocationFailureCode(final: GetInvocationResponse): string | undefined {
|
|
457
|
+
return this._firstText(
|
|
458
|
+
final.userError?.code,
|
|
459
|
+
final.user_error?.code,
|
|
460
|
+
final.userErrorCode,
|
|
461
|
+
final.user_error_code,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private _invocationFailureMessage(final: GetInvocationResponse, fallback: string): string {
|
|
466
|
+
const userMessage = this._firstText(
|
|
467
|
+
final.userError?.message,
|
|
468
|
+
final.user_error?.message,
|
|
469
|
+
final.userErrorMessage,
|
|
470
|
+
final.user_error_message,
|
|
471
|
+
);
|
|
472
|
+
if (userMessage) return userMessage;
|
|
473
|
+
|
|
474
|
+
const inlineResult = final.inlineResult ?? final.inline_result;
|
|
475
|
+
const visible = final.visibleResult ?? final.visible_result;
|
|
476
|
+
const fallbackMessage = this._firstText(
|
|
477
|
+
inlineResult?.summary,
|
|
478
|
+
visible?.inlineResultSummary,
|
|
479
|
+
visible?.inline_result_summary,
|
|
480
|
+
final.progress?.safeStatusMessage,
|
|
481
|
+
final.progress?.safe_status_message,
|
|
482
|
+
);
|
|
483
|
+
const code = this._invocationFailureCode(final);
|
|
484
|
+
if (fallbackMessage && code && !fallbackMessage.includes(code)) return `${fallbackMessage} (${code})`;
|
|
485
|
+
return fallbackMessage ?? code ?? fallback;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private _firstText(...values: Array<unknown>): string | undefined {
|
|
489
|
+
for (const value of values) {
|
|
490
|
+
if (typeof value !== 'string') continue;
|
|
491
|
+
const trimmed = value.trim();
|
|
492
|
+
if (trimmed) return trimmed;
|
|
493
|
+
}
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
|
|
462
497
|
private _toArtifactRef(art: ArtifactResponse): ArtifactRef {
|
|
463
498
|
return {
|
|
464
499
|
id: art.artifactId ?? art.artifact_id ?? '',
|
|
@@ -316,8 +316,12 @@ export interface GetInvocationResponse {
|
|
|
316
316
|
nextPollAfterMs?: number | null;
|
|
317
317
|
next_poll_after_ms?: number | null;
|
|
318
318
|
warnings?: string[];
|
|
319
|
-
userError?: { code
|
|
320
|
-
user_error?: { code
|
|
319
|
+
userError?: { code?: string; message?: string | null };
|
|
320
|
+
user_error?: { code?: string; message?: string | null };
|
|
321
|
+
userErrorCode?: string;
|
|
322
|
+
user_error_code?: string;
|
|
323
|
+
userErrorMessage?: string | null;
|
|
324
|
+
user_error_message?: string | null;
|
|
321
325
|
createdAt?: string;
|
|
322
326
|
created_at?: string;
|
|
323
327
|
updatedAt?: string;
|