@deepseek-harness-tui/dsh-tui 0.9.2 → 0.9.3

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 (214) hide show
  1. package/README.md +4 -4
  2. package/bin/dsh-tui.js +26 -4
  3. package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -13
  4. package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -59
  5. package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -15
  6. package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -32
  7. package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -59
  8. package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -166
  9. package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -42
  10. package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -143
  11. package/dsh-ecosystem-spec/registry/README.md +14 -14
  12. package/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -56
  13. package/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -13
  14. package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -149
  15. package/lib/types/commands.d.ts.map +1 -1
  16. package/lib/types/commands.js +7 -8
  17. package/lib/types/components/BalanceReportRow.d.ts +24 -0
  18. package/lib/types/components/BalanceReportRow.d.ts.map +1 -0
  19. package/lib/types/components/BalanceReportRow.js +87 -0
  20. package/lib/types/components/HistorySearchDialog.js +1 -1
  21. package/lib/types/components/MessageList.d.ts +3 -1
  22. package/lib/types/components/MessageList.d.ts.map +1 -1
  23. package/lib/types/components/MessageList.js +11 -7
  24. package/lib/types/components/PluginSceneBoundary.d.ts +1 -1
  25. package/lib/types/components/PluginSceneBoundary.js +1 -1
  26. package/lib/types/components/PromptInput.d.ts.map +1 -1
  27. package/lib/types/components/PromptInput.js +16 -0
  28. package/lib/types/components/Whale.d.ts +15 -4
  29. package/lib/types/components/Whale.d.ts.map +1 -1
  30. package/lib/types/components/Whale.js +9 -8
  31. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +7 -1
  32. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  33. package/lib/types/components/messages/AssistantToolUseMessage.js +34 -4
  34. package/lib/types/deepseekBalance.d.ts +53 -0
  35. package/lib/types/deepseekBalance.d.ts.map +1 -0
  36. package/lib/types/deepseekBalance.js +86 -0
  37. package/lib/types/deepseekPricing.d.ts +82 -0
  38. package/lib/types/deepseekPricing.d.ts.map +1 -0
  39. package/lib/types/deepseekPricing.js +133 -0
  40. package/lib/types/dsh-adapter/channel.d.ts +46 -1
  41. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  42. package/lib/types/dsh-adapter/channel.js +84 -10
  43. package/lib/types/dsh-adapter/index.d.ts +5 -0
  44. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  45. package/lib/types/dsh-adapter/index.js +1 -0
  46. package/lib/types/dsh-adapter/packaged-skills.d.ts +9 -0
  47. package/lib/types/dsh-adapter/packaged-skills.d.ts.map +1 -1
  48. package/lib/types/dsh-adapter/packaged-skills.js +19 -4
  49. package/lib/types/dsh-adapter/plugin.d.ts +6 -0
  50. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  51. package/lib/types/dsh-adapter/plugin.js +122 -18
  52. package/lib/types/dsh-adapter/settings-sections.d.ts +1 -0
  53. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  54. package/lib/types/dsh-adapter/settings-sections.js +35 -2
  55. package/lib/types/history.d.ts +4 -3
  56. package/lib/types/history.d.ts.map +1 -1
  57. package/lib/types/history.js +66 -18
  58. package/lib/types/i18n.d.ts +122 -55
  59. package/lib/types/i18n.d.ts.map +1 -1
  60. package/lib/types/i18n.js +39 -18
  61. package/lib/types/ink/ink.d.ts.map +1 -1
  62. package/lib/types/ink/ink.js +67 -17
  63. package/lib/types/ink/root.d.ts +16 -0
  64. package/lib/types/ink/root.d.ts.map +1 -1
  65. package/lib/types/ink/root.js +2 -0
  66. package/lib/types/ink/terminal-querier.d.ts +8 -0
  67. package/lib/types/ink/terminal-querier.d.ts.map +1 -1
  68. package/lib/types/ink/terminal-querier.js +13 -0
  69. package/lib/types/ink/terminal.d.ts +42 -0
  70. package/lib/types/ink/terminal.d.ts.map +1 -1
  71. package/lib/types/ink/terminal.js +52 -2
  72. package/lib/types/plugin-spec/registry.d.ts +1 -1
  73. package/lib/types/plugin-spec/registry.js +1 -1
  74. package/lib/types/screens/Chat.d.ts.map +1 -1
  75. package/lib/types/screens/Chat.js +72 -37
  76. package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
  77. package/lib/types/screens/SessionBrowser.js +28 -13
  78. package/lib/types/screens/SessionTree.d.ts.map +1 -1
  79. package/lib/types/screens/SessionTree.js +33 -9
  80. package/lib/types/screens/Settings.d.ts +7 -5
  81. package/lib/types/screens/Settings.d.ts.map +1 -1
  82. package/lib/types/screens/Settings.js +206 -99
  83. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  84. package/lib/types/screens/StatusLine.js +32 -1
  85. package/lib/types/sessionHistory.d.ts +11 -0
  86. package/lib/types/sessionHistory.d.ts.map +1 -1
  87. package/lib/types/sessionHistory.js +29 -0
  88. package/lib/types/sessions/format.d.ts.map +1 -1
  89. package/lib/types/sessions/format.js +14 -4
  90. package/lib/types/tips.js +2 -2
  91. package/lib/types/tuiDisplayPrefs.d.ts +3 -0
  92. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  93. package/lib/types/tuiDisplayPrefs.js +1 -0
  94. package/lib/types/update.d.ts +46 -0
  95. package/lib/types/update.d.ts.map +1 -1
  96. package/lib/types/update.js +232 -3
  97. package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +21 -21
  98. package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +44 -44
  99. package/node_modules/@dsh-std/command/CHANGELOG.md +8 -8
  100. package/node_modules/@dsh-std/command/LICENSE +21 -21
  101. package/node_modules/@dsh-std/command/README.md +26 -26
  102. package/node_modules/@dsh-std/command/README.zh.md +28 -28
  103. package/node_modules/@dsh-std/command/lib/index.js.map +1 -1
  104. package/node_modules/@dsh-std/command/package.json +22 -22
  105. package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  106. package/node_modules/@dsh-std/connection/LICENSE +21 -21
  107. package/node_modules/@dsh-std/connection/README.md +42 -42
  108. package/node_modules/@dsh-std/connection/README.zh.md +42 -42
  109. package/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  110. package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  111. package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  112. package/node_modules/@dsh-std/connection/package.json +20 -20
  113. package/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  114. package/node_modules/@dsh-std/core/LICENSE +21 -21
  115. package/node_modules/@dsh-std/core/README.md +13 -13
  116. package/node_modules/@dsh-std/core/README.zh.md +13 -13
  117. package/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  118. package/node_modules/@dsh-std/core/package.json +21 -21
  119. package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  120. package/node_modules/@dsh-std/manifest/LICENSE +21 -21
  121. package/node_modules/@dsh-std/manifest/README.md +5 -5
  122. package/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  123. package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  124. package/node_modules/@dsh-std/manifest/package.json +20 -20
  125. package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  126. package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -8
  127. package/node_modules/@dsh-std/messages/LICENSE +21 -21
  128. package/node_modules/@dsh-std/messages/README.md +5 -5
  129. package/node_modules/@dsh-std/messages/README.zh.md +7 -7
  130. package/node_modules/@dsh-std/messages/lib/index.js.map +1 -1
  131. package/node_modules/@dsh-std/messages/package.json +20 -20
  132. package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -54
  133. package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  134. package/node_modules/@dsh-std/presentation/LICENSE +21 -21
  135. package/node_modules/@dsh-std/presentation/README.md +27 -27
  136. package/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  137. package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  138. package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  139. package/node_modules/@dsh-std/presentation/package.json +25 -25
  140. package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -8
  141. package/node_modules/@dsh-std/storage/LICENSE +21 -21
  142. package/node_modules/@dsh-std/storage/README.md +5 -5
  143. package/node_modules/@dsh-std/storage/README.zh.md +7 -7
  144. package/node_modules/@dsh-std/storage/lib/index.js.map +1 -1
  145. package/node_modules/@dsh-std/storage/package.json +20 -20
  146. package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -56
  147. package/package.json +95 -5
  148. package/skills/pr-comments/SKILL.md +1 -1
  149. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  150. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -21
  151. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -42
  152. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -42
  153. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  154. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  155. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  156. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -20
  157. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  158. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -21
  159. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -13
  160. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -13
  161. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  162. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -21
  163. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  164. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -21
  165. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -5
  166. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  167. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  168. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -20
  169. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  170. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  171. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -21
  172. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +27 -27
  173. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  174. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  175. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  176. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +25 -25
  177. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  178. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -21
  179. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -13
  180. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -13
  181. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  182. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -21
  183. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  184. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -21
  185. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -13
  186. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -13
  187. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  188. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -21
  189. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  190. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -21
  191. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -13
  192. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -13
  193. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  194. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -21
  195. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  196. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -21
  197. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -42
  198. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -42
  199. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  200. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  201. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  202. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -20
  203. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  204. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -21
  205. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -13
  206. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -13
  207. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  208. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -21
  209. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  210. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -21
  211. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -13
  212. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -13
  213. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  214. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -21
@@ -20,9 +20,9 @@ import { readPresetPref } from '../presetPrefs.js';
20
20
  import { composePreset, filterMinimalPresetTools, resolvePersistedPreset, resolvePersistedRoute, runningPresetOf } from './presets.js';
21
21
  import { ensurePackagedPresets } from './packaged-presets.js';
22
22
  import { ensureLegacySessionEventTypes } from './compat/index.js';
23
- import { clearResumeTarget, writeResumeTarget } from '../sessionHistory.js';
23
+ import { clearResumeTarget, resumeTargetFromArgv, writeResumeTarget } from '../sessionHistory.js';
24
24
  import { resolveSessionCwd } from '../utils/workspaceRoot.js';
25
- import { beginRestartAttempt, checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isVersionNewer, logRestartEvent, resolveDshProfileName, resolveTuiUpdateTarget, restartTui, updateTuiAndRestart, writeHandoffNotice } from '../update.js';
25
+ import { beginRestartAttempt, checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isStandaloneRuntime, isVersionNewer, logRestartEvent, resolveDshProfileName, resolveTuiUpdateTarget, restartTui, updateTuiAndRestart, writeHandoffNotice } from '../update.js';
26
26
  import { getLang, isLang, resolveStartupLang, setLang, t, writeLangPref } from '../i18n.js';
27
27
  import { DEFAULT_STATUS_BAR, normalizeScrollGutter, normalizeStatusBar, normalizeToolBackground } from '../tuiDisplayPrefs.js';
28
28
  import { draftComboConflicts, effectiveComboString, parseComboDraft, setKeymapOverrides, SHORTCUT_ACTIONS, } from '../utils/keymap.js';
@@ -35,7 +35,7 @@ import { getHostStatusStore } from './status.js';
35
35
  import { getHostShortcuts } from './shortcuts.js';
36
36
  import { attachSessionToWorkspace } from './workspace.js';
37
37
  import { createLocalWorkspaceRuntime, getHostWorkspaceRuntime } from './workspaces.js';
38
- import { getHostSettingsSections } from './settings-sections.js';
38
+ import { getHostSettingsSections, getLocalSettingsSectionsHost } from './settings-sections.js';
39
39
  import { withHostRootCapability } from './host-access.js';
40
40
  import { render, ThemeProvider, AlternateScreen } from '../ui.js';
41
41
  import instances from '../ink/instances.js';
@@ -350,7 +350,11 @@ export async function apply(ctx, config) {
350
350
  }
351
351
  const sessionCwd = initialWorkspace?.cwd ?? resolveSessionCwd(config.cwd);
352
352
  const meta = { cwd: sessionCwd };
353
- const { agent, handle, agentPreset, route: createdRoute } = await resolveAgent(ctx, config.sessionId, configuredRoute, startupRoute, meta, config.preset);
353
+ // Launch-time resume target: the env handoff (launchers like naive-dsh) wins;
354
+ // `dsh --profile tui` forwards `--resume` verbatim instead, so fall back to
355
+ // parsing the forwarded app args (parity with the standalone bin).
356
+ const launchSessionId = config.sessionId ?? resumeTargetFromArgv(process.argv.slice(2));
357
+ const { agent, handle, agentPreset, route: createdRoute } = await resolveAgent(ctx, launchSessionId, configuredRoute, startupRoute, meta, config.preset);
354
358
  try {
355
359
  // Opening a persisted TUI session is an explicit ownership action too.
356
360
  // Older TUI versions only wrote the Session log, so attaching on every
@@ -408,6 +412,7 @@ export async function apply(ctx, config) {
408
412
  thinkingFold: config.thinkingFold,
409
413
  toolBackground: config.toolBackground,
410
414
  scrollGutter: config.scrollGutter,
415
+ foldTerminalCommand: config.foldTerminalCommand,
411
416
  promptSessionLabel: config.promptSessionLabel,
412
417
  statusBar: config.statusBar,
413
418
  handle,
@@ -444,6 +449,13 @@ export async function apply(ctx, config) {
444
449
  thinkingFold: Schema.union(['preview', 'full']).default('preview'),
445
450
  toolBackground: Schema.union(['none', 'subtle', 'strong']).default('none'),
446
451
  scrollGutter: Schema.union(['timeline', 'scrollbar', 'hidden']).default('timeline'),
452
+ // No default on purpose (same rule as `fullscreen` below): a schema
453
+ // default here would come back from scope.get()/watch() and shadow
454
+ // an explicit cordis.yml `foldTerminalCommand: true` while the
455
+ // settings user layer is unset — applyDisplay's
456
+ // `?? config.foldTerminalCommand ?? false` already supplies the
457
+ // default and keeps cordis.yml decisive.
458
+ foldTerminalCommand: Schema.boolean(),
447
459
  promptSessionLabel: Schema.boolean().default(false),
448
460
  statusBar: Schema.object({
449
461
  compact: Schema.boolean().default(DEFAULT_STATUS_BAR.compact),
@@ -516,6 +528,7 @@ export async function apply(ctx, config) {
516
528
  channel.setThinkingFold(value.thinkingFold ?? config.thinkingFold ?? 'preview');
517
529
  channel.setToolBackground(normalizeToolBackground(value.toolBackground ?? config.toolBackground));
518
530
  channel.setScrollGutter(normalizeScrollGutter(value.scrollGutter ?? config.scrollGutter));
531
+ channel.setFoldTerminalCommand(value.foldTerminalCommand ?? config.foldTerminalCommand ?? false);
519
532
  channel.setPromptSessionLabel(value.promptSessionLabel ?? config.promptSessionLabel ?? false);
520
533
  channel.setStatusBar(normalizeStatusBar(value.statusBar ?? config.statusBar));
521
534
  };
@@ -672,8 +685,14 @@ export async function apply(ctx, config) {
672
685
  },
673
686
  };
674
687
  });
675
- const settingsSections = getHostSettingsSections(ctx.get('tuiSettingsSections'));
676
- if (settingsSections !== undefined) {
688
+ // Prefer the composition's sections service; fall back to the in-package
689
+ // local host. Real compositions have been observed disposing the whole
690
+ // dsh-tui-* host-seam insert list right after load (issue #557), which
691
+ // left this registration silently skipped and /settings read-only.
692
+ // channel.ts reads through the same fallback, so both sides meet in the
693
+ // same registry either way.
694
+ {
695
+ const settingsSections = getHostSettingsSections(ctx.get('tuiSettingsSections')) ?? getLocalSettingsSectionsHost();
677
696
  const unregister = settingsSections.register({
678
697
  ns: 'dsh-tui',
679
698
  title: 'dsh-tui',
@@ -768,6 +787,19 @@ export async function apply(ctx, config) {
768
787
  { value: 'hidden', label: 'Hidden', descriptions: { zh: '隐藏' } },
769
788
  ],
770
789
  },
790
+ {
791
+ path: ['foldTerminalCommand'],
792
+ label: 'Fold terminal command',
793
+ descriptions: { zh: '折叠终端命令' },
794
+ hint: 'Terminal cards (Bash/PowerShell): collapse a multi-line command header to its first line + count; Ctrl+O or a click expands it.',
795
+ hintDescriptions: { zh: '终端卡(Bash/PowerShell):多行命令头部折叠为首行 + 计数;Ctrl+O 或点击卡片展开。' },
796
+ kind: 'boolean',
797
+ format(value) {
798
+ // Unset in settings.yaml: show the effective resolution (cordis.yml
799
+ // → off) instead of a blank — same rule as `fullscreen`'s field.
800
+ return String(typeof value === 'boolean' ? value : config.foldTerminalCommand === true);
801
+ },
802
+ },
771
803
  {
772
804
  path: ['promptSessionLabel'],
773
805
  label: 'Session name chip',
@@ -852,6 +884,15 @@ export async function apply(ctx, config) {
852
884
  group: 'status-bar',
853
885
  kind: 'boolean',
854
886
  },
887
+ {
888
+ path: ['statusBar', 'cost'],
889
+ label: 'Show session cost estimate',
890
+ descriptions: { zh: '显示本会话花费估算' },
891
+ hint: 'Show the estimated session spend (≈¥) next to the token totals. Only appears for official DeepSeek providers whose model has a known price; the estimate follows the official per-million-token rates (peak/idle hours) and is not a bill.',
892
+ hintDescriptions: { zh: '在 Token 总量旁显示本会话花费估算(≈¥)。仅在使用 DeepSeek 官方 API key 且模型有已知单价时显示;按官方每百万 token 单价(高峰/空闲时段)估算,非账单。' },
893
+ group: 'status-bar',
894
+ kind: 'boolean',
895
+ },
855
896
  {
856
897
  path: ['statusBar', 'tps'],
857
898
  label: 'Show output speed',
@@ -985,10 +1026,13 @@ export async function apply(ctx, config) {
985
1026
  }
986
1027
  // Positional command-line arguments are the initial prompt (issue #53):
987
1028
  // `dsh-tui "run the tests"` forwards positionals through the dsh CLI,
988
- // which mounts them as ctx.cmdlineArgs. Submit once the channel exists —
1029
+ // which mounts them as ctx.cmdlineArgs. The service shape drifted across
1030
+ // dsh-cmdline builds — `{ get() }` is the current contract, older builds
1031
+ // exposed `{ args }` — so read both. Submit once the channel exists;
989
1032
  // delivery goes through the normal pending/inbox chain, so no special
990
1033
  // timing is needed; flag-shaped leftovers are not prompt text.
991
- const cmdlineArgs = ctx.cmdlineArgs?.args;
1034
+ const cmdline = ctx.cmdlineArgs;
1035
+ const cmdlineArgs = cmdline?.get?.() ?? cmdline?.args;
992
1036
  const initialPrompt = cmdlineArgs?.filter(arg => !arg.startsWith('-')).join(' ').trim();
993
1037
  if (initialPrompt)
994
1038
  channel.submit(initialPrompt);
@@ -1042,7 +1086,10 @@ export async function apply(ctx, config) {
1042
1086
  catch {
1043
1087
  // Resume persistence is best effort and must never block an update.
1044
1088
  }
1045
- void finishExit(ctx, instance, bootedFullscreen, 'Updating @deepseek-harness-tui/dsh-tui and restarting…', undefined, () => runUpdate(ctx, profile, channel.agentId, updateTargetVersion));
1089
+ const hintText = isStandaloneRuntime()
1090
+ ? t('update-standalone-starting')
1091
+ : t('update-starting');
1092
+ void finishExit(ctx, instance, bootedFullscreen, hintText, undefined, () => runUpdate(ctx, profile, channel.agentId, updateTargetVersion));
1046
1093
  return;
1047
1094
  }
1048
1095
  // `/restart`: same handoff as the update path, no installation step.
@@ -1159,7 +1206,12 @@ export async function apply(ctx, config) {
1159
1206
  }
1160
1207
  updateTargetVersion = target.latest;
1161
1208
  }
1162
- channel.notify(t('update-starting'));
1209
+ if (isStandaloneRuntime()) {
1210
+ channel.notify(t('update-standalone-starting'));
1211
+ }
1212
+ else {
1213
+ channel.notify(t('update-starting'));
1214
+ }
1163
1215
  updateRequested = true;
1164
1216
  handleExit();
1165
1217
  });
@@ -1199,7 +1251,8 @@ export async function apply(ctx, config) {
1199
1251
  void checkForTuiUpdate().then((update) => {
1200
1252
  if (update === undefined || exited || updateRequested)
1201
1253
  return;
1202
- channel.notify(t('update-available', { current: update.current, latest: update.latest }), { color: 'warning', timeoutMs: 12000 });
1254
+ const key = update.isStandalone ? 'update-standalone-available' : 'update-available';
1255
+ channel.notify(t(key, { current: update.current, latest: update.latest }), { color: 'warning', timeoutMs: 12000 });
1203
1256
  });
1204
1257
  // If the surrounding tree goes down (reload, teardown), unmount the UI —
1205
1258
  // but flag it as teardown first so the settling waitUntilExit does not
@@ -1289,9 +1342,15 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
1289
1342
  };
1290
1343
  }
1291
1344
  catch (error) {
1292
- // No artifact (first run / cleared storage) or persistence not
1293
- // mounted: fall through to a fresh session, but stay loud in the log.
1294
- ctx.logger.warn(`dsh-tui: resume of "${requestedSessionId}" failed: ${error instanceof Error ? error.message : String(error)}`);
1345
+ // A launch-time --resume is an explicit request: silently substituting a
1346
+ // fresh session presents a cold conversation as the resumed one (the
1347
+ // "resume did nothing" failure mode — the warn below never reached a
1348
+ // terminal). Fail the boot loudly instead; the loader surfaces this to
1349
+ // stderr. The in-session /resume picker has its own error path.
1350
+ const reason = error instanceof Error ? error.message : String(error);
1351
+ throw new Error(`dsh-tui: cannot resume session "${requestedSessionId}": ${reason} — ` +
1352
+ 'the stored log is unreadable or corrupt; no fresh session was started instead. ' +
1353
+ 'Drop --resume to start fresh, or repair the session log first.');
1295
1354
  }
1296
1355
  }
1297
1356
  const sessionId = SessionId(randomUUID());
@@ -1370,12 +1429,45 @@ export function isExitResumable(deps) {
1370
1429
  return (deps.pendingCount > 0 ||
1371
1430
  agent.session.events.some(event => event.type === 'user/message' && event.data.source.kind === 'user'));
1372
1431
  }
1373
- /** Finish terminal I/O before handing control to a process-level exit action. */
1374
- async function finishExit(ctx, instance, fullscreen, notice, stderrNotice, done) {
1432
+ /**
1433
+ * Finish terminal I/O before handing control to a process-level exit action.
1434
+ * Exported for scripts/verify-shutdown-fallback.
1435
+ */
1436
+ export async function finishExit(ctx, instance, fullscreen, notice, stderrNotice, done) {
1375
1437
  try {
1376
- const runtime = readInkShutdownState(instances.get(process.stdout));
1377
- if (runtime === undefined && instance !== undefined) {
1438
+ // Resolve the Ink runtime twice: the instances map is keyed by stdout
1439
+ // identity, so a replaced/overridden stdout misses it; the render()
1440
+ // handle is the caller's own instance and always matches (issue #522 —
1441
+ // a missed lookup skipped detachForShutdown, leaving the stdin pump,
1442
+ // TTY handlers and querier alive so the self-heal probe re-wrote
1443
+ // ENABLE_MOUSE_TRACKING after DISABLE_MOUSE_TRACKING had been sent).
1444
+ const fromMap = readInkShutdownState(instances.get(process.stdout));
1445
+ const fromHandle = instance === undefined ? undefined : readInkShutdownState(instance);
1446
+ // A handle that exposes neither detach hook is not an Ink runtime we can
1447
+ // latch (e.g. the fake render handles in shutdown regressions) — treat it
1448
+ // as a lookup miss so the full-unmount fallback below can still run.
1449
+ const runtime = fromMap ?? (fromHandle?.detachForShutdown === undefined && fromHandle?.detachStdinForHandoff === undefined
1450
+ ? undefined
1451
+ : fromHandle);
1452
+ if (runtime === undefined) {
1378
1453
  ctx.logger.debug('dsh-tui: Ink runtime unavailable during shutdown; using generic terminal cleanup');
1454
+ if (instance !== undefined) {
1455
+ ctx.logger.debug('dsh-tui: Ink shutdown using full unmount as the terminal-restore fallback');
1456
+ // Lookup-miss (custom stdout embedders / detach-less handles): the
1457
+ // registry cannot hand us the detach hooks, so run the full Ink
1458
+ // unmount first. It restores raw mode, alt screen and listeners
1459
+ // synchronously before the notice below is written — the process
1460
+ // must never hand a broken terminal back to the shell.
1461
+ try {
1462
+ instance.unmount();
1463
+ }
1464
+ catch {
1465
+ ctx.logger.debug('dsh-tui: Ink shutdown unmount fallback failed; continuing with generic terminal cleanup');
1466
+ }
1467
+ }
1468
+ }
1469
+ else if (fromMap === undefined) {
1470
+ ctx.logger.debug('dsh-tui: Ink runtime resolved from the render handle (instances map missed); detaching');
1379
1471
  }
1380
1472
  const cursor = fullscreen ? '' : cursorMoveToFrameEnd(runtime);
1381
1473
  try {
@@ -1404,6 +1496,16 @@ async function finishExit(ctx, instance, fullscreen, notice, stderrNotice, done)
1404
1496
  ].join('');
1405
1497
  const suffix = notice === undefined ? '' : `${notice}\n`;
1406
1498
  await writeStream(process.stdout, `${cleanup}\r\n${suffix}`);
1499
+ // Re-drain AFTER the cleanup sequences have landed (#507): terminal
1500
+ // replies and mouse packets already in flight when the exit started
1501
+ // keep arriving while cleanup is being written — the detach-time drain
1502
+ // cannot see them. Unconsumed at process exit they land in the shell's
1503
+ // input queue (DECRPM/DA1/XTVERSION garbage pasted into the prompt).
1504
+ // 150ms settle covers reply RTT on slow links (ssh/ghostty is #522's
1505
+ // environment; 50ms proved too tight there) while staying well inside
1506
+ // the exit window the user already waits through.
1507
+ await new Promise(resolve => setTimeout(resolve, 150));
1508
+ runtime?.drainStdin?.();
1407
1509
  if (stderrNotice !== undefined) {
1408
1510
  await writeStream(process.stderr, `\n${stderrNotice}\n`);
1409
1511
  }
@@ -1421,6 +1523,8 @@ function readInkShutdownState(value) {
1421
1523
  return undefined;
1422
1524
  if (candidate.detachStdinForHandoff !== undefined && typeof candidate.detachStdinForHandoff !== 'function')
1423
1525
  return undefined;
1526
+ if (candidate.drainStdin !== undefined && typeof candidate.drainStdin !== 'function')
1527
+ return undefined;
1424
1528
  if (candidate.frontFrame !== undefined && !isFrameState(candidate.frontFrame))
1425
1529
  return undefined;
1426
1530
  if (candidate.displayCursor !== undefined && candidate.displayCursor !== null && !isCursorState(candidate.displayCursor))
@@ -129,5 +129,6 @@ export declare class TuiSettingsSectionsRuntime extends Service {
129
129
  subscribe(listener: () => void): () => void;
130
130
  }
131
131
  export declare function getHostSettingsSections(runtime: TuiSettingsSectionsRuntime | undefined): TuiSettingsSectionsHost | undefined;
132
+ export declare function getLocalSettingsSectionsHost(): TuiSettingsSectionsHost;
132
133
  export default TuiSettingsSectionsRuntime;
133
134
  //# sourceMappingURL=settings-sections.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings-sections.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/settings-sections.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings-sections.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/settings-sections.ts"],"names":[],"mappings":"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"}
@@ -82,7 +82,7 @@ function settingsSectionStateFor(runtime) {
82
82
  return state;
83
83
  }
84
84
  function registerSection(runtime, section, owner) {
85
- const state = settingsSectionStateFor(runtime);
85
+ const state = isSectionState(runtime) ? runtime : settingsSectionStateFor(runtime);
86
86
  const ns = section.ns.trim();
87
87
  if (!/^[a-z][a-z0-9_-]*$/u.test(ns))
88
88
  throw new TypeError(`invalid TUI settings-section namespace: ${section.ns}`);
@@ -141,7 +141,7 @@ function registerSection(runtime, section, owner) {
141
141
  };
142
142
  }
143
143
  function subscribeSections(runtime, listener, owner) {
144
- const state = settingsSectionStateFor(runtime);
144
+ const state = isSectionState(runtime) ? runtime : settingsSectionStateFor(runtime);
145
145
  const entry = { owner, listener };
146
146
  state.listeners.add(entry);
147
147
  return () => {
@@ -165,4 +165,37 @@ export function getHostSettingsSections(runtime) {
165
165
  return undefined;
166
166
  }
167
167
  }
168
+ function isSectionState(value) {
169
+ return value instanceof Map === false && 'sections' in value && 'listeners' in value;
170
+ }
171
+ /**
172
+ * In-package fallback registry: some real compositions dispose the
173
+ * `dsh-tui-settings-sections` service row right after it loads (the whole
174
+ * dsh-tui-* host-seam insert list is affected — see the issue-#183 skew
175
+ * family), leaving `ctx.get('tuiSettingsSections')` permanently undefined.
176
+ * The TUI's own section must not depend on that: plugin.ts registers into —
177
+ * and channel.ts reads from — this local host whenever the composition
178
+ * service is unavailable. Third-party sections still require the service
179
+ * row; the local host only carries the TUI's own section.
180
+ */
181
+ const localSectionsState = {
182
+ sections: new Map(),
183
+ owners: new Map(),
184
+ listeners: new Set(),
185
+ host: undefined,
186
+ };
187
+ export function getLocalSettingsSectionsHost() {
188
+ localSectionsState.host ??= Object.freeze({
189
+ register(section) {
190
+ return registerSection(localSectionsState, section);
191
+ },
192
+ list() {
193
+ return [...localSectionsState.sections.values()];
194
+ },
195
+ subscribe(listener) {
196
+ return subscribeSections(localSectionsState, listener);
197
+ },
198
+ });
199
+ return localSectionsState.host;
200
+ }
168
201
  export default TuiSettingsSectionsRuntime;
@@ -16,9 +16,10 @@ export declare function appendHistory(text: string): void;
16
16
  */
17
17
  export declare function loadHistory(): HistoryEntry[];
18
18
  /**
19
- * Stable id for a history entry (dedupes React keys across identical texts).
19
+ * Stable id for a history entry (keeps React keys distinct across identical texts).
20
20
  * @param entry - The history entry to hash.
21
- * @returns A 12-char hex id derived from the entry text.
21
+ * @param index - Position in the currently rendered result list.
22
+ * @returns A 12-char hex id derived from the entry text, timestamp, and index.
22
23
  */
23
- export declare function historyEntryId(entry: HistoryEntry): string;
24
+ export declare function historyEntryId(entry: HistoryEntry, index?: number): string;
24
25
  //# sourceMappingURL=history.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/history.ts"],"names":[],"mappings":"AAQA,yCAAyC;AACzC,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,yBAAyB;IACzB,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AA0BD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAuBhD;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,YAAY,EAAE,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAE1D"}
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/history.ts"],"names":[],"mappings":"AASA,yCAAyC;AACzC,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,yBAAyB;IACzB,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAmED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAwBhD;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,YAAY,EAAE,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,SAAI,GAAG,MAAM,CAErE"}
@@ -1,10 +1,56 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { DATA_DIR } from './utils/paths.js';
5
5
  const HISTORY_DIR = DATA_DIR;
6
6
  const HISTORY_FILE = join(HISTORY_DIR, 'history.jsonl');
7
+ const HISTORY_LOCK = `${HISTORY_FILE}.lock`;
7
8
  const HISTORY_LIMIT = 200;
9
+ const LOCK_RETRY_LIMIT = 500;
10
+ const LOCK_RETRY_DELAY_MS = 5;
11
+ const STALE_LOCK_MS = 30_000;
12
+ function sleepSync(ms) {
13
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
14
+ }
15
+ function removeStaleHistoryLock() {
16
+ try {
17
+ const ageMs = Date.now() - statSync(HISTORY_LOCK).mtimeMs;
18
+ if (ageMs < STALE_LOCK_MS)
19
+ return false;
20
+ rmSync(HISTORY_LOCK, { recursive: true, force: true });
21
+ return true;
22
+ }
23
+ catch (error) {
24
+ const code = error.code;
25
+ if (code === 'ENOENT')
26
+ return true;
27
+ throw error;
28
+ }
29
+ }
30
+ function withHistoryLock(write) {
31
+ mkdirSync(HISTORY_DIR, { recursive: true });
32
+ for (let attempt = 0; attempt < LOCK_RETRY_LIMIT; attempt += 1) {
33
+ try {
34
+ mkdirSync(HISTORY_LOCK);
35
+ try {
36
+ write();
37
+ }
38
+ finally {
39
+ rmSync(HISTORY_LOCK, { recursive: true, force: true });
40
+ }
41
+ return;
42
+ }
43
+ catch (error) {
44
+ const code = error.code;
45
+ if (code !== 'EEXIST')
46
+ throw error;
47
+ if (removeStaleHistoryLock())
48
+ continue;
49
+ sleepSync(LOCK_RETRY_DELAY_MS + Math.floor(Math.random() * 5));
50
+ }
51
+ }
52
+ throw new Error('history lock busy');
53
+ }
8
54
  function loadRaw() {
9
55
  if (!existsSync(HISTORY_FILE))
10
56
  return [];
@@ -39,20 +85,21 @@ export function appendHistory(text) {
39
85
  const trimmed = text.trim();
40
86
  if (!trimmed)
41
87
  return;
42
- const entries = loadRaw();
43
- // Skip consecutive duplicates (CC behavior: repeated submits of the same
44
- // command only advance the existing entry's timestamp).
45
- const last = entries[entries.length - 1];
46
- if (last && last.text === trimmed) {
47
- last.ts = Date.now();
48
- }
49
- else {
50
- entries.push({ text: trimmed, ts: Date.now() });
51
- }
52
- const sliced = entries.slice(-HISTORY_LIMIT);
53
88
  try {
54
- mkdirSync(HISTORY_DIR, { recursive: true });
55
- writeFileSync(HISTORY_FILE, sliced.map(e => JSON.stringify(e)).join('\n') + '\n', 'utf8');
89
+ withHistoryLock(() => {
90
+ const entries = loadRaw();
91
+ // Skip consecutive duplicates (CC behavior: repeated submits of the same
92
+ // command only advance the existing entry's timestamp).
93
+ const last = entries[entries.length - 1];
94
+ if (last && last.text === trimmed) {
95
+ last.ts = Date.now();
96
+ }
97
+ else {
98
+ entries.push({ text: trimmed, ts: Date.now() });
99
+ }
100
+ const sliced = entries.slice(-HISTORY_LIMIT);
101
+ writeFileSync(HISTORY_FILE, sliced.map(e => JSON.stringify(e)).join('\n') + '\n', 'utf8');
102
+ });
56
103
  }
57
104
  catch {
58
105
  // Best-effort persistence; history still works for the session.
@@ -66,10 +113,11 @@ export function loadHistory() {
66
113
  return loadRaw().reverse();
67
114
  }
68
115
  /**
69
- * Stable id for a history entry (dedupes React keys across identical texts).
116
+ * Stable id for a history entry (keeps React keys distinct across identical texts).
70
117
  * @param entry - The history entry to hash.
71
- * @returns A 12-char hex id derived from the entry text.
118
+ * @param index - Position in the currently rendered result list.
119
+ * @returns A 12-char hex id derived from the entry text, timestamp, and index.
72
120
  */
73
- export function historyEntryId(entry) {
74
- return createHash('sha1').update(entry.text).digest('hex').slice(0, 12);
121
+ export function historyEntryId(entry, index = 0) {
122
+ return createHash('sha1').update(`${entry.text}\0${entry.ts}\0${index}`).digest('hex').slice(0, 12);
75
123
  }