From a2eaa1d2707cf4eb8140ed38a3ded928198b283a Mon Sep 17 00:00:00 2001 From: jharris1679 Date: Thu, 28 May 2026 17:18:24 -0400 Subject: [PATCH] fix: auto-start queued generation jobs on create The backend only dispatches a generation job's worker when a client opens the SSE stream at /api/v1/semantic/jobs/{id}/stream. The UI does this automatically after create; the CLI's `generation start` returned the queued JobResponse and exited, so jobs sat QUEUED indefinitely until somebody opened the stream. After a successful create, fire a one-shot fetch to the stream URL and abort as soon as the first chunk arrives. That's enough for the server to dispatch the worker without the CLI buffering or printing the event stream. Errors during the kick are swallowed so a create still appears successful even if the stream endpoint is briefly unavailable. --- src/cli.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/cli.js b/src/cli.js index b854d8e..c8165cd 100644 --- a/src/cli.js +++ b/src/cli.js @@ -515,9 +515,15 @@ async function handleInquiry(client, command, positionals, parsed, io) { async function handleGeneration(client, command, positionals, parsed, io) { const base = "/api/v1/semantic/jobs"; if (command === "start" || command === "create") { - return requestAndPrint(client, "POST", base, parsed, io, { + const result = await client.rawRequest("POST", base, { body: await readData(parsed.flags, io), }); + const jobId = result.data && result.data.id; + if (jobId) { + await kickJobWorker(client, `${base}/${encodeURIComponent(jobId)}/stream`); + } + write(io.stdout, formatJson(result.data)); + return; } if (command === "list" || !command) { return requestAndPrint(client, "GET", base, parsed, io, { @@ -998,6 +1004,28 @@ async function readData(flags, io, defaults = {}) { return dropUndefined({ ...defaults, ...data }); } +async function kickJobWorker(client, streamPath) { + // Backend only starts a queued generation job when something opens the SSE + // stream. Hit the stream just long enough for the server to dispatch the + // worker, then abort so the CLI returns promptly. + const controller = new AbortController(); + const url = new URL(`${client.baseUrl}${streamPath}`); + const headers = { Accept: "text/event-stream", "User-Agent": "answerlayer-cli/0.1.0" }; + if (client.apiKey) headers["X-API-Key"] = client.apiKey; + try { + const response = await client.fetchImpl(url, { method: "GET", headers, signal: controller.signal }); + if (response.body && typeof response.body.getReader === "function") { + const reader = response.body.getReader(); + await reader.read(); + try { await reader.cancel(); } catch {} + } + } catch { + // Best-effort kick; ignore network/abort errors so create still succeeds. + } finally { + controller.abort(); + } +} + function multipart({ file, fields = {} }) { const form = new FormData(); for (const [key, value] of Object.entries(dropUndefined(fields))) {