@maudecode/openclaw-cowtail 0.12.3 → 0.14.0

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.
@@ -13801,6 +13801,7 @@ var openclawThreadRecordSchema = exports_external.object({
13801
13801
  var openclawMessageRecordBaseSchema = exports_external.object({
13802
13802
  id: nonEmptyStringSchema,
13803
13803
  threadId: nonEmptyStringSchema,
13804
+ streamId: nonEmptyStringSchema.optional(),
13804
13805
  direction: openclawMessageDirectionSchema,
13805
13806
  authorLabel: nonEmptyStringSchema.optional(),
13806
13807
  text: openclawMessageTextSchema,
@@ -13855,13 +13856,16 @@ var openclawPluginMessageCommandSchema = requireOpenClawRenderableContent(export
13855
13856
  requestId: openclawRequestIdSchema,
13856
13857
  idempotencyKey: openclawIdempotencyKeySchema,
13857
13858
  sessionKey: nonEmptyStringSchema,
13859
+ threadId: nonEmptyStringSchema.optional(),
13860
+ threadHint: nonEmptyStringSchema.optional(),
13858
13861
  title: nonEmptyStringSchema.optional(),
13859
13862
  text: openclawMessageTextSchema,
13860
13863
  authorLabel: nonEmptyStringSchema.optional(),
13861
13864
  links: exports_external.array(openclawLinkSchema).default([]),
13862
13865
  toolCalls: exports_external.array(openclawToolCallRecordSchema).default([]),
13863
13866
  actions: exports_external.array(openclawActionDraftSchema).default([]),
13864
- deliveryState: openclawDeliveryStateSchema.optional()
13867
+ deliveryState: openclawDeliveryStateSchema.optional(),
13868
+ streamId: nonEmptyStringSchema
13865
13869
  }));
13866
13870
  var openclawPluginMessageUpdateCommandSchema = requireOpenClawRenderableContent(exports_external.object({
13867
13871
  type: exports_external.literal("openclaw_message_update"),
@@ -13872,8 +13876,24 @@ var openclawPluginMessageUpdateCommandSchema = requireOpenClawRenderableContent(
13872
13876
  links: exports_external.array(openclawLinkSchema).optional(),
13873
13877
  toolCalls: exports_external.array(openclawToolCallRecordSchema).optional(),
13874
13878
  actions: exports_external.array(openclawActionDraftSchema).optional(),
13875
- deliveryState: openclawDeliveryStateSchema.optional()
13879
+ deliveryState: openclawDeliveryStateSchema.optional(),
13880
+ streamId: nonEmptyStringSchema
13876
13881
  }));
13882
+ var openclawMessageStreamSnapshotCommandBaseSchema = exports_external.object({
13883
+ type: exports_external.literal("openclaw_message_stream_snapshot"),
13884
+ requestId: openclawRequestIdSchema,
13885
+ streamId: nonEmptyStringSchema,
13886
+ sessionKey: nonEmptyStringSchema,
13887
+ threadId: nonEmptyStringSchema,
13888
+ text: openclawMessageTextSchema,
13889
+ links: exports_external.array(openclawLinkSchema).default([]),
13890
+ toolCalls: exports_external.array(openclawToolCallRecordSchema).default([]),
13891
+ isFinal: exports_external.boolean(),
13892
+ snapshotSequence: exports_external.number().int().nonnegative(),
13893
+ updatedAt: timestampSchema
13894
+ });
13895
+ var openclawMessageStreamSnapshotCommandSchema = requireOpenClawRenderableContent(openclawMessageStreamSnapshotCommandBaseSchema);
13896
+ var openclawMessageStreamSnapshotServerMessageSchema = requireOpenClawRenderableContent(openclawMessageStreamSnapshotCommandBaseSchema.omit({ requestId: true }));
13877
13897
  var openclawIosNewThreadCommandSchema = exports_external.object({
13878
13898
  type: exports_external.literal("ios_new_thread"),
13879
13899
  requestId: openclawRequestIdSchema,
@@ -13932,6 +13952,7 @@ var openclawActionResultCommandSchema = exports_external.object({
13932
13952
  var openclawRealtimeClientMessageSchema = exports_external.union([
13933
13953
  openclawPluginMessageCommandSchema,
13934
13954
  openclawPluginMessageUpdateCommandSchema,
13955
+ openclawMessageStreamSnapshotCommandSchema,
13935
13956
  openclawIosNewThreadCommandSchema,
13936
13957
  openclawIosReplyCommandSchema,
13937
13958
  openclawIosActionCommandSchema,
@@ -13941,11 +13962,18 @@ var openclawRealtimeClientMessageSchema = exports_external.union([
13941
13962
  openclawSessionBoundCommandSchema,
13942
13963
  openclawActionResultCommandSchema
13943
13964
  ]);
13965
+ var openclawRealtimeAckPayloadSchema = exports_external.object({
13966
+ threadId: nonEmptyStringSchema.optional(),
13967
+ messageId: nonEmptyStringSchema.optional(),
13968
+ dropped: exports_external.literal(true).optional(),
13969
+ duplicate: exports_external.literal(true).optional(),
13970
+ reason: nonEmptyStringSchema.optional()
13971
+ });
13944
13972
  var openclawRealtimeAckSchema = exports_external.object({
13945
13973
  type: exports_external.literal("ack"),
13946
13974
  requestId: openclawRequestIdSchema,
13947
13975
  sequence: openclawSequenceSchema.optional(),
13948
- payload: jsonObjectSchema.optional()
13976
+ payload: openclawRealtimeAckPayloadSchema.optional()
13949
13977
  });
13950
13978
  var openclawRealtimeErrorSchema = exports_external.object({
13951
13979
  type: exports_external.literal("realtime_error"),
@@ -13954,6 +13982,7 @@ var openclawRealtimeErrorSchema = exports_external.object({
13954
13982
  });
13955
13983
  var openclawRealtimeServerMessageSchema = exports_external.union([
13956
13984
  openclawEventEnvelopeSchema,
13985
+ openclawMessageStreamSnapshotServerMessageSchema,
13957
13986
  openclawRealtimeAckSchema,
13958
13987
  openclawRealtimeErrorSchema
13959
13988
  ]);
@@ -13993,6 +14022,13 @@ var openclawEventReplayResponseSchema = exports_external.object({
13993
14022
  ok: exports_external.literal(true),
13994
14023
  events: exports_external.array(openclawEventEnvelopeSchema)
13995
14024
  });
14025
+ var openclawPushNotificationPayloadSchema = exports_external.object({
14026
+ kind: exports_external.literal("openclaw"),
14027
+ version: exports_external.literal(1),
14028
+ threadId: nonEmptyStringSchema,
14029
+ messageId: nonEmptyStringSchema,
14030
+ url: nonEmptyStringSchema.optional()
14031
+ });
13996
14032
  // ../protocol/src/fixes.ts
13997
14033
  var fixScopeSchema = exports_external.enum(fixScopes);
13998
14034
  var fixCreateRequestSchema = exports_external.object({
@@ -14048,11 +14084,12 @@ var healthResponseSchema = exports_external.object({
14048
14084
  storageUnit: nonEmptyStringSchema
14049
14085
  });
14050
14086
  // ../protocol/src/push.ts
14087
+ var pushEnvironmentSchema = exports_external.enum(["development", "production"]);
14051
14088
  var pushRegisterRequestSchema = exports_external.object({
14052
14089
  identityToken: nonEmptyStringSchema,
14053
14090
  deviceToken: nonEmptyStringSchema,
14054
14091
  platform: nonEmptyStringSchema.optional(),
14055
- environment: nonEmptyStringSchema.optional(),
14092
+ environment: pushEnvironmentSchema.optional(),
14056
14093
  deviceName: nonEmptyStringSchema.optional()
14057
14094
  });
14058
14095
  var pushRegisterResponseSchema = exports_external.object({
@@ -14061,6 +14098,7 @@ var pushRegisterResponseSchema = exports_external.object({
14061
14098
  id: nonEmptyStringSchema
14062
14099
  });
14063
14100
  var pushUnregisterRequestSchema = exports_external.object({
14101
+ identityToken: nonEmptyStringSchema,
14064
14102
  deviceToken: nonEmptyStringSchema
14065
14103
  });
14066
14104
  var pushUnregisterResponseSchema = exports_external.object({
@@ -14084,6 +14122,7 @@ var pushResultSchema = exports_external.object({
14084
14122
  userId: nonEmptyStringSchema,
14085
14123
  sent: exports_external.number().int().nonnegative(),
14086
14124
  failed: exports_external.number().int().nonnegative(),
14125
+ skipped: exports_external.number().int().nonnegative().default(0),
14087
14126
  results: exports_external.array(jsonObjectSchema)
14088
14127
  });
14089
14128
  // ../protocol/src/responses.ts
@@ -14104,7 +14143,8 @@ var usersListEntrySchema = exports_external.object({
14104
14143
  enabledDeviceCount: exports_external.number().int().nonnegative()
14105
14144
  });
14106
14145
  var userDeviceSchema = exports_external.object({
14107
- deviceToken: nonEmptyStringSchema,
14146
+ id: nonEmptyStringSchema,
14147
+ deviceTokenPreview: nonEmptyStringSchema,
14108
14148
  platform: nonEmptyStringSchema,
14109
14149
  environment: nonEmptyStringSchema,
14110
14150
  enabled: exports_external.boolean(),
@@ -14176,10 +14216,12 @@ class CowtailRealtimeClient {
14176
14216
  }
14177
14217
  sendOpenClawMessage(command) {
14178
14218
  const requestId = this.#requestIdFactory();
14219
+ const idempotencyKey = command.idempotencyKey ?? `cowtail:request:${requestId}`;
14179
14220
  return this.#sendCommand({
14180
14221
  ...command,
14181
14222
  requestId,
14182
- idempotencyKey: command.idempotencyKey ?? `cowtail:request:${requestId}`,
14223
+ idempotencyKey,
14224
+ streamId: command.streamId ?? idempotencyKey,
14183
14225
  links: command.links ?? [],
14184
14226
  toolCalls: command.toolCalls ?? [],
14185
14227
  actions: command.actions ?? []
@@ -14212,6 +14254,12 @@ class CowtailRealtimeClient {
14212
14254
  idempotencyKey: command.idempotencyKey ?? `cowtail:request:${requestId}`
14213
14255
  }).then((result) => result.sequence);
14214
14256
  }
14257
+ sendOpenClawStreamSnapshot(command) {
14258
+ return this.#sendTransientCommand({
14259
+ ...command,
14260
+ requestId: this.#requestIdFactory()
14261
+ });
14262
+ }
14215
14263
  #connect() {
14216
14264
  if (!this.#started || this.#socket) {
14217
14265
  return;
@@ -14311,6 +14359,9 @@ class CowtailRealtimeClient {
14311
14359
  this.#rejectPendingRequest(message);
14312
14360
  return;
14313
14361
  }
14362
+ if (message.type === "openclaw_message_stream_snapshot") {
14363
+ return;
14364
+ }
14314
14365
  await this.#onEvent(message);
14315
14366
  await this.#stateStore.writeLastSeenSequence(message.sequence);
14316
14367
  }
@@ -14387,6 +14438,19 @@ class CowtailRealtimeClient {
14387
14438
  }
14388
14439
  });
14389
14440
  }
14441
+ async#sendTransientCommand(command) {
14442
+ const socket = this.#socket;
14443
+ const handshake = this.#handshake;
14444
+ if (!socket || !handshake) {
14445
+ throw new Error("Cowtail websocket is disconnected");
14446
+ }
14447
+ await handshake.promise;
14448
+ const currentSocket = this.#socket;
14449
+ if (!currentSocket || socket !== currentSocket || handshake !== this.#handshake || !this.#isSocketOpen(currentSocket)) {
14450
+ throw new Error("Cowtail websocket is disconnected");
14451
+ }
14452
+ currentSocket.send(JSON.stringify(command));
14453
+ }
14390
14454
  #resolvePendingRequest(requestId, sequence, payload) {
14391
14455
  const pending = this.#pendingRequests.get(requestId);
14392
14456
  if (!pending) {
@@ -14491,6 +14555,8 @@ var CHANNEL_LABEL = "Cowtail";
14491
14555
  var SENDER_LABEL = "Cowtail iOS";
14492
14556
  var SENDER_ID = "cowtail-ios";
14493
14557
  var SENDER_ADDRESS = "cowtail:ios";
14558
+ var STREAM_MIN_INTERVAL_MS = 45;
14559
+ var STREAM_MIN_CHARS_DELTA = 3;
14494
14560
  async function handleCowtailEvent(params) {
14495
14561
  const { event, account, client, runtime, logger } = params;
14496
14562
  switch (event.type) {
@@ -14626,7 +14692,11 @@ async function dispatchCowtailTextTurn(params) {
14626
14692
  });
14627
14693
  const streamState = {
14628
14694
  idempotencyKey: `cowtail:reply:${message.id}`,
14695
+ streamId: `cowtail:stream:${message.id}`,
14629
14696
  updateIndex: 0,
14697
+ snapshotSequence: 0,
14698
+ toolFallbackIndex: 0,
14699
+ toolFallbackIds: {},
14630
14700
  text: "",
14631
14701
  links: [],
14632
14702
  toolCalls: []
@@ -14636,12 +14706,17 @@ async function dispatchCowtailTextTurn(params) {
14636
14706
  channel: CHANNEL_ID,
14637
14707
  accountId: account.accountId,
14638
14708
  agentId: route.agentId,
14709
+ client,
14710
+ logger,
14639
14711
  route,
14712
+ thread,
14640
14713
  storePath,
14641
14714
  ctxPayload,
14642
14715
  runtime,
14716
+ streamState,
14643
14717
  deliver: async (payload, info, rawPayload) => deliverCowtailReply({
14644
14718
  client,
14719
+ logger,
14645
14720
  route,
14646
14721
  thread,
14647
14722
  payload,
@@ -14658,6 +14733,9 @@ async function dispatchCowtailTextTurn(params) {
14658
14733
  });
14659
14734
  await finalizeCowtailStreamedReply({
14660
14735
  client,
14736
+ logger,
14737
+ route,
14738
+ thread,
14661
14739
  streamState
14662
14740
  });
14663
14741
  }
@@ -14688,7 +14766,11 @@ async function dispatchCowtailActionTurn(params) {
14688
14766
  let dispatchFailed = false;
14689
14767
  const streamState = {
14690
14768
  idempotencyKey: `cowtail:action:${action.id}`,
14769
+ streamId: `cowtail:action-stream:${action.id}`,
14691
14770
  updateIndex: 0,
14771
+ snapshotSequence: 0,
14772
+ toolFallbackIndex: 0,
14773
+ toolFallbackIds: {},
14692
14774
  text: "",
14693
14775
  links: [],
14694
14776
  toolCalls: []
@@ -14698,12 +14780,17 @@ async function dispatchCowtailActionTurn(params) {
14698
14780
  channel: CHANNEL_ID,
14699
14781
  accountId: account.accountId,
14700
14782
  agentId: route.agentId,
14783
+ client,
14784
+ logger,
14701
14785
  route,
14786
+ thread,
14702
14787
  storePath,
14703
14788
  ctxPayload,
14704
14789
  runtime,
14790
+ streamState,
14705
14791
  deliver: async (replyPayload, info, rawPayload) => deliverCowtailReply({
14706
14792
  client,
14793
+ logger,
14707
14794
  route,
14708
14795
  thread,
14709
14796
  payload: replyPayload,
@@ -14721,6 +14808,9 @@ async function dispatchCowtailActionTurn(params) {
14721
14808
  });
14722
14809
  await finalizeCowtailStreamedReply({
14723
14810
  client,
14811
+ logger,
14812
+ route,
14813
+ thread,
14724
14814
  streamState
14725
14815
  });
14726
14816
  return !dispatchFailed;
@@ -14754,7 +14844,79 @@ async function recordCowtailInboundSessionAndDispatchReply(params) {
14754
14844
  },
14755
14845
  replyOptions: {
14756
14846
  disableBlockStreaming: false,
14757
- onModelSelected
14847
+ onModelSelected,
14848
+ onPartialReply: (payload) => {
14849
+ deliverCowtailPartialReply({
14850
+ client: params.client,
14851
+ logger: params.logger,
14852
+ route: params.route,
14853
+ thread: params.thread,
14854
+ payload: normalizeCowtailReplyPayload(payload),
14855
+ streamState: params.streamState
14856
+ });
14857
+ },
14858
+ onToolStart: (payload) => {
14859
+ deliverCowtailToolProgress({
14860
+ client: params.client,
14861
+ logger: params.logger,
14862
+ route: params.route,
14863
+ thread: params.thread,
14864
+ streamState: params.streamState,
14865
+ payload,
14866
+ requireStableId: true
14867
+ });
14868
+ },
14869
+ onItemEvent: (payload) => {
14870
+ deliverCowtailToolProgress({
14871
+ client: params.client,
14872
+ logger: params.logger,
14873
+ route: params.route,
14874
+ thread: params.thread,
14875
+ streamState: params.streamState,
14876
+ payload
14877
+ });
14878
+ },
14879
+ onCommandOutput: (payload) => {
14880
+ deliverCowtailToolProgress({
14881
+ client: params.client,
14882
+ logger: params.logger,
14883
+ route: params.route,
14884
+ thread: params.thread,
14885
+ streamState: params.streamState,
14886
+ payload
14887
+ });
14888
+ },
14889
+ onPatchSummary: (payload) => {
14890
+ deliverCowtailToolProgress({
14891
+ client: params.client,
14892
+ logger: params.logger,
14893
+ route: params.route,
14894
+ thread: params.thread,
14895
+ streamState: params.streamState,
14896
+ payload
14897
+ });
14898
+ },
14899
+ onApprovalEvent: (payload) => {
14900
+ deliverCowtailToolProgress({
14901
+ client: params.client,
14902
+ logger: params.logger,
14903
+ route: params.route,
14904
+ thread: params.thread,
14905
+ streamState: params.streamState,
14906
+ payload
14907
+ });
14908
+ },
14909
+ onToolResult: (payload) => {
14910
+ deliverCowtailToolProgress({
14911
+ client: params.client,
14912
+ logger: params.logger,
14913
+ route: params.route,
14914
+ thread: params.thread,
14915
+ streamState: params.streamState,
14916
+ payload,
14917
+ status: "complete"
14918
+ });
14919
+ }
14758
14920
  }
14759
14921
  });
14760
14922
  }
@@ -14774,31 +14936,30 @@ async function deliverCowtailReply(params) {
14774
14936
  rawPayload: params.rawPayload,
14775
14937
  streamState: params.streamState
14776
14938
  });
14777
- params.streamState.toolCalls = [...params.streamState.toolCalls, toolCall];
14939
+ upsertCowtailToolCall(params.streamState, toolCall);
14778
14940
  const messageText = params.streamState.text;
14941
+ emitCowtailStreamSnapshot({
14942
+ client: params.client,
14943
+ logger: params.logger,
14944
+ route: params.route,
14945
+ thread: params.thread,
14946
+ streamState: params.streamState,
14947
+ text: currentLiveCowtailStreamText(params.streamState),
14948
+ isFinal: false,
14949
+ updatedAt: Date.now()
14950
+ });
14779
14951
  if (!params.streamState.messageId) {
14780
- const result = await params.client.sendOpenClawMessage({
14781
- type: "openclaw_message",
14782
- idempotencyKey: params.streamState.idempotencyKey,
14783
- sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14784
- title: params.thread.title,
14952
+ await createDurableCowtailReply(params, {
14785
14953
  text: messageText,
14786
- authorLabel: "OpenClaw",
14787
14954
  links,
14788
- toolCalls: params.streamState.toolCalls,
14789
- actions: [],
14790
14955
  deliveryState: "pending"
14791
14956
  });
14792
- const messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14793
- if (!messageId) {
14794
- return;
14795
- }
14796
- params.streamState.messageId = messageId;
14797
14957
  return;
14798
14958
  }
14799
14959
  await params.client.sendOpenClawMessageUpdate({
14800
14960
  type: "openclaw_message_update",
14801
14961
  idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14962
+ streamId: params.streamState.streamId,
14802
14963
  messageId: params.streamState.messageId,
14803
14964
  text: messageText,
14804
14965
  links,
@@ -14812,29 +14973,28 @@ async function deliverCowtailReply(params) {
14812
14973
  }
14813
14974
  if (kind === "block") {
14814
14975
  params.streamState.text = appendReplyBlock(params.streamState.text, text);
14976
+ emitCowtailStreamSnapshot({
14977
+ client: params.client,
14978
+ logger: params.logger,
14979
+ route: params.route,
14980
+ thread: params.thread,
14981
+ streamState: params.streamState,
14982
+ text: params.streamState.text,
14983
+ isFinal: false,
14984
+ updatedAt: Date.now()
14985
+ });
14815
14986
  if (!params.streamState.messageId) {
14816
- const result = await params.client.sendOpenClawMessage({
14817
- type: "openclaw_message",
14818
- idempotencyKey: params.streamState.idempotencyKey,
14819
- sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14820
- title: params.thread.title,
14987
+ await createDurableCowtailReply(params, {
14821
14988
  text: params.streamState.text,
14822
- authorLabel: "OpenClaw",
14823
14989
  links,
14824
- toolCalls: params.streamState.toolCalls,
14825
- actions: [],
14826
14990
  deliveryState: "pending"
14827
14991
  });
14828
- const messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14829
- if (!messageId) {
14830
- return;
14831
- }
14832
- params.streamState.messageId = messageId;
14833
14992
  return;
14834
14993
  }
14835
14994
  await params.client.sendOpenClawMessageUpdate({
14836
14995
  type: "openclaw_message_update",
14837
14996
  idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
14997
+ streamId: params.streamState.streamId,
14838
14998
  messageId: params.streamState.messageId,
14839
14999
  text: params.streamState.text,
14840
15000
  links,
@@ -14848,6 +15008,7 @@ async function deliverCowtailReply(params) {
14848
15008
  await params.client.sendOpenClawMessageUpdate({
14849
15009
  type: "openclaw_message_update",
14850
15010
  idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
15011
+ streamId: params.streamState.streamId,
14851
15012
  messageId: params.streamState.messageId,
14852
15013
  text,
14853
15014
  links,
@@ -14855,23 +15016,67 @@ async function deliverCowtailReply(params) {
14855
15016
  actions: [],
14856
15017
  deliveryState: "sent"
14857
15018
  });
15019
+ emitCowtailStreamSnapshot({
15020
+ client: params.client,
15021
+ logger: params.logger,
15022
+ route: params.route,
15023
+ thread: params.thread,
15024
+ streamState: params.streamState,
15025
+ text,
15026
+ isFinal: true,
15027
+ updatedAt: Date.now()
15028
+ });
14858
15029
  params.streamState.completed = true;
14859
15030
  return;
14860
15031
  }
14861
- await params.client.sendOpenClawMessage({
14862
- type: "openclaw_message",
14863
- idempotencyKey: params.streamState.idempotencyKey,
14864
- sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14865
- title: params.thread.title,
15032
+ params.streamState.text = text;
15033
+ const messageId = await createDurableCowtailReply(params, {
14866
15034
  text,
14867
- authorLabel: "OpenClaw",
14868
15035
  links,
14869
- toolCalls: params.streamState.toolCalls,
14870
- actions: [],
14871
15036
  deliveryState: "sent"
14872
15037
  });
15038
+ if (!messageId) {
15039
+ return;
15040
+ }
15041
+ emitCowtailStreamSnapshot({
15042
+ client: params.client,
15043
+ logger: params.logger,
15044
+ route: params.route,
15045
+ thread: params.thread,
15046
+ streamState: params.streamState,
15047
+ text,
15048
+ isFinal: true,
15049
+ updatedAt: Date.now()
15050
+ });
14873
15051
  params.streamState.completed = true;
14874
15052
  }
15053
+ async function createDurableCowtailReply(params, message) {
15054
+ try {
15055
+ const result = await params.client.sendOpenClawMessage({
15056
+ type: "openclaw_message",
15057
+ idempotencyKey: params.streamState.idempotencyKey,
15058
+ streamId: params.streamState.streamId,
15059
+ sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
15060
+ title: params.thread.title,
15061
+ text: message.text,
15062
+ authorLabel: "OpenClaw",
15063
+ links: message.links,
15064
+ toolCalls: params.streamState.toolCalls,
15065
+ actions: [],
15066
+ deliveryState: message.deliveryState
15067
+ });
15068
+ const messageId = recordCreatedOpenClawMessageId(params.streamState, result);
15069
+ if (!messageId) {
15070
+ failCowtailStreamBeforeDurableCreate(params);
15071
+ return;
15072
+ }
15073
+ params.streamState.messageId = messageId;
15074
+ return messageId;
15075
+ } catch (error48) {
15076
+ failCowtailStreamBeforeDurableCreate(params);
15077
+ throw error48;
15078
+ }
15079
+ }
14875
15080
  async function finalizeCowtailStreamedReply(params) {
14876
15081
  if (params.streamState.failed || params.streamState.completed || !params.streamState.messageId || !params.streamState.text.trim() && params.streamState.toolCalls.length === 0) {
14877
15082
  return;
@@ -14879,6 +15084,7 @@ async function finalizeCowtailStreamedReply(params) {
14879
15084
  await params.client.sendOpenClawMessageUpdate({
14880
15085
  type: "openclaw_message_update",
14881
15086
  idempotencyKey: nextStreamUpdateIdempotencyKey(params.streamState),
15087
+ streamId: params.streamState.streamId,
14882
15088
  messageId: params.streamState.messageId,
14883
15089
  text: params.streamState.text,
14884
15090
  links: params.streamState.links,
@@ -14886,8 +15092,137 @@ async function finalizeCowtailStreamedReply(params) {
14886
15092
  actions: [],
14887
15093
  deliveryState: "sent"
14888
15094
  });
15095
+ emitCowtailStreamSnapshot({
15096
+ client: params.client,
15097
+ logger: params.logger,
15098
+ route: params.route,
15099
+ thread: params.thread,
15100
+ streamState: params.streamState,
15101
+ text: params.streamState.text,
15102
+ isFinal: true,
15103
+ updatedAt: Date.now()
15104
+ });
14889
15105
  params.streamState.completed = true;
14890
15106
  }
15107
+ function deliverCowtailPartialReply(params) {
15108
+ const text = params.payload.text;
15109
+ if (params.streamState.failed || typeof text !== "string" || !text.trim()) {
15110
+ return;
15111
+ }
15112
+ const now = Date.now();
15113
+ const previous = params.streamState.lastSnapshotText ?? "";
15114
+ const changedChars = Math.abs(text.length - previous.length);
15115
+ const sentRecently = params.streamState.lastSnapshotSentAt !== undefined && now - params.streamState.lastSnapshotSentAt < STREAM_MIN_INTERVAL_MS;
15116
+ if (text === previous || sentRecently && changedChars < STREAM_MIN_CHARS_DELTA) {
15117
+ return;
15118
+ }
15119
+ emitCowtailStreamSnapshot({
15120
+ client: params.client,
15121
+ logger: params.logger,
15122
+ route: params.route,
15123
+ thread: params.thread,
15124
+ streamState: params.streamState,
15125
+ text,
15126
+ isFinal: false,
15127
+ updatedAt: now
15128
+ });
15129
+ }
15130
+ function deliverCowtailToolProgress(params) {
15131
+ if (params.streamState.failed) {
15132
+ return;
15133
+ }
15134
+ const payload = readRecord(params.payload) ?? {};
15135
+ const stableId = readString(payload.itemId) ?? readString(payload.toolCallId) ?? readString(payload.id);
15136
+ if (params.requireStableId && !stableId) {
15137
+ return;
15138
+ }
15139
+ const id = stableId ?? fallbackCowtailToolCallId(params.streamState, payload);
15140
+ const name = readString(payload.name) ?? readString(payload.title) ?? readString(payload.command) ?? "tool_result";
15141
+ const status = params.status ?? normalizeCowtailToolCallStatus(payload.status) ?? normalizeCowtailToolCallStatus(payload.phase) ?? "running";
15142
+ const result = readCowtailToolProgressResult(payload);
15143
+ const args = readRecord(payload.args) ?? readRecord(payload.input);
15144
+ const now = Date.now();
15145
+ const currentText = currentLiveCowtailStreamText(params.streamState);
15146
+ const nextToolCall = {
15147
+ id,
15148
+ name,
15149
+ ...args ? { args } : {},
15150
+ ...result !== undefined ? { result } : {},
15151
+ status,
15152
+ ...status === "complete" || status === "error" ? { completedAt: now } : {},
15153
+ insertedAtContentLength: currentText.length,
15154
+ contentSnapshotAtStart: currentText
15155
+ };
15156
+ upsertCowtailToolCall(params.streamState, nextToolCall);
15157
+ emitCowtailStreamSnapshot({
15158
+ client: params.client,
15159
+ logger: params.logger,
15160
+ route: params.route,
15161
+ thread: params.thread,
15162
+ streamState: params.streamState,
15163
+ text: currentText,
15164
+ isFinal: false,
15165
+ updatedAt: now
15166
+ });
15167
+ }
15168
+ function emitCowtailStreamSnapshot(params) {
15169
+ if (params.streamState.failed && params.allowFailed !== true || !params.text.trim() && params.streamState.links.length === 0 && params.streamState.toolCalls.length === 0) {
15170
+ return;
15171
+ }
15172
+ params.streamState.liveText = params.text;
15173
+ params.streamState.lastSnapshotText = params.text;
15174
+ params.streamState.lastSnapshotSentAt = params.updatedAt;
15175
+ params.streamState.snapshotSequence += 1;
15176
+ params.client.sendOpenClawStreamSnapshot({
15177
+ type: "openclaw_message_stream_snapshot",
15178
+ streamId: params.streamState.streamId,
15179
+ sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
15180
+ threadId: params.thread.id,
15181
+ text: params.text,
15182
+ links: params.streamState.links.map((link) => ({ ...link })),
15183
+ toolCalls: structuredClone(params.streamState.toolCalls),
15184
+ isFinal: params.isFinal,
15185
+ snapshotSequence: params.streamState.snapshotSequence,
15186
+ updatedAt: params.updatedAt
15187
+ }).catch((error48) => {
15188
+ params.logger?.warn?.(`Cowtail stream snapshot send failed: ${errorMessage(error48)}`);
15189
+ });
15190
+ }
15191
+ function failCowtailStreamBeforeDurableCreate(params) {
15192
+ if (!params.streamState.messageId && params.streamState.snapshotSequence > 0) {
15193
+ const text = params.streamState.text.trim() ? params.streamState.text : currentLiveCowtailStreamText(params.streamState);
15194
+ emitCowtailStreamSnapshot({
15195
+ client: params.client,
15196
+ logger: params.logger,
15197
+ route: params.route,
15198
+ thread: params.thread,
15199
+ streamState: params.streamState,
15200
+ text,
15201
+ isFinal: true,
15202
+ updatedAt: Date.now(),
15203
+ allowFailed: true
15204
+ });
15205
+ }
15206
+ params.streamState.failed = true;
15207
+ params.streamState.completed = true;
15208
+ }
15209
+ function currentLiveCowtailStreamText(streamState) {
15210
+ return streamState.liveText ?? streamState.text;
15211
+ }
15212
+ function upsertCowtailToolCall(streamState, toolCall) {
15213
+ const index = streamState.toolCalls.findIndex((candidate) => candidate.id === toolCall.id);
15214
+ if (index < 0) {
15215
+ streamState.toolCalls = [...streamState.toolCalls, toolCall];
15216
+ return;
15217
+ }
15218
+ const existing = streamState.toolCalls[index];
15219
+ streamState.toolCalls[index] = {
15220
+ ...existing,
15221
+ ...toolCall,
15222
+ insertedAtContentLength: existing.insertedAtContentLength ?? toolCall.insertedAtContentLength,
15223
+ contentSnapshotAtStart: existing.contentSnapshotAtStart ?? toolCall.contentSnapshotAtStart
15224
+ };
15225
+ }
14891
15226
  function recordCreatedOpenClawMessageId(streamState, result) {
14892
15227
  if (result.payload?.dropped === true) {
14893
15228
  if (result.payload.duplicate === true) {
@@ -14983,6 +15318,48 @@ function readRecord(value) {
14983
15318
  function readToolCallStatus(value) {
14984
15319
  return value === "pending" || value === "running" || value === "complete" || value === "error" ? value : undefined;
14985
15320
  }
15321
+ function normalizeCowtailToolCallStatus(value) {
15322
+ const status = readToolCallStatus(value);
15323
+ if (status) {
15324
+ return status;
15325
+ }
15326
+ if (value === "start" || value === "started" || value === "working") {
15327
+ return "running";
15328
+ }
15329
+ if (value === "end" || value === "done" || value === "completed" || value === "success") {
15330
+ return "complete";
15331
+ }
15332
+ if (value === "failed" || value === "failure") {
15333
+ return "error";
15334
+ }
15335
+ return;
15336
+ }
15337
+ function readCowtailToolProgressResult(payload) {
15338
+ const stringResult = readString(payload.output) ?? readString(payload.summary) ?? readString(payload.message) ?? readString(payload.progressText) ?? readString(payload.text);
15339
+ if (stringResult !== undefined) {
15340
+ return stringResult;
15341
+ }
15342
+ if (payload.result !== undefined) {
15343
+ return payload.result;
15344
+ }
15345
+ const structuredPatch = {
15346
+ ...Array.isArray(payload.added) ? { added: payload.added } : {},
15347
+ ...Array.isArray(payload.modified) ? { modified: payload.modified } : {},
15348
+ ...Array.isArray(payload.deleted) ? { deleted: payload.deleted } : {}
15349
+ };
15350
+ return Object.keys(structuredPatch).length > 0 ? structuredPatch : undefined;
15351
+ }
15352
+ function fallbackCowtailToolCallId(streamState, payload) {
15353
+ const key = readString(payload.name) ?? readString(payload.title) ?? readString(payload.command) ?? JSON.stringify(Object.keys(payload).sort());
15354
+ const existing = streamState.toolFallbackIds[key];
15355
+ if (existing) {
15356
+ return existing;
15357
+ }
15358
+ streamState.toolFallbackIndex += 1;
15359
+ const next = `tool-${streamState.toolFallbackIndex}`;
15360
+ streamState.toolFallbackIds[key] = next;
15361
+ return next;
15362
+ }
14986
15363
  function readTimestamp(value) {
14987
15364
  return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
14988
15365
  }
@@ -15105,23 +15482,29 @@ function resolveCowtailTarget(to) {
15105
15482
  if (!normalizedTarget) {
15106
15483
  throw new Error("Cowtail target must not be blank");
15107
15484
  }
15108
- return buildCowtailTarget(normalizedTarget);
15485
+ return normalizedTarget;
15109
15486
  }
15110
15487
  async function sendCowtailText(params) {
15111
15488
  const { client, to } = params;
15112
15489
  const text = ensureNonBlankText(params.text);
15113
15490
  const target = resolveCowtailTarget(to);
15491
+ const sessionKey = buildCowtailTarget(target);
15114
15492
  const result = await client.sendOpenClawMessage({
15115
15493
  type: "openclaw_message",
15116
- sessionKey: target,
15494
+ sessionKey,
15495
+ threadHint: target,
15117
15496
  text,
15118
15497
  links: [],
15119
15498
  actions: []
15120
15499
  });
15500
+ const messageId = result.payload?.messageId;
15501
+ if (typeof messageId !== "string" || messageId.trim() === "") {
15502
+ throw new Error("Cowtail did not acknowledge a durable message id");
15503
+ }
15121
15504
  return {
15122
15505
  channel: "cowtail",
15123
- messageId: String(result.sequence ?? result.requestId),
15124
- to: target
15506
+ messageId,
15507
+ to: buildCowtailTarget(target)
15125
15508
  };
15126
15509
  }
15127
15510