npcsh 1.1.5__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 +438 -319
- npcsh/npc_team/jinxs/code/sh.jinx +0 -1
- npcsh/npc_team/jinxs/code/sql.jinx +1 -3
- npcsh/npc_team/jinxs/utils/npc-studio.jinx +33 -38
- npcsh/npc_team/jinxs/utils/ots.jinx +34 -65
- npcsh/npc_team/jinxs/utils/search.jinx +130 -0
- npcsh/npc_team/jinxs/utils/vixynt.jinx +33 -45
- npcsh/routes.py +32 -14
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npc-studio.jinx +33 -38
- npcsh-1.1.6.data/data/npcsh/npc_team/ots.jinx +61 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/search.jinx +130 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sh.jinx +0 -1
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sql.jinx +1 -3
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/vixynt.jinx +33 -45
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/METADATA +1 -10
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/RECORD +65 -73
- npcsh/npc_team/jinxs/utils/search/brainblast.jinx +0 -51
- npcsh/npc_team/jinxs/utils/search/kg_search.jinx +0 -43
- npcsh/npc_team/jinxs/utils/search/memory_search.jinx +0 -36
- npcsh/npc_team/jinxs/utils/search/rag.jinx +0 -70
- npcsh/npc_team/jinxs/utils/search/search.jinx +0 -192
- npcsh-1.1.5.data/data/npcsh/npc_team/brainblast.jinx +0 -51
- npcsh-1.1.5.data/data/npcsh/npc_team/kg_search.jinx +0 -43
- npcsh-1.1.5.data/data/npcsh/npc_team/memory_search.jinx +0 -36
- npcsh-1.1.5.data/data/npcsh/npc_team/ots.jinx +0 -92
- npcsh-1.1.5.data/data/npcsh/npc_team/rag.jinx +0 -70
- npcsh-1.1.5.data/data/npcsh/npc_team/search.jinx +0 -192
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/breathe.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/flush.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/guac.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plan.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/pti.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/spool.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/wander.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/yap.jinx +0 -0
- {npcsh-1.1.5.data → npcsh-1.1.6.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/WHEEL +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/entry_points.txt +0 -0
- {npcsh-1.1.5.dist-info → npcsh-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.5.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,18 +2001,19 @@ 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
|
|
|
2016
|
+
# --- NPC SWITCHING LOGIC ---
|
|
1878
2017
|
if command_name in ['n', 'npc']:
|
|
1879
2018
|
npc_to_switch_to = all_command_parts[1] if len(all_command_parts) > 1 else None
|
|
1880
2019
|
if npc_to_switch_to and state.team and npc_to_switch_to in state.team.npcs:
|
|
@@ -1884,129 +2023,35 @@ def execute_slash_command(command: str,
|
|
|
1884
2023
|
available_npcs = list(state.team.npcs.keys()) if state.team else []
|
|
1885
2024
|
return state, {"output": colored(f"NPC '{npc_to_switch_to}' not found. Available NPCs: {', '.join(available_npcs)}", "red"), "messages": state.messages}
|
|
1886
2025
|
|
|
2026
|
+
# --- ROUTER LOGIC ---
|
|
1887
2027
|
handler = router.get_route(command_name)
|
|
1888
2028
|
if handler:
|
|
1889
|
-
parsed_flags, positional_args = parse_generic_command_flags(all_command_parts[1:])
|
|
1890
|
-
normalized_flags = normalize_and_expand_flags(parsed_flags)
|
|
1891
|
-
|
|
1892
2029
|
handler_kwargs = {
|
|
1893
|
-
'stream': stream,
|
|
1894
|
-
'
|
|
1895
|
-
'messages': state.messages,
|
|
1896
|
-
'api_url': state.api_url,
|
|
1897
|
-
'api_key': state.api_key,
|
|
1898
|
-
'stdin_input': stdin_input,
|
|
1899
|
-
'positional_args': positional_args,
|
|
1900
|
-
'plonk_context': state.team.shared_context.get('PLONK_CONTEXT') if state.team and hasattr(state.team, 'shared_context') else None,
|
|
1901
|
-
|
|
2030
|
+
'stream': stream, 'team': state.team, 'messages': state.messages, 'api_url': state.api_url,
|
|
2031
|
+
'api_key': state.api_key, 'stdin_input': stdin_input,
|
|
1902
2032
|
'model': state.npc.model if isinstance(state.npc, NPC) and state.npc.model else state.chat_model,
|
|
1903
2033
|
'provider': state.npc.provider if isinstance(state.npc, NPC) and state.npc.provider else state.chat_provider,
|
|
1904
|
-
'npc': state.npc,
|
|
1905
|
-
|
|
1906
|
-
'
|
|
1907
|
-
'
|
|
1908
|
-
'eprovider': state.embedding_provider,
|
|
1909
|
-
'igmodel': state.image_gen_model,
|
|
1910
|
-
'igprovider': state.image_gen_provider,
|
|
1911
|
-
'vgmodel': state.video_gen_model,
|
|
1912
|
-
'vgprovider': state.video_gen_provider,
|
|
1913
|
-
'vmodel': state.vision_model,
|
|
1914
|
-
'vprovider': state.vision_provider,
|
|
1915
|
-
'rmodel': state.reasoning_model,
|
|
1916
|
-
'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
|
|
1917
2038
|
}
|
|
1918
|
-
|
|
1919
|
-
if len(normalized_flags) > 0:
|
|
1920
|
-
kwarg_part = 'with kwargs: \n -' + '\n -'.join(f'{key}={item}' for key, item in normalized_flags.items())
|
|
1921
|
-
else:
|
|
1922
|
-
kwarg_part = ''
|
|
1923
|
-
|
|
1924
|
-
render_markdown(f'- Calling {command_name} handler {kwarg_part} ')
|
|
1925
|
-
|
|
1926
|
-
if 'model' in normalized_flags and 'provider' not in normalized_flags:
|
|
1927
|
-
inferred_provider = lookup_provider(normalized_flags['model'])
|
|
1928
|
-
if inferred_provider:
|
|
1929
|
-
handler_kwargs['provider'] = inferred_provider
|
|
1930
|
-
print(colored(f"Info: Inferred provider '{inferred_provider}' for model '{normalized_flags['model']}'.", "cyan"))
|
|
1931
|
-
|
|
1932
|
-
if 'provider' in normalized_flags and 'model' not in normalized_flags:
|
|
1933
|
-
current_provider = lookup_provider(handler_kwargs['model'])
|
|
1934
|
-
if current_provider != normalized_flags['provider']:
|
|
1935
|
-
prov = normalized_flags['provider']
|
|
1936
|
-
print(f'Please specify a model for the provider: {prov}')
|
|
1937
|
-
|
|
1938
|
-
handler_kwargs.update(normalized_flags)
|
|
1939
|
-
|
|
1940
2039
|
try:
|
|
1941
2040
|
result = handler(command=command, **handler_kwargs)
|
|
1942
|
-
|
|
1943
|
-
if isinstance(result, dict):
|
|
2041
|
+
if isinstance(result, dict):
|
|
1944
2042
|
state.messages = result.get("messages", state.messages)
|
|
1945
|
-
|
|
1946
|
-
elif isinstance(result, str):
|
|
1947
|
-
return state, {"output": result, "messages": state.messages}
|
|
1948
|
-
else:
|
|
1949
|
-
return state, {"output": str(result), "messages": state.messages}
|
|
1950
|
-
|
|
2043
|
+
return state, result
|
|
1951
2044
|
except Exception as e:
|
|
1952
2045
|
import traceback
|
|
1953
|
-
print(f"Error executing slash command '{command_name}':", file=sys.stderr)
|
|
1954
2046
|
traceback.print_exc()
|
|
1955
2047
|
return state, {"output": colored(f"Error executing slash command '{command_name}': {e}", "red"), "messages": state.messages}
|
|
1956
|
-
|
|
1957
|
-
active_npc = state.npc if isinstance(state.npc, NPC) else None
|
|
1958
|
-
jinx_to_execute = None
|
|
1959
|
-
executor = None
|
|
1960
|
-
|
|
1961
|
-
if active_npc and hasattr(active_npc, 'jinxs_dict') and command_name in active_npc.jinxs_dict:
|
|
1962
|
-
jinx_to_execute = active_npc.jinxs_dict[command_name]
|
|
1963
|
-
executor = active_npc
|
|
1964
|
-
elif state.team and hasattr(state.team, 'jinxs_dict') and command_name in state.team.jinxs_dict:
|
|
1965
|
-
jinx_to_execute = state.team.jinxs_dict[command_name]
|
|
1966
|
-
executor = state.team
|
|
1967
|
-
if jinx_to_execute:
|
|
1968
|
-
args = all_command_parts[1:]
|
|
1969
|
-
try:
|
|
1970
|
-
input_values = {}
|
|
1971
|
-
if hasattr(jinx_to_execute, 'inputs') and jinx_to_execute.inputs:
|
|
1972
|
-
for i, input_name in enumerate(jinx_to_execute.inputs):
|
|
1973
|
-
if i < len(args):
|
|
1974
|
-
input_values[input_name] = args[i]
|
|
1975
|
-
|
|
1976
|
-
if isinstance(executor, NPC):
|
|
1977
|
-
jinx_output = jinx_to_execute.execute(
|
|
1978
|
-
input_values=input_values,
|
|
1979
|
-
jinxs_dict=executor.jinxs_dict if hasattr(executor, 'jinxs_dict') else {},
|
|
1980
|
-
npc=executor,
|
|
1981
|
-
messages=state.messages
|
|
1982
|
-
)
|
|
1983
|
-
else:
|
|
1984
|
-
jinx_output = jinx_to_execute.execute(
|
|
1985
|
-
input_values=input_values,
|
|
1986
|
-
jinxs_dict=executor.jinxs_dict if hasattr(executor, 'jinxs_dict') else {},
|
|
1987
|
-
npc=active_npc or state.npc,
|
|
1988
|
-
messages=state.messages
|
|
1989
|
-
)
|
|
1990
|
-
if isinstance(jinx_output, dict) and 'messages' in jinx_output:
|
|
1991
|
-
state.messages = jinx_output['messages']
|
|
1992
|
-
return state, jinx_output
|
|
1993
|
-
elif isinstance(jinx_output, dict):
|
|
1994
|
-
return state, jinx_output
|
|
1995
|
-
else:
|
|
1996
|
-
return state, {"output": str(jinx_output), "messages": state.messages}
|
|
1997
|
-
|
|
1998
|
-
except Exception as e:
|
|
1999
|
-
import traceback
|
|
2000
|
-
print(f"Error executing jinx '{command_name}':", file=sys.stderr)
|
|
2001
|
-
traceback.print_exc()
|
|
2002
|
-
return state, {"output": colored(f"Error executing jinx '{command_name}': {e}", "red"), "messages": state.messages}
|
|
2003
2048
|
|
|
2049
|
+
# Fallback for switching NPC by name
|
|
2004
2050
|
if state.team and command_name in state.team.npcs:
|
|
2005
|
-
|
|
2006
|
-
state.npc
|
|
2007
|
-
return state, {"output": f"Switched to NPC: {new_npc.name}", "messages": state.messages}
|
|
2051
|
+
state.npc = state.team.npcs[command_name]
|
|
2052
|
+
return state, {"output": f"Switched to NPC: {state.npc.name}", "messages": state.messages}
|
|
2008
2053
|
|
|
2009
|
-
return state, {"output": colored(f"Unknown slash command
|
|
2054
|
+
return state, {"output": colored(f"Unknown slash command or NPC: {command_name}", "red"), "messages": state.messages}
|
|
2010
2055
|
|
|
2011
2056
|
|
|
2012
2057
|
def process_pipeline_command(
|
|
@@ -2017,15 +2062,14 @@ def process_pipeline_command(
|
|
|
2017
2062
|
review = False,
|
|
2018
2063
|
router = None,
|
|
2019
2064
|
) -> Tuple[ShellState, Any]:
|
|
2020
|
-
'''
|
|
2021
|
-
Processing command
|
|
2022
|
-
'''
|
|
2023
2065
|
|
|
2024
2066
|
if not cmd_segment:
|
|
2025
2067
|
return state, stdin_input
|
|
2026
2068
|
|
|
2027
2069
|
available_models_all = get_locally_available_models(state.current_path)
|
|
2028
|
-
available_models_all_list = [
|
|
2070
|
+
available_models_all_list = [
|
|
2071
|
+
item for key, item in available_models_all.items()
|
|
2072
|
+
]
|
|
2029
2073
|
|
|
2030
2074
|
model_override, provider_override, cmd_cleaned = get_model_and_provider(
|
|
2031
2075
|
cmd_segment, available_models_all_list
|
|
@@ -2034,18 +2078,33 @@ def process_pipeline_command(
|
|
|
2034
2078
|
if not cmd_to_process:
|
|
2035
2079
|
return state, stdin_input
|
|
2036
2080
|
|
|
2037
|
-
npc_model =
|
|
2038
|
-
|
|
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
|
+
)
|
|
2039
2091
|
|
|
2040
2092
|
exec_model = model_override or npc_model or state.chat_model
|
|
2041
2093
|
exec_provider = provider_override or npc_provider or state.chat_provider
|
|
2042
2094
|
|
|
2043
2095
|
if cmd_to_process.startswith("/"):
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
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
|
|
2049
2108
|
|
|
2050
2109
|
cmd_parts = parse_command_safely(cmd_to_process)
|
|
2051
2110
|
if not cmd_parts:
|
|
@@ -2058,6 +2117,7 @@ def process_pipeline_command(
|
|
|
2058
2117
|
|
|
2059
2118
|
if command_name in interactive_commands:
|
|
2060
2119
|
return handle_interactive_command(cmd_parts, state)
|
|
2120
|
+
|
|
2061
2121
|
if command_name in TERMINAL_EDITORS:
|
|
2062
2122
|
print(f"Starting interactive editor: {command_name}...")
|
|
2063
2123
|
full_command_str = " ".join(cmd_parts)
|
|
@@ -2065,46 +2125,107 @@ def process_pipeline_command(
|
|
|
2065
2125
|
return state, output
|
|
2066
2126
|
|
|
2067
2127
|
if validate_bash_command(cmd_parts):
|
|
2068
|
-
|
|
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
|
+
|
|
2069
2136
|
if success:
|
|
2070
2137
|
return state, result
|
|
2071
2138
|
else:
|
|
2072
|
-
print(
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
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."
|
|
2081
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
|
+
|
|
2082
2164
|
state.messages = response['messages']
|
|
2083
2165
|
return state, response['response']
|
|
2084
2166
|
else:
|
|
2085
|
-
full_llm_cmd =
|
|
2167
|
+
full_llm_cmd = (
|
|
2168
|
+
f"{cmd_to_process} {stdin_input}"
|
|
2169
|
+
if stdin_input
|
|
2170
|
+
else cmd_to_process
|
|
2171
|
+
)
|
|
2086
2172
|
path_cmd = 'The current working directory is: ' + state.current_path
|
|
2087
|
-
ls_files =
|
|
2088
|
-
|
|
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
|
+
)
|
|
2089
2186
|
info = path_cmd + '\n' + ls_files + '\n' + platform_info + '\n'
|
|
2090
2187
|
state.messages.append({'role':'user', 'content':full_llm_cmd})
|
|
2091
2188
|
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
provider=exec_provider,
|
|
2097
|
-
api_url=state.api_url,
|
|
2098
|
-
api_key=state.api_key,
|
|
2099
|
-
npc=state.npc,
|
|
2100
|
-
team=state.team,
|
|
2101
|
-
messages=state.messages,
|
|
2102
|
-
images=state.attachments,
|
|
2103
|
-
stream=stream_final,
|
|
2104
|
-
context=info,
|
|
2189
|
+
npc_name = (
|
|
2190
|
+
state.npc.name
|
|
2191
|
+
if isinstance(state.npc, NPC)
|
|
2192
|
+
else "Assistant"
|
|
2105
2193
|
)
|
|
2106
|
-
|
|
2107
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
|
+
)
|
|
2108
2229
|
if not review:
|
|
2109
2230
|
if isinstance(llm_result, dict):
|
|
2110
2231
|
state.messages = llm_result.get("messages", state.messages)
|
|
@@ -2112,7 +2233,6 @@ def process_pipeline_command(
|
|
|
2112
2233
|
return state, output
|
|
2113
2234
|
else:
|
|
2114
2235
|
return state, llm_result
|
|
2115
|
-
|
|
2116
2236
|
else:
|
|
2117
2237
|
return review_and_iterate_command(
|
|
2118
2238
|
original_command=full_llm_cmd,
|
|
@@ -2123,6 +2243,8 @@ def process_pipeline_command(
|
|
|
2123
2243
|
stream_final=stream_final,
|
|
2124
2244
|
info=info
|
|
2125
2245
|
)
|
|
2246
|
+
|
|
2247
|
+
|
|
2126
2248
|
def review_and_iterate_command(
|
|
2127
2249
|
original_command: str,
|
|
2128
2250
|
initial_result: Any,
|
|
@@ -2181,6 +2303,71 @@ def check_mode_switch(command:str , state: ShellState):
|
|
|
2181
2303
|
return True, state
|
|
2182
2304
|
return False, state
|
|
2183
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
|
+
|
|
2184
2371
|
def execute_command(
|
|
2185
2372
|
command: str,
|
|
2186
2373
|
state: ShellState,
|
|
@@ -2194,29 +2381,51 @@ def execute_command(
|
|
|
2194
2381
|
|
|
2195
2382
|
mode_change, state = check_mode_switch(command, state)
|
|
2196
2383
|
if mode_change:
|
|
2384
|
+
print(colored(f"⚡ Switched to {state.current_mode} mode", "green"))
|
|
2197
2385
|
return state, 'Mode changed.'
|
|
2198
2386
|
|
|
2199
|
-
npc_name =
|
|
2387
|
+
npc_name = (
|
|
2388
|
+
state.npc.name
|
|
2389
|
+
if isinstance(state.npc, NPC)
|
|
2390
|
+
else "__none__"
|
|
2391
|
+
)
|
|
2200
2392
|
team_name = state.team.name if state.team else "__none__"
|
|
2201
2393
|
|
|
2202
|
-
|
|
2203
2394
|
original_command_for_embedding = command
|
|
2204
2395
|
commands = split_by_pipes(command)
|
|
2205
2396
|
|
|
2206
2397
|
stdin_for_next = None
|
|
2207
2398
|
final_output = None
|
|
2208
2399
|
current_state = state
|
|
2209
|
-
npc_model =
|
|
2210
|
-
|
|
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
|
+
)
|
|
2211
2410
|
active_model = npc_model or state.chat_model
|
|
2212
2411
|
active_provider = npc_provider or state.chat_provider
|
|
2412
|
+
|
|
2213
2413
|
if state.current_mode == 'agent':
|
|
2214
|
-
|
|
2215
|
-
|
|
2414
|
+
total_stages = len(commands)
|
|
2415
|
+
|
|
2216
2416
|
for i, cmd_segment in enumerate(commands):
|
|
2217
|
-
|
|
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
|
+
|
|
2218
2426
|
is_last_command = (i == len(commands) - 1)
|
|
2219
2427
|
stream_this_segment = state.stream_output and not is_last_command
|
|
2428
|
+
|
|
2220
2429
|
try:
|
|
2221
2430
|
current_state, output = process_pipeline_command(
|
|
2222
2431
|
cmd_segment.strip(),
|
|
@@ -2224,19 +2433,26 @@ def execute_command(
|
|
|
2224
2433
|
current_state,
|
|
2225
2434
|
stream_final=stream_this_segment,
|
|
2226
2435
|
review=review,
|
|
2227
|
-
router=
|
|
2436
|
+
router=router
|
|
2228
2437
|
)
|
|
2438
|
+
|
|
2229
2439
|
if is_last_command:
|
|
2440
|
+
print(colored("✅ Pipeline complete", "green"))
|
|
2230
2441
|
return current_state, output
|
|
2442
|
+
|
|
2231
2443
|
if isinstance(output, str):
|
|
2232
2444
|
stdin_for_next = output
|
|
2233
2445
|
elif not isinstance(output, str):
|
|
2234
2446
|
try:
|
|
2235
2447
|
if stream_this_segment:
|
|
2236
|
-
full_stream_output =
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
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
|
+
)
|
|
2240
2456
|
stdin_for_next = full_stream_output
|
|
2241
2457
|
if is_last_command:
|
|
2242
2458
|
final_output = full_stream_output
|
|
@@ -2245,24 +2461,40 @@ def execute_command(
|
|
|
2245
2461
|
try:
|
|
2246
2462
|
stdin_for_next = str(output)
|
|
2247
2463
|
except Exception:
|
|
2248
|
-
print(
|
|
2464
|
+
print(
|
|
2465
|
+
f"Warning: Cannot convert output to "
|
|
2466
|
+
f"string for piping: {type(output)}",
|
|
2467
|
+
file=sys.stderr
|
|
2468
|
+
)
|
|
2249
2469
|
stdin_for_next = None
|
|
2250
2470
|
else:
|
|
2251
2471
|
stdin_for_next = None
|
|
2472
|
+
|
|
2473
|
+
print(colored(
|
|
2474
|
+
f" → Passing to stage {stage_num + 1}",
|
|
2475
|
+
"blue"
|
|
2476
|
+
))
|
|
2477
|
+
|
|
2252
2478
|
except Exception as pipeline_error:
|
|
2253
2479
|
import traceback
|
|
2254
2480
|
traceback.print_exc()
|
|
2255
|
-
error_msg = colored(
|
|
2481
|
+
error_msg = colored(
|
|
2482
|
+
f"❌ Error in stage {stage_num} "
|
|
2483
|
+
f"('{cmd_segment[:50]}...'): {pipeline_error}",
|
|
2484
|
+
"red"
|
|
2485
|
+
)
|
|
2256
2486
|
return current_state, error_msg
|
|
2257
2487
|
|
|
2258
2488
|
if final_output is not None and isinstance(final_output,str):
|
|
2259
|
-
store_command_embeddings(
|
|
2489
|
+
store_command_embeddings(
|
|
2490
|
+
original_command_for_embedding,
|
|
2491
|
+
final_output,
|
|
2492
|
+
current_state
|
|
2493
|
+
)
|
|
2260
2494
|
|
|
2261
2495
|
return current_state, final_output
|
|
2262
2496
|
|
|
2263
|
-
|
|
2264
2497
|
elif state.current_mode == 'chat':
|
|
2265
|
-
|
|
2266
2498
|
cmd_parts = parse_command_safely(command)
|
|
2267
2499
|
is_probably_bash = (
|
|
2268
2500
|
cmd_parts
|
|
@@ -2273,6 +2505,7 @@ def execute_command(
|
|
|
2273
2505
|
or command.strip().startswith("/")
|
|
2274
2506
|
)
|
|
2275
2507
|
)
|
|
2508
|
+
|
|
2276
2509
|
if is_probably_bash:
|
|
2277
2510
|
try:
|
|
2278
2511
|
command_name = cmd_parts[0]
|
|
@@ -2282,36 +2515,55 @@ def execute_command(
|
|
|
2282
2515
|
return handle_cd_command(cmd_parts, state)
|
|
2283
2516
|
else:
|
|
2284
2517
|
try:
|
|
2285
|
-
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
|
+
)
|
|
2286
2524
|
return state, bash_output
|
|
2287
2525
|
except Exception as bash_err:
|
|
2288
|
-
return state, colored(
|
|
2526
|
+
return state, colored(
|
|
2527
|
+
f"Bash execution failed: {bash_err}",
|
|
2528
|
+
"red"
|
|
2529
|
+
)
|
|
2289
2530
|
except Exception:
|
|
2290
2531
|
pass
|
|
2291
2532
|
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
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
|
+
|
|
2301
2546
|
state.messages = response['messages']
|
|
2302
2547
|
return state, response['response']
|
|
2303
2548
|
|
|
2304
2549
|
elif state.current_mode == 'cmd':
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
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
|
+
|
|
2312
2563
|
state.messages = response['messages']
|
|
2313
2564
|
return state, response['response']
|
|
2314
2565
|
|
|
2566
|
+
|
|
2315
2567
|
def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
2316
2568
|
setup_npcsh_config()
|
|
2317
2569
|
|
|
@@ -2445,139 +2697,6 @@ def initialize_router_with_jinxs(team, router):
|
|
|
2445
2697
|
|
|
2446
2698
|
return router
|
|
2447
2699
|
|
|
2448
|
-
from npcpy.memory.memory_processor import memory_approval_ui
|
|
2449
|
-
from npcpy.ft.memory_trainer import MemoryTrainer
|
|
2450
|
-
from npcpy.llm_funcs import get_facts
|
|
2451
|
-
|
|
2452
|
-
def get_relevant_memories(
|
|
2453
|
-
command_history: CommandHistory,
|
|
2454
|
-
npc_name: str,
|
|
2455
|
-
team_name: str,
|
|
2456
|
-
path: str,
|
|
2457
|
-
query: Optional[str] = None,
|
|
2458
|
-
max_memories: int = 10,
|
|
2459
|
-
state: Optional[ShellState] = None
|
|
2460
|
-
) -> List[Dict]:
|
|
2461
|
-
|
|
2462
|
-
engine = command_history.engine
|
|
2463
|
-
|
|
2464
|
-
all_memories = command_history.get_memories_for_scope(
|
|
2465
|
-
npc=npc_name,
|
|
2466
|
-
team=team_name,
|
|
2467
|
-
directory_path=path,
|
|
2468
|
-
status='human-approved'
|
|
2469
|
-
)
|
|
2470
|
-
|
|
2471
|
-
if not all_memories:
|
|
2472
|
-
return []
|
|
2473
|
-
|
|
2474
|
-
if len(all_memories) <= max_memories and not query:
|
|
2475
|
-
return all_memories
|
|
2476
|
-
|
|
2477
|
-
if query:
|
|
2478
|
-
query_lower = query.lower()
|
|
2479
|
-
keyword_matches = [
|
|
2480
|
-
m for m in all_memories
|
|
2481
|
-
if query_lower in (m.get('final_memory') or m.get('initial_memory') or '').lower()
|
|
2482
|
-
]
|
|
2483
|
-
|
|
2484
|
-
if keyword_matches:
|
|
2485
|
-
return keyword_matches[:max_memories]
|
|
2486
|
-
|
|
2487
|
-
if state and state.embedding_model and state.embedding_provider:
|
|
2488
|
-
try:
|
|
2489
|
-
from npcpy.gen.embeddings import get_embeddings
|
|
2490
|
-
|
|
2491
|
-
search_text = query if query else "recent context"
|
|
2492
|
-
query_embedding = get_embeddings(
|
|
2493
|
-
[search_text],
|
|
2494
|
-
state.embedding_model,
|
|
2495
|
-
state.embedding_provider
|
|
2496
|
-
)[0]
|
|
2497
|
-
|
|
2498
|
-
memory_texts = [
|
|
2499
|
-
m.get('final_memory', '') for m in all_memories
|
|
2500
|
-
]
|
|
2501
|
-
memory_embeddings = get_embeddings(
|
|
2502
|
-
memory_texts,
|
|
2503
|
-
state.embedding_model,
|
|
2504
|
-
state.embedding_provider
|
|
2505
|
-
)
|
|
2506
|
-
|
|
2507
|
-
import numpy as np
|
|
2508
|
-
similarities = []
|
|
2509
|
-
for mem_emb in memory_embeddings:
|
|
2510
|
-
similarity = np.dot(query_embedding, mem_emb) / (
|
|
2511
|
-
np.linalg.norm(query_embedding) *
|
|
2512
|
-
np.linalg.norm(mem_emb)
|
|
2513
|
-
)
|
|
2514
|
-
similarities.append(similarity)
|
|
2515
|
-
|
|
2516
|
-
sorted_indices = np.argsort(similarities)[::-1]
|
|
2517
|
-
return [all_memories[i] for i in sorted_indices[:max_memories]]
|
|
2518
|
-
|
|
2519
|
-
except Exception as e:
|
|
2520
|
-
print(colored(
|
|
2521
|
-
f"RAG search failed, using recent: {e}",
|
|
2522
|
-
"yellow"
|
|
2523
|
-
))
|
|
2524
|
-
|
|
2525
|
-
return all_memories[-max_memories:]
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
def search_kg_facts(
|
|
2529
|
-
self,
|
|
2530
|
-
npc: str,
|
|
2531
|
-
team: str,
|
|
2532
|
-
directory_path: str,
|
|
2533
|
-
query: str
|
|
2534
|
-
) -> List[Dict]:
|
|
2535
|
-
|
|
2536
|
-
kg = load_kg_from_db(
|
|
2537
|
-
self.engine,
|
|
2538
|
-
team,
|
|
2539
|
-
npc,
|
|
2540
|
-
directory_path
|
|
2541
|
-
)
|
|
2542
|
-
|
|
2543
|
-
if not kg or 'facts' not in kg:
|
|
2544
|
-
return []
|
|
2545
|
-
|
|
2546
|
-
query_lower = query.lower()
|
|
2547
|
-
matching_facts = []
|
|
2548
|
-
|
|
2549
|
-
for fact in kg['facts']:
|
|
2550
|
-
statement = fact.get('statement', '').lower()
|
|
2551
|
-
if query_lower in statement:
|
|
2552
|
-
matching_facts.append(fact)
|
|
2553
|
-
|
|
2554
|
-
return matching_facts
|
|
2555
|
-
|
|
2556
|
-
def format_memory_context(memory_examples):
|
|
2557
|
-
if not memory_examples:
|
|
2558
|
-
return ""
|
|
2559
|
-
|
|
2560
|
-
context_parts = []
|
|
2561
|
-
|
|
2562
|
-
approved_examples = memory_examples.get("approved", [])
|
|
2563
|
-
rejected_examples = memory_examples.get("rejected", [])
|
|
2564
|
-
|
|
2565
|
-
if approved_examples:
|
|
2566
|
-
context_parts.append("EXAMPLES OF GOOD MEMORIES:")
|
|
2567
|
-
for ex in approved_examples[:5]:
|
|
2568
|
-
final = ex.get("final_memory") or ex.get("initial_memory")
|
|
2569
|
-
context_parts.append(f"- {final}")
|
|
2570
|
-
|
|
2571
|
-
if rejected_examples:
|
|
2572
|
-
context_parts.append("\nEXAMPLES OF POOR MEMORIES TO AVOID:")
|
|
2573
|
-
for ex in rejected_examples[:3]:
|
|
2574
|
-
context_parts.append(f"- {ex.get('initial_memory')}")
|
|
2575
|
-
|
|
2576
|
-
if context_parts:
|
|
2577
|
-
context_parts.append("\nLearn from these examples to generate similar high-quality memories.")
|
|
2578
|
-
return "\n".join(context_parts)
|
|
2579
|
-
|
|
2580
|
-
return ""
|
|
2581
2700
|
|
|
2582
2701
|
def process_memory_approvals(command_history, memory_queue):
|
|
2583
2702
|
pending_memories = memory_queue.get_approval_batch(max_items=5)
|