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,1474 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Three-stage trajectory aggregation for the P0 dashboard.
|
|
4
|
+
|
|
5
|
+
The loader performs one physical JSONL scan into compact, reversible facts.
|
|
6
|
+
Resolution happens only after the scan reaches EOF (or the complete live-tail
|
|
7
|
+
prefix), so rollback projection and forward references are settled before any
|
|
8
|
+
metric is published.
|
|
9
|
+
|
|
10
|
+
``_resolve`` wires the stages: context evidence (``_context_evidence``) and
|
|
11
|
+
physical turn attempts (``_turns``) first, then actions and change
|
|
12
|
+
verification (``_actions``). Session totals and retry folding stay here:
|
|
13
|
+
retry amplification feeds validation, and the folded logical turns become the
|
|
14
|
+
published turns. The insight panels (``_insights``) read the physical attempts
|
|
15
|
+
and use the folded turns only to count skill use per logical turn.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from array import array
|
|
21
|
+
from collections import Counter, defaultdict
|
|
22
|
+
from dataclasses import replace
|
|
23
|
+
from io import BufferedReader
|
|
24
|
+
from os import fstat
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from threading import Event
|
|
27
|
+
from typing import Final, cast
|
|
28
|
+
|
|
29
|
+
import chrys.service.analytics._turns as _turns
|
|
30
|
+
from chrys.foundation.config.settings import DEFAULT_TRAJECTORY_VERIFY_COMMANDS
|
|
31
|
+
from chrys.foundation.platform.files import secure_open_owner_only_binary
|
|
32
|
+
from chrys.foundation.trajectory.fingerprint import FINGERPRINT_KEY_BYTES
|
|
33
|
+
from chrys.foundation.trajectory.keys import TRAJECTORY_KEY_FILE_NAME
|
|
34
|
+
from chrys.service.analytics._actions import (
|
|
35
|
+
_action_count_metrics,
|
|
36
|
+
_degrade_change_verification,
|
|
37
|
+
_degrade_validation_metrics,
|
|
38
|
+
_resolve_actions,
|
|
39
|
+
_resolve_change_verification,
|
|
40
|
+
_validation_metrics,
|
|
41
|
+
)
|
|
42
|
+
from chrys.service.analytics._context_evidence import _revision_memberships, _RevisionResolution
|
|
43
|
+
from chrys.service.analytics._facts import (
|
|
44
|
+
_active,
|
|
45
|
+
_closed_sequence_union,
|
|
46
|
+
_Endpoint,
|
|
47
|
+
_Intermediate,
|
|
48
|
+
_lifecycle_cut,
|
|
49
|
+
_LifecycleCut,
|
|
50
|
+
_Node,
|
|
51
|
+
_payload_str,
|
|
52
|
+
)
|
|
53
|
+
from chrys.service.analytics._insights import (
|
|
54
|
+
_context_carrying_load,
|
|
55
|
+
_degrade_insights,
|
|
56
|
+
_degrade_tool_usage_panel,
|
|
57
|
+
_insights_analysis,
|
|
58
|
+
_tool_usage_panel,
|
|
59
|
+
)
|
|
60
|
+
from chrys.service.analytics._metric_ops import (
|
|
61
|
+
_cap_session_metric,
|
|
62
|
+
_percentile_metric,
|
|
63
|
+
_sum_metrics,
|
|
64
|
+
_sum_optional_bucket_metrics,
|
|
65
|
+
)
|
|
66
|
+
from chrys.service.analytics._session_projection import _lazy_session_projection, _SessionProjectionCache
|
|
67
|
+
from chrys.service.analytics._timeline import (
|
|
68
|
+
_exclusively_inactive,
|
|
69
|
+
_merge_projection_memberships,
|
|
70
|
+
_node_owner_membership,
|
|
71
|
+
_timeline_diagnostics,
|
|
72
|
+
_turn_start_membership,
|
|
73
|
+
)
|
|
74
|
+
from chrys.service.analytics._workflows import include_workflow_overview, workflow_runs
|
|
75
|
+
from chrys.service.analytics.findings import evaluate_findings
|
|
76
|
+
from chrys.service.analytics.model import (
|
|
77
|
+
FLOW_TERMINAL_INDEX,
|
|
78
|
+
ActionClass,
|
|
79
|
+
ActionOperation,
|
|
80
|
+
AnalysisAvailability,
|
|
81
|
+
Metric,
|
|
82
|
+
Precision,
|
|
83
|
+
SequenceRangeDiagnostic,
|
|
84
|
+
SessionCounterSamples,
|
|
85
|
+
SessionSpan,
|
|
86
|
+
SubmissionLatencyBucket,
|
|
87
|
+
SubmissionLatencyOverview,
|
|
88
|
+
SubmissionLatencySample,
|
|
89
|
+
SubmissionLatencyStats,
|
|
90
|
+
TimelineOperation,
|
|
91
|
+
TimelineOperationDiagnostic,
|
|
92
|
+
TimeSlice,
|
|
93
|
+
TokenUsage,
|
|
94
|
+
TrajectoryAnalysis,
|
|
95
|
+
TrajectoryDiagnostics,
|
|
96
|
+
TrajectoryOverview,
|
|
97
|
+
TurnAnalysis,
|
|
98
|
+
TurnAttemptRef,
|
|
99
|
+
TurnFlow,
|
|
100
|
+
UsageBucket,
|
|
101
|
+
WallBucket,
|
|
102
|
+
least_precision,
|
|
103
|
+
)
|
|
104
|
+
from chrys.service.analytics.reader import (
|
|
105
|
+
ScanCursor,
|
|
106
|
+
TrajectoryScanCancelled,
|
|
107
|
+
_PrefixHasher,
|
|
108
|
+
scan_open_trajectory_batch,
|
|
109
|
+
verify_prefix,
|
|
110
|
+
)
|
|
111
|
+
from chrys.service.analytics.reader import raise_if_cancelled as _check_cancelled
|
|
112
|
+
from chrys.service.trajectory.preparation import PreparationOutcome
|
|
113
|
+
|
|
114
|
+
MAX_RESIDENT_MEMORY_BYTES: Final = 200 * 1024 * 1024
|
|
115
|
+
"""P0 acceptance ceiling for a 200 MiB input fixture."""
|
|
116
|
+
|
|
117
|
+
_PREFIX_REVERIFY_FLOOR_BYTES: Final = 1 << 20
|
|
118
|
+
"""Smallest growth that triggers a full prefix replay between doublings."""
|
|
119
|
+
|
|
120
|
+
_PREFIX_PROBE_BYTES: Final = 1 << 12
|
|
121
|
+
"""Consumed-suffix window reread before trusting growth as an append."""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _read_prefix_probe(handle: BufferedReader, byte_offset: int) -> bytes:
|
|
125
|
+
length = min(byte_offset, _PREFIX_PROBE_BYTES)
|
|
126
|
+
handle.seek(byte_offset - length)
|
|
127
|
+
return handle.read(length)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _read_installed_fingerprint_key(path: Path) -> bytes | None:
|
|
131
|
+
if (
|
|
132
|
+
len(path.parents) < 4
|
|
133
|
+
or path.name != "events.jsonl"
|
|
134
|
+
or path.parent.name != "trajectory"
|
|
135
|
+
or path.parents[2].name != "sessions"
|
|
136
|
+
):
|
|
137
|
+
return None
|
|
138
|
+
from chrys.foundation.platform import get_platform
|
|
139
|
+
|
|
140
|
+
# The recorder keeps the key in the platform config directory. Beside the
|
|
141
|
+
# session root covers the default layout (the root IS the config
|
|
142
|
+
# directory) and trees copied along with their key; the config directory
|
|
143
|
+
# is the fallback for a custom session root, whose sessions live
|
|
144
|
+
# elsewhere while the key stays put.
|
|
145
|
+
candidates = [path.parents[3] / TRAJECTORY_KEY_FILE_NAME]
|
|
146
|
+
config_key_path = get_platform().config_dir / TRAJECTORY_KEY_FILE_NAME
|
|
147
|
+
if config_key_path not in candidates:
|
|
148
|
+
candidates.append(config_key_path)
|
|
149
|
+
for key_path in candidates:
|
|
150
|
+
try:
|
|
151
|
+
with secure_open_owner_only_binary(key_path) as handle:
|
|
152
|
+
key = handle.read(FINGERPRINT_KEY_BYTES + 1)
|
|
153
|
+
except OSError:
|
|
154
|
+
continue
|
|
155
|
+
if len(key) == FINGERPRINT_KEY_BYTES:
|
|
156
|
+
return key
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class TrajectoryAnalyzer:
|
|
161
|
+
"""Path-identity cache with append-only live-tail scans and dirty-turn resolves."""
|
|
162
|
+
|
|
163
|
+
def __init__(
|
|
164
|
+
self,
|
|
165
|
+
*,
|
|
166
|
+
fingerprint_key: bytes | None = None,
|
|
167
|
+
verify_commands: str = DEFAULT_TRAJECTORY_VERIFY_COMMANDS,
|
|
168
|
+
) -> None:
|
|
169
|
+
self._path: Path | None = None
|
|
170
|
+
self._identity: tuple[int, int] | None = None
|
|
171
|
+
self._size = 0
|
|
172
|
+
self._mtime_ns = 0
|
|
173
|
+
self._prefix_digest = b""
|
|
174
|
+
self._prefix_hasher: _PrefixHasher | None = None
|
|
175
|
+
self._prefix_probe = b""
|
|
176
|
+
self._verified_offset = 0
|
|
177
|
+
self._cursor = ScanCursor()
|
|
178
|
+
self._intermediate: _Intermediate | None = None
|
|
179
|
+
self._resolution_cache = _turns._ResolutionCache()
|
|
180
|
+
self._analysis: TrajectoryAnalysis | None = None
|
|
181
|
+
self._generation = 0
|
|
182
|
+
self._fingerprint_key = fingerprint_key
|
|
183
|
+
self._verify_commands = verify_commands
|
|
184
|
+
self._session_projection_cache = _SessionProjectionCache()
|
|
185
|
+
|
|
186
|
+
def load(self, path: Path, *, cancel_event: Event | None = None) -> TrajectoryAnalysis:
|
|
187
|
+
"""Perform the one initial physical scan and resolve after EOF."""
|
|
188
|
+
self._path = path
|
|
189
|
+
self._generation += 1
|
|
190
|
+
try:
|
|
191
|
+
handle = path.open("rb")
|
|
192
|
+
except FileNotFoundError:
|
|
193
|
+
self._reset_cache()
|
|
194
|
+
self._path = path
|
|
195
|
+
self._analysis = TrajectoryAnalysis(
|
|
196
|
+
availability=AnalysisAvailability.UNAVAILABLE,
|
|
197
|
+
path=path,
|
|
198
|
+
generation=self._generation,
|
|
199
|
+
)
|
|
200
|
+
return self._analysis
|
|
201
|
+
except OSError as exc:
|
|
202
|
+
return self._read_error(path, exc)
|
|
203
|
+
with handle:
|
|
204
|
+
return self._load_opened(path, handle, cancel_event=cancel_event)
|
|
205
|
+
|
|
206
|
+
def _load_opened(
|
|
207
|
+
self,
|
|
208
|
+
path: Path,
|
|
209
|
+
handle: BufferedReader,
|
|
210
|
+
*,
|
|
211
|
+
cancel_event: Event | None,
|
|
212
|
+
) -> TrajectoryAnalysis:
|
|
213
|
+
intermediate = _Intermediate(path)
|
|
214
|
+
cursor = ScanCursor()
|
|
215
|
+
try:
|
|
216
|
+
batch = scan_open_trajectory_batch(
|
|
217
|
+
handle,
|
|
218
|
+
intermediate.consume,
|
|
219
|
+
cursor=cursor,
|
|
220
|
+
cancel_event=cancel_event,
|
|
221
|
+
)
|
|
222
|
+
except TrajectoryScanCancelled:
|
|
223
|
+
self._reset_cache()
|
|
224
|
+
raise
|
|
225
|
+
except OSError as exc:
|
|
226
|
+
return self._read_error(path, exc)
|
|
227
|
+
if cancel_event is not None and cancel_event.is_set():
|
|
228
|
+
self._reset_cache()
|
|
229
|
+
raise TrajectoryScanCancelled
|
|
230
|
+
intermediate.absorb_batch(batch)
|
|
231
|
+
stat = fstat(handle.fileno())
|
|
232
|
+
self._cursor = cursor
|
|
233
|
+
self._intermediate = intermediate
|
|
234
|
+
self._identity = (stat.st_dev, stat.st_ino)
|
|
235
|
+
self._size = cursor.byte_offset + batch.torn_tail_bytes
|
|
236
|
+
self._mtime_ns = stat.st_mtime_ns
|
|
237
|
+
self._prefix_digest = batch.prefix_digest
|
|
238
|
+
self._prefix_hasher = batch.prefix_hasher
|
|
239
|
+
self._prefix_probe = _read_prefix_probe(handle, cursor.byte_offset)
|
|
240
|
+
self._verified_offset = cursor.byte_offset
|
|
241
|
+
self._resolution_cache = _turns._ResolutionCache()
|
|
242
|
+
try:
|
|
243
|
+
self._analysis = _resolve(
|
|
244
|
+
intermediate,
|
|
245
|
+
generation=self._generation,
|
|
246
|
+
resolution_cache=self._resolution_cache,
|
|
247
|
+
cancel_event=cancel_event,
|
|
248
|
+
fingerprint_key=self._fingerprint_key or _read_installed_fingerprint_key(path),
|
|
249
|
+
verify_commands=self._verify_commands,
|
|
250
|
+
session_cache=self._session_projection_cache,
|
|
251
|
+
)
|
|
252
|
+
except TrajectoryScanCancelled:
|
|
253
|
+
self._reset_cache()
|
|
254
|
+
raise
|
|
255
|
+
intermediate.mark_resolved()
|
|
256
|
+
return self._analysis
|
|
257
|
+
|
|
258
|
+
def refresh(self, *, cancel_event: Event | None = None) -> TrajectoryAnalysis:
|
|
259
|
+
"""Consume one append batch; replacement or truncation performs a fresh load."""
|
|
260
|
+
path = self._path
|
|
261
|
+
if path is None:
|
|
262
|
+
raise RuntimeError("TrajectoryAnalyzer.refresh() requires load() first")
|
|
263
|
+
try:
|
|
264
|
+
handle = path.open("rb")
|
|
265
|
+
except OSError:
|
|
266
|
+
return self.load(path, cancel_event=cancel_event)
|
|
267
|
+
with handle:
|
|
268
|
+
stat = fstat(handle.fileno())
|
|
269
|
+
identity = (stat.st_dev, stat.st_ino)
|
|
270
|
+
intermediate = self._intermediate
|
|
271
|
+
self._generation += 1
|
|
272
|
+
if intermediate is None or identity != self._identity or stat.st_size < self._cursor.byte_offset:
|
|
273
|
+
return self._load_opened(path, handle, cancel_event=cancel_event)
|
|
274
|
+
if stat.st_size == self._size:
|
|
275
|
+
# An unchanged log still goes stale when the companion
|
|
276
|
+
# session document moved under it: carriers, commands, and
|
|
277
|
+
# mutation detail all fold from that document.
|
|
278
|
+
if (
|
|
279
|
+
stat.st_mtime_ns == self._mtime_ns
|
|
280
|
+
and self._analysis is not None
|
|
281
|
+
and not self._session_projection_cache.changed(path)
|
|
282
|
+
):
|
|
283
|
+
self._generation -= 1
|
|
284
|
+
return self._analysis
|
|
285
|
+
return self._load_opened(path, handle, cancel_event=cancel_event)
|
|
286
|
+
retained = self._prefix_hasher
|
|
287
|
+
appended_since_verify = self._cursor.byte_offset - self._verified_offset
|
|
288
|
+
# Replaying the whole consumed prefix on every append batch is
|
|
289
|
+
# quadratic against a log polled twice a second, so the retained
|
|
290
|
+
# digest state continues across batches and the replay re-verifies
|
|
291
|
+
# only once the log outgrows the last verified length again:
|
|
292
|
+
# amortized linear, still fail-closed at every doubling and load.
|
|
293
|
+
if retained is not None and appended_since_verify < max(
|
|
294
|
+
self._verified_offset, _PREFIX_REVERIFY_FLOOR_BYTES
|
|
295
|
+
):
|
|
296
|
+
# Growth alone is not append provenance: a same-inode
|
|
297
|
+
# replacement also grows the file. Rereading the consumed
|
|
298
|
+
# suffix catches a replaced prefix at the boundary before any
|
|
299
|
+
# stale state is combined with the new bytes; a replacement
|
|
300
|
+
# that forges the probe window still fails the next replay.
|
|
301
|
+
if _read_prefix_probe(handle, self._cursor.byte_offset) != self._prefix_probe:
|
|
302
|
+
return self._load_opened(path, handle, cancel_event=cancel_event)
|
|
303
|
+
prefix_hasher = retained
|
|
304
|
+
else:
|
|
305
|
+
try:
|
|
306
|
+
prefix_hasher = verify_prefix(
|
|
307
|
+
handle,
|
|
308
|
+
length=self._cursor.byte_offset,
|
|
309
|
+
expected_digest=self._prefix_digest,
|
|
310
|
+
cancel_event=cancel_event,
|
|
311
|
+
)
|
|
312
|
+
except TrajectoryScanCancelled:
|
|
313
|
+
self._reset_cache()
|
|
314
|
+
raise
|
|
315
|
+
if prefix_hasher is None:
|
|
316
|
+
return self._load_opened(path, handle, cancel_event=cancel_event)
|
|
317
|
+
self._verified_offset = self._cursor.byte_offset
|
|
318
|
+
try:
|
|
319
|
+
batch = scan_open_trajectory_batch(
|
|
320
|
+
handle,
|
|
321
|
+
intermediate.consume,
|
|
322
|
+
cursor=self._cursor,
|
|
323
|
+
cancel_event=cancel_event,
|
|
324
|
+
prefix_hasher=prefix_hasher,
|
|
325
|
+
)
|
|
326
|
+
except TrajectoryScanCancelled:
|
|
327
|
+
self._reset_cache()
|
|
328
|
+
raise
|
|
329
|
+
except OSError as exc:
|
|
330
|
+
return self._read_error(path, exc)
|
|
331
|
+
if cancel_event is not None and cancel_event.is_set():
|
|
332
|
+
self._reset_cache()
|
|
333
|
+
raise TrajectoryScanCancelled
|
|
334
|
+
intermediate.absorb_batch(batch)
|
|
335
|
+
final_stat = fstat(handle.fileno())
|
|
336
|
+
self._size = self._cursor.byte_offset + batch.torn_tail_bytes
|
|
337
|
+
self._mtime_ns = final_stat.st_mtime_ns
|
|
338
|
+
self._prefix_digest = batch.prefix_digest
|
|
339
|
+
self._prefix_hasher = batch.prefix_hasher
|
|
340
|
+
self._prefix_probe = _read_prefix_probe(handle, self._cursor.byte_offset)
|
|
341
|
+
try:
|
|
342
|
+
self._analysis = _resolve(
|
|
343
|
+
intermediate,
|
|
344
|
+
generation=self._generation,
|
|
345
|
+
resolution_cache=self._resolution_cache,
|
|
346
|
+
previous=self._analysis,
|
|
347
|
+
cancel_event=cancel_event,
|
|
348
|
+
fingerprint_key=self._fingerprint_key or _read_installed_fingerprint_key(path),
|
|
349
|
+
verify_commands=self._verify_commands,
|
|
350
|
+
session_cache=self._session_projection_cache,
|
|
351
|
+
)
|
|
352
|
+
except TrajectoryScanCancelled:
|
|
353
|
+
self._reset_cache()
|
|
354
|
+
raise
|
|
355
|
+
intermediate.mark_resolved()
|
|
356
|
+
return self._analysis
|
|
357
|
+
|
|
358
|
+
def release(self) -> None:
|
|
359
|
+
"""Release all generation-bound state owned by the analyzer."""
|
|
360
|
+
self._path = None
|
|
361
|
+
self._analysis = None
|
|
362
|
+
self._session_projection_cache = _SessionProjectionCache()
|
|
363
|
+
self._reset_cache()
|
|
364
|
+
|
|
365
|
+
def counter_samples(self) -> SessionCounterSamples:
|
|
366
|
+
"""Project per-turn counter samples from the retained scan facts.
|
|
367
|
+
|
|
368
|
+
Computed transiently for export; retaining timestamped samples on every
|
|
369
|
+
TurnAnalysis would scale the resident footprint with session length.
|
|
370
|
+
"""
|
|
371
|
+
intermediate = self._intermediate
|
|
372
|
+
if intermediate is None:
|
|
373
|
+
return SessionCounterSamples(usage_by_turn={}, context_by_turn={})
|
|
374
|
+
inactive_ranges = _closed_sequence_union(intermediate.rollback_ranges)
|
|
375
|
+
return SessionCounterSamples(
|
|
376
|
+
usage_by_turn=_turns._usage_samples_by_turn(intermediate, inactive_ranges),
|
|
377
|
+
context_by_turn=_turns._context_samples_by_turn(intermediate, inactive_ranges),
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
def _read_error(self, path: Path, exc: OSError) -> TrajectoryAnalysis:
|
|
381
|
+
self._reset_cache()
|
|
382
|
+
self._path = path
|
|
383
|
+
self._analysis = TrajectoryAnalysis(
|
|
384
|
+
availability=AnalysisAvailability.READ_ERROR,
|
|
385
|
+
path=path,
|
|
386
|
+
generation=self._generation,
|
|
387
|
+
read_error=str(exc),
|
|
388
|
+
)
|
|
389
|
+
return self._analysis
|
|
390
|
+
|
|
391
|
+
def _reset_cache(self) -> None:
|
|
392
|
+
self._identity = None
|
|
393
|
+
self._size = 0
|
|
394
|
+
self._mtime_ns = 0
|
|
395
|
+
self._prefix_digest = b""
|
|
396
|
+
# A scan abandoned mid-batch leaves the retained digest state partially
|
|
397
|
+
# updated, so every reset path must also drop it.
|
|
398
|
+
self._prefix_hasher = None
|
|
399
|
+
self._prefix_probe = b""
|
|
400
|
+
self._verified_offset = 0
|
|
401
|
+
self._cursor = ScanCursor()
|
|
402
|
+
self._intermediate = None
|
|
403
|
+
self._resolution_cache = _turns._ResolutionCache()
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def analyze_trajectory(
|
|
407
|
+
path: Path,
|
|
408
|
+
*,
|
|
409
|
+
fingerprint_key: bytes | None = None,
|
|
410
|
+
verify_commands: str = DEFAULT_TRAJECTORY_VERIFY_COMMANDS,
|
|
411
|
+
) -> TrajectoryAnalysis:
|
|
412
|
+
"""Analyze *path* without retaining a live-tail cache."""
|
|
413
|
+
return TrajectoryAnalyzer(fingerprint_key=fingerprint_key, verify_commands=verify_commands).load(path)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _resolve(
|
|
417
|
+
intermediate: _Intermediate,
|
|
418
|
+
*,
|
|
419
|
+
generation: int,
|
|
420
|
+
resolution_cache: _turns._ResolutionCache,
|
|
421
|
+
previous: TrajectoryAnalysis | None = None,
|
|
422
|
+
cancel_event: Event | None = None,
|
|
423
|
+
fingerprint_key: bytes | None = None,
|
|
424
|
+
session_cache: _SessionProjectionCache | None = None,
|
|
425
|
+
verify_commands: str = DEFAULT_TRAJECTORY_VERIFY_COMMANDS,
|
|
426
|
+
) -> TrajectoryAnalysis:
|
|
427
|
+
_check_cancelled(cancel_event)
|
|
428
|
+
active_ranges = _closed_sequence_union(intermediate.rollback_ranges)
|
|
429
|
+
rollback_projection_unresolved = _rollback_projection_unresolved(intermediate, active_ranges)
|
|
430
|
+
session_integrity_reason = _session_integrity_reason(
|
|
431
|
+
intermediate,
|
|
432
|
+
rollback_projection_unresolved=rollback_projection_unresolved,
|
|
433
|
+
)
|
|
434
|
+
active_turn_ids: set[str] = set()
|
|
435
|
+
for turn_id, turn in intermediate.turns.items():
|
|
436
|
+
_check_cancelled(cancel_event)
|
|
437
|
+
if any(_active(event.sequence, active_ranges) for event in turn.starts):
|
|
438
|
+
active_turn_ids.add(turn_id)
|
|
439
|
+
revisions = _revision_memberships(
|
|
440
|
+
intermediate,
|
|
441
|
+
active_ranges,
|
|
442
|
+
fingerprint_key=fingerprint_key,
|
|
443
|
+
cancel_event=cancel_event,
|
|
444
|
+
)
|
|
445
|
+
session_projection = (
|
|
446
|
+
session_cache.lazy(intermediate.path, cancel_event=cancel_event)
|
|
447
|
+
if session_cache is not None
|
|
448
|
+
else _lazy_session_projection(intermediate.path, cancel_event=cancel_event)
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
def mapped_carrier(item_id: str) -> str | None:
|
|
452
|
+
return session_projection().carriers.get(item_id)
|
|
453
|
+
|
|
454
|
+
# A folded logical turn is not a valid cache entry for any one of its
|
|
455
|
+
# physical attempts. Re-resolve those attempts and fold them again; regular
|
|
456
|
+
# one-attempt turns retain the live-tail fast path.
|
|
457
|
+
previous_turns = (
|
|
458
|
+
{turn.turn_id: turn for turn in previous.turns if len(turn.attempts) == 1} if previous is not None else {}
|
|
459
|
+
)
|
|
460
|
+
session_carrier_turn_ids = {
|
|
461
|
+
turn_id
|
|
462
|
+
for turn_id in active_turn_ids
|
|
463
|
+
if _turns._turn_uses_session_carrier_fallback(
|
|
464
|
+
intermediate,
|
|
465
|
+
turn_id,
|
|
466
|
+
active_ranges,
|
|
467
|
+
cancel_event=cancel_event,
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
resolved_turns: list[TurnAnalysis] = []
|
|
471
|
+
for turn_id in active_turn_ids:
|
|
472
|
+
_check_cancelled(cancel_event)
|
|
473
|
+
if (
|
|
474
|
+
not intermediate.dirty.full
|
|
475
|
+
and turn_id not in intermediate.dirty.turn_ids
|
|
476
|
+
and turn_id not in session_carrier_turn_ids
|
|
477
|
+
and turn_id in previous_turns
|
|
478
|
+
):
|
|
479
|
+
resolved_turns.append(previous_turns[turn_id])
|
|
480
|
+
else:
|
|
481
|
+
resolved_turns.append(
|
|
482
|
+
_turns._resolve_turn(
|
|
483
|
+
intermediate,
|
|
484
|
+
intermediate.turns[turn_id],
|
|
485
|
+
active_ranges,
|
|
486
|
+
resolution_cache=resolution_cache,
|
|
487
|
+
revisions=revisions,
|
|
488
|
+
mapped_carrier=mapped_carrier,
|
|
489
|
+
rollback_projection_unresolved=rollback_projection_unresolved,
|
|
490
|
+
refresh_counter_axis=intermediate.dirty.full or turn_id in intermediate.dirty.turn_ids,
|
|
491
|
+
cancel_event=cancel_event,
|
|
492
|
+
)
|
|
493
|
+
)
|
|
494
|
+
resolved_turns.sort(key=lambda turn: turn.start_sequence)
|
|
495
|
+
actions = _resolve_actions(
|
|
496
|
+
intermediate,
|
|
497
|
+
active_ranges,
|
|
498
|
+
resolved_turns,
|
|
499
|
+
session_projection=session_projection,
|
|
500
|
+
verify_commands=verify_commands,
|
|
501
|
+
cancel_event=cancel_event,
|
|
502
|
+
)
|
|
503
|
+
actions_by_turn: dict[str, list[ActionOperation]] = defaultdict(list)
|
|
504
|
+
for action in actions:
|
|
505
|
+
actions_by_turn[action.turn_id].append(action)
|
|
506
|
+
resolved_turns = [
|
|
507
|
+
replace(
|
|
508
|
+
turn,
|
|
509
|
+
action_counts=_action_count_metrics(
|
|
510
|
+
tuple(actions_by_turn.get(turn.turn_id, ())),
|
|
511
|
+
projection_precision=turn.action_projection_precision,
|
|
512
|
+
projection_reason=turn.action_projection_reason,
|
|
513
|
+
),
|
|
514
|
+
)
|
|
515
|
+
for turn in resolved_turns
|
|
516
|
+
]
|
|
517
|
+
change_verification = _resolve_change_verification(
|
|
518
|
+
intermediate,
|
|
519
|
+
active_ranges,
|
|
520
|
+
resolved_turns,
|
|
521
|
+
actions,
|
|
522
|
+
session_projection(),
|
|
523
|
+
rollback_projection_unresolved=rollback_projection_unresolved,
|
|
524
|
+
cancel_event=cancel_event,
|
|
525
|
+
)
|
|
526
|
+
if session_integrity_reason is not None:
|
|
527
|
+
change_verification = _degrade_change_verification(change_verification, session_integrity_reason)
|
|
528
|
+
retry_amplification, retry_evidence, retry_target = _retry_amplification(
|
|
529
|
+
intermediate,
|
|
530
|
+
active_ranges,
|
|
531
|
+
revisions,
|
|
532
|
+
cancel_event=cancel_event,
|
|
533
|
+
)
|
|
534
|
+
validation = _validation_metrics(
|
|
535
|
+
actions,
|
|
536
|
+
resolved_turns,
|
|
537
|
+
change_verification,
|
|
538
|
+
retry_amplification,
|
|
539
|
+
cancel_event=cancel_event,
|
|
540
|
+
)
|
|
541
|
+
if session_integrity_reason is not None:
|
|
542
|
+
validation = _degrade_validation_metrics(
|
|
543
|
+
validation,
|
|
544
|
+
session_integrity_reason,
|
|
545
|
+
session_integrity_cap=True,
|
|
546
|
+
)
|
|
547
|
+
context_carrying_load = _context_carrying_load(
|
|
548
|
+
intermediate,
|
|
549
|
+
active_ranges,
|
|
550
|
+
resolved_turns,
|
|
551
|
+
revisions,
|
|
552
|
+
session_projection(),
|
|
553
|
+
cancel_event=cancel_event,
|
|
554
|
+
)
|
|
555
|
+
findings = evaluate_findings(
|
|
556
|
+
actions=actions,
|
|
557
|
+
turns=resolved_turns,
|
|
558
|
+
validation=validation,
|
|
559
|
+
change_verification=change_verification,
|
|
560
|
+
retry_amplification_evidence=retry_evidence,
|
|
561
|
+
retry_target=retry_target,
|
|
562
|
+
context_carrying_load=context_carrying_load,
|
|
563
|
+
cancel_event=cancel_event,
|
|
564
|
+
)
|
|
565
|
+
span_mismatches, containment_violations = _timeline_diagnostics(
|
|
566
|
+
intermediate,
|
|
567
|
+
active_ranges,
|
|
568
|
+
cancel_event=cancel_event,
|
|
569
|
+
)
|
|
570
|
+
diagnostics = TrajectoryDiagnostics(
|
|
571
|
+
line_count=intermediate.line_count,
|
|
572
|
+
byte_count=intermediate.byte_count,
|
|
573
|
+
torn_tail_bytes=intermediate.torn_tail_bytes,
|
|
574
|
+
corrupt_line_count=len(intermediate.corrupt_after_sequences),
|
|
575
|
+
unsupported_event_count=intermediate.unsupported_event_count,
|
|
576
|
+
accounted_prefix_violations=tuple(violation.message for violation in intermediate.prefix_violations),
|
|
577
|
+
accounted_prefix_violation_details=tuple(
|
|
578
|
+
SequenceRangeDiagnostic(violation.message, violation.first_sequence, violation.last_sequence)
|
|
579
|
+
for violation in intermediate.prefix_violations
|
|
580
|
+
),
|
|
581
|
+
explicit_gap_count=len(intermediate.explicit_gaps),
|
|
582
|
+
explicit_gaps=tuple(
|
|
583
|
+
SequenceRangeDiagnostic("trajectory gap", first, last) for first, last in intermediate.explicit_gaps
|
|
584
|
+
),
|
|
585
|
+
rollback_projection_unresolved=rollback_projection_unresolved,
|
|
586
|
+
span_duration_mismatch_count=len(span_mismatches),
|
|
587
|
+
span_duration_mismatches=span_mismatches,
|
|
588
|
+
containment_violation_count=len(containment_violations),
|
|
589
|
+
containment_violations=containment_violations,
|
|
590
|
+
malformed_hook_execution_mode_count=intermediate.malformed_hook_execution_mode_count,
|
|
591
|
+
side_call_empty_shell_revisions=revisions.side_call_empty_shell_revisions,
|
|
592
|
+
unidentified_membership_revision_count=revisions.unidentified_membership_revision_count,
|
|
593
|
+
corrupt_lines=tuple(intermediate.corrupt_lines),
|
|
594
|
+
unsupported_lines=tuple(intermediate.unsupported_lines),
|
|
595
|
+
)
|
|
596
|
+
overview = _overview(resolved_turns, cancel_event=cancel_event)
|
|
597
|
+
token_usage = _token_usage(intermediate, active_ranges, resolved_turns, cancel_event=cancel_event)
|
|
598
|
+
skill_usage = _tool_usage_panel(
|
|
599
|
+
actions,
|
|
600
|
+
resolved_turns,
|
|
601
|
+
tool_kind="skill",
|
|
602
|
+
display_name=lambda action: session_projection().skill_names.get(action.call_item_id or ""),
|
|
603
|
+
)
|
|
604
|
+
mcp_usage = _tool_usage_panel(
|
|
605
|
+
actions,
|
|
606
|
+
resolved_turns,
|
|
607
|
+
tool_kind="mcp",
|
|
608
|
+
display_name=lambda action: action.tool_name,
|
|
609
|
+
)
|
|
610
|
+
logical_turns = _fold_retry_turns(resolved_turns, cancel_event=cancel_event)
|
|
611
|
+
diagnostics = replace(
|
|
612
|
+
diagnostics,
|
|
613
|
+
timeline_operations=_timeline_operation_diagnostics(logical_turns),
|
|
614
|
+
)
|
|
615
|
+
insights = _insights_analysis(
|
|
616
|
+
intermediate,
|
|
617
|
+
active_ranges,
|
|
618
|
+
resolved_turns,
|
|
619
|
+
logical_turns,
|
|
620
|
+
actions,
|
|
621
|
+
context_carrying_load=context_carrying_load,
|
|
622
|
+
cancel_event=cancel_event,
|
|
623
|
+
)
|
|
624
|
+
submission_latency = _submission_latency(intermediate, active_ranges, cancel_event=cancel_event)
|
|
625
|
+
if session_integrity_reason is not None:
|
|
626
|
+
overview = _degrade_overview(overview, session_integrity_reason)
|
|
627
|
+
token_usage = _degrade_token_usage(token_usage, session_integrity_reason)
|
|
628
|
+
skill_usage = _degrade_tool_usage_panel(skill_usage, session_integrity_reason)
|
|
629
|
+
mcp_usage = _degrade_tool_usage_panel(mcp_usage, session_integrity_reason)
|
|
630
|
+
insights = _degrade_insights(insights, session_integrity_reason)
|
|
631
|
+
submission_latency = _degrade_submission_latency(submission_latency, session_integrity_reason)
|
|
632
|
+
runs = workflow_runs(
|
|
633
|
+
intermediate, active_ranges, integrity_reason=session_integrity_reason, cancel_event=cancel_event
|
|
634
|
+
)
|
|
635
|
+
if runs:
|
|
636
|
+
overview = include_workflow_overview(overview, runs)
|
|
637
|
+
return TrajectoryAnalysis(
|
|
638
|
+
availability=AnalysisAvailability.AVAILABLE,
|
|
639
|
+
path=intermediate.path,
|
|
640
|
+
generation=generation,
|
|
641
|
+
overview=overview,
|
|
642
|
+
workflow_runs=runs,
|
|
643
|
+
turns=tuple(logical_turns),
|
|
644
|
+
diagnostics=diagnostics,
|
|
645
|
+
actions=actions,
|
|
646
|
+
validation=None if runs else validation,
|
|
647
|
+
findings=findings,
|
|
648
|
+
submission_latency=None if runs else submission_latency,
|
|
649
|
+
change_verification=None if runs else change_verification,
|
|
650
|
+
token_usage=None if runs else token_usage,
|
|
651
|
+
skill_usage=None if runs else skill_usage,
|
|
652
|
+
mcp_usage=None if runs else mcp_usage,
|
|
653
|
+
insights=None if runs else insights,
|
|
654
|
+
session_span=SessionSpan(
|
|
655
|
+
first_turn_started_at=intermediate.first_turn_started_at,
|
|
656
|
+
last_turn_finished_at=intermediate.last_turn_finished_at,
|
|
657
|
+
runtime_count=len(intermediate.runtime_starts),
|
|
658
|
+
),
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _session_integrity_reason(
|
|
663
|
+
intermediate: _Intermediate,
|
|
664
|
+
*,
|
|
665
|
+
rollback_projection_unresolved: bool,
|
|
666
|
+
) -> str | None:
|
|
667
|
+
"""Explain physical damage or an unresolvable live-history projection."""
|
|
668
|
+
|
|
669
|
+
problems: list[str] = []
|
|
670
|
+
if intermediate.line_count == 0 and intermediate.byte_count == 0 and intermediate.torn_tail_bytes == 0:
|
|
671
|
+
problems.append("empty log")
|
|
672
|
+
if intermediate.corrupt_after_sequences:
|
|
673
|
+
problems.append("corrupt lines")
|
|
674
|
+
if intermediate.unsupported_event_count:
|
|
675
|
+
problems.append("unsupported events")
|
|
676
|
+
if intermediate.prefix_violations:
|
|
677
|
+
problems.append("accounted-prefix violations")
|
|
678
|
+
if intermediate.explicit_gaps:
|
|
679
|
+
problems.append("explicit gaps")
|
|
680
|
+
if intermediate.torn_tail_bytes:
|
|
681
|
+
problems.append("torn tail")
|
|
682
|
+
if rollback_projection_unresolved:
|
|
683
|
+
problems.append("unresolved rollback projection")
|
|
684
|
+
if not problems:
|
|
685
|
+
return None
|
|
686
|
+
return f"session trajectory integrity is unresolved: {', '.join(problems)}"
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def _rollback_projection_unresolved(
|
|
690
|
+
intermediate: _Intermediate,
|
|
691
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
692
|
+
) -> bool:
|
|
693
|
+
if any(_active(sequence, inactive_ranges) for sequence in intermediate.rollback_errors):
|
|
694
|
+
return True
|
|
695
|
+
rollback_pairs = {
|
|
696
|
+
(old_branch, new_branch)
|
|
697
|
+
for sequence, old_branch, new_branch in intermediate.rollback_branch_pairs
|
|
698
|
+
if _active(sequence, inactive_ranges)
|
|
699
|
+
}
|
|
700
|
+
return any(
|
|
701
|
+
_active(sequence, inactive_ranges)
|
|
702
|
+
and (old_branch is None or new_branch is None or (old_branch, new_branch) not in rollback_pairs)
|
|
703
|
+
for sequence, old_branch, new_branch in intermediate.branch_supersessions
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def _timeline_operation_diagnostics(
|
|
708
|
+
turns: list[TurnAnalysis],
|
|
709
|
+
) -> tuple[TimelineOperationDiagnostic, ...]:
|
|
710
|
+
"""Project sparse row diagnostics without losing physical retry ownership."""
|
|
711
|
+
diagnostics: list[TimelineOperationDiagnostic] = []
|
|
712
|
+
for turn in turns:
|
|
713
|
+
for attempt in turn.attempts:
|
|
714
|
+
for operation in turn.operations[attempt.operation_start_index : attempt.operation_end_index]:
|
|
715
|
+
code = operation.diagnostic_code
|
|
716
|
+
reason = operation.reason
|
|
717
|
+
if code is None or reason is None:
|
|
718
|
+
continue
|
|
719
|
+
diagnostics.append(
|
|
720
|
+
TimelineOperationDiagnostic(
|
|
721
|
+
turn_id=attempt.turn_id,
|
|
722
|
+
turn_number=turn.turn_number,
|
|
723
|
+
operation_id=operation.operation_id,
|
|
724
|
+
family=operation.family,
|
|
725
|
+
precision=operation.precision,
|
|
726
|
+
code=code,
|
|
727
|
+
reason=reason,
|
|
728
|
+
identity=operation.identity,
|
|
729
|
+
hook_id=operation.hook_id,
|
|
730
|
+
)
|
|
731
|
+
)
|
|
732
|
+
return tuple(diagnostics)
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def _overview(
|
|
736
|
+
turns: list[TurnAnalysis],
|
|
737
|
+
*,
|
|
738
|
+
cancel_event: Event | None = None,
|
|
739
|
+
metric_subject: str = "selected turns",
|
|
740
|
+
) -> TrajectoryOverview:
|
|
741
|
+
for _turn in turns:
|
|
742
|
+
_check_cancelled(cancel_event)
|
|
743
|
+
elapsed = _sum_metrics([turn.elapsed_ns for turn in turns], metric_subject=metric_subject)
|
|
744
|
+
compute_cp = _sum_metrics([turn.compute_cp_ns for turn in turns], metric_subject=metric_subject)
|
|
745
|
+
response_cp = _sum_metrics([turn.response_cp_ns for turn in turns], metric_subject=metric_subject)
|
|
746
|
+
exclusive = _sum_metrics([turn.exclusive_work_ns for turn in turns], metric_subject=metric_subject)
|
|
747
|
+
overlap = _sum_metrics([turn.overlap_gain_ns for turn in turns], metric_subject=metric_subject)
|
|
748
|
+
usage = _sum_metrics(
|
|
749
|
+
[turn.usage_tokens for turn in turns],
|
|
750
|
+
missing_is_missing=True,
|
|
751
|
+
metric_subject=metric_subject,
|
|
752
|
+
)
|
|
753
|
+
ratio_precision = (
|
|
754
|
+
Precision.EXACT
|
|
755
|
+
if elapsed.precision is Precision.EXACT and exclusive.precision is Precision.EXACT
|
|
756
|
+
else Precision.UNRESOLVED
|
|
757
|
+
)
|
|
758
|
+
elapsed_value = int(elapsed.value) if elapsed.value is not None else None
|
|
759
|
+
exclusive_value = int(exclusive.value) if exclusive.value is not None else None
|
|
760
|
+
wall = {
|
|
761
|
+
bucket: _sum_metrics(
|
|
762
|
+
[turn.wall_time_ns[bucket] for turn in turns],
|
|
763
|
+
metric_subject=metric_subject,
|
|
764
|
+
)
|
|
765
|
+
for bucket in WallBucket
|
|
766
|
+
}
|
|
767
|
+
utilization: dict[WallBucket, Metric] = {}
|
|
768
|
+
for bucket in (WallBucket.MODEL, WallBucket.TOOLS):
|
|
769
|
+
work_ns = 0
|
|
770
|
+
for turn in turns:
|
|
771
|
+
_check_cancelled(cancel_event)
|
|
772
|
+
work_ns += sum(
|
|
773
|
+
item.duration_ns for item in turn.slices if item.counts_as_work and item.wall_bucket is bucket
|
|
774
|
+
)
|
|
775
|
+
utilization[bucket] = Metric(
|
|
776
|
+
work_ns / elapsed_value
|
|
777
|
+
if elapsed_value is not None and elapsed_value > 0
|
|
778
|
+
else 0.0
|
|
779
|
+
if elapsed_value == 0
|
|
780
|
+
else None,
|
|
781
|
+
ratio_precision,
|
|
782
|
+
None if ratio_precision is Precision.EXACT else f"one or more {metric_subject} are unresolved",
|
|
783
|
+
)
|
|
784
|
+
return TrajectoryOverview(
|
|
785
|
+
elapsed_ns=elapsed,
|
|
786
|
+
compute_cp_ns=compute_cp,
|
|
787
|
+
response_cp_ns=response_cp,
|
|
788
|
+
exclusive_work_ns=exclusive,
|
|
789
|
+
parallelism=Metric(
|
|
790
|
+
exclusive_value / elapsed_value
|
|
791
|
+
if elapsed_value is not None and elapsed_value > 0 and exclusive_value is not None
|
|
792
|
+
else 0.0
|
|
793
|
+
if elapsed_value == 0 and exclusive_value is not None
|
|
794
|
+
else None,
|
|
795
|
+
ratio_precision,
|
|
796
|
+
None if ratio_precision is Precision.EXACT else f"one or more {metric_subject} are unresolved",
|
|
797
|
+
),
|
|
798
|
+
overlap_gain_ns=overlap,
|
|
799
|
+
wall_time_ns=wall,
|
|
800
|
+
utilization=utilization,
|
|
801
|
+
usage_tokens=usage,
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def _token_usage(
|
|
806
|
+
intermediate: _Intermediate,
|
|
807
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
808
|
+
turns: list[TurnAnalysis],
|
|
809
|
+
*,
|
|
810
|
+
cancel_event: Event | None,
|
|
811
|
+
) -> TokenUsage:
|
|
812
|
+
per_turn: dict[UsageBucket, list[Metric]] = {bucket: [] for bucket in UsageBucket}
|
|
813
|
+
for turn in turns:
|
|
814
|
+
_check_cancelled(cancel_event)
|
|
815
|
+
usage = turn.token_usage
|
|
816
|
+
if usage is None:
|
|
817
|
+
usage = _turns._turn_token_usage(intermediate, turn.turn_id, inactive_ranges, turn.usage_tokens)
|
|
818
|
+
for bucket in UsageBucket:
|
|
819
|
+
per_turn[bucket].append(usage.buckets[bucket])
|
|
820
|
+
buckets = {
|
|
821
|
+
UsageBucket.INPUT: _sum_metrics(per_turn[UsageBucket.INPUT], missing_is_missing=True),
|
|
822
|
+
UsageBucket.OUTPUT: _sum_metrics(per_turn[UsageBucket.OUTPUT], missing_is_missing=True),
|
|
823
|
+
}
|
|
824
|
+
for bucket in _turns._OPTIONAL_USAGE_BUCKETS:
|
|
825
|
+
buckets[bucket] = _sum_optional_bucket_metrics(per_turn[bucket])
|
|
826
|
+
return TokenUsage(buckets=buckets)
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def _fold_retry_turns(turns: list[TurnAnalysis], *, cancel_event: Event | None = None) -> list[TurnAnalysis]:
|
|
830
|
+
"""Fold consecutive retry/resume attempts into their one user-level turn.
|
|
831
|
+
|
|
832
|
+
Producers keep a fresh physical ``turn_id`` per pass so every lifecycle can
|
|
833
|
+
still be validated independently. ``turn_number`` is the user-level
|
|
834
|
+
ordinal, and ``is_retry`` explicitly says that a later pass continues the
|
|
835
|
+
preceding ordinal rather than opening another turn.
|
|
836
|
+
"""
|
|
837
|
+
groups: list[list[TurnAnalysis]] = []
|
|
838
|
+
for turn in turns:
|
|
839
|
+
_check_cancelled(cancel_event)
|
|
840
|
+
if (
|
|
841
|
+
turn.attempts[0].is_retry
|
|
842
|
+
and groups
|
|
843
|
+
and turn.turn_number is not None
|
|
844
|
+
and turn.turn_number == groups[-1][0].turn_number
|
|
845
|
+
):
|
|
846
|
+
groups[-1].append(turn)
|
|
847
|
+
else:
|
|
848
|
+
groups.append([turn])
|
|
849
|
+
return [_fold_turn_group(group, cancel_event=cancel_event) for group in groups]
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def _fold_turn_group(attempts: list[TurnAnalysis], *, cancel_event: Event | None) -> TurnAnalysis:
|
|
853
|
+
canonical = attempts[0]
|
|
854
|
+
if len(attempts) == 1:
|
|
855
|
+
attempt = attempts[0]
|
|
856
|
+
source = attempt.attempts[0]
|
|
857
|
+
diagnostics = list(attempt.diagnostics)
|
|
858
|
+
if source.physical_axis_end_ns < source.physical_axis_start_ns:
|
|
859
|
+
diagnostics.append("folded attempt axis end precedes its start")
|
|
860
|
+
normalized = replace(
|
|
861
|
+
source,
|
|
862
|
+
logical_axis_start_ns=attempt.axis_start_ns,
|
|
863
|
+
operation_start_index=0,
|
|
864
|
+
operation_end_index=len(attempt.operations),
|
|
865
|
+
slice_start_index=0,
|
|
866
|
+
slice_end_index=len(attempt.slices),
|
|
867
|
+
)
|
|
868
|
+
return replace(
|
|
869
|
+
attempt,
|
|
870
|
+
axis_end_ns=attempt.axis_start_ns + normalized.duration_ns,
|
|
871
|
+
diagnostics=tuple(dict.fromkeys(diagnostics)),
|
|
872
|
+
attempts=(normalized,),
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
overview = _overview(
|
|
876
|
+
attempts,
|
|
877
|
+
cancel_event=cancel_event,
|
|
878
|
+
metric_subject="attempts of this turn",
|
|
879
|
+
)
|
|
880
|
+
axis_start = canonical.axis_start_ns
|
|
881
|
+
cursor = axis_start
|
|
882
|
+
operations: list[TimelineOperation] = []
|
|
883
|
+
slices: list[TimeSlice] = []
|
|
884
|
+
attempt_refs: list[TurnAttemptRef] = []
|
|
885
|
+
fold_diagnostics: list[str] = []
|
|
886
|
+
parent_pairs = array("I")
|
|
887
|
+
causal_pairs = array("I")
|
|
888
|
+
root_index: int | None = None
|
|
889
|
+
flows_exact = all(attempt.flow is not None for attempt in attempts)
|
|
890
|
+
if len({attempt.runtime_id for attempt in attempts}) > 1:
|
|
891
|
+
fold_diagnostics.append("logical turn spans multiple trajectory runtimes")
|
|
892
|
+
for attempt_index, attempt in enumerate(attempts):
|
|
893
|
+
_check_cancelled(cancel_event)
|
|
894
|
+
source = attempt.attempts[0]
|
|
895
|
+
if len(attempt.attempts) != 1:
|
|
896
|
+
fold_diagnostics.append("fold input already contains multiple physical attempts")
|
|
897
|
+
if source.physical_axis_end_ns < source.physical_axis_start_ns:
|
|
898
|
+
fold_diagnostics.append("folded attempt axis end precedes its start")
|
|
899
|
+
shift = cursor - attempt.axis_start_ns
|
|
900
|
+
operation_offset = len(operations)
|
|
901
|
+
operations.extend(
|
|
902
|
+
replace(
|
|
903
|
+
operation,
|
|
904
|
+
start_ns=operation.start_ns + shift if operation.start_ns is not None else None,
|
|
905
|
+
end_ns=operation.end_ns + shift if operation.end_ns is not None else None,
|
|
906
|
+
)
|
|
907
|
+
for operation in attempt.operations
|
|
908
|
+
)
|
|
909
|
+
operation_end = len(operations)
|
|
910
|
+
slice_offset = len(slices)
|
|
911
|
+
for item in attempt.slices:
|
|
912
|
+
slices.append(
|
|
913
|
+
replace(
|
|
914
|
+
item,
|
|
915
|
+
slice_index=len(slices),
|
|
916
|
+
start_ns=item.start_ns + shift,
|
|
917
|
+
end_ns=item.end_ns + shift,
|
|
918
|
+
)
|
|
919
|
+
)
|
|
920
|
+
attempt_refs.append(
|
|
921
|
+
TurnAttemptRef(
|
|
922
|
+
turn_id=source.turn_id,
|
|
923
|
+
runtime_id=source.runtime_id,
|
|
924
|
+
is_retry=source.is_retry,
|
|
925
|
+
physical_axis_start_ns=source.physical_axis_start_ns,
|
|
926
|
+
physical_axis_end_ns=source.physical_axis_end_ns,
|
|
927
|
+
logical_axis_start_ns=cursor,
|
|
928
|
+
operation_start_index=operation_offset,
|
|
929
|
+
operation_end_index=operation_end,
|
|
930
|
+
slice_start_index=slice_offset,
|
|
931
|
+
slice_end_index=len(slices),
|
|
932
|
+
)
|
|
933
|
+
)
|
|
934
|
+
flow = attempt.flow
|
|
935
|
+
if flow is not None:
|
|
936
|
+
if root_index is None and flow.root_index is not None:
|
|
937
|
+
root_index = flow.root_index + operation_offset
|
|
938
|
+
# A cancelled/interrupted physical attempt may still have a typed
|
|
939
|
+
# response terminal. Only the final attempt owns the response of
|
|
940
|
+
# the stitched logical turn, so earlier terminal edges stop here.
|
|
941
|
+
for edge_source, target in flow.parent_edges():
|
|
942
|
+
if target == FLOW_TERMINAL_INDEX and attempt_index < len(attempts) - 1:
|
|
943
|
+
continue
|
|
944
|
+
# FLOW_TERMINAL_INDEX is producer-defined as a target-only
|
|
945
|
+
# response sentinel, so every source is a real operation index.
|
|
946
|
+
parent_pairs.extend(
|
|
947
|
+
(
|
|
948
|
+
edge_source + operation_offset,
|
|
949
|
+
FLOW_TERMINAL_INDEX if target == FLOW_TERMINAL_INDEX else target + operation_offset,
|
|
950
|
+
)
|
|
951
|
+
)
|
|
952
|
+
for edge_source, target in flow.causal_edges():
|
|
953
|
+
if target == FLOW_TERMINAL_INDEX and attempt_index < len(attempts) - 1:
|
|
954
|
+
continue
|
|
955
|
+
causal_pairs.extend(
|
|
956
|
+
(
|
|
957
|
+
edge_source + operation_offset,
|
|
958
|
+
FLOW_TERMINAL_INDEX if target == FLOW_TERMINAL_INDEX else target + operation_offset,
|
|
959
|
+
)
|
|
960
|
+
)
|
|
961
|
+
cursor += attempt_refs[-1].duration_ns
|
|
962
|
+
|
|
963
|
+
action_counts = {
|
|
964
|
+
action_class: _sum_metrics(
|
|
965
|
+
[attempt.action_counts.get(action_class, Metric(0, Precision.EXACT)) for attempt in attempts],
|
|
966
|
+
metric_subject="attempts of this turn",
|
|
967
|
+
)
|
|
968
|
+
for action_class in ActionClass
|
|
969
|
+
}
|
|
970
|
+
action_precision = least_precision(attempt.action_projection_precision for attempt in attempts)
|
|
971
|
+
action_reason = next(
|
|
972
|
+
(
|
|
973
|
+
attempt.action_projection_reason
|
|
974
|
+
for attempt in attempts
|
|
975
|
+
if attempt.action_projection_precision is not Precision.EXACT and attempt.action_projection_reason
|
|
976
|
+
),
|
|
977
|
+
None,
|
|
978
|
+
)
|
|
979
|
+
token_usages = [attempt.token_usage for attempt in attempts]
|
|
980
|
+
token_usage = (
|
|
981
|
+
_merge_turn_token_usage([usage for usage in token_usages if usage is not None])
|
|
982
|
+
if all(usage is not None for usage in token_usages)
|
|
983
|
+
else None
|
|
984
|
+
)
|
|
985
|
+
flow = (
|
|
986
|
+
TurnFlow(
|
|
987
|
+
turn_id=canonical.turn_id,
|
|
988
|
+
root_index=root_index,
|
|
989
|
+
has_terminal=bool(attempts[-1].flow and attempts[-1].flow.has_terminal),
|
|
990
|
+
parent_pairs=parent_pairs.tobytes(),
|
|
991
|
+
causal_pairs=causal_pairs.tobytes(),
|
|
992
|
+
acyclic=all(attempt.flow is not None and attempt.flow.acyclic for attempt in attempts),
|
|
993
|
+
)
|
|
994
|
+
if flows_exact
|
|
995
|
+
else None
|
|
996
|
+
)
|
|
997
|
+
return replace(
|
|
998
|
+
canonical,
|
|
999
|
+
end_sequence=attempts[-1].end_sequence,
|
|
1000
|
+
elapsed_ns=overview.elapsed_ns,
|
|
1001
|
+
compute_cp_ns=overview.compute_cp_ns,
|
|
1002
|
+
response_cp_ns=overview.response_cp_ns,
|
|
1003
|
+
exclusive_work_ns=overview.exclusive_work_ns,
|
|
1004
|
+
parallelism=overview.parallelism,
|
|
1005
|
+
overlap_gain_ns=overview.overlap_gain_ns,
|
|
1006
|
+
wall_time_ns=overview.wall_time_ns,
|
|
1007
|
+
utilization=overview.utilization,
|
|
1008
|
+
usage_tokens=overview.usage_tokens,
|
|
1009
|
+
axis_end_ns=cursor,
|
|
1010
|
+
operations=tuple(operations),
|
|
1011
|
+
slices=tuple(slices),
|
|
1012
|
+
diagnostics=tuple(
|
|
1013
|
+
dict.fromkeys(
|
|
1014
|
+
(
|
|
1015
|
+
*(diagnostic for attempt in attempts for diagnostic in attempt.diagnostics),
|
|
1016
|
+
*fold_diagnostics,
|
|
1017
|
+
)
|
|
1018
|
+
)
|
|
1019
|
+
),
|
|
1020
|
+
action_counts=action_counts,
|
|
1021
|
+
critical_tool_contributions_ns=dict(
|
|
1022
|
+
sum((Counter(attempt.critical_tool_contributions_ns) for attempt in attempts), Counter())
|
|
1023
|
+
),
|
|
1024
|
+
server_critical_contributions_ns=dict(
|
|
1025
|
+
sum((Counter(attempt.server_critical_contributions_ns) for attempt in attempts), Counter())
|
|
1026
|
+
),
|
|
1027
|
+
action_projection_precision=action_precision,
|
|
1028
|
+
action_projection_reason=action_reason,
|
|
1029
|
+
token_usage=token_usage,
|
|
1030
|
+
flow=flow,
|
|
1031
|
+
attempts=tuple(attempt_refs),
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def _merge_turn_token_usage(usages: list[TokenUsage]) -> TokenUsage:
|
|
1036
|
+
per_bucket = {bucket: [usage.buckets[bucket] for usage in usages] for bucket in UsageBucket}
|
|
1037
|
+
buckets = {
|
|
1038
|
+
UsageBucket.INPUT: _sum_metrics(
|
|
1039
|
+
per_bucket[UsageBucket.INPUT],
|
|
1040
|
+
missing_is_missing=True,
|
|
1041
|
+
metric_subject="attempts of this turn",
|
|
1042
|
+
),
|
|
1043
|
+
UsageBucket.OUTPUT: _sum_metrics(
|
|
1044
|
+
per_bucket[UsageBucket.OUTPUT],
|
|
1045
|
+
missing_is_missing=True,
|
|
1046
|
+
metric_subject="attempts of this turn",
|
|
1047
|
+
),
|
|
1048
|
+
}
|
|
1049
|
+
for bucket in _turns._OPTIONAL_USAGE_BUCKETS:
|
|
1050
|
+
buckets[bucket] = _sum_optional_bucket_metrics(
|
|
1051
|
+
per_bucket[bucket],
|
|
1052
|
+
metric_subject="attempts of this turn",
|
|
1053
|
+
)
|
|
1054
|
+
return TokenUsage(buckets=buckets)
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def _retry_amplification(
|
|
1058
|
+
intermediate: _Intermediate,
|
|
1059
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
1060
|
+
revisions: _RevisionResolution,
|
|
1061
|
+
*,
|
|
1062
|
+
cancel_event: Event | None,
|
|
1063
|
+
) -> tuple[Metric, tuple[str, ...], tuple[str | None, str | None, str | None]]:
|
|
1064
|
+
attribution_exact = True
|
|
1065
|
+
retry_ids: set[str] = set()
|
|
1066
|
+
for node in intermediate.nodes.get("retry", {}).values():
|
|
1067
|
+
_check_cancelled(cancel_event)
|
|
1068
|
+
if _all_side_call_lifecycle(node):
|
|
1069
|
+
continue
|
|
1070
|
+
cut = _lifecycle_cut(node, inactive_ranges)
|
|
1071
|
+
if cut is not _LifecycleCut.NONE:
|
|
1072
|
+
ownership = _node_owner_membership(
|
|
1073
|
+
intermediate,
|
|
1074
|
+
node,
|
|
1075
|
+
inactive_ranges,
|
|
1076
|
+
target_operation_id=node.starts[0].parent_operation_id,
|
|
1077
|
+
)
|
|
1078
|
+
if not _exclusively_inactive(ownership):
|
|
1079
|
+
attribution_exact = False
|
|
1080
|
+
continue
|
|
1081
|
+
starts = [event for event in node.starts if _active(event.sequence, inactive_ranges)]
|
|
1082
|
+
finishes = [event for event in node.finishes if _active(event.sequence, inactive_ranges)]
|
|
1083
|
+
endpoints = (*starts, *finishes)
|
|
1084
|
+
if not endpoints or all(event.side_call for event in endpoints):
|
|
1085
|
+
continue
|
|
1086
|
+
if any(event.side_call for event in endpoints):
|
|
1087
|
+
attribution_exact = False
|
|
1088
|
+
continue
|
|
1089
|
+
if len(starts) == 1 and not finishes:
|
|
1090
|
+
# A scheduled retry whose backoff never starts adds no retry usage.
|
|
1091
|
+
continue
|
|
1092
|
+
if (
|
|
1093
|
+
len(starts) != 1
|
|
1094
|
+
or len(finishes) != 1
|
|
1095
|
+
or starts[0].scope != finishes[0].scope
|
|
1096
|
+
or finishes[0].sequence <= starts[0].sequence
|
|
1097
|
+
):
|
|
1098
|
+
attribution_exact = False
|
|
1099
|
+
continue
|
|
1100
|
+
retry_ids.add(node.operation_id)
|
|
1101
|
+
if not retry_ids:
|
|
1102
|
+
metric = (
|
|
1103
|
+
Metric(0, Precision.EXACT)
|
|
1104
|
+
if attribution_exact
|
|
1105
|
+
else Metric(None, Precision.UNRESOLVED, "retry lifecycle attribution is incomplete")
|
|
1106
|
+
)
|
|
1107
|
+
return metric, (), (None, None, None)
|
|
1108
|
+
parent_by_operation: dict[str, str | None] = {}
|
|
1109
|
+
for family in ("model.run", "model.cycle", "model.exchange"):
|
|
1110
|
+
for node in intermediate.nodes.get(family, {}).values():
|
|
1111
|
+
_check_cancelled(cancel_event)
|
|
1112
|
+
if _all_side_call_lifecycle(node):
|
|
1113
|
+
continue
|
|
1114
|
+
cut = _lifecycle_cut(node, inactive_ranges)
|
|
1115
|
+
if cut is not _LifecycleCut.NONE:
|
|
1116
|
+
ownership = _node_owner_membership(
|
|
1117
|
+
intermediate,
|
|
1118
|
+
node,
|
|
1119
|
+
inactive_ranges,
|
|
1120
|
+
target_operation_id=node.starts[0].parent_operation_id,
|
|
1121
|
+
)
|
|
1122
|
+
if not _exclusively_inactive(ownership):
|
|
1123
|
+
attribution_exact = False
|
|
1124
|
+
continue
|
|
1125
|
+
starts = [event for event in node.starts if _active(event.sequence, inactive_ranges)]
|
|
1126
|
+
finishes = [event for event in node.finishes if _active(event.sequence, inactive_ranges)]
|
|
1127
|
+
endpoints = (*starts, *finishes)
|
|
1128
|
+
if not endpoints or all(event.side_call for event in endpoints):
|
|
1129
|
+
continue
|
|
1130
|
+
if any(event.side_call for event in endpoints) or len(starts) != 1:
|
|
1131
|
+
attribution_exact = False
|
|
1132
|
+
else:
|
|
1133
|
+
parent_by_operation[node.operation_id] = starts[0].parent_operation_id
|
|
1134
|
+
|
|
1135
|
+
def retried(operation_id: str | None) -> bool:
|
|
1136
|
+
current = operation_id
|
|
1137
|
+
visited: set[str] = set()
|
|
1138
|
+
while current is not None and current not in visited:
|
|
1139
|
+
_check_cancelled(cancel_event)
|
|
1140
|
+
if current in retry_ids:
|
|
1141
|
+
return True
|
|
1142
|
+
visited.add(current)
|
|
1143
|
+
current = parent_by_operation.get(current)
|
|
1144
|
+
return False
|
|
1145
|
+
|
|
1146
|
+
retried_exchanges: dict[int, tuple[str, _Endpoint]] = {}
|
|
1147
|
+
for node in intermediate.nodes.get("model.exchange", {}).values():
|
|
1148
|
+
_check_cancelled(cancel_event)
|
|
1149
|
+
if _all_side_call_lifecycle(node):
|
|
1150
|
+
continue
|
|
1151
|
+
if not retried(node.operation_id):
|
|
1152
|
+
continue
|
|
1153
|
+
cut = _lifecycle_cut(node, inactive_ranges)
|
|
1154
|
+
if cut is not _LifecycleCut.NONE:
|
|
1155
|
+
ownership = _node_owner_membership(
|
|
1156
|
+
intermediate,
|
|
1157
|
+
node,
|
|
1158
|
+
inactive_ranges,
|
|
1159
|
+
target_operation_id=node.starts[0].parent_operation_id,
|
|
1160
|
+
)
|
|
1161
|
+
if not _exclusively_inactive(ownership):
|
|
1162
|
+
attribution_exact = False
|
|
1163
|
+
continue
|
|
1164
|
+
starts = [event for event in node.starts if _active(event.sequence, inactive_ranges)]
|
|
1165
|
+
finishes = [event for event in node.finishes if _active(event.sequence, inactive_ranges)]
|
|
1166
|
+
endpoints = (*starts, *finishes)
|
|
1167
|
+
if endpoints and all(event.side_call for event in endpoints):
|
|
1168
|
+
continue
|
|
1169
|
+
if (
|
|
1170
|
+
any(event.side_call for event in endpoints)
|
|
1171
|
+
or len(starts) != 1
|
|
1172
|
+
or len(finishes) != 1
|
|
1173
|
+
or starts[0].scope != finishes[0].scope
|
|
1174
|
+
or finishes[0].sequence <= starts[0].sequence
|
|
1175
|
+
):
|
|
1176
|
+
attribution_exact = False
|
|
1177
|
+
else:
|
|
1178
|
+
retried_exchanges[finishes[0].sequence] = (node.operation_id, starts[0])
|
|
1179
|
+
amplified = [
|
|
1180
|
+
usage
|
|
1181
|
+
for usages in intermediate.usage_by_turn.values()
|
|
1182
|
+
for usage in usages
|
|
1183
|
+
if _active(usage.sequence, inactive_ranges) and usage.sequence in retried_exchanges
|
|
1184
|
+
]
|
|
1185
|
+
if not amplified:
|
|
1186
|
+
metric = (
|
|
1187
|
+
Metric(0, Precision.EXACT)
|
|
1188
|
+
if attribution_exact
|
|
1189
|
+
else Metric(None, Precision.UNRESOLVED, "retry exchange attribution is incomplete")
|
|
1190
|
+
)
|
|
1191
|
+
return metric, (), (None, None, None)
|
|
1192
|
+
missing = any(
|
|
1193
|
+
usage.normalization_unavailable or usage.input_total is None or usage.output_total is None
|
|
1194
|
+
for usage in amplified
|
|
1195
|
+
)
|
|
1196
|
+
value = sum((usage.input_total or 0) + (usage.output_total or 0) for usage in amplified)
|
|
1197
|
+
provenance_exact = all(
|
|
1198
|
+
usage.bucket_has_provider_provenance(UsageBucket.INPUT)
|
|
1199
|
+
and usage.bucket_has_provider_provenance(UsageBucket.OUTPUT)
|
|
1200
|
+
for usage in amplified
|
|
1201
|
+
)
|
|
1202
|
+
precision = (
|
|
1203
|
+
Precision.MISSING
|
|
1204
|
+
if missing
|
|
1205
|
+
else Precision.EXACT
|
|
1206
|
+
if provenance_exact and attribution_exact
|
|
1207
|
+
else Precision.UNRESOLVED
|
|
1208
|
+
)
|
|
1209
|
+
reason = (
|
|
1210
|
+
"normalized usage is missing for one or more retried exchanges"
|
|
1211
|
+
if missing
|
|
1212
|
+
else None
|
|
1213
|
+
if provenance_exact and attribution_exact
|
|
1214
|
+
else "retry exchange attribution is incomplete"
|
|
1215
|
+
if not attribution_exact
|
|
1216
|
+
else "normalized retry usage provenance is incomplete"
|
|
1217
|
+
)
|
|
1218
|
+
stable_items: set[str] = set()
|
|
1219
|
+
target: tuple[str | None, str | None, str | None] = (None, None, None)
|
|
1220
|
+
for usage in amplified:
|
|
1221
|
+
_check_cancelled(cancel_event)
|
|
1222
|
+
operation_id, start = retried_exchanges[usage.sequence]
|
|
1223
|
+
revision_id = _payload_str(start.payload, "context_revision_id")
|
|
1224
|
+
stable_items.update(revisions.memberships.get(revision_id or "", ()))
|
|
1225
|
+
if target == (None, None, None):
|
|
1226
|
+
target = (start.turn_id, operation_id, str(usage.sequence))
|
|
1227
|
+
return Metric(value if not missing else None, precision, reason), tuple(sorted(stable_items)), target
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
def _all_side_call_lifecycle(node: _Node) -> bool:
|
|
1231
|
+
"""Whether every raw endpoint belongs to a non-main actor."""
|
|
1232
|
+
endpoints = (*node.starts, *node.finishes)
|
|
1233
|
+
return bool(endpoints) and all(endpoint.side_call for endpoint in endpoints)
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
# The vocabulary this build's writer can emit; an outcome outside it is
|
|
1237
|
+
# damaged or from a different writer and cannot pass for read evidence.
|
|
1238
|
+
_RECOGNIZED_PREPARATION_OUTCOMES: Final = frozenset(
|
|
1239
|
+
value for name, value in vars(PreparationOutcome).items() if not name.startswith("_") and isinstance(value, str)
|
|
1240
|
+
)
|
|
1241
|
+
_PROMOTED_PREPARATION_OUTCOMES: Final = frozenset({PreparationOutcome.FRESH_TURN, PreparationOutcome.RETRY_TURN})
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
def _submission_latency(
|
|
1245
|
+
intermediate: _Intermediate,
|
|
1246
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
1247
|
+
*,
|
|
1248
|
+
cancel_event: Event | None,
|
|
1249
|
+
) -> SubmissionLatencyOverview:
|
|
1250
|
+
turn_by_scope: dict[str, list[_Endpoint]] = defaultdict(list)
|
|
1251
|
+
turn_scope_membership: dict[str, tuple[bool, bool]] = {}
|
|
1252
|
+
for turn in intermediate.turns.values():
|
|
1253
|
+
_check_cancelled(cancel_event)
|
|
1254
|
+
for start in turn.starts:
|
|
1255
|
+
scope_id = _payload_str(start.payload, "preparation_scope_operation_id")
|
|
1256
|
+
if scope_id is None:
|
|
1257
|
+
continue
|
|
1258
|
+
active = _active(start.sequence, inactive_ranges)
|
|
1259
|
+
turn_scope_membership[scope_id] = _merge_projection_memberships(
|
|
1260
|
+
turn_scope_membership.get(scope_id, (False, False)),
|
|
1261
|
+
(active, not active),
|
|
1262
|
+
)
|
|
1263
|
+
if active:
|
|
1264
|
+
turn_by_scope[scope_id].append(start)
|
|
1265
|
+
samples: list[SubmissionLatencySample] = []
|
|
1266
|
+
for node in intermediate.nodes.get("preparation", {}).values():
|
|
1267
|
+
_check_cancelled(cancel_event)
|
|
1268
|
+
starts = [event for event in node.starts if _active(event.sequence, inactive_ranges)]
|
|
1269
|
+
finishes = [event for event in node.finishes if _active(event.sequence, inactive_ranges)]
|
|
1270
|
+
endpoints = sorted((*starts, *finishes), key=lambda endpoint: endpoint.sequence)
|
|
1271
|
+
if not endpoints or _payload_str(endpoints[0].payload, "scope") != "pre_turn":
|
|
1272
|
+
continue
|
|
1273
|
+
if all(endpoint.side_call for endpoint in endpoints):
|
|
1274
|
+
continue
|
|
1275
|
+
if _drop_submission_lifecycle_cut(intermediate, node, inactive_ranges, turn_scope_membership):
|
|
1276
|
+
# The raw lifecycle is complete, but rollback cut its projection
|
|
1277
|
+
# away from every active owner. Its surviving endpoint is not a
|
|
1278
|
+
# live submission sample; ownership ambiguity remains diagnosable.
|
|
1279
|
+
continue
|
|
1280
|
+
start = starts[0] if len(starts) == 1 else None
|
|
1281
|
+
finish = finishes[0] if len(finishes) == 1 else None
|
|
1282
|
+
outcome = _payload_str(finish.payload, "outcome") if finish is not None else None
|
|
1283
|
+
bucket = _submission_bucket(outcome)
|
|
1284
|
+
turn_candidates = turn_by_scope.get(node.operation_id, ())
|
|
1285
|
+
if outcome in {"fresh_turn", "retry_turn"}:
|
|
1286
|
+
end = turn_candidates[0] if len(turn_candidates) == 1 else None
|
|
1287
|
+
turn_id = (
|
|
1288
|
+
end.turn_id if end is not None else _payload_str(finish.payload, "target_turn_id") if finish else None
|
|
1289
|
+
)
|
|
1290
|
+
else:
|
|
1291
|
+
end = finish
|
|
1292
|
+
turn_id = _payload_str(finish.payload, "target_turn_id") if finish is not None else None
|
|
1293
|
+
exact = (
|
|
1294
|
+
start is not None
|
|
1295
|
+
and finish is not None
|
|
1296
|
+
and end is not None
|
|
1297
|
+
and outcome in _RECOGNIZED_PREPARATION_OUTCOMES
|
|
1298
|
+
and not start.side_call
|
|
1299
|
+
and finish.monotonic_measurement
|
|
1300
|
+
and finish.scope == start.scope
|
|
1301
|
+
and finish.sequence > start.sequence
|
|
1302
|
+
and finish.monotonic_ns >= start.monotonic_ns
|
|
1303
|
+
and end.runtime_id == start.runtime_id
|
|
1304
|
+
and end.branch_id == start.branch_id
|
|
1305
|
+
and end.coverage_id == start.coverage_id
|
|
1306
|
+
and end.actor_id == start.actor_id
|
|
1307
|
+
and not end.side_call
|
|
1308
|
+
and end.sequence > start.sequence
|
|
1309
|
+
and end.monotonic_ns >= start.monotonic_ns
|
|
1310
|
+
)
|
|
1311
|
+
reason = None
|
|
1312
|
+
if len(starts) != 1:
|
|
1313
|
+
reason = "preparation scope does not have exactly one start"
|
|
1314
|
+
elif len(finishes) != 1:
|
|
1315
|
+
reason = "preparation scope does not have exactly one finished event"
|
|
1316
|
+
elif outcome is None:
|
|
1317
|
+
reason = "preparation terminal outcome is missing"
|
|
1318
|
+
elif outcome not in _RECOGNIZED_PREPARATION_OUTCOMES:
|
|
1319
|
+
reason = "preparation terminal outcome is unrecognized"
|
|
1320
|
+
elif end is None:
|
|
1321
|
+
reason = "promoted turn start cannot be resolved uniquely"
|
|
1322
|
+
elif start is not None and start.side_call:
|
|
1323
|
+
reason = "preparation scope belongs to a side-call actor"
|
|
1324
|
+
elif finish is not None and not finish.monotonic_measurement:
|
|
1325
|
+
reason = "preparation duration lacks monotonic provenance"
|
|
1326
|
+
elif start is not None and finish is not None and finish.scope != start.scope:
|
|
1327
|
+
reason = "preparation endpoints cross scope"
|
|
1328
|
+
elif (
|
|
1329
|
+
start is not None
|
|
1330
|
+
and finish is not None
|
|
1331
|
+
and (finish.sequence <= start.sequence or finish.monotonic_ns < start.monotonic_ns)
|
|
1332
|
+
):
|
|
1333
|
+
reason = "preparation lifecycle endpoints are not ordered"
|
|
1334
|
+
elif (
|
|
1335
|
+
start is not None
|
|
1336
|
+
and end is not None
|
|
1337
|
+
and (
|
|
1338
|
+
end.runtime_id != start.runtime_id
|
|
1339
|
+
or end.branch_id != start.branch_id
|
|
1340
|
+
or end.coverage_id != start.coverage_id
|
|
1341
|
+
or end.actor_id != start.actor_id
|
|
1342
|
+
or end.side_call
|
|
1343
|
+
)
|
|
1344
|
+
):
|
|
1345
|
+
reason = "submission endpoints cross scope"
|
|
1346
|
+
elif end is not None and start is not None and end.sequence <= start.sequence:
|
|
1347
|
+
reason = "submission endpoints are not ordered"
|
|
1348
|
+
elif end is not None and start is not None and end.monotonic_ns < start.monotonic_ns:
|
|
1349
|
+
reason = "submission latency has a negative endpoint interval"
|
|
1350
|
+
start_ns = start.monotonic_ns if start is not None else None
|
|
1351
|
+
raw_end_ns = end.monotonic_ns if end is not None else None
|
|
1352
|
+
end_ns = raw_end_ns if start_ns is None or raw_end_ns is None or raw_end_ns >= start_ns else None
|
|
1353
|
+
samples.append(
|
|
1354
|
+
SubmissionLatencySample(
|
|
1355
|
+
scope_operation_id=node.operation_id,
|
|
1356
|
+
outcome=outcome,
|
|
1357
|
+
bucket=bucket,
|
|
1358
|
+
start_sequence=start.sequence if start is not None else endpoints[0].sequence,
|
|
1359
|
+
start_ns=start_ns,
|
|
1360
|
+
end_ns=end_ns,
|
|
1361
|
+
duration_ns=Metric(
|
|
1362
|
+
end_ns - start_ns if exact and end_ns is not None and start_ns is not None else None,
|
|
1363
|
+
Precision.EXACT if exact else Precision.UNRESOLVED,
|
|
1364
|
+
reason,
|
|
1365
|
+
),
|
|
1366
|
+
turn_id=turn_id,
|
|
1367
|
+
finished_count=len(finishes),
|
|
1368
|
+
)
|
|
1369
|
+
)
|
|
1370
|
+
ordered = tuple(sorted(samples, key=lambda sample: sample.start_sequence))
|
|
1371
|
+
stats: list[SubmissionLatencyStats] = []
|
|
1372
|
+
for bucket in SubmissionLatencyBucket:
|
|
1373
|
+
_check_cancelled(cancel_event)
|
|
1374
|
+
bucket_samples = tuple(sample for sample in ordered if sample.bucket is bucket)
|
|
1375
|
+
resolved = [
|
|
1376
|
+
cast("int", sample.duration_ns.value)
|
|
1377
|
+
for sample in bucket_samples
|
|
1378
|
+
if sample.duration_ns.precision is Precision.EXACT
|
|
1379
|
+
]
|
|
1380
|
+
stats.append(
|
|
1381
|
+
SubmissionLatencyStats(
|
|
1382
|
+
bucket=bucket,
|
|
1383
|
+
sample_count=len(bucket_samples),
|
|
1384
|
+
unresolved_count=len(bucket_samples) - len(resolved),
|
|
1385
|
+
p50_ns=_percentile_metric(resolved, 0.50),
|
|
1386
|
+
p90_ns=_percentile_metric(resolved, 0.90),
|
|
1387
|
+
max_ns=Metric(max(resolved), Precision.EXACT)
|
|
1388
|
+
if resolved
|
|
1389
|
+
else Metric(None, Precision.MISSING, "no resolved samples"),
|
|
1390
|
+
samples=bucket_samples,
|
|
1391
|
+
)
|
|
1392
|
+
)
|
|
1393
|
+
return SubmissionLatencyOverview(tuple(stats))
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
def _drop_submission_lifecycle_cut(
|
|
1397
|
+
intermediate: _Intermediate,
|
|
1398
|
+
node: _Node,
|
|
1399
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
1400
|
+
turn_scope_membership: dict[str, tuple[bool, bool]],
|
|
1401
|
+
) -> bool:
|
|
1402
|
+
"""Whether a complete cut lifecycle has no active submission owner."""
|
|
1403
|
+
if _lifecycle_cut(node, inactive_ranges) is _LifecycleCut.NONE:
|
|
1404
|
+
return False
|
|
1405
|
+
raw_finish = node.finishes[0]
|
|
1406
|
+
outcome = _payload_str(raw_finish.payload, "outcome")
|
|
1407
|
+
if outcome not in _RECOGNIZED_PREPARATION_OUTCOMES:
|
|
1408
|
+
return False
|
|
1409
|
+
scope_membership = turn_scope_membership.get(node.operation_id, (False, False))
|
|
1410
|
+
target_turn_id = _payload_str(raw_finish.payload, "target_turn_id")
|
|
1411
|
+
target_membership = _turn_start_membership(
|
|
1412
|
+
intermediate,
|
|
1413
|
+
{target_turn_id} if target_turn_id is not None else set(),
|
|
1414
|
+
inactive_ranges,
|
|
1415
|
+
)
|
|
1416
|
+
ownership = _merge_projection_memberships(scope_membership, target_membership)
|
|
1417
|
+
if ownership[0]:
|
|
1418
|
+
return False
|
|
1419
|
+
if outcome in _PROMOTED_PREPARATION_OUTCOMES:
|
|
1420
|
+
# A promoted turn must claim the preparation scope. Absence is damaged
|
|
1421
|
+
# evidence rather than proof that the lifecycle was superseded.
|
|
1422
|
+
return _exclusively_inactive(scope_membership)
|
|
1423
|
+
if outcome == PreparationOutcome.INJECTED:
|
|
1424
|
+
# Successful injection terminals explicitly identify their target turn.
|
|
1425
|
+
return target_turn_id is not None and _exclusively_inactive(target_membership)
|
|
1426
|
+
# Rejected/cancelled/failed admission normally creates no turn claim. For
|
|
1427
|
+
# these derived samples, a complete cut with no active owner is superseded.
|
|
1428
|
+
return True
|
|
1429
|
+
|
|
1430
|
+
|
|
1431
|
+
def _submission_bucket(outcome: str | None) -> SubmissionLatencyBucket:
|
|
1432
|
+
if outcome in {"fresh_turn", "retry_turn"}:
|
|
1433
|
+
return SubmissionLatencyBucket.BECAME_TURN
|
|
1434
|
+
if outcome == "injected":
|
|
1435
|
+
return SubmissionLatencyBucket.INJECTED
|
|
1436
|
+
return SubmissionLatencyBucket.DID_NOT_BECOME_TURN
|
|
1437
|
+
|
|
1438
|
+
|
|
1439
|
+
def _degrade_overview(overview: TrajectoryOverview, reason: str) -> TrajectoryOverview:
|
|
1440
|
+
"""Cap selection-wide metrics without changing still-provable turn metrics."""
|
|
1441
|
+
|
|
1442
|
+
return TrajectoryOverview(
|
|
1443
|
+
elapsed_ns=_cap_session_metric(overview.elapsed_ns, reason),
|
|
1444
|
+
compute_cp_ns=_cap_session_metric(overview.compute_cp_ns, reason),
|
|
1445
|
+
response_cp_ns=_cap_session_metric(overview.response_cp_ns, reason),
|
|
1446
|
+
exclusive_work_ns=_cap_session_metric(overview.exclusive_work_ns, reason),
|
|
1447
|
+
parallelism=_cap_session_metric(overview.parallelism, reason),
|
|
1448
|
+
overlap_gain_ns=_cap_session_metric(overview.overlap_gain_ns, reason),
|
|
1449
|
+
wall_time_ns={bucket: _cap_session_metric(metric, reason) for bucket, metric in overview.wall_time_ns.items()},
|
|
1450
|
+
utilization={bucket: _cap_session_metric(metric, reason) for bucket, metric in overview.utilization.items()},
|
|
1451
|
+
usage_tokens=_cap_session_metric(overview.usage_tokens, reason),
|
|
1452
|
+
)
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
def _degrade_token_usage(usage: TokenUsage, reason: str) -> TokenUsage:
|
|
1456
|
+
"""Cap normalized session token totals when events may be missing."""
|
|
1457
|
+
|
|
1458
|
+
return TokenUsage(buckets={bucket: _cap_session_metric(metric, reason) for bucket, metric in usage.buckets.items()})
|
|
1459
|
+
|
|
1460
|
+
|
|
1461
|
+
def _degrade_submission_latency(latency: SubmissionLatencyOverview, reason: str) -> SubmissionLatencyOverview:
|
|
1462
|
+
"""Cap session percentile summaries without degrading intact samples."""
|
|
1463
|
+
|
|
1464
|
+
return SubmissionLatencyOverview(
|
|
1465
|
+
buckets=tuple(
|
|
1466
|
+
replace(
|
|
1467
|
+
stats,
|
|
1468
|
+
p50_ns=_cap_session_metric(stats.p50_ns, reason),
|
|
1469
|
+
p90_ns=_cap_session_metric(stats.p90_ns, reason),
|
|
1470
|
+
max_ns=_cap_session_metric(stats.max_ns, reason),
|
|
1471
|
+
)
|
|
1472
|
+
for stats in latency.buckets
|
|
1473
|
+
)
|
|
1474
|
+
)
|