@mokuyoaxis/dsh-iris 0.2.2 → 0.2.3

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 (63) hide show
  1. package/.dsh/skills/iris-compose-media/SKILL.md +23 -73
  2. package/.dsh/skills/iris-compose-media/agents/openai.yaml +1 -1
  3. package/.dsh/skills/iris-compose-media/references/task-v2-recovery.md +23 -22
  4. package/.dsh/skills/iris-compose-media/references/workflows.md +25 -30
  5. package/.dsh/skills/iris-use/SKILL.md +41 -0
  6. package/.dsh/skills/iris-use/agents/openai.yaml +4 -0
  7. package/.dsh/skills/iris-use/references/media-contract.md +73 -0
  8. package/.dsh/skills/iris-verify-ui/SKILL.md +9 -7
  9. package/.dsh/skills/iris-verify-ui/agents/openai.yaml +1 -1
  10. package/.dsh/skills/iris-verify-ui/references/verdict-and-degradation.md +3 -0
  11. package/CHANGELOG.md +26 -2
  12. package/README.en.md +39 -32
  13. package/README.md +37 -31
  14. package/bin/dsh-iris.js +27 -6
  15. package/docs/AGENT_QUICKSTART.md +9 -9
  16. package/docs/ARCHITECTURE.md +1 -1
  17. package/docs/CORE_RUNTIME_CONTRACT.md +3 -1
  18. package/docs/DSH_CORE_MIGRATION.md +6 -2
  19. package/docs/FAULT_INJECTION.md +1 -0
  20. package/docs/HEADLESS_CLI.md +8 -4
  21. package/docs/HOST_DOCTOR.md +3 -3
  22. package/docs/MODEL_PORT_CONTRACT.md +6 -2
  23. package/docs/MUSIC.md +77 -0
  24. package/docs/PROMPT_OPTIMIZER.md +48 -10
  25. package/docs/PROVIDER_ADAPTER_CONTRACT.md +2 -0
  26. package/docs/ROADMAP.md +14 -25
  27. package/docs/SECURITY.md +3 -2
  28. package/docs/SYNC_DELIVERY_RECOVERY.md +39 -0
  29. package/docs/TASK_SEMANTICS.md +4 -4
  30. package/docs/releases/0.2.3.md +54 -0
  31. package/lib/actions.js +40 -3
  32. package/lib/adapters.js +1 -0
  33. package/lib/api.js +14 -5
  34. package/lib/bundled-skills.js +1 -1
  35. package/lib/capability.js +2 -0
  36. package/lib/client.js +111 -26
  37. package/lib/command-service.js +12 -3
  38. package/lib/config.js +26 -2
  39. package/lib/core-artifact-store.js +44 -6
  40. package/lib/core-delivery-cache.js +145 -0
  41. package/lib/core-maintenance.js +12 -1
  42. package/lib/core-tasks.js +19 -5
  43. package/lib/core-user-projection.js +2 -2
  44. package/lib/dsh-core-adapter.js +14 -4
  45. package/lib/dsh-text-model-adapter.js +15 -2
  46. package/lib/generation-input.js +10 -0
  47. package/lib/host-runtime.js +4 -2
  48. package/lib/index.js +23 -4
  49. package/lib/models.js +4 -1
  50. package/lib/music-generation.js +21 -0
  51. package/lib/music-options.js +18 -0
  52. package/lib/music-transport.js +46 -0
  53. package/lib/prompt-optimizer-config.js +92 -21
  54. package/lib/prompt-optimizer-core.js +51 -6
  55. package/lib/prompt-optimizer.js +20 -14
  56. package/lib/provider-adapter.js +1 -0
  57. package/lib/provider-adapters.js +24 -1
  58. package/lib/provider-catalog.js +18 -7
  59. package/lib/provider-config-service.js +13 -1
  60. package/lib/provider-protocol.js +12 -0
  61. package/lib/provider-task-runner.js +80 -5
  62. package/package.json +4 -2
  63. package/user_guide.md +30 -16
package/lib/actions.js CHANGED
@@ -23,7 +23,7 @@ import { registerMedia, mediaLinksOf, webBase } from './media.js';
23
23
  import { RED_TEST_IMAGE } from './vision.js';
24
24
  import { runVisionRequest, readVisionImageFile, probeVisionModel, visionImageFromDataUrl } from './vision-model-routing.js';
25
25
  import { HOST_CONTRACT_VERSION, HOST_PORTS, defineHostAdapter, hasHostPort, requireHostPort } from './host-contract.js';
26
- import { coreSnapshotForDsh, cropForDsh, diffForDsh, framesForDsh, summaryFramesForDsh, visionImageForDsh, htmlForDsh, inspectProviderTaskForDsh, submitProviderTaskForDsh, watchProviderTaskForDsh } from './dsh-core-adapter.js';
26
+ import { coreSnapshotForDsh, cropForDsh, diffForDsh, framesForDsh, summaryFramesForDsh, visionImageForDsh, htmlForDsh, inspectProviderTaskForDsh, submitMusicForDsh, submitProviderTaskForDsh, watchProviderTaskForDsh } from './dsh-core-adapter.js';
27
27
  import { runLocateRequest, runSummaryRequest } from './composite-vision-routing.js';
28
28
  import { normalizeSummarySource, summaryFrameRecords, summaryTimelineLabel } from './summary-input.js';
29
29
  import { normalizeVisionImageSource } from './vision-input.js';
@@ -32,7 +32,7 @@ import { runOcrRequest } from './ocr-model-routing.js';
32
32
  import { ffmpegAvailable, extractFrames, probeVideo } from './media-probe.js';
33
33
  import { ensurePrivateDir, writePrivateFile } from './private-storage.js';
34
34
  import { selectMediaProtocol, selectImageProtocol, providerForImageModel } from './provider-protocol.js';
35
- import { providerTaskBinding } from './provider-catalog.js';
35
+ import { musicCandidatesFromCatalog, providerTaskBinding } from './provider-catalog.js';
36
36
  import { invokeProviderOperation, providerPollTaskView, prepareProviderInput } from './provider-adapter.js';
37
37
  import { createConfiguredProviderAdapter as createProviderAdapter } from './provider-adapters.js';
38
38
  import { deriveUserState } from './task-semantics.js';
@@ -564,6 +564,33 @@ async function submitCoreTts(routes, normalizedInput, signal) {
564
564
  }
565
565
 
566
566
  /* ---------- 🔊 语音合成 ---------- */
567
+ register('music', async (host, args, { signal }) => {
568
+ const normalized = normalizeGenerationInput('music', {
569
+ prompt: args.prompt,
570
+ ...(optionalActionText(args.timeout_ms) !== undefined ? { timeout_ms: args.timeout_ms } : {}),
571
+ ...(optionalActionText(args.retry_502) !== undefined ? { retry_502: args.retry_502 } : {}),
572
+ ...(optionalActionText(args.retry_delay_ms) !== undefined ? { retry_delay_ms: args.retry_delay_ms } : {})
573
+ });
574
+ const routes = musicCandidatesFromCatalog({ providers: store.allProviders(), assignments: store.assignments() }, args.model);
575
+ const submitted = await submitMusicForDsh({
576
+ candidates: routes.map(route => ({ adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
577
+ selectionReason: route.selectionReason, providerBinding: providerTaskBinding(route.provider) })),
578
+ providerInput: normalized.providerInput, musicOptions: normalized.musicOptions, signal
579
+ });
580
+ const task = submitted.task;
581
+ recordCoreTaskAttemptHealth(task, cap.CAPABILITIES.MUSIC);
582
+ if (submitted.localError) throw taskActionError(task.id, 'Music 结果未能可靠落盘', submitted.localError);
583
+ if (task.outcome !== 'succeeded' || task.deliveryState !== 'ready') {
584
+ throw taskActionError(task.id, task.outcome === 'succeeded'
585
+ ? '音乐已返回,但交付失败;可从缓存重新交付'
586
+ : `音乐未取得产物(已重试${submitted.retries}次)`, task.lastError);
587
+ }
588
+ return { ok: true, storage: 'core', taskId: task.id, taskIds: submitted.taskIds, retries: submitted.retries,
589
+ model: task.modelRef, providerId: task.providerId, artifactIds: task.artifactIds,
590
+ audioUrl: '/iris/api/core/artifact/' + encodeURIComponent(task.artifactIds[0]) + '/media',
591
+ text: `[iris] 音乐已生成(${task.modelRef},重试${submitted.retries}次,Core task: ${task.id})` };
592
+ });
593
+
567
594
  register('tts', async (host, args, { signal }) => {
568
595
  const { providerInput } = normalizeGenerationInput('tts', {
569
596
  text: args.text, voice: optionalActionText(args.voice)
@@ -1008,6 +1035,7 @@ register('providers_list', async (host, args) => {
1008
1035
  id: m.id,
1009
1036
  capabilities: m.capabilities,
1010
1037
  ...(m.imageProtocol !== undefined ? { imageProtocol: m.imageProtocol } : {}),
1038
+ ...(m.musicProtocol !== undefined ? { musicProtocol: m.musicProtocol } : {}),
1011
1039
  ...(m.visionInput !== undefined ? { visionInput: m.visionInput } : {}),
1012
1040
  ...(m.capabilities.includes(cap.CAPABILITIES.VISION) ? { visionInputEffective: visionInputLimits(p, m.id) } : {}),
1013
1041
  ...(m.capabilities.includes(cap.CAPABILITIES.IMAGE) ? { imageRouting: selectImageProtocol(p, m.id) } : {}),
@@ -1100,6 +1128,9 @@ register('providers_test_vision', async (host, args, { signal }) => {
1100
1128
 
1101
1129
  /** 按能力实测单个模型;返回 {ok, note}。真实调用仅在 UI 明确确认后执行。 */
1102
1130
  async function probeModel(host, p, modelId, capability, signal) {
1131
+ if (capability === cap.CAPABILITIES.MUSIC) {
1132
+ return { skipped: true, ok: false, note: 'Music 不发起小样本探测;请通过音乐功能提交实际创作要求' };
1133
+ }
1103
1134
  if (capability === cap.CAPABILITIES.VISION) {
1104
1135
  const r = await probeVisionModel(p, modelId, visionImageFromDataUrl(RED_TEST_IMAGE), { timeoutMs: 15000, signal });
1105
1136
  return {
@@ -1207,6 +1238,12 @@ register('providers_set_model_image_protocol', async (host, args) => {
1207
1238
  return { ok: true, text: '[iris] 模型图片协议已更新' };
1208
1239
  });
1209
1240
 
1241
+ register('providers_set_model_music_protocol', async (host, args) => {
1242
+ const ok = store.setModelMusicProtocol(String(args.id || ''), String(args.model_id || ''), args.musicProtocol);
1243
+ if (!ok) throw new Error('iris: 模型不在池中');
1244
+ return { ok: true, text: '[iris] 模型音乐协议已更新' };
1245
+ });
1246
+
1210
1247
  register('providers_set_model_vision_input', async (host, args) => {
1211
1248
  if (!Object.hasOwn(args, 'visionInput')) throw new Error('iris: 需要 visionInput 对象或 null');
1212
1249
  const ok = store.setModelVisionInput(String(args.id || ''), String(args.model_id || ''), args.visionInput);
@@ -1221,7 +1258,7 @@ register('providers_test_model', async (host, args, { signal }) => {
1221
1258
  const modelId = String(args.model_id || '').trim();
1222
1259
  const capability = String(args.capability || '').trim();
1223
1260
  if (!modelId || !cap.isKnownCapability(capability)) throw new Error('iris: 需 model_id 与合法 capability');
1224
- if (![cap.CAPABILITIES.VIDEO, cap.CAPABILITIES.TRANSCRIBE].includes(capability) && args.confirm_paid !== true) {
1261
+ if (![cap.CAPABILITIES.VIDEO, cap.CAPABILITIES.TRANSCRIBE, cap.CAPABILITIES.MUSIC].includes(capability) && args.confirm_paid !== true) {
1225
1262
  throw new Error('iris: 模型实测会发起真实供应商请求,请先明确确认');
1226
1263
  }
1227
1264
  let result;
package/lib/adapters.js CHANGED
@@ -15,6 +15,7 @@ import { dashscopeApiBase, imageSizeViolation, isDashScopeBaseUrl, normalizeImag
15
15
  import { chmodPrivateFile, ensurePrivateDir, privateSibling } from './private-storage.js';
16
16
  import { isFreeQuotaExhausted, retryAfterDelay } from './provider-health.js';
17
17
  import { ProviderContractError } from './provider-contract.js';
18
+ export { generateMusic } from './music-transport.js';
18
19
 
19
20
  /**
20
21
  * 网络调用超时档位(2026-09-03 健康检查):挂死的连接绝不能冻住盯守 tick——
package/lib/api.js CHANGED
@@ -33,9 +33,12 @@ import {
33
33
  importPromptOptimizerConfig,
34
34
  loadPromptOptimizerConfig,
35
35
  resetPromptOptimizerConfig,
36
+ resetPromptOptimizerInstructions,
36
37
  setPromptOptimizerEnabled
37
38
  } from './prompt-optimizer-config.js';
38
39
 
40
+ const PROMPT_CONFIG_CAPABILITIES = Object.freeze({ ...PROMPT_OPTIMIZER_CAPABILITIES, resetInstructions: true });
41
+
39
42
  function readJsonSafe(file) {
40
43
  try {
41
44
  const raw = fs.readFileSync(file, 'utf8');
@@ -369,7 +372,7 @@ export function serveApi(req, res, host) {
369
372
  return serveSse(res);
370
373
  }
371
374
  if (urlPath === '/iris/api/prompt-optimizer/config') {
372
- return sendJson(res, 200, method === 'HEAD' ? {} : { ...loadPromptOptimizerConfig(), capabilities: PROMPT_OPTIMIZER_CAPABILITIES });
375
+ return sendJson(res, 200, method === 'HEAD' ? {} : { ...loadPromptOptimizerConfig(), capabilities: PROMPT_CONFIG_CAPABILITIES });
373
376
  }
374
377
  if (urlPath === '/iris/api/doctor') {
375
378
  if (!host || typeof host !== 'object') return sendJson(res, 503, { error: 'host unavailable' });
@@ -506,7 +509,9 @@ function handleJsonPost(req, res, execute, respond = sendJson) {
506
509
  if (overflow) return finish(413, { error: 'request body too large (limit 1MB)' });
507
510
  try {
508
511
  const raw = Buffer.concat(chunks).toString('utf8');
509
- const body = raw ? JSON.parse(raw) : {};
512
+ let body;
513
+ try { body = raw ? JSON.parse(raw) : {}; }
514
+ catch (_) { return finish(controller.signal.aborted ? 499 : 400, { error: controller.signal.aborted ? 'request canceled' : 'invalid JSON' }); }
510
515
  const result = await execute(body, controller.signal);
511
516
  if (controller.signal.aborted) return finish(499, { error: 'request canceled' });
512
517
  finish(200, result);
@@ -630,10 +635,14 @@ function handlePromptOptimizer(host, operation, req, res) {
630
635
  if (operation === 'optimize') return optimizePrompt(host, body, { signal });
631
636
  if (operation === 'import') {
632
637
  const imported = body && body.config !== undefined ? body.config : body;
633
- return { ok: true, ...importPromptOptimizerConfig(imported), capabilities: PROMPT_OPTIMIZER_CAPABILITIES };
638
+ return { ok: true, ...importPromptOptimizerConfig(imported), capabilities: PROMPT_CONFIG_CAPABILITIES };
639
+ }
640
+ if (operation === 'reset') {
641
+ const scope = body?.scope === undefined ? 'all' : body.scope;
642
+ if (!['all', 'instructions'].includes(scope)) throw new Error('scope 只能是 all 或 instructions');
643
+ return { ok: true, ...(scope === 'instructions' ? resetPromptOptimizerInstructions() : resetPromptOptimizerConfig()), capabilities: PROMPT_CONFIG_CAPABILITIES };
634
644
  }
635
- if (operation === 'reset') return { ok: true, ...resetPromptOptimizerConfig(), capabilities: PROMPT_OPTIMIZER_CAPABILITIES };
636
- if (operation === 'enabled') return { ok: true, ...setPromptOptimizerEnabled(body && body.enabled), capabilities: PROMPT_OPTIMIZER_CAPABILITIES };
645
+ if (operation === 'enabled') return { ok: true, ...setPromptOptimizerEnabled(body && body.enabled), capabilities: PROMPT_CONFIG_CAPABILITIES };
637
646
  throw new Error('unknown prompt optimizer operation');
638
647
  });
639
648
  }
@@ -5,7 +5,7 @@ import path from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { requireHostPort } from './host-contract.js';
7
7
 
8
- const SKILL_NAMES = ['iris-verify-ui', 'iris-compose-media'];
8
+ const SKILL_NAMES = ['iris-use', 'iris-compose-media', 'iris-verify-ui'];
9
9
  const SKILL_ROOT = fileURLToPath(new URL('../.dsh/skills/', import.meta.url));
10
10
 
11
11
  function parseScalar(value, file) {
package/lib/capability.js CHANGED
@@ -14,6 +14,7 @@ export const CAPABILITIES = Object.freeze({
14
14
  IMAGE: 'image-gen',
15
15
  VIDEO: 'video-gen',
16
16
  TTS: 'tts',
17
+ MUSIC: 'music-gen',
17
18
  TRANSCRIBE: 'transcribe',
18
19
  VISION: 'vision'
19
20
  });
@@ -26,6 +27,7 @@ const INFER_FIELD = {
26
27
  [CAPABILITIES.IMAGE]: 'imageModel',
27
28
  [CAPABILITIES.VIDEO]: 'videoModel',
28
29
  [CAPABILITIES.TTS]: 'ttsModel',
30
+ [CAPABILITIES.MUSIC]: 'musicModel',
29
31
  [CAPABILITIES.TRANSCRIBE]: 'transcribeModel',
30
32
  [CAPABILITIES.VISION]: 'visionModel'
31
33
  };
package/lib/client.js CHANGED
@@ -21,7 +21,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
21
21
  var exports = module.exports;
22
22
 
23
23
  var React = require('react');
24
- var IRIS_CLIENT_VERSION = '0.2.2';
24
+ var IRIS_CLIENT_VERSION = '0.2.3';
25
25
  var HOST_CLIENT_PROTOCOL_VERSION = 0;
26
26
  var reportedSeats = [];
27
27
 
@@ -810,7 +810,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
810
810
  }
811
811
 
812
812
  /* ---- 模型池(阶段 9 P4):发现/手动模型 + verified 标记 + 逐模型测试/移除 + 手动添加 ---- */
813
- var CAP_SHORT = { 'image-gen': '图', 'video-gen': '视', 'tts': '音', 'transcribe': '转', 'vision': '视👁' };
813
+ var CAP_SHORT = { 'image-gen': '图', 'video-gen': '视', 'tts': '音', 'music-gen': '乐', 'transcribe': '转', 'vision': '视👁' };
814
814
  function ModelPool(props) {
815
815
  var p = props.provider;
816
816
  var addPair = React.useState('');
@@ -821,6 +821,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
821
821
  postAction('providers_set_model_image_protocol', { id: p.id, model_id: m.id, imageProtocol: value })
822
822
  .then(function (r) { props.onNote(r.ok ? '模型图片协议已更新' : (r.d.error || '更新失败')); if (r.ok) props.onDone(); });
823
823
  }
824
+ function setMusicProtocol(m, value) {
825
+ if (!value) return;
826
+ postAction('providers_set_model_music_protocol', { id: p.id, model_id: m.id, musicProtocol: value })
827
+ .then(function (r) { props.onNote(r.ok ? '模型音乐协议已更新' : (r.d.error || '更新失败')); if (r.ok) props.onDone(); });
828
+ }
824
829
  function verBadge(m, c) {
825
830
  var health = m.health && m.health[c];
826
831
  var status = health && HEALTH_META[health.status] ? health.status : 'configured';
@@ -849,6 +854,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
849
854
  React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
850
855
  React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图')),
851
856
  m.imageRouting && m.imageRouting.protocolInferred ? ' · 协议待确认' : null) : null,
857
+ caps.includes('music-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '音乐协议 ',
858
+ React.createElement('select', { value: m.musicProtocol || '', onChange: function (e) { setMusicProtocol(m, e.target.value); } },
859
+ React.createElement('option', { value: '' }, '请选择音乐协议'),
860
+ React.createElement('option', { value: 'gemini-web-music' }, 'Chat Completions(内嵌音频)'))) : null,
852
861
  caps.includes('vision') || (caps.includes('image-gen') && m.imageRouting && m.imageRouting.mediaProtocol === 'openai-chat-images') ? React.createElement(VisionInputEditor, { key: 'vision-input', provider: p, model: m,
853
862
  onNote: props.onNote, onDone: props.onDone }) : null,
854
863
  blocked ? React.createElement('span', { className: 'iris-pm-ver failed ' + healthClass(blocked), title: healthText(blocked) }, rateLimitLabel(blocked)) : null,
@@ -1000,7 +1009,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1000
1009
  postAction('providers_discover', { id: p.id }).then(function (r) { setNote(r.d.text || r.d.error || '发现完成'); refresh(); });
1001
1010
  }
1002
1011
  function testModel(p, m, capability) {
1003
- var noProbe = capability === 'video-gen' || capability === 'transcribe';
1012
+ var noProbe = capability === 'video-gen' || capability === 'transcribe' || capability === 'music-gen';
1004
1013
  if (!noProbe && !window.confirm('将用模型「' + m.id + '」发起一次真实的 ' + capability + ' 请求,可能产生费用。继续吗?')) return;
1005
1014
  setNote('验证 ' + m.id + ' · ' + capability + '…');
1006
1015
  postAction('providers_test_model', { id: p.id, model_id: m.id, capability: capability, confirm_paid: !noProbe })
@@ -1166,7 +1175,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1166
1175
  if (!r.ok || !r.d) return;
1167
1176
  var opts = (r.d.poolByCapability && r.d.poolByCapability[capability]) || [];
1168
1177
  var ord = (r.d.order && r.d.order[capability]) || [];
1169
- var assignedRef = ord[0] || (opts[0] && opts[0].ref) || '';
1178
+ if (capability === 'music-gen') opts = opts.filter(function (option) { return ord.includes(option.ref); });
1179
+ var assignedRef = ord[0] || (capability !== 'music-gen' && opts[0] && opts[0].ref) || '';
1170
1180
  var assignedOpt = opts.find(function (o) { return o.ref === assignedRef; });
1171
1181
  // 不再自动填 vals.model:留空 = 用能力分配的解析结果(避免绕过分配)
1172
1182
  setModelInfo({ assigned: assignedOpt ? assignedOpt.label : '', assignedRef: assignedRef, options: opts, loaded: true });
@@ -1183,7 +1193,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1183
1193
  })
1184
1194
  .then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
1185
1195
  .then(function (res) {
1186
- setRun(res.ok ? { ok: true, text: res.d.text || '', img: res.d.imageUrl || res.d.imageDataUrl || null } : { ok: false, text: res.d.error || '执行失败' });
1196
+ setRun(res.ok ? { ok: true, text: res.d.text || '', img: res.d.imageUrl || res.d.imageDataUrl || null, audio: res.d.audioUrl || null } : { ok: false, text: res.d.error || '执行失败' });
1187
1197
  if (res.ok && res.d && res.d.storage === 'core') window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
1188
1198
  })
1189
1199
  .catch(function () { setRun({ ok: false, text: '网络错误' }); });
@@ -1193,6 +1203,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1193
1203
  if (open) {
1194
1204
  var inputs = fields.map(function (f) {
1195
1205
  var node;
1206
+ if (f.key === 'model' && capability === 'music-gen') {
1207
+ node = React.createElement('select', { value: vals.model || '', onChange: function (e) { setVal('model', e.target.value); } },
1208
+ React.createElement('option', { value: '' }, '按 Music 分配顺序'),
1209
+ ((modelInfo && modelInfo.options) || []).map(function (option) {
1210
+ return React.createElement('option', { key: option.ref, value: option.ref }, option.label);
1211
+ }));
1212
+ return React.createElement('div', { key: f.key, className: 'iris-act-field' }, React.createElement('label', {}, f.label), node);
1213
+ }
1196
1214
  if (f.type === 'file') {
1197
1215
  node = React.createElement(FileField, { value: vals[f.key] || '', accept: f.accept || '', onChange: function (v) { setVal(f.key, v); } });
1198
1216
  return React.createElement('div', { key: f.key, className: 'iris-act-field' },
@@ -1220,6 +1238,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1220
1238
  result = React.createElement('div', { className: 'iris-act-result' },
1221
1239
  React.createElement('div', {}, run.text),
1222
1240
  React.createElement('img', { className: 'iris-act-img', src: run.img, alt: '结果' }));
1241
+ } else if (run.ok && run.audio) {
1242
+ result = React.createElement('div', { className: 'iris-act-result' }, React.createElement('div', {}, run.text),
1243
+ React.createElement('audio', { src: run.audio, controls: true, preload: 'metadata' }),
1244
+ React.createElement('a', { href: run.audio, download: '' }, '下载音乐'));
1223
1245
  } else {
1224
1246
  result = React.createElement('div', { className: 'iris-act-result' + (run.ok ? '' : ' err') }, run.text);
1225
1247
  }
@@ -1232,7 +1254,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1232
1254
  modelBar = React.createElement('div', { className: 'iris-act-field' },
1233
1255
  React.createElement('label', {},
1234
1256
  React.createElement('span', { className: 'iris-pm-dot ' + healthClass(cardHealth) }, cardHealth.retryAt ? '◷' : HEALTH_META[cardHealth.status].symbol),
1235
- ' 模型:' + (shown || '自动') + (available ? '' : '(无可用模型)')),
1257
+ ' 模型:' + (shown || (capability === 'music-gen' ? '未分配' : '自动')) + (available ? '' : '(无可用模型)')),
1236
1258
  React.createElement('div', { className: 'iris-wb-muted' },
1237
1259
  healthText(cardHealth) + ' · 改分配 → 上方「能力分配(failover 顺序)」'));
1238
1260
  }
@@ -1272,6 +1294,12 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1272
1294
  { action: 'tts', title: '🔊 语音合成', capability: 'tts', fields: [
1273
1295
  { key: 'text', label: '文本', type: 'textarea', placeholder: '要合成的文字' },
1274
1296
  { key: 'voice', label: '音色 (可选)', placeholder: '如 Cherry' }] },
1297
+ { action: 'music', title: '🎵 音乐生成', capability: 'music-gen', fields: [
1298
+ { key: 'prompt', label: '音乐描述', type: 'textarea', placeholder: '描述风格、情绪、乐器;歌词、演唱语言和期望时长也可写在这里' },
1299
+ { key: 'model', label: '音乐模型' },
1300
+ { key: 'timeout_ms', label: '单次等待上限(毫秒,可选)', type: 'number', placeholder: '300000' },
1301
+ { key: 'retry_502', label: '502重新生成次数(可选,会额外占用调用额度)', type: 'number', placeholder: '2(0关闭,最多5)' },
1302
+ { key: 'retry_delay_ms', label: '重试等待(毫秒,可选)', type: 'number', placeholder: '5000' }] },
1275
1303
  { action: 'transcribe', title: '🎙️ 音频转写', capability: 'transcribe', fields: [
1276
1304
  { key: 'audio_path', label: '音频文件', type: 'file', accept: 'audio/*', placeholder: '/path/audio.wav' }] },
1277
1305
  { action: 'video_frames', title: '🎞️ 视频抽帧', fields: [
@@ -1325,6 +1353,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1325
1353
  { cap: 'image-gen', label: '🎨 画图' },
1326
1354
  { cap: 'video-gen', label: '🎬 视频' },
1327
1355
  { cap: 'tts', label: '🔊 语音' },
1356
+ { cap: 'music-gen', label: '🎵 音乐' },
1328
1357
  { cap: 'transcribe', label: '🎙️ 转写' },
1329
1358
  { cap: 'vision', label: '👁 视觉' }
1330
1359
  ];
@@ -1370,7 +1399,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1370
1399
  });
1371
1400
  return React.createElement('div', { key: row.cap, className: 'iris-cap-row' },
1372
1401
  React.createElement('span', { className: 'iris-cap-name' }, row.label),
1373
- chips.length ? chips : React.createElement('span', { className: 'iris-cap-auto' }, '自动(按池顺序)'),
1402
+ chips.length ? chips : React.createElement('span', { className: 'iris-cap-auto' }, row.cap === 'music-gen' ? '尚未分配音乐模型' : '自动(按池顺序)'),
1374
1403
  available.length ? React.createElement('select', {
1375
1404
  className: 'iris-pm-model-sel', value: '',
1376
1405
  onChange: function (e) { if (e.target.value) save(row.cap, list.concat([e.target.value])); }
@@ -1988,7 +2017,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1988
2017
  }
1989
2018
 
1990
2019
  function coreCapabilityLabel(value) {
1991
- return ({ image: '图片生成', video: '视频生成', tts: '语音合成', transcribe: '音频转写' })[value] || String(value || '媒体任务');
2020
+ return ({ image: '图片生成', video: '视频生成', tts: '语音合成', music: '音乐生成', transcribe: '音频转写' })[value] || String(value || '媒体任务');
1992
2021
  }
1993
2022
 
1994
2023
  /* 高级诊断与用户任务区共用五类用户状态投影;与服务端 lib/core-user-projection.js 保持同一映射,
@@ -2522,6 +2551,24 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2522
2551
  note ? React.createElement('div', { className: 'iris-clean-note', style: { width: '100%' } }, note) : null);
2523
2552
  }
2524
2553
 
2554
+ function promptConfigFeedback(state) {
2555
+ var warnings = state && state.feedback && state.feedback.warnings;
2556
+ if (!Array.isArray(warnings) || !warnings.length) return null;
2557
+ return React.createElement('div', { className: 'iris-po-note', role: 'status' }, warnings.map(function (warning, index) {
2558
+ var text = '';
2559
+ if (warning.code === 'config-fallback') {
2560
+ text = warning.reason === 'read-failed' ? '配置读取失败,当前使用 Iris 默认设置。' : '配置内容无效,当前使用 Iris 默认设置。';
2561
+ text += warning.isolation === 'saved' ? '原文件已隔离保留,可在修正后重新导入。'
2562
+ : warning.isolation === 'failed' ? '隔离未完成,Iris 没有覆盖原文件;请检查原文件。' : '原文件未移动;请检查文件读取权限。';
2563
+ } else if (warning.code === 'ignored-fields' && Array.isArray(warning.fields)) {
2564
+ text = '已忽略不支持或当前路由未使用的字段:' + warning.fields.filter(function (field) { return typeof field === 'string'; }).map(function (field) { return JSON.stringify(field); }).join('、');
2565
+ if (warning.count > warning.fields.length) text += '(共 ' + warning.count + ' 个,仅显示前 ' + warning.fields.length + ' 个)';
2566
+ text += '。请检查字段名;这些字段未保存到配置。';
2567
+ }
2568
+ return text ? React.createElement('div', { key: index }, text) : null;
2569
+ }));
2570
+ }
2571
+
2525
2572
  function PromptOptimizerSettings() {
2526
2573
  var pair = React.useState(null);
2527
2574
  var state = pair[0];
@@ -2566,6 +2613,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2566
2613
  React.createElement('span', { style: { flex: 1 } }, enabled ? '对话框入口已启用' : '对话框入口已关闭'),
2567
2614
  React.createElement('button', { className: 'iris-pm-btn', onClick: toggle }, enabled ? '关闭入口' : '重新启用')),
2568
2615
  React.createElement('div', { className: 'iris-wb-muted' }, '这里只控制对话框提示词优化入口;Iris 工作台、Agent 工具和任务后台始终保持运行。'),
2616
+ promptConfigFeedback(state),
2569
2617
  note ? React.createElement('div', { className: 'iris-clean-note' }, note) : null);
2570
2618
  }
2571
2619
 
@@ -2757,6 +2805,18 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2757
2805
  return { removed: before.slice(start, before.length - end).join(''), added: after.slice(start, after.length - end).join('') };
2758
2806
  }
2759
2807
 
2808
+ function promptRequestSnapshot(processing, title) {
2809
+ var request = processing && processing.request;
2810
+ if (!request || typeof request.system !== 'string' || typeof request.prompt !== 'string') return null;
2811
+ return React.createElement('details', { className: 'iris-po-config' },
2812
+ React.createElement('summary', {}, title),
2813
+ React.createElement('div', { className: 'iris-po-muted' }, 'Iris 发送的指令快照;宿主或供应商后续处理可能不同。'),
2814
+ React.createElement('div', { className: 'iris-po-result-label' }, '系统指令 · 含本次规则与输出协议'),
2815
+ React.createElement('div', { className: 'iris-po-template', 'aria-label': title + ' · 系统指令' }, request.system),
2816
+ React.createElement('div', { className: 'iris-po-result-label' }, '用户输入 · 含素材包装'),
2817
+ React.createElement('div', { className: 'iris-po-template', 'aria-label': title + ' · 用户输入' }, request.prompt));
2818
+ }
2819
+
2760
2820
  function PromptOptimizerControl(props) {
2761
2821
  var draft = props.useInput(function (value) { return value.draft; });
2762
2822
  var phase = props.useInput(function (value) { return value.phase; });
@@ -2822,7 +2882,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2822
2882
  var supportsAssembly = !!(configState && configState.capabilities && configState.capabilities.assemble === true);
2823
2883
  var editingCapabilities = configState && configState.capabilities && configState.capabilities.editing;
2824
2884
  var supportsFaithful = Array.isArray(editingCapabilities) && editingCapabilities.indexOf('faithful') >= 0 && editingCapabilities.indexOf('rewrite') >= 0;
2885
+ var supportsLocalFaithful = supportsFaithful && configState.capabilities.localFaithful === true;
2825
2886
  var supportsSuggestions = Array.isArray(editingCapabilities) && editingCapabilities.indexOf('suggestions') >= 0;
2887
+ var supportsResetInstructions = !!(configState && configState.capabilities && configState.capabilities.resetInstructions === true);
2826
2888
  function supportsTarget(value) {
2827
2889
  var available = configState && configState.capabilities && configState.capabilities.targets;
2828
2890
  return Array.isArray(available) ? available.indexOf(value) >= 0 : value !== 'image-edit';
@@ -2922,12 +2984,12 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2922
2984
  if (abortRef.current) abortRef.current.abort();
2923
2985
  var controller = new AbortController();
2924
2986
  var requestSessionId = props.sessionId;
2987
+ var previousResult = result;
2988
+ var previewSnapshot = edited;
2925
2989
  abortRef.current = controller;
2926
2990
  setBusy(true);
2927
- setNote(mode === 'assemble' ? '正在组装,不调用模型…' : '正在调用模型优化…这可能产生模型费用');
2928
- setResult(null);
2929
- setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
2930
- lastWrittenRef.current = null;
2991
+ setNote(mode === 'assemble' ? '正在组装,不调用模型…' : supportsLocalFaithful && editing === 'faithful'
2992
+ ? '正在忠实整理…需要分段时可能调用模型并产生费用' : '正在调用模型优化…这可能产生模型费用');
2931
2993
  promptRequest('optimize', {
2932
2994
  text: draft,
2933
2995
  target: target,
@@ -2943,14 +3005,20 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2943
3005
  if (data.original !== draft) throw new Error('结果原稿与本次请求不一致');
2944
3006
  if (mode === 'optimize' && supportsFaithful && editing === 'faithful'
2945
3007
  && (data.editing?.strategy !== 'faithful' || data.editing.coverage !== 'exact' || data.editing.order !== 'preserved')) throw new Error('后端未确认完整的忠实整理结果');
3008
+ if (resultRef.current !== previousResult || editedRef.current !== previewSnapshot) {
3009
+ setNote('预览已变化,本次结果未替换;你的编辑已保留。'); return;
3010
+ }
2946
3011
  setResult(data);
2947
3012
  setEdited(data.optimized);
3013
+ setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
3014
+ lastWrittenRef.current = null;
2948
3015
  resultSessionRef.current = requestSessionId;
2949
- setNote('已生成预览,尚未写回输入框');
3016
+ setNote(data.execution?.reason === 'single-part-faithful' && data.execution.modelInvocations === 0
3017
+ ? '原文无需分段,未调用模型;尚未写回输入框' : '已生成预览,尚未写回输入框');
2950
3018
  }).catch(function (err) {
2951
3019
  if (abortRef.current !== controller) return;
2952
3020
  if (controller.signal.aborted) setNote('已取消');
2953
- else setNote(String(err && err.message || err));
3021
+ else setNote(String(err && err.message || err) + (previousResult ? ';原有预览已保留' : ''));
2954
3022
  }).finally(function () {
2955
3023
  if (abortRef.current === controller) { abortRef.current = null; setBusy(false); }
2956
3024
  });
@@ -2980,7 +3048,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2980
3048
  || typeof item.text !== 'string' || !item.text.trim() || item.text.length > 600) return false;
2981
3049
  ids.push(item.id); return true;
2982
3050
  }) || !data.questions.every(function (text) { return typeof text === 'string' && text.trim() && text.length <= 600; })) throw new Error('未收到完整补充建议');
2983
- setSuggestions({ source: snapshot, items: data.suggestions, questions: data.questions, adopted: [] });
3051
+ setSuggestions({ source: snapshot, items: data.suggestions, questions: data.questions, adopted: [], processing: data.processing });
2984
3052
  setSelectedSuggestions([]); setSuggestionUndo([]);
2985
3053
  setNote('建议尚未加入正文;只有采纳所选建议才会改变预览。');
2986
3054
  }).catch(function (err) {
@@ -3060,7 +3128,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
3060
3128
  event.target.value = '';
3061
3129
  if (!file) return;
3062
3130
  file.text().then(function (raw) {
3063
- var parsed = JSON.parse(raw);
3131
+ var parsed;
3132
+ try { parsed = JSON.parse(raw); } catch (_) { throw new Error('文件不是有效 JSON,请检查格式后重新导入'); }
3064
3133
  return promptRequest('import', { config: parsed });
3065
3134
  }).then(function (data) {
3066
3135
  publishConfig(data);
@@ -3086,11 +3155,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
3086
3155
  }
3087
3156
  }
3088
3157
 
3089
- function resetConfig() {
3090
- if (!window.confirm('恢复 Iris 内置默认 Prompt、目标模板和会话模型路由?')) return;
3091
- promptRequest('reset', {}).then(function (data) {
3158
+ function resetConfig(scope) {
3159
+ if (scope === 'instructions' && !supportsResetInstructions) { setNote('当前后端不支持仅恢复指令,请更新后重新载入'); return; }
3160
+ var question = scope === 'instructions' ? '仅恢复 Iris 内置系统指令和全部用途模板?模型路由、生成参数和入口开关保持不变。'
3161
+ : '恢复全部 Iris 提示词设置?系统指令、全部用途模板、模型路由、生成参数与预算、入口开关都会重置;模型路由恢复为当前会话,入口重新启用。';
3162
+ if (!window.confirm(question)) return;
3163
+ promptRequest('reset', { scope: scope }).then(function (data) {
3092
3164
  publishConfig(data);
3093
- setNote('已恢复 Iris 内置默认配置');
3165
+ setNote(scope === 'instructions' ? '已恢复默认指令;模型路由、生成参数与入口开关保持不变' : '已恢复全部 Iris 提示词设置');
3094
3166
  }).catch(function (err) {
3095
3167
  setNote('重置失败:' + String(err && err.message || err));
3096
3168
  });
@@ -3120,6 +3192,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
3120
3192
  var resultTarget = result && PROMPT_TARGET_OPTIONS.find(function (item) { return item.value === result.target; });
3121
3193
  var config = configState && configState.config;
3122
3194
  var targetTemplate = config && config.targets && config.targets[target];
3195
+ var requestConfigFeedback = result && promptConfigFeedback({ feedback: result.configFeedback });
3123
3196
  function ruleField(label, value, update, placeholder) {
3124
3197
  return React.createElement('label', { className: 'iris-po-field' }, React.createElement('span', {}, label),
3125
3198
  React.createElement('textarea', { value: value, disabled: busy || !supportsRules, placeholder: placeholder,
@@ -3141,7 +3214,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
3141
3214
  React.createElement('strong', { className: 'iris-po-title' }, React.createElement('span', { className: 'iris-po-mark' }, '🫧'), 'Iris 提示词优化'),
3142
3215
  React.createElement('button', { className: 'iris-po-close', onClick: closePanel, title: '关闭', 'aria-label': '关闭提示词优化' }, '✕')),
3143
3216
  React.createElement('div', { className: 'iris-po-intro' },
3144
- React.createElement('div', { className: 'iris-po-muted' }, '按用途整理草稿,先预览和编辑,再决定是否写回。'),
3217
+ React.createElement('div', { className: 'iris-po-muted' }, '按用途整理草稿,先预览和编辑,再决定是否写回。'),
3218
+ promptConfigFeedback(configState),
3145
3219
  React.createElement('div', { className: 'iris-po-principles' },
3146
3220
  React.createElement('span', {}, '保留原意'), React.createElement('span', {}, '可查看改动'), React.createElement('span', {}, '确认后写回'))),
3147
3221
  React.createElement('details', { className: 'iris-po-section', open: generalOpen,
@@ -3177,7 +3251,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
3177
3251
  React.createElement('button', { className: 'iris-po-primary', disabled: busy || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('optimize'); } }, busy ? '处理中…' : supportsFaithful ? editing === 'faithful' ? '忠实整理' : '改写表达' : '智能优化'),
3178
3252
  React.createElement('button', { className: 'iris-po-btn', disabled: busy || !supportsAssembly || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('assemble'); } }, '只组装(不调用模型)'),
3179
3253
  busy ? React.createElement('button', { className: 'iris-po-btn', onClick: cancel }, '取消') : null),
3180
- React.createElement('div', { className: 'iris-po-muted' }, '智能优化调用文本模型,可能产生费用;只组装原稿与前后缀。'),
3254
+ React.createElement('div', { className: 'iris-po-muted' }, supportsLocalFaithful
3255
+ ? '单片段忠实整理与只组装不调用模型;其他优化及获取建议可能产生模型费用。'
3256
+ : '智能优化调用文本模型,可能产生费用;只组装原稿与前后缀。'),
3181
3257
  result ? React.createElement('div', { className: 'iris-po-result' },
3182
3258
  React.createElement('div', { className: 'iris-po-result-label' }, '结果预览 · ' + (resultTarget ? resultTarget.label + ' · ' : '') + '可编辑 · ' + edited.length + ' 字符'),
3183
3259
  React.createElement('textarea', { 'aria-label': '结果预览', value: edited, onChange: function (event) { editPreview(event.target.value); } }),
@@ -3197,6 +3273,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
3197
3273
  !suggestions.items.length ? React.createElement('div', { className: 'iris-po-muted' }, '没有额外补充建议。') : null,
3198
3274
  suggestions.questions.length ? React.createElement('div', { className: 'iris-po-suggestion' }, React.createElement('strong', {}, '待确认 · 尚未作为要求'),
3199
3275
  suggestions.questions.map(function (question, index) { return React.createElement('p', { key: index }, question); })) : null,
3276
+ promptRequestSnapshot(suggestions.processing, '本次建议完整指令'),
3200
3277
  React.createElement('div', { className: 'iris-po-actions' },
3201
3278
  React.createElement('button', { className: 'iris-po-btn', disabled: busy || !selectedSuggestions.length, onClick: adoptSuggestions }, '采纳所选建议'),
3202
3279
  React.createElement('button', { className: 'iris-po-btn', disabled: busy || !suggestionUndo.length, onClick: undoSuggestions }, '撤回上次采纳'))) : null) : null,
@@ -3212,7 +3289,13 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
3212
3289
  return React.createElement('div', { className: 'iris-po-diff', key: rule.id }, rule.label + '(' + (rule.kind === 'optimization' ? '指导改写' : rule.position === 'prefix' ? '输出前缀' : '输出后缀') + ')\n' + rule.text);
3213
3290
  }) : React.createElement('div', { className: 'iris-po-muted' }, '未选择额外规则'),
3214
3291
  result.processing ? React.createElement('div', { className: 'iris-po-diff' }, '优化处理规则:\n' + result.processing.systemPrompt + '\n\n目标模板:\n' + result.processing.targetTemplate) : null),
3215
- React.createElement('div', { className: 'iris-po-route' }, result.route ? '实际使用:' + result.route.provider + ' / ' + result.route.model + ' · 思考:' + (result.route.reasoningEffort === 'provider-default' ? '供应商默认' : result.route.reasoningEffort) : '本次只组装,未调用模型'),
3292
+ promptRequestSnapshot(result.processing, '本次正文处理完整指令'),
3293
+ React.createElement('div', { className: 'iris-po-route' }, result.route ? '实际使用:' + result.route.provider + ' / ' + result.route.model + ' · 思考:' + (result.route.reasoningEffort === 'provider-default' ? '供应商默认' : result.route.reasoningEffort)
3294
+ : result.execution?.reason === 'single-part-faithful' && result.execution.modelInvocations === 0 ? '忠实整理:无需分段,未调用模型'
3295
+ : result.mode === 'assemble' ? '本次只组装,未调用模型' : '未提供模型调用信息'),
3296
+ result.route && result.route.reason ? React.createElement('div', { className: 'iris-po-muted' }, '选择原因:' + result.route.reason + ' · ' + (result.route.textInput === 'supported' ? '宿主声明支持文本输入' : '文本输入能力未知')) : null,
3297
+ requestConfigFeedback ? React.createElement('details', { className: 'iris-po-config' },
3298
+ React.createElement('summary', {}, '本次请求的配置反馈'), requestConfigFeedback) : null,
3216
3299
  React.createElement('div', { className: 'iris-po-actions' },
3217
3300
  React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(edited); } }, '写回输入框'),
3218
3301
  React.createElement('button', { className: 'iris-po-btn', onClick: copyResult }, '复制'),
@@ -3226,11 +3309,13 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
3226
3309
  React.createElement('div', { className: 'iris-po-template' }, config && config.systemPrompt || '配置尚未载入'),
3227
3310
  React.createElement('div', { className: 'iris-po-template' }, targetTemplate || '当前后端未提供此目标模板')),
3228
3311
  React.createElement('div', { className: 'iris-po-result-label' }, 'JSON 配置'),
3229
- React.createElement('div', { className: 'iris-po-muted' }, '可导出后修改 systemPrompt、targets、route 和 generation,再重新导入。'),
3312
+ React.createElement('div', { className: 'iris-po-muted' }, '导入会替换当前配置;局部 JSON 缺少的字段使用内置默认值。可导出后修改 systemPrompt、targets、route 和 generation,再重新导入。'),
3230
3313
  React.createElement('div', { className: 'iris-po-actions' },
3231
3314
  React.createElement('button', { className: 'iris-po-btn', onClick: exportConfig }, '导出'),
3232
- React.createElement('button', { className: 'iris-po-btn', onClick: function () { if (fileRef.current) fileRef.current.click(); } }, '导入'),
3233
- React.createElement('button', { className: 'iris-po-btn danger', onClick: resetConfig }, '恢复默认'),
3315
+ React.createElement('button', { className: 'iris-po-btn', disabled: busy, onClick: function () { if (fileRef.current) fileRef.current.click(); } }, '导入'),
3316
+ React.createElement('button', { className: 'iris-po-btn', disabled: busy || !supportsResetInstructions,
3317
+ title: supportsResetInstructions ? '只更新系统指令与全部用途模板' : '当前后端尚未支持仅恢复指令', onClick: function () { resetConfig('instructions'); } }, '仅恢复默认指令'),
3318
+ React.createElement('button', { className: 'iris-po-btn danger', disabled: busy, onClick: function () { resetConfig('all'); } }, '恢复全部设置'),
3234
3319
  React.createElement('input', { ref: fileRef, className: 'iris-po-file', type: 'file', accept: 'application/json,.json', onChange: importConfig })),
3235
3320
  React.createElement('div', { className: 'iris-po-muted' }, '当前:' + (configState && configState.source === 'custom' ? '用户 JSON' : 'Iris 默认'))),
3236
3321
  React.createElement('button', { className: 'iris-po-disable', disabled: busy, onClick: disableEntry }, '关闭此对话入口(工作台仍保留)')));
@@ -20,6 +20,7 @@ import { GenerationInputError, normalizeGenerationInput } from './generation-inp
20
20
  import { videoTaskCandidates } from './video-input.js';
21
21
  import { waitForCoreTask } from './task-wait.js';
22
22
  import { planCoreDeletion, planCoreCleanup, quarantineCoreFiles, inspectCoreTransactions, restoreCoreTransaction } from './core-maintenance.js';
23
+ import { deliveryCacheNeedsProvider } from './core-delivery-cache.js';
23
24
 
24
25
  export const COMMAND_CONTRACT_VERSION = 0;
25
26
  export const CORE_COMMANDS = Object.freeze([
@@ -366,16 +367,20 @@ export function createCommandService(runtime, ports = {}) {
366
367
  });
367
368
  }
368
369
  // task.redeliver:只对 outcome=succeeded / deliveryState=failed 的任务重新取回
369
- // 已生成的远端产物(re-poll 拿产物清单,绝不 submit,也不会重新生成)。
370
+ // 已生成产物:同步结果读私有缓存,异步结果 re-poll;绝不 submit。
370
371
  if (name === 'task.redeliver') {
371
372
  exactInput(input, ['task_id']);
372
373
  return runtime.run('execute', async ({ dataRoot }) => {
373
374
  const before = inspectCoreTask(dataRoot, input.task_id);
374
- if (before.outcome !== 'succeeded' || before.deliveryState !== 'failed' || !before.remoteTaskId) throw commandError('IRIS_TASK_NOT_REDELIVERABLE', 'Task 不处于可重新交付状态;不会重新生成');
375
+ if (before.outcome !== 'succeeded' || before.deliveryState !== 'failed' || (!before.remoteTaskId && !before.deliveryCache)) throw commandError('IRIS_TASK_NOT_REDELIVERABLE', 'Task 不处于可重新交付状态;不会重新生成');
376
+ if (before.deliveryCache && !deliveryCacheNeedsProvider(dataRoot, before)) {
377
+ const task = await createProviderTaskRunner(runtime).redeliver(input.task_id);
378
+ return Object.freeze({ contractVersion: COMMAND_CONTRACT_VERSION, command: name, taskId: task.id, task });
379
+ }
375
380
  if (typeof ports.resolveTaskAdapter !== 'function') throw commandError('IRIS_COMMAND_PROVIDER_REQUIRED', 'task.redeliver 需要宿主显式提供 Task Provider resolver');
376
381
  const adapter = await ports.resolveTaskAdapter(before);
377
382
  if (!adapter || adapter.id !== before.providerId || !adapter.capabilities?.includes(before.capability)) throw commandError('IRIS_PROVIDER_TASK_IDENTITY_MISMATCH', 'Task Provider resolver 返回了不匹配的 Adapter');
378
- if (!hasProviderOperation(adapter, 'poll')) throw commandError('IRIS_PROVIDER_TASK_OBSERVE_UNSUPPORTED', 'Task 原 Provider 不支持 poll;不会重新生成');
383
+ if (!before.deliveryCache && !hasProviderOperation(adapter, 'poll')) throw commandError('IRIS_PROVIDER_TASK_OBSERVE_UNSUPPORTED', 'Task 原 Provider 不支持 poll;不会重新生成');
379
384
  const task = await createProviderTaskRunner(runtime).redeliver(input.task_id, adapter);
380
385
  return Object.freeze({ contractVersion: COMMAND_CONTRACT_VERSION, command: name, taskId: task.id, task });
381
386
  });
@@ -415,6 +420,10 @@ export function createCommandService(runtime, ports = {}) {
415
420
  throw commandError('IRIS_TASK_NOT_RETRYABLE', '只有终态且未成功交付的 Task 可以重试为新任务');
416
421
  }
417
422
  let normalized;
423
+ if (before.capability === 'music' && input.provider_input &&
424
+ ['retry_502', 'retry_delay_ms'].some(field => Object.hasOwn(input.provider_input, field))) {
425
+ throw commandError('IRIS_COMMAND_INPUT_INVALID', 'task.retry 只执行一次重新生成;502重试配置请使用Music生成入口');
426
+ }
418
427
  try { normalized = normalizeGenerationInput(before.capability, input.provider_input, { allowVideoPaths: before.capability === 'video' }); }
419
428
  catch (error) {
420
429
  if (error instanceof GenerationInputError) throw commandError('IRIS_COMMAND_INPUT_INVALID', error.message);