@cortexkit/aft-opencode 0.58.2 → 0.59.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 (85) hide show
  1. package/dist/bash-wait-detach.d.ts.map +1 -1
  2. package/dist/bg-notifications.d.ts +8 -0
  3. package/dist/bg-notifications.d.ts.map +1 -1
  4. package/dist/bridge-bootstrap.d.ts +4 -4
  5. package/dist/config-live-reload.d.ts +36 -8
  6. package/dist/config-live-reload.d.ts.map +1 -1
  7. package/dist/config.d.ts +72 -13
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/configure-warnings.d.ts.map +1 -1
  10. package/dist/entry/server.js +3738 -1780
  11. package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
  12. package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +3580 -1817
  15. package/dist/lsp-auto-install.d.ts.map +1 -1
  16. package/dist/lsp-github-install.d.ts +5 -2
  17. package/dist/lsp-github-install.d.ts.map +1 -1
  18. package/dist/notifications.d.ts +1 -1
  19. package/dist/notifications.d.ts.map +1 -1
  20. package/dist/rpc/contract.d.ts +1 -1
  21. package/dist/rpc/contract.d.ts.map +1 -1
  22. package/dist/rpc/register.d.ts +3 -1
  23. package/dist/rpc/register.d.ts.map +1 -1
  24. package/dist/shared/ignored-message.d.ts.map +1 -1
  25. package/dist/shared/read-vision.d.ts.map +1 -1
  26. package/dist/shared/rpc-client.d.ts +8 -0
  27. package/dist/shared/rpc-client.d.ts.map +1 -1
  28. package/dist/shared/rpc-server.d.ts.map +1 -1
  29. package/dist/shared/session-directory.d.ts +7 -5
  30. package/dist/shared/session-directory.d.ts.map +1 -1
  31. package/dist/shared/status.d.ts +1 -0
  32. package/dist/shared/status.d.ts.map +1 -1
  33. package/dist/shared/v2-context.d.ts +3 -0
  34. package/dist/shared/v2-context.d.ts.map +1 -0
  35. package/dist/subc-tool-schemas.d.ts +23 -1
  36. package/dist/subc-tool-schemas.d.ts.map +1 -1
  37. package/dist/tool-registration.d.ts +3 -3
  38. package/dist/tool-registration.d.ts.map +1 -1
  39. package/dist/tools/_shared.d.ts.map +1 -1
  40. package/dist/tools/bash.d.ts +17 -0
  41. package/dist/tools/bash.d.ts.map +1 -1
  42. package/dist/tools/bash_watch.d.ts +11 -1
  43. package/dist/tools/bash_watch.d.ts.map +1 -1
  44. package/dist/tools/definitions/v2.d.ts +2 -0
  45. package/dist/tools/definitions/v2.d.ts.map +1 -1
  46. package/dist/tools/hoisted.d.ts +18 -0
  47. package/dist/tools/hoisted.d.ts.map +1 -1
  48. package/dist/tools/inspect.d.ts +10 -1
  49. package/dist/tools/inspect.d.ts.map +1 -1
  50. package/dist/tools/permissions.d.ts +2 -1
  51. package/dist/tools/permissions.d.ts.map +1 -1
  52. package/dist/tools/search.d.ts.map +1 -1
  53. package/dist/tools/semantic.d.ts.map +1 -1
  54. package/dist/tui/aft-rpc.d.ts +14 -0
  55. package/dist/tui/aft-rpc.d.ts.map +1 -1
  56. package/dist/tui/v2-status.d.ts.map +1 -1
  57. package/dist/v2-host-tool-overlap.d.ts +66 -0
  58. package/dist/v2-host-tool-overlap.d.ts.map +1 -0
  59. package/dist/v2-prompt-detach.d.ts +1 -0
  60. package/dist/v2-prompt-detach.d.ts.map +1 -1
  61. package/dist/v2-tool-hooks.d.ts +6 -0
  62. package/dist/v2-tool-hooks.d.ts.map +1 -0
  63. package/dist/v2-workflow-hints.d.ts +4 -0
  64. package/dist/v2-workflow-hints.d.ts.map +1 -0
  65. package/dist/wakes/runtime-consumer.d.ts.map +1 -1
  66. package/dist/wakes/session-delivery.d.ts.map +1 -1
  67. package/package.json +9 -9
  68. package/src/entry/server-runtime.mjs +21 -1
  69. package/src/shared/ignored-message.ts +18 -0
  70. package/src/shared/read-vision.ts +14 -4
  71. package/src/shared/rpc-client.ts +46 -5
  72. package/src/shared/rpc-server.ts +10 -0
  73. package/src/shared/session-directory.ts +67 -19
  74. package/src/shared/status.ts +15 -5
  75. package/src/shared/v2-context.ts +7 -0
  76. package/src/tui/aft-rpc.ts +11 -0
  77. package/src/tui/index.tsx +20 -16
  78. package/src/tui/sidebar-view.tsx +9 -1
  79. package/src/tui/v2-status.ts +7 -5
  80. package/src/tui/v2.tsx +47 -0
  81. package/src/tui-compiled/aft-rpc.ts +11 -0
  82. package/src/tui-compiled/index.tsx +27 -16
  83. package/src/tui-compiled/sidebar-view.tsx +11 -2
  84. package/src/tui-compiled/v2-status.ts +7 -5
  85. package/src/tui-compiled/v2.tsx +26 -0
@@ -24,8 +24,18 @@ export const IndexProgress = Schema.Struct({
24
24
  total: Schema.optional(Schema.Number),
25
25
  });
26
26
 
27
+ /**
28
+ * A one-off notice for the user, shown by the OpenCode 2 TUI as a toast. It
29
+ * travels over RPC rather than into the session because anything written into
30
+ * a session (a synthetic record included) is sent to the model on later turns.
31
+ */
32
+ export const HostToolOverlapNotice = Schema.Struct({
33
+ message: Schema.String,
34
+ });
35
+
27
36
  export type AftRpcSession = typeof RpcSession.Type;
28
37
  export type AftIndexProgress = typeof IndexProgress.Type;
38
+ export type AftHostToolOverlapNotice = typeof HostToolOverlapNotice.Type;
29
39
 
30
40
  export const AftRpc = Rpc.define({
31
41
  id: "aft",
@@ -39,5 +49,6 @@ export const AftRpc = Rpc.define({
39
49
  statusInvalidated: { schema: RpcSession },
40
50
  showStatusDialog: { schema: RpcSession },
41
51
  indexProgress: { schema: IndexProgress },
52
+ hostToolOverlap: { schema: HostToolOverlapNotice },
42
53
  },
43
54
  });
package/src/tui/index.tsx CHANGED
@@ -230,6 +230,7 @@ const StatusDialog = (props: StatusDialogProps) => {
230
230
  // The earlier `formatCompressionDialogRows` returned padded strings that
231
231
  // looked offset against neighboring sections.
232
232
  const compressionAggregateRows = () => formatCompressionSidebarRows(status()?.compression);
233
+ const statusBar = () => status()?.status_bar ?? status()?.status_bar_values;
233
234
 
234
235
  return (
235
236
  <box
@@ -492,56 +493,59 @@ const StatusDialog = (props: StatusDialogProps) => {
492
493
 
493
494
  {/* Human health values are optional. Omit a category until the server
494
495
  proves it rather than displaying an unproven zero as clean. */}
495
- {status()?.status_bar ? (
496
+ {statusBar() ? (
496
497
  <box flexDirection="column" width="100%" marginTop={1}>
497
498
  <text fg={t().text}>
498
- <b>{status()!.status_bar!.tier2_stale ? "Code Health ~" : "Code Health"}</b>
499
+ <b>{statusBar()!.tier2_stale ? "Code Health ~" : "Code Health"}</b>
499
500
  </text>
500
- {status()!.status_bar!.errors !== undefined ? (
501
+ {(statusBar()!.disabled_categories ?? []).map((key) => (
502
+ <R theme={t()} label={key.replaceAll("_", " ")} value="○ off" tone="muted" />
503
+ ))}
504
+ {statusBar()!.errors !== undefined ? (
501
505
  <R
502
506
  theme={t()}
503
507
  label="Errors"
504
- value={formatCountShort(status()!.status_bar!.errors)}
505
- tone={status()!.status_bar!.errors! > 0 ? "err" : "muted"}
508
+ value={formatCountShort(statusBar()!.errors)}
509
+ tone={statusBar()!.errors! > 0 ? "err" : "muted"}
506
510
  />
507
511
  ) : null}
508
- {status()!.status_bar!.warnings !== undefined ? (
512
+ {statusBar()!.warnings !== undefined ? (
509
513
  <R
510
514
  theme={t()}
511
515
  label="Warnings"
512
- value={formatCountShort(status()!.status_bar!.warnings)}
513
- tone={status()!.status_bar!.warnings! > 0 ? "warn" : "muted"}
516
+ value={formatCountShort(statusBar()!.warnings)}
517
+ tone={statusBar()!.warnings! > 0 ? "warn" : "muted"}
514
518
  />
515
519
  ) : null}
516
- {status()!.status_bar!.dead_code !== undefined ? (
520
+ {statusBar()!.dead_code !== undefined ? (
517
521
  <R
518
522
  theme={t()}
519
523
  label="Dead Code"
520
- value={formatCountShort(status()!.status_bar!.dead_code)}
524
+ value={formatCountShort(statusBar()!.dead_code)}
521
525
  tone="muted"
522
526
  />
523
527
  ) : null}
524
- {status()!.status_bar!.unused_exports !== undefined ? (
528
+ {statusBar()!.unused_exports !== undefined ? (
525
529
  <R
526
530
  theme={t()}
527
531
  label="Unused Exports"
528
- value={formatCountShort(status()!.status_bar!.unused_exports)}
532
+ value={formatCountShort(statusBar()!.unused_exports)}
529
533
  tone="muted"
530
534
  />
531
535
  ) : null}
532
- {status()!.status_bar!.duplicates !== undefined ? (
536
+ {statusBar()!.duplicates !== undefined ? (
533
537
  <R
534
538
  theme={t()}
535
539
  label="Duplicates"
536
- value={formatCountShort(status()!.status_bar!.duplicates)}
540
+ value={formatCountShort(statusBar()!.duplicates)}
537
541
  tone="muted"
538
542
  />
539
543
  ) : null}
540
- {status()!.status_bar!.todos !== undefined ? (
544
+ {statusBar()!.todos !== undefined ? (
541
545
  <R
542
546
  theme={t()}
543
547
  label="TODOs"
544
- value={formatCountShort(status()!.status_bar!.todos)}
548
+ value={formatCountShort(statusBar()!.todos)}
545
549
  tone="muted"
546
550
  />
547
551
  ) : null}
@@ -421,7 +421,7 @@ export const AftSidebarPanel = (props: AftSidebarPanelProps) => {
421
421
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
422
422
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
423
423
  const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
424
- const statusBar = () => s()?.status_bar;
424
+ const statusBar = () => s()?.status_bar ?? s()?.status_bar_values;
425
425
 
426
426
  const degradedSummary = () => {
427
427
  const snap = s();
@@ -653,6 +653,14 @@ export const AftSidebarPanel = (props: AftSidebarPanelProps) => {
653
653
  palette={props.palette}
654
654
  title={statusBar()!.tier2_stale ? "Code Health ~" : "Code Health"}
655
655
  />
656
+ {(statusBar()!.disabled_categories ?? []).map((key) => (
657
+ <StatRow
658
+ palette={props.palette}
659
+ label={key.replaceAll("_", " ")}
660
+ value="○ off"
661
+ tone="muted"
662
+ />
663
+ ))}
656
664
  {statusBar()!.errors !== undefined && (
657
665
  <StatRow
658
666
  palette={props.palette}
@@ -1,6 +1,7 @@
1
1
  import { type AftStatusSnapshot, degradedHeaderMarker, type StatusBar } from "../shared/status";
2
2
 
3
- function count(value: number | undefined): string {
3
+ function count(value: number | undefined, off = false): string {
4
+ if (off) return "○";
4
5
  return value === undefined ? "?" : String(value);
5
6
  }
6
7
 
@@ -10,6 +11,7 @@ function count(value: number | undefined): string {
10
11
  * indefinitely.
11
12
  */
12
13
  function diagnosticsPart(bar: StatusBar): string {
14
+ if (bar.disabled_categories?.includes("diagnostics")) return "E○ W○";
13
15
  if (
14
16
  bar.errors === undefined &&
15
17
  bar.warnings === undefined &&
@@ -36,8 +38,8 @@ export function formatAftStatusSegment(status: AftStatusSnapshot | null): string
36
38
  const stale = bar.tier2_stale ? "~" : "";
37
39
  return (
38
40
  `AFT ${diagnosticsPart(bar)} | ` +
39
- `${stale}D${count(bar.dead_code)} U${count(bar.unused_exports)} ` +
40
- `C${count(bar.duplicates)} | T${count(bar.todos)}`
41
+ `${stale}D${count(bar.dead_code, bar.disabled_categories?.includes("dead_code"))} U${count(bar.unused_exports, bar.disabled_categories?.includes("unused_exports"))} ` +
42
+ `C${count(bar.duplicates, bar.disabled_categories?.includes("duplicates"))} | T${count(bar.todos, bar.disabled_categories?.includes("todos"))}`
41
43
  );
42
44
  }
43
45
 
@@ -53,8 +55,8 @@ function healthSummary(bar: StatusBar | undefined): string {
53
55
  const stale = bar.tier2_stale ? "~" : "";
54
56
  return (
55
57
  `${diagnosticsPart(bar)} ` +
56
- `${stale}D${count(bar.dead_code)} U${count(bar.unused_exports)} ` +
57
- `C${count(bar.duplicates)} T${count(bar.todos)}`
58
+ `${stale}D${count(bar.dead_code, bar.disabled_categories?.includes("dead_code"))} U${count(bar.unused_exports, bar.disabled_categories?.includes("unused_exports"))} ` +
59
+ `C${count(bar.duplicates, bar.disabled_categories?.includes("duplicates"))} T${count(bar.todos, bar.disabled_categories?.includes("todos"))}`
58
60
  );
59
61
  }
60
62
 
package/src/tui/v2.tsx CHANGED
@@ -28,6 +28,11 @@ type AftRpcClient = {
28
28
  handler: (event: { data: { sessionID?: string } }) => void | Promise<void>,
29
29
  options?: { signal?: AbortSignal },
30
30
  ): () => void;
31
+ on(
32
+ name: "hostToolOverlap",
33
+ handler: (event: { data: { message: string } }) => void | Promise<void>,
34
+ options?: { signal?: AbortSignal },
35
+ ): () => void;
31
36
  };
32
37
  };
33
38
 
@@ -87,6 +92,14 @@ type V2TuiContext = {
87
92
  };
88
93
  ui: {
89
94
  dialog: { alert(input: { title: string; message: string }): Promise<void> };
95
+ toast?: {
96
+ show(input: {
97
+ title?: string;
98
+ message: string;
99
+ variant?: "info" | "success" | "warning" | "error";
100
+ duration?: number;
101
+ }): void;
102
+ };
90
103
  router: { current(): { type: string; sessionID?: string } };
91
104
  slot(claim: {
92
105
  append: "app" | "prompt.footer.status" | "sidebar.content";
@@ -316,6 +329,36 @@ function AftStatusCommands(props: { context: V2TuiContext; rpc: AftRpcClient })
316
329
  return null;
317
330
  }
318
331
 
332
+ /** How long the built-in tool overlap toast stays up: long enough to read the command. */
333
+ const HOST_TOOL_OVERLAP_TOAST_MS = 15_000;
334
+
335
+ /**
336
+ * Show the server's one-off built-in tool overlap notice as a toast. A toast
337
+ * reaches the person at the terminal and nothing else; the server sends this
338
+ * at most once per host process, so every TUI subscribed at that moment shows
339
+ * it once. A host without a toast surface shows nothing.
340
+ */
341
+ export function subscribeV2HostToolOverlapToast(
342
+ context: Pick<V2TuiContext, "ui">,
343
+ rpc: AftRpcClient,
344
+ options?: { signal?: AbortSignal },
345
+ ): () => void {
346
+ return rpc.events.on(
347
+ "hostToolOverlap",
348
+ (event) => {
349
+ const toast = context.ui.toast;
350
+ if (typeof toast?.show !== "function") return;
351
+ toast.show({
352
+ title: "AFT",
353
+ message: event.data.message,
354
+ variant: "warning",
355
+ duration: HOST_TOOL_OVERLAP_TOAST_MS,
356
+ });
357
+ },
358
+ options,
359
+ );
360
+ }
361
+
319
362
  export async function setupV2Tui(context: V2TuiContext): Promise<() => void> {
320
363
  const rpc = context.client.rpc(AftRpc);
321
364
  const controller = new AbortController();
@@ -343,10 +386,14 @@ export async function setupV2Tui(context: V2TuiContext): Promise<() => void> {
343
386
  (event) => showStatusDialog(context, rpc, event.data.sessionID ?? activeSessionID(context)),
344
387
  { signal: controller.signal },
345
388
  );
389
+ const stopOverlapToasts = subscribeV2HostToolOverlapToast(context, rpc, {
390
+ signal: controller.signal,
391
+ });
346
392
 
347
393
  return () => {
348
394
  controller.abort();
349
395
  stopDialogEvents();
396
+ stopOverlapToasts();
350
397
  for (const cleanup of slotCleanups) cleanup();
351
398
  };
352
399
  }
@@ -24,8 +24,18 @@ export const IndexProgress = Schema.Struct({
24
24
  total: Schema.optional(Schema.Number),
25
25
  });
26
26
 
27
+ /**
28
+ * A one-off notice for the user, shown by the OpenCode 2 TUI as a toast. It
29
+ * travels over RPC rather than into the session because anything written into
30
+ * a session (a synthetic record included) is sent to the model on later turns.
31
+ */
32
+ export const HostToolOverlapNotice = Schema.Struct({
33
+ message: Schema.String,
34
+ });
35
+
27
36
  export type AftRpcSession = typeof RpcSession.Type;
28
37
  export type AftIndexProgress = typeof IndexProgress.Type;
38
+ export type AftHostToolOverlapNotice = typeof HostToolOverlapNotice.Type;
29
39
 
30
40
  export const AftRpc = Rpc.define({
31
41
  id: "aft",
@@ -39,5 +49,6 @@ export const AftRpc = Rpc.define({
39
49
  statusInvalidated: { schema: RpcSession },
40
50
  showStatusDialog: { schema: RpcSession },
41
51
  indexProgress: { schema: IndexProgress },
52
+ hostToolOverlap: { schema: HostToolOverlapNotice },
42
53
  },
43
54
  });
@@ -197,6 +197,7 @@ const StatusDialog = props => {
197
197
  // The earlier `formatCompressionDialogRows` returned padded strings that
198
198
  // looked offset against neighboring sections.
199
199
  const compressionAggregateRows = () => formatCompressionSidebarRows(status()?.compression);
200
+ const statusBar = () => status()?.status_bar ?? status()?.status_bar_values;
200
201
  return (() => {
201
202
  var _el$4 = _$createElement("box"),
202
203
  _el$5 = _$createElement("box"),
@@ -698,7 +699,7 @@ const StatusDialog = props => {
698
699
  })() : null;
699
700
  })(), _el$9);
700
701
  _$insert(_el$4, (() => {
701
- var _c$9 = _$memo(() => !!status()?.status_bar);
702
+ var _c$9 = _$memo(() => !!statusBar());
702
703
  return () => _c$9() ? (() => {
703
704
  var _el$53 = _$createElement("box"),
704
705
  _el$54 = _$createElement("text"),
@@ -708,85 +709,95 @@ const StatusDialog = props => {
708
709
  _$setProp(_el$53, "width", "100%");
709
710
  _$setProp(_el$53, "marginTop", 1);
710
711
  _$insertNode(_el$54, _el$55);
711
- _$insert(_el$55, () => status().status_bar.tier2_stale ? "Code Health ~" : "Code Health");
712
+ _$insert(_el$55, () => statusBar().tier2_stale ? "Code Health ~" : "Code Health");
713
+ _$insert(_el$53, () => (statusBar().disabled_categories ?? []).map(key => _$createComponent(R, {
714
+ get theme() {
715
+ return t();
716
+ },
717
+ get label() {
718
+ return key.replaceAll("_", " ");
719
+ },
720
+ value: "\u25CB off",
721
+ tone: "muted"
722
+ })), null);
712
723
  _$insert(_el$53, (() => {
713
- var _c$14 = _$memo(() => status().status_bar.errors !== undefined);
724
+ var _c$14 = _$memo(() => statusBar().errors !== undefined);
714
725
  return () => _c$14() ? _$createComponent(R, {
715
726
  get theme() {
716
727
  return t();
717
728
  },
718
729
  label: "Errors",
719
730
  get value() {
720
- return formatCountShort(status().status_bar.errors);
731
+ return formatCountShort(statusBar().errors);
721
732
  },
722
733
  get tone() {
723
- return status().status_bar.errors > 0 ? "err" : "muted";
734
+ return statusBar().errors > 0 ? "err" : "muted";
724
735
  }
725
736
  }) : null;
726
737
  })(), null);
727
738
  _$insert(_el$53, (() => {
728
- var _c$15 = _$memo(() => status().status_bar.warnings !== undefined);
739
+ var _c$15 = _$memo(() => statusBar().warnings !== undefined);
729
740
  return () => _c$15() ? _$createComponent(R, {
730
741
  get theme() {
731
742
  return t();
732
743
  },
733
744
  label: "Warnings",
734
745
  get value() {
735
- return formatCountShort(status().status_bar.warnings);
746
+ return formatCountShort(statusBar().warnings);
736
747
  },
737
748
  get tone() {
738
- return status().status_bar.warnings > 0 ? "warn" : "muted";
749
+ return statusBar().warnings > 0 ? "warn" : "muted";
739
750
  }
740
751
  }) : null;
741
752
  })(), null);
742
753
  _$insert(_el$53, (() => {
743
- var _c$16 = _$memo(() => status().status_bar.dead_code !== undefined);
754
+ var _c$16 = _$memo(() => statusBar().dead_code !== undefined);
744
755
  return () => _c$16() ? _$createComponent(R, {
745
756
  get theme() {
746
757
  return t();
747
758
  },
748
759
  label: "Dead Code",
749
760
  get value() {
750
- return formatCountShort(status().status_bar.dead_code);
761
+ return formatCountShort(statusBar().dead_code);
751
762
  },
752
763
  tone: "muted"
753
764
  }) : null;
754
765
  })(), null);
755
766
  _$insert(_el$53, (() => {
756
- var _c$17 = _$memo(() => status().status_bar.unused_exports !== undefined);
767
+ var _c$17 = _$memo(() => statusBar().unused_exports !== undefined);
757
768
  return () => _c$17() ? _$createComponent(R, {
758
769
  get theme() {
759
770
  return t();
760
771
  },
761
772
  label: "Unused Exports",
762
773
  get value() {
763
- return formatCountShort(status().status_bar.unused_exports);
774
+ return formatCountShort(statusBar().unused_exports);
764
775
  },
765
776
  tone: "muted"
766
777
  }) : null;
767
778
  })(), null);
768
779
  _$insert(_el$53, (() => {
769
- var _c$18 = _$memo(() => status().status_bar.duplicates !== undefined);
780
+ var _c$18 = _$memo(() => statusBar().duplicates !== undefined);
770
781
  return () => _c$18() ? _$createComponent(R, {
771
782
  get theme() {
772
783
  return t();
773
784
  },
774
785
  label: "Duplicates",
775
786
  get value() {
776
- return formatCountShort(status().status_bar.duplicates);
787
+ return formatCountShort(statusBar().duplicates);
777
788
  },
778
789
  tone: "muted"
779
790
  }) : null;
780
791
  })(), null);
781
792
  _$insert(_el$53, (() => {
782
- var _c$19 = _$memo(() => status().status_bar.todos !== undefined);
793
+ var _c$19 = _$memo(() => statusBar().todos !== undefined);
783
794
  return () => _c$19() ? _$createComponent(R, {
784
795
  get theme() {
785
796
  return t();
786
797
  },
787
798
  label: "TODOs",
788
799
  get value() {
789
- return formatCountShort(status().status_bar.todos);
800
+ return formatCountShort(statusBar().todos);
790
801
  },
791
802
  tone: "muted"
792
803
  }) : null;
@@ -407,7 +407,7 @@ export const AftSidebarPanel = props => {
407
407
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
408
408
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
409
409
  const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
410
- const statusBar = () => s()?.status_bar;
410
+ const statusBar = () => s()?.status_bar ?? s()?.status_bar_values;
411
411
  const degradedSummary = () => {
412
412
  const snap = s();
413
413
  if (!snap?.degraded) return null;
@@ -744,7 +744,16 @@ export const AftSidebarPanel = props => {
744
744
  get title() {
745
745
  return statusBar().tier2_stale ? "Code Health ~" : "Code Health";
746
746
  }
747
- }), _$memo(() => _$memo(() => statusBar().errors !== undefined)() && _$createComponent(StatRow, {
747
+ }), _$memo(() => (statusBar().disabled_categories ?? []).map(key => _$createComponent(StatRow, {
748
+ get palette() {
749
+ return props.palette;
750
+ },
751
+ get label() {
752
+ return key.replaceAll("_", " ");
753
+ },
754
+ value: "\u25CB off",
755
+ tone: "muted"
756
+ }))), _$memo(() => _$memo(() => statusBar().errors !== undefined)() && _$createComponent(StatRow, {
748
757
  get palette() {
749
758
  return props.palette;
750
759
  },
@@ -1,6 +1,7 @@
1
1
  import { type AftStatusSnapshot, degradedHeaderMarker, type StatusBar } from "../shared/status";
2
2
 
3
- function count(value: number | undefined): string {
3
+ function count(value: number | undefined, off = false): string {
4
+ if (off) return "○";
4
5
  return value === undefined ? "?" : String(value);
5
6
  }
6
7
 
@@ -10,6 +11,7 @@ function count(value: number | undefined): string {
10
11
  * indefinitely.
11
12
  */
12
13
  function diagnosticsPart(bar: StatusBar): string {
14
+ if (bar.disabled_categories?.includes("diagnostics")) return "E○ W○";
13
15
  if (
14
16
  bar.errors === undefined &&
15
17
  bar.warnings === undefined &&
@@ -36,8 +38,8 @@ export function formatAftStatusSegment(status: AftStatusSnapshot | null): string
36
38
  const stale = bar.tier2_stale ? "~" : "";
37
39
  return (
38
40
  `AFT ${diagnosticsPart(bar)} | ` +
39
- `${stale}D${count(bar.dead_code)} U${count(bar.unused_exports)} ` +
40
- `C${count(bar.duplicates)} | T${count(bar.todos)}`
41
+ `${stale}D${count(bar.dead_code, bar.disabled_categories?.includes("dead_code"))} U${count(bar.unused_exports, bar.disabled_categories?.includes("unused_exports"))} ` +
42
+ `C${count(bar.duplicates, bar.disabled_categories?.includes("duplicates"))} | T${count(bar.todos, bar.disabled_categories?.includes("todos"))}`
41
43
  );
42
44
  }
43
45
 
@@ -53,8 +55,8 @@ function healthSummary(bar: StatusBar | undefined): string {
53
55
  const stale = bar.tier2_stale ? "~" : "";
54
56
  return (
55
57
  `${diagnosticsPart(bar)} ` +
56
- `${stale}D${count(bar.dead_code)} U${count(bar.unused_exports)} ` +
57
- `C${count(bar.duplicates)} T${count(bar.todos)}`
58
+ `${stale}D${count(bar.dead_code, bar.disabled_categories?.includes("dead_code"))} U${count(bar.unused_exports, bar.disabled_categories?.includes("unused_exports"))} ` +
59
+ `C${count(bar.duplicates, bar.disabled_categories?.includes("duplicates"))} T${count(bar.todos, bar.disabled_categories?.includes("todos"))}`
58
60
  );
59
61
  }
60
62
 
@@ -246,6 +246,28 @@ function AftStatusCommands(props) {
246
246
  }));
247
247
  return null;
248
248
  }
249
+
250
+ /** How long the built-in tool overlap toast stays up: long enough to read the command. */
251
+ const HOST_TOOL_OVERLAP_TOAST_MS = 15_000;
252
+
253
+ /**
254
+ * Show the server's one-off built-in tool overlap notice as a toast. A toast
255
+ * reaches the person at the terminal and nothing else; the server sends this
256
+ * at most once per host process, so every TUI subscribed at that moment shows
257
+ * it once. A host without a toast surface shows nothing.
258
+ */
259
+ export function subscribeV2HostToolOverlapToast(context, rpc, options) {
260
+ return rpc.events.on("hostToolOverlap", event => {
261
+ const toast = context.ui.toast;
262
+ if (typeof toast?.show !== "function") return;
263
+ toast.show({
264
+ title: "AFT",
265
+ message: event.data.message,
266
+ variant: "warning",
267
+ duration: HOST_TOOL_OVERLAP_TOAST_MS
268
+ });
269
+ }, options);
270
+ }
249
271
  export async function setupV2Tui(context) {
250
272
  const rpc = context.client.rpc(AftRpc);
251
273
  const controller = new AbortController();
@@ -282,9 +304,13 @@ export async function setupV2Tui(context) {
282
304
  const stopDialogEvents = rpc.events.on("showStatusDialog", event => showStatusDialog(context, rpc, event.data.sessionID ?? activeSessionID(context)), {
283
305
  signal: controller.signal
284
306
  });
307
+ const stopOverlapToasts = subscribeV2HostToolOverlapToast(context, rpc, {
308
+ signal: controller.signal
309
+ });
285
310
  return () => {
286
311
  controller.abort();
287
312
  stopDialogEvents();
313
+ stopOverlapToasts();
288
314
  for (const cleanup of slotCleanups) cleanup();
289
315
  };
290
316
  }