@mokuyoaxis/dsh-iris 0.2.0 → 0.2.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.
Files changed (88) hide show
  1. package/.dsh/skills/iris-compose-media/SKILL.md +10 -5
  2. package/.dsh/skills/iris-compose-media/references/workflows.md +7 -1
  3. package/.dsh/skills/iris-verify-ui/SKILL.md +2 -1
  4. package/CHANGELOG.md +47 -2
  5. package/README.en.md +65 -29
  6. package/README.md +65 -29
  7. package/bin/dsh-iris.js +222 -21
  8. package/docs/AGENT_QUICKSTART.md +10 -6
  9. package/docs/ARCHITECTURE.md +25 -18
  10. package/docs/ARTIFACT_LIBRARY.md +5 -5
  11. package/docs/ARTIFACT_MANIFEST.md +1 -1
  12. package/docs/CLI_MANAGEMENT.md +181 -0
  13. package/docs/COMPOSITE_VISION.md +35 -0
  14. package/docs/CORE_RUNTIME_CONTRACT.md +8 -7
  15. package/docs/DSH_CORE_MIGRATION.md +48 -19
  16. package/docs/DSH_RC2_ADAPTATION.md +4 -2
  17. package/docs/FAULT_INJECTION.md +12 -2
  18. package/docs/HEADLESS_CLI.md +13 -7
  19. package/docs/HOST_ADAPTER_CONTRACT.md +10 -10
  20. package/docs/HOST_DOCTOR.md +3 -3
  21. package/docs/MODEL_PORT_CONTRACT.md +19 -14
  22. package/docs/OCR_MODEL.md +39 -0
  23. package/docs/PROMPT_OPTIMIZER.md +47 -0
  24. package/docs/PROVIDER_ADAPTER_CONTRACT.md +6 -4
  25. package/docs/PROVIDER_HEALTH.md +22 -5
  26. package/docs/PROVIDER_RUNTIME_HARNESS.md +1 -1
  27. package/docs/PROVIDER_SUBMISSION_CONTRACT.md +2 -2
  28. package/docs/ROADMAP.md +29 -19
  29. package/docs/SECURITY.md +5 -5
  30. package/docs/TASK_SEMANTICS.md +2 -2
  31. package/docs/VISION_CLI.md +132 -0
  32. package/docs/VISION_MODEL.md +51 -0
  33. package/docs/WORKBENCH_ARTIFACTS.md +67 -0
  34. package/docs/file-access-across-environments.md +8 -5
  35. package/docs/releases/0.2.1.md +56 -0
  36. package/lib/actions.js +187 -270
  37. package/lib/adapters.js +133 -22
  38. package/lib/api.js +49 -7
  39. package/lib/chromium-browser.js +117 -0
  40. package/lib/client.js +671 -101
  41. package/lib/command-service.js +121 -7
  42. package/lib/composite-vision-routing.js +42 -0
  43. package/lib/config.js +89 -10
  44. package/lib/core-artifact-store.js +45 -13
  45. package/lib/core-maintenance.js +213 -0
  46. package/lib/core-tasks.js +12 -5
  47. package/lib/core-user-projection.js +1 -0
  48. package/lib/dsh-core-adapter.js +63 -9
  49. package/lib/dsh-text-model-adapter.js +222 -0
  50. package/lib/dsh-vision-model-adapter.js +75 -0
  51. package/lib/generation-input.js +36 -5
  52. package/lib/headless-vision.js +181 -0
  53. package/lib/host-runtime.js +1 -0
  54. package/lib/http-vision-model-adapter.js +187 -0
  55. package/lib/image-edit-input.js +34 -0
  56. package/lib/index.js +205 -175
  57. package/lib/locate.js +86 -28
  58. package/lib/media-probe.js +2 -1
  59. package/lib/model-call-runtime.js +45 -0
  60. package/lib/model-invoker.js +4 -2
  61. package/lib/model-port-contract.js +13 -5
  62. package/lib/models.js +10 -5
  63. package/lib/ocr-model-routing.js +26 -0
  64. package/lib/ocr.js +110 -64
  65. package/lib/prompt-optimizer-config.js +2 -1
  66. package/lib/prompt-optimizer-core.js +121 -0
  67. package/lib/prompt-optimizer.js +37 -193
  68. package/lib/provider-adapters.js +85 -9
  69. package/lib/provider-catalog.js +19 -7
  70. package/lib/provider-config-service.js +236 -0
  71. package/lib/provider-contract.js +3 -2
  72. package/lib/provider-health.js +80 -1
  73. package/lib/provider-protocol.js +23 -0
  74. package/lib/provider-task-runner.js +58 -16
  75. package/lib/summarize.js +31 -20
  76. package/lib/summary-input.js +52 -0
  77. package/lib/task-wait.js +40 -0
  78. package/lib/video-input.js +30 -0
  79. package/lib/vision-core.js +42 -0
  80. package/lib/vision-image-input.js +83 -0
  81. package/lib/vision-input.js +31 -0
  82. package/lib/vision-model-routing.js +180 -0
  83. package/lib/vision.js +3 -2
  84. package/lib/workbench-artifacts.js +52 -0
  85. package/lib/workbench-management.js +151 -0
  86. package/lib/workbench-zip.js +39 -0
  87. package/package.json +1 -1
  88. package/user_guide.md +52 -22
@@ -0,0 +1,151 @@
1
+ /** 工作台显式选择/详情/下载和可恢复管理;不修改既有 Core 维护语义。 */
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import crypto from 'node:crypto';
5
+ import { setImmediate } from 'node:timers/promises';
6
+ import * as legacy from './artifacts.js';
7
+ import { mimeOf } from './media.js';
8
+ import { inspectWorkbenchArtifactForDsh, inspectProviderTaskForDsh, maintainWorkbenchForDsh, readCoreArtifactMediaForDsh } from './dsh-core-adapter.js';
9
+ import { workbenchZip } from './workbench-zip.js';
10
+
11
+ export const MAX_WORKBENCH_SELECTION = 200;
12
+ export const MAX_WORKBENCH_DOWNLOAD_BYTES = 128 * 1024 * 1024;
13
+ const CORE_ID = /^artifact_[a-f0-9]{24}$/, LEGACY_ID = /^a_[a-f0-9]{24}$/, TASK_ID = /^task_[a-f0-9]{24}$/;
14
+ const EXTENSIONS = { 'image/png': '.png', 'image/jpeg': '.jpg', 'image/webp': '.webp', 'image/gif': '.gif',
15
+ 'video/mp4': '.mp4', 'video/webm': '.webm', 'audio/wav': '.wav', 'audio/mpeg': '.mp3', 'audio/ogg': '.ogg',
16
+ 'audio/flac': '.flac', 'text/plain': '.txt', 'application/json': '.json' };
17
+ const fail = (code, message, statusCode = 400) => { throw Object.assign(new Error(message), { code, statusCode }); };
18
+ function active(signal) { if (signal?.aborted) throw Object.assign(new Error('作品操作已取消'), { name: 'AbortError' }); }
19
+ function exact(input, keys) {
20
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(key => !keys.includes(key))) {
21
+ fail('IRIS_COMMAND_INPUT_INVALID', '作品操作参数无效');
22
+ }
23
+ }
24
+ function validItem(item) {
25
+ exact(item, ['source', 'id']);
26
+ if (!(item.source === 'core' ? CORE_ID.test(item.id) : item.source === 'legacy' && LEGACY_ID.test(item.id))) {
27
+ fail('IRIS_COMMAND_INPUT_INVALID', '作品来源或 ID 无效');
28
+ }
29
+ return item;
30
+ }
31
+ function selection(input) {
32
+ exact(input, ['items']);
33
+ if (!Array.isArray(input.items) || !input.items.length || input.items.length > MAX_WORKBENCH_SELECTION) {
34
+ fail('IRIS_COMMAND_INPUT_INVALID', '请明确选择 1–200 个作品');
35
+ }
36
+ const items = input.items.map(validItem);
37
+ if (new Set(items.map(item => item.source + ':' + item.id)).size !== items.length) fail('IRIS_COMMAND_INPUT_INVALID', '选中作品不能重复');
38
+ return items;
39
+ }
40
+
41
+ function legacyFile(id) {
42
+ const entry = legacy.all().find(item => item.id === id);
43
+ const hit = entry && legacy.authorize(entry.id, entry.token, entry.file);
44
+ if (!hit) fail('IRIS_WORK_NOT_FOUND', '作品文件不存在或不可读取', 404);
45
+ return { entry, ...hit };
46
+ }
47
+ function safeMetadata(metadata) {
48
+ const result = {};
49
+ for (const key of ['width', 'height', 'durationMs', 'frameCount', 'frameIndex', 'sourceFrameIndex', 'timeMs']) {
50
+ if (typeof metadata[key] === 'number' && Number.isFinite(metadata[key])) result[key] = metadata[key];
51
+ }
52
+ if (TASK_ID.test(metadata.taskId)) result.taskId = metadata.taskId;
53
+ return result;
54
+ }
55
+
56
+ export async function workbenchDetail(source, id, options = {}) {
57
+ validItem({ source, id }); active(options.signal);
58
+ if (source === 'core') {
59
+ const artifact = await inspectWorkbenchArtifactForDsh(id, options);
60
+ return { item: { id, source, file: id + EXTENSIONS[artifact.mediaType], mime: artifact.mediaType,
61
+ kind: artifact.kind, size: artifact.size, createdAt: artifact.createdAt, digest: artifact.digest.value,
62
+ relations: artifact.relations, metadata: safeMetadata(artifact.metadata), integrity: 'verified',
63
+ url: '/iris/api/core/artifact/' + id + '/media' } };
64
+ }
65
+ const hit = legacyFile(id), entry = hit.entry;
66
+ return { item: { id, source, file: entry.file, mime: entry.mime || mimeOf(entry.file), size: hit.size,
67
+ kind: 'legacy-output', createdAt: entry.createdAt || '', integrity: 'file-exists', url: legacy.artifactUrl(entry) } };
68
+ }
69
+
70
+ export async function downloadWorkbenchSelection(input, options = {}) {
71
+ const items = selection(input), details = [];
72
+ let bytes = 0;
73
+ for (const item of items) {
74
+ active(options.signal);
75
+ const detail = (await workbenchDetail(item.source, item.id, options)).item;
76
+ bytes += detail.size;
77
+ if (bytes > MAX_WORKBENCH_DOWNLOAD_BYTES) fail('IRIS_WORK_DOWNLOAD_TOO_LARGE', '单次下载最多 128 MiB,请减少选中作品', 413);
78
+ details.push(detail);
79
+ await setImmediate();
80
+ }
81
+ const entries = [], manifest = [];
82
+ bytes = 0;
83
+ for (const detail of details) {
84
+ active(options.signal);
85
+ const data = detail.source === 'core' ? (await readCoreArtifactMediaForDsh(detail.id, options)).bytes : fs.readFileSync(legacyFile(detail.id).abs);
86
+ const digest = crypto.createHash('sha256').update(data).digest('hex');
87
+ if (detail.source === 'core' && digest !== detail.digest) fail('IRIS_ARTIFACT_DIGEST_MISMATCH', '选中作品内容已改变', 409);
88
+ bytes += data.length;
89
+ if (bytes > MAX_WORKBENCH_DOWNLOAD_BYTES) fail('IRIS_WORK_DOWNLOAD_TOO_LARGE', '单次下载最多 128 MiB,请减少选中作品', 413);
90
+ const name = detail.source + '/' + detail.id + path.extname(detail.file).toLowerCase();
91
+ entries.push({ name, bytes: data });
92
+ manifest.push({ source: detail.source, id: detail.id, file: detail.file, archiveFile: name, mime: detail.mime,
93
+ size: data.length, createdAt: detail.createdAt, digest });
94
+ await setImmediate();
95
+ }
96
+ active(options.signal);
97
+ return workbenchZip([{ name: 'works.json', bytes: Buffer.from(JSON.stringify({ schemaVersion: 0, items: manifest }, null, 2) + '\n') }, ...entries]);
98
+ }
99
+
100
+ export async function deleteWorkbenchArtifacts(input, options = {}) {
101
+ exact(input, ['artifact_ids', 'task_ids', 'confirm_delete']);
102
+ const chosen = selection({ items: Array.isArray(input.artifact_ids) ? input.artifact_ids.map(id => ({ source: 'core', id })) : null });
103
+ if (input.confirm_delete !== undefined && typeof input.confirm_delete !== 'boolean') fail('IRIS_COMMAND_INPUT_INVALID', 'confirm_delete 必须是布尔值');
104
+ const taskIds = input.task_ids ?? [];
105
+ if (!Array.isArray(taskIds) || taskIds.length > 200 || !taskIds.every(id => typeof id === 'string' && TASK_ID.test(id))
106
+ || new Set(taskIds).size !== taskIds.length) fail('IRIS_COMMAND_INPUT_INVALID', '关联任务 ID 无效');
107
+ active(options.signal);
108
+ const artifactIds = chosen.map(item => item.id);
109
+ const base = await maintainWorkbenchForDsh('core.delete', { artifact_ids: artifactIds }, options);
110
+ const referencing = [...new Set(base.blockers.filter(blocker => ['task_artifact_reference', 'task_input_reference'].includes(blocker.reason)).map(blocker => blocker.referencedBy))];
111
+ if (taskIds.some(id => !referencing.includes(id))) fail('IRIS_COMMAND_INPUT_INVALID', '只能显式选择这些作品直接关联的任务');
112
+ if (input.confirm_delete === true) return { ok: true, ...await maintainWorkbenchForDsh('core.delete', {
113
+ artifact_ids: artifactIds, task_ids: taskIds, confirm_delete: true }, options) };
114
+ const plan = taskIds.length ? await maintainWorkbenchForDsh('core.delete', { artifact_ids: artifactIds, task_ids: taskIds }, options) : base;
115
+ const tasks = [];
116
+ for (const id of referencing) {
117
+ const task = await inspectProviderTaskForDsh(id, options);
118
+ tasks.push({ id, settled: task.phase === 'terminal' && task.acceptance !== 'unknown' && task.outcome !== 'unknown' });
119
+ }
120
+ return { ok: true, preview: true, allowed: plan.allowed, artifactIds, taskIds, files: plan.entries.length,
121
+ bytes: plan.bytes, blockers: plan.blockers, referencingTasks: tasks };
122
+ }
123
+
124
+ export async function workbenchTransactions(options = {}) {
125
+ return { ok: true, ...await maintainWorkbenchForDsh('core.transactions', {}, options) };
126
+ }
127
+
128
+ export async function restoreWorkbenchTransaction(input, options = {}) {
129
+ exact(input, ['transaction_id', 'confirm_restore']);
130
+ if (input.confirm_restore !== true) fail('IRIS_WORK_RESTORE_CONFIRM_REQUIRED', '请明确确认恢复此隔离事务');
131
+ if (typeof input.transaction_id !== 'string' || !/^delete_[a-f0-9]{24}$/.test(input.transaction_id)) fail('IRIS_COMMAND_INPUT_INVALID', '隔离事务 ID 无效');
132
+ active(options.signal);
133
+ return { ok: true, ...await maintainWorkbenchForDsh('core.restore', { transaction_id: input.transaction_id }, options) };
134
+ }
135
+
136
+ /** API 边界只返回固定提示和稳定码,避免底层 ENOENT/JSON 错误带出文件路径。 */
137
+ export function workbenchError(error) {
138
+ if (error.name === 'AbortError') return error;
139
+ const code = String(error.code || 'IRIS_WORK_FAILED');
140
+ const messages = { IRIS_CORE_DELETE_BLOCKED: '作品仍被引用或任务尚未结束,请重新查看删除预览',
141
+ IRIS_CORE_MAINTENANCE_FAILED: '隔离操作未完成,恢复清单已保留,请查看 Core 回收区',
142
+ IRIS_CORE_RESTORE_CONFLICT: '恢复目标已存在,未覆盖已有作品', IRIS_CORE_RESTORE_INVALID: '此事务当前不可恢复',
143
+ IRIS_CORE_DATA_ROOT_BUSY: 'Core 正在被其他进程写入,请稍后重试',
144
+ IRIS_ARTIFACT_DIGEST_MISMATCH: '作品内容哈希核验失败', IRIS_WORK_DOWNLOAD_TOO_LARGE: '单次下载最多 128 MiB,请减少选中作品',
145
+ IRIS_WORK_RESTORE_CONFIRM_REQUIRED: '请明确确认恢复此隔离事务', IRIS_COMMAND_INPUT_INVALID: '作品操作参数无效' };
146
+ const missing = code === 'ENOENT' || /NOT_FOUND/.test(code);
147
+ return Object.assign(new Error(messages[code] || (missing ? '作品或隔离事务不存在' : '作品操作失败,请刷新后重试')), {
148
+ code, statusCode: error.statusCode || (missing ? 404 : code === 'IRIS_COMMAND_INPUT_INVALID' || code === 'IRIS_WORK_RESTORE_CONFIRM_REQUIRED' ? 400 : 409),
149
+ ...(error.transactionId ? { transactionId: error.transactionId } : {})
150
+ });
151
+ }
@@ -0,0 +1,39 @@
1
+ /** 作品下载专用 ZIP stored writer;无压缩/加密/ZIP64,不读取或解压外部归档。
2
+ * 布局依据 https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT §4.3。
3
+ * 调用方已限制 200 项/128 MiB,并生成安全的来源/ID 文件名。
4
+ */
5
+ const crcTable = Uint32Array.from({ length: 256 }, (_, value) => {
6
+ let crc = value;
7
+ for (let bit = 0; bit < 8; bit++) crc = crc & 1 ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1;
8
+ return crc >>> 0;
9
+ });
10
+
11
+ function crc32(bytes) {
12
+ let crc = 0xffffffff;
13
+ for (const byte of bytes) crc = crcTable[(crc ^ byte) & 255] ^ (crc >>> 8);
14
+ return (crc ^ 0xffffffff) >>> 0;
15
+ }
16
+
17
+ export function workbenchZip(entries) {
18
+ const local = [], central = [];
19
+ let offset = 0;
20
+ for (const entry of entries) {
21
+ const name = Buffer.from(entry.name, 'utf8'), bytes = entry.bytes, crc = crc32(bytes);
22
+ const header = Buffer.alloc(30);
23
+ header.writeUInt32LE(0x04034b50, 0); header.writeUInt16LE(20, 4);
24
+ header.writeUInt16LE(0x800, 6); header.writeUInt16LE(33, 12); // UTF-8,1980-01-01,stored。
25
+ header.writeUInt32LE(crc, 14); header.writeUInt32LE(bytes.length, 18); header.writeUInt32LE(bytes.length, 22);
26
+ header.writeUInt16LE(name.length, 26);
27
+ const record = Buffer.alloc(46);
28
+ record.writeUInt32LE(0x02014b50, 0); record.writeUInt16LE(20, 4); record.writeUInt16LE(20, 6);
29
+ record.writeUInt16LE(0x800, 8); record.writeUInt16LE(33, 14);
30
+ record.writeUInt32LE(crc, 16); record.writeUInt32LE(bytes.length, 20); record.writeUInt32LE(bytes.length, 24);
31
+ record.writeUInt16LE(name.length, 28); record.writeUInt32LE(offset, 42);
32
+ local.push(header, name, bytes); central.push(record, name);
33
+ offset += header.length + name.length + bytes.length;
34
+ }
35
+ const centralSize = central.reduce((sum, bytes) => sum + bytes.length, 0), end = Buffer.alloc(22);
36
+ end.writeUInt32LE(0x06054b50, 0); end.writeUInt16LE(entries.length, 8); end.writeUInt16LE(entries.length, 10);
37
+ end.writeUInt32LE(centralSize, 12); end.writeUInt32LE(offset, 16);
38
+ return Buffer.concat([...local, ...central, end]);
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mokuyoaxis/dsh-iris",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Multi-provider image, video, and speech generation, transcription, visual understanding, and prompt optimization with an integrated Iris workbench for DeepSeek Harness.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
package/user_guide.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # dsh-iris 用户指南
2
2
 
3
- 本指南说明如何安装、配置和使用 dsh-iris。项目仍处于早期版本;升级前请先阅读 [CHANGELOG](CHANGELOG.md)。
3
+ 本指南说明如何安装、配置和使用 Iris 0.2.1。升级范围见 [CHANGELOG](CHANGELOG.md) 和 [发布说明](docs/releases/0.2.1.md)。
4
4
 
5
5
  ## 安装
6
6
 
7
- 先运行 `dsh --version`。Iris `0.2.0` 仅声明支持 DSH `0.2.0-rc.2`;旧 DSH `>=0.1.2-rc.1 <0.1.3-0` 或 `0.1.5-rc.1` 使用固定版本 Iris `0.1.4`。验证边界见[适配说明](docs/DSH_RC2_ADAPTATION.md)。还需要 PATH 中的 `pnpm`,以及至少一个媒体或视觉服务供应商。Iris 0.2.0 要求 Node.js 22.0.0 或更高版本,旧 npm 0.1.4 的历史要求为 20.10.0;如果所用 DSH 版本要求更高,以 DSH 为准。Iris 0.1.1 及后续 0.1.x 不兼容 DSH 0.1.0/0.1.1 的旧客户端 Runtime。
7
+ 先运行 `dsh --version`。Iris `0.2.1` 仅声明支持 DSH `0.2.0-rc.2`;旧 DSH `>=0.1.2-rc.1 <0.1.3-0` 或 `0.1.5-rc.1` 使用固定版本 Iris `0.1.4`。验证边界见[适配说明](docs/DSH_RC2_ADAPTATION.md)。还需要 PATH 中的 `pnpm`,以及至少一个媒体或视觉服务供应商。Iris 0.2.1 要求 Node.js 22.0.0 或更高版本,旧 npm 0.1.4 的历史要求为 20.10.0;如果所用 DSH 版本要求更高,以 DSH 为准。Iris 0.1.1 及后续 0.1.x 不兼容 DSH 0.1.0/0.1.1 的旧客户端 Runtime。
8
8
 
9
9
  在上述旧 DSH 范围内,从 npm 安装固定稳定版到 Web profile:
10
10
 
@@ -15,17 +15,21 @@ dsh web
15
15
 
16
16
  npm 上无 scope 的 `dsh-iris` 是另一款插件;安装或更新 Iris Media 时必须使用完整 scoped 包名。
17
17
 
18
- 只下载旧稳定包可运行 `npm pack @mokuyoaxis/dsh-iris@0.1.4`,安装为普通 npm 依赖可运行 `npm install @mokuyoaxis/dsh-iris@0.1.4`;后者不会自动加入 DSH profile。固定版本仍可在 latest 更新后安装,0.1.4 不包含新的 Core 媒体 CLI。DSH rc.2 使用 0.2.0。
18
+ 只下载旧稳定包可运行 `npm pack @mokuyoaxis/dsh-iris@0.1.4`,安装为普通 npm 依赖可运行 `npm install @mokuyoaxis/dsh-iris@0.1.4`;后者不会自动加入 DSH profile。固定版本仍可在 latest 更新后安装,0.1.4 不包含新的 Core 媒体 CLI。DSH rc.2 使用 0.2.1。
19
19
 
20
20
  在 DSH `0.2.0-rc.2` 上,从 npm 安装:
21
21
 
22
22
  ```bash
23
- dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.0
23
+ dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.1
24
24
  dsh web
25
25
  ```
26
26
 
27
27
  浏览器默认打开 `http://127.0.0.1:3080`。插件安装后只会在下一次 DSH 启动时装载;如果 DSH 已经运行,请先停止再启动。启动输出包含一次性认证参数时,必须完整打开该 URL,不能只输入裸地址。
28
28
 
29
+ ### 独立 CLI
30
+
31
+ 无需 DSH,在自己的项目安装 `npm install @mokuyoaxis/dsh-iris@0.2.1`,通过 `npx dsh-iris --help` 查看入口。详见 [Headless CLI](docs/HEADLESS_CLI.md) 和 [视觉 CLI](docs/VISION_CLI.md)。
32
+
29
33
  其他 DSH 版本,包括 `0.2.0` 正式版与其他 rc,须另行验收。让 Agent 协助安装时,可使用[简要 Prompt](docs/AGENT_QUICKSTART.md)。
30
34
 
31
35
  通过 dsh-market 安装时也要核对显示的 Iris 版本、完整 scoped 包名和 DSH 支持范围。市场可能缓存 npm 信息;旧宿主仍按固定 `@0.1.4` 命令安装,不直接跟随 latest。
@@ -59,25 +63,28 @@ Iris 当前支持两类调用路径:
59
63
  | 路径 | 可用能力 | 说明 |
60
64
  |---|---|---|
61
65
  | DashScope 百炼 | 图片、视频、TTS、转写、视觉 | 覆盖最完整;媒体请求只允许阿里云官方 HTTPS 域名 |
62
- | OpenAI 兼容 | Images 图片生成、Chat Completions 视觉 | 具体模型和能力取决于兼容服务 |
66
+ | OpenAI 兼容 | Images/聊天/Responses 图片生成、聊天带图编辑、Chat Completions 视觉 | 具体模型和能力取决于兼容服务 |
63
67
 
64
68
  工作台会按实际媒体端点安全推断协议:设置了“媒体 Base URL”时以它为准,否则沿用 Base URL。阿里云官方 DashScope、地域和 Workspace HTTPS 域名可使用 `dashscope`,其他地址使用 `openai-images`,也可以在供应商卡片中明确选择。即使配置被误标为 `dashscope`,Iris 也会在 fetch 前拒绝向非官方地址发送 DashScope API Key。分离端点后,普通 Base URL 可继续服务视觉/对话,媒体 Base URL 专门服务图片、视频、TTS、转写和媒体模型发现;留空即保持旧行为。
65
69
 
66
- 同一个 OpenAI 兼容 Base URL 只有在实际提供相应接口时才能承担对应能力:图片生成需要 Images 接口,视觉理解需要支持图片输入的 Chat Completions 接口。通用 OpenAI 兼容端点目前不能替代 Iris 的 DashScope 视频、TTS 或转写协议。
70
+ 0.2.1支持每个生图模型独立设置 `imageProtocol`,省略或 `auto` 继承账号默认。例如网关的 `gemini-image` 如果通过聊天接口返回图片,可显式设置 `image-gen` 能力和 `openai-chat-images` 协议;已验证 Responses 生图的模型可选择 `openai-responses-images`(当前同步单图)。同账号的其他模型仍可使用 `openai-images` 或 `dashscope`。仅有模型名称的目录不会自动证明生图能力。产物保留真实 PNG/JPEG/WebP 字节和后缀,保存为 Core Artifact 后可按 ID 看图/OCR,详见 [配置与使用示例](docs/CLI_MANAGEMENT.md#模型级图片协议)。
67
71
 
68
- 开发分支中,自动选择的未知端点会在供应商管理区显示“协议为推断值,请确认”,仍按 OpenAI Images 兼容格式请求。显式选择协议后清除标记;不支持的显式协议会在请求前报错,配置值仍保留。视觉类型 `type` 与媒体协议独立,未支持的视觉类型不会被转换为 OpenAI。
72
+ 同一个 OpenAI 兼容 Base URL 只有在实际提供相应接口时才能承担对应能力:图片生成按模型配置选择 Images、聊天或 Responses 接口,聊天改图需模型支持带图生成,视觉理解需要支持图片输入的 Chat Completions 接口。通用 OpenAI 兼容端点目前不能替代 Iris 的 DashScope 视频、TTS 或转写协议。
73
+
74
+ 自动选择的未知端点会在供应商管理区显示“协议为推断值,请确认”,仍按 OpenAI Images 兼容格式请求。显式选择协议后清除标记;不支持的显式协议会在请求前报错,配置值仍保留。视觉类型 `type` 与媒体协议独立,未支持的视觉类型不会被转换为 OpenAI。
69
75
 
70
76
  ## 模型池
71
77
 
72
78
  “发现模型”对普通 OpenAI 兼容服务读取 `GET /models`;对阿里云官方端点读取并分页拉完 `GET /api/v1/models`。官方 `IG/VG/ASR/TTS/VU` 能力元数据优先,缺少元数据时才按模型名兜底。用户手工纠正的能力标签优先级最高,后续重新发现不会覆盖。发现失败时,原模型池不会被清空。
73
79
 
74
- 开发分支采用合并发现:保留手工模型、手工能力标注及本次目录未返回的旧模型,只追加或更新发现项。删除模型仍需用户显式操作。运行时仅使用已配置模型;某项能力缺少模型时会提示配置,不再猜测厂商默认值。旧裸账号的兼容目录会在配置写入时转成可编辑的模型条目,显式空模型池不会触发该迁移。
80
+ 模型发现采用合并更新:保留手工模型、手工能力标注及本次目录未返回的旧模型,只追加或更新发现项。删除模型仍需用户显式操作。运行时仅使用已配置模型;某项能力缺少模型时会提示配置,不再猜测厂商默认值。旧裸账号的兼容目录会在配置写入时转成可编辑的模型条目,显式空模型池不会触发该迁移。
75
81
 
76
82
  模型池中的能力名称如下:
77
83
 
78
84
  | 能力 | 配置值 | 典型工具 |
79
85
  |---|---|---|
80
86
  | 图片生成 | `image-gen` | `iris_draw_image` |
87
+ | Artifact 原图编辑(聊天生图) | `image-gen` | `iris_edit_image` |
81
88
  | 视频生成 | `video-gen` | `iris_generate_video` |
82
89
  | 语音合成 | `tts` | `iris_speak_text` |
83
90
  | 音频转写 | `transcribe` | `iris_transcribe_audio` |
@@ -89,8 +96,8 @@ Iris 当前支持两类调用路径:
89
96
 
90
97
  - 视觉测试会发送一张最小测试图;
91
98
  - 图片和 TTS 测试会生成真实产物;
92
- - 视频不会自动提交探针,因为“只确认受理”也可能创建计费任务;应通过“视频生成”操作验证。
93
- - 转写不使用空样本探针,应通过“音频转写”操作用真实音频验证。
99
+ - 视频不会自动提交探针,因为“只确认受理”也可能创建计费任务;正常使用可通过“视频生成”验证,耗尽后的恢复须用 CLI `models test --capability video-gen --input` 明确提供真实素材。
100
+ - 转写不使用空样本探针;耗尽后的恢复须用 CLI `models test --capability transcribe --input` 明确提供真实音频。
94
101
 
95
102
  模型不必经过“测”才能使用;正常的真实任务成功也会形成健康证据。模型池按钮只测试被点击的“供应商 × 模型 × 能力”,不会顺带验证同名模型或其他能力。
96
103
 
@@ -104,10 +111,12 @@ Iris 当前支持两类调用路径:
104
111
  | 蓝色 | 已配置,待验证 | 配置可用于路由,但尚无 7 天内成功证据,或旧成功已经过期 |
105
112
  | 绿色 | 近期验证成功 | 显式实测或真实任务在 7 天内成功 |
106
113
  | 暗红色 | 认证失败 | 最近出现明确 401、403 或认证/权限错误 |
114
+ | 琥珀色 | 429 冷却中 | 临时跳过模型,显示恢复候选资格的时间;优先遵从 `Retry-After`,默认 60 秒 |
115
+ | 红色 | 额度/预算耗尽已停用 | 明确免费额度或预算耗尽,手动实测成功后恢复 |
107
116
 
108
- 绿色旁显示的是最近成功时间,红色旁显示的是最近决定性认证失败时间。修改 API Key、Base URL、协议、供应商类型或能力模型会使相关旧证据失效。429/额度限制、网络、5xx、内容安全、取消和受理未知不会把近期绿色改红;Iris 也不会定时后台探测或为了点亮状态产生隐形费用。
117
+ 绿色旁显示最近成功时间,受阻模型显示原因和时间。短时限流与明确耗尽按账号 × 模型持久记录,重启后继续跳过;冷却到期只恢复候选资格,不发起请求、不自动变绿。实测按钮保持可用,可提前检测;只有明确的实测成功才解除持续停用,失败、发现、改变分配或旧任务成功都不能解除。修改 Key/端点会使旧验证证据失效,但不会清除耗尽停用。网络、5xx、内容安全、取消和受理未知不覆盖近期成功。此冷却/耗尽规则已纳入 0.2.1。
109
118
 
110
- 有多条 failover 路径时,只要一条近期成功,能力就是绿色;没有绿色但有待验证路径时为蓝色;只有全部已配置路径都明确认证失败时才为暗红。没有路径则为灰色。完整契约见 [Provider 与能力健康状态](docs/PROVIDER_HEALTH.md)。
119
+ 有多条 failover 路径时,可用候选近期成功则汇总为绿色,其次为待验证的蓝色;全部仅冷却时为琥珀色,全部受阻且含决定性失败时显示调用受阻。没有路径则为灰色。完整契约见 [Provider 与能力健康状态](docs/PROVIDER_HEALTH.md)。
111
120
 
112
121
  ## 能力分配和故障转移
113
122
 
@@ -138,6 +147,20 @@ IRIS_IMPORT_WORKBENCH_CONFIG="/absolute/path/ai-paint/data/config.json" dsh web
138
147
 
139
148
  变量未设置时不进行导入;相对路径会被拒绝。文件不可读、JSON 无效或没有有效供应商时会给出提示,不输出密钥。导入后的密钥写入 Iris 自己的配置文件,权限设为仅当前用户可读写;工作台和接口只返回密钥提示,不返回完整值。该操作不发送模型请求。
140
149
 
150
+ ## 工作台作品管理
151
+
152
+ 作品库统一展示 Core 与旧版的图片、视频、音频和文本,默认每页 24 个,可按媒体类型、来源和产物类型筛选。刷新或关闭识别结果保留当前页码与筛选。
153
+
154
+ Core 图片卡可直接点击“看图”或“OCR”;结果可以复制、下载为文本或取消。原始 PNG 可直接识别,不需要复制附件路径或转换 JPG。OCR 会明确显示部分完成,详细用法见 [工作台作品管理](docs/WORKBENCH_ARTIFACTS.md)。这些入口属于 0.2.1,更新本地插件后需重新加载 DSH 后端与客户端。
155
+
156
+ 作品详情和跨页选择支持批量管理:可复制选中 ID,或把 Core/旧版作品合为一个 ZIP 下载(最多 200 项、128 MiB)。Core 删除先展示预览,明确确认后移入可恢复回收区;任务/产物引用会阻止删除,关联的已结束任务记录需逐项明确勾选。“Core 回收区”可恢复同一隔离事务,遇到原位置冲突不会覆盖文件;旧版作品仍保留原有永久删除语义。
157
+
158
+ ### 按 Artifact ID 改图
159
+
160
+ Core 静态 PNG/JPEG/WebP 图片卡可点击“改图”,填写修改指令,再选择聊天生图模型。点击“生成修改版”才调用模型,修改版保存为关联原图的新作品,原图保留;可继续看图、OCR、下载或编辑。
161
+
162
+ Agent 可调用 `iris_edit_image`,提供 `source_artifact_id`、`prompt` 与可选 `model_ref`。CLI 使用 `run image` 的同名字段,详见 [Artifact 聊天改图](docs/CLI_MANAGEMENT.md#artifact-聊天改图)。仅支持 `openai-chat-images` 的带图生成,不提供 mask、多参考图或连续编辑历史。
163
+
141
164
  ## 高级配置文件
142
165
 
143
166
  一般应使用 Iris 工作台。只有在配置工作台尚未覆盖的协议或模型能力时,才需要直接编辑:
@@ -159,7 +182,6 @@ $DSH_HOME/iris/v1/providers.json
159
182
  "name": "Primary media provider",
160
183
  "type": "openai",
161
184
  "baseUrl": "https://api.example.com/v1",
162
- "mediaBaseUrl": "https://WORKSPACE_ID.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
163
185
  "apiKey": "YOUR_API_KEY",
164
186
  "enabled": true,
165
187
  "mediaProtocol": "openai-images",
@@ -178,7 +200,7 @@ $DSH_HOME/iris/v1/providers.json
178
200
 
179
201
  `mediaBaseUrl` 可以省略或留空,此时媒体调用沿用 `baseUrl`。`assignments` 可以省略;省略后使用模型池顺序。包含特殊字符的 provider 或模型引用会经过 URL 编码,复杂引用建议在工作台中生成,不要手写。
180
202
 
181
- 当前开发分支允许为明确不需要认证的本地端点设置 `"auth": "none"` 并省略 `apiKey`;缺省或 `"auth": "bearer"` 仍要求 Key,保持旧配置行为。无认证模式不会发送 Authorization 头,已有的旧 Key 也不会被带入该模式的媒体请求。工作台尚无专门的无认证开关,需要按上述停机原则编辑私有配置;不要对需要认证的公网端点使用 `auth:none`。
203
+ 可为明确不需要认证的本地端点设置 `"auth": "none"` 并省略 `apiKey`;缺省或 `"auth": "bearer"` 仍要求 Key,保持旧配置行为。无认证模式不会发送 Authorization 头,已有的旧 Key 也不会被带入该模式的媒体请求。工作台尚无专门的无认证开关,需要按上述停机原则编辑私有配置;不要对需要认证的公网端点使用 `auth:none`。
182
204
 
183
205
  API Key 以明文保存在宿主侧的 `providers.json` 中。POSIX 上 Iris 目录为 `0700`、文件为 `0600`,0.1.1 首次启动会收紧既有 Iris 树但不修改内容或跟随符号链接;Windows 的 mode 不能替代 ACL。状态接口和界面只返回掩码,不会显示完整 Key。不要把这个文件提交到版本库或发给他人。
184
206
 
@@ -192,6 +214,10 @@ Iris 工作台中的文件字段提供三种方式:
192
214
 
193
215
  浏览器上传单文件上限为 64 MB,默认保留 7 天。视频和音频操作最终都需要一个宿主可读路径;浏览器自己的路径、`content://` URI 和远程电脑上的路径不能直接使用。跨系统细节见[文件访问与跨环境](docs/file-access-across-environments.md)。
194
216
 
217
+ 看图、定位和 OCR 还可直接使用 Core 图片 `artifact_id`;视频摘要可复用 `frame_artifact_ids`。Core ID 与会话附件 ID 不混用,原 PNG 无需先转换为 JPG。Artifact 输入只读核验 MIME 与哈希,不要求原文件或临时导出。
218
+
219
+ 视觉操作共用完整终态、取消和整体预算。账号/模型的输入预算只处理发送副本,默认 8 MiB;Core 原图与下载保持不变。长图 OCR 最多 32 块、64 次生成、120 秒,明确标记部分完成和缩小时的细字风险;定位将模型的 0–1000 归一化坐标映射回原图像素。配置与示例见 [输入预算](docs/CLI_MANAGEMENT.md#看图输入预算)、[OCR](docs/OCR_MODEL.md) 和 [定位/摘要](docs/COMPOSITE_VISION.md)。
220
+
195
221
  ## 最小指令
196
222
 
197
223
  配置完成后,可以直接在会话中说:
@@ -210,7 +236,7 @@ Iris 工作台中的文件字段提供三种方式:
210
236
 
211
237
  直接写工具名最明确;自然语言也可以,由 Agent 判断需要调用哪个 Iris 工具。视频、图片和语音生成可能产生供应商费用。
212
238
 
213
- 从 0.1.2 起,插件会在 DSH Skill registry 可用时自动注册 `iris-verify-ui` 和 `iris-compose-media`,不要求会话工作区位于本仓库,也不需要用户复制 `.dsh/skills/`。项目目录中存在同名 Skill 时,DSH 仍优先使用项目版本;宿主未提供 Skill registry 时不影响 14 个 Iris 工具。
239
+ 从 0.1.2 起,插件会在 DSH Skill registry 可用时自动注册 `iris-verify-ui` 和 `iris-compose-media`,不要求会话工作区位于本仓库,也不需要用户复制 `.dsh/skills/`。项目目录中存在同名 Skill 时,DSH 仍优先使用项目版本;宿主未提供 Skill registry 时不影响 Iris 工具(0.2.1共 15 个)。
214
240
 
215
241
  用户想稳定指定方法时,可直接用 DSH 的 `/name` 形式:
216
242
 
@@ -224,6 +250,8 @@ Iris 工作台中的文件字段提供三种方式:
224
250
 
225
251
  ## 对话框提示词优化
226
252
 
253
+ 0.2.1增加“智能优化 / 只组装”、本次规则、可编辑结果和改动对照,详见 [提示词优化与规则组装](docs/PROMPT_OPTIMIZER.md)。规则按次传入,不修改现有 v1 配置;后续独立提示词优化系统另行安排。
254
+
227
255
  安装并启用 Iris 后,DSH 对话输入区会出现一个没有边框、底色和文字的“🫧”入口。点击后打开半透明玻璃悬浮窗;桌面端靠近输入区,手机窄屏自动变为底部面板,并适配浏览器安全区、可用高度和内部滚动。它独立于 Iris 工作台,可用于普通问答、写作、编程以及图片/视频生成提示词。[查看 Android 16 真机完整流程](docs/screenshots.md)。
228
256
 
229
257
  使用流程:
@@ -293,7 +321,7 @@ Task v2 将“供应商是否受理、观察是否继续、远端结果和本地
293
321
 
294
322
  工作台中的作品库独立于任务历史。新落盘媒体会自动入库;首次升级会接回已有 `outputs/`,也可以随时点击“找回本地作品”重新扫描。清空已完成任务或清理旧任务只删除任务状态、Prompt、错误与重试记录,作品仍可在工作台和泡泡打开。
295
323
 
296
- 删除单件作品或清空作品库会删除实际媒体文件,需要独立确认;相关任务记录不会连带删除,但其中的媒体会无法打开。“删除孤儿产物”不会触碰已入库作品。重要作品仍应另行备份,详见[作品库 v0](docs/ARTIFACT_LIBRARY.md)。收藏、标签和搜索将在完整 Artifact Manifest 之后实现。
324
+ 旧版作品删除或清空会永久删除实际媒体文件,需独立确认;相关旧任务记录保留,但媒体链接失效。“删除孤儿产物”只处理 legacy `outputs/`。Core 作品使用预览和可恢复回收区,引用保护会阻止单独删除;关联已结束任务需逐项明确选择,恢复不覆盖冲突。详见 [旧版作品库](docs/ARTIFACT_LIBRARY.md) 与 [Core 作品管理](docs/WORKBENCH_ARTIFACTS.md)。收藏、标签和搜索仍为后续功能。
297
325
 
298
326
  默认数据目录:
299
327
 
@@ -325,21 +353,23 @@ npx @mokuyoaxis/dsh-iris doctor --json
325
353
 
326
354
  已安装包也可以直接运行 `dsh-iris doctor`。退出码为 `0`(正常)、`1`(警告)和 `2`(硬错误)。Doctor 会执行一个随后立即删除的本地写入探针,并检查配置、任务、能力分配、临时文件和产物引用;输出不含完整 API Key。离线模式无法检查 DSH 工具/客户端是否已经装载,也不会用付费请求验证 Provider。
327
355
 
328
- **0.2.0** 另提供 `doctor --data-root <绝对路径> --json`:只读盘点指定 Core 数据根的 Task、Artifact、Index 和租约,不执行上述写入探针,也不自动修复。`providers list` 与 `capabilities list` 可用 `--provider-config <私有配置绝对路径>` 只读查看脱敏供应商摘要和按配置排序的候选链;它们不代表远端能力已实测。异常退出留下租约时,先用显式 Doctor 查看 owner,再按 [Headless CLI 的恢复步骤](docs/HEADLESS_CLI.md#异常退出后的写者租约)人工确认并运行 `runtime recover`,不要手动删除租约。安装 npm 0.2.0 后使用 `dsh-iris` 命令;临时运行可用 `npx --package @mokuyoaxis/dsh-iris@0.2.0 dsh-iris ...`。旧版 0.1.4 不包含这些 Core 命令。
356
+ **0.2.0** 另提供 `doctor --data-root <绝对路径> --json`:只读盘点指定 Core 数据根的 Task、Artifact、Index 和租约,不执行上述写入探针,也不自动修复。`providers list` 与 `capabilities list` 可用 `--provider-config <私有配置绝对路径>` 只读查看脱敏供应商摘要和按配置排序的候选链;它们不代表远端能力已实测。异常退出留下租约时,先用显式 Doctor 查看 owner,再按 [Headless CLI 的恢复步骤](docs/HEADLESS_CLI.md#异常退出后的写者租约)人工确认并运行 `runtime recover`,不要手动删除租约。安装 npm 0.2.1 后使用 `dsh-iris` 命令;临时运行可用 `npx --package @mokuyoaxis/dsh-iris@0.2.1 dsh-iris ...`。旧版 0.1.4 不包含这些 Core 命令。
329
357
 
330
358
  ## Host Doctor
331
359
 
332
- DSH 运行后,打开“设置 → Iris 工作台 → 宿主诊断”。卡片会自动检查当前 DSH 版本、Iris 服务端和客户端版本、14 个工具、两项随包 Skill、四组 Web 路由、四个 UI Slot,以及 Browser、附件、会话和宿主模型端口;点击“刷新”可重新读取,展开项可查看全部结果。需要原始 JSON 时,可在已经完成 DSH Web 认证的同一浏览器访问 `/iris/api/doctor`。
360
+ DSH 运行后,打开“设置 → Iris 工作台 → 宿主诊断”。卡片会自动检查当前 DSH 版本、Iris 服务端和客户端版本、工具注册(0.2.1为 15 个)、两项随包 Skill、四组 Web 路由、四个 UI Slot,以及 Browser、附件、会话和宿主模型端口;点击“刷新”可重新读取,展开项可查看全部结果。需要原始 JSON 时,可在已经完成 DSH Web 认证的同一浏览器访问 `/iris/api/doctor`。
333
361
 
334
362
  Host Doctor 不打开 Browser、不读取附件或会话、不调用模型,也不请求供应商。报告中的 `exitCode` 是诊断严重度,不是 HTTP 状态:`0` 全部健康,`1` 有可降级警告,`2` 有硬错误。未安装 HTML Browser 时出现一条 Browser 警告是预期降级;如果提示尚未收到客户端握手,刷新 DSH 页面后再点“刷新”。若显示 `not found`,通常是前端已更新而 DSH 后端仍运行旧插件代码,应重启 DSH 后再刷新页面。
335
363
 
336
- Host Doctor 显示通过,只代表“插件与宿主接口已正确装载”,不代表 API Key 或某个模型可调用。供应商侧应看四色能力健康,并在模型池中显式实测或完成一项真实任务。完整字段和安全边界见 [Host Doctor](docs/HOST_DOCTOR.md),两类检查的区别见 [Provider 与能力健康状态](docs/PROVIDER_HEALTH.md)。
364
+ Host Doctor 显示通过,只代表“插件与宿主接口已正确装载”,不代表 API Key 或某个模型可调用。供应商侧应看能力健康颜色与恢复条件,并在模型池中显式实测或完成一项真实任务。完整字段和安全边界见 [Host Doctor](docs/HOST_DOCTOR.md),两类检查的区别见 [Provider 与能力健康状态](docs/PROVIDER_HEALTH.md)。
337
365
 
338
366
  ## 可选依赖
339
367
 
340
- 视频抽帧和视频摘要依赖 PATH 中的 `ffmpeg` 与 `ffprobe`。缺少它们只会禁用这两项能力,不影响生成、看图、OCR 或语音工具。
368
+ 0.2.1 提供 [独立视觉 CLI](docs/VISION_CLI.md):`vision look / locate / ocr / summarize`。必须显式指定私有 Provider 配置;可指定 `--model-ref providerId::modelId` 严格选型、保存 JSON/文本或摘要拼图。默认只分析画面;文件输入不要求数据根,使用 `artifact_id` 或 `frame_artifact_ids` 时需显式数据根并只读 Core,主动音轨转写使用 Core writer。
369
+
370
+ 视频文件抽帧依赖 PATH 中的 `ffmpeg` 与 `ffprobe`。缺少它们无法从视频文件生成新帧;摘要可复用既有 Core 帧 ID,不需要这两个工具。生成、看图、OCR 或语音工具不受影响。
341
371
 
342
- HTML 截图依赖 DSH 提供 `dsh-builtin-browser`。截图页在离线沙箱中运行,不加载远程脚本、字体或图片;需要的资源应内联到 HTML。
372
+ DSH HTML 截图依赖宿主的 `dsh-builtin-browser`;独立 CLI 显式提供 Chromium 可执行文件,见 [HTML 截图](docs/CLI_MANAGEMENT.md#html-截图)。截图页在离线沙箱中运行,不加载远程脚本、字体或图片;需要的资源应内联到 HTML。
343
373
 
344
374
  ## 更新和卸载
345
375
 
@@ -365,7 +395,7 @@ dsh plugin --profile web remove @mokuyoaxis/dsh-iris
365
395
  - **提示词优化提示没有模型**:先在当前会话选择主模型,或在 JSON 中配置 `route.mode: "fixed"`。
366
396
  - **设置中没有 Iris 工作台**:确认插件已加入 Web profile,并在安装后重启 DSH;可用 `dsh plugin --profile web list` 和 `--dump-config` 检查。
367
397
  - **没有可用模型**:先确认供应商已启用且 Key 有效,再运行“发现模型”;不支持 `/models` 时手动添加。
368
- - **OpenAI 兼容图片请求走错协议**:确认该供应商的 `mediaProtocol` 是 `openai-images`。
398
+ - **OpenAI 兼容图片请求走错协议**:核对模型的有效图片协议;`/images/generations` 使用 `openai-images`,网关聊天图片扩展使用 `openai-chat-images`,同步 `/responses` 生图使用 `openai-responses-images`。可在生图模型行或 CLI `models protocol` 设置模型级覆盖,不需要改变同账号其他模型的接口。
369
399
  - **反向代理访问返回 `untrusted host`**:把浏览器实际使用的域名或 `host:port` 加入 `IRIS_TRUSTED_HOSTS`,保留 Host,并确认代理层已经启用认证。
370
400
  - **看图可用但 Iris 模型池显示为空**:Agent 视觉工具可以回退到 DSH 全局视觉模型;需要在工作台操作时仍建议配置 Iris 的 `vision` 模型。
371
401
  - **视频摘要提示 ffmpeg 不可用**:安装系统级 `ffmpeg` 和 `ffprobe`,并确保 DSH 进程的 PATH 能找到它们。