npcsh 1.1.16__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 +138 -100
- npcsh/alicanto.py +2 -2
- npcsh/benchmark/__init__.py +28 -0
- npcsh/benchmark/npcsh_agent.py +296 -0
- npcsh/benchmark/runner.py +611 -0
- 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 +146 -0
- npcsh/npc_team/jinxs/bin/nql.jinx +7 -7
- npcsh/npc_team/jinxs/bin/roll.jinx +20 -23
- npcsh/npc_team/jinxs/bin/sample.jinx +6 -7
- npcsh/npc_team/jinxs/bin/sync.jinx +6 -6
- npcsh/npc_team/jinxs/bin/vixynt.jinx +8 -8
- npcsh/npc_team/jinxs/incognide/add_tab.jinx +11 -0
- npcsh/npc_team/jinxs/incognide/close_pane.jinx +9 -0
- npcsh/npc_team/jinxs/incognide/close_tab.jinx +10 -0
- npcsh/npc_team/jinxs/incognide/confirm.jinx +10 -0
- npcsh/npc_team/jinxs/incognide/focus_pane.jinx +9 -0
- npcsh/npc_team/jinxs/{npc_studio/npc-studio.jinx → incognide/incognide.jinx} +2 -2
- npcsh/npc_team/jinxs/incognide/list_panes.jinx +8 -0
- npcsh/npc_team/jinxs/incognide/navigate.jinx +10 -0
- npcsh/npc_team/jinxs/incognide/notify.jinx +10 -0
- npcsh/npc_team/jinxs/incognide/open_pane.jinx +13 -0
- npcsh/npc_team/jinxs/incognide/read_pane.jinx +9 -0
- npcsh/npc_team/jinxs/incognide/run_terminal.jinx +10 -0
- npcsh/npc_team/jinxs/incognide/send_message.jinx +10 -0
- npcsh/npc_team/jinxs/incognide/split_pane.jinx +12 -0
- npcsh/npc_team/jinxs/incognide/switch_npc.jinx +10 -0
- npcsh/npc_team/jinxs/incognide/switch_tab.jinx +10 -0
- npcsh/npc_team/jinxs/incognide/write_file.jinx +11 -0
- npcsh/npc_team/jinxs/incognide/zen_mode.jinx +9 -0
- npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +4 -4
- npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +1 -1
- npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +2 -2
- npcsh/npc_team/jinxs/lib/computer_use/click.jinx +2 -2
- npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +1 -1
- npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +1 -1
- npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +1 -1
- npcsh/npc_team/jinxs/lib/computer_use/trigger.jinx +2 -2
- npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +1 -1
- npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/chat.jinx +4 -4
- npcsh/npc_team/jinxs/lib/core/cmd.jinx +4 -4
- npcsh/npc_team/jinxs/lib/core/compress.jinx +8 -8
- npcsh/npc_team/jinxs/lib/core/edit_file.jinx +3 -0
- npcsh/npc_team/jinxs/lib/core/ots.jinx +7 -7
- npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +348 -0
- npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +339 -0
- npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +418 -0
- npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +73 -0
- npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +388 -0
- npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +283 -0
- npcsh/npc_team/jinxs/lib/core/search.jinx +52 -129
- npcsh/npc_team/jinxs/lib/core/sh.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/sleep.jinx +29 -18
- npcsh/npc_team/jinxs/lib/core/sql.jinx +15 -11
- npcsh/npc_team/jinxs/lib/orchestration/convene.jinx +7 -7
- npcsh/npc_team/jinxs/lib/orchestration/delegate.jinx +8 -9
- npcsh/npc_team/jinxs/lib/research/paper_search.jinx +389 -78
- npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +373 -56
- npcsh/npc_team/jinxs/lib/utils/build.jinx +5 -5
- npcsh/npc_team/jinxs/lib/utils/compile.jinx +2 -2
- npcsh/npc_team/jinxs/lib/utils/help.jinx +1 -1
- npcsh/npc_team/jinxs/lib/utils/init.jinx +5 -5
- npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +300 -145
- npcsh/npc_team/jinxs/lib/utils/serve.jinx +2 -2
- npcsh/npc_team/jinxs/lib/utils/set.jinx +2 -2
- npcsh/npc_team/jinxs/lib/utils/switch.jinx +3 -3
- npcsh/npc_team/jinxs/lib/utils/switches.jinx +1 -1
- npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +2 -2
- 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-1.1.16.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/modes}/yap.jinx +8 -2
- npcsh/npc_team/sibiji.npc +1 -1
- npcsh/npcsh.py +87 -46
- 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/add_tab.jinx +11 -0
- 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.18.data/data/npcsh/npc_team/benchmark.jinx +146 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_action.jinx +4 -4
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_screenshot.jinx +1 -1
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/build.jinx +5 -5
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/chat.jinx +4 -4
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/click.jinx +2 -2
- npcsh-1.1.18.data/data/npcsh/npc_team/close_pane.jinx +9 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/close_tab.jinx +10 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/cmd.jinx +4 -4
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compile.jinx +2 -2
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compress.jinx +8 -8
- npcsh-1.1.18.data/data/npcsh/npc_team/confirm.jinx +10 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/convene.jinx +7 -7
- 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.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/delegate.jinx +8 -9
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/edit_file.jinx +3 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/file_search.jinx +339 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/focus_pane.jinx +9 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/guac.jinx +544 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/help.jinx +1 -1
- npcsh-1.1.16.data/data/npcsh/npc_team/npc-studio.jinx → npcsh-1.1.18.data/data/npcsh/npc_team/incognide.jinx +2 -2
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/init.jinx +5 -5
- npcsh-1.1.18.data/data/npcsh/npc_team/jinxs.jinx +331 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/key_press.jinx +1 -1
- npcsh-1.1.18.data/data/npcsh/npc_team/kg_search.jinx +418 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/launch_app.jinx +1 -1
- npcsh-1.1.18.data/data/npcsh/npc_team/list_panes.jinx +8 -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/navigate.jinx +10 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/notify.jinx +10 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/nql.jinx +7 -7
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/open_browser.jinx +2 -2
- npcsh-1.1.18.data/data/npcsh/npc_team/open_pane.jinx +13 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/ots.jinx +7 -7
- 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/read_pane.jinx +9 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/reattach.jinx +291 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/roll.jinx +20 -23
- npcsh-1.1.18.data/data/npcsh/npc_team/run_terminal.jinx +10 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sample.jinx +6 -7
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/screenshot.jinx +1 -1
- npcsh-1.1.18.data/data/npcsh/npc_team/search.jinx +54 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/semantic_scholar.jinx +386 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/send_message.jinx +10 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/serve.jinx +2 -2
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/set.jinx +2 -2
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sh.jinx +1 -1
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.npc +1 -1
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sleep.jinx +29 -18
- npcsh-1.1.18.data/data/npcsh/npc_team/split_pane.jinx +12 -0
- 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.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch.jinx +3 -3
- npcsh-1.1.18.data/data/npcsh/npc_team/switch_npc.jinx +10 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/switch_tab.jinx +10 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switches.jinx +1 -1
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sync.jinx +6 -6
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/teamviz.jinx +2 -2
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/trigger.jinx +2 -2
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/type_text.jinx +1 -1
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/vixynt.jinx +8 -8
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/wait.jinx +1 -1
- 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.18.data/data/npcsh/npc_team/write_file.jinx +11 -0
- {npcsh/npc_team/jinxs/bin → npcsh-1.1.18.data/data/npcsh/npc_team}/yap.jinx +8 -2
- npcsh-1.1.18.data/data/npcsh/npc_team/zen_mode.jinx +9 -0
- {npcsh-1.1.16.dist-info → npcsh-1.1.18.dist-info}/METADATA +99 -7
- npcsh-1.1.18.dist-info/RECORD +235 -0
- {npcsh-1.1.16.dist-info → npcsh-1.1.18.dist-info}/WHEEL +1 -1
- {npcsh-1.1.16.dist-info → npcsh-1.1.18.dist-info}/entry_points.txt +2 -3
- npcsh/npc_team/jinxs/bin/spool.jinx +0 -161
- npcsh/npc_team/jinxs/bin/wander.jinx +0 -152
- npcsh/npc_team/jinxs/lib/research/arxiv.jinx +0 -76
- npcsh-1.1.16.data/data/npcsh/npc_team/arxiv.jinx +0 -76
- npcsh-1.1.16.data/data/npcsh/npc_team/jinxs.jinx +0 -176
- npcsh-1.1.16.data/data/npcsh/npc_team/paper_search.jinx +0 -101
- npcsh-1.1.16.data/data/npcsh/npc_team/search.jinx +0 -131
- npcsh-1.1.16.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -69
- npcsh-1.1.16.data/data/npcsh/npc_team/spool.jinx +0 -161
- npcsh-1.1.16.data/data/npcsh/npc_team/sql.jinx +0 -16
- npcsh-1.1.16.data/data/npcsh/npc_team/wander.jinx +0 -152
- npcsh-1.1.16.dist-info/RECORD +0 -170
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.npc +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/paste.jinx +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/shh.jinx +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/verbose.jinx +0 -0
- {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.16.dist-info → npcsh-1.1.18.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.16.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()
|
|
@@ -351,8 +393,7 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
351
393
|
None
|
|
352
394
|
"""
|
|
353
395
|
|
|
354
|
-
|
|
355
|
-
return
|
|
396
|
+
already_initialized = is_npcsh_initialized()
|
|
356
397
|
|
|
357
398
|
conn = sqlite3.connect(db_path)
|
|
358
399
|
cursor = conn.cursor()
|
|
@@ -425,10 +466,10 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
425
466
|
old_package_jinxs = set()
|
|
426
467
|
if os.path.exists(manifest_path):
|
|
427
468
|
try:
|
|
428
|
-
|
|
469
|
+
|
|
429
470
|
with open(manifest_path, 'r') as f:
|
|
430
471
|
old_package_jinxs = set(json.load(f).get('jinxs', []))
|
|
431
|
-
except:
|
|
472
|
+
except Exception:
|
|
432
473
|
pass
|
|
433
474
|
|
|
434
475
|
# Track current package jinxs
|
|
@@ -481,7 +522,7 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
481
522
|
|
|
482
523
|
# Save updated manifest
|
|
483
524
|
try:
|
|
484
|
-
|
|
525
|
+
|
|
485
526
|
with open(manifest_path, 'w') as f:
|
|
486
527
|
json.dump({'jinxs': list(current_package_jinxs), 'updated': str(__import__('datetime').datetime.now())}, f, indent=2)
|
|
487
528
|
except Exception as e:
|
|
@@ -508,8 +549,10 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
508
549
|
print(f"Copied template {file} to {destination_template_path}")
|
|
509
550
|
conn.commit()
|
|
510
551
|
conn.close()
|
|
511
|
-
|
|
512
|
-
|
|
552
|
+
|
|
553
|
+
if not already_initialized:
|
|
554
|
+
set_npcsh_initialized()
|
|
555
|
+
add_npcshrc_to_shell_config()
|
|
513
556
|
|
|
514
557
|
|
|
515
558
|
def get_shell_config_file() -> str:
|
|
@@ -560,9 +603,6 @@ def get_relevant_memories(
|
|
|
560
603
|
max_memories: int = 10,
|
|
561
604
|
state: Optional[ShellState] = None
|
|
562
605
|
) -> List[Dict]:
|
|
563
|
-
|
|
564
|
-
engine = command_history.engine
|
|
565
|
-
|
|
566
606
|
all_memories = command_history.get_memories_for_scope(
|
|
567
607
|
npc=npc_name,
|
|
568
608
|
team=team_name,
|
|
@@ -587,7 +627,7 @@ def get_relevant_memories(
|
|
|
587
627
|
|
|
588
628
|
if state and state.embedding_model and state.embedding_provider:
|
|
589
629
|
try:
|
|
590
|
-
|
|
630
|
+
|
|
591
631
|
|
|
592
632
|
search_text = query if query else "recent context"
|
|
593
633
|
query_embedding = get_embeddings(
|
|
@@ -605,7 +645,7 @@ def get_relevant_memories(
|
|
|
605
645
|
state.embedding_provider
|
|
606
646
|
)
|
|
607
647
|
|
|
608
|
-
|
|
648
|
+
|
|
609
649
|
similarities = []
|
|
610
650
|
for mem_emb in memory_embeddings:
|
|
611
651
|
similarity = np.dot(query_embedding, mem_emb) / (
|
|
@@ -815,7 +855,6 @@ BASH_COMMANDS = [
|
|
|
815
855
|
"command",
|
|
816
856
|
"compgen",
|
|
817
857
|
"complete",
|
|
818
|
-
"continue",
|
|
819
858
|
"declare",
|
|
820
859
|
"dirs",
|
|
821
860
|
"disown",
|
|
@@ -1282,7 +1321,7 @@ def get_setting_windows(key, default=None):
|
|
|
1282
1321
|
|
|
1283
1322
|
|
|
1284
1323
|
def setup_readline() -> str:
|
|
1285
|
-
|
|
1324
|
+
|
|
1286
1325
|
if readline is None:
|
|
1287
1326
|
return None
|
|
1288
1327
|
try:
|
|
@@ -1428,7 +1467,7 @@ def make_completer(shell_state: ShellState, router: Any):
|
|
|
1428
1467
|
else:
|
|
1429
1468
|
return None # readline expects None when no more completions
|
|
1430
1469
|
|
|
1431
|
-
except Exception
|
|
1470
|
+
except Exception:
|
|
1432
1471
|
# Using completion_logger for internal debugging, not printing to stdout for user.
|
|
1433
1472
|
# completion_logger.error(f"Exception in completion: {e}", exc_info=True)
|
|
1434
1473
|
return None
|
|
@@ -1588,7 +1627,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1588
1627
|
try:
|
|
1589
1628
|
import termios
|
|
1590
1629
|
import tty
|
|
1591
|
-
|
|
1630
|
+
|
|
1592
1631
|
except ImportError:
|
|
1593
1632
|
return input(prompt)
|
|
1594
1633
|
|
|
@@ -1624,7 +1663,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1624
1663
|
try:
|
|
1625
1664
|
import shutil
|
|
1626
1665
|
term_width = shutil.get_terminal_size().columns
|
|
1627
|
-
except:
|
|
1666
|
+
except json.JSONDecodeError:
|
|
1628
1667
|
term_width = 80
|
|
1629
1668
|
|
|
1630
1669
|
def draw():
|
|
@@ -1637,7 +1676,6 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1637
1676
|
sys.stdout.write('\r')
|
|
1638
1677
|
# Move up for each wrapped line we're on
|
|
1639
1678
|
cursor_total = prompt_visible_len + pos
|
|
1640
|
-
cursor_line = cursor_total // term_width
|
|
1641
1679
|
# Go up to the first line of input
|
|
1642
1680
|
for _ in range(num_lines - 1):
|
|
1643
1681
|
sys.stdout.write('\033[A')
|
|
@@ -1720,7 +1758,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1720
1758
|
# Check if this looks like binary/image data
|
|
1721
1759
|
# Image signatures: PNG (\x89PNG), JPEG (\xff\xd8\xff), GIF (GIF8), BMP (BM)
|
|
1722
1760
|
# Also check for high ratio of non-printable chars
|
|
1723
|
-
|
|
1761
|
+
|
|
1724
1762
|
if len(paste_buffer) > 4:
|
|
1725
1763
|
# Check for common image magic bytes
|
|
1726
1764
|
if paste_buffer[:4] == '\x89PNG' or paste_buffer[:8] == '\x89PNG\r\n\x1a\n':
|
|
@@ -1741,8 +1779,8 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1741
1779
|
|
|
1742
1780
|
if is_binary:
|
|
1743
1781
|
# Save image data to temp file
|
|
1744
|
-
|
|
1745
|
-
|
|
1782
|
+
|
|
1783
|
+
|
|
1746
1784
|
try:
|
|
1747
1785
|
# Determine extension from magic bytes
|
|
1748
1786
|
ext = '.bin'
|
|
@@ -1765,14 +1803,14 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1765
1803
|
with os.fdopen(fd, 'wb') as f:
|
|
1766
1804
|
if paste_buffer.startswith('data:image/'):
|
|
1767
1805
|
# Decode base64 data URL
|
|
1768
|
-
|
|
1806
|
+
|
|
1769
1807
|
_, data = paste_buffer.split(',', 1)
|
|
1770
1808
|
f.write(base64.b64decode(data))
|
|
1771
1809
|
else:
|
|
1772
1810
|
f.write(paste_buffer.encode('latin-1'))
|
|
1773
1811
|
pasted_content = temp_path # Store path to image
|
|
1774
1812
|
placeholder = f"[pasted image: {temp_path}]"
|
|
1775
|
-
except:
|
|
1813
|
+
except Exception:
|
|
1776
1814
|
pasted_content = None
|
|
1777
1815
|
placeholder = "[pasted image: failed to save]"
|
|
1778
1816
|
else:
|
|
@@ -1970,7 +2008,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
|
|
|
1970
2008
|
sys.stdout.flush()
|
|
1971
2009
|
else:
|
|
1972
2010
|
pass # No tool call to show
|
|
1973
|
-
except:
|
|
2011
|
+
except Exception:
|
|
1974
2012
|
pass
|
|
1975
2013
|
|
|
1976
2014
|
elif c and ord(c) >= 32: # Printable
|
|
@@ -1999,7 +2037,7 @@ def _get_slash_hints(state, router, prefix='/') -> str:
|
|
|
1999
2037
|
try:
|
|
2000
2038
|
import shutil
|
|
2001
2039
|
term_width = shutil.get_terminal_size().columns
|
|
2002
|
-
except:
|
|
2040
|
+
except Exception:
|
|
2003
2041
|
term_width = 80
|
|
2004
2042
|
|
|
2005
2043
|
# Build hint string that fits in terminal
|
|
@@ -2148,44 +2186,20 @@ def wrap_text(text: str, width: int = 80) -> str:
|
|
|
2148
2186
|
|
|
2149
2187
|
|
|
2150
2188
|
|
|
2151
|
-
def setup_readline() -> str:
|
|
2152
|
-
"""Setup readline with history and completion"""
|
|
2153
|
-
try:
|
|
2154
|
-
readline.read_history_file(READLINE_HISTORY_FILE)
|
|
2155
|
-
readline.set_history_length(1000)
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
readline.parse_and_bind("tab: complete")
|
|
2159
|
-
|
|
2160
|
-
readline.parse_and_bind("set enable-bracketed-paste on")
|
|
2161
|
-
readline.parse_and_bind(r'"\C-r": reverse-search-history')
|
|
2162
|
-
readline.parse_and_bind(r'"\C-e": end-of-line')
|
|
2163
|
-
readline.parse_and_bind(r'"\C-a": beginning-of-line')
|
|
2164
|
-
|
|
2165
|
-
return READLINE_HISTORY_FILE
|
|
2166
|
-
|
|
2167
|
-
except FileNotFoundError:
|
|
2168
|
-
pass
|
|
2169
|
-
except OSError as e:
|
|
2170
|
-
print(f"Warning: Could not read readline history file {READLINE_HISTORY_FILE}: {e}")
|
|
2171
|
-
|
|
2172
2189
|
|
|
2173
|
-
def save_readline_history():
|
|
2174
|
-
try:
|
|
2175
|
-
readline.write_history_file(READLINE_HISTORY_FILE)
|
|
2176
|
-
except OSError as e:
|
|
2177
|
-
print(f"Warning: Could not write readline history file {READLINE_HISTORY_FILE}: {e}")
|
|
2178
2190
|
|
|
2179
2191
|
def store_command_embeddings(command: str, output: Any, state: ShellState):
|
|
2180
2192
|
if not chroma_client or not state.embedding_model or not state.embedding_provider:
|
|
2181
|
-
if not chroma_client:
|
|
2193
|
+
if not chroma_client:
|
|
2194
|
+
print("Warning: ChromaDB client not available for embeddings.", file=sys.stderr)
|
|
2182
2195
|
return
|
|
2183
2196
|
if not command and not output:
|
|
2184
2197
|
return
|
|
2185
2198
|
|
|
2186
2199
|
try:
|
|
2187
2200
|
output_str = str(output) if output else ""
|
|
2188
|
-
if not command and not output_str:
|
|
2201
|
+
if not command and not output_str:
|
|
2202
|
+
return
|
|
2189
2203
|
|
|
2190
2204
|
texts_to_embed = [command, output_str]
|
|
2191
2205
|
|
|
@@ -2357,10 +2371,7 @@ def _ollama_supports_tools(model: str) -> Optional[bool]:
|
|
|
2357
2371
|
Best-effort check for tool-call support on an Ollama model by inspecting its template/metadata.
|
|
2358
2372
|
Mirrors the lightweight check used in the Flask serve path.
|
|
2359
2373
|
"""
|
|
2360
|
-
|
|
2361
|
-
import ollama # Local import to avoid hard dependency when Ollama isn't installed
|
|
2362
|
-
except Exception:
|
|
2363
|
-
return None
|
|
2374
|
+
|
|
2364
2375
|
|
|
2365
2376
|
try:
|
|
2366
2377
|
details = ollama.show(model)
|
|
@@ -2467,7 +2478,7 @@ def wrap_tool_with_display(tool_name: str, tool_func: Callable, state: ShellStat
|
|
|
2467
2478
|
print(colored(f" ⚡ {tool_name}", "cyan") + colored(f" {args_display}", "white", attrs=["dark"]), end="", flush=True)
|
|
2468
2479
|
else:
|
|
2469
2480
|
print(colored(f" ⚡ {tool_name}", "cyan"), end="", flush=True)
|
|
2470
|
-
except:
|
|
2481
|
+
except Exception:
|
|
2471
2482
|
pass
|
|
2472
2483
|
|
|
2473
2484
|
# Execute tool
|
|
@@ -2483,14 +2494,14 @@ def wrap_tool_with_display(tool_name: str, tool_func: Callable, state: ShellStat
|
|
|
2483
2494
|
result_preview = result_preview[:200] + "..."
|
|
2484
2495
|
if result_preview and result_preview not in ('None', '', '{}', '[]'):
|
|
2485
2496
|
print(colored(f" → {result_preview}", "white", attrs=["dark"]), flush=True)
|
|
2486
|
-
except:
|
|
2497
|
+
except Exception:
|
|
2487
2498
|
pass
|
|
2488
2499
|
return result
|
|
2489
2500
|
except Exception as e:
|
|
2490
2501
|
if log_level != "silent":
|
|
2491
2502
|
try:
|
|
2492
2503
|
print(colored(f" ✗ {str(e)[:100]}", "red"), flush=True)
|
|
2493
|
-
except:
|
|
2504
|
+
except Exception as e:
|
|
2494
2505
|
pass
|
|
2495
2506
|
raise
|
|
2496
2507
|
return wrapped
|
|
@@ -2540,8 +2551,19 @@ def collect_llm_tools(state: ShellState) -> Tuple[List[Dict[str, Any]], Dict[str
|
|
|
2540
2551
|
if not jinja_env_for_jinx and state.team and isinstance(state.team, Team):
|
|
2541
2552
|
jinja_env_for_jinx = getattr(state.team, "jinja_env", None)
|
|
2542
2553
|
|
|
2554
|
+
jinx_globals = {
|
|
2555
|
+
"state": state,
|
|
2556
|
+
"CommandHistory": CommandHistory,
|
|
2557
|
+
"load_kg_from_db": load_kg_from_db,
|
|
2558
|
+
"execute_rag_command": execute_rag_command,
|
|
2559
|
+
"execute_brainblast_command": execute_brainblast_command,
|
|
2560
|
+
"load_file_contents": load_file_contents,
|
|
2561
|
+
"search_web": search_web,
|
|
2562
|
+
"get_relevant_memories": get_relevant_memories,
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2543
2565
|
for name, jinx_obj in aggregated_jinxs.items():
|
|
2544
|
-
def _make_runner(jinx=jinx_obj, jinja_env=jinja_env_for_jinx, tool_name=name):
|
|
2566
|
+
def _make_runner(jinx=jinx_obj, jinja_env=jinja_env_for_jinx, tool_name=name, extras=jinx_globals):
|
|
2545
2567
|
def runner(**kwargs):
|
|
2546
2568
|
input_values = kwargs if isinstance(kwargs, dict) else {}
|
|
2547
2569
|
try:
|
|
@@ -2549,7 +2571,7 @@ def collect_llm_tools(state: ShellState) -> Tuple[List[Dict[str, Any]], Dict[str
|
|
|
2549
2571
|
input_values=input_values,
|
|
2550
2572
|
npc=npc_obj,
|
|
2551
2573
|
messages=state.messages,
|
|
2552
|
-
extra_globals=
|
|
2574
|
+
extra_globals=extras,
|
|
2553
2575
|
jinja_env=jinja_env
|
|
2554
2576
|
)
|
|
2555
2577
|
return ctx.get("output", ctx)
|
|
@@ -2630,10 +2652,10 @@ def should_skip_kg_processing(user_input: str, assistant_output: str) -> bool:
|
|
|
2630
2652
|
|
|
2631
2653
|
return False
|
|
2632
2654
|
|
|
2633
|
-
def execute_slash_command(command: str,
|
|
2634
|
-
stdin_input: Optional[str],
|
|
2635
|
-
state: ShellState,
|
|
2636
|
-
stream: bool,
|
|
2655
|
+
def execute_slash_command(command: str,
|
|
2656
|
+
stdin_input: Optional[str],
|
|
2657
|
+
state: ShellState,
|
|
2658
|
+
stream: bool,
|
|
2637
2659
|
router) -> Tuple[ShellState, Any]:
|
|
2638
2660
|
"""Executes slash commands using the router."""
|
|
2639
2661
|
try:
|
|
@@ -2641,7 +2663,13 @@ def execute_slash_command(command: str,
|
|
|
2641
2663
|
except ValueError:
|
|
2642
2664
|
all_command_parts = command.split()
|
|
2643
2665
|
command_name = all_command_parts[0].lstrip('/')
|
|
2666
|
+
|
|
2667
|
+
# --- QUIT/EXIT HANDLING ---
|
|
2668
|
+
if command_name in ['quit', 'exit', 'q']:
|
|
2644
2669
|
|
|
2670
|
+
print("Goodbye!")
|
|
2671
|
+
sys.exit(0)
|
|
2672
|
+
|
|
2645
2673
|
# --- NPC SWITCHING LOGIC ---
|
|
2646
2674
|
if command_name in ['n', 'npc']:
|
|
2647
2675
|
npc_to_switch_to = all_command_parts[1] if len(all_command_parts) > 1 else None
|
|
@@ -2902,9 +2930,6 @@ def process_pipeline_command(
|
|
|
2902
2930
|
"tools": tools_for_llm,
|
|
2903
2931
|
"tool_map": tool_exec_map,
|
|
2904
2932
|
}
|
|
2905
|
-
# Only add tool_choice for providers that support it (not gemini)
|
|
2906
|
-
is_gemini = (exec_provider and "gemini" in exec_provider.lower()) or \
|
|
2907
|
-
(exec_model and "gemini" in exec_model.lower())
|
|
2908
2933
|
llm_kwargs["tool_choice"] = 'auto'
|
|
2909
2934
|
|
|
2910
2935
|
# Agent loop: keep calling LLM until it stops making tool calls
|
|
@@ -2945,14 +2970,17 @@ def process_pipeline_command(
|
|
|
2945
2970
|
tool_name = msg.get("name", "tool")
|
|
2946
2971
|
tool_content = msg.get("content", "")
|
|
2947
2972
|
if tool_content and tool_content.strip():
|
|
2973
|
+
# Decode escaped newlines if present
|
|
2974
|
+
if isinstance(tool_content, str):
|
|
2975
|
+
tool_content = tool_content.replace('\\n', '\n').replace('\\t', '\t')
|
|
2948
2976
|
print(colored(f"\n⚡ {tool_name}:", "cyan"))
|
|
2949
2977
|
lines = tool_content.split('\n')
|
|
2950
2978
|
if len(lines) > 50:
|
|
2951
|
-
|
|
2979
|
+
render_markdown('\n'.join(lines[:25]))
|
|
2952
2980
|
print(colored(f"\n... ({len(lines) - 50} lines hidden) ...\n", "white", attrs=["dark"]))
|
|
2953
|
-
|
|
2981
|
+
render_markdown('\n'.join(lines[-25:]))
|
|
2954
2982
|
else:
|
|
2955
|
-
|
|
2983
|
+
render_markdown(tool_content)
|
|
2956
2984
|
|
|
2957
2985
|
# Check if LLM made tool calls - if not, it's done
|
|
2958
2986
|
tool_calls_made = isinstance(llm_result, dict) and llm_result.get("tool_calls")
|
|
@@ -3090,7 +3118,7 @@ def _delegate_to_npc(state: ShellState, npc_name: str, command: str, delegation_
|
|
|
3090
3118
|
MAX_DELEGATION_DEPTH = 1 # Only allow one level of delegation
|
|
3091
3119
|
|
|
3092
3120
|
if delegation_depth > MAX_DELEGATION_DEPTH:
|
|
3093
|
-
return state, {'output':
|
|
3121
|
+
return state, {'output': "⚠ Maximum delegation depth reached."}
|
|
3094
3122
|
|
|
3095
3123
|
if not state.team or not hasattr(state.team, 'npcs') or npc_name not in state.team.npcs:
|
|
3096
3124
|
return state, {'output': f"⚠ NPC '{npc_name}' not found in team"}
|
|
@@ -3296,7 +3324,7 @@ def execute_command(
|
|
|
3296
3324
|
)
|
|
3297
3325
|
)
|
|
3298
3326
|
stdin_for_next = full_stream_output
|
|
3299
|
-
except:
|
|
3327
|
+
except Exception:
|
|
3300
3328
|
if output is not None:
|
|
3301
3329
|
try:
|
|
3302
3330
|
stdin_for_next = str(output)
|
|
@@ -3398,7 +3426,7 @@ def execute_command(
|
|
|
3398
3426
|
def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
3399
3427
|
setup_npcsh_config()
|
|
3400
3428
|
|
|
3401
|
-
db_path =
|
|
3429
|
+
db_path = NPCSH_DB_PATH
|
|
3402
3430
|
db_path = os.path.expanduser(db_path)
|
|
3403
3431
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
3404
3432
|
command_history = CommandHistory(db_path)
|
|
@@ -3409,11 +3437,11 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
3409
3437
|
print("NPCSH initialization complete. Restart or source ~/.npcshrc.")
|
|
3410
3438
|
|
|
3411
3439
|
try:
|
|
3412
|
-
|
|
3440
|
+
setup_readline()
|
|
3413
3441
|
atexit.register(save_readline_history)
|
|
3414
3442
|
atexit.register(command_history.close)
|
|
3415
|
-
except:
|
|
3416
|
-
|
|
3443
|
+
except OSError as e:
|
|
3444
|
+
print(f"Warning: Failed to setup readline history: {e}", file=sys.stderr)
|
|
3417
3445
|
|
|
3418
3446
|
project_team_path = os.path.abspath(PROJECT_NPC_TEAM_PATH)
|
|
3419
3447
|
global_team_path = os.path.expanduser(DEFAULT_NPC_TEAM_PATH)
|
|
@@ -3422,7 +3450,7 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
3422
3450
|
default_forenpc_name = None
|
|
3423
3451
|
global_team_path = os.path.expanduser(DEFAULT_NPC_TEAM_PATH)
|
|
3424
3452
|
if not os.path.exists(global_team_path):
|
|
3425
|
-
print(
|
|
3453
|
+
print("Global NPC team directory doesn't exist. Initializing...")
|
|
3426
3454
|
initialize_base_npcs_if_needed(db_path)
|
|
3427
3455
|
if os.path.exists(project_team_path):
|
|
3428
3456
|
team_dir = project_team_path
|
|
@@ -3642,6 +3670,10 @@ def process_result(
|
|
|
3642
3670
|
final_output_str = None
|
|
3643
3671
|
|
|
3644
3672
|
# FIX: Handle dict output properly
|
|
3673
|
+
msg_input_tokens = None
|
|
3674
|
+
msg_output_tokens = None
|
|
3675
|
+
msg_cost = None
|
|
3676
|
+
|
|
3645
3677
|
if isinstance(output, dict):
|
|
3646
3678
|
# Use None-safe check to not skip empty strings
|
|
3647
3679
|
output_content = output.get('output') if 'output' in output else output.get('response')
|
|
@@ -3651,15 +3683,18 @@ def process_result(
|
|
|
3651
3683
|
# Accumulate token usage if available
|
|
3652
3684
|
if 'usage' in output:
|
|
3653
3685
|
usage = output['usage']
|
|
3654
|
-
|
|
3655
|
-
|
|
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
|
|
3656
3690
|
# Calculate cost
|
|
3657
3691
|
from npcpy.gen.response import calculate_cost
|
|
3658
|
-
|
|
3692
|
+
msg_cost = calculate_cost(
|
|
3659
3693
|
model_for_stream,
|
|
3660
|
-
|
|
3661
|
-
|
|
3694
|
+
msg_input_tokens,
|
|
3695
|
+
msg_output_tokens
|
|
3662
3696
|
)
|
|
3697
|
+
result_state.session_cost_usd += msg_cost
|
|
3663
3698
|
|
|
3664
3699
|
# If output_content is still a dict, convert to string
|
|
3665
3700
|
if isinstance(output_content, dict):
|
|
@@ -3721,6 +3756,9 @@ def process_result(
|
|
|
3721
3756
|
provider=active_npc.provider,
|
|
3722
3757
|
npc=npc_name,
|
|
3723
3758
|
team=team_name,
|
|
3759
|
+
input_tokens=msg_input_tokens,
|
|
3760
|
+
output_tokens=msg_output_tokens,
|
|
3761
|
+
cost=msg_cost,
|
|
3724
3762
|
)
|
|
3725
3763
|
|
|
3726
3764
|
result_state.turn_count += 1
|
|
@@ -3829,15 +3867,15 @@ def process_result(
|
|
|
3829
3867
|
result_state.current_path
|
|
3830
3868
|
)
|
|
3831
3869
|
evolved_npc_kg, _ = kg_evolve_incremental(
|
|
3832
|
-
existing_kg=npc_kg,
|
|
3870
|
+
existing_kg=npc_kg,
|
|
3833
3871
|
new_facts=approved_facts,
|
|
3834
|
-
model=active_npc.model,
|
|
3835
|
-
provider=active_npc.provider,
|
|
3872
|
+
model=active_npc.model,
|
|
3873
|
+
provider=active_npc.provider,
|
|
3836
3874
|
npc=active_npc,
|
|
3837
3875
|
get_concepts=True,
|
|
3838
|
-
link_concepts_facts=
|
|
3839
|
-
link_concepts_concepts=
|
|
3840
|
-
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,
|
|
3841
3879
|
)
|
|
3842
3880
|
save_kg_to_db(
|
|
3843
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.")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
npcsh benchmark integration for Terminal-Bench.
|
|
3
|
+
|
|
4
|
+
This module provides integration with Terminal-Bench (tbench.ai) for benchmarking
|
|
5
|
+
npcsh against standardized terminal/CLI agent evaluation tasks.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
# Install terminal-bench
|
|
9
|
+
pip install terminal-bench harbor
|
|
10
|
+
|
|
11
|
+
# Run benchmarks with npcsh
|
|
12
|
+
harbor run -d terminal-bench@2.0 --agent-import-path npcsh.benchmark:NpcshAgent -m anthropic/claude-sonnet-4-20250514
|
|
13
|
+
|
|
14
|
+
# Or use the convenience function
|
|
15
|
+
from npcsh.benchmark import run_benchmark
|
|
16
|
+
run_benchmark(model="claude-sonnet-4-20250514", provider="anthropic")
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .runner import run_benchmark, BenchmarkRunner
|
|
20
|
+
|
|
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
|