wotann 0.5.92 → 0.5.93

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.
@@ -46,10 +46,26 @@ export interface ResolveStdinOptions {
46
46
  }
47
47
  /**
48
48
  * Resolve an input stream Ink can drive `useInput()` with:
49
- * 1. `stdin` itself if it is already raw-capable.
50
- * 2. else the controlling terminal (`/dev/tty`) if raw-capable.
49
+ * 1. controlling terminal (`/dev/tty`) if raw-capable — preferred.
50
+ * 2. else `stdin` if it is raw-capable — fallback for CI / Windows /
51
+ * detached processes that have no controlling terminal.
51
52
  * 3. else `null` — caller MUST NOT mount the interactive Ink app
52
53
  * (it would hang); print guidance + exit instead.
54
+ *
55
+ * Why /dev/tty FIRST (changed 2026-05-28 from stdin-first):
56
+ * Under `npx wotann`, `npm exec`, `sudo`, and other launchers, the
57
+ * subprocess's `process.stdin` can pass the static shape check
58
+ * (`isTTY === true`, `setRawMode` is a function) yet behave oddly
59
+ * when Ink actually drives it — the probe's no-op `setRawMode(false)`
60
+ * succeeds while Ink's `setRawMode(true)` then fails, the stream is in
61
+ * flowing mode because some boot import attached a `data` listener,
62
+ * the launcher transformed the descriptor, etc. The controlling
63
+ * terminal is the canonical "user's keyboard" source and is
64
+ * unaffected by any of that — the same pattern `less`, `vim`, `sudo`,
65
+ * and `git rebase -i` use for stdin-needs-tty scenarios. The fallback
66
+ * to `stdin` covers the no-`/dev/tty` cases (CI containers, Windows
67
+ * without a console, detached processes).
68
+ *
53
69
  * Never throws.
54
70
  */
55
71
  export declare function resolveInteractiveStdin(stdin?: unknown, opts?: ResolveStdinOptions): NodeJS.ReadStream | null;
@@ -77,10 +93,14 @@ export interface StdinDiagnosis {
77
93
  readonly ttyProbeResult?: string;
78
94
  }
79
95
  /**
80
- * Walks the same `resolveInteractiveStdin` steps and records what each
81
- * one saw. Use this when the resolver returns `null` to print a
82
- * self-explanatory refusal future bug reports will include the
83
- * diagnosis automatically instead of just "guard refused, why?".
96
+ * Records the full state of BOTH potential input sources — /dev/tty
97
+ * (the preferred source after the 2026-05-28 reorder) and `stdin`
98
+ * (the fallback). Always walks both regardless of whether the
99
+ * resolver would have short-circuited, because the diagnostic is most
100
+ * useful when something looks wrong with one source: the user / next
101
+ * agent gets the full picture, not "stdin worked, didn't look at tty".
102
+ *
103
+ * Never throws.
84
104
  */
85
105
  export declare function diagnoseStdin(stdin?: unknown, opts?: ResolveStdinOptions): StdinDiagnosis;
86
106
  /**
@@ -72,24 +72,42 @@ function defaultOpenControllingTty() {
72
72
  }
73
73
  /**
74
74
  * Resolve an input stream Ink can drive `useInput()` with:
75
- * 1. `stdin` itself if it is already raw-capable.
76
- * 2. else the controlling terminal (`/dev/tty`) if raw-capable.
75
+ * 1. controlling terminal (`/dev/tty`) if raw-capable — preferred.
76
+ * 2. else `stdin` if it is raw-capable — fallback for CI / Windows /
77
+ * detached processes that have no controlling terminal.
77
78
  * 3. else `null` — caller MUST NOT mount the interactive Ink app
78
79
  * (it would hang); print guidance + exit instead.
80
+ *
81
+ * Why /dev/tty FIRST (changed 2026-05-28 from stdin-first):
82
+ * Under `npx wotann`, `npm exec`, `sudo`, and other launchers, the
83
+ * subprocess's `process.stdin` can pass the static shape check
84
+ * (`isTTY === true`, `setRawMode` is a function) yet behave oddly
85
+ * when Ink actually drives it — the probe's no-op `setRawMode(false)`
86
+ * succeeds while Ink's `setRawMode(true)` then fails, the stream is in
87
+ * flowing mode because some boot import attached a `data` listener,
88
+ * the launcher transformed the descriptor, etc. The controlling
89
+ * terminal is the canonical "user's keyboard" source and is
90
+ * unaffected by any of that — the same pattern `less`, `vim`, `sudo`,
91
+ * and `git rebase -i` use for stdin-needs-tty scenarios. The fallback
92
+ * to `stdin` covers the no-`/dev/tty` cases (CI containers, Windows
93
+ * without a console, detached processes).
94
+ *
79
95
  * Never throws.
80
96
  */
81
97
  export function resolveInteractiveStdin(stdin = process.stdin, opts = {}) {
82
- if (isRawModeCapable(stdin))
83
- return stdin;
84
98
  const open = opts.openControllingTty ?? defaultOpenControllingTty;
85
99
  let tty = null;
86
100
  try {
87
101
  tty = open();
88
102
  }
89
103
  catch {
90
- return null;
104
+ tty = null;
91
105
  }
92
- return isRawModeCapable(tty) ? tty : null;
106
+ if (tty !== null && isRawModeCapable(tty))
107
+ return tty;
108
+ if (isRawModeCapable(stdin))
109
+ return stdin;
110
+ return null;
93
111
  }
94
112
  /**
95
113
  * Same observation pattern as `isRawModeCapable` but captures the
@@ -123,21 +141,16 @@ function probeStdinDetail(stream) {
123
141
  }
124
142
  }
125
143
  /**
126
- * Walks the same `resolveInteractiveStdin` steps and records what each
127
- * one saw. Use this when the resolver returns `null` to print a
128
- * self-explanatory refusal future bug reports will include the
129
- * diagnosis automatically instead of just "guard refused, why?".
144
+ * Records the full state of BOTH potential input sources — /dev/tty
145
+ * (the preferred source after the 2026-05-28 reorder) and `stdin`
146
+ * (the fallback). Always walks both regardless of whether the
147
+ * resolver would have short-circuited, because the diagnostic is most
148
+ * useful when something looks wrong with one source: the user / next
149
+ * agent gets the full picture, not "stdin worked, didn't look at tty".
150
+ *
151
+ * Never throws.
130
152
  */
131
153
  export function diagnoseStdin(stdin = process.stdin, opts = {}) {
132
- const stdinProbe = probeStdinDetail(stdin);
133
- if (stdinProbe.probeResult === "ok") {
134
- return {
135
- stdinIsTTY: stdinProbe.isTTY,
136
- stdinHasSetRawMode: stdinProbe.hasSetRawMode,
137
- stdinProbeResult: stdinProbe.probeResult,
138
- ttyOpenResult: "not-attempted",
139
- };
140
- }
141
154
  const open = opts.openControllingTty ?? defaultOpenControllingTty;
142
155
  let tty = null;
143
156
  let ttyOpenResult;
@@ -148,6 +161,7 @@ export function diagnoseStdin(stdin = process.stdin, opts = {}) {
148
161
  catch (e) {
149
162
  ttyOpenResult = e instanceof Error ? e.message : String(e);
150
163
  }
164
+ const stdinProbe = probeStdinDetail(stdin);
151
165
  if (tty === null) {
152
166
  return {
153
167
  stdinIsTTY: stdinProbe.isTTY,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wotann",
3
- "version": "0.5.92",
3
+ "version": "0.5.93",
4
4
  "description": "WOTANN — The All-Father of AI Agent Harnesses",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",