@stagewhisper/stagewhisper 0.59.0 → 0.60.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
@@ -119,6 +119,54 @@ var init_client = __esm({
119
119
  throw new Error(`Chat reply failed (${res.status}): ${text}`);
120
120
  }
121
121
  }
122
+ async startChatReply(userMessageId) {
123
+ const res = await fetch(
124
+ `${this.baseUrl}/api/v1/openclaw/chat/messages/${userMessageId}/reply/start`,
125
+ {
126
+ method: "POST",
127
+ headers: this.headers()
128
+ }
129
+ );
130
+ if (!res.ok) {
131
+ const text = await res.text();
132
+ throw new Error(`Chat reply start failed (${res.status}): ${text}`);
133
+ }
134
+ return await res.json();
135
+ }
136
+ async postChatReplyDelta(assistantMessageId, delta) {
137
+ const res = await fetch(
138
+ `${this.baseUrl}/api/v1/openclaw/chat/messages/${assistantMessageId}/reply/delta`,
139
+ {
140
+ method: "POST",
141
+ headers: this.headers(),
142
+ body: JSON.stringify({ delta })
143
+ }
144
+ );
145
+ if (!res.ok) {
146
+ const text = await res.text();
147
+ throw new Error(`Chat reply delta failed (${res.status}): ${text}`);
148
+ }
149
+ }
150
+ async completeChatReply(assistantMessageId, options) {
151
+ const body = {
152
+ status: options?.status ?? "completed"
153
+ };
154
+ if (options?.content !== void 0) body.content = options.content;
155
+ if (options?.errorCode) body.error_code = options.errorCode;
156
+ if (options?.errorMessage) body.error_message = options.errorMessage;
157
+ const res = await fetch(
158
+ `${this.baseUrl}/api/v1/openclaw/chat/messages/${assistantMessageId}/reply/complete`,
159
+ {
160
+ method: "POST",
161
+ headers: this.headers(),
162
+ body: JSON.stringify(body)
163
+ }
164
+ );
165
+ if (!res.ok) {
166
+ const text = await res.text();
167
+ throw new Error(`Chat reply complete failed (${res.status}): ${text}`);
168
+ }
169
+ }
122
170
  async heartbeat(capabilities) {
123
171
  const res = await fetch(
124
172
  `${this.baseUrl}/api/v1/openclaw/integrations/${this.integrationId}/heartbeat`,
@@ -4898,6 +4946,15 @@ function createRelayService(api) {
4898
4946
  const decoratedPrompt = `${plaintextContent}
4899
4947
 
4900
4948
  [StageWhisper chat: ${userMessageId}]`;
4949
+ if (envelopeKey === null) {
4950
+ await handleChatMessageStreaming(
4951
+ client,
4952
+ sessionKey,
4953
+ userMessageId,
4954
+ decoratedPrompt
4955
+ );
4956
+ return;
4957
+ }
4901
4958
  try {
4902
4959
  const result = await api.runtime.subagent.run({
4903
4960
  sessionKey,
@@ -4948,6 +5005,134 @@ function createRelayService(api) {
4948
5005
  }
4949
5006
  }
4950
5007
  }
5008
+ async function handleChatMessageStreaming(client, sessionKey, userMessageId, decoratedPrompt) {
5009
+ let assistantId;
5010
+ try {
5011
+ const started = await client.startChatReply(userMessageId);
5012
+ assistantId = started.id;
5013
+ } catch (err) {
5014
+ const errMsg = err instanceof Error ? err.message : String(err);
5015
+ api.logger.error(
5016
+ `Chat ${userMessageId} failed to start streaming reply: ${errMsg}`
5017
+ );
5018
+ try {
5019
+ await client.postChatReply(userMessageId, "(streaming start failed)", {
5020
+ status: "errored",
5021
+ errorCode: "stream_start_failed",
5022
+ errorMessage: errMsg
5023
+ });
5024
+ } catch (postErr) {
5025
+ api.logger.error(
5026
+ `Chat ${userMessageId} fallback errored post failed: ${postErr}`
5027
+ );
5028
+ }
5029
+ return;
5030
+ }
5031
+ let ackedText = "";
5032
+ const sendDelta = async (full) => {
5033
+ if (!full || full.length <= ackedText.length) return;
5034
+ if (!full.startsWith(ackedText)) return;
5035
+ const diff = full.slice(ackedText.length);
5036
+ if (!diff) return;
5037
+ try {
5038
+ await client.postChatReplyDelta(assistantId, diff);
5039
+ ackedText = full;
5040
+ } catch (err) {
5041
+ api.logger.warn(`Chat ${userMessageId} delta post failed: ${err}`);
5042
+ }
5043
+ };
5044
+ const finalizeErrored = async (content, errorCode, errorMessage) => {
5045
+ try {
5046
+ await client.completeChatReply(assistantId, {
5047
+ status: "errored",
5048
+ content: content || void 0,
5049
+ errorCode,
5050
+ errorMessage
5051
+ });
5052
+ } catch (postErr) {
5053
+ api.logger.error(
5054
+ `Chat ${userMessageId} finalize errored failed: ${postErr}`
5055
+ );
5056
+ }
5057
+ };
5058
+ let pollDone = false;
5059
+ const pollInterval = 400;
5060
+ const pollLoop = (async () => {
5061
+ while (!pollDone) {
5062
+ await new Promise((r) => setTimeout(r, pollInterval));
5063
+ if (pollDone) break;
5064
+ try {
5065
+ const partial = await extractReplyForChatMessage(
5066
+ sessionKey,
5067
+ userMessageId,
5068
+ 1
5069
+ );
5070
+ if (partial) await sendDelta(partial);
5071
+ } catch (err) {
5072
+ api.logger.warn(`Chat ${userMessageId} poll failed: ${err}`);
5073
+ }
5074
+ }
5075
+ })();
5076
+ try {
5077
+ const result = await api.runtime.subagent.run({
5078
+ sessionKey,
5079
+ message: decoratedPrompt,
5080
+ deliver: true,
5081
+ idempotencyKey: `sw-chat-${userMessageId}`
5082
+ });
5083
+ const waitResult = await api.runtime.subagent.waitForRun({
5084
+ runId: result.runId,
5085
+ timeoutMs: 12e4
5086
+ });
5087
+ pollDone = true;
5088
+ await pollLoop;
5089
+ const finalReply = await extractReplyForChatMessage(
5090
+ sessionKey,
5091
+ userMessageId
5092
+ );
5093
+ if (waitResult.status !== "ok") {
5094
+ api.logger.error(
5095
+ `Agent run failed for chat ${userMessageId}: ${waitResult.error}`
5096
+ );
5097
+ await finalizeErrored(
5098
+ finalReply ?? ackedText,
5099
+ "agent_error",
5100
+ waitResult.error ?? "Agent run failed"
5101
+ );
5102
+ return;
5103
+ }
5104
+ if (!finalReply) {
5105
+ api.logger.warn(
5106
+ `Chat message ${userMessageId} completed but no reply found`
5107
+ );
5108
+ await finalizeErrored(
5109
+ ackedText,
5110
+ "no_reply",
5111
+ "Agent run produced no reply"
5112
+ );
5113
+ return;
5114
+ }
5115
+ try {
5116
+ await client.completeChatReply(assistantId, { content: finalReply });
5117
+ api.logger.info(`Chat message ${userMessageId} streamed`);
5118
+ } catch (completeErr) {
5119
+ const errMsg = completeErr instanceof Error ? completeErr.message : String(completeErr);
5120
+ api.logger.error(
5121
+ `Chat ${userMessageId} complete failed: ${errMsg}`
5122
+ );
5123
+ await finalizeErrored(finalReply, "complete_failed", errMsg);
5124
+ }
5125
+ } catch (err) {
5126
+ pollDone = true;
5127
+ try {
5128
+ await pollLoop;
5129
+ } catch {
5130
+ }
5131
+ const errMsg = err instanceof Error ? err.message : String(err);
5132
+ api.logger.error(`Failed to stream chat ${userMessageId}: ${errMsg}`);
5133
+ await finalizeErrored(ackedText, "execution_error", errMsg);
5134
+ }
5135
+ }
4951
5136
  async function handleReasoningJob(job, client) {
4952
5137
  const hydrated = await hydrateReasoningJobEnvelope(
4953
5138
  job,
@@ -2,7 +2,7 @@
2
2
  "id": "stagewhisper",
3
3
  "name": "StageWhisper",
4
4
  "description": "Turn live call moments into assistant tasks via StageWhisper",
5
- "version": "0.59.0",
5
+ "version": "0.60.0",
6
6
  "channels": [
7
7
  "stagewhisper"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stagewhisper/stagewhisper",
3
- "version": "0.59.0",
3
+ "version": "0.60.0",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin that connects StageWhisper live calls to your AI assistant",
6
6
  "license": "MIT",