claw-control-center 0.1.1 → 0.1.3

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.cjs CHANGED
@@ -2238,7 +2238,7 @@ var require_websocket = __commonJS({
2238
2238
  var http = require("http");
2239
2239
  var net = require("net");
2240
2240
  var tls = require("tls");
2241
- var { randomBytes, createHash } = require("crypto");
2241
+ var { randomBytes, createHash: createHash2 } = require("crypto");
2242
2242
  var { Duplex, Readable } = require("stream");
2243
2243
  var { URL: URL2 } = require("url");
2244
2244
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -2898,7 +2898,7 @@ var require_websocket = __commonJS({
2898
2898
  abortHandshake(websocket, socket, "Invalid Upgrade header");
2899
2899
  return;
2900
2900
  }
2901
- const digest = createHash("sha1").update(key + GUID).digest("base64");
2901
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
2902
2902
  if (res.headers["sec-websocket-accept"] !== digest) {
2903
2903
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
2904
2904
  return;
@@ -3265,7 +3265,7 @@ var require_websocket_server = __commonJS({
3265
3265
  var EventEmitter = require("events");
3266
3266
  var http = require("http");
3267
3267
  var { Duplex } = require("stream");
3268
- var { createHash } = require("crypto");
3268
+ var { createHash: createHash2 } = require("crypto");
3269
3269
  var extension2 = require_extension();
3270
3270
  var PerMessageDeflate2 = require_permessage_deflate();
3271
3271
  var subprotocol2 = require_subprotocol();
@@ -3566,7 +3566,7 @@ var require_websocket_server = __commonJS({
3566
3566
  );
3567
3567
  }
3568
3568
  if (this._state > RUNNING) return abortHandshake(socket, 503);
3569
- const digest = createHash("sha1").update(key + GUID).digest("base64");
3569
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
3570
3570
  const headers = [
3571
3571
  "HTTP/1.1 101 Switching Protocols",
3572
3572
  "Upgrade: websocket",
@@ -3666,7 +3666,7 @@ __export(index_exports, {
3666
3666
  module.exports = __toCommonJS(index_exports);
3667
3667
  var import_node_fs5 = require("fs");
3668
3668
  var import_node_path7 = require("path");
3669
- var import_node_crypto4 = require("crypto");
3669
+ var import_node_crypto5 = require("crypto");
3670
3670
 
3671
3671
  // src/agent-event-probe.ts
3672
3672
  var RECENT_EVENT_LIMIT = 200;
@@ -3850,6 +3850,7 @@ var DEFAULT_THINKING_MESSAGE = "\u6B63\u5728\u5904\u7406\u60A8\u7684\u8BF7\u6C42
3850
3850
  var MAX_OUTBOX_FRAMES = 200;
3851
3851
  var RUN_WAIT_TIMEOUT_MS = 30 * 60 * 1e3;
3852
3852
  var HUB_SESSION_TITLE_PREFIX = "53AI Hub-";
3853
+ var CONTROL_CENTER_SESSION_TITLE = "Claw Control Center";
3853
3854
  var HUB_TITLE_SUMMARY_LENGTH = 40;
3854
3855
  var HUB_RPC_ACTIONS = /* @__PURE__ */ new Set([
3855
3856
  "sessions.list",
@@ -4069,10 +4070,11 @@ function createHub53AIBridge(input) {
4069
4070
  async function resolveRPCRequest(request) {
4070
4071
  if (request.action === "sessions.list") {
4071
4072
  const pagination = readRPCPagination(request.data, 50);
4072
- return input.gateway.listSessionPage({
4073
+ const page = await input.gateway.listSessionPage({
4073
4074
  limit: pagination.limit,
4074
4075
  offset: pagination.offset
4075
4076
  });
4077
+ return mergeKnownHubSessionTitles(page);
4076
4078
  }
4077
4079
  if (request.action === "sessions.current") {
4078
4080
  return resolveCurrentSessionRPC(request.data);
@@ -4203,6 +4205,7 @@ function createHub53AIBridge(input) {
4203
4205
  async function resolveCurrentSessionRPC(payload) {
4204
4206
  const record = toRecord2(payload);
4205
4207
  const userObject = toRecord2(record.user);
4208
+ const userName = stringOr(record.userName, record.user_name, userObject.name, userObject.userName, userObject.username);
4206
4209
  const chatId = stringOr(
4207
4210
  record.chat_id,
4208
4211
  record.chatId,
@@ -4215,11 +4218,7 @@ function createHub53AIBridge(input) {
4215
4218
  if (!chatId) {
4216
4219
  throw new HubRPCError("PARAM_ERROR", "chat_id or user is required");
4217
4220
  }
4218
- const mappedSession = await getMappedSession(chatId);
4219
- if (mappedSession) {
4220
- return mappedSession;
4221
- }
4222
- return null;
4221
+ return restoreLatestHubSession(chatId, userName);
4223
4222
  }
4224
4223
  async function getMappedSession(chatId) {
4225
4224
  const mappedId = state.mappings[chatId];
@@ -4227,13 +4226,55 @@ function createHub53AIBridge(input) {
4227
4226
  return null;
4228
4227
  }
4229
4228
  try {
4230
- return await input.gateway.getSession(mappedId);
4229
+ const session = await input.gateway.getSession(mappedId);
4230
+ if (!isHubManagedSessionTitle(session.title, chatId)) {
4231
+ delete state.mappings[chatId];
4232
+ await persistState();
4233
+ input.logger?.warn?.(
4234
+ `Ignored stale 53AIHub session mapping for ${chatId}: mapped session ${mappedId} is "${session.title}"`
4235
+ );
4236
+ return null;
4237
+ }
4238
+ return session;
4231
4239
  } catch {
4232
4240
  delete state.mappings[chatId];
4233
4241
  await persistState();
4234
4242
  return null;
4235
4243
  }
4236
4244
  }
4245
+ async function restoreLatestHubSession(chatId, userName) {
4246
+ const page = await input.gateway.listSessionPage({
4247
+ limit: 100,
4248
+ offset: 0
4249
+ });
4250
+ const knownSessions = await listKnownSessions();
4251
+ const sessions = mergeKnownHubSessions(page.sessions, knownSessions).filter((session2) => isRestorableHubSession(session2, chatId, userName)).sort((left, right) => toTime(right.updatedAt || right.createdAt) - toTime(left.updatedAt || left.createdAt));
4252
+ const session = sessions[0];
4253
+ if (!session) {
4254
+ return null;
4255
+ }
4256
+ return session;
4257
+ }
4258
+ async function mergeKnownHubSessionTitles(page) {
4259
+ const knownSessions = await listKnownSessions();
4260
+ if (!knownSessions.length) {
4261
+ return page;
4262
+ }
4263
+ return {
4264
+ ...page,
4265
+ sessions: mergeKnownHubSessions(page.sessions, knownSessions)
4266
+ };
4267
+ }
4268
+ async function listKnownSessions() {
4269
+ try {
4270
+ return await input.callbacks.listKnownSessions?.() ?? [];
4271
+ } catch (error) {
4272
+ input.logger?.warn?.(
4273
+ `Failed to read known 53AIHub sessions: ${error instanceof Error ? error.message : String(error)}`
4274
+ );
4275
+ return [];
4276
+ }
4277
+ }
4237
4278
  async function buildFallbackStatus() {
4238
4279
  const [gatewayHealth, runtimeInfo] = await Promise.allSettled([input.gateway.getHealth(), input.gateway.getRuntimeInfo()]);
4239
4280
  return {
@@ -4405,7 +4446,7 @@ function createHub53AIBridge(input) {
4405
4446
  if (event.kind === "tool.call" || event.kind === "tool.result") {
4406
4447
  const summary = summarizeVisibleActivity(event);
4407
4448
  if (summary && input.config.sendThinkingMessage) {
4408
- await recordBridgeThinkingEvent(resolveMappedSessionId(message), summary);
4449
+ await recordBridgeThinkingEvent(sessionId, summary);
4409
4450
  await sendReply({
4410
4451
  reqId: message.reqId,
4411
4452
  text: summary,
@@ -4478,9 +4519,6 @@ function createHub53AIBridge(input) {
4478
4519
  const payload = toRecord2(event.payload);
4479
4520
  return event.kind === "status.update" && String(payload.phase ?? payload.status ?? "") === "running";
4480
4521
  }
4481
- function resolveMappedSessionId(message) {
4482
- return state.mappings[message.chatId] ?? message.chatId;
4483
- }
4484
4522
  async function recordBridgeThinkingEvent(sessionId, content) {
4485
4523
  const normalized = content.trim();
4486
4524
  if (!normalized || !input.callbacks.onBridgeThinkingEvent) {
@@ -4523,26 +4561,27 @@ function createHub53AIBridge(input) {
4523
4561
  }
4524
4562
  async function resolveSession(message) {
4525
4563
  const desiredTitle = buildHubSessionTitle(message);
4526
- const mappedId = state.mappings[message.chatId];
4527
- if (mappedId) {
4528
- const session2 = await input.gateway.getSession(mappedId);
4529
- const nextSession = await renamePlaceholderSessionIfNeeded(session2, message, desiredTitle);
4530
- await input.callbacks.onSessionUpsert(nextSession);
4531
- return nextSession;
4532
- }
4533
4564
  if (isOpenClawSessionId(message.chatId)) {
4534
- const session2 = await input.gateway.getSession(message.chatId);
4535
- state.mappings[message.chatId] = session2.id;
4536
- await persistState();
4565
+ const session2 = await getSessionWithKnownHubTitle(message.chatId, message.conversationTitle);
4537
4566
  await input.callbacks.onSessionUpsert(session2);
4538
4567
  return session2;
4539
4568
  }
4569
+ const restoredSession = await restoreLatestHubSession(message.chatId, message.userName || message.userId);
4570
+ if (restoredSession) {
4571
+ const nextSession = await renamePlaceholderSessionIfNeeded(restoredSession, message, desiredTitle);
4572
+ await input.callbacks.onSessionUpsert(nextSession);
4573
+ return nextSession;
4574
+ }
4540
4575
  const session = await createSessionWithUniqueTitle(desiredTitle);
4541
- state.mappings[message.chatId] = session.id;
4542
- await persistState();
4543
4576
  await input.callbacks.onSessionUpsert(session);
4544
4577
  return session;
4545
4578
  }
4579
+ async function getSessionWithKnownHubTitle(sessionId, titleHint) {
4580
+ const session = await input.gateway.getSession(sessionId);
4581
+ const knownSessions = await listKnownSessions();
4582
+ const titleHintSession = applyHubTitleHint(session, titleHint);
4583
+ return mergeKnownHubSessions([titleHintSession], knownSessions)[0] ?? titleHintSession;
4584
+ }
4546
4585
  async function createSessionWithUniqueTitle(baseTitle) {
4547
4586
  let lastDuplicateError;
4548
4587
  for (let attempt = 0; attempt < 6; attempt += 1) {
@@ -4708,6 +4747,7 @@ function parseIncomingMessage(rawJson) {
4708
4747
  chatId: chatId2,
4709
4748
  userId: userId2,
4710
4749
  userName: extractUserName(openAIReq, metadata, userObject2, userMessage),
4750
+ conversationTitle: extractConversationTitle(openAIReq, metadata),
4711
4751
  text: extractTextFromContent(content),
4712
4752
  imageUrls: extractImagesFromContent(content),
4713
4753
  fileUrls: extractFilesFromContent(content)
@@ -4727,6 +4767,7 @@ function parseIncomingMessage(rawJson) {
4727
4767
  chatId,
4728
4768
  userId,
4729
4769
  userName: extractUserName(data, userObject),
4770
+ conversationTitle: extractConversationTitle(data),
4730
4771
  text: stringOr(data.text, data.content, ""),
4731
4772
  imageUrls: normalizeUrlList(data.imageUrls, data.images),
4732
4773
  fileUrls: normalizeUrlList(data.fileUrls, data.files),
@@ -4866,6 +4907,23 @@ function extractUserName(...sources) {
4866
4907
  }
4867
4908
  return void 0;
4868
4909
  }
4910
+ function extractConversationTitle(...sources) {
4911
+ const titleKeys = [
4912
+ "openclaw_conversation_title",
4913
+ "openclawConversationTitle",
4914
+ "conversation_title",
4915
+ "conversationTitle",
4916
+ "title"
4917
+ ];
4918
+ for (const source of sources) {
4919
+ const record = toRecord2(source);
4920
+ const title = stringFromKeys(record, titleKeys);
4921
+ if (title) {
4922
+ return title;
4923
+ }
4924
+ }
4925
+ return void 0;
4926
+ }
4869
4927
  function stringFromKeys(record, keys) {
4870
4928
  for (const key of keys) {
4871
4929
  const value = record[key];
@@ -4920,6 +4978,63 @@ function isOldHubPlaceholderTitle(title, chatId) {
4920
4978
  const normalized = title.trim();
4921
4979
  return [`53AIHub ${chatId}`, `53AIHub:${chatId}`, `53AIHub-${chatId}`, chatId].includes(normalized);
4922
4980
  }
4981
+ function isHubManagedSessionTitle(title, chatId) {
4982
+ const normalized = title.trim();
4983
+ return normalized.startsWith(HUB_SESSION_TITLE_PREFIX) || isOldHubPlaceholderTitle(normalized, chatId);
4984
+ }
4985
+ function isRestorableHubSession(session, chatId, userName) {
4986
+ const normalized = session.title.trim();
4987
+ if (isOldHubPlaceholderTitle(normalized, chatId)) {
4988
+ return true;
4989
+ }
4990
+ if (!normalized.startsWith(HUB_SESSION_TITLE_PREFIX) || !userName) {
4991
+ return false;
4992
+ }
4993
+ return normalized.startsWith(`${HUB_SESSION_TITLE_PREFIX}${sanitizeTitlePart(userName)}\uFF1A`);
4994
+ }
4995
+ function mergeKnownHubSessions(gatewaySessions, knownSessions) {
4996
+ const knownHubById = new Map(
4997
+ knownSessions.filter((session) => isHubTitle(session.title)).map((session) => [session.id, session])
4998
+ );
4999
+ if (!knownHubById.size) {
5000
+ return gatewaySessions;
5001
+ }
5002
+ const merged = gatewaySessions.map((session) => {
5003
+ const knownSession = knownHubById.get(session.id);
5004
+ if (!knownSession || !shouldPreferKnownHubTitle(session.title, knownSession.title)) {
5005
+ return session;
5006
+ }
5007
+ return {
5008
+ ...session,
5009
+ title: knownSession.title
5010
+ };
5011
+ });
5012
+ return merged;
5013
+ }
5014
+ function applyHubTitleHint(session, titleHint) {
5015
+ const normalizedTitle = titleHint?.trim();
5016
+ if (!normalizedTitle || !isHubTitle(normalizedTitle)) {
5017
+ return session;
5018
+ }
5019
+ return {
5020
+ ...session,
5021
+ title: normalizedTitle
5022
+ };
5023
+ }
5024
+ function shouldPreferKnownHubTitle(gatewayTitle, knownTitle) {
5025
+ return isHubTitle(knownTitle) && (isControlCenterTitle(gatewayTitle) || !isHubTitle(gatewayTitle));
5026
+ }
5027
+ function isHubTitle(title) {
5028
+ return title.trim().startsWith(HUB_SESSION_TITLE_PREFIX);
5029
+ }
5030
+ function isControlCenterTitle(title) {
5031
+ return title.trim() === CONTROL_CENTER_SESSION_TITLE;
5032
+ }
5033
+ function toTime(value) {
5034
+ if (!value) return 0;
5035
+ const time = Date.parse(value);
5036
+ return Number.isFinite(time) ? time : 0;
5037
+ }
4923
5038
  function isOpenClawSessionId(value) {
4924
5039
  return value.startsWith("agent:");
4925
5040
  }
@@ -5049,7 +5164,10 @@ function normalizeUrlList(primary, fallback) {
5049
5164
  }).filter(Boolean);
5050
5165
  }
5051
5166
  function summarizeVisibleActivity(event) {
5052
- const name = String(event.payload?.name ?? event.payload?.toolName ?? event.payload?.skillName ?? "").trim();
5167
+ const data = toRecord2(event.payload?.data);
5168
+ const name = String(
5169
+ event.payload?.name ?? event.payload?.toolName ?? event.payload?.skillName ?? data.name ?? data.toolName ?? data.skillName ?? ""
5170
+ ).trim();
5053
5171
  if (event.kind === "tool.call") {
5054
5172
  return name ? `Used tool ${name}` : "Used a tool";
5055
5173
  }
@@ -5130,6 +5248,8 @@ function toRecord2(value) {
5130
5248
  // src/file-store.ts
5131
5249
  var import_promises2 = require("fs/promises");
5132
5250
  var import_node_path2 = require("path");
5251
+ var HUB_SESSION_TITLE_PREFIX2 = "53AI Hub-";
5252
+ var CONTROL_CENTER_SESSION_TITLE2 = "Claw Control Center";
5133
5253
  var FileSessionStore = class {
5134
5254
  constructor(filePath, maxSessions) {
5135
5255
  this.filePath = filePath;
@@ -5162,9 +5282,29 @@ var FileSessionStore = class {
5162
5282
  return this.state.sessions[sessionId]?.hydrated ?? false;
5163
5283
  }
5164
5284
  async upsertSession(session) {
5285
+ this.mergeSession(session);
5286
+ this.trimSessions();
5287
+ await this.persist();
5288
+ }
5289
+ async replaceSessions(sessions) {
5290
+ const remoteIds = new Set(sessions.map((session) => session.id));
5291
+ for (const session of sessions) {
5292
+ this.mergeSession(session);
5293
+ }
5294
+ for (const sessionId of Object.keys(this.state.sessions)) {
5295
+ if (!remoteIds.has(sessionId)) {
5296
+ delete this.state.sessions[sessionId];
5297
+ }
5298
+ }
5299
+ this.trimSessions();
5300
+ await this.persist();
5301
+ }
5302
+ mergeSession(session) {
5165
5303
  const existing = this.state.sessions[session.id];
5304
+ const title = preserveExistingHubTitle(existing?.session.title, session.title);
5166
5305
  const mergedSession = {
5167
5306
  ...session,
5307
+ title,
5168
5308
  lastEventSeq: Math.max(session.lastEventSeq, existing?.session.lastEventSeq ?? 0)
5169
5309
  };
5170
5310
  this.state.sessions[session.id] = {
@@ -5173,8 +5313,6 @@ var FileSessionStore = class {
5173
5313
  events: existing?.events ?? [],
5174
5314
  hydrated: existing?.hydrated ?? false
5175
5315
  };
5176
- this.trimSessions();
5177
- await this.persist();
5178
5316
  }
5179
5317
  async renameSession(sessionId, title) {
5180
5318
  const record = this.requireSession(sessionId);
@@ -5243,6 +5381,18 @@ var FileSessionStore = class {
5243
5381
  await this.persistChain;
5244
5382
  }
5245
5383
  };
5384
+ function preserveExistingHubTitle(existingTitle, incomingTitle) {
5385
+ if (isHubTitle2(existingTitle) && isControlCenterTitle2(incomingTitle)) {
5386
+ return existingTitle;
5387
+ }
5388
+ return incomingTitle;
5389
+ }
5390
+ function isHubTitle2(title) {
5391
+ return typeof title === "string" && title.trim().startsWith(HUB_SESSION_TITLE_PREFIX2);
5392
+ }
5393
+ function isControlCenterTitle2(title) {
5394
+ return title.trim() === CONTROL_CENTER_SESSION_TITLE2;
5395
+ }
5246
5396
  function dedupeMessages(messages) {
5247
5397
  return Array.from(new Map(messages.map((message) => [message.id, message])).values()).sort(
5248
5398
  (left, right) => left.createdAt.localeCompare(right.createdAt)
@@ -5588,6 +5738,7 @@ function createConsoleServer(input) {
5588
5738
  broadcastSessionEvent(event.sessionId, event);
5589
5739
  },
5590
5740
  listSessionEvents: (sessionId) => store.getSession(sessionId)?.events ?? [],
5741
+ listKnownSessions: () => store.listSessions(),
5591
5742
  onEnsureSessionStream: ensureSessionStream,
5592
5743
  getLastEventSeq: (sessionId) => store.getLastEventSeq(sessionId),
5593
5744
  onStatusChange: broadcastStatus
@@ -5929,9 +6080,7 @@ function createConsoleServer(input) {
5929
6080
  const before = sessionListSignature(store.listSessions());
5930
6081
  const remoteSessions = await input.gateway.listSessions(input.persistence.maxSessions);
5931
6082
  lastGatewayError = null;
5932
- for (const session of remoteSessions) {
5933
- await store.upsertSession(session);
5934
- }
6083
+ await store.replaceSessions(remoteSessions);
5935
6084
  return before !== sessionListSignature(store.listSessions());
5936
6085
  } catch (error) {
5937
6086
  lastGatewayError = error instanceof Error ? error : new Error(String(error));
@@ -8617,11 +8766,13 @@ function toRecord3(value) {
8617
8766
  }
8618
8767
 
8619
8768
  // src/install-qclaw.ts
8769
+ var import_node_crypto4 = require("crypto");
8620
8770
  var import_promises4 = require("fs/promises");
8621
8771
  var import_node_fs4 = require("fs");
8622
8772
  var import_node_os2 = require("os");
8623
8773
  var import_node_path6 = require("path");
8624
8774
  var PLUGIN_ID = "claw-control-center";
8775
+ var LEGACY_PLUGIN_ID = "53ai-openclaw";
8625
8776
  var DEFAULT_QCLAW_HOME = (0, import_node_path6.resolve)((0, import_node_os2.homedir)(), ".qclaw");
8626
8777
  var DEFAULT_OPENCLAW_HOME = (0, import_node_path6.resolve)((0, import_node_os2.homedir)(), ".openclaw");
8627
8778
  var DEFAULT_EXTENSIONS_DIR = (0, import_node_path6.resolve)(
@@ -8681,10 +8832,18 @@ async function installIntoHost(input, hostLabel) {
8681
8832
  const consolePort = normalizePort(input.consolePort);
8682
8833
  const plugins = ensureObject(config, "plugins");
8683
8834
  plugins.enabled = true;
8684
- plugins.allow = dedupeStrings([...Array.isArray(plugins.allow) ? plugins.allow : [], PLUGIN_ID]);
8835
+ plugins.allow = dedupeStrings([
8836
+ ...Array.isArray(plugins.allow) ? plugins.allow.filter((entry) => entry !== LEGACY_PLUGIN_ID) : [],
8837
+ PLUGIN_ID
8838
+ ]);
8685
8839
  const load = ensureObject(plugins, "load");
8686
8840
  load.paths = dedupeStrings([...Array.isArray(load.paths) ? load.paths : [], input.extensionsDir]);
8687
8841
  const entries = ensureObject(plugins, "entries");
8842
+ const legacyEntry = entries[LEGACY_PLUGIN_ID] && typeof entries[LEGACY_PLUGIN_ID] === "object" && !Array.isArray(entries[LEGACY_PLUGIN_ID]) ? entries[LEGACY_PLUGIN_ID] : void 0;
8843
+ if (legacyEntry) {
8844
+ legacyEntry.enabled = false;
8845
+ entries[LEGACY_PLUGIN_ID] = legacyEntry;
8846
+ }
8688
8847
  const previousEntry = ensureObject(entries, PLUGIN_ID);
8689
8848
  const previousConfig = ensureObject(previousEntry, "config");
8690
8849
  const previousGateway = ensureObject(previousConfig, "gateway");
@@ -8737,7 +8896,8 @@ async function installIntoHost(input, hostLabel) {
8737
8896
  extensionsDir: input.extensionsDir,
8738
8897
  destination,
8739
8898
  gatewayBaseUrl,
8740
- hub53aiConfigured: hubConfigured
8899
+ hub53aiConfigured: hubConfigured,
8900
+ pluginBuild: await readPluginBuildInfo(destination)
8741
8901
  };
8742
8902
  }
8743
8903
  async function runInstallCommand(input) {
@@ -8777,6 +8937,7 @@ async function runInstallCommand(input) {
8777
8937
  `Config: ${result.configPath}`,
8778
8938
  `Gateway: ${result.gatewayBaseUrl}`,
8779
8939
  `53AIHub: ${result.hub53aiConfigured ? "configured" : "not configured"}`,
8940
+ `Plugin build: ${result.pluginBuild}`,
8780
8941
  `Restart ${targetInfo.label} to load the plugin.`
8781
8942
  ].join("\n") + "\n"
8782
8943
  );
@@ -8818,6 +8979,26 @@ async function copyPublishablePackage(packageRoot, destination) {
8818
8979
  await (0, import_promises4.cp)(source, target, { recursive: true, force: true });
8819
8980
  }
8820
8981
  }
8982
+ async function readPluginBuildInfo(destination) {
8983
+ const packagePath = (0, import_node_path6.join)(destination, "package.json");
8984
+ const entryPath = (0, import_node_path6.join)(destination, "dist", "index.cjs");
8985
+ let version = "unknown";
8986
+ try {
8987
+ const packageJson = JSON.parse(await (0, import_promises4.readFile)(packagePath, "utf8"));
8988
+ if (typeof packageJson.version === "string" && packageJson.version.trim()) {
8989
+ version = packageJson.version.trim();
8990
+ }
8991
+ } catch {
8992
+ version = "unknown";
8993
+ }
8994
+ try {
8995
+ const entry = await (0, import_promises4.readFile)(entryPath);
8996
+ const digest = (0, import_node_crypto4.createHash)("sha256").update(entry).digest("hex").slice(0, 12);
8997
+ return `${PLUGIN_ID}@${version} sha256:${digest}`;
8998
+ } catch {
8999
+ return `${PLUGIN_ID}@${version} sha256:missing`;
9000
+ }
9001
+ }
8821
9002
  async function readOpenClawConfig(configPath) {
8822
9003
  if (!(0, import_node_fs4.existsSync)(configPath)) {
8823
9004
  return {};
@@ -8963,7 +9144,7 @@ async function createRuntime(input) {
8963
9144
  configPath: input.configPath,
8964
9145
  hostKind,
8965
9146
  pluginVersion: input.version,
8966
- token: (0, import_node_crypto4.randomUUID)(),
9147
+ token: (0, import_node_crypto5.randomUUID)(),
8967
9148
  gatewayConfig,
8968
9149
  hub53aiConfig: config.hub53ai,
8969
9150
  consoleConfig: config.console,
package/dist/index.d.cts CHANGED
@@ -142,6 +142,7 @@ type Hub53AIIncomingMessage = {
142
142
  imageUrls?: string[];
143
143
  fileUrls?: string[];
144
144
  quoteContent?: string;
145
+ conversationTitle?: string;
145
146
  };
146
147
  type Hub53AIOutgoingChunk = {
147
148
  req_id: string;
@@ -187,6 +188,7 @@ type HubBridgeCallbacks = {
187
188
  onSessionStatus(sessionId: string, status: SessionStatus): Promise<void>;
188
189
  onBridgeThinkingEvent?(event: TimelineEvent): Promise<void>;
189
190
  listSessionEvents?(sessionId: string): TimelineEvent[] | Promise<TimelineEvent[]>;
191
+ listKnownSessions?(): GatewaySession[] | Promise<GatewaySession[]>;
190
192
  onEnsureSessionStream(sessionId: string): Promise<void>;
191
193
  getLastEventSeq(sessionId: string): number;
192
194
  onStatusChange(): void;
@@ -25,11 +25,13 @@ __export(install_qclaw_exports, {
25
25
  runInstallCommand: () => runInstallCommand
26
26
  });
27
27
  module.exports = __toCommonJS(install_qclaw_exports);
28
+ var import_node_crypto = require("crypto");
28
29
  var import_promises = require("fs/promises");
29
30
  var import_node_fs = require("fs");
30
31
  var import_node_os = require("os");
31
32
  var import_node_path = require("path");
32
33
  var PLUGIN_ID = "claw-control-center";
34
+ var LEGACY_PLUGIN_ID = "53ai-openclaw";
33
35
  var DEFAULT_QCLAW_HOME = (0, import_node_path.resolve)((0, import_node_os.homedir)(), ".qclaw");
34
36
  var DEFAULT_OPENCLAW_HOME = (0, import_node_path.resolve)((0, import_node_os.homedir)(), ".openclaw");
35
37
  var DEFAULT_EXTENSIONS_DIR = (0, import_node_path.resolve)(
@@ -89,10 +91,18 @@ async function installIntoHost(input, hostLabel) {
89
91
  const consolePort = normalizePort(input.consolePort);
90
92
  const plugins = ensureObject(config, "plugins");
91
93
  plugins.enabled = true;
92
- plugins.allow = dedupeStrings([...Array.isArray(plugins.allow) ? plugins.allow : [], PLUGIN_ID]);
94
+ plugins.allow = dedupeStrings([
95
+ ...Array.isArray(plugins.allow) ? plugins.allow.filter((entry) => entry !== LEGACY_PLUGIN_ID) : [],
96
+ PLUGIN_ID
97
+ ]);
93
98
  const load = ensureObject(plugins, "load");
94
99
  load.paths = dedupeStrings([...Array.isArray(load.paths) ? load.paths : [], input.extensionsDir]);
95
100
  const entries = ensureObject(plugins, "entries");
101
+ const legacyEntry = entries[LEGACY_PLUGIN_ID] && typeof entries[LEGACY_PLUGIN_ID] === "object" && !Array.isArray(entries[LEGACY_PLUGIN_ID]) ? entries[LEGACY_PLUGIN_ID] : void 0;
102
+ if (legacyEntry) {
103
+ legacyEntry.enabled = false;
104
+ entries[LEGACY_PLUGIN_ID] = legacyEntry;
105
+ }
96
106
  const previousEntry = ensureObject(entries, PLUGIN_ID);
97
107
  const previousConfig = ensureObject(previousEntry, "config");
98
108
  const previousGateway = ensureObject(previousConfig, "gateway");
@@ -145,7 +155,8 @@ async function installIntoHost(input, hostLabel) {
145
155
  extensionsDir: input.extensionsDir,
146
156
  destination,
147
157
  gatewayBaseUrl,
148
- hub53aiConfigured: hubConfigured
158
+ hub53aiConfigured: hubConfigured,
159
+ pluginBuild: await readPluginBuildInfo(destination)
149
160
  };
150
161
  }
151
162
  async function runInstallCommand(input) {
@@ -185,6 +196,7 @@ async function runInstallCommand(input) {
185
196
  `Config: ${result.configPath}`,
186
197
  `Gateway: ${result.gatewayBaseUrl}`,
187
198
  `53AIHub: ${result.hub53aiConfigured ? "configured" : "not configured"}`,
199
+ `Plugin build: ${result.pluginBuild}`,
188
200
  `Restart ${targetInfo.label} to load the plugin.`
189
201
  ].join("\n") + "\n"
190
202
  );
@@ -226,6 +238,26 @@ async function copyPublishablePackage(packageRoot, destination) {
226
238
  await (0, import_promises.cp)(source, target, { recursive: true, force: true });
227
239
  }
228
240
  }
241
+ async function readPluginBuildInfo(destination) {
242
+ const packagePath = (0, import_node_path.join)(destination, "package.json");
243
+ const entryPath = (0, import_node_path.join)(destination, "dist", "index.cjs");
244
+ let version = "unknown";
245
+ try {
246
+ const packageJson = JSON.parse(await (0, import_promises.readFile)(packagePath, "utf8"));
247
+ if (typeof packageJson.version === "string" && packageJson.version.trim()) {
248
+ version = packageJson.version.trim();
249
+ }
250
+ } catch {
251
+ version = "unknown";
252
+ }
253
+ try {
254
+ const entry = await (0, import_promises.readFile)(entryPath);
255
+ const digest = (0, import_node_crypto.createHash)("sha256").update(entry).digest("hex").slice(0, 12);
256
+ return `${PLUGIN_ID}@${version} sha256:${digest}`;
257
+ } catch {
258
+ return `${PLUGIN_ID}@${version} sha256:missing`;
259
+ }
260
+ }
229
261
  async function readOpenClawConfig(configPath) {
230
262
  if (!(0, import_node_fs.existsSync)(configPath)) {
231
263
  return {};
@@ -20,6 +20,7 @@ declare function installIntoQClaw(input: InstallInput): Promise<{
20
20
  destination: string;
21
21
  gatewayBaseUrl: string;
22
22
  hub53aiConfigured: boolean;
23
+ pluginBuild: string;
23
24
  }>;
24
25
  declare function installIntoOpenClaw(input: InstallInput): Promise<{
25
26
  configPath: string;
@@ -27,6 +28,7 @@ declare function installIntoOpenClaw(input: InstallInput): Promise<{
27
28
  destination: string;
28
29
  gatewayBaseUrl: string;
29
30
  hub53aiConfigured: boolean;
31
+ pluginBuild: string;
30
32
  }>;
31
33
  declare function runInstallCommand(input: {
32
34
  argv?: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-control-center",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "types": "dist/index.d.ts",