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.
- package/docs/migration/runtime-test-inventory.md +3 -1
- package/lib/grokProxyLauncher.js +110 -1
- package/package.json +2 -2
- package/routes/auth.js +6 -3
- package/routes/grok.js +13 -3
- package/routes/index.js +1 -1
- package/routes/models.js +33 -4
- package/server.js +46 -23
- package/ui/dist/.vite/manifest.json +36 -36
- package/ui/dist/assets/{AgentWorkspace-DU9efUdT.js → AgentWorkspace-JQ0K_ndE.js} +1 -1
- package/ui/dist/assets/{AssetGenWorkspace-ldEVBldg.js → AssetGenWorkspace-B2d-aiIb.js} +2 -2
- package/ui/dist/assets/{AssetsWorkspace-D_kJ7dP_.js → AssetsWorkspace-Di0BYZZW.js} +1 -1
- package/ui/dist/assets/{CardNewsWorkspace-DIhk26qD.js → CardNewsWorkspace-F3LrEj0b.js} +1 -1
- package/ui/dist/assets/{GenerationRequestLogPanel-BZZJh4w_.js → GenerationRequestLogPanel-CpyvTE6t.js} +1 -1
- package/ui/dist/assets/{HomeWorkspace-DwRS4Cim.js → HomeWorkspace-KplbVQT9.js} +1 -1
- package/ui/dist/assets/{KeyingPanel-YkL9305F.js → KeyingPanel-BY_xV317.js} +1 -1
- package/ui/dist/assets/{NodeCanvas-CaFDJn_9.js → NodeCanvas-scDxt0LA.js} +1 -1
- package/ui/dist/assets/{PromptBuilderPanel-Bl5-fDQs.js → PromptBuilderPanel-DeSY9dl-.js} +1 -1
- package/ui/dist/assets/{PromptImportDialog-5RbU95_Y.js → PromptImportDialog-BTP0imZl.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-uhS5xI0p.js → PromptImportDiscoverySection-BBThhJZM.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-COwFVcbE.js → PromptImportFolderSection-6sXWWJoK.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-DCtxfzy1.js → PromptLibraryPanel-BYTG-mBa.js} +2 -2
- package/ui/dist/assets/{SettingsWorkspace-aHoTeB4-.js → SettingsWorkspace-chsddbrB.js} +1 -1
- package/ui/dist/assets/{SpriteRecipeWorkspace-B0fR6yXF.js → SpriteRecipeWorkspace-HgacgO0x.js} +1 -1
- package/ui/dist/assets/{index-Bj2sxDP3.js → index-BqFlxfzT.js} +2 -2
- package/ui/dist/assets/{index-DBh-jt0i.js → index-VXkP0z69.js} +3 -3
- package/ui/dist/assets/index-waPOg2eb.css +1 -0
- package/ui/dist/assets/{pptxgen.es-AWrWIHuf.js → pptxgen.es-DX8PxAzM.js} +1 -1
- package/ui/dist/assets/{useAgentDialogFocus-BB6ds9jo.js → useAgentDialogFocus-DYjA7lCJ.js} +1 -1
- package/ui/dist/index.html +2 -2
- 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:
|
|
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`
|
package/lib/grokProxyLauncher.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
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
|
|
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-
|
|
3
|
-
"file": "assets/AssetGenWorkspace-
|
|
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-
|
|
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
|
-
"
|
|
22
|
-
"file": "assets/
|
|
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-
|
|
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-
|
|
36
|
-
"file": "assets/useAgentDialogFocus-
|
|
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-
|
|
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-
|
|
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-
|
|
63
|
+
"assets/index-waPOg2eb.css"
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"node_modules/pptxgenjs/dist/pptxgen.es.js": {
|
|
67
|
-
"file": "assets/pptxgen.es-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
160
|
+
"_useAgentDialogFocus-DYjA7lCJ.js"
|
|
161
161
|
]
|
|
162
162
|
},
|
|
163
163
|
"src/components/assetgen/SpriteRecipeWorkspace.tsx": {
|
|
164
|
-
"file": "assets/SpriteRecipeWorkspace-
|
|
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-
|
|
171
|
-
"_KeyingPanel-
|
|
172
|
-
"_useAgentDialogFocus-
|
|
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-
|
|
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-
|
|
183
|
-
"_useAgentDialogFocus-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
220
|
+
"file": "assets/PromptBuilderPanel-DeSY9dl-.js",
|
|
221
221
|
"name": "PromptBuilderPanel",
|
|
222
222
|
"src": "src/components/prompt-builder/PromptBuilderPanel.tsx",
|
|
223
223
|
"isDynamicEntry": true,
|