monoco-toolkit 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. monoco/core/agent/adapters.py +24 -1
  2. monoco/core/config.py +77 -17
  3. monoco/core/integrations.py +8 -0
  4. monoco/core/lsp.py +7 -0
  5. monoco/core/output.py +8 -1
  6. monoco/core/resources/zh/SKILL.md +6 -7
  7. monoco/core/setup.py +8 -0
  8. monoco/features/i18n/resources/zh/SKILL.md +5 -5
  9. monoco/features/issue/commands.py +135 -55
  10. monoco/features/issue/core.py +157 -122
  11. monoco/features/issue/domain/__init__.py +0 -0
  12. monoco/features/issue/domain/lifecycle.py +126 -0
  13. monoco/features/issue/domain/models.py +170 -0
  14. monoco/features/issue/domain/parser.py +223 -0
  15. monoco/features/issue/domain/workspace.py +104 -0
  16. monoco/features/issue/engine/__init__.py +22 -0
  17. monoco/features/issue/engine/config.py +172 -0
  18. monoco/features/issue/engine/machine.py +185 -0
  19. monoco/features/issue/engine/models.py +18 -0
  20. monoco/features/issue/linter.py +32 -11
  21. monoco/features/issue/lsp/__init__.py +3 -0
  22. monoco/features/issue/lsp/definition.py +72 -0
  23. monoco/features/issue/models.py +26 -9
  24. monoco/features/issue/resources/zh/SKILL.md +8 -9
  25. monoco/features/issue/validator.py +181 -65
  26. monoco/features/spike/core.py +5 -22
  27. monoco/features/spike/resources/zh/SKILL.md +2 -2
  28. monoco/main.py +2 -26
  29. monoco_toolkit-0.2.7.dist-info/METADATA +129 -0
  30. {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/RECORD +33 -27
  31. monoco/features/agent/commands.py +0 -166
  32. monoco/features/agent/doctor.py +0 -30
  33. monoco/features/pty/core.py +0 -185
  34. monoco/features/pty/router.py +0 -138
  35. monoco/features/pty/server.py +0 -56
  36. monoco_toolkit-0.2.5.dist-info/METADATA +0 -93
  37. {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/WHEEL +0 -0
  38. {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/entry_points.txt +0 -0
  39. {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/licenses/LICENSE +0 -0
@@ -1,18 +1,18 @@
1
- monoco/main.py,sha256=MhS-vdL05KGZeEYU4XNbJEFXuNyRUyTFiZ1hrmxNCMs,6098
1
+ monoco/main.py,sha256=wWOtB54RB4_opq_DuRQyEStAYbJHgGCZfQglUup6q28,5370
2
2
  monoco/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  monoco/cli/project.py,sha256=w4oUWzOV42qh34g4klyzZfTUg6ux0oEdSinMCzmhH3E,2786
4
4
  monoco/cli/workspace.py,sha256=vhRsbjgO6ek6PWYdmhk9EeYW_xWRV196yl7-xPfcRAI,1305
5
5
  monoco/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- monoco/core/config.py,sha256=WJvYtLCLG4sRYcg46-fD7D5Uv32Ex1KQXD-i257hZGs,10781
6
+ monoco/core/config.py,sha256=UpP6OE3wex8ZesnMZVZKPRy1peHrjq8nXv_BVtZzNIk,12105
7
7
  monoco/core/execution.py,sha256=rySL0ueJvTEQ1JhdpumsNBQHJpj08nfaHQMlX-1uh6k,1846
8
8
  monoco/core/feature.py,sha256=oAtQWUHMuqIFbq2gj8sPfYBlJZvMMXZqyUlCMrIVGUE,2007
9
9
  monoco/core/git.py,sha256=i_NKT9_n57S9EKeb8kF2yTEdw70FZ88-lpMqW4lzVds,8228
10
10
  monoco/core/injection.py,sha256=4BuQ5RVozyU99uSOzPwgc6MFMZUcECerTik5lF39I9A,7078
11
- monoco/core/integrations.py,sha256=QRZjCwHb6xudIoxrFSE_gvQKNWlIOXGn-SxZelqqrWw,7667
12
- monoco/core/lsp.py,sha256=aif3iMgfpyCrBL1qlbakJerjT--ePwoWnRa-5CCMMkY,1844
13
- monoco/core/output.py,sha256=9JbCxqxHZlviNJXpUorGVxv_gAn4q72FPgjwBQrPiCI,3502
11
+ monoco/core/integrations.py,sha256=MGw6dCipX2S5GtnboFwVxpkuWMGa_I6T9s0owsIjU4o,7885
12
+ monoco/core/lsp.py,sha256=23OxtVomvvE0GgAbdw_8-DclIac1SzjJGt8urbPKXEA,2006
13
+ monoco/core/output.py,sha256=S5jbOBRuLleBPfypS5nUUoNIUAdub4zJO63RePLb0LU,3787
14
14
  monoco/core/registry.py,sha256=xix21TDeJgRiZ2kvHitnac4RsqMdp1aERpYGSrwfIpY,1180
15
- monoco/core/setup.py,sha256=gx_jrSlRpHxIisuA3JUG-dnHTzIpq4Jq5zz_5Y2MkNY,9313
15
+ monoco/core/setup.py,sha256=5fAsj4HZAC1sG-pEn5hKTZ6JsCAqNa8-eWOs739wpqs,9657
16
16
  monoco/core/skills.py,sha256=5qK-0YlOnNQLqyo6-T4GVBrkjsbrszb5ugXeXRRcQ00,16597
17
17
  monoco/core/state.py,sha256=KlmOn0KXO8g9S_KUIrUylAKdBpyH_1AOBvrYSj_khdE,1884
18
18
  monoco/core/sync.py,sha256=s0qQ5PsVn_IWGWUNThdvNFS-CncztDzsTM3QLupC6Og,8514
@@ -20,13 +20,13 @@ monoco/core/telemetry.py,sha256=DZQGOhvpe0XL34RCDaTZEUhmkD4abBTZumZJQlALzpY,2923
20
20
  monoco/core/workspace.py,sha256=0RWx8H_Kx56TYVrAURTSJW4aWOUylXofkvXBhRNjP_E,3074
21
21
  monoco/core/agent/__init__.py,sha256=tBCm6PDqPFo81yO949nW897INjl7ot46CPup9IrXExE,108
22
22
  monoco/core/agent/action.py,sha256=HpOLzp-mttJY0SDVbRlstDVqjFKozIAdjQdJ4Gp3xiY,5161
23
- monoco/core/agent/adapters.py,sha256=3M6-8uz_WOxXY1bMdu3epQ15v9dc-8Bolq00-mwLxwM,3517
23
+ monoco/core/agent/adapters.py,sha256=s8dzeXZY-m1pb8re5U5O1ED4d6MT9WNFWIeSpHBcmRg,4391
24
24
  monoco/core/agent/protocol.py,sha256=E7y_i2JzYGpzSCRCUIuzu1ATav-Xu1K01ka6Zncm4-o,831
25
25
  monoco/core/agent/state.py,sha256=sRav6uwkXao8yPl08CEB-cqK1EfiDyMnVxoSYxvYcis,3523
26
26
  monoco/core/resources/en/AGENTS.md,sha256=3TpCLNC1s_lIbDfJJBRmmROMoy9sZXu8BOag8M9NXI0,327
27
27
  monoco/core/resources/en/SKILL.md,sha256=1PrBqCgjDxT1LMSeu11BzlMhfboo_UlgZxdzBm7Tp9c,2161
28
28
  monoco/core/resources/zh/AGENTS.md,sha256=pGQOLt8mcRygJotd5oC8l9604hikQoUiS99QsHCe-UM,298
29
- monoco/core/resources/zh/SKILL.md,sha256=YlO0OO-yxwtZG4pMwTMAVaZ5-tfwysNYzLJNdULLHiE,1896
29
+ monoco/core/resources/zh/SKILL.md,sha256=8py8tD7s23cw2srKFnTjLO_miyuGXb3NLyEbk5gchqA,1886
30
30
  monoco/daemon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  monoco/daemon/app.py,sha256=I_BWs4WcJoTobssFKRBlAcD2CRnJtr6EPlFoGB5Hy-Y,15586
32
32
  monoco/daemon/commands.py,sha256=dN4D8ca0vPjj0WyjCSs8senneta1afm_bnNYv_kmGlU,1125
@@ -35,8 +35,6 @@ monoco/daemon/reproduce_stats.py,sha256=Q_zAj0Yj8l-77QDdtsLz1kWr68HeO7f1T6xC6VP4
35
35
  monoco/daemon/services.py,sha256=dMhy2ddzGtBBWTGtI14R-2aikENAgPsphxDkno1JAPU,5377
36
36
  monoco/daemon/stats.py,sha256=r-L0k6CdxkAkwLZV3V-jW7PldB9S3uNklQGLCEKA3Sc,4563
37
37
  monoco/features/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- monoco/features/agent/commands.py,sha256=p2wc05pzEeyPEYpfkHo2-X89IdnDp7hO1LCc2JBTCuI,5898
39
- monoco/features/agent/doctor.py,sha256=qqUu_rUjVp7ur1sYpL4VSpw8vwbwbjvXAFvXQP-lbNQ,924
40
38
  monoco/features/config/commands.py,sha256=PQaSq81zH4h8d1bFa4B2ANSXSdyjBFqVm_nBvEq1rNE,4889
41
39
  monoco/features/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
40
  monoco/features/i18n/adapter.py,sha256=M9iSEKykEiUe1YobHKmvfEjv7x7KgGuh-6yhgUJuN_s,973
@@ -45,33 +43,41 @@ monoco/features/i18n/core.py,sha256=s0r-48yMxrIrpWNX7r8JhtGX4X-Kd1P1bDAgNyluQ24,
45
43
  monoco/features/i18n/resources/en/AGENTS.md,sha256=zzuF7BW6x8Mj6LZZmeM6wTbG5CSCDMShf4rwtN7XITg,197
46
44
  monoco/features/i18n/resources/en/SKILL.md,sha256=Z8fAAqeMvpLDw1D_9AzZIykS5-HLVM9nnlRZLWBTPqM,2203
47
45
  monoco/features/i18n/resources/zh/AGENTS.md,sha256=lKkwLLCADRH7UDq9no4eQY2sRfJrb64JoZ_HNved8vA,175
48
- monoco/features/i18n/resources/zh/SKILL.md,sha256=y2UuwhZmCBy0pXGxWLNrhxb94zcNfGEMBA_imJgDqVg,1894
46
+ monoco/features/i18n/resources/zh/SKILL.md,sha256=Wynk7zVBW7CO0_6AEQNUvlD_3eMW_7EPnzEn9QUaiDs,1884
49
47
  monoco/features/issue/adapter.py,sha256=Y-ghwRoSEgm_A-cqY8Un-5srxydXdb9ytlKHLm89eJQ,1265
50
- monoco/features/issue/commands.py,sha256=LjBKplstvSAPxQ-PSvxe1y2sP-6wPrc8O9wGiXNpH_E,32189
51
- monoco/features/issue/core.py,sha256=cAPmJVLMCPMkfMe2WNZVdSQr0Pg6s8sN-SMU7LlhtR0,47108
52
- monoco/features/issue/linter.py,sha256=g98_NKXZFGw0ewBX7g-80CAKvPE0zdazQw1hT2uFtcU,11117
48
+ monoco/features/issue/commands.py,sha256=7QMklWteteG2eO1ERe2V7oFjoSEhz3jjEvD6_4ySpug,34479
49
+ monoco/features/issue/core.py,sha256=s1K1FWdsOq1-pBQ8HTayg1o0dK9_JWHXIcw2wBd_WpM,47155
50
+ monoco/features/issue/linter.py,sha256=-S3-DK0HKP_3EhSC69za8MyB-bVU4vOkNGM_Ajmjxoo,12056
53
51
  monoco/features/issue/migration.py,sha256=9gtgFi1V1pzwXo0-H4cIoBvSBERIWopXLCB4oSxTQLc,4715
54
- monoco/features/issue/models.py,sha256=pMBLvcGrqO0tpf-7TMnt9VAIzgkq5P79-hqfH8T_EBw,5044
52
+ monoco/features/issue/models.py,sha256=waj5JDzL3UPs2qW4lv6c7v_5aSYEDWZftLkwS2U70RA,5778
55
53
  monoco/features/issue/monitor.py,sha256=QEN0mqZ3tKwBfMjN87-LdrVoIEe0prA-uMHOBGy1VZk,3476
56
- monoco/features/issue/validator.py,sha256=oFKl3xG0tM0A33LVCHoLbsCK5PE-GlTTfJNcWnVDOPY,11373
54
+ monoco/features/issue/validator.py,sha256=6Q8LyjGpHXOf97q0-3bXvrWFqk7iO6fHhoGemkPTbps,17172
55
+ monoco/features/issue/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
+ monoco/features/issue/domain/lifecycle.py,sha256=-WreIDCeEQPjD82Q4wJMs_S5-9SQHCvGlPgtMllvXGw,4943
57
+ monoco/features/issue/domain/models.py,sha256=pjeb_Lp0zykNzAfrRma26IMkdlxYDQ0IdvNmM_7tepU,5720
58
+ monoco/features/issue/domain/parser.py,sha256=HfJh9uBN7Q7KyeXz1tk3tBjszu8ffStfZf9IS27v0K8,8770
59
+ monoco/features/issue/domain/workspace.py,sha256=AOv7H_he39UnYplCaEex6mdFgpAlGk6T8cDMxKwQLz0,3833
60
+ monoco/features/issue/engine/__init__.py,sha256=bWONZnIb0IkAhw09_pMbyqrDh-oUOLTrNvlQfypHXYw,785
61
+ monoco/features/issue/engine/config.py,sha256=BhTw9S8Oe10P1VfdLN64Nk0Woj613dHwfHiX87_AgJo,5762
62
+ monoco/features/issue/engine/machine.py,sha256=8wr5txLlzv0QO2sAlPl7JSPN-VkxJUpRNK25F7L7DVI,7530
63
+ monoco/features/issue/engine/models.py,sha256=u8Z7ilB702Jj6Imv4_vXpP8QTSWEnQdPxv8JA_9g1GU,579
64
+ monoco/features/issue/lsp/__init__.py,sha256=8bl8-WzSj3C4SkPKgVGnCeyemmvHVWBsuMZsJuJggG8,77
65
+ monoco/features/issue/lsp/definition.py,sha256=Y4dX7MmrEfYbWuh85W0djwUj_qed7cDpGadtiXEZDh4,3107
57
66
  monoco/features/issue/resources/en/AGENTS.md,sha256=OjEnkXCqwfMArJfJQhldrQYXqoregfGthyhJeyx7MPw,715
58
67
  monoco/features/issue/resources/en/SKILL.md,sha256=p0vhVpe3xLKg0iXSNofoLNvsq8j2pmv5CD1B2mUeIGk,2732
59
68
  monoco/features/issue/resources/zh/AGENTS.md,sha256=2MLIZ5-i-oBnl7_YAZBBoKkNqANpHj73iw18QQbSm5c,759
60
- monoco/features/issue/resources/zh/SKILL.md,sha256=_aXDThhsJag9OLK2X5LRVhaFyMcl4DcZvcSj1_UUkx8,3828
61
- monoco/features/pty/core.py,sha256=eM1EvHQrgExSnatO15pyfhh1VZoz0BBTfNYdXqG8HZ8,5711
62
- monoco/features/pty/router.py,sha256=7h80EPpOuE7hX5ifkxkzffcLZGecd5X8OmNvOji5ToI,5078
63
- monoco/features/pty/server.py,sha256=kw2csMZ_R4_Xx6ta2dbznWtgNZLfrWOAkMp8NjlZYBc,1920
69
+ monoco/features/issue/resources/zh/SKILL.md,sha256=vT1-Cp5ENLrpmCuv14D5xdRGQh3ferL6zCPLSRfsoE8,3816
64
70
  monoco/features/skills/__init__.py,sha256=L8YNGPWyyFWq5WqNossfeB0AKHJF_omrn1VzJBrRFcM,23
65
71
  monoco/features/skills/core.py,sha256=mpd0Cq-k2MvHRTPq9saFvZgYXUBGJ9pnK5lUmzUfZbY,3418
66
72
  monoco/features/spike/adapter.py,sha256=npJ4J775Df0DDi-LH-3u2jCuKjTIXyZUbLD0KNvHlcc,1062
67
73
  monoco/features/spike/commands.py,sha256=ctC2Kbe0fgpeDfw5106P0rsKEBDue0rFI4eMNRpXMDw,3880
68
- monoco/features/spike/core.py,sha256=vGMA3Tzuy0tB6r-FjnqcDalpiFWNw9CANSfQ5uV7Hg4,4589
74
+ monoco/features/spike/core.py,sha256=oP1yAalOnoA5fHpf2o4gteKx73XGwZ6G0WrO86z2iWE,4174
69
75
  monoco/features/spike/resources/en/AGENTS.md,sha256=NG3CMnlDk_0J8hnRUcueAM9lgIQr_dZ42R_31-LC48E,306
70
76
  monoco/features/spike/resources/en/SKILL.md,sha256=qKDcVh0D3pDRvfNLh1Bzo4oQU3obpl4tqdlzxeiWYMk,1911
71
77
  monoco/features/spike/resources/zh/AGENTS.md,sha256=5RHNl7fc3RdYYTFH483ojJl_arGPKkyYziOuGgFbqqg,290
72
- monoco/features/spike/resources/zh/SKILL.md,sha256=boGPgAfTHbEzdwomRh-qVEveWSgvYaPUdi_4YZVXGHI,1714
73
- monoco_toolkit-0.2.5.dist-info/METADATA,sha256=k2Bb7dwO7fDCUisJeMwmH53rJ7Gx4f44_8VHJF1O30A,3743
74
- monoco_toolkit-0.2.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
75
- monoco_toolkit-0.2.5.dist-info/entry_points.txt,sha256=iYj7FWYBdtClU15-Du1skqD0s6SFSIhJvxJ29VWp8ng,43
76
- monoco_toolkit-0.2.5.dist-info/licenses/LICENSE,sha256=ACAGGjV6aod4eIlVUTx1q9PZbnZGN5bBwkSs9RHj83s,1071
77
- monoco_toolkit-0.2.5.dist-info/RECORD,,
78
+ monoco/features/spike/resources/zh/SKILL.md,sha256=Q82e9lCQOAYIwBs5rGnvlVUDq7bp0pz8yvO10KTWFYQ,1710
79
+ monoco_toolkit-0.2.7.dist-info/METADATA,sha256=25z_zO0Ycius1qg0GT66bgQRBEIUPJS4IBq6sHQ6aqU,4716
80
+ monoco_toolkit-0.2.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
81
+ monoco_toolkit-0.2.7.dist-info/entry_points.txt,sha256=iYj7FWYBdtClU15-Du1skqD0s6SFSIhJvxJ29VWp8ng,43
82
+ monoco_toolkit-0.2.7.dist-info/licenses/LICENSE,sha256=ACAGGjV6aod4eIlVUTx1q9PZbnZGN5bBwkSs9RHj83s,1071
83
+ monoco_toolkit-0.2.7.dist-info/RECORD,,
@@ -1,166 +0,0 @@
1
-
2
- import typer
3
- from typing import Optional, Annotated
4
- from pathlib import Path
5
- from monoco.core.output import print_output, print_error, AgentOutput, OutputManager
6
- from monoco.core.agent.adapters import get_agent_client
7
- from monoco.core.agent.state import AgentStateManager
8
- from monoco.core.agent.action import ActionRegistry, ActionContext
9
- from monoco.core.config import get_config
10
- import asyncio
11
- import re
12
- import json as j
13
-
14
- app = typer.Typer()
15
-
16
- @app.command(name="run")
17
- def run_command(
18
- prompt_or_task: str = typer.Argument(..., help="Prompt string OR execution task name (e.g. 'refine-issue')"),
19
- target: Optional[str] = typer.Argument(None, help="Target file argument for the task"),
20
- provider: Optional[str] = typer.Option(None, "--using", "-u", help="Override agent provider"),
21
- instruction: Optional[str] = typer.Option(None, "--instruction", "-i", help="Additional instruction for the agent"),
22
- json: AgentOutput = False,
23
- ):
24
- """
25
- Execute a prompt or a named task using an Agent CLI.
26
- """
27
- # 0. Setup
28
- settings = get_config()
29
- state_manager = AgentStateManager()
30
- registry = ActionRegistry(Path(settings.paths.root))
31
-
32
- # 1. Check if it's a named task
33
- action = registry.get(prompt_or_task)
34
-
35
- final_prompt = prompt_or_task
36
- context_files = []
37
-
38
- # Determine Provider Priority: CLI > Action Def > Config > Default
39
- prov_name = provider
40
-
41
- if action:
42
- # It IS an action
43
- if not OutputManager.is_agent_mode():
44
- print(f"Running action: {action.name}")
45
-
46
- # Simple template substitution
47
- final_prompt = action.template
48
-
49
- if "{{file}}" in final_prompt:
50
- if not target:
51
- print_error("This task requires a target file argument.")
52
- raise typer.Exit(1)
53
-
54
- target_path = Path(target).resolve()
55
- if not target_path.exists():
56
- print_error(f"Target file not found: {target}")
57
- raise typer.Exit(1)
58
-
59
- final_prompt = final_prompt.replace("{{file}}", target_path.read_text())
60
- # Also add to context files? Ideally the prompt has it.
61
- # Let's add it to context files list to be safe if prompt didn't embed it fully
62
- context_files.append(target_path)
63
-
64
- if not prov_name:
65
- prov_name = action.provider
66
-
67
- # 2. Append Instruction if provided
68
- if instruction:
69
- final_prompt = f"{final_prompt}\n\n[USER INSTRUCTION]\n{instruction}"
70
-
71
- # 2. Provider Resolution Fallback
72
- prov_name = prov_name or settings.agent.framework or "gemini"
73
-
74
- # 3. State Check
75
- state = state_manager.load()
76
- if not state or state.is_stale:
77
- if not OutputManager.is_agent_mode():
78
- print("Agent state stale or missing, refreshing...")
79
- state = state_manager.refresh()
80
-
81
- if prov_name not in state.providers:
82
- print_error(f"Provider '{prov_name}' unknown.")
83
- raise typer.Exit(1)
84
-
85
- if not state.providers[prov_name].available:
86
- print_error(f"Provider '{prov_name}' is not available. Run 'monoco doctor' to diagnose.")
87
- raise typer.Exit(1)
88
-
89
- # 4. Execute
90
- try:
91
- client = get_agent_client(prov_name)
92
- result = asyncio.run(client.execute(final_prompt, context_files=context_files))
93
-
94
- if OutputManager.is_agent_mode():
95
- OutputManager.print({"result": result, "provider": prov_name})
96
- else:
97
- print(result)
98
-
99
- except Exception as e:
100
- print_error(f"Execution failed: {e}")
101
- raise typer.Exit(1)
102
-
103
- @app.command()
104
- def list(
105
- json: AgentOutput = False,
106
- context: Optional[str] = typer.Option(None, "--context", help="Context for filtering (JSON string)")
107
- ):
108
- """List available actions."""
109
- settings = get_config()
110
- registry = ActionRegistry(Path(settings.paths.root))
111
-
112
- action_context = None
113
- if context:
114
- try:
115
- ctx_data = j.loads(context)
116
- action_context = ActionContext(**ctx_data)
117
- except Exception as e:
118
- print_error(f"Invalid context JSON: {e}")
119
-
120
- actions = registry.list_available(action_context)
121
- # OutputManager handles list of Pydantic models automatically for both JSON and Table
122
- print_output(actions, title="Available Actions")
123
-
124
- @app.command()
125
- def status(
126
- json: AgentOutput = False,
127
- force: bool = typer.Option(False, "--force", "-f", help="Force refresh of agent state")
128
- ):
129
- """View status of Agent Providers."""
130
- state_manager = AgentStateManager()
131
- state = state_manager.get_or_refresh(force=force)
132
-
133
- if OutputManager.is_agent_mode():
134
- # Convert datetime to ISO string for JSON serialization
135
- data = state.dict()
136
- data["last_checked"] = data["last_checked"].isoformat()
137
- OutputManager.print(data)
138
- else:
139
- # Standard output using existing print_output or custom formatting
140
- from monoco.core.output import Table
141
- from rich import print as rprint
142
-
143
- table = Table(title=f"Agent Status (Last Checked: {state.last_checked.strftime('%Y-%m-%d %H:%M:%S')})")
144
- table.add_column("Provider")
145
- table.add_column("Available")
146
- table.add_column("Path")
147
- table.add_column("Error")
148
-
149
- for name, p_state in state.providers.items():
150
- table.add_row(
151
- name,
152
- "✅" if p_state.available else "❌",
153
- p_state.path or "-",
154
- p_state.error or "-"
155
- )
156
- rprint(table)
157
-
158
- @app.command()
159
- def doctor(
160
- force: bool = typer.Option(False, "--force", "-f", help="Force refresh of agent state")
161
- ):
162
- """
163
- Diagnose Agent Environment and refresh state.
164
- """
165
- from monoco.features.agent.doctor import doctor as doc_impl
166
- doc_impl(force)
@@ -1,30 +0,0 @@
1
- import typer
2
- from monoco.core.output import print_output, print_error
3
- from monoco.core.agent.state import AgentStateManager
4
-
5
- app = typer.Typer()
6
-
7
- @app.command()
8
- def doctor(
9
- force: bool = typer.Option(False, "--force", "-f", help="Force refresh of agent state")
10
- ):
11
- """
12
- Diagnose Agent Environment and refresh state.
13
- """
14
- manager = AgentStateManager()
15
- try:
16
- if force:
17
- print("Force refreshing agent state...")
18
- state = manager.refresh()
19
- else:
20
- state = manager.get_or_refresh()
21
-
22
- print_output(state, title="Agent Diagnosis Report")
23
-
24
- # Simple summary
25
- available = [k for k, v in state.providers.items() if v.available]
26
- print(f"\n✅ Available Agents: {', '.join(available) if available else 'None'}")
27
-
28
- except Exception as e:
29
- print_error(f"Doctor failed: {e}")
30
- raise typer.Exit(1)
@@ -1,185 +0,0 @@
1
-
2
- import asyncio
3
- import os
4
- import pty
5
- import select
6
- import signal
7
- import struct
8
- import fcntl
9
- import termios
10
- import logging
11
- from typing import Dict, Optional, Tuple, Any
12
-
13
- logger = logging.getLogger("monoco.pty")
14
-
15
- class PTYSession:
16
- """
17
- Manages a single PTY session connected to a subprocess (shell).
18
- """
19
- def __init__(self, session_id: str, cmd: list[str], env: Optional[Dict[str, str]] = None, cwd: Optional[str] = None):
20
- self.session_id = session_id
21
- self.cmd = cmd
22
- self.env = env or os.environ.copy()
23
- self.cwd = cwd or os.getcwd()
24
-
25
- self.fd: Optional[int] = None
26
- self.pid: Optional[int] = None
27
- self.proc = None # subprocess.Popen object
28
- self.running = False
29
- self.loop = asyncio.get_running_loop()
30
-
31
- def start(self, cols: int = 80, rows: int = 24):
32
- """
33
- Spawn a subprocess connected to a new PTY using subprocess.Popen.
34
- This provides better safety in threaded/asyncio environments than pty.fork().
35
- """
36
- import subprocess
37
-
38
- # 1. Open PTY pair
39
- master_fd, slave_fd = pty.openpty()
40
-
41
- # 2. Set initial size
42
- self._set_winsize(master_fd, rows, cols)
43
-
44
- try:
45
- # 3. Spawn process
46
- # start_new_session=True executes setsid()
47
- self.proc = subprocess.Popen(
48
- self.cmd,
49
- stdin=slave_fd,
50
- stdout=slave_fd,
51
- stderr=slave_fd,
52
- cwd=self.cwd,
53
- env=self.env,
54
- start_new_session=True,
55
- close_fds=True # Important to close other FDs in child
56
- )
57
-
58
- self.pid = self.proc.pid
59
- self.fd = master_fd
60
- self.running = True
61
-
62
- # 4. Close slave fd in parent (child has it open now)
63
- os.close(slave_fd)
64
-
65
- logger.info(f"Started session {self.session_id} (PID: {self.pid})")
66
-
67
- except Exception as e:
68
- logger.error(f"Failed to spawn process: {e}")
69
- # Ensure we clean up fds if spawn fails
70
- try:
71
- os.close(master_fd)
72
- except: pass
73
- try:
74
- os.close(slave_fd)
75
- except: pass
76
- raise e
77
-
78
-
79
- def resize(self, cols: int, rows: int):
80
- """
81
- Resize the PTY.
82
- """
83
- if self.fd and self.running:
84
- self._set_winsize(self.fd, rows, cols)
85
-
86
- def write(self, data: bytes):
87
- """
88
- Write input data (from websocket) to the PTY master fd.
89
- """
90
- if self.fd and self.running:
91
- os.write(self.fd, data)
92
-
93
- async def read(self) -> bytes:
94
- """
95
- Read output data from PTY master fd (to forward to websocket).
96
- """
97
- if not self.fd or not self.running:
98
- return b""
99
-
100
- try:
101
- # Run in executor to avoid blocking the event loop
102
- # pty read is blocking
103
- return await self.loop.run_in_executor(None, self._read_blocking)
104
- except OSError:
105
- return b""
106
-
107
- def _read_blocking(self) -> bytes:
108
- try:
109
- return os.read(self.fd, 1024)
110
- except OSError:
111
- return b""
112
-
113
- def terminate(self):
114
- """
115
- Terminate the process and close the PTY.
116
- """
117
- self.running = False
118
-
119
- # Use Popen object if available
120
- if self.proc:
121
- try:
122
- self.proc.terminate()
123
- try:
124
- self.proc.wait(timeout=1.0)
125
- except:
126
- # Force kill if not terminated
127
- self.proc.kill()
128
- self.proc.wait()
129
- except Exception as e:
130
- logger.error(f"Error terminating process: {e}")
131
- self.proc = None
132
- self.pid = None
133
- elif self.pid:
134
- # Fallback for legacy or if Popen obj lost
135
- try:
136
- os.kill(self.pid, signal.SIGTERM)
137
- os.waitpid(self.pid, 0) # Reap zombie
138
- except OSError:
139
- pass
140
- self.pid = None
141
-
142
- if self.fd:
143
- try:
144
- os.close(self.fd)
145
- except OSError:
146
- pass
147
- self.fd = None
148
- logger.info(f"Terminated session {self.session_id}")
149
-
150
- def _set_winsize(self, fd: int, row: int, col: int, xpix: int = 0, ypix: int = 0):
151
- winsize = struct.pack("HHHH", row, col, xpix, ypix)
152
- fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
153
-
154
-
155
- class PTYManager:
156
- """
157
- Singleton to manage multiple PTY sessions.
158
- """
159
- def __init__(self):
160
- self.sessions: Dict[str, PTYSession] = {}
161
-
162
- def create_session(self, session_id: str, cwd: str, cmd: list[str] = ["/bin/zsh"], env: Dict = None) -> PTYSession:
163
- if session_id in self.sessions:
164
- # In a real app, we might want to attach to existing?
165
- # For now, kill and recreate (or error)
166
- self.close_session(session_id)
167
-
168
- session = PTYSession(session_id, cmd, env, cwd)
169
- self.sessions[session_id] = session
170
- return session
171
-
172
- def get_session(self, session_id: str) -> Optional[PTYSession]:
173
- return self.sessions.get(session_id)
174
-
175
- def close_session(self, session_id: str):
176
- if session_id in self.sessions:
177
- self.sessions[session_id].terminate()
178
- del self.sessions[session_id]
179
-
180
- def close_all_sessions(self):
181
- """
182
- Terminate all active PTY sessions.
183
- """
184
- for session_id in list(self.sessions.keys()):
185
- self.close_session(session_id)
@@ -1,138 +0,0 @@
1
-
2
- from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query
3
- from pydantic import BaseModel
4
- from typing import Optional, Dict
5
- import json
6
- import asyncio
7
- import logging
8
- import os
9
- from pathlib import Path
10
- from monoco.features.pty.core import PTYManager
11
- from monoco.core.config import get_config
12
-
13
- # We will use dependency injection or a global singleton for now
14
- # Ideally attached to app state
15
- pty_manager = PTYManager()
16
-
17
- router = APIRouter(prefix="/api/v1/pty", tags=["pty"])
18
-
19
- logger = logging.getLogger("monoco.pty")
20
-
21
- @router.websocket("/ws/{session_id}")
22
- async def websocket_pty_endpoint(
23
- websocket: WebSocket,
24
- session_id: str,
25
- cwd: Optional[str] = Query(None),
26
- cols: int = Query(80),
27
- rows: int = Query(24),
28
- env: Optional[str] = Query(None) # JSON-encoded env vars
29
- ):
30
- await websocket.accept()
31
-
32
- # Determine working directory
33
- # 1. Provide explicit CWD in query
34
- # 2. Or fallback to ProjectRoot from env (if integrated)
35
- # 3. Or fallback to process CWD
36
-
37
- # Since monoco pty runs as a separate service, we expect CWD to be passed
38
- # or we default to where monoco pty was started
39
- working_dir = cwd if cwd else os.getcwd()
40
-
41
- # Prepare environment
42
- env_vars = os.environ.copy()
43
- env_vars["TERM"] = "xterm-256color"
44
- env_vars["COLORTERM"] = "truecolor"
45
- if "SHELL" not in env_vars:
46
- env_vars["SHELL"] = "/bin/zsh"
47
- if "HOME" not in env_vars:
48
- import pathlib
49
- env_vars["HOME"] = str(pathlib.Path.home())
50
-
51
- # Filter out Trae/Gemini specific variables to avoid shell integration conflicts
52
- # This prevents the shell from trying to write to IDE-specific logs which causes EPERM
53
- keys_to_remove = [k for k in env_vars.keys() if k.startswith("TRAE_") or k.startswith("GEMINI_") or k == "AI_AGENT"]
54
- for k in keys_to_remove:
55
- del env_vars[k]
56
-
57
- if env:
58
- try:
59
- custom_env = json.loads(env)
60
- env_vars.update(custom_env)
61
- except:
62
- logger.warning("Failed to parse custom env vars")
63
-
64
- # Start Session
65
- try:
66
- session = pty_manager.create_session(
67
- session_id=session_id,
68
- cwd=working_dir,
69
- cmd=["/bin/zsh", "-l"], # Use login shell to ensure full user environment
70
- env=env_vars
71
- )
72
- session.start(cols, rows)
73
- except Exception as e:
74
- logger.error(f"Failed to start session: {e}")
75
- await websocket.close(code=1011)
76
- return
77
-
78
- # Pipe Loop
79
- reader_task = None
80
- try:
81
- # Task to read from PTY and send to WebSocket
82
- async def pty_reader():
83
- while session.running:
84
- data = await session.read()
85
- if not data:
86
- break
87
- # xterm.js expects string or binary. We send string/bytes.
88
- # Usually text is fine, but binary is safer for control codes.
89
- await websocket.send_bytes(data)
90
-
91
- # If PTY exits, close WS
92
- await websocket.close()
93
-
94
- reader_task = asyncio.create_task(pty_reader())
95
-
96
- # Main loop: Read from WebSocket and write to PTY
97
- try:
98
- while True:
99
- # Receive message from Client (xterm.js)
100
- # Message can be simple input string, or a JSON command (resize)
101
- message = await websocket.receive()
102
-
103
- if message["type"] == "websocket.disconnect":
104
- raise WebSocketDisconnect(code=message.get("code", 1000))
105
-
106
- if "text" in message:
107
- payload = message["text"]
108
-
109
- # Check if it's a control message (Hack: usually client sends raw input)
110
- # We can enforce a protocol: binary for Input, text JSON for Control.
111
- try:
112
- # Try parsing as JSON control message
113
- cmd = json.loads(payload)
114
- if cmd.get("type") == "resize":
115
- session.resize(cmd["cols"], cmd["rows"])
116
- continue
117
- except:
118
- pass # Not JSON, treat as raw input
119
-
120
- session.write(payload.encode())
121
-
122
- elif "bytes" in message:
123
- session.write(message["bytes"])
124
- except RuntimeError:
125
- # Handle "Cannot call 'receive' once a disconnect message has been received"
126
- # This happens if Starlette/FastAPI already processed the disconnect internally
127
- # but we called receive() again.
128
- logger.info(f"Runtime disconnect for session {session_id}")
129
-
130
- except WebSocketDisconnect:
131
- logger.info(f"Client disconnected for session {session_id}")
132
- except Exception as e:
133
- logger.error(f"WebSocket error: {e}")
134
- finally:
135
- # Cleanup
136
- pty_manager.close_session(session_id)
137
- if reader_task and not reader_task.done():
138
- reader_task.cancel()