iCode-TUI 0.29.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chrys/__init__.py +16 -0
- chrys/app/__init__.py +3 -0
- chrys/app/acp/__init__.py +3 -0
- chrys/app/acp/bridge.py +664 -0
- chrys/app/acp/content.py +132 -0
- chrys/app/acp/doc/frontend-api.md +425 -0
- chrys/app/acp/history.py +585 -0
- chrys/app/acp/server.py +1796 -0
- chrys/app/acp/session_manager.py +1815 -0
- chrys/app/acp/tool_status.py +45 -0
- chrys/app/cli/__init__.py +3 -0
- chrys/app/cli/acp.py +178 -0
- chrys/app/cli/app.py +165 -0
- chrys/app/cli/approvals.py +101 -0
- chrys/app/cli/headless.py +223 -0
- chrys/app/cli/launch_cwd.py +42 -0
- chrys/app/cli/profiles.py +298 -0
- chrys/app/cli/progress.py +857 -0
- chrys/app/cli/run.py +425 -0
- chrys/app/cli/serve.py +1593 -0
- chrys/app/cli/tool_summary.py +225 -0
- chrys/app/cli/trajectory.py +201 -0
- chrys/app/cli/workflow.py +601 -0
- chrys/app/features/__init__.py +3 -0
- chrys/app/features/buddy/__init__.py +3 -0
- chrys/app/features/buddy/actions.py +68 -0
- chrys/app/features/buddy/animation.py +76 -0
- chrys/app/features/buddy/commands.py +196 -0
- chrys/app/features/buddy/docs/design.md +187 -0
- chrys/app/features/buddy/hatchery.py +80 -0
- chrys/app/features/buddy/lifecycle.py +41 -0
- chrys/app/features/buddy/model.py +265 -0
- chrys/app/features/buddy/pixel_renderer.py +113 -0
- chrys/app/features/buddy/pixel_sprites.py +279 -0
- chrys/app/features/buddy/portrait.py +76 -0
- chrys/app/features/buddy/progression.py +58 -0
- chrys/app/features/buddy/replies.py +191 -0
- chrys/app/features/buddy/sprites/alien.toml +70 -0
- chrys/app/features/buddy/sprites/axolotl.toml +71 -0
- chrys/app/features/buddy/sprites/bat.toml +69 -0
- chrys/app/features/buddy/sprites/bee.toml +69 -0
- chrys/app/features/buddy/sprites/cactus.toml +73 -0
- chrys/app/features/buddy/sprites/capybara.toml +70 -0
- chrys/app/features/buddy/sprites/cat.toml +69 -0
- chrys/app/features/buddy/sprites/crab.toml +70 -0
- chrys/app/features/buddy/sprites/dragon.toml +71 -0
- chrys/app/features/buddy/sprites/duck.toml +69 -0
- chrys/app/features/buddy/sprites/fox.toml +69 -0
- chrys/app/features/buddy/sprites/frog.toml +70 -0
- chrys/app/features/buddy/sprites/ghost.toml +70 -0
- chrys/app/features/buddy/sprites/goose.toml +69 -0
- chrys/app/features/buddy/sprites/jelly.toml +70 -0
- chrys/app/features/buddy/sprites/llama.toml +70 -0
- chrys/app/features/buddy/sprites/mushroom.toml +69 -0
- chrys/app/features/buddy/sprites/octopus.toml +70 -0
- chrys/app/features/buddy/sprites/owl.toml +70 -0
- chrys/app/features/buddy/sprites/panda.toml +71 -0
- chrys/app/features/buddy/sprites/penguin.toml +69 -0
- chrys/app/features/buddy/sprites/rabbit.toml +70 -0
- chrys/app/features/buddy/sprites/robot.toml +72 -0
- chrys/app/features/buddy/sprites/shark.toml +70 -0
- chrys/app/features/buddy/sprites/snail.toml +69 -0
- chrys/app/features/buddy/sprites/snake.toml +69 -0
- chrys/app/features/buddy/sprites/tabby.toml +69 -0
- chrys/app/features/buddy/sprites/turtle.toml +69 -0
- chrys/app/features/buddy/store.py +143 -0
- chrys/app/features/session_title/__init__.py +7 -0
- chrys/app/features/session_title/updater.py +582 -0
- chrys/app/installer.py +651 -0
- chrys/app/parsing.py +17 -0
- chrys/app/tui/__init__.py +3 -0
- chrys/app/tui/app.py +1287 -0
- chrys/app/tui/behaviors/__init__.py +3 -0
- chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
- chrys/app/tui/behaviors/insert_clipboard.py +73 -0
- chrys/app/tui/behaviors/right_click_copy.py +146 -0
- chrys/app/tui/binding_display.py +139 -0
- chrys/app/tui/buddy_messages.py +17 -0
- chrys/app/tui/buddy_reply.py +86 -0
- chrys/app/tui/chrys.tcss +549 -0
- chrys/app/tui/clipboard.py +109 -0
- chrys/app/tui/copy_messages.py +10 -0
- chrys/app/tui/i18n.py +230 -0
- chrys/app/tui/language.py +58 -0
- chrys/app/tui/notifications/__init__.py +12 -0
- chrys/app/tui/notifications/drivers.py +180 -0
- chrys/app/tui/notifications/service.py +126 -0
- chrys/app/tui/notifications/settings.py +141 -0
- chrys/app/tui/screens/__init__.py +3 -0
- chrys/app/tui/screens/agents/__init__.py +8 -0
- chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
- chrys/app/tui/screens/agents/config.py +2142 -0
- chrys/app/tui/screens/agents/config.tcss +169 -0
- chrys/app/tui/screens/agents/panels/__init__.py +3 -0
- chrys/app/tui/screens/agents/panels/acp.py +1176 -0
- chrys/app/tui/screens/agents/panels/basic.py +497 -0
- chrys/app/tui/screens/agents/panels/compaction.py +231 -0
- chrys/app/tui/screens/agents/panels/config_card.py +218 -0
- chrys/app/tui/screens/agents/panels/instructions.py +91 -0
- chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
- chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
- chrys/app/tui/screens/agents/panels/memory.py +684 -0
- chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
- chrys/app/tui/screens/agents/panels/skills.py +806 -0
- chrys/app/tui/screens/agents/panels/subagents.py +652 -0
- chrys/app/tui/screens/agents/panels/tools.py +205 -0
- chrys/app/tui/screens/agents/picker.py +103 -0
- chrys/app/tui/screens/agents/skill_paths.py +38 -0
- chrys/app/tui/screens/agents/validation_messages.py +452 -0
- chrys/app/tui/screens/dialogs/__init__.py +35 -0
- chrys/app/tui/screens/dialogs/agent_load.py +834 -0
- chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
- chrys/app/tui/screens/dialogs/app_mode.py +55 -0
- chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
- chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
- chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
- chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
- chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
- chrys/app/tui/screens/dialogs/approval/body.py +110 -0
- chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
- chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
- chrys/app/tui/screens/dialogs/ask_user.py +148 -0
- chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
- chrys/app/tui/screens/dialogs/base.py +84 -0
- chrys/app/tui/screens/dialogs/confirm.py +184 -0
- chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
- chrys/app/tui/screens/dialogs/connection_test.py +183 -0
- chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
- chrys/app/tui/screens/dialogs/diagram.py +102 -0
- chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
- chrys/app/tui/screens/dialogs/editor.py +378 -0
- chrys/app/tui/screens/dialogs/editor.tcss +132 -0
- chrys/app/tui/screens/dialogs/file_picker.py +582 -0
- chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
- chrys/app/tui/screens/dialogs/fork_session.py +219 -0
- chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
- chrys/app/tui/screens/dialogs/image_compression.py +102 -0
- chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
- chrys/app/tui/screens/dialogs/man_page.py +172 -0
- chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
- chrys/app/tui/screens/dialogs/new_folder.py +277 -0
- chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
- chrys/app/tui/screens/dialogs/notice.tcss +14 -0
- chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
- chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
- chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
- chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
- chrys/app/tui/screens/dialogs/session_title.py +110 -0
- chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
- chrys/app/tui/screens/dialogs/tool_view.py +207 -0
- chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
- chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
- chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
- chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
- chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
- chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
- chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
- chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
- chrys/app/tui/screens/diff/__init__.py +22 -0
- chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
- chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
- chrys/app/tui/screens/diff/screen.py +1532 -0
- chrys/app/tui/screens/diff/screen.tcss +104 -0
- chrys/app/tui/screens/guides/__init__.py +7 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
- chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/index.py +215 -0
- chrys/app/tui/screens/guides/reader.py +173 -0
- chrys/app/tui/screens/guides/screen.py +442 -0
- chrys/app/tui/screens/guides/screen.tcss +52 -0
- chrys/app/tui/screens/logs/__init__.py +7 -0
- chrys/app/tui/screens/logs/screen.py +504 -0
- chrys/app/tui/screens/logs/screen.tcss +45 -0
- chrys/app/tui/screens/main/__init__.py +7 -0
- chrys/app/tui/screens/main/action_availability.py +30 -0
- chrys/app/tui/screens/main/buddy_command.py +143 -0
- chrys/app/tui/screens/main/commands.py +1073 -0
- chrys/app/tui/screens/main/config_actions.py +464 -0
- chrys/app/tui/screens/main/copy_actions.py +117 -0
- chrys/app/tui/screens/main/dialog_controllers.py +826 -0
- chrys/app/tui/screens/main/dialog_gateway.py +166 -0
- chrys/app/tui/screens/main/diff_controller.py +478 -0
- chrys/app/tui/screens/main/engine_read_model.py +184 -0
- chrys/app/tui/screens/main/event_handlers.py +2014 -0
- chrys/app/tui/screens/main/input_flow.py +241 -0
- chrys/app/tui/screens/main/live_diff.py +244 -0
- chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
- chrys/app/tui/screens/main/model_indicator.py +217 -0
- chrys/app/tui/screens/main/navigation.py +343 -0
- chrys/app/tui/screens/main/ports.py +571 -0
- chrys/app/tui/screens/main/recent_dirs.py +123 -0
- chrys/app/tui/screens/main/rollback_controller.py +586 -0
- chrys/app/tui/screens/main/runtime_info.py +71 -0
- chrys/app/tui/screens/main/screen.py +2265 -0
- chrys/app/tui/screens/main/screen.tcss +21 -0
- chrys/app/tui/screens/main/session_handlers.py +1127 -0
- chrys/app/tui/screens/main/session_title.py +239 -0
- chrys/app/tui/screens/main/settings_coordinator.py +365 -0
- chrys/app/tui/screens/main/settings_persistence.py +184 -0
- chrys/app/tui/screens/main/shell_mode.py +112 -0
- chrys/app/tui/screens/main/state.py +289 -0
- chrys/app/tui/screens/main/submit_state.py +11 -0
- chrys/app/tui/screens/main/subscriptions.py +177 -0
- chrys/app/tui/screens/main/suggestions.py +982 -0
- chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
- chrys/app/tui/screens/main/view_adapter.py +1567 -0
- chrys/app/tui/screens/main/workflow_browser.py +747 -0
- chrys/app/tui/screens/main/workflow_content.py +248 -0
- chrys/app/tui/screens/main/workflow_controller.py +240 -0
- chrys/app/tui/screens/main/workflow_feedback.py +137 -0
- chrys/app/tui/screens/main/workflow_flow.py +49 -0
- chrys/app/tui/screens/main/workflow_run_control.py +456 -0
- chrys/app/tui/screens/main/workflow_session_view.py +316 -0
- chrys/app/tui/screens/main/workspace_actions.py +272 -0
- chrys/app/tui/screens/main/workspace_branch.py +194 -0
- chrys/app/tui/screens/menus/__init__.py +8 -0
- chrys/app/tui/screens/menus/languages.py +114 -0
- chrys/app/tui/screens/models/__init__.py +7 -0
- chrys/app/tui/screens/models/picker.py +147 -0
- chrys/app/tui/screens/models/screen.py +2043 -0
- chrys/app/tui/screens/models/screen.tcss +255 -0
- chrys/app/tui/screens/sessions/__init__.py +8 -0
- chrys/app/tui/screens/sessions/presenter.py +586 -0
- chrys/app/tui/screens/sessions/screen.py +779 -0
- chrys/app/tui/screens/sessions/screen.tcss +127 -0
- chrys/app/tui/screens/settings/__init__.py +29 -0
- chrys/app/tui/screens/settings/danger.py +26 -0
- chrys/app/tui/screens/settings/dialog.py +281 -0
- chrys/app/tui/screens/settings/layout.py +519 -0
- chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
- chrys/app/tui/screens/settings/panes/__init__.py +3 -0
- chrys/app/tui/screens/settings/panes/notifications.py +248 -0
- chrys/app/tui/screens/settings/panes/sessions.py +148 -0
- chrys/app/tui/screens/settings/ports.py +55 -0
- chrys/app/tui/screens/settings/provenance.py +102 -0
- chrys/app/tui/screens/settings/rows.py +546 -0
- chrys/app/tui/screens/settings/settings.tcss +406 -0
- chrys/app/tui/screens/themes/__init__.py +8 -0
- chrys/app/tui/screens/themes/dialogs.py +330 -0
- chrys/app/tui/screens/themes/editor.py +632 -0
- chrys/app/tui/screens/themes/editor.tcss +268 -0
- chrys/app/tui/screens/themes/messages.py +95 -0
- chrys/app/tui/screens/themes/palette.py +694 -0
- chrys/app/tui/screens/themes/panel.py +113 -0
- chrys/app/tui/screens/themes/picker.py +203 -0
- chrys/app/tui/screens/themes/save.py +191 -0
- chrys/app/tui/support/__init__.py +3 -0
- chrys/app/tui/support/file_manager.py +64 -0
- chrys/app/tui/support/gc_freeze.py +1114 -0
- chrys/app/tui/support/path_shortcuts.py +50 -0
- chrys/app/tui/support/prompt_history.py +298 -0
- chrys/app/tui/support/workspace_mru.py +476 -0
- chrys/app/tui/terminal/__init__.py +6 -0
- chrys/app/tui/terminal/_pty_backend.py +114 -0
- chrys/app/tui/terminal/emulator/__init__.py +24 -0
- chrys/app/tui/terminal/emulator/buffer.py +456 -0
- chrys/app/tui/terminal/emulator/charsets.py +113 -0
- chrys/app/tui/terminal/emulator/core.py +963 -0
- chrys/app/tui/terminal/emulator/input.py +342 -0
- chrys/app/tui/terminal/emulator/parser.py +227 -0
- chrys/app/tui/terminal/emulator/pen.py +158 -0
- chrys/app/tui/terminal/launcher.py +455 -0
- chrys/app/tui/terminal/leaked_reports.py +44 -0
- chrys/app/tui/terminal/panel.py +323 -0
- chrys/app/tui/terminal/pty_process.py +405 -0
- chrys/app/tui/terminal/rendering.py +151 -0
- chrys/app/tui/terminal/shell.py +273 -0
- chrys/app/tui/terminal/shell_integration.py +199 -0
- chrys/app/tui/terminal/title.py +224 -0
- chrys/app/tui/terminal/widget.py +654 -0
- chrys/app/tui/theme.py +422 -0
- chrys/app/tui/theme_loader.py +201 -0
- chrys/app/tui/theme_stylesheet.py +49 -0
- chrys/app/tui/themes/__init__.py +3 -0
- chrys/app/tui/themes/document.py +120 -0
- chrys/app/tui/themes/preview.py +170 -0
- chrys/app/tui/themes/store.py +176 -0
- chrys/app/tui/toasts.py +69 -0
- chrys/app/tui/util/__init__.py +3 -0
- chrys/app/tui/util/context_pressure.py +146 -0
- chrys/app/tui/util/debug_log.py +292 -0
- chrys/app/tui/util/diff_entries.py +64 -0
- chrys/app/tui/util/formatting.py +53 -0
- chrys/app/tui/util/git_branch.py +326 -0
- chrys/app/tui/util/invocation_progress.py +57 -0
- chrys/app/tui/util/logo.py +32 -0
- chrys/app/tui/util/message_gate.py +35 -0
- chrys/app/tui/util/mutation_sources.py +14 -0
- chrys/app/tui/util/picker_text.py +27 -0
- chrys/app/tui/util/removal.py +47 -0
- chrys/app/tui/util/rich_style.py +36 -0
- chrys/app/tui/util/source_text.py +12 -0
- chrys/app/tui/util/static_update.py +88 -0
- chrys/app/tui/util/todo_format.py +51 -0
- chrys/app/tui/util/visibility.py +214 -0
- chrys/app/tui/widgets/__init__.py +60 -0
- chrys/app/tui/widgets/ask_user_controls.py +652 -0
- chrys/app/tui/widgets/ask_user_markdown.py +17 -0
- chrys/app/tui/widgets/ask_user_prompt.py +606 -0
- chrys/app/tui/widgets/buttons.py +52 -0
- chrys/app/tui/widgets/chat/__init__.py +3 -0
- chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
- chrys/app/tui/widgets/chat/chrome.py +296 -0
- chrys/app/tui/widgets/chat/compaction_card.py +353 -0
- chrys/app/tui/widgets/chat/context_fold.py +77 -0
- chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
- chrys/app/tui/widgets/chat/image_preview.py +214 -0
- chrys/app/tui/widgets/chat/live_renderer.py +383 -0
- chrys/app/tui/widgets/chat/messages.py +1039 -0
- chrys/app/tui/widgets/chat/panel.py +1769 -0
- chrys/app/tui/widgets/chat/ports.py +245 -0
- chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
- chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
- chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
- chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
- chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
- chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
- chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
- chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
- chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
- chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
- chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
- chrys/app/tui/widgets/chat/renderers/search.py +348 -0
- chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
- chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
- chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
- chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
- chrys/app/tui/widgets/chat/renderers/web.py +69 -0
- chrys/app/tui/widgets/chat/replay.py +1605 -0
- chrys/app/tui/widgets/chat/replay_mount.py +322 -0
- chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
- chrys/app/tui/widgets/chat/selection_controller.py +435 -0
- chrys/app/tui/widgets/chat/session_json.py +508 -0
- chrys/app/tui/widgets/chat/toc_model.py +156 -0
- chrys/app/tui/widgets/chat/tool_call.py +2478 -0
- chrys/app/tui/widgets/chat/tool_registry.py +753 -0
- chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
- chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
- chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
- chrys/app/tui/widgets/checkbox.py +24 -0
- chrys/app/tui/widgets/chrome/__init__.py +3 -0
- chrys/app/tui/widgets/chrome/app_header.py +283 -0
- chrys/app/tui/widgets/chrome/commands.py +95 -0
- chrys/app/tui/widgets/chrome/file_index.py +68 -0
- chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
- chrys/app/tui/widgets/chrome/footer.py +284 -0
- chrys/app/tui/widgets/chrome/image_paste.py +351 -0
- chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
- chrys/app/tui/widgets/chrome/status_bar.py +911 -0
- chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
- chrys/app/tui/widgets/click_affordance.py +24 -0
- chrys/app/tui/widgets/color_picker/__init__.py +254 -0
- chrys/app/tui/widgets/color_picker/controls.py +171 -0
- chrys/app/tui/widgets/color_picker/model.py +89 -0
- chrys/app/tui/widgets/dialog_buttons.py +58 -0
- chrys/app/tui/widgets/dialog_scroll.py +24 -0
- chrys/app/tui/widgets/diff_view/__init__.py +20 -0
- chrys/app/tui/widgets/diff_view/cells.py +59 -0
- chrys/app/tui/widgets/diff_view/code.py +280 -0
- chrys/app/tui/widgets/diff_view/compute.py +286 -0
- chrys/app/tui/widgets/diff_view/gutter.py +161 -0
- chrys/app/tui/widgets/diff_view/inline.py +77 -0
- chrys/app/tui/widgets/diff_view/palette.py +87 -0
- chrys/app/tui/widgets/diff_view/protocols.py +24 -0
- chrys/app/tui/widgets/diff_view/rows.py +138 -0
- chrys/app/tui/widgets/diff_view/unified.py +267 -0
- chrys/app/tui/widgets/diff_view/widget.py +209 -0
- chrys/app/tui/widgets/editor/__init__.py +79 -0
- chrys/app/tui/widgets/editor/coordinates.py +30 -0
- chrys/app/tui/widgets/editor/highlighter.py +139 -0
- chrys/app/tui/widgets/editor/keymaps.py +357 -0
- chrys/app/tui/widgets/editor/types.py +216 -0
- chrys/app/tui/widgets/editor/vim.py +1195 -0
- chrys/app/tui/widgets/editor/widget.py +401 -0
- chrys/app/tui/widgets/file_preview.py +58 -0
- chrys/app/tui/widgets/hatch.py +138 -0
- chrys/app/tui/widgets/input.py +66 -0
- chrys/app/tui/widgets/loading.py +125 -0
- chrys/app/tui/widgets/markdown/__init__.py +25 -0
- chrys/app/tui/widgets/markdown/_utils.py +21 -0
- chrys/app/tui/widgets/markdown/anchors.py +71 -0
- chrys/app/tui/widgets/markdown/blocks.py +115 -0
- chrys/app/tui/widgets/markdown/copyable.py +163 -0
- chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
- chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
- chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
- chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
- chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
- chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
- chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
- chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
- chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
- chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
- chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
- chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
- chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
- chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
- chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
- chrys/app/tui/widgets/markdown/links.py +47 -0
- chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
- chrys/app/tui/widgets/markdown/math/layout.py +739 -0
- chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
- chrys/app/tui/widgets/markdown/math/parser.py +325 -0
- chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
- chrys/app/tui/widgets/markdown/navigator.py +80 -0
- chrys/app/tui/widgets/markdown/parser.py +1057 -0
- chrys/app/tui/widgets/markdown/stream.py +73 -0
- chrys/app/tui/widgets/markdown/toc.py +98 -0
- chrys/app/tui/widgets/markdown/viewer.py +146 -0
- chrys/app/tui/widgets/markdown/widget.py +2046 -0
- chrys/app/tui/widgets/marquee.py +101 -0
- chrys/app/tui/widgets/option_menu.py +82 -0
- chrys/app/tui/widgets/page_navigator.py +118 -0
- chrys/app/tui/widgets/select.py +196 -0
- chrys/app/tui/widgets/selection.py +59 -0
- chrys/app/tui/widgets/sidebar/__init__.py +3 -0
- chrys/app/tui/widgets/sidebar/buddy.py +367 -0
- chrys/app/tui/widgets/sidebar/context.py +482 -0
- chrys/app/tui/widgets/sidebar/debug.py +195 -0
- chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
- chrys/app/tui/widgets/sidebar/panel.py +198 -0
- chrys/app/tui/widgets/sidebar/tasks.py +198 -0
- chrys/app/tui/widgets/sidebar/toc.py +166 -0
- chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
- chrys/app/tui/widgets/syntax_theme.py +85 -0
- chrys/app/tui/widgets/text_area.py +258 -0
- chrys/app/tui/widgets/trajectory/__init__.py +8 -0
- chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
- chrys/app/tui/widgets/trajectory/insights.py +1266 -0
- chrys/app/tui/widgets/trajectory/overview.py +1101 -0
- chrys/app/tui/widgets/trajectory/panel.py +983 -0
- chrys/app/tui/widgets/trajectory/presentation.py +311 -0
- chrys/app/tui/widgets/trajectory/session_info.py +124 -0
- chrys/app/tui/widgets/trajectory/text_view.py +124 -0
- chrys/app/tui/widgets/trajectory/timeline.py +380 -0
- chrys/app/tui/widgets/welcome.py +126 -0
- chrys/app/tui/widgets/workflow/__init__.py +5 -0
- chrys/app/tui/widgets/workflow/edges.py +89 -0
- chrys/app/tui/widgets/workflow/graph.py +869 -0
- chrys/app/tui/widgets/workflow/info.py +310 -0
- chrys/app/tui/widgets/workflow/node_view.py +27 -0
- chrys/app/tui/widgets/workflow/output.py +225 -0
- chrys/app/tui/widgets/workflow/panel.py +658 -0
- chrys/app/tui/widgets/workflow/projector.py +324 -0
- chrys/app/tui/widgets/workflow/records.py +69 -0
- chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
- chrys/app/tui/widgets/workflow/selection.py +151 -0
- chrys/app/tui/widgets/workflow/source.py +65 -0
- chrys/app/tui/widgets/workflow/text.py +257 -0
- chrys/app/tui/widgets/workflow/values.py +317 -0
- chrys/foundation/__init__.py +0 -0
- chrys/foundation/branding.py +15 -0
- chrys/foundation/config/__init__.py +7 -0
- chrys/foundation/config/coercion.py +374 -0
- chrys/foundation/config/context.py +34 -0
- chrys/foundation/config/env_file.py +141 -0
- chrys/foundation/config/env_layers.py +370 -0
- chrys/foundation/config/migrations.py +521 -0
- chrys/foundation/config/notification_events.py +22 -0
- chrys/foundation/config/process_settings.py +332 -0
- chrys/foundation/config/runtime_pointer.py +133 -0
- chrys/foundation/config/settings.py +1952 -0
- chrys/foundation/config/settings_store.py +996 -0
- chrys/foundation/config/spec.py +396 -0
- chrys/foundation/config/user_settings.py +140 -0
- chrys/foundation/config/warnings.py +262 -0
- chrys/foundation/config/web_values.py +135 -0
- chrys/foundation/config/yaml_store.py +487 -0
- chrys/foundation/errors/__init__.py +68 -0
- chrys/foundation/errors/_legacy.py +117 -0
- chrys/foundation/errors/_text.py +13 -0
- chrys/foundation/errors/_walk.py +85 -0
- chrys/foundation/errors/classify.py +278 -0
- chrys/foundation/errors/display.py +274 -0
- chrys/foundation/errors/formatting.py +283 -0
- chrys/foundation/errors/kinds.py +67 -0
- chrys/foundation/errors/network.py +497 -0
- chrys/foundation/errors/provider.py +445 -0
- chrys/foundation/errors/route.py +85 -0
- chrys/foundation/events/__init__.py +8 -0
- chrys/foundation/events/bus.py +143 -0
- chrys/foundation/events/types.py +1629 -0
- chrys/foundation/events/workflow.py +38 -0
- chrys/foundation/hosted_tools.py +142 -0
- chrys/foundation/i18n/__init__.py +32 -0
- chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
- chrys/foundation/i18n/catalogs.py +42 -0
- chrys/foundation/i18n/formatting.py +202 -0
- chrys/foundation/i18n/locale.py +92 -0
- chrys/foundation/i18n/localizer.py +187 -0
- chrys/foundation/i18n/messages.py +268 -0
- chrys/foundation/io/__init__.py +3 -0
- chrys/foundation/io/result_content.py +237 -0
- chrys/foundation/models/__init__.py +3 -0
- chrys/foundation/models/approval_reuse.py +23 -0
- chrys/foundation/models/ask_user.py +468 -0
- chrys/foundation/models/execution.py +18 -0
- chrys/foundation/models/history_markers.py +115 -0
- chrys/foundation/models/invocations.py +63 -0
- chrys/foundation/models/mutation_scope.py +28 -0
- chrys/foundation/models/session_env.py +87 -0
- chrys/foundation/models/session_surface.py +29 -0
- chrys/foundation/models/todos.py +74 -0
- chrys/foundation/models/turns.py +125 -0
- chrys/foundation/models/workflow_session.py +89 -0
- chrys/foundation/models/workspace.py +59 -0
- chrys/foundation/net/__init__.py +3 -0
- chrys/foundation/net/address_class.py +57 -0
- chrys/foundation/net/route_probe.py +94 -0
- chrys/foundation/net/url.py +107 -0
- chrys/foundation/observability/__init__.py +3 -0
- chrys/foundation/observability/exporters.py +174 -0
- chrys/foundation/observability/gate.py +31 -0
- chrys/foundation/observability/setup.py +497 -0
- chrys/foundation/observability/sink.py +228 -0
- chrys/foundation/patches/__init__.py +19 -0
- chrys/foundation/patches/mdurl_cache.py +59 -0
- chrys/foundation/patches/patcher.py +287 -0
- chrys/foundation/patches/staged_members.py +205 -0
- chrys/foundation/patches/textual_block_border.py +105 -0
- chrys/foundation/patches/textual_button_ansi.py +65 -0
- chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
- chrys/foundation/patches/textual_callback_cache.py +248 -0
- chrys/foundation/patches/textual_callback_dispatch.py +109 -0
- chrys/foundation/patches/textual_compositor_cjk.py +566 -0
- chrys/foundation/patches/textual_dispatch_cache.py +150 -0
- chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
- chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
- chrys/foundation/patches/textual_lru_acyclic.py +241 -0
- chrys/foundation/patches/textual_message_pump.py +136 -0
- chrys/foundation/patches/textual_node_diet.py +875 -0
- chrys/foundation/patches/textual_one_shot_timer.py +48 -0
- chrys/foundation/patches/textual_option_list.py +90 -0
- chrys/foundation/patches/textual_precompose.py +89 -0
- chrys/foundation/patches/textual_pruned_tabs.py +69 -0
- chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
- chrys/foundation/patches/textual_removed_node_caches.py +86 -0
- chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
- chrys/foundation/patches/textual_selection_extract.py +48 -0
- chrys/foundation/patches/textual_strip_cycles.py +119 -0
- chrys/foundation/patches/textual_tab_selection.py +1611 -0
- chrys/foundation/patches/textual_timer_skip.py +70 -0
- chrys/foundation/patches/textual_utf8_decoder.py +106 -0
- chrys/foundation/patches/textual_win_sleep.py +167 -0
- chrys/foundation/patches/watchdog_fsevents.py +70 -0
- chrys/foundation/patches/watchdog_windows.py +52 -0
- chrys/foundation/platform/__init__.py +556 -0
- chrys/foundation/platform/c_api.py +32 -0
- chrys/foundation/platform/child_reap.py +107 -0
- chrys/foundation/platform/command_line.py +95 -0
- chrys/foundation/platform/files.py +1470 -0
- chrys/foundation/platform/output_capture.py +113 -0
- chrys/foundation/platform/paths.py +193 -0
- chrys/foundation/platform/process.py +1468 -0
- chrys/foundation/platform/pty_output.py +19 -0
- chrys/foundation/platform/runtime_paths.py +281 -0
- chrys/foundation/platform/win_console.py +129 -0
- chrys/foundation/reasoning_origin.py +67 -0
- chrys/foundation/recovery.py +16 -0
- chrys/foundation/retry.py +278 -0
- chrys/foundation/text/__init__.py +3 -0
- chrys/foundation/text/encoding.py +603 -0
- chrys/foundation/text/images.py +304 -0
- chrys/foundation/text/lines.py +24 -0
- chrys/foundation/text/mentions.py +90 -0
- chrys/foundation/text/model_json.py +25 -0
- chrys/foundation/text/reading_order.py +20 -0
- chrys/foundation/text/tokenizer.py +25 -0
- chrys/foundation/text/tool_output.py +287 -0
- chrys/foundation/text/yaml_io.py +62 -0
- chrys/foundation/tool_call_context.py +202 -0
- chrys/foundation/tool_execution_stamp.py +184 -0
- chrys/foundation/tool_invocation_order.py +48 -0
- chrys/foundation/tool_kinds.py +102 -0
- chrys/foundation/tool_result_metadata.py +287 -0
- chrys/foundation/trajectory/__init__.py +10 -0
- chrys/foundation/trajectory/context.py +481 -0
- chrys/foundation/trajectory/envelope.py +571 -0
- chrys/foundation/trajectory/event_types.py +294 -0
- chrys/foundation/trajectory/finalizers.py +46 -0
- chrys/foundation/trajectory/fingerprint.py +62 -0
- chrys/foundation/trajectory/ids.py +155 -0
- chrys/foundation/trajectory/keys.py +99 -0
- chrys/foundation/trajectory/lease.py +173 -0
- chrys/foundation/trajectory/metadata.py +72 -0
- chrys/foundation/trajectory/reader.py +271 -0
- chrys/foundation/trajectory/recovery.py +225 -0
- chrys/foundation/trajectory/revisions.py +152 -0
- chrys/foundation/trajectory/segments.py +225 -0
- chrys/foundation/trajectory/tools.py +117 -0
- chrys/foundation/trajectory/usage.py +151 -0
- chrys/foundation/trajectory/writer.py +972 -0
- chrys/foundation/trajectory_timing.py +97 -0
- chrys/foundation/util/__init__.py +3 -0
- chrys/foundation/util/async_tasks.py +29 -0
- chrys/foundation/util/chrys_headers.py +23 -0
- chrys/foundation/util/env_templates.py +104 -0
- chrys/foundation/util/filenames.py +96 -0
- chrys/foundation/util/header_charset.py +138 -0
- chrys/foundation/util/httpx_helpers.py +12 -0
- chrys/foundation/util/lock.py +236 -0
- chrys/foundation/util/once_close.py +46 -0
- chrys/foundation/util/session_ids.py +29 -0
- chrys/foundation/util/shell_tokens.py +88 -0
- chrys/foundation/util/sub_agent_context.py +38 -0
- chrys/foundation/util/time.py +35 -0
- chrys/foundation/util/unicode_scalars.py +36 -0
- chrys/foundation/vendor/__init__.py +43 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
- chrys/kernel/__init__.py +256 -0
- chrys/kernel/_content.py +1866 -0
- chrys/kernel/_loop_recorder.py +631 -0
- chrys/kernel/_null_overlay.py +1026 -0
- chrys/kernel/_result_ceiling.py +216 -0
- chrys/kernel/_serialization.py +431 -0
- chrys/kernel/_tool_arg_errors.py +1629 -0
- chrys/kernel/_tool_execution.py +1172 -0
- chrys/kernel/_tool_expansion.py +26 -0
- chrys/kernel/_types.py +2598 -0
- chrys/kernel/agent.py +994 -0
- chrys/kernel/client.py +1084 -0
- chrys/kernel/compaction.py +1315 -0
- chrys/kernel/exceptions.py +211 -0
- chrys/kernel/exchanges.py +683 -0
- chrys/kernel/identity.py +95 -0
- chrys/kernel/images.py +36 -0
- chrys/kernel/instrumentation.py +1618 -0
- chrys/kernel/loop.py +2308 -0
- chrys/kernel/middleware.py +616 -0
- chrys/kernel/sessions.py +604 -0
- chrys/kernel/tools.py +1061 -0
- chrys/kernel/types.py +126 -0
- chrys/orchestration/__init__.py +3 -0
- chrys/orchestration/engine/__init__.py +3 -0
- chrys/orchestration/engine/assembly.py +236 -0
- chrys/orchestration/engine/build/__init__.py +3 -0
- chrys/orchestration/engine/build/builder.py +1061 -0
- chrys/orchestration/engine/build/construction.py +408 -0
- chrys/orchestration/engine/build/loaded.py +139 -0
- chrys/orchestration/engine/engine.py +789 -0
- chrys/orchestration/engine/execution.py +771 -0
- chrys/orchestration/engine/loader.py +865 -0
- chrys/orchestration/engine/rollback.py +918 -0
- chrys/orchestration/engine/run/__init__.py +3 -0
- chrys/orchestration/engine/run/active_injection.py +419 -0
- chrys/orchestration/engine/run/attachments.py +519 -0
- chrys/orchestration/engine/run/bindings.py +1095 -0
- chrys/orchestration/engine/run/coordinator.py +1062 -0
- chrys/orchestration/engine/run/finalizer.py +417 -0
- chrys/orchestration/engine/run/input_refs.py +60 -0
- chrys/orchestration/engine/run/prompt_content.py +457 -0
- chrys/orchestration/engine/run/resume.py +299 -0
- chrys/orchestration/engine/run/retry.py +948 -0
- chrys/orchestration/engine/run/runner.py +625 -0
- chrys/orchestration/engine/run/runtime_skills.py +146 -0
- chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
- chrys/orchestration/engine/run/turn_hooks.py +258 -0
- chrys/orchestration/engine/run/turn_state.py +149 -0
- chrys/orchestration/engine/run/working_dir.py +50 -0
- chrys/orchestration/engine/session_lifecycle.py +2013 -0
- chrys/orchestration/engine/state/__init__.py +3 -0
- chrys/orchestration/engine/state/active_session.py +183 -0
- chrys/orchestration/engine/state/controls.py +639 -0
- chrys/orchestration/engine/state/current_agent.py +26 -0
- chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
- chrys/orchestration/engine/state/machine.py +193 -0
- chrys/orchestration/engine/state/session_writer.py +434 -0
- chrys/orchestration/engine/trajectory.py +714 -0
- chrys/orchestration/engine/usage.py +118 -0
- chrys/orchestration/invoker/__init__.py +3 -0
- chrys/orchestration/invoker/acp.py +727 -0
- chrys/orchestration/invoker/acp_protocol.py +1115 -0
- chrys/orchestration/invoker/acp_spec.py +79 -0
- chrys/orchestration/invoker/attempts.py +994 -0
- chrys/orchestration/invoker/child_compaction.py +131 -0
- chrys/orchestration/invoker/child_history.py +99 -0
- chrys/orchestration/invoker/contracts.py +253 -0
- chrys/orchestration/invoker/evidence.py +94 -0
- chrys/orchestration/invoker/kernel.py +343 -0
- chrys/orchestration/invoker/origin.py +79 -0
- chrys/orchestration/invoker/resources.py +343 -0
- chrys/orchestration/invoker/runtime.py +299 -0
- chrys/orchestration/invoker/usage.py +80 -0
- chrys/orchestration/session_hooks.py +117 -0
- chrys/orchestration/session_host.py +1023 -0
- chrys/orchestration/session_resources.py +95 -0
- chrys/orchestration/session_usage.py +219 -0
- chrys/orchestration/startup.py +255 -0
- chrys/orchestration/sub_agents/__init__.py +3 -0
- chrys/orchestration/sub_agents/acp_policy.py +269 -0
- chrys/orchestration/sub_agents/kernel_policy.py +848 -0
- chrys/orchestration/sub_agents/registration.py +44 -0
- chrys/orchestration/sub_agents/shell.py +337 -0
- chrys/orchestration/sub_agents/tools.py +2228 -0
- chrys/orchestration/workflows/__init__.py +3 -0
- chrys/orchestration/workflows/agent_archive.py +156 -0
- chrys/orchestration/workflows/agent_node.py +894 -0
- chrys/orchestration/workflows/agent_node_build.py +687 -0
- chrys/orchestration/workflows/catalog.py +332 -0
- chrys/orchestration/workflows/coordinator.py +771 -0
- chrys/orchestration/workflows/hooks.py +176 -0
- chrys/orchestration/workflows/preview.py +322 -0
- chrys/orchestration/workflows/rollback.py +151 -0
- chrys/orchestration/workflows/runner.py +986 -0
- chrys/orchestration/workflows/session.py +390 -0
- chrys/orchestration/workflows/settings.py +97 -0
- chrys/orchestration/workflows/validation.py +674 -0
- chrys/orchestration/workflows/worker_client.py +1002 -0
- chrys/service/__init__.py +3 -0
- chrys/service/acp_client/__init__.py +65 -0
- chrys/service/acp_client/_transport.py +922 -0
- chrys/service/acp_client/client.py +1311 -0
- chrys/service/acp_client/errors.py +224 -0
- chrys/service/acp_client/protocol.py +220 -0
- chrys/service/acp_client/spawn.py +239 -0
- chrys/service/acp_client/spec.py +100 -0
- chrys/service/agent_middleware/__init__.py +60 -0
- chrys/service/agent_middleware/_metadata_keys.py +52 -0
- chrys/service/agent_middleware/control/__init__.py +3 -0
- chrys/service/agent_middleware/control/approval.py +769 -0
- chrys/service/agent_middleware/control/approval_hooks.py +62 -0
- chrys/service/agent_middleware/control/ask_user.py +207 -0
- chrys/service/agent_middleware/control/interrupt.py +50 -0
- chrys/service/agent_middleware/control/sleep.py +118 -0
- chrys/service/agent_middleware/control/todo.py +117 -0
- chrys/service/agent_middleware/events/__init__.py +3 -0
- chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
- chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
- chrys/service/agent_middleware/events/intermediate_text.py +128 -0
- chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
- chrys/service/agent_middleware/events/result_persistence.py +98 -0
- chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
- chrys/service/agent_middleware/events/tool_error_text.py +22 -0
- chrys/service/agent_middleware/events/tool_events.py +779 -0
- chrys/service/agent_middleware/injection.py +510 -0
- chrys/service/agent_middleware/reminders/__init__.py +91 -0
- chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
- chrys/service/agent_middleware/reminders/base.py +25 -0
- chrys/service/agent_middleware/reminders/context_usage.py +74 -0
- chrys/service/agent_middleware/reminders/file_change.py +63 -0
- chrys/service/agent_middleware/reminders/mcp.py +39 -0
- chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
- chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
- chrys/service/agent_middleware/reminders/skills.py +44 -0
- chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
- chrys/service/agent_middleware/reminders/todo.py +35 -0
- chrys/service/agent_middleware/reminders/turn_line.py +48 -0
- chrys/service/agent_middleware/response_validation.py +1380 -0
- chrys/service/agent_middleware/system_reminder.py +1107 -0
- chrys/service/agent_middleware/validators.py +396 -0
- chrys/service/analytics/__init__.py +124 -0
- chrys/service/analytics/_actions.py +896 -0
- chrys/service/analytics/_context_evidence.py +357 -0
- chrys/service/analytics/_critical_path.py +179 -0
- chrys/service/analytics/_facts.py +919 -0
- chrys/service/analytics/_insights.py +769 -0
- chrys/service/analytics/_metric_ops.py +83 -0
- chrys/service/analytics/_session_projection.py +460 -0
- chrys/service/analytics/_timeline.py +434 -0
- chrys/service/analytics/_turn_graph.py +1237 -0
- chrys/service/analytics/_turns.py +1377 -0
- chrys/service/analytics/_workflows.py +171 -0
- chrys/service/analytics/aggregation.py +1474 -0
- chrys/service/analytics/classification.py +128 -0
- chrys/service/analytics/export.py +803 -0
- chrys/service/analytics/findings.py +282 -0
- chrys/service/analytics/math.py +79 -0
- chrys/service/analytics/model.py +886 -0
- chrys/service/analytics/reader.py +321 -0
- chrys/service/approval/__init__.py +3 -0
- chrys/service/approval/arbitration.py +102 -0
- chrys/service/approval/command_identity.py +109 -0
- chrys/service/approval/correlation.py +57 -0
- chrys/service/approval/grant_store.py +108 -0
- chrys/service/approval/judge.py +589 -0
- chrys/service/approval/level.py +15 -0
- chrys/service/approval/policy.py +209 -0
- chrys/service/approval/reuse.py +223 -0
- chrys/service/approval/reuse_binding.py +133 -0
- chrys/service/approval/safety_classifier.py +536 -0
- chrys/service/approval/turn_context.py +49 -0
- chrys/service/context/__init__.py +3 -0
- chrys/service/context/compaction/__init__.py +38 -0
- chrys/service/context/compaction/breaker.py +156 -0
- chrys/service/context/compaction/budgets.py +93 -0
- chrys/service/context/compaction/calibration.py +77 -0
- chrys/service/context/compaction/compression.py +690 -0
- chrys/service/context/compaction/current_turn_drop.py +458 -0
- chrys/service/context/compaction/events.py +201 -0
- chrys/service/context/compaction/exclusions.py +261 -0
- chrys/service/context/compaction/groups.py +361 -0
- chrys/service/context/compaction/last_words.py +1996 -0
- chrys/service/context/compaction/last_words_state.py +752 -0
- chrys/service/context/compaction/scoped.py +472 -0
- chrys/service/context/compaction/spill.py +1947 -0
- chrys/service/context/compaction/strategy.py +1116 -0
- chrys/service/context/compaction/summaries.py +319 -0
- chrys/service/context/compaction/turns.py +375 -0
- chrys/service/context/manager.py +174 -0
- chrys/service/context/memory_loader.py +792 -0
- chrys/service/context/middleware/__init__.py +3 -0
- chrys/service/context/middleware/usage.py +332 -0
- chrys/service/context/providers/__init__.py +3 -0
- chrys/service/context/providers/context_management.py +432 -0
- chrys/service/context/providers/history.py +647 -0
- chrys/service/context/providers/memory.py +50 -0
- chrys/service/context/tool_content.py +22 -0
- chrys/service/hooks/__init__.py +37 -0
- chrys/service/hooks/detached_worker.py +118 -0
- chrys/service/hooks/docs/README.md +14 -0
- chrys/service/hooks/docs/authoring.md +684 -0
- chrys/service/hooks/docs/configuration.md +499 -0
- chrys/service/hooks/docs/design.md +359 -0
- chrys/service/hooks/docs/project-hooks.md +348 -0
- chrys/service/hooks/events.py +157 -0
- chrys/service/hooks/loader.py +665 -0
- chrys/service/hooks/manager.py +807 -0
- chrys/service/hooks/matcher.py +111 -0
- chrys/service/hooks/outbox.py +294 -0
- chrys/service/hooks/runner.py +572 -0
- chrys/service/hooks/schema.py +407 -0
- chrys/service/llm/__init__.py +7 -0
- chrys/service/llm/_structured_outputs.py +192 -0
- chrys/service/llm/anthropic_messages/__init__.py +9 -0
- chrys/service/llm/anthropic_messages/client.py +254 -0
- chrys/service/llm/anthropic_messages/decode.py +331 -0
- chrys/service/llm/anthropic_messages/history.py +279 -0
- chrys/service/llm/anthropic_messages/request.py +323 -0
- chrys/service/llm/anthropic_messages/server_tools.py +319 -0
- chrys/service/llm/anthropic_messages/stream.py +297 -0
- chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
- chrys/service/llm/chat_completions/__init__.py +9 -0
- chrys/service/llm/chat_completions/client.py +369 -0
- chrys/service/llm/chat_completions/decode.py +338 -0
- chrys/service/llm/chat_completions/history.py +657 -0
- chrys/service/llm/chat_completions/reasoning.py +244 -0
- chrys/service/llm/chat_completions/request.py +192 -0
- chrys/service/llm/chat_completions/stream.py +546 -0
- chrys/service/llm/chat_completions/validation.py +308 -0
- chrys/service/llm/clients.py +613 -0
- chrys/service/llm/images.py +65 -0
- chrys/service/llm/mock.py +413 -0
- chrys/service/llm/observer.py +446 -0
- chrys/service/llm/one_shot.py +106 -0
- chrys/service/llm/openai_exceptions.py +93 -0
- chrys/service/llm/openai_responses/__init__.py +10 -0
- chrys/service/llm/openai_responses/client.py +323 -0
- chrys/service/llm/openai_responses/decode.py +432 -0
- chrys/service/llm/openai_responses/history.py +519 -0
- chrys/service/llm/openai_responses/hosted.py +625 -0
- chrys/service/llm/openai_responses/replay.py +629 -0
- chrys/service/llm/openai_responses/request.py +342 -0
- chrys/service/llm/openai_responses/stream.py +847 -0
- chrys/service/llm/openai_timestamps.py +57 -0
- chrys/service/llm/providers.py +107 -0
- chrys/service/llm/proxy_route.py +54 -0
- chrys/service/llm/raw_http_log.py +309 -0
- chrys/service/llm/route_facts.py +63 -0
- chrys/service/llm/route_sessions.py +49 -0
- chrys/service/llm/sdk_anthropic.py +18 -0
- chrys/service/llm/sdk_headers.py +47 -0
- chrys/service/llm/sdk_openai.py +18 -0
- chrys/service/llm/sdk_retry.py +59 -0
- chrys/service/llm/wire_client.py +246 -0
- chrys/service/mcp/__init__.py +3 -0
- chrys/service/mcp/_connection.py +235 -0
- chrys/service/mcp/_http_transport.py +350 -0
- chrys/service/mcp/_progressive.py +459 -0
- chrys/service/mcp/_stdio_transport.py +614 -0
- chrys/service/mcp/_tool_mixins.py +96 -0
- chrys/service/mcp/adapter.py +1031 -0
- chrys/service/mcp/cache.py +476 -0
- chrys/service/mcp/content.py +32 -0
- chrys/service/mcp/errors.py +169 -0
- chrys/service/mcp/owned.py +1330 -0
- chrys/service/mcp/result_limits.py +168 -0
- chrys/service/mcp/thinking_warning.py +95 -0
- chrys/service/mcp/validation.py +74 -0
- chrys/service/mutations/__init__.py +84 -0
- chrys/service/mutations/coordination.py +894 -0
- chrys/service/mutations/detector.py +458 -0
- chrys/service/mutations/git_calibrator.py +397 -0
- chrys/service/mutations/git_state.py +647 -0
- chrys/service/mutations/pipeline.py +779 -0
- chrys/service/mutations/scanner.py +466 -0
- chrys/service/mutations/snapshot_files.py +233 -0
- chrys/service/mutations/store.py +625 -0
- chrys/service/mutations/tool_names.py +15 -0
- chrys/service/mutations/trace.py +246 -0
- chrys/service/mutations/tracker.py +1289 -0
- chrys/service/mutations/types.py +559 -0
- chrys/service/mutations/workspace_changes.py +1583 -0
- chrys/service/profiles/__init__.py +2 -0
- chrys/service/profiles/agents/__init__.py +3 -0
- chrys/service/profiles/agents/builtins/Code.yaml +199 -0
- chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
- chrys/service/profiles/agents/builtins/General.yaml +114 -0
- chrys/service/profiles/agents/builtins/QA.yaml +128 -0
- chrys/service/profiles/agents/loader.py +808 -0
- chrys/service/profiles/agents/registry.py +727 -0
- chrys/service/profiles/agents/schema.py +371 -0
- chrys/service/profiles/agents/serializer.py +196 -0
- chrys/service/profiles/coercion.py +22 -0
- chrys/service/profiles/models/__init__.py +3 -0
- chrys/service/profiles/models/env_bridge.py +157 -0
- chrys/service/profiles/models/loader.py +282 -0
- chrys/service/profiles/models/options.py +438 -0
- chrys/service/profiles/models/registry.py +104 -0
- chrys/service/profiles/models/resolver.py +286 -0
- chrys/service/profiles/models/schema.py +91 -0
- chrys/service/profiles/models/serializer.py +76 -0
- chrys/service/session/__init__.py +3 -0
- chrys/service/session/checkpoint.py +156 -0
- chrys/service/session/history.py +1616 -0
- chrys/service/session/message_metadata.py +151 -0
- chrys/service/session/persistence.py +435 -0
- chrys/service/session/runtime_metadata.py +210 -0
- chrys/service/session/sub_agent_logs.py +1069 -0
- chrys/service/session/sub_agent_transcript.py +654 -0
- chrys/service/skills/__init__.py +3 -0
- chrys/service/skills/adapter.py +408 -0
- chrys/service/skills/constants.py +8 -0
- chrys/service/skills/loader.py +575 -0
- chrys/service/skills/model.py +130 -0
- chrys/service/skills/provider.py +841 -0
- chrys/service/skills/runner.py +390 -0
- chrys/service/state/__init__.py +3 -0
- chrys/service/state/_fork.py +710 -0
- chrys/service/state/_session_files.py +175 -0
- chrys/service/state/_session_meta.py +552 -0
- chrys/service/state/locks.py +154 -0
- chrys/service/state/serializers.py +156 -0
- chrys/service/state/session_catalog.py +361 -0
- chrys/service/state/session_listing.py +84 -0
- chrys/service/state/session_migration.py +446 -0
- chrys/service/state/session_mru.py +315 -0
- chrys/service/state/store.py +2132 -0
- chrys/service/state/workflow.py +158 -0
- chrys/service/text_blocks.py +47 -0
- chrys/service/todos/__init__.py +3 -0
- chrys/service/todos/reminder.py +31 -0
- chrys/service/todos/tracker.py +58 -0
- chrys/service/tools/__init__.py +3 -0
- chrys/service/tools/approval_targets.py +92 -0
- chrys/service/tools/builtins/__init__.py +3 -0
- chrys/service/tools/builtins/ask_user.py +326 -0
- chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
- chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
- chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
- chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
- chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
- chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
- chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
- chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
- chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
- chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
- chrys/service/tools/builtins/doc_converter/registry.py +55 -0
- chrys/service/tools/builtins/filesystem.py +1071 -0
- chrys/service/tools/builtins/search.py +1265 -0
- chrys/service/tools/builtins/shell.py +967 -0
- chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
- chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
- chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
- chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
- chrys/service/tools/builtins/shell_filter.py +24 -0
- chrys/service/tools/builtins/sleep.py +91 -0
- chrys/service/tools/builtins/todo.py +64 -0
- chrys/service/tools/builtins/web/__init__.py +6 -0
- chrys/service/tools/builtins/web/build.py +268 -0
- chrys/service/tools/builtins/web/config.py +338 -0
- chrys/service/tools/builtins/web/credentials.py +37 -0
- chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
- chrys/service/tools/builtins/web/fetch/cache.py +68 -0
- chrys/service/tools/builtins/web/fetch/convert.py +56 -0
- chrys/service/tools/builtins/web/fetch/tool.py +223 -0
- chrys/service/tools/builtins/web/hosted.py +61 -0
- chrys/service/tools/builtins/web/http.py +330 -0
- chrys/service/tools/builtins/web/progress.py +18 -0
- chrys/service/tools/builtins/web/search/__init__.py +3 -0
- chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
- chrys/service/tools/builtins/web/search/html.py +152 -0
- chrys/service/tools/builtins/web/search/json_api.py +227 -0
- chrys/service/tools/builtins/web/search/providers.py +77 -0
- chrys/service/tools/builtins/web/search/tool.py +218 -0
- chrys/service/tools/builtins/web/search/types.py +28 -0
- chrys/service/tools/builtins/web/tls_identity.py +66 -0
- chrys/service/tools/kinds.py +115 -0
- chrys/service/tools/names.py +47 -0
- chrys/service/tools/registry.py +242 -0
- chrys/service/tools/result_metadata.py +103 -0
- chrys/service/tools/session_artifacts.py +196 -0
- chrys/service/tools/spill.py +218 -0
- chrys/service/tools/workspace_paths.py +81 -0
- chrys/service/trajectory/__init__.py +11 -0
- chrys/service/trajectory/approvals.py +201 -0
- chrys/service/trajectory/compaction.py +420 -0
- chrys/service/trajectory/fork.py +75 -0
- chrys/service/trajectory/hooks.py +217 -0
- chrys/service/trajectory/items.py +41 -0
- chrys/service/trajectory/preparation.py +374 -0
- chrys/service/trajectory/retries.py +95 -0
- chrys/service/trajectory/revisions.py +129 -0
- chrys/service/trajectory/segmented.py +163 -0
- chrys/service/trajectory/session.py +762 -0
- chrys/service/trajectory/state.py +83 -0
- chrys/service/trajectory/sub_agents.py +169 -0
- chrys/service/trajectory/tombstone.py +262 -0
- chrys/service/trajectory/tools.py +323 -0
- chrys/service/trajectory/validation.py +130 -0
- chrys/service/trajectory/waits.py +161 -0
- chrys/service/trajectory/workflow.py +148 -0
- chrys/service/vision.py +128 -0
- chrys/service/workflows/__init__.py +3 -0
- chrys/service/workflows/admission.py +151 -0
- chrys/service/workflows/artifacts.py +109 -0
- chrys/service/workflows/asks.py +130 -0
- chrys/service/workflows/builtins/__init__.py +12 -0
- chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
- chrys/service/workflows/builtins/demo-workflow.py +756 -0
- chrys/service/workflows/discovery.py +554 -0
- chrys/service/workflows/environment.py +284 -0
- chrys/service/workflows/graph.py +430 -0
- chrys/service/workflows/history.py +82 -0
- chrys/service/workflows/interpreter.py +107 -0
- chrys/service/workflows/journal.py +334 -0
- chrys/service/workflows/layout.py +56 -0
- chrys/service/workflows/ledger.py +149 -0
- chrys/service/workflows/model_selection.py +18 -0
- chrys/service/workflows/orphans.py +209 -0
- chrys/service/workflows/outcomes.py +46 -0
- chrys/service/workflows/protocol.py +210 -0
- chrys/service/workflows/records.py +142 -0
- chrys/service/workflows/scheduler.py +770 -0
- chrys/service/workflows/sdk/__init__.py +51 -0
- chrys/service/workflows/sdk/_ask.py +167 -0
- chrys/service/workflows/sdk/_builder.py +781 -0
- chrys/service/workflows/sdk/_values.py +92 -0
- chrys/service/workflows/sdk_artifact.py +74 -0
- chrys/service/workflows/store.py +494 -0
- chrys/service/workflows/transcript.py +102 -0
- chrys/service/workflows/values.py +73 -0
- chrys/service/workflows/worker_host.py +1820 -0
- chrys/workflows.py +38 -0
- icode_tui-0.29.1.dist-info/METADATA +104 -0
- icode_tui-0.29.1.dist-info/RECORD +1138 -0
- icode_tui-0.29.1.dist-info/WHEEL +4 -0
- icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
- icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
- icode_tui-0.29.1.dist-info/licenses/NOTICE +1955 -0
|
@@ -0,0 +1,1952 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Centralized configuration for Chrys."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import math
|
|
9
|
+
import os
|
|
10
|
+
import tempfile
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Final, Literal
|
|
14
|
+
|
|
15
|
+
from chrys.foundation.config.coercion import (
|
|
16
|
+
MISSING,
|
|
17
|
+
Coerced,
|
|
18
|
+
Coercer,
|
|
19
|
+
CoerceReason,
|
|
20
|
+
CoerceStatus,
|
|
21
|
+
bool_coercer,
|
|
22
|
+
choice_coercer,
|
|
23
|
+
float_coercer,
|
|
24
|
+
int_coercer,
|
|
25
|
+
invalid,
|
|
26
|
+
optional_int_coercer,
|
|
27
|
+
text_coercer,
|
|
28
|
+
)
|
|
29
|
+
from chrys.foundation.config.context import EvalContext
|
|
30
|
+
from chrys.foundation.config.spec import (
|
|
31
|
+
Apply,
|
|
32
|
+
ChoiceProvider,
|
|
33
|
+
InvalidPolicy,
|
|
34
|
+
Kind,
|
|
35
|
+
ProjectMerge,
|
|
36
|
+
Risk,
|
|
37
|
+
spec,
|
|
38
|
+
)
|
|
39
|
+
from chrys.foundation.config.web_values import (
|
|
40
|
+
web_custom_endpoints_coercer,
|
|
41
|
+
web_mode_coercer,
|
|
42
|
+
web_origins_coercer,
|
|
43
|
+
web_proxy_url_coercer,
|
|
44
|
+
)
|
|
45
|
+
from chrys.foundation.i18n import msg
|
|
46
|
+
from chrys.foundation.i18n.locale import SUPPORTED_LOCALES
|
|
47
|
+
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
|
|
50
|
+
DEFAULT_THEME = "chrys"
|
|
51
|
+
"""Fallback theme name when ``CHRYS_THEME`` is empty, missing, or invalid."""
|
|
52
|
+
|
|
53
|
+
RENAMED_THEMES: Final[dict[str, str]] = {"chrys-dark": "chrys"}
|
|
54
|
+
"""Built-in theme names that are gone, and the name each of them goes by now.
|
|
55
|
+
|
|
56
|
+
The ``ui.theme`` coercer follows this at every layer, so a retired name in an
|
|
57
|
+
environment variable or a project file still selects the theme it meant. A
|
|
58
|
+
retired name saved in the user document is also rewritten there, by
|
|
59
|
+
:func:`chrys.foundation.config.migrations.migrate_renamed_theme`. The old names
|
|
60
|
+
stay reserved: a user theme taking one would be renamed away on the next start.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
DEFAULT_LOCALE = "system"
|
|
64
|
+
"""Fallback locale selector when ``CHRYS_LOCALE`` is empty or missing."""
|
|
65
|
+
|
|
66
|
+
DEFAULT_APPROVAL_MODE = "manual"
|
|
67
|
+
"""Fallback approval mode when ``CHRYS_DEFAULT_APPROVAL_MODE`` is empty, missing, or invalid.
|
|
68
|
+
|
|
69
|
+
Valid values are ``manual``, ``auto``, ``bypass``. The persistence helper
|
|
70
|
+
(:func:`persist_approval_mode`) deliberately downgrades ``bypass`` to ``auto``
|
|
71
|
+
so that Chrys never launches in BYPASS mode — that mode is opt-in per launch:
|
|
72
|
+
once chosen it stays in force for every session until the app exits.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
DEFAULT_AGENT_PROFILE = "Code"
|
|
76
|
+
"""Fallback TUI launch agent profile when ``CHRYS_DEFAULT_AGENT`` is empty or unusable."""
|
|
77
|
+
|
|
78
|
+
DEFAULT_EDITOR_KEYMAP = "standard"
|
|
79
|
+
"""Fallback message-editor keymap when its persisted value is missing or invalid."""
|
|
80
|
+
|
|
81
|
+
SESSION_ROOT_DIR_ENV_VAR: Final[str] = "CHRYS_SESSION_ROOT_DIR"
|
|
82
|
+
"""Base directory for session storage. The actual session store lives under ``sessions/`` within it."""
|
|
83
|
+
|
|
84
|
+
_VALID_APPROVAL_MODES = ("manual", "auto", "bypass")
|
|
85
|
+
_VALID_EDITOR_KEYMAPS = ("standard", "emacs", "vim")
|
|
86
|
+
|
|
87
|
+
_TRUTHY_ENV = {"1", "true", "yes", "on"}
|
|
88
|
+
|
|
89
|
+
_RESOLVED_SESSIONS_DIR_CACHE: dict[str, Path] = {}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _load_default_approval_mode() -> str:
|
|
93
|
+
"""Read ``CHRYS_DEFAULT_APPROVAL_MODE`` from env, fall back on invalid/missing."""
|
|
94
|
+
raw = os.environ.get("CHRYS_DEFAULT_APPROVAL_MODE", "").strip().lower()
|
|
95
|
+
return raw if raw in _VALID_APPROVAL_MODES else DEFAULT_APPROVAL_MODE
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _load_default_agent() -> str:
|
|
99
|
+
"""Read ``CHRYS_DEFAULT_AGENT`` from env, falling back to the built-in Code profile."""
|
|
100
|
+
return os.environ.get("CHRYS_DEFAULT_AGENT", "").strip() or DEFAULT_AGENT_PROFILE
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _load_editor_keymap() -> str:
|
|
104
|
+
"""Read and normalize ``CHRYS_EDITOR_KEYMAP`` with a Standard fallback."""
|
|
105
|
+
raw = os.environ.get("CHRYS_EDITOR_KEYMAP", "").strip().lower()
|
|
106
|
+
return raw if raw in _VALID_EDITOR_KEYMAPS else DEFAULT_EDITOR_KEYMAP
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def parse_bool_env_value(raw: str | None, *, default: bool = False) -> bool:
|
|
110
|
+
"""Parse one environment-style boolean with Chrys's shared semantics."""
|
|
111
|
+
normalized = (raw or "").strip().lower()
|
|
112
|
+
if not normalized:
|
|
113
|
+
return default
|
|
114
|
+
return normalized in _TRUTHY_ENV
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _load_bool_env(name: str, *, default: bool = False) -> bool:
|
|
118
|
+
"""Parse ``name`` as a boolean (1/true/yes/on, case-insensitive)."""
|
|
119
|
+
return parse_bool_env_value(os.environ.get(name), default=default)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
DEFAULT_ASK_USER_TIMEOUT_SECONDS = 900
|
|
123
|
+
"""Default ``ask_user`` reply wait (15 minutes) when ``CHRYS_ASK_USER_TIMEOUT_SECONDS`` is unset."""
|
|
124
|
+
|
|
125
|
+
DEFAULT_MAX_TRANSIENT_RETRIES = 10
|
|
126
|
+
"""Default application-layer transient retry budget for interactive frontends."""
|
|
127
|
+
|
|
128
|
+
HEADLESS_DEFAULT_MAX_TRANSIENT_RETRIES = 18
|
|
129
|
+
"""Default application-layer transient retry budget for ``chrys run``."""
|
|
130
|
+
|
|
131
|
+
MAX_TRANSIENT_RETRIES_LIMIT = 50
|
|
132
|
+
"""Upper bound for ``CHRYS_MAX_TRANSIENT_RETRIES``."""
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(frozen=True, slots=True)
|
|
136
|
+
class MaxTransientRetriesWarning:
|
|
137
|
+
"""Structured warning emitted while parsing the transient retry budget."""
|
|
138
|
+
|
|
139
|
+
variant: Literal["invalid", "clamped"]
|
|
140
|
+
raw: str
|
|
141
|
+
value: int | None
|
|
142
|
+
limit: int
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
DEFAULT_TOOL_RESULT_CEILING_TOKENS = 64_000
|
|
146
|
+
"""Default kernel backstop for one local tool result."""
|
|
147
|
+
|
|
148
|
+
TOOL_RESULT_CEILING_FLOOR = 2_000
|
|
149
|
+
"""Minimum positive ``CHRYS_TOOL_RESULT_CEILING_TOKENS`` value."""
|
|
150
|
+
|
|
151
|
+
DEFAULT_WORKSPACE_MRU_MAX_ENTRIES = 20
|
|
152
|
+
"""Default number of recently used workspace directories kept in the MRU index."""
|
|
153
|
+
|
|
154
|
+
WORKSPACE_MRU_MAX_ENTRIES_LIMIT = 100
|
|
155
|
+
"""Upper clamp for ``CHRYS_WORKSPACE_MRU_MAX_ENTRIES`` so quick-access lists stay bounded."""
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _load_workspace_mru_max_entries() -> int:
|
|
159
|
+
"""Read ``CHRYS_WORKSPACE_MRU_MAX_ENTRIES``; garbage → default, non-positive → 0 (disabled)."""
|
|
160
|
+
raw = os.environ.get("CHRYS_WORKSPACE_MRU_MAX_ENTRIES", "").strip()
|
|
161
|
+
if not raw:
|
|
162
|
+
return DEFAULT_WORKSPACE_MRU_MAX_ENTRIES
|
|
163
|
+
try:
|
|
164
|
+
value = int(raw)
|
|
165
|
+
except ValueError:
|
|
166
|
+
return DEFAULT_WORKSPACE_MRU_MAX_ENTRIES
|
|
167
|
+
if value <= 0:
|
|
168
|
+
return 0
|
|
169
|
+
return min(value, WORKSPACE_MRU_MAX_ENTRIES_LIMIT)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
DEFAULT_WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES = 50
|
|
173
|
+
WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES_LIMIT = 100
|
|
174
|
+
|
|
175
|
+
DEFAULT_TRAJECTORY_VERIFY_COMMANDS = (
|
|
176
|
+
"pytest,ruff,ty,mypy,pyright,flake8,pylint,tox,nox,unittest,"
|
|
177
|
+
"npm test,npm run test,pnpm test,yarn test,bun test,deno test,node --test,"
|
|
178
|
+
"vitest,jest,mocha,playwright test,cypress run,eslint,biome check,tsc,"
|
|
179
|
+
"cargo test,cargo clippy,cargo check,cargo nextest,"
|
|
180
|
+
"go test,go vet,golangci-lint,staticcheck,"
|
|
181
|
+
"mvn test,mvn verify,gradle test,gradlew test,gradle check,gradlew check,"
|
|
182
|
+
"rspec,rake test,rubocop,phpunit,phpstan,dotnet test,mix test,swift test,"
|
|
183
|
+
"ctest,shellcheck,make"
|
|
184
|
+
)
|
|
185
|
+
"""Comma-separated command words and phrases classified as verification work."""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _load_workspace_change_notice_max_entries() -> int:
|
|
189
|
+
"""Read and clamp the workspace-change notice path limit to ``1..100``."""
|
|
190
|
+
raw = os.environ.get("CHRYS_WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES", "").strip()
|
|
191
|
+
if not raw:
|
|
192
|
+
return DEFAULT_WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES
|
|
193
|
+
try:
|
|
194
|
+
value = int(raw)
|
|
195
|
+
except ValueError:
|
|
196
|
+
return DEFAULT_WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES
|
|
197
|
+
return min(max(value, 1), WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES_LIMIT)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
DEFAULT_MUTATION_SNAPSHOT_MAX_FILE_MB = 50
|
|
201
|
+
"""Default per-file cap (in MiB) on mutation backup blobs stored under the session dir."""
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _load_mutation_snapshot_max_file_mb() -> int:
|
|
205
|
+
"""Read ``CHRYS_MUTATION_SNAPSHOT_MAX_FILE_MB``; garbage → default, non-positive → 0 (no cap)."""
|
|
206
|
+
raw = os.environ.get("CHRYS_MUTATION_SNAPSHOT_MAX_FILE_MB", "").strip()
|
|
207
|
+
if not raw:
|
|
208
|
+
return DEFAULT_MUTATION_SNAPSHOT_MAX_FILE_MB
|
|
209
|
+
try:
|
|
210
|
+
value = int(raw)
|
|
211
|
+
except ValueError:
|
|
212
|
+
return DEFAULT_MUTATION_SNAPSHOT_MAX_FILE_MB
|
|
213
|
+
return max(0, value)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _load_ask_user_timeout() -> int | None:
|
|
217
|
+
"""Read ``CHRYS_ASK_USER_TIMEOUT_SECONDS``; empty → default, non-positive → no timeout."""
|
|
218
|
+
raw = os.environ.get("CHRYS_ASK_USER_TIMEOUT_SECONDS", "").strip()
|
|
219
|
+
if not raw:
|
|
220
|
+
return DEFAULT_ASK_USER_TIMEOUT_SECONDS
|
|
221
|
+
try:
|
|
222
|
+
value = int(raw)
|
|
223
|
+
except ValueError:
|
|
224
|
+
return DEFAULT_ASK_USER_TIMEOUT_SECONDS
|
|
225
|
+
return value if value > 0 else None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def parse_max_transient_retries_structured(
|
|
229
|
+
raw: str | None,
|
|
230
|
+
) -> tuple[int | None, MaxTransientRetriesWarning | None]:
|
|
231
|
+
"""Parse ``CHRYS_MAX_TRANSIENT_RETRIES`` and retain warning arguments."""
|
|
232
|
+
normalized = (raw or "").strip()
|
|
233
|
+
if not normalized:
|
|
234
|
+
return None, None
|
|
235
|
+
try:
|
|
236
|
+
value = int(normalized)
|
|
237
|
+
except ValueError:
|
|
238
|
+
return None, MaxTransientRetriesWarning(
|
|
239
|
+
variant="invalid",
|
|
240
|
+
raw=str(raw),
|
|
241
|
+
value=None,
|
|
242
|
+
limit=MAX_TRANSIENT_RETRIES_LIMIT,
|
|
243
|
+
)
|
|
244
|
+
if value < 0:
|
|
245
|
+
return None, MaxTransientRetriesWarning(
|
|
246
|
+
variant="invalid",
|
|
247
|
+
raw=str(raw),
|
|
248
|
+
value=None,
|
|
249
|
+
limit=MAX_TRANSIENT_RETRIES_LIMIT,
|
|
250
|
+
)
|
|
251
|
+
if value > MAX_TRANSIENT_RETRIES_LIMIT:
|
|
252
|
+
return MAX_TRANSIENT_RETRIES_LIMIT, MaxTransientRetriesWarning(
|
|
253
|
+
variant="clamped",
|
|
254
|
+
raw=str(raw),
|
|
255
|
+
value=value,
|
|
256
|
+
limit=MAX_TRANSIENT_RETRIES_LIMIT,
|
|
257
|
+
)
|
|
258
|
+
return value, None
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def parse_max_transient_retries(raw: str | None) -> tuple[int | None, str | None]:
|
|
262
|
+
"""Parse ``CHRYS_MAX_TRANSIENT_RETRIES`` without raising."""
|
|
263
|
+
value, warning = parse_max_transient_retries_structured(raw)
|
|
264
|
+
if warning is None:
|
|
265
|
+
return value, None
|
|
266
|
+
if warning.variant == "invalid":
|
|
267
|
+
message = (
|
|
268
|
+
f"Ignoring invalid CHRYS_MAX_TRANSIENT_RETRIES={raw!r}; "
|
|
269
|
+
"expected a non-negative integer and will use the frontend default."
|
|
270
|
+
)
|
|
271
|
+
else:
|
|
272
|
+
message = (
|
|
273
|
+
f"CHRYS_MAX_TRANSIENT_RETRIES={warning.value} exceeds the limit of {warning.limit}; "
|
|
274
|
+
f"clamping to {warning.limit}."
|
|
275
|
+
)
|
|
276
|
+
return value, message
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _load_max_transient_retries() -> int | None:
|
|
280
|
+
"""Read the transient retry budget from the environment without raising."""
|
|
281
|
+
value, warning = parse_max_transient_retries(os.environ.get("CHRYS_MAX_TRANSIENT_RETRIES"))
|
|
282
|
+
if warning is not None:
|
|
283
|
+
logger.warning(warning)
|
|
284
|
+
return value
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def parse_tool_result_ceiling_tokens(raw: str | None) -> tuple[int, str | None]:
|
|
288
|
+
"""Parse ``CHRYS_TOOL_RESULT_CEILING_TOKENS`` without raising."""
|
|
289
|
+
normalized = (raw or "").strip()
|
|
290
|
+
if not normalized:
|
|
291
|
+
return DEFAULT_TOOL_RESULT_CEILING_TOKENS, None
|
|
292
|
+
try:
|
|
293
|
+
value = int(normalized)
|
|
294
|
+
except ValueError:
|
|
295
|
+
return DEFAULT_TOOL_RESULT_CEILING_TOKENS, (
|
|
296
|
+
f"Ignoring invalid CHRYS_TOOL_RESULT_CEILING_TOKENS={raw!r}; "
|
|
297
|
+
f"using the default of {DEFAULT_TOOL_RESULT_CEILING_TOKENS}."
|
|
298
|
+
)
|
|
299
|
+
if value == 0:
|
|
300
|
+
return 0, None
|
|
301
|
+
if value < 0:
|
|
302
|
+
return DEFAULT_TOOL_RESULT_CEILING_TOKENS, (
|
|
303
|
+
f"Ignoring invalid CHRYS_TOOL_RESULT_CEILING_TOKENS={raw!r}; "
|
|
304
|
+
f"using the default of {DEFAULT_TOOL_RESULT_CEILING_TOKENS}."
|
|
305
|
+
)
|
|
306
|
+
if value < TOOL_RESULT_CEILING_FLOOR:
|
|
307
|
+
return TOOL_RESULT_CEILING_FLOOR, (
|
|
308
|
+
f"CHRYS_TOOL_RESULT_CEILING_TOKENS={value} is below the minimum of "
|
|
309
|
+
f"{TOOL_RESULT_CEILING_FLOOR}; clamping to {TOOL_RESULT_CEILING_FLOOR}."
|
|
310
|
+
)
|
|
311
|
+
return value, None
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _load_tool_result_ceiling_tokens() -> int:
|
|
315
|
+
value, warning = parse_tool_result_ceiling_tokens(os.environ.get("CHRYS_TOOL_RESULT_CEILING_TOKENS"))
|
|
316
|
+
if warning is not None:
|
|
317
|
+
logger.warning(warning)
|
|
318
|
+
return value
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def default_session_root_dir(config_dir: str | Path | None = None) -> Path:
|
|
322
|
+
"""Return the default root under which session storage lives."""
|
|
323
|
+
if config_dir is None:
|
|
324
|
+
from chrys.foundation.platform import get_platform
|
|
325
|
+
|
|
326
|
+
config_dir = get_platform().config_dir
|
|
327
|
+
return Path(config_dir)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def resolve_session_root_dir(config_dir: str | Path | None = None, *, create: bool = True) -> Path:
|
|
331
|
+
"""Return the active base root for session storage.
|
|
332
|
+
|
|
333
|
+
Reads the process snapshot rather than the environment: the twelve callers
|
|
334
|
+
that resolve a sessions directory hold no ``Settings`` between them, and
|
|
335
|
+
threading one through all of them would only move the problem — the value
|
|
336
|
+
is ``Apply.RESTART``, so a single per-process answer is the honest one.
|
|
337
|
+
"""
|
|
338
|
+
# Local import: ``process_settings`` falls back to a fresh ``load_settings``
|
|
339
|
+
# when bootstrap has not installed a snapshot, which needs this module.
|
|
340
|
+
from chrys.foundation.config.process_settings import process_settings
|
|
341
|
+
|
|
342
|
+
fallback = default_session_root_dir(config_dir)
|
|
343
|
+
raw = process_settings().session_root_dir.strip()
|
|
344
|
+
if raw:
|
|
345
|
+
candidate = _session_root_candidate(raw)
|
|
346
|
+
if candidate is not None and _session_root_dir_is_valid(candidate, create=create):
|
|
347
|
+
return candidate
|
|
348
|
+
logger.warning("Ignoring invalid %s=%r; using %s", SESSION_ROOT_DIR_ENV_VAR, raw, fallback)
|
|
349
|
+
|
|
350
|
+
if create:
|
|
351
|
+
fallback.mkdir(parents=True, exist_ok=True)
|
|
352
|
+
return fallback
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def resolve_sessions_dir(config_dir: str | Path | None = None, *, create: bool = True) -> Path:
|
|
356
|
+
"""Return the active directory containing saved session folders."""
|
|
357
|
+
root = resolve_session_root_dir(config_dir, create=create)
|
|
358
|
+
sessions_dir = root / "sessions"
|
|
359
|
+
if create:
|
|
360
|
+
cache_key = str(sessions_dir)
|
|
361
|
+
cached = _RESOLVED_SESSIONS_DIR_CACHE.get(cache_key)
|
|
362
|
+
if cached is not None:
|
|
363
|
+
return cached
|
|
364
|
+
if _session_storage_dir_is_valid(sessions_dir, create=create):
|
|
365
|
+
if create:
|
|
366
|
+
_RESOLVED_SESSIONS_DIR_CACHE[str(sessions_dir)] = sessions_dir
|
|
367
|
+
return sessions_dir
|
|
368
|
+
|
|
369
|
+
fallback = default_session_root_dir(config_dir) / "sessions"
|
|
370
|
+
if sessions_dir != fallback:
|
|
371
|
+
logger.warning("Ignoring invalid session storage directory %s; using %s", sessions_dir, fallback)
|
|
372
|
+
if _session_storage_dir_is_valid(fallback, create=create) and create:
|
|
373
|
+
_RESOLVED_SESSIONS_DIR_CACHE[str(fallback)] = fallback
|
|
374
|
+
return fallback
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def session_root_is_ruled_out(raw: str) -> bool:
|
|
378
|
+
"""Whether *raw* can be rejected as a session root without creating it.
|
|
379
|
+
|
|
380
|
+
Answers only what can be settled by looking: that the system takes the path
|
|
381
|
+
at all, and that nothing already sitting on the way down to
|
|
382
|
+
``<root>/sessions`` rules out a directory ever being there. A root that gets
|
|
383
|
+
past this is one the settings may go on naming.
|
|
384
|
+
|
|
385
|
+
It deliberately does not predict whether the write will be *permitted* —
|
|
386
|
+
whether a directory that exists will accept a new one, whether one that does
|
|
387
|
+
not exist can be created. Those cannot be answered without performing the
|
|
388
|
+
operation being predicted, on the real names, which is the creation this is
|
|
389
|
+
supposed to avoid; every approximation of it (a file probe standing in for a
|
|
390
|
+
``mkdir``, one level standing in for the descent, a scratch name standing in
|
|
391
|
+
for the real one) is wrong in both directions for some real permission
|
|
392
|
+
scheme. So permission is left to the moment it is exercised, where the
|
|
393
|
+
answer is observed rather than guessed: :func:`resolve_sessions_dir` falls
|
|
394
|
+
back to the default location and logs that it did. The cost of that choice
|
|
395
|
+
is bounded and known — until first use the settings can name a root that
|
|
396
|
+
turns out to be unwritable — and it is the only part of this that a
|
|
397
|
+
check running here cannot get right.
|
|
398
|
+
"""
|
|
399
|
+
candidate = _session_root_candidate(raw)
|
|
400
|
+
if candidate is None:
|
|
401
|
+
return True
|
|
402
|
+
# The root is only ever used through this child, and the walk to the child
|
|
403
|
+
# passes through the root, so asking about the child asks about both.
|
|
404
|
+
sessions = candidate / "sessions"
|
|
405
|
+
start = _deepest_existing_directory(sessions)
|
|
406
|
+
if start is None:
|
|
407
|
+
return True
|
|
408
|
+
return any(_name_is_refused(start, part) for part in sessions.relative_to(start).parts)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _name_is_refused(parent: Path, name: str) -> bool:
|
|
412
|
+
"""Whether *name* is one this filesystem will not take as a child of *parent*.
|
|
413
|
+
|
|
414
|
+
Asking about the whole path only gets as far as the first component that is
|
|
415
|
+
not there: resolution stops at that one with ``ENOENT``, so a name below it
|
|
416
|
+
that no filesystem would accept is never looked at, and the ``mkdir`` that
|
|
417
|
+
creates the level above then fails on it. Each of them therefore gets asked
|
|
418
|
+
about separately, hung off the deepest directory that does exist.
|
|
419
|
+
|
|
420
|
+
Which is the right place to ask, not an approximation of it: nothing can be
|
|
421
|
+
mounted under a directory that is not there, so every level still to be
|
|
422
|
+
created lands in that one's filesystem and is held to its limits. And it
|
|
423
|
+
stays a question — ``lstat`` on a name that is merely free answers ``ENOENT``
|
|
424
|
+
and creates nothing. Some FUSE filesystems also answer ``ENOENT`` for a
|
|
425
|
+
name they refuse as too long, so that ambiguous answer is checked against
|
|
426
|
+
the filesystem's advertised name limit.
|
|
427
|
+
"""
|
|
428
|
+
try:
|
|
429
|
+
(parent / name).lstat()
|
|
430
|
+
except FileNotFoundError:
|
|
431
|
+
if not hasattr(os, "statvfs"):
|
|
432
|
+
return False
|
|
433
|
+
try:
|
|
434
|
+
name_max = os.statvfs(parent).f_namemax
|
|
435
|
+
except OSError, ValueError:
|
|
436
|
+
return False
|
|
437
|
+
return name_max > 0 and len(os.fsencode(name)) > name_max
|
|
438
|
+
except OSError, ValueError:
|
|
439
|
+
return True
|
|
440
|
+
return False
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _deepest_existing_directory(path: Path) -> Path | None:
|
|
444
|
+
"""The directory ``mkdir(parents=True)`` would build *path* down from.
|
|
445
|
+
|
|
446
|
+
``None`` when there is none, which is a rejection: everything below it the
|
|
447
|
+
``mkdir`` creates, and it creates none of it if what it finds is not a
|
|
448
|
+
directory. A dangling link two levels up leaves the root reading as absent
|
|
449
|
+
while the entry holding its name is very much present.
|
|
450
|
+
|
|
451
|
+
``lstat`` rather than ``exists`` or ``lexists``, both of which answer two
|
|
452
|
+
questions with one ``False``: "nothing is there" and "the system would not
|
|
453
|
+
even take the question". Only the first keeps the walk going. The second
|
|
454
|
+
covers a component too long for the filesystem, an embedded NUL, a path
|
|
455
|
+
Windows spells illegally, a symlink loop, a parent that is a regular file —
|
|
456
|
+
every one of which reads as free to create, and then fails the ``mkdir``,
|
|
457
|
+
leaving storage on the default while the settings go on naming this root.
|
|
458
|
+
Asking the system beats keeping a list of what it rejects, which would grow
|
|
459
|
+
a case per platform and still be a case behind.
|
|
460
|
+
|
|
461
|
+
``exists`` misreports one more: it follows the link, so a dangling one reads
|
|
462
|
+
as absent while its entry still occupies the name. ``lstat`` does not follow
|
|
463
|
+
the last component and ``is_dir`` does, which is what keeps a link resolving
|
|
464
|
+
to a real directory usable while a dangling one is not.
|
|
465
|
+
"""
|
|
466
|
+
for existing in (path, *path.parents):
|
|
467
|
+
try:
|
|
468
|
+
existing.lstat()
|
|
469
|
+
except FileNotFoundError:
|
|
470
|
+
continue
|
|
471
|
+
except OSError, ValueError:
|
|
472
|
+
return None
|
|
473
|
+
return existing if existing.is_dir() else None
|
|
474
|
+
# Nothing on the way up is there — not even the anchor the walk ends at.
|
|
475
|
+
# POSIX bottoms out at ``/``, which is always there, but a Windows path ends
|
|
476
|
+
# at its drive or share, and an unmapped ``Z:\`` is as missing as its
|
|
477
|
+
# children.
|
|
478
|
+
return None
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def _session_root_candidate(raw: str) -> Path | None:
|
|
482
|
+
"""Parse a session root env value, returning ``None`` on unusable path syntax."""
|
|
483
|
+
try:
|
|
484
|
+
return Path(raw).expanduser()
|
|
485
|
+
except OSError, RuntimeError, ValueError:
|
|
486
|
+
return None
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _session_root_dir_is_valid(path: Path, *, create: bool) -> bool:
|
|
490
|
+
"""Return whether *path* can be used as a session storage root."""
|
|
491
|
+
try:
|
|
492
|
+
if path.exists():
|
|
493
|
+
return path.is_dir()
|
|
494
|
+
if not create:
|
|
495
|
+
return True
|
|
496
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
497
|
+
return path.is_dir()
|
|
498
|
+
except OSError, ValueError:
|
|
499
|
+
return False
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
DEFAULT_ROLLBACK_SNAPSHOTS_KEEP = 20
|
|
503
|
+
"""Per-turn ``session.json`` snapshots retained for ``/rollback``."""
|
|
504
|
+
|
|
505
|
+
DEFAULT_FILE_SNAPSHOT_INLINE_CHARS = 128 * 1024
|
|
506
|
+
"""Combined before/after snapshot bytes chat tool records keep inline."""
|
|
507
|
+
|
|
508
|
+
DEFAULT_MUTATION_TRACE_MODE = "auto"
|
|
509
|
+
MUTATION_TRACE_MODES = ("auto", "off")
|
|
510
|
+
"""``auto`` probes for fsatrace once per process; ``off`` skips tracing."""
|
|
511
|
+
|
|
512
|
+
_TRACE_MODE_OFF_SPELLINGS = frozenset({"off", "0", "false", "no"})
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _trace_mode_env_coercer() -> Coercer:
|
|
516
|
+
"""Coerce ``CHRYS_MUTATION_TRACE``, preserving its historical spellings.
|
|
517
|
+
|
|
518
|
+
The consumer only ever asked "is it off", so anything that is not one of
|
|
519
|
+
the off spellings has always meant ``auto`` — including typos. Keep that
|
|
520
|
+
for the variable rather than start rejecting values a working setup may
|
|
521
|
+
rely on. The dotted key is a new surface with no such history, so it gets
|
|
522
|
+
the ordinary closed-choice grammar instead of inheriting this one.
|
|
523
|
+
"""
|
|
524
|
+
|
|
525
|
+
def coerce(raw: object) -> Coerced:
|
|
526
|
+
if raw is None:
|
|
527
|
+
return MISSING
|
|
528
|
+
if not isinstance(raw, str):
|
|
529
|
+
return invalid(raw, CoerceReason.EXPECTED_TEXT)
|
|
530
|
+
if not raw.strip():
|
|
531
|
+
return MISSING
|
|
532
|
+
off = raw.strip().lower() in _TRACE_MODE_OFF_SPELLINGS
|
|
533
|
+
return Coerced(status=CoerceStatus.VALID, value="off" if off else DEFAULT_MUTATION_TRACE_MODE)
|
|
534
|
+
|
|
535
|
+
return coerce
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _theme_coercer() -> Coercer:
|
|
539
|
+
"""Coerce a theme name, following a retired built-in name to the one it goes by now.
|
|
540
|
+
|
|
541
|
+
Whether the name is a theme at all is still the app's call — themes are
|
|
542
|
+
registered in a tier this one cannot import. A rename is different: it is
|
|
543
|
+
a fact about the value, the same whichever layer wrote it.
|
|
544
|
+
"""
|
|
545
|
+
text = text_coercer()
|
|
546
|
+
|
|
547
|
+
def coerce(raw: object) -> Coerced:
|
|
548
|
+
outcome = text(raw)
|
|
549
|
+
if outcome.status is not CoerceStatus.VALID:
|
|
550
|
+
return outcome
|
|
551
|
+
return Coerced(status=CoerceStatus.VALID, value=RENAMED_THEMES.get(outcome.value, outcome.value))
|
|
552
|
+
|
|
553
|
+
return coerce
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _history_disable_coercer() -> Coercer:
|
|
557
|
+
"""Coerce ``CHRYS_HISTORY_DISABLE`` into the positive key it feeds.
|
|
558
|
+
|
|
559
|
+
Exactly ``"1"`` disables history; every other value, including ``"0"`` and
|
|
560
|
+
the empty string, leaves it on. Narrower than the general boolean grammar
|
|
561
|
+
on purpose — that is what the variable has always meant.
|
|
562
|
+
"""
|
|
563
|
+
|
|
564
|
+
def coerce(raw: object) -> Coerced:
|
|
565
|
+
if raw is None:
|
|
566
|
+
return MISSING
|
|
567
|
+
if not isinstance(raw, str):
|
|
568
|
+
return invalid(raw, CoerceReason.EXPECTED_TEXT)
|
|
569
|
+
return Coerced(status=CoerceStatus.VALID, value=raw != "1")
|
|
570
|
+
|
|
571
|
+
return coerce
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _semantic_max_transient_retries(settings: Settings, ctx: EvalContext) -> int:
|
|
575
|
+
"""Comparable retry budget: ``None`` means "whatever this frontend uses".
|
|
576
|
+
|
|
577
|
+
The stored value cannot be compared directly — a project value of 10 is a
|
|
578
|
+
tightening against headless (15) and a loosening against the TUI (7), and
|
|
579
|
+
``None`` is neither until the frontend is known.
|
|
580
|
+
"""
|
|
581
|
+
if settings.max_transient_retries is not None:
|
|
582
|
+
return settings.max_transient_retries
|
|
583
|
+
return ctx.frontend_default_max_transient_retries
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _semantic_tool_result_ceiling(settings: Settings, _ctx: EvalContext) -> float:
|
|
587
|
+
"""Comparable ceiling: ``0`` disables the backstop, so it is the largest."""
|
|
588
|
+
ceiling = settings.tool_result_ceiling_tokens
|
|
589
|
+
return math.inf if ceiling <= 0 else float(ceiling)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def _session_storage_dir_is_valid(path: Path, *, create: bool) -> bool:
|
|
593
|
+
"""Return whether the concrete sessions directory is usable."""
|
|
594
|
+
try:
|
|
595
|
+
if path.exists():
|
|
596
|
+
if not path.is_dir():
|
|
597
|
+
return False
|
|
598
|
+
elif not create:
|
|
599
|
+
return True
|
|
600
|
+
else:
|
|
601
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
602
|
+
if create:
|
|
603
|
+
with tempfile.TemporaryFile(dir=path):
|
|
604
|
+
pass
|
|
605
|
+
return True
|
|
606
|
+
except OSError, ValueError:
|
|
607
|
+
return False
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def probe_session_root(raw: str) -> Path | None:
|
|
611
|
+
"""Try the sessions directory a session-root value would resolve to.
|
|
612
|
+
|
|
613
|
+
Where :func:`session_root_is_ruled_out` only looks, this creates: it makes
|
|
614
|
+
``<root>/sessions`` (or the default location's, for a blank *raw*) and
|
|
615
|
+
checks that it takes a write, exactly as :func:`resolve_sessions_dir` will
|
|
616
|
+
on first use. The returned path is for validation and display only — the
|
|
617
|
+
persisted value stays the string, and ``None`` means the value would send
|
|
618
|
+
storage to the fallback.
|
|
619
|
+
|
|
620
|
+
A blank value is probed at the default location directly rather than
|
|
621
|
+
through the ruled-out check, whose empty-string candidate is the current
|
|
622
|
+
directory: a file named ``sessions`` sitting there would otherwise reject
|
|
623
|
+
the one value that never touches it.
|
|
624
|
+
"""
|
|
625
|
+
# The persisted value is the trimmed string, so that is what gets probed.
|
|
626
|
+
raw = raw.strip()
|
|
627
|
+
if not raw:
|
|
628
|
+
default_sessions = default_session_root_dir() / "sessions"
|
|
629
|
+
return default_sessions if _session_storage_dir_is_valid(default_sessions, create=True) else None
|
|
630
|
+
if session_root_is_ruled_out(raw):
|
|
631
|
+
return None
|
|
632
|
+
candidate = _session_root_candidate(raw)
|
|
633
|
+
if candidate is None:
|
|
634
|
+
return None
|
|
635
|
+
sessions = candidate / "sessions"
|
|
636
|
+
return sessions if _session_storage_dir_is_valid(sessions, create=True) else None
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# ── Panel labels ─────────────────────────────────────────────────────
|
|
640
|
+
# One per persisted field, keyed ``settings.<dotted key>.label``. Declared at
|
|
641
|
+
# module level so extraction sees them; ``SettingSpec`` refuses a persisted
|
|
642
|
+
# field without one.
|
|
643
|
+
|
|
644
|
+
_LABEL_MODEL_PROFILE_ACTIVE = msg("settings.model.profile.active.label", fallback="Active model profile")
|
|
645
|
+
_LABEL_AGENT_DEFAULT_PROFILE = msg("settings.agent.default_profile.label", fallback="Default agent")
|
|
646
|
+
_LABEL_MODEL_ROLE_APPROVAL_JUDGE = msg("settings.model.role.approval_judge.label", fallback="Approval judge model")
|
|
647
|
+
_LABEL_MODEL_ROLE_SESSION_TITLE = msg("settings.model.role.session_title.label", fallback="Session title model")
|
|
648
|
+
_LABEL_SESSION_TITLE_AUTO = msg("settings.session.title.auto.label", fallback="Auto-generate session titles")
|
|
649
|
+
_LABEL_CONTEXT_WARN_THRESHOLD_PCT = msg(
|
|
650
|
+
"settings.context.warn_threshold_pct.label", fallback="Context warning threshold"
|
|
651
|
+
)
|
|
652
|
+
_LABEL_TRAJECTORY_VERIFY_COMMANDS = msg(
|
|
653
|
+
"settings.trajectory.verify_commands.label", fallback="Trajectory verification commands"
|
|
654
|
+
)
|
|
655
|
+
_LABEL_LLM_RETRY_MAX_TRANSIENT = msg("settings.llm.retry.max_transient.label", fallback="Max transient retries")
|
|
656
|
+
_LABEL_TOOLS_RESULT_CEILING_TOKENS = msg(
|
|
657
|
+
"settings.tools.result.ceiling_tokens.label", fallback="Tool result ceiling (tokens)"
|
|
658
|
+
)
|
|
659
|
+
_LABEL_SEARCH_RESPECT_GITIGNORE = msg("settings.tools.search.respect_gitignore.label", fallback="Respect .gitignore")
|
|
660
|
+
_LABEL_UI_THEME = msg("settings.ui.theme.label", fallback="Theme")
|
|
661
|
+
_LABEL_UI_LOCALE = msg("settings.ui.locale.label", fallback="UI Language")
|
|
662
|
+
_LABEL_UI_CHAT_TOOL_GROUPS_EXPANDED = msg(
|
|
663
|
+
"settings.ui.chat.tool_groups_expanded.label",
|
|
664
|
+
fallback="Expand tool details by default",
|
|
665
|
+
)
|
|
666
|
+
_LABEL_UI_EDITOR_KEYMAP = msg("settings.ui.editor.keymap.label", fallback="Editor keymap")
|
|
667
|
+
_LABEL_WORKSPACE_MRU_MAX_ENTRIES = msg("settings.workspace.mru_max_entries.label", fallback="Recent workspaces to keep")
|
|
668
|
+
_LABEL_APPROVAL_DEFAULT_MODE = msg("settings.approval.default_mode.label", fallback="Default approval mode")
|
|
669
|
+
_LABEL_UI_APPROVAL_DEFER_WHILE_JUDGING = msg(
|
|
670
|
+
"settings.ui.approval.defer_while_judging.label",
|
|
671
|
+
fallback="Show the approval dialog only when Auto-Review flags a call",
|
|
672
|
+
)
|
|
673
|
+
_LABEL_APP_DEV_MODE = msg("settings.app.dev_mode.label", fallback="Developer mode")
|
|
674
|
+
_LABEL_MUTATIONS_PARALLEL_IMPLICIT_TOOLS = msg(
|
|
675
|
+
"settings.mutations.parallel_implicit_tools.label", fallback="Parallel implicit tools"
|
|
676
|
+
)
|
|
677
|
+
_LABEL_MUTATIONS_COORDINATION_ENABLED = msg(
|
|
678
|
+
"settings.mutations.coordination.enabled.label", fallback="Mutation coordination"
|
|
679
|
+
)
|
|
680
|
+
_LABEL_MUTATIONS_SNAPSHOT_MAX_FILE_MB = msg(
|
|
681
|
+
"settings.mutations.snapshot.max_file_mb.label", fallback="Snapshot max file size (MB)"
|
|
682
|
+
)
|
|
683
|
+
_LABEL_MUTATIONS_SNAPSHOT_SKIP_BINARY = msg(
|
|
684
|
+
"settings.mutations.snapshot.skip_binary.label", fallback="Skip binary files in snapshots"
|
|
685
|
+
)
|
|
686
|
+
_LABEL_WORKSPACE_CHANGE_NOTICE_ENABLED = msg(
|
|
687
|
+
"settings.workspace.change_notice.enabled.label", fallback="Workspace change notices"
|
|
688
|
+
)
|
|
689
|
+
_LABEL_WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES = msg(
|
|
690
|
+
"settings.workspace.change_notice.max_entries.label", fallback="Workspace change notice entries"
|
|
691
|
+
)
|
|
692
|
+
_LABEL_ROLLBACK_SNAPSHOTS_KEEP = msg("settings.rollback.snapshots_keep.label", fallback="Rollback snapshots to keep")
|
|
693
|
+
_LABEL_OTEL_ENABLED = msg("settings.otel.enabled.label", fallback="OpenTelemetry export")
|
|
694
|
+
_LABEL_OTEL_ENDPOINT = msg("settings.otel.endpoint.label", fallback="Telemetry endpoint")
|
|
695
|
+
_LABEL_OTEL_SENSITIVE_DATA = msg("settings.otel.sensitive_data.label", fallback="Include sensitive data in telemetry")
|
|
696
|
+
_LABEL_TOOLS_ASK_USER_TIMEOUT_SECONDS = msg(
|
|
697
|
+
"settings.tools.ask_user.timeout_seconds.label", fallback="Ask-user timeout (seconds)"
|
|
698
|
+
)
|
|
699
|
+
_LABEL_TOOLS_ASK_USER_INLINE = msg("settings.tools.ask_user.inline.label", fallback="Show questions in the chat")
|
|
700
|
+
_LABEL_STORAGE_SESSION_ROOT_DIR = msg("settings.storage.session_root_dir.label", fallback="Session storage root")
|
|
701
|
+
_LABEL_HISTORY_PROMPT_ENABLED = msg("settings.history.prompt.enabled.label", fallback="Save prompt history")
|
|
702
|
+
_LABEL_UI_CHAT_FILE_SNAPSHOT_INLINE_CHARS = msg(
|
|
703
|
+
"settings.ui.chat.file_snapshot_inline_chars.label", fallback="Inline file snapshot limit (chars)"
|
|
704
|
+
)
|
|
705
|
+
_LABEL_MODEL_ROLE_BUDDY_MODEL_ID = msg("settings.model.role.buddy_model_id.label", fallback="Buddy model")
|
|
706
|
+
_LABEL_LOG_RAW_HTTP_CAPTURE = msg("settings.log.raw_http_capture.label", fallback="Capture raw HTTP traffic")
|
|
707
|
+
_LABEL_MUTATIONS_TRACE_MODE = msg("settings.mutations.trace.mode.label", fallback="Mutation trace mode")
|
|
708
|
+
_LABEL_MUTATIONS_TRACE_FSATRACE_PATH = msg("settings.mutations.trace.fsatrace_path.label", fallback="fsatrace path")
|
|
709
|
+
_LABEL_PROJECT_CONFIG_ENABLED = msg("settings.project.config_enabled.label", fallback="Load project settings")
|
|
710
|
+
_LABEL_PROJECT_HOOKS_ENABLED = msg("settings.project.hooks_enabled.label", fallback="Load project hooks")
|
|
711
|
+
_LABEL_PROJECT_SKILLS_ENABLED = msg("settings.project.skills_enabled.label", fallback="Load project skills")
|
|
712
|
+
_LABEL_NOTIFICATIONS_ENABLED = msg("settings.notifications.enabled.label", fallback="Enable notifications")
|
|
713
|
+
_LABEL_NOTIFICATIONS_DELIVERY_DESKTOP = msg("settings.notifications.delivery.desktop.label", fallback="Desktop popup")
|
|
714
|
+
_LABEL_NOTIFICATIONS_DELIVERY_SOUND = msg("settings.notifications.delivery.sound.label", fallback="Sound")
|
|
715
|
+
_LABEL_NOTIFICATIONS_SUPPRESS_WHEN_FOCUSED = msg(
|
|
716
|
+
"settings.notifications.suppress_when_focused.label", fallback="Suppress while focused"
|
|
717
|
+
)
|
|
718
|
+
_LABEL_NOTIFICATIONS_EVENTS_APPROVAL_REQUIRED = msg(
|
|
719
|
+
"settings.notifications.events.approval_required.label", fallback="Approval required"
|
|
720
|
+
)
|
|
721
|
+
_LABEL_NOTIFICATIONS_EVENTS_ASK_USER = msg("settings.notifications.events.ask_user.label", fallback="Question asked")
|
|
722
|
+
_LABEL_NOTIFICATIONS_EVENTS_TURN_COMPLETE = msg(
|
|
723
|
+
"settings.notifications.events.turn_complete.label", fallback="Turn complete"
|
|
724
|
+
)
|
|
725
|
+
_LABEL_NOTIFICATIONS_EVENTS_TURN_ERROR = msg("settings.notifications.events.turn_error.label", fallback="Turn error")
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
_WEB_LABEL_WEB_SEARCH_MODE = msg("settings.tools.web_search.mode.label", fallback="Web search mode")
|
|
729
|
+
_WEB_LABEL_WEB_SEARCH_NUM_RESULTS = msg(
|
|
730
|
+
"settings.tools.web_search.num_results.label", fallback="Results per web search"
|
|
731
|
+
)
|
|
732
|
+
_WEB_LABEL_WEB_SEARCH_TIMEOUT_SECONDS = msg(
|
|
733
|
+
"settings.tools.web_search.timeout_seconds.label", fallback="Web search timeout (seconds)"
|
|
734
|
+
)
|
|
735
|
+
_WEB_LABEL_WEB_FETCH_MODE = msg("settings.tools.web_fetch.mode.label", fallback="Web fetch mode")
|
|
736
|
+
_WEB_LABEL_WEB_FETCH_MAX_TOKENS = msg("settings.tools.web_fetch.max_tokens.label", fallback="Web fetch token budget")
|
|
737
|
+
_WEB_LABEL_WEB_FETCH_TIMEOUT_SECONDS = msg(
|
|
738
|
+
"settings.tools.web_fetch.timeout_seconds.label", fallback="Web fetch timeout (seconds)"
|
|
739
|
+
)
|
|
740
|
+
_WEB_LABEL_WEB_EGRESS_PROXY_URL = msg("settings.tools.web_egress.proxy_url.label", fallback="Web proxy URL")
|
|
741
|
+
_WEB_LABEL_WEB_EGRESS_PROXY_DNS = msg("settings.tools.web_egress.proxy_dns.label", fallback="Web proxy DNS")
|
|
742
|
+
_WEB_LABEL_WEB_SEARCH_CUSTOM_ENDPOINTS = msg(
|
|
743
|
+
"settings.tools.web_search.custom_endpoints.label", fallback="Custom search endpoints"
|
|
744
|
+
)
|
|
745
|
+
_WEB_LABEL_WEB_SEARCH_PRIVATE_ORIGINS = msg(
|
|
746
|
+
"settings.tools.web_search.private_origins.label", fallback="Web search private-network origins"
|
|
747
|
+
)
|
|
748
|
+
_WEB_LABEL_WEB_SEARCH_HTTP_ORIGINS = msg(
|
|
749
|
+
"settings.tools.web_search.http_origins.label", fallback="Web search plain-HTTP origins"
|
|
750
|
+
)
|
|
751
|
+
_WEB_LABEL_WEB_FETCH_PRIVATE_ORIGINS = msg(
|
|
752
|
+
"settings.tools.web_fetch.private_origins.label", fallback="Web fetch private-network origins"
|
|
753
|
+
)
|
|
754
|
+
_WEB_LABEL_WEB_FETCH_HTTP_ORIGINS = msg(
|
|
755
|
+
"settings.tools.web_fetch.http_origins.label", fallback="Web fetch plain-HTTP origins"
|
|
756
|
+
)
|
|
757
|
+
_WEB_LABEL_WEB_FETCH_ALLOWED_ORIGINS = msg(
|
|
758
|
+
"settings.tools.web_fetch.allowed_origins.label", fallback="Web fetch allowed origins"
|
|
759
|
+
)
|
|
760
|
+
_WEB_LABEL_WEB_FETCH_DENIED_ORIGINS = msg(
|
|
761
|
+
"settings.tools.web_fetch.denied_origins.label", fallback="Web fetch denied origins"
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
@dataclass(frozen=True)
|
|
766
|
+
class Settings:
|
|
767
|
+
"""Application settings.
|
|
768
|
+
|
|
769
|
+
Field defaults are plain literals: every source (built-in default, user
|
|
770
|
+
YAML, project layer, dotenv, real process env, CLI, session pin) is merged
|
|
771
|
+
by :func:`chrys.foundation.config.settings_store.load_settings`, which is
|
|
772
|
+
the only thing that reads the environment. A bare ``Settings()`` is
|
|
773
|
+
therefore a pure value with no hidden process dependency.
|
|
774
|
+
|
|
775
|
+
Each field carries its own :class:`SettingSpec` in ``field(metadata=...)``:
|
|
776
|
+
the dotted key, the coercer both YAML and env values go through, when a
|
|
777
|
+
change takes effect, and whether a repository may set it.
|
|
778
|
+
|
|
779
|
+
**Frozen on purpose.** A value only means something alongside the layer it
|
|
780
|
+
came from and whether that layer was sealed, and those three live in
|
|
781
|
+
:class:`~chrys.foundation.config.settings_store.LoadedSettings`. Writing a
|
|
782
|
+
field in place changes one of the three and silently leaves the other two
|
|
783
|
+
describing the value it replaced — which is how a key sealed at its
|
|
784
|
+
built-in default keeps its seal while holding a value nobody defaulted to.
|
|
785
|
+
Go through ``LoadedSettings.overlay()`` and install the result; use
|
|
786
|
+
``dataclasses.replace`` only where no provenance exists to keep (tests
|
|
787
|
+
building a fixture, a caller composing a fresh value).
|
|
788
|
+
"""
|
|
789
|
+
|
|
790
|
+
# ── Active model profile pointers ─────────────────────────────
|
|
791
|
+
# Active model profile selector for the main agent (id or unique name).
|
|
792
|
+
# Looked up against ``ModelProfileRegistry`` by ``resolve_active_profile``.
|
|
793
|
+
model_profile: str = field(
|
|
794
|
+
default="",
|
|
795
|
+
metadata=spec(
|
|
796
|
+
key="model.profile.active",
|
|
797
|
+
label=_LABEL_MODEL_PROFILE_ACTIVE,
|
|
798
|
+
env="CHRYS_MODEL_PROFILE",
|
|
799
|
+
coerce=text_coercer(),
|
|
800
|
+
apply=Apply.RELOAD,
|
|
801
|
+
group="model",
|
|
802
|
+
kind=Kind.TEXT,
|
|
803
|
+
),
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
# Explicit per-session model profile selector override (id or unique name).
|
|
807
|
+
# This is not loaded from env; frontends set it when the user provides an
|
|
808
|
+
# out-of-band model selection that must win over agent-level
|
|
809
|
+
# ``model.profile_id`` bindings.
|
|
810
|
+
model_profile_override: str = field(
|
|
811
|
+
default="",
|
|
812
|
+
metadata=spec(
|
|
813
|
+
key="model.profile.override",
|
|
814
|
+
coerce=text_coercer(),
|
|
815
|
+
apply=Apply.RELOAD,
|
|
816
|
+
group="model",
|
|
817
|
+
kind=Kind.TEXT,
|
|
818
|
+
persist=False,
|
|
819
|
+
),
|
|
820
|
+
)
|
|
821
|
+
model_profile_override_sub_agents: bool = field(
|
|
822
|
+
default=False,
|
|
823
|
+
metadata=spec(
|
|
824
|
+
key="model.profile.override_sub_agents",
|
|
825
|
+
coerce=bool_coercer(),
|
|
826
|
+
apply=Apply.RELOAD,
|
|
827
|
+
group="model",
|
|
828
|
+
kind=Kind.BOOL,
|
|
829
|
+
persist=False,
|
|
830
|
+
),
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
# Agent profile used by the TUI for fresh sessions. Invalid/missing
|
|
834
|
+
# profile names are resolved by the TUI after the agent registry is loaded.
|
|
835
|
+
default_agent: str = field(
|
|
836
|
+
default=DEFAULT_AGENT_PROFILE,
|
|
837
|
+
metadata=spec(
|
|
838
|
+
key="agent.default_profile",
|
|
839
|
+
label=_LABEL_AGENT_DEFAULT_PROFILE,
|
|
840
|
+
env="CHRYS_DEFAULT_AGENT",
|
|
841
|
+
coerce=text_coercer(),
|
|
842
|
+
apply=Apply.RELOAD,
|
|
843
|
+
group="agent",
|
|
844
|
+
kind=Kind.TEXT,
|
|
845
|
+
# A profile carries its own approval policy, which ``builder.py``
|
|
846
|
+
# adopts wholesale — a repository must not get to pick it.
|
|
847
|
+
project_merge=ProjectMerge.DENY,
|
|
848
|
+
risk=Risk.CAUTION,
|
|
849
|
+
),
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
# Approval judge model profile ID. Empty = use the main agent's
|
|
853
|
+
# active profile. Looked up by ``resolve_judge_profile``.
|
|
854
|
+
approval_judge_model_profile: str = field(
|
|
855
|
+
default="",
|
|
856
|
+
metadata=spec(
|
|
857
|
+
key="model.role.approval_judge",
|
|
858
|
+
label=_LABEL_MODEL_ROLE_APPROVAL_JUDGE,
|
|
859
|
+
env="CHRYS_MODEL_PROFILE_APPROVAL_JUDGE",
|
|
860
|
+
coerce=text_coercer(),
|
|
861
|
+
apply=Apply.RELOAD,
|
|
862
|
+
group="model",
|
|
863
|
+
kind=Kind.TEXT,
|
|
864
|
+
),
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
# ── Session titles ────────────────────────────────────────────
|
|
868
|
+
# Session-title summarizer model profile selector (id or unique name).
|
|
869
|
+
# Empty = use the main agent's active profile. Looked up by
|
|
870
|
+
# ``resolve_session_title_profile``.
|
|
871
|
+
session_title_model_profile: str = field(
|
|
872
|
+
default="",
|
|
873
|
+
metadata=spec(
|
|
874
|
+
key="model.role.session_title",
|
|
875
|
+
label=_LABEL_MODEL_ROLE_SESSION_TITLE,
|
|
876
|
+
env="CHRYS_MODEL_PROFILE_SESSION_TITLE",
|
|
877
|
+
coerce=text_coercer(),
|
|
878
|
+
apply=Apply.RELOAD,
|
|
879
|
+
group="model",
|
|
880
|
+
kind=Kind.TEXT,
|
|
881
|
+
),
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
# Whether each successful turn schedules an async LLM call that
|
|
885
|
+
# summarizes the conversation into a short session title (skipped
|
|
886
|
+
# forever once the user sets a custom title). Set
|
|
887
|
+
# ``CHRYS_SESSION_TITLE_AUTO=0`` to disable the extra LLM traffic.
|
|
888
|
+
session_title_auto: bool = field(
|
|
889
|
+
default=True,
|
|
890
|
+
metadata=spec(
|
|
891
|
+
key="session.title.auto",
|
|
892
|
+
label=_LABEL_SESSION_TITLE_AUTO,
|
|
893
|
+
env="CHRYS_SESSION_TITLE_AUTO",
|
|
894
|
+
coerce=bool_coercer(),
|
|
895
|
+
apply=Apply.RELOAD,
|
|
896
|
+
group="session",
|
|
897
|
+
kind=Kind.BOOL,
|
|
898
|
+
# A repository may switch the extra LLM traffic off, never back on.
|
|
899
|
+
project_merge=ProjectMerge.DISABLE_ONLY,
|
|
900
|
+
),
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
# ── Compaction / context-display thresholds ───────────────────
|
|
904
|
+
# Threshold (fraction of context window) at which the TUI starts
|
|
905
|
+
# warning the user about high context usage. App-level because it
|
|
906
|
+
# tunes UI behaviour, not the LLM call itself.
|
|
907
|
+
warn_threshold_pct: float = field(
|
|
908
|
+
default=0.50,
|
|
909
|
+
metadata=spec(
|
|
910
|
+
key="context.warn_threshold_pct",
|
|
911
|
+
label=_LABEL_CONTEXT_WARN_THRESHOLD_PCT,
|
|
912
|
+
coerce=float_coercer(minimum=0.0, maximum=1.0),
|
|
913
|
+
apply=Apply.RELOAD,
|
|
914
|
+
group="context",
|
|
915
|
+
kind=Kind.FLOAT,
|
|
916
|
+
project_merge=ProjectMerge.FREE,
|
|
917
|
+
),
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
# Comma-separated rather than a list so Settings remains an all-scalar
|
|
921
|
+
# value object and project-layer FREE replacement has unambiguous
|
|
922
|
+
# override semantics.
|
|
923
|
+
trajectory_verify_commands: str = field(
|
|
924
|
+
default=DEFAULT_TRAJECTORY_VERIFY_COMMANDS,
|
|
925
|
+
metadata=spec(
|
|
926
|
+
key="trajectory.verify_commands",
|
|
927
|
+
label=_LABEL_TRAJECTORY_VERIFY_COMMANDS,
|
|
928
|
+
coerce=text_coercer(),
|
|
929
|
+
apply=Apply.LIVE,
|
|
930
|
+
group="trajectory",
|
|
931
|
+
kind=Kind.TEXT,
|
|
932
|
+
project_merge=ProjectMerge.FREE,
|
|
933
|
+
),
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
# ── Application-layer transient retries ──────────────────────
|
|
937
|
+
# The environment-derived override stays separate from the frontend
|
|
938
|
+
# policy default so a settings reload can re-read the former while
|
|
939
|
+
# retaining the launch mode's default.
|
|
940
|
+
max_transient_retries: int | None = field(
|
|
941
|
+
default=None,
|
|
942
|
+
metadata=spec(
|
|
943
|
+
key="llm.retry.max_transient",
|
|
944
|
+
label=_LABEL_LLM_RETRY_MAX_TRANSIENT,
|
|
945
|
+
env="CHRYS_MAX_TRANSIENT_RETRIES",
|
|
946
|
+
coerce=int_coercer(reject_negative=True, maximum=MAX_TRANSIENT_RETRIES_LIMIT),
|
|
947
|
+
apply=Apply.RELOAD,
|
|
948
|
+
group="llm",
|
|
949
|
+
kind=Kind.OPTIONAL_INT,
|
|
950
|
+
# Raising it multiplies cost and wait against the backoff (up to
|
|
951
|
+
# 600 s a retry), so a repository may only lower it.
|
|
952
|
+
project_merge=ProjectMerge.TIGHTEN_ONLY,
|
|
953
|
+
semantic_value=_semantic_max_transient_retries,
|
|
954
|
+
),
|
|
955
|
+
)
|
|
956
|
+
frontend_default_max_transient_retries: int = field(
|
|
957
|
+
default=DEFAULT_MAX_TRANSIENT_RETRIES,
|
|
958
|
+
metadata=spec(
|
|
959
|
+
key="llm.retry.frontend_default",
|
|
960
|
+
coerce=int_coercer(minimum=0),
|
|
961
|
+
apply=Apply.RESTART,
|
|
962
|
+
group="llm",
|
|
963
|
+
kind=Kind.INT,
|
|
964
|
+
persist=False,
|
|
965
|
+
),
|
|
966
|
+
)
|
|
967
|
+
|
|
968
|
+
# Static sanity bound for every result on the local tool-invocation path.
|
|
969
|
+
# Zero disables the backstop; producer-specific caps remain independent.
|
|
970
|
+
tool_result_ceiling_tokens: int = field(
|
|
971
|
+
default=DEFAULT_TOOL_RESULT_CEILING_TOKENS,
|
|
972
|
+
metadata=spec(
|
|
973
|
+
key="tools.result.ceiling_tokens",
|
|
974
|
+
label=_LABEL_TOOLS_RESULT_CEILING_TOKENS,
|
|
975
|
+
env="CHRYS_TOOL_RESULT_CEILING_TOKENS",
|
|
976
|
+
coerce=int_coercer(zero=0, reject_negative=True, minimum=TOOL_RESULT_CEILING_FLOOR),
|
|
977
|
+
apply=Apply.RELOAD,
|
|
978
|
+
group="tools",
|
|
979
|
+
kind=Kind.INT,
|
|
980
|
+
# ``0`` means "no ceiling", so it is the loosest value, not the
|
|
981
|
+
# strictest — see the comparator. That also makes falling through
|
|
982
|
+
# to a lower layer a posture reversal rather than a preference
|
|
983
|
+
# question: a rejected value must not uncap the backstop.
|
|
984
|
+
invalid_policy=InvalidPolicy.SAFE_DEFAULT,
|
|
985
|
+
project_merge=ProjectMerge.TIGHTEN_ONLY,
|
|
986
|
+
semantic_value=_semantic_tool_result_ceiling,
|
|
987
|
+
),
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
search_respect_gitignore: bool = field(
|
|
991
|
+
default=True,
|
|
992
|
+
metadata=spec(
|
|
993
|
+
key="tools.search.respect_gitignore",
|
|
994
|
+
label=_LABEL_SEARCH_RESPECT_GITIGNORE,
|
|
995
|
+
coerce=bool_coercer(),
|
|
996
|
+
apply=Apply.RELOAD,
|
|
997
|
+
group="tools",
|
|
998
|
+
kind=Kind.BOOL,
|
|
999
|
+
),
|
|
1000
|
+
)
|
|
1001
|
+
|
|
1002
|
+
# ── TUI theme ─────────────────────────────────────────────────
|
|
1003
|
+
# Persisted theme name. Validated against registered themes at app
|
|
1004
|
+
# startup; invalid/unknown values fall back to :data:`DEFAULT_THEME`.
|
|
1005
|
+
theme: str = field(
|
|
1006
|
+
default=DEFAULT_THEME,
|
|
1007
|
+
metadata=spec(
|
|
1008
|
+
key="ui.theme",
|
|
1009
|
+
label=_LABEL_UI_THEME,
|
|
1010
|
+
env="CHRYS_THEME",
|
|
1011
|
+
# Themes are registered in the app layer, which foundation cannot
|
|
1012
|
+
# import, so validation stays where it is today: at app startup.
|
|
1013
|
+
coerce=_theme_coercer(),
|
|
1014
|
+
apply=Apply.LIVE,
|
|
1015
|
+
group="ui",
|
|
1016
|
+
kind=Kind.ENUM,
|
|
1017
|
+
choices=ChoiceProvider.THEMES,
|
|
1018
|
+
),
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
# ── Display locale ───────────────────────────────────────────────
|
|
1022
|
+
# Persisted locale selector. Resolution and fallback are owned by
|
|
1023
|
+
# ``Localizer``; Settings only strips surrounding whitespace.
|
|
1024
|
+
locale: str = field(
|
|
1025
|
+
default=DEFAULT_LOCALE,
|
|
1026
|
+
metadata=spec(
|
|
1027
|
+
key="ui.locale",
|
|
1028
|
+
label=_LABEL_UI_LOCALE,
|
|
1029
|
+
env="CHRYS_LOCALE",
|
|
1030
|
+
# Deliberately not a ``choice_coercer``: ``normalize_locale``
|
|
1031
|
+
# accepts far more spellings than the picker offers (``zh_CN``,
|
|
1032
|
+
# ``en_US.UTF-8``, ``C``, …) and maps them itself. ``choices``
|
|
1033
|
+
# here describes the picker, not the accepted set.
|
|
1034
|
+
coerce=text_coercer(),
|
|
1035
|
+
apply=Apply.LIVE,
|
|
1036
|
+
group="ui",
|
|
1037
|
+
kind=Kind.ENUM,
|
|
1038
|
+
choices=(DEFAULT_LOCALE, *SUPPORTED_LOCALES),
|
|
1039
|
+
),
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
# ── Message editor ────────────────────────────────────────────
|
|
1043
|
+
# Persisted keymap selected in the message editor.
|
|
1044
|
+
editor_keymap: str = field(
|
|
1045
|
+
default=DEFAULT_EDITOR_KEYMAP,
|
|
1046
|
+
metadata=spec(
|
|
1047
|
+
key="ui.editor.keymap",
|
|
1048
|
+
label=_LABEL_UI_EDITOR_KEYMAP,
|
|
1049
|
+
env="CHRYS_EDITOR_KEYMAP",
|
|
1050
|
+
coerce=choice_coercer(choices=_VALID_EDITOR_KEYMAPS),
|
|
1051
|
+
apply=Apply.LIVE,
|
|
1052
|
+
group="ui",
|
|
1053
|
+
kind=Kind.ENUM,
|
|
1054
|
+
choices=_VALID_EDITOR_KEYMAPS,
|
|
1055
|
+
),
|
|
1056
|
+
)
|
|
1057
|
+
|
|
1058
|
+
# ── Workspace MRU ─────────────────────────────────────────────
|
|
1059
|
+
# Maximum entries kept in the persisted workspace MRU index that feeds
|
|
1060
|
+
# quick-access surfaces such as the Change Directory picker's recent
|
|
1061
|
+
# paths. ``0`` disables the MRU entirely (no index file is created,
|
|
1062
|
+
# loaded, or updated). Workspace-level UX config, not bound to any one
|
|
1063
|
+
# consumer; read once at TUI startup (no live reload).
|
|
1064
|
+
workspace_mru_max_entries: int = field(
|
|
1065
|
+
default=DEFAULT_WORKSPACE_MRU_MAX_ENTRIES,
|
|
1066
|
+
metadata=spec(
|
|
1067
|
+
key="workspace.mru_max_entries",
|
|
1068
|
+
label=_LABEL_WORKSPACE_MRU_MAX_ENTRIES,
|
|
1069
|
+
env="CHRYS_WORKSPACE_MRU_MAX_ENTRIES",
|
|
1070
|
+
coerce=int_coercer(non_positive=0, maximum=WORKSPACE_MRU_MAX_ENTRIES_LIMIT),
|
|
1071
|
+
apply=Apply.RESTART,
|
|
1072
|
+
group="workspace",
|
|
1073
|
+
kind=Kind.INT,
|
|
1074
|
+
),
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
# ── Approval ──────────────────────────────────────────────────
|
|
1078
|
+
# Persisted default approval mode applied at engine startup. One of
|
|
1079
|
+
# ``manual`` / ``auto`` / ``bypass``. Invalid or missing values fall
|
|
1080
|
+
# back to :data:`DEFAULT_APPROVAL_MODE`. :func:`persist_approval_mode`
|
|
1081
|
+
# maps ``bypass`` → ``auto`` on write so Chrys never boots directly
|
|
1082
|
+
# into BYPASS mode.
|
|
1083
|
+
default_approval_mode: str = field(
|
|
1084
|
+
default=DEFAULT_APPROVAL_MODE,
|
|
1085
|
+
metadata=spec(
|
|
1086
|
+
key="approval.default_mode",
|
|
1087
|
+
label=_LABEL_APPROVAL_DEFAULT_MODE,
|
|
1088
|
+
env="CHRYS_DEFAULT_APPROVAL_MODE",
|
|
1089
|
+
coerce=choice_coercer(choices=_VALID_APPROVAL_MODES),
|
|
1090
|
+
apply=Apply.RELOAD,
|
|
1091
|
+
group="approval",
|
|
1092
|
+
kind=Kind.ENUM,
|
|
1093
|
+
choices=_VALID_APPROVAL_MODES,
|
|
1094
|
+
risk=Risk.DANGEROUS,
|
|
1095
|
+
# Falling through could land on a persisted ``bypass``.
|
|
1096
|
+
invalid_policy=InvalidPolicy.SAFE_DEFAULT,
|
|
1097
|
+
),
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
# TUI presentation only: in AUTO mode, keep a call the judge is still
|
|
1101
|
+
# reviewing out of sight and open its dialog only once it is flagged.
|
|
1102
|
+
# The backend sends the same events either way; ACP and headless ignore it.
|
|
1103
|
+
approval_defer_while_judging: bool = field(
|
|
1104
|
+
default=True,
|
|
1105
|
+
metadata=spec(
|
|
1106
|
+
key="ui.approval.defer_while_judging",
|
|
1107
|
+
label=_LABEL_UI_APPROVAL_DEFER_WHILE_JUDGING,
|
|
1108
|
+
coerce=bool_coercer(),
|
|
1109
|
+
apply=Apply.LIVE,
|
|
1110
|
+
group="ui",
|
|
1111
|
+
kind=Kind.BOOL,
|
|
1112
|
+
),
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
# ── Developer mode ─────────────────────────────────────────────
|
|
1116
|
+
# Enables developer-only behaviors (debugging hooks, review surfaces, etc).
|
|
1117
|
+
dev_mode: bool = field(
|
|
1118
|
+
default=False,
|
|
1119
|
+
metadata=spec(
|
|
1120
|
+
key="app.dev_mode",
|
|
1121
|
+
label=_LABEL_APP_DEV_MODE,
|
|
1122
|
+
env="CHRYS_DEV_MODE",
|
|
1123
|
+
coerce=bool_coercer(),
|
|
1124
|
+
# Its one consumer takes it from ``Settings`` during the rebuild a
|
|
1125
|
+
# reload performs, so a reload genuinely applies it — RESTART would
|
|
1126
|
+
# promise a stability this process never had.
|
|
1127
|
+
apply=Apply.RELOAD,
|
|
1128
|
+
group="app",
|
|
1129
|
+
kind=Kind.BOOL,
|
|
1130
|
+
),
|
|
1131
|
+
)
|
|
1132
|
+
|
|
1133
|
+
# ── Mutation tracking ──────────────────────────────────────────
|
|
1134
|
+
# Whether shell-like tools that can mutate files implicitly may run
|
|
1135
|
+
# concurrently inside one session. True preserves the historical
|
|
1136
|
+
# parallel behaviour. Set ``CHRYS_PARALLEL_IMPLICIT_TOOLS=0`` to
|
|
1137
|
+
# serialize shell/run_skill_script mutation-observation windows for
|
|
1138
|
+
# stronger in-session /diff attribution.
|
|
1139
|
+
parallel_implicit_tools: bool = field(
|
|
1140
|
+
default=True,
|
|
1141
|
+
metadata=spec(
|
|
1142
|
+
key="mutations.parallel_implicit_tools",
|
|
1143
|
+
label=_LABEL_MUTATIONS_PARALLEL_IMPLICIT_TOOLS,
|
|
1144
|
+
env="CHRYS_PARALLEL_IMPLICIT_TOOLS",
|
|
1145
|
+
coerce=bool_coercer(),
|
|
1146
|
+
apply=Apply.RELOAD,
|
|
1147
|
+
group="mutations",
|
|
1148
|
+
kind=Kind.BOOL,
|
|
1149
|
+
# Setting it false buys stronger /diff attribution; a repository
|
|
1150
|
+
# must not undo a user who chose that.
|
|
1151
|
+
project_merge=ProjectMerge.DISABLE_ONLY,
|
|
1152
|
+
),
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
# Cross-session mutation coordination: sessions sharing a working
|
|
1156
|
+
# tree publish proven writes to a registry and reclassify their
|
|
1157
|
+
# window inferences against peers' claims. Escape hatch only — the feature is inert (one
|
|
1158
|
+
# listdir) when no peer sessions exist. Set
|
|
1159
|
+
# ``CHRYS_MUTATION_COORDINATION=0`` to disable.
|
|
1160
|
+
mutation_coordination: bool = field(
|
|
1161
|
+
default=True,
|
|
1162
|
+
metadata=spec(
|
|
1163
|
+
key="mutations.coordination.enabled",
|
|
1164
|
+
label=_LABEL_MUTATIONS_COORDINATION_ENABLED,
|
|
1165
|
+
env="CHRYS_MUTATION_COORDINATION",
|
|
1166
|
+
coerce=bool_coercer(),
|
|
1167
|
+
apply=Apply.RELOAD,
|
|
1168
|
+
group="mutations",
|
|
1169
|
+
kind=Kind.BOOL,
|
|
1170
|
+
# Turning it off loses cross-session attribution.
|
|
1171
|
+
project_merge=ProjectMerge.ENABLE_ONLY,
|
|
1172
|
+
),
|
|
1173
|
+
)
|
|
1174
|
+
|
|
1175
|
+
# Per-file cap (MiB) on mutation backup blobs written under
|
|
1176
|
+
# ``{session_dir}/mutations/``. Files larger than this are still
|
|
1177
|
+
# *recorded* as mutated, but their content is not backed up — no
|
|
1178
|
+
# diff rendering and no rollback restore for them. ``0`` (or any
|
|
1179
|
+
# non-positive env value) disables the size cap. Consumed via
|
|
1180
|
+
# ``SnapshotPolicy.from_settings``.
|
|
1181
|
+
mutation_snapshot_max_file_mb: int = field(
|
|
1182
|
+
default=DEFAULT_MUTATION_SNAPSHOT_MAX_FILE_MB,
|
|
1183
|
+
metadata=spec(
|
|
1184
|
+
key="mutations.snapshot.max_file_mb",
|
|
1185
|
+
label=_LABEL_MUTATIONS_SNAPSHOT_MAX_FILE_MB,
|
|
1186
|
+
env="CHRYS_MUTATION_SNAPSHOT_MAX_FILE_MB",
|
|
1187
|
+
coerce=int_coercer(non_positive=0),
|
|
1188
|
+
# ``SnapshotPolicy`` is captured when ``SnapshotStore`` is first
|
|
1189
|
+
# built and never rebuilt on reload, so RELOAD would be a promise
|
|
1190
|
+
# nothing keeps.
|
|
1191
|
+
apply=Apply.RESTART,
|
|
1192
|
+
group="mutations",
|
|
1193
|
+
kind=Kind.INT,
|
|
1194
|
+
),
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1197
|
+
# Whether binary (non-text) file contents are excluded from mutation
|
|
1198
|
+
# backups, regardless of size. Detection uses
|
|
1199
|
+
# ``EncodingDetector.looks_binary`` — the same heuristic diff
|
|
1200
|
+
# surfaces use for rendering. Set
|
|
1201
|
+
# ``CHRYS_MUTATION_SNAPSHOT_SKIP_BINARY=0`` to back up binary files.
|
|
1202
|
+
mutation_snapshot_skip_binary: bool = field(
|
|
1203
|
+
default=True,
|
|
1204
|
+
metadata=spec(
|
|
1205
|
+
key="mutations.snapshot.skip_binary",
|
|
1206
|
+
label=_LABEL_MUTATIONS_SNAPSHOT_SKIP_BINARY,
|
|
1207
|
+
env="CHRYS_MUTATION_SNAPSHOT_SKIP_BINARY",
|
|
1208
|
+
coerce=bool_coercer(),
|
|
1209
|
+
apply=Apply.RESTART, # Same captured policy as max_file_mb.
|
|
1210
|
+
group="mutations",
|
|
1211
|
+
kind=Kind.BOOL,
|
|
1212
|
+
),
|
|
1213
|
+
)
|
|
1214
|
+
|
|
1215
|
+
# One-shot reminders describing agent and external workspace changes at
|
|
1216
|
+
# real turn boundaries.
|
|
1217
|
+
workspace_change_notice: bool = field(
|
|
1218
|
+
default=True,
|
|
1219
|
+
metadata=spec(
|
|
1220
|
+
key="workspace.change_notice.enabled",
|
|
1221
|
+
label=_LABEL_WORKSPACE_CHANGE_NOTICE_ENABLED,
|
|
1222
|
+
env="CHRYS_WORKSPACE_CHANGE_NOTICE",
|
|
1223
|
+
coerce=bool_coercer(),
|
|
1224
|
+
apply=Apply.RELOAD,
|
|
1225
|
+
group="workspace",
|
|
1226
|
+
kind=Kind.BOOL,
|
|
1227
|
+
# A repository must not hide file changes from the user.
|
|
1228
|
+
project_merge=ProjectMerge.ENABLE_ONLY,
|
|
1229
|
+
),
|
|
1230
|
+
)
|
|
1231
|
+
workspace_change_notice_max_entries: int = field(
|
|
1232
|
+
default=DEFAULT_WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES,
|
|
1233
|
+
metadata=spec(
|
|
1234
|
+
key="workspace.change_notice.max_entries",
|
|
1235
|
+
label=_LABEL_WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES,
|
|
1236
|
+
env="CHRYS_WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES",
|
|
1237
|
+
coerce=int_coercer(minimum=1, maximum=WORKSPACE_CHANGE_NOTICE_MAX_ENTRIES_LIMIT),
|
|
1238
|
+
apply=Apply.RELOAD,
|
|
1239
|
+
group="workspace",
|
|
1240
|
+
kind=Kind.INT,
|
|
1241
|
+
# Already clamped to 1..100, so there is no room to hide in.
|
|
1242
|
+
project_merge=ProjectMerge.FREE,
|
|
1243
|
+
),
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
# ── Rollback ──────────────────────────────────────────────────
|
|
1247
|
+
# How many per-turn ``session.json`` snapshots to retain under
|
|
1248
|
+
# ``{session_dir}/snapshots/``. Each snapshot is a full copy of
|
|
1249
|
+
# ``session.json`` taken just before a new user turn begins, enabling
|
|
1250
|
+
# ``/rollback`` to restore the session (and optionally file-system
|
|
1251
|
+
# changes) to any of the last N turn boundaries. Older snapshots
|
|
1252
|
+
# are pruned on every new turn.
|
|
1253
|
+
rollback_snapshots_keep: int = field(
|
|
1254
|
+
default=DEFAULT_ROLLBACK_SNAPSHOTS_KEEP,
|
|
1255
|
+
metadata=spec(
|
|
1256
|
+
key="rollback.snapshots_keep",
|
|
1257
|
+
label=_LABEL_ROLLBACK_SNAPSHOTS_KEEP,
|
|
1258
|
+
env="CHRYS_ROLLBACK_SNAPSHOTS_KEEP",
|
|
1259
|
+
# Behaviour change: a non-numeric value used to raise ``ValueError``
|
|
1260
|
+
# out of ``Settings()`` and take the process down at startup. It is
|
|
1261
|
+
# now rejected like any other invalid value.
|
|
1262
|
+
coerce=int_coercer(minimum=1),
|
|
1263
|
+
apply=Apply.RELOAD,
|
|
1264
|
+
group="rollback",
|
|
1265
|
+
kind=Kind.INT,
|
|
1266
|
+
),
|
|
1267
|
+
)
|
|
1268
|
+
|
|
1269
|
+
# ── Observability ─────────────────────────────────────────────
|
|
1270
|
+
# When True, :func:`chrys.foundation.observability.setup.setup_otel` builds the OTel
|
|
1271
|
+
# providers/exporters and opens TELEMETRY_GATE. Kernel chat, agent and
|
|
1272
|
+
# function spans and the MCP spans from service/mcp/owned.py share this gate.
|
|
1273
|
+
#
|
|
1274
|
+
# Exporter destination priority:
|
|
1275
|
+
# 1. If ``OTEL_EXPORTER_OTLP_ENDPOINT`` (or a signal-specific
|
|
1276
|
+
# variant) is set, or ``otel_endpoint`` below is non-blank, Chrys
|
|
1277
|
+
# builds OTLP exporters from the OTel-spec'd env vars
|
|
1278
|
+
# through _get_exporters_from_env; the setting supplies a fallback
|
|
1279
|
+
# base endpoint.
|
|
1280
|
+
# 2. Otherwise, Chrys installs a local JSONL file exporter that
|
|
1281
|
+
# writes to ``{session_dir}/otel/{traces,logs}.jsonl`` once
|
|
1282
|
+
# a session is live. Zero-config default; safe in the TUI
|
|
1283
|
+
# (no stdout contention).
|
|
1284
|
+
#
|
|
1285
|
+
# ``otel_sensitive_data`` (``CHRYS_OTEL_SENSITIVE_DATA``) feeds TELEMETRY_GATE.
|
|
1286
|
+
# When enabled, prompt, response and tool-argument content is captured;
|
|
1287
|
+
# SessionJsonlLogExporter writes message events alongside span traces.
|
|
1288
|
+
otel_enabled: bool = field(
|
|
1289
|
+
default=False,
|
|
1290
|
+
metadata=spec(
|
|
1291
|
+
key="otel.enabled",
|
|
1292
|
+
label=_LABEL_OTEL_ENABLED,
|
|
1293
|
+
env="CHRYS_OTEL",
|
|
1294
|
+
coerce=bool_coercer(),
|
|
1295
|
+
apply=Apply.RESTART, # Providers are installed once per process.
|
|
1296
|
+
group="otel",
|
|
1297
|
+
kind=Kind.BOOL,
|
|
1298
|
+
),
|
|
1299
|
+
)
|
|
1300
|
+
otel_sensitive_data: bool = field(
|
|
1301
|
+
default=False,
|
|
1302
|
+
metadata=spec(
|
|
1303
|
+
key="otel.sensitive_data",
|
|
1304
|
+
label=_LABEL_OTEL_SENSITIVE_DATA,
|
|
1305
|
+
env="CHRYS_OTEL_SENSITIVE_DATA",
|
|
1306
|
+
coerce=bool_coercer(),
|
|
1307
|
+
apply=Apply.RESTART,
|
|
1308
|
+
group="otel",
|
|
1309
|
+
kind=Kind.BOOL,
|
|
1310
|
+
# Full prompt / response / tool-arg content is exported.
|
|
1311
|
+
risk=Risk.DANGEROUS,
|
|
1312
|
+
invalid_policy=InvalidPolicy.SAFE_DEFAULT,
|
|
1313
|
+
),
|
|
1314
|
+
)
|
|
1315
|
+
# Base OTLP endpoint for the exporters when the standard
|
|
1316
|
+
# ``OTEL_EXPORTER_OTLP_ENDPOINT`` is not set; the standard variables keep
|
|
1317
|
+
# winning so an OpenTelemetry-configured environment behaves as documented.
|
|
1318
|
+
# Blank keeps the session-folder JSONL fallback.
|
|
1319
|
+
otel_endpoint: str = field(
|
|
1320
|
+
default="",
|
|
1321
|
+
metadata=spec(
|
|
1322
|
+
key="otel.endpoint",
|
|
1323
|
+
label=_LABEL_OTEL_ENDPOINT,
|
|
1324
|
+
env="CHRYS_OTEL_ENDPOINT",
|
|
1325
|
+
coerce=text_coercer(),
|
|
1326
|
+
apply=Apply.RESTART, # Read once by setup_otel with the other otel keys.
|
|
1327
|
+
group="otel",
|
|
1328
|
+
kind=Kind.TEXT,
|
|
1329
|
+
),
|
|
1330
|
+
)
|
|
1331
|
+
|
|
1332
|
+
# ── Ask-user ──────────────────────────────────────────────────
|
|
1333
|
+
# Seconds to wait for a user reply to an ``ask_user`` tool call before
|
|
1334
|
+
# the middleware gives up and reports a timeout to the model. Read from
|
|
1335
|
+
# ``CHRYS_ASK_USER_TIMEOUT_SECONDS`` (non-positive/empty → no timeout via
|
|
1336
|
+
# the loader). Per-session: each engine carries its own ``Settings``, so
|
|
1337
|
+
# frontends pick their own policy — the TUI/CLI keep the bounded default,
|
|
1338
|
+
# while ``chrys acp`` defaults to ``None`` so the ACP client owns timing.
|
|
1339
|
+
ask_user_timeout_seconds: int | None = field(
|
|
1340
|
+
default=DEFAULT_ASK_USER_TIMEOUT_SECONDS,
|
|
1341
|
+
metadata=spec(
|
|
1342
|
+
key="tools.ask_user.timeout_seconds",
|
|
1343
|
+
label=_LABEL_TOOLS_ASK_USER_TIMEOUT_SECONDS,
|
|
1344
|
+
env="CHRYS_ASK_USER_TIMEOUT_SECONDS",
|
|
1345
|
+
coerce=optional_int_coercer(),
|
|
1346
|
+
apply=Apply.RELOAD,
|
|
1347
|
+
group="tools",
|
|
1348
|
+
kind=Kind.OPTIONAL_INT,
|
|
1349
|
+
),
|
|
1350
|
+
)
|
|
1351
|
+
# Where an ``ask_user`` question first appears in the TUI: inside the
|
|
1352
|
+
# tool card in the transcript (True) or as a modal dialog (False, from
|
|
1353
|
+
# which "Answer Inline" still hands the question over to the transcript).
|
|
1354
|
+
ask_user_inline: bool = field(
|
|
1355
|
+
default=False,
|
|
1356
|
+
metadata=spec(
|
|
1357
|
+
key="tools.ask_user.inline",
|
|
1358
|
+
label=_LABEL_TOOLS_ASK_USER_INLINE,
|
|
1359
|
+
env="CHRYS_ASK_USER_INLINE",
|
|
1360
|
+
coerce=bool_coercer(),
|
|
1361
|
+
apply=Apply.RELOAD,
|
|
1362
|
+
group="tools",
|
|
1363
|
+
kind=Kind.BOOL,
|
|
1364
|
+
),
|
|
1365
|
+
)
|
|
1366
|
+
|
|
1367
|
+
# ── Storage ───────────────────────────────────────────────────
|
|
1368
|
+
# Base directory under which session storage lives; the session store
|
|
1369
|
+
# itself is ``<root>/sessions``. Empty selects the platform config dir.
|
|
1370
|
+
# Resolution and validation stay in ``resolve_session_root_dir``.
|
|
1371
|
+
session_root_dir: str = field(
|
|
1372
|
+
default="",
|
|
1373
|
+
metadata=spec(
|
|
1374
|
+
key="storage.session_root_dir",
|
|
1375
|
+
label=_LABEL_STORAGE_SESSION_ROOT_DIR,
|
|
1376
|
+
env=SESSION_ROOT_DIR_ENV_VAR,
|
|
1377
|
+
coerce=text_coercer(),
|
|
1378
|
+
apply=Apply.RESTART,
|
|
1379
|
+
group="storage",
|
|
1380
|
+
kind=Kind.PATH,
|
|
1381
|
+
# Where a user's sessions live is not a repository's business.
|
|
1382
|
+
project_merge=ProjectMerge.DENY,
|
|
1383
|
+
risk=Risk.CAUTION,
|
|
1384
|
+
),
|
|
1385
|
+
)
|
|
1386
|
+
|
|
1387
|
+
# ── Prompt history ────────────────────────────────────────────
|
|
1388
|
+
# Whether the TUI keeps a persistent prompt history. The key is
|
|
1389
|
+
# positive; the legacy variable is negative, hence the separate alias
|
|
1390
|
+
# coercer.
|
|
1391
|
+
prompt_history_enabled: bool = field(
|
|
1392
|
+
default=True,
|
|
1393
|
+
metadata=spec(
|
|
1394
|
+
key="history.prompt.enabled",
|
|
1395
|
+
label=_LABEL_HISTORY_PROMPT_ENABLED,
|
|
1396
|
+
env="CHRYS_HISTORY_DISABLE",
|
|
1397
|
+
coerce=bool_coercer(),
|
|
1398
|
+
env_coerce=_history_disable_coercer(),
|
|
1399
|
+
# The consumer is a module-level function on the TUI side with no
|
|
1400
|
+
# engine in reach, and ``ChrysApp._settings`` is never refreshed on
|
|
1401
|
+
# reload, so RELOAD would be a promise nothing keeps. This is not a
|
|
1402
|
+
# regression: today the variable can only be set before launch.
|
|
1403
|
+
apply=Apply.RESTART,
|
|
1404
|
+
group="history",
|
|
1405
|
+
kind=Kind.BOOL,
|
|
1406
|
+
),
|
|
1407
|
+
)
|
|
1408
|
+
|
|
1409
|
+
# ── Chat rendering ────────────────────────────────────────────
|
|
1410
|
+
tool_groups_expanded: bool = field(
|
|
1411
|
+
default=False,
|
|
1412
|
+
metadata=spec(
|
|
1413
|
+
key="ui.chat.tool_groups_expanded",
|
|
1414
|
+
label=_LABEL_UI_CHAT_TOOL_GROUPS_EXPANDED,
|
|
1415
|
+
coerce=bool_coercer(),
|
|
1416
|
+
apply=Apply.LIVE,
|
|
1417
|
+
group="ui",
|
|
1418
|
+
kind=Kind.BOOL,
|
|
1419
|
+
project_merge=ProjectMerge.DENY,
|
|
1420
|
+
),
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
# Maximum combined before/after snapshot bytes chat tool records keep
|
|
1424
|
+
# inline. ``0`` keeps nothing inline.
|
|
1425
|
+
chat_file_snapshot_inline_chars: int = field(
|
|
1426
|
+
default=DEFAULT_FILE_SNAPSHOT_INLINE_CHARS,
|
|
1427
|
+
metadata=spec(
|
|
1428
|
+
key="ui.chat.file_snapshot_inline_chars",
|
|
1429
|
+
label=_LABEL_UI_CHAT_FILE_SNAPSHOT_INLINE_CHARS,
|
|
1430
|
+
env="CHRYS_TUI_FILE_SNAPSHOT_INLINE_CHARS",
|
|
1431
|
+
coerce=int_coercer(non_positive=0),
|
|
1432
|
+
apply=Apply.RESTART, # Read once, from the process snapshot.
|
|
1433
|
+
group="ui",
|
|
1434
|
+
kind=Kind.INT,
|
|
1435
|
+
),
|
|
1436
|
+
)
|
|
1437
|
+
|
|
1438
|
+
# ── Buddy ─────────────────────────────────────────────────────
|
|
1439
|
+
# Model id used for what the buddy says when petted. Empty = the active
|
|
1440
|
+
# profile's model. Sits under ``model.role`` with the other per-role
|
|
1441
|
+
# selectors, but holds a *model id* rather than a profile selector —
|
|
1442
|
+
# hence the ``_model_id`` leaf, which is the only thing telling a reader
|
|
1443
|
+
# of the YAML that a profile name will not work here.
|
|
1444
|
+
buddy_model: str = field(
|
|
1445
|
+
default="",
|
|
1446
|
+
metadata=spec(
|
|
1447
|
+
key="model.role.buddy_model_id",
|
|
1448
|
+
label=_LABEL_MODEL_ROLE_BUDDY_MODEL_ID,
|
|
1449
|
+
env="CHRYS_PET_MODEL",
|
|
1450
|
+
coerce=text_coercer(),
|
|
1451
|
+
apply=Apply.RELOAD,
|
|
1452
|
+
group="model",
|
|
1453
|
+
kind=Kind.TEXT,
|
|
1454
|
+
),
|
|
1455
|
+
)
|
|
1456
|
+
|
|
1457
|
+
# ── Diagnostics ───────────────────────────────────────────────
|
|
1458
|
+
# Full LLM HTTP exchange capture, written under the session dir.
|
|
1459
|
+
raw_http_capture: bool = field(
|
|
1460
|
+
default=False,
|
|
1461
|
+
metadata=spec(
|
|
1462
|
+
key="log.raw_http_capture",
|
|
1463
|
+
label=_LABEL_LOG_RAW_HTTP_CAPTURE,
|
|
1464
|
+
env="CHRYS_DEBUG_LLM_RAW_HTTP_LOG",
|
|
1465
|
+
coerce=bool_coercer(),
|
|
1466
|
+
apply=Apply.RESTART,
|
|
1467
|
+
group="log",
|
|
1468
|
+
kind=Kind.BOOL,
|
|
1469
|
+
# Captured exchanges contain API keys and full message content.
|
|
1470
|
+
risk=Risk.DANGEROUS,
|
|
1471
|
+
invalid_policy=InvalidPolicy.SAFE_DEFAULT,
|
|
1472
|
+
),
|
|
1473
|
+
)
|
|
1474
|
+
|
|
1475
|
+
# ── Mutation tracing ──────────────────────────────────────────
|
|
1476
|
+
# ``auto`` probes for fsatrace once per process; ``off`` skips tracing.
|
|
1477
|
+
mutation_trace_mode: str = field(
|
|
1478
|
+
default=DEFAULT_MUTATION_TRACE_MODE,
|
|
1479
|
+
metadata=spec(
|
|
1480
|
+
key="mutations.trace.mode",
|
|
1481
|
+
label=_LABEL_MUTATIONS_TRACE_MODE,
|
|
1482
|
+
env="CHRYS_MUTATION_TRACE",
|
|
1483
|
+
coerce=choice_coercer(choices=MUTATION_TRACE_MODES),
|
|
1484
|
+
env_coerce=_trace_mode_env_coercer(),
|
|
1485
|
+
apply=Apply.RESTART, # The probe result is cached process-wide.
|
|
1486
|
+
group="mutations",
|
|
1487
|
+
kind=Kind.ENUM,
|
|
1488
|
+
choices=MUTATION_TRACE_MODES,
|
|
1489
|
+
),
|
|
1490
|
+
)
|
|
1491
|
+
|
|
1492
|
+
# Explicit fsatrace binary, overriding the ``PATH`` lookup.
|
|
1493
|
+
mutation_trace_fsatrace_path: str = field(
|
|
1494
|
+
default="",
|
|
1495
|
+
metadata=spec(
|
|
1496
|
+
key="mutations.trace.fsatrace_path",
|
|
1497
|
+
label=_LABEL_MUTATIONS_TRACE_FSATRACE_PATH,
|
|
1498
|
+
env="CHRYS_FSATRACE_PATH",
|
|
1499
|
+
coerce=text_coercer(),
|
|
1500
|
+
apply=Apply.RESTART,
|
|
1501
|
+
group="mutations",
|
|
1502
|
+
kind=Kind.PATH,
|
|
1503
|
+
# This value becomes a subprocess argv[0]. A cloned repository
|
|
1504
|
+
# naming its own binary here would have Chrys execute it.
|
|
1505
|
+
project_merge=ProjectMerge.DENY,
|
|
1506
|
+
risk=Risk.DANGEROUS,
|
|
1507
|
+
invalid_policy=InvalidPolicy.SAFE_DEFAULT,
|
|
1508
|
+
),
|
|
1509
|
+
)
|
|
1510
|
+
|
|
1511
|
+
# ── Project layer ─────────────────────────────────────────────
|
|
1512
|
+
# Master switch for the project trust domain: whether
|
|
1513
|
+
# ``<root>/.chrys/settings.yaml`` is consulted at all. Off by
|
|
1514
|
+
# default — cloning a repository must not hand it configuration
|
|
1515
|
+
# authority.
|
|
1516
|
+
project_config_enabled: bool = field(
|
|
1517
|
+
default=False,
|
|
1518
|
+
metadata=spec(
|
|
1519
|
+
key="project.config_enabled",
|
|
1520
|
+
label=_LABEL_PROJECT_CONFIG_ENABLED,
|
|
1521
|
+
coerce=bool_coercer(),
|
|
1522
|
+
apply=Apply.RELOAD,
|
|
1523
|
+
group="project",
|
|
1524
|
+
kind=Kind.BOOL,
|
|
1525
|
+
# Read only from DEFAULT/USER: letting a project enable the
|
|
1526
|
+
# project layer would be the layer authorising itself.
|
|
1527
|
+
project_merge=ProjectMerge.DENY,
|
|
1528
|
+
risk=Risk.CAUTION,
|
|
1529
|
+
),
|
|
1530
|
+
)
|
|
1531
|
+
|
|
1532
|
+
# Whether ``<root>/.chrys/hooks/hooks.{yaml,yml,json}`` is loaded. Off
|
|
1533
|
+
# by default: a hook is a command that runs on its own, outside tool
|
|
1534
|
+
# approval, so a cloned repository must not get to run one unasked.
|
|
1535
|
+
project_hooks_enabled: bool = field(
|
|
1536
|
+
default=False,
|
|
1537
|
+
metadata=spec(
|
|
1538
|
+
key="project.hooks_enabled",
|
|
1539
|
+
label=_LABEL_PROJECT_HOOKS_ENABLED,
|
|
1540
|
+
coerce=bool_coercer(),
|
|
1541
|
+
apply=Apply.RELOAD,
|
|
1542
|
+
group="project",
|
|
1543
|
+
kind=Kind.BOOL,
|
|
1544
|
+
# A project must not be able to switch its own hooks back on.
|
|
1545
|
+
project_merge=ProjectMerge.DENY,
|
|
1546
|
+
risk=Risk.CAUTION,
|
|
1547
|
+
),
|
|
1548
|
+
)
|
|
1549
|
+
|
|
1550
|
+
# Whether ``<root>/.agents/skills`` is loaded. Off by default for the
|
|
1551
|
+
# same reason: a skill's instructions and scripts come from whoever
|
|
1552
|
+
# wrote the repository. The agent profile's own working-folder switch
|
|
1553
|
+
# still applies on top of this one.
|
|
1554
|
+
project_skills_enabled: bool = field(
|
|
1555
|
+
default=False,
|
|
1556
|
+
metadata=spec(
|
|
1557
|
+
key="project.skills_enabled",
|
|
1558
|
+
label=_LABEL_PROJECT_SKILLS_ENABLED,
|
|
1559
|
+
coerce=bool_coercer(),
|
|
1560
|
+
apply=Apply.RELOAD,
|
|
1561
|
+
group="project",
|
|
1562
|
+
kind=Kind.BOOL,
|
|
1563
|
+
# A project must not be able to switch its own skills back on.
|
|
1564
|
+
project_merge=ProjectMerge.DENY,
|
|
1565
|
+
risk=Risk.CAUTION,
|
|
1566
|
+
),
|
|
1567
|
+
)
|
|
1568
|
+
|
|
1569
|
+
# ── Desktop notifications ─────────────────────────────────────
|
|
1570
|
+
# Flattened from the former ``notifications.yaml`` document. The TUI
|
|
1571
|
+
# consumes these through the ``NotificationSettings`` view object
|
|
1572
|
+
# (``app/tui/notifications``); delivery behaviour stays there.
|
|
1573
|
+
notifications_enabled: bool = field(
|
|
1574
|
+
default=True,
|
|
1575
|
+
metadata=spec(
|
|
1576
|
+
key="notifications.enabled",
|
|
1577
|
+
label=_LABEL_NOTIFICATIONS_ENABLED,
|
|
1578
|
+
coerce=bool_coercer(),
|
|
1579
|
+
apply=Apply.LIVE,
|
|
1580
|
+
group="notifications",
|
|
1581
|
+
kind=Kind.BOOL,
|
|
1582
|
+
),
|
|
1583
|
+
)
|
|
1584
|
+
|
|
1585
|
+
notifications_desktop: bool = field(
|
|
1586
|
+
default=True,
|
|
1587
|
+
metadata=spec(
|
|
1588
|
+
key="notifications.delivery.desktop",
|
|
1589
|
+
label=_LABEL_NOTIFICATIONS_DELIVERY_DESKTOP,
|
|
1590
|
+
coerce=bool_coercer(),
|
|
1591
|
+
apply=Apply.LIVE,
|
|
1592
|
+
group="notifications",
|
|
1593
|
+
kind=Kind.BOOL,
|
|
1594
|
+
),
|
|
1595
|
+
)
|
|
1596
|
+
|
|
1597
|
+
notifications_sound: bool = field(
|
|
1598
|
+
default=True,
|
|
1599
|
+
metadata=spec(
|
|
1600
|
+
key="notifications.delivery.sound",
|
|
1601
|
+
label=_LABEL_NOTIFICATIONS_DELIVERY_SOUND,
|
|
1602
|
+
coerce=bool_coercer(),
|
|
1603
|
+
apply=Apply.LIVE,
|
|
1604
|
+
group="notifications",
|
|
1605
|
+
kind=Kind.BOOL,
|
|
1606
|
+
),
|
|
1607
|
+
)
|
|
1608
|
+
|
|
1609
|
+
notifications_suppress_when_focused: bool = field(
|
|
1610
|
+
default=True,
|
|
1611
|
+
metadata=spec(
|
|
1612
|
+
key="notifications.suppress_when_focused",
|
|
1613
|
+
label=_LABEL_NOTIFICATIONS_SUPPRESS_WHEN_FOCUSED,
|
|
1614
|
+
coerce=bool_coercer(),
|
|
1615
|
+
apply=Apply.LIVE,
|
|
1616
|
+
group="notifications",
|
|
1617
|
+
kind=Kind.BOOL,
|
|
1618
|
+
),
|
|
1619
|
+
)
|
|
1620
|
+
|
|
1621
|
+
notifications_event_approval_required: bool = field(
|
|
1622
|
+
default=True,
|
|
1623
|
+
metadata=spec(
|
|
1624
|
+
key="notifications.events.approval_required",
|
|
1625
|
+
label=_LABEL_NOTIFICATIONS_EVENTS_APPROVAL_REQUIRED,
|
|
1626
|
+
coerce=bool_coercer(),
|
|
1627
|
+
apply=Apply.LIVE,
|
|
1628
|
+
group="notifications",
|
|
1629
|
+
kind=Kind.BOOL,
|
|
1630
|
+
),
|
|
1631
|
+
)
|
|
1632
|
+
|
|
1633
|
+
notifications_event_ask_user: bool = field(
|
|
1634
|
+
default=True,
|
|
1635
|
+
metadata=spec(
|
|
1636
|
+
key="notifications.events.ask_user",
|
|
1637
|
+
label=_LABEL_NOTIFICATIONS_EVENTS_ASK_USER,
|
|
1638
|
+
coerce=bool_coercer(),
|
|
1639
|
+
apply=Apply.LIVE,
|
|
1640
|
+
group="notifications",
|
|
1641
|
+
kind=Kind.BOOL,
|
|
1642
|
+
),
|
|
1643
|
+
)
|
|
1644
|
+
|
|
1645
|
+
notifications_event_turn_complete: bool = field(
|
|
1646
|
+
default=True,
|
|
1647
|
+
metadata=spec(
|
|
1648
|
+
key="notifications.events.turn_complete",
|
|
1649
|
+
label=_LABEL_NOTIFICATIONS_EVENTS_TURN_COMPLETE,
|
|
1650
|
+
coerce=bool_coercer(),
|
|
1651
|
+
apply=Apply.LIVE,
|
|
1652
|
+
group="notifications",
|
|
1653
|
+
kind=Kind.BOOL,
|
|
1654
|
+
),
|
|
1655
|
+
)
|
|
1656
|
+
|
|
1657
|
+
notifications_event_turn_error: bool = field(
|
|
1658
|
+
default=True,
|
|
1659
|
+
metadata=spec(
|
|
1660
|
+
key="notifications.events.turn_error",
|
|
1661
|
+
label=_LABEL_NOTIFICATIONS_EVENTS_TURN_ERROR,
|
|
1662
|
+
coerce=bool_coercer(),
|
|
1663
|
+
apply=Apply.LIVE,
|
|
1664
|
+
group="notifications",
|
|
1665
|
+
kind=Kind.BOOL,
|
|
1666
|
+
),
|
|
1667
|
+
)
|
|
1668
|
+
|
|
1669
|
+
web_search_mode: str = field(
|
|
1670
|
+
default="auto",
|
|
1671
|
+
metadata=spec(
|
|
1672
|
+
key="tools.web_search.mode",
|
|
1673
|
+
label=_WEB_LABEL_WEB_SEARCH_MODE,
|
|
1674
|
+
# "provider" needs a profile-level provider map, so it cannot build from
|
|
1675
|
+
# global settings and the panel must not offer it.
|
|
1676
|
+
coerce=web_mode_coercer(on="auto"),
|
|
1677
|
+
apply=Apply.RELOAD,
|
|
1678
|
+
group="tools",
|
|
1679
|
+
kind=Kind.ENUM,
|
|
1680
|
+
project_merge=ProjectMerge.DENY,
|
|
1681
|
+
choices=("off", "auto"),
|
|
1682
|
+
),
|
|
1683
|
+
)
|
|
1684
|
+
|
|
1685
|
+
web_search_num_results: int = field(
|
|
1686
|
+
default=8,
|
|
1687
|
+
metadata=spec(
|
|
1688
|
+
key="tools.web_search.num_results",
|
|
1689
|
+
label=_WEB_LABEL_WEB_SEARCH_NUM_RESULTS,
|
|
1690
|
+
coerce=int_coercer(minimum=1, maximum=20),
|
|
1691
|
+
apply=Apply.RELOAD,
|
|
1692
|
+
group="tools",
|
|
1693
|
+
kind=Kind.INT,
|
|
1694
|
+
project_merge=ProjectMerge.DENY,
|
|
1695
|
+
),
|
|
1696
|
+
)
|
|
1697
|
+
|
|
1698
|
+
web_search_timeout_seconds: int = field(
|
|
1699
|
+
default=30,
|
|
1700
|
+
metadata=spec(
|
|
1701
|
+
key="tools.web_search.timeout_seconds",
|
|
1702
|
+
label=_WEB_LABEL_WEB_SEARCH_TIMEOUT_SECONDS,
|
|
1703
|
+
coerce=int_coercer(minimum=1, maximum=120),
|
|
1704
|
+
apply=Apply.RELOAD,
|
|
1705
|
+
group="tools",
|
|
1706
|
+
kind=Kind.INT,
|
|
1707
|
+
project_merge=ProjectMerge.DENY,
|
|
1708
|
+
),
|
|
1709
|
+
)
|
|
1710
|
+
|
|
1711
|
+
web_fetch_mode: str = field(
|
|
1712
|
+
default="on",
|
|
1713
|
+
metadata=spec(
|
|
1714
|
+
key="tools.web_fetch.mode",
|
|
1715
|
+
label=_WEB_LABEL_WEB_FETCH_MODE,
|
|
1716
|
+
coerce=web_mode_coercer(on="on"),
|
|
1717
|
+
apply=Apply.RELOAD,
|
|
1718
|
+
group="tools",
|
|
1719
|
+
kind=Kind.ENUM,
|
|
1720
|
+
project_merge=ProjectMerge.DENY,
|
|
1721
|
+
choices=("off", "on"),
|
|
1722
|
+
),
|
|
1723
|
+
)
|
|
1724
|
+
|
|
1725
|
+
web_fetch_max_tokens: int = field(
|
|
1726
|
+
default=16000,
|
|
1727
|
+
metadata=spec(
|
|
1728
|
+
key="tools.web_fetch.max_tokens",
|
|
1729
|
+
label=_WEB_LABEL_WEB_FETCH_MAX_TOKENS,
|
|
1730
|
+
coerce=int_coercer(minimum=1, maximum=64000),
|
|
1731
|
+
apply=Apply.RELOAD,
|
|
1732
|
+
group="tools",
|
|
1733
|
+
kind=Kind.INT,
|
|
1734
|
+
project_merge=ProjectMerge.DENY,
|
|
1735
|
+
),
|
|
1736
|
+
)
|
|
1737
|
+
|
|
1738
|
+
web_fetch_timeout_seconds: int = field(
|
|
1739
|
+
default=60,
|
|
1740
|
+
metadata=spec(
|
|
1741
|
+
key="tools.web_fetch.timeout_seconds",
|
|
1742
|
+
label=_WEB_LABEL_WEB_FETCH_TIMEOUT_SECONDS,
|
|
1743
|
+
coerce=int_coercer(minimum=1, maximum=120),
|
|
1744
|
+
apply=Apply.RELOAD,
|
|
1745
|
+
group="tools",
|
|
1746
|
+
kind=Kind.INT,
|
|
1747
|
+
project_merge=ProjectMerge.DENY,
|
|
1748
|
+
),
|
|
1749
|
+
)
|
|
1750
|
+
|
|
1751
|
+
web_egress_proxy_url: str = field(
|
|
1752
|
+
default="",
|
|
1753
|
+
metadata=spec(
|
|
1754
|
+
key="tools.web_egress.proxy_url",
|
|
1755
|
+
label=_WEB_LABEL_WEB_EGRESS_PROXY_URL,
|
|
1756
|
+
coerce=web_proxy_url_coercer,
|
|
1757
|
+
apply=Apply.RELOAD,
|
|
1758
|
+
group="tools",
|
|
1759
|
+
kind=Kind.TEXT,
|
|
1760
|
+
project_merge=ProjectMerge.DENY,
|
|
1761
|
+
),
|
|
1762
|
+
)
|
|
1763
|
+
|
|
1764
|
+
web_egress_proxy_dns: str = field(
|
|
1765
|
+
default="local",
|
|
1766
|
+
metadata=spec(
|
|
1767
|
+
key="tools.web_egress.proxy_dns",
|
|
1768
|
+
label=_WEB_LABEL_WEB_EGRESS_PROXY_DNS,
|
|
1769
|
+
# remote hands the final-address check to the proxy, so only the user may choose it.
|
|
1770
|
+
coerce=choice_coercer(choices=("local", "remote")),
|
|
1771
|
+
apply=Apply.RELOAD,
|
|
1772
|
+
group="tools",
|
|
1773
|
+
kind=Kind.ENUM,
|
|
1774
|
+
project_merge=ProjectMerge.DENY,
|
|
1775
|
+
choices=("local", "remote"),
|
|
1776
|
+
),
|
|
1777
|
+
)
|
|
1778
|
+
|
|
1779
|
+
web_search_custom_endpoints: str = field(
|
|
1780
|
+
default="[]",
|
|
1781
|
+
metadata=spec(
|
|
1782
|
+
key="tools.web_search.custom_endpoints",
|
|
1783
|
+
label=_WEB_LABEL_WEB_SEARCH_CUSTOM_ENDPOINTS,
|
|
1784
|
+
coerce=web_custom_endpoints_coercer,
|
|
1785
|
+
apply=Apply.RELOAD,
|
|
1786
|
+
group="tools",
|
|
1787
|
+
kind=Kind.TEXT,
|
|
1788
|
+
project_merge=ProjectMerge.DENY,
|
|
1789
|
+
),
|
|
1790
|
+
)
|
|
1791
|
+
|
|
1792
|
+
web_search_private_origins: str = field(
|
|
1793
|
+
default="[]",
|
|
1794
|
+
metadata=spec(
|
|
1795
|
+
key="tools.web_search.private_origins",
|
|
1796
|
+
label=_WEB_LABEL_WEB_SEARCH_PRIVATE_ORIGINS,
|
|
1797
|
+
coerce=web_origins_coercer,
|
|
1798
|
+
apply=Apply.RELOAD,
|
|
1799
|
+
group="tools",
|
|
1800
|
+
kind=Kind.TEXT,
|
|
1801
|
+
project_merge=ProjectMerge.DENY,
|
|
1802
|
+
),
|
|
1803
|
+
)
|
|
1804
|
+
|
|
1805
|
+
web_search_http_origins: str = field(
|
|
1806
|
+
default="[]",
|
|
1807
|
+
metadata=spec(
|
|
1808
|
+
key="tools.web_search.http_origins",
|
|
1809
|
+
label=_WEB_LABEL_WEB_SEARCH_HTTP_ORIGINS,
|
|
1810
|
+
coerce=web_origins_coercer,
|
|
1811
|
+
apply=Apply.RELOAD,
|
|
1812
|
+
group="tools",
|
|
1813
|
+
kind=Kind.TEXT,
|
|
1814
|
+
project_merge=ProjectMerge.DENY,
|
|
1815
|
+
),
|
|
1816
|
+
)
|
|
1817
|
+
|
|
1818
|
+
web_fetch_private_origins: str = field(
|
|
1819
|
+
default="[]",
|
|
1820
|
+
metadata=spec(
|
|
1821
|
+
key="tools.web_fetch.private_origins",
|
|
1822
|
+
label=_WEB_LABEL_WEB_FETCH_PRIVATE_ORIGINS,
|
|
1823
|
+
coerce=web_origins_coercer,
|
|
1824
|
+
apply=Apply.RELOAD,
|
|
1825
|
+
group="tools",
|
|
1826
|
+
kind=Kind.TEXT,
|
|
1827
|
+
project_merge=ProjectMerge.DENY,
|
|
1828
|
+
),
|
|
1829
|
+
)
|
|
1830
|
+
|
|
1831
|
+
web_fetch_http_origins: str = field(
|
|
1832
|
+
default="[]",
|
|
1833
|
+
metadata=spec(
|
|
1834
|
+
key="tools.web_fetch.http_origins",
|
|
1835
|
+
label=_WEB_LABEL_WEB_FETCH_HTTP_ORIGINS,
|
|
1836
|
+
coerce=web_origins_coercer,
|
|
1837
|
+
apply=Apply.RELOAD,
|
|
1838
|
+
group="tools",
|
|
1839
|
+
kind=Kind.TEXT,
|
|
1840
|
+
project_merge=ProjectMerge.DENY,
|
|
1841
|
+
),
|
|
1842
|
+
)
|
|
1843
|
+
|
|
1844
|
+
web_fetch_allowed_origins: str = field(
|
|
1845
|
+
default="[]",
|
|
1846
|
+
metadata=spec(
|
|
1847
|
+
key="tools.web_fetch.allowed_origins",
|
|
1848
|
+
label=_WEB_LABEL_WEB_FETCH_ALLOWED_ORIGINS,
|
|
1849
|
+
coerce=web_origins_coercer,
|
|
1850
|
+
apply=Apply.RELOAD,
|
|
1851
|
+
group="tools",
|
|
1852
|
+
kind=Kind.TEXT,
|
|
1853
|
+
project_merge=ProjectMerge.DENY,
|
|
1854
|
+
),
|
|
1855
|
+
)
|
|
1856
|
+
|
|
1857
|
+
web_fetch_denied_origins: str = field(
|
|
1858
|
+
default="[]",
|
|
1859
|
+
metadata=spec(
|
|
1860
|
+
key="tools.web_fetch.denied_origins",
|
|
1861
|
+
label=_WEB_LABEL_WEB_FETCH_DENIED_ORIGINS,
|
|
1862
|
+
coerce=web_origins_coercer,
|
|
1863
|
+
apply=Apply.RELOAD,
|
|
1864
|
+
group="tools",
|
|
1865
|
+
kind=Kind.TEXT,
|
|
1866
|
+
project_merge=ProjectMerge.DENY,
|
|
1867
|
+
),
|
|
1868
|
+
)
|
|
1869
|
+
|
|
1870
|
+
@classmethod
|
|
1871
|
+
def from_env(cls, **overrides: Any) -> Settings:
|
|
1872
|
+
"""Create settings from every source, dropping provenance and warnings.
|
|
1873
|
+
|
|
1874
|
+
Compatibility shim. Production paths must call ``load_settings`` and
|
|
1875
|
+
keep all three parts of its result — a caller that throws provenance
|
|
1876
|
+
away cannot tell the user why the value they typed has no effect, and
|
|
1877
|
+
one that throws warnings away silently swallows rejected values.
|
|
1878
|
+
|
|
1879
|
+
``overrides`` are pins, so each one must already be the field's
|
|
1880
|
+
canonical value: they outrank every layer and never meet a coercer, and
|
|
1881
|
+
a ``TypeError`` here is better than a value that is stored as one thing
|
|
1882
|
+
and shown to the panel as another. ``ask_user_timeout_seconds=0`` is
|
|
1883
|
+
rejected in favour of ``None``, for instance, rather than quietly
|
|
1884
|
+
becoming a zero-second timeout.
|
|
1885
|
+
"""
|
|
1886
|
+
from chrys.foundation.config.settings_store import load_settings
|
|
1887
|
+
|
|
1888
|
+
return load_settings(**overrides).settings
|
|
1889
|
+
|
|
1890
|
+
def effective_max_transient_retries(self) -> int:
|
|
1891
|
+
"""Return the environment override or this frontend's policy default."""
|
|
1892
|
+
if self.max_transient_retries is not None:
|
|
1893
|
+
return self.max_transient_retries
|
|
1894
|
+
return self.frontend_default_max_transient_retries
|
|
1895
|
+
|
|
1896
|
+
|
|
1897
|
+
def _persist_one(key: str, value: Any, *, log_value: bool = True) -> None:
|
|
1898
|
+
"""Write one dotted key to the user settings document, without raising.
|
|
1899
|
+
|
|
1900
|
+
The thin shared body of the ``persist_*`` helpers: failures are logged and
|
|
1901
|
+
swallowed so a disk/permission issue doesn't crash the TUI, and a value the
|
|
1902
|
+
store refuses is logged rather than written. No ``os.environ`` mirror —
|
|
1903
|
+
the mirror would turn the panel's own write into an ``ENV``-layer value,
|
|
1904
|
+
and provenance would then call it "overridden, editing has no effect".
|
|
1905
|
+
"""
|
|
1906
|
+
from chrys.foundation.config.settings_store import persist
|
|
1907
|
+
|
|
1908
|
+
try:
|
|
1909
|
+
result = persist({key: value})
|
|
1910
|
+
except Exception:
|
|
1911
|
+
if log_value:
|
|
1912
|
+
logger.warning("Failed to persist %s=%r", key, value, exc_info=True)
|
|
1913
|
+
else:
|
|
1914
|
+
logger.warning("Failed to persist %s", key, exc_info=True)
|
|
1915
|
+
return
|
|
1916
|
+
if not result.ok:
|
|
1917
|
+
if log_value:
|
|
1918
|
+
logger.warning("Refusing to persist %s: %r", key, value)
|
|
1919
|
+
else:
|
|
1920
|
+
logger.warning("Refusing to persist %s: value rejected", key)
|
|
1921
|
+
|
|
1922
|
+
|
|
1923
|
+
def persist_theme(theme: str) -> None:
|
|
1924
|
+
"""Write ``ui.theme`` to the user settings document so it survives restart."""
|
|
1925
|
+
_persist_one("ui.theme", theme)
|
|
1926
|
+
|
|
1927
|
+
|
|
1928
|
+
def persist_locale(locale: str) -> None:
|
|
1929
|
+
"""Write ``ui.locale`` to the user settings document without raising."""
|
|
1930
|
+
# Content-free logging by design: the requested value must not reach the log.
|
|
1931
|
+
_persist_one("ui.locale", locale, log_value=False)
|
|
1932
|
+
|
|
1933
|
+
|
|
1934
|
+
def persist_tool_groups_expanded(value: bool) -> None:
|
|
1935
|
+
"""Persist whether newly created live tool groups start expanded."""
|
|
1936
|
+
_persist_one("ui.chat.tool_groups_expanded", value)
|
|
1937
|
+
|
|
1938
|
+
|
|
1939
|
+
def persist_approval_mode(mode: str) -> None:
|
|
1940
|
+
"""Write ``approval.default_mode`` to the user settings document.
|
|
1941
|
+
|
|
1942
|
+
``bypass`` never persists — :func:`chrys.foundation.config.settings_store.persist`
|
|
1943
|
+
downgrades it to ``auto`` so the *next* app launch cannot start in BYPASS
|
|
1944
|
+
mode; that mode must always be an explicit opt-in within a launch. Unknown
|
|
1945
|
+
values are ignored (logged, not written).
|
|
1946
|
+
"""
|
|
1947
|
+
_persist_one("approval.default_mode", mode)
|
|
1948
|
+
|
|
1949
|
+
|
|
1950
|
+
def persist_editor_keymap(mode: str) -> None:
|
|
1951
|
+
"""Persist the selected message-editor keymap."""
|
|
1952
|
+
_persist_one("ui.editor.keymap", mode)
|