@stagewhisper/stagewhisper 0.44.0 → 0.46.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.44.0",
5
+ "version": "0.46.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.44.0",
3
+ "version": "0.46.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/plugin-main.ts CHANGED
@@ -3,6 +3,34 @@ import { stagewhisperPlugin } from "./src/channel.js";
3
3
  import { setRuntime } from "./src/runtime.js";
4
4
  import { createRelayService } from "./src/service.js";
5
5
 
6
+ async function ensureResponsesEndpoint(api: Parameters<Parameters<typeof definePluginEntry>[0]["register"]>[0]): Promise<void> {
7
+ try {
8
+ const cfg = await api.runtime.config.loadConfig();
9
+ const gw = ((cfg as Record<string, unknown>)["gateway"] ?? {}) as Record<string, unknown>;
10
+ const http = (gw["http"] ?? {}) as Record<string, unknown>;
11
+ const endpoints = (http["endpoints"] ?? {}) as Record<string, unknown>;
12
+ const responses = (endpoints["responses"] ?? {}) as Record<string, unknown>;
13
+
14
+ if (responses["enabled"] === true) return;
15
+
16
+ const auth = (gw["auth"] ?? {}) as Record<string, unknown>;
17
+ if (auth["mode"] === "none" && !auth["token"] && !auth["password"]) return;
18
+
19
+ responses["enabled"] = true;
20
+ endpoints["responses"] = responses;
21
+ http["endpoints"] = endpoints;
22
+ gw["http"] = http;
23
+ (cfg as Record<string, unknown>)["gateway"] = gw;
24
+
25
+ await api.runtime.config.writeConfigFile(cfg);
26
+ api.logger.info(
27
+ "Enabled gateway.http.endpoints.responses for StageWhisper reasoning. Restart the gateway for it to take effect.",
28
+ );
29
+ } catch {
30
+ // best-effort — reasoning-check will surface the real error
31
+ }
32
+ }
33
+
6
34
  export default definePluginEntry({
7
35
  id: "stagewhisper",
8
36
  name: "StageWhisper",
@@ -10,6 +38,8 @@ export default definePluginEntry({
10
38
  register(api) {
11
39
  api.registerChannel({ plugin: stagewhisperPlugin });
12
40
 
41
+ ensureResponsesEndpoint(api);
42
+
13
43
  api.registerCli(
14
44
  ({ program }) => {
15
45
  const sw = program
@@ -27,11 +57,13 @@ export default definePluginEntry({
27
57
  "https://api.stagewhisper.io",
28
58
  )
29
59
  .option("--label <label>", "Label for this OpenClaw host", "OpenClaw")
60
+ .option("--no-enable-responses", "Skip enabling the gateway OpenResponses HTTP API")
30
61
  .action(
31
62
  async (opts: {
32
63
  code: string;
33
64
  apiUrl: string;
34
65
  label?: string;
66
+ enableResponses: boolean;
35
67
  }) => {
36
68
  const { StageWhisperClient } = await import("./src/client.js");
37
69
  const client = new StageWhisperClient(opts.apiUrl, "", "");
@@ -65,13 +97,33 @@ export default definePluginEntry({
65
97
  };
66
98
  (cfg as Record<string, unknown>)["channels"] = channels;
67
99
 
100
+ if (opts.enableResponses) {
101
+ const gw = ((cfg as Record<string, unknown>)["gateway"] ?? {}) as Record<string, unknown>;
102
+ const gwAuth = (gw["auth"] as Record<string, unknown>) ?? {};
103
+ const authMode = gwAuth["mode"] as string | undefined;
104
+ const hasToken = typeof gwAuth["token"] === "string" && (gwAuth["token"] as string).length > 0;
105
+
106
+ if (authMode === "none" && !hasToken) {
107
+ console.warn(" ⚠ gateway.auth.mode is 'none' with no token — skipping HTTP API enablement.");
108
+ console.warn(" Set a gateway auth token first for reasoning to work.\n");
109
+ } else {
110
+ const http = (gw["http"] ?? {}) as Record<string, unknown>;
111
+ const endpoints = (http["endpoints"] ?? {}) as Record<string, unknown>;
112
+ const responses = (endpoints["responses"] ?? {}) as Record<string, unknown>;
113
+ responses["enabled"] = true;
114
+ endpoints["responses"] = responses;
115
+ http["endpoints"] = endpoints;
116
+ gw["http"] = http;
117
+ (cfg as Record<string, unknown>)["gateway"] = gw;
118
+ }
119
+ }
120
+
68
121
  await api.runtime.config.writeConfigFile(cfg);
69
122
 
70
123
  console.log(
71
124
  `\n✓ Paired with StageWhisper (${result.label})`,
72
125
  );
73
- console.log(" Config saved automatically.\n");
74
- console.log(" Restart the gateway to activate the relay:\n");
126
+ console.log(" Config saved. Restart the gateway to activate:\n");
75
127
  console.log(" openclaw gateway restart\n");
76
128
  } catch (err) {
77
129
  console.error(`\n✗ Pairing failed: ${err}\n`);
@@ -84,7 +136,8 @@ export default definePluginEntry({
84
136
  .description(
85
137
  "Remove StageWhisper pairing (run before `openclaw plugins uninstall`)",
86
138
  )
87
- .action(async () => {
139
+ .option("--keep-responses", "Keep the OpenResponses HTTP API enabled after unpair")
140
+ .action(async (opts: { keepResponses?: boolean }) => {
88
141
  try {
89
142
  const cfg = await api.runtime.config.loadConfig();
90
143
  const plugins = (cfg as Record<string, unknown>)["plugins"] as Record<string, unknown> ?? {};
@@ -102,6 +155,20 @@ export default definePluginEntry({
102
155
  }
103
156
  }
104
157
 
158
+ if (!opts.keepResponses) {
159
+ const gw = (cfg as Record<string, unknown>)["gateway"] as Record<string, unknown> | undefined;
160
+ const http = gw?.["http"] as Record<string, unknown> | undefined;
161
+ const endpoints = http?.["endpoints"] as Record<string, unknown> | undefined;
162
+ const responses = endpoints?.["responses"] as Record<string, unknown> | undefined;
163
+ if (responses?.["enabled"] === true) {
164
+ delete responses["enabled"];
165
+ if (Object.keys(responses).length === 0 && endpoints) delete endpoints["responses"];
166
+ if (endpoints && Object.keys(endpoints).length === 0 && http) delete http["endpoints"];
167
+ if (http && Object.keys(http).length === 0 && gw) delete gw["http"];
168
+ console.log(" ℹ Disabled gateway.http.endpoints.responses. Use --keep-responses to preserve it.");
169
+ }
170
+ }
171
+
105
172
  await api.runtime.config.writeConfigFile(cfg);
106
173
  console.log("\n✓ StageWhisper unpaired.");
107
174
  console.log(" Config cleaned. You can now safely uninstall:\n");
@@ -116,7 +183,7 @@ export default definePluginEntry({
116
183
  .description("Test reasoning capability against the local OpenResponses endpoint")
117
184
  .option("--model <model>", "Model to use (omit to use your configured default)", "openclaw/default")
118
185
  .action(async (opts: { model: string }) => {
119
- const { callOpenResponses } = await import("./src/openresponses.js");
186
+ const { callOpenResponses, isResponsesEndpointEnabled } = await import("./src/openresponses.js");
120
187
  const modelLabel = opts.model === "openclaw/default" ? "default (configured)" : opts.model;
121
188
 
122
189
  const cfg = api.config as Record<string, unknown>;
@@ -124,8 +191,24 @@ export default definePluginEntry({
124
191
  const auth = (gw?.auth as Record<string, unknown>) ?? {};
125
192
  const port = Number(gw?.port) || 18789;
126
193
  const hasToken = typeof auth?.token === "string" && auth.token.length > 0;
127
- console.log(`Gateway: 127.0.0.1:${port}, auth token: ${hasToken ? "present" : "MISSING"}`);
128
- console.log(`Testing reasoning with model: ${modelLabel}`);
194
+ const responsesEnabled = isResponsesEndpointEnabled(api);
195
+
196
+ console.log("Preflight checks:");
197
+ console.log(` Gateway port: ${port}`);
198
+ console.log(` Auth token: ${hasToken ? "✓ present" : "✗ MISSING"}`);
199
+ console.log(` responses.enabled: ${responsesEnabled ? "✓ true" : "✗ false"}`);
200
+
201
+ if (!responsesEnabled) {
202
+ console.warn("\n⚠ responses.enabled is false in the running config.");
203
+ console.warn(" The plugin auto-enables it on startup — restart the gateway if you haven't:");
204
+ console.warn(" openclaw gateway restart\n");
205
+ }
206
+
207
+ if (!hasToken) {
208
+ console.warn("\n⚠ No gateway auth token found — request may be rejected.\n");
209
+ }
210
+
211
+ console.log(`\nTesting reasoning with model: ${modelLabel}`);
129
212
  console.log("Sending test request to local /v1/responses ...");
130
213
 
131
214
  const start = Date.now();
@@ -11,13 +11,20 @@ function resolveGatewayConfig(api: OpenClawPluginApi): GatewayConfig {
11
11
  const explicitUrl = typeof gw?.url === "string" ? gw.url : null;
12
12
  const url = (explicitUrl ?? `http://127.0.0.1:${port}`).replace(/\/+$/, "");
13
13
 
14
- const token =
15
- (typeof auth?.token === "string" ? auth.token : null) ??
16
- (typeof process !== "undefined" ? process.env.OPENCLAW_GATEWAY_TOKEN ?? null : null);
14
+ const token = typeof auth?.token === "string" ? auth.token : null;
17
15
 
18
16
  return { url, apiKey: token };
19
17
  }
20
18
 
19
+ export function isResponsesEndpointEnabled(api: OpenClawPluginApi): boolean {
20
+ const cfg = api.config as Record<string, unknown>;
21
+ const gw = (cfg?.gateway as Record<string, unknown>) ?? {};
22
+ const http = (gw?.http as Record<string, unknown>) ?? {};
23
+ const endpoints = (http?.endpoints as Record<string, unknown>) ?? {};
24
+ const responses = (endpoints?.responses as Record<string, unknown>) ?? {};
25
+ return responses?.enabled === true;
26
+ }
27
+
21
28
  export async function callOpenResponses(
22
29
  api: OpenClawPluginApi,
23
30
  requestBody: OpenResponsesCreateResponseRequestBody,
@@ -46,6 +53,13 @@ export async function callOpenResponses(
46
53
 
47
54
  if (!response.ok) {
48
55
  const body = await response.text().catch(() => "");
56
+ if (response.status === 404) {
57
+ throw new OpenResponsesError(
58
+ "POST /v1/responses returned 404 — the OpenResponses HTTP API is most likely disabled. " +
59
+ 'Enable it in OpenClaw config: gateway.http.endpoints.responses.enabled = true, then restart the gateway.',
60
+ response.status,
61
+ );
62
+ }
49
63
  throw new OpenResponsesError(`POST /v1/responses returned ${response.status}: ${body}`, response.status);
50
64
  }
51
65
  return (await response.json()) as OpenResponsesResponseResource;
package/src/service.ts CHANGED
@@ -7,6 +7,7 @@ import type { StageWhisperAccount } from "./channel.js";
7
7
  import { resolveAccount } from "./channel.js";
8
8
  import { createHealthTracker } from "./health.js";
9
9
  import { executeReasoningJob, probeOpenResponses, type ReasoningJobEnvelope } from "./reasoning.js";
10
+ import { isResponsesEndpointEnabled } from "./openresponses.js";
10
11
 
11
12
  const HEARTBEAT_INTERVAL_MS = 30_000;
12
13
  const RECONNECT_BASE_MS = 1_000;
@@ -497,6 +498,13 @@ export function createRelayService(api: OpenClawPluginApi) {
497
498
 
498
499
  state.running = true;
499
500
 
501
+ if (!isResponsesEndpointEnabled(api)) {
502
+ api.logger.warn(
503
+ "gateway.http.endpoints.responses.enabled is not true — reasoning jobs will fail with 404. " +
504
+ "Enable it in config and restart the gateway, or re-pair with: openclaw stagewhisper pair --code <CODE> --enable-responses",
505
+ );
506
+ }
507
+
500
508
  api.logger.info("Probing /v1/responses to verify local AI connectivity...");
501
509
  const probe = await probeOpenResponses(api);
502
510
  if (probe.ok) {