@maudecode/openclaw-cowtail 0.9.1 → 0.11.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.
package/dist/index.js CHANGED
@@ -13728,6 +13728,7 @@ var openclawEventTypes = [
13728
13728
  "thread_created",
13729
13729
  "thread_updated",
13730
13730
  "message_created",
13731
+ "message_updated",
13731
13732
  "message_acknowledged",
13732
13733
  "reply_created",
13733
13734
  "action_submitted",
@@ -13742,12 +13743,24 @@ var openclawTargetAgentSchema = exports_external.literal("default");
13742
13743
  var openclawMessageDirectionSchema = exports_external.enum(["openclaw_to_user", "user_to_openclaw"]);
13743
13744
  var openclawDeliveryStateSchema = exports_external.enum(["pending", "sent", "failed"]);
13744
13745
  var openclawActionStateSchema = exports_external.enum(["pending", "submitted", "failed", "expired"]);
13746
+ var openclawToolCallStatusSchema = exports_external.enum(["pending", "running", "complete", "error"]);
13745
13747
  var openclawEventTypeSchema = exports_external.enum(openclawEventTypes);
13746
13748
  var openclawSequenceSchema = exports_external.number().int().nonnegative();
13747
13749
  var openclawLinkSchema = exports_external.object({
13748
13750
  label: nonEmptyStringSchema,
13749
13751
  url: nonEmptyStringSchema
13750
13752
  });
13753
+ var openclawToolCallRecordSchema = exports_external.object({
13754
+ id: nonEmptyStringSchema,
13755
+ name: nonEmptyStringSchema,
13756
+ args: jsonObjectSchema.optional(),
13757
+ result: exports_external.unknown().optional(),
13758
+ status: openclawToolCallStatusSchema,
13759
+ startedAt: timestampSchema.optional(),
13760
+ completedAt: timestampSchema.optional(),
13761
+ insertedAtContentLength: exports_external.number().int().nonnegative().optional(),
13762
+ contentSnapshotAtStart: exports_external.string().optional()
13763
+ });
13751
13764
  var openclawPluginClientHelloSchema = exports_external.object({
13752
13765
  protocolVersion: openclawProtocolVersionSchema,
13753
13766
  clientKind: exports_external.literal("openclaw_plugin"),
@@ -13782,6 +13795,7 @@ var openclawMessageRecordSchema = exports_external.object({
13782
13795
  authorLabel: nonEmptyStringSchema.optional(),
13783
13796
  text: nonEmptyStringSchema,
13784
13797
  links: exports_external.array(openclawLinkSchema).default([]),
13798
+ toolCalls: exports_external.array(openclawToolCallRecordSchema).default([]),
13785
13799
  deliveryState: openclawDeliveryStateSchema,
13786
13800
  createdAt: timestampSchema,
13787
13801
  updatedAt: timestampSchema
@@ -13832,7 +13846,19 @@ var openclawPluginMessageCommandSchema = exports_external.object({
13832
13846
  text: nonEmptyStringSchema,
13833
13847
  authorLabel: nonEmptyStringSchema.optional(),
13834
13848
  links: exports_external.array(openclawLinkSchema).default([]),
13835
- actions: exports_external.array(openclawActionDraftSchema).default([])
13849
+ toolCalls: exports_external.array(openclawToolCallRecordSchema).default([]),
13850
+ actions: exports_external.array(openclawActionDraftSchema).default([]),
13851
+ deliveryState: openclawDeliveryStateSchema.optional()
13852
+ });
13853
+ var openclawPluginMessageUpdateCommandSchema = exports_external.object({
13854
+ type: exports_external.literal("openclaw_message_update"),
13855
+ requestId: openclawRequestIdSchema,
13856
+ messageId: nonEmptyStringSchema,
13857
+ text: nonEmptyStringSchema,
13858
+ links: exports_external.array(openclawLinkSchema).optional(),
13859
+ toolCalls: exports_external.array(openclawToolCallRecordSchema).optional(),
13860
+ actions: exports_external.array(openclawActionDraftSchema).optional(),
13861
+ deliveryState: openclawDeliveryStateSchema.optional()
13836
13862
  });
13837
13863
  var openclawIosNewThreadCommandSchema = exports_external.object({
13838
13864
  type: exports_external.literal("ios_new_thread"),
@@ -13883,6 +13909,7 @@ var openclawActionResultCommandSchema = exports_external.object({
13883
13909
  });
13884
13910
  var openclawRealtimeClientMessageSchema = exports_external.discriminatedUnion("type", [
13885
13911
  openclawPluginMessageCommandSchema,
13912
+ openclawPluginMessageUpdateCommandSchema,
13886
13913
  openclawIosNewThreadCommandSchema,
13887
13914
  openclawIosReplyCommandSchema,
13888
13915
  openclawIosActionCommandSchema,
@@ -13895,7 +13922,8 @@ var openclawRealtimeClientMessageSchema = exports_external.discriminatedUnion("t
13895
13922
  var openclawRealtimeAckSchema = exports_external.object({
13896
13923
  type: exports_external.literal("ack"),
13897
13924
  requestId: openclawRequestIdSchema,
13898
- sequence: openclawSequenceSchema.optional()
13925
+ sequence: openclawSequenceSchema.optional(),
13926
+ payload: jsonObjectSchema.optional()
13899
13927
  });
13900
13928
  var openclawRealtimeErrorSchema = exports_external.object({
13901
13929
  type: exports_external.literal("realtime_error"),
@@ -14129,9 +14157,19 @@ class CowtailRealtimeClient {
14129
14157
  ...command,
14130
14158
  requestId: this.#requestIdFactory(),
14131
14159
  links: command.links ?? [],
14160
+ toolCalls: command.toolCalls ?? [],
14132
14161
  actions: command.actions ?? []
14133
14162
  });
14134
14163
  }
14164
+ sendOpenClawMessageUpdate(command) {
14165
+ return this.#sendCommand({
14166
+ ...command,
14167
+ requestId: this.#requestIdFactory(),
14168
+ ...command.links ? { links: command.links } : {},
14169
+ ...command.toolCalls ? { toolCalls: command.toolCalls } : {},
14170
+ ...command.actions ? { actions: command.actions } : {}
14171
+ });
14172
+ }
14135
14173
  sendSessionBound(command) {
14136
14174
  return this.#sendCommand({
14137
14175
  ...command,
@@ -14236,7 +14274,7 @@ class CowtailRealtimeClient {
14236
14274
  }
14237
14275
  const message = parsed.data;
14238
14276
  if (message.type === "ack") {
14239
- this.#resolvePendingRequest(message.requestId, message.sequence);
14277
+ this.#resolvePendingRequest(message.requestId, message.sequence, message.payload);
14240
14278
  return;
14241
14279
  }
14242
14280
  if (message.type === "realtime_error") {
@@ -14319,13 +14357,17 @@ class CowtailRealtimeClient {
14319
14357
  }
14320
14358
  });
14321
14359
  }
14322
- #resolvePendingRequest(requestId, sequence) {
14360
+ #resolvePendingRequest(requestId, sequence, payload) {
14323
14361
  const pending = this.#pendingRequests.get(requestId);
14324
14362
  if (!pending) {
14325
14363
  return;
14326
14364
  }
14327
14365
  this.#pendingRequests.delete(requestId);
14328
- pending.resolve({ requestId, sequence });
14366
+ pending.resolve({
14367
+ requestId,
14368
+ sequence,
14369
+ ...payload ? { payload } : {}
14370
+ });
14329
14371
  }
14330
14372
  #rejectPendingRequest(message) {
14331
14373
  if (!message.requestId) {
@@ -14399,7 +14441,8 @@ class CowtailRealtimeClient {
14399
14441
  }
14400
14442
 
14401
14443
  // src/inbound.ts
14402
- import { dispatchInboundReplyWithBase } from "openclaw/plugin-sdk/inbound-reply-dispatch";
14444
+ import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
14445
+ import { normalizeOutboundReplyPayload } from "openclaw/plugin-sdk/reply-payload";
14403
14446
 
14404
14447
  // src/session-keys.ts
14405
14448
  function normalizeCowtailTarget(raw) {
@@ -14548,19 +14591,24 @@ async function dispatchCowtailTextTurn(params) {
14548
14591
  body,
14549
14592
  timestamp: message.createdAt
14550
14593
  });
14551
- await dispatchInboundReplyWithBase({
14594
+ const streamState = { text: "", toolCalls: [] };
14595
+ await recordCowtailInboundSessionAndDispatchReply({
14552
14596
  cfg: runtime.config.loadConfig(),
14553
14597
  channel: CHANNEL_ID,
14554
14598
  accountId: account.accountId,
14599
+ agentId: route.agentId,
14555
14600
  route,
14556
14601
  storePath,
14557
14602
  ctxPayload,
14558
- core: runtime,
14559
- deliver: async (payload) => deliverCowtailReply({
14603
+ runtime,
14604
+ deliver: async (payload, info, rawPayload) => deliverCowtailReply({
14560
14605
  client,
14561
14606
  route,
14562
14607
  thread,
14563
- payload
14608
+ payload,
14609
+ rawPayload,
14610
+ info,
14611
+ streamState
14564
14612
  }),
14565
14613
  onRecordError: (error48) => {
14566
14614
  logger?.error?.(`Cowtail inbound session record failed: ${errorMessage(error48)}`);
@@ -14595,19 +14643,24 @@ async function dispatchCowtailActionTurn(params) {
14595
14643
  timestamp
14596
14644
  });
14597
14645
  let dispatchFailed = false;
14598
- await dispatchInboundReplyWithBase({
14646
+ const streamState = { text: "", toolCalls: [] };
14647
+ await recordCowtailInboundSessionAndDispatchReply({
14599
14648
  cfg: runtime.config.loadConfig(),
14600
14649
  channel: CHANNEL_ID,
14601
14650
  accountId: account.accountId,
14651
+ agentId: route.agentId,
14602
14652
  route,
14603
14653
  storePath,
14604
14654
  ctxPayload,
14605
- core: runtime,
14606
- deliver: async (replyPayload) => deliverCowtailReply({
14655
+ runtime,
14656
+ deliver: async (replyPayload, info, rawPayload) => deliverCowtailReply({
14607
14657
  client,
14608
14658
  route,
14609
14659
  thread,
14610
- payload: replyPayload
14660
+ payload: replyPayload,
14661
+ rawPayload,
14662
+ info,
14663
+ streamState
14611
14664
  }),
14612
14665
  onRecordError: (error48) => {
14613
14666
  logger?.error?.(`Cowtail inbound session record failed: ${errorMessage(error48)}`);
@@ -14619,21 +14672,141 @@ async function dispatchCowtailActionTurn(params) {
14619
14672
  });
14620
14673
  return !dispatchFailed;
14621
14674
  }
14675
+ async function recordCowtailInboundSessionAndDispatchReply(params) {
14676
+ await params.runtime.channel.session.recordInboundSession({
14677
+ storePath: params.storePath,
14678
+ sessionKey: typeof params.ctxPayload.SessionKey === "string" ? params.ctxPayload.SessionKey : params.route.sessionKey,
14679
+ ctx: params.ctxPayload,
14680
+ onRecordError: params.onRecordError
14681
+ });
14682
+ const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
14683
+ cfg: params.cfg,
14684
+ agentId: params.agentId,
14685
+ channel: params.channel,
14686
+ accountId: params.accountId
14687
+ });
14688
+ await params.runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
14689
+ ctx: params.ctxPayload,
14690
+ cfg: params.cfg,
14691
+ dispatcherOptions: {
14692
+ ...replyPipeline,
14693
+ deliver: async (payload, info) => {
14694
+ try {
14695
+ await params.deliver(normalizeCowtailReplyPayload(payload), info, payload);
14696
+ } catch (error48) {
14697
+ params.onDispatchError(error48, { kind: info?.kind ?? "final" });
14698
+ }
14699
+ },
14700
+ onError: params.onDispatchError
14701
+ },
14702
+ replyOptions: {
14703
+ onModelSelected
14704
+ }
14705
+ });
14706
+ }
14622
14707
  async function deliverCowtailReply(params) {
14708
+ if (params.streamState.failed) {
14709
+ return;
14710
+ }
14623
14711
  const text = params.payload.text;
14712
+ const kind = params.info?.kind ?? "final";
14713
+ const links = resolveCowtailReplyLinks(params.payload);
14714
+ if (kind === "tool") {
14715
+ const { toolCall, summary } = buildCowtailToolCall({
14716
+ payload: params.payload,
14717
+ rawPayload: params.rawPayload,
14718
+ streamState: params.streamState
14719
+ });
14720
+ params.streamState.toolCalls = [...params.streamState.toolCalls, toolCall];
14721
+ const messageText = params.streamState.text || summary || "Tool activity";
14722
+ if (!params.streamState.messageId) {
14723
+ const result = await params.client.sendOpenClawMessage({
14724
+ type: "openclaw_message",
14725
+ sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14726
+ title: params.thread.title,
14727
+ text: messageText,
14728
+ authorLabel: "OpenClaw",
14729
+ links,
14730
+ toolCalls: params.streamState.toolCalls,
14731
+ actions: [],
14732
+ deliveryState: "pending"
14733
+ });
14734
+ params.streamState.messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14735
+ return;
14736
+ }
14737
+ await params.client.sendOpenClawMessageUpdate({
14738
+ type: "openclaw_message_update",
14739
+ messageId: params.streamState.messageId,
14740
+ text: messageText,
14741
+ links,
14742
+ toolCalls: params.streamState.toolCalls,
14743
+ deliveryState: "pending"
14744
+ });
14745
+ return;
14746
+ }
14624
14747
  if (typeof text !== "string" || !text.trim()) {
14625
14748
  return;
14626
14749
  }
14750
+ if (kind === "block") {
14751
+ params.streamState.text = appendReplyBlock(params.streamState.text, text);
14752
+ if (!params.streamState.messageId) {
14753
+ const result = await params.client.sendOpenClawMessage({
14754
+ type: "openclaw_message",
14755
+ sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14756
+ title: params.thread.title,
14757
+ text: params.streamState.text,
14758
+ authorLabel: "OpenClaw",
14759
+ links,
14760
+ toolCalls: params.streamState.toolCalls,
14761
+ actions: [],
14762
+ deliveryState: "pending"
14763
+ });
14764
+ params.streamState.messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14765
+ return;
14766
+ }
14767
+ await params.client.sendOpenClawMessageUpdate({
14768
+ type: "openclaw_message_update",
14769
+ messageId: params.streamState.messageId,
14770
+ text: params.streamState.text,
14771
+ links,
14772
+ toolCalls: params.streamState.toolCalls,
14773
+ deliveryState: "pending"
14774
+ });
14775
+ return;
14776
+ }
14777
+ if (params.streamState.messageId) {
14778
+ params.streamState.text = text;
14779
+ await params.client.sendOpenClawMessageUpdate({
14780
+ type: "openclaw_message_update",
14781
+ messageId: params.streamState.messageId,
14782
+ text,
14783
+ links,
14784
+ toolCalls: params.streamState.toolCalls,
14785
+ actions: [],
14786
+ deliveryState: "sent"
14787
+ });
14788
+ return;
14789
+ }
14627
14790
  await params.client.sendOpenClawMessage({
14628
14791
  type: "openclaw_message",
14629
14792
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14630
14793
  title: params.thread.title,
14631
14794
  text,
14632
14795
  authorLabel: "OpenClaw",
14633
- links: resolveCowtailReplyLinks(params.payload),
14634
- actions: []
14796
+ links,
14797
+ toolCalls: params.streamState.toolCalls,
14798
+ actions: [],
14799
+ deliveryState: "sent"
14635
14800
  });
14636
14801
  }
14802
+ function recordCreatedOpenClawMessageId(streamState, result) {
14803
+ const messageId = result.payload?.messageId;
14804
+ if (typeof messageId === "string" && messageId.trim()) {
14805
+ return messageId;
14806
+ }
14807
+ streamState.failed = true;
14808
+ throw new Error("Cowtail realtime ack missing messageId for streamed OpenClaw reply");
14809
+ }
14637
14810
  function resolveCowtailReplyLinks(payload) {
14638
14811
  const urls = [
14639
14812
  ...Array.isArray(payload.mediaUrls) ? payload.mediaUrls : [],
@@ -14644,6 +14817,101 @@ function resolveCowtailReplyLinks(payload) {
14644
14817
  url: url2
14645
14818
  }));
14646
14819
  }
14820
+ function normalizeCowtailReplyPayload(payload) {
14821
+ if (!payload || typeof payload !== "object") {
14822
+ return {};
14823
+ }
14824
+ return normalizeOutboundReplyPayload(payload);
14825
+ }
14826
+ function buildCowtailToolCall(params) {
14827
+ const rawToolCall = readRawToolCall(params.rawPayload);
14828
+ const summary = resolveToolCallSummary(params.payload, rawToolCall);
14829
+ const index = params.streamState.toolCalls.length + 1;
14830
+ const toolCall = {
14831
+ id: rawToolCall.id ?? `tool-${index}`,
14832
+ name: rawToolCall.name ?? "tool_result",
14833
+ ...rawToolCall.args ? { args: rawToolCall.args } : {},
14834
+ result: rawToolCall.result !== undefined ? rawToolCall.result : params.payload.text ?? params.payload,
14835
+ status: rawToolCall.status ?? "complete",
14836
+ ...rawToolCall.startedAt !== undefined ? { startedAt: rawToolCall.startedAt } : {},
14837
+ completedAt: rawToolCall.completedAt ?? Date.now(),
14838
+ insertedAtContentLength: params.streamState.text.length,
14839
+ contentSnapshotAtStart: params.streamState.text
14840
+ };
14841
+ return {
14842
+ toolCall,
14843
+ ...summary ? { summary } : {}
14844
+ };
14845
+ }
14846
+ function readRawToolCall(payload) {
14847
+ if (!payload || typeof payload !== "object") {
14848
+ return {};
14849
+ }
14850
+ const candidate = payload;
14851
+ const channelData = candidate.channelData;
14852
+ const toolCall = channelData && typeof channelData === "object" ? channelData.toolCall : undefined;
14853
+ const source = toolCall && typeof toolCall === "object" ? toolCall : candidate;
14854
+ const name = readString(source.name) ?? readString(source.toolName) ?? readString(source.tool);
14855
+ const id = readString(source.id) ?? readString(source.toolCallId);
14856
+ const status = readToolCallStatus(source.status);
14857
+ const args = readRecord(source.args) ?? readRecord(source.input);
14858
+ const startedAt = readTimestamp(source.startedAt);
14859
+ const completedAt = readTimestamp(source.completedAt);
14860
+ const insertedAtContentLength = readNonnegativeInteger(source.insertedAtContentLength);
14861
+ const contentSnapshotAtStart = readString(source.contentSnapshotAtStart);
14862
+ const result = source.result ?? source.output;
14863
+ return {
14864
+ ...id ? { id } : {},
14865
+ ...name ? { name } : {},
14866
+ ...args ? { args } : {},
14867
+ ...result !== undefined ? { result } : {},
14868
+ ...status ? { status } : {},
14869
+ ...startedAt !== undefined ? { startedAt } : {},
14870
+ ...completedAt !== undefined ? { completedAt } : {},
14871
+ ...insertedAtContentLength !== undefined ? { insertedAtContentLength } : {},
14872
+ ...contentSnapshotAtStart !== undefined ? { contentSnapshotAtStart } : {}
14873
+ };
14874
+ }
14875
+ function resolveToolCallSummary(payload, toolCall) {
14876
+ if (typeof payload.text === "string" && payload.text.trim()) {
14877
+ return payload.text.trim().split(`
14878
+ `)[0];
14879
+ }
14880
+ if (toolCall.name) {
14881
+ return toolCall.name;
14882
+ }
14883
+ return;
14884
+ }
14885
+ function readString(value) {
14886
+ return typeof value === "string" && value.trim() ? value : undefined;
14887
+ }
14888
+ function readRecord(value) {
14889
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
14890
+ return;
14891
+ }
14892
+ return value;
14893
+ }
14894
+ function readToolCallStatus(value) {
14895
+ return value === "pending" || value === "running" || value === "complete" || value === "error" ? value : undefined;
14896
+ }
14897
+ function readTimestamp(value) {
14898
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
14899
+ }
14900
+ function readNonnegativeInteger(value) {
14901
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
14902
+ }
14903
+ function appendReplyBlock(current, next) {
14904
+ if (!next.trim()) {
14905
+ return current;
14906
+ }
14907
+ if (!current) {
14908
+ return next;
14909
+ }
14910
+ if (next.startsWith(current)) {
14911
+ return next;
14912
+ }
14913
+ return `${current}${next}`;
14914
+ }
14647
14915
  function buildCowtailInboundBody(params) {
14648
14916
  const cfg = params.runtime.config.loadConfig();
14649
14917
  const storePath = params.runtime.agent.session.resolveStorePath(cfg.session?.store, {
@@ -13728,6 +13728,7 @@ var openclawEventTypes = [
13728
13728
  "thread_created",
13729
13729
  "thread_updated",
13730
13730
  "message_created",
13731
+ "message_updated",
13731
13732
  "message_acknowledged",
13732
13733
  "reply_created",
13733
13734
  "action_submitted",
@@ -13742,12 +13743,24 @@ var openclawTargetAgentSchema = exports_external.literal("default");
13742
13743
  var openclawMessageDirectionSchema = exports_external.enum(["openclaw_to_user", "user_to_openclaw"]);
13743
13744
  var openclawDeliveryStateSchema = exports_external.enum(["pending", "sent", "failed"]);
13744
13745
  var openclawActionStateSchema = exports_external.enum(["pending", "submitted", "failed", "expired"]);
13746
+ var openclawToolCallStatusSchema = exports_external.enum(["pending", "running", "complete", "error"]);
13745
13747
  var openclawEventTypeSchema = exports_external.enum(openclawEventTypes);
13746
13748
  var openclawSequenceSchema = exports_external.number().int().nonnegative();
13747
13749
  var openclawLinkSchema = exports_external.object({
13748
13750
  label: nonEmptyStringSchema,
13749
13751
  url: nonEmptyStringSchema
13750
13752
  });
13753
+ var openclawToolCallRecordSchema = exports_external.object({
13754
+ id: nonEmptyStringSchema,
13755
+ name: nonEmptyStringSchema,
13756
+ args: jsonObjectSchema.optional(),
13757
+ result: exports_external.unknown().optional(),
13758
+ status: openclawToolCallStatusSchema,
13759
+ startedAt: timestampSchema.optional(),
13760
+ completedAt: timestampSchema.optional(),
13761
+ insertedAtContentLength: exports_external.number().int().nonnegative().optional(),
13762
+ contentSnapshotAtStart: exports_external.string().optional()
13763
+ });
13751
13764
  var openclawPluginClientHelloSchema = exports_external.object({
13752
13765
  protocolVersion: openclawProtocolVersionSchema,
13753
13766
  clientKind: exports_external.literal("openclaw_plugin"),
@@ -13782,6 +13795,7 @@ var openclawMessageRecordSchema = exports_external.object({
13782
13795
  authorLabel: nonEmptyStringSchema.optional(),
13783
13796
  text: nonEmptyStringSchema,
13784
13797
  links: exports_external.array(openclawLinkSchema).default([]),
13798
+ toolCalls: exports_external.array(openclawToolCallRecordSchema).default([]),
13785
13799
  deliveryState: openclawDeliveryStateSchema,
13786
13800
  createdAt: timestampSchema,
13787
13801
  updatedAt: timestampSchema
@@ -13832,7 +13846,19 @@ var openclawPluginMessageCommandSchema = exports_external.object({
13832
13846
  text: nonEmptyStringSchema,
13833
13847
  authorLabel: nonEmptyStringSchema.optional(),
13834
13848
  links: exports_external.array(openclawLinkSchema).default([]),
13835
- actions: exports_external.array(openclawActionDraftSchema).default([])
13849
+ toolCalls: exports_external.array(openclawToolCallRecordSchema).default([]),
13850
+ actions: exports_external.array(openclawActionDraftSchema).default([]),
13851
+ deliveryState: openclawDeliveryStateSchema.optional()
13852
+ });
13853
+ var openclawPluginMessageUpdateCommandSchema = exports_external.object({
13854
+ type: exports_external.literal("openclaw_message_update"),
13855
+ requestId: openclawRequestIdSchema,
13856
+ messageId: nonEmptyStringSchema,
13857
+ text: nonEmptyStringSchema,
13858
+ links: exports_external.array(openclawLinkSchema).optional(),
13859
+ toolCalls: exports_external.array(openclawToolCallRecordSchema).optional(),
13860
+ actions: exports_external.array(openclawActionDraftSchema).optional(),
13861
+ deliveryState: openclawDeliveryStateSchema.optional()
13836
13862
  });
13837
13863
  var openclawIosNewThreadCommandSchema = exports_external.object({
13838
13864
  type: exports_external.literal("ios_new_thread"),
@@ -13883,6 +13909,7 @@ var openclawActionResultCommandSchema = exports_external.object({
13883
13909
  });
13884
13910
  var openclawRealtimeClientMessageSchema = exports_external.discriminatedUnion("type", [
13885
13911
  openclawPluginMessageCommandSchema,
13912
+ openclawPluginMessageUpdateCommandSchema,
13886
13913
  openclawIosNewThreadCommandSchema,
13887
13914
  openclawIosReplyCommandSchema,
13888
13915
  openclawIosActionCommandSchema,
@@ -13895,7 +13922,8 @@ var openclawRealtimeClientMessageSchema = exports_external.discriminatedUnion("t
13895
13922
  var openclawRealtimeAckSchema = exports_external.object({
13896
13923
  type: exports_external.literal("ack"),
13897
13924
  requestId: openclawRequestIdSchema,
13898
- sequence: openclawSequenceSchema.optional()
13925
+ sequence: openclawSequenceSchema.optional(),
13926
+ payload: jsonObjectSchema.optional()
13899
13927
  });
13900
13928
  var openclawRealtimeErrorSchema = exports_external.object({
13901
13929
  type: exports_external.literal("realtime_error"),
@@ -14129,9 +14157,19 @@ class CowtailRealtimeClient {
14129
14157
  ...command,
14130
14158
  requestId: this.#requestIdFactory(),
14131
14159
  links: command.links ?? [],
14160
+ toolCalls: command.toolCalls ?? [],
14132
14161
  actions: command.actions ?? []
14133
14162
  });
14134
14163
  }
14164
+ sendOpenClawMessageUpdate(command) {
14165
+ return this.#sendCommand({
14166
+ ...command,
14167
+ requestId: this.#requestIdFactory(),
14168
+ ...command.links ? { links: command.links } : {},
14169
+ ...command.toolCalls ? { toolCalls: command.toolCalls } : {},
14170
+ ...command.actions ? { actions: command.actions } : {}
14171
+ });
14172
+ }
14135
14173
  sendSessionBound(command) {
14136
14174
  return this.#sendCommand({
14137
14175
  ...command,
@@ -14236,7 +14274,7 @@ class CowtailRealtimeClient {
14236
14274
  }
14237
14275
  const message = parsed.data;
14238
14276
  if (message.type === "ack") {
14239
- this.#resolvePendingRequest(message.requestId, message.sequence);
14277
+ this.#resolvePendingRequest(message.requestId, message.sequence, message.payload);
14240
14278
  return;
14241
14279
  }
14242
14280
  if (message.type === "realtime_error") {
@@ -14319,13 +14357,17 @@ class CowtailRealtimeClient {
14319
14357
  }
14320
14358
  });
14321
14359
  }
14322
- #resolvePendingRequest(requestId, sequence) {
14360
+ #resolvePendingRequest(requestId, sequence, payload) {
14323
14361
  const pending = this.#pendingRequests.get(requestId);
14324
14362
  if (!pending) {
14325
14363
  return;
14326
14364
  }
14327
14365
  this.#pendingRequests.delete(requestId);
14328
- pending.resolve({ requestId, sequence });
14366
+ pending.resolve({
14367
+ requestId,
14368
+ sequence,
14369
+ ...payload ? { payload } : {}
14370
+ });
14329
14371
  }
14330
14372
  #rejectPendingRequest(message) {
14331
14373
  if (!message.requestId) {
@@ -14399,7 +14441,8 @@ class CowtailRealtimeClient {
14399
14441
  }
14400
14442
 
14401
14443
  // src/inbound.ts
14402
- import { dispatchInboundReplyWithBase } from "openclaw/plugin-sdk/inbound-reply-dispatch";
14444
+ import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
14445
+ import { normalizeOutboundReplyPayload } from "openclaw/plugin-sdk/reply-payload";
14403
14446
 
14404
14447
  // src/session-keys.ts
14405
14448
  function normalizeCowtailTarget(raw) {
@@ -14548,19 +14591,24 @@ async function dispatchCowtailTextTurn(params) {
14548
14591
  body,
14549
14592
  timestamp: message.createdAt
14550
14593
  });
14551
- await dispatchInboundReplyWithBase({
14594
+ const streamState = { text: "", toolCalls: [] };
14595
+ await recordCowtailInboundSessionAndDispatchReply({
14552
14596
  cfg: runtime.config.loadConfig(),
14553
14597
  channel: CHANNEL_ID,
14554
14598
  accountId: account.accountId,
14599
+ agentId: route.agentId,
14555
14600
  route,
14556
14601
  storePath,
14557
14602
  ctxPayload,
14558
- core: runtime,
14559
- deliver: async (payload) => deliverCowtailReply({
14603
+ runtime,
14604
+ deliver: async (payload, info, rawPayload) => deliverCowtailReply({
14560
14605
  client,
14561
14606
  route,
14562
14607
  thread,
14563
- payload
14608
+ payload,
14609
+ rawPayload,
14610
+ info,
14611
+ streamState
14564
14612
  }),
14565
14613
  onRecordError: (error48) => {
14566
14614
  logger?.error?.(`Cowtail inbound session record failed: ${errorMessage(error48)}`);
@@ -14595,19 +14643,24 @@ async function dispatchCowtailActionTurn(params) {
14595
14643
  timestamp
14596
14644
  });
14597
14645
  let dispatchFailed = false;
14598
- await dispatchInboundReplyWithBase({
14646
+ const streamState = { text: "", toolCalls: [] };
14647
+ await recordCowtailInboundSessionAndDispatchReply({
14599
14648
  cfg: runtime.config.loadConfig(),
14600
14649
  channel: CHANNEL_ID,
14601
14650
  accountId: account.accountId,
14651
+ agentId: route.agentId,
14602
14652
  route,
14603
14653
  storePath,
14604
14654
  ctxPayload,
14605
- core: runtime,
14606
- deliver: async (replyPayload) => deliverCowtailReply({
14655
+ runtime,
14656
+ deliver: async (replyPayload, info, rawPayload) => deliverCowtailReply({
14607
14657
  client,
14608
14658
  route,
14609
14659
  thread,
14610
- payload: replyPayload
14660
+ payload: replyPayload,
14661
+ rawPayload,
14662
+ info,
14663
+ streamState
14611
14664
  }),
14612
14665
  onRecordError: (error48) => {
14613
14666
  logger?.error?.(`Cowtail inbound session record failed: ${errorMessage(error48)}`);
@@ -14619,21 +14672,141 @@ async function dispatchCowtailActionTurn(params) {
14619
14672
  });
14620
14673
  return !dispatchFailed;
14621
14674
  }
14675
+ async function recordCowtailInboundSessionAndDispatchReply(params) {
14676
+ await params.runtime.channel.session.recordInboundSession({
14677
+ storePath: params.storePath,
14678
+ sessionKey: typeof params.ctxPayload.SessionKey === "string" ? params.ctxPayload.SessionKey : params.route.sessionKey,
14679
+ ctx: params.ctxPayload,
14680
+ onRecordError: params.onRecordError
14681
+ });
14682
+ const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
14683
+ cfg: params.cfg,
14684
+ agentId: params.agentId,
14685
+ channel: params.channel,
14686
+ accountId: params.accountId
14687
+ });
14688
+ await params.runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
14689
+ ctx: params.ctxPayload,
14690
+ cfg: params.cfg,
14691
+ dispatcherOptions: {
14692
+ ...replyPipeline,
14693
+ deliver: async (payload, info) => {
14694
+ try {
14695
+ await params.deliver(normalizeCowtailReplyPayload(payload), info, payload);
14696
+ } catch (error48) {
14697
+ params.onDispatchError(error48, { kind: info?.kind ?? "final" });
14698
+ }
14699
+ },
14700
+ onError: params.onDispatchError
14701
+ },
14702
+ replyOptions: {
14703
+ onModelSelected
14704
+ }
14705
+ });
14706
+ }
14622
14707
  async function deliverCowtailReply(params) {
14708
+ if (params.streamState.failed) {
14709
+ return;
14710
+ }
14623
14711
  const text = params.payload.text;
14712
+ const kind = params.info?.kind ?? "final";
14713
+ const links = resolveCowtailReplyLinks(params.payload);
14714
+ if (kind === "tool") {
14715
+ const { toolCall, summary } = buildCowtailToolCall({
14716
+ payload: params.payload,
14717
+ rawPayload: params.rawPayload,
14718
+ streamState: params.streamState
14719
+ });
14720
+ params.streamState.toolCalls = [...params.streamState.toolCalls, toolCall];
14721
+ const messageText = params.streamState.text || summary || "Tool activity";
14722
+ if (!params.streamState.messageId) {
14723
+ const result = await params.client.sendOpenClawMessage({
14724
+ type: "openclaw_message",
14725
+ sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14726
+ title: params.thread.title,
14727
+ text: messageText,
14728
+ authorLabel: "OpenClaw",
14729
+ links,
14730
+ toolCalls: params.streamState.toolCalls,
14731
+ actions: [],
14732
+ deliveryState: "pending"
14733
+ });
14734
+ params.streamState.messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14735
+ return;
14736
+ }
14737
+ await params.client.sendOpenClawMessageUpdate({
14738
+ type: "openclaw_message_update",
14739
+ messageId: params.streamState.messageId,
14740
+ text: messageText,
14741
+ links,
14742
+ toolCalls: params.streamState.toolCalls,
14743
+ deliveryState: "pending"
14744
+ });
14745
+ return;
14746
+ }
14624
14747
  if (typeof text !== "string" || !text.trim()) {
14625
14748
  return;
14626
14749
  }
14750
+ if (kind === "block") {
14751
+ params.streamState.text = appendReplyBlock(params.streamState.text, text);
14752
+ if (!params.streamState.messageId) {
14753
+ const result = await params.client.sendOpenClawMessage({
14754
+ type: "openclaw_message",
14755
+ sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14756
+ title: params.thread.title,
14757
+ text: params.streamState.text,
14758
+ authorLabel: "OpenClaw",
14759
+ links,
14760
+ toolCalls: params.streamState.toolCalls,
14761
+ actions: [],
14762
+ deliveryState: "pending"
14763
+ });
14764
+ params.streamState.messageId = recordCreatedOpenClawMessageId(params.streamState, result);
14765
+ return;
14766
+ }
14767
+ await params.client.sendOpenClawMessageUpdate({
14768
+ type: "openclaw_message_update",
14769
+ messageId: params.streamState.messageId,
14770
+ text: params.streamState.text,
14771
+ links,
14772
+ toolCalls: params.streamState.toolCalls,
14773
+ deliveryState: "pending"
14774
+ });
14775
+ return;
14776
+ }
14777
+ if (params.streamState.messageId) {
14778
+ params.streamState.text = text;
14779
+ await params.client.sendOpenClawMessageUpdate({
14780
+ type: "openclaw_message_update",
14781
+ messageId: params.streamState.messageId,
14782
+ text,
14783
+ links,
14784
+ toolCalls: params.streamState.toolCalls,
14785
+ actions: [],
14786
+ deliveryState: "sent"
14787
+ });
14788
+ return;
14789
+ }
14627
14790
  await params.client.sendOpenClawMessage({
14628
14791
  type: "openclaw_message",
14629
14792
  sessionKey: params.thread.sessionKey ?? params.route.sessionKey,
14630
14793
  title: params.thread.title,
14631
14794
  text,
14632
14795
  authorLabel: "OpenClaw",
14633
- links: resolveCowtailReplyLinks(params.payload),
14634
- actions: []
14796
+ links,
14797
+ toolCalls: params.streamState.toolCalls,
14798
+ actions: [],
14799
+ deliveryState: "sent"
14635
14800
  });
14636
14801
  }
14802
+ function recordCreatedOpenClawMessageId(streamState, result) {
14803
+ const messageId = result.payload?.messageId;
14804
+ if (typeof messageId === "string" && messageId.trim()) {
14805
+ return messageId;
14806
+ }
14807
+ streamState.failed = true;
14808
+ throw new Error("Cowtail realtime ack missing messageId for streamed OpenClaw reply");
14809
+ }
14637
14810
  function resolveCowtailReplyLinks(payload) {
14638
14811
  const urls = [
14639
14812
  ...Array.isArray(payload.mediaUrls) ? payload.mediaUrls : [],
@@ -14644,6 +14817,101 @@ function resolveCowtailReplyLinks(payload) {
14644
14817
  url: url2
14645
14818
  }));
14646
14819
  }
14820
+ function normalizeCowtailReplyPayload(payload) {
14821
+ if (!payload || typeof payload !== "object") {
14822
+ return {};
14823
+ }
14824
+ return normalizeOutboundReplyPayload(payload);
14825
+ }
14826
+ function buildCowtailToolCall(params) {
14827
+ const rawToolCall = readRawToolCall(params.rawPayload);
14828
+ const summary = resolveToolCallSummary(params.payload, rawToolCall);
14829
+ const index = params.streamState.toolCalls.length + 1;
14830
+ const toolCall = {
14831
+ id: rawToolCall.id ?? `tool-${index}`,
14832
+ name: rawToolCall.name ?? "tool_result",
14833
+ ...rawToolCall.args ? { args: rawToolCall.args } : {},
14834
+ result: rawToolCall.result !== undefined ? rawToolCall.result : params.payload.text ?? params.payload,
14835
+ status: rawToolCall.status ?? "complete",
14836
+ ...rawToolCall.startedAt !== undefined ? { startedAt: rawToolCall.startedAt } : {},
14837
+ completedAt: rawToolCall.completedAt ?? Date.now(),
14838
+ insertedAtContentLength: params.streamState.text.length,
14839
+ contentSnapshotAtStart: params.streamState.text
14840
+ };
14841
+ return {
14842
+ toolCall,
14843
+ ...summary ? { summary } : {}
14844
+ };
14845
+ }
14846
+ function readRawToolCall(payload) {
14847
+ if (!payload || typeof payload !== "object") {
14848
+ return {};
14849
+ }
14850
+ const candidate = payload;
14851
+ const channelData = candidate.channelData;
14852
+ const toolCall = channelData && typeof channelData === "object" ? channelData.toolCall : undefined;
14853
+ const source = toolCall && typeof toolCall === "object" ? toolCall : candidate;
14854
+ const name = readString(source.name) ?? readString(source.toolName) ?? readString(source.tool);
14855
+ const id = readString(source.id) ?? readString(source.toolCallId);
14856
+ const status = readToolCallStatus(source.status);
14857
+ const args = readRecord(source.args) ?? readRecord(source.input);
14858
+ const startedAt = readTimestamp(source.startedAt);
14859
+ const completedAt = readTimestamp(source.completedAt);
14860
+ const insertedAtContentLength = readNonnegativeInteger(source.insertedAtContentLength);
14861
+ const contentSnapshotAtStart = readString(source.contentSnapshotAtStart);
14862
+ const result = source.result ?? source.output;
14863
+ return {
14864
+ ...id ? { id } : {},
14865
+ ...name ? { name } : {},
14866
+ ...args ? { args } : {},
14867
+ ...result !== undefined ? { result } : {},
14868
+ ...status ? { status } : {},
14869
+ ...startedAt !== undefined ? { startedAt } : {},
14870
+ ...completedAt !== undefined ? { completedAt } : {},
14871
+ ...insertedAtContentLength !== undefined ? { insertedAtContentLength } : {},
14872
+ ...contentSnapshotAtStart !== undefined ? { contentSnapshotAtStart } : {}
14873
+ };
14874
+ }
14875
+ function resolveToolCallSummary(payload, toolCall) {
14876
+ if (typeof payload.text === "string" && payload.text.trim()) {
14877
+ return payload.text.trim().split(`
14878
+ `)[0];
14879
+ }
14880
+ if (toolCall.name) {
14881
+ return toolCall.name;
14882
+ }
14883
+ return;
14884
+ }
14885
+ function readString(value) {
14886
+ return typeof value === "string" && value.trim() ? value : undefined;
14887
+ }
14888
+ function readRecord(value) {
14889
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
14890
+ return;
14891
+ }
14892
+ return value;
14893
+ }
14894
+ function readToolCallStatus(value) {
14895
+ return value === "pending" || value === "running" || value === "complete" || value === "error" ? value : undefined;
14896
+ }
14897
+ function readTimestamp(value) {
14898
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
14899
+ }
14900
+ function readNonnegativeInteger(value) {
14901
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
14902
+ }
14903
+ function appendReplyBlock(current, next) {
14904
+ if (!next.trim()) {
14905
+ return current;
14906
+ }
14907
+ if (!current) {
14908
+ return next;
14909
+ }
14910
+ if (next.startsWith(current)) {
14911
+ return next;
14912
+ }
14913
+ return `${current}${next}`;
14914
+ }
14647
14915
  function buildCowtailInboundBody(params) {
14648
14916
  const cfg = params.runtime.config.loadConfig();
14649
14917
  const storePath = params.runtime.agent.session.resolveStorePath(cfg.session?.store, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maudecode/openclaw-cowtail",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "Cowtail channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "repository": {