@wrenai/genbi 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ the rest:
25
25
 
26
26
  ## Developer preview
27
27
 
28
- `@wrenai/genbi` is published at `0.0.3`. Nothing here is under a compatibility
28
+ `@wrenai/genbi` is published at `0.0.4`. Nothing here is under a compatibility
29
29
  commitment yet — commands, environment variables and interfaces move without
30
30
  notice. The Codex runtime is supported in code but has not been exercised end
31
31
  to end; Claude is the one that is. See
@@ -31,6 +31,16 @@ import { isWarbleSiblingCheckoutDevModeEnabled, resolveInstalledPackageBin } fro
31
31
  */
32
32
  export async function resolveWarbleBinary(explicit) {
33
33
  if (explicit !== undefined) {
34
+ // A value with no path separator is a command name, not a file path, and is
35
+ // resolved on PATH — the same reading the native-session preflight gives it.
36
+ // Requiring it to exist relative to the working directory made `"warble"` an
37
+ // explicit value that could never resolve, while the preflight found it and
38
+ // reported the producer healthy: one string, two answers.
39
+ if (!explicit.includes(path.sep) && !path.isAbsolute(explicit)) {
40
+ if (await isExecutableOnPath(explicit))
41
+ return explicit;
42
+ throw new WarbleBinaryNotFoundError([`explicit warbleBin "${explicit}" is not on PATH`]);
43
+ }
34
44
  if (!existsSync(explicit)) {
35
45
  throw new WarbleBinaryNotFoundError([`explicit warbleBin "${explicit}" does not exist`]);
36
46
  }
@@ -105,6 +105,7 @@ import { createApp } from "./app.js";
105
105
  import { recoverBootstrapProjectBinding } from "./bootstrap-project-binding.js";
106
106
  import { createHarnessProfileSources } from "./harness-profile-sources.js";
107
107
  import { compileProfile, compileRawProfile } from "../harness/compile/index.js";
108
+ import { resolveWarbleBinary } from "../harness/compile/resolve-binary.js";
108
109
  import { toAuthChoiceFromRuntimeSettings } from "./auth-choice.js";
109
110
  import { Store } from "./db.js";
110
111
  import { resolveEnrichmentBinding, resolveProjectIdentity } from "./enrichment.js";
@@ -170,6 +171,22 @@ async function main() {
170
171
  // (server/turn.ts) and `server/spa.ts`.
171
172
  const candidateStaticDir = path.join(packageRoot, "dist");
172
173
  const staticDir = existsSync(path.join(candidateStaticDir, "index.html")) ? candidateStaticDir : undefined;
174
+ // Resolve the Warble binary once, here, and hand the resolved path to every
175
+ // consumer. Passing the bare name `"warble"` looked like a harmless default and
176
+ // was not: `resolveWarbleBinary` treats any value it is given as an explicit
177
+ // path and only searches when given nothing, so that default disabled the one
178
+ // lookup that finds the `@warble/cli` an install actually ships. It also made
179
+ // two resolvers disagree about the same string — the native preflight resolves
180
+ // a bare name through PATH and reported the producer healthy, while every
181
+ // compile failed.
182
+ const resolvedWarbleBin = await resolveWarbleBinary(flags.warbleBin).catch((error) => {
183
+ // Not fatal: the app still starts, and readiness reports the reason per
184
+ // vendor rather than the process dying at boot over a feature the user may
185
+ // not reach.
186
+ process.stderr.write(`warning: ${error instanceof Error ? error.message : String(error)}\n`);
187
+ return undefined;
188
+ });
189
+ const warbleBinOption = resolvedWarbleBin !== undefined ? { warbleBin: resolvedWarbleBin } : {};
173
190
  const profileSource = flags.profile ?? resolveDefaultProfileSource();
174
191
  // Frozen at boot: GET /api/harness accepts only a purpose and must never
175
192
  // inherit a caller-supplied RouteOptions.profileSource.
@@ -459,7 +476,7 @@ async function main() {
459
476
  binding,
460
477
  artifactRoot,
461
478
  materializationState: nativeMaterializationState,
462
- materialize: () => dispatchInteractiveArtifacts({ warbleBin: flags.warbleBin ?? "warble", irPath: enrichIrPath, target, cwd: artifactRoot, boundProject: binding.path, materializationState: nativeMaterializationState }),
479
+ materialize: () => dispatchInteractiveArtifacts({ warbleBin: resolvedWarbleBin ?? "warble", irPath: enrichIrPath, target, cwd: artifactRoot, boundProject: binding.path, materializationState: nativeMaterializationState }),
463
480
  }, { getCurrentBinding: currentInteractiveBinding });
464
481
  }
465
482
  const nativeMcpUrl = `http://127.0.0.1:${port}/api/native-sessions/mcp`;
@@ -489,11 +506,13 @@ async function main() {
489
506
  resolveDispatchIr: async (purpose, binding) => {
490
507
  const profileSource = harnessProfileSources[purpose];
491
508
  const compiled = purpose === "setup" || binding === undefined
492
- ? await compileRawProfile({ profileSource, mode: "native", warbleBin: flags.warbleBin ?? "warble" })
493
- : await compileProfile({ profileSource, userProject: binding.path, mode: "native", warbleBin: flags.warbleBin ?? "warble" });
509
+ ? await compileRawProfile({ profileSource, mode: "native", ...warbleBinOption })
510
+ : await compileProfile({ profileSource, userProject: binding.path, mode: "native", ...warbleBinOption });
494
511
  return compiled.irPath;
495
512
  },
496
- warbleBin: flags.warbleBin ?? "warble",
513
+ // The service's option is a required string; when resolution failed the bare
514
+ // name is what the preflight will report as unresolvable, with its reason.
515
+ warbleBin: resolvedWarbleBin ?? "warble",
497
516
  ...(process.env["WREN_HARNESS_WREN_SHIM"] !== undefined ? { wrenShim: process.env["WREN_HARNESS_WREN_SHIM"] } : {}),
498
517
  terminalHostAvailable: async () => {
499
518
  try {
@@ -7,6 +7,7 @@ import { execFileSync, spawn } from "node:child_process";
7
7
  import { accessSync, chmodSync, constants, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
8
8
  import os from "node:os";
9
9
  import path from "node:path";
10
+ import { WarbleBinaryNotFoundError } from "../harness/compile/errors.js";
10
11
  import { InteractiveLaunchError, NO_INTERACTIVE_TERMINAL_LEASES, interactiveExecutableAvailable } from "./interactive-terminal.js";
11
12
  import { newId } from "./db.js";
12
13
  import { NATIVE_MCP_CREDENTIAL_ENV_VAR } from "./native-artifacts.js";
@@ -39,11 +40,13 @@ const CODEX_NATIVE_PERMISSION_HEADER = "[permissions.warble_native_wren.filesyst
39
40
  const PROVIDER_SESSION_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
40
41
  const CODEX_RESUME_HANDLE_FILE = path.join(".warble", "codex-thread-id");
41
42
  function logNativeLaunchFailure(purpose, vendor, phase, error) {
42
- const category = error instanceof InteractiveLaunchError ? "rejected" : "unexpected";
43
- // Keep the precise boundary server-side without logging exception text,
44
- // paths, credentials, argv, or other launch material. Browser/API callers
45
- // continue through nativeSessionLaunchFailure's closed public projection.
46
- console.error(`[native-sessions] launch failed purpose=${purpose} vendor=${vendor} phase=${phase} category=${category}`);
43
+ const rejected = error instanceof InteractiveLaunchError;
44
+ // Exception text, paths, credentials and argv stay out of the log for an
45
+ // unexpected error, which may carry any of them. A rejection carries a
46
+ // sentence authored here, and suppressing that turned every diagnosis into a
47
+ // guess: an operator saw a phase and a category and no cause.
48
+ const detail = rejected ? ` reason=${error.message}` : "";
49
+ console.error(`[native-sessions] launch failed purpose=${purpose} vendor=${vendor} phase=${phase} category=${rejected ? "rejected" : "unexpected"}${detail}`);
47
50
  }
48
51
  /**
49
52
  * The producer deliberately sees only a sealed binding identity, never a caller-supplied path.
@@ -352,6 +355,16 @@ export function nativeSessionLaunchFailure(error) {
352
355
  return "native session launch artifacts are unavailable";
353
356
  if (message === "native session launch paths are unavailable" || message === "native session launch paths are outside its server-owned scope")
354
357
  return "native session launch artifacts are unavailable";
358
+ // Every InteractiveLaunchError message is authored in this file: a fixed
359
+ // sentence, a fixed profile name, or a reason readiness already publishes.
360
+ // None is built from a path, argv, or process output. Falling through to the
361
+ // generic string threw away the cause of eighteen distinct failures, which is
362
+ // what left "native session launch failed" as the only thing a user could see.
363
+ if (error instanceof InteractiveLaunchError)
364
+ return message;
365
+ // This one is NOT safe to forward: its text embeds the configured binary path.
366
+ if (error instanceof WarbleBinaryNotFoundError)
367
+ return "the Warble binary could not be resolved";
355
368
  return "native session launch failed";
356
369
  }
357
370
  const NATIVE_PRODUCER_CATEGORY = "native_session_producer_incompatible";
@@ -1310,9 +1323,20 @@ export class NativeSessionService {
1310
1323
  // Compile for this binding rather than dispatching the profile's golden.
1311
1324
  // Falls back to the configured path only where no resolver is wired (the
1312
1325
  // fixture-mode tests), never silently in production — see `bin.ts`.
1313
- const irPath = this.options.resolveDispatchIr === undefined
1314
- ? configuredIr
1315
- : await this.options.resolveDispatchIr(purpose, binding);
1326
+ // Compiling for this binding runs before the launch try/catch below, so a
1327
+ // failure here reached the HTTP route unlogged and unclassified — the case
1328
+ // where the server printed nothing at all and the browser said only "native
1329
+ // session launch failed".
1330
+ let irPath;
1331
+ try {
1332
+ irPath = this.options.resolveDispatchIr === undefined
1333
+ ? configuredIr
1334
+ : await this.options.resolveDispatchIr(purpose, binding);
1335
+ }
1336
+ catch (error) {
1337
+ logNativeLaunchFailure(purpose, dispatchDefinition.provider, "dispatch_ir", error);
1338
+ throw error;
1339
+ }
1316
1340
  const producer = this.legacyFixtureMode() ? undefined : await this.productionProducerPreflight();
1317
1341
  const launchProducer = producer ? producer.vendors[dispatchDefinition.provider] : undefined;
1318
1342
  if (producer && (!launchProducer?.available || !producer.producer)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrenai/genbi",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Wren GenBI — the web UI for WrenAI: ask questions of your data and get dashboards back",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [