@stagewhisper/stagewhisper 0.47.0 → 0.49.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.47.0",
5
+ "version": "0.49.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.47.0",
3
+ "version": "0.49.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
@@ -38,8 +38,6 @@ export default definePluginEntry({
38
38
  register(api) {
39
39
  api.registerChannel({ plugin: stagewhisperPlugin });
40
40
 
41
- ensureResponsesEndpoint(api);
42
-
43
41
  api.registerCli(
44
42
  ({ program }) => {
45
43
  const sw = program
@@ -336,6 +334,7 @@ export default definePluginEntry({
336
334
 
337
335
  if (api.registrationMode !== "full") return;
338
336
 
337
+ ensureResponsesEndpoint(api);
339
338
  setRuntime(api.runtime);
340
339
  const service = createRelayService(api);
341
340
  api.registerService(service);
package/src/service.ts CHANGED
@@ -302,12 +302,101 @@ export function createRelayService(api: OpenClawPluginApi) {
302
302
  }
303
303
  }
304
304
 
305
+ async function handleCapabilityProbe(
306
+ job: ReasoningJobEnvelope,
307
+ client: StageWhisperClient,
308
+ ): Promise<void> {
309
+ const correlationId = job.correlation_id;
310
+ const displayModel = health.get().displayModel ?? null;
311
+ const prompt =
312
+ ((job.payload as Record<string, unknown>)?.prompt as string) ??
313
+ "Briefly describe your capabilities, personality, tools, expertise, goals, and constraints. Plain text, no JSON.";
314
+
315
+ const sessionKey = buildAgentSessionKey({
316
+ agentId: "default",
317
+ channel: "stagewhisper",
318
+ peer: { kind: "direct", id: `sw-probe-${job.job_id}` },
319
+ });
320
+
321
+ try {
322
+ const result = await api.runtime.subagent.run({
323
+ sessionKey,
324
+ message: prompt,
325
+ deliver: false,
326
+ idempotencyKey: `sw-probe-${job.job_id}`,
327
+ });
328
+
329
+ const waitResult = await api.runtime.subagent.waitForRun({
330
+ runId: result.runId,
331
+ timeoutMs: 35_000,
332
+ });
333
+
334
+ if (waitResult.status === "ok") {
335
+ const reply = await extractReplyWithRetry(sessionKey);
336
+
337
+ await client.postReasoningResult(
338
+ job.job_id,
339
+ {
340
+ job_id: job.job_id,
341
+ status: "completed",
342
+ provider_run_id: result.runId,
343
+ model_ref: displayModel,
344
+ usage: null,
345
+ output: { raw_description: (reply ?? "").slice(0, 2000) },
346
+ error_code: null,
347
+ error_message: null,
348
+ },
349
+ correlationId,
350
+ );
351
+ api.logger.info(`Capability probe ${job.job_id} completed`);
352
+ } else {
353
+ await client.postReasoningResult(
354
+ job.job_id,
355
+ {
356
+ job_id: job.job_id,
357
+ status: "failed",
358
+ provider_run_id: result.runId,
359
+ model_ref: displayModel,
360
+ usage: null,
361
+ output: null,
362
+ error_code: "agent_error",
363
+ error_message: waitResult.error ?? "Agent run failed",
364
+ },
365
+ correlationId,
366
+ );
367
+ }
368
+ } catch (err) {
369
+ const errMsg = err instanceof Error ? err.message : String(err);
370
+ api.logger.error(`Capability probe ${job.job_id} failed: ${errMsg}`);
371
+ try {
372
+ await client.postReasoningResult(
373
+ job.job_id,
374
+ {
375
+ job_id: job.job_id,
376
+ status: "failed",
377
+ provider_run_id: null,
378
+ model_ref: displayModel,
379
+ usage: null,
380
+ output: null,
381
+ error_code: "execution_error",
382
+ error_message: errMsg,
383
+ },
384
+ correlationId,
385
+ );
386
+ } catch (postErr) {
387
+ api.logger.error(`Failed to report probe failure: ${postErr}`);
388
+ }
389
+ }
390
+ }
391
+
305
392
  async function handleReasoningJob(
306
393
  job: ReasoningJobEnvelope,
307
394
  client: StageWhisperClient,
308
395
  ): Promise<void> {
309
396
  const correlationId = job.correlation_id;
310
- api.logger.info(`Received reasoning job: ${job.job_id} (purpose: ${job.purpose}, correlation: ${correlationId ?? "none"})`);
397
+ api.logger.info(
398
+ `Received reasoning job: ${job.job_id} (purpose: ${job.purpose}, correlation: ${correlationId ?? "none"})`,
399
+ );
311
400
 
312
401
  if (completedReasoningJobs.has(job.job_id)) {
313
402
  api.logger.info(`Skipping completed reasoning job: ${job.job_id}`);
@@ -319,6 +408,16 @@ export function createRelayService(api: OpenClawPluginApi) {
319
408
  }
320
409
  processingReasoningJobs.add(job.job_id);
321
410
 
411
+ if (job.purpose === "capability_probe") {
412
+ try {
413
+ await handleCapabilityProbe(job, client);
414
+ completedReasoningJobs.set(job.job_id, Date.now());
415
+ } finally {
416
+ processingReasoningJobs.delete(job.job_id);
417
+ }
418
+ return;
419
+ }
420
+
322
421
  try {
323
422
  const displayModel = health.get().displayModel ?? null;
324
423
 
@@ -331,16 +430,20 @@ export function createRelayService(api: OpenClawPluginApi) {
331
430
  api.logger.error(`Reasoning job ${job.job_id} failed: ${errMsg}`);
332
431
 
333
432
  try {
334
- await client.postReasoningResult(job.job_id, {
335
- job_id: job.job_id,
336
- status: "failed",
337
- provider_run_id: null,
338
- model_ref: displayModel,
339
- usage: null,
340
- output: null,
341
- error_code: "execution_error",
342
- error_message: errMsg,
343
- }, correlationId);
433
+ await client.postReasoningResult(
434
+ job.job_id,
435
+ {
436
+ job_id: job.job_id,
437
+ status: "failed",
438
+ provider_run_id: null,
439
+ model_ref: displayModel,
440
+ usage: null,
441
+ output: null,
442
+ error_code: "execution_error",
443
+ error_message: errMsg,
444
+ },
445
+ correlationId,
446
+ );
344
447
  } catch (postErr) {
345
448
  api.logger.error(`Failed to report reasoning failure: ${postErr}`);
346
449
  }
@@ -358,7 +461,11 @@ export function createRelayService(api: OpenClawPluginApi) {
358
461
  }
359
462
 
360
463
  try {
361
- await client.postReasoningResult(job.job_id, result as unknown as Record<string, unknown>, correlationId);
464
+ await client.postReasoningResult(
465
+ job.job_id,
466
+ result as unknown as Record<string, unknown>,
467
+ correlationId,
468
+ );
362
469
  completedReasoningJobs.set(job.job_id, Date.now());
363
470
  if (completedReasoningJobs.size > COMPLETED_JOB_MAX_SIZE) {
364
471
  evictStaleCompletedJobs();
@@ -401,6 +508,18 @@ export function createRelayService(api: OpenClawPluginApi) {
401
508
  health.setConnected();
402
509
  api.logger.info("Connected to StageWhisper relay stream");
403
510
 
511
+ if (health.get().status !== "healthy") {
512
+ api.logger.info("Re-probing /v1/responses after reconnect...");
513
+ const probe = await probeOpenResponses(api);
514
+ if (probe.ok) {
515
+ health.recordSuccess();
516
+ if (probe.model) health.setModel(probe.model);
517
+ api.logger.info(`Local AI verified on reconnect — model: ${probe.model ?? "unknown"}`);
518
+ } else {
519
+ api.logger.warn(`Reconnect probe failed: ${probe.error}`);
520
+ }
521
+ }
522
+
404
523
  const reader = res.body.getReader();
405
524
  const decoder = new TextDecoder();
406
525
  let buffer = "";
@@ -501,7 +620,7 @@ export function createRelayService(api: OpenClawPluginApi) {
501
620
  if (!isResponsesEndpointEnabled(api)) {
502
621
  api.logger.warn(
503
622
  "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",
623
+ "Enable it in config and restart the gateway, or re-pair with: openclaw stagewhisper pair --code <CODE> --enable-responses",
505
624
  );
506
625
  }
507
626