@starreel/mcp 0.1.218 → 0.1.220

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.
@@ -111,6 +111,14 @@ const ASSET_QC_HINT = '\n★【完成后质检·免费】出完等图落库(轮
111
111
  '再按它返回的 agent_qc 亲自逐张看图(像不像档案、是不是同一个人、时代对不对;设定图本身各角度齐不齐、比例与结构对不对;' +
112
112
  '道具与场景图放在剧情里合不合理——对照素材 used_in_shots 那几镜),submit_qc_report 提交质检报告;' +
113
113
  '出镜头图时把它的 review_token 作为 asset_review_token 传给 generate_frames / generate_shot_frame。';
114
+ /**
115
+ * 道具图就绪后平台自动跑的两笔小额视觉检查(后台 fire-and-forget,不需要也不该手动再触发)。
116
+ * 2026-10-26 客户实测:账单里冒出没提示过的检查费,且道具那笔曾被标成「产品画面分析」。
117
+ */
118
+ const PROP_IMAGE_AUTO_CHECKS_HINT = '\n★【出图后自动检查·按用量后付】道具图落库(AI 出图或上传)后,平台会自动做一次道具外形分析(看图补尺寸提示,账单名「道具外形分析」),' +
119
+ '灰度阀开时还会自动做一次道具时代审核(结果见 get_props 的 era_audit)。两笔都是小额视觉审核,事先告诉客户;别再手动 audit_prop_era 重复付费。';
120
+ /** 角色外貌 / 道具描述改动后平台自动跑的设定一致性检查(防抖合并,同值保存不触发)。 */
121
+ const SETTING_LINT_AUTO_HINT = '约 5 分钟后会自动做一次设定一致性检查(文本后付,账单名「设定一致性检查」;同一部剧 5 分钟内多次改动合并成一次,同值保存不触发)';
114
122
  /** 视频与配音生成工具的「完成后质检」引导。 */
115
123
  const VIDEO_QC_HINT = '\n★【完成后质检·免费】出完等它落库(get_storyboards 看 video_status / get_pipeline_status),再调 review_videos 审视频与配音层' +
116
124
  '(缺视频 / 旧帧出的视频 / 片尾硬切 / 意外人声 / 配音缺失 / 音色未分配 / 原声声线过期),修完复审;' +
@@ -591,9 +599,12 @@ export function registerProduceTools(server, client) {
591
599
  payload: z.record(z.string(), z.unknown()).describe('通过 check_bulk_import 的载荷对象:{ mode, episode_meta?, characters?, scenes?, storyboards }'),
592
600
  auto_complete: z.boolean().optional().describe('导入后自动补全(专业字段 + 平台拼基础描述的镜写出图/视频提示词;文本步后付)。不传=跟随剧目开关(默认开);false=只导入'),
593
601
  }, async ({ episode_id, payload, auto_complete }) => jsonResult(await client.producePost(`/episodes/${episode_id}/storyboards/bulk-import`, { payload, ...(typeof auto_complete === 'boolean' ? { auto_complete } : {}) })));
594
- server.tool('get_autofill_status', '查本集后台补全批次(导入 auto_complete / autofill_storyboards 发起的 AI 一键填空)的进度:{ status: running|done|failed|idle, total, done, ok, fail }。免费。' +
602
+ server.tool('get_autofill_status', '查本集后台补全批次(导入 auto_complete / autofill_storyboards 发起的 AI 一键填空)的进度与回执:{ status: running|done|failed|idle, total, done, ok, fail, skipped_no_gap, fields_written_counts, contract_coverage }。免费。' +
603
+ '★回执就是「补全写了什么」的证据:补全写的是内部叙事字段与构图契约,get_storyboards / get_shot_prompts **读不到**,回读看不到变化属正常——看这里的计数即可。' +
604
+ 'skipped_no_gap=没有空缺、跳过未调 AI 未扣费的镜数;fields_written_counts=按字段名计的写入镜数(如 {shot_intent: 44});' +
605
+ 'contract_coverage={total, missing_contract}=本集出图镜数与仍缺构图契约的镜数。只给计数、不给字段内容,把这几个数原样告诉客户。' +
595
606
  '★导入带自动补全后必须等 status=done 再 review_storyboards / generate_frames——补全逐镜改写字段,先审查的 review_token 会因内容变化失效。' +
596
- '每镜数十秒,整集可能十几分钟;轮询间隔 ≥10 秒,别紧轮。status=failed 或 fail>0 时看 fail 数,少量失败的镜可直接 autofill_storyboards 重跑(只填空,不重复扣已填的)。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/autofill-status`)));
607
+ '每镜数十秒,整集可能十几分钟;轮询间隔 ≥10 秒,别紧轮。status=failed 或 fail>0 时看 fail 数,少量失败的镜可直接 autofill_storyboards 重跑(只填空模式下已补过的镜跳过、不调 AI、不扣费;只有没补过的镜计费)。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/autofill-status`)));
597
608
  server.tool('adopt_external_script', '【出口 A】把外部 AI 按范本产出的稿子**直接采用为可拍稿**,我方 AI 完全不介入。秒级、不计费。' +
598
609
  '★前置硬闸:服务端会先跑一遍格式自检,errors 非空直接 400 并把清单原样返回——先用 check_script_format 改到全绿再调本工具。' +
599
610
  'warnings 不拦(与默认宽松模式的 save_script 同尺度)。' +
@@ -612,7 +623,7 @@ export function registerProduceTools(server, client) {
612
623
  '★分钟级后台任务;用 get_run_status 判断是否还在跑,别拿 60 秒当失败判据。' +
613
624
  REVIEW_GATE_HINT('review_script', '改写稿') + WORKFLOW_HINT +
614
625
  '\n★【完成后质检】提取完用 get_characters / get_scenes / get_props 核对档案(名字、外貌、时代)与客户剧本一致;' +
615
- '定妆图/设定图/场景图/道具图出完后由 review_assets 统一审覆盖与质量。', { episode_id: z.number().int().positive(), ...REVIEW_ARGS }, async ({ episode_id, review_token, acknowledge_review, auto_acknowledge }) => jsonResult(await client.producePost(`/episodes/${episode_id}/extract`, { review_token, acknowledge_review, auto_acknowledge })));
626
+ '定妆图/设定图/场景图/道具图出完后由 review_assets 统一审覆盖与质量。' + PROP_IMAGE_AUTO_CHECKS_HINT, { episode_id: z.number().int().positive(), ...REVIEW_ARGS }, async ({ episode_id, review_token, acknowledge_review, auto_acknowledge }) => jsonResult(await client.producePost(`/episodes/${episode_id}/extract`, { review_token, acknowledge_review, auto_acknowledge })));
616
627
  // ---------- 完整工作流进度 ----------
617
628
  server.tool('get_pipeline_status', '查某一集完整工作流的进度(script_rewrite/提取/分镜/语音/出图/出视频/合成/配乐/终拼…各步 ' +
618
629
  'done/partial/pending/not_required)。照它按序推进、不跳步。免费。' +
@@ -842,8 +853,9 @@ export function registerProduceTools(server, client) {
842
853
  server.tool('review_storyboards', '【第②道硬闸·免费】审查分镜:禁区词(会被厂商审核拒、白扣费)、镜头时长分布、相邻构图重复、' +
843
854
  '同场景角色站位漂移、情绪曲线峰谷、关键镜标记。★generate_frames 之前必须先跑本工具——' +
844
855
  '分镜里的问题一旦整集出图就变成整集废图,单镜修不回来。按 findings.action 用 update_shot/split_shot 修完再复审。' +
845
- '★code=forbidden_term 的 finding 带 `terms` 字段(命中的具体词,如「背景音乐」「强光」)与 `shots`(镜号),' +
846
- '照着这两个去 update_shot 改掉即可,不用逐字猜。' +
856
+ '★code=forbidden_term 的 finding 一次列全所有命中(不再只报前几处),带 `hits`:[{shot 镜号, field 字段, term 命中词}],' +
857
+ 'field 是 description / image_prompt / video_prompt / sound_effect 之一;先 get_shot_prompts 读该字段现值,再 update_shot 改掉那个词,不用逐字猜。' +
858
+ '★要表达用光(逆光/侧光/光线明暗)别写进画面描述或提示词,写进 update_shot 的 lighting 字段——它不在禁区词扫描范围,出视频与出图都读它。' +
847
859
  '★禁区词判**否定语义**:写「无背景音乐」「不要配乐」不算违规(那是在遵守约束),不必为此改稿。' +
848
860
  '★warnings 里的「动作过载」(action_overload,advisory 不拦):一镜塞了超过时长承载量的独立动作,出视频前用 split_shot 拆开或删动作——成片后靠慢放/叠化补救不了。多人各做一件事或本身连贯的一串可忽略。' + AGENT_QC_HINT, { episode_id: z.number().int().positive(), base_review_token: BASE_REVIEW_ARG }, async ({ episode_id, base_review_token }) => jsonResult(await client.producePost(`/episodes/${episode_id}/review/storyboards`, base_review_token ? { base_review_token } : undefined)));
849
861
  server.tool('review_frames', '【第③道硬闸·免费】审查镜头图片层:角色身份锚覆盖(缺定妆图的角色在镜头里必漂)、出图失败率、' +
@@ -905,8 +917,32 @@ export function registerProduceTools(server, client) {
905
917
  server.tool('review_all', '(推荐·免费)三层一次跑完的整体体检:剧本+分镜+镜头图片。开工前摸底、交付前复查用。' +
906
918
  '★它**不发 review_token** —— 进收费步前仍需对应层的 review_script / review_storyboards / review_frames 各跑一次' +
907
919
  '(闸认的是「针对当前产出物刚审过」,不是「审过一次」)。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/review/all`)));
908
- server.tool('autofill_storyboards', '(推荐)AI 一键给全集分镜补全空缺字段,默认**只填空缺、不覆盖已有**(overwrite=true 才覆盖)。提升分镜完整性,出图前做。后台异步,文本步后付。' +
909
- '★平台开启契约自动补全时,也会尝试给**没有构图契约**的镜补上契约(是否开启以导入回执 contract_coverage 的 note 为准;可见人数/主体范围/景别,出图与审核都按它判;默认 overwrite:false 时已有契约不动,**overwrite:true 会连已有契约一起重写**)——导入时关了 auto_complete、回执 contract_coverage 的 note 指向本工具的,出图前调一次。本集已有补全在跑时本次**不排队**(防重复点击重复扣费),新导入的镜要等 get_autofill_status 到 done 后再调。', { episode_id: z.number().int().positive(), overwrite: z.boolean().optional().describe('true=覆盖已有字段(默认 false 只填空缺)') }, async ({ episode_id, overwrite }) => jsonResult(await client.producePost(`/episodes/${episode_id}/storyboards/autofill`, overwrite ? { overwrite } : {})));
920
+ server.tool('quote_autofill', '报价:autofill_storyboards 要多少点。零扣费、不改任何数据。口径与补全本身一致:只填空模式下已经没有空缺的镜跳过、不计费;overwrite:true 全部计费。' +
921
+ '返回 { shots_to_fill(会真正调 AI 的镜数), shots_skipped_no_gap(没有空缺、跳过不扣费的镜数), estimated_points(预算上沿,按它备余额), typical_points(通常花费) }——' +
922
+ '两个点数都告诉客户,客户同意后再用**同样的** overwrite / storyboard_ids 调 autofill_storyboards。补全是后付文本步,报价不发 quote_id,也不锁价。', {
923
+ episode_id: z.number().int().positive(),
924
+ overwrite: z.boolean().optional().describe('与 autofill_storyboards 相同:true=覆盖已有字段、全部计费(默认 false 只填空缺)'),
925
+ storyboard_ids: z.array(z.number().int().positive()).min(1).max(50).optional()
926
+ .describe('与 autofill_storyboards 相同:只报这些镜(本集镜 id,1~50 个);不传 = 整集。含别集的镜 → 400'),
927
+ }, async ({ episode_id, overwrite, storyboard_ids }) => jsonResult(await client.producePost(`/episodes/${episode_id}/storyboards/autofill/quote`, {
928
+ ...(overwrite ? { overwrite } : {}),
929
+ ...(storyboard_ids ? { storyboard_ids } : {}),
930
+ })));
931
+ server.tool('autofill_storyboards', '(推荐)AI 一键给全集(或 storyboard_ids 指定的几镜)分镜补全空缺字段,默认**只填空缺、不覆盖已有**(overwrite=true 才覆盖)。提升分镜完整性,出图前做。后台异步,文本步后付(按实际调 AI 的镜计费)。' +
932
+ '★**先 quote_autofill 报价**(同样的 overwrite / storyboard_ids),把 shots_to_fill 与 estimated_points / typical_points 告诉客户,**客户同意再跑**本工具。' +
933
+ '★跑完用 get_autofill_status 看回执(写了哪些字段各几镜、跳过几镜、还缺几镜契约)告诉客户——补全写的内部字段 get_storyboards 读不到,别让客户以为白扣。' +
934
+ '★拆镜落库时平台已自动补过一轮:只填空模式下已没有空缺的镜(六个叙事文本字段都已有、有台词的镜声音方向也已有,契约阀开时契约也已有)直接跳过,不调 AI、不扣费;overwrite:true 才会整集重跑并逐镜计费。' +
935
+ '★补全写的是叙事/节奏类内部字段与构图契约,get_storyboards / get_shot_prompts 不返回这些字段,回读看不到变化属正常,看 get_autofill_status 回执里的计数即可;它**不改**出图/视频提示词正文。' +
936
+ '★平台开启契约自动补全时,也会尝试给**没有构图契约**的镜补上契约(是否开启以导入回执 contract_coverage 的 note 为准;可见人数/主体范围/景别,出图与审核都按它判;默认 overwrite:false 时已有契约不动,**overwrite:true 会连已有契约一起重写**)——导入时关了 auto_complete、回执 contract_coverage 的 note 指向本工具的,出图前调一次。本集已有补全在跑时本次**不排队**(防重复点击重复扣费),新导入的镜要等 get_autofill_status 到 done 后再调。' +
937
+ '★想先小范围试跑看效果:传 storyboard_ids(1~50 个本集镜 id,用 get_storyboards 取),只补这些镜,其它镜不调 AI、不扣费;不传 = 整集。本集已有补全在跑时子集不启动,回执 started=false、reason=fill batch already running,等 get_autofill_status 到 done 再调。', {
938
+ episode_id: z.number().int().positive(),
939
+ overwrite: z.boolean().optional().describe('true=覆盖已有字段(默认 false 只填空缺)'),
940
+ storyboard_ids: z.array(z.number().int().positive()).min(1).max(50).optional()
941
+ .describe('只补这些镜(本集镜 id,1~50 个),其它镜不调 AI、不扣费;不传 = 整集。含别集的镜 → 400'),
942
+ }, async ({ episode_id, overwrite, storyboard_ids }) => jsonResult(await client.producePost(`/episodes/${episode_id}/storyboards/autofill`, {
943
+ ...(overwrite ? { overwrite } : {}),
944
+ ...(storyboard_ids ? { storyboard_ids } : {}),
945
+ })));
910
946
  server.tool('enhance_shot_prompts', '(可选增强·★非必须·有副作用)AI 批量增强全集 image_prompt。★会**改写已有 prompt**(含手调的);且**务必 generate_frames 之前**做——' +
911
947
  '出图后再改 prompt 会让图陈旧、要重生浪费钱。非跑通一部片的必需,客户点名再做。后台异步,文本步后付。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/storyboards/enhance-prompts`)));
912
948
  server.tool('complete_ending_motifs', '(可选增强·★非必须·条件性)给结尾段补全缺失的视觉母题(嫁接视觉回响到结尾镜)。' +
@@ -1489,7 +1525,7 @@ export function registerProduceTools(server, client) {
1489
1525
  const image_url = await client.uploadLocalFile(file_path, 'image');
1490
1526
  return jsonResult(await client.producePost(`/scenes/${scene_id}/image`, { image_url, ...(allow_issues ? { allow_issues: true } : {}) }));
1491
1527
  });
1492
- server.tool('upload_prop_sheet', '用客户自有图片作为某道具的设定图。自动上传+登记。免费。' + ASSET_QC_HINT, { prop_id: z.number().int().positive(), file_path: z.string().describe('本地道具图路径') }, async ({ prop_id, file_path }) => {
1528
+ server.tool('upload_prop_sheet', '用客户自有图片作为某道具的设定图。自动上传+登记。上传本身免费。' + PROP_IMAGE_AUTO_CHECKS_HINT + ASSET_QC_HINT, { prop_id: z.number().int().positive(), file_path: z.string().describe('本地道具图路径') }, async ({ prop_id, file_path }) => {
1493
1529
  const image_url = await client.uploadLocalFile(file_path, 'image');
1494
1530
  return jsonResult(await client.producePost(`/props/${prop_id}/sheet`, { image_url }));
1495
1531
  });
@@ -1730,7 +1766,10 @@ export function registerProduceTools(server, client) {
1730
1766
  '出场数据是旧格式时 409 APPEARANCES_NONCANONICAL(一字不写),单人镜改用 active_wardrobe_id。回执带 appearance_wardrobes(现值)与 appearance_wardrobes_effective:' +
1731
1767
  'false = 已落库但本剧尚未开启逐角色服装读取(灰度),出图暂不按它选——单人镜请改用 active_wardrobe_id'),
1732
1768
  title: z.string().optional().describe('镜头标题'),
1733
- description: z.string().optional().describe('画面描述'),
1769
+ description: z.string().optional().describe('画面描述(现值用 get_shot_prompts 读)。★用光不要写进这里——写 lighting'),
1770
+ lighting: z.string().optional().describe('本镜用光(如「逆光」「左侧窗光、脸一半在暗处」)。★用光写这里,不要写进画面描述(description)或提示词:' +
1771
+ '那几个字段写光影词会被 review_storyboards 判 forbidden_term 拦下,这一条不在禁区词扫描范围。出视频时作为 [Lighting] 段送达,出图按它匹配场景光照变体。' +
1772
+ '全量覆盖现值,现值用 get_shot_prompts 读;改完不会自动重出图/视频'),
1734
1773
  shot_type: z.string().optional().describe('景别(如 特写/中景/全景/远景)'),
1735
1774
  angle: z.string().optional().describe('机位角度'),
1736
1775
  camera_movement: z.string().optional().describe('运镜(推/拉/摇/移/跟/固定)'),
@@ -1780,6 +1819,7 @@ export function registerProduceTools(server, client) {
1780
1819
  '★回执里的 terminal_desc_missing=true 表示本镜标为「状态改变」却三处都没说终态——' +
1781
1820
  '出尾帧会被前置闸拦下,修法是把结束状态写进 last_frame_prompt(别重试出图,重试必然重复被拒且照常扣费)。' +
1782
1821
  '★pose_timeline_stale 非 null = 本镜动作起止姿态里的秒数时间轴超出了当前时长(多见于改短 duration 后),出视频时那一端已自动不送;新时间轴写进 video_prompt。' +
1822
+ '★同时回 description(画面描述)与 lighting(本镜用光)现值——review_storyboards 的禁区词命中常在 description,改前先在这里读;用光写 lighting。' +
1783
1823
  '★同时回 expression_hint(本镜作者指定的面部表情,null=未指定、由平台按台词反推),改它用 update_shot。' +
1784
1824
  'expression_inferred 是拆镜 AI 在剧本没写神态时按本镜内容推断的表情(只读;null=没推断,或本镜台词/动作/氛围/描述/强度改过、推断已过期不再使用);它优先级低于 expression_hint——' +
1785
1825
  '推断不合意就用 update_shot 写 expression_hint 覆盖它。' +
@@ -2240,7 +2280,8 @@ export function registerProduceTools(server, client) {
2240
2280
  track_id: z.number().int().positive().describe('get_mv_song 返回的 candidates[].track_id'),
2241
2281
  }, async ({ drama_id, track_id }) => jsonResult(await client.producePost(`/dramas/${drama_id}/mv/song/select`, { track_id })));
2242
2282
  // ========== P1 · 资产精修(提取后改设定再出图)==========
2243
- server.tool('update_character', '改角色设定(名字/外貌/年龄段/族裔/性别/性格/戏份/档案锁定)。改后需重出定妆图/相关图。免费。' +
2283
+ server.tool('update_character', '改角色设定(名字/外貌/年龄段/族裔/性别/性格/戏份/档案锁定)。改后需重出定妆图/相关图。' +
2284
+ '本身免费;改了外貌(appearance 真的变了)' + SETTING_LINT_AUTO_HINT + ';只改 profile_locked 等其它字段不触发。' +
2244
2285
  '★profile_locked=1:客户确认外观后锁定档案,后续 AI 提取不再覆盖外貌/性格/描述——外貌锚着定妆图与人脸锁定,被覆盖=全片换脸;确认满意即锁,要再改先传 0 解锁。', {
2245
2286
  character_id: z.number().int().positive(),
2246
2287
  name: z.string().optional(),
@@ -2254,17 +2295,16 @@ export function registerProduceTools(server, client) {
2254
2295
  profile_locked: z.number().int().min(0).max(1).optional().describe('1=锁定档案(AI 提取不再覆盖外貌/性格/描述,防定妆图换脸);0=解锁'),
2255
2296
  }, async ({ character_id, ...fields }) => jsonResult(await client.producePut(`/characters/${character_id}`, fields)));
2256
2297
  server.tool('delete_character', '删除一个角色(提取误建/合并时用)。免费。', { character_id: z.number().int().positive() }, async ({ character_id }) => jsonResult(await client.produceDelete(`/characters/${character_id}`)));
2257
- server.tool('update_scene', '改场景设定(名字/描述/地点/时段/氛围),以及**场景图的出图提示词正文**(image_prompt)。免费(纯文本写库)。' +
2298
+ server.tool('update_scene', '改场景设定(地点/时段/物理地点归并键/时代契约),以及**场景图的出图提示词正文**(image_prompt)。免费(纯文本写库)。' +
2299
+ '★场景名就是 location;氛围、描述写进 image_prompt。改 time_of_day 后 import_storyboard_table 按「地点+时段」绑场景就能对上。' +
2258
2300
  '★场景图不满意时,改 image_prompt 才是直接手段——只改地点/时段是让平台重拼,拧不过来;' +
2259
2301
  '改前先用 get_scene_prompt 读现值,改完用 regenerate_scene_image 重出这一场的图。' +
2260
2302
  '★场景图变了不会自动重出已生成的镜头帧:那些帧仍拿旧场景图当背景锚,要跟上得逐镜重出。', {
2261
2303
  scene_id: z.number().int().positive(),
2262
- name: z.string().optional(),
2263
- description: z.string().optional(),
2264
- location: z.string().optional(),
2265
- physical_location: z.string().optional(),
2266
- time_of_day: z.string().optional(),
2267
- mood: z.string().optional(),
2304
+ location: z.string().optional().describe('场景地点(即场景名),如「内景 · 地点」'),
2305
+ physical_location: z.string().min(1).max(120).optional()
2306
+ .describe('物理地点归并键:同一地点的不同写法填同一个键,平台按它认作同一处。与 location 同传时以它为准'),
2307
+ time_of_day: z.string().optional().describe('时段,写入后 get_scene_prompt 的 time 读回同值'),
2268
2308
  image_prompt: z.string().optional()
2269
2309
  .describe('这一场**空景图**的提示词正文(全量覆盖本场现值)。★先 get_scene_prompt 读现值再改;★写空景——这张是场景基板,画面里不该有人物(人物由镜头帧那层注入)。出图时平台会在正文之上再拼画风块、空景约束与时代锁,不必你写'),
2270
2310
  era_contract: z.object({
@@ -2348,17 +2388,18 @@ export function registerProduceTools(server, client) {
2348
2388
  server.tool('delete_scene', '删除一个场景。免费。', { scene_id: z.number().int().positive() }, async ({ scene_id }) => jsonResult(await client.produceDelete(`/scenes/${scene_id}`)));
2349
2389
  server.tool('generate_character_sheet', '给**单个**角色出三视图设定图(镜头一致性根锚,所有镜头帧都会引用;比定妆图更完整)。整集批量用 generate_character_sheets。' +
2350
2390
  '前置:该角色已有定妆图。图片步,按用量后付不欠费。' + ASSET_QC_HINT, { character_id: z.number().int().positive(), episode_id: z.number().int().positive().optional() }, async ({ character_id, episode_id }) => jsonResult(await client.producePost(`/characters/${character_id}/sheet`, episode_id ? { episode_id } : {})));
2351
- server.tool('generate_prop_sheet', '给道具 AI 出设定图。图片步,按用量后付不欠费。' + ASSET_QC_HINT, { prop_id: z.number().int().positive() }, async ({ prop_id }) => jsonResult(await client.producePost(`/props/${prop_id}/generate-sheet`)));
2391
+ server.tool('generate_prop_sheet', '给道具 AI 出设定图。图片步,按用量后付不欠费。' + PROP_IMAGE_AUTO_CHECKS_HINT + ASSET_QC_HINT, { prop_id: z.number().int().positive() }, async ({ prop_id }) => jsonResult(await client.producePost(`/props/${prop_id}/generate-sheet`)));
2352
2392
  // ========== 剧目级资产:道具库 CRUD + 色彩脚本/动作模板读取 ==========
2353
2393
  server.tool('get_props', '列剧目道具库(每个道具名称/类型/描述/设定图)。★道具库≠广告商品库(add_product/list_products 是带货商品);道具库是剧目道具。免费。' +
2354
2394
  '★每个道具带 era_audit(道具图的时代/形制审核结论,图换过则为 null):form_consistent=false 表示道具图与它的 era_lock 不符,' +
2355
2395
  'conflicting_scopes 列出它被绑定却时代不合的场景/分集(scope 形如 scene:12 / episode:3),violations 点名具体冲突特征。' +
2356
2396
  '有冲突时:先 update_prop 改 era_lock(写清朝代与形制)再 generate_prop_sheet 重出道具图;或者给那些镜换用合适的道具。', { drama_id: z.number().int().positive() }, async ({ drama_id }) => jsonResult(await client.produceGet(`/dramas/${drama_id}/props`)));
2357
2397
  server.tool('audit_prop_era', '重审某个道具图的时代/形制(一次小额视觉审核,按用量后付)。依据该道具的 era_lock,以及它被绑定的各镜所在场景/分集的时代契约。' +
2358
- '跨时代剧(不同场景属于不同朝代/年代)出完或换完道具图后跑一次;结果同 get_props 里的 era_audit。' +
2398
+ '★出完或换完道具图后平台已自动审一次(灰度阀开时),先等它落库、看 get_props 的 era_audit,别马上手动再审(重复付费)。' +
2399
+ '本工具用于:改了 era_lock 或道具的镜头绑定之后重审,或 era_audit 迟迟为 null(自动审没跑)时补审;结果同 get_props 里的 era_audit。' +
2359
2400
  '道具还没有图、或既没写 era_lock 也没被绑到带时代契约的镜头上时返回 400(没有可比的依据)。' +
2360
2401
  '只有点名了具体冲突特征才判冲突;笼统的「不像那个年代」不算。', { prop_id: z.number().int().positive() }, async ({ prop_id }) => jsonResult(await client.producePost(`/props/${prop_id}/era-audit`, {})));
2361
- server.tool('create_prop', '往道具库加一个道具。extract_assets 会自动提取道具,这里供手动补建。跨时代剧建道具时就把 era_lock 填上(建完会自动触发出设定图,补填要重出才生效)。免费(建条目;填了 description 会自动触发出设定图、后付)。', {
2402
+ server.tool('create_prop', '往道具库加一个道具。extract_assets 会自动提取道具,这里供手动补建。跨时代剧建道具时就把 era_lock 填上(建完会自动触发出设定图,补填要重出才生效)。本身免费(建条目);填了 description 会自动触发出设定图(后付),填了 description/prompt 还会' + SETTING_LINT_AUTO_HINT + '。' + PROP_IMAGE_AUTO_CHECKS_HINT, {
2362
2403
  drama_id: z.number().int().positive(),
2363
2404
  name: z.string().min(1).describe('道具名'),
2364
2405
  type: z.string().optional().describe('类型(如 武器/信物/家具)'),
@@ -2374,8 +2415,10 @@ export function registerProduceTools(server, client) {
2374
2415
  .describe("道具级时代锁:这件道具属于哪个年代、材质工艺该读作什么年代(跨时代剧必填,否则名字中性的道具年代随机)。'' 清空"),
2375
2416
  episode_id: z.number().int().positive().optional(),
2376
2417
  }, async ({ drama_id, ...fields }) => jsonResult(await client.producePost(`/props`, { drama_id, ...fields })));
2377
- server.tool('update_prop', '改道具(名称/类型/描述/prompt/尺寸/多视角参考图/时代锁)。跨时代剧务必填 era_lock,否则名字中性的道具年代随机。免费。' +
2378
- '★改了 era_lock 不会自动重出道具图——要 generate_prop_sheet 重出,重出后若 get_props 的 era_audit 为 null(还没审过新图),用 audit_prop_era 审一次。', {
2418
+ server.tool('update_prop', '改道具(名称/类型/描述/prompt/尺寸/多视角参考图/时代锁)。跨时代剧务必填 era_lock,否则名字中性的道具年代随机。' +
2419
+ '本身免费;改了 description/prompt(真的变了)' + SETTING_LINT_AUTO_HINT + ';道具还没有图时改 description/prompt 会自动出设定图(后付)。' +
2420
+ '★改了 era_lock 不会自动重出道具图——要 generate_prop_sheet 重出;新图落库后平台会自动做道具外形分析,灰度阀开时还会自动做时代审核,' +
2421
+ '等一会儿再看 get_props 的 era_audit,别马上手动 audit_prop_era(会重复付费);只有 era_audit 迟迟为 null(自动审没跑)时才手动审一次。', {
2379
2422
  prop_id: z.number().int().positive(),
2380
2423
  name: z.string().optional(),
2381
2424
  type: z.string().optional(),