@agi-fans/dsh-tui 0.23.0 → 0.24.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 (52) hide show
  1. package/lib/CHANGELOG.md +40 -1
  2. package/lib/commands/diff.js +58 -33
  3. package/lib/commands/diff.js.map +1 -1
  4. package/lib/commands/export.js +3 -1
  5. package/lib/commands/export.js.map +1 -1
  6. package/lib/commands/files.js +10 -2
  7. package/lib/commands/files.js.map +1 -1
  8. package/lib/commands/model.js +15 -8
  9. package/lib/commands/model.js.map +1 -1
  10. package/lib/commands/session.js +6 -1
  11. package/lib/commands/session.js.map +1 -1
  12. package/lib/definition.d.ts +10 -0
  13. package/lib/locale/zh.d.ts +26 -6
  14. package/lib/locale/zh.js +26 -6
  15. package/lib/locale/zh.js.map +1 -1
  16. package/lib/runtime/account-settings.d.ts +1 -0
  17. package/lib/runtime/account-settings.js +2 -2
  18. package/lib/runtime/account-settings.js.map +1 -1
  19. package/lib/runtime/model-settings.d.ts +2 -0
  20. package/lib/runtime/model-settings.js +2 -2
  21. package/lib/runtime/model-settings.js.map +1 -1
  22. package/lib/runtime/plugin-settings.d.ts +1 -0
  23. package/lib/runtime/plugin-settings.js +6 -6
  24. package/lib/runtime/plugin-settings.js.map +1 -1
  25. package/lib/runtime/provider-local.d.ts +3 -0
  26. package/lib/runtime/provider-local.js +110 -22
  27. package/lib/runtime/provider-local.js.map +1 -1
  28. package/lib/runtime/settings-preferences.js +1 -1
  29. package/lib/runtime/settings-preferences.js.map +1 -1
  30. package/lib/session/feature-toggles.js +2 -2
  31. package/lib/session/feature-toggles.js.map +1 -1
  32. package/lib/session/session-controller.d.ts +2 -8
  33. package/lib/session/session-controller.js +57 -32
  34. package/lib/session/session-controller.js.map +1 -1
  35. package/lib/session/session-library.d.ts +3 -1
  36. package/lib/session/session-library.js +15 -0
  37. package/lib/session/session-library.js.map +1 -1
  38. package/lib/session/tui-settings.d.ts +2 -0
  39. package/lib/session/tui-settings.js +1 -0
  40. package/lib/session/tui-settings.js.map +1 -1
  41. package/lib/views/autocomplete.d.ts +3 -6
  42. package/lib/views/autocomplete.js +10 -8
  43. package/lib/views/autocomplete.js.map +1 -1
  44. package/lib/views/document-reader.js +4 -1
  45. package/lib/views/document-reader.js.map +1 -1
  46. package/lib/views/settings-list.d.ts +5 -0
  47. package/lib/views/settings-list.js +46 -10
  48. package/lib/views/settings-list.js.map +1 -1
  49. package/lib/views/transcript-render.d.ts +2 -0
  50. package/lib/views/transcript-render.js +31 -19
  51. package/lib/views/transcript-render.js.map +1 -1
  52. package/package.json +1 -1
@@ -414,6 +414,9 @@ export class LocalTui {
414
414
  #mermaidMode = 'auto';
415
415
  /** Transcript collapses every thought to one line; Ctrl+T flips it. */
416
416
  #hideThinking = true;
417
+ #showFileChanges = true;
418
+ #workspaceReview;
419
+ #workspacePress;
417
420
  #mouseInteraction = 'auto';
418
421
  #tmuxMouseEnabled;
419
422
  #disclosurePress;
@@ -439,6 +442,8 @@ export class LocalTui {
439
442
  */
440
443
  #transientStatus;
441
444
  #transientTimer = null;
445
+ /** Idle submissions also pass through the inbox, but are not queue gestures. */
446
+ #directInputIds = new Set();
442
447
  /** Aborts the live Shell card, so Ctrl+C interrupts the command it belongs to. */
443
448
  #shellAbort;
444
449
  /** Turn scopes whose tools stay detailed as new calls arrive during inspection. */
@@ -596,6 +601,7 @@ export class LocalTui {
596
601
  // live window so it stays anchored at the bottom.
597
602
  this.#selection.clear();
598
603
  this.#mouseActionPress = undefined;
604
+ this.#workspacePress = undefined;
599
605
  this.#jumpPress = undefined;
600
606
  this.#disclosurePress = undefined;
601
607
  const repaint = () => {
@@ -680,9 +686,23 @@ export class LocalTui {
680
686
  this.#retryDeadline = Date.now() + event.data.delayMs;
681
687
  if (event.type === 'llm/retry-started' || event.type === 'turn/end')
682
688
  this.#retryDeadline = undefined;
683
- const claimedHumanInput = event.type === 'agent/inbox/spliced' && event.data.outcome !== 'canceled'
684
- && event.data.inserted.length === 0 && (event.data.target === 'next-step' ? this.#state.nextStepInbox : this.#state.nextTurnInbox)
685
- .slice(event.data.start, event.data.start + (event.data.removedCount ?? 0)).some(message => message.source.kind === 'user');
689
+ let claimedHumanInput = false;
690
+ if (event.type === 'agent/inbox/spliced') {
691
+ const inbox = event.data.target === 'next-step' ? this.#state.nextStepInbox : this.#state.nextTurnInbox;
692
+ const removed = inbox.slice(event.data.start, event.data.start + (event.data.removedCount ?? 0));
693
+ claimedHumanInput = event.data.outcome !== 'canceled' && event.data.inserted.length === 0
694
+ && removed.some(message => message.source.kind === 'user' && !this.#directInputIds.has(message.id));
695
+ for (const message of removed)
696
+ this.#directInputIds.delete(message.id);
697
+ if (event.data.target === 'next-turn' && this.#state.status === 'idle'
698
+ && !inbox.some(message => message.source.kind === 'user')) {
699
+ const first = event.data.inserted.find(message => message.source.kind === 'user');
700
+ if (first !== undefined)
701
+ this.#directInputIds.add(first.id);
702
+ }
703
+ }
704
+ else if (event.type === 'session/end-seed')
705
+ this.#directInputIds.clear();
686
706
  this.#state = applyEvent(this.#state, event, presentation, workspace);
687
707
  if (claimedHumanInput && this.#tty && event.type === 'agent/inbox/spliced') {
688
708
  this.#showTransientStatus(event.data.target === 'next-step' ? 'Steer taken for the next step' : 'Queue taken for the next turn');
@@ -843,6 +863,7 @@ export class LocalTui {
843
863
  && this.#inspected?.writable === inspected?.writable)
844
864
  return;
845
865
  if (this.#inspected?.id !== inspected?.id) {
866
+ this.#workspacePress = undefined;
846
867
  this.#fileReviewAbort?.abort();
847
868
  this.#closeFileBrowser();
848
869
  }
@@ -870,6 +891,7 @@ export class LocalTui {
870
891
  async reviewFiles(request) {
871
892
  if (!this.#tty || this.#disposed || request.signal.aborted)
872
893
  return;
894
+ this.#workspacePress = undefined;
873
895
  this.#flushPasteBurst(true);
874
896
  this.#pasteBurst.clear();
875
897
  this.#finishPrompt(null);
@@ -1204,7 +1226,8 @@ export class LocalTui {
1204
1226
  const browserState = promptBrowserState(request);
1205
1227
  request = localizePrompt(request, resolveUiLanguage(this.#uiLanguage));
1206
1228
  // Background approvals take the terminal from a user-opened file reader.
1207
- if (this.#fileReviewAbort !== undefined && request.signal !== this.#fileReviewAbort.signal)
1229
+ if (this.#fileReviewAbort !== undefined && request.signal !== this.#fileReviewAbort.signal
1230
+ && request.signal !== this.#fileBrowser?.request.signal)
1208
1231
  this.#fileReviewAbort.abort();
1209
1232
  if (this.#disposed || request.signal?.aborted === true)
1210
1233
  return Promise.resolve(null);
@@ -1235,6 +1258,7 @@ export class LocalTui {
1235
1258
  this.#promptChoicePress = undefined;
1236
1259
  this.#disclosurePress = undefined;
1237
1260
  this.#mouseActionPress = undefined;
1261
+ this.#workspacePress = undefined;
1238
1262
  this.#jumpPress = undefined;
1239
1263
  this.#wheelGesture = IDLE_WHEEL;
1240
1264
  const savedInput = this.#editor.snapshot();
@@ -1325,6 +1349,7 @@ export class LocalTui {
1325
1349
  });
1326
1350
  }
1327
1351
  replaceSession(events, presentations, status = 'idle', reason = 'open') {
1352
+ this.#workspacePress = undefined;
1328
1353
  if (reason !== 'refresh') {
1329
1354
  this.#fileReviewAbort?.abort();
1330
1355
  this.#closeFileBrowser();
@@ -1332,6 +1357,14 @@ export class LocalTui {
1332
1357
  }
1333
1358
  this.#trajectory = null;
1334
1359
  const state = replayEvents(events, presentations);
1360
+ if (reason === 'refresh') {
1361
+ const pendingIds = new Set(state.nextTurnInbox.map(message => message.id));
1362
+ for (const id of this.#directInputIds)
1363
+ if (!pendingIds.has(id))
1364
+ this.#directInputIds.delete(id);
1365
+ }
1366
+ else
1367
+ this.#directInputIds.clear();
1335
1368
  // A notice does not turn a blank session into a document that /new needs
1336
1369
  // to separate. Refreshes are the same document.
1337
1370
  const boundary = reason === 'open' || (reason === 'new' && this.#state.blocks.some(block => block.kind !== 'notice'));
@@ -1348,6 +1381,7 @@ export class LocalTui {
1348
1381
  }
1349
1382
  setSession(info) {
1350
1383
  if (info.id !== this.#sessionId) {
1384
+ this.#workspacePress = undefined;
1351
1385
  this.#fileReviewAbort?.abort();
1352
1386
  this.#closeFileBrowser();
1353
1387
  }
@@ -1432,6 +1466,7 @@ export class LocalTui {
1432
1466
  mathMode: this.#mathMode,
1433
1467
  mermaidMode: this.#mermaidMode,
1434
1468
  hideThinking: this.#hideThinking,
1469
+ showFileChanges: this.#showFileChanges,
1435
1470
  editor: this.#externalEditor,
1436
1471
  terminalTitle: this.#terminalTitle,
1437
1472
  terminalState: this.#terminalState,
@@ -1719,6 +1754,15 @@ export class LocalTui {
1719
1754
  return pending.queued.length === 0 && pending.sending.size === 0
1720
1755
  && this.#state.nextTurnInbox.length === 0 && !this.#state.nextStepInbox.some(message => message.source.kind === 'user');
1721
1756
  }
1757
+ setWorkspaceReviewHandler(handler) {
1758
+ this.#workspaceReview = handler;
1759
+ return () => {
1760
+ if (this.#workspaceReview === handler) {
1761
+ this.#workspaceReview = undefined;
1762
+ this.#workspacePress = undefined;
1763
+ }
1764
+ };
1765
+ }
1722
1766
  setGoalHandler(handler) {
1723
1767
  this.#goalHandler = handler;
1724
1768
  this.#syncSettingsActions();
@@ -2137,6 +2181,7 @@ export class LocalTui {
2137
2181
  mathMode: this.#mathMode,
2138
2182
  mermaidMode: this.#mermaidMode,
2139
2183
  hideThinking: true,
2184
+ showFileChanges: this.#showFileChanges,
2140
2185
  height: this.#term.height(),
2141
2186
  model: this.#model,
2142
2187
  ...(this.#reasoningEffort === undefined ? {} : { reasoningEffort: this.#reasoningEffort }),
@@ -2262,7 +2307,8 @@ export class LocalTui {
2262
2307
  return Math.max(1, this.#scrollBudget > 2 ? this.#scrollBudget - 2 : 1);
2263
2308
  }
2264
2309
  #scrollBy(delta) {
2265
- if (this.#follow && delta > 0)
2310
+ this.#workspacePress = undefined;
2311
+ if (this.#follow && (delta > 0 || this.#maxStart === 0))
2266
2312
  return;
2267
2313
  if (delta === 0 && this.#maxStart === 0)
2268
2314
  return;
@@ -2325,6 +2371,7 @@ export class LocalTui {
2325
2371
  enabled = enabled && this.#mouseInteraction !== 'native'
2326
2372
  && (this.#mouseInteraction === 'tui' || this.#tmuxMouseEnabled !== false);
2327
2373
  if (!enabled) {
2374
+ this.#workspacePress = undefined;
2328
2375
  this.#mouseActionPress = undefined;
2329
2376
  this.#selection.clear();
2330
2377
  this.#jumpPress = undefined;
@@ -2665,6 +2712,18 @@ export class LocalTui {
2665
2712
  return { row, column: tool.actionColumn, width: tool.key.startsWith('output:') ? 6 : 5, key: tool.key, action: 'tool', block: tool.block };
2666
2713
  return undefined;
2667
2714
  }
2715
+ #fileRegionAt(row) {
2716
+ const document = this.#renderer.documentRowAt(row);
2717
+ const frame = this.#fileActionFrame;
2718
+ const source = frame?.documentRows;
2719
+ const region = document === undefined ? undefined : frame?.transcript?.fileRegions?.find(region => document >= region.start && document < region.end);
2720
+ const expected = document === undefined || source === undefined || source === null ? undefined
2721
+ : frame?.lines[source.frameStart + document - source.documentStart];
2722
+ const actual = this.#renderer.viewport()[row];
2723
+ // Committed native history can differ from the live document after a reflow.
2724
+ return expected !== undefined && actual !== undefined
2725
+ && stripAnsi(sanitizeDisplayLine(expected)).trimEnd() === stripAnsi(actual).trimEnd() ? region : undefined;
2726
+ }
2668
2727
  #handleMouse(event) {
2669
2728
  if (!this.#mouseTracking || this.#paste || this.#pasteInFlight > 0)
2670
2729
  return;
@@ -2741,6 +2800,7 @@ export class LocalTui {
2741
2800
  const target = this.#mouseActionAt(event.row, event.column);
2742
2801
  if (event.action === 'release') {
2743
2802
  this.#mouseActionPress = undefined;
2803
+ this.#workspacePress = undefined;
2744
2804
  if (event.button === 'left' && !event.shift && target?.key === pressedAction.key && target.row === pressedAction.row && target.column === pressedAction.column) {
2745
2805
  if (target.action === 'copy')
2746
2806
  void this.#copySelection(true);
@@ -2771,21 +2831,14 @@ export class LocalTui {
2771
2831
  }
2772
2832
  const inside = (target) => event.row === target.row && event.column >= target.column && event.column < target.column + target.width;
2773
2833
  if (event.button === 'right') {
2834
+ this.#workspacePress = undefined;
2774
2835
  this.#disclosurePress = undefined;
2775
2836
  if (event.action === 'press') {
2776
2837
  if (this.#selection.active)
2777
2838
  void this.#copySelection(true);
2778
2839
  else {
2779
- const document = this.#renderer.documentRowAt(event.row);
2780
- const frame = this.#fileActionFrame;
2781
- const source = frame?.documentRows;
2782
- const region = document === undefined ? undefined : frame?.transcript?.fileRegions?.find(region => document >= region.start && document < region.end);
2783
- const expected = document === undefined || source === undefined || source === null ? undefined
2784
- : frame?.lines[source.frameStart + document - source.documentStart];
2785
- const actual = this.#renderer.viewport()[event.row];
2786
- // Native-history snapshots may differ from the current document. Do
2787
- // not reinterpret their old text as another file after a reflow.
2788
- if (region !== undefined && expected !== undefined && actual !== undefined && stripAnsi(sanitizeDisplayLine(expected)).trimEnd() === stripAnsi(actual).trimEnd()) {
2840
+ const region = this.#fileRegionAt(event.row);
2841
+ if (region !== undefined) {
2789
2842
  const block = this.#state.blocks[region.block];
2790
2843
  const action = block === undefined ? undefined : blockFileAction(block);
2791
2844
  if (action !== undefined)
@@ -2796,6 +2849,10 @@ export class LocalTui {
2796
2849
  return;
2797
2850
  }
2798
2851
  if (event.action === 'press') {
2852
+ const region = !event.shift && this.#workspaceReview !== undefined ? this.#fileRegionAt(event.row) : undefined;
2853
+ const source = region === undefined ? undefined : this.#state.blocks[region.block];
2854
+ this.#workspacePress = source?.kind === 'workspace' && region !== undefined
2855
+ ? { row: event.row, column: event.column, block: region.block, source, ...(region.file === undefined ? {} : { file: region.file }) } : undefined;
2799
2856
  const disclosure = event.shift ? undefined : this.#disclosureAt(event.row, event.column);
2800
2857
  this.#disclosurePress = disclosure === undefined ? undefined : { target: disclosure, row: event.row, column: event.column };
2801
2858
  const target = this.#jumpToLatest;
@@ -2817,6 +2874,9 @@ export class LocalTui {
2817
2874
  }
2818
2875
  }
2819
2876
  else if (event.action === 'drag') {
2877
+ const pressed = this.#workspacePress;
2878
+ if (pressed !== undefined && (event.shift || event.row !== pressed.row || Math.abs(event.column - pressed.column) > 1))
2879
+ this.#workspacePress = undefined;
2820
2880
  // Motion confined to the empty gutter is click jitter. Once it leaves,
2821
2881
  // selection owns the gesture even if the pointer returns to the arrow.
2822
2882
  if (event.shift || this.#pressedDisclosureAt(event.row, event.column) === undefined) {
@@ -2825,6 +2885,15 @@ export class LocalTui {
2825
2885
  }
2826
2886
  }
2827
2887
  else {
2888
+ const pressed = this.#workspacePress;
2889
+ this.#workspacePress = undefined;
2890
+ const region = pressed === undefined ? undefined : this.#fileRegionAt(event.row);
2891
+ if (!event.shift && pressed !== undefined && event.row === pressed.row && Math.abs(event.column - pressed.column) <= 1
2892
+ && region?.block === pressed.block && region.file === pressed.file && this.#state.blocks[pressed.block] === pressed.source) {
2893
+ this.#selection.clear();
2894
+ this.#openWorkspaceReview(pressed.source, pressed.file);
2895
+ return;
2896
+ }
2828
2897
  const target = this.#pressedDisclosureAt(event.row, event.column);
2829
2898
  this.#disclosurePress = undefined;
2830
2899
  if (!event.shift && target !== undefined) {
@@ -3017,6 +3086,20 @@ export class LocalTui {
3017
3086
  this.#fileReviewAbort = undefined;
3018
3087
  }
3019
3088
  }
3089
+ #openWorkspaceReview(block, file) {
3090
+ const handler = this.#workspaceReview;
3091
+ const sessionId = this.#inspected?.id ?? this.#sessionId;
3092
+ if (handler === undefined || sessionId === undefined || block.kind !== 'workspace' || !this.#tty || this.#disposed
3093
+ || this.#prompt !== null || this.#fileReviewAbort !== undefined)
3094
+ return;
3095
+ const abort = new AbortController();
3096
+ this.#fileReviewAbort = abort;
3097
+ void handler({ sessionId, turn: block.turn, ...(file === undefined ? {} : { file }), signal: abort.signal })
3098
+ .catch(error => { if (!abort.signal.aborted && !this.#disposed)
3099
+ this.notice(error instanceof Error ? error.message : String(error), { level: 'error' }); })
3100
+ .finally(() => { if (this.#fileReviewAbort === abort)
3101
+ this.#fileReviewAbort = undefined; });
3102
+ }
3020
3103
  #openFileAction(action, initial) {
3021
3104
  if (!this.#tty || this.#disposed || this.#prompt !== null || this.#fileReviewAbort !== undefined)
3022
3105
  return;
@@ -3068,6 +3151,7 @@ export class LocalTui {
3068
3151
  this.#jumpPress = undefined;
3069
3152
  this.#disclosurePress = undefined;
3070
3153
  this.#mouseActionPress = undefined;
3154
+ this.#workspacePress = undefined;
3071
3155
  if (this.#keyTest !== undefined && this.#prompt?.request.signal === this.#keyTest.signal
3072
3156
  && (event.type === 'text' || event.type === 'paste-start' || event.type === 'paste-end' || event.type === 'key' && !['escape', 'ctrl+c'].includes(event.id))) {
3073
3157
  this.#keyTest.observe(event);
@@ -3898,6 +3982,7 @@ export class LocalTui {
3898
3982
  const previous = options.persist ? this.prefs() : undefined;
3899
3983
  const version = this.#prefsVersion;
3900
3984
  const previousMotion = this.#motion;
3985
+ this.#workspacePress = undefined;
3901
3986
  this.#uiLanguage = prefs.uiLanguage ?? 'auto';
3902
3987
  if (this.#prompt !== null)
3903
3988
  this.#prompt = { ...this.#prompt, request: localizePrompt(this.#prompt.sourceRequest, resolveUiLanguage(this.#uiLanguage)) };
@@ -3916,6 +4001,7 @@ export class LocalTui {
3916
4001
  this.#mathMode = prefs.mathMode ?? 'auto';
3917
4002
  this.#mermaidMode = prefs.mermaidMode ?? 'auto';
3918
4003
  this.#hideThinking = prefs.hideThinking !== false;
4004
+ this.#showFileChanges = prefs.showFileChanges !== false;
3919
4005
  this.#mouseInteraction = prefs.mouseInteraction ?? 'auto';
3920
4006
  this.#checkUpdates = prefs.checkUpdates ?? true;
3921
4007
  this.#startupChangelog = prefs.startupChangelog ?? 'summary';
@@ -4899,17 +4985,17 @@ export class LocalTui {
4899
4985
  }
4900
4986
  #settingsActions() {
4901
4987
  const runtimeActions = [
4902
- { id: 'command:model', behavior: 'Session + Profile · Choose scope in Model', group: 'Models', label: 'Model and reasoning', description: 'Open /model; changes the current selection and saves the default route.', value: [this.#model, this.#reasoningEffort].filter(Boolean).join(' · '), values: [], action: 'model' },
4903
- { id: 'command:permission', behavior: 'Current session · Applies now', group: 'General', label: 'Current session access', description: 'Changes only this session. Default access controls future sessions.', value: this.#sessionControls?.permission ?? 'Choose', values: [], action: 'permission' },
4904
- { id: 'command:agent', behavior: 'Current session · Before the first prompt', group: 'Agent presets', label: 'Current session preset', description: 'Choose before the first prompt; does not change the new-session default.', value: this.#sessionControls?.agentPreset === undefined ? 'Choose' : formatAgentPreset(this.#sessionControls.agentPreset), values: [], action: 'agent' },
4905
- { id: 'command:login', behavior: 'Provider credentials', group: 'Models', label: 'Provider credentials', description: 'Sign in or configure a provider. Does not change the selected model.', value: 'Configure', values: [], action: 'login' },
4906
- { id: 'command:logout', behavior: 'Provider credentials', group: 'Models', label: 'Sign out', description: 'Remove saved provider credentials; environment credentials remain available.', value: 'Choose provider', values: [], action: 'logout' },
4907
- { id: 'command:plugins', group: 'Plugins', label: 'Manage plugins', description: 'Inspect installed entries, enable bundles, discover plugins or start Creator.', value: 'Open', values: [], action: 'plugins' },
4988
+ { id: 'command:model', behavior: 'Session + Profile · Choose scope in Model', group: 'Models', subgroup: 'Model selection', label: 'Model and reasoning', description: 'Choose the current model and reasoning effort. Save as the default or use for this session.', value: [this.#model, this.#reasoningEffort].filter(Boolean).join(' · '), values: [], action: 'model' },
4989
+ { id: 'command:permission', behavior: 'Current session · Applies now', group: 'General', subgroup: 'Session controls', label: 'Current session access', description: 'Changes only this session. Default access controls future sessions.', value: this.#sessionControls?.permission ?? 'Choose', values: [], action: 'permission' },
4990
+ { id: 'command:agent', behavior: 'Current session · Before the first prompt', group: 'Agent presets', subgroup: 'Current session', label: 'Current session preset', description: 'Choose before the first prompt; does not change the new-session default.', value: this.#sessionControls?.agentPreset === undefined ? 'Choose' : formatAgentPreset(this.#sessionControls.agentPreset), values: [], action: 'agent' },
4991
+ { id: 'command:login', behavior: 'Accounts and API keys', group: 'Models', subgroup: 'Accounts and API keys', label: 'Sign in / add API key', description: 'Sign in to a provider account or save an API key.', keywords: ['Provider credentials', 'login', 'API key', 'OAuth'], value: 'Choose provider', values: [], action: 'login' },
4992
+ { id: 'command:logout', behavior: 'Accounts and API keys', group: 'Models', subgroup: 'Accounts and API keys', label: 'Sign out / remove key', description: 'Choose a provider or account to sign out or remove locally saved credentials.', keywords: ['logout', 'credentials'], value: 'Choose provider', values: [], action: 'logout' },
4993
+ { id: 'command:plugins', group: 'Plugins', subgroup: 'Plugin management', label: 'Manage plugins', description: 'Inspect installed entries, enable bundles, discover plugins or start Creator.', value: 'Open', values: [], action: 'plugins' },
4908
4994
  ].filter(item => this.#runtimeCommands.some(command => command.name === item.action));
4909
4995
  const customBindings = Object.entries(this.#keybindings).filter(([key, action]) => DEFAULT_KEYBINDINGS[key] !== action).length;
4910
4996
  return [
4911
4997
  ...runtimeActions,
4912
- ...(this.#goalHandler === undefined ? [] : [{ id: 'action:goal', behavior: 'Current session · Applies now', group: 'General', label: 'Current goal', description: 'Edit, pause, resume or clear this session’s goal.', value: 'Open', values: [], action: 'manage-goal' }]),
4998
+ ...(this.#goalHandler === undefined ? [] : [{ id: 'action:goal', behavior: 'Current session · Applies now', group: 'General', subgroup: 'Session controls', label: 'Current goal', description: 'Edit, pause, resume or clear this session’s goal.', value: 'Open', values: [], action: 'manage-goal' }]),
4913
4999
  ...(this.#keybindingsPath === undefined ? [] : [{
4914
5000
  id: 'action:keybindings',
4915
5001
  label: 'Keybindings',
@@ -5828,6 +5914,7 @@ export class LocalTui {
5828
5914
  toolsExpanded: this.#toolsExpanded,
5829
5915
  expandedOutputs: this.#expandedOutputs,
5830
5916
  hideThinking: true,
5917
+ showFileChanges: this.#showFileChanges,
5831
5918
  expandedTools: this.#detailedToolIds(),
5832
5919
  expandedReasoning: this.#visibleReasoning(),
5833
5920
  ...(this.#openedGroups === undefined ? {} : { openedGroups: this.#openedGroups }),
@@ -5938,6 +6025,7 @@ export function apply(ctx, config) {
5938
6025
  motion: config.motion.get(),
5939
6026
  mathMode: config.mathMode.get(),
5940
6027
  mermaidMode: config.mermaidMode.get(),
6028
+ showFileChanges: config.showFileChanges.get(),
5941
6029
  editor: config.editor.get(),
5942
6030
  terminalTitle: config.terminalTitle.get(),
5943
6031
  terminalState: config.terminalState.get(),