@mokuyoaxis/dsh-iris 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.dsh/skills/iris-compose-media/SKILL.md +23 -49
  2. package/.dsh/skills/iris-compose-media/references/task-v2-recovery.md +30 -0
  3. package/.dsh/skills/iris-compose-media/references/workflows.md +38 -0
  4. package/.dsh/skills/iris-verify-ui/SKILL.md +3 -25
  5. package/.dsh/skills/iris-verify-ui/agents/openai.yaml +4 -0
  6. package/.dsh/skills/iris-verify-ui/references/verdict-and-degradation.md +31 -0
  7. package/CHANGELOG.md +77 -2
  8. package/README.en.md +98 -19
  9. package/README.md +98 -19
  10. package/bin/dsh-iris.js +392 -13
  11. package/docs/AGENT_QUICKSTART.md +35 -0
  12. package/docs/ARCHITECTURE.md +82 -52
  13. package/docs/ARTIFACT_LIBRARY.md +24 -0
  14. package/docs/ARTIFACT_MANIFEST.md +60 -0
  15. package/docs/CORE_RUNTIME_CONTRACT.md +83 -0
  16. package/docs/DSH_CORE_MIGRATION.md +106 -0
  17. package/docs/DSH_RC2_ADAPTATION.md +36 -0
  18. package/docs/HEADLESS_CLI.md +217 -0
  19. package/docs/HOST_ADAPTER_CONTRACT.md +105 -0
  20. package/docs/HOST_DOCTOR.md +49 -0
  21. package/docs/MODEL_PORT_CONTRACT.md +300 -0
  22. package/docs/PROVIDER_ADAPTER_CONTRACT.md +138 -0
  23. package/docs/PROVIDER_HEALTH.md +39 -0
  24. package/docs/PROVIDER_RUNTIME_HARNESS.md +33 -0
  25. package/docs/PROVIDER_SUBMISSION_CONTRACT.md +2 -2
  26. package/docs/ROADMAP.md +42 -76
  27. package/docs/SECURITY.md +10 -5
  28. package/docs/TASK_SEMANTICS.md +3 -2
  29. package/docs/assets/diagrams/iris-architecture.png +0 -0
  30. package/docs/assets/diagrams/iris-architecture.svg +5 -5
  31. package/docs/assets/logo/README.md +55 -0
  32. package/docs/assets/logo/iris-bubble-concept.svg +67 -0
  33. package/docs/assets/logo/iris-bubble.svg +14 -0
  34. package/docs/assets/logo/iris-interim-flower.png +0 -0
  35. package/docs/assets/logo/iris-logo-mono-inverse.svg +78 -0
  36. package/docs/assets/logo/iris-logo-mono.svg +78 -0
  37. package/docs/assets/logo/iris-logo.svg +78 -0
  38. package/docs/assets/logo/iris-mark-mono-inverse.svg +42 -0
  39. package/docs/assets/logo/iris-mark-mono.svg +42 -0
  40. package/docs/assets/logo/iris-mark.svg +42 -0
  41. package/docs/releases/0.1.4.md +59 -0
  42. package/docs/releases/0.2.0.md +48 -0
  43. package/lib/actions.js +673 -525
  44. package/lib/adapters.js +58 -21
  45. package/lib/api.js +182 -16
  46. package/lib/artifacts.js +239 -0
  47. package/lib/bundled-skills.js +5 -5
  48. package/lib/client.js +1116 -86
  49. package/lib/command-service.js +420 -0
  50. package/lib/config.js +245 -49
  51. package/lib/core-artifact-store.js +598 -0
  52. package/lib/core-artifacts.js +4 -0
  53. package/lib/core-attention.js +103 -0
  54. package/lib/core-contract.js +105 -0
  55. package/lib/core-doctor.js +41 -0
  56. package/lib/core-lease-recovery.js +170 -0
  57. package/lib/core-runtime.js +232 -0
  58. package/lib/core-tasks.js +560 -0
  59. package/lib/core-user-projection.js +192 -0
  60. package/lib/doctor.js +260 -30
  61. package/lib/dsh-core-adapter.js +599 -0
  62. package/lib/dsh-host-adapter.js +240 -0
  63. package/lib/generation-input.js +95 -0
  64. package/lib/guard.js +3 -3
  65. package/lib/host-contract.js +131 -0
  66. package/lib/host-runtime.js +156 -0
  67. package/lib/index.js +255 -166
  68. package/lib/locate.js +1 -1
  69. package/lib/media.js +16 -6
  70. package/lib/model-invoker.js +218 -0
  71. package/lib/model-port-contract.js +333 -0
  72. package/lib/models.js +97 -11
  73. package/lib/prompt-optimizer.js +10 -20
  74. package/lib/provider-adapter.js +397 -0
  75. package/lib/provider-adapters.js +342 -0
  76. package/lib/provider-catalog.js +171 -0
  77. package/lib/provider-contract.js +72 -3
  78. package/lib/provider-health.js +59 -0
  79. package/lib/provider-protocol.js +51 -2
  80. package/lib/provider-task-runner.js +328 -0
  81. package/lib/render.js +43 -12
  82. package/lib/tasks.js +45 -9
  83. package/lib/vision.js +51 -56
  84. package/package.json +20 -14
  85. package/user_guide.md +67 -12
  86. package/docs/assets/diagrams/iris-architecture.drawio +0 -1
  87. package/docs/assets/diagrams/iris-task-lifecycle.drawio +0 -1
  88. package/docs/assets/diagrams/iris-workflow-compose.drawio +0 -1
  89. package/docs/releases/0.1.1.md +0 -51
  90. package/docs/releases/0.1.2.md +0 -26
  91. package/docs/releases/0.1.3.md +0 -30
package/lib/client.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * DSH web client bundle 标准形态(与 dsh-at-file / dsh-better-sidebar 同构):
5
5
  * window.__ModuleLoader__.load({ id, factory }) → CJS 工厂,require 解析宿主依赖,
6
6
  * `inject` 声明本 bundle 需要的 ctx 服务 key(这里是 slots),
7
- * `apply(ctx)` 里直接 `ctx.slots.inject(...)` 注册座位。
7
+ * `apply(ctx)` 通过本地 clientSlots 适配端口注册座位。
8
8
  *
9
9
  * 三个零替换风险的座位:
10
10
  * - settings.section(id: 'iris-workbench'):Iris 工作台整页 ——
@@ -14,13 +14,49 @@
14
14
  *
15
15
  * 数据通道:host 侧 /iris/api/state 同源 JSON 路由(复用 /iris/media 同款
16
16
  * webServer 前缀模式),client 侧 fetch 轮询。全走标量,
17
- * apiKey 永不明文,产物给授权播放链接。
17
+ * apiKey 永不明文;legacy 产物使用 token 链接,Core 产物使用受 Host/浏览器来源守卫保护的 Artifact ID 链接。
18
18
  */
19
19
  window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) => {
20
20
  var module = { exports: {} };
21
21
  var exports = module.exports;
22
22
 
23
23
  var React = require('react');
24
+ var IRIS_CLIENT_VERSION = '0.2.0';
25
+ var HOST_CLIENT_PROTOCOL_VERSION = 0;
26
+ var reportedSeats = [];
27
+
28
+ function reportClientSeat(seat) {
29
+ if (reportedSeats.indexOf(seat) < 0) reportedSeats.push(seat);
30
+ if (!window || typeof window.fetch !== 'function') return;
31
+ try {
32
+ Promise.resolve(window.fetch('/iris/api/host-client', {
33
+ method: 'POST',
34
+ headers: { 'content-type': 'application/json' },
35
+ credentials: 'same-origin',
36
+ body: JSON.stringify({
37
+ pluginId: '@mokuyoaxis/dsh-iris',
38
+ version: IRIS_CLIENT_VERSION,
39
+ protocolVersion: HOST_CLIENT_PROTOCOL_VERSION,
40
+ seats: reportedSeats.slice()
41
+ })
42
+ })).catch(function () {});
43
+ } catch (_) { /* Doctor handshake 不影响 UI */ }
44
+ }
45
+
46
+ // Client bundle 不能导入 server-side Host contract;只在此边界读取 DSH slots 服务。
47
+ function clientSlotsPort(ctx) {
48
+ var slots;
49
+ try { slots = ctx && ctx.slots; } catch (_) { slots = null; }
50
+ if (!slots || typeof slots.inject !== 'function' || typeof slots.register !== 'function') {
51
+ var error = new Error('Iris clientSlots 不可用:DSH slots 需要 inject/register');
52
+ error.code = slots ? 'IRIS_HOST_CAPABILITY_INCOMPATIBLE' : 'IRIS_HOST_CAPABILITY_UNAVAILABLE';
53
+ throw error;
54
+ }
55
+ return Object.freeze({
56
+ inject: function (seat, callback) { return slots.inject(seat, callback); },
57
+ register: function (definition, component) { return slots.register(definition, component); }
58
+ });
59
+ }
24
60
 
25
61
  var STYLE_ID = 'iris-wb-css';
26
62
  var cssText = [
@@ -57,6 +93,44 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
57
93
  '.iris-wb-drawer .iris-wb-prompt { white-space: pre-wrap; word-break: break-word; font-size: 12px; }',
58
94
  '.iris-wb-drawer .iris-wb-err { color: var(--dsw-alias-state-error-primary, #e06c6c); white-space: pre-wrap; word-break: break-word; font-size: 12px; }',
59
95
  '.iris-wb-loading { color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 12px; }',
96
+ '.iris-doctor-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }',
97
+ '.iris-doctor-score { font-weight: 600; flex: 1; }',
98
+ '.iris-doctor-list { display: grid; gap: 4px; margin-top: 7px; }',
99
+ '.iris-doctor-row { display: grid; grid-template-columns: 14px 1fr; gap: 6px; font-size: 11px; }',
100
+ '.iris-doctor-row.ok { color: var(--dsw-alias-label-secondary, #9a9a9a); }',
101
+ '.iris-doctor-row.warn { color: var(--dsw-alias-state-warn-primary, #d9a941); }',
102
+ '.iris-doctor-row.error { color: var(--dsw-alias-state-error-primary, #e06c6c); }',
103
+ '.iris-doctor-details { margin-top: 7px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
104
+ '.iris-doctor-details summary { cursor: pointer; font-size: 11px; }',
105
+ '.iris-core-runtime { cursor: default; padding: 0; overflow: hidden; }',
106
+ '.iris-core-runtime-head { display: flex; align-items: center; gap: 8px; padding: 9px 10px; }',
107
+ '.iris-core-runtime-title { min-width: 0; flex: 1; display: grid; gap: 1px; }',
108
+ '.iris-core-runtime-title strong { font-size: 12px; }',
109
+ '.iris-core-runtime-body { display: grid; gap: 8px; padding: 0 10px 10px; border-top: 1px dashed var(--dsw-alias-border-l1, #3a3a44); }',
110
+ '.iris-core-runtime-tools { display: flex; align-items: center; gap: 7px; padding-top: 8px; flex-wrap: wrap; }',
111
+ '.iris-core-task-list { display: grid; gap: 6px; }',
112
+ '.iris-core-task-row { width: 100%; box-sizing: border-box; display: grid; grid-template-columns: 9px minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 8px 9px; text-align: left; color: inherit; background: var(--dsw-alias-bg-layer-2, #32323c); border: 1px solid transparent; border-radius: 8px; cursor: pointer; font-family: inherit; }',
113
+ '.iris-core-task-row:hover, .iris-core-task-row.selected { border-color: var(--dsw-alias-brand-primary, #7aa2f7); }',
114
+ '.iris-core-state-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #73737d); }',
115
+ '.iris-core-state-dot.ok { background: var(--dsw-alias-state-success-primary, #6fcf6f); box-shadow: 0 0 6px rgba(111,207,111,.45); }',
116
+ '.iris-core-state-dot.warn { background: var(--dsw-alias-state-warn-primary, #d9a941); }',
117
+ '.iris-core-state-dot.error { background: #b9656d; }',
118
+ '.iris-core-task-main { min-width: 0; display: grid; gap: 1px; }',
119
+ '.iris-core-task-main strong { font-size: 12px; font-weight: 600; }',
120
+ '.iris-core-task-arrow { color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 10px; }',
121
+ '.iris-core-detail { display: grid; gap: 9px; padding: 10px; border-radius: 9px; background: var(--dsw-alias-bg-layer-2, #32323c); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); }',
122
+ '.iris-core-detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }',
123
+ '.iris-core-detail-cell { min-width: 0; display: grid; gap: 1px; }',
124
+ '.iris-core-detail-cell span:last-child { overflow-wrap: anywhere; }',
125
+ '.iris-core-id { user-select: all; overflow-wrap: anywhere; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; }',
126
+ '.iris-core-attempt { display: grid; gap: 3px; padding: 7px 8px; border-radius: 7px; background: var(--dsw-alias-bg-layer-1, #26262e); }',
127
+ '.iris-core-artifact { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; padding: 7px 8px; border-radius: 7px; background: var(--dsw-alias-bg-layer-1, #26262e); }',
128
+ '@media (max-width: 420px) { .iris-core-detail-grid { grid-template-columns: 1fr; } .iris-core-task-row { grid-template-columns: 8px minmax(0, 1fr) auto; padding: 8px; } }',
129
+ /* ---- Core 用户侧只读投影(任务区安全 DTO 行) ---- */
130
+ '.iris-core-user-row { cursor: default; }',
131
+ '.iris-core-user-head { min-width: 0; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }',
132
+ '.iris-core-user-model { min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
133
+ '@media (max-width: 420px) { .iris-core-user-head { display: grid; grid-template-columns: auto minmax(0, 1fr); row-gap: 2px; justify-items: start; } .iris-core-user-head .iris-wb-muted, .iris-core-user-row .iris-core-id { grid-column: 1 / -1; } }',
60
134
  /* ---- 操作卡片组(阶段 5 GUI 直连) ---- */
61
135
  '.iris-act-group { display: grid; gap: 8px; }',
62
136
  '.iris-act-card { background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; overflow: hidden; }',
@@ -96,8 +170,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
96
170
  '.iris-pm-field { display: grid; gap: 3px; }',
97
171
  '.iris-pm-field input, .iris-pm-field textarea { background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 4px; padding: 4px 6px; font-size: 12px; width: 100%; box-sizing: border-box; }',
98
172
  '.iris-pm-act { font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
99
- '.iris-pm-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-secondary, #9a9a9a); margin-right: 4px; }',
100
- '.iris-pm-dot.lit { background: var(--dsw-alias-state-success-primary, #6fcf6f); box-shadow: 0 0 6px var(--dsw-alias-state-success-primary, #6fcf6f); }',
173
+ '.iris-pm-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #73737d); margin-right: 4px; }',
174
+ '.iris-pm-dot.configured { background: #6f9df6; box-shadow: 0 0 7px rgba(111,157,246,.55); }',
175
+ '.iris-pm-dot.verified { background: var(--dsw-alias-state-success-primary, #6fcf6f); box-shadow: 0 0 7px var(--dsw-alias-state-success-primary, #6fcf6f); }',
176
+ '.iris-pm-dot.failed { background: #b9656d; box-shadow: 0 0 6px rgba(185,101,109,.38); opacity: .82; }',
177
+ '.iris-health-grid { display: grid; gap: 5px; }',
178
+ '.iris-health-row { display: grid; grid-template-columns: minmax(72px, auto) 1fr auto; align-items: center; gap: 7px; padding: 6px 9px; border-radius: 9px; background: var(--dsw-alias-bg-layer-1, #26262e); font-size: 11px; }',
179
+ '.iris-health-name { font-weight: 600; }',
180
+ '.iris-health-state { color: var(--dsw-alias-label-secondary, #9a9a9a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
181
+ '.iris-health-row.failed .iris-health-state { color: #c87a81; }',
182
+ '.iris-health-row.configured .iris-health-state { color: #7da8fa; }',
183
+ '.iris-health-row.verified .iris-health-state { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
101
184
  '.iris-cap-grid { display: grid; gap: 6px; }',
102
185
  '.iris-cap-row { display: flex; align-items: center; flex-wrap: wrap; gap: 5px; background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; padding: 6px 9px; font-size: 12px; }',
103
186
  '.iris-cap-name { font-weight: 600; min-width: 64px; }',
@@ -109,10 +192,12 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
109
192
  '.iris-bubble { position: fixed; z-index: 9999; cursor: grab; user-select: none; touch-action: none; }',
110
193
  '.iris-bubble.dragging { cursor: grabbing; }',
111
194
  '.iris-bubble-btn { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px; background: var(--dsw-alias-bg-layer-2, #32323c); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35); transition: box-shadow 0.2s, border-color 0.2s, opacity 0.2s; }',
112
- '.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); }',
113
- '.iris-bubble.dim .iris-bubble-btn { opacity: 0.55; border-color: var(--dsw-alias-border-l1, #3a3a44); }',
195
+ '.iris-bubble.health-configured .iris-bubble-btn { border-color: #6f9df6; box-shadow: 0 0 14px rgba(111,157,246,.55); }',
196
+ '.iris-bubble.health-verified .iris-bubble-btn { border-color: var(--dsw-alias-state-success-primary, #6fcf6f); box-shadow: 0 0 14px var(--dsw-alias-state-success-primary, #6fcf6f); }',
197
+ '.iris-bubble.health-failed .iris-bubble-btn { opacity: .72; border-color: #b9656d; box-shadow: 0 0 11px rgba(185,101,109,.34); }',
198
+ '.iris-bubble.health-unconfigured .iris-bubble-btn { opacity: 0.55; border-color: var(--dsw-alias-border-l1, #3a3a44); }',
114
199
  '.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; }',
115
- '.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); }',
200
+ '.iris-bubble-panel { position: fixed; z-index: 9998; box-sizing: border-box; width: min(360px, calc(100vw - 16px)); max-height: min(520px, calc(100dvh - 16px)); overflow: auto; overscroll-behavior: contain; 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); }',
116
201
  '.iris-bubble-panel-head { position: absolute; top: 6px; right: 6px; z-index: 2; }',
117
202
  '.iris-bubble-close { border: none; background: transparent; color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 14px; cursor: pointer; padding: 4px 6px; }',
118
203
  '.iris-bubble-close:hover { color: var(--dsw-alias-label-primary, #e6e6e6); }',
@@ -136,6 +221,22 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
136
221
  '.iris-clean-btn:hover { border-color: var(--dsw-alias-brand-primary, #7aa2f7); }',
137
222
  '.iris-clean-btn.danger:hover { color: var(--dsw-alias-state-error-primary, #e06c6c); border-color: var(--dsw-alias-state-error-primary, #e06c6c); }',
138
223
  '.iris-clean-note { font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
224
+ /* 独立作品库 v0:任务清理后仍可浏览,窄屏自动收缩。 */
225
+ '.iris-gallery-tools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }',
226
+ '.iris-gallery-tools .iris-clean-note { flex: 1; min-width: 100px; }',
227
+ '.iris-gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 10px; }',
228
+ '.iris-gallery-card { min-width: 0; overflow: hidden; border: 1px solid rgba(150,164,215,.16); border-radius: 13px; background: color-mix(in srgb, var(--dsw-alias-bg-layer-1, #26262e) 92%, transparent); box-shadow: 0 7px 22px rgba(0,0,0,.12); }',
229
+ '.iris-gallery-preview { display: flex; align-items: center; justify-content: center; width: 100%; aspect-ratio: 1 / 1; overflow: hidden; background: rgba(8,10,16,.28); }',
230
+ '.iris-gallery-preview img, .iris-gallery-preview video { width: 100%; height: 100%; object-fit: contain; }',
231
+ '.iris-gallery-preview audio { width: calc(100% - 16px); }',
232
+ '.iris-gallery-file { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; font-weight: 600; }',
233
+ '.iris-gallery-meta { color: var(--dsw-alias-label-tertiary, #7a7a85); font-size: 10px; }',
234
+ '.iris-gallery-info { display: grid; gap: 4px; padding: 8px 9px 9px; }',
235
+ '.iris-gallery-actions { display: flex; align-items: center; gap: 8px; }',
236
+ '.iris-artifact-mini { display: flex; align-items: center; gap: 7px; min-width: 0; padding: 4px 6px; border-radius: 7px; color: var(--dsw-alias-label-secondary, #9a9a9a); text-decoration: none; }',
237
+ '.iris-artifact-mini:hover { background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); }',
238
+ '.iris-artifact-mini span:nth-child(2) { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
239
+ '@media (max-width: 360px) { .iris-gallery-grid { grid-template-columns: 1fr; } }',
139
240
  /* 模型池(阶段 9 P4) */
140
241
  '.iris-pm-pool { display: grid; gap: 4px; }',
141
242
  '.iris-pm-mrows { display: grid; gap: 3px; max-height: 220px; overflow: auto; }',
@@ -144,8 +245,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
144
245
  '.iris-pm-mtag { font-size: 9px; color: var(--dsw-alias-brand-primary, #7aa2f7); border: 1px solid var(--dsw-alias-brand-primary, #7aa2f7); border-radius: 3px; padding: 0 3px; margin-left: 4px; }',
145
246
  '.iris-pm-mcaps { display: flex; gap: 3px; }',
146
247
  '.iris-pm-ver { font-size: 10px; padding: 0 4px; border-radius: 3px; background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-secondary, #9a9a9a); }',
147
- '.iris-pm-ver.ok { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
148
- '.iris-pm-ver.bad { color: var(--dsw-alias-state-error-primary, #e06c6c); }',
248
+ '.iris-pm-ver.configured { color: #7da8fa; }',
249
+ '.iris-pm-ver.verified { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
250
+ '.iris-pm-ver.failed { color: #c87a81; border-color: rgba(185,101,109,.5); }',
251
+ '.iris-pm-ver.unconfigured { opacity: .55; }',
149
252
  '.iris-pm-madd { flex: 1; background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 4px; padding: 3px 6px; font-size: 12px; }',
150
253
  /* 文件选择器(阶段 10) */
151
254
  '.iris-ff { display: grid; gap: 4px; }',
@@ -222,15 +325,30 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
222
325
  console.log('[iris] client apply start');
223
326
  adoptStyles();
224
327
 
225
- /* ---- 当前会话 id(文件选择器 L1 用;ctx.sessions 是标准插件能力) ---- */
328
+ /* ---- 当前主视图会话(旧 current / 0.2 mainView 持有者;不猜测列表首项) ---- */
226
329
  var currentSessionId = null;
227
330
  try {
228
- var sessions = ctx && ctx.sessions;
331
+ var sessions = ctx && (typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions);
229
332
  var listObs = sessions && sessions.list;
230
333
  if (listObs && typeof listObs.getSnapshot === 'function') {
231
- currentSessionId = (listObs.getSnapshot() || {}).current || null;
334
+ function readCurrentSession() {
335
+ var snap = listObs.getSnapshot() || {};
336
+ if (Object.prototype.hasOwnProperty.call(snap, 'current')) {
337
+ currentSessionId = typeof snap.current === 'string' && snap.current ? snap.current : null;
338
+ return;
339
+ }
340
+ var rows = Object.values(snap.byId || {}).filter(function (row) {
341
+ return row && typeof row.id === 'string' && row.id && row.retainedBy && row.retainedBy.mainView > 0;
342
+ });
343
+ currentSessionId = rows.length === 1 ? rows[0].id : null;
344
+ }
345
+ readCurrentSession();
232
346
  if (typeof listObs.subscribe === 'function') {
233
- listObs.subscribe(function (snap) { currentSessionId = (snap || {}).current || null; });
347
+ // ObservableSnapshot 的变更通知不承诺携带快照,始终回读权威值。
348
+ var unsubscribeSession = listObs.subscribe(readCurrentSession);
349
+ if (typeof unsubscribeSession === 'function' && typeof ctx.effect === 'function') {
350
+ ctx.effect(function () { return unsubscribeSession; });
351
+ }
234
352
  }
235
353
  }
236
354
  } catch (_) { /* 拿不到会话 → L1 退化为只列 iris 产物 */ }
@@ -264,7 +382,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
264
382
  fetch('/iris/api/state')
265
383
  .then(function (res) { return res.ok ? res.json() : null; })
266
384
  .then(function (data) {
267
- if (data) apply(data);
385
+ if (data) {
386
+ apply(data);
387
+ window.dispatchEvent(new CustomEvent('iris-core-refresh-tick'));
388
+ }
268
389
  })
269
390
  .catch(function () { /* 网络抖动静默 */ });
270
391
  }
@@ -294,17 +415,64 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
294
415
  return sharedState;
295
416
  }
296
417
 
297
- /** 功能卡就绪态直接来自共享 Iris 状态,不再等用户点开卡片后才加载。 */
298
- function capabilityReady(state, capability) {
299
- if (!capability) return true;
418
+ var HEALTH_META = {
419
+ unconfigured: { label: '未配置', symbol: '○' },
420
+ configured: { label: '已配置,待验证', symbol: '◌' },
421
+ verified: { label: '近期验证成功', symbol: '●' },
422
+ failed: { label: '认证失败', symbol: '×' }
423
+ };
424
+
425
+ function capabilityHealth(state, capability) {
426
+ var health = state && state.health && state.health.capabilities
427
+ && state.health.capabilities[capability];
428
+ if (health && HEALTH_META[health.status]) return health;
300
429
  var providers = (state && state.providers) || [];
301
- return providers.some(function (p) {
302
- return p.enabled && p.apiKeyHint && Array.isArray(p.capabilities) && p.capabilities.indexOf(capability) >= 0;
430
+ var configured = providers.some(function (p) {
431
+ return p.enabled && p.apiKeyHint && Array.isArray(p.capabilities)
432
+ && p.capabilities.indexOf(capability) >= 0;
303
433
  });
434
+ return { status: configured ? 'configured' : 'unconfigured', candidateCount: configured ? 1 : 0 };
435
+ }
436
+
437
+ function relativeHealthTime(value) {
438
+ var ms = Date.now() - Date.parse(String(value || ''));
439
+ if (!Number.isFinite(ms) || ms < 0) return '';
440
+ if (ms < 60000) return '刚刚';
441
+ if (ms < 3600000) return Math.floor(ms / 60000) + ' 分钟前';
442
+ if (ms < 86400000) return Math.floor(ms / 3600000) + ' 小时前';
443
+ return Math.floor(ms / 86400000) + ' 天前';
444
+ }
445
+
446
+ function healthText(health) {
447
+ var status = health && HEALTH_META[health.status] ? health.status : 'unconfigured';
448
+ var text = HEALTH_META[status].label;
449
+ var age = relativeHealthTime(health && health.observedAt);
450
+ return text + (age ? ' · ' + age : '');
451
+ }
452
+
453
+ function CapabilityHealthOverview() {
454
+ var state = useIrisState(5000);
455
+ var rows = [
456
+ ['image-gen', '图片'],
457
+ ['video-gen', '视频'],
458
+ ['tts', '语音'],
459
+ ['transcribe', '转写'],
460
+ ['vision', '视觉']
461
+ ];
462
+ return React.createElement('div', { className: 'iris-health-grid' }, rows.map(function (row) {
463
+ var health = capabilityHealth(state, row[0]);
464
+ var meta = HEALTH_META[health.status] || HEALTH_META.unconfigured;
465
+ return React.createElement('div', { key: row[0], className: 'iris-health-row ' + health.status },
466
+ React.createElement('span', { className: 'iris-health-name' },
467
+ React.createElement('span', { className: 'iris-pm-dot ' + health.status }, meta.symbol),
468
+ row[1]),
469
+ React.createElement('span', { className: 'iris-health-state', title: healthText(health) }, healthText(health)),
470
+ React.createElement('span', { className: 'iris-wb-muted' }, String(health.candidateCount || 0) + ' 路'));
471
+ }));
304
472
  }
305
473
 
306
474
  /* ---- 任务详情抽屉:按需拉取 /iris/api/task/:id ---- */
307
- function useTaskDetail(taskId) {
475
+ function useTaskDetail(taskId, revision) {
308
476
  var pair = React.useState(null);
309
477
  var setDetail = pair[1];
310
478
  React.useEffect(function () {
@@ -316,7 +484,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
316
484
  .then(function (data) { if (alive && data) setDetail(data); })
317
485
  .catch(function () { /* 静默 */ });
318
486
  return function () { alive = false; };
319
- }, [taskId]);
487
+ }, [taskId, revision]);
320
488
  return pair[0];
321
489
  }
322
490
 
@@ -392,7 +560,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
392
560
  React.createElement('span', { className: 'iris-wb-muted' }, task.createdAt ? new Date(task.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''));
393
561
  }
394
562
 
395
- function TaskRecoveryActions({ detail }) {
563
+ function TaskRecoveryActions({ detail, onDeleted }) {
396
564
  var pair = React.useState('');
397
565
  var note = pair[0];
398
566
  var setNote = pair[1];
@@ -409,13 +577,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
409
577
  var canRedeliver = detail.schemaVersion === 2 && !acknowledged && state === 'artifact_unavailable' && detail.remoteTaskId;
410
578
  var canRetry = detail.schemaVersion === 2 && !acknowledged && ['needs_attention', 'failed', 'canceled'].indexOf(state) >= 0
411
579
  && (detail.cap === 'image' || (detail.cap === 'video' && detail.mode === 't2v'));
412
- if (!canAcknowledge && !canRestore && !canReobserve && !canRedeliver && !canRetry) return null;
580
+ var canDelete = detail.status !== 'running' || (detail.schemaVersion === 2 && attentionState && acknowledged);
581
+ if (!canAcknowledge && !canRestore && !canReobserve && !canRedeliver && !canRetry && !canDelete) return null;
413
582
  function run(action, body, confirmMessage) {
414
583
  if (confirmMessage && !window.confirm(confirmMessage)) return;
415
584
  setBusy(true);
416
585
  setNote('处理中…');
417
586
  postAction(action, body).then(function (r) {
418
587
  setNote(r.ok ? ((r.d && r.d.text) || '操作已接受') : ('失败:' + ((r.d && r.d.error) || '未知错误')));
588
+ if (r.ok && action === 'tasks_delete' && typeof onDeleted === 'function') onDeleted();
419
589
  }).catch(function (error) { setNote('失败:' + String((error && error.message) || error)); })
420
590
  .finally(function () { setBusy(false); });
421
591
  }
@@ -430,12 +600,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
430
600
  onClick: function () { run('task_ack_attention', { task_id: detail.id }); } }, '标为已读') : null,
431
601
  canRestore ? React.createElement('button', { className: 'iris-pm-btn', disabled: busy,
432
602
  onClick: function () { run('task_restore_attention', { task_id: detail.id }); } }, '恢复提醒') : null,
603
+ canDelete ? React.createElement('button', { className: 'iris-pm-btn danger', disabled: busy,
604
+ onClick: function () { run('tasks_delete', { task_id: detail.id },
605
+ attentionState ? '该任务仍含未知远程事实;删除后无法再观察。确认只删除任务记录?' : '确认删除该任务记录?作品文件保留。'); } }, '删除记录') : null,
433
606
  note ? React.createElement('span', { className: 'iris-clean-note' }, note) : null);
434
607
  }
435
608
 
436
609
  /* ---- 任务详情抽屉:点击任务卡片行展开 ---- */
437
- function TaskDetailDrawer({ taskId }) {
438
- var detail = useTaskDetail(taskId);
610
+ function TaskDetailDrawer({ taskId, revision, onDeleted }) {
611
+ var detail = useTaskDetail(taskId, revision);
439
612
  if (!detail) {
440
613
  return React.createElement('div', { className: 'iris-wb-drawer iris-wb-loading' }, '加载任务详情…');
441
614
  }
@@ -444,7 +617,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
444
617
  el.push(React.createElement('div', { key: 'p', className: 'iris-wb-prompt' }, detail.prompt || '(空)'));
445
618
  if (Number(detail.schemaVersion) === 1) {
446
619
  el.push(React.createElement('div', { key: 'legacy', className: 'iris-clean-note' },
447
- '旧版任务记录 · 仅支持查看。由于缺少可靠的远端受理与交付事实,不提供重新观察、重新交付或知情重试;0.1.3 新任务将使用可信任务语义。'));
620
+ '旧版任务记录 · 仅支持查看恢复事实。由于缺少可靠的远端受理与交付事实,不提供重新观察、重新交付或知情重试;已终止记录仍可删除。'));
448
621
  }
449
622
  var dispositionLabel = attentionDispositionLabel(detail);
450
623
  if (dispositionLabel) {
@@ -501,7 +674,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
501
674
  return React.createElement('span', { key: a.attachmentId, className: 'iris-wb-kv' }, a.attachmentId + (a.file ? ' (' + a.file + ')' : ''));
502
675
  })));
503
676
  }
504
- el.push(React.createElement(TaskRecoveryActions, { key: 'recovery', detail: detail }));
677
+ el.push(React.createElement(TaskRecoveryActions, { key: 'recovery', detail: detail, onDeleted: onDeleted }));
505
678
  return React.createElement('div', { className: 'iris-wb-drawer' }, ...el);
506
679
  }
507
680
 
@@ -547,7 +720,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
547
720
  task.createdAt ? '发起 ' + fmtTime(task.createdAt) : '',
548
721
  task.finishedAt ? ' · 完成 ' + fmtTime(task.finishedAt) : ''));
549
722
  }
550
- if (selected) body.push(React.createElement(TaskDetailDrawer, { key: 'drawer', taskId: task.id }));
723
+ if (selected) body.push(React.createElement(TaskDetailDrawer, { key: 'drawer', taskId: task.id, revision: task.revision, onDeleted: function () { onSelect(null); } }));
551
724
  return React.createElement('div', {
552
725
  key: task.id,
553
726
  className: 'iris-wb-card' + (isRunning ? ' iris-running' : '') + (selected ? ' selected' : ''),
@@ -571,10 +744,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
571
744
  var setNewId = addPair[1];
572
745
  var models = p.models || [];
573
746
  function verBadge(m, c) {
574
- var v = m.verified && m.verified[c];
575
- var cls = 'iris-pm-ver' + (v ? (' ' + (v.ok ? 'ok' : 'bad')) : '');
576
- var title = v ? ((v.ok ? '实测通过:' : '实测未通过:') + (v.note || '')) : '点击单独验证此能力';
577
- return React.createElement('button', { key: c, className: cls, title: title, onClick: function () { props.testModel(p, m, c); } }, c + (v ? (v.ok ? '✓' : '✗') : '?'));
747
+ var health = m.health && m.health[c];
748
+ var status = health && HEALTH_META[health.status] ? health.status : 'configured';
749
+ var cls = 'iris-pm-ver ' + status;
750
+ var title = healthText(health || { status: status });
751
+ var symbol = status === 'verified' ? '✓' : (status === 'failed' ? '×' : (status === 'unconfigured' ? '○' : '?'));
752
+ return React.createElement('button', {
753
+ key: c,
754
+ className: cls,
755
+ title: title + ';点击单独验证此能力',
756
+ onClick: function () { props.testModel(p, m, c); }
757
+ }, c + symbol);
578
758
  }
579
759
  var rows = models.map(function (m) {
580
760
  var caps = m.capabilities || [];
@@ -591,6 +771,26 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
591
771
  React.createElement('button', { className: 'iris-pm-btn', onClick: function () { props.addModel(p, newId.trim()); setNewId(''); } }, '+ 添加')));
592
772
  }
593
773
 
774
+ function ProviderEndpointEditor(props) {
775
+ var p = props.provider;
776
+ var valuePair = React.useState(p.mediaBaseUrl || '');
777
+ var value = valuePair[0];
778
+ var setValue = valuePair[1];
779
+ React.useEffect(function () { setValue(p.mediaBaseUrl || ''); }, [p.mediaBaseUrl]);
780
+ function save() {
781
+ postAction('providers_upsert', { id: p.id, mediaBaseUrl: value.trim() }).then(function (r) {
782
+ props.onNote(r.ok ? '媒体 Base URL 已更新' : (r.d.error || '更新失败'));
783
+ if (r.ok) props.onDone();
784
+ });
785
+ }
786
+ return React.createElement('div', { className: 'iris-pm-field' },
787
+ React.createElement('label', {}, '媒体 Base URL(可选)'),
788
+ React.createElement('div', { className: 'iris-wb-row' },
789
+ React.createElement('input', { value: value, placeholder: '留空则沿用 Base URL', onChange: function (e) { setValue(e.target.value); } }),
790
+ React.createElement('button', { className: 'iris-pm-btn', onClick: save }, '保存')),
791
+ React.createElement('span', { className: 'iris-pm-note' }, '百炼 Workspace 可填 …maas.aliyuncs.com/compatible-mode/v1;视觉仍使用普通 Base URL。'));
792
+ }
793
+
594
794
  function ProviderManager() {
595
795
  var listPair = React.useState(null);
596
796
  var list = listPair[0];
@@ -600,7 +800,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
600
800
  var addingPair = React.useState(false);
601
801
  var adding = addingPair[0];
602
802
  var setAdding = addingPair[1];
603
- var newForm = React.useState({ name: '', baseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
803
+ var newForm = React.useState({ name: '', baseUrl: '', mediaBaseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
604
804
  var notePair = React.useState('');
605
805
  var note = notePair[0];
606
806
  var setNote = notePair[1];
@@ -661,7 +861,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
661
861
  if (open) {
662
862
  body = React.createElement('div', { className: 'iris-pm-body' },
663
863
  React.createElement('div', { className: 'iris-pm-note' }, 'Key ' + (p.apiKeyHint || '未配置') + ' · ' + (p.type || '') + ' · ' + (p.mediaProtocol || '')),
664
- React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体协议'), React.createElement('select', { value: p.mediaProtocol || 'openai-images', onChange: function (e) { setProtocol(p, e.target.value); } },
864
+ p.protocolInferred ? React.createElement('div', { className: 'iris-pm-note' }, '协议为推断值,请确认') : null,
865
+ React.createElement(ProviderEndpointEditor, { provider: p, onNote: setNote, onDone: refresh }),
866
+ React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体协议'), React.createElement('select', { value: p.protocolInferred ? 'auto' : (p.mediaProtocol || 'auto'), onChange: function (e) { setProtocol(p, e.target.value); } },
867
+ React.createElement('option', { value: 'auto' }, '自动'),
868
+ p.mediaProtocol && !['dashscope', 'openai-images', 'auto'].includes(p.mediaProtocol) ? React.createElement('option', { value: p.mediaProtocol }, p.mediaProtocol + '(未支持)') : null,
665
869
  React.createElement('option', { value: 'dashscope' }, 'DashScope(仅阿里云官方 HTTPS)'),
666
870
  React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'))),
667
871
  React.createElement('div', { className: 'iris-pm-note' }, '能力: ' + ((p.capabilities || []).join(' / ') || '无')),
@@ -682,9 +886,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
682
886
  addBlock = React.createElement('div', { className: 'iris-pm-card' },
683
887
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '名称'), React.createElement('input', { value: newForm[0].name, placeholder: '如 阿里云百炼', onChange: function (e) { newForm[1]({ ...newForm[0], name: e.target.value }); } })),
684
888
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, 'Base URL *'), React.createElement('input', { value: newForm[0].baseUrl, placeholder: 'https://dashscope.aliyuncs.com/compatible-mode/v1', onChange: function (e) { newForm[1]({ ...newForm[0], baseUrl: e.target.value }); } })),
889
+ React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体 Base URL(可选)'), React.createElement('input', { value: newForm[0].mediaBaseUrl, placeholder: 'Workspace 媒体端点;留空沿用 Base URL', onChange: function (e) { newForm[1]({ ...newForm[0], mediaBaseUrl: e.target.value }); } })),
685
890
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, 'API Key *'), React.createElement('input', { type: 'password', value: newForm[0].apiKey, placeholder: 'sk-...', onChange: function (e) { newForm[1]({ ...newForm[0], apiKey: e.target.value }); } })),
686
891
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体协议'), React.createElement('select', { value: newForm[0].mediaProtocol, onChange: function (e) { newForm[1]({ ...newForm[0], mediaProtocol: e.target.value }); } },
687
- React.createElement('option', { value: 'auto' }, '自动(按 Base URL 安全判断)'),
892
+ React.createElement('option', { value: 'auto' }, '自动(按媒体 Base URL 安全判断)'),
688
893
  React.createElement('option', { value: 'dashscope' }, 'DashScope(阿里云官方)'),
689
894
  React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'))),
690
895
  React.createElement('div', { className: 'iris-wb-row' },
@@ -808,9 +1013,13 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
808
1013
  body: JSON.stringify(vals)
809
1014
  })
810
1015
  .then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
811
- .then(function (res) { setRun(res.ok ? { ok: true, text: res.d.text || '', img: res.d.imageDataUrl || null } : { ok: false, text: res.d.error || '执行失败' }); })
1016
+ .then(function (res) {
1017
+ setRun(res.ok ? { ok: true, text: res.d.text || '', img: res.d.imageUrl || res.d.imageDataUrl || null } : { ok: false, text: res.d.error || '执行失败' });
1018
+ if (res.ok && res.d && res.d.storage === 'core') window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
1019
+ })
812
1020
  .catch(function () { setRun({ ok: false, text: '网络错误' }); });
813
1021
  }
1022
+ var cardHealth = capability ? capabilityHealth(irisState, capability) : { status: 'verified' };
814
1023
  var body = null;
815
1024
  if (open) {
816
1025
  var inputs = fields.map(function (f) {
@@ -846,16 +1055,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
846
1055
  result = React.createElement('div', { className: 'iris-act-result' + (run.ok ? '' : ' err') }, run.text);
847
1056
  }
848
1057
  }
849
- // 只读模型提示:当前能力解析到的模型;改分配去「能力分配」区(唯一写入口)
1058
+ // 只读模型提示:当前能力解析到的模型与持久健康事实。
850
1059
  var modelBar = null;
851
1060
  if (capability) {
852
- var lit = modelInfo && modelInfo.loaded && modelInfo.options.length > 0;
1061
+ var available = modelInfo && modelInfo.loaded && modelInfo.options.length > 0;
853
1062
  var shown = (vals.model && vals.model.trim()) || (modelInfo && modelInfo.assigned) || '';
854
1063
  modelBar = React.createElement('div', { className: 'iris-act-field' },
855
1064
  React.createElement('label', {},
856
- React.createElement('span', { className: lit ? 'iris-pm-dot lit' : 'iris-pm-dot' }, '●'),
857
- ' 模型:' + (shown || '自动') + (lit ? '' : '(无可用模型)')),
858
- React.createElement('div', { className: 'iris-wb-muted' }, '改分配 → 上方「能力分配(failover 顺序)」'));
1065
+ React.createElement('span', { className: 'iris-pm-dot ' + cardHealth.status }, HEALTH_META[cardHealth.status].symbol),
1066
+ ' 模型:' + (shown || '自动') + (available ? '' : '(无可用模型)')),
1067
+ React.createElement('div', { className: 'iris-wb-muted' },
1068
+ healthText(cardHealth) + ' · 改分配 → 上方「能力分配(failover 顺序)」'));
859
1069
  }
860
1070
  body = React.createElement('div', { className: 'iris-act-body' },
861
1071
  modelBar,
@@ -864,13 +1074,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
864
1074
  run === 'running' ? '执行中…' : '执行'),
865
1075
  result);
866
1076
  }
867
- var headError = !!(run && run !== 'running' && run.ok === false);
868
- var headLit = !capability || (capabilityReady(irisState, capability) && !headError);
869
- var headTitle = headError ? '最近一次执行出错' : (headLit ? 'API 与能力已配置' : '缺少可用 API 或能力模型');
870
- return React.createElement('div', { className: 'iris-act-card' + (open ? ' open' : '') + (headLit ? ' lit' : ' dim') },
1077
+ var headStatus = capability ? cardHealth.status : 'verified';
1078
+ var headTitle = capability ? healthText(cardHealth) : '本地功能';
1079
+ return React.createElement('div', {
1080
+ className: 'iris-act-card' + (open ? ' open' : '') + (capability ? ' health-' + headStatus : '')
1081
+ },
871
1082
  React.createElement('div', { className: 'iris-act-head', onClick: function () { setOpen(!open); } },
872
1083
  React.createElement('span', { className: 'iris-act-title' }, title),
873
- capability ? React.createElement('span', { className: headLit ? 'iris-pm-dot lit' : 'iris-pm-dot', title: headTitle }, '●') : null,
1084
+ capability ? React.createElement('span', {
1085
+ className: 'iris-pm-dot ' + headStatus,
1086
+ title: headTitle
1087
+ }, HEALTH_META[headStatus].symbol) : null,
874
1088
  React.createElement('span', { className: 'iris-act-arrow' }, '▶')),
875
1089
  body);
876
1090
  }
@@ -1031,14 +1245,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1031
1245
  /* ---- 历史浏览器(设置页:按今天/昨天/更早分组折叠) ---- */
1032
1246
  function HistoryBrowser(props) {
1033
1247
  var recent = props.recent || [];
1248
+ var coreHistory = props.coreHistory || [];
1034
1249
  var selected = props.selected;
1035
1250
  var onSelect = props.onSelect;
1036
1251
  var collapsedPair = React.useState({});
1037
1252
  var collapsed = collapsedPair[0];
1038
1253
  var setCollapsed = collapsedPair[1];
1039
- if (!recent.length) return React.createElement('div', { className: 'iris-wb-empty' }, '尚无任务记录');
1254
+ if (!recent.length && !coreHistory.length) return React.createElement('div', { className: 'iris-wb-empty' }, '尚无任务记录');
1040
1255
  var buckets = { 今天: [], 昨天: [], 更早: [] };
1041
- recent.forEach(function (t) { buckets[dayBucket(t.createdAt)].push(t); });
1256
+ recent.forEach(function (t) { buckets[dayBucket(t.createdAt)].push({ legacy: t }); });
1257
+ coreHistory.forEach(function (row) { buckets[dayBucket(row.updatedAt)].push({ core: row }); });
1042
1258
  var order = ['今天', '昨天', '更早'];
1043
1259
  return React.createElement('div', {}, order.map(function (name) {
1044
1260
  var rows = buckets[name];
@@ -1050,10 +1266,689 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1050
1266
  onClick: function () { var n = {}; Object.keys(collapsed).forEach(function (k) { n[k] = collapsed[k]; }); n[name] = !isCollapsed; setCollapsed(n); }
1051
1267
  }, React.createElement('span', {}, isCollapsed ? '▶' : '▼'), name + ' · ' + rows.length),
1052
1268
  isCollapsed ? null : React.createElement('div', { className: 'iris-wb-box' },
1053
- rows.map(function (t) { return taskRow(t, false, selected === t.id, onSelect); })));
1269
+ rows.map(function (entry) {
1270
+ return entry.core
1271
+ ? coreTaskCard(entry.core)
1272
+ : taskRow(entry.legacy, false, selected === entry.legacy.id, onSelect);
1273
+ })));
1054
1274
  }));
1055
1275
  }
1056
1276
 
1277
+ function fmtBytes(bytes) {
1278
+ var value = Number(bytes || 0);
1279
+ if (value < 1024) return value + 'B';
1280
+ if (value < 1048576) return (value / 1024).toFixed(0) + 'KB';
1281
+ return (value / 1048576).toFixed(1) + 'MB';
1282
+ }
1283
+
1284
+ function artifactIcon(item) {
1285
+ var mime = String(item && item.mime || '');
1286
+ if (mime.indexOf('image/') === 0) return '🖼';
1287
+ if (mime.indexOf('video/') === 0) return '🎬';
1288
+ if (mime.indexOf('audio/') === 0) return '🔊';
1289
+ return '📄';
1290
+ }
1291
+
1292
+ function artifactRowMini(item) {
1293
+ return React.createElement('a', { key: item.id, className: 'iris-artifact-mini', href: item.url, target: '_blank', rel: 'noreferrer', title: '打开作品' },
1294
+ React.createElement('span', {}, artifactIcon(item)),
1295
+ React.createElement('span', {}, item.file),
1296
+ React.createElement('span', { className: 'iris-gallery-meta' }, fmtBytes(item.size)));
1297
+ }
1298
+
1299
+ function artifactPreview(item) {
1300
+ var mime = String(item.mime || '');
1301
+ if (mime.indexOf('image/') === 0) {
1302
+ return React.createElement('a', { className: 'iris-gallery-preview', href: item.url, target: '_blank', rel: 'noreferrer', title: '打开原图' },
1303
+ React.createElement('img', {
1304
+ src: item.url, alt: 'Iris 作品 ' + item.file, loading: 'lazy',
1305
+ // 媒体文件丢失(404/损坏)时局部降级为占位提示,作品区其余项不受影响
1306
+ onError: function (event) {
1307
+ var img = event && event.target;
1308
+ var box = img && img.parentNode;
1309
+ if (!box || box.querySelector('span')) return;
1310
+ img.style.display = 'none';
1311
+ var note = document.createElement('span');
1312
+ note.className = 'iris-gallery-meta';
1313
+ note.textContent = '作品文件暂时不可用';
1314
+ box.appendChild(note);
1315
+ }
1316
+ }));
1317
+ }
1318
+ if (mime.indexOf('video/') === 0) {
1319
+ return React.createElement('div', { className: 'iris-gallery-preview' },
1320
+ React.createElement('video', { src: item.url, controls: true, playsInline: true, preload: 'metadata' }));
1321
+ }
1322
+ if (mime.indexOf('audio/') === 0) {
1323
+ return React.createElement('div', { className: 'iris-gallery-preview' },
1324
+ React.createElement('audio', { src: item.url, controls: true, preload: 'metadata' }));
1325
+ }
1326
+ return React.createElement('a', { className: 'iris-gallery-preview iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开文件');
1327
+ }
1328
+
1329
+ function coreGalleryItems(data, limit) {
1330
+ return (((data && data.artifacts && data.artifacts.recent) || []).filter(function (artifact) {
1331
+ return artifact.kind !== 'host-input' && String(artifact.mediaType || '').indexOf('image/') === 0;
1332
+ }).map(function (artifact) {
1333
+ return {
1334
+ id: artifact.id, file: artifact.id, mime: artifact.mediaType,
1335
+ size: artifact.size, createdAt: artifact.createdAt || '', source: 'core',
1336
+ kind: artifact.kind,
1337
+ digest: artifact.digest && artifact.digest.value ? artifact.digest.value.slice(0, 12) : '',
1338
+ url: '/iris/api/core/artifact/' + encodeURIComponent(artifact.id) + '/media'
1339
+ };
1340
+ })).slice(0, limit || 200);
1341
+ }
1342
+
1343
+ function useCoreWorks(limit) {
1344
+ var pair = React.useState([]);
1345
+ var setItems = pair[1];
1346
+ React.useEffect(function () {
1347
+ var alive = true;
1348
+ function load() {
1349
+ fetch('/iris/api/core/snapshot?limit=' + Math.max(1, Math.min(200, Number(limit) || 6)))
1350
+ .then(function (res) { return res.ok ? res.json() : null; })
1351
+ .then(function (data) { if (alive && data) setItems(coreGalleryItems(data, limit)); })
1352
+ .catch(function () { /* Core 不可用不影响旧作品 */ });
1353
+ }
1354
+ function changed() { load(); }
1355
+ load();
1356
+ window.addEventListener('iris-core-artifacts-changed', changed);
1357
+ window.addEventListener('iris-core-refresh-tick', changed);
1358
+ return function () {
1359
+ alive = false;
1360
+ window.removeEventListener('iris-core-artifacts-changed', changed);
1361
+ window.removeEventListener('iris-core-refresh-tick', changed);
1362
+ };
1363
+ }, [limit]);
1364
+ return pair[0];
1365
+ }
1366
+
1367
+ function ArtifactGallery(props) {
1368
+ var itemsPair = React.useState([]);
1369
+ var items = itemsPair[0];
1370
+ var setItems = itemsPair[1];
1371
+ var totalPair = React.useState(Number(props.total || 0));
1372
+ var total = totalPair[0];
1373
+ var setTotal = totalPair[1];
1374
+ var corePair = React.useState([]);
1375
+ var coreItems = corePair[0];
1376
+ var setCoreItems = corePair[1];
1377
+ var busyPair = React.useState(false);
1378
+ var busy = busyPair[0];
1379
+ var setBusy = busyPair[1];
1380
+ var notePair = React.useState('');
1381
+ var note = notePair[0];
1382
+ var setNote = notePair[1];
1383
+
1384
+ function load(append) {
1385
+ if (busy) return;
1386
+ setBusy(true);
1387
+ var offset = append ? items.length : 0;
1388
+ var legacyRequest = fetch('/iris/api/artifacts?offset=' + offset + '&limit=24')
1389
+ .then(function (res) { return res.ok ? res.json() : Promise.reject(new Error('旧版作品读取失败')); });
1390
+ var coreRequest = append ? Promise.resolve(null) : fetch('/iris/api/core/snapshot?limit=200')
1391
+ .then(function (res) {
1392
+ if (res.status === 404) return { available: false, backendReloadRequired: true, artifacts: { recent: [] } };
1393
+ return res.ok ? res.json() : Promise.reject(new Error('Core 作品读取失败(HTTP ' + res.status + ')'));
1394
+ }).catch(function (error) { return { available: false, error: error.message, artifacts: { recent: [] } }; });
1395
+ Promise.all([legacyRequest, coreRequest])
1396
+ .then(function (values) {
1397
+ var data = values[0];
1398
+ var core = values[1];
1399
+ setItems(append ? items.concat(data.items || []) : (data.items || []));
1400
+ setTotal(Number(data.total || 0));
1401
+ if (core) {
1402
+ var nextCore = coreGalleryItems(core, 200);
1403
+ setCoreItems(nextCore);
1404
+ if (core.error) setNote(core.error);
1405
+ else if (core.backendReloadRequired) setNote('DSH 后端尚未加载 Core 路由;重启后新作品会出现在这里。');
1406
+ }
1407
+ })
1408
+ .catch(function (error) { setNote('作品库读取失败:' + error.message); })
1409
+ .finally(function () { setBusy(false); });
1410
+ }
1411
+
1412
+ function run(action, body, confirmText) {
1413
+ if (confirmText && !window.confirm(confirmText)) return;
1414
+ setBusy(true);
1415
+ postAction(action, body || {}).then(function (result) {
1416
+ var succeeded = result.ok && result.d && result.d.ok !== false;
1417
+ setNote(succeeded ? (result.d.text || '完成') : ('失败:' + ((result.d && (result.d.error || result.d.text)) || '未知错误')));
1418
+ if (succeeded) load(false);
1419
+ else setBusy(false);
1420
+ }).catch(function (error) { setBusy(false); setNote('失败:' + error.message); });
1421
+ }
1422
+
1423
+ React.useEffect(function () {
1424
+ function changed() { load(false); }
1425
+ window.addEventListener('iris-core-artifacts-changed', changed);
1426
+ window.addEventListener('iris-core-refresh-tick', changed);
1427
+ load(false);
1428
+ return function () {
1429
+ window.removeEventListener('iris-core-artifacts-changed', changed);
1430
+ window.removeEventListener('iris-core-refresh-tick', changed);
1431
+ };
1432
+ }, [props.total]);
1433
+ var galleryItems = coreItems.concat(items).sort(function (a, b) {
1434
+ return String(b.createdAt || '').localeCompare(String(a.createdAt || ''));
1435
+ });
1436
+ var combinedTotal = coreItems.length + total;
1437
+
1438
+ return React.createElement('div', {},
1439
+ React.createElement('div', { className: 'iris-gallery-tools' },
1440
+ React.createElement('span', { className: 'iris-clean-note' },
1441
+ '共 ' + combinedTotal + ' 个作品 · Core ' + coreItems.length + ' · 旧版 ' + total + ' · 清任务历史不会删除'),
1442
+ React.createElement('button', { className: 'iris-clean-btn', disabled: busy, onClick: function () { load(false); } }, '刷新'),
1443
+ React.createElement('button', { className: 'iris-clean-btn', disabled: busy, onClick: function () { run('artifacts_reindex'); } }, '扫描旧版 outputs'),
1444
+ total ? React.createElement('button', { className: 'iris-clean-btn danger', disabled: busy, onClick: function () { run('artifacts_clear', {}, '永久删除旧版 outputs 作品?Core 作品不会删除;任务历史会保留。此操作不可逆。'); } }, '清空旧版作品') : null),
1445
+ galleryItems.length ? React.createElement('div', { className: 'iris-gallery-grid' }, galleryItems.map(function (item) {
1446
+ var isCore = item.source === 'core';
1447
+ return React.createElement('div', { key: (isCore ? 'core-' : 'legacy-') + item.id, className: 'iris-gallery-card' },
1448
+ artifactPreview(item),
1449
+ React.createElement('div', { className: 'iris-gallery-info' },
1450
+ React.createElement('div', { className: 'iris-gallery-file', title: item.file }, item.file),
1451
+ React.createElement('div', { className: 'iris-gallery-meta' },
1452
+ (isCore ? 'Core · ' + (item.kind || 'artifact') + (item.digest ? ' · sha256 ' + item.digest : '') : fmtTime(item.createdAt)) + ' · ' + fmtBytes(item.size)),
1453
+ React.createElement('div', { className: 'iris-gallery-actions' },
1454
+ React.createElement('a', { className: 'iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开'),
1455
+ isCore ? React.createElement('span', { className: 'iris-wb-muted' }, '内容哈希保护')
1456
+ : React.createElement('button', { className: 'iris-pm-btn danger', disabled: busy, onClick: function () { run('artifacts_delete', { artifact_id: item.id }, '永久删除作品“' + item.file + '”?任务历史会保留,但媒体将无法打开。'); } }, '删除'))));
1457
+ })) : React.createElement('div', { className: 'iris-wb-empty' }, busy ? '正在读取作品…' : '尚无作品;旧 outputs 文件可点“扫描旧版 outputs”找回。'),
1458
+ items.length < total ? React.createElement('button', { className: 'iris-clean-btn', disabled: busy, onClick: function () { load(true); } }, busy ? '加载中…' : '加载更多旧版作品') : null,
1459
+ note ? React.createElement('div', { className: 'iris-clean-note', style: { marginTop: '7px' } }, note) : null);
1460
+ }
1461
+
1462
+ function coreCapabilityLabel(value) {
1463
+ return ({ image: '图片生成', video: '视频生成', tts: '语音合成', transcribe: '音频转写' })[value] || String(value || '媒体任务');
1464
+ }
1465
+
1466
+ /* 高级诊断与用户任务区共用五类用户状态投影;与服务端 lib/core-user-projection.js 保持同一映射,
1467
+ 文案必须区分“远端可能还在运行”和“已经明确失败”。 */
1468
+ function coreTaskState(task) {
1469
+ if (task.outcome === 'succeeded' && task.deliveryState === 'ready') return { tone: 'ok', state: 'succeeded', label: '已完成,作品可用' };
1470
+ if (task.outcome === 'succeeded' && task.deliveryState === 'failed') return { tone: 'error', state: 'delivery_failed', label: '已生成,作品取回失败' };
1471
+ if (task.outcome === 'succeeded') return { tone: 'warn', state: 'running', label: '已生成,正在保存作品' };
1472
+ if (task.outcome === 'failed') return { tone: 'error', state: 'attention', label: '已失败' };
1473
+ if (task.outcome === 'canceled') return { tone: 'idle', state: 'attention', label: '已取消' };
1474
+ if (task.outcome === 'unknown') return { tone: 'warn', state: 'attention', label: '任务结果未知,需要检查' };
1475
+ if (task.acceptance === 'unknown') return { tone: 'warn', state: 'attention', label: '提交结果未知,请检查后处理' };
1476
+ if (task.deliveryState === 'failed') return { tone: 'error', state: 'attention', label: '结果状态异常,需要检查' };
1477
+ if (task.watchState === 'suspended' || task.watchState === 'exhausted') return { tone: 'warn', state: 'observation_paused', label: '观察已暂停,远端可能仍在运行' };
1478
+ return { tone: 'idle', state: 'running', label: '运行中' };
1479
+ }
1480
+
1481
+ function coreAttemptState(attempt) {
1482
+ if (attempt.resultKind === 'completed') return '已完成';
1483
+ if (attempt.acceptance === 'not_accepted') return '未受理';
1484
+ if (attempt.acceptance === 'unknown') return '是否受理未知';
1485
+ if (attempt.acceptance === 'accepted') return '已受理';
1486
+ return '正在提交';
1487
+ }
1488
+
1489
+ function coreModelLabel(value) {
1490
+ var model = String(value || '');
1491
+ var separator = model.indexOf('::');
1492
+ return separator >= 0 ? model.slice(separator + 2) : (model || '未记录');
1493
+ }
1494
+
1495
+ /* 诊断层允许更多事实:取消三态如实区分,不把"已请求/无法确认"显示成"已取消"。 */
1496
+ function coreCancelLabel(value) {
1497
+ if (value === 'remote_confirmed') return '供应商已明确确认取消';
1498
+ if (value === 'local_confirmed') return '本地已确认取消(远端未确认)';
1499
+ if (value === 'requested') return '取消请求进行中';
1500
+ if (value === 'unknown') return '已请求取消,远端结果未确认;可显式重新观察';
1501
+ return '无取消请求';
1502
+ }
1503
+
1504
+ /* ---- Core 用户侧只读投影:任务区消费 /iris/api/core/snapshot 的 userTasks 安全 DTO ---- */
1505
+ var EMPTY_CORE_USER = Object.freeze({ rows: [], available: false, degraded: false, droppedTasks: 0 });
1506
+
1507
+ function useCoreUserTasks() {
1508
+ var pair = React.useState(EMPTY_CORE_USER);
1509
+ var setData = pair[1];
1510
+ React.useEffect(function () {
1511
+ var alive = true;
1512
+ function load() {
1513
+ fetch('/iris/api/core/snapshot?limit=200')
1514
+ .then(function (res) { return res.ok ? res.json() : null; })
1515
+ .then(function (data) {
1516
+ if (!alive || !data || !Array.isArray(data.userTasks)) return;
1517
+ setData({
1518
+ rows: data.userTasks,
1519
+ available: !!data.available,
1520
+ degraded: !!data.degraded,
1521
+ droppedTasks: Number(data.droppedTasks || 0)
1522
+ });
1523
+ })
1524
+ .catch(function () { /* Core 不可用不阻塞 legacy 任务区 */ });
1525
+ }
1526
+ function changed() { load(); }
1527
+ load();
1528
+ window.addEventListener('iris-core-artifacts-changed', changed);
1529
+ window.addEventListener('iris-core-refresh-tick', changed);
1530
+ return function () {
1531
+ alive = false;
1532
+ window.removeEventListener('iris-core-artifacts-changed', changed);
1533
+ window.removeEventListener('iris-core-refresh-tick', changed);
1534
+ };
1535
+ }, []);
1536
+ return pair[0];
1537
+ }
1538
+
1539
+ /* 五类用户状态 → 既有 badge 色调;不新增第三套卡片样式,复用 iris-wb/iris-task-mini。 */
1540
+ function coreUserStateClass(state) {
1541
+ if (state === 'succeeded') return 'ok';
1542
+ if (state === 'delivery_failed' || state === 'attention') return 'err';
1543
+ if (state === 'observation_paused') return 'warn';
1544
+ return '';
1545
+ }
1546
+
1547
+ function coreTaskBadge(row) {
1548
+ var tone = row.historical === true ? '' : coreUserStateClass(row.userState);
1549
+ return React.createElement('span', { className: 'iris-wb-badge' + (tone ? ' ' + tone : '') }, row.label);
1550
+ }
1551
+
1552
+ /* 完成提示只出现一次:会话内 Map 记录 core:id@revision,刷新页面后从事实重新计算基线。 */
1553
+ var coreCompletionSeen = null; // null = 尚未建立基线(首屏把既有完成项记为已见)
1554
+ function coreRowKey(row) { return 'core:' + row.id + '@' + Number(row.revision || 0); }
1555
+ function noteCoreCompletions(rows) {
1556
+ var current = {};
1557
+ for (var i = 0; i < rows.length; i++) {
1558
+ if (rows[i].userState === 'succeeded') current[coreRowKey(rows[i])] = true;
1559
+ }
1560
+ if (coreCompletionSeen === null) { coreCompletionSeen = current; return []; }
1561
+ var added = [];
1562
+ for (var j = 0; j < rows.length; j++) {
1563
+ var row = rows[j];
1564
+ if (row.userState === 'succeeded' && !coreCompletionSeen[coreRowKey(row)]) added.push(row);
1565
+ }
1566
+ for (var key in current) coreCompletionSeen[key] = true;
1567
+ return added;
1568
+ }
1569
+
1570
+ /* D1/D2 人工动作按钮:投影门满足才出现;每次点击只调用一次显式单步 API,绝不
1571
+ 重新提交、也不间接重新生成;点击期间按钮禁用防重复,完成后复用
1572
+ iris-core-refresh-tick 让任务区/高级诊断重拉同一快照。服务端 Command 门仍
1573
+ 是最终裁决(binding/能力校验失败返回稳定错误,行内提示,不改任务事实)。 */
1574
+ function CoreTaskManualButton(props) {
1575
+ var busyPair = React.useState(false);
1576
+ var busy = busyPair[0];
1577
+ var setBusy = busyPair[1];
1578
+ var notePair = React.useState('');
1579
+ var note = notePair[0];
1580
+ var setNote = notePair[1];
1581
+ function act() {
1582
+ if (busy) return; // 防重复触发:请求未结束前后端各拦一层
1583
+ var body = props.buildBody ? props.buildBody() : {};
1584
+ if (body === null) return; // 用户取消输入(如重试时放弃重新输入 prompt)
1585
+ if (props.confirm && !window.confirm(props.confirm)) return; // 改变远端事实/计费的动作需要二次确认
1586
+ setBusy(true);
1587
+ setNote('');
1588
+ fetch('/iris/api/core/task/' + encodeURIComponent(props.id) + '/' + props.action, {
1589
+ method: 'POST',
1590
+ headers: { 'Content-Type': 'application/json' },
1591
+ body: JSON.stringify(body)
1592
+ }).then(function (res) {
1593
+ return res.json().then(function (data) { return { status: res.status, data: data }; })
1594
+ .catch(function () { return { status: res.status, data: null }; });
1595
+ }).then(function (result) {
1596
+ var detail = result.data && result.data.error;
1597
+ if (result.status !== 200) {
1598
+ setNote(props.failPrefix + '被拒绝:' + (detail && detail.message || '状态 ' + result.status));
1599
+ return;
1600
+ }
1601
+ var created = result.data && result.data.task;
1602
+ if (props.createdPrefix && created && created.id && created.id !== props.id) {
1603
+ setNote(props.createdPrefix + created.id);
1604
+ }
1605
+ window.dispatchEvent(new CustomEvent('iris-core-refresh-tick'));
1606
+ }).catch(function () {
1607
+ setNote(props.failPrefix + '请求失败;任务事实未改动');
1608
+ }).finally(function () { setBusy(false); });
1609
+ }
1610
+ return React.createElement('span', { className: 'iris-wb-row', style: { alignItems: 'center' } },
1611
+ React.createElement('button', {
1612
+ type: 'button', className: 'iris-pm-btn', disabled: busy,
1613
+ title: props.title,
1614
+ onClick: act
1615
+ }, busy ? props.busyLabel : props.idleLabel),
1616
+ note ? React.createElement('span', { className: 'iris-wb-err' }, note) : null);
1617
+ }
1618
+
1619
+ /* D1 reobserve:显式单步观察入口,投影标记 observable 才出现。 */
1620
+ function CoreReobserveButton(props) {
1621
+ if (!props.row || props.row.observable !== true) return null;
1622
+ return React.createElement(CoreTaskManualButton, {
1623
+ id: props.row.id, action: 'reobserve',
1624
+ idleLabel: '重新观察', busyLabel: '观察中…', failPrefix: '重新观察',
1625
+ title: '只向供应商查询一次该任务的最新状态,绝不重新提交'
1626
+ });
1627
+ }
1628
+
1629
+ /* D2 redeliver:delivery_failed 的唯一恢复入口;重新下载已生成的作品,不重新生成。 */
1630
+ function CoreRedeliverButton(props) {
1631
+ if (!props.row || props.row.redeliverable !== true) return null;
1632
+ return React.createElement(CoreTaskManualButton, {
1633
+ id: props.row.id, action: 'redeliver',
1634
+ idleLabel: '重新取回作品', busyLabel: '取回中…', failPrefix: '重新取回作品',
1635
+ title: '重新下载已生成的作品,不会再次生成,也不会增加生成费用'
1636
+ });
1637
+ }
1638
+
1639
+ /* D3 cancel:改变远端事实,需二次确认。只有供应商明确确认才会显示「已取消」;
1640
+ 不支持或无法确认时保持真实状态(观察暂停/结果未知),绝不伪造已取消。 */
1641
+ function CoreCancelButton(props) {
1642
+ if (!props.row || props.row.cancelable !== true) return null;
1643
+ return React.createElement(CoreTaskManualButton, {
1644
+ id: props.row.id, action: 'cancel',
1645
+ idleLabel: '取消任务', busyLabel: '取消中…', failPrefix: '取消任务',
1646
+ title: '请求远端取消该任务;只有供应商明确确认才会记为已取消',
1647
+ confirm: '请求远端取消该任务?\n只有供应商明确确认后才会记为「已取消」;不支持或无法确认时保持真实状态,绝不伪造已取消。'
1648
+ });
1649
+ }
1650
+
1651
+ /* D4 retry as new task:产生新的真实计费。Core 记录不持久化生成指令,所以必须
1652
+ 重新输入 prompt(绝不从旧任务"恢复");二次确认文案明确重复计费。 */
1653
+ function CoreRetryButton(props) {
1654
+ if (!props.row || props.row.retryable !== true) return null;
1655
+ return React.createElement(CoreTaskManualButton, {
1656
+ id: props.row.id, action: 'retry',
1657
+ idleLabel: '重试为新任务', busyLabel: '创建中…', failPrefix: '重试',
1658
+ title: '创建一个新任务重新生成,可能产生重复费用;生成指令需要重新输入',
1659
+ confirm: '将创建一个新任务并可能产生重复生成费用,确定继续?',
1660
+ createdPrefix: '已创建新任务:',
1661
+ buildBody: function () {
1662
+ var isTts = props.row.capability === 'tts';
1663
+ var isTranscribe = props.row.capability === 'transcribe';
1664
+ var draft = window.prompt(isTts
1665
+ ? 'Core 记录不保存合成文本。请重新输入新任务要合成的文本:'
1666
+ : isTranscribe
1667
+ ? 'Core 记录不保存音频地址。请重新输入新任务要转写音频的公网或 oss:// 地址:'
1668
+ : 'Core 记录不保存生成指令。请重新输入新任务使用的生成指令:');
1669
+ if (draft === null) return null;
1670
+ draft = String(draft).trim();
1671
+ if (!draft) return null;
1672
+ return isTts ? { text: draft, confirmBilling: true }
1673
+ : isTranscribe ? { audio_url: draft, confirmBilling: true }
1674
+ : { prompt: draft, confirmBilling: true };
1675
+ }
1676
+ });
1677
+ }
1678
+
1679
+ /* 注意力处置(Host 偏好,零 Core 写入):不再提醒/恢复提醒/移除/恢复显示。
1680
+ 文案明确"仅在本机隐藏提醒,任务与产物记录保留",不使用"消除记录"类措辞。 */
1681
+ function CoreDispositionButtons(props) {
1682
+ var row = props.row;
1683
+ if (!row || row.suppressed === true || row.historical === true) return null;
1684
+ if (row.disposition === 'hidden') {
1685
+ return React.createElement(CoreTaskManualButton, {
1686
+ id: row.id, action: 'unhide',
1687
+ idleLabel: '恢复显示', busyLabel: '恢复中…', failPrefix: '恢复显示',
1688
+ title: '在本机恢复显示该任务;Core 记录一直在,只是被隐藏'
1689
+ });
1690
+ }
1691
+ var attentionish = row.userState === 'attention' || row.userState === 'delivery_failed';
1692
+ var children = [];
1693
+ if (attentionish && row.disposition !== 'acknowledged') {
1694
+ children.push(React.createElement(CoreTaskManualButton, {
1695
+ key: 'ack', id: row.id, action: 'acknowledge',
1696
+ idleLabel: '不再提醒', busyLabel: '处理中…', failPrefix: '不再提醒',
1697
+ title: '仅在本机不再提醒该任务;任务与产物记录保留在 Core'
1698
+ }));
1699
+ children.push(React.createElement(CoreTaskManualButton, {
1700
+ key: 'hide', id: row.id, action: 'hide',
1701
+ idleLabel: '移除', busyLabel: '移除中…', failPrefix: '移除',
1702
+ title: '仅在本机隐藏提醒,任务与产物记录保留在 Core',
1703
+ confirm: '仅在本机隐藏此任务的提醒;任务与产物记录保留在 Core,可随时在高级诊断恢复显示。确定移除?'
1704
+ }));
1705
+ }
1706
+ if (attentionish && row.disposition === 'acknowledged') {
1707
+ children.push(React.createElement(CoreTaskManualButton, {
1708
+ key: 'restore', id: row.id, action: 'restore',
1709
+ idleLabel: '恢复提醒', busyLabel: '恢复中…', failPrefix: '恢复提醒',
1710
+ title: '取消"不再提醒",该任务重新出现在需要处理分区'
1711
+ }));
1712
+ }
1713
+ return children.length ? React.createElement('span', {}, children) : null;
1714
+ }
1715
+
1716
+ /* 只读卡片:显示稳定 Task ID、模型、更新时间和关联作品;不渲染供应商身份、错误原文或路径,无展开/删除入口。
1717
+ 「重新观察」/「重新取回作品」/「取消任务」/「重试为新任务」是仅有的动作(子组件,卡片本体仍不 fetch),
1718
+ 且仅当投影标记对应门时出现。 */
1719
+ function coreTaskCard(row) {
1720
+ var artifactIds = Array.isArray(row.artifactIds) ? row.artifactIds : [];
1721
+ var links = row.mediaReady && artifactIds.length
1722
+ ? React.createElement('div', { className: 'iris-wb-row' }, artifactIds.map(function (id, index) {
1723
+ return React.createElement('a', {
1724
+ key: id, className: 'iris-wb-link', target: '_blank', rel: 'noreferrer',
1725
+ href: '/iris/api/core/artifact/' + encodeURIComponent(id) + '/media'
1726
+ }, '▶ 作品 ' + (index + 1));
1727
+ }))
1728
+ : null;
1729
+ return React.createElement('div', {
1730
+ key: 'core:' + row.id,
1731
+ className: 'iris-wb-card iris-core-user-row',
1732
+ title: '只读投影:更多事实见下方「高级诊断 · Core 任务事实」'
1733
+ },
1734
+ React.createElement('div', { className: 'iris-core-user-head' },
1735
+ React.createElement('span', {}, capIcon(row.capability)),
1736
+ coreTaskBadge(row),
1737
+ React.createElement('span', { className: 'iris-wb-kv iris-core-user-model' }, row.model || '未记录'),
1738
+ React.createElement('span', { className: 'iris-wb-muted' }, '更新 ' + fmtTime(row.updatedAt))),
1739
+ React.createElement('div', { className: 'iris-wb-row' },
1740
+ React.createElement('span', { className: 'iris-core-id' }, row.id)),
1741
+ React.createElement(CoreReobserveButton, { row: row }),
1742
+ React.createElement(CoreRedeliverButton, { row: row }),
1743
+ React.createElement(CoreCancelButton, { row: row }),
1744
+ React.createElement(CoreRetryButton, { row: row }),
1745
+ React.createElement(CoreDispositionButtons, { row: row }),
1746
+ row.userState === 'succeeded' && !row.mediaReady
1747
+ ? React.createElement('div', { className: 'iris-wb-row' },
1748
+ React.createElement('span', { className: 'iris-wb-badge warn', title: '关联作品记录不在当前快照中,请稍后刷新' }, '作品文件暂时不可用'))
1749
+ : null,
1750
+ links);
1751
+ }
1752
+
1753
+ function coreTaskMini(row) {
1754
+ return React.createElement('div', {
1755
+ key: 'core:' + row.id, className: 'iris-task-mini',
1756
+ title: row.label + ' · ' + row.id
1757
+ },
1758
+ React.createElement('span', {}, capIcon(row.capability)),
1759
+ coreTaskBadge(row),
1760
+ React.createElement('span', { className: 'iris-task-mini-prompt' }, row.model || row.id),
1761
+ React.createElement('span', { className: 'iris-wb-muted' }, fmtTime(row.updatedAt)));
1762
+ }
1763
+
1764
+ /* hidden 与 suppressed(重试成功自动静默)从用户任务区整体消失;canceled
1765
+ 用 historical 进入历史且不计 attention;acknowledged 异常行也进入历史但可恢复。 */
1766
+ function splitCoreUserRows(rows) {
1767
+ var live = [], attention = [], done = [], acknowledged = [];
1768
+ for (var i = 0; i < rows.length; i++) {
1769
+ var row = rows[i];
1770
+ if (!row || row.suppressed === true || row.disposition === 'hidden') continue;
1771
+ if (row.historical === true) done.push(row);
1772
+ else if (row.userState === 'running' || row.userState === 'observation_paused') live.push(row);
1773
+ else if (row.userState === 'attention' || row.userState === 'delivery_failed') {
1774
+ if (row.disposition === 'acknowledged') acknowledged.push(row);
1775
+ else attention.push(row);
1776
+ }
1777
+ else if (row.userState === 'succeeded') done.push(row);
1778
+ }
1779
+ return { live: live, attention: attention, done: done, acknowledged: acknowledged };
1780
+ }
1781
+
1782
+ function CoreRuntimePanel() {
1783
+ var dataPair = React.useState(null);
1784
+ var data = dataPair[0];
1785
+ var setData = dataPair[1];
1786
+ var busyPair = React.useState(false);
1787
+ var busy = busyPair[0];
1788
+ var setBusy = busyPair[1];
1789
+ var errorPair = React.useState('');
1790
+ var error = errorPair[0];
1791
+ var setError = errorPair[1];
1792
+ var openPair = React.useState(false);
1793
+ var open = openPair[0];
1794
+ var setOpen = openPair[1];
1795
+ var selectedPair = React.useState('');
1796
+ var selectedCoreTask = selectedPair[0];
1797
+ var setSelectedCoreTask = selectedPair[1];
1798
+ var copyPair = React.useState('');
1799
+ var copyNote = copyPair[0];
1800
+ var setCopyNote = copyPair[1];
1801
+ function load() {
1802
+ if (busy) return;
1803
+ setBusy(true);
1804
+ setError('');
1805
+ fetch('/iris/api/core/snapshot?limit=12')
1806
+ .then(function (res) {
1807
+ if (res.status === 404) return { available: false, backendReloadRequired: true, tasks: { total: 0, recent: [] }, artifacts: { total: 0, recent: [] } };
1808
+ return res.ok ? res.json() : Promise.reject(new Error('读取失败(HTTP ' + res.status + ')'));
1809
+ }).then(setData)
1810
+ .catch(function (caught) { setError('Core 运行事实读取失败:' + caught.message); })
1811
+ .finally(function () { setBusy(false); });
1812
+ }
1813
+ function copyCoreId(value, label) {
1814
+ setCopyNote('');
1815
+ var clipboard = window.navigator && window.navigator.clipboard;
1816
+ if (!clipboard || typeof clipboard.writeText !== 'function') {
1817
+ setCopyNote('浏览器不允许自动复制;请长按上方 ID 复制。');
1818
+ return;
1819
+ }
1820
+ clipboard.writeText(value)
1821
+ .then(function () { setCopyNote(label + ' 已复制'); })
1822
+ .catch(function () { setCopyNote('复制失败;请长按上方 ID 复制。'); });
1823
+ }
1824
+ React.useEffect(function () {
1825
+ function changed() { load(); }
1826
+ load();
1827
+ window.addEventListener('iris-core-artifacts-changed', changed);
1828
+ window.addEventListener('iris-core-refresh-tick', changed);
1829
+ return function () {
1830
+ window.removeEventListener('iris-core-artifacts-changed', changed);
1831
+ window.removeEventListener('iris-core-refresh-tick', changed);
1832
+ };
1833
+ }, []);
1834
+ var taskData = data && data.tasks || { total: 0, recent: [] };
1835
+ var artifactData = data && data.artifacts || { total: 0, recent: [] };
1836
+ /* 处置偏好投影:诊断层可见 hidden/acknowledged/suppressed,并可恢复显示。 */
1837
+ var dispositionMap = {};
1838
+ ((data && data.userTasks) || []).forEach(function (row) { dispositionMap[row.id] = row; });
1839
+ function dispositionLabelOf(row) {
1840
+ if (!row) return '';
1841
+ if (row.suppressed) return '已由新任务成功交付自动静默';
1842
+ if (row.disposition === 'hidden') return '已在本机隐藏(记录保留,可恢复显示)';
1843
+ if (row.disposition === 'acknowledged') return '已设「不再提醒」(本机偏好,记录保留)';
1844
+ return '';
1845
+ }
1846
+ var selectedTask = (taskData.recent || []).find(function (task) { return task.id === selectedCoreTask; });
1847
+ var taskRows = (taskData.recent || []).slice(0, 6).map(function (task) {
1848
+ var state = coreTaskState(task);
1849
+ var disposed = dispositionMap[task.id];
1850
+ var hiddenMark = disposed && disposed.disposition === 'hidden' ? ' · 已隐藏' : '';
1851
+ var selected = selectedCoreTask === task.id;
1852
+ return React.createElement('button', {
1853
+ key: task.id,
1854
+ type: 'button',
1855
+ className: 'iris-core-task-row' + (selected ? ' selected' : ''),
1856
+ 'aria-expanded': selected,
1857
+ onClick: function () { setSelectedCoreTask(selected ? '' : task.id); }
1858
+ },
1859
+ React.createElement('span', { className: 'iris-core-state-dot ' + state.tone }),
1860
+ React.createElement('span', { className: 'iris-core-task-main' },
1861
+ React.createElement('strong', {}, coreCapabilityLabel(task.capability) + ' · ' + state.label + hiddenMark),
1862
+ React.createElement('span', { className: 'iris-wb-muted' }, coreModelLabel(task.modelRef) + ' · ' + fmtTime(task.updatedAt))),
1863
+ React.createElement('span', { className: 'iris-core-task-arrow' }, selected ? '▼' : '›'));
1864
+ });
1865
+ var selectedArtifacts = selectedTask ? (selectedTask.artifactIds || []).map(function (id) {
1866
+ return (artifactData.recent || []).find(function (artifact) { return artifact.id === id; }) || { id: id };
1867
+ }) : [];
1868
+ var selectedDisposition = selectedTask ? dispositionMap[selectedTask.id] : null;
1869
+ var dispositionNote = dispositionLabelOf(selectedDisposition);
1870
+ var detail = selectedTask ? React.createElement('div', { className: 'iris-core-detail' },
1871
+ React.createElement('div', { className: 'iris-doctor-top' },
1872
+ React.createElement('strong', { className: 'iris-doctor-score' }, '任务事实'),
1873
+ React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: function () { copyCoreId(selectedTask.id, 'Task ID'); } }, '复制 Task ID')),
1874
+ React.createElement('div', { className: 'iris-core-id' }, selectedTask.id),
1875
+ React.createElement('div', { className: 'iris-core-detail-grid' },
1876
+ React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '模型'), React.createElement('span', {}, coreModelLabel(selectedTask.modelRef))),
1877
+ React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, 'Provider'), React.createElement('span', { className: 'iris-core-id' }, selectedTask.providerId || '未记录')),
1878
+ React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '取消状态'), React.createElement('span', {}, coreCancelLabel(selectedTask.cancelState))),
1879
+ dispositionNote ? React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '处置状态'), React.createElement('span', {}, dispositionNote)) : null,
1880
+ React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '重试来源'), React.createElement('span', { className: 'iris-core-id' }, selectedTask.retriedFrom || '无')),
1881
+ React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '创建时间'), React.createElement('span', {}, fmtTime(selectedTask.createdAt))),
1882
+ React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '最后更新'), React.createElement('span', {}, fmtTime(selectedTask.updatedAt)))),
1883
+ React.createElement('div', {},
1884
+ React.createElement('div', { className: 'iris-wb-muted', style: { marginBottom: '4px' } }, '尝试记录 · ' + (selectedTask.attempts || []).length),
1885
+ React.createElement('div', { className: 'iris-core-task-list' }, (selectedTask.attempts || []).map(function (attempt) {
1886
+ return React.createElement('div', { key: attempt.id, className: 'iris-core-attempt' },
1887
+ React.createElement('span', {}, '第 ' + attempt.ordinal + ' 次 · ' + coreAttemptState(attempt) + ' · ' + coreModelLabel(attempt.model)),
1888
+ React.createElement('span', { className: 'iris-wb-muted' }, fmtTime(attempt.finishedAt || attempt.startedAt)),
1889
+ attempt.error && attempt.error.safeMessage ? React.createElement('span', { className: 'iris-wb-err' }, attempt.error.safeMessage) : null);
1890
+ }))),
1891
+ React.createElement('div', {},
1892
+ React.createElement('div', { className: 'iris-wb-muted', style: { marginBottom: '4px' } }, '关联作品 · ' + selectedArtifacts.length),
1893
+ selectedArtifacts.length ? React.createElement('div', { className: 'iris-core-task-list' }, selectedArtifacts.map(function (artifact) {
1894
+ var canOpen = selectedTask.capability === 'image' && (!artifact.mediaType || String(artifact.mediaType).indexOf('image/') === 0);
1895
+ return React.createElement('div', { key: artifact.id, className: 'iris-core-artifact' },
1896
+ React.createElement('span', { className: 'iris-core-id', style: { flex: 1 } }, artifact.id),
1897
+ artifact.size ? React.createElement('span', { className: 'iris-wb-muted' }, fmtBytes(artifact.size)) : null,
1898
+ canOpen ? React.createElement('a', { className: 'iris-wb-link', href: '/iris/api/core/artifact/' + encodeURIComponent(artifact.id) + '/media', target: '_blank', rel: 'noreferrer' }, '打开作品') : null,
1899
+ React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: function () { copyCoreId(artifact.id, 'Artifact ID'); } }, '复制 ID'));
1900
+ })) : React.createElement('div', { className: 'iris-wb-empty' }, '当前任务没有可用作品')),
1901
+ selectedTask.lastError && selectedTask.lastError.safeMessage ? React.createElement('div', { className: 'iris-wb-err' }, selectedTask.lastError.safeMessage) : null,
1902
+ React.createElement(CoreReobserveButton, {
1903
+ row: selectedTask && selectedTask.acceptance === 'accepted' && selectedTask.remoteTaskId
1904
+ && ['none', 'unknown'].indexOf(selectedTask.outcome) >= 0 && selectedTask.phase !== 'terminal'
1905
+ ? { id: selectedTask.id, observable: true } : null
1906
+ }),
1907
+ React.createElement(CoreRedeliverButton, {
1908
+ row: selectedTask && selectedTask.outcome === 'succeeded'
1909
+ && selectedTask.deliveryState === 'failed' && selectedTask.remoteTaskId
1910
+ ? { id: selectedTask.id, redeliverable: true } : null
1911
+ }),
1912
+ React.createElement(CoreCancelButton, {
1913
+ row: selectedTask && selectedTask.acceptance === 'accepted' && selectedTask.remoteTaskId
1914
+ && ['none', 'unknown'].indexOf(selectedTask.outcome) >= 0 && selectedTask.phase !== 'terminal'
1915
+ && (!selectedTask.cancelState || selectedTask.cancelState === 'none')
1916
+ ? { id: selectedTask.id, cancelable: true } : null
1917
+ }),
1918
+ React.createElement(CoreRetryButton, {
1919
+ row: selectedTask && selectedTask.phase === 'terminal'
1920
+ && !(selectedTask.outcome === 'succeeded' && selectedTask.deliveryState === 'ready')
1921
+ ? { id: selectedTask.id, retryable: true } : null
1922
+ }),
1923
+ React.createElement(CoreDispositionButtons, {
1924
+ row: selectedDisposition
1925
+ ? { id: selectedDisposition.id, userState: selectedDisposition.userState,
1926
+ disposition: selectedDisposition.disposition, suppressed: selectedDisposition.suppressed }
1927
+ : null
1928
+ }),
1929
+ React.createElement('div', { className: 'iris-clean-note' }, '事实面板:「重新观察」只查询远端最新状态、「重新取回作品」只重新下载已生成的产物、「取消任务」只有供应商明确确认才记为已取消、「重试为新任务」会创建新任务并可能产生重复费用;这里不会重新提交、删除或修改既有任务。')) : null;
1930
+ var countText = data && data.available
1931
+ ? taskData.total + ' 个任务 · ' + artifactData.total + ' 个作品'
1932
+ : (busy ? '正在读取…' : '查看底层任务与作品关系');
1933
+ return React.createElement('div', { className: 'iris-wb-card iris-core-runtime' },
1934
+ React.createElement('div', { className: 'iris-core-runtime-head' },
1935
+ React.createElement('div', { className: 'iris-core-runtime-title' },
1936
+ React.createElement('strong', {}, 'Core 任务事实'),
1937
+ React.createElement('span', { className: 'iris-wb-muted' }, countText)),
1938
+ React.createElement('button', { className: 'iris-pm-btn', type: 'button', 'aria-expanded': open, onClick: function () { setOpen(!open); } }, open ? '收起' : '展开')),
1939
+ open ? React.createElement('div', { className: 'iris-core-runtime-body' },
1940
+ React.createElement('div', { className: 'iris-core-runtime-tools' },
1941
+ React.createElement('span', { className: 'iris-wb-muted', style: { flex: 1 } }, '只读开发诊断 · 点击任务查看尝试与作品'),
1942
+ React.createElement('button', { className: 'iris-pm-btn', type: 'button', disabled: busy, onClick: load }, busy ? '读取中…' : '刷新')),
1943
+ error ? React.createElement('div', { className: 'iris-doctor-row error' }, React.createElement('span', {}, '×'), React.createElement('span', {}, error)) : null,
1944
+ data && !data.available ? React.createElement('div', { className: 'iris-wb-empty' },
1945
+ data.backendReloadRequired ? 'DSH 后端尚未加载 Core 路由;请重启 DSH 后重试。' : '尚无 Core 运行事实;读取不会创建目录。') : null,
1946
+ data && data.available ? (taskRows.length ? React.createElement('div', { className: 'iris-core-task-list' }, taskRows)
1947
+ : React.createElement('div', { className: 'iris-wb-empty' }, '当前没有 Core Task')) : null,
1948
+ detail,
1949
+ copyNote ? React.createElement('div', { className: 'iris-clean-note' }, copyNote) : null) : null);
1950
+ }
1951
+
1057
1952
  /* ---- 泡泡常用卡片选择器(设置页:多项勾选 → 出现在悬浮窗「常用」标签) ---- */
1058
1953
  function BubbleCardPicker() {
1059
1954
  var pair = useBubbleCards();
@@ -1141,11 +2036,63 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1141
2036
  note ? React.createElement('div', { className: 'iris-clean-note' }, note) : null);
1142
2037
  }
1143
2038
 
2039
+ function HostDoctorPanel() {
2040
+ var pair = React.useState(null);
2041
+ var report = pair[0];
2042
+ var setReport = pair[1];
2043
+ var busyPair = React.useState(false);
2044
+ var busy = busyPair[0];
2045
+ var setBusy = busyPair[1];
2046
+ var errorPair = React.useState('');
2047
+ var error = errorPair[0];
2048
+ var setError = errorPair[1];
2049
+
2050
+ function load() {
2051
+ setBusy(true);
2052
+ setError('');
2053
+ fetch('/iris/api/doctor')
2054
+ .then(function (res) { return res.json().then(function (data) { if (!res.ok) throw new Error(data.error || '诊断失败'); return data; }); })
2055
+ .then(function (data) { setReport(data); })
2056
+ .catch(function (err) { setError(String(err && err.message || err)); })
2057
+ .finally(function () { setBusy(false); });
2058
+ }
2059
+ React.useEffect(function () { load(); }, []);
2060
+
2061
+ var checks = report && Array.isArray(report.checks) ? report.checks : [];
2062
+ var issues = checks.filter(function (item) { return item.status !== 'ok'; });
2063
+ var mark = { ok: '✓', warn: '!', error: '×' };
2064
+ function rows(list) {
2065
+ return React.createElement('div', { className: 'iris-doctor-list' }, list.map(function (item) {
2066
+ return React.createElement('div', { key: item.id, className: 'iris-doctor-row ' + item.status },
2067
+ React.createElement('span', {}, mark[item.status] || '·'),
2068
+ React.createElement('span', {}, item.summary));
2069
+ }));
2070
+ }
2071
+ var score = report
2072
+ ? report.summary.errors + ' 错误 · ' + report.summary.warnings + ' 警告'
2073
+ : (busy ? '正在检查宿主…' : '尚未运行');
2074
+ return React.createElement('div', { className: 'iris-wb-card' },
2075
+ React.createElement('div', { className: 'iris-doctor-top' },
2076
+ React.createElement('span', { className: 'iris-doctor-score' }, score),
2077
+ report && report.host ? React.createElement('span', { className: 'iris-wb-muted' }, 'DSH ' + report.host.version) : null,
2078
+ React.createElement('button', { className: 'iris-pm-btn', disabled: busy, onClick: load }, busy ? '检查中…' : '刷新')),
2079
+ error ? React.createElement('div', { className: 'iris-doctor-row error' }, React.createElement('span', {}, '×'), React.createElement('span', {}, error)) : null,
2080
+ issues.length ? rows(issues) : (report ? React.createElement('div', { className: 'iris-wb-muted' }, '当前可观察项全部健康') : null),
2081
+ checks.length ? React.createElement('details', { className: 'iris-doctor-details' },
2082
+ React.createElement('summary', {}, '查看全部 ' + checks.length + ' 项'),
2083
+ rows(checks)) : null,
2084
+ React.createElement('div', { className: 'iris-wb-muted' }, '只读本机注册证据;不调用 Browser、模型或供应商。'));
2085
+ }
2086
+
1144
2087
  function WorkbenchPanel() {
1145
2088
  var state = useIrisState(5000);
1146
2089
  var running = (state && state.tasks && state.tasks.running) || [];
1147
2090
  var recent = (state && state.tasks && state.tasks.recent) || [];
1148
2091
  var attention = (state && state.tasks && state.tasks.attention) || [];
2092
+ var artifactTotal = (state && state.artifacts && state.artifacts.total) || 0;
2093
+ // 同一任务区合并 legacy 与 Core 只读投影;Core 行仅有的动作是「重新观察」「重新取回作品」与需确认的「取消任务」。
2094
+ var coreUser = useCoreUserTasks();
2095
+ var coreGroups = splitCoreUserRows(coreUser.rows || []);
1149
2096
  var selPair = React.useState(null);
1150
2097
  var selectedTask = selPair[0];
1151
2098
  var setSelectedTask = selPair[1];
@@ -1156,23 +2103,38 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1156
2103
  React.createElement('span', { className: 'iris-wb-date' }, state ? '刷新于 ' + new Date().toLocaleTimeString() : '加载中…')),
1157
2104
  React.createElement('div', { className: 'iris-wb-sec' }, '供应商'),
1158
2105
  React.createElement(ProviderManager, {}),
2106
+ React.createElement('div', { className: 'iris-wb-sec' }, '能力健康'),
2107
+ React.createElement(CapabilityHealthOverview, {}),
2108
+ React.createElement('div', { className: 'iris-wb-muted' }, '绿色有效 7 天;真实成功或显式实测会刷新。不会后台探测,也不会因 429、网络或供应商 5xx 变红。'),
1159
2109
  React.createElement('div', { className: 'iris-wb-sec' }, '能力分配(failover 顺序)'),
1160
2110
  React.createElement(CapabilityAssigner, {}),
1161
2111
  React.createElement('div', { className: 'iris-wb-sec' }, 'Iris 泡泡快捷卡片(勾选后显示在「⚡ 常用」标签)'),
1162
2112
  React.createElement(BubbleCardPicker, {}),
1163
2113
  React.createElement('div', { className: 'iris-wb-sec' }, '对话框提示词优化'),
1164
2114
  React.createElement(PromptOptimizerSettings, {}),
2115
+ React.createElement('div', { className: 'iris-wb-sec' }, '宿主诊断'),
2116
+ React.createElement(HostDoctorPanel, {}),
1165
2117
  React.createElement('div', { className: 'iris-wb-sec' }, '运行中任务'),
1166
2118
  React.createElement('div', { className: 'iris-wb-box' },
1167
- running.length ? running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })
2119
+ running.length || coreGroups.live.length ? [].concat(
2120
+ running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); }),
2121
+ coreGroups.live.map(coreTaskCard))
1168
2122
  : React.createElement('div', { className: 'iris-wb-empty' }, '暂无进行中任务')),
1169
- attention.length ? React.createElement('div', {},
2123
+ attention.length || coreGroups.attention.length || coreUser.degraded ? React.createElement('div', {},
1170
2124
  React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
1171
2125
  React.createElement('div', { className: 'iris-wb-box' },
1172
- attention.map(function (t) { return taskRow(t, false, selectedTask === t.id, onSelect); }))) : null,
2126
+ coreUser.degraded ? React.createElement('div', { className: 'iris-clean-note' },
2127
+ '部分 Core 记录损坏或媒体缺失,已跳过 ' + coreUser.droppedTasks + ' 条;其余事实照常显示。') : null,
2128
+ [].concat(
2129
+ attention.map(function (t) { return taskRow(t, false, selectedTask === t.id, onSelect); }),
2130
+ coreGroups.attention.map(coreTaskCard)))) : null,
2131
+ React.createElement('div', { className: 'iris-wb-sec' }, '作品库'),
2132
+ React.createElement(ArtifactGallery, { total: artifactTotal }),
2133
+ React.createElement('div', { className: 'iris-wb-sec' }, '高级诊断'),
2134
+ React.createElement(CoreRuntimePanel, {}),
1173
2135
  React.createElement('div', { className: 'iris-wb-sec' }, '历史任务'),
1174
2136
  React.createElement(CleanupBar, {}),
1175
- React.createElement(HistoryBrowser, { recent: recent, selected: selectedTask, onSelect: onSelect }),
2137
+ React.createElement(HistoryBrowser, { recent: recent, coreHistory: [].concat(coreGroups.done, coreGroups.acknowledged), selected: selectedTask, onSelect: onSelect }),
1176
2138
  React.createElement('div', { className: 'iris-wb-sec' }, '操作'),
1177
2139
  React.createElement(ActionGroups, {}));
1178
2140
  }
@@ -1184,6 +2146,23 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1184
2146
  var recent = (state && state.tasks && state.tasks.recent) || [];
1185
2147
  var attention = (state && state.tasks && state.tasks.attention) || [];
1186
2148
  var recentTotal = (state && state.tasks && state.tasks.recentTotal) || recent.length;
2149
+ var legacyWorks = (state && state.artifacts && state.artifacts.recent) || [];
2150
+ var coreWorks = useCoreWorks(6);
2151
+ // 同一任务/作品区合并 Core 只读投影;完成提示会话内只出现一次,刷新后从事实重算。
2152
+ var coreUser = useCoreUserTasks();
2153
+ var coreGroups = splitCoreUserRows(coreUser.rows || []);
2154
+ var arrivedPair = React.useState([]);
2155
+ var coreArrived = arrivedPair[0];
2156
+ var setCoreArrived = arrivedPair[1];
2157
+ var coreRowsKey = (coreUser.rows || []).map(function (row) { return row.id + '@' + row.revision; }).join(',');
2158
+ React.useEffect(function () {
2159
+ var added = noteCoreCompletions(coreUser.rows || []);
2160
+ if (added.length) setCoreArrived(added);
2161
+ }, [coreRowsKey]);
2162
+ var works = coreWorks.concat(legacyWorks).sort(function (a, b) {
2163
+ return String(b.createdAt || '').localeCompare(String(a.createdAt || ''));
2164
+ }).slice(0, 6);
2165
+ var artifactTotal = ((state && state.artifacts && state.artifacts.total) || legacyWorks.length) + coreWorks.length;
1187
2166
  var cards = useBubbleCards()[0];
1188
2167
  var tabPair = React.useState('tasks');
1189
2168
  var tab = tabPair[0];
@@ -1194,24 +2173,24 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1194
2173
  function onSelect(id) { setSelectedTask(id); }
1195
2174
  var body;
1196
2175
  if (tab === 'tasks') {
1197
- // 快捷面板承载当前任务、需要处理的异常事实与可直接查看的成功产物。
1198
- var succeeded = recent.filter(function (t) {
1199
- return t.status === 'succeeded' && Array.isArray(t.media) && t.media.length > 0;
1200
- });
1201
- var shown = succeeded.slice(0, 6);
2176
+ // 快捷面板承载当前任务、需要处理的异常事实与独立作品库中的最近作品。
1202
2177
  body = React.createElement('div', { className: 'iris-wb-box' },
1203
- running.length ? React.createElement('div', {},
2178
+ coreArrived.length ? React.createElement('div', { className: 'iris-wb-badge ok', role: 'status' },
2179
+ '新完成:' + coreArrived.map(function (row) { return row.model || row.id; }).join('、')) : null,
2180
+ running.length || coreGroups.live.length ? React.createElement('div', {},
1204
2181
  React.createElement('div', { className: 'iris-wb-sec' }, '运行中'),
1205
- running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })) : null,
1206
- attention.length ? React.createElement('div', {},
2182
+ running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); }),
2183
+ coreGroups.live.map(coreTaskMini)) : null,
2184
+ attention.length || coreGroups.attention.length ? React.createElement('div', {},
1207
2185
  React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
1208
- attention.slice(0, 4).map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); })) : null,
1209
- React.createElement('div', { className: 'iris-wb-sec' }, '最近完成'),
1210
- shown.length ? shown.map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); })
1211
- : React.createElement('div', { className: 'iris-wb-empty' }, '尚无成功产物'),
1212
- succeeded.length > 6 || recentTotal > succeeded.length
2186
+ attention.slice(0, 4).map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); }),
2187
+ coreGroups.attention.slice(0, 4).map(coreTaskMini)) : null,
2188
+ React.createElement('div', { className: 'iris-wb-sec' }, '最近作品'),
2189
+ works.length ? works.map(artifactRowMini)
2190
+ : React.createElement('div', { className: 'iris-wb-empty' }, '尚无作品'),
2191
+ artifactTotal > works.length || recentTotal > 0
1213
2192
  ? React.createElement('div', { className: 'iris-bubble-more', style: { cursor: 'default' } },
1214
- '失败、取消与完整历史请到 Iris 工作台查看') : null);
2193
+ '完整作品库、失败与取消历史请到 Iris 工作台查看') : null);
1215
2194
  } else {
1216
2195
  var defs = CARD_DEFS.filter(function (d) { return cards.indexOf(d.action) >= 0; });
1217
2196
  body = defs.length
@@ -1467,10 +2446,35 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1467
2446
  }));
1468
2447
  }
1469
2448
 
2449
+ /* 曲线鸢尾花:矢量源见 docs/assets/logo/iris-bubble.svg。
2450
+ 使用实色与无 ID 的路径,多个座位同时渲染也不会发生 SVG 引用冲突。 */
2451
+ var IRIS_MARK_PARTS = [
2452
+ { fill: '#7db9a4', d: 'M30.5 39h3v18h-3z' },
2453
+ { fill: '#7db9a4', d: 'M32 55C24 55 20 50 19 44C26 45 31 48 32 55Z' },
2454
+ { fill: '#9cd1b7', d: 'M32 54C40 53 44 49 45 43C38 44 33 48 32 54Z' },
2455
+ { fill: '#b5a0f3', d: 'M29 31C18 30 11 22 15 14C24 13 30 20 29 31Z' },
2456
+ { fill: '#b5a0f3', d: 'M35 31C46 30 53 22 49 14C40 13 34 20 35 31Z' },
2457
+ { fill: '#e0d4ff', d: 'M32 7C24 13 23 23 32 34C41 23 40 13 32 7Z' },
2458
+ { fill: '#9b80df', d: 'M31 31C22 24 11 28 8 36C12 45 24 47 31 34Z' },
2459
+ { fill: '#9b80df', d: 'M33 31C42 24 53 28 56 36C52 45 40 47 33 34Z' },
2460
+ { fill: '#c4aff6', d: 'M32 29C24 35 23 44 32 50C41 44 40 35 32 29Z' },
2461
+ { fill: '#f2ce83', d: 'M32 31C29 32 28 34 29 36L32 40L35 36C36 34 35 32 32 31Z' }
2462
+ ];
2463
+ function IRIS_MARK_SVG() {
2464
+ return React.createElement('svg', {
2465
+ width: 26, height: 26, viewBox: '0 0 64 64',
2466
+ role: 'img', 'aria-label': 'Iris', focusable: 'false'
2467
+ }, IRIS_MARK_PARTS.map(function (part, i) {
2468
+ return React.createElement('path', { key: i, d: part.d, fill: part.fill });
2469
+ }));
2470
+ }
2471
+
1470
2472
  function FloatingBubble() {
1471
2473
  var state = useIrisState(5000);
1472
2474
  var providers = (state && state.providers) || [];
1473
- var configured = providers.some(function (p) { return p.enabled && p.apiKeyHint; });
2475
+ var fallbackConfigured = providers.some(function (p) { return p.enabled && p.apiKeyHint; });
2476
+ var overallHealth = state && state.health && HEALTH_META[state.health.overall]
2477
+ ? state.health.overall : (fallbackConfigured ? 'configured' : 'unconfigured');
1474
2478
  var running = (state && state.tasks && state.tasks.running) || [];
1475
2479
  var attention = (state && state.tasks && state.tasks.attention) || [];
1476
2480
 
@@ -1538,17 +2542,30 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1538
2542
  }
1539
2543
  }
1540
2544
 
2545
+ function persistCurrentPosition() {
2546
+ setPos(function (current) {
2547
+ if (current && current.x != null && current.y != null) {
2548
+ try { localStorage.setItem('iris-bubble-pos', JSON.stringify(current)); } catch (_) {}
2549
+ }
2550
+ return current;
2551
+ });
2552
+ }
2553
+
1541
2554
  function onPointerUp(e) {
1542
2555
  var drag = dragRef.current;
1543
2556
  if (!drag) return;
1544
2557
  try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
1545
2558
  var moved = drag.moved;
1546
2559
  dragRef.current = null;
1547
- if (moved) {
1548
- try { localStorage.setItem('iris-bubble-pos', JSON.stringify(pos)); } catch (_) {}
1549
- } else {
1550
- setOpen(!open);
1551
- }
2560
+ if (moved) persistCurrentPosition();
2561
+ else setOpen(!open);
2562
+ }
2563
+
2564
+ function onPointerCancel(e) {
2565
+ if (!dragRef.current) return;
2566
+ try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
2567
+ dragRef.current = null;
2568
+ persistCurrentPosition();
1552
2569
  }
1553
2570
 
1554
2571
  var style;
@@ -1566,8 +2583,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1566
2583
  var panel = null;
1567
2584
  if (open) {
1568
2585
  var xy = bubbleXY();
1569
- var panelLeft = Math.max(8, xy.x - 330);
1570
- var panelTop = Math.max(8, Math.min(xy.y - 260, window.innerHeight - 520));
2586
+ var panelWidth = Math.min(360, Math.max(0, window.innerWidth - 16));
2587
+ var panelHeight = Math.min(520, Math.max(0, window.innerHeight - 16));
2588
+ var panelLeft = Math.max(8, Math.min(xy.x - panelWidth + 30, window.innerWidth - panelWidth - 8));
2589
+ var panelTop = Math.max(8, Math.min(xy.y - 260, window.innerHeight - panelHeight - 8));
1571
2590
  panel = React.createElement('div', { className: 'iris-bubble-panel', style: { left: panelLeft + 'px', top: panelTop + 'px' } },
1572
2591
  React.createElement('div', { className: 'iris-bubble-panel-head' },
1573
2592
  React.createElement('button', { className: 'iris-bubble-close', onClick: function () { setOpen(false); } }, '✕')),
@@ -1575,52 +2594,63 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1575
2594
  }
1576
2595
 
1577
2596
  return React.createElement('div', {
1578
- className: 'iris-bubble' + (configured ? ' lit' : ' dim') + (dragRef.current ? ' dragging' : ''),
2597
+ className: 'iris-bubble health-' + overallHealth + (dragRef.current ? ' dragging' : ''),
1579
2598
  style: style,
1580
2599
  onPointerDown: onPointerDown,
1581
2600
  onPointerMove: onPointerMove,
1582
2601
  onPointerUp: onPointerUp,
1583
- title: configured ? 'Iris 已就绪:点击打开快捷面板' : 'Iris 尚未配置:点击打开快捷面板'
2602
+ onPointerCancel: onPointerCancel,
2603
+ title: 'Iris ' + healthText({ status: overallHealth }) + ':点击打开快捷面板'
1584
2604
  },
1585
- React.createElement('div', { className: 'iris-bubble-btn' }, '🫧'),
2605
+ React.createElement('div', { className: 'iris-bubble-btn' }, React.createElement(IRIS_MARK_SVG)),
1586
2606
  badge,
1587
2607
  panel);
1588
2608
  }
1589
2609
 
1590
- ctx.slots.inject('settings.section', function () {
1591
- return ctx.slots.register({
2610
+ var clientSlots = clientSlotsPort(ctx);
2611
+
2612
+ clientSlots.inject('settings.section', function () {
2613
+ var dispose = clientSlots.register({
1592
2614
  name: 'settings.section',
1593
2615
  id: 'iris-workbench',
1594
2616
  order: 140,
1595
2617
  label: function () { return 'Iris 工作台'; }
1596
2618
  }, WorkbenchPanel);
2619
+ reportClientSeat('settings.section');
2620
+ return dispose;
1597
2621
  });
1598
2622
 
1599
- ctx.slots.inject('conversation.input.right', function () {
1600
- return ctx.slots.register({
2623
+ clientSlots.inject('conversation.input.right', function () {
2624
+ var dispose = clientSlots.register({
1601
2625
  name: 'conversation.input.right',
1602
2626
  id: 'iris-prompt-optimizer',
1603
2627
  order: 40,
1604
2628
  label: function () { return 'Iris 提示词优化'; }
1605
2629
  }, PromptOptimizerControl);
2630
+ reportClientSeat('conversation.input.right');
2631
+ return dispose;
1606
2632
  });
1607
2633
 
1608
- ctx.slots.inject('conversation.input.dock', function () {
1609
- return ctx.slots.register({
2634
+ clientSlots.inject('conversation.input.dock', function () {
2635
+ var dispose = clientSlots.register({
1610
2636
  name: 'conversation.input.dock',
1611
2637
  id: 'iris-progress',
1612
2638
  order: 0,
1613
2639
  label: function () { return 'Iris 任务进度'; }
1614
2640
  }, ProgressDock);
2641
+ reportClientSeat('conversation.input.dock');
2642
+ return dispose;
1615
2643
  });
1616
2644
 
1617
- ctx.slots.inject('shell.overlay', function () {
1618
- return ctx.slots.register({
2645
+ clientSlots.inject('shell.overlay', function () {
2646
+ var dispose = clientSlots.register({
1619
2647
  name: 'shell.overlay',
1620
2648
  id: 'iris-bubble',
1621
2649
  order: 200,
1622
2650
  label: function () { return 'Iris 泡泡'; }
1623
2651
  }, FloatingBubble);
2652
+ reportClientSeat('shell.overlay');
2653
+ return dispose;
1624
2654
  });
1625
2655
  console.log('[iris] client slots registered');
1626
2656
  } catch (err) {