@cortexkit/aft-opencode 0.64.0 → 0.65.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 (37) hide show
  1. package/dist/bg-notifications.d.ts.map +1 -1
  2. package/dist/config-live-reload.d.ts +2 -14
  3. package/dist/config-live-reload.d.ts.map +1 -1
  4. package/dist/config.d.ts +4 -16
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/entry/server.js +1271 -583
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1271 -583
  9. package/dist/shared/session-directory.d.ts +7 -2
  10. package/dist/shared/session-directory.d.ts.map +1 -1
  11. package/dist/shared/status.d.ts +2 -2
  12. package/dist/shared/status.d.ts.map +1 -1
  13. package/dist/tool-registration.d.ts +1 -1
  14. package/dist/tool-registration.d.ts.map +1 -1
  15. package/dist/tools/bash.d.ts.map +1 -1
  16. package/dist/tools/bash_watch.d.ts.map +1 -1
  17. package/dist/tools/definitions/v2.d.ts +1 -1
  18. package/dist/tools/definitions/v2.d.ts.map +1 -1
  19. package/dist/tui/shared-sidebar.d.ts +4 -0
  20. package/dist/tui/shared-sidebar.d.ts.map +1 -0
  21. package/dist/v2-session-lifecycle.d.ts.map +1 -1
  22. package/dist/workflow-hints.d.ts +1 -5
  23. package/dist/workflow-hints.d.ts.map +1 -1
  24. package/package.json +14 -12
  25. package/src/shared/session-directory.ts +80 -12
  26. package/src/shared/status.ts +4 -4
  27. package/src/tui/index.tsx +7 -3
  28. package/src/tui/shared-sidebar.ts +14 -0
  29. package/src/tui/sidebar-view.tsx +256 -245
  30. package/src/tui-compiled/index.tsx +4 -3
  31. package/src/tui-compiled/shared-sidebar-host.tsx +816 -0
  32. package/src/tui-compiled/shared-sidebar-local.tsx +816 -0
  33. package/src/tui-compiled/shared-sidebar.LICENSE +21 -0
  34. package/src/tui-compiled/shared-sidebar.ts +14 -0
  35. package/src/tui-compiled/sidebar-view.tsx +183 -221
  36. package/src/tui-compiled/sidebar.tsx +1 -0
  37. package/src/tui-compiled/v2.tsx +1 -0
@@ -33,6 +33,7 @@ import {
33
33
  seedCollapsedFromPrefs,
34
34
  watchTuiPreferences,
35
35
  } from "./preferences";
36
+ import { SidebarFrame, type SidebarTheme } from "./shared-sidebar";
36
37
 
37
38
  const SINGLE_BORDER = { type: "single" } as any;
38
39
 
@@ -429,287 +430,297 @@ export const AftSidebarPanel = (props: AftSidebarPanelProps) => {
429
430
  // entry. Keep this muted and separate from the DEGRADED badge above.
430
431
  const worktreeNote = () => worktreeCacheRoleNote(s()?.cache_role);
431
432
 
432
- return (
433
- <box
434
- width="100%"
435
- flexDirection="column"
436
- border={SINGLE_BORDER}
437
- borderColor={props.palette.border}
438
- paddingTop={1}
439
- paddingBottom={1}
440
- paddingLeft={1}
441
- paddingRight={1}
442
- >
443
- {/* Header: triangle toggle + AFT badge + binary version + degraded badge.
444
- Clicking the header row collapses/expands the panel (mirrors OpenCode's
445
- native MCP sidebar section). Only interactive once initialized — the
446
- lazy-bridge placeholder has nothing to collapse. */}
447
- <box
448
- flexDirection="row"
449
- justifyContent="space-between"
450
- alignItems="center"
451
- onMouseDown={() => {
452
- if (notInitialized()) return;
453
- props.onToggleCollapsed();
454
- }}
455
- >
456
- <box flexDirection="row" alignItems="center">
457
- {/* Triangle lives inside the accent badge so the toggle reads as one
458
- unit: "▶ AFT" / "▼ AFT". Hidden pre-init (nothing to collapse). */}
459
- <box paddingLeft={1} paddingRight={1} backgroundColor={props.palette.accent}>
460
- <text fg={badgeForeground(props.palette.accent, props.palette.background)}>
461
- <b>
462
- {notInitialized() ? "" : props.collapsed ? "▶ " : "▼ "}
463
- {props.prefs.header.label}
464
- </b>
465
- </text>
466
- </box>
467
- {s()?.degraded && degradedHeaderMarker(s()?.degraded_reasons ?? []).calm && (
468
- <box marginLeft={1}>
469
- <text fg={props.palette.warning}>
470
- {degradedHeaderMarker(s()?.degraded_reasons ?? []).text}
471
- </text>
472
- </box>
473
- )}
474
- {s()?.degraded && !degradedHeaderMarker(s()?.degraded_reasons ?? []).calm && (
475
- <box
476
- paddingLeft={1}
477
- paddingRight={1}
478
- marginLeft={1}
479
- backgroundColor={props.palette.warning}
480
- >
481
- <text fg={badgeForeground(props.palette.warning, props.palette.background)}>
482
- <b>{degradedHeaderMarker(s()?.degraded_reasons ?? []).text}</b>
483
- </text>
484
- </box>
485
- )}
433
+ const headerMarkers = () => (
434
+ <>
435
+ {s()?.degraded && degradedHeaderMarker(s()?.degraded_reasons ?? []).calm && (
436
+ <box marginLeft={1}>
437
+ <text fg={props.palette.warning}>
438
+ {degradedHeaderMarker(s()?.degraded_reasons ?? []).text}
439
+ </text>
486
440
  </box>
487
- {!notInitialized() && props.prefs.header.showVersion && (
488
- <text fg={props.palette.textMuted}>v{s()?.version ?? props.pluginVersion}</text>
489
- )}
490
- </box>
441
+ )}
442
+ {s()?.degraded && !degradedHeaderMarker(s()?.degraded_reasons ?? []).calm && (
443
+ <box
444
+ paddingLeft={1}
445
+ paddingRight={1}
446
+ marginLeft={1}
447
+ backgroundColor={props.palette.warning}
448
+ >
449
+ <text fg={badgeForeground(props.palette.warning, props.palette.background)}>
450
+ <b>{degradedHeaderMarker(s()?.degraded_reasons ?? []).text}</b>
451
+ </text>
452
+ </box>
453
+ )}
454
+ </>
455
+ );
491
456
 
492
- {/* Degraded reason — explains why heavy tools (aft_search, aft_callgraph)
493
- are disabled. Surface this prominently so users know to open a real
494
- project subdirectory if they want full features. */}
457
+ const notices = () => (
458
+ <>
459
+ {/* These explanations remain visible when the metric rows are collapsed. */}
495
460
  {s()?.degraded && degradedSummary() && (
496
461
  <box marginTop={1} width="100%">
497
462
  <text fg={props.palette.warning}>⚠ {degradedSummary()}</text>
498
463
  </box>
499
464
  )}
500
-
501
465
  {!notInitialized() && worktreeNote() && (
502
466
  <box marginTop={1} width="100%">
503
467
  <text fg={props.palette.textMuted}>{worktreeNote()}</text>
504
468
  </box>
505
469
  )}
506
-
507
- {/* Lazy-bridge placeholder. AFT skips spawning the `aft` binary at
508
- plugin init to keep memory/CPU low on OpenCode Desktop sessions
509
- that have many projects pinned in the sidebar. The RPC server
510
- returns a synthetic `cache_role === "not_initialized"` snapshot
511
- until the first tool call routes through `callBridge()` and warms
512
- the bridge. Show the explanatory message instead of empty status
513
- rows so users understand why metrics are blank. */}
514
470
  {notInitialized() && (
515
471
  <box marginTop={1} width="100%">
516
472
  {s()!.config_error ? (
517
- // Config error state: no bridge will ever start in this process,
518
- // so the placeholder text would mislead; show the error instead.
519
473
  <text fg={props.palette.error}>✖ {s()!.message}</text>
520
474
  ) : (
521
475
  <text fg={props.palette.textMuted}>{s()!.message || NOT_STARTED_STATUS_TEXT}</text>
522
476
  )}
523
477
  </box>
524
478
  )}
479
+ </>
480
+ );
525
481
 
526
- {/* Collapsed view — condensed status dots + compact compression. Shown
482
+ return (
483
+ <box
484
+ width="100%"
485
+ flexDirection="column"
486
+ border={SINGLE_BORDER}
487
+ borderColor={props.palette.border}
488
+ paddingTop={1}
489
+ paddingBottom={1}
490
+ paddingLeft={1}
491
+ paddingRight={1}
492
+ >
493
+ <SidebarFrame
494
+ header={{
495
+ badge: props.prefs.header.label,
496
+ version:
497
+ !notInitialized() && props.prefs.header.showVersion
498
+ ? `v${s()?.version ?? props.pluginVersion}`
499
+ : undefined,
500
+ afterBadge: headerMarkers,
501
+ }}
502
+ accounts={[]}
503
+ theme={
504
+ {
505
+ ...props.palette,
506
+ // The shared background role is used only as the badge foreground.
507
+ background: badgeForeground(props.palette.accent, props.palette.background),
508
+ } as SidebarTheme
509
+ }
510
+ appearance={{
511
+ barWidth: 0,
512
+ barFilledChar: "",
513
+ barEmptyChar: "",
514
+ warnThreshold: 70,
515
+ errorThreshold: 90,
516
+ }}
517
+ showQuota={false}
518
+ now={0}
519
+ collapsed={props.collapsed}
520
+ collapsible={!notInitialized()}
521
+ onToggle={props.onToggleCollapsed}
522
+ collapsedSections={() => (
523
+ <>
524
+ {notices()}
525
+ {/* Collapsed view — condensed status dots + compact compression. Shown
527
526
  only when initialized AND collapsed. Three rows mirroring the section
528
527
  order of the expanded grid. */}
529
- {!notInitialized() && props.collapsed && (
530
- <box width="100%" flexDirection="column">
531
- {props.prefs.sections.searchIndex && (
532
- <CollapsedRow palette={props.palette} label="Search Index">
533
- <text fg={toneColor(props.palette, searchStatus().tone)}>●</text>
534
- </CollapsedRow>
535
- )}
536
- {props.prefs.sections.semanticIndex && (
537
- <CollapsedRow palette={props.palette} label="Semantic Index">
538
- <text fg={toneColor(props.palette, semanticStatus().tone)}>●</text>
539
- </CollapsedRow>
540
- )}
541
- {props.prefs.sections.codeHealth && collapsedHealthLights(statusBar()) && (
542
- <CollapsedRow palette={props.palette} label="Code Health">
543
- <box flexDirection="row" gap={1}>
544
- <text
545
- fg={toneColor(props.palette, collapsedHealthLights(statusBar())!.diagnostics)}
546
- >
547
- ●
548
- </text>
549
- <text fg={toneColor(props.palette, collapsedHealthLights(statusBar())!.todos)}>
550
- ●
551
- </text>
528
+ {!notInitialized() && props.collapsed && (
529
+ <box width="100%" flexDirection="column">
530
+ {props.prefs.sections.searchIndex && (
531
+ <CollapsedRow palette={props.palette} label="Search Index">
532
+ <text fg={toneColor(props.palette, searchStatus().tone)}>●</text>
533
+ </CollapsedRow>
534
+ )}
535
+ {props.prefs.sections.semanticIndex && (
536
+ <CollapsedRow palette={props.palette} label="Semantic Index">
537
+ <text fg={toneColor(props.palette, semanticStatus().tone)}>●</text>
538
+ </CollapsedRow>
539
+ )}
540
+ {props.prefs.sections.codeHealth && collapsedHealthLights(statusBar()) && (
541
+ <CollapsedRow palette={props.palette} label="Code Health">
542
+ <box flexDirection="row" gap={1}>
543
+ <text
544
+ fg={toneColor(
545
+ props.palette,
546
+ collapsedHealthLights(statusBar())!.diagnostics,
547
+ )}
548
+ >
549
+ ●
550
+ </text>
551
+ <text
552
+ fg={toneColor(props.palette, collapsedHealthLights(statusBar())!.todos)}
553
+ >
554
+ ●
555
+ </text>
556
+ </box>
557
+ </CollapsedRow>
558
+ )}
559
+ {props.prefs.sections.compression &&
560
+ collapsedCompressionValue(s()?.compression) && (
561
+ <CollapsedRow palette={props.palette} label="Compression">
562
+ <text fg={props.palette.textMuted}>
563
+ <b>{collapsedCompressionValue(s()?.compression)}</b>
564
+ </text>
565
+ </CollapsedRow>
566
+ )}
552
567
  </box>
553
- </CollapsedRow>
554
- )}
555
- {props.prefs.sections.compression && collapsedCompressionValue(s()?.compression) && (
556
- <CollapsedRow palette={props.palette} label="Compression">
557
- <text fg={props.palette.textMuted}>
558
- <b>{collapsedCompressionValue(s()?.compression)}</b>
559
- </text>
560
- </CollapsedRow>
561
- )}
562
- </box>
563
- )}
564
-
565
- {/* Search index */}
566
- {!notInitialized() && !props.collapsed && (
567
- <>
568
- {props.prefs.sections.searchIndex && (
569
- <>
570
- <SectionHeader palette={props.palette} title="Search Index" />
571
- <StatRow
572
- palette={props.palette}
573
- label="Status"
574
- value={searchStatus().label}
575
- tone={searchStatus().tone}
576
- />
577
- {(s()?.search_index?.files ?? null) != null && (
578
- <StatRow
579
- palette={props.palette}
580
- label="Files"
581
- value={formatCount(s()!.search_index.files)}
582
- tone="muted"
583
- />
584
- )}
585
- <StatRow
586
- palette={props.palette}
587
- label="Disk"
588
- value={formatBytes(trigramBytes())}
589
- tone="muted"
590
- />
591
- </>
592
- )}
568
+ )}
569
+ </>
570
+ )}
571
+ sections={() => (
572
+ <>
573
+ {notices()}
574
+ {/* Search index */}
575
+ {!notInitialized() && !props.collapsed && (
576
+ <>
577
+ {props.prefs.sections.searchIndex && (
578
+ <>
579
+ <SectionHeader palette={props.palette} title="Search Index" />
580
+ <StatRow
581
+ palette={props.palette}
582
+ label="Status"
583
+ value={searchStatus().label}
584
+ tone={searchStatus().tone}
585
+ />
586
+ {(s()?.search_index?.files ?? null) != null && (
587
+ <StatRow
588
+ palette={props.palette}
589
+ label="Files"
590
+ value={formatCount(s()!.search_index.files)}
591
+ tone="muted"
592
+ />
593
+ )}
594
+ <StatRow
595
+ palette={props.palette}
596
+ label="Disk"
597
+ value={formatBytes(trigramBytes())}
598
+ tone="muted"
599
+ />
600
+ </>
601
+ )}
593
602
 
594
- {props.prefs.sections.semanticIndex && (
595
- <>
596
- <SectionHeader palette={props.palette} title="Semantic Index" />
597
- <StatRow
598
- palette={props.palette}
599
- label="Status"
600
- value={semanticStatus().label}
601
- tone={semanticStatus().tone}
602
- />
603
- {semanticRefreshing() && (
604
- <box width="100%">
605
- <text fg={props.palette.textMuted}>{semanticRefreshing()}</text>
606
- </box>
607
- )}
608
- {/* When loading, magic-context-style progress hint helps users see
609
- background work is making progress instead of stuck. */}
610
- {s()?.semantic_index?.status === "loading" &&
611
- s()?.semantic_index?.entries_total != null &&
612
- s()!.semantic_index.entries_total! > 0 && (
613
- <StatRow
614
- palette={props.palette}
615
- label="Progress"
616
- value={`${formatCount(s()!.semantic_index.entries_done)} / ${formatCount(
617
- s()!.semantic_index.entries_total,
618
- )}`}
619
- tone="warn"
620
- />
603
+ {props.prefs.sections.semanticIndex && (
604
+ <>
605
+ <SectionHeader palette={props.palette} title="Semantic Index" />
606
+ <StatRow
607
+ palette={props.palette}
608
+ label="Status"
609
+ value={semanticStatus().label}
610
+ tone={semanticStatus().tone}
611
+ />
612
+ {semanticRefreshing() && (
613
+ <box width="100%">
614
+ <text fg={props.palette.textMuted}>{semanticRefreshing()}</text>
615
+ </box>
616
+ )}
617
+ {/* Show loading progress so background work does not look stalled. */}
618
+ {s()?.semantic_index?.status === "loading" &&
619
+ s()?.semantic_index?.entries_total != null &&
620
+ s()!.semantic_index.entries_total! > 0 && (
621
+ <StatRow
622
+ palette={props.palette}
623
+ label="Progress"
624
+ value={`${formatCount(s()!.semantic_index.entries_done)} / ${formatCount(
625
+ s()!.semantic_index.entries_total,
626
+ )}`}
627
+ tone="warn"
628
+ />
629
+ )}
630
+ {(s()?.semantic_index?.entries ?? null) != null && (
631
+ <StatRow
632
+ palette={props.palette}
633
+ label="Entries"
634
+ value={formatCount(s()!.semantic_index.entries)}
635
+ tone="muted"
636
+ />
637
+ )}
638
+ <StatRow
639
+ palette={props.palette}
640
+ label="Disk"
641
+ value={formatBytes(semanticBytes())}
642
+ tone="muted"
643
+ />
644
+ </>
621
645
  )}
622
- {(s()?.semantic_index?.entries ?? null) != null && (
623
- <StatRow
624
- palette={props.palette}
625
- label="Entries"
626
- value={formatCount(s()!.semantic_index.entries)}
627
- tone="muted"
628
- />
629
- )}
630
- <StatRow
631
- palette={props.palette}
632
- label="Disk"
633
- value={formatBytes(semanticBytes())}
634
- tone="muted"
635
- />
636
- </>
637
- )}
638
646
 
639
- {/* Human health values are optional. A category stays absent until the
647
+ {/* Human health values are optional. A category stays absent until the
640
648
  server proves it, rather than appearing as a clean zero. */}
641
- {props.prefs.sections.codeHealth && statusBar() && (
642
- <>
643
- <SectionHeader palette={props.palette} title="Code Health" />
644
- {(statusBar()!.disabled_categories ?? []).map((key) => (
645
- <StatRow
646
- palette={props.palette}
647
- label={key.replaceAll("_", " ")}
648
- value="○ off"
649
- tone="muted"
650
- />
651
- ))}
652
- {statusBar()!.errors !== undefined && (
653
- <StatRow
654
- palette={props.palette}
655
- label="Errors"
656
- value={formatCount(statusBar()!.errors)}
657
- tone={statusBar()!.errors! > 0 ? "err" : "muted"}
658
- />
659
- )}
660
- {statusBar()!.warnings !== undefined && (
661
- <StatRow
662
- palette={props.palette}
663
- label="Warnings"
664
- value={formatCount(statusBar()!.warnings)}
665
- tone={statusBar()!.warnings! > 0 ? "warn" : "muted"}
666
- />
667
- )}
668
- {statusBar()!.todos !== undefined && (
669
- <StatRow
670
- palette={props.palette}
671
- label="TODOs"
672
- value={formatCount(statusBar()!.todos)}
673
- tone="muted"
674
- />
675
- )}
676
- </>
677
- )}
649
+ {props.prefs.sections.codeHealth && statusBar() && (
650
+ <>
651
+ <SectionHeader palette={props.palette} title="Code Health" />
652
+ {(statusBar()!.disabled_categories ?? []).map((key) => (
653
+ <StatRow
654
+ palette={props.palette}
655
+ label={key.replaceAll("_", " ")}
656
+ value="○ off"
657
+ tone="muted"
658
+ />
659
+ ))}
660
+ {statusBar()!.errors !== undefined && (
661
+ <StatRow
662
+ palette={props.palette}
663
+ label="Errors"
664
+ value={formatCount(statusBar()!.errors)}
665
+ tone={statusBar()!.errors! > 0 ? "err" : "muted"}
666
+ />
667
+ )}
668
+ {statusBar()!.warnings !== undefined && (
669
+ <StatRow
670
+ palette={props.palette}
671
+ label="Warnings"
672
+ value={formatCount(statusBar()!.warnings)}
673
+ tone={statusBar()!.warnings! > 0 ? "warn" : "muted"}
674
+ />
675
+ )}
676
+ {statusBar()!.todos !== undefined && (
677
+ <StatRow
678
+ palette={props.palette}
679
+ label="TODOs"
680
+ value={formatCount(statusBar()!.todos)}
681
+ tone="muted"
682
+ />
683
+ )}
684
+ </>
685
+ )}
678
686
 
679
- {/* Compression aggregates. Tabular layout matching Search/Semantic
687
+ {/* Compression aggregates. Tabular layout matching Search/Semantic
680
688
  Index above: each scope ("Session", "Project") renders as a
681
689
  subheader followed by two StatRows (Tokens Saved, Compression
682
690
  Ratio). Keeps numbers right-aligned in the value column instead
683
691
  of jamming them after the label on the same line. */}
684
- {props.prefs.sections.compression && compressionRows().length > 0 && (
685
- <>
686
- <SectionHeader palette={props.palette} title="Compression" />
687
- {compressionRows().map((row) =>
688
- row.kind === "scope" ? (
689
- <box width="100%">
690
- <text fg={props.palette.text}>{row.label}</text>
691
- </box>
692
- ) : (
693
- <StatRow
694
- palette={props.palette}
695
- label={row.label}
696
- value={row.value}
697
- tone="muted"
698
- />
699
- ),
700
- )}
701
- </>
702
- )}
692
+ {props.prefs.sections.compression && compressionRows().length > 0 && (
693
+ <>
694
+ <SectionHeader palette={props.palette} title="Compression" />
695
+ {compressionRows().map((row) =>
696
+ row.kind === "scope" ? (
697
+ <box width="100%">
698
+ <text fg={props.palette.text}>{row.label}</text>
699
+ </box>
700
+ ) : (
701
+ <StatRow
702
+ palette={props.palette}
703
+ label={row.label}
704
+ value={row.value}
705
+ tone="muted"
706
+ />
707
+ ),
708
+ )}
709
+ </>
710
+ )}
703
711
 
704
- {/* Surface failures clearly so users know to act (install ONNX,
712
+ {/* Surface failures clearly so users know to act (install ONNX Runtime,
705
713
  fix config, etc.) rather than silently leaving the panel "off". */}
706
- {s()?.semantic_index?.status === "failed" && s()?.semantic_index?.error && (
707
- <box marginTop={1} width="100%">
708
- <text fg={props.palette.error}>⚠ {s()!.semantic_index.error}</text>
709
- </box>
710
- )}
711
- </>
712
- )}
714
+ {s()?.semantic_index?.status === "failed" && s()?.semantic_index?.error && (
715
+ <box marginTop={1} width="100%">
716
+ <text fg={props.palette.error}>⚠ {s()!.semantic_index.error}</text>
717
+ </box>
718
+ )}
719
+ </>
720
+ )}
721
+ </>
722
+ )}
723
+ />
713
724
  </box>
714
725
  );
715
726
  };
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
2
3
  import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid";
3
4
  import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid";
@@ -131,7 +132,7 @@ function statusAcceptGate(directory) {
131
132
  return isSnapshotForContext(coerceAftStatus(rec), directory, rec?.served_directory);
132
133
  };
133
134
  }
134
- const StatusDialog = props => {
135
+ export const StatusDialog = props => {
135
136
  const theme = createMemo(() => props.api.theme.current);
136
137
  const t = () => theme();
137
138
 
@@ -563,7 +564,7 @@ const StatusDialog = props => {
563
564
  },
564
565
  label: "Checkpoints",
565
566
  get value() {
566
- return String(status().session.checkpoints);
567
+ return status().session.checkpoints?.toString() ?? "—";
567
568
  }
568
569
  }), null);
569
570
  _$insert(_el$30, _$createComponent(R, {
@@ -572,7 +573,7 @@ const StatusDialog = props => {
572
573
  },
573
574
  label: "All-session checkpoints",
574
575
  get value() {
575
- return String(status().checkpoints_total);
576
+ return status().checkpoints_total?.toString() ?? "—";
576
577
  },
577
578
  tone: "muted"
578
579
  }), null);