@maudecode/openclaw-cowtail 0.12.1 → 0.12.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.js CHANGED
@@ -13844,6 +13844,7 @@ var openclawActionSubmittedEventSchema = openclawEventEnvelopeSchema.extend({
13844
13844
  payload: jsonObjectSchema
13845
13845
  });
13846
13846
  var openclawRequestIdSchema = nonEmptyStringSchema;
13847
+ var openclawIdempotencyKeySchema = nonEmptyStringSchema;
13847
13848
  var openclawActionDraftSchema = exports_external.object({
13848
13849
  label: nonEmptyStringSchema,
13849
13850
  kind: nonEmptyStringSchema,
@@ -13852,6 +13853,7 @@ var openclawActionDraftSchema = exports_external.object({
13852
13853
  var openclawPluginMessageCommandSchema = requireOpenClawRenderableContent(exports_external.object({
13853
13854
  type: exports_external.literal("openclaw_message"),
13854
13855
  requestId: openclawRequestIdSchema,
13856
+ idempotencyKey: openclawIdempotencyKeySchema,
13855
13857
  sessionKey: nonEmptyStringSchema,
13856
13858
  title: nonEmptyStringSchema.optional(),
13857
13859
  text: openclawMessageTextSchema,
@@ -13864,6 +13866,7 @@ var openclawPluginMessageCommandSchema = requireOpenClawRenderableContent(export
13864
13866
  var openclawPluginMessageUpdateCommandSchema = requireOpenClawRenderableContent(exports_external.object({
13865
13867
  type: exports_external.literal("openclaw_message_update"),
13866
13868
  requestId: openclawRequestIdSchema,
13869
+ idempotencyKey: openclawIdempotencyKeySchema,
13867
13870
  messageId: nonEmptyStringSchema,
13868
13871
  text: openclawMessageTextSchema,
13869
13872
  links: exports_external.array(openclawLinkSchema).optional(),
@@ -13874,46 +13877,54 @@ var openclawPluginMessageUpdateCommandSchema = requireOpenClawRenderableContent(
13874
13877
  var openclawIosNewThreadCommandSchema = exports_external.object({
13875
13878
  type: exports_external.literal("ios_new_thread"),
13876
13879
  requestId: openclawRequestIdSchema,
13880
+ idempotencyKey: openclawIdempotencyKeySchema,
13877
13881
  title: nonEmptyStringSchema.optional(),
13878
13882
  text: nonEmptyStringSchema
13879
13883
  });
13880
13884
  var openclawIosReplyCommandSchema = exports_external.object({
13881
13885
  type: exports_external.literal("ios_reply"),
13882
13886
  requestId: openclawRequestIdSchema,
13887
+ idempotencyKey: openclawIdempotencyKeySchema,
13883
13888
  threadId: nonEmptyStringSchema,
13884
13889
  text: nonEmptyStringSchema
13885
13890
  });
13886
13891
  var openclawIosActionCommandSchema = exports_external.object({
13887
13892
  type: exports_external.literal("ios_action"),
13888
13893
  requestId: openclawRequestIdSchema,
13894
+ idempotencyKey: openclawIdempotencyKeySchema,
13889
13895
  actionId: nonEmptyStringSchema,
13890
13896
  payload: jsonObjectSchema
13891
13897
  });
13892
13898
  var openclawIosMarkThreadReadCommandSchema = exports_external.object({
13893
13899
  type: exports_external.literal("ios_mark_thread_read"),
13894
13900
  requestId: openclawRequestIdSchema,
13901
+ idempotencyKey: openclawIdempotencyKeySchema,
13895
13902
  threadId: nonEmptyStringSchema
13896
13903
  });
13897
13904
  var openclawIosRenameThreadCommandSchema = exports_external.object({
13898
13905
  type: exports_external.literal("ios_rename_thread"),
13899
13906
  requestId: openclawRequestIdSchema,
13907
+ idempotencyKey: openclawIdempotencyKeySchema,
13900
13908
  threadId: nonEmptyStringSchema,
13901
13909
  title: nonEmptyStringSchema
13902
13910
  });
13903
13911
  var openclawIosDeleteThreadCommandSchema = exports_external.object({
13904
13912
  type: exports_external.literal("ios_delete_thread"),
13905
13913
  requestId: openclawRequestIdSchema,
13914
+ idempotencyKey: openclawIdempotencyKeySchema,
13906
13915
  threadId: nonEmptyStringSchema
13907
13916
  });
13908
13917
  var openclawSessionBoundCommandSchema = exports_external.object({
13909
13918
  type: exports_external.literal("openclaw_session_bound"),
13910
13919
  requestId: openclawRequestIdSchema,
13920
+ idempotencyKey: openclawIdempotencyKeySchema,
13911
13921
  threadId: nonEmptyStringSchema,
13912
13922
  sessionKey: nonEmptyStringSchema
13913
13923
  });
13914
13924
  var openclawActionResultCommandSchema = exports_external.object({
13915
13925
  type: exports_external.literal("openclaw_action_result"),
13916
13926
  requestId: openclawRequestIdSchema,
13927
+ idempotencyKey: openclawIdempotencyKeySchema,
13917
13928
  actionId: nonEmptyStringSchema,
13918
13929
  state: exports_external.enum(["submitted", "failed", "expired"]),
13919
13930
  resultMetadata: jsonObjectSchema.optional()
@@ -14164,33 +14175,41 @@ class CowtailRealtimeClient {
14164
14175
  this.#closeSocket();
14165
14176
  }
14166
14177
  sendOpenClawMessage(command) {
14178
+ const requestId = this.#requestIdFactory();
14167
14179
  return this.#sendCommand({
14168
14180
  ...command,
14169
- requestId: this.#requestIdFactory(),
14181
+ requestId,
14182
+ idempotencyKey: command.idempotencyKey ?? `cowtail:request:${requestId}`,
14170
14183
  links: command.links ?? [],
14171
14184
  toolCalls: command.toolCalls ?? [],
14172
14185
  actions: command.actions ?? []
14173
14186
  });
14174
14187
  }
14175
14188
  sendOpenClawMessageUpdate(command) {
14189
+ const requestId = this.#requestIdFactory();
14176
14190
  return this.#sendCommand({
14177
14191
  ...command,
14178
- requestId: this.#requestIdFactory(),
14192
+ requestId,
14193
+ idempotencyKey: command.idempotencyKey ?? `cowtail:request:${requestId}`,
14179
14194
  ...command.links ? { links: command.links } : {},
14180
14195
  ...command.toolCalls ? { toolCalls: command.toolCalls } : {},
14181
14196
  ...command.actions ? { actions: command.actions } : {}
14182
14197
  });
14183
14198
  }
14184
14199
  sendSessionBound(command) {
14200
+ const requestId = this.#requestIdFactory();
14185
14201
  return this.#sendCommand({
14186
14202
  ...command,
14187
- requestId: this.#requestIdFactory()
14203
+ requestId,
14204
+ idempotencyKey: command.idempotencyKey ?? `cowtail:request:${requestId}`
14188
14205
  }).then((result) => result.sequence);
14189
14206
  }
14190
14207
  sendActionResult(command) {
14208
+ const requestId = this.#requestIdFactory();
14191
14209
  return this.#sendCommand({
14192
14210
  ...command,
14193
- requestId: this.#requestIdFactory()
14211
+ requestId,
14212
+ idempotencyKey: command.idempotencyKey ?? `cowtail:request:${requestId}`
14194
14213
  }).then((result) => result.sequence);
14195
14214
  }
14196
14215
  #connect() {
@@ -14498,6 +14517,7 @@ async function handleCowtailEvent(params) {
14498
14517
  if (!thread.sessionKey) {
14499
14518
  await client.sendSessionBound({
14500
14519
  type: "openclaw_session_bound",
14520
+ idempotencyKey: `cowtail:session-bound:${thread.id}`,
14501
14521
  threadId: thread.id,
14502
14522
  sessionKey: route.sessionKey
14503
14523
  });
@@ -14566,6 +14586,7 @@ async function handleCowtailEvent(params) {
14566
14586
  });
14567
14587
  await client.sendActionResult({
14568
14588
  type: "openclaw_action_result",
14589
+ idempotencyKey: `cowtail:action-result:${action.id}:${dispatchSucceeded ? "submitted" : "failed"}`,
14569
14590
  actionId: action.id,
14570
14591
  state: dispatchSucceeded ? "submitted" : "failed"
14571
14592
  });
@@ -14573,6 +14594,7 @@ async function handleCowtailEvent(params) {
14573
14594
  logger?.error?.(`Cowtail action dispatch failed: ${errorMessage(error48)}`);
14574
14595
  await client.sendActionResult({
14575
14596
  type: "openclaw_action_result",
14597
+ idempotencyKey: `cowtail:action-result:${action.id}:failed`,
14576
14598
  actionId: action.id,
14577
14599
  state: "failed"
14578
14600
  });
@@ -14602,7 +14624,13 @@ async function dispatchCowtailTextTurn(params) {
14602
14624
  body,
14603
14625
  timestamp: message.createdAt
14604
14626
  });
14605
- const streamState = { text: "", links: [], toolCalls: [] };
14627
+ const streamState = {
14628
+ idempotencyKey: `cowtail:reply:${message.id}`,
14629
+ updateIndex: 0,
14630
+ text: "",
14631
+ links: [],
14632
+ toolCalls: []
14633
+ };
14606
14634
  await recordCowtailInboundSessionAndDispatchReply({
14607
14635
  cfg: runtime.config.loadConfig(),
14608
14636
  channel: CHANNEL_ID,
@@ -14658,7 +14686,13 @@ async function dispatchCowtailActionTurn(params) {
14658
14686
  timestamp
14659
14687
  });
14660
14688
  let dispatchFailed = false;
14661
- const streamState = { text: "", links: [], toolCalls: [] };
14689
+ const streamState = {
14690
+ idempotencyKey: `cowtail:action:${action.id}`,
14691
+ updateIndex: 0,
14692
+ text: "",
14693
+ links: [],
14694
+ toolCalls: []
14695
+ };
14662
14696
  await recordCowtailInboundSessionAndDispatchReply({
14663
14697
  cfg: runtime.config.loadConfig(),
14664
14698
  channel: CHANNEL_ID,
@@ -14745,6 +14779,7 @@ async function deliverCowtailReply(params) {
14745
14779
  if (!params.streamState.messageId) {
14746
14780
  const result = await params.client.sendOpenClawMessage({
14747
14781
  type: "openclaw_message",
14782
+ idempotencyKey: params.streamState.idempotencyKey,
14748
14783
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14749
14784
  title: params.thread.title,
14750
14785
  text: messageText,
@@ -14763,6 +14798,7 @@ async function deliverCowtailReply(params) {
14763
14798
  }
14764
14799
  await params.client.sendOpenClawMessageUpdate({
14765
14800
  type: "openclaw_message_update",
14801
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14766
14802
  messageId: params.streamState.messageId,
14767
14803
  text: messageText,
14768
14804
  links,
@@ -14779,6 +14815,7 @@ async function deliverCowtailReply(params) {
14779
14815
  if (!params.streamState.messageId) {
14780
14816
  const result = await params.client.sendOpenClawMessage({
14781
14817
  type: "openclaw_message",
14818
+ idempotencyKey: params.streamState.idempotencyKey,
14782
14819
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14783
14820
  title: params.thread.title,
14784
14821
  text: params.streamState.text,
@@ -14797,6 +14834,7 @@ async function deliverCowtailReply(params) {
14797
14834
  }
14798
14835
  await params.client.sendOpenClawMessageUpdate({
14799
14836
  type: "openclaw_message_update",
14837
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14800
14838
  messageId: params.streamState.messageId,
14801
14839
  text: params.streamState.text,
14802
14840
  links,
@@ -14809,6 +14847,7 @@ async function deliverCowtailReply(params) {
14809
14847
  params.streamState.text = text;
14810
14848
  await params.client.sendOpenClawMessageUpdate({
14811
14849
  type: "openclaw_message_update",
14850
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14812
14851
  messageId: params.streamState.messageId,
14813
14852
  text,
14814
14853
  links,
@@ -14821,6 +14860,7 @@ async function deliverCowtailReply(params) {
14821
14860
  }
14822
14861
  await params.client.sendOpenClawMessage({
14823
14862
  type: "openclaw_message",
14863
+ idempotencyKey: params.streamState.idempotencyKey,
14824
14864
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14825
14865
  title: params.thread.title,
14826
14866
  text,
@@ -14838,6 +14878,7 @@ async function finalizeCowtailStreamedReply(params) {
14838
14878
  }
14839
14879
  await params.client.sendOpenClawMessageUpdate({
14840
14880
  type: "openclaw_message_update",
14881
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14841
14882
  messageId: params.streamState.messageId,
14842
14883
  text: params.streamState.text,
14843
14884
  links: params.streamState.links,
@@ -14849,6 +14890,12 @@ async function finalizeCowtailStreamedReply(params) {
14849
14890
  }
14850
14891
  function recordCreatedOpenClawMessageId(streamState, result) {
14851
14892
  if (result.payload?.dropped === true) {
14893
+ if (result.payload.duplicate === true) {
14894
+ const duplicateMessageId = result.payload.messageId;
14895
+ if (typeof duplicateMessageId === "string" && duplicateMessageId.trim()) {
14896
+ return duplicateMessageId;
14897
+ }
14898
+ }
14852
14899
  streamState.failed = true;
14853
14900
  streamState.completed = true;
14854
14901
  return;
@@ -14860,6 +14907,10 @@ function recordCreatedOpenClawMessageId(streamState, result) {
14860
14907
  streamState.failed = true;
14861
14908
  throw new Error("Cowtail realtime ack missing messageId for streamed OpenClaw reply");
14862
14909
  }
14910
+ function nextStreamUpdateIdempotencyKey(streamState) {
14911
+ streamState.updateIndex += 1;
14912
+ return `${streamState.idempotencyKey}:update:${streamState.updateIndex}`;
14913
+ }
14863
14914
  function resolveCowtailReplyLinks(payload) {
14864
14915
  const urls = [
14865
14916
  ...Array.isArray(payload.mediaUrls) ? payload.mediaUrls : [],
@@ -13844,6 +13844,7 @@ var openclawActionSubmittedEventSchema = openclawEventEnvelopeSchema.extend({
13844
13844
  payload: jsonObjectSchema
13845
13845
  });
13846
13846
  var openclawRequestIdSchema = nonEmptyStringSchema;
13847
+ var openclawIdempotencyKeySchema = nonEmptyStringSchema;
13847
13848
  var openclawActionDraftSchema = exports_external.object({
13848
13849
  label: nonEmptyStringSchema,
13849
13850
  kind: nonEmptyStringSchema,
@@ -13852,6 +13853,7 @@ var openclawActionDraftSchema = exports_external.object({
13852
13853
  var openclawPluginMessageCommandSchema = requireOpenClawRenderableContent(exports_external.object({
13853
13854
  type: exports_external.literal("openclaw_message"),
13854
13855
  requestId: openclawRequestIdSchema,
13856
+ idempotencyKey: openclawIdempotencyKeySchema,
13855
13857
  sessionKey: nonEmptyStringSchema,
13856
13858
  title: nonEmptyStringSchema.optional(),
13857
13859
  text: openclawMessageTextSchema,
@@ -13864,6 +13866,7 @@ var openclawPluginMessageCommandSchema = requireOpenClawRenderableContent(export
13864
13866
  var openclawPluginMessageUpdateCommandSchema = requireOpenClawRenderableContent(exports_external.object({
13865
13867
  type: exports_external.literal("openclaw_message_update"),
13866
13868
  requestId: openclawRequestIdSchema,
13869
+ idempotencyKey: openclawIdempotencyKeySchema,
13867
13870
  messageId: nonEmptyStringSchema,
13868
13871
  text: openclawMessageTextSchema,
13869
13872
  links: exports_external.array(openclawLinkSchema).optional(),
@@ -13874,46 +13877,54 @@ var openclawPluginMessageUpdateCommandSchema = requireOpenClawRenderableContent(
13874
13877
  var openclawIosNewThreadCommandSchema = exports_external.object({
13875
13878
  type: exports_external.literal("ios_new_thread"),
13876
13879
  requestId: openclawRequestIdSchema,
13880
+ idempotencyKey: openclawIdempotencyKeySchema,
13877
13881
  title: nonEmptyStringSchema.optional(),
13878
13882
  text: nonEmptyStringSchema
13879
13883
  });
13880
13884
  var openclawIosReplyCommandSchema = exports_external.object({
13881
13885
  type: exports_external.literal("ios_reply"),
13882
13886
  requestId: openclawRequestIdSchema,
13887
+ idempotencyKey: openclawIdempotencyKeySchema,
13883
13888
  threadId: nonEmptyStringSchema,
13884
13889
  text: nonEmptyStringSchema
13885
13890
  });
13886
13891
  var openclawIosActionCommandSchema = exports_external.object({
13887
13892
  type: exports_external.literal("ios_action"),
13888
13893
  requestId: openclawRequestIdSchema,
13894
+ idempotencyKey: openclawIdempotencyKeySchema,
13889
13895
  actionId: nonEmptyStringSchema,
13890
13896
  payload: jsonObjectSchema
13891
13897
  });
13892
13898
  var openclawIosMarkThreadReadCommandSchema = exports_external.object({
13893
13899
  type: exports_external.literal("ios_mark_thread_read"),
13894
13900
  requestId: openclawRequestIdSchema,
13901
+ idempotencyKey: openclawIdempotencyKeySchema,
13895
13902
  threadId: nonEmptyStringSchema
13896
13903
  });
13897
13904
  var openclawIosRenameThreadCommandSchema = exports_external.object({
13898
13905
  type: exports_external.literal("ios_rename_thread"),
13899
13906
  requestId: openclawRequestIdSchema,
13907
+ idempotencyKey: openclawIdempotencyKeySchema,
13900
13908
  threadId: nonEmptyStringSchema,
13901
13909
  title: nonEmptyStringSchema
13902
13910
  });
13903
13911
  var openclawIosDeleteThreadCommandSchema = exports_external.object({
13904
13912
  type: exports_external.literal("ios_delete_thread"),
13905
13913
  requestId: openclawRequestIdSchema,
13914
+ idempotencyKey: openclawIdempotencyKeySchema,
13906
13915
  threadId: nonEmptyStringSchema
13907
13916
  });
13908
13917
  var openclawSessionBoundCommandSchema = exports_external.object({
13909
13918
  type: exports_external.literal("openclaw_session_bound"),
13910
13919
  requestId: openclawRequestIdSchema,
13920
+ idempotencyKey: openclawIdempotencyKeySchema,
13911
13921
  threadId: nonEmptyStringSchema,
13912
13922
  sessionKey: nonEmptyStringSchema
13913
13923
  });
13914
13924
  var openclawActionResultCommandSchema = exports_external.object({
13915
13925
  type: exports_external.literal("openclaw_action_result"),
13916
13926
  requestId: openclawRequestIdSchema,
13927
+ idempotencyKey: openclawIdempotencyKeySchema,
13917
13928
  actionId: nonEmptyStringSchema,
13918
13929
  state: exports_external.enum(["submitted", "failed", "expired"]),
13919
13930
  resultMetadata: jsonObjectSchema.optional()
@@ -14164,33 +14175,41 @@ class CowtailRealtimeClient {
14164
14175
  this.#closeSocket();
14165
14176
  }
14166
14177
  sendOpenClawMessage(command) {
14178
+ const requestId = this.#requestIdFactory();
14167
14179
  return this.#sendCommand({
14168
14180
  ...command,
14169
- requestId: this.#requestIdFactory(),
14181
+ requestId,
14182
+ idempotencyKey: command.idempotencyKey ?? `cowtail:request:${requestId}`,
14170
14183
  links: command.links ?? [],
14171
14184
  toolCalls: command.toolCalls ?? [],
14172
14185
  actions: command.actions ?? []
14173
14186
  });
14174
14187
  }
14175
14188
  sendOpenClawMessageUpdate(command) {
14189
+ const requestId = this.#requestIdFactory();
14176
14190
  return this.#sendCommand({
14177
14191
  ...command,
14178
- requestId: this.#requestIdFactory(),
14192
+ requestId,
14193
+ idempotencyKey: command.idempotencyKey ?? `cowtail:request:${requestId}`,
14179
14194
  ...command.links ? { links: command.links } : {},
14180
14195
  ...command.toolCalls ? { toolCalls: command.toolCalls } : {},
14181
14196
  ...command.actions ? { actions: command.actions } : {}
14182
14197
  });
14183
14198
  }
14184
14199
  sendSessionBound(command) {
14200
+ const requestId = this.#requestIdFactory();
14185
14201
  return this.#sendCommand({
14186
14202
  ...command,
14187
- requestId: this.#requestIdFactory()
14203
+ requestId,
14204
+ idempotencyKey: command.idempotencyKey ?? `cowtail:request:${requestId}`
14188
14205
  }).then((result) => result.sequence);
14189
14206
  }
14190
14207
  sendActionResult(command) {
14208
+ const requestId = this.#requestIdFactory();
14191
14209
  return this.#sendCommand({
14192
14210
  ...command,
14193
- requestId: this.#requestIdFactory()
14211
+ requestId,
14212
+ idempotencyKey: command.idempotencyKey ?? `cowtail:request:${requestId}`
14194
14213
  }).then((result) => result.sequence);
14195
14214
  }
14196
14215
  #connect() {
@@ -14498,6 +14517,7 @@ async function handleCowtailEvent(params) {
14498
14517
  if (!thread.sessionKey) {
14499
14518
  await client.sendSessionBound({
14500
14519
  type: "openclaw_session_bound",
14520
+ idempotencyKey: `cowtail:session-bound:${thread.id}`,
14501
14521
  threadId: thread.id,
14502
14522
  sessionKey: route.sessionKey
14503
14523
  });
@@ -14566,6 +14586,7 @@ async function handleCowtailEvent(params) {
14566
14586
  });
14567
14587
  await client.sendActionResult({
14568
14588
  type: "openclaw_action_result",
14589
+ idempotencyKey: `cowtail:action-result:${action.id}:${dispatchSucceeded ? "submitted" : "failed"}`,
14569
14590
  actionId: action.id,
14570
14591
  state: dispatchSucceeded ? "submitted" : "failed"
14571
14592
  });
@@ -14573,6 +14594,7 @@ async function handleCowtailEvent(params) {
14573
14594
  logger?.error?.(`Cowtail action dispatch failed: ${errorMessage(error48)}`);
14574
14595
  await client.sendActionResult({
14575
14596
  type: "openclaw_action_result",
14597
+ idempotencyKey: `cowtail:action-result:${action.id}:failed`,
14576
14598
  actionId: action.id,
14577
14599
  state: "failed"
14578
14600
  });
@@ -14602,7 +14624,13 @@ async function dispatchCowtailTextTurn(params) {
14602
14624
  body,
14603
14625
  timestamp: message.createdAt
14604
14626
  });
14605
- const streamState = { text: "", links: [], toolCalls: [] };
14627
+ const streamState = {
14628
+ idempotencyKey: `cowtail:reply:${message.id}`,
14629
+ updateIndex: 0,
14630
+ text: "",
14631
+ links: [],
14632
+ toolCalls: []
14633
+ };
14606
14634
  await recordCowtailInboundSessionAndDispatchReply({
14607
14635
  cfg: runtime.config.loadConfig(),
14608
14636
  channel: CHANNEL_ID,
@@ -14658,7 +14686,13 @@ async function dispatchCowtailActionTurn(params) {
14658
14686
  timestamp
14659
14687
  });
14660
14688
  let dispatchFailed = false;
14661
- const streamState = { text: "", links: [], toolCalls: [] };
14689
+ const streamState = {
14690
+ idempotencyKey: `cowtail:action:${action.id}`,
14691
+ updateIndex: 0,
14692
+ text: "",
14693
+ links: [],
14694
+ toolCalls: []
14695
+ };
14662
14696
  await recordCowtailInboundSessionAndDispatchReply({
14663
14697
  cfg: runtime.config.loadConfig(),
14664
14698
  channel: CHANNEL_ID,
@@ -14745,6 +14779,7 @@ async function deliverCowtailReply(params) {
14745
14779
  if (!params.streamState.messageId) {
14746
14780
  const result = await params.client.sendOpenClawMessage({
14747
14781
  type: "openclaw_message",
14782
+ idempotencyKey: params.streamState.idempotencyKey,
14748
14783
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14749
14784
  title: params.thread.title,
14750
14785
  text: messageText,
@@ -14763,6 +14798,7 @@ async function deliverCowtailReply(params) {
14763
14798
  }
14764
14799
  await params.client.sendOpenClawMessageUpdate({
14765
14800
  type: "openclaw_message_update",
14801
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14766
14802
  messageId: params.streamState.messageId,
14767
14803
  text: messageText,
14768
14804
  links,
@@ -14779,6 +14815,7 @@ async function deliverCowtailReply(params) {
14779
14815
  if (!params.streamState.messageId) {
14780
14816
  const result = await params.client.sendOpenClawMessage({
14781
14817
  type: "openclaw_message",
14818
+ idempotencyKey: params.streamState.idempotencyKey,
14782
14819
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14783
14820
  title: params.thread.title,
14784
14821
  text: params.streamState.text,
@@ -14797,6 +14834,7 @@ async function deliverCowtailReply(params) {
14797
14834
  }
14798
14835
  await params.client.sendOpenClawMessageUpdate({
14799
14836
  type: "openclaw_message_update",
14837
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14800
14838
  messageId: params.streamState.messageId,
14801
14839
  text: params.streamState.text,
14802
14840
  links,
@@ -14809,6 +14847,7 @@ async function deliverCowtailReply(params) {
14809
14847
  params.streamState.text = text;
14810
14848
  await params.client.sendOpenClawMessageUpdate({
14811
14849
  type: "openclaw_message_update",
14850
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14812
14851
  messageId: params.streamState.messageId,
14813
14852
  text,
14814
14853
  links,
@@ -14821,6 +14860,7 @@ async function deliverCowtailReply(params) {
14821
14860
  }
14822
14861
  await params.client.sendOpenClawMessage({
14823
14862
  type: "openclaw_message",
14863
+ idempotencyKey: params.streamState.idempotencyKey,
14824
14864
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14825
14865
  title: params.thread.title,
14826
14866
  text,
@@ -14838,6 +14878,7 @@ async function finalizeCowtailStreamedReply(params) {
14838
14878
  }
14839
14879
  await params.client.sendOpenClawMessageUpdate({
14840
14880
  type: "openclaw_message_update",
14881
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14841
14882
  messageId: params.streamState.messageId,
14842
14883
  text: params.streamState.text,
14843
14884
  links: params.streamState.links,
@@ -14849,6 +14890,12 @@ async function finalizeCowtailStreamedReply(params) {
14849
14890
  }
14850
14891
  function recordCreatedOpenClawMessageId(streamState, result) {
14851
14892
  if (result.payload?.dropped === true) {
14893
+ if (result.payload.duplicate === true) {
14894
+ const duplicateMessageId = result.payload.messageId;
14895
+ if (typeof duplicateMessageId === "string" && duplicateMessageId.trim()) {
14896
+ return duplicateMessageId;
14897
+ }
14898
+ }
14852
14899
  streamState.failed = true;
14853
14900
  streamState.completed = true;
14854
14901
  return;
@@ -14860,6 +14907,10 @@ function recordCreatedOpenClawMessageId(streamState, result) {
14860
14907
  streamState.failed = true;
14861
14908
  throw new Error("Cowtail realtime ack missing messageId for streamed OpenClaw reply");
14862
14909
  }
14910
+ function nextStreamUpdateIdempotencyKey(streamState) {
14911
+ streamState.updateIndex += 1;
14912
+ return `${streamState.idempotencyKey}:update:${streamState.updateIndex}`;
14913
+ }
14863
14914
  function resolveCowtailReplyLinks(payload) {
14864
14915
  const urls = [
14865
14916
  ...Array.isArray(payload.mediaUrls) ? payload.mediaUrls : [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maudecode/openclaw-cowtail",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "description": "Cowtail channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "repository": {