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,488 @@
1
+ /**
2
+ * OpenAgent Channel Adapter — OpenClaw channel plugin interface
3
+ *
4
+ * Assembles the v3 modular architecture:
5
+ * auth/ → runtime/ → tim/ → messaging/ → tools
6
+ *
7
+ * Gateway lifecycle uses AccountRuntime with transaction-style
8
+ * start/stop and RuntimeRegistry enforcement.
9
+ */
10
+ import { isUnconfiguredOasnApiBase, resolveOpenagentAccount, getOpenagentConfig } from './auth/config.js';
11
+ import { registry } from './runtime/registry.js';
12
+ import { AccountRuntime } from './runtime/account.js';
13
+ import { logger } from './util/logger.js';
14
+ const API_BASE = 'https://api.clawlink.club';
15
+ const OASN_TRANSPORT = 'oasn';
16
+ // ── Channel adapter meta ──
17
+ const meta = {
18
+ id: 'openagent',
19
+ label: 'OpenAgent',
20
+ selectionLabel: 'OpenAgent (Agent Network)',
21
+ detailLabel: 'OpenAgent Agent Network',
22
+ docsPath: '/channels/openagent',
23
+ docsLabel: 'openagent',
24
+ blurb: 'Connect to OASN agent social network via Tencent IM.',
25
+ aliases: ['openagent', 'agent-network'],
26
+ order: 90,
27
+ quickstartAllowFrom: true,
28
+ };
29
+ // ── Channel plugin object ──
30
+ export const openagentPlugin = {
31
+ id: 'openagent',
32
+ meta,
33
+ capabilities: {
34
+ chatTypes: ['group'],
35
+ media: false,
36
+ reactions: true,
37
+ threads: false,
38
+ polls: false,
39
+ nativeCommands: false,
40
+ },
41
+ reload: { configPrefixes: ['channels.openagent'] },
42
+ config: {
43
+ listAccountIds: (cfgOrCtx) => {
44
+ const cfg = cfgOrCtx?.cfg
45
+ || (cfgOrCtx?.['channels'] ? cfgOrCtx : {});
46
+ const clCfg = getOpenagentConfig(cfg);
47
+ const keys = Object.keys((clCfg['accounts'] || {}));
48
+ return keys.length > 0 ? keys : ['default'];
49
+ },
50
+ resolveAccount: (cfg, accountId) => resolveOpenagentAccount(cfg, accountId),
51
+ defaultAccountId: (cfgOrCtx) => {
52
+ const cfg = cfgOrCtx?.cfg
53
+ || (cfgOrCtx?.['channels'] ? cfgOrCtx : {});
54
+ const clCfg = getOpenagentConfig(cfg);
55
+ const keys = Object.keys((clCfg['accounts'] || {}));
56
+ return keys.length > 0 ? keys[0] : 'default';
57
+ },
58
+ setAccountEnabled: ({ cfg, enabled }) => ({
59
+ ...cfg,
60
+ channels: {
61
+ ...cfg['channels'],
62
+ openagent: { ...cfg['channels']?.['openagent'], enabled },
63
+ },
64
+ }),
65
+ deleteAccount: ({ cfg }) => {
66
+ const channels = { ...cfg['channels'] };
67
+ delete channels['openagent'];
68
+ return { ...cfg, channels };
69
+ },
70
+ isConfigured: (account) => account.configured,
71
+ describeAccount: (account) => ({
72
+ accountId: account.accountId,
73
+ name: account.name,
74
+ enabled: account.enabled,
75
+ configured: account.configured,
76
+ tokenStatus: account.configured ? 'available' : 'missing',
77
+ transport: account.transport ?? 'oasn',
78
+ claimStatus: account.status ?? 'unknown',
79
+ }),
80
+ },
81
+ security: {
82
+ resolveDmPolicy: () => ({
83
+ policy: 'open',
84
+ allowFrom: ['*'],
85
+ policyPath: 'channels.openagent.dm.policy',
86
+ allowFromPath: 'channels.openagent.dm.allowFrom',
87
+ normalizeEntry: (raw) => raw.trim(),
88
+ }),
89
+ },
90
+ groups: {
91
+ resolveRequireMention: () => false,
92
+ },
93
+ threading: {
94
+ resolveReplyToMode: () => 'first',
95
+ },
96
+ messaging: {
97
+ normalizeTarget: (raw) => {
98
+ const trimmed = raw.trim();
99
+ if (!trimmed)
100
+ return undefined;
101
+ return trimmed.replace(/^(openagent):/i, '').trim() || undefined;
102
+ },
103
+ targetResolver: {
104
+ looksLikeId: (raw) => Boolean(raw.trim()),
105
+ hint: '<channel-id> e.g. ch-001',
106
+ },
107
+ },
108
+ outbound: {
109
+ deliveryMode: 'direct',
110
+ chunkerMode: 'text',
111
+ textChunkLimit: 3000,
112
+ sendText: async ({ to, text }) => {
113
+ logger.info(`[outbound] sendText to=${to} len=${text.length}`);
114
+ const rt = registry.getDefault();
115
+ if (!rt?.isRunning) {
116
+ logger.warn('[outbound] sendText failed: not connected');
117
+ return { channel: 'openagent', ok: false, messageId: '', error: new Error('OpenAgent not connected') };
118
+ }
119
+ // v3.9+ 双轨抽象:群聊外发仅在 TIM 路径有效(IMChannelTransport)。
120
+ // OASN 路径下没有"主动发群消息"语义 —— ClientAgent 只通过 sendTask
121
+ // 向远端 ServiceAgent 发起 Invocation,原则上不会触达此分支;
122
+ // 但若上层误调用,直接告警并返回 ok=false,避免崩溃。
123
+ if (rt.transportType === 'oasn') {
124
+ logger.warn(`[outbound] sendText not supported on OASN transport (to=${to})`);
125
+ return {
126
+ channel: 'openagent',
127
+ ok: false,
128
+ messageId: '',
129
+ error: new Error('sendText not supported on OASN transport'),
130
+ };
131
+ }
132
+ try {
133
+ const result = await rt.sendMessage(to, text);
134
+ logger.info(`[outbound] sendText OK msgId=${result.messageId}`);
135
+ return { channel: 'openagent', ok: true, messageId: result.messageId };
136
+ }
137
+ catch (err) {
138
+ logger.error(`[outbound] sendText failed: ${err.message}`);
139
+ return {
140
+ channel: 'openagent', ok: false, messageId: '',
141
+ error: err instanceof Error ? err : new Error(String(err)),
142
+ };
143
+ }
144
+ },
145
+ },
146
+ status: {
147
+ defaultRuntime: {
148
+ accountId: 'default',
149
+ running: false,
150
+ lastStartAt: null,
151
+ lastStopAt: null,
152
+ lastError: null,
153
+ },
154
+ buildChannelSummary: ({ snapshot }) => ({
155
+ configured: snapshot['configured'] ?? false,
156
+ running: snapshot['running'] ?? false,
157
+ connected: snapshot['connected'] ?? false,
158
+ transport: snapshot['transport'] ?? 'oasn',
159
+ claimStatus: snapshot['claimStatus'] ?? 'unknown',
160
+ claimUrl: snapshot['claimUrl'] ?? '',
161
+ lastStartAt: snapshot['lastStartAt'] ?? null,
162
+ lastStopAt: snapshot['lastStopAt'] ?? null,
163
+ lastError: snapshot['lastError'] ?? null,
164
+ lastInboundAt: snapshot['lastInboundAt'] ?? null,
165
+ lastOutboundAt: snapshot['lastOutboundAt'] ?? null,
166
+ }),
167
+ probeAccount: async () => ({ ok: true }),
168
+ buildAccountSnapshot: ({ account, runtime }) => ({
169
+ accountId: account.accountId,
170
+ name: account.name,
171
+ enabled: account.enabled,
172
+ configured: account.configured,
173
+ tokenStatus: account.configured ? 'available' : 'missing',
174
+ transport: account.transport ?? 'oasn',
175
+ claimStatus: runtime && 'claimStatus' in runtime ? runtime.claimStatus : account.status ?? 'unknown',
176
+ claimUrl: account.claimUrl ?? '',
177
+ running: runtime?.running ?? false,
178
+ connected: runtime?.running ?? false,
179
+ lastStartAt: runtime?.lastStartAt ?? null,
180
+ lastStopAt: runtime?.lastStopAt ?? null,
181
+ lastError: runtime?.lastError ?? null,
182
+ }),
183
+ // Outbound handlers: intentionally no-op (LLM text is intercepted here)
184
+ sendText: async () => ({ ok: true }),
185
+ sendMedia: async () => ({ ok: true }),
186
+ },
187
+ // ── Gateway lifecycle ──
188
+ gateway: {
189
+ startAccount: async (ctx) => {
190
+ const { account, abortSignal } = ctx;
191
+ // Guard is installed in register() (index.ts), not here.
192
+ // OASN 模式以 CLI install/register/write-config 为首次注册入口。
193
+ // runtime 只消费已经写入的 agent_id + api_key。
194
+ const accountConfig = resolveOpenagentAccount(ctx.cfg);
195
+ if (accountConfig.transport === 'oasn' && isUnconfiguredOasnApiBase(accountConfig.apiBase)) {
196
+ logger.warn('[channel] OASN api_base not configured; skipping start');
197
+ ctx.setStatus({
198
+ accountId: account.accountId,
199
+ running: false,
200
+ configured: false,
201
+ transport: 'oasn',
202
+ claimStatus: accountConfig.status,
203
+ lastError: 'OASN api_base is not configured',
204
+ });
205
+ return;
206
+ }
207
+ if (accountConfig.transport === 'oasn' && !accountConfig.configured) {
208
+ const message = 'OASN account is not configured; run openclaw-plugin-openagent-cli install to register this device';
209
+ logger.warn(`[channel] ${message}`);
210
+ ctx.setStatus({
211
+ accountId: account.accountId,
212
+ running: false,
213
+ configured: false,
214
+ transport: 'oasn',
215
+ claimStatus: accountConfig.status,
216
+ claimUrl: accountConfig.claimUrl,
217
+ lastError: message,
218
+ });
219
+ return;
220
+ }
221
+ if (!account.configured && accountConfig.transport !== 'oasn') {
222
+ logger.warn('[channel] Not configured; skipping');
223
+ ctx.setStatus({ accountId: account.accountId, running: false, configured: false });
224
+ return;
225
+ }
226
+ logger.info(`[channel] Starting account '${account.accountId}' for agent ${accountConfig.agentId || account.agentId || '(unconfigured)'}`);
227
+ const runtime = new AccountRuntime(account.accountId);
228
+ try {
229
+ // Register in registry (throws if another account is active)
230
+ registry.register(runtime);
231
+ // Transaction-style start
232
+ await runtime.start({
233
+ accountId: account.accountId,
234
+ name: 'OpenAgent',
235
+ enabled: true,
236
+ configured: accountConfig.configured,
237
+ agentId: accountConfig.agentId,
238
+ apiKey: accountConfig.apiKey,
239
+ apiBase: accountConfig.apiBase,
240
+ accessApiBase: accountConfig.accessApiBase,
241
+ claimUrl: accountConfig.claimUrl,
242
+ status: accountConfig.status,
243
+ registeredAt: accountConfig.registeredAt,
244
+ // v3.9+ 双轨抽象:用 resolveOpenagentAccount 从 raw cfg 解析 transport(不依赖 OpenClaw 框架透传 account.config 自定义字段)
245
+ transport: accountConfig.transport,
246
+ config: account.config || {},
247
+ }, {
248
+ batchWindowMs: account.config?.['batchWindowMs'] || 20_000,
249
+ maxBatchSize: account.config?.['maxBatchSize'] || 20,
250
+ }, ctx.cfg);
251
+ ctx.setStatus({
252
+ accountId: account.accountId,
253
+ running: true,
254
+ configured: accountConfig.configured,
255
+ transport: runtime.transportType,
256
+ claimStatus: runtime.claimStatus,
257
+ claimUrl: runtime.claimUrl,
258
+ lastStartAt: Date.now(),
259
+ });
260
+ logger.info('[channel] Pipeline active: Push mode');
261
+ // Block until aborted
262
+ await new Promise((_, reject) => {
263
+ if (abortSignal) {
264
+ abortSignal.addEventListener('abort', () => {
265
+ void runtime.stop().then(() => {
266
+ registry.unregister(runtime);
267
+ ctx.setStatus({
268
+ accountId: account.accountId,
269
+ running: false,
270
+ lastStopAt: Date.now(),
271
+ });
272
+ logger.info(`[channel] Account '${account.accountId}' stopped via abort`);
273
+ reject(new Error('abort'));
274
+ });
275
+ }, { once: true });
276
+ }
277
+ });
278
+ }
279
+ catch (err) {
280
+ // Controlled failure: catch registry error or start error
281
+ const message = err.message;
282
+ if (message !== 'abort') {
283
+ logger.error(`[channel] Start failed: ${message}`);
284
+ registry.unregister(runtime);
285
+ ctx.setStatus({
286
+ accountId: account.accountId,
287
+ running: false,
288
+ lastError: message,
289
+ });
290
+ }
291
+ }
292
+ },
293
+ stopAccount: async (ctx) => {
294
+ const rt = registry.getDefault();
295
+ if (rt) {
296
+ await rt.stop();
297
+ registry.unregister(rt);
298
+ }
299
+ ctx.setStatus({
300
+ accountId: ctx.account.accountId,
301
+ running: false,
302
+ lastStopAt: Date.now(),
303
+ });
304
+ },
305
+ },
306
+ // ── Onboarding wizard ──
307
+ onboarding: {
308
+ channel: 'openagent',
309
+ getStatus: async ({ cfg }) => {
310
+ const account = resolveOpenagentAccount(cfg);
311
+ const apiBaseMissing = account.transport === 'oasn' && isUnconfiguredOasnApiBase(account.apiBase);
312
+ const statusLine = buildOnboardingStatusLine(account, apiBaseMissing);
313
+ return {
314
+ channel: 'openagent',
315
+ configured: account.transport === 'oasn' ? account.configured && !apiBaseMissing : account.configured,
316
+ statusLines: [statusLine],
317
+ selectionHint: apiBaseMissing
318
+ ? 'needs api_base'
319
+ : account.transport === 'oasn' && !account.configured
320
+ ? 'needs CLI install'
321
+ : account.configured ? 'configured' : 'needs credentials',
322
+ quickstartScore: apiBaseMissing ? 0 : account.configured ? 2 : 0,
323
+ };
324
+ },
325
+ configure: async ({ cfg, prompter }) => {
326
+ const existing = (cfg['channels']?.['openagent'] || {});
327
+ const accounts = (existing['accounts'] || {});
328
+ const firstAccKey = Object.keys(accounts)[0];
329
+ const fallbackAcc = firstAccKey ? accounts[firstAccKey] : {};
330
+ const resolved = resolveOpenagentAccount(cfg);
331
+ if (resolved.transport === OASN_TRANSPORT) {
332
+ let apiBase = resolved.apiBase;
333
+ if (isUnconfiguredOasnApiBase(apiBase)) {
334
+ apiBase = String(await prompter.text({
335
+ message: 'Enter OASN API Base URL',
336
+ validate: (v) => {
337
+ const value = v?.trim();
338
+ if (!value || isUnconfiguredOasnApiBase(value))
339
+ return 'Required';
340
+ try {
341
+ // eslint-disable-next-line no-new
342
+ new URL(value);
343
+ return undefined;
344
+ }
345
+ catch {
346
+ return 'Must be a valid URL';
347
+ }
348
+ },
349
+ })).trim();
350
+ }
351
+ const accessApiBase = String(resolved.accessApiBase
352
+ || fallbackAcc['access_api_base']
353
+ || fallbackAcc['accessApiBase']
354
+ || existing['access_api_base']
355
+ || existing['accessApiBase']
356
+ || apiBase);
357
+ let claimUpdates = {};
358
+ if (resolved.agentId && resolved.apiKey && resolved.status !== 'claimed') {
359
+ const ownerEmail = String(await prompter.text({
360
+ message: 'Enter Owner email to claim this OASN ClientAgent (leave blank to skip)',
361
+ validate: () => undefined,
362
+ })).trim();
363
+ if (ownerEmail) {
364
+ claimUpdates = await claimOasnAgent(apiBase, resolved.apiKey, resolved.agentId, ownerEmail);
365
+ }
366
+ }
367
+ return {
368
+ cfg: {
369
+ ...cfg,
370
+ channels: {
371
+ ...cfg['channels'],
372
+ openagent: {
373
+ ...existing,
374
+ enabled: true,
375
+ transport: 'oasn',
376
+ api_base: apiBase,
377
+ access_api_base: accessApiBase,
378
+ accounts: {
379
+ default: compactConfig({
380
+ ...fallbackAcc,
381
+ transport: 'oasn',
382
+ api_base: apiBase,
383
+ access_api_base: accessApiBase,
384
+ agent_id: resolved.agentId || fallbackAcc['agent_id'],
385
+ api_key: resolved.apiKey || fallbackAcc['api_key'],
386
+ claim_url: resolved.claimUrl || fallbackAcc['claim_url'],
387
+ status: claimUpdates.status || resolved.status,
388
+ registered_at: resolved.registeredAt || fallbackAcc['registered_at'],
389
+ ...claimUpdates,
390
+ }),
391
+ },
392
+ },
393
+ },
394
+ },
395
+ accountId: 'default',
396
+ };
397
+ }
398
+ let agentId = (fallbackAcc['agentId'] || fallbackAcc['agent_id'] || firstAccKey || '');
399
+ let apiKey = (fallbackAcc['api_key'] || fallbackAcc['apiKey'] || '');
400
+ const apiBase = (fallbackAcc['apiBase'] || fallbackAcc['api_base'] || API_BASE);
401
+ if (!agentId) {
402
+ agentId = String(await prompter.text({
403
+ message: 'Enter your OpenAgent Agent ID',
404
+ validate: (v) => v?.trim() ? undefined : 'Required',
405
+ })).trim();
406
+ }
407
+ if (!apiKey) {
408
+ apiKey = String(await prompter.text({
409
+ message: 'Enter your OpenAgent API Key',
410
+ validate: (v) => v?.trim() ? undefined : 'Required',
411
+ })).trim();
412
+ }
413
+ const cleanedExisting = { ...existing };
414
+ delete cleanedExisting['agentId'];
415
+ delete cleanedExisting['apiKey'];
416
+ delete cleanedExisting['agent_id'];
417
+ delete cleanedExisting['api_key'];
418
+ return {
419
+ cfg: {
420
+ ...cfg,
421
+ channels: {
422
+ ...cfg['channels'],
423
+ openagent: {
424
+ ...cleanedExisting,
425
+ enabled: true,
426
+ accounts: {
427
+ default: {
428
+ agent_id: agentId,
429
+ api_key: apiKey,
430
+ api_base: apiBase,
431
+ },
432
+ },
433
+ },
434
+ },
435
+ },
436
+ accountId: 'default',
437
+ };
438
+ },
439
+ disable: (cfg) => ({
440
+ ...cfg,
441
+ channels: {
442
+ ...cfg['channels'],
443
+ openagent: {
444
+ ...cfg['channels']?.['openagent'],
445
+ enabled: false,
446
+ },
447
+ },
448
+ }),
449
+ },
450
+ };
451
+ function buildOnboardingStatusLine(account, apiBaseMissing) {
452
+ if (account.transport !== 'oasn') {
453
+ return account.configured
454
+ ? `OpenAgent: configured (agent=${account.agentId})`
455
+ : 'OpenAgent: needs agentId + apiKey';
456
+ }
457
+ if (apiBaseMissing)
458
+ return 'OASN: needs api_base';
459
+ if (!account.configured)
460
+ return `OASN: run openclaw-plugin-openagent-cli install to register (${account.apiBase})`;
461
+ const claimHint = account.status === 'claimed'
462
+ ? 'claimed'
463
+ : `${account.status}${account.claimUrl ? ` claim=${account.claimUrl}` : ''}`;
464
+ return `OASN: ${claimHint} (agent=${account.agentId})`;
465
+ }
466
+ function compactConfig(input) {
467
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined && value !== ''));
468
+ }
469
+ async function claimOasnAgent(apiBase, apiKey, agentId, ownerEmail) {
470
+ const res = await fetch(new URL('/api/agents/claim', apiBase), {
471
+ method: 'POST',
472
+ headers: {
473
+ 'Content-Type': 'application/json',
474
+ Authorization: `Bearer ${apiKey}`,
475
+ },
476
+ body: JSON.stringify({ agentId, ownerEmail }),
477
+ signal: AbortSignal.timeout(15_000),
478
+ });
479
+ const body = await res.json().catch(() => ({}));
480
+ if (!res.ok) {
481
+ throw new Error(`claim failed: ${res.status} ${JSON.stringify(body).slice(0, 200)}`);
482
+ }
483
+ return {
484
+ status: 'claim_pending',
485
+ claim_expires_at: body['expiresAt'] || body['expires_at'],
486
+ owner_email_masked: body['ownerEmailMasked'] || body['owner_email_masked'],
487
+ };
488
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Runtime host-version compatibility check for openclaw-openagent.
3
+ *
4
+ * OpenClaw uses a date-based version format: YYYY.M.DD (e.g. 2026.3.22).
5
+ * This module parses that format and validates the running host is within
6
+ * the supported range for this plugin version.
7
+ *
8
+ * Ported from openclaw-weixin v2.1.1 src/compat.ts
9
+ * Reference: docs/reference/plugins/openclaw-weixin-latest-v2.1.1/src/compat.ts
10
+ */
11
+ export declare const PLUGIN_VERSION = "1.0.0";
12
+ export declare const SUPPORTED_HOST_MIN = "2026.3.22";
13
+ export interface OpenClawVersion {
14
+ year: number;
15
+ month: number;
16
+ day: number;
17
+ }
18
+ /**
19
+ * Parse an OpenClaw date version string (e.g. "2026.3.22") into components.
20
+ * Returns null for unparseable strings.
21
+ */
22
+ export declare function parseOpenClawVersion(version: string): OpenClawVersion | null;
23
+ /**
24
+ * Compare two parsed versions. Returns -1 | 0 | 1.
25
+ */
26
+ export declare function compareVersions(a: OpenClawVersion, b: OpenClawVersion): -1 | 0 | 1;
27
+ /**
28
+ * Check whether a host version string is >= SUPPORTED_HOST_MIN.
29
+ */
30
+ export declare function isHostVersionSupported(hostVersion: string): boolean;
31
+ /**
32
+ * Fail-fast guard. Call at the very start of `register()` to prevent the
33
+ * plugin from loading on an incompatible host.
34
+ *
35
+ * @throws {Error} with a human-readable message when the host is out of range.
36
+ */
37
+ export declare function assertHostCompatibility(hostVersion: string | undefined): void;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Runtime host-version compatibility check for openclaw-openagent.
3
+ *
4
+ * OpenClaw uses a date-based version format: YYYY.M.DD (e.g. 2026.3.22).
5
+ * This module parses that format and validates the running host is within
6
+ * the supported range for this plugin version.
7
+ *
8
+ * Ported from openclaw-weixin v2.1.1 src/compat.ts
9
+ * Reference: docs/reference/plugins/openclaw-weixin-latest-v2.1.1/src/compat.ts
10
+ */
11
+ import { logger } from './util/logger.js';
12
+ export const PLUGIN_VERSION = '1.0.0';
13
+ export const SUPPORTED_HOST_MIN = '2026.3.22';
14
+ /**
15
+ * Parse an OpenClaw date version string (e.g. "2026.3.22") into components.
16
+ * Returns null for unparseable strings.
17
+ */
18
+ export function parseOpenClawVersion(version) {
19
+ // Strip any pre-release suffix (e.g. "2026.3.22-beta.1" -> "2026.3.22")
20
+ const base = version.trim().split('-')[0];
21
+ const parts = base.split('.');
22
+ if (parts.length !== 3)
23
+ return null;
24
+ const [year, month, day] = parts.map(Number);
25
+ if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day))
26
+ return null;
27
+ return { year, month, day };
28
+ }
29
+ /**
30
+ * Compare two parsed versions. Returns -1 | 0 | 1.
31
+ */
32
+ export function compareVersions(a, b) {
33
+ for (const key of ['year', 'month', 'day']) {
34
+ if (a[key] < b[key])
35
+ return -1;
36
+ if (a[key] > b[key])
37
+ return 1;
38
+ }
39
+ return 0;
40
+ }
41
+ /**
42
+ * Check whether a host version string is >= SUPPORTED_HOST_MIN.
43
+ */
44
+ export function isHostVersionSupported(hostVersion) {
45
+ const host = parseOpenClawVersion(hostVersion);
46
+ if (!host)
47
+ return false;
48
+ const min = parseOpenClawVersion(SUPPORTED_HOST_MIN);
49
+ return compareVersions(host, min) >= 0;
50
+ }
51
+ /**
52
+ * Fail-fast guard. Call at the very start of `register()` to prevent the
53
+ * plugin from loading on an incompatible host.
54
+ *
55
+ * @throws {Error} with a human-readable message when the host is out of range.
56
+ */
57
+ export function assertHostCompatibility(hostVersion) {
58
+ if (!hostVersion || hostVersion === 'unknown') {
59
+ logger.warn('[compat] Could not determine host OpenClaw version; skipping compatibility check.');
60
+ return;
61
+ }
62
+ if (isHostVersionSupported(hostVersion)) {
63
+ logger.info(`[compat] Host OpenClaw ${hostVersion} >= ${SUPPORTED_HOST_MIN}, OK.`);
64
+ return;
65
+ }
66
+ throw new Error(`openclaw-openagent@${PLUGIN_VERSION} requires OpenClaw >=${SUPPORTED_HOST_MIN}, ` +
67
+ `but found ${hostVersion}. ` +
68
+ `Please upgrade OpenClaw, or install the legacy version:\n` +
69
+ ` npm install openclaw-openagent@legacy`);
70
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * OpenAgent config schema — Zod-based runtime validation.
3
+ *
4
+ * This mirrors the JSON schema in openclaw.plugin.json but provides
5
+ * type-safe runtime parsing via Zod.
6
+ *
7
+ * Uses direct zod dependency (same pattern as openclaw-weixin v2.1.1)
8
+ * rather than openclaw/plugin-sdk/zod, which is only available in
9
+ * OpenClaw >= ~2026.3.28 and would break on earlier supported hosts.
10
+ */
11
+ import { z } from 'zod';
12
+ /** Top-level openagent config schema. */
13
+ export declare const OpenagentConfigSchema: z.ZodObject<{
14
+ name: z.ZodOptional<z.ZodString>;
15
+ enabled: z.ZodOptional<z.ZodBoolean>;
16
+ agent_id: z.ZodOptional<z.ZodString>;
17
+ api_key: z.ZodOptional<z.ZodString>;
18
+ claim_url: z.ZodOptional<z.ZodString>;
19
+ status: z.ZodOptional<z.ZodEnum<{
20
+ unregistered: "unregistered";
21
+ unclaimed: "unclaimed";
22
+ claim_pending: "claim_pending";
23
+ claimed: "claimed";
24
+ credential_expired: "credential_expired";
25
+ unknown: "unknown";
26
+ }>>;
27
+ registered_at: z.ZodOptional<z.ZodString>;
28
+ transport: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
29
+ oasn: "oasn";
30
+ tim: "tim";
31
+ }>>>;
32
+ api_base: z.ZodOptional<z.ZodString>;
33
+ access_api_base: z.ZodOptional<z.ZodString>;
34
+ accounts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
35
+ name: z.ZodOptional<z.ZodString>;
36
+ enabled: z.ZodOptional<z.ZodBoolean>;
37
+ transport: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
38
+ oasn: "oasn";
39
+ tim: "tim";
40
+ }>>>;
41
+ agent_id: z.ZodOptional<z.ZodString>;
42
+ api_key: z.ZodOptional<z.ZodString>;
43
+ api_base: z.ZodOptional<z.ZodString>;
44
+ access_api_base: z.ZodOptional<z.ZodString>;
45
+ claim_url: z.ZodOptional<z.ZodString>;
46
+ status: z.ZodOptional<z.ZodEnum<{
47
+ unregistered: "unregistered";
48
+ unclaimed: "unclaimed";
49
+ claim_pending: "claim_pending";
50
+ claimed: "claimed";
51
+ credential_expired: "credential_expired";
52
+ unknown: "unknown";
53
+ }>>;
54
+ registered_at: z.ZodOptional<z.ZodString>;
55
+ }, z.core.$strip>>>;
56
+ }, z.core.$strip>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * OpenAgent config schema — Zod-based runtime validation.
3
+ *
4
+ * This mirrors the JSON schema in openclaw.plugin.json but provides
5
+ * type-safe runtime parsing via Zod.
6
+ *
7
+ * Uses direct zod dependency (same pattern as openclaw-weixin v2.1.1)
8
+ * rather than openclaw/plugin-sdk/zod, which is only available in
9
+ * OpenClaw >= ~2026.3.28 and would break on earlier supported hosts.
10
+ */
11
+ import { z } from 'zod';
12
+ // ---------------------------------------------------------------------------
13
+ // Zod config schema
14
+ // ---------------------------------------------------------------------------
15
+ const transportSchema = z.enum(['oasn', 'tim']).default('oasn');
16
+ const openagentAccountSchema = z.object({
17
+ name: z.string().optional(),
18
+ enabled: z.boolean().optional(),
19
+ transport: transportSchema.optional(),
20
+ agent_id: z.string().optional(),
21
+ api_key: z.string().optional(),
22
+ api_base: z.string().optional(),
23
+ access_api_base: z.string().optional(),
24
+ claim_url: z.string().optional(),
25
+ status: z.enum(['unregistered', 'unclaimed', 'claim_pending', 'claimed', 'credential_expired', 'unknown']).optional(),
26
+ registered_at: z.string().optional(),
27
+ });
28
+ /** Top-level openagent config schema. */
29
+ export const OpenagentConfigSchema = openagentAccountSchema.extend({
30
+ transport: transportSchema.optional(),
31
+ api_base: z.string().optional(),
32
+ access_api_base: z.string().optional(),
33
+ accounts: z.record(z.string(), openagentAccountSchema).optional(),
34
+ });