ima2-gen 3.13.1 → 3.14.0

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 (157) hide show
  1. package/.env.example +10 -0
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +15 -2
  4. package/bin/commands/capabilities.js +5 -3
  5. package/bin/commands/config.js +15 -4
  6. package/bin/commands/defaults.js +7 -5
  7. package/bin/commands/doctor.js +88 -39
  8. package/bin/commands/gen.js +21 -4
  9. package/bin/commands/models.js +2 -2
  10. package/bin/commands/ping.js +1 -1
  11. package/bin/commands/prompt.js +5 -3
  12. package/bin/commands/service.js +32 -4
  13. package/bin/commands/tools.js +19 -3
  14. package/bin/commands/upscale.js +18 -4
  15. package/bin/commands/video.js +37 -50
  16. package/bin/ima2.js +28 -9
  17. package/bin/lib/characterResolve.js +2 -1
  18. package/bin/lib/client.js +155 -20
  19. package/bin/lib/config-store.js +40 -7
  20. package/bin/lib/doctor-bundle.js +6 -2
  21. package/bin/lib/doctor-checks.js +13 -29
  22. package/bin/lib/doctor-media.js +3 -3
  23. package/bin/lib/doctor-providers.js +48 -35
  24. package/bin/lib/doctor-report.js +78 -0
  25. package/bin/lib/doctor-runtime.js +173 -0
  26. package/bin/lib/mcpJob.js +19 -7
  27. package/bin/lib/output.js +3 -0
  28. package/bin/lib/platform.js +12 -9
  29. package/bin/lib/recover-output.js +9 -2
  30. package/bin/lib/sse.js +9 -1
  31. package/bin/lib/videoMcp.js +16 -8
  32. package/config.js +41 -5
  33. package/docs/API.md +154 -16
  34. package/docs/CLI.md +65 -1
  35. package/docs/DOCKER.md +19 -4
  36. package/docs/DOCKER.zh-CN.md +11 -4
  37. package/docs/DOCKER.zh-TW.md +11 -4
  38. package/docs/FAQ.md +13 -0
  39. package/docs/NPX_QUICKSTART.md +6 -0
  40. package/docs/README.ja.md +15 -2
  41. package/docs/README.ko.md +15 -2
  42. package/docs/README.zh-CN.md +15 -2
  43. package/docs/README.zh-TW.md +15 -2
  44. package/docs/migration/runtime-test-inventory.md +79 -8
  45. package/lib/agyArtifact.js +82 -0
  46. package/lib/agyArtifactRead.js +249 -0
  47. package/lib/agyImageAdapter.js +2 -329
  48. package/lib/agyProcess.js +122 -0
  49. package/lib/apiRequestBudget.js +48 -0
  50. package/lib/assetLifecycle.js +42 -9
  51. package/lib/canvasVersionStore.js +46 -14
  52. package/lib/capabilities.js +7 -1
  53. package/lib/configKeys.js +2 -0
  54. package/lib/errors/providerMap.js +26 -0
  55. package/lib/eventBus.js +10 -6
  56. package/lib/eventsPolicy.js +9 -0
  57. package/lib/geminiApiImageAdapter.js +1 -214
  58. package/lib/generatePipeline.js +39 -165
  59. package/lib/generatedMediaAccess.js +74 -0
  60. package/lib/generationErrors.js +3 -22
  61. package/lib/grokImageAdapter.js +2 -353
  62. package/lib/grokImageCore.js +1 -57
  63. package/lib/grokImageDownload.js +159 -0
  64. package/lib/grokImageDownloadPolicy.js +124 -0
  65. package/lib/grokImagePlanner.js +301 -0
  66. package/lib/grokMultimodeAdapter.js +1 -75
  67. package/lib/grokVideoDownload.js +137 -34
  68. package/lib/inflight.js +112 -112
  69. package/lib/jobs/terminalStore.js +45 -0
  70. package/lib/lanSessionStore.js +131 -0
  71. package/lib/localAccessPolicy.js +180 -0
  72. package/lib/localLanAccess.js +281 -0
  73. package/lib/logger.js +22 -13
  74. package/lib/mcp/downloadMediaResult.js +72 -78
  75. package/lib/multimodePipeline.js +39 -126
  76. package/lib/nodeGeneration.js +44 -101
  77. package/lib/nodeStore.js +25 -11
  78. package/lib/nodeValidation.js +5 -1
  79. package/lib/pinnedHttpGet.js +174 -0
  80. package/lib/promptImport/githubFolder.js +32 -18
  81. package/lib/promptImport/githubSource.js +14 -10
  82. package/lib/promptImport/parsePromptCandidates.js +32 -2
  83. package/lib/providers/adapters/agyOperations.js +179 -0
  84. package/lib/providers/adapters/comfy.js +1 -0
  85. package/lib/providers/adapters/geminiOperations.js +214 -0
  86. package/lib/providers/adapters/googleExecution.js +97 -0
  87. package/lib/providers/adapters/grokExecution.js +137 -0
  88. package/lib/providers/adapters/grokMultimodeOperations.js +83 -0
  89. package/lib/providers/adapters/grokOperations.js +63 -0
  90. package/lib/providers/adapters/openaiExecution.js +118 -0
  91. package/lib/providers/adapters/openaiOperations.js +201 -0
  92. package/lib/providers/adapters/openaiTypes.js +1 -0
  93. package/lib/providers/derive.js +4 -0
  94. package/lib/providers/deriveCore.js +2 -7
  95. package/lib/providers/execution/admission.js +25 -0
  96. package/lib/providers/execution/index.js +34 -0
  97. package/lib/providers/execution/legacy.js +17 -0
  98. package/lib/providers/execution/legacyClassic.js +66 -0
  99. package/lib/providers/execution/legacyEdit.js +30 -0
  100. package/lib/providers/execution/legacyMultimode.js +34 -0
  101. package/lib/providers/execution/legacyNode.js +41 -0
  102. package/lib/providers/execution/types.js +1 -0
  103. package/lib/providers/registry.js +19 -14
  104. package/lib/providers/surfaceSupport.js +22 -0
  105. package/lib/responsesImageAdapter.js +1 -389
  106. package/lib/responsesTransport.js +190 -0
  107. package/lib/spriteJobEvents.js +20 -2
  108. package/lib/ssePublish.js +5 -4
  109. package/lib/videoExtendI2vOperation.js +76 -0
  110. package/package.json +5 -2
  111. package/routes/edit.js +31 -123
  112. package/routes/events.js +140 -63
  113. package/routes/mcpMedia.js +10 -2
  114. package/routes/models.js +28 -25
  115. package/routes/videoExtended.js +9 -68
  116. package/server.js +24 -46
  117. package/ui/dist/.vite/manifest.json +90 -63
  118. package/ui/dist/assets/{AgentWorkspace-PbBytTeV.js → AgentWorkspace-CL3MLv3l.js} +2 -2
  119. package/ui/dist/assets/App-B9cEM6dJ.js +9 -0
  120. package/ui/dist/assets/AssetGenWorkspace-swWPvXrx.js +2 -0
  121. package/ui/dist/assets/AssetsWorkspace-CRVeYU2F.js +1 -0
  122. package/ui/dist/assets/{CardNewsWorkspace-BS3jKOja.js → CardNewsWorkspace-BAOxWLKf.js} +2 -2
  123. package/ui/dist/assets/GenerationRequestLogPanel-DZtYZm-D.js +1 -0
  124. package/ui/dist/assets/HomeWorkspace-BeZ1s0Jm.js +1 -0
  125. package/ui/dist/assets/KeyingPanel-ZyBSXioh.js +1 -0
  126. package/ui/dist/assets/NodeCanvas-BvvcCErf.js +7 -0
  127. package/ui/dist/assets/{PromptBuilderPanel-D97A4pwB.js → PromptBuilderPanel-DBycmShA.js} +2 -2
  128. package/ui/dist/assets/PromptImportDialog-ChvMv7ek.js +2 -0
  129. package/ui/dist/assets/PromptImportDiscoverySection-60py7whH.js +1 -0
  130. package/ui/dist/assets/PromptImportFolderSection-CCRh_-_7.js +1 -0
  131. package/ui/dist/assets/PromptLibraryPanel-BFWQO8OT.js +2 -0
  132. package/ui/dist/assets/SettingsWorkspace-B5Ghsgdj.js +1 -0
  133. package/ui/dist/assets/SpriteRecipeWorkspace-DRWdkmwU.js +1 -0
  134. package/ui/dist/assets/index-7ngEOVxt.js +5 -0
  135. package/ui/dist/assets/index-B5jy7ePg.css +1 -0
  136. package/ui/dist/assets/index-gxh_02DQ.js +22 -0
  137. package/ui/dist/assets/{pptxgen.es-D4pedbwM.js → pptxgen.es-OUNhQLEV.js} +1 -1
  138. package/ui/dist/assets/promptBuilderStore-BTUUzbBT.js +1 -0
  139. package/ui/dist/assets/useAgentDialogFocus-DSc2qY8p.js +1 -0
  140. package/ui/dist/index.html +3 -2
  141. package/ui/dist/assets/AssetGenWorkspace-B-TGlvVb.js +0 -2
  142. package/ui/dist/assets/AssetsWorkspace-LnGaW151.js +0 -1
  143. package/ui/dist/assets/GenerationRequestLogPanel-DuDWD_ic.js +0 -1
  144. package/ui/dist/assets/HomeWorkspace-DVP7UG6O.js +0 -1
  145. package/ui/dist/assets/KeyingPanel-5bRUFQaq.js +0 -1
  146. package/ui/dist/assets/NodeCanvas-BBEpERTb.js +0 -7
  147. package/ui/dist/assets/PromptImportDialog-C89MN5JY.js +0 -2
  148. package/ui/dist/assets/PromptImportDiscoverySection-BgRfDOs2.js +0 -1
  149. package/ui/dist/assets/PromptImportFolderSection-zTMyriT1.js +0 -1
  150. package/ui/dist/assets/PromptLibraryPanel-BD96qZu_.js +0 -2
  151. package/ui/dist/assets/SettingsWorkspace-BAOk3X_8.js +0 -1
  152. package/ui/dist/assets/SpriteRecipeWorkspace-ClIM5QRo.js +0 -1
  153. package/ui/dist/assets/index-0fpfY8vu.css +0 -1
  154. package/ui/dist/assets/index-BFciPGTn.js +0 -5
  155. package/ui/dist/assets/index-BbykeXR9.js +0 -30
  156. package/ui/dist/assets/promptBuilderStore-Do7M8g7J.js +0 -1
  157. package/ui/dist/assets/useAgentDialogFocus-B6-QgEVI.js +0 -1
@@ -9,7 +9,7 @@ ima2-gen运送多级`Dockerfile`(问题#114)。该图像构建了
9
9
  docker build -t ima2-gen .
10
10
  docker run -d --name ima2 \
11
11
  -p 3333:3333 \
12
- -e IMA2_LAN_TOKEN=change-me \
12
+ -e IMA2_LAN_TOKEN \
13
13
  -v ima2-data:/data \
14
14
  ima2-gen
15
15
  ```
@@ -17,11 +17,18 @@ docker run -d --name ima2 \
17
17
  或者使用 compose:
18
18
 
19
19
  ```bash
20
- IMA2_LAN_TOKEN=change-me docker compose up -d
20
+ docker compose up -d
21
21
  ```
22
22
 
23
- 然后打开`http://localhost:3333/?token=change-me`(或将令牌作为
24
- `x-ima2-token`标头打开API来电)。
23
+ 请先在环境中设置自己生成的强随机 `IMA2_LAN_TOKEN`,不要使用示例令牌。
24
+ 然后打开 `http://localhost:3333/`,在工作室登录表单中输入令牌。
25
+ 浏览器使用 HttpOnly 会话访问 API、图片、视频和 SSE;令牌不保存在浏览器存储中。
26
+
27
+ 更改发布端口或使用代理时,设置 `IMA2_PUBLIC_ORIGINS` JSON 数组,
28
+ 例如 `["http://localhost:8080"]`。TLS 代理必须保留 Host/Origin 并转发 Set-Cookie。
29
+ HTTP 不加密凭据,仅适用于可信网络;不可信网络请使用 TLS/VPN。
30
+ 生成文件现需授权且不缓存,但旧缓存和已下载的副本无法撤回。
31
+ 重启会使会话失效,不会删除文件。详见 [访问协议](API.md#local-and-lan-access) 和 [CLI 认证](CLI.md#lan-server-authentication)。
25
32
 
26
33
  ## 为什么`IMA2_LAN_TOKEN`是必须的
27
34
 
@@ -9,7 +9,7 @@ ima2-gen運送多級`Dockerfile`(問題#114)。該圖像建構了
9
9
  docker build -t ima2-gen .
10
10
  docker run -d --name ima2 \
11
11
  -p 3333:3333 \
12
- -e IMA2_LAN_TOKEN=change-me \
12
+ -e IMA2_LAN_TOKEN \
13
13
  -v ima2-data:/data \
14
14
  ima2-gen
15
15
  ```
@@ -17,11 +17,18 @@ docker run -d --name ima2 \
17
17
  或使用 compose:
18
18
 
19
19
  ```bash
20
- IMA2_LAN_TOKEN=change-me docker compose up -d
20
+ docker compose up -d
21
21
  ```
22
22
 
23
- 然後打開`http://localhost:3333/?token=change-me`(或將令牌作為
24
- `x-ima2-token`標頭打開API來電)。
23
+ 請先在環境中設定自行產生的強隨機 `IMA2_LAN_TOKEN`,不要使用範例權杖。
24
+ 然後開啟 `http://localhost:3333/`,在工作室登入表單輸入權杖。
25
+ 瀏覽器使用 HttpOnly 工作階段存取 API、圖片、影片與 SSE;權杖不會存入瀏覽器儲存空間。
26
+
27
+ 變更發布連接埠或使用代理時,設定 `IMA2_PUBLIC_ORIGINS` JSON 陣列,
28
+ 例如 `["http://localhost:8080"]`。TLS 代理須保留 Host/Origin 並轉送 Set-Cookie。
29
+ HTTP 不會加密憑證,僅適用可信網路;不可信網路請使用 TLS/VPN。
30
+ 生成檔案現在需要授權且不快取,但無法撤回舊快取或已下載的副本。
31
+ 重新啟動會使工作階段失效,不會刪除檔案。詳見 [存取協定](API.md#local-and-lan-access) 與 [CLI 認證](CLI.md#lan-server-authentication)。
25
32
 
26
33
  ## 為什麼`IMA2_LAN_TOKEN`是必須的
27
34
 
package/docs/FAQ.md CHANGED
@@ -71,6 +71,19 @@ again.
71
71
 
72
72
  ## Authentication and providers
73
73
 
74
+ ### Why does LAN access now show a studio sign-in page or return 401 for media?
75
+
76
+ LAN APIs and generated images/videos now share an authenticated session. Open the
77
+ studio and enter its configured LAN token; CLI users set `IMA2_LAN_TOKEN` and an
78
+ explicit `--server` / `IMA2_SERVER`. This is separate from provider login. Sessions
79
+ expire after eight hours or server restart. Reconnect does not replay generation,
80
+ and sign-out does not cancel accepted work. No generated files are deleted.
81
+
82
+ If a Docker port or proxy address differs, configure its exact
83
+ `IMA2_PUBLIC_ORIGINS` value. HTTP requires a trusted network; use TLS/VPN otherwise.
84
+ Old downloaded/cached copies cannot be revoked. See the [access contract](API.md#local-and-lan-access)
85
+ and [CLI setup](CLI.md#lan-server-authentication).
86
+
74
87
  ### Do I need an OpenAI API key?
75
88
 
76
89
  No for the default generation path. The normal path uses your local Codex/ChatGPT OAuth session.
@@ -1,5 +1,7 @@
1
1
  # npx Quickstart
2
2
 
3
+ The package engine requirement applies to npx too; source `.ts` files require the normal build before emitted `.js` files are run.
4
+
3
5
  > The recommended install method is `npm install -g ima2-gen`. This page covers
4
6
  > the alternative `npx` workflow for one-shot usage without a global install.
5
7
 
@@ -11,6 +13,10 @@ npx ima2-gen serve
11
13
 
12
14
  Then open `http://localhost:3333`.
13
15
 
16
+ To check the installed package without starting a server or reading provider
17
+ credentials, run `npx ima2-gen doctor --installation --json`. A failed local
18
+ prerequisite exits1; missing provider credentials are not installation failures.
19
+
14
20
  If ChatGPT OAuth is not logged in yet:
15
21
 
16
22
  ```bash
package/docs/README.ja.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # ima2-gen
2
2
 
3
+ <!-- runtime-install:generated:start -->
4
+ | Contract | Value |
5
+ |---|---|
6
+ | Node engine | `>=22` |
7
+ | npm toolchain | `npm@11.18.0` |
8
+ | Release Node | `24.17.0` |
9
+ | CLI entry | `bin/ima2.js` |
10
+ | OpenAI SDK | `^7.4.0` |
11
+ | Express | `^5.1.0` |
12
+ <!-- runtime-install:generated:end -->
13
+
14
+ インストーラーはパッケージメタデータから Node.js の最低バージョンを読み、サーバー起動前にオフライン検査を行います。
15
+
3
16
  [![npm version](https://img.shields.io/npm/v/ima2-gen)](https://www.npmjs.com/package/ima2-gen)
4
17
  [![Node.js](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org/)
5
18
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../LICENSE)
@@ -54,7 +67,7 @@ irm https://lidge-jun.github.io/ima2-gen/install-windows.ps1 | iex
54
67
  curl -fsSL https://lidge-jun.github.io/ima2-gen/install-linux.sh | bash
55
68
  ```
56
69
 
57
- 各スクリプトは nvm/fnm/brew/winget を確認し、最適な方法で Node LTS を入れ、古いプロセスのクリーンアップも自動で行います。
70
+ 各スクリプトは対応する Node バージョンを確認し、必要に応じて nvm/fnm/brew/winget で Node LTS を入れます。npm のインストールは一度だけ試し、`ima2 doctor --installation --json` が成功してからサーバーを起動します。プロセスの強制終了やグローバルロックの削除は行いません。
58
71
 
59
72
  ### セットアップ
60
73
 
@@ -75,7 +88,7 @@ curl -fsSL https://lidge-jun.github.io/ima2-gen/install-linux.sh | bash
75
88
  npm install -g ima2-gen@latest
76
89
  ```
77
90
 
78
- Ctrl+C は DB クローズ、子プロセス停止、ファイルロック解放まで行うクリーンシャットダウンです。古いバージョン(< 1.1.22)や Windows で `EBUSY` が出る場合は、インストールスクリプトが stale プロセスを自動クリーンアップします。
91
+ Ctrl+C は DB クローズ、子プロセス停止、ファイルロック解放まで行うクリーンシャットダウンです。インストールに失敗した場合は表示された npm 権限を確認するか、対象の `ima2` プロセスを手動で停止してください。インストーラーは無関係なプロセスやグローバルロックを操作しません。
79
92
 
80
93
  ## できること
81
94
 
package/docs/README.ko.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # ima2-gen
2
2
 
3
+ <!-- runtime-install:generated:start -->
4
+ | Contract | Value |
5
+ |---|---|
6
+ | Node engine | `>=22` |
7
+ | npm toolchain | `npm@11.18.0` |
8
+ | Release Node | `24.17.0` |
9
+ | CLI entry | `bin/ima2.js` |
10
+ | OpenAI SDK | `^7.4.0` |
11
+ | Express | `^5.1.0` |
12
+ <!-- runtime-install:generated:end -->
13
+
14
+ 설치 프로그램은 패키지 메타데이터에서 Node.js 최소 버전을 읽고 서버 시작 전에 오프라인 설치 검사를 수행합니다.
15
+
3
16
  [![npm version](https://img.shields.io/npm/v/ima2-gen)](https://www.npmjs.com/package/ima2-gen)
4
17
  [![Node.js](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org/)
5
18
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../LICENSE)
@@ -62,7 +75,7 @@ irm https://lidge-jun.github.io/ima2-gen/install-windows.ps1 | iex
62
75
  curl -fsSL https://lidge-jun.github.io/ima2-gen/install-linux.sh | bash
63
76
  ```
64
77
 
65
- 각 스크립트가 nvm/fnm/brew/winget을 감지하고, 없으면 Node LTS를 자동 설치한 뒤 ima2-gen을 설치합니다. npm 12 승인 옵션과 잔여 프로세스 정리를 자동 처리하고, 마지막에 `ima2 doctor`로 native runtime도 확인합니다.
78
+ 각 스크립트는 지원하는 Node 버전을 확인하고, 필요하면 nvm/fnm/brew/winget으로 Node LTS를 설치합니다. npm 설치는 한 번만 시도하며, `ima2 doctor --installation --json`을 통과해야 서버를 시작합니다. 다른 프로세스를 종료하거나 전역 잠금 파일을 지우지는 않습니다.
66
79
 
67
80
  ### 업데이트
68
81
 
@@ -74,7 +87,7 @@ npm install -g ima2-gen@latest
74
87
 
75
88
  npm 12라면 업데이트 명령에도 `--allow-scripts=ima2-gen,better-sqlite3,sharp`를 붙이세요. 원클릭 설치 스크립트는 npm 버전을 감지해 자동으로 처리합니다.
76
89
 
77
- v1.1.22부터 Ctrl+C가 DB, 소켓, 자식 프로세스를 깨끗하게 정리합니다. 이전 버전이거나 Windows에서 `EBUSY` 에러가 나면 위의 설치 스크립트를 다시 실행하세요 — 잔여 프로세스를 자동으로 정리합니다.
90
+ Ctrl+C로 DB, 소켓, 자식 프로세스를 정리할 수 있습니다. Windows에서 `EBUSY`가 나면 실행 중인 해당 ima2 서버를 직접 종료한 뒤 다시 설치하세요. 설치기는 프로세스를 임의로 종료하거나 관리자 권한으로 재시도하지 않습니다.
78
91
 
79
92
  ## 최근 주요 변경
80
93
 
@@ -1,5 +1,18 @@
1
1
  # ima2-gen
2
2
 
3
+ <!-- runtime-install:generated:start -->
4
+ | Contract | Value |
5
+ |---|---|
6
+ | Node engine | `>=22` |
7
+ | npm toolchain | `npm@11.18.0` |
8
+ | Release Node | `24.17.0` |
9
+ | CLI entry | `bin/ima2.js` |
10
+ | OpenAI SDK | `^7.4.0` |
11
+ | Express | `^5.1.0` |
12
+ <!-- runtime-install:generated:end -->
13
+
14
+ 安装程序从包元数据读取 Node.js 最低版本,并在启动服务器前执行离线安装检查。
15
+
3
16
  <p align="center">
4
17
  <img src="../assets/logo.png" alt="ima2-gen logo" width="240">
5
18
  </p>
@@ -75,7 +88,7 @@ irm https://lidge-jun.github.io/ima2-gen/install-windows.ps1 | iex
75
88
  curl -fsSL https://lidge-jun.github.io/ima2-gen/install-linux.sh | bash
76
89
  ```
77
90
 
78
- 每个脚本都会检查 nvm/fnm/brew/winget,通过最佳可用方法安装 Node LTS,并自动处理过时进程清理。
91
+ 每个脚本都会检查支持的 Node 版本,必要时使用 nvm/fnm/brew/winget 安装 Node LTS。npm 安装只尝试一次,`ima2 doctor --installation --json` 通过后才启动服务器。脚本不会终止其他进程或删除全局锁文件。
79
92
 
80
93
  ### 设置
81
94
 
@@ -96,7 +109,7 @@ curl -fsSL https://lidge-jun.github.io/ima2-gen/install-linux.sh | bash
96
109
  npm install -g ima2-gen@latest
97
110
  ```
98
111
 
99
- Ctrl+C 现在执行干净关闭 — 关闭数据库、停止子进程并释放文件锁。在旧版本 (< 1.1.22) 上或者如果您看到`EBUSY`在 Windows 上,使用自动处理过时进程清理的安装脚本。
112
+ Ctrl+C 会关闭数据库、停止子进程并释放文件锁。如果 Windows 报告 `EBUSY`,请先手动停止对应的 ima2 服务再重试安装。安装器不会自动终止进程或提升权限。
100
113
 
101
114
  ## 它的作用
102
115
 
@@ -1,5 +1,18 @@
1
1
  # ima2-gen
2
2
 
3
+ <!-- runtime-install:generated:start -->
4
+ | Contract | Value |
5
+ |---|---|
6
+ | Node engine | `>=22` |
7
+ | npm toolchain | `npm@11.18.0` |
8
+ | Release Node | `24.17.0` |
9
+ | CLI entry | `bin/ima2.js` |
10
+ | OpenAI SDK | `^7.4.0` |
11
+ | Express | `^5.1.0` |
12
+ <!-- runtime-install:generated:end -->
13
+
14
+ 安裝程式從套件中繼資料讀取 Node.js 最低版本,並在啟動伺服器前執行離線安裝檢查。
15
+
3
16
  <p align="center">
4
17
  <img src="../assets/logo.png" alt="ima2-gen logo" width="240">
5
18
  </p>
@@ -75,7 +88,7 @@ irm https://lidge-jun.github.io/ima2-gen/install-windows.ps1 | iex
75
88
  curl -fsSL https://lidge-jun.github.io/ima2-gen/install-linux.sh | bash
76
89
  ```
77
90
 
78
- 每個腳本都會檢查 nvm、fnm、Homebrew 或 winget,選擇最適合的方法安裝 Node LTS,並自動清理殘留的舊程序。
91
+ 每個腳本都會檢查支援的 Node 版本,必要時使用 nvm、fnm、Homebrew 或 winget 安裝 Node LTS。npm 安裝只嘗試一次,通過 `ima2 doctor --installation --json` 後才啟動伺服器。腳本不會終止其他程序或刪除全域鎖定檔案。
79
92
 
80
93
  ### 設定
81
94
 
@@ -96,7 +109,7 @@ curl -fsSL https://lidge-jun.github.io/ima2-gen/install-linux.sh | bash
96
109
  npm install -g ima2-gen@latest
97
110
  ```
98
111
 
99
- 現在按下 Ctrl+C 會正常關閉程式:關閉資料庫、停止子程序並釋放檔案鎖定。如果使用舊版(< 1.1.22),或在 Windows 上看到 `EBUSY`,請改用會自動清理殘留程序的安裝腳本。
112
+ 按下 Ctrl+C 會關閉資料庫、停止子程序並釋放檔案鎖定。如果 Windows 顯示 `EBUSY`,請先手動停止對應的 ima2 服務,再重新安裝。安裝器不會自動終止程序或提高權限。
100
113
 
101
114
  ## 功能總覽
102
115
 
@@ -4,7 +4,7 @@ Generated by `npm run test:inventory` (script: `scripts/classify-tests.mjs`).
4
4
 
5
5
  _Tests considered "runtime-importing" if they import from `../lib/`, `../routes/`, `../bin/`, `../server`, or `../config`._
6
6
 
7
- Total: 416 (runtime: 191, contract: 225)
7
+ Total: 487 (runtime: 224, contract: 263)
8
8
 
9
9
  ## Runtime-importing tests
10
10
  - `tests/agent-image-reference-contract.test.ts`
@@ -19,13 +19,18 @@ Total: 416 (runtime: 191, contract: 225)
19
19
  - `tests/agent-queue-store-contract.test.ts`
20
20
  - `tests/agent-video-intent.test.ts`
21
21
  - `tests/agent-video-reference-contract.test.ts`
22
+ - `tests/agy-artifact-confinement.test.ts`
22
23
  - `tests/agy-artifact-fallback.test.ts`
23
24
  - `tests/agy-cli.test.ts`
25
+ - `tests/agy-execution-cleanup.test.ts`
26
+ - `tests/agy-execution-process.test.ts`
24
27
  - `tests/api-cache-policy.test.ts`
25
28
  - `tests/api-provider-parity.test.ts`
29
+ - `tests/api-request-budget.test.ts`
26
30
  - `tests/asset-character-bindings.test.ts`
27
31
  - `tests/asset-derived-vector-contract.test.ts`
28
32
  - `tests/asset-derived.test.ts`
33
+ - `tests/asset-lifecycle-path.test.ts`
29
34
  - `tests/asset-ref-contract.test.ts`
30
35
  - `tests/assets-routes-contract.test.ts`
31
36
  - `tests/assets-store-contract.test.ts`
@@ -34,12 +39,14 @@ Total: 416 (runtime: 191, contract: 225)
34
39
  - `tests/backend-input-lan-hardening.test.ts`
35
40
  - `tests/background-presets.test.ts`
36
41
  - `tests/billing-source.test.ts`
42
+ - `tests/canvas-version-normalization.test.ts`
37
43
  - `tests/capabilities-lane-contract.test.ts`
38
44
  - `tests/capabilities-video-modes-contract.test.ts`
39
45
  - `tests/card-news-contract.test.ts`
40
46
  - `tests/card-news-template.test.ts`
41
47
  - `tests/classic-generate-async.test.ts`
42
48
  - `tests/cli-error-hints.test.ts`
49
+ - `tests/cli-lan-auth.test.ts`
43
50
  - `tests/cli-lib.test.ts`
44
51
  - `tests/cli-model-resolver.test.ts`
45
52
  - `tests/cli-models-command-contract.test.ts`
@@ -54,22 +61,29 @@ Total: 416 (runtime: 191, contract: 225)
54
61
  - `tests/contracts-catalog.test.ts`
55
62
  - `tests/contracts-discovery.test.ts`
56
63
  - `tests/doctor-provider-contract.test.ts`
64
+ - `tests/doctor-report.test.ts`
65
+ - `tests/doctor-runtime.test.ts`
57
66
  - `tests/element-compiler.test.ts`
58
67
  - `tests/element-metadata.test.ts`
59
68
  - `tests/error-class-coverage.test.ts`
60
69
  - `tests/error-classify.test.ts`
61
70
  - `tests/error-envelope-contract.test.ts`
62
71
  - `tests/event-bus.test.ts`
72
+ - `tests/events-backpressure.test.ts`
63
73
  - `tests/events-channel-contract.test.ts`
64
74
  - `tests/filename.test.ts`
65
- - `tests/gemini-api-wire-contract.test.ts`
66
75
  - `tests/generate-route-validation-error.test.ts`
67
76
  - `tests/generated-static-privacy.test.ts`
68
77
  - `tests/generation-errors.test.ts`
69
78
  - `tests/generation-request-log.test.ts`
79
+ - `tests/google-execution-parity.test.ts`
70
80
  - `tests/gpt56-rollout-contract.test.ts`
71
81
  - `tests/grok-advertise-liveness-contract.test.ts`
72
82
  - `tests/grok-command-login-contract.test.ts`
83
+ - `tests/grok-execution-parity.test.ts`
84
+ - `tests/grok-image-address-policy.test.ts`
85
+ - `tests/grok-image-download-pinning.test.ts`
86
+ - `tests/grok-image-download-policy.test.ts`
73
87
  - `tests/grok-planner-adapter.test.ts`
74
88
  - `tests/grok-planner-config-route.test.ts`
75
89
  - `tests/grok-proxy-launcher.test.ts`
@@ -78,6 +92,7 @@ Total: 416 (runtime: 191, contract: 225)
78
92
  - `tests/grok-size-mapper.test.ts`
79
93
  - `tests/grok-upstream-retry.test.ts`
80
94
  - `tests/grokVideoAdapter.test.ts`
95
+ - `tests/grokVideoDownload.test.ts`
81
96
  - `tests/grokVideoPlannerFallback.test.ts`
82
97
  - `tests/grokVideoTimeoutBudgets.test.ts`
83
98
  - `tests/history-metadata-fallback.test.ts`
@@ -89,11 +104,15 @@ Total: 416 (runtime: 191, contract: 225)
89
104
  - `tests/inflight-guard-contract.test.ts`
90
105
  - `tests/inflight-persistence.test.ts`
91
106
  - `tests/inflight.test.ts`
107
+ - `tests/job-cancellation-terminal.test.ts`
92
108
  - `tests/job-envelope-contract.test.ts`
93
109
  - `tests/job-envelope-terminal-coverage.test.ts`
94
110
  - `tests/job-idempotency.test.ts`
95
111
  - `tests/job-terminal-status-contract.test.ts`
112
+ - `tests/lan-media-access.test.ts`
113
+ - `tests/lan-session.test.ts`
96
114
  - `tests/local-import-contract.test.ts`
115
+ - `tests/local-lan-policy.test.ts`
97
116
  - `tests/logging.test.ts`
98
117
  - `tests/mcp-character-route.test.ts`
99
118
  - `tests/mcp-connection-manager.test.ts`
@@ -131,6 +150,7 @@ Total: 416 (runtime: 191, contract: 225)
131
150
  - `tests/nai-zip-decode.test.ts`
132
151
  - `tests/node-parent-source-contract.test.ts`
133
152
  - `tests/node-route-refs.test.ts`
153
+ - `tests/node-store-metadata.test.ts`
134
154
  - `tests/node-streaming-sse.test.ts`
135
155
  - `tests/node-template-contract.test.ts`
136
156
  - `tests/node-validation-error-contract.test.ts`
@@ -138,19 +158,30 @@ Total: 416 (runtime: 191, contract: 225)
138
158
  - `tests/oauth-normalize.test.ts`
139
159
  - `tests/oauth-proxy-error-safety.test.ts`
140
160
  - `tests/open-directory.test.ts`
161
+ - `tests/openai-execution-parity.test.ts`
162
+ - `tests/openai-transport-parity.test.ts`
141
163
  - `tests/preset-compiler.test.ts`
142
164
  - `tests/preset-restore-contract.test.ts`
143
165
  - `tests/preset-xmp-roundtrip.test.ts`
144
166
  - `tests/prompt-builder-contract.test.ts`
167
+ - `tests/prompt-candidates-normalization.test.ts`
145
168
  - `tests/prompt-discovery-contract.test.ts`
146
169
  - `tests/prompt-fidelity.test.ts`
147
170
  - `tests/prompt-import-folder-contract.test.ts`
148
171
  - `tests/prompt-import-github-contract.test.ts`
149
172
  - `tests/prompt-index-ranking-contract.test.ts`
150
173
  - `tests/provider-adapter-v1-contract.test.ts`
174
+ - `tests/provider-execution-boundary.test.ts`
175
+ - `tests/provider-execution-classic.test.ts`
176
+ - `tests/provider-execution-multimode.test.ts`
177
+ - `tests/provider-execution-node.test.ts`
178
+ - `tests/provider-execution-routes.test.ts`
151
179
  - `tests/provider-registry-contract.test.ts`
152
180
  - `tests/provider-registry-parity.test.ts`
181
+ - `tests/provider-surface-boundary.test.ts`
182
+ - `tests/provider-surface-support.test.ts`
153
183
  - `tests/reference-image-compress.test.ts`
184
+ - `tests/reference-limits-node-recovery.test.mjs`
154
185
  - `tests/refs-size.test.ts`
155
186
  - `tests/request-logging.test.ts`
156
187
  - `tests/responses-adapter-safety.test.ts`
@@ -170,6 +201,7 @@ Total: 416 (runtime: 191, contract: 225)
170
201
  - `tests/sprite-atlas-manifest.test.ts`
171
202
  - `tests/sprite-curation-store.test.ts`
172
203
  - `tests/sprite-gif-export.test.ts`
204
+ - `tests/sprite-job-events.test.ts`
173
205
  - `tests/sprite-layout-guide.test.ts`
174
206
  - `tests/sprite-recipe-routes.test.ts`
175
207
  - `tests/sprite-recipe-store.test.ts`
@@ -186,6 +218,7 @@ Total: 416 (runtime: 191, contract: 225)
186
218
  - `tests/vectorize-image-contract.test.ts`
187
219
  - `tests/video-chroma-key.test.ts`
188
220
  - `tests/video-concat.test.ts`
221
+ - `tests/video-download-cancellation.test.ts`
189
222
  - `tests/video-history-item.test.ts`
190
223
  - `tests/video-motion-presets.test.ts`
191
224
  - `tests/video-ref2v-duration-contract.test.ts`
@@ -210,6 +243,8 @@ Total: 416 (runtime: 191, contract: 225)
210
243
  - `tests/agent-mode-streaming-a11y-contract.test.js`
211
244
  - `tests/agent-mode-tool-call-rendering-contract.test.js`
212
245
  - `tests/agent-mode-tool-folding-contract.test.js`
246
+ - `tests/agy-artifact-check-contract.test.ts`
247
+ - `tests/agy-artifact-read-bounds.test.ts`
213
248
  - `tests/api-docs-contract.test.js`
214
249
  - `tests/app-weight-splitting-contract.test.js`
215
250
  - `tests/asset-element-toggle-contract.test.ts`
@@ -260,6 +295,7 @@ Total: 416 (runtime: 191, contract: 225)
260
295
  - `tests/card-news-smoke.test.js`
261
296
  - `tests/card-news-visible-text-language-contract.test.ts`
262
297
  - `tests/character-binding-ui-contract.test.ts`
298
+ - `tests/ci-windows-candidate.test.ts`
263
299
  - `tests/cli-capabilities-contract.test.js`
264
300
  - `tests/cli-character-contract.test.js`
265
301
  - `tests/cli-commands.test.js`
@@ -274,12 +310,13 @@ Total: 416 (runtime: 191, contract: 225)
274
310
  - `tests/cli-prompt-builder-contract.test.js`
275
311
  - `tests/cli-prompt-import-contract.test.js`
276
312
  - `tests/cli-skill-command-contract.test.js`
313
+ - `tests/cli-unknown-options.test.ts`
277
314
  - `tests/cli-upscale-contract.test.js`
278
315
  - `tests/cli-video-command-contract.test.js`
279
316
  - `tests/color-key.test.ts`
280
317
  - `tests/comfy-cli-contract.test.ts`
318
+ - `tests/comfy-display.test.ts`
281
319
  - `tests/comfy-export-ui-contract.test.js`
282
- - `tests/comfy-selection-persistence.test.js`
283
320
  - `tests/comfy-ui-contract.test.ts`
284
321
  - `tests/comfyui-custom-node-contract.test.js`
285
322
  - `tests/composer-feedback-contract.test.js`
@@ -287,14 +324,25 @@ Total: 416 (runtime: 191, contract: 225)
287
324
  - `tests/composer-tray-ui-contract.test.js`
288
325
  - `tests/config.test.js`
289
326
  - `tests/contract-docs-projection.test.ts`
327
+ - `tests/core-generation-mode.test.ts`
328
+ - `tests/core-selection-actions.test.ts`
329
+ - `tests/core-selection-memory.test.ts`
330
+ - `tests/core-selection-reconcile.test.ts`
331
+ - `tests/core-selection-transport.test.mjs`
290
332
  - `tests/current-image-actions-readiness-contract.test.js`
291
333
  - `tests/direct-mode-visual-contract.test.js`
292
334
  - `tests/duration-slider-contract.test.js`
335
+ - `tests/e2e-app-environment.test.ts`
336
+ - `tests/e2e-runtime-build.test.mjs`
337
+ - `tests/e2e-runtime-guards.test.mjs`
293
338
  - `tests/edit-mask-api-contract.test.js`
294
339
  - `tests/element-mention-ui-contract.test.js`
295
340
  - `tests/error-ui-consumption.test.ts`
341
+ - `tests/event-channel-replay-gap.test.ts`
342
+ - `tests/execution-network-isolation.test.ts`
343
+ - `tests/execution-process-isolation.test.ts`
296
344
  - `tests/frame-extraction-fallback.test.ts`
297
- - `tests/frontend-connection-state-contract.test.js`
345
+ - `tests/frontend-connection-state-contract.test.ts`
298
346
  - `tests/frontend-sse-risk-contract.test.js`
299
347
  - `tests/gallery-hang-regression-contract.test.ts`
300
348
  - `tests/gallery-load-older-contract.test.js`
@@ -304,6 +352,8 @@ Total: 416 (runtime: 191, contract: 225)
304
352
  - `tests/gallery-shortcuts-behavior.test.js`
305
353
  - `tests/gallery-shortcuts-visible-domain-contract.test.js`
306
354
  - `tests/gallery-viewer-navigation-contract.test.js`
355
+ - `tests/gemini-api-wire-contract.test.ts`
356
+ - `tests/gemini-transport-parity.test.ts`
307
357
  - `tests/gen-output-path-contract.test.js`
308
358
  - `tests/generation-controls-ux-contract.test.js`
309
359
  - `tests/generation-limit-unlock-contract.test.js`
@@ -311,7 +361,7 @@ Total: 416 (runtime: 191, contract: 225)
311
361
  - `tests/health.test.js`
312
362
  - `tests/history-index-contract.test.js`
313
363
  - `tests/history-permanent-delete.test.js`
314
- - `tests/history-strip-duplicate-contract.test.js`
364
+ - `tests/history-strip-duplicate-contract.test.ts`
315
365
  - `tests/i18n-coverage-contract.test.ts`
316
366
  - `tests/i18n-dictionary-contract.test.ts`
317
367
  - `tests/image-metadata-ui-contract.test.js`
@@ -319,14 +369,23 @@ Total: 416 (runtime: 191, contract: 225)
319
369
  - `tests/inflight-cancel-contract.test.js`
320
370
  - `tests/inflight-list-tooltip-contract.test.js`
321
371
  - `tests/inflight-popup-polish-contract.test.js`
322
- - `tests/inflight-reload-race.test.js`
323
- - `tests/inflight-reload-reconcile-contract.test.js`
372
+ - `tests/inflight-reconciliation-behavior.test.ts`
373
+ - `tests/inflight-reload-race.test.ts`
374
+ - `tests/inflight-reload-reconcile-contract.test.ts`
375
+ - `tests/install-runtime-contract.test.ts`
324
376
  - `tests/install-windows-contract.test.js`
325
377
  - `tests/issue75-prompt-studio-state-contract.test.js`
326
378
  - `tests/issue77-long-prompt-preview-contract.test.js`
379
+ - `tests/j6-isolation-preflight.test.mjs`
380
+ - `tests/job-tracking-ci-contract.test.ts`
381
+ - `tests/job-tracking-timeout-ui.test.ts`
382
+ - `tests/lan-config.test.ts`
383
+ - `tests/lane-catalog.test.ts`
327
384
  - `tests/mcp-media-kind-behavior.test.ts`
328
385
  - `tests/mcp-provider-smoke.test.ts`
329
386
  - `tests/mcp-provider-ui-contract.test.js`
387
+ - `tests/mcp-readiness-observation.test.ts`
388
+ - `tests/mcp-readiness.test.ts`
330
389
  - `tests/mcp-schema-spike-contract.test.ts`
331
390
  - `tests/mcp-selection-helpers.test.ts`
332
391
  - `tests/mcp-settings-states-contract.test.ts`
@@ -367,8 +426,11 @@ Total: 416 (runtime: 191, contract: 225)
367
426
  - `tests/oauth-proxy-edit-mask-contract.test.js`
368
427
  - `tests/package-global-update-smoke-contract.test.ts`
369
428
  - `tests/package-smoke.test.js`
429
+ - `tests/pages-publication-contract.test.ts`
430
+ - `tests/platform-open-url.test.ts`
370
431
  - `tests/png-info-contract.test.js`
371
432
  - `tests/portal-dropdown-scroll-dismiss-contract.test.ts`
433
+ - `tests/pr-fast-history.test.mjs`
372
434
  - `tests/prompt-curated-search-contract.test.js`
373
435
  - `tests/prompt-discovery-ui-contract.test.js`
374
436
  - `tests/prompt-import-dialog-ui-contract.test.js`
@@ -380,10 +442,15 @@ Total: 416 (runtime: 191, contract: 225)
380
442
  - `tests/provenance-chip-contract.test.ts`
381
443
  - `tests/provider-canary-live-contract.test.ts`
382
444
  - `tests/provider-canary-parity.test.ts`
445
+ - `tests/provider-execution-edit.test.ts`
446
+ - `tests/provider-execution-harness.test.ts`
447
+ - `tests/provider-execution-imports.test.ts`
383
448
  - `tests/provider-ui-polish-contract.test.js`
384
449
  - `tests/reference-limits.test.ts`
385
450
  - `tests/reference-tray-model.test.ts`
386
451
  - `tests/release-pipeline-contract.test.ts`
452
+ - `tests/runtime-install-projection.test.ts`
453
+ - `tests/selector-comfy-admission.test.ts`
387
454
  - `tests/server-fallback-contract.test.js`
388
455
  - `tests/server.test.js`
389
456
  - `tests/session-conflict.test.js`
@@ -403,11 +470,15 @@ Total: 416 (runtime: 191, contract: 225)
403
470
  - `tests/studio-surface-docs-contract.test.ts`
404
471
  - `tests/style-feature-removal-contract.test.js`
405
472
  - `tests/subprocess-deadline-contract.test.ts`
473
+ - `tests/test-runner-invocation.test.ts`
406
474
  - `tests/toast-stack-contract.test.js`
407
475
  - `tests/tools-cli-contract.test.ts`
408
476
  - `tests/transparent-background-ui-contract.test.js`
477
+ - `tests/ui-build-receipt-inventory.test.mjs`
478
+ - `tests/ui-build-receipt-transaction.test.mjs`
479
+ - `tests/ui-build-receipt.test.mjs`
409
480
  - `tests/ui-color-token-contract.test.ts`
410
- - `tests/ui-error-code-contract.test.js`
481
+ - `tests/ui-error-code-contract.test.ts`
411
482
  - `tests/ui-glyph-policy.test.ts`
412
483
  - `tests/ui-gradient-manifest-contract.test.ts`
413
484
  - `tests/ui-radius-scale-contract.test.ts`
@@ -0,0 +1,82 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { extname, join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { agyError } from "./agyProcess.js";
5
+ export function parseAgyOutput(stdout) {
6
+ const lines = stdout.replace(/\r/g, "").trim().split("\n").filter((l) => l.trim().length > 0);
7
+ const resultLine = lines.find((l) => l.startsWith("RESULT|"));
8
+ if (resultLine) {
9
+ const parts = resultLine.split("|");
10
+ const artifactPath = parts[1];
11
+ const ext = parts[2];
12
+ if (parts.length >= 3 && artifactPath && ext) {
13
+ return { artifactPath: artifactPath.trim(), ext: ext.trim() };
14
+ }
15
+ throw agyError(`Malformed RESULT line: ${resultLine}`, 502, "AGY_MALFORMED_RESULT");
16
+ }
17
+ const errorLine = lines.find((l) => l.startsWith("ERROR|"));
18
+ if (errorLine) {
19
+ const msg = errorLine.slice("ERROR|".length).trim() || "Unknown agy error";
20
+ const lower = msg.toLowerCase();
21
+ if (lower.includes("resource exhausted") || lower.includes("exhausted your capacity") || lower.includes("quota will reset")) {
22
+ throw agyError(`Agy generation failed: ${msg}`, 429, "AGY_QUOTA_EXHAUSTED");
23
+ }
24
+ throw agyError(`Agy generation failed: ${msg}`, 502, "AGY_GENERATION_FAILED");
25
+ }
26
+ const fullLower = stdout.toLowerCase();
27
+ if (fullLower.includes("resource exhausted") || fullLower.includes("exhausted your capacity")) {
28
+ throw agyError(`Agy quota exhausted: ${stdout.trim().slice(0, 200)}`, 429, "AGY_QUOTA_EXHAUSTED");
29
+ }
30
+ const savedPathLine = lines.find((l) => l.startsWith("SAVED_PATH="));
31
+ if (savedPathLine) {
32
+ const p = savedPathLine.slice("SAVED_PATH=".length).trim();
33
+ const ext = p.split(".").pop() || "png";
34
+ return { artifactPath: p, ext };
35
+ }
36
+ const normalizedStdout = stdout.replace(/\r/g, "").replace(/\\/g, "/");
37
+ const pathMatch = normalizedStdout.match(/(?:[A-Za-z]:)?\/[^\s"']+\/(brain|artifacts|\.gemini)\/[^\s"']+\.(png|jpg|jpeg|webp)/i);
38
+ if (pathMatch) {
39
+ const matched = pathMatch[0];
40
+ const artifactPath = process.platform === "win32" ? matched.replace(/\//g, "\\") : matched;
41
+ const ext = extname(artifactPath).slice(1) || "png";
42
+ return { artifactPath, ext };
43
+ }
44
+ throw agyError(`Could not parse artifact path from agy output (${stdout.length} chars): ${stdout.slice(0, 200)}`, 502, "AGY_PARSE_FAILED");
45
+ }
46
+ export async function findRecentAgyArtifact(sinceMs, rootOverrides) {
47
+ const roots = rootOverrides ?? [
48
+ join(homedir(), ".gemini", "antigravity-cli", "brain"),
49
+ join(homedir(), ".gemini"),
50
+ ];
51
+ const candidates = [];
52
+ const seen = new Set();
53
+ async function walk(dir, depth) {
54
+ if (depth > 5 || seen.has(dir))
55
+ return;
56
+ seen.add(dir);
57
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
58
+ for (const entry of entries) {
59
+ if (entry.isSymbolicLink())
60
+ continue;
61
+ const p = join(dir, entry.name);
62
+ if (entry.isDirectory()) {
63
+ await walk(p, depth + 1);
64
+ continue;
65
+ }
66
+ if (!entry.isFile() || !/^ima2_generated.*\.(png|jpg|jpeg|webp)$/i.test(entry.name)) {
67
+ continue;
68
+ }
69
+ const s = await stat(p).catch(() => null);
70
+ if (!s?.isFile())
71
+ continue;
72
+ if (s.mtimeMs >= sinceMs - 5_000) {
73
+ candidates.push({ path: p, mtimeMs: s.mtimeMs });
74
+ }
75
+ }
76
+ }
77
+ for (const root of roots) {
78
+ await walk(root, 0);
79
+ }
80
+ candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
81
+ return candidates[0]?.path ?? null;
82
+ }