@stagewhisper/stagewhisper 0.38.0 → 0.40.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.
@@ -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.38.0",
5
+ "version": "0.40.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.38.0",
3
+ "version": "0.40.0",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin that connects StageWhisper live calls to your AI assistant",
6
6
  "license": "MIT",
package/src/channel.ts CHANGED
@@ -4,7 +4,6 @@ import {
4
4
  DEFAULT_ACCOUNT_ID,
5
5
  } from "openclaw/plugin-sdk/core";
6
6
  import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
7
- import { StageWhisperClient } from "./client.js";
8
7
 
9
8
  export type StageWhisperAccount = {
10
9
  accountId: string | null;
@@ -163,23 +162,15 @@ export const stagewhisperPlugin = createChatChannelPlugin<StageWhisperAccount>(
163
162
  attachedResults: {
164
163
  channel: "stagewhisper",
165
164
  sendText: async (ctx) => {
166
- const account = resolveAccount(ctx.cfg, ctx.accountId);
167
- const client = new StageWhisperClient(
168
- account.apiBaseUrl,
169
- account.integrationId,
170
- account.relayToken,
171
- );
172
-
173
- const target = (ctx as Record<string, unknown>).to as string | undefined;
174
- const threadId = ctx.threadId as string | undefined;
175
- const raw = target ?? threadId ?? "";
176
- const taskId = raw.replace(/^sw-task-/, "");
177
- if (!taskId) {
178
- return { messageId: `sw-noop-${Date.now()}`, ok: true };
165
+ const target = (ctx as Record<string, unknown>).to as string | undefined ?? "";
166
+ if (target.startsWith("sw-session-")) {
167
+ return { messageId: `sw-relay-ack-${Date.now()}`, ok: true };
179
168
  }
180
-
181
- await client.postReply(taskId, ctx.text);
182
- return { messageId: `sw-reply-${taskId}-${Date.now()}`, ok: true };
169
+ console.warn(
170
+ `[stagewhisper] sendText called for unrecognised target "${target}"; ` +
171
+ `StageWhisper channel is inbound-only task replies are routed by the relay service`,
172
+ );
173
+ return { messageId: `sw-dropped-${Date.now()}`, ok: false };
183
174
  },
184
175
  },
185
176
  },
package/src/service.ts CHANGED
@@ -90,6 +90,27 @@ export function createRelayService(api: OpenClawPluginApi) {
90
90
  await client.updateTaskStatus(task.id, status);
91
91
  }
92
92
 
93
+ function extractContentFromMessage(
94
+ msg: Record<string, unknown>,
95
+ ): string | null {
96
+ const content = msg["content"];
97
+ if (typeof content === "string") return content;
98
+
99
+ if (Array.isArray(content)) {
100
+ for (const part of content) {
101
+ if (
102
+ typeof part === "object" &&
103
+ part !== null &&
104
+ (part as Record<string, unknown>)["type"] === "text" &&
105
+ typeof (part as Record<string, unknown>)["text"] === "string"
106
+ ) {
107
+ return (part as Record<string, unknown>)["text"] as string;
108
+ }
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+
93
114
  function extractAssistantReply(
94
115
  messages: unknown[],
95
116
  ): string | null {
@@ -98,20 +119,39 @@ export function createRelayService(api: OpenClawPluginApi) {
98
119
  if (!msg) continue;
99
120
  const role = msg["role"];
100
121
  if (role !== "assistant" && role !== "model") continue;
122
+ return extractContentFromMessage(msg);
123
+ }
124
+ return null;
125
+ }
101
126
 
102
- const content = msg["content"];
103
- if (typeof content === "string") return content;
104
-
105
- if (Array.isArray(content)) {
106
- for (const part of content) {
107
- if (
108
- typeof part === "object" &&
109
- part !== null &&
110
- (part as Record<string, unknown>)["type"] === "text" &&
111
- typeof (part as Record<string, unknown>)["text"] === "string"
112
- ) {
113
- return (part as Record<string, unknown>)["text"] as string;
127
+ async function extractReplyForTask(
128
+ sessionKey: string,
129
+ taskId: string,
130
+ maxAttempts: number = 3,
131
+ ): Promise<string | null> {
132
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
133
+ if (attempt > 0) {
134
+ await new Promise((r) => setTimeout(r, 1500));
135
+ }
136
+ const session = await api.runtime.subagent.getSessionMessages({
137
+ sessionKey,
138
+ limit: 50,
139
+ });
140
+ const messages = session.messages as Record<string, unknown>[];
141
+
142
+ for (let i = 0; i < messages.length; i++) {
143
+ const msg = messages[i];
144
+ if (msg["role"] !== "user") continue;
145
+ const text = extractContentFromMessage(msg) ?? "";
146
+ if (!text.includes(`StageWhisper task: ${taskId}`)) continue;
147
+
148
+ for (let j = i + 1; j < messages.length; j++) {
149
+ const reply = messages[j];
150
+ const role = reply["role"];
151
+ if (role === "assistant" || role === "model") {
152
+ return extractContentFromMessage(reply);
114
153
  }
154
+ if (role === "user") break;
115
155
  }
116
156
  }
117
157
  }
@@ -143,14 +183,16 @@ export function createRelayService(api: OpenClawPluginApi) {
143
183
  try {
144
184
  await updateStatus(client, task, "delivered");
145
185
  } catch (err) {
146
- api.logger.warn(`Failed to mark task as delivered: ${err}`);
186
+ api.logger.warn(`Failed to mark task as delivered, skipping to prevent duplicates: ${err}`);
187
+ return;
147
188
  }
148
189
 
149
190
  const messageContent = buildTaskMessage(task);
191
+ const peerId = `sw-session-${task.session_id}`;
150
192
  const sessionKey = buildAgentSessionKey({
151
193
  agentId: "default",
152
194
  channel: "stagewhisper",
153
- peer: { kind: "direct", id: `sw-task-${task.id}` },
195
+ peer: { kind: "direct", id: peerId },
154
196
  });
155
197
 
156
198
  const result = await api.runtime.subagent.run({
@@ -174,8 +216,14 @@ export function createRelayService(api: OpenClawPluginApi) {
174
216
  .waitForRun({ runId: result.runId, timeoutMs: 120_000 })
175
217
  .then(async (waitResult) => {
176
218
  if (waitResult.status === "ok") {
219
+ const reply = await extractReplyForTask(sessionKey, task.id);
220
+ if (reply) {
221
+ await client.postReply(task.id, reply);
222
+ api.logger.info(`Task ${task.id} completed with reply`);
223
+ } else {
224
+ api.logger.warn(`Task ${task.id} completed but no reply found`);
225
+ }
177
226
  await updateStatus(client, task, "completed").catch(() => {});
178
- api.logger.info(`Task ${task.id} completed`);
179
227
  } else {
180
228
  api.logger.error(
181
229
  `Agent run failed for task ${task.id}: ${waitResult.error}`,
@@ -183,8 +231,9 @@ export function createRelayService(api: OpenClawPluginApi) {
183
231
  await updateStatus(client, task, "failed").catch(() => {});
184
232
  }
185
233
  })
186
- .catch((err) => {
234
+ .catch(async (err) => {
187
235
  api.logger.error(`Failed to track task ${task.id}: ${err}`);
236
+ await updateStatus(client, task, "failed").catch(() => {});
188
237
  });
189
238
  }
190
239