@klarkxy/dsh-deep-view 0.1.0 → 0.2.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.
@@ -1027,6 +1027,281 @@ function validateForm(node, values) {
1027
1027
  return { fields };
1028
1028
  }
1029
1029
  //#endregion
1030
+ //#region ../dsh-plugin-kit/src/log-shared.ts
1031
+ const PLUGIN_LOG_LEVELS = [
1032
+ "debug",
1033
+ "info",
1034
+ "warn",
1035
+ "error",
1036
+ "fault"
1037
+ ];
1038
+ const DEFAULT_PLUGIN_LOG_LEVEL = "info";
1039
+ const PLUGIN_LOG_MAX_MESSAGE_CHARS = 16e3;
1040
+ const SOURCE_RE = /^[a-z][a-z0-9-]{0,63}$/;
1041
+ const EVENT_RE = /^[a-z][a-z0-9._-]{0,127}$/;
1042
+ const CONTEXT_KEY_RE = /^[a-zA-Z][a-zA-Z0-9._-]{0,63}$/;
1043
+ const SECRET_KEY_RE = /(?:api[_-]?key|access[_-]?token|refresh[_-]?token|bearer|authorization|cookie|set[_-]?cookie|nonce|credential|private[_-]?key|password|secret|app[_-]?secret|client[_-]?secret|z_c0|d_c0)$/i;
1044
+ const CHAT_PAYLOAD_RE = /"role"\s*:\s*"(?:system|user|assistant|tool)"|"(?:messages|content|prompt|input_text|input_image|image_url)"\s*:\s*[\[{"']/i;
1045
+ const CREDENTIAL_BODY_RE = /-----BEGIN (?:RSA |EC |OPENSSH |PRIVATE )?PRIVATE KEY-----|"type"\s*:\s*"service_account"|credentials\.json|\.dsh[/\\](?:credentials|auth)/i;
1046
+ const AUTH_HEADER_RE = /\b(Authorization|Proxy-Authorization|Cookie|Set-Cookie)\s*:\s*[^\r\n]*/gi;
1047
+ const BEARER_RE = /\bBearer\s+[A-Za-z0-9._\-+=/]{8,}/g;
1048
+ const SECRET_FIELD_RE = /(["']?(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|bearer|authorization|cookie|set-cookie|nonce|credential|private[_-]?key|password|secret|app[_-]?secret|client[_-]?secret|password|z_c0|d_c0)["']?\s*[:=]\s*)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,&;}]+)/gi;
1049
+ const SECRET_QUERY_RE = /([?&](?:token|api[_-]?key|key|secret|password|bootstrap|code|sig|signature|credential|password)=)[^\s&#"']+/gi;
1050
+ const URL_CREDENTIAL_RE = /\b(https?:\/\/)[^\s/:@]+:[^\s/@]+@/gi;
1051
+ const BOOTSTRAP_RE = /\/bootstrap\/[^\s"'?&#]+/gi;
1052
+ const KNOWN_TOKEN_RE = /\b(?:sk-[A-Za-z0-9_-]{10,}|gh[pousr]_[A-Za-z0-9_]{16,}|github_pat_[A-Za-z0-9_]{20,}|tvly-[A-Za-z0-9_-]{10,}|AKIA[0-9A-Z]{16})\b/g;
1053
+ const PATH_RE = /(?:[A-Za-z]:\\|\\\\)[^\s"'`<>|]+|(?:\/(?:home|Users|tmp|var|root)\/|~\/)[^\s"'`<>|]+/g;
1054
+ function isPluginLogLevel(value) {
1055
+ return typeof value === "string" && PLUGIN_LOG_LEVELS.includes(value);
1056
+ }
1057
+ function pluginLogLevelEnabled(level, minimum) {
1058
+ return PLUGIN_LOG_LEVELS.indexOf(level) >= PLUGIN_LOG_LEVELS.indexOf(minimum);
1059
+ }
1060
+ function normalizePluginLogSource(raw) {
1061
+ const stripped = String(raw || "").trim().replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
1062
+ const bounded = (/^[a-z]/.test(stripped) ? stripped : `p-${stripped}`).slice(0, 64).replace(/-+$/g, "");
1063
+ return SOURCE_RE.test(bounded) ? bounded : "dsh-plugin";
1064
+ }
1065
+ function normalizePluginLogEvent(raw) {
1066
+ const cleaned = String(raw || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z]+/, "").slice(0, 128);
1067
+ return EVENT_RE.test(cleaned) ? cleaned : "plugin.event";
1068
+ }
1069
+ /**
1070
+ * Parse a level specification like `debug` or `dsh-zhihu=debug,dsh-dot:warn,info`.
1071
+ * Source-specific rules win over a bare fallback level.
1072
+ */
1073
+ function parsePluginLogLevel(spec, source, fallback = DEFAULT_PLUGIN_LOG_LEVEL) {
1074
+ if (!spec || typeof spec !== "string") return fallback;
1075
+ const normalizedSource = source ? normalizePluginLogSource(source) : void 0;
1076
+ let wildcard;
1077
+ for (const token of spec.split(",").map((part) => part.trim()).filter(Boolean)) {
1078
+ const lowered = token.toLowerCase();
1079
+ if (isPluginLogLevel(lowered)) {
1080
+ wildcard = lowered;
1081
+ continue;
1082
+ }
1083
+ const match = /^([^=:]+)[=:]([a-z]+)$/i.exec(token);
1084
+ if (!match) continue;
1085
+ const target = match[1].trim();
1086
+ const candidate = match[2].trim().toLowerCase();
1087
+ if (!isPluginLogLevel(candidate)) continue;
1088
+ if (target === "*") {
1089
+ wildcard = candidate;
1090
+ continue;
1091
+ }
1092
+ if (normalizedSource && normalizePluginLogSource(target) === normalizedSource) return candidate;
1093
+ }
1094
+ return wildcard ?? fallback;
1095
+ }
1096
+ function sanitizePluginLogText(text, maxChars = PLUGIN_LOG_MAX_MESSAGE_CHARS) {
1097
+ if (!text) return "";
1098
+ if (CREDENTIAL_BODY_RE.test(text)) return "[omitted credential material]";
1099
+ if (CHAT_PAYLOAD_RE.test(text)) return "[omitted chat payload]";
1100
+ return truncateLogText(text.replace(AUTH_HEADER_RE, "$1: [redacted]").replace(BEARER_RE, "Bearer [redacted]").replace(SECRET_FIELD_RE, (full, prefix) => {
1101
+ const value = full.slice(prefix.length);
1102
+ if (value.startsWith("\"")) return `${prefix}"[redacted]"`;
1103
+ if (value.startsWith("'")) return `${prefix}'[redacted]'`;
1104
+ return `${prefix}[redacted]`;
1105
+ }).replace(SECRET_QUERY_RE, "$1[redacted]").replace(URL_CREDENTIAL_RE, "$1[redacted]@").replace(BOOTSTRAP_RE, "/bootstrap/[redacted]").replace(KNOWN_TOKEN_RE, "[redacted]").replace(PATH_RE, "[path]"), maxChars);
1106
+ }
1107
+ function sanitizePluginLogContext(input) {
1108
+ if (!input) return void 0;
1109
+ const output = {};
1110
+ for (const [key, value] of Object.entries(input).slice(0, 32)) {
1111
+ if (value === void 0 || !CONTEXT_KEY_RE.test(key)) continue;
1112
+ if (typeof value === "number" && !Number.isFinite(value)) continue;
1113
+ if (SECRET_KEY_RE.test(key)) {
1114
+ output[key] = "[redacted]";
1115
+ continue;
1116
+ }
1117
+ output[key] = typeof value === "string" ? sanitizePluginLogText(value, 1e3) : value;
1118
+ }
1119
+ if (!Object.keys(output).length) return void 0;
1120
+ if (JSON.stringify(output).length <= 8e3) return output;
1121
+ return {
1122
+ omitted: true,
1123
+ reason: "Diagnostic context exceeded its size limit."
1124
+ };
1125
+ }
1126
+ function sanitizePluginLogError(error) {
1127
+ if (error === void 0) return void 0;
1128
+ const row = error && typeof error === "object" ? error : {};
1129
+ const rawMessage = typeof row.message === "string" ? row.message : String(error);
1130
+ const result = {
1131
+ name: typeof row.name === "string" && row.name.trim() ? truncateLogText(row.name.trim(), 100) : "Error",
1132
+ message: sanitizePluginLogText(rawMessage) || "Unknown error"
1133
+ };
1134
+ if (typeof row.stack === "string" && row.stack.trim()) result.stack = sanitizePluginLogText(row.stack, 24e3);
1135
+ if (typeof row.code === "string" && /^[A-Za-z0-9_./:-]{1,100}$/.test(row.code)) result.code = row.code;
1136
+ else if (typeof row.code === "number" && Number.isFinite(row.code)) result.code = String(row.code);
1137
+ return result;
1138
+ }
1139
+ function mergePluginLogContext(base, extra) {
1140
+ if (!base) return extra;
1141
+ if (!extra) return base;
1142
+ return {
1143
+ ...base,
1144
+ ...extra
1145
+ };
1146
+ }
1147
+ function buildPluginLogEntry(input, meta) {
1148
+ const entry = {
1149
+ version: 1,
1150
+ at: meta.at.toISOString(),
1151
+ level: isPluginLogLevel(input.level) ? input.level : "info",
1152
+ source: normalizePluginLogSource(meta.source),
1153
+ surface: meta.surface,
1154
+ ...meta.profile ? { profile: meta.profile } : {},
1155
+ event: normalizePluginLogEvent(input.event),
1156
+ message: sanitizePluginLogText(input.message || "No diagnostic message was provided.", PLUGIN_LOG_MAX_MESSAGE_CHARS),
1157
+ ...meta.pid !== void 0 ? { pid: meta.pid } : {}
1158
+ };
1159
+ const context = sanitizePluginLogContext(mergePluginLogContext(meta.correlation, input.context));
1160
+ if (context) entry.context = context;
1161
+ const error = sanitizePluginLogError(input.error);
1162
+ if (error) entry.error = error;
1163
+ return entry;
1164
+ }
1165
+ function createChildPluginLogger(parent, bound) {
1166
+ return {
1167
+ get source() {
1168
+ return parent.source;
1169
+ },
1170
+ get file() {
1171
+ return parent.file;
1172
+ },
1173
+ get lastWriteError() {
1174
+ return parent.lastWriteError;
1175
+ },
1176
+ debug: (event, message, context) => parent.debug(event, message, mergePluginLogContext(bound, context)),
1177
+ info: (event, message, context) => parent.info(event, message, mergePluginLogContext(bound, context)),
1178
+ warn: (event, message, error, context) => parent.warn(event, message, error, mergePluginLogContext(bound, context)),
1179
+ error: (event, message, error, context) => parent.error(event, message, error, mergePluginLogContext(bound, context)),
1180
+ fault: (event, message, error, context) => parent.fault(event, message, error, mergePluginLogContext(bound, context)),
1181
+ write: (input) => parent.write({
1182
+ ...input,
1183
+ context: mergePluginLogContext(bound, input.context)
1184
+ }),
1185
+ child: (next) => createChildPluginLogger(parent, mergePluginLogContext(bound, next) ?? {})
1186
+ };
1187
+ }
1188
+ function truncateLogText(text, maxChars) {
1189
+ if (text.length <= maxChars) return text;
1190
+ const marker = "[truncated]\n";
1191
+ if (maxChars <= 12) return text.slice(0, Math.max(0, maxChars));
1192
+ return `${marker}${text.slice(-(maxChars - 12))}`;
1193
+ }
1194
+ //#endregion
1195
+ //#region ../dsh-plugin-kit/src/client-logger.ts
1196
+ const CLIENT_PLUGIN_LOG_GLOBAL = "__KLARKXY_PLUGIN_LOGS__";
1197
+ function readStorageFlag(key) {
1198
+ try {
1199
+ const value = (globalThis.window?.localStorage)?.getItem?.(key);
1200
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
1201
+ } catch {
1202
+ return;
1203
+ }
1204
+ }
1205
+ function sharedClientBuffer(limit) {
1206
+ const carrier = globalThis;
1207
+ if (!Array.isArray(carrier["__KLARKXY_PLUGIN_LOGS__"])) try {
1208
+ carrier[CLIENT_PLUGIN_LOG_GLOBAL] = [];
1209
+ } catch {
1210
+ return [];
1211
+ }
1212
+ const list = carrier[CLIENT_PLUGIN_LOG_GLOBAL];
1213
+ const overflow = list.length - limit;
1214
+ if (overflow > 0) list.splice(0, overflow);
1215
+ return list;
1216
+ }
1217
+ function createClientPluginLogger(source, ctx, options = {}) {
1218
+ const normalized = normalizePluginLogSource(source);
1219
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
1220
+ const limit = Math.max(10, options.bufferLimit ?? 500);
1221
+ const browserCtx = ctx && typeof ctx === "object" ? ctx : void 0;
1222
+ const resolveMinimum = () => {
1223
+ if (options.minimumLevel) return options.minimumLevel;
1224
+ return parsePluginLogLevel(readStorageFlag("dsh-plugin-log-level"), normalized, DEFAULT_PLUGIN_LOG_LEVEL);
1225
+ };
1226
+ const logger = {
1227
+ source: normalized,
1228
+ file: null,
1229
+ lastWriteError: null,
1230
+ debug: (event, message, context) => logger.write({
1231
+ level: "debug",
1232
+ event,
1233
+ message,
1234
+ ...context ? { context } : {}
1235
+ }),
1236
+ info: (event, message, context) => logger.write({
1237
+ level: "info",
1238
+ event,
1239
+ message,
1240
+ ...context ? { context } : {}
1241
+ }),
1242
+ warn: (event, message, error, context) => logger.write({
1243
+ level: "warn",
1244
+ event,
1245
+ message,
1246
+ ...error !== void 0 ? { error } : {},
1247
+ ...context ? { context } : {}
1248
+ }),
1249
+ error: (event, message, error, context) => logger.write({
1250
+ level: "error",
1251
+ event,
1252
+ message,
1253
+ ...error !== void 0 ? { error } : {},
1254
+ ...context ? { context } : {}
1255
+ }),
1256
+ fault: (event, message, error, context) => logger.write({
1257
+ level: "fault",
1258
+ event,
1259
+ message,
1260
+ ...error !== void 0 ? { error } : {},
1261
+ ...context ? { context } : {}
1262
+ }),
1263
+ child: (context) => createChildPluginLogger(logger, context),
1264
+ write(input) {
1265
+ try {
1266
+ if (!pluginLogLevelEnabled(input.level, resolveMinimum())) return false;
1267
+ const entry = buildPluginLogEntry(input, {
1268
+ at: now(),
1269
+ source: normalized,
1270
+ surface: "client"
1271
+ });
1272
+ const buffer = sharedClientBuffer(limit);
1273
+ buffer.push(entry);
1274
+ const overflow = buffer.length - limit;
1275
+ if (overflow > 0) buffer.splice(0, overflow);
1276
+ if (options.forwardToHostLogger !== false && browserCtx?.logger) {
1277
+ const summary = sanitizePluginLogText(`${normalized} [${entry.event}]: ${entry.message}`, 500);
1278
+ if (entry.level === "warn" && typeof browserCtx.logger.warn === "function") browserCtx.logger.warn(summary);
1279
+ else if ((entry.level === "error" || entry.level === "fault") && typeof browserCtx.logger.error === "function") browserCtx.logger.error(summary);
1280
+ }
1281
+ if (readStorageFlag("dsh-plugin-log-console") === "1") {
1282
+ const con = globalThis.console;
1283
+ (entry.level === "debug" ? con?.debug : entry.level === "info" ? con?.info : entry.level === "warn" ? con?.warn : con?.error)?.call(con, `[${normalized}] ${entry.event}: ${entry.message}`, entry);
1284
+ }
1285
+ return true;
1286
+ } catch {
1287
+ return false;
1288
+ }
1289
+ }
1290
+ };
1291
+ return logger;
1292
+ }
1293
+ function installClientPluginLogger(ctx, source, options = {}) {
1294
+ const logger = createClientPluginLogger(source, ctx, options);
1295
+ logger.info("client.loaded", "Client plugin initialized.", options.loadedContext);
1296
+ const browserCtx = ctx && typeof ctx === "object" ? ctx : void 0;
1297
+ if (typeof browserCtx?.effect === "function") try {
1298
+ browserCtx.effect(() => () => {
1299
+ logger.debug("client.disposed", "Client plugin disposed.");
1300
+ }, `${logger.source}.client-logger`);
1301
+ } catch {}
1302
+ return logger;
1303
+ }
1304
+ //#endregion
1030
1305
  //#region src/contracts.ts
1031
1306
  const PLUGIN_NAME = "@klarkxy/dsh-deep-view";
1032
1307
  const RPC_CHANNEL = "/dsh-deep-view";
@@ -2693,14 +2968,7 @@ function sameAnswers(left, right) {
2693
2968
  });
2694
2969
  }
2695
2970
  //#endregion
2696
- //#region src/client.tsx
2697
- const name = "dsh-deep-view-client";
2698
- const inject = [
2699
- "slots",
2700
- "connection",
2701
- "locale"
2702
- ];
2703
- const LOCALE = "deep-view";
2971
+ //#region src/copy.ts
2704
2972
  const uiZh = {
2705
2973
  copy: "复制",
2706
2974
  copied: "已复制",
@@ -2765,6 +3033,15 @@ const uiEn = {
2765
3033
  send: "Send",
2766
3034
  selected: "Selected"
2767
3035
  };
3036
+ //#endregion
3037
+ //#region src/client.tsx
3038
+ const name = "dsh-deep-view-client";
3039
+ const inject = [
3040
+ "slots",
3041
+ "connection",
3042
+ "locale"
3043
+ ];
3044
+ const LOCALE = "deep-view";
2768
3045
  const viewT = (t) => (key, params) => {
2769
3046
  const keys = {
2770
3047
  Copy: "copy",
@@ -2921,15 +3198,29 @@ function ToolCallView(props) {
2921
3198
  action,
2922
3199
  values
2923
3200
  }).then((result) => {
2924
- if (result?.ok) {
3201
+ const envelope = result;
3202
+ if (envelope?.ok) {
2925
3203
  setAnswer({
2926
3204
  id: pending.id,
2927
3205
  action,
2928
3206
  values
2929
3207
  });
2930
3208
  setPending(null);
2931
- } else setFailed(true);
2932
- }).catch(() => setFailed(true)).finally(() => setBusy(false));
3209
+ } else {
3210
+ createClientPluginLogger(PLUGIN_NAME).warn("reply.rejected", "Deep View reply was rejected by host.", void 0, {
3211
+ sessionId: props.sessionId,
3212
+ viewId: pending.id,
3213
+ code: envelope && !envelope.ok ? envelope.error.code : null
3214
+ });
3215
+ setFailed(true);
3216
+ }
3217
+ }).catch((error) => {
3218
+ createClientPluginLogger(PLUGIN_NAME).error("reply.transport-error", "Deep View reply RPC failed.", error, {
3219
+ sessionId: props.sessionId,
3220
+ viewId: pending.id
3221
+ });
3222
+ setFailed(true);
3223
+ }).finally(() => setBusy(false));
2933
3224
  }, [
2934
3225
  props.call,
2935
3226
  props.sessionId,
@@ -2961,7 +3252,7 @@ function ToolCallView(props) {
2961
3252
  view: shown.view,
2962
3253
  onSubmit: canAnswer ? submit : void 0,
2963
3254
  busy,
2964
- settled: answer !== null || !canAnswer,
3255
+ settled: answer !== null || shown.awaitReply && !canAnswer,
2965
3256
  answer,
2966
3257
  t: viewT(t),
2967
3258
  labels,
@@ -3029,8 +3320,65 @@ function ToolCallStatus(props) {
3029
3320
  children: props.t(read.isError ? "error" : read.phase === "result" && !read.presentation ? "invalid" : read.phase === "result" ? "prepared" : "preparing")
3030
3321
  });
3031
3322
  }
3323
+ /** Client service name other plugins read to draw show_view cards in their own surfaces. */
3324
+ const CARDS_SERVICE = "dshDeepView";
3325
+ function staticArgs(values) {
3326
+ return {
3327
+ has: (key) => values[key] !== void 0,
3328
+ complete: (key) => values[key] !== void 0,
3329
+ text: (key) => typeof values[key] === "string" ? values[key] : void 0,
3330
+ value: (key) => values[key]
3331
+ };
3332
+ }
3333
+ /** One show_view card for a plugin-drawn chat: same card, same answer channel as the host chat. */
3334
+ function createCards(call, locale) {
3335
+ const subscribe = (listener) => locale?.subscribe?.(listener) ?? (() => {});
3336
+ const active = () => locale?.getSnapshot?.().active ?? "en";
3337
+ function Card(props) {
3338
+ const language = (0, react.useSyncExternalStore)(subscribe, active);
3339
+ const t = (0, react.useMemo)(() => {
3340
+ const table = language.toLowerCase().startsWith("zh") ? uiZh : uiEn;
3341
+ return (key, params) => (table[key] ?? key).replace(/\{(\w+)\}/g, (_, name) => String(params?.[name] ?? `{${name}}`));
3342
+ }, [language]);
3343
+ const block = (0, react.useMemo)(() => props.presentation !== void 0 ? {
3344
+ callId: props.callId,
3345
+ kind: "tool-result",
3346
+ meta: props.presentation,
3347
+ isError: false
3348
+ } : props.failed ? {
3349
+ callId: props.callId,
3350
+ kind: "tool-result",
3351
+ isError: true
3352
+ } : {
3353
+ callId: props.callId,
3354
+ args: staticArgs({
3355
+ title: props.title,
3356
+ view: props.view
3357
+ })
3358
+ }, [
3359
+ props.callId,
3360
+ props.presentation,
3361
+ props.failed,
3362
+ props.title,
3363
+ props.view
3364
+ ]);
3365
+ const phase = props.presentation !== void 0 || props.failed ? "result" : "start";
3366
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallView, {
3367
+ sessionId: props.sessionId,
3368
+ callId: props.callId,
3369
+ phase,
3370
+ block,
3371
+ t,
3372
+ call,
3373
+ ...props.answer ? { restoredAnswer: props.answer } : {}
3374
+ });
3375
+ }
3376
+ return { Card };
3377
+ }
3032
3378
  function apply(ctx) {
3033
3379
  const client = ctx;
3380
+ installClientPluginLogger(ctx, PLUGIN_NAME);
3381
+ client.provide(CARDS_SERVICE, createCards((endpoint, payload) => client.connection.rpc.call(RPC_CHANNEL, endpoint, payload), client.locale));
3034
3382
  ctx.effect(() => client.locale.register(LOCALE, {
3035
3383
  zh: { ...uiZh },
3036
3384
  en: { ...uiEn }
@@ -3057,11 +3405,13 @@ function apply(ctx) {
3057
3405
  }, ReplyView)), "deep-view.reply");
3058
3406
  }
3059
3407
  //#endregion
3408
+ exports.CARDS_SERVICE = CARDS_SERVICE;
3060
3409
  exports.LOCALE = LOCALE;
3061
3410
  exports.ReplyView = ReplyView;
3062
3411
  exports.ToolCallStatus = ToolCallStatus;
3063
3412
  exports.ToolCallView = ToolCallView;
3064
3413
  exports.apply = apply;
3414
+ exports.createCards = createCards;
3065
3415
  exports.inject = inject;
3066
3416
  exports.name = name;
3067
3417
  exports.readToolCall = readToolCall;