@mokuyoaxis/dsh-iris 0.2.5 → 0.2.7

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 (83) hide show
  1. package/.dsh/skills/iris-use/SKILL.md +2 -2
  2. package/.dsh/skills/iris-use/references/media-contract.md +2 -2
  3. package/CHANGELOG.md +49 -1
  4. package/README.en.md +39 -22
  5. package/README.md +40 -21
  6. package/bin/dsh-iris.js +166 -35
  7. package/docs/AGENT_QUICKSTART.md +7 -7
  8. package/docs/ARCHITECTURE.md +9 -3
  9. package/docs/CLI_MANAGEMENT.md +5 -1
  10. package/docs/CORE_RUNTIME_CONTRACT.md +36 -5
  11. package/docs/CORE_SKILLS.md +62 -0
  12. package/docs/DSH_COMPATIBILITY.md +8 -4
  13. package/docs/DSH_CORE_MIGRATION.md +4 -2
  14. package/docs/DSH_PROVIDER_CONNECTIONS.md +34 -0
  15. package/docs/DSH_RUNTIME.md +36 -0
  16. package/docs/FUNCTION_MODEL_ASSIGNMENTS.md +2 -0
  17. package/docs/FUNCTION_MODEL_POOLS.md +84 -0
  18. package/docs/HEADLESS_CLI.md +16 -8
  19. package/docs/HOST_ADAPTER_CONTRACT.md +7 -2
  20. package/docs/HOST_DOCTOR.md +14 -2
  21. package/docs/IMAGE_FLOW.md +71 -0
  22. package/docs/LOCAL_API.md +60 -0
  23. package/docs/LOCAL_API_CONTRACT.md +115 -0
  24. package/docs/LOCAL_CREATION.md +50 -0
  25. package/docs/MUSIC.md +2 -2
  26. package/docs/PROVIDER_HEALTH.md +26 -2
  27. package/docs/ROADMAP.md +18 -4
  28. package/docs/SECURITY.md +20 -2
  29. package/docs/SYNC_DELIVERY_RECOVERY.md +2 -0
  30. package/docs/VISION_CLI.md +2 -2
  31. package/docs/WEB_PROTOTYPE.md +47 -0
  32. package/docs/releases/0.2.6.md +50 -0
  33. package/docs/releases/0.2.7.md +62 -0
  34. package/lib/actions.js +208 -90
  35. package/lib/adapters.js +36 -9
  36. package/lib/api.js +40 -39
  37. package/lib/application-runtime.js +186 -0
  38. package/lib/client.js +805 -182
  39. package/lib/composite-vision-routing.js +6 -6
  40. package/lib/config.js +140 -29
  41. package/lib/core-artifact-store.js +5 -0
  42. package/lib/core-maintenance.js +25 -2
  43. package/lib/core-runtime.js +19 -0
  44. package/lib/core-tasks.js +62 -8
  45. package/lib/creation-request-store.js +159 -0
  46. package/lib/doctor.js +20 -2
  47. package/lib/dsh-core-adapter.js +669 -564
  48. package/lib/dsh-host-adapter.js +14 -1
  49. package/lib/dsh-provider-config.js +35 -0
  50. package/lib/dsh-provider-source.js +102 -0
  51. package/lib/flow-store.js +195 -0
  52. package/lib/headless-vision.js +5 -5
  53. package/lib/host-contract.js +1 -0
  54. package/lib/http-vision-model-adapter.js +5 -2
  55. package/lib/image-flow.js +188 -0
  56. package/lib/index.js +103 -78
  57. package/lib/local-api-projection.js +27 -0
  58. package/lib/local-api.js +381 -0
  59. package/lib/local-creation.js +85 -0
  60. package/lib/model-directory-service.js +53 -0
  61. package/lib/model-directory.js +103 -0
  62. package/lib/models.js +71 -6
  63. package/lib/ocr-model-routing.js +2 -2
  64. package/lib/private-storage.js +12 -0
  65. package/lib/provider-adapters.js +42 -9
  66. package/lib/provider-catalog.js +14 -9
  67. package/lib/provider-config-scope.js +16 -0
  68. package/lib/provider-config-service.js +82 -13
  69. package/lib/provider-health.js +33 -0
  70. package/lib/provider-protocol.js +30 -0
  71. package/lib/provider-task-runner.js +22 -2
  72. package/lib/render-address.js +35 -0
  73. package/lib/render.js +52 -13
  74. package/lib/skill-installation.js +199 -0
  75. package/lib/vision-model-routing.js +25 -12
  76. package/lib/workbench-artifacts.js +1 -2
  77. package/lib/workbench-management.js +8 -9
  78. package/package.json +4 -3
  79. package/skills/iris-core/SKILL.md +40 -0
  80. package/skills/iris-core/agents/openai.yaml +4 -0
  81. package/skills/iris-core/references/cli.md +95 -0
  82. package/skills/iris-core/references/tasks.md +58 -0
  83. package/user_guide.md +32 -20
@@ -2,10 +2,12 @@
2
2
 
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
- import { allProviders, assignments, irisHome, recordRateLimit } from './config.js';
5
+ import { runtimeProviders, assignments, irisHome, recordRateLimit, recordAvailability, refreshAutomaticDirectories } from './config.js';
6
+ import { withDshProviderConfig } from './dsh-provider-config.js';
6
7
  import { createCommandService } from './command-service.js';
7
8
  import { createCoreArtifact, inspectCoreArtifact, listCoreArtifacts, listCoreArtifactMetadata, readCoreArtifactBytes } from './core-artifacts.js';
8
- import { createCoreRuntime } from './core-runtime.js';
9
+ import { createApplicationRuntime } from './application-runtime.js';
10
+ import { normalizeCoreOptions } from './core-contract.js';
9
11
  import { readSummaryFrames } from './summary-input.js';
10
12
  import { readVisionArtifact } from './vision-input.js';
11
13
  import { inspectCoreTask, listCoreTasks, recoverCoreTask } from './core-tasks.js';
@@ -18,14 +20,12 @@ import { parseModelRef } from './models.js';
18
20
  import { imageCandidatesFromCatalog, musicCandidatesFromCatalog, providerForTaskFromCatalog, providerTaskBinding, transcribeCandidatesFromCatalog, ttsCandidatesFromCatalog, videoCandidatesFromCatalog } from './provider-catalog.js';
19
21
  import { generateMusicTask } from './music-generation.js';
20
22
 
21
- let writerTail = Promise.resolve();
22
- const providerWatchers = new Map();
23
23
  const DEFAULT_POLL_INTERVAL_MS = 2500;
24
24
  const DEFAULT_MAX_WATCH_MS = 20 * 60 * 1000;
25
25
  const POLL_ERROR_TOLERANCE = 5;
26
26
 
27
27
  function createConfiguredProviderAdapter(provider, options = {}) {
28
- return createProviderAdapter(provider, { ...options, onRateLimit: recordRateLimit });
28
+ return createProviderAdapter(provider, { ...options, onRateLimit: recordRateLimit, onModelAvailability: recordAvailability });
29
29
  }
30
30
 
31
31
  export class DshCoreAdapterError extends Error {
@@ -45,625 +45,730 @@ export function dshCoreDataRoot() {
45
45
  return path.join(irisHome(), 'core-v0');
46
46
  }
47
47
 
48
- async function withRuntime(mode, callback, signal) {
49
- if (signal?.aborted) throw abortError();
50
- const runtime = createCoreRuntime({ dataRoot: dshCoreDataRoot(), mode });
51
- runtime.start();
52
- const onAbort = () => { runtime.dispose().catch(() => {}); };
53
- signal?.addEventListener('abort', onAbort, { once: true });
54
- try { return await callback(runtime, mode === 'writer' ? createCommandService(runtime) : null); }
55
- finally {
56
- signal?.removeEventListener('abort', onAbort);
57
- await runtime.dispose();
58
- }
59
- }
60
-
61
- async function withWriter(callback, signal) {
62
- const operation = writerTail.then(() => withRuntime('writer', callback, signal));
63
- writerTail = operation.catch(() => {});
64
- return operation;
65
- }
66
-
67
- function withReader(callback, signal) {
68
- return withRuntime('reader', callback, signal);
69
- }
70
-
71
- /** 本地图片与会话附件都交给同一导入Command,不创建生成任务。 */
72
- export function importImageForDsh(input = {}) {
73
- return withWriter((_runtime, commands) => commands.execute('artifact.import', input.imageBytes !== undefined
74
- ? { image_bytes: input.imageBytes } : { image_path: input.imagePath }), input.signal);
75
- }
76
-
77
48
  /**
78
- * 将 DSH 图片输入投影到共享 crop Command,再把中性 Artifact 读成宿主可保存的字节。
79
- * bytes 输入先成为 host-input Artifact,输出通过 derived-from 保留关系。
49
+ * One plugin-owned DSH assembly. Construction performs no I/O; Core writes share the
50
+ * Application Runtime FIFO, while polling and foreign Host operations belong here.
80
51
  */
81
- export async function cropForDsh(input = {}) {
82
- return withWriter(async (runtime, commands) => {
83
- if (input.signal?.aborted) throw Object.assign(new Error('已取消'), { name: 'AbortError' });
84
- let commandInput;
85
- if (input.bytes !== undefined) {
86
- const source = await runtime.run('execute', ({ dataRoot }) => createCoreArtifact(dataRoot, {
87
- bytes: input.bytes,
88
- mediaType: input.mediaType,
89
- kind: 'host-input',
90
- metadata: {}
91
- }));
92
- commandInput = { artifact_id: source.id };
93
- } else {
94
- commandInput = { image_path: input.imagePath };
95
- }
96
- Object.assign(commandInput, {
97
- left: input.left,
98
- top: input.top,
99
- width: input.width,
100
- height: input.height
101
- });
102
- const result = await commands.execute('crop', commandInput);
103
- if (input.signal?.aborted) throw Object.assign(new Error('已取消'), { name: 'AbortError' });
104
- const materialized = await runtime.run('inspect', ({ dataRoot }) =>
105
- readCoreArtifactBytes(dataRoot, result.artifact.id));
106
- return Object.freeze({
107
- artifact: result.artifact,
108
- bytes: materialized.bytes,
109
- mediaType: result.artifact.mediaType,
110
- width: result.artifact.metadata.width,
111
- height: result.artifact.metadata.height
52
+ export function createDshRuntime({ dataRoot: requestedRoot, getHost = () => undefined } = {}) {
53
+ const dataRoot = normalizeCoreOptions({ dataRoot: requestedRoot }).dataRoot;
54
+ if (typeof getHost !== 'function') throw new TypeError('getHost 必须是宿主端口工厂');
55
+ const application = createApplicationRuntime({ dataRoot });
56
+ const providerWatchers = new Map(), polls = new Set(), active = new Set();
57
+ const lifetime = new AbortController();
58
+ let state = 'open', disposal;
59
+
60
+ function accepting() { return state === 'open' && application.snapshot().state === 'open'; }
61
+ function closed() {
62
+ return Object.assign(new DshCoreAdapterError('IRIS_DSH_RUNTIME_CLOSED', 'Iris DSH 实例正在关闭或已关闭'), { statusCode: 503 });
63
+ }
64
+ function operationSignal(signal) {
65
+ return signal === undefined ? lifetime.signal : AbortSignal.any([signal, lifetime.signal]);
66
+ }
67
+ function track(value) {
68
+ const job = Promise.resolve(value);
69
+ active.add(job);
70
+ void job.then(() => active.delete(job), () => {
71
+ active.delete(job);
72
+ // The failed operation already reports cleanup failure; retain it for the owner too.
73
+ if (application.snapshot().cleanupFailed) void dispose().catch(() => {});
112
74
  });
113
- }, input.signal);
114
- }
75
+ return job;
76
+ }
77
+ // Keep live ports by reference; snapshot only owned business inputs before enqueueing.
78
+ function ownedInput(input = {}) {
79
+ const { signal, browser, adapter, candidates, core: _owner, ...value } = input;
80
+ return { ...structuredClone(value),
81
+ ...(browser !== undefined ? { browser } : {}),
82
+ ...(adapter !== undefined ? { adapter } : {}),
83
+ ...(candidates !== undefined ? { candidates: candidates.map(({ adapter, prepareInput, ...route }) => ({
84
+ ...structuredClone(route), adapter, ...(prepareInput !== undefined ? { prepareInput } : {})
85
+ })) } : {}), signal: operationSignal(signal) };
86
+ }
87
+ function invoke(fn, optionsIndex, args) {
88
+ if (!accepting()) return Promise.reject(closed());
89
+ try {
90
+ if (fn === maintainWorkbenchForDsh) args[1] = structuredClone(args[1]);
91
+ args[optionsIndex] = ownedInput(args[optionsIndex]);
92
+ args[optionsIndex].signal.throwIfAborted();
93
+ return track(fn(...args));
94
+ } catch (error) { return Promise.reject(error); }
95
+ }
96
+ function runHostOperation(action, { signal } = {}) {
97
+ if (!accepting()) return Promise.reject(closed());
98
+ const combined = operationSignal(signal);
99
+ try {
100
+ combined.throwIfAborted();
101
+ return track(action(combined));
102
+ } catch (error) { return Promise.reject(error); }
103
+ }
104
+ function snapshot() {
105
+ return Object.freeze({ state: accepting() ? 'open' : state === 'closed' ? 'closed' : 'closing',
106
+ watchers: providerWatchers.size, polls: polls.size, operations: active.size, application: application.snapshot() });
107
+ }
108
+ function dispose() {
109
+ if (disposal) return disposal;
110
+ state = 'closing'; lifetime.abort();
111
+ const stopped = stopProviderTaskWatchesForDsh();
112
+ const released = application.dispose();
113
+ disposal = (async () => {
114
+ await Promise.allSettled([...active, ...polls, stopped, released]);
115
+ await released;
116
+ state = 'closed';
117
+ return snapshot();
118
+ })();
119
+ return disposal;
120
+ }
115
121
 
116
- /** Browser 只提供截图字节;DSH/CLI 共用 HTML Command 和 Artifact 提交。 */
117
- export function htmlForDsh(input = {}) {
118
- return withWriter(async runtime => {
119
- const commands = createCommandService(runtime, { browser: input.browser });
120
- const result = await commands.execute('media.html', { html: input.html, width: input.width,
121
- height: input.height, full_page: input.fullPage });
122
- return runtime.run('inspect', ({ dataRoot }) => readCoreArtifactBytes(dataRoot, result.artifact.id));
123
- }, input.signal);
124
- }
122
+ function withRuntime(mode, callback, signal) {
123
+ return application.run(mode, runtime => callback(runtime,
124
+ mode === 'writer' ? createCommandService(runtime) : null), { signal });
125
+ }
126
+ function withWriter(callback, signal) { return withRuntime('writer', callback, signal); }
127
+ function withReader(callback, signal) { return withRuntime('reader', callback, signal); }
125
128
 
126
- /** DSH 只把图片来源转换为 Command 输入,差异算法与 Artifact 提交由 Core 执行。 */
127
- export function diffForDsh(input = {}) {
128
- return withWriter(async (runtime, commands) => {
129
- const commandInput = { grid: input.grid, top_regions: input.topRegions };
130
- for (const [side, source] of [['a', input.imageA], ['b', input.imageB]]) {
131
- if (input.signal?.aborted) throw abortError();
132
- if (source?.bytes !== undefined) {
133
- const artifact = await runtime.run('execute', ({ dataRoot }) => createCoreArtifact(dataRoot, {
134
- bytes: source.bytes, mediaType: source.mediaType, kind: 'host-input', metadata: {}
129
+ /** 本地图片与会话附件都交给同一导入Command,不创建生成任务。 */
130
+ function importImageForDsh(input = {}) {
131
+ return withWriter((_runtime, commands) => commands.execute('artifact.import', input.imageBytes !== undefined
132
+ ? { image_bytes: input.imageBytes } : { image_path: input.imagePath }), input.signal);
133
+ }
134
+
135
+ /**
136
+ * 将 DSH 图片输入投影到共享 crop Command,再把中性 Artifact 读成宿主可保存的字节。
137
+ * bytes 输入先成为 host-input Artifact,输出通过 derived-from 保留关系。
138
+ */
139
+ async function cropForDsh(input = {}) {
140
+ return withWriter(async (runtime, commands) => {
141
+ if (input.signal?.aborted) throw Object.assign(new Error('已取消'), { name: 'AbortError' });
142
+ let commandInput;
143
+ if (input.bytes !== undefined) {
144
+ const source = await runtime.run('execute', ({ dataRoot }) => createCoreArtifact(dataRoot, {
145
+ bytes: input.bytes,
146
+ mediaType: input.mediaType,
147
+ kind: 'host-input',
148
+ metadata: {}
135
149
  }));
136
- commandInput['image_' + side + '_artifact_id'] = artifact.id;
150
+ commandInput = { artifact_id: source.id };
137
151
  } else {
138
- commandInput['image_' + side + '_path'] = source?.imagePath;
152
+ commandInput = { image_path: input.imagePath };
139
153
  }
140
- }
141
- const result = await commands.execute('media.diff', commandInput);
142
- const materialized = await runtime.run('inspect', ({ dataRoot }) =>
143
- readCoreArtifactBytes(dataRoot, result.artifact.id));
144
- if (input.signal?.aborted) throw abortError();
145
- return Object.freeze({ ...materialized, mediaType: result.artifact.mediaType, metrics: result.metrics });
146
- }, input.signal);
147
- }
154
+ Object.assign(commandInput, {
155
+ left: input.left,
156
+ top: input.top,
157
+ width: input.width,
158
+ height: input.height
159
+ });
160
+ const result = await commands.execute('crop', commandInput);
161
+ if (input.signal?.aborted) throw Object.assign(new Error('已取消'), { name: 'AbortError' });
162
+ const materialized = await runtime.run('inspect', ({ dataRoot }) =>
163
+ readCoreArtifactBytes(dataRoot, result.artifact.id));
164
+ return Object.freeze({
165
+ artifact: result.artifact,
166
+ bytes: materialized.bytes,
167
+ mediaType: result.artifact.mediaType,
168
+ width: result.artifact.metadata.width,
169
+ height: result.artifact.metadata.height
170
+ });
171
+ }, input.signal);
172
+ }
148
173
 
149
- /** 抽帧结果先落 Core Artifact,再只读转换为 DSH 可保存的附件字节。 */
150
- export function framesForDsh(input = {}) {
151
- return withWriter(async (runtime, commands) => {
152
- const result = await commands.execute('media.frames', {
153
- video_path: input.videoPath,
154
- max_frames: input.maxFrames,
155
- target_width: input.targetWidth,
156
- format: input.format,
157
- quality: input.quality
158
- });
159
- const frames = await runtime.run('inspect', ({ dataRoot }) => result.artifacts.map((artifact) => ({
160
- ...readCoreArtifactBytes(dataRoot, artifact.id),
161
- mediaType: artifact.mediaType,
162
- ...artifact.metadata
163
- })));
164
- if (input.signal?.aborted) throw abortError();
165
- return Object.freeze({ media: result.media, frames: Object.freeze(frames.map(Object.freeze)) });
166
- }, input.signal);
167
- }
174
+ /** Browser 只提供截图字节;DSH/CLI 共用 HTML Command 和 Artifact 提交。 */
175
+ function htmlForDsh(input = {}) {
176
+ return withWriter(async runtime => {
177
+ const commands = createCommandService(runtime, { browser: input.browser });
178
+ const result = await commands.execute('media.html', { html: input.html, width: input.width,
179
+ height: input.height, full_page: input.fullPage });
180
+ return runtime.run('inspect', ({ dataRoot }) => readCoreArtifactBytes(dataRoot, result.artifact.id));
181
+ }, input.signal);
182
+ }
168
183
 
169
- /** 已有抽帧 Artifact 直接进入共享摘要;reader 不取得写者租约。 */
170
- export function summaryFramesForDsh(input = {}) {
171
- return withReader(runtime => readSummaryFrames(runtime, input.frameArtifactIds, input.signal), input.signal);
172
- }
184
+ /** DSH 只把图片来源转换为 Command 输入,差异算法与 Artifact 提交由 Core 执行。 */
185
+ function diffForDsh(input = {}) {
186
+ return withWriter(async (runtime, commands) => {
187
+ const commandInput = { grid: input.grid, top_regions: input.topRegions };
188
+ for (const [side, source] of [['a', input.imageA], ['b', input.imageB]]) {
189
+ if (input.signal?.aborted) throw abortError();
190
+ if (source?.bytes !== undefined) {
191
+ const artifact = await runtime.run('execute', ({ dataRoot }) => createCoreArtifact(dataRoot, {
192
+ bytes: source.bytes, mediaType: source.mediaType, kind: 'host-input', metadata: {}
193
+ }));
194
+ commandInput['image_' + side + '_artifact_id'] = artifact.id;
195
+ } else {
196
+ commandInput['image_' + side + '_path'] = source?.imagePath;
197
+ }
198
+ }
199
+ const result = await commands.execute('media.diff', commandInput);
200
+ const materialized = await runtime.run('inspect', ({ dataRoot }) =>
201
+ readCoreArtifactBytes(dataRoot, result.artifact.id));
202
+ if (input.signal?.aborted) throw abortError();
203
+ return Object.freeze({ ...materialized, mediaType: result.artifact.mediaType, metrics: result.metrics });
204
+ }, input.signal);
205
+ }
173
206
 
174
- /** 共享单图读取器用于 look/locate/OCR,Host 不解释 Core 对象路径。 */
175
- export function visionImageForDsh(input = {}) {
176
- return withReader(runtime => readVisionArtifact(runtime, input.artifactId, input.signal), input.signal);
177
- }
207
+ /** 抽帧结果先落 Core Artifact,再只读转换为 DSH 可保存的附件字节。 */
208
+ function framesForDsh(input = {}) {
209
+ return withWriter(async (runtime, commands) => {
210
+ const result = await commands.execute('media.frames', {
211
+ video_path: input.videoPath,
212
+ max_frames: input.maxFrames,
213
+ target_width: input.targetWidth,
214
+ format: input.format,
215
+ quality: input.quality
216
+ });
217
+ const frames = await runtime.run('inspect', ({ dataRoot }) => result.artifacts.map((artifact) => ({
218
+ ...readCoreArtifactBytes(dataRoot, artifact.id),
219
+ mediaType: artifact.mediaType,
220
+ ...artifact.metadata
221
+ })));
222
+ if (input.signal?.aborted) throw abortError();
223
+ return Object.freeze({ media: result.media, frames: Object.freeze(frames.map(Object.freeze)) });
224
+ }, input.signal);
225
+ }
178
226
 
179
- /** 提交一个 Core Provider Task;Provider Adapter 由 DSH 配置边界选择后显式传入。 */
180
- export function submitProviderTaskForDsh(input = {}) {
181
- return withWriter((runtime) => createProviderTaskRunner(runtime).submit({
182
- capability: input.capability,
183
- candidates: input.candidates,
184
- providerInput: input.providerInput,
185
- sourceArtifactId: input.sourceArtifactId
186
- }), input.signal);
187
- }
227
+ /** 已有抽帧 Artifact 直接进入共享摘要;reader 不取得写者租约。 */
228
+ function summaryFramesForDsh(input = {}) {
229
+ return withReader(runtime => readSummaryFrames(runtime, input.frameArtifactIds, input.signal), input.signal);
230
+ }
188
231
 
189
- export function submitMusicForDsh(input) {
190
- return withWriter(runtime => generateMusicTask({
191
- runner: createProviderTaskRunner(runtime), candidates: input.candidates,
192
- providerInput: input.providerInput, options: input.musicOptions, signal: input.signal
193
- }), input.signal);
194
- }
232
+ /** 共享单图读取器用于 look/locate/OCR,Host 不解释 Core 对象路径。 */
233
+ function visionImageForDsh(input = {}) {
234
+ return withReader(runtime => readVisionArtifact(runtime, input.artifactId, input.signal), input.signal);
235
+ }
195
236
 
196
- /** 单步观察已经受理的 Core Task;不会自动循环,也不会创建第二次 submit。 */
197
- export function observeProviderTaskForDsh(input = {}) {
198
- return withWriter((runtime) => createProviderTaskRunner(runtime).observe(
199
- input.taskId, input.adapter, { resumeActive: input.resumeActive === true }
200
- ), input.signal);
201
- }
237
+ /** 提交一个 Core Provider Task;Provider Adapter 由 DSH 配置边界选择后显式传入。 */
238
+ function submitProviderTaskForDsh(input = {}) {
239
+ return withWriter((runtime) => createProviderTaskRunner(runtime).submit({
240
+ capability: input.capability,
241
+ candidates: input.candidates,
242
+ providerInput: input.providerInput,
243
+ sourceArtifactId: input.sourceArtifactId
244
+ }), input.signal);
245
+ }
202
246
 
203
- /**
204
- * D1/D2 人工动作公共通路:与 Host 观察节拍共用同一个 resolver / binding 校验
205
- * (configuredAdapterForTask),经 Command Service 显式执行一次人工命令;合法性
206
- * 门在 Command 内于网络与写入之前完成。返回任务事实对应的五类用户投影行
207
- * (无 providerId/binding/错误原文/路径)。
208
- */
209
- function manualTaskCommandForDsh(command, input = {}) {
210
- const taskId = String(input.taskId || '').trim();
211
- if (!/^task_[a-f0-9]{24}$/.test(taskId)) {
212
- return Promise.reject(new DshCoreAdapterError('IRIS_DSH_TASK_INVALID', 'Core Task ID 无效'));
213
- }
214
- return withWriter(async (runtime) => {
215
- const commands = createCommandService(runtime, { resolveTaskAdapter: configuredAdapterForTask });
216
- const result = await commands.execute(command, { task_id: taskId });
217
- return runtime.run('inspect', ({ dataRoot }) => {
218
- const artifacts = [];
219
- for (const artifactId of result.task.artifactIds) {
220
- try { artifacts.push(inspectCoreArtifact(dataRoot, artifactId)); }
221
- catch (_) { /* 缺失媒体在投影行里降级为不可用 */ }
222
- }
223
- return Object.freeze({
224
- taskId: result.task.id,
225
- row: projectCoreTaskUserRow(result.task, artifacts)
247
+ function submitMusicForDsh(input) {
248
+ return withWriter(runtime => generateMusicTask({
249
+ runner: createProviderTaskRunner(runtime), candidates: input.candidates,
250
+ providerInput: input.providerInput, options: input.musicOptions, signal: input.signal
251
+ }), input.signal);
252
+ }
253
+
254
+ /** 单步观察已经受理的 Core Task;不会自动循环,也不会创建第二次 submit。 */
255
+ function observeProviderTaskForDsh(input = {}) {
256
+ return withWriter((runtime) => createProviderTaskRunner(runtime).observe(
257
+ input.taskId, input.adapter, { resumeActive: input.resumeActive === true }
258
+ ), input.signal);
259
+ }
260
+
261
+ /**
262
+ * D1/D2 人工动作公共通路:与 Host 观察节拍共用同一个 resolver / binding 校验
263
+ * (configuredAdapterForTask),经 Command Service 显式执行一次人工命令;合法性
264
+ * 门在 Command 内于网络与写入之前完成。返回任务事实对应的五类用户投影行
265
+ * (无 providerId/binding/错误原文/路径)。
266
+ */
267
+ function manualTaskCommandForDsh(command, input = {}) {
268
+ const taskId = String(input.taskId || '').trim();
269
+ if (!/^task_[a-f0-9]{24}$/.test(taskId)) {
270
+ return Promise.reject(new DshCoreAdapterError('IRIS_DSH_TASK_INVALID', 'Core Task ID 无效'));
271
+ }
272
+ return withWriter(async (runtime) => {
273
+ const commands = createCommandService(runtime, { resolveTaskAdapter: task => configuredAdapterForTask(task, runtime.signal) });
274
+ const result = await commands.execute(command, { task_id: taskId });
275
+ return runtime.run('inspect', ({ dataRoot }) => {
276
+ const artifacts = [];
277
+ for (const artifactId of result.task.artifactIds) {
278
+ try { artifacts.push(inspectCoreArtifact(dataRoot, artifactId)); }
279
+ catch (_) { /* 缺失媒体在投影行里降级为不可用 */ }
280
+ }
281
+ return Object.freeze({
282
+ taskId: result.task.id,
283
+ row: projectCoreTaskUserRow(result.task, artifacts)
284
+ });
226
285
  });
227
- });
228
- }, input.signal);
229
- }
286
+ }, input.signal);
287
+ }
230
288
 
231
- /** D1 人工 reobserve:显式单步 poll 一次,绝不 submit。 */
232
- export function reobserveProviderTaskForDsh(input = {}) {
233
- return manualTaskCommandForDsh('task.reobserve', input);
234
- }
289
+ /** D1 人工 reobserve:显式单步 poll 一次,绝不 submit。 */
290
+ function reobserveProviderTaskForDsh(input = {}) {
291
+ return manualTaskCommandForDsh('task.reobserve', input);
292
+ }
235
293
 
236
- /**
237
- * D2 人工 redeliver:只对 outcome=succeeded / deliveryState=failed 的 Task
238
- * 重新交付原结果(同步读缓存,异步 re-poll → download),绝不 submit、
239
- * 绝不重新生成。
240
- */
241
- export function redeliverProviderTaskForDsh(input = {}) {
242
- return manualTaskCommandForDsh('task.redeliver', input);
243
- }
294
+ /**
295
+ * D2 人工 redeliver:只对 outcome=succeeded / deliveryState=failed 的 Task
296
+ * 重新交付原结果(同步读缓存,异步 re-poll → download),绝不 submit、
297
+ * 绝不重新生成。
298
+ */
299
+ function redeliverProviderTaskForDsh(input = {}) {
300
+ return manualTaskCommandForDsh('task.redeliver', input);
301
+ }
244
302
 
245
- /**
246
- * D3 人工 cancel:只有 Provider 明确确认才写 outcome=canceled;不支持或无法确认
247
- * 时保持真实状态(详见 core-tasks 的 recordCoreCancelResult 三分支)。
248
- */
249
- export function cancelProviderTaskForDsh(input = {}) {
250
- return manualTaskCommandForDsh('task.cancel', input);
251
- }
303
+ /**
304
+ * D3 人工 cancel:只有 Provider 明确确认才写 outcome=canceled;不支持或无法确认
305
+ * 时保持真实状态(详见 core-tasks 的 recordCoreCancelResult 三分支)。
306
+ */
307
+ function cancelProviderTaskForDsh(input = {}) {
308
+ return manualTaskCommandForDsh('task.cancel', input);
309
+ }
252
310
 
253
- /**
254
- * 注意力处置(Host 偏好,零 Core 写入):acknowledge/restore/hide/unhide。
255
- * 只校验任务存在;动作幂等(偏好不变时不写盘);返回装饰后的投影行。
256
- */
257
- export async function disposeCoreTaskAttention(input = {}) {
258
- const taskId = String(input.taskId || '').trim();
259
- const action = String(input.action || '').trim();
260
- if (!/^task_[a-f0-9]{24}$/.test(taskId)) {
261
- throw new DshCoreAdapterError('IRIS_DSH_TASK_INVALID', 'Core Task ID 无效');
262
- }
263
- // 任务必须存在(reader 只读);不存在统一 404。
264
- const task = await inspectProviderTaskForDsh(taskId, { signal: input.signal });
265
- const applied = applyCoreAttentionAction(task.id, action);
266
- const row = await withReader((runtime) => runtime.run('inspect', ({ dataRoot }) => {
267
- const artifacts = [];
268
- for (const artifactId of task.artifactIds) {
269
- try { artifacts.push(inspectCoreArtifact(dataRoot, artifactId)); }
270
- catch (_) { /* 缺失媒体在投影行里降级为不可用 */ }
311
+ /**
312
+ * 注意力处置(Host 偏好,零 Core 写入):acknowledge/restore/hide/unhide。
313
+ * 只校验任务存在;动作幂等(偏好不变时不写盘);返回装饰后的投影行。
314
+ */
315
+ async function disposeCoreTaskAttention(input = {}) {
316
+ const taskId = String(input.taskId || '').trim();
317
+ const action = String(input.action || '').trim();
318
+ if (!/^task_[a-f0-9]{24}$/.test(taskId)) {
319
+ throw new DshCoreAdapterError('IRIS_DSH_TASK_INVALID', 'Core Task ID 无效');
271
320
  }
272
- return decorateCoreUserRows(
273
- [projectCoreTaskUserRow(task, artifacts)],
274
- listAllCoreTaskRecords(dataRoot),
275
- readCoreAttentionPrefs()
276
- )[0];
277
- }), input.signal);
278
- return Object.freeze({ taskId: task.id, changed: applied.changed, row });
279
- }
280
-
281
- /**
282
- * D4 人工 retry as new task:产生新的真实计费。候选链按当前 assignments/池实况
283
- * 重新解析(不绑定旧 Provider/binding——旧配置可能正是失败原因),modelRef 可
284
- * 显式指定。prompt 必须由调用方重新提供(Core 记录不持久化 Prompt)。
285
- */
286
- export function retryProviderTaskForDsh(input = {}) {
287
- const taskId = String(input.taskId || '').trim();
288
- if (!/^task_[a-f0-9]{24}$/.test(taskId)) {
289
- return Promise.reject(new DshCoreAdapterError('IRIS_DSH_TASK_INVALID', 'Core Task ID 无效'));
290
- }
291
- return withWriter(async (runtime) => {
292
- const commands = createCommandService(runtime, {
293
- resolveTaskCandidates: ({ capability, modelRef }) => {
294
- const catalog = { providers: allProviders(), assignments: assignments() };
295
- const ref = modelRef || undefined;
296
- const candidates = capability === 'video'
297
- ? videoCandidatesFromCatalog(catalog, ref)
298
- : capability === 'tts'
299
- ? ttsCandidatesFromCatalog(catalog, ref)
300
- : capability === 'music'
301
- ? musicCandidatesFromCatalog(catalog, ref)
302
- : capability === 'transcribe'
303
- ? transcribeCandidatesFromCatalog(catalog, ref)
304
- : imageCandidatesFromCatalog(catalog, ref);
305
- return candidates.map((route) => ({
306
- adapter: createConfiguredProviderAdapter(route.provider),
307
- model: route.modelRef,
308
- selectionReason: route.selectionReason,
309
- providerBinding: providerTaskBinding(route.provider)
310
- }));
311
- }
312
- });
313
- const result = await commands.execute('task.retry', {
314
- task_id: taskId,
315
- provider_input: input.text !== undefined
316
- ? { text: input.text, ...(input.voice !== undefined ? { voice: input.voice } : {}) }
317
- : input.audio_url !== undefined
318
- ? { audio_url: input.audio_url }
319
- : { prompt: input.prompt, ...(input.sourceArtifactId !== undefined ? { source_artifact_id: input.sourceArtifactId } : {}) },
320
- model_ref: input.modelRef,
321
- confirm_billing: input.confirmBilling
322
- });
323
- return runtime.run('inspect', ({ dataRoot }) => {
321
+ // 任务必须存在(reader 只读);不存在统一 404。
322
+ const task = await inspectProviderTaskForDsh(taskId, { signal: input.signal });
323
+ const applied = applyCoreAttentionAction(task.id, action);
324
+ const row = await withReader((runtime) => runtime.run('inspect', ({ dataRoot }) => {
324
325
  const artifacts = [];
325
- for (const artifactId of result.task.artifactIds) {
326
+ for (const artifactId of task.artifactIds) {
326
327
  try { artifacts.push(inspectCoreArtifact(dataRoot, artifactId)); }
327
328
  catch (_) { /* 缺失媒体在投影行里降级为不可用 */ }
328
329
  }
329
- return Object.freeze({
330
- taskId: result.task.id,
331
- retriedFrom: result.retriedFrom,
332
- row: projectCoreTaskUserRow(result.task, artifacts)
330
+ return decorateCoreUserRows(
331
+ [projectCoreTaskUserRow(task, artifacts)],
332
+ listAllCoreTaskRecords(dataRoot),
333
+ readCoreAttentionPrefs()
334
+ )[0];
335
+ }), input.signal);
336
+ return Object.freeze({ taskId: task.id, changed: applied.changed, row });
337
+ }
338
+
339
+ /**
340
+ * D4 人工 retry as new task:产生新的真实计费。候选链按当前 assignments/池实况
341
+ * 重新解析(不绑定旧 Provider/binding——旧配置可能正是失败原因),modelRef 可
342
+ * 显式指定。prompt 必须由调用方重新提供(Core 记录不持久化 Prompt)。
343
+ */
344
+ function retryProviderTaskForDsh(input = {}) {
345
+ const taskId = String(input.taskId || '').trim();
346
+ if (!/^task_[a-f0-9]{24}$/.test(taskId)) {
347
+ return Promise.reject(new DshCoreAdapterError('IRIS_DSH_TASK_INVALID', 'Core Task ID 无效'));
348
+ }
349
+ return withWriter(async (runtime) => {
350
+ const commands = createCommandService(runtime, {
351
+ resolveTaskCandidates: ({ capability, modelRef }) => withDshProviderConfig(getHost(), async () => {
352
+ if (!modelRef) await refreshAutomaticDirectories({ capability: { image: 'image-gen', video: 'video-gen', music: 'music-gen' }[capability] || capability, signal: input.signal });
353
+ const catalog = { providers: runtimeProviders(), assignments: assignments() };
354
+ const ref = modelRef || undefined;
355
+ const candidates = capability === 'video'
356
+ ? videoCandidatesFromCatalog(catalog, ref)
357
+ : capability === 'tts'
358
+ ? ttsCandidatesFromCatalog(catalog, ref)
359
+ : capability === 'music'
360
+ ? musicCandidatesFromCatalog(catalog, ref)
361
+ : capability === 'transcribe'
362
+ ? transcribeCandidatesFromCatalog(catalog, ref)
363
+ : imageCandidatesFromCatalog(catalog, ref);
364
+ return candidates.map((route) => ({
365
+ adapter: createConfiguredProviderAdapter(route.provider),
366
+ model: route.modelRef,
367
+ selectionReason: route.selectionReason,
368
+ providerBinding: providerTaskBinding(route.provider)
369
+ }));
370
+ }, { signal: input.signal, fresh: true })
333
371
  });
334
- });
335
- }, input.signal);
336
- }
372
+ const result = await commands.execute('task.retry', {
373
+ task_id: taskId,
374
+ provider_input: input.text !== undefined
375
+ ? { text: input.text, ...(input.voice !== undefined ? { voice: input.voice } : {}) }
376
+ : input.audio_url !== undefined
377
+ ? { audio_url: input.audio_url }
378
+ : { prompt: input.prompt, ...(input.sourceArtifactId !== undefined ? { source_artifact_id: input.sourceArtifactId } : {}) },
379
+ model_ref: input.modelRef,
380
+ confirm_billing: input.confirmBilling
381
+ });
382
+ return runtime.run('inspect', ({ dataRoot }) => {
383
+ const artifacts = [];
384
+ for (const artifactId of result.task.artifactIds) {
385
+ try { artifacts.push(inspectCoreArtifact(dataRoot, artifactId)); }
386
+ catch (_) { /* 缺失媒体在投影行里降级为不可用 */ }
387
+ }
388
+ return Object.freeze({
389
+ taskId: result.task.id,
390
+ retriedFrom: result.retriedFrom,
391
+ row: projectCoreTaskUserRow(result.task, artifacts)
392
+ });
393
+ });
394
+ }, input.signal);
395
+ }
337
396
 
338
- /** DSH Host 观察接管的媒体能力白名单:图片、视频与转写共用 Task/Attempt 事实轴。 */
339
- const WATCHABLE_CAPABILITIES = new Set(['image', 'video', 'transcribe']);
340
- const WATCH_INTERVAL_BY_CAPABILITY = Object.freeze({ image: DEFAULT_POLL_INTERVAL_MS, video: 6000, transcribe: 2500 });
397
+ /** DSH Host 观察接管的媒体能力白名单:图片、视频与转写共用 Task/Attempt 事实轴。 */
398
+ const WATCHABLE_CAPABILITIES = new Set(['image', 'video', 'transcribe']);
399
+ const WATCH_INTERVAL_BY_CAPABILITY = Object.freeze({ image: DEFAULT_POLL_INTERVAL_MS, video: 6000, transcribe: 2500 });
341
400
 
342
- function observableProviderTask(task) {
343
- return Boolean(task && WATCHABLE_CAPABILITIES.has(task.capability)
344
- && task.acceptance === 'accepted' && task.remoteTaskId
345
- && ['none', 'unknown'].includes(task.outcome)
346
- && task.phase !== 'terminal');
347
- }
401
+ function observableProviderTask(task) {
402
+ return Boolean(task && WATCHABLE_CAPABILITIES.has(task.capability)
403
+ && task.acceptance === 'accepted' && task.remoteTaskId
404
+ && ['none', 'unknown'].includes(task.outcome)
405
+ && task.phase !== 'terminal');
406
+ }
348
407
 
349
- function configuredAdapterForTask(task) {
350
- const provider = providerForTaskFromCatalog({ providers: allProviders() }, task);
351
- return createConfiguredProviderAdapter(provider, { modelId: parseModelRef(task.modelRef)?.modelId });
352
- }
408
+ async function configuredAdapterForTask(task, signal = lifetime.signal) {
409
+ signal.throwIfAborted();
410
+ return withDshProviderConfig(getHost(), () => {
411
+ const provider = providerForTaskFromCatalog({ providers: runtimeProviders() }, task);
412
+ return createConfiguredProviderAdapter(provider, { modelId: parseModelRef(task.modelRef)?.modelId });
413
+ }, { signal, fresh: true });
414
+ }
353
415
 
354
- function stopProviderWatch(taskId, expectedState) {
355
- const current = providerWatchers.get(taskId);
356
- if (expectedState && current !== expectedState) return;
357
- if (current?.timer) clearTimeout(current.timer);
358
- providerWatchers.delete(taskId);
359
- current?.controller.abort();
360
- }
416
+ function stopProviderWatch(taskId, expectedState) {
417
+ const current = providerWatchers.get(taskId);
418
+ if (expectedState && current !== expectedState) return;
419
+ if (current?.timer) clearTimeout(current.timer);
420
+ providerWatchers.delete(taskId);
421
+ current?.controller.abort();
422
+ }
361
423
 
362
- /**
363
- * DSH Host 拥有的有界 Core 观察器。每个 tick 重新读取 Task 与配置,最多执行
364
- * 一次 poll;任何路径都不会调用 submit。Runtime/Writer 只在单次观察期间存活。
365
- */
366
- export async function watchProviderTaskForDsh(input = {}) {
367
- const taskId = String(input.taskId || '').trim();
368
- if (!/^task_[a-f0-9]{24}$/.test(taskId)) {
369
- throw new DshCoreAdapterError('IRIS_DSH_TASK_INVALID', 'Core Task ID 无效');
370
- }
371
- if (providerWatchers.has(taskId)) return false;
372
- const initial = await inspectProviderTaskForDsh(taskId, { signal: input.signal });
373
- if (!observableProviderTask(initial) || providerWatchers.has(taskId)) return false;
374
- configuredAdapterForTask(initial); // binding/模型漂移在定时器和网络前失败
375
-
376
- const state = {
377
- timer: null,
378
- controller: new AbortController(),
379
- startedAt: Date.now(),
380
- errors: 0,
381
- intervalMs: Math.max(5, Number(input.intervalMs)
382
- || WATCH_INTERVAL_BY_CAPABILITY[initial.capability] || DEFAULT_POLL_INTERVAL_MS),
383
- maxWatchMs: Math.max(50, Number(input.maxWatchMs) || DEFAULT_MAX_WATCH_MS)
384
- };
385
- providerWatchers.set(taskId, state);
386
-
387
- const schedule = (delay = state.intervalMs) => {
388
- if (providerWatchers.get(taskId) !== state) return;
389
- state.timer = setTimeout(() => { tick().catch(() => stopProviderWatch(taskId, state)); }, delay);
390
- if (state.timer.unref) state.timer.unref();
391
- };
392
- const tick = async () => {
393
- if (providerWatchers.get(taskId) !== state) return;
394
- const before = await inspectProviderTaskForDsh(taskId, { signal: state.controller.signal });
395
- if (providerWatchers.get(taskId) !== state) return;
396
- if (!observableProviderTask(before)) return stopProviderWatch(taskId, state);
397
- if (Date.now() - state.startedAt > state.maxWatchMs) {
398
- console.warn('[iris] Core Task 自动观察已达到本轮时限,保留远端受理事实:' + taskId);
399
- return stopProviderWatch(taskId, state);
400
- }
401
- let adapter;
402
- try {
403
- adapter = configuredAdapterForTask(before);
404
- } catch (error) {
405
- console.warn('[iris] Core Task 无法按原 Provider binding 恢复观察:' + taskId
406
- + ' · ' + String(error?.code || error?.message || error));
407
- return stopProviderWatch(taskId, state);
424
+ /**
425
+ * DSH Host 拥有的有界 Core 观察器。每个 tick 重新读取 Task 与配置,最多执行
426
+ * 一次 poll;任何路径都不会调用 submit。Runtime/Writer 只在单次观察期间存活。
427
+ */
428
+ async function watchProviderTaskForDsh(input = {}) {
429
+ const taskId = String(input.taskId || '').trim();
430
+ if (!/^task_[a-f0-9]{24}$/.test(taskId)) {
431
+ throw new DshCoreAdapterError('IRIS_DSH_TASK_INVALID', 'Core Task ID 无效');
408
432
  }
409
- let after;
410
- try {
411
- after = await observeProviderTaskForDsh({
412
- taskId, adapter, resumeActive: true, signal: state.controller.signal
413
- });
414
- } catch (error) {
415
- if (error?.code === 'IRIS_CORE_DATA_ROOT_BUSY') return schedule();
416
- state.errors += 1;
433
+ if (providerWatchers.has(taskId)) return false;
434
+ const initial = await inspectProviderTaskForDsh(taskId, { signal: input.signal });
435
+ if (!observableProviderTask(initial) || providerWatchers.has(taskId)) return false;
436
+ await configuredAdapterForTask(initial, input.signal); // binding/模型漂移在定时器和网络前失败
437
+
438
+ input.signal.throwIfAborted();
439
+ if (!accepting()) throw closed();
440
+ if (providerWatchers.has(taskId)) return false;
441
+ const state = {
442
+ timer: null,
443
+ controller: new AbortController(),
444
+ startedAt: Date.now(),
445
+ errors: 0,
446
+ intervalMs: Math.max(5, Number(input.intervalMs)
447
+ || WATCH_INTERVAL_BY_CAPABILITY[initial.capability] || DEFAULT_POLL_INTERVAL_MS),
448
+ maxWatchMs: Math.max(50, Number(input.maxWatchMs) || DEFAULT_MAX_WATCH_MS)
449
+ };
450
+ providerWatchers.set(taskId, state);
451
+
452
+ const schedule = (delay = state.intervalMs) => {
453
+ if (providerWatchers.get(taskId) !== state) return;
454
+ if (!accepting()) return stopProviderWatch(taskId, state);
455
+ state.timer = setTimeout(() => {
456
+ const job = tick().catch(() => stopProviderWatch(taskId, state));
457
+ polls.add(job); void job.then(() => polls.delete(job));
458
+ }, delay);
459
+ if (state.timer.unref) state.timer.unref();
460
+ };
461
+ const tick = async () => {
462
+ if (providerWatchers.get(taskId) !== state) return;
463
+ const before = await inspectProviderTaskForDsh(taskId, { signal: state.controller.signal });
464
+ if (providerWatchers.get(taskId) !== state) return;
465
+ if (!observableProviderTask(before)) return stopProviderWatch(taskId, state);
466
+ if (Date.now() - state.startedAt > state.maxWatchMs) {
467
+ console.warn('[iris] Core Task 自动观察已达到本轮时限,保留远端受理事实:' + taskId);
468
+ return stopProviderWatch(taskId, state);
469
+ }
470
+ let adapter;
471
+ try {
472
+ adapter = await configuredAdapterForTask(before, state.controller.signal);
473
+ } catch (error) {
474
+ console.warn('[iris] Core Task 无法按原 Provider binding 恢复观察:' + taskId
475
+ + ' · ' + String(error?.code || error?.message || error));
476
+ return stopProviderWatch(taskId, state);
477
+ }
478
+ let after;
479
+ try {
480
+ after = await observeProviderTaskForDsh({
481
+ taskId, adapter, resumeActive: true, signal: state.controller.signal
482
+ });
483
+ } catch (error) {
484
+ if (error?.code === 'IRIS_CORE_DATA_ROOT_BUSY') return schedule();
485
+ state.errors += 1;
486
+ if (state.errors >= POLL_ERROR_TOLERANCE) {
487
+ console.warn('[iris] Core Task 自动观察连续失败,保留事实供人工继续:' + taskId);
488
+ return stopProviderWatch(taskId, state);
489
+ }
490
+ return schedule();
491
+ }
492
+ if (providerWatchers.get(taskId) !== state) return;
493
+ if (!observableProviderTask(after)) return stopProviderWatch(taskId, state);
494
+ if (after.outcome === 'unknown') state.errors += 1;
495
+ else state.errors = 0;
417
496
  if (state.errors >= POLL_ERROR_TOLERANCE) {
418
- console.warn('[iris] Core Task 自动观察连续失败,保留事实供人工继续:' + taskId);
497
+ console.warn('[iris] Core Task Provider 观察连续失败,保留事实供人工继续:' + taskId);
419
498
  return stopProviderWatch(taskId, state);
420
499
  }
421
- return schedule();
422
- }
423
- if (providerWatchers.get(taskId) !== state) return;
424
- if (!observableProviderTask(after)) return stopProviderWatch(taskId, state);
425
- if (after.outcome === 'unknown') state.errors += 1;
426
- else state.errors = 0;
427
- if (state.errors >= POLL_ERROR_TOLERANCE) {
428
- console.warn('[iris] Core Task Provider 观察连续失败,保留事实供人工继续:' + taskId);
429
- return stopProviderWatch(taskId, state);
430
- }
431
- schedule();
432
- };
433
- schedule(Math.min(500, state.intervalMs));
434
- return true;
435
- }
500
+ schedule();
501
+ };
502
+ schedule(Math.min(500, state.intervalMs));
503
+ return true;
504
+ }
436
505
 
437
- /** DSH 启动时只接管仍有明确远端受理证据的 Core 图片 Task。 */
438
- export async function resumeProviderTaskWatchesForDsh(options = {}) {
439
- if (!fs.existsSync(dshCoreDataRoot())) return Object.freeze([]);
440
- const records = await withReader((runtime) => runtime.run('inspect', ({ dataRoot }) => {
441
- const all = [];
442
- let page;
443
- do {
444
- page = listCoreTasks(dataRoot, { offset: all.length, limit: 200 });
445
- all.push(...page.tasks);
446
- } while (all.length < page.total);
447
- return all;
448
- }));
449
- const resumed = [];
450
- for (const original of records) {
451
- if (!WATCHABLE_CAPABILITIES.has(original.capability)) continue;
452
- try {
453
- if (observableProviderTask(original)) configuredAdapterForTask(original);
454
- const needsRecovery = (original.phase === 'submitting' && original.acceptance === 'none')
455
- || original.cancelState === 'requested'
456
- || (original.outcome === 'succeeded' && ['pending', 'downloading'].includes(original.deliveryState))
457
- || (original.acceptance === 'accepted' && original.watchState === 'active');
458
- const task = needsRecovery
459
- ? await withWriter((runtime) => runtime.run('recover', ({ dataRoot }) => recoverCoreTask(dataRoot, original.id)))
460
- : original;
461
- if (!observableProviderTask(task)) continue;
462
- const createdAt = Date.parse(task.createdAt);
463
- if (!Number.isFinite(createdAt) || Date.now() - createdAt > (options.maxWatchMs || DEFAULT_MAX_WATCH_MS)) {
464
- console.warn('[iris] Core Task 超过自动接管窗口,保留事实供显式观察:' + task.id);
465
- continue;
506
+ /** DSH 启动时只接管仍有明确远端受理证据的 Core 图片 Task。 */
507
+ async function resumeProviderTaskWatchesForDsh(options = {}) {
508
+ options.signal.throwIfAborted();
509
+ if (!fs.existsSync(dataRoot)) return Object.freeze([]);
510
+ const records = await withReader((runtime) => runtime.run('inspect', ({ dataRoot }) => {
511
+ const all = [];
512
+ let page;
513
+ do {
514
+ page = listCoreTasks(dataRoot, { offset: all.length, limit: 200 });
515
+ all.push(...page.tasks);
516
+ } while (all.length < page.total);
517
+ return all;
518
+ }));
519
+ const resumed = [];
520
+ for (const original of records) {
521
+ if (!WATCHABLE_CAPABILITIES.has(original.capability)) continue;
522
+ try {
523
+ options.signal.throwIfAborted();
524
+ if (observableProviderTask(original)) await configuredAdapterForTask(original, options.signal);
525
+ const needsRecovery = (original.phase === 'submitting' && original.acceptance === 'none')
526
+ || original.cancelState === 'requested'
527
+ || (original.outcome === 'succeeded' && ['pending', 'downloading'].includes(original.deliveryState))
528
+ || (original.acceptance === 'accepted' && original.watchState === 'active');
529
+ const task = needsRecovery
530
+ ? await withWriter((runtime) => runtime.run('recover', ({ dataRoot }) => recoverCoreTask(dataRoot, original.id)), options.signal)
531
+ : original;
532
+ options.signal.throwIfAborted();
533
+ if (!observableProviderTask(task)) continue;
534
+ const createdAt = Date.parse(task.createdAt);
535
+ if (!Number.isFinite(createdAt) || Date.now() - createdAt > (options.maxWatchMs || DEFAULT_MAX_WATCH_MS)) {
536
+ console.warn('[iris] Core Task 超过自动接管窗口,保留事实供显式观察:' + task.id);
537
+ continue;
538
+ }
539
+ if (await watchProviderTaskForDsh({
540
+ taskId: task.id, signal: options.signal,
541
+ intervalMs: options.intervalMs,
542
+ maxWatchMs: options.maxWatchMs
543
+ })) resumed.push(task.id);
544
+ } catch (error) {
545
+ if (options.signal.aborted || !accepting()) throw error;
546
+ console.warn('[iris] Core Task 启动接管失败:' + original.id
547
+ + ' · ' + String(error?.code || error?.message || error));
466
548
  }
467
- if (await watchProviderTaskForDsh({
468
- taskId: task.id,
469
- intervalMs: options.intervalMs,
470
- maxWatchMs: options.maxWatchMs
471
- })) resumed.push(task.id);
472
- } catch (error) {
473
- console.warn('[iris] Core Task 启动接管失败:' + original.id
474
- + ' · ' + String(error?.code || error?.message || error));
475
549
  }
550
+ return Object.freeze(resumed);
476
551
  }
477
- return Object.freeze(resumed);
478
- }
479
552
 
480
- /** 插件 Fiber 释放时停止 Host 定时器并中止在途观察;不请求远端取消或撤销既有受理/成功事实。 */
481
- export function stopProviderTaskWatchesForDsh() {
482
- for (const taskId of [...providerWatchers.keys()]) stopProviderWatch(taskId);
483
- }
484
-
485
- /** 读取 Core Task 事实;reader 不初始化、修复或更新记录。 */
486
- export function inspectProviderTaskForDsh(taskId, options = {}) {
487
- return withReader((runtime) => runtime.run('inspect', ({ dataRoot }) =>
488
- inspectCoreTask(dataRoot, taskId)), options.signal);
489
- }
553
+ /** 插件 Fiber 释放时停止 Host 定时器并中止在途观察;不请求远端取消或撤销既有受理/成功事实。 */
554
+ function stopProviderTaskWatchesForDsh() {
555
+ for (const taskId of [...providerWatchers.keys()]) stopProviderWatch(taskId);
556
+ return Promise.allSettled([...polls]);
557
+ }
490
558
 
491
- /** 作品目录与任务 snapshot 分开查询,目录不加载媒体字节、不取得 writer 租约。 */
492
- export function galleryArtifactsForDsh(options = {}) {
493
- if (!fs.existsSync(dshCoreDataRoot())) return Promise.resolve({ artifacts: [], revision: 0, dropped: 0 });
494
- return withReader((runtime) => runtime.run('inspect', ({ dataRoot }) =>
495
- listCoreArtifactMetadata(dataRoot)), options.signal);
496
- }
559
+ /** 读取 Core Task 事实;reader 不初始化、修复或更新记录。 */
560
+ function inspectProviderTaskForDsh(taskId, options = {}) {
561
+ return withReader((runtime) => runtime.run('inspect', ({ dataRoot }) =>
562
+ inspectCoreTask(dataRoot, taskId)), options.signal);
563
+ }
497
564
 
498
- export function inspectWorkbenchArtifactForDsh(artifactId, options = {}) {
499
- return withReader(runtime => runtime.run('inspect', ({ dataRoot }) =>
500
- inspectCoreArtifact(dataRoot, artifactId)), options.signal);
501
- }
565
+ /** 作品目录与任务 snapshot 分开查询,目录不加载媒体字节、不取得 writer 租约。 */
566
+ function galleryArtifactsForDsh(options = {}) {
567
+ if (!fs.existsSync(dataRoot)) return Promise.resolve({ artifacts: [], revision: 0, dropped: 0 });
568
+ return withReader((runtime) => runtime.run('inspect', ({ dataRoot }) =>
569
+ listCoreArtifactMetadata(dataRoot)), options.signal);
570
+ }
502
571
 
503
- /** 工作台管理沿用 CLI 的维护命令;预览/事务列表只读,执行使用既有串行 writer。 */
504
- export function maintainWorkbenchForDsh(command, input, options = {}) {
505
- if (!['core.delete', 'core.transactions', 'core.restore'].includes(command)) {
506
- throw new DshCoreAdapterError('IRIS_COMMAND_INPUT_INVALID', '不支持的作品管理命令');
572
+ function inspectWorkbenchArtifactForDsh(artifactId, options = {}) {
573
+ return withReader(runtime => runtime.run('inspect', ({ dataRoot }) =>
574
+ inspectCoreArtifact(dataRoot, artifactId)), options.signal);
507
575
  }
508
- if (command === 'core.transactions' && !fs.existsSync(dshCoreDataRoot())) return Promise.resolve({ transactions: [] });
509
- if (command === 'core.restore' || input.confirm_delete === true) {
510
- return withWriter((_runtime, commands) => commands.execute(command, input), options.signal);
576
+
577
+ /** 工作台管理沿用 CLI 的维护命令;预览/事务列表只读,执行使用既有串行 writer。 */
578
+ function maintainWorkbenchForDsh(command, input, options = {}) {
579
+ if (!['core.delete', 'core.transactions', 'core.restore'].includes(command)) {
580
+ throw new DshCoreAdapterError('IRIS_COMMAND_INPUT_INVALID', '不支持的作品管理命令');
581
+ }
582
+ if (command === 'core.transactions' && !fs.existsSync(dataRoot)) return Promise.resolve({ transactions: [] });
583
+ if (command === 'core.restore' || input.confirm_delete === true) {
584
+ return withWriter((_runtime, commands) => commands.execute(command, input), options.signal);
585
+ }
586
+ return withReader(runtime => createCommandService(runtime).execute(command, input), options.signal);
511
587
  }
512
- return withReader(runtime => createCommandService(runtime).execute(command, input), options.signal);
513
- }
514
588
 
515
- /**
516
- * 工作台只读候选快照;数据根不存在时不创建任何目录。
517
- * userTasks 是用户任务区消费的五类状态安全投影(lib/core-user-projection.js),
518
- * 单条损坏记录或媒体缺失只触发局部降级(degraded/droppedTasks),快照整体仍可加载。
519
- */
520
- export function coreSnapshotForDsh(options = {}) {
521
- if (!fs.existsSync(dshCoreDataRoot())) {
522
- return Promise.resolve(Object.freeze({
523
- schemaVersion: 0, available: false, readOnly: true, degraded: false, droppedTasks: 0,
524
- tasks: Object.freeze({ total: 0, recent: Object.freeze([]) }),
525
- artifacts: Object.freeze({ total: 0, recent: Object.freeze([]) }),
526
- userTasks: Object.freeze([])
527
- }));
589
+ /**
590
+ * 工作台只读候选快照;数据根不存在时不创建任何目录。
591
+ * userTasks 是用户任务区消费的五类状态安全投影(lib/core-user-projection.js),
592
+ * 单条损坏记录或媒体缺失只触发局部降级(degraded/droppedTasks),快照整体仍可加载。
593
+ */
594
+ function coreSnapshotForDsh(options = {}) {
595
+ if (!fs.existsSync(dataRoot)) {
596
+ return Promise.resolve(Object.freeze({
597
+ schemaVersion: 0, available: false, readOnly: true, degraded: false, droppedTasks: 0,
598
+ tasks: Object.freeze({ total: 0, recent: Object.freeze([]) }),
599
+ artifacts: Object.freeze({ total: 0, recent: Object.freeze([]) }),
600
+ userTasks: Object.freeze([])
601
+ }));
602
+ }
603
+ return withReader((runtime) => runtime.run('inspect', ({ dataRoot }) => {
604
+ const tasks = listCoreTasks(dataRoot, { limit: options.limit || 12, skipInvalid: true });
605
+ let artifacts = { total: 0, artifacts: Object.freeze([]) };
606
+ let artifactDegraded = false;
607
+ try { artifacts = listCoreArtifacts(dataRoot, { limit: options.limit || 12 }); }
608
+ catch (_) { artifactDegraded = true; }
609
+ const userTasks = decorateCoreUserRows(
610
+ tasks.tasks.map((task) => projectCoreTaskUserRow(task, artifacts.artifacts)),
611
+ listAllCoreTaskRecords(dataRoot),
612
+ readCoreAttentionPrefs()
613
+ );
614
+ return Object.freeze({
615
+ schemaVersion: 0, available: true, readOnly: true,
616
+ degraded: artifactDegraded || tasks.dropped > 0,
617
+ droppedTasks: tasks.dropped,
618
+ tasks: Object.freeze({ total: tasks.total, recent: tasks.tasks }),
619
+ artifacts: Object.freeze({ total: artifacts.total, recent: artifacts.artifacts }),
620
+ userTasks: Object.freeze(userTasks)
621
+ });
622
+ }), options.signal);
528
623
  }
529
- return withReader((runtime) => runtime.run('inspect', ({ dataRoot }) => {
530
- const tasks = listCoreTasks(dataRoot, { limit: options.limit || 12, skipInvalid: true });
531
- let artifacts = { total: 0, artifacts: Object.freeze([]) };
532
- let artifactDegraded = false;
533
- try { artifacts = listCoreArtifacts(dataRoot, { limit: options.limit || 12 }); }
534
- catch (_) { artifactDegraded = true; }
535
- const userTasks = decorateCoreUserRows(
536
- tasks.tasks.map((task) => projectCoreTaskUserRow(task, artifacts.artifacts)),
537
- listAllCoreTaskRecords(dataRoot),
538
- readCoreAttentionPrefs()
539
- );
540
- return Object.freeze({
541
- schemaVersion: 0, available: true, readOnly: true,
542
- degraded: artifactDegraded || tasks.dropped > 0,
543
- droppedTasks: tasks.dropped,
544
- tasks: Object.freeze({ total: tasks.total, recent: tasks.tasks }),
545
- artifacts: Object.freeze({ total: artifacts.total, recent: artifacts.artifacts }),
546
- userTasks: Object.freeze(userTasks)
547
- });
548
- }), options.signal);
549
- }
550
624
 
551
- /** bounded:全量扫描仅供自动静默派生;超界仍有界退出。 */
552
- const SUCCESSOR_SCAN_LIMIT = 2000;
553
- function listAllCoreTaskRecords(dataRoot) {
554
- const all = [];
555
- let page;
556
- do {
557
- page = listCoreTasks(dataRoot, { offset: all.length, limit: 200, skipInvalid: true });
558
- all.push(...page.tasks);
559
- } while (all.length < page.total && all.length < SUCCESSOR_SCAN_LIMIT);
560
- return all;
561
- }
625
+ /** bounded:全量扫描仅供自动静默派生;超界仍有界退出。 */
626
+ const SUCCESSOR_SCAN_LIMIT = 2000;
627
+ function listAllCoreTaskRecords(dataRoot) {
628
+ const all = [];
629
+ let page;
630
+ do {
631
+ page = listCoreTasks(dataRoot, { offset: all.length, limit: 200, skipInvalid: true });
632
+ all.push(...page.tasks);
633
+ } while (all.length < page.total && all.length < SUCCESSOR_SCAN_LIMIT);
634
+ return all;
635
+ }
562
636
 
563
- /** retriedFrom → 后继任务列表(自动静默只从事实派生,不缓存、不落偏好)。 */
564
- function successorsByRetriedFrom(records) {
565
- const map = new Map();
566
- for (const record of records) {
567
- if (!record.retriedFrom) continue;
568
- if (!map.has(record.retriedFrom)) map.set(record.retriedFrom, []);
569
- map.get(record.retriedFrom).push(record);
637
+ /** retriedFrom → 后继任务列表(自动静默只从事实派生,不缓存、不落偏好)。 */
638
+ function successorsByRetriedFrom(records) {
639
+ const map = new Map();
640
+ for (const record of records) {
641
+ if (!record.retriedFrom) continue;
642
+ if (!map.has(record.retriedFrom)) map.set(record.retriedFrom, []);
643
+ map.get(record.retriedFrom).push(record);
644
+ }
645
+ return map;
570
646
  }
571
- return map;
572
- }
573
647
 
574
- function suppressedByRetrySuccess(taskId, successors) {
575
- return (successors.get(taskId) || [])
576
- .some((record) => record.outcome === 'succeeded' && record.deliveryState === 'ready');
577
- }
648
+ function suppressedByRetrySuccess(taskId, successors) {
649
+ return (successors.get(taskId) || [])
650
+ .some((record) => record.outcome === 'succeeded' && record.deliveryState === 'ready');
651
+ }
578
652
 
579
- /**
580
- * 投影行装饰:合并 Host 处置偏好与"重试成功自动静默"派生(每次都是事实重算)。
581
- * historical 只控制 DSH 分区;canceled 的 Core 五类事实投影仍保持 attention 不变。
582
- */
583
- function decorateCoreUserRows(rows, records, prefs) {
584
- const successors = successorsByRetriedFrom(records);
585
- const recordsById = new Map(records.map((record) => [record.id, record]));
586
- return rows.map((row) => Object.freeze({
587
- ...row,
588
- disposition: attentionDispositionOf(prefs.entries[row.id]),
589
- suppressed: suppressedByRetrySuccess(row.id, successors),
590
- historical: recordsById.get(row.id)?.outcome === 'canceled'
591
- }));
592
- }
653
+ /**
654
+ * 投影行装饰:合并 Host 处置偏好与"重试成功自动静默"派生(每次都是事实重算)。
655
+ * historical 只控制 DSH 分区;canceled 的 Core 五类事实投影仍保持 attention 不变。
656
+ */
657
+ function decorateCoreUserRows(rows, records, prefs) {
658
+ const successors = successorsByRetriedFrom(records);
659
+ const recordsById = new Map(records.map((record) => [record.id, record]));
660
+ return rows.map((row) => Object.freeze({
661
+ ...row,
662
+ disposition: attentionDispositionOf(prefs.entries[row.id]),
663
+ suppressed: suppressedByRetrySuccess(row.id, successors),
664
+ historical: recordsById.get(row.id)?.outcome === 'canceled'
665
+ }));
666
+ }
593
667
 
594
- /** 为 DSH 同源媒体路由读取已验证 hash 的 Core 图片;不创建宿主 URL 或 token。 */
595
- export async function readCoreArtifactMediaForDsh(artifactId, options = {}) {
596
- const id = String(artifactId || '').trim();
597
- if (!/^artifact_[a-f0-9]{24}$/.test(id) || !fs.existsSync(dshCoreDataRoot())) {
598
- throw new DshCoreAdapterError('IRIS_DSH_ARTIFACT_NOT_FOUND', 'Core Artifact 不存在');
599
- }
600
- try {
601
- const result = await withReader((runtime) => runtime.run('inspect', ({ dataRoot }) =>
602
- readCoreArtifactBytes(dataRoot, id)), options.signal);
603
- if (!result.artifact.mediaType.startsWith('image/') && result.artifact.mediaType !== 'video/mp4'
604
- && result.artifact.mediaType !== 'video/webm' && !result.artifact.mediaType.startsWith('audio/')
605
- && result.artifact.mediaType !== 'text/plain' && result.artifact.mediaType !== 'application/json') {
606
- throw new DshCoreAdapterError('IRIS_DSH_ARTIFACT_UNSUPPORTED', '当前 DSH 媒体预览不支持此 Artifact 类型');
668
+ /** 为 DSH 同源媒体路由读取已验证 hash 的 Core 图片;不创建宿主 URL 或 token。 */
669
+ async function readCoreArtifactMediaForDsh(artifactId, options = {}) {
670
+ const id = String(artifactId || '').trim();
671
+ if (!/^artifact_[a-f0-9]{24}$/.test(id) || !fs.existsSync(dataRoot)) {
672
+ throw new DshCoreAdapterError('IRIS_DSH_ARTIFACT_NOT_FOUND', 'Core Artifact 不存在');
673
+ }
674
+ try {
675
+ const result = await withReader((runtime) => runtime.run('inspect', ({ dataRoot }) =>
676
+ readCoreArtifactBytes(dataRoot, id)), options.signal);
677
+ if (!result.artifact.mediaType.startsWith('image/') && result.artifact.mediaType !== 'video/mp4'
678
+ && result.artifact.mediaType !== 'video/webm' && !result.artifact.mediaType.startsWith('audio/')
679
+ && result.artifact.mediaType !== 'text/plain' && result.artifact.mediaType !== 'application/json') {
680
+ throw new DshCoreAdapterError('IRIS_DSH_ARTIFACT_UNSUPPORTED', '当前 DSH 媒体预览不支持此 Artifact 类型');
681
+ }
682
+ return result;
683
+ } catch (error) {
684
+ if (options.signal?.aborted || error?.code === 'IRIS_APP_CLOSED' || error?.code === 'IRIS_CORE_LEASE_INVALID') throw error;
685
+ if (error instanceof DshCoreAdapterError) throw error;
686
+ throw new DshCoreAdapterError('IRIS_DSH_ARTIFACT_NOT_FOUND', 'Core Artifact 不存在');
607
687
  }
608
- return result;
609
- } catch (error) {
610
- if (error instanceof DshCoreAdapterError) throw error;
611
- throw new DshCoreAdapterError('IRIS_DSH_ARTIFACT_NOT_FOUND', 'Core Artifact 不存在');
612
688
  }
613
- }
614
689
 
615
- /**
616
- * 将已经 ready 的 Core 图片投影为 DSH attachment blocks。重复调用只重复宿主展示,
617
- * 不修改 Task/Artifact,也不触发 submit、poll 或 download。
618
- */
619
- export async function projectCoreTaskForDsh(host, taskId, options = {}) {
620
- const attachments = requireHostPort(host, 'attachments', 'Core 任务产物展示');
621
- const materialized = await withReader((runtime) => runtime.run('inspect', ({ dataRoot }) => {
622
- const task = inspectCoreTask(dataRoot, taskId);
623
- if (task.outcome !== 'succeeded' || task.deliveryState !== 'ready' || !task.artifactIds.length) {
624
- throw new DshCoreAdapterError('IRIS_DSH_TASK_NOT_READY', 'Core Task 尚无可展示的本地产物');
690
+ /**
691
+ * 将已经 ready 的 Core 图片投影为 DSH attachment blocks。重复调用只重复宿主展示,
692
+ * 不修改 Task/Artifact,也不触发 submit、poll 或 download。
693
+ */
694
+ async function projectCoreTaskForDsh(host, taskId, options = {}) {
695
+ const attachments = requireHostPort(host, 'attachments', 'Core 任务产物展示');
696
+ const materialized = await withReader((runtime) => runtime.run('inspect', ({ dataRoot }) => {
697
+ const task = inspectCoreTask(dataRoot, taskId);
698
+ if (task.outcome !== 'succeeded' || task.deliveryState !== 'ready' || !task.artifactIds.length) {
699
+ throw new DshCoreAdapterError('IRIS_DSH_TASK_NOT_READY', 'Core Task 尚无可展示的本地产物');
700
+ }
701
+ return {
702
+ task,
703
+ artifacts: task.artifactIds.map((artifactId) => readCoreArtifactBytes(dataRoot, artifactId))
704
+ };
705
+ }), options.signal);
706
+
707
+ const projected = [];
708
+ for (const item of materialized.artifacts) {
709
+ if (options.signal?.aborted) throw abortError();
710
+ if (!item.artifact.mediaType.startsWith('image/')) {
711
+ throw new DshCoreAdapterError('IRIS_DSH_ARTIFACT_UNSUPPORTED', '当前 DSH 投影只支持图片 Artifact');
712
+ }
713
+ let attachment;
714
+ try {
715
+ const extension = item.artifact.mediaType === 'image/jpeg' ? 'jpg' : item.artifact.mediaType.slice(6);
716
+ attachment = await attachments.saveImage({
717
+ data: new Uint8Array(item.bytes),
718
+ mediaType: item.artifact.mediaType,
719
+ name: 'iris-core-' + item.artifact.id + '.' + extension
720
+ });
721
+ } catch (_) {
722
+ throw new DshCoreAdapterError('IRIS_DSH_PROJECTION_FAILED', 'Core 产物无法保存为 DSH attachment');
723
+ }
724
+ options.signal.throwIfAborted();
725
+ if (!attachment || !String(attachment.attachmentId || '').trim()) {
726
+ throw new DshCoreAdapterError('IRIS_DSH_PROJECTION_FAILED', 'DSH attachment 返回无效');
727
+ }
728
+ projected.push(Object.freeze({ artifact: item.artifact, attachment }));
625
729
  }
626
- return {
627
- task,
628
- artifacts: task.artifactIds.map((artifactId) => readCoreArtifactBytes(dataRoot, artifactId))
629
- };
630
- }), options.signal);
631
730
 
632
- const projected = [];
633
- for (const item of materialized.artifacts) {
634
- if (options.signal?.aborted) throw abortError();
635
- if (!item.artifact.mediaType.startsWith('image/')) {
636
- throw new DshCoreAdapterError('IRIS_DSH_ARTIFACT_UNSUPPORTED', '当前 DSH 投影只支持图片 Artifact');
637
- }
638
- let attachment;
639
- try {
640
- const extension = item.artifact.mediaType === 'image/jpeg' ? 'jpg' : item.artifact.mediaType.slice(6);
641
- attachment = await attachments.saveImage({
642
- data: new Uint8Array(item.bytes),
643
- mediaType: item.artifact.mediaType,
644
- name: 'iris-core-' + item.artifact.id + '.' + extension
645
- });
646
- } catch (_) {
647
- throw new DshCoreAdapterError('IRIS_DSH_PROJECTION_FAILED', 'Core 产物无法保存为 DSH attachment');
648
- }
649
- if (!attachment || !String(attachment.attachmentId || '').trim()) {
650
- throw new DshCoreAdapterError('IRIS_DSH_PROJECTION_FAILED', 'DSH attachment 返回无效');
731
+ const blocks = [];
732
+ for (const item of projected) {
733
+ blocks.push(Object.freeze({
734
+ type: 'text',
735
+ text: '[iris] Core 任务完成:' + materialized.task.id
736
+ + '\nartifact: ' + item.artifact.id + '\nattachment: ' + item.attachment.attachmentId
737
+ }));
738
+ blocks.push(Object.freeze({ type: 'image', attachment: item.attachment }));
651
739
  }
652
- projected.push(Object.freeze({ artifact: item.artifact, attachment }));
740
+ return Object.freeze({
741
+ task: materialized.task,
742
+ artifacts: Object.freeze(projected),
743
+ blocks: Object.freeze(blocks)
744
+ });
653
745
  }
654
746
 
655
- const blocks = [];
656
- for (const item of projected) {
657
- blocks.push(Object.freeze({
658
- type: 'text',
659
- text: '[iris] Core 任务完成:' + materialized.task.id
660
- + '\nartifact: ' + item.artifact.id + '\nattachment: ' + item.attachment.attachmentId
661
- }));
662
- blocks.push(Object.freeze({ type: 'image', attachment: item.attachment }));
663
- }
664
- return Object.freeze({
665
- task: materialized.task,
666
- artifacts: Object.freeze(projected),
667
- blocks: Object.freeze(blocks)
747
+ return Object.freeze({ dataRoot, snapshot, dispose, runHostOperation,
748
+ importImageForDsh: (...args) => invoke(importImageForDsh, 0, args),
749
+ cropForDsh: (...args) => invoke(cropForDsh, 0, args),
750
+ htmlForDsh: (...args) => invoke(htmlForDsh, 0, args),
751
+ diffForDsh: (...args) => invoke(diffForDsh, 0, args),
752
+ framesForDsh: (...args) => invoke(framesForDsh, 0, args),
753
+ summaryFramesForDsh: (...args) => invoke(summaryFramesForDsh, 0, args),
754
+ visionImageForDsh: (...args) => invoke(visionImageForDsh, 0, args),
755
+ submitProviderTaskForDsh: (...args) => invoke(submitProviderTaskForDsh, 0, args),
756
+ submitMusicForDsh: (...args) => invoke(submitMusicForDsh, 0, args),
757
+ observeProviderTaskForDsh: (...args) => invoke(observeProviderTaskForDsh, 0, args),
758
+ reobserveProviderTaskForDsh: (...args) => invoke(reobserveProviderTaskForDsh, 0, args),
759
+ redeliverProviderTaskForDsh: (...args) => invoke(redeliverProviderTaskForDsh, 0, args),
760
+ cancelProviderTaskForDsh: (...args) => invoke(cancelProviderTaskForDsh, 0, args),
761
+ disposeCoreTaskAttention: (...args) => invoke(disposeCoreTaskAttention, 0, args),
762
+ retryProviderTaskForDsh: (...args) => invoke(retryProviderTaskForDsh, 0, args),
763
+ watchProviderTaskForDsh: (...args) => invoke(watchProviderTaskForDsh, 0, args),
764
+ resumeProviderTaskWatchesForDsh: (...args) => invoke(resumeProviderTaskWatchesForDsh, 0, args),
765
+ stopProviderTaskWatchesForDsh,
766
+ inspectProviderTaskForDsh: (...args) => invoke(inspectProviderTaskForDsh, 1, args),
767
+ galleryArtifactsForDsh: (...args) => invoke(galleryArtifactsForDsh, 0, args),
768
+ inspectWorkbenchArtifactForDsh: (...args) => invoke(inspectWorkbenchArtifactForDsh, 1, args),
769
+ maintainWorkbenchForDsh: (...args) => invoke(maintainWorkbenchForDsh, 2, args),
770
+ coreSnapshotForDsh: (...args) => invoke(coreSnapshotForDsh, 0, args),
771
+ readCoreArtifactMediaForDsh: (...args) => invoke(readCoreArtifactMediaForDsh, 1, args),
772
+ projectCoreTaskForDsh: (...args) => invoke(projectCoreTaskForDsh, 2, args),
668
773
  });
669
774
  }