kiwi-code 0.0.513__tar.gz → 0.0.514__tar.gz

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 (143) hide show
  1. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/PKG-INFO +1 -1
  2. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/pyproject.toml +1 -1
  3. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/role_mode.py +69 -24
  4. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_dashboard.py +9 -6
  5. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_role_mode_injection.py +76 -18
  6. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/uv.lock +1 -1
  7. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/.github/workflows/publish.yml +0 -0
  8. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/.github/workflows/test.yml +0 -0
  9. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/.gitignore +0 -0
  10. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/.python-version +0 -0
  11. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/Makefile +0 -0
  12. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/README.md +0 -0
  13. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/docs/runtime_audit.html +0 -0
  14. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/docs/social_login_e2e.md +0 -0
  15. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/scripts/set_edition.py +0 -0
  16. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/scripts/ws_auth_check.py +0 -0
  17. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/common/__init__.py +0 -0
  18. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/common/banner.py +0 -0
  19. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/__init__.py +0 -0
  20. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/_edition.py +0 -0
  21. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/auth.py +0 -0
  22. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/autocode.py +0 -0
  23. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/checkpoints.py +0 -0
  24. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/cli.py +0 -0
  25. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/client.py +0 -0
  26. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/commands.py +0 -0
  27. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/edition.py +0 -0
  28. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/human_questions.py +0 -0
  29. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/io_threads.py +0 -0
  30. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/logger.py +0 -0
  31. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/models.py +0 -0
  32. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/oauth.py +0 -0
  33. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/run_activity.py +0 -0
  34. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/runtime_manager.py +0 -0
  35. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/server.py +0 -0
  36. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/subagents.py +0 -0
  37. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_cli/terminal_mode.py +0 -0
  38. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/__init__.py +0 -0
  39. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/__main__.py +0 -0
  40. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/auth.py +0 -0
  41. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/checkpoints.py +0 -0
  42. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/cli.py +0 -0
  43. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/commands.py +0 -0
  44. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/constants.py +0 -0
  45. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/__init__.py +0 -0
  46. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/batch.py +0 -0
  47. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/codelens.py +0 -0
  48. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/context.py +0 -0
  49. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/edits.py +0 -0
  50. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/handler.py +0 -0
  51. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/paths.py +0 -0
  52. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/read.py +0 -0
  53. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/search.py +0 -0
  54. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/text.py +0 -0
  55. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/upload.py +0 -0
  56. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/write.py +0 -0
  57. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/handlers.py +0 -0
  58. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/link.py +0 -0
  59. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/main.py +0 -0
  60. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/restricted.py +0 -0
  61. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/sessions.py +0 -0
  62. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/state.py +0 -0
  63. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/tui_bridge.py +0 -0
  64. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/ui.py +0 -0
  65. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_runtime/uploads.py +0 -0
  66. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/__init__.py +0 -0
  67. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/attention.py +0 -0
  68. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/autoupdate.py +0 -0
  69. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/inline_file_picker.py +0 -0
  70. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/latex_unicode.py +0 -0
  71. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/main.py +0 -0
  72. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/random_words.py +0 -0
  73. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/runtime_agent.py +0 -0
  74. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/__init__.py +0 -0
  75. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/attach_content.py +0 -0
  76. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/command_result.py +0 -0
  77. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/detach_files.py +0 -0
  78. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/file_browser.py +0 -0
  79. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/help.py +0 -0
  80. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/id_picker.py +0 -0
  81. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/login.py +0 -0
  82. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/runtime_cleanup.py +0 -0
  83. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/runtime_logs.py +0 -0
  84. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/slash_picker.py +0 -0
  85. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_activity_view.py +0 -0
  86. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_dashboard_css.py +0 -0
  87. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_dashboard_helpers.py +0 -0
  88. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_dashboard_widgets.py +0 -0
  89. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_question_widgets.py +0 -0
  90. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_status_line.py +0 -0
  91. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_subagent_widgets.py +0 -0
  92. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/slash_commands.py +0 -0
  93. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/status_words.py +0 -0
  94. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/term_app.py +0 -0
  95. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/terminal_theme.py +0 -0
  96. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/ui_settings.py +0 -0
  97. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/widgets.py +0 -0
  98. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/src/kiwi_tui/worktrees.py +0 -0
  99. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/test_hello.py +0 -0
  100. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/__init__.py +0 -0
  101. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/conftest.py +0 -0
  102. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_account_server.py +0 -0
  103. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_attention.py +0 -0
  104. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_autocode_actions.py +0 -0
  105. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_autoupdate.py +0 -0
  106. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_batch_fs_tool.py +0 -0
  107. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_checkpoints.py +0 -0
  108. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_cli_help.py +0 -0
  109. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_command_tips.py +0 -0
  110. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_copy_path_fs_tool.py +0 -0
  111. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_fs_edit_fidelity.py +0 -0
  112. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_human_questions.py +0 -0
  113. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_imports.py +0 -0
  114. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_io_threads.py +0 -0
  115. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_login_social.py +0 -0
  116. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_math_and_tables.py +0 -0
  117. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_oauth.py +0 -0
  118. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_read_file_streaming.py +0 -0
  119. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_reexec_kiwi.py +0 -0
  120. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_reply_selection.py +0 -0
  121. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_run_activity.py +0 -0
  122. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_run_steering.py +0 -0
  123. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_runtime_cleanup_per_account.py +0 -0
  124. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_runtime_connection.py +0 -0
  125. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_runtime_edit_diffs.py +0 -0
  126. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_runtime_fixes.py +0 -0
  127. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_runtime_imports_on_windows.py +0 -0
  128. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_runtime_log_trimming.py +0 -0
  129. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_runtime_social_login.py +0 -0
  130. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_search_in_files_fs_tool.py +0 -0
  131. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_slash_commands.py +0 -0
  132. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_stats_command.py +0 -0
  133. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_status_line.py +0 -0
  134. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_subagents.py +0 -0
  135. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_term_dashboard_ui.py +0 -0
  136. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_terminal_mode.py +0 -0
  137. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_terminal_theme.py +0 -0
  138. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_toasts.py +0 -0
  139. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_tokens.py +0 -0
  140. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_tui_interactive_runtime.py +0 -0
  141. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_tui_palette.py +0 -0
  142. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_unified_diff.py +0 -0
  143. {kiwi_code-0.0.513 → kiwi_code-0.0.514}/tests/test_worktrees.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: kiwi-code
3
- Version: 0.0.513
3
+ Version: 0.0.514
4
4
  Summary: A textual-based terminal user interface application
5
5
  Project-URL: Homepage, https://kiwicode.ai
6
6
  Author-email: Anurag <anurag@meetkiwi.co>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kiwi-code"
3
- version = "0.0.513"
3
+ version = "0.0.514"
4
4
  description = "A textual-based terminal user interface application"
5
5
  readme = {file = "README.md", content-type = "text/markdown"}
6
6
  requires-python = ">=3.11,<4.0"
@@ -113,7 +113,20 @@ def mode_skill_line(*, role: str, mode: str) -> str:
113
113
 
114
114
 
115
115
 
116
+ SYSTEM_PROMPT_HEADING = "# System Prompt"
117
+ MODE_SKILL_INTRO = "Plus use this exact mode skill(only)."
118
+
119
+ # Wording sent by older versions; still stripped when rendering stored history.
120
+ _LEGACY_MODE_SKILL_INTRO = "Plus extract this exact mode skill(only)."
121
+
122
+
116
123
  def _general_role_line(role: str) -> str:
124
+ return (
125
+ f"Your Current Role is : {role}. Use autocode.{role}.general skill and act accordingly."
126
+ )
127
+
128
+
129
+ def _legacy_general_role_line(role: str) -> str:
117
130
  return (
118
131
  f"Your Current Role is : {role}. Fetch autocode.{role}.general skill and act accordingly."
119
132
  )
@@ -122,14 +135,35 @@ def _general_role_line(role: str) -> str:
122
135
  _SKILL_LINE_RE = re.compile(r"^autocode\.([a-z\-]+)\.(plan|build|validate) skill$")
123
136
 
124
137
 
125
- def strip_injected_prefix(text: str) -> str:
126
- """Strip the silent injection prefix (if present) from a stored user prompt.
138
+ def _injected_lines_end(lines: list[str], start: int) -> int:
139
+ """Index just past the injected role/mode lines found at `start` (== start if none)."""
140
+ i = start
141
+ current = (lines[i] or "").strip() if i < len(lines) else ""
142
+ for r in CANONICAL_ROLES:
143
+ if current in (_general_role_line(r), _legacy_general_role_line(r)):
144
+ i += 1
145
+ break
146
+
147
+ if i < len(lines) and (lines[i] or "").strip() in (MODE_SKILL_INTRO, _LEGACY_MODE_SKILL_INTRO):
148
+ # Skip the "Plus..." line
149
+ i += 1
150
+ # Skip the skill line only if it matches the expected pattern.
151
+ if i < len(lines) and _SKILL_LINE_RE.match((lines[i] or "").strip()):
152
+ i += 1
153
+ return i
154
+
155
+
156
+ def strip_injected_prompt(text: str) -> str:
157
+ """Strip the silent role/mode injection (if present) from a stored user prompt.
127
158
 
128
159
  The backend persists the full user_input we send, which will include the
129
- injected prefix. When rendering conversation history, we must not show these
160
+ injected lines. When rendering conversation history, we must not show these
130
161
  injected lines to the user.
131
162
 
132
- We only strip when the prefix matches *exactly* our known injection format,
163
+ Current format: the user's text, then a "# System Prompt" block at the end.
164
+ Older formats put the injected lines (with or without the heading) first.
165
+
166
+ We only strip when the lines match *exactly* our known injection format,
133
167
  to avoid altering legitimate user content.
134
168
  """
135
169
  raw = (text or "")
@@ -140,19 +174,29 @@ def strip_injected_prefix(text: str) -> str:
140
174
  if not lines:
141
175
  return raw
142
176
 
177
+ # Current format: a trailing "# System Prompt" block that runs to the end.
178
+ for h in range(len(lines) - 1, -1, -1):
179
+ if (lines[h] or "").strip() != SYSTEM_PROMPT_HEADING:
180
+ continue
181
+ end = _injected_lines_end(lines, h + 1)
182
+ if end > h + 1 and all(not (rest or "").strip() for rest in lines[end:]):
183
+ body = lines[:h]
184
+ while body and body[-1] == "":
185
+ body.pop()
186
+ return "\n".join(body)
187
+ break
188
+
189
+ # Older formats: injected lines at the start.
143
190
  i = 0
144
- first = (lines[0] or "").strip()
145
- for r in CANONICAL_ROLES:
146
- if first == _general_role_line(r):
147
- i = 1
148
- break
149
-
150
- if i < len(lines) and (lines[i] or "").strip() == "Plus extract this exact mode skill(only).":
151
- # Skip the "Plus..." line
152
- i += 1
153
- # Skip the skill line only if it matches the expected pattern.
154
- if i < len(lines) and _SKILL_LINE_RE.match((lines[i] or "").strip()):
155
- i += 1
191
+ has_heading = (lines[0] or "").strip() == SYSTEM_PROMPT_HEADING
192
+ if has_heading:
193
+ i = 1
194
+ body_start = i
195
+ i = _injected_lines_end(lines, i)
196
+
197
+ # A heading with no injected lines after it is the user's own text; keep it.
198
+ if has_heading and i == body_start:
199
+ return raw
156
200
 
157
201
  # Skip blank separator lines
158
202
  while i < len(lines) and lines[i] == "":
@@ -223,7 +267,8 @@ def build_model_user_prompt(
223
267
  ) -> tuple[str, RoleModeState]:
224
268
  """Return (model_user_prompt, new_state).
225
269
 
226
- The returned prompt includes any required silent injection prefix.
270
+ The user's text comes first; any required silent injection follows it as a
271
+ "# System Prompt" block.
227
272
 
228
273
  Injection rules:
229
274
  - If first model prompt OR role changed: inject the role+general line.
@@ -236,16 +281,16 @@ def build_model_user_prompt(
236
281
  inject_general = is_first or state.role_changed
237
282
  inject_mode = is_first or state.role_changed or state.mode_changed
238
283
 
239
- prefix = ""
284
+ injected: list[str] = []
240
285
  if inject_general:
241
- prefix += (
242
- f"Your Current Role is : {state.role}. Fetch autocode.{state.role}.general skill and act accordingly.\n"
243
- )
286
+ injected.append(_general_role_line(state.role))
244
287
  if inject_mode:
245
- prefix += "Plus extract this exact mode skill(only).\n"
246
- prefix += mode_skill_line(role=state.role, mode=state.mode) + "\n"
288
+ injected.append(MODE_SKILL_INTRO)
289
+ injected.append(mode_skill_line(role=state.role, mode=state.mode))
247
290
 
248
- model_prompt = prefix + (raw_user_prompt or "")
291
+ model_prompt = raw_user_prompt or ""
292
+ if injected:
293
+ model_prompt += "\n\n" + "\n".join([SYSTEM_PROMPT_HEADING, *injected])
249
294
 
250
295
  new_state = replace(
251
296
  state,
@@ -28,13 +28,14 @@ from kiwi_tui.random_words import random_verb
28
28
  from kiwi_tui.role_mode import (
29
29
  RoleModeState,
30
30
  ROLE_DISPLAY,
31
+ SYSTEM_PROMPT_HEADING,
31
32
  allowed_modes_for_role,
32
33
  build_model_user_prompt,
33
34
  parse_mode_arg,
34
35
  parse_role_arg,
35
36
  set_mode,
36
37
  set_role,
37
- strip_injected_prefix,
38
+ strip_injected_prompt,
38
39
  )
39
40
  from kiwi_tui.screens.file_browser import FileBrowserScreen
40
41
  from kiwi_tui.screens.help import HelpScreen
@@ -3813,7 +3814,7 @@ class TermDashboardScreen(Screen):
3813
3814
  show(f"Could not read the queue ({lines[0]}).", is_error=True)
3814
3815
  return
3815
3816
  # AutoCode prompts are stored with their role/mode lines; show what was typed.
3816
- prompts = [strip_injected_prefix(prompt) for prompt in found[0]]
3817
+ prompts = [strip_injected_prompt(prompt) for prompt in found[0]]
3817
3818
  if run_id == self.current_run_id:
3818
3819
  self._waiting_locally()[:] = prompts
3819
3820
  self._set_queued_count(len(prompts))
@@ -3827,7 +3828,7 @@ class TermDashboardScreen(Screen):
3827
3828
  known = self._waiting_locally() if run_id == self.current_run_id else []
3828
3829
  expected = known[number - 1] if number <= len(known) else None
3829
3830
  is_expected = (
3830
- (lambda stored: strip_injected_prefix(stored).strip() == expected.strip())
3831
+ (lambda stored: strip_injected_prompt(stored).strip() == expected.strip())
3831
3832
  if expected is not None else None
3832
3833
  )
3833
3834
  outcome: list[QueueRemoval] = []
@@ -3857,7 +3858,7 @@ class TermDashboardScreen(Screen):
3857
3858
  if number <= len(known):
3858
3859
  del known[number - 1]
3859
3860
  self._set_queued_count(result.waiting)
3860
- text = " ".join(strip_injected_prefix(result.removed).split())
3861
+ text = " ".join(strip_injected_prompt(result.removed).split())
3861
3862
  toast(
3862
3863
  f"Removed: {text if len(text) <= 60 else text[:59] + '…'} ({result.waiting} waiting)",
3863
3864
  title="/queue",
@@ -4537,8 +4538,10 @@ class TermDashboardScreen(Screen):
4537
4538
  )
4538
4539
  user_text = str(extracted_in or "").strip()
4539
4540
  logger.info(f"user text : {user_text}")
4540
- if is_autocode_run or ("Your Current Role is" in user_text and "autocode." in user_text):
4541
- user_text = strip_injected_prefix(user_text)
4541
+ if is_autocode_run or SYSTEM_PROMPT_HEADING in user_text or (
4542
+ "Your Current Role is" in user_text and "autocode." in user_text
4543
+ ):
4544
+ user_text = strip_injected_prompt(user_text)
4542
4545
 
4543
4546
  logger.info(f"User Text after stripping : {user_text}")
4544
4547
  assistant_text = None
@@ -14,7 +14,7 @@ from kiwi_tui.role_mode import (
14
14
  parse_role_arg,
15
15
  set_mode,
16
16
  set_role,
17
- strip_injected_prefix,
17
+ strip_injected_prompt,
18
18
  )
19
19
 
20
20
 
@@ -58,14 +58,14 @@ def test_first_prompt_injects_general_and_mode_lines() -> None:
58
58
  state = RoleModeState() # default
59
59
  model_prompt, new_state = build_model_user_prompt(state, raw_user_prompt="Hello")
60
60
 
61
- assert "Your Current Role is :" in model_prompt
62
- assert (
63
- f"Your Current Role is : {DEFAULT_ROLE}. Fetch autocode.{DEFAULT_ROLE}.general skill"
64
- in model_prompt
61
+ assert model_prompt == (
62
+ "Hello\n"
63
+ "\n"
64
+ "# System Prompt\n"
65
+ f"Your Current Role is : {DEFAULT_ROLE}. Use autocode.{DEFAULT_ROLE}.general skill and act accordingly.\n"
66
+ "Plus use this exact mode skill(only).\n"
67
+ f"autocode.{DEFAULT_ROLE}.{DEFAULT_MODE} skill"
65
68
  )
66
- assert "Plus extract this exact mode skill(only)." in model_prompt
67
- assert f"autocode.{DEFAULT_ROLE}.{DEFAULT_MODE} skill" in model_prompt
68
- assert model_prompt.endswith("Hello")
69
69
 
70
70
  assert new_state.has_sent_model_prompt is True
71
71
  assert new_state.role_changed is False
@@ -82,10 +82,10 @@ def test_role_change_forces_mode_build_and_triggers_injection_on_next_prompt() -
82
82
 
83
83
  model_prompt, state3 = build_model_user_prompt(state2, raw_user_prompt="Next")
84
84
 
85
+ assert model_prompt.startswith("Next\n\n# System Prompt\n")
85
86
  assert f"Your Current Role is : {ROLE_ZERO_TO_ONE_ENGINEER}." in model_prompt
86
- assert "Plus extract this exact mode skill(only)." in model_prompt
87
- assert "autocode.zero-to-one-engineer.build skill" in model_prompt
88
- assert model_prompt.endswith("Next")
87
+ assert "Plus use this exact mode skill(only)." in model_prompt
88
+ assert model_prompt.endswith("autocode.zero-to-one-engineer.build skill")
89
89
 
90
90
  assert state3.role_changed is False
91
91
  assert state3.mode_changed is False
@@ -98,9 +98,13 @@ def test_mode_change_triggers_only_mode_injection_not_general() -> None:
98
98
 
99
99
  model_prompt, _ = build_model_user_prompt(state2, raw_user_prompt="Ping")
100
100
 
101
- assert "Your Current Role is :" not in model_prompt
102
- assert "Plus extract this exact mode skill(only)." in model_prompt
103
- assert "autocode.senior-software-engineer.validate skill" in model_prompt
101
+ assert model_prompt == (
102
+ "Ping\n"
103
+ "\n"
104
+ "# System Prompt\n"
105
+ "Plus use this exact mode skill(only).\n"
106
+ "autocode.senior-software-engineer.validate skill"
107
+ )
104
108
 
105
109
 
106
110
  def test_x2se_disallows_plan_without_error() -> None:
@@ -112,20 +116,74 @@ def test_x2se_disallows_plan_without_error() -> None:
112
116
  assert state2.mode == MODE_BUILD
113
117
 
114
118
 
115
- def test_strip_injected_prefix_strips_general_and_mode_block() -> None:
119
+ def test_no_change_sends_prompt_without_system_prompt() -> None:
120
+ state = RoleModeState(has_sent_model_prompt=True)
121
+ model_prompt, _ = build_model_user_prompt(state, raw_user_prompt="Plain")
122
+ assert model_prompt == "Plain"
123
+
124
+
125
+ def test_strip_injected_prompt_strips_trailing_general_and_mode_block() -> None:
126
+ injected = (
127
+ "Hello world\n"
128
+ "\n"
129
+ "# System Prompt\n"
130
+ f"Your Current Role is : {DEFAULT_ROLE}. Use autocode.{DEFAULT_ROLE}.general skill and act accordingly.\n"
131
+ "Plus use this exact mode skill(only).\n"
132
+ f"autocode.{DEFAULT_ROLE}.{DEFAULT_MODE} skill"
133
+ )
134
+ assert strip_injected_prompt(injected) == "Hello world"
135
+
136
+
137
+ def test_strip_injected_prompt_strips_trailing_mode_only_block() -> None:
138
+ injected = (
139
+ "Ping\n"
140
+ "\n"
141
+ "# System Prompt\n"
142
+ "Plus use this exact mode skill(only).\n"
143
+ "autocode.senior-software-engineer.validate skill"
144
+ )
145
+ assert strip_injected_prompt(injected) == "Ping"
146
+
147
+
148
+ def test_strip_injected_prompt_round_trips_built_prompt() -> None:
149
+ raw = "first line\n\nsecond paragraph"
150
+ model_prompt, _ = build_model_user_prompt(RoleModeState(), raw_user_prompt=raw)
151
+ assert strip_injected_prompt(model_prompt) == raw
152
+
153
+
154
+ def test_strip_injected_prompt_keeps_users_own_heading() -> None:
155
+ text = "# System Prompt\nthis is my own markdown"
156
+ assert strip_injected_prompt(text) == text
157
+ trailing = "notes\n\n# System Prompt\nmy own section"
158
+ assert strip_injected_prompt(trailing) == trailing
159
+
160
+
161
+ def test_strip_injected_prompt_strips_leading_heading_block() -> None:
162
+ # Format briefly sent before the block moved below the user's text.
163
+ injected = (
164
+ "# System Prompt\n"
165
+ f"Your Current Role is : {DEFAULT_ROLE}. Use autocode.{DEFAULT_ROLE}.general skill and act accordingly.\n"
166
+ "Plus use this exact mode skill(only).\n"
167
+ f"autocode.{DEFAULT_ROLE}.{DEFAULT_MODE} skill\n"
168
+ "hello"
169
+ )
170
+ assert strip_injected_prompt(injected) == "hello"
171
+
172
+
173
+ def test_strip_injected_prompt_strips_legacy_general_and_mode_block() -> None:
116
174
  injected = (
117
175
  f"Your Current Role is : {DEFAULT_ROLE}. Fetch autocode.{DEFAULT_ROLE}.general skill and act accordingly.\n"
118
176
  "Plus extract this exact mode skill(only).\n"
119
177
  f"autocode.{DEFAULT_ROLE}.{DEFAULT_MODE} skill\n"
120
178
  "Hello world"
121
179
  )
122
- assert strip_injected_prefix(injected) == "Hello world"
180
+ assert strip_injected_prompt(injected) == "Hello world"
123
181
 
124
182
 
125
- def test_strip_injected_prefix_strips_mode_only_block() -> None:
183
+ def test_strip_injected_prompt_strips_legacy_mode_only_block() -> None:
126
184
  injected = (
127
185
  "Plus extract this exact mode skill(only).\n"
128
186
  "autocode.senior-software-engineer.validate skill\n"
129
187
  "Ping"
130
188
  )
131
- assert strip_injected_prefix(injected) == "Ping"
189
+ assert strip_injected_prompt(injected) == "Ping"
@@ -412,7 +412,7 @@ wheels = [
412
412
 
413
413
  [[package]]
414
414
  name = "kiwi-code"
415
- version = "0.0.513"
415
+ version = "0.0.514"
416
416
  source = { editable = "." }
417
417
  dependencies = [
418
418
  { name = "codeglass" },
File without changes
File without changes
File without changes
File without changes
File without changes