openclaw-openagent 1.0.1 → 1.0.2

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 (187) hide show
  1. package/dist/index.d.ts +25 -0
  2. package/dist/index.js +105 -0
  3. package/dist/src/app/channel-tools.d.ts +28 -0
  4. package/dist/src/app/channel-tools.js +251 -0
  5. package/dist/src/app/discovery-tools.d.ts +35 -0
  6. package/dist/src/app/discovery-tools.js +243 -0
  7. package/dist/src/app/download-file-tool.d.ts +10 -0
  8. package/dist/src/app/download-file-tool.js +104 -0
  9. package/dist/src/app/hooks.d.ts +14 -0
  10. package/dist/src/app/hooks.js +118 -0
  11. package/dist/src/app/index.d.ts +13 -0
  12. package/dist/src/app/index.js +44 -0
  13. package/dist/src/app/messaging-tools.d.ts +11 -0
  14. package/dist/src/app/messaging-tools.js +79 -0
  15. package/dist/src/app/ops-tools.d.ts +21 -0
  16. package/dist/src/app/ops-tools.js +158 -0
  17. package/dist/src/app/remote-agent-tool.d.ts +27 -0
  18. package/dist/src/app/remote-agent-tool.js +461 -0
  19. package/dist/src/app/types.d.ts +61 -0
  20. package/dist/src/app/types.js +11 -0
  21. package/dist/src/app/upload-file-tool.d.ts +16 -0
  22. package/dist/src/app/upload-file-tool.js +353 -0
  23. package/dist/src/app/verbose-preflight.d.ts +2 -0
  24. package/dist/src/app/verbose-preflight.js +145 -0
  25. package/dist/src/auth/config.d.ts +79 -0
  26. package/dist/src/auth/config.js +133 -0
  27. package/dist/src/auth/credential-manager.d.ts +65 -0
  28. package/dist/src/auth/credential-manager.js +122 -0
  29. package/dist/src/auth/index.d.ts +6 -0
  30. package/dist/src/auth/index.js +6 -0
  31. package/dist/src/auth/verify.d.ts +42 -0
  32. package/dist/src/auth/verify.js +60 -0
  33. package/dist/src/channel.d.ts +269 -0
  34. package/dist/src/channel.js +488 -0
  35. package/dist/src/compat.d.ts +37 -0
  36. package/dist/src/compat.js +70 -0
  37. package/dist/src/config/config-schema.d.ts +56 -0
  38. package/dist/src/config/config-schema.js +34 -0
  39. package/dist/src/messaging/aggregator.d.ts +25 -0
  40. package/dist/src/messaging/aggregator.js +90 -0
  41. package/dist/src/messaging/collector.d.ts +27 -0
  42. package/dist/src/messaging/collector.js +76 -0
  43. package/dist/src/messaging/executor.d.ts +14 -0
  44. package/dist/src/messaging/executor.js +59 -0
  45. package/dist/src/messaging/inbound.d.ts +97 -0
  46. package/dist/src/messaging/inbound.js +63 -0
  47. package/dist/src/messaging/index.d.ts +10 -0
  48. package/dist/src/messaging/index.js +9 -0
  49. package/dist/src/messaging/mention-protocol.d.ts +42 -0
  50. package/dist/src/messaging/mention-protocol.js +74 -0
  51. package/dist/src/messaging/process-c2c-request.d.ts +55 -0
  52. package/dist/src/messaging/process-c2c-request.js +445 -0
  53. package/dist/src/messaging/process-message.d.ts +62 -0
  54. package/dist/src/messaging/process-message.js +282 -0
  55. package/dist/src/messaging/scheduler.d.ts +17 -0
  56. package/dist/src/messaging/scheduler.js +47 -0
  57. package/dist/src/messaging/types.d.ts +34 -0
  58. package/dist/src/messaging/types.js +4 -0
  59. package/dist/src/plugin-ui/assets/openagent-override.js +9267 -0
  60. package/dist/src/plugin-ui/index.d.ts +13 -0
  61. package/dist/src/plugin-ui/index.js +16 -0
  62. package/dist/src/plugin-ui/ui-extension-loader/backup.d.ts +23 -0
  63. package/dist/src/plugin-ui/ui-extension-loader/backup.js +82 -0
  64. package/dist/src/plugin-ui/ui-extension-loader/index.d.ts +28 -0
  65. package/dist/src/plugin-ui/ui-extension-loader/index.js +240 -0
  66. package/dist/src/plugin-ui/ui-extension-loader/locator.d.ts +35 -0
  67. package/dist/src/plugin-ui/ui-extension-loader/locator.js +129 -0
  68. package/dist/src/plugin-ui/ui-extension-loader/manifest.d.ts +26 -0
  69. package/dist/src/plugin-ui/ui-extension-loader/manifest.js +45 -0
  70. package/dist/src/plugin-ui/ui-extension-loader/registry-regex.d.ts +9 -0
  71. package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +701 -0
  72. package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.d.ts +8 -0
  73. package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.js +58 -0
  74. package/dist/src/plugin-ui/ui-extension-loader/types.d.ts +55 -0
  75. package/dist/src/plugin-ui/ui-extension-loader/types.js +23 -0
  76. package/dist/src/proxy/auth-proxy.d.ts +20 -0
  77. package/dist/src/proxy/auth-proxy.js +337 -0
  78. package/dist/src/runtime/account.d.ts +156 -0
  79. package/dist/src/runtime/account.js +491 -0
  80. package/dist/src/runtime/index.d.ts +5 -0
  81. package/dist/src/runtime/index.js +6 -0
  82. package/dist/src/runtime/plugin-runtime.d.ts +40 -0
  83. package/dist/src/runtime/plugin-runtime.js +72 -0
  84. package/dist/src/runtime/registry.d.ts +41 -0
  85. package/dist/src/runtime/registry.js +60 -0
  86. package/dist/src/sdk/CLASS_MAP.md +143 -0
  87. package/dist/src/sdk/index.d.ts +126 -0
  88. package/dist/src/sdk/index.js +23990 -0
  89. package/dist/src/sdk/modules/cloud-search-module.js +1117 -0
  90. package/dist/src/sdk/modules/follow-module.js +1069 -0
  91. package/dist/src/sdk/modules/group-module.js +7397 -0
  92. package/dist/src/sdk/modules/relationship-module.js +2269 -0
  93. package/dist/src/sdk/modules/signaling-module.js +1468 -0
  94. package/dist/src/sdk/modules/tim-upload-plugin.js +730 -0
  95. package/dist/src/sdk/node-env/http-request.js +90 -0
  96. package/dist/src/sdk/node-env/index.js +57 -0
  97. package/dist/src/sdk/node-env/storage.js +114 -0
  98. package/dist/src/sdk/package.json +10 -0
  99. package/dist/src/sdk/sdk/CLASS_MAP.md +143 -0
  100. package/dist/src/sdk/sdk/index.d.ts +126 -0
  101. package/dist/src/sdk/sdk/index.js +23990 -0
  102. package/dist/src/sdk/sdk/modules/cloud-search-module.js +1117 -0
  103. package/dist/src/sdk/sdk/modules/follow-module.js +1069 -0
  104. package/dist/src/sdk/sdk/modules/group-module.js +7397 -0
  105. package/dist/src/sdk/sdk/modules/relationship-module.js +2269 -0
  106. package/dist/src/sdk/sdk/modules/signaling-module.js +1468 -0
  107. package/dist/src/sdk/sdk/modules/tim-upload-plugin.js +730 -0
  108. package/dist/src/sdk/sdk/node-env/http-request.js +90 -0
  109. package/dist/src/sdk/sdk/node-env/index.js +57 -0
  110. package/dist/src/sdk/sdk/node-env/storage.js +114 -0
  111. package/dist/src/sdk/sdk/package.json +10 -0
  112. package/dist/src/sdk/sdk/tsconfig.json +16 -0
  113. package/dist/src/sdk/tsconfig.json +16 -0
  114. package/dist/src/state/pending-invocation-store.d.ts +26 -0
  115. package/dist/src/state/pending-invocation-store.js +39 -0
  116. package/dist/src/state/store.d.ts +165 -0
  117. package/dist/src/state/store.js +535 -0
  118. package/dist/src/tim/c2c.d.ts +83 -0
  119. package/dist/src/tim/c2c.js +318 -0
  120. package/dist/src/tim/channels.d.ts +95 -0
  121. package/dist/src/tim/channels.js +279 -0
  122. package/dist/src/tim/client.d.ts +54 -0
  123. package/dist/src/tim/client.js +268 -0
  124. package/dist/src/tim/index.d.ts +6 -0
  125. package/dist/src/tim/index.js +6 -0
  126. package/dist/src/tim/messages.d.ts +50 -0
  127. package/dist/src/tim/messages.js +104 -0
  128. package/dist/src/tim/sdk-logger-init.d.ts +13 -0
  129. package/dist/src/tim/sdk-logger-init.js +46 -0
  130. package/dist/src/tools.d.ts +9 -0
  131. package/dist/src/tools.js +8 -0
  132. package/dist/src/transport/factory.d.ts +63 -0
  133. package/dist/src/transport/factory.js +54 -0
  134. package/dist/src/transport/oasn/index.d.ts +12 -0
  135. package/dist/src/transport/oasn/index.js +9 -0
  136. package/dist/src/transport/oasn/oasn-agent-card.d.ts +38 -0
  137. package/dist/src/transport/oasn/oasn-agent-card.js +102 -0
  138. package/dist/src/transport/oasn/oasn-discovery.d.ts +33 -0
  139. package/dist/src/transport/oasn/oasn-discovery.js +97 -0
  140. package/dist/src/transport/oasn/oasn-files.d.ts +64 -0
  141. package/dist/src/transport/oasn/oasn-files.js +174 -0
  142. package/dist/src/transport/oasn/oasn-http.d.ts +98 -0
  143. package/dist/src/transport/oasn/oasn-http.js +362 -0
  144. package/dist/src/transport/oasn/oasn-invocation.d.ts +154 -0
  145. package/dist/src/transport/oasn/oasn-invocation.js +432 -0
  146. package/dist/src/transport/oasn/oasn-normalize.d.ts +6 -0
  147. package/dist/src/transport/oasn/oasn-normalize.js +112 -0
  148. package/dist/src/transport/oasn/oasn-register.d.ts +19 -0
  149. package/dist/src/transport/oasn/oasn-register.js +24 -0
  150. package/dist/src/transport/oasn/oasn-transport.d.ts +114 -0
  151. package/dist/src/transport/oasn/oasn-transport.js +230 -0
  152. package/dist/src/transport/oasn/oasn-types.d.ts +331 -0
  153. package/dist/src/transport/oasn/oasn-types.js +44 -0
  154. package/dist/src/transport/tim/index.d.ts +7 -0
  155. package/dist/src/transport/tim/index.js +6 -0
  156. package/dist/src/transport/tim/tim-transport.d.ts +122 -0
  157. package/dist/src/transport/tim/tim-transport.js +402 -0
  158. package/dist/src/transport/types.d.ts +450 -0
  159. package/dist/src/transport/types.js +38 -0
  160. package/dist/src/util/http.d.ts +21 -0
  161. package/dist/src/util/http.js +93 -0
  162. package/dist/src/util/logger.d.ts +20 -0
  163. package/dist/src/util/logger.js +100 -0
  164. package/dist/src/util/url-resolver.d.ts +7 -0
  165. package/dist/src/util/url-resolver.js +20 -0
  166. package/index.ts +11 -0
  167. package/openclaw.plugin.json +9 -0
  168. package/package.json +7 -4
  169. package/src/app/download-file-tool.ts +133 -0
  170. package/src/app/hooks.ts +89 -5
  171. package/src/app/index.ts +6 -0
  172. package/src/app/remote-agent-tool.ts +46 -0
  173. package/src/app/types.ts +1 -0
  174. package/src/app/upload-file-tool.ts +411 -0
  175. package/src/plugin-ui/assets/openagent-override.js +1 -63
  176. package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -61
  177. package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -1
  178. package/src/plugin-ui/ui-extension-loader/registry-regex.ts +30 -13
  179. package/src/runtime/account.ts +0 -23
  180. package/src/transport/oasn/oasn-files.ts +5 -5
  181. package/src/transport/oasn/oasn-invocation.ts +44 -2
  182. package/src/transport/oasn/oasn-normalize.ts +0 -26
  183. package/src/transport/oasn/oasn-register.ts +8 -81
  184. package/src/transport/oasn/oasn-transport.ts +9 -32
  185. package/src/transport/oasn/oasn-types.ts +53 -6
  186. package/src/transport/types.ts +33 -36
  187. package/src/util/url-resolver.ts +17 -0
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Plugin UI — UIExtensionLoader 入口
3
+ *
4
+ * 将 UI 扩展加载委托给 ui-extension-loader 模块。
5
+ * 保持对外 API 不变(injectControlUiOverride),内部全部走新框架。
6
+ */
7
+ /**
8
+ * 加载全部 UI 扩展。
9
+ *
10
+ * 由 index.ts register() 调用。内部通过 Manifest Reconciler
11
+ * 自动完成扩展点的 apply / revert / skip。
12
+ */
13
+ export declare function injectControlUiOverride(): Promise<void>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Plugin UI — UIExtensionLoader 入口
3
+ *
4
+ * 将 UI 扩展加载委托给 ui-extension-loader 模块。
5
+ * 保持对外 API 不变(injectControlUiOverride),内部全部走新框架。
6
+ */
7
+ import { loadUIExtensions } from './ui-extension-loader/index.js';
8
+ /**
9
+ * 加载全部 UI 扩展。
10
+ *
11
+ * 由 index.ts register() 调用。内部通过 Manifest Reconciler
12
+ * 自动完成扩展点的 apply / revert / skip。
13
+ */
14
+ export async function injectControlUiOverride() {
15
+ await loadUIExtensions();
16
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Backup — 文件备份与还原
3
+ *
4
+ * 代码级扩展修改宿主 minified bundle 前,先备份原始文件。
5
+ * 卸载时从备份恢复。OC 升级时备份随 control-ui/ 一起被清除。
6
+ */
7
+ /**
8
+ * 备份文件。如果备份已存在则跳过(保持最早的原始版本)。
9
+ * 如果文件包含无 marker 的手工补丁,拒绝备份。
10
+ *
11
+ * @returns true = 备份就绪(已存在或新创建),false = 检测到脏 bundle,调用方必须中止 apply。
12
+ */
13
+ export declare function backupFile(filepath: string): boolean;
14
+ /**
15
+ * 从备份还原文件。
16
+ * 还原后删除备份文件。
17
+ * 如果备份不存在,记录警告但不抛异常。
18
+ */
19
+ export declare function restoreFile(filepath: string): boolean;
20
+ /**
21
+ * 检查备份是否存在。
22
+ */
23
+ export declare function hasBackup(filepath: string): boolean;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Backup — 文件备份与还原
3
+ *
4
+ * 代码级扩展修改宿主 minified bundle 前,先备份原始文件。
5
+ * 卸载时从备份恢复。OC 升级时备份随 control-ui/ 一起被清除。
6
+ */
7
+ import { copyFileSync, unlinkSync, existsSync, readFileSync } from 'node:fs';
8
+ import { logger } from '../../util/logger.js';
9
+ const BACKUP_SUFFIX = '.openagent-bak';
10
+ /** Patterns that indicate openagent modifications in the bundle.
11
+ * NOTE: __openagentMarked is NOT listed here — it's handled by the
12
+ * removed-extensions scanner for marked-expose cleanup. */
13
+ const OPENAGENT_FINGERPRINTS = [
14
+ '__openagentRenderToolCard',
15
+ '__openagentOwnsToolCards',
16
+ '__tcid',
17
+ '__openagentIsRemoteToolName',
18
+ '__openagentToolCallIds',
19
+ '__openagentKeepToolPayload',
20
+ ];
21
+ const OPENAGENT_MARKER_PATTERN = '/*openagent:';
22
+ /**
23
+ * 检测文件是否包含无 marker 的 openagent 手工补丁。
24
+ * 如果文件包含 openagent 特征字符串但没有对应的 marker 注释,
25
+ * 说明这是一个被手工修改过的文件,不应该作为"原始备份"保存。
26
+ */
27
+ function isDirtyBundle(filepath) {
28
+ try {
29
+ const content = readFileSync(filepath, 'utf-8');
30
+ const hasFingerprint = OPENAGENT_FINGERPRINTS.some(fp => content.includes(fp));
31
+ const hasMarker = content.includes(OPENAGENT_MARKER_PATTERN);
32
+ // 有 openagent 特征但没有 marker → 手工补丁
33
+ return hasFingerprint && !hasMarker;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ /**
40
+ * 备份文件。如果备份已存在则跳过(保持最早的原始版本)。
41
+ * 如果文件包含无 marker 的手工补丁,拒绝备份。
42
+ *
43
+ * @returns true = 备份就绪(已存在或新创建),false = 检测到脏 bundle,调用方必须中止 apply。
44
+ */
45
+ export function backupFile(filepath) {
46
+ const backupPath = filepath + BACKUP_SUFFIX;
47
+ if (existsSync(backupPath)) {
48
+ logger.debug(`[ui-ext] Backup already exists: ${backupPath}`);
49
+ return true;
50
+ }
51
+ // 检测脏 bundle:包含 openagent 代码但没有 marker
52
+ if (isDirtyBundle(filepath)) {
53
+ logger.warn(`[ui-ext] Dirty bundle detected: ${filepath} contains unmarked openagent patches. ` +
54
+ `Refusing to backup or patch. Please reinstall OpenClaw to get a clean bundle, then restart.`);
55
+ return false;
56
+ }
57
+ copyFileSync(filepath, backupPath);
58
+ logger.debug(`[ui-ext] Backed up: ${filepath}`);
59
+ return true;
60
+ }
61
+ /**
62
+ * 从备份还原文件。
63
+ * 还原后删除备份文件。
64
+ * 如果备份不存在,记录警告但不抛异常。
65
+ */
66
+ export function restoreFile(filepath) {
67
+ const backupPath = filepath + BACKUP_SUFFIX;
68
+ if (!existsSync(backupPath)) {
69
+ logger.debug(`[ui-ext] No backup found: ${backupPath}`);
70
+ return false;
71
+ }
72
+ copyFileSync(backupPath, filepath);
73
+ unlinkSync(backupPath);
74
+ logger.debug(`[ui-ext] Restored from backup: ${filepath}`);
75
+ return true;
76
+ }
77
+ /**
78
+ * 检查备份是否存在。
79
+ */
80
+ export function hasBackup(filepath) {
81
+ return existsSync(filepath + BACKUP_SUFFIX);
82
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * UIExtensionLoader — 主流程
3
+ *
4
+ * Manifest Reconciler:对比注册表(期望状态)与清单文件(当前状态),
5
+ * 自动完成扩展点的 apply / revert / skip。
6
+ *
7
+ * 共享文件的扩展按组处理:任何一个需要变更 → 恢复 backup → 重新 apply 全组。
8
+ *
9
+ * 每次 Gateway 启动时调用 loadUIExtensions()。
10
+ */
11
+ /**
12
+ * 加载全部 UI 扩展。
13
+ *
14
+ * Reconciliation 流程:
15
+ * 1. 读取 manifest(不存在视为空)
16
+ * 2. 检测 bundleHash:不同 → OC 升级 → 全部重新 apply
17
+ * 3. 独立扩展逐个 apply/revert
18
+ * 4. 共享文件扩展按组处理:任何一个变更 → 恢复 backup → 重新 apply 全组
19
+ * 5. 写入新 manifest
20
+ */
21
+ export declare function loadUIExtensions(): Promise<void>;
22
+ /**
23
+ * 卸载全部 UI 扩展(插件卸载时调用)。
24
+ *
25
+ * 共享文件扩展:恢复 backup 即可(一次还原覆盖所有修改)。
26
+ * 独立扩展:逐个 revert。
27
+ */
28
+ export declare function unloadUIExtensions(): Promise<void>;
@@ -0,0 +1,240 @@
1
+ /**
2
+ * UIExtensionLoader — 主流程
3
+ *
4
+ * Manifest Reconciler:对比注册表(期望状态)与清单文件(当前状态),
5
+ * 自动完成扩展点的 apply / revert / skip。
6
+ *
7
+ * 共享文件的扩展按组处理:任何一个需要变更 → 恢复 backup → 重新 apply 全组。
8
+ *
9
+ * 每次 Gateway 启动时调用 loadUIExtensions()。
10
+ */
11
+ import { join } from 'node:path';
12
+ import { logger } from '../../util/logger.js';
13
+ import { resolveControlUiDir, findFileByPrefix, extractBundleHash } from './locator.js';
14
+ import { readManifest, writeManifest, removeManifest } from './manifest.js';
15
+ import { restoreFile } from './backup.js';
16
+ import { UI_EXTENSIONS } from './registry-regex.js';
17
+ import { REMOVED_UI_EXTENSIONS } from './removed-extensions.js';
18
+ // ── 注入策略:正则模式链(无需 .map,21 版本全仿真验证通过)──────────────────
19
+ /**
20
+ * 获取当前 bundle 的 hash(用于检测 OC 升级)。
21
+ */
22
+ function getCurrentBundleHash(controlUiDir) {
23
+ const assetsDir = join(controlUiDir, 'assets');
24
+ const bundlePath = findFileByPrefix(assetsDir, 'index-');
25
+ return bundlePath ? extractBundleHash(bundlePath) : 'unknown';
26
+ }
27
+ /**
28
+ * 判断一个扩展是否需要变更(新增、更新、或删除)。
29
+ */
30
+ function needsChange(ext, // null = 已从注册表删除
31
+ oldVersion // null = manifest 中不存在
32
+ ) {
33
+ if (ext === null)
34
+ return true; // 需要删除
35
+ if (oldVersion === null)
36
+ return true; // 需要新增
37
+ return ext.version !== oldVersion; // 需要更新
38
+ }
39
+ /**
40
+ * 加载全部 UI 扩展。
41
+ *
42
+ * Reconciliation 流程:
43
+ * 1. 读取 manifest(不存在视为空)
44
+ * 2. 检测 bundleHash:不同 → OC 升级 → 全部重新 apply
45
+ * 3. 独立扩展逐个 apply/revert
46
+ * 4. 共享文件扩展按组处理:任何一个变更 → 恢复 backup → 重新 apply 全组
47
+ * 5. 写入新 manifest
48
+ */
49
+ export async function loadUIExtensions() {
50
+ const controlUiDir = resolveControlUiDir();
51
+ if (!controlUiDir) {
52
+ logger.warn('[ui-ext] control-ui directory not found, skipping');
53
+ return;
54
+ }
55
+ logger.info(`[ui-ext] control-ui located: ${controlUiDir}`);
56
+ const currentHash = getCurrentBundleHash(controlUiDir);
57
+ const manifest = readManifest(controlUiDir);
58
+ const removedIds = new Set(REMOVED_UI_EXTENSIONS.map(ext => ext.id));
59
+ // bundleHash 不同 → OC 升级了
60
+ const isOcUpgrade = manifest !== null && manifest.bundleHash !== currentHash;
61
+ const needFullReapply = isOcUpgrade;
62
+ if (isOcUpgrade) {
63
+ logger.info(`[ui-ext] OC upgrade detected (${manifest.bundleHash} → ${currentHash}), re-applying all`);
64
+ }
65
+ // These are removed backend dist patches from the previous SSE strategy.
66
+ // Scan every startup regardless of manifest state: migration sites may have
67
+ // missing manifests, stale backups, or hand-applied copies.
68
+ // Track which shared files were restored so we force those groups to rebuild.
69
+ const forceSharedRebuild = new Set();
70
+ for (const ext of REMOVED_UI_EXTENSIONS) {
71
+ const result = safeRevertRemoved(ext, controlUiDir);
72
+ if (result === 'failed') {
73
+ logger.error(`[ui-ext] ${ext.id}: removed backend patch could not be reverted; aborting UI extension load`);
74
+ return;
75
+ }
76
+ if (result === 'restored' && ext.sharedFile) {
77
+ forceSharedRebuild.add(ext.sharedFile);
78
+ logger.info(`[ui-ext] ${ext.id}: shared file '${ext.sharedFile}' restored, will force group rebuild`);
79
+ }
80
+ }
81
+ const oldExtensions = (manifest && !needFullReapply)
82
+ ? Object.fromEntries(Object.entries(manifest.extensions).filter(([id]) => !removedIds.has(id)))
83
+ : {};
84
+ // ── 分组:独立扩展 vs 共享文件扩展 ──
85
+ const independentExts = [];
86
+ const sharedGroups = new Map();
87
+ for (const ext of UI_EXTENSIONS) {
88
+ if (ext.sharedFile) {
89
+ const group = sharedGroups.get(ext.sharedFile) ?? [];
90
+ group.push(ext);
91
+ sharedGroups.set(ext.sharedFile, group);
92
+ }
93
+ else {
94
+ independentExts.push(ext);
95
+ }
96
+ }
97
+ const newExtensions = {};
98
+ let applied = 0;
99
+ let skipped = 0;
100
+ // ── 处理独立扩展(逐个 apply/revert)──
101
+ for (const ext of independentExts) {
102
+ const old = oldExtensions[ext.id];
103
+ if (old && old.version === ext.version && !ext.alwaysApply) {
104
+ skipped++;
105
+ newExtensions[ext.id] = { version: ext.version };
106
+ logger.debug(`[ui-ext] ${ext.id}: skip (v${ext.version} unchanged)`);
107
+ continue;
108
+ }
109
+ if (old && old.version !== ext.version) {
110
+ // 版本变了,先 revert
111
+ logger.debug(`[ui-ext] ${ext.id}: revert (v${old.version} → v${ext.version})`);
112
+ safeRevert(ext, controlUiDir);
113
+ }
114
+ logger.debug(`[ui-ext] ${ext.id}: apply v${ext.version}`);
115
+ if (safeApply(ext, controlUiDir)) {
116
+ newExtensions[ext.id] = { version: ext.version };
117
+ applied++;
118
+ }
119
+ }
120
+ // ── 处理共享文件扩展(按组批量 reconcile)──
121
+ for (const [sharedFile, group] of sharedGroups) {
122
+ // 检查这个组里是否有任何扩展需要变更
123
+ let groupNeedsChange = forceSharedRebuild.has(sharedFile);
124
+ if (groupNeedsChange) {
125
+ logger.info(`[ui-ext] Shared group '${sharedFile}': forced rebuild due to removed extension cleanup`);
126
+ }
127
+ if (!groupNeedsChange) {
128
+ for (const ext of group) {
129
+ const old = oldExtensions[ext.id];
130
+ if (needsChange(ext, old?.version ?? null)) {
131
+ groupNeedsChange = true;
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ // 检查是否有被删除的扩展(在 manifest 中属于该组但不在当前注册表中)
137
+ // 如果有,必须触发组重建以清除残留补丁
138
+ const groupRegistryIds = new Set(group.map(e => e.id));
139
+ for (const oldId of Object.keys(oldExtensions)) {
140
+ // 只关心曾经属于本组的扩展(不在当前注册表中 + 不在当前组中)
141
+ if (!groupRegistryIds.has(oldId) && !UI_EXTENSIONS.some(e => e.id === oldId)) {
142
+ // 无法确定旧扩展属于哪个组(manifest 不记录 sharedFile),保守触发重建
143
+ logger.info(`[ui-ext] Orphaned extension '${oldId}' found, triggering group '${sharedFile}' rebuild`);
144
+ groupNeedsChange = true;
145
+ break;
146
+ }
147
+ }
148
+ if (!groupNeedsChange) {
149
+ // 全组无变更,跳过
150
+ for (const ext of group) {
151
+ newExtensions[ext.id] = { version: ext.version };
152
+ skipped++;
153
+ logger.debug(`[ui-ext] ${ext.id}: skip (shared group '${sharedFile}' unchanged)`);
154
+ }
155
+ continue;
156
+ }
157
+ // 有变更 → 恢复 backup → 重新 apply 全组
158
+ logger.info(`[ui-ext] Shared group '${sharedFile}': changes detected, rebuilding`);
159
+ // 恢复 backup(仅当之前有补丁时才需要还原)
160
+ const assetsDir = join(controlUiDir, 'assets');
161
+ const bundlePath = findFileByPrefix(assetsDir, 'index-');
162
+ if (bundlePath) {
163
+ const restored = restoreFile(bundlePath);
164
+ if (restored) {
165
+ logger.debug(`[ui-ext] Restored backup for shared group '${sharedFile}'`);
166
+ }
167
+ else {
168
+ logger.debug(`[ui-ext] No backup to restore (first install or OC upgrade)`);
169
+ }
170
+ }
171
+ // 重新 apply 全组中所有应保留的扩展
172
+ for (const ext of group) {
173
+ logger.debug(`[ui-ext] ${ext.id}: apply v${ext.version} (group rebuild)`);
174
+ if (safeApply(ext, controlUiDir)) {
175
+ newExtensions[ext.id] = { version: ext.version };
176
+ applied++;
177
+ }
178
+ }
179
+ }
180
+ // ── 写入新 manifest ──
181
+ const newManifest = {
182
+ bundleHash: currentHash,
183
+ strategy: 'regex',
184
+ extensions: newExtensions,
185
+ };
186
+ writeManifest(controlUiDir, newManifest);
187
+ logger.info(`[ui-ext] Done: ${applied} applied, ${skipped} skipped`);
188
+ }
189
+ /**
190
+ * 卸载全部 UI 扩展(插件卸载时调用)。
191
+ *
192
+ * 共享文件扩展:恢复 backup 即可(一次还原覆盖所有修改)。
193
+ * 独立扩展:逐个 revert。
194
+ */
195
+ export async function unloadUIExtensions() {
196
+ const controlUiDir = resolveControlUiDir();
197
+ if (!controlUiDir)
198
+ return;
199
+ // 先恢复共享文件的 backup
200
+ const assetsDir = join(controlUiDir, 'assets');
201
+ const bundlePath = findFileByPrefix(assetsDir, 'index-');
202
+ if (bundlePath) {
203
+ restoreFile(bundlePath);
204
+ }
205
+ // 再逆序 revert 独立扩展
206
+ for (const ext of UI_EXTENSIONS.slice().reverse()) {
207
+ if (!ext.sharedFile) {
208
+ safeRevert(ext, controlUiDir);
209
+ }
210
+ }
211
+ removeManifest(controlUiDir);
212
+ logger.info('[ui-ext] All UI extensions unloaded');
213
+ }
214
+ // ── Helpers ──────────────────────────────────────────────────────────────
215
+ function safeApply(ext, controlUiDir) {
216
+ try {
217
+ return ext.apply(controlUiDir);
218
+ }
219
+ catch (err) {
220
+ logger.error(`[ui-ext] Failed to apply '${ext.id}': ${err.message}`);
221
+ return false;
222
+ }
223
+ }
224
+ function safeRevert(ext, controlUiDir) {
225
+ try {
226
+ ext.revert(controlUiDir);
227
+ }
228
+ catch (err) {
229
+ logger.error(`[ui-ext] Failed to revert '${ext.id}': ${err.message}`);
230
+ }
231
+ }
232
+ function safeRevertRemoved(ext, controlUiDir) {
233
+ try {
234
+ return ext.revert(controlUiDir);
235
+ }
236
+ catch (err) {
237
+ logger.error(`[ui-ext] Failed to revert removed extension '${ext.id}': ${err.message}`);
238
+ return 'failed';
239
+ }
240
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Locator — 定位 OpenClaw control-ui 目录和文件
3
+ *
4
+ * 唯一定位方法:require.resolve('openclaw')
5
+ * 插件通过 symlink 依赖宿主 openclaw 包,如果 symlink 不存在,
6
+ * 插件的 import 语句就会失败,根本执行不到这里。
7
+ */
8
+ /**
9
+ * 定位 control-ui 目录。
10
+ *
11
+ * require.resolve('openclaw')
12
+ * → /usr/lib/node_modules/openclaw/dist/index.js
13
+ * → dirname → dist/
14
+ * → join('control-ui') → dist/control-ui/
15
+ */
16
+ export declare function resolveControlUiDir(): string | null;
17
+ /**
18
+ * 定位 OpenClaw dist 目录。
19
+ *
20
+ * control-ui 是 dist/control-ui 的子目录,后端 gateway-cli-*.js 也在同一个
21
+ * dist 下。这里优先从已解析出的 control-ui 反推,避免 Docker 场景下
22
+ * require.resolve('openclaw') 失效。
23
+ */
24
+ export declare function resolveOpenClawDistDir(controlUiDir?: string | null): string | null;
25
+ /**
26
+ * 在目录中查找匹配前缀的第一个文件。
27
+ * 用于定位 Vite code-split 后的文件(如 index-D7mg1IkY.js)。
28
+ * @param suffix 文件后缀,默认 '.js',传 '.js.map' 可找 source map
29
+ */
30
+ export declare function findFileByPrefix(dir: string, prefix: string, suffix?: string): string | null;
31
+ /**
32
+ * 获取 bundle 文件名中的 hash 部分,用作 bundleHash。
33
+ * index-D7mg1IkY.js → D7mg1IkY
34
+ */
35
+ export declare function extractBundleHash(bundlePath: string): string;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Locator — 定位 OpenClaw control-ui 目录和文件
3
+ *
4
+ * 唯一定位方法:require.resolve('openclaw')
5
+ * 插件通过 symlink 依赖宿主 openclaw 包,如果 symlink 不存在,
6
+ * 插件的 import 语句就会失败,根本执行不到这里。
7
+ */
8
+ import { existsSync, readdirSync } from 'node:fs';
9
+ import { dirname, join, resolve } from 'node:path';
10
+ import { createRequire } from 'node:module';
11
+ import { logger } from '../../util/logger.js';
12
+ function addControlUiCandidatesFromEntrypoint(candidates, entryPoint) {
13
+ if (!entryPoint)
14
+ return;
15
+ const entryDir = dirname(resolve(entryPoint));
16
+ candidates.push(
17
+ // npm/global install: openclaw/dist/index.js -> openclaw/dist/control-ui
18
+ join(entryDir, 'control-ui'),
19
+ // package root entry: openclaw/openclaw.mjs or openclaw/index.js -> openclaw/dist/control-ui
20
+ join(entryDir, 'dist', 'control-ui'),
21
+ // package sub-entry fallback: openclaw/dist/index.js -> openclaw/dist/control-ui
22
+ join(dirname(entryDir), 'dist', 'control-ui'));
23
+ }
24
+ function addDistCandidatesFromEntrypoint(candidates, entryPoint) {
25
+ if (!entryPoint)
26
+ return;
27
+ const entryDir = dirname(resolve(entryPoint));
28
+ candidates.push(
29
+ // npm/global install: openclaw/dist/index.js -> openclaw/dist
30
+ entryDir,
31
+ // package root entry: openclaw/openclaw.mjs or openclaw/index.js -> openclaw/dist
32
+ join(entryDir, 'dist'),
33
+ // package sub-entry fallback: openclaw/dist/index.js -> openclaw/dist
34
+ join(dirname(entryDir), 'dist'));
35
+ }
36
+ /**
37
+ * 定位 control-ui 目录。
38
+ *
39
+ * require.resolve('openclaw')
40
+ * → /usr/lib/node_modules/openclaw/dist/index.js
41
+ * → dirname → dist/
42
+ * → join('control-ui') → dist/control-ui/
43
+ */
44
+ export function resolveControlUiDir() {
45
+ const candidates = [];
46
+ // In local development it is possible for the plugin's peerDependency
47
+ // symlink to resolve to a different OpenClaw installation than the gateway
48
+ // process that is currently serving /chat. Prefer the actual process
49
+ // entrypoint when available.
50
+ addControlUiCandidatesFromEntrypoint(candidates, process.argv[1]);
51
+ try {
52
+ const require = createRequire(import.meta.url);
53
+ const entryPoint = require.resolve('openclaw');
54
+ addControlUiCandidatesFromEntrypoint(candidates, entryPoint);
55
+ }
56
+ catch {
57
+ // Docker installs the plugin outside /app/node_modules, so resolving the
58
+ // host package from the plugin path can fail even though OpenClaw is
59
+ // running from /app. Fall through to cwd-based candidates below.
60
+ }
61
+ // Docker image layout: process cwd is /app, UI lives at /app/dist/control-ui.
62
+ candidates.push(join(process.cwd(), 'dist', 'control-ui'), join(resolve(process.cwd(), '..'), 'dist', 'control-ui'));
63
+ for (const candidate of candidates) {
64
+ if (existsSync(candidate)) {
65
+ logger.debug(`[ui-ext] control-ui located: ${candidate}`);
66
+ return candidate;
67
+ }
68
+ }
69
+ logger.warn('[ui-ext] Could not locate control-ui directory');
70
+ return null;
71
+ }
72
+ /**
73
+ * 定位 OpenClaw dist 目录。
74
+ *
75
+ * control-ui 是 dist/control-ui 的子目录,后端 gateway-cli-*.js 也在同一个
76
+ * dist 下。这里优先从已解析出的 control-ui 反推,避免 Docker 场景下
77
+ * require.resolve('openclaw') 失效。
78
+ */
79
+ export function resolveOpenClawDistDir(controlUiDir) {
80
+ const candidates = [];
81
+ if (controlUiDir) {
82
+ candidates.push(dirname(controlUiDir));
83
+ }
84
+ addDistCandidatesFromEntrypoint(candidates, process.argv[1]);
85
+ try {
86
+ const require = createRequire(import.meta.url);
87
+ const entryPoint = require.resolve('openclaw');
88
+ addDistCandidatesFromEntrypoint(candidates, entryPoint);
89
+ }
90
+ catch {
91
+ // Docker 插件路径下可能无法 resolve 宿主 openclaw;继续走 cwd 兜底。
92
+ }
93
+ candidates.push(join(process.cwd(), 'dist'), join(resolve(process.cwd(), '..'), 'dist'));
94
+ for (const candidate of candidates) {
95
+ if (existsSync(candidate)) {
96
+ logger.debug(`[ui-ext] openclaw dist located: ${candidate}`);
97
+ return candidate;
98
+ }
99
+ }
100
+ logger.warn('[ui-ext] Could not locate OpenClaw dist directory');
101
+ return null;
102
+ }
103
+ /**
104
+ * 在目录中查找匹配前缀的第一个文件。
105
+ * 用于定位 Vite code-split 后的文件(如 index-D7mg1IkY.js)。
106
+ * @param suffix 文件后缀,默认 '.js',传 '.js.map' 可找 source map
107
+ */
108
+ export function findFileByPrefix(dir, prefix, suffix = '.js') {
109
+ try {
110
+ const entries = readdirSync(dir);
111
+ const match = entries.find(e => e.startsWith(prefix) && e.endsWith(suffix));
112
+ if (match) {
113
+ logger.debug(`[ui-ext] Found file: ${match}`);
114
+ }
115
+ return match ? join(dir, match) : null;
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ /**
122
+ * 获取 bundle 文件名中的 hash 部分,用作 bundleHash。
123
+ * index-D7mg1IkY.js → D7mg1IkY
124
+ */
125
+ export function extractBundleHash(bundlePath) {
126
+ const filename = bundlePath.split('/').pop() ?? '';
127
+ const match = filename.match(/^index-(.+)\.js$/);
128
+ return match?.[1] ?? filename;
129
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Manifest — 清单文件读写
3
+ *
4
+ * 存储在 control-ui/openagent-manifest.json,
5
+ * 记录当前已加载的扩展点及其版本,用于 reconciliation。
6
+ */
7
+ export interface ManifestData {
8
+ bundleHash: string;
9
+ /** 当前使用的注入策略。 */
10
+ strategy?: 'regex';
11
+ extensions: Record<string, {
12
+ version: number;
13
+ }>;
14
+ }
15
+ /**
16
+ * 读取清单文件。不存在或解析失败返回 null。
17
+ */
18
+ export declare function readManifest(controlUiDir: string): ManifestData | null;
19
+ /**
20
+ * 写入清单文件。
21
+ */
22
+ export declare function writeManifest(controlUiDir: string, data: ManifestData): void;
23
+ /**
24
+ * 删除清单文件。
25
+ */
26
+ export declare function removeManifest(controlUiDir: string): void;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Manifest — 清单文件读写
3
+ *
4
+ * 存储在 control-ui/openagent-manifest.json,
5
+ * 记录当前已加载的扩展点及其版本,用于 reconciliation。
6
+ */
7
+ import { readFileSync, writeFileSync, unlinkSync, existsSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { logger } from '../../util/logger.js';
10
+ const MANIFEST_FILENAME = 'openagent-manifest.json';
11
+ /**
12
+ * 读取清单文件。不存在或解析失败返回 null。
13
+ */
14
+ export function readManifest(controlUiDir) {
15
+ const filepath = join(controlUiDir, MANIFEST_FILENAME);
16
+ if (!existsSync(filepath)) {
17
+ return null;
18
+ }
19
+ try {
20
+ const raw = readFileSync(filepath, 'utf-8');
21
+ return JSON.parse(raw);
22
+ }
23
+ catch (err) {
24
+ logger.warn(`[ui-ext] Failed to parse manifest: ${err.message}`);
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * 写入清单文件。
30
+ */
31
+ export function writeManifest(controlUiDir, data) {
32
+ const filepath = join(controlUiDir, MANIFEST_FILENAME);
33
+ writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf-8');
34
+ logger.debug('[ui-ext] Manifest written');
35
+ }
36
+ /**
37
+ * 删除清单文件。
38
+ */
39
+ export function removeManifest(controlUiDir) {
40
+ const filepath = join(controlUiDir, MANIFEST_FILENAME);
41
+ if (existsSync(filepath)) {
42
+ unlinkSync(filepath);
43
+ logger.debug('[ui-ext] Manifest removed');
44
+ }
45
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * UIExtension 注册表 — 正则模式链方案(无需 source map)
3
+ *
4
+ * 用正则锚点定位注入位置,无需 source map。
5
+ *
6
+ * 验证来源:full-inject.mjs — 21 版本 × 6 扩展点,acorn 语法 100% 合法。
7
+ */
8
+ import type { UIExtension } from './types.js';
9
+ export declare const UI_EXTENSIONS: UIExtension[];