@botim/mp-debug-sdk 0.5.2 → 0.6.1

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/dist/index.d.cts CHANGED
@@ -52,6 +52,36 @@ interface BuiltinHostHooks {
52
52
  * The SDK enforces a 1 MB base64/JSON cap regardless.
53
53
  */
54
54
  screenshot?: () => ScreenshotResult | Promise<ScreenshotResult>;
55
+ /**
56
+ * Default-enabled remote-eval command (`exec`). Pass `false` to opt out
57
+ * of registration entirely; pass an object to inject extra locals into
58
+ * every snippet's scope.
59
+ *
60
+ * builtins: { exec: false }
61
+ * → exec NOT registered; agents calling exec see `unknown-command`.
62
+ *
63
+ * builtins: { exec: { globals: { app, store } } }
64
+ * → exec registered; snippets can reference `app` and `store` as
65
+ * bare identifiers. Default locals (`BOT`, `window`, `document`)
66
+ * are always present.
67
+ *
68
+ * builtins.exec is undefined / not set
69
+ * → default behaviour: exec registered with default locals only.
70
+ *
71
+ * See `openspec/changes/add-default-exec-builtin/design.md` for the
72
+ * security rationale (no prod-specific gating in MVP; relies on the
73
+ * existing `enableRemoteDebug` consent gate).
74
+ */
75
+ exec?: false | ExecOptions;
76
+ }
77
+ interface ExecOptions {
78
+ /**
79
+ * Additional locals bound into every snippet's scope. Keys MUST be
80
+ * valid identifier names (the SDK does not validate this — passing a
81
+ * key like "1foo" or "a-b" will throw at AsyncFunction construction
82
+ * time and surface as a `command-rejected`).
83
+ */
84
+ globals?: Record<string, unknown>;
55
85
  }
56
86
  type ScreenshotResult = string | {
57
87
  data: string;
@@ -71,6 +101,7 @@ type ScreenshotResult = string | {
71
101
  */
72
102
  | 'html-snapshot';
73
103
  };
104
+ declare function getBOT(): unknown;
74
105
 
75
106
  /**
76
107
  * Per-signature sliding-window event deduper.
@@ -211,4 +242,4 @@ declare const DEFAULT_BUFFER_SIZE = 1000;
211
242
  declare const DEFAULT_MAX_BATCH_SIZE = 50;
212
243
  declare function enableRemoteDebug(options: RemoteDebugOptions): Promise<RemoteDebugHandle>;
213
244
 
214
- export { type AppInfo, BotimConfig, BotimConfigError, BotimConsentError, type BuiltinHostHooks, CommandHandler, ConsentInput, ConsolePayload, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, type DedupOptions, DeviceInfo, ErrorPayload, EventLevel, EventType, NetworkPayload, type RedactionConfig, type RemoteDebugHandle, type RemoteDebugOptions, type SamplingConfig, type SuppressionSummary, enableRemoteDebug };
245
+ export { type AppInfo, BotimConfig, BotimConfigError, BotimConsentError, type BuiltinHostHooks, CommandHandler, ConsentInput, ConsolePayload, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, type DedupOptions, DeviceInfo, ErrorPayload, EventLevel, EventType, type ExecOptions, NetworkPayload, type RedactionConfig, type RemoteDebugHandle, type RemoteDebugOptions, type SamplingConfig, type SuppressionSummary, enableRemoteDebug, getBOT };
package/dist/index.d.ts CHANGED
@@ -52,6 +52,36 @@ interface BuiltinHostHooks {
52
52
  * The SDK enforces a 1 MB base64/JSON cap regardless.
53
53
  */
54
54
  screenshot?: () => ScreenshotResult | Promise<ScreenshotResult>;
55
+ /**
56
+ * Default-enabled remote-eval command (`exec`). Pass `false` to opt out
57
+ * of registration entirely; pass an object to inject extra locals into
58
+ * every snippet's scope.
59
+ *
60
+ * builtins: { exec: false }
61
+ * → exec NOT registered; agents calling exec see `unknown-command`.
62
+ *
63
+ * builtins: { exec: { globals: { app, store } } }
64
+ * → exec registered; snippets can reference `app` and `store` as
65
+ * bare identifiers. Default locals (`BOT`, `window`, `document`)
66
+ * are always present.
67
+ *
68
+ * builtins.exec is undefined / not set
69
+ * → default behaviour: exec registered with default locals only.
70
+ *
71
+ * See `openspec/changes/add-default-exec-builtin/design.md` for the
72
+ * security rationale (no prod-specific gating in MVP; relies on the
73
+ * existing `enableRemoteDebug` consent gate).
74
+ */
75
+ exec?: false | ExecOptions;
76
+ }
77
+ interface ExecOptions {
78
+ /**
79
+ * Additional locals bound into every snippet's scope. Keys MUST be
80
+ * valid identifier names (the SDK does not validate this — passing a
81
+ * key like "1foo" or "a-b" will throw at AsyncFunction construction
82
+ * time and surface as a `command-rejected`).
83
+ */
84
+ globals?: Record<string, unknown>;
55
85
  }
56
86
  type ScreenshotResult = string | {
57
87
  data: string;
@@ -71,6 +101,7 @@ type ScreenshotResult = string | {
71
101
  */
72
102
  | 'html-snapshot';
73
103
  };
104
+ declare function getBOT(): unknown;
74
105
 
75
106
  /**
76
107
  * Per-signature sliding-window event deduper.
@@ -211,4 +242,4 @@ declare const DEFAULT_BUFFER_SIZE = 1000;
211
242
  declare const DEFAULT_MAX_BATCH_SIZE = 50;
212
243
  declare function enableRemoteDebug(options: RemoteDebugOptions): Promise<RemoteDebugHandle>;
213
244
 
214
- export { type AppInfo, BotimConfig, BotimConfigError, BotimConsentError, type BuiltinHostHooks, CommandHandler, ConsentInput, ConsolePayload, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, type DedupOptions, DeviceInfo, ErrorPayload, EventLevel, EventType, NetworkPayload, type RedactionConfig, type RemoteDebugHandle, type RemoteDebugOptions, type SamplingConfig, type SuppressionSummary, enableRemoteDebug };
245
+ export { type AppInfo, BotimConfig, BotimConfigError, BotimConsentError, type BuiltinHostHooks, CommandHandler, ConsentInput, ConsolePayload, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, type DedupOptions, DeviceInfo, ErrorPayload, EventLevel, EventType, type ExecOptions, NetworkPayload, type RedactionConfig, type RemoteDebugHandle, type RemoteDebugOptions, type SamplingConfig, type SuppressionSummary, enableRemoteDebug, getBOT };
package/dist/index.js CHANGED
@@ -817,6 +817,10 @@ var CommandRegistry = class {
817
817
  };
818
818
  var MAX_DUMP_BYTES = 64 * 1024;
819
819
  var MAX_SCREENSHOT_BYTES = 1024 * 1024;
820
+ var MAX_EXEC_CODE_BYTES = 8 * 1024;
821
+ var DEFAULT_EXEC_TIMEOUT_MS = 5e3;
822
+ var MIN_EXEC_TIMEOUT_MS = 250;
823
+ var MAX_EXEC_TIMEOUT_MS = 3e4;
820
824
  async function defaultDomScreenshot() {
821
825
  if (typeof document === "undefined" || typeof window === "undefined") {
822
826
  throw new Error(
@@ -931,6 +935,9 @@ function registerBuiltins(registry, hooks = {}) {
931
935
  registry.register("dump-state", makeDumpState(hooks.getState));
932
936
  registry.register("set-feature-flag", makeSetFlag(hooks.setFeatureFlag));
933
937
  registry.register("screenshot", makeScreenshot(hooks.screenshot));
938
+ if (hooks.exec !== false) {
939
+ registry.register("exec", makeExec(hooks.exec ?? {}));
940
+ }
934
941
  }
935
942
  var ping = () => ({ ok: true, ts: Date.now() });
936
943
  function makeReload(reload) {
@@ -993,6 +1000,168 @@ function makeScreenshot(screenshot) {
993
1000
  return { format, data, bytes: data.length };
994
1001
  };
995
1002
  }
1003
+ var cachedBOT = void 0;
1004
+ function getBOT() {
1005
+ if (cachedBOT !== void 0) return cachedBOT;
1006
+ if (typeof window === "undefined") {
1007
+ cachedBOT = null;
1008
+ return null;
1009
+ }
1010
+ try {
1011
+ const w = window;
1012
+ if (w.BOT) {
1013
+ cachedBOT = w.BOT;
1014
+ return cachedBOT;
1015
+ }
1016
+ } catch {
1017
+ }
1018
+ try {
1019
+ const w = window;
1020
+ const ng = w.angular;
1021
+ if (ng?.element && typeof document !== "undefined") {
1022
+ const injector = ng.element(document).injector?.();
1023
+ const bot = injector?.get?.("BOT");
1024
+ if (bot != null) {
1025
+ cachedBOT = bot;
1026
+ return cachedBOT;
1027
+ }
1028
+ }
1029
+ } catch {
1030
+ }
1031
+ return null;
1032
+ }
1033
+ function makeExec(opts) {
1034
+ const extraGlobalNames = Object.keys(opts.globals ?? {});
1035
+ const extraGlobalValues = extraGlobalNames.map((k) => opts.globals[k]);
1036
+ return async (args, ctx) => {
1037
+ if (typeof args.code !== "string" || args.code.length === 0) {
1038
+ throw new Error("args.code (non-empty string) is required");
1039
+ }
1040
+ if (args.code.length > MAX_EXEC_CODE_BYTES) {
1041
+ throw new Error(`args.code ${args.code.length} bytes exceeds limit ${MAX_EXEC_CODE_BYTES}`);
1042
+ }
1043
+ const timeoutMs = clamp(
1044
+ typeof args.timeoutMs === "number" ? args.timeoutMs : DEFAULT_EXEC_TIMEOUT_MS,
1045
+ MIN_EXEC_TIMEOUT_MS,
1046
+ MAX_EXEC_TIMEOUT_MS
1047
+ );
1048
+ const start = Date.now();
1049
+ const logs = [];
1050
+ const consoleMethods = ["log", "info", "warn", "error", "debug"];
1051
+ const originals = {};
1052
+ if (typeof console !== "undefined") {
1053
+ for (const m of consoleMethods) {
1054
+ const orig = console[m];
1055
+ if (typeof orig !== "function") continue;
1056
+ originals[m] = orig;
1057
+ const captured = (...callArgs) => {
1058
+ logs.push({ method: m, args: callArgs.map(execSafeClone), ts: Date.now() });
1059
+ try {
1060
+ orig.call(console, ...callArgs);
1061
+ } catch {
1062
+ }
1063
+ };
1064
+ console[m] = captured;
1065
+ }
1066
+ }
1067
+ let value;
1068
+ let threw = void 0;
1069
+ try {
1070
+ const AsyncFunction = Object.getPrototypeOf(async function() {
1071
+ }).constructor;
1072
+ const localNames = ["BOT", "window", "document", ...extraGlobalNames];
1073
+ const localValues = [
1074
+ getBOT(),
1075
+ typeof window !== "undefined" ? window : void 0,
1076
+ typeof document !== "undefined" ? document : void 0,
1077
+ ...extraGlobalValues
1078
+ ];
1079
+ let fnTry = null;
1080
+ try {
1081
+ fnTry = new AsyncFunction(...localNames, `return (${args.code});`);
1082
+ } catch (e) {
1083
+ if (!(e instanceof SyntaxError)) throw e;
1084
+ }
1085
+ const fn = fnTry ?? new AsyncFunction(...localNames, args.code);
1086
+ let timer = null;
1087
+ let abortReject = null;
1088
+ const onAbort = () => abortReject?.(new Error("exec cancelled"));
1089
+ ctx.signal?.addEventListener?.("abort", onAbort);
1090
+ try {
1091
+ value = await Promise.race([
1092
+ fn(...localValues),
1093
+ new Promise((_, reject) => {
1094
+ timer = setTimeout(
1095
+ () => reject(new Error(`exec exceeded ${timeoutMs}ms`)),
1096
+ timeoutMs
1097
+ );
1098
+ }),
1099
+ new Promise((_, reject) => {
1100
+ abortReject = reject;
1101
+ })
1102
+ ]);
1103
+ } finally {
1104
+ if (timer) clearTimeout(timer);
1105
+ ctx.signal?.removeEventListener?.("abort", onAbort);
1106
+ }
1107
+ } catch (err) {
1108
+ threw = err;
1109
+ } finally {
1110
+ if (typeof console !== "undefined") {
1111
+ for (const m of consoleMethods) {
1112
+ if (originals[m]) {
1113
+ console[m] = originals[m];
1114
+ }
1115
+ }
1116
+ }
1117
+ }
1118
+ if (threw !== void 0) {
1119
+ const e = threw instanceof Error ? threw : new Error(String(threw));
1120
+ const stackTruncated = typeof e.stack === "string" ? e.stack.split("\n").slice(0, 20).join("\n") : void 0;
1121
+ const detail = {
1122
+ name: e.name,
1123
+ message: e.message,
1124
+ stack: stackTruncated,
1125
+ logs,
1126
+ durationMs: Date.now() - start
1127
+ };
1128
+ const wrapped = new Error(JSON.stringify(detail));
1129
+ wrapped.detail = detail;
1130
+ throw wrapped;
1131
+ }
1132
+ return {
1133
+ ok: true,
1134
+ value: execSafeClone(value),
1135
+ logs,
1136
+ durationMs: Date.now() - start
1137
+ };
1138
+ };
1139
+ }
1140
+ function clamp(n, lo, hi) {
1141
+ if (!Number.isFinite(n)) return lo;
1142
+ return Math.min(Math.max(n, lo), hi);
1143
+ }
1144
+ function execSafeClone(v) {
1145
+ try {
1146
+ return JSON.parse(JSON.stringify(v, (_k, val) => {
1147
+ if (typeof val === "function") {
1148
+ return `[Function: ${val.name || "anonymous"}]`;
1149
+ }
1150
+ if (val instanceof Error) {
1151
+ return { name: val.name, message: val.message, stack: val.stack };
1152
+ }
1153
+ if (typeof val === "bigint") return val.toString() + "n";
1154
+ if (typeof val === "undefined") return null;
1155
+ if (val && typeof val === "object" && val.nodeType === 1) {
1156
+ const el = val;
1157
+ return `[${el.tagName ?? "Element"}${el.id ? "#" + el.id : ""}]`;
1158
+ }
1159
+ return val;
1160
+ }));
1161
+ } catch {
1162
+ return { __unserializable: typeof v };
1163
+ }
1164
+ }
996
1165
  function safeStringify(v) {
997
1166
  try {
998
1167
  const seen = /* @__PURE__ */ new WeakSet();
@@ -1430,6 +1599,6 @@ async function enableRemoteDebug(options) {
1430
1599
  };
1431
1600
  }
1432
1601
 
1433
- export { BotimConfigError, BotimConsentError, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, SCHEMA_VERSION, enableRemoteDebug };
1602
+ export { BotimConfigError, BotimConsentError, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, SCHEMA_VERSION, enableRemoteDebug, getBOT };
1434
1603
  //# sourceMappingURL=index.js.map
1435
1604
  //# sourceMappingURL=index.js.map