openclacky 1.2.12 → 1.2.13

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.clacky/skills/gem-release/SKILL.md +1 -1
  3. data/.clacky/skills/gem-release/scripts/release.sh +4 -1
  4. data/CHANGELOG.md +23 -0
  5. data/lib/clacky/agent/llm_caller.rb +40 -25
  6. data/lib/clacky/agent/memory_updater.rb +12 -0
  7. data/lib/clacky/agent/skill_auto_creator.rb +7 -4
  8. data/lib/clacky/agent/skill_evolution.rb +23 -5
  9. data/lib/clacky/agent/skill_manager.rb +86 -1
  10. data/lib/clacky/agent/skill_reflector.rb +18 -23
  11. data/lib/clacky/agent.rb +9 -1
  12. data/lib/clacky/agent_config.rb +59 -15
  13. data/lib/clacky/cli.rb +55 -0
  14. data/lib/clacky/default_skills/persist-memory/SKILL.md +4 -3
  15. data/lib/clacky/default_skills/search-skills/SKILL.md +61 -0
  16. data/lib/clacky/idle_compression_timer.rb +1 -1
  17. data/lib/clacky/message_format/open_ai.rb +7 -1
  18. data/lib/clacky/openai_stream_aggregator.rb +4 -1
  19. data/lib/clacky/providers.rb +40 -12
  20. data/lib/clacky/server/http_server.rb +117 -3
  21. data/lib/clacky/server/session_registry.rb +30 -8
  22. data/lib/clacky/server/web_ui_controller.rb +24 -1
  23. data/lib/clacky/session_manager.rb +120 -0
  24. data/lib/clacky/tools/web_search.rb +59 -8
  25. data/lib/clacky/ui2/layout_manager.rb +15 -5
  26. data/lib/clacky/ui2/progress_handle.rb +7 -1
  27. data/lib/clacky/ui2/ui_controller.rb +27 -0
  28. data/lib/clacky/ui_interface.rb +22 -0
  29. data/lib/clacky/utils/model_pricing.rb +96 -0
  30. data/lib/clacky/version.rb +1 -1
  31. data/lib/clacky/web/app.css +209 -4
  32. data/lib/clacky/web/app.js +6 -5
  33. data/lib/clacky/web/i18n.js +18 -4
  34. data/lib/clacky/web/index.html +2 -1
  35. data/lib/clacky/web/sessions.js +408 -80
  36. data/lib/clacky/web/settings.js +213 -51
  37. data/lib/clacky/web/skills.js +5 -14
  38. data/lib/clacky/web/utils.js +57 -0
  39. data/lib/clacky/web/ws-dispatcher.js +136 -0
  40. metadata +4 -2
@@ -856,6 +856,43 @@ body {
856
856
  opacity: 0.5;
857
857
  }
858
858
 
859
+ .session-search-group {
860
+ padding: 0.5rem 0.75rem 0.25rem;
861
+ font-size: 0.6875rem;
862
+ font-weight: 600;
863
+ color: var(--color-text-muted);
864
+ text-transform: none;
865
+ letter-spacing: 0.02em;
866
+ }
867
+ .session-search-group + .session-search-group {
868
+ margin-top: 0.25rem;
869
+ }
870
+
871
+ .session-snippet {
872
+ white-space: nowrap;
873
+ overflow: hidden;
874
+ text-overflow: ellipsis;
875
+ font-size: 0.6875rem;
876
+ color: var(--color-text-muted);
877
+ margin-top: 2px;
878
+ line-height: 1.35;
879
+ opacity: 0.85;
880
+ }
881
+ .session-snippet mark {
882
+ background: var(--color-warning-bg, #fef3c7);
883
+ color: var(--color-text-primary);
884
+ padding: 0 2px;
885
+ border-radius: 2px;
886
+ font-weight: 600;
887
+ }
888
+
889
+ .session-name__text mark {
890
+ background: var(--color-warning-bg, #fef3c7);
891
+ color: var(--color-text-primary);
892
+ padding: 0 1px;
893
+ border-radius: 2px;
894
+ }
895
+
859
896
  /* Legacy .btn-new-inline — kept for compat, can be removed after cleanup */
860
897
  .btn-new-inline {
861
898
  display: none;
@@ -984,9 +1021,22 @@ body {
984
1021
  z-index: 1000;
985
1022
  animation: fadeIn 0.15s ease;
986
1023
  }
1024
+ .session-context-menu {
1025
+ background: var(--color-bg-secondary);
1026
+ border: 1px solid var(--color-border-primary);
1027
+ border-radius: 8px;
1028
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.04);
1029
+ padding: 0.25rem;
1030
+ min-width: 10rem;
1031
+ z-index: 1001;
1032
+ animation: fadeIn 0.15s ease;
1033
+ }
987
1034
  [data-theme="dark"] .session-actions-menu {
988
1035
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);
989
1036
  }
1037
+ [data-theme="dark"] .session-context-menu {
1038
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);
1039
+ }
990
1040
  .session-actions-menu-item {
991
1041
  display: flex;
992
1042
  align-items: center;
@@ -1622,7 +1672,7 @@ body {
1622
1672
  .sib-status-* classes instead. Legacy .status-* styles removed. */
1623
1673
 
1624
1674
  /* ── Messages ────────────────────────────────────────────────────────────── */
1625
- #messages {
1675
+ .chat-messages-scroll {
1626
1676
  flex: 1;
1627
1677
  overflow-y: auto;
1628
1678
  overflow-x: hidden;
@@ -2108,6 +2158,15 @@ body {
2108
2158
  .tu-delta-ok { color: #34d399; } /* green — normal */
2109
2159
  .tu-delta-mid { color: #f59e0b; } /* yellow — moderate */
2110
2160
  .tu-delta-high { color: #f87171; } /* red — large */
2161
+
2162
+ .tool-item .token-usage-line.tu-attached {
2163
+ align-self: stretch;
2164
+ margin: 0.25rem 0 0;
2165
+ padding: 2px 0 0;
2166
+ opacity: 0.55;
2167
+ flex-wrap: wrap;
2168
+ }
2169
+ .tool-item .token-usage-line.tu-attached:hover { opacity: 0.95; }
2111
2170
  .tu-delta-neg { color: var(--color-accent-primary); } /* cyan — compression */
2112
2171
 
2113
2172
  /* Detail fields: hidden by default, revealed on hover */
@@ -2221,6 +2280,30 @@ body {
2221
2280
  .tool-item-stdout .ansi-black { color: #5c6370; }
2222
2281
  .tool-item-stdout .ansi-bold { font-weight: 700; }
2223
2282
 
2283
+ /* ── Diff block (rendered inline within edit/write tool-item) ─────────────── */
2284
+ .tool-item-diff {
2285
+ margin: 0.25rem 0 0.25rem 1.25rem;
2286
+ padding: 0.375rem 0.5rem;
2287
+ background: var(--color-bg-secondary);
2288
+ border: 1px solid var(--color-border-secondary);
2289
+ border-radius: 4px;
2290
+ font-size: 0.6875rem;
2291
+ font-family: monospace;
2292
+ line-height: 1.5;
2293
+ max-height: 20rem;
2294
+ overflow: auto;
2295
+ }
2296
+ .diff-line {
2297
+ white-space: pre;
2298
+ padding: 0 0.25rem;
2299
+ border-radius: 2px;
2300
+ }
2301
+ .diff-line.diff-add { background: rgba(46, 160, 67, 0.18); color: #98c379; }
2302
+ .diff-line.diff-del { background: rgba(248, 81, 73, 0.18); color: #e06c75; }
2303
+ .diff-line.diff-hunk { color: var(--color-text-tertiary); font-style: italic; }
2304
+ .diff-line.diff-ctx { color: var(--color-text-secondary); }
2305
+ .diff-line.diff-more { color: var(--color-text-tertiary); font-style: italic; margin-top: 0.25rem; }
2306
+
2224
2307
  /* ── Thinking block (collapsible <think>...</think> sections) ─────────────── */
2225
2308
  .thinking-block {
2226
2309
  margin: 0 0 0.375rem 0;
@@ -9966,6 +10049,7 @@ body.setup-mode[data-theme="dark"] {
9966
10049
  background: var(--color-bg-secondary);
9967
10050
  margin-bottom: 0.5rem;
9968
10051
  overflow: hidden;
10052
+ max-width: 48rem;
9969
10053
  }
9970
10054
  .media-row.is-expanded {
9971
10055
  background: var(--color-bg-secondary);
@@ -9988,25 +10072,28 @@ body.setup-mode[data-theme="dark"] {
9988
10072
  background: var(--color-bg-primary);
9989
10073
  border: 1px solid var(--color-border-primary);
9990
10074
  border-radius: 6px;
9991
- padding: 1px;
10075
+ padding: 2px;
9992
10076
  }
9993
10077
  .media-row-segmented button {
9994
10078
  background: transparent;
9995
10079
  border: none;
9996
- padding: 0.25rem 0.625rem;
10080
+ padding: 0.3125rem 0.875rem;
9997
10081
  font-size: 0.75rem;
10082
+ font-weight: 500;
9998
10083
  color: var(--color-text-secondary);
9999
10084
  border-radius: 5px;
10000
10085
  cursor: pointer;
10001
- transition: background-color 0.15s ease, color 0.15s ease;
10086
+ transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
10002
10087
  font-family: inherit;
10003
10088
  }
10004
10089
  .media-row-segmented button:hover:not(.is-active):not(:disabled) {
10005
10090
  color: var(--color-text-primary);
10091
+ background: var(--color-bg-secondary);
10006
10092
  }
10007
10093
  .media-row-segmented button.is-active {
10008
10094
  background: var(--color-button-primary);
10009
10095
  color: #fff;
10096
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1);
10010
10097
  }
10011
10098
  .media-row-segmented button:disabled {
10012
10099
  opacity: 0.4;
@@ -10108,3 +10195,121 @@ body.setup-mode[data-theme="dark"] {
10108
10195
  justify-content: flex-end;
10109
10196
  margin-top: 0.25rem;
10110
10197
  }
10198
+
10199
+ .media-auto-grid {
10200
+ display: grid;
10201
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
10202
+ gap: 0.625rem 1rem;
10203
+ }
10204
+ .media-provider-line {
10205
+ display: flex;
10206
+ align-items: baseline;
10207
+ gap: 0.5rem;
10208
+ font-size: 0.8125rem;
10209
+ margin-bottom: 0.625rem;
10210
+ }
10211
+ .media-provider-label {
10212
+ color: var(--color-text-secondary);
10213
+ }
10214
+ .media-provider-value {
10215
+ color: var(--color-text-primary);
10216
+ font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
10217
+ }
10218
+ .media-auto-readonly {
10219
+ background: var(--color-bg-primary);
10220
+ border: 1px solid var(--color-border-primary);
10221
+ border-radius: 6px;
10222
+ padding: 0.4375rem 0.625rem;
10223
+ font-size: 0.8125rem;
10224
+ color: var(--color-text-primary);
10225
+ font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
10226
+ word-break: break-all;
10227
+ min-height: 1.125rem;
10228
+ line-height: 1.4;
10229
+ }
10230
+ .media-custom-fields {
10231
+ display: grid;
10232
+ grid-template-columns: 1fr;
10233
+ gap: 0.625rem;
10234
+ }
10235
+ .media-custom-readonly-list {
10236
+ display: flex;
10237
+ flex-direction: column;
10238
+ gap: 0.375rem;
10239
+ }
10240
+ .media-custom-readonly-row {
10241
+ display: flex;
10242
+ align-items: baseline;
10243
+ gap: 0.5rem;
10244
+ font-size: 0.8125rem;
10245
+ line-height: 1.4;
10246
+ }
10247
+ .media-custom-readonly-label {
10248
+ flex: 0 0 auto;
10249
+ min-width: 4.5rem;
10250
+ color: var(--color-text-secondary);
10251
+ }
10252
+ .media-custom-readonly-value {
10253
+ flex: 1 1 auto;
10254
+ color: var(--color-text-primary);
10255
+ font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
10256
+ word-break: break-all;
10257
+ }
10258
+ .media-row-btn-sm {
10259
+ font-size: 0.75rem;
10260
+ padding: 0.3125rem 0.875rem;
10261
+ }
10262
+ .media-row-detail .model-test-result {
10263
+ margin-top: 0.5rem;
10264
+ min-height: 1rem;
10265
+ }
10266
+ .media-row-detail .media-row-actions {
10267
+ margin-top: 0.625rem;
10268
+ }
10269
+
10270
+ /* ── Phase grouping (folded subagent runs like skill evolution) ────────── */
10271
+ .msg-phase {
10272
+ align-self: stretch;
10273
+ margin: 0.25rem 0;
10274
+ border: 1px dashed var(--color-border-primary);
10275
+ border-radius: 8px;
10276
+ background: var(--color-bg-secondary);
10277
+ font-size: 0.8125rem;
10278
+ }
10279
+ .msg-phase-summary {
10280
+ cursor: pointer;
10281
+ padding: 0.375rem 0.625rem;
10282
+ color: var(--color-text-secondary);
10283
+ list-style: none;
10284
+ user-select: none;
10285
+ display: flex;
10286
+ align-items: center;
10287
+ gap: 0.375rem;
10288
+ }
10289
+ .msg-phase-summary::-webkit-details-marker { display: none; }
10290
+ .msg-phase-summary::before {
10291
+ content: "\25B8";
10292
+ font-size: 0.6875rem;
10293
+ transition: transform 0.15s;
10294
+ }
10295
+ .msg-phase[open] .msg-phase-summary::before { transform: rotate(90deg); }
10296
+ .msg-phase-icon { font-size: 0.875rem; }
10297
+ .msg-phase-label { font-weight: 500; }
10298
+ .msg-phase-status { opacity: 0.7; font-size: 0.75rem; }
10299
+ .msg-phase-status-incomplete { color: var(--color-warning, #c97f00); }
10300
+ .msg-phase-body {
10301
+ padding: 0.625rem 0.75rem 0.75rem;
10302
+ border-top: 1px dashed var(--color-border-primary);
10303
+ display: flex;
10304
+ flex-direction: column;
10305
+ gap: 0.625rem;
10306
+ }
10307
+ .msg-phase-body .msg { opacity: 0.9; }
10308
+
10309
+ .msg-phase-empty {
10310
+ opacity: 0.55;
10311
+ border-style: dotted;
10312
+ }
10313
+ .msg-phase-empty .msg-phase-summary { cursor: default; }
10314
+ .msg-phase-empty .msg-phase-summary::before { visibility: hidden; }
10315
+ .msg-phase-empty .msg-phase-body { display: none; }
@@ -335,15 +335,15 @@ const Modal = (() => {
335
335
  $("prompt-modal-ok").onclick = null;
336
336
  $("prompt-modal-cancel").onclick = null;
337
337
  input.onkeydown = null;
338
+ unbindEnter();
338
339
  resolve(result);
339
340
  };
340
341
 
341
342
  $("prompt-modal-ok").onclick = () => cleanup(input.value.trim() || null);
342
343
  $("prompt-modal-cancel").onclick = () => cleanup(null);
343
-
344
- // Support Enter to confirm, Escape to cancel
344
+
345
+ const unbindEnter = IME.bindEnter(input, () => cleanup(input.value.trim() || null));
345
346
  input.onkeydown = (e) => {
346
- if (e.key === "Enter") cleanup(input.value.trim() || null);
347
347
  if (e.key === "Escape") cleanup(null);
348
348
  };
349
349
  });
@@ -369,6 +369,7 @@ const Modal = (() => {
369
369
  $("rename-modal-overlay").onclick = null;
370
370
  input.onkeydown = null;
371
371
  input.oninput = null;
372
+ unbindEnter();
372
373
  resolve(result);
373
374
  };
374
375
 
@@ -386,9 +387,9 @@ const Modal = (() => {
386
387
 
387
388
  $("rename-modal-save").onclick = saveHandler;
388
389
  $("rename-modal-cancel").onclick = () => cleanup(null);
389
-
390
+
391
+ const unbindEnter = IME.bindEnter(input, saveHandler);
390
392
  input.onkeydown = (e) => {
391
- if (e.key === "Enter") { e.preventDefault(); saveHandler(); }
392
393
  if (e.key === "Escape") cleanup(null);
393
394
  };
394
395
 
@@ -69,6 +69,9 @@ const I18n = (() => {
69
69
  // ── Session list ──
70
70
  "sessions.empty": "No sessions yet",
71
71
  "sessions.confirmDelete": "Delete session {{name}}? This cannot be undone.",
72
+ "sessions.search.byName": "Title matches ({{n}})",
73
+ "sessions.search.byContent": "Content matches ({{n}})",
74
+ "sessions.search.contentEmpty": "No content matches",
72
75
  "sessions.meta": "{{tasks}} tasks · ${{cost}}",
73
76
  "sessions.metaTasks": "{{n}} tasks",
74
77
  "sessions.deleteTitle": "Delete session",
@@ -122,6 +125,7 @@ const I18n = (() => {
122
125
  "sessions.newSession": "+ New Session",
123
126
  "sessions.actions.pin": "Pin",
124
127
  "sessions.actions.unpin": "Unpin",
128
+ "sessions.actions.fork": "Fork",
125
129
  "sessions.actions.rename": "Rename",
126
130
  "sessions.actions.delete": "Delete",
127
131
  "sessions.newSessionAdvanced": "More Options",
@@ -515,16 +519,19 @@ const I18n = (() => {
515
519
  "settings.media.field.apiKey": "API Key",
516
520
  "settings.media.field.provider":"Provider",
517
521
  "settings.media.off.hint": "Disabled.",
518
- "settings.media.auto.followsDefault": "Follows default chat model",
522
+ "settings.media.auto.followsDefault": "Auto-enabled via your default provider",
523
+ "settings.media.auto.stale": "Selected model {{requested}} is unavailable on the current provider — using {{current}} instead.",
519
524
  "settings.media.auto.noDefaultModel": "Set a default chat model first.",
520
525
  "settings.media.auto.unsupported": "Current provider has no built-in model for this kind. Switch to Custom.",
521
526
  "settings.media.auto.comingSoon": "Not available yet — no built-in providers.",
522
- "settings.media.auto.disabledTitle": "No built-in provider available — use Custom.",
527
+ "settings.media.auto.disabledTitle": "Provider has no built-in model — use Custom.",
523
528
  "settings.media.action.edit": "Edit",
524
529
  "settings.media.action.save": "Save",
525
530
  "settings.media.action.cancel": "Cancel",
526
531
  "settings.media.action.saving": "Saving…",
527
532
  "settings.media.action.saved": "Saved",
533
+ "settings.media.action.test": "Test",
534
+ "settings.media.testing": "Testing connection…",
528
535
  "settings.media.apiKey.placeholder": "Enter API key",
529
536
  "settings.media.apiKey.required": "API key required",
530
537
  "settings.media.model.required": "Model name required",
@@ -803,6 +810,9 @@ const I18n = (() => {
803
810
  // ── Session list ──
804
811
  "sessions.empty": "暂无会话",
805
812
  "sessions.confirmDelete": "删除会话 {{name}}?此操作不可撤销。",
813
+ "sessions.search.byName": "标题匹配 ({{n}})",
814
+ "sessions.search.byContent": "内容匹配 ({{n}})",
815
+ "sessions.search.contentEmpty": "没有内容匹配",
806
816
  "sessions.meta": "{{tasks}} 个任务 · ${{cost}}",
807
817
  "sessions.metaTasks": "{{n}} 个任务",
808
818
  "sessions.deleteTitle": "删除会话",
@@ -858,6 +868,7 @@ const I18n = (() => {
858
868
  "sessions.loadMore": "加载更多会话",
859
869
  "sessions.actions.pin": "置顶",
860
870
  "sessions.actions.unpin": "取消置顶",
871
+ "sessions.actions.fork": "复制会话",
861
872
  "sessions.actions.rename": "重命名",
862
873
  "sessions.actions.delete": "删除",
863
874
  "sessions.loadingMore": "加载中…",
@@ -1248,16 +1259,19 @@ const I18n = (() => {
1248
1259
  "settings.media.field.apiKey": "API Key",
1249
1260
  "settings.media.field.provider":"服务商",
1250
1261
  "settings.media.off.hint": "已关闭。",
1251
- "settings.media.auto.followsDefault": "跟随默认聊天模型",
1262
+ "settings.media.auto.followsDefault": "该服务商已支持,已自动启用",
1263
+ "settings.media.auto.stale": "你选定的 {{requested}} 在当前服务商不可用,已临时使用 {{current}}。",
1252
1264
  "settings.media.auto.noDefaultModel": "请先设置默认聊天模型。",
1253
1265
  "settings.media.auto.unsupported": "当前服务商无内置模型,请切换到「自定义」。",
1254
1266
  "settings.media.auto.comingSoon": "暂无内置服务商,敬请期待。",
1255
- "settings.media.auto.disabledTitle": "暂无内置服务商,请使用自定义。",
1267
+ "settings.media.auto.disabledTitle": "服务商尚未配置支持,请使用自定义",
1256
1268
  "settings.media.action.edit": "编辑",
1257
1269
  "settings.media.action.save": "保存",
1258
1270
  "settings.media.action.cancel": "取消",
1259
1271
  "settings.media.action.saving": "保存中…",
1260
1272
  "settings.media.action.saved": "已保存",
1273
+ "settings.media.action.test": "测试",
1274
+ "settings.media.testing": "测试连接中…",
1261
1275
  "settings.media.apiKey.placeholder": "请输入 API Key",
1262
1276
  "settings.media.apiKey.required": "请填写 API Key",
1263
1277
  "settings.media.model.required": "请填写模型名称",
@@ -316,7 +316,7 @@
316
316
 
317
317
  <!-- Chat column (messages + info bar + input) -->
318
318
  <div id="chat-main">
319
- <div id="messages"></div>
319
+ <div id="messages" class="chat-messages-scroll"></div>
320
320
  <!-- New message notification banner -->
321
321
  <div id="new-message-banner" class="new-message-banner" style="display:none">
322
322
  <span data-i18n="chat.newMessageHint">New messages ↓</span>
@@ -1296,6 +1296,7 @@
1296
1296
  <script src="/vendor/hljs/highlight.min.js"></script>
1297
1297
  <script src="/vendor/katex/katex.min.js"></script>
1298
1298
  <script src="/vendor/katex/auto-render.min.js"></script>
1299
+ <script src="/utils.js"></script>
1299
1300
  <script src="/i18n.js"></script>
1300
1301
  <script src="/auth.js"></script>
1301
1302
  <script src="/theme.js"></script>