@aiwayds/dsh-tui-pi 2.20.0 → 2.22.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.
Files changed (70) hide show
  1. package/README.md +19 -8
  2. package/README.zh-CN.md +19 -8
  3. package/cordis.patch.yml +11 -2
  4. package/icon.svg +7 -0
  5. package/lib/agents.js +23 -3
  6. package/lib/agents.js.map +1 -1
  7. package/lib/btw-overlay.d.ts +18 -2
  8. package/lib/btw-overlay.js +55 -15
  9. package/lib/btw-overlay.js.map +1 -1
  10. package/lib/btw.d.ts +43 -5
  11. package/lib/btw.js +40 -6
  12. package/lib/btw.js.map +1 -1
  13. package/lib/dsh-events.d.ts +23 -9
  14. package/lib/dsh-events.js +20 -8
  15. package/lib/dsh-events.js.map +1 -1
  16. package/lib/history.d.ts +8 -2
  17. package/lib/history.js +4 -1
  18. package/lib/history.js.map +1 -1
  19. package/lib/index.d.ts +3 -1
  20. package/lib/index.js +119 -61
  21. package/lib/index.js.map +1 -1
  22. package/lib/live-widgets.js +6 -3
  23. package/lib/live-widgets.js.map +1 -1
  24. package/lib/login.js.map +1 -1
  25. package/lib/messages.d.ts +10 -0
  26. package/lib/messages.js +39 -0
  27. package/lib/messages.js.map +1 -1
  28. package/lib/preset.d.ts +128 -58
  29. package/lib/preset.js +208 -113
  30. package/lib/preset.js.map +1 -1
  31. package/lib/remote-tail.js +3 -0
  32. package/lib/remote-tail.js.map +1 -1
  33. package/lib/selectors.d.ts +4 -1
  34. package/lib/selectors.js +8 -3
  35. package/lib/selectors.js.map +1 -1
  36. package/lib/session.d.ts +3 -1
  37. package/lib/session.js +16 -11
  38. package/lib/session.js.map +1 -1
  39. package/lib/sessions.d.ts +7 -0
  40. package/lib/sessions.js +15 -1
  41. package/lib/sessions.js.map +1 -1
  42. package/lib/settings.d.ts +17 -11
  43. package/lib/settings.js +35 -26
  44. package/lib/settings.js.map +1 -1
  45. package/lib/source-kind.d.ts +26 -0
  46. package/lib/source-kind.js +17 -0
  47. package/lib/source-kind.js.map +1 -0
  48. package/lib/subagent-policy.js +1 -1
  49. package/lib/subagent-policy.js.map +1 -1
  50. package/lib/subagent-viewer.d.ts +3 -3
  51. package/lib/subagent-viewer.js +9 -6
  52. package/lib/subagent-viewer.js.map +1 -1
  53. package/lib/theme-settings.d.ts +353 -159
  54. package/lib/theme-settings.js +258 -336
  55. package/lib/theme-settings.js.map +1 -1
  56. package/lib/tokens.d.ts +5 -3
  57. package/lib/tokens.js +5 -8
  58. package/lib/tokens.js.map +1 -1
  59. package/locale/en.json +6 -0
  60. package/locale/ja.json +6 -0
  61. package/locale/ko.json +6 -0
  62. package/locale/zh-CN.json +6 -0
  63. package/locales/en.json +7 -2
  64. package/locales/ja.json +7 -2
  65. package/locales/ko.json +7 -2
  66. package/locales/zh-CN.json +7 -2
  67. package/package.json +22 -16
  68. package/scripts/diagnose-container-tui.sh +3 -3
  69. package/scripts/smoke-boot.mjs +2 -2
  70. package/skills/dsh-tui-pi-config/SKILL.md +11 -7
package/lib/index.js CHANGED
@@ -28,7 +28,12 @@ import { AGENT_TICK_MS, LiveWidgets } from "./live-widgets.js";
28
28
  import { displayPermissionPreset } from "./permission.js";
29
29
  import { pickEffort, pickModel, pickPermission, pickPreset, pickTheme } from "./selectors.js";
30
30
  import { DshSessionBridge, persistDefaultModel, stashSessionIdForReload, takeStashedSessionId } from "./session.js";
31
- import { currentCacheHitMode, currentRememberPreset, currentThemePreference, readAskUserExplicit, readFooterHintsPreference, readIconSetPreference, readLanguagePreference, readPanelHeightPreference, readRememberPreset, readSessionManagementExplicit, readSubagentLimits, readThemePreference, registerThemeSettings, writeLanguagePreference, writeThemePreference, } from "./theme-settings.js";
31
+ import { bindTuiConfig, currentCacheHitMode, currentRememberPreset, currentThemePreference, readAskUserExplicit, readFooterHintsPreference, readIconSetPreference, readLanguagePreference, readPanelHeightPreference, readRememberPreset, readSessionManagementExplicit, readSubagentLimits, readThemePreference, subscribeThemeSettings, writeLanguagePreference, writeThemePreference, } from "./theme-settings.js";
32
+ // The plugin's user-facing configuration schema. Exported at the module root:
33
+ // the loader reads `plugin.Config` off the entry module and projects every
34
+ // `.volatile()` field into the settings surface (the 0.1.7 replacement of the
35
+ // 0.1.5 runtime registration call).
36
+ export { Config } from "./theme-settings.js";
32
37
  import { currentLocale, initI18n, listLocales, setLocale, t } from "./i18n/index.js";
33
38
  import { loadWorkspacePresets, rememberedPresetFor, saveWorkspacePresets, withRememberedPreset, workspacePresetsPath, } from "./workspace-presets.js";
34
39
  import { applyIconSet, resolveIconSet, stopIcon } from "./icons.js";
@@ -102,7 +107,7 @@ const SKILL_RESOURCE_BASE = {
102
107
  };
103
108
  const SKILL_INVOCATION = { modelInvocable: true, userInvocable: true };
104
109
  /** Routing description; must stay identical to the SKILL.md frontmatter (asserted in tests). */
105
- const SKILL_DESCRIPTION = 'dsh TUI 增强套件使用与配置指南。凡涉及 TUI 主题/面板/footer、界面语言、子代理并发与轮数限制、模型收藏与隐藏、会话保留与 /resume 过滤、ask_user 超时、preset 记忆,或要配置 dsh-tui 段时先读本指南:settings.yaml 顶层 `dsh-tui:` 段 18 键(language/theme/panelHeight/maxAgents/maxRounds/maxRoundsGrace/disableSubagent/registeredOnly/footerHints/cacheHitMode/iconSet/rememberPreset/favoriteModels/hiddenModels/retention/resume/askUser)、DSH_TUI_* 环境变量、快速上手向导、keybindings.json 与 /hotkeys。触发词:tui、主题、theme、面板、footer、语言、language、收藏模型、隐藏模型、保留策略、panelHeight、resume、preset。';
110
+ const SKILL_DESCRIPTION = 'dsh TUI 增强套件使用与配置指南。凡涉及 TUI 主题/面板/footer、界面语言、子代理并发与轮数限制、模型收藏与隐藏、会话保留与 /resume 过滤、ask_user 超时、preset 记忆,或要配置 dsh-tui 段时先读本指南:插件配置 entry `dsh-tui` 的 17 键(language/theme/panelHeight/maxAgents/maxRounds/maxRoundsGrace/disableSubagent/registeredOnly/footerHints/cacheHitMode/iconSet/rememberPreset/favoriteModels/hiddenModels/retention/resume/askUser)、DSH_TUI_* 环境变量、快速上手向导、keybindings.json 与 /hotkeys。触发词:tui、主题、theme、面板、footer、语言、language、收藏模型、隐藏模型、保留策略、panelHeight、resume、preset。';
106
111
  const SKILL_CANDIDATE = {
107
112
  name: SKILL_PROVIDER_NAME,
108
113
  description: SKILL_DESCRIPTION,
@@ -155,7 +160,14 @@ export function stripFrontmatter(raw) {
155
160
  }
156
161
  return raw;
157
162
  }
158
- export function apply(ctx) {
163
+ export function apply(ctx, config) {
164
+ // Bind the loader-resolved entry config: every field is a live Volatile
165
+ // reference the loader swaps in place on a volatile-only commit, so every
166
+ // theme-settings reader (readThemePreference, readSubagentLimits, …) sees
167
+ // the latest committed values through `.get()` from here on. A test driving
168
+ // `apply(ctx)` without a loader passes nothing — the readers then degrade
169
+ // to the schema defaults.
170
+ bindTuiConfig(config);
159
171
  // Host floor guard, before any side effect: a host older than the peer
160
172
  // floor would only crash deeper in boot with raw TypeErrors (the
161
173
  // alpha-era and rc/stable-era host APIs diverged), so warn in plain
@@ -180,7 +192,7 @@ export function apply(ctx) {
180
192
  let handle;
181
193
  // Theme registry: bundled themes/ plus $DSH_HOME/themes (user overrides
182
194
  // bundled by name). Built ONCE per apply() and handed to every resolveTheme
183
- // / pickTheme call site (startup, the settings watch sink, the /theme
195
+ // / pickTheme call site (startup, the settings hot-reload sink, the /theme
184
196
  // handler), so a custom theme named by DSH_TUI_THEME or by the persisted
185
197
  // preference resolves through the same snapshot. `themeUserNames` marks
186
198
  // which registry ids are user-owned (for the picker's "user theme" /
@@ -209,26 +221,27 @@ export function apply(ctx) {
209
221
  void ensureAppendSystemFile();
210
222
  void migrateAgentsMdTodoSection();
211
223
  /**
212
- * Live theme hot-reload sink, wired to the settings watch hook: a committed
224
+ * Live theme hot-reload sink, wired to the settings/document-updated
225
+ * subscription: a committed
213
226
  * `dsh-tui` theme change (this TUI's /theme write included) is applied to
214
227
  * the running TUI. Set inside runTui once the renderer exists; the first
215
228
  * commit can only follow a user write, long after startup.
216
229
  */
217
230
  let applyThemeRef;
218
231
  /**
219
- * Live panel-height hot-reload sink, wired to the same watch hook: a
232
+ * Live panel-height hot-reload sink, wired to the same subscription: a
220
233
  * committed `dsh-tui` panelHeight change re-budgets the fixed think/tool
221
234
  * panels above the chat input. Armed inside runTui once the widgets exist.
222
235
  */
223
236
  let applyPanelHeightRef;
224
237
  /**
225
- * Live footer-hints hot-reload sink, wired to the same watch hook: a
238
+ * Live footer-hints hot-reload sink, wired to the same subscription: a
226
239
  * committed `dsh-tui` footerHints change repaints the footer hint bar with
227
240
  * the new segment selection. Armed inside runTui once the TUI exists.
228
241
  */
229
242
  let applyFooterHintsRef;
230
243
  /**
231
- * Live icon-set hot-reload sink, wired to the same watch hook: a committed
244
+ * Live icon-set hot-reload sink, wired to the same subscription: a committed
232
245
  * `dsh-tui` iconSet change re-resolves the risky glyphs (an 'auto' change
233
246
  * resolves against the startup font-detection snapshot) and repaints the
234
247
  * footer/notices/panels that carry them. Armed inside runTui once the TUI
@@ -238,19 +251,19 @@ export function apply(ctx) {
238
251
  ctx.effect(async () => {
239
252
  // The theme bundle is built once at TUI startup and held by every
240
253
  // component, so the persisted preference must land before startTui — the
241
- // namespace registration rides the settings injection fiber and the read
242
- // awaits it (bounded, degrades to the defaults without a settings
243
- // service). The registration also watches the namespace: `applies:
244
- // 'live'`, so later commits (the /theme picker, the /settings browser, an
245
- // external edit) hot-apply through applyThemeRef / applyPanelHeightRef /
246
- // applyFooterHintsRef.
254
+ // startup readers resolve the bound entry config synchronously (volatile
255
+ // references, already seeded by the loader before apply ran; a
256
+ // config-less deployment degrades to the schema defaults). The
257
+ // subscription below keeps later commits (the /theme picker, the
258
+ // /settings browser, an external patch edit) hot-applying through
259
+ // applyThemeRef / applyPanelHeightRef / applyFooterHintsRef.
247
260
  // Panel height FIRST, theme second: a single commit of both fields (a
248
- // namespace-level reset, an external edit) must not replay at the wrong
261
+ // config reset, an external edit) must not replay at the wrong
249
262
  // height. setPanelHeight re-budgets the fixed think/tool panels; the
250
263
  // setTheme replay that follows already renders at that new height.
251
264
  // applyTheme carries the theme-bundle identity guard, so a height-only
252
265
  // commit never triggers a second rebuild.
253
- registerThemeSettings(ctx, (pref, height, footerHints, iconSet, language) => {
266
+ subscribeThemeSettings(ctx, (pref, height, footerHints, iconSet, language) => {
254
267
  setLocale(language);
255
268
  applyPanelHeightRef?.(height);
256
269
  applyThemeRef?.(pref);
@@ -259,15 +272,13 @@ export function apply(ctx) {
259
272
  });
260
273
  // Session log retention (async, fire-and-forget): prune jsonl session
261
274
  // directories outside the retention window so the store the core only
262
- // appends to never grows unbounded. Fired AFTER registerThemeSettings —
275
+ // appends to never grows unbounded. Fired AFTER subscribeThemeSettings —
263
276
  // the janitor resolves its knobs through the precedence chain
264
- // (settings.yaml explicit > env > default), and reading settings means
265
- // waiting for the namespace registration that just left; the bounded
266
- // wait rides INSIDE the fire-and-forget pass (readSettings below), so
267
- // the first frame is never blocked. Silent and non-fatal; the pass
268
- // reports its result through the shared notice bridge (deferred to the
269
- // TUI's sink, see setNoticeSink below); process-global one-shot so a
270
- // /reload re-running apply does not start a second pass
277
+ // (settings explicit > env > default), where the explicit layer rides the
278
+ // settings service's describe() (readSettings below). Silent and
279
+ // non-fatal; the pass reports its result through the shared notice bridge
280
+ // (deferred to the TUI's sink, see setNoticeSink below); process-global
281
+ // one-shot so a /reload re-running apply does not start a second pass
271
282
  // (src/retention.ts).
272
283
  void runSessionRetentionOnce({
273
284
  getSessionId: () => {
@@ -282,9 +293,9 @@ export function apply(ctx) {
282
293
  const id = bridgeRef?.getResumingSessionId();
283
294
  return id === undefined ? undefined : String(id);
284
295
  },
285
- // Explicit dsh-tui.retention overrides from settings.yaml (the user
286
- // layer — readSessionManagementExplicit waits for the registration
287
- // bounded, so a settings-less deployment degrades to env/defaults).
296
+ // Explicit dsh-tui.retention overrides from the profile patch (the
297
+ // descriptor's user layer — a settings-less deployment degrades to
298
+ // env/defaults).
288
299
  readSettings: async () => (await readSessionManagementExplicit(ctx))?.retention,
289
300
  });
290
301
  const themePreference = await readThemePreference(ctx);
@@ -292,9 +303,9 @@ export function apply(ctx) {
292
303
  const footerHints = await readFooterHintsPreference(ctx);
293
304
  // UI language (`dsh-tui.language`, default 'en'): discover the language
294
305
  // files and activate the persisted id BEFORE the TUI's first frame, so
295
- // the boot UI speaks it from the start. The registration's watch hook
296
- // re-applies later commits live via setLocale. An unknown id degrades to
297
- // 'en' inside initI18n.
306
+ // the boot UI speaks it from the start. The settings/document-updated
307
+ // subscription re-applies later commits live via setLocale. An unknown id
308
+ // degrades to 'en' inside initI18n.
298
309
  initI18n({ id: await readLanguagePreference(ctx), warn: message => emitNotice(message) });
299
310
  // Icon-set self-adaptation: probe the platform once at startup (the
300
311
  // memoised snapshot shared with every later 'auto' resolution), resolve
@@ -304,7 +315,7 @@ export function apply(ctx) {
304
315
  const iconSetPreference = await readIconSetPreference(ctx);
305
316
  const nerdfontAvailable = await detectNerdFontAvailable();
306
317
  applyIconSet(resolveIconSet(iconSetPreference, nerdfontAvailable));
307
- const presetRoster = await fetchPresetRoster();
318
+ const presetRoster = await fetchPresetRoster(ctx);
308
319
  // Preset memory (`dsh-tui.rememberPreset`, default ON): the last /preset
309
320
  // selection committed in THIS workspace (keyed by the session backend's
310
321
  // project key of the cwd) becomes the launch selection — runTui seeds it
@@ -357,21 +368,22 @@ export function apply(ctx) {
357
368
  const keyBindings = loadKeyBindings(keyFile);
358
369
  // Preset state: /preset owns the selection (Tab is deliberately unbound —
359
370
  // switching is an explicit, confirmed action). The roster is fetched once
360
- // at startup from the api-proxy service; an empty roster (no service / no
361
- // presets) disables the feature gracefully (/preset errors, footer shows
371
+ // at startup from the host's `agentPresets` registry (dsh 0.1.7
372
+ // declarative presets); an empty roster (no service / no presets)
373
+ // disables the feature gracefully (/preset errors, footer shows
362
374
  // plain "dsh"). The initial selection is the workspace's REMEMBERED
363
375
  // preset when preset memory is on and the id is still on the roster
364
- // (initialPresetIndex prefers it), else the `standard` entry, else the
365
- // first-scanned one. Without memory the launch selection is a local
366
- // display only: before the user switches via /preset, NO
367
- // `meta.agentPreset` is sent at session create, so the server-side
368
- // default (`agent-presets.default` settings / deployment config)
369
- // governs. WITH memory the remembered id is ALSO seeded into the bridge
370
- // below (bridge.setAgentPreset), which is the point: the first session
371
- // of the launch composes under the remembered preset, not the server
372
- // default. We deliberately seed ONLY a remembered id, never
373
- // DEFAULT_PRESET_ID — that would override the server-side default with
374
- // a client-side assumption.
376
+ // (initialPresetIndex prefers it), else the roster's own default row,
377
+ // else `standard`, else the first-listed one. Without memory the launch
378
+ // selection is a local display only: before the user switches via
379
+ // /preset, NO `meta.agentPreset` is sent at session create, so the
380
+ // registry's deployment default (`default`/`selectedDefault` config of
381
+ // the agent-preset-registry entry) governs. WITH memory the remembered
382
+ // id is ALSO seeded into the bridge below (bridge.setAgentPreset), which
383
+ // is the point: the first session of the launch composes under the
384
+ // remembered preset, not the server default. We deliberately seed ONLY a
385
+ // remembered id, never DEFAULT_PRESET_ID — that would override the
386
+ // server-side default with a client-side assumption.
375
387
  const rememberedPresetEntry = rememberedPresetId !== undefined
376
388
  ? presetRoster.find(preset => preset.id === rememberedPresetId)
377
389
  : undefined;
@@ -606,7 +618,7 @@ export function apply(ctx) {
606
618
  if (!cancelled && stoppedChildren === 0)
607
619
  renderer.renderNotice(t('tui.stop.nothingRunning'), 'info');
608
620
  };
609
- // Arm the settings watch sink now that the renderer exists (see apply()).
621
+ // Arm the settings hot-reload sink now that the renderer exists (see apply()).
610
622
  applyThemeRef = (pref) => {
611
623
  themePreferenceRef = pref;
612
624
  // 'auto' subscribes to the terminal's live color-scheme pushes; an
@@ -621,8 +633,7 @@ export function apply(ctx) {
621
633
  renderer.renderNotice(t('tui.keys.warningPrefix', { warning }), 'error');
622
634
  }
623
635
  // Arm the shared notice sink now that the TUI is up. Every operator
624
- // trace that fired before this point — the settings-namespace
625
- // registration failure, invalid dsh-tui.retention/resume values, a
636
+ // trace that fired before this point — invalid dsh-tui.retention/resume values, a
626
637
  // missing userQuestions service, the janitor's result line — has been
627
638
  // queueing on the bridge (src/notice-bridge.ts); registering here
628
639
  // drains that batch as stacked transient notices above the footer
@@ -641,7 +652,7 @@ export function apply(ctx) {
641
652
  // Live maxRounds for the compact running-agent lines' `round N/M` — read
642
653
  // at every rebuild (the /agents limits panel hot-applies).
643
654
  () => readSubagentLimits(ctx).maxRounds);
644
- // Arm the panel-height watch sink now that the widgets exist: a committed
655
+ // Arm the panel-height hot-reload sink now that the widgets exist: a committed
645
656
  // panelHeight change re-budgets the think/tool panels — they are
646
657
  // self-drawing, so the next frame already renders at the new height.
647
658
  applyPanelHeightRef = (height) => {
@@ -744,6 +755,10 @@ export function apply(ctx) {
744
755
  liveWidgets.clear();
745
756
  const session = resumed.agent.session;
746
757
  const adopted = 'adopted' in resumed && resumed.adopted === true;
758
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
759
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
760
+ // here deliberately — this path re-consumes RAW events (renderer/stats
761
+ // replay), which the projection successor does not serve. (T14 review.)
747
762
  bridge.replay(adopted ? session.snapshotEvents() : session.snapshotEvents().filter(event => event.seq < session.firstLiveSeq));
748
763
  for (const text of bridge.takePendingRemoteFollowups()) {
749
764
  await bridge.prompt(text);
@@ -768,7 +783,7 @@ export function apply(ctx) {
768
783
  // child's cap and hard-stops it after the grace window — the per-agent
769
784
  // tier reads the agent file's frontmatter `maxRounds` through the
770
785
  // registry contract. Limits are read live from the `dsh-tui` settings
771
- // namespace (/agents → l limits).
786
+ // entry config (/agents → l limits).
772
787
  const subagentPolicy = applySubagentPolicy(ctx, {
773
788
  getLive: () => bridge.getLiveChildren(),
774
789
  getRoundCount: childId => bridge.getRoundCount(childId),
@@ -925,6 +940,10 @@ export function apply(ctx) {
925
940
  const agent = bridge.getAgent();
926
941
  return agent === undefined
927
942
  ? []
943
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
944
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
945
+ // here deliberately — this path re-consumes RAW events (renderer/stats
946
+ // replay), which the projection successor does not serve. (T14 review.)
928
947
  : buildBtwSnapshot(agent.session.snapshotEvents(), btwSnapshotLimit);
929
948
  },
930
949
  requestRender: () => ui.requestRender(),
@@ -1133,6 +1152,10 @@ export function apply(ctx) {
1133
1152
  // log is the stable source (dispose drops the registry handle), and
1134
1153
  // slicing here keeps the running turn out of the seed (the same
1135
1154
  // completed-turn rule /new obeys by cancelling it).
1155
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
1156
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
1157
+ // here deliberately — this path re-consumes RAW events (renderer/stats
1158
+ // replay), which the projection successor does not serve. (T14 review.)
1136
1159
  const seed = completedTurnSeed(agent.session.snapshotEvents());
1137
1160
  if (seed.length === 0) {
1138
1161
  // No completed turn to carry — a fork is indistinguishable from a
@@ -1163,6 +1186,10 @@ export function apply(ctx) {
1163
1186
  // rebuild rides the same replay. `firstLiveSeq` excludes nothing
1164
1187
  // published (constructor seeds never emit).
1165
1188
  const session = handle.agent.session;
1189
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
1190
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
1191
+ // here deliberately — this path re-consumes RAW events (renderer/stats
1192
+ // replay), which the projection successor does not serve. (T14 review.)
1166
1193
  bridge.replay(session.snapshotEvents().filter(event => event.seq < session.firstLiveSeq));
1167
1194
  ui.requestRender();
1168
1195
  }
@@ -1277,9 +1304,10 @@ export function apply(ctx) {
1277
1304
  let picked;
1278
1305
  try {
1279
1306
  const currentId = bridge.getSessionId();
1280
- // Explicit dsh-tui.resume overrides from settings.yaml (the user
1281
- // layer): the picker resolves them against env/defaults per open,
1282
- // so a committed settings change applies to the next /resume.
1307
+ // Explicit dsh-tui.resume overrides from the profile patch (the
1308
+ // descriptor's user layer): the picker resolves them against
1309
+ // env/defaults per open, so a committed settings change applies to
1310
+ // the next /resume.
1283
1311
  const resumeSettings = (await readSessionManagementExplicit(ctx))?.resume;
1284
1312
  picked = await pickPersistedSession(ctx, ui.tui, ui.theme, currentId === undefined ? undefined : String(currentId), refocusEditor, resumeSettings);
1285
1313
  }
@@ -1371,6 +1399,10 @@ export function apply(ctx) {
1371
1399
  // everything the other surface did. Cold resumes keep the firstLiveSeq
1372
1400
  // filter (seeded history replays once; live events re-arrive).
1373
1401
  const adopted = 'adopted' in resumed && resumed.adopted === true;
1402
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
1403
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
1404
+ // here deliberately — this path re-consumes RAW events (renderer/stats
1405
+ // replay), which the projection successor does not serve. (T14 review.)
1374
1406
  bridge.replay(adopted ? session.snapshotEvents() : session.snapshotEvents().filter(event => event.seq < session.firstLiveSeq));
1375
1407
  ui.requestRender();
1376
1408
  return {
@@ -1447,6 +1479,10 @@ export function apply(ctx) {
1447
1479
  const id = bridge.getSessionId();
1448
1480
  return id === undefined ? undefined : String(id);
1449
1481
  },
1482
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
1483
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
1484
+ // here deliberately — this path re-consumes RAW events (renderer/stats
1485
+ // replay), which the projection successor does not serve. (T14 review.)
1450
1486
  getLiveEvents: () => bridge.getAgent()?.session.snapshotEvents(),
1451
1487
  copyToEditor: text => {
1452
1488
  ui.editor.setText(text);
@@ -1473,6 +1509,10 @@ export function apply(ctx) {
1473
1509
  // the seed IS the point, so it replays in full (`firstLiveSeq`
1474
1510
  // covers seed + end-seed).
1475
1511
  const session = handle.agent.session;
1512
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
1513
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
1514
+ // here deliberately — this path re-consumes RAW events (renderer/stats
1515
+ // replay), which the projection successor does not serve. (T14 review.)
1476
1516
  bridge.replay(session.snapshotEvents().filter(event => event.seq < session.firstLiveSeq));
1477
1517
  ui.requestRender();
1478
1518
  }
@@ -1515,6 +1555,10 @@ export function apply(ctx) {
1515
1555
  renderer.clear();
1516
1556
  liveWidgets.clear();
1517
1557
  const session = resumed.agent.session;
1558
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
1559
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
1560
+ // here deliberately — this path re-consumes RAW events (renderer/stats
1561
+ // replay), which the projection successor does not serve. (T14 review.)
1518
1562
  bridge.replay(session.snapshotEvents().filter(event => event.seq < session.firstLiveSeq));
1519
1563
  ui.requestRender();
1520
1564
  }
@@ -1538,6 +1582,10 @@ export function apply(ctx) {
1538
1582
  renderer.clear();
1539
1583
  liveWidgets.clear();
1540
1584
  const session = resumed.agent.session;
1585
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
1586
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
1587
+ // here deliberately — this path re-consumes RAW events (renderer/stats
1588
+ // replay), which the projection successor does not serve. (T14 review.)
1541
1589
  bridge.replay(session.snapshotEvents().filter(event => event.seq < session.firstLiveSeq));
1542
1590
  ui.requestRender();
1543
1591
  }
@@ -1560,6 +1608,10 @@ export function apply(ctx) {
1560
1608
  description: t('tui.cmd.export.description'),
1561
1609
  input: { hint: '[path]' },
1562
1610
  handler: async (invocation) => {
1611
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
1612
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
1613
+ // here deliberately — this path re-consumes RAW events (renderer/stats
1614
+ // replay), which the projection successor does not serve. (T14 review.)
1563
1615
  const events = invocation.agent.session.snapshotEvents();
1564
1616
  const fallback = join(homedir(), 'Downloads', `dsh-session-${clipToWidth(String(invocation.agent.session.id), 8)}.jsonl`);
1565
1617
  const target = invocation.rawInput.trim() === '' ? fallback : resolve(invocation.rawInput.trim());
@@ -1611,6 +1663,10 @@ export function apply(ctx) {
1611
1663
  renderer.clear();
1612
1664
  liveWidgets.clear();
1613
1665
  const session = restored.agent.session;
1666
+ // dsh 0.1.7 soft-deprecation: snapshotEvents is deprecated upstream in favor of
1667
+ // projection registration / sessionPersistence.handle.read() pagination. Kept
1668
+ // here deliberately — this path re-consumes RAW events (renderer/stats
1669
+ // replay), which the projection successor does not serve. (T14 review.)
1614
1670
  bridge.replay(session.snapshotEvents().filter(event => event.seq < session.firstLiveSeq));
1615
1671
  }
1616
1672
  catch (error) {
@@ -1627,7 +1683,8 @@ export function apply(ctx) {
1627
1683
  registerLocalCommand('new', t('tui.cmd.new.description'), newHandler);
1628
1684
  // /settings: text-based configuration browser — the terminal counterpart
1629
1685
  // of the web GUI's settings surface. Enumerates ctx.settings.describe()
1630
- // and walks each namespace's schema (drill-ins, cycle rows, inline editors,
1686
+ // (one descriptor per settings-bearing plugin entry) and walks each
1687
+ // entry's schema (drill-ins, cycle rows, inline editors,
1631
1688
  // reset-to-defaults), writing through settings.mutate path ops.
1632
1689
  const settingsHandler = async () => {
1633
1690
  if (ctx.get('settings') === undefined) {
@@ -1665,11 +1722,12 @@ export function apply(ctx) {
1665
1722
  };
1666
1723
  registerLocalCommand('skills', t('tui.cmd.skills.description'), skillsHandler);
1667
1724
  // /theme: pick a color scheme and apply it immediately — the choice is
1668
- // persisted to the dsh-tui settings namespace (`applies: 'live'`, so the
1669
- // watch hook would re-apply the same change anyway) and hot-swapped into
1670
- // the running TUI: footer hint, editor border and the whole transcript
1671
- // repaint on the next frame, no restart needed. The settings guard
1672
- // mirrors /settings: without the service there is nowhere to write.
1725
+ // persisted to the dsh-tui entry config (a volatile-only write: the
1726
+ // document-updated subscription re-applies the same value anyway, an
1727
+ // idempotent echo) and hot-swapped into the running TUI: footer hint,
1728
+ // editor border and the whole transcript repaint on the next frame, no
1729
+ // restart needed. The settings guard mirrors /settings: without the
1730
+ // service there is nowhere to write.
1673
1731
  const themeHandler = async () => {
1674
1732
  if (ctx.get('settings') === undefined) {
1675
1733
  return { kind: 'error', text: t('tui.settings.unavailable') };
@@ -1817,7 +1875,7 @@ export function apply(ctx) {
1817
1875
  registerLocalCommand('login', t('tui.cmd.login.description'), loginHandler);
1818
1876
  // /logout: unsubscribe a provider — pi-agent's /logout on the dsh side.
1819
1877
  // Lists the providers with a stored credential; on selection removes the
1820
- // key AND the settings.yaml provider entry, so the provider's models
1878
+ // key AND the profile patch's llm-pi-ai provider entry, so the provider's models
1821
1879
  // leave /model right away (the llm-pi-ai adapter keeps a route
1822
1880
  // registered for every profile key). /login re-subscribes and serves
1823
1881
  // the installed catalog's current model list.
@@ -1922,12 +1980,12 @@ export function apply(ctx) {
1922
1980
  let footerHintsRef = footerHints;
1923
1981
  const footerHint = new FooterHint(() => ui.theme, () => footerHintsRef);
1924
1982
  ui.footer.addChild(footerHint);
1925
- // Arm the footer-hints watch sink now that the TUI exists (see apply()).
1983
+ // Arm the footer-hints hot-reload sink now that the TUI exists (see apply()).
1926
1984
  applyFooterHintsRef = (hints) => {
1927
1985
  footerHintsRef = hints;
1928
1986
  ui.requestRender();
1929
1987
  };
1930
- // Arm the icon-set watch sink: a committed iconSet change hot-applies the
1988
+ // Arm the icon-set hot-reload sink: a committed iconSet change hot-applies the
1931
1989
  // risky glyphs (an 'auto' change resolves against the startup font-
1932
1990
  // detection snapshot, matching the startup glyph choice) and repaints the
1933
1991
  // footer/notices/panels that carry them on the next frame.