@mokuyoaxis/dsh-iris 0.2.7 → 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.
Files changed (234) hide show
  1. package/CHANGELOG.md +143 -1
  2. package/README.en.md +109 -311
  3. package/README.md +106 -309
  4. package/bin/dsh-iris.js +452 -92
  5. package/docs/AGENT_CORE_QUICKSTART.md +64 -0
  6. package/docs/AGENT_QUICKSTART.md +2 -0
  7. package/docs/ARCHITECTURE.md +13 -3
  8. package/docs/ARTIFACT_MANIFEST.md +4 -0
  9. package/docs/AUDIO_FLOWS.md +87 -0
  10. package/docs/BETA_SCOPE.md +42 -0
  11. package/docs/CONTACT_SHEET.md +62 -0
  12. package/docs/CORE_CONNECTIONS.md +101 -0
  13. package/docs/CORE_QUICKSTART.md +110 -0
  14. package/docs/CORE_RUNTIME_CONTRACT.md +7 -2
  15. package/docs/CORE_SERVICE.md +101 -0
  16. package/docs/CORE_SKILLS.md +35 -3
  17. package/docs/DSH_COMPATIBILITY.md +21 -1
  18. package/docs/DSH_CORE_MIGRATION.md +6 -0
  19. package/docs/DSH_RC2_ADAPTATION.md +1 -1
  20. package/docs/DSH_RUNTIME.md +6 -2
  21. package/docs/HEADLESS_CLI.md +30 -5
  22. package/docs/HOST_ADAPTER_CONTRACT.md +5 -3
  23. package/docs/IMAGE_FLOW.md +4 -0
  24. package/docs/IMAGE_GENERATION.md +127 -0
  25. package/docs/LOCAL_API.md +4 -2
  26. package/docs/LOCAL_API_CONTRACT.md +42 -15
  27. package/docs/LOCAL_CREATION.md +7 -1
  28. package/docs/LOCAL_OPERATIONS.md +46 -0
  29. package/docs/LOCAL_SELECTION.md +57 -0
  30. package/docs/LOCAL_WEB.md +207 -0
  31. package/docs/MCP.md +212 -0
  32. package/docs/MEDIA_SCHEDULING.md +72 -0
  33. package/docs/MUSIC.md +2 -0
  34. package/docs/NETWORK_RESOURCES.md +106 -0
  35. package/docs/NETWORK_ROUTING.md +103 -0
  36. package/docs/NETWORK_WORKBENCH.md +65 -0
  37. package/docs/PANEL_EXTENSIONS.md +64 -0
  38. package/docs/PLUGINS.md +193 -0
  39. package/docs/PLUGIN_DEVELOPMENT.md +39 -0
  40. package/docs/PLUGIN_STARTUP.md +113 -0
  41. package/docs/PROVIDER_ADAPTER_CONTRACT.md +8 -2
  42. package/docs/PROVIDER_CREDENTIALS.md +67 -0
  43. package/docs/PROVIDER_EXTENSIONS.md +82 -0
  44. package/docs/PROVIDER_HEALTH.md +5 -1
  45. package/docs/PROVIDER_NETWORK.md +45 -0
  46. package/docs/PROVIDER_PLUGIN_CONTRACT.md +24 -0
  47. package/docs/PROVIDER_SETTINGS.md +91 -0
  48. package/docs/PROVIDER_SUBMISSION_CONTRACT.md +8 -0
  49. package/docs/ROADMAP.md +18 -8
  50. package/docs/SECURITY.md +1 -1
  51. package/docs/SYNC_DELIVERY_RECOVERY.md +5 -1
  52. package/docs/VIDEO_FLOWS.md +89 -0
  53. package/docs/VISION_CLI.md +1 -1
  54. package/docs/VISION_MODEL.md +2 -0
  55. package/docs/WEB_PROTOTYPE.md +21 -6
  56. package/docs/WEB_RECOVERY.md +46 -0
  57. package/docs/WORKBENCH_ARTIFACTS.md +6 -0
  58. package/docs/WORKBENCH_LOGIN.md +63 -0
  59. package/docs/assets/screenshots/core-connections-preview.webp +0 -0
  60. package/docs/assets/screenshots/core-model-pools-preview.webp +0 -0
  61. package/docs/assets/screenshots/core-overview-preview.webp +0 -0
  62. package/docs/assets/screenshots/core-spectrum-preview.webp +0 -0
  63. package/docs/assets/screenshots/core-studio-dark-preview.webp +0 -0
  64. package/docs/assets/screenshots/core-studio-mobile-preview.webp +0 -0
  65. package/docs/assets/screenshots/core-studio-preview.webp +0 -0
  66. package/docs/releases/0.3.0-alpha.1.md +51 -0
  67. package/docs/releases/0.3.0-beta.1.md +34 -0
  68. package/docs/screenshots.md +41 -1
  69. package/lib/actions.js +56 -4
  70. package/lib/adapters.js +75 -49
  71. package/lib/api.js +23 -13
  72. package/lib/application-runtime.js +73 -17
  73. package/lib/application-writer.js +193 -0
  74. package/lib/audio-flow-input.js +37 -0
  75. package/lib/client.d.ts +3 -3
  76. package/lib/client.js +402 -72
  77. package/lib/comfyui-adapter.js +95 -0
  78. package/lib/comfyui-client.js +60 -0
  79. package/lib/comfyui-inspection.js +73 -0
  80. package/lib/comfyui-workflow.js +94 -0
  81. package/lib/command-service.js +18 -5
  82. package/lib/config.js +49 -10
  83. package/lib/configured-image-runtime.js +69 -0
  84. package/lib/connection-skill.js +98 -0
  85. package/lib/connection-template.js +23 -0
  86. package/lib/core-artifact-store.js +84 -28
  87. package/lib/core-bridge-selection.js +31 -0
  88. package/lib/core-bridge-tools.js +85 -0
  89. package/lib/core-capability-catalog.js +192 -0
  90. package/lib/core-connection-diagnostics.js +49 -0
  91. package/lib/core-connection-store.js +277 -0
  92. package/lib/core-connections.js +225 -0
  93. package/lib/core-delivery-cache.js +8 -3
  94. package/lib/core-maintenance.js +9 -3
  95. package/lib/core-runtime.js +2 -1
  96. package/lib/core-service-client.js +174 -0
  97. package/lib/core-tasks.js +113 -21
  98. package/lib/core-writer-lease.js +16 -0
  99. package/lib/creation-request-store.js +41 -64
  100. package/lib/doctor.js +2 -2
  101. package/lib/dsh-core-adapter.js +61 -9
  102. package/lib/dsh-host-adapter.js +4 -1
  103. package/lib/dsh-provider-config.js +1 -1
  104. package/lib/dsh-service-bridge.js +67 -0
  105. package/lib/execution-ownership.js +43 -0
  106. package/lib/flow-store.js +156 -14
  107. package/lib/generation-input.js +10 -1
  108. package/lib/headless-vision.js +7 -7
  109. package/lib/image-edit-input.js +1 -1
  110. package/lib/image-flow.js +97 -31
  111. package/lib/index.js +47 -22
  112. package/lib/local-api-projection.js +15 -6
  113. package/lib/local-api-token-directory.js +31 -0
  114. package/lib/local-api.js +861 -71
  115. package/lib/local-creation.js +96 -24
  116. package/lib/local-operations.js +36 -0
  117. package/lib/local-request-root.js +86 -0
  118. package/lib/local-web-assets.js +39 -0
  119. package/lib/mcp-bridge.js +58 -0
  120. package/lib/mcp-check.js +26 -0
  121. package/lib/mcp-config.js +42 -0
  122. package/lib/mcp-delivery.js +46 -0
  123. package/lib/mcp-http.js +174 -0
  124. package/lib/mcp-server.js +69 -0
  125. package/lib/media-scheduler.js +194 -0
  126. package/lib/model-directory-service.js +37 -10
  127. package/lib/model-directory.js +3 -3
  128. package/lib/models.js +2 -2
  129. package/lib/music-transport.js +6 -3
  130. package/lib/network-config-service.js +123 -0
  131. package/lib/network-config.js +127 -0
  132. package/lib/network-doctor.js +94 -0
  133. package/lib/network-management.js +51 -0
  134. package/lib/network-policy.js +19 -0
  135. package/lib/network-route-evidence.js +38 -0
  136. package/lib/network-routing.js +283 -0
  137. package/lib/operation-runner.js +281 -0
  138. package/lib/operation-store.js +216 -0
  139. package/lib/panel-extension.js +36 -0
  140. package/lib/plugin-contract.js +194 -0
  141. package/lib/plugin-execution.js +51 -0
  142. package/lib/plugin-launch.js +86 -0
  143. package/lib/plugin-operation-store.js +101 -0
  144. package/lib/plugin-operations.js +28 -0
  145. package/lib/plugin-registry.js +109 -0
  146. package/lib/plugin-selection.js +15 -0
  147. package/lib/plugin-source-image.js +5 -0
  148. package/lib/plugin-web-assets.js +54 -0
  149. package/lib/plugins.d.ts +307 -0
  150. package/lib/plugins.js +59 -0
  151. package/lib/provider-adapter.js +7 -3
  152. package/lib/provider-adapters.js +92 -22
  153. package/lib/provider-catalog.js +64 -36
  154. package/lib/provider-config-format.js +51 -0
  155. package/lib/provider-config-service.js +143 -31
  156. package/lib/provider-context.d.ts +90 -0
  157. package/lib/provider-context.js +42 -0
  158. package/lib/provider-credentials.js +97 -0
  159. package/lib/provider-diagnostics.js +56 -0
  160. package/lib/provider-extensions.js +29 -0
  161. package/lib/provider-health.js +18 -6
  162. package/lib/provider-monitor.js +81 -0
  163. package/lib/provider-network.js +52 -0
  164. package/lib/provider-protocol.js +73 -22
  165. package/lib/provider-registry.js +128 -0
  166. package/lib/provider-response.js +38 -0
  167. package/lib/provider-settings.js +136 -0
  168. package/lib/provider-task-runner.js +132 -43
  169. package/lib/provider-transport.js +16 -0
  170. package/lib/render.js +11 -1
  171. package/lib/scheduling-policy.js +66 -0
  172. package/lib/skill-installation.js +23 -11
  173. package/lib/tasks.js +3 -2
  174. package/lib/video-flow-input.js +16 -0
  175. package/lib/video-input.js +12 -5
  176. package/lib/vision-model-routing.js +5 -0
  177. package/lib/web/app.js +703 -0
  178. package/lib/web/appearance.js +111 -0
  179. package/lib/web/comfyui.js +146 -0
  180. package/lib/web/connections.js +516 -0
  181. package/lib/web/creation.js +359 -0
  182. package/lib/web/flow-view.js +34 -0
  183. package/lib/web/gallery.js +133 -0
  184. package/lib/web/index.html +344 -0
  185. package/lib/web/media-client.js +218 -0
  186. package/lib/web/network.js +232 -0
  187. package/lib/web/plugin-panel.js +223 -0
  188. package/lib/web/plugin-status.js +140 -0
  189. package/lib/web/plugin-surface.css +17 -0
  190. package/lib/web/providers.js +513 -0
  191. package/lib/web/recovery.js +105 -0
  192. package/lib/web/startup.js +18 -0
  193. package/lib/web/styles.css +571 -0
  194. package/lib/web/transport.js +101 -0
  195. package/lib/web/workbench-login.js +165 -0
  196. package/lib/workbench-artifacts.js +3 -2
  197. package/lib/workbench-auth-cli.js +47 -0
  198. package/lib/workbench-auth.js +200 -0
  199. package/package.json +24 -6
  200. package/plugins/contact-sheet/index.d.mts +4 -0
  201. package/plugins/contact-sheet/index.mjs +33 -0
  202. package/plugins/contact-sheet/web/board.css +14 -0
  203. package/plugins/contact-sheet/web/board.mjs +93 -0
  204. package/skills/iris-core/SKILL.md +8 -4
  205. package/skills/iris-core/references/cli.md +2 -2
  206. package/skills/iris-core/references/comfyui.md +69 -0
  207. package/skills/iris-core/references/tasks.md +2 -0
  208. package/templates/plugin-starter/LICENSE +21 -0
  209. package/templates/plugin-starter/README.md +74 -0
  210. package/templates/plugin-starter/assets/peach.png +0 -0
  211. package/templates/plugin-starter/assets/violet.png +0 -0
  212. package/templates/plugin-starter/index.mjs +27 -0
  213. package/templates/plugin-starter/package.json +18 -0
  214. package/templates/plugin-starter/scripts/core-cli.mjs +20 -0
  215. package/templates/plugin-starter/scripts/environment.mjs +65 -0
  216. package/templates/plugin-starter/scripts/mcp-probe.mjs +22 -0
  217. package/templates/plugin-starter/scripts/setup.mjs +42 -0
  218. package/templates/plugin-starter/scripts/smoke.mjs +76 -0
  219. package/templates/plugin-starter/web/board.css +27 -0
  220. package/templates/plugin-starter/web/board.mjs +160 -0
  221. package/templates/plugin-starter/web/png.mjs +23 -0
  222. package/templates/provider-starter/LICENSE +21 -0
  223. package/templates/provider-starter/README.md +50 -0
  224. package/templates/provider-starter/index.d.mts +5 -0
  225. package/templates/provider-starter/index.mjs +165 -0
  226. package/templates/provider-starter/package.json +13 -0
  227. package/templates/provider-starter/scripts/core-cli.mjs +22 -0
  228. package/templates/provider-starter/scripts/environment.mjs +122 -0
  229. package/templates/provider-starter/scripts/fixture.mjs +54 -0
  230. package/templates/provider-starter/scripts/mcp-probe.mjs +21 -0
  231. package/templates/provider-starter/scripts/protocol-smoke.mjs +48 -0
  232. package/templates/provider-starter/scripts/setup.mjs +44 -0
  233. package/templates/provider-starter/scripts/smoke.mjs +145 -0
  234. package/user_guide.md +5 -1
@@ -0,0 +1,66 @@
1
+ // Explicit startup policy; never discovers files or rewrites existing configuration.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export function schedulingError(code, message) {
6
+ return Object.assign(new Error(message), { code, stage: 'prepare', category: 'rate_limit', acceptance: 'not_accepted' });
7
+ }
8
+ const invalid = message => { throw schedulingError('IRIS_SCHEDULER_CONFIG_INVALID', message); };
9
+ function fields(value, names, where) {
10
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some(name => !names.includes(name))) invalid(`${where} 包含无效字段`);
11
+ }
12
+ function integer(value, min, max, name) {
13
+ if (!Number.isSafeInteger(value) || value < min || value > max) invalid(`${name} 需要 ${min}–${max} 的整数`);
14
+ return value;
15
+ }
16
+ function identity(value, where) {
17
+ if (typeof value !== 'string' || !value.trim() || value !== value.trim() || value.length > 256 || /[\x00-\x1f\x7f]/.test(value)) invalid(`${where} 无效`);
18
+ return value;
19
+ }
20
+ function limits(value, defaults = {}) {
21
+ return Object.fromEntries([
22
+ ['httpConcurrency', 1, 4], ['requestsPerSecond', 1, 1000], ['remoteLimit', 1, 1000]
23
+ ].flatMap(([key, min, max]) => value[key] === undefined && defaults[key] === undefined ? []
24
+ : [[key, integer(value[key] === undefined ? defaults[key] : value[key], min, max, key)]]));
25
+ }
26
+ export function checkedSchedulingPolicy(input) {
27
+ fields(input, ['schemaVersion', 'httpConcurrency', 'requestsPerSecond', 'remoteLimit', 'queueLimit', 'queueTimeoutMs', 'providers'], '调度策略');
28
+ if (input.schemaVersion !== 1) invalid('仅支持 scheduler schemaVersion=1;不会忽略或迁移其他版本');
29
+ const providers = input.providers === undefined ? [] : input.providers;
30
+ if (!Array.isArray(providers) || providers.length > 128) invalid('providers 需要至多 128 项的数组');
31
+ const seen = new Set();
32
+ return Object.freeze({ schemaVersion: 1, ...limits(input, { httpConcurrency: 4, requestsPerSecond: 2, remoteLimit: 8 }),
33
+ queueLimit: integer(input.queueLimit === undefined ? 32 : input.queueLimit, 8, 128, 'queueLimit'),
34
+ queueTimeoutMs: integer(input.queueTimeoutMs === undefined ? 30000 : input.queueTimeoutMs, 1, 300000, 'queueTimeoutMs'),
35
+ providers: Object.freeze(providers.map(provider => {
36
+ fields(provider, ['providerId', 'httpConcurrency', 'requestsPerSecond', 'remoteLimit', 'models'], '供应商策略');
37
+ const providerId = identity(provider.providerId, 'providerId');
38
+ if (seen.has(providerId)) invalid('providerId 重复'); seen.add(providerId);
39
+ const models = provider.models === undefined ? [] : provider.models, ids = new Set();
40
+ if (!Array.isArray(models) || models.length > 256) invalid('models 需要至多 256 项的数组');
41
+ return Object.freeze({ providerId, ...limits(provider), models: Object.freeze(models.map(model => {
42
+ fields(model, ['modelId', 'httpConcurrency', 'requestsPerSecond', 'remoteLimit'], '模型策略');
43
+ const modelId = identity(model.modelId, 'modelId');
44
+ if (ids.has(modelId)) invalid('modelId 重复'); ids.add(modelId);
45
+ return Object.freeze({ modelId, ...limits(model) });
46
+ })) });
47
+ })) });
48
+ }
49
+ export function readSchedulingPolicy(file) {
50
+ if (typeof file !== 'string' || !path.isAbsolute(file)) invalid('scheduler-config 需要明确的绝对路径');
51
+ let fd;
52
+ try {
53
+ fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
54
+ const stat = fs.fstatSync(fd);
55
+ if (!stat.isFile() || stat.size > 262144) invalid('scheduler-config 必须是 ≤256 KiB 的普通文件');
56
+ if (process.platform !== 'win32' && (stat.mode & 0o077)) invalid('scheduler-config 请限制为仅当前用户可读写');
57
+ const bytes = fs.readFileSync(fd);
58
+ if (bytes.length > 262144) invalid('scheduler-config 过大');
59
+ let value;
60
+ try { value = JSON.parse(bytes.toString('utf8')); } catch { invalid('scheduler-config 不是有效 JSON'); }
61
+ return checkedSchedulingPolicy(value);
62
+ } catch (error) {
63
+ if (error.code === 'IRIS_SCHEDULER_CONFIG_INVALID') throw error;
64
+ throw schedulingError('IRIS_SCHEDULER_CONFIG_IO', '无法读取 scheduler-config;请检查路径、权限和文件状态,不会退回无调度模式');
65
+ } finally { if (fd !== undefined) fs.closeSync(fd); }
66
+ }
@@ -1,9 +1,10 @@
1
- /** Explicit, copy-based installation of the bundled Core skill. No host/model I/O. */
1
+ /** Explicit installation of the bundled Core skill or a rendered connection snapshot. No host/model I/O. */
2
2
  import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import crypto from 'node:crypto';
6
6
  import { fileURLToPath } from 'node:url';
7
+ import { connectionSkillResources } from './connection-skill.js';
7
8
 
8
9
  const PACKAGE_ROOT = fileURLToPath(new URL('../', import.meta.url));
9
10
  const SKILL = 'iris-core';
@@ -58,10 +59,10 @@ function sameTree(tree, recorded) {
58
59
  && JSON.stringify(tree.directories) === JSON.stringify([...recorded.directories].sort());
59
60
  }
60
61
 
61
- function validReceipt(value, packageName) {
62
+ function validReceipt(value, packageName, skillName) {
62
63
  const relative = name => typeof name === 'string' && name.length > 0 && !name.includes('\\')
63
64
  && !name.includes('\0') && !name.split('/').some(part => !part || part === '.' || part === '..');
64
- return value?.schemaVersion === 1 && value.packageName === packageName && value.skillName === SKILL
65
+ return value?.schemaVersion === 1 && value.packageName === packageName && value.skillName === skillName
65
66
  && typeof value.packageVersion === 'string' && value.packageVersion.length > 0
66
67
  && value.files && !Array.isArray(value.files) && typeof value.files === 'object'
67
68
  && Object.hasOwn(value.files, 'SKILL.md') && !Object.hasOwn(value.files, RECEIPT)
@@ -77,7 +78,18 @@ function packageResources(packageRoot) {
77
78
  if (!tree.files['SKILL.md'] || tree.files[RECEIPT]) {
78
79
  throw error('IRIS_SKILL_PACKAGE_INVALID', '包内缺少 Skill 入口,或含有不应发布的安装记录');
79
80
  }
80
- return { source, metadata, ...tree };
81
+ return { skillName: SKILL, source, metadata, ...tree };
82
+ }
83
+
84
+ function bundleResources(file, packageRoot) {
85
+ if (typeof file !== 'string' || !path.isAbsolute(file)) throw error('IRIS_SKILL_OPTIONS_INVALID', '--bundle 必须是绝对路径');
86
+ const entry = fs.lstatSync(file);
87
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size > 16384) throw error('IRIS_SKILL_BUNDLE_INVALID', 'Skill 交付文件必须是最多 16 KiB 的普通 JSON 文件');
88
+ let value;
89
+ try { value = JSON.parse(fs.readFileSync(file, 'utf8')); }
90
+ catch (cause) { if (!(cause instanceof SyntaxError)) throw cause; throw error('IRIS_SKILL_BUNDLE_INVALID', 'Skill 交付文件不是有效 JSON'); }
91
+ const metadata = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
92
+ return { ...connectionSkillResources(value, metadata), source: fs.realpathSync(file) };
81
93
  }
82
94
 
83
95
  function inspect(destination, resources) {
@@ -92,7 +104,7 @@ function inspect(destination, resources) {
92
104
  if (!(cause instanceof SyntaxError)) throw cause;
93
105
  return { status: 'unmanaged', reason: 'invalid_receipt' };
94
106
  }
95
- if (!validReceipt(receipt, resources.metadata.name)) return { status: 'unmanaged', reason: 'invalid_receipt' };
107
+ if (!validReceipt(receipt, resources.metadata.name, resources.skillName)) return { status: 'unmanaged', reason: 'invalid_receipt' };
96
108
  const base = { installedVersion: receipt.packageVersion };
97
109
  let tree;
98
110
  try { tree = readTree(destination, { installed: true, expected: receipt.files }); }
@@ -125,7 +137,7 @@ function ensureDirectory(directory) {
125
137
 
126
138
  function archive(destination, control, action) {
127
139
  const transaction = fs.mkdtempSync(path.join(control, 'backup-'));
128
- const backupPath = path.join(transaction, SKILL);
140
+ const backupPath = path.join(transaction, path.basename(destination));
129
141
  fs.writeFileSync(path.join(transaction, 'move.json'), JSON.stringify({ schemaVersion: 1, action,
130
142
  at: new Date().toISOString(), origin: destination, backupPath }, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
131
143
  fs.renameSync(destination, backupPath);
@@ -137,24 +149,24 @@ function copyResources(destination, resources) {
137
149
  for (const relative of resources.directories) fs.mkdirSync(path.join(destination, relative), { recursive: true, mode: 0o700 });
138
150
  for (const [relative, bytes] of Object.entries(resources.bytes)) fs.writeFileSync(path.join(destination, relative), bytes, { flag: 'wx', mode: 0o600 });
139
151
  fs.writeFileSync(path.join(destination, RECEIPT), JSON.stringify({ schemaVersion: 1,
140
- packageName: resources.metadata.name, packageVersion: resources.metadata.version, skillName: SKILL,
152
+ packageName: resources.metadata.name, packageVersion: resources.metadata.version, skillName: resources.skillName,
141
153
  installedAt: new Date().toISOString(), files: resources.files, directories: resources.directories
142
154
  }, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
143
155
  }
144
156
 
145
157
  /** Internal CLI service. Explicit mutations only; no postinstall or public SDK export. */
146
- export function manageCoreSkill({ action, target, directory, dryRun = false }, {
158
+ export function manageCoreSkill({ action, target, directory, bundle, dryRun = false }, {
147
159
  packageRoot = PACKAGE_ROOT, env = process.env, home = os.homedir()
148
160
  } = {}) {
149
161
  if (!['install', 'status', 'uninstall'].includes(action) || action === 'status' && dryRun) {
150
162
  throw error('IRIS_SKILL_OPTIONS_INVALID', '使用 skills install/status/uninstall;status 不接受 --dry-run');
151
163
  }
152
- const resources = packageResources(packageRoot);
153
- const resolved = resolveDirectory({ target, directory, env, home }), destination = path.join(resolved, SKILL);
164
+ const resources = bundle === undefined ? packageResources(packageRoot) : bundleResources(bundle, packageRoot);
165
+ const resolved = resolveDirectory({ target, directory, env, home }), destination = path.join(resolved, resources.skillName);
154
166
  if (isWithin(resources.source, destination) || isWithin(destination, resources.source)) {
155
167
  throw error('IRIS_SKILL_OPTIONS_INVALID', '安装目标不能与包内 Skill 源目录重叠');
156
168
  }
157
- const base = { skill: SKILL, target: target || 'directory', directory: resolved, destination,
169
+ const base = { skill: resources.skillName, target: target || 'directory', directory: resolved, destination,
158
170
  packageVersion: resources.metadata.version, discovery: 'not_checked' };
159
171
  const before = inspect(destination, resources);
160
172
  const result = { ...base, ...before };
package/lib/tasks.js CHANGED
@@ -718,7 +718,7 @@ export async function submitGuard(task, fn) {
718
718
  /**
719
719
  * 重启恢复:接管仍在运行的远程任务。DSH 重启期间百炼任务在服务端继续跑,
720
720
  * 结果 URL 存活 24h,重启后重新盯守即可落袋。
721
- * @param {(task) => deps|null} depsFor 返回盯守依赖;null 表示无法接管(如供应商已删除)
721
+ * @param {(task) => deps|null} depsFor 返回盯守依赖;null 仅暂停接管(如连接暂不可用),不证明远端失败
722
722
  * @returns {string[]} 成功接管的任务 id
723
723
  */
724
724
  export function resumePending(depsFor) {
@@ -737,9 +737,10 @@ export function resumePending(depsFor) {
737
737
  }
738
738
  const deps = depsFor(t);
739
739
  if (!deps) {
740
- update(t.id, { status: 'failed', error: '无法恢复:对应供应商不可用' });
740
+ update(t.id, { error: '无法恢复盯守:对应供应商不可用;远端任务状态未改变,可在连接恢复后再次接管' });
741
741
  continue;
742
742
  }
743
+ if (t.error) update(t.id, { error: undefined });
743
744
  watch(t, deps);
744
745
  resumed.push(t.id);
745
746
  continue;
@@ -0,0 +1,16 @@
1
+ import { normalizeGenerationInput } from './generation-input.js';
2
+ import { parseModelRef } from './models.js';
3
+
4
+ export const VIDEO_RECIPE = 'video-generate/v0';
5
+ export const MAX_VIDEO_BYTES = 32 * 1024 * 1024;
6
+ export const VIDEO_TYPES = Object.freeze({ 'video/mp4': 'mp4', 'video/webm': 'webm' });
7
+ export function normalizeVideoFlowInput(input) {
8
+ if (!input || typeof input !== 'object' || Array.isArray(input)
9
+ || Object.keys(input).some(key => !['model_ref', 'prompt', 'size', 'duration'].includes(key))
10
+ || !parseModelRef(input.model_ref) || /s2v/i.test(parseModelRef(input.model_ref).modelId)
11
+ || input.duration !== undefined && (typeof input.duration !== 'number' || !Number.isFinite(input.duration))) {
12
+ throw Object.assign(new Error('文生视频仅接受明确模型、prompt 和可选 size/duration;不接受首帧、S2V 或路径'), { code: 'IRIS_VIDEO_FLOW_INPUT_INVALID' });
13
+ }
14
+ const value = normalizeGenerationInput('video', input);
15
+ return { model_ref: value.modelRef, ...value.providerInput };
16
+ }
@@ -7,19 +7,26 @@ import { parseModelRef } from './models.js';
7
7
  import { prepareProviderInput } from './provider-adapter.js';
8
8
  import { ProviderContractError } from './provider-contract.js';
9
9
 
10
+ /** Recognized input families constrain routing; opaque compatible IDs keep their existing t2v/i2v support. */
11
+ export function videoModelSupportsMode(modelId, mode) {
12
+ const family = /(?:^|[-_.])(t2v|i2v|s2v|r2v)(?=$|[-_.])/i.exec(modelId)?.[1].toLowerCase();
13
+ if (family) return family === mode;
14
+ return mode === 't2v' || mode === 'i2v';
15
+ }
16
+
10
17
  export function videoTaskCandidates(candidates, normalized) {
11
18
  const s2v = normalized.videoMode === 's2v';
12
- const compatible = candidates.filter(candidate => /s2v/i.test(parseModelRef(candidate.model)?.modelId || '') === s2v);
19
+ const compatible = candidates.filter(candidate => videoModelSupportsMode(parseModelRef(candidate.model)?.modelId || '', normalized.videoMode));
13
20
  if (!compatible.length) throw new GenerationInputError(s2v
14
- ? 'audio_path 只能配合 s2v 数字人模型' : 's2v 模型需要首帧和音频输入');
21
+ ? 'audio_path 只能配合 s2v 数字人模型' : '没有与 ' + normalized.videoMode + ' 输入匹配的视频模型;s2v 模型需要首帧和音频输入');
15
22
  return compatible.map(candidate => ({ ...candidate,
16
- ...(normalized.firstFramePath ? { async prepareInput({ signal }) {
23
+ ...(normalized.firstFramePath ? { async prepareInput({ signal, taskId, attemptId, networkBudget, recordNetworkRoute }) {
17
24
  if (signal.aborted) throw new ProviderContractError('视频输入准备已取消', { stage: 'prepare', category: 'aborted', acceptance: 'not_accepted' });
18
25
  if (s2v) {
19
26
  const model = parseModelRef(candidate.model).modelId;
20
- const image = await prepareProviderInput(candidate.adapter, { model, filePath: normalized.firstFramePath, signal });
27
+ const image = await prepareProviderInput(candidate.adapter, { model, filePath: normalized.firstFramePath, signal }, { taskId, attemptId, networkBudget, recordNetworkRoute });
21
28
  if (signal.aborted) throw new ProviderContractError('视频输入准备已取消', { stage: 'upload', category: 'aborted', acceptance: 'not_accepted' });
22
- const audio = await prepareProviderInput(candidate.adapter, { model, filePath: normalized.audioPath, signal });
29
+ const audio = await prepareProviderInput(candidate.adapter, { model, filePath: normalized.audioPath, signal }, { taskId, attemptId, networkBudget, recordNetworkRoute });
23
30
  return { ...normalized.providerInput, imgDataUrl: image.url, audioUrl: audio.url };
24
31
  }
25
32
  const mime = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.gif': 'image/gif' }[path.extname(normalized.firstFramePath).toLowerCase()];
@@ -12,6 +12,7 @@ import { modelAvailabilityBinding, modelUnavailable } from './model-directory.js
12
12
  import { completeVision, VISION_BUDGET } from './vision-core.js';
13
13
  import { open } from 'node:fs/promises';
14
14
  import { DEFAULT_VISION_INPUT, visionInputLimits } from './vision-image-input.js';
15
+ import { providerNetworkRequirement, networkPolicyError } from './network-policy.js';
15
16
 
16
17
  const features = Object.freeze({ system: 'unknown', temperature: 'unknown', maxOutputTokens: 'unknown', reasoning: 'unknown' });
17
18
 
@@ -50,6 +51,7 @@ function deferredDshPort(host, { prepareImages, orientImages, prepareImageReques
50
51
  /** 配置只绑定一个自持模型;CLI 不附加宿主候选。 */
51
52
  export function createConfiguredVisionModelPort(provider, model, { allowRateLimited = false, onRateLimit, onModelAvailability, prepareImages = false,
52
53
  orientImages = false, prepareImageRequest } = {}) {
54
+ if (providerNetworkRequirement(provider)) throw networkPolicyError('IRIS_NETWORK_CAPABILITY_UNSUPPORTED', '独立视觉尚未接入此网络目录;已停止调用,不会默认直连。请保留原视觉配置');
53
55
  const parsed = parseModelRef(resolveModelRef(provider, 'vision', model));
54
56
  const id = 'iris-provider:' + encodeURIComponent(provider.id);
55
57
  if (!parsed) return unavailablePort(id);
@@ -61,6 +63,9 @@ export function createConfiguredVisionModelPort(provider, model, { allowRateLimi
61
63
  orientImages, prepareImageRequest,
62
64
  ...(prepareImages ? { imageInput: visionInputLimits(provider, parsed.modelId) } : {}) });
63
65
  const availability = result => {
66
+ // Ordinary vision/OCR is read-only for provider configuration. Explicit probes
67
+ // and definitive failures still update availability through the existing hook.
68
+ if (result.ok === true && !allowRateLimited) return;
64
69
  const event = { ...result, stage: 'submit', source: allowRateLimited ? 'probe' : 'task',
65
70
  binding: modelAvailabilityBinding(provider, parsed.modelId, 'vision') };
66
71
  if (!recordModelAvailability(provider, parsed.modelId, 'vision', event)) return;