@stagewhisper/stagewhisper 0.46.0 → 0.47.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.46.0",
5
+ "version": "0.47.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.46.0",
3
+ "version": "0.47.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
@@ -211,6 +211,27 @@ export default definePluginEntry({
211
211
  console.log(`\nTesting reasoning with model: ${modelLabel}`);
212
212
  console.log("Sending test request to local /v1/responses ...");
213
213
 
214
+ const testSchema = {
215
+ type: "object",
216
+ properties: {
217
+ signals: {
218
+ type: "array",
219
+ items: {
220
+ type: "object",
221
+ properties: {
222
+ severity: { type: "string", enum: ["green", "orange", "red"] },
223
+ message: { type: "string" },
224
+ },
225
+ required: ["severity", "message"],
226
+ additionalProperties: false,
227
+ },
228
+ },
229
+ no_signal_reason: { type: "string" },
230
+ },
231
+ required: ["signals", "no_signal_reason"],
232
+ additionalProperties: false,
233
+ };
234
+
214
235
  const start = Date.now();
215
236
  try {
216
237
  const result = await callOpenResponses(api, {
@@ -219,34 +240,14 @@ export default definePluginEntry({
219
240
  transcript: "Candidate: I think we should use Redis for caching.",
220
241
  playbook_guidance: "Evaluate technical decisions",
221
242
  }),
222
- text: {
223
- format: {
224
- type: "json_schema" as const,
225
- name: "reasoning_test",
226
- schema: {
227
- type: "object",
228
- properties: {
229
- signals: {
230
- type: "array",
231
- items: {
232
- type: "object",
233
- properties: {
234
- severity: { type: "string", enum: ["green", "orange", "red"] },
235
- message: { type: "string" },
236
- },
237
- required: ["severity", "message"],
238
- additionalProperties: false,
239
- },
240
- },
241
- no_signal_reason: { type: "string" },
242
- },
243
- required: ["signals", "no_signal_reason"],
244
- additionalProperties: false,
245
- },
246
- strict: true,
247
- },
248
- },
249
- temperature: 0.2,
243
+ instructions: [
244
+ 'You are a structured reasoning engine for the "reasoning_test" task.',
245
+ "You MUST respond with a JSON object conforming to this schema.",
246
+ "Output ONLY valid JSON. No markdown fences, no explanation, no extra text.",
247
+ "",
248
+ "JSON Schema:",
249
+ JSON.stringify(testSchema, null, 2),
250
+ ].join("\n"),
250
251
  max_output_tokens: 1024,
251
252
  });
252
253
 
@@ -260,17 +261,18 @@ export default definePluginEntry({
260
261
  }
261
262
 
262
263
  const output = result.output;
263
- const text = Array.isArray(output)
264
+ const msgItem = Array.isArray(output)
264
265
  ? (output.find((o) => o.type === "message") as Record<string, unknown> | undefined)
265
266
  : null;
266
- const textContent = text
267
- ? ((text.content as Array<Record<string, unknown>>)?.find(
267
+ const textContent = msgItem
268
+ ? ((msgItem.content as Array<Record<string, unknown>>)?.find(
268
269
  (c) => c.type === "output_text",
269
270
  )?.text as string | undefined)
270
271
  : null;
271
272
  if (textContent) {
273
+ const cleaned = textContent.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/, "").trim();
272
274
  try {
273
- const parsed = JSON.parse(textContent);
275
+ const parsed = JSON.parse(cleaned);
274
276
  console.log(" Schema-valid JSON: ✓");
275
277
  console.log(` Output: ${JSON.stringify(parsed, null, 2)}`);
276
278
  } catch {
package/src/reasoning.ts CHANGED
@@ -19,7 +19,6 @@ export async function probeOpenResponses(
19
19
  model: "openclaw/default",
20
20
  input: "Reply with exactly: OK",
21
21
  max_output_tokens: 16,
22
- temperature: 0,
23
22
  };
24
23
 
25
24
  const controller = new AbortController();
@@ -81,6 +80,23 @@ function extractTextOutput(result: OpenResponsesResponseResource): string | null
81
80
  return null;
82
81
  }
83
82
 
83
+ function buildSchemaInstruction(schema: Record<string, unknown>, purpose: string, systemInstruction?: string): string {
84
+ const parts: string[] = [];
85
+ if (systemInstruction) {
86
+ parts.push(systemInstruction);
87
+ parts.push("");
88
+ }
89
+ parts.push(
90
+ `You are a structured reasoning engine for the "${purpose}" task.`,
91
+ "You MUST respond with a JSON object conforming to this schema.",
92
+ "Output ONLY valid JSON. No markdown fences, no explanation, no extra text.",
93
+ "",
94
+ "JSON Schema:",
95
+ JSON.stringify(schema, null, 2),
96
+ );
97
+ return parts.join("\n");
98
+ }
99
+
84
100
  export async function executeReasoningJob(
85
101
  api: OpenClawPluginApi,
86
102
  job: ReasoningJobEnvelope,
@@ -109,17 +125,12 @@ export async function executeReasoningJob(
109
125
  const requestBody: OpenResponsesCreateResponseRequestBody = {
110
126
  model,
111
127
  input: JSON.stringify(job.payload),
112
- instructions: (job.payload.system_instruction as string) ?? undefined,
113
- text: {
114
- format: {
115
- type: "json_schema",
116
- name: `reasoning_${job.purpose}`,
117
- schema: job.response_schema,
118
- strict: true,
119
- },
120
- },
128
+ instructions: buildSchemaInstruction(
129
+ job.response_schema,
130
+ job.purpose,
131
+ (job.payload.system_instruction as string) ?? undefined,
132
+ ),
121
133
  max_output_tokens: 4096,
122
- temperature: 0.2,
123
134
  };
124
135
 
125
136
  const controller = new AbortController();
@@ -131,8 +142,9 @@ export async function executeReasoningJob(
131
142
 
132
143
  let parsed: Record<string, unknown> | null = null;
133
144
  if (textOutput) {
145
+ const cleaned = textOutput.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/, "").trim();
134
146
  try {
135
- parsed = JSON.parse(textOutput) as Record<string, unknown>;
147
+ parsed = JSON.parse(cleaned) as Record<string, unknown>;
136
148
  } catch {
137
149
  return {
138
150
  job_id: job.job_id,