wotann 0.5.83 → 0.5.85
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
|
@@ -260,7 +260,9 @@ program
|
|
|
260
260
|
try {
|
|
261
261
|
const { createRuntime } = await import("./core/runtime.js");
|
|
262
262
|
const { runRuntimeQuery } = await import("./cli/runtime-query.js");
|
|
263
|
-
const jsonSchema = options.jsonSchema !== undefined
|
|
263
|
+
const jsonSchema = options.jsonSchema !== undefined
|
|
264
|
+
? parseJsonSchemaOption(options.jsonSchema)
|
|
265
|
+
: undefined;
|
|
264
266
|
const prContext = options.fromPr !== undefined ? loadPullRequestContext(options.fromPr) : undefined;
|
|
265
267
|
const runtime = await createRuntime(process.cwd(), undefined, {
|
|
266
268
|
...(options.bare
|
|
@@ -402,13 +404,22 @@ program
|
|
|
402
404
|
process.stderr.write("[wotann] Unknown renderer. Use --renderer ink or --renderer opentui.\n");
|
|
403
405
|
return;
|
|
404
406
|
}
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
const {
|
|
407
|
+
// Pre-load the heavy UI modules BEFORE entering alt-buffer so the
|
|
408
|
+
// window between alt-buffer entry and first Ink frame is near-zero.
|
|
409
|
+
// Otherwise the user stares at a pitch-black alt-buffer while
|
|
410
|
+
// AppV3 + Ink + react-reconciler hydrate, which is exactly the
|
|
411
|
+
// "npx wotann hangs and shows a black screen" symptom users have
|
|
412
|
+
// reported (2026-05-23 P0). Parallel imports cut the wait further.
|
|
413
|
+
const [{ AppV3 }, { mountInteractiveInk }, altBufferModule] = await Promise.all([
|
|
414
|
+
import("./ui/components/v3/index.js"),
|
|
415
|
+
import("./ui/mount-interactive-ink.js"),
|
|
416
|
+
import("./ui/alt-buffer.js"),
|
|
417
|
+
]);
|
|
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`.
|
|
412
423
|
if (isAltBufferRequested(options.fullscreen !== false)) {
|
|
413
424
|
enterAltBuffer();
|
|
414
425
|
}
|
|
@@ -419,17 +430,34 @@ program
|
|
|
419
430
|
// (process.stdin → /dev/tty → none), and refuse-cleanly-with-
|
|
420
431
|
// guidance instead of mounting Ink into the error-render-loop
|
|
421
432
|
// hang ("npx wotann just hangs"). See ui/mount-interactive-ink.ts.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
433
|
+
//
|
|
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.
|
|
439
|
+
let mountResult;
|
|
440
|
+
try {
|
|
441
|
+
mountResult = await mountInteractiveInk(React.createElement(AppV3, {
|
|
442
|
+
version: VERSION,
|
|
443
|
+
providers: interactive.providers,
|
|
444
|
+
initialModel: interactive.initialModel,
|
|
445
|
+
initialProvider: interactive.initialProvider,
|
|
446
|
+
runtime: interactive.runtime,
|
|
447
|
+
}));
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
exitAltBuffer();
|
|
451
|
+
process.stderr.write(`[wotann] Interactive TUI failed to mount: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
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
|
+
exitAltBuffer();
|
|
432
459
|
return;
|
|
460
|
+
}
|
|
433
461
|
});
|
|
434
462
|
// ── wotann cli-generate (CLI-Anything 7-phase generator) ─────────
|
|
435
463
|
program
|
|
@@ -1908,10 +1936,16 @@ program
|
|
|
1908
1936
|
console.log(chalk.green(" Session context restored. Continue where you left off.\n"));
|
|
1909
1937
|
const ReactModule = await import("react");
|
|
1910
1938
|
const React = ReactModule.default;
|
|
1911
|
-
//
|
|
1912
|
-
// (
|
|
1913
|
-
// `
|
|
1914
|
-
|
|
1939
|
+
// Pre-load heavy UI modules in parallel BEFORE entering alt-buffer
|
|
1940
|
+
// (npx pitch-black fix 2026-05-23). Same fullscreen-mode hook as
|
|
1941
|
+
// `wotann start` — env var only here (no CLI flag on `wotann
|
|
1942
|
+
// resume`). Default ON; disable via `WOTANN_FULLSCREEN=0`.
|
|
1943
|
+
const [{ AppV3 }, { mountInteractiveInk }, altBufferModule] = await Promise.all([
|
|
1944
|
+
import("./ui/components/v3/index.js"),
|
|
1945
|
+
import("./ui/mount-interactive-ink.js"),
|
|
1946
|
+
import("./ui/alt-buffer.js"),
|
|
1947
|
+
]);
|
|
1948
|
+
const { isAltBufferRequested, enterAltBuffer, exitAltBuffer } = altBufferModule;
|
|
1915
1949
|
if (isAltBufferRequested(true)) {
|
|
1916
1950
|
enterAltBuffer();
|
|
1917
1951
|
}
|
|
@@ -1919,18 +1953,28 @@ program
|
|
|
1919
1953
|
// resumed session continues where it left off (AppV3 wires it via
|
|
1920
1954
|
// useState([...initialMessages])). Same guarded gate as the start
|
|
1921
1955
|
// path: viewport repair + raw-mode stdin + refuse-instead-of-hang.
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1956
|
+
// try/catch + exitAltBuffer pair ensures the user always sees the
|
|
1957
|
+
// diagnostic on Ink-mount failure instead of a frozen black screen.
|
|
1958
|
+
let mountResult;
|
|
1959
|
+
try {
|
|
1960
|
+
mountResult = await mountInteractiveInk(React.createElement(AppV3, {
|
|
1961
|
+
version: VERSION,
|
|
1962
|
+
providers: interactive.providers,
|
|
1963
|
+
initialModel: session.model,
|
|
1964
|
+
initialProvider: session.provider,
|
|
1965
|
+
initialMessages: session.messages,
|
|
1966
|
+
runtime: interactive.runtime,
|
|
1967
|
+
}));
|
|
1968
|
+
}
|
|
1969
|
+
catch (error) {
|
|
1970
|
+
exitAltBuffer();
|
|
1971
|
+
process.stderr.write(`[wotann] Interactive TUI failed to mount: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1933
1972
|
return;
|
|
1973
|
+
}
|
|
1974
|
+
if (mountResult.refused) {
|
|
1975
|
+
exitAltBuffer();
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1934
1978
|
});
|
|
1935
1979
|
// ── wotann rewind ───────────────────────────────────────────
|
|
1936
1980
|
//
|
|
@@ -56,6 +56,14 @@ export interface MountInteractiveResult {
|
|
|
56
56
|
* degraded-terminal case — it returns `{ instance: null, refused: true }`
|
|
57
57
|
* after writing actionable guidance, so the caller exits cleanly
|
|
58
58
|
* instead of hanging.
|
|
59
|
+
*
|
|
60
|
+
* Ink-render-throws backstop (added 2026-05-23, npx pitch-black fix):
|
|
61
|
+
* even with the raw-mode-guard probe in front, Ink's reconciler can
|
|
62
|
+
* still throw during its first `useInput` commit on terminal combos
|
|
63
|
+
* where `setRawMode` exists, passes the probe, then fails on the
|
|
64
|
+
* *specific* `(true)` call Ink makes. Catching here and returning
|
|
65
|
+
* `refused: true` lets the caller exit the alt-buffer + show the
|
|
66
|
+
* user the actual failure instead of a frozen black screen.
|
|
59
67
|
*/
|
|
60
68
|
export declare function mountInteractiveInk(element: ReactElement, opts?: MountInteractiveOptions): Promise<MountInteractiveResult>;
|
|
61
69
|
export {};
|
|
@@ -33,6 +33,14 @@ export const DEFAULT_REFUSAL_MESSAGE = "[wotann] No raw-mode-capable terminal av
|
|
|
33
33
|
* degraded-terminal case — it returns `{ instance: null, refused: true }`
|
|
34
34
|
* after writing actionable guidance, so the caller exits cleanly
|
|
35
35
|
* instead of hanging.
|
|
36
|
+
*
|
|
37
|
+
* Ink-render-throws backstop (added 2026-05-23, npx pitch-black fix):
|
|
38
|
+
* even with the raw-mode-guard probe in front, Ink's reconciler can
|
|
39
|
+
* still throw during its first `useInput` commit on terminal combos
|
|
40
|
+
* where `setRawMode` exists, passes the probe, then fails on the
|
|
41
|
+
* *specific* `(true)` call Ink makes. Catching here and returning
|
|
42
|
+
* `refused: true` lets the caller exit the alt-buffer + show the
|
|
43
|
+
* user the actual failure instead of a frozen black screen.
|
|
36
44
|
*/
|
|
37
45
|
export async function mountInteractiveInk(element, opts = {}) {
|
|
38
46
|
const ensureViewport = opts.ensureViewport ?? ensureRenderableViewport;
|
|
@@ -47,6 +55,18 @@ export async function mountInteractiveInk(element, opts = {}) {
|
|
|
47
55
|
return { instance: null, refused: true };
|
|
48
56
|
}
|
|
49
57
|
const inkRender = opts.inkRender ?? (await import("ink")).render;
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
try {
|
|
59
|
+
const instance = inkRender(element, { stdin: inputStdin });
|
|
60
|
+
return { instance, refused: false };
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const sink = opts.stderr ?? process.stderr;
|
|
64
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
65
|
+
sink.write(`[wotann] Interactive TUI failed to mount: ${message}\n` +
|
|
66
|
+
" This usually means the terminal accepted setRawMode but Ink's\n" +
|
|
67
|
+
" first input commit threw under this launcher. Options:\n" +
|
|
68
|
+
" • `npm i -g wotann` then run `wotann` in a fresh terminal\n" +
|
|
69
|
+
' • non-interactive: `wotann -p "your prompt"`\n');
|
|
70
|
+
return { instance: null, refused: true };
|
|
71
|
+
}
|
|
52
72
|
}
|
|
@@ -18,11 +18,26 @@
|
|
|
18
18
|
*/
|
|
19
19
|
export interface RawModeStream {
|
|
20
20
|
readonly isTTY?: unknown;
|
|
21
|
+
readonly isRaw?: unknown;
|
|
21
22
|
setRawMode?: unknown;
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
|
-
* True iff `stream` is a TTY that exposes a `setRawMode` function
|
|
25
|
-
* exactly Ink's precondition
|
|
25
|
+
* True iff `stream` is a TTY that exposes a `setRawMode` function AND
|
|
26
|
+
* that function works when invoked — exactly Ink's runtime precondition
|
|
27
|
+
* for `useInput()`. Never throws.
|
|
28
|
+
*
|
|
29
|
+
* The shape check (`isTTY === true` + `typeof setRawMode === "function"`)
|
|
30
|
+
* caught one silent-failure class — degraded stdin — but missed a
|
|
31
|
+
* second one: some `npx`/terminal combinations expose `setRawMode` as
|
|
32
|
+
* a function that THROWS the moment Ink invokes it during the first
|
|
33
|
+
* `useInput` hook. The property-only check passed, Ink mounted into
|
|
34
|
+
* an alt-buffer, Ink's reconciler error-looped on the rejected raw
|
|
35
|
+
* mode call, and the user saw the reported "pitch-black screen, TUI
|
|
36
|
+
* never paints" symptom. The probe below catches that case: call
|
|
37
|
+
* `setRawMode` with the CURRENT raw-state (idempotent — terminal
|
|
38
|
+
* state is preserved when it works) inside a try/catch. If it throws
|
|
39
|
+
* here, the resolver falls through to `/dev/tty` or refuses cleanly
|
|
40
|
+
* instead of mounting Ink into the silent loop.
|
|
26
41
|
*/
|
|
27
42
|
export declare function isRawModeCapable(stream: unknown): boolean;
|
|
28
43
|
export interface ResolveStdinOptions {
|
|
@@ -19,14 +19,38 @@
|
|
|
19
19
|
import { openSync } from "node:fs";
|
|
20
20
|
import { ReadStream } from "node:tty";
|
|
21
21
|
/**
|
|
22
|
-
* True iff `stream` is a TTY that exposes a `setRawMode` function
|
|
23
|
-
* exactly Ink's precondition
|
|
22
|
+
* True iff `stream` is a TTY that exposes a `setRawMode` function AND
|
|
23
|
+
* that function works when invoked — exactly Ink's runtime precondition
|
|
24
|
+
* for `useInput()`. Never throws.
|
|
25
|
+
*
|
|
26
|
+
* The shape check (`isTTY === true` + `typeof setRawMode === "function"`)
|
|
27
|
+
* caught one silent-failure class — degraded stdin — but missed a
|
|
28
|
+
* second one: some `npx`/terminal combinations expose `setRawMode` as
|
|
29
|
+
* a function that THROWS the moment Ink invokes it during the first
|
|
30
|
+
* `useInput` hook. The property-only check passed, Ink mounted into
|
|
31
|
+
* an alt-buffer, Ink's reconciler error-looped on the rejected raw
|
|
32
|
+
* mode call, and the user saw the reported "pitch-black screen, TUI
|
|
33
|
+
* never paints" symptom. The probe below catches that case: call
|
|
34
|
+
* `setRawMode` with the CURRENT raw-state (idempotent — terminal
|
|
35
|
+
* state is preserved when it works) inside a try/catch. If it throws
|
|
36
|
+
* here, the resolver falls through to `/dev/tty` or refuses cleanly
|
|
37
|
+
* instead of mounting Ink into the silent loop.
|
|
24
38
|
*/
|
|
25
39
|
export function isRawModeCapable(stream) {
|
|
26
40
|
if (stream === null || typeof stream !== "object")
|
|
27
41
|
return false;
|
|
28
42
|
const s = stream;
|
|
29
|
-
|
|
43
|
+
if (s.isTTY !== true || typeof s.setRawMode !== "function")
|
|
44
|
+
return false;
|
|
45
|
+
try {
|
|
46
|
+
const setRawMode = s.setRawMode;
|
|
47
|
+
const currentIsRaw = s.isRaw === true;
|
|
48
|
+
setRawMode(currentIsRaw);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
30
54
|
}
|
|
31
55
|
/**
|
|
32
56
|
* Open the controlling terminal as a raw-capable input stream, or
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wotann",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.85",
|
|
4
4
|
"description": "WOTANN — The All-Father of AI Agent Harnesses",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -62,7 +62,6 @@
|
|
|
62
62
|
"tokens:check": "node scripts/generate-tokens.mjs --check",
|
|
63
63
|
"sounds:generate": "node scripts/generate-sound-cues.mjs",
|
|
64
64
|
"sounds:check": "node scripts/generate-sound-cues.mjs --check-only",
|
|
65
|
-
"prepare": "node scripts/build-typescript.mjs && chmod +x dist/index.js",
|
|
66
65
|
"prepublishOnly": "npm run build:clean",
|
|
67
66
|
"postinstall": "node -e \"try { require('node:fs').chmodSync('dist/index.js', 0o755); } catch {}\"",
|
|
68
67
|
"wotann": "tsx src/index.ts",
|