openclacky 1.2.6 → 1.2.8

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +34 -0
  4. data/README_CN.md +34 -0
  5. data/lib/clacky/agent/cost_tracker.rb +7 -1
  6. data/lib/clacky/agent/message_compressor.rb +2 -1
  7. data/lib/clacky/agent/message_compressor_helper.rb +6 -2
  8. data/lib/clacky/agent/session_serializer.rb +23 -4
  9. data/lib/clacky/agent.rb +46 -2
  10. data/lib/clacky/agent_config.rb +54 -6
  11. data/lib/clacky/billing/billing_store.rb +107 -3
  12. data/lib/clacky/brand_config.rb +0 -6
  13. data/lib/clacky/cli.rb +107 -1
  14. data/lib/clacky/client.rb +56 -6
  15. data/lib/clacky/default_skills/deploy/SKILL.md +2 -1
  16. data/lib/clacky/default_skills/extend-openclacky/SKILL.md +39 -0
  17. data/lib/clacky/default_skills/mcp-manager/SKILL.md +0 -7
  18. data/lib/clacky/default_skills/onboard/SKILL.md +2 -2
  19. data/lib/clacky/json_ui_controller.rb +5 -2
  20. data/lib/clacky/patch_loader.rb +282 -0
  21. data/lib/clacky/plain_ui_controller.rb +1 -1
  22. data/lib/clacky/providers.rb +11 -2
  23. data/lib/clacky/server/channel/adapters/base.rb +4 -0
  24. data/lib/clacky/server/channel/channel_manager.rb +149 -13
  25. data/lib/clacky/server/channel/channel_ui_controller.rb +4 -2
  26. data/lib/clacky/server/channel/user_adapter_loader.rb +177 -0
  27. data/lib/clacky/server/channel.rb +5 -0
  28. data/lib/clacky/server/http_server.rb +135 -14
  29. data/lib/clacky/server/scheduler.rb +1 -4
  30. data/lib/clacky/server/session_registry.rb +30 -4
  31. data/lib/clacky/server/web_ui_controller.rb +6 -3
  32. data/lib/clacky/shell_hook_loader.rb +181 -0
  33. data/lib/clacky/tools/terminal.rb +22 -26
  34. data/lib/clacky/ui2/ui_controller.rb +1 -1
  35. data/lib/clacky/ui_interface.rb +1 -1
  36. data/lib/clacky/version.rb +1 -1
  37. data/lib/clacky/web/app.css +392 -14
  38. data/lib/clacky/web/app.js +0 -1
  39. data/lib/clacky/web/billing.js +117 -22
  40. data/lib/clacky/web/i18n.js +50 -6
  41. data/lib/clacky/web/index.html +33 -0
  42. data/lib/clacky/web/sessions.js +203 -14
  43. data/lib/clacky/web/settings.js +59 -17
  44. data/lib/clacky/web/workspace.js +204 -0
  45. data/lib/clacky/web/ws-dispatcher.js +19 -3
  46. data/lib/clacky.rb +15 -0
  47. metadata +7 -2
@@ -1514,7 +1514,106 @@ body {
1514
1514
  #btn-welcome-new:hover { background: var(--color-button-primary-hover); }
1515
1515
 
1516
1516
  /* ── Chat panel ──────────────────────────────────────────────────────────── */
1517
- #chat-panel { flex: 1; display: flex; flex-direction: column; overflow: hidden; position: relative; }
1517
+ #chat-panel { flex: 1; display: flex; flex-direction: row; overflow: hidden; position: relative; }
1518
+ #chat-main { flex: 1; display: flex; flex-direction: column; overflow: hidden; position: relative; min-width: 0; }
1519
+
1520
+ /* ── Workspace panel (right file browser) ───────────────────────────────── */
1521
+ #workspace-panel {
1522
+ width: 280px;
1523
+ flex-shrink: 0;
1524
+ display: flex;
1525
+ flex-direction: column;
1526
+ border-left: 1px solid var(--color-border-primary);
1527
+ background: var(--color-bg-primary);
1528
+ overflow: hidden;
1529
+ transition: width var(--transition-base);
1530
+ }
1531
+ #workspace-panel.collapsed { width: 0; border-left: none; }
1532
+ #workspace-header {
1533
+ display: flex;
1534
+ align-items: center;
1535
+ justify-content: space-between;
1536
+ height: 2.5rem;
1537
+ min-height: 2.5rem;
1538
+ padding: 0 0.5rem 0 0.75rem;
1539
+ border-bottom: 1px solid var(--color-border-primary);
1540
+ font-size: 0.8125rem;
1541
+ font-weight: 600;
1542
+ color: var(--color-text-primary);
1543
+ }
1544
+ .workspace-header-actions { display: flex; gap: 0.125rem; }
1545
+ .workspace-icon-btn {
1546
+ display: inline-flex;
1547
+ align-items: center;
1548
+ justify-content: center;
1549
+ width: 1.75rem;
1550
+ height: 1.75rem;
1551
+ border: none;
1552
+ background: transparent;
1553
+ color: var(--color-text-secondary);
1554
+ border-radius: var(--radius-sm);
1555
+ cursor: pointer;
1556
+ transition: background-color var(--transition-base), color var(--transition-base);
1557
+ }
1558
+ .workspace-icon-btn:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
1559
+ #workspace-tree {
1560
+ flex: 1;
1561
+ overflow-y: auto;
1562
+ padding: 0.375rem 0.25rem;
1563
+ font-size: 0.8125rem;
1564
+ }
1565
+ .wt-node { user-select: none; }
1566
+ .wt-row {
1567
+ display: flex;
1568
+ align-items: center;
1569
+ gap: 0.25rem;
1570
+ padding: 0.1875rem 0.375rem;
1571
+ border-radius: var(--radius-sm);
1572
+ cursor: pointer;
1573
+ color: var(--color-text-primary);
1574
+ white-space: nowrap;
1575
+ overflow: hidden;
1576
+ }
1577
+ .wt-row:hover { background: var(--color-bg-hover); }
1578
+ .wt-caret {
1579
+ display: inline-flex;
1580
+ width: 0.875rem;
1581
+ flex-shrink: 0;
1582
+ color: var(--color-text-secondary);
1583
+ transition: transform var(--transition-base);
1584
+ }
1585
+ .wt-caret.open { transform: rotate(90deg); }
1586
+ .wt-caret.leaf { visibility: hidden; }
1587
+ .wt-icon { display: inline-flex; flex-shrink: 0; color: var(--color-text-secondary); }
1588
+ .wt-name { overflow: hidden; text-overflow: ellipsis; }
1589
+ .wt-size { margin-left: auto; padding-left: 0.5rem; font-size: 0.6875rem; color: var(--color-text-tertiary, var(--color-text-secondary)); flex-shrink: 0; }
1590
+ .wt-children { margin-left: 0.75rem; }
1591
+ .wt-empty, .wt-loading, .wt-error {
1592
+ padding: 0.375rem 0.625rem;
1593
+ font-size: 0.75rem;
1594
+ color: var(--color-text-secondary);
1595
+ }
1596
+ .wt-error { color: var(--color-danger, #d23); }
1597
+
1598
+ /* Collapsed-state opener tab — floats at the right edge of the chat area */
1599
+ #btn-workspace-open {
1600
+ position: absolute;
1601
+ top: 0.5rem;
1602
+ right: 0.5rem;
1603
+ z-index: 8;
1604
+ display: inline-flex;
1605
+ align-items: center;
1606
+ justify-content: center;
1607
+ width: 2rem;
1608
+ height: 2rem;
1609
+ border: 1px solid var(--color-border-primary);
1610
+ background: var(--color-bg-secondary);
1611
+ color: var(--color-text-secondary);
1612
+ border-radius: var(--radius-sm);
1613
+ cursor: pointer;
1614
+ transition: background-color var(--transition-base), color var(--transition-base);
1615
+ }
1616
+ #btn-workspace-open:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
1518
1617
 
1519
1618
  /* Mobile-only floating back button — replaces the old in-header back button.
1520
1619
  Hidden on desktop; mobile media query enables it. Positioned absolutely so
@@ -1971,6 +2070,14 @@ body {
1971
2070
  opacity: 0.5;
1972
2071
  cursor: not-allowed;
1973
2072
  }
2073
+ .msg-error a {
2074
+ color: var(--color-error);
2075
+ font-weight: 600;
2076
+ text-decoration: underline;
2077
+ }
2078
+ .msg-error a:hover {
2079
+ opacity: 0.8;
2080
+ }
1974
2081
  .msg-success { color: var(--color-success); align-self: flex-start; font-size: 0.8125rem; }
1975
2082
  .tool-name { color: var(--color-warning); font-weight: 600; }
1976
2083
  .progress-msg { color: var(--color-accent-primary); font-size: 0.75rem; align-self: center; }
@@ -2551,6 +2658,99 @@ body {
2551
2658
  color: var(--color-accent-primary);
2552
2659
  }
2553
2660
 
2661
+ .sib-submodel-default-tag {
2662
+ margin-left: 0.5rem;
2663
+ font-size: 0.5625rem;
2664
+ padding: 1px 0.3125rem;
2665
+ border-radius: 3px;
2666
+ background: color-mix(in srgb, var(--color-text-secondary) 15%, transparent);
2667
+ color: var(--color-text-secondary);
2668
+ opacity: 0.8;
2669
+ }
2670
+
2671
+ .sib-submodel-toggle {
2672
+ margin-left: 0.375rem;
2673
+ width: 1.125rem;
2674
+ height: 1.125rem;
2675
+ padding: 0;
2676
+ border: none;
2677
+ border-radius: 4px;
2678
+ background: transparent;
2679
+ color: var(--color-text-secondary);
2680
+ cursor: pointer;
2681
+ display: inline-flex;
2682
+ align-items: center;
2683
+ justify-content: center;
2684
+ transition: background-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
2685
+ }
2686
+ .sib-submodel-toggle:hover {
2687
+ background: color-mix(in srgb, var(--color-accent-primary) 15%, transparent);
2688
+ color: var(--color-accent-primary);
2689
+ }
2690
+ .sib-submodel-toggle[aria-expanded="true"] {
2691
+ background: color-mix(in srgb, var(--color-accent-primary) 18%, transparent);
2692
+ color: var(--color-accent-primary);
2693
+ }
2694
+ .sib-submodel-toggle[aria-expanded="true"] svg {
2695
+ transform: rotate(90deg);
2696
+ }
2697
+ .sib-submodel-toggle svg {
2698
+ transition: transform 0.18s ease;
2699
+ }
2700
+
2701
+ .sib-model-option.submodel-open {
2702
+ background: var(--color-bg-hover);
2703
+ }
2704
+
2705
+ .sib-submodel-panel {
2706
+ position: fixed;
2707
+ min-width: 11rem;
2708
+ max-width: 16rem;
2709
+ background: var(--color-bg-secondary);
2710
+ border: 1px solid var(--color-border-primary);
2711
+ border-radius: 8px;
2712
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
2713
+ z-index: 10000;
2714
+ max-height: 18.75rem;
2715
+ overflow-y: auto;
2716
+ padding: 0.25rem 0;
2717
+ }
2718
+ .sib-submodel-panel-header {
2719
+ padding: 0.375rem 0.875rem 0.4375rem;
2720
+ font-size: 0.625rem;
2721
+ text-transform: uppercase;
2722
+ letter-spacing: 0.06em;
2723
+ color: var(--color-text-secondary);
2724
+ border-bottom: 1px solid var(--color-border-primary);
2725
+ margin-bottom: 0.25rem;
2726
+ }
2727
+ .sib-submodel-row {
2728
+ padding: 0.5rem 0.875rem;
2729
+ font-size: 0.7rem;
2730
+ font-family: var(--font-mono, monospace);
2731
+ color: var(--color-text-primary);
2732
+ cursor: pointer;
2733
+ transition: background-color 0.15s ease;
2734
+ display: flex;
2735
+ align-items: center;
2736
+ justify-content: space-between;
2737
+ gap: 0.5rem;
2738
+ }
2739
+ .sib-submodel-row:hover {
2740
+ background: var(--color-bg-hover);
2741
+ color: var(--color-accent-primary);
2742
+ }
2743
+ .sib-submodel-row.current {
2744
+ background: var(--color-bg-hover);
2745
+ font-weight: 600;
2746
+ color: var(--color-accent-primary);
2747
+ }
2748
+ .sib-submodel-row-name {
2749
+ overflow: hidden;
2750
+ text-overflow: ellipsis;
2751
+ white-space: nowrap;
2752
+ }
2753
+
2554
2754
  /* ── Model switcher benchmark banner & latency column ──────────────────────
2555
2755
  The banner sits at the top of the dropdown with a subtle border so it
2556
2756
  visually separates from the scrollable model list below. The ⚡ button is
@@ -2638,6 +2838,15 @@ body {
2638
2838
  white-space: nowrap;
2639
2839
  max-width: 100%;
2640
2840
  }
2841
+ .sib-model-option .sib-model-name-override {
2842
+ font-size: 0.6875rem;
2843
+ color: var(--color-accent-primary);
2844
+ font-weight: 600;
2845
+ overflow: hidden;
2846
+ text-overflow: ellipsis;
2847
+ white-space: nowrap;
2848
+ max-width: 100%;
2849
+ }
2641
2850
  .sib-model-option .sib-model-right {
2642
2851
  display: inline-flex;
2643
2852
  align-items: center;
@@ -3691,6 +3900,33 @@ body {
3691
3900
  color: var(--color-error);
3692
3901
  background: color-mix(in srgb, var(--color-error) 8%, transparent);
3693
3902
  }
3903
+ a.btn-card-grid-action {
3904
+ text-decoration: none;
3905
+ }
3906
+ .model-card-grid-footer {
3907
+ display: flex;
3908
+ justify-content: flex-end;
3909
+ margin-top: 0.25rem;
3910
+ }
3911
+ .model-card-grid-link {
3912
+ display: inline-flex;
3913
+ align-items: center;
3914
+ gap: 0.25rem;
3915
+ font-size: 0.6875rem;
3916
+ color: var(--color-text-tertiary);
3917
+ text-decoration: none;
3918
+ padding: 0.125rem 0.25rem;
3919
+ border-radius: 4px;
3920
+ transition: color 0.15s;
3921
+ }
3922
+ .model-card-grid-link:hover {
3923
+ color: var(--color-accent-primary);
3924
+ text-decoration: underline;
3925
+ }
3926
+ .model-card-grid-link svg {
3927
+ opacity: 0.7;
3928
+ flex-shrink: 0;
3929
+ }
3694
3930
 
3695
3931
  /* ── Settings Toggle List ───────────────────────────────────────────────────── */
3696
3932
  .settings-toggle-list {
@@ -8782,11 +9018,10 @@ body.setup-mode[data-theme="dark"] {
8782
9018
  transition: height 0.2s ease;
8783
9019
  }
8784
9020
  .billing-cache-miss {
8785
- background: #3b82f6;
9021
+ background: #f59e0b;
8786
9022
  width: 100%;
8787
9023
  transition: height 0.2s ease;
8788
- }
8789
- .billing-output-bar {
9024
+ }.billing-output-bar {
8790
9025
  width: 12px;
8791
9026
  background: #10b981;
8792
9027
  border-radius: 2px 2px 0 0;
@@ -8802,10 +9037,10 @@ body.setup-mode[data-theme="dark"] {
8802
9037
  }
8803
9038
 
8804
9039
  /* Legend colors */
9040
+ .billing-legend-total { background: #6366f1; }
8805
9041
  .billing-legend-cache-hit { background: #93c5fd; }
8806
- .billing-legend-cache-miss { background: #3b82f6; }
9042
+ .billing-legend-cache-miss { background: #f59e0b; }
8807
9043
  .billing-legend-output { background: #10b981; }
8808
-
8809
9044
  /* ── Chart Tooltip ───────────────────────────────────────────────────── */
8810
9045
  .billing-chart-tooltip {
8811
9046
  display: none;
@@ -8849,10 +9084,10 @@ body.setup-mode[data-theme="dark"] {
8849
9084
  border-radius: 50%;
8850
9085
  flex-shrink: 0;
8851
9086
  }
9087
+ .tooltip-total { background: #6366f1; }
8852
9088
  .tooltip-cache-hit { background: #93c5fd; }
8853
- .tooltip-cache-miss { background: #3b82f6; }
8854
- .tooltip-output { background: #10b981; }
8855
- .tooltip-label {
9089
+ .tooltip-cache-miss { background: #f59e0b; }
9090
+ .tooltip-output { background: #10b981; }.tooltip-label {
8856
9091
  flex: 1;
8857
9092
  color: var(--color-text-secondary);
8858
9093
  }
@@ -8919,8 +9154,8 @@ body.setup-mode[data-theme="dark"] {
8919
9154
  border-radius: 3px;
8920
9155
  transition: width 0.3s ease;
8921
9156
  }
8922
- .billing-bar-prompt {
8923
- background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%);
9157
+ .billing-bar-total {
9158
+ background: linear-gradient(90deg, #6366f1 0%, #818cf8 100%);
8924
9159
  }
8925
9160
  .billing-bar-completion {
8926
9161
  background: linear-gradient(90deg, #10b981 0%, #34d399 100%);
@@ -8928,10 +9163,9 @@ body.setup-mode[data-theme="dark"] {
8928
9163
  .billing-bar-cache-read {
8929
9164
  background: linear-gradient(90deg, #93c5fd 0%, #bfdbfe 100%);
8930
9165
  }
8931
- .billing-bar-cache-write {
8932
- background: linear-gradient(90deg, #6366f1 0%, #818cf8 100%);
9166
+ .billing-bar-cache-miss {
9167
+ background: linear-gradient(90deg, #f59e0b 0%, #fbbf24 100%);
8933
9168
  }
8934
-
8935
9169
  /* ── Model List ──────────────────────────────────────────────────────── */
8936
9170
  .billing-model-list {
8937
9171
  display: flex;
@@ -9333,3 +9567,147 @@ body.setup-mode[data-theme="dark"] {
9333
9567
  color: var(--color-text-secondary);
9334
9568
  font-weight: 500;
9335
9569
  }
9570
+
9571
+ /* ── Sessions List ───────────────────────────────────────────────────── */
9572
+ .billing-sessions-row {
9573
+ margin-top: 1rem;
9574
+ }
9575
+ .billing-sessions-section {
9576
+ background: var(--color-bg-secondary);
9577
+ border: 1px solid var(--color-border-primary);
9578
+ border-radius: 12px;
9579
+ padding: 1.25rem;
9580
+ }
9581
+ .billing-sessions-section h3 {
9582
+ font-size: 0.9375rem;
9583
+ font-weight: 600;
9584
+ color: var(--color-text-primary);
9585
+ margin: 0 0 1rem 0;
9586
+ }
9587
+ .billing-sessions-empty {
9588
+ text-align: center;
9589
+ padding: 2rem;
9590
+ color: var(--color-text-secondary);
9591
+ font-size: 0.875rem;
9592
+ }
9593
+ .billing-sessions-header {
9594
+ display: grid;
9595
+ grid-template-columns: 36px 1.5fr 0.8fr 0.8fr 0.8fr 0.8fr 0.8fr 1fr;
9596
+ gap: 0.5rem;
9597
+ padding: 0.75rem 1rem;
9598
+ background: var(--color-bg-tertiary);
9599
+ border-radius: 8px;
9600
+ font-size: 0.75rem;
9601
+ font-weight: 600;
9602
+ color: var(--color-text-secondary);
9603
+ text-transform: uppercase;
9604
+ letter-spacing: 0.05em;
9605
+ margin-bottom: 0.5rem;
9606
+ }
9607
+ .billing-sessions-list {
9608
+ display: flex;
9609
+ flex-direction: column;
9610
+ gap: 0.25rem;
9611
+ }
9612
+ .billing-session-row {
9613
+ display: grid;
9614
+ grid-template-columns: 36px 1.5fr 0.8fr 0.8fr 0.8fr 0.8fr 0.8fr 1fr;
9615
+ gap: 0.5rem;
9616
+ padding: 0.75rem 1rem;
9617
+ border-radius: 8px;
9618
+ transition: background 0.15s;
9619
+ align-items: center;
9620
+ }
9621
+ .billing-session-row:hover {
9622
+ background: var(--color-bg-tertiary);
9623
+ }
9624
+ .billing-session-deleted {
9625
+ opacity: 0.7;
9626
+ border-left: 3px solid var(--color-warning);
9627
+ }
9628
+ .billing-session-deleted .billing-cell-main {
9629
+ color: var(--color-text-secondary);
9630
+ font-style: italic;
9631
+ }
9632
+ .billing-cell {
9633
+ font-size: 0.875rem;
9634
+ white-space: nowrap;
9635
+ overflow: hidden;
9636
+ text-overflow: ellipsis;
9637
+ }
9638
+ .billing-cell-index {
9639
+ font-size: 0.75rem;
9640
+ color: var(--color-text-secondary);
9641
+ text-align: center;
9642
+ }
9643
+ .billing-cell-session {
9644
+ display: flex;
9645
+ flex-direction: column;
9646
+ gap: 0.125rem;
9647
+ min-width: 0;
9648
+ }
9649
+ .billing-cell-main {
9650
+ font-weight: 500;
9651
+ color: var(--color-text-primary);
9652
+ font-family: var(--font-mono);
9653
+ white-space: nowrap;
9654
+ overflow: hidden;
9655
+ text-overflow: ellipsis;
9656
+ }
9657
+ .billing-cell-sub {
9658
+ font-size: 0.7rem;
9659
+ color: var(--color-text-secondary);
9660
+ white-space: nowrap;
9661
+ overflow: hidden;
9662
+ text-overflow: ellipsis;
9663
+ }
9664
+ .billing-cell-number {
9665
+ font-family: var(--font-mono);
9666
+ text-align: right;
9667
+ color: var(--color-text-primary);
9668
+ }
9669
+ .billing-cell-hit {
9670
+ color: #3b82f6;
9671
+ }
9672
+ .billing-cell-miss {
9673
+ color: #f59e0b;
9674
+ }
9675
+ .billing-cell-cost {
9676
+ font-family: var(--font-mono);
9677
+ font-weight: 600;
9678
+ color: var(--color-accent);
9679
+ text-align: right;
9680
+ }
9681
+ .billing-cell-time {
9682
+ font-size: 0.75rem;
9683
+ color: var(--color-text-secondary);
9684
+ }
9685
+ @media (max-width: 768px) {
9686
+ .billing-sessions-header {
9687
+ display: none;
9688
+ }
9689
+ .billing-session-row {
9690
+ grid-template-columns: 1fr 1fr;
9691
+ gap: 0.5rem;
9692
+ padding: 1rem;
9693
+ }
9694
+ .billing-cell-index {
9695
+ display: none;
9696
+ }
9697
+ .billing-cell-session {
9698
+ grid-column: 1 / -1;
9699
+ }
9700
+ .billing-cell-number {
9701
+ text-align: left;
9702
+ }
9703
+ .billing-cell-number::before {
9704
+ font-size: 0.625rem;
9705
+ color: var(--color-text-secondary);
9706
+ text-transform: uppercase;
9707
+ display: block;
9708
+ }
9709
+ .billing-cell-time {
9710
+ grid-column: 1 / -1;
9711
+ text-align: right;
9712
+ }
9713
+ }
@@ -158,7 +158,6 @@ const Router = (() => {
158
158
  }
159
159
  _setHash(`session/${id}`);
160
160
  $("chat-panel").style.display = "flex";
161
- $("chat-panel").style.flexDirection = "column";
162
161
  Sessions.updateChatHeader(s);
163
162
  Sessions.updateStatusBar(s.status);
164
163
  Sessions.updateInfoBar(s);