@mokuyoaxis/dsh-iris 0.3.0-alpha.1 → 0.3.0-beta.1
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 +63 -1
- package/README.en.md +20 -13
- package/README.md +20 -13
- package/bin/dsh-iris.js +143 -51
- package/docs/AGENT_CORE_QUICKSTART.md +2 -2
- package/docs/AGENT_QUICKSTART.md +1 -1
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/ARTIFACT_MANIFEST.md +4 -0
- package/docs/AUDIO_FLOWS.md +20 -4
- package/docs/BETA_SCOPE.md +42 -0
- package/docs/CONTACT_SHEET.md +1 -1
- package/docs/CORE_CONNECTIONS.md +3 -1
- package/docs/CORE_QUICKSTART.md +7 -5
- package/docs/CORE_RUNTIME_CONTRACT.md +3 -3
- package/docs/CORE_SERVICE.md +9 -5
- package/docs/CORE_SKILLS.md +2 -2
- package/docs/DSH_COMPATIBILITY.md +11 -1
- package/docs/DSH_CORE_MIGRATION.md +6 -0
- package/docs/DSH_RUNTIME.md +2 -2
- package/docs/HEADLESS_CLI.md +29 -4
- package/docs/HOST_ADAPTER_CONTRACT.md +5 -3
- package/docs/IMAGE_FLOW.md +2 -2
- package/docs/IMAGE_GENERATION.md +1 -1
- package/docs/LOCAL_API.md +2 -2
- package/docs/LOCAL_API_CONTRACT.md +17 -15
- package/docs/LOCAL_CREATION.md +2 -2
- package/docs/LOCAL_OPERATIONS.md +2 -2
- package/docs/LOCAL_SELECTION.md +1 -1
- package/docs/LOCAL_WEB.md +11 -5
- package/docs/MCP.md +30 -9
- package/docs/MEDIA_SCHEDULING.md +72 -0
- package/docs/NETWORK_RESOURCES.md +106 -0
- package/docs/NETWORK_ROUTING.md +103 -0
- package/docs/NETWORK_WORKBENCH.md +65 -0
- package/docs/PANEL_EXTENSIONS.md +1 -1
- package/docs/PLUGINS.md +9 -5
- package/docs/PLUGIN_DEVELOPMENT.md +39 -0
- package/docs/PLUGIN_STARTUP.md +15 -2
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +8 -2
- package/docs/PROVIDER_CREDENTIALS.md +67 -0
- package/docs/PROVIDER_EXTENSIONS.md +8 -4
- package/docs/PROVIDER_HEALTH.md +4 -2
- package/docs/PROVIDER_NETWORK.md +45 -0
- package/docs/PROVIDER_PLUGIN_CONTRACT.md +24 -0
- package/docs/PROVIDER_SETTINGS.md +13 -7
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +3 -1
- package/docs/ROADMAP.md +6 -6
- package/docs/SECURITY.md +1 -1
- package/docs/SYNC_DELIVERY_RECOVERY.md +5 -1
- package/docs/VIDEO_FLOWS.md +89 -0
- package/docs/VISION_MODEL.md +2 -0
- package/docs/WEB_PROTOTYPE.md +3 -3
- package/docs/WEB_RECOVERY.md +2 -2
- package/docs/WORKBENCH_ARTIFACTS.md +6 -0
- package/docs/WORKBENCH_LOGIN.md +2 -2
- package/docs/releases/0.3.0-beta.1.md +34 -0
- package/docs/screenshots.md +2 -2
- package/lib/actions.js +5 -4
- package/lib/adapters.js +58 -41
- package/lib/api.js +23 -13
- package/lib/application-runtime.js +44 -15
- package/lib/application-writer.js +193 -0
- package/lib/client.d.ts +3 -3
- package/lib/client.js +62 -25
- package/lib/comfyui-adapter.js +3 -2
- package/lib/comfyui-client.js +10 -1
- package/lib/command-service.js +18 -5
- package/lib/config.js +13 -9
- package/lib/configured-image-runtime.js +28 -9
- package/lib/connection-skill.js +9 -0
- package/lib/core-artifact-store.js +68 -26
- package/lib/core-bridge-tools.js +5 -3
- package/lib/core-capability-catalog.js +21 -11
- package/lib/core-delivery-cache.js +8 -3
- package/lib/core-service-client.js +6 -2
- package/lib/core-tasks.js +86 -11
- package/lib/creation-request-store.js +8 -5
- package/lib/doctor.js +2 -2
- package/lib/dsh-core-adapter.js +61 -9
- package/lib/dsh-provider-config.js +1 -1
- package/lib/dsh-service-bridge.js +1 -0
- package/lib/flow-store.js +64 -17
- package/lib/image-flow.js +39 -17
- package/lib/index.js +10 -5
- package/lib/local-api-projection.js +6 -2
- package/lib/local-api.js +137 -49
- package/lib/local-creation.js +12 -5
- package/lib/local-operations.js +2 -2
- package/lib/local-request-root.js +2 -2
- package/lib/local-web-assets.js +2 -0
- package/lib/mcp-http.js +82 -9
- package/lib/mcp-server.js +23 -1
- package/lib/media-scheduler.js +194 -0
- package/lib/model-directory-service.js +25 -8
- package/lib/music-transport.js +4 -2
- package/lib/network-config-service.js +123 -0
- package/lib/network-config.js +127 -0
- package/lib/network-doctor.js +94 -0
- package/lib/network-management.js +51 -0
- package/lib/network-policy.js +19 -0
- package/lib/network-route-evidence.js +38 -0
- package/lib/network-routing.js +283 -0
- package/lib/operation-runner.js +83 -38
- package/lib/operation-store.js +6 -6
- package/lib/plugins.d.ts +25 -6
- package/lib/plugins.js +2 -1
- package/lib/provider-adapter.js +2 -2
- package/lib/provider-adapters.js +37 -5
- package/lib/provider-catalog.js +33 -16
- package/lib/provider-config-format.js +51 -0
- package/lib/provider-config-service.js +52 -22
- package/lib/provider-context.d.ts +90 -0
- package/lib/provider-context.js +42 -0
- package/lib/provider-credentials.js +97 -0
- package/lib/provider-diagnostics.js +5 -5
- package/lib/provider-health.js +10 -5
- package/lib/provider-monitor.js +5 -3
- package/lib/provider-network.js +52 -0
- package/lib/provider-protocol.js +3 -2
- package/lib/provider-registry.js +15 -5
- package/lib/provider-response.js +10 -0
- package/lib/provider-settings.js +14 -12
- package/lib/provider-task-runner.js +113 -37
- package/lib/provider-transport.js +16 -0
- package/lib/render.js +11 -1
- package/lib/scheduling-policy.js +66 -0
- package/lib/tasks.js +3 -2
- package/lib/video-flow-input.js +16 -0
- package/lib/video-input.js +12 -5
- package/lib/vision-model-routing.js +2 -0
- package/lib/web/app.js +107 -46
- package/lib/web/connections.js +151 -12
- package/lib/web/creation.js +29 -25
- package/lib/web/flow-view.js +4 -3
- package/lib/web/gallery.js +17 -16
- package/lib/web/index.html +58 -5
- package/lib/web/media-client.js +18 -4
- package/lib/web/network.js +232 -0
- package/lib/web/plugin-panel.js +4 -2
- package/lib/web/plugin-status.js +140 -0
- package/lib/web/providers.js +34 -13
- package/lib/web/recovery.js +2 -2
- package/lib/web/styles.css +54 -1
- package/lib/web/transport.js +6 -1
- package/lib/workbench-artifacts.js +3 -2
- package/package.json +5 -2
- package/skills/iris-core/references/comfyui.md +1 -1
- package/templates/plugin-starter/LICENSE +21 -0
- package/templates/plugin-starter/README.md +74 -0
- package/templates/plugin-starter/assets/peach.png +0 -0
- package/templates/plugin-starter/assets/violet.png +0 -0
- package/templates/plugin-starter/index.mjs +27 -0
- package/templates/plugin-starter/package.json +18 -0
- package/templates/plugin-starter/scripts/core-cli.mjs +20 -0
- package/templates/plugin-starter/scripts/environment.mjs +65 -0
- package/templates/plugin-starter/scripts/mcp-probe.mjs +22 -0
- package/templates/plugin-starter/scripts/setup.mjs +42 -0
- package/templates/plugin-starter/scripts/smoke.mjs +76 -0
- package/templates/plugin-starter/web/board.css +27 -0
- package/templates/plugin-starter/web/board.mjs +160 -0
- package/templates/plugin-starter/web/png.mjs +23 -0
- package/templates/provider-starter/LICENSE +21 -0
- package/templates/provider-starter/README.md +50 -0
- package/templates/provider-starter/index.d.mts +5 -0
- package/templates/provider-starter/index.mjs +165 -0
- package/templates/provider-starter/package.json +13 -0
- package/templates/provider-starter/scripts/core-cli.mjs +22 -0
- package/templates/provider-starter/scripts/environment.mjs +122 -0
- package/templates/provider-starter/scripts/fixture.mjs +54 -0
- package/templates/provider-starter/scripts/mcp-probe.mjs +21 -0
- package/templates/provider-starter/scripts/protocol-smoke.mjs +48 -0
- package/templates/provider-starter/scripts/setup.mjs +44 -0
- package/templates/provider-starter/scripts/smoke.mjs +145 -0
- package/user_guide.md +3 -3
package/bin/dsh-iris.js
CHANGED
|
@@ -18,28 +18,46 @@ import { executeVisionCommand, normalizeVisionInput } from '../lib/headless-visi
|
|
|
18
18
|
import { videoTaskCandidates } from '../lib/video-input.js';
|
|
19
19
|
import { createChromiumBrowser } from '../lib/chromium-browser.js';
|
|
20
20
|
import { executeConfigCommand, recordCatalogRateLimit, recordCatalogAvailability, refreshCatalogDirectories } from '../lib/provider-config-service.js';
|
|
21
|
+
import { executeNetworkConfigCommand } from '../lib/network-config-service.js';
|
|
21
22
|
import { probeVisionModel } from '../lib/vision-model-routing.js';
|
|
22
23
|
import { RED_TEST_IMAGE } from '../lib/vision.js';
|
|
23
24
|
import { parseModelRef, modelRef as qualifiedModelRef } from '../lib/models.js';
|
|
24
25
|
import { coreTaskWaitFinished } from '../lib/task-wait.js';
|
|
25
26
|
import { createLocalReadApi, writeLocalApiTokenFile } from '../lib/local-api.js';
|
|
26
27
|
import { manageCoreSkill } from '../lib/skill-installation.js';
|
|
27
|
-
import { createTrackedProviderAdapter } from '../lib/configured-image-runtime.js';
|
|
28
|
+
import { createTrackedProviderAdapter, configuredImagePorts } from '../lib/configured-image-runtime.js';
|
|
28
29
|
import { inspectExecutionLease, recoverExecutionLease } from '../lib/operation-runner.js';
|
|
29
30
|
import { createConnectionPair } from '../lib/core-connections.js';
|
|
30
31
|
import { builtinLaunchPlugins, loadPluginLaunch } from '../lib/plugin-launch.js';
|
|
31
32
|
import { checkLocalApiTokenDirectory, writeLocalApiTokenDirectory } from '../lib/local-api-token-directory.js';
|
|
32
33
|
import { resolveWorkbenchAuthDirectory } from '../lib/workbench-auth.js';
|
|
33
34
|
import { manageWorkbenchPassword } from '../lib/workbench-auth-cli.js';
|
|
35
|
+
import crypto from 'node:crypto';
|
|
36
|
+
import { atomicCreatePrivate } from '../lib/private-storage.js';
|
|
37
|
+
import { providerNetworkRequirement, networkPolicyError } from '../lib/network-policy.js';
|
|
38
|
+
import { createProviderNetworkManager } from '../lib/network-routing.js';
|
|
39
|
+
import { encodeProviderConfig, wrapProviderCatalog } from '../lib/provider-config-format.js';
|
|
40
|
+
import { readNetworkConfig } from '../lib/network-config-service.js';
|
|
41
|
+
import { readSchedulingPolicy } from '../lib/scheduling-policy.js';
|
|
42
|
+
import { createMediaScheduler } from '../lib/media-scheduler.js';
|
|
43
|
+
|
|
44
|
+
let cliNetworkConfigPath, cliNetworkManager, cliScheduler;
|
|
45
|
+
function cliResolveNetwork({ provider, ...context }) {
|
|
46
|
+
const requirement = context.networkRequirement || providerNetworkRequirement(provider);
|
|
47
|
+
if (requirement === undefined && !cliScheduler) return undefined;
|
|
48
|
+
cliNetworkManager ||= createProviderNetworkManager({ networkConfigPath: cliNetworkConfigPath, scheduler: cliScheduler });
|
|
49
|
+
return cliNetworkManager.resolveProviderNetwork({ ...context, networkRequirement: requirement });
|
|
50
|
+
}
|
|
34
51
|
|
|
35
52
|
function trackedProviderAdapter(provider, options, task) {
|
|
36
|
-
return createTrackedProviderAdapter(provider, options['provider-config'], task, options.providerRegistry
|
|
53
|
+
return createTrackedProviderAdapter(provider, options['provider-config'], task, options.providerRegistry, {
|
|
54
|
+
network: cliResolveNetwork({ provider, providerId: provider.id, taskId: task?.id }) });
|
|
37
55
|
}
|
|
38
56
|
|
|
39
57
|
async function generationCatalog(options, capability, modelRef, signal = cliAbortController.signal) {
|
|
40
58
|
const file = required(options['provider-config'], 'provider-config');
|
|
41
59
|
const catalog = loadProviderCatalog(file);
|
|
42
|
-
if (!modelRef) await refreshCatalogDirectories(file, catalog, capability, { signal, registry: options.providerRegistry });
|
|
60
|
+
if (!modelRef) await refreshCatalogDirectories(file, catalog, capability, { signal, registry: options.providerRegistry, resolveProviderNetwork: cliResolveNetwork });
|
|
43
61
|
return catalog;
|
|
44
62
|
}
|
|
45
63
|
|
|
@@ -68,13 +86,13 @@ function usage() {
|
|
|
68
86
|
' install/uninstall accept --dry-run. Copy only iris-core; preserve local changes and archive managed updates/removals outside the scan directory. status is read-only and does not verify host discovery.',
|
|
69
87
|
' dsh-iris doctor [--data-root <absolute-path>] [--json]',
|
|
70
88
|
' dsh-iris api init --data-root <empty-existing-flow-root>',
|
|
71
|
-
' dsh-iris execution init --data-root <empty-existing-flow-root> [--image-generation|--audio]',
|
|
72
|
-
' Initialize a NEW v1 request container, v2 with --image-generation,
|
|
89
|
+
' dsh-iris execution init --data-root <empty-existing-flow-root> [--image-generation|--audio|--video]',
|
|
90
|
+
' Initialize a NEW v1 request container, v2 with --image-generation, v3 with --audio, or v4 with --video (images + audio + text-to-video). Existing roots are never migrated.',
|
|
73
91
|
' dsh-iris execution inspect --data-root <v1-flow-root>',
|
|
74
92
|
' dsh-iris execution recover-lease --data-root <v1-flow-root> --confirm-stale-pid <pid>',
|
|
75
|
-
' dsh-iris connection create --data-root <execution-flow-root> --directory <new-private-directory> --origin <http://127.0.0.1:port> --label <name> [--harness <generic|kimi|dsh>] [--scopes <read,create,execute,stop>] [--capabilities <image-edit,image-generate,tts,transcribe|none>] [--tools <comma-separated-names|none>]',
|
|
93
|
+
' dsh-iris connection create --data-root <execution-flow-root> --directory <new-private-directory> --origin <http://127.0.0.1:port> --label <name> [--harness <generic|kimi|dsh>] [--scopes <read,create,execute,stop>] [--capabilities <image-edit,image-generate,tts,transcribe,video-generate|none>] [--tools <comma-separated-names|none>]',
|
|
76
94
|
' dsh-iris api serve ... --allow-execution --provider-config <absolute-path> [--connections-file <private-grants-file>]',
|
|
77
|
-
' dsh-iris mcp serve --connection-file <private-client-json> [--scopes <read,create,execute,stop>] [--capabilities <image-edit,image-generate,tts,transcribe|none>] [--tools <comma-separated-names|none>]',
|
|
95
|
+
' dsh-iris mcp serve --connection-file <private-client-json> [--scopes <read,create,execute,stop>] [--capabilities <image-edit,image-generate,tts,transcribe,video-generate|none>] [--tools <comma-separated-names|none>]',
|
|
78
96
|
' Official SDK stdio bridge to an already running Core service. Read-only by default; stdout contains MCP messages only.',
|
|
79
97
|
' dsh-iris mcp capabilities',
|
|
80
98
|
' dsh-iris mcp presets',
|
|
@@ -84,6 +102,8 @@ function usage() {
|
|
|
84
102
|
' dsh-iris api serve ... --connections-file <private-grants-file> --allow-mcp',
|
|
85
103
|
' dsh-iris web serve ... --connection-store <private-directory-outside-core> --allow-connection-management [--allow-mcp] [--allow-execution --provider-config <absolute-path>]',
|
|
86
104
|
' dsh-iris web serve ... --provider-config <private-config> --allow-provider-management [--panel <trusted-panel-manifest.json>]',
|
|
105
|
+
' dsh-iris api|web serve ... --network-config <private-config> --allow-network-management',
|
|
106
|
+
' dsh-iris api|web serve ... --scheduler-config <absolute-private-json> enables bounded media scheduling (HTTP concurrency <=4, rolling RPS and remote outstanding limits).',
|
|
87
107
|
' Explicit standard Streamable HTTP endpoint at /mcp; default remains disabled. Uses the existing Core service and user-selected connection tools.',
|
|
88
108
|
' dsh-iris operation list --data-root <execution-root> [--run-id <run-id>] [--limit <1..30>] [--cursor <operation-id>]',
|
|
89
109
|
' dsh-iris operation inspect <operation-id> --data-root <execution-root>',
|
|
@@ -103,6 +123,14 @@ function usage() {
|
|
|
103
123
|
' dsh-iris providers list --provider-config <absolute-path>',
|
|
104
124
|
' dsh-iris capabilities list --provider-config <absolute-path>',
|
|
105
125
|
' dsh-iris config <init|show|check> --provider-config <absolute-path>',
|
|
126
|
+
' dsh-iris network <init|show|check> --network-config <absolute-private-path>',
|
|
127
|
+
' dsh-iris network routing <init|copy> --network-config <new-private-path> [--input <json with source>]',
|
|
128
|
+
' dsh-iris network catalog create --network-config <v2-path> --provider-config <source-path> --output <new-private-path>',
|
|
129
|
+
' dsh-iris network bindings <list|set|remove> --network-config <v2-path> [--provider-config <wrapped-path> --input <json>]',
|
|
130
|
+
' Media/flow/tasks/web accept --network-config with wrapped Provider catalogs. Explicit policy failures never silently inherit or replay generation.',
|
|
131
|
+
' dsh-iris network <nodes|pools> list --network-config <absolute-private-path>',
|
|
132
|
+
' dsh-iris network <nodes|pools> <add|set|remove> --network-config <absolute-private-path> --input <json>',
|
|
133
|
+
' Owner-only resources use environment credential references and revision-checked mutations. Resources alone do not change routes; wrapped catalogs and explicit --network-config enable bound policies.',
|
|
106
134
|
' dsh-iris providers <add|set> --provider-config <absolute-path> --input <provider-json>',
|
|
107
135
|
' dsh-iris providers remove <provider-id> --provider-config <absolute-path>',
|
|
108
136
|
' dsh-iris models list --provider-config <absolute-path>',
|
|
@@ -150,7 +178,7 @@ function usage() {
|
|
|
150
178
|
' dsh-iris core transactions --data-root <absolute-path>',
|
|
151
179
|
' dsh-iris core restore <transaction-id> --data-root <absolute-path>',
|
|
152
180
|
'',
|
|
153
|
-
'Commands never start DSH. run image and the task observe/redeliver/cancel/retry verbs contact a provider only when explicitly requested;
|
|
181
|
+
'Commands never start DSH. run image and the task observe/redeliver/cancel/retry verbs contact a provider only when explicitly requested; provider configuration comes from a private file; explicit environment key references are resolved in this process. 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 Task records never persist prompts. Flow inputs do retain the prompt privately. flow continue may perform the first submission or observe/redeliver the original task, but never resubmits a task with an Attempt.'
|
|
154
182
|
].join('\n');
|
|
155
183
|
}
|
|
156
184
|
|
|
@@ -223,7 +251,8 @@ function registerRuntime(runtime) {
|
|
|
223
251
|
async function disposeActiveRuntimes() {
|
|
224
252
|
await Promise.allSettled([...activeRuntimes].map((runtime) => runtime.dispose().catch(() => {})));
|
|
225
253
|
activeRuntimes.clear();
|
|
226
|
-
await cliProviderRegistry?.dispose();
|
|
254
|
+
try { await cliProviderRegistry?.dispose(); }
|
|
255
|
+
finally { try { await cliNetworkManager?.dispose(); } finally { cliScheduler?.dispose(); } }
|
|
227
256
|
}
|
|
228
257
|
|
|
229
258
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
@@ -253,8 +282,8 @@ async function withRuntime(mode, dataRoot, callback, ports = {}) {
|
|
|
253
282
|
return withRawRuntime(mode, dataRoot, (runtime) => callback(createCommandService(runtime, ports)));
|
|
254
283
|
}
|
|
255
284
|
|
|
256
|
-
async function withApplication(dataRoot, callback,
|
|
257
|
-
const application = createApplicationRuntime({ dataRoot: required(dataRoot, 'data-root'),
|
|
285
|
+
async function withApplication(dataRoot, callback, ports = {}) {
|
|
286
|
+
const application = createApplicationRuntime({ dataRoot: required(dataRoot, 'data-root'), ...ports });
|
|
258
287
|
const unregister = registerRuntime(application);
|
|
259
288
|
try { return await callback(application); }
|
|
260
289
|
finally { try { await application.dispose(); } finally { unregister(); } }
|
|
@@ -392,16 +421,16 @@ async function main(args, providerRegistry) {
|
|
|
392
421
|
console.log(JSON.stringify(result, null, 2)); return 0;
|
|
393
422
|
}
|
|
394
423
|
if (args[1] !== 'init') throw new CliUsageError('不支持的 execution 子命令');
|
|
395
|
-
const initArgs = args.slice(2), generationFlags = initArgs.filter(value => value === '--image-generation'), audioFlags = initArgs.filter(value => value === '--audio');
|
|
396
|
-
if (generationFlags.length
|
|
397
|
-
const options = flags(initArgs.filter(value => !['--image-generation', '--audio'].includes(value)), ['data-root']);
|
|
424
|
+
const initArgs = args.slice(2), generationFlags = initArgs.filter(value => value === '--image-generation'), audioFlags = initArgs.filter(value => value === '--audio'), videoFlags = initArgs.filter(value => value === '--video');
|
|
425
|
+
if (generationFlags.length + audioFlags.length + videoFlags.length > 1) throw new CliUsageError('初始化开关重复或冲突;video包含音频和图片,audio包含图片');
|
|
426
|
+
const options = flags(initArgs.filter(value => !['--image-generation', '--audio', '--video'].includes(value)), ['data-root']);
|
|
398
427
|
const suppliedRoot = required(options['data-root'], 'data-root');
|
|
399
428
|
if (!path.isAbsolute(suppliedRoot)) throw new CliUsageError('data-root 必须是已初始化的空 Flow 根绝对路径');
|
|
400
429
|
let dataRoot;
|
|
401
430
|
try { dataRoot = fs.realpathSync(suppliedRoot); }
|
|
402
431
|
catch (_) { throw new CliUsageError('请先在新的专用数据根执行 flow init'); }
|
|
403
432
|
const result = await withApplication(dataRoot, application =>
|
|
404
|
-
application.execute('execution.init', audioFlags.length ? { audio: true } : generationFlags.length ? { imageGeneration: true } : {}, { signal: cliAbortController.signal }));
|
|
433
|
+
application.execute('execution.init', videoFlags.length ? { video: true } : audioFlags.length ? { audio: true } : generationFlags.length ? { imageGeneration: true } : {}, { signal: cliAbortController.signal }));
|
|
405
434
|
console.log(JSON.stringify(result, null, 2)); return 0;
|
|
406
435
|
}
|
|
407
436
|
if (!['list', 'inspect'].includes(args[1])) throw new CliUsageError('operation 只支持只读 list/inspect');
|
|
@@ -471,12 +500,12 @@ async function main(args, providerRegistry) {
|
|
|
471
500
|
return 0;
|
|
472
501
|
}
|
|
473
502
|
if (args[1] !== 'serve') throw new CliUsageError(`不支持的 ${args[0]} 子命令`);
|
|
474
|
-
const rest = args.slice(2), localWriteFlags = rest.filter(value => value === '--allow-local-writes'), executionFlags = rest.filter(value => value === '--allow-execution'), selectionFlags = rest.filter(value => value === '--allow-selection'), mcpFlags = rest.filter(value => value === '--allow-mcp'), managementFlags = rest.filter(value => value === '--allow-connection-management'), providerFlags = rest.filter(value => value === '--allow-provider-management');
|
|
475
|
-
if (localWriteFlags.length > 1 || executionFlags.length > 1 || selectionFlags.length > 1 || mcpFlags.length > 1 || managementFlags.length > 1 || providerFlags.length > 1) throw new CliUsageError('写入/执行/选择/MCP/管理参数重复');
|
|
503
|
+
const rest = args.slice(2), localWriteFlags = rest.filter(value => value === '--allow-local-writes'), executionFlags = rest.filter(value => value === '--allow-execution'), selectionFlags = rest.filter(value => value === '--allow-selection'), mcpFlags = rest.filter(value => value === '--allow-mcp'), managementFlags = rest.filter(value => value === '--allow-connection-management'), providerFlags = rest.filter(value => value === '--allow-provider-management'), networkFlags = rest.filter(value => value === '--allow-network-management');
|
|
504
|
+
if (localWriteFlags.length > 1 || executionFlags.length > 1 || selectionFlags.length > 1 || mcpFlags.length > 1 || managementFlags.length > 1 || providerFlags.length > 1 || networkFlags.length > 1) throw new CliUsageError('写入/执行/选择/MCP/管理参数重复');
|
|
476
505
|
const allowExecution = executionFlags.length === 1, allowLocalWrites = localWriteFlags.length === 1 || allowExecution;
|
|
477
506
|
const allowSelection = selectionFlags.length === 1;
|
|
478
507
|
const allowConnectionManagement = managementFlags.length === 1;
|
|
479
|
-
const options = flags(rest.filter(value => !['--allow-local-writes', '--allow-execution', '--allow-selection', '--allow-mcp', '--allow-connection-management', '--allow-provider-management'].includes(value)), ['data-root', 'token-file', 'token-dir', 'auth-store', 'provider-config', 'port', 'connections-file', 'connection-store', 'panel', 'plugin-config']);
|
|
508
|
+
const options = flags(rest.filter(value => !['--allow-local-writes', '--allow-execution', '--allow-selection', '--allow-mcp', '--allow-connection-management', '--allow-provider-management', '--allow-network-management'].includes(value)), ['data-root', 'token-file', 'token-dir', 'auth-store', 'provider-config', 'port', 'connections-file', 'connection-store', 'panel', 'plugin-config']);
|
|
480
509
|
const suppliedRoot = required(options['data-root'], 'data-root');
|
|
481
510
|
const passwordMode = serveWorkbench && options['token-file'] === undefined && options['token-dir'] === undefined;
|
|
482
511
|
if (!passwordMode && Boolean(options['token-file']) === Boolean(options['token-dir'])) throw new CliUsageError('请选择且只选择 --token-file 或 --token-dir');
|
|
@@ -492,11 +521,13 @@ async function main(args, providerRegistry) {
|
|
|
492
521
|
if (options['token-dir']) checkLocalApiTokenDirectory(options['token-dir'], dataRoot);
|
|
493
522
|
return withCliLaunch(options['plugin-config'], { serveWorkbench, panelManifestPath: options.panel }, async (launch, ownApi) => {
|
|
494
523
|
const api = createLocalReadApi({ dataRoot, providerConfigPath: options['provider-config'], allowLocalWrites, serveWorkbench,
|
|
524
|
+
...(cliNetworkConfigPath || cliScheduler ? { networkConfigPath: cliNetworkConfigPath,
|
|
525
|
+
networkManager: cliNetworkManager ||= createProviderNetworkManager({ networkConfigPath: cliNetworkConfigPath, scheduler: cliScheduler }), scheduler: cliScheduler } : {}),
|
|
495
526
|
...(passwordMode ? { workbenchAuth: { directory: authDirectory } } : {}),
|
|
496
527
|
allowExecution, allowSelection, allowMcp: mcpFlags.length === 1, connectionsFile: options['connections-file'], allowConnectionManagement, connectionStore: options['connection-store'], providerRegistry,
|
|
497
528
|
...(launch ? { pluginRegistry: launch.registry, providerRegistry: launch.registry.providers, allowedOperations: launch.allowedOperations,
|
|
498
529
|
panelContributions: launch.panelContributions } : {}),
|
|
499
|
-
allowProviderManagement: providerFlags.length === 1, panelManifestPath: options.panel });
|
|
530
|
+
allowProviderManagement: providerFlags.length === 1, allowNetworkManagement: networkFlags.length === 1, panelManifestPath: options.panel });
|
|
500
531
|
ownApi(api);
|
|
501
532
|
const started = await api.start({ port: options.port === undefined ? 0 : Number(options.port) });
|
|
502
533
|
if (!passwordMode) {
|
|
@@ -509,6 +540,7 @@ async function main(args, providerRegistry) {
|
|
|
509
540
|
...(serveWorkbench ? { loginUrl: started.loginUrl, loginExpiresAt: started.loginExpiresAt } : {}),
|
|
510
541
|
...(allowConnectionManagement ? { connectionManagement: true } : {}),
|
|
511
542
|
...(providerFlags.length ? { providerManagement: true } : {}),
|
|
543
|
+
...(networkFlags.length ? { networkManagement: true } : {}),
|
|
512
544
|
...(started.mcpEndpoint ? { mcpEndpoint: started.mcpEndpoint } : {}),
|
|
513
545
|
message: passwordMode ? (started.authentication.configured ? '工作台已启动,打开 origin 用访问口令登录;记住设备后可跨正常重启继续使用。'
|
|
514
546
|
: '首次使用请打开 loginUrl 设置访问口令;之后打开普通地址即可登录,无需 Token 文件。')
|
|
@@ -539,20 +571,12 @@ async function main(args, providerRegistry) {
|
|
|
539
571
|
if (verb === 'select' && Object.keys(input).some(key => key !== 'artifact_id')) throw new CliUsageError('flow select 只接受 artifact_id');
|
|
540
572
|
if (needsId) input.run_id = args[2];
|
|
541
573
|
if (verb === 'export') input.output_path = required(options.output, 'output');
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
return imageCandidatesFromCatalog(catalog, modelRef, providerRegistry).map(route => ({ adapter: trackedProviderAdapter(route.provider, options),
|
|
545
|
-
model: route.modelRef, supportsImageEdit: supportsImageEdit(route.provider.mediaProtocol, route.model, providerRegistry),
|
|
546
|
-
selectionReason: route.selectionReason, providerBinding: providerTaskBinding(route.provider, providerRegistry) }));
|
|
547
|
-
} : undefined;
|
|
548
|
-
const resolveTaskAdapter = options['provider-config'] ? task => {
|
|
549
|
-
const catalog = loadProviderCatalog(options['provider-config']);
|
|
550
|
-
return trackedProviderAdapter(providerForTaskFromCatalog(catalog, task, providerRegistry), options, task);
|
|
551
|
-
} : undefined;
|
|
574
|
+
const ports = options['provider-config'] ? configuredImagePorts(options['provider-config'], providerRegistry,
|
|
575
|
+
{ resolveProviderNetwork: cliResolveNetwork }) : {};
|
|
552
576
|
let result;
|
|
553
577
|
try {
|
|
554
578
|
result = await withApplication(options['data-root'], application =>
|
|
555
|
-
application.execute('flow.' + verb, input, { signal: cliAbortController.signal }),
|
|
579
|
+
application.execute('flow.' + verb, input, { signal: cliAbortController.signal }), ports);
|
|
556
580
|
} catch (error) {
|
|
557
581
|
if (error instanceof GenerationInputError) throw new CliUsageError(error.message);
|
|
558
582
|
throw error;
|
|
@@ -602,6 +626,50 @@ async function main(args, providerRegistry) {
|
|
|
602
626
|
return 0;
|
|
603
627
|
}
|
|
604
628
|
|
|
629
|
+
if (args[0] === 'network') {
|
|
630
|
+
const group = args[1];
|
|
631
|
+
const collection = ['nodes', 'pools', 'bindings', 'routing', 'catalog'].includes(group);
|
|
632
|
+
const verb = collection ? args[2] : group;
|
|
633
|
+
const verbs = group === 'routing' ? ['init', 'copy'] : group === 'catalog' ? ['create'] : group === 'bindings' ? ['list', 'set', 'remove'] : collection ? ['list', 'add', 'set', 'remove'] : ['init', 'show', 'check'];
|
|
634
|
+
if (!verbs.includes(verb)) {
|
|
635
|
+
throw new CliUsageError('未知网络资源命令');
|
|
636
|
+
}
|
|
637
|
+
const mutation = collection && verb !== 'list' && !(group === 'routing' && verb === 'init') && group !== 'catalog';
|
|
638
|
+
let options;
|
|
639
|
+
try { options = flags(args.slice(collection ? 3 : 2), ['network-config', ...(mutation ? ['input'] : []),
|
|
640
|
+
...(['catalog', 'bindings'].includes(group) ? ['provider-config'] : []), ...(group === 'catalog' ? ['output'] : [])]); }
|
|
641
|
+
catch (error) {
|
|
642
|
+
if (!(error instanceof CliUsageError)) throw error;
|
|
643
|
+
throw new CliUsageError('网络参数无效:请使用 --network-config;写入内容通过 --input、--input-file 或 --input - 提供。代理地址请在节点 JSON 中分别填写 host/port,认证使用环境变量引用');
|
|
644
|
+
}
|
|
645
|
+
const file = required(options['network-config'], 'network-config');
|
|
646
|
+
if (group === 'catalog') {
|
|
647
|
+
const network = readNetworkConfig(file).config;
|
|
648
|
+
if (network.schemaVersion !== 2) throw networkPolicyError('IRIS_NETWORK_CONFIG_VERSION', '网络目录需使用显式新建或另存的 v2 网络文件');
|
|
649
|
+
const source = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
650
|
+
const requirement = { schemaVersion: 1, configId: network.configId, catalogId: crypto.randomUUID() };
|
|
651
|
+
const output = required(options.output, 'output');
|
|
652
|
+
if (!path.isAbsolute(output)) throw new CliUsageError('output 必须是新私有文件的绝对路径');
|
|
653
|
+
atomicCreatePrivate(output, JSON.stringify(encodeProviderConfig(wrapProviderCatalog(source, requirement)), null, 2) + '\n');
|
|
654
|
+
console.log(JSON.stringify({ created: true, networkRequirement: requirement }));
|
|
655
|
+
return 0;
|
|
656
|
+
}
|
|
657
|
+
let input = mutation ? jsonInput(options.input) : {};
|
|
658
|
+
if (group === 'bindings' && verb === 'remove' && !options['provider-config']) {
|
|
659
|
+
if (Object.keys(input).some(key => !['expectedRevision', 'catalogId', 'providerId', 'confirm'].includes(key))) throw new CliUsageError('解除遗留绑定需要明确目录身份、供应商、修订与确认');
|
|
660
|
+
} else if (group === 'bindings' && verb !== 'list') {
|
|
661
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
662
|
+
const requirement = providerNetworkRequirement(catalog), network = readNetworkConfig(file).config;
|
|
663
|
+
if (!requirement || requirement.configId !== network.configId) throw networkPolicyError('IRIS_NETWORK_CONFIG_MISMATCH', '绑定需要匹配的新版供应商目录与网络文件');
|
|
664
|
+
const allowed = verb === 'set' ? ['expectedRevision', 'providerId', 'poolId'] : ['expectedRevision', 'providerId', 'confirm'];
|
|
665
|
+
if (Object.keys(input).some(key => !allowed.includes(key)) || catalog.providers.filter(provider => provider.id === input.providerId).length !== 1) throw new CliUsageError('绑定输入需要该目录中明确且唯一的供应商身份');
|
|
666
|
+
input = { ...input, catalogId: requirement.catalogId };
|
|
667
|
+
}
|
|
668
|
+
const result = executeNetworkConfigCommand(file, collection ? group + '.' + verb : verb, input, { signal: cliAbortController.signal });
|
|
669
|
+
console.log(JSON.stringify(result, null, 2));
|
|
670
|
+
return result.valid === false ? 1 : 0;
|
|
671
|
+
}
|
|
672
|
+
|
|
605
673
|
if (['config', 'providers', 'models', 'assignments'].includes(args[0])) {
|
|
606
674
|
const [group, verb] = args;
|
|
607
675
|
const verbs = { config: ['init', 'show', 'check'], providers: ['add', 'set', 'remove'], models: ['list', 'add', 'remove', 'caps', 'protocol', 'music-protocol', 'vision-input', 'discover', 'test', 'workflow-check'], assignments: ['list', 'set', 'clear'] };
|
|
@@ -621,7 +689,7 @@ async function main(args, providerRegistry) {
|
|
|
621
689
|
}
|
|
622
690
|
}
|
|
623
691
|
const result = await executeConfigCommand(required(options['provider-config'], 'provider-config'), group === 'config' ? verb : group + '.' + verb, input, {
|
|
624
|
-
signal: cliAbortController.signal, registry: providerRegistry,
|
|
692
|
+
signal: cliAbortController.signal, registry: providerRegistry, resolveProviderNetwork: cliResolveNetwork,
|
|
625
693
|
probe: async ({ provider, modelId, capability, signal }) => {
|
|
626
694
|
if (capability === 'music-gen') return { skipped: true, ok: false, reason: 'Music 不发起小样本探测;请使用 run music 提交创作要求' };
|
|
627
695
|
if (capability === 'vision') return probeVisionModel(provider, modelId, { bytes: Buffer.from(RED_TEST_IMAGE.split(',')[1], 'base64'), mediaType: 'image/png' }, { signal });
|
|
@@ -635,7 +703,8 @@ async function main(args, providerRegistry) {
|
|
|
635
703
|
return withRawRuntime('writer', options['data-root'], async runtime => {
|
|
636
704
|
const mediaCapability = capability === 'image-gen' ? 'image' : capability === 'video-gen' ? 'video' : capability;
|
|
637
705
|
if (mediaCapability === 'image') provider = providerForImageModel(provider, modelId, providerRegistry);
|
|
638
|
-
const adapter = createConfiguredProviderAdapter(provider, { allowRateLimited: true, modelId, registry: providerRegistry
|
|
706
|
+
const adapter = createConfiguredProviderAdapter(provider, { allowRateLimited: true, modelId, registry: providerRegistry,
|
|
707
|
+
network: cliResolveNetwork({ provider, providerId: provider.id }) });
|
|
639
708
|
let providerInput = mediaCapability === 'image'
|
|
640
709
|
? { prompt: 'red circle', n: 1, ...(['openai-chat-images', 'openai-responses-images', 'comfyui'].includes(adapter.protocol) ? {} : {
|
|
641
710
|
size: adapter.protocol === 'openai-images' ? '256x256' : '512*512'
|
|
@@ -648,8 +717,14 @@ async function main(args, providerRegistry) {
|
|
|
648
717
|
providerInput = normalized.providerInput;
|
|
649
718
|
if (mediaCapability === 'video') candidates = videoTaskCandidates(candidates, normalized);
|
|
650
719
|
else if (normalized.audioPath) {
|
|
651
|
-
|
|
652
|
-
|
|
720
|
+
if (adapter.networkRequirement) candidates = candidates.map(candidate => ({ ...candidate, prepareInput: async ({ signal, taskId, attemptId, networkBudget, recordNetworkRoute }) => {
|
|
721
|
+
const prepared = await prepareProviderInput(adapter, { model: modelId, filePath: normalized.audioPath, signal }, { taskId, attemptId, networkBudget, recordNetworkRoute });
|
|
722
|
+
return { audioUrl: prepared.url };
|
|
723
|
+
} }));
|
|
724
|
+
else {
|
|
725
|
+
const prepared = await prepareProviderInput(adapter, { model: modelId, filePath: normalized.audioPath, signal });
|
|
726
|
+
providerInput = generationInput('transcribe', { audio_url: prepared.url }).providerInput;
|
|
727
|
+
}
|
|
653
728
|
}
|
|
654
729
|
}
|
|
655
730
|
const submitted = await createProviderTaskRunner(runtime).submit({ capability: mediaCapability,
|
|
@@ -751,7 +826,7 @@ async function main(args, providerRegistry) {
|
|
|
751
826
|
let result;
|
|
752
827
|
try {
|
|
753
828
|
result = await withApplication(options['data-root'], application =>
|
|
754
|
-
application.generateImage(input, { signal: cliAbortController.signal }), resolveImageCandidates);
|
|
829
|
+
application.generateImage(input, { signal: cliAbortController.signal }), { resolveImageCandidates });
|
|
755
830
|
} catch (error) {
|
|
756
831
|
if (error instanceof GenerationInputError) throw new CliUsageError(error.message);
|
|
757
832
|
throw error;
|
|
@@ -820,29 +895,33 @@ async function main(args, providerRegistry) {
|
|
|
820
895
|
}
|
|
821
896
|
|
|
822
897
|
// 转写输入冻结(E3 Profile):上传型异步——audio_url(公网/oss://)或
|
|
823
|
-
// audio_path
|
|
898
|
+
// audio_path(本地文件,在写者与原 Attempt 建立后按实际候选上传,签名 URL 不落 Core)。
|
|
824
899
|
if (args[0] === 'run' && args[1] === 'transcribe') {
|
|
825
900
|
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
826
901
|
const normalized = generationInput('transcribe', jsonInput(options.input), { allowAudioPath: true });
|
|
827
902
|
const catalog = await generationCatalog(options, 'transcribe', normalized.modelRef);
|
|
828
903
|
const routes = transcribeCandidatesFromCatalog(catalog, normalized.modelRef, providerRegistry);
|
|
829
|
-
|
|
830
|
-
if (normalized.audioPath) {
|
|
831
|
-
if (!fs.existsSync(normalized.audioPath)) throw new CliUsageError('音频文件不存在');
|
|
832
|
-
const prepared = await prepareProviderInput(trackedProviderAdapter(routes[0].provider, options), {
|
|
833
|
-
model: routes[0].model, filePath: normalized.audioPath
|
|
834
|
-
});
|
|
835
|
-
providerInput = generationInput('transcribe', { audio_url: prepared.url }).providerInput;
|
|
836
|
-
}
|
|
904
|
+
if (normalized.audioPath && !fs.existsSync(normalized.audioPath)) throw new CliUsageError('音频文件不存在');
|
|
837
905
|
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
838
|
-
const candidates = routes.map(
|
|
839
|
-
adapter
|
|
906
|
+
const candidates = routes.map(route => {
|
|
907
|
+
const adapter = trackedProviderAdapter(route.provider, options);
|
|
908
|
+
return { adapter, model: route.modelRef,
|
|
840
909
|
selectionReason: route.selectionReason,
|
|
841
|
-
providerBinding: providerTaskBinding(route.provider, providerRegistry)
|
|
842
|
-
|
|
910
|
+
providerBinding: providerTaskBinding(route.provider, providerRegistry),
|
|
911
|
+
...(normalized.audioPath ? { prepareInput: async ({ signal, taskId, attemptId, networkBudget, recordNetworkRoute, modelRef, providerId }) => {
|
|
912
|
+
const prepared = await prepareProviderInput(adapter, { model: route.model, filePath: normalized.audioPath, signal },
|
|
913
|
+
{ taskId, attemptId, networkBudget, recordNetworkRoute, modelRef, providerId });
|
|
914
|
+
return { audioUrl: prepared.url };
|
|
915
|
+
} } : {}) };
|
|
916
|
+
});
|
|
917
|
+
if (normalized.audioPath && candidates.every(candidate => typeof candidate.adapter.prepareInput !== 'function')) {
|
|
918
|
+
throw Object.assign(new Error('所选协议未实现临时音频上传;请使用支持上传的转写协议或重新提供 audio_url'), {
|
|
919
|
+
code: 'IRIS_PROVIDER_INPUT_PREPARATION_UNSUPPORTED'
|
|
920
|
+
});
|
|
921
|
+
}
|
|
843
922
|
const result = await createProviderTaskRunner(runtime).submit({
|
|
844
923
|
capability: 'transcribe', candidates,
|
|
845
|
-
providerInput
|
|
924
|
+
providerInput: normalized.providerInput
|
|
846
925
|
});
|
|
847
926
|
console.log(JSON.stringify(result, null, 2));
|
|
848
927
|
});
|
|
@@ -1016,13 +1095,26 @@ try {
|
|
|
1016
1095
|
entries.push(entry);
|
|
1017
1096
|
}
|
|
1018
1097
|
}
|
|
1098
|
+
if (args[0] !== 'network') for (let index = 0; index < args.length; index++) {
|
|
1099
|
+
if (args[index] !== '--network-config') continue;
|
|
1100
|
+
if (cliNetworkConfigPath !== undefined || !args[index + 1] || !path.isAbsolute(args[index + 1])) throw new CliUsageError('network-config 需要唯一、明确的绝对路径');
|
|
1101
|
+
cliNetworkConfigPath = args[index + 1]; args.splice(index, 2); index--;
|
|
1102
|
+
}
|
|
1103
|
+
let schedulerFile;
|
|
1104
|
+
for (let index = 0; index < args.length; index++) {
|
|
1105
|
+
if (args[index] !== '--scheduler-config') continue;
|
|
1106
|
+
if (!['api', 'web'].includes(args[0]) || args[1] !== 'serve') throw new CliUsageError('scheduler-config 首片仅用于 api/web serve;单次 CLI 和 DSH 独立路径保持原行为');
|
|
1107
|
+
if (schedulerFile !== undefined || !args[index + 1]) throw new CliUsageError('scheduler-config 需要唯一、明确的绝对路径');
|
|
1108
|
+
schedulerFile = args[index + 1]; args.splice(index, 2); index--;
|
|
1109
|
+
}
|
|
1110
|
+
if (schedulerFile !== undefined) cliScheduler = createMediaScheduler(readSchedulingPolicy(schedulerFile));
|
|
1019
1111
|
try {
|
|
1020
1112
|
if (entries.length && args.includes('--plugin-config')) throw new CliUsageError('--plugin-config 与 --provider-extension 不能混用;Provider 插件应通过同一插件装配实例加载');
|
|
1021
1113
|
if (args.length && !args.includes('--help') && !args.includes('-h')) cliProviderRegistry = await loadProviderRegistry(entries);
|
|
1022
1114
|
process.exitCode = await main(args, cliProviderRegistry);
|
|
1023
|
-
} finally { await cliProviderRegistry?.dispose(); }
|
|
1115
|
+
} finally { try { await cliProviderRegistry?.dispose(); } finally { try { await cliNetworkManager?.dispose(); } finally { cliScheduler?.dispose(); } } }
|
|
1024
1116
|
} catch (error) {
|
|
1025
|
-
const code = error && error.code || 'IRIS_CLI_FAILED';
|
|
1117
|
+
const code = error && error.code || (error?.providerCode === 'IRIS_PROVIDER_DIRECTORY_CREDENTIALS' ? error.providerCode : 'IRIS_CLI_FAILED');
|
|
1026
1118
|
const message = error && error.message || '命令执行失败';
|
|
1027
1119
|
if (error.diagnostic) console.error(JSON.stringify(error.diagnostic));
|
|
1028
1120
|
else console.error(code + ': ' + message);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Agent Quickstart · Core 与 MCP 接入指导
|
|
2
2
|
|
|
3
|
-
For **0.3.0-
|
|
3
|
+
For **0.3.0-beta.1 Core Web/MCP**, use the [Core quickstart](CORE_QUICKSTART.md). Stable npm **0.2.7 has CLI/DSH support, without this Web/MCP entry**; use [the stable Agent guide](AGENT_QUICKSTART.md) for it. Install the exact beta or explicitly choose the beta channel.
|
|
4
4
|
|
|
5
5
|
Give this instruction to an Agent helping with setup:
|
|
6
6
|
|
|
7
7
|
```text
|
|
8
8
|
Help me use Iris (@mokuyoaxis/dsh-iris). First read README and check the actual
|
|
9
9
|
installed package or source revision, Node version and available CLI commands.
|
|
10
|
-
Distinguish stable 0.2.7 from the 0.3.0-
|
|
10
|
+
Distinguish stable 0.2.7 from the 0.3.0-beta.1 Core workbench/MCP implementation.
|
|
11
11
|
Installing latest does not opt into alpha; use the exact prerelease or alpha tag.
|
|
12
12
|
Do not install a different package, upgrade the host or migrate a data root silently.
|
|
13
13
|
|
package/docs/AGENT_QUICKSTART.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 给 AI Agent 的简要 Prompt
|
|
2
2
|
|
|
3
|
-
先选择入口:0.3.0-
|
|
3
|
+
先选择入口:0.3.0-beta.1 的 Core 工作台/MCP 使用 [Agent Core 指导](AGENT_CORE_QUICKSTART.md)与[从零启动](CORE_QUICKSTART.md);下面的安装提示保留面向**稳定 0.2.7 的 DSH / CLI**,不是新 MCP 配置步骤。
|
|
4
4
|
|
|
5
5
|
把下面一段交给负责安装或使用 Iris 的 Agent。版本与发布状态以当前 [README](../README.md) 为准;Iris 和 DSH 的版本号独立。
|
|
6
6
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Iris 架构与边界
|
|
2
2
|
|
|
3
|
-
状态:本文说明 **Iris 0.3.0-
|
|
3
|
+
状态:本文说明 **Iris 0.3.0-beta.1** 的共享Core及Web、MCP与插件装配,并保留0.2.7入口图作历史对照。0.2.0建立实例化Core Runtime、Headless CLI及共享Task/Artifact事实;后续加入共享视觉、图片编辑、音乐和交付恢复。0.2.5恢复旧宿主支持,0.2.6增加连接引用、可选功能自动池和目录同步。准确版本及历史范围见 [兼容矩阵](DSH_COMPATIBILITY.md)。Node.js最低22.1.0;Core与插件契约仍为实验性。
|
|
4
4
|
|
|
5
5
|
## 当前形态
|
|
6
6
|
|
|
7
7
|
当前源码的 [Core 工作台](LOCAL_WEB.md)已连接真实持久执行、停止、作品选择和供应商管理;[通用 MCP](MCP.md)及[显式插件贡献](PLUGINS.md)复用同一服务与授权边界。图片生成/编辑、语音/转写和插件业务按用户所选范围启用。下方示意图表示 0.2.7 已发布入口,完整当前覆盖见 [README](../README.md#当前入口覆盖)。
|
|
8
8
|
|
|
9
|
-
0.3
|
|
9
|
+
0.3 beta另提供独立 [Panel/主题](PANEL_EXTENSIONS.md)和 [C3 供应商设置](PROVIDER_SETTINGS.md):页面可显式替换,配置管理复用应用层原子写入;默认媒体权限与 Core Task/Artifact 格式保持。它们不随已发布 0.2.7 提供,也不自动替换 DSH 内嵌 UI。
|
|
10
10
|
|
|
11
11
|
Iris仍以一个npm包交付。DSH插件负责原生Agent工具、附件、浏览器和内嵌UI;Headless CLI与独立Core服务(Web/MCP)在没有DSH/Cordis的进程中接收明确数据根和私有Provider配置。各入口共用Core Runtime、Provider Task Runner、Task/Artifact Store和本地Command,不能在同一根同时充当写者;MCP桥连接已有Core服务,不另建业务写者。
|
|
12
12
|
|
|
@@ -39,7 +39,7 @@ DSH Host / Cordis Headless CLI
|
|
|
39
39
|
|
|
40
40
|
`lib/index.js` 是 DSH 插件装配入口,`lib/dsh-host-adapter.js` 是原始 DSH/Cordis 对象进入 Iris 的服务端边界;`lib/dsh-core-adapter.js` 负责 DSH 的 Core 实例装配、Provider 连接解析和 Host DTO 投影。`bin/dsh-iris.js` 不启动 DSH,也不推断 profile。`lib/api.js` 与 `lib/client.js` 是 Host 投影,不是真相来源。
|
|
41
41
|
|
|
42
|
-
0.2.7 增加 `lib/application-runtime.js`,首批 CLI 图片命令和固定改图流程通过它装配 Core;[DSH 实例](DSH_RUNTIME.md)也已接入同一队列机制。写队列、操作取消和关闭归属于应用实例;每次写操作独立取得、释放 Core 租约,DSH 的 Core 观察器由插件实例持有并等待卸载。[固定流程](IMAGE_FLOW.md)使用专用根中的版本化 Run、稳定 Task/比较作品身份及引用保护,私有输入保存提示词,Task 仅保存可选流程绑定。[本地 API](LOCAL_API.md)已提供默认只读与主动启用的 [本地创建](LOCAL_CREATION.md),在 0.2.7 中,执行/取消和真实独立 Web 尚未提供,现已接入上文的0.3
|
|
42
|
+
0.2.7 增加 `lib/application-runtime.js`,首批 CLI 图片命令和固定改图流程通过它装配 Core;[DSH 实例](DSH_RUNTIME.md)也已接入同一队列机制。写队列、操作取消和关闭归属于应用实例;每次写操作独立取得、释放 Core 租约,DSH 的 Core 观察器由插件实例持有并等待卸载。[固定流程](IMAGE_FLOW.md)使用专用根中的版本化 Run、稳定 Task/比较作品身份及引用保护,私有输入保存提示词,Task 仅保存可选流程绑定。[本地 API](LOCAL_API.md)已提供默认只读与主动启用的 [本地创建](LOCAL_CREATION.md),在 0.2.7 中,执行/取消和真实独立 Web 尚未提供,现已接入上文的0.3 beta;通用 Recipe 仍待实现。详见 [应用运行时契约](CORE_RUNTIME_CONTRACT.md#应用运行时装配)。上图表示已发布入口的共同核心。
|
|
43
43
|
|
|
44
44
|
0.2.1的 S2V 使用 Core Task/Attempt,HTML 使用 `media.html` 与显式 Browser Port;视觉和提示词预览通过共享 Model Ports 执行。纯视觉不创建 Task/Artifact,摘要主动转写仍使用 Core 任务。聊天改图属于媒体生成链,按来源 Artifact ID 创建新 Task 和关联产物。
|
|
45
45
|
|
|
@@ -67,9 +67,9 @@ DSH Host / Cordis Headless CLI
|
|
|
67
67
|
|
|
68
68
|
完整规则见 [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 兼容承诺。
|
|
69
69
|
|
|
70
|
-
面向 0.3 的0.3
|
|
70
|
+
面向 0.3 的0.3 beta增加[Provider 扩展首版](PROVIDER_EXTENSIONS.md):实例拥有协议描述、工厂、活动调用和异步释放;CLI/Core 服务显式传入注册实例,六类内置协议(含 ComfyUI)与外部协议共用候选/健康/恢复。实验性包入口仅开放媒体协议所需接口,尚未提供 DSH 外部模块加载设置、通用 Recipe 或完整 SDK。ComfyUI Task binding 另纳入模板摘要,见 [文生图与工作流入口](IMAGE_GENERATION.md);原五类协议的 binding 与既有记录保持。
|
|
71
71
|
|
|
72
|
-
同属0.3
|
|
72
|
+
同属0.3 beta的 [Core 插件贡献 v0](PLUGINS.md)统一五类贡献的身份、兼容与生命周期。新插件 Provider 的 Task/Attempt 在提交前记录准确依赖,旧绑定保持。默认 Panel 可显式组合页面/设置/预览,共享主题、认证客户端和会话;公开装配保持媒体只读,设置沿用 Core Provider 管理,整套替换仍可用。另由 `allowedOperations` 明确选择持久业务,复用同一单根 runner/FIFO/租约及有版本 `plugin.execute` 记录;Core绑定输入/来源、预留输出身份并管理实际作品存储。[多图对照板](CONTACT_SHEET.md)只引用公开 SDK 和现有 Sharp,已贯穿 Web、用户选择、通用 MCP 与原记录恢复。无稳定 SDK、市场或旧数据迁移。
|
|
73
73
|
|
|
74
74
|
Core能力目录派生MCP/DSH、Web选项/会话和Skill,投影五类贡献的真实边界。注册/发现不授权,旧工具名单不扩张。MCP用通用目录/执行工具投影连接明确保存的准确业务依赖,Core在接受和后续命令处检查有效权限;断开代理不停止Core。重启只记录interrupted,插件缺失或版本变化不重放,原作品/记录仍可读取。实例生命周期由装配者持有,先关服务再关注册器。
|
|
75
75
|
|
|
@@ -21,12 +21,16 @@ artifact-store/v0/
|
|
|
21
21
|
|
|
22
22
|
Index 更新失败不会撤销已经提交的 Artifact,避免调用方因本地缓存错误重新生成或重复付费。显式 `artifact rebuild` 会从提交记录和旁车 Manifest 重建 Index。
|
|
23
23
|
|
|
24
|
+
beta.1修复在提交 record 前写入私有 `index.dirty` 标记,成功发布完整 Index 后移除。索引发布失败或进程在两者之间中断时,列表明确返回 `IRIS_ARTIFACT_INDEX_INVALID`,Doctor 报告索引异常;按 ID 读取原作品仍可用。下一次成功刷新或显式重建可消除标记,不需要迁移既有记录,也不自动删除媒体。
|
|
25
|
+
|
|
24
26
|
## 内容完整性
|
|
25
27
|
|
|
26
28
|
Manifest 使用小写十六进制 SHA-256,并同时记录字节数。`inspect`、`export`、`list` 和重建都会核验对象;即使文件大小不变,内容被替换也返回 `IRIS_ARTIFACT_DIGEST_MISMATCH`。
|
|
27
29
|
|
|
28
30
|
哈希通过固定大小分块读取,不把完整视频再次载入内存。Manifest、错误和 CLI 结果不保存输入路径、旧作品文件名、DSH URL 或供应商下载 URL。
|
|
29
31
|
|
|
32
|
+
beta.1中,新作品只核验自身内容和实际使用的关系目标;随后刷新 Index 时检查所有历史提交记录、Manifest 和普通文件大小,不重复重哈希历史媒体。历史结构/大小异常会阻止发布新索引,不静默跳过坏记录。已有摘要的正常提交仍需遍历元数据并原子重写 Index,尚非增量数据库;没有摘要的早期 v0 记录仍须计算真实哈希,可通过显式重建升级。媒体读取、CLI `inspect/list/export` 和显式重建继续验证完整内容哈希,目录刷新本身不证明历史媒体本次已重新验过内容。
|
|
33
|
+
|
|
30
34
|
## 关系边
|
|
31
35
|
|
|
32
36
|
关系方向固定为“当前 Artifact 指向既有 Artifact”。v0 支持:
|
package/docs/AUDIO_FLOWS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Core 语音合成与音频转写(0.3
|
|
1
|
+
# Core 语音合成与音频转写(0.3 beta)
|
|
2
2
|
|
|
3
3
|
0.3.0-alpha.1的 Web 与通用 MCP 接入 `speech/v0`、`transcribe/v0`,共用现有 Provider、Task/Attempt、持久 Run 和单根 Operation runner。**稳定 npm 0.2.7 不含这些新增入口。** 原独立 CLI/DSH 的语音和转写继续使用各自原入口;这里描述新的 Core 流程。
|
|
4
4
|
|
|
@@ -14,12 +14,26 @@ node bin/dsh-iris.js web serve --data-root /absolute/new-audio-core \
|
|
|
14
14
|
--token-file /absolute/new-audio-owner-token --allow-execution
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
`--audio` 建立 `creation-requests/v3`,同时支持原图片编辑/生成。不要与 `--image-generation` 混用,也不要在已有 v0/v1/v2
|
|
17
|
+
`--audio` 建立 `creation-requests/v3`,同时支持原图片编辑/生成。不要与 `--image-generation` 混用,也不要在已有 v0/v1/v2 根上执行迁移;原根继续按原能力运行。beta.1视频流程另提供 `--video` 新 v4 根,保留这些音频流程;已有 v3 不迁移,三个开关不混用。省略这些开关的 `execution init` 仍建立 v1 图片编辑根,`--image-generation` 仍建立 v2。
|
|
18
18
|
|
|
19
19
|
打开输出的 `loginUrl`;刷新保留当前标签页登录,过期后可用 `web login` 获取新链接,见 [工作台登录](LOCAL_WEB.md#启动与连接)。播放和下载已有结果无需 Provider;新建 Run 需要显式配置,执行另需 `--allow-execution`。音频不需要 `--allow-selection`。
|
|
20
20
|
|
|
21
21
|
配置沿用[模型服务](PROVIDER_SETTINGS.md):为模型明确确认 TTS/转写用途和功能池,再从实际候选中选择。候选不是健康证明,打开页面不做实测。内置音频协议沿用现有 DashScope 实现;配置了 OpenAI 图片接口不等于接入 OpenAI TTS/ASR。这里没有新增语音协议、模型自动替换或音色发现接口。
|
|
22
22
|
|
|
23
|
+
## CLI 保存与继续(beta.1修复)
|
|
24
|
+
|
|
25
|
+
独立 `flow create/continue` 现装配与服务相同的图片、语音、转写和视频候选。先建立上述 v3/v4 根,并在功能池中允许所选模型:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
node bin/dsh-iris.js flow create --data-root /absolute/new-audio-core \
|
|
29
|
+
--provider-config /absolute/existing-providers.json \
|
|
30
|
+
--input '{"recipe":"speech/v0","model_ref":"provider::tts-model","text":"鸢尾花","voice":"Cherry"}'
|
|
31
|
+
node bin/dsh-iris.js flow continue run_0123456789abcdef01234567 \
|
|
32
|
+
--data-root /absolute/new-audio-core --provider-config /absolute/existing-providers.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Run ID 使用创建命令的实际返回值。转写使用 `recipe:"transcribe/v0"`、明确 `model_ref` 及 `source_artifact_id`,来源应先由当前根的 Web/MCP 音频导入保存;这里不新增 CLI 音频导入命令。创建不合成或上传;首次继续才执行,后续继续只观察或交付原 Task,受理未知不重提。已完成且本地结果完整时,无需 Provider 配置再次读取结果。
|
|
36
|
+
|
|
23
37
|
## 页面使用
|
|
24
38
|
|
|
25
39
|
- **语音合成**:选择实际语音模型,填写朗读文字,必要时展开音色并填该模型支持的标识;留空使用现有协议默认值。点击合成后,保存输入、明确执行一次、保存原音频,可试听并下载。
|
|
@@ -28,9 +42,11 @@ node bin/dsh-iris.js web serve --data-root /absolute/new-audio-core \
|
|
|
28
42
|
|
|
29
43
|
生成音频/转写是单一结果,保存后即可下载,无需图片版本选择。下载保留原字节;实际落盘以浏览器下载记录为准。离开创作页暂停播放,退出或切换流程释放相应媒体。当前不提供流式合成、音色目录、录音、长音频切片、字幕时间轴或本地 TTS。
|
|
30
44
|
|
|
45
|
+
beta.1交付新语音/音乐结果时,会读取有限文件头确认WAV或MP3容器,再保存真实MIME与后缀;不把供应商声明的MP3套在WAV字节上。不支持的音频响应保留已受理事实并报交付失败,不重新合成。此为容器识别,不是完整解码或格式迁移;已入库的旧作品不自动改写。
|
|
46
|
+
|
|
31
47
|
## 有限 HTTP 契约
|
|
32
48
|
|
|
33
|
-
以下均以 `/api/v0` 开头,保留 Bearer、Host/Origin、无 CORS 和有限权限。新路由只在 v3
|
|
49
|
+
以下均以 `/api/v0` 开头,保留 Bearer、Host/Origin、无 CORS 和有限权限。新路由只在 v3 根(beta.1也支持 v4)开放;创建使用原 UUIDv4 `Idempotency-Key`,首次 201、同 Key 同输入 200、不同输入 409。
|
|
34
50
|
|
|
35
51
|
| 路由 | 输入与结果 |
|
|
36
52
|
|---|---|
|
|
@@ -68,4 +84,4 @@ DSH 可选的原生共享 Flow 桥仍限图片,未接入 Core 音频附件投
|
|
|
68
84
|
|
|
69
85
|
原结果保存失败时,通过原 Run 明确接回;已有私有交付缓存时无需 Provider。浏览器恢复表只存有界身份,不存朗读文字、音色、音频或转写正文;查到创建成功不自动首次执行。查询类型另含 audio-import、speech-create、transcription-create,见 [刷新恢复](WEB_RECOVERY.md)。
|
|
70
86
|
|
|
71
|
-
定向测试为 `tests/audio-flow.mjs`、`tests/audio-api.mjs`、`tests/audio-recovery.mjs`、`tests/audio-upload.mjs`。真实 Chromium 可用[工作台验证脚本](LOCAL_WEB.md#验证与下一阶段)的独立 `--audio` 场景,覆盖原字节播放/下载、刷新、丢失创建回包、只读及窄屏/主题。SDK验证包含HTTP 2025/2026与真实stdio进程的2026
|
|
87
|
+
定向测试为 `tests/audio-flow.mjs`、`tests/audio-api.mjs`、`tests/audio-recovery.mjs`、`tests/audio-upload.mjs`。真实 Chromium 可用[工作台验证脚本](LOCAL_WEB.md#验证与下一阶段)的独立 `--audio` 场景,覆盖原字节播放/下载、刷新、丢失创建回包、只读及窄屏/主题。SDK验证包含HTTP 2025/2026与真实stdio进程的2026路径。受控 Provider/上传回归与真实账号验收分别记录;**不能把回放、目录存在或单一账号成功推断为所有模型、编码或 harness 已通过验收**。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 0.3.0-beta.1 范围与兼容约定
|
|
2
|
+
|
|
3
|
+
本页定义 **0.3.0-beta.1** 的发行范围、最小插件契约与主要用户流程。使用准确版本或 npm `beta` 通道,稳定 `latest` 保留0.2.7;历史alpha.1没有新增网络、视频、调度与Provider工厂上下文v1。安装与升级入口见[发布说明](releases/0.3.0-beta.1.md)。
|
|
4
|
+
|
|
5
|
+
## 纳入的主要流程
|
|
6
|
+
|
|
7
|
+
| 用户入口 | 候选约定 | 继续保留的边界 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 启动/登录 | Core明确启动,首次设置访问口令、记住设备、退出/刷新恢复 | 不修改其他harness配置;Token模式继续作为显式选项 |
|
|
10
|
+
| 模型服务 | 添加/编辑/停用/删除供应商,显式凭据来源、完整目录确认、按功能选择模型池 | 目录和近期成功不是永久健康承诺;无跨请求模型轮转;不默认读取其他宿主或.env |
|
|
11
|
+
| 网络出口 | 独立HTTP/HTTPS节点、固定/有序池、供应商绑定、owner页面/CLI/API/Doctor | 无绑定继承实际进程环境;显式失效不悄悄直连;SOCKS、系统TUN控制、地域自动分流和Clash自动配置不在范围 |
|
|
12
|
+
| 创作/作品 | 图片生成/已支持的编辑协议、ComfyUI声明参数,语音/转写、固定文生视频,原件预览/下载 | 协议及账号能力以实际目录/实现和实测为准;没有新增Core音乐/视觉持久流程或远端视频编辑 |
|
|
13
|
+
| 执行/恢复 | 单物理根单写者,持久Run/Task/Attempt/Operation,显式有界调度、原UUID/原任务接回、本地停止 | 调度缺省串行;并发与HTTP速率/远端受理数分别限制;unknown与交付失败不自动新生成;停止不证明远端停费 |
|
|
14
|
+
| MCP连接 | 通用stdio和Streamable HTTP,显式客户端预设,用户选择功能/工具/scopes,导出/检查/握手/撤销/缩权 | 配置检查、协议握手与真实Agent调用分别验证;不扫描harness、不默认授权或聚合第三方MCP服务 |
|
|
15
|
+
| 插件/Panel | Provider、持久业务、页面/设置挂载、作品渲染,共享主题/API/登录;保留整套Panel替换 | 可信代码,无市场/自动安装/热加载/任意插件设置仓库;页面挂载不自动授权业务 |
|
|
16
|
+
|
|
17
|
+
入口说明:[从零启动](CORE_QUICKSTART.md)、[模型服务](PROVIDER_SETTINGS.md)、[网络工作台](NETWORK_WORKBENCH.md)、[调度](MEDIA_SCHEDULING.md)、[MCP](MCP.md)、[插件开发](PLUGIN_DEVELOPMENT.md)。
|
|
18
|
+
|
|
19
|
+
## 最小插件兼容面
|
|
20
|
+
|
|
21
|
+
本轮冻结的是下列窄约定,内部模块与完整通用SDK不纳入冻结:
|
|
22
|
+
|
|
23
|
+
- Plugin/Adapter契约v0:稳定插件/贡献ID、各自独立版本、Core兼容范围、明确启用与授权、准确持久依赖及缺失/错版错误。
|
|
24
|
+
- Provider工厂上下文v1:Core提供当次解析的连接与借用fetch,管理完整方法的正文/多跳及关闭;插件不拥有Core队列、任务存储或网络资源管理。使用功能标记确认支持,详见[Provider最小契约](PROVIDER_PLUGIN_CONTRACT.md)。
|
|
25
|
+
- 持久业务通过公开有限Core命令和原Operation执行;页面、设置挂载与预览器使用公开浏览器端上下文。Node插件和Shadow DOM都不是安全沙箱。
|
|
26
|
+
- 变更上述可观察输入/结果、权限、生命周期或准确依赖语义时,先审查兼容性并更新相应契约/贡献版本,不把实现升级伪装成原版本恢复。
|
|
27
|
+
|
|
28
|
+
两类[独立模板](PLUGIN_DEVELOPMENT.md)分别验证业务/页面/PNG预览与真实异步Provider协议。旧窄Provider扩展保留原用法。内部文件不因随包存在而成为公开导出;不要绕过package exports依赖私有路径。
|
|
29
|
+
|
|
30
|
+
## 数据与平台
|
|
31
|
+
|
|
32
|
+
旧根不自动迁移;新音频/视频根与网络任务有明确版本门禁,不能让旧CLI写入它们。已发布DSH原工具和原CLI仍按既有范围使用,Core新音视频不自动加入DSH原生共享Flow桥。
|
|
33
|
+
|
|
34
|
+
beta.1最低Node为22.1.0:隔离复核确认22.0.0原生fetch正文取消不会关闭连接,会占用调度名额;22.1.0与当前22.23.4通过同一验证。CI配置包含Ubuntu/Windows及最低/当前Node22;**工作流配置存在和历史CI成功都不能证明当前候选通过**。本机Termux/PRoot ARM64、真实浏览器、本地代理、实际供应商和原生Agent结果应分别记录。最终发布还需核对同一候选的完整测试、独立包消费者和标准平台矩阵,不能拿旧压测或一次握手替代。
|
|
35
|
+
|
|
36
|
+
自动配额/GC、复杂模型/出口加权轮询、更多宿主凭据桥、legacy统一迁移、全部供应商/GPU/手机/浏览器/harness认证继续后置;不因本页列出能力就宣称这些覆盖已完成。
|
|
37
|
+
|
|
38
|
+
## 源码验收与发行候选
|
|
39
|
+
|
|
40
|
+
文档中的 `node scripts/...`、`tests/...`、`npm test` 和 `npm run lint` 从源码仓库根运行;开发脚本与测试不随npm包根目录分发。安装用户继续使用包内CLI/Web/MCP入口,不能把源码验证命令当作已安装包的使用要求。
|
|
41
|
+
|
|
42
|
+
CI除Ubuntu/Windows、最低/当前Node22完整测试外,另在Ubuntu当前Node22构建真实tarball,检查包外插件/Provider与凭据/调度消费者、strict NodeNext公共类型,并用真实Chromium验证打包后的总览、凭据、视频与调度页面。缺少必需编译器或浏览器会使该发行检查失败;其他可选运行时的SKIP不计作通过。具体运行结果以对应提交的CI和发行记录为准,历史B5记录不代替本轮结果。
|
package/docs/CONTACT_SHEET.md
CHANGED