klaude-code 2.8.0__py3-none-any.whl → 2.9.0__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 (100) hide show
  1. klaude_code/app/runtime.py +2 -1
  2. klaude_code/auth/antigravity/oauth.py +0 -9
  3. klaude_code/auth/antigravity/token_manager.py +0 -18
  4. klaude_code/auth/base.py +53 -0
  5. klaude_code/auth/codex/exceptions.py +0 -4
  6. klaude_code/auth/codex/oauth.py +32 -28
  7. klaude_code/auth/codex/token_manager.py +0 -18
  8. klaude_code/cli/cost_cmd.py +128 -39
  9. klaude_code/cli/list_model.py +27 -10
  10. klaude_code/cli/main.py +15 -4
  11. klaude_code/config/assets/builtin_config.yaml +8 -24
  12. klaude_code/config/config.py +47 -25
  13. klaude_code/config/sub_agent_model_helper.py +18 -13
  14. klaude_code/config/thinking.py +0 -8
  15. klaude_code/const.py +2 -2
  16. klaude_code/core/agent_profile.py +11 -53
  17. klaude_code/core/compaction/compaction.py +4 -6
  18. klaude_code/core/compaction/overflow.py +0 -4
  19. klaude_code/core/executor.py +51 -5
  20. klaude_code/core/manager/llm_clients.py +9 -1
  21. klaude_code/core/prompts/prompt-claude-code.md +4 -4
  22. klaude_code/core/reminders.py +21 -23
  23. klaude_code/core/task.py +0 -4
  24. klaude_code/core/tool/__init__.py +3 -2
  25. klaude_code/core/tool/file/apply_patch.py +0 -27
  26. klaude_code/core/tool/file/edit_tool.py +1 -2
  27. klaude_code/core/tool/file/read_tool.md +3 -2
  28. klaude_code/core/tool/file/read_tool.py +15 -2
  29. klaude_code/core/tool/offload.py +0 -35
  30. klaude_code/core/tool/sub_agent/__init__.py +6 -0
  31. klaude_code/core/tool/sub_agent/image_gen.md +16 -0
  32. klaude_code/core/tool/sub_agent/image_gen.py +146 -0
  33. klaude_code/core/tool/sub_agent/task.md +20 -0
  34. klaude_code/core/tool/sub_agent/task.py +205 -0
  35. klaude_code/core/tool/tool_registry.py +0 -16
  36. klaude_code/core/turn.py +1 -1
  37. klaude_code/llm/anthropic/input.py +6 -5
  38. klaude_code/llm/antigravity/input.py +14 -7
  39. klaude_code/llm/codex/client.py +22 -0
  40. klaude_code/llm/codex/prompt_sync.py +237 -0
  41. klaude_code/llm/google/client.py +8 -6
  42. klaude_code/llm/google/input.py +20 -12
  43. klaude_code/llm/image.py +18 -11
  44. klaude_code/llm/input_common.py +14 -6
  45. klaude_code/llm/json_stable.py +37 -0
  46. klaude_code/llm/openai_compatible/input.py +0 -10
  47. klaude_code/llm/openai_compatible/stream.py +16 -1
  48. klaude_code/llm/registry.py +0 -5
  49. klaude_code/llm/responses/input.py +15 -5
  50. klaude_code/llm/usage.py +0 -8
  51. klaude_code/protocol/commands.py +1 -0
  52. klaude_code/protocol/events.py +2 -1
  53. klaude_code/protocol/message.py +2 -2
  54. klaude_code/protocol/model.py +20 -1
  55. klaude_code/protocol/op.py +27 -0
  56. klaude_code/protocol/op_handler.py +10 -0
  57. klaude_code/protocol/sub_agent/AGENTS.md +5 -5
  58. klaude_code/protocol/sub_agent/__init__.py +13 -34
  59. klaude_code/protocol/sub_agent/explore.py +7 -34
  60. klaude_code/protocol/sub_agent/image_gen.py +3 -74
  61. klaude_code/protocol/sub_agent/task.py +3 -47
  62. klaude_code/protocol/sub_agent/web.py +8 -52
  63. klaude_code/protocol/tools.py +2 -0
  64. klaude_code/session/export.py +308 -299
  65. klaude_code/session/session.py +58 -21
  66. klaude_code/session/store.py +0 -4
  67. klaude_code/session/templates/export_session.html +430 -134
  68. klaude_code/skill/assets/deslop/SKILL.md +9 -0
  69. klaude_code/skill/system_skills.py +0 -20
  70. klaude_code/tui/command/__init__.py +3 -0
  71. klaude_code/tui/command/continue_cmd.py +34 -0
  72. klaude_code/tui/command/fork_session_cmd.py +5 -2
  73. klaude_code/tui/command/resume_cmd.py +9 -2
  74. klaude_code/tui/command/sub_agent_model_cmd.py +85 -18
  75. klaude_code/tui/components/assistant.py +0 -26
  76. klaude_code/tui/components/command_output.py +3 -1
  77. klaude_code/tui/components/developer.py +3 -0
  78. klaude_code/tui/components/diffs.py +2 -208
  79. klaude_code/tui/components/errors.py +4 -0
  80. klaude_code/tui/components/mermaid_viewer.py +2 -2
  81. klaude_code/tui/components/rich/markdown.py +60 -63
  82. klaude_code/tui/components/rich/theme.py +2 -0
  83. klaude_code/tui/components/sub_agent.py +2 -46
  84. klaude_code/tui/components/thinking.py +0 -33
  85. klaude_code/tui/components/tools.py +43 -21
  86. klaude_code/tui/input/images.py +21 -18
  87. klaude_code/tui/input/key_bindings.py +2 -2
  88. klaude_code/tui/input/prompt_toolkit.py +49 -49
  89. klaude_code/tui/machine.py +15 -11
  90. klaude_code/tui/renderer.py +12 -20
  91. klaude_code/tui/runner.py +2 -1
  92. klaude_code/tui/terminal/image.py +6 -34
  93. klaude_code/ui/common.py +0 -70
  94. {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/METADATA +3 -6
  95. {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/RECORD +97 -92
  96. klaude_code/core/tool/sub_agent_tool.py +0 -126
  97. klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -108
  98. klaude_code/tui/components/rich/searchable_text.py +0 -68
  99. {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/WHEEL +0 -0
  100. {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/entry_points.txt +0 -0
@@ -44,13 +44,6 @@ def parse_env_var_syntax(value: str | None) -> tuple[str | None, str | None]:
44
44
  return None, value
45
45
 
46
46
 
47
- def is_env_var_syntax(value: str | None) -> bool:
48
- """Check if a value uses ${ENV_VAR} syntax."""
49
- if value is None:
50
- return False
51
- return _ENV_VAR_PATTERN.match(value) is not None
52
-
53
-
54
47
  def resolve_api_key(value: str | None) -> str | None:
55
48
  """Resolve an API key value, expanding ${ENV_VAR} syntax if present."""
56
49
  _, resolved = parse_env_var_syntax(value)
@@ -70,6 +63,7 @@ class ModelConfig(llm_param.LLMConfigModelParameter):
70
63
  class ProviderConfig(llm_param.LLMConfigProviderParameter):
71
64
  """Full provider configuration (used in merged config)."""
72
65
 
66
+ disabled: bool = False
73
67
  model_list: list[ModelConfig] = Field(default_factory=lambda: [])
74
68
 
75
69
  def get_resolved_api_key(self) -> str | None:
@@ -141,6 +135,7 @@ class UserProviderConfig(BaseModel):
141
135
 
142
136
  provider_name: str
143
137
  protocol: llm_param.LLMClientProtocol | None = None
138
+ disabled: bool = False
144
139
  base_url: str | None = None
145
140
  api_key: str | None = None
146
141
  is_azure: bool = False
@@ -290,6 +285,9 @@ class Config(BaseModel):
290
285
  if requested_provider is not None and provider.provider_name.casefold() != requested_provider.casefold():
291
286
  continue
292
287
 
288
+ if provider.disabled:
289
+ continue
290
+
293
291
  api_key = provider.get_resolved_api_key()
294
292
  if (
295
293
  provider.protocol
@@ -303,7 +301,11 @@ class Config(BaseModel):
303
301
  ):
304
302
  continue
305
303
 
306
- if any(m.model_name == requested_model for m in provider.model_list):
304
+ for model in provider.model_list:
305
+ if model.model_name != requested_model:
306
+ continue
307
+ if model.disabled:
308
+ continue
307
309
  return requested_model, provider.provider_name
308
310
 
309
311
  return None
@@ -315,6 +317,11 @@ class Config(BaseModel):
315
317
  if requested_provider is not None and provider.provider_name.casefold() != requested_provider.casefold():
316
318
  continue
317
319
 
320
+ if provider.disabled:
321
+ if requested_provider is not None:
322
+ raise ValueError(f"Provider '{provider.provider_name}' is disabled for: {model_name}")
323
+ continue
324
+
318
325
  # Resolve ${ENV_VAR} syntax for api_key
319
326
  api_key = provider.get_resolved_api_key()
320
327
 
@@ -339,7 +346,15 @@ class Config(BaseModel):
339
346
  for model in provider.model_list:
340
347
  if model.model_name != requested_model:
341
348
  continue
342
- provider_dump = provider.model_dump(exclude={"model_list"})
349
+
350
+ if model.disabled:
351
+ if requested_provider is not None:
352
+ raise ValueError(
353
+ f"Model '{requested_model}' is disabled in provider '{provider.provider_name}' for: {model_name}"
354
+ )
355
+ break
356
+
357
+ provider_dump = provider.model_dump(exclude={"model_list", "disabled"})
343
358
  provider_dump["api_key"] = api_key
344
359
  return llm_param.LLMConfigParameter(
345
360
  **provider_dump,
@@ -353,7 +368,7 @@ class Config(BaseModel):
353
368
 
354
369
  Args:
355
370
  only_available: If True, only return models from providers with valid API keys.
356
- include_disabled: If False, exclude models with disabled=True.
371
+ include_disabled: If False, exclude models/providers with disabled=True.
357
372
  """
358
373
  return [
359
374
  ModelEntry(
@@ -362,7 +377,8 @@ class Config(BaseModel):
362
377
  **model.model_dump(exclude={"model_name"}),
363
378
  )
364
379
  for provider in self.provider_list
365
- if not only_available or not provider.is_api_key_missing()
380
+ if include_disabled or not provider.disabled
381
+ if not only_available or (not provider.disabled and not provider.is_api_key_missing())
366
382
  for model in provider.model_list
367
383
  if include_disabled or not model.disabled
368
384
  ]
@@ -374,13 +390,6 @@ class Config(BaseModel):
374
390
  return True
375
391
  return False
376
392
 
377
- def get_first_available_nano_banana_model(self) -> str | None:
378
- """Get the first available nano-banana model, or None."""
379
- for entry in self.iter_model_entries(only_available=True, include_disabled=False):
380
- if "nano-banana" in entry.model_name:
381
- return entry.model_name
382
- return None
383
-
384
393
  def get_first_available_image_model(self) -> str | None:
385
394
  """Get the first available image generation model, or None."""
386
395
  for entry in self.iter_model_entries(only_available=True, include_disabled=False):
@@ -406,7 +415,21 @@ class Config(BaseModel):
406
415
  user_config.theme = self.theme
407
416
  # Note: provider_list is NOT synced - user providers are already in user_config
408
417
 
409
- config_dict = user_config.model_dump(mode="json", exclude_none=True, exclude_defaults=True)
418
+ # Keep the saved file compact (exclude defaults), but preserve explicit
419
+ # overrides inside provider_list (e.g. `disabled: false` to re-enable a
420
+ # builtin provider that is disabled by default).
421
+ config_dict = user_config.model_dump(
422
+ mode="json",
423
+ exclude_none=True,
424
+ exclude_defaults=True,
425
+ exclude={"provider_list"},
426
+ )
427
+
428
+ provider_list = [
429
+ p.model_dump(mode="json", exclude_none=True, exclude_unset=True) for p in (user_config.provider_list or [])
430
+ ]
431
+ if provider_list:
432
+ config_dict["provider_list"] = provider_list
410
433
 
411
434
  def _save_config() -> None:
412
435
  config_path.parent.mkdir(parents=True, exist_ok=True)
@@ -454,12 +477,12 @@ def _get_builtin_config() -> Config:
454
477
  def _merge_model(builtin: ModelConfig, user: ModelConfig) -> ModelConfig:
455
478
  """Merge user model config with builtin model config.
456
479
 
457
- Strategy: user values take precedence if explicitly set (not default).
458
- This allows users to override specific fields (e.g., disabled=true)
480
+ Strategy: user values take precedence if explicitly set (not unset).
481
+ This allows users to override specific fields (e.g., disabled=true/false)
459
482
  without losing other builtin settings (e.g., model_id, max_tokens).
460
483
  """
461
484
  merged_data = builtin.model_dump()
462
- user_data = user.model_dump(exclude_defaults=True, exclude={"model_name"})
485
+ user_data = user.model_dump(exclude_unset=True, exclude={"model_name"})
463
486
  for key, value in user_data.items():
464
487
  if value is not None:
465
488
  merged_data[key] = value
@@ -485,10 +508,9 @@ def _merge_provider(builtin: ProviderConfig, user: UserProviderConfig) -> Provid
485
508
  # New model from user
486
509
  merged_models[m.model_name] = m
487
510
 
488
- # For other fields, use user values if explicitly set, otherwise use builtin
489
- # We check if user explicitly provided a value by comparing to defaults
511
+ # For other fields, use user values if explicitly set, otherwise use builtin.
490
512
  merged_data = builtin.model_dump()
491
- user_data = user.model_dump(exclude_defaults=True, exclude={"model_list"})
513
+ user_data = user.model_dump(exclude_unset=True, exclude={"model_list"})
492
514
 
493
515
  # Update with user's explicit settings
494
516
  for key, value in user_data.items():
@@ -5,13 +5,12 @@ from __future__ import annotations
5
5
  from dataclasses import dataclass
6
6
  from typing import TYPE_CHECKING
7
7
 
8
+ from klaude_code.protocol import tools
8
9
  from klaude_code.protocol.sub_agent import (
9
10
  AVAILABILITY_IMAGE_MODEL,
10
11
  SubAgentProfile,
11
12
  get_sub_agent_profile,
12
- get_sub_agent_profile_by_tool,
13
13
  iter_sub_agent_profiles,
14
- sub_agent_tool_names,
15
14
  )
16
15
  from klaude_code.protocol.tools import SubAgentType
17
16
 
@@ -30,7 +29,7 @@ class SubAgentModelInfo:
30
29
  # Effective model name used by this sub-agent.
31
30
  # - When configured_model is set: equals configured_model.
32
31
  # - When requirement-based default applies (e.g. ImageGen): resolved model.
33
- # - When inheriting from main agent: None.
32
+ # - When inheriting from defaults: resolved model name.
34
33
  effective_model: str | None
35
34
 
36
35
 
@@ -106,10 +105,11 @@ class SubAgentModelHelper:
106
105
  ) -> EmptySubAgentModelBehavior:
107
106
  """Describe what happens when a sub-agent model is not configured.
108
107
 
109
- Most sub-agents default to inheriting the main model.
108
+ Most sub-agents default to the Task model if configured, otherwise
109
+ they inherit the main model.
110
110
 
111
111
  Sub-agents with an availability requirement (e.g. ImageGen) do NOT
112
- inherit from the main model; instead they auto-resolve a suitable model
112
+ inherit from Task/main; instead they auto-resolve a suitable model
113
113
  (currently: the first available image model).
114
114
  """
115
115
 
@@ -117,9 +117,11 @@ class SubAgentModelHelper:
117
117
 
118
118
  requirement = profile.availability_requirement
119
119
  if requirement is None:
120
+ task_model = self._config.sub_agent_models.get(tools.TASK)
121
+ resolved = task_model or main_model_name
120
122
  return EmptySubAgentModelBehavior(
121
- description=f"inherit from main agent: {main_model_name}",
122
- resolved_model_name=main_model_name,
123
+ description=f"use default behavior: {resolved}",
124
+ resolved_model_name=resolved,
123
125
  )
124
126
 
125
127
  resolved = self.resolve_model_for_requirement(requirement)
@@ -154,13 +156,15 @@ class SubAgentModelHelper:
154
156
  For sub-agents without explicit config, resolves model based on availability_requirement.
155
157
  """
156
158
  result: list[SubAgentModelInfo] = []
157
- for profile in iter_sub_agent_profiles(enabled_only=True):
159
+ for profile in iter_sub_agent_profiles():
158
160
  if not self.check_availability_requirement(profile.availability_requirement):
159
161
  continue
160
162
  configured_model = self._config.sub_agent_models.get(profile.name)
161
163
  effective_model = configured_model
162
164
  if not effective_model and profile.availability_requirement:
163
165
  effective_model = self.resolve_model_for_requirement(profile.availability_requirement)
166
+ if not effective_model and profile.availability_requirement is None:
167
+ effective_model = self._config.sub_agent_models.get(tools.TASK) or self._config.main_model
164
168
  result.append(
165
169
  SubAgentModelInfo(
166
170
  profile=profile,
@@ -189,11 +193,9 @@ class SubAgentModelHelper:
189
193
 
190
194
  def get_enabled_sub_agent_tool_names(self) -> list[str]:
191
195
  """Return sub-agent tool names that should be added to main agent's tool list."""
192
- result: list[str] = []
193
- for name in sub_agent_tool_names(enabled_only=True):
194
- profile = get_sub_agent_profile_by_tool(name)
195
- if profile is not None and self.check_availability_requirement(profile.availability_requirement):
196
- result.append(name)
196
+ result: list[str] = [tools.TASK]
197
+ if self.check_availability_requirement(AVAILABILITY_IMAGE_MODEL):
198
+ result.append(tools.IMAGE_GEN)
197
199
  return result
198
200
 
199
201
  def build_sub_agent_client_configs(self) -> dict[SubAgentType, str]:
@@ -205,4 +207,7 @@ class SubAgentModelHelper:
205
207
  model_name = self.resolve_model_for_requirement(profile.availability_requirement)
206
208
  if model_name:
207
209
  result[profile.name] = model_name
210
+ task_model = self._config.sub_agent_models.get(tools.TASK)
211
+ if task_model:
212
+ result.setdefault(tools.TASK, task_model)
208
213
  return result
@@ -62,14 +62,6 @@ def _is_gemini_flash_model(model_name: str | None) -> bool:
62
62
  return "gemini-3-flash" in model_name.lower()
63
63
 
64
64
 
65
- def should_auto_trigger_thinking(model_name: str | None) -> bool:
66
- """Check if model should auto-trigger thinking selection on switch."""
67
- if not model_name:
68
- return False
69
- model_lower = model_name.lower()
70
- return "gpt-5" in model_lower or "gemini-3" in model_lower or "opus" in model_lower
71
-
72
-
73
65
  def get_levels_for_responses(model_name: str | None) -> list[str]:
74
66
  """Get thinking levels for responses protocol."""
75
67
  if _is_codex_max_model(model_name):
klaude_code/const.py CHANGED
@@ -81,7 +81,7 @@ MEMORY_FILE_NAMES = ["CLAUDE.md", "AGENTS.md", "AGENT.md"] # Memory file names
81
81
  READ_CHAR_LIMIT_PER_LINE = 2000 # Maximum characters per line before truncation
82
82
  READ_GLOBAL_LINE_CAP = _get_int_env("KLAUDE_READ_GLOBAL_LINE_CAP", 2000) # Maximum lines to read from a file
83
83
  READ_MAX_CHARS = _get_int_env("KLAUDE_READ_MAX_CHARS", 50000) # Maximum total characters to read
84
- READ_MAX_IMAGE_BYTES = _get_int_env("KLAUDE_READ_MAX_IMAGE_BYTES", 4 * 1024 * 1024) # Max image size (4MB)
84
+ READ_MAX_IMAGE_BYTES = _get_int_env("KLAUDE_READ_MAX_IMAGE_BYTES", 64 * 1024 * 1024) # Max image size (64MB)
85
85
  IMAGE_OUTPUT_MAX_BYTES = _get_int_env("KLAUDE_IMAGE_OUTPUT_MAX_BYTES", 64 * 1024 * 1024) # Max decoded image (64MB)
86
86
  BINARY_CHECK_SIZE = 8192 # Bytes to check for binary file detection
87
87
 
@@ -141,7 +141,7 @@ MIN_HIDDEN_LINES_FOR_INDICATOR = 5 # Minimum hidden lines before showing trunca
141
141
  SUB_AGENT_RESULT_MAX_LINES = 10 # Maximum lines for sub-agent result display
142
142
  TRUNCATE_HEAD_MAX_LINES = 2 # Maximum lines for sub-agent error display
143
143
  BASH_OUTPUT_PANEL_THRESHOLD = 10 # Bash output line threshold for CodePanel display
144
- BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES = 2 # Max lines shown for heredoc / multiline string tokens in bash tool calls
144
+ BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES = 4 # Max lines shown for heredoc / multiline string tokens in bash tool calls
145
145
  URL_TRUNCATE_MAX_LENGTH = 400 # Maximum length for URL truncation in display
146
146
  QUERY_DISPLAY_TRUNCATE_LENGTH = 80 # Maximum length for search query display
147
147
  NOTIFY_COMPACT_LIMIT = 160 # Maximum length for notification body text
@@ -12,12 +12,10 @@ from typing import TYPE_CHECKING, Any, Protocol
12
12
  if TYPE_CHECKING:
13
13
  from klaude_code.config.config import Config
14
14
 
15
- from klaude_code.auth.codex.exceptions import CodexUnsupportedModelError
16
15
  from klaude_code.config.sub_agent_model_helper import SubAgentModelHelper
17
16
  from klaude_code.core.reminders import (
18
17
  at_file_reader_reminder,
19
18
  empty_todo_reminder,
20
- file_changed_externally_reminder,
21
19
  image_reminder,
22
20
  last_path_memory_reminder,
23
21
  memory_reminder,
@@ -28,7 +26,7 @@ from klaude_code.core.tool.report_back_tool import ReportBackTool
28
26
  from klaude_code.core.tool.tool_registry import get_tool_schemas
29
27
  from klaude_code.llm import LLMClientABC
30
28
  from klaude_code.protocol import llm_param, message, tools
31
- from klaude_code.protocol.sub_agent import get_sub_agent_profile
29
+ from klaude_code.protocol.sub_agent import AVAILABILITY_IMAGE_MODEL, get_sub_agent_profile
32
30
  from klaude_code.session import Session
33
31
 
34
32
  type Reminder = Callable[[Session], Awaitable[message.DeveloperMessage | None]]
@@ -54,12 +52,6 @@ COMMAND_DESCRIPTIONS: dict[str, str] = {
54
52
  }
55
53
 
56
54
 
57
- # Prompts for codex_oauth protocol - must be used exactly as-is without any additions.
58
- CODEX_OAUTH_PROMPTS: dict[str, str] = {
59
- "gpt-5.2-codex": "prompts/prompt-codex-gpt-5-2-codex.md",
60
- "gpt-5.2": "prompts/prompt-codex-gpt-5-2.md",
61
- }
62
-
63
55
  # Prompt for antigravity protocol - used exactly as-is without any additions.
64
56
  ANTIGRAVITY_PROMPT_PATH = "prompts/prompt-antigravity.md"
65
57
 
@@ -73,20 +65,6 @@ Only the content passed to `report_back` will be returned to user.\
73
65
  """
74
66
 
75
67
 
76
- SUB_AGENT_COMMON_PROMPT_FOR_MAIN_AGENT = """\
77
-
78
- # Sub-agent capabilities
79
- You have sub-agents (e.g. Task, Explore, WebAgent, ImageGen) with structured output and resume capabilites:
80
- - Agents can be provided with an `output_format` (JSON Schema) parameter for structured output
81
- - 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"]}`
82
- - Agents can be resumed using the `resume` parameter by passing the agent ID from a previous invocation. \
83
- When resumed, the agent continues with its full previous context preserved. \
84
- When NOT resuming, each invocation starts fresh and you should provide a detailed task description with all necessary context.
85
- - When the agent is done, it will return a single message back to you along with its agent ID. \
86
- You can use this ID to resume the agent later if needed for follow-up work.
87
- """
88
-
89
-
90
68
  @cache
91
69
  def _load_prompt_by_path(prompt_path: str) -> str:
92
70
  """Load and cache prompt content from a file path relative to core package."""
@@ -144,19 +122,6 @@ def _build_env_info(model_name: str) -> str:
144
122
  return "\n".join(env_lines)
145
123
 
146
124
 
147
- def _has_sub_agents(config: Config | None) -> bool:
148
- """Check if there are any sub-agent tools available for the main agent."""
149
- if config is not None:
150
- from klaude_code.config.sub_agent_model_helper import SubAgentModelHelper
151
-
152
- helper = SubAgentModelHelper(config)
153
- return bool(helper.get_enabled_sub_agent_tool_names())
154
-
155
- from klaude_code.protocol.sub_agent import sub_agent_tool_names
156
-
157
- return bool(sub_agent_tool_names(enabled_only=True))
158
-
159
-
160
125
  def load_system_prompt(
161
126
  model_name: str,
162
127
  protocol: llm_param.LLMClientProtocol,
@@ -165,12 +130,11 @@ def load_system_prompt(
165
130
  ) -> str:
166
131
  """Get system prompt content for the given model and sub-agent type."""
167
132
 
168
- # For codex_oauth protocol, use exact prompts without any additions.
133
+ # For codex_oauth protocol, use dynamic prompts from GitHub (no additions).
169
134
  if protocol == llm_param.LLMClientProtocol.CODEX_OAUTH:
170
- for model_key, prompt_path in CODEX_OAUTH_PROMPTS.items():
171
- if model_key in model_name:
172
- return _load_prompt_by_path(prompt_path)
173
- raise CodexUnsupportedModelError(f"codex_oauth protocol does not support model: {model_name}")
135
+ from klaude_code.llm.codex.prompt_sync import get_codex_instructions
136
+
137
+ return get_codex_instructions(model_name)
174
138
 
175
139
  # For antigravity protocol, use exact prompt without any additions.
176
140
  if protocol == llm_param.LLMClientProtocol.ANTIGRAVITY:
@@ -183,18 +147,13 @@ def load_system_prompt(
183
147
  base_prompt = _load_prompt_by_model(model_name)
184
148
 
185
149
  skills_prompt = ""
186
- sub_agent_prompt = ""
187
150
  if sub_agent_type is None:
188
151
  # Skills are progressive-disclosure: keep only metadata in the system prompt.
189
152
  from klaude_code.skill.manager import format_available_skills_for_system_prompt
190
153
 
191
154
  skills_prompt = format_available_skills_for_system_prompt()
192
155
 
193
- # Add sub-agent resume instructions if there are sub-agent tools available.
194
- if _has_sub_agents(config):
195
- sub_agent_prompt = "\n" + SUB_AGENT_COMMON_PROMPT_FOR_MAIN_AGENT
196
-
197
- return base_prompt + _build_env_info(model_name) + skills_prompt + sub_agent_prompt
156
+ return base_prompt + _build_env_info(model_name) + skills_prompt
198
157
 
199
158
 
200
159
  def load_agent_tools(
@@ -222,13 +181,13 @@ def load_agent_tools(
222
181
  else:
223
182
  tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE, tools.TODO_WRITE]
224
183
 
184
+ tool_names.append(tools.TASK)
225
185
  if config is not None:
226
186
  helper = SubAgentModelHelper(config)
227
- tool_names.extend(helper.get_enabled_sub_agent_tool_names())
187
+ if helper.check_availability_requirement(AVAILABILITY_IMAGE_MODEL):
188
+ tool_names.append(tools.IMAGE_GEN)
228
189
  else:
229
- from klaude_code.protocol.sub_agent import sub_agent_tool_names
230
-
231
- tool_names.extend(sub_agent_tool_names(enabled_only=True))
190
+ tool_names.append(tools.IMAGE_GEN)
232
191
 
233
192
  tool_names.extend([tools.MERMAID])
234
193
  # tool_names.extend([tools.MEMORY])
@@ -258,7 +217,6 @@ def load_agent_reminders(
258
217
  memory_reminder,
259
218
  at_file_reader_reminder,
260
219
  last_path_memory_reminder,
261
- file_changed_externally_reminder,
262
220
  image_reminder,
263
221
  skill_reminder,
264
222
  ]
@@ -311,7 +269,7 @@ class DefaultModelProfileProvider(ModelProfileProvider):
311
269
  if is_image_model:
312
270
  agent_system_prompt: str | None = None
313
271
  agent_tools: list[llm_param.ToolSchema] = []
314
- agent_reminders: list[Reminder] = []
272
+ agent_reminders: list[Reminder] = [at_file_reader_reminder, image_reminder]
315
273
  else:
316
274
  agent_system_prompt = load_system_prompt(
317
275
  model_name, llm_client.protocol, sub_agent_type, config=self._config
@@ -268,9 +268,7 @@ def _call_args_probably_modify_file(args: dict[str, object]) -> bool:
268
268
  return True
269
269
  # Batch edits.
270
270
  edits = args.get("edits")
271
- if isinstance(edits, list):
272
- return True
273
- return False
271
+ return bool(isinstance(edits, list))
274
272
 
275
273
 
276
274
  def _collect_file_operations(
@@ -478,12 +476,12 @@ async def _build_summary(
478
476
  previous_summary,
479
477
  cancel,
480
478
  )
481
- if messages_to_summarize or previous_summary
482
- else asyncio.sleep(0, result="No prior history.")
479
+ if messages_to_summarize
480
+ else asyncio.sleep(0, result=previous_summary or "")
483
481
  )
484
482
  prefix_task = _generate_task_prefix_summary(task_prefix_messages, llm_client, config, cancel)
485
483
  history_summary, task_prefix_summary = await asyncio.gather(history_task, prefix_task)
486
- return f"{COMPACTION_SUMMARY_PREFIX}\n\n<summary>{history_summary}\n\n---\n\n**Task Context (split task):**\n\n{task_prefix_summary}\n\n</summary>"
484
+ return f"{COMPACTION_SUMMARY_PREFIX}\n\n<summary>{history_summary}\n\n---\n\n**Task Context (current task):**\n\n{task_prefix_summary}\n\n</summary>"
487
485
 
488
486
  return await _generate_summary(
489
487
  messages_to_summarize,
@@ -24,7 +24,3 @@ def is_context_overflow(error_message: str | None) -> bool:
24
24
  if _STATUS_CODE_PATTERN.search(error_message):
25
25
  return True
26
26
  return any(pattern.search(error_message) for pattern in _OVERFLOW_PATTERNS)
27
-
28
-
29
- def get_overflow_patterns() -> list[re.Pattern[str]]:
30
- return list(_OVERFLOW_PATTERNS)
@@ -179,6 +179,21 @@ class AgentRuntime:
179
179
  )
180
180
  self._task_manager.register(operation.id, task, operation.session_id)
181
181
 
182
+ async def continue_agent(self, operation: op.ContinueAgentOperation) -> None:
183
+ """Continue agent execution without adding a new user message."""
184
+ agent = await self.ensure_agent(operation.session_id)
185
+
186
+ existing_active = self._task_manager.get(operation.id)
187
+ if existing_active is not None and not existing_active.task.done():
188
+ raise RuntimeError(f"Active task already registered for operation {operation.id}")
189
+
190
+ # Use empty input since we're continuing from existing history
191
+ empty_input = message.UserInputPayload(text="")
192
+ task: asyncio.Task[None] = asyncio.create_task(
193
+ self._run_agent_task(agent, empty_input, operation.id, operation.session_id)
194
+ )
195
+ self._task_manager.register(operation.id, task, operation.session_id)
196
+
182
197
  async def compact_session(self, operation: op.CompactSessionOperation) -> None:
183
198
  agent = await self.ensure_agent(operation.session_id)
184
199
 
@@ -525,6 +540,9 @@ class ExecutorContext:
525
540
  async def handle_run_agent(self, operation: op.RunAgentOperation) -> None:
526
541
  await self._agent_runtime.run_agent(operation)
527
542
 
543
+ async def handle_continue_agent(self, operation: op.ContinueAgentOperation) -> None:
544
+ await self._agent_runtime.continue_agent(operation)
545
+
528
546
  async def handle_compact_session(self, operation: op.CompactSessionOperation) -> None:
529
547
  await self._agent_runtime.compact_session(operation)
530
548
 
@@ -658,6 +676,39 @@ class ExecutorContext:
658
676
  )
659
677
  )
660
678
 
679
+ async def handle_change_compact_model(self, operation: op.ChangeCompactModelOperation) -> None:
680
+ """Handle a change compact model operation."""
681
+ agent = await self._agent_runtime.ensure_agent(operation.session_id)
682
+ config = load_config()
683
+
684
+ model_name = operation.model_name
685
+
686
+ if model_name is None:
687
+ # Clear explicit override and use main client for compaction
688
+ self.llm_clients.compact = None
689
+ agent.compact_llm_client = None
690
+ display_model = "(inherit from main agent)"
691
+ else:
692
+ # Create new client for compaction
693
+ llm_config = config.get_model_config(model_name)
694
+ new_client = create_llm_client(llm_config)
695
+ self.llm_clients.compact = new_client
696
+ agent.compact_llm_client = new_client
697
+ display_model = new_client.model_name
698
+
699
+ if operation.save_as_default:
700
+ config.compact_model = model_name
701
+ await config.save()
702
+
703
+ saved_note = " (saved in ~/.klaude/klaude-config.yaml)" if operation.save_as_default else ""
704
+ await self.emit_event(
705
+ events.CommandOutputEvent(
706
+ session_id=agent.session.id,
707
+ command_name=commands.CommandName.SUB_AGENT_MODEL,
708
+ content=f"Compact model: {display_model}{saved_note}",
709
+ )
710
+ )
711
+
661
712
  async def handle_clear_session(self, operation: op.ClearSessionOperation) -> None:
662
713
  await self._agent_runtime.clear_session(operation.session_id)
663
714
 
@@ -745,11 +796,6 @@ class ExecutorContext:
745
796
  return None
746
797
  return active.task
747
798
 
748
- def has_active_task(self, submission_id: str) -> bool:
749
- """Return True if a task is registered for the submission id."""
750
-
751
- return self.task_manager.get(submission_id) is not None
752
-
753
799
 
754
800
  class Executor:
755
801
  """
@@ -6,6 +6,7 @@ from dataclasses import dataclass
6
6
  from dataclasses import field as dataclass_field
7
7
 
8
8
  from klaude_code.llm.client import LLMClientABC
9
+ from klaude_code.protocol import tools
9
10
  from klaude_code.protocol.tools import SubAgentType
10
11
 
11
12
 
@@ -26,7 +27,14 @@ class LLMClients:
26
27
 
27
28
  if sub_agent_type is None:
28
29
  return self.main
29
- return self.sub_clients.get(sub_agent_type) or self.main
30
+ client = self.sub_clients.get(sub_agent_type)
31
+ if client is not None:
32
+ return client
33
+ if sub_agent_type != tools.TASK:
34
+ fallback = self.sub_clients.get(tools.TASK)
35
+ if fallback is not None:
36
+ return fallback
37
+ return self.main
30
38
 
31
39
  def get_compact_client(self) -> LLMClientABC:
32
40
  """Return compact client if configured, otherwise main client."""
@@ -72,16 +72,16 @@ The user will primarily request you perform software engineering tasks. This inc
72
72
  - Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.
73
73
 
74
74
  ## Tool usage policy
75
- - When doing file search, prefer to use the Explore tool in order to reduce context usage.
75
+ - When doing file search, prefer to use the Task tool in order to reduce context usage.
76
76
  - You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.
77
77
  - If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks. For example, if you need to launch multiple agents in parallel, send a single message with multiple Task tool calls.
78
78
  - Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
79
- - VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Explore tool instead of running search commands directly.
79
+ - VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Task tool with subagent_type=Explore instead of running search commands directly.
80
80
  <example>
81
81
  user: Where are errors from the client handled?
82
- assistant: [Uses the Explore tool to find the files that handle client errors instead of using Glob or Grep directly]
82
+ assistant: [Uses the Task tool with subagent_type=Explore to find the files that handle client errors instead of using Glob or Grep directly]
83
83
  </example>
84
84
  <example>
85
85
  user: What is the codebase structure?
86
- assistant: [Uses the Explore tool]
86
+ assistant: [Uses the Task tool with subagent_type=Explore]
87
87
  </example>