@mokuyoaxis/dsh-iris 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh/skills/iris-compose-media/SKILL.md +10 -5
- package/.dsh/skills/iris-compose-media/references/workflows.md +7 -1
- package/.dsh/skills/iris-verify-ui/SKILL.md +2 -1
- package/CHANGELOG.md +69 -2
- package/README.en.md +67 -31
- package/README.md +67 -31
- package/bin/dsh-iris.js +222 -21
- package/docs/AGENT_QUICKSTART.md +10 -6
- package/docs/ARCHITECTURE.md +34 -19
- package/docs/ARTIFACT_LIBRARY.md +5 -5
- package/docs/ARTIFACT_MANIFEST.md +1 -1
- package/docs/CLI_MANAGEMENT.md +183 -0
- package/docs/COMPOSITE_VISION.md +35 -0
- package/docs/CORE_RUNTIME_CONTRACT.md +16 -7
- package/docs/DSH_CORE_MIGRATION.md +48 -19
- package/docs/DSH_RC2_ADAPTATION.md +4 -2
- package/docs/FAULT_INJECTION.md +21 -2
- package/docs/HEADLESS_CLI.md +15 -7
- package/docs/HOST_ADAPTER_CONTRACT.md +10 -10
- package/docs/HOST_DOCTOR.md +3 -3
- package/docs/MODEL_PORT_CONTRACT.md +21 -14
- package/docs/OCR_MODEL.md +39 -0
- package/docs/PROMPT_OPTIMIZER.md +77 -0
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +15 -5
- package/docs/PROVIDER_HEALTH.md +22 -5
- package/docs/PROVIDER_RUNTIME_HARNESS.md +1 -1
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +2 -2
- package/docs/ROADMAP.md +37 -19
- package/docs/SECURITY.md +5 -5
- package/docs/TASK_SEMANTICS.md +2 -2
- package/docs/VISION_CLI.md +132 -0
- package/docs/VISION_MODEL.md +51 -0
- package/docs/WORKBENCH_ARTIFACTS.md +67 -0
- package/docs/file-access-across-environments.md +8 -5
- package/docs/releases/0.2.1.md +56 -0
- package/docs/releases/0.2.2.md +31 -0
- package/lib/actions.js +199 -286
- package/lib/adapters.js +216 -41
- package/lib/api.js +49 -7
- package/lib/capability.js +23 -16
- package/lib/chromium-browser.js +117 -0
- package/lib/client.js +933 -117
- package/lib/command-service.js +121 -7
- package/lib/composite-vision-routing.js +42 -0
- package/lib/config.js +102 -14
- package/lib/core-artifact-store.js +45 -13
- package/lib/core-lease-recovery.js +64 -92
- package/lib/core-maintenance.js +213 -0
- package/lib/core-runtime.js +45 -47
- package/lib/core-tasks.js +12 -5
- package/lib/core-user-projection.js +1 -0
- package/lib/core-writer-lease.js +134 -0
- package/lib/dsh-core-adapter.js +63 -9
- package/lib/dsh-text-model-adapter.js +222 -0
- package/lib/dsh-vision-model-adapter.js +75 -0
- package/lib/generation-input.js +36 -5
- package/lib/headless-vision.js +181 -0
- package/lib/host-runtime.js +1 -0
- package/lib/http-vision-model-adapter.js +187 -0
- package/lib/image-edit-input.js +34 -0
- package/lib/index.js +205 -175
- package/lib/locate.js +86 -28
- package/lib/media-probe.js +2 -1
- package/lib/model-call-runtime.js +45 -0
- package/lib/model-invoker.js +4 -2
- package/lib/model-port-contract.js +13 -5
- package/lib/models.js +29 -42
- package/lib/ocr-model-routing.js +26 -0
- package/lib/ocr.js +110 -64
- package/lib/prompt-optimizer-config.js +21 -10
- package/lib/prompt-optimizer-core.js +247 -0
- package/lib/prompt-optimizer.js +37 -193
- package/lib/provider-adapters.js +87 -10
- package/lib/provider-catalog.js +19 -7
- package/lib/provider-config-service.js +235 -0
- package/lib/provider-contract.js +3 -2
- package/lib/provider-health.js +80 -1
- package/lib/provider-protocol.js +84 -0
- package/lib/provider-task-runner.js +63 -19
- package/lib/summarize.js +31 -20
- package/lib/summary-input.js +52 -0
- package/lib/task-wait.js +40 -0
- package/lib/video-input.js +30 -0
- package/lib/vision-core.js +42 -0
- package/lib/vision-image-input.js +83 -0
- package/lib/vision-input.js +31 -0
- package/lib/vision-model-routing.js +180 -0
- package/lib/vision.js +3 -2
- package/lib/workbench-artifacts.js +52 -0
- package/lib/workbench-management.js +151 -0
- package/lib/workbench-zip.js +39 -0
- package/package.json +1 -1
- package/user_guide.md +74 -30
package/bin/dsh-iris.js
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { createCommandService } from '../lib/command-service.js';
|
|
4
5
|
import { createCoreRuntime } from '../lib/core-runtime.js';
|
|
5
6
|
import { recoverCoreWriterLease } from '../lib/core-lease-recovery.js';
|
|
6
7
|
import { doctor, formatDoctorReport } from '../lib/doctor.js';
|
|
7
8
|
import { loadProviderCatalog, providerCatalogSnapshot, catalogCapabilitySnapshot, imageCandidatesFromCatalog, providerForTaskFromCatalog, providerTaskBinding, transcribeCandidatesFromCatalog, ttsCandidatesFromCatalog, videoCandidatesFromCatalog } from '../lib/provider-catalog.js';
|
|
8
9
|
import { createConfiguredProviderAdapter } from '../lib/provider-adapters.js';
|
|
10
|
+
import { providerForImageModel } from '../lib/provider-protocol.js';
|
|
9
11
|
import { prepareProviderInput } from '../lib/provider-adapter.js';
|
|
10
12
|
import { createProviderTaskRunner } from '../lib/provider-task-runner.js';
|
|
11
13
|
import { GenerationInputError, normalizeGenerationInput } from '../lib/generation-input.js';
|
|
14
|
+
import { executeVisionCommand, normalizeVisionInput } from '../lib/headless-vision.js';
|
|
15
|
+
import { videoTaskCandidates } from '../lib/video-input.js';
|
|
16
|
+
import { createChromiumBrowser } from '../lib/chromium-browser.js';
|
|
17
|
+
import { executeConfigCommand, recordCatalogRateLimit } from '../lib/provider-config-service.js';
|
|
18
|
+
import { probeVisionModel } from '../lib/vision-model-routing.js';
|
|
19
|
+
import { RED_TEST_IMAGE } from '../lib/vision.js';
|
|
20
|
+
import { parseModelRef, modelRef as qualifiedModelRef } from '../lib/models.js';
|
|
21
|
+
import { coreTaskWaitFinished } from '../lib/task-wait.js';
|
|
22
|
+
|
|
23
|
+
function trackedProviderAdapter(provider, options, task) {
|
|
24
|
+
return createConfiguredProviderAdapter(provider, {
|
|
25
|
+
modelId: parseModelRef(task?.modelRef)?.modelId,
|
|
26
|
+
onRateLimit: event => recordCatalogRateLimit(options['provider-config'], event)
|
|
27
|
+
});
|
|
28
|
+
}
|
|
12
29
|
|
|
13
30
|
class CliUsageError extends Error {
|
|
14
31
|
constructor(message) {
|
|
@@ -21,27 +38,48 @@ class CliUsageError extends Error {
|
|
|
21
38
|
function usage() {
|
|
22
39
|
return [
|
|
23
40
|
'Usage:',
|
|
41
|
+
' JSON input: --input <json>, --input-file <path>, or --input - (stdin).',
|
|
24
42
|
' dsh-iris doctor [--data-root <absolute-path>] [--json]',
|
|
25
43
|
' dsh-iris runtime recover --data-root <absolute-path> --confirm-stale-pid <pid>',
|
|
26
44
|
' dsh-iris providers list --provider-config <absolute-path>',
|
|
27
45
|
' dsh-iris capabilities list --provider-config <absolute-path>',
|
|
46
|
+
' dsh-iris config <init|show|check> --provider-config <absolute-path>',
|
|
47
|
+
' dsh-iris providers <add|set> --provider-config <absolute-path> --input <provider-json>',
|
|
48
|
+
' dsh-iris providers remove <provider-id> --provider-config <absolute-path>',
|
|
49
|
+
' dsh-iris models list --provider-config <absolute-path>',
|
|
50
|
+
' dsh-iris models <add|remove|caps|protocol|vision-input> <providerId::modelId> --provider-config <absolute-path> [--input <json>]',
|
|
51
|
+
' dsh-iris models discover <provider-id> --provider-config <absolute-path> [--apply <true|false>]',
|
|
52
|
+
' dsh-iris models test <providerId::modelId> --capability <vision|tts|image-gen|video-gen|transcribe> --provider-config <absolute-path> [--data-root <absolute-path>]',
|
|
53
|
+
' dsh-iris assignments <list|set|clear> --provider-config <absolute-path> [--input <json>]',
|
|
54
|
+
' dsh-iris vision <look|locate|ocr|summarize> --provider-config <absolute-path> --input <json> [--model-ref <providerId::modelId>] [--format <json|text>] [--output <path>] [--timeout-ms <1..120000>]',
|
|
55
|
+
' Core artifact_id / frame_artifact_ids inputs or transcribe=true require --data-root <absolute-path>; summarize supports --sheet-output <path>.',
|
|
28
56
|
' dsh-iris run crop --data-root <absolute-path> --input <json>',
|
|
29
57
|
' dsh-iris media diff --data-root <absolute-path> --input <json>',
|
|
30
58
|
' dsh-iris media frames --data-root <absolute-path> --input <json>',
|
|
59
|
+
' dsh-iris media html --data-root <absolute-path> --input <json> --browser-executable <absolute-path> [--browser-no-sandbox <true|false>] [--output <absolute-path>]',
|
|
31
60
|
' dsh-iris run image --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
32
61
|
' dsh-iris run video --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
33
62
|
' dsh-iris run tts --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
34
63
|
' dsh-iris run transcribe --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
35
64
|
' dsh-iris task inspect <id> --data-root <absolute-path>',
|
|
36
65
|
' dsh-iris task list --data-root <absolute-path>',
|
|
66
|
+
' list supports --offset, --limit, --status, --capability, --provider-id, --model-ref, --outcome, --delivery-state.',
|
|
67
|
+
' dsh-iris <task|artifact> inspect-many --data-root <absolute-path> --input <json>',
|
|
37
68
|
' dsh-iris task observe <id> --data-root <absolute-path> --provider-config <absolute-path>',
|
|
69
|
+
' dsh-iris task wait <id> --data-root <absolute-path> [--provider-config <absolute-path>] [--timeout-ms <1..1200000>] [--poll-interval-ms <50..60000>]',
|
|
38
70
|
' dsh-iris task redeliver <id> --data-root <absolute-path> --provider-config <absolute-path>',
|
|
39
71
|
' dsh-iris task cancel <id> --data-root <absolute-path> --provider-config <absolute-path>',
|
|
40
72
|
' dsh-iris task retry <id> --data-root <absolute-path> --provider-config <absolute-path> --input <json> --confirm-billing [--model-ref <providerId::modelId>]',
|
|
41
73
|
' dsh-iris artifact inspect <id> --data-root <absolute-path>',
|
|
42
74
|
' dsh-iris artifact list --data-root <absolute-path>',
|
|
75
|
+
' artifact list supports --offset, --limit, --kind, --media-type, --task-id.',
|
|
76
|
+
' dsh-iris artifact export-many --data-root <absolute-path> --input <json> --output <absolute-directory>',
|
|
43
77
|
' dsh-iris artifact export <id> --data-root <absolute-path> --output <absolute-path>',
|
|
44
78
|
' dsh-iris artifact rebuild --data-root <absolute-path>',
|
|
79
|
+
' dsh-iris core <delete|cleanup> --data-root <absolute-path> [--input <json>] [--confirm-delete]',
|
|
80
|
+
' Default is a read-only preview. delete selects task_ids/artifact_ids; cleanup executes only explicitly selected preview paths.',
|
|
81
|
+
' dsh-iris core transactions --data-root <absolute-path>',
|
|
82
|
+
' dsh-iris core restore <transaction-id> --data-root <absolute-path>',
|
|
45
83
|
'',
|
|
46
84
|
'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.'
|
|
47
85
|
].join('\n');
|
|
@@ -52,15 +90,32 @@ function flags(args, allowed) {
|
|
|
52
90
|
for (let index = 0; index < args.length; index += 2) {
|
|
53
91
|
const flag = args[index];
|
|
54
92
|
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('--')) {
|
|
93
|
+
if (!name || !(allowed.includes(name) || name === 'input-file' && allowed.includes('input')) || index + 1 >= args.length || String(args[index + 1]).startsWith('--')) {
|
|
56
94
|
throw new CliUsageError('参数无效:' + String(flag || ''));
|
|
57
95
|
}
|
|
58
96
|
if (Object.prototype.hasOwnProperty.call(parsed, name)) throw new CliUsageError('参数重复:--' + name);
|
|
59
97
|
parsed[name] = args[index + 1];
|
|
60
98
|
}
|
|
99
|
+
if (parsed['input-file']) {
|
|
100
|
+
if (parsed.input !== undefined) throw new CliUsageError('--input 与 --input-file 只能选一个');
|
|
101
|
+
parsed.input = readJsonSource(parsed['input-file']);
|
|
102
|
+
} else if (parsed.input === '-') parsed.input = readJsonSource(0);
|
|
61
103
|
return parsed;
|
|
62
104
|
}
|
|
63
105
|
|
|
106
|
+
function readJsonSource(file) {
|
|
107
|
+
let fd;
|
|
108
|
+
try {
|
|
109
|
+
fd = file === 0 ? 0 : fs.openSync(file, 'r');
|
|
110
|
+
const bytes = Buffer.alloc(2 * 1024 * 1024 + 1);
|
|
111
|
+
let offset = 0, count;
|
|
112
|
+
while (offset < bytes.length && (count = fs.readSync(fd, bytes, offset, bytes.length - offset, null))) offset += count;
|
|
113
|
+
if (offset === bytes.length) throw new Error('too large');
|
|
114
|
+
return bytes.subarray(0, offset).toString('utf8');
|
|
115
|
+
} catch (_) { throw new CliUsageError('无法读取 JSON 输入,或大小超过 2 MiB'); }
|
|
116
|
+
finally { if (fd !== undefined && fd !== 0) fs.closeSync(fd); }
|
|
117
|
+
}
|
|
118
|
+
|
|
64
119
|
function required(value, flag) {
|
|
65
120
|
if (!String(value || '').trim()) throw new CliUsageError('缺少参数:--' + flag);
|
|
66
121
|
return String(value);
|
|
@@ -87,6 +142,7 @@ function generationInput(capability, input, options) {
|
|
|
87
142
|
|
|
88
143
|
/** C-8: catchable signals dispose active runtimes before the CLI exits. */
|
|
89
144
|
const activeRuntimes = new Set();
|
|
145
|
+
const cliAbortController = new AbortController();
|
|
90
146
|
let shuttingDown = false;
|
|
91
147
|
|
|
92
148
|
function registerRuntime(runtime) {
|
|
@@ -104,6 +160,7 @@ for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
|
104
160
|
const exitCode = signal === 'SIGINT' ? 130 : 143;
|
|
105
161
|
if (shuttingDown) process.exit(exitCode);
|
|
106
162
|
shuttingDown = true;
|
|
163
|
+
cliAbortController.abort();
|
|
107
164
|
disposeActiveRuntimes()
|
|
108
165
|
.catch(() => {})
|
|
109
166
|
.finally(() => process.exit(exitCode));
|
|
@@ -151,6 +208,19 @@ async function main(args) {
|
|
|
151
208
|
return 0;
|
|
152
209
|
}
|
|
153
210
|
|
|
211
|
+
if (args[0] === 'core' && ['delete', 'cleanup', 'transactions', 'restore'].includes(args[1])) {
|
|
212
|
+
const verb = args[1], restoring = verb === 'restore', selecting = ['delete', 'cleanup'].includes(verb);
|
|
213
|
+
const rest = args.slice(restoring ? 3 : 2);
|
|
214
|
+
const confirmation = rest.filter(arg => arg === '--confirm-delete').length;
|
|
215
|
+
if (confirmation > 1 || (confirmation && !selecting)) throw new CliUsageError('--confirm-delete 参数无效或重复');
|
|
216
|
+
const options = flags(rest.filter(arg => arg !== '--confirm-delete'), ['data-root', ...(selecting ? ['input'] : [])]);
|
|
217
|
+
const input = restoring ? { transaction_id: required(args[2], 'transaction-id') } : selecting
|
|
218
|
+
? { ...(options.input ? jsonInput(options.input) : {}), confirm_delete: confirmation === 1 } : {};
|
|
219
|
+
const result = await withRuntime(restoring || confirmation ? 'writer' : 'reader', options['data-root'], commands => commands.execute('core.' + verb, input));
|
|
220
|
+
console.log(JSON.stringify(result, null, 2));
|
|
221
|
+
return result.allowed === false ? 1 : 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
154
224
|
if (['providers', 'capabilities'].includes(args[0]) && args[1] === 'list') {
|
|
155
225
|
const options = flags(args.slice(2), ['provider-config']);
|
|
156
226
|
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
@@ -159,6 +229,113 @@ async function main(args) {
|
|
|
159
229
|
return 0;
|
|
160
230
|
}
|
|
161
231
|
|
|
232
|
+
if (['config', 'providers', 'models', 'assignments'].includes(args[0])) {
|
|
233
|
+
const [group, verb] = args;
|
|
234
|
+
const verbs = { config: ['init', 'show', 'check'], providers: ['add', 'set', 'remove'], models: ['list', 'add', 'remove', 'caps', 'protocol', 'vision-input', 'discover', 'test'], assignments: ['list', 'set', 'clear'] };
|
|
235
|
+
if (!verbs[group].includes(verb)) throw new CliUsageError('未知配置命令');
|
|
236
|
+
const positional = (group === 'providers' && verb === 'remove') || (group === 'models' && verb !== 'list');
|
|
237
|
+
const id = positional ? required(args[2], group === 'providers' || verb === 'discover' ? 'provider-id' : 'model-ref') : undefined;
|
|
238
|
+
const options = flags(args.slice(positional ? 3 : 2), ['provider-config', 'input', 'apply', 'capability', 'data-root']);
|
|
239
|
+
if (options.apply !== undefined && !['true', 'false'].includes(options.apply)) throw new CliUsageError('--apply 必须是 true 或 false');
|
|
240
|
+
let input = options.input ? jsonInput(options.input) : {};
|
|
241
|
+
const probeInput = { ...input };
|
|
242
|
+
if (group === 'providers') input = ['add', 'set'].includes(verb) ? { provider: jsonInput(options.input) } : { provider_id: id };
|
|
243
|
+
if (group === 'models' && positional) {
|
|
244
|
+
if (verb === 'discover') input = { provider_id: id, apply: options.apply === 'true' };
|
|
245
|
+
else {
|
|
246
|
+
if (!parseModelRef(id)) throw new CliUsageError('模型必须用 providerId::modelId 复合引用');
|
|
247
|
+
input = { ...input, model_ref: id, ...(verb === 'test' ? { capability: required(options.capability, 'capability') } : {}) };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const result = await executeConfigCommand(required(options['provider-config'], 'provider-config'), group === 'config' ? verb : group + '.' + verb, input, {
|
|
251
|
+
signal: cliAbortController.signal,
|
|
252
|
+
probe: async ({ provider, modelId, capability, signal }) => {
|
|
253
|
+
if (capability === 'vision') return probeVisionModel(provider, modelId, { bytes: Buffer.from(RED_TEST_IMAGE.split(',')[1], 'base64'), mediaType: 'image/png' }, { signal });
|
|
254
|
+
if (['video-gen', 'transcribe'].includes(capability) && !options.input) return {
|
|
255
|
+
skipped: true, ok: false, reason: '请用 models test --input 提供与 run video/transcribe 相同的真实素材;未发起请求'
|
|
256
|
+
};
|
|
257
|
+
return withRawRuntime('writer', options['data-root'], async runtime => {
|
|
258
|
+
const mediaCapability = capability === 'image-gen' ? 'image' : capability === 'video-gen' ? 'video' : capability;
|
|
259
|
+
if (mediaCapability === 'image') provider = providerForImageModel(provider, modelId);
|
|
260
|
+
const adapter = createConfiguredProviderAdapter(provider, { allowRateLimited: true, modelId });
|
|
261
|
+
let providerInput = mediaCapability === 'image'
|
|
262
|
+
? { prompt: 'red circle', n: 1, ...(['openai-chat-images', 'openai-responses-images'].includes(adapter.protocol) ? {} : {
|
|
263
|
+
size: adapter.protocol === 'openai-images' ? '256x256' : '512*512'
|
|
264
|
+
}) }
|
|
265
|
+
: { text: '你好', voice: provider.ttsVoice || 'Cherry' };
|
|
266
|
+
let candidates = [{ adapter, model: qualifiedModelRef(provider.id, modelId), selectionReason: 'explicit', providerBinding: providerTaskBinding(provider) }];
|
|
267
|
+
if (['video', 'transcribe'].includes(mediaCapability)) {
|
|
268
|
+
const normalized = generationInput(mediaCapability, { ...probeInput, model_ref: qualifiedModelRef(provider.id, modelId) },
|
|
269
|
+
{ allowVideoPaths: true, allowAudioPath: true });
|
|
270
|
+
providerInput = normalized.providerInput;
|
|
271
|
+
if (mediaCapability === 'video') candidates = videoTaskCandidates(candidates, normalized);
|
|
272
|
+
else if (normalized.audioPath) {
|
|
273
|
+
const prepared = await prepareProviderInput(adapter, { model: modelId, filePath: normalized.audioPath, signal });
|
|
274
|
+
providerInput = generationInput('transcribe', { audio_url: prepared.url }).providerInput;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const submitted = await createProviderTaskRunner(runtime).submit({ capability: mediaCapability,
|
|
278
|
+
candidates, providerInput });
|
|
279
|
+
const waited = await createCommandService(runtime, { resolveTaskAdapter: () => adapter }).execute('task.wait', {
|
|
280
|
+
task_id: submitted.task.id, timeout_ms: 60000, poll_interval_ms: 1000 });
|
|
281
|
+
const task = waited.task;
|
|
282
|
+
return { ok: task.outcome === 'succeeded' && task.deliveryState === 'ready', taskId: task.id,
|
|
283
|
+
category: task.lastError?.category, httpStatus: task.lastError?.httpStatus };
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
console.log(JSON.stringify(result, null, 2));
|
|
288
|
+
return result.valid === false || result.passed === false ? 1 : 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (args[0] === 'vision' && ['look', 'locate', 'ocr', 'summarize'].includes(args[1])) {
|
|
292
|
+
const command = args[1];
|
|
293
|
+
const options = flags(args.slice(2), ['provider-config', 'input', 'model-ref', 'format', 'output', 'sheet-output', 'data-root', 'timeout-ms']);
|
|
294
|
+
const input = normalizeVisionInput(command, jsonInput(options.input));
|
|
295
|
+
const format = options.format || 'json';
|
|
296
|
+
if (!['json', 'text'].includes(format)) throw new CliUsageError('--format 只支持 json 或 text');
|
|
297
|
+
if (options['sheet-output'] && command !== 'summarize') throw new CliUsageError('--sheet-output 仅适用于 summarize');
|
|
298
|
+
const outputs = [options.output, options['sheet-output']].filter(Boolean).map(file => path.resolve(file));
|
|
299
|
+
if (new Set(outputs).size !== outputs.length) throw new CliUsageError('结果文件与拼图文件不能是同一路径');
|
|
300
|
+
for (const file of outputs) {
|
|
301
|
+
try {
|
|
302
|
+
if (fs.existsSync(file) || !fs.statSync(path.dirname(file)).isDirectory()) throw new Error();
|
|
303
|
+
fs.accessSync(path.dirname(file), fs.constants.W_OK);
|
|
304
|
+
} catch (_) { throw new CliUsageError('输出文件已存在或父目录不可写'); }
|
|
305
|
+
}
|
|
306
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
307
|
+
const execute = runtime => executeVisionCommand({ command, input, catalog, runtime,
|
|
308
|
+
onRateLimit: event => recordCatalogRateLimit(options['provider-config'], event),
|
|
309
|
+
modelRef: options['model-ref'] || '', signal: cliAbortController.signal,
|
|
310
|
+
...(options['timeout-ms'] !== undefined ? { timeoutMs: Number(options['timeout-ms']) } : {}) });
|
|
311
|
+
const completed = input.transcribe
|
|
312
|
+
? await withRawRuntime('writer', options['data-root'], execute)
|
|
313
|
+
: input.artifact_id || input.frame_artifact_ids ? await withRawRuntime('reader', options['data-root'], execute) : await execute();
|
|
314
|
+
if (cliAbortController.signal.aborted) return 130;
|
|
315
|
+
const rendered = (format === 'text' ? completed.text : JSON.stringify(completed.result, null, 2)) + '\n';
|
|
316
|
+
try {
|
|
317
|
+
if (options.output) fs.writeFileSync(path.resolve(options.output), rendered, { flag: 'wx', mode: 0o600 });
|
|
318
|
+
if (options['sheet-output']) fs.writeFileSync(path.resolve(options['sheet-output']), completed.sheetBuffer, { flag: 'wx', mode: 0o600 });
|
|
319
|
+
} catch (_) { throw new CliUsageError('无法保存输出文件(不会覆盖已有文件)'); }
|
|
320
|
+
process.stdout.write(rendered);
|
|
321
|
+
return completed.exitCode;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (args[0] === 'media' && args[1] === 'html') {
|
|
325
|
+
const options = flags(args.slice(2), ['data-root', 'input', 'browser-executable', 'browser-no-sandbox', 'output']);
|
|
326
|
+
if (options['browser-no-sandbox'] !== undefined && !['true', 'false'].includes(options['browser-no-sandbox'])) throw new CliUsageError('--browser-no-sandbox 必须是 true 或 false');
|
|
327
|
+
const browser = createChromiumBrowser({ executable: options['browser-executable'] || process.env.IRIS_BROWSER_EXECUTABLE,
|
|
328
|
+
noSandbox: options['browser-no-sandbox'] === 'true' });
|
|
329
|
+
const input = jsonInput(options.input);
|
|
330
|
+
if (options.output && (!path.isAbsolute(options.output) || fs.existsSync(options.output))) throw new CliUsageError('--output 必须是未存在的绝对路径');
|
|
331
|
+
const result = await withRuntime('writer', options['data-root'], async commands => {
|
|
332
|
+
const rendered = await commands.execute('media.html', input);
|
|
333
|
+
return options.output ? { ...rendered, export: await commands.execute('artifact.export', { artifact_id: rendered.artifact.id, output_path: options.output }) } : rendered;
|
|
334
|
+
}, { browser });
|
|
335
|
+
console.log(JSON.stringify(result, null, 2));
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
162
339
|
if (args[0] === 'media' && ['diff', 'frames'].includes(args[1])) {
|
|
163
340
|
const options = flags(args.slice(2), ['data-root', 'input']);
|
|
164
341
|
const input = jsonInput(options.input);
|
|
@@ -180,37 +357,37 @@ async function main(args) {
|
|
|
180
357
|
|
|
181
358
|
if (args[0] === 'run' && args[1] === 'image') {
|
|
182
359
|
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
183
|
-
const { providerInput, modelRef } = generationInput('image', jsonInput(options.input));
|
|
360
|
+
const { providerInput, modelRef, sourceArtifactId } = generationInput('image', jsonInput(options.input));
|
|
184
361
|
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
185
362
|
const routes = imageCandidatesFromCatalog(catalog, modelRef);
|
|
186
363
|
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
187
364
|
const candidates = routes.map((route) => ({
|
|
188
|
-
adapter:
|
|
365
|
+
adapter: trackedProviderAdapter(route.provider, options), model: route.modelRef,
|
|
189
366
|
selectionReason: route.selectionReason,
|
|
190
367
|
providerBinding: providerTaskBinding(route.provider)
|
|
191
368
|
}));
|
|
192
369
|
const result = await createProviderTaskRunner(runtime).submit({
|
|
193
370
|
capability: 'image', candidates,
|
|
194
|
-
providerInput
|
|
371
|
+
providerInput, sourceArtifactId
|
|
195
372
|
});
|
|
196
373
|
console.log(JSON.stringify(result, null, 2));
|
|
197
374
|
});
|
|
198
375
|
return 0;
|
|
199
376
|
}
|
|
200
377
|
|
|
201
|
-
//
|
|
202
|
-
// s2v 上传流程不在 headless 面开放。
|
|
378
|
+
// 视频共用 Core;本地首帧/音频按实际候选在 Attempt 内准备。
|
|
203
379
|
if (args[0] === 'run' && args[1] === 'video') {
|
|
204
380
|
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
205
|
-
const
|
|
381
|
+
const normalized = generationInput('video', jsonInput(options.input), { allowVideoPaths: true });
|
|
382
|
+
const { providerInput, modelRef } = normalized;
|
|
206
383
|
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
207
384
|
const routes = videoCandidatesFromCatalog(catalog, modelRef);
|
|
208
385
|
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
209
|
-
const candidates = routes.map((route) => ({
|
|
210
|
-
adapter:
|
|
386
|
+
const candidates = videoTaskCandidates(routes.map((route) => ({
|
|
387
|
+
adapter: trackedProviderAdapter(route.provider, options), model: route.modelRef,
|
|
211
388
|
selectionReason: route.selectionReason,
|
|
212
389
|
providerBinding: providerTaskBinding(route.provider)
|
|
213
|
-
}));
|
|
390
|
+
})), normalized);
|
|
214
391
|
const result = await createProviderTaskRunner(runtime).submit({
|
|
215
392
|
capability: 'video', candidates,
|
|
216
393
|
providerInput
|
|
@@ -228,7 +405,7 @@ async function main(args) {
|
|
|
228
405
|
const routes = ttsCandidatesFromCatalog(catalog, modelRef);
|
|
229
406
|
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
230
407
|
const candidates = routes.map((route) => ({
|
|
231
|
-
adapter:
|
|
408
|
+
adapter: trackedProviderAdapter(route.provider, options), model: route.modelRef,
|
|
232
409
|
selectionReason: route.selectionReason,
|
|
233
410
|
providerBinding: providerTaskBinding(route.provider)
|
|
234
411
|
}));
|
|
@@ -251,14 +428,14 @@ async function main(args) {
|
|
|
251
428
|
let providerInput = normalized.providerInput;
|
|
252
429
|
if (normalized.audioPath) {
|
|
253
430
|
if (!fs.existsSync(normalized.audioPath)) throw new CliUsageError('音频文件不存在');
|
|
254
|
-
const prepared = await prepareProviderInput(
|
|
431
|
+
const prepared = await prepareProviderInput(trackedProviderAdapter(routes[0].provider, options), {
|
|
255
432
|
model: routes[0].model, filePath: normalized.audioPath
|
|
256
433
|
});
|
|
257
434
|
providerInput = generationInput('transcribe', { audio_url: prepared.url }).providerInput;
|
|
258
435
|
}
|
|
259
436
|
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
260
437
|
const candidates = routes.map((route) => ({
|
|
261
|
-
adapter:
|
|
438
|
+
adapter: trackedProviderAdapter(route.provider, options), model: route.modelRef,
|
|
262
439
|
selectionReason: route.selectionReason,
|
|
263
440
|
providerBinding: providerTaskBinding(route.provider)
|
|
264
441
|
}));
|
|
@@ -271,12 +448,24 @@ async function main(args) {
|
|
|
271
448
|
return 0;
|
|
272
449
|
}
|
|
273
450
|
|
|
451
|
+
if (args[0] === 'task' && args[1] === 'wait' && args[2]) {
|
|
452
|
+
const options = flags(args.slice(3), ['data-root', 'provider-config', 'timeout-ms', 'poll-interval-ms']);
|
|
453
|
+
const input = { task_id: args[2], ...(options['timeout-ms'] !== undefined ? { timeout_ms: Number(options['timeout-ms']) } : {}),
|
|
454
|
+
...(options['poll-interval-ms'] !== undefined ? { poll_interval_ms: Number(options['poll-interval-ms']) } : {}) };
|
|
455
|
+
const inspected = await withRuntime('reader', options['data-root'], commands => commands.execute('task.inspect', { task_id: args[2] }));
|
|
456
|
+
const catalog = coreTaskWaitFinished(inspected.task) ? null : loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
457
|
+
const result = await withRuntime(catalog ? 'writer' : 'reader', options['data-root'], commands => commands.execute('task.wait', input), {
|
|
458
|
+
...(catalog ? { resolveTaskAdapter: task => trackedProviderAdapter(providerForTaskFromCatalog(catalog, task), options, task) } : {}) });
|
|
459
|
+
console.log(JSON.stringify(result, null, 2));
|
|
460
|
+
return result.timedOut ? 3 : result.ready ? 0 : 1;
|
|
461
|
+
}
|
|
462
|
+
|
|
274
463
|
if (args[0] === 'task' && args[1] === 'observe' && args[2]) {
|
|
275
464
|
const options = flags(args.slice(3), ['data-root', 'provider-config']);
|
|
276
465
|
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
277
466
|
await withRuntime('reader', options['data-root'], (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
278
467
|
const result = await withRuntime('writer', options['data-root'], (commands) => commands.execute('task.observe', { task_id: args[2] }), {
|
|
279
|
-
resolveTaskAdapter: (task) =>
|
|
468
|
+
resolveTaskAdapter: (task) => trackedProviderAdapter(providerForTaskFromCatalog(catalog, task), options, task)
|
|
280
469
|
});
|
|
281
470
|
console.log(JSON.stringify(result, null, 2));
|
|
282
471
|
return 0;
|
|
@@ -287,7 +476,7 @@ async function main(args) {
|
|
|
287
476
|
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
288
477
|
await withRuntime('reader', options['data-root'], (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
289
478
|
const result = await withRuntime('writer', options['data-root'], (commands) => commands.execute('task.redeliver', { task_id: args[2] }), {
|
|
290
|
-
resolveTaskAdapter: (task) =>
|
|
479
|
+
resolveTaskAdapter: (task) => trackedProviderAdapter(providerForTaskFromCatalog(catalog, task), options, task)
|
|
291
480
|
});
|
|
292
481
|
console.log(JSON.stringify(result, null, 2));
|
|
293
482
|
return 0;
|
|
@@ -298,7 +487,7 @@ async function main(args) {
|
|
|
298
487
|
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
299
488
|
await withRuntime('reader', options['data-root'], (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
300
489
|
const result = await withRuntime('writer', options['data-root'], (commands) => commands.execute('task.cancel', { task_id: args[2] }), {
|
|
301
|
-
resolveTaskAdapter: (task) =>
|
|
490
|
+
resolveTaskAdapter: (task) => trackedProviderAdapter(providerForTaskFromCatalog(catalog, task), options, task)
|
|
302
491
|
});
|
|
303
492
|
console.log(JSON.stringify(result, null, 2));
|
|
304
493
|
return 0;
|
|
@@ -327,7 +516,7 @@ async function main(args) {
|
|
|
327
516
|
? transcribeCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
328
517
|
: imageCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
329
518
|
).map((route) => ({
|
|
330
|
-
adapter:
|
|
519
|
+
adapter: trackedProviderAdapter(route.provider, options),
|
|
331
520
|
model: route.modelRef,
|
|
332
521
|
selectionReason: route.selectionReason,
|
|
333
522
|
providerBinding: providerTaskBinding(route.provider)
|
|
@@ -346,9 +535,10 @@ async function main(args) {
|
|
|
346
535
|
}
|
|
347
536
|
|
|
348
537
|
if (args[0] === 'task' && args[1] === 'list') {
|
|
349
|
-
const options = flags(args.slice(2), ['data-root']);
|
|
538
|
+
const options = flags(args.slice(2), ['data-root', 'offset', 'limit', 'status', 'capability', 'provider-id', 'model-ref', 'outcome', 'delivery-state']);
|
|
539
|
+
const input = Object.fromEntries(Object.entries(options).filter(([key]) => key !== 'data-root').map(([key, value]) => [key.replaceAll('-', '_'), ['offset', 'limit'].includes(key) ? Number(value) : value]));
|
|
350
540
|
const result = await withRuntime('reader', options['data-root'], (commands) =>
|
|
351
|
-
commands.execute('task.list',
|
|
541
|
+
commands.execute('task.list', input));
|
|
352
542
|
console.log(JSON.stringify(result, null, 2));
|
|
353
543
|
return 0;
|
|
354
544
|
}
|
|
@@ -362,13 +552,24 @@ async function main(args) {
|
|
|
362
552
|
}
|
|
363
553
|
|
|
364
554
|
if (args[0] === 'artifact' && args[1] === 'list') {
|
|
365
|
-
const options = flags(args.slice(2), ['data-root']);
|
|
555
|
+
const options = flags(args.slice(2), ['data-root', 'offset', 'limit', 'kind', 'media-type', 'task-id']);
|
|
556
|
+
const input = Object.fromEntries(Object.entries(options).filter(([key]) => key !== 'data-root').map(([key, value]) => [key.replaceAll('-', '_'), ['offset', 'limit'].includes(key) ? Number(value) : value]));
|
|
366
557
|
const result = await withRuntime('reader', options['data-root'], (commands) =>
|
|
367
|
-
commands.execute('artifact.list',
|
|
558
|
+
commands.execute('artifact.list', input));
|
|
368
559
|
console.log(JSON.stringify(result, null, 2));
|
|
369
560
|
return 0;
|
|
370
561
|
}
|
|
371
562
|
|
|
563
|
+
if (['task', 'artifact'].includes(args[0]) && ['inspect-many', 'export-many'].includes(args[1])) {
|
|
564
|
+
if (args[0] === 'task' && args[1] === 'export-many') throw new CliUsageError('仅 Artifact 支持 export-many');
|
|
565
|
+
const exporting = args[1] === 'export-many';
|
|
566
|
+
const options = flags(args.slice(2), ['data-root', 'input', ...(exporting ? ['output'] : [])]);
|
|
567
|
+
const input = { ...jsonInput(options.input), ...(exporting ? { output_directory: required(options.output, 'output') } : {}) };
|
|
568
|
+
const result = await withRuntime(exporting ? 'writer' : 'reader', options['data-root'], commands => commands.execute(args[0] + '.' + args[1], input));
|
|
569
|
+
console.log(JSON.stringify(result, null, 2));
|
|
570
|
+
return result.results.some(entry => entry.error) ? 1 : 0;
|
|
571
|
+
}
|
|
572
|
+
|
|
372
573
|
if (args[0] === 'artifact' && args[1] === 'rebuild') {
|
|
373
574
|
const options = flags(args.slice(2), ['data-root']);
|
|
374
575
|
const result = await withRuntime('writer', options['data-root'], (commands) =>
|
package/docs/AGENT_QUICKSTART.md
CHANGED
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
把下面一段交给负责安装或使用 Iris 的 Agent。版本与发布状态以当前 [README](../README.md) 为准;Iris 和 DSH 的版本号独立。
|
|
4
4
|
|
|
5
|
+
当前安装版本为 Iris 0.2.2。先核对宿主与实际安装版本,再使用新视觉命令或 Artifact 改图;旧宿主仍固定安装 0.1.4。
|
|
6
|
+
|
|
5
7
|
## 中文
|
|
6
8
|
|
|
7
9
|
```text
|
|
8
10
|
请根据本仓库 README,帮我安装并使用 Iris(@mokuyoaxis/dsh-iris)。
|
|
9
|
-
先确认运行环境、目标 DSH profile,并用 dsh --version 核对宿主;Iris 0.2.
|
|
10
|
-
- DSH 0.2.0-rc.2:固定安装 dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.
|
|
11
|
+
先确认运行环境、目标 DSH profile,并用 dsh --version 核对宿主;Iris 0.2.x 要求 Node.js >=22.0.0(旧 npm 0.1.4 的历史要求为 >=20.10.0):
|
|
12
|
+
- DSH 0.2.0-rc.2:固定安装 dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.2。
|
|
11
13
|
- 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
14
|
- 其他 DSH 版本:先报告没有支持声明,不自行升级宿主或跳过版本检查。
|
|
13
|
-
没有 DSH、希望用 CLI 时,按 docs/HEADLESS_CLI.md 使用 npm 0.2.
|
|
15
|
+
没有 DSH、希望用 CLI 时,按 docs/HEADLESS_CLI.md 使用 npm 0.2.2;npm 0.1.4 不含新的 Core 媒体 CLI。
|
|
16
|
+
先用 --help 和 Doctor 核对实际版本与命令。Iris 0.2.2 支持 OpenAI 兼容图片与视觉工作流;其他协议与 music 是后续计划。
|
|
14
17
|
从 dsh-market 安装也要核对实际包名、版本和兼容范围;市场缓存不代表支持旧宿主,不对旧 DSH 安装 latest。
|
|
15
18
|
若使用其他 profile,把示例中的 web 换成已确认的目标。保留完整 scoped 包名,不把 dsh-iris 当作同一 npm 包。
|
|
16
19
|
安装后检查 Doctor 和工具可达性,再执行我指定的媒体任务;不要输出 API Key,重启宿主前说明影响。
|
|
@@ -22,11 +25,12 @@
|
|
|
22
25
|
|
|
23
26
|
```text
|
|
24
27
|
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.
|
|
26
|
-
- DSH 0.2.0-rc.2: pin dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.
|
|
28
|
+
Check the environment, target DSH profile, and dsh --version first; Iris 0.2.x requires Node.js >=22.0.0 (the published npm 0.1.4 historically requires >=20.10.0):
|
|
29
|
+
- DSH 0.2.0-rc.2: pin dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.2.
|
|
27
30
|
- 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
31
|
- 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.
|
|
32
|
+
For CLI use without DSH, follow docs/HEADLESS_CLI.md using npm 0.2.2; npm 0.1.4 does not include the new Core media CLI.
|
|
33
|
+
Check --help, Doctor and the actual installed version before using commands. Iris 0.2.2 supports the OpenAI-compatible image and vision workflow; additional protocols and music remain future work.
|
|
30
34
|
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
35
|
Replace web with the confirmed profile when needed. Keep the scoped package name; unscoped dsh-iris is a different npm package.
|
|
32
36
|
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.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Iris 架构与边界
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
状态:本文面向 **Iris 0.2.2**。0.2.0 建立实例化 Core Runtime、Headless CLI、共享 Provider 生命周期与 Task/Artifact 事实;0.2.2 补共享视觉、模型级图片协议、Artifact 编辑及维护入口。DSH 插件仅支持 `0.2.0-rc.2`,Node.js 最低为 22.0.0;旧宿主固定使用 npm `0.1.4`。Core 内部 API 尚未冻结为公共 SDK。
|
|
4
4
|
|
|
5
5
|
## 当前形态
|
|
6
6
|
|
|
@@ -22,7 +22,7 @@ DSH Host / Cordis Headless CLI
|
|
|
22
22
|
┌────────────┴────────────┐
|
|
23
23
|
▼ ▼
|
|
24
24
|
Command Service v0 Provider Task Runner
|
|
25
|
-
crop / diff / frames
|
|
25
|
+
crop / diff / frames / html submit / observe /
|
|
26
26
|
inspect / control cancel / delivery
|
|
27
27
|
└────────────┬────────────┘
|
|
28
28
|
▼
|
|
@@ -35,20 +35,21 @@ DSH Host / Cordis Headless CLI
|
|
|
35
35
|
|
|
36
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 投影,不是真相来源。
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
0.2.1的 S2V 使用 Core Task/Attempt,HTML 使用 `media.html` 与显式 Browser Port;视觉和提示词预览通过共享 Model Ports 执行。纯视觉不创建 Task/Artifact,摘要主动转写仍使用 Core 任务。聊天改图属于媒体生成链,按来源 Artifact ID 创建新 Task 和关联产物。
|
|
39
39
|
|
|
40
|
-
##
|
|
40
|
+
## 共享核心
|
|
41
41
|
|
|
42
42
|
| 模块 | 职责 | DSH 运行时依赖 |
|
|
43
43
|
|---|---|---:|
|
|
44
|
-
| `core-contract.js` / `core-runtime.js` | 显式数据根、reader/writer 权限、单写者租约、取消与释放 | 无 |
|
|
44
|
+
| `core-contract.js` / `core-runtime.js` / `core-writer-lease.js`(0.2.2) | 显式数据根、reader/writer 权限、单写者租约、取消与释放 | 无 |
|
|
45
45
|
| `command-service.js` | 本地媒体命令、Task 控制面和 Artifact 查询/导出 | 无 |
|
|
46
46
|
| `generation-input.js` | CLI、DSH 与重试共用的生成参数校验和 Provider 字段转换 | 无 |
|
|
47
|
-
| `model-port-contract.js` / `model-invoker.js` | Text/Vision
|
|
47
|
+
| `model-port-contract.js` / `model-invoker.js` | Text/Vision 内部契约、有限调用预算与取消 | 无 |
|
|
48
48
|
| `core-tasks.js` | Core Task/Attempt 持久事实、受理与交付状态 | 无 |
|
|
49
49
|
| `core-artifacts.js` / `core-artifact-store.js` | 对象、Manifest、SHA-256、关系边与可重建 Index | 无 |
|
|
50
50
|
| `provider-contract.js` / `provider-adapter.js` | canonical 结果、错误脱敏、受理边界与 Provider v0 操作 | 无 |
|
|
51
|
-
| `provider-adapters.js` | DashScope /
|
|
51
|
+
| `provider-adapters.js` | DashScope / Images / 聊天 / Responses 图片协议和可选输入准备 | 无 |
|
|
52
|
+
| `vision-image-input.js` / `image-edit-input.js` | 视觉发送副本与聊天改图来源预检;Core 原图保持不变 | 无 |
|
|
52
53
|
| `provider-task-runner.js` | 写前 Attempt、submit/poll/cancel/download 与 Profile 化交付 | 无 |
|
|
53
54
|
| `core-doctor.js` / `core-lease-recovery.js` | 只读完整性诊断与需显式确认的陈旧租约恢复 | 无 |
|
|
54
55
|
| `provider-catalog.js` | 显式 Provider 配置、能力候选和原 Task Adapter 恢复 | 无 |
|
|
@@ -56,7 +57,9 @@ DSH Host / Cordis Headless CLI
|
|
|
56
57
|
| `dsh-host-adapter.js` / `index.js` | Host 能力映射、工具/路由/Skill 与生命周期装配 | 有 |
|
|
57
58
|
| `client.js` | DSH Web Slot、统一任务/作品区、泡泡和提示词入口 | 有 |
|
|
58
59
|
|
|
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)
|
|
60
|
+
完整规则见 [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)。这些仍是内部契约,不是第三方 SDK 兼容承诺。
|
|
61
|
+
|
|
62
|
+
0.2.2 的 Core writer 在创建目录前发布完整 owner 意图,释放和显式恢复通过整体退役保留证据;普通启动不抢占。非图片 staging 复用文件复制接收与分块哈希,图片格式验证和 inline 解码仍可能使用完整 Buffer,不声称端到端流式传输。
|
|
60
63
|
|
|
61
64
|
## Task、Attempt 与 Artifact
|
|
62
65
|
|
|
@@ -65,23 +68,35 @@ DSH Host / Cordis Headless CLI
|
|
|
65
68
|
- 生成结果与本地交付是两类事实。远端成功后下载失败仍保持 `outcome=succeeded / deliveryState=failed`,可显式重新交付而不重新生成。
|
|
66
69
|
- 新 Attempt 可选保存阶段时间戳与模型选择来源;旧记录缺少这些字段仍可读取。
|
|
67
70
|
- Artifact Manifest v0 保存内容哈希、媒体类型、中性元数据和关系边;不保存 Prompt、API Key、签名 URL、Provider 端点或宿主绝对路径。
|
|
68
|
-
- Core
|
|
71
|
+
- Core/CLI 已提供默认只读预览、明确选择与确认、文件隔离和恢复的删除/清理 Command,保护活跃 Task 和引用;索引更新只使用已提交记录,不升级或接回孤儿。DSH 作品区共用删除预览、隔离与事务恢复,永久 purge 未开放。Doctor 只报告,不自动修复。见 [CLI 管理与补齐](CLI_MANAGEMENT.md)。
|
|
69
72
|
|
|
70
|
-
0.1.4 的 `tasks.json`、`artifacts.json`、`outputs/` 与上传目录继续作为 legacy 数据存在;Core 使用隔离的 `core-v0`。已迁移的新图片、视频、TTS 与转写任务不双写 legacy,回退到 0.1.4 时也不删除或改写 Core 数据。
|
|
73
|
+
0.1.4 的 `tasks.json`、`artifacts.json`、`outputs/` 与上传目录继续作为 legacy 数据存在;Core 使用隔离的 `core-v0`。已迁移的新图片、视频、S2V、TTS 与转写任务不双写 legacy,回退到 0.1.4 时也不删除或改写 Core 数据。
|
|
71
74
|
|
|
72
75
|
## Provider 边界
|
|
73
76
|
|
|
77
|
+
0.2.2按功能组织有序模型候选池,能力来自明确模型标签、旧能力字段绑定或供应商能力元数据。裸账号不物化默认模型,模型名称不授权进入候选链;明确空池与空标签保持权威。模型与供应商复合身份用于底层执行器,功能入口、分配排序及安全 failover 没有改为模型中心产品或默认轮转。旧 0.2.1 的能力推断行为不再用于裸账号或模型名称赋能。
|
|
78
|
+
|
|
74
79
|
Provider Adapter v0 固定 discovery、submit、poll、cancel、download 与 error mapping 六类生命周期操作;提交前的临时输入上传是可选准备能力,不自动成为 Core 事实。协议实现只接收显式凭据、模型和媒体端点,Core 保存复合模型身份与非敏感 binding,不保存端点原文。
|
|
75
80
|
|
|
76
|
-
|
|
81
|
+
0.2.2 的生图错误将 HTTP 前的尺寸拒绝明确记为未受理,网络异常保留实际请求/正文阶段及可用错误码,原生取消/超时保留原分类。错误诊断不改变受理门,网络超时或正文丢失仍不能触发自动重提。
|
|
82
|
+
|
|
83
|
+
Core Runner 不拥有后台 timer。Headless `task observe` 每次最多 poll 一次,`task wait` 仅在本次命令内有界观察;DSH 的有界自动观察属于 Host,插件释放时停止 timer 并中止在途观察,但不会把“停止本地观察”写成“远端已取消”。端点或协议 binding 漂移时恢复在联网前拒绝。
|
|
77
84
|
|
|
78
85
|
每个操作必须明确声明 supported 或 unsupported。当前 DashScope 与 OpenAI Images 媒体协议没有经过验证的远端取消实现,因此取消请求如实返回 not-supported,而不是伪造 canceled。完整受理与错误规则见 [Provider 提交契约 v0](PROVIDER_SUBMISSION_CONTRACT.md)。
|
|
79
86
|
|
|
80
|
-
## Text/Vision
|
|
87
|
+
## Text/Vision 共享边界
|
|
88
|
+
|
|
89
|
+
[Text/Vision Model Port v0](MODEL_PORT_CONTRACT.md) 的纯契约、调用控制与 test-only Fake Port 已发布于 0.2.0。它以 `describe()` 和单轮 `complete()` 统一文本/视觉输入、正常终态、预算、取消与安全错误;图片以字节和 MIME 进入共享层,模型选择、会话与附件桥接保留在入口/适配器。
|
|
90
|
+
|
|
91
|
+
0.2.1通过 `prompt-optimizer-core.js` → `model-invoker.js` → `dsh-text-model-adapter.js` 接入首个提示词消费者;`prompt-optimizer.js` 继续拥有 v1 配置、模型选择、reasoning 策略和 Host 投影。共享核心不读取 Host/配置/Store,不取得 writer,也不创建媒体 Task/Attempt/Artifact。规则与预览见 [提示词优化系统](PROMPT_OPTIMIZER.md)。
|
|
92
|
+
|
|
93
|
+
0.2.2 的一般提示词功能在这个共享消费者上增加 faithful / rewrite / suggestions 策略:忠实正文由原稿片段按原顺序拼回,建议独立返回并要求明确采纳。五用途和折叠预览属于 DSH UI,不自动进入生图模型池,也不新增自持文本目录、CLI 或公共 SDK。
|
|
94
|
+
|
|
95
|
+
单图视觉将 Agent/工作台 look/relook 和显式视觉实测接入 `vision-core.js` 与 HTTP/DSH Vision Port:共享 120 秒整体预算、完整终态与候选策略,图片桥接核验原图与实际附件,接受合法宿主规范化。DSH 附件保存属于宿主副作用,不是 Core Artifact;rc.2 保存不可取消的限制如实保留。见 [单图视觉调用](VISION_MODEL.md)。视觉 CLI 复用这些业务;可选 `visionInput` 在入口层控制发送副本。未新增公共 SDK export,自持文本目录和独立提示词入口另行安排。
|
|
81
96
|
|
|
82
|
-
|
|
97
|
+
OCR 切片通过 `ocr-model-routing.js` 在入口解引用前建立整体 operation,`ocr.js` 仅接收图片字节和模型端口,使用现有 Sharp 做有限分块;全部块复用 `completeVision`,没有每块重置计时或调用额度。普通块失败可以产生明确部分结果,取消/超时终止整次操作;不取得 Core writer 或写入 Task/Artifact。见 [长图 OCR](OCR_MODEL.md)。
|
|
83
98
|
|
|
84
|
-
|
|
99
|
+
定位、拼图摘要与生成后自述复用 `runVisionOperation`,`locate.js` / `summarize.js` 消费共享端口;入口处理附件、抽帧和可选 Core 转写,摘要读取已成功转写的文本 Artifact。纯视觉业务不写 Task/Artifact。legacy 视觉兼容实现保留,现有入口不再调用。见 [复合视觉调用](COMPOSITE_VISION.md)。
|
|
85
100
|
|
|
86
101
|
## Host Port 与 Headless 边界
|
|
87
102
|
|
|
@@ -90,7 +105,7 @@ Core Runner 不拥有后台 timer。Headless `task observe` 每次最多 poll
|
|
|
90
105
|
| 工具注册与 Fiber 生命周期 | `ctx.tools` / `ctx.effect` | 不注册 DSH 工具;CLI/Core 不受影响 |
|
|
91
106
|
| Web 路由 | `webServer` 或 `httpServer` | 无工作台 API 和同源媒体路由;CLI 直接导出 Artifact |
|
|
92
107
|
| UI Slot | DSH Web Client | 不影响 Core Task、Artifact 与离线 Doctor |
|
|
93
|
-
| Browser | DSH browser service | HTML
|
|
108
|
+
| Browser | DSH browser service | CLI 可显式提供独立 Chromium Browser Port;未提供时 HTML 不可用 |
|
|
94
109
|
| 会话附件与输入草稿 | session/input ports | CLI 使用显式文件或 JSON 输入,不创建会话对象 |
|
|
95
110
|
| Skills registry | DSH skills service | 随包 Skill 不自动注册;CLI/Core 仍可用 |
|
|
96
111
|
|
|
@@ -98,14 +113,14 @@ Command、Core Store、Provider Runner 和 Headless CLI 不接收原始 `ctx`、
|
|
|
98
113
|
|
|
99
114
|
## 事件、UI 与媒体读取
|
|
100
115
|
|
|
101
|
-
DSH 工作台把 Core 与 legacy Task 合并到一个用户任务区,把 Core
|
|
116
|
+
DSH 工作台把 Core 与 legacy Task 合并到一个用户任务区,把 Core 与旧 `outputs/` 的图片、视频、音频和文本合并到一个作品区。普通投影只有五类用户状态;原始 Attempt 事实留在高级诊断。刷新、展开、关闭、读取作品和查询状态都不得触发 submit、poll 或隐式修复。
|
|
102
117
|
|
|
103
118
|
Core 媒体路由按随机 Artifact ID 读取并逐次验证 SHA-256;它没有独立 token。浏览器请求还受可信 Host、`Origin`/`Sec-Fetch-Site` 和 `Referrer-Policy: no-referrer` 约束。Artifact ID 是持有者标识,不等于多租户认证;Iris 与 DSH 仍处于同一本机信任域。
|
|
104
119
|
|
|
105
120
|
## 非目标与发布边界
|
|
106
121
|
|
|
107
|
-
- `0.2.
|
|
122
|
+
- `0.2.1` 不启动独立常驻服务,也不复制 DSH 聊天界面。
|
|
108
123
|
- 不迁移到 SQLite/JSONL,不原地改写 0.1.4 数据,不自动接管不确定的写者租约。
|
|
109
|
-
-
|
|
124
|
+
- 0.2.1 未新增原生 Gemini、Fal、Replicate 或本地模型协议;注册表只是后续接入点。
|
|
110
125
|
- 不把离线测试、一次 canary 或 Headless 可运行等同于公开 Core API 已冻结。
|
|
111
|
-
-
|
|
126
|
+
- 发布检查覆盖版本元数据、实际包审计、仓库外安装与跨平台 CI;浏览器/移动端和真实异步重启范围单独记录,不由程序化检查推定通过。
|