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.
Files changed (132) hide show
  1. package/dist/index.js +2 -0
  2. package/dist/src/config/config-schema.d.ts +9 -0
  3. package/dist/src/config/config-schema.js +6 -0
  4. package/dist/src/messaging/executor.d.ts +2 -3
  5. package/dist/src/messaging/executor.js +4 -31
  6. package/dist/src/plugin-ui/assets/openagent-override.js +124 -18
  7. package/dist/src/proxy/auth-proxy.js +5 -0
  8. package/dist/src/runtime/update-checker.d.ts +18 -0
  9. package/dist/src/runtime/update-checker.js +216 -0
  10. package/index.ts +3 -0
  11. package/package.json +1 -3
  12. package/src/app/channel-tools.ts +0 -249
  13. package/src/app/discovery-tools.ts +0 -273
  14. package/src/app/download-file-tool.ts +0 -133
  15. package/src/app/hooks.ts +0 -144
  16. package/src/app/index.ts +0 -84
  17. package/src/app/messaging-tools.ts +0 -79
  18. package/src/app/ops-tools.ts +0 -155
  19. package/src/app/remote-agent-tool.ts +0 -762
  20. package/src/app/types.ts +0 -68
  21. package/src/app/upload-file-tool.ts +0 -411
  22. package/src/app/verbose-preflight.ts +0 -190
  23. package/src/auth/config.ts +0 -266
  24. package/src/auth/credential-manager.ts +0 -146
  25. package/src/auth/index.ts +0 -24
  26. package/src/auth/verify.ts +0 -99
  27. package/src/channel.ts +0 -566
  28. package/src/compat.ts +0 -94
  29. package/src/config/config-schema.ts +0 -39
  30. package/src/messaging/aggregator.ts +0 -120
  31. package/src/messaging/collector.ts +0 -89
  32. package/src/messaging/executor.ts +0 -72
  33. package/src/messaging/inbound.ts +0 -150
  34. package/src/messaging/index.ts +0 -11
  35. package/src/messaging/mention-protocol.ts +0 -94
  36. package/src/messaging/process-c2c-request.ts +0 -564
  37. package/src/messaging/process-message.ts +0 -373
  38. package/src/messaging/scheduler.ts +0 -55
  39. package/src/messaging/types.ts +0 -38
  40. package/src/plugin-ui/adapters/oc-2026-04.js +0 -103
  41. package/src/plugin-ui/adapters/oc-2026-05.js +0 -125
  42. package/src/plugin-ui/adapters/oc-2026-06.js +0 -125
  43. package/src/plugin-ui/adapters/oc-unknown.js +0 -48
  44. package/src/plugin-ui/assets/agentbook-icon.svg +0 -5
  45. package/src/plugin-ui/assets/bg.png +0 -0
  46. package/src/plugin-ui/assets/icon.png +0 -0
  47. package/src/plugin-ui/assets/magic.svg +0 -5
  48. package/src/plugin-ui/assets/openagent-override.js +0 -11156
  49. package/src/plugin-ui/build.cjs +0 -463
  50. package/src/plugin-ui/index.ts +0 -18
  51. package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -570
  52. package/src/plugin-ui/modules/agent-book/panel/agent-card.js +0 -170
  53. package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -643
  54. package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +0 -665
  55. package/src/plugin-ui/modules/agent-book/panel/mention-state.js +0 -206
  56. package/src/plugin-ui/modules/agent-book/panel/styles.js +0 -833
  57. package/src/plugin-ui/modules/agent-book/panel/theme-adapter.js +0 -86
  58. package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +0 -295
  59. package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +0 -211
  60. package/src/plugin-ui/modules/agent-book/scanner.js +0 -131
  61. package/src/plugin-ui/modules/agent-book/travelcard/travel-cards.js +0 -500
  62. package/src/plugin-ui/modules/agent-book/travelcard/travel-engine.js +0 -652
  63. package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +0 -256
  64. package/src/plugin-ui/modules/loader/bootstrap.js +0 -38
  65. package/src/plugin-ui/modules/loader/shared-state.js +0 -838
  66. package/src/plugin-ui/modules/loader/ws-intercept.js +0 -199
  67. package/src/plugin-ui/modules/remote-agent/chunk-parser.js +0 -161
  68. package/src/plugin-ui/modules/remote-agent/execution-card.js +0 -530
  69. package/src/plugin-ui/modules/remote-agent/markdown-renderer.js +0 -256
  70. package/src/plugin-ui/modules/remote-agent/media-links.js +0 -151
  71. package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +0 -134
  72. package/src/plugin-ui/modules/remote-agent/output-card.js +0 -342
  73. package/src/plugin-ui/modules/remote-agent/progress-store.js +0 -363
  74. package/src/plugin-ui/modules/remote-agent/render-hooks.js +0 -1700
  75. package/src/plugin-ui/modules/remote-agent/styles.js +0 -896
  76. package/src/plugin-ui/modules/remote-agent/tool-card-model.js +0 -130
  77. package/src/plugin-ui/postinstall-deploy.cjs +0 -52
  78. package/src/plugin-ui/ui-extension-loader/backup.ts +0 -92
  79. package/src/plugin-ui/ui-extension-loader/index.ts +0 -276
  80. package/src/plugin-ui/ui-extension-loader/locator.ts +0 -152
  81. package/src/plugin-ui/ui-extension-loader/manifest.ts +0 -57
  82. package/src/plugin-ui/ui-extension-loader/registry-regex.ts +0 -944
  83. package/src/plugin-ui/ui-extension-loader/removed-extensions.ts +0 -70
  84. package/src/plugin-ui/ui-extension-loader/types.ts +0 -72
  85. package/src/proxy/auth-proxy.ts +0 -409
  86. package/src/runtime/account.ts +0 -549
  87. package/src/runtime/index.ts +0 -7
  88. package/src/runtime/plugin-runtime.ts +0 -94
  89. package/src/runtime/registry.ts +0 -71
  90. package/src/sdk/CLASS_MAP.md +0 -143
  91. package/src/sdk/index.d.ts +0 -126
  92. package/src/sdk/index.js +0 -23990
  93. package/src/sdk/modules/cloud-search-module.js +0 -1117
  94. package/src/sdk/modules/follow-module.js +0 -1069
  95. package/src/sdk/modules/group-module.js +0 -7397
  96. package/src/sdk/modules/relationship-module.js +0 -2269
  97. package/src/sdk/modules/signaling-module.js +0 -1468
  98. package/src/sdk/modules/tim-upload-plugin.js +0 -730
  99. package/src/sdk/node-env/http-request.js +0 -90
  100. package/src/sdk/node-env/index.js +0 -57
  101. package/src/sdk/node-env/storage.js +0 -114
  102. package/src/sdk/package.json +0 -10
  103. package/src/sdk/tsconfig.json +0 -16
  104. package/src/state/pending-invocation-store.ts +0 -43
  105. package/src/state/store.ts +0 -756
  106. package/src/tim/c2c.ts +0 -451
  107. package/src/tim/channels.ts +0 -364
  108. package/src/tim/client.ts +0 -330
  109. package/src/tim/index.ts +0 -18
  110. package/src/tim/messages.ts +0 -166
  111. package/src/tim/sdk-logger-init.ts +0 -50
  112. package/src/tools.ts +0 -10
  113. package/src/transport/factory.ts +0 -95
  114. package/src/transport/oasn/index.ts +0 -17
  115. package/src/transport/oasn/oasn-agent-card.ts +0 -111
  116. package/src/transport/oasn/oasn-discovery.ts +0 -108
  117. package/src/transport/oasn/oasn-files.ts +0 -210
  118. package/src/transport/oasn/oasn-http.ts +0 -529
  119. package/src/transport/oasn/oasn-invocation.ts +0 -604
  120. package/src/transport/oasn/oasn-normalize.ts +0 -133
  121. package/src/transport/oasn/oasn-register.ts +0 -33
  122. package/src/transport/oasn/oasn-transport.ts +0 -318
  123. package/src/transport/oasn/oasn-types.ts +0 -404
  124. package/src/transport/tim/index.ts +0 -8
  125. package/src/transport/tim/tim-transport.ts +0 -515
  126. package/src/transport/types.ts +0 -538
  127. package/src/types/openclaw-plugin-sdk-media-store.d.ts +0 -9
  128. package/src/types/openclaw.d.ts +0 -97
  129. package/src/types/tencentcloud-chat.d.ts +0 -15
  130. package/src/util/http.ts +0 -113
  131. package/src/util/logger.ts +0 -131
  132. 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
- }