claw-control-center 0.1.2 → 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
@@ -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);
@@ -4216,13 +4218,6 @@ function createHub53AIBridge(input) {
4216
4218
  if (!chatId) {
4217
4219
  throw new HubRPCError("PARAM_ERROR", "chat_id or user is required");
4218
4220
  }
4219
- if (isOpenClawSessionId(chatId)) {
4220
- return input.gateway.getSession(chatId);
4221
- }
4222
- const mappedSession = await getMappedSession(chatId);
4223
- if (mappedSession) {
4224
- return mappedSession;
4225
- }
4226
4221
  return restoreLatestHubSession(chatId, userName);
4227
4222
  }
4228
4223
  async function getMappedSession(chatId) {
@@ -4252,15 +4247,34 @@ function createHub53AIBridge(input) {
4252
4247
  limit: 100,
4253
4248
  offset: 0
4254
4249
  });
4255
- const sessions = page.sessions.filter((session2) => isRestorableHubSession(session2, chatId, userName)).sort((left, right) => toTime(right.updatedAt || right.createdAt) - toTime(left.updatedAt || left.createdAt));
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));
4256
4252
  const session = sessions[0];
4257
4253
  if (!session) {
4258
4254
  return null;
4259
4255
  }
4260
- state.mappings[chatId] = session.id;
4261
- await persistState();
4262
4256
  return session;
4263
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
+ }
4264
4278
  async function buildFallbackStatus() {
4265
4279
  const [gatewayHealth, runtimeInfo] = await Promise.allSettled([input.gateway.getHealth(), input.gateway.getRuntimeInfo()]);
4266
4280
  return {
@@ -4548,18 +4562,10 @@ function createHub53AIBridge(input) {
4548
4562
  async function resolveSession(message) {
4549
4563
  const desiredTitle = buildHubSessionTitle(message);
4550
4564
  if (isOpenClawSessionId(message.chatId)) {
4551
- const session2 = await input.gateway.getSession(message.chatId);
4552
- state.mappings[message.chatId] = session2.id;
4553
- await persistState();
4565
+ const session2 = await getSessionWithKnownHubTitle(message.chatId, message.conversationTitle);
4554
4566
  await input.callbacks.onSessionUpsert(session2);
4555
4567
  return session2;
4556
4568
  }
4557
- const mappedSession = await getMappedSession(message.chatId);
4558
- if (mappedSession) {
4559
- const nextSession = await renamePlaceholderSessionIfNeeded(mappedSession, message, desiredTitle);
4560
- await input.callbacks.onSessionUpsert(nextSession);
4561
- return nextSession;
4562
- }
4563
4569
  const restoredSession = await restoreLatestHubSession(message.chatId, message.userName || message.userId);
4564
4570
  if (restoredSession) {
4565
4571
  const nextSession = await renamePlaceholderSessionIfNeeded(restoredSession, message, desiredTitle);
@@ -4567,11 +4573,15 @@ function createHub53AIBridge(input) {
4567
4573
  return nextSession;
4568
4574
  }
4569
4575
  const session = await createSessionWithUniqueTitle(desiredTitle);
4570
- state.mappings[message.chatId] = session.id;
4571
- await persistState();
4572
4576
  await input.callbacks.onSessionUpsert(session);
4573
4577
  return session;
4574
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
+ }
4575
4585
  async function createSessionWithUniqueTitle(baseTitle) {
4576
4586
  let lastDuplicateError;
4577
4587
  for (let attempt = 0; attempt < 6; attempt += 1) {
@@ -4737,6 +4747,7 @@ function parseIncomingMessage(rawJson) {
4737
4747
  chatId: chatId2,
4738
4748
  userId: userId2,
4739
4749
  userName: extractUserName(openAIReq, metadata, userObject2, userMessage),
4750
+ conversationTitle: extractConversationTitle(openAIReq, metadata),
4740
4751
  text: extractTextFromContent(content),
4741
4752
  imageUrls: extractImagesFromContent(content),
4742
4753
  fileUrls: extractFilesFromContent(content)
@@ -4756,6 +4767,7 @@ function parseIncomingMessage(rawJson) {
4756
4767
  chatId,
4757
4768
  userId,
4758
4769
  userName: extractUserName(data, userObject),
4770
+ conversationTitle: extractConversationTitle(data),
4759
4771
  text: stringOr(data.text, data.content, ""),
4760
4772
  imageUrls: normalizeUrlList(data.imageUrls, data.images),
4761
4773
  fileUrls: normalizeUrlList(data.fileUrls, data.files),
@@ -4895,6 +4907,23 @@ function extractUserName(...sources) {
4895
4907
  }
4896
4908
  return void 0;
4897
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
+ }
4898
4927
  function stringFromKeys(record, keys) {
4899
4928
  for (const key of keys) {
4900
4929
  const value = record[key];
@@ -4963,6 +4992,44 @@ function isRestorableHubSession(session, chatId, userName) {
4963
4992
  }
4964
4993
  return normalized.startsWith(`${HUB_SESSION_TITLE_PREFIX}${sanitizeTitlePart(userName)}\uFF1A`);
4965
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
+ }
4966
5033
  function toTime(value) {
4967
5034
  if (!value) return 0;
4968
5035
  const time = Date.parse(value);
@@ -5181,6 +5248,8 @@ function toRecord2(value) {
5181
5248
  // src/file-store.ts
5182
5249
  var import_promises2 = require("fs/promises");
5183
5250
  var import_node_path2 = require("path");
5251
+ var HUB_SESSION_TITLE_PREFIX2 = "53AI Hub-";
5252
+ var CONTROL_CENTER_SESSION_TITLE2 = "Claw Control Center";
5184
5253
  var FileSessionStore = class {
5185
5254
  constructor(filePath, maxSessions) {
5186
5255
  this.filePath = filePath;
@@ -5213,9 +5282,29 @@ var FileSessionStore = class {
5213
5282
  return this.state.sessions[sessionId]?.hydrated ?? false;
5214
5283
  }
5215
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) {
5216
5303
  const existing = this.state.sessions[session.id];
5304
+ const title = preserveExistingHubTitle(existing?.session.title, session.title);
5217
5305
  const mergedSession = {
5218
5306
  ...session,
5307
+ title,
5219
5308
  lastEventSeq: Math.max(session.lastEventSeq, existing?.session.lastEventSeq ?? 0)
5220
5309
  };
5221
5310
  this.state.sessions[session.id] = {
@@ -5224,8 +5313,6 @@ var FileSessionStore = class {
5224
5313
  events: existing?.events ?? [],
5225
5314
  hydrated: existing?.hydrated ?? false
5226
5315
  };
5227
- this.trimSessions();
5228
- await this.persist();
5229
5316
  }
5230
5317
  async renameSession(sessionId, title) {
5231
5318
  const record = this.requireSession(sessionId);
@@ -5294,6 +5381,18 @@ var FileSessionStore = class {
5294
5381
  await this.persistChain;
5295
5382
  }
5296
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
+ }
5297
5396
  function dedupeMessages(messages) {
5298
5397
  return Array.from(new Map(messages.map((message) => [message.id, message])).values()).sort(
5299
5398
  (left, right) => left.createdAt.localeCompare(right.createdAt)
@@ -5639,6 +5738,7 @@ function createConsoleServer(input) {
5639
5738
  broadcastSessionEvent(event.sessionId, event);
5640
5739
  },
5641
5740
  listSessionEvents: (sessionId) => store.getSession(sessionId)?.events ?? [],
5741
+ listKnownSessions: () => store.listSessions(),
5642
5742
  onEnsureSessionStream: ensureSessionStream,
5643
5743
  getLastEventSeq: (sessionId) => store.getLastEventSeq(sessionId),
5644
5744
  onStatusChange: broadcastStatus
@@ -5980,9 +6080,7 @@ function createConsoleServer(input) {
5980
6080
  const before = sessionListSignature(store.listSessions());
5981
6081
  const remoteSessions = await input.gateway.listSessions(input.persistence.maxSessions);
5982
6082
  lastGatewayError = null;
5983
- for (const session of remoteSessions) {
5984
- await store.upsertSession(session);
5985
- }
6083
+ await store.replaceSessions(remoteSessions);
5986
6084
  return before !== sessionListSignature(store.listSessions());
5987
6085
  } catch (error) {
5988
6086
  lastGatewayError = error instanceof Error ? error : new Error(String(error));
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-control-center",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "types": "dist/index.d.ts",