npcsh 1.0.22__tar.gz → 1.0.24__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.
- {npcsh-1.0.22 → npcsh-1.0.24}/PKG-INFO +1 -1
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/guac.py +205 -160
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/routes.py +1 -1
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh.egg-info/PKG-INFO +1 -1
- {npcsh-1.0.22 → npcsh-1.0.24}/setup.py +1 -1
- {npcsh-1.0.22 → npcsh-1.0.24}/LICENSE +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/README.md +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/__init__.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/_state.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/alicanto.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/corca.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/mcp_helpers.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/mcp_server.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/jinxs/bash_executer.jinx +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/jinxs/edit_file.jinx +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/jinxs/image_generation.jinx +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/jinxs/internet_search.jinx +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/jinxs/python_executor.jinx +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/jinxs/screen_cap.jinx +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/npcsh.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/plonk.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/pti.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/spool.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/wander.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh/yap.py +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh.egg-info/SOURCES.txt +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh.egg-info/dependency_links.txt +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh.egg-info/entry_points.txt +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh.egg-info/requires.txt +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/npcsh.egg-info/top_level.txt +0 -0
- {npcsh-1.0.22 → npcsh-1.0.24}/setup.cfg +0 -0
|
@@ -11,7 +11,7 @@ import matplotlib.pyplot as plt
|
|
|
11
11
|
|
|
12
12
|
import logging
|
|
13
13
|
plt.ioff()
|
|
14
|
-
|
|
14
|
+
import shlex
|
|
15
15
|
import platform
|
|
16
16
|
import yaml
|
|
17
17
|
import re
|
|
@@ -396,9 +396,13 @@ def _handle_guac_refresh(state: ShellState, project_name: str, src_dir: Path):
|
|
|
396
396
|
prompt = "\n".join(prompt_parts)
|
|
397
397
|
|
|
398
398
|
try:
|
|
399
|
+
# Ensure state.npc is not None before accessing .model or .provider
|
|
400
|
+
npc_model = state.npc.model if state.npc and state.npc.model else state.chat_model
|
|
401
|
+
npc_provider = state.npc.provider if state.npc and state.npc.provider else state.chat_provider
|
|
402
|
+
|
|
399
403
|
response = get_llm_response(prompt,
|
|
400
|
-
model=
|
|
401
|
-
provider=
|
|
404
|
+
model=npc_model,
|
|
405
|
+
provider=npc_provider,
|
|
402
406
|
npc=state.npc,
|
|
403
407
|
stream=False)
|
|
404
408
|
suggested_code_raw = response.get("response", "").strip()
|
|
@@ -434,22 +438,69 @@ def _handle_guac_refresh(state: ShellState, project_name: str, src_dir: Path):
|
|
|
434
438
|
except Exception as e:
|
|
435
439
|
print(f"Error during /refresh: {e}")
|
|
436
440
|
traceback.print_exc()
|
|
437
|
-
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def ensure_global_guac_team():
|
|
445
|
+
"""Ensure a global guac team exists at ~/.npcsh/guac/npc_team/."""
|
|
446
|
+
base_dir = Path.home() / ".npcsh" / "guac"
|
|
447
|
+
team_dir = base_dir / "npc_team"
|
|
448
|
+
team_dir.mkdir(parents=True, exist_ok=True)
|
|
449
|
+
|
|
450
|
+
guac_npc_path = team_dir / "guac.npc"
|
|
451
|
+
if not guac_npc_path.exists():
|
|
452
|
+
guac = {
|
|
453
|
+
"name": "guac",
|
|
454
|
+
"primary_directive": (
|
|
455
|
+
"You are guac, the global coordinator NPC for Guac Mode. "
|
|
456
|
+
"Always prioritize Python code, concise answers, and coordination."
|
|
457
|
+
),
|
|
458
|
+
"model": os.environ.get("NPCSH_CHAT_MODEL", "gemma3:4b"),
|
|
459
|
+
"provider": os.environ.get("NPCSH_CHAT_PROVIDER", "ollama")
|
|
460
|
+
}
|
|
461
|
+
with open(guac_npc_path, "w") as f:
|
|
462
|
+
yaml.dump(guac, f, default_flow_style=False)
|
|
463
|
+
print(f"✅ Created global guac NPC at {guac_npc_path}")
|
|
464
|
+
|
|
465
|
+
ctx_path = team_dir / "team.ctx"
|
|
466
|
+
if not ctx_path.exists():
|
|
467
|
+
ctx = {
|
|
468
|
+
"team_name": "guac_global_team",
|
|
469
|
+
"forenpc": "guac",
|
|
470
|
+
"model": os.environ.get("NPCSH_CHAT_MODEL", "gemma3:4b"),
|
|
471
|
+
"provider": os.environ.get("NPCSH_CHAT_PROVIDER", "ollama"),
|
|
472
|
+
"description": "Global guac team context"
|
|
473
|
+
}
|
|
474
|
+
with open(ctx_path, "w") as f:
|
|
475
|
+
yaml.dump(ctx, f, default_flow_style=False)
|
|
476
|
+
print(f"✅ Created global guac team.ctx at {ctx_path}")
|
|
477
|
+
|
|
478
|
+
return team_dir
|
|
479
|
+
def setup_guac_mode(config_dir=None, plots_dir=None, npc_team_dir=None,
|
|
480
|
+
lang='python', default_mode_choice=None):
|
|
438
481
|
base_dir = Path.cwd()
|
|
482
|
+
|
|
483
|
+
# Check if we should default to global without prompting
|
|
484
|
+
if GUAC_GLOBAL_FLAG_FILE.exists():
|
|
485
|
+
print("💡 Using global Guac team as default (previously set).")
|
|
486
|
+
team_dir = ensure_global_guac_team()
|
|
487
|
+
return {
|
|
488
|
+
"language": lang, "package_root": team_dir, "plots_dir": plots_dir,
|
|
489
|
+
"npc_team_dir": team_dir, "config_dir": config_dir, "default_mode": default_mode_choice or "agent",
|
|
490
|
+
"project_description": "Global guac team for analysis.", "package_name": "guac"
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
# default: project npc_team_dir
|
|
439
494
|
if npc_team_dir is None:
|
|
440
495
|
npc_team_dir = base_dir / "npc_team"
|
|
441
496
|
else:
|
|
442
497
|
npc_team_dir = Path(npc_team_dir)
|
|
443
498
|
npc_team_dir.mkdir(parents=True, exist_ok=True)
|
|
444
|
-
# Setup Guac workspace
|
|
445
499
|
workspace_dirs = _get_workspace_dirs(npc_team_dir)
|
|
446
|
-
|
|
447
500
|
_ensure_workspace_dirs(workspace_dirs)
|
|
448
501
|
|
|
449
|
-
# Rest of existing setup_guac_mode code...
|
|
450
502
|
team_ctx_path = npc_team_dir / "team.ctx"
|
|
451
503
|
existing_ctx = {}
|
|
452
|
-
|
|
453
504
|
if team_ctx_path.exists():
|
|
454
505
|
try:
|
|
455
506
|
with open(team_ctx_path, "r") as f:
|
|
@@ -459,99 +510,68 @@ def setup_guac_mode(config_dir=None, plots_dir=None, npc_team_dir=None, lang='py
|
|
|
459
510
|
|
|
460
511
|
package_root = existing_ctx.get("GUAC_PACKAGE_ROOT")
|
|
461
512
|
package_name = existing_ctx.get("GUAC_PACKAGE_NAME")
|
|
462
|
-
|
|
513
|
+
|
|
463
514
|
if package_root is None or package_name is None:
|
|
464
515
|
try:
|
|
465
|
-
response = input("Enter
|
|
516
|
+
response = input("Enter package root (Enter for current dir): ").strip()
|
|
466
517
|
package_root = response if response else str(base_dir)
|
|
467
|
-
|
|
468
|
-
response = input("Enter your package name (press Enter to use 'project'): ").strip()
|
|
518
|
+
response = input("Enter package name (Enter for 'project'): ").strip()
|
|
469
519
|
package_name = response if response else "project"
|
|
470
|
-
except EOFError:
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
520
|
+
except (KeyboardInterrupt, EOFError):
|
|
521
|
+
print("⚠️ Project setup interrupted. Falling back to global guac team...")
|
|
522
|
+
GUAC_GLOBAL_FLAG_FILE.touch() # Create the flag file to remember this choice
|
|
523
|
+
team_dir = ensure_global_guac_team()
|
|
524
|
+
return {
|
|
525
|
+
"language": lang, "package_root": team_dir, "plots_dir": plots_dir,
|
|
526
|
+
"npc_team_dir": team_dir, "config_dir": config_dir, "default_mode": default_mode_choice or "agent",
|
|
527
|
+
"project_description": "Global guac team for analysis.", "package_name": "guac"
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
project_description = existing_ctx.get("GUAC_PROJECT_DESCRIPTION", "")
|
|
531
|
+
if not project_description:
|
|
477
532
|
try:
|
|
478
|
-
project_description = input("Enter a
|
|
479
|
-
except EOFError:
|
|
533
|
+
project_description = input("Enter a project description: ").strip() or "No description."
|
|
534
|
+
except (KeyboardInterrupt, EOFError):
|
|
480
535
|
project_description = "No description provided."
|
|
481
536
|
|
|
482
537
|
updated_ctx = {**existing_ctx}
|
|
483
538
|
updated_ctx.update({
|
|
484
539
|
"GUAC_TEAM_NAME": "guac_team",
|
|
485
|
-
"GUAC_DESCRIPTION": f"A team
|
|
486
|
-
"GUAC_FORENPC": "guac",
|
|
487
|
-
"
|
|
488
|
-
"GUAC_LANG": lang,
|
|
489
|
-
"GUAC_PACKAGE_ROOT": package_root,
|
|
490
|
-
"GUAC_PACKAGE_NAME": package_name,
|
|
540
|
+
"GUAC_DESCRIPTION": f"A team for {lang} analysis for project {package_name}",
|
|
541
|
+
"GUAC_FORENPC": "guac", "GUAC_PROJECT_DESCRIPTION": project_description,
|
|
542
|
+
"GUAC_LANG": lang, "GUAC_PACKAGE_ROOT": package_root, "GUAC_PACKAGE_NAME": package_name,
|
|
491
543
|
"GUAC_WORKSPACE_PATHS": {k: str(v) for k, v in workspace_dirs.items()},
|
|
492
544
|
})
|
|
493
|
-
|
|
494
|
-
pkg_root_path = Path(package_root)
|
|
495
|
-
try:
|
|
496
|
-
pkg_root_path.mkdir(parents=True, exist_ok=True)
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
(package_dir / "__init__.py").write_text("# package initialized by setup_guac_mode\n")
|
|
502
|
-
logging.info("Created minimal package directory at %s", package_dir)
|
|
503
|
-
except Exception as e:
|
|
504
|
-
logging.warning("Could not ensure package root/dir: %s", e)
|
|
545
|
+
|
|
546
|
+
pkg_root_path = Path(package_root)
|
|
547
|
+
try:
|
|
548
|
+
pkg_root_path.mkdir(parents=True, exist_ok=True)
|
|
549
|
+
(pkg_root_path / package_name / "__init__.py").touch()
|
|
550
|
+
except Exception as e:
|
|
551
|
+
logging.warning("Could not ensure package root/dir: %s", e)
|
|
552
|
+
|
|
505
553
|
with open(team_ctx_path, "w") as f:
|
|
506
554
|
yaml.dump(updated_ctx, f, default_flow_style=False)
|
|
507
555
|
print("Updated team.ctx with GUAC-specific information.")
|
|
508
556
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
from setuptools import setup, find_packages
|
|
518
|
-
setup(
|
|
519
|
-
name="{package_name}",
|
|
520
|
-
version="{existing_ctx.get("GUAC_PACKAGE_VERSION", "0.0.0")}",
|
|
521
|
-
description="{project_description.replace('"', '\\"')}",
|
|
522
|
-
packages=find_packages(),
|
|
523
|
-
include_package_data=True,
|
|
524
|
-
install_requires=[],
|
|
525
|
-
)
|
|
526
|
-
'''
|
|
527
|
-
setup_py_path.write_text(setup_content)
|
|
528
|
-
logging.info("Created minimal setup.py at %s", setup_py_path)
|
|
529
|
-
except Exception as e:
|
|
530
|
-
logging.warning("Could not write setup.py: %s", e)
|
|
531
|
-
|
|
557
|
+
setup_py_path = pkg_root_path / "setup.py"
|
|
558
|
+
if not setup_py_path.exists():
|
|
559
|
+
setup_content = f'''from setuptools import setup, find_packages
|
|
560
|
+
setup(name="{package_name}", version="0.0.1", description="{project_description.replace('"', '\\"')}", packages=find_packages())
|
|
561
|
+
'''
|
|
562
|
+
setup_py_path.write_text(setup_content)
|
|
563
|
+
logging.info("Created minimal setup.py at %s", setup_py_path)
|
|
564
|
+
|
|
532
565
|
default_mode_val = default_mode_choice or "agent"
|
|
533
566
|
setup_npc_team(npc_team_dir, lang)
|
|
534
|
-
|
|
535
|
-
|
|
536
567
|
|
|
537
568
|
print(f"\nGuac mode configured for package: {package_name} at {package_root}")
|
|
538
569
|
print(f"Workspace created at: {workspace_dirs['workspace']}")
|
|
539
|
-
|
|
540
570
|
return {
|
|
541
|
-
"language": lang,
|
|
542
|
-
"
|
|
543
|
-
"
|
|
544
|
-
"npc_team_dir": npc_team_dir,
|
|
545
|
-
"config_dir": config_dir,
|
|
546
|
-
"default_mode": default_mode_val,
|
|
547
|
-
"project_description": project_description,
|
|
548
|
-
"package_name": package_name
|
|
571
|
+
"language": lang, "package_root": Path(package_root), "plots_dir": plots_dir,
|
|
572
|
+
"npc_team_dir": npc_team_dir, "config_dir": config_dir, "default_mode": default_mode_val,
|
|
573
|
+
"project_description": project_description, "package_name": package_name
|
|
549
574
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
575
|
def setup_npc_team(npc_team_dir, lang, is_subteam=False):
|
|
556
576
|
# Create Guac-specific NPCs
|
|
557
577
|
guac_npc = {
|
|
@@ -566,7 +586,7 @@ def setup_npc_team(npc_team_dir, lang, is_subteam=False):
|
|
|
566
586
|
}
|
|
567
587
|
caug_npc = {
|
|
568
588
|
"name": "caug",
|
|
569
|
-
"primary_directive": f"You are caug, a specialist in big data statistical methods in {lang}."
|
|
589
|
+
"primary_directive": f"You are caug, a specialist in big data statistical methods in {lang}. You never make scatter plots with discrete values unless asked. "
|
|
570
590
|
}
|
|
571
591
|
|
|
572
592
|
parsely_npc = {
|
|
@@ -1005,7 +1025,7 @@ def _capture_plot_state(session_id: str, db_path: str, npc_team_dir: Path):
|
|
|
1005
1025
|
data_summary=f"{data_points} data points",
|
|
1006
1026
|
change_significance=1.0 if not last else 0.5
|
|
1007
1027
|
)
|
|
1008
|
-
|
|
1028
|
+
|
|
1009
1029
|
session.add(plot_state)
|
|
1010
1030
|
session.commit()
|
|
1011
1031
|
session.close()
|
|
@@ -1122,17 +1142,42 @@ import sys
|
|
|
1122
1142
|
from io import StringIO
|
|
1123
1143
|
from contextlib import redirect_stdout, redirect_stderr
|
|
1124
1144
|
|
|
1145
|
+
# --- New Emoji Function for Agentic Mode ---
|
|
1146
|
+
def _get_guac_agent_emoji(failures: int, max_fail: int = 3) -> str:
|
|
1147
|
+
"""
|
|
1148
|
+
Returns an avocado emoji representing the state based on consecutive failures.
|
|
1149
|
+
Includes "puke" emoji for max_fail, and "skull" for exceeding max_fail + 20.
|
|
1150
|
+
"""
|
|
1151
|
+
if failures == 0:
|
|
1152
|
+
return "🥑" # Fresh
|
|
1153
|
+
elif failures == 1:
|
|
1154
|
+
return "🥑🔪" # Sliced, contemplating next steps
|
|
1155
|
+
elif failures == 2:
|
|
1156
|
+
return "🥑🥣" # In the bowl, getting mashed
|
|
1157
|
+
elif failures == max_fail:
|
|
1158
|
+
return "🥑🤢" # Going bad, critical issue (puke)
|
|
1159
|
+
elif failures > max_fail + 20: # Skull for 20+ over max
|
|
1160
|
+
return "🥑💀" # Rotten
|
|
1161
|
+
elif failures > max_fail:
|
|
1162
|
+
return "🥑🟤" # Bruised and bad
|
|
1163
|
+
else:
|
|
1164
|
+
return "🥑❓" # Unknown state
|
|
1165
|
+
|
|
1166
|
+
# --- New Helper for Persisting Global Choice ---
|
|
1167
|
+
GUAC_GLOBAL_FLAG_FILE = Path.home() / ".npcsh" / ".guac_use_global"
|
|
1168
|
+
|
|
1169
|
+
|
|
1125
1170
|
def _run_agentic_mode(command: str,
|
|
1126
1171
|
state: ShellState,
|
|
1127
1172
|
locals_dict: Dict[str, Any],
|
|
1128
1173
|
npc_team_dir: Path) -> Tuple[ShellState, Any]:
|
|
1129
1174
|
"""Run agentic mode with continuous iteration based on progress"""
|
|
1130
|
-
max_iterations =
|
|
1175
|
+
max_iterations = 5 # Increased slightly for more complex tasks
|
|
1131
1176
|
iteration = 0
|
|
1132
1177
|
full_output = []
|
|
1133
1178
|
current_command = command
|
|
1134
1179
|
consecutive_failures = 0
|
|
1135
|
-
max_consecutive_failures = 3
|
|
1180
|
+
max_consecutive_failures = 3 # This is the limit before stopping
|
|
1136
1181
|
|
|
1137
1182
|
# Build context of existing variables
|
|
1138
1183
|
existing_vars_context = "EXISTING VARIABLES IN ENVIRONMENT:\n"
|
|
@@ -1151,7 +1196,7 @@ def _run_agentic_mode(command: str,
|
|
|
1151
1196
|
steps = []
|
|
1152
1197
|
while iteration < max_iterations and consecutive_failures < max_consecutive_failures:
|
|
1153
1198
|
iteration += 1
|
|
1154
|
-
print(f"\n
|
|
1199
|
+
print(f"\n{_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)} Agentic iteration {iteration} ")
|
|
1155
1200
|
|
|
1156
1201
|
prompt = f"""
|
|
1157
1202
|
USER REQUEST: {current_command} {next_step}
|
|
@@ -1202,12 +1247,13 @@ def _run_agentic_mode(command: str,
|
|
|
1202
1247
|
If a user is asking for help debugging, it's better to figure out what is wrong by attempting to run it yourself, and if they do not prefer that,
|
|
1203
1248
|
then it's best to use static parsing methods and arguments based on deduction rather than attempting to just fix everything over and over.
|
|
1204
1249
|
|
|
1205
|
-
Do not over-complicate the code.
|
|
1250
|
+
Do not over- complicate the code.
|
|
1206
1251
|
|
|
1207
1252
|
Do not include any '__name__'=='__main__' block.
|
|
1208
1253
|
"""
|
|
1209
|
-
#
|
|
1210
1254
|
|
|
1255
|
+
npc_model = state.npc.model if state.npc and state.npc.model else state.chat_model
|
|
1256
|
+
npc_provider = state.npc.provider if state.npc and state.npc.provider else state.chat_provider
|
|
1211
1257
|
|
|
1212
1258
|
llm_response = get_llm_response(prompt,
|
|
1213
1259
|
npc=state.npc,
|
|
@@ -1215,21 +1261,18 @@ def _run_agentic_mode(command: str,
|
|
|
1215
1261
|
messages=state.messages)
|
|
1216
1262
|
|
|
1217
1263
|
generated_code = print_and_process_stream(llm_response.get('response'),
|
|
1218
|
-
|
|
1219
|
-
|
|
1264
|
+
npc_model,
|
|
1265
|
+
npc_provider
|
|
1220
1266
|
)
|
|
1221
1267
|
|
|
1222
|
-
|
|
1223
1268
|
state.messages.append({'role':'user', 'content':current_command })
|
|
1224
1269
|
state.messages.append({'role':'assistant', 'content': generated_code})
|
|
1225
1270
|
|
|
1226
1271
|
if '<request_for_input>' in generated_code:
|
|
1227
1272
|
generated_code = generated_code.split('>')[1].split('<')[0]
|
|
1228
1273
|
user_feedback = input("\n🤔 Agent requests feedback (press Enter to continue or type your input): ").strip()
|
|
1229
|
-
|
|
1230
1274
|
current_command = f"{current_command} - User feedback: {user_feedback}"
|
|
1231
1275
|
max_iterations += int(max_iterations/2)
|
|
1232
|
-
|
|
1233
1276
|
continue
|
|
1234
1277
|
|
|
1235
1278
|
if generated_code.startswith('```python'):
|
|
@@ -1237,13 +1280,9 @@ def _run_agentic_mode(command: str,
|
|
|
1237
1280
|
if generated_code.endswith('```'):
|
|
1238
1281
|
generated_code = generated_code[:-3].strip()
|
|
1239
1282
|
|
|
1240
|
-
#print(f"\n# Generated Code (Iteration {iteration}):\n---\n{generated_code}\n---\n")
|
|
1241
|
-
|
|
1242
1283
|
try:
|
|
1243
|
-
# Capture stdout/stderr during execution
|
|
1244
1284
|
stdout_capture = StringIO()
|
|
1245
1285
|
stderr_capture = StringIO()
|
|
1246
|
-
|
|
1247
1286
|
with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
|
|
1248
1287
|
state, exec_output = execute_python_code(generated_code,
|
|
1249
1288
|
state,
|
|
@@ -1251,97 +1290,73 @@ def _run_agentic_mode(command: str,
|
|
|
1251
1290
|
|
|
1252
1291
|
captured_stdout = stdout_capture.getvalue()
|
|
1253
1292
|
captured_stderr = stderr_capture.getvalue()
|
|
1254
|
-
print(exec_output)
|
|
1255
|
-
|
|
1256
|
-
if captured_stdout:
|
|
1293
|
+
if exec_output: print(exec_output)
|
|
1294
|
+
if captured_stdout:
|
|
1257
1295
|
print("\n📤 Captured stdout:\n", captured_stdout)
|
|
1258
|
-
if captured_stderr:
|
|
1296
|
+
if captured_stderr:
|
|
1259
1297
|
print("\n❌ Captured stderr:\n", captured_stderr)
|
|
1260
1298
|
|
|
1261
1299
|
combined_output = f"{exec_output}\nstdout:\n{captured_stdout}\nstderr:\n{captured_stderr}"
|
|
1262
1300
|
full_output.append(f"Iteration {iteration}:\nCode:\n{generated_code}\nOutput:\n{combined_output}")
|
|
1263
1301
|
|
|
1264
|
-
# Update the context with new variables
|
|
1265
1302
|
new_vars = []
|
|
1266
1303
|
for var_name, var_value in locals_dict.items():
|
|
1267
1304
|
if (not var_name.startswith('_') and
|
|
1268
1305
|
var_name not in existing_vars_context and
|
|
1269
1306
|
var_name not in ['In', 'Out', 'exit', 'quit', 'get_ipython']):
|
|
1270
1307
|
new_vars.append(var_name)
|
|
1271
|
-
|
|
1272
1308
|
if new_vars:
|
|
1273
1309
|
existing_vars_context += f"\nNEW VARIABLES CREATED: {', '.join(new_vars)}\n"
|
|
1274
1310
|
|
|
1275
1311
|
analysis_prompt = f"""
|
|
1276
1312
|
CODE EXECUTION RESULTS: {combined_output}
|
|
1277
|
-
|
|
1278
1313
|
EXISTING VARIABLES: {existing_vars_context}
|
|
1279
|
-
|
|
1280
1314
|
EXECUTED_CODE: {generated_code}
|
|
1281
|
-
|
|
1282
1315
|
PREVIOUS_CODE: {previous_code}
|
|
1283
|
-
|
|
1284
1316
|
PREVIOUS ATTEMPTS: ```{full_output[-3:] if full_output else 'None'}```
|
|
1285
|
-
|
|
1286
1317
|
Here are the steps so far: {steps}
|
|
1287
|
-
|
|
1288
1318
|
ANALYSIS:
|
|
1289
|
-
- Is there MEANINGFUL PROGRESS? Return 'progress' if making good progress.
|
|
1290
|
-
- Is there a PROBLEM? Return 'problem' if stuck or error occurred
|
|
1291
|
-
- Is there an
|
|
1292
|
-
- Is the analysis
|
|
1293
|
-
- Return ONLY one of these words followed by a brief explanation
|
|
1319
|
+
- Is there MEANINGFUL PROGRESS? Return 'progress' if making good progress.
|
|
1320
|
+
- Is there a PROBLEM? Return 'problem' if stuck or error occurred.
|
|
1321
|
+
- Is there an AMBIGUITY that should be resolved? Return 'question'.
|
|
1322
|
+
- Is the analysis COMPLETE enough to get feedback? If it's pretty much done, return 'complete'.
|
|
1323
|
+
- Return ONLY one of these words followed by a brief explanation for the next step.
|
|
1294
1324
|
"""
|
|
1295
|
-
analysis_response = get_llm_response(analysis_prompt,
|
|
1296
|
-
npc=state.npc,
|
|
1297
|
-
stream=False)
|
|
1298
|
-
|
|
1325
|
+
analysis_response = get_llm_response(analysis_prompt, npc=state.npc, stream=False)
|
|
1299
1326
|
analysis = analysis_response.get("response", "").strip().lower()
|
|
1300
1327
|
next_step = analysis[8:]
|
|
1301
|
-
state.messages.append({'role':'assistant',
|
|
1302
|
-
|
|
1303
|
-
print(f"\n# Analysis:\n{analysis}")
|
|
1304
|
-
|
|
1305
|
-
previous_code = generated_code
|
|
1328
|
+
state.messages.append({'role':'assistant',
|
|
1329
|
+
'content':f'Is there progress? is there a problem/ is there ambiguity? is it complete?\n {analysis}'})
|
|
1306
1330
|
|
|
1307
1331
|
if analysis.startswith('complete'):
|
|
1308
|
-
print("✅ Task completed
|
|
1332
|
+
print(f"✅ Task completed! {_get_guac_agent_emoji(0, max_consecutive_failures)}")
|
|
1309
1333
|
break
|
|
1310
|
-
|
|
1311
|
-
print(
|
|
1334
|
+
elif analysis.startswith('question'):
|
|
1335
|
+
print(f"🤔 Agent has a question: {next_step} {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}")
|
|
1312
1336
|
break
|
|
1313
1337
|
elif analysis.startswith('progress'):
|
|
1314
|
-
consecutive_failures = 0
|
|
1315
|
-
print("➡️ Making progress,
|
|
1338
|
+
consecutive_failures = 0
|
|
1339
|
+
print(f"➡️ Making progress... {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}")
|
|
1316
1340
|
current_command = next_step
|
|
1317
1341
|
elif analysis.startswith('problem'):
|
|
1318
|
-
|
|
1319
|
-
print(f"⚠️ Problem detected ({consecutive_failures}/{max_consecutive_failures}
|
|
1320
|
-
|
|
1321
|
-
current_command = f"{current_command} - PROBLEM in addressing it: {analysis}"
|
|
1322
|
-
max_iterations += int(max_iterations/2)
|
|
1323
|
-
continue
|
|
1342
|
+
consecutive_failures += 1
|
|
1343
|
+
print(f"⚠️ Problem detected ({consecutive_failures}/{max_consecutive_failures}) {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}")
|
|
1344
|
+
current_command = f"{current_command} - PROBLEM: {analysis}"
|
|
1324
1345
|
else:
|
|
1325
|
-
# Default behavior for unexpected responses
|
|
1326
1346
|
consecutive_failures += 1
|
|
1327
|
-
print(f"❓ Unexpected analysis
|
|
1328
|
-
|
|
1329
|
-
print("❌ Too many consecutive failures, stopping iteration.")
|
|
1330
|
-
break
|
|
1331
|
-
except KeyboardInterrupt as e:
|
|
1347
|
+
print(f"❓ Unexpected analysis, counting as failure ({consecutive_failures}/{max_consecutive_failures}) {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}")
|
|
1348
|
+
except KeyboardInterrupt:
|
|
1332
1349
|
user_input = input('User input: ')
|
|
1333
|
-
current_command
|
|
1350
|
+
current_command += user_input
|
|
1334
1351
|
except Exception as e:
|
|
1335
|
-
|
|
1352
|
+
consecutive_failures += 1
|
|
1353
|
+
error_msg = f"Error in iteration {iteration}: {str(e)} {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}"
|
|
1336
1354
|
print(error_msg)
|
|
1337
1355
|
full_output.append(error_msg)
|
|
1338
|
-
consecutive_failures += 1
|
|
1339
1356
|
current_command = f"{current_command} - Error: {str(e)}"
|
|
1340
1357
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
print("❌ Too many consecutive errors, stopping iteration.")
|
|
1344
|
-
break
|
|
1358
|
+
if consecutive_failures >= max_consecutive_failures:
|
|
1359
|
+
print(f"❌ Too many consecutive failures, stopping. {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}")
|
|
1345
1360
|
|
|
1346
1361
|
return state, "# Agentic execution completed\n" + '\n'.join(full_output)
|
|
1347
1362
|
|
|
@@ -1371,14 +1386,17 @@ def get_guac_prompt_char(command_count: int, guac_refresh_period = 100) -> str:
|
|
|
1371
1386
|
def execute_guac_command(command: str, state: ShellState, locals_dict: Dict[str, Any], project_name: str, src_dir: Path, router) -> Tuple[ShellState, Any]:
|
|
1372
1387
|
stripped_command = command.strip()
|
|
1373
1388
|
output = None
|
|
1374
|
-
|
|
1389
|
+
cmd_parts = shlex.split(stripped_command)
|
|
1390
|
+
if cmd_parts and cmd_parts[0] in ["cd", "ls", "pwd"]:
|
|
1391
|
+
return execute_command(stripped_command, state, review=False, router=router)
|
|
1392
|
+
|
|
1393
|
+
npc_team_dir = Path(state.team.team_path) if state.team and hasattr(state.team, 'team_path') else Path.cwd() / "npc_team"
|
|
1394
|
+
|
|
1375
1395
|
if not stripped_command:
|
|
1376
1396
|
return state, None
|
|
1377
1397
|
if stripped_command.lower() in ["exit", "quit", "exit()", "quit()"]:
|
|
1378
1398
|
raise SystemExit("Exiting Guac Mode.")
|
|
1379
1399
|
|
|
1380
|
-
# Get npc_team_dir from current working directory
|
|
1381
|
-
npc_team_dir = Path.cwd() / "npc_team"
|
|
1382
1400
|
if stripped_command.startswith('run '):
|
|
1383
1401
|
file_path = stripped_command[4:].strip()
|
|
1384
1402
|
try:
|
|
@@ -1497,18 +1515,24 @@ def execute_guac_command(command: str, state: ShellState, locals_dict: Dict[str,
|
|
|
1497
1515
|
{locals_context_string}
|
|
1498
1516
|
Begin directly with the code
|
|
1499
1517
|
"""
|
|
1518
|
+
# Ensure state.npc is not None before accessing .model or .provider
|
|
1519
|
+
npc_model = state.npc.model if state.npc and state.npc.model else state.chat_model
|
|
1520
|
+
npc_provider = state.npc.provider if state.npc and state.npc.provider else state.chat_provider
|
|
1521
|
+
|
|
1500
1522
|
llm_response = get_llm_response(prompt_cmd,
|
|
1501
|
-
model=state.chat_model,
|
|
1502
|
-
provider=state.chat_provider,
|
|
1503
1523
|
npc=state.npc,
|
|
1504
1524
|
stream=True,
|
|
1505
1525
|
messages=state.messages)
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1526
|
+
response = print_and_process_stream(llm_response.get('response'),
|
|
1527
|
+
npc_model,
|
|
1528
|
+
npc_provider )
|
|
1529
|
+
|
|
1530
|
+
|
|
1531
|
+
if response.startswith('```python'):
|
|
1532
|
+
generated_code = response.strip()[len('```python'):].strip()
|
|
1509
1533
|
generated_code = generated_code.rsplit('```', 1)[0].strip()
|
|
1510
1534
|
else:
|
|
1511
|
-
generated_code =
|
|
1535
|
+
generated_code = response.strip()
|
|
1512
1536
|
|
|
1513
1537
|
state.messages = llm_response.get("messages", state.messages)
|
|
1514
1538
|
|
|
@@ -1776,6 +1800,7 @@ def run_guac_repl(state: ShellState, project_name: str, package_root: Path, pack
|
|
|
1776
1800
|
state.current_path = os.getcwd()
|
|
1777
1801
|
|
|
1778
1802
|
display_model = state.chat_model
|
|
1803
|
+
# Ensure state.npc is not None before accessing .model or .provider
|
|
1779
1804
|
if isinstance(state.npc, NPC) and state.npc.model:
|
|
1780
1805
|
display_model = state.npc.model
|
|
1781
1806
|
|
|
@@ -1860,12 +1885,34 @@ def enter_guac_mode(npc=None,
|
|
|
1860
1885
|
default_mode_choice=default_mode_choice
|
|
1861
1886
|
)
|
|
1862
1887
|
|
|
1863
|
-
project_name = setup_result.get("
|
|
1888
|
+
project_name = setup_result.get("package_name", "project")
|
|
1864
1889
|
package_root = setup_result["package_root"]
|
|
1865
1890
|
package_name = setup_result.get("package_name", "project")
|
|
1891
|
+
npc_team_dir = setup_result.get("npc_team_dir")
|
|
1866
1892
|
|
|
1893
|
+
# Always call setup_shell to build history, team, and default_npc
|
|
1867
1894
|
command_history, default_team, default_npc = setup_shell()
|
|
1868
|
-
|
|
1895
|
+
|
|
1896
|
+
# 🔑 Ensure global guac gets loaded if npc is None
|
|
1897
|
+
if npc is None and default_npc is None:
|
|
1898
|
+
# Construct the path correctly based on where ensure_global_guac_team puts it
|
|
1899
|
+
guac_npc_path = Path(npc_team_dir) / "guac.npc"
|
|
1900
|
+
if guac_npc_path.exists():
|
|
1901
|
+
npc = NPC(file=str(guac_npc_path), db_conn=command_history.engine)
|
|
1902
|
+
# Ensure the team is also correctly set to the global guac team
|
|
1903
|
+
team_ctx_path = Path(npc_team_dir) / "team.ctx"
|
|
1904
|
+
if team_ctx_path.exists():
|
|
1905
|
+
with open(team_ctx_path, "r") as f:
|
|
1906
|
+
team_ctx = yaml.safe_load(f) or {}
|
|
1907
|
+
team = Team(team_path=str(npc_team_dir), forenpc=npc, jinxs={}) # Simplified team creation
|
|
1908
|
+
team.name = team_ctx.get("team_name", "guac_global_team")
|
|
1909
|
+
else:
|
|
1910
|
+
raise RuntimeError(f"No NPC loaded and {guac_npc_path} not found!")
|
|
1911
|
+
elif default_npc and npc is None:
|
|
1912
|
+
# If setup_shell provided a default_npc (e.g., sibiji), ensure it's used
|
|
1913
|
+
npc = default_npc
|
|
1914
|
+
|
|
1915
|
+
|
|
1869
1916
|
state = ShellState(
|
|
1870
1917
|
conversation_id=start_new_conversation(),
|
|
1871
1918
|
stream_output=True,
|
|
@@ -1873,8 +1920,8 @@ def enter_guac_mode(npc=None,
|
|
|
1873
1920
|
chat_model=os.environ.get("NPCSH_CHAT_MODEL", "gemma3:4b"),
|
|
1874
1921
|
chat_provider=os.environ.get("NPCSH_CHAT_PROVIDER", "ollama"),
|
|
1875
1922
|
current_path=os.getcwd(),
|
|
1876
|
-
npc=npc
|
|
1877
|
-
team=team or default_team
|
|
1923
|
+
npc=npc, # This is now guaranteed to be an NPC object
|
|
1924
|
+
team=team or default_team # Use the correctly loaded team or default
|
|
1878
1925
|
)
|
|
1879
1926
|
|
|
1880
1927
|
state.command_history = command_history
|
|
@@ -1889,8 +1936,6 @@ def enter_guac_mode(npc=None,
|
|
|
1889
1936
|
print(f"Warning: Could not read readline history file {READLINE_HISTORY_FILE}: {e}")
|
|
1890
1937
|
|
|
1891
1938
|
run_guac_repl(state, project_name, package_root, package_name)
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
1939
|
|
|
1895
1940
|
def main():
|
|
1896
1941
|
parser = argparse.ArgumentParser(description="Enter Guac Mode - Interactive Python with LLM assistance.")
|
|
@@ -1913,4 +1958,4 @@ def main():
|
|
|
1913
1958
|
)
|
|
1914
1959
|
|
|
1915
1960
|
if __name__ == "__main__":
|
|
1916
|
-
main()
|
|
1961
|
+
main()
|
|
@@ -78,7 +78,7 @@ extra_files = package_files("npcsh/npc_team/")
|
|
|
78
78
|
|
|
79
79
|
setup(
|
|
80
80
|
name="npcsh",
|
|
81
|
-
version="1.0.
|
|
81
|
+
version="1.0.24",
|
|
82
82
|
packages=find_packages(exclude=["tests*"]),
|
|
83
83
|
install_requires=base_requirements, # Only install base requirements by default
|
|
84
84
|
extras_require={
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|