@mokuyoaxis/dsh-iris 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh/skills/iris-compose-media/SKILL.md +23 -49
- package/.dsh/skills/iris-compose-media/references/task-v2-recovery.md +30 -0
- package/.dsh/skills/iris-compose-media/references/workflows.md +38 -0
- package/.dsh/skills/iris-verify-ui/SKILL.md +3 -25
- package/.dsh/skills/iris-verify-ui/agents/openai.yaml +4 -0
- package/.dsh/skills/iris-verify-ui/references/verdict-and-degradation.md +31 -0
- package/CHANGELOG.md +77 -2
- package/README.en.md +98 -19
- package/README.md +98 -19
- package/bin/dsh-iris.js +392 -13
- package/docs/AGENT_QUICKSTART.md +35 -0
- package/docs/ARCHITECTURE.md +82 -52
- package/docs/ARTIFACT_LIBRARY.md +24 -0
- package/docs/ARTIFACT_MANIFEST.md +60 -0
- package/docs/CORE_RUNTIME_CONTRACT.md +83 -0
- package/docs/DSH_CORE_MIGRATION.md +106 -0
- package/docs/DSH_RC2_ADAPTATION.md +36 -0
- package/docs/HEADLESS_CLI.md +217 -0
- package/docs/HOST_ADAPTER_CONTRACT.md +105 -0
- package/docs/HOST_DOCTOR.md +49 -0
- package/docs/MODEL_PORT_CONTRACT.md +300 -0
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +138 -0
- package/docs/PROVIDER_HEALTH.md +39 -0
- package/docs/PROVIDER_RUNTIME_HARNESS.md +33 -0
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +2 -2
- package/docs/ROADMAP.md +42 -76
- package/docs/SECURITY.md +10 -5
- package/docs/TASK_SEMANTICS.md +3 -2
- package/docs/assets/diagrams/iris-architecture.png +0 -0
- package/docs/assets/diagrams/iris-architecture.svg +5 -5
- package/docs/assets/logo/README.md +55 -0
- package/docs/assets/logo/iris-bubble-concept.svg +67 -0
- package/docs/assets/logo/iris-bubble.svg +14 -0
- package/docs/assets/logo/iris-interim-flower.png +0 -0
- package/docs/assets/logo/iris-logo-mono-inverse.svg +78 -0
- package/docs/assets/logo/iris-logo-mono.svg +78 -0
- package/docs/assets/logo/iris-logo.svg +78 -0
- package/docs/assets/logo/iris-mark-mono-inverse.svg +42 -0
- package/docs/assets/logo/iris-mark-mono.svg +42 -0
- package/docs/assets/logo/iris-mark.svg +42 -0
- package/docs/releases/0.1.4.md +59 -0
- package/docs/releases/0.2.0.md +48 -0
- package/lib/actions.js +673 -525
- package/lib/adapters.js +58 -21
- package/lib/api.js +182 -16
- package/lib/artifacts.js +239 -0
- package/lib/bundled-skills.js +5 -5
- package/lib/client.js +1116 -86
- package/lib/command-service.js +420 -0
- package/lib/config.js +245 -49
- package/lib/core-artifact-store.js +598 -0
- package/lib/core-artifacts.js +4 -0
- package/lib/core-attention.js +103 -0
- package/lib/core-contract.js +105 -0
- package/lib/core-doctor.js +41 -0
- package/lib/core-lease-recovery.js +170 -0
- package/lib/core-runtime.js +232 -0
- package/lib/core-tasks.js +560 -0
- package/lib/core-user-projection.js +192 -0
- package/lib/doctor.js +260 -30
- package/lib/dsh-core-adapter.js +599 -0
- package/lib/dsh-host-adapter.js +240 -0
- package/lib/generation-input.js +95 -0
- package/lib/guard.js +3 -3
- package/lib/host-contract.js +131 -0
- package/lib/host-runtime.js +156 -0
- package/lib/index.js +255 -166
- package/lib/locate.js +1 -1
- package/lib/media.js +16 -6
- package/lib/model-invoker.js +218 -0
- package/lib/model-port-contract.js +333 -0
- package/lib/models.js +97 -11
- package/lib/prompt-optimizer.js +10 -20
- package/lib/provider-adapter.js +397 -0
- package/lib/provider-adapters.js +342 -0
- package/lib/provider-catalog.js +171 -0
- package/lib/provider-contract.js +72 -3
- package/lib/provider-health.js +59 -0
- package/lib/provider-protocol.js +51 -2
- package/lib/provider-task-runner.js +328 -0
- package/lib/render.js +43 -12
- package/lib/tasks.js +45 -9
- package/lib/vision.js +51 -56
- package/package.json +20 -14
- package/user_guide.md +67 -12
- package/docs/assets/diagrams/iris-architecture.drawio +0 -1
- package/docs/assets/diagrams/iris-task-lifecycle.drawio +0 -1
- package/docs/assets/diagrams/iris-workflow-compose.drawio +0 -1
- package/docs/releases/0.1.1.md +0 -51
- package/docs/releases/0.1.2.md +0 -26
- package/docs/releases/0.1.3.md +0 -30
package/bin/dsh-iris.js
CHANGED
|
@@ -1,23 +1,402 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { createCommandService } from '../lib/command-service.js';
|
|
4
|
+
import { createCoreRuntime } from '../lib/core-runtime.js';
|
|
5
|
+
import { recoverCoreWriterLease } from '../lib/core-lease-recovery.js';
|
|
2
6
|
import { doctor, formatDoctorReport } from '../lib/doctor.js';
|
|
7
|
+
import { loadProviderCatalog, providerCatalogSnapshot, catalogCapabilitySnapshot, imageCandidatesFromCatalog, providerForTaskFromCatalog, providerTaskBinding, transcribeCandidatesFromCatalog, ttsCandidatesFromCatalog, videoCandidatesFromCatalog } from '../lib/provider-catalog.js';
|
|
8
|
+
import { createConfiguredProviderAdapter } from '../lib/provider-adapters.js';
|
|
9
|
+
import { prepareProviderInput } from '../lib/provider-adapter.js';
|
|
10
|
+
import { createProviderTaskRunner } from '../lib/provider-task-runner.js';
|
|
11
|
+
import { GenerationInputError, normalizeGenerationInput } from '../lib/generation-input.js';
|
|
12
|
+
|
|
13
|
+
class CliUsageError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'CliUsageError';
|
|
17
|
+
this.code = 'IRIS_CLI_USAGE';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
3
20
|
|
|
4
21
|
function usage() {
|
|
5
22
|
return [
|
|
6
|
-
'Usage:
|
|
23
|
+
'Usage:',
|
|
24
|
+
' dsh-iris doctor [--data-root <absolute-path>] [--json]',
|
|
25
|
+
' dsh-iris runtime recover --data-root <absolute-path> --confirm-stale-pid <pid>',
|
|
26
|
+
' dsh-iris providers list --provider-config <absolute-path>',
|
|
27
|
+
' dsh-iris capabilities list --provider-config <absolute-path>',
|
|
28
|
+
' dsh-iris run crop --data-root <absolute-path> --input <json>',
|
|
29
|
+
' dsh-iris media diff --data-root <absolute-path> --input <json>',
|
|
30
|
+
' dsh-iris media frames --data-root <absolute-path> --input <json>',
|
|
31
|
+
' dsh-iris run image --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
32
|
+
' dsh-iris run video --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
33
|
+
' dsh-iris run tts --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
34
|
+
' dsh-iris run transcribe --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
35
|
+
' dsh-iris task inspect <id> --data-root <absolute-path>',
|
|
36
|
+
' dsh-iris task list --data-root <absolute-path>',
|
|
37
|
+
' dsh-iris task observe <id> --data-root <absolute-path> --provider-config <absolute-path>',
|
|
38
|
+
' dsh-iris task redeliver <id> --data-root <absolute-path> --provider-config <absolute-path>',
|
|
39
|
+
' dsh-iris task cancel <id> --data-root <absolute-path> --provider-config <absolute-path>',
|
|
40
|
+
' dsh-iris task retry <id> --data-root <absolute-path> --provider-config <absolute-path> --input <json> --confirm-billing [--model-ref <providerId::modelId>]',
|
|
41
|
+
' dsh-iris artifact inspect <id> --data-root <absolute-path>',
|
|
42
|
+
' dsh-iris artifact list --data-root <absolute-path>',
|
|
43
|
+
' dsh-iris artifact export <id> --data-root <absolute-path> --output <absolute-path>',
|
|
44
|
+
' dsh-iris artifact rebuild --data-root <absolute-path>',
|
|
7
45
|
'',
|
|
8
|
-
'
|
|
46
|
+
'Commands never start DSH. run image and the task observe/redeliver/cancel/retry verbs contact a provider only when explicitly requested; credentials come from a private file. observe polls once, redeliver only re-downloads, and cancel is recorded as canceled only when the provider explicitly confirms; none of them ever submits an existing task again. task retry creates a NEW task that may incur duplicate generation billing and requires --confirm-billing; the prompt must be supplied again because Core never persists prompts.'
|
|
9
47
|
].join('\n');
|
|
10
48
|
}
|
|
11
49
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
50
|
+
function flags(args, allowed) {
|
|
51
|
+
const parsed = {};
|
|
52
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
53
|
+
const flag = args[index];
|
|
54
|
+
const name = typeof flag === 'string' && flag.startsWith('--') ? flag.slice(2) : '';
|
|
55
|
+
if (!name || !allowed.includes(name) || index + 1 >= args.length || String(args[index + 1]).startsWith('--')) {
|
|
56
|
+
throw new CliUsageError('参数无效:' + String(flag || ''));
|
|
57
|
+
}
|
|
58
|
+
if (Object.prototype.hasOwnProperty.call(parsed, name)) throw new CliUsageError('参数重复:--' + name);
|
|
59
|
+
parsed[name] = args[index + 1];
|
|
60
|
+
}
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function required(value, flag) {
|
|
65
|
+
if (!String(value || '').trim()) throw new CliUsageError('缺少参数:--' + flag);
|
|
66
|
+
return String(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function jsonInput(value) {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(required(value, 'input'));
|
|
72
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('not object');
|
|
73
|
+
return parsed;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof CliUsageError) throw error;
|
|
76
|
+
throw new CliUsageError('--input 必须是 JSON 对象');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function generationInput(capability, input, options) {
|
|
81
|
+
try { return normalizeGenerationInput(capability, input, options); }
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error instanceof GenerationInputError) throw new CliUsageError(error.message);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** C-8: catchable signals dispose active runtimes before the CLI exits. */
|
|
89
|
+
const activeRuntimes = new Set();
|
|
90
|
+
let shuttingDown = false;
|
|
91
|
+
|
|
92
|
+
function registerRuntime(runtime) {
|
|
93
|
+
activeRuntimes.add(runtime);
|
|
94
|
+
return () => activeRuntimes.delete(runtime);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function disposeActiveRuntimes() {
|
|
98
|
+
await Promise.allSettled([...activeRuntimes].map((runtime) => runtime.dispose().catch(() => {})));
|
|
99
|
+
activeRuntimes.clear();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
103
|
+
process.on(signal, () => {
|
|
104
|
+
const exitCode = signal === 'SIGINT' ? 130 : 143;
|
|
105
|
+
if (shuttingDown) process.exit(exitCode);
|
|
106
|
+
shuttingDown = true;
|
|
107
|
+
disposeActiveRuntimes()
|
|
108
|
+
.catch(() => {})
|
|
109
|
+
.finally(() => process.exit(exitCode));
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function withRawRuntime(mode, dataRoot, callback) {
|
|
114
|
+
const runtime = createCoreRuntime({ dataRoot: required(dataRoot, 'data-root'), mode });
|
|
115
|
+
const unregister = registerRuntime(runtime);
|
|
116
|
+
try {
|
|
117
|
+
runtime.start();
|
|
118
|
+
return await callback(runtime);
|
|
119
|
+
} finally {
|
|
120
|
+
try { await runtime.dispose(); } finally { unregister(); }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function withRuntime(mode, dataRoot, callback, ports = {}) {
|
|
125
|
+
return withRawRuntime(mode, dataRoot, (runtime) => callback(createCommandService(runtime, ports)));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function main(args) {
|
|
129
|
+
if (!args.length || args.includes('--help') || args.includes('-h')) {
|
|
130
|
+
console.log(usage());
|
|
131
|
+
return args.length ? 0 : 2;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (args[0] === 'doctor') {
|
|
135
|
+
const rest = args.slice(1);
|
|
136
|
+
if (rest.filter((arg) => arg === '--json').length > 1) throw new CliUsageError('参数重复:--json');
|
|
137
|
+
const options = flags(rest.filter((arg) => arg !== '--json'), ['data-root']);
|
|
138
|
+
const report = await doctor({ ...(options['data-root'] ? { dataRoot: options['data-root'] } : {}) });
|
|
139
|
+
console.log(rest.includes('--json') ? JSON.stringify(report, null, 2) : formatDoctorReport(report));
|
|
140
|
+
return report.exitCode;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (args[0] === 'runtime' && args[1] === 'recover') {
|
|
144
|
+
const options = flags(args.slice(2), ['data-root', 'confirm-stale-pid']);
|
|
145
|
+
const confirmStalePid = Number(required(options['confirm-stale-pid'], 'confirm-stale-pid'));
|
|
146
|
+
if (!Number.isSafeInteger(confirmStalePid) || confirmStalePid <= 0) {
|
|
147
|
+
throw new CliUsageError('--confirm-stale-pid 必须是 Doctor 报告中的正整数 PID');
|
|
148
|
+
}
|
|
149
|
+
const result = recoverCoreWriterLease(required(options['data-root'], 'data-root'), { confirmStalePid });
|
|
150
|
+
console.log(JSON.stringify(result, null, 2));
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (['providers', 'capabilities'].includes(args[0]) && args[1] === 'list') {
|
|
155
|
+
const options = flags(args.slice(2), ['provider-config']);
|
|
156
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
157
|
+
const report = args[0] === 'providers' ? providerCatalogSnapshot(catalog) : catalogCapabilitySnapshot(catalog);
|
|
158
|
+
console.log(JSON.stringify(report, null, 2));
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (args[0] === 'media' && ['diff', 'frames'].includes(args[1])) {
|
|
163
|
+
const options = flags(args.slice(2), ['data-root', 'input']);
|
|
164
|
+
const input = jsonInput(options.input);
|
|
165
|
+
const command = args[1] === 'diff' ? 'media.diff' : 'media.frames';
|
|
166
|
+
const result = await withRuntime('writer', options['data-root'], (commands) =>
|
|
167
|
+
commands.execute(command, input));
|
|
168
|
+
console.log(JSON.stringify(result, null, 2));
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (args[0] === 'run' && args[1] === 'crop') {
|
|
173
|
+
const options = flags(args.slice(2), ['data-root', 'input']);
|
|
174
|
+
const input = jsonInput(options.input);
|
|
175
|
+
const result = await withRuntime('writer', options['data-root'], (commands) =>
|
|
176
|
+
commands.execute('crop', input));
|
|
177
|
+
console.log(JSON.stringify(result, null, 2));
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (args[0] === 'run' && args[1] === 'image') {
|
|
182
|
+
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
183
|
+
const { providerInput, modelRef } = generationInput('image', jsonInput(options.input));
|
|
184
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
185
|
+
const routes = imageCandidatesFromCatalog(catalog, modelRef);
|
|
186
|
+
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
187
|
+
const candidates = routes.map((route) => ({
|
|
188
|
+
adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
|
|
189
|
+
selectionReason: route.selectionReason,
|
|
190
|
+
providerBinding: providerTaskBinding(route.provider)
|
|
191
|
+
}));
|
|
192
|
+
const result = await createProviderTaskRunner(runtime).submit({
|
|
193
|
+
capability: 'image', candidates,
|
|
194
|
+
providerInput
|
|
195
|
+
});
|
|
196
|
+
console.log(JSON.stringify(result, null, 2));
|
|
197
|
+
});
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 视频输入冻结(E 阶段 Profile):t2v/i2v 的 prompt/img_data_url/size/duration;
|
|
202
|
+
// s2v 上传流程不在 headless 面开放。
|
|
203
|
+
if (args[0] === 'run' && args[1] === 'video') {
|
|
204
|
+
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
205
|
+
const { providerInput, modelRef } = generationInput('video', jsonInput(options.input));
|
|
206
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
207
|
+
const routes = videoCandidatesFromCatalog(catalog, modelRef);
|
|
208
|
+
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
209
|
+
const candidates = routes.map((route) => ({
|
|
210
|
+
adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
|
|
211
|
+
selectionReason: route.selectionReason,
|
|
212
|
+
providerBinding: providerTaskBinding(route.provider)
|
|
213
|
+
}));
|
|
214
|
+
const result = await createProviderTaskRunner(runtime).submit({
|
|
215
|
+
capability: 'video', candidates,
|
|
216
|
+
providerInput
|
|
217
|
+
});
|
|
218
|
+
console.log(JSON.stringify(result, null, 2));
|
|
219
|
+
});
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 语音合成输入冻结(E2 Profile):同步完成型,一次命令内完成 Task → Artifact。
|
|
224
|
+
if (args[0] === 'run' && args[1] === 'tts') {
|
|
225
|
+
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
226
|
+
const { providerInput, modelRef } = generationInput('tts', jsonInput(options.input));
|
|
227
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
228
|
+
const routes = ttsCandidatesFromCatalog(catalog, modelRef);
|
|
229
|
+
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
230
|
+
const candidates = routes.map((route) => ({
|
|
231
|
+
adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
|
|
232
|
+
selectionReason: route.selectionReason,
|
|
233
|
+
providerBinding: providerTaskBinding(route.provider)
|
|
234
|
+
}));
|
|
235
|
+
const result = await createProviderTaskRunner(runtime).submit({
|
|
236
|
+
capability: 'tts', candidates,
|
|
237
|
+
providerInput
|
|
238
|
+
});
|
|
239
|
+
console.log(JSON.stringify(result, null, 2));
|
|
240
|
+
});
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 转写输入冻结(E3 Profile):上传型异步——audio_url(公网/oss://)或
|
|
245
|
+
// audio_path(本地文件,经首选候选 Provider 的临时存储上传,签名 URL 不落 Core)。
|
|
246
|
+
if (args[0] === 'run' && args[1] === 'transcribe') {
|
|
247
|
+
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
248
|
+
const normalized = generationInput('transcribe', jsonInput(options.input), { allowAudioPath: true });
|
|
249
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
250
|
+
const routes = transcribeCandidatesFromCatalog(catalog, normalized.modelRef);
|
|
251
|
+
let providerInput = normalized.providerInput;
|
|
252
|
+
if (normalized.audioPath) {
|
|
253
|
+
if (!fs.existsSync(normalized.audioPath)) throw new CliUsageError('音频文件不存在');
|
|
254
|
+
const prepared = await prepareProviderInput(createConfiguredProviderAdapter(routes[0].provider), {
|
|
255
|
+
model: routes[0].model, filePath: normalized.audioPath
|
|
256
|
+
});
|
|
257
|
+
providerInput = generationInput('transcribe', { audio_url: prepared.url }).providerInput;
|
|
258
|
+
}
|
|
259
|
+
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
260
|
+
const candidates = routes.map((route) => ({
|
|
261
|
+
adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
|
|
262
|
+
selectionReason: route.selectionReason,
|
|
263
|
+
providerBinding: providerTaskBinding(route.provider)
|
|
264
|
+
}));
|
|
265
|
+
const result = await createProviderTaskRunner(runtime).submit({
|
|
266
|
+
capability: 'transcribe', candidates,
|
|
267
|
+
providerInput
|
|
268
|
+
});
|
|
269
|
+
console.log(JSON.stringify(result, null, 2));
|
|
270
|
+
});
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (args[0] === 'task' && args[1] === 'observe' && args[2]) {
|
|
275
|
+
const options = flags(args.slice(3), ['data-root', 'provider-config']);
|
|
276
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
277
|
+
await withRuntime('reader', options['data-root'], (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
278
|
+
const result = await withRuntime('writer', options['data-root'], (commands) => commands.execute('task.observe', { task_id: args[2] }), {
|
|
279
|
+
resolveTaskAdapter: (task) => createConfiguredProviderAdapter(providerForTaskFromCatalog(catalog, task))
|
|
280
|
+
});
|
|
281
|
+
console.log(JSON.stringify(result, null, 2));
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (args[0] === 'task' && args[1] === 'redeliver' && args[2]) {
|
|
286
|
+
const options = flags(args.slice(3), ['data-root', 'provider-config']);
|
|
287
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
288
|
+
await withRuntime('reader', options['data-root'], (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
289
|
+
const result = await withRuntime('writer', options['data-root'], (commands) => commands.execute('task.redeliver', { task_id: args[2] }), {
|
|
290
|
+
resolveTaskAdapter: (task) => createConfiguredProviderAdapter(providerForTaskFromCatalog(catalog, task))
|
|
291
|
+
});
|
|
292
|
+
console.log(JSON.stringify(result, null, 2));
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (args[0] === 'task' && args[1] === 'cancel' && args[2]) {
|
|
297
|
+
const options = flags(args.slice(3), ['data-root', 'provider-config']);
|
|
298
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
299
|
+
await withRuntime('reader', options['data-root'], (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
300
|
+
const result = await withRuntime('writer', options['data-root'], (commands) => commands.execute('task.cancel', { task_id: args[2] }), {
|
|
301
|
+
resolveTaskAdapter: (task) => createConfiguredProviderAdapter(providerForTaskFromCatalog(catalog, task))
|
|
302
|
+
});
|
|
303
|
+
console.log(JSON.stringify(result, null, 2));
|
|
304
|
+
return 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (args[0] === 'task' && args[1] === 'retry' && args[2]) {
|
|
308
|
+
const rest = args.slice(3);
|
|
309
|
+
const confirmBilling = rest.includes('--confirm-billing');
|
|
310
|
+
const options = flags(rest.filter((arg) => arg !== '--confirm-billing'),
|
|
311
|
+
['data-root', 'provider-config', 'input', 'model-ref']);
|
|
312
|
+
const input = jsonInput(options.input);
|
|
313
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
314
|
+
const dataRoot = required(options['data-root'], 'data-root');
|
|
315
|
+
await withRuntime('reader', dataRoot, (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
316
|
+
const result = await withRuntime('writer', dataRoot, (commands) => commands.execute('task.retry', {
|
|
317
|
+
task_id: args[2],
|
|
318
|
+
provider_input: input,
|
|
319
|
+
model_ref: options['model-ref'],
|
|
320
|
+
confirm_billing: confirmBilling
|
|
321
|
+
}), {
|
|
322
|
+
resolveTaskCandidates: ({ capability, modelRef }) => (capability === 'video'
|
|
323
|
+
? videoCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
324
|
+
: capability === 'tts'
|
|
325
|
+
? ttsCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
326
|
+
: capability === 'transcribe'
|
|
327
|
+
? transcribeCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
328
|
+
: imageCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
329
|
+
).map((route) => ({
|
|
330
|
+
adapter: createConfiguredProviderAdapter(route.provider),
|
|
331
|
+
model: route.modelRef,
|
|
332
|
+
selectionReason: route.selectionReason,
|
|
333
|
+
providerBinding: providerTaskBinding(route.provider)
|
|
334
|
+
}))
|
|
335
|
+
});
|
|
336
|
+
console.log(JSON.stringify(result, null, 2));
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (args[0] === 'task' && args[1] === 'inspect' && args[2]) {
|
|
341
|
+
const options = flags(args.slice(3), ['data-root']);
|
|
342
|
+
const result = await withRuntime('reader', options['data-root'], (commands) =>
|
|
343
|
+
commands.execute('task.inspect', { task_id: args[2] }));
|
|
344
|
+
console.log(JSON.stringify(result, null, 2));
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (args[0] === 'task' && args[1] === 'list') {
|
|
349
|
+
const options = flags(args.slice(2), ['data-root']);
|
|
350
|
+
const result = await withRuntime('reader', options['data-root'], (commands) =>
|
|
351
|
+
commands.execute('task.list', {}));
|
|
352
|
+
console.log(JSON.stringify(result, null, 2));
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (args[0] === 'artifact' && args[1] === 'inspect' && args[2]) {
|
|
357
|
+
const options = flags(args.slice(3), ['data-root']);
|
|
358
|
+
const result = await withRuntime('reader', options['data-root'], (commands) =>
|
|
359
|
+
commands.execute('artifact.inspect', { artifact_id: args[2] }));
|
|
360
|
+
console.log(JSON.stringify(result, null, 2));
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (args[0] === 'artifact' && args[1] === 'list') {
|
|
365
|
+
const options = flags(args.slice(2), ['data-root']);
|
|
366
|
+
const result = await withRuntime('reader', options['data-root'], (commands) =>
|
|
367
|
+
commands.execute('artifact.list', {}));
|
|
368
|
+
console.log(JSON.stringify(result, null, 2));
|
|
369
|
+
return 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (args[0] === 'artifact' && args[1] === 'rebuild') {
|
|
373
|
+
const options = flags(args.slice(2), ['data-root']);
|
|
374
|
+
const result = await withRuntime('writer', options['data-root'], (commands) =>
|
|
375
|
+
commands.execute('artifact.rebuild', {}));
|
|
376
|
+
console.log(JSON.stringify(result, null, 2));
|
|
377
|
+
return 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (args[0] === 'artifact' && args[1] === 'export' && args[2]) {
|
|
381
|
+
const options = flags(args.slice(3), ['data-root', 'output']);
|
|
382
|
+
const result = await withRuntime('writer', options['data-root'], (commands) =>
|
|
383
|
+
commands.execute('artifact.export', {
|
|
384
|
+
artifact_id: args[2],
|
|
385
|
+
output_path: required(options.output, 'output')
|
|
386
|
+
}));
|
|
387
|
+
console.log(JSON.stringify(result, null, 2));
|
|
388
|
+
return 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
throw new CliUsageError('未知命令');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
396
|
+
} catch (error) {
|
|
397
|
+
const code = error && error.code || 'IRIS_CLI_FAILED';
|
|
398
|
+
const message = error && error.message || '命令执行失败';
|
|
399
|
+
console.error(code + ': ' + message);
|
|
400
|
+
if (error instanceof CliUsageError) console.error('\n' + usage());
|
|
401
|
+
process.exitCode = error instanceof CliUsageError ? 2 : 1;
|
|
23
402
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 给 AI Agent 的简要 Prompt
|
|
2
|
+
|
|
3
|
+
把下面一段交给负责安装或使用 Iris 的 Agent。版本与发布状态以当前 [README](../README.md) 为准;Iris 和 DSH 的版本号独立。
|
|
4
|
+
|
|
5
|
+
## 中文
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
请根据本仓库 README,帮我安装并使用 Iris(@mokuyoaxis/dsh-iris)。
|
|
9
|
+
先确认运行环境、目标 DSH profile,并用 dsh --version 核对宿主;Iris 0.2.0 要求 Node.js >=22.0.0(旧 npm 0.1.4 的历史要求为 >=20.10.0):
|
|
10
|
+
- DSH 0.2.0-rc.2:固定安装 dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.0。
|
|
11
|
+
- DSH >=0.1.2-rc.1 <0.1.3-0 或 0.1.5-rc.1:固定安装 dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.1.4;只下载用 npm pack @mokuyoaxis/dsh-iris@0.1.4。
|
|
12
|
+
- 其他 DSH 版本:先报告没有支持声明,不自行升级宿主或跳过版本检查。
|
|
13
|
+
没有 DSH、希望用 CLI 时,按 docs/HEADLESS_CLI.md 使用 npm 0.2.0;npm 0.1.4 不含新的 Core 媒体 CLI。
|
|
14
|
+
从 dsh-market 安装也要核对实际包名、版本和兼容范围;市场缓存不代表支持旧宿主,不对旧 DSH 安装 latest。
|
|
15
|
+
若使用其他 profile,把示例中的 web 换成已确认的目标。保留完整 scoped 包名,不把 dsh-iris 当作同一 npm 包。
|
|
16
|
+
安装后检查 Doctor 和工具可达性,再执行我指定的媒体任务;不要输出 API Key,重启宿主前说明影响。
|
|
17
|
+
查询、观察和重新取回不等于重新生成;任务已受理或受理未知时,不自动重新提交。
|
|
18
|
+
最后报告所用 Iris/DSH 版本、安装来源和实际验证结果。
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## English
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
Help me install and use Iris (@mokuyoaxis/dsh-iris) according to this repository's README.
|
|
25
|
+
Check the environment, target DSH profile, and dsh --version first; Iris 0.2.0 requires Node.js >=22.0.0 (the published npm 0.1.4 historically requires >=20.10.0):
|
|
26
|
+
- DSH 0.2.0-rc.2: pin dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.0.
|
|
27
|
+
- DSH >=0.1.2-rc.1 <0.1.3-0 or 0.1.5-rc.1: pin dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.1.4; download only with npm pack @mokuyoaxis/dsh-iris@0.1.4.
|
|
28
|
+
- Other DSH versions: report the missing support claim; do not upgrade the host or bypass its version checks automatically.
|
|
29
|
+
For CLI use without DSH, follow docs/HEADLESS_CLI.md using npm 0.2.0; npm 0.1.4 does not include the new Core media CLI.
|
|
30
|
+
For dsh-market, check the actual package name, version, and compatibility range too. Cached listings do not establish older-host support; do not install latest on an old DSH host.
|
|
31
|
+
Replace web with the confirmed profile when needed. Keep the scoped package name; unscoped dsh-iris is a different npm package.
|
|
32
|
+
Check Doctor and tool availability after installation, then carry out my requested media task. Do not expose API keys; explain the impact before restarting the host.
|
|
33
|
+
Inspect, observe, and redeliver do not create a new generation. Never resubmit automatically after accepted or unknown acceptance.
|
|
34
|
+
Report the Iris/DSH versions, installation source, and actual validation results.
|
|
35
|
+
```
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,81 +1,111 @@
|
|
|
1
1
|
# Iris 架构与边界
|
|
2
2
|
|
|
3
|
-
状态:
|
|
3
|
+
状态:Iris `0.2.0` 提供可在无 DSH 进程中运行的实例化 Core Runtime、Headless CLI、共享 Provider 生命周期、Core Task/Attempt 与 Artifact Manifest,并让 DSH 逐项消费同一套事实。DSH 插件仅支持 `0.2.0-rc.2`,Node.js 最低为 22.0.0;旧宿主固定使用 npm `0.1.4`。Core 内部 API 尚未冻结为公共 SDK。
|
|
4
4
|
|
|
5
5
|
## 当前形态
|
|
6
6
|
|
|
7
|
-
Iris
|
|
7
|
+
Iris 仍以一个 npm 包交付,但已有两个执行入口:DSH 插件负责 Agent 工具、会话附件、浏览器、Web UI 和后台观察;Headless CLI 在没有 DSH/Cordis 的进程中显式接收数据根与私有 Provider 配置。两者共用 Core Runtime、Provider Task Runner、Task/Artifact Store 和本地 Command,不能在同一数据根上同时充当写者。
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
|
-
DSH Host / Cordis
|
|
11
|
-
├─ Agent tools
|
|
12
|
-
├─
|
|
13
|
-
└─
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
DSH Host / Cordis Headless CLI
|
|
11
|
+
├─ Agent tools / Web UI ├─ explicit --data-root
|
|
12
|
+
├─ attachment / browser ports └─ private provider config
|
|
13
|
+
└─ bounded observation timers │
|
|
14
|
+
│ │
|
|
15
|
+
dsh-host-adapter.js bin/dsh-iris.js
|
|
16
|
+
│ │
|
|
17
|
+
dsh-core-adapter.js ────────────────┬───────────┘
|
|
18
|
+
▼
|
|
19
|
+
Core Runtime v0
|
|
20
|
+
explicit root · reader/writer
|
|
21
|
+
lease · AbortSignal · disposal
|
|
22
|
+
┌────────────┴────────────┐
|
|
23
|
+
▼ ▼
|
|
24
|
+
Command Service v0 Provider Task Runner
|
|
25
|
+
crop / diff / frames submit / observe /
|
|
26
|
+
inspect / control cancel / delivery
|
|
27
|
+
└────────────┬────────────┘
|
|
28
|
+
▼
|
|
29
|
+
Core Task Store + Artifact Manifest
|
|
30
|
+
records · objects · SHA-256 · index
|
|
31
|
+
│
|
|
32
|
+
DSH: $DSH_HOME/iris/v1/core-v0
|
|
33
|
+
CLI: explicit absolute data root
|
|
25
34
|
```
|
|
26
35
|
|
|
27
|
-
`lib/index.js` 是 DSH
|
|
36
|
+
`lib/index.js` 是 DSH 插件装配入口,`lib/dsh-host-adapter.js` 是原始 DSH/Cordis 对象进入 Iris 的服务端边界;`lib/dsh-core-adapter.js` 只负责选择 DSH profile 的 Core 数据根、恢复 Provider 配置和投影 Host DTO。`bin/dsh-iris.js` 不启动 DSH,也不推断 profile。`lib/api.js` 与 `lib/client.js` 是 Host 投影,不是真相来源。
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
未迁移能力仍走 0.1.4 legacy 链。当前主要包括 s2v 数字人视频、视觉理解、长图 OCR、媒体摘要、HTML 渲染和提示词优化;它们不会被伪装成 Core 能力。
|
|
39
|
+
|
|
40
|
+
## 0.2.0 核心
|
|
30
41
|
|
|
31
42
|
| 模块 | 职责 | DSH 运行时依赖 |
|
|
32
43
|
|---|---|---:|
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
| `core-contract.js` / `core-runtime.js` | 显式数据根、reader/writer 权限、单写者租约、取消与释放 | 无 |
|
|
45
|
+
| `command-service.js` | 本地媒体命令、Task 控制面和 Artifact 查询/导出 | 无 |
|
|
46
|
+
| `generation-input.js` | CLI、DSH 与重试共用的生成参数校验和 Provider 字段转换 | 无 |
|
|
47
|
+
| `model-port-contract.js` / `model-invoker.js` | Text/Vision 内部契约、有限调用预算与取消;真实消费者尚未接入 | 无 |
|
|
48
|
+
| `core-tasks.js` | Core Task/Attempt 持久事实、受理与交付状态 | 无 |
|
|
49
|
+
| `core-artifacts.js` / `core-artifact-store.js` | 对象、Manifest、SHA-256、关系边与可重建 Index | 无 |
|
|
50
|
+
| `provider-contract.js` / `provider-adapter.js` | canonical 结果、错误脱敏、受理边界与 Provider v0 操作 | 无 |
|
|
51
|
+
| `provider-adapters.js` | DashScope / OpenAI Images 协议实现和可选输入准备 | 无 |
|
|
52
|
+
| `provider-task-runner.js` | 写前 Attempt、submit/poll/cancel/download 与 Profile 化交付 | 无 |
|
|
53
|
+
| `core-doctor.js` / `core-lease-recovery.js` | 只读完整性诊断与需显式确认的陈旧租约恢复 | 无 |
|
|
54
|
+
| `provider-catalog.js` | 显式 Provider 配置、能力候选和原 Task Adapter 恢复 | 无 |
|
|
55
|
+
| `dsh-core-adapter.js` | DSH 数据根、后台观察与 Core 到 Host 的投影 | 有;DSH 专属边界 |
|
|
56
|
+
| `dsh-host-adapter.js` / `index.js` | Host 能力映射、工具/路由/Skill 与生命周期装配 | 有 |
|
|
57
|
+
| `client.js` | DSH Web Slot、统一任务/作品区、泡泡和提示词入口 | 有 |
|
|
58
|
+
|
|
59
|
+
完整规则见 [Core Runtime v0](CORE_RUNTIME_CONTRACT.md)、[Artifact Manifest v0](ARTIFACT_MANIFEST.md)、[Provider Adapter v0](PROVIDER_ADAPTER_CONTRACT.md) 与 [DSH → Core 渐进迁移](DSH_CORE_MIGRATION.md)。这些是 rc 候选内部契约,不是第三方 SDK 兼容承诺。
|
|
42
60
|
|
|
43
61
|
## Task、Attempt 与 Artifact
|
|
44
62
|
|
|
45
|
-
- 一个用户意图对应一个 Task
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
63
|
+
- 一个用户意图对应一个 Core Task;每次候选供应商调用对应一个稳定 Attempt。Attempt 必须在可能发送计费请求前落盘。
|
|
64
|
+
- 只有明确 `not_accepted` 才允许自动切换候选;`accepted` 或 `unknown` 后禁止自动重提。重试会创建新 Task,并要求用户再次提供未持久化的输入和明确确认潜在费用。
|
|
65
|
+
- 生成结果与本地交付是两类事实。远端成功后下载失败仍保持 `outcome=succeeded / deliveryState=failed`,可显式重新交付而不重新生成。
|
|
66
|
+
- 新 Attempt 可选保存阶段时间戳与模型选择来源;旧记录缺少这些字段仍可读取。
|
|
67
|
+
- Artifact Manifest v0 保存内容哈希、媒体类型、中性元数据和关系边;不保存 Prompt、API Key、签名 URL、Provider 端点或宿主绝对路径。
|
|
68
|
+
- Core Task/Artifact 的删除与清理能力在 0.2.0 中保持封闭:DSH 与 CLI 都不提供 delete、clear 或 orphan purge。Doctor 只报告,不自动修复。
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
0.1.4 的 `tasks.json`、`artifacts.json`、`outputs/` 与上传目录继续作为 legacy 数据存在;Core 使用隔离的 `core-v0`。已迁移的新图片、视频、TTS 与转写任务不双写 legacy,回退到 0.1.4 时也不删除或改写 Core 数据。
|
|
52
71
|
|
|
53
72
|
## Provider 边界
|
|
54
73
|
|
|
55
|
-
|
|
74
|
+
Provider Adapter v0 固定 discovery、submit、poll、cancel、download 与 error mapping 六类生命周期操作;提交前的临时输入上传是可选准备能力,不自动成为 Core 事实。协议实现只接收显式凭据、模型和媒体端点,Core 保存复合模型身份与非敏感 binding,不保存端点原文。
|
|
75
|
+
|
|
76
|
+
Core Runner 不拥有后台 timer。Headless `task observe` 每次最多 poll 一次;DSH 的有界自动观察属于 Host,插件释放时停止 timer 并中止在途观察,但不会把“停止本地观察”写成“远端已取消”。端点或协议 binding 漂移时恢复在联网前拒绝。
|
|
77
|
+
|
|
78
|
+
每个操作必须明确声明 supported 或 unsupported。当前 DashScope 与 OpenAI Images 媒体协议没有经过验证的远端取消实现,因此取消请求如实返回 not-supported,而不是伪造 canceled。完整受理与错误规则见 [Provider 提交契约 v0](PROVIDER_SUBMISSION_CONTRACT.md)。
|
|
56
79
|
|
|
57
|
-
|
|
80
|
+
## Text/Vision 共享边界(M1 已实现)
|
|
58
81
|
|
|
59
|
-
|
|
82
|
+
[Text/Vision Model Port v0](MODEL_PORT_CONTRACT.md) 的纯契约、调用控制与 test-only Fake Port 已实现,尚未接入真实消费者。它以 `describe()` 和单轮 `complete()` 统一文本/视觉输入、正常终态、预算、取消与安全错误;图片以字节和 MIME 进入共享层,模型选择、会话与附件桥接保留在入口/适配器。
|
|
60
83
|
|
|
61
|
-
|
|
84
|
+
提示词优化与视觉理解不创建媒体生成 Task,不进入 Provider Task Runner。提示词优化的功能定位与输入隔离正在讨论,其接入待后续确定;look/relook、定位、OCR 与摘要仍按独立切片迁移。现有 Host 方法、配置与 CLI 均未因 M1 改变;自持文本目录和独立入口需要另行审查。
|
|
85
|
+
|
|
86
|
+
## Host Port 与 Headless 边界
|
|
87
|
+
|
|
88
|
+
| 能力 | DSH 来源 | 缺失或 Headless 时行为 |
|
|
62
89
|
|---|---|---|
|
|
63
|
-
| 工具注册与 Fiber 生命周期 |
|
|
64
|
-
| Web 路由 |
|
|
65
|
-
| UI Slot | DSH Web Client | 不影响
|
|
66
|
-
| Browser | DSH browser service |
|
|
67
|
-
| 会话附件与输入草稿 |
|
|
68
|
-
| Skills registry | DSH skills service | 随包 Skill
|
|
90
|
+
| 工具注册与 Fiber 生命周期 | `ctx.tools` / `ctx.effect` | 不注册 DSH 工具;CLI/Core 不受影响 |
|
|
91
|
+
| Web 路由 | `webServer` 或 `httpServer` | 无工作台 API 和同源媒体路由;CLI 直接导出 Artifact |
|
|
92
|
+
| UI Slot | DSH Web Client | 不影响 Core Task、Artifact 与离线 Doctor |
|
|
93
|
+
| Browser | DSH browser service | HTML 渲染不可用;本地媒体命令不受影响 |
|
|
94
|
+
| 会话附件与输入草稿 | session/input ports | CLI 使用显式文件或 JSON 输入,不创建会话对象 |
|
|
95
|
+
| Skills registry | DSH skills service | 随包 Skill 不自动注册;CLI/Core 仍可用 |
|
|
96
|
+
|
|
97
|
+
Command、Core Store、Provider Runner 和 Headless CLI 不接收原始 `ctx`、会话对象或 Browser 对象。Host URL、attachment 与 UI 文案只在 DSH 投影层产生。
|
|
69
98
|
|
|
70
|
-
|
|
99
|
+
## 事件、UI 与媒体读取
|
|
71
100
|
|
|
72
|
-
|
|
101
|
+
DSH 工作台把 Core 与 legacy Task 合并到一个用户任务区,把 Core 图片与旧 `outputs/` 合并到一个作品区。普通投影只有五类用户状态;原始 Attempt 事实留在高级诊断。刷新、展开、关闭、读取作品和查询状态都不得触发 submit、poll 或隐式修复。
|
|
73
102
|
|
|
74
|
-
|
|
103
|
+
Core 媒体路由按随机 Artifact ID 读取并逐次验证 SHA-256;它没有独立 token。浏览器请求还受可信 Host、`Origin`/`Sec-Fetch-Site` 和 `Referrer-Policy: no-referrer` 约束。Artifact ID 是持有者标识,不等于多租户认证;Iris 与 DSH 仍处于同一本机信任域。
|
|
75
104
|
|
|
76
|
-
##
|
|
105
|
+
## 非目标与发布边界
|
|
77
106
|
|
|
78
|
-
-
|
|
79
|
-
- 不迁移到 SQLite/JSONL
|
|
80
|
-
-
|
|
81
|
-
-
|
|
107
|
+
- `0.2.0` 不启动独立常驻服务,也不复制 DSH 聊天界面。
|
|
108
|
+
- 不迁移到 SQLite/JSONL,不原地改写 0.1.4 数据,不自动接管不确定的写者租约。
|
|
109
|
+
- 不在本候选新增 Gemini、Fal、Replicate 或本地模型协议;注册表只是后续接入点。
|
|
110
|
+
- 不把离线测试、一次 canary 或 Headless 可运行等同于公开 Core API 已冻结。
|
|
111
|
+
- rc.1 仍须通过最终包审计、仓库外安装、跨平台 CI、Android 目视、回退和机器可读验证摘要后才能发布。
|