ima2-gen 3.7.1 → 3.7.2

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 (31) hide show
  1. package/docs/migration/runtime-test-inventory.md +3 -1
  2. package/lib/grokProxyLauncher.js +110 -1
  3. package/package.json +2 -2
  4. package/routes/auth.js +6 -3
  5. package/routes/grok.js +13 -3
  6. package/routes/index.js +1 -1
  7. package/routes/models.js +33 -4
  8. package/server.js +46 -23
  9. package/ui/dist/.vite/manifest.json +36 -36
  10. package/ui/dist/assets/{AgentWorkspace-DU9efUdT.js → AgentWorkspace-JQ0K_ndE.js} +1 -1
  11. package/ui/dist/assets/{AssetGenWorkspace-ldEVBldg.js → AssetGenWorkspace-B2d-aiIb.js} +2 -2
  12. package/ui/dist/assets/{AssetsWorkspace-D_kJ7dP_.js → AssetsWorkspace-Di0BYZZW.js} +1 -1
  13. package/ui/dist/assets/{CardNewsWorkspace-DIhk26qD.js → CardNewsWorkspace-F3LrEj0b.js} +1 -1
  14. package/ui/dist/assets/{GenerationRequestLogPanel-BZZJh4w_.js → GenerationRequestLogPanel-CpyvTE6t.js} +1 -1
  15. package/ui/dist/assets/{HomeWorkspace-DwRS4Cim.js → HomeWorkspace-KplbVQT9.js} +1 -1
  16. package/ui/dist/assets/{KeyingPanel-YkL9305F.js → KeyingPanel-BY_xV317.js} +1 -1
  17. package/ui/dist/assets/{NodeCanvas-CaFDJn_9.js → NodeCanvas-scDxt0LA.js} +1 -1
  18. package/ui/dist/assets/{PromptBuilderPanel-Bl5-fDQs.js → PromptBuilderPanel-DeSY9dl-.js} +1 -1
  19. package/ui/dist/assets/{PromptImportDialog-5RbU95_Y.js → PromptImportDialog-BTP0imZl.js} +2 -2
  20. package/ui/dist/assets/{PromptImportDiscoverySection-uhS5xI0p.js → PromptImportDiscoverySection-BBThhJZM.js} +1 -1
  21. package/ui/dist/assets/{PromptImportFolderSection-COwFVcbE.js → PromptImportFolderSection-6sXWWJoK.js} +1 -1
  22. package/ui/dist/assets/{PromptLibraryPanel-DCtxfzy1.js → PromptLibraryPanel-BYTG-mBa.js} +2 -2
  23. package/ui/dist/assets/{SettingsWorkspace-aHoTeB4-.js → SettingsWorkspace-chsddbrB.js} +1 -1
  24. package/ui/dist/assets/{SpriteRecipeWorkspace-B0fR6yXF.js → SpriteRecipeWorkspace-HgacgO0x.js} +1 -1
  25. package/ui/dist/assets/{index-Bj2sxDP3.js → index-BqFlxfzT.js} +2 -2
  26. package/ui/dist/assets/{index-DBh-jt0i.js → index-VXkP0z69.js} +3 -3
  27. package/ui/dist/assets/index-waPOg2eb.css +1 -0
  28. package/ui/dist/assets/{pptxgen.es-AWrWIHuf.js → pptxgen.es-DX8PxAzM.js} +1 -1
  29. package/ui/dist/assets/{useAgentDialogFocus-BB6ds9jo.js → useAgentDialogFocus-DYjA7lCJ.js} +1 -1
  30. package/ui/dist/index.html +2 -2
  31. package/ui/dist/assets/index-BnMUy9kc.css +0 -1
@@ -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: 364 (runtime: 161, contract: 203)
7
+ Total: 366 (runtime: 163, contract: 203)
8
8
 
9
9
  ## Runtime-importing tests
10
10
  - `tests/agent-mode-auto-planner-contract.test.ts`
@@ -58,11 +58,13 @@ Total: 364 (runtime: 161, contract: 203)
58
58
  - `tests/generation-errors.test.ts`
59
59
  - `tests/generation-request-log.test.ts`
60
60
  - `tests/gpt56-rollout-contract.test.ts`
61
+ - `tests/grok-advertise-liveness-contract.test.ts`
61
62
  - `tests/grok-command-login-contract.test.ts`
62
63
  - `tests/grok-planner-adapter.test.ts`
63
64
  - `tests/grok-planner-config-route.test.ts`
64
65
  - `tests/grok-proxy-launcher.test.ts`
65
66
  - `tests/grok-proxy-restart.test.ts`
67
+ - `tests/grok-proxy-supervisor-contract.test.ts`
66
68
  - `tests/grok-size-mapper.test.ts`
67
69
  - `tests/grok-upstream-retry.test.ts`
68
70
  - `tests/grokVideoAdapter.test.ts`
@@ -6,6 +6,11 @@ import { config } from "../config.js";
6
6
  import { findAvailablePort } from "./runtimePorts.js";
7
7
  const rootDir = join(dirname(fileURLToPath(import.meta.url)), "..");
8
8
  const PROGROK_LOGIN_COMMAND = ["progrok", "login"].join(" ");
9
+ /** Only these states may spawn. Everything else makes `ensure()` a no-op. */
10
+ const SPAWNABLE = new Set([
11
+ "stopped",
12
+ "gave-up-retryable",
13
+ ]);
9
14
  /**
10
15
  * Bounded exponential restart. Exported so the policy is testable without spawning a
11
16
  * process: a fixed 2s retry loop spins forever on a permanently broken binary or port.
@@ -47,6 +52,12 @@ export async function startGrokProxy(options = {}) {
47
52
  let authRequired = false;
48
53
  let restartAttempt = 0;
49
54
  let lastSpawnAt = 0;
55
+ let state = "stopped";
56
+ /** Bumped by every credential event, so a login racing an in-flight spawn is not lost. */
57
+ let credentialGeneration = 0;
58
+ /** Bumped by every spawn, so a late probe response can be matched to its child. */
59
+ let spawnGeneration = 0;
60
+ let inflight = null;
50
61
  const scheduleRestart = () => {
51
62
  // A child that stayed up for a while counts as a healthy start, so an occasional
52
63
  // crash later does not inherit an exhausted budget.
@@ -59,9 +70,11 @@ export async function startGrokProxy(options = {}) {
59
70
  });
60
71
  if (plan.giveUp) {
61
72
  console.error(`[grok] progrok failed ${restartAttempt} times in a row; giving up. Fix the cause and restart ima2 serve to retry.`);
73
+ state = "gave-up";
62
74
  return;
63
75
  }
64
76
  restartAttempt += 1;
77
+ state = "backoff";
65
78
  console.log(`[grok] restarting in ${Math.round(plan.delayMs / 1000)}s (attempt ${restartAttempt}/${restartMaxAttempts})...`);
66
79
  restartTimer = setTimeout(() => {
67
80
  void spawnProxy();
@@ -81,6 +94,12 @@ export async function startGrokProxy(options = {}) {
81
94
  }
82
95
  return;
83
96
  }
97
+ // Shutdown can begin while the port lookup above is awaiting. Without this
98
+ // re-check the woken continuation spawns a child nobody tracks or kills.
99
+ if (stopping) {
100
+ state = "stopped";
101
+ return;
102
+ }
84
103
  if (port !== requestedPort) {
85
104
  console.log(`[grok] requested port ${requestedPort}, actual port ${port}`);
86
105
  }
@@ -96,6 +115,9 @@ export async function startGrokProxy(options = {}) {
96
115
  currentChild = child;
97
116
  authRequired = false;
98
117
  lastSpawnAt = Date.now();
118
+ spawnGeneration += 1;
119
+ // Captured per spawn: a login arriving after this point invalidates this child.
120
+ const spawnedAtCredentialGeneration = credentialGeneration;
99
121
  // A missing or unlaunchable binary emits `error` (and `close`) without ever emitting
100
122
  // `exit`, so the bounded restart must arm from here too — otherwise the backoff never
101
123
  // runs on the very failure it targets.
@@ -110,6 +132,10 @@ export async function startGrokProxy(options = {}) {
110
132
  scheduleRestart();
111
133
  });
112
134
  child.stdout?.on("data", (d) => {
135
+ // Node can deliver buffered stdio after the exit event fires. Once the
136
+ // exit handler has settled lifecycle state, late output must not overwrite it.
137
+ if (spawnSettled)
138
+ return;
113
139
  const msg = normalizeGrokProxyMessage(d.toString().trim());
114
140
  if (!msg)
115
141
  return;
@@ -121,10 +147,13 @@ export async function startGrokProxy(options = {}) {
121
147
  if (!ready)
122
148
  continue;
123
149
  console.log(`[grok] ready for ima2 Grok provider at ${ready.url}`);
150
+ state = "ready";
124
151
  options.onReady?.({ url: ready.url, port: ready.port, requestedPort });
125
152
  }
126
153
  });
127
154
  child.stderr?.on("data", (d) => {
155
+ if (spawnSettled)
156
+ return;
128
157
  const msg = normalizeGrokProxyMessage(d.toString().trim());
129
158
  if (msg)
130
159
  console.error(`[grok] ${msg}`);
@@ -141,6 +170,16 @@ export async function startGrokProxy(options = {}) {
141
170
  spawnSettled = true;
142
171
  options.onExit?.({ code });
143
172
  if (authRequired && code !== 0) {
173
+ // A login that landed while this child was starting means the child was
174
+ // spawned against stale credentials. Its death is expected, not terminal.
175
+ if (credentialGeneration !== spawnedAtCredentialGeneration) {
176
+ authRequired = false;
177
+ restartAttempt = 0;
178
+ state = "gave-up-retryable";
179
+ void ensure();
180
+ return;
181
+ }
182
+ state = "waiting-for-login";
144
183
  console.error("[grok] Grok OAuth is not logged in. Run `ima2 grok login` to enable Grok images/video.");
145
184
  console.error("[grok] Continuing without auto-restarting the Grok proxy. GPT OAuth/API image generation can still run.");
146
185
  return;
@@ -149,16 +188,86 @@ export async function startGrokProxy(options = {}) {
149
188
  scheduleRestart();
150
189
  });
151
190
  };
191
+ /**
192
+ * Idempotent entry point. Order matters: the in-flight check must come FIRST so
193
+ * concurrent callers share one spawn even when state is already `starting`.
194
+ */
195
+ async function ensure() {
196
+ if (stopping)
197
+ return state;
198
+ if (inflight)
199
+ return inflight;
200
+ if (!SPAWNABLE.has(state))
201
+ return state;
202
+ state = "starting";
203
+ inflight = spawnProxy()
204
+ .then(() => state)
205
+ .finally(() => { inflight = null; });
206
+ return inflight;
207
+ }
208
+ state = "starting";
152
209
  await spawnProxy();
210
+ if (state === "starting" && !currentChild)
211
+ state = "stopped";
153
212
  return {
154
213
  get child() {
155
214
  return currentChild;
156
215
  },
216
+ get state() {
217
+ return state;
218
+ },
219
+ ensure,
220
+ /**
221
+ * The ONLY door back into a spawnable state. `gave-up` is left alone on
222
+ * purpose: a login cannot fix a missing binary or an occupied port, and
223
+ * re-arming it would let the 10s status poll spawn a child forever.
224
+ */
225
+ notifyCredentialsChanged() {
226
+ credentialGeneration += 1;
227
+ if (stopping)
228
+ return;
229
+ if (state !== "waiting-for-login")
230
+ return;
231
+ authRequired = false;
232
+ restartAttempt = 0;
233
+ state = "gave-up-retryable";
234
+ void ensure();
235
+ },
236
+ probeToken() {
237
+ return { gen: spawnGeneration };
238
+ },
239
+ /**
240
+ * A successful live probe is stronger evidence than stdout parsing, which only
241
+ * recognizes 127.0.0.1/localhost. The token and child checks stop a response
242
+ * that outlived its child from resurrecting a dead proxy on paper.
243
+ */
244
+ markProbedReady(token, url) {
245
+ if (stopping)
246
+ return false;
247
+ if (token.gen !== spawnGeneration)
248
+ return false;
249
+ if (!currentChild)
250
+ return false;
251
+ if (state === "ready")
252
+ return true;
253
+ const port = Number(/:(\d+)/.exec(url)?.[1]) || requestedPort;
254
+ state = "ready";
255
+ options.onReady?.({ url, port, requestedPort });
256
+ return true;
257
+ },
157
258
  kill(signal = "SIGTERM") {
158
- this.stop(signal);
259
+ stopping = true;
260
+ state = "stopped";
261
+ if (restartTimer)
262
+ clearTimeout(restartTimer);
263
+ try {
264
+ currentChild?.kill(signal);
265
+ }
266
+ catch { }
159
267
  },
160
268
  stop(signal = "SIGTERM") {
161
269
  stopping = true;
270
+ state = "stopped";
162
271
  if (restartTimer)
163
272
  clearTimeout(restartTimer);
164
273
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ima2-gen",
3
- "version": "3.7.1",
3
+ "version": "3.7.2",
4
4
  "packageManager": "npm@11.18.0",
5
5
  "description": "Local-first visual generation runtime and studio for people and coding agents, with reproducible image and video workflows across multiple providers.",
6
6
  "type": "module",
@@ -119,5 +119,5 @@
119
119
  "tsx": "^4.23.12",
120
120
  "typescript": "^5.9.3"
121
121
  },
122
- "gitHead": "be79588c6149ec8a2b62a70ff7ec35e40135769a"
122
+ "gitHead": "8f72a5b19e9aa58c7ec9ea540e988b3edc8ae4f5"
123
123
  }
package/routes/auth.js CHANGED
@@ -58,7 +58,7 @@ function saveGrokTokens(tokens) {
58
58
  writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
59
59
  renameSync(tmp, target);
60
60
  }
61
- async function startGrokDeviceCode() {
61
+ async function startGrokDeviceCode(onCredentialsSaved) {
62
62
  const discovery = await fetch("https://auth.x.ai/.well-known/openid-configuration", { signal: AbortSignal.timeout(10000) });
63
63
  const disc = await discovery.json();
64
64
  if (!disc.device_authorization_endpoint)
@@ -107,6 +107,9 @@ async function startGrokDeviceCode() {
107
107
  if (tokenRes.ok) {
108
108
  const tokens = await tokenRes.json();
109
109
  saveGrokTokens(tokens);
110
+ // progrok checks credentials only at startup, so a proxy that gave up
111
+ // before login stays dead until something tells it the world changed.
112
+ onCredentialsSaved?.();
110
113
  session.status = "complete";
111
114
  cleanup(id);
112
115
  return;
@@ -215,7 +218,7 @@ function startCodexDeviceCode() {
215
218
  }, 30000);
216
219
  });
217
220
  }
218
- export function registerAuthRoutes(app) {
221
+ export function registerAuthRoutes(app, ctx) {
219
222
  app.post("/api/auth/switch", async (req, res) => {
220
223
  const provider = req.body?.provider;
221
224
  if (provider !== "grok" && provider !== "codex") {
@@ -226,7 +229,7 @@ export function registerAuthRoutes(app) {
226
229
  }
227
230
  try {
228
231
  const result = provider === "grok"
229
- ? await startGrokDeviceCode()
232
+ ? await startGrokDeviceCode(() => ctx?.grokProxy?.notifyCredentialsChanged())
230
233
  : await startCodexDeviceCode();
231
234
  res.json(result);
232
235
  }
package/routes/grok.js CHANGED
@@ -1,8 +1,11 @@
1
- import { getGrokProxyUrl } from "../lib/grokRuntime.js";
1
+ import { getGrokProxyBaseUrl, getGrokProxyUrl } from "../lib/grokRuntime.js";
2
2
  export function registerGrokRoutes(app, ctx) {
3
3
  app.get("/api/grok/status", async (_req, res) => {
4
4
  const grokCfg = ctx.config.grokProvider || {};
5
5
  const timeoutMs = grokCfg.statusTimeoutMs || 3000;
6
+ // Captured BEFORE the await: a response that outlives its child must not be
7
+ // able to promote a dead proxy back to ready.
8
+ const token = ctx.grokProxy?.probeToken();
6
9
  try {
7
10
  const r = await fetch(getGrokProxyUrl(ctx, "/v1/models"), {
8
11
  signal: AbortSignal.timeout(timeoutMs),
@@ -11,12 +14,19 @@ export function registerGrokRoutes(app, ctx) {
11
14
  const data = await r.json();
12
15
  const models = data?.data?.map((m) => m.id).filter(Boolean) || [];
13
16
  const hasImageModel = models.some((m) => m.startsWith("grok-imagine"));
17
+ // A live 200 is stronger evidence than stdout parsing, which only
18
+ // recognizes 127.0.0.1/localhost and so can strand a custom host.
19
+ if (token)
20
+ ctx.grokProxy?.markProbedReady(token, getGrokProxyBaseUrl(ctx));
14
21
  return res.json({ status: hasImageModel ? "ready" : "no_image_model", models });
15
22
  }
16
- return res.json({ status: "error", reason: `HTTP ${r.status}` });
23
+ return res.json({ status: "error", reason: `HTTP ${r.status}`, state: ctx.grokProxy?.state });
17
24
  }
18
25
  catch {
19
- return res.json({ status: "offline" });
26
+ // Deliberately does NOT call ensure(): the UI polls every 10s while not
27
+ // ready, so self-healing here would spawn a child per poll forever.
28
+ // Recovery is driven by the login event only.
29
+ return res.json({ status: "offline", state: ctx.grokProxy?.state });
20
30
  }
21
31
  });
22
32
  }
package/routes/index.js CHANGED
@@ -76,7 +76,7 @@ export function configureRoutes(app, ctxRaw) {
76
76
  registerVideoRoutes(app, ctx);
77
77
  registerVideoExtendedRoutes(app, ctx);
78
78
  registerQuotaRoutes(app, ctx);
79
- registerAuthRoutes(app);
79
+ registerAuthRoutes(app, ctx);
80
80
  registerMcpConnectionRoutes(app, ctx);
81
81
  registerModelsRoutes(app, ctx);
82
82
  registerMcpMediaRoutes(app, ctx);
package/routes/models.js CHANGED
@@ -49,11 +49,40 @@ function oauthLane(ctx, image) {
49
49
  function apiLane(ctx, image) {
50
50
  return lane(ctx.hasApiKey ? { status: "ready" } : { status: "key-missing", reason: "OpenAI API key missing" }, { image: ctx.config.apiProvider.defaultImageModel }, { image, video: [] });
51
51
  }
52
+ const UNPROBED_GROK_REASON = "configured proxy endpoint; live session not probed";
53
+ /**
54
+ * Lane status follows the supervisor instead of the mere existence of a URL
55
+ * string, so /api/models and /api/grok/status can no longer disagree.
56
+ *
57
+ * Transient states (starting/backoff/re-armed) keep the legacy optimistic
58
+ * answer on purpose: flickering the lane during a few hundred ms of boot or a
59
+ * bounded retry is noise, not honesty. Only settled-bad states go disconnected.
60
+ */
61
+ function grokLaneState(ctx) {
62
+ if (!ctx.grokUrl)
63
+ return { status: "disconnected", reason: "Grok proxy not configured" };
64
+ switch (ctx.grokProxy?.state) {
65
+ case "ready":
66
+ return { status: "ready" };
67
+ case "starting":
68
+ return { status: "ready", reason: UNPROBED_GROK_REASON };
69
+ case "backoff":
70
+ return { status: "disconnected", reason: "Grok proxy restarting" };
71
+ case "gave-up-retryable":
72
+ return { status: "ready", reason: UNPROBED_GROK_REASON };
73
+ case "waiting-for-login":
74
+ return { status: "disconnected", reason: "Grok login required" };
75
+ case "gave-up":
76
+ return { status: "disconnected", reason: "Grok proxy failed to start" };
77
+ case "stopped":
78
+ return { status: "disconnected", reason: "Grok proxy stopped" };
79
+ default:
80
+ // No supervisor (autoStart off, or a test context): legacy contract.
81
+ return { status: "ready", reason: UNPROBED_GROK_REASON };
82
+ }
83
+ }
52
84
  function grokLane(ctx) {
53
- const configured = Boolean(ctx.grokUrl);
54
- const state = configured
55
- ? { status: "ready", reason: "configured proxy endpoint; live session not probed" }
56
- : { status: "disconnected", reason: "Grok proxy not configured" };
85
+ const state = grokLaneState(ctx);
57
86
  return lane(state, {
58
87
  image: ctx.config.grokProvider.defaultImageModel,
59
88
  video: ctx.config.grokProvider.defaultVideoModel,
package/server.js CHANGED
@@ -280,6 +280,41 @@ function runtimeHostUrl(host) {
280
280
  return "localhost";
281
281
  return host;
282
282
  }
283
+ /**
284
+ * Pure payload builder, exported so the liveness contract is testable without
285
+ * booting a server or spawning a real progrok child.
286
+ *
287
+ * The grok section only carries an endpoint while a supervised child is actually
288
+ * listening. Publishing `actualPort`/`url` for a dead child is a claim the file
289
+ * cannot back up, so those go null and `configuredPort` stays for diagnostics.
290
+ */
291
+ export function buildAdvertisePayload(ctx) {
292
+ const grokLive = ctx.grokProxyLive === true;
293
+ return {
294
+ port: Number(ctx.serverActualPort || ctx.config.server.port),
295
+ url: ctx.serverUrl,
296
+ pid: process.pid,
297
+ startedAt: ctx.startedAt,
298
+ version: ctx.packageVersion,
299
+ backend: {
300
+ configuredPort: Number(ctx.serverConfiguredPort || ctx.config.server.port),
301
+ actualPort: Number(ctx.serverActualPort || ctx.config.server.port),
302
+ url: ctx.serverUrl,
303
+ },
304
+ oauth: {
305
+ configuredPort: Number(ctx.oauthPort),
306
+ actualPort: Number(ctx.oauthActualPort || ctx.oauthPort),
307
+ url: ctx.oauthUrl,
308
+ status: ctx.oauthReadyState,
309
+ },
310
+ grok: {
311
+ configuredPort: Number(ctx.grokPort),
312
+ actualPort: grokLive ? Number(ctx.grokActualPort || ctx.grokPort) : null,
313
+ url: grokLive ? ctx.grokUrl : null,
314
+ live: grokLive,
315
+ },
316
+ };
317
+ }
283
318
  function advertise(ctx) {
284
319
  // Proxy readiness can arrive before the backend has bound. Publishing that
285
320
  // intermediate state makes consumers treat the configured port as live.
@@ -287,29 +322,7 @@ function advertise(ctx) {
287
322
  return;
288
323
  try {
289
324
  mkdirSync(dirname(ctx.config.storage.advertiseFile), { recursive: true });
290
- writeFileSync(ctx.config.storage.advertiseFile, JSON.stringify({
291
- port: Number(ctx.serverActualPort || ctx.config.server.port),
292
- url: ctx.serverUrl,
293
- pid: process.pid,
294
- startedAt: ctx.startedAt,
295
- version: ctx.packageVersion,
296
- backend: {
297
- configuredPort: Number(ctx.serverConfiguredPort || ctx.config.server.port),
298
- actualPort: Number(ctx.serverActualPort || ctx.config.server.port),
299
- url: ctx.serverUrl,
300
- },
301
- oauth: {
302
- configuredPort: Number(ctx.oauthPort),
303
- actualPort: Number(ctx.oauthActualPort || ctx.oauthPort),
304
- url: ctx.oauthUrl,
305
- status: ctx.oauthReadyState,
306
- },
307
- grok: {
308
- configuredPort: Number(ctx.grokPort),
309
- actualPort: Number(ctx.grokActualPort || ctx.grokPort),
310
- url: ctx.grokUrl,
311
- },
312
- }));
325
+ writeFileSync(ctx.config.storage.advertiseFile, JSON.stringify(buildAdvertisePayload(ctx)));
313
326
  }
314
327
  catch (e) {
315
328
  const err = errInfo(e);
@@ -450,14 +463,24 @@ export async function startServer(overrides = {}) {
450
463
  restartDelayMs: ctx.config.grokProvider.restartDelayMs,
451
464
  onPortSelected: ({ url, port }) => {
452
465
  ctx.markGrokProxyPort({ url, port });
466
+ // Port selection is an intent to bind, not a successful bind.
467
+ ctx.grokProxyLive = false;
453
468
  advertise(ctx);
454
469
  },
455
470
  onReady: ({ url, port }) => {
456
471
  ctx.markGrokProxyPort({ url, port });
472
+ ctx.grokProxyLive = true;
473
+ advertise(ctx);
474
+ },
475
+ onExit: () => {
476
+ // Without this the advertise file keeps publishing the port of a child
477
+ // that is already gone.
478
+ ctx.grokProxyLive = false;
457
479
  advertise(ctx);
458
480
  },
459
481
  })
460
482
  : null;
483
+ ctx.grokProxy = grokChild ?? undefined;
461
484
  let server;
462
485
  let reapTimer;
463
486
  let tempReferenceReapTimer;
@@ -1,15 +1,11 @@
1
1
  {
2
- "_AssetGenWorkspace-Dkr80l12.css": {
3
- "file": "assets/AssetGenWorkspace-Dkr80l12.css",
4
- "src": "_AssetGenWorkspace-Dkr80l12.css"
5
- },
6
- "_AssetGenWorkspace-ldEVBldg.js": {
7
- "file": "assets/AssetGenWorkspace-ldEVBldg.js",
2
+ "_AssetGenWorkspace-B2d-aiIb.js": {
3
+ "file": "assets/AssetGenWorkspace-B2d-aiIb.js",
8
4
  "name": "AssetGenWorkspace",
9
5
  "isDynamicEntry": true,
10
6
  "imports": [
11
7
  "index.html",
12
- "_KeyingPanel-YkL9305F.js"
8
+ "_KeyingPanel-BY_xV317.js"
13
9
  ],
14
10
  "dynamicImports": [
15
11
  "src/components/assetgen/SpriteRecipeWorkspace.tsx"
@@ -18,12 +14,16 @@
18
14
  "assets/AssetGenWorkspace-Dkr80l12.css"
19
15
  ]
20
16
  },
21
- "_KeyingPanel-YkL9305F.js": {
22
- "file": "assets/KeyingPanel-YkL9305F.js",
17
+ "_AssetGenWorkspace-Dkr80l12.css": {
18
+ "file": "assets/AssetGenWorkspace-Dkr80l12.css",
19
+ "src": "_AssetGenWorkspace-Dkr80l12.css"
20
+ },
21
+ "_KeyingPanel-BY_xV317.js": {
22
+ "file": "assets/KeyingPanel-BY_xV317.js",
23
23
  "name": "KeyingPanel",
24
24
  "imports": [
25
25
  "index.html",
26
- "_useAgentDialogFocus-BB6ds9jo.js"
26
+ "_useAgentDialogFocus-DYjA7lCJ.js"
27
27
  ]
28
28
  },
29
29
  "__vite-browser-external": {
@@ -32,15 +32,15 @@
32
32
  "src": "__vite-browser-external",
33
33
  "isDynamicEntry": true
34
34
  },
35
- "_useAgentDialogFocus-BB6ds9jo.js": {
36
- "file": "assets/useAgentDialogFocus-BB6ds9jo.js",
35
+ "_useAgentDialogFocus-DYjA7lCJ.js": {
36
+ "file": "assets/useAgentDialogFocus-DYjA7lCJ.js",
37
37
  "name": "useAgentDialogFocus",
38
38
  "imports": [
39
39
  "index.html"
40
40
  ]
41
41
  },
42
42
  "index.html": {
43
- "file": "assets/index-Bj2sxDP3.js",
43
+ "file": "assets/index-BqFlxfzT.js",
44
44
  "name": "index",
45
45
  "src": "index.html",
46
46
  "isEntry": true,
@@ -55,16 +55,16 @@
55
55
  "src/components/card-news/CardNewsWorkspace.tsx",
56
56
  "src/components/agent/AgentWorkspace.tsx",
57
57
  "src/components/assets/AssetsWorkspace.tsx",
58
- "_AssetGenWorkspace-ldEVBldg.js",
58
+ "_AssetGenWorkspace-B2d-aiIb.js",
59
59
  "src/components/home/HomeWorkspace.tsx",
60
60
  "src/components/PromptLibraryPanel.tsx"
61
61
  ],
62
62
  "css": [
63
- "assets/index-BnMUy9kc.css"
63
+ "assets/index-waPOg2eb.css"
64
64
  ]
65
65
  },
66
66
  "node_modules/pptxgenjs/dist/pptxgen.es.js": {
67
- "file": "assets/pptxgen.es-AWrWIHuf.js",
67
+ "file": "assets/pptxgen.es-DX8PxAzM.js",
68
68
  "name": "pptxgen.es",
69
69
  "src": "node_modules/pptxgenjs/dist/pptxgen.es.js",
70
70
  "isDynamicEntry": true,
@@ -78,7 +78,7 @@
78
78
  ]
79
79
  },
80
80
  "src/components/GenerationRequestLogPanel.tsx": {
81
- "file": "assets/GenerationRequestLogPanel-BZZJh4w_.js",
81
+ "file": "assets/GenerationRequestLogPanel-CpyvTE6t.js",
82
82
  "name": "GenerationRequestLogPanel",
83
83
  "src": "src/components/GenerationRequestLogPanel.tsx",
84
84
  "isDynamicEntry": true,
@@ -87,7 +87,7 @@
87
87
  ]
88
88
  },
89
89
  "src/components/NodeCanvas.tsx": {
90
- "file": "assets/NodeCanvas-CaFDJn_9.js",
90
+ "file": "assets/NodeCanvas-scDxt0LA.js",
91
91
  "name": "NodeCanvas",
92
92
  "src": "src/components/NodeCanvas.tsx",
93
93
  "isDynamicEntry": true,
@@ -99,7 +99,7 @@
99
99
  ]
100
100
  },
101
101
  "src/components/PromptImportDialog.tsx": {
102
- "file": "assets/PromptImportDialog-5RbU95_Y.js",
102
+ "file": "assets/PromptImportDialog-BTP0imZl.js",
103
103
  "name": "PromptImportDialog",
104
104
  "src": "src/components/PromptImportDialog.tsx",
105
105
  "isDynamicEntry": true,
@@ -112,7 +112,7 @@
112
112
  ]
113
113
  },
114
114
  "src/components/PromptImportDiscoverySection.tsx": {
115
- "file": "assets/PromptImportDiscoverySection-uhS5xI0p.js",
115
+ "file": "assets/PromptImportDiscoverySection-BBThhJZM.js",
116
116
  "name": "PromptImportDiscoverySection",
117
117
  "src": "src/components/PromptImportDiscoverySection.tsx",
118
118
  "isDynamicEntry": true,
@@ -121,7 +121,7 @@
121
121
  ]
122
122
  },
123
123
  "src/components/PromptImportFolderSection.tsx": {
124
- "file": "assets/PromptImportFolderSection-COwFVcbE.js",
124
+ "file": "assets/PromptImportFolderSection-6sXWWJoK.js",
125
125
  "name": "PromptImportFolderSection",
126
126
  "src": "src/components/PromptImportFolderSection.tsx",
127
127
  "isDynamicEntry": true,
@@ -130,7 +130,7 @@
130
130
  ]
131
131
  },
132
132
  "src/components/PromptLibraryPanel.tsx": {
133
- "file": "assets/PromptLibraryPanel-DCtxfzy1.js",
133
+ "file": "assets/PromptLibraryPanel-BYTG-mBa.js",
134
134
  "name": "PromptLibraryPanel",
135
135
  "src": "src/components/PromptLibraryPanel.tsx",
136
136
  "isDynamicEntry": true,
@@ -142,7 +142,7 @@
142
142
  ]
143
143
  },
144
144
  "src/components/SettingsWorkspace.tsx": {
145
- "file": "assets/SettingsWorkspace-aHoTeB4-.js",
145
+ "file": "assets/SettingsWorkspace-chsddbrB.js",
146
146
  "name": "SettingsWorkspace",
147
147
  "src": "src/components/SettingsWorkspace.tsx",
148
148
  "isDynamicEntry": true,
@@ -151,43 +151,43 @@
151
151
  ]
152
152
  },
153
153
  "src/components/agent/AgentWorkspace.tsx": {
154
- "file": "assets/AgentWorkspace-DU9efUdT.js",
154
+ "file": "assets/AgentWorkspace-JQ0K_ndE.js",
155
155
  "name": "AgentWorkspace",
156
156
  "src": "src/components/agent/AgentWorkspace.tsx",
157
157
  "isDynamicEntry": true,
158
158
  "imports": [
159
159
  "index.html",
160
- "_useAgentDialogFocus-BB6ds9jo.js"
160
+ "_useAgentDialogFocus-DYjA7lCJ.js"
161
161
  ]
162
162
  },
163
163
  "src/components/assetgen/SpriteRecipeWorkspace.tsx": {
164
- "file": "assets/SpriteRecipeWorkspace-B0fR6yXF.js",
164
+ "file": "assets/SpriteRecipeWorkspace-HgacgO0x.js",
165
165
  "name": "SpriteRecipeWorkspace",
166
166
  "src": "src/components/assetgen/SpriteRecipeWorkspace.tsx",
167
167
  "isDynamicEntry": true,
168
168
  "imports": [
169
169
  "index.html",
170
- "_AssetGenWorkspace-ldEVBldg.js",
171
- "_KeyingPanel-YkL9305F.js",
172
- "_useAgentDialogFocus-BB6ds9jo.js"
170
+ "_AssetGenWorkspace-B2d-aiIb.js",
171
+ "_KeyingPanel-BY_xV317.js",
172
+ "_useAgentDialogFocus-DYjA7lCJ.js"
173
173
  ]
174
174
  },
175
175
  "src/components/assets/AssetsWorkspace.tsx": {
176
- "file": "assets/AssetsWorkspace-D_kJ7dP_.js",
176
+ "file": "assets/AssetsWorkspace-Di0BYZZW.js",
177
177
  "name": "AssetsWorkspace",
178
178
  "src": "src/components/assets/AssetsWorkspace.tsx",
179
179
  "isDynamicEntry": true,
180
180
  "imports": [
181
181
  "index.html",
182
- "_KeyingPanel-YkL9305F.js",
183
- "_useAgentDialogFocus-BB6ds9jo.js"
182
+ "_KeyingPanel-BY_xV317.js",
183
+ "_useAgentDialogFocus-DYjA7lCJ.js"
184
184
  ],
185
185
  "css": [
186
186
  "assets/AssetsWorkspace-BkUjPPKU.css"
187
187
  ]
188
188
  },
189
189
  "src/components/canvas-mode/index.ts": {
190
- "file": "assets/index-DBh-jt0i.js",
190
+ "file": "assets/index-VXkP0z69.js",
191
191
  "name": "index",
192
192
  "src": "src/components/canvas-mode/index.ts",
193
193
  "isDynamicEntry": true,
@@ -199,7 +199,7 @@
199
199
  ]
200
200
  },
201
201
  "src/components/card-news/CardNewsWorkspace.tsx": {
202
- "file": "assets/CardNewsWorkspace-DIhk26qD.js",
202
+ "file": "assets/CardNewsWorkspace-F3LrEj0b.js",
203
203
  "name": "CardNewsWorkspace",
204
204
  "src": "src/components/card-news/CardNewsWorkspace.tsx",
205
205
  "isDynamicEntry": true,
@@ -208,7 +208,7 @@
208
208
  ]
209
209
  },
210
210
  "src/components/home/HomeWorkspace.tsx": {
211
- "file": "assets/HomeWorkspace-DwRS4Cim.js",
211
+ "file": "assets/HomeWorkspace-KplbVQT9.js",
212
212
  "name": "HomeWorkspace",
213
213
  "src": "src/components/home/HomeWorkspace.tsx",
214
214
  "isDynamicEntry": true,
@@ -217,7 +217,7 @@
217
217
  ]
218
218
  },
219
219
  "src/components/prompt-builder/PromptBuilderPanel.tsx": {
220
- "file": "assets/PromptBuilderPanel-Bl5-fDQs.js",
220
+ "file": "assets/PromptBuilderPanel-DeSY9dl-.js",
221
221
  "name": "PromptBuilderPanel",
222
222
  "src": "src/components/prompt-builder/PromptBuilderPanel.tsx",
223
223
  "isDynamicEntry": true,