@mokuyoaxis/dsh-iris 0.1.1 → 0.1.2

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 (32) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.en.md +245 -0
  3. package/README.md +55 -6
  4. package/cordis.patch.yml +1 -1
  5. package/docs/ROADMAP.md +22 -5
  6. package/docs/assets/diagrams/iris-architecture.drawio +1 -0
  7. package/docs/assets/diagrams/iris-architecture.png +0 -0
  8. package/docs/assets/diagrams/iris-architecture.svg +63 -0
  9. package/docs/assets/diagrams/iris-task-lifecycle.drawio +1 -0
  10. package/docs/assets/diagrams/iris-task-lifecycle.png +0 -0
  11. package/docs/assets/diagrams/iris-task-lifecycle.svg +64 -0
  12. package/docs/assets/diagrams/iris-workflow-compose.drawio +1 -0
  13. package/docs/assets/diagrams/iris-workflow-compose.png +0 -0
  14. package/docs/assets/diagrams/iris-workflow-compose.svg +41 -0
  15. package/docs/assets/examples/iris-flower-generated.webp +0 -0
  16. package/docs/assets/screenshots/image-task-succeeded.webp +0 -0
  17. package/docs/assets/screenshots/prompt-general-after.webp +0 -0
  18. package/docs/assets/screenshots/prompt-general-before.webp +0 -0
  19. package/docs/assets/screenshots/prompt-general-preview.webp +0 -0
  20. package/docs/assets/screenshots/prompt-image-before.webp +0 -0
  21. package/docs/assets/screenshots/prompt-image-preview.webp +0 -0
  22. package/docs/releases/0.1.2.md +26 -0
  23. package/docs/screenshots.md +34 -0
  24. package/lib/api.js +42 -11
  25. package/lib/bundled-skills.js +62 -0
  26. package/lib/client.js +352 -9
  27. package/lib/index.js +11 -1
  28. package/lib/prompt-optimizer-config.js +170 -0
  29. package/lib/prompt-optimizer.js +244 -0
  30. package/package.json +7 -3
  31. package/screenshots.json +6 -0
  32. package/user_guide.md +64 -3
package/lib/client.js CHANGED
@@ -111,7 +111,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
111
111
  '.iris-bubble.lit .iris-bubble-btn { border-color: var(--dsw-alias-state-success-primary, #6fcf6f); box-shadow: 0 0 14px var(--dsw-alias-state-success-primary, #6fcf6f); }',
112
112
  '.iris-bubble.dim .iris-bubble-btn { opacity: 0.55; border-color: var(--dsw-alias-border-l1, #3a3a44); }',
113
113
  '.iris-bubble-badge { position: absolute; top: -2px; right: -2px; min-width: 16px; height: 16px; border-radius: 8px; background: var(--dsw-alias-state-warn-primary, #d9a941); color: #1a1a1e; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; padding: 0 3px; }',
114
- '.iris-bubble-panel { position: fixed; z-index: 9998; width: 360px; max-height: min(520px, 70vh); overflow: auto; background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 12px; padding: 12px 14px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45); }',
114
+ '.iris-bubble-panel { position: fixed; z-index: 9998; width: 360px; max-height: min(520px, 70vh); overflow: auto; background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 12px; padding: 12px 14px; font-size: 12px; line-height: 1.45; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45); }',
115
115
  '.iris-bubble-panel-head { position: absolute; top: 6px; right: 6px; z-index: 2; }',
116
116
  '.iris-bubble-close { border: none; background: transparent; color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 14px; cursor: pointer; padding: 4px 6px; }',
117
117
  '.iris-bubble-close:hover { color: var(--dsw-alias-label-primary, #e6e6e6); }',
@@ -160,7 +160,48 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
160
160
  '.iris-ff-list { display: grid; gap: 2px; max-height: 160px; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 6px; padding: 4px; background: var(--dsw-alias-bg-layer-2, #32323c); }',
161
161
  '.iris-ff-item { display: flex; align-items: center; gap: 6px; font-size: 11px; cursor: pointer; padding: 2px 4px; border-radius: 4px; }',
162
162
  '.iris-ff-item:hover { background: var(--dsw-alias-bg-layer-1, #26262e); }',
163
- '.iris-ff-item .iris-ff-src { color: var(--dsw-alias-brand-primary, #7aa2f7); font-size: 10px; }'
163
+ '.iris-ff-item .iris-ff-src { color: var(--dsw-alias-brand-primary, #7aa2f7); font-size: 10px; }',
164
+ /* 对话框级提示词优化器:桌面浮层 + 窄屏底部 Sheet */
165
+ '.iris-po-wrap { position: relative; display: inline-flex; flex: 0 0 auto; }',
166
+ '.iris-po-trigger { appearance: none; -webkit-appearance: none; width: 30px; height: 30px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border: 0; outline: 0; background: transparent; color: inherit; font-size: 19px; line-height: 1; cursor: pointer; box-shadow: none; filter: drop-shadow(0 3px 7px rgba(91,113,220,.22)); transition: transform .18s ease, filter .18s ease, opacity .18s ease; }',
167
+ '.iris-po-trigger:hover { transform: translateY(-2px) scale(1.10); filter: drop-shadow(0 5px 10px rgba(112,137,255,.42)); }',
168
+ '.iris-po-trigger:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #7aa2f7); outline-offset: 2px; }',
169
+ '.iris-po-backdrop { position: fixed; inset: 0; z-index: 10019; background: rgba(8,10,18,.30); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); touch-action: none; animation: iris-po-fade .16s ease-out; }',
170
+ '.iris-po-panel { position: fixed; z-index: 10020; right: max(14px, env(safe-area-inset-right)); bottom: max(78px, calc(env(safe-area-inset-bottom) + 68px)); width: min(440px, calc(100vw - 28px)); max-height: min(680px, calc(100dvh - 104px)); overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y; box-sizing: border-box; padding: 18px; display: grid; gap: 13px; background-color: rgba(35,36,47,.84); background: radial-gradient(circle at 10% -5%, rgba(132,160,255,.24), transparent 42%), color-mix(in srgb, var(--dsw-alias-bg-layer-1, #26262e) 82%, transparent); color: var(--dsw-alias-label-primary, #eeeeF2); border: 1px solid rgba(150,164,215,.25); border-radius: 20px; box-shadow: 0 24px 70px rgba(0,0,0,.48), inset 0 1px 0 rgba(255,255,255,.07); backdrop-filter: blur(28px) saturate(1.28); -webkit-backdrop-filter: blur(28px) saturate(1.28); animation: iris-po-rise .20s cubic-bezier(.2,.8,.2,1); scrollbar-width: thin; }',
171
+ '.iris-po-head { position: sticky; top: -18px; z-index: 1; margin: -18px -18px 0; padding: 16px 18px 10px; display: flex; align-items: center; justify-content: space-between; gap: 10px; background: linear-gradient(to bottom, color-mix(in srgb, var(--dsw-alias-bg-layer-1, #26262e) 88%, transparent) 72%, transparent); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }',
172
+ '.iris-po-title { display: inline-flex; align-items: center; gap: 9px; font-size: 14px; letter-spacing: .01em; }',
173
+ '.iris-po-mark, .iris-po-settings-mark { display: inline-flex; align-items: center; justify-content: center; width: 29px; height: 29px; border-radius: 999px; background: rgba(255,255,255,.055); box-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 5px 16px rgba(67,83,168,.16); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); filter: drop-shadow(0 2px 5px rgba(105,128,235,.16)); }',
174
+ '.iris-po-close { width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 999px; background: rgba(255,255,255,.05); color: var(--dsw-alias-label-secondary, #a6a6b2); font-size: 14px; cursor: pointer; transition: background .15s, color .15s; }',
175
+ '.iris-po-close:hover { background: rgba(255,255,255,.10); color: var(--dsw-alias-label-primary, #eeeef2); }',
176
+ '.iris-po-muted, .iris-po-route, .iris-po-note { font-size: 11px; line-height: 1.55; color: var(--dsw-alias-label-secondary, #a6a6b2); word-break: break-word; }',
177
+ '.iris-po-route { padding: 7px 10px; border-radius: 10px; background: rgba(255,255,255,.045); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
178
+ '.iris-po-note { padding: 8px 10px; border-radius: 10px; color: var(--dsw-alias-state-warn-primary, #dfbd69); background: rgba(217,169,65,.08); }',
179
+ '.iris-po-field { display: grid; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary, #a6a6b2); }',
180
+ '.iris-po-field select, .iris-po-result textarea { width: 100%; box-sizing: border-box; background: rgba(255,255,255,.055); color: var(--dsw-alias-label-primary, #eeeef2); border: 1px solid rgba(150,164,215,.20); border-radius: 11px; padding: 9px 11px; font: inherit; font-size: 12px; outline: none; transition: border-color .15s, background .15s, box-shadow .15s; }',
181
+ '.iris-po-field select:focus, .iris-po-result textarea:focus { border-color: rgba(122,162,247,.72); background: rgba(255,255,255,.075); box-shadow: 0 0 0 3px rgba(122,162,247,.11); }',
182
+ '.iris-po-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }',
183
+ '.iris-po-btn, .iris-po-primary { min-height: 34px; border: 1px solid rgba(150,164,215,.20); border-radius: 10px; padding: 6px 11px; font: inherit; font-size: 11px; cursor: pointer; transition: transform .15s, background .15s, border-color .15s; }',
184
+ '.iris-po-btn { background: rgba(255,255,255,.045); color: var(--dsw-alias-label-secondary, #b0b0bb); }',
185
+ '.iris-po-btn:hover { background: rgba(255,255,255,.085); color: var(--dsw-alias-label-primary, #eeeef2); border-color: rgba(150,174,255,.50); }',
186
+ '.iris-po-primary { background: linear-gradient(135deg, #779cf3, #8c79de); color: #fff; border-color: rgba(174,187,255,.55); font-weight: 650; box-shadow: 0 6px 18px rgba(76,92,188,.22); }',
187
+ '.iris-po-primary:hover { transform: translateY(-1px); box-shadow: 0 8px 22px rgba(76,92,188,.32); }',
188
+ '.iris-po-primary:disabled, .iris-po-btn:disabled { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }',
189
+ '.iris-po-result { display: grid; gap: 8px; padding: 12px; border: 1px solid rgba(137,155,218,.18); border-radius: 15px; background: rgba(10,12,20,.16); }',
190
+ '.iris-po-result-label { font-size: 11px; font-weight: 650; letter-spacing: .02em; }',
191
+ '.iris-po-result textarea { min-height: 156px; max-height: 34dvh; resize: vertical; line-height: 1.58; }',
192
+ '.iris-po-config { padding: 11px 12px; border: 1px solid rgba(150,164,215,.14); border-radius: 13px; background: rgba(255,255,255,.025); font-size: 11px; }',
193
+ '.iris-po-config summary { cursor: pointer; color: var(--dsw-alias-label-secondary, #a6a6b2); }',
194
+ '.iris-po-config[open] summary { margin-bottom: 9px; color: var(--dsw-alias-label-primary, #eeeef2); }',
195
+ '.iris-po-config .iris-po-actions { margin: 9px 0; }',
196
+ '.iris-po-btn.danger:hover { color: var(--dsw-alias-state-error-primary, #eb7d86); border-color: rgba(235,125,134,.55); background: rgba(235,125,134,.08); }',
197
+ '.iris-po-file { display: none; }',
198
+ '.iris-po-disable { justify-self: start; border: 0; padding: 2px 0; background: transparent; color: var(--dsw-alias-label-tertiary, #777986); font: inherit; font-size: 10px; cursor: pointer; }',
199
+ '.iris-po-disable:hover { color: var(--dsw-alias-state-error-primary, #eb7d86); }',
200
+ '@keyframes iris-po-fade { from { opacity: 0; } to { opacity: 1; } }',
201
+ '@keyframes iris-po-rise { from { opacity: 0; transform: translateY(12px) scale(.985); } to { opacity: 1; transform: translateY(0) scale(1); } }',
202
+ '@media (max-width: 640px) { .iris-po-backdrop { background: rgba(6,8,14,.48); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); } .iris-po-panel { left: max(8px, env(safe-area-inset-left)); right: max(8px, env(safe-area-inset-right)); bottom: max(8px, env(safe-area-inset-bottom)); width: auto; max-height: calc(100dvh - max(16px, env(safe-area-inset-top))); padding: 16px; gap: 11px; border-radius: 22px; } .iris-po-head { top: -16px; margin: -16px -16px 0; padding: 14px 16px 10px; } .iris-po-btn, .iris-po-primary { min-height: 42px; flex: 1 1 auto; } .iris-po-actions .iris-po-primary:first-child { flex-basis: 100%; } .iris-po-result textarea { min-height: 122px; max-height: 28dvh; resize: none; } .iris-po-route { white-space: normal; } }',
203
+ '@media (max-width: 360px) { .iris-po-panel { left: 4px; right: 4px; bottom: max(4px, env(safe-area-inset-bottom)); padding: 13px; border-radius: 18px; } .iris-po-head { top: -13px; margin: -13px -13px 0; padding: 12px 13px 8px; } .iris-po-actions { gap: 6px; } }',
204
+ '@media (prefers-reduced-motion: reduce) { .iris-po-panel, .iris-po-backdrop { animation: none; } .iris-po-trigger, .iris-po-btn, .iris-po-primary { transition: none; } }',
164
205
  ].join('\n');
165
206
 
166
207
  function adoptStyles() {
@@ -250,6 +291,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
250
291
  return sharedState;
251
292
  }
252
293
 
294
+ /** 功能卡就绪态直接来自共享 Iris 状态,不再等用户点开卡片后才加载。 */
295
+ function capabilityReady(state, capability) {
296
+ if (!capability) return true;
297
+ var providers = (state && state.providers) || [];
298
+ return providers.some(function (p) {
299
+ return p.enabled && p.apiKeyHint && Array.isArray(p.capabilities) && p.capabilities.indexOf(capability) >= 0;
300
+ });
301
+ }
302
+
253
303
  /* ---- 任务详情抽屉:按需拉取 /iris/api/task/:id ---- */
254
304
  function useTaskDetail(taskId) {
255
305
  var pair = React.useState(null);
@@ -650,6 +700,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
650
700
  var modelPair = React.useState(null); // { assigned, options:[{id,providerId}], tested }
651
701
  var modelInfo = modelPair[0];
652
702
  var setModelInfo = modelPair[1];
703
+ var irisState = useIrisState(5000);
653
704
  function setVal(key, value) { var nv = {}; Object.keys(vals).forEach(function (k) { nv[k] = vals[k]; }); nv[key] = value; setVals(nv); }
654
705
  function loadModels() {
655
706
  if (!capability) return;
@@ -729,11 +780,13 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
729
780
  run === 'running' ? '执行中…' : '执行'),
730
781
  result);
731
782
  }
732
- var headLit = !capability || (modelInfo && modelInfo.loaded && modelInfo.options.length > 0);
783
+ var headError = !!(run && run !== 'running' && run.ok === false);
784
+ var headLit = !capability || (capabilityReady(irisState, capability) && !headError);
785
+ var headTitle = headError ? '最近一次执行出错' : (headLit ? 'API 与能力已配置' : '缺少可用 API 或能力模型');
733
786
  return React.createElement('div', { className: 'iris-act-card' + (open ? ' open' : '') + (headLit ? ' lit' : ' dim') },
734
787
  React.createElement('div', { className: 'iris-act-head', onClick: function () { setOpen(!open); } },
735
788
  React.createElement('span', { className: 'iris-act-title' }, title),
736
- capability ? React.createElement('span', { className: headLit ? 'iris-pm-dot lit' : 'iris-pm-dot', title: headLit ? '模型可用' : '无可用模型' }, '●') : null,
789
+ capability ? React.createElement('span', { className: headLit ? 'iris-pm-dot lit' : 'iris-pm-dot', title: headTitle }, '●') : null,
737
790
  React.createElement('span', { className: 'iris-act-arrow' }, '▶')),
738
791
  body);
739
792
  }
@@ -957,6 +1010,53 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
957
1010
  note ? React.createElement('div', { className: 'iris-clean-note', style: { width: '100%' } }, note) : null);
958
1011
  }
959
1012
 
1013
+ function PromptOptimizerSettings() {
1014
+ var pair = React.useState(null);
1015
+ var state = pair[0];
1016
+ var setState = pair[1];
1017
+ var notePair = React.useState('');
1018
+ var note = notePair[0];
1019
+ var setNote = notePair[1];
1020
+
1021
+ function load() {
1022
+ fetch('/iris/api/prompt-optimizer/config')
1023
+ .then(function (res) { return res.ok ? res.json() : null; })
1024
+ .then(function (data) { if (data && data.config) setState(data); })
1025
+ .catch(function () { setNote('读取提示词优化配置失败'); });
1026
+ }
1027
+ React.useEffect(function () {
1028
+ function changed(event) { if (event.detail && event.detail.config) setState(event.detail); }
1029
+ window.addEventListener('iris-prompt-config-changed', changed);
1030
+ load();
1031
+ return function () { window.removeEventListener('iris-prompt-config-changed', changed); };
1032
+ }, []);
1033
+
1034
+ function toggle() {
1035
+ var enabled = !(state && state.config && state.config.enabled !== false);
1036
+ fetch('/iris/api/prompt-optimizer/enabled', {
1037
+ method: 'POST',
1038
+ headers: { 'Content-Type': 'application/json' },
1039
+ body: JSON.stringify({ enabled: enabled })
1040
+ }).then(function (res) {
1041
+ return res.json().then(function (data) {
1042
+ if (!res.ok) throw new Error(data.error || '更新失败');
1043
+ setState(data);
1044
+ window.dispatchEvent(new CustomEvent('iris-prompt-config-changed', { detail: data }));
1045
+ setNote(enabled ? '对话框 🫧 入口已启用' : '对话框入口已关闭;Iris 工作台与后台能力不受影响');
1046
+ });
1047
+ }).catch(function (err) { setNote(String(err && err.message || err)); });
1048
+ }
1049
+
1050
+ var enabled = !state || !state.config || state.config.enabled !== false;
1051
+ return React.createElement('div', { className: 'iris-wb-card' },
1052
+ React.createElement('div', { className: 'iris-wb-row' },
1053
+ React.createElement('span', { className: 'iris-po-settings-mark' }, '🫧'),
1054
+ React.createElement('span', { style: { flex: 1 } }, enabled ? '对话框入口已启用' : '对话框入口已关闭'),
1055
+ React.createElement('button', { className: 'iris-pm-btn', onClick: toggle }, enabled ? '关闭入口' : '重新启用')),
1056
+ React.createElement('div', { className: 'iris-wb-muted' }, '这里只控制对话框提示词优化入口;Iris 工作台、Agent 工具和任务后台始终保持运行。'),
1057
+ note ? React.createElement('div', { className: 'iris-clean-note' }, note) : null);
1058
+ }
1059
+
960
1060
  function WorkbenchPanel() {
961
1061
  var state = useIrisState(5000);
962
1062
  var running = (state && state.tasks && state.tasks.running) || [];
@@ -975,6 +1075,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
975
1075
  React.createElement(CapabilityAssigner, {}),
976
1076
  React.createElement('div', { className: 'iris-wb-sec' }, 'Iris 泡泡快捷卡片(勾选后显示在「⚡ 常用」标签)'),
977
1077
  React.createElement(BubbleCardPicker, {}),
1078
+ React.createElement('div', { className: 'iris-wb-sec' }, '对话框提示词优化'),
1079
+ React.createElement(PromptOptimizerSettings, {}),
978
1080
  React.createElement('div', { className: 'iris-wb-sec' }, '运行中任务'),
979
1081
  React.createElement('div', { className: 'iris-wb-box' },
980
1082
  running.length ? running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })
@@ -1002,17 +1104,21 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1002
1104
  function onSelect(id) { setSelectedTask(id); }
1003
1105
  var body;
1004
1106
  if (tab === 'tasks') {
1005
- var shown = recent.slice(0, 6);
1107
+ // 快捷面板只承载当前任务与可直接查看的成功产物;失败/取消历史留在完整工作台。
1108
+ var succeeded = recent.filter(function (t) {
1109
+ return t.status === 'succeeded' && Array.isArray(t.media) && t.media.length > 0;
1110
+ });
1111
+ var shown = succeeded.slice(0, 6);
1006
1112
  body = React.createElement('div', { className: 'iris-wb-box' },
1007
1113
  running.length ? React.createElement('div', {},
1008
1114
  React.createElement('div', { className: 'iris-wb-sec' }, '运行中'),
1009
1115
  running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })) : null,
1010
- React.createElement('div', { className: 'iris-wb-sec' }, '最近'),
1116
+ React.createElement('div', { className: 'iris-wb-sec' }, '最近完成'),
1011
1117
  shown.length ? shown.map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); })
1012
- : React.createElement('div', { className: 'iris-wb-empty' }, '尚无任务记录'),
1013
- recent.length > 6 || recentTotal > recent.length
1118
+ : React.createElement('div', { className: 'iris-wb-empty' }, '尚无成功产物'),
1119
+ succeeded.length > 6 || recentTotal > succeeded.length
1014
1120
  ? React.createElement('div', { className: 'iris-bubble-more', style: { cursor: 'default' } },
1015
- '共 ' + recentTotal + ' 条 · 完整历史与清理见设置页') : null);
1121
+ '失败、取消与完整历史请到 Iris 工作台查看') : null);
1016
1122
  } else {
1017
1123
  var defs = CARD_DEFS.filter(function (d) { return cards.indexOf(d.action) >= 0; });
1018
1124
  body = defs.length
@@ -1027,6 +1133,234 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1027
1133
  selectedTask ? React.createElement(TaskDetailDrawer, { taskId: selectedTask }) : null);
1028
1134
  }
1029
1135
 
1136
+ function PromptOptimizerControl(props) {
1137
+ var draft = props.useInput(function (value) { return value.draft; });
1138
+ var phase = props.useInput(function (value) { return value.phase; });
1139
+ var refCount = props.useInput(function (value) { return (value.occurrences || []).length; });
1140
+ var projection = props.useProjection('modelSelection');
1141
+ var selectedRoute = projection && (projection.current || projection.selection || projection.next || projection.lastUsed || (projection.provider && projection.model ? projection : null));
1142
+
1143
+ var openPair = React.useState(false);
1144
+ var open = openPair[0];
1145
+ var setOpen = openPair[1];
1146
+ var targetPair = React.useState('general');
1147
+ var target = targetPair[0];
1148
+ var setTarget = targetPair[1];
1149
+ var busyPair = React.useState(false);
1150
+ var busy = busyPair[0];
1151
+ var setBusy = busyPair[1];
1152
+ var resultPair = React.useState(null);
1153
+ var result = resultPair[0];
1154
+ var setResult = resultPair[1];
1155
+ var notePair = React.useState('');
1156
+ var note = notePair[0];
1157
+ var setNote = notePair[1];
1158
+ var configPair = React.useState(null);
1159
+ var configState = configPair[0];
1160
+ var setConfigState = configPair[1];
1161
+ var abortRef = React.useRef(null);
1162
+ var fileRef = React.useRef(null);
1163
+
1164
+ function promptRequest(operation, body, signal) {
1165
+ return fetch('/iris/api/prompt-optimizer/' + operation, {
1166
+ method: 'POST',
1167
+ headers: { 'Content-Type': 'application/json' },
1168
+ body: JSON.stringify(body || {}),
1169
+ signal: signal
1170
+ }).then(function (res) {
1171
+ return res.json().catch(function () { return {}; }).then(function (data) {
1172
+ if (!res.ok) throw new Error(data.error || ('请求失败 (' + res.status + ')'));
1173
+ return data;
1174
+ });
1175
+ });
1176
+ }
1177
+
1178
+ function loadConfig() {
1179
+ fetch('/iris/api/prompt-optimizer/config')
1180
+ .then(function (res) { return res.ok ? res.json() : null; })
1181
+ .then(function (data) { if (data && data.config) setConfigState(data); })
1182
+ .catch(function () { setNote('无法读取提示词优化配置'); });
1183
+ }
1184
+
1185
+ React.useEffect(function () {
1186
+ return function () {
1187
+ if (abortRef.current) abortRef.current.abort();
1188
+ };
1189
+ }, []);
1190
+
1191
+ React.useEffect(function () {
1192
+ function changed(event) { if (event.detail && event.detail.config) setConfigState(event.detail); }
1193
+ window.addEventListener('iris-prompt-config-changed', changed);
1194
+ loadConfig();
1195
+ return function () { window.removeEventListener('iris-prompt-config-changed', changed); };
1196
+ }, []);
1197
+
1198
+ function publishConfig(data) {
1199
+ setConfigState(data);
1200
+ try { window.dispatchEvent(new CustomEvent('iris-prompt-config-changed', { detail: data })); } catch (_) {}
1201
+ }
1202
+
1203
+ function optimize() {
1204
+ if (!draft || !draft.trim()) { setNote('请先在输入框中写入提示词'); return; }
1205
+ if (refCount) { setNote('当前版本暂不改写含 @ 或 / 结构化引用的草稿,以免引用失效'); return; }
1206
+ if (phase !== 'plain') { setNote('输入框正忙,请稍后再试'); return; }
1207
+ if (abortRef.current) abortRef.current.abort();
1208
+ var controller = new AbortController();
1209
+ abortRef.current = controller;
1210
+ setBusy(true);
1211
+ setNote('正在调用模型优化…这可能产生模型费用');
1212
+ setResult(null);
1213
+ promptRequest('optimize', {
1214
+ text: draft,
1215
+ target: target,
1216
+ sessionId: props.sessionId,
1217
+ route: selectedRoute || undefined
1218
+ }, controller.signal).then(function (data) {
1219
+ setResult(data);
1220
+ setNote('已生成预览,尚未写回输入框');
1221
+ }).catch(function (err) {
1222
+ if (controller.signal.aborted) setNote('已取消');
1223
+ else setNote(String(err && err.message || err));
1224
+ }).finally(function () {
1225
+ if (abortRef.current === controller) abortRef.current = null;
1226
+ setBusy(false);
1227
+ });
1228
+ }
1229
+
1230
+ function cancel() {
1231
+ if (abortRef.current) abortRef.current.abort();
1232
+ }
1233
+
1234
+ function replaceDraft(text) {
1235
+ if (!result) return;
1236
+ if (draft !== result.original && !window.confirm('输入框内容在优化期间已变化,仍要替换吗?')) return;
1237
+ props.inputActions.setDraft(text);
1238
+ setNote(text === result.original ? '已恢复优化前原文' : '已写回输入框;不会自动发送');
1239
+ }
1240
+
1241
+ function copyResult() {
1242
+ if (!result) return;
1243
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
1244
+ navigator.clipboard.writeText(result.optimized).then(function () { setNote('已复制优化结果'); }).catch(function () { setNote('复制失败,请手动选择文本'); });
1245
+ } else {
1246
+ setNote('浏览器不支持自动复制,请手动选择文本');
1247
+ }
1248
+ }
1249
+
1250
+ function importConfig(event) {
1251
+ var file = event.target.files && event.target.files[0];
1252
+ event.target.value = '';
1253
+ if (!file) return;
1254
+ file.text().then(function (raw) {
1255
+ var parsed = JSON.parse(raw);
1256
+ return promptRequest('import', { config: parsed });
1257
+ }).then(function (data) {
1258
+ publishConfig(data);
1259
+ setNote('JSON 配置已导入并生效');
1260
+ }).catch(function (err) {
1261
+ setNote('导入失败:' + String(err && err.message || err));
1262
+ });
1263
+ }
1264
+
1265
+ function exportConfig() {
1266
+ if (!configState || !configState.config) { setNote('配置尚未载入'); return; }
1267
+ try {
1268
+ var blob = new Blob([JSON.stringify(configState.config, null, 2) + '\n'], { type: 'application/json' });
1269
+ var url = URL.createObjectURL(blob);
1270
+ var a = document.createElement('a');
1271
+ a.href = url;
1272
+ a.download = 'iris-prompt-optimizer.json';
1273
+ a.click();
1274
+ setTimeout(function () { URL.revokeObjectURL(url); }, 0);
1275
+ setNote('已导出当前 JSON 配置');
1276
+ } catch (_) {
1277
+ setNote('导出失败');
1278
+ }
1279
+ }
1280
+
1281
+ function resetConfig() {
1282
+ if (!window.confirm('恢复 Iris 内置默认 Prompt、目标模板和会话模型路由?')) return;
1283
+ promptRequest('reset', {}).then(function (data) {
1284
+ publishConfig(data);
1285
+ setNote('已恢复 Iris 内置默认配置');
1286
+ }).catch(function (err) {
1287
+ setNote('重置失败:' + String(err && err.message || err));
1288
+ });
1289
+ }
1290
+
1291
+ function disableEntry() {
1292
+ if (busy) return;
1293
+ promptRequest('enabled', { enabled: false }).then(function (data) {
1294
+ publishConfig(data);
1295
+ setOpen(false);
1296
+ }).catch(function (err) { setNote('关闭失败:' + String(err && err.message || err)); });
1297
+ }
1298
+
1299
+ if (configState && configState.config && configState.config.enabled === false) return null;
1300
+
1301
+ var routeLabel = 'DSH 默认模型';
1302
+ if (configState && configState.config && configState.config.route.mode === 'fixed') {
1303
+ routeLabel = configState.config.route.provider + ' / ' + configState.config.route.model;
1304
+ } else if (selectedRoute) {
1305
+ routeLabel = selectedRoute.provider + ' / ' + selectedRoute.model;
1306
+ }
1307
+ var reasoningSetting = configState && configState.config && configState.config.generation && configState.config.generation.reasoningEffort || 'off-if-supported';
1308
+ var reasoningLabel = reasoningSetting === 'off-if-supported' ? '支持时关闭' : reasoningSetting === 'provider-default' ? '供应商默认' : reasoningSetting === 'inherit' ? '跟随会话' : reasoningSetting;
1309
+ var outputBudget = configState && configState.config && configState.config.generation && configState.config.generation.maxOutputTokens || 1200;
1310
+
1311
+ var panel = null;
1312
+ if (open) {
1313
+ panel = React.createElement(React.Fragment, {},
1314
+ React.createElement('div', { className: 'iris-po-backdrop', onClick: function () { setOpen(false); }, 'aria-hidden': 'true' }),
1315
+ React.createElement('div', { className: 'iris-po-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Iris 提示词优化', onClick: function (e) { e.stopPropagation(); } },
1316
+ React.createElement('div', { className: 'iris-po-head' },
1317
+ React.createElement('strong', { className: 'iris-po-title' }, React.createElement('span', { className: 'iris-po-mark' }, '🫧'), 'Iris 提示词优化'),
1318
+ React.createElement('button', { className: 'iris-po-close', onClick: function () { setOpen(false); }, title: '关闭' }, '✕')),
1319
+ React.createElement('div', { className: 'iris-po-muted' }, '只处理当前未发送草稿,不读取会话历史;调用模型可能产生费用。'),
1320
+ React.createElement('label', { className: 'iris-po-field' },
1321
+ React.createElement('span', {}, '优化目标'),
1322
+ React.createElement('select', { value: target, disabled: busy, onChange: function (e) { setTarget(e.target.value); } },
1323
+ React.createElement('option', { value: 'general' }, '通用对话'),
1324
+ React.createElement('option', { value: 'image' }, '图片生成'),
1325
+ React.createElement('option', { value: 'video' }, '视频生成'),
1326
+ React.createElement('option', { value: 's2v' }, '首尾帧视频'))),
1327
+ React.createElement('div', { className: 'iris-po-route', title: routeLabel }, '模型:' + routeLabel + ' · 思考:' + reasoningLabel + ' · 输出:' + outputBudget + ' tokens'),
1328
+ React.createElement('div', { className: 'iris-po-actions' },
1329
+ React.createElement('button', { className: 'iris-po-primary', disabled: busy || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: optimize }, busy ? '优化中…' : '生成预览'),
1330
+ busy ? React.createElement('button', { className: 'iris-po-btn', onClick: cancel }, '取消') : null),
1331
+ result ? React.createElement('div', { className: 'iris-po-result' },
1332
+ React.createElement('div', { className: 'iris-po-result-label' }, '优化结果'),
1333
+ React.createElement('textarea', { readOnly: true, value: result.optimized }),
1334
+ React.createElement('div', { className: 'iris-po-route' }, '实际使用:' + result.route.provider + ' / ' + result.route.model + ' · 思考:' + (result.route.reasoningEffort === 'provider-default' ? '供应商默认' : result.route.reasoningEffort)),
1335
+ React.createElement('div', { className: 'iris-po-actions' },
1336
+ React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(result.optimized); } }, '写回输入框'),
1337
+ React.createElement('button', { className: 'iris-po-btn', onClick: copyResult }, '复制'),
1338
+ React.createElement('button', { className: 'iris-po-btn', onClick: optimize }, '再优化'),
1339
+ React.createElement('button', { className: 'iris-po-btn', onClick: function () { replaceDraft(result.original); } }, '恢复原文'))) : null,
1340
+ note ? React.createElement('div', { className: 'iris-po-note' }, note) : null,
1341
+ React.createElement('details', { className: 'iris-po-config' },
1342
+ React.createElement('summary', {}, 'JSON 配置'),
1343
+ React.createElement('div', { className: 'iris-po-muted' }, '可导出后修改 systemPrompt、targets、route 和 generation,再重新导入。'),
1344
+ React.createElement('div', { className: 'iris-po-actions' },
1345
+ React.createElement('button', { className: 'iris-po-btn', onClick: exportConfig }, '导出'),
1346
+ React.createElement('button', { className: 'iris-po-btn', onClick: function () { if (fileRef.current) fileRef.current.click(); } }, '导入'),
1347
+ React.createElement('button', { className: 'iris-po-btn danger', onClick: resetConfig }, '恢复默认'),
1348
+ React.createElement('input', { ref: fileRef, className: 'iris-po-file', type: 'file', accept: 'application/json,.json', onChange: importConfig })),
1349
+ React.createElement('div', { className: 'iris-po-muted' }, '当前:' + (configState && configState.source === 'custom' ? '用户 JSON' : 'Iris 默认'))),
1350
+ React.createElement('button', { className: 'iris-po-disable', disabled: busy, onClick: disableEntry }, '关闭此对话入口(工作台仍保留)')));
1351
+ }
1352
+
1353
+ return React.createElement('div', { className: 'iris-po-wrap' },
1354
+ React.createElement('button', {
1355
+ className: 'iris-po-trigger',
1356
+ type: 'button',
1357
+ title: refCount ? '含结构化引用的草稿暂不支持优化' : '优化当前输入框提示词',
1358
+ 'aria-expanded': open ? 'true' : 'false',
1359
+ onClick: function () { setOpen(!open); }
1360
+ }, '🫧'),
1361
+ panel);
1362
+ }
1363
+
1030
1364
  function ProgressDock() {
1031
1365
  var state = useIrisState(5000);
1032
1366
  var running = (state && state.tasks && state.tasks.running) || [];
@@ -1167,6 +1501,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1167
1501
  }, WorkbenchPanel);
1168
1502
  });
1169
1503
 
1504
+ ctx.slots.inject('conversation.input.right', function () {
1505
+ return ctx.slots.register({
1506
+ name: 'conversation.input.right',
1507
+ id: 'iris-prompt-optimizer',
1508
+ order: 40,
1509
+ label: function () { return 'Iris 提示词优化'; }
1510
+ }, PromptOptimizerControl);
1511
+ });
1512
+
1170
1513
  ctx.slots.inject('conversation.input.dock', function () {
1171
1514
  return ctx.slots.register({
1172
1515
  name: 'conversation.input.dock',
package/lib/index.js CHANGED
@@ -24,6 +24,7 @@ import { longOcr } from './ocr.js';
24
24
  import { runAction, listActions } from './actions.js';
25
25
  import { ffmpegAvailable, extractFrames, probeVideo, extractAudioTrack } from './media-probe.js';
26
26
  import { buildContactSheet, summarizeMedia } from './summarize.js';
27
+ import { registerBundledSkills } from './bundled-skills.js';
27
28
 
28
29
  const MEDIA_TYPES = {
29
30
  '.png': 'image/png',
@@ -155,7 +156,7 @@ function fmtTask(t) {
155
156
  return lines.join('\n');
156
157
  }
157
158
 
158
- export const name = 'dsh-iris';
159
+ export const name = 'mokuyoaxis-dsh-iris';
159
160
  export const inject = ['tools'];
160
161
 
161
162
  /**
@@ -214,6 +215,15 @@ export async function apply(ctx) {
214
215
  // 若 webServer 已在运行,inject 回调也会触发;这里兜底立即挂载一次
215
216
  guard('媒体路由兜底挂载', () => mountIrisRoutes(ctx));
216
217
 
218
+ // DSH 的 Skill registry 是可选宿主能力。存在时把 npm 包内两项 Skill 注册到
219
+ // 全局层;项目目录中的同名 Skill 仍由 DSH 原生优先级覆盖。
220
+ ctx.inject(['skills'], (skillCtx) => {
221
+ guard('随包 Skill 注册', () => {
222
+ const names = registerBundledSkills(skillCtx);
223
+ console.log('[iris] 随包 Skill 已注册:' + names.join(' · '));
224
+ });
225
+ });
226
+
217
227
  const tools = ctx.tools;
218
228
  if (!tools || typeof tools.register !== 'function') {
219
229
  console.error('[iris] tools 服务不可用,跳过全部工具注册(宿主不受影响)');
@@ -0,0 +1,170 @@
1
+ 'use strict';
2
+ /**
3
+ * Iris 提示词优化器配置。
4
+ *
5
+ * 与 providers.json 分离保存:这里不含凭据,只记录用户可导入/导出的 Prompt、
6
+ * 目标媒介要求与模型路由偏好。损坏配置会被隔离,运行时回退到内置默认值。
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { irisHome } from './config.js';
11
+ import { atomicWritePrivate, chmodPrivateFile, privateSibling } from './private-storage.js';
12
+
13
+ export const PROMPT_TARGETS = ['general', 'image', 'video', 's2v'];
14
+
15
+ const DEFAULT_VALUE = {
16
+ version: 1,
17
+ enabled: true,
18
+ systemPrompt: [
19
+ '你是 Iris 提示词编辑器。把用户草稿改写成一段可直接交给目标 AI 的高质量提示词。',
20
+ '必须保留原始意图、事实、专有名词、引号内原文、硬性约束和否定条件;不得臆造用户没有提供的事实。',
21
+ '只补充能提高可执行性、结构清晰度、风格一致性和验收明确度的具体信息。信息不足时保持克制,不替用户作关键决定。',
22
+ '默认沿用草稿的语言。只输出优化后的提示词正文,不解释、不加标题、不使用 Markdown 代码块。'
23
+ ].join('\n'),
24
+ targets: {
25
+ general: '适用于一般对话任务:明确目标、必要背景、约束、期望输出和验收标准;避免无意义地拉长文本。',
26
+ image: '适用于图像生成:在原意允许的范围内明确主体、环境、构图、视角、光线、色彩、材质、风格和负面约束。',
27
+ video: '适用于视频生成:在原意允许的范围内明确主体动作、场景变化、镜头运动、景别、节奏、时长感、连续性和负面约束。',
28
+ s2v: '适用于首尾帧视频:明确首帧到尾帧之间的动作、镜头、空间关系和连续性,避免引入与两端画面冲突的新主体。'
29
+ },
30
+ route: {
31
+ mode: 'session'
32
+ },
33
+ generation: {
34
+ temperature: 0.3,
35
+ reasoningEffort: 'off-if-supported',
36
+ maxOutputTokens: 1200,
37
+ timeoutMs: 45000
38
+ }
39
+ };
40
+
41
+ export const DEFAULT_PROMPT_OPTIMIZER_CONFIG = Object.freeze(structuredClone(DEFAULT_VALUE));
42
+
43
+ function configFile() {
44
+ return path.join(irisHome(), 'prompt-optimizer.json');
45
+ }
46
+
47
+ function plain(value) {
48
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
49
+ }
50
+
51
+ function text(value, fallback, field, maxLength) {
52
+ if (value === undefined) return fallback;
53
+ if (typeof value !== 'string' || !value.trim()) throw new Error(`${field} 必须是非空字符串`);
54
+ if (value.length > maxLength) throw new Error(`${field} 不能超过 ${maxLength} 个字符`);
55
+ return value.trim();
56
+ }
57
+
58
+ function boundedNumber(value, fallback, field, min, max) {
59
+ if (value === undefined) return fallback;
60
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < min || value > max) {
61
+ throw new Error(`${field} 必须是 ${min}–${max} 之间的数字`);
62
+ }
63
+ return value;
64
+ }
65
+
66
+ /** 规范化用户导入的完整或局部 JSON,不保留未知字段。 */
67
+ export function normalizePromptOptimizerConfig(input) {
68
+ if (!plain(input)) throw new Error('提示词优化器配置根节点必须是对象');
69
+ if (input.version !== undefined && input.version !== 1) throw new Error('仅支持 version: 1');
70
+
71
+ const targetInput = input.targets === undefined ? {} : input.targets;
72
+ if (!plain(targetInput)) throw new Error('targets 必须是对象');
73
+ const targets = {};
74
+ for (const target of PROMPT_TARGETS) {
75
+ targets[target] = text(targetInput[target], DEFAULT_VALUE.targets[target], `targets.${target}`, 8000);
76
+ }
77
+
78
+ const routeInput = input.route === undefined ? {} : input.route;
79
+ if (!plain(routeInput)) throw new Error('route 必须是对象');
80
+ const mode = routeInput.mode === undefined ? DEFAULT_VALUE.route.mode : routeInput.mode;
81
+ if (mode !== 'session' && mode !== 'fixed') throw new Error('route.mode 只能是 session 或 fixed');
82
+ const route = { mode };
83
+ if (mode === 'fixed') {
84
+ route.provider = text(routeInput.provider, undefined, 'route.provider', 256);
85
+ route.model = text(routeInput.model, undefined, 'route.model', 256);
86
+ if (!route.provider || !route.model) throw new Error('fixed 路由必须同时提供 route.provider 与 route.model');
87
+ }
88
+
89
+ const generationInput = input.generation === undefined ? {} : input.generation;
90
+ if (!plain(generationInput)) throw new Error('generation 必须是对象');
91
+ const generation = {
92
+ temperature: boundedNumber(generationInput.temperature, DEFAULT_VALUE.generation.temperature, 'generation.temperature', 0, 2),
93
+ reasoningEffort: text(generationInput.reasoningEffort, DEFAULT_VALUE.generation.reasoningEffort, 'generation.reasoningEffort', 128),
94
+ maxOutputTokens: Math.round(boundedNumber(generationInput.maxOutputTokens, DEFAULT_VALUE.generation.maxOutputTokens, 'generation.maxOutputTokens', 64, 4096)),
95
+ timeoutMs: Math.round(boundedNumber(generationInput.timeoutMs, DEFAULT_VALUE.generation.timeoutMs, 'generation.timeoutMs', 1000, 120000))
96
+ };
97
+
98
+ if (input.enabled !== undefined && typeof input.enabled !== 'boolean') throw new Error('enabled 必须是布尔值');
99
+
100
+ return {
101
+ version: 1,
102
+ enabled: input.enabled === undefined ? DEFAULT_VALUE.enabled : input.enabled,
103
+ systemPrompt: text(input.systemPrompt, DEFAULT_VALUE.systemPrompt, 'systemPrompt', 16000),
104
+ targets,
105
+ route,
106
+ generation
107
+ };
108
+ }
109
+
110
+ let cache = null;
111
+ let cacheSource = 'default';
112
+
113
+ export function loadPromptOptimizerConfig() {
114
+ if (cache) return { source: cacheSource, config: structuredClone(cache) };
115
+ const file = configFile();
116
+ if (!fs.existsSync(file)) {
117
+ cache = structuredClone(DEFAULT_VALUE);
118
+ cacheSource = 'default';
119
+ return { source: cacheSource, config: structuredClone(cache) };
120
+ }
121
+ try {
122
+ cache = normalizePromptOptimizerConfig(JSON.parse(fs.readFileSync(file, 'utf8')));
123
+ cacheSource = JSON.stringify(cache) === JSON.stringify(DEFAULT_VALUE) ? 'default' : 'custom';
124
+ } catch (err) {
125
+ const backup = privateSibling(file, 'corrupted');
126
+ try {
127
+ fs.renameSync(file, backup);
128
+ chmodPrivateFile(backup);
129
+ } catch (_) { /* 隔离失败也必须能使用内置默认值 */ }
130
+ console.error('[iris] prompt-optimizer.json 已损坏,已回退内置默认值:', err && err.message);
131
+ cache = structuredClone(DEFAULT_VALUE);
132
+ cacheSource = 'default';
133
+ }
134
+ return { source: cacheSource, config: structuredClone(cache) };
135
+ }
136
+
137
+ export function importPromptOptimizerConfig(input) {
138
+ const next = normalizePromptOptimizerConfig(input);
139
+ atomicWritePrivate(configFile(), JSON.stringify(next, null, 2));
140
+ cache = next;
141
+ cacheSource = 'custom';
142
+ return { source: cacheSource, config: structuredClone(cache) };
143
+ }
144
+
145
+ export function setPromptOptimizerEnabled(enabled) {
146
+ if (typeof enabled !== 'boolean') throw new Error('enabled 必须是布尔值');
147
+ const next = normalizePromptOptimizerConfig({ ...loadPromptOptimizerConfig().config, enabled });
148
+ atomicWritePrivate(configFile(), JSON.stringify(next, null, 2));
149
+ cache = next;
150
+ cacheSource = JSON.stringify(cache) === JSON.stringify(DEFAULT_VALUE) ? 'default' : 'custom';
151
+ return { source: cacheSource, config: structuredClone(cache) };
152
+ }
153
+
154
+ export function resetPromptOptimizerConfig() {
155
+ const next = structuredClone(DEFAULT_VALUE);
156
+ atomicWritePrivate(configFile(), JSON.stringify(next, null, 2));
157
+ cache = next;
158
+ cacheSource = 'default';
159
+ return { source: cacheSource, config: structuredClone(cache) };
160
+ }
161
+
162
+ /** 仅供测试/热重载丢弃进程内缓存。 */
163
+ export function resetPromptOptimizerConfigCache() {
164
+ cache = null;
165
+ cacheSource = 'default';
166
+ }
167
+
168
+ export function promptOptimizerConfigFile() {
169
+ return configFile();
170
+ }