npcsh 1.1.4__py3-none-any.whl → 1.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- npcsh/_state.py +470 -367
- npcsh/npc_team/corca_example.png +0 -0
- npcsh/npc_team/jinxs/{python_executor.jinx → code/python.jinx} +1 -1
- npcsh/npc_team/jinxs/{bash_executer.jinx → code/sh.jinx} +1 -2
- npcsh/npc_team/jinxs/code/sql.jinx +16 -0
- npcsh/npc_team/jinxs/modes/alicanto.jinx +88 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +28 -0
- npcsh/npc_team/jinxs/modes/guac.jinx +46 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +57 -0
- npcsh/npc_team/jinxs/modes/pti.jinx +28 -0
- npcsh/npc_team/jinxs/modes/spool.jinx +40 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +81 -0
- npcsh/npc_team/jinxs/modes/yap.jinx +25 -0
- npcsh/npc_team/jinxs/utils/breathe.jinx +20 -0
- npcsh/npc_team/jinxs/utils/core/build.jinx +65 -0
- npcsh/npc_team/jinxs/utils/core/compile.jinx +50 -0
- npcsh/npc_team/jinxs/utils/core/help.jinx +52 -0
- npcsh/npc_team/jinxs/utils/core/init.jinx +41 -0
- npcsh/npc_team/jinxs/utils/core/jinxs.jinx +32 -0
- npcsh/npc_team/jinxs/utils/core/set.jinx +40 -0
- npcsh/npc_team/jinxs/{edit_file.jinx → utils/edit_file.jinx} +1 -1
- npcsh/npc_team/jinxs/utils/flush.jinx +39 -0
- npcsh/npc_team/jinxs/utils/npc-studio.jinx +77 -0
- npcsh/npc_team/jinxs/utils/ots.jinx +61 -0
- npcsh/npc_team/jinxs/utils/plan.jinx +33 -0
- npcsh/npc_team/jinxs/utils/roll.jinx +66 -0
- npcsh/npc_team/jinxs/utils/sample.jinx +56 -0
- npcsh/npc_team/jinxs/utils/search.jinx +130 -0
- npcsh/npc_team/jinxs/utils/serve.jinx +29 -0
- npcsh/npc_team/jinxs/utils/sleep.jinx +116 -0
- npcsh/npc_team/jinxs/utils/trigger.jinx +36 -0
- npcsh/npc_team/jinxs/utils/vixynt.jinx +117 -0
- npcsh/npcsh.py +13 -11
- npcsh/routes.py +97 -1419
- npcsh-1.1.6.data/data/npcsh/npc_team/alicanto.jinx +88 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/breathe.jinx +20 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/build.jinx +65 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/compile.jinx +50 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/corca.jinx +28 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/edit_file.jinx +1 -1
- npcsh-1.1.6.data/data/npcsh/npc_team/flush.jinx +39 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/guac.jinx +46 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/help.jinx +52 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/init.jinx +41 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/jinxs.jinx +32 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/npc-studio.jinx +77 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/ots.jinx +61 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/plan.jinx +33 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/plonk.jinx +57 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/pti.jinx +28 -0
- npcsh-1.1.4.data/data/npcsh/npc_team/python_executor.jinx → npcsh-1.1.6.data/data/npcsh/npc_team/python.jinx +1 -1
- npcsh-1.1.6.data/data/npcsh/npc_team/roll.jinx +66 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/sample.jinx +56 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/search.jinx +130 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/serve.jinx +29 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/set.jinx +40 -0
- npcsh-1.1.4.data/data/npcsh/npc_team/bash_executer.jinx → npcsh-1.1.6.data/data/npcsh/npc_team/sh.jinx +1 -2
- npcsh-1.1.6.data/data/npcsh/npc_team/sleep.jinx +116 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/spool.jinx +40 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/sql.jinx +16 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/trigger.jinx +36 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/vixynt.jinx +117 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/wander.jinx +81 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/yap.jinx +25 -0
- {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/METADATA +1 -10
- npcsh-1.1.6.dist-info/RECORD +124 -0
- npcsh/npc_team/jinxs/image_generation.jinx +0 -29
- npcsh/npc_team/jinxs/internet_search.jinx +0 -31
- npcsh/npc_team/jinxs/kg_search.jinx +0 -43
- npcsh/npc_team/jinxs/memory_search.jinx +0 -36
- npcsh/npc_team/jinxs/screen_cap.jinx +0 -25
- npcsh-1.1.4.data/data/npcsh/npc_team/image_generation.jinx +0 -29
- npcsh-1.1.4.data/data/npcsh/npc_team/internet_search.jinx +0 -31
- npcsh-1.1.4.data/data/npcsh/npc_team/kg_search.jinx +0 -43
- npcsh-1.1.4.data/data/npcsh/npc_team/memory_search.jinx +0 -36
- npcsh-1.1.4.data/data/npcsh/npc_team/screen_cap.jinx +0 -25
- npcsh-1.1.4.dist-info/RECORD +0 -78
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/WHEEL +0 -0
- {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/entry_points.txt +0 -0
- {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/top_level.txt +0 -0
npcsh/_state.py
CHANGED
|
@@ -88,12 +88,18 @@ from npcpy.llm_funcs import (
|
|
|
88
88
|
breathe,
|
|
89
89
|
|
|
90
90
|
)
|
|
91
|
+
|
|
91
92
|
from npcpy.memory.knowledge_graph import (
|
|
92
93
|
kg_evolve_incremental,
|
|
93
94
|
|
|
94
95
|
)
|
|
95
96
|
from npcpy.gen.embeddings import get_embeddings
|
|
96
97
|
|
|
98
|
+
import inspect
|
|
99
|
+
import sys
|
|
100
|
+
from npcpy.memory.search import execute_rag_command, execute_brainblast_command
|
|
101
|
+
from npcpy.data.load import load_file_contents
|
|
102
|
+
from npcpy.data.web import search_web
|
|
97
103
|
try:
|
|
98
104
|
import readline
|
|
99
105
|
except:
|
|
@@ -443,6 +449,138 @@ def get_team_ctx_path(team_path: str) -> Optional[str]:
|
|
|
443
449
|
return str(ctx_files[0]) if ctx_files else None
|
|
444
450
|
|
|
445
451
|
|
|
452
|
+
from npcpy.memory.memory_processor import memory_approval_ui
|
|
453
|
+
from npcpy.ft.memory_trainer import MemoryTrainer
|
|
454
|
+
from npcpy.llm_funcs import get_facts
|
|
455
|
+
|
|
456
|
+
def get_relevant_memories(
|
|
457
|
+
command_history: CommandHistory,
|
|
458
|
+
npc_name: str,
|
|
459
|
+
team_name: str,
|
|
460
|
+
path: str,
|
|
461
|
+
query: Optional[str] = None,
|
|
462
|
+
max_memories: int = 10,
|
|
463
|
+
state: Optional[ShellState] = None
|
|
464
|
+
) -> List[Dict]:
|
|
465
|
+
|
|
466
|
+
engine = command_history.engine
|
|
467
|
+
|
|
468
|
+
all_memories = command_history.get_memories_for_scope(
|
|
469
|
+
npc=npc_name,
|
|
470
|
+
team=team_name,
|
|
471
|
+
directory_path=path,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
if not all_memories:
|
|
475
|
+
return []
|
|
476
|
+
|
|
477
|
+
if len(all_memories) <= max_memories and not query:
|
|
478
|
+
return all_memories
|
|
479
|
+
|
|
480
|
+
if query:
|
|
481
|
+
query_lower = query.lower()
|
|
482
|
+
keyword_matches = [
|
|
483
|
+
m for m in all_memories
|
|
484
|
+
if query_lower in (m.get('final_memory') or m.get('initial_memory') or '').lower()
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
if keyword_matches:
|
|
488
|
+
return keyword_matches[:max_memories]
|
|
489
|
+
|
|
490
|
+
if state and state.embedding_model and state.embedding_provider:
|
|
491
|
+
try:
|
|
492
|
+
from npcpy.gen.embeddings import get_embeddings
|
|
493
|
+
|
|
494
|
+
search_text = query if query else "recent context"
|
|
495
|
+
query_embedding = get_embeddings(
|
|
496
|
+
[search_text],
|
|
497
|
+
state.embedding_model,
|
|
498
|
+
state.embedding_provider
|
|
499
|
+
)[0]
|
|
500
|
+
|
|
501
|
+
memory_texts = [
|
|
502
|
+
m.get('final_memory', '') for m in all_memories
|
|
503
|
+
]
|
|
504
|
+
memory_embeddings = get_embeddings(
|
|
505
|
+
memory_texts,
|
|
506
|
+
state.embedding_model,
|
|
507
|
+
state.embedding_provider
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
import numpy as np
|
|
511
|
+
similarities = []
|
|
512
|
+
for mem_emb in memory_embeddings:
|
|
513
|
+
similarity = np.dot(query_embedding, mem_emb) / (
|
|
514
|
+
np.linalg.norm(query_embedding) *
|
|
515
|
+
np.linalg.norm(mem_emb)
|
|
516
|
+
)
|
|
517
|
+
similarities.append(similarity)
|
|
518
|
+
|
|
519
|
+
sorted_indices = np.argsort(similarities)[::-1]
|
|
520
|
+
return [all_memories[i] for i in sorted_indices[:max_memories]]
|
|
521
|
+
|
|
522
|
+
except Exception as e:
|
|
523
|
+
print(colored(
|
|
524
|
+
f"RAG search failed, using recent: {e}",
|
|
525
|
+
"yellow"
|
|
526
|
+
))
|
|
527
|
+
|
|
528
|
+
return all_memories[-max_memories:]
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def search_kg_facts(
|
|
532
|
+
self,
|
|
533
|
+
npc: str,
|
|
534
|
+
team: str,
|
|
535
|
+
directory_path: str,
|
|
536
|
+
query: str
|
|
537
|
+
) -> List[Dict]:
|
|
538
|
+
|
|
539
|
+
kg = load_kg_from_db(
|
|
540
|
+
self.engine,
|
|
541
|
+
team,
|
|
542
|
+
npc,
|
|
543
|
+
directory_path
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
if not kg or 'facts' not in kg:
|
|
547
|
+
return []
|
|
548
|
+
|
|
549
|
+
query_lower = query.lower()
|
|
550
|
+
matching_facts = []
|
|
551
|
+
|
|
552
|
+
for fact in kg['facts']:
|
|
553
|
+
statement = fact.get('statement', '').lower()
|
|
554
|
+
if query_lower in statement:
|
|
555
|
+
matching_facts.append(fact)
|
|
556
|
+
|
|
557
|
+
return matching_facts
|
|
558
|
+
|
|
559
|
+
def format_memory_context(memory_examples):
|
|
560
|
+
if not memory_examples:
|
|
561
|
+
return ""
|
|
562
|
+
|
|
563
|
+
context_parts = []
|
|
564
|
+
|
|
565
|
+
approved_examples = memory_examples.get("approved", [])
|
|
566
|
+
rejected_examples = memory_examples.get("rejected", [])
|
|
567
|
+
|
|
568
|
+
if approved_examples:
|
|
569
|
+
context_parts.append("EXAMPLES OF GOOD MEMORIES:")
|
|
570
|
+
for ex in approved_examples[:5]:
|
|
571
|
+
final = ex.get("final_memory") or ex.get("initial_memory")
|
|
572
|
+
context_parts.append(f"- {final}")
|
|
573
|
+
|
|
574
|
+
if rejected_examples:
|
|
575
|
+
context_parts.append("\nEXAMPLES OF POOR MEMORIES TO AVOID:")
|
|
576
|
+
for ex in rejected_examples[:3]:
|
|
577
|
+
context_parts.append(f"- {ex.get('initial_memory')}")
|
|
578
|
+
|
|
579
|
+
if context_parts:
|
|
580
|
+
context_parts.append("\nLearn from these examples to generate similar high-quality memories.")
|
|
581
|
+
return "\n".join(context_parts)
|
|
582
|
+
|
|
583
|
+
return ""
|
|
446
584
|
def add_npcshrc_to_shell_config() -> None:
|
|
447
585
|
"""
|
|
448
586
|
Function Description:
|
|
@@ -1863,156 +2001,57 @@ def should_skip_kg_processing(user_input: str, assistant_output: str) -> bool:
|
|
|
1863
2001
|
|
|
1864
2002
|
return False
|
|
1865
2003
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
2004
|
def execute_slash_command(command: str,
|
|
1870
2005
|
stdin_input: Optional[str],
|
|
1871
2006
|
state: ShellState,
|
|
1872
2007
|
stream: bool,
|
|
1873
2008
|
router) -> Tuple[ShellState, Any]:
|
|
1874
|
-
"""Executes slash commands using the router
|
|
1875
|
-
|
|
2009
|
+
"""Executes slash commands using the router."""
|
|
2010
|
+
try:
|
|
2011
|
+
all_command_parts = shlex.split(command)
|
|
2012
|
+
except ValueError:
|
|
2013
|
+
all_command_parts = command.split()
|
|
1876
2014
|
command_name = all_command_parts[0].lstrip('/')
|
|
1877
2015
|
|
|
1878
|
-
|
|
2016
|
+
# --- NPC SWITCHING LOGIC ---
|
|
1879
2017
|
if command_name in ['n', 'npc']:
|
|
1880
2018
|
npc_to_switch_to = all_command_parts[1] if len(all_command_parts) > 1 else None
|
|
1881
2019
|
if npc_to_switch_to and state.team and npc_to_switch_to in state.team.npcs:
|
|
1882
2020
|
state.npc = state.team.npcs[npc_to_switch_to]
|
|
1883
|
-
return state, f"Switched to NPC: {npc_to_switch_to}"
|
|
2021
|
+
return state, {"output": f"Switched to NPC: {npc_to_switch_to}", "messages": state.messages}
|
|
1884
2022
|
else:
|
|
1885
2023
|
available_npcs = list(state.team.npcs.keys()) if state.team else []
|
|
1886
|
-
return state, colored(f"NPC '{npc_to_switch_to}' not found. Available NPCs: {', '.join(available_npcs)}", "red")
|
|
2024
|
+
return state, {"output": colored(f"NPC '{npc_to_switch_to}' not found. Available NPCs: {', '.join(available_npcs)}", "red"), "messages": state.messages}
|
|
1887
2025
|
|
|
1888
|
-
|
|
2026
|
+
# --- ROUTER LOGIC ---
|
|
1889
2027
|
handler = router.get_route(command_name)
|
|
1890
2028
|
if handler:
|
|
1891
|
-
parsed_flags, positional_args = parse_generic_command_flags(all_command_parts[1:])
|
|
1892
|
-
normalized_flags = normalize_and_expand_flags(parsed_flags)
|
|
1893
|
-
|
|
1894
2029
|
handler_kwargs = {
|
|
1895
|
-
'stream': stream,
|
|
1896
|
-
'
|
|
1897
|
-
'messages': state.messages,
|
|
1898
|
-
'api_url': state.api_url,
|
|
1899
|
-
'api_key': state.api_key,
|
|
1900
|
-
'stdin_input': stdin_input,
|
|
1901
|
-
'positional_args': positional_args,
|
|
1902
|
-
'plonk_context': state.team.shared_context.get('PLONK_CONTEXT') if state.team and hasattr(state.team, 'shared_context') else None,
|
|
1903
|
-
|
|
1904
|
-
|
|
2030
|
+
'stream': stream, 'team': state.team, 'messages': state.messages, 'api_url': state.api_url,
|
|
2031
|
+
'api_key': state.api_key, 'stdin_input': stdin_input,
|
|
1905
2032
|
'model': state.npc.model if isinstance(state.npc, NPC) and state.npc.model else state.chat_model,
|
|
1906
2033
|
'provider': state.npc.provider if isinstance(state.npc, NPC) and state.npc.provider else state.chat_provider,
|
|
1907
|
-
'npc': state.npc,
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
'
|
|
1911
|
-
'emodel': state.embedding_model,
|
|
1912
|
-
'eprovider': state.embedding_provider,
|
|
1913
|
-
'igmodel': state.image_gen_model,
|
|
1914
|
-
'igprovider': state.image_gen_provider,
|
|
1915
|
-
'vgmodel': state.video_gen_model,
|
|
1916
|
-
'vgprovider': state.video_gen_provider,
|
|
1917
|
-
'vmodel': state.vision_model,
|
|
1918
|
-
'vprovider': state.vision_provider,
|
|
1919
|
-
'rmodel': state.reasoning_model,
|
|
1920
|
-
'rprovider': state.reasoning_provider,
|
|
2034
|
+
'npc': state.npc, 'sprovider': state.search_provider, 'emodel': state.embedding_model,
|
|
2035
|
+
'eprovider': state.embedding_provider, 'igmodel': state.image_gen_model, 'igprovider': state.image_gen_provider,
|
|
2036
|
+
'vmodel': state.vision_model, 'vprovider': state.vision_provider, 'rmodel': state.reasoning_model,
|
|
2037
|
+
'rprovider': state.reasoning_provider, 'state': state
|
|
1921
2038
|
}
|
|
1922
|
-
|
|
1923
|
-
if len(normalized_flags) > 0:
|
|
1924
|
-
kwarg_part = 'with kwargs: \n -' + '\n -'.join(f'{key}={item}' for key, item in normalized_flags.items())
|
|
1925
|
-
else:
|
|
1926
|
-
kwarg_part = ''
|
|
1927
|
-
|
|
1928
|
-
render_markdown(f'- Calling {command_name} handler {kwarg_part} ')
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
if 'model' in normalized_flags and 'provider' not in normalized_flags:
|
|
1932
|
-
inferred_provider = lookup_provider(normalized_flags['model'])
|
|
1933
|
-
if inferred_provider:
|
|
1934
|
-
handler_kwargs['provider'] = inferred_provider
|
|
1935
|
-
print(colored(f"Info: Inferred provider '{inferred_provider}' for model '{normalized_flags['model']}'.", "cyan"))
|
|
1936
|
-
|
|
1937
|
-
if 'provider' in normalized_flags and 'model' not in normalized_flags:
|
|
1938
|
-
current_provider = lookup_provider(handler_kwargs['model'])
|
|
1939
|
-
if current_provider != normalized_flags['provider']:
|
|
1940
|
-
prov = normalized_flags['provider']
|
|
1941
|
-
print(f'Please specify a model for the provider: {prov}')
|
|
1942
|
-
|
|
1943
|
-
handler_kwargs.update(normalized_flags)
|
|
1944
|
-
|
|
1945
2039
|
try:
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
return state, result_dict
|
|
1951
|
-
else:
|
|
1952
|
-
return state, result_dict
|
|
2040
|
+
result = handler(command=command, **handler_kwargs)
|
|
2041
|
+
if isinstance(result, dict):
|
|
2042
|
+
state.messages = result.get("messages", state.messages)
|
|
2043
|
+
return state, result
|
|
1953
2044
|
except Exception as e:
|
|
1954
2045
|
import traceback
|
|
1955
|
-
print(f"Error executing slash command '{command_name}':", file=sys.stderr)
|
|
1956
2046
|
traceback.print_exc()
|
|
1957
|
-
return state, colored(f"Error executing slash command '{command_name}': {e}", "red")
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
active_npc = state.npc if isinstance(state.npc, NPC) else None
|
|
1961
|
-
jinx_to_execute = None
|
|
1962
|
-
executor = None
|
|
2047
|
+
return state, {"output": colored(f"Error executing slash command '{command_name}': {e}", "red"), "messages": state.messages}
|
|
1963
2048
|
|
|
1964
|
-
|
|
1965
|
-
jinx_to_execute = active_npc.jinxs_dict[command_name]
|
|
1966
|
-
executor = active_npc
|
|
1967
|
-
elif state.team and hasattr(state.team, 'jinxs_dict') and command_name in state.team.jinxs_dict:
|
|
1968
|
-
jinx_to_execute = state.team.jinxs_dict[command_name]
|
|
1969
|
-
executor = state.team
|
|
1970
|
-
if jinx_to_execute:
|
|
1971
|
-
args = all_command_parts[1:]
|
|
1972
|
-
try:
|
|
1973
|
-
|
|
1974
|
-
input_values = {}
|
|
1975
|
-
if hasattr(jinx_to_execute, 'inputs') and jinx_to_execute.inputs:
|
|
1976
|
-
for i, input_name in enumerate(jinx_to_execute.inputs):
|
|
1977
|
-
if i < len(args):
|
|
1978
|
-
input_values[input_name] = args[i]
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
if isinstance(executor, NPC):
|
|
1982
|
-
jinx_output = jinx_to_execute.execute(
|
|
1983
|
-
input_values=input_values,
|
|
1984
|
-
jinxs_dict=executor.jinxs_dict if hasattr(executor, 'jinxs_dict') else {},
|
|
1985
|
-
npc=executor,
|
|
1986
|
-
messages=state.messages
|
|
1987
|
-
)
|
|
1988
|
-
else:
|
|
1989
|
-
jinx_output = jinx_to_execute.execute(
|
|
1990
|
-
input_values=input_values,
|
|
1991
|
-
jinxs_dict=executor.jinxs_dict if hasattr(executor, 'jinxs_dict') else {},
|
|
1992
|
-
npc=active_npc or state.npc,
|
|
1993
|
-
messages=state.messages
|
|
1994
|
-
)
|
|
1995
|
-
if isinstance(jinx_output, dict) and 'messages' in jinx_output:
|
|
1996
|
-
state.messages = jinx_output['messages']
|
|
1997
|
-
return state, str(jinx_output.get('output', jinx_output))
|
|
1998
|
-
elif isinstance(jinx_output, dict):
|
|
1999
|
-
return state, str(jinx_output.get('output', jinx_output))
|
|
2000
|
-
else:
|
|
2001
|
-
return state, jinx_output
|
|
2002
|
-
|
|
2003
|
-
except Exception as e:
|
|
2004
|
-
import traceback
|
|
2005
|
-
print(f"Error executing jinx '{command_name}':", file=sys.stderr)
|
|
2006
|
-
traceback.print_exc()
|
|
2007
|
-
return state, colored(f"Error executing jinx '{command_name}': {e}", "red")
|
|
2049
|
+
# Fallback for switching NPC by name
|
|
2008
2050
|
if state.team and command_name in state.team.npcs:
|
|
2009
|
-
|
|
2010
|
-
state.npc
|
|
2011
|
-
return state, f"Switched to NPC: {new_npc.name}"
|
|
2012
|
-
|
|
2013
|
-
return state, colored(f"Unknown slash command, jinx, or NPC: {command_name}", "red")
|
|
2014
|
-
|
|
2051
|
+
state.npc = state.team.npcs[command_name]
|
|
2052
|
+
return state, {"output": f"Switched to NPC: {state.npc.name}", "messages": state.messages}
|
|
2015
2053
|
|
|
2054
|
+
return state, {"output": colored(f"Unknown slash command or NPC: {command_name}", "red"), "messages": state.messages}
|
|
2016
2055
|
|
|
2017
2056
|
|
|
2018
2057
|
def process_pipeline_command(
|
|
@@ -2023,15 +2062,14 @@ def process_pipeline_command(
|
|
|
2023
2062
|
review = False,
|
|
2024
2063
|
router = None,
|
|
2025
2064
|
) -> Tuple[ShellState, Any]:
|
|
2026
|
-
'''
|
|
2027
|
-
Processing command
|
|
2028
|
-
'''
|
|
2029
2065
|
|
|
2030
2066
|
if not cmd_segment:
|
|
2031
2067
|
return state, stdin_input
|
|
2032
2068
|
|
|
2033
2069
|
available_models_all = get_locally_available_models(state.current_path)
|
|
2034
|
-
available_models_all_list = [
|
|
2070
|
+
available_models_all_list = [
|
|
2071
|
+
item for key, item in available_models_all.items()
|
|
2072
|
+
]
|
|
2035
2073
|
|
|
2036
2074
|
model_override, provider_override, cmd_cleaned = get_model_and_provider(
|
|
2037
2075
|
cmd_segment, available_models_all_list
|
|
@@ -2040,18 +2078,33 @@ def process_pipeline_command(
|
|
|
2040
2078
|
if not cmd_to_process:
|
|
2041
2079
|
return state, stdin_input
|
|
2042
2080
|
|
|
2043
|
-
npc_model =
|
|
2044
|
-
|
|
2081
|
+
npc_model = (
|
|
2082
|
+
state.npc.model
|
|
2083
|
+
if isinstance(state.npc, NPC) and state.npc.model
|
|
2084
|
+
else None
|
|
2085
|
+
)
|
|
2086
|
+
npc_provider = (
|
|
2087
|
+
state.npc.provider
|
|
2088
|
+
if isinstance(state.npc, NPC) and state.npc.provider
|
|
2089
|
+
else None
|
|
2090
|
+
)
|
|
2045
2091
|
|
|
2046
2092
|
exec_model = model_override or npc_model or state.chat_model
|
|
2047
2093
|
exec_provider = provider_override or npc_provider or state.chat_provider
|
|
2048
2094
|
|
|
2049
2095
|
if cmd_to_process.startswith("/"):
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2096
|
+
with SpinnerContext(
|
|
2097
|
+
f"Routing to {cmd_to_process.split()[0]}",
|
|
2098
|
+
style="arrow"
|
|
2099
|
+
):
|
|
2100
|
+
result = execute_slash_command(
|
|
2101
|
+
cmd_to_process,
|
|
2102
|
+
stdin_input,
|
|
2103
|
+
state,
|
|
2104
|
+
stream_final,
|
|
2105
|
+
router
|
|
2106
|
+
)
|
|
2107
|
+
return result
|
|
2055
2108
|
|
|
2056
2109
|
cmd_parts = parse_command_safely(cmd_to_process)
|
|
2057
2110
|
if not cmd_parts:
|
|
@@ -2064,6 +2117,7 @@ def process_pipeline_command(
|
|
|
2064
2117
|
|
|
2065
2118
|
if command_name in interactive_commands:
|
|
2066
2119
|
return handle_interactive_command(cmd_parts, state)
|
|
2120
|
+
|
|
2067
2121
|
if command_name in TERMINAL_EDITORS:
|
|
2068
2122
|
print(f"Starting interactive editor: {command_name}...")
|
|
2069
2123
|
full_command_str = " ".join(cmd_parts)
|
|
@@ -2071,46 +2125,107 @@ def process_pipeline_command(
|
|
|
2071
2125
|
return state, output
|
|
2072
2126
|
|
|
2073
2127
|
if validate_bash_command(cmd_parts):
|
|
2074
|
-
|
|
2128
|
+
with SpinnerContext(f"Executing {command_name}", style="line"):
|
|
2129
|
+
success, result = handle_bash_command(
|
|
2130
|
+
cmd_parts,
|
|
2131
|
+
cmd_to_process,
|
|
2132
|
+
stdin_input,
|
|
2133
|
+
state
|
|
2134
|
+
)
|
|
2135
|
+
|
|
2075
2136
|
if success:
|
|
2076
2137
|
return state, result
|
|
2077
2138
|
else:
|
|
2078
|
-
print(
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2139
|
+
print(
|
|
2140
|
+
colored(
|
|
2141
|
+
f"Command failed. Consulting {exec_model}...",
|
|
2142
|
+
"yellow"
|
|
2143
|
+
),
|
|
2144
|
+
file=sys.stderr
|
|
2145
|
+
)
|
|
2146
|
+
fixer_prompt = (
|
|
2147
|
+
f"The command '{cmd_to_process}' failed with error: "
|
|
2148
|
+
f"'{result}'. Provide the correct command."
|
|
2087
2149
|
)
|
|
2150
|
+
|
|
2151
|
+
with SpinnerContext(
|
|
2152
|
+
f"{exec_model} analyzing error",
|
|
2153
|
+
style="brain"
|
|
2154
|
+
):
|
|
2155
|
+
response = execute_llm_command(
|
|
2156
|
+
fixer_prompt,
|
|
2157
|
+
model=exec_model,
|
|
2158
|
+
provider=exec_provider,
|
|
2159
|
+
npc=state.npc,
|
|
2160
|
+
stream=stream_final,
|
|
2161
|
+
messages=state.messages
|
|
2162
|
+
)
|
|
2163
|
+
|
|
2088
2164
|
state.messages = response['messages']
|
|
2089
2165
|
return state, response['response']
|
|
2090
2166
|
else:
|
|
2091
|
-
full_llm_cmd =
|
|
2167
|
+
full_llm_cmd = (
|
|
2168
|
+
f"{cmd_to_process} {stdin_input}"
|
|
2169
|
+
if stdin_input
|
|
2170
|
+
else cmd_to_process
|
|
2171
|
+
)
|
|
2092
2172
|
path_cmd = 'The current working directory is: ' + state.current_path
|
|
2093
|
-
ls_files =
|
|
2094
|
-
|
|
2173
|
+
ls_files = (
|
|
2174
|
+
'Files in the current directory (full paths):\n' +
|
|
2175
|
+
"\n".join([
|
|
2176
|
+
os.path.join(state.current_path, f)
|
|
2177
|
+
for f in os.listdir(state.current_path)
|
|
2178
|
+
])
|
|
2179
|
+
if os.path.exists(state.current_path)
|
|
2180
|
+
else 'No files found in the current directory.'
|
|
2181
|
+
)
|
|
2182
|
+
platform_info = (
|
|
2183
|
+
f"Platform: {platform.system()} {platform.release()} "
|
|
2184
|
+
f"({platform.machine()})"
|
|
2185
|
+
)
|
|
2095
2186
|
info = path_cmd + '\n' + ls_files + '\n' + platform_info + '\n'
|
|
2096
2187
|
state.messages.append({'role':'user', 'content':full_llm_cmd})
|
|
2097
2188
|
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
provider=exec_provider,
|
|
2103
|
-
api_url=state.api_url,
|
|
2104
|
-
api_key=state.api_key,
|
|
2105
|
-
npc=state.npc,
|
|
2106
|
-
team=state.team,
|
|
2107
|
-
messages=state.messages,
|
|
2108
|
-
images=state.attachments,
|
|
2109
|
-
stream=stream_final,
|
|
2110
|
-
context=info,
|
|
2189
|
+
npc_name = (
|
|
2190
|
+
state.npc.name
|
|
2191
|
+
if isinstance(state.npc, NPC)
|
|
2192
|
+
else "Assistant"
|
|
2111
2193
|
)
|
|
2112
|
-
|
|
2113
2194
|
|
|
2195
|
+
with SpinnerContext(
|
|
2196
|
+
f"{npc_name} processing with {exec_model}",
|
|
2197
|
+
style="dots_pulse"
|
|
2198
|
+
):
|
|
2199
|
+
# Build extra_globals for jinx execution
|
|
2200
|
+
application_globals_for_jinx = {
|
|
2201
|
+
"CommandHistory": CommandHistory,
|
|
2202
|
+
"load_kg_from_db": load_kg_from_db,
|
|
2203
|
+
"execute_rag_command": execute_rag_command,
|
|
2204
|
+
"execute_brainblast_command": execute_brainblast_command,
|
|
2205
|
+
"load_file_contents": load_file_contents,
|
|
2206
|
+
"search_web": search_web,
|
|
2207
|
+
"get_relevant_memories": get_relevant_memories,
|
|
2208
|
+
"search_kg_facts": search_kg_facts,
|
|
2209
|
+
'state': state
|
|
2210
|
+
}
|
|
2211
|
+
current_module = sys.modules[__name__]
|
|
2212
|
+
for name, func in inspect.getmembers(current_module, inspect.isfunction):
|
|
2213
|
+
application_globals_for_jinx[name] = func
|
|
2214
|
+
|
|
2215
|
+
llm_result = check_llm_command(
|
|
2216
|
+
full_llm_cmd,
|
|
2217
|
+
model=exec_model,
|
|
2218
|
+
provider=exec_provider,
|
|
2219
|
+
api_url=state.api_url,
|
|
2220
|
+
api_key=state.api_key,
|
|
2221
|
+
npc=state.npc,
|
|
2222
|
+
team=state.team,
|
|
2223
|
+
messages=state.messages,
|
|
2224
|
+
images=state.attachments,
|
|
2225
|
+
stream=stream_final,
|
|
2226
|
+
context=info,
|
|
2227
|
+
extra_globals=application_globals_for_jinx # NOW PASS IT
|
|
2228
|
+
)
|
|
2114
2229
|
if not review:
|
|
2115
2230
|
if isinstance(llm_result, dict):
|
|
2116
2231
|
state.messages = llm_result.get("messages", state.messages)
|
|
@@ -2118,7 +2233,6 @@ def process_pipeline_command(
|
|
|
2118
2233
|
return state, output
|
|
2119
2234
|
else:
|
|
2120
2235
|
return state, llm_result
|
|
2121
|
-
|
|
2122
2236
|
else:
|
|
2123
2237
|
return review_and_iterate_command(
|
|
2124
2238
|
original_command=full_llm_cmd,
|
|
@@ -2129,6 +2243,8 @@ def process_pipeline_command(
|
|
|
2129
2243
|
stream_final=stream_final,
|
|
2130
2244
|
info=info
|
|
2131
2245
|
)
|
|
2246
|
+
|
|
2247
|
+
|
|
2132
2248
|
def review_and_iterate_command(
|
|
2133
2249
|
original_command: str,
|
|
2134
2250
|
initial_result: Any,
|
|
@@ -2187,6 +2303,71 @@ def check_mode_switch(command:str , state: ShellState):
|
|
|
2187
2303
|
return True, state
|
|
2188
2304
|
return False, state
|
|
2189
2305
|
|
|
2306
|
+
import sys
|
|
2307
|
+
import time
|
|
2308
|
+
import threading
|
|
2309
|
+
from itertools import cycle
|
|
2310
|
+
|
|
2311
|
+
class SpinnerContext:
|
|
2312
|
+
def __init__(self, message="Processing", style="dots"):
|
|
2313
|
+
self.message = message
|
|
2314
|
+
self.spinning = False
|
|
2315
|
+
self.thread = None
|
|
2316
|
+
|
|
2317
|
+
styles = {
|
|
2318
|
+
"dots": ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
2319
|
+
"line": ["-", "\\", "|", "/"],
|
|
2320
|
+
"arrow": ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
|
|
2321
|
+
"box": ["◰", "◳", "◲", "◱"],
|
|
2322
|
+
"dots_pulse": ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
|
|
2323
|
+
"brain": ["🧠", "💭", "🤔", "💡"],
|
|
2324
|
+
}
|
|
2325
|
+
self.frames = cycle(styles.get(style, styles["dots"]))
|
|
2326
|
+
|
|
2327
|
+
def _spin(self):
|
|
2328
|
+
while self.spinning:
|
|
2329
|
+
sys.stdout.write(
|
|
2330
|
+
f"\r{colored(next(self.frames), 'cyan')} "
|
|
2331
|
+
f"{colored(self.message, 'yellow')}..."
|
|
2332
|
+
)
|
|
2333
|
+
sys.stdout.flush()
|
|
2334
|
+
time.sleep(0.1)
|
|
2335
|
+
|
|
2336
|
+
def __enter__(self):
|
|
2337
|
+
self.spinning = True
|
|
2338
|
+
self.thread = threading.Thread(target=self._spin)
|
|
2339
|
+
self.thread.start()
|
|
2340
|
+
return self
|
|
2341
|
+
|
|
2342
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
2343
|
+
self.spinning = False
|
|
2344
|
+
if self.thread:
|
|
2345
|
+
self.thread.join()
|
|
2346
|
+
sys.stdout.write("\r" + " " * 80 + "\r")
|
|
2347
|
+
sys.stdout.flush()
|
|
2348
|
+
|
|
2349
|
+
def show_thinking_animation(message="Thinking", duration=None):
|
|
2350
|
+
frames = ["🤔", "💭", "🧠", "💡", "✨"]
|
|
2351
|
+
colors = ["cyan", "blue", "magenta", "yellow", "green"]
|
|
2352
|
+
|
|
2353
|
+
start = time.time()
|
|
2354
|
+
i = 0
|
|
2355
|
+
while duration is None or (time.time() - start) < duration:
|
|
2356
|
+
frame = frames[i % len(frames)]
|
|
2357
|
+
color = colors[i % len(colors)]
|
|
2358
|
+
sys.stdout.write(
|
|
2359
|
+
f"\r{colored(frame, color)} "
|
|
2360
|
+
f"{colored(message, 'yellow')}..."
|
|
2361
|
+
)
|
|
2362
|
+
sys.stdout.flush()
|
|
2363
|
+
time.sleep(0.3)
|
|
2364
|
+
i += 1
|
|
2365
|
+
if duration and (time.time() - start) >= duration:
|
|
2366
|
+
break
|
|
2367
|
+
|
|
2368
|
+
sys.stdout.write("\r" + " " * 80 + "\r")
|
|
2369
|
+
sys.stdout.flush()
|
|
2370
|
+
|
|
2190
2371
|
def execute_command(
|
|
2191
2372
|
command: str,
|
|
2192
2373
|
state: ShellState,
|
|
@@ -2200,29 +2381,51 @@ def execute_command(
|
|
|
2200
2381
|
|
|
2201
2382
|
mode_change, state = check_mode_switch(command, state)
|
|
2202
2383
|
if mode_change:
|
|
2384
|
+
print(colored(f"⚡ Switched to {state.current_mode} mode", "green"))
|
|
2203
2385
|
return state, 'Mode changed.'
|
|
2204
2386
|
|
|
2205
|
-
npc_name =
|
|
2387
|
+
npc_name = (
|
|
2388
|
+
state.npc.name
|
|
2389
|
+
if isinstance(state.npc, NPC)
|
|
2390
|
+
else "__none__"
|
|
2391
|
+
)
|
|
2206
2392
|
team_name = state.team.name if state.team else "__none__"
|
|
2207
2393
|
|
|
2208
|
-
|
|
2209
2394
|
original_command_for_embedding = command
|
|
2210
2395
|
commands = split_by_pipes(command)
|
|
2211
2396
|
|
|
2212
2397
|
stdin_for_next = None
|
|
2213
2398
|
final_output = None
|
|
2214
2399
|
current_state = state
|
|
2215
|
-
npc_model =
|
|
2216
|
-
|
|
2400
|
+
npc_model = (
|
|
2401
|
+
state.npc.model
|
|
2402
|
+
if isinstance(state.npc, NPC) and state.npc.model
|
|
2403
|
+
else None
|
|
2404
|
+
)
|
|
2405
|
+
npc_provider = (
|
|
2406
|
+
state.npc.provider
|
|
2407
|
+
if isinstance(state.npc, NPC) and state.npc.provider
|
|
2408
|
+
else None
|
|
2409
|
+
)
|
|
2217
2410
|
active_model = npc_model or state.chat_model
|
|
2218
2411
|
active_provider = npc_provider or state.chat_provider
|
|
2412
|
+
|
|
2219
2413
|
if state.current_mode == 'agent':
|
|
2220
|
-
|
|
2221
|
-
|
|
2414
|
+
total_stages = len(commands)
|
|
2415
|
+
|
|
2222
2416
|
for i, cmd_segment in enumerate(commands):
|
|
2223
|
-
|
|
2417
|
+
stage_num = i + 1
|
|
2418
|
+
stage_emoji = ["🎯", "⚙️", "🔧", "✨", "🚀"][i % 5]
|
|
2419
|
+
|
|
2420
|
+
print(colored(
|
|
2421
|
+
f"\n{stage_emoji} Pipeline Stage {stage_num}/{total_stages}",
|
|
2422
|
+
"cyan",
|
|
2423
|
+
attrs=["bold"]
|
|
2424
|
+
))
|
|
2425
|
+
|
|
2224
2426
|
is_last_command = (i == len(commands) - 1)
|
|
2225
2427
|
stream_this_segment = state.stream_output and not is_last_command
|
|
2428
|
+
|
|
2226
2429
|
try:
|
|
2227
2430
|
current_state, output = process_pipeline_command(
|
|
2228
2431
|
cmd_segment.strip(),
|
|
@@ -2230,19 +2433,26 @@ def execute_command(
|
|
|
2230
2433
|
current_state,
|
|
2231
2434
|
stream_final=stream_this_segment,
|
|
2232
2435
|
review=review,
|
|
2233
|
-
router=
|
|
2436
|
+
router=router
|
|
2234
2437
|
)
|
|
2438
|
+
|
|
2235
2439
|
if is_last_command:
|
|
2440
|
+
print(colored("✅ Pipeline complete", "green"))
|
|
2236
2441
|
return current_state, output
|
|
2442
|
+
|
|
2237
2443
|
if isinstance(output, str):
|
|
2238
2444
|
stdin_for_next = output
|
|
2239
2445
|
elif not isinstance(output, str):
|
|
2240
2446
|
try:
|
|
2241
2447
|
if stream_this_segment:
|
|
2242
|
-
full_stream_output =
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2448
|
+
full_stream_output = (
|
|
2449
|
+
print_and_process_stream_with_markdown(
|
|
2450
|
+
output,
|
|
2451
|
+
state.npc.model,
|
|
2452
|
+
state.npc.provider,
|
|
2453
|
+
show=True
|
|
2454
|
+
)
|
|
2455
|
+
)
|
|
2246
2456
|
stdin_for_next = full_stream_output
|
|
2247
2457
|
if is_last_command:
|
|
2248
2458
|
final_output = full_stream_output
|
|
@@ -2251,24 +2461,40 @@ def execute_command(
|
|
|
2251
2461
|
try:
|
|
2252
2462
|
stdin_for_next = str(output)
|
|
2253
2463
|
except Exception:
|
|
2254
|
-
print(
|
|
2464
|
+
print(
|
|
2465
|
+
f"Warning: Cannot convert output to "
|
|
2466
|
+
f"string for piping: {type(output)}",
|
|
2467
|
+
file=sys.stderr
|
|
2468
|
+
)
|
|
2255
2469
|
stdin_for_next = None
|
|
2256
2470
|
else:
|
|
2257
2471
|
stdin_for_next = None
|
|
2472
|
+
|
|
2473
|
+
print(colored(
|
|
2474
|
+
f" → Passing to stage {stage_num + 1}",
|
|
2475
|
+
"blue"
|
|
2476
|
+
))
|
|
2477
|
+
|
|
2258
2478
|
except Exception as pipeline_error:
|
|
2259
2479
|
import traceback
|
|
2260
2480
|
traceback.print_exc()
|
|
2261
|
-
error_msg = colored(
|
|
2481
|
+
error_msg = colored(
|
|
2482
|
+
f"❌ Error in stage {stage_num} "
|
|
2483
|
+
f"('{cmd_segment[:50]}...'): {pipeline_error}",
|
|
2484
|
+
"red"
|
|
2485
|
+
)
|
|
2262
2486
|
return current_state, error_msg
|
|
2263
2487
|
|
|
2264
2488
|
if final_output is not None and isinstance(final_output,str):
|
|
2265
|
-
store_command_embeddings(
|
|
2489
|
+
store_command_embeddings(
|
|
2490
|
+
original_command_for_embedding,
|
|
2491
|
+
final_output,
|
|
2492
|
+
current_state
|
|
2493
|
+
)
|
|
2266
2494
|
|
|
2267
2495
|
return current_state, final_output
|
|
2268
2496
|
|
|
2269
|
-
|
|
2270
2497
|
elif state.current_mode == 'chat':
|
|
2271
|
-
|
|
2272
2498
|
cmd_parts = parse_command_safely(command)
|
|
2273
2499
|
is_probably_bash = (
|
|
2274
2500
|
cmd_parts
|
|
@@ -2279,6 +2505,7 @@ def execute_command(
|
|
|
2279
2505
|
or command.strip().startswith("/")
|
|
2280
2506
|
)
|
|
2281
2507
|
)
|
|
2508
|
+
|
|
2282
2509
|
if is_probably_bash:
|
|
2283
2510
|
try:
|
|
2284
2511
|
command_name = cmd_parts[0]
|
|
@@ -2288,38 +2515,56 @@ def execute_command(
|
|
|
2288
2515
|
return handle_cd_command(cmd_parts, state)
|
|
2289
2516
|
else:
|
|
2290
2517
|
try:
|
|
2291
|
-
bash_state, bash_output = handle_bash_command(
|
|
2518
|
+
bash_state, bash_output = handle_bash_command(
|
|
2519
|
+
cmd_parts,
|
|
2520
|
+
command,
|
|
2521
|
+
None,
|
|
2522
|
+
state
|
|
2523
|
+
)
|
|
2292
2524
|
return state, bash_output
|
|
2293
2525
|
except Exception as bash_err:
|
|
2294
|
-
return state, colored(
|
|
2526
|
+
return state, colored(
|
|
2527
|
+
f"Bash execution failed: {bash_err}",
|
|
2528
|
+
"red"
|
|
2529
|
+
)
|
|
2295
2530
|
except Exception:
|
|
2296
2531
|
pass
|
|
2297
2532
|
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2533
|
+
with SpinnerContext(
|
|
2534
|
+
f"Chatting with {active_model}",
|
|
2535
|
+
style="brain"
|
|
2536
|
+
):
|
|
2537
|
+
response = get_llm_response(
|
|
2538
|
+
command,
|
|
2539
|
+
model=active_model,
|
|
2540
|
+
provider=active_provider,
|
|
2541
|
+
npc=state.npc,
|
|
2542
|
+
stream=state.stream_output,
|
|
2543
|
+
messages=state.messages
|
|
2544
|
+
)
|
|
2545
|
+
|
|
2307
2546
|
state.messages = response['messages']
|
|
2308
2547
|
return state, response['response']
|
|
2309
2548
|
|
|
2310
2549
|
elif state.current_mode == 'cmd':
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2550
|
+
with SpinnerContext(
|
|
2551
|
+
f"Executing with {active_model}",
|
|
2552
|
+
style="dots_pulse"
|
|
2553
|
+
):
|
|
2554
|
+
response = execute_llm_command(
|
|
2555
|
+
command,
|
|
2556
|
+
model=active_model,
|
|
2557
|
+
provider=active_provider,
|
|
2558
|
+
npc=state.npc,
|
|
2559
|
+
stream=state.stream_output,
|
|
2560
|
+
messages=state.messages
|
|
2561
|
+
)
|
|
2562
|
+
|
|
2318
2563
|
state.messages = response['messages']
|
|
2319
2564
|
return state, response['response']
|
|
2320
2565
|
|
|
2321
|
-
def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
2322
2566
|
|
|
2567
|
+
def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
2323
2568
|
setup_npcsh_config()
|
|
2324
2569
|
|
|
2325
2570
|
db_path = os.getenv("NPCSH_DB_PATH", HISTORY_DB_DEFAULT_PATH)
|
|
@@ -2327,14 +2572,11 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2327
2572
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
2328
2573
|
command_history = CommandHistory(db_path)
|
|
2329
2574
|
|
|
2330
|
-
|
|
2331
2575
|
if not is_npcsh_initialized():
|
|
2332
2576
|
print("Initializing NPCSH...")
|
|
2333
2577
|
initialize_base_npcs_if_needed(db_path)
|
|
2334
2578
|
print("NPCSH initialization complete. Restart or source ~/.npcshrc.")
|
|
2335
2579
|
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
2580
|
try:
|
|
2339
2581
|
history_file = setup_readline()
|
|
2340
2582
|
atexit.register(save_readline_history)
|
|
@@ -2397,7 +2639,6 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2397
2639
|
team_dir = global_team_path
|
|
2398
2640
|
default_forenpc_name = "sibiji"
|
|
2399
2641
|
|
|
2400
|
-
|
|
2401
2642
|
team_ctx = {}
|
|
2402
2643
|
team_ctx_path = get_team_ctx_path(team_dir)
|
|
2403
2644
|
if team_ctx_path:
|
|
@@ -2410,34 +2651,12 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2410
2651
|
|
|
2411
2652
|
print('forenpc_name:', forenpc_name)
|
|
2412
2653
|
|
|
2413
|
-
if team_ctx.get("use_global_jinxs", False):
|
|
2414
|
-
jinxs_dir = os.path.expanduser("~/.npcsh/npc_team/jinxs")
|
|
2415
|
-
else:
|
|
2416
|
-
jinxs_dir = os.path.join(team_dir, "jinxs")
|
|
2417
|
-
|
|
2418
|
-
jinxs_list = load_jinxs_from_directory(jinxs_dir)
|
|
2419
|
-
jinxs_dict = {jinx.jinx_name: jinx for jinx in jinxs_list}
|
|
2420
|
-
|
|
2421
|
-
forenpc_obj = None
|
|
2422
2654
|
forenpc_path = os.path.join(team_dir, f"{forenpc_name}.npc")
|
|
2423
|
-
|
|
2424
2655
|
print('forenpc_path:', forenpc_path)
|
|
2425
2656
|
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
db_conn=command_history.engine)
|
|
2430
|
-
if forenpc_obj.model is None:
|
|
2431
|
-
forenpc_obj.model= team_ctx.get("model", initial_state.chat_model)
|
|
2432
|
-
if forenpc_obj.provider is None:
|
|
2433
|
-
forenpc_obj.provider=team_ctx.get('provider', initial_state.chat_provider)
|
|
2434
|
-
|
|
2435
|
-
else:
|
|
2436
|
-
print(f"Warning: Forenpc file '{forenpc_name}.npc' not found in {team_dir}.")
|
|
2437
|
-
|
|
2438
|
-
team = Team(team_path=team_dir,
|
|
2439
|
-
forenpc=forenpc_obj,
|
|
2440
|
-
jinxs=jinxs_dict)
|
|
2657
|
+
team = Team(team_path=team_dir, db_conn=command_history.engine)
|
|
2658
|
+
|
|
2659
|
+
forenpc_obj = team.forenpc if hasattr(team, 'forenpc') and team.forenpc else None
|
|
2441
2660
|
|
|
2442
2661
|
for npc_name, npc_obj in team.npcs.items():
|
|
2443
2662
|
if not npc_obj.model:
|
|
@@ -2445,12 +2664,12 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2445
2664
|
if not npc_obj.provider:
|
|
2446
2665
|
npc_obj.provider = initial_state.chat_provider
|
|
2447
2666
|
|
|
2448
|
-
|
|
2449
2667
|
if team.forenpc and isinstance(team.forenpc, NPC):
|
|
2450
2668
|
if not team.forenpc.model:
|
|
2451
2669
|
team.forenpc.model = initial_state.chat_model
|
|
2452
2670
|
if not team.forenpc.provider:
|
|
2453
2671
|
team.forenpc.provider = initial_state.chat_provider
|
|
2672
|
+
|
|
2454
2673
|
team_name_from_ctx = team_ctx.get("name")
|
|
2455
2674
|
if team_name_from_ctx:
|
|
2456
2675
|
team.name = team_name_from_ctx
|
|
@@ -2464,145 +2683,20 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2464
2683
|
else:
|
|
2465
2684
|
team.name = "npcsh"
|
|
2466
2685
|
|
|
2467
|
-
|
|
2468
2686
|
return command_history, team, forenpc_obj
|
|
2469
2687
|
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
from npcpy.ft.memory_trainer import MemoryTrainer
|
|
2475
|
-
from npcpy.llm_funcs import get_facts
|
|
2476
|
-
|
|
2477
|
-
def get_relevant_memories(
|
|
2478
|
-
command_history: CommandHistory,
|
|
2479
|
-
npc_name: str,
|
|
2480
|
-
team_name: str,
|
|
2481
|
-
path: str,
|
|
2482
|
-
query: Optional[str] = None,
|
|
2483
|
-
max_memories: int = 10,
|
|
2484
|
-
state: Optional[ShellState] = None
|
|
2485
|
-
) -> List[Dict]:
|
|
2486
|
-
|
|
2487
|
-
engine = command_history.engine
|
|
2488
|
-
|
|
2489
|
-
all_memories = command_history.get_memories_for_scope(
|
|
2490
|
-
npc=npc_name,
|
|
2491
|
-
team=team_name,
|
|
2492
|
-
directory_path=path,
|
|
2493
|
-
status='human-approved'
|
|
2494
|
-
)
|
|
2495
|
-
|
|
2496
|
-
if not all_memories:
|
|
2497
|
-
return []
|
|
2498
|
-
|
|
2499
|
-
if len(all_memories) <= max_memories and not query:
|
|
2500
|
-
return all_memories
|
|
2501
|
-
|
|
2502
|
-
if query:
|
|
2503
|
-
query_lower = query.lower()
|
|
2504
|
-
keyword_matches = [
|
|
2505
|
-
m for m in all_memories
|
|
2506
|
-
if query_lower in (m.get('final_memory') or m.get('initial_memory') or '').lower()
|
|
2507
|
-
]
|
|
2508
|
-
|
|
2509
|
-
if keyword_matches:
|
|
2510
|
-
return keyword_matches[:max_memories]
|
|
2511
|
-
|
|
2512
|
-
if state and state.embedding_model and state.embedding_provider:
|
|
2513
|
-
try:
|
|
2514
|
-
from npcpy.gen.embeddings import get_embeddings
|
|
2515
|
-
|
|
2516
|
-
search_text = query if query else "recent context"
|
|
2517
|
-
query_embedding = get_embeddings(
|
|
2518
|
-
[search_text],
|
|
2519
|
-
state.embedding_model,
|
|
2520
|
-
state.embedding_provider
|
|
2521
|
-
)[0]
|
|
2522
|
-
|
|
2523
|
-
memory_texts = [
|
|
2524
|
-
m.get('final_memory', '') for m in all_memories
|
|
2525
|
-
]
|
|
2526
|
-
memory_embeddings = get_embeddings(
|
|
2527
|
-
memory_texts,
|
|
2528
|
-
state.embedding_model,
|
|
2529
|
-
state.embedding_provider
|
|
2530
|
-
)
|
|
2531
|
-
|
|
2532
|
-
import numpy as np
|
|
2533
|
-
similarities = []
|
|
2534
|
-
for mem_emb in memory_embeddings:
|
|
2535
|
-
similarity = np.dot(query_embedding, mem_emb) / (
|
|
2536
|
-
np.linalg.norm(query_embedding) *
|
|
2537
|
-
np.linalg.norm(mem_emb)
|
|
2538
|
-
)
|
|
2539
|
-
similarities.append(similarity)
|
|
2540
|
-
|
|
2541
|
-
sorted_indices = np.argsort(similarities)[::-1]
|
|
2542
|
-
return [all_memories[i] for i in sorted_indices[:max_memories]]
|
|
2543
|
-
|
|
2544
|
-
except Exception as e:
|
|
2545
|
-
print(colored(
|
|
2546
|
-
f"RAG search failed, using recent: {e}",
|
|
2547
|
-
"yellow"
|
|
2548
|
-
))
|
|
2688
|
+
def initialize_router_with_jinxs(team, router):
|
|
2689
|
+
"""Load global and team Jinxs into router"""
|
|
2690
|
+
global_jinxs_dir = os.path.expanduser("~/.npcsh/npc_team/jinxs")
|
|
2691
|
+
router.load_jinx_routes(global_jinxs_dir)
|
|
2549
2692
|
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
self,
|
|
2555
|
-
npc: str,
|
|
2556
|
-
team: str,
|
|
2557
|
-
directory_path: str,
|
|
2558
|
-
query: str
|
|
2559
|
-
) -> List[Dict]:
|
|
2560
|
-
|
|
2561
|
-
kg = load_kg_from_db(
|
|
2562
|
-
self.engine,
|
|
2563
|
-
team,
|
|
2564
|
-
npc,
|
|
2565
|
-
directory_path
|
|
2566
|
-
)
|
|
2567
|
-
|
|
2568
|
-
if not kg or 'facts' not in kg:
|
|
2569
|
-
return []
|
|
2570
|
-
|
|
2571
|
-
query_lower = query.lower()
|
|
2572
|
-
matching_facts = []
|
|
2573
|
-
|
|
2574
|
-
for fact in kg['facts']:
|
|
2575
|
-
statement = fact.get('statement', '').lower()
|
|
2576
|
-
if query_lower in statement:
|
|
2577
|
-
matching_facts.append(fact)
|
|
2578
|
-
|
|
2579
|
-
return matching_facts
|
|
2580
|
-
|
|
2581
|
-
def format_memory_context(memory_examples):
|
|
2582
|
-
if not memory_examples:
|
|
2583
|
-
return ""
|
|
2693
|
+
if team and team.team_path:
|
|
2694
|
+
team_jinxs_dir = os.path.join(team.team_path, "jinxs")
|
|
2695
|
+
if os.path.exists(team_jinxs_dir):
|
|
2696
|
+
router.load_jinx_routes(team_jinxs_dir)
|
|
2584
2697
|
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
approved_examples = memory_examples.get("approved", [])
|
|
2588
|
-
rejected_examples = memory_examples.get("rejected", [])
|
|
2589
|
-
|
|
2590
|
-
if approved_examples:
|
|
2591
|
-
context_parts.append("EXAMPLES OF GOOD MEMORIES:")
|
|
2592
|
-
for ex in approved_examples[:5]:
|
|
2593
|
-
final = ex.get("final_memory") or ex.get("initial_memory")
|
|
2594
|
-
context_parts.append(f"- {final}")
|
|
2595
|
-
|
|
2596
|
-
if rejected_examples:
|
|
2597
|
-
context_parts.append("\nEXAMPLES OF POOR MEMORIES TO AVOID:")
|
|
2598
|
-
for ex in rejected_examples[:3]:
|
|
2599
|
-
context_parts.append(f"- {ex.get('initial_memory')}")
|
|
2600
|
-
|
|
2601
|
-
if context_parts:
|
|
2602
|
-
context_parts.append("\nLearn from these examples to generate similar high-quality memories.")
|
|
2603
|
-
return "\n".join(context_parts)
|
|
2604
|
-
|
|
2605
|
-
return ""
|
|
2698
|
+
return router
|
|
2699
|
+
|
|
2606
2700
|
|
|
2607
2701
|
def process_memory_approvals(command_history, memory_queue):
|
|
2608
2702
|
pending_memories = memory_queue.get_approval_batch(max_items=5)
|
|
@@ -2688,13 +2782,22 @@ def process_result(
|
|
|
2688
2782
|
result_state.attachments = None
|
|
2689
2783
|
|
|
2690
2784
|
final_output_str = None
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2785
|
+
|
|
2786
|
+
if isinstance(output, dict):
|
|
2787
|
+
output_content = output.get('output')
|
|
2788
|
+
model_for_stream = output.get('model', active_npc.model)
|
|
2789
|
+
provider_for_stream = output.get('provider', active_npc.provider)
|
|
2790
|
+
else:
|
|
2791
|
+
output_content = output
|
|
2792
|
+
model_for_stream = active_npc.model
|
|
2793
|
+
provider_for_stream = active_npc.provider
|
|
2694
2794
|
|
|
2695
2795
|
print('\n')
|
|
2696
2796
|
if user_input == '/help':
|
|
2697
|
-
|
|
2797
|
+
if isinstance(output_content, str):
|
|
2798
|
+
render_markdown(output_content)
|
|
2799
|
+
else:
|
|
2800
|
+
render_markdown(str(output_content))
|
|
2698
2801
|
elif result_state.stream_output:
|
|
2699
2802
|
final_output_str = print_and_process_stream_with_markdown(
|
|
2700
2803
|
output_content,
|