klaude-code 2.5.1__py3-none-any.whl → 2.5.3__py3-none-any.whl

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 (58) hide show
  1. klaude_code/.DS_Store +0 -0
  2. klaude_code/cli/auth_cmd.py +2 -13
  3. klaude_code/cli/cost_cmd.py +10 -10
  4. klaude_code/cli/list_model.py +8 -0
  5. klaude_code/cli/main.py +41 -8
  6. klaude_code/cli/session_cmd.py +2 -11
  7. klaude_code/config/assets/builtin_config.yaml +45 -26
  8. klaude_code/config/config.py +30 -7
  9. klaude_code/config/model_matcher.py +3 -3
  10. klaude_code/config/sub_agent_model_helper.py +1 -1
  11. klaude_code/const.py +2 -1
  12. klaude_code/core/agent_profile.py +1 -0
  13. klaude_code/core/executor.py +4 -0
  14. klaude_code/core/loaded_skills.py +36 -0
  15. klaude_code/core/tool/context.py +1 -3
  16. klaude_code/core/tool/file/edit_tool.py +1 -1
  17. klaude_code/core/tool/file/read_tool.py +2 -2
  18. klaude_code/core/tool/file/write_tool.py +1 -1
  19. klaude_code/core/turn.py +19 -7
  20. klaude_code/llm/anthropic/client.py +97 -60
  21. klaude_code/llm/anthropic/input.py +20 -9
  22. klaude_code/llm/google/client.py +223 -148
  23. klaude_code/llm/google/input.py +44 -36
  24. klaude_code/llm/openai_compatible/stream.py +109 -99
  25. klaude_code/llm/openrouter/reasoning.py +4 -29
  26. klaude_code/llm/partial_message.py +2 -32
  27. klaude_code/llm/responses/client.py +99 -81
  28. klaude_code/llm/responses/input.py +11 -25
  29. klaude_code/llm/stream_parts.py +94 -0
  30. klaude_code/log.py +57 -0
  31. klaude_code/protocol/events/system.py +3 -0
  32. klaude_code/protocol/llm_param.py +1 -0
  33. klaude_code/session/export.py +259 -91
  34. klaude_code/session/templates/export_session.html +141 -59
  35. klaude_code/skill/.DS_Store +0 -0
  36. klaude_code/skill/assets/.DS_Store +0 -0
  37. klaude_code/skill/loader.py +1 -0
  38. klaude_code/tui/command/fork_session_cmd.py +14 -23
  39. klaude_code/tui/command/model_picker.py +2 -17
  40. klaude_code/tui/command/refresh_cmd.py +2 -0
  41. klaude_code/tui/command/resume_cmd.py +2 -18
  42. klaude_code/tui/command/sub_agent_model_cmd.py +5 -19
  43. klaude_code/tui/command/thinking_cmd.py +2 -14
  44. klaude_code/tui/components/common.py +1 -1
  45. klaude_code/tui/components/metadata.py +22 -21
  46. klaude_code/tui/components/rich/markdown.py +8 -0
  47. klaude_code/tui/components/rich/quote.py +36 -8
  48. klaude_code/tui/components/rich/theme.py +2 -0
  49. klaude_code/tui/components/welcome.py +32 -0
  50. klaude_code/tui/input/prompt_toolkit.py +3 -1
  51. klaude_code/tui/machine.py +19 -1
  52. klaude_code/tui/renderer.py +3 -4
  53. klaude_code/tui/terminal/selector.py +174 -31
  54. {klaude_code-2.5.1.dist-info → klaude_code-2.5.3.dist-info}/METADATA +1 -1
  55. {klaude_code-2.5.1.dist-info → klaude_code-2.5.3.dist-info}/RECORD +57 -53
  56. klaude_code/skill/assets/jj-workspace/SKILL.md +0 -20
  57. {klaude_code-2.5.1.dist-info → klaude_code-2.5.3.dist-info}/WHEEL +0 -0
  58. {klaude_code-2.5.1.dist-info → klaude_code-2.5.3.dist-info}/entry_points.txt +0 -0
@@ -19,7 +19,7 @@ from klaude_code.protocol.sub_agent import is_sub_agent_tool
19
19
  if TYPE_CHECKING:
20
20
  from klaude_code.session.session import Session
21
21
 
22
- _TOOL_OUTPUT_PREVIEW_LINES: Final[int] = 12
22
+ _TOOL_OUTPUT_PREVIEW_LINES: Final[int] = 8
23
23
  _MAX_FILENAME_MESSAGE_LEN: Final[int] = 50
24
24
  _IMAGE_MAX_DISPLAY_WIDTH: Final[int] = 600
25
25
 
@@ -489,9 +489,9 @@ def _render_text_block(text: str) -> str:
489
489
  full = "\n".join(escaped_lines)
490
490
 
491
491
  return (
492
- f'<div class="expandable-output expandable">'
492
+ f'<div class="expandable-output expandable" style="--preview-max-lines: {_TOOL_OUTPUT_PREVIEW_LINES};">'
493
493
  f'<div class="preview-text" style="white-space: pre-wrap; font-family: var(--font-mono);">{preview}</div>'
494
- f'<div class="expand-hint expand-text">click to expand full output ({len(lines)} lines)</div>'
494
+ f'<div class="expand-hint expand-text">click to expand full output ({len(lines)} lines; showing first {_TOOL_OUTPUT_PREVIEW_LINES})</div>'
495
495
  f'<div class="full-text" style="white-space: pre-wrap; font-family: var(--font-mono);">{full}</div>'
496
496
  f'<div class="collapse-hint">click to collapse</div>'
497
497
  f"</div>"
@@ -827,106 +827,274 @@ def _build_messages_html(
827
827
  seen_session_ids: set[str] | None = None,
828
828
  nesting_level: int = 0,
829
829
  ) -> str:
830
+ """Render session history into HTML.
831
+
832
+ This export groups the conversation into Tasks:
833
+ - A Task starts with a UserMessage.
834
+ - The Task includes all subsequent model thinking/replies/tool calls/results until the next UserMessage.
835
+ - By default, only the final assistant reply is shown; intermediate steps are folded.
836
+ - Mermaid is a special case: show the last consecutive assistant messages + Mermaid diagram result.
837
+ """
838
+
830
839
  if seen_session_ids is None:
831
840
  seen_session_ids = set()
832
841
 
842
+ renderable_items = [item for item in history if not isinstance(item, message.ToolResultMessage)]
833
843
  blocks: list[str] = []
834
- assistant_counter = 0
835
844
 
836
- renderable_items = [item for item in history if not isinstance(item, message.ToolResultMessage)]
845
+ def _render_user_message(item: message.UserMessage) -> str:
846
+ text = message.join_text_parts(item.parts)
847
+ images = _extract_image_parts(item.parts)
848
+ images_html = _render_image_parts(images)
849
+ ts_str = _format_msg_timestamp(item.created_at)
850
+ body_parts: list[str] = []
851
+ if images_html:
852
+ body_parts.append(images_html)
853
+ if text:
854
+ body_parts.append(f'<div style="white-space: pre-wrap;">{_escape_html(text)}</div>')
855
+ if not body_parts:
856
+ body_parts.append('<div style="color: var(--text-dim); font-style: italic;">(empty)</div>')
857
+ return (
858
+ f'<div class="message-group">'
859
+ f'<div class="role-label user">'
860
+ f"User"
861
+ f'<span class="timestamp">{ts_str}</span>'
862
+ f"</div>"
863
+ f'<div class="message-content user">{"".join(body_parts)}</div>'
864
+ f"</div>"
865
+ )
837
866
 
838
- for i, item in enumerate(renderable_items):
839
- if isinstance(item, message.UserMessage):
840
- text = message.join_text_parts(item.parts)
841
- images = _extract_image_parts(item.parts)
842
- images_html = _render_image_parts(images)
843
- ts_str = _format_msg_timestamp(item.created_at)
844
- body_parts: list[str] = []
845
- if images_html:
846
- body_parts.append(images_html)
847
- if text:
848
- body_parts.append(f'<div style="white-space: pre-wrap;">{_escape_html(text)}</div>')
849
- if not body_parts:
850
- body_parts.append('<div style="color: var(--text-dim); font-style: italic;">(empty)</div>')
851
- blocks.append(
852
- f'<div class="message-group">'
853
- f'<div class="role-label user">'
854
- f"User"
855
- f'<span class="timestamp">{ts_str}</span>'
856
- f"</div>"
857
- f'<div class="message-content user">{"".join(body_parts)}</div>'
858
- f"</div>"
867
+ def _render_developer_message(item: message.DeveloperMessage, next_item: message.HistoryEvent | None) -> str:
868
+ content = message.join_text_parts(item.parts)
869
+ images = _extract_image_parts(item.parts)
870
+ images_html = _render_image_parts(images)
871
+ ts_str = _format_msg_timestamp(item.created_at)
872
+
873
+ extra_class = ""
874
+ if isinstance(next_item, (message.UserMessage, message.AssistantMessage)):
875
+ extra_class = " gap-below"
876
+
877
+ detail_body = ""
878
+ if images_html:
879
+ detail_body += images_html
880
+ if content:
881
+ detail_body += f'<div style="white-space: pre-wrap;">{_escape_html(content)}</div>'
882
+ if not detail_body:
883
+ detail_body = '<div style="color: var(--text-dim); font-style: italic;">(empty)</div>'
884
+
885
+ return (
886
+ f'<details class="developer-message{extra_class}">'
887
+ f"<summary>"
888
+ f"Developer"
889
+ f'<span class="timestamp">{ts_str}</span>'
890
+ f"</summary>"
891
+ f'<div class="details-content">{detail_body}</div>'
892
+ f"</details>"
893
+ )
894
+
895
+ def _render_system_message(item: message.SystemMessage) -> str | None:
896
+ content = message.join_text_parts(item.parts)
897
+ if not content:
898
+ return None
899
+ ts_str = _format_msg_timestamp(item.created_at)
900
+ return (
901
+ f'<details class="developer-message">'
902
+ f"<summary>"
903
+ f"System"
904
+ f'<span class="timestamp">{ts_str}</span>'
905
+ f"</summary>"
906
+ f'<div class="details-content" style="white-space: pre-wrap;">{_escape_html(content)}</div>'
907
+ f"</details>"
908
+ )
909
+
910
+ def _render_task_full(task_events: list[message.HistoryEvent]) -> str:
911
+ full_parts: list[str] = []
912
+ assistant_counter = 0
913
+
914
+ for idx, event in enumerate(task_events):
915
+ next_event = task_events[idx + 1] if idx + 1 < len(task_events) else None
916
+
917
+ if isinstance(event, message.AssistantMessage):
918
+ assistant_counter += 1
919
+
920
+ thinking_text = "".join(
921
+ part.text for part in event.parts if isinstance(part, message.ThinkingTextPart)
922
+ ).strip()
923
+ if thinking_text:
924
+ full_parts.append(_render_thinking_block(thinking_text))
925
+
926
+ assistant_text = message.join_text_parts(event.parts)
927
+ assistant_images = _extract_image_parts(event.parts)
928
+ if assistant_text or assistant_images:
929
+ full_parts.append(
930
+ _render_assistant_message(
931
+ assistant_counter,
932
+ assistant_text,
933
+ event.created_at,
934
+ assistant_images,
935
+ )
936
+ )
937
+
938
+ for part in event.parts:
939
+ if isinstance(part, message.ToolCallPart):
940
+ result = tool_results.get(part.call_id)
941
+ full_parts.append(_format_tool_call(part, result, event.created_at))
942
+ if result is not None:
943
+ sub_agent_html = _render_sub_agent_session(result, seen_session_ids, nesting_level)
944
+ if sub_agent_html:
945
+ full_parts.append(sub_agent_html)
946
+ elif isinstance(event, model.TaskMetadataItem):
947
+ full_parts.append(_render_metadata_item(event))
948
+ elif isinstance(event, message.DeveloperMessage):
949
+ full_parts.append(_render_developer_message(event, next_event))
950
+ elif isinstance(event, message.SystemMessage):
951
+ rendered = _render_system_message(event)
952
+ if rendered:
953
+ full_parts.append(rendered)
954
+
955
+ return "\n".join(full_parts)
956
+
957
+ def _choose_compact_assistants(
958
+ task_events: list[message.HistoryEvent],
959
+ ) -> tuple[list[message.AssistantMessage], bool]:
960
+ # Mermaid exception: show last consecutive assistant messages + Mermaid diagram result.
961
+ tail: list[message.AssistantMessage] = []
962
+
963
+ idx = len(task_events) - 1
964
+ while idx >= 0 and isinstance(task_events[idx], model.TaskMetadataItem):
965
+ idx -= 1
966
+
967
+ while idx >= 0 and isinstance(task_events[idx], message.AssistantMessage):
968
+ tail.append(cast(message.AssistantMessage, task_events[idx]))
969
+ idx -= 1
970
+
971
+ tail.reverse()
972
+ if tail:
973
+ has_mermaid = any(
974
+ isinstance(p, message.ToolCallPart) and p.tool_name == "Mermaid" for msg in tail for p in msg.parts
859
975
  )
860
- elif isinstance(item, message.AssistantMessage):
861
- assistant_counter += 1
862
- thinking_text = "".join(part.text for part in item.parts if isinstance(part, message.ThinkingTextPart))
863
- if thinking_text:
864
- blocks.append(_render_thinking_block(thinking_text))
976
+ if has_mermaid:
977
+ return tail, True
865
978
 
866
- assistant_text = message.join_text_parts(item.parts)
867
- assistant_images = _extract_image_parts(item.parts)
979
+ for idx in range(len(task_events) - 1, -1, -1):
980
+ if isinstance(task_events[idx], message.AssistantMessage):
981
+ return [cast(message.AssistantMessage, task_events[idx])], False
982
+
983
+ return [], False
984
+
985
+ def _render_assistant_compact(assistant_messages: list[message.AssistantMessage], *, show_mermaid: bool) -> str:
986
+ parts: list[str] = []
987
+
988
+ for counter, msg in enumerate(assistant_messages, start=1):
989
+ assistant_text = message.join_text_parts(msg.parts)
990
+ assistant_images = _extract_image_parts(msg.parts)
868
991
  if assistant_text or assistant_images:
869
- blocks.append(
870
- _render_assistant_message(
871
- assistant_counter,
872
- assistant_text,
873
- item.created_at,
874
- assistant_images,
875
- )
876
- )
877
-
878
- for part in item.parts:
879
- if isinstance(part, message.ToolCallPart):
880
- result = tool_results.get(part.call_id)
881
- blocks.append(_format_tool_call(part, result, item.created_at))
882
- if result is not None:
883
- sub_agent_html = _render_sub_agent_session(result, seen_session_ids, nesting_level)
884
- if sub_agent_html:
885
- blocks.append(sub_agent_html)
886
- elif isinstance(item, model.TaskMetadataItem):
887
- blocks.append(_render_metadata_item(item))
888
- elif isinstance(item, message.DeveloperMessage):
889
- content = message.join_text_parts(item.parts)
890
- images = _extract_image_parts(item.parts)
891
- images_html = _render_image_parts(images)
892
- ts_str = _format_msg_timestamp(item.created_at)
893
-
894
- next_item = renderable_items[i + 1] if i + 1 < len(renderable_items) else None
895
- extra_class = ""
896
- if isinstance(next_item, (message.UserMessage, message.AssistantMessage)):
897
- extra_class = " gap-below"
898
-
899
- detail_body = ""
900
- if images_html:
901
- detail_body += images_html
902
- if content:
903
- detail_body += f'<div style="white-space: pre-wrap;">{_escape_html(content)}</div>'
904
- if not detail_body:
905
- detail_body = '<div style="color: var(--text-dim); font-style: italic;">(empty)</div>'
906
-
907
- blocks.append(
908
- f'<details class="developer-message{extra_class}">'
909
- f"<summary>"
910
- f"Developer"
911
- f'<span class="timestamp">{ts_str}</span>'
912
- f"</summary>"
913
- f'<div class="details-content">{detail_body}</div>'
992
+ parts.append(_render_assistant_message(counter, assistant_text, msg.created_at, assistant_images))
993
+
994
+ if show_mermaid:
995
+ # Keep Mermaid diagram interleaved where the tool call occurred.
996
+ for p in msg.parts:
997
+ if not isinstance(p, message.ToolCallPart) or p.tool_name != "Mermaid":
998
+ continue
999
+
1000
+ result = tool_results.get(p.call_id)
1001
+ ui_extra = result.ui_extra if result else None
1002
+ extras = _collect_ui_extras(ui_extra)
1003
+ mermaid_extra = next((x for x in extras if isinstance(x, model.MermaidLinkUIExtra)), None)
1004
+ mermaid_source = mermaid_extra if mermaid_extra else ui_extra
1005
+ mermaid_html = _get_mermaid_link_html(mermaid_source, p)
1006
+ if mermaid_html:
1007
+ parts.append(f'<div class="task-mermaid-result">{mermaid_html}</div>')
1008
+
1009
+ return "\n".join(parts)
1010
+
1011
+ def _count_hidden_steps(
1012
+ task_events: list[message.HistoryEvent],
1013
+ compact_assistants: list[message.AssistantMessage],
1014
+ ) -> int:
1015
+ compact_ids = {id(x) for x in compact_assistants}
1016
+ hidden_assistant_messages = sum(
1017
+ 1 for x in task_events if isinstance(x, message.AssistantMessage) and id(x) not in compact_ids
1018
+ )
1019
+
1020
+ thinking_blocks = 0
1021
+ tool_calls = 0
1022
+ other_events = 0
1023
+ for e in task_events:
1024
+ if isinstance(e, message.AssistantMessage):
1025
+ for part in e.parts:
1026
+ if isinstance(part, message.ThinkingTextPart) and part.text.strip():
1027
+ thinking_blocks += 1
1028
+ elif isinstance(part, message.ToolCallPart):
1029
+ tool_calls += 1
1030
+ elif isinstance(e, (model.TaskMetadataItem, message.DeveloperMessage, message.SystemMessage)):
1031
+ other_events += 1
1032
+
1033
+ return hidden_assistant_messages + thinking_blocks + tool_calls + other_events
1034
+
1035
+ def _render_task(user: message.UserMessage, task_events: list[message.HistoryEvent]) -> str:
1036
+ compact_assistants, show_mermaid = _choose_compact_assistants(task_events)
1037
+ hidden_steps = _count_hidden_steps(task_events, compact_assistants)
1038
+
1039
+ full_html = _render_task_full(task_events)
1040
+ compact_html = _render_assistant_compact(compact_assistants, show_mermaid=show_mermaid)
1041
+
1042
+ if not compact_html.strip() and full_html.strip():
1043
+ compact_html = '<div style="color: var(--text-dim); font-style: italic;">(no assistant message)</div>'
1044
+
1045
+ steps_details = ""
1046
+ if hidden_steps > 0 and full_html.strip():
1047
+ steps_details = (
1048
+ f'<details class="task-steps" data-step-count="{hidden_steps}">'
1049
+ f'<summary data-step-count="{hidden_steps}">Show {hidden_steps} steps</summary>'
1050
+ f'<div class="task-steps-content">{full_html}</div>'
914
1051
  f"</details>"
915
1052
  )
1053
+
1054
+ return (
1055
+ '<div class="task">'
1056
+ f"{_render_user_message(user)}"
1057
+ f"{steps_details}"
1058
+ f'<div class="task-final">{compact_html}</div>'
1059
+ "</div>"
1060
+ )
1061
+
1062
+ current_user: message.UserMessage | None = None
1063
+ current_task_events: list[message.HistoryEvent] = []
1064
+
1065
+ def _flush_task() -> None:
1066
+ nonlocal current_user, current_task_events
1067
+ if current_user is None:
1068
+ return
1069
+ blocks.append(_render_task(current_user, current_task_events))
1070
+ current_user = None
1071
+ current_task_events = []
1072
+
1073
+ for idx, item in enumerate(renderable_items):
1074
+ if isinstance(item, message.UserMessage):
1075
+ _flush_task()
1076
+ current_user = item
1077
+ current_task_events = []
1078
+ continue
1079
+
1080
+ if current_user is not None:
1081
+ current_task_events.append(item)
1082
+ continue
1083
+
1084
+ # Events before the first user message.
1085
+ next_item = renderable_items[idx + 1] if idx + 1 < len(renderable_items) else None
1086
+ if isinstance(item, message.DeveloperMessage):
1087
+ blocks.append(_render_developer_message(item, next_item))
916
1088
  elif isinstance(item, message.SystemMessage):
917
- content = message.join_text_parts(item.parts)
918
- if not content:
919
- continue
920
- ts_str = _format_msg_timestamp(item.created_at)
921
- blocks.append(
922
- f'<details class="developer-message">'
923
- f"<summary>"
924
- f"System"
925
- f'<span class="timestamp">{ts_str}</span>'
926
- f"</summary>"
927
- f'<div class="details-content" style="white-space: pre-wrap;">{_escape_html(content)}</div>'
928
- f"</details>"
929
- )
1089
+ rendered = _render_system_message(item)
1090
+ if rendered:
1091
+ blocks.append(rendered)
1092
+ elif isinstance(item, message.AssistantMessage):
1093
+ blocks.append(_render_task_full([item]))
1094
+ elif isinstance(item, model.TaskMetadataItem):
1095
+ blocks.append(_render_metadata_item(item))
1096
+
1097
+ _flush_task()
930
1098
 
931
1099
  return "\n".join(blocks)
932
1100
 
@@ -202,6 +202,86 @@
202
202
  display: block;
203
203
  }
204
204
 
205
+ /* Task: one user message + full agent run */
206
+ .task {
207
+ margin-bottom: 24px;
208
+ }
209
+
210
+ .task:last-child {
211
+ margin-bottom: 0;
212
+ }
213
+
214
+ /* Task steps: fold intermediate events by default */
215
+ details.task-steps {
216
+ margin: 6px 0 0 0;
217
+ background: transparent;
218
+ }
219
+
220
+ details.task-steps > summary {
221
+ list-style: none;
222
+ user-select: none;
223
+ cursor: pointer;
224
+ display: inline-flex;
225
+ align-items: center;
226
+ gap: 8px;
227
+ padding: 4px 10px;
228
+ border-radius: 999px;
229
+ border: 1px solid var(--border);
230
+ background: var(--bg-card);
231
+ color: var(--text-dim);
232
+ font-family: var(--font-mono);
233
+ font-size: var(--font-size-xs);
234
+ line-height: 1.2;
235
+ transition: color 0.2s, border-color 0.2s, background 0.2s;
236
+ }
237
+
238
+ details.task-steps > summary::-webkit-details-marker {
239
+ display: none;
240
+ }
241
+
242
+ details.task-steps > summary::before {
243
+ content: "▶";
244
+ font-size: 0.8em;
245
+ color: var(--accent);
246
+ transform: translateY(-0.05em);
247
+ transition: transform 0.2s;
248
+ }
249
+
250
+ details.task-steps[open] > summary {
251
+ color: var(--accent);
252
+ border-color: var(--accent);
253
+ background: var(--accent-dim);
254
+ }
255
+
256
+ details.task-steps[open] > summary::before {
257
+ transform: rotate(90deg) translateY(-0.05em);
258
+ }
259
+
260
+ details.task-steps > summary:hover {
261
+ color: var(--text);
262
+ border-color: var(--accent);
263
+ }
264
+
265
+ details.task-steps > .task-steps-content {
266
+ margin-top: 12px;
267
+ padding: 0 0 0 18px;
268
+ margin-left: 10px;
269
+ border-left: 2px solid var(--border);
270
+ }
271
+
272
+ /* When expanded, hide compact final answer to avoid duplication */
273
+ .task details.task-steps[open] ~ .task-final {
274
+ display: none;
275
+ }
276
+
277
+ .task-final {
278
+ margin-top: 12px;
279
+ }
280
+
281
+ .task-mermaid-result {
282
+ margin: 12px 0;
283
+ }
284
+
205
285
  .message-group {
206
286
  display: flex;
207
287
  flex-direction: column;
@@ -1036,6 +1116,19 @@
1036
1116
  .expandable.expanded {
1037
1117
  cursor: auto;
1038
1118
  }
1119
+
1120
+ .expandable-output {
1121
+ /* Default fallback; per-block override comes from inline style in export.py */
1122
+ --preview-max-lines: 8;
1123
+ }
1124
+ .expandable-output .preview-text {
1125
+ line-height: 1.45;
1126
+ }
1127
+ .expandable-output:not(.expanded) .preview-text {
1128
+ max-height: calc(var(--preview-max-lines) * 1.45em);
1129
+ overflow: hidden;
1130
+ }
1131
+
1039
1132
  .expandable .full-text {
1040
1133
  display: none;
1041
1134
  }
@@ -1423,6 +1516,21 @@
1423
1516
  el.textContent = el.textContent.trim();
1424
1517
  });
1425
1518
 
1519
+ // Task steps toggle label
1520
+ document.querySelectorAll("details.task-steps").forEach((details) => {
1521
+ const summary = details.querySelector("summary");
1522
+ const count = details.getAttribute("data-step-count");
1523
+ if (!summary || !count) return;
1524
+
1525
+ const setLabel = () => {
1526
+ summary.textContent =
1527
+ (details.open ? "Hide" : "Show") + " " + count + " steps";
1528
+ };
1529
+
1530
+ setLabel();
1531
+ details.addEventListener("toggle", setLabel);
1532
+ });
1533
+
1426
1534
  // Process markdown for collapsible sections
1427
1535
  // foldH2: if true, H2 headers start collapsed; otherwise all headers start open
1428
1536
  function structureMarkdown(root, foldH2 = false) {
@@ -1809,70 +1917,44 @@
1809
1917
  });
1810
1918
  }
1811
1919
 
1812
- // 2. Messages and Tools
1920
+ // 2. Tasks (User + final Assistant)
1813
1921
  const stream = document.querySelector(".message-stream");
1814
1922
  if (stream) {
1815
- // Use a Walker to find top-level relevant items if structure is complex
1816
- // But assuming flat children of message-stream based on CSS
1817
- Array.from(stream.children).forEach((child) => {
1818
- let label = null;
1819
- let idPrefix = "section";
1820
-
1821
- if (child.classList.contains("message-group")) {
1822
- const roleLabel = child.querySelector(".role-label");
1823
- if (roleLabel) {
1824
- if (roleLabel.classList.contains("user")) {
1825
- userCount++;
1826
- label = `USER $$$${userCount}`;
1827
- idPrefix = "user";
1828
- } else if (roleLabel.classList.contains("assistant")) {
1829
- assistantCount++;
1830
- label = `ASSISTANT $$$${assistantCount}`;
1831
- idPrefix = "assistant";
1832
- } else {
1833
- label = roleLabel.textContent.trim();
1834
- }
1835
- }
1836
- } else if (child.classList.contains("tool-call")) {
1837
- const toolName = child.querySelector(".tool-name");
1838
- if (toolName) {
1839
- label = toolName.textContent.trim();
1840
- idPrefix = "tool";
1841
-
1842
- // Try to extract description for Sub Agents
1843
- try {
1844
- const argsContent = child.querySelector(".tool-args-content");
1845
- if (argsContent) {
1846
- const argsText = argsContent.textContent.trim();
1847
- if (argsText.startsWith("{")) {
1848
- const args = JSON.parse(argsText);
1849
- if (
1850
- args &&
1851
- typeof args.description === "string" &&
1852
- args.description.trim()
1853
- ) {
1854
- let desc = args.description.trim();
1855
- if (desc.length > 30) {
1856
- desc = desc.substring(0, 30) + "…";
1857
- }
1858
- label = label + " - " + desc;
1859
- }
1860
- }
1861
- }
1862
- } catch (e) {
1863
- // Ignore JSON parse errors
1864
- }
1865
- } else {
1866
- label = "Tool";
1923
+ Array.from(stream.querySelectorAll(":scope > .task")).forEach((task) => {
1924
+ const userBlock = task.querySelector(":scope > .message-group");
1925
+ if (userBlock) {
1926
+ const roleLabel = userBlock.querySelector(".role-label");
1927
+ if (roleLabel && roleLabel.classList.contains("user")) {
1928
+ userCount++;
1929
+ userBlock.id =
1930
+ userBlock.id || "user-" + Math.random().toString(36).substr(2, 9);
1931
+ sections.push({
1932
+ id: userBlock.id,
1933
+ label: "USER " + userCount,
1934
+ el: userBlock,
1935
+ });
1867
1936
  }
1868
1937
  }
1869
1938
 
1870
- if (label) {
1871
- child.id =
1872
- child.id ||
1873
- `$$$${idPrefix}-$$$${Math.random().toString(36).substr(2, 9)}`;
1874
- sections.push({ id: child.id, label: label, el: child });
1875
- }
1939
+ const finalContainer = task.querySelector(":scope > .task-final");
1940
+ if (!finalContainer) return;
1941
+
1942
+ Array.from(finalContainer.querySelectorAll(":scope > .message-group")).forEach(
1943
+ (child) => {
1944
+ const roleLabel = child.querySelector(".role-label");
1945
+ if (!roleLabel || !roleLabel.classList.contains("assistant")) return;
1946
+
1947
+ assistantCount++;
1948
+ child.id =
1949
+ child.id ||
1950
+ "assistant-" + Math.random().toString(36).substr(2, 9);
1951
+ sections.push({
1952
+ id: child.id,
1953
+ label: "ASSISTANT " + assistantCount,
1954
+ el: child,
1955
+ });
1956
+ }
1957
+ );
1876
1958
  });
1877
1959
  }
1878
1960
 
Binary file
Binary file
@@ -223,6 +223,7 @@ class SkillLoader:
223
223
  f"""<skill>
224
224
  <name>{skill.name}</name>
225
225
  <description>{skill.description}</description>
226
+ <scope>{skill.location}</scope>
226
227
  <location>{skill.skill_path}</location>
227
228
  </skill>"""
228
229
  )