@deepseek-harness-tui/dsh-tui 0.8.4 → 0.8.6

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 (80) hide show
  1. package/lib/types/activityPrefs.d.ts +10 -0
  2. package/lib/types/activityPrefs.d.ts.map +1 -1
  3. package/lib/types/activityPrefs.js +17 -0
  4. package/lib/types/cc/figures.d.ts +7 -0
  5. package/lib/types/cc/figures.d.ts.map +1 -1
  6. package/lib/types/cc/figures.js +23 -0
  7. package/lib/types/cc/markdown.d.ts.map +1 -1
  8. package/lib/types/cc/markdown.js +47 -14
  9. package/lib/types/cc/syntaxTheme.d.ts +12 -0
  10. package/lib/types/cc/syntaxTheme.d.ts.map +1 -0
  11. package/lib/types/cc/syntaxTheme.js +76 -0
  12. package/lib/types/components/EffortChargeGlyph.d.ts +26 -0
  13. package/lib/types/components/EffortChargeGlyph.d.ts.map +1 -0
  14. package/lib/types/components/EffortChargeGlyph.js +70 -0
  15. package/lib/types/components/EffortInputBorder.d.ts +36 -0
  16. package/lib/types/components/EffortInputBorder.d.ts.map +1 -0
  17. package/lib/types/components/EffortInputBorder.js +73 -0
  18. package/lib/types/components/EffortTierBadge.d.ts +26 -0
  19. package/lib/types/components/EffortTierBadge.d.ts.map +1 -0
  20. package/lib/types/components/EffortTierBadge.js +97 -0
  21. package/lib/types/components/LoadedContextPanel.js +1 -1
  22. package/lib/types/components/MessageList.d.ts +4 -1
  23. package/lib/types/components/MessageList.d.ts.map +1 -1
  24. package/lib/types/components/MessageList.js +9 -11
  25. package/lib/types/components/PromptInput.d.ts.map +1 -1
  26. package/lib/types/components/PromptInput.js +8 -3
  27. package/lib/types/components/SplitDiffView.d.ts +4 -5
  28. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  29. package/lib/types/components/SplitDiffView.js +18 -65
  30. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  31. package/lib/types/components/StreamingMarkdown.js +10 -2
  32. package/lib/types/components/activityFrames.d.ts +7 -28
  33. package/lib/types/components/activityFrames.d.ts.map +1 -1
  34. package/lib/types/components/activityFrames.js +7 -94
  35. package/lib/types/components/design-system/ThemedBox.d.ts.map +1 -1
  36. package/lib/types/components/design-system/ThemedBox.js +4 -1
  37. package/lib/types/components/design-system/ThemedText.js +2 -2
  38. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +12 -6
  39. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  40. package/lib/types/components/messages/AssistantThinkingMessage.js +25 -9
  41. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +4 -1
  42. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  43. package/lib/types/components/messages/AssistantToolUseMessage.js +45 -17
  44. package/lib/types/components/messages/UserPromptMessage.d.ts +4 -6
  45. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  46. package/lib/types/components/messages/UserPromptMessage.js +5 -9
  47. package/lib/types/dsh-adapter/channel.d.ts +22 -0
  48. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  49. package/lib/types/dsh-adapter/channel.js +204 -19
  50. package/lib/types/dsh-adapter/index.d.ts +5 -0
  51. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  52. package/lib/types/dsh-adapter/index.js +19 -0
  53. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  54. package/lib/types/dsh-adapter/plugin.js +176 -4
  55. package/lib/types/dsh-adapter/settings-sections.d.ts +13 -0
  56. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  57. package/lib/types/dsh-adapter/settings-sections.js +31 -6
  58. package/lib/types/ink/ink.d.ts +9 -0
  59. package/lib/types/ink/ink.d.ts.map +1 -1
  60. package/lib/types/ink/ink.js +24 -1
  61. package/lib/types/screens/Chat.d.ts.map +1 -1
  62. package/lib/types/screens/Chat.js +35 -6
  63. package/lib/types/screens/Settings.d.ts.map +1 -1
  64. package/lib/types/screens/Settings.js +109 -50
  65. package/lib/types/screens/StatusLine.d.ts +8 -0
  66. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  67. package/lib/types/screens/StatusLine.js +62 -39
  68. package/lib/types/settings-sections.d.ts +1 -1
  69. package/lib/types/settings-sections.d.ts.map +1 -1
  70. package/lib/types/theme.d.ts +12 -0
  71. package/lib/types/theme.d.ts.map +1 -1
  72. package/lib/types/theme.js +51 -21
  73. package/lib/types/tips.js +2 -2
  74. package/lib/types/trajectory/effortIgnition.d.ts +66 -0
  75. package/lib/types/trajectory/effortIgnition.d.ts.map +1 -0
  76. package/lib/types/trajectory/effortIgnition.js +142 -0
  77. package/lib/types/tuiDisplayPrefs.d.ts +47 -0
  78. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -0
  79. package/lib/types/tuiDisplayPrefs.js +60 -0
  80. package/package.json +6 -3
@@ -24,7 +24,10 @@ import { homeDir, LEGACY_DATA_DIR } from '../utils/paths.js';
24
24
  import { extractMentions } from '../utils/mentions.js';
25
25
  import { t } from '../i18n.js';
26
26
  import { modeDisplayName, resolveSessionModes } from '../sessionModes.js';
27
+ import { normalizeStatusBar, normalizeToolBackground } from '../tuiDisplayPrefs.js';
27
28
  import { ActivityTracker } from 'dsh-working-activity/status';
29
+ import { featureOn } from 'dsh-working-activity/config';
30
+ import { readActivityConfig } from '../activityPrefs.js';
28
31
  import { attachSessionToWorkspace } from './workspace.js';
29
32
  import { createLocalWorkspaceRuntime, getHostWorkspaceRuntime } from './workspaces.js';
30
33
  import { getHostCommandTrees } from './command-trees.js';
@@ -700,6 +703,7 @@ export function createChannel(ctx, initialAgent, options) {
700
703
  return;
701
704
  try {
702
705
  const info = await llmRuntime.resolveModelInfo(state.provider, state.model);
706
+ state.effortLevels = (info.reasoning?.efforts ?? []).map(level => level.id);
703
707
  if (!info.reasoning?.efforts.some(effort => effort.id === preferredEffort))
704
708
  return;
705
709
  selection.current = {
@@ -713,6 +717,31 @@ export function createChannel(ctx, initialAgent, options) {
713
717
  // provider default in effect.
714
718
  }
715
719
  };
720
+ /** Best-effort refresh of the live route's effort-level table for
721
+ * top-tier-triggered UI (effort ignition): fire-and-forget on route
722
+ * changes (bind/model switch/resume); the /effort paths refresh it
723
+ * authoritatively via resolveEfforts. */
724
+ let effortLevelsGeneration = 0;
725
+ const refreshEffortLevels = () => {
726
+ if (llmRuntime === undefined || typeof llmRuntime.resolveModelInfo !== 'function')
727
+ return;
728
+ // 代际保护:快速连续切路由时并发的 resolveModelInfo 可能乱序返回,
729
+ // 只有最新一代的解析才允许落表;落表后 emit 让 useSyncExternalStore
730
+ // 消费者立刻可见(否则要等下一次无关 emit)。
731
+ const generation = ++effortLevelsGeneration;
732
+ void llmRuntime
733
+ .resolveModelInfo(state.provider, state.model)
734
+ .then(info => {
735
+ if (generation !== effortLevelsGeneration)
736
+ return;
737
+ state.effortLevels = (info.reasoning?.efforts ?? []).map(level => level.id);
738
+ state.emit();
739
+ })
740
+ .catch(() => {
741
+ // Route metadata resolution is best-effort; a failure keeps the
742
+ // previous table until the next /effort interaction clears it.
743
+ });
744
+ };
716
745
  /** Resolve the live route's effort levels + adapter default through the
717
746
  * llm runtime; 'unavailable' when the service is unmounted, 'error' when
718
747
  * resolution throws (notified here). */
@@ -721,6 +750,7 @@ export function createChannel(ctx, initialAgent, options) {
721
750
  return 'unavailable';
722
751
  try {
723
752
  const info = await llmRuntime.resolveModelInfo(state.provider, state.model);
753
+ state.effortLevels = (info.reasoning?.efforts ?? []).map(level => level.id);
724
754
  return {
725
755
  efforts: info.reasoning?.efforts ?? [],
726
756
  defaultEffort: info.reasoning?.defaultEffort,
@@ -938,6 +968,7 @@ export function createChannel(ctx, initialAgent, options) {
938
968
  await applyMode(sessionModes[(index + 1) % sessionModes.length]);
939
969
  };
940
970
  const state = {
971
+ effortLevels: undefined,
941
972
  version: 0,
942
973
  rows: [],
943
974
  status: 'starting',
@@ -968,6 +999,8 @@ export function createChannel(ctx, initialAgent, options) {
968
999
  activityFrames: options.activityFrames,
969
1000
  diffLayout: options.diffLayout ?? 'auto',
970
1001
  thinkingFold: options.thinkingFold ?? 'preview',
1002
+ toolBackground: normalizeToolBackground(options.toolBackground),
1003
+ statusBar: normalizeStatusBar(options.statusBar),
971
1004
  activityEnabled: options.activity !== false,
972
1005
  contextBarEnabled: options.contextBar !== false,
973
1006
  agentPreset: options.agentPreset,
@@ -1505,6 +1538,11 @@ export function createChannel(ctx, initialAgent, options) {
1505
1538
  state.lastUsage = undefined;
1506
1539
  state.workingActivity = undefined;
1507
1540
  state.contextWindow = undefined;
1541
+ // Route changed: a stale tier table would let top-tier UI fire on the
1542
+ // wrong level (or never fire on the real one); clear and re-resolve.
1543
+ state.effortLevels = undefined;
1544
+ state.reasoningEffort = undefined;
1545
+ refreshEffortLevels();
1508
1546
  state.contextSegments = {
1509
1547
  system: 0,
1510
1548
  prompt: 0,
@@ -1613,10 +1651,16 @@ export function createChannel(ctx, initialAgent, options) {
1613
1651
  streaming = undefined;
1614
1652
  reasoning = undefined;
1615
1653
  // Stale sealed/thinking bookkeeping belongs to the OLD agent's rows;
1616
- // keep it out of the next turn's settle logs and revive cache.
1654
+ // keep it out of the next turn's settle logs and revive cache. Event
1655
+ // sequence numbers restart in the fresh session, so its dedupe ledgers
1656
+ // must not retain the old session's sequence ids.
1617
1657
  sealedReasoning.length = 0;
1618
1658
  lastReasoningRow = undefined;
1619
1659
  toolCards.clear();
1660
+ handledAssistantMessages.clear();
1661
+ handledAssistantChunks.clear();
1662
+ assistantRowsByStep.clear();
1663
+ lastTextDelta.clear();
1620
1664
  nextRowId = 0;
1621
1665
  state.rows.length = 0;
1622
1666
  // Goal/todo/title are session-scoped; the replay re-derives them for
@@ -1644,7 +1688,13 @@ export function createChannel(ctx, initialAgent, options) {
1644
1688
  state.tpsSamples = [];
1645
1689
  state.lastUsage = undefined;
1646
1690
  state.workingActivity = undefined;
1691
+ state.loadedContext = undefined;
1647
1692
  state.contextWindow = undefined;
1693
+ // Route changed: a stale tier table would let top-tier UI fire on the
1694
+ // wrong level (or never fire on the real one); clear and re-resolve.
1695
+ state.effortLevels = undefined;
1696
+ state.reasoningEffort = undefined;
1697
+ refreshEffortLevels();
1648
1698
  state.contextSegments = {
1649
1699
  system: 0,
1650
1700
  prompt: 0,
@@ -1822,6 +1872,11 @@ export function createChannel(ctx, initialAgent, options) {
1822
1872
  state.lastUsage = undefined;
1823
1873
  state.workingActivity = undefined;
1824
1874
  state.contextWindow = undefined;
1875
+ // Route changed: a stale tier table would let top-tier UI fire on the
1876
+ // wrong level (or never fire on the real one); clear and re-resolve.
1877
+ state.effortLevels = undefined;
1878
+ state.reasoningEffort = undefined;
1879
+ refreshEffortLevels();
1825
1880
  state.contextSegments = {
1826
1881
  system: 0,
1827
1882
  prompt: 0,
@@ -1837,6 +1892,8 @@ export function createChannel(ctx, initialAgent, options) {
1837
1892
  agent = handle.agent;
1838
1893
  currentHandle = handle;
1839
1894
  bindAgent();
1895
+ // Model-switch quip rides the fresh tracker (pi parity).
1896
+ activityTracker.onModelSwitch(model);
1840
1897
  refreshCommandList();
1841
1898
  void refreshLoadedContext();
1842
1899
  void refreshSkillCommands();
@@ -1913,6 +1970,21 @@ export function createChannel(ctx, initialAgent, options) {
1913
1970
  state.thinkingFold = mode;
1914
1971
  state.emit();
1915
1972
  },
1973
+ setToolBackground(background) {
1974
+ const normalized = normalizeToolBackground(background);
1975
+ if (normalized === state.toolBackground)
1976
+ return;
1977
+ state.toolBackground = normalized;
1978
+ state.emit();
1979
+ },
1980
+ setStatusBar(config) {
1981
+ const next = normalizeStatusBar({ ...state.statusBar, ...config });
1982
+ const changed = Object.keys(next).some(key => next[key] !== state.statusBar[key]);
1983
+ if (!changed)
1984
+ return;
1985
+ state.statusBar = next;
1986
+ state.emit();
1987
+ },
1916
1988
  setActivityFrames(name) {
1917
1989
  if (!isPresetName(name)) {
1918
1990
  state.notify(t('unknown-activity-preset', { name }), { color: 'error' });
@@ -2318,6 +2390,9 @@ export function createChannel(ctx, initialAgent, options) {
2318
2390
  .compactNow(agent, signal)
2319
2391
  .then((result) => {
2320
2392
  state.notify(result ? t('compact-done') : t('compact-nothing'));
2393
+ // Compaction quip rides the next thinking rotation (pi parity).
2394
+ if (result)
2395
+ activityTracker.onCompact('done');
2321
2396
  })
2322
2397
  .catch((error) => {
2323
2398
  state.notify(t('compact-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error', timeoutMs: 8000 });
@@ -2983,6 +3058,39 @@ ${output}
2983
3058
  let tpsStep;
2984
3059
  /** Tool cards by callId, so tool/result can settle the running card. */
2985
3060
  const toolCards = new Map();
3061
+ /**
3062
+ * Session events are delivered live and can also be replayed around a
3063
+ * reconnect. A repeated sealed message must not create a second assistant
3064
+ * row for the same durable sequence number.
3065
+ */
3066
+ const handledAssistantMessages = new Set();
3067
+ const handledAssistantChunks = new Set();
3068
+ const assistantRowsByStep = new Map();
3069
+ const lastTextDelta = new Map();
3070
+ const stepKey = (turn, step) => `${turn}:${step}`;
3071
+ /** Append a stream delta idempotently. Providers normally send a pure
3072
+ * delta, but reconnect/proxy paths can resend a cumulative prefix or a
3073
+ * delta whose beginning overlaps the previous tail. Merge the overlap
3074
+ * instead of blindly concatenating it into the visible transcript. */
3075
+ const appendTextDelta = (row, delta) => {
3076
+ if (delta === '')
3077
+ return;
3078
+ if (lastTextDelta.get(row) === delta)
3079
+ return;
3080
+ lastTextDelta.set(row, delta);
3081
+ if (delta.startsWith(row.text)) {
3082
+ row.text = delta;
3083
+ return;
3084
+ }
3085
+ const maxOverlap = Math.min(row.text.length, delta.length, 4096);
3086
+ for (let size = maxOverlap; size > 0; size--) {
3087
+ if (row.text.endsWith(delta.slice(0, size))) {
3088
+ row.text += delta.slice(size);
3089
+ return;
3090
+ }
3091
+ }
3092
+ row.text += delta;
3093
+ };
2986
3094
  /** The host-plane tools registry (dsh-tools). Resolved once; absent in
2987
3095
  * bare embedders — every presenter call soft-fails to undefined and the
2988
3096
  * card falls back to raw text. */
@@ -3034,11 +3142,22 @@ ${output}
3034
3142
  */
3035
3143
  const firstTextOf = (content) => (content ?? []).find(block => block.type === 'text')?.text.trim() ?? '';
3036
3144
  const ensureStreaming = (seq) => {
3037
- if (streaming === undefined) {
3038
- streaming = { id: nextRowId, kind: 'assistant', text: '', streaming: true, ...seq !== undefined ? { seq } : {} };
3039
- nextRowId += 1;
3040
- state.rows.push(streaming);
3145
+ if (streaming !== undefined)
3146
+ return streaming;
3147
+ // A reconnect can replay the first delta after the sealed message was
3148
+ // already observed. Reuse that durable row instead of opening a second
3149
+ // assistant bubble for the same event sequence.
3150
+ const existing = seq === undefined
3151
+ ? undefined
3152
+ : [...state.rows].reverse().find(row => row.kind === 'assistant' && row.seq === seq);
3153
+ if (existing !== undefined) {
3154
+ existing.streaming = true;
3155
+ streaming = existing;
3156
+ return existing;
3041
3157
  }
3158
+ streaming = { id: nextRowId, kind: 'assistant', text: '', streaming: true, ...seq !== undefined ? { seq } : {} };
3159
+ nextRowId += 1;
3160
+ state.rows.push(streaming);
3042
3161
  return streaming;
3043
3162
  };
3044
3163
  /** Latest reasoning row keyed by its (turn, step) — lets a resumed
@@ -3169,6 +3288,13 @@ ${output}
3169
3288
  * using it would rebuild a second thinking block per step. */
3170
3289
  let replaying = false;
3171
3290
  const replayEvents = (events) => {
3291
+ // Event sequence numbers restart with a replacement session; reset the
3292
+ // idempotency ledger before replay so an old session cannot suppress a
3293
+ // legitimate message in the new transcript.
3294
+ handledAssistantMessages.clear();
3295
+ handledAssistantChunks.clear();
3296
+ assistantRowsByStep.clear();
3297
+ lastTextDelta.clear();
3172
3298
  replaying = true;
3173
3299
  try {
3174
3300
  for (const event of prepareReplayEvents(events))
@@ -3256,6 +3382,9 @@ ${output}
3256
3382
  break;
3257
3383
  }
3258
3384
  case 'assistant/chunk': {
3385
+ if (handledAssistantChunks.has(event.seq))
3386
+ break;
3387
+ handledAssistantChunks.add(event.seq);
3259
3388
  const chunk = event.data.chunk;
3260
3389
  if (chunk.type === 'text-delta') {
3261
3390
  if (chunk.text) {
@@ -3263,13 +3392,20 @@ ${output}
3263
3392
  // window (see foldLiveReasoning) — before this text grows the
3264
3393
  // transcript and pushes the block into scrollback.
3265
3394
  foldLiveReasoning('first text token');
3266
- ensureStreaming(event.seq).text += chunk.text;
3267
- state.responseChars += chunk.text.length;
3395
+ const key = stepKey(event.data.turn, event.data.step);
3396
+ const row = assistantRowsByStep.get(key) ?? ensureStreaming(event.seq);
3397
+ assistantRowsByStep.set(key, row);
3398
+ streaming = row;
3399
+ row.streaming = true;
3400
+ const before = row.text.length;
3401
+ appendTextDelta(row, chunk.text);
3402
+ state.responseChars += Math.max(0, row.text.length - before);
3268
3403
  }
3269
3404
  }
3270
3405
  else if (chunk.type === 'reasoning-delta') {
3271
3406
  if (chunk.text) {
3272
- ensureReasoning(event.seq, event.data.turn, event.data.step).text += chunk.text;
3407
+ const row = ensureReasoning(event.seq, event.data.turn, event.data.step);
3408
+ appendTextDelta(row, chunk.text);
3273
3409
  }
3274
3410
  }
3275
3411
  const step = tpsStep;
@@ -3290,6 +3426,9 @@ ${output}
3290
3426
  break;
3291
3427
  }
3292
3428
  case 'assistant/message': {
3429
+ if (handledAssistantMessages.has(event.seq))
3430
+ break;
3431
+ handledAssistantMessages.add(event.seq);
3293
3432
  const text = textOf(event.data.message.content);
3294
3433
  // Replay without chunk deltas (prepareReplayEvents drops settled
3295
3434
  // ones): rebuild the reasoning row from the sealed message's
@@ -3319,8 +3458,22 @@ ${output}
3319
3458
  // anyway leaves an empty `●` bullet in the transcript. A pre-existing
3320
3459
  // streaming row always has text (ensureStreaming is only reached on
3321
3460
  // non-empty text deltas), so only create one when text arrives.
3322
- const row = streaming ?? (text ? ensureStreaming(event.seq) : undefined);
3461
+ // Key the step→row ledger only when the event carries a durable
3462
+ // turn/step; a message without them must never collide onto a
3463
+ // previous step's row (a bare `undefined:undefined` key would make
3464
+ // every turn/step-less message reuse the FIRST one's assistant row).
3465
+ const msgTurn = event.data.turn;
3466
+ const msgStep = event.data.step;
3467
+ const msgKey = msgTurn !== undefined && msgStep !== undefined
3468
+ ? stepKey(msgTurn, msgStep)
3469
+ : undefined;
3470
+ const row = (msgKey !== undefined ? assistantRowsByStep.get(msgKey) : undefined) ?? streaming ??
3471
+ (text
3472
+ ? ([...state.rows].reverse().find(candidate => candidate.kind === 'assistant' && candidate.seq === event.seq) ?? ensureStreaming(event.seq))
3473
+ : undefined);
3323
3474
  if (row !== undefined) {
3475
+ if (msgKey !== undefined)
3476
+ assistantRowsByStep.set(msgKey, row);
3324
3477
  row.time = event.time;
3325
3478
  if (text)
3326
3479
  row.text = text;
@@ -3616,11 +3769,36 @@ ${output}
3616
3769
  // Live subscription list and activity timer, rebound to every replacement
3617
3770
  // agent so no status from the previous session can leak across a swap.
3618
3771
  let agentSubscriptions = [];
3619
- let activityTracker = new ActivityTracker({
3620
- phrases: true,
3621
- detailLimit: 40,
3622
- showIdle: false,
3623
- });
3772
+ /** Tracker knobs + custom actions from the persisted pi-style config
3773
+ * (`~/.dsh-tui/working-activity.json`); a missing file means lively
3774
+ * defaults (all eggs on). */
3775
+ const activityPrefsSnapshot = () => {
3776
+ const cfg = readActivityConfig();
3777
+ if (cfg === undefined) {
3778
+ return { config: { phrases: true, detailLimit: 40, showIdle: false } };
3779
+ }
3780
+ return {
3781
+ config: {
3782
+ phrases: featureOn(cfg, 'phrases'),
3783
+ detailLimit: 40,
3784
+ showIdle: false,
3785
+ features: {
3786
+ rareEggs: featureOn(cfg, 'rareEggs'),
3787
+ weekend: featureOn(cfg, 'weekend'),
3788
+ holidays: featureOn(cfg, 'holidays'),
3789
+ nightPhrases: featureOn(cfg, 'nightPhrases'),
3790
+ },
3791
+ customPhrases: cfg.customPhrases,
3792
+ showTokPerSec: cfg.showTokPerSec,
3793
+ workRemindAt: cfg.workRemindAt,
3794
+ },
3795
+ customActions: cfg.customActions,
3796
+ };
3797
+ };
3798
+ let activityTracker = (() => {
3799
+ const prefs = activityPrefsSnapshot();
3800
+ return new ActivityTracker(prefs.config, Date.now, prefs.customActions);
3801
+ })();
3624
3802
  let activityTickTimer;
3625
3803
  const stopActivityTick = () => {
3626
3804
  if (activityTickTimer === undefined)
@@ -3642,11 +3820,8 @@ ${output}
3642
3820
  for (const dispose of agentSubscriptions)
3643
3821
  dispose();
3644
3822
  stopActivityTick();
3645
- activityTracker = new ActivityTracker({
3646
- phrases: true,
3647
- detailLimit: 40,
3648
- showIdle: false,
3649
- });
3823
+ const prefs = activityPrefsSnapshot();
3824
+ activityTracker = new ActivityTracker(prefs.config, Date.now, prefs.customActions);
3650
3825
  activityTracker.onAgentStatus(agent.status);
3651
3826
  renderWorkingActivity();
3652
3827
  activityTickTimer = setInterval(() => {
@@ -3742,6 +3917,14 @@ ${output}
3742
3917
  // publish never throws into this arm.
3743
3918
  messageObserver?.publish(session, event);
3744
3919
  activityTracker.onSessionEvent(event);
3920
+ // Interrupt quip: an aborted/interrupted turn ends the round; the
3921
+ // comeback copy shows on the next thinking rotation (pi parity).
3922
+ if (event.type === 'turn/end') {
3923
+ const reason = event.data.reason;
3924
+ if (reason?.kind === 'aborted' || reason?.kind === 'interrupted') {
3925
+ activityTracker.onInterrupted();
3926
+ }
3927
+ }
3745
3928
  renderWorkingActivity();
3746
3929
  // Mode-affecting atoms fold into the Shift+Tab mode indicator the
3747
3930
  // moment they land (whether appended by cycleMode or by hand).
@@ -3809,6 +3992,8 @@ ${output}
3809
3992
  // only show a branch for sessions this install actually used — which
3810
3993
  // is exactly what the column claims.
3811
3994
  noteBranch(agent.session.id, branch);
3995
+ // Feed the working line so git tools can show ` · git <branch>`.
3996
+ activityTracker.onGitBranch(branch);
3812
3997
  state.emit();
3813
3998
  }
3814
3999
  })
@@ -9,6 +9,7 @@
9
9
  import type { Context } from '@deepseek-ai/cordis';
10
10
  import Schema from '@deepseek-ai/schemastery';
11
11
  import type { SessionModeSpec } from '../sessionModes.js';
12
+ import { type StatusBarConfig, type ToolBackground } from '../tuiDisplayPrefs.js';
12
13
  export declare const name = "dsh-tui";
13
14
  export declare const inject: string[];
14
15
  /**
@@ -70,6 +71,10 @@ export interface Config {
70
71
  * preview and folds each step when it settles; `full` keeps thinking
71
72
  * expanded until the whole turn ends. Editable live from `/settings`. */
72
73
  thinkingFold?: 'preview' | 'full';
74
+ /** Tool-card background strength; defaults to no added background. */
75
+ toolBackground?: ToolBackground;
76
+ /** Status-footer field visibility and compact presentation preferences. */
77
+ statusBar?: Partial<StatusBarConfig>;
73
78
  /** Shift+Tab session-mode cycle (array order IS the cycle order; index 0
74
79
  * is the unmarked base mode). Each entry bundles any subset of the
75
80
  * `plan`/`sandbox`/`approval` atoms; absent → the built-in
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,eAAO,MAAM,IAAI,YAAY,CAAA;AAU7B,eAAO,MAAM,MAAM,UAAa,CAAA;AAEhC;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;yEAKqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;gDAC4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;gFAE4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;oCACgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;sBAIkB;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;4BAGwB;IACxB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;wDAEoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;kFAC8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;gCAG4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;6DAEyD;IACzD,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC;;8EAE0E;IAC1E,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IACjC;;;yDAGqD;IACrD,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;CAC1B;AAED,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,CA4BhC,CAAA;AAEF;;;;;;GAMG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWvE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAsB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAErG,eAAO,MAAM,IAAI,YAAY,CAAA;AAU7B,eAAO,MAAM,MAAM,UAAa,CAAA;AAEhC;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;yEAKqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;gDAC4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;gFAE4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;oCACgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;sBAIkB;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;4BAGwB;IACxB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;wDAEoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;kFAC8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;gCAG4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;6DAEyD;IACzD,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC;;8EAE0E;IAC1E,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IACjC,sEAAsE;IACtE,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;IACpC;;;yDAGqD;IACrD,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;CAC1B;AAED,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,CA8ChC,CAAA;AAEF;;;;;;GAMG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWvE"}
@@ -1,4 +1,5 @@
1
1
  import Schema from '@deepseek-ai/schemastery';
2
+ import { DEFAULT_STATUS_BAR } from '../tuiDisplayPrefs.js';
2
3
  export const name = 'dsh-tui';
3
4
  // `tuiWorkspaces` must stay OUT of this code-level inject (issue #183): the
4
5
  // dsh CLI resolves the bundle's cordis.patch.yml from the FIRST copy of this
@@ -29,6 +30,24 @@ export const Config = Schema.object({
29
30
  preset: Schema.string().required(false),
30
31
  diffLayout: Schema.union(['auto', 'split', 'unified']).default('auto'),
31
32
  thinkingFold: Schema.union(['preview', 'full']).default('preview'),
33
+ toolBackground: Schema.union(['none', 'subtle', 'strong']).default('none'),
34
+ statusBar: Schema.object({
35
+ compact: Schema.boolean().default(DEFAULT_STATUS_BAR.compact),
36
+ model: Schema.boolean().default(DEFAULT_STATUS_BAR.model),
37
+ thinking: Schema.boolean().default(DEFAULT_STATUS_BAR.thinking),
38
+ cwd: Schema.boolean().default(DEFAULT_STATUS_BAR.cwd),
39
+ contextUsage: Schema.boolean().default(DEFAULT_STATUS_BAR.contextUsage),
40
+ cache: Schema.boolean().default(DEFAULT_STATUS_BAR.cache),
41
+ tokens: Schema.boolean().default(DEFAULT_STATUS_BAR.tokens),
42
+ tps: Schema.boolean().default(DEFAULT_STATUS_BAR.tps),
43
+ gitBranch: Schema.boolean().default(DEFAULT_STATUS_BAR.gitBranch),
44
+ sessionTitle: Schema.boolean().default(DEFAULT_STATUS_BAR.sessionTitle),
45
+ mode: Schema.boolean().default(DEFAULT_STATUS_BAR.mode),
46
+ contextBar: Schema.boolean().default(DEFAULT_STATUS_BAR.contextBar),
47
+ activity: Schema.boolean().default(DEFAULT_STATUS_BAR.activity),
48
+ trajectory: Schema.boolean().default(DEFAULT_STATUS_BAR.trajectory),
49
+ shortcutHint: Schema.boolean().default(DEFAULT_STATUS_BAR.shortcutHint),
50
+ }).default({ ...DEFAULT_STATUS_BAR }),
32
51
  modes: Schema.array(Schema.object({
33
52
  id: Schema.string(),
34
53
  label: Schema.string().required(false),
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,wBAAwB,CAAA;AAGhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAmCnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAonBvE;AA6GD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG;IACjF,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB,CAcA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,KAAK,GAAG,SAAS,CAAA;IAC5B,YAAY,EAAE,KAAK,CAAA;CACpB,GAAG,OAAO,CAQV"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,wBAAwB,CAAA;AAGhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAoCnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsyBvE;AA6GD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG;IACjF,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB,CAcA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,KAAK,GAAG,SAAS,CAAA;IAC5B,YAAY,EAAE,KAAK,CAAA;CACpB,GAAG,OAAO,CAQV"}