@mindscraft/branch-video-agent-cli 0.4.4 → 0.4.5

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/README.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # Branch Video Agent CLI
2
2
 
3
- `@mindscraft/branch-video-agent-cli` 是一个可脱离当前代码仓库使用的发布版 CLI,用于通过 Bearer agent token 调用 branch-video、AIHub workflow 与 AI Gateway agent API。
3
+ `@mindscraft/branch-video-agent-cli` 是一个可脱离当前代码仓库使用的发布版 CLI,用于通过 Bearer agent token 调用 branch-video、AIHub workflow 与 AI Gateway agent API。
4
+
5
+ ## 图片持久任务(待发布)
6
+
7
+ `ai-gateway image-generate` / `image-edit` 支持 `--async` 提交、`--wait` 等待,stdin JSON 必须含稳定 `idempotencyKey`。无这两个标记时保持同步兼容。
8
+
9
+ ```powershell
10
+ '{"idempotencyKey":"asset-shot09-v1","model":"gpt-image-2","prompt":"原提示词"}' | branch-video-agent ai-gateway image-generate --async --raw 2>receipt.jsonl
11
+ '{"idempotencyKey":"asset-shot09-v1"}' | branch-video-agent ai-gateway image-get --wait --raw
12
+ '{"taskId":"img_..."}' | branch-video-agent ai-gateway image-get --raw
13
+ ```
14
+
15
+ stderr 输出提交前幂等身份、提交后 taskId 回执;stdout 为单个 JSON。提交响应丢失后按同 key 查询或原样重提,不换 key。等待超时返回任务最后状态及 `waitStopped`,Ctrl+C 不取消服务器任务;unknown 需要人工核对,不能自动重新生成。成功结果保留 `data[].url`,URL 需同项目 Bearer 下载再进入现有资产上传流程,不是公开 CDN URL。
16
+
17
+ 完整 API、幂等与发布边界见仓库 `docs/ai-gateway-image-tasks.md`。本地构建验收需设 `BRANCH_VIDEO_AGENT_DISABLE_AUTO_UPDATE=1`,确保运行候选 dist 而非自动切换到 registry 版本;本任务不执行 npm 发布。
4
18
 
5
19
  ## 安装与执行
6
20
 
@@ -94,11 +108,11 @@ sidecar 使用 `branch-video-playtest/1`,Web 结果必须提供 iframe 内的
94
108
 
95
109
  支持 `click`、`fill`、`selectOption`、`check`、`press`、`dragTo`、`setInputFiles` 和 `waitFor`。只要输入中有合法 `reportDir`,静态门禁、契约、版本漂移和浏览器启动等前置失败也会输出 `branch-video-playtest-report.json`、`junit.xml`;失败场景另存截图、trace、视频和浏览器日志。若 Chromium 缺失,执行 `npx playwright install chromium`。
96
110
 
97
- CI 顺序为:V3 静态 self-test → 草稿 `playtest run` → 外部发布步骤 → `version get` 精确内容读回。发布内容与已测草稿一致时无需再跑发布版全量测试;内容变化后应重新测试。`playtest run` 自身始终只读,不承担发布。
98
-
99
- 从 `0.4.3` 起,规划器删除被更长场景完整包含的有序前缀场景,保留条件、状态效果与全部目标边;场景数由实际图决定,不固定为 96。重复搜索状态也会合并以减少规划开销。覆盖或路径预算失败仍返回失败,不通过删边取得通过。复测时记录实际可执行文件路径;升级全局 npm 包不会更新任务内复制的旧 CLI。
100
-
101
- 从 `0.4.4` 起,runner 在现有超时内等待“进入游戏”或“开始体验”入口;已开始播放时直接继续,后续节点和路由断言保持不变。
111
+ CI 顺序为:V3 静态 self-test → 草稿 `playtest run` → 外部发布步骤 → `version get` 精确内容读回。发布内容与已测草稿一致时无需再跑发布版全量测试;内容变化后应重新测试。`playtest run` 自身始终只读,不承担发布。
112
+
113
+ 从 `0.4.3` 起,规划器删除被更长场景完整包含的有序前缀场景,保留条件、状态效果与全部目标边;场景数由实际图决定,不固定为 96。重复搜索状态也会合并以减少规划开销。覆盖或路径预算失败仍返回失败,不通过删边取得通过。复测时记录实际可执行文件路径;升级全局 npm 包不会更新任务内复制的旧 CLI。
114
+
115
+ 从 `0.4.4` 起,runner 在现有超时内等待“进入游戏”或“开始体验”入口;已开始播放时直接继续,后续节点和路由断言保持不变。
102
116
 
103
117
  AIHub 导入公共库示例:
104
118
 
@@ -127,6 +127,58 @@ function normalizeImageEditPayload(payload) {
127
127
  files,
128
128
  };
129
129
  }
130
+ const imageTasksPath = '/api/agent/ai-gateway/images/tasks';
131
+ function validateImageWait(context) {
132
+ if (context.flags.wait && [context.flags.timeoutMs, context.flags.pollIntervalMs].some((value) => !Number.isSafeInteger(value) || value <= 0)) {
133
+ throw new CliCommandError('Image wait timeout and poll interval must be positive integer milliseconds', 'VALIDATION_ERROR');
134
+ }
135
+ }
136
+ async function finishImageTask(context, initial) {
137
+ let task = initial;
138
+ context.runtime.writeStderr(`${JSON.stringify({ event: 'image-task', taskId: task.taskId, idempotencyKey: task.idempotencyKey, status: task.status })}\n`);
139
+ if (!context.flags.wait)
140
+ return { data: task };
141
+ const deadline = context.runtime.now() + context.flags.timeoutMs;
142
+ while (true) {
143
+ if (task.status === 'succeeded') {
144
+ const result = task.result || {};
145
+ return { data: { ...result, data: (result.data || []).map((item) => ({ ...item, url: context.client.resolveUrl(item.url) })),
146
+ taskId: task.taskId, idempotencyKey: task.idempotencyKey, status: task.status } };
147
+ }
148
+ if (task.status === 'failed' || task.status === 'unknown') {
149
+ throw new CliCommandError(task.error?.message || `Image task ${task.status}`, task.status === 'unknown' ? 'RECONCILIATION_REQUIRED' : 'RUN_FAILED', task);
150
+ }
151
+ const remaining = deadline - context.runtime.now();
152
+ if (remaining <= 0)
153
+ return { data: { ...task, waitStopped: 'timeout', message: 'Local wait stopped; query this task to continue. No new submission was made.' } };
154
+ await context.runtime.sleep(Math.min(context.flags.pollIntervalMs, remaining));
155
+ if (context.runtime.now() >= deadline)
156
+ return { data: { ...task, waitStopped: 'timeout', message: 'Local wait stopped; query this task to continue. No new submission was made.' } };
157
+ try {
158
+ task = (await context.client.request(`${imageTasksPath}/${encodeURIComponent(task.taskId)}`, { method: 'GET', timeoutMs: Math.max(1, Math.min(15000, deadline - context.runtime.now())) })).data;
159
+ }
160
+ catch {
161
+ return { data: { ...task, waitStopped: context.runtime.now() >= deadline ? 'timeout' : 'query_error', message: 'Local wait stopped; task status is last observed. Recover using taskId.' } };
162
+ }
163
+ }
164
+ }
165
+ async function submitImage(context, operation) {
166
+ validateImageWait(context);
167
+ const idempotencyKey = getRequiredString(context.payload, 'idempotencyKey');
168
+ if (!/^[A-Za-z0-9._:-]{1,128}$/.test(idempotencyKey))
169
+ throw new CliCommandError('Invalid idempotencyKey', 'VALIDATION_ERROR');
170
+ const payload = omitKeys(context.payload, ['idempotencyKey']);
171
+ context.runtime.writeStderr(`${JSON.stringify({ event: 'image-submit', idempotencyKey, recovery: 'ai-gateway image-get' })}\n`);
172
+ let result;
173
+ try {
174
+ result = await context.client.request(imageTasksPath, { method: 'POST', timeoutMs: 15000,
175
+ body: { idempotencyKey, operation, body: operation === 'edits' ? normalizeImageEditPayload(payload) : payload } });
176
+ }
177
+ catch (error) {
178
+ throw new CliCommandError('Submission response unavailable or rejected; recover by idempotencyKey before any new generation.', 'IMAGE_SUBMIT_ERROR', { idempotencyKey, status: 'submission_unconfirmed', error: error instanceof Error ? error.message : String(error) });
179
+ }
180
+ return finishImageTask(context, result.data);
181
+ }
130
182
  function inferMusicQueryOperation(operation) {
131
183
  switch (operation) {
132
184
  case 'vocal-removal':
@@ -197,20 +249,33 @@ export const aiGatewayCommands = {
197
249
  });
198
250
  return { data: result.data, requestId: result.requestId };
199
251
  },
200
- 'image-generate': async ({ client, payload }) => {
252
+ 'image-generate': async (context) => {
253
+ if (context.flags.async || context.flags.wait)
254
+ return submitImage(context, 'generations');
255
+ const { client, payload } = context;
201
256
  const result = await client.request('/api/agent/ai-gateway/images/generations', {
202
257
  method: 'POST',
203
258
  body: payload,
204
259
  });
205
260
  return { data: result.data, requestId: result.requestId };
206
261
  },
207
- 'image-edit': async ({ client, payload }) => {
262
+ 'image-edit': async (context) => {
263
+ if (context.flags.async || context.flags.wait)
264
+ return submitImage(context, 'edits');
265
+ const { client, payload } = context;
208
266
  const result = await client.request('/api/agent/ai-gateway/images/edits', {
209
267
  method: 'POST',
210
268
  body: normalizeImageEditPayload(payload),
211
269
  });
212
270
  return { data: result.data, requestId: result.requestId };
213
271
  },
272
+ 'image-get': async (context) => {
273
+ validateImageWait(context);
274
+ const taskId = getOptionalString(context.payload, 'taskId');
275
+ const idempotencyKey = taskId ? '' : getRequiredString(context.payload, 'idempotencyKey');
276
+ const result = await context.client.request(taskId ? `${imageTasksPath}/${encodeURIComponent(taskId)}` : imageTasksPath, { method: 'GET', query: idempotencyKey ? { idempotencyKey } : undefined, timeoutMs: 15000 });
277
+ return finishImageTask(context, result.data);
278
+ },
214
279
  'video-create': async (context) => {
215
280
  const result = await context.client.request('/api/agent/ai-gateway/predictions', {
216
281
  method: 'POST',
@@ -3,6 +3,7 @@ import type { CliRuntime } from './runtime.js';
3
3
  export type CliFlags = {
4
4
  raw: boolean;
5
5
  wait: boolean;
6
+ async?: boolean;
6
7
  help: boolean;
7
8
  timeoutMs: number;
8
9
  pollIntervalMs: number;
package/dist/lib/flags.js CHANGED
@@ -58,6 +58,9 @@ export function parseCliArgs(argv) {
58
58
  case '--wait':
59
59
  flags.wait = true;
60
60
  break;
61
+ case '--async':
62
+ flags.async = true;
63
+ break;
61
64
  case '--timeout':
62
65
  flags.timeoutMs = Number(readValue(argv, index, arg));
63
66
  index += 1;
@@ -100,7 +103,8 @@ export function getCliHelpText() {
100
103
  ' agent-production create | status | cancel | finalize | autopilot-binding | events | metrics | multica-comment-payload | node claim|inputs|complete|submit|block|fail|retry|expire | blocker resolve | artifact submit|list | asset-job poll|retry|revise|attach-artifact|fail | gate evaluate|results',
101
104
  ' aihub-production schema | start | status | retry',
102
105
  ' character search | create | get',
103
- ' ai-gateway capabilities | request | media-understand | image-generate | image-edit | video-create | video-get | seed-audio-create | seed-audio-get | music-submit | music-get | tts-create | tts-get',
106
+ ' ai-gateway capabilities | request | media-understand | image-generate | image-edit | image-get | video-create | video-get | seed-audio-create | seed-audio-get | music-submit | music-get | tts-create | tts-get',
107
+ ' Images: --async (or --wait) requires stdin idempotencyKey; image-get accepts taskId or idempotencyKey. Recovery receipts go to stderr; stdout remains one JSON document.',
104
108
  ' playtest run',
105
109
  '',
106
110
  'Config precedence:',
@@ -2,6 +2,7 @@ type RequestOptions = {
2
2
  method: string;
3
3
  body?: unknown;
4
4
  query?: Record<string, unknown>;
5
+ timeoutMs?: number;
5
6
  };
6
7
  type RequestResult<T> = {
7
8
  data: T;
package/dist/lib/http.js CHANGED
@@ -42,6 +42,7 @@ export class CliHttpClient {
42
42
  method: options.method,
43
43
  headers,
44
44
  body: options.body === undefined ? undefined : JSON.stringify(options.body),
45
+ signal: options.timeoutMs ? AbortSignal.timeout(options.timeoutMs) : undefined,
45
46
  });
46
47
  }
47
48
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindscraft/branch-video-agent-cli",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Published CLI for branch-video and AIHub agent APIs.",
5
5
  "type": "module",
6
6
  "bin": {