micro-cc 0.2.97__tar.gz → 0.2.99__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {micro_cc-0.2.97/src/micro_cc.egg-info → micro_cc-0.2.99}/PKG-INFO +1 -1
- {micro_cc-0.2.97 → micro_cc-0.2.99}/pyproject.toml +1 -1
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/claude_loop_.py +9 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/start_headless_.py +1 -1
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/start_live_tui_.py +9 -30
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/message_session_.py +3 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/monitor_.py +8 -13
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/alt_screen_.py +81 -9
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/session_ipc_.py +51 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/subagent_tracker_.py +40 -62
- {micro_cc-0.2.97 → micro_cc-0.2.99/src/micro_cc.egg-info}/PKG-INFO +1 -1
- {micro_cc-0.2.97 → micro_cc-0.2.99}/tests/test_subagent_lifecycle.py +4 -4
- {micro_cc-0.2.97 → micro_cc-0.2.99}/LICENSE +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/README.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/setup.cfg +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/browser/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/browser/_cookies.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/browser/_md_convert.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/browser/browser_manager.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/browser/simpletextbrowser.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/cache/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/cache/state_store.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/execute_tool.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/adapters/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/adapters/anthropic.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/adapters/chat.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/adapters/responses.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/anthropic.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/client.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/foundry.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/litellm.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/ollama.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/openai.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/openrouter.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/priming.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/registry.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/schema.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/types.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/postgres_store/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/postgres_store/pg_store_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/postgres_store/schema_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/self_heal_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/SKILL.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/ArsenalSC-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Boldonse-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/DMMono-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/DMMono-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/EricaOne-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/GeistMono-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Gloock-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Gloock-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Italiana-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Italiana-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Jura-Light.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Jura-Medium.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Jura-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/LibreBaskerville-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Lora-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Lora-Italic.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Lora-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Lora-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/NationalPark-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Outfit-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Outfit-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Outfit-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/PixelifySans-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/PoiretOne-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Silkscreen-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/SmoochSans-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Tektur-Medium.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Tektur-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Tektur-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/WorkSans-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/YoungSerif-OFL.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/references/blueprint-tokens.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/docx/SKILL.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/docx/docx-js.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/docx/ooxml.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/github/SKILL.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/graph-orchestration/SKILL.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/RENDERER.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/SKILL.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/TIMING.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/analysis/align.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/analysis/build_data.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/analysis/features.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/analysis/transcribe.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/check.mjs +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/install_stack.sh +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/render.mjs +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/scaffold.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/SKILL.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/brief.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/scripts/build_deck.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/scripts/deck_to_images.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/scripts/office/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/scripts/office/soffice.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/scripts/render_svg.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/svg-slides.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/skill_loader.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/system-design-review/REFERENCE.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/system-design-review/SKILL.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/system-design-review/scripts/design_graph.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/system-design-review/scripts/design_review.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/xlsx/SKILL.md +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/ask_user_tool.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/bash_tool.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/browser_tool_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/computer_tool_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/file_tools_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/mcp_client_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/memory_tool_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/monitor_schedule_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/monitor_schedule_runtime.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/monitor_watch_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/search_history_tool_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/search_tool_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/skill_tools_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/todo_tools_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/vision_tools_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/web_tools_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/detect_images_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/diff_viewer_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/etype_handler_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/keys_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/kill_ring_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/list_picker_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/md_render_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/message_row_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/multi_select_list_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/prompt_input_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/question_panel_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/screen_cmds_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/scroll_view_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/self_terminal_setup_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/self_update_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/stack_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/stdin_buffer_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/terminal_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/text_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/text_utils_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/theme_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/truncated_text_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/undo_stack_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/word_navigation_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/annotate_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/banner_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/claude_md_loader.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/claude_subscription.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/command_registry.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/default_status_sh.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/helpers.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/hidden_prompts.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/hints.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/history_mount_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/inbox_store_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/memory_store_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/msg_normalize_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/msg_store_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/project_files_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/self_reload_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/settings_store_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/status_marker.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/terminal_setup.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/theme_store_.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/tokenization_simple.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/tool_limits.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/tool_result_storage.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/__init__.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/bridge.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/dist/assets/index-9B5FcRm2.css +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/dist/assets/index-CDG9HO3B.js +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/dist/index.html +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/history.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/launch.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/server.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/session.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc.egg-info/SOURCES.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc.egg-info/dependency_links.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc.egg-info/entry_points.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc.egg-info/requires.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc.egg-info/top_level.txt +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/tests/test_model_consumers.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/tests/test_models.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/tests/test_schedule.py +0 -0
- {micro_cc-0.2.97 → micro_cc-0.2.99}/tests/test_self_reload.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "micro-cc"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.99"
|
|
8
8
|
description = "Harness that gives frontier models full system access — shell, filesystem, browser, MCP — running directly on the metal."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -492,6 +492,15 @@ whole conversation history, including compacted turns.
|
|
|
492
492
|
if proc_info:
|
|
493
493
|
status_sections.append(f"<process-status>\n{proc_info}\n</process-status>")
|
|
494
494
|
|
|
495
|
+
# Other live micro-cc TUIs on this machine (see session_ipc_.list_peers),
|
|
496
|
+
# so message_session_ has real target paths to use.
|
|
497
|
+
from micro_cc.utils.session_ipc_ import format_peers
|
|
498
|
+
peers = format_peers(project_dir)
|
|
499
|
+
if peers:
|
|
500
|
+
status_sections.append(
|
|
501
|
+
f"<peer-sessions>\nOther live micro-cc sessions — message with message_session_(target_project_dir=...); replies arrive as incoming turns (do not poll them with monitor_):\n{peers}\n</peer-sessions>"
|
|
502
|
+
)
|
|
503
|
+
|
|
495
504
|
# Conversation summary from sliding window — old-format fallback
|
|
496
505
|
# only. Once a checkpoint exists, its content already rides inside
|
|
497
506
|
# trimmed_rest below as token_cutter's prepended stand-in, so
|
|
@@ -66,7 +66,7 @@ def _register(caller_project_dir: str, project_dir: str) -> str | None:
|
|
|
66
66
|
from micro_cc.utils.subagent_tracker_ import add_tracked, NameInUse, CapReached
|
|
67
67
|
from micro_cc.utils.settings_store_ import max_subagents
|
|
68
68
|
try:
|
|
69
|
-
add_tracked(caller_project_dir, project_dir,
|
|
69
|
+
add_tracked(caller_project_dir, project_dir, max_running=max_subagents())
|
|
70
70
|
except (NameInUse, CapReached) as e:
|
|
71
71
|
return str(e)
|
|
72
72
|
except OSError:
|
|
@@ -588,6 +588,8 @@ class MicroTui():
|
|
|
588
588
|
|
|
589
589
|
async def _render_loop(self) -> None:
|
|
590
590
|
while self._running:
|
|
591
|
+
if self.tui.autoscroll_tick():
|
|
592
|
+
self._dirty = True
|
|
591
593
|
if self._dirty:
|
|
592
594
|
try:
|
|
593
595
|
self.tui.do_render()
|
|
@@ -779,32 +781,8 @@ class MicroTui():
|
|
|
779
781
|
self.action_scroll_messages_down()
|
|
780
782
|
return
|
|
781
783
|
self.tui.handle_selection_mouse_event(mouse)
|
|
782
|
-
# Drag-
|
|
783
|
-
#
|
|
784
|
-
# toward either edge to reach content currently scrolled out of
|
|
785
|
-
# view did nothing (this was the actual "I cannot drag and
|
|
786
|
-
# scroll down" bug: there was no auto-scroll at all, not a
|
|
787
|
-
# broken one). Only fires on an actual drag-motion event
|
|
788
|
-
# (button & 32, not a release) with a selection in progress,
|
|
789
|
-
# same guard handle_selection_mouse_event itself uses — a
|
|
790
|
-
# mouse-move with no button held is button==0 with the 32 bit
|
|
791
|
-
# unset, this never fires for those. Only scrolls once per
|
|
792
|
-
# motion event (there's no periodic timer driving this while
|
|
793
|
-
# the mouse sits still at the edge), but the terminal keeps
|
|
794
|
-
# sending motion events as long as the mouse keeps moving, so
|
|
795
|
-
# holding the drag past the edge and continuing to move it
|
|
796
|
-
# scrolls continuously, same physical gesture as any other
|
|
797
|
-
# drag-to-scroll UI.
|
|
798
|
-
if (
|
|
799
|
-
mouse["button"] & 32
|
|
800
|
-
and not mouse["release"]
|
|
801
|
-
and self.tui.selection_press_active
|
|
802
|
-
):
|
|
803
|
-
_EDGE = 1
|
|
804
|
-
if mouse["y"] <= _EDGE:
|
|
805
|
-
self.action_scroll_messages_up(1)
|
|
806
|
-
elif mouse["y"] >= self.terminal.rows - 1 - _EDGE:
|
|
807
|
-
self.action_scroll_messages_down(1)
|
|
784
|
+
# Drag auto-scroll lives in TuiAltScreen.autoscroll_tick (driven by
|
|
785
|
+
# _render_loop) — selection is in content rows, so it survives scrolling.
|
|
808
786
|
# Click-to-expand: on release, if handle_selection_mouse_event
|
|
809
787
|
# didn't end up forming a real selection (anchor == focus —
|
|
810
788
|
# exactly the "press and release landed at the same
|
|
@@ -1154,6 +1132,7 @@ class MicroTui():
|
|
|
1154
1132
|
settings_store_.edit_setting("dangerous", list(self._dangerous_tools))
|
|
1155
1133
|
self._close_picker("_dangerous_picker_overlay")
|
|
1156
1134
|
self._refresh_status()
|
|
1135
|
+
self.set_focus(self.prompt)
|
|
1157
1136
|
|
|
1158
1137
|
def _on_slash_picked(self, item: PickerItem) -> None:
|
|
1159
1138
|
self._close_picker("_slash_picker_overlay")
|
|
@@ -2699,7 +2678,7 @@ class MicroTui():
|
|
|
2699
2678
|
Excludes any pid that's also a tracked subagent's own process — a
|
|
2700
2679
|
`microcc-headless <dir> &` backgrounded through bash_ is both a
|
|
2701
2680
|
survivor bash_tool sees via _track_survivors AND self-registers with
|
|
2702
|
-
subagent_tracker_ (see add_tracked
|
|
2681
|
+
subagent_tracker_ (see add_tracked), so without
|
|
2703
2682
|
this filter the exact same process shows up twice: once as a
|
|
2704
2683
|
subagent glyph, once as a raw PID pill. The two widgets are meant to
|
|
2705
2684
|
be mutually exclusive for that pid — #subagent-status wins since it
|
|
@@ -2857,8 +2836,8 @@ class MicroTui():
|
|
|
2857
2836
|
a later tick can't fire it again. PAUSED/NEEDS_INPUT are non-terminal
|
|
2858
2837
|
(see _SUBAGENT_TERMINAL_STATUSES) so they still wake boss once, but
|
|
2859
2838
|
keep displaying/prune-ineligible afterward — the process already
|
|
2860
|
-
exited but still expects a relaunch, and a relaunch's
|
|
2861
|
-
|
|
2839
|
+
exited but still expects a relaunch, and a relaunch's add_tracked
|
|
2840
|
+
call is what resets notified for the next checkpoint."""
|
|
2862
2841
|
from micro_cc.utils import subagent_tracker_
|
|
2863
2842
|
from micro_cc.tools.monitor_ import poll_status, format_status_glyph
|
|
2864
2843
|
from micro_cc.utils.inbox_store_ import peek_pending_count
|
|
@@ -2891,7 +2870,7 @@ class MicroTui():
|
|
|
2891
2870
|
# line for a process that's dead. Without this check an
|
|
2892
2871
|
# entry a user had boss `kill <pid>` stays displayed
|
|
2893
2872
|
# (and un-prunable) forever. Only fires once pid is on
|
|
2894
|
-
# the entry (see add_tracked
|
|
2873
|
+
# the entry (see add_tracked) — older
|
|
2895
2874
|
# entries from before that field existed just fall
|
|
2896
2875
|
# through unchanged, same as today.
|
|
2897
2876
|
pid = entry.get("pid")
|
|
@@ -27,6 +27,9 @@ async def message_session_(
|
|
|
27
27
|
checkpoint while it's running), even if that run starts well after
|
|
28
28
|
this call returns.
|
|
29
29
|
|
|
30
|
+
Don't follow up with monitor_ — a live session's reply arrives as its
|
|
31
|
+
own incoming turn, and monitor_ is for headless subagents and PIDs only.
|
|
32
|
+
|
|
30
33
|
Args:
|
|
31
34
|
target_project_dir: absolute path of the target project — whatever
|
|
32
35
|
it was launched with, e.g. `python start_.py /path/to/project`
|
|
@@ -7,7 +7,7 @@ from rich.markup import escape as _markup_escape
|
|
|
7
7
|
|
|
8
8
|
from micro_cc.utils.msg_store_ import load_msgs, _get_storage_dir
|
|
9
9
|
from micro_cc.utils.inbox_store_ import peek_pending_count
|
|
10
|
-
from micro_cc.utils.subagent_tracker_ import
|
|
10
|
+
from micro_cc.utils.subagent_tracker_ import read_tracked
|
|
11
11
|
from micro_cc.utils.tokenization_simple import peek_token_stats
|
|
12
12
|
from micro_cc.utils import status_marker
|
|
13
13
|
from micro_cc.tools.bash_tool import list_background_processes, get_output_tail
|
|
@@ -16,15 +16,10 @@ from micro_cc.tools import monitor_schedule_ as sched
|
|
|
16
16
|
|
|
17
17
|
_POLL_TAIL_BYTES = 8192
|
|
18
18
|
|
|
19
|
-
# Mirrors subagent_tracker_'s
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# this project_dir is DONE/FAILED, there's nothing left to watch for — the
|
|
24
|
-
# caller already has that answer in this call's own return string — so
|
|
25
|
-
# there's no reason to (re)create a placeholder for the poller to track.
|
|
26
|
-
# Duplicated rather than imported, same as screen_cmds_.py's own copy —
|
|
27
|
-
# each answers a locally different question and happens to share values.
|
|
19
|
+
# Mirrors subagent_tracker_'s prune-eligible set (DONE/FAILED — dead ends, see
|
|
20
|
+
# subagent_tracker_.py's _TERMINAL_STATUSES). Duplicated rather than imported,
|
|
21
|
+
# same as screen_cmds_.py's own copy — each answers a locally different
|
|
22
|
+
# question and happens to share values.
|
|
28
23
|
_DONE_STATUSES = {"DONE", "FAILED"}
|
|
29
24
|
|
|
30
25
|
|
|
@@ -335,7 +330,9 @@ async def monitor_(
|
|
|
335
330
|
watches over either. Pick the action:
|
|
336
331
|
|
|
337
332
|
action="check" — on-demand snapshot of one or more `targets`, mixed
|
|
338
|
-
freely
|
|
333
|
+
freely (headless subagents and PIDs only — NOT for a live session you
|
|
334
|
+
messaged with message_session_; its delivery result is the answer, a
|
|
335
|
+
reply arrives as its own turn): a subagent's project_dir (absolute path, exactly as passed
|
|
339
336
|
to microcc-headless) reports DONE/FAILED/PAUSED/NEEDS_INPUT (or "no
|
|
340
337
|
output yet") plus its one-line summary and unread message_session_
|
|
341
338
|
mail count; while it's running, also an activity line — pid alive or
|
|
@@ -422,8 +419,6 @@ async def monitor_(
|
|
|
422
419
|
lines.append(_format_bgproc_line(int(target.strip())))
|
|
423
420
|
else:
|
|
424
421
|
info = get_subagent_status(target, caller_project_dir=project_dir)
|
|
425
|
-
if info["status"] not in _DONE_STATUSES:
|
|
426
|
-
add_tracked(project_dir, info["resolved"])
|
|
427
422
|
lines.append(format_status_line(info))
|
|
428
423
|
return "\n".join(lines)
|
|
429
424
|
|
|
@@ -414,13 +414,11 @@ class OverlayHandle:
|
|
|
414
414
|
# applySelectionHighlight, getWordSelection, getLineSelection, plus the
|
|
415
415
|
# button-press/drag/release state machine (handleSelectionMouseEvent).
|
|
416
416
|
#
|
|
417
|
-
#
|
|
418
|
-
# (self.previous_screen)
|
|
419
|
-
#
|
|
420
|
-
#
|
|
421
|
-
#
|
|
422
|
-
# its full (not just visible) content, which needs each ScrollView's
|
|
423
|
-
# on-screen rectangle known ahead of time, not just a flat list of lines.
|
|
417
|
+
# A press outside the primary ScrollView selects against the composited
|
|
418
|
+
# screen (self.previous_screen) in screen rows. A press inside it selects in
|
|
419
|
+
# CONTENT rows (scroll_top + row-in-viewport), so the range survives
|
|
420
|
+
# scrolling (wheel or drag auto-scroll) and copy reads the full content, not
|
|
421
|
+
# just the painted window.
|
|
424
422
|
_WORD_RE = re.compile(r"\w+|[^\w\s]+|\s+", re.UNICODE)
|
|
425
423
|
|
|
426
424
|
|
|
@@ -474,6 +472,11 @@ class TuiAltScreen:
|
|
|
474
472
|
self.selection_initial_range: tuple[SelectionPoint, SelectionPoint] | None = None
|
|
475
473
|
self.selection_press_active = False
|
|
476
474
|
self._last_click: ClickTarget | None = None
|
|
475
|
+
self.selection_in_content = False # points are content rows of primary_scroll_view
|
|
476
|
+
self.drag_edge = 0 # -1/0/1: drag is parked at top/bottom edge of the viewport
|
|
477
|
+
self._drag_x = 0
|
|
478
|
+
self._last_autoscroll = 0.0
|
|
479
|
+
self._content_cache: list[str] | None = None
|
|
477
480
|
|
|
478
481
|
# The scroll view search highlighting/navigation operates on.
|
|
479
482
|
# Whoever builds the actual widget tree hands this in explicitly
|
|
@@ -612,8 +615,47 @@ class TuiAltScreen:
|
|
|
612
615
|
return result
|
|
613
616
|
|
|
614
617
|
# --- selection: text lookups over the last composited screen ------
|
|
618
|
+
def _scroll_region(self):
|
|
619
|
+
"""(scroll_view, screen_offset, viewport_height) of the primary scroll view, or None."""
|
|
620
|
+
sv = self.primary_scroll_view
|
|
621
|
+
if sv is None or not hasattr(self.root, "find_offset"):
|
|
622
|
+
return None
|
|
623
|
+
offset = self.root.find_offset(sv)
|
|
624
|
+
if offset is None or sv.viewport_height <= 0:
|
|
625
|
+
return None
|
|
626
|
+
return sv, offset, sv.viewport_height
|
|
627
|
+
|
|
628
|
+
def _content_lines(self) -> list[str]:
|
|
629
|
+
if self._content_cache is None:
|
|
630
|
+
self._content_cache = [
|
|
631
|
+
strip_osc133_zone(l) for l in self.primary_scroll_view.child.render(self._terminal_size()[0])
|
|
632
|
+
]
|
|
633
|
+
return self._content_cache
|
|
634
|
+
|
|
615
635
|
def _selection_source_line(self, row: int) -> str:
|
|
616
|
-
|
|
636
|
+
lines = self._content_lines() if self.selection_in_content else self.previous_screen
|
|
637
|
+
return lines[row] if 0 <= row < len(lines) else ""
|
|
638
|
+
|
|
639
|
+
def autoscroll_tick(self) -> bool:
|
|
640
|
+
"""Called every frame by the app's render loop: while a drag is parked
|
|
641
|
+
on the viewport's top/bottom edge, scroll one line and extend the
|
|
642
|
+
selection to the newly revealed edge row (motion events stop when the
|
|
643
|
+
mouse sits still, so this can't be event-driven). True if it scrolled."""
|
|
644
|
+
if not (self.selection_press_active and self.selection_in_content and self.drag_edge):
|
|
645
|
+
return False
|
|
646
|
+
now = _time.monotonic()
|
|
647
|
+
if now - self._last_autoscroll < 0.04:
|
|
648
|
+
return False
|
|
649
|
+
region = self._scroll_region()
|
|
650
|
+
if region is None:
|
|
651
|
+
return False
|
|
652
|
+
sv, offset, height = region
|
|
653
|
+
if sv.scroll_by(self.drag_edge) == self.drag_edge:
|
|
654
|
+
return False # already at the end of the content
|
|
655
|
+
self._last_autoscroll = now
|
|
656
|
+
edge_row = offset if self.drag_edge < 0 else offset + height - 1
|
|
657
|
+
self._update_selection_focus(SelectionPoint(edge_row - offset + sv.scroll_top, self._drag_x))
|
|
658
|
+
return True
|
|
617
659
|
|
|
618
660
|
def _word_selection(self, point: SelectionPoint) -> tuple[SelectionPoint, SelectionPoint] | None:
|
|
619
661
|
line = strip_terminal_sequences(self._selection_source_line(point.row))
|
|
@@ -702,8 +744,17 @@ class TuiAltScreen:
|
|
|
702
744
|
if not sel:
|
|
703
745
|
return screen
|
|
704
746
|
start, end = sel
|
|
747
|
+
region = self._scroll_region() if self.selection_in_content else None
|
|
748
|
+
if self.selection_in_content and region is None:
|
|
749
|
+
return screen
|
|
705
750
|
|
|
706
|
-
def transform(
|
|
751
|
+
def transform(screen_row: int, line: str) -> str:
|
|
752
|
+
row = screen_row
|
|
753
|
+
if region:
|
|
754
|
+
sv, offset, height = region
|
|
755
|
+
if not offset <= screen_row < offset + height:
|
|
756
|
+
return line
|
|
757
|
+
row = screen_row - offset + sv.scroll_top
|
|
707
758
|
if row < start.row or row > end.row:
|
|
708
759
|
return line
|
|
709
760
|
col_start, col_end = self._selection_columns(line, row, sel)
|
|
@@ -782,6 +833,12 @@ class TuiAltScreen:
|
|
|
782
833
|
button, x, y, kind = int(m.group(1)), int(m.group(2)) - 1, int(m.group(3)) - 1, m.group(4)
|
|
783
834
|
return {"button": button, "x": x, "y": y, "release": kind == "m"}
|
|
784
835
|
|
|
836
|
+
def _content_point(self, region, point: SelectionPoint) -> SelectionPoint:
|
|
837
|
+
"""Screen point -> content-row point, clamped into the viewport."""
|
|
838
|
+
sv, offset, height = region
|
|
839
|
+
screen_row = max(offset, min(offset + height - 1, point.row))
|
|
840
|
+
return SelectionPoint(screen_row - offset + sv.scroll_top, point.col)
|
|
841
|
+
|
|
785
842
|
def handle_selection_mouse_event(self, event: dict) -> None:
|
|
786
843
|
term_width, term_height = self._terminal_size()
|
|
787
844
|
button = event["button"] & 3
|
|
@@ -791,10 +848,14 @@ class TuiAltScreen:
|
|
|
791
848
|
row=max(0, min(term_height - 1, event["y"])),
|
|
792
849
|
col=max(0, min(term_width - 1, event["x"])),
|
|
793
850
|
)
|
|
851
|
+
region = self._scroll_region()
|
|
852
|
+
if self.selection_press_active and self.selection_in_content and region:
|
|
853
|
+
point = self._content_point(region, point)
|
|
794
854
|
if event["release"]:
|
|
795
855
|
if not self.selection_press_active:
|
|
796
856
|
return
|
|
797
857
|
self.selection_press_active = False
|
|
858
|
+
self.drag_edge = 0
|
|
798
859
|
if self.selection_anchor is None:
|
|
799
860
|
return
|
|
800
861
|
self._update_selection_focus(point)
|
|
@@ -804,10 +865,20 @@ class TuiAltScreen:
|
|
|
804
865
|
if not self.selection_press_active or self.selection_anchor is None:
|
|
805
866
|
return
|
|
806
867
|
self._last_click = None
|
|
868
|
+
if self.selection_in_content and region:
|
|
869
|
+
sv, offset, height = region
|
|
870
|
+
self._drag_x = point.col
|
|
871
|
+
self.drag_edge = -1 if event["y"] <= offset else (1 if event["y"] >= offset + height - 1 else 0)
|
|
807
872
|
self._update_selection_focus(point)
|
|
808
873
|
return
|
|
809
874
|
# fresh press
|
|
810
875
|
self.selection_press_active = True
|
|
876
|
+
self.drag_edge = 0
|
|
877
|
+
self.selection_in_content = bool(
|
|
878
|
+
region and region[1] <= point.row < region[1] + region[2] and not self.has_overlay()
|
|
879
|
+
)
|
|
880
|
+
if self.selection_in_content:
|
|
881
|
+
point = self._content_point(region, point)
|
|
811
882
|
word = self._word_selection(point)
|
|
812
883
|
click_count = self._get_click_count(point, word)
|
|
813
884
|
rng = word if click_count == 2 else (self._line_selection(point) if click_count == 3 else None)
|
|
@@ -831,6 +902,7 @@ class TuiAltScreen:
|
|
|
831
902
|
def do_render(self) -> None:
|
|
832
903
|
width, height = self._terminal_size()
|
|
833
904
|
width, height = max(1, width), max(1, height)
|
|
905
|
+
self._content_cache = None
|
|
834
906
|
|
|
835
907
|
layout_lines = render_layout_frame(self.root, width, height)
|
|
836
908
|
screen = [strip_osc133_zone(line) for line in layout_lines]
|
|
@@ -11,6 +11,8 @@ feature this mirrors.
|
|
|
11
11
|
|
|
12
12
|
import asyncio
|
|
13
13
|
import json
|
|
14
|
+
import os
|
|
15
|
+
import time
|
|
14
16
|
from pathlib import Path
|
|
15
17
|
|
|
16
18
|
from micro_cc.utils.msg_store_ import _get_storage_dir
|
|
@@ -18,6 +20,45 @@ from micro_cc.utils.msg_store_ import _get_storage_dir
|
|
|
18
20
|
_SEND_TIMEOUT = 3.0
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
def _registry_dir() -> Path:
|
|
24
|
+
return Path.home() / ".micro-cc" / "sessions"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _pid_alive(pid: int) -> bool:
|
|
28
|
+
try:
|
|
29
|
+
os.kill(pid, 0)
|
|
30
|
+
except ProcessLookupError:
|
|
31
|
+
return False
|
|
32
|
+
except PermissionError:
|
|
33
|
+
return True
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def list_peers(exclude_project_dir: str | None = None) -> list[dict]:
|
|
38
|
+
"""Live TUI sessions registered in ~/.micro-cc/sessions/<pid>.json (what
|
|
39
|
+
start_listener writes), minus `exclude_project_dir` (the caller). Entries
|
|
40
|
+
whose pid is dead or whose socket is gone are swept."""
|
|
41
|
+
me = os.path.abspath(os.path.expanduser(exclude_project_dir)) if exclude_project_dir else None
|
|
42
|
+
peers = []
|
|
43
|
+
for f in _registry_dir().glob("*.json"):
|
|
44
|
+
try:
|
|
45
|
+
info = json.loads(f.read_text())
|
|
46
|
+
pid, pd = int(info["pid"]), info["project_dir"]
|
|
47
|
+
except (OSError, ValueError, KeyError, TypeError):
|
|
48
|
+
continue
|
|
49
|
+
if not _pid_alive(pid) or not socket_path(pd).exists():
|
|
50
|
+
f.unlink(missing_ok=True)
|
|
51
|
+
continue
|
|
52
|
+
if pd != me:
|
|
53
|
+
peers.append(info)
|
|
54
|
+
return sorted(peers, key=lambda i: i.get("started_at", 0))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def format_peers(exclude_project_dir: str | None = None) -> str:
|
|
58
|
+
"""One line per peer, for the per-loop status reminder; "" if none."""
|
|
59
|
+
return "\n".join(f"- {p['project_dir']} (pid {p['pid']})" for p in list_peers(exclude_project_dir))
|
|
60
|
+
|
|
61
|
+
|
|
21
62
|
def socket_path(project_dir: str) -> Path:
|
|
22
63
|
return _get_storage_dir(project_dir) / "session.sock"
|
|
23
64
|
|
|
@@ -36,6 +77,15 @@ async def start_listener(project_dir: str, on_message):
|
|
|
36
77
|
path = socket_path(project_dir)
|
|
37
78
|
if path.exists():
|
|
38
79
|
path.unlink()
|
|
80
|
+
try:
|
|
81
|
+
_registry_dir().mkdir(parents=True, exist_ok=True)
|
|
82
|
+
(_registry_dir() / f"{os.getpid()}.json").write_text(json.dumps({
|
|
83
|
+
"pid": os.getpid(),
|
|
84
|
+
"project_dir": os.path.abspath(os.path.expanduser(project_dir)),
|
|
85
|
+
"started_at": time.time(),
|
|
86
|
+
}))
|
|
87
|
+
except OSError:
|
|
88
|
+
pass # discovery is best-effort; messaging by explicit path still works
|
|
39
89
|
|
|
40
90
|
async def handle(reader, writer):
|
|
41
91
|
try:
|
|
@@ -54,6 +104,7 @@ async def start_listener(project_dir: str, on_message):
|
|
|
54
104
|
|
|
55
105
|
def stop_listener(project_dir: str, server) -> None:
|
|
56
106
|
server.close()
|
|
107
|
+
(_registry_dir() / f"{os.getpid()}.json").unlink(missing_ok=True)
|
|
57
108
|
path = socket_path(project_dir)
|
|
58
109
|
if path.exists():
|
|
59
110
|
path.unlink()
|
|
@@ -13,10 +13,10 @@ from list membership:
|
|
|
13
13
|
3. When do we free the entry? — `prune`, a fully separate, low-stakes pass
|
|
14
14
|
that only ever touches entries where (1) and (2) are already resolved.
|
|
15
15
|
|
|
16
|
-
Populated
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
Populated only by start_headless_.py's self-registration on process startup
|
|
17
|
+
(`add_tracked`), off MICROCC_CALLER_PROJECT_DIR — which bash_ stamps on every
|
|
18
|
+
subprocess, so a boss-spawned run always registers itself. monitor_ is
|
|
19
|
+
read-only: checking a dir never creates an entry.
|
|
20
20
|
|
|
21
21
|
This is what survives both /clear (a different file, untouched by
|
|
22
22
|
cmd_clear's erase_msgs/erase_summary/clear_todos) and a full microcc
|
|
@@ -103,7 +103,7 @@ _TERMINAL_STATUSES = {"DONE", "FAILED"}
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
class NameInUse(Exception):
|
|
106
|
-
"""add_tracked
|
|
106
|
+
"""add_tracked refused: `subagent_project_dir` already has a live
|
|
107
107
|
RUNNING run. Without this, two concurrent microcc-headless invocations against
|
|
108
108
|
the same project_dir would silently share one tracker entry AND one
|
|
109
109
|
messages.jsonl conversation history — a real corruption risk, not just a
|
|
@@ -111,7 +111,7 @@ class NameInUse(Exception):
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
class CapReached(Exception):
|
|
114
|
-
"""add_tracked(
|
|
114
|
+
"""add_tracked(max_running=N) refused: N other subagents of this
|
|
115
115
|
boss are already live. Checked under the same flock as the write, so N
|
|
116
116
|
simultaneous spawns can't all read "N-1 running" and all register."""
|
|
117
117
|
|
|
@@ -127,14 +127,14 @@ def _pid_alive(pid) -> bool:
|
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
def add_tracked(
|
|
130
|
-
caller_project_dir: str, subagent_project_dir: str, *,
|
|
130
|
+
caller_project_dir: str, subagent_project_dir: str, *,
|
|
131
131
|
max_running: int | None = None,
|
|
132
132
|
) -> None:
|
|
133
|
-
"""
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
"""start_headless_.py's self-registration — a microcc-headless process
|
|
134
|
+
announcing "I am starting a run right now," including a legitimate
|
|
135
|
+
relaunch of a project_dir that already finished once before.
|
|
136
|
+
(Over)writes a brand-new RUNNING/not-notified entry, which is what lets a
|
|
137
|
+
relaunch notify again on its second completion — UNLESS
|
|
138
138
|
`subagent_project_dir` is currently claimed by another still-alive
|
|
139
139
|
RUNNING process, in which case this raises NameInUse instead of
|
|
140
140
|
silently clobbering it. The liveness check and the write happen under
|
|
@@ -143,29 +143,13 @@ def add_tracked(
|
|
|
143
143
|
entry (dead pid — crashed without ever updating status) is still safely
|
|
144
144
|
reclaimed.
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if the key is entirely absent; if the key is already present, this is a
|
|
149
|
-
no-op. This function alone can't tell "genuinely never tracked" apart
|
|
150
|
-
from "was tracked, finished, and already got pruned" — both look like
|
|
151
|
-
"key absent" from here, and inserting a RUNNING/no-pid placeholder for
|
|
152
|
-
the latter is a real bug: that placeholder never gets a pid (only
|
|
153
|
-
fresh=True sets one), so the poller's pid-liveness self-heal
|
|
154
|
-
(start_live_.py's _poll_subagents_tick) can never clear it, and it sits
|
|
155
|
-
on the status line forever, unnotified. The guard against that lives on
|
|
156
|
-
the caller's side instead — monitor_ only calls this when the
|
|
157
|
-
status it just read for subagent_project_dir isn't already DONE/FAILED,
|
|
158
|
-
since a call that already knows the terminal answer has nothing left to
|
|
159
|
-
watch for. Don't drop that caller-side check without replacing it.
|
|
160
|
-
|
|
161
|
-
max_running (fresh=True only): refuse with CapReached if that many OTHER
|
|
162
|
-
subagents of this boss are live — same flock as the NameInUse check.
|
|
163
|
-
Placeholder entries (monitor_'s passive check-in, no pid) don't count.
|
|
146
|
+
max_running: refuse with CapReached if that many OTHER subagents of this
|
|
147
|
+
boss are live — same flock as the NameInUse check.
|
|
164
148
|
"""
|
|
165
149
|
def _do():
|
|
166
150
|
data = _read(caller_project_dir)
|
|
167
151
|
subagents = data["subagents"]
|
|
168
|
-
if
|
|
152
|
+
if max_running is not None:
|
|
169
153
|
running = [
|
|
170
154
|
pd for pd, e in subagents.items()
|
|
171
155
|
if pd != subagent_project_dir and e.get("status") == "RUNNING"
|
|
@@ -178,37 +162,31 @@ def add_tracked(
|
|
|
178
162
|
f"{max_running} (the user can change it with /subagents). Wait for one to "
|
|
179
163
|
f"finish (you get a checkpoint wakeup), or stop one, then spawn again."
|
|
180
164
|
)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
_write(caller_project_dir, data)
|
|
207
|
-
elif subagent_project_dir not in subagents:
|
|
208
|
-
subagents[subagent_project_dir] = {
|
|
209
|
-
"status": "RUNNING", "notified": False, "updated_at": time.time(),
|
|
210
|
-
}
|
|
211
|
-
_write(caller_project_dir, data)
|
|
165
|
+
existing = subagents.get(subagent_project_dir)
|
|
166
|
+
if existing and existing.get("status") == "RUNNING" and existing.get("pid") is not None:
|
|
167
|
+
try:
|
|
168
|
+
os.kill(existing["pid"], 0)
|
|
169
|
+
except ProcessLookupError:
|
|
170
|
+
pass # stale entry (crashed without updating status) — safe to reclaim
|
|
171
|
+
else:
|
|
172
|
+
raise NameInUse(
|
|
173
|
+
f"'{subagent_project_dir}' already has a run in progress "
|
|
174
|
+
f"(pid {existing['pid']}) — wait for it to finish, or check "
|
|
175
|
+
f"monitor_ before relaunching."
|
|
176
|
+
)
|
|
177
|
+
subagents[subagent_project_dir] = {
|
|
178
|
+
"status": "RUNNING", "notified": False, "updated_at": time.time(),
|
|
179
|
+
# This runs inside the microcc-headless process itself (see
|
|
180
|
+
# start_headless_.py's run_headless), so os.getpid() here IS
|
|
181
|
+
# the subagent for its whole lifetime — lets the ambient
|
|
182
|
+
# poller (start_live_.py's _poll_subagents_tick) tell "still
|
|
183
|
+
# actually running" apart from "killed/crashed before ever
|
|
184
|
+
# writing a terminal STATUS line," which would otherwise
|
|
185
|
+
# leave the entry stuck RUNNING forever (nothing else ever
|
|
186
|
+
# updates status for a process that's dead).
|
|
187
|
+
"pid": os.getpid(),
|
|
188
|
+
}
|
|
189
|
+
_write(caller_project_dir, data)
|
|
212
190
|
_with_lock(caller_project_dir, _do)
|
|
213
191
|
|
|
214
192
|
|
|
@@ -196,9 +196,9 @@ class CapTests(unittest.TestCase):
|
|
|
196
196
|
"/x/placeholder": {"status": "RUNNING", "notified": False},
|
|
197
197
|
"/x/done": {"status": "DONE", "notified": True, "pid": os.getpid()},
|
|
198
198
|
}}))
|
|
199
|
-
tracker.add_tracked(boss, "/x/new1",
|
|
199
|
+
tracker.add_tracked(boss, "/x/new1", max_running=2) # 1 live other -> ok
|
|
200
200
|
with self.assertRaises(tracker.CapReached) as cm:
|
|
201
|
-
tracker.add_tracked(boss, "/x/new2",
|
|
201
|
+
tracker.add_tracked(boss, "/x/new2", max_running=2)
|
|
202
202
|
self.assertIn("limit is 2", str(cm.exception))
|
|
203
203
|
|
|
204
204
|
def test_cap_holds_under_simultaneous_registration_by_real_processes(self):
|
|
@@ -208,7 +208,7 @@ class CapTests(unittest.TestCase):
|
|
|
208
208
|
"import os,sys,time\n"
|
|
209
209
|
"from micro_cc.utils import subagent_tracker_ as t\n"
|
|
210
210
|
"try:\n"
|
|
211
|
-
f" t.add_tracked({boss!r}, sys.argv[1],
|
|
211
|
+
f" t.add_tracked({boss!r}, sys.argv[1], max_running=2); print('OK', flush=True)\n"
|
|
212
212
|
"except t.CapReached: print('CAP', flush=True)\n"
|
|
213
213
|
f"while not os.path.exists({release!r}): time.sleep(0.05)\n"
|
|
214
214
|
)
|
|
@@ -339,7 +339,7 @@ class DetachedSpawnTests(unittest.TestCase):
|
|
|
339
339
|
settings_store_.edit_setting("max_subagents", 1)
|
|
340
340
|
holder = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(60)"])
|
|
341
341
|
try:
|
|
342
|
-
tracker.add_tracked(self.boss, "/x/busy"
|
|
342
|
+
tracker.add_tracked(self.boss, "/x/busy")
|
|
343
343
|
data = tracker.read_tracked(self.boss)
|
|
344
344
|
data["subagents"]["/x/busy"]["pid"] = holder.pid
|
|
345
345
|
(_get_storage_dir(self.boss) / "tracked_subagents.json").write_text(json.dumps(data))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|