opalacoder 0.1.19__tar.gz → 0.1.20__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 (101) hide show
  1. {opalacoder-0.1.19 → opalacoder-0.1.20}/PKG-INFO +1 -1
  2. opalacoder-0.1.20/docs/specs/backlog_register.md +39 -0
  3. {opalacoder-0.1.19 → opalacoder-0.1.20}/gui_src/src/App.jsx +306 -16
  4. {opalacoder-0.1.19 → opalacoder-0.1.20}/gui_src/src/index.css +7 -0
  5. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/agent_stdin.py +15 -0
  6. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/cli_commands.py +52 -1
  7. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/config.py +12 -3
  8. opalacoder-0.1.19/opalacoder/gui/assets/index-Cka3h8dT.js → opalacoder-0.1.20/opalacoder/gui/assets/index-BLhtmpm0.js +53 -53
  9. opalacoder-0.1.19/opalacoder/gui/assets/index-BzHZtmXE.css → opalacoder-0.1.20/opalacoder/gui/assets/index-fY3LKaOS.css +1 -1
  10. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/gui/index.html +2 -2
  11. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/ide_server.py +95 -4
  12. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/project.py +35 -10
  13. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/skills.py +1 -1
  14. {opalacoder-0.1.19 → opalacoder-0.1.20}/pyproject.toml +1 -1
  15. opalacoder-0.1.20/skills/view-editor/SKILL.md +17 -0
  16. opalacoder-0.1.20/skills/view-editor/scripts/run_view_editor.py +52 -0
  17. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_model_commands.py +25 -0
  18. opalacoder-0.1.20/tests/test_view_editor_skill.py +37 -0
  19. opalacoder-0.1.19/docs/specs/backlog_register.md +0 -28
  20. {opalacoder-0.1.19 → opalacoder-0.1.20}/.claude/rules/RULES.md +0 -0
  21. {opalacoder-0.1.19 → opalacoder-0.1.20}/.claude/settings.json +0 -0
  22. {opalacoder-0.1.19 → opalacoder-0.1.20}/.github/workflows/publish.yml +0 -0
  23. {opalacoder-0.1.19 → opalacoder-0.1.20}/.gitignore +0 -0
  24. {opalacoder-0.1.19 → opalacoder-0.1.20}/AGENTS.md +0 -0
  25. {opalacoder-0.1.19 → opalacoder-0.1.20}/CLAUDE.md +0 -0
  26. {opalacoder-0.1.19 → opalacoder-0.1.20}/GEMINI.md +0 -0
  27. {opalacoder-0.1.19 → opalacoder-0.1.20}/README.md +0 -0
  28. {opalacoder-0.1.19 → opalacoder-0.1.20}/agents.yaml +0 -0
  29. {opalacoder-0.1.19 → opalacoder-0.1.20}/config.yaml +0 -0
  30. {opalacoder-0.1.19 → opalacoder-0.1.20}/debug/scratch_tail.py +0 -0
  31. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/guide/analysis_results.md +0 -0
  32. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/guide/figs/logotipo.png +0 -0
  33. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/guide/paper_memplan.md +0 -0
  34. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/guide/skills-plugin-system.md +0 -0
  35. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/specs/01-arquitetura.md +0 -0
  36. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/specs/02-memgpt-orquestrador.md +0 -0
  37. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/specs/03-skill-implement-feature.md +0 -0
  38. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/specs/04-memoria.md +0 -0
  39. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/specs/05-vcs-sombra.md +0 -0
  40. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/specs/06-skills-e-plugins.md +0 -0
  41. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/specs/07-configuracao.md +0 -0
  42. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/specs/08-ide.md +0 -0
  43. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/specs/09-ide-projectcreationinterface.md +0 -0
  44. {opalacoder-0.1.19 → opalacoder-0.1.20}/docs/specs/README.md +0 -0
  45. {opalacoder-0.1.19 → opalacoder-0.1.20}/gui_src/index.html +0 -0
  46. {opalacoder-0.1.19 → opalacoder-0.1.20}/gui_src/package-lock.json +0 -0
  47. {opalacoder-0.1.19 → opalacoder-0.1.20}/gui_src/package.json +0 -0
  48. {opalacoder-0.1.19 → opalacoder-0.1.20}/gui_src/src/main.jsx +0 -0
  49. {opalacoder-0.1.19 → opalacoder-0.1.20}/gui_src/vite.config.js +0 -0
  50. {opalacoder-0.1.19 → opalacoder-0.1.20}/main.py +0 -0
  51. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/__init__.py +0 -0
  52. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/agents.py +0 -0
  53. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/api_keys.py +0 -0
  54. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/archival.py +0 -0
  55. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/cli.py +0 -0
  56. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/code_index.py +0 -0
  57. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/copy_gui.py +0 -0
  58. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/embeddings.py +0 -0
  59. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/i18n.py +0 -0
  60. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/memgpt_runtime.py +0 -0
  61. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/orchestrator.py +0 -0
  62. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/planner.py +0 -0
  63. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/plugins/__init__.py +0 -0
  64. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/plugins/html_css_js_tools.py +0 -0
  65. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/session.py +0 -0
  66. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/structured.py +0 -0
  67. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/terminal.py +0 -0
  68. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/terminal_manager.py +0 -0
  69. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/tools.py +0 -0
  70. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/vcs.py +0 -0
  71. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/vector_index.py +0 -0
  72. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/workflow_orchestrator.py +0 -0
  73. {opalacoder-0.1.19 → opalacoder-0.1.20}/opalacoder/workflow_tools.py +0 -0
  74. {opalacoder-0.1.19 → opalacoder-0.1.20}/pytest.ini +0 -0
  75. {opalacoder-0.1.19 → opalacoder-0.1.20}/requirements.txt +0 -0
  76. {opalacoder-0.1.19 → opalacoder-0.1.20}/skills/chat-orchestrator/SKILL.md +0 -0
  77. {opalacoder-0.1.19 → opalacoder-0.1.20}/skills/command-line/SKILL.md +0 -0
  78. {opalacoder-0.1.19 → opalacoder-0.1.20}/skills/command-line/scripts/command_executor.py +0 -0
  79. {opalacoder-0.1.19 → opalacoder-0.1.20}/skills/skills_store/html-css-js/SKILL.md +0 -0
  80. {opalacoder-0.1.19 → opalacoder-0.1.20}/skills/skills_store/html-css-js/scripts/check_contracts.py +0 -0
  81. {opalacoder-0.1.19 → opalacoder-0.1.20}/skills/skills_store/implement-feature/SKILL.md +0 -0
  82. {opalacoder-0.1.19 → opalacoder-0.1.20}/skills/skills_store/implement-feature/scripts/run_workflow.py +0 -0
  83. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_agent_config.py +0 -0
  84. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_agent_stdin.py +0 -0
  85. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_code_index_integration.py +0 -0
  86. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_command_line_skill.py +0 -0
  87. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_complexity_evaluator.py +0 -0
  88. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_context_guard.py +0 -0
  89. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_file_delete.py +0 -0
  90. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_i18n_coverage.py +0 -0
  91. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_implement_feature_script.py +0 -0
  92. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_memgpt_runtime.py +0 -0
  93. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_planner_oracle_live.py +0 -0
  94. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_planner_output.py +0 -0
  95. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_project_store.py +0 -0
  96. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_refinement_loop.py +0 -0
  97. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_search_bugs.py +0 -0
  98. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_skills_directory_loader.py +0 -0
  99. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_unhashable_dict_bug.py +0 -0
  100. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_verification_strategies.py +0 -0
  101. {opalacoder-0.1.19 → opalacoder-0.1.20}/tests/test_workflow_pipeline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opalacoder
3
- Version: 0.1.19
3
+ Version: 0.1.20
4
4
  Summary: Autonomous coding agent with interactive planning and modular execution
5
5
  Project-URL: Homepage, https://github.com/gilzamir/OpalaCoder
6
6
  Project-URL: Bug Tracker, https://github.com/gilzamir/OpalaCoder/issues
@@ -0,0 +1,39 @@
1
+ # BUGS TO FIX DETECTED ON LAST PUBLIC RELEASE (READ THE LAST SUBTOPIC)
2
+
3
+ ## FROM CURRENT = 0.1.17 TO NEXT = 0.1.18
4
+
5
+ 1. Terminal dont work, no obvious message in back terminal, but on IDE terminal we get the message "[OpalaCoder] Conexão com o terminal perdida. Reconectando...".
6
+
7
+ 2. A janela de criação de projeto somente deveria permitir confirmar criação de projeto se o diretório especificado for válido.
8
+
9
+ 3. New feature: install optional modules must be on IDE startup.
10
+
11
+ ## FROM CURRENT = 0.1.18 TO NEXT = 0.1.19
12
+
13
+ 1. Projeto não mostra nenhum erro quando o backend de modelos falha, por exemplo, tenta-se rodar um modelo que o ollama não tem instalado.
14
+
15
+ 2. Ao criar um novo arquivo, ficou congelado em carregando.
16
+
17
+ 3. A aba de Problems nunca mostra nada errado, mesmo tendo.
18
+
19
+ 4. Adicionar opção de renomear arquivo/diretório selecionado
20
+
21
+ 5. Adicionar opção de limpar output e problems.
22
+
23
+ 6. Abrir mais de uma aba no editor de arquivo (vários arquivos abertos ao mesmo tempo)
24
+
25
+ 7. Verificar se o agente de comunicação com o backend é um LLMAgentBlock e, se for, como está as configurações de limite de chamada de ferramentas e de reflexão e outras.
26
+
27
+ ## FROM CURRENT = 0.1.19 TO NEXT = 0.1.20
28
+
29
+ 1. Implementar a visualização do pensamento do agente em uma aba thinking do painel inferior Implementar em uma aba separada thinking
30
+ 2. Aumentar tamanho da fonte quando digitar ctrl+ no editor, ou diminuir quando digitar ctrl-.
31
+ 3. Disponibilizar uma skill da ide que permite ao chat visualizar o conteúdo atual do editor e retornar o trecho selecionado.
32
+ 4. Colocar botão de interroper o agente.
33
+ 5. Descobrir porque o agente granite4:latest demora a responder.
34
+ 6. O erro ao criar um projeto em um diretório existente ou proibido deveria ser mostrado como mensagem na janela de criação de projeto e não no terminal (colocar mensagem correta, de acordo com exceção).
35
+ 6.1 Se diretório já existe e der erro de permissão, mostrar erro de permissão, se diretório não existe, criar diretório com nome do projeto.
36
+ 7. Colocar hint de completação de servidor ollama (já trazer preenchido com o valor que geralmente é).
37
+ 8. Criar uma janela de configuração de modelo com parâmetros mais usados que são aceitos no agenticblocks. E criar um comando set-model-param param-name value que permite qualquer parâmetro geralmente permitido pro litellm/ollama. Cuidado para implementar controle de verdade (valores adequados de parâmetro, por example).
38
+
39
+ 9. Permitir que mensagens no chat não podem ser copiadas.
@@ -68,6 +68,15 @@ export default function App() {
68
68
  const [activeSidebarTab, setActiveSidebarTab] = useState('explorer');
69
69
  const [activeBottomTab, setActiveBottomTab] = useState('output');
70
70
  const [problems, setProblems] = useState([]);
71
+ const [thinkingLogs, setThinkingLogs] = useState([]);
72
+ const [showAdvancedParams, setShowAdvancedParams] = useState(false);
73
+ const [newProjError, setNewProjError] = useState('');
74
+
75
+ useEffect(() => {
76
+ if (!showNewProjectModal) {
77
+ setNewProjError('');
78
+ }
79
+ }, [showNewProjectModal]);
71
80
 
72
81
  // IDE Settings States
73
82
  const [isSettingsOpen, setIsSettingsOpen] = useState(false);
@@ -111,6 +120,12 @@ export default function App() {
111
120
  // Shape: { name, project_name, project_path, model, alternative_model, mode, description } | null
112
121
  const [editingProject, setEditingProject] = useState(null);
113
122
 
123
+ useEffect(() => {
124
+ if (!editingProject) {
125
+ setShowAdvancedParams(false);
126
+ }
127
+ }, [editingProject]);
128
+
114
129
  const fetchProblems = async () => {
115
130
  if (!activeProject) return;
116
131
  try {
@@ -165,10 +180,15 @@ export default function App() {
165
180
  const [newProjModel, setNewProjModel] = useState('gemini/gemini-2.5-flash');
166
181
  const [newProjMode, setNewProjMode] = useState('auto');
167
182
  const [newProjApiKey, setNewProjApiKey] = useState('');
168
- const [newProjApiBase, setNewProjApiBase] = useState('');
183
+ const [newProjApiBase, setNewProjApiBase] = useState('http://localhost:11434/v1');
169
184
 
170
185
  const chatEndRef = useRef(null);
171
186
  const logEndRef = useRef(null);
187
+ const editorRef = useRef(null);
188
+
189
+ const handleEditorDidMount = (editor, monaco) => {
190
+ editorRef.current = editor;
191
+ };
172
192
 
173
193
  // Initial load
174
194
  useEffect(() => {
@@ -234,12 +254,26 @@ export default function App() {
234
254
  logEndRef.current?.scrollIntoView({ behavior: 'smooth' });
235
255
  }, [terminalLogs]);
236
256
 
237
- // Keybindings (Ctrl+S to save)
257
+ // Keybindings (Ctrl+S to save, Ctrl+ / Ctrl- to zoom)
238
258
  useEffect(() => {
239
259
  const handleKeyDown = (e) => {
240
260
  if ((e.ctrlKey || e.metaKey) && e.key === 's') {
241
261
  e.preventDefault();
242
262
  saveFile();
263
+ } else if ((e.ctrlKey || e.metaKey) && (e.key === '+' || e.key === '=')) {
264
+ e.preventDefault();
265
+ setEditorFontSize(prev => {
266
+ const nextVal = Math.min(30, prev + 1);
267
+ safeSetLocalStorage('editorFontSize', nextVal);
268
+ return nextVal;
269
+ });
270
+ } else if ((e.ctrlKey || e.metaKey) && e.key === '-') {
271
+ e.preventDefault();
272
+ setEditorFontSize(prev => {
273
+ const nextVal = Math.max(10, prev - 1);
274
+ safeSetLocalStorage('editorFontSize', nextVal);
275
+ return nextVal;
276
+ });
243
277
  }
244
278
  };
245
279
  window.addEventListener('keydown', handleKeyDown);
@@ -802,6 +836,7 @@ export default function App() {
802
836
  const handleCreateProject = async (e) => {
803
837
  e.preventDefault();
804
838
  if (!newProjName || !newProjPath) return;
839
+ setNewProjError('');
805
840
  try {
806
841
  const res = await fetch('/api/opalacoder/create-project', {
807
842
  method: 'POST',
@@ -823,13 +858,15 @@ export default function App() {
823
858
  setNewProjPath('');
824
859
  setNewProjDesc('');
825
860
  setNewProjApiKey('');
826
- setNewProjApiBase('');
861
+ setNewProjApiBase('http://localhost:11434/v1');
827
862
  fetchProjects();
828
863
  } else {
829
864
  const err = await res.json();
865
+ setNewProjError(err.error || 'Erro ao criar projeto.');
830
866
  addLog('error', `Erro ao criar projeto: ${err.error}`);
831
867
  }
832
868
  } catch (err) {
869
+ setNewProjError(err.message || 'Erro ao criar projeto.');
833
870
  addLog('error', `Erro ao criar: ${err.message}`);
834
871
  }
835
872
  };
@@ -864,6 +901,7 @@ export default function App() {
864
901
  alternative_model: proj.alternative_model || '',
865
902
  mode: proj.mode || 'auto',
866
903
  description: proj.description || '',
904
+ model_params: proj.model_params || {},
867
905
  });
868
906
  };
869
907
 
@@ -882,6 +920,7 @@ export default function App() {
882
920
  alternative_model: editingProject.alternative_model,
883
921
  mode: editingProject.mode,
884
922
  description: editingProject.description,
923
+ model_params: editingProject.model_params,
885
924
  })
886
925
  });
887
926
  if (res.ok) {
@@ -914,6 +953,21 @@ export default function App() {
914
953
  };
915
954
 
916
955
 
956
+ const handleInterruptAgent = async () => {
957
+ try {
958
+ const res = await fetch('/api/opalacoder/interrupt', {
959
+ method: 'POST',
960
+ });
961
+ if (res.ok) {
962
+ addLog('info', 'Sinal de interrupção enviado ao agente.');
963
+ } else {
964
+ addLog('error', 'Falha ao enviar sinal de interrupção.');
965
+ }
966
+ } catch (err) {
967
+ addLog('error', `Erro ao interromper: ${err.message}`);
968
+ }
969
+ };
970
+
917
971
  const handleSendMessage = async (e) => {
918
972
  if (e) e.preventDefault();
919
973
  if (!chatInput.trim() || !activeProject || isAgentRunning) return;
@@ -923,8 +977,22 @@ export default function App() {
923
977
  setChatMessages(prev => [...prev, { role: 'user', content: userText }]);
924
978
  setIsAgentRunning(true);
925
979
  setProblems([]);
980
+ setThinkingLogs([]);
926
981
  addLog('info', `Iniciando agente para prompt: "${userText}"`);
927
982
 
983
+ let selectedText = '';
984
+ if (editorRef.current) {
985
+ try {
986
+ const model = editorRef.current.getModel();
987
+ const selection = editorRef.current.getSelection();
988
+ if (model && selection) {
989
+ selectedText = model.getValueInRange(selection);
990
+ }
991
+ } catch (e) {
992
+ // ignore
993
+ }
994
+ }
995
+
928
996
  try {
929
997
  const res = await fetch('/api/opalacoder/run', {
930
998
  method: 'POST',
@@ -935,7 +1003,10 @@ export default function App() {
935
1003
  prompt: userText,
936
1004
  project_name: activeProject.name,
937
1005
  project_path: activeProject.project_path,
938
- model: activeProject.model
1006
+ model: activeProject.model,
1007
+ current_file: selectedFile || '',
1008
+ editor_content: fileContent || '',
1009
+ selected_text: selectedText || '',
939
1010
  })
940
1011
  });
941
1012
 
@@ -998,6 +1069,11 @@ export default function App() {
998
1069
  break;
999
1070
  case 'thought':
1000
1071
  addLog('thought', data.content);
1072
+ setThinkingLogs(prev => [...prev, { timestamp: new Date().toLocaleTimeString(), content: data.content }]);
1073
+ break;
1074
+ case 'cancelled':
1075
+ addLog('warning', data.message || 'Execução cancelada.');
1076
+ setChatMessages(prev => [...prev, { role: 'assistant', content: `⚠️ Interrompido: ${data.message || 'A execução do agente foi parada.'}` }]);
1001
1077
  break;
1002
1078
  case 'tool_call':
1003
1079
  addLog('tool_call', `Chamando: ${data.tool} (${JSON.stringify(data.arguments)})`);
@@ -1382,6 +1458,7 @@ export default function App() {
1382
1458
  theme={theme === 'light' ? 'light' : 'vs-dark'}
1383
1459
  value={fileContent}
1384
1460
  onChange={(val) => setFileContent(val)}
1461
+ onMount={handleEditorDidMount}
1385
1462
  options={{
1386
1463
  minimap: { enabled: true },
1387
1464
  fontSize: editorFontSize,
@@ -1427,6 +1504,12 @@ export default function App() {
1427
1504
  >
1428
1505
  PROBLEMS {problems.length > 0 && <span style={{ marginLeft: '4px', background: '#f48771', color: '#1e1e1e', borderRadius: '10px', padding: '0 6px', fontSize: '10px', fontWeight: 'bold' }}>{problems.length}</span>}
1429
1506
  </span>
1507
+ <span
1508
+ className={`vscode-bottom-tab ${activeBottomTab === 'thinking' ? 'active' : ''}`}
1509
+ onClick={() => selectTab('thinking')}
1510
+ >
1511
+ THINKING
1512
+ </span>
1430
1513
  <span
1431
1514
  className={`vscode-bottom-tab ${activeBottomTab === 'terminal' ? 'active' : ''}`}
1432
1515
  onClick={() => selectTab('terminal')}
@@ -1435,11 +1518,23 @@ export default function App() {
1435
1518
  </span>
1436
1519
  </div>
1437
1520
  <div className="flex items-center" style={{ gap: '8px' }}>
1438
- {(activeBottomTab === 'output' || activeBottomTab === 'problems') && (
1521
+ {(activeBottomTab === 'output' || activeBottomTab === 'problems' || activeBottomTab === 'thinking') && (
1439
1522
  <button
1440
- onClick={activeBottomTab === 'output' ? () => setTerminalLogs([]) : () => setProblems([])}
1523
+ onClick={
1524
+ activeBottomTab === 'output'
1525
+ ? () => setTerminalLogs([])
1526
+ : activeBottomTab === 'problems'
1527
+ ? () => setProblems([])
1528
+ : () => setThinkingLogs([])
1529
+ }
1441
1530
  className="vscode-bottom-panel-clear-btn"
1442
- title={activeBottomTab === 'output' ? 'Limpar Output' : 'Limpar Problemas'}
1531
+ title={
1532
+ activeBottomTab === 'output'
1533
+ ? 'Limpar Output'
1534
+ : activeBottomTab === 'problems'
1535
+ ? 'Limpar Problemas'
1536
+ : 'Limpar Pensamentos'
1537
+ }
1443
1538
  >
1444
1539
  <Trash size={12} />
1445
1540
  <span>Clear</span>
@@ -1506,6 +1601,22 @@ export default function App() {
1506
1601
  </div>
1507
1602
  )}
1508
1603
 
1604
+ {activeBottomTab === 'thinking' && (
1605
+ <div className="vscode-logs" style={{ height: '100%' }}>
1606
+ {thinkingLogs.length === 0 ? (
1607
+ <div style={{ color: '#808080', fontStyle: 'italic' }}>Nenhum pensamento gerado ainda. Execute uma instrução para ver o raciocínio do agente...</div>
1608
+ ) : (
1609
+ thinkingLogs.map((log, i) => (
1610
+ <div key={i} className="text-[#da70d6]" style={{ display: 'flex', alignItems: 'flex-start', gap: '6px', marginBottom: '4px' }}>
1611
+ <span style={{ color: '#5a5a5a' }}>[{log.timestamp}]</span>
1612
+ <span style={{ fontWeight: 'bold' }}>[THOUGHT]</span>
1613
+ <span style={{ whiteSpace: 'pre-wrap', flex: 1, fontFamily: 'Consolas, monospace' }}>{log.content}</span>
1614
+ </div>
1615
+ ))
1616
+ )}
1617
+ </div>
1618
+ )}
1619
+
1509
1620
  <div style={{ display: activeBottomTab === 'terminal' ? 'block' : 'none', height: '100%', background: '#1e1e1e', overflow: 'hidden' }}>
1510
1621
  {!activeProject ? (
1511
1622
  <div style={{ color: '#808080', fontStyle: 'italic', padding: '16px' }}>Defina um projeto/workspace para habilitar o terminal.</div>
@@ -1604,14 +1715,26 @@ export default function App() {
1604
1715
  placeholder={!activeProject ? "Defina um projeto..." : isAgentRunning ? "Pensando..." : "Pergunte ao OpalaCoder..."}
1605
1716
  style={{ flex: 1 }}
1606
1717
  />
1607
- <button
1608
- type="submit"
1609
- disabled={!activeProject || isAgentRunning || !chatInput.trim()}
1610
- className="vscode-button"
1611
- style={{ padding: '6px' }}
1612
- >
1613
- <ArrowRight size={14} />
1614
- </button>
1718
+ {isAgentRunning ? (
1719
+ <button
1720
+ type="button"
1721
+ onClick={handleInterruptAgent}
1722
+ className="vscode-button"
1723
+ style={{ padding: '6px', backgroundColor: '#f48771', color: '#1e1e1e' }}
1724
+ title="Interromper agente"
1725
+ >
1726
+ <X size={14} />
1727
+ </button>
1728
+ ) : (
1729
+ <button
1730
+ type="submit"
1731
+ disabled={!activeProject || !chatInput.trim()}
1732
+ className="vscode-button"
1733
+ style={{ padding: '6px' }}
1734
+ >
1735
+ <ArrowRight size={14} />
1736
+ </button>
1737
+ )}
1615
1738
  </div>
1616
1739
  </form>
1617
1740
  </aside>
@@ -1792,6 +1915,12 @@ export default function App() {
1792
1915
  </div>
1793
1916
  </div>
1794
1917
 
1918
+ {newProjError && (
1919
+ <div style={{ color: '#f48771', fontSize: '11px', marginTop: '4px', whiteSpace: 'pre-wrap' }}>
1920
+ ⚠️ {newProjError}
1921
+ </div>
1922
+ )}
1923
+
1795
1924
  <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px', paddingTop: '12px', borderTop: '1px solid #3c3c3c', marginTop: '4px' }}>
1796
1925
  <button
1797
1926
  type="button"
@@ -1961,6 +2090,167 @@ export default function App() {
1961
2090
  />
1962
2091
  </div>
1963
2092
 
2093
+ {/* Collapsible advanced parameters */}
2094
+ <div className="flex flex-col" style={{ marginTop: '4px' }}>
2095
+ <button
2096
+ type="button"
2097
+ onClick={() => setShowAdvancedParams(!showAdvancedParams)}
2098
+ style={{
2099
+ background: 'transparent',
2100
+ border: 'none',
2101
+ color: '#007acc',
2102
+ cursor: 'pointer',
2103
+ display: 'flex',
2104
+ alignItems: 'center',
2105
+ gap: '4px',
2106
+ padding: '4px 0',
2107
+ fontSize: '12px',
2108
+ fontWeight: 'bold',
2109
+ textAlign: 'left',
2110
+ width: 'fit-content'
2111
+ }}
2112
+ >
2113
+ <span>{showAdvancedParams ? '▼' : '▶'} Parâmetros do Modelo (Avançado)</span>
2114
+ </button>
2115
+
2116
+ {showAdvancedParams && (
2117
+ <div
2118
+ style={{
2119
+ border: '1px solid #3c3c3c',
2120
+ borderRadius: '4px',
2121
+ padding: '12px',
2122
+ marginTop: '8px',
2123
+ backgroundColor: '#252526',
2124
+ display: 'grid',
2125
+ gridTemplateColumns: '1fr 1fr',
2126
+ gap: '12px'
2127
+ }}
2128
+ >
2129
+ <div className="flex flex-col" style={{ gap: '4px' }}>
2130
+ <label className="vscode-sidebar-section-title" style={{ padding: 0 }}>Temperature</label>
2131
+ <input
2132
+ type="number"
2133
+ step="0.1"
2134
+ min="0"
2135
+ max="2"
2136
+ placeholder="Ex: 0.7"
2137
+ value={editingProject.model_params?.temperature !== undefined ? editingProject.model_params.temperature : ''}
2138
+ onChange={e => {
2139
+ const val = e.target.value === '' ? undefined : parseFloat(e.target.value);
2140
+ setEditingProject(p => {
2141
+ const nextParams = { ...p.model_params };
2142
+ if (val === undefined) delete nextParams.temperature;
2143
+ else nextParams.temperature = val;
2144
+ return { ...p, model_params: nextParams };
2145
+ });
2146
+ }}
2147
+ />
2148
+ </div>
2149
+
2150
+ <div className="flex flex-col" style={{ gap: '4px' }}>
2151
+ <label className="vscode-sidebar-section-title" style={{ padding: 0 }}>Max Tokens</label>
2152
+ <input
2153
+ type="number"
2154
+ min="1"
2155
+ placeholder="Ex: 4096"
2156
+ value={editingProject.model_params?.max_tokens !== undefined ? editingProject.model_params.max_tokens : ''}
2157
+ onChange={e => {
2158
+ const val = e.target.value === '' ? undefined : parseInt(e.target.value, 10);
2159
+ setEditingProject(p => {
2160
+ const nextParams = { ...p.model_params };
2161
+ if (val === undefined) delete nextParams.max_tokens;
2162
+ else nextParams.max_tokens = val;
2163
+ return { ...p, model_params: nextParams };
2164
+ });
2165
+ }}
2166
+ />
2167
+ </div>
2168
+
2169
+ <div className="flex flex-col" style={{ gap: '4px' }}>
2170
+ <label className="vscode-sidebar-section-title" style={{ padding: 0 }}>Context Window (num_ctx)</label>
2171
+ <input
2172
+ type="number"
2173
+ min="1"
2174
+ placeholder="Ex: 8192"
2175
+ value={editingProject.model_params?.num_ctx !== undefined ? editingProject.model_params.num_ctx : ''}
2176
+ onChange={e => {
2177
+ const val = e.target.value === '' ? undefined : parseInt(e.target.value, 10);
2178
+ setEditingProject(p => {
2179
+ const nextParams = { ...p.model_params };
2180
+ if (val === undefined) delete nextParams.num_ctx;
2181
+ else nextParams.num_ctx = val;
2182
+ return { ...p, model_params: nextParams };
2183
+ });
2184
+ }}
2185
+ />
2186
+ </div>
2187
+
2188
+ <div className="flex flex-col" style={{ gap: '4px' }}>
2189
+ <label className="vscode-sidebar-section-title" style={{ padding: 0 }}>Top P</label>
2190
+ <input
2191
+ type="number"
2192
+ step="0.05"
2193
+ min="0"
2194
+ max="1"
2195
+ placeholder="Ex: 0.9"
2196
+ value={editingProject.model_params?.top_p !== undefined ? editingProject.model_params.top_p : ''}
2197
+ onChange={e => {
2198
+ const val = e.target.value === '' ? undefined : parseFloat(e.target.value);
2199
+ setEditingProject(p => {
2200
+ const nextParams = { ...p.model_params };
2201
+ if (val === undefined) delete nextParams.top_p;
2202
+ else nextParams.top_p = val;
2203
+ return { ...p, model_params: nextParams };
2204
+ });
2205
+ }}
2206
+ />
2207
+ </div>
2208
+
2209
+ <div className="flex flex-col" style={{ gap: '4px' }}>
2210
+ <label className="vscode-sidebar-section-title" style={{ padding: 0 }}>Frequency Penalty</label>
2211
+ <input
2212
+ type="number"
2213
+ step="0.1"
2214
+ min="-2"
2215
+ max="2"
2216
+ placeholder="Ex: 0.0"
2217
+ value={editingProject.model_params?.frequency_penalty !== undefined ? editingProject.model_params.frequency_penalty : ''}
2218
+ onChange={e => {
2219
+ const val = e.target.value === '' ? undefined : parseFloat(e.target.value);
2220
+ setEditingProject(p => {
2221
+ const nextParams = { ...p.model_params };
2222
+ if (val === undefined) delete nextParams.frequency_penalty;
2223
+ else nextParams.frequency_penalty = val;
2224
+ return { ...p, model_params: nextParams };
2225
+ });
2226
+ }}
2227
+ />
2228
+ </div>
2229
+
2230
+ <div className="flex flex-col" style={{ gap: '4px' }}>
2231
+ <label className="vscode-sidebar-section-title" style={{ padding: 0 }}>Presence Penalty</label>
2232
+ <input
2233
+ type="number"
2234
+ step="0.1"
2235
+ min="-2"
2236
+ max="2"
2237
+ placeholder="Ex: 0.0"
2238
+ value={editingProject.model_params?.presence_penalty !== undefined ? editingProject.model_params.presence_penalty : ''}
2239
+ onChange={e => {
2240
+ const val = e.target.value === '' ? undefined : parseFloat(e.target.value);
2241
+ setEditingProject(p => {
2242
+ const nextParams = { ...p.model_params };
2243
+ if (val === undefined) delete nextParams.presence_penalty;
2244
+ else nextParams.presence_penalty = val;
2245
+ return { ...p, model_params: nextParams };
2246
+ });
2247
+ }}
2248
+ />
2249
+ </div>
2250
+ </div>
2251
+ )}
2252
+ </div>
2253
+
1964
2254
  {/* Actions */}
1965
2255
  <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px', paddingTop: '12px', borderTop: '1px solid #3c3c3c', marginTop: '4px' }}>
1966
2256
  <button
@@ -2212,7 +2502,7 @@ export default function App() {
2212
2502
  <div style={{ display: 'flex', flexDirection: 'column', gap: '14px', color: '#cccccc' }}>
2213
2503
  <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
2214
2504
  <span className="vscode-sidebar-section-title" style={{ padding: 0 }}>Versão</span>
2215
- <span style={{ fontSize: '13px', fontWeight: 'bold', color: '#ffffff' }}>0.1.19 alfa</span>
2505
+ <span style={{ fontSize: '13px', fontWeight: 'bold', color: '#ffffff' }}>0.1.20 alfa</span>
2216
2506
  </div>
2217
2507
  <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
2218
2508
  <span className="vscode-sidebar-section-title" style={{ padding: 0 }}>Autor</span>
@@ -649,6 +649,13 @@ input:focus, select:focus, textarea:focus {
649
649
  user-select: none;
650
650
  }
651
651
 
652
+ /* Disable copying text in chat messages */
653
+ .vscode-chat-history,
654
+ .vscode-chat-msg-content,
655
+ .vscode-chat-msg-header {
656
+ user-select: none;
657
+ }
658
+
652
659
 
653
660
 
654
661
 
@@ -190,6 +190,21 @@ async def handle_run(data: dict):
190
190
  # Setup project context if provided
191
191
  if "project_path" in data or "project_name" in data:
192
192
  await handle_load_project(data)
193
+
194
+ if current_project and current_project.project_path:
195
+ state_dir = os.path.join(current_project.project_path, ".opalacoder")
196
+ os.makedirs(state_dir, exist_ok=True)
197
+ state_file = os.path.join(state_dir, "_editor_state.json")
198
+ editor_state = {
199
+ "current_file": data.get("current_file", ""),
200
+ "editor_content": data.get("editor_content", ""),
201
+ "selected_text": data.get("selected_text", "")
202
+ }
203
+ try:
204
+ with open(state_file, "w", encoding="utf-8") as f:
205
+ json.dump(editor_state, f, indent=2, ensure_ascii=False)
206
+ except Exception as e:
207
+ print(f"Warning: Failed to save editor state: {e}", file=sys.stderr)
193
208
 
194
209
  # Intercept and process slash commands (like /help, /undo, /commit)
195
210
  if prompt.startswith("/"):
@@ -244,7 +244,12 @@ async def cmd_models(state: REPLState, _args: list[str]) -> None:
244
244
  T.console.print(f"\n[dim]Models for project '{state.display_name}':[/dim]")
245
245
  T.console.print(f" [cyan]main[/cyan] {main_model}")
246
246
  T.console.print(f" [cyan]alternative[/cyan] {alt_model} [dim]({alt_origin})[/dim]")
247
- T.console.print(f"\n[dim]Change with /set-main-model <id> or /set-alternative-model <id>.[/dim]\n")
247
+ params = getattr(state.project, "model_params", {})
248
+ if params:
249
+ T.console.print(f" [cyan]parameters[/cyan]")
250
+ for k, v in params.items():
251
+ T.console.print(f" {k}: {v}")
252
+ T.console.print(f"\n[dim]Change with /set-main-model <id>, /set-alternative-model <id>, or /set-model-param <name> <value>.[/dim]\n")
248
253
 
249
254
 
250
255
  @_registry.register("/set-main-model", usage="<model_id>",
@@ -273,6 +278,52 @@ async def cmd_set_alternative_model(state: REPLState, args: list[str]) -> str |
273
278
  T.success(f"Alternative model set to '{model_id}' for this project.")
274
279
 
275
280
 
281
+ @_registry.register("/set-model-param", usage="<param_name> <value>",
282
+ description="Set advanced model parameter (temperature, max_tokens, num_ctx, top_p, frequency_penalty, presence_penalty)")
283
+ async def cmd_set_model_param(state: REPLState, args: list[str]) -> str | None:
284
+ if len(args) < 2:
285
+ T.error("Usage: /set-model-param <param_name> <value>\n"
286
+ "Allowed params: temperature, max_tokens, num_ctx, top_p, frequency_penalty, presence_penalty")
287
+ return "continue"
288
+
289
+ param = args[0].strip().lower()
290
+ val_str = args[1].strip()
291
+
292
+ allowed_params = {"temperature", "max_tokens", "num_ctx", "top_p", "frequency_penalty", "presence_penalty"}
293
+ if param not in allowed_params:
294
+ T.error(f"Unknown parameter '{param}'. Allowed parameters: {', '.join(allowed_params)}")
295
+ return "continue"
296
+
297
+ try:
298
+ if param in {"max_tokens", "num_ctx"}:
299
+ val = int(val_str)
300
+ if val <= 0:
301
+ raise ValueError("Must be a positive integer")
302
+ elif param == "temperature":
303
+ val = float(val_str)
304
+ if not (0.0 <= val <= 2.0):
305
+ raise ValueError("Must be between 0.0 and 2.0")
306
+ elif param == "top_p":
307
+ val = float(val_str)
308
+ if not (0.0 <= val <= 1.0):
309
+ raise ValueError("Must be between 0.0 and 1.0")
310
+ elif param in {"frequency_penalty", "presence_penalty"}:
311
+ val = float(val_str)
312
+ if not (-2.0 <= val <= 2.0):
313
+ raise ValueError("Must be between -2.0 and 2.0")
314
+ except ValueError as e:
315
+ T.error(f"Invalid value for '{param}': {e}")
316
+ return "continue"
317
+
318
+ if not hasattr(state.project, "model_params") or state.project.model_params is None:
319
+ state.project.model_params = {}
320
+
321
+ state.project.model_params[param] = val
322
+ state.store.save(state.project)
323
+ _rebuild_memgpt(state)
324
+ T.success(f"Model parameter '{param}' set to {val} for this project.")
325
+
326
+
276
327
  @_registry.register("/undo", description=_("undo_desc"))
277
328
  async def cmd_undo(state: REPLState, _args: list[str]) -> str | None:
278
329
  from .vcs import get_vcs_strategy
@@ -119,14 +119,23 @@ def get_agent_llm_kwargs(agent_name: str) -> dict:
119
119
  """Return merged litellm kwargs for *agent_name*.
120
120
 
121
121
  Priority (highest first):
122
- 1. Per-agent override in agents.yaml ``agents.<name>``
123
- 2. Global ``llm_defaults`` in agents.yaml
124
- 3. Hard-coded defaults above
122
+ 1. Project-specific model_params (dynamically merged if session exists)
123
+ 2. Per-agent override in agents.yaml ``agents.<name>``
124
+ 3. Global ``llm_defaults`` in agents.yaml
125
+ 4. Hard-coded defaults above
125
126
 
126
127
  Non-litellm fields (model, max_heartbeats) are excluded.
127
128
  """
128
129
  merged = dict(_LLM_DEFAULTS)
129
130
  merged.update(_AGENT_OVERRIDES.get(agent_name, {}))
131
+
132
+ try:
133
+ from .tools import _PROJECT_SESSION
134
+ if _PROJECT_SESSION and hasattr(_PROJECT_SESSION, "model_params") and _PROJECT_SESSION.model_params:
135
+ merged.update(_PROJECT_SESSION.model_params)
136
+ except Exception:
137
+ pass
138
+
130
139
  for field in _NON_LITELLM_FIELDS:
131
140
  merged.pop(field, None)
132
141
  return merged