klaude-code 1.6.0__py3-none-any.whl → 1.7.1__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/list_model.py +55 -4
- klaude_code/cli/main.py +10 -0
- klaude_code/cli/runtime.py +2 -2
- klaude_code/cli/session_cmd.py +3 -2
- klaude_code/command/fork_session_cmd.py +7 -0
- klaude_code/config/assets/builtin_config.yaml +61 -2
- klaude_code/config/builtin_config.py +1 -0
- klaude_code/config/config.py +19 -0
- klaude_code/config/thinking.py +14 -0
- klaude_code/const.py +17 -2
- klaude_code/core/executor.py +16 -3
- klaude_code/core/task.py +5 -3
- klaude_code/core/tool/shell/command_safety.py +3 -5
- klaude_code/llm/anthropic/client.py +127 -114
- klaude_code/llm/bedrock/__init__.py +3 -0
- klaude_code/llm/bedrock/client.py +60 -0
- klaude_code/llm/google/__init__.py +3 -0
- klaude_code/llm/google/client.py +309 -0
- klaude_code/llm/google/input.py +215 -0
- klaude_code/llm/registry.py +10 -5
- klaude_code/protocol/events.py +1 -0
- klaude_code/protocol/llm_param.py +9 -0
- klaude_code/session/export.py +14 -2
- klaude_code/session/session.py +52 -3
- klaude_code/session/store.py +3 -0
- klaude_code/session/templates/export_session.html +210 -18
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +6 -46
- klaude_code/ui/modes/repl/renderer.py +5 -1
- klaude_code/ui/renderers/developer.py +1 -1
- klaude_code/ui/renderers/sub_agent.py +1 -1
- {klaude_code-1.6.0.dist-info → klaude_code-1.7.1.dist-info}/METADATA +82 -10
- {klaude_code-1.6.0.dist-info → klaude_code-1.7.1.dist-info}/RECORD +34 -29
- {klaude_code-1.6.0.dist-info → klaude_code-1.7.1.dist-info}/WHEEL +0 -0
- {klaude_code-1.6.0.dist-info → klaude_code-1.7.1.dist-info}/entry_points.txt +0 -0
klaude_code/cli/list_model.py
CHANGED
|
@@ -6,7 +6,7 @@ from rich.table import Table
|
|
|
6
6
|
from rich.text import Text
|
|
7
7
|
|
|
8
8
|
from klaude_code.config import Config
|
|
9
|
-
from klaude_code.config.config import ModelConfig, ProviderConfig
|
|
9
|
+
from klaude_code.config.config import ModelConfig, ProviderConfig, parse_env_var_syntax
|
|
10
10
|
from klaude_code.protocol.llm_param import LLMClientProtocol
|
|
11
11
|
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
12
12
|
from klaude_code.ui.rich.theme import ThemeKey, get_theme
|
|
@@ -94,6 +94,29 @@ def format_api_key_display(provider: ProviderConfig) -> Text:
|
|
|
94
94
|
return Text("N/A")
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
def format_env_var_display(value: str | None) -> Text:
|
|
98
|
+
"""Format environment variable display with warning if not set."""
|
|
99
|
+
env_var, resolved = parse_env_var_syntax(value)
|
|
100
|
+
|
|
101
|
+
if env_var:
|
|
102
|
+
# Using ${ENV_VAR} syntax
|
|
103
|
+
if resolved:
|
|
104
|
+
return Text.assemble(
|
|
105
|
+
(f"${{{env_var}}} = ", "dim"),
|
|
106
|
+
(mask_api_key(resolved), ""),
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
return Text.assemble(
|
|
110
|
+
(f"${{{env_var}}} ", ""),
|
|
111
|
+
("(not set)", ThemeKey.CONFIG_STATUS_ERROR),
|
|
112
|
+
)
|
|
113
|
+
elif value:
|
|
114
|
+
# Plain value
|
|
115
|
+
return Text(mask_api_key(value))
|
|
116
|
+
else:
|
|
117
|
+
return Text("N/A")
|
|
118
|
+
|
|
119
|
+
|
|
97
120
|
def _get_model_params_display(model: ModelConfig) -> list[Text]:
|
|
98
121
|
"""Get display elements for model parameters."""
|
|
99
122
|
params: list[Text] = []
|
|
@@ -162,15 +185,43 @@ def display_models_and_providers(config: Config):
|
|
|
162
185
|
format_api_key_display(provider),
|
|
163
186
|
)
|
|
164
187
|
|
|
188
|
+
# AWS Bedrock parameters
|
|
189
|
+
if provider.protocol == LLMClientProtocol.BEDROCK:
|
|
190
|
+
if provider.aws_access_key:
|
|
191
|
+
provider_info.add_row(
|
|
192
|
+
Text("AWS Key:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
193
|
+
format_env_var_display(provider.aws_access_key),
|
|
194
|
+
)
|
|
195
|
+
if provider.aws_secret_key:
|
|
196
|
+
provider_info.add_row(
|
|
197
|
+
Text("AWS Secret:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
198
|
+
format_env_var_display(provider.aws_secret_key),
|
|
199
|
+
)
|
|
200
|
+
if provider.aws_region:
|
|
201
|
+
provider_info.add_row(
|
|
202
|
+
Text("AWS Region:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
203
|
+
format_env_var_display(provider.aws_region),
|
|
204
|
+
)
|
|
205
|
+
if provider.aws_session_token:
|
|
206
|
+
provider_info.add_row(
|
|
207
|
+
Text("AWS Token:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
208
|
+
format_env_var_display(provider.aws_session_token),
|
|
209
|
+
)
|
|
210
|
+
if provider.aws_profile:
|
|
211
|
+
provider_info.add_row(
|
|
212
|
+
Text("AWS Profile:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
213
|
+
format_env_var_display(provider.aws_profile),
|
|
214
|
+
)
|
|
215
|
+
|
|
165
216
|
# Check if provider has valid API key
|
|
166
217
|
provider_available = not provider.is_api_key_missing()
|
|
167
218
|
|
|
168
219
|
# Models table for this provider
|
|
169
220
|
models_table = Table.grid(padding=(0, 1), expand=True)
|
|
170
221
|
models_table.add_column(width=2, no_wrap=True) # Status
|
|
171
|
-
models_table.add_column(overflow="fold", ratio=
|
|
172
|
-
models_table.add_column(overflow="fold", ratio=
|
|
173
|
-
models_table.add_column(overflow="fold", ratio=
|
|
222
|
+
models_table.add_column(overflow="fold", ratio=2) # Name
|
|
223
|
+
models_table.add_column(overflow="fold", ratio=3) # Model
|
|
224
|
+
models_table.add_column(overflow="fold", ratio=4) # Params
|
|
174
225
|
|
|
175
226
|
# Add header
|
|
176
227
|
models_table.add_row(
|
klaude_code/cli/main.py
CHANGED
|
@@ -95,10 +95,20 @@ def read_input_content(cli_argument: str) -> str | None:
|
|
|
95
95
|
return content
|
|
96
96
|
|
|
97
97
|
|
|
98
|
+
ENV_HELP = """\
|
|
99
|
+
Environment Variables:
|
|
100
|
+
|
|
101
|
+
KLAUDE_READ_GLOBAL_LINE_CAP Max lines to read (default: 2000)
|
|
102
|
+
|
|
103
|
+
KLAUDE_READ_MAX_CHARS Max total chars to read (default: 50000)
|
|
104
|
+
"""
|
|
105
|
+
|
|
98
106
|
app = typer.Typer(
|
|
99
107
|
add_completion=False,
|
|
100
108
|
pretty_exceptions_enable=False,
|
|
101
109
|
no_args_is_help=False,
|
|
110
|
+
rich_markup_mode="rich",
|
|
111
|
+
epilog=ENV_HELP,
|
|
102
112
|
)
|
|
103
113
|
|
|
104
114
|
# Register subcommands from modules
|
klaude_code/cli/runtime.py
CHANGED
|
@@ -379,7 +379,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
379
379
|
model_name=model_name,
|
|
380
380
|
save_as_default=False,
|
|
381
381
|
defer_thinking_selection=True,
|
|
382
|
-
emit_welcome_event=
|
|
382
|
+
emit_welcome_event=True,
|
|
383
383
|
emit_switch_message=False,
|
|
384
384
|
)
|
|
385
385
|
)
|
|
@@ -398,7 +398,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
398
398
|
op.ChangeThinkingOperation(
|
|
399
399
|
session_id=sid,
|
|
400
400
|
thinking=thinking,
|
|
401
|
-
emit_welcome_event=
|
|
401
|
+
emit_welcome_event=True,
|
|
402
402
|
emit_switch_message=False,
|
|
403
403
|
)
|
|
404
404
|
)
|
klaude_code/cli/session_cmd.py
CHANGED
|
@@ -22,8 +22,9 @@ def _session_confirm(sessions: list[Session.SessionMetaBrief], message: str) ->
|
|
|
22
22
|
log(f"Sessions to delete ({len(sessions)}):")
|
|
23
23
|
for s in sessions:
|
|
24
24
|
msg_count_display = "N/A" if s.messages_count == -1 else str(s.messages_count)
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
first_msg_text = s.user_messages[0] if s.user_messages else ""
|
|
26
|
+
first_msg = first_msg_text.strip().replace("\n", " ")[:50]
|
|
27
|
+
if len(first_msg_text) > 50:
|
|
27
28
|
first_msg += "..."
|
|
28
29
|
log(f" {_fmt(s.updated_at)} {msg_count_display:>3} msgs {first_msg}")
|
|
29
30
|
|
|
@@ -7,6 +7,7 @@ from prompt_toolkit.styles import Style
|
|
|
7
7
|
|
|
8
8
|
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
9
9
|
from klaude_code.protocol import commands, events, model
|
|
10
|
+
from klaude_code.ui.modes.repl.clipboard import copy_to_clipboard
|
|
10
11
|
from klaude_code.ui.terminal.selector import SelectItem, select_one
|
|
11
12
|
|
|
12
13
|
FORK_SELECT_STYLE = Style(
|
|
@@ -215,6 +216,9 @@ class ForkSessionCommand(CommandABC):
|
|
|
215
216
|
new_session = agent.session.fork()
|
|
216
217
|
await new_session.wait_for_flush()
|
|
217
218
|
|
|
219
|
+
resume_cmd = f"klaude --resume-by-id {new_session.id}"
|
|
220
|
+
copy_to_clipboard(resume_cmd)
|
|
221
|
+
|
|
218
222
|
event = events.DeveloperMessageEvent(
|
|
219
223
|
session_id=agent.session.id,
|
|
220
224
|
item=model.DeveloperMessageItem(
|
|
@@ -247,6 +251,9 @@ class ForkSessionCommand(CommandABC):
|
|
|
247
251
|
# Build result message
|
|
248
252
|
fork_description = "entire conversation" if selected is None else f"up to message index {selected}"
|
|
249
253
|
|
|
254
|
+
resume_cmd = f"klaude --resume-by-id {new_session.id}"
|
|
255
|
+
copy_to_clipboard(resume_cmd)
|
|
256
|
+
|
|
250
257
|
event = events.DeveloperMessageEvent(
|
|
251
258
|
session_id=agent.session.id,
|
|
252
259
|
item=model.DeveloperMessageItem(
|
|
@@ -7,7 +7,7 @@ provider_list:
|
|
|
7
7
|
protocol: anthropic
|
|
8
8
|
api_key: ${ANTHROPIC_API_KEY}
|
|
9
9
|
model_list:
|
|
10
|
-
- model_name: sonnet
|
|
10
|
+
- model_name: sonnet@ant
|
|
11
11
|
model_params:
|
|
12
12
|
model: claude-sonnet-4-5-20250929
|
|
13
13
|
context_limit: 200000
|
|
@@ -18,7 +18,7 @@ provider_list:
|
|
|
18
18
|
output: 15.0
|
|
19
19
|
cache_read: 0.3
|
|
20
20
|
cache_write: 3.75
|
|
21
|
-
- model_name: opus
|
|
21
|
+
- model_name: opus@ant
|
|
22
22
|
model_params:
|
|
23
23
|
model: claude-opus-4-5-20251101
|
|
24
24
|
context_limit: 200000
|
|
@@ -87,6 +87,30 @@ provider_list:
|
|
|
87
87
|
input: 1.75
|
|
88
88
|
output: 14.0
|
|
89
89
|
cache_read: 0.17
|
|
90
|
+
- model_name: gpt-5.2-medium
|
|
91
|
+
model_params:
|
|
92
|
+
model: openai/gpt-5.2
|
|
93
|
+
max_tokens: 128000
|
|
94
|
+
context_limit: 400000
|
|
95
|
+
verbosity: high
|
|
96
|
+
thinking:
|
|
97
|
+
reasoning_effort: medium
|
|
98
|
+
cost:
|
|
99
|
+
input: 1.75
|
|
100
|
+
output: 14.0
|
|
101
|
+
cache_read: 0.17
|
|
102
|
+
- model_name: gpt-5.2-low
|
|
103
|
+
model_params:
|
|
104
|
+
model: openai/gpt-5.2
|
|
105
|
+
max_tokens: 128000
|
|
106
|
+
context_limit: 400000
|
|
107
|
+
verbosity: low
|
|
108
|
+
thinking:
|
|
109
|
+
reasoning_effort: low
|
|
110
|
+
cost:
|
|
111
|
+
input: 1.75
|
|
112
|
+
output: 14.0
|
|
113
|
+
cache_read: 0.17
|
|
90
114
|
- model_name: gpt-5.2-fast
|
|
91
115
|
model_params:
|
|
92
116
|
model: openai/gpt-5.2
|
|
@@ -194,6 +218,41 @@ provider_list:
|
|
|
194
218
|
output: 1.74
|
|
195
219
|
cache_read: 0.04
|
|
196
220
|
|
|
221
|
+
- provider_name: google
|
|
222
|
+
protocol: google
|
|
223
|
+
api_key: ${GOOGLE_API_KEY}
|
|
224
|
+
model_list:
|
|
225
|
+
- model_name: gemini-pro@google
|
|
226
|
+
model_params:
|
|
227
|
+
model: gemini-3-pro-preview
|
|
228
|
+
context_limit: 1048576
|
|
229
|
+
cost:
|
|
230
|
+
input: 2.0
|
|
231
|
+
output: 12.0
|
|
232
|
+
cache_read: 0.2
|
|
233
|
+
- model_name: gemini-flash@google
|
|
234
|
+
model_params:
|
|
235
|
+
model: gemini-3-flash-preview
|
|
236
|
+
context_limit: 1048576
|
|
237
|
+
cost:
|
|
238
|
+
input: 0.5
|
|
239
|
+
output: 3.0
|
|
240
|
+
cache_read: 0.05
|
|
241
|
+
- provider_name: bedrock
|
|
242
|
+
protocol: bedrock
|
|
243
|
+
aws_access_key: ${AWS_ACCESS_KEY_ID}
|
|
244
|
+
aws_secret_key: ${AWS_SECRET_ACCESS_KEY}
|
|
245
|
+
aws_region: ${AWS_REGION}
|
|
246
|
+
model_list:
|
|
247
|
+
- model_name: sonnet@bedrock
|
|
248
|
+
model_params:
|
|
249
|
+
model: us.anthropic.claude-sonnet-4-5-20250929-v1:0
|
|
250
|
+
context_limit: 200000
|
|
251
|
+
cost:
|
|
252
|
+
input: 3.0
|
|
253
|
+
output: 15.0
|
|
254
|
+
cache_read: 0.3
|
|
255
|
+
cache_write: 3.75
|
|
197
256
|
- provider_name: deepseek
|
|
198
257
|
protocol: anthropic
|
|
199
258
|
api_key: ${DEEPSEEK_API_KEY}
|
klaude_code/config/config.py
CHANGED
|
@@ -77,6 +77,7 @@ class ProviderConfig(llm_param.LLMConfigProviderParameter):
|
|
|
77
77
|
"""Check if the API key is missing (either not set or env var not found).
|
|
78
78
|
|
|
79
79
|
For codex protocol, checks OAuth login status instead of API key.
|
|
80
|
+
For bedrock protocol, checks AWS credentials instead of API key.
|
|
80
81
|
"""
|
|
81
82
|
from klaude_code.protocol.llm_param import LLMClientProtocol
|
|
82
83
|
|
|
@@ -89,6 +90,19 @@ class ProviderConfig(llm_param.LLMConfigProviderParameter):
|
|
|
89
90
|
# Consider available if logged in and token not expired
|
|
90
91
|
return state is None or state.is_expired()
|
|
91
92
|
|
|
93
|
+
if self.protocol == LLMClientProtocol.BEDROCK:
|
|
94
|
+
# Bedrock uses AWS credentials, not API key. Region is always required.
|
|
95
|
+
_, resolved_profile = parse_env_var_syntax(self.aws_profile)
|
|
96
|
+
_, resolved_region = parse_env_var_syntax(self.aws_region)
|
|
97
|
+
|
|
98
|
+
# When using profile, we still need region to initialize the client.
|
|
99
|
+
if resolved_profile:
|
|
100
|
+
return resolved_region is None
|
|
101
|
+
|
|
102
|
+
_, resolved_access_key = parse_env_var_syntax(self.aws_access_key)
|
|
103
|
+
_, resolved_secret_key = parse_env_var_syntax(self.aws_secret_key)
|
|
104
|
+
return resolved_region is None or resolved_access_key is None or resolved_secret_key is None
|
|
105
|
+
|
|
92
106
|
return self.get_resolved_api_key() is None
|
|
93
107
|
|
|
94
108
|
|
|
@@ -243,6 +257,11 @@ def get_example_config() -> UserConfig:
|
|
|
243
257
|
model="model-id-from-provider",
|
|
244
258
|
max_tokens=16000,
|
|
245
259
|
context_limit=200000,
|
|
260
|
+
cost=llm_param.Cost(
|
|
261
|
+
input=1,
|
|
262
|
+
output=10,
|
|
263
|
+
cache_read=0.1,
|
|
264
|
+
),
|
|
246
265
|
),
|
|
247
266
|
),
|
|
248
267
|
],
|
klaude_code/config/thinking.py
CHANGED
|
@@ -121,6 +121,13 @@ def format_current_thinking(config: llm_param.LLMConfigParameter) -> str:
|
|
|
121
121
|
return f"enabled (budget_tokens={thinking.budget_tokens})"
|
|
122
122
|
return "not set"
|
|
123
123
|
|
|
124
|
+
if protocol == llm_param.LLMClientProtocol.GOOGLE:
|
|
125
|
+
if thinking.type == "disabled":
|
|
126
|
+
return "off"
|
|
127
|
+
if thinking.type == "enabled":
|
|
128
|
+
return f"enabled (budget_tokens={thinking.budget_tokens})"
|
|
129
|
+
return "not set"
|
|
130
|
+
|
|
124
131
|
return "unknown protocol"
|
|
125
132
|
|
|
126
133
|
|
|
@@ -230,6 +237,13 @@ def get_thinking_picker_data(config: llm_param.LLMConfigParameter) -> ThinkingPi
|
|
|
230
237
|
current_value=_get_current_budget_value(thinking),
|
|
231
238
|
)
|
|
232
239
|
|
|
240
|
+
if protocol == llm_param.LLMClientProtocol.GOOGLE:
|
|
241
|
+
return ThinkingPickerData(
|
|
242
|
+
options=_build_budget_options(),
|
|
243
|
+
message="Select thinking level:",
|
|
244
|
+
current_value=_get_current_budget_value(thinking),
|
|
245
|
+
)
|
|
246
|
+
|
|
233
247
|
return None
|
|
234
248
|
|
|
235
249
|
|
klaude_code/const.py
CHANGED
|
@@ -4,8 +4,21 @@ This module consolidates all magic numbers and configuration values
|
|
|
4
4
|
that were previously scattered across the codebase.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import os
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
10
|
+
|
|
11
|
+
def _get_int_env(name: str, default: int) -> int:
|
|
12
|
+
"""Get an integer value from environment variable, or return default."""
|
|
13
|
+
val = os.environ.get(name)
|
|
14
|
+
if val is None:
|
|
15
|
+
return default
|
|
16
|
+
try:
|
|
17
|
+
return int(val)
|
|
18
|
+
except ValueError:
|
|
19
|
+
return default
|
|
20
|
+
|
|
21
|
+
|
|
9
22
|
# =============================================================================
|
|
10
23
|
# Agent Configuration
|
|
11
24
|
# =============================================================================
|
|
@@ -47,10 +60,12 @@ TODO_REMINDER_TOOL_CALL_THRESHOLD = 10
|
|
|
47
60
|
READ_CHAR_LIMIT_PER_LINE = 2000
|
|
48
61
|
|
|
49
62
|
# Maximum number of lines to read from a file
|
|
50
|
-
|
|
63
|
+
# Can be overridden via KLAUDE_READ_GLOBAL_LINE_CAP environment variable
|
|
64
|
+
READ_GLOBAL_LINE_CAP = _get_int_env("KLAUDE_READ_GLOBAL_LINE_CAP", 2000)
|
|
51
65
|
|
|
52
66
|
# Maximum total characters to read (truncates beyond this limit)
|
|
53
|
-
|
|
67
|
+
# Can be overridden via KLAUDE_READ_MAX_CHARS environment variable
|
|
68
|
+
READ_MAX_CHARS = _get_int_env("KLAUDE_READ_MAX_CHARS", 50000)
|
|
54
69
|
|
|
55
70
|
# Maximum image file size in bytes (4MB)
|
|
56
71
|
READ_MAX_IMAGE_BYTES = 4 * 1024 * 1024
|
klaude_code/core/executor.py
CHANGED
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import subprocess
|
|
12
|
+
import sys
|
|
12
13
|
from collections.abc import Callable
|
|
13
14
|
from dataclasses import dataclass
|
|
14
15
|
from pathlib import Path
|
|
@@ -427,14 +428,26 @@ class ExecutorContext:
|
|
|
427
428
|
return build_export_html(agent.session, system_prompt, tool_schemas, model_name)
|
|
428
429
|
|
|
429
430
|
def _open_file(self, path: Path) -> None:
|
|
431
|
+
# Select platform-appropriate command
|
|
432
|
+
if sys.platform == "darwin":
|
|
433
|
+
cmd = "open"
|
|
434
|
+
elif sys.platform == "win32":
|
|
435
|
+
cmd = "start"
|
|
436
|
+
else:
|
|
437
|
+
cmd = "xdg-open"
|
|
438
|
+
|
|
430
439
|
try:
|
|
431
440
|
# Detach stdin to prevent interference with prompt_toolkit's terminal state
|
|
432
|
-
|
|
441
|
+
if sys.platform == "win32":
|
|
442
|
+
# Windows 'start' requires shell=True
|
|
443
|
+
subprocess.run(f'start "" "{path}"', shell=True, stdin=subprocess.DEVNULL, check=True)
|
|
444
|
+
else:
|
|
445
|
+
subprocess.run([cmd, str(path)], stdin=subprocess.DEVNULL, check=True)
|
|
433
446
|
except FileNotFoundError as exc: # pragma: no cover
|
|
434
|
-
msg = "`
|
|
447
|
+
msg = f"`{cmd}` command not found; please open the HTML manually."
|
|
435
448
|
raise RuntimeError(msg) from exc
|
|
436
449
|
except subprocess.CalledProcessError as exc: # pragma: no cover
|
|
437
|
-
msg = f"Failed to open HTML with `
|
|
450
|
+
msg = f"Failed to open HTML with `{cmd}`: {exc}"
|
|
438
451
|
raise RuntimeError(msg) from exc
|
|
439
452
|
|
|
440
453
|
async def handle_interrupt(self, operation: op.InterruptOperation) -> None:
|
klaude_code/core/task.py
CHANGED
|
@@ -220,7 +220,9 @@ class TaskExecutor:
|
|
|
220
220
|
error_msg = f"Retrying {attempt + 1}/{const.MAX_FAILED_TURN_RETRIES} in {delay:.1f}s"
|
|
221
221
|
if last_error_message:
|
|
222
222
|
error_msg = f"{error_msg} - {last_error_message}"
|
|
223
|
-
yield events.ErrorEvent(
|
|
223
|
+
yield events.ErrorEvent(
|
|
224
|
+
error_message=error_msg, can_retry=True, session_id=session_ctx.session_id
|
|
225
|
+
)
|
|
224
226
|
await asyncio.sleep(delay)
|
|
225
227
|
finally:
|
|
226
228
|
self._current_turn = None
|
|
@@ -234,7 +236,7 @@ class TaskExecutor:
|
|
|
234
236
|
final_error = f"Turn failed after {const.MAX_FAILED_TURN_RETRIES} retries."
|
|
235
237
|
if last_error_message:
|
|
236
238
|
final_error = f"{last_error_message}\n{final_error}"
|
|
237
|
-
yield events.ErrorEvent(error_message=final_error, can_retry=False)
|
|
239
|
+
yield events.ErrorEvent(error_message=final_error, can_retry=False, session_id=session_ctx.session_id)
|
|
238
240
|
return
|
|
239
241
|
|
|
240
242
|
if turn is None or turn.task_finished:
|
|
@@ -244,7 +246,7 @@ class TaskExecutor:
|
|
|
244
246
|
error_msg = "Sub-agent returned empty result, retrying..."
|
|
245
247
|
else:
|
|
246
248
|
error_msg = "Agent returned empty result, retrying..."
|
|
247
|
-
yield events.ErrorEvent(error_message=error_msg, can_retry=True)
|
|
249
|
+
yield events.ErrorEvent(error_message=error_msg, can_retry=True, session_id=session_ctx.session_id)
|
|
248
250
|
continue
|
|
249
251
|
break
|
|
250
252
|
|
|
@@ -275,12 +275,10 @@ def _is_safe_argv(argv: list[str]) -> SafetyCheckResult:
|
|
|
275
275
|
"tag",
|
|
276
276
|
"clone",
|
|
277
277
|
"worktree",
|
|
278
|
+
"push",
|
|
279
|
+
"pull",
|
|
280
|
+
"remote",
|
|
278
281
|
}
|
|
279
|
-
# Block remote operations
|
|
280
|
-
blocked_git_cmds = {"push", "pull", "remote"}
|
|
281
|
-
|
|
282
|
-
if sub in blocked_git_cmds:
|
|
283
|
-
return SafetyCheckResult(False, f"git: Remote operation '{sub}' not allowed")
|
|
284
282
|
if sub not in allowed_git_cmds:
|
|
285
283
|
return SafetyCheckResult(False, f"git: Subcommand '{sub}' not in allow list")
|
|
286
284
|
return SafetyCheckResult(True)
|