npcsh 1.0.23__tar.gz → 1.0.25__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.23 → npcsh-1.0.25}/PKG-INFO +1 -1
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/guac.py +206 -160
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh.egg-info/PKG-INFO +1 -1
- {npcsh-1.0.23 → npcsh-1.0.25}/setup.py +1 -1
- {npcsh-1.0.23 → npcsh-1.0.25}/LICENSE +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/README.md +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/__init__.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/_state.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/alicanto.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/corca.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/mcp_helpers.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/mcp_server.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/jinxs/bash_executer.jinx +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/jinxs/edit_file.jinx +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/jinxs/image_generation.jinx +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/jinxs/internet_search.jinx +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/jinxs/python_executor.jinx +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/jinxs/screen_cap.jinx +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/npcsh.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/plonk.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/pti.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/routes.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/spool.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/wander.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh/yap.py +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh.egg-info/SOURCES.txt +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh.egg-info/dependency_links.txt +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh.egg-info/entry_points.txt +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh.egg-info/requires.txt +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/npcsh.egg-info/top_level.txt +0 -0
- {npcsh-1.0.23 → npcsh-1.0.25}/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,69 @@ 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
|
-
|
|
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
|
+
desc = project_description.replace('"', '\\"')
|
|
559
|
+
if not setup_py_path.exists():
|
|
560
|
+
setup_content = f'''from setuptools import setup, find_packages
|
|
561
|
+
setup(name="{package_name}", version="0.0.1", description="{desc}", packages=find_packages())
|
|
562
|
+
'''
|
|
563
|
+
setup_py_path.write_text(setup_content)
|
|
564
|
+
logging.info("Created minimal setup.py at %s", setup_py_path)
|
|
565
|
+
|
|
532
566
|
default_mode_val = default_mode_choice or "agent"
|
|
533
567
|
setup_npc_team(npc_team_dir, lang)
|
|
534
|
-
|
|
535
|
-
|
|
536
568
|
|
|
537
569
|
print(f"\nGuac mode configured for package: {package_name} at {package_root}")
|
|
538
570
|
print(f"Workspace created at: {workspace_dirs['workspace']}")
|
|
539
|
-
|
|
540
571
|
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
|
|
572
|
+
"language": lang, "package_root": Path(package_root), "plots_dir": plots_dir,
|
|
573
|
+
"npc_team_dir": npc_team_dir, "config_dir": config_dir, "default_mode": default_mode_val,
|
|
574
|
+
"project_description": project_description, "package_name": package_name
|
|
549
575
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
576
|
def setup_npc_team(npc_team_dir, lang, is_subteam=False):
|
|
556
577
|
# Create Guac-specific NPCs
|
|
557
578
|
guac_npc = {
|
|
@@ -566,7 +587,7 @@ def setup_npc_team(npc_team_dir, lang, is_subteam=False):
|
|
|
566
587
|
}
|
|
567
588
|
caug_npc = {
|
|
568
589
|
"name": "caug",
|
|
569
|
-
"primary_directive": f"You are caug, a specialist in big data statistical methods in {lang}."
|
|
590
|
+
"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
591
|
}
|
|
571
592
|
|
|
572
593
|
parsely_npc = {
|
|
@@ -1005,7 +1026,7 @@ def _capture_plot_state(session_id: str, db_path: str, npc_team_dir: Path):
|
|
|
1005
1026
|
data_summary=f"{data_points} data points",
|
|
1006
1027
|
change_significance=1.0 if not last else 0.5
|
|
1007
1028
|
)
|
|
1008
|
-
|
|
1029
|
+
|
|
1009
1030
|
session.add(plot_state)
|
|
1010
1031
|
session.commit()
|
|
1011
1032
|
session.close()
|
|
@@ -1122,17 +1143,42 @@ import sys
|
|
|
1122
1143
|
from io import StringIO
|
|
1123
1144
|
from contextlib import redirect_stdout, redirect_stderr
|
|
1124
1145
|
|
|
1146
|
+
# --- New Emoji Function for Agentic Mode ---
|
|
1147
|
+
def _get_guac_agent_emoji(failures: int, max_fail: int = 3) -> str:
|
|
1148
|
+
"""
|
|
1149
|
+
Returns an avocado emoji representing the state based on consecutive failures.
|
|
1150
|
+
Includes "puke" emoji for max_fail, and "skull" for exceeding max_fail + 20.
|
|
1151
|
+
"""
|
|
1152
|
+
if failures == 0:
|
|
1153
|
+
return "🥑" # Fresh
|
|
1154
|
+
elif failures == 1:
|
|
1155
|
+
return "🥑🔪" # Sliced, contemplating next steps
|
|
1156
|
+
elif failures == 2:
|
|
1157
|
+
return "🥑🥣" # In the bowl, getting mashed
|
|
1158
|
+
elif failures == max_fail:
|
|
1159
|
+
return "🥑🤢" # Going bad, critical issue (puke)
|
|
1160
|
+
elif failures > max_fail + 20: # Skull for 20+ over max
|
|
1161
|
+
return "🥑💀" # Rotten
|
|
1162
|
+
elif failures > max_fail:
|
|
1163
|
+
return "🥑🟤" # Bruised and bad
|
|
1164
|
+
else:
|
|
1165
|
+
return "🥑❓" # Unknown state
|
|
1166
|
+
|
|
1167
|
+
# --- New Helper for Persisting Global Choice ---
|
|
1168
|
+
GUAC_GLOBAL_FLAG_FILE = Path.home() / ".npcsh" / ".guac_use_global"
|
|
1169
|
+
|
|
1170
|
+
|
|
1125
1171
|
def _run_agentic_mode(command: str,
|
|
1126
1172
|
state: ShellState,
|
|
1127
1173
|
locals_dict: Dict[str, Any],
|
|
1128
1174
|
npc_team_dir: Path) -> Tuple[ShellState, Any]:
|
|
1129
1175
|
"""Run agentic mode with continuous iteration based on progress"""
|
|
1130
|
-
max_iterations =
|
|
1176
|
+
max_iterations = 5 # Increased slightly for more complex tasks
|
|
1131
1177
|
iteration = 0
|
|
1132
1178
|
full_output = []
|
|
1133
1179
|
current_command = command
|
|
1134
1180
|
consecutive_failures = 0
|
|
1135
|
-
max_consecutive_failures = 3
|
|
1181
|
+
max_consecutive_failures = 3 # This is the limit before stopping
|
|
1136
1182
|
|
|
1137
1183
|
# Build context of existing variables
|
|
1138
1184
|
existing_vars_context = "EXISTING VARIABLES IN ENVIRONMENT:\n"
|
|
@@ -1151,7 +1197,7 @@ def _run_agentic_mode(command: str,
|
|
|
1151
1197
|
steps = []
|
|
1152
1198
|
while iteration < max_iterations and consecutive_failures < max_consecutive_failures:
|
|
1153
1199
|
iteration += 1
|
|
1154
|
-
print(f"\n
|
|
1200
|
+
print(f"\n{_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)} Agentic iteration {iteration} ")
|
|
1155
1201
|
|
|
1156
1202
|
prompt = f"""
|
|
1157
1203
|
USER REQUEST: {current_command} {next_step}
|
|
@@ -1202,12 +1248,13 @@ def _run_agentic_mode(command: str,
|
|
|
1202
1248
|
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
1249
|
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
1250
|
|
|
1205
|
-
Do not over-complicate the code.
|
|
1251
|
+
Do not over- complicate the code.
|
|
1206
1252
|
|
|
1207
1253
|
Do not include any '__name__'=='__main__' block.
|
|
1208
1254
|
"""
|
|
1209
|
-
#
|
|
1210
1255
|
|
|
1256
|
+
npc_model = state.npc.model if state.npc and state.npc.model else state.chat_model
|
|
1257
|
+
npc_provider = state.npc.provider if state.npc and state.npc.provider else state.chat_provider
|
|
1211
1258
|
|
|
1212
1259
|
llm_response = get_llm_response(prompt,
|
|
1213
1260
|
npc=state.npc,
|
|
@@ -1215,21 +1262,18 @@ def _run_agentic_mode(command: str,
|
|
|
1215
1262
|
messages=state.messages)
|
|
1216
1263
|
|
|
1217
1264
|
generated_code = print_and_process_stream(llm_response.get('response'),
|
|
1218
|
-
|
|
1219
|
-
|
|
1265
|
+
npc_model,
|
|
1266
|
+
npc_provider
|
|
1220
1267
|
)
|
|
1221
1268
|
|
|
1222
|
-
|
|
1223
1269
|
state.messages.append({'role':'user', 'content':current_command })
|
|
1224
1270
|
state.messages.append({'role':'assistant', 'content': generated_code})
|
|
1225
1271
|
|
|
1226
1272
|
if '<request_for_input>' in generated_code:
|
|
1227
1273
|
generated_code = generated_code.split('>')[1].split('<')[0]
|
|
1228
1274
|
user_feedback = input("\n🤔 Agent requests feedback (press Enter to continue or type your input): ").strip()
|
|
1229
|
-
|
|
1230
1275
|
current_command = f"{current_command} - User feedback: {user_feedback}"
|
|
1231
1276
|
max_iterations += int(max_iterations/2)
|
|
1232
|
-
|
|
1233
1277
|
continue
|
|
1234
1278
|
|
|
1235
1279
|
if generated_code.startswith('```python'):
|
|
@@ -1237,13 +1281,9 @@ def _run_agentic_mode(command: str,
|
|
|
1237
1281
|
if generated_code.endswith('```'):
|
|
1238
1282
|
generated_code = generated_code[:-3].strip()
|
|
1239
1283
|
|
|
1240
|
-
#print(f"\n# Generated Code (Iteration {iteration}):\n---\n{generated_code}\n---\n")
|
|
1241
|
-
|
|
1242
1284
|
try:
|
|
1243
|
-
# Capture stdout/stderr during execution
|
|
1244
1285
|
stdout_capture = StringIO()
|
|
1245
1286
|
stderr_capture = StringIO()
|
|
1246
|
-
|
|
1247
1287
|
with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
|
|
1248
1288
|
state, exec_output = execute_python_code(generated_code,
|
|
1249
1289
|
state,
|
|
@@ -1251,97 +1291,73 @@ def _run_agentic_mode(command: str,
|
|
|
1251
1291
|
|
|
1252
1292
|
captured_stdout = stdout_capture.getvalue()
|
|
1253
1293
|
captured_stderr = stderr_capture.getvalue()
|
|
1254
|
-
print(exec_output)
|
|
1255
|
-
|
|
1256
|
-
if captured_stdout:
|
|
1294
|
+
if exec_output: print(exec_output)
|
|
1295
|
+
if captured_stdout:
|
|
1257
1296
|
print("\n📤 Captured stdout:\n", captured_stdout)
|
|
1258
|
-
if captured_stderr:
|
|
1297
|
+
if captured_stderr:
|
|
1259
1298
|
print("\n❌ Captured stderr:\n", captured_stderr)
|
|
1260
1299
|
|
|
1261
1300
|
combined_output = f"{exec_output}\nstdout:\n{captured_stdout}\nstderr:\n{captured_stderr}"
|
|
1262
1301
|
full_output.append(f"Iteration {iteration}:\nCode:\n{generated_code}\nOutput:\n{combined_output}")
|
|
1263
1302
|
|
|
1264
|
-
# Update the context with new variables
|
|
1265
1303
|
new_vars = []
|
|
1266
1304
|
for var_name, var_value in locals_dict.items():
|
|
1267
1305
|
if (not var_name.startswith('_') and
|
|
1268
1306
|
var_name not in existing_vars_context and
|
|
1269
1307
|
var_name not in ['In', 'Out', 'exit', 'quit', 'get_ipython']):
|
|
1270
1308
|
new_vars.append(var_name)
|
|
1271
|
-
|
|
1272
1309
|
if new_vars:
|
|
1273
1310
|
existing_vars_context += f"\nNEW VARIABLES CREATED: {', '.join(new_vars)}\n"
|
|
1274
1311
|
|
|
1275
1312
|
analysis_prompt = f"""
|
|
1276
1313
|
CODE EXECUTION RESULTS: {combined_output}
|
|
1277
|
-
|
|
1278
1314
|
EXISTING VARIABLES: {existing_vars_context}
|
|
1279
|
-
|
|
1280
1315
|
EXECUTED_CODE: {generated_code}
|
|
1281
|
-
|
|
1282
1316
|
PREVIOUS_CODE: {previous_code}
|
|
1283
|
-
|
|
1284
1317
|
PREVIOUS ATTEMPTS: ```{full_output[-3:] if full_output else 'None'}```
|
|
1285
|
-
|
|
1286
1318
|
Here are the steps so far: {steps}
|
|
1287
|
-
|
|
1288
1319
|
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
|
|
1320
|
+
- Is there MEANINGFUL PROGRESS? Return 'progress' if making good progress.
|
|
1321
|
+
- Is there a PROBLEM? Return 'problem' if stuck or error occurred.
|
|
1322
|
+
- Is there an AMBIGUITY that should be resolved? Return 'question'.
|
|
1323
|
+
- Is the analysis COMPLETE enough to get feedback? If it's pretty much done, return 'complete'.
|
|
1324
|
+
- Return ONLY one of these words followed by a brief explanation for the next step.
|
|
1294
1325
|
"""
|
|
1295
|
-
analysis_response = get_llm_response(analysis_prompt,
|
|
1296
|
-
npc=state.npc,
|
|
1297
|
-
stream=False)
|
|
1298
|
-
|
|
1326
|
+
analysis_response = get_llm_response(analysis_prompt, npc=state.npc, stream=False)
|
|
1299
1327
|
analysis = analysis_response.get("response", "").strip().lower()
|
|
1300
1328
|
next_step = analysis[8:]
|
|
1301
|
-
state.messages.append({'role':'assistant',
|
|
1302
|
-
|
|
1303
|
-
print(f"\n# Analysis:\n{analysis}")
|
|
1304
|
-
|
|
1305
|
-
previous_code = generated_code
|
|
1329
|
+
state.messages.append({'role':'assistant',
|
|
1330
|
+
'content':f'Is there progress? is there a problem/ is there ambiguity? is it complete?\n {analysis}'})
|
|
1306
1331
|
|
|
1307
1332
|
if analysis.startswith('complete'):
|
|
1308
|
-
print("✅ Task completed
|
|
1333
|
+
print(f"✅ Task completed! {_get_guac_agent_emoji(0, max_consecutive_failures)}")
|
|
1309
1334
|
break
|
|
1310
|
-
|
|
1311
|
-
print(
|
|
1335
|
+
elif analysis.startswith('question'):
|
|
1336
|
+
print(f"🤔 Agent has a question: {next_step} {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}")
|
|
1312
1337
|
break
|
|
1313
1338
|
elif analysis.startswith('progress'):
|
|
1314
|
-
consecutive_failures = 0
|
|
1315
|
-
print("➡️ Making progress,
|
|
1339
|
+
consecutive_failures = 0
|
|
1340
|
+
print(f"➡️ Making progress... {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}")
|
|
1316
1341
|
current_command = next_step
|
|
1317
1342
|
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
|
|
1343
|
+
consecutive_failures += 1
|
|
1344
|
+
print(f"⚠️ Problem detected ({consecutive_failures}/{max_consecutive_failures}) {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}")
|
|
1345
|
+
current_command = f"{current_command} - PROBLEM: {analysis}"
|
|
1324
1346
|
else:
|
|
1325
|
-
# Default behavior for unexpected responses
|
|
1326
1347
|
consecutive_failures += 1
|
|
1327
|
-
print(f"❓ Unexpected analysis
|
|
1328
|
-
|
|
1329
|
-
print("❌ Too many consecutive failures, stopping iteration.")
|
|
1330
|
-
break
|
|
1331
|
-
except KeyboardInterrupt as e:
|
|
1348
|
+
print(f"❓ Unexpected analysis, counting as failure ({consecutive_failures}/{max_consecutive_failures}) {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}")
|
|
1349
|
+
except KeyboardInterrupt:
|
|
1332
1350
|
user_input = input('User input: ')
|
|
1333
|
-
current_command
|
|
1351
|
+
current_command += user_input
|
|
1334
1352
|
except Exception as e:
|
|
1335
|
-
|
|
1353
|
+
consecutive_failures += 1
|
|
1354
|
+
error_msg = f"Error in iteration {iteration}: {str(e)} {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}"
|
|
1336
1355
|
print(error_msg)
|
|
1337
1356
|
full_output.append(error_msg)
|
|
1338
|
-
consecutive_failures += 1
|
|
1339
1357
|
current_command = f"{current_command} - Error: {str(e)}"
|
|
1340
1358
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
print("❌ Too many consecutive errors, stopping iteration.")
|
|
1344
|
-
break
|
|
1359
|
+
if consecutive_failures >= max_consecutive_failures:
|
|
1360
|
+
print(f"❌ Too many consecutive failures, stopping. {_get_guac_agent_emoji(consecutive_failures, max_consecutive_failures)}")
|
|
1345
1361
|
|
|
1346
1362
|
return state, "# Agentic execution completed\n" + '\n'.join(full_output)
|
|
1347
1363
|
|
|
@@ -1371,14 +1387,17 @@ def get_guac_prompt_char(command_count: int, guac_refresh_period = 100) -> str:
|
|
|
1371
1387
|
def execute_guac_command(command: str, state: ShellState, locals_dict: Dict[str, Any], project_name: str, src_dir: Path, router) -> Tuple[ShellState, Any]:
|
|
1372
1388
|
stripped_command = command.strip()
|
|
1373
1389
|
output = None
|
|
1374
|
-
|
|
1390
|
+
cmd_parts = shlex.split(stripped_command)
|
|
1391
|
+
if cmd_parts and cmd_parts[0] in ["cd", "ls", "pwd"]:
|
|
1392
|
+
return execute_command(stripped_command, state, review=False, router=router)
|
|
1393
|
+
|
|
1394
|
+
npc_team_dir = Path(state.team.team_path) if state.team and hasattr(state.team, 'team_path') else Path.cwd() / "npc_team"
|
|
1395
|
+
|
|
1375
1396
|
if not stripped_command:
|
|
1376
1397
|
return state, None
|
|
1377
1398
|
if stripped_command.lower() in ["exit", "quit", "exit()", "quit()"]:
|
|
1378
1399
|
raise SystemExit("Exiting Guac Mode.")
|
|
1379
1400
|
|
|
1380
|
-
# Get npc_team_dir from current working directory
|
|
1381
|
-
npc_team_dir = Path.cwd() / "npc_team"
|
|
1382
1401
|
if stripped_command.startswith('run '):
|
|
1383
1402
|
file_path = stripped_command[4:].strip()
|
|
1384
1403
|
try:
|
|
@@ -1497,18 +1516,24 @@ def execute_guac_command(command: str, state: ShellState, locals_dict: Dict[str,
|
|
|
1497
1516
|
{locals_context_string}
|
|
1498
1517
|
Begin directly with the code
|
|
1499
1518
|
"""
|
|
1519
|
+
# Ensure state.npc is not None before accessing .model or .provider
|
|
1520
|
+
npc_model = state.npc.model if state.npc and state.npc.model else state.chat_model
|
|
1521
|
+
npc_provider = state.npc.provider if state.npc and state.npc.provider else state.chat_provider
|
|
1522
|
+
|
|
1500
1523
|
llm_response = get_llm_response(prompt_cmd,
|
|
1501
|
-
model=state.chat_model,
|
|
1502
|
-
provider=state.chat_provider,
|
|
1503
1524
|
npc=state.npc,
|
|
1504
1525
|
stream=True,
|
|
1505
1526
|
messages=state.messages)
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1527
|
+
response = print_and_process_stream(llm_response.get('response'),
|
|
1528
|
+
npc_model,
|
|
1529
|
+
npc_provider )
|
|
1530
|
+
|
|
1531
|
+
|
|
1532
|
+
if response.startswith('```python'):
|
|
1533
|
+
generated_code = response.strip()[len('```python'):].strip()
|
|
1509
1534
|
generated_code = generated_code.rsplit('```', 1)[0].strip()
|
|
1510
1535
|
else:
|
|
1511
|
-
generated_code =
|
|
1536
|
+
generated_code = response.strip()
|
|
1512
1537
|
|
|
1513
1538
|
state.messages = llm_response.get("messages", state.messages)
|
|
1514
1539
|
|
|
@@ -1776,6 +1801,7 @@ def run_guac_repl(state: ShellState, project_name: str, package_root: Path, pack
|
|
|
1776
1801
|
state.current_path = os.getcwd()
|
|
1777
1802
|
|
|
1778
1803
|
display_model = state.chat_model
|
|
1804
|
+
# Ensure state.npc is not None before accessing .model or .provider
|
|
1779
1805
|
if isinstance(state.npc, NPC) and state.npc.model:
|
|
1780
1806
|
display_model = state.npc.model
|
|
1781
1807
|
|
|
@@ -1860,12 +1886,34 @@ def enter_guac_mode(npc=None,
|
|
|
1860
1886
|
default_mode_choice=default_mode_choice
|
|
1861
1887
|
)
|
|
1862
1888
|
|
|
1863
|
-
project_name = setup_result.get("
|
|
1889
|
+
project_name = setup_result.get("package_name", "project")
|
|
1864
1890
|
package_root = setup_result["package_root"]
|
|
1865
1891
|
package_name = setup_result.get("package_name", "project")
|
|
1892
|
+
npc_team_dir = setup_result.get("npc_team_dir")
|
|
1866
1893
|
|
|
1894
|
+
# Always call setup_shell to build history, team, and default_npc
|
|
1867
1895
|
command_history, default_team, default_npc = setup_shell()
|
|
1868
|
-
|
|
1896
|
+
|
|
1897
|
+
# 🔑 Ensure global guac gets loaded if npc is None
|
|
1898
|
+
if npc is None and default_npc is None:
|
|
1899
|
+
# Construct the path correctly based on where ensure_global_guac_team puts it
|
|
1900
|
+
guac_npc_path = Path(npc_team_dir) / "guac.npc"
|
|
1901
|
+
if guac_npc_path.exists():
|
|
1902
|
+
npc = NPC(file=str(guac_npc_path), db_conn=command_history.engine)
|
|
1903
|
+
# Ensure the team is also correctly set to the global guac team
|
|
1904
|
+
team_ctx_path = Path(npc_team_dir) / "team.ctx"
|
|
1905
|
+
if team_ctx_path.exists():
|
|
1906
|
+
with open(team_ctx_path, "r") as f:
|
|
1907
|
+
team_ctx = yaml.safe_load(f) or {}
|
|
1908
|
+
team = Team(team_path=str(npc_team_dir), forenpc=npc, jinxs={}) # Simplified team creation
|
|
1909
|
+
team.name = team_ctx.get("team_name", "guac_global_team")
|
|
1910
|
+
else:
|
|
1911
|
+
raise RuntimeError(f"No NPC loaded and {guac_npc_path} not found!")
|
|
1912
|
+
elif default_npc and npc is None:
|
|
1913
|
+
# If setup_shell provided a default_npc (e.g., sibiji), ensure it's used
|
|
1914
|
+
npc = default_npc
|
|
1915
|
+
|
|
1916
|
+
|
|
1869
1917
|
state = ShellState(
|
|
1870
1918
|
conversation_id=start_new_conversation(),
|
|
1871
1919
|
stream_output=True,
|
|
@@ -1873,8 +1921,8 @@ def enter_guac_mode(npc=None,
|
|
|
1873
1921
|
chat_model=os.environ.get("NPCSH_CHAT_MODEL", "gemma3:4b"),
|
|
1874
1922
|
chat_provider=os.environ.get("NPCSH_CHAT_PROVIDER", "ollama"),
|
|
1875
1923
|
current_path=os.getcwd(),
|
|
1876
|
-
npc=npc
|
|
1877
|
-
team=team or default_team
|
|
1924
|
+
npc=npc, # This is now guaranteed to be an NPC object
|
|
1925
|
+
team=team or default_team # Use the correctly loaded team or default
|
|
1878
1926
|
)
|
|
1879
1927
|
|
|
1880
1928
|
state.command_history = command_history
|
|
@@ -1889,8 +1937,6 @@ def enter_guac_mode(npc=None,
|
|
|
1889
1937
|
print(f"Warning: Could not read readline history file {READLINE_HISTORY_FILE}: {e}")
|
|
1890
1938
|
|
|
1891
1939
|
run_guac_repl(state, project_name, package_root, package_name)
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
1940
|
|
|
1895
1941
|
def main():
|
|
1896
1942
|
parser = argparse.ArgumentParser(description="Enter Guac Mode - Interactive Python with LLM assistance.")
|
|
@@ -1913,4 +1959,4 @@ def main():
|
|
|
1913
1959
|
)
|
|
1914
1960
|
|
|
1915
1961
|
if __name__ == "__main__":
|
|
1916
|
-
main()
|
|
1962
|
+
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.25",
|
|
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
|
|
File without changes
|