@xmoon76/dsh-pi-tui 0.3.2 → 0.3.4

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
@@ -336,17 +336,32 @@ the Advanced/Unstable roadmap).
336
336
  pinned earlier** (`/rename` is an alias).
337
337
  - `/tasks` — the merged task browser: background jobs AND subagents in one
338
338
  searchable list (type to filter rows by kind/label/status — `subagent`,
339
- `bash`, `failed`…). `Enter` opens the detail (child transcript for a
340
- subagent, status viewer for a job), `i` interrupts the selected subagent.
341
- `/subagents` is an alias.
339
+ `bash`, `failed`…). Subagent rows show their mode as part of the label
340
+ (`subagent · <label> · continuable` / `… · one-shot`) — you know before
341
+ Enter whether the viewer will be interactive — and the durable descendant
342
+ tree renders as indented rows (`├─` per depth), so a subagent's subagent
343
+ is visible here without entering its parent. `Enter` opens the detail
344
+ (child viewer for a subagent — nested rows read-only — status viewer for
345
+ a job), `i` interrupts the selected subagent. `/subagents` is an alias.
342
346
  - `/yolo` — switch to `danger-full-access` (alias of `/permission danger-full-access`).
343
347
  - `/status` — show the current session's stats and identity (turn counts,
344
348
  token usage, workspace, installed dsh version).
349
+ - `/rewind` — fork this conversation from an earlier user turn: the picker
350
+ lists every completed user prompt (newest first), and choosing one creates
351
+ a new child session whose history ends right before that turn, then
352
+ restores the selected prompt into the editor for editing. The original
353
+ session is never modified and stays reachable through `/sessions`;
354
+ **workspace and external side effects (files, shell, APIs) are not
355
+ reverted.** The same picker opens with `Esc Esc` on an empty editor.
345
356
  - `/preset`, `/model`, `/settings`, `/export`, `/fork` — see
346
357
  `dsh --profile pi-tui`'s command autocomplete (`/` + Tab).
347
358
 
348
359
  ## Keybindings (selection)
349
360
 
361
+ - `Esc Esc` — conversation rewind (on an empty editor while idle): open the
362
+ rewind picker and fork from an earlier user turn. One `Esc` while the
363
+ agent is busy still cancels the active turn/tool/shell command; with a
364
+ non-empty draft the double-Esc keeps its historical cancel behavior.
350
365
  - `Ctrl+F` — toggle transcript search (the `/search <query>` overlay; a
351
366
  second press closes it).
352
367
  - `Shift+Tab` — cycle the permission preset (read-only → workspace-write →
@@ -357,6 +372,23 @@ the Advanced/Unstable roadmap).
357
372
  draft, if any) into the running turn at once; otherwise sends the draft
358
373
  alone. An idle agent starts a fresh turn with everything.
359
374
  - `Alt+↑` — dequeue: pull every queued message back into the editor draft.
375
+ - `Ctrl+O` — expand/collapse the most recent turns' collapsible entries
376
+ (tool and system cards). The same master switch expands the local
377
+ `!`/`!!` shell cards (see Local shell below). In Focus Mode (regular
378
+ surface) it derives a full reveal of the recent Thoughts; the
379
+ fullscreen Focus secondary cards are mouse-owned and Ctrl+O does not
380
+ expand them. Thinking detail has its own owner — see `Alt+T`.
381
+ - `Alt+T` — collapse/expand every Thinking block (compact: the latest
382
+ reasoning line as a preview; full: the whole reasoning body). Thinking
383
+ blocks are disclosure, never visibility: a block stays present
384
+ whenever the model produced reasoning and the current projection
385
+ contains it, in Focus ON/OFF and both surfaces alike. In fullscreen a
386
+ click toggles a single Thinking card on top of this bulk level, and
387
+ `Ctrl+O` never touches Thinking detail (it owns only tool/system
388
+ cards). `/settings` → `Thinking detail` is the same state.
389
+ - `Alt+K` — dismiss the SETTLED local `!`/`!!` shell cards from the live
390
+ view; a RUNNING card is never dismissed and the process is not
391
+ cancelled (Esc owns that). See Local shell below.
360
392
  - `Ctrl+T` — toggle the full todo list (in fullscreen, clicking the panel
361
393
  expands it to the full list and back); the dock above the editor always
362
394
  shows the todo summary and background tasks, and queued input renders
@@ -366,10 +398,26 @@ the Advanced/Unstable roadmap).
366
398
  fallback otherwise). The literal `@path` is submitted and the model reads
367
399
  the file itself. With background work running, an empty editor's `↓` opens
368
400
  the same merged task browser as `/tasks`:
369
- - **subagent rows** (live continuable children) — `Enter` opens the child's
370
- transcript read-only (`Esc` returns); `i` interrupts the selected child.
371
- They never register jobs records, so this browser is their only glanceable
372
- home.
401
+ - **subagent rows** (the durable descendant tree, labeled with their
402
+ catalog mode: `continuable` or `one-shot`, indented by depth with a
403
+ `├─` connector — a subagent's subagent is visible right here, in
404
+ stable pre-order) — `Enter` opens the child's viewer:
405
+ a **direct (depth-1) continuable** child's viewer is **interactive**
406
+ — type a follow-up and press Enter, and it is delivered as the
407
+ child's NEXT turn through `ctx.subagents.followup` (FIFO — a running
408
+ child is never interrupted or steered; an inactive child cold-resumes
409
+ automatically), while `Esc` returns to your session. A **one-shot**
410
+ child's viewer stays **read-only**, and so does any **nested
411
+ (depth > 1)** descendant — even a continuable one (`<mode> · nested ·
412
+ read-only from this parent`, the real mode always shown): the root
413
+ may only continue its own direct children. A FINISHED one-shot child stays in the
414
+ browser — `inactive` is live-store presence, not an outcome — and
415
+ Enter opens its persisted transcript. While a viewer is open the
416
+ footer switches to the CHILD's own identity (`[subagent ·
417
+ continuable]` badge, label, activity, cwd, the child's own
418
+ turns/steps/stats) and returns to your session's on exit. `i`
419
+ interrupts the selected child. They never register job records, so
420
+ this browser is their only glanceable home.
373
421
  - **job rows** (bash and one-shot subagent jobs) — `Enter` shows the status
374
422
  viewer only: a bash job's output read cursor belongs to the model's
375
423
  `job_output`, and a one-shot subagent job record carries no child session
@@ -397,6 +445,20 @@ user message (text, slash command, `Ctrl+S` steer, or `!` shell) starts it
397
445
  lazily. `--session <id>` still resumes immediately, and a local `!!` command
398
446
  runs without needing a session.
399
447
 
448
+ ## Local shell (`!` / `!!`)
449
+
450
+ `! command` runs the command locally and submits the completed command +
451
+ output to the session as an ordinary user message; `!! command` runs purely
452
+ locally — the card is the only record (never sent to the model). The card
453
+ is COLLAPSED by default so a long log cannot fill the TUI: a running card
454
+ shows the newest 5 lines, a settled card at most 20 visual rows (long
455
+ lines wrap and count as several), each with an honest hidden-line marker.
456
+ `Ctrl+O` (the same master switch as the recent-turn fold) expands the card
457
+ to the retained buffer — live while the command still streams; `Alt+K`
458
+ quick-dismisses the SETTLED cards (a running card is never dismissed, the
459
+ process is not cancelled, and an already-submitted `!` context payload is
460
+ untouched).
461
+
400
462
  ## Verified in the P0 spike
401
463
 
402
464
  - Vendored pi-tui: the fork's own suite passes under `node --test` (run it as
package/README.zh-CN.md CHANGED
@@ -299,18 +299,29 @@ SDK。原始终端访问、pre-host 输入拦截与完整输入所有权**不属
299
299
  生成标题——这会覆盖当前标题,包括你之前固定的标题**(`/rename` 是
300
300
  别名)。
301
301
  - `/tasks` — 合并任务浏览器:后台 job 与子代理在同一个可搜索列表中
302
- (直接输入过滤行——`subagent`、`bash`、`failed`…)。`Enter` 打开详情
303
- (子代理为子转录,job 为状态查看器),`i` 中断选中的子代理。
304
- `/subagents` 是别名。
302
+ (直接输入过滤行——`subagent`、`bash`、`failed`…)。子代理行把 mode
303
+ 写进主标签(`subagent · <label> · continuable` / `… · one-shot`),
304
+ 按下 `Enter` 之前就能知道查看器是否可交互;durable 后代树以缩进行
305
+ 渲染(`├─` 按深度缩进),子代理的子代理无需进入父节点就能在这里
306
+ 看到。`Enter` 打开详情(子代理为子查看器——嵌套行只读——job 为
307
+ 状态查看器),`i` 中断选中的子代理。`/subagents` 是别名。
305
308
  - `/yolo` — 切换到 `danger-full-access`(`/permission danger-full-access`
306
309
  的别名)。
307
310
  - `/status` — 显示当前会话的统计与身份信息(回合数、token 用量、
308
311
  工作区、已安装的 dsh 版本)。
312
+ - `/rewind` — 从更早的用户回合 fork 当前对话:选择器列出每个已完成
313
+ 的用户提示(最新在上),选中一个会创建一个新的子会话,其历史恰好
314
+ 止于该回合之前,并把选中的提示回填到编辑器供修改。原会话从不被
315
+ 修改,仍可通过 `/sessions` 回到;**工作区与外部副作用(文件、shell、
316
+ API)不会回滚。** 空编辑器下按 `Esc Esc` 也会打开同一个选择器。
309
317
  - `/preset`、`/model`、`/settings`、`/export`、`/fork` —
310
318
  见 `dsh --profile pi-tui` 的命令自动补全(`/` + Tab)。
311
319
 
312
320
  ## 快捷键(节选)
313
321
 
322
+ - `Esc Esc` — 对话 rewind(空闲且编辑器为空时):打开 rewind 选择器,
323
+ 从更早的用户回合 fork。agent 忙碌时按一次 `Esc` 仍只取消当前
324
+ 回合/工具/shell 命令;草稿非空时双击 Esc 保持原有的取消语义。
314
325
  - `Ctrl+F` — 切换转录搜索(`/search <query>` 覆盖层;再按一次关闭)。
315
326
  - `Shift+Tab` — 循环切换权限预设(read-only → workspace-write →
316
327
  danger-full-access);页脚的模式槽位会为每个预设显示徽章
@@ -320,6 +331,21 @@ SDK。原始终端访问、pre-host 输入拦截与完整输入所有权**不属
320
331
  送入正在运行的回合;否则只发送草稿。空闲的 agent 会用全部内容开启
321
332
  新回合。
322
333
  - `Alt+↑` — 出队:把所有排队消息拉回编辑器草稿。
334
+ - `Ctrl+O` — 展开/折叠最近回合的可折叠条目(工具、system 卡片);
335
+ 同一个主开关也展开本地 `!`/`!!` shell 卡片(默认折叠——见下文
336
+ "本地 shell")。在 Focus 模式(常规表面)下它派生展开最近几个
337
+ Thought 的完整过程;全屏 Focus 的二级卡片由鼠标控制,Ctrl+O 不会
338
+ 展开它们。Thinking 的 detail 有自己的拥有者——见 `Alt+T`。
339
+ - `Alt+T` — 折叠/展开所有 Thinking 块(折叠:最新一行推理作为
340
+ preview;展开:完整推理正文)。Thinking 块是 disclosure,不是
341
+ visibility:只要模型产生过 reasoning 且当前 projection 包含该块,
342
+ 它就始终存在——Focus 开/关、两种表面完全一致。全屏下点击可在该
343
+ bulk 级别之上单独切换单个 Thinking 卡片;`Ctrl+O` 绝不触碰
344
+ Thinking detail(它只拥有 tool/system 卡片)。`/settings` 的
345
+ `Thinking detail` 与它共享同一状态。
346
+ - `Alt+K` — 从实时视图快速清除**已结束**的本地 `!`/`!!` shell 卡片;
347
+ **运行中**的卡片绝不会被清除,进程也不会被取消(那是 Esc 的职责)。
348
+ 见下文"本地 shell"。
323
349
  - `Ctrl+T` — 切换完整待办列表(全屏时点击面板可在完整列表与折叠之间
324
350
  切换);编辑器上方的 dock 始终显示待办摘要与后台任务,排队的输入渲染
325
351
  在两者之间。
@@ -327,9 +353,22 @@ SDK。原始终端访问、pre-host 输入拦截与完整输入所有权**不属
327
353
  (`fd` 在 PATH 上时以其为后端,否则使用内置的递归回退)。字面
328
354
  `@path` 会被提交,由模型自行读取文件。有后台工作时,空编辑器的 `↓`
329
355
  会打开与 `/tasks` 相同的合并任务浏览器:
330
- - **子代理行**(可继续的实时子任务)——`Enter` 以只读方式打开子代理
331
- 的转录(`Esc` 返回);`i` 中断选中的子代理。它们从不注册 job 记录,
332
- 所以此浏览器是它们唯一可一览的归宿。
356
+ - **子代理行**(durable 后代树,标签携带目录 mode:`continuable` 或
357
+ `one-shot`,按深度缩进并带 `├─` 连接符——子代理的子代理在这里
358
+ 直接可见,顺序为稳定的 pre-order)——`Enter` 打开子代理查看器:
359
+ **直接(深度 1)continuable** 子代理的查看器是**可交互的**——输入
360
+ 后续消息并回车,它会作为子代理的**下一回合**通过
361
+ `ctx.subagents.followup` 投递(FIFO——运行中的子代理不会被中断或
362
+ steer;非活跃的子代理会自动冷恢复),`Esc` 返回主会话。**one-shot**
363
+ 子代理的查看器保持**只读**,任何**嵌套(深度 > 1)**后代也一样
364
+ ——即使它是 continuable(`<mode> · nested · read-only from this
365
+ parent`,始终显示真实 mode):根会话只能继续自己的直接子代理。已结束的 one-shot
366
+ 子代理仍留在浏览器里——`inactive` 只是 live-store 的存在状态,
367
+ 不是结果——Enter 依然能打开其持久化 transcript。查看器打开期间,
368
+ 页脚切换为**子代理自己的身份**(`[subagent · continuable]` 徽章、
369
+ 标签、activity、cwd 与子代理自己的 turns/steps/stats),退出后恢复
370
+ 主会话页脚。`i` 中断选中的子代理。它们从不注册 job 记录,所以此
371
+ 浏览器是它们唯一可一览的归宿。
333
372
  - **job 行**(bash 与一次性子代理 job)——`Enter` 只显示状态查看器:
334
373
  bash job 的输出读取游标属于模型的 `job_output`,而一次性子代理 job
335
374
  记录不带子会话 id,因此子代理 job 的转录需通过 `/tasks` 按标签
@@ -353,6 +392,17 @@ TUI 的启动行新增了 `--preset <id>`——新会话启动时使用的 agent
353
392
  斜杠命令、`Ctrl+S` steer 或 `!` shell)才会惰性启动会话。`--session <id>`
354
393
  仍然立即恢复会话,本地 `!!` 命令无需会话即可运行。
355
394
 
395
+ ## 本地 shell(`!` / `!!`)
396
+
397
+ `! command` 在本地运行命令,并把完成后的命令 + 输出作为普通用户消息
398
+ 提交给会话;`!! command` 完全本地运行——卡片是唯一记录(绝不发给模型)。
399
+ 卡片**默认折叠**,长日志不会塞满 TUI:运行中的卡片显示最新 5 行,
400
+ 已结束的卡片最多显示 20 个视觉行(超长行会换行并按多行计),各自带
401
+ 诚实的隐藏行数标记。`Ctrl+O`(与折叠最近工具回合的同一个主开关)把
402
+ 卡片展开到 retained buffer——命令仍在跑时实时跟随;`Alt+K` 快速清除
403
+ **已结束**的卡片(运行中的卡片绝不会被清除,进程不会被取消,已经
404
+ 提交的 `!` context payload 不受影响)。
405
+
356
406
  ## 在 P0 spike 中验证过的事项
357
407
 
358
408
  - 内置 pi-tui:fork 自带的套件在 `node --test` 下通过(每次重新
package/dist/builtins.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TUI_STARTUP_SERVICE } from "./startup.mjs";
2
- import { L as PI_TUI_EXTENSIONS_SERVICE, M as dshVersion } from "./src-oZjot5jQ.mjs";
2
+ import { H as PI_TUI_EXTENSIONS_SERVICE, L as dshVersion } from "./src-B1uP5j0d.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  //#region src/builtins.ts
@@ -1,3 +1,3 @@
1
1
  import "./startup.mjs";
2
- import { F as inject, I as name, L as PI_TUI_EXTENSIONS_SERVICE, P as apply, R as API_VERSION, z as describeKey } from "./src-oZjot5jQ.mjs";
2
+ import { B as inject, H as PI_TUI_EXTENSIONS_SERVICE, U as API_VERSION, V as name, W as describeKey, z as apply } from "./src-B1uP5j0d.mjs";
3
3
  export { API_VERSION, PI_TUI_EXTENSIONS_SERVICE, apply, describeKey, inject, name };
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { V as TuiAction } from "./public-types-CZz5_arj.mjs";
1
2
  import z from "@deepseek-ai/schemastery";
2
3
  import { Agent, ModelSelectionRef } from "@deepseek-ai/dsh-agent";
3
4
  import { ContentBlock } from "@deepseek-ai/dsh-llm";
@@ -95,6 +96,12 @@ interface Diag {
95
96
  }
96
97
  //#endregion
97
98
  //#region src/tui-app.d.ts
99
+ /** The compaction lifecycle phase the working row advertises: idle (no
100
+ * compaction), summarizing (compaction/start seen, the summary is being
101
+ * generated), or applying (the summary landed, the compacted surface is
102
+ * being committed). Derived by the runner from compaction/start →
103
+ * compaction/summary → compaction/end (foldCompactionEvent). */
104
+ type CompactionPhase = 'idle' | 'summarizing' | 'applying';
98
105
  /** One queued inbox row for the queue pane (mirrors the agent Inbox's lists). */
99
106
  interface QueueItem {
100
107
  /** The pending message id (agent inbox identity). */
@@ -311,11 +318,107 @@ declare function normalizeSkillInvocation(text: string): string | undefined;
311
318
  declare function shouldConsumeAdvertisedMiss(execution: {
312
319
  readonly result: unknown;
313
320
  } | undefined, wasAdvertised: boolean): boolean;
321
+ /** The live-agent surface {@link interruptAgent} needs (structural — the
322
+ * TUI never imports the agent runtime for this call). */
323
+ interface InterruptAgentLike {
324
+ readonly session: {
325
+ readonly id: string;
326
+ };
327
+ readonly status: string;
328
+ cancel(cause: {
329
+ kind: 'user';
330
+ }, options?: {
331
+ keepInbox?: boolean;
332
+ }): void;
333
+ }
334
+ /** The writer surface {@link interruptAgent} needs (structural — a LOCAL
335
+ * type so the public declaration never inlines internal runtime modules;
336
+ * the runner's SessionWriter satisfies it). */
337
+ interface InterruptWriterLike {
338
+ cancel(sessionId: string, reason: {
339
+ kind: 'user';
340
+ }, options: {
341
+ keepInbox: boolean;
342
+ }): void;
343
+ }
344
+ /**
345
+ * Interrupt the live agent (web Stop parity): abort the current
346
+ * turn/tool run while PRESERVING the pending queue. dsh's DEFAULT
347
+ * `cancel()` clears queued AND steering input, so a bare cancel would
348
+ * destroy everything the user queued with Ctrl+S / queue-mode Enter —
349
+ * the Esc-interrupt semantic is "stop the current thinking", never
350
+ * "drop the queue". `keepInbox: true` parks the preserved work; dsh's
351
+ * cancel is a documented no-op when nothing is active, so an idle
352
+ * interrupt (double-Esc while idle) is harmless and still aborts a
353
+ * local shell / maintenance task through the caller.
354
+ *
355
+ * NOTE (upstream dependency): dsh currently PARKS the preserved queue
356
+ * after an abort — the "Esc with a queue continues immediately" UX
357
+ * needs an upstream `wakePending`/`continueInbox` capability (not yet
358
+ * in dsh). A TUI-side emulation (remove + re-send a queue message)
359
+ * would fabricate durable discarded/inserted events in the session
360
+ * log, which the design explicitly rejects — the parked queue is the
361
+ * agreed web-parity behavior until upstream lands the capability.
362
+ */
363
+ declare function interruptAgent(agent: InterruptAgentLike | undefined, writer: InterruptWriterLike): void;
314
364
  /** One unsettled subagent delegation, in tool/call order. */
315
365
  interface PendingSubagentCall {
316
366
  readonly callId: string;
317
367
  readonly description: string;
318
368
  }
369
+ /**
370
+ * The async viewer-OPEN invalidation token (pure, exported for the headless
371
+ * suite — the runner closure itself is not drivable in the headless tests,
372
+ * so the lifecycle rule is tested through these token semantics). An open
373
+ * request captures a token; EVERY viewer session change — opening another
374
+ * child, leaving the viewer (Esc), or a session swap (which routes through
375
+ * exitView) — invalidates the token, so a slow transcript inspection can
376
+ * never commit an obsolete child over the current surface. Invalidation is
377
+ * unconditional: an exit that finds NO mounted viewer still invalidates,
378
+ * because the open is exactly then still in flight (round-5 finding).
379
+ */
380
+ interface ViewerOpenToken {
381
+ /** The current token value (bumped by every open and every invalidate). */
382
+ readonly current: number;
383
+ /** Start one async open; returns the request's token. */
384
+ open(): number;
385
+ /** Invalidate every in-flight open (a viewer session change). */
386
+ invalidate(): void;
387
+ /** Whether a request may still commit. */
388
+ isCurrent(request: number): boolean;
389
+ }
390
+ declare function createViewerOpenToken(): ViewerOpenToken;
391
+ /**
392
+ * Session-swap viewer teardown (pure, exported for the headless suite —
393
+ * the runner closure is not headless-drivable, so the rule is pinned
394
+ * through this seam). A session swap must do BOTH: invalidate any
395
+ * in-flight viewer OPEN — UNCONDITIONALLY, because the open may still be
396
+ * loading when nothing is mounted yet, and the swap must still cancel it
397
+ * (round-6 finding) — and close a MOUNTED viewer when there is one.
398
+ * @param token - the shared viewer-open token.
399
+ * @param mounted - whether a viewer is currently mounted.
400
+ * @param closeMounted - closes the mounted viewer (a no-op when unmounted).
401
+ * @returns whether a mounted viewer was closed.
402
+ */
403
+ declare function teardownViewerForSessionSwap(token: ViewerOpenToken, mounted: boolean, closeMounted: () => void): boolean;
404
+ /**
405
+ * The viewer capability gate for SEMANTIC plugin actions (pure, exported
406
+ * for the headless suite — the runner closure is not drivable there).
407
+ * While a subagent viewer is open, only actions that stay CHILD- or
408
+ * SURFACE-local are allowed; every action with PARENT-session side
409
+ * effects (steer, cancel/interrupt, permission cycling, the main
410
+ * transcript search) is blocked, so a plugin keybinding can never
411
+ * interrupt/steer/reconfigure the parent from inside the viewer. The
412
+ * raw-key viewer guard already consumes the parent chords — this gate
413
+ * closes the plugin-keybinding path, the only other way a semantic
414
+ * action reaches the runner.
415
+ * @param action - the semantic action the plugin requested.
416
+ * @param viewer - the open viewer (mode), or undefined when no viewer.
417
+ * @returns whether the runner may execute the action.
418
+ */
419
+ declare function viewerActionCapability(action: TuiAction, viewer: {
420
+ mode: 'one-shot' | 'continuable';
421
+ } | undefined): boolean;
319
422
  /**
320
423
  * Match the child a user is about to view against the unsettled subagent
321
424
  * calls (pure, exported for the headless suite). The child's durable label
@@ -499,6 +602,11 @@ interface CompactionFold {
499
602
  * re-derived from the turn log (a stale end never clears a newer
500
603
  * compaction's state). */
501
604
  clear: boolean;
605
+ /** The compaction phase the event implies: compaction/start →
606
+ * 'summarizing', a MATCHED compaction/summary → 'applying'. Undefined
607
+ * when the event does not advance the phase (a stale summary, any
608
+ * compaction/end — the settle clears via {@link clear}). */
609
+ phase?: Exclude<CompactionPhase, 'idle'>;
502
610
  /** The settle notification for compaction/end, when one fires. */
503
611
  notify: {
504
612
  text: string;
@@ -515,6 +623,23 @@ declare function foldCompactionEvent(state: {
515
623
  error?: unknown;
516
624
  };
517
625
  }): CompactionFold;
626
+ /** The minimal compaction-settle surface the runner passes in — STRUCTURAL
627
+ * on purpose: referencing the full {@link TuiApp} class from a public
628
+ * export would inline the whole surface (and its internal registry/
629
+ * presentation dependencies) into the published declaration bundle. The
630
+ * settle contract only needs the three phase/busy/working setters. */
631
+ interface CompactionSettleSurface {
632
+ setCompactionPhase(phase: 'idle'): void;
633
+ setBusy(busy: boolean): void;
634
+ setWorking(busy: boolean): void;
635
+ }
636
+ /** The UI side effects of a MATCHED compaction settle: clear the phase,
637
+ * hand the working row back to the turn state, and re-measure the session
638
+ * surface so the footer context reflects the compacted log IMMEDIATELY —
639
+ * the next step/start or turn/end would otherwise delay the refresh.
640
+ * Exported as a seam so the settle contract is testable without a full
641
+ * runner driver (the firehose closure is not). */
642
+ declare function settleCompactionSurface(app: CompactionSettleSurface, refreshStatus: () => void, busyNow: boolean): void;
518
643
  /** The busy flag after a turn-boundary event: a turn end must NOT clear
519
644
  * the busy state while a compaction is still in flight — an interrupted
520
645
  * turn can close (turn/end) before its compaction settles, and the
@@ -557,10 +682,20 @@ interface AgentComposition {
557
682
  * @param ctx - the runner context (services read through `ctx.get`).
558
683
  * @param selected - the mutable model selection every setup installs.
559
684
  * @param presetId - the requested preset, or `undefined` for the default.
685
+ * @param focusState - the shared Focus runtime state (STRUCTURAL on
686
+ * purpose: the public declaration bundle must not inline src/focus.ts —
687
+ * the parameter only ever carries the runner's FocusState object, so a
688
+ * bare `{ enabled: boolean }` keeps the shipped .d.mts clean); when
689
+ * provided, the setup ALSO installs the dynamic Focus system-prompt
690
+ * section exactly once per composed agent (plan §9 — every composed
691
+ * root TUI agent gets it; /focus toggles never re-register).
692
+ * @param diag - the diagnostics channel, when the caller has one.
560
693
  * @returns the id to record on the header (absent without a roster) and the setup callback.
561
694
  * @throws when the roster supplies no such preset.
562
695
  */
563
- declare function composeAgent(ctx: Context, selected: ModelSelectionRef, presetId?: string): Promise<AgentComposition>;
696
+ declare function composeAgent(ctx: Context, selected: ModelSelectionRef, presetId?: string, focusState?: {
697
+ enabled: boolean;
698
+ }, diag?: Diag): Promise<AgentComposition>;
564
699
  /**
565
700
  * The preset a persisted session actually runs, from its log (newest
566
701
  * selection winning), or undefined when persistence is absent, the session is
@@ -610,4 +745,4 @@ declare function recomposeBlank(ctx: Context, agent: {
610
745
  */
611
746
  declare function apply(ctx: Context, config: Config): void;
612
747
  //#endregion
613
- export { AgentComposition, CompactionFold, Config, HOST_COMMAND_CATALOG, InitialCatalogResolution, LOCAL_COMMANDS, PendingSubagentCall, QueueFoldResult, QueueInboxMessage, QueueNoticeSource, RecomposableSession, RecomposeOutcome, ResolveInitialCatalogOptions, SESSIONLESS_COMMANDS, SessionLockRefusedError, apply, busyAfterTurnBoundary, commandRejectsImages, compactingFromLog, composeAgent, dangerCommand, foldCompactionEvent, foldQueueRows, forkSeed, inject, isPlainExitPrompt, isSubagentSettlementNotice, isUserQueueInput, matchPendingSubagentCall, name, normalizeSkillInvocation, recomposeBlank, recordedPreset, registerCredentialSurfaceRefresh, resolveInitialCatalog, resumeCommand, runningProfile, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure };
748
+ export { AgentComposition, CompactionFold, CompactionSettleSurface, Config, HOST_COMMAND_CATALOG, InitialCatalogResolution, InterruptAgentLike, InterruptWriterLike, LOCAL_COMMANDS, PendingSubagentCall, QueueFoldResult, QueueInboxMessage, QueueNoticeSource, RecomposableSession, RecomposeOutcome, ResolveInitialCatalogOptions, SESSIONLESS_COMMANDS, SessionLockRefusedError, ViewerOpenToken, apply, busyAfterTurnBoundary, commandRejectsImages, compactingFromLog, composeAgent, createViewerOpenToken, dangerCommand, foldCompactionEvent, foldQueueRows, forkSeed, inject, interruptAgent, isPlainExitPrompt, isSubagentSettlementNotice, isUserQueueInput, matchPendingSubagentCall, name, normalizeSkillInvocation, recomposeBlank, recordedPreset, registerCredentialSurfaceRefresh, resolveInitialCatalog, resumeCommand, runningProfile, settleCompactionSurface, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure, teardownViewerForSessionSwap, viewerActionCapability };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import "./startup.mjs";
2
- import { A as subagentJobViewHint, C as registerCredentialSurfaceRefresh, D as shouldConsumeAdvertisedMiss, E as runningProfile, N as forkSeed, O as shouldSteerOnEnter, S as recordedPreset, T as resumeCommand, _ as isUserQueueInput, a as SessionLockRefusedError, b as normalizeSkillInvocation, c as commandRejectsImages, d as dangerCommand, f as foldCompactionEvent, g as isSubagentSettlementNotice, h as isPlainExitPrompt, i as SESSIONLESS_COMMANDS, j as subagentNoticeIsFailure, k as subagentJobTranscriptId, l as compactingFromLog, m as inject, n as HOST_COMMAND_CATALOG, o as apply, p as foldQueueRows, r as LOCAL_COMMANDS, s as busyAfterTurnBoundary, t as Config, u as composeAgent, v as matchPendingSubagentCall, w as resolveInitialCatalog, x as recomposeBlank, y as name } from "./src-oZjot5jQ.mjs";
3
- export { Config, HOST_COMMAND_CATALOG, LOCAL_COMMANDS, SESSIONLESS_COMMANDS, SessionLockRefusedError, apply, busyAfterTurnBoundary, commandRejectsImages, compactingFromLog, composeAgent, dangerCommand, foldCompactionEvent, foldQueueRows, forkSeed, inject, isPlainExitPrompt, isSubagentSettlementNotice, isUserQueueInput, matchPendingSubagentCall, name, normalizeSkillInvocation, recomposeBlank, recordedPreset, registerCredentialSurfaceRefresh, resolveInitialCatalog, resumeCommand, runningProfile, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure };
2
+ import { A as shouldConsumeAdvertisedMiss, C as recomposeBlank, D as resumeCommand, E as resolveInitialCatalog, F as teardownViewerForSessionSwap, I as viewerActionCapability, M as subagentJobTranscriptId, N as subagentJobViewHint, O as runningProfile, P as subagentNoticeIsFailure, R as forkSeed, S as normalizeSkillInvocation, T as registerCredentialSurfaceRefresh, _ as isPlainExitPrompt, a as SessionLockRefusedError, b as matchPendingSubagentCall, c as commandRejectsImages, d as createViewerOpenToken, f as dangerCommand, g as interruptAgent, h as inject, i as SESSIONLESS_COMMANDS, j as shouldSteerOnEnter, k as settleCompactionSurface, l as compactingFromLog, m as foldQueueRows, n as HOST_COMMAND_CATALOG, o as apply, p as foldCompactionEvent, r as LOCAL_COMMANDS, s as busyAfterTurnBoundary, t as Config, u as composeAgent, v as isSubagentSettlementNotice, w as recordedPreset, x as name, y as isUserQueueInput } from "./src-B1uP5j0d.mjs";
3
+ export { Config, HOST_COMMAND_CATALOG, LOCAL_COMMANDS, SESSIONLESS_COMMANDS, SessionLockRefusedError, apply, busyAfterTurnBoundary, commandRejectsImages, compactingFromLog, composeAgent, createViewerOpenToken, dangerCommand, foldCompactionEvent, foldQueueRows, forkSeed, inject, interruptAgent, isPlainExitPrompt, isSubagentSettlementNotice, isUserQueueInput, matchPendingSubagentCall, name, normalizeSkillInvocation, recomposeBlank, recordedPreset, registerCredentialSurfaceRefresh, resolveInitialCatalog, resumeCommand, runningProfile, settleCompactionSurface, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure, teardownViewerForSessionSwap, viewerActionCapability };