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.
Files changed (241) hide show
  1. {micro_cc-0.2.97/src/micro_cc.egg-info → micro_cc-0.2.99}/PKG-INFO +1 -1
  2. {micro_cc-0.2.97 → micro_cc-0.2.99}/pyproject.toml +1 -1
  3. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/claude_loop_.py +9 -0
  4. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/start_headless_.py +1 -1
  5. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/start_live_tui_.py +9 -30
  6. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/message_session_.py +3 -0
  7. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/monitor_.py +8 -13
  8. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/alt_screen_.py +81 -9
  9. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/session_ipc_.py +51 -0
  10. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/subagent_tracker_.py +40 -62
  11. {micro_cc-0.2.97 → micro_cc-0.2.99/src/micro_cc.egg-info}/PKG-INFO +1 -1
  12. {micro_cc-0.2.97 → micro_cc-0.2.99}/tests/test_subagent_lifecycle.py +4 -4
  13. {micro_cc-0.2.97 → micro_cc-0.2.99}/LICENSE +0 -0
  14. {micro_cc-0.2.97 → micro_cc-0.2.99}/README.md +0 -0
  15. {micro_cc-0.2.97 → micro_cc-0.2.99}/setup.cfg +0 -0
  16. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/__init__.py +0 -0
  17. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/browser/__init__.py +0 -0
  18. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/browser/_cookies.py +0 -0
  19. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/browser/_md_convert.py +0 -0
  20. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/browser/browser_manager.py +0 -0
  21. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/browser/simpletextbrowser.py +0 -0
  22. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/cache/__init__.py +0 -0
  23. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/cache/state_store.py +0 -0
  24. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/execute_tool.py +0 -0
  25. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/__init__.py +0 -0
  26. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/adapters/__init__.py +0 -0
  27. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/adapters/anthropic.py +0 -0
  28. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/adapters/chat.py +0 -0
  29. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/adapters/responses.py +0 -0
  30. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/anthropic.py +0 -0
  31. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/client.py +0 -0
  32. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/foundry.py +0 -0
  33. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/litellm.py +0 -0
  34. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/ollama.py +0 -0
  35. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/openai.py +0 -0
  36. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/openrouter.py +0 -0
  37. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/priming.py +0 -0
  38. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/registry.py +0 -0
  39. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/schema.py +0 -0
  40. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/models/types.py +0 -0
  41. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/postgres_store/__init__.py +0 -0
  42. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/postgres_store/pg_store_.py +0 -0
  43. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/postgres_store/schema_.py +0 -0
  44. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/self_heal_.py +0 -0
  45. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/SKILL.md +0 -0
  46. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/ArsenalSC-OFL.txt +0 -0
  47. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  48. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  49. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-OFL.txt +0 -0
  50. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  51. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Boldonse-OFL.txt +0 -0
  52. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  53. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  54. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -0
  55. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  56. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  57. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  58. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-OFL.txt +0 -0
  59. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  60. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/DMMono-OFL.txt +0 -0
  61. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/DMMono-Regular.ttf +0 -0
  62. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/EricaOne-OFL.txt +0 -0
  63. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  64. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  65. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/GeistMono-OFL.txt +0 -0
  66. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  67. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Gloock-OFL.txt +0 -0
  68. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Gloock-Regular.ttf +0 -0
  69. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  70. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-OFL.txt +0 -0
  71. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  72. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  73. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  74. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  75. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  76. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  77. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  78. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  79. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-OFL.txt +0 -0
  80. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  81. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  82. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  83. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Italiana-OFL.txt +0 -0
  84. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Italiana-Regular.ttf +0 -0
  85. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  86. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-OFL.txt +0 -0
  87. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  88. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Jura-Light.ttf +0 -0
  89. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Jura-Medium.ttf +0 -0
  90. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Jura-OFL.txt +0 -0
  91. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/LibreBaskerville-OFL.txt +0 -0
  92. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  93. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Lora-Bold.ttf +0 -0
  94. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  95. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Lora-Italic.ttf +0 -0
  96. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Lora-OFL.txt +0 -0
  97. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Lora-Regular.ttf +0 -0
  98. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  99. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/NationalPark-OFL.txt +0 -0
  100. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  101. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -0
  102. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  103. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Outfit-Bold.ttf +0 -0
  104. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Outfit-OFL.txt +0 -0
  105. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Outfit-Regular.ttf +0 -0
  106. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  107. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/PixelifySans-OFL.txt +0 -0
  108. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/PoiretOne-OFL.txt +0 -0
  109. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  110. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  111. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-OFL.txt +0 -0
  112. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  113. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Silkscreen-OFL.txt +0 -0
  114. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  115. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  116. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/SmoochSans-OFL.txt +0 -0
  117. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Tektur-Medium.ttf +0 -0
  118. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Tektur-OFL.txt +0 -0
  119. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/Tektur-Regular.ttf +0 -0
  120. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  121. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  122. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  123. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/WorkSans-OFL.txt +0 -0
  124. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  125. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/YoungSerif-OFL.txt +0 -0
  126. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  127. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/design/references/blueprint-tokens.md +0 -0
  128. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/docx/SKILL.md +0 -0
  129. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/docx/docx-js.md +0 -0
  130. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/docx/ooxml.md +0 -0
  131. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/github/SKILL.md +0 -0
  132. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/graph-orchestration/SKILL.md +0 -0
  133. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/RENDERER.md +0 -0
  134. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/SKILL.md +0 -0
  135. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/TIMING.md +0 -0
  136. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/analysis/align.py +0 -0
  137. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/analysis/build_data.py +0 -0
  138. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/analysis/features.py +0 -0
  139. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/analysis/transcribe.py +0 -0
  140. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/check.mjs +0 -0
  141. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/install_stack.sh +0 -0
  142. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/render.mjs +0 -0
  143. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/painted-video/scripts/scaffold.py +0 -0
  144. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/SKILL.md +0 -0
  145. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/brief.md +0 -0
  146. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/scripts/build_deck.py +0 -0
  147. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/scripts/deck_to_images.py +0 -0
  148. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/scripts/office/__init__.py +0 -0
  149. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/scripts/office/soffice.py +0 -0
  150. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/scripts/render_svg.py +0 -0
  151. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/pptx/svg-slides.md +0 -0
  152. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/skill_loader.py +0 -0
  153. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/system-design-review/REFERENCE.md +0 -0
  154. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/system-design-review/SKILL.md +0 -0
  155. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/system-design-review/scripts/design_graph.py +0 -0
  156. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/system-design-review/scripts/design_review.py +0 -0
  157. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/skills/xlsx/SKILL.md +0 -0
  158. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/__init__.py +0 -0
  159. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/ask_user_tool.py +0 -0
  160. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/bash_tool.py +0 -0
  161. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/browser_tool_.py +0 -0
  162. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/computer_tool_.py +0 -0
  163. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/file_tools_.py +0 -0
  164. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/mcp_client_.py +0 -0
  165. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/memory_tool_.py +0 -0
  166. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/monitor_schedule_.py +0 -0
  167. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/monitor_schedule_runtime.py +0 -0
  168. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/monitor_watch_.py +0 -0
  169. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/search_history_tool_.py +0 -0
  170. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/search_tool_.py +0 -0
  171. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/skill_tools_.py +0 -0
  172. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/todo_tools_.py +0 -0
  173. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/vision_tools_.py +0 -0
  174. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tools/web_tools_.py +0 -0
  175. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/__init__.py +0 -0
  176. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/detect_images_.py +0 -0
  177. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/diff_viewer_.py +0 -0
  178. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/etype_handler_.py +0 -0
  179. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/keys_.py +0 -0
  180. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/kill_ring_.py +0 -0
  181. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/list_picker_.py +0 -0
  182. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/md_render_.py +0 -0
  183. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/message_row_.py +0 -0
  184. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/multi_select_list_.py +0 -0
  185. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/prompt_input_.py +0 -0
  186. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/question_panel_.py +0 -0
  187. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/screen_cmds_.py +0 -0
  188. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/scroll_view_.py +0 -0
  189. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/self_terminal_setup_.py +0 -0
  190. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/self_update_.py +0 -0
  191. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/stack_.py +0 -0
  192. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/stdin_buffer_.py +0 -0
  193. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/terminal_.py +0 -0
  194. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/text_.py +0 -0
  195. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/text_utils_.py +0 -0
  196. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/theme_.py +0 -0
  197. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/truncated_text_.py +0 -0
  198. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/undo_stack_.py +0 -0
  199. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/tui_native/word_navigation_.py +0 -0
  200. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/__init__.py +0 -0
  201. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/annotate_.py +0 -0
  202. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/banner_.py +0 -0
  203. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/claude_md_loader.py +0 -0
  204. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/claude_subscription.py +0 -0
  205. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/command_registry.py +0 -0
  206. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/default_status_sh.py +0 -0
  207. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/helpers.py +0 -0
  208. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/hidden_prompts.py +0 -0
  209. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/hints.py +0 -0
  210. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/history_mount_.py +0 -0
  211. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/inbox_store_.py +0 -0
  212. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/memory_store_.py +0 -0
  213. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/msg_normalize_.py +0 -0
  214. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/msg_store_.py +0 -0
  215. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/project_files_.py +0 -0
  216. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/self_reload_.py +0 -0
  217. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/settings_store_.py +0 -0
  218. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/status_marker.py +0 -0
  219. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/terminal_setup.py +0 -0
  220. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/theme_store_.py +0 -0
  221. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/tokenization_simple.py +0 -0
  222. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/tool_limits.py +0 -0
  223. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/utils/tool_result_storage.py +0 -0
  224. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/__init__.py +0 -0
  225. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/bridge.py +0 -0
  226. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/dist/assets/index-9B5FcRm2.css +0 -0
  227. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/dist/assets/index-CDG9HO3B.js +0 -0
  228. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/dist/index.html +0 -0
  229. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/history.py +0 -0
  230. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/launch.py +0 -0
  231. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/server.py +0 -0
  232. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc/webui/session.py +0 -0
  233. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc.egg-info/SOURCES.txt +0 -0
  234. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc.egg-info/dependency_links.txt +0 -0
  235. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc.egg-info/entry_points.txt +0 -0
  236. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc.egg-info/requires.txt +0 -0
  237. {micro_cc-0.2.97 → micro_cc-0.2.99}/src/micro_cc.egg-info/top_level.txt +0 -0
  238. {micro_cc-0.2.97 → micro_cc-0.2.99}/tests/test_model_consumers.py +0 -0
  239. {micro_cc-0.2.97 → micro_cc-0.2.99}/tests/test_models.py +0 -0
  240. {micro_cc-0.2.97 → micro_cc-0.2.99}/tests/test_schedule.py +0 -0
  241. {micro_cc-0.2.97 → micro_cc-0.2.99}/tests/test_self_reload.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: micro-cc
3
- Version: 0.2.97
3
+ Version: 0.2.99
4
4
  Summary: Harness that gives frontier models full system access — shell, filesystem, browser, MCP — running directly on the metal.
5
5
  Author: George Salapa
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "micro-cc"
7
- version = "0.2.97"
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, fresh=True, max_running=max_subagents())
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-to-scroll: handle_selection_mouse_event only ever grows
783
- # the selection range, it never moves the viewport — so a drag
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's fresh=True path), so without
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 fresh=True
2861
- add_tracked call is what resets notified for the next checkpoint."""
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's fresh=True path) — older
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 add_tracked, read_tracked
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 own prune-eligible set (DONE/FAILED — the
20
- # statuses that make an entry a DEAD END, see subagent_tracker_.py's
21
- # _TERMINAL_STATUSES). Used below to stop the passive add_tracked call from
22
- # resurrecting an already-pruned entry: if get_subagent_status just told us
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: a subagent's project_dir (absolute path, exactly as passed
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
- # Selection reads/writes directly against the composited screen
418
- # (self.previous_screen), bounded by terminal rows/columns. It does not
419
- # select inside a ScrollView's off-screen content that's scrolled out of
420
- # view — only what's currently painted. Needed to go further: routing a
421
- # selection point to the specific ScrollView under the cursor and reading
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
- return self.previous_screen[row] if 0 <= row < len(self.previous_screen) else ""
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(row: int, line: str) -> str:
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 two ways, both additive:
17
- - start_headless_.py's self-registration on process startup
18
- (`add_tracked(..., fresh=True)`), off MICROCC_CALLER_PROJECT_DIR.
19
- - monitor_'s passive check-in (`add_tracked(..., fresh=False)`).
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(fresh=True) refused: `subagent_project_dir` already has a live
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(fresh=True, max_running=N) refused: N other subagents of this
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, *, fresh: bool = False,
130
+ caller_project_dir: str, subagent_project_dir: str, *,
131
131
  max_running: int | None = None,
132
132
  ) -> None:
133
- """fresh=True is start_headless_.py's self-registration — a
134
- microcc-headless process announcing "I am starting a run right now,"
135
- including a legitimate relaunch of a project_dir that already finished
136
- once before. (Over)writes a brand-new RUNNING/not-notified entry, which
137
- is what lets a relaunch notify again on its second completion — UNLESS
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
- fresh=False (the default) is monitor_'s passive "check on
147
- this" call — no run is starting, so it only inserts a placeholder entry
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 fresh and max_running is not None:
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
- if fresh:
182
- existing = subagents.get(subagent_project_dir)
183
- if existing and existing.get("status") == "RUNNING" and existing.get("pid") is not None:
184
- try:
185
- os.kill(existing["pid"], 0)
186
- except ProcessLookupError:
187
- pass # stale entry (crashed without updating status) — safe to reclaim
188
- else:
189
- raise NameInUse(
190
- f"'{subagent_project_dir}' already has a run in progress "
191
- f"(pid {existing['pid']}) — wait for it to finish, or check "
192
- f"monitor_ before relaunching."
193
- )
194
- subagents[subagent_project_dir] = {
195
- "status": "RUNNING", "notified": False, "updated_at": time.time(),
196
- # This runs inside the microcc-headless process itself (see
197
- # start_headless_.py's run_headless), so os.getpid() here IS
198
- # the subagent for its whole lifetime — lets the ambient
199
- # poller (start_live_.py's _poll_subagents_tick) tell "still
200
- # actually running" apart from "killed/crashed before ever
201
- # writing a terminal STATUS line," which would otherwise
202
- # leave the entry stuck RUNNING forever (nothing else ever
203
- # updates status for a process that's dead).
204
- "pid": os.getpid(),
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: micro-cc
3
- Version: 0.2.97
3
+ Version: 0.2.99
4
4
  Summary: Harness that gives frontier models full system access — shell, filesystem, browser, MCP — running directly on the metal.
5
5
  Author: George Salapa
6
6
  License: MIT License
@@ -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", fresh=True, max_running=2) # 1 live other -> ok
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", fresh=True, max_running=2)
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], fresh=True, max_running=2); print('OK', flush=True)\n"
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", fresh=True)
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