openclaw-openagent 1.0.0 → 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.
- package/dist/index.d.ts +25 -0
- package/dist/index.js +105 -0
- package/dist/src/app/channel-tools.d.ts +28 -0
- package/dist/src/app/channel-tools.js +251 -0
- package/dist/src/app/discovery-tools.d.ts +35 -0
- package/dist/src/app/discovery-tools.js +243 -0
- package/dist/src/app/download-file-tool.d.ts +10 -0
- package/dist/src/app/download-file-tool.js +104 -0
- package/dist/src/app/hooks.d.ts +14 -0
- package/dist/src/app/hooks.js +118 -0
- package/dist/src/app/index.d.ts +13 -0
- package/dist/src/app/index.js +44 -0
- package/dist/src/app/messaging-tools.d.ts +11 -0
- package/dist/src/app/messaging-tools.js +79 -0
- package/dist/src/app/ops-tools.d.ts +21 -0
- package/dist/src/app/ops-tools.js +158 -0
- package/dist/src/app/remote-agent-tool.d.ts +27 -0
- package/dist/src/app/remote-agent-tool.js +461 -0
- package/dist/src/app/types.d.ts +61 -0
- package/dist/src/app/types.js +11 -0
- package/dist/src/app/upload-file-tool.d.ts +16 -0
- package/dist/src/app/upload-file-tool.js +353 -0
- package/dist/src/app/verbose-preflight.d.ts +2 -0
- package/dist/src/app/verbose-preflight.js +145 -0
- package/dist/src/auth/config.d.ts +79 -0
- package/dist/src/auth/config.js +133 -0
- package/dist/src/auth/credential-manager.d.ts +65 -0
- package/dist/src/auth/credential-manager.js +122 -0
- package/dist/src/auth/index.d.ts +6 -0
- package/dist/src/auth/index.js +6 -0
- package/dist/src/auth/verify.d.ts +42 -0
- package/dist/src/auth/verify.js +60 -0
- package/dist/src/channel.d.ts +269 -0
- package/dist/src/channel.js +488 -0
- package/dist/src/compat.d.ts +37 -0
- package/dist/src/compat.js +70 -0
- package/dist/src/config/config-schema.d.ts +56 -0
- package/dist/src/config/config-schema.js +34 -0
- package/dist/src/messaging/aggregator.d.ts +25 -0
- package/dist/src/messaging/aggregator.js +90 -0
- package/dist/src/messaging/collector.d.ts +27 -0
- package/dist/src/messaging/collector.js +76 -0
- package/dist/src/messaging/executor.d.ts +14 -0
- package/dist/src/messaging/executor.js +59 -0
- package/dist/src/messaging/inbound.d.ts +97 -0
- package/dist/src/messaging/inbound.js +63 -0
- package/dist/src/messaging/index.d.ts +10 -0
- package/dist/src/messaging/index.js +9 -0
- package/dist/src/messaging/mention-protocol.d.ts +42 -0
- package/dist/src/messaging/mention-protocol.js +74 -0
- package/dist/src/messaging/process-c2c-request.d.ts +55 -0
- package/dist/src/messaging/process-c2c-request.js +445 -0
- package/dist/src/messaging/process-message.d.ts +62 -0
- package/dist/src/messaging/process-message.js +282 -0
- package/dist/src/messaging/scheduler.d.ts +17 -0
- package/dist/src/messaging/scheduler.js +47 -0
- package/dist/src/messaging/types.d.ts +34 -0
- package/dist/src/messaging/types.js +4 -0
- package/dist/src/plugin-ui/assets/openagent-override.js +9267 -0
- package/dist/src/plugin-ui/index.d.ts +13 -0
- package/dist/src/plugin-ui/index.js +16 -0
- package/dist/src/plugin-ui/ui-extension-loader/backup.d.ts +23 -0
- package/dist/src/plugin-ui/ui-extension-loader/backup.js +82 -0
- package/dist/src/plugin-ui/ui-extension-loader/index.d.ts +28 -0
- package/dist/src/plugin-ui/ui-extension-loader/index.js +240 -0
- package/dist/src/plugin-ui/ui-extension-loader/locator.d.ts +35 -0
- package/dist/src/plugin-ui/ui-extension-loader/locator.js +129 -0
- package/dist/src/plugin-ui/ui-extension-loader/manifest.d.ts +26 -0
- package/dist/src/plugin-ui/ui-extension-loader/manifest.js +45 -0
- package/dist/src/plugin-ui/ui-extension-loader/registry-regex.d.ts +9 -0
- package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +701 -0
- package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.d.ts +8 -0
- package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.js +58 -0
- package/dist/src/plugin-ui/ui-extension-loader/types.d.ts +55 -0
- package/dist/src/plugin-ui/ui-extension-loader/types.js +23 -0
- package/dist/src/proxy/auth-proxy.d.ts +20 -0
- package/dist/src/proxy/auth-proxy.js +337 -0
- package/dist/src/runtime/account.d.ts +156 -0
- package/dist/src/runtime/account.js +491 -0
- package/dist/src/runtime/index.d.ts +5 -0
- package/dist/src/runtime/index.js +6 -0
- package/dist/src/runtime/plugin-runtime.d.ts +40 -0
- package/dist/src/runtime/plugin-runtime.js +72 -0
- package/dist/src/runtime/registry.d.ts +41 -0
- package/dist/src/runtime/registry.js +60 -0
- package/dist/src/sdk/CLASS_MAP.md +143 -0
- package/dist/src/sdk/index.d.ts +126 -0
- package/dist/src/sdk/index.js +23990 -0
- package/dist/src/sdk/modules/cloud-search-module.js +1117 -0
- package/dist/src/sdk/modules/follow-module.js +1069 -0
- package/dist/src/sdk/modules/group-module.js +7397 -0
- package/dist/src/sdk/modules/relationship-module.js +2269 -0
- package/dist/src/sdk/modules/signaling-module.js +1468 -0
- package/dist/src/sdk/modules/tim-upload-plugin.js +730 -0
- package/dist/src/sdk/node-env/http-request.js +90 -0
- package/dist/src/sdk/node-env/index.js +57 -0
- package/dist/src/sdk/node-env/storage.js +114 -0
- package/dist/src/sdk/package.json +10 -0
- package/dist/src/sdk/sdk/CLASS_MAP.md +143 -0
- package/dist/src/sdk/sdk/index.d.ts +126 -0
- package/dist/src/sdk/sdk/index.js +23990 -0
- package/dist/src/sdk/sdk/modules/cloud-search-module.js +1117 -0
- package/dist/src/sdk/sdk/modules/follow-module.js +1069 -0
- package/dist/src/sdk/sdk/modules/group-module.js +7397 -0
- package/dist/src/sdk/sdk/modules/relationship-module.js +2269 -0
- package/dist/src/sdk/sdk/modules/signaling-module.js +1468 -0
- package/dist/src/sdk/sdk/modules/tim-upload-plugin.js +730 -0
- package/dist/src/sdk/sdk/node-env/http-request.js +90 -0
- package/dist/src/sdk/sdk/node-env/index.js +57 -0
- package/dist/src/sdk/sdk/node-env/storage.js +114 -0
- package/dist/src/sdk/sdk/package.json +10 -0
- package/dist/src/sdk/sdk/tsconfig.json +16 -0
- package/dist/src/sdk/tsconfig.json +16 -0
- package/dist/src/state/pending-invocation-store.d.ts +26 -0
- package/dist/src/state/pending-invocation-store.js +39 -0
- package/dist/src/state/store.d.ts +165 -0
- package/dist/src/state/store.js +535 -0
- package/dist/src/tim/c2c.d.ts +83 -0
- package/dist/src/tim/c2c.js +318 -0
- package/dist/src/tim/channels.d.ts +95 -0
- package/dist/src/tim/channels.js +279 -0
- package/dist/src/tim/client.d.ts +54 -0
- package/dist/src/tim/client.js +268 -0
- package/dist/src/tim/index.d.ts +6 -0
- package/dist/src/tim/index.js +6 -0
- package/dist/src/tim/messages.d.ts +50 -0
- package/dist/src/tim/messages.js +104 -0
- package/dist/src/tim/sdk-logger-init.d.ts +13 -0
- package/dist/src/tim/sdk-logger-init.js +46 -0
- package/dist/src/tools.d.ts +9 -0
- package/dist/src/tools.js +8 -0
- package/dist/src/transport/factory.d.ts +63 -0
- package/dist/src/transport/factory.js +54 -0
- package/dist/src/transport/oasn/index.d.ts +12 -0
- package/dist/src/transport/oasn/index.js +9 -0
- package/dist/src/transport/oasn/oasn-agent-card.d.ts +38 -0
- package/dist/src/transport/oasn/oasn-agent-card.js +102 -0
- package/dist/src/transport/oasn/oasn-discovery.d.ts +33 -0
- package/dist/src/transport/oasn/oasn-discovery.js +97 -0
- package/dist/src/transport/oasn/oasn-files.d.ts +64 -0
- package/dist/src/transport/oasn/oasn-files.js +174 -0
- package/dist/src/transport/oasn/oasn-http.d.ts +98 -0
- package/dist/src/transport/oasn/oasn-http.js +362 -0
- package/dist/src/transport/oasn/oasn-invocation.d.ts +154 -0
- package/dist/src/transport/oasn/oasn-invocation.js +432 -0
- package/dist/src/transport/oasn/oasn-normalize.d.ts +6 -0
- package/dist/src/transport/oasn/oasn-normalize.js +112 -0
- package/dist/src/transport/oasn/oasn-register.d.ts +19 -0
- package/dist/src/transport/oasn/oasn-register.js +24 -0
- package/dist/src/transport/oasn/oasn-transport.d.ts +114 -0
- package/dist/src/transport/oasn/oasn-transport.js +230 -0
- package/dist/src/transport/oasn/oasn-types.d.ts +331 -0
- package/dist/src/transport/oasn/oasn-types.js +44 -0
- package/dist/src/transport/tim/index.d.ts +7 -0
- package/dist/src/transport/tim/index.js +6 -0
- package/dist/src/transport/tim/tim-transport.d.ts +122 -0
- package/dist/src/transport/tim/tim-transport.js +402 -0
- package/dist/src/transport/types.d.ts +450 -0
- package/dist/src/transport/types.js +38 -0
- package/dist/src/util/http.d.ts +21 -0
- package/dist/src/util/http.js +93 -0
- package/dist/src/util/logger.d.ts +20 -0
- package/dist/src/util/logger.js +100 -0
- package/dist/src/util/url-resolver.d.ts +7 -0
- package/dist/src/util/url-resolver.js +20 -0
- package/index.ts +11 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +7 -4
- package/src/app/download-file-tool.ts +133 -0
- package/src/app/hooks.ts +89 -5
- package/src/app/index.ts +6 -0
- package/src/app/remote-agent-tool.ts +46 -0
- package/src/app/types.ts +1 -0
- package/src/app/upload-file-tool.ts +411 -0
- package/src/plugin-ui/assets/openagent-override.js +1 -63
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -61
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -1
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +30 -13
- package/src/runtime/account.ts +0 -23
- package/src/transport/oasn/oasn-files.ts +5 -5
- package/src/transport/oasn/oasn-invocation.ts +44 -2
- package/src/transport/oasn/oasn-normalize.ts +0 -26
- package/src/transport/oasn/oasn-register.ts +8 -81
- package/src/transport/oasn/oasn-transport.ts +9 -32
- package/src/transport/oasn/oasn-types.ts +53 -6
- package/src/transport/types.ts +33 -36
- package/src/util/url-resolver.ts +17 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AccountRuntime — All per-account state and lifecycle
|
|
3
|
+
*
|
|
4
|
+
* Owns the lifecycle of a single active account:
|
|
5
|
+
* 1. StateStore (SQLite)
|
|
6
|
+
* 2. CredentialManager (auth token lifecycle)
|
|
7
|
+
* 3. TIMClient (SDK connection)
|
|
8
|
+
* 4. Message pipeline: channelRuntime dispatch (via process-message.ts)
|
|
9
|
+
*
|
|
10
|
+
* Supports "half-alive" mode: when agent is not yet claimed,
|
|
11
|
+
* start() succeeds without TIM connection. Tools trigger
|
|
12
|
+
* lazy connection via ensureConnected().
|
|
13
|
+
*
|
|
14
|
+
* v3.2.0: Replaced shell-out executor pipeline with channelRuntime dispatch.
|
|
15
|
+
* See docs/audit/004-messaging-pipeline-v3.1.4.md for migration context.
|
|
16
|
+
*
|
|
17
|
+
* v3.9.0: 引入 AgentTransport 双轨抽象。
|
|
18
|
+
* - transport='oasn'(默认):走 OASN HTTP API,不再创建 TIMClient
|
|
19
|
+
* - transport='tim'(显式配置回退):保留原 TIMClient + CredentialManager 路径
|
|
20
|
+
* 两条路径在 start() 内按 config.transport 字段分发;旧路径在阶段 4 上层切换完毕后再删除。
|
|
21
|
+
* 详见 docs/specs/2026-06-16-oasn-transport-abstraction-design.md §5.2
|
|
22
|
+
*/
|
|
23
|
+
import { logger } from '../util/logger.js';
|
|
24
|
+
import { CredentialManager, CredentialError } from '../auth/credential-manager.js';
|
|
25
|
+
import { StateStore } from '../state/store.js';
|
|
26
|
+
import { createPendingInvocationStore } from '../state/pending-invocation-store.js';
|
|
27
|
+
import { TIMClient } from '../tim/client.js';
|
|
28
|
+
import * as channels from '../tim/channels.js';
|
|
29
|
+
import * as messages from '../tim/messages.js';
|
|
30
|
+
import { initC2CHandler } from '../tim/c2c.js';
|
|
31
|
+
import { resolveOpenagentChannelRuntime } from './plugin-runtime.js';
|
|
32
|
+
import * as fs from 'node:fs';
|
|
33
|
+
import * as os from 'node:os';
|
|
34
|
+
import * as path from 'node:path';
|
|
35
|
+
import { processOneMessage } from '../messaging/process-message.js';
|
|
36
|
+
import { processC2CRequest } from '../messaging/process-c2c-request.js';
|
|
37
|
+
import { createTransport } from '../transport/factory.js';
|
|
38
|
+
import { isUnconfiguredOasnApiBase } from '../auth/config.js';
|
|
39
|
+
export class AccountRuntime {
|
|
40
|
+
accountId;
|
|
41
|
+
// Sub-modules (accessible by tools via registry)
|
|
42
|
+
store = null;
|
|
43
|
+
client = null;
|
|
44
|
+
/**
|
|
45
|
+
* v3.9+ 双轨抽象:统一 transport 接口。
|
|
46
|
+
*
|
|
47
|
+
* - transport='tim' 下:内部仍持有 TIMClient(this.client)作为 SDK 直连;
|
|
48
|
+
* transport 实例额外提供能力接口,便于阶段 4 上层调用
|
|
49
|
+
* - transport='oasn' 下:this.client 始终为 null,所有调用走 transport
|
|
50
|
+
*
|
|
51
|
+
* 阶段 4 完成后,上层将不再直接访问 this.client,只使用 this.transport。
|
|
52
|
+
*/
|
|
53
|
+
transport = null;
|
|
54
|
+
credentialManager = null;
|
|
55
|
+
_transportType = 'oasn';
|
|
56
|
+
_claimStatus = 'unknown';
|
|
57
|
+
_claimUrl = '';
|
|
58
|
+
_claimExpiresAt = '';
|
|
59
|
+
_caAccessToken;
|
|
60
|
+
_statusPollTimer = null;
|
|
61
|
+
_running = false;
|
|
62
|
+
_config = null;
|
|
63
|
+
_cfg = null;
|
|
64
|
+
_connectingPromise = null;
|
|
65
|
+
constructor(accountId) {
|
|
66
|
+
this.accountId = accountId;
|
|
67
|
+
}
|
|
68
|
+
get isRunning() { return this._running; }
|
|
69
|
+
get config() { return this._config; }
|
|
70
|
+
/** v3.9+ 当前账号选择的 transport 类型('oasn' | 'tim') */
|
|
71
|
+
get transportType() { return this._transportType; }
|
|
72
|
+
get claimStatus() { return this._claimStatus; }
|
|
73
|
+
get claimUrl() { return this._claimUrl; }
|
|
74
|
+
get claimExpiresAt() { return this._claimExpiresAt; }
|
|
75
|
+
get caAccessToken() { return this._caAccessToken; }
|
|
76
|
+
get isOasnClaimed() { return this._transportType !== 'oasn' || this._claimStatus === 'claimed'; }
|
|
77
|
+
/**
|
|
78
|
+
* Account startup with half-alive support.
|
|
79
|
+
*
|
|
80
|
+
* If agent is claimed: full startup (StateStore + TIM + pipeline).
|
|
81
|
+
* If agent is unclaimed: half-alive (StateStore only, _running = true).
|
|
82
|
+
* Tools will trigger lazy connection via ensureConnected().
|
|
83
|
+
*
|
|
84
|
+
* IMPORTANT: This method NEVER throws for unclaimed/credential errors.
|
|
85
|
+
* It must return normally so channel.ts reaches the blocking await Promise,
|
|
86
|
+
* preventing OpenClaw auto-restart.
|
|
87
|
+
*
|
|
88
|
+
* @param cfg - Full OpenClaw config object, needed for channelRuntime APIs
|
|
89
|
+
*/
|
|
90
|
+
async start(account, _pipelineConfig, cfg) {
|
|
91
|
+
this._config = account;
|
|
92
|
+
this._cfg = cfg || {};
|
|
93
|
+
this._transportType = account.transport;
|
|
94
|
+
this._claimStatus = account.status;
|
|
95
|
+
this._claimUrl = account.claimUrl;
|
|
96
|
+
try {
|
|
97
|
+
// ── Phase 1: StateStore (always) ──
|
|
98
|
+
logger.info(`[runtime] Starting account '${this.accountId}': init StateStore`);
|
|
99
|
+
this.store = new StateStore();
|
|
100
|
+
await this.store.init(this.accountId);
|
|
101
|
+
// ── Phase 2: 按 transport 分发凭证管理 ──
|
|
102
|
+
// OASN 路径:api_key 直接作为 Bearer,不走 CredentialManager(无缓存/冷却)。
|
|
103
|
+
// TIM 路径:保留原有 CredentialManager(apiKey → auth.ai-talk.live → UserSig)。
|
|
104
|
+
if (this._transportType === 'tim') {
|
|
105
|
+
this.credentialManager = new CredentialManager(account.agentId, account.apiKey);
|
|
106
|
+
logger.info(`[runtime] CredentialManager created for agent ${account.agentId} (transport=tim)`);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
logger.info(`[runtime] transport=oasn: skipping CredentialManager (api_key used as Bearer directly)`);
|
|
110
|
+
}
|
|
111
|
+
// ── Phase 3: 建立 transport 连接 ──
|
|
112
|
+
try {
|
|
113
|
+
if (this._transportType === 'oasn') {
|
|
114
|
+
await this._connectOASN();
|
|
115
|
+
logger.info(`[runtime] Account '${this.accountId}' started: OASN transport ready`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
await this._connectTIM();
|
|
119
|
+
logger.info(`[runtime] Account '${this.accountId}' started: fully connected (transport=tim)`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err instanceof CredentialError) {
|
|
124
|
+
// Unclaimed or cooldown: enter half-alive state(仅 TIM)
|
|
125
|
+
logger.warn(`[runtime] Account '${this.accountId}' started in half-alive mode (${err.reason}: ${err.message})`);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Unexpected error during connect: still go half-alive, don't crash
|
|
129
|
+
logger.error(`[runtime] Account '${this.accountId}' transport connect failed, entering half-alive: ${err.message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
this._running = true;
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
// Only StateStore init failure reaches here
|
|
136
|
+
logger.error(`[runtime] Account '${this.accountId}' start failed: ${err.message}`);
|
|
137
|
+
await this._cleanup();
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* OASN 连接路径(v3.9+)。
|
|
143
|
+
*
|
|
144
|
+
* 与 _connectTIM 不同:
|
|
145
|
+
* - 无 WebSocket,ready() 只校验 api_key 有效性(GET /api/agent-cards/{agent_id})
|
|
146
|
+
* - 不需要 channelRuntime 接管 'message' / 'c2c_request' 事件(OASN ClientAgent 不接收外来 Invocation)
|
|
147
|
+
* - 'credential_expired' 事件由 OasnTransport 在 401 时触发,处理:清 transport + 标记 half-alive
|
|
148
|
+
*/
|
|
149
|
+
async _connectOASN() {
|
|
150
|
+
if (!this._config)
|
|
151
|
+
throw new Error('Account config not set');
|
|
152
|
+
if (!this.store)
|
|
153
|
+
throw new Error('StateStore not initialized');
|
|
154
|
+
logger.info(`[runtime] Connecting OASN transport for ${this.accountId} `
|
|
155
|
+
+ `api_base=${this._config.apiBase} access_api_base=${this._config.accessApiBase}`);
|
|
156
|
+
if (isUnconfiguredOasnApiBase(this._config.apiBase)) {
|
|
157
|
+
throw new Error('OASN api_base is not configured; set channels.openagent.api_base or accounts.default.api_base');
|
|
158
|
+
}
|
|
159
|
+
// §11.1:把 StateStore 包装成 PendingInvocationStore,注入到 OasnTransport。
|
|
160
|
+
// 这样 sendTask 时会按 toolCallId 在 SQLite 里查/写 idempotency_key 与 invocation_id,
|
|
161
|
+
// 进程崩溃重启后 listUnsettledInvocations() 可定位未结调用做续轮询。
|
|
162
|
+
const pendingStore = createPendingInvocationStore(this.store);
|
|
163
|
+
const transport = await createTransport({
|
|
164
|
+
transportType: 'oasn',
|
|
165
|
+
apiBase: this._config.apiBase,
|
|
166
|
+
accessApiBase: this._config.accessApiBase,
|
|
167
|
+
agentId: this._config.agentId,
|
|
168
|
+
accountId: this.accountId,
|
|
169
|
+
pendingStore,
|
|
170
|
+
});
|
|
171
|
+
// §11.5 凭证撤销路径
|
|
172
|
+
transport.on('credential_expired', () => {
|
|
173
|
+
logger.error(`[runtime] OASN credential_expired for ${this.accountId} - clearing transport`);
|
|
174
|
+
// 不直接清 config 里的 api_key —— 持久层清理由上层(plugin-ui / register 工具)决定
|
|
175
|
+
// 但本地 transport 已不可用,标记为待重连
|
|
176
|
+
this.transport = null;
|
|
177
|
+
this._claimStatus = 'credential_expired';
|
|
178
|
+
this._caAccessToken = undefined;
|
|
179
|
+
this._stopOasnStatusPolling();
|
|
180
|
+
this._saveOasnAccountConfig({ status: 'credential_expired' });
|
|
181
|
+
});
|
|
182
|
+
// 拼装 OASN 凭证:identity=agent_id, token=api_key, expiresAt=0(不过期)。
|
|
183
|
+
// 首次注册由 openclaw-plugin-openagent-cli 安装器负责;runtime 不创建新 ClientAgent。
|
|
184
|
+
const agentId = this._config.agentId;
|
|
185
|
+
const apiKey = this._config.apiKey;
|
|
186
|
+
if (!agentId || !apiKey) {
|
|
187
|
+
throw new Error('OASN account is not configured; run openclaw-plugin-openagent-cli install to register this device');
|
|
188
|
+
}
|
|
189
|
+
const creds = {
|
|
190
|
+
identity: agentId,
|
|
191
|
+
token: apiKey,
|
|
192
|
+
expiresAt: 0,
|
|
193
|
+
};
|
|
194
|
+
await transport.ready(creds);
|
|
195
|
+
await this._syncOasnStatus(transport);
|
|
196
|
+
this._scheduleOasnStatusPolling(transport);
|
|
197
|
+
// §11.1 持久化已通过 factory 的 pendingStore 注入完成 —— OasnInvocation 内部在
|
|
198
|
+
// sendTask/waitForTaskResult 流程里直接读写 store,AccountRuntime 这一层无需再介入。
|
|
199
|
+
// (原先将 createPendingInvocationStore 作为 TODO 留在外部组合,阶段 4 已收敛到工厂)
|
|
200
|
+
this.transport = transport;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* 将 OASN 账号状态持久化到 OpenClaw 配置文件。
|
|
204
|
+
*
|
|
205
|
+
* 写入位置:~/.openclaw/openclaw.json → channels.openagent.accounts.{accountId}
|
|
206
|
+
* 采用 merge 策略。CA access_token 永远只留在 runtime 内存,不写入配置。
|
|
207
|
+
*/
|
|
208
|
+
_saveOasnAccountConfig(updates) {
|
|
209
|
+
try {
|
|
210
|
+
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
211
|
+
if (!fs.existsSync(configPath)) {
|
|
212
|
+
logger.warn(`[runtime] Config file not found: ${configPath}, skipping credential persistence`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
216
|
+
const cfg = JSON.parse(raw);
|
|
217
|
+
// 确保路径存在:channels.openagent.accounts.{accountId}
|
|
218
|
+
cfg.channels = cfg.channels || {};
|
|
219
|
+
cfg.channels.openagent = cfg.channels.openagent || {};
|
|
220
|
+
cfg.channels.openagent.accounts = cfg.channels.openagent.accounts || {};
|
|
221
|
+
const accId = this._config?.accountId || 'default';
|
|
222
|
+
cfg.channels.openagent.accounts[accId] = cfg.channels.openagent.accounts[accId] || {};
|
|
223
|
+
const acc = cfg.channels.openagent.accounts[accId];
|
|
224
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
225
|
+
if (value === undefined || key === 'access_token' || key === 'accessToken' || key === 'agent_card')
|
|
226
|
+
continue;
|
|
227
|
+
acc[key] = value;
|
|
228
|
+
}
|
|
229
|
+
fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
230
|
+
logger.info(`[runtime] OASN account config persisted to ${configPath} (keys=${Object.keys(updates).join(',')})`);
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
logger.warn(`[runtime] Failed to persist OASN account config: ${err.message}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async _syncOasnStatus(transport) {
|
|
237
|
+
const agentId = this._config?.agentId;
|
|
238
|
+
if (!agentId)
|
|
239
|
+
return null;
|
|
240
|
+
const status = await transport.getAgentStatus(agentId);
|
|
241
|
+
this._claimStatus = status.status;
|
|
242
|
+
this._caAccessToken = status.access_token;
|
|
243
|
+
this._config = this._config
|
|
244
|
+
? { ...this._config, status: status.status }
|
|
245
|
+
: this._config;
|
|
246
|
+
this._saveOasnAccountConfig({ status: status.status });
|
|
247
|
+
logger.info(`[runtime] OASN status synced: agent_id=${agentId} status=${status.status} access_token=${status.access_token ? 'present' : 'absent'}`);
|
|
248
|
+
return status;
|
|
249
|
+
}
|
|
250
|
+
_scheduleOasnStatusPolling(transport) {
|
|
251
|
+
this._stopOasnStatusPolling();
|
|
252
|
+
if (this._transportType !== 'oasn' || this._claimStatus === 'claimed' || this._claimStatus === 'credential_expired') {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this._statusPollTimer = setTimeout(() => {
|
|
256
|
+
void this._syncOasnStatus(transport)
|
|
257
|
+
.then(() => {
|
|
258
|
+
if (this._claimStatus !== 'claimed' && this._claimStatus !== 'credential_expired' && this.transport === transport) {
|
|
259
|
+
this._scheduleOasnStatusPolling(transport);
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
.catch((err) => {
|
|
263
|
+
logger.warn(`[runtime] OASN status polling failed: ${err.message}`);
|
|
264
|
+
if (this.transport === transport)
|
|
265
|
+
this._scheduleOasnStatusPolling(transport);
|
|
266
|
+
});
|
|
267
|
+
}, 30_000);
|
|
268
|
+
}
|
|
269
|
+
_stopOasnStatusPolling() {
|
|
270
|
+
if (!this._statusPollTimer)
|
|
271
|
+
return;
|
|
272
|
+
clearTimeout(this._statusPollTimer);
|
|
273
|
+
this._statusPollTimer = null;
|
|
274
|
+
}
|
|
275
|
+
assertOasnInvocationAllowed() {
|
|
276
|
+
if (this._transportType === 'oasn' && this._claimStatus !== 'claimed') {
|
|
277
|
+
throw new Error(`OASN ClientAgent is not claimed yet (status=${this._claimStatus}). Complete claim before calling remote agents.`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Connect TIM and wire the channelRuntime dispatch pipeline.
|
|
282
|
+
* Called by start() and ensureConnected().
|
|
283
|
+
*/
|
|
284
|
+
async _connectTIM() {
|
|
285
|
+
if (!this.credentialManager)
|
|
286
|
+
throw new Error('CredentialManager not initialized');
|
|
287
|
+
if (!this._config)
|
|
288
|
+
throw new Error('Account config not set');
|
|
289
|
+
const credentials = await this.credentialManager.getCredentials();
|
|
290
|
+
logger.info(`[runtime] Connecting TIM SDK for ${this.accountId}`);
|
|
291
|
+
this.client = new TIMClient();
|
|
292
|
+
await this.client.connect(credentials);
|
|
293
|
+
// Initialize C2C message handler for remote agent communication
|
|
294
|
+
initC2CHandler(this.client);
|
|
295
|
+
// Listen for UserSig expiry
|
|
296
|
+
this.client.on('credential_expired', () => {
|
|
297
|
+
logger.warn(`[runtime] Received credential_expired for ${this.accountId}, invalidating + reconnecting`);
|
|
298
|
+
this.credentialManager.invalidate();
|
|
299
|
+
// Attempt to reconnect with fresh credentials
|
|
300
|
+
void this._connectTIM().catch((err) => {
|
|
301
|
+
logger.error(`[runtime] Reconnect after UserSig expiry failed: ${err.message}`);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
// ── channelRuntime dispatch pipeline ──
|
|
305
|
+
// Replaces: Collector → Aggregator → Scheduler → Executor (shell-out)
|
|
306
|
+
// With: processOneMessage → channelRuntime.reply.dispatchReplyFromConfig
|
|
307
|
+
try {
|
|
308
|
+
const channelRuntime = await resolveOpenagentChannelRuntime({});
|
|
309
|
+
this.client.on('message', (rawMsg) => {
|
|
310
|
+
void processOneMessage(rawMsg, {
|
|
311
|
+
accountId: this.accountId,
|
|
312
|
+
selfUserId: this.client.agentId,
|
|
313
|
+
config: this._cfg || {},
|
|
314
|
+
channelRuntime,
|
|
315
|
+
sendMessage: async (chId, text, atUserList) => {
|
|
316
|
+
await messages.sendMessage(this.client, chId, text, atUserList);
|
|
317
|
+
},
|
|
318
|
+
getChannelSkill: async (chId) => {
|
|
319
|
+
return channels.getChannelSkill(this.client, chId);
|
|
320
|
+
},
|
|
321
|
+
getGroupName: async (chId) => {
|
|
322
|
+
return channels.getGroupName(this.client, chId);
|
|
323
|
+
},
|
|
324
|
+
getGroupCustomField: async (chId, key) => {
|
|
325
|
+
return channels.getGroupCustomField(this.client, chId, key);
|
|
326
|
+
},
|
|
327
|
+
getGroupMembers: async (chId) => {
|
|
328
|
+
const members = await channels.getMembers(this.client, chId);
|
|
329
|
+
return members.map(m => ({ id: m.id, nick: m.nick }));
|
|
330
|
+
},
|
|
331
|
+
log: logger,
|
|
332
|
+
}).catch((err) => {
|
|
333
|
+
logger.error(`[runtime] processOneMessage failed: ${err.message}`);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
// ── C2C remote agent request handler (T3) ──
|
|
337
|
+
// When another agent sends a task request via C2C, dispatch it through
|
|
338
|
+
// the same channelRuntime pipeline. Mirrors the 'message' listener above.
|
|
339
|
+
// @see docs/products/orchestrator/specs/T3-remote-handler.md
|
|
340
|
+
this.client.on('c2c_request', (event) => {
|
|
341
|
+
void processC2CRequest(event, {
|
|
342
|
+
accountId: this.accountId,
|
|
343
|
+
selfUserId: this.client.agentId,
|
|
344
|
+
config: this._cfg || {},
|
|
345
|
+
channelRuntime,
|
|
346
|
+
}).catch((err) => {
|
|
347
|
+
logger.error(`[runtime] processC2CRequest failed: ${err.message}`);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
logger.info(`[runtime] TIM connected + channelRuntime pipeline wired for ${this.accountId}`);
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
// channelRuntime resolution failed — fall through to half-alive
|
|
354
|
+
// TIM is connected but AI dispatch won't work
|
|
355
|
+
logger.error(`[runtime] channelRuntime resolution failed: ${err.message}`);
|
|
356
|
+
logger.warn(`[runtime] TIM connected but AI dispatch unavailable for ${this.accountId}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Ensure TIM is connected before executing a tool operation.
|
|
361
|
+
* If not connected, attempts lazy connection.
|
|
362
|
+
*
|
|
363
|
+
* v3.9+:OASN 路径下走 _connectOASN();TIM 路径维持原行为。
|
|
364
|
+
*
|
|
365
|
+
* @throws CredentialError — propagated to tool, which returns a user-friendly message
|
|
366
|
+
*/
|
|
367
|
+
async ensureConnected() {
|
|
368
|
+
if (this._transportType === 'oasn') {
|
|
369
|
+
if (this.transport?.isReady)
|
|
370
|
+
return;
|
|
371
|
+
if (this._connectingPromise) {
|
|
372
|
+
logger.debug(`[runtime] ensureConnected: waiting for in-progress OASN connection`);
|
|
373
|
+
return this._connectingPromise;
|
|
374
|
+
}
|
|
375
|
+
logger.info(`[runtime] ensureConnected: OASN transport not ready, initiating lazy connect for ${this.accountId}`);
|
|
376
|
+
this._connectingPromise = this._connectOASN();
|
|
377
|
+
try {
|
|
378
|
+
await this._connectingPromise;
|
|
379
|
+
}
|
|
380
|
+
finally {
|
|
381
|
+
this._connectingPromise = null;
|
|
382
|
+
}
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
// TIM 路径(原逻辑保留)
|
|
386
|
+
if (this.client?.isReady)
|
|
387
|
+
return;
|
|
388
|
+
// Prevent concurrent connect attempts
|
|
389
|
+
if (this._connectingPromise) {
|
|
390
|
+
logger.debug(`[runtime] ensureConnected: waiting for in-progress connection`);
|
|
391
|
+
return this._connectingPromise;
|
|
392
|
+
}
|
|
393
|
+
logger.info(`[runtime] ensureConnected: TIM not ready, initiating lazy connect for ${this.accountId}`);
|
|
394
|
+
this._connectingPromise = this._connectTIM();
|
|
395
|
+
try {
|
|
396
|
+
await this._connectingPromise;
|
|
397
|
+
}
|
|
398
|
+
finally {
|
|
399
|
+
this._connectingPromise = null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Graceful shutdown of all modules.
|
|
404
|
+
*/
|
|
405
|
+
async stop() {
|
|
406
|
+
logger.info(`[runtime] Stopping account '${this.accountId}'`);
|
|
407
|
+
await this._cleanup();
|
|
408
|
+
logger.info(`[runtime] Account '${this.accountId}' stopped`);
|
|
409
|
+
}
|
|
410
|
+
async _cleanup() {
|
|
411
|
+
this._running = false;
|
|
412
|
+
this._stopOasnStatusPolling();
|
|
413
|
+
if (this.client) {
|
|
414
|
+
await this.client.disconnect();
|
|
415
|
+
this.client = null;
|
|
416
|
+
}
|
|
417
|
+
if (this.transport) {
|
|
418
|
+
try {
|
|
419
|
+
await this.transport.dispose();
|
|
420
|
+
}
|
|
421
|
+
catch (err) {
|
|
422
|
+
logger.warn(`[runtime] transport.dispose error: ${err.message}`);
|
|
423
|
+
}
|
|
424
|
+
this.transport = null;
|
|
425
|
+
}
|
|
426
|
+
if (this.store) {
|
|
427
|
+
this.store.close();
|
|
428
|
+
this.store = null;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// ── Strategy + status queries (used by ops-tools) ──
|
|
432
|
+
/**
|
|
433
|
+
* @deprecated Pipeline batching is now handled by channelRuntime.
|
|
434
|
+
* Retained for backward compatibility with openagent_set_strategy tool.
|
|
435
|
+
*/
|
|
436
|
+
updateStrategy(_config) {
|
|
437
|
+
logger.debug('[runtime] updateStrategy called (no-op: batching handled by channelRuntime)');
|
|
438
|
+
return {
|
|
439
|
+
batchWindowMs: _config.batchWindowMs || 20_000,
|
|
440
|
+
maxBatchSize: _config.maxBatchSize || 20,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* @deprecated Scheduler queue no longer exists in channelRuntime mode.
|
|
445
|
+
*/
|
|
446
|
+
get schedulerQueueSize() {
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
// ── Delegated SDK operations (used by tools) ──
|
|
450
|
+
// All operations go through ensureConnected() for lazy connection.
|
|
451
|
+
async listChannels() {
|
|
452
|
+
await this.ensureConnected();
|
|
453
|
+
return channels.listChannels(this.client);
|
|
454
|
+
}
|
|
455
|
+
async searchChannels(keyword) {
|
|
456
|
+
await this.ensureConnected();
|
|
457
|
+
return channels.searchChannels(this.client, keyword);
|
|
458
|
+
}
|
|
459
|
+
async joinChannel(channelId) {
|
|
460
|
+
await this.ensureConnected();
|
|
461
|
+
return channels.joinChannel(this.client, channelId);
|
|
462
|
+
}
|
|
463
|
+
async leaveChannel(channelId) {
|
|
464
|
+
await this.ensureConnected();
|
|
465
|
+
return channels.leaveChannel(this.client, channelId);
|
|
466
|
+
}
|
|
467
|
+
async getMembers(channelId) {
|
|
468
|
+
await this.ensureConnected();
|
|
469
|
+
return channels.getMembers(this.client, channelId);
|
|
470
|
+
}
|
|
471
|
+
async createChannel(name, description, skill) {
|
|
472
|
+
await this.ensureConnected();
|
|
473
|
+
return channels.createChannel(this.client, name, description, skill);
|
|
474
|
+
}
|
|
475
|
+
async getChannelSkill(channelId) {
|
|
476
|
+
await this.ensureConnected();
|
|
477
|
+
return channels.getChannelSkill(this.client, channelId);
|
|
478
|
+
}
|
|
479
|
+
async setChannelSkill(channelId, skill) {
|
|
480
|
+
await this.ensureConnected();
|
|
481
|
+
return channels.setChannelSkill(this.client, channelId, skill);
|
|
482
|
+
}
|
|
483
|
+
async sendMessage(channelId, text) {
|
|
484
|
+
await this.ensureConnected();
|
|
485
|
+
return messages.sendMessage(this.client, channelId, text);
|
|
486
|
+
}
|
|
487
|
+
async fetchMessages(channelId, count) {
|
|
488
|
+
await this.ensureConnected();
|
|
489
|
+
return messages.fetchMessages(this.client, channelId, count);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Runtime — channelRuntime acquisition and readiness gate
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the pattern established by openclaw-weixin v2.1.1 runtime.ts:
|
|
5
|
+
* 1. register() stores the PluginRuntime via setOpenagentPluginRuntime()
|
|
6
|
+
* 2. startAccount() resolves channelRuntime via resolveOpenagentChannelRuntime()
|
|
7
|
+
* with a three-level fallback: ctx-injected → global → async wait
|
|
8
|
+
*
|
|
9
|
+
* Reference: docs/reference/plugins/openclaw-weixin/src/runtime.ts
|
|
10
|
+
*/
|
|
11
|
+
import type { PluginRuntime } from 'openclaw/plugin-sdk/core';
|
|
12
|
+
export type PluginChannelRuntime = PluginRuntime['channel'];
|
|
13
|
+
/**
|
|
14
|
+
* Store the PluginRuntime received during register().
|
|
15
|
+
* Called once from index.ts register().
|
|
16
|
+
*/
|
|
17
|
+
export declare function setOpenagentPluginRuntime(next: PluginRuntime): void;
|
|
18
|
+
/**
|
|
19
|
+
* Synchronous getter — throws if not initialized.
|
|
20
|
+
* Use only when you are certain register() has completed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getOpenagentPluginRuntime(): PluginRuntime;
|
|
23
|
+
/**
|
|
24
|
+
* Async polling wait for the runtime to become available.
|
|
25
|
+
* Used during startAccount() if register() hasn't completed yet.
|
|
26
|
+
*/
|
|
27
|
+
export declare function waitForOpenagentPluginRuntime(timeoutMs?: number): Promise<PluginRuntime>;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve PluginRuntime['channel'] with three-level fallback:
|
|
30
|
+
*
|
|
31
|
+
* 1. Gateway-injected channelRuntime on ctx (new OpenClaw versions)
|
|
32
|
+
* 2. Module-global from register()
|
|
33
|
+
* 3. Async wait for register() to complete
|
|
34
|
+
*
|
|
35
|
+
* This matches openclaw-weixin's resolveWeixinChannelRuntime() exactly.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveOpenagentChannelRuntime(params: {
|
|
38
|
+
channelRuntime?: PluginChannelRuntime;
|
|
39
|
+
waitTimeoutMs?: number;
|
|
40
|
+
}): Promise<PluginChannelRuntime>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Runtime — channelRuntime acquisition and readiness gate
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the pattern established by openclaw-weixin v2.1.1 runtime.ts:
|
|
5
|
+
* 1. register() stores the PluginRuntime via setOpenagentPluginRuntime()
|
|
6
|
+
* 2. startAccount() resolves channelRuntime via resolveOpenagentChannelRuntime()
|
|
7
|
+
* with a three-level fallback: ctx-injected → global → async wait
|
|
8
|
+
*
|
|
9
|
+
* Reference: docs/reference/plugins/openclaw-weixin/src/runtime.ts
|
|
10
|
+
*/
|
|
11
|
+
import { logger } from '../util/logger.js';
|
|
12
|
+
// ── Module-level singleton ──
|
|
13
|
+
let pluginRuntime = null;
|
|
14
|
+
// ── Public API ──
|
|
15
|
+
/**
|
|
16
|
+
* Store the PluginRuntime received during register().
|
|
17
|
+
* Called once from index.ts register().
|
|
18
|
+
*/
|
|
19
|
+
export function setOpenagentPluginRuntime(next) {
|
|
20
|
+
pluginRuntime = next;
|
|
21
|
+
logger.info('[plugin-runtime] setOpenagentPluginRuntime called, runtime set');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Synchronous getter — throws if not initialized.
|
|
25
|
+
* Use only when you are certain register() has completed.
|
|
26
|
+
*/
|
|
27
|
+
export function getOpenagentPluginRuntime() {
|
|
28
|
+
if (!pluginRuntime) {
|
|
29
|
+
throw new Error('OpenAgent PluginRuntime not initialized');
|
|
30
|
+
}
|
|
31
|
+
return pluginRuntime;
|
|
32
|
+
}
|
|
33
|
+
// ── Async wait ──
|
|
34
|
+
const WAIT_INTERVAL_MS = 100;
|
|
35
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
36
|
+
/**
|
|
37
|
+
* Async polling wait for the runtime to become available.
|
|
38
|
+
* Used during startAccount() if register() hasn't completed yet.
|
|
39
|
+
*/
|
|
40
|
+
export async function waitForOpenagentPluginRuntime(timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
41
|
+
const start = Date.now();
|
|
42
|
+
while (!pluginRuntime) {
|
|
43
|
+
if (Date.now() - start > timeoutMs) {
|
|
44
|
+
throw new Error('OpenAgent PluginRuntime initialization timeout');
|
|
45
|
+
}
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, WAIT_INTERVAL_MS));
|
|
47
|
+
}
|
|
48
|
+
return pluginRuntime;
|
|
49
|
+
}
|
|
50
|
+
// ── Three-level fallback resolver ──
|
|
51
|
+
/**
|
|
52
|
+
* Resolve PluginRuntime['channel'] with three-level fallback:
|
|
53
|
+
*
|
|
54
|
+
* 1. Gateway-injected channelRuntime on ctx (new OpenClaw versions)
|
|
55
|
+
* 2. Module-global from register()
|
|
56
|
+
* 3. Async wait for register() to complete
|
|
57
|
+
*
|
|
58
|
+
* This matches openclaw-weixin's resolveWeixinChannelRuntime() exactly.
|
|
59
|
+
*/
|
|
60
|
+
export async function resolveOpenagentChannelRuntime(params) {
|
|
61
|
+
if (params.channelRuntime) {
|
|
62
|
+
logger.debug('[plugin-runtime] channelRuntime from gateway context');
|
|
63
|
+
return params.channelRuntime;
|
|
64
|
+
}
|
|
65
|
+
if (pluginRuntime) {
|
|
66
|
+
logger.debug('[plugin-runtime] channelRuntime from register() global');
|
|
67
|
+
return pluginRuntime.channel;
|
|
68
|
+
}
|
|
69
|
+
logger.warn('[plugin-runtime] no channelRuntime on ctx and no global runtime yet; waiting for register()');
|
|
70
|
+
const pr = await waitForOpenagentPluginRuntime(params.waitTimeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
71
|
+
return pr.channel;
|
|
72
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuntimeRegistry — Strict single-active-account enforcement
|
|
3
|
+
*
|
|
4
|
+
* Prevents concurrent accounts from running.
|
|
5
|
+
* Registry.register() throws if another account is already active.
|
|
6
|
+
* This replaces the old shared.js singleton pattern.
|
|
7
|
+
*
|
|
8
|
+
* See §1.1 of refactoring-plan.md.
|
|
9
|
+
*/
|
|
10
|
+
import type { AccountRuntime } from './account.js';
|
|
11
|
+
export declare class RuntimeRegistry {
|
|
12
|
+
private _active;
|
|
13
|
+
/**
|
|
14
|
+
* Register an AccountRuntime as the active instance.
|
|
15
|
+
* @throws Error if another account is already active
|
|
16
|
+
*/
|
|
17
|
+
register(runtime: AccountRuntime): void;
|
|
18
|
+
/**
|
|
19
|
+
* Unregister the active runtime.
|
|
20
|
+
*/
|
|
21
|
+
unregister(runtime: AccountRuntime): void;
|
|
22
|
+
/**
|
|
23
|
+
* Get the currently active runtime.
|
|
24
|
+
* Returns null if no account is running.
|
|
25
|
+
*/
|
|
26
|
+
getActive(): AccountRuntime | null;
|
|
27
|
+
/**
|
|
28
|
+
* Get the default (only) runtime.
|
|
29
|
+
* In single-active-account mode, this is the same as getActive().
|
|
30
|
+
*/
|
|
31
|
+
getDefault(): AccountRuntime | null;
|
|
32
|
+
/**
|
|
33
|
+
* Check if any account is currently running.
|
|
34
|
+
*/
|
|
35
|
+
hasActive(): boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Global registry instance.
|
|
39
|
+
* Shared by channel adapter, tools, and hooks.
|
|
40
|
+
*/
|
|
41
|
+
export declare const registry: RuntimeRegistry;
|