@pippit-dev/cli 1.0.43 → 1.0.45

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/checksums.txt CHANGED
@@ -1,6 +1,6 @@
1
- e7ee32acef1c0a16912c46500ae7f15c46d920953792d1012886fac7e6424b2e pippit-tool-cli-1.0.43-darwin-amd64.tar.gz
2
- 57e4df86eaa244cd9845eb966299c8c2e1186f74495da0c16f0b378fb4bc5125 pippit-tool-cli-1.0.43-darwin-arm64.tar.gz
3
- 21823c7a675ebcaadebe2ddbfa0324b9e939220d4dd74d5d5d4546f5e6938060 pippit-tool-cli-1.0.43-linux-amd64.tar.gz
4
- e387bc8eaeafa430a9a594ed1cc381964953b598875b520a6d009a12aec9d619 pippit-tool-cli-1.0.43-linux-arm64.tar.gz
5
- 4569d389a20c75f0485ecc24ad5034308a818f294886dabdf07b2804e915f3a2 pippit-tool-cli-1.0.43-windows-amd64.zip
6
- 078fb4fb09353a58095e1370668884664aad1760ae065983bb23abfdcd9939d1 pippit-tool-cli-1.0.43-windows-arm64.zip
1
+ 3802437e93dcd395df47e4e735a8a4b958528f76ea64d672cd3fa3cb719fd163 pippit-tool-cli-1.0.45-darwin-amd64.tar.gz
2
+ b80b030e77b0fbdffb0b05c8a38fdc5c939d01ca69cb1ab6d2c0bd0c1e236892 pippit-tool-cli-1.0.45-darwin-arm64.tar.gz
3
+ 33095e1ceec2220ea1b79abfa56948dd5d237c1831ab2437c8af064d00db7c99 pippit-tool-cli-1.0.45-linux-amd64.tar.gz
4
+ 85a91aeae7b9ea1324a8aee1be576fad12297cecdfe2828851147d50f4dbc177 pippit-tool-cli-1.0.45-linux-arm64.tar.gz
5
+ e93240dbc814104ea278197aff983c2491089be1b9fe55f216007b1a612c7f93 pippit-tool-cli-1.0.45-windows-amd64.zip
6
+ d5f196ea5b5f466911573a1177702ad00f27da153d2f29b5b1e65cd60f7f5100 pippit-tool-cli-1.0.45-windows-arm64.zip
@@ -75,7 +75,7 @@ func (manager *fakeAuthManager) CredentialScope(context.Context) (string, error)
75
75
 
76
76
  func TestLoginCommandPrintsMetadataWithoutAccessKey(t *testing.T) {
77
77
  const accessKey = "must-never-be-printed"
78
- const loginURL = "https://xyq.jianying.com/cli/pippit-tool-login?callback=http%3A%2F%2F127.0.0.1%3A1234&random_secret_key=one-time-secret"
78
+ const loginURL = "https://www.xiaoyunque.com/cli/pippit-tool-login?callback=http%3A%2F%2F127.0.0.1%3A1234&random_secret_key=one-time-secret"
79
79
  expiresAt := time.Now().Add(time.Hour).Unix()
80
80
  manager := &fakeAuthManager{credential: &internal_auth.Credential{
81
81
  AccessKey: accessKey, UID: "123", CredentialScope: "device-scope", ExpiredAt: expiresAt,
@@ -1,6 +1,6 @@
1
- e7ee32acef1c0a16912c46500ae7f15c46d920953792d1012886fac7e6424b2e pippit-tool-cli-1.0.43-darwin-amd64.tar.gz
2
- 57e4df86eaa244cd9845eb966299c8c2e1186f74495da0c16f0b378fb4bc5125 pippit-tool-cli-1.0.43-darwin-arm64.tar.gz
3
- 21823c7a675ebcaadebe2ddbfa0324b9e939220d4dd74d5d5d4546f5e6938060 pippit-tool-cli-1.0.43-linux-amd64.tar.gz
4
- e387bc8eaeafa430a9a594ed1cc381964953b598875b520a6d009a12aec9d619 pippit-tool-cli-1.0.43-linux-arm64.tar.gz
5
- 4569d389a20c75f0485ecc24ad5034308a818f294886dabdf07b2804e915f3a2 pippit-tool-cli-1.0.43-windows-amd64.zip
6
- 078fb4fb09353a58095e1370668884664aad1760ae065983bb23abfdcd9939d1 pippit-tool-cli-1.0.43-windows-arm64.zip
1
+ 3802437e93dcd395df47e4e735a8a4b958528f76ea64d672cd3fa3cb719fd163 pippit-tool-cli-1.0.45-darwin-amd64.tar.gz
2
+ b80b030e77b0fbdffb0b05c8a38fdc5c939d01ca69cb1ab6d2c0bd0c1e236892 pippit-tool-cli-1.0.45-darwin-arm64.tar.gz
3
+ 33095e1ceec2220ea1b79abfa56948dd5d237c1831ab2437c8af064d00db7c99 pippit-tool-cli-1.0.45-linux-amd64.tar.gz
4
+ 85a91aeae7b9ea1324a8aee1be576fad12297cecdfe2828851147d50f4dbc177 pippit-tool-cli-1.0.45-linux-arm64.tar.gz
5
+ e93240dbc814104ea278197aff983c2491089be1b9fe55f216007b1a612c7f93 pippit-tool-cli-1.0.45-windows-amd64.zip
6
+ d5f196ea5b5f466911573a1177702ad00f27da153d2f29b5b1e65cd60f7f5100 pippit-tool-cli-1.0.45-windows-arm64.zip
@@ -96,7 +96,7 @@ func (s *memoryCredentialStore) Delete(ctx context.Context) error {
96
96
  }
97
97
 
98
98
  func TestBrowserFlowUsesDedicatedPageAndStrictBoundCallback(t *testing.T) {
99
- authURL, _ := url.Parse(config.DefaultBaseURL)
99
+ authURL, _ := url.Parse(config.DefaultAuthBaseURL)
100
100
  deviceID, _ := randomEncoded(bytes.NewReader(bytes.Repeat([]byte{0x21}, deviceIDBytes)), deviceIDBytes)
101
101
  flow, err := startBrowserFlow(
102
102
  authURL, bytes.NewReader(bytes.Repeat([]byte{0x42}, 2*randomBindingBytes)),
@@ -111,7 +111,7 @@ func TestBrowserFlowUsesDedicatedPageAndStrictBoundCallback(t *testing.T) {
111
111
  if err != nil {
112
112
  t.Fatal(err)
113
113
  }
114
- if loginURL.Scheme+"://"+loginURL.Host != config.DefaultBaseURL || loginURL.Path != loginPagePath {
114
+ if loginURL.Scheme+"://"+loginURL.Host != config.DefaultAuthBaseURL || loginURL.Path != loginPagePath {
115
115
  t.Fatalf("login URL target = %s://%s%s", loginURL.Scheme, loginURL.Host, loginURL.Path)
116
116
  }
117
117
  query := loginURL.Query()
@@ -152,30 +152,30 @@ func TestBrowserFlowUsesDedicatedPageAndStrictBoundCallback(t *testing.T) {
152
152
  }
153
153
  wrongSecret := payload
154
154
  wrongSecret.RandomSecretKey = "wrong-secret"
155
- if status := sendCallback(t, callbackURL, config.DefaultBaseURL, wrongSecret); status != http.StatusBadRequest {
155
+ if status := sendCallback(t, callbackURL, config.DefaultAuthBaseURL, wrongSecret); status != http.StatusBadRequest {
156
156
  t.Fatalf("wrong secret status = %d", status)
157
157
  }
158
- if status := postRawCallback(t, callbackURL, config.DefaultBaseURL, `{"type":"access_key","unknown":true}`); status != http.StatusBadRequest {
158
+ if status := postRawCallback(t, callbackURL, config.DefaultAuthBaseURL, `{"type":"access_key","unknown":true}`); status != http.StatusBadRequest {
159
159
  t.Fatalf("unknown field status = %d", status)
160
160
  }
161
- if status := sendCallback(t, callbackURL+"&state=duplicate", config.DefaultBaseURL, payload); status != http.StatusBadRequest {
161
+ if status := sendCallback(t, callbackURL+"&state=duplicate", config.DefaultAuthBaseURL, payload); status != http.StatusBadRequest {
162
162
  t.Fatalf("duplicate state status = %d", status)
163
163
  }
164
- if status := sendCallback(t, callbackURL+"&unexpected=value", config.DefaultBaseURL, payload); status != http.StatusBadRequest {
164
+ if status := sendCallback(t, callbackURL+"&unexpected=value", config.DefaultAuthBaseURL, payload); status != http.StatusBadRequest {
165
165
  t.Fatalf("unexpected transport query status = %d", status)
166
166
  }
167
- if status := sendPreflight(t, callbackURL, config.DefaultBaseURL); status != http.StatusNoContent {
167
+ if status := sendPreflight(t, callbackURL, config.DefaultAuthBaseURL); status != http.StatusNoContent {
168
168
  t.Fatalf("preflight status = %d", status)
169
169
  }
170
170
  securityRuntimeCallback := callbackURL + "&a_bogus=signed-request&msToken=transport-token"
171
- if status := sendCallback(t, securityRuntimeCallback, config.DefaultBaseURL, payload); status != http.StatusOK {
171
+ if status := sendCallback(t, securityRuntimeCallback, config.DefaultAuthBaseURL, payload); status != http.StatusOK {
172
172
  t.Fatalf("security-runtime callback status = %d", status)
173
173
  }
174
174
  got, err := flow.wait(context.Background())
175
175
  if err != nil || got.AccessKey != payload.AccessKey || got.UID != payload.UID || got.TokenID != payload.TokenID {
176
176
  t.Fatalf("callback payload = %#v, %v", credentialPayloadWithoutSecret(got), err)
177
177
  }
178
- if status := sendCallback(t, callbackURL, config.DefaultBaseURL, payload); status != http.StatusConflict {
178
+ if status := sendCallback(t, callbackURL, config.DefaultAuthBaseURL, payload); status != http.StatusConflict {
179
179
  t.Fatalf("replayed callback status = %d", status)
180
180
  }
181
181
  }
@@ -185,6 +185,7 @@ func TestManagerLoginStoresPageIssuedCredentialWithoutServerExchange(t *testing.
185
185
  store := &memoryCredentialStore{}
186
186
  cfg := config.Load()
187
187
  cfg.AccessKey = ""
188
+ cfg.BaseURL = "https://untrusted.invalid"
188
189
  manager := NewManager(
189
190
  cfg,
190
191
  WithCredentialStore(store),
@@ -204,6 +205,9 @@ func TestManagerLoginStoresPageIssuedCredentialWithoutServerExchange(t *testing.
204
205
  if parseErr != nil {
205
206
  return parseErr
206
207
  }
208
+ if loginURL.Scheme != "https" || loginURL.Host != "www.xiaoyunque.com" {
209
+ t.Errorf("login origin = %s://%s, want https://www.xiaoyunque.com", loginURL.Scheme, loginURL.Host)
210
+ }
207
211
  if loginURL.Path != loginPagePath || loginURL.Query().Get("force") != "" {
208
212
  return fmt.Errorf("unexpected login URL")
209
213
  }
@@ -218,7 +222,7 @@ func TestManagerLoginStoresPageIssuedCredentialWithoutServerExchange(t *testing.
218
222
  Source: loginSource,
219
223
  CallbackURL: loginURL.Query().Get("callback"),
220
224
  }
221
- if status := sendCallback(t, payload.CallbackURL, config.DefaultBaseURL, payload); status != http.StatusOK {
225
+ if status := sendCallback(t, payload.CallbackURL, loginURL.Scheme+"://"+loginURL.Host, payload); status != http.StatusOK {
222
226
  return fmt.Errorf("callback status %d", status)
223
227
  }
224
228
  return nil
@@ -273,7 +277,7 @@ func TestManagerWaitsForCallbackWhenBrowserOpenFails(t *testing.T) {
273
277
  Source: loginSource,
274
278
  CallbackURL: loginURL.Query().Get("callback"),
275
279
  }
276
- if status := sendCallback(t, payload.CallbackURL, config.DefaultBaseURL, payload); status != http.StatusOK {
280
+ if status := sendCallback(t, payload.CallbackURL, config.DefaultAuthBaseURL, payload); status != http.StatusOK {
277
281
  return fmt.Errorf("callback status %d", status)
278
282
  }
279
283
  return errors.New("browser opener unavailable")
@@ -347,7 +351,7 @@ func TestManagerReauthenticationPinsAccountAndRequiresRotation(t *testing.T) {
347
351
  }
348
352
 
349
353
  func TestBrowserFlowRequiresExpectedAccountForForceAndNeverUsesRawUID(t *testing.T) {
350
- authURL, _ := url.Parse(config.DefaultBaseURL)
354
+ authURL, _ := url.Parse(config.DefaultAuthBaseURL)
351
355
  deviceID, _ := randomEncoded(bytes.NewReader(bytes.Repeat([]byte{0x22}, deviceIDBytes)), deviceIDBytes)
352
356
  if _, err := startBrowserFlow(
353
357
  authURL, bytes.NewReader(bytes.Repeat([]byte{0x42}, 2*randomBindingBytes)),
@@ -378,7 +382,7 @@ func TestBrowserFlowRequiresExpectedAccountForForceAndNeverUsesRawUID(t *testing
378
382
  }
379
383
 
380
384
  func TestBrowserFlowSendsStoredTokenIDOnNormalLogin(t *testing.T) {
381
- authURL, _ := url.Parse(config.DefaultBaseURL)
385
+ authURL, _ := url.Parse(config.DefaultAuthBaseURL)
382
386
  deviceID, _ := randomEncoded(bytes.NewReader(bytes.Repeat([]byte{0x23}, deviceIDBytes)), deviceIDBytes)
383
387
  flow, err := startBrowserFlow(
384
388
  authURL, bytes.NewReader(bytes.Repeat([]byte{0x44}, 2*randomBindingBytes)),
@@ -398,7 +402,7 @@ func TestBrowserFlowSendsStoredTokenIDOnNormalLogin(t *testing.T) {
398
402
  }
399
403
 
400
404
  func TestBrowserFlowAllowsLogoutIdentityTokenWithoutAccountBinding(t *testing.T) {
401
- authURL, _ := url.Parse(config.DefaultBaseURL)
405
+ authURL, _ := url.Parse(config.DefaultAuthBaseURL)
402
406
  deviceID, _ := randomEncoded(bytes.NewReader(bytes.Repeat([]byte{0x24}, deviceIDBytes)), deviceIDBytes)
403
407
  flow, err := startBrowserFlow(
404
408
  authURL, bytes.NewReader(bytes.Repeat([]byte{0x45}, 2*randomBindingBytes)),
@@ -464,7 +468,7 @@ func TestManagerDerivesExpectedAccountFromDurableScope(t *testing.T) {
464
468
  Source: loginSource,
465
469
  CallbackURL: loginURL.Query().Get("callback"),
466
470
  }
467
- if status := sendCallback(t, payload.CallbackURL, config.DefaultBaseURL, payload); status != http.StatusOK {
471
+ if status := sendCallback(t, payload.CallbackURL, config.DefaultAuthBaseURL, payload); status != http.StatusOK {
468
472
  return fmt.Errorf("callback status %d", status)
469
473
  }
470
474
  return nil
@@ -488,7 +492,7 @@ func TestResolveStatusLogoutAndAuthOrigin(t *testing.T) {
488
492
  cfg.AccessKey = " env-ak "
489
493
  cfg.BaseURL = "https://untrusted.invalid"
490
494
  manager := NewManager(cfg, WithCredentialStore(store), withClockForTest(func() time.Time { return fixedNow }))
491
- if manager.authBaseURL.String() != config.DefaultBaseURL {
495
+ if manager.authBaseURL.String() != config.DefaultAuthBaseURL {
492
496
  t.Fatalf("browser login followed runtime business base URL: %q", manager.authBaseURL)
493
497
  }
494
498
  if key, err := manager.ResolveAccessKey(context.Background()); err != nil || key != "env-ak" {
@@ -738,7 +742,7 @@ func callbackOpener(t *testing.T, expiry time.Time, uid, tokenID, accessKey stri
738
742
  Source: loginSource,
739
743
  CallbackURL: loginURL.Query().Get("callback"),
740
744
  }
741
- if status := sendCallback(t, payload.CallbackURL, config.DefaultBaseURL, payload); status != http.StatusOK {
745
+ if status := sendCallback(t, payload.CallbackURL, config.DefaultAuthBaseURL, payload); status != http.StatusOK {
742
746
  return fmt.Errorf("callback status %d", status)
743
747
  }
744
748
  return nil
@@ -16,7 +16,7 @@ import (
16
16
 
17
17
  func newOriginTestFlow(t *testing.T) (*browserFlow, accessKeyPayload) {
18
18
  t.Helper()
19
- authURL, _ := url.Parse(config.DefaultBaseURL)
19
+ authURL, _ := url.Parse(config.DefaultAuthBaseURL)
20
20
  deviceID, _ := randomEncoded(bytes.NewReader(bytes.Repeat([]byte{0x21}, deviceIDBytes)), deviceIDBytes)
21
21
  flow, err := startBrowserFlow(authURL, bytes.NewReader(bytes.Repeat([]byte{0x42}, 2*randomBindingBytes)), deviceID, "", "", false)
22
22
  if err != nil {
@@ -46,7 +46,7 @@ func WithCredentialStore(store CredentialStore) ManagerOption {
46
46
  // API configuration does not affect the browser identity origin.
47
47
  func NewManager(cfg *config.Config, options ...ManagerOption) *Manager {
48
48
  serviceName := config.DefaultAuthStoreServiceName
49
- authBaseURL, _ := url.Parse(config.DefaultBaseURL)
49
+ authBaseURL, _ := url.Parse(config.DefaultAuthBaseURL)
50
50
  manager := &Manager{
51
51
  localCredentialsEnabled: true,
52
52
  cfg: cfg,
@@ -94,12 +94,12 @@ func UploadFile(ctx context.Context, opts UploadFileOptions, runner *Runner) (*U
94
94
  if resp.Errmsg == "" {
95
95
  resp.Errmsg = "未知错误"
96
96
  }
97
- return nil, fmt.Errorf("上传文件请求返回失败: ret=%s errmsg=%s", resp.Ret, resp.Errmsg)
97
+ return nil, NewLogIDError(fmt.Sprintf("上传文件请求返回失败: ret=%s errmsg=%s", resp.Ret, resp.Errmsg), resp.LogID)
98
98
  }
99
99
 
100
100
  assetID := strings.TrimSpace(resp.Data.PippitAssetID)
101
101
  if assetID == "" {
102
- return nil, fmt.Errorf("上传文件响应缺少 pippit_asset_id")
102
+ return nil, NewLogIDError("上传文件响应缺少 pippit_asset_id", resp.LogID)
103
103
  }
104
104
 
105
105
  return &UploadFileResult{
@@ -13,6 +13,7 @@ import (
13
13
 
14
14
  const (
15
15
  DefaultBaseURL = "https://xyq.jianying.com"
16
+ DefaultAuthBaseURL = "https://www.xiaoyunque.com"
16
17
  DefaultHTTPTimeout = 30 * time.Minute
17
18
  DefaultAuthTTL = 30 * time.Second
18
19
  DefaultAuthStoreServiceName = "pippit-cli"
@@ -60,7 +60,7 @@ func Run(ctx context.Context, opts *Options, runner *common.Runner) (*common.Sub
60
60
 
61
61
  imageAssetIDs, err := uploadImageList(ctx, opts.ImagePaths, runner)
62
62
  if err != nil {
63
- return nil, fmt.Errorf("上传图片失败: %w", err)
63
+ return nil, err
64
64
  }
65
65
 
66
66
  body := buildSubmitRunBody(opts, modelKey, imageAssetIDs)
@@ -128,14 +128,14 @@ func SupportedRatioUsage() string {
128
128
 
129
129
  func uploadImageList(ctx context.Context, paths []string, runner *common.Runner) ([]string, error) {
130
130
  assetIDs := make([]string, 0, len(paths))
131
- for _, path := range paths {
131
+ for i, path := range paths {
132
132
  expanded, err := common.ExpandPath(path)
133
133
  if err != nil {
134
- return nil, err
134
+ return nil, fmt.Errorf("第%d张参考图片 %q 路径解析失败: %w", i+1, filepath.Base(path), err)
135
135
  }
136
136
  result, err := common.UploadFile(ctx, common.UploadFileOptions{Path: expanded}, runner)
137
137
  if err != nil {
138
- return nil, err
138
+ return nil, fmt.Errorf("第%d张参考图片 %q 上传失败: %w", i+1, filepath.Base(path), err)
139
139
  }
140
140
  assetIDs = append(assetIDs, result.AssetID)
141
141
  }
@@ -45,17 +45,17 @@ func Run(ctx context.Context, opts *Options, runner *common.Runner) (*Result, er
45
45
  return nil, err
46
46
  }
47
47
 
48
- imageAssetIDs, err := uploadMediaList(ctx, opts.ImagePaths, runner)
48
+ imageAssetIDs, err := uploadMediaList(ctx, "张参考图片", opts.ImagePaths, runner)
49
49
  if err != nil {
50
- return nil, fmt.Errorf("上传图片失败: %w", err)
50
+ return nil, err
51
51
  }
52
- videoAssetIDs, err := uploadMediaList(ctx, opts.VideoPaths, runner)
52
+ videoAssetIDs, err := uploadMediaList(ctx, "个参考视频", opts.VideoPaths, runner)
53
53
  if err != nil {
54
- return nil, fmt.Errorf("上传视频失败: %w", err)
54
+ return nil, err
55
55
  }
56
- audioAssetIDs, err := uploadMediaList(ctx, opts.AudioPaths, runner)
56
+ audioAssetIDs, err := uploadMediaList(ctx, "段参考音频", opts.AudioPaths, runner)
57
57
  if err != nil {
58
- return nil, fmt.Errorf("上传音频失败: %w", err)
58
+ return nil, err
59
59
  }
60
60
 
61
61
  body := buildSubmitRunBody(opts, imageAssetIDs, videoAssetIDs, audioAssetIDs)
@@ -99,16 +99,16 @@ func validateMediaExtensions(kind string, paths []string, allowed map[string]str
99
99
  return nil
100
100
  }
101
101
 
102
- func uploadMediaList(ctx context.Context, paths []string, runner *common.Runner) ([]string, error) {
102
+ func uploadMediaList(ctx context.Context, mediaLabel string, paths []string, runner *common.Runner) ([]string, error) {
103
103
  assetIDs := make([]string, 0, len(paths))
104
- for _, path := range paths {
104
+ for i, path := range paths {
105
105
  expanded, err := common.ExpandPath(path)
106
106
  if err != nil {
107
- return nil, err
107
+ return nil, fmt.Errorf("第%d%s %q 路径解析失败: %w", i+1, mediaLabel, filepath.Base(path), err)
108
108
  }
109
109
  result, err := common.UploadFile(ctx, common.UploadFileOptions{Path: expanded}, runner)
110
110
  if err != nil {
111
- return nil, err
111
+ return nil, fmt.Errorf("第%d%s %q 上传失败: %w", i+1, mediaLabel, filepath.Base(path), err)
112
112
  }
113
113
  assetIDs = append(assetIDs, result.AssetID)
114
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pippit-dev/cli",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
4
4
  "description": "Pippit CLI",
5
5
  "bin": {
6
6
  "pippit-tool-cli": "scripts/run.js"
@@ -58,6 +58,8 @@ metadata:
58
58
  - 用户明确要求生成或处理,即可在该范围内执行;仅咨询用法、费用或方案时不提交。范围、必填信息或消耗 credits 的授权不明确时,先确认,不重复索要已给出的授权。
59
59
  - 提问优先使用宿主实际提供且当前模式允许的工具:Codex 的 `request_user_input` 或 `request_user_input_async`,WorkBuddy 的 `ask_user_question`;不可用时用普通聊天。需要答案时等待答复。
60
60
  - 素材参数接收本地文件路径,CLI 内部上传。远程链接不能冒充本地路径;缺少可访问文件时先解决素材获取。单文件必须小于 500 MB(500000000 字节)。
61
+ - `generate-image` / `generate-video` 上传素材失败时,向用户说明 CLI 返回的素材类型、类型内序号、文件名、原始原因和可用的 LogID,并说明尚未提交生成任务。序号按同类型参数的传入顺序从 1 开始,不是所有附件的混合序号;只有服务端明确返回审核拒绝时才说明审核未通过,不能把网络、权限等失败都归为审核问题。
62
+ - 上传遇到首个失败即停止,后续素材尚未上传,不能宣称它们审核通过。根据失败原因等待用户替换对应素材或解决问题,不自动删减素材、逐个重传排查或重新提交生成。旧 CLI 未返回文件定位时如实说明无法确定具体素材,不凭图片内容猜测。
61
63
  - 提交成功后立即展示真实 `web_thread_link`;未返回链接时如实说明,保留任务 ID。后续查询和下载失败不能触发重复生成。
62
64
  - 每个最终图片/视频都通过宿主文件交付或媒体渲染能力展示为真实附件或可预览媒体。URL、路径列表仅作补充;详细完成标准见共用交付流程。
63
65