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.
- monoco/core/agent/adapters.py +24 -1
- monoco/core/config.py +77 -17
- monoco/core/integrations.py +8 -0
- monoco/core/lsp.py +7 -0
- monoco/core/output.py +8 -1
- monoco/core/resources/zh/SKILL.md +6 -7
- monoco/core/setup.py +8 -0
- monoco/features/i18n/resources/zh/SKILL.md +5 -5
- monoco/features/issue/commands.py +135 -55
- monoco/features/issue/core.py +157 -122
- monoco/features/issue/domain/__init__.py +0 -0
- monoco/features/issue/domain/lifecycle.py +126 -0
- monoco/features/issue/domain/models.py +170 -0
- monoco/features/issue/domain/parser.py +223 -0
- monoco/features/issue/domain/workspace.py +104 -0
- monoco/features/issue/engine/__init__.py +22 -0
- monoco/features/issue/engine/config.py +172 -0
- monoco/features/issue/engine/machine.py +185 -0
- monoco/features/issue/engine/models.py +18 -0
- monoco/features/issue/linter.py +32 -11
- monoco/features/issue/lsp/__init__.py +3 -0
- monoco/features/issue/lsp/definition.py +72 -0
- monoco/features/issue/models.py +26 -9
- monoco/features/issue/resources/zh/SKILL.md +8 -9
- monoco/features/issue/validator.py +181 -65
- monoco/features/spike/core.py +5 -22
- monoco/features/spike/resources/zh/SKILL.md +2 -2
- monoco/main.py +2 -26
- monoco_toolkit-0.2.7.dist-info/METADATA +129 -0
- {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/RECORD +33 -27
- monoco/features/agent/commands.py +0 -166
- monoco/features/agent/doctor.py +0 -30
- monoco/features/pty/core.py +0 -185
- monoco/features/pty/router.py +0 -138
- monoco/features/pty/server.py +0 -56
- monoco_toolkit-0.2.5.dist-info/METADATA +0 -93
- {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/entry_points.txt +0 -0
- {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=
|
|
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=
|
|
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=
|
|
12
|
-
monoco/core/lsp.py,sha256=
|
|
13
|
-
monoco/core/output.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
51
|
-
monoco/features/issue/core.py,sha256=
|
|
52
|
-
monoco/features/issue/linter.py,sha256
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
73
|
-
monoco_toolkit-0.2.
|
|
74
|
-
monoco_toolkit-0.2.
|
|
75
|
-
monoco_toolkit-0.2.
|
|
76
|
-
monoco_toolkit-0.2.
|
|
77
|
-
monoco_toolkit-0.2.
|
|
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)
|
monoco/features/agent/doctor.py
DELETED
|
@@ -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)
|
monoco/features/pty/core.py
DELETED
|
@@ -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)
|
monoco/features/pty/router.py
DELETED
|
@@ -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()
|