@maudecode/openclaw-cowtail 0.12.0 → 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,
@@ -14754,11 +14789,16 @@ async function deliverCowtailReply(params) {
14754
14789
  actions: [],
14755
14790
  deliveryState: "pending"
14756
14791
  });
14757
- params.streamState.messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14792
+ const messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14793
+ if (!messageId) {
14794
+ return;
14795
+ }
14796
+ params.streamState.messageId = messageId;
14758
14797
  return;
14759
14798
  }
14760
14799
  await params.client.sendOpenClawMessageUpdate({
14761
14800
  type: "openclaw_message_update",
14801
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14762
14802
  messageId: params.streamState.messageId,
14763
14803
  text: messageText,
14764
14804
  links,
@@ -14775,6 +14815,7 @@ async function deliverCowtailReply(params) {
14775
14815
  if (!params.streamState.messageId) {
14776
14816
  const result = await params.client.sendOpenClawMessage({
14777
14817
  type: "openclaw_message",
14818
+ idempotencyKey: params.streamState.idempotencyKey,
14778
14819
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14779
14820
  title: params.thread.title,
14780
14821
  text: params.streamState.text,
@@ -14784,11 +14825,16 @@ async function deliverCowtailReply(params) {
14784
14825
  actions: [],
14785
14826
  deliveryState: "pending"
14786
14827
  });
14787
- params.streamState.messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14828
+ const messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14829
+ if (!messageId) {
14830
+ return;
14831
+ }
14832
+ params.streamState.messageId = messageId;
14788
14833
  return;
14789
14834
  }
14790
14835
  await params.client.sendOpenClawMessageUpdate({
14791
14836
  type: "openclaw_message_update",
14837
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14792
14838
  messageId: params.streamState.messageId,
14793
14839
  text: params.streamState.text,
14794
14840
  links,
@@ -14801,6 +14847,7 @@ async function deliverCowtailReply(params) {
14801
14847
  params.streamState.text = text;
14802
14848
  await params.client.sendOpenClawMessageUpdate({
14803
14849
  type: "openclaw_message_update",
14850
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14804
14851
  messageId: params.streamState.messageId,
14805
14852
  text,
14806
14853
  links,
@@ -14813,6 +14860,7 @@ async function deliverCowtailReply(params) {
14813
14860
  }
14814
14861
  await params.client.sendOpenClawMessage({
14815
14862
  type: "openclaw_message",
14863
+ idempotencyKey: params.streamState.idempotencyKey,
14816
14864
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14817
14865
  title: params.thread.title,
14818
14866
  text,
@@ -14830,6 +14878,7 @@ async function finalizeCowtailStreamedReply(params) {
14830
14878
  }
14831
14879
  await params.client.sendOpenClawMessageUpdate({
14832
14880
  type: "openclaw_message_update",
14881
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14833
14882
  messageId: params.streamState.messageId,
14834
14883
  text: params.streamState.text,
14835
14884
  links: params.streamState.links,
@@ -14840,6 +14889,17 @@ async function finalizeCowtailStreamedReply(params) {
14840
14889
  params.streamState.completed = true;
14841
14890
  }
14842
14891
  function recordCreatedOpenClawMessageId(streamState, result) {
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
+ }
14899
+ streamState.failed = true;
14900
+ streamState.completed = true;
14901
+ return;
14902
+ }
14843
14903
  const messageId = result.payload?.messageId;
14844
14904
  if (typeof messageId === "string" && messageId.trim()) {
14845
14905
  return messageId;
@@ -14847,6 +14907,10 @@ function recordCreatedOpenClawMessageId(streamState, result) {
14847
14907
  streamState.failed = true;
14848
14908
  throw new Error("Cowtail realtime ack missing messageId for streamed OpenClaw reply");
14849
14909
  }
14910
+ function nextStreamUpdateIdempotencyKey(streamState) {
14911
+ streamState.updateIndex += 1;
14912
+ return `${streamState.idempotencyKey}:update:${streamState.updateIndex}`;
14913
+ }
14850
14914
  function resolveCowtailReplyLinks(payload) {
14851
14915
  const urls = [
14852
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,
@@ -14754,11 +14789,16 @@ async function deliverCowtailReply(params) {
14754
14789
  actions: [],
14755
14790
  deliveryState: "pending"
14756
14791
  });
14757
- params.streamState.messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14792
+ const messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14793
+ if (!messageId) {
14794
+ return;
14795
+ }
14796
+ params.streamState.messageId = messageId;
14758
14797
  return;
14759
14798
  }
14760
14799
  await params.client.sendOpenClawMessageUpdate({
14761
14800
  type: "openclaw_message_update",
14801
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14762
14802
  messageId: params.streamState.messageId,
14763
14803
  text: messageText,
14764
14804
  links,
@@ -14775,6 +14815,7 @@ async function deliverCowtailReply(params) {
14775
14815
  if (!params.streamState.messageId) {
14776
14816
  const result = await params.client.sendOpenClawMessage({
14777
14817
  type: "openclaw_message",
14818
+ idempotencyKey: params.streamState.idempotencyKey,
14778
14819
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14779
14820
  title: params.thread.title,
14780
14821
  text: params.streamState.text,
@@ -14784,11 +14825,16 @@ async function deliverCowtailReply(params) {
14784
14825
  actions: [],
14785
14826
  deliveryState: "pending"
14786
14827
  });
14787
- params.streamState.messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14828
+ const messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14829
+ if (!messageId) {
14830
+ return;
14831
+ }
14832
+ params.streamState.messageId = messageId;
14788
14833
  return;
14789
14834
  }
14790
14835
  await params.client.sendOpenClawMessageUpdate({
14791
14836
  type: "openclaw_message_update",
14837
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14792
14838
  messageId: params.streamState.messageId,
14793
14839
  text: params.streamState.text,
14794
14840
  links,
@@ -14801,6 +14847,7 @@ async function deliverCowtailReply(params) {
14801
14847
  params.streamState.text = text;
14802
14848
  await params.client.sendOpenClawMessageUpdate({
14803
14849
  type: "openclaw_message_update",
14850
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14804
14851
  messageId: params.streamState.messageId,
14805
14852
  text,
14806
14853
  links,
@@ -14813,6 +14860,7 @@ async function deliverCowtailReply(params) {
14813
14860
  }
14814
14861
  await params.client.sendOpenClawMessage({
14815
14862
  type: "openclaw_message",
14863
+ idempotencyKey: params.streamState.idempotencyKey,
14816
14864
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14817
14865
  title: params.thread.title,
14818
14866
  text,
@@ -14830,6 +14878,7 @@ async function finalizeCowtailStreamedReply(params) {
14830
14878
  }
14831
14879
  await params.client.sendOpenClawMessageUpdate({
14832
14880
  type: "openclaw_message_update",
14881
+ idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14833
14882
  messageId: params.streamState.messageId,
14834
14883
  text: params.streamState.text,
14835
14884
  links: params.streamState.links,
@@ -14840,6 +14889,17 @@ async function finalizeCowtailStreamedReply(params) {
14840
14889
  params.streamState.completed = true;
14841
14890
  }
14842
14891
  function recordCreatedOpenClawMessageId(streamState, result) {
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
+ }
14899
+ streamState.failed = true;
14900
+ streamState.completed = true;
14901
+ return;
14902
+ }
14843
14903
  const messageId = result.payload?.messageId;
14844
14904
  if (typeof messageId === "string" && messageId.trim()) {
14845
14905
  return messageId;
@@ -14847,6 +14907,10 @@ function recordCreatedOpenClawMessageId(streamState, result) {
14847
14907
  streamState.failed = true;
14848
14908
  throw new Error("Cowtail realtime ack missing messageId for streamed OpenClaw reply");
14849
14909
  }
14910
+ function nextStreamUpdateIdempotencyKey(streamState) {
14911
+ streamState.updateIndex += 1;
14912
+ return `${streamState.idempotencyKey}:update:${streamState.updateIndex}`;
14913
+ }
14850
14914
  function resolveCowtailReplyLinks(payload) {
14851
14915
  const urls = [
14852
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.0",
3
+ "version": "0.12.3",
4
4
  "description": "Cowtail channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "repository": {