@openclaw/plugin-inspector 0.3.24 → 0.3.26

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.
@@ -1,4 +1,5 @@
1
1
  import { renderPaddedMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
2
+ import { resolveProcessLimits } from "./process-profile.js";
2
3
 
3
4
  export const syntheticRegistrationExecutionProfiles = {
4
5
  createChatChannelPlugin: {
@@ -46,6 +47,11 @@ export const syntheticRegistrationExecutionProfiles = {
46
47
  callableProperties: [],
47
48
  reason: "auto-enable probes are captured as registration metadata before runtime activation checks",
48
49
  },
50
+ registerBoardWidgetContentKind: {
51
+ mode: "metadata-only",
52
+ callableProperties: [],
53
+ reason: "board widget content kinds are captured as registration metadata before source validation or document composition",
54
+ },
49
55
  registerCli: {
50
56
  mode: "direct",
51
57
  callableProperties: ["handler", "run", "execute"],
@@ -126,6 +132,16 @@ export const syntheticRegistrationExecutionProfiles = {
126
132
  callableProperties: [],
127
133
  reason: "hosted media resolvers are captured as registration metadata before media URL resolution",
128
134
  },
135
+ registerMcpServerConnectionResolver: {
136
+ mode: "metadata-only",
137
+ callableProperties: [],
138
+ reason: "MCP server connection resolvers are captured as registration metadata before requester-bound transport resolution",
139
+ },
140
+ registerMemoryPromptPreparation: {
141
+ mode: "metadata-only",
142
+ callableProperties: [],
143
+ reason: "memory prompt preparation callbacks are captured as registration metadata before prompt-runtime execution",
144
+ },
129
145
  registerMemoryPromptSection: {
130
146
  mode: "metadata-only",
131
147
  callableProperties: [],
@@ -275,6 +291,11 @@ export const syntheticRegistrationExecutionProfiles = {
275
291
  callableProperties: [],
276
292
  reason: "text transforms are captured as registration metadata before content mutation execution",
277
293
  },
294
+ registerTranscriptSourceProvider: {
295
+ mode: "metadata-only",
296
+ callableProperties: [],
297
+ reason: "transcript source providers are captured as registration metadata before live capture or transcript import",
298
+ },
278
299
  registerVideoGenerationProvider: {
279
300
  mode: "metadata-only",
280
301
  callableProperties: [],
@@ -300,6 +321,11 @@ export const syntheticRegistrationExecutionProfiles = {
300
321
  callableProperties: [],
301
322
  reason: "widget presenters are captured as registration metadata before presentation runtime execution",
302
323
  },
324
+ registerWorkerProvider: {
325
+ mode: "metadata-only",
326
+ callableProperties: [],
327
+ reason: "worker providers are captured as registration metadata before cloud-worker lifecycle execution",
328
+ },
303
329
  };
304
330
 
305
331
  export const defaultSyntheticHookEvents = {
@@ -483,6 +509,7 @@ export const defaultSyntheticRegistrationProbeInputs = {
483
509
  registerGatewayMethod: {
484
510
  execute: gatewayProbeArgs,
485
511
  handler: gatewayProbeArgs,
512
+ invoke: gatewayProbeArgs,
486
513
  run: gatewayProbeArgs,
487
514
  },
488
515
  registerHttpRoute: {
@@ -597,8 +624,28 @@ export async function writeSyntheticProbePlan(plan, options = {}) {
597
624
  }
598
625
 
599
626
  export async function runCapturedSyntheticProbes(capture, options = {}) {
627
+ options.signal?.throwIfAborted();
600
628
  const hookEvents = options.hookEvents ?? defaultSyntheticHookEvents;
601
629
  const hookContexts = options.hookContexts ?? defaultSyntheticHookContexts;
630
+ const { timeoutMs } = resolveProcessLimits(options, "PROBE");
631
+ const controller = new AbortController();
632
+ const onCancel = () => controller.abort(new Error("Synthetic probes cancelled"));
633
+ options.signal?.addEventListener("abort", onCancel, { once: true });
634
+ if (options.signal?.aborted) onCancel();
635
+ try {
636
+ const result = await runCapturedProbes(capture, {
637
+ ...options, hookEvents, hookContexts, timeoutMs, controller, signal: controller.signal,
638
+ gatewayConfig: options.apiOptions?.config ?? {},
639
+ });
640
+ if (options.signal?.aborted) throw controller.signal.reason;
641
+ return result;
642
+ } finally {
643
+ options.signal?.removeEventListener("abort", onCancel);
644
+ }
645
+ }
646
+
647
+ async function runCapturedProbes(capture, options) {
648
+ const { hookEvents, hookContexts, timeoutMs, controller, signal } = options;
602
649
  const captured = capture.captured ?? [];
603
650
  const retained = new Map((capture.retained ?? []).map((item) => [item.captureIndex, item]));
604
651
  const resultsByCaptureIndex = new Map();
@@ -611,6 +658,10 @@ export async function runCapturedSyntheticProbes(capture, options = {}) {
611
658
  );
612
659
 
613
660
  for (const { entry, captureIndex } of executionEntries) {
661
+ if (signal.aborted) {
662
+ resultsByCaptureIndex.set(captureIndex, [blockedResult(entry, captureIndex, signal.reason.message)]);
663
+ continue;
664
+ }
614
665
  const retainedEntry = retained.get(captureIndex);
615
666
  if (!retainedEntry) {
616
667
  resultsByCaptureIndex.set(captureIndex, [blockedResult(entry, captureIndex, "handler retention was not enabled")]);
@@ -618,7 +669,7 @@ export async function runCapturedSyntheticProbes(capture, options = {}) {
618
669
  }
619
670
  if (entry.kind === "hook") {
620
671
  resultsByCaptureIndex.set(captureIndex, [
621
- await runHookProbe(entry, retainedEntry, captureIndex, { hookEvents, hookContexts }),
672
+ await runHookProbe(entry, retainedEntry, captureIndex, { hookEvents, hookContexts, timeoutMs, controller }),
622
673
  ]);
623
674
  continue;
624
675
  }
@@ -719,7 +770,7 @@ function probeBlocker({ hasSyntheticArguments, execution }) {
719
770
  return null;
720
771
  }
721
772
 
722
- async function runHookProbe(entry, retainedEntry, captureIndex, { hookEvents, hookContexts }) {
773
+ async function runHookProbe(entry, retainedEntry, captureIndex, { hookEvents, hookContexts, timeoutMs, controller }) {
723
774
  if (typeof retainedEntry.handler !== "function") {
724
775
  return blockedResult(entry, captureIndex, "captured hook has no callable handler");
725
776
  }
@@ -728,6 +779,8 @@ async function runHookProbe(entry, retainedEntry, captureIndex, { hookEvents, ho
728
779
  kind: "hook",
729
780
  seam: entry.name,
730
781
  label: entry.name,
782
+ timeoutMs,
783
+ controller,
731
784
  invoke: () =>
732
785
  retainedEntry.handler(
733
786
  hookEvents[entry.name] ?? { hook: entry.name },
@@ -746,10 +799,22 @@ async function runRegistrationProbes(entry, retainedEntry, captureIndex, options
746
799
  }
747
800
 
748
801
  const descriptor =
749
- retainedEntry.arguments?.find((value) => value && typeof value === "object") ?? retainedEntry.returnValue;
802
+ entry.name === "registerGatewayMethod" && typeof retainedEntry.arguments?.[0] === "string"
803
+ ? retainedEntry.returnValue
804
+ : retainedEntry.arguments?.find((value) => value && typeof value === "object") ?? retainedEntry.returnValue;
750
805
  if (!descriptor || typeof descriptor !== "object") {
751
806
  return [blockedResult(entry, captureIndex, "captured registration has no object descriptor")];
752
807
  }
808
+ const method = descriptor.method ?? descriptor.name;
809
+ if (entry.name === "registerGatewayMethod" && Object.hasOwn(options.gatewayMethodPrerequisites ?? {}, method)) {
810
+ const reason = options.gatewayMethodPrerequisites[method];
811
+ if (typeof reason !== "string" || reason.trim().length === 0) {
812
+ throw new TypeError(`Gateway probe prerequisite for ${method} must be a non-empty string`);
813
+ }
814
+ // A method needing host state or live credentials cannot be exercised with
815
+ // the default empty request. Record the missing prerequisite before calling it.
816
+ return [{ ...blockedResult(entry, captureIndex, reason), method }];
817
+ }
753
818
  if (profile.option && options[profile.option] !== true) {
754
819
  return [blockedResult(entry, captureIndex, `captured registration requires ${profile.option}=true`)];
755
820
  }
@@ -759,17 +824,29 @@ async function runRegistrationProbes(entry, retainedEntry, captureIndex, options
759
824
  return [blockedResult(entry, captureIndex, "captured registration has no supported callable probe")];
760
825
  }
761
826
 
762
- return Promise.all(
763
- invocations.map((invocation) =>
764
- runProbe({
827
+ // The profile owns lifecycle order; finish each callback before starting the next.
828
+ const results = [];
829
+ for (const invocation of invocations) {
830
+ if (options.signal.aborted) {
831
+ results.push({
832
+ ...blockedResult(entry, captureIndex, options.signal.reason.message),
833
+ label: invocation.label,
834
+ });
835
+ continue;
836
+ }
837
+ results.push(
838
+ await runProbe({
765
839
  captureIndex,
766
840
  kind: "registration",
767
841
  seam: entry.name,
768
842
  label: invocation.label,
843
+ timeoutMs: options.timeoutMs,
844
+ controller: options.controller,
769
845
  invoke: invocation.invoke,
770
846
  }),
771
- ),
772
- );
847
+ );
848
+ }
849
+ return results;
773
850
  }
774
851
 
775
852
  function registrationInvocations(registrar, descriptor, returnValue, profile, options) {
@@ -786,20 +863,100 @@ function registrationInvocations(registrar, descriptor, returnValue, profile, op
786
863
  if (typeof callable === "function") {
787
864
  invocations.push({
788
865
  label: `${registrar}.${property}`,
789
- invoke: () => invokeRegistrationCallable(callable, registrar, property, options),
866
+ invoke: () => registrar === "registerGatewayMethod"
867
+ ? invokeGatewayCallable(callable, property, descriptor, options)
868
+ : invokeRegistrationCallable(callable, registrar, property, options),
790
869
  });
870
+ // Gateway aliases describe one method, not independent lifecycle callbacks.
871
+ if (registrar === "registerGatewayMethod") break;
791
872
  }
792
873
  }
793
874
  return invocations;
794
875
  }
795
876
 
796
- function invokeRegistrationCallable(callable, registrar, property, options) {
797
- const event = syntheticRegistrationEvent(registrar, property, options);
877
+ function invokeRegistrationCallable(
878
+ callable, registrar, property, options,
879
+ event = syntheticRegistrationEvent(registrar, property, options),
880
+ ) {
798
881
  const inputFactory = options.registrationProbeInputs?.[registrar]?.[property] ?? defaultSyntheticRegistrationProbeInputs[registrar]?.[property];
799
882
  const args = inputFactory ? inputFactory(event, options) : [event];
800
883
  return callable(...args);
801
884
  }
802
885
 
886
+ async function invokeGatewayCallable(callable, property, descriptor, options) {
887
+ let responded = false;
888
+ let closed = false;
889
+ let resolveResponse;
890
+ const response = new Promise((resolve) => { resolveResponse = resolve; });
891
+ const onAbort = () => {
892
+ closed = true;
893
+ resolveResponse({ error: options.signal.reason });
894
+ };
895
+ options.signal.addEventListener("abort", onAbort, { once: true });
896
+ const respond = (ok, payload, error) => {
897
+ if (closed || responded) return;
898
+ // Snapshot the first emission, including invalid frames. Later responses
899
+ // cannot recover it; logging metadata is not part of the wire envelope.
900
+ responded = true;
901
+ try {
902
+ resolveResponse({ frame: gatewayResponseFrame(ok, payload, error) });
903
+ } catch (error) {
904
+ resolveResponse({ error });
905
+ }
906
+ };
907
+ try {
908
+ options.signal.throwIfAborted();
909
+ const event = {
910
+ ...syntheticRegistrationEvent("registerGatewayMethod", property, options),
911
+ method: descriptor.method ?? descriptor.name,
912
+ respond,
913
+ };
914
+ const result = await invokeRegistrationCallable(callable, "registerGatewayMethod", property, options, event);
915
+ // OpenClaw's plugin registrar adapts returns only without an explicit reply.
916
+ if (!responded && result !== undefined) respond(true, result);
917
+ // A void handler may respond later. The existing probe deadline bounds this
918
+ // observation as well as handler settlement; it owns timeout/cancellation.
919
+ const outcome = await response;
920
+ if (outcome.error) throw outcome.error;
921
+ if (!outcome.frame.ok) {
922
+ throw new Error(`Gateway response error: ${outcome.frame.error?.message ?? "request failed"}`);
923
+ }
924
+ return outcome.frame;
925
+ } finally {
926
+ closed = true;
927
+ options.signal.removeEventListener("abort", onAbort);
928
+ }
929
+ }
930
+
931
+ function gatewayResponseFrame(ok, payload, error) {
932
+ let frame;
933
+ try {
934
+ frame = JSON.parse(JSON.stringify({ type: "res", id: "fixture-request", ok, payload, error }));
935
+ } catch {
936
+ throw new Error("Gateway response is not JSON serializable");
937
+ }
938
+ // ResponseFrameSchema / ErrorShapeSchema in OpenClaw v2026.9.3. Payload and
939
+ // error are optional independently of ok; error codes are nonempty strings.
940
+ const responseError = frame.error;
941
+ if (
942
+ typeof frame.ok !== "boolean" ||
943
+ (responseError !== undefined && (
944
+ !responseError ||
945
+ typeof responseError !== "object" ||
946
+ Array.isArray(responseError) ||
947
+ typeof responseError.code !== "string" || responseError.code.length === 0 ||
948
+ typeof responseError.message !== "string" || responseError.message.length === 0 ||
949
+ (responseError.retryable !== undefined && typeof responseError.retryable !== "boolean") ||
950
+ (responseError.retryAfterMs !== undefined &&
951
+ (!Number.isInteger(responseError.retryAfterMs) || responseError.retryAfterMs < 0)) ||
952
+ Object.keys(responseError).some((key) => !["code", "message", "details", "retryable", "retryAfterMs"].includes(key))
953
+ ))
954
+ ) {
955
+ throw new Error("Gateway response is malformed");
956
+ }
957
+ return frame;
958
+ }
959
+
803
960
  function syntheticRegistrationEvent(registrar, property, options) {
804
961
  const hookEvents = options.hookEvents ?? defaultSyntheticHookEvents;
805
962
  const beforeToolCall = hookEvents.before_tool_call ?? defaultSyntheticHookEvents.before_tool_call;
@@ -822,21 +979,21 @@ function syntheticRegistrationEvent(registrar, property, options) {
822
979
  };
823
980
  }
824
981
 
825
- function toolRunProbeArgs(event) {
982
+ function toolRunProbeArgs(event, options = {}) {
826
983
  return [
827
984
  event.params,
828
985
  {
829
986
  source: event.source,
830
987
  toolName: event.toolName,
831
988
  toolCallId: event.toolCall.id,
832
- signal: new AbortController().signal,
989
+ signal: options.signal ?? new AbortController().signal,
833
990
  logger: console,
834
991
  },
835
992
  ];
836
993
  }
837
994
 
838
- function toolExecuteProbeArgs(event) {
839
- return [event.toolCall.id, event.params, new AbortController().signal, () => undefined];
995
+ function toolExecuteProbeArgs(event, options = {}) {
996
+ return [event.toolCall.id, event.params, options.signal ?? new AbortController().signal, () => undefined];
840
997
  }
841
998
 
842
999
  function httpRouteProbeArgs(event) {
@@ -858,34 +1015,31 @@ function httpRouteProbeArgs(event) {
858
1015
  ];
859
1016
  }
860
1017
 
861
- function commandProbeArgs(event) {
1018
+ function commandProbeArgs(event, options = {}) {
862
1019
  return [
863
1020
  event.input,
864
1021
  {
865
1022
  source: event.source,
866
- signal: new AbortController().signal,
1023
+ signal: options.signal ?? new AbortController().signal,
867
1024
  logger: console,
868
1025
  },
869
1026
  ];
870
1027
  }
871
1028
 
872
- function gatewayProbeArgs(event) {
1029
+ function gatewayProbeArgs(event, options = {}) {
873
1030
  return [
874
1031
  {
875
1032
  ...event,
876
- params: event.params,
877
- body: event.body,
878
- headers: event.headers,
879
- respond: event.respond,
880
- },
881
- {
882
- source: event.source,
883
- logger: console,
1033
+ req: { type: "req", id: "fixture-request", method: event.method ?? "fixture.gateway.method", params: event.params },
1034
+ client: null,
1035
+ isWebchatConnect: () => false,
1036
+ context: { source: event.source, logger: console, getRuntimeConfig: () => options.gatewayConfig },
1037
+ signal: options.signal,
884
1038
  },
885
1039
  ];
886
1040
  }
887
1041
 
888
- function channelSendProbeArgs(event) {
1042
+ function channelSendProbeArgs(event, options = {}) {
889
1043
  return [
890
1044
  {
891
1045
  source: event.source,
@@ -896,12 +1050,12 @@ function channelSendProbeArgs(event) {
896
1050
  replyToId: "fixture-reply",
897
1051
  threadId: "fixture-thread",
898
1052
  logger: console,
899
- signal: new AbortController().signal,
1053
+ signal: options.signal ?? new AbortController().signal,
900
1054
  },
901
1055
  ];
902
1056
  }
903
1057
 
904
- function channelReceiveProbeArgs(event) {
1058
+ function channelReceiveProbeArgs(event, options = {}) {
905
1059
  return [
906
1060
  {
907
1061
  source: event.source,
@@ -921,7 +1075,7 @@ function channelReceiveProbeArgs(event) {
921
1075
  to: "fixture-channel",
922
1076
  },
923
1077
  logger: console,
924
- signal: new AbortController().signal,
1078
+ signal: options.signal ?? new AbortController().signal,
925
1079
  },
926
1080
  ];
927
1081
  }
@@ -939,7 +1093,7 @@ function interactiveProbeArgs(event) {
939
1093
  ];
940
1094
  }
941
1095
 
942
- function lifecycleProbeArgs(event) {
1096
+ function lifecycleProbeArgs(event, options = {}) {
943
1097
  return [
944
1098
  {
945
1099
  source: event.source,
@@ -947,7 +1101,7 @@ function lifecycleProbeArgs(event) {
947
1101
  logger: console,
948
1102
  runtime: { env: {}, logger: console },
949
1103
  secrets: { get: async () => null, has: async () => false },
950
- signal: new AbortController().signal,
1104
+ signal: options.signal ?? new AbortController().signal,
951
1105
  },
952
1106
  ];
953
1107
  }
@@ -965,9 +1119,9 @@ function speechProbeArgs(event) {
965
1119
  ];
966
1120
  }
967
1121
 
968
- async function runProbe({ captureIndex, kind, seam, label, invoke }) {
1122
+ async function runProbe({ captureIndex, kind, seam, label, invoke, timeoutMs, controller }) {
969
1123
  try {
970
- const output = await invoke();
1124
+ const output = await invokeWithTimeout(invoke, timeoutMs, controller);
971
1125
  return {
972
1126
  captureIndex,
973
1127
  kind,
@@ -988,6 +1142,30 @@ async function runProbe({ captureIndex, kind, seam, label, invoke }) {
988
1142
  }
989
1143
  }
990
1144
 
1145
+ function invokeWithTimeout(invoke, timeoutMs, controller) {
1146
+ const { signal } = controller;
1147
+ let rejectAborted;
1148
+ const aborted = new Promise((_, reject) => { rejectAborted = reject; });
1149
+ const onAbort = () => rejectAborted(signal.reason);
1150
+ signal.addEventListener("abort", onAbort, { once: true });
1151
+ const timeoutId = setTimeout(() => {
1152
+ controller.abort(new Error(`Synthetic probe timed out after ${timeoutMs}ms`));
1153
+ }, timeoutMs);
1154
+ // Keep observing settlement after abort without starting dependent callbacks.
1155
+ // In-process JavaScript itself is not preempted by this deadline.
1156
+ const result = Promise.resolve().then(() => {
1157
+ signal.throwIfAborted();
1158
+ return invoke();
1159
+ }).then((output) => {
1160
+ signal.throwIfAborted();
1161
+ return output;
1162
+ });
1163
+ return Promise.race([result, aborted]).finally(() => {
1164
+ clearTimeout(timeoutId);
1165
+ signal.removeEventListener("abort", onAbort);
1166
+ });
1167
+ }
1168
+
991
1169
  function blockedResult(entry, captureIndex, reason) {
992
1170
  return {
993
1171
  captureIndex,