kiwi-code 0.0.512__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.
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/PKG-INFO +1 -1
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/pyproject.toml +1 -1
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/autocode.py +33 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/terminal_mode.py +3 -2
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/role_mode.py +69 -24
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_dashboard.py +21 -11
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_autocode_actions.py +49 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_role_mode_injection.py +76 -18
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_term_dashboard_ui.py +53 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/uv.lock +1 -1
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/.github/workflows/publish.yml +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/.github/workflows/test.yml +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/.gitignore +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/.python-version +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/Makefile +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/README.md +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/docs/runtime_audit.html +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/docs/social_login_e2e.md +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/scripts/set_edition.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/scripts/ws_auth_check.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/common/__init__.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/common/banner.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/__init__.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/_edition.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/auth.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/checkpoints.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/cli.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/client.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/commands.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/edition.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/human_questions.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/io_threads.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/logger.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/models.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/oauth.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/run_activity.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/runtime_manager.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/server.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_cli/subagents.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/__init__.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/__main__.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/auth.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/checkpoints.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/cli.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/commands.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/constants.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/__init__.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/batch.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/codelens.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/context.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/edits.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/handler.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/paths.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/read.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/search.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/text.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/upload.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/fs/write.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/handlers.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/link.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/main.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/restricted.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/sessions.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/state.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/tui_bridge.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/ui.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_runtime/uploads.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/__init__.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/attention.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/autoupdate.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/inline_file_picker.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/latex_unicode.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/main.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/random_words.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/runtime_agent.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/__init__.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/attach_content.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/command_result.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/detach_files.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/file_browser.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/help.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/id_picker.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/login.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/runtime_cleanup.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/runtime_logs.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/slash_picker.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_activity_view.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_dashboard_css.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_dashboard_helpers.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_dashboard_widgets.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_question_widgets.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_status_line.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/screens/term_subagent_widgets.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/slash_commands.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/status_words.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/term_app.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/terminal_theme.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/ui_settings.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/widgets.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/src/kiwi_tui/worktrees.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/test_hello.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/__init__.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/conftest.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_account_server.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_attention.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_autoupdate.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_batch_fs_tool.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_checkpoints.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_cli_help.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_command_tips.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_copy_path_fs_tool.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_fs_edit_fidelity.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_human_questions.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_imports.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_io_threads.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_login_social.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_math_and_tables.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_oauth.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_read_file_streaming.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_reexec_kiwi.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_reply_selection.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_run_activity.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_run_steering.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_runtime_cleanup_per_account.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_runtime_connection.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_runtime_edit_diffs.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_runtime_fixes.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_runtime_imports_on_windows.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_runtime_log_trimming.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_runtime_social_login.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_search_in_files_fs_tool.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_slash_commands.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_stats_command.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_status_line.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_subagents.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_terminal_mode.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_terminal_theme.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_toasts.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_tokens.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_tui_interactive_runtime.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_tui_palette.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_unified_diff.py +0 -0
- {kiwi_code-0.0.512 → kiwi_code-0.0.514}/tests/test_worktrees.py +0 -0
|
@@ -8,6 +8,11 @@ contains "autocode" in any spelling: "Autocode", "AutoCodeClaude",
|
|
|
8
8
|
When two of them have the same name ignoring case, spaces and punctuation
|
|
9
9
|
("AutocodeClaude" and "AutoCodeClaude", "Autocode OpenAI Flash" and
|
|
10
10
|
"AutocodeOpenaiFlash"), only the most recently created one is kept.
|
|
11
|
+
|
|
12
|
+
Which one a new session starts with depends on the account (``default_id_for``):
|
|
13
|
+
Work (Enterprise, api.meetkiwi.ai) starts with "Autocode OpenAI Flash", Personal
|
|
14
|
+
(Subscription, api.runkai.ai) and every other server with "Autocode". The VS Code
|
|
15
|
+
extension makes the same choice (src/defaultAgent.ts).
|
|
11
16
|
"""
|
|
12
17
|
|
|
13
18
|
import re
|
|
@@ -17,11 +22,17 @@ from datetime import datetime, timezone
|
|
|
17
22
|
from pykiwiai import AuthenticatedClient
|
|
18
23
|
from pykiwiai.api.actions import list_system_actions_v1_actions_system_actions_get
|
|
19
24
|
|
|
25
|
+
from kiwi_cli.server import SERVER_PRESETS
|
|
26
|
+
|
|
20
27
|
# Fetch every system action in one page: paging with offset is not reliable on
|
|
21
28
|
# the servers (offset > 0 returns an empty page).
|
|
22
29
|
_SYSTEM_ACTIONS_LIMIT = 500
|
|
23
30
|
|
|
24
31
|
|
|
32
|
+
#: The default on the Work (Enterprise) server, compared as _normalize compares names.
|
|
33
|
+
_ENTERPRISE_DEFAULT = "autocodeopenaiflash"
|
|
34
|
+
|
|
35
|
+
|
|
25
36
|
def _normalize(name: object) -> str:
|
|
26
37
|
return re.sub(r"[^a-z0-9]", "", str(name or "").lower())
|
|
27
38
|
|
|
@@ -64,6 +75,28 @@ class AutocodeActions:
|
|
|
64
75
|
return action.id
|
|
65
76
|
return self.actions[0].id if self.actions else None
|
|
66
77
|
|
|
78
|
+
def name_of(self, action_id: object) -> str | None:
|
|
79
|
+
"""The name of the AutoCode action ``action_id``, or None if it is not one."""
|
|
80
|
+
for action in self.actions:
|
|
81
|
+
if action.id == action_id:
|
|
82
|
+
return action.name
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
def default_id_for(self, backend_url: object) -> str | None:
|
|
86
|
+
"""The action a new session starts with on ``backend_url``: "Autocode OpenAI
|
|
87
|
+
Flash" on the Work (Enterprise) server when it has one, else ``default_id``."""
|
|
88
|
+
if is_enterprise_backend(backend_url):
|
|
89
|
+
for action in self.actions:
|
|
90
|
+
if _normalize(action.name) == _ENTERPRISE_DEFAULT:
|
|
91
|
+
return action.id
|
|
92
|
+
return self.default_id
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def is_enterprise_backend(backend_url: object) -> bool:
|
|
96
|
+
"""True for the Work (Enterprise) server, api.meetkiwi.ai."""
|
|
97
|
+
url = str(backend_url or "").strip().rstrip("/").lower()
|
|
98
|
+
return url == SERVER_PRESETS["work"]["http"]
|
|
99
|
+
|
|
67
100
|
|
|
68
101
|
def _created(action: AutocodeAction) -> datetime:
|
|
69
102
|
"""When the action was created. Falls back to the timestamp in its ObjectId."""
|
|
@@ -227,10 +227,11 @@ def _default_action_id(session: _TerminalSession) -> str:
|
|
|
227
227
|
found = fetch_autocode_actions(session.client.client)
|
|
228
228
|
if found.status_code in (401, 403) and _refresh_tokens(session):
|
|
229
229
|
found = fetch_autocode_actions(session.client.client)
|
|
230
|
-
|
|
230
|
+
default_id = found.default_id_for(session.backend_url)
|
|
231
|
+
if default_id is None:
|
|
231
232
|
detail = "no AutoCode actions on this server" if found.ok else f"HTTP {found.status_code}"
|
|
232
233
|
raise TerminalModeError(f"Could not find the AutoCode action ({detail}). Pass --action-id to choose one.")
|
|
233
|
-
return
|
|
234
|
+
return default_id
|
|
234
235
|
|
|
235
236
|
|
|
236
237
|
def _build_authenticated_session(server: str | None) -> _TerminalSession:
|
|
@@ -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
|
|
126
|
-
"""
|
|
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
|
|
160
|
+
injected lines. When rendering conversation history, we must not show these
|
|
130
161
|
injected lines to the user.
|
|
131
162
|
|
|
132
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
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
|
-
|
|
284
|
+
injected: list[str] = []
|
|
240
285
|
if inject_general:
|
|
241
|
-
|
|
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
|
-
|
|
246
|
-
|
|
288
|
+
injected.append(MODE_SKILL_INTRO)
|
|
289
|
+
injected.append(mode_skill_line(role=state.role, mode=state.mode))
|
|
247
290
|
|
|
248
|
-
model_prompt =
|
|
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
|
-
|
|
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
|
|
@@ -1503,7 +1504,8 @@ class TermDashboardScreen(Screen):
|
|
|
1503
1504
|
cwd = str(Path.cwd())
|
|
1504
1505
|
cwd_short = cwd if len(cwd) <= 44 else f"{cwd[:20]}…{cwd[-20:]}"
|
|
1505
1506
|
|
|
1506
|
-
|
|
1507
|
+
# While its name loads, an action shows as "…", not as its id.
|
|
1508
|
+
action = self.current_action_name or ("…" if self.current_action_id else "")
|
|
1507
1509
|
action = str(action)
|
|
1508
1510
|
action_short = action if len(action) <= 16 else (action[:12] + "…")
|
|
1509
1511
|
|
|
@@ -1643,7 +1645,7 @@ class TermDashboardScreen(Screen):
|
|
|
1643
1645
|
def _refresh_welcome_meta(self) -> None:
|
|
1644
1646
|
"""Update action name + run id displayed in the welcome header."""
|
|
1645
1647
|
try:
|
|
1646
|
-
action = self.current_action_name or self.current_action_id
|
|
1648
|
+
action = self.current_action_name or ("…" if self.current_action_id else "—")
|
|
1647
1649
|
run = self.current_run_id or "—"
|
|
1648
1650
|
self.query_one("#term-welcome-account", Static).update(self._account_text())
|
|
1649
1651
|
self.query_one("#term-welcome-action", Static).update(f"Action: {action}")
|
|
@@ -1764,9 +1766,14 @@ class TermDashboardScreen(Screen):
|
|
|
1764
1766
|
except Exception as e:
|
|
1765
1767
|
logger.debug(f"Loading AutoCode actions failed: {e}")
|
|
1766
1768
|
if not self.current_action_id and self._autocode is not None:
|
|
1767
|
-
self.current_action_id = self._autocode.
|
|
1769
|
+
self.current_action_id = self._autocode.default_id_for(self._autocode_backend) or ""
|
|
1768
1770
|
requested_id = self.current_action_id
|
|
1769
|
-
|
|
1771
|
+
# An AutoCode action's name is already in the list just fetched: show it with the
|
|
1772
|
+
# id, rather than the id first and the name a request later.
|
|
1773
|
+
known = self._autocode.name_of(requested_id) if self._autocode is not None and requested_id else None
|
|
1774
|
+
if known and not self.current_action_name:
|
|
1775
|
+
self.current_action_name = known
|
|
1776
|
+
if not requested_id or known:
|
|
1770
1777
|
self._refresh_top_bar()
|
|
1771
1778
|
self._refresh_hint_model()
|
|
1772
1779
|
self._refresh_input_label()
|
|
@@ -1779,7 +1786,8 @@ class TermDashboardScreen(Screen):
|
|
|
1779
1786
|
if resp.status_code == 200 and resp.parsed:
|
|
1780
1787
|
self.current_action_name = getattr(resp.parsed, "name", None) or requested_id
|
|
1781
1788
|
else:
|
|
1782
|
-
|
|
1789
|
+
# The name could not be found: the id is all there is to show.
|
|
1790
|
+
self.current_action_name = requested_id
|
|
1783
1791
|
self._refresh_top_bar()
|
|
1784
1792
|
self._refresh_hint_model()
|
|
1785
1793
|
self._refresh_input_label()
|
|
@@ -3806,7 +3814,7 @@ class TermDashboardScreen(Screen):
|
|
|
3806
3814
|
show(f"Could not read the queue ({lines[0]}).", is_error=True)
|
|
3807
3815
|
return
|
|
3808
3816
|
# AutoCode prompts are stored with their role/mode lines; show what was typed.
|
|
3809
|
-
prompts = [
|
|
3817
|
+
prompts = [strip_injected_prompt(prompt) for prompt in found[0]]
|
|
3810
3818
|
if run_id == self.current_run_id:
|
|
3811
3819
|
self._waiting_locally()[:] = prompts
|
|
3812
3820
|
self._set_queued_count(len(prompts))
|
|
@@ -3820,7 +3828,7 @@ class TermDashboardScreen(Screen):
|
|
|
3820
3828
|
known = self._waiting_locally() if run_id == self.current_run_id else []
|
|
3821
3829
|
expected = known[number - 1] if number <= len(known) else None
|
|
3822
3830
|
is_expected = (
|
|
3823
|
-
(lambda stored:
|
|
3831
|
+
(lambda stored: strip_injected_prompt(stored).strip() == expected.strip())
|
|
3824
3832
|
if expected is not None else None
|
|
3825
3833
|
)
|
|
3826
3834
|
outcome: list[QueueRemoval] = []
|
|
@@ -3850,7 +3858,7 @@ class TermDashboardScreen(Screen):
|
|
|
3850
3858
|
if number <= len(known):
|
|
3851
3859
|
del known[number - 1]
|
|
3852
3860
|
self._set_queued_count(result.waiting)
|
|
3853
|
-
text = " ".join(
|
|
3861
|
+
text = " ".join(strip_injected_prompt(result.removed).split())
|
|
3854
3862
|
toast(
|
|
3855
3863
|
f"Removed: {text if len(text) <= 60 else text[:59] + '…'} ({result.waiting} waiting)",
|
|
3856
3864
|
title="/queue",
|
|
@@ -4530,8 +4538,10 @@ class TermDashboardScreen(Screen):
|
|
|
4530
4538
|
)
|
|
4531
4539
|
user_text = str(extracted_in or "").strip()
|
|
4532
4540
|
logger.info(f"user text : {user_text}")
|
|
4533
|
-
if is_autocode_run or
|
|
4534
|
-
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)
|
|
4535
4545
|
|
|
4536
4546
|
logger.info(f"User Text after stripping : {user_text}")
|
|
4537
4547
|
assistant_text = None
|
|
@@ -154,3 +154,52 @@ def test_the_default_autocode_is_also_the_latest_one(monkeypatch):
|
|
|
154
154
|
)
|
|
155
155
|
found = fetch_autocode_actions(client=None)
|
|
156
156
|
assert [a.id for a in found.actions] == ["new"] and found.default_id == "new"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# The default by account: Work (Enterprise, api.meetkiwi.ai) starts with Autocode OpenAI
|
|
160
|
+
# Flash, Personal (Subscription, api.runkai.ai) and every other server with Autocode. The
|
|
161
|
+
# VS Code extension makes the same choice (src/defaultAgent.ts).
|
|
162
|
+
|
|
163
|
+
WORK = "https://api.meetkiwi.ai"
|
|
164
|
+
PERSONAL = "https://api.runkai.ai"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _flash_and_autocode(monkeypatch):
|
|
168
|
+
_respond(
|
|
169
|
+
monkeypatch,
|
|
170
|
+
200,
|
|
171
|
+
[
|
|
172
|
+
_doc("a", "Autocode"),
|
|
173
|
+
_doc("f1", "Autocode OpenAI Flash", created_at=datetime(2026, 5, 1)),
|
|
174
|
+
_doc("f2", "AutocodeOpenaiFlash", created_at=datetime(2026, 10, 24)),
|
|
175
|
+
_doc("c", "AutoCodeClaude", "claude"),
|
|
176
|
+
],
|
|
177
|
+
)
|
|
178
|
+
return fetch_autocode_actions(client=None)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@pytest.mark.parametrize("url", [WORK, WORK + "/", "https://API.meetkiwi.ai"])
|
|
182
|
+
def test_work_starts_with_the_latest_flash(monkeypatch, url):
|
|
183
|
+
assert _flash_and_autocode(monkeypatch).default_id_for(url) == "f2"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@pytest.mark.parametrize("url", [PERSONAL, "https://dev.api.myautobots.com", "http://localhost:8000", "", None])
|
|
187
|
+
def test_personal_and_other_servers_start_with_autocode(monkeypatch, url):
|
|
188
|
+
assert _flash_and_autocode(monkeypatch).default_id_for(url) == "a"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_work_without_flash_starts_with_autocode(monkeypatch):
|
|
192
|
+
_respond(monkeypatch, 200, [_doc("a", "Autocode"), _doc("c", "AutoCodeClaude", "claude")])
|
|
193
|
+
assert fetch_autocode_actions(client=None).default_id_for(WORK) == "a"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_no_actions_means_no_default_on_work_either():
|
|
197
|
+
assert AutocodeActions(status_code=200).default_id_for(WORK) is None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_name_of_gives_an_autocode_actions_name(monkeypatch):
|
|
201
|
+
found = _flash_and_autocode(monkeypatch)
|
|
202
|
+
assert found.name_of("f2") == "AutocodeOpenaiFlash"
|
|
203
|
+
assert found.name_of("a") == "Autocode"
|
|
204
|
+
assert found.name_of("unknown") is None
|
|
205
|
+
assert found.name_of(None) is 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
|
-
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
87
|
-
assert "autocode.zero-to-one-engineer.build skill"
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
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
|
|
180
|
+
assert strip_injected_prompt(injected) == "Hello world"
|
|
123
181
|
|
|
124
182
|
|
|
125
|
-
def
|
|
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
|
|
189
|
+
assert strip_injected_prompt(injected) == "Ping"
|
|
@@ -979,3 +979,56 @@ async def test_scrolling_up_while_a_reply_streams_stays_up_until_back_at_the_end
|
|
|
979
979
|
await pilot.pause(0.3)
|
|
980
980
|
await stream(5)
|
|
981
981
|
assert gap() <= 1
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
@pytest.mark.asyncio
|
|
985
|
+
async def test_an_action_shows_by_name_never_by_its_id_while_the_name_loads() -> None:
|
|
986
|
+
app = _TermDashboardTestApp()
|
|
987
|
+
async with app.run_test(size=(120, 60)) as pilot:
|
|
988
|
+
await pilot.pause(0.05)
|
|
989
|
+
screen = app.screen
|
|
990
|
+
screen.current_action_id = "6a0625b8ab5f80ba8ac5d012"
|
|
991
|
+
screen.current_action_name = None
|
|
992
|
+
screen._refresh_welcome_meta()
|
|
993
|
+
screen._refresh_top_bar()
|
|
994
|
+
await pilot.pause(0)
|
|
995
|
+
|
|
996
|
+
assert str(screen.query_one("#term-welcome-action").content) == "Action: …"
|
|
997
|
+
assert "6a0625b8" not in _top_bar_text(screen)
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
@pytest.mark.asyncio
|
|
1001
|
+
async def test_the_default_autocode_is_named_from_its_list_without_a_second_request(monkeypatch) -> None:
|
|
1002
|
+
from kiwi_cli.autocode import AutocodeAction, AutocodeActions
|
|
1003
|
+
from pykiwiai.api.actions import get_action_v1_actions_id_get
|
|
1004
|
+
|
|
1005
|
+
app = _TermDashboardTestApp()
|
|
1006
|
+
async with app.run_test(size=(120, 60)) as pilot:
|
|
1007
|
+
await pilot.pause(0.05)
|
|
1008
|
+
screen = app.screen
|
|
1009
|
+
app.autobots_client = SimpleNamespace(client=object(), base_url="https://api.meetkiwi.ai")
|
|
1010
|
+
app._sync_autobots_client_from_disk = lambda: None
|
|
1011
|
+
listed = AutocodeActions(
|
|
1012
|
+
status_code=200,
|
|
1013
|
+
actions=(
|
|
1014
|
+
AutocodeAction(id="a", name="Autocode", type="openai"),
|
|
1015
|
+
AutocodeAction(id="f", name="Autocode OpenAI Flash", type="openai"),
|
|
1016
|
+
),
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
async def load(refresh: bool = False):
|
|
1020
|
+
screen._autocode, screen._autocode_backend = listed, "https://api.meetkiwi.ai"
|
|
1021
|
+
return listed
|
|
1022
|
+
|
|
1023
|
+
monkeypatch.setattr(screen, "_load_autocode_actions", load)
|
|
1024
|
+
|
|
1025
|
+
def no_request(**_kwargs):
|
|
1026
|
+
raise AssertionError("the name was already known")
|
|
1027
|
+
|
|
1028
|
+
monkeypatch.setattr(get_action_v1_actions_id_get, "sync_detailed", no_request)
|
|
1029
|
+
screen.current_action_id = ""
|
|
1030
|
+
screen.current_action_name = None
|
|
1031
|
+
|
|
1032
|
+
await screen._fetch_action_name_async()
|
|
1033
|
+
assert screen.current_action_id == "f"
|
|
1034
|
+
assert screen.current_action_name == "Autocode OpenAI Flash"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|