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.
Files changed (64) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/src/app/remote-agent-tool.js +110 -14
  3. package/dist/src/app/types.d.ts +2 -2
  4. package/dist/src/plugin-ui/adapters/adapters/oc-2026-04.js +103 -0
  5. package/dist/src/plugin-ui/adapters/adapters/oc-2026-05.js +125 -0
  6. package/dist/src/plugin-ui/adapters/adapters/oc-2026-06.js +125 -0
  7. package/dist/src/plugin-ui/adapters/adapters/oc-unknown.js +48 -0
  8. package/dist/src/plugin-ui/adapters/oc-2026-04.js +103 -0
  9. package/dist/src/plugin-ui/adapters/oc-2026-05.js +125 -0
  10. package/dist/src/plugin-ui/adapters/oc-2026-06.js +125 -0
  11. package/dist/src/plugin-ui/adapters/oc-unknown.js +48 -0
  12. package/dist/src/plugin-ui/assets/bg.png +0 -0
  13. package/dist/src/plugin-ui/assets/icon.png +0 -0
  14. package/dist/src/plugin-ui/assets/openagent-override.js +2480 -1004
  15. package/dist/src/plugin-ui/index.d.ts +1 -1
  16. package/dist/src/plugin-ui/index.js +2 -2
  17. package/dist/src/plugin-ui/ui-extension-loader/index.d.ts +2 -1
  18. package/dist/src/plugin-ui/ui-extension-loader/index.js +5 -5
  19. package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +128 -13
  20. package/dist/src/plugin-ui/ui-extension-loader/types.d.ts +4 -1
  21. package/dist/src/state/store.d.ts +21 -0
  22. package/dist/src/state/store.js +54 -0
  23. package/dist/src/transport/oasn/oasn-invocation.d.ts +3 -0
  24. package/dist/src/transport/oasn/oasn-invocation.js +28 -12
  25. package/dist/src/transport/oasn/oasn-types.d.ts +8 -4
  26. package/index.ts +1 -1
  27. package/openclaw.plugin.json +1 -1
  28. package/package.json +5 -4
  29. package/src/app/remote-agent-tool.ts +131 -16
  30. package/src/app/types.ts +2 -2
  31. package/src/plugin-ui/adapters/oc-2026-04.js +103 -0
  32. package/src/plugin-ui/adapters/oc-2026-05.js +125 -0
  33. package/src/plugin-ui/adapters/oc-2026-06.js +125 -0
  34. package/src/plugin-ui/adapters/oc-unknown.js +48 -0
  35. package/src/plugin-ui/assets/bg.png +0 -0
  36. package/src/plugin-ui/assets/icon.png +0 -0
  37. package/src/plugin-ui/assets/openagent-override.js +2480 -1004
  38. package/src/plugin-ui/build.cjs +309 -23
  39. package/src/plugin-ui/index.ts +2 -2
  40. package/src/plugin-ui/modules/agent-book/panel/agent-book.js +187 -14
  41. package/src/plugin-ui/modules/agent-book/panel/agent-card.js +26 -10
  42. package/src/plugin-ui/modules/agent-book/panel/agent-data.js +1 -1
  43. package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +149 -1
  44. package/src/plugin-ui/modules/agent-book/panel/styles.js +352 -210
  45. package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +4 -2
  46. package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +4 -1
  47. package/src/plugin-ui/modules/agent-book/scanner.js +17 -5
  48. package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +5 -0
  49. package/src/plugin-ui/modules/loader/bootstrap.js +1 -1
  50. package/src/plugin-ui/modules/loader/shared-state.js +278 -0
  51. package/src/plugin-ui/modules/remote-agent/execution-card.js +385 -124
  52. package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +5 -23
  53. package/src/plugin-ui/modules/remote-agent/output-card.js +97 -31
  54. package/src/plugin-ui/modules/remote-agent/render-hooks.js +149 -58
  55. package/src/plugin-ui/modules/remote-agent/styles.js +690 -523
  56. package/src/plugin-ui/modules/remote-agent/tool-card-model.js +77 -3
  57. package/src/plugin-ui/postinstall-deploy.cjs +52 -0
  58. package/src/plugin-ui/ui-extension-loader/index.ts +6 -6
  59. package/src/plugin-ui/ui-extension-loader/registry-regex.ts +131 -14
  60. package/src/plugin-ui/ui-extension-loader/types.ts +5 -1
  61. package/src/state/store.ts +80 -0
  62. package/src/transport/oasn/oasn-invocation.ts +47 -12
  63. package/src/transport/oasn/oasn-types.ts +6 -2
  64. package/src/types/openclaw-plugin-sdk-media-store.d.ts +9 -0
@@ -10,4 +10,4 @@
10
10
  * 由 index.ts register() 调用。内部通过 Manifest Reconciler
11
11
  * 自动完成扩展点的 apply / revert / skip。
12
12
  */
13
- export declare function injectControlUiOverride(): Promise<void>;
13
+ export declare function injectControlUiOverride(hostVersion?: string): Promise<void>;
@@ -11,6 +11,6 @@ import { loadUIExtensions } from './ui-extension-loader/index.js';
11
11
  * 由 index.ts register() 调用。内部通过 Manifest Reconciler
12
12
  * 自动完成扩展点的 apply / revert / skip。
13
13
  */
14
- export async function injectControlUiOverride() {
15
- await loadUIExtensions();
14
+ export async function injectControlUiOverride(hostVersion) {
15
+ await loadUIExtensions({ hostVersion });
16
16
  }
@@ -8,6 +8,7 @@
8
8
  *
9
9
  * 每次 Gateway 启动时调用 loadUIExtensions()。
10
10
  */
11
+ import type { UIExtensionContext } from './types.js';
11
12
  /**
12
13
  * 加载全部 UI 扩展。
13
14
  *
@@ -18,7 +19,7 @@
18
19
  * 4. 共享文件扩展按组处理:任何一个变更 → 恢复 backup → 重新 apply 全组
19
20
  * 5. 写入新 manifest
20
21
  */
21
- export declare function loadUIExtensions(): Promise<void>;
22
+ export declare function loadUIExtensions(context?: UIExtensionContext): Promise<void>;
22
23
  /**
23
24
  * 卸载全部 UI 扩展(插件卸载时调用)。
24
25
  *
@@ -46,7 +46,7 @@ oldVersion // null = manifest 中不存在
46
46
  * 4. 共享文件扩展按组处理:任何一个变更 → 恢复 backup → 重新 apply 全组
47
47
  * 5. 写入新 manifest
48
48
  */
49
- export async function loadUIExtensions() {
49
+ export async function loadUIExtensions(context = {}) {
50
50
  const controlUiDir = resolveControlUiDir();
51
51
  if (!controlUiDir) {
52
52
  logger.warn('[ui-ext] control-ui directory not found, skipping');
@@ -112,7 +112,7 @@ export async function loadUIExtensions() {
112
112
  safeRevert(ext, controlUiDir);
113
113
  }
114
114
  logger.debug(`[ui-ext] ${ext.id}: apply v${ext.version}`);
115
- if (safeApply(ext, controlUiDir)) {
115
+ if (safeApply(ext, controlUiDir, context)) {
116
116
  newExtensions[ext.id] = { version: ext.version };
117
117
  applied++;
118
118
  }
@@ -171,7 +171,7 @@ export async function loadUIExtensions() {
171
171
  // 重新 apply 全组中所有应保留的扩展
172
172
  for (const ext of group) {
173
173
  logger.debug(`[ui-ext] ${ext.id}: apply v${ext.version} (group rebuild)`);
174
- if (safeApply(ext, controlUiDir)) {
174
+ if (safeApply(ext, controlUiDir, context)) {
175
175
  newExtensions[ext.id] = { version: ext.version };
176
176
  applied++;
177
177
  }
@@ -212,9 +212,9 @@ export async function unloadUIExtensions() {
212
212
  logger.info('[ui-ext] All UI extensions unloaded');
213
213
  }
214
214
  // ── Helpers ──────────────────────────────────────────────────────────────
215
- function safeApply(ext, controlUiDir) {
215
+ function safeApply(ext, controlUiDir, context) {
216
216
  try {
217
- return ext.apply(controlUiDir);
217
+ return ext.apply(controlUiDir, context);
218
218
  }
219
219
  catch (err) {
220
220
  logger.error(`[ui-ext] Failed to apply '${ext.id}': ${err.message}`);
@@ -14,9 +14,76 @@ import { backupFile, restoreFile } from './backup.js';
14
14
  import { makeMarker, hasMarker } from './types.js';
15
15
  // ── 常量 ──────────────────────────────────────────────────────────────────
16
16
  const OVERRIDE_ASSET_URL = new URL('../assets/openagent-override.js', import.meta.url);
17
- function getOverrideAssetHash() {
17
+ const ICON_ASSET_URL = new URL('../assets/icon.png', import.meta.url);
18
+ const BG_ASSET_URL = new URL('../assets/bg.png', import.meta.url);
19
+ const HOST_ADAPTER_URLS = {
20
+ 'oc-2026-04': new URL('../adapters/oc-2026-04.js', import.meta.url),
21
+ 'oc-2026-05': new URL('../adapters/oc-2026-05.js', import.meta.url),
22
+ 'oc-2026-06': new URL('../adapters/oc-2026-06.js', import.meta.url),
23
+ 'oc-unknown': new URL('../adapters/oc-unknown.js', import.meta.url),
24
+ };
25
+ const HOST_ADAPTER_START = '/*__OPENAGENT_HOST_ADAPTER_START__*/';
26
+ const HOST_ADAPTER_END = '/*__OPENAGENT_HOST_ADAPTER_END__*/';
27
+ const HOST_ADAPTER_BLOCK_RE = /\/\*__OPENAGENT_HOST_ADAPTER_START__\*\/[\s\S]*?\/\*__OPENAGENT_HOST_ADAPTER_END__\*\//;
28
+ const BUILD_ID_TOKEN = "buildId: '__OPENAGENT_BUILD_ID__'";
29
+ const BUILD_ID_ASSIGNMENT_RE = /buildId:\s*(?:"[^"]*"|'[^']*')/;
30
+ function normalizeHostVersion(hostVersion) {
31
+ const value = String(hostVersion || '').trim();
32
+ if (!value || value === 'unknown')
33
+ return '';
34
+ const match = value.match(/20\d{2}\.\d{1,2}\.\d{1,2}(?:-[A-Za-z0-9._-]+)?/);
35
+ return match ? match[0] : '';
36
+ }
37
+ function resolveHostAdapterId(hostVersion) {
38
+ const version = normalizeHostVersion(hostVersion);
39
+ const parts = version.split('.');
40
+ const year = Number.parseInt(parts[0], 10);
41
+ const month = Number.parseInt(parts[1], 10);
42
+ if (year === 2026 && month === 4)
43
+ return 'oc-2026-04';
44
+ if (year === 2026 && month === 5)
45
+ return 'oc-2026-05';
46
+ if (year === 2026 && month === 6)
47
+ return 'oc-2026-06';
48
+ return 'oc-unknown';
49
+ }
50
+ function getHostAdapterBlock(hostVersion) {
51
+ let adapterId = resolveHostAdapterId(hostVersion);
52
+ let adapterUrl = HOST_ADAPTER_URLS[adapterId];
53
+ if (!adapterUrl || !existsSync(adapterUrl)) {
54
+ adapterId = 'oc-unknown';
55
+ adapterUrl = HOST_ADAPTER_URLS[adapterId];
56
+ }
57
+ const adapterSource = readFileSync(adapterUrl, 'utf-8').trimEnd();
58
+ return [
59
+ HOST_ADAPTER_START,
60
+ `// HOST ADAPTER: ${adapterId}`,
61
+ adapterSource,
62
+ HOST_ADAPTER_END,
63
+ ].join('\n');
64
+ }
65
+ function injectHostAdapter(source, hostVersion) {
66
+ const block = getHostAdapterBlock(hostVersion);
67
+ return HOST_ADAPTER_BLOCK_RE.test(source)
68
+ ? source.replace(HOST_ADAPTER_BLOCK_RE, block)
69
+ : source;
70
+ }
71
+ function injectBuildId(source) {
72
+ const normalized = source.replace(BUILD_ID_ASSIGNMENT_RE, BUILD_ID_TOKEN);
73
+ const buildId = createHash('sha256').update(normalized).digest('hex').slice(0, 12);
74
+ return normalized.replace(BUILD_ID_TOKEN, `buildId: ${JSON.stringify(buildId)}`);
75
+ }
76
+ function getOverrideAssetContent(context) {
77
+ let source = readFileSync(OVERRIDE_ASSET_URL, 'utf-8');
78
+ const hostVersion = normalizeHostVersion(context?.hostVersion);
79
+ if (hostVersion) {
80
+ source = source.replace("hostVersion: '__OPENCLAW_HOST_VERSION__'", `hostVersion: ${JSON.stringify(hostVersion)}`);
81
+ }
82
+ return injectBuildId(injectHostAdapter(source, hostVersion));
83
+ }
84
+ function getOverrideAssetHash(context) {
18
85
  return createHash('sha256')
19
- .update(readFileSync(OVERRIDE_ASSET_URL))
86
+ .update(getOverrideAssetContent(context))
20
87
  .digest('hex')
21
88
  .slice(0, 12);
22
89
  }
@@ -97,22 +164,43 @@ const overrideScript = {
97
164
  id: 'override-script',
98
165
  version: 1,
99
166
  alwaysApply: true,
100
- apply(controlUiDir) {
167
+ apply(controlUiDir, context) {
101
168
  if (!existsSync(OVERRIDE_ASSET_URL)) {
102
169
  logger.warn('[ui-ext:regex] override-script: asset missing');
103
170
  return false;
104
171
  }
105
172
  const dest = join(controlUiDir, 'openagent-override.js');
173
+ const srcContent = getOverrideAssetContent(context);
106
174
  if (existsSync(dest)) {
107
- const srcContent = readFileSync(OVERRIDE_ASSET_URL, 'utf-8');
108
175
  const destContent = readFileSync(dest, 'utf-8');
109
176
  if (srcContent === destContent) {
110
177
  logger.debug('[ui-ext:regex] override-script: content unchanged, skip');
111
178
  return true;
112
179
  }
113
180
  }
114
- copyFileSync(OVERRIDE_ASSET_URL, dest);
181
+ writeFileSync(dest, srcContent, 'utf-8');
115
182
  logger.info('[ui-ext:regex] override-script: deployed openagent-override.js');
183
+ // Deploy icon.png alongside
184
+ if (existsSync(ICON_ASSET_URL)) {
185
+ const iconDest = join(controlUiDir, 'icon.png');
186
+ try {
187
+ copyFileSync(ICON_ASSET_URL, iconDest);
188
+ logger.info('[ui-ext:regex] override-script: deployed icon.png');
189
+ }
190
+ catch (err) {
191
+ logger.warn('[ui-ext:regex] override-script: failed to deploy icon.png');
192
+ }
193
+ }
194
+ if (existsSync(BG_ASSET_URL)) {
195
+ const bgDest = join(controlUiDir, 'bg.png');
196
+ try {
197
+ copyFileSync(BG_ASSET_URL, bgDest);
198
+ logger.info('[ui-ext:regex] override-script: deployed bg.png');
199
+ }
200
+ catch (err) {
201
+ logger.warn('[ui-ext:regex] override-script: failed to deploy bg.png');
202
+ }
203
+ }
116
204
  return true;
117
205
  },
118
206
  revert(controlUiDir) {
@@ -121,14 +209,30 @@ const overrideScript = {
121
209
  unlinkSync(dest);
122
210
  logger.info('[ui-ext:regex] override-script: removed');
123
211
  }
212
+ const iconDest = join(controlUiDir, 'icon.png');
213
+ if (existsSync(iconDest)) {
214
+ try {
215
+ unlinkSync(iconDest);
216
+ logger.info('[ui-ext:regex] override-script: removed icon.png');
217
+ }
218
+ catch { }
219
+ }
220
+ const bgDest = join(controlUiDir, 'bg.png');
221
+ if (existsSync(bgDest)) {
222
+ try {
223
+ unlinkSync(bgDest);
224
+ logger.info('[ui-ext:regex] override-script: removed bg.png');
225
+ }
226
+ catch { }
227
+ }
124
228
  },
125
229
  };
126
230
  // ── Extension 2: html-script-tag ─────────────────────────────────────────
127
231
  const htmlScriptTag = {
128
232
  id: 'html-script-tag',
129
- version: 4,
233
+ version: 5,
130
234
  alwaysApply: true,
131
- apply(controlUiDir) {
235
+ apply(controlUiDir, context) {
132
236
  const indexPath = join(controlUiDir, 'index.html');
133
237
  if (!existsSync(indexPath)) {
134
238
  logger.warn('[ui-ext:regex] html-script-tag: index.html not found');
@@ -136,11 +240,23 @@ const htmlScriptTag = {
136
240
  }
137
241
  let html = readFileSync(indexPath, 'utf-8');
138
242
  const htmlMarker = `<!--openagent:${this.id}-->`;
139
- const overrideHash = getOverrideAssetHash();
140
- const scriptTag = `${htmlMarker}<script src="./openagent-override.js?v=${overrideHash}"></script>`;
243
+ const markerPattern = /(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)/;
244
+ const overrideHash = getOverrideAssetHash(context);
245
+ const hostVersion = normalizeHostVersion(context?.hostVersion);
246
+ const hostVersionAttr = hostVersion ? ` data-openagent-host-version="${hostVersion}"` : '';
247
+ const scriptTag = `${htmlMarker}<script src="./openagent-override.js?v=${overrideHash}"${hostVersionAttr}></script>`;
141
248
  const markedScriptPattern = /(?:\/\*openagent:html-script-tag\*\/|<!--openagent:html-script-tag-->)<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
142
- if (html.includes(htmlMarker) || hasMarker(html, this.id)) {
143
- const nextHtml = html.replace(markedScriptPattern, `${scriptTag}\n `);
249
+ const oldScriptPattern = /<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
250
+ if (markerPattern.test(html)) {
251
+ let nextHtml = html.replace(markedScriptPattern, `${scriptTag}\n `);
252
+ if (nextHtml === html && !nextHtml.includes('openagent-override.js')) {
253
+ nextHtml = nextHtml.replace(markerPattern, `${scriptTag}\n `);
254
+ }
255
+ else if (nextHtml === html && nextHtml.includes('openagent-override.js')) {
256
+ nextHtml = nextHtml
257
+ .replace(oldScriptPattern, '')
258
+ .replace(markerPattern, `${scriptTag}\n `);
259
+ }
144
260
  if (nextHtml === html) {
145
261
  logger.debug('[ui-ext:regex] html-script-tag: already applied');
146
262
  }
@@ -150,7 +266,6 @@ const htmlScriptTag = {
150
266
  }
151
267
  return true;
152
268
  }
153
- const oldScriptPattern = /<script[^>]*openagent-override[^>]*><\/script>\n?\s*/g;
154
269
  if (oldScriptPattern.test(html)) {
155
270
  html = html.replace(oldScriptPattern, '');
156
271
  }
@@ -333,7 +448,7 @@ const toolcardRender = {
333
448
  toolMessageOk = false;
334
449
  }
335
450
  if (toolMessageOk) {
336
- const toolMessageHook = `${marker}/* openagent-media-aware-tool-message */let __openagentToolMsgCard=null;console.log('[openagent] toolMsg hook: cards='+${cardsVar}.length+' mediaVar='+${mediaVar});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>\`;`;
451
+ const toolMessageHook = `${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>\`;`;
337
452
  const toolMessageInsertAt = toolMessageFn.bodyStart + toolMessageReturnIdx;
338
453
  content = content.substring(0, toolMessageInsertAt) + toolMessageHook + content.substring(toolMessageInsertAt);
339
454
  patchedToolMessageWrapper = true;
@@ -32,7 +32,7 @@ export interface UIExtension {
32
32
  * 接收 control-ui 目录路径,内部自行决定改什么文件、怎么改。
33
33
  * 返回 true 表示成功,false 表示失败(不写入 manifest,下次重试)。
34
34
  */
35
- apply: (controlUiDir: string) => boolean;
35
+ apply: (controlUiDir: string, context?: UIExtensionContext) => boolean;
36
36
  /**
37
37
  * 卸载扩展。
38
38
  * 还原 apply 所做的全部修改。
@@ -40,6 +40,9 @@ export interface UIExtension {
40
40
  */
41
41
  revert: (controlUiDir: string) => void;
42
42
  }
43
+ export interface UIExtensionContext {
44
+ hostVersion?: string;
45
+ }
43
46
  /**
44
47
  * OpenAgent 标记前缀,注入的代码旁加此注释以标识修改来源。
45
48
  * 格式:/*openagent:<extension-id>* /
@@ -53,6 +53,21 @@ export interface PendingInvocationRecord {
53
53
  created_at: number;
54
54
  updated_at: number;
55
55
  }
56
+ /**
57
+ * 远程 ServiceAgent 会话续传记录。
58
+ *
59
+ * 一个 OpenClaw 本地会话里,同一个远程 Agent 的第一次 invocation 会返回
60
+ * session_id;后续 mode='session' 的调用应带回这个 session_id,才能保留
61
+ * 远端上下文。account_id + openclaw_session_key + target_agent_id 共同隔离,
62
+ * 避免不同账号、不同本地聊天、不同远程 Agent 之间串 session。
63
+ */
64
+ export interface RemoteAgentSessionRecord {
65
+ openclaw_session_key: string;
66
+ target_agent_id: string;
67
+ session_id: string;
68
+ created_at: number;
69
+ updated_at: number;
70
+ }
56
71
  export declare class StateStore {
57
72
  private db;
58
73
  private accountId;
@@ -121,6 +136,12 @@ export declare class StateStore {
121
136
  * Close the database connection
122
137
  */
123
138
  close(): void;
139
+ getRemoteAgentSession(openclawSessionKey: string, targetAgentId: string): string | null;
140
+ upsertRemoteAgentSession(record: {
141
+ openclaw_session_key: string;
142
+ target_agent_id: string;
143
+ session_id: string;
144
+ }): void;
124
145
  /**
125
146
  * 查 tool_call_id 是否有未完成记录。命中 → 复用其 idempotency_key 防止重复创建 Invocation。
126
147
  *
@@ -211,6 +211,21 @@ export class StateStore {
211
211
  this.db.run(`
212
212
  CREATE INDEX IF NOT EXISTS idx_pending_invocations_invocation
213
213
  ON pending_invocations (account_id, invocation_id)
214
+ `);
215
+ this.db.run(`
216
+ CREATE TABLE IF NOT EXISTS remote_agent_sessions (
217
+ account_id TEXT NOT NULL,
218
+ openclaw_session_key TEXT NOT NULL,
219
+ target_agent_id TEXT NOT NULL,
220
+ session_id TEXT NOT NULL,
221
+ created_at INTEGER NOT NULL DEFAULT 0,
222
+ updated_at INTEGER NOT NULL DEFAULT 0,
223
+ PRIMARY KEY (account_id, openclaw_session_key, target_agent_id)
224
+ )
225
+ `);
226
+ this.db.run(`
227
+ CREATE INDEX IF NOT EXISTS idx_remote_agent_sessions_updated
228
+ ON remote_agent_sessions (account_id, updated_at)
214
229
  `);
215
230
  }
216
231
  // ── Write to disk ──
@@ -408,6 +423,45 @@ export class StateStore {
408
423
  }
409
424
  }
410
425
  // ═════════════════════════════════════════════════════════════════
426
+ // OASN remote_agent_sessions CRUD
427
+ //
428
+ // 保存远端 ServiceAgent 返回的 session_id,用于同一 OpenClaw 会话内后续
429
+ // mode='session' 调用续传。run 模式不读写这张表。
430
+ // ═════════════════════════════════════════════════════════════════
431
+ getRemoteAgentSession(openclawSessionKey, targetAgentId) {
432
+ if (!this.db)
433
+ return null;
434
+ const scopeKey = openclawSessionKey.trim();
435
+ const agentId = targetAgentId.trim();
436
+ if (!scopeKey || !agentId)
437
+ return null;
438
+ const stmt = this.db.prepare(`SELECT session_id
439
+ FROM remote_agent_sessions
440
+ WHERE account_id = ? AND openclaw_session_key = ? AND target_agent_id = ?`);
441
+ stmt.bind([this.accountId, scopeKey, agentId]);
442
+ const row = stmt.step() ? stmt.getAsObject() : null;
443
+ stmt.free();
444
+ const sessionId = row?.['session_id'];
445
+ return typeof sessionId === 'string' && sessionId.trim() ? sessionId : null;
446
+ }
447
+ upsertRemoteAgentSession(record) {
448
+ if (!this.db)
449
+ return;
450
+ const scopeKey = record.openclaw_session_key.trim();
451
+ const agentId = record.target_agent_id.trim();
452
+ const sessionId = record.session_id.trim();
453
+ if (!scopeKey || !agentId || !sessionId)
454
+ return;
455
+ const now = Date.now();
456
+ this.db.run(`INSERT INTO remote_agent_sessions
457
+ (account_id, openclaw_session_key, target_agent_id, session_id, created_at, updated_at)
458
+ VALUES (?, ?, ?, ?, ?, ?)
459
+ ON CONFLICT(account_id, openclaw_session_key, target_agent_id) DO UPDATE SET
460
+ session_id = excluded.session_id,
461
+ updated_at = excluded.updated_at`, [this.accountId, scopeKey, agentId, sessionId, now, now]);
462
+ this.persist();
463
+ }
464
+ // ═════════════════════════════════════════════════════════════════
411
465
  // §11.1 OASN pending_invocations CRUD(OasnInvocation.PendingInvocationStore 适配)
412
466
  //
413
467
  // 设计依据:docs/specs/2026-06-16-oasn-transport-abstraction-design.md §11.1
@@ -139,6 +139,9 @@ export declare class OasnInvocation {
139
139
  private _toWebuiContinuation;
140
140
  /** 终态错误专用 TransportError,便于上层 catch */
141
141
  private _terminalError;
142
+ private _invocationFailureCode;
143
+ private _invocationFailureMessage;
144
+ private _firstText;
142
145
  private _toArtifactRef;
143
146
  private _normalizeCreateInvocation;
144
147
  /** clamp 轮询间隔到合理范围 */
@@ -288,15 +288,7 @@ export class OasnInvocation {
288
288
  webuiContinuation,
289
289
  };
290
290
  }
291
- // 失败/超时/取消 —— 文本统一用 user_error.message 兜底
292
- const userError = final.userError ?? final.user_error;
293
- const inlineResult = final.inlineResult ?? final.inline_result;
294
- const visible = final.visibleResult ?? final.visible_result;
295
- const message = userError?.message
296
- ?? inlineResult?.summary
297
- ?? visible?.inlineResultSummary
298
- ?? visible?.inline_result_summary
299
- ?? `Invocation ended with status=${final.status}`;
291
+ const message = this._invocationFailureMessage(final, `Invocation ended with status=${final.status}`);
300
292
  return {
301
293
  requestId: invocationId,
302
294
  status: 'error',
@@ -328,14 +320,38 @@ export class OasnInvocation {
328
320
  }
329
321
  /** 终态错误专用 TransportError,便于上层 catch */
330
322
  _terminalError(final, handle) {
331
- const userError = final.userError ?? final.user_error;
332
- const code = userError?.code ?? `INVOCATION_${final.status.toUpperCase()}`;
333
- const msg = userError?.message ?? `Invocation ${final.status}`;
323
+ const code = this._invocationFailureCode(final) ?? `INVOCATION_${final.status.toUpperCase()}`;
324
+ const msg = this._invocationFailureMessage(final, `Invocation ${final.status}`);
334
325
  return makeTransportError(code, msg, {
335
326
  invocationId: handle.invocationId,
336
327
  sessionId: handle.sessionId,
337
328
  });
338
329
  }
330
+ _invocationFailureCode(final) {
331
+ return this._firstText(final.userError?.code, final.user_error?.code, final.userErrorCode, final.user_error_code);
332
+ }
333
+ _invocationFailureMessage(final, fallback) {
334
+ const userMessage = this._firstText(final.userError?.message, final.user_error?.message, final.userErrorMessage, final.user_error_message);
335
+ if (userMessage)
336
+ return userMessage;
337
+ const inlineResult = final.inlineResult ?? final.inline_result;
338
+ const visible = final.visibleResult ?? final.visible_result;
339
+ const fallbackMessage = this._firstText(inlineResult?.summary, visible?.inlineResultSummary, visible?.inline_result_summary, final.progress?.safeStatusMessage, final.progress?.safe_status_message);
340
+ const code = this._invocationFailureCode(final);
341
+ if (fallbackMessage && code && !fallbackMessage.includes(code))
342
+ return `${fallbackMessage} (${code})`;
343
+ return fallbackMessage ?? code ?? fallback;
344
+ }
345
+ _firstText(...values) {
346
+ for (const value of values) {
347
+ if (typeof value !== 'string')
348
+ continue;
349
+ const trimmed = value.trim();
350
+ if (trimmed)
351
+ return trimmed;
352
+ }
353
+ return undefined;
354
+ }
339
355
  _toArtifactRef(art) {
340
356
  return {
341
357
  id: art.artifactId ?? art.artifact_id ?? '',
@@ -272,13 +272,17 @@ export interface GetInvocationResponse {
272
272
  next_poll_after_ms?: number | null;
273
273
  warnings?: string[];
274
274
  userError?: {
275
- code: string;
276
- message: string;
275
+ code?: string;
276
+ message?: string | null;
277
277
  };
278
278
  user_error?: {
279
- code: string;
280
- message: string;
279
+ code?: string;
280
+ message?: string | null;
281
281
  };
282
+ userErrorCode?: string;
283
+ user_error_code?: string;
284
+ userErrorMessage?: string | null;
285
+ user_error_message?: string | null;
282
286
  createdAt?: string;
283
287
  created_at?: string;
284
288
  updatedAt?: string;
package/index.ts CHANGED
@@ -118,7 +118,7 @@ export default {
118
118
  }
119
119
 
120
120
  // Auto-inject Control UI override (copies openagent-override.js + patches)
121
- injectControlUiOverride().catch(err => {
121
+ injectControlUiOverride(api.runtime?.version).catch(err => {
122
122
  logger.warn(`[entry] Control UI injection failed: ${(err as Error).message}`);
123
123
  });
124
124
 
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-openagent",
3
3
  "name": "OpenAgent",
4
4
  "description": "OpenAgent channel plugin — real-time messaging between AI agents via OASN",
5
- "version": "1.0.0",
5
+ "version": "1.0.10",
6
6
  "contracts": {
7
7
  "tools": [
8
8
  "openagent_call_remote_agent",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-openagent",
3
- "version": "1.0.9",
3
+ "version": "1.0.12",
4
4
  "type": "module",
5
5
  "description": "OpenAgent channel plugin for OpenClaw",
6
6
  "license": "MIT",
@@ -47,13 +47,14 @@
47
47
  "node": ">=22"
48
48
  },
49
49
  "scripts": {
50
- "build": "tsc -p tsconfig.build.json && cp -r src/sdk dist/src/sdk && mkdir -p dist/src/plugin-ui/assets && cp src/plugin-ui/assets/openagent-override.js dist/src/plugin-ui/assets/",
50
+ "build": "tsc -p tsconfig.build.json && cp -r src/sdk dist/src/sdk && mkdir -p dist/src/plugin-ui/assets && cp src/plugin-ui/assets/openagent-override.js src/plugin-ui/assets/icon.png src/plugin-ui/assets/bg.png dist/src/plugin-ui/assets/ && cp -r src/plugin-ui/adapters dist/src/plugin-ui/adapters",
51
51
  "build:override": "node ./src/plugin-ui/build.cjs",
52
52
  "watch:override": "node ./src/plugin-ui/build.cjs --watch",
53
+ "postinstall": "node ./src/plugin-ui/postinstall-deploy.cjs",
53
54
  "prepack": "npm run build:override && npm run build && npm run typecheck",
54
55
  "typecheck": "tsc --noEmit",
55
56
  "prepublishOnly": "npm run typecheck",
56
- "test:contract": "node tests/contract.test.mjs && node tests/agent-book-batch-category.test.mjs",
57
+ "test:contract": "node tests/contract.test.mjs && node tests/agent-book-batch-category.test.mjs && tsx tests/ui-extension-loader.test.mts",
57
58
  "test:smoke": "node tests/smoke.test.mjs",
58
59
  "test": "npm run test:contract && npm run test:smoke"
59
60
  },
@@ -75,7 +76,7 @@
75
76
  "@types/node": "^25.5.0",
76
77
  "@types/sql.js": "^1.4.11",
77
78
  "@types/yazl": "^3.3.1",
78
- "openclaw": "^2026.6.10",
79
+ "openclaw": "2026.4.22",
79
80
  "tsx": "^4.21.0",
80
81
  "typescript": "^6.0.2"
81
82
  }