@wisdoverse/dsh-inline-media-viewer 1.0.8 → 1.0.10-alpha.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/CHANGELOG.md CHANGED
@@ -1,14 +1,29 @@
1
1
  # Changelog
2
2
 
3
- ## 1.0.8
3
+ ## 1.0.10-alpha.1
4
+
5
+ - Adapt to DSH v0.1.3-alpha.1: register host settings through `settings.register`,
6
+ register client projections through `uiConversation.events`, and use the
7
+ authenticated two-argument RPC registration contract.
8
+ - Remove the unused settings package peer dependency; consume the host-provided
9
+ settings service and add a real-dependency host registration check.
10
+
11
+ ## 1.0.9
12
+
13
+ - Collect media paths from tool-call arguments as well as the assistant's final
14
+ text, so every image read during a turn remains available in its preview.
15
+
16
+ ## 1.0.8 (deprecated)
4
17
 
5
18
  - Preserve output-directory context across assistant steps in one turn, so a
6
19
  later final answer containing only media filenames still renders previews.
20
+ - Superseded by 1.0.9 because tool-call-only media references were not retained.
7
21
 
8
- ## 1.0.7
22
+ ## 1.0.7 (deprecated)
9
23
 
10
24
  - Resolve backticked media filenames against a preceding backticked directory,
11
25
  matching the common "output directory plus file list" response format.
26
+ - Superseded by 1.0.9 because it only inspected assistant text.
12
27
 
13
28
  ## 1.0.6
14
29
 
package/README.md CHANGED
@@ -59,12 +59,14 @@ audio/sound.mp3 → <audio controls>
59
59
 
60
60
  ## Installation
61
61
 
62
+ Targets DSH **v0.1.3-alpha.1**. For older DSH versions, use plugin **1.0.9**.
63
+
62
64
  ### From npm
63
65
 
64
66
  Install and activate the bundle in your Web profile:
65
67
 
66
68
  ```bash
67
- dsh plugin --profile web add @wisdoverse/dsh-inline-media-viewer
69
+ dsh plugin --profile web add @wisdoverse/dsh-inline-media-viewer@1.0.10-alpha.1
68
70
  ```
69
71
 
70
72
  Restart the Web profile after installation.
package/README.zh-CN.md CHANGED
@@ -57,12 +57,14 @@ audio/sound.mp3 → <audio controls>
57
57
 
58
58
  ## 安装
59
59
 
60
+ 适配 DSH **v0.1.3-alpha.1**;旧版 DSH 请使用插件 **1.0.9**。
61
+
60
62
  ### 通过 npm 安装
61
63
 
62
64
  在 Web profile 中安装并启用 Bundle:
63
65
 
64
66
  ```bash
65
- dsh plugin --profile web add @wisdoverse/dsh-inline-media-viewer
67
+ dsh plugin --profile web add @wisdoverse/dsh-inline-media-viewer@1.0.10-alpha.1
66
68
  ```
67
69
 
68
70
  安装后重启 Web profile。
package/client/client.js CHANGED
@@ -80,6 +80,11 @@ window.__ModuleLoader__.load({
80
80
  const found = [];
81
81
  const seen = new Set();
82
82
  let directory = "";
83
+ const ext = Array.from(MEDIA_EXTENSIONS).join("|");
84
+ const directories = new Set(Array.from(text.matchAll(
85
+ /[`'"]((?:\/|\.\.?\/|[\w.-]+\/)[^`'"\n]*[\/\\])[`'"]/g,
86
+ ), (match) => cleanCandidate(match[1])));
87
+ const soleDirectory = directories.size === 1 ? directories.values().next().value : "";
83
88
  const add = (raw) => {
84
89
  const source = cleanCandidate(raw);
85
90
  const kind = mediaKind(source);
@@ -95,7 +100,10 @@ window.__ModuleLoader__.load({
95
100
  plain = plain.replace(/`([^`\n]+)`/g, (match, source) => {
96
101
  const value = cleanCandidate(source);
97
102
  if (/[/\\]$/.test(value)) directory = value;
98
- else add(directory && !/[/\\]/.test(value) ? `${directory}${value}` : value);
103
+ else {
104
+ const base = directory || soleDirectory;
105
+ add(base && !/[/\\]/.test(value) ? `${base}${value}` : value);
106
+ }
99
107
  return " ".repeat(match.length);
100
108
  });
101
109
  plain = plain.replace(/https?:\/\/[^\s<>"'`]+/gi, (source) => {
@@ -103,7 +111,11 @@ window.__ModuleLoader__.load({
103
111
  return " ".repeat(source.length);
104
112
  });
105
113
 
106
- const ext = Array.from(MEDIA_EXTENSIONS).join("|");
114
+ if (soleDirectory) {
115
+ const filenamePattern = new RegExp(String.raw`[\x60'"]([^\x60'"/\\\n]+\.(?:${ext})(?:\?[^\x60'"\s]*)?)[\x60'"]`, "gi");
116
+ for (const match of text.matchAll(filenamePattern)) add(`${soleDirectory}${match[1]}`);
117
+ }
118
+
107
119
  const pathPattern = new RegExp(
108
120
  String.raw`(?:\/|\.\.?\/|[\w.-]+\/)[^\s<>"'\x60()\[\]{}]+?\.(?:${ext})(?:\?[^\s<>"'\x60]*)?`,
109
121
  "gi",
@@ -112,10 +124,48 @@ window.__ModuleLoader__.load({
112
124
  return found;
113
125
  }
114
126
 
127
+ function toolArgumentText(raw) {
128
+ try {
129
+ const text = [];
130
+ const visit = (value) => {
131
+ if (typeof value === "string") text.push(value);
132
+ else if (value && typeof value === "object") Object.values(value).forEach(visit);
133
+ };
134
+ visit(JSON.parse(raw));
135
+ return text.join("\n");
136
+ } catch (_error) {
137
+ return raw;
138
+ }
139
+ }
140
+
141
+ const mediaMentionsDefinition = {
142
+ kind: "inline-media-mentions",
143
+ match: (event) => {
144
+ if (event.type === "turn/start") return { id: String(event.data.turn), role: "start" };
145
+ if (event.type === "tool/call") return { id: String(event.data.turn), role: "update" };
146
+ return null;
147
+ },
148
+ start: (_context, match) => ({ turn: match.event.data.turn, texts: [] }),
149
+ update: (context, match) => typeof match.event.data.arguments !== "string" ? context.state : {
150
+ ...context.state,
151
+ texts: [...context.state.texts, { seq: match.event.seq, text: toolArgumentText(match.event.data.arguments) }],
152
+ },
153
+ buildLocationData: (context, scope) => scope !== "turn" || context.state === undefined ? null : {
154
+ kind: "turn",
155
+ turn: context.state.turn,
156
+ key: "inline-media-mentions",
157
+ value: { texts: context.state.texts },
158
+ },
159
+ };
160
+
115
161
  function selectMedia(owner) {
116
162
  const settings = readSettings();
117
163
  if (!settings.autoRender) return null;
118
164
  const text = [];
165
+ const mentions = owner.turn.data && owner.turn.data.get("inline-media-mentions");
166
+ if (mentions) {
167
+ text.push(...mentions.texts.filter((entry) => entry.seq <= owner.seq).map((entry) => entry.text));
168
+ }
119
169
  for (const step of owner.turn.steps) {
120
170
  const assistant = step.data.get("assistant-step");
121
171
  if (!assistant || !assistant.finalNode || assistant.finalNode.seq > owner.seq || !Array.isArray(assistant.blocks)) continue;
@@ -492,10 +542,11 @@ window.__ModuleLoader__.load({
492
542
  }
493
543
 
494
544
  const name = "dsh-inline-media-viewer";
495
- const inject = ["slots", "connection", "settingsScope", "locale"];
545
+ const inject = ["slots", "connection", "settingsScope", "locale", "uiConversation"];
496
546
 
497
547
  function apply(ctx) {
498
548
  const connection = ctx.get("connection");
549
+ ctx.uiConversation.events.register(mediaMentionsDefinition);
499
550
  ctx.effect(() => ctx.locale.register(SETTINGS_NS, SETTINGS_DICT), "inline-media: dictionaries");
500
551
  const t = ctx.locale.bind(SETTINGS_NS);
501
552
  const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
@@ -518,7 +569,7 @@ window.__ModuleLoader__.load({
518
569
  exports.apply = apply;
519
570
  exports.inject = inject;
520
571
  exports.name = name;
521
- exports.testing = Object.freeze({ extractCandidates, mediaTransport, selectMedia });
572
+ exports.testing = Object.freeze({ extractCandidates, mediaMentionsDefinition, mediaTransport, selectMedia, toolArgumentText });
522
573
  return module.exports;
523
574
  },
524
575
  });
package/index.js CHANGED
@@ -20,7 +20,6 @@
20
20
  import { readFile, realpath, stat } from "node:fs/promises";
21
21
  import { isAbsolute, resolve } from "node:path";
22
22
  import z from "@deepseek-ai/schemastery";
23
- import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
24
23
 
25
24
  import { COMFY_DEFAULT_ORIGIN, MAX_BYTES, comfyUrl, isInside, mimeOf, normalizeComfyOrigin, resolveSessionCwd, testing } from "./lib.js";
26
25
 
@@ -33,7 +32,7 @@ const CHANNEL = "/inline-media";
33
32
  const ENDPOINT = "read";
34
33
 
35
34
  /** Persistent user settings namespace for this plugin. */
36
- export const MEDIA_SETTINGS_NAMESPACE = settingsNamespace("inline-media");
35
+ export const MEDIA_SETTINGS_NAMESPACE = "inline-media";
37
36
 
38
37
  /** Schema of the user settings section. */
39
38
  export const MEDIA_SETTINGS_SCHEMA = z.object({
@@ -157,28 +156,19 @@ async function handleRead(ctx, endpoint, payload, signal, resolveSettings) {
157
156
  }
158
157
 
159
158
  export function apply(ctx) {
160
- let resolveSettings = () => MEDIA_SETTINGS_DEFAULTS;
159
+ const settings = ctx.settings.register(MEDIA_SETTINGS_NAMESPACE, MEDIA_SETTINGS_SCHEMA, {
160
+ base: MEDIA_SETTINGS_DEFAULTS,
161
+ });
162
+ const resolveSettings = () => settings.get();
161
163
  ctx.effect(() => {
162
164
  const dispose = ctx.connection.rpc.handle(
163
165
  CHANNEL,
164
166
  (endpoint, payload, signal) => handleRead(ctx, endpoint, payload, signal, resolveSettings),
165
- { authority: "trusted-host" },
166
167
  );
167
168
  return () => {
168
169
  void dispose();
169
170
  };
170
171
  }, "inline-media: rpc");
171
-
172
- // Register the user settings namespace (defaults + schema). The client reads
173
- // and writes this section through the settings mirror. autoRender /
174
- // displayCap / imageMaxPx are consumed client-side; comfyUrl is read here,
175
- // per request, to decide which remote URLs to proxy and where to fetch them.
176
- installSettingsSection(ctx, MEDIA_SETTINGS_NAMESPACE, MEDIA_SETTINGS_SCHEMA, MEDIA_SETTINGS_DEFAULTS, {
177
- setSource: (source) => {
178
- resolveSettings = source;
179
- },
180
- onChange: () => {},
181
- });
182
172
  }
183
173
 
184
174
  export { MIME } from "./lib.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wisdoverse/dsh-inline-media-viewer",
3
- "version": "1.0.8",
3
+ "version": "1.0.10-alpha.1",
4
4
  "description": "Persistent inline image, video, and audio previews for DeepSeek Harness Web conversations, with workspace-confined local reads and an optional ComfyUI proxy.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -49,11 +49,11 @@
49
49
  ],
50
50
  "scripts": {
51
51
  "test": "node test.mjs",
52
- "lint": "node --check index.js && node --check lib.js && node --check test.mjs"
52
+ "test:host": "node test.mjs --host",
53
+ "lint": "node --check index.js && node --check lib.js && node --check client/client.js && node --check test.mjs"
53
54
  },
54
55
  "peerDependencies": {
55
56
  "@deepseek-ai/cordis": "^4.0.1",
56
- "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
57
57
  "@deepseek-ai/schemastery": "^3.18.1"
58
58
  },
59
59
  "dsh": {