@stagewhisper/stagewhisper 0.45.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.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/plugin-main.ts +89 -6
- package/src/openresponses.ts +16 -0
- package/src/service.ts +8 -0
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
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
|
|
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
|
-
.
|
|
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
|
-
|
|
128
|
-
|
|
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();
|
package/src/openresponses.ts
CHANGED
|
@@ -16,6 +16,15 @@ function resolveGatewayConfig(api: OpenClawPluginApi): GatewayConfig {
|
|
|
16
16
|
return { url, apiKey: token };
|
|
17
17
|
}
|
|
18
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
|
+
|
|
19
28
|
export async function callOpenResponses(
|
|
20
29
|
api: OpenClawPluginApi,
|
|
21
30
|
requestBody: OpenResponsesCreateResponseRequestBody,
|
|
@@ -44,6 +53,13 @@ export async function callOpenResponses(
|
|
|
44
53
|
|
|
45
54
|
if (!response.ok) {
|
|
46
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
|
+
}
|
|
47
63
|
throw new OpenResponsesError(`POST /v1/responses returned ${response.status}: ${body}`, response.status);
|
|
48
64
|
}
|
|
49
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) {
|