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,1377 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Resolve one physical turn attempt into a ``TurnAnalysis``.
|
|
4
|
+
|
|
5
|
+
This module pairs lifecycles, checks coverage and runtime bounds, resolves the
|
|
6
|
+
turn tail, continuation polls and suspensions, computes token usage, and
|
|
7
|
+
assembles the attempt's metrics and their precision; counter-axis checks only
|
|
8
|
+
add diagnostics. The causal graph and time attribution live in
|
|
9
|
+
``_turn_graph``; context membership evidence lives in ``_context_evidence``.
|
|
10
|
+
Folding retry attempts into one logical turn is ``aggregation``'s job.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections import defaultdict
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from functools import lru_cache
|
|
19
|
+
from itertools import pairwise
|
|
20
|
+
from threading import Event
|
|
21
|
+
from typing import Final, cast
|
|
22
|
+
|
|
23
|
+
from chrys.foundation.trajectory.event_types import TurnEndReason
|
|
24
|
+
from chrys.service.analytics._context_evidence import _compaction_consumption_resolution, _RevisionResolution
|
|
25
|
+
from chrys.service.analytics._facts import (
|
|
26
|
+
_START_FAMILIES,
|
|
27
|
+
_active,
|
|
28
|
+
_Endpoint,
|
|
29
|
+
_ExchangeUsage,
|
|
30
|
+
_Intermediate,
|
|
31
|
+
_lifecycle_cut,
|
|
32
|
+
_LifecycleCut,
|
|
33
|
+
_Node,
|
|
34
|
+
_payload_bool,
|
|
35
|
+
_payload_int,
|
|
36
|
+
_payload_str,
|
|
37
|
+
_payload_value,
|
|
38
|
+
_Segment,
|
|
39
|
+
_Turn,
|
|
40
|
+
)
|
|
41
|
+
from chrys.service.analytics._timeline import (
|
|
42
|
+
_endpoint_in_coverage_runtime,
|
|
43
|
+
_hook_drain_scope,
|
|
44
|
+
_infrastructure_branch_reaches,
|
|
45
|
+
_materialize_timeline,
|
|
46
|
+
_resolved_timeline_operation,
|
|
47
|
+
_ResolvedNode,
|
|
48
|
+
_timeline_hook_id,
|
|
49
|
+
_timeline_identity,
|
|
50
|
+
_timeline_parent_operation,
|
|
51
|
+
_TimelineProjection,
|
|
52
|
+
)
|
|
53
|
+
from chrys.service.analytics._turn_graph import (
|
|
54
|
+
_critical_paths,
|
|
55
|
+
_failed_tool_cp_contributions,
|
|
56
|
+
_project_turn,
|
|
57
|
+
_required_causes_present,
|
|
58
|
+
_server_tool_cp_contributions,
|
|
59
|
+
_turn_flow,
|
|
60
|
+
_wall_partition,
|
|
61
|
+
)
|
|
62
|
+
from chrys.service.analytics.math import interval_length
|
|
63
|
+
from chrys.service.analytics.model import (
|
|
64
|
+
ContextSample,
|
|
65
|
+
Metric,
|
|
66
|
+
Precision,
|
|
67
|
+
TimelineDiagnosticCode,
|
|
68
|
+
TimelineOperation,
|
|
69
|
+
TimeSlice,
|
|
70
|
+
TokenUsage,
|
|
71
|
+
TokenUsageSample,
|
|
72
|
+
TurnAnalysis,
|
|
73
|
+
TurnAttemptRef,
|
|
74
|
+
UsageBucket,
|
|
75
|
+
WallBucket,
|
|
76
|
+
)
|
|
77
|
+
from chrys.service.analytics.reader import raise_if_cancelled as _check_cancelled
|
|
78
|
+
|
|
79
|
+
_WEIGHTED_FAMILIES: Final = frozenset(_START_FAMILIES.values()) | {"approval", "compaction.phase"}
|
|
80
|
+
_OPTIONAL_USAGE_BUCKETS: Final = (UsageBucket.REASONING, UsageBucket.CACHE_READ, UsageBucket.CACHE_CREATION)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(slots=True)
|
|
84
|
+
class _ResolutionCache:
|
|
85
|
+
"""Resolve-derived state retained for exactly one live fact index."""
|
|
86
|
+
|
|
87
|
+
counter_axis_diagnostics_by_turn: dict[str, tuple[str, ...]] = field(default_factory=dict)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _turn_token_usage(
|
|
91
|
+
intermediate: _Intermediate,
|
|
92
|
+
turn_id: str,
|
|
93
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
94
|
+
verdict: Metric,
|
|
95
|
+
) -> TokenUsage:
|
|
96
|
+
exchanges = [
|
|
97
|
+
item for item in intermediate.usage_by_turn.get(turn_id, ()) if _active(item.sequence, inactive_ranges)
|
|
98
|
+
]
|
|
99
|
+
return _interned_token_usage(tuple(_turn_bucket_usage(exchanges, bucket, verdict) for bucket in UsageBucket))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _turn_uses_session_carrier_fallback(
|
|
103
|
+
intermediate: _Intermediate,
|
|
104
|
+
turn_id: str,
|
|
105
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
106
|
+
*,
|
|
107
|
+
cancel_event: Event | None,
|
|
108
|
+
) -> bool:
|
|
109
|
+
for node in intermediate.nodes_by_turn.get(turn_id, ()):
|
|
110
|
+
_check_cancelled(cancel_event)
|
|
111
|
+
if node.family != "tool.operation":
|
|
112
|
+
continue
|
|
113
|
+
for finish in node.finishes:
|
|
114
|
+
if not _active(finish.sequence, inactive_ranges):
|
|
115
|
+
continue
|
|
116
|
+
if (
|
|
117
|
+
_payload_str(finish.payload, "result_item_id") is not None
|
|
118
|
+
and _payload_str(finish.payload, "result_carrier_item_id") is None
|
|
119
|
+
):
|
|
120
|
+
return True
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _resolve_turn(
|
|
125
|
+
intermediate: _Intermediate,
|
|
126
|
+
turn: _Turn,
|
|
127
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
128
|
+
*,
|
|
129
|
+
resolution_cache: _ResolutionCache,
|
|
130
|
+
revisions: _RevisionResolution,
|
|
131
|
+
mapped_carrier: Callable[[str], str | None],
|
|
132
|
+
rollback_projection_unresolved: bool,
|
|
133
|
+
refresh_counter_axis: bool,
|
|
134
|
+
cancel_event: Event | None,
|
|
135
|
+
) -> TurnAnalysis:
|
|
136
|
+
_check_cancelled(cancel_event)
|
|
137
|
+
diagnostics: list[str] = []
|
|
138
|
+
starts = [event for event in turn.starts if _active(event.sequence, inactive_ranges)]
|
|
139
|
+
finishes = [event for event in turn.finishes if _active(event.sequence, inactive_ranges)]
|
|
140
|
+
if len(starts) != 1:
|
|
141
|
+
return _empty_turn(
|
|
142
|
+
intermediate,
|
|
143
|
+
turn.turn_id,
|
|
144
|
+
starts,
|
|
145
|
+
"turn lifecycle is not uniquely opened",
|
|
146
|
+
inactive_ranges=inactive_ranges,
|
|
147
|
+
resolution_cache=resolution_cache,
|
|
148
|
+
revisions=revisions,
|
|
149
|
+
refresh=refresh_counter_axis,
|
|
150
|
+
cancel_event=cancel_event,
|
|
151
|
+
)
|
|
152
|
+
start = starts[0]
|
|
153
|
+
finish = finishes[0] if len(finishes) == 1 else None
|
|
154
|
+
turn_lifecycle_exact = True
|
|
155
|
+
if finish is None:
|
|
156
|
+
diagnostics.append("turn lifecycle has no unique terminal")
|
|
157
|
+
turn_lifecycle_exact = False
|
|
158
|
+
elif finish.scope != start.scope or finish.sequence <= start.sequence or finish.monotonic_ns < start.monotonic_ns:
|
|
159
|
+
diagnostics.append("turn interval has invalid monotonic endpoints")
|
|
160
|
+
turn_lifecycle_exact = False
|
|
161
|
+
tail_end, tail_exact, end_sequence, waited_hook_ids = _turn_tail(
|
|
162
|
+
intermediate, turn, start, finish, inactive_ranges, diagnostics
|
|
163
|
+
)
|
|
164
|
+
visible_end = tail_end or (finish.monotonic_ns if finish is not None else start.monotonic_ns)
|
|
165
|
+
turn_bounds = (start.monotonic_ns, max(start.monotonic_ns, visible_end))
|
|
166
|
+
counter_axis_diagnostics = _counter_axis_diagnostics(
|
|
167
|
+
intermediate,
|
|
168
|
+
turn.turn_id,
|
|
169
|
+
inactive_ranges,
|
|
170
|
+
resolution_cache=resolution_cache,
|
|
171
|
+
axis_start_ns=turn_bounds[0],
|
|
172
|
+
axis_end_ns=turn_bounds[1],
|
|
173
|
+
revisions=revisions,
|
|
174
|
+
refresh=refresh_counter_axis,
|
|
175
|
+
cancel_event=cancel_event,
|
|
176
|
+
)
|
|
177
|
+
integrity_diagnostics: list[str] = []
|
|
178
|
+
coverage_exact = _turn_coverage_exact(
|
|
179
|
+
intermediate,
|
|
180
|
+
start,
|
|
181
|
+
end_sequence,
|
|
182
|
+
inactive_ranges,
|
|
183
|
+
integrity_diagnostics,
|
|
184
|
+
rollback_projection_unresolved=rollback_projection_unresolved,
|
|
185
|
+
)
|
|
186
|
+
diagnostics.extend(integrity_diagnostics)
|
|
187
|
+
resolved_nodes, lifecycle_exact = _resolved_turn_nodes(intermediate, start, inactive_ranges, diagnostics)
|
|
188
|
+
closed_exchange_terminal_sequences = frozenset(
|
|
189
|
+
node.finish.sequence for node in resolved_nodes if node.family == "model.exchange"
|
|
190
|
+
)
|
|
191
|
+
resolved_nodes = [node for node in resolved_nodes if not node.start.side_call]
|
|
192
|
+
compaction_consumption = _compaction_consumption_resolution(
|
|
193
|
+
intermediate,
|
|
194
|
+
resolved_nodes,
|
|
195
|
+
inactive_ranges,
|
|
196
|
+
cancel_event=cancel_event,
|
|
197
|
+
diagnostics=diagnostics,
|
|
198
|
+
)
|
|
199
|
+
_check_cancelled(cancel_event)
|
|
200
|
+
resolved_nodes.extend(_continuation_poll_nodes(resolved_nodes))
|
|
201
|
+
suspension_nodes, suspension_deductions, suspension_exact = _suspension_nodes(
|
|
202
|
+
turn, resolved_nodes, start, finish, inactive_ranges, diagnostics
|
|
203
|
+
)
|
|
204
|
+
resolved_nodes.extend(suspension_nodes)
|
|
205
|
+
projection = _project_turn(
|
|
206
|
+
resolved_nodes,
|
|
207
|
+
turn_bounds,
|
|
208
|
+
start=start,
|
|
209
|
+
finish=finish,
|
|
210
|
+
tail_end=tail_end,
|
|
211
|
+
waited_hook_ids=waited_hook_ids,
|
|
212
|
+
suspension_deductions=suspension_deductions,
|
|
213
|
+
revisions=revisions,
|
|
214
|
+
compaction_consumption=compaction_consumption,
|
|
215
|
+
mapped_carrier=mapped_carrier,
|
|
216
|
+
cancel_event=cancel_event,
|
|
217
|
+
diagnostics=diagnostics,
|
|
218
|
+
)
|
|
219
|
+
wall_durations, idle_intervals = _wall_partition(turn_bounds, projection.slices, cancel_event=cancel_event)
|
|
220
|
+
idle_slices = tuple(
|
|
221
|
+
TimeSlice(
|
|
222
|
+
family="idle",
|
|
223
|
+
slice_index=index,
|
|
224
|
+
turn_id=turn.turn_id,
|
|
225
|
+
runtime_id=start.runtime_id,
|
|
226
|
+
operation_id=None,
|
|
227
|
+
owner="idle(unattributed)",
|
|
228
|
+
start_ns=idle_start,
|
|
229
|
+
end_ns=idle_end,
|
|
230
|
+
wall_bucket=WallBucket.IDLE,
|
|
231
|
+
counts_as_work=False,
|
|
232
|
+
compute_weight=False,
|
|
233
|
+
response_weight=True,
|
|
234
|
+
)
|
|
235
|
+
for index, (idle_start, idle_end) in enumerate(idle_intervals)
|
|
236
|
+
)
|
|
237
|
+
all_slices = tuple(
|
|
238
|
+
sorted((*projection.slices, *idle_slices), key=lambda item: (item.start_ns, item.end_ns, item.slice_id))
|
|
239
|
+
)
|
|
240
|
+
work_slices = [item for item in all_slices if item.counts_as_work]
|
|
241
|
+
exclusive_work = sum(item.duration_ns for item in work_slices)
|
|
242
|
+
work_union = interval_length((item.start_ns, item.end_ns) for item in work_slices)
|
|
243
|
+
overlap_gain = exclusive_work - work_union
|
|
244
|
+
elapsed = turn_bounds[1] - turn_bounds[0]
|
|
245
|
+
precision = (
|
|
246
|
+
Precision.EXACT
|
|
247
|
+
if coverage_exact
|
|
248
|
+
and turn_lifecycle_exact
|
|
249
|
+
and lifecycle_exact
|
|
250
|
+
and tail_exact
|
|
251
|
+
and projection.timeline_exact
|
|
252
|
+
and suspension_exact
|
|
253
|
+
else Precision.UNRESOLVED
|
|
254
|
+
)
|
|
255
|
+
causes_exact = _required_causes_present(resolved_nodes, diagnostics)
|
|
256
|
+
compute_cp_exact = (
|
|
257
|
+
coverage_exact
|
|
258
|
+
and turn_lifecycle_exact
|
|
259
|
+
and lifecycle_exact
|
|
260
|
+
and projection.timeline_exact
|
|
261
|
+
and projection.dag_exact
|
|
262
|
+
and causes_exact
|
|
263
|
+
)
|
|
264
|
+
response_cp_exact = compute_cp_exact and tail_exact and suspension_exact and projection.response_dependency_exact
|
|
265
|
+
critical_paths = _critical_paths(
|
|
266
|
+
resolved_nodes,
|
|
267
|
+
all_slices,
|
|
268
|
+
projection.dependency,
|
|
269
|
+
cancel_event=cancel_event,
|
|
270
|
+
)
|
|
271
|
+
if not critical_paths.compute_bounded:
|
|
272
|
+
diagnostics.append("compute critical-path candidate cap exceeded")
|
|
273
|
+
compute_cp_exact = False
|
|
274
|
+
if not critical_paths.response_bounded:
|
|
275
|
+
diagnostics.append("response critical-path candidate cap exceeded")
|
|
276
|
+
response_cp_exact = False
|
|
277
|
+
if not critical_paths.acyclic:
|
|
278
|
+
diagnostics.append("operation dependency graph contains a cycle")
|
|
279
|
+
compute_cp_exact = False
|
|
280
|
+
response_cp_exact = False
|
|
281
|
+
if not critical_paths.response_reachable:
|
|
282
|
+
diagnostics.append("terminal response is not reachable from the turn root through typed edges")
|
|
283
|
+
response_cp_exact = False
|
|
284
|
+
critical_tool_contributions = (
|
|
285
|
+
_failed_tool_cp_contributions(
|
|
286
|
+
resolved_nodes,
|
|
287
|
+
all_slices,
|
|
288
|
+
projection.dependency,
|
|
289
|
+
response_cp=critical_paths.response_ns,
|
|
290
|
+
cancel_event=cancel_event,
|
|
291
|
+
)
|
|
292
|
+
if response_cp_exact and critical_paths.response_ns is not None
|
|
293
|
+
else {}
|
|
294
|
+
)
|
|
295
|
+
server_critical_contributions = (
|
|
296
|
+
_server_tool_cp_contributions(
|
|
297
|
+
intermediate,
|
|
298
|
+
resolved_nodes,
|
|
299
|
+
all_slices,
|
|
300
|
+
projection.dependency,
|
|
301
|
+
response_cp=critical_paths.response_ns,
|
|
302
|
+
cancel_event=cancel_event,
|
|
303
|
+
)
|
|
304
|
+
if response_cp_exact and critical_paths.response_ns is not None
|
|
305
|
+
else {}
|
|
306
|
+
)
|
|
307
|
+
metric_reason = None if precision is Precision.EXACT else "; ".join(dict.fromkeys(diagnostics))
|
|
308
|
+
unresolved_reason = "; ".join(dict.fromkeys(diagnostics))
|
|
309
|
+
compute_cp_reason = None if compute_cp_exact else unresolved_reason
|
|
310
|
+
response_cp_reason = None if response_cp_exact else unresolved_reason
|
|
311
|
+
wall_metrics = {
|
|
312
|
+
bucket: Metric(value=value, precision=precision, reason=metric_reason)
|
|
313
|
+
for bucket, value in wall_durations.items()
|
|
314
|
+
}
|
|
315
|
+
utilization = {
|
|
316
|
+
bucket: Metric(
|
|
317
|
+
value=(
|
|
318
|
+
sum(item.duration_ns for item in work_slices if item.wall_bucket is bucket) / elapsed
|
|
319
|
+
if elapsed
|
|
320
|
+
else 0.0
|
|
321
|
+
),
|
|
322
|
+
precision=precision,
|
|
323
|
+
reason=metric_reason,
|
|
324
|
+
)
|
|
325
|
+
for bucket in (WallBucket.MODEL, WallBucket.TOOLS)
|
|
326
|
+
}
|
|
327
|
+
usage = _turn_usage(
|
|
328
|
+
intermediate,
|
|
329
|
+
turn.turn_id,
|
|
330
|
+
inactive_ranges,
|
|
331
|
+
integrity_exact=coverage_exact,
|
|
332
|
+
integrity_reason="; ".join(dict.fromkeys(integrity_diagnostics)),
|
|
333
|
+
turn_lifecycle_exact=turn_lifecycle_exact,
|
|
334
|
+
closed_exchange_terminal_sequences=closed_exchange_terminal_sequences,
|
|
335
|
+
cancel_event=cancel_event,
|
|
336
|
+
)
|
|
337
|
+
token_usage = _turn_token_usage(intermediate, turn.turn_id, inactive_ranges, usage)
|
|
338
|
+
turn_number = _payload_int(start.payload, "turn_number")
|
|
339
|
+
action_projection_diagnostics: list[str] = []
|
|
340
|
+
action_projection_exact = (
|
|
341
|
+
coverage_exact
|
|
342
|
+
and turn_lifecycle_exact
|
|
343
|
+
and _tool_action_projection_exact(
|
|
344
|
+
intermediate,
|
|
345
|
+
start,
|
|
346
|
+
finish.sequence if finish is not None else None,
|
|
347
|
+
inactive_ranges,
|
|
348
|
+
action_projection_diagnostics,
|
|
349
|
+
)
|
|
350
|
+
)
|
|
351
|
+
action_projection_reason = "; ".join(dict.fromkeys((*integrity_diagnostics, *action_projection_diagnostics)))
|
|
352
|
+
operations = _timeline_operations(
|
|
353
|
+
intermediate,
|
|
354
|
+
turn_id=turn.turn_id,
|
|
355
|
+
inactive_ranges=inactive_ranges,
|
|
356
|
+
resolved_nodes=resolved_nodes,
|
|
357
|
+
cancel_event=cancel_event,
|
|
358
|
+
)
|
|
359
|
+
flow = _turn_flow(turn.turn_id, projection.dependency, operations, acyclic=critical_paths.acyclic)
|
|
360
|
+
is_retry = _payload_bool(start.payload, "is_retry")
|
|
361
|
+
return TurnAnalysis(
|
|
362
|
+
turn_id=turn.turn_id,
|
|
363
|
+
turn_number=turn_number,
|
|
364
|
+
runtime_id=start.runtime_id,
|
|
365
|
+
start_sequence=start.sequence,
|
|
366
|
+
end_sequence=end_sequence,
|
|
367
|
+
elapsed_ns=Metric(elapsed, precision, metric_reason),
|
|
368
|
+
compute_cp_ns=Metric(
|
|
369
|
+
critical_paths.compute_ns if compute_cp_exact else None,
|
|
370
|
+
Precision.EXACT if compute_cp_exact else Precision.UNRESOLVED,
|
|
371
|
+
compute_cp_reason,
|
|
372
|
+
),
|
|
373
|
+
response_cp_ns=Metric(
|
|
374
|
+
critical_paths.response_ns if response_cp_exact else None,
|
|
375
|
+
Precision.EXACT if response_cp_exact else Precision.UNRESOLVED,
|
|
376
|
+
response_cp_reason,
|
|
377
|
+
),
|
|
378
|
+
exclusive_work_ns=Metric(exclusive_work, precision, metric_reason),
|
|
379
|
+
parallelism=Metric(exclusive_work / elapsed if elapsed else 0.0, precision, metric_reason),
|
|
380
|
+
overlap_gain_ns=Metric(overlap_gain, precision, metric_reason),
|
|
381
|
+
wall_time_ns=wall_metrics,
|
|
382
|
+
utilization=utilization,
|
|
383
|
+
usage_tokens=usage,
|
|
384
|
+
attempts=(
|
|
385
|
+
TurnAttemptRef(
|
|
386
|
+
turn_id=turn.turn_id,
|
|
387
|
+
runtime_id=start.runtime_id,
|
|
388
|
+
is_retry=is_retry,
|
|
389
|
+
physical_axis_start_ns=turn_bounds[0],
|
|
390
|
+
physical_axis_end_ns=turn_bounds[1],
|
|
391
|
+
logical_axis_start_ns=turn_bounds[0],
|
|
392
|
+
operation_start_index=0,
|
|
393
|
+
operation_end_index=len(operations),
|
|
394
|
+
slice_start_index=0,
|
|
395
|
+
slice_end_index=len(all_slices),
|
|
396
|
+
),
|
|
397
|
+
),
|
|
398
|
+
axis_start_ns=turn_bounds[0],
|
|
399
|
+
axis_end_ns=turn_bounds[1],
|
|
400
|
+
operations=operations,
|
|
401
|
+
slices=all_slices,
|
|
402
|
+
diagnostics=tuple(dict.fromkeys((*diagnostics, *counter_axis_diagnostics))),
|
|
403
|
+
critical_tool_contributions_ns=critical_tool_contributions,
|
|
404
|
+
server_critical_contributions_ns=server_critical_contributions,
|
|
405
|
+
action_projection_precision=Precision.EXACT if action_projection_exact else Precision.UNRESOLVED,
|
|
406
|
+
action_projection_reason=None if action_projection_exact else action_projection_reason,
|
|
407
|
+
token_usage=token_usage,
|
|
408
|
+
flow=flow,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _turn_tail(
|
|
413
|
+
intermediate: _Intermediate,
|
|
414
|
+
turn: _Turn,
|
|
415
|
+
start: _Endpoint,
|
|
416
|
+
finish: _Endpoint | None,
|
|
417
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
418
|
+
diagnostics: list[str],
|
|
419
|
+
) -> tuple[int | None, bool, int | None, frozenset[str] | None]:
|
|
420
|
+
if finish is None:
|
|
421
|
+
return None, False, None, None
|
|
422
|
+
if finish.scope != start.scope or finish.sequence <= start.sequence or finish.monotonic_ns < start.monotonic_ns:
|
|
423
|
+
return finish.monotonic_ns, False, finish.sequence, None
|
|
424
|
+
end_reason = _payload_str(finish.payload, "end_reason")
|
|
425
|
+
if end_reason in {TurnEndReason.PROCESS_EXIT, TurnEndReason.CANCELLED}:
|
|
426
|
+
return finish.monotonic_ns, True, finish.sequence, frozenset()
|
|
427
|
+
markers = [marker for marker in turn.response_markers if _active(marker.sequence, inactive_ranges)]
|
|
428
|
+
if len(markers) != 1:
|
|
429
|
+
diagnostics.append("turn response fence is missing or duplicated")
|
|
430
|
+
return finish.monotonic_ns, False, finish.sequence, None
|
|
431
|
+
marker = markers[0]
|
|
432
|
+
segment_end_sequence = max(
|
|
433
|
+
(
|
|
434
|
+
segment.sequence
|
|
435
|
+
for segment in intermediate.segments.get(marker.event_id or "", ())
|
|
436
|
+
if _active(segment.sequence, inactive_ranges)
|
|
437
|
+
),
|
|
438
|
+
default=marker.sequence,
|
|
439
|
+
)
|
|
440
|
+
if marker.scope != start.scope or marker.sequence <= finish.sequence or marker.monotonic_ns < finish.monotonic_ns:
|
|
441
|
+
diagnostics.append("turn response fence is outside the turn runtime or precedes turn.finished")
|
|
442
|
+
return finish.monotonic_ns, False, segment_end_sequence, None
|
|
443
|
+
waited_ids = _reassemble_waited_hook_ids(intermediate, marker, inactive_ranges)
|
|
444
|
+
expected = _payload_int(marker.payload, "waited_hook_operation_count")
|
|
445
|
+
if waited_ids is None or expected is None or len(waited_ids) != expected or len(set(waited_ids)) != expected:
|
|
446
|
+
diagnostics.append("turn response fence hook membership is incomplete")
|
|
447
|
+
return marker.monotonic_ns, False, segment_end_sequence, None
|
|
448
|
+
drained_scopes = _payload_value(marker.payload, "drained_scopes")
|
|
449
|
+
outcome = _payload_str(marker.payload, "outcome")
|
|
450
|
+
if (
|
|
451
|
+
not isinstance(drained_scopes, tuple)
|
|
452
|
+
or not all(isinstance(scope, str) for scope in drained_scopes)
|
|
453
|
+
or len(drained_scopes) != len(set(drained_scopes))
|
|
454
|
+
or any(scope != "turn" for scope in drained_scopes)
|
|
455
|
+
or outcome not in {"settled", "cancelled", "partial"}
|
|
456
|
+
or (outcome == "settled" and drained_scopes not in {(), ("turn",)})
|
|
457
|
+
or (waited_ids and "turn" not in drained_scopes)
|
|
458
|
+
):
|
|
459
|
+
diagnostics.append("turn response fence drained_scopes are inconsistent with its outcome or membership")
|
|
460
|
+
return marker.monotonic_ns, False, segment_end_sequence, None
|
|
461
|
+
hook_nodes = {
|
|
462
|
+
node.operation_id: node
|
|
463
|
+
for node in intermediate.nodes_by_turn.get(turn.turn_id, ())
|
|
464
|
+
if node.family == "hook.operation"
|
|
465
|
+
}
|
|
466
|
+
for operation_id in waited_ids:
|
|
467
|
+
node = hook_nodes.get(operation_id)
|
|
468
|
+
starts = [] if node is None else [event for event in node.starts if _active(event.sequence, inactive_ranges)]
|
|
469
|
+
terminals = (
|
|
470
|
+
[] if node is None else [event for event in node.finishes if _active(event.sequence, inactive_ranges)]
|
|
471
|
+
)
|
|
472
|
+
if (
|
|
473
|
+
len(starts) != 1
|
|
474
|
+
or len(terminals) != 1
|
|
475
|
+
or _payload_str(starts[0].payload, "execution_mode") != "async"
|
|
476
|
+
or _hook_drain_scope(starts[0]) != "turn"
|
|
477
|
+
or starts[0].scope != start.scope
|
|
478
|
+
or terminals[0].scope != start.scope
|
|
479
|
+
or starts[0].sequence <= start.sequence
|
|
480
|
+
or terminals[0].sequence <= starts[0].sequence
|
|
481
|
+
or terminals[0].sequence >= marker.sequence
|
|
482
|
+
or terminals[0].monotonic_ns < starts[0].monotonic_ns
|
|
483
|
+
or not _endpoint_in_coverage_runtime(intermediate, starts[0], inactive_ranges)
|
|
484
|
+
or not _endpoint_in_coverage_runtime(intermediate, terminals[0], inactive_ranges)
|
|
485
|
+
):
|
|
486
|
+
diagnostics.append("a waited hook is not a uniquely closed async turn-scope hook before the response fence")
|
|
487
|
+
return marker.monotonic_ns, False, segment_end_sequence, None
|
|
488
|
+
return marker.monotonic_ns, True, segment_end_sequence, frozenset(waited_ids)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _reassemble_waited_hook_ids(
|
|
492
|
+
intermediate: _Intermediate,
|
|
493
|
+
marker: _Endpoint,
|
|
494
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
495
|
+
) -> list[str] | None:
|
|
496
|
+
declarations = [
|
|
497
|
+
declaration
|
|
498
|
+
for declaration in marker.segmented_fields
|
|
499
|
+
if declaration.field_pointer == "/payload/waited_hook_operation_ids"
|
|
500
|
+
]
|
|
501
|
+
if len(declarations) != 1:
|
|
502
|
+
return None
|
|
503
|
+
declaration = declarations[0]
|
|
504
|
+
segments = [
|
|
505
|
+
segment
|
|
506
|
+
for segment in intermediate.segments.get(marker.event_id, [])
|
|
507
|
+
if _active(segment.sequence, inactive_ranges) and segment.field_pointer == declaration.field_pointer
|
|
508
|
+
]
|
|
509
|
+
if len(segments) != declaration.segment_count:
|
|
510
|
+
return None
|
|
511
|
+
indexed_segments: list[tuple[int, _Segment]] = []
|
|
512
|
+
for segment in segments:
|
|
513
|
+
segment_index = segment.segment_index
|
|
514
|
+
if segment_index is None:
|
|
515
|
+
return None
|
|
516
|
+
indexed_segments.append((segment_index, segment))
|
|
517
|
+
indices = [segment_index for segment_index, _ in indexed_segments]
|
|
518
|
+
if sorted(indices) != list(range(declaration.segment_count)):
|
|
519
|
+
return None
|
|
520
|
+
values: list[str] = []
|
|
521
|
+
for _, segment in sorted(indexed_segments):
|
|
522
|
+
if (
|
|
523
|
+
segment.segment_count != declaration.segment_count
|
|
524
|
+
or segment.segment_group_id != declaration.segment_group_id
|
|
525
|
+
or segment.encoding != "array_slice"
|
|
526
|
+
or segment.entry_oversized
|
|
527
|
+
or segment.runtime_id != marker.runtime_id
|
|
528
|
+
or segment.branch_id != marker.branch_id
|
|
529
|
+
or segment.coverage_id != marker.coverage_id
|
|
530
|
+
or segment.turn_id != marker.turn_id
|
|
531
|
+
or segment.sequence <= marker.sequence
|
|
532
|
+
):
|
|
533
|
+
return None
|
|
534
|
+
entries = segment.entries
|
|
535
|
+
if entries is None or not all(isinstance(item, str) for item in entries):
|
|
536
|
+
return None
|
|
537
|
+
values.extend(item for item in entries if isinstance(item, str))
|
|
538
|
+
return values
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _turn_coverage_exact(
|
|
542
|
+
intermediate: _Intermediate,
|
|
543
|
+
start: _Endpoint,
|
|
544
|
+
end_sequence: int | None,
|
|
545
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
546
|
+
diagnostics: list[str],
|
|
547
|
+
*,
|
|
548
|
+
rollback_projection_unresolved: bool,
|
|
549
|
+
) -> bool:
|
|
550
|
+
if end_sequence is None:
|
|
551
|
+
return False
|
|
552
|
+
exact = True
|
|
553
|
+
coverage_starts = [
|
|
554
|
+
event
|
|
555
|
+
for event in intermediate.coverage_starts.get(start.coverage_id, [])
|
|
556
|
+
if event.sequence <= start.sequence
|
|
557
|
+
and event.runtime_id == start.runtime_id
|
|
558
|
+
and _infrastructure_branch_reaches(intermediate, event, start, inactive_ranges)
|
|
559
|
+
]
|
|
560
|
+
if len(coverage_starts) != 1:
|
|
561
|
+
diagnostics.append("turn is not covered by trajectory.coverage.started")
|
|
562
|
+
exact = False
|
|
563
|
+
coverage_ends = [
|
|
564
|
+
event
|
|
565
|
+
for event in intermediate.coverage_ends.get(start.coverage_id, ())
|
|
566
|
+
if event.runtime_id == start.runtime_id
|
|
567
|
+
and _infrastructure_branch_reaches(intermediate, start, event, inactive_ranges)
|
|
568
|
+
]
|
|
569
|
+
if len(coverage_ends) > 1:
|
|
570
|
+
diagnostics.append("trajectory coverage has duplicate terminal markers")
|
|
571
|
+
exact = False
|
|
572
|
+
elif coverage_ends:
|
|
573
|
+
coverage_end = coverage_ends[0]
|
|
574
|
+
last_sequence = _payload_int(coverage_end.payload, "last_sequence")
|
|
575
|
+
if last_sequence != coverage_end.sequence - 1:
|
|
576
|
+
diagnostics.append("trajectory coverage terminal has an invalid last_sequence")
|
|
577
|
+
exact = False
|
|
578
|
+
elif start.sequence > last_sequence or end_sequence > last_sequence:
|
|
579
|
+
diagnostics.append("turn falls outside its trajectory coverage window")
|
|
580
|
+
exact = False
|
|
581
|
+
runtime_starts = [
|
|
582
|
+
event
|
|
583
|
+
for event in intermediate.runtime_starts.get(start.runtime_id, ())
|
|
584
|
+
if _infrastructure_branch_reaches(intermediate, event, start, inactive_ranges)
|
|
585
|
+
]
|
|
586
|
+
if len(runtime_starts) > 1:
|
|
587
|
+
diagnostics.append("trajectory runtime has duplicate start endpoints")
|
|
588
|
+
exact = False
|
|
589
|
+
elif runtime_starts and runtime_starts[0].sequence >= start.sequence:
|
|
590
|
+
diagnostics.append("turn begins before its trajectory runtime endpoint")
|
|
591
|
+
exact = False
|
|
592
|
+
runtime_recoveries = [
|
|
593
|
+
event
|
|
594
|
+
for event in intermediate.runtime_recoveries.get(start.runtime_id, ())
|
|
595
|
+
if _infrastructure_branch_reaches(intermediate, event, start, inactive_ranges)
|
|
596
|
+
]
|
|
597
|
+
if len(runtime_recoveries) > 1:
|
|
598
|
+
diagnostics.append("trajectory runtime has duplicate recovery endpoints")
|
|
599
|
+
exact = False
|
|
600
|
+
elif runtime_recoveries and runtime_recoveries[0].sequence >= start.sequence:
|
|
601
|
+
diagnostics.append("turn begins before its trajectory runtime recovery endpoint")
|
|
602
|
+
exact = False
|
|
603
|
+
runtime_finishes = [
|
|
604
|
+
event
|
|
605
|
+
for event in intermediate.runtime_finishes.get(start.runtime_id, ())
|
|
606
|
+
if _infrastructure_branch_reaches(intermediate, start, event, inactive_ranges)
|
|
607
|
+
]
|
|
608
|
+
if len(runtime_finishes) > 1:
|
|
609
|
+
diagnostics.append("trajectory runtime has duplicate terminal markers")
|
|
610
|
+
exact = False
|
|
611
|
+
elif runtime_finishes:
|
|
612
|
+
runtime_finish = runtime_finishes[0]
|
|
613
|
+
if start.sequence >= runtime_finish.sequence or end_sequence >= runtime_finish.sequence:
|
|
614
|
+
diagnostics.append("turn occurs after trajectory.runtime.finished")
|
|
615
|
+
exact = False
|
|
616
|
+
matching_coverage_end = coverage_ends[0] if len(coverage_ends) == 1 else None
|
|
617
|
+
if matching_coverage_end is None or matching_coverage_end.sequence >= runtime_finish.sequence:
|
|
618
|
+
diagnostics.append("trajectory runtime closure lacks an ordered coverage terminal")
|
|
619
|
+
exact = False
|
|
620
|
+
for first, last in intermediate.explicit_gaps:
|
|
621
|
+
if first <= end_sequence and last >= start.sequence:
|
|
622
|
+
diagnostics.append("trajectory gap intersects the turn")
|
|
623
|
+
exact = False
|
|
624
|
+
break
|
|
625
|
+
if any(start.sequence <= sequence <= end_sequence for sequence in intermediate.unsupported_sequences):
|
|
626
|
+
diagnostics.append("unsupported trajectory event intersects the turn")
|
|
627
|
+
exact = False
|
|
628
|
+
if any(start.sequence <= sequence + 1 <= end_sequence for sequence in intermediate.corrupt_after_sequences):
|
|
629
|
+
diagnostics.append("corrupt trajectory line intersects the turn")
|
|
630
|
+
exact = False
|
|
631
|
+
if any(violation.first_sequence <= end_sequence for violation in intermediate.prefix_violations):
|
|
632
|
+
diagnostics.append("trajectory accounted-prefix invariant failed")
|
|
633
|
+
exact = False
|
|
634
|
+
if rollback_projection_unresolved:
|
|
635
|
+
diagnostics.append("rollback live-history range is unresolved")
|
|
636
|
+
exact = False
|
|
637
|
+
return exact
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def _resolved_turn_nodes(
|
|
641
|
+
intermediate: _Intermediate,
|
|
642
|
+
turn_start: _Endpoint,
|
|
643
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
644
|
+
diagnostics: list[str],
|
|
645
|
+
) -> tuple[list[_ResolvedNode], bool]:
|
|
646
|
+
resolved: list[_ResolvedNode] = []
|
|
647
|
+
exact = True
|
|
648
|
+
turn_id = turn_start.turn_id or ""
|
|
649
|
+
for node in intermediate.nodes_by_turn.get(turn_id, ()):
|
|
650
|
+
starts = [
|
|
651
|
+
event for event in node.starts if event.turn_id == turn_id and _active(event.sequence, inactive_ranges)
|
|
652
|
+
]
|
|
653
|
+
finishes = [
|
|
654
|
+
event for event in node.finishes if event.turn_id == turn_id and _active(event.sequence, inactive_ranges)
|
|
655
|
+
]
|
|
656
|
+
if not starts and not finishes:
|
|
657
|
+
continue
|
|
658
|
+
endpoints = (*starts, *finishes)
|
|
659
|
+
if endpoints and all(event.side_call for event in endpoints):
|
|
660
|
+
# Side-call lifecycles stay out of the turn's arithmetic; the
|
|
661
|
+
# closed ones are kept only so their exchange terminals can vouch
|
|
662
|
+
# for the usage they contributed, and are dropped right after.
|
|
663
|
+
side_call_node = _resolved_side_call_node(intermediate, node, turn_start, starts, finishes, inactive_ranges)
|
|
664
|
+
if side_call_node is not None:
|
|
665
|
+
resolved.append(side_call_node)
|
|
666
|
+
continue
|
|
667
|
+
if any(event.side_call for event in endpoints):
|
|
668
|
+
diagnostics.append(f"{node.family} lifecycle mixes main and non-main actors")
|
|
669
|
+
exact = False
|
|
670
|
+
continue
|
|
671
|
+
cut = _lifecycle_cut(node, inactive_ranges)
|
|
672
|
+
if cut is not _LifecycleCut.NONE:
|
|
673
|
+
diagnostics.append(_lifecycle_cut_reason(node.family, cut))
|
|
674
|
+
exact = False
|
|
675
|
+
continue
|
|
676
|
+
if node.family == "retry" and len(starts) == 1 and not finishes:
|
|
677
|
+
# A lone scheduled marker is a cancelled backoff, not an open span.
|
|
678
|
+
continue
|
|
679
|
+
if len(starts) != 1 or len(finishes) != 1:
|
|
680
|
+
diagnostics.append(f"{node.family} lifecycle is not uniquely closed")
|
|
681
|
+
exact = False
|
|
682
|
+
continue
|
|
683
|
+
start, finish = starts[0], finishes[0]
|
|
684
|
+
if (
|
|
685
|
+
start.runtime_id != turn_start.runtime_id
|
|
686
|
+
or finish.runtime_id != turn_start.runtime_id
|
|
687
|
+
or start.branch_id != turn_start.branch_id
|
|
688
|
+
or finish.branch_id != turn_start.branch_id
|
|
689
|
+
or start.coverage_id != turn_start.coverage_id
|
|
690
|
+
or finish.coverage_id != turn_start.coverage_id
|
|
691
|
+
or start.sequence <= turn_start.sequence
|
|
692
|
+
or finish.sequence <= turn_start.sequence
|
|
693
|
+
):
|
|
694
|
+
diagnostics.append(f"{node.family} lifecycle does not belong to the owning turn runtime and branch")
|
|
695
|
+
exact = False
|
|
696
|
+
continue
|
|
697
|
+
if (
|
|
698
|
+
start.scope != finish.scope
|
|
699
|
+
or (node.family != "compaction.phase" and finish.sequence <= start.sequence)
|
|
700
|
+
or finish.monotonic_ns < start.monotonic_ns
|
|
701
|
+
):
|
|
702
|
+
diagnostics.append(f"{node.family} interval has invalid monotonic endpoints")
|
|
703
|
+
exact = False
|
|
704
|
+
continue
|
|
705
|
+
if not _endpoint_in_coverage_runtime(intermediate, start, inactive_ranges) or not _endpoint_in_coverage_runtime(
|
|
706
|
+
intermediate, finish, inactive_ranges
|
|
707
|
+
):
|
|
708
|
+
diagnostics.append(f"{node.family} lifecycle falls outside coverage or after runtime closure")
|
|
709
|
+
exact = False
|
|
710
|
+
continue
|
|
711
|
+
if node.family in _WEIGHTED_FAMILIES and not finish.monotonic_measurement:
|
|
712
|
+
diagnostics.append(f"{node.family} duration lacks monotonic provenance")
|
|
713
|
+
exact = False
|
|
714
|
+
resolved.append(
|
|
715
|
+
_ResolvedNode(
|
|
716
|
+
node_id=f"{node.family}:{node.operation_id}",
|
|
717
|
+
family=node.family,
|
|
718
|
+
operation_id=node.operation_id,
|
|
719
|
+
start=start,
|
|
720
|
+
finish=finish,
|
|
721
|
+
)
|
|
722
|
+
)
|
|
723
|
+
return resolved, exact
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def _resolved_side_call_node(
|
|
727
|
+
intermediate: _Intermediate,
|
|
728
|
+
node: _Node,
|
|
729
|
+
turn_start: _Endpoint,
|
|
730
|
+
starts: list[_Endpoint],
|
|
731
|
+
finishes: list[_Endpoint],
|
|
732
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
733
|
+
) -> _ResolvedNode | None:
|
|
734
|
+
"""Validate a side-call lifecycle in its own actor domain without entering main-turn metrics."""
|
|
735
|
+
if len(starts) != 1 or len(finishes) != 1:
|
|
736
|
+
return None
|
|
737
|
+
start, finish = starts[0], finishes[0]
|
|
738
|
+
if (
|
|
739
|
+
start.scope != finish.scope
|
|
740
|
+
or start.runtime_id != turn_start.runtime_id
|
|
741
|
+
or start.branch_id != turn_start.branch_id
|
|
742
|
+
or start.coverage_id != turn_start.coverage_id
|
|
743
|
+
or start.sequence <= turn_start.sequence
|
|
744
|
+
or finish.sequence <= start.sequence
|
|
745
|
+
or finish.monotonic_ns < start.monotonic_ns
|
|
746
|
+
or not _endpoint_in_coverage_runtime(intermediate, start, inactive_ranges)
|
|
747
|
+
or not _endpoint_in_coverage_runtime(intermediate, finish, inactive_ranges)
|
|
748
|
+
):
|
|
749
|
+
return None
|
|
750
|
+
return _ResolvedNode(
|
|
751
|
+
node_id=f"{node.family}:{node.operation_id}",
|
|
752
|
+
family=node.family,
|
|
753
|
+
operation_id=node.operation_id,
|
|
754
|
+
start=start,
|
|
755
|
+
finish=finish,
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def _tool_action_projection_exact(
|
|
760
|
+
intermediate: _Intermediate,
|
|
761
|
+
turn_start: _Endpoint,
|
|
762
|
+
terminal_sequence: int | None,
|
|
763
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
764
|
+
diagnostics: list[str],
|
|
765
|
+
) -> bool:
|
|
766
|
+
exact = True
|
|
767
|
+
turn_id = turn_start.turn_id or ""
|
|
768
|
+
for node in intermediate.nodes_by_turn.get(turn_id, ()):
|
|
769
|
+
if node.family != "tool.operation":
|
|
770
|
+
continue
|
|
771
|
+
starts = [event for event in node.starts if _active(event.sequence, inactive_ranges)]
|
|
772
|
+
finishes = [event for event in node.finishes if _active(event.sequence, inactive_ranges)]
|
|
773
|
+
if not starts and not finishes:
|
|
774
|
+
continue
|
|
775
|
+
endpoints = (*starts, *finishes)
|
|
776
|
+
if endpoints and all(event.side_call for event in endpoints):
|
|
777
|
+
continue
|
|
778
|
+
if any(event.side_call for event in endpoints):
|
|
779
|
+
diagnostics.append("tool action projection mixes main and non-main actors")
|
|
780
|
+
exact = False
|
|
781
|
+
continue
|
|
782
|
+
cut = _lifecycle_cut(node, inactive_ranges)
|
|
783
|
+
if cut is not _LifecycleCut.NONE:
|
|
784
|
+
diagnostics.append(_lifecycle_cut_reason("tool action", cut))
|
|
785
|
+
exact = False
|
|
786
|
+
continue
|
|
787
|
+
if len(starts) != 1:
|
|
788
|
+
diagnostics.append("tool action projection lacks a unique start event")
|
|
789
|
+
exact = False
|
|
790
|
+
continue
|
|
791
|
+
start = starts[0]
|
|
792
|
+
if (
|
|
793
|
+
start.runtime_id != turn_start.runtime_id
|
|
794
|
+
or start.branch_id != turn_start.branch_id
|
|
795
|
+
or start.coverage_id != turn_start.coverage_id
|
|
796
|
+
or start.turn_id != turn_start.turn_id
|
|
797
|
+
or start.sequence <= turn_start.sequence
|
|
798
|
+
):
|
|
799
|
+
diagnostics.append("tool action start does not belong to the owning turn scope")
|
|
800
|
+
exact = False
|
|
801
|
+
continue
|
|
802
|
+
# The scope check above bounds the start from below; tool activity
|
|
803
|
+
# past the turn terminal is just as unprovable as activity before the
|
|
804
|
+
# turn opened. The bound is ``turn.finished`` itself, not the tail's
|
|
805
|
+
# response fence — the fence settles after the turn closes and no
|
|
806
|
+
# tool may run in between. Terminals count too: an outcome read from
|
|
807
|
+
# a finish beyond the turn is no better founded than a stray start.
|
|
808
|
+
# An open turn has no terminal yet, and is already inexact through
|
|
809
|
+
# its lifecycle.
|
|
810
|
+
if terminal_sequence is not None and any(event.sequence > terminal_sequence for event in (start, *finishes)):
|
|
811
|
+
diagnostics.append("tool action endpoint lies beyond the turn terminal")
|
|
812
|
+
exact = False
|
|
813
|
+
return exact
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def _timeline_operations(
|
|
817
|
+
intermediate: _Intermediate,
|
|
818
|
+
*,
|
|
819
|
+
turn_id: str,
|
|
820
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
821
|
+
resolved_nodes: list[_ResolvedNode],
|
|
822
|
+
cancel_event: Event | None,
|
|
823
|
+
) -> tuple[TimelineOperation, ...]:
|
|
824
|
+
"""Project lifecycle nodes for display without deriving metric values from them."""
|
|
825
|
+
rows = [_resolved_timeline_operation(node) for node in resolved_nodes]
|
|
826
|
+
resolved_ids = {node.node_id for node in resolved_nodes}
|
|
827
|
+
for node in intermediate.nodes_by_turn.get(turn_id, ()):
|
|
828
|
+
_check_cancelled(cancel_event)
|
|
829
|
+
node_id = f"{node.family}:{node.operation_id}"
|
|
830
|
+
if node_id in resolved_ids:
|
|
831
|
+
continue
|
|
832
|
+
starts = [
|
|
833
|
+
event for event in node.starts if event.turn_id == turn_id and _active(event.sequence, inactive_ranges)
|
|
834
|
+
]
|
|
835
|
+
finishes = [
|
|
836
|
+
event for event in node.finishes if event.turn_id == turn_id and _active(event.sequence, inactive_ranges)
|
|
837
|
+
]
|
|
838
|
+
endpoints = sorted((*starts, *finishes), key=lambda endpoint: endpoint.sequence)
|
|
839
|
+
if not endpoints:
|
|
840
|
+
continue
|
|
841
|
+
if all(endpoint.side_call for endpoint in endpoints):
|
|
842
|
+
continue
|
|
843
|
+
cut = _lifecycle_cut(node, inactive_ranges)
|
|
844
|
+
identity_endpoint = starts[0] if starts else endpoints[0]
|
|
845
|
+
identity_source = (
|
|
846
|
+
node.starts[0] if cut is _LifecycleCut.FINISH_SURVIVES and len(node.starts) == 1 else identity_endpoint
|
|
847
|
+
)
|
|
848
|
+
if cut is not _LifecycleCut.NONE:
|
|
849
|
+
reason = _lifecycle_cut_reason(node.family, cut)
|
|
850
|
+
diagnostic_code = _lifecycle_cut_diagnostic_code(cut)
|
|
851
|
+
else:
|
|
852
|
+
reason, diagnostic_code = _unresolved_operation_reason(node.family, starts, finishes)
|
|
853
|
+
rows.append(
|
|
854
|
+
_TimelineProjection(
|
|
855
|
+
operation_id=node.operation_id,
|
|
856
|
+
parent_operation_id=_timeline_parent_operation(node.family, identity_endpoint),
|
|
857
|
+
start_sequence=endpoints[0].sequence,
|
|
858
|
+
family=node.family,
|
|
859
|
+
start_ns=None,
|
|
860
|
+
end_ns=None,
|
|
861
|
+
precision=Precision.UNRESOLVED,
|
|
862
|
+
reason=reason,
|
|
863
|
+
diagnostic_code=diagnostic_code,
|
|
864
|
+
identity=_timeline_identity(node.family, identity_source),
|
|
865
|
+
hook_id=_timeline_hook_id(node.family, identity_source),
|
|
866
|
+
)
|
|
867
|
+
)
|
|
868
|
+
return _materialize_timeline(rows)
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def _unresolved_operation_reason(
|
|
872
|
+
family: str,
|
|
873
|
+
starts: list[_Endpoint],
|
|
874
|
+
finishes: list[_Endpoint],
|
|
875
|
+
) -> tuple[str, TimelineDiagnosticCode]:
|
|
876
|
+
if not starts:
|
|
877
|
+
return f"{family} lifecycle has no start endpoint", TimelineDiagnosticCode.MISSING_START
|
|
878
|
+
if not finishes:
|
|
879
|
+
return f"{family} lifecycle has no terminal endpoint", TimelineDiagnosticCode.MISSING_TERMINAL
|
|
880
|
+
if len(starts) != 1 or len(finishes) != 1:
|
|
881
|
+
return f"{family} lifecycle is not uniquely closed", TimelineDiagnosticCode.NONUNIQUE_LIFECYCLE
|
|
882
|
+
if finishes[0].sequence <= starts[0].sequence or finishes[0].monotonic_ns < starts[0].monotonic_ns:
|
|
883
|
+
return f"{family} interval has invalid monotonic endpoints", TimelineDiagnosticCode.INVALID_ENDPOINTS
|
|
884
|
+
return f"{family} lifecycle falls outside the owning turn coverage", TimelineDiagnosticCode.OUTSIDE_TURN_COVERAGE
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
def _lifecycle_cut_reason(family: str, cut: _LifecycleCut) -> str:
|
|
888
|
+
"""Describe which endpoint of a raw lifecycle remains active."""
|
|
889
|
+
surviving = "start" if cut is _LifecycleCut.START_SURVIVES else "terminal"
|
|
890
|
+
return f"{family} lifecycle crosses rollback projection; only its {surviving} endpoint remains active"
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
def _lifecycle_cut_diagnostic_code(cut: _LifecycleCut) -> TimelineDiagnosticCode:
|
|
894
|
+
return (
|
|
895
|
+
TimelineDiagnosticCode.ROLLBACK_START_SURVIVES
|
|
896
|
+
if cut is _LifecycleCut.START_SURVIVES
|
|
897
|
+
else TimelineDiagnosticCode.ROLLBACK_TERMINAL_SURVIVES
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def _continuation_poll_nodes(nodes: list[_ResolvedNode]) -> list[_ResolvedNode]:
|
|
902
|
+
"""Derive the uninstrumented fixed continuation-poll pauses."""
|
|
903
|
+
exchanges = [node for node in nodes if node.family == "model.exchange"]
|
|
904
|
+
retries = [node for node in nodes if node.family == "retry"]
|
|
905
|
+
by_cycle: dict[str, list[_ResolvedNode]] = defaultdict(list)
|
|
906
|
+
for exchange in exchanges:
|
|
907
|
+
parent = exchange.start.parent_operation_id
|
|
908
|
+
if parent is not None:
|
|
909
|
+
by_cycle[parent].append(exchange)
|
|
910
|
+
derived: list[_ResolvedNode] = []
|
|
911
|
+
for cycle_operation_id, cycle_exchanges in by_cycle.items():
|
|
912
|
+
ordered = sorted(cycle_exchanges, key=lambda node: (node.start.monotonic_ns, node.start.sequence))
|
|
913
|
+
for previous, current in pairwise(ordered):
|
|
914
|
+
if (
|
|
915
|
+
_payload_str(previous.finish.payload, "outcome") != "success"
|
|
916
|
+
or _payload_str(current.start.payload, "continuation_mode") != "poll"
|
|
917
|
+
or previous.finish.monotonic_ns >= current.start.monotonic_ns
|
|
918
|
+
):
|
|
919
|
+
continue
|
|
920
|
+
if any(
|
|
921
|
+
retry.start.monotonic_ns < current.start.monotonic_ns
|
|
922
|
+
and retry.finish.monotonic_ns > previous.finish.monotonic_ns
|
|
923
|
+
for retry in retries
|
|
924
|
+
):
|
|
925
|
+
continue
|
|
926
|
+
operation_id = f"continuation_poll:{previous.operation_id}:{current.operation_id}"
|
|
927
|
+
start = _synthetic_endpoint(
|
|
928
|
+
previous.finish,
|
|
929
|
+
monotonic_ns=previous.finish.monotonic_ns,
|
|
930
|
+
parent_operation_id=cycle_operation_id,
|
|
931
|
+
payload={
|
|
932
|
+
"category": "continuation_poll",
|
|
933
|
+
"previous_exchange_operation_id": previous.operation_id,
|
|
934
|
+
"next_exchange_operation_id": current.operation_id,
|
|
935
|
+
},
|
|
936
|
+
)
|
|
937
|
+
finish = _synthetic_endpoint(
|
|
938
|
+
current.start,
|
|
939
|
+
monotonic_ns=current.start.monotonic_ns,
|
|
940
|
+
parent_operation_id=cycle_operation_id,
|
|
941
|
+
payload={
|
|
942
|
+
"category": "continuation_poll",
|
|
943
|
+
"outcome": "completed",
|
|
944
|
+
"previous_exchange_operation_id": previous.operation_id,
|
|
945
|
+
"next_exchange_operation_id": current.operation_id,
|
|
946
|
+
},
|
|
947
|
+
)
|
|
948
|
+
derived.append(
|
|
949
|
+
_ResolvedNode(
|
|
950
|
+
node_id=f"continuation.poll:{operation_id}",
|
|
951
|
+
family="continuation.poll",
|
|
952
|
+
operation_id=operation_id,
|
|
953
|
+
start=start,
|
|
954
|
+
finish=finish,
|
|
955
|
+
)
|
|
956
|
+
)
|
|
957
|
+
return derived
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
def _suspension_nodes(
|
|
961
|
+
turn: _Turn,
|
|
962
|
+
nodes: list[_ResolvedNode],
|
|
963
|
+
turn_start: _Endpoint,
|
|
964
|
+
finish: _Endpoint | None,
|
|
965
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
966
|
+
diagnostics: list[str],
|
|
967
|
+
) -> tuple[list[_ResolvedNode], dict[str, list[tuple[int, int]]], bool]:
|
|
968
|
+
"""Pair turn suspension markers and identify the sole deductible boundary."""
|
|
969
|
+
suspended = sorted(
|
|
970
|
+
(event for event in turn.suspended if _active(event.sequence, inactive_ranges)),
|
|
971
|
+
key=lambda event: event.sequence,
|
|
972
|
+
)
|
|
973
|
+
resumed = sorted(
|
|
974
|
+
(event for event in turn.resumed if _active(event.sequence, inactive_ranges)),
|
|
975
|
+
key=lambda event: event.sequence,
|
|
976
|
+
)
|
|
977
|
+
resume_index = 0
|
|
978
|
+
exact = True
|
|
979
|
+
derived: list[_ResolvedNode] = []
|
|
980
|
+
deductions: dict[str, list[tuple[int, int]]] = defaultdict(list)
|
|
981
|
+
boundaries = [node for node in nodes if node.family == "sub_agent"]
|
|
982
|
+
for index, start in enumerate(suspended):
|
|
983
|
+
while resume_index < len(resumed) and resumed[resume_index].sequence <= start.sequence:
|
|
984
|
+
diagnostics.append("turn.resumed has no preceding suspension")
|
|
985
|
+
exact = False
|
|
986
|
+
resume_index += 1
|
|
987
|
+
if resume_index < len(resumed):
|
|
988
|
+
terminal = resumed[resume_index]
|
|
989
|
+
resume_index += 1
|
|
990
|
+
elif finish is not None:
|
|
991
|
+
terminal = finish
|
|
992
|
+
diagnostics.append("turn suspension reaches the turn terminal without resume")
|
|
993
|
+
exact = False
|
|
994
|
+
else:
|
|
995
|
+
diagnostics.append("turn suspension has no observable terminal")
|
|
996
|
+
exact = False
|
|
997
|
+
continue
|
|
998
|
+
if (
|
|
999
|
+
start.runtime_id != turn_start.runtime_id
|
|
1000
|
+
or terminal.runtime_id != turn_start.runtime_id
|
|
1001
|
+
or start.branch_id != turn_start.branch_id
|
|
1002
|
+
or terminal.branch_id != turn_start.branch_id
|
|
1003
|
+
or terminal.sequence <= start.sequence
|
|
1004
|
+
or terminal.monotonic_ns < start.monotonic_ns
|
|
1005
|
+
):
|
|
1006
|
+
diagnostics.append("turn suspension has invalid monotonic endpoints")
|
|
1007
|
+
exact = False
|
|
1008
|
+
continue
|
|
1009
|
+
operation_id = f"turn_suspension:{turn.turn_id}:{index}"
|
|
1010
|
+
derived.append(
|
|
1011
|
+
_ResolvedNode(
|
|
1012
|
+
node_id=f"turn.suspension:{operation_id}",
|
|
1013
|
+
family="turn.suspension",
|
|
1014
|
+
operation_id=operation_id,
|
|
1015
|
+
start=_synthetic_endpoint(
|
|
1016
|
+
start,
|
|
1017
|
+
monotonic_ns=start.monotonic_ns,
|
|
1018
|
+
parent_operation_id=None,
|
|
1019
|
+
payload={"category": "sub_agent_suspension"},
|
|
1020
|
+
),
|
|
1021
|
+
finish=_synthetic_endpoint(
|
|
1022
|
+
terminal,
|
|
1023
|
+
monotonic_ns=terminal.monotonic_ns,
|
|
1024
|
+
parent_operation_id=None,
|
|
1025
|
+
payload={"category": "sub_agent_suspension", "outcome": "completed"},
|
|
1026
|
+
),
|
|
1027
|
+
)
|
|
1028
|
+
)
|
|
1029
|
+
candidates = [
|
|
1030
|
+
boundary
|
|
1031
|
+
for boundary in boundaries
|
|
1032
|
+
if boundary.start.monotonic_ns <= start.monotonic_ns
|
|
1033
|
+
and boundary.finish.monotonic_ns >= terminal.monotonic_ns
|
|
1034
|
+
]
|
|
1035
|
+
if len(candidates) == 1:
|
|
1036
|
+
deductions[candidates[0].node_id].append((start.monotonic_ns, terminal.monotonic_ns))
|
|
1037
|
+
elif len(candidates) > 1:
|
|
1038
|
+
diagnostics.append("turn suspension overlaps multiple open sub-agent boundaries")
|
|
1039
|
+
exact = False
|
|
1040
|
+
if resume_index < len(resumed):
|
|
1041
|
+
diagnostics.append("turn.resumed has no preceding suspension")
|
|
1042
|
+
exact = False
|
|
1043
|
+
return derived, deductions, exact
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
def _synthetic_endpoint(
|
|
1047
|
+
source: _Endpoint,
|
|
1048
|
+
*,
|
|
1049
|
+
monotonic_ns: int,
|
|
1050
|
+
parent_operation_id: str | None,
|
|
1051
|
+
payload: dict[str, object],
|
|
1052
|
+
) -> _Endpoint:
|
|
1053
|
+
return _Endpoint(
|
|
1054
|
+
event_id=source.event_id,
|
|
1055
|
+
sequence=source.sequence,
|
|
1056
|
+
scope=source.scope,
|
|
1057
|
+
monotonic_ns=monotonic_ns,
|
|
1058
|
+
parent_operation_id=parent_operation_id,
|
|
1059
|
+
side_call=source.side_call,
|
|
1060
|
+
payload=tuple(item for pair in payload.items() for item in pair),
|
|
1061
|
+
links=(),
|
|
1062
|
+
segmented_fields=(),
|
|
1063
|
+
monotonic_measurement=True,
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def _usage_samples_by_turn(
|
|
1068
|
+
intermediate: _Intermediate,
|
|
1069
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
1070
|
+
) -> dict[str, tuple[TokenUsageSample, ...]]:
|
|
1071
|
+
"""Project timestamped per-exchange usage counters on demand for export."""
|
|
1072
|
+
finish_ns = {
|
|
1073
|
+
endpoint.sequence: endpoint.monotonic_ns
|
|
1074
|
+
for node in intermediate.nodes.get("model.exchange", {}).values()
|
|
1075
|
+
for endpoint in node.finishes
|
|
1076
|
+
}
|
|
1077
|
+
samples: dict[str, tuple[TokenUsageSample, ...]] = {}
|
|
1078
|
+
for turn_id, items in intermediate.usage_by_turn.items():
|
|
1079
|
+
rows = tuple(
|
|
1080
|
+
TokenUsageSample(
|
|
1081
|
+
sequence=item.sequence,
|
|
1082
|
+
end_ns=finish_ns.get(item.sequence),
|
|
1083
|
+
input_tokens=item.input_total,
|
|
1084
|
+
output_tokens=item.output_total,
|
|
1085
|
+
reasoning_tokens=item.extras.reasoning if item.extras is not None else None,
|
|
1086
|
+
cache_read_tokens=item.extras.cache_read if item.extras is not None else None,
|
|
1087
|
+
cache_creation_tokens=item.extras.cache_creation if item.extras is not None else None,
|
|
1088
|
+
)
|
|
1089
|
+
for item in items
|
|
1090
|
+
if _active(item.sequence, inactive_ranges)
|
|
1091
|
+
and (item.input_total is not None or item.output_total is not None or item.extras is not None)
|
|
1092
|
+
)
|
|
1093
|
+
if rows:
|
|
1094
|
+
samples[turn_id] = rows
|
|
1095
|
+
return samples
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def _context_samples_by_turn(
|
|
1099
|
+
intermediate: _Intermediate,
|
|
1100
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
1101
|
+
) -> dict[str, tuple[ContextSample, ...]]:
|
|
1102
|
+
"""Project timestamped context-size counters on demand for export.
|
|
1103
|
+
|
|
1104
|
+
The declared item_count is read straight off each uniquely defined revision
|
|
1105
|
+
endpoint, so no membership replay is needed; declaration inconsistencies
|
|
1106
|
+
already surface through the diagnostics pipeline.
|
|
1107
|
+
"""
|
|
1108
|
+
by_turn: dict[str, list[ContextSample]] = defaultdict(list)
|
|
1109
|
+
for candidates in intermediate.context_revisions.values():
|
|
1110
|
+
active = [endpoint for endpoint in candidates if _active(endpoint.sequence, inactive_ranges)]
|
|
1111
|
+
if len(active) != 1:
|
|
1112
|
+
continue
|
|
1113
|
+
endpoint = active[0]
|
|
1114
|
+
item_count = _payload_int(endpoint.payload, "item_count")
|
|
1115
|
+
if endpoint.turn_id is None or item_count is None or item_count < 0 or endpoint.side_call:
|
|
1116
|
+
continue
|
|
1117
|
+
by_turn[endpoint.turn_id].append(
|
|
1118
|
+
ContextSample(
|
|
1119
|
+
sequence=endpoint.sequence,
|
|
1120
|
+
ns=endpoint.monotonic_ns,
|
|
1121
|
+
item_count=item_count,
|
|
1122
|
+
)
|
|
1123
|
+
)
|
|
1124
|
+
return {
|
|
1125
|
+
turn_id: tuple(sorted(rows, key=lambda sample: sample.sequence)) for turn_id, rows in sorted(by_turn.items())
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
def _turn_usage(
|
|
1130
|
+
intermediate: _Intermediate,
|
|
1131
|
+
turn_id: str,
|
|
1132
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
1133
|
+
*,
|
|
1134
|
+
integrity_exact: bool,
|
|
1135
|
+
integrity_reason: str,
|
|
1136
|
+
turn_lifecycle_exact: bool,
|
|
1137
|
+
closed_exchange_terminal_sequences: frozenset[int],
|
|
1138
|
+
cancel_event: Event | None,
|
|
1139
|
+
) -> Metric:
|
|
1140
|
+
unclosed_start = False
|
|
1141
|
+
exchange_projection_exact = True
|
|
1142
|
+
for node in intermediate.nodes_by_turn.get(turn_id, ()):
|
|
1143
|
+
_check_cancelled(cancel_event)
|
|
1144
|
+
if node.family != "model.exchange":
|
|
1145
|
+
continue
|
|
1146
|
+
starts = [
|
|
1147
|
+
event for event in node.starts if event.turn_id == turn_id and _active(event.sequence, inactive_ranges)
|
|
1148
|
+
]
|
|
1149
|
+
finishes = [
|
|
1150
|
+
event for event in node.finishes if event.turn_id == turn_id and _active(event.sequence, inactive_ranges)
|
|
1151
|
+
]
|
|
1152
|
+
endpoints = (*starts, *finishes)
|
|
1153
|
+
if not endpoints:
|
|
1154
|
+
continue
|
|
1155
|
+
if _lifecycle_cut(node, inactive_ranges) is not _LifecycleCut.NONE:
|
|
1156
|
+
exchange_projection_exact = False
|
|
1157
|
+
continue
|
|
1158
|
+
if len(starts) == 1 and not finishes:
|
|
1159
|
+
unclosed_start = True
|
|
1160
|
+
continue
|
|
1161
|
+
if len(starts) != 1 or len(finishes) != 1 or finishes[0].sequence not in closed_exchange_terminal_sequences:
|
|
1162
|
+
exchange_projection_exact = False
|
|
1163
|
+
if unclosed_start:
|
|
1164
|
+
return Metric(None, Precision.MISSING, "one or more model exchanges have no terminal usage")
|
|
1165
|
+
exchanges = [
|
|
1166
|
+
item for item in intermediate.usage_by_turn.get(turn_id, ()) if _active(item.sequence, inactive_ranges)
|
|
1167
|
+
]
|
|
1168
|
+
if any(
|
|
1169
|
+
item.normalization_unavailable or item.input_total is None or item.output_total is None for item in exchanges
|
|
1170
|
+
):
|
|
1171
|
+
return Metric(None, Precision.MISSING, "one or more normalized usage totals are missing")
|
|
1172
|
+
value = sum(cast("int", item.input_total) + cast("int", item.output_total) for item in exchanges)
|
|
1173
|
+
if not integrity_exact:
|
|
1174
|
+
return Metric(value, Precision.UNRESOLVED, integrity_reason or "turn sequence integrity is unresolved")
|
|
1175
|
+
usage_terminals = [item.sequence for item in exchanges]
|
|
1176
|
+
if (
|
|
1177
|
+
not turn_lifecycle_exact
|
|
1178
|
+
or not exchange_projection_exact
|
|
1179
|
+
or any(sequence not in closed_exchange_terminal_sequences for sequence in usage_terminals)
|
|
1180
|
+
):
|
|
1181
|
+
return Metric(value, Precision.UNRESOLVED, "usage requires a uniquely closed exchange and exact turn lifecycle")
|
|
1182
|
+
if not all(
|
|
1183
|
+
item.bucket_has_provider_provenance(UsageBucket.INPUT)
|
|
1184
|
+
and item.bucket_has_provider_provenance(UsageBucket.OUTPUT)
|
|
1185
|
+
for item in exchanges
|
|
1186
|
+
):
|
|
1187
|
+
return Metric(value, Precision.UNRESOLVED, "normalized usage provenance is incomplete")
|
|
1188
|
+
return Metric(value, Precision.EXACT)
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
def _turn_bucket_usage(exchanges: list[_ExchangeUsage], bucket: UsageBucket, verdict: Metric) -> Metric:
|
|
1192
|
+
"""Split one turn's usage verdict into a per-bucket display metric.
|
|
1193
|
+
|
|
1194
|
+
The turn-level verdict caps every bucket: a missing or unresolved turn can
|
|
1195
|
+
never yield an exact bucket. Optional buckets additionally degrade on
|
|
1196
|
+
partial reporting because providers normalize them inconsistently.
|
|
1197
|
+
"""
|
|
1198
|
+
if verdict.value is None:
|
|
1199
|
+
return Metric(None, verdict.precision, verdict.reason)
|
|
1200
|
+
reported = [value for item in exchanges if (value := item.bucket_value(bucket)) is not None]
|
|
1201
|
+
if bucket in _OPTIONAL_USAGE_BUCKETS and exchanges and not reported:
|
|
1202
|
+
return Metric(None, Precision.MISSING, "no exchange reported this bucket")
|
|
1203
|
+
value = sum(reported)
|
|
1204
|
+
if verdict.precision is not Precision.EXACT:
|
|
1205
|
+
return Metric(value, Precision.UNRESOLVED, verdict.reason)
|
|
1206
|
+
if bucket is UsageBucket.REASONING and any(
|
|
1207
|
+
item.extras is not None
|
|
1208
|
+
and item.extras.reasoning is not None
|
|
1209
|
+
and item.output_total is not None
|
|
1210
|
+
and item.extras.reasoning > item.output_total
|
|
1211
|
+
for item in exchanges
|
|
1212
|
+
):
|
|
1213
|
+
return Metric(value, Precision.UNRESOLVED, "reasoning tokens exceed normalized output tokens")
|
|
1214
|
+
if len(reported) < len(exchanges):
|
|
1215
|
+
return Metric(value, Precision.ESTIMATED, "not every exchange reported this bucket")
|
|
1216
|
+
if not all(item.bucket_has_provider_provenance(bucket) for item in exchanges):
|
|
1217
|
+
return Metric(value, Precision.UNRESOLVED, "normalized usage provenance is incomplete")
|
|
1218
|
+
return Metric(value, Precision.EXACT)
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
|
+
def _counter_axis_diagnostics(
|
|
1222
|
+
intermediate: _Intermediate,
|
|
1223
|
+
turn_id: str,
|
|
1224
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
1225
|
+
*,
|
|
1226
|
+
resolution_cache: _ResolutionCache,
|
|
1227
|
+
axis_start_ns: int,
|
|
1228
|
+
axis_end_ns: int,
|
|
1229
|
+
revisions: _RevisionResolution,
|
|
1230
|
+
refresh: bool,
|
|
1231
|
+
cancel_event: Event | None,
|
|
1232
|
+
) -> tuple[str, ...]:
|
|
1233
|
+
"""Validate one dirty physical turn's counter timestamps without a session scan."""
|
|
1234
|
+
if not refresh:
|
|
1235
|
+
return resolution_cache.counter_axis_diagnostics_by_turn.get(turn_id, ())
|
|
1236
|
+
diagnostics: list[str] = []
|
|
1237
|
+
usage_sequences: set[int] = set()
|
|
1238
|
+
for item in intermediate.usage_by_turn.get(turn_id, ()):
|
|
1239
|
+
_check_cancelled(cancel_event)
|
|
1240
|
+
if _active(item.sequence, inactive_ranges) and (
|
|
1241
|
+
item.input_total is not None or item.output_total is not None or item.extras is not None
|
|
1242
|
+
):
|
|
1243
|
+
usage_sequences.add(item.sequence)
|
|
1244
|
+
usage_outside = False
|
|
1245
|
+
if usage_sequences:
|
|
1246
|
+
for node in intermediate.nodes_by_turn.get(turn_id, ()):
|
|
1247
|
+
_check_cancelled(cancel_event)
|
|
1248
|
+
if node.family != "model.exchange":
|
|
1249
|
+
continue
|
|
1250
|
+
for endpoint in node.finishes:
|
|
1251
|
+
_check_cancelled(cancel_event)
|
|
1252
|
+
if (
|
|
1253
|
+
endpoint.turn_id == turn_id
|
|
1254
|
+
and endpoint.sequence in usage_sequences
|
|
1255
|
+
and _active(endpoint.sequence, inactive_ranges)
|
|
1256
|
+
and not axis_start_ns <= endpoint.monotonic_ns <= axis_end_ns
|
|
1257
|
+
):
|
|
1258
|
+
usage_outside = True
|
|
1259
|
+
break
|
|
1260
|
+
if usage_outside:
|
|
1261
|
+
break
|
|
1262
|
+
if usage_outside:
|
|
1263
|
+
diagnostics.append("usage sample lies outside its owning attempt axis")
|
|
1264
|
+
|
|
1265
|
+
for revision_id in intermediate.context_revision_ids_by_turn.get(turn_id, ()):
|
|
1266
|
+
_check_cancelled(cancel_event)
|
|
1267
|
+
endpoint = revisions.endpoints.get(revision_id)
|
|
1268
|
+
if endpoint is None:
|
|
1269
|
+
continue
|
|
1270
|
+
item_count = _payload_int(endpoint.payload, "item_count")
|
|
1271
|
+
if (
|
|
1272
|
+
item_count is not None
|
|
1273
|
+
and item_count >= 0
|
|
1274
|
+
and not endpoint.side_call
|
|
1275
|
+
and not axis_start_ns <= endpoint.monotonic_ns <= axis_end_ns
|
|
1276
|
+
):
|
|
1277
|
+
diagnostics.append("context sample lies outside its owning attempt axis")
|
|
1278
|
+
break
|
|
1279
|
+
result = tuple(diagnostics)
|
|
1280
|
+
resolution_cache.counter_axis_diagnostics_by_turn[turn_id] = result
|
|
1281
|
+
return result
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
@lru_cache(maxsize=256)
|
|
1285
|
+
def _interned_token_usage(metrics: tuple[Metric, ...]) -> TokenUsage:
|
|
1286
|
+
"""Share value-identical per-turn usage; every retained turn carries one.
|
|
1287
|
+
|
|
1288
|
+
Long sessions repeat the same bucket shapes turn after turn, and a private
|
|
1289
|
+
five-metric dict per turn is what the residency ceiling notices first.
|
|
1290
|
+
"""
|
|
1291
|
+
return TokenUsage(buckets=dict(zip(UsageBucket, metrics, strict=True)))
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
def _empty_turn(
|
|
1295
|
+
intermediate: _Intermediate,
|
|
1296
|
+
turn_id: str,
|
|
1297
|
+
starts: list[_Endpoint],
|
|
1298
|
+
reason: str,
|
|
1299
|
+
*,
|
|
1300
|
+
inactive_ranges: tuple[tuple[int, int], ...],
|
|
1301
|
+
resolution_cache: _ResolutionCache,
|
|
1302
|
+
revisions: _RevisionResolution,
|
|
1303
|
+
refresh: bool,
|
|
1304
|
+
cancel_event: Event | None,
|
|
1305
|
+
) -> TurnAnalysis:
|
|
1306
|
+
"""Build an unresolved physical attempt without discarding stable opener identity.
|
|
1307
|
+
|
|
1308
|
+
Duplicate starts may still prove one ``turn_number``/``is_retry`` pair and
|
|
1309
|
+
therefore participate in logical retry folding. With no surviving start
|
|
1310
|
+
(for example, an opener hidden by an unaccounted gap), no such association
|
|
1311
|
+
is provable and the attempt deliberately remains an unnumbered turn.
|
|
1312
|
+
"""
|
|
1313
|
+
start = starts[0] if starts else None
|
|
1314
|
+
turn_numbers = {_payload_int(candidate.payload, "turn_number") for candidate in starts}
|
|
1315
|
+
retry_flags = {_payload_bool(candidate.payload, "is_retry") for candidate in starts}
|
|
1316
|
+
runtime_ids = {candidate.runtime_id for candidate in starts}
|
|
1317
|
+
turn_number = next(iter(turn_numbers)) if len(turn_numbers) == 1 else None
|
|
1318
|
+
is_retry = next(iter(retry_flags)) if len(retry_flags) == 1 else False
|
|
1319
|
+
diagnostics = [reason]
|
|
1320
|
+
if starts and (len(turn_numbers) != 1 or len(retry_flags) != 1):
|
|
1321
|
+
diagnostics.append("turn starts disagree on logical retry identity")
|
|
1322
|
+
if len(runtime_ids) > 1:
|
|
1323
|
+
diagnostics.append("turn starts disagree on runtime ownership")
|
|
1324
|
+
unresolved = Metric(None, Precision.UNRESOLVED, reason)
|
|
1325
|
+
runtime_id = start.runtime_id if start is not None else ""
|
|
1326
|
+
sequence = start.sequence if start is not None else 0
|
|
1327
|
+
axis_ns = start.monotonic_ns if start is not None else 0
|
|
1328
|
+
diagnostics.extend(
|
|
1329
|
+
_counter_axis_diagnostics(
|
|
1330
|
+
intermediate,
|
|
1331
|
+
turn_id,
|
|
1332
|
+
inactive_ranges,
|
|
1333
|
+
resolution_cache=resolution_cache,
|
|
1334
|
+
axis_start_ns=axis_ns,
|
|
1335
|
+
axis_end_ns=axis_ns,
|
|
1336
|
+
revisions=revisions,
|
|
1337
|
+
refresh=refresh,
|
|
1338
|
+
cancel_event=cancel_event,
|
|
1339
|
+
)
|
|
1340
|
+
)
|
|
1341
|
+
wall = dict.fromkeys(WallBucket, unresolved)
|
|
1342
|
+
utilization = dict.fromkeys((WallBucket.MODEL, WallBucket.TOOLS), unresolved)
|
|
1343
|
+
return TurnAnalysis(
|
|
1344
|
+
turn_id=turn_id,
|
|
1345
|
+
turn_number=turn_number,
|
|
1346
|
+
runtime_id=runtime_id,
|
|
1347
|
+
start_sequence=sequence,
|
|
1348
|
+
end_sequence=None,
|
|
1349
|
+
elapsed_ns=unresolved,
|
|
1350
|
+
compute_cp_ns=unresolved,
|
|
1351
|
+
response_cp_ns=unresolved,
|
|
1352
|
+
exclusive_work_ns=unresolved,
|
|
1353
|
+
parallelism=unresolved,
|
|
1354
|
+
overlap_gain_ns=unresolved,
|
|
1355
|
+
wall_time_ns=wall,
|
|
1356
|
+
utilization=utilization,
|
|
1357
|
+
usage_tokens=Metric(None, Precision.MISSING, reason),
|
|
1358
|
+
attempts=(
|
|
1359
|
+
TurnAttemptRef(
|
|
1360
|
+
turn_id=turn_id,
|
|
1361
|
+
runtime_id=runtime_id,
|
|
1362
|
+
is_retry=is_retry,
|
|
1363
|
+
physical_axis_start_ns=axis_ns,
|
|
1364
|
+
physical_axis_end_ns=axis_ns,
|
|
1365
|
+
logical_axis_start_ns=axis_ns,
|
|
1366
|
+
operation_start_index=0,
|
|
1367
|
+
operation_end_index=0,
|
|
1368
|
+
slice_start_index=0,
|
|
1369
|
+
slice_end_index=0,
|
|
1370
|
+
),
|
|
1371
|
+
),
|
|
1372
|
+
axis_start_ns=axis_ns,
|
|
1373
|
+
axis_end_ns=axis_ns,
|
|
1374
|
+
diagnostics=tuple(diagnostics),
|
|
1375
|
+
action_projection_precision=Precision.UNRESOLVED,
|
|
1376
|
+
action_projection_reason=reason,
|
|
1377
|
+
)
|