@klarkxy/dsh-deep-view 0.0.0-stage → 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.
package/lib/index.js ADDED
@@ -0,0 +1,1153 @@
1
+ import { _ as viewSummary, i as checkShownView, p as validateViewAnswer } from "./core2.js";
2
+ import { HOST_SERVICE, PLUGIN_NAME, RPC_CHANNEL, VIEW_OWNER, isRecord } from "./contracts.js";
3
+ import { answerText } from "./answer-text.js";
4
+ import { randomUUID } from "node:crypto";
5
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
6
+ import { AsyncLocalStorage } from "node:async_hooks";
7
+ import { appendFileSync, existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, unlinkSync } from "node:fs";
8
+ import { homedir } from "node:os";
9
+ import { basename, dirname, join, resolve, sep } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ //#region ../dsh-plugin-kit/src/log-shared.ts
12
+ const PLUGIN_LOG_LEVELS = [
13
+ "debug",
14
+ "info",
15
+ "warn",
16
+ "error",
17
+ "fault"
18
+ ];
19
+ const DEFAULT_PLUGIN_LOG_LEVEL = "info";
20
+ const PLUGIN_LOG_MAX_MESSAGE_CHARS = 16e3;
21
+ const SOURCE_RE = /^[a-z][a-z0-9-]{0,63}$/;
22
+ const EVENT_RE = /^[a-z][a-z0-9._-]{0,127}$/;
23
+ const CONTEXT_KEY_RE = /^[a-zA-Z][a-zA-Z0-9._-]{0,63}$/;
24
+ 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;
25
+ const CHAT_PAYLOAD_RE = /"role"\s*:\s*"(?:system|user|assistant|tool)"|"(?:messages|content|prompt|input_text|input_image|image_url)"\s*:\s*[\[{"']/i;
26
+ const CREDENTIAL_BODY_RE = /-----BEGIN (?:RSA |EC |OPENSSH |PRIVATE )?PRIVATE KEY-----|"type"\s*:\s*"service_account"|credentials\.json|\.dsh[/\\](?:credentials|auth)/i;
27
+ const AUTH_HEADER_RE = /\b(Authorization|Proxy-Authorization|Cookie|Set-Cookie)\s*:\s*[^\r\n]*/gi;
28
+ const BEARER_RE = /\bBearer\s+[A-Za-z0-9._\-+=/]{8,}/g;
29
+ 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;
30
+ const SECRET_QUERY_RE = /([?&](?:token|api[_-]?key|key|secret|password|bootstrap|code|sig|signature|credential|password)=)[^\s&#"']+/gi;
31
+ const URL_CREDENTIAL_RE = /\b(https?:\/\/)[^\s/:@]+:[^\s/@]+@/gi;
32
+ const BOOTSTRAP_RE = /\/bootstrap\/[^\s"'?&#]+/gi;
33
+ 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;
34
+ const PATH_RE = /(?:[A-Za-z]:\\|\\\\)[^\s"'`<>|]+|(?:\/(?:home|Users|tmp|var|root)\/|~\/)[^\s"'`<>|]+/g;
35
+ function isPluginLogLevel(value) {
36
+ return typeof value === "string" && PLUGIN_LOG_LEVELS.includes(value);
37
+ }
38
+ function pluginLogLevelEnabled(level, minimum) {
39
+ return PLUGIN_LOG_LEVELS.indexOf(level) >= PLUGIN_LOG_LEVELS.indexOf(minimum);
40
+ }
41
+ function normalizePluginLogSource(raw) {
42
+ const stripped = String(raw || "").trim().replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
43
+ const bounded = (/^[a-z]/.test(stripped) ? stripped : `p-${stripped}`).slice(0, 64).replace(/-+$/g, "");
44
+ return SOURCE_RE.test(bounded) ? bounded : "dsh-plugin";
45
+ }
46
+ function normalizePluginLogEvent(raw) {
47
+ const cleaned = String(raw || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z]+/, "").slice(0, 128);
48
+ return EVENT_RE.test(cleaned) ? cleaned : "plugin.event";
49
+ }
50
+ /**
51
+ * Parse a level specification like `debug` or `dsh-zhihu=debug,dsh-dot:warn,info`.
52
+ * Source-specific rules win over a bare fallback level.
53
+ */
54
+ function parsePluginLogLevel(spec, source, fallback = DEFAULT_PLUGIN_LOG_LEVEL) {
55
+ if (!spec || typeof spec !== "string") return fallback;
56
+ const normalizedSource = source ? normalizePluginLogSource(source) : void 0;
57
+ let wildcard;
58
+ for (const token of spec.split(",").map((part) => part.trim()).filter(Boolean)) {
59
+ const lowered = token.toLowerCase();
60
+ if (isPluginLogLevel(lowered)) {
61
+ wildcard = lowered;
62
+ continue;
63
+ }
64
+ const match = /^([^=:]+)[=:]([a-z]+)$/i.exec(token);
65
+ if (!match) continue;
66
+ const target = match[1].trim();
67
+ const candidate = match[2].trim().toLowerCase();
68
+ if (!isPluginLogLevel(candidate)) continue;
69
+ if (target === "*") {
70
+ wildcard = candidate;
71
+ continue;
72
+ }
73
+ if (normalizedSource && normalizePluginLogSource(target) === normalizedSource) return candidate;
74
+ }
75
+ return wildcard ?? fallback;
76
+ }
77
+ function sanitizePluginLogText(text, maxChars = PLUGIN_LOG_MAX_MESSAGE_CHARS) {
78
+ if (!text) return "";
79
+ if (CREDENTIAL_BODY_RE.test(text)) return "[omitted credential material]";
80
+ if (CHAT_PAYLOAD_RE.test(text)) return "[omitted chat payload]";
81
+ return truncateLogText(text.replace(AUTH_HEADER_RE, "$1: [redacted]").replace(BEARER_RE, "Bearer [redacted]").replace(SECRET_FIELD_RE, (full, prefix) => {
82
+ const value = full.slice(prefix.length);
83
+ if (value.startsWith("\"")) return `${prefix}"[redacted]"`;
84
+ if (value.startsWith("'")) return `${prefix}'[redacted]'`;
85
+ return `${prefix}[redacted]`;
86
+ }).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);
87
+ }
88
+ function sanitizePluginLogContext(input) {
89
+ if (!input) return void 0;
90
+ const output = {};
91
+ for (const [key, value] of Object.entries(input).slice(0, 32)) {
92
+ if (value === void 0 || !CONTEXT_KEY_RE.test(key)) continue;
93
+ if (typeof value === "number" && !Number.isFinite(value)) continue;
94
+ if (SECRET_KEY_RE.test(key)) {
95
+ output[key] = "[redacted]";
96
+ continue;
97
+ }
98
+ output[key] = typeof value === "string" ? sanitizePluginLogText(value, 1e3) : value;
99
+ }
100
+ if (!Object.keys(output).length) return void 0;
101
+ if (JSON.stringify(output).length <= 8e3) return output;
102
+ return {
103
+ omitted: true,
104
+ reason: "Diagnostic context exceeded its size limit."
105
+ };
106
+ }
107
+ function sanitizePluginLogError(error) {
108
+ if (error === void 0) return void 0;
109
+ const row = error && typeof error === "object" ? error : {};
110
+ const rawMessage = typeof row.message === "string" ? row.message : String(error);
111
+ const result = {
112
+ name: typeof row.name === "string" && row.name.trim() ? truncateLogText(row.name.trim(), 100) : "Error",
113
+ message: sanitizePluginLogText(rawMessage) || "Unknown error"
114
+ };
115
+ if (typeof row.stack === "string" && row.stack.trim()) result.stack = sanitizePluginLogText(row.stack, 24e3);
116
+ if (typeof row.code === "string" && /^[A-Za-z0-9_./:-]{1,100}$/.test(row.code)) result.code = row.code;
117
+ else if (typeof row.code === "number" && Number.isFinite(row.code)) result.code = String(row.code);
118
+ return result;
119
+ }
120
+ function mergePluginLogContext(base, extra) {
121
+ if (!base) return extra;
122
+ if (!extra) return base;
123
+ return {
124
+ ...base,
125
+ ...extra
126
+ };
127
+ }
128
+ function buildPluginLogEntry(input, meta) {
129
+ const entry = {
130
+ version: 1,
131
+ at: meta.at.toISOString(),
132
+ level: isPluginLogLevel(input.level) ? input.level : "info",
133
+ source: normalizePluginLogSource(meta.source),
134
+ surface: meta.surface,
135
+ ...meta.profile ? { profile: meta.profile } : {},
136
+ event: normalizePluginLogEvent(input.event),
137
+ message: sanitizePluginLogText(input.message || "No diagnostic message was provided.", PLUGIN_LOG_MAX_MESSAGE_CHARS),
138
+ ...meta.pid !== void 0 ? { pid: meta.pid } : {}
139
+ };
140
+ const context = sanitizePluginLogContext(mergePluginLogContext(meta.correlation, input.context));
141
+ if (context) entry.context = context;
142
+ const error = sanitizePluginLogError(input.error);
143
+ if (error) entry.error = error;
144
+ return entry;
145
+ }
146
+ function createChildPluginLogger(parent, bound) {
147
+ return {
148
+ get source() {
149
+ return parent.source;
150
+ },
151
+ get file() {
152
+ return parent.file;
153
+ },
154
+ get lastWriteError() {
155
+ return parent.lastWriteError;
156
+ },
157
+ debug: (event, message, context) => parent.debug(event, message, mergePluginLogContext(bound, context)),
158
+ info: (event, message, context) => parent.info(event, message, mergePluginLogContext(bound, context)),
159
+ warn: (event, message, error, context) => parent.warn(event, message, error, mergePluginLogContext(bound, context)),
160
+ error: (event, message, error, context) => parent.error(event, message, error, mergePluginLogContext(bound, context)),
161
+ fault: (event, message, error, context) => parent.fault(event, message, error, mergePluginLogContext(bound, context)),
162
+ write: (input) => parent.write({
163
+ ...input,
164
+ context: mergePluginLogContext(bound, input.context)
165
+ }),
166
+ child: (next) => createChildPluginLogger(parent, mergePluginLogContext(bound, next) ?? {})
167
+ };
168
+ }
169
+ function truncateLogText(text, maxChars) {
170
+ if (text.length <= maxChars) return text;
171
+ const marker = "[truncated]\n";
172
+ if (maxChars <= 12) return text.slice(0, Math.max(0, maxChars));
173
+ return `${marker}${text.slice(-(maxChars - 12))}`;
174
+ }
175
+ const PROFILE_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
176
+ const ARCHIVE_RE = /^[a-z][a-z0-9-]{0,63}\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z\.\d+\.\d+\.jsonl$/;
177
+ const correlationStorage = new AsyncLocalStorage();
178
+ const processFileLoggers = /* @__PURE__ */ new Map();
179
+ let operationSequence = 0;
180
+ function safeProfileName(profile) {
181
+ const trimmed = String(profile ?? "").trim();
182
+ if (PROFILE_RE.test(trimmed)) return trimmed.toLowerCase();
183
+ const cleaned = trimmed.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
184
+ return cleaned && PROFILE_RE.test(cleaned) ? cleaned : "default";
185
+ }
186
+ function pluginLogDirectory(home, profile = "default") {
187
+ return join(resolve(home), "logs", "plugins", safeProfileName(profile));
188
+ }
189
+ function resolveHostLogTarget(ctx, options = {}) {
190
+ const env = options.env ?? process.env;
191
+ if (options.directory?.trim()) {
192
+ const dir = resolve(options.directory.trim());
193
+ return {
194
+ directory: dir,
195
+ anchor: dir,
196
+ ...options.profile ? { profile: safeProfileName(options.profile) } : {}
197
+ };
198
+ }
199
+ if (options.home?.trim()) {
200
+ const home = resolve(options.home.trim());
201
+ const profile = safeProfileName(options.profile);
202
+ return {
203
+ directory: pluginLogDirectory(home, profile),
204
+ anchor: home,
205
+ profile
206
+ };
207
+ }
208
+ if (env.DSH_PLUGIN_LOG_DIR?.trim()) {
209
+ const dir = resolve(env.DSH_PLUGIN_LOG_DIR.trim());
210
+ return {
211
+ directory: dir,
212
+ anchor: dir,
213
+ ...options.profile ? { profile: safeProfileName(options.profile) } : {}
214
+ };
215
+ }
216
+ const host = ctx && typeof ctx === "object" ? ctx : void 0;
217
+ let profileCtx;
218
+ try {
219
+ profileCtx = host?.get?.("profileContext", false);
220
+ } catch {
221
+ try {
222
+ profileCtx = host?.get?.("profileContext");
223
+ } catch {
224
+ profileCtx = void 0;
225
+ }
226
+ }
227
+ if (typeof profileCtx?.home === "string" && profileCtx.home.trim() && typeof profileCtx.dir === "string" && profileCtx.dir.trim()) {
228
+ const home = resolve(profileCtx.home.trim());
229
+ const dir = resolve(profileCtx.dir.trim());
230
+ if (isInside(home, dir)) {
231
+ const profile = safeProfileName(typeof profileCtx.name === "string" ? profileCtx.name : basename(dir));
232
+ return {
233
+ directory: pluginLogDirectory(home, profile),
234
+ anchor: home,
235
+ profile
236
+ };
237
+ }
238
+ }
239
+ if (!Boolean(env.VITEST || env.NODE_TEST_CONTEXT) && typeof host?.baseUrl === "string" && host.baseUrl.trim()) {
240
+ const basePath = pathFromBaseUrl(host.baseUrl);
241
+ const configuredHome = resolve((env.DSH_HOME?.trim() || join(homedir(), ".dsh")).trim());
242
+ const profilesRoot = join(configuredHome, "profiles");
243
+ if (basePath && isInside(profilesRoot, basePath) && !samePath(profilesRoot, basePath)) {
244
+ const firstSegment = basePath.slice(profilesRoot.length).replace(/^[/\\]+/, "").split(/[/\\]/)[0];
245
+ if (firstSegment) {
246
+ const profile = safeProfileName(firstSegment);
247
+ return {
248
+ directory: pluginLogDirectory(configuredHome, profile),
249
+ anchor: configuredHome,
250
+ profile
251
+ };
252
+ }
253
+ }
254
+ }
255
+ return {
256
+ directory: null,
257
+ anchor: null,
258
+ ...options.profile ? { profile: safeProfileName(options.profile) } : {}
259
+ };
260
+ }
261
+ /**
262
+ * Synchronous, append-only rotating JSONL logger modeled on DSH Spaces
263
+ * diagnostics: 7-day or 100 MiB active rotation, 7-day / 100 MiB archive
264
+ * retention, secret and path redaction, and non-throwing write resilience.
265
+ */
266
+ var RotatingPluginLogger = class {
267
+ source;
268
+ file;
269
+ lastError = null;
270
+ size = 0;
271
+ openedAt = 0;
272
+ initialized = false;
273
+ writing = false;
274
+ sequence = 0;
275
+ loggedWriteFailure = false;
276
+ recent = [];
277
+ directory;
278
+ anchor;
279
+ profile;
280
+ minimum;
281
+ now;
282
+ pid;
283
+ maxFileBytes;
284
+ maxArchiveBytes;
285
+ maxAgeMs;
286
+ recentLimit;
287
+ constructor(source, options = {}) {
288
+ this.source = normalizePluginLogSource(source);
289
+ const env = options.env ?? process.env;
290
+ const resolved = options.directory !== void 0 || options.home !== void 0 ? resolveHostLogTarget(void 0, options) : {
291
+ directory: null,
292
+ anchor: options.anchor ?? null,
293
+ profile: options.profile
294
+ };
295
+ this.directory = resolved.directory;
296
+ this.anchor = options.anchor ?? resolved.anchor;
297
+ this.profile = options.profile ?? resolved.profile;
298
+ this.file = this.directory ? join(this.directory, `${this.source}.jsonl`) : null;
299
+ this.minimum = options.minimumLevel ?? parsePluginLogLevel(env.DSH_PLUGIN_LOG_LEVEL, this.source, "info");
300
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
301
+ this.pid = options.pid ?? process.pid;
302
+ this.maxFileBytes = options.maxFileBytes ?? 104857600;
303
+ this.maxArchiveBytes = options.maxArchiveBytes ?? 104857600;
304
+ this.maxAgeMs = options.maxAgeMs ?? 6048e5;
305
+ this.recentLimit = Math.max(1, options.recentLimit ?? 200);
306
+ }
307
+ get lastWriteError() {
308
+ return this.lastError;
309
+ }
310
+ recentEntries(limit = this.recentLimit) {
311
+ return this.recent.slice(-Math.max(0, limit));
312
+ }
313
+ debug(event, message, context) {
314
+ return this.write({
315
+ level: "debug",
316
+ event,
317
+ message,
318
+ ...context ? { context } : {}
319
+ });
320
+ }
321
+ info(event, message, context) {
322
+ return this.write({
323
+ level: "info",
324
+ event,
325
+ message,
326
+ ...context ? { context } : {}
327
+ });
328
+ }
329
+ warn(event, message, error, context) {
330
+ return this.write({
331
+ level: "warn",
332
+ event,
333
+ message,
334
+ ...error !== void 0 ? { error } : {},
335
+ ...context ? { context } : {}
336
+ });
337
+ }
338
+ error(event, message, error, context) {
339
+ return this.write({
340
+ level: "error",
341
+ event,
342
+ message,
343
+ ...error !== void 0 ? { error } : {},
344
+ ...context ? { context } : {}
345
+ });
346
+ }
347
+ fault(event, message, error, context) {
348
+ return this.write({
349
+ level: "fault",
350
+ event,
351
+ message,
352
+ ...error !== void 0 ? { error } : {},
353
+ ...context ? { context } : {}
354
+ });
355
+ }
356
+ child(context) {
357
+ return createChildPluginLogger(this, context);
358
+ }
359
+ write(input) {
360
+ if (!pluginLogLevelEnabled(input.level, this.minimum) || this.writing) return false;
361
+ const at = this.now();
362
+ let entry;
363
+ let line;
364
+ try {
365
+ entry = buildPluginLogEntry(input, {
366
+ at,
367
+ source: this.source,
368
+ surface: "host",
369
+ profile: this.profile,
370
+ pid: this.pid,
371
+ correlation: correlationStorage.getStore()
372
+ });
373
+ line = `${JSON.stringify(entry)}\n`;
374
+ } catch {
375
+ return false;
376
+ }
377
+ this.pushRecent(entry);
378
+ if (!this.directory || !this.file) return true;
379
+ this.writing = true;
380
+ try {
381
+ this.openManagedDirectory();
382
+ this.prepare(at);
383
+ if (this.size > 0 && (this.size + Buffer.byteLength(line) > this.maxFileBytes || at.getTime() - this.openedAt >= this.maxAgeMs)) this.rotate(at);
384
+ appendFileSync(this.file, line, {
385
+ encoding: "utf8",
386
+ mode: 384
387
+ });
388
+ this.size += Buffer.byteLength(line);
389
+ this.prune(at);
390
+ this.lastError = null;
391
+ this.loggedWriteFailure = false;
392
+ return true;
393
+ } catch (error) {
394
+ const detail = sanitizePluginLogText(error instanceof Error ? error.message : String(error), 500);
395
+ this.lastError = `The plugin diagnostic log could not be written: ${detail}`;
396
+ this.initialized = false;
397
+ if (!this.loggedWriteFailure) {
398
+ this.loggedWriteFailure = true;
399
+ try {
400
+ process.stderr.write(`DSH Plugin (${this.source}): ${this.lastError}. The original operation continues unchanged.\n`);
401
+ } catch {}
402
+ }
403
+ return false;
404
+ } finally {
405
+ this.writing = false;
406
+ }
407
+ }
408
+ pushRecent(entry) {
409
+ this.recent.push(entry);
410
+ const overflow = this.recent.length - this.recentLimit;
411
+ if (overflow > 0) this.recent.splice(0, overflow);
412
+ }
413
+ prepare(at) {
414
+ if (this.initialized || !this.file) return;
415
+ this.openManagedDirectory();
416
+ const stat = this.regularFile(this.file);
417
+ this.size = stat?.size ?? 0;
418
+ const stamps = stat ? [stat.birthtimeMs, stat.mtimeMs].filter((value) => value > 0) : [];
419
+ this.openedAt = stamps.length ? Math.min(...stamps) : at.getTime();
420
+ this.initialized = true;
421
+ }
422
+ rotate(at) {
423
+ if (!this.directory || !this.file) return;
424
+ const stamp = at.toISOString().replace(/:/g, "-");
425
+ const name = `${this.source}.${stamp}.${this.pid}.${this.sequence++}.jsonl`;
426
+ if (!ARCHIVE_RE.test(name)) throw new Error("Diagnostic log archive name is invalid.");
427
+ const archive = join(this.directory, name);
428
+ if (lexists(archive)) throw new Error("Diagnostic log archive already exists.");
429
+ try {
430
+ renameSync(this.file, archive);
431
+ } catch (error) {
432
+ if (error.code === "ENOENT") {
433
+ this.size = 0;
434
+ this.openedAt = at.getTime();
435
+ return;
436
+ }
437
+ throw error;
438
+ }
439
+ this.size = 0;
440
+ this.openedAt = at.getTime();
441
+ }
442
+ rotateStaleActive(at) {
443
+ if (!this.file) return;
444
+ const stat = this.regularFile(this.file);
445
+ if (!stat || stat.size === 0) return;
446
+ if (at.getTime() - stat.mtimeMs < this.maxAgeMs) return;
447
+ this.rotate(at);
448
+ }
449
+ prune(at) {
450
+ if (!this.directory) return;
451
+ this.rotateStaleActive(at);
452
+ const archives = [];
453
+ let total = 0;
454
+ for (const name of readdirSync(this.directory)) {
455
+ if (!ARCHIVE_RE.test(name) || !name.startsWith(`${this.source}.`)) continue;
456
+ const file = join(this.directory, name);
457
+ const stat = lstatSync(file);
458
+ if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("Diagnostic log archive is not a regular file.");
459
+ if (at.getTime() - stat.mtimeMs >= this.maxAgeMs) {
460
+ try {
461
+ unlinkSync(file);
462
+ } catch {}
463
+ continue;
464
+ }
465
+ archives.push({
466
+ name,
467
+ size: stat.size,
468
+ mtime: stat.mtimeMs
469
+ });
470
+ total += stat.size;
471
+ }
472
+ archives.sort((left, right) => left.mtime - right.mtime || left.name.localeCompare(right.name));
473
+ while (total > this.maxArchiveBytes && archives.length) {
474
+ const oldest = archives.shift();
475
+ try {
476
+ unlinkSync(join(this.directory, oldest.name));
477
+ } catch {}
478
+ total -= oldest.size;
479
+ }
480
+ }
481
+ openManagedDirectory() {
482
+ if (!this.directory) return;
483
+ if (this.anchor) {
484
+ if ((existsSync(this.anchor) ? lstatSync(this.anchor) : void 0)?.isSymbolicLink()) throw new Error("Diagnostic log anchor cannot be a symbolic link.");
485
+ }
486
+ mkdirSync(this.directory, {
487
+ recursive: true,
488
+ mode: 448
489
+ });
490
+ const stat = lstatSync(this.directory);
491
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Diagnostic log directory is not a real directory.");
492
+ const realDir = realpathSync(this.directory);
493
+ if (this.anchor) {
494
+ if (!isInside(realpathSync(this.anchor), realDir)) throw new Error("Diagnostic log directory escaped its managed root.");
495
+ }
496
+ }
497
+ regularFile(file) {
498
+ try {
499
+ const stat = lstatSync(file);
500
+ if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("Diagnostic log is not a regular file.");
501
+ const expected = join(realpathSync(dirname(file)), basename(file));
502
+ if (!samePath(realpathSync(file), expected)) throw new Error("Diagnostic log path could not be verified.");
503
+ return stat;
504
+ } catch (error) {
505
+ if (error.code === "ENOENT") return void 0;
506
+ throw error;
507
+ }
508
+ }
509
+ };
510
+ function createPluginLogger(ctxOrSource, maybeSource, maybeOptions) {
511
+ const hasCtx = typeof ctxOrSource !== "string";
512
+ const ctx = hasCtx ? ctxOrSource : void 0;
513
+ const source = hasCtx ? String(maybeSource ?? "dsh-plugin") : ctxOrSource;
514
+ const options = (hasCtx ? maybeOptions : maybeSource) ?? {};
515
+ const normalized = normalizePluginLogSource(source);
516
+ const target = resolveHostLogTarget(ctx, options);
517
+ let backing;
518
+ if (Boolean(target.directory && options.minimumLevel === void 0 && options.now === void 0 && options.pid === void 0 && options.maxFileBytes === void 0 && options.maxArchiveBytes === void 0 && options.maxAgeMs === void 0) && target.directory) {
519
+ const key = resolve(target.directory, `${normalized}.jsonl`);
520
+ let shared = processFileLoggers.get(key);
521
+ if (!shared) {
522
+ shared = new RotatingPluginLogger(normalized, {
523
+ ...options,
524
+ directory: target.directory,
525
+ anchor: target.anchor,
526
+ profile: target.profile
527
+ });
528
+ processFileLoggers.set(key, shared);
529
+ }
530
+ backing = shared;
531
+ } else backing = new RotatingPluginLogger(normalized, {
532
+ ...options,
533
+ ...target.directory ? {
534
+ directory: target.directory,
535
+ anchor: target.anchor
536
+ } : {},
537
+ ...target.profile ? { profile: target.profile } : {}
538
+ });
539
+ if (options.forwardHostWarnError === false || !ctx || typeof ctx !== "object") return backing;
540
+ return wrapWithHostForwarding(backing, ctx);
541
+ }
542
+ function installPluginLogger(ctx, source, options = {}) {
543
+ const logger = createPluginLogger(ctx, source, options);
544
+ logger.info("plugin.loaded", "Host plugin initialized.", options.loadedContext);
545
+ const host = ctx && typeof ctx === "object" ? ctx : void 0;
546
+ if (typeof host?.effect === "function") try {
547
+ host.effect(() => () => {
548
+ logger.debug("plugin.disposed", "Host plugin disposed.");
549
+ }, `${logger.source}.logger`);
550
+ } catch {}
551
+ return logger;
552
+ }
553
+ function wrapWithHostForwarding(backing, host) {
554
+ const forward = (input) => {
555
+ if (input.level !== "warn" && input.level !== "error" && input.level !== "fault") return;
556
+ try {
557
+ const hostLogger = host.logger;
558
+ if (!hostLogger) return;
559
+ const detail = input.error instanceof Error ? `${input.message} (${input.error.message})` : input.error !== void 0 ? `${input.message} (${String(input.error)})` : input.message;
560
+ const text = sanitizePluginLogText(`${backing.source} [${input.event}]: ${detail}`, 1e3);
561
+ if (input.level === "warn") {
562
+ if (typeof hostLogger.warn === "function") hostLogger.warn(text);
563
+ } else if (typeof hostLogger.error === "function") hostLogger.error(text);
564
+ else if (typeof hostLogger.warn === "function") hostLogger.warn(text);
565
+ } catch {}
566
+ };
567
+ const wrapped = {
568
+ get source() {
569
+ return backing.source;
570
+ },
571
+ get file() {
572
+ return backing.file;
573
+ },
574
+ get lastWriteError() {
575
+ return backing.lastWriteError;
576
+ },
577
+ debug: (event, message, context) => backing.debug(event, message, context),
578
+ info: (event, message, context) => backing.info(event, message, context),
579
+ warn: (event, message, error, context) => {
580
+ const wrote = backing.warn(event, message, error, context);
581
+ forward({
582
+ level: "warn",
583
+ event,
584
+ message,
585
+ error,
586
+ context
587
+ });
588
+ return wrote;
589
+ },
590
+ error: (event, message, error, context) => {
591
+ const wrote = backing.error(event, message, error, context);
592
+ forward({
593
+ level: "error",
594
+ event,
595
+ message,
596
+ error,
597
+ context
598
+ });
599
+ return wrote;
600
+ },
601
+ fault: (event, message, error, context) => {
602
+ const wrote = backing.fault(event, message, error, context);
603
+ forward({
604
+ level: "fault",
605
+ event,
606
+ message,
607
+ error,
608
+ context
609
+ });
610
+ return wrote;
611
+ },
612
+ write: (input) => {
613
+ const wrote = backing.write(input);
614
+ forward(input);
615
+ return wrote;
616
+ },
617
+ child: (context) => createChildPluginLogger(wrapped, context)
618
+ };
619
+ return wrapped;
620
+ }
621
+ function nextPluginOperationId(prefix = "op") {
622
+ return `${normalizePluginLogSource(prefix)}-${process.pid}-${++operationSequence}`;
623
+ }
624
+ function withPluginLogContext(context, action) {
625
+ const merged = mergePluginLogContext(correlationStorage.getStore(), context) ?? {};
626
+ return correlationStorage.run(merged, action);
627
+ }
628
+ function lexists(file) {
629
+ try {
630
+ lstatSync(file);
631
+ return true;
632
+ } catch (error) {
633
+ if (error.code === "ENOENT") return false;
634
+ throw error;
635
+ }
636
+ }
637
+ function pathFromBaseUrl(baseUrl) {
638
+ try {
639
+ return baseUrl.includes("://") ? resolve(fileURLToPath(baseUrl)) : resolve(baseUrl);
640
+ } catch {
641
+ return null;
642
+ }
643
+ }
644
+ function samePath(left, right) {
645
+ const a = resolve(left);
646
+ const b = resolve(right);
647
+ return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
648
+ }
649
+ function isInside(root, target) {
650
+ const rootResolved = resolve(root);
651
+ const targetResolved = resolve(target);
652
+ if (samePath(rootResolved, targetResolved)) return true;
653
+ const prefix = rootResolved.endsWith(sep) ? rootResolved : rootResolved + sep;
654
+ return process.platform === "win32" ? targetResolved.toLowerCase().startsWith(prefix.toLowerCase()) : targetResolved.startsWith(prefix);
655
+ }
656
+ //#endregion
657
+ //#region ../dsh-plugin-kit/src/host-rpc.ts
658
+ /** The shared authenticated HTTP boundary. Each plugin still owns its RPC envelope semantics. */
659
+ function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
660
+ const bodyLimit = options.maxBodyBytes ?? 65536;
661
+ if (!Number.isSafeInteger(bodyLimit) || bodyLimit <= 0) throw new Error("Invalid RPC body limit");
662
+ const isEndpoint = options.isEndpoint ?? ((endpoint) => /^[a-z.]+$/.test(endpoint));
663
+ const log = options.logger;
664
+ return ctx.webServer.register({
665
+ kind: "prefix",
666
+ path: channel,
667
+ handler: async (req, res) => {
668
+ const reject = (status, error, override) => {
669
+ if (res.destroyed) return;
670
+ if (status === 413) res.writeHead(status, { connection: "close" });
671
+ else res.writeHead(status);
672
+ res.end(override ?? options.errorBody?.(status, error) ?? (status === 500 ? "rpc request failed" : ""));
673
+ };
674
+ let endpoint = "";
675
+ try {
676
+ if (typeof ctx.connection.requestRejection !== "function") {
677
+ log?.error("rpc.admission.unavailable", "Host RPC admission policy is unavailable.", void 0, {
678
+ channel,
679
+ status: 503
680
+ });
681
+ reject(503);
682
+ return;
683
+ }
684
+ const rejection = ctx.connection.requestRejection(req);
685
+ if (rejection !== void 0) {
686
+ log?.warn("rpc.admission.rejected", "Host RPC request rejected by connection policy.", void 0, {
687
+ channel,
688
+ status: rejection
689
+ });
690
+ reject(rejection);
691
+ return;
692
+ }
693
+ const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
694
+ endpoint = pathname.startsWith(`${channel}/`) ? pathname.slice(channel.length + 1) : "";
695
+ if (req.method !== "POST" || !isEndpoint(endpoint)) {
696
+ log?.debug("rpc.route.rejected", "Host RPC route or method rejected.", {
697
+ channel,
698
+ endpoint: endpoint || null,
699
+ method: req.method ?? null,
700
+ status: 404
701
+ });
702
+ reject(404);
703
+ return;
704
+ }
705
+ const contentType = String(req.headers["content-type"] ?? "").split(";", 1)[0]?.trim();
706
+ if ((options.caseInsensitiveContentType ? contentType.toLowerCase() : contentType) !== "application/json") {
707
+ log?.debug("rpc.content-type.rejected", "Host RPC content-type rejected.", {
708
+ channel,
709
+ endpoint,
710
+ status: 415
711
+ });
712
+ reject(415);
713
+ return;
714
+ }
715
+ const guarded = options.beforeBody?.(req, endpoint);
716
+ if (guarded) {
717
+ log?.debug("rpc.guard.rejected", "Host RPC request rejected before body read.", {
718
+ channel,
719
+ endpoint,
720
+ status: guarded.status
721
+ });
722
+ reject(guarded.status, void 0, guarded.body);
723
+ return;
724
+ }
725
+ const controller = new AbortController();
726
+ res.on("close", () => {
727
+ if (!res.writableEnded) controller.abort();
728
+ });
729
+ const chunks = [];
730
+ let size = 0;
731
+ for await (const chunk of req) {
732
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
733
+ size += buffer.length;
734
+ if (size > bodyLimit) {
735
+ log?.warn("rpc.body.too-large", "Host RPC request exceeded body limit.", void 0, {
736
+ channel,
737
+ endpoint,
738
+ maxBodyBytes: bodyLimit
739
+ });
740
+ reject(413);
741
+ req.destroy();
742
+ return;
743
+ }
744
+ chunks.push(buffer);
745
+ }
746
+ let body;
747
+ try {
748
+ body = JSON.parse(Buffer.concat(chunks).toString("utf8") || (options.emptyBodyAsNull ? "null" : ""));
749
+ } catch {
750
+ log?.debug("rpc.body.invalid-json", "Host RPC request body is not valid JSON.", {
751
+ channel,
752
+ endpoint,
753
+ status: 400
754
+ });
755
+ reject(400);
756
+ return;
757
+ }
758
+ if (log) await withPluginLogContext({
759
+ rpcChannel: channel,
760
+ rpcEndpoint: endpoint,
761
+ operationId: nextPluginOperationId("rpc")
762
+ }, () => dispatch(endpoint, body, controller.signal, res));
763
+ else await dispatch(endpoint, body, controller.signal, res);
764
+ } catch (error) {
765
+ log?.error("rpc.dispatch.failed", "Unhandled host RPC handler error.", error, {
766
+ channel,
767
+ endpoint: endpoint || null,
768
+ status: 500
769
+ });
770
+ reject(500, error);
771
+ }
772
+ }
773
+ });
774
+ }
775
+ /** Write the native Connection response envelope after a plugin has handled a request. */
776
+ function sendHostRpcResponse(response, rpcId, result, noStore = true) {
777
+ if (response.destroyed) return;
778
+ response.writeHead(200, {
779
+ "content-type": "application/json",
780
+ ...noStore ? { "cache-control": "no-store" } : {}
781
+ });
782
+ response.end(JSON.stringify({
783
+ type: "server-response",
784
+ rpcId,
785
+ result
786
+ }));
787
+ }
788
+ /** Safe host RPC registration. Missing requestRejection fails closed. */
789
+ function registerHostRpc(ctx, channel, call, options = {}) {
790
+ const log = options.logger;
791
+ return registerHostRpcTransport(ctx, channel, async (endpoint, body, signal, res) => {
792
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
793
+ log?.debug("rpc.envelope.invalid", "Host RPC envelope must be a JSON object.", {
794
+ channel,
795
+ endpoint
796
+ });
797
+ res.writeHead(400);
798
+ res.end();
799
+ return;
800
+ }
801
+ const request = body;
802
+ if (request.type !== "client-request" || request.method !== endpoint || typeof request.rpcId !== "string" || options.requirePayload && !Object.hasOwn(request, "payload")) {
803
+ log?.debug("rpc.envelope.invalid", "Host RPC envelope failed validation.", {
804
+ channel,
805
+ endpoint
806
+ });
807
+ res.writeHead(400);
808
+ res.end();
809
+ return;
810
+ }
811
+ const started = Date.now();
812
+ const raw = await call(endpoint, request.payload, signal);
813
+ const durationMs = Date.now() - started;
814
+ if (raw.ok) log?.debug("rpc.call.completed", "Host RPC call succeeded.", {
815
+ channel,
816
+ endpoint,
817
+ durationMs
818
+ });
819
+ else log?.warn("rpc.call.error", "Host RPC call returned an application error.", void 0, {
820
+ channel,
821
+ endpoint,
822
+ code: raw.error?.code ?? null,
823
+ durationMs
824
+ });
825
+ const result = raw.ok ? raw : {
826
+ ...raw,
827
+ error: {
828
+ details: {},
829
+ ...raw.error
830
+ }
831
+ };
832
+ sendHostRpcResponse(res, request.rpcId, result);
833
+ }, options);
834
+ }
835
+ //#endregion
836
+ //#region src/index.ts
837
+ const name = "deep-view";
838
+ const inject = [
839
+ "tools",
840
+ "sessionController",
841
+ "sessions",
842
+ "agents",
843
+ "connection",
844
+ "webServer"
845
+ ];
846
+ const MAX_REDIRECTS = 256;
847
+ function resolveSessionId(redirects, sessionId) {
848
+ let current = sessionId;
849
+ const seen = /* @__PURE__ */ new Set();
850
+ while (!seen.has(current)) {
851
+ seen.add(current);
852
+ const next = redirects.get(current);
853
+ if (!next) break;
854
+ current = next;
855
+ }
856
+ return current;
857
+ }
858
+ function handoffPendingSession(pending, redirects, fromSessionId, toSessionId) {
859
+ if (!short(fromSessionId, 200) || !short(toSessionId, 200) || fromSessionId === toSessionId) return false;
860
+ const source = resolveSessionId(redirects, fromSessionId);
861
+ const target = resolveSessionId(redirects, toSessionId);
862
+ if (source === target) return false;
863
+ redirects.set(source, target);
864
+ if (fromSessionId !== source) redirects.set(fromSessionId, target);
865
+ while (redirects.size > MAX_REDIRECTS) {
866
+ const oldest = redirects.keys().next().value;
867
+ if (oldest === void 0) break;
868
+ redirects.delete(oldest);
869
+ }
870
+ const record = pending.get(source);
871
+ if (!record) return false;
872
+ pending.delete(source);
873
+ if (pending.has(target)) return false;
874
+ pending.set(target, {
875
+ ...record,
876
+ sessionId: target
877
+ });
878
+ return true;
879
+ }
880
+ const isLiveRoot = (host, agent) => agent !== void 0 && host.agents.roots().some((root) => root.id === agent.id);
881
+ const ok = (value) => ({
882
+ ok: true,
883
+ value
884
+ });
885
+ const fail = (code, message) => ({
886
+ ok: false,
887
+ error: {
888
+ code,
889
+ message
890
+ }
891
+ });
892
+ const short = (value, max = 160) => typeof value === "string" && value.length > 0 && value.length <= max;
893
+ const checkedShown = (title, view) => checkShownView(title, view);
894
+ const isShown = (value) => !("error" in value);
895
+ function presentation(args, value) {
896
+ if (!isRecord(value) || value.owner !== "dsh-deep-view" || !["shown", "pending"].includes(String(value.status))) return null;
897
+ const shown = checkedShown(isRecord(args) ? args.title : void 0, isRecord(args) ? args.view : void 0);
898
+ if (!isShown(shown)) return null;
899
+ return {
900
+ owner: VIEW_OWNER,
901
+ ...short(value.viewId, 80) ? { viewId: value.viewId } : {},
902
+ ...shown,
903
+ awaitReply: value.awaitReply === true
904
+ };
905
+ }
906
+ function createShowViewTool(host, pending, makeId = randomUUID, logger) {
907
+ return {
908
+ name: "show_view",
909
+ description: [
910
+ "Attach one card below your final reply when a visual or interactive form beats prose: compared numbers, a trend, a share, progress; an estimate with user-adjustable inputs; or a choice the user answers afterwards (use ask_user_question when you cannot continue without it).",
911
+ "Prefer plain Markdown otherwise. Call once, then finish the reply; the card is not yet visible when this tool returns.",
912
+ "Never end on the call, wait or poll, repeat the card JSON, or claim the user saw it. Answers accept one reply, sent as the next user message. Never collect keys, tokens or passwords."
913
+ ].join(" "),
914
+ parameters: {
915
+ type: "object",
916
+ properties: {
917
+ title: {
918
+ type: "string",
919
+ description: "Short heading above the card."
920
+ },
921
+ view: {
922
+ type: "object",
923
+ additionalProperties: true,
924
+ description: [
925
+ "One ViewNode, nodes nest under stack|row(children,let?).",
926
+ "Display: text,metric{label,value},table{columns:[{key,label}],rows:[[cell]]},chart{kind:bar|hbar|line|area|donut,categories,series:[{name?,values}]},progress{percent|value+max},callout{text},sources{items:[{label,url}]}.",
927
+ "Inputs: slider,number,choice,toggle{id,label,value}. A {\"=\":\"expr\"} formula replaces a value or cell, reading input ids and let names.",
928
+ "Answers(need await_reply true): options{id,options:[{label,description?}],multi?},confirm{id},form{id,fields:[{id,label,placeholder?}]},submit{id,label}. No html."
929
+ ].join(" ")
930
+ },
931
+ await_reply: {
932
+ type: "boolean",
933
+ description: "Whether this card asks the user for a response."
934
+ }
935
+ },
936
+ required: ["view"]
937
+ },
938
+ output: {
939
+ schema: {
940
+ type: "object",
941
+ properties: {
942
+ owner: {
943
+ type: "string",
944
+ const: VIEW_OWNER
945
+ },
946
+ status: { type: "string" },
947
+ viewId: { type: "string" },
948
+ awaitReply: { type: "boolean" },
949
+ summary: { type: "string" }
950
+ },
951
+ required: [
952
+ "owner",
953
+ "status",
954
+ "awaitReply",
955
+ "summary"
956
+ ],
957
+ additionalProperties: false
958
+ },
959
+ render: (_args, value) => [{
960
+ type: "text",
961
+ text: String(value.summary ?? "")
962
+ }],
963
+ presentationMeta: presentation
964
+ },
965
+ async execute(args, exec) {
966
+ const input = isRecord(args) ? args : {};
967
+ const shown = checkedShown(input.title, input.view);
968
+ if (!isShown(shown)) {
969
+ logger?.debug("view.invalid", "Rejected invalid show_view arguments.", {
970
+ sessionId: exec.agent?.id ?? null,
971
+ reason: shown.error
972
+ });
973
+ return {
974
+ owner: VIEW_OWNER,
975
+ status: "invalid",
976
+ awaitReply: false,
977
+ summary: shown.error
978
+ };
979
+ }
980
+ if (typeof input.await_reply !== "undefined" && typeof input.await_reply !== "boolean") return {
981
+ owner: VIEW_OWNER,
982
+ status: "invalid",
983
+ awaitReply: false,
984
+ summary: "await_reply: expected true or false"
985
+ };
986
+ const awaitReply = input.await_reply ?? shown.interactive;
987
+ if (awaitReply !== shown.interactive) return {
988
+ owner: VIEW_OWNER,
989
+ status: "invalid",
990
+ awaitReply: false,
991
+ summary: shown.interactive ? "This card has answer controls; omit await_reply or set it true." : "A display-only card cannot wait for an answer; omit await_reply or set it false."
992
+ };
993
+ const id = exec.agent?.id;
994
+ const agent = id === void 0 ? void 0 : host.agents.get(id);
995
+ if (awaitReply && !isLiveRoot(host, agent)) {
996
+ logger?.debug("view.non-root-rejected", "Rejected interactive show_view outside a live root session.", { sessionId: id ?? null });
997
+ return {
998
+ owner: VIEW_OWNER,
999
+ status: "invalid",
1000
+ awaitReply: false,
1001
+ summary: "An interactive view only works in a live root session. Put the question or decision in your response instead."
1002
+ };
1003
+ }
1004
+ let viewId;
1005
+ if (awaitReply && agent) {
1006
+ viewId = makeId();
1007
+ pending.set(agent.id, {
1008
+ id: viewId,
1009
+ sessionId: agent.id,
1010
+ ...exec.callId ? { callId: exec.callId } : {},
1011
+ ...shown
1012
+ });
1013
+ }
1014
+ logger?.info("view.prepared", "Prepared Deep View card.", {
1015
+ sessionId: agent?.id ?? id ?? null,
1016
+ viewId: viewId ?? null,
1017
+ awaitReply
1018
+ });
1019
+ return {
1020
+ owner: VIEW_OWNER,
1021
+ status: awaitReply ? "pending" : "shown",
1022
+ ...viewId ? { viewId } : {},
1023
+ awaitReply,
1024
+ summary: `Card prepared for the final reply. Finish with a concise final answer; do not call this tool again for the same card.${awaitReply ? " The user can answer the card after your reply finishes; do not wait or poll." : ""} ${viewSummary(shown.view)}`
1025
+ };
1026
+ },
1027
+ isConcurrencySafe: () => false
1028
+ };
1029
+ }
1030
+ function createRpc(host, pending, redirects = /* @__PURE__ */ new Map(), logger) {
1031
+ const claims = /* @__PURE__ */ new Set();
1032
+ return async (endpoint, payload, signal) => {
1033
+ if (!isRecord(payload) || !short(payload.sessionId, 200)) return fail("bad-request", "A bounded sessionId is required");
1034
+ const sessionId = resolveSessionId(redirects, payload.sessionId);
1035
+ const record = pending.get(sessionId);
1036
+ if (endpoint === "pending") return ok(record?.callId === payload.callId || payload.callId === void 0 ? record ?? null : null);
1037
+ if (endpoint === "dismiss") {
1038
+ if (record && record.id === payload.viewId) {
1039
+ pending.delete(sessionId);
1040
+ logger?.debug("view.dismissed", "Dismissed pending Deep View card.", {
1041
+ sessionId,
1042
+ viewId: record.id
1043
+ });
1044
+ }
1045
+ return ok(true);
1046
+ }
1047
+ if (endpoint !== "reply") return fail("not-found", "Unknown Deep View endpoint");
1048
+ if (!record || !short(payload.viewId, 80) || payload.viewId !== record.id || !short(payload.action, 160) || !isRecord(payload.values)) {
1049
+ logger?.debug("reply.stale", "Rejected reply for stale or closed Deep View card.", { sessionId });
1050
+ return fail("stale", "This view is no longer open");
1051
+ }
1052
+ if (claims.has(record.id)) return fail("busy", "This view is already being answered");
1053
+ const checked = validateViewAnswer(record.view, payload.action, payload.values);
1054
+ if (checked.error !== void 0) {
1055
+ logger?.debug("reply.invalid", "Rejected invalid Deep View reply.", {
1056
+ sessionId,
1057
+ viewId: record.id,
1058
+ action: payload.action,
1059
+ reason: checked.error
1060
+ });
1061
+ return fail("invalid", checked.error);
1062
+ }
1063
+ claims.add(record.id);
1064
+ pending.delete(sessionId);
1065
+ let appended = false;
1066
+ try {
1067
+ if (signal.aborted) return fail("cancelled", "The request was cancelled");
1068
+ const live = host.agents.get(sessionId);
1069
+ const resolved = isLiveRoot(host, live) ? { agent: live } : await host.sessionController.resolveAgent(sessionId);
1070
+ const agent = resolved.agent;
1071
+ if (!agent || host.agents.get(agent.id) !== agent || !host.agents.roots().some((root) => root.id === agent.id)) throw new Error(("error" in resolved ? resolved.error?.message : void 0) ?? "The session is not a live root session");
1072
+ if (signal.aborted) return fail("cancelled", "The request was cancelled");
1073
+ const deepView = {
1074
+ owner: VIEW_OWNER,
1075
+ version: 1,
1076
+ answer: {
1077
+ id: record.id,
1078
+ action: payload.action,
1079
+ values: checked
1080
+ }
1081
+ };
1082
+ agent.followup(createUserMessage({
1083
+ content: [{
1084
+ type: "text",
1085
+ text: answerText(record, payload.action, checked)
1086
+ }],
1087
+ source: {
1088
+ kind: "user",
1089
+ deepView
1090
+ }
1091
+ }));
1092
+ appended = true;
1093
+ await host.sessions.flush(agent.session);
1094
+ logger?.info("reply.submitted", "Submitted Deep View reply to live session.", {
1095
+ sessionId: agent.id,
1096
+ viewId: record.id,
1097
+ action: payload.action
1098
+ });
1099
+ return ok({ answered: true });
1100
+ } catch (error) {
1101
+ logger?.error("reply.failed", "Failed to deliver Deep View reply.", error, {
1102
+ sessionId,
1103
+ viewId: record.id,
1104
+ action: payload.action,
1105
+ appended
1106
+ });
1107
+ return fail("internal", error instanceof Error ? error.message : String(error));
1108
+ } finally {
1109
+ if (!appended && !pending.has(sessionId)) pending.set(sessionId, record);
1110
+ claims.delete(record.id);
1111
+ }
1112
+ };
1113
+ }
1114
+ async function apply(ctx) {
1115
+ const host = ctx;
1116
+ const logger = installPluginLogger(ctx, PLUGIN_NAME, { forwardHostWarnError: false });
1117
+ const pending = /* @__PURE__ */ new Map();
1118
+ const redirects = /* @__PURE__ */ new Map();
1119
+ ctx.provide(HOST_SERVICE, { handoffSession: (fromSessionId, toSessionId) => {
1120
+ const moved = handoffPendingSession(pending, redirects, fromSessionId, toSessionId);
1121
+ if (moved) logger.debug("session.handoff", "Moved pending Deep View card across session handoff.", {
1122
+ fromSessionId,
1123
+ toSessionId
1124
+ });
1125
+ return moved;
1126
+ } });
1127
+ ctx.effect(() => {
1128
+ if (host.tools.get("show_view")) {
1129
+ logger.warn("tool.already-registered", "show_view is already registered by another plugin; skipping registration.");
1130
+ host.logger?.warn("deep-view: show_view is already registered by another plugin; this bundle stays disabled for its own tool");
1131
+ return () => {};
1132
+ }
1133
+ try {
1134
+ return host.tools.register(createShowViewTool(host, pending, randomUUID, logger));
1135
+ } catch (error) {
1136
+ logger.warn("tool.register-failed", "show_view tool registration failed.", error);
1137
+ host.logger?.warn("deep-view: show_view registration skipped: %s", error instanceof Error ? error.message : String(error));
1138
+ return () => {};
1139
+ }
1140
+ }, "deep-view.tool");
1141
+ ctx.effect(() => ctx.on("session/event", (session, event) => {
1142
+ if (event.type !== "user/message" || !isRecord(event.data) || !isRecord(event.data.source) || event.data.source.kind !== "user") return;
1143
+ pending.delete(resolveSessionId(redirects, session.id));
1144
+ }), "deep-view.close-pending");
1145
+ ctx.effect(() => registerHostRpc(host, RPC_CHANNEL, createRpc(host, pending, redirects, logger), {
1146
+ maxBodyBytes: 24576,
1147
+ logger
1148
+ }), "deep-view.rpc");
1149
+ }
1150
+ //#endregion
1151
+ export { apply, createRpc, createShowViewTool, handoffPendingSession, inject, name, resolveSessionId };
1152
+
1153
+ //# sourceMappingURL=index.js.map