@mokuyoaxis/dsh-iris 0.2.6 → 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.
- package/CHANGELOG.md +23 -1
- package/README.en.md +28 -19
- package/README.md +27 -18
- package/bin/dsh-iris.js +140 -26
- package/docs/AGENT_QUICKSTART.md +5 -5
- package/docs/ARCHITECTURE.md +9 -3
- package/docs/CLI_MANAGEMENT.md +1 -1
- package/docs/CORE_RUNTIME_CONTRACT.md +36 -5
- package/docs/CORE_SKILLS.md +62 -0
- package/docs/DSH_COMPATIBILITY.md +4 -4
- package/docs/DSH_CORE_MIGRATION.md +4 -2
- package/docs/DSH_PROVIDER_CONNECTIONS.md +1 -1
- package/docs/DSH_RUNTIME.md +36 -0
- package/docs/FUNCTION_MODEL_POOLS.md +1 -1
- package/docs/HEADLESS_CLI.md +11 -3
- package/docs/HOST_DOCTOR.md +13 -1
- package/docs/IMAGE_FLOW.md +71 -0
- package/docs/LOCAL_API.md +60 -0
- package/docs/LOCAL_API_CONTRACT.md +115 -0
- package/docs/LOCAL_CREATION.md +50 -0
- package/docs/PROVIDER_HEALTH.md +1 -1
- package/docs/ROADMAP.md +14 -2
- package/docs/SECURITY.md +9 -1
- package/docs/SYNC_DELIVERY_RECOVERY.md +2 -0
- package/docs/VISION_CLI.md +1 -1
- package/docs/WEB_PROTOTYPE.md +47 -0
- package/docs/releases/0.2.7.md +62 -0
- package/lib/actions.js +79 -68
- package/lib/api.js +38 -38
- package/lib/application-runtime.js +186 -0
- package/lib/client.js +89 -3
- package/lib/core-artifact-store.js +5 -0
- package/lib/core-maintenance.js +25 -2
- package/lib/core-runtime.js +19 -0
- package/lib/core-tasks.js +62 -8
- package/lib/creation-request-store.js +159 -0
- package/lib/doctor.js +12 -1
- package/lib/dsh-core-adapter.js +666 -572
- package/lib/dsh-host-adapter.js +4 -1
- package/lib/flow-store.js +195 -0
- package/lib/image-flow.js +188 -0
- package/lib/index.js +59 -53
- package/lib/local-api-projection.js +27 -0
- package/lib/local-api.js +381 -0
- package/lib/local-creation.js +85 -0
- package/lib/private-storage.js +12 -0
- package/lib/provider-task-runner.js +22 -2
- package/lib/render-address.js +35 -0
- package/lib/render.js +52 -13
- package/lib/skill-installation.js +199 -0
- package/lib/workbench-artifacts.js +1 -2
- package/lib/workbench-management.js +8 -9
- package/package.json +3 -2
- package/skills/iris-core/SKILL.md +40 -0
- package/skills/iris-core/agents/openai.yaml +4 -0
- package/skills/iris-core/references/cli.md +95 -0
- package/skills/iris-core/references/tasks.md +58 -0
- package/user_guide.md +14 -8
package/lib/dsh-core-adapter.js
CHANGED
|
@@ -6,7 +6,8 @@ import { runtimeProviders, assignments, irisHome, recordRateLimit, recordAvailab
|
|
|
6
6
|
import { withDshProviderConfig } from './dsh-provider-config.js';
|
|
7
7
|
import { createCommandService } from './command-service.js';
|
|
8
8
|
import { createCoreArtifact, inspectCoreArtifact, listCoreArtifacts, listCoreArtifactMetadata, readCoreArtifactBytes } from './core-artifacts.js';
|
|
9
|
-
import {
|
|
9
|
+
import { createApplicationRuntime } from './application-runtime.js';
|
|
10
|
+
import { normalizeCoreOptions } from './core-contract.js';
|
|
10
11
|
import { readSummaryFrames } from './summary-input.js';
|
|
11
12
|
import { readVisionArtifact } from './vision-input.js';
|
|
12
13
|
import { inspectCoreTask, listCoreTasks, recoverCoreTask } from './core-tasks.js';
|
|
@@ -19,18 +20,9 @@ import { parseModelRef } from './models.js';
|
|
|
19
20
|
import { imageCandidatesFromCatalog, musicCandidatesFromCatalog, providerForTaskFromCatalog, providerTaskBinding, transcribeCandidatesFromCatalog, ttsCandidatesFromCatalog, videoCandidatesFromCatalog } from './provider-catalog.js';
|
|
20
21
|
import { generateMusicTask } from './music-generation.js';
|
|
21
22
|
|
|
22
|
-
let writerTail = Promise.resolve();
|
|
23
|
-
const providerWatchers = new Map();
|
|
24
23
|
const DEFAULT_POLL_INTERVAL_MS = 2500;
|
|
25
24
|
const DEFAULT_MAX_WATCH_MS = 20 * 60 * 1000;
|
|
26
25
|
const POLL_ERROR_TOLERANCE = 5;
|
|
27
|
-
let providerHost = () => undefined;
|
|
28
|
-
|
|
29
|
-
/** The Host supplies a fresh optional connection port; no credentials are retained here. */
|
|
30
|
-
export function configureProviderHostForDsh(factory) {
|
|
31
|
-
providerHost = factory;
|
|
32
|
-
return () => { if (providerHost === factory) providerHost = () => undefined; };
|
|
33
|
-
}
|
|
34
26
|
|
|
35
27
|
function createConfiguredProviderAdapter(provider, options = {}) {
|
|
36
28
|
return createProviderAdapter(provider, { ...options, onRateLimit: recordRateLimit, onModelAvailability: recordAvailability });
|
|
@@ -53,628 +45,730 @@ export function dshCoreDataRoot() {
|
|
|
53
45
|
return path.join(irisHome(), 'core-v0');
|
|
54
46
|
}
|
|
55
47
|
|
|
56
|
-
async function withRuntime(mode, callback, signal) {
|
|
57
|
-
if (signal?.aborted) throw abortError();
|
|
58
|
-
const runtime = createCoreRuntime({ dataRoot: dshCoreDataRoot(), mode });
|
|
59
|
-
runtime.start();
|
|
60
|
-
const onAbort = () => { runtime.dispose().catch(() => {}); };
|
|
61
|
-
signal?.addEventListener('abort', onAbort, { once: true });
|
|
62
|
-
try { return await callback(runtime, mode === 'writer' ? createCommandService(runtime) : null); }
|
|
63
|
-
finally {
|
|
64
|
-
signal?.removeEventListener('abort', onAbort);
|
|
65
|
-
await runtime.dispose();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function withWriter(callback, signal) {
|
|
70
|
-
const operation = writerTail.then(() => withRuntime('writer', callback, signal));
|
|
71
|
-
writerTail = operation.catch(() => {});
|
|
72
|
-
return operation;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function withReader(callback, signal) {
|
|
76
|
-
return withRuntime('reader', callback, signal);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** 本地图片与会话附件都交给同一导入Command,不创建生成任务。 */
|
|
80
|
-
export function importImageForDsh(input = {}) {
|
|
81
|
-
return withWriter((_runtime, commands) => commands.execute('artifact.import', input.imageBytes !== undefined
|
|
82
|
-
? { image_bytes: input.imageBytes } : { image_path: input.imagePath }), input.signal);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
48
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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.
|
|
88
51
|
*/
|
|
89
|
-
export
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (input.signal?.aborted) throw Object.assign(new Error('已取消'), { name: 'AbortError' });
|
|
112
|
-
const materialized = await runtime.run('inspect', ({ dataRoot }) =>
|
|
113
|
-
readCoreArtifactBytes(dataRoot, result.artifact.id));
|
|
114
|
-
return Object.freeze({
|
|
115
|
-
artifact: result.artifact,
|
|
116
|
-
bytes: materialized.bytes,
|
|
117
|
-
mediaType: result.artifact.mediaType,
|
|
118
|
-
width: result.artifact.metadata.width,
|
|
119
|
-
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(() => {});
|
|
120
74
|
});
|
|
121
|
-
|
|
122
|
-
}
|
|
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
|
+
}
|
|
123
121
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return runtime.run('inspect', ({ dataRoot }) => readCoreArtifactBytes(dataRoot, result.artifact.id));
|
|
131
|
-
}, input.signal);
|
|
132
|
-
}
|
|
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); }
|
|
133
128
|
|
|
134
|
-
/**
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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: {}
|
|
143
149
|
}));
|
|
144
|
-
commandInput
|
|
150
|
+
commandInput = { artifact_id: source.id };
|
|
145
151
|
} else {
|
|
146
|
-
commandInput
|
|
152
|
+
commandInput = { image_path: input.imagePath };
|
|
147
153
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
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
|
+
}
|
|
156
173
|
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
});
|
|
167
|
-
const frames = await runtime.run('inspect', ({ dataRoot }) => result.artifacts.map((artifact) => ({
|
|
168
|
-
...readCoreArtifactBytes(dataRoot, artifact.id),
|
|
169
|
-
mediaType: artifact.mediaType,
|
|
170
|
-
...artifact.metadata
|
|
171
|
-
})));
|
|
172
|
-
if (input.signal?.aborted) throw abortError();
|
|
173
|
-
return Object.freeze({ media: result.media, frames: Object.freeze(frames.map(Object.freeze)) });
|
|
174
|
-
}, input.signal);
|
|
175
|
-
}
|
|
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
|
+
}
|
|
176
183
|
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
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
|
+
}
|
|
181
206
|
|
|
182
|
-
/**
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
}
|
|
186
226
|
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
candidates: input.candidates,
|
|
192
|
-
providerInput: input.providerInput,
|
|
193
|
-
sourceArtifactId: input.sourceArtifactId
|
|
194
|
-
}), input.signal);
|
|
195
|
-
}
|
|
227
|
+
/** 已有抽帧 Artifact 直接进入共享摘要;reader 不取得写者租约。 */
|
|
228
|
+
function summaryFramesForDsh(input = {}) {
|
|
229
|
+
return withReader(runtime => readSummaryFrames(runtime, input.frameArtifactIds, input.signal), input.signal);
|
|
230
|
+
}
|
|
196
231
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}), input.signal);
|
|
202
|
-
}
|
|
232
|
+
/** 共享单图读取器用于 look/locate/OCR,Host 不解释 Core 对象路径。 */
|
|
233
|
+
function visionImageForDsh(input = {}) {
|
|
234
|
+
return withReader(runtime => readVisionArtifact(runtime, input.artifactId, input.signal), input.signal);
|
|
235
|
+
}
|
|
203
236
|
|
|
204
|
-
/**
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
+
}
|
|
210
246
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
+
});
|
|
234
285
|
});
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
286
|
+
}, input.signal);
|
|
287
|
+
}
|
|
238
288
|
|
|
239
|
-
/** D1 人工 reobserve:显式单步 poll 一次,绝不 submit。 */
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
289
|
+
/** D1 人工 reobserve:显式单步 poll 一次,绝不 submit。 */
|
|
290
|
+
function reobserveProviderTaskForDsh(input = {}) {
|
|
291
|
+
return manualTaskCommandForDsh('task.reobserve', input);
|
|
292
|
+
}
|
|
243
293
|
|
|
244
|
-
/**
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
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
|
+
}
|
|
252
302
|
|
|
253
|
-
/**
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
303
|
+
/**
|
|
304
|
+
* D3 人工 cancel:只有 Provider 明确确认才写 outcome=canceled;不支持或无法确认
|
|
305
|
+
* 时保持真实状态(详见 core-tasks 的 recordCoreCancelResult 三分支)。
|
|
306
|
+
*/
|
|
307
|
+
function cancelProviderTaskForDsh(input = {}) {
|
|
308
|
+
return manualTaskCommandForDsh('task.cancel', input);
|
|
309
|
+
}
|
|
260
310
|
|
|
261
|
-
/**
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
// 任务必须存在(reader 只读);不存在统一 404。
|
|
272
|
-
const task = await inspectProviderTaskForDsh(taskId, { signal: input.signal });
|
|
273
|
-
const applied = applyCoreAttentionAction(task.id, action);
|
|
274
|
-
const row = await withReader((runtime) => runtime.run('inspect', ({ dataRoot }) => {
|
|
275
|
-
const artifacts = [];
|
|
276
|
-
for (const artifactId of task.artifactIds) {
|
|
277
|
-
try { artifacts.push(inspectCoreArtifact(dataRoot, artifactId)); }
|
|
278
|
-
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 无效');
|
|
279
320
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
)[0];
|
|
285
|
-
}), input.signal);
|
|
286
|
-
return Object.freeze({ taskId: task.id, changed: applied.changed, row });
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* D4 人工 retry as new task:产生新的真实计费。候选链按当前 assignments/池实况
|
|
291
|
-
* 重新解析(不绑定旧 Provider/binding——旧配置可能正是失败原因),modelRef 可
|
|
292
|
-
* 显式指定。prompt 必须由调用方重新提供(Core 记录不持久化 Prompt)。
|
|
293
|
-
*/
|
|
294
|
-
export function retryProviderTaskForDsh(input = {}) {
|
|
295
|
-
const taskId = String(input.taskId || '').trim();
|
|
296
|
-
if (!/^task_[a-f0-9]{24}$/.test(taskId)) {
|
|
297
|
-
return Promise.reject(new DshCoreAdapterError('IRIS_DSH_TASK_INVALID', 'Core Task ID 无效'));
|
|
298
|
-
}
|
|
299
|
-
return withWriter(async (runtime) => {
|
|
300
|
-
const commands = createCommandService(runtime, {
|
|
301
|
-
resolveTaskCandidates: ({ capability, modelRef }) => withDshProviderConfig(providerHost(), async () => {
|
|
302
|
-
if (!modelRef) await refreshAutomaticDirectories({ capability: { image: 'image-gen', video: 'video-gen', music: 'music-gen' }[capability] || capability, signal: input.signal });
|
|
303
|
-
const catalog = { providers: runtimeProviders(), assignments: assignments() };
|
|
304
|
-
const ref = modelRef || undefined;
|
|
305
|
-
const candidates = capability === 'video'
|
|
306
|
-
? videoCandidatesFromCatalog(catalog, ref)
|
|
307
|
-
: capability === 'tts'
|
|
308
|
-
? ttsCandidatesFromCatalog(catalog, ref)
|
|
309
|
-
: capability === 'music'
|
|
310
|
-
? musicCandidatesFromCatalog(catalog, ref)
|
|
311
|
-
: capability === 'transcribe'
|
|
312
|
-
? transcribeCandidatesFromCatalog(catalog, ref)
|
|
313
|
-
: imageCandidatesFromCatalog(catalog, ref);
|
|
314
|
-
return candidates.map((route) => ({
|
|
315
|
-
adapter: createConfiguredProviderAdapter(route.provider),
|
|
316
|
-
model: route.modelRef,
|
|
317
|
-
selectionReason: route.selectionReason,
|
|
318
|
-
providerBinding: providerTaskBinding(route.provider)
|
|
319
|
-
}));
|
|
320
|
-
}, { signal: input.signal, fresh: true })
|
|
321
|
-
});
|
|
322
|
-
const result = await commands.execute('task.retry', {
|
|
323
|
-
task_id: taskId,
|
|
324
|
-
provider_input: input.text !== undefined
|
|
325
|
-
? { text: input.text, ...(input.voice !== undefined ? { voice: input.voice } : {}) }
|
|
326
|
-
: input.audio_url !== undefined
|
|
327
|
-
? { audio_url: input.audio_url }
|
|
328
|
-
: { prompt: input.prompt, ...(input.sourceArtifactId !== undefined ? { source_artifact_id: input.sourceArtifactId } : {}) },
|
|
329
|
-
model_ref: input.modelRef,
|
|
330
|
-
confirm_billing: input.confirmBilling
|
|
331
|
-
});
|
|
332
|
-
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 }) => {
|
|
333
325
|
const artifacts = [];
|
|
334
|
-
for (const artifactId of
|
|
326
|
+
for (const artifactId of task.artifactIds) {
|
|
335
327
|
try { artifacts.push(inspectCoreArtifact(dataRoot, artifactId)); }
|
|
336
328
|
catch (_) { /* 缺失媒体在投影行里降级为不可用 */ }
|
|
337
329
|
}
|
|
338
|
-
return
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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 })
|
|
342
371
|
});
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
+
}
|
|
346
396
|
|
|
347
|
-
/** DSH Host 观察接管的媒体能力白名单:图片、视频与转写共用 Task/Attempt 事实轴。 */
|
|
348
|
-
const WATCHABLE_CAPABILITIES = new Set(['image', 'video', 'transcribe']);
|
|
349
|
-
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 });
|
|
350
400
|
|
|
351
|
-
function observableProviderTask(task) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
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
|
+
}
|
|
357
407
|
|
|
358
|
-
async function configuredAdapterForTask(task) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
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
|
+
}
|
|
364
415
|
|
|
365
|
-
function stopProviderWatch(taskId, expectedState) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
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
|
+
}
|
|
372
423
|
|
|
373
|
-
/**
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
if (providerWatchers.has(taskId)) return false;
|
|
383
|
-
const initial = await inspectProviderTaskForDsh(taskId, { signal: input.signal });
|
|
384
|
-
if (!observableProviderTask(initial) || providerWatchers.has(taskId)) return false;
|
|
385
|
-
await configuredAdapterForTask(initial); // binding/模型漂移在定时器和网络前失败
|
|
386
|
-
|
|
387
|
-
const state = {
|
|
388
|
-
timer: null,
|
|
389
|
-
controller: new AbortController(),
|
|
390
|
-
startedAt: Date.now(),
|
|
391
|
-
errors: 0,
|
|
392
|
-
intervalMs: Math.max(5, Number(input.intervalMs)
|
|
393
|
-
|| WATCH_INTERVAL_BY_CAPABILITY[initial.capability] || DEFAULT_POLL_INTERVAL_MS),
|
|
394
|
-
maxWatchMs: Math.max(50, Number(input.maxWatchMs) || DEFAULT_MAX_WATCH_MS)
|
|
395
|
-
};
|
|
396
|
-
providerWatchers.set(taskId, state);
|
|
397
|
-
|
|
398
|
-
const schedule = (delay = state.intervalMs) => {
|
|
399
|
-
if (providerWatchers.get(taskId) !== state) return;
|
|
400
|
-
state.timer = setTimeout(() => { tick().catch(() => stopProviderWatch(taskId, state)); }, delay);
|
|
401
|
-
if (state.timer.unref) state.timer.unref();
|
|
402
|
-
};
|
|
403
|
-
const tick = async () => {
|
|
404
|
-
if (providerWatchers.get(taskId) !== state) return;
|
|
405
|
-
const before = await inspectProviderTaskForDsh(taskId, { signal: state.controller.signal });
|
|
406
|
-
if (providerWatchers.get(taskId) !== state) return;
|
|
407
|
-
if (!observableProviderTask(before)) return stopProviderWatch(taskId, state);
|
|
408
|
-
if (Date.now() - state.startedAt > state.maxWatchMs) {
|
|
409
|
-
console.warn('[iris] Core Task 自动观察已达到本轮时限,保留远端受理事实:' + taskId);
|
|
410
|
-
return stopProviderWatch(taskId, state);
|
|
411
|
-
}
|
|
412
|
-
let adapter;
|
|
413
|
-
try {
|
|
414
|
-
adapter = await configuredAdapterForTask(before);
|
|
415
|
-
} catch (error) {
|
|
416
|
-
console.warn('[iris] Core Task 无法按原 Provider binding 恢复观察:' + taskId
|
|
417
|
-
+ ' · ' + String(error?.code || error?.message || error));
|
|
418
|
-
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 无效');
|
|
419
432
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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;
|
|
428
496
|
if (state.errors >= POLL_ERROR_TOLERANCE) {
|
|
429
|
-
console.warn('[iris] Core Task
|
|
497
|
+
console.warn('[iris] Core Task Provider 观察连续失败,保留事实供人工继续:' + taskId);
|
|
430
498
|
return stopProviderWatch(taskId, state);
|
|
431
499
|
}
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
else state.errors = 0;
|
|
438
|
-
if (state.errors >= POLL_ERROR_TOLERANCE) {
|
|
439
|
-
console.warn('[iris] Core Task Provider 观察连续失败,保留事实供人工继续:' + taskId);
|
|
440
|
-
return stopProviderWatch(taskId, state);
|
|
441
|
-
}
|
|
442
|
-
schedule();
|
|
443
|
-
};
|
|
444
|
-
schedule(Math.min(500, state.intervalMs));
|
|
445
|
-
return true;
|
|
446
|
-
}
|
|
500
|
+
schedule();
|
|
501
|
+
};
|
|
502
|
+
schedule(Math.min(500, state.intervalMs));
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
447
505
|
|
|
448
|
-
/** DSH 启动时只接管仍有明确远端受理证据的 Core 图片 Task。 */
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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));
|
|
477
548
|
}
|
|
478
|
-
if (await watchProviderTaskForDsh({
|
|
479
|
-
taskId: task.id,
|
|
480
|
-
intervalMs: options.intervalMs,
|
|
481
|
-
maxWatchMs: options.maxWatchMs
|
|
482
|
-
})) resumed.push(task.id);
|
|
483
|
-
} catch (error) {
|
|
484
|
-
console.warn('[iris] Core Task 启动接管失败:' + original.id
|
|
485
|
-
+ ' · ' + String(error?.code || error?.message || error));
|
|
486
549
|
}
|
|
550
|
+
return Object.freeze(resumed);
|
|
487
551
|
}
|
|
488
|
-
return Object.freeze(resumed);
|
|
489
|
-
}
|
|
490
552
|
|
|
491
|
-
/** 插件 Fiber 释放时停止 Host 定时器并中止在途观察;不请求远端取消或撤销既有受理/成功事实。 */
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
/** 读取 Core Task 事实;reader 不初始化、修复或更新记录。 */
|
|
497
|
-
export function inspectProviderTaskForDsh(taskId, options = {}) {
|
|
498
|
-
return withReader((runtime) => runtime.run('inspect', ({ dataRoot }) =>
|
|
499
|
-
inspectCoreTask(dataRoot, taskId)), options.signal);
|
|
500
|
-
}
|
|
553
|
+
/** 插件 Fiber 释放时停止 Host 定时器并中止在途观察;不请求远端取消或撤销既有受理/成功事实。 */
|
|
554
|
+
function stopProviderTaskWatchesForDsh() {
|
|
555
|
+
for (const taskId of [...providerWatchers.keys()]) stopProviderWatch(taskId);
|
|
556
|
+
return Promise.allSettled([...polls]);
|
|
557
|
+
}
|
|
501
558
|
|
|
502
|
-
/**
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
559
|
+
/** 读取 Core Task 事实;reader 不初始化、修复或更新记录。 */
|
|
560
|
+
function inspectProviderTaskForDsh(taskId, options = {}) {
|
|
561
|
+
return withReader((runtime) => runtime.run('inspect', ({ dataRoot }) =>
|
|
562
|
+
inspectCoreTask(dataRoot, taskId)), options.signal);
|
|
563
|
+
}
|
|
508
564
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
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
|
+
}
|
|
513
571
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
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);
|
|
518
575
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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);
|
|
522
587
|
}
|
|
523
|
-
return withReader(runtime => createCommandService(runtime).execute(command, input), options.signal);
|
|
524
|
-
}
|
|
525
588
|
|
|
526
|
-
/**
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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);
|
|
539
623
|
}
|
|
540
|
-
return withReader((runtime) => runtime.run('inspect', ({ dataRoot }) => {
|
|
541
|
-
const tasks = listCoreTasks(dataRoot, { limit: options.limit || 12, skipInvalid: true });
|
|
542
|
-
let artifacts = { total: 0, artifacts: Object.freeze([]) };
|
|
543
|
-
let artifactDegraded = false;
|
|
544
|
-
try { artifacts = listCoreArtifacts(dataRoot, { limit: options.limit || 12 }); }
|
|
545
|
-
catch (_) { artifactDegraded = true; }
|
|
546
|
-
const userTasks = decorateCoreUserRows(
|
|
547
|
-
tasks.tasks.map((task) => projectCoreTaskUserRow(task, artifacts.artifacts)),
|
|
548
|
-
listAllCoreTaskRecords(dataRoot),
|
|
549
|
-
readCoreAttentionPrefs()
|
|
550
|
-
);
|
|
551
|
-
return Object.freeze({
|
|
552
|
-
schemaVersion: 0, available: true, readOnly: true,
|
|
553
|
-
degraded: artifactDegraded || tasks.dropped > 0,
|
|
554
|
-
droppedTasks: tasks.dropped,
|
|
555
|
-
tasks: Object.freeze({ total: tasks.total, recent: tasks.tasks }),
|
|
556
|
-
artifacts: Object.freeze({ total: artifacts.total, recent: artifacts.artifacts }),
|
|
557
|
-
userTasks: Object.freeze(userTasks)
|
|
558
|
-
});
|
|
559
|
-
}), options.signal);
|
|
560
|
-
}
|
|
561
624
|
|
|
562
|
-
/** bounded:全量扫描仅供自动静默派生;超界仍有界退出。 */
|
|
563
|
-
const SUCCESSOR_SCAN_LIMIT = 2000;
|
|
564
|
-
function listAllCoreTaskRecords(dataRoot) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
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
|
+
}
|
|
573
636
|
|
|
574
|
-
/** retriedFrom → 后继任务列表(自动静默只从事实派生,不缓存、不落偏好)。 */
|
|
575
|
-
function successorsByRetriedFrom(records) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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;
|
|
581
646
|
}
|
|
582
|
-
return map;
|
|
583
|
-
}
|
|
584
647
|
|
|
585
|
-
function suppressedByRetrySuccess(taskId, successors) {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
648
|
+
function suppressedByRetrySuccess(taskId, successors) {
|
|
649
|
+
return (successors.get(taskId) || [])
|
|
650
|
+
.some((record) => record.outcome === 'succeeded' && record.deliveryState === 'ready');
|
|
651
|
+
}
|
|
589
652
|
|
|
590
|
-
/**
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
function decorateCoreUserRows(rows, records, prefs) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
}
|
|
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
|
+
}
|
|
604
667
|
|
|
605
|
-
/** 为 DSH 同源媒体路由读取已验证 hash 的 Core 图片;不创建宿主 URL 或 token。 */
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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 不存在');
|
|
618
687
|
}
|
|
619
|
-
return result;
|
|
620
|
-
} catch (error) {
|
|
621
|
-
if (error instanceof DshCoreAdapterError) throw error;
|
|
622
|
-
throw new DshCoreAdapterError('IRIS_DSH_ARTIFACT_NOT_FOUND', 'Core Artifact 不存在');
|
|
623
688
|
}
|
|
624
|
-
}
|
|
625
689
|
|
|
626
|
-
/**
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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 }));
|
|
636
729
|
}
|
|
637
|
-
return {
|
|
638
|
-
task,
|
|
639
|
-
artifacts: task.artifactIds.map((artifactId) => readCoreArtifactBytes(dataRoot, artifactId))
|
|
640
|
-
};
|
|
641
|
-
}), options.signal);
|
|
642
730
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const extension = item.artifact.mediaType === 'image/jpeg' ? 'jpg' : item.artifact.mediaType.slice(6);
|
|
652
|
-
attachment = await attachments.saveImage({
|
|
653
|
-
data: new Uint8Array(item.bytes),
|
|
654
|
-
mediaType: item.artifact.mediaType,
|
|
655
|
-
name: 'iris-core-' + item.artifact.id + '.' + extension
|
|
656
|
-
});
|
|
657
|
-
} catch (_) {
|
|
658
|
-
throw new DshCoreAdapterError('IRIS_DSH_PROJECTION_FAILED', 'Core 产物无法保存为 DSH attachment');
|
|
659
|
-
}
|
|
660
|
-
if (!attachment || !String(attachment.attachmentId || '').trim()) {
|
|
661
|
-
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 }));
|
|
662
739
|
}
|
|
663
|
-
|
|
740
|
+
return Object.freeze({
|
|
741
|
+
task: materialized.task,
|
|
742
|
+
artifacts: Object.freeze(projected),
|
|
743
|
+
blocks: Object.freeze(blocks)
|
|
744
|
+
});
|
|
664
745
|
}
|
|
665
746
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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),
|
|
679
773
|
});
|
|
680
774
|
}
|