klaude-code 1.2.14__py3-none-any.whl → 1.2.16__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.
- klaude_code/cli/main.py +66 -42
- klaude_code/cli/runtime.py +24 -13
- klaude_code/command/export_cmd.py +2 -2
- klaude_code/command/prompt-handoff.md +33 -0
- klaude_code/command/thinking_cmd.py +6 -2
- klaude_code/config/config.py +5 -5
- klaude_code/config/list_model.py +1 -1
- klaude_code/const/__init__.py +3 -0
- klaude_code/core/executor.py +2 -2
- klaude_code/core/manager/llm_clients_builder.py +1 -1
- klaude_code/core/manager/sub_agent_manager.py +30 -6
- klaude_code/core/prompt.py +15 -13
- klaude_code/core/prompts/{prompt-subagent-explore.md → prompt-sub-agent-explore.md} +0 -1
- klaude_code/core/prompts/{prompt-subagent-oracle.md → prompt-sub-agent-oracle.md} +1 -1
- klaude_code/core/reminders.py +75 -32
- klaude_code/core/task.py +10 -22
- klaude_code/core/tool/__init__.py +2 -0
- klaude_code/core/tool/report_back_tool.py +58 -0
- klaude_code/core/tool/sub_agent_tool.py +6 -0
- klaude_code/core/tool/tool_runner.py +9 -1
- klaude_code/core/turn.py +45 -4
- klaude_code/llm/anthropic/input.py +14 -5
- klaude_code/llm/input_common.py +1 -1
- klaude_code/llm/openrouter/input.py +14 -3
- klaude_code/llm/responses/input.py +19 -0
- klaude_code/protocol/events.py +1 -0
- klaude_code/protocol/model.py +24 -14
- klaude_code/protocol/sub_agent/__init__.py +117 -0
- klaude_code/protocol/sub_agent/explore.py +63 -0
- klaude_code/protocol/sub_agent/oracle.py +91 -0
- klaude_code/protocol/sub_agent/task.py +61 -0
- klaude_code/protocol/sub_agent/web_fetch.py +74 -0
- klaude_code/protocol/tools.py +1 -0
- klaude_code/session/export.py +12 -6
- klaude_code/session/session.py +12 -2
- klaude_code/session/templates/export_session.html +20 -24
- klaude_code/ui/modes/repl/completers.py +1 -1
- klaude_code/ui/modes/repl/event_handler.py +34 -3
- klaude_code/ui/modes/repl/renderer.py +9 -9
- klaude_code/ui/renderers/developer.py +18 -7
- klaude_code/ui/renderers/metadata.py +57 -84
- klaude_code/ui/renderers/sub_agent.py +59 -3
- klaude_code/ui/renderers/thinking.py +3 -3
- klaude_code/ui/renderers/tools.py +67 -30
- klaude_code/ui/rich/markdown.py +45 -57
- klaude_code/ui/rich/status.py +32 -14
- klaude_code/ui/rich/theme.py +18 -17
- {klaude_code-1.2.14.dist-info → klaude_code-1.2.16.dist-info}/METADATA +3 -2
- {klaude_code-1.2.14.dist-info → klaude_code-1.2.16.dist-info}/RECORD +53 -47
- klaude_code/protocol/sub_agent.py +0 -354
- /klaude_code/core/prompts/{prompt-subagent-webfetch.md → prompt-sub-agent-webfetch.md} +0 -0
- /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
- {klaude_code-1.2.14.dist-info → klaude_code-1.2.16.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.14.dist-info → klaude_code-1.2.16.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from klaude_code.protocol import tools
|
|
6
|
+
from klaude_code.protocol.sub_agent import SubAgentProfile, register_sub_agent
|
|
7
|
+
|
|
8
|
+
ORACLE_DESCRIPTION = """\
|
|
9
|
+
Consult the Oracle - an AI advisor powered by OpenAI's premium reasoning model that can plan, review, and provide expert guidance.
|
|
10
|
+
|
|
11
|
+
The Oracle has access to the following tools: Read, Bash.
|
|
12
|
+
|
|
13
|
+
The Oracle acts as your senior engineering advisor and can help with:
|
|
14
|
+
|
|
15
|
+
WHEN TO USE THE ORACLE:
|
|
16
|
+
- Code reviews and architecture feedback
|
|
17
|
+
- Finding a bug in multiple files
|
|
18
|
+
- Planning complex implementations or refactoring
|
|
19
|
+
- Analyzing code quality and suggesting improvements
|
|
20
|
+
- Answering complex technical questions that require deep reasoning
|
|
21
|
+
|
|
22
|
+
WHEN NOT TO USE THE ORACLE:
|
|
23
|
+
- Simple file reading or searching tasks (use Read or Grep directly)
|
|
24
|
+
- Codebase searches (use Task)
|
|
25
|
+
- Basic code modifications and when you need to execute code changes (do it yourself or use Task)
|
|
26
|
+
|
|
27
|
+
USAGE GUIDELINES:
|
|
28
|
+
1. Be specific about what you want the Oracle to review, plan, or debug
|
|
29
|
+
2. Provide relevant context about what you're trying to achieve. If you know that any files are involved, list them and they will be attached.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
EXAMPLES:
|
|
33
|
+
- "Review the authentication system architecture and suggest improvements"
|
|
34
|
+
- "Plan the implementation of real-time collaboration features"
|
|
35
|
+
- "Analyze the performance bottlenecks in the data processing pipeline"
|
|
36
|
+
- "Review this API design and suggest better patterns"\
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
ORACLE_PARAMETERS = {
|
|
40
|
+
"properties": {
|
|
41
|
+
"context": {
|
|
42
|
+
"description": "Optional context about the current situation, what you've tried, or background information that would help the Oracle provide better guidance.",
|
|
43
|
+
"type": "string",
|
|
44
|
+
},
|
|
45
|
+
"files": {
|
|
46
|
+
"description": "Optional list of specific file paths (text files, images) that the Oracle should examine as part of its analysis. These files will be attached to the Oracle input.",
|
|
47
|
+
"items": {"type": "string"},
|
|
48
|
+
"type": "array",
|
|
49
|
+
},
|
|
50
|
+
"task": {
|
|
51
|
+
"description": "The task or question you want the Oracle to help with. Be specific about what kind of guidance, review, or planning you need.",
|
|
52
|
+
"type": "string",
|
|
53
|
+
},
|
|
54
|
+
"description": {
|
|
55
|
+
"description": "A short (3-5 word) description of the task",
|
|
56
|
+
"type": "string",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
"required": ["task", "description"],
|
|
60
|
+
"type": "object",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _oracle_prompt_builder(args: dict[str, Any]) -> str:
|
|
65
|
+
"""Build the Oracle prompt from tool arguments."""
|
|
66
|
+
context = args.get("context", "")
|
|
67
|
+
task = args.get("task", "")
|
|
68
|
+
files = args.get("files", [])
|
|
69
|
+
|
|
70
|
+
prompt = f"""\
|
|
71
|
+
Context: {context}
|
|
72
|
+
Task: {task}\
|
|
73
|
+
"""
|
|
74
|
+
if files:
|
|
75
|
+
files_str = "\n".join(f"@{file}" for file in files)
|
|
76
|
+
prompt += f"\nRelated files to review:\n{files_str}"
|
|
77
|
+
return prompt
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
register_sub_agent(
|
|
81
|
+
SubAgentProfile(
|
|
82
|
+
name="Oracle",
|
|
83
|
+
description=ORACLE_DESCRIPTION,
|
|
84
|
+
parameters=ORACLE_PARAMETERS,
|
|
85
|
+
prompt_file="prompts/prompt-sub-agent-oracle.md",
|
|
86
|
+
tool_set=(tools.READ, tools.BASH),
|
|
87
|
+
prompt_builder=_oracle_prompt_builder,
|
|
88
|
+
active_form="Consulting Oracle",
|
|
89
|
+
target_model_filter=lambda model: ("gpt-5" not in model) and ("gemini-3" not in model),
|
|
90
|
+
)
|
|
91
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from klaude_code.protocol import tools
|
|
4
|
+
from klaude_code.protocol.sub_agent import SubAgentProfile, register_sub_agent
|
|
5
|
+
|
|
6
|
+
TASK_DESCRIPTION = """\
|
|
7
|
+
Launch a new agent to handle complex, multi-step tasks autonomously. \
|
|
8
|
+
|
|
9
|
+
When NOT to use the Task tool:
|
|
10
|
+
- If you want to read a specific file path, use the Read or Bash tool for `rg` instead of the Task tool, to find the match more quickly
|
|
11
|
+
- If you are searching for a specific class definition like "class Foo", use the Bash tool for `rg` instead, to find the match more quickly
|
|
12
|
+
- If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead of the Task tool, to find the match more quickly
|
|
13
|
+
- Other tasks that are not related to the agent descriptions above
|
|
14
|
+
|
|
15
|
+
Usage notes:
|
|
16
|
+
- Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
|
|
17
|
+
- When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
|
|
18
|
+
- Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
|
|
19
|
+
- The agent's outputs should generally be trusted
|
|
20
|
+
- Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, etc.), since it is not aware of the user's intent
|
|
21
|
+
- If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
|
|
22
|
+
- If the user specifies that they want you to run agents "in parallel", you MUST send a single message with multiple Task tool use content blocks. For example, if you need to launch both a code-reviewer agent and a test-runner agent in parallel, send a single message with both tool calls.
|
|
23
|
+
|
|
24
|
+
Structured output:
|
|
25
|
+
- Provide an `output_format` (JSON Schema) parameter for structured data back from the agent
|
|
26
|
+
- Example: `output_format={"type": "object", "properties": {"files": {"type": "array", "items": {"type": "string"}, "description": "List of file paths that match the search criteria, e.g. ['src/main.py', 'src/utils/helper.py']"}}, "required": ["files"]}`\
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
TASK_PARAMETERS = {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"description": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "A short (3-5 word) description of the task",
|
|
35
|
+
},
|
|
36
|
+
"prompt": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "The task for the agent to perform",
|
|
39
|
+
},
|
|
40
|
+
"output_format": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"description": (
|
|
43
|
+
"Optional JSON Schema for structured output, better with examples in argument descriptions."
|
|
44
|
+
),
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
"required": ["description", "prompt"],
|
|
48
|
+
"additionalProperties": False,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
register_sub_agent(
|
|
52
|
+
SubAgentProfile(
|
|
53
|
+
name="Task",
|
|
54
|
+
description=TASK_DESCRIPTION,
|
|
55
|
+
parameters=TASK_PARAMETERS,
|
|
56
|
+
prompt_file="prompts/prompt-sub-agent.md",
|
|
57
|
+
tool_set=(tools.BASH, tools.READ, tools.EDIT, tools.WRITE),
|
|
58
|
+
active_form="Tasking",
|
|
59
|
+
output_schema_arg="output_format",
|
|
60
|
+
)
|
|
61
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from klaude_code.protocol import tools
|
|
6
|
+
from klaude_code.protocol.sub_agent import SubAgentProfile, register_sub_agent
|
|
7
|
+
|
|
8
|
+
WEB_FETCH_AGENT_DESCRIPTION = """\
|
|
9
|
+
Launch a sub-agent to fetch and analyze web content. Use this when you need to:
|
|
10
|
+
- Retrieve and extract information from a webpage
|
|
11
|
+
- Analyze web page content based on specific instructions
|
|
12
|
+
- Get structured data from URLs
|
|
13
|
+
|
|
14
|
+
This is an autonomous agent with its own reasoning capabilities. It can:
|
|
15
|
+
- Follow links and navigate across multiple pages to gather comprehensive information
|
|
16
|
+
- Decide which related pages to visit based on the initial content
|
|
17
|
+
- Aggregate information from multiple sources into a coherent response
|
|
18
|
+
|
|
19
|
+
The agent will fetch the URL content, handle HTML-to-Markdown conversion automatically, \
|
|
20
|
+
and can use tools like rg to search through large responses that were truncated and saved to files.
|
|
21
|
+
|
|
22
|
+
Usage notes:
|
|
23
|
+
- Provide a clear prompt describing what information to extract or analyze
|
|
24
|
+
- Provide an `output_format` (JSON Schema) parameter for structured data back from the sub-agent
|
|
25
|
+
- Example: `output_format={"type": "object", "properties": {"main_content": {"type": "string", "description": "The main content extracted from the page, e.g. 'This article discusses...'"}, "key_insights": {"type": "array", "items": {"type": "string"}, "description": "Key takeaways from the content, e.g. ['Insight 1', 'Insight 2']"}}, "required": ["main_content", "key_insights"]}`
|
|
26
|
+
- The agent will return a summary of the findings
|
|
27
|
+
- For large web pages, the content may be truncated and saved to a file; the agent can search through it
|
|
28
|
+
- The agent can autonomously follow links to related pages if needed to complete the task\
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
WEB_FETCH_AGENT_PARAMETERS = {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"properties": {
|
|
34
|
+
"description": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "A short (3-5 word) description of the task",
|
|
37
|
+
},
|
|
38
|
+
"url": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "The URL to fetch and analyze",
|
|
41
|
+
},
|
|
42
|
+
"prompt": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Instructions for analyzing or extracting content from the web page",
|
|
45
|
+
},
|
|
46
|
+
"output_format": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"description": "Optional JSON Schema for sub-agent structured output",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
"required": ["description", "url", "prompt", "output_format"],
|
|
52
|
+
"additionalProperties": False,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _web_fetch_prompt_builder(args: dict[str, Any]) -> str:
|
|
57
|
+
"""Build the WebFetchAgent prompt from tool arguments."""
|
|
58
|
+
url = args.get("url", "")
|
|
59
|
+
prompt = args.get("prompt", "")
|
|
60
|
+
return f"URL to fetch: {url}\nTask: {prompt}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
register_sub_agent(
|
|
64
|
+
SubAgentProfile(
|
|
65
|
+
name="WebFetchAgent",
|
|
66
|
+
description=WEB_FETCH_AGENT_DESCRIPTION,
|
|
67
|
+
parameters=WEB_FETCH_AGENT_PARAMETERS,
|
|
68
|
+
prompt_file="prompts/prompt-sub-agent-webfetch.md",
|
|
69
|
+
tool_set=(tools.BASH, tools.READ, tools.WEB_FETCH),
|
|
70
|
+
prompt_builder=_web_fetch_prompt_builder,
|
|
71
|
+
active_form="Fetching Web",
|
|
72
|
+
output_schema_arg="output_format",
|
|
73
|
+
)
|
|
74
|
+
)
|
klaude_code/protocol/tools.py
CHANGED
klaude_code/session/export.py
CHANGED
|
@@ -299,18 +299,24 @@ def _try_render_todo_args(arguments: str) -> str | None:
|
|
|
299
299
|
|
|
300
300
|
|
|
301
301
|
def _render_sub_agent_result(content: str) -> str:
|
|
302
|
-
|
|
302
|
+
# Try to format as JSON for better readability
|
|
303
|
+
try:
|
|
304
|
+
parsed = json.loads(content)
|
|
305
|
+
formatted = "```json\n" + json.dumps(parsed, ensure_ascii=False, indent=2) + "\n```"
|
|
306
|
+
except (json.JSONDecodeError, TypeError):
|
|
307
|
+
formatted = content
|
|
308
|
+
encoded = _escape_html(formatted)
|
|
303
309
|
return (
|
|
304
|
-
f'<div class="
|
|
305
|
-
f'<div class="
|
|
310
|
+
f'<div class="sub-agent-result-container">'
|
|
311
|
+
f'<div class="sub-agent-toolbar">'
|
|
306
312
|
f'<button type="button" class="raw-toggle" aria-pressed="false" title="Toggle raw text view">Raw</button>'
|
|
307
313
|
f'<button type="button" class="copy-raw-btn" title="Copy raw content">Copy</button>'
|
|
308
314
|
f"</div>"
|
|
309
|
-
f'<div class="
|
|
310
|
-
f'<div class="
|
|
315
|
+
f'<div class="sub-agent-content">'
|
|
316
|
+
f'<div class="sub-agent-rendered markdown-content markdown-body" data-raw="{encoded}">'
|
|
311
317
|
f'<noscript><pre style="white-space: pre-wrap;">{encoded}</pre></noscript>'
|
|
312
318
|
f"</div>"
|
|
313
|
-
f'<pre class="
|
|
319
|
+
f'<pre class="sub-agent-raw">{encoded}</pre>'
|
|
314
320
|
f"</div>"
|
|
315
321
|
f"</div>"
|
|
316
322
|
)
|
klaude_code/session/session.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import ClassVar
|
|
|
7
7
|
|
|
8
8
|
from pydantic import BaseModel, Field, PrivateAttr
|
|
9
9
|
|
|
10
|
-
from klaude_code.protocol import events, model
|
|
10
|
+
from klaude_code.protocol import events, model, tools
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Session(BaseModel):
|
|
@@ -270,6 +270,7 @@ class Session(BaseModel):
|
|
|
270
270
|
seen_sub_agent_sessions: set[str] = set()
|
|
271
271
|
prev_item: model.ConversationItem | None = None
|
|
272
272
|
last_assistant_content: str = ""
|
|
273
|
+
report_back_result: str | None = None
|
|
273
274
|
yield events.TaskStartEvent(session_id=self.id, sub_agent_state=self.sub_agent_state)
|
|
274
275
|
for it in self.conversation_history:
|
|
275
276
|
if self.need_turn_start(prev_item, it):
|
|
@@ -286,6 +287,8 @@ class Session(BaseModel):
|
|
|
286
287
|
session_id=self.id,
|
|
287
288
|
)
|
|
288
289
|
case model.ToolCallItem() as tc:
|
|
290
|
+
if tc.name == tools.REPORT_BACK:
|
|
291
|
+
report_back_result = tc.arguments
|
|
289
292
|
yield events.ToolCallEvent(
|
|
290
293
|
tool_call_id=tc.call_id,
|
|
291
294
|
tool_name=tc.name,
|
|
@@ -336,7 +339,14 @@ class Session(BaseModel):
|
|
|
336
339
|
case _:
|
|
337
340
|
continue
|
|
338
341
|
prev_item = it
|
|
339
|
-
|
|
342
|
+
|
|
343
|
+
has_structured_output = report_back_result is not None
|
|
344
|
+
task_result = report_back_result if has_structured_output else last_assistant_content
|
|
345
|
+
yield events.TaskFinishEvent(
|
|
346
|
+
session_id=self.id,
|
|
347
|
+
task_result=task_result,
|
|
348
|
+
has_structured_output=has_structured_output,
|
|
349
|
+
)
|
|
340
350
|
|
|
341
351
|
def _iter_sub_agent_history(
|
|
342
352
|
self, tool_result: model.ToolResultItem, seen_sub_agent_sessions: set[str]
|
|
@@ -9,19 +9,15 @@
|
|
|
9
9
|
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22%230851b2%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><polyline points=%2216 18 22 12 16 6%22></polyline><polyline points=%228 6 2 12 8 18%22></polyline></svg>"
|
|
10
10
|
/>
|
|
11
11
|
<link
|
|
12
|
-
href="https://cdn.jsdelivr.net/npm/@fontsource/
|
|
12
|
+
href="https://cdn.jsdelivr.net/npm/@fontsource/geist-sans/latin-400.css"
|
|
13
13
|
rel="stylesheet"
|
|
14
14
|
/>
|
|
15
15
|
<link
|
|
16
|
-
href="https://cdn.jsdelivr.net/npm/@fontsource/
|
|
16
|
+
href="https://cdn.jsdelivr.net/npm/@fontsource/geist-sans/latin-500.css"
|
|
17
17
|
rel="stylesheet"
|
|
18
18
|
/>
|
|
19
19
|
<link
|
|
20
|
-
href="https://cdn.jsdelivr.net/npm/@fontsource/
|
|
21
|
-
rel="stylesheet"
|
|
22
|
-
/>
|
|
23
|
-
<link
|
|
24
|
-
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
|
|
20
|
+
href="https://cdn.jsdelivr.net/npm/@fontsource/geist-sans/latin-700.css"
|
|
25
21
|
rel="stylesheet"
|
|
26
22
|
/>
|
|
27
23
|
<style>
|
|
@@ -39,9 +35,9 @@
|
|
|
39
35
|
--bg-error: #ffebee;
|
|
40
36
|
--bg-code: #f3f3f3;
|
|
41
37
|
--fg-inline-code: #4f4fc7;
|
|
42
|
-
--font-mono:
|
|
43
|
-
--font-markdown-mono:
|
|
44
|
-
--font-markdown: "
|
|
38
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
|
39
|
+
--font-markdown-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
|
40
|
+
--font-markdown: "Geist Sans", system-ui, sans-serif;
|
|
45
41
|
--font-weight-bold: 800;
|
|
46
42
|
--font-size-xs: 13px;
|
|
47
43
|
--font-size-sm: 14px;
|
|
@@ -114,7 +110,7 @@
|
|
|
114
110
|
|
|
115
111
|
.meta-value {
|
|
116
112
|
font-family: var(--font-mono);
|
|
117
|
-
font-size: var(--font-size-
|
|
113
|
+
font-size: var(--font-size-xs);
|
|
118
114
|
color: var(--text);
|
|
119
115
|
overflow: hidden;
|
|
120
116
|
text-overflow: ellipsis;
|
|
@@ -636,26 +632,26 @@
|
|
|
636
632
|
color: var(--text-dim);
|
|
637
633
|
}
|
|
638
634
|
|
|
639
|
-
/* Sub
|
|
640
|
-
.
|
|
635
|
+
/* Sub-Agent Result */
|
|
636
|
+
.sub-agent-result-container {
|
|
641
637
|
display: flex;
|
|
642
638
|
flex-direction: column;
|
|
643
639
|
gap: 8px;
|
|
644
640
|
margin-top: 8px;
|
|
645
641
|
}
|
|
646
642
|
|
|
647
|
-
.
|
|
643
|
+
.sub-agent-toolbar {
|
|
648
644
|
display: flex;
|
|
649
645
|
justify-content: flex-end;
|
|
650
646
|
align-items: center;
|
|
651
647
|
margin-bottom: 4px;
|
|
652
648
|
}
|
|
653
649
|
|
|
654
|
-
.
|
|
650
|
+
.sub-agent-content {
|
|
655
651
|
width: 100%;
|
|
656
652
|
}
|
|
657
653
|
|
|
658
|
-
.
|
|
654
|
+
.sub-agent-raw {
|
|
659
655
|
display: none;
|
|
660
656
|
font-family: var(--font-mono);
|
|
661
657
|
font-size: var(--font-size-base);
|
|
@@ -666,10 +662,10 @@
|
|
|
666
662
|
padding: 20px;
|
|
667
663
|
}
|
|
668
664
|
|
|
669
|
-
.
|
|
665
|
+
.sub-agent-content.show-raw .sub-agent-rendered {
|
|
670
666
|
display: none;
|
|
671
667
|
}
|
|
672
|
-
.
|
|
668
|
+
.sub-agent-content.show-raw .sub-agent-raw {
|
|
673
669
|
display: block;
|
|
674
670
|
}
|
|
675
671
|
|
|
@@ -1199,7 +1195,7 @@
|
|
|
1199
1195
|
mermaid.initialize({
|
|
1200
1196
|
startOnLoad: true,
|
|
1201
1197
|
theme: "neutral",
|
|
1202
|
-
fontFamily: '
|
|
1198
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace',
|
|
1203
1199
|
});
|
|
1204
1200
|
</script>
|
|
1205
1201
|
<script>
|
|
@@ -1301,17 +1297,17 @@
|
|
|
1301
1297
|
});
|
|
1302
1298
|
});
|
|
1303
1299
|
|
|
1304
|
-
//
|
|
1300
|
+
// Sub-agent raw toggle
|
|
1305
1301
|
document
|
|
1306
|
-
.querySelectorAll(".
|
|
1302
|
+
.querySelectorAll(".sub-agent-result-container")
|
|
1307
1303
|
.forEach((group) => {
|
|
1308
1304
|
const toggle = group.querySelector(".raw-toggle");
|
|
1309
1305
|
const copyBtn = group.querySelector(".copy-raw-btn");
|
|
1310
|
-
const block = group.querySelector(".
|
|
1306
|
+
const block = group.querySelector(".sub-agent-content");
|
|
1311
1307
|
const rendered = block
|
|
1312
|
-
? block.querySelector(".
|
|
1308
|
+
? block.querySelector(".sub-agent-rendered")
|
|
1313
1309
|
: null;
|
|
1314
|
-
const raw = block ? block.querySelector(".
|
|
1310
|
+
const raw = block ? block.querySelector(".sub-agent-raw") : null;
|
|
1315
1311
|
|
|
1316
1312
|
// Copy button logic
|
|
1317
1313
|
if (copyBtn && rendered) {
|
|
@@ -85,7 +85,7 @@ class _SlashCommandCompleter(Completer):
|
|
|
85
85
|
matched: list[tuple[str, object, str]] = []
|
|
86
86
|
for cmd_name, cmd_obj in commands.items():
|
|
87
87
|
if cmd_name.startswith(frag):
|
|
88
|
-
hint = " [
|
|
88
|
+
hint = " [instructions]" if cmd_obj.support_addition_params else ""
|
|
89
89
|
matched.append((cmd_name, cmd_obj, hint))
|
|
90
90
|
|
|
91
91
|
if not matched:
|
|
@@ -9,7 +9,7 @@ from klaude_code import const
|
|
|
9
9
|
from klaude_code.protocol import events
|
|
10
10
|
from klaude_code.ui.core.stage_manager import Stage, StageManager
|
|
11
11
|
from klaude_code.ui.modes.repl.renderer import REPLRenderer
|
|
12
|
-
from klaude_code.ui.rich.markdown import MarkdownStream
|
|
12
|
+
from klaude_code.ui.rich.markdown import MarkdownStream, ThinkingMarkdown
|
|
13
13
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
14
14
|
from klaude_code.ui.terminal.notifier import Notification, NotificationType, TerminalNotifier
|
|
15
15
|
from klaude_code.ui.terminal.progress_bar import OSC94States, emit_osc94
|
|
@@ -121,7 +121,7 @@ class ActivityState:
|
|
|
121
121
|
for name, count in self._tool_calls.items():
|
|
122
122
|
if not first:
|
|
123
123
|
activity_text.append(", ")
|
|
124
|
-
activity_text.append(name
|
|
124
|
+
activity_text.append(name)
|
|
125
125
|
if count > 1:
|
|
126
126
|
activity_text.append(f" x {count}")
|
|
127
127
|
first = False
|
|
@@ -175,6 +175,10 @@ class SpinnerStatusState:
|
|
|
175
175
|
"""Clear activity state for a new turn."""
|
|
176
176
|
self._activity.reset()
|
|
177
177
|
|
|
178
|
+
def get_activity_text(self) -> Text | None:
|
|
179
|
+
"""Get current activity text. Returns None if idle."""
|
|
180
|
+
return self._activity.get_activity_text()
|
|
181
|
+
|
|
178
182
|
def get_status(self) -> Text:
|
|
179
183
|
"""Get current spinner status as rich Text."""
|
|
180
184
|
activity_text = self._activity.get_activity_text()
|
|
@@ -318,6 +322,7 @@ class DisplayEventHandler:
|
|
|
318
322
|
console=self.renderer.console,
|
|
319
323
|
spinner=self.renderer.spinner_renderable(),
|
|
320
324
|
indent=2,
|
|
325
|
+
markdown_class=ThinkingMarkdown,
|
|
321
326
|
)
|
|
322
327
|
self.thinking_stream.start(mdstream)
|
|
323
328
|
self.renderer.spinner_stop()
|
|
@@ -376,6 +381,7 @@ class DisplayEventHandler:
|
|
|
376
381
|
self.spinner_status.set_composing(False)
|
|
377
382
|
self._update_spinner()
|
|
378
383
|
await self.stage_manager.transition_to(Stage.WAITING)
|
|
384
|
+
self.renderer.print()
|
|
379
385
|
self.renderer.spinner_start()
|
|
380
386
|
|
|
381
387
|
def _on_tool_call_start(self, event: events.TurnToolCallStartEvent) -> None:
|
|
@@ -475,6 +481,7 @@ class DisplayEventHandler:
|
|
|
475
481
|
mdstream.update(self.thinking_stream.buffer, final=True)
|
|
476
482
|
self.thinking_stream.finish()
|
|
477
483
|
self.renderer.console.pop_theme()
|
|
484
|
+
self.renderer.print()
|
|
478
485
|
self.renderer.spinner_start()
|
|
479
486
|
|
|
480
487
|
def _maybe_notify_task_finish(self, event: events.TaskFinishEvent) -> None:
|
|
@@ -511,7 +518,31 @@ class DisplayEventHandler:
|
|
|
511
518
|
if len(todo.content) > 0:
|
|
512
519
|
status_text = todo.content
|
|
513
520
|
status_text = status_text.replace("\n", "")
|
|
514
|
-
|
|
521
|
+
max_length = self._calculate_base_status_max_length()
|
|
522
|
+
return self._truncate_status_text(status_text, max_length=max_length)
|
|
523
|
+
|
|
524
|
+
def _calculate_base_status_max_length(self) -> int:
|
|
525
|
+
"""Calculate max length for base_status based on terminal width.
|
|
526
|
+
|
|
527
|
+
Reserve space for:
|
|
528
|
+
- Spinner glyph + space: 2 chars
|
|
529
|
+
- " | " separator: 3 chars (only if activity text present)
|
|
530
|
+
- Activity text: actual length (only if present)
|
|
531
|
+
- Status hint text (esc to interrupt)
|
|
532
|
+
"""
|
|
533
|
+
terminal_width = self.renderer.console.size.width
|
|
534
|
+
|
|
535
|
+
# Base reserved space: spinner + status hint
|
|
536
|
+
reserved_space = 2 + len(const.STATUS_HINT_TEXT)
|
|
537
|
+
|
|
538
|
+
# Add space for activity text if present
|
|
539
|
+
activity_text = self.spinner_status.get_activity_text()
|
|
540
|
+
if activity_text:
|
|
541
|
+
# " | " separator + actual activity text length
|
|
542
|
+
reserved_space += 3 + len(activity_text.plain)
|
|
543
|
+
|
|
544
|
+
max_length = max(10, terminal_width - reserved_space)
|
|
545
|
+
return max_length
|
|
515
546
|
|
|
516
547
|
def _truncate_status_text(self, text: str, max_length: int) -> str:
|
|
517
548
|
if len(text) <= max_length:
|
|
@@ -50,7 +50,7 @@ class REPLRenderer:
|
|
|
50
50
|
|
|
51
51
|
self.session_map: dict[str, SessionStatus] = {}
|
|
52
52
|
self.current_sub_agent_color: Style | None = None
|
|
53
|
-
self.
|
|
53
|
+
self.sub_agent_color_index = 0
|
|
54
54
|
|
|
55
55
|
def register_session(self, session_id: str, sub_agent_state: model.SubAgentState | None = None) -> None:
|
|
56
56
|
session_status = SessionStatus(
|
|
@@ -66,16 +66,16 @@ class REPLRenderer:
|
|
|
66
66
|
def _advance_sub_agent_color_index(self) -> None:
|
|
67
67
|
palette_size = len(self.themes.sub_agent_colors)
|
|
68
68
|
if palette_size == 0:
|
|
69
|
-
self.
|
|
69
|
+
self.sub_agent_color_index = 0
|
|
70
70
|
return
|
|
71
|
-
self.
|
|
71
|
+
self.sub_agent_color_index = (self.sub_agent_color_index + 1) % palette_size
|
|
72
72
|
|
|
73
73
|
def pick_sub_agent_color(self) -> Style:
|
|
74
74
|
self._advance_sub_agent_color_index()
|
|
75
75
|
palette = self.themes.sub_agent_colors
|
|
76
76
|
if not palette:
|
|
77
77
|
return Style()
|
|
78
|
-
return palette[self.
|
|
78
|
+
return palette[self.sub_agent_color_index]
|
|
79
79
|
|
|
80
80
|
def get_session_sub_agent_color(self, session_id: str) -> Style:
|
|
81
81
|
status = self.session_map.get(session_id)
|
|
@@ -100,9 +100,9 @@ class REPLRenderer:
|
|
|
100
100
|
if self.current_sub_agent_color:
|
|
101
101
|
if objects:
|
|
102
102
|
content = objects[0] if len(objects) == 1 else objects
|
|
103
|
-
self.console.print(Quote(content, style=self.current_sub_agent_color))
|
|
103
|
+
self.console.print(Quote(content, style=self.current_sub_agent_color), overflow="ellipsis")
|
|
104
104
|
return
|
|
105
|
-
self.console.print(*objects, style=style, end=end)
|
|
105
|
+
self.console.print(*objects, style=style, end=end, overflow="ellipsis")
|
|
106
106
|
|
|
107
107
|
def display_tool_call(self, e: events.ToolCallEvent) -> None:
|
|
108
108
|
if r_tools.is_sub_agent_tool(e.tool_name):
|
|
@@ -145,15 +145,14 @@ class REPLRenderer:
|
|
|
145
145
|
case events.AssistantMessageEvent() as e:
|
|
146
146
|
if is_sub_agent:
|
|
147
147
|
continue
|
|
148
|
-
renderable = r_assistant.render_assistant_message(
|
|
149
|
-
e.content, code_theme=self.themes.code_theme
|
|
150
|
-
)
|
|
148
|
+
renderable = r_assistant.render_assistant_message(e.content, code_theme=self.themes.code_theme)
|
|
151
149
|
if renderable is not None:
|
|
152
150
|
self.print(renderable)
|
|
153
151
|
self.print()
|
|
154
152
|
case events.ThinkingEvent() as e:
|
|
155
153
|
if is_sub_agent:
|
|
156
154
|
continue
|
|
155
|
+
self.display_thinking_prefix()
|
|
157
156
|
self.display_thinking(e.content)
|
|
158
157
|
case events.DeveloperMessageEvent() as e:
|
|
159
158
|
self.display_developer_message(e)
|
|
@@ -235,6 +234,7 @@ class REPLRenderer:
|
|
|
235
234
|
r_sub_agent.render_sub_agent_result(
|
|
236
235
|
event.task_result,
|
|
237
236
|
code_theme=self.themes.code_theme,
|
|
237
|
+
has_structured_output=event.has_structured_output,
|
|
238
238
|
)
|
|
239
239
|
)
|
|
240
240
|
|
|
@@ -67,13 +67,24 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
|
|
|
67
67
|
if e.item.at_files:
|
|
68
68
|
grid = create_grid()
|
|
69
69
|
for at_file in e.item.at_files:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
if at_file.mentioned_in:
|
|
71
|
+
grid.add_row(
|
|
72
|
+
Text(" +", style=ThemeKey.REMINDER),
|
|
73
|
+
Text.assemble(
|
|
74
|
+
(f"{at_file.operation} ", ThemeKey.REMINDER),
|
|
75
|
+
render_path(at_file.path, ThemeKey.REMINDER_BOLD),
|
|
76
|
+
(" mentioned in ", ThemeKey.REMINDER),
|
|
77
|
+
render_path(at_file.mentioned_in, ThemeKey.REMINDER_BOLD),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
grid.add_row(
|
|
82
|
+
Text(" +", style=ThemeKey.REMINDER),
|
|
83
|
+
Text.assemble(
|
|
84
|
+
(f"{at_file.operation} ", ThemeKey.REMINDER),
|
|
85
|
+
render_path(at_file.path, ThemeKey.REMINDER_BOLD),
|
|
86
|
+
),
|
|
87
|
+
)
|
|
77
88
|
parts.append(grid)
|
|
78
89
|
|
|
79
90
|
if uic := e.item.user_image_count:
|