@inline-openclaw/inline 0.0.41 → 0.0.43

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/README.md CHANGED
@@ -20,7 +20,7 @@ Supports:
20
20
  Reply-thread behavior:
21
21
 
22
22
  - Small/new parent-chat conversations stay in the parent chat by default.
23
- - `replyThreadMode: "thread"` opts a parent chat into automatic reply-thread delivery once `replyThreadAutoCreateMinMessages` is reached.
23
+ - `replyThreadMode: "thread"` opts a parent chat into automatic reply-thread delivery once `replyThreadAutoCreateMinMessages` is reached; each triggering parent-chat message gets its own child reply thread.
24
24
  - `replyThreadMode: "main"` keeps automatic replies in the parent chat, while explicit `thread-create` and `thread-reply` tools remain available.
25
25
  - `thread-create` creates a real Inline reply thread. `thread-reply` sends into the child reply-thread chat id returned by `thread-create`.
26
26
  - Inbound reply-thread messages use the parent chat as the base conversation target and the child reply-thread chat id as `MessageThreadId`.
@@ -135,7 +135,7 @@ channels:
135
135
  - "inline:123" # or "user:123" or just "123"
136
136
  requireMention: true # optional: default is false
137
137
  replyToBotWithoutMention: true # if true, replies to bot messages can bypass mention requirement
138
- replyThreadMode: "auto" # auto|thread|main; thread auto-routes long parent-chat replies into one reply thread
138
+ replyThreadMode: "auto" # auto|thread|main; thread auto-routes long parent-chat replies into per-message reply threads
139
139
  replyThreadAutoCreateMinMessages: 50 # optional, default 50; avoids creating reply threads for small/new chats
140
140
  replyThreadRequireExplicitMention: false # optional, default false; bot-participated reply threads continue without @mention
141
141
  replyThreadParentHistoryLimit: 10 # optional, default 10; set 0 to disable parent-chat context before the anchor
@@ -186,7 +186,8 @@ Reply behavior summary:
186
186
  - `replyToId` is always an Inline message id.
187
187
  - Inline reply threads are separate and use OpenClaw `threadId`.
188
188
  - Use `replyThreadMode: "main"` if you only want automatic parent-chat replies to stay in the parent chat.
189
- - Use `replyThreadMode: "thread"` plus `replyThreadAutoCreateMinMessages` when long parent-chat conversations should move into one stable reply thread.
189
+ - Use `replyThreadMode: "thread"` plus `replyThreadAutoCreateMinMessages` when parent-chat bot turns should move into child reply threads anchored to the triggering parent messages.
190
+ - In an Inline group, authorized users can run `/threadreply` to choose the chat's mode with buttons, or `/threadreply thread|main|auto|inherit|status`.
190
191
  - Bot-participated reply threads continue without `@bot` by default, including from persisted recent participation state. Set `replyThreadRequireExplicitMention: true` if a chat should require `@bot` on every reply-thread message.
191
192
  - `replyThreadParentHistoryLimit` defaults to `10`, so reply-thread turns include nearby parent-chat context before the anchor. Set it to `0` only when a chat should stay strictly thread-local.
192
193
 
@@ -237,7 +238,7 @@ Inline reply-thread semantics:
237
238
  - `thread-reply` expects `threadId` to be the child reply-thread chat id, while `to` stays the parent chat id.
238
239
  - `thread-create` creates a real reply thread from a parent chat and optional `replyToId` anchor.
239
240
  - Automatic thread creation falls back to parent-chat delivery if the reply thread cannot be created.
240
- - Existing route state is reused so long conversations stay in one reply thread instead of creating one thread per message.
241
+ - Existing route state is reused only for the same parent-message anchor. New parent-chat messages get separate reply threads; messages already inside a reply thread stay in that thread and do not create nested threads.
241
242
  - If the current reply-thread message has no direct media, OpenClaw can inherit the anchor message media so image/file-only thread starters remain visible to the agent.
242
243
 
243
244
  You can gate action groups from config:
@@ -291,6 +292,7 @@ Bot command sync:
291
292
 
292
293
  - On `gateway_start`, the plugin registers default bot commands for each enabled/configured Inline account.
293
294
  - Default commands include the same user-facing command set as bundled chat providers (for example: `/status`, `/model`, `/exec`, `/usage`, etc.).
295
+ - Inline also registers `/threadreply` to manage this group's reply-thread mode from chat.
294
296
  - The plugin uses OpenClaw's command, skill command, and plugin command registries when available.
295
297
  - Disable startup sync globally with `commands.native: false`, or per-channel with `channels.inline.commands.native: false`. Disabled startup sync clears existing Inline bot commands for the affected account.
296
298
  - Disable skill command inclusion with `commands.nativeSkills: false`, or per-channel with `channels.inline.commands.nativeSkills: false`.
@@ -20572,6 +20572,8 @@ class InlineSdkClient {
20572
20572
  saveInFlight = null;
20573
20573
  catchUpInFlightByChatId = new Map;
20574
20574
  catchUpRequestedByChatId = new Map;
20575
+ catchUpInFlightBySpaceId = new Map;
20576
+ catchUpRequestedBySpaceId = new Map;
20575
20577
  userCatchUpInFlight = null;
20576
20578
  constructor(options) {
20577
20579
  this.options = options;
@@ -20636,7 +20638,11 @@ class InlineSdkClient {
20636
20638
  this.started = false;
20637
20639
  this.rejectOpen(new Error("closed"));
20638
20640
  this.eventStream.close();
20639
- await Promise.allSettled(this.catchUpInFlightByChatId.values());
20641
+ await Promise.allSettled([
20642
+ ...this.catchUpInFlightByChatId.values(),
20643
+ ...this.catchUpInFlightBySpaceId.values(),
20644
+ ...this.userCatchUpInFlight ? [this.userCatchUpInFlight] : []
20645
+ ]);
20640
20646
  await this.flushStateSave();
20641
20647
  await this.protocol.stopTransport();
20642
20648
  }
@@ -20660,6 +20666,7 @@ class InlineSdkClient {
20660
20666
  version: 1,
20661
20667
  ...this.state.dateCursor != null ? { dateCursor: this.state.dateCursor } : {},
20662
20668
  ...this.state.lastSeqByChatId != null ? { lastSeqByChatId: { ...this.state.lastSeqByChatId } } : {},
20669
+ ...this.state.lastSeqBySpaceId != null ? { lastSeqBySpaceId: { ...this.state.lastSeqBySpaceId } } : {},
20663
20670
  ...this.state.lastUserSeq != null ? { lastUserSeq: this.state.lastUserSeq } : {}
20664
20671
  };
20665
20672
  }
@@ -20680,12 +20687,7 @@ class InlineSdkClient {
20680
20687
  const chat = result.getChat.chat;
20681
20688
  if (!chat)
20682
20689
  throw new Error("getChat: missing chat");
20683
- return {
20684
- chatId: chat.id,
20685
- peer: chat.peerId,
20686
- title: chat.title,
20687
- ...chat.lastMsgId != null ? { lastMsgId: chat.lastMsgId } : {}
20688
- };
20690
+ return { chatId: chat.id, peer: chat.peerId, title: chat.title };
20689
20691
  }
20690
20692
  async getMessages(params) {
20691
20693
  const peerId = this.inputPeerFromTarget(params, "getMessages");
@@ -21014,32 +21016,57 @@ class InlineSdkClient {
21014
21016
  }
21015
21017
  case "clearChatHistory": {
21016
21018
  const payload = update.update.clearChatHistory;
21019
+ if (!payload.target) {
21020
+ this.log.warn?.("Skipping clearChatHistory update without target");
21021
+ return;
21022
+ }
21017
21023
  if (payload.target.oneofKind === "spaceId") {
21024
+ this.bumpSpaceSeq(payload.target.spaceId, seq);
21018
21025
  await this.eventStream.send({
21019
21026
  kind: "space.history.clear",
21020
21027
  spaceId: payload.target.spaceId,
21021
21028
  ...payload.beforeDate != null ? { beforeDate: payload.beforeDate } : {},
21022
21029
  deleteReplyThreads: payload.deleteReplyThreads,
21030
+ deletedChatIds: payload.deletedChatIds,
21031
+ orphanedChatIds: payload.orphanedChatIds,
21032
+ detachedChatIds: payload.detachedChatIds,
21023
21033
  seq,
21024
21034
  date
21025
21035
  });
21026
21036
  return;
21027
21037
  }
21028
21038
  const peerId = payload.target.oneofKind === "peerId" ? payload.target.peerId : undefined;
21029
- const chatId = peerId?.type.oneofKind === "chat" ? peerId.type.chat.chatId : null;
21030
- if (!chatId) {
21031
- this.log.warn?.("Skipping clearChatHistory update without chat peer", peerId);
21039
+ if (peerId?.type.oneofKind === "chat") {
21040
+ const chatId = peerId.type.chat.chatId;
21041
+ this.bumpChatSeq(chatId, seq);
21042
+ await this.eventStream.send({
21043
+ kind: "message.history.clear",
21044
+ chatId,
21045
+ ...payload.beforeDate != null ? { beforeDate: payload.beforeDate } : {},
21046
+ deleteReplyThreads: payload.deleteReplyThreads,
21047
+ deletedChatIds: payload.deletedChatIds,
21048
+ orphanedChatIds: payload.orphanedChatIds,
21049
+ detachedChatIds: payload.detachedChatIds,
21050
+ seq,
21051
+ date
21052
+ });
21032
21053
  return;
21033
21054
  }
21034
- this.bumpChatSeq(chatId, seq);
21035
- await this.eventStream.send({
21036
- kind: "message.history.clear",
21037
- chatId,
21038
- ...payload.beforeDate != null ? { beforeDate: payload.beforeDate } : {},
21039
- deleteReplyThreads: payload.deleteReplyThreads,
21040
- seq,
21041
- date
21042
- });
21055
+ if (peerId?.type.oneofKind === "user") {
21056
+ await this.eventStream.send({
21057
+ kind: "message.history.clear",
21058
+ userId: peerId.type.user.userId,
21059
+ ...payload.beforeDate != null ? { beforeDate: payload.beforeDate } : {},
21060
+ deleteReplyThreads: payload.deleteReplyThreads,
21061
+ deletedChatIds: payload.deletedChatIds,
21062
+ orphanedChatIds: payload.orphanedChatIds,
21063
+ detachedChatIds: payload.detachedChatIds,
21064
+ seq,
21065
+ date
21066
+ });
21067
+ return;
21068
+ }
21069
+ this.log.warn?.("Skipping clearChatHistory update without peer target", peerId);
21043
21070
  return;
21044
21071
  }
21045
21072
  case "updateReaction": {
@@ -21123,6 +21150,7 @@ class InlineSdkClient {
21123
21150
  seq,
21124
21151
  date
21125
21152
  });
21153
+ this.requestCatchUpSpace({ spaceId: payload.spaceId, updateSeq: payload.updateSeq });
21126
21154
  return;
21127
21155
  }
21128
21156
  default:
@@ -21141,6 +21169,18 @@ class InlineSdkClient {
21141
21169
  this.scheduleStateSave();
21142
21170
  }
21143
21171
  }
21172
+ bumpSpaceSeq(spaceId, seq) {
21173
+ if (!Number.isFinite(seq))
21174
+ return;
21175
+ if (!this.state.lastSeqBySpaceId)
21176
+ this.state.lastSeqBySpaceId = {};
21177
+ const key = spaceId.toString();
21178
+ const prev = this.state.lastSeqBySpaceId[key] ?? 0;
21179
+ if (seq > prev) {
21180
+ this.state.lastSeqBySpaceId[key] = seq;
21181
+ this.scheduleStateSave();
21182
+ }
21183
+ }
21144
21184
  shouldSkipUserSeq(seq) {
21145
21185
  if (!Number.isFinite(seq))
21146
21186
  return false;
@@ -21320,6 +21360,112 @@ class InlineSdkClient {
21320
21360
  }
21321
21361
  return false;
21322
21362
  }
21363
+ requestCatchUpSpace(params) {
21364
+ const previous = this.catchUpRequestedBySpaceId.get(params.spaceId);
21365
+ this.catchUpRequestedBySpaceId.set(params.spaceId, {
21366
+ endSeq: Math.max(previous?.endSeq ?? 0, params.updateSeq)
21367
+ });
21368
+ if (this.catchUpInFlightBySpaceId.has(params.spaceId)) {
21369
+ return;
21370
+ }
21371
+ const task = this.drainCatchUpSpace(params.spaceId).catch((error) => {
21372
+ this.catchUpRequestedBySpaceId.delete(params.spaceId);
21373
+ this.log.warn?.("GET_UPDATES space catch-up failed; continuing live delivery", {
21374
+ spaceId: params.spaceId.toString(),
21375
+ error: extractErrorMessage(error)
21376
+ });
21377
+ }).finally(() => {
21378
+ this.catchUpInFlightBySpaceId.delete(params.spaceId);
21379
+ });
21380
+ this.catchUpInFlightBySpaceId.set(params.spaceId, task);
21381
+ }
21382
+ async drainCatchUpSpace(spaceId) {
21383
+ const key = spaceId.toString();
21384
+ while (true) {
21385
+ const request = this.catchUpRequestedBySpaceId.get(spaceId);
21386
+ if (!request)
21387
+ return;
21388
+ const lastSeq = this.state.lastSeqBySpaceId?.[key];
21389
+ const startSeq = lastSeq ?? Math.max(0, request.endSeq - defaultColdStartCatchUpWindow);
21390
+ if (request.endSeq <= startSeq) {
21391
+ this.catchUpRequestedBySpaceId.delete(spaceId);
21392
+ return;
21393
+ }
21394
+ const stop = await this.doCatchUpSpace(spaceId, startSeq, request.endSeq);
21395
+ if (stop) {
21396
+ this.catchUpRequestedBySpaceId.delete(spaceId);
21397
+ return;
21398
+ }
21399
+ const latest = this.catchUpRequestedBySpaceId.get(spaceId);
21400
+ const syncedSeq = this.state.lastSeqBySpaceId?.[key] ?? 0;
21401
+ if (!latest || latest.endSeq <= syncedSeq) {
21402
+ this.catchUpRequestedBySpaceId.delete(spaceId);
21403
+ return;
21404
+ }
21405
+ }
21406
+ }
21407
+ async doCatchUpSpace(spaceId, startSeq, endSeq) {
21408
+ let cursor = startSeq;
21409
+ while (cursor < endSeq) {
21410
+ const result = await this.invoke(Method.GET_UPDATES, {
21411
+ oneofKind: "getUpdates",
21412
+ getUpdates: GetUpdatesInput.create({
21413
+ bucket: UpdateBucket.create({
21414
+ type: {
21415
+ oneofKind: "space",
21416
+ space: {
21417
+ spaceId
21418
+ }
21419
+ }
21420
+ }),
21421
+ startSeq: BigInt(cursor),
21422
+ seqEnd: BigInt(endSeq),
21423
+ totalLimit: defaultCatchUpTotalLimit,
21424
+ limit: defaultCatchUpPageLimit
21425
+ })
21426
+ });
21427
+ const payload = result.getUpdates;
21428
+ if (payload.resultType === GetUpdatesResult_ResultType.TOO_LONG) {
21429
+ this.log.warn?.("GET_UPDATES space too long; fast-forwarding cursor", {
21430
+ spaceId: spaceId.toString(),
21431
+ seq: payload.seq
21432
+ });
21433
+ this.bumpSpaceSeq(spaceId, endSeq);
21434
+ if (payload.date !== 0n) {
21435
+ this.state.dateCursor = payload.date;
21436
+ }
21437
+ this.scheduleStateSave();
21438
+ return true;
21439
+ }
21440
+ const deliveredSeq = Number(payload.seq ?? 0n);
21441
+ if (!Number.isSafeInteger(deliveredSeq)) {
21442
+ this.log.warn?.("GET_UPDATES space returned non-integer seq; aborting catch-up", {
21443
+ spaceId: spaceId.toString()
21444
+ });
21445
+ return true;
21446
+ }
21447
+ this.bumpSpaceSeq(spaceId, deliveredSeq);
21448
+ for (const update of payload.updates) {
21449
+ await this.handleUpdate(update);
21450
+ }
21451
+ if (payload.date !== 0n) {
21452
+ this.state.dateCursor = payload.date;
21453
+ }
21454
+ this.scheduleStateSave();
21455
+ if (payload.final)
21456
+ return true;
21457
+ if (deliveredSeq <= cursor) {
21458
+ this.log.warn?.("GET_UPDATES space made no progress; aborting catch-up", {
21459
+ spaceId: spaceId.toString(),
21460
+ cursor,
21461
+ deliveredSeq
21462
+ });
21463
+ return true;
21464
+ }
21465
+ cursor = deliveredSeq;
21466
+ }
21467
+ return false;
21468
+ }
21323
21469
  peerToInputPeer(peer, chatId) {
21324
21470
  if (!peer) {
21325
21471
  return InputPeer.create({ type: { oneofKind: "chat", chat: { chatId } } });
@@ -21400,6 +21546,7 @@ class InlineSdkClient {
21400
21546
  version: 1,
21401
21547
  ...this.state.dateCursor != null ? { dateCursor: this.state.dateCursor } : {},
21402
21548
  ...this.state.lastSeqByChatId != null ? { lastSeqByChatId: { ...this.state.lastSeqByChatId } } : {},
21549
+ ...this.state.lastSeqBySpaceId != null ? { lastSeqBySpaceId: { ...this.state.lastSeqBySpaceId } } : {},
21403
21550
  ...this.state.lastUserSeq != null ? { lastUserSeq: this.state.lastUserSeq } : {}
21404
21551
  };
21405
21552
  this.saveInFlight = store.save(snapshot).catch((error) => {
@@ -21617,7 +21764,9 @@ var serializeStateV1 = (state) => {
21617
21764
  const json = {
21618
21765
  version: 1,
21619
21766
  ...state.dateCursor != null ? { dateCursor: state.dateCursor.toString() } : {},
21620
- ...state.lastSeqByChatId != null ? { lastSeqByChatId: state.lastSeqByChatId } : {}
21767
+ ...state.lastSeqByChatId != null ? { lastSeqByChatId: state.lastSeqByChatId } : {},
21768
+ ...state.lastSeqBySpaceId != null ? { lastSeqBySpaceId: state.lastSeqBySpaceId } : {},
21769
+ ...state.lastUserSeq != null ? { lastUserSeq: state.lastUserSeq } : {}
21621
21770
  };
21622
21771
  return JSON.stringify(json, null, 2);
21623
21772
  };
@@ -21629,10 +21778,21 @@ var deserializeStateV1 = (raw) => {
21629
21778
  return {
21630
21779
  version: 1,
21631
21780
  ...parsed.dateCursor != null ? { dateCursor: BigInt(parsed.dateCursor) } : {},
21632
- ...parsed.lastSeqByChatId != null ? { lastSeqByChatId: parsed.lastSeqByChatId } : {}
21781
+ ...parsed.lastSeqByChatId != null ? { lastSeqByChatId: parsed.lastSeqByChatId } : {},
21782
+ ...parsed.lastSeqBySpaceId != null ? { lastSeqBySpaceId: parsed.lastSeqBySpaceId } : {},
21783
+ ...parsed.lastUserSeq != null ? { lastUserSeq: parsed.lastUserSeq } : {}
21633
21784
  };
21634
21785
  };
21635
21786
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21787
+ var isSeqRecord = (value) => {
21788
+ if (!isRecord2(value))
21789
+ return false;
21790
+ for (const v of Object.values(value)) {
21791
+ if (typeof v !== "number" || !Number.isFinite(v))
21792
+ return false;
21793
+ }
21794
+ return true;
21795
+ };
21636
21796
  var isStateJsonV1 = (value) => {
21637
21797
  if (!isRecord2(value))
21638
21798
  return false;
@@ -21640,14 +21800,12 @@ var isStateJsonV1 = (value) => {
21640
21800
  return false;
21641
21801
  if (value.dateCursor != null && typeof value.dateCursor !== "string")
21642
21802
  return false;
21643
- if (value.lastSeqByChatId != null) {
21644
- if (!isRecord2(value.lastSeqByChatId))
21645
- return false;
21646
- for (const v of Object.values(value.lastSeqByChatId)) {
21647
- if (typeof v !== "number" || !Number.isFinite(v))
21648
- return false;
21649
- }
21650
- }
21803
+ if (value.lastSeqByChatId != null && !isSeqRecord(value.lastSeqByChatId))
21804
+ return false;
21805
+ if (value.lastSeqBySpaceId != null && !isSeqRecord(value.lastSeqBySpaceId))
21806
+ return false;
21807
+ if (value.lastUserSeq != null && (typeof value.lastUserSeq !== "number" || !Number.isFinite(value.lastUserSeq)))
21808
+ return false;
21651
21809
  return true;
21652
21810
  };
21653
21811
 
@@ -37356,6 +37514,7 @@ var INLINE_FORMATTING_RULES = [
37356
37514
  "Prefer bullet lists over markdown tables.",
37357
37515
  "If a table is necessary, render it inside a fenced code block.",
37358
37516
  "Use plain URLs or markdown links; do not wrap bare URLs in inline code or backticks.",
37517
+ "Mention Inline users with markdown links like [@FirstName](inline://user?id=123); use inline://user?username=username only when the user id is unavailable.",
37359
37518
  "Use inline code only for actual code, commands, file paths, env vars, or identifiers."
37360
37519
  ];
37361
37520
  var INLINE_COPY_REPLACEMENTS = [
@@ -37570,6 +37729,7 @@ function keysForLookup(params) {
37570
37729
  const keys = new Set;
37571
37730
  if (params.parentMessageId) {
37572
37731
  keys.add(messageKey(params.accountId, params.parentChatId, params.parentMessageId));
37732
+ return [...keys];
37573
37733
  }
37574
37734
  if (params.agentId) {
37575
37735
  keys.add(activeKey(params.accountId, params.parentChatId, params.agentId));
@@ -40283,5 +40443,5 @@ export {
40283
40443
  inlineApprovalNativeRuntime
40284
40444
  };
40285
40445
 
40286
- //# debugId=9CC8894E01E42C4C64756E2164756E21
40446
+ //# debugId=7E42F18FB91A9A3F64756E2164756E21
40287
40447
  //# sourceMappingURL=approval-handler.runtime.js.map