@coclaw/openclaw-coclaw 0.2.4 → 0.3.0

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/index.js CHANGED
@@ -3,7 +3,7 @@ import { registerCoclawCli } from './src/cli-registrar.js';
3
3
  import { resolveErrorMessage } from './src/common/errors.js';
4
4
  import { notBound, bindOk, unbindOk } from './src/common/messages.js';
5
5
  import { coclawChannelPlugin } from './src/channel-plugin.js';
6
- import { refreshRealtimeBridge, startRealtimeBridge, stopRealtimeBridge } from './src/realtime-bridge.js';
6
+ import { ensureAgentSession, refreshRealtimeBridge, startRealtimeBridge, stopRealtimeBridge } from './src/realtime-bridge.js';
7
7
  import { setRuntime } from './src/runtime.js';
8
8
  import { createSessionManager } from './src/session-manager/manager.js';
9
9
  import { AutoUpgradeScheduler } from './src/auto-upgrade/updater.js';
@@ -88,8 +88,12 @@ const plugin = {
88
88
  }
89
89
  });
90
90
 
91
- api.registerGatewayMethod('nativeui.sessions.listAll', ({ params, respond }) => {
91
+ api.registerGatewayMethod('nativeui.sessions.listAll', async ({ params, respond }) => {
92
92
  try {
93
+ const agentId = params?.agentId?.trim?.() || 'main';
94
+ // best-effort ensure:失败不阻断 listAll
95
+ try { await ensureAgentSession(agentId); }
96
+ catch {}
93
97
  respond(true, manager.listAll(params ?? {}));
94
98
  }
95
99
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coclaw/openclaw-coclaw",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "OpenClaw CoClaw channel plugin for remote chat",
@@ -90,8 +90,6 @@ export class RealtimeBridge {
90
90
  this.gatewayConnectReqId = null;
91
91
  this.gatewayRpcSeq = 0;
92
92
  this.gatewayPendingRequests = new Map();
93
- this.mainSessionEnsurePromise = null;
94
- this.mainSessionEnsured = false;
95
93
  this.logger = console;
96
94
  this.pluginConfig = {};
97
95
  this.intentionallyClosed = false;
@@ -248,45 +246,58 @@ export class RealtimeBridge {
248
246
  });
249
247
  }
250
248
 
251
- async __ensureMainSessionKey() {
252
- if (this.mainSessionEnsured) {
249
+ /**
250
+ * 确保指定 agent 的主 session 存在(sessions.resolve + 条件 sessions.reset)
251
+ * @param {string} [agentId] - agent ID,默认 'main'
252
+ * @returns {Promise<{ok: boolean, state?: string, error?: string}>}
253
+ */
254
+ async ensureAgentSession(agentId) {
255
+ const aid = typeof agentId === 'string' && agentId.trim() ? agentId.trim() : 'main';
256
+ const key = `agent:${aid}:main`;
257
+ const resolved = await this.__gatewayRpc('sessions.resolve', { key }, { timeoutMs: 2000 });
258
+ if (resolved?.ok === true) {
259
+ this.__logDebug(`ensure agent session: ready agentId=${aid}`);
253
260
  return { ok: true, state: 'ready' };
254
261
  }
255
- /* c8 ignore next 3 -- 并发调用防御,复用进行中的 promise */
256
- if (this.mainSessionEnsurePromise) {
257
- return await this.mainSessionEnsurePromise;
258
- }
259
- this.mainSessionEnsurePromise = (async () => {
260
- const key = 'agent:main:main';
261
- // sessions.resolve 仅返回 { ok, key },不含 entry
262
- const resolved = await this.__gatewayRpc('sessions.resolve', { key }, { timeoutMs: 2000 });
263
- if (resolved?.ok === true) {
264
- this.mainSessionEnsured = true;
265
- this.__logDebug(`main session key ensure: ready key=${key}`);
266
- return { ok: true, state: 'ready' };
267
- }
268
- // 仅当网关真实响应 "不存在" 时才创建;超时/网关未就绪等瞬态错误不触发 reset
269
- if (!resolved?.response) {
270
- return { ok: false, error: resolved?.error ?? 'resolve_transient_failure' };
262
+ // 仅当网关真实响应 "不存在" 时才创建;超时/网关未就绪等瞬态错误不触发 reset
263
+ if (!resolved?.response) {
264
+ return { ok: false, error: resolved?.error ?? 'resolve_transient_failure' };
265
+ }
266
+ // session key 不存在,通过 sessions.reset 创建
267
+ const reset = await this.__gatewayRpc('sessions.reset', { key, reason: 'new' }, { timeoutMs: 2500 });
268
+ if (reset?.ok !== true) {
269
+ return { ok: false, error: reset?.error ?? 'sessions_reset_failed' };
270
+ }
271
+ this.__logDebug(`ensure agent session: created agentId=${aid}`);
272
+ return { ok: true, state: 'created' };
273
+ }
274
+
275
+ async __ensureAllAgentSessions() {
276
+ try {
277
+ const listResult = await this.__gatewayRpc('agents.list', {}, { timeoutMs: 3000 });
278
+ let agentIds = ['main'];
279
+ if (listResult?.ok === true && Array.isArray(listResult?.response?.payload?.agents)) {
280
+ const ids = listResult.response.payload.agents
281
+ .map((a) => a?.id)
282
+ .filter((id) => typeof id === 'string' && id.trim());
283
+ if (ids.length > 0) agentIds = ids;
271
284
  }
272
- // session key 不存在,通过 sessions.reset 创建
273
- const reset = await this.__gatewayRpc('sessions.reset', { key, reason: 'new' }, { timeoutMs: 2500 });
274
- if (reset?.ok !== true) {
275
- return { ok: false, error: reset?.error ?? 'sessions_reset_failed' };
285
+ else {
286
+ this.logger.warn?.(`[coclaw] agents.list failed, falling back to main: ${listResult?.error ?? 'unknown'}`);
276
287
  }
277
- this.mainSessionEnsured = true;
278
- this.__logDebug(`main session key ensure: created key=${key}`);
279
- return { ok: true, state: 'created' };
280
- })();
281
- try {
282
- const result = await this.mainSessionEnsurePromise;
283
- if (!result?.ok) {
284
- this.logger.warn?.(`[coclaw] ensure main session key failed: ${result?.error ?? 'unknown'}`);
288
+ const results = await Promise.allSettled(
289
+ agentIds.map((id) => this.ensureAgentSession(id)),
290
+ );
291
+ for (let i = 0; i < results.length; i++) {
292
+ const r = results[i];
293
+ if (r.status === 'fulfilled' && r.value?.ok) continue;
294
+ const err = r.status === 'fulfilled' ? r.value?.error : String(r.reason);
295
+ this.logger.warn?.(`[coclaw] ensure agent session failed: agentId=${agentIds[i]} error=${err ?? 'unknown'}`);
285
296
  }
286
- return result;
287
297
  }
288
- finally {
289
- this.mainSessionEnsurePromise = null;
298
+ /* c8 ignore next 3 -- 防御性兜底,__gatewayRpc 内部已有完整错误处理 */
299
+ catch (err) {
300
+ this.logger.warn?.(`[coclaw] ensureAllAgentSessions unexpected error: ${String(err?.message ?? err)}`);
290
301
  }
291
302
  }
292
303
 
@@ -395,7 +406,7 @@ export class RealtimeBridge {
395
406
  this.gatewayReady = true;
396
407
  this.__logDebug(`gateway connect ok <- id=${payload.id}`);
397
408
  this.gatewayConnectReqId = null;
398
- void this.__ensureMainSessionKey();
409
+ void this.__ensureAllAgentSessions();
399
410
  }
400
411
  else {
401
412
  this.gatewayReady = false;
@@ -672,8 +683,6 @@ export class RealtimeBridge {
672
683
 
673
684
  async stop() {
674
685
  this.started = false;
675
- this.mainSessionEnsured = false;
676
- this.mainSessionEnsurePromise = null;
677
686
  this.__clearServerHeartbeat();
678
687
  this.__clearConnectTimer();
679
688
  if (this.reconnectTimer) {
@@ -724,4 +733,12 @@ export async function stopRealtimeBridge() {
724
733
  return;
725
734
  }
726
735
  await singleton.stop();
736
+ singleton = null;
737
+ }
738
+
739
+ export async function ensureAgentSession(agentId) {
740
+ if (!singleton) {
741
+ return { ok: false, error: 'bridge_not_started' };
742
+ }
743
+ return singleton.ensureAgentSession(agentId);
727
744
  }