openclaw-openagent 1.0.12 → 1.0.14
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 +2 -0
- package/dist/src/config/config-schema.d.ts +9 -0
- package/dist/src/config/config-schema.js +6 -0
- package/dist/src/messaging/executor.d.ts +2 -3
- package/dist/src/messaging/executor.js +4 -31
- package/dist/src/plugin-ui/assets/openagent-override.js +124 -18
- 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 +216 -0
- package/index.ts +3 -0
- package/package.json +1 -3
- package/src/app/channel-tools.ts +0 -249
- package/src/app/discovery-tools.ts +0 -273
- package/src/app/download-file-tool.ts +0 -133
- package/src/app/hooks.ts +0 -144
- package/src/app/index.ts +0 -84
- package/src/app/messaging-tools.ts +0 -79
- package/src/app/ops-tools.ts +0 -155
- package/src/app/remote-agent-tool.ts +0 -762
- package/src/app/types.ts +0 -68
- package/src/app/upload-file-tool.ts +0 -411
- package/src/app/verbose-preflight.ts +0 -190
- package/src/auth/config.ts +0 -266
- package/src/auth/credential-manager.ts +0 -146
- package/src/auth/index.ts +0 -24
- package/src/auth/verify.ts +0 -99
- package/src/channel.ts +0 -566
- package/src/compat.ts +0 -94
- package/src/config/config-schema.ts +0 -39
- package/src/messaging/aggregator.ts +0 -120
- package/src/messaging/collector.ts +0 -89
- package/src/messaging/executor.ts +0 -72
- package/src/messaging/inbound.ts +0 -150
- package/src/messaging/index.ts +0 -11
- package/src/messaging/mention-protocol.ts +0 -94
- package/src/messaging/process-c2c-request.ts +0 -564
- package/src/messaging/process-message.ts +0 -373
- package/src/messaging/scheduler.ts +0 -55
- package/src/messaging/types.ts +0 -38
- package/src/plugin-ui/adapters/oc-2026-04.js +0 -103
- package/src/plugin-ui/adapters/oc-2026-05.js +0 -125
- package/src/plugin-ui/adapters/oc-2026-06.js +0 -125
- package/src/plugin-ui/adapters/oc-unknown.js +0 -48
- package/src/plugin-ui/assets/agentbook-icon.svg +0 -5
- package/src/plugin-ui/assets/bg.png +0 -0
- package/src/plugin-ui/assets/icon.png +0 -0
- package/src/plugin-ui/assets/magic.svg +0 -5
- package/src/plugin-ui/assets/openagent-override.js +0 -11156
- package/src/plugin-ui/build.cjs +0 -463
- package/src/plugin-ui/index.ts +0 -18
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -570
- package/src/plugin-ui/modules/agent-book/panel/agent-card.js +0 -170
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -643
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +0 -665
- package/src/plugin-ui/modules/agent-book/panel/mention-state.js +0 -206
- package/src/plugin-ui/modules/agent-book/panel/styles.js +0 -833
- package/src/plugin-ui/modules/agent-book/panel/theme-adapter.js +0 -86
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +0 -295
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +0 -211
- package/src/plugin-ui/modules/agent-book/scanner.js +0 -131
- package/src/plugin-ui/modules/agent-book/travelcard/travel-cards.js +0 -500
- package/src/plugin-ui/modules/agent-book/travelcard/travel-engine.js +0 -652
- package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +0 -256
- package/src/plugin-ui/modules/loader/bootstrap.js +0 -38
- package/src/plugin-ui/modules/loader/shared-state.js +0 -838
- package/src/plugin-ui/modules/loader/ws-intercept.js +0 -199
- package/src/plugin-ui/modules/remote-agent/chunk-parser.js +0 -161
- package/src/plugin-ui/modules/remote-agent/execution-card.js +0 -530
- package/src/plugin-ui/modules/remote-agent/markdown-renderer.js +0 -256
- package/src/plugin-ui/modules/remote-agent/media-links.js +0 -151
- package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +0 -134
- package/src/plugin-ui/modules/remote-agent/output-card.js +0 -342
- package/src/plugin-ui/modules/remote-agent/progress-store.js +0 -363
- package/src/plugin-ui/modules/remote-agent/render-hooks.js +0 -1700
- package/src/plugin-ui/modules/remote-agent/styles.js +0 -896
- package/src/plugin-ui/modules/remote-agent/tool-card-model.js +0 -130
- package/src/plugin-ui/postinstall-deploy.cjs +0 -52
- package/src/plugin-ui/ui-extension-loader/backup.ts +0 -92
- package/src/plugin-ui/ui-extension-loader/index.ts +0 -276
- package/src/plugin-ui/ui-extension-loader/locator.ts +0 -152
- package/src/plugin-ui/ui-extension-loader/manifest.ts +0 -57
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +0 -944
- package/src/plugin-ui/ui-extension-loader/removed-extensions.ts +0 -70
- package/src/plugin-ui/ui-extension-loader/types.ts +0 -72
- package/src/proxy/auth-proxy.ts +0 -409
- package/src/runtime/account.ts +0 -549
- package/src/runtime/index.ts +0 -7
- package/src/runtime/plugin-runtime.ts +0 -94
- package/src/runtime/registry.ts +0 -71
- package/src/sdk/CLASS_MAP.md +0 -143
- package/src/sdk/index.d.ts +0 -126
- package/src/sdk/index.js +0 -23990
- package/src/sdk/modules/cloud-search-module.js +0 -1117
- package/src/sdk/modules/follow-module.js +0 -1069
- package/src/sdk/modules/group-module.js +0 -7397
- package/src/sdk/modules/relationship-module.js +0 -2269
- package/src/sdk/modules/signaling-module.js +0 -1468
- package/src/sdk/modules/tim-upload-plugin.js +0 -730
- package/src/sdk/node-env/http-request.js +0 -90
- package/src/sdk/node-env/index.js +0 -57
- package/src/sdk/node-env/storage.js +0 -114
- package/src/sdk/package.json +0 -10
- package/src/sdk/tsconfig.json +0 -16
- package/src/state/pending-invocation-store.ts +0 -43
- package/src/state/store.ts +0 -756
- package/src/tim/c2c.ts +0 -451
- package/src/tim/channels.ts +0 -364
- package/src/tim/client.ts +0 -330
- package/src/tim/index.ts +0 -18
- package/src/tim/messages.ts +0 -166
- package/src/tim/sdk-logger-init.ts +0 -50
- package/src/tools.ts +0 -10
- package/src/transport/factory.ts +0 -95
- package/src/transport/oasn/index.ts +0 -17
- package/src/transport/oasn/oasn-agent-card.ts +0 -111
- package/src/transport/oasn/oasn-discovery.ts +0 -108
- package/src/transport/oasn/oasn-files.ts +0 -210
- package/src/transport/oasn/oasn-http.ts +0 -529
- package/src/transport/oasn/oasn-invocation.ts +0 -604
- package/src/transport/oasn/oasn-normalize.ts +0 -133
- package/src/transport/oasn/oasn-register.ts +0 -33
- package/src/transport/oasn/oasn-transport.ts +0 -318
- package/src/transport/oasn/oasn-types.ts +0 -404
- package/src/transport/tim/index.ts +0 -8
- package/src/transport/tim/tim-transport.ts +0 -515
- package/src/transport/types.ts +0 -538
- package/src/types/openclaw-plugin-sdk-media-store.d.ts +0 -9
- package/src/types/openclaw.d.ts +0 -97
- package/src/types/tencentcloud-chat.d.ts +0 -15
- package/src/util/http.ts +0 -113
- package/src/util/logger.ts +0 -131
- package/src/util/url-resolver.ts +0 -77
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
// ── OpenAgent Tool Card Model — ToolCard → OpenAgentCardModel 转换 ──
|
|
2
|
-
//
|
|
3
|
-
// 输入:
|
|
4
|
-
// 旧版 OpenClaw ToolCard = { kind, name, args, text }
|
|
5
|
-
// 4.11+ OpenClaw ToolCard = { name, args, inputText, outputText }
|
|
6
|
-
// 输出: OpenAgentCardModel = { agentId, agentName, taskText, status, rawText, chunks, isError }
|
|
7
|
-
|
|
8
|
-
function _clInferToolCardKind(card) {
|
|
9
|
-
if (!card) return '';
|
|
10
|
-
if (card.kind === 'call' || card.kind === 'result') return card.kind;
|
|
11
|
-
|
|
12
|
-
var outputText = card.outputText || '';
|
|
13
|
-
var text = card.text || '';
|
|
14
|
-
if (String(outputText || text || '').trim()) return 'result';
|
|
15
|
-
return 'call';
|
|
16
|
-
}
|
|
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
|
-
|
|
85
|
-
function _clParseToolCardModel(card) {
|
|
86
|
-
var inferredKind = _clInferToolCardKind(card);
|
|
87
|
-
|
|
88
|
-
// 从 args 提取 agent 信息
|
|
89
|
-
var args = (typeof card.args === 'object' && card.args) || {};
|
|
90
|
-
var agentId = args.agent_id || args.agentId || null;
|
|
91
|
-
var agentName = args.agent_name || args.agentName || agentId || 'Remote Agent';
|
|
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万';
|
|
95
|
-
var taskText = args.task || args.message || args.instruction || '';
|
|
96
|
-
|
|
97
|
-
// 从 text 解析思考步骤 / 最终文本
|
|
98
|
-
var rawText = card.text || card.outputText || '';
|
|
99
|
-
var chunks = _clParseChunks(rawText, {
|
|
100
|
-
toolCallId: card.toolCallId || '',
|
|
101
|
-
rawText: rawText
|
|
102
|
-
});
|
|
103
|
-
var hasOutput = inferredKind === 'result' && rawText.trim().length > 0;
|
|
104
|
-
var isError = _clInferToolCardError(card, chunks, rawText);
|
|
105
|
-
|
|
106
|
-
var model = {
|
|
107
|
-
agentId: agentId,
|
|
108
|
-
agentName: agentName,
|
|
109
|
-
agentAvatar: agentAvatar,
|
|
110
|
-
agentBio: agentBio,
|
|
111
|
-
agentStars: agentStars,
|
|
112
|
-
taskText: taskText,
|
|
113
|
-
toolCallId: card.toolCallId || '',
|
|
114
|
-
status: isError ? 'failed' : (hasOutput ? 'completed' : 'executing'),
|
|
115
|
-
rawText: rawText,
|
|
116
|
-
text: rawText,
|
|
117
|
-
outputText: rawText,
|
|
118
|
-
chunks: chunks,
|
|
119
|
-
isError: isError,
|
|
120
|
-
inferredKind: inferredKind,
|
|
121
|
-
};
|
|
122
|
-
_clApplyAgentIdentityToModel(model);
|
|
123
|
-
|
|
124
|
-
console.log('[cl-model] parsed:', 'agent=' + model.agentName,
|
|
125
|
-
'status=' + model.status,
|
|
126
|
-
'chunks=' + chunks.length,
|
|
127
|
-
'error=' + isError);
|
|
128
|
-
|
|
129
|
-
return model;
|
|
130
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
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();
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Backup — 文件备份与还原
|
|
3
|
-
*
|
|
4
|
-
* 代码级扩展修改宿主 minified bundle 前,先备份原始文件。
|
|
5
|
-
* 卸载时从备份恢复。OC 升级时备份随 control-ui/ 一起被清除。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { copyFileSync, unlinkSync, existsSync, readFileSync } from 'node:fs';
|
|
9
|
-
import { logger } from '../../util/logger.js';
|
|
10
|
-
|
|
11
|
-
const BACKUP_SUFFIX = '.openagent-bak';
|
|
12
|
-
|
|
13
|
-
/** Patterns that indicate openagent modifications in the bundle.
|
|
14
|
-
* NOTE: __openagentMarked is NOT listed here — it's handled by the
|
|
15
|
-
* removed-extensions scanner for marked-expose cleanup. */
|
|
16
|
-
const OPENAGENT_FINGERPRINTS = [
|
|
17
|
-
'__openagentRenderToolCard',
|
|
18
|
-
'__openagentOwnsToolCards',
|
|
19
|
-
'__tcid',
|
|
20
|
-
'__openagentIsRemoteToolName',
|
|
21
|
-
'__openagentToolCallIds',
|
|
22
|
-
'__openagentKeepToolPayload',
|
|
23
|
-
];
|
|
24
|
-
const OPENAGENT_MARKER_PATTERN = '/*openagent:';
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 检测文件是否包含无 marker 的 openagent 手工补丁。
|
|
28
|
-
* 如果文件包含 openagent 特征字符串但没有对应的 marker 注释,
|
|
29
|
-
* 说明这是一个被手工修改过的文件,不应该作为"原始备份"保存。
|
|
30
|
-
*/
|
|
31
|
-
function isDirtyBundle(filepath: string): boolean {
|
|
32
|
-
try {
|
|
33
|
-
const content = readFileSync(filepath, 'utf-8');
|
|
34
|
-
const hasFingerprint = OPENAGENT_FINGERPRINTS.some(fp => content.includes(fp));
|
|
35
|
-
const hasMarker = content.includes(OPENAGENT_MARKER_PATTERN);
|
|
36
|
-
// 有 openagent 特征但没有 marker → 手工补丁
|
|
37
|
-
return hasFingerprint && !hasMarker;
|
|
38
|
-
} catch {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 备份文件。如果备份已存在则跳过(保持最早的原始版本)。
|
|
45
|
-
* 如果文件包含无 marker 的手工补丁,拒绝备份。
|
|
46
|
-
*
|
|
47
|
-
* @returns true = 备份就绪(已存在或新创建),false = 检测到脏 bundle,调用方必须中止 apply。
|
|
48
|
-
*/
|
|
49
|
-
export function backupFile(filepath: string): boolean {
|
|
50
|
-
const backupPath = filepath + BACKUP_SUFFIX;
|
|
51
|
-
if (existsSync(backupPath)) {
|
|
52
|
-
logger.debug(`[ui-ext] Backup already exists: ${backupPath}`);
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 检测脏 bundle:包含 openagent 代码但没有 marker
|
|
57
|
-
if (isDirtyBundle(filepath)) {
|
|
58
|
-
logger.warn(
|
|
59
|
-
`[ui-ext] Dirty bundle detected: ${filepath} contains unmarked openagent patches. ` +
|
|
60
|
-
`Refusing to backup or patch. Please reinstall OpenClaw to get a clean bundle, then restart.`
|
|
61
|
-
);
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
copyFileSync(filepath, backupPath);
|
|
66
|
-
logger.debug(`[ui-ext] Backed up: ${filepath}`);
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 从备份还原文件。
|
|
72
|
-
* 还原后删除备份文件。
|
|
73
|
-
* 如果备份不存在,记录警告但不抛异常。
|
|
74
|
-
*/
|
|
75
|
-
export function restoreFile(filepath: string): boolean {
|
|
76
|
-
const backupPath = filepath + BACKUP_SUFFIX;
|
|
77
|
-
if (!existsSync(backupPath)) {
|
|
78
|
-
logger.debug(`[ui-ext] No backup found: ${backupPath}`);
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
copyFileSync(backupPath, filepath);
|
|
82
|
-
unlinkSync(backupPath);
|
|
83
|
-
logger.debug(`[ui-ext] Restored from backup: ${filepath}`);
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* 检查备份是否存在。
|
|
89
|
-
*/
|
|
90
|
-
export function hasBackup(filepath: string): boolean {
|
|
91
|
-
return existsSync(filepath + BACKUP_SUFFIX);
|
|
92
|
-
}
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UIExtensionLoader — 主流程
|
|
3
|
-
*
|
|
4
|
-
* Manifest Reconciler:对比注册表(期望状态)与清单文件(当前状态),
|
|
5
|
-
* 自动完成扩展点的 apply / revert / skip。
|
|
6
|
-
*
|
|
7
|
-
* 共享文件的扩展按组处理:任何一个需要变更 → 恢复 backup → 重新 apply 全组。
|
|
8
|
-
*
|
|
9
|
-
* 每次 Gateway 启动时调用 loadUIExtensions()。
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { join } from 'node:path';
|
|
13
|
-
import { logger } from '../../util/logger.js';
|
|
14
|
-
import { resolveControlUiDir, findFileByPrefix, extractBundleHash } from './locator.js';
|
|
15
|
-
import { readManifest, writeManifest, removeManifest } from './manifest.js';
|
|
16
|
-
import { restoreFile } from './backup.js';
|
|
17
|
-
import { UI_EXTENSIONS } from './registry-regex.js';
|
|
18
|
-
import { REMOVED_UI_EXTENSIONS } from './removed-extensions.js';
|
|
19
|
-
import type { ManifestData } from './manifest.js';
|
|
20
|
-
import type { UIExtension, UIExtensionContext } from './types.js';
|
|
21
|
-
import type { RemovedUIExtension, RemovedRevertResult } from './removed-extensions.js';
|
|
22
|
-
|
|
23
|
-
// ── 注入策略:正则模式链(无需 .map,21 版本全仿真验证通过)──────────────────
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 获取当前 bundle 的 hash(用于检测 OC 升级)。
|
|
27
|
-
*/
|
|
28
|
-
function getCurrentBundleHash(controlUiDir: string): string {
|
|
29
|
-
const assetsDir = join(controlUiDir, 'assets');
|
|
30
|
-
const bundlePath = findFileByPrefix(assetsDir, 'index-');
|
|
31
|
-
return bundlePath ? extractBundleHash(bundlePath) : 'unknown';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 判断一个扩展是否需要变更(新增、更新、或删除)。
|
|
36
|
-
*/
|
|
37
|
-
function needsChange(
|
|
38
|
-
ext: UIExtension | null, // null = 已从注册表删除
|
|
39
|
-
oldVersion: number | null // null = manifest 中不存在
|
|
40
|
-
): boolean {
|
|
41
|
-
if (ext === null) return true; // 需要删除
|
|
42
|
-
if (oldVersion === null) return true; // 需要新增
|
|
43
|
-
return ext.version !== oldVersion; // 需要更新
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 加载全部 UI 扩展。
|
|
48
|
-
*
|
|
49
|
-
* Reconciliation 流程:
|
|
50
|
-
* 1. 读取 manifest(不存在视为空)
|
|
51
|
-
* 2. 检测 bundleHash:不同 → OC 升级 → 全部重新 apply
|
|
52
|
-
* 3. 独立扩展逐个 apply/revert
|
|
53
|
-
* 4. 共享文件扩展按组处理:任何一个变更 → 恢复 backup → 重新 apply 全组
|
|
54
|
-
* 5. 写入新 manifest
|
|
55
|
-
*/
|
|
56
|
-
export async function loadUIExtensions(context: UIExtensionContext = {}): Promise<void> {
|
|
57
|
-
const controlUiDir = resolveControlUiDir();
|
|
58
|
-
|
|
59
|
-
if (!controlUiDir) {
|
|
60
|
-
logger.warn('[ui-ext] control-ui directory not found, skipping');
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
logger.info(`[ui-ext] control-ui located: ${controlUiDir}`);
|
|
65
|
-
|
|
66
|
-
const currentHash = getCurrentBundleHash(controlUiDir);
|
|
67
|
-
const manifest = readManifest(controlUiDir);
|
|
68
|
-
const removedIds = new Set(REMOVED_UI_EXTENSIONS.map(ext => ext.id));
|
|
69
|
-
|
|
70
|
-
// bundleHash 不同 → OC 升级了
|
|
71
|
-
const isOcUpgrade = manifest !== null && manifest.bundleHash !== currentHash;
|
|
72
|
-
const needFullReapply = isOcUpgrade;
|
|
73
|
-
if (isOcUpgrade) {
|
|
74
|
-
logger.info(`[ui-ext] OC upgrade detected (${manifest!.bundleHash} → ${currentHash}), re-applying all`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// These are removed backend dist patches from the previous SSE strategy.
|
|
78
|
-
// Scan every startup regardless of manifest state: migration sites may have
|
|
79
|
-
// missing manifests, stale backups, or hand-applied copies.
|
|
80
|
-
// Track which shared files were restored so we force those groups to rebuild.
|
|
81
|
-
const forceSharedRebuild = new Set<string>();
|
|
82
|
-
|
|
83
|
-
for (const ext of REMOVED_UI_EXTENSIONS) {
|
|
84
|
-
const result = safeRevertRemoved(ext, controlUiDir);
|
|
85
|
-
if (result === 'failed') {
|
|
86
|
-
logger.error(`[ui-ext] ${ext.id}: removed backend patch could not be reverted; aborting UI extension load`);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (result === 'restored' && ext.sharedFile) {
|
|
90
|
-
forceSharedRebuild.add(ext.sharedFile);
|
|
91
|
-
logger.info(`[ui-ext] ${ext.id}: shared file '${ext.sharedFile}' restored, will force group rebuild`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const oldExtensions = (manifest && !needFullReapply)
|
|
96
|
-
? Object.fromEntries(Object.entries(manifest.extensions).filter(([id]) => !removedIds.has(id)))
|
|
97
|
-
: {};
|
|
98
|
-
|
|
99
|
-
// ── 分组:独立扩展 vs 共享文件扩展 ──
|
|
100
|
-
|
|
101
|
-
const independentExts: UIExtension[] = [];
|
|
102
|
-
const sharedGroups = new Map<string, UIExtension[]>();
|
|
103
|
-
|
|
104
|
-
for (const ext of UI_EXTENSIONS) {
|
|
105
|
-
if (ext.sharedFile) {
|
|
106
|
-
const group = sharedGroups.get(ext.sharedFile) ?? [];
|
|
107
|
-
group.push(ext);
|
|
108
|
-
sharedGroups.set(ext.sharedFile, group);
|
|
109
|
-
} else {
|
|
110
|
-
independentExts.push(ext);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const newExtensions: Record<string, { version: number }> = {};
|
|
115
|
-
let applied = 0;
|
|
116
|
-
let skipped = 0;
|
|
117
|
-
|
|
118
|
-
// ── 处理独立扩展(逐个 apply/revert)──
|
|
119
|
-
|
|
120
|
-
for (const ext of independentExts) {
|
|
121
|
-
const old = oldExtensions[ext.id];
|
|
122
|
-
|
|
123
|
-
if (old && old.version === ext.version && !ext.alwaysApply) {
|
|
124
|
-
skipped++;
|
|
125
|
-
newExtensions[ext.id] = { version: ext.version };
|
|
126
|
-
logger.debug(`[ui-ext] ${ext.id}: skip (v${ext.version} unchanged)`);
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (old && old.version !== ext.version) {
|
|
131
|
-
// 版本变了,先 revert
|
|
132
|
-
logger.debug(`[ui-ext] ${ext.id}: revert (v${old.version} → v${ext.version})`);
|
|
133
|
-
safeRevert(ext, controlUiDir);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
logger.debug(`[ui-ext] ${ext.id}: apply v${ext.version}`);
|
|
137
|
-
if (safeApply(ext, controlUiDir, context)) {
|
|
138
|
-
newExtensions[ext.id] = { version: ext.version };
|
|
139
|
-
applied++;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ── 处理共享文件扩展(按组批量 reconcile)──
|
|
144
|
-
|
|
145
|
-
for (const [sharedFile, group] of sharedGroups) {
|
|
146
|
-
// 检查这个组里是否有任何扩展需要变更
|
|
147
|
-
let groupNeedsChange = forceSharedRebuild.has(sharedFile);
|
|
148
|
-
if (groupNeedsChange) {
|
|
149
|
-
logger.info(`[ui-ext] Shared group '${sharedFile}': forced rebuild due to removed extension cleanup`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (!groupNeedsChange) {
|
|
153
|
-
for (const ext of group) {
|
|
154
|
-
const old = oldExtensions[ext.id];
|
|
155
|
-
if (needsChange(ext, old?.version ?? null)) {
|
|
156
|
-
groupNeedsChange = true;
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// 检查是否有被删除的扩展(在 manifest 中属于该组但不在当前注册表中)
|
|
163
|
-
// 如果有,必须触发组重建以清除残留补丁
|
|
164
|
-
const groupRegistryIds = new Set(group.map(e => e.id));
|
|
165
|
-
for (const oldId of Object.keys(oldExtensions)) {
|
|
166
|
-
// 只关心曾经属于本组的扩展(不在当前注册表中 + 不在当前组中)
|
|
167
|
-
if (!groupRegistryIds.has(oldId) && !UI_EXTENSIONS.some(e => e.id === oldId)) {
|
|
168
|
-
// 无法确定旧扩展属于哪个组(manifest 不记录 sharedFile),保守触发重建
|
|
169
|
-
logger.info(`[ui-ext] Orphaned extension '${oldId}' found, triggering group '${sharedFile}' rebuild`);
|
|
170
|
-
groupNeedsChange = true;
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (!groupNeedsChange) {
|
|
176
|
-
// 全组无变更,跳过
|
|
177
|
-
for (const ext of group) {
|
|
178
|
-
newExtensions[ext.id] = { version: ext.version };
|
|
179
|
-
skipped++;
|
|
180
|
-
logger.debug(`[ui-ext] ${ext.id}: skip (shared group '${sharedFile}' unchanged)`);
|
|
181
|
-
}
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// 有变更 → 恢复 backup → 重新 apply 全组
|
|
186
|
-
logger.info(`[ui-ext] Shared group '${sharedFile}': changes detected, rebuilding`);
|
|
187
|
-
|
|
188
|
-
// 恢复 backup(仅当之前有补丁时才需要还原)
|
|
189
|
-
const assetsDir = join(controlUiDir, 'assets');
|
|
190
|
-
const bundlePath = findFileByPrefix(assetsDir, 'index-');
|
|
191
|
-
if (bundlePath) {
|
|
192
|
-
const restored = restoreFile(bundlePath);
|
|
193
|
-
if (restored) {
|
|
194
|
-
logger.debug(`[ui-ext] Restored backup for shared group '${sharedFile}'`);
|
|
195
|
-
} else {
|
|
196
|
-
logger.debug(`[ui-ext] No backup to restore (first install or OC upgrade)`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 重新 apply 全组中所有应保留的扩展
|
|
201
|
-
for (const ext of group) {
|
|
202
|
-
logger.debug(`[ui-ext] ${ext.id}: apply v${ext.version} (group rebuild)`);
|
|
203
|
-
if (safeApply(ext, controlUiDir, context)) {
|
|
204
|
-
newExtensions[ext.id] = { version: ext.version };
|
|
205
|
-
applied++;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ── 写入新 manifest ──
|
|
211
|
-
|
|
212
|
-
const newManifest: ManifestData = {
|
|
213
|
-
bundleHash: currentHash,
|
|
214
|
-
strategy: 'regex',
|
|
215
|
-
extensions: newExtensions,
|
|
216
|
-
};
|
|
217
|
-
writeManifest(controlUiDir, newManifest);
|
|
218
|
-
|
|
219
|
-
logger.info(`[ui-ext] Done: ${applied} applied, ${skipped} skipped`);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* 卸载全部 UI 扩展(插件卸载时调用)。
|
|
224
|
-
*
|
|
225
|
-
* 共享文件扩展:恢复 backup 即可(一次还原覆盖所有修改)。
|
|
226
|
-
* 独立扩展:逐个 revert。
|
|
227
|
-
*/
|
|
228
|
-
export async function unloadUIExtensions(): Promise<void> {
|
|
229
|
-
const controlUiDir = resolveControlUiDir();
|
|
230
|
-
if (!controlUiDir) return;
|
|
231
|
-
|
|
232
|
-
// 先恢复共享文件的 backup
|
|
233
|
-
const assetsDir = join(controlUiDir, 'assets');
|
|
234
|
-
const bundlePath = findFileByPrefix(assetsDir, 'index-');
|
|
235
|
-
if (bundlePath) {
|
|
236
|
-
restoreFile(bundlePath);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// 再逆序 revert 独立扩展
|
|
240
|
-
for (const ext of UI_EXTENSIONS.slice().reverse()) {
|
|
241
|
-
if (!ext.sharedFile) {
|
|
242
|
-
safeRevert(ext, controlUiDir);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
removeManifest(controlUiDir);
|
|
247
|
-
logger.info('[ui-ext] All UI extensions unloaded');
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
251
|
-
|
|
252
|
-
function safeApply(ext: UIExtension, controlUiDir: string, context: UIExtensionContext): boolean {
|
|
253
|
-
try {
|
|
254
|
-
return ext.apply(controlUiDir, context);
|
|
255
|
-
} catch (err) {
|
|
256
|
-
logger.error(`[ui-ext] Failed to apply '${ext.id}': ${(err as Error).message}`);
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function safeRevert(ext: UIExtension, controlUiDir: string): void {
|
|
262
|
-
try {
|
|
263
|
-
ext.revert(controlUiDir);
|
|
264
|
-
} catch (err) {
|
|
265
|
-
logger.error(`[ui-ext] Failed to revert '${ext.id}': ${(err as Error).message}`);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function safeRevertRemoved(ext: RemovedUIExtension, controlUiDir: string): RemovedRevertResult {
|
|
270
|
-
try {
|
|
271
|
-
return ext.revert(controlUiDir);
|
|
272
|
-
} catch (err) {
|
|
273
|
-
logger.error(`[ui-ext] Failed to revert removed extension '${ext.id}': ${(err as Error).message}`);
|
|
274
|
-
return 'failed';
|
|
275
|
-
}
|
|
276
|
-
}
|