npcsh 1.1.14__py3-none-any.whl → 1.1.16__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 +533 -80
- npcsh/mcp_server.py +2 -1
- npcsh/npc.py +84 -32
- npcsh/npc_team/alicanto.npc +22 -1
- npcsh/npc_team/corca.npc +28 -9
- npcsh/npc_team/frederic.npc +25 -4
- npcsh/npc_team/guac.npc +22 -0
- npcsh/npc_team/jinxs/bin/nql.jinx +141 -0
- npcsh/npc_team/jinxs/bin/sync.jinx +230 -0
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/bin}/vixynt.jinx +8 -30
- npcsh/npc_team/jinxs/bin/wander.jinx +152 -0
- npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +220 -0
- npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +40 -0
- npcsh/npc_team/jinxs/lib/browser/close_browser.jinx +14 -0
- npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +43 -0
- npcsh/npc_team/jinxs/lib/computer_use/click.jinx +23 -0
- npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +26 -0
- npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +37 -0
- npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +23 -0
- npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +27 -0
- npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +21 -0
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/edit_file.jinx +3 -3
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/load_file.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/paste.jinx +134 -0
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/search.jinx +2 -1
- npcsh/npc_team/jinxs/{code → lib/core}/sh.jinx +2 -8
- npcsh/npc_team/jinxs/{code → lib/core}/sql.jinx +1 -1
- npcsh/npc_team/jinxs/lib/orchestration/convene.jinx +232 -0
- npcsh/npc_team/jinxs/lib/orchestration/delegate.jinx +184 -0
- npcsh/npc_team/jinxs/lib/research/arxiv.jinx +76 -0
- npcsh/npc_team/jinxs/lib/research/paper_search.jinx +101 -0
- npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +69 -0
- npcsh/npc_team/jinxs/{utils/core → lib/utils}/build.jinx +8 -8
- npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +176 -0
- npcsh/npc_team/jinxs/lib/utils/shh.jinx +17 -0
- npcsh/npc_team/jinxs/lib/utils/switch.jinx +62 -0
- npcsh/npc_team/jinxs/lib/utils/switches.jinx +61 -0
- npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +205 -0
- npcsh/npc_team/jinxs/lib/utils/verbose.jinx +17 -0
- npcsh/npc_team/kadiefa.npc +19 -1
- npcsh/npc_team/plonk.npc +26 -1
- npcsh/npc_team/plonkjr.npc +22 -1
- npcsh/npc_team/sibiji.npc +23 -2
- npcsh/npcsh.py +153 -39
- npcsh/ui.py +22 -1
- npcsh-1.1.16.data/data/npcsh/npc_team/alicanto.npc +23 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/arxiv.jinx +76 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/browser_action.jinx +220 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/browser_screenshot.jinx +40 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/build.jinx +8 -8
- npcsh-1.1.16.data/data/npcsh/npc_team/click.jinx +23 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/close_browser.jinx +14 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/convene.jinx +232 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/corca.npc +31 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/delegate.jinx +184 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.16.data/data/npcsh/npc_team}/edit_file.jinx +3 -3
- npcsh-1.1.16.data/data/npcsh/npc_team/frederic.npc +27 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/guac.npc +22 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/jinxs.jinx +176 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/kadiefa.npc +21 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/key_press.jinx +26 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/launch_app.jinx +37 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.16.data/data/npcsh/npc_team}/load_file.jinx +1 -1
- npcsh-1.1.16.data/data/npcsh/npc_team/nql.jinx +141 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/open_browser.jinx +43 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/paper_search.jinx +101 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/paste.jinx +134 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/plonk.npc +27 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/plonkjr.npc +23 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/screenshot.jinx +23 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.16.data/data/npcsh/npc_team}/search.jinx +2 -1
- npcsh-1.1.16.data/data/npcsh/npc_team/semantic_scholar.jinx +69 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/sh.jinx +2 -8
- npcsh-1.1.16.data/data/npcsh/npc_team/shh.jinx +17 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/sibiji.npc +24 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/sql.jinx +1 -1
- npcsh-1.1.16.data/data/npcsh/npc_team/switch.jinx +62 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/switches.jinx +61 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/sync.jinx +230 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/teamviz.jinx +205 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/type_text.jinx +27 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/verbose.jinx +17 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.16.data/data/npcsh/npc_team}/vixynt.jinx +8 -30
- npcsh-1.1.16.data/data/npcsh/npc_team/wait.jinx +21 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/wander.jinx +152 -0
- {npcsh-1.1.14.dist-info → npcsh-1.1.16.dist-info}/METADATA +399 -58
- npcsh-1.1.16.dist-info/RECORD +170 -0
- npcsh-1.1.16.dist-info/entry_points.txt +19 -0
- npcsh-1.1.16.dist-info/top_level.txt +2 -0
- project/__init__.py +1 -0
- npcsh/npc_team/foreman.npc +0 -7
- npcsh/npc_team/jinxs/modes/alicanto.jinx +0 -194
- npcsh/npc_team/jinxs/modes/corca.jinx +0 -249
- npcsh/npc_team/jinxs/modes/guac.jinx +0 -317
- npcsh/npc_team/jinxs/modes/plonk.jinx +0 -214
- npcsh/npc_team/jinxs/modes/pti.jinx +0 -170
- npcsh/npc_team/jinxs/modes/wander.jinx +0 -186
- npcsh/npc_team/jinxs/utils/agent.jinx +0 -17
- npcsh/npc_team/jinxs/utils/core/jinxs.jinx +0 -32
- npcsh-1.1.14.data/data/npcsh/npc_team/agent.jinx +0 -17
- npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.jinx +0 -194
- npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.npc +0 -2
- npcsh-1.1.14.data/data/npcsh/npc_team/corca.jinx +0 -249
- npcsh-1.1.14.data/data/npcsh/npc_team/corca.npc +0 -12
- npcsh-1.1.14.data/data/npcsh/npc_team/foreman.npc +0 -7
- npcsh-1.1.14.data/data/npcsh/npc_team/frederic.npc +0 -6
- npcsh-1.1.14.data/data/npcsh/npc_team/guac.jinx +0 -317
- npcsh-1.1.14.data/data/npcsh/npc_team/jinxs.jinx +0 -32
- npcsh-1.1.14.data/data/npcsh/npc_team/kadiefa.npc +0 -3
- npcsh-1.1.14.data/data/npcsh/npc_team/plonk.jinx +0 -214
- npcsh-1.1.14.data/data/npcsh/npc_team/plonk.npc +0 -2
- npcsh-1.1.14.data/data/npcsh/npc_team/plonkjr.npc +0 -2
- npcsh-1.1.14.data/data/npcsh/npc_team/pti.jinx +0 -170
- npcsh-1.1.14.data/data/npcsh/npc_team/sibiji.npc +0 -3
- npcsh-1.1.14.data/data/npcsh/npc_team/wander.jinx +0 -186
- npcsh-1.1.14.dist-info/RECORD +0 -135
- npcsh-1.1.14.dist-info/entry_points.txt +0 -9
- npcsh-1.1.14.dist-info/top_level.txt +0 -1
- /npcsh/npc_team/jinxs/{utils → bin}/roll.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → bin}/sample.jinx +0 -0
- /npcsh/npc_team/jinxs/{modes → bin}/spool.jinx +0 -0
- /npcsh/npc_team/jinxs/{modes → bin}/yap.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/computer_use}/trigger.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/chat.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/cmd.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/compress.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/ots.jinx +0 -0
- /npcsh/npc_team/jinxs/{code → lib/core}/python.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/sleep.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/compile.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/help.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/init.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/utils}/serve.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/set.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/utils}/usage.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/spool.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/yap.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.14.dist-info → npcsh-1.1.16.dist-info}/WHEEL +0 -0
- {npcsh-1.1.14.dist-info → npcsh-1.1.16.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
jinx_name: type_text
|
|
2
|
+
description: Type text using keyboard
|
|
3
|
+
inputs:
|
|
4
|
+
- text: "" # Text to type
|
|
5
|
+
|
|
6
|
+
steps:
|
|
7
|
+
- name: perform_type
|
|
8
|
+
engine: python
|
|
9
|
+
code: |
|
|
10
|
+
from npcpy.work.desktop import perform_action
|
|
11
|
+
|
|
12
|
+
text = context.get('text', '')
|
|
13
|
+
messages = context.get('messages', [])
|
|
14
|
+
|
|
15
|
+
if not text:
|
|
16
|
+
context['output'] = "Usage: /type_text <text to type>"
|
|
17
|
+
context['messages'] = messages
|
|
18
|
+
exit()
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
perform_action({'type': 'type', 'text': text})
|
|
22
|
+
preview = text[:30] + "..." if len(text) > 30 else text
|
|
23
|
+
context['output'] = f"Typed: {preview}"
|
|
24
|
+
except Exception as e:
|
|
25
|
+
context['output'] = f"Type failed: {e}"
|
|
26
|
+
|
|
27
|
+
context['messages'] = messages
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
jinx_name: wait
|
|
2
|
+
description: Wait/pause for a specified duration in seconds
|
|
3
|
+
inputs:
|
|
4
|
+
- duration: 1 # Duration to wait in seconds
|
|
5
|
+
|
|
6
|
+
steps:
|
|
7
|
+
- name: perform_wait
|
|
8
|
+
engine: python
|
|
9
|
+
code: |
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
duration = float(context.get('duration', 1))
|
|
13
|
+
messages = context.get('messages', [])
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
time.sleep(duration)
|
|
17
|
+
context['output'] = f"Waited {duration} seconds"
|
|
18
|
+
except Exception as e:
|
|
19
|
+
context['output'] = f"Wait failed: {e}"
|
|
20
|
+
|
|
21
|
+
context['messages'] = messages
|
|
@@ -13,9 +13,9 @@ steps:
|
|
|
13
13
|
from npcpy.llm_funcs import get_llm_response
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
file_path = os.path.expanduser(
|
|
17
|
-
edit_instructions =
|
|
18
|
-
backup_str =
|
|
16
|
+
file_path = os.path.expanduser({{ file_path | tojson }})
|
|
17
|
+
edit_instructions = {{ edit_instructions | string | tojson }}
|
|
18
|
+
backup_str = {{ backup | default("true") | string | tojson }}
|
|
19
19
|
create_backup = backup_str.lower() not in ('false', 'no', '0', '')
|
|
20
20
|
|
|
21
21
|
|
|
@@ -10,7 +10,7 @@ steps:
|
|
|
10
10
|
from npcpy.data.load import load_file_contents
|
|
11
11
|
|
|
12
12
|
# Expand user path and get absolute path
|
|
13
|
-
file_path = os.path.expanduser(
|
|
13
|
+
file_path = os.path.expanduser({{ file_path | tojson }})
|
|
14
14
|
|
|
15
15
|
# Check if file exists
|
|
16
16
|
if not os.path.exists(file_path):
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
jinx_name: paste
|
|
2
|
+
description: Grabs content from clipboard (images or text) and saves/displays it. Use this when Ctrl+V paste doesn't work properly.
|
|
3
|
+
inputs:
|
|
4
|
+
- output_path:
|
|
5
|
+
default: ""
|
|
6
|
+
description: "Optional path to save image to. If empty, saves to temp file."
|
|
7
|
+
steps:
|
|
8
|
+
- name: "paste_clipboard"
|
|
9
|
+
engine: "python"
|
|
10
|
+
code: |
|
|
11
|
+
import tempfile
|
|
12
|
+
import os
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
import io
|
|
15
|
+
|
|
16
|
+
output_path = ({{ output_path | default("") | tojson }} or "").strip()
|
|
17
|
+
image_saved = False
|
|
18
|
+
text_content = None
|
|
19
|
+
|
|
20
|
+
# Try PIL/Pillow with ImageGrab first (works on most systems)
|
|
21
|
+
try:
|
|
22
|
+
from PIL import ImageGrab, Image
|
|
23
|
+
|
|
24
|
+
# Try to grab image from clipboard
|
|
25
|
+
img = ImageGrab.grabclipboard()
|
|
26
|
+
|
|
27
|
+
if img is not None:
|
|
28
|
+
if isinstance(img, Image.Image):
|
|
29
|
+
# It's an image
|
|
30
|
+
if output_path:
|
|
31
|
+
save_path = os.path.expanduser(output_path)
|
|
32
|
+
else:
|
|
33
|
+
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
34
|
+
fd, save_path = tempfile.mkstemp(suffix='.png', prefix=f'npcsh_paste_{timestamp}_')
|
|
35
|
+
os.close(fd)
|
|
36
|
+
|
|
37
|
+
img.save(save_path, 'PNG')
|
|
38
|
+
file_size = os.path.getsize(save_path)
|
|
39
|
+
|
|
40
|
+
if file_size > 1024 * 1024:
|
|
41
|
+
size_str = f"{file_size / (1024*1024):.1f} MB"
|
|
42
|
+
elif file_size > 1024:
|
|
43
|
+
size_str = f"{file_size / 1024:.1f} KB"
|
|
44
|
+
else:
|
|
45
|
+
size_str = f"{file_size} bytes"
|
|
46
|
+
|
|
47
|
+
output = f"Image saved to: {save_path} ({size_str}, {img.size[0]}x{img.size[1]})"
|
|
48
|
+
context['pasted_image_path'] = save_path
|
|
49
|
+
image_saved = True
|
|
50
|
+
|
|
51
|
+
elif isinstance(img, list):
|
|
52
|
+
# It's a list of file paths (copied files)
|
|
53
|
+
output = f"Clipboard contains {len(img)} file(s):\n" + "\n".join(img)
|
|
54
|
+
context['pasted_files'] = img
|
|
55
|
+
image_saved = True
|
|
56
|
+
|
|
57
|
+
except ImportError:
|
|
58
|
+
pass
|
|
59
|
+
except Exception as e:
|
|
60
|
+
# ImageGrab failed, try other methods
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# If no image, try to get text via tkinter
|
|
64
|
+
if not image_saved:
|
|
65
|
+
try:
|
|
66
|
+
import tkinter as tk
|
|
67
|
+
|
|
68
|
+
root = tk.Tk()
|
|
69
|
+
root.withdraw()
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
text_content = root.clipboard_get()
|
|
73
|
+
except tk.TclError:
|
|
74
|
+
text_content = None
|
|
75
|
+
|
|
76
|
+
root.destroy()
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
# If still nothing, try GTK
|
|
82
|
+
if not image_saved and text_content is None:
|
|
83
|
+
try:
|
|
84
|
+
import gi
|
|
85
|
+
gi.require_version('Gtk', '3.0')
|
|
86
|
+
from gi.repository import Gtk, Gdk, GdkPixbuf
|
|
87
|
+
|
|
88
|
+
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
|
89
|
+
|
|
90
|
+
# Try image first
|
|
91
|
+
pixbuf = clipboard.wait_for_image()
|
|
92
|
+
if pixbuf:
|
|
93
|
+
if output_path:
|
|
94
|
+
save_path = os.path.expanduser(output_path)
|
|
95
|
+
else:
|
|
96
|
+
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
97
|
+
fd, save_path = tempfile.mkstemp(suffix='.png', prefix=f'npcsh_paste_{timestamp}_')
|
|
98
|
+
os.close(fd)
|
|
99
|
+
|
|
100
|
+
pixbuf.savev(save_path, 'png', [], [])
|
|
101
|
+
file_size = os.path.getsize(save_path)
|
|
102
|
+
|
|
103
|
+
if file_size > 1024 * 1024:
|
|
104
|
+
size_str = f"{file_size / (1024*1024):.1f} MB"
|
|
105
|
+
elif file_size > 1024:
|
|
106
|
+
size_str = f"{file_size / 1024:.1f} KB"
|
|
107
|
+
else:
|
|
108
|
+
size_str = f"{file_size} bytes"
|
|
109
|
+
|
|
110
|
+
output = f"Image saved to: {save_path} ({size_str}, {pixbuf.get_width()}x{pixbuf.get_height()})"
|
|
111
|
+
context['pasted_image_path'] = save_path
|
|
112
|
+
image_saved = True
|
|
113
|
+
else:
|
|
114
|
+
# Try text
|
|
115
|
+
text_content = clipboard.wait_for_text()
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
# Handle text content
|
|
121
|
+
if not image_saved and text_content:
|
|
122
|
+
line_count = text_content.count('\n') + 1
|
|
123
|
+
char_count = len(text_content)
|
|
124
|
+
context['pasted_text'] = text_content
|
|
125
|
+
|
|
126
|
+
if line_count > 10:
|
|
127
|
+
preview_lines = text_content.split('\n')[:10]
|
|
128
|
+
preview = '\n'.join(preview_lines)
|
|
129
|
+
output = f"Clipboard text ({line_count} lines, {char_count} chars):\n---\n{preview}\n... ({line_count - 10} more lines)\n---"
|
|
130
|
+
else:
|
|
131
|
+
output = f"Clipboard text ({line_count} lines, {char_count} chars):\n---\n{text_content}\n---"
|
|
132
|
+
|
|
133
|
+
elif not image_saved:
|
|
134
|
+
output = "Clipboard is empty or could not access clipboard.\nMake sure you have PIL/Pillow installed: pip install Pillow"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
jinx_name: sh
|
|
2
|
-
description: Execute bash queries. Should be used to grep for file contents, list directories, explore information to answer user questions more practically.
|
|
2
|
+
description: Execute bash queries. Should be used to grep for file contents, list directories, explore information to answer user questions more practically. NEVER use ls -R on directories that may contain node_modules, .git, or other large dependency folders - this will exceed token limits. Use targeted ls commands instead.
|
|
3
3
|
inputs:
|
|
4
4
|
- bash_command
|
|
5
5
|
steps:
|
|
@@ -9,7 +9,7 @@ steps:
|
|
|
9
9
|
import subprocess
|
|
10
10
|
import os
|
|
11
11
|
|
|
12
|
-
cmd =
|
|
12
|
+
cmd = {{ bash_command | tojson }}
|
|
13
13
|
output = ""
|
|
14
14
|
|
|
15
15
|
process = subprocess.Popen(
|
|
@@ -20,12 +20,6 @@ steps:
|
|
|
20
20
|
)
|
|
21
21
|
stdout, stderr = process.communicate()
|
|
22
22
|
|
|
23
|
-
# Only show debug output if NPCSH_DEBUG is set
|
|
24
|
-
if os.environ.get("NPCSH_DEBUG") == "1":
|
|
25
|
-
import sys
|
|
26
|
-
print(f"[sh] cmd: {cmd}", file=sys.stderr)
|
|
27
|
-
print(f"[sh] stdout: {stdout.decode('utf-8', errors='ignore')[:200]}", file=sys.stderr)
|
|
28
|
-
|
|
29
23
|
if stderr:
|
|
30
24
|
output = f"Error: {stderr.decode('utf-8')}"
|
|
31
25
|
else:
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
jinx_name: convene
|
|
2
|
+
description: Run a cycle of discussions between NPCs on a topic. The orchestrator convenes agents to discuss and synthesize.
|
|
3
|
+
inputs:
|
|
4
|
+
- topic: ""
|
|
5
|
+
- npcs: "alicanto,corca,guac"
|
|
6
|
+
- rounds: 3
|
|
7
|
+
- model: null
|
|
8
|
+
- provider: null
|
|
9
|
+
steps:
|
|
10
|
+
- name: convene_discussion
|
|
11
|
+
engine: python
|
|
12
|
+
code: |
|
|
13
|
+
from termcolor import colored
|
|
14
|
+
from npcpy.llm_funcs import get_llm_response
|
|
15
|
+
|
|
16
|
+
topic = context.get('topic', '')
|
|
17
|
+
npcs_str = context.get('npcs', 'alicanto,corca,guac')
|
|
18
|
+
rounds = int(context.get('rounds', 3))
|
|
19
|
+
|
|
20
|
+
npc = context.get('npc')
|
|
21
|
+
team = context.get('team')
|
|
22
|
+
messages = context.get('messages', [])
|
|
23
|
+
|
|
24
|
+
model = context.get('model') or (npc.model if npc else 'gemini-1.5-flash')
|
|
25
|
+
provider = context.get('provider') or (npc.provider if npc else 'gemini')
|
|
26
|
+
|
|
27
|
+
if not topic:
|
|
28
|
+
context['output'] = """Usage: /convene <topic>
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--npcs LIST Comma-separated NPC names (default: alicanto,corca,guac)
|
|
32
|
+
--rounds N Number of discussion rounds (default: 3)
|
|
33
|
+
|
|
34
|
+
Example: /convene "How should we approach the database migration?" --npcs corca,guac,frederic
|
|
35
|
+
"""
|
|
36
|
+
exit()
|
|
37
|
+
|
|
38
|
+
npc_names = [n.strip() for n in npcs_str.split(',')]
|
|
39
|
+
|
|
40
|
+
print(f"""
|
|
41
|
+
██████ ██████ ███ ██ ██ ██ ███████ ███ ██ ███████
|
|
42
|
+
██ ██ ██ ████ ██ ██ ██ ██ ████ ██ ██
|
|
43
|
+
██ ██ ██ ██ ██ ██ ██ ██ █████ ██ ██ ██ █████
|
|
44
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
45
|
+
██████ ██████ ██ ████ ████ ███████ ██ ████ ███████
|
|
46
|
+
|
|
47
|
+
Convening Discussion
|
|
48
|
+
Topic: {topic}
|
|
49
|
+
Participants: {', '.join(npc_names)}
|
|
50
|
+
Rounds: {rounds}
|
|
51
|
+
""")
|
|
52
|
+
|
|
53
|
+
# Get NPC personas
|
|
54
|
+
participants = []
|
|
55
|
+
for name in npc_names:
|
|
56
|
+
if team and hasattr(team, 'npcs') and name in team.npcs:
|
|
57
|
+
target_npc = team.npcs[name]
|
|
58
|
+
persona = getattr(target_npc, 'primary_directive', f'{name} specialist')
|
|
59
|
+
participants.append({'name': name, 'persona': persona, 'npc': target_npc})
|
|
60
|
+
else:
|
|
61
|
+
participants.append({'name': name, 'persona': f'{name} - general assistant', 'npc': None})
|
|
62
|
+
|
|
63
|
+
import random
|
|
64
|
+
|
|
65
|
+
discussion_log = []
|
|
66
|
+
|
|
67
|
+
for round_num in range(1, rounds + 1):
|
|
68
|
+
print(colored(f"\n{'='*60}", "cyan"))
|
|
69
|
+
print(colored(f" ROUND {round_num}/{rounds}", "cyan", attrs=["bold"]))
|
|
70
|
+
print(colored(f"{'='*60}", "cyan"))
|
|
71
|
+
|
|
72
|
+
round_contributions = []
|
|
73
|
+
|
|
74
|
+
for participant in participants:
|
|
75
|
+
name = participant['name']
|
|
76
|
+
persona = participant['persona']
|
|
77
|
+
|
|
78
|
+
# Build context from previous contributions
|
|
79
|
+
prev_context = ""
|
|
80
|
+
if discussion_log:
|
|
81
|
+
prev_context = "\n\nPrevious discussion:\n"
|
|
82
|
+
for entry in discussion_log[-len(participants)*2:]:
|
|
83
|
+
prev_context += f"[{entry['speaker']}]: {entry['contribution'][:200]}...\n"
|
|
84
|
+
|
|
85
|
+
if round_contributions:
|
|
86
|
+
prev_context += "\nThis round so far:\n"
|
|
87
|
+
for entry in round_contributions:
|
|
88
|
+
prev_context += f"[{entry['speaker']}]: {entry['contribution'][:200]}...\n"
|
|
89
|
+
|
|
90
|
+
prompt = f"""You are {name}. {persona}
|
|
91
|
+
|
|
92
|
+
Topic under discussion: "{topic}"
|
|
93
|
+
{prev_context}
|
|
94
|
+
|
|
95
|
+
Provide your perspective on this topic. Be concise but insightful.
|
|
96
|
+
Build on what others have said if applicable.
|
|
97
|
+
If you disagree with something, explain why constructively.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
print(colored(f"\n[{name}]:", "yellow", attrs=["bold"]))
|
|
101
|
+
|
|
102
|
+
resp = get_llm_response(
|
|
103
|
+
prompt,
|
|
104
|
+
model=model,
|
|
105
|
+
provider=provider,
|
|
106
|
+
npc=participant.get('npc') or npc,
|
|
107
|
+
temperature=0.7
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
contribution = str(resp.get('response', ''))
|
|
111
|
+
print(contribution)
|
|
112
|
+
|
|
113
|
+
entry = {
|
|
114
|
+
'round': round_num,
|
|
115
|
+
'speaker': name,
|
|
116
|
+
'contribution': contribution
|
|
117
|
+
}
|
|
118
|
+
round_contributions.append(entry)
|
|
119
|
+
discussion_log.append(entry)
|
|
120
|
+
|
|
121
|
+
# Sample a followup from another participant
|
|
122
|
+
other_participants = [p for p in participants if p['name'] != name]
|
|
123
|
+
if other_participants:
|
|
124
|
+
followup_participant = random.choice(other_participants)
|
|
125
|
+
followup_name = followup_participant['name']
|
|
126
|
+
followup_persona = followup_participant['persona']
|
|
127
|
+
|
|
128
|
+
followup_prompt = f"""You are {followup_name}. {followup_persona}
|
|
129
|
+
|
|
130
|
+
Topic: "{topic}"
|
|
131
|
+
|
|
132
|
+
{name} just said: "{contribution[:500]}"
|
|
133
|
+
|
|
134
|
+
Respond briefly to this specific point - agree, disagree, build on it, or ask a clarifying question.
|
|
135
|
+
Keep it to 2-3 sentences.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
print(colored(f"\n [{followup_name} responds]:", "cyan"))
|
|
139
|
+
|
|
140
|
+
followup_resp = get_llm_response(
|
|
141
|
+
followup_prompt,
|
|
142
|
+
model=model,
|
|
143
|
+
provider=provider,
|
|
144
|
+
npc=followup_participant.get('npc') or npc,
|
|
145
|
+
temperature=0.7
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
followup_contribution = str(followup_resp.get('response', ''))
|
|
149
|
+
print(f" {followup_contribution}")
|
|
150
|
+
|
|
151
|
+
discussion_log.append({
|
|
152
|
+
'round': round_num,
|
|
153
|
+
'speaker': followup_name,
|
|
154
|
+
'contribution': followup_contribution,
|
|
155
|
+
'type': 'followup'
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
# Probability of original speaker responding back vs someone else
|
|
159
|
+
if random.random() < 0.4:
|
|
160
|
+
# Original speaker responds
|
|
161
|
+
responder = participant
|
|
162
|
+
responder_name = name
|
|
163
|
+
else:
|
|
164
|
+
# Sample from others (could be followup person or someone else)
|
|
165
|
+
responder = random.choice(other_participants)
|
|
166
|
+
responder_name = responder['name']
|
|
167
|
+
|
|
168
|
+
if random.random() < 0.6: # 60% chance of a counter-response
|
|
169
|
+
counter_prompt = f"""You are {responder_name}. {responder['persona']}
|
|
170
|
+
|
|
171
|
+
Topic: "{topic}"
|
|
172
|
+
|
|
173
|
+
{followup_name} responded: "{followup_contribution}"
|
|
174
|
+
|
|
175
|
+
Brief reaction (1-2 sentences). Move the discussion forward.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
print(colored(f"\n [{responder_name}]:", "magenta"))
|
|
179
|
+
|
|
180
|
+
counter_resp = get_llm_response(
|
|
181
|
+
counter_prompt,
|
|
182
|
+
model=model,
|
|
183
|
+
provider=provider,
|
|
184
|
+
npc=responder.get('npc') or npc,
|
|
185
|
+
temperature=0.7
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
counter_contribution = str(counter_resp.get('response', ''))
|
|
189
|
+
print(f" {counter_contribution}")
|
|
190
|
+
|
|
191
|
+
discussion_log.append({
|
|
192
|
+
'round': round_num,
|
|
193
|
+
'speaker': responder_name,
|
|
194
|
+
'contribution': counter_contribution,
|
|
195
|
+
'type': 'counter'
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
# Synthesis
|
|
199
|
+
print(colored(f"\n{'='*60}", "green"))
|
|
200
|
+
print(colored(" SYNTHESIS", "green", attrs=["bold"]))
|
|
201
|
+
print(colored(f"{'='*60}", "green"))
|
|
202
|
+
|
|
203
|
+
all_contributions = "\n".join([
|
|
204
|
+
f"[{e['speaker']} - Round {e['round']}]: {e['contribution']}"
|
|
205
|
+
for e in discussion_log
|
|
206
|
+
])
|
|
207
|
+
|
|
208
|
+
synthesis_prompt = f"""As the convener of this discussion on "{topic}", synthesize the key points:
|
|
209
|
+
|
|
210
|
+
Full discussion:
|
|
211
|
+
{all_contributions}
|
|
212
|
+
|
|
213
|
+
Provide:
|
|
214
|
+
1. Key agreements and consensus points
|
|
215
|
+
2. Areas of disagreement or tension
|
|
216
|
+
3. Novel ideas that emerged
|
|
217
|
+
4. Recommended next steps or actions
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
resp = get_llm_response(synthesis_prompt, model=model, provider=provider, npc=npc, temperature=0.4)
|
|
221
|
+
synthesis = str(resp.get('response', ''))
|
|
222
|
+
print(synthesis)
|
|
223
|
+
|
|
224
|
+
context['output'] = synthesis
|
|
225
|
+
context['messages'] = messages
|
|
226
|
+
context['convene_result'] = {
|
|
227
|
+
'topic': topic,
|
|
228
|
+
'participants': npc_names,
|
|
229
|
+
'rounds': rounds,
|
|
230
|
+
'discussion': discussion_log,
|
|
231
|
+
'synthesis': synthesis
|
|
232
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
jinx_name: delegate
|
|
2
|
+
description: Delegate a task to another NPC with review and feedback loop until completion. Choose the NPC whose directive best matches the task.
|
|
3
|
+
inputs:
|
|
4
|
+
- npc_name:
|
|
5
|
+
description: "Name of the NPC to delegate to"
|
|
6
|
+
- task:
|
|
7
|
+
description: "The task or request to delegate to the NPC"
|
|
8
|
+
- max_iterations: "10"
|
|
9
|
+
steps:
|
|
10
|
+
- name: delegate_with_review
|
|
11
|
+
engine: python
|
|
12
|
+
code: |
|
|
13
|
+
from termcolor import colored
|
|
14
|
+
from npcpy.llm_funcs import get_llm_response
|
|
15
|
+
|
|
16
|
+
# Try to get spinner for status updates
|
|
17
|
+
try:
|
|
18
|
+
from npcsh.ui import get_current_spinner
|
|
19
|
+
spinner = get_current_spinner()
|
|
20
|
+
except:
|
|
21
|
+
spinner = None
|
|
22
|
+
|
|
23
|
+
target_name = {{ npc_name | default("") | tojson }}.lower().strip()
|
|
24
|
+
task_request = {{ task | default("") | tojson }}
|
|
25
|
+
max_iters = int({{ max_iterations | default("10") | tojson }} or "10")
|
|
26
|
+
|
|
27
|
+
team_obj = context.get('team') or getattr(npc, 'team', None)
|
|
28
|
+
orchestrator = context.get('npc') or npc
|
|
29
|
+
orchestrator_name = getattr(orchestrator, 'name', 'orchestrator')
|
|
30
|
+
|
|
31
|
+
if not team_obj:
|
|
32
|
+
output = "Error: No team available for delegation"
|
|
33
|
+
exit()
|
|
34
|
+
|
|
35
|
+
if not hasattr(team_obj, 'npcs') or target_name not in team_obj.npcs:
|
|
36
|
+
available = list(team_obj.npcs.keys()) if hasattr(team_obj, 'npcs') else []
|
|
37
|
+
output = "Error: NPC '{}' not found. Available: {}".format(target_name, ', '.join(available))
|
|
38
|
+
exit()
|
|
39
|
+
|
|
40
|
+
target_npc = team_obj.npcs[target_name]
|
|
41
|
+
target_jinxs = dict((k, v) for k, v in target_npc.jinxs_dict.items() if k != 'delegate')
|
|
42
|
+
|
|
43
|
+
sep = '-' * 60
|
|
44
|
+
print(colored("\n" + sep, "cyan"))
|
|
45
|
+
print(colored(" Delegating to @" + target_name, "yellow", attrs=["bold"]))
|
|
46
|
+
task_preview = task_request[:100] + ('...' if len(task_request) > 100 else '')
|
|
47
|
+
print(colored(" Task: " + task_preview, "white", attrs=["dark"]))
|
|
48
|
+
print(colored(sep + "\n", "cyan"))
|
|
49
|
+
print(colored(" [{}] Model: {}".format(target_name, target_npc.model), "white", attrs=["dark"]))
|
|
50
|
+
jinx_list = ', '.join(list(target_jinxs.keys())[:8])
|
|
51
|
+
print(colored(" [{}] Jinxs: {}...".format(target_name, jinx_list), "white", attrs=["dark"]))
|
|
52
|
+
|
|
53
|
+
# Update spinner to show sub-agent
|
|
54
|
+
if spinner:
|
|
55
|
+
spinner.set_message("{} delegated to {}".format(orchestrator_name, target_name))
|
|
56
|
+
|
|
57
|
+
current_task = task_request
|
|
58
|
+
iteration = 0
|
|
59
|
+
final_output = None
|
|
60
|
+
task_complete = False
|
|
61
|
+
|
|
62
|
+
while iteration < max_iters and not task_complete:
|
|
63
|
+
iteration += 1
|
|
64
|
+
|
|
65
|
+
# Update spinner with current iteration
|
|
66
|
+
if spinner:
|
|
67
|
+
spinner.set_message("{} working (iter {}/{})".format(target_name, iteration, max_iters))
|
|
68
|
+
|
|
69
|
+
if iteration > 1:
|
|
70
|
+
print(colored("\n" + sep, "yellow"))
|
|
71
|
+
iter_msg = " Iteration {}/{} - Re-tasking @{}".format(iteration, max_iters, target_name)
|
|
72
|
+
print(colored(iter_msg, "yellow", attrs=["bold"]))
|
|
73
|
+
print(colored(sep + "\n", "yellow"))
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
result = target_npc.check_llm_command(
|
|
77
|
+
current_task,
|
|
78
|
+
context=context,
|
|
79
|
+
team=team_obj,
|
|
80
|
+
jinxs=target_jinxs,
|
|
81
|
+
stream=False,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if isinstance(result, dict):
|
|
85
|
+
delegate_output = result.get('output') or result.get('response') or str(result)
|
|
86
|
+
delegate_messages = result.get('messages', [])
|
|
87
|
+
else:
|
|
88
|
+
delegate_output = str(result)
|
|
89
|
+
delegate_messages = []
|
|
90
|
+
|
|
91
|
+
print(colored("\n" + sep, "cyan"))
|
|
92
|
+
print(colored(" @{} iteration {} complete".format(target_name, iteration), "green"))
|
|
93
|
+
print(colored(sep + "\n", "cyan"))
|
|
94
|
+
|
|
95
|
+
# Update spinner for review phase
|
|
96
|
+
if spinner:
|
|
97
|
+
spinner.set_message("{} reviewing {}'s work".format(orchestrator_name, target_name))
|
|
98
|
+
|
|
99
|
+
# Build review prompt without f-strings to avoid YAML issues
|
|
100
|
+
output_preview = delegate_output[:2000] if delegate_output else 'No output received'
|
|
101
|
+
msg_preview = str(delegate_messages[-5:]) if delegate_messages else 'No messages'
|
|
102
|
+
|
|
103
|
+
review_lines = [
|
|
104
|
+
"You are reviewing work done by @{} on this task:".format(target_name),
|
|
105
|
+
"",
|
|
106
|
+
"ORIGINAL TASK: " + task_request,
|
|
107
|
+
"",
|
|
108
|
+
"ITERATION: {}/{}".format(iteration, max_iters),
|
|
109
|
+
"",
|
|
110
|
+
"SUB-AGENT OUTPUT:",
|
|
111
|
+
output_preview,
|
|
112
|
+
"",
|
|
113
|
+
"RECENT MESSAGES:",
|
|
114
|
+
msg_preview,
|
|
115
|
+
"",
|
|
116
|
+
"Evaluate if the task is complete. Consider:",
|
|
117
|
+
"1. Did the sub-agent accomplish what was asked?",
|
|
118
|
+
"2. Are there obvious errors or incomplete steps?",
|
|
119
|
+
"3. For GUI tasks: Did they fill in all required fields?",
|
|
120
|
+
"",
|
|
121
|
+
"Respond EXACTLY like this:",
|
|
122
|
+
"COMPLETE: YES or NO",
|
|
123
|
+
"FEEDBACK: If NO, what should be done next",
|
|
124
|
+
"SUMMARY: Brief summary of progress"
|
|
125
|
+
]
|
|
126
|
+
review_prompt = "\n".join(review_lines)
|
|
127
|
+
|
|
128
|
+
review_result = get_llm_response(
|
|
129
|
+
review_prompt,
|
|
130
|
+
model=getattr(orchestrator, 'model', 'gemini-2.5-flash'),
|
|
131
|
+
provider=getattr(orchestrator, 'provider', 'gemini'),
|
|
132
|
+
npc=orchestrator,
|
|
133
|
+
temperature=0.3
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
review_text = str(review_result.get('response', ''))
|
|
137
|
+
is_complete = 'COMPLETE: YES' in review_text.upper()
|
|
138
|
+
|
|
139
|
+
feedback = ""
|
|
140
|
+
if 'FEEDBACK:' in review_text:
|
|
141
|
+
fb_start = review_text.find('FEEDBACK:') + 9
|
|
142
|
+
fb_end = review_text.find('SUMMARY:', fb_start) if 'SUMMARY:' in review_text else len(review_text)
|
|
143
|
+
feedback = review_text[fb_start:fb_end].strip()
|
|
144
|
+
|
|
145
|
+
summary = ""
|
|
146
|
+
if 'SUMMARY:' in review_text:
|
|
147
|
+
summary = review_text[review_text.find('SUMMARY:') + 8:].strip()
|
|
148
|
+
|
|
149
|
+
if is_complete:
|
|
150
|
+
task_complete = True
|
|
151
|
+
print(colored("\n Task completed successfully", "green", attrs=["bold"]))
|
|
152
|
+
if summary:
|
|
153
|
+
print(colored(" Summary: " + summary[:200], "white", attrs=["dark"]))
|
|
154
|
+
final_output = "[{}] Task completed.\n{}".format(target_name, summary)
|
|
155
|
+
else:
|
|
156
|
+
print(colored("\n Task incomplete - providing feedback", "yellow"))
|
|
157
|
+
if feedback:
|
|
158
|
+
print(colored(" Feedback: " + feedback[:200] + "...", "white", attrs=["dark"]))
|
|
159
|
+
|
|
160
|
+
followup_lines = [
|
|
161
|
+
"Continue the previous task. Feedback from orchestrator:",
|
|
162
|
+
"",
|
|
163
|
+
"ORIGINAL TASK: " + task_request,
|
|
164
|
+
"",
|
|
165
|
+
"FEEDBACK: " + feedback,
|
|
166
|
+
"",
|
|
167
|
+
"Continue and complete the task based on this feedback."
|
|
168
|
+
]
|
|
169
|
+
current_task = "\n".join(followup_lines)
|
|
170
|
+
|
|
171
|
+
if delegate_messages:
|
|
172
|
+
context['messages'] = delegate_messages
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
print(colored(" Error in iteration {}: {}".format(iteration, e), "red"))
|
|
176
|
+
final_output = "Error delegating to {}: {}".format(target_name, str(e))
|
|
177
|
+
break
|
|
178
|
+
|
|
179
|
+
if not task_complete and iteration >= max_iters:
|
|
180
|
+
print(colored("\n Max iterations ({}) reached".format(max_iters), "yellow"))
|
|
181
|
+
status = summary if summary else 'Unknown'
|
|
182
|
+
final_output = "[{}] Task incomplete after {} iterations. Status: {}".format(target_name, max_iters, status)
|
|
183
|
+
|
|
184
|
+
output = final_output or "[{}]: No output received".format(target_name)
|