@aiwayds/dsh-tui-pi 2.18.2 → 2.19.0

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
@@ -149,8 +149,13 @@ dsh-tui:
149
149
  resume: # /resume display filter — only HIDES picker rows, never deletes.
150
150
  maxAgeDays: 30
151
151
  minBytes: 1024
152
+ askUser: # ask_user_question auto-answer timeouts — the panel never waits forever.
153
+ idleMinutes: 5 # no-input window per question; <= 0 disables (DSH_TUI_ASK_USER_IDLE_MINUTES)
154
+ absoluteMinutes: 10 # hard cap per question even with input; <= 0 disables (DSH_TUI_ASK_USER_ABSOLUTE_MINUTES)
152
155
  ```
153
156
 
157
+ On a timeout the focused question is auto-answered with the **recommended option** (first in the list); plans are never auto-approved, a half-typed answer is committed, and every automatic pick is noted to the model in the answer. Details: [Ask User Question → Timeouts](docs/features/ask-user-question.md#timeouts--the-panel-never-waits-forever).
158
+
154
159
  Key remaps live in `~/.dsh/keybindings.json` (keyboard section above); `DSH_TUI_COPY_ON_SELECT=0` keeps drag-selection visual-only.
155
160
 
156
161
  The plugin ships a bundled skill (`dsh-tui-pi-config`): ask the agent to "configure the TUI" and the guide loads automatically — it collects your choices interactively via ask_user_question (theme, panel height, subagent concurrency) and writes the `dsh-tui:` section for you. The full key table and the `DSH_TUI_*` env var list live in `skills/dsh-tui-pi-config/SKILL.md`.
package/README.zh-CN.md CHANGED
@@ -145,8 +145,13 @@ dsh-tui:
145
145
  resume: # /resume 显示过滤器——只隐藏选择器行,从不删除。
146
146
  maxAgeDays: 30
147
147
  minBytes: 1024
148
+ askUser: # ask_user_question 自动应答超时——面板绝不无限等待。
149
+ idleMinutes: 5 # 每题无操作窗口;<= 0 关闭(DSH_TUI_ASK_USER_IDLE_MINUTES)
150
+ absoluteMinutes: 10 # 每题硬上限,有操作也生效;<= 0 关闭(DSH_TUI_ASK_USER_ABSOLUTE_MINUTES)
148
151
  ```
149
152
 
153
+ 超时后,聚焦的未答题目自动选**推荐项**(列表第一项);计划审批绝不自动批准;打了一半的自由文本会被提交;每次自动作答都会在答案里向模型注明。详见 [Ask User Question → Timeouts](docs/features/ask-user-question.md#timeouts--the-panel-never-waits-forever)。
154
+
150
155
  按键重映射见 `~/.dsh/keybindings.json`(上文键盘一节)。
151
156
 
152
157
  插件内置了一个 skill(`dsh-tui-pi-config`):直接让 agent「帮我配置 TUI」,指南会自动加载——以 ask_user_question 问答方式逐项收集(主题、面板高度、子代理并发)并代写 `dsh-tui:` 段;全键表与 `DSH_TUI_*` 环境变量清单见 `skills/dsh-tui-pi-config/SKILL.md`。
package/lib/ask-user.d.ts CHANGED
@@ -124,6 +124,34 @@ export declare const ASK_COLLAPSE_KEY: KeyId;
124
124
  export declare const DECLINE_MESSAGE = "User declined to answer questions.";
125
125
  /** Transient hint when Enter lands on an incomplete confirm/submit row. */
126
126
  export declare const INCOMPLETE_HINT = "Answer every question first";
127
+ /** Default no-input window per focused question, in minutes: after this long
128
+ * without a single keypress the question is auto-answered (recommended
129
+ * option, plan-safe) and the panel moves on. */
130
+ export declare const ASK_USER_IDLE_MINUTES_DEFAULT = 5;
131
+ /** Default hard cap per focused question, in minutes: after this long the
132
+ * question is auto-answered EVEN IF the user keeps interacting — the panel
133
+ * must never hold a run hostage indefinitely (dual rule with the idle
134
+ * window; either firing resolves the question). */
135
+ export declare const ASK_USER_ABSOLUTE_MINUTES_DEFAULT = 10;
136
+ /** Env override for the no-input window (minutes; <= 0 disables the rule). */
137
+ export declare const ASK_USER_IDLE_ENV = "DSH_TUI_ASK_USER_IDLE_MINUTES";
138
+ /** Env override for the hard cap (minutes; <= 0 disables the rule). */
139
+ export declare const ASK_USER_ABSOLUTE_ENV = "DSH_TUI_ASK_USER_ABSOLUTE_MINUTES";
140
+ /** Note folded into the answer envelope's `custom` field when a question was
141
+ * auto-answered with the recommended option — the model reads the answer as
142
+ * a tool result, so an automatic pick is declared in-band (same spirit as
143
+ * DECLINE_MESSAGE), never silently passed off as a human choice. */
144
+ export declare const TIMEOUT_RECOMMENDED_NOTE = "Auto-answered after the no-input timeout: the recommended option was picked.";
145
+ /** Note for a plan-review question auto-answered by timeout: plans are NEVER
146
+ * auto-approved — the first non-approve option is picked instead. */
147
+ export declare const TIMEOUT_PLAN_DECLINED_NOTE = "Auto-answered after the no-input timeout: no user input, so the plan was NOT approved.";
148
+ /** Note for a question with no options at all — there is no recommended value
149
+ * to fall back to, so the answer carries the explanation only. */
150
+ export declare const TIMEOUT_NO_DEFAULT_NOTE = "Auto-answered after the no-input timeout: the question offers no default option to pick.";
151
+ /** Note appended when the timeout commits a half-typed sentinel buffer: the
152
+ * text is the user's own, but it never went through Enter, so the envelope
153
+ * says so. */
154
+ export declare const TIMEOUT_CUSTOM_NOTE = "Auto-committed after the no-input timeout: the text typed so far.";
127
155
  /**
128
156
  * Fallback visible body-line cap for the questions/review panes, used only
129
157
  * when the terminal row count is unknown (fake TUIs in tests, exotic
@@ -286,6 +314,81 @@ export declare function needsConfirmRow(questions: readonly AskUserQuestionItem[
286
314
  * question may want further toggles), and every question answered.
287
315
  */
288
316
  export declare function canAutoSubmit(state: AskUserState): boolean;
317
+ /**
318
+ * Timeout rules for one open ask-user panel — the unit is ms internally,
319
+ * minutes in settings/env (human units, like retention's maxAgeDays).
320
+ * `0` disables that rule; both zero = the legacy wait-forever panel.
321
+ */
322
+ export interface AskUserTimeouts {
323
+ /** No-input window per focused question. Any keypress restarts it. */
324
+ idleMs: number;
325
+ /** Hard cap per focused question, measured from when focus entered it —
326
+ * fires even under continuous input. */
327
+ absoluteMs: number;
328
+ }
329
+ /** Both rules off — the panel waits for the human exactly as before. */
330
+ export declare const ASK_USER_TIMEOUTS_DISABLED: AskUserTimeouts;
331
+ /** Raw user-layer `dsh-tui.askUser` section (handed over by the
332
+ * theme-settings reader): every field is `unknown` because a hand-edited
333
+ * settings.yaml can carry anything. */
334
+ export interface AskUserTimeoutSettings {
335
+ idleMinutes?: unknown;
336
+ absoluteMinutes?: unknown;
337
+ }
338
+ /**
339
+ * Resolve the ask-user timeout knobs through the repo's standard precedence
340
+ * chain — explicit settings.yaml values (`dsh-tui.askUser.*`) outrank the
341
+ * `DSH_TUI_ASK_USER_*` environment variables, which outrank the defaults
342
+ * (5 min idle / 10 min absolute). Invalid settings values emit one notice
343
+ * each (notice bridge) and fall to the next level; invalid env values fall
344
+ * back silently. Pure; `process.env` and the settings section are passed
345
+ * explicitly so tests can pin them.
346
+ */
347
+ export declare function resolveAskUserTimeouts(settings?: AskUserTimeoutSettings, env?: Record<string, string | undefined>): AskUserTimeouts;
348
+ /**
349
+ * Earliest deadline at which the panel's timeout should fire, or null when
350
+ * no rule is armed. The idle deadline runs from `lastInputAt` (any
351
+ * keypress); the absolute deadline runs from `focusEnteredAt` (focus entry
352
+ * into the current question) and applies in BOTH phases — a review page
353
+ * entered late inherits its question's remaining budget, so the total wait
354
+ * per question stays bounded. Pure; the panel arms a timer on this.
355
+ */
356
+ export declare function nextTimeoutDeadline(timeouts: AskUserTimeouts, now: number, focusEnteredAt: number, lastInputAt: number): number | null;
357
+ /**
358
+ * The timeout auto-answer for ONE unanswered question — what the panel
359
+ * writes into `perQuestion` when a timer fires on it. Priority:
360
+ *
361
+ * 1. a non-empty live sentinel buffer is committed as the custom answer
362
+ * (the panel's own ↑↓ arrow-exit semantics commit non-empty buffers, so
363
+ * typed-but-uncommitted text is honored, not discarded);
364
+ * 2. a `plan-review` question NEVER auto-approves — the first option that
365
+ * is not the intent's `approve` label is picked (decline by omission);
366
+ * 3. otherwise the FIRST option — the dsh tool contract declares the
367
+ * recommended choice by putting it first ("put it first and append
368
+ * '(Recommended)' to that label").
369
+ *
370
+ * The companion envelope note is returned alongside so the panel can fold
371
+ * it into the answer at settle time; the note never enters panel state
372
+ * (it would leak into the sentinel row / review rendering).
373
+ */
374
+ export declare function timeoutAnswerFor(question: AskUserQuestionItem, liveBuffer: string | null | undefined): {
375
+ answer: PendingAnswer;
376
+ note?: string;
377
+ };
378
+ /**
379
+ * `buildAnswerEnvelope` with the timeout notes folded in: `notes` maps a
380
+ * question index to the note appended to that answer item's `custom` field
381
+ * (created when absent, concatenated when the answer already carries custom
382
+ * text — the buffer-commit case). The model reads the envelope as the tool
383
+ * result, so every automatic pick is declared in-band. Omitting `notes`
384
+ * reproduces `buildAnswerEnvelope` exactly.
385
+ */
386
+ export declare function buildAnswerEnvelopeWithNotes(state: AskUserState, notes?: ReadonlyMap<number, string>): AskUserQuestionAnswer;
387
+ /**
388
+ * The idle countdown for the panel footer: `m:ss` until the next deadline,
389
+ * floored at 0:00. Pure so tests pin it without a clock.
390
+ */
391
+ export declare function formatCountdown(msRemaining: number): string;
289
392
  /**
290
393
  * Row index to land on after a single-select answer on question `answeredQi`
291
394
  * (option toggle or committed custom text): advance the tab focus to the
@@ -321,9 +424,9 @@ export declare function switchFocus(state: AskUserState, direction: 1 | -1): Ask
321
424
  */
322
425
  export declare function toggleCollapse(state: AskUserState): AskUserState;
323
426
  /** Render the questions pane (one focused question tab) as a flat table line list behind a scroll window. */
324
- export declare function renderQuestionsView(theme: TuiTheme, state: AskUserState, width: number, maxVisible?: number): string[];
427
+ export declare function renderQuestionsView(theme: TuiTheme, state: AskUserState, width: number, maxVisible?: number, countdown?: string): string[];
325
428
  /** Render the review page for multi-question overlays (scroll-windowed). */
326
- export declare function renderReviewView(theme: TuiTheme, state: AskUserState, width: number, maxVisible?: number): string[];
429
+ export declare function renderReviewView(theme: TuiTheme, state: AskUserState, width: number, maxVisible?: number, countdown?: string): string[];
327
430
  /**
328
431
  * The ONE interior line of the folded (Ctrl+T) panel. The panel is a hard
329
432
  * modal that owns the keyboard while it pends, so the strip keeps the
@@ -411,6 +514,13 @@ export interface AskUserPanelDeps {
411
514
  setModalActive: (active: boolean) => void;
412
515
  /** Injectable clock for tests; defaults to Date.now. */
413
516
  now?: () => number;
517
+ /**
518
+ * Resolve the timeout rules for ONE ask (settings > env > defaults, read
519
+ * fresh per ask so a committed settings change applies to the next
520
+ * question without a reload). Absent = disabled — the panel waits for the
521
+ * human exactly as before (direct callers, tests, embedders).
522
+ */
523
+ resolveTimeouts?: () => Promise<AskUserTimeouts>;
414
524
  }
415
525
  /** Result promise from `openAskUserPanel`. Declined carries the canonical decline envelope. */
416
526
  export type AskUserResult = AskUserQuestionAnswer;
@@ -424,8 +534,17 @@ export type AskUserResult = AskUserQuestionAnswer;
424
534
  * service already screens entry-time aborts, and a step aborted after this
425
535
  * point discards the tool result anyway — resolving keeps the pending promise
426
536
  * from ever hanging either way.
537
+ *
538
+ * `timeouts` arms the auto-answer rules (see {@link AskUserTimeouts}): the
539
+ * FOCUSED question's idle window and hard cap. A firing timer auto-answers
540
+ * the focused question (recommended option, plan-safe —
541
+ * {@link timeoutAnswerFor}), hops to the next unanswered question with fresh
542
+ * budgets, and — once nothing is left unanswered — settles the envelope
543
+ * directly (the review page exists for a human double-check an absent human
544
+ * cannot do). Defaults to disabled: callers opt in through the provider's
545
+ * `resolveTimeouts`.
427
546
  */
428
- export declare function openAskUserPanel(deps: AskUserPanelDeps, questions: readonly AskUserQuestionItem[], signal?: AbortSignal): Promise<AskUserResult>;
547
+ export declare function openAskUserPanel(deps: AskUserPanelDeps, questions: readonly AskUserQuestionItem[], signal?: AbortSignal, timeouts?: AskUserTimeouts): Promise<AskUserResult>;
429
548
  /**
430
549
  * Wires the TUI into `ctx.userQuestions`. Call from inside `ctx.effect`.
431
550
  * Two registration shapes, chosen by what the host exposes: