@wrongstack/tui 1.0.27 → 1.0.29

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.
@@ -1,8 +1,39 @@
1
1
  import { type KeyEvent } from './components/input.js';
2
2
  import type { AppKeyHandlerOptions } from './key-handler-context.js';
3
3
  export type { AppKeyHandlerOptions } from './key-handler-context.js';
4
- /** Keyboard activity means the user has taken control of an armed automatic turn. */
5
- export declare function stopNextStepsAutoSubmitOnKey(cancel: () => void): void;
4
+ /** One delivered input event, as the host passes it to `handleKey`. */
5
+ export interface NextStepsTakeoverEvent {
6
+ /** Text payload of the event — empty for special keys and pointer reports. */
7
+ input: string;
8
+ key: KeyEvent;
9
+ /**
10
+ * Current composer buffer. The armed submit clears the composer when it
11
+ * fires, so text sitting here is what makes ANY event a takeover.
12
+ */
13
+ draft: string;
14
+ }
15
+ /**
16
+ * True when this event means the user has taken control of an armed automatic
17
+ * turn, i.e. the armed next-steps submit must be cancelled.
18
+ *
19
+ * Pointer reports are NOT keyboard activity: the input layer emits mouse
20
+ * press/release/wheel/move as `KeyEvent`s carrying `mouse` (see
21
+ * `components/input.tsx`, which builds them as `EMPTY_KEY` + `mouse`). Treating
22
+ * them as input made a plain click, a wheel scroll, or a drag through the
23
+ * transcript look like typing and silently killed the armed countdown, so the
24
+ * automatic turn never advanced.
25
+ *
26
+ * The same reasoning covers keyboard navigation: `PgUp`/`PgDn`, the caret
27
+ * keys, and the Ctrl+U/Ctrl+D paging chord move through the transcript exactly
28
+ * like the wheel does, so on an empty composer they leave the countdown armed
29
+ * too. Everything that can put text in the composer — typing, editing, pasting,
30
+ * ↑/↓ history recall, Tab accepting the suggestion, Enter — still cancels, and
31
+ * so does any event at all once the composer holds text, because the armed
32
+ * submit calls `clearDraft()` when it fires.
33
+ */
34
+ export declare function shouldStopNextStepsAutoSubmit(event: NextStepsTakeoverEvent): boolean;
35
+ /** Cancel an armed automatic turn when this event is a real user takeover. */
36
+ export declare function stopNextStepsAutoSubmitOnKey(cancel: () => void, event: NextStepsTakeoverEvent): void;
6
37
  /** Creates the terminal key host around focused overlay/input routers. */
7
38
  export declare function createAppKeyHandler(options: AppKeyHandlerOptions): (input: string, key: KeyEvent) => Promise<void>;
8
39
  //# sourceMappingURL=app-key-handler.d.ts.map
@@ -42,6 +42,13 @@ export interface AuthOAuthStrategyRow {
42
42
  providerId: string;
43
43
  label: string;
44
44
  description?: string | undefined;
45
+ /**
46
+ * Extended guidance, one entry per paragraph (mirrors
47
+ * `ProviderAuthStrategyMetadata.notes`). Unlike `description` this may be
48
+ * several sentences, so the oauth view renders it wrapped in the
49
+ * focused-strategy detail area, not inline on the row.
50
+ */
51
+ notes?: readonly string[] | undefined;
45
52
  }
46
53
  /** Strategy ids are runtime-extensible. */
47
54
  export type AuthOAuthKind = string;
package/dist/index.js CHANGED
@@ -42278,7 +42278,7 @@ function KanbanPanel({
42278
42278
  setPrompt({ ...prompt, buffer: prompt.buffer.slice(0, -1) });
42279
42279
  return;
42280
42280
  }
42281
- if (input && !key2.ctrl && !key2.meta && input >= " ") {
42281
+ if (input && !key2.ctrl && !key2.meta && input >= " " && !isLeakedMouseInput(input)) {
42282
42282
  setPrompt({ ...prompt, buffer: `${prompt.buffer}${input}` });
42283
42283
  }
42284
42284
  return;
@@ -47456,6 +47456,23 @@ function keySummary(keyRow) {
47456
47456
  const created = keyRow.createdAt ? ` ${keyRow.createdAt.slice(0, 10)}` : "";
47457
47457
  return `${keyRow.masked}${method}${created}`;
47458
47458
  }
47459
+ var MAX_OAUTH_NOTE_LINES = 8;
47460
+ function wrapNoteText(text, width) {
47461
+ const words = text.split(/\s+/).filter((w) => w.length > 0);
47462
+ if (words.length === 0) return [];
47463
+ const lines = [];
47464
+ let cur = words[0] ?? "";
47465
+ for (const word of words.slice(1)) {
47466
+ if (cur.length + 1 + word.length <= width) {
47467
+ cur += ` ${word}`;
47468
+ continue;
47469
+ }
47470
+ lines.push(cur);
47471
+ cur = word.length > width ? word.slice(0, width) : word;
47472
+ }
47473
+ lines.push(cur);
47474
+ return lines;
47475
+ }
47459
47476
  function renderRow(row, focused, i) {
47460
47477
  const marker = focused ? "\u203A" : " ";
47461
47478
  const rowColor = focused ? UI_COLORS.focused : void 0;
@@ -47646,7 +47663,21 @@ function AuthPanel({ panel, maxRows, columns }) {
47646
47663
  const size = useTerminalSize();
47647
47664
  const budget = maxRows ?? Math.max(8, size.rows - 7);
47648
47665
  const compact2 = budget < 14;
47649
- const extraRows = (panel.hint ? 1 : 0) + (panel.confirm ? compact2 ? 1 : 2 : 0) + (panel.input ? compact2 ? 1 : 2 : 0) + (panel.view === "catalog" ? compact2 ? 1 : 2 : 0) + (!compact2 && panel.view === "provider" ? 2 : 0) + (!compact2 && panel.view === "oauth" ? 3 : 0) + (!compact2 && panel.view === "list" && panel.providers.length === 0 ? 2 : 0) + (!compact2 && panel.view === "catalog" && panel.busy ? 2 : 0);
47666
+ const noteWidth = Math.max(20, (columns ?? size.columns) - 4);
47667
+ const oauthFocus = panel.view === "oauth" ? panel.oauthStrategies?.[panel.selected] : void 0;
47668
+ const oauthNoteLines = !compact2 && oauthFocus?.notes ? oauthFocus.notes.flatMap(
47669
+ (note, i) => (
47670
+ // Blank separator between paragraphs; a space (not '') so Ink
47671
+ // emits the row and the reserve below matches the render.
47672
+ (i > 0 ? [" "] : []).concat(wrapNoteText(note, noteWidth))
47673
+ )
47674
+ ).slice(
47675
+ 0,
47676
+ // 8 keeps at least one list row visible on any non-compact
47677
+ // budget: total = 8 border/title/legend/warning + N + 1 list.
47678
+ Math.min(MAX_OAUTH_NOTE_LINES, Math.max(0, budget - 8))
47679
+ ) : [];
47680
+ const extraRows = (panel.hint ? 1 : 0) + (panel.confirm ? compact2 ? 1 : 2 : 0) + (panel.input ? compact2 ? 1 : 2 : 0) + (panel.view === "catalog" ? compact2 ? 1 : 2 : 0) + (!compact2 && panel.view === "provider" ? 2 : 0) + (!compact2 && panel.view === "oauth" ? 3 + oauthNoteLines.length : 0) + (!compact2 && panel.view === "list" && panel.providers.length === 0 ? 2 : 0) + (!compact2 && panel.view === "catalog" && panel.busy ? 2 : 0);
47650
47681
  const maxVisible = Math.max(
47651
47682
  1,
47652
47683
  budget - 4 - extraRows - (panel.view === "flow" ? compact2 ? 0 : 1 : 2)
@@ -47698,6 +47729,7 @@ function AuthPanel({ panel, maxRows, columns }) {
47698
47729
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Text2, { color: UI_COLORS.warning, wrap: "truncate-end", children: "\u26A0 Subscription tokens used outside official clients may violate provider Terms \u2014" }),
47699
47730
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Text2, { color: UI_COLORS.warning, wrap: "truncate-end", children: "your account could be rate-limited or banned. An API key is the sanctioned path." })
47700
47731
  ] }) : null,
47732
+ oauthNoteLines.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Box2, { flexDirection: "column", children: oauthNoteLines.map((line, ni) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Text2, { dimColor: true, wrap: "truncate-end", children: line }, `on-${ni}`)) }) : null,
47701
47733
  panel.view === "catalog" ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Box2, { marginTop: compact2 ? 0 : 1, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(Text2, { wrap: "truncate-end", children: [
47702
47734
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Text2, { dimColor: true, children: "filter: " }),
47703
47735
  panel.filter.length > 0 ? panel.filter : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Text2, { dimColor: true, children: "(type to search)" }),
@@ -48670,6 +48702,7 @@ function isClearConfirmation(value) {
48670
48702
  return value === "YES";
48671
48703
  }
48672
48704
  function clearConfirmationKeyResult(value, input, key2) {
48705
+ if (input && isLeakedMouseInput(input)) return { decision: null, value };
48673
48706
  if (key2.escape) return { decision: false, value };
48674
48707
  if (key2.ctrl || key2.meta) return { decision: null, value };
48675
48708
  if (key2.return) {
@@ -54856,6 +54889,7 @@ function RefineCountdownPanel({
54856
54889
  }, []);
54857
54890
  use_input_default((input, key2) => {
54858
54891
  if (resolvedRef.current) return;
54892
+ if (input && isLeakedMouseInput(input)) return;
54859
54893
  resolvedRef.current = true;
54860
54894
  const isBackspace = key2.backspace || key2.delete || input === "\x7F" || input === "\b";
54861
54895
  if (key2.escape || isBackspace) onDecision("cancel");
@@ -83620,9 +83654,9 @@ function useResourceSlashCommands(deps) {
83620
83654
  // src/hooks/use-session-slash-commands.ts
83621
83655
  var import_react157 = __toESM(require_react(), 1);
83622
83656
  import {
83623
- areSubagentsAllowed,
83624
83657
  isSubagentPolicyLocked,
83625
- setSessionSubagentsAllowed
83658
+ setSessionSubagentPolicy,
83659
+ subagentPolicyMode
83626
83660
  } from "@wrongstack/core/coordination";
83627
83661
  import { toErrorMessage as toErrorMessage12 } from "@wrongstack/core/utils";
83628
83662
  var RESUME_PICKER_SESSIONS = 500;
@@ -83632,22 +83666,26 @@ function useSessionSlashCommands(deps, part) {
83632
83666
  if (part !== "head") return;
83633
83667
  const cmd = {
83634
83668
  name: "solo",
83635
- description: "Control session-only subagents before the first message: /solo on|off|status.",
83669
+ description: "Control session-only subagents before the first message: /solo on|companions|off|status.",
83636
83670
  async run(args) {
83637
83671
  const action = (args ?? "").trim().toLowerCase() || "status";
83638
- const allowed = areSubagentsAllowed(agent.ctx);
83672
+ const mode = subagentPolicyMode(agent.ctx);
83639
83673
  if (action === "status") {
83674
+ const state = mode === "all" ? "off" : mode === "companions" ? "on (companions allowed)" : "on";
83640
83675
  return {
83641
- message: `Solo session is ${allowed ? "off" : "on"}${isSubagentPolicyLocked(agent.ctx) ? " (locked)" : ""}.`
83676
+ message: `Solo session is ${state}${isSubagentPolicyLocked(agent.ctx) ? " (locked)" : ""}.`
83642
83677
  };
83643
83678
  }
83644
- if (action !== "on" && action !== "off") {
83645
- return { message: "Usage: /solo on|off|status" };
83679
+ if (action !== "on" && action !== "off" && action !== "companions") {
83680
+ return { message: "Usage: /solo on|companions|off|status" };
83646
83681
  }
83647
83682
  try {
83648
- await setSessionSubagentsAllowed(agent.ctx, action === "off");
83683
+ await setSessionSubagentPolicy(
83684
+ agent.ctx,
83685
+ action === "off" ? "all" : action === "companions" ? "companions" : "none"
83686
+ );
83649
83687
  return {
83650
- message: action === "on" ? "Solo session enabled. Chimera, delegation, and background subagents are blocked." : "Solo session disabled. Subagents are allowed for this session."
83688
+ message: action === "on" ? "Solo session enabled. Chimera, delegation, background subagents, and companions are blocked." : action === "companions" ? "Solo session enabled with companions. Chimera, delegation, and background subagents are blocked; the read-only memory and explore companions still run." : "Solo session disabled. Subagents are allowed for this session."
83651
83689
  };
83652
83690
  } catch (err) {
83653
83691
  return { message: toErrorMessage12(err) };
@@ -85646,8 +85684,19 @@ async function routePointerEvents(ctx, input, key2) {
85646
85684
  }
85647
85685
 
85648
85686
  // src/app-key-handler.ts
85649
- function stopNextStepsAutoSubmitOnKey(cancel) {
85650
- cancel();
85687
+ function isComposerEmptyNavigation(input, key2) {
85688
+ if (key2.pageUp || key2.pageDown || key2.home || key2.end) return true;
85689
+ if (key2.leftArrow || key2.rightArrow) return true;
85690
+ return key2.ctrl && (input === "u" || input === "d");
85691
+ }
85692
+ function shouldStopNextStepsAutoSubmit(event) {
85693
+ const { input, key: key2, draft } = event;
85694
+ if (draft !== "") return true;
85695
+ if (key2.mouse) return false;
85696
+ return !isComposerEmptyNavigation(input, key2);
85697
+ }
85698
+ function stopNextStepsAutoSubmitOnKey(cancel, event) {
85699
+ if (shouldStopNextStepsAutoSubmit(event)) cancel();
85651
85700
  }
85652
85701
  function createAppKeyHandler(options) {
85653
85702
  const {
@@ -85660,7 +85709,8 @@ function createAppKeyHandler(options) {
85660
85709
  terminalColumns,
85661
85710
  terminalRows,
85662
85711
  mainColumnWidth,
85663
- cancelNextStepsCountdown
85712
+ cancelNextStepsCountdown,
85713
+ draftRef
85664
85714
  } = options;
85665
85715
  const stdout = { columns: terminalColumns, rows: terminalRows };
85666
85716
  const historyWidth = mainColumnWidth > 0 ? mainColumnWidth : stdout?.columns ?? 80;
@@ -85679,7 +85729,11 @@ function createAppKeyHandler(options) {
85679
85729
  );
85680
85730
  const handleKey = async (input, key2) => {
85681
85731
  if (!key2.escape) ctx.lastEscAtRef.current = 0;
85682
- stopNextStepsAutoSubmitOnKey(cancelNextStepsCountdown);
85732
+ stopNextStepsAutoSubmitOnKey(cancelNextStepsCountdown, {
85733
+ input,
85734
+ key: key2,
85735
+ draft: draftRef.current.buffer
85736
+ });
85683
85737
  if (routeCtrlCEscalation(ctx, input, key2)) return;
85684
85738
  if (routeModalOverlay(ctx, input, key2)) {
85685
85739
  ctx.lastEscAtRef.current = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/tui",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack terminal UI: Ink-based interactive renderer with history, pickers, and slash menus.",
6
6
  "repository": {
@@ -26,12 +26,12 @@
26
26
  "!dist/**/*.map"
27
27
  ],
28
28
  "dependencies": {
29
- "@wrongstack/core": "1.0.27",
30
- "@wrongstack/kanban": "1.0.27",
31
- "@wrongstack/runtime": "1.0.27",
32
- "@wrongstack/sage": "1.0.27",
33
- "@wrongstack/sdd": "1.0.27",
34
- "@wrongstack/tools": "1.0.27",
29
+ "@wrongstack/core": "1.0.29",
30
+ "@wrongstack/kanban": "1.0.29",
31
+ "@wrongstack/runtime": "1.0.29",
32
+ "@wrongstack/sage": "1.0.29",
33
+ "@wrongstack/sdd": "1.0.29",
34
+ "@wrongstack/tools": "1.0.29",
35
35
  "string-width": "8.2.2",
36
36
  "wrap-ansi": "^10.0.1"
37
37
  },