npcsh 1.1.17__py3-none-any.whl → 1.1.18__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 +114 -91
- npcsh/alicanto.py +2 -2
- npcsh/benchmark/__init__.py +8 -2
- npcsh/benchmark/npcsh_agent.py +46 -12
- npcsh/benchmark/runner.py +85 -43
- npcsh/benchmark/templates/install-npcsh.sh.j2 +35 -0
- npcsh/build.py +2 -4
- npcsh/completion.py +2 -6
- npcsh/config.py +1 -3
- npcsh/conversation_viewer.py +389 -0
- npcsh/corca.py +0 -1
- npcsh/execution.py +0 -1
- npcsh/guac.py +0 -1
- npcsh/mcp_helpers.py +2 -3
- npcsh/mcp_server.py +5 -10
- npcsh/npc.py +10 -11
- npcsh/npc_team/jinxs/bin/benchmark.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +321 -17
- npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +312 -67
- npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +366 -44
- npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +73 -0
- npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +328 -20
- npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +242 -10
- npcsh/npc_team/jinxs/lib/core/sleep.jinx +22 -11
- npcsh/npc_team/jinxs/lib/core/sql.jinx +10 -6
- npcsh/npc_team/jinxs/lib/research/paper_search.jinx +387 -76
- npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +372 -55
- npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +299 -144
- npcsh/npc_team/jinxs/modes/alicanto.jinx +356 -0
- npcsh/npc_team/jinxs/modes/arxiv.jinx +720 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +430 -0
- npcsh/npc_team/jinxs/modes/guac.jinx +544 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +379 -0
- npcsh/npc_team/jinxs/modes/pti.jinx +357 -0
- npcsh/npc_team/jinxs/modes/reattach.jinx +291 -0
- npcsh/npc_team/jinxs/modes/spool.jinx +350 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +455 -0
- npcsh/npc_team/jinxs/{bin → modes}/yap.jinx +13 -7
- npcsh/npcsh.py +7 -4
- npcsh/plonk.py +0 -1
- npcsh/pti.py +0 -1
- npcsh/routes.py +1 -3
- npcsh/spool.py +0 -1
- npcsh/ui.py +0 -1
- npcsh/wander.py +0 -1
- npcsh/yap.py +0 -1
- npcsh-1.1.18.data/data/npcsh/npc_team/alicanto.jinx +356 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/arxiv.jinx +720 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/benchmark.jinx +1 -1
- npcsh-1.1.18.data/data/npcsh/npc_team/corca.jinx +430 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/db_search.jinx +348 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/file_search.jinx +339 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/guac.jinx +544 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/jinxs.jinx +331 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/kg_search.jinx +418 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/mem_review.jinx +73 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/mem_search.jinx +388 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/paper_search.jinx +412 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/plonk.jinx +379 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/pti.jinx +357 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/reattach.jinx +291 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/semantic_scholar.jinx +386 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sleep.jinx +22 -11
- npcsh-1.1.18.data/data/npcsh/npc_team/spool.jinx +350 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/sql.jinx +20 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/wander.jinx +455 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/web_search.jinx +283 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/yap.jinx +13 -7
- {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/METADATA +90 -1
- npcsh-1.1.18.dist-info/RECORD +235 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/WHEEL +1 -1
- {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/entry_points.txt +0 -3
- npcsh/npc_team/jinxs/bin/spool.jinx +0 -161
- npcsh/npc_team/jinxs/bin/wander.jinx +0 -242
- npcsh/npc_team/jinxs/lib/research/arxiv.jinx +0 -76
- npcsh-1.1.17.data/data/npcsh/npc_team/arxiv.jinx +0 -76
- npcsh-1.1.17.data/data/npcsh/npc_team/db_search.jinx +0 -44
- npcsh-1.1.17.data/data/npcsh/npc_team/file_search.jinx +0 -94
- npcsh-1.1.17.data/data/npcsh/npc_team/jinxs.jinx +0 -176
- npcsh-1.1.17.data/data/npcsh/npc_team/kg_search.jinx +0 -96
- npcsh-1.1.17.data/data/npcsh/npc_team/mem_search.jinx +0 -80
- npcsh-1.1.17.data/data/npcsh/npc_team/paper_search.jinx +0 -101
- npcsh-1.1.17.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -69
- npcsh-1.1.17.data/data/npcsh/npc_team/spool.jinx +0 -161
- npcsh-1.1.17.data/data/npcsh/npc_team/sql.jinx +0 -16
- npcsh-1.1.17.data/data/npcsh/npc_team/wander.jinx +0 -242
- npcsh-1.1.17.data/data/npcsh/npc_team/web_search.jinx +0 -51
- npcsh-1.1.17.dist-info/RECORD +0 -219
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/click.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/confirm.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/convene.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/delegate.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/incognide.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/key_press.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/navigate.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/notify.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/nql.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/paste.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/search.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/send_message.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sh.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/shh.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switches.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sync.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/type_text.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/verbose.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/wait.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/write_file.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/top_level.txt +0 -0
npcsh/_state.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# Standard library imports
|
|
2
2
|
import atexit
|
|
3
|
+
import base64
|
|
4
|
+
import os
|
|
3
5
|
from dataclasses import dataclass, field
|
|
4
6
|
from datetime import datetime
|
|
5
7
|
import filecmp
|
|
6
8
|
import inspect
|
|
9
|
+
|
|
7
10
|
import logging
|
|
8
|
-
|
|
11
|
+
|
|
9
12
|
from pathlib import Path
|
|
10
13
|
import platform
|
|
11
14
|
import re
|
|
@@ -16,8 +19,11 @@ import signal
|
|
|
16
19
|
import sqlite3
|
|
17
20
|
import subprocess
|
|
18
21
|
import sys
|
|
22
|
+
import tempfile
|
|
19
23
|
import time
|
|
20
24
|
import textwrap
|
|
25
|
+
import readline
|
|
26
|
+
import json
|
|
21
27
|
from typing import Dict, List, Any, Tuple, Union, Optional, Callable
|
|
22
28
|
import yaml
|
|
23
29
|
|
|
@@ -40,9 +46,9 @@ try:
|
|
|
40
46
|
import pty
|
|
41
47
|
import tty
|
|
42
48
|
import termios
|
|
43
|
-
|
|
49
|
+
|
|
44
50
|
except ImportError:
|
|
45
|
-
|
|
51
|
+
|
|
46
52
|
pty = None
|
|
47
53
|
tty = None
|
|
48
54
|
termios = None
|
|
@@ -53,15 +59,21 @@ try:
|
|
|
53
59
|
except ImportError:
|
|
54
60
|
chromadb = None
|
|
55
61
|
|
|
62
|
+
try:
|
|
63
|
+
import ollama
|
|
64
|
+
except ImportError:
|
|
65
|
+
ollama = None
|
|
66
|
+
|
|
56
67
|
# Third-party imports
|
|
57
|
-
from colorama import
|
|
68
|
+
from colorama import Style
|
|
58
69
|
from litellm import RateLimitError
|
|
70
|
+
import numpy as np
|
|
59
71
|
from termcolor import colored
|
|
60
72
|
|
|
61
73
|
# npcpy imports
|
|
62
74
|
from npcpy.data.load import load_file_contents
|
|
63
75
|
from npcpy.data.web import search_web
|
|
64
|
-
|
|
76
|
+
|
|
65
77
|
from npcpy.llm_funcs import (
|
|
66
78
|
check_llm_command,
|
|
67
79
|
get_llm_response,
|
|
@@ -74,25 +86,25 @@ from npcpy.memory.command_history import (
|
|
|
74
86
|
save_conversation_message,
|
|
75
87
|
load_kg_from_db,
|
|
76
88
|
save_kg_to_db,
|
|
89
|
+
format_memory_context,
|
|
77
90
|
)
|
|
78
91
|
from npcpy.memory.knowledge_graph import kg_evolve_incremental
|
|
79
92
|
from npcpy.memory.search import execute_rag_command, execute_brainblast_command
|
|
80
|
-
from npcpy.npc_compiler import NPC, Team,
|
|
93
|
+
from npcpy.npc_compiler import NPC, Team, build_jinx_tool_catalog
|
|
81
94
|
from npcpy.npc_sysenv import (
|
|
82
95
|
print_and_process_stream_with_markdown,
|
|
83
96
|
render_markdown,
|
|
84
97
|
get_model_and_provider,
|
|
85
98
|
get_locally_available_models,
|
|
86
|
-
|
|
99
|
+
|
|
87
100
|
)
|
|
88
101
|
from npcpy.tools import auto_tools
|
|
102
|
+
from npcpy.gen.embeddings import get_embeddings
|
|
89
103
|
|
|
90
104
|
# Local module imports
|
|
91
105
|
from .config import (
|
|
92
|
-
VERSION,
|
|
93
106
|
DEFAULT_NPC_TEAM_PATH,
|
|
94
107
|
PROJECT_NPC_TEAM_PATH,
|
|
95
|
-
HISTORY_DB_DEFAULT_PATH,
|
|
96
108
|
READLINE_HISTORY_FILE,
|
|
97
109
|
NPCSH_CHAT_MODEL,
|
|
98
110
|
NPCSH_CHAT_PROVIDER,
|
|
@@ -156,6 +168,9 @@ class ShellState:
|
|
|
156
168
|
video_gen_provider: str = NPCSH_VIDEO_GEN_PROVIDER
|
|
157
169
|
current_mode: str = NPCSH_DEFAULT_MODE
|
|
158
170
|
build_kg: bool = NPCSH_BUILD_KG
|
|
171
|
+
kg_link_facts: bool = False # Link facts to concepts (requires LLM calls)
|
|
172
|
+
kg_link_concepts: bool = False # Link concepts to concepts (requires LLM calls)
|
|
173
|
+
kg_link_facts_facts: bool = False # Link facts to facts (requires LLM calls)
|
|
159
174
|
api_key: Optional[str] = None
|
|
160
175
|
api_url: Optional[str] = NPCSH_API_URL
|
|
161
176
|
current_path: str = field(default_factory=os.getcwd)
|
|
@@ -303,6 +318,33 @@ def set_npcsh_config_value(key: str, value: str):
|
|
|
303
318
|
}
|
|
304
319
|
if env_key in field_map:
|
|
305
320
|
setattr(ShellState, field_map[env_key], parsed_val)
|
|
321
|
+
|
|
322
|
+
# Persist to ~/.npcshrc
|
|
323
|
+
npcshrc_path = os.path.expanduser("~/.npcshrc")
|
|
324
|
+
try:
|
|
325
|
+
existing_lines = []
|
|
326
|
+
if os.path.exists(npcshrc_path):
|
|
327
|
+
with open(npcshrc_path, 'r') as f:
|
|
328
|
+
existing_lines = f.readlines()
|
|
329
|
+
|
|
330
|
+
# Update or add the export line
|
|
331
|
+
export_line = f"export {env_key}=\"{value}\"\n"
|
|
332
|
+
found = False
|
|
333
|
+
for i, line in enumerate(existing_lines):
|
|
334
|
+
if line.strip().startswith(f"export {env_key}="):
|
|
335
|
+
existing_lines[i] = export_line
|
|
336
|
+
found = True
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
if not found:
|
|
340
|
+
existing_lines.append(export_line)
|
|
341
|
+
|
|
342
|
+
with open(npcshrc_path, 'w') as f:
|
|
343
|
+
f.writelines(existing_lines)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
print(f"Warning: Could not persist config to {npcshrc_path}: {e}")
|
|
346
|
+
|
|
347
|
+
|
|
306
348
|
def get_npc_path(npc_name: str, db_path: str) -> str:
|
|
307
349
|
project_npc_team_dir = os.path.abspath("./npc_team")
|
|
308
350
|
project_npc_path = os.path.join(project_npc_team_dir, f"{npc_name}.npc")
|
|
@@ -317,7 +359,7 @@ def get_npc_path(npc_name: str, db_path: str) -> str:
|
|
|
317
359
|
if result:
|
|
318
360
|
return result[0]
|
|
319
361
|
|
|
320
|
-
except Exception
|
|
362
|
+
except Exception:
|
|
321
363
|
try:
|
|
322
364
|
with sqlite3.connect(db_path) as conn:
|
|
323
365
|
cursor = conn.cursor()
|
|
@@ -424,10 +466,10 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
424
466
|
old_package_jinxs = set()
|
|
425
467
|
if os.path.exists(manifest_path):
|
|
426
468
|
try:
|
|
427
|
-
|
|
469
|
+
|
|
428
470
|
with open(manifest_path, 'r') as f:
|
|
429
471
|
old_package_jinxs = set(json.load(f).get('jinxs', []))
|
|
430
|
-
except:
|
|
472
|
+
except Exception:
|
|
431
473
|
pass
|
|
432
474
|
|
|
433
475
|
# Track current package jinxs
|
|
@@ -480,7 +522,7 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
480
522
|
|
|
481
523
|
# Save updated manifest
|
|
482
524
|
try:
|
|
483
|
-
|
|
525
|
+
|
|
484
526
|
with open(manifest_path, 'w') as f:
|
|
485
527
|
json.dump({'jinxs': list(current_package_jinxs), 'updated': str(__import__('datetime').datetime.now())}, f, indent=2)
|
|
486
528
|
except Exception as e:
|
|
@@ -561,9 +603,6 @@ def get_relevant_memories(
|
|
|
561
603
|
max_memories: int = 10,
|
|
562
604
|
state: Optional[ShellState] = None
|
|
563
605
|
) -> List[Dict]:
|
|
564
|
-
|
|
565
|
-
engine = command_history.engine
|
|
566
|
-
|
|
567
606
|
all_memories = command_history.get_memories_for_scope(
|
|
568
607
|
npc=npc_name,
|
|
569
608
|
team=team_name,
|
|
@@ -588,7 +627,7 @@ def get_relevant_memories(
|
|
|
588
627
|
|
|
589
628
|
if state and state.embedding_model and state.embedding_provider:
|
|
590
629
|
try:
|
|
591
|
-
|
|
630
|
+
|
|
592
631
|
|
|
593
632
|
search_text = query if query else "recent context"
|
|
594
633
|
query_embedding = get_embeddings(
|
|
@@ -606,7 +645,7 @@ def get_relevant_memories(
|
|
|
606
645
|
state.embedding_provider
|
|
607
646
|
)
|
|
608
647
|
|
|
609
|
-
|
|
648
|
+
|
|
610
649
|
similarities = []
|
|
611
650
|
for mem_emb in memory_embeddings:
|
|
612
651
|
similarity = np.dot(query_embedding, mem_emb) / (
|
|
@@ -816,7 +855,6 @@ BASH_COMMANDS = [
|
|
|
816
855
|
"command",
|
|
817
856
|
"compgen",
|
|
818
857
|
"complete",
|
|
819
|
-
"continue",
|
|
820
858
|
"declare",
|
|
821
859
|
"dirs",
|
|
822
860
|
"disown",
|
|
@@ -1283,7 +1321,7 @@ def get_setting_windows(key, default=None):
|
|
|
1283
1321
|
|
|
1284
1322
|
|
|
1285
1323
|
def setup_readline() -> str:
|
|
1286
|
-
|
|
1324
|
+
|
|
1287
1325
|
if readline is None:
|
|
1288
1326
|
return None
|
|
1289
1327
|
try:
|
|
@@ -1429,7 +1467,7 @@ def make_completer(shell_state: ShellState, router: Any):
|
|
|
1429
1467
|
else:
|
|
1430
1468
|
return None # readline expects None when no more completions
|
|
1431
1469
|
|
|
1432
|
-
except Exception
|
|
1470
|
+
except Exception:
|
|
1433
1471
|
# Using completion_logger for internal debugging, not printing to stdout for user.
|
|
1434
1472
|
# completion_logger.error(f"Exception in completion: {e}", exc_info=True)
|
|
1435
1473
|
return None
|
|
@@ -1589,7 +1627,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1589
1627
|
try:
|
|
1590
1628
|
import termios
|
|
1591
1629
|
import tty
|
|
1592
|
-
|
|
1630
|
+
|
|
1593
1631
|
except ImportError:
|
|
1594
1632
|
return input(prompt)
|
|
1595
1633
|
|
|
@@ -1625,7 +1663,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1625
1663
|
try:
|
|
1626
1664
|
import shutil
|
|
1627
1665
|
term_width = shutil.get_terminal_size().columns
|
|
1628
|
-
except:
|
|
1666
|
+
except json.JSONDecodeError:
|
|
1629
1667
|
term_width = 80
|
|
1630
1668
|
|
|
1631
1669
|
def draw():
|
|
@@ -1638,7 +1676,6 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1638
1676
|
sys.stdout.write('\r')
|
|
1639
1677
|
# Move up for each wrapped line we're on
|
|
1640
1678
|
cursor_total = prompt_visible_len + pos
|
|
1641
|
-
cursor_line = cursor_total // term_width
|
|
1642
1679
|
# Go up to the first line of input
|
|
1643
1680
|
for _ in range(num_lines - 1):
|
|
1644
1681
|
sys.stdout.write('\033[A')
|
|
@@ -1721,7 +1758,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1721
1758
|
# Check if this looks like binary/image data
|
|
1722
1759
|
# Image signatures: PNG (\x89PNG), JPEG (\xff\xd8\xff), GIF (GIF8), BMP (BM)
|
|
1723
1760
|
# Also check for high ratio of non-printable chars
|
|
1724
|
-
|
|
1761
|
+
|
|
1725
1762
|
if len(paste_buffer) > 4:
|
|
1726
1763
|
# Check for common image magic bytes
|
|
1727
1764
|
if paste_buffer[:4] == '\x89PNG' or paste_buffer[:8] == '\x89PNG\r\n\x1a\n':
|
|
@@ -1742,8 +1779,8 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1742
1779
|
|
|
1743
1780
|
if is_binary:
|
|
1744
1781
|
# Save image data to temp file
|
|
1745
|
-
|
|
1746
|
-
|
|
1782
|
+
|
|
1783
|
+
|
|
1747
1784
|
try:
|
|
1748
1785
|
# Determine extension from magic bytes
|
|
1749
1786
|
ext = '.bin'
|
|
@@ -1766,14 +1803,14 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1766
1803
|
with os.fdopen(fd, 'wb') as f:
|
|
1767
1804
|
if paste_buffer.startswith('data:image/'):
|
|
1768
1805
|
# Decode base64 data URL
|
|
1769
|
-
|
|
1806
|
+
|
|
1770
1807
|
_, data = paste_buffer.split(',', 1)
|
|
1771
1808
|
f.write(base64.b64decode(data))
|
|
1772
1809
|
else:
|
|
1773
1810
|
f.write(paste_buffer.encode('latin-1'))
|
|
1774
1811
|
pasted_content = temp_path # Store path to image
|
|
1775
1812
|
placeholder = f"[pasted image: {temp_path}]"
|
|
1776
|
-
except:
|
|
1813
|
+
except Exception:
|
|
1777
1814
|
pasted_content = None
|
|
1778
1815
|
placeholder = "[pasted image: failed to save]"
|
|
1779
1816
|
else:
|
|
@@ -1971,7 +2008,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1971
2008
|
sys.stdout.flush()
|
|
1972
2009
|
else:
|
|
1973
2010
|
pass # No tool call to show
|
|
1974
|
-
except:
|
|
2011
|
+
except Exception:
|
|
1975
2012
|
pass
|
|
1976
2013
|
|
|
1977
2014
|
elif c and ord(c) >= 32: # Printable
|
|
@@ -2000,7 +2037,7 @@ def _get_slash_hints(state, router, prefix='/') -> str:
|
|
|
2000
2037
|
try:
|
|
2001
2038
|
import shutil
|
|
2002
2039
|
term_width = shutil.get_terminal_size().columns
|
|
2003
|
-
except:
|
|
2040
|
+
except Exception:
|
|
2004
2041
|
term_width = 80
|
|
2005
2042
|
|
|
2006
2043
|
# Build hint string that fits in terminal
|
|
@@ -2149,44 +2186,20 @@ def wrap_text(text: str, width: int = 80) -> str:
|
|
|
2149
2186
|
|
|
2150
2187
|
|
|
2151
2188
|
|
|
2152
|
-
def setup_readline() -> str:
|
|
2153
|
-
"""Setup readline with history and completion"""
|
|
2154
|
-
try:
|
|
2155
|
-
readline.read_history_file(READLINE_HISTORY_FILE)
|
|
2156
|
-
readline.set_history_length(1000)
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
readline.parse_and_bind("tab: complete")
|
|
2160
|
-
|
|
2161
|
-
readline.parse_and_bind("set enable-bracketed-paste on")
|
|
2162
|
-
readline.parse_and_bind(r'"\C-r": reverse-search-history')
|
|
2163
|
-
readline.parse_and_bind(r'"\C-e": end-of-line')
|
|
2164
|
-
readline.parse_and_bind(r'"\C-a": beginning-of-line')
|
|
2165
|
-
|
|
2166
|
-
return READLINE_HISTORY_FILE
|
|
2167
|
-
|
|
2168
|
-
except FileNotFoundError:
|
|
2169
|
-
pass
|
|
2170
|
-
except OSError as e:
|
|
2171
|
-
print(f"Warning: Could not read readline history file {READLINE_HISTORY_FILE}: {e}")
|
|
2172
2189
|
|
|
2173
|
-
|
|
2174
|
-
def save_readline_history():
|
|
2175
|
-
try:
|
|
2176
|
-
readline.write_history_file(READLINE_HISTORY_FILE)
|
|
2177
|
-
except OSError as e:
|
|
2178
|
-
print(f"Warning: Could not write readline history file {READLINE_HISTORY_FILE}: {e}")
|
|
2179
2190
|
|
|
2180
2191
|
def store_command_embeddings(command: str, output: Any, state: ShellState):
|
|
2181
2192
|
if not chroma_client or not state.embedding_model or not state.embedding_provider:
|
|
2182
|
-
if not chroma_client:
|
|
2193
|
+
if not chroma_client:
|
|
2194
|
+
print("Warning: ChromaDB client not available for embeddings.", file=sys.stderr)
|
|
2183
2195
|
return
|
|
2184
2196
|
if not command and not output:
|
|
2185
2197
|
return
|
|
2186
2198
|
|
|
2187
2199
|
try:
|
|
2188
2200
|
output_str = str(output) if output else ""
|
|
2189
|
-
if not command and not output_str:
|
|
2201
|
+
if not command and not output_str:
|
|
2202
|
+
return
|
|
2190
2203
|
|
|
2191
2204
|
texts_to_embed = [command, output_str]
|
|
2192
2205
|
|
|
@@ -2358,10 +2371,7 @@ def _ollama_supports_tools(model: str) -> Optional[bool]:
|
|
|
2358
2371
|
Best-effort check for tool-call support on an Ollama model by inspecting its template/metadata.
|
|
2359
2372
|
Mirrors the lightweight check used in the Flask serve path.
|
|
2360
2373
|
"""
|
|
2361
|
-
|
|
2362
|
-
import ollama # Local import to avoid hard dependency when Ollama isn't installed
|
|
2363
|
-
except Exception:
|
|
2364
|
-
return None
|
|
2374
|
+
|
|
2365
2375
|
|
|
2366
2376
|
try:
|
|
2367
2377
|
details = ollama.show(model)
|
|
@@ -2468,7 +2478,7 @@ def wrap_tool_with_display(tool_name: str, tool_func: Callable, state: ShellStat
|
|
|
2468
2478
|
print(colored(f" ⚡ {tool_name}", "cyan") + colored(f" {args_display}", "white", attrs=["dark"]), end="", flush=True)
|
|
2469
2479
|
else:
|
|
2470
2480
|
print(colored(f" ⚡ {tool_name}", "cyan"), end="", flush=True)
|
|
2471
|
-
except:
|
|
2481
|
+
except Exception:
|
|
2472
2482
|
pass
|
|
2473
2483
|
|
|
2474
2484
|
# Execute tool
|
|
@@ -2484,14 +2494,14 @@ def wrap_tool_with_display(tool_name: str, tool_func: Callable, state: ShellStat
|
|
|
2484
2494
|
result_preview = result_preview[:200] + "..."
|
|
2485
2495
|
if result_preview and result_preview not in ('None', '', '{}', '[]'):
|
|
2486
2496
|
print(colored(f" → {result_preview}", "white", attrs=["dark"]), flush=True)
|
|
2487
|
-
except:
|
|
2497
|
+
except Exception:
|
|
2488
2498
|
pass
|
|
2489
2499
|
return result
|
|
2490
2500
|
except Exception as e:
|
|
2491
2501
|
if log_level != "silent":
|
|
2492
2502
|
try:
|
|
2493
2503
|
print(colored(f" ✗ {str(e)[:100]}", "red"), flush=True)
|
|
2494
|
-
except:
|
|
2504
|
+
except Exception as e:
|
|
2495
2505
|
pass
|
|
2496
2506
|
raise
|
|
2497
2507
|
return wrapped
|
|
@@ -2642,10 +2652,10 @@ def should_skip_kg_processing(user_input: str, assistant_output: str) -> bool:
|
|
|
2642
2652
|
|
|
2643
2653
|
return False
|
|
2644
2654
|
|
|
2645
|
-
def execute_slash_command(command: str,
|
|
2646
|
-
stdin_input: Optional[str],
|
|
2647
|
-
state: ShellState,
|
|
2648
|
-
stream: bool,
|
|
2655
|
+
def execute_slash_command(command: str,
|
|
2656
|
+
stdin_input: Optional[str],
|
|
2657
|
+
state: ShellState,
|
|
2658
|
+
stream: bool,
|
|
2649
2659
|
router) -> Tuple[ShellState, Any]:
|
|
2650
2660
|
"""Executes slash commands using the router."""
|
|
2651
2661
|
try:
|
|
@@ -2653,7 +2663,13 @@ def execute_slash_command(command: str,
|
|
|
2653
2663
|
except ValueError:
|
|
2654
2664
|
all_command_parts = command.split()
|
|
2655
2665
|
command_name = all_command_parts[0].lstrip('/')
|
|
2666
|
+
|
|
2667
|
+
# --- QUIT/EXIT HANDLING ---
|
|
2668
|
+
if command_name in ['quit', 'exit', 'q']:
|
|
2656
2669
|
|
|
2670
|
+
print("Goodbye!")
|
|
2671
|
+
sys.exit(0)
|
|
2672
|
+
|
|
2657
2673
|
# --- NPC SWITCHING LOGIC ---
|
|
2658
2674
|
if command_name in ['n', 'npc']:
|
|
2659
2675
|
npc_to_switch_to = all_command_parts[1] if len(all_command_parts) > 1 else None
|
|
@@ -2914,9 +2930,6 @@ def process_pipeline_command(
|
|
|
2914
2930
|
"tools": tools_for_llm,
|
|
2915
2931
|
"tool_map": tool_exec_map,
|
|
2916
2932
|
}
|
|
2917
|
-
# Only add tool_choice for providers that support it (not gemini)
|
|
2918
|
-
is_gemini = (exec_provider and "gemini" in exec_provider.lower()) or \
|
|
2919
|
-
(exec_model and "gemini" in exec_model.lower())
|
|
2920
2933
|
llm_kwargs["tool_choice"] = 'auto'
|
|
2921
2934
|
|
|
2922
2935
|
# Agent loop: keep calling LLM until it stops making tool calls
|
|
@@ -3105,7 +3118,7 @@ def _delegate_to_npc(state: ShellState, npc_name: str, command: str, delegation_
|
|
|
3105
3118
|
MAX_DELEGATION_DEPTH = 1 # Only allow one level of delegation
|
|
3106
3119
|
|
|
3107
3120
|
if delegation_depth > MAX_DELEGATION_DEPTH:
|
|
3108
|
-
return state, {'output':
|
|
3121
|
+
return state, {'output': "⚠ Maximum delegation depth reached."}
|
|
3109
3122
|
|
|
3110
3123
|
if not state.team or not hasattr(state.team, 'npcs') or npc_name not in state.team.npcs:
|
|
3111
3124
|
return state, {'output': f"⚠ NPC '{npc_name}' not found in team"}
|
|
@@ -3311,7 +3324,7 @@ def execute_command(
|
|
|
3311
3324
|
)
|
|
3312
3325
|
)
|
|
3313
3326
|
stdin_for_next = full_stream_output
|
|
3314
|
-
except:
|
|
3327
|
+
except Exception:
|
|
3315
3328
|
if output is not None:
|
|
3316
3329
|
try:
|
|
3317
3330
|
stdin_for_next = str(output)
|
|
@@ -3413,7 +3426,7 @@ def execute_command(
|
|
|
3413
3426
|
def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
3414
3427
|
setup_npcsh_config()
|
|
3415
3428
|
|
|
3416
|
-
db_path =
|
|
3429
|
+
db_path = NPCSH_DB_PATH
|
|
3417
3430
|
db_path = os.path.expanduser(db_path)
|
|
3418
3431
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
3419
3432
|
command_history = CommandHistory(db_path)
|
|
@@ -3424,11 +3437,11 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
3424
3437
|
print("NPCSH initialization complete. Restart or source ~/.npcshrc.")
|
|
3425
3438
|
|
|
3426
3439
|
try:
|
|
3427
|
-
|
|
3440
|
+
setup_readline()
|
|
3428
3441
|
atexit.register(save_readline_history)
|
|
3429
3442
|
atexit.register(command_history.close)
|
|
3430
|
-
except:
|
|
3431
|
-
|
|
3443
|
+
except OSError as e:
|
|
3444
|
+
print(f"Warning: Failed to setup readline history: {e}", file=sys.stderr)
|
|
3432
3445
|
|
|
3433
3446
|
project_team_path = os.path.abspath(PROJECT_NPC_TEAM_PATH)
|
|
3434
3447
|
global_team_path = os.path.expanduser(DEFAULT_NPC_TEAM_PATH)
|
|
@@ -3437,7 +3450,7 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
3437
3450
|
default_forenpc_name = None
|
|
3438
3451
|
global_team_path = os.path.expanduser(DEFAULT_NPC_TEAM_PATH)
|
|
3439
3452
|
if not os.path.exists(global_team_path):
|
|
3440
|
-
print(
|
|
3453
|
+
print("Global NPC team directory doesn't exist. Initializing...")
|
|
3441
3454
|
initialize_base_npcs_if_needed(db_path)
|
|
3442
3455
|
if os.path.exists(project_team_path):
|
|
3443
3456
|
team_dir = project_team_path
|
|
@@ -3657,6 +3670,10 @@ def process_result(
|
|
|
3657
3670
|
final_output_str = None
|
|
3658
3671
|
|
|
3659
3672
|
# FIX: Handle dict output properly
|
|
3673
|
+
msg_input_tokens = None
|
|
3674
|
+
msg_output_tokens = None
|
|
3675
|
+
msg_cost = None
|
|
3676
|
+
|
|
3660
3677
|
if isinstance(output, dict):
|
|
3661
3678
|
# Use None-safe check to not skip empty strings
|
|
3662
3679
|
output_content = output.get('output') if 'output' in output else output.get('response')
|
|
@@ -3666,15 +3683,18 @@ def process_result(
|
|
|
3666
3683
|
# Accumulate token usage if available
|
|
3667
3684
|
if 'usage' in output:
|
|
3668
3685
|
usage = output['usage']
|
|
3669
|
-
|
|
3670
|
-
|
|
3686
|
+
msg_input_tokens = usage.get('input_tokens', 0)
|
|
3687
|
+
msg_output_tokens = usage.get('output_tokens', 0)
|
|
3688
|
+
result_state.session_input_tokens += msg_input_tokens
|
|
3689
|
+
result_state.session_output_tokens += msg_output_tokens
|
|
3671
3690
|
# Calculate cost
|
|
3672
3691
|
from npcpy.gen.response import calculate_cost
|
|
3673
|
-
|
|
3692
|
+
msg_cost = calculate_cost(
|
|
3674
3693
|
model_for_stream,
|
|
3675
|
-
|
|
3676
|
-
|
|
3694
|
+
msg_input_tokens,
|
|
3695
|
+
msg_output_tokens
|
|
3677
3696
|
)
|
|
3697
|
+
result_state.session_cost_usd += msg_cost
|
|
3678
3698
|
|
|
3679
3699
|
# If output_content is still a dict, convert to string
|
|
3680
3700
|
if isinstance(output_content, dict):
|
|
@@ -3736,6 +3756,9 @@ def process_result(
|
|
|
3736
3756
|
provider=active_npc.provider,
|
|
3737
3757
|
npc=npc_name,
|
|
3738
3758
|
team=team_name,
|
|
3759
|
+
input_tokens=msg_input_tokens,
|
|
3760
|
+
output_tokens=msg_output_tokens,
|
|
3761
|
+
cost=msg_cost,
|
|
3739
3762
|
)
|
|
3740
3763
|
|
|
3741
3764
|
result_state.turn_count += 1
|
|
@@ -3844,15 +3867,15 @@ def process_result(
|
|
|
3844
3867
|
result_state.current_path
|
|
3845
3868
|
)
|
|
3846
3869
|
evolved_npc_kg, _ = kg_evolve_incremental(
|
|
3847
|
-
existing_kg=npc_kg,
|
|
3870
|
+
existing_kg=npc_kg,
|
|
3848
3871
|
new_facts=approved_facts,
|
|
3849
|
-
model=active_npc.model,
|
|
3850
|
-
provider=active_npc.provider,
|
|
3872
|
+
model=active_npc.model,
|
|
3873
|
+
provider=active_npc.provider,
|
|
3851
3874
|
npc=active_npc,
|
|
3852
3875
|
get_concepts=True,
|
|
3853
|
-
link_concepts_facts=
|
|
3854
|
-
link_concepts_concepts=
|
|
3855
|
-
link_facts_facts=
|
|
3876
|
+
link_concepts_facts=result_state.kg_link_facts,
|
|
3877
|
+
link_concepts_concepts=result_state.kg_link_concepts,
|
|
3878
|
+
link_facts_facts=result_state.kg_link_facts_facts,
|
|
3856
3879
|
)
|
|
3857
3880
|
save_kg_to_db(
|
|
3858
3881
|
engine,
|
npcsh/alicanto.py
CHANGED
|
@@ -4,7 +4,7 @@ alicanto - Deep research mode CLI entry point
|
|
|
4
4
|
This is a thin wrapper that executes the alicanto.jinx through the jinx mechanism.
|
|
5
5
|
"""
|
|
6
6
|
import argparse
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
10
|
from npcsh._state import setup_shell
|
|
@@ -30,7 +30,7 @@ def main():
|
|
|
30
30
|
sys.exit(1)
|
|
31
31
|
|
|
32
32
|
# Setup shell to get team and default NPC
|
|
33
|
-
|
|
33
|
+
_, team, default_npc = setup_shell()
|
|
34
34
|
|
|
35
35
|
if not team or "alicanto" not in team.jinxs_dict:
|
|
36
36
|
print("Error: alicanto jinx not found. Ensure npc_team/jinxs/modes/alicanto.jinx exists.")
|
npcsh/benchmark/__init__.py
CHANGED
|
@@ -16,7 +16,13 @@ Usage:
|
|
|
16
16
|
run_benchmark(model="claude-sonnet-4-20250514", provider="anthropic")
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
from .npcsh_agent import NpcshAgent
|
|
20
19
|
from .runner import run_benchmark, BenchmarkRunner
|
|
21
20
|
|
|
22
|
-
__all__ = ["
|
|
21
|
+
__all__ = ["run_benchmark", "BenchmarkRunner"]
|
|
22
|
+
|
|
23
|
+
# NpcshAgent requires harbor to be installed - import lazily
|
|
24
|
+
try:
|
|
25
|
+
from .npcsh_agent import NpcshAgent
|
|
26
|
+
__all__.append("NpcshAgent")
|
|
27
|
+
except ImportError:
|
|
28
|
+
NpcshAgent = None # Harbor not installed
|
npcsh/benchmark/npcsh_agent.py
CHANGED
|
@@ -9,7 +9,6 @@ import json
|
|
|
9
9
|
import os
|
|
10
10
|
import shlex
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Optional
|
|
13
12
|
|
|
14
13
|
from harbor.agents.installed.base import BaseInstalledAgent, ExecInput
|
|
15
14
|
from harbor.models.agent.context import AgentContext
|
|
@@ -54,7 +53,16 @@ class NpcshAgent(BaseInstalledAgent):
|
|
|
54
53
|
Returns:
|
|
55
54
|
List of ExecInput commands to execute
|
|
56
55
|
"""
|
|
57
|
-
|
|
56
|
+
# Wrap the instruction with explicit jinx usage directions
|
|
57
|
+
tool_instruction = f"""You have access to jinxs including edit_file (for writing/creating files), sh (for running shell commands), and python (for running Python code).
|
|
58
|
+
|
|
59
|
+
IMPORTANT: You MUST use these jinxs to complete the task. Do NOT just output code as text - use the edit_file jinx to actually write files to disk.
|
|
60
|
+
|
|
61
|
+
Task: {instruction}
|
|
62
|
+
|
|
63
|
+
Remember: Use edit_file to write any code files. Use sh to run shell commands like gcc, make, etc."""
|
|
64
|
+
|
|
65
|
+
escaped_instruction = shlex.quote(tool_instruction)
|
|
58
66
|
model_name = self.model_name
|
|
59
67
|
|
|
60
68
|
if model_name and "/" in model_name:
|
|
@@ -105,24 +113,33 @@ class NpcshAgent(BaseInstalledAgent):
|
|
|
105
113
|
|
|
106
114
|
# Create output directory
|
|
107
115
|
commands.append(ExecInput(
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
command=f"mkdir -p {shlex.quote(output_dir)}",
|
|
117
|
+
timeout_sec=30
|
|
118
|
+
))
|
|
119
|
+
|
|
120
|
+
# Create .npcsh_global file to use global team and avoid interactive prompts
|
|
121
|
+
commands.append(ExecInput(
|
|
122
|
+
command="touch /app/.npcsh_global",
|
|
123
|
+
timeout_sec=10
|
|
110
124
|
))
|
|
111
125
|
|
|
112
126
|
# Run npcsh with the instruction
|
|
127
|
+
# Using corca NPC which has edit_file tool for writing files
|
|
113
128
|
# Using the npc CLI which supports single-command execution
|
|
129
|
+
# NPCSH_DEFAULT_MODE=agent enables automatic tool execution
|
|
114
130
|
npcsh_cmd = (
|
|
115
131
|
f'{env_prefix}'
|
|
116
132
|
f'NPCSH_CHAT_MODEL="{model}" '
|
|
117
133
|
f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
|
|
118
134
|
f'NPCSH_STREAM_OUTPUT=0 '
|
|
119
|
-
f'
|
|
135
|
+
f'NPCSH_DEFAULT_MODE=agent '
|
|
136
|
+
f'npc --npc corca {escaped_instruction} '
|
|
120
137
|
f'2>&1 | tee {shlex.quote(output_file)}'
|
|
121
138
|
)
|
|
122
139
|
|
|
123
140
|
commands.append(ExecInput(
|
|
124
|
-
|
|
125
|
-
|
|
141
|
+
command=npcsh_cmd,
|
|
142
|
+
timeout_sec=600, # 10 minute timeout for complex tasks
|
|
126
143
|
))
|
|
127
144
|
|
|
128
145
|
return commands
|
|
@@ -198,7 +215,16 @@ class NpcshAgentWithNpc(NpcshAgent):
|
|
|
198
215
|
|
|
199
216
|
def create_run_agent_commands(self, instruction: str) -> list:
|
|
200
217
|
"""Create commands using a specific NPC."""
|
|
201
|
-
|
|
218
|
+
# Wrap the instruction with explicit jinx usage directions
|
|
219
|
+
tool_instruction = f"""You have access to jinxs including edit_file (for writing/creating files), sh (for running shell commands), and python (for running Python code).
|
|
220
|
+
|
|
221
|
+
IMPORTANT: You MUST use these jinxs to complete the task. Do NOT just output code as text - use the edit_file jinx to actually write files to disk.
|
|
222
|
+
|
|
223
|
+
Task: {instruction}
|
|
224
|
+
|
|
225
|
+
Remember: Use edit_file to write any code files. Use sh to run shell commands like gcc, make, etc."""
|
|
226
|
+
|
|
227
|
+
escaped_instruction = shlex.quote(tool_instruction)
|
|
202
228
|
model_name = self.model_name
|
|
203
229
|
|
|
204
230
|
if model_name and "/" in model_name:
|
|
@@ -240,23 +266,31 @@ class NpcshAgentWithNpc(NpcshAgent):
|
|
|
240
266
|
commands = []
|
|
241
267
|
|
|
242
268
|
commands.append(ExecInput(
|
|
243
|
-
|
|
244
|
-
|
|
269
|
+
command=f"mkdir -p {shlex.quote(output_dir)}",
|
|
270
|
+
timeout_sec=30
|
|
271
|
+
))
|
|
272
|
+
|
|
273
|
+
# Create .npcsh_global file to use global team and avoid interactive prompts
|
|
274
|
+
commands.append(ExecInput(
|
|
275
|
+
command="touch /app/.npcsh_global",
|
|
276
|
+
timeout_sec=10
|
|
245
277
|
))
|
|
246
278
|
|
|
247
279
|
# Use specific NPC with --npc flag
|
|
280
|
+
# NPCSH_DEFAULT_MODE=agent enables automatic tool execution
|
|
248
281
|
npcsh_cmd = (
|
|
249
282
|
f'{env_prefix}'
|
|
250
283
|
f'NPCSH_CHAT_MODEL="{model}" '
|
|
251
284
|
f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
|
|
252
285
|
f'NPCSH_STREAM_OUTPUT=0 '
|
|
286
|
+
f'NPCSH_DEFAULT_MODE=agent '
|
|
253
287
|
f'npc --npc {self.npc_name} {escaped_instruction} '
|
|
254
288
|
f'2>&1 | tee {shlex.quote(output_file)}'
|
|
255
289
|
)
|
|
256
290
|
|
|
257
291
|
commands.append(ExecInput(
|
|
258
|
-
|
|
259
|
-
|
|
292
|
+
command=npcsh_cmd,
|
|
293
|
+
timeout_sec=600,
|
|
260
294
|
))
|
|
261
295
|
|
|
262
296
|
return commands
|