wotann 0.5.88 → 0.5.89

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/dist/index.js CHANGED
@@ -416,26 +416,21 @@ program
416
416
  import("./ui/alt-buffer.js"),
417
417
  ]);
418
418
  const { isAltBufferRequested, enterAltBuffer, exitAltBuffer } = altBufferModule;
419
- // Switch to the alternate screen buffer NOW that the heavy modules
420
- // are loaded. Crash-safe — alt-buffer.ts wires SIGINT/SIGTERM/
421
- // uncaughtException to always restore the main buffer. Default ON;
422
- // disable with `--no-fullscreen` or `WOTANN_FULLSCREEN=0`.
423
- if (isAltBufferRequested(options.fullscreen !== false)) {
424
- enterAltBuffer();
425
- }
419
+ const wantsAltBuffer = isAltBufferRequested(options.fullscreen !== false);
426
420
  // Mount the single V3 shell through the ONE guarded gate every
427
- // interactive Ink mount must use: viewport repair (Ink reads
428
- // stdout.columns directly undefined under npx collapses the
429
- // root box to width 0), raw-mode-capable stdin resolution
430
- // (process.stdin → /dev/tty → none), and refuse-cleanly-with-
431
- // guidance instead of mounting Ink into the error-render-loop
432
- // hang ("npx wotann just hangs"). See ui/mount-interactive-ink.ts.
421
+ // interactive Ink mount must use: viewport repair, raw-mode-
422
+ // capable stdin resolution, refuse-cleanly-with-guidance.
423
+ // See src/ui/mount-interactive-ink.ts for the full contract.
433
424
  //
434
- // The try/catch + exitAltBuffer pair is the recovery contract:
435
- // if Ink throws (e.g. setRawMode passes the guard probe but
436
- // fails on Ink's first useInput commit on some terminal combos),
437
- // we ALWAYS restore the main buffer first so the user sees the
438
- // diagnostic instead of a frozen black screen.
425
+ // v0.5.89 hotfix: alt-buffer entry is now gated through
426
+ // `onResolved`, which fires AFTER the guard probe succeeds and
427
+ // BEFORE Ink's first paint. Entering eagerly (v0.5.85 v0.5.88
428
+ // behavior) meant any guard refusal would write the diagnostic
429
+ // INTO the alt-buffer, then exitAltBuffer would tear it down,
430
+ // leaving the user with no visible error message ("npx wotann
431
+ // exits silently" — user-reported 2026-05-24). Now: refusal
432
+ // path NEVER enters alt-buffer, so the diagnostic stays visible
433
+ // in the main buffer.
439
434
  let mountResult;
440
435
  try {
441
436
  mountResult = await mountInteractiveInk(React.createElement(AppV3, {
@@ -444,17 +439,26 @@ program
444
439
  initialModel: interactive.initialModel,
445
440
  initialProvider: interactive.initialProvider,
446
441
  runtime: interactive.runtime,
447
- }));
442
+ }), {
443
+ onResolved: () => {
444
+ if (wantsAltBuffer)
445
+ enterAltBuffer();
446
+ },
447
+ });
448
448
  }
449
449
  catch (error) {
450
+ // If onResolved fired, alt-buffer is up; restore main buffer
451
+ // so the error message is visible. Idempotent — no-op if
452
+ // alt-buffer was never entered.
450
453
  exitAltBuffer();
451
454
  process.stderr.write(`[wotann] Interactive TUI failed to mount: ${error instanceof Error ? error.message : String(error)}\n`);
452
455
  return;
453
456
  }
454
457
  if (mountResult.refused) {
455
- // mountInteractiveInk already wrote actionable guidance to
456
- // stderr. Restore main buffer so guidance is visible instead
457
- // of trapped behind an alt-buffer Ink never painted into.
458
+ // onResolved did NOT fire (guard refused or Ink threw before
459
+ // commit). Alt-buffer was never entered. exitAltBuffer is
460
+ // still safe (idempotent), but the diagnostic mountInteractiveInk
461
+ // wrote to stderr is already visible in the main buffer.
458
462
  exitAltBuffer();
459
463
  return;
460
464
  }
@@ -1946,15 +1950,10 @@ program
1946
1950
  import("./ui/alt-buffer.js"),
1947
1951
  ]);
1948
1952
  const { isAltBufferRequested, enterAltBuffer, exitAltBuffer } = altBufferModule;
1949
- if (isAltBufferRequested(true)) {
1950
- enterAltBuffer();
1951
- }
1952
- // Single V3 shell — initialMessages seeds the transcript so the
1953
- // resumed session continues where it left off (AppV3 wires it via
1954
- // useState([...initialMessages])). Same guarded gate as the start
1955
- // path: viewport repair + raw-mode stdin + refuse-instead-of-hang.
1956
- // try/catch + exitAltBuffer pair ensures the user always sees the
1957
- // diagnostic on Ink-mount failure instead of a frozen black screen.
1953
+ const wantsAltBuffer = isAltBufferRequested(true);
1954
+ // v0.5.89 hotfix: alt-buffer entry is now gated through `onResolved`
1955
+ // so a guard refusal never swallows the diagnostic. See start
1956
+ // command for the full discussion.
1958
1957
  let mountResult;
1959
1958
  try {
1960
1959
  mountResult = await mountInteractiveInk(React.createElement(AppV3, {
@@ -1964,7 +1963,12 @@ program
1964
1963
  initialProvider: session.provider,
1965
1964
  initialMessages: session.messages,
1966
1965
  runtime: interactive.runtime,
1967
- }));
1966
+ }), {
1967
+ onResolved: () => {
1968
+ if (wantsAltBuffer)
1969
+ enterAltBuffer();
1970
+ },
1971
+ });
1968
1972
  }
1969
1973
  catch (error) {
1970
1974
  exitAltBuffer();
@@ -39,6 +39,20 @@ export interface MountInteractiveOptions {
39
39
  };
40
40
  /** Replace the default refusal guidance text. */
41
41
  readonly refusalMessage?: string;
42
+ /**
43
+ * Fires AFTER stdin resolution succeeds and BEFORE Ink's `render`
44
+ * commits its first frame. Lets the caller enter the alt-buffer
45
+ * ONLY when we're committed to mounting — avoiding the
46
+ * "enter-alt-buffer, write refusal in alt-buffer, exit-alt-buffer,
47
+ * refusal swallowed" bug fixed in v0.5.89 (user-reported regression
48
+ * in v0.5.88 where `npx wotann` exited silently because the
49
+ * diagnostic was lost in the alt-buffer tear-down).
50
+ *
51
+ * When the guard refuses or Ink throws, this callback DOES NOT fire,
52
+ * so any caller-side side effects (alt-buffer entry) are skipped and
53
+ * the stderr diagnostic stays visible in the main buffer.
54
+ */
55
+ readonly onResolved?: () => void;
42
56
  }
43
57
  export interface MountInteractiveResult {
44
58
  /**
@@ -55,6 +55,10 @@ export async function mountInteractiveInk(element, opts = {}) {
55
55
  return { instance: null, refused: true };
56
56
  }
57
57
  const inkRender = opts.inkRender ?? (await import("ink")).render;
58
+ // We are committed to mounting — fire the caller's pre-mount hook so
59
+ // it can enter alt-buffer (or any other render-time setup) NOW, after
60
+ // the guard passed and before Ink's first frame paints.
61
+ opts.onResolved?.();
58
62
  try {
59
63
  const instance = inkRender(element, { stdin: inputStdin });
60
64
  return { instance, refused: false };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wotann",
3
- "version": "0.5.88",
3
+ "version": "0.5.89",
4
4
  "description": "WOTANN — The All-Father of AI Agent Harnesses",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",