npcsh 1.1.20__py3-none-any.whl → 1.1.22__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 +15 -76
- npcsh/benchmark/npcsh_agent.py +22 -14
- npcsh/benchmark/templates/install-npcsh.sh.j2 +2 -2
- npcsh/diff_viewer.py +3 -3
- npcsh/mcp_server.py +9 -1
- npcsh/npc_team/alicanto.npc +12 -6
- npcsh/npc_team/corca.npc +0 -1
- npcsh/npc_team/frederic.npc +2 -3
- npcsh/npc_team/jinxs/lib/core/compress.jinx +373 -85
- npcsh/npc_team/jinxs/lib/core/edit_file.jinx +83 -61
- npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +17 -6
- npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +17 -6
- npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +52 -14
- npcsh/npc_team/jinxs/{bin → lib/utils}/benchmark.jinx +2 -2
- npcsh/npc_team/jinxs/{bin → lib/utils}/jinxs.jinx +12 -12
- npcsh/npc_team/jinxs/{bin → lib/utils}/models.jinx +7 -7
- npcsh/npc_team/jinxs/{bin → lib/utils}/setup.jinx +6 -6
- npcsh/npc_team/jinxs/modes/alicanto.jinx +1633 -295
- npcsh/npc_team/jinxs/modes/arxiv.jinx +5 -5
- npcsh/npc_team/jinxs/modes/build.jinx +378 -0
- npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
- npcsh/npc_team/jinxs/modes/convene.jinx +597 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +777 -387
- npcsh/npc_team/jinxs/modes/git.jinx +795 -0
- {npcsh-1.1.20.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/modes}/kg.jinx +82 -15
- npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
- npcsh/npc_team/jinxs/{bin → modes}/nql.jinx +10 -21
- npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +503 -308
- npcsh/npc_team/jinxs/modes/reattach.jinx +3 -3
- npcsh/npc_team/jinxs/modes/spool.jinx +3 -3
- npcsh/npc_team/jinxs/{bin → modes}/team.jinx +12 -12
- npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +454 -181
- npcsh/npc_team/jinxs/modes/yap.jinx +630 -182
- npcsh/npc_team/kadiefa.npc +2 -1
- npcsh/npc_team/sibiji.npc +3 -3
- npcsh/npcsh.py +112 -47
- npcsh/routes.py +4 -1
- npcsh/salmon_simulation.py +0 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/alicanto.jinx +1694 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.npc +12 -6
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/arxiv.jinx +5 -5
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/benchmark.jinx +2 -2
- npcsh-1.1.22.data/data/npcsh/npc_team/build.jinx +378 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/compress.jinx +428 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/config_tui.jinx +300 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/corca.jinx +820 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.npc +0 -1
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/db_search.jinx +17 -6
- npcsh-1.1.22.data/data/npcsh/npc_team/edit_file.jinx +119 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/file_search.jinx +17 -6
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic.npc +2 -3
- npcsh-1.1.22.data/data/npcsh/npc_team/git.jinx +795 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/jinxs.jinx +12 -12
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.npc +2 -1
- {npcsh/npc_team/jinxs/bin → npcsh-1.1.22.data/data/npcsh/npc_team}/kg.jinx +82 -15
- npcsh-1.1.22.data/data/npcsh/npc_team/memories.jinx +414 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/models.jinx +7 -7
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/nql.jinx +10 -21
- npcsh-1.1.22.data/data/npcsh/npc_team/papers.jinx +578 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/plonk.jinx +574 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/reattach.jinx +3 -3
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/setup.jinx +6 -6
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.npc +3 -3
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.jinx +3 -3
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/team.jinx +12 -12
- npcsh-1.1.22.data/data/npcsh/npc_team/vixynt.jinx +388 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/wander.jinx +728 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/web_search.jinx +52 -14
- npcsh-1.1.22.data/data/npcsh/npc_team/yap.jinx +716 -0
- {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/METADATA +246 -281
- npcsh-1.1.22.dist-info/RECORD +240 -0
- npcsh-1.1.22.dist-info/entry_points.txt +11 -0
- npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -300
- npcsh/npc_team/jinxs/bin/memories.jinx +0 -317
- npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
- npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +0 -418
- npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
- npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
- npcsh/npc_team/jinxs/lib/core/search.jinx +0 -54
- npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
- npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
- npcsh/npc_team/jinxs/lib/utils/build.jinx +0 -65
- npcsh/npc_team/plonkjr.npc +0 -23
- npcsh-1.1.20.data/data/npcsh/npc_team/alicanto.jinx +0 -356
- npcsh-1.1.20.data/data/npcsh/npc_team/build.jinx +0 -65
- npcsh-1.1.20.data/data/npcsh/npc_team/compress.jinx +0 -140
- npcsh-1.1.20.data/data/npcsh/npc_team/config_tui.jinx +0 -300
- npcsh-1.1.20.data/data/npcsh/npc_team/corca.jinx +0 -430
- npcsh-1.1.20.data/data/npcsh/npc_team/edit_file.jinx +0 -97
- npcsh-1.1.20.data/data/npcsh/npc_team/kg_search.jinx +0 -418
- npcsh-1.1.20.data/data/npcsh/npc_team/mem_review.jinx +0 -73
- npcsh-1.1.20.data/data/npcsh/npc_team/mem_search.jinx +0 -388
- npcsh-1.1.20.data/data/npcsh/npc_team/memories.jinx +0 -317
- npcsh-1.1.20.data/data/npcsh/npc_team/paper_search.jinx +0 -412
- npcsh-1.1.20.data/data/npcsh/npc_team/plonk.jinx +0 -379
- npcsh-1.1.20.data/data/npcsh/npc_team/plonkjr.npc +0 -23
- npcsh-1.1.20.data/data/npcsh/npc_team/search.jinx +0 -54
- npcsh-1.1.20.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -386
- npcsh-1.1.20.data/data/npcsh/npc_team/vixynt.jinx +0 -122
- npcsh-1.1.20.data/data/npcsh/npc_team/wander.jinx +0 -455
- npcsh-1.1.20.data/data/npcsh/npc_team/yap.jinx +0 -268
- npcsh-1.1.20.dist-info/RECORD +0 -248
- npcsh-1.1.20.dist-info/entry_points.txt +0 -25
- /npcsh/npc_team/jinxs/lib/{orchestration → core}/convene.jinx +0 -0
- /npcsh/npc_team/jinxs/lib/{orchestration → core}/delegate.jinx +0 -0
- /npcsh/npc_team/jinxs/{bin → lib/core}/sample.jinx +0 -0
- /npcsh/npc_team/jinxs/lib/{core → utils}/chat.jinx +0 -0
- /npcsh/npc_team/jinxs/lib/{core → utils}/cmd.jinx +0 -0
- /npcsh/npc_team/jinxs/{bin → lib/utils}/sync.jinx +0 -0
- /npcsh/npc_team/jinxs/{bin → modes}/roll.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/click.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/confirm.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/convene.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/delegate.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.npc +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/incognide.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/key_press.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/navigate.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/notify.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/paste.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/pti.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/send_message.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sh.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/shh.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sql.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switches.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sync.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/type_text.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/verbose.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/wait.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/write_file.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
- {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/WHEEL +0 -0
- {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
jinx_name: wander
|
|
2
|
+
description: Interactive wandering mode - creative exploration with live TUI dashboard
|
|
3
|
+
interactive: true
|
|
4
|
+
inputs:
|
|
5
|
+
- problem: null
|
|
6
|
+
- environment: null
|
|
7
|
+
- low_temp: 0.5
|
|
8
|
+
- high_temp: 1.9
|
|
9
|
+
- n_min: 30
|
|
10
|
+
- n_max: 150
|
|
11
|
+
- interruption_likelihood: 0.1
|
|
12
|
+
- sample_rate: 0.5
|
|
13
|
+
- n_high_temp_streams: 5
|
|
14
|
+
- include_events: true
|
|
15
|
+
- num_events: 3
|
|
16
|
+
- model: null
|
|
17
|
+
- provider: null
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: wander_interactive
|
|
21
|
+
engine: python
|
|
22
|
+
code: |
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
import tty
|
|
26
|
+
import termios
|
|
27
|
+
import random
|
|
28
|
+
import threading
|
|
29
|
+
import time
|
|
30
|
+
import textwrap
|
|
31
|
+
from datetime import datetime
|
|
32
|
+
from termcolor import colored
|
|
33
|
+
|
|
34
|
+
from npcpy.llm_funcs import get_llm_response
|
|
35
|
+
|
|
36
|
+
npc = context.get('npc')
|
|
37
|
+
team = context.get('team')
|
|
38
|
+
messages = context.get('messages', [])
|
|
39
|
+
|
|
40
|
+
problem = context.get('problem')
|
|
41
|
+
environment = context.get('environment')
|
|
42
|
+
low_temp = float(context.get('low_temp') or 0.5)
|
|
43
|
+
high_temp = float(context.get('high_temp') or 1.9)
|
|
44
|
+
n_min = int(context.get('n_min') or 30)
|
|
45
|
+
n_max = int(context.get('n_max') or 150)
|
|
46
|
+
interruption_likelihood = float(context.get('interruption_likelihood') or 0.1)
|
|
47
|
+
sample_rate = float(context.get('sample_rate') or 0.5)
|
|
48
|
+
n_high_temp_streams = int(context.get('n_high_temp_streams') or 5)
|
|
49
|
+
include_events = bool(context.get('include_events', True))
|
|
50
|
+
num_events = int(context.get('num_events') or 3)
|
|
51
|
+
|
|
52
|
+
# Resolve npc
|
|
53
|
+
if isinstance(npc, str) and team:
|
|
54
|
+
npc = team.get(npc) if hasattr(team, 'get') else None
|
|
55
|
+
elif isinstance(npc, str):
|
|
56
|
+
npc = None
|
|
57
|
+
|
|
58
|
+
model = context.get('model') or (npc.model if npc and hasattr(npc, 'model') else None)
|
|
59
|
+
provider = context.get('provider') or (npc.provider if npc and hasattr(npc, 'provider') else None)
|
|
60
|
+
|
|
61
|
+
# ========== Problem Entry ==========
|
|
62
|
+
if not problem:
|
|
63
|
+
if not sys.stdin.isatty():
|
|
64
|
+
context['output'] = "Wander requires an interactive terminal or a problem argument."
|
|
65
|
+
context['messages'] = messages
|
|
66
|
+
exit()
|
|
67
|
+
print("\033[1;35m WANDER - Creative Exploration \033[0m")
|
|
68
|
+
print("\033[90mEnter a problem or question to explore (or 'q' to quit):\033[0m")
|
|
69
|
+
try:
|
|
70
|
+
problem = input("\033[33m> \033[0m").strip()
|
|
71
|
+
except (EOFError, KeyboardInterrupt):
|
|
72
|
+
problem = ""
|
|
73
|
+
if not problem or problem.lower() == 'q':
|
|
74
|
+
context['output'] = "Wander cancelled."
|
|
75
|
+
context['messages'] = messages
|
|
76
|
+
exit()
|
|
77
|
+
|
|
78
|
+
# ========== State ==========
|
|
79
|
+
class WanderState:
|
|
80
|
+
def __init__(self):
|
|
81
|
+
self.environment = ""
|
|
82
|
+
self.streams = [] # [{temp, insight, words, samples, event, starred, timestamp}]
|
|
83
|
+
self.starred = []
|
|
84
|
+
self.current_temp = high_temp
|
|
85
|
+
self.scroll_offset = 0
|
|
86
|
+
self.current_panel = 0 # 0=main, 1=streams, 2=starred, 3=review
|
|
87
|
+
self.status = "Ready"
|
|
88
|
+
self.generating = False
|
|
89
|
+
self.last_output = ""
|
|
90
|
+
self.all_samples = [] # collected word samples across streams
|
|
91
|
+
self.events = [] # [{type, text}]
|
|
92
|
+
self.quit_requested = False
|
|
93
|
+
self.auto_done = False
|
|
94
|
+
# Review mode
|
|
95
|
+
self.review_mode = False
|
|
96
|
+
self.review_items = [] # [{idx, text_preview, selected}]
|
|
97
|
+
self.review_cursor = 0
|
|
98
|
+
self.review_scroll = 0
|
|
99
|
+
self.review_sample_count = 0
|
|
100
|
+
self.editing_count = False
|
|
101
|
+
self.count_buf = ""
|
|
102
|
+
|
|
103
|
+
state = WanderState()
|
|
104
|
+
|
|
105
|
+
# ========== Event Weights ==========
|
|
106
|
+
EVENT_WEIGHTS = {
|
|
107
|
+
'encounter': 0.20,
|
|
108
|
+
'discovery': 0.20,
|
|
109
|
+
'obstacle': 0.15,
|
|
110
|
+
'insight': 0.20,
|
|
111
|
+
'shift': 0.10,
|
|
112
|
+
'memory': 0.15,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def weighted_event_type():
|
|
116
|
+
types = list(EVENT_WEIGHTS.keys())
|
|
117
|
+
weights = list(EVENT_WEIGHTS.values())
|
|
118
|
+
return random.choices(types, weights=weights, k=1)[0]
|
|
119
|
+
|
|
120
|
+
# ========== Original Algorithm Helpers ==========
|
|
121
|
+
def truncate_to_word_count(text, n):
|
|
122
|
+
words = text.split()
|
|
123
|
+
if len(words) <= n:
|
|
124
|
+
return text
|
|
125
|
+
return ' '.join(words[:n])
|
|
126
|
+
|
|
127
|
+
def subsample_words(text, k=20):
|
|
128
|
+
words = text.split()
|
|
129
|
+
if len(words) <= k:
|
|
130
|
+
return words
|
|
131
|
+
return random.sample(words, k)
|
|
132
|
+
|
|
133
|
+
def extract_samples(text, rate):
|
|
134
|
+
words = text.split()
|
|
135
|
+
n_sample = max(1, int(len(words) * rate))
|
|
136
|
+
if len(words) <= n_sample:
|
|
137
|
+
return words
|
|
138
|
+
return random.sample(words, n_sample)
|
|
139
|
+
|
|
140
|
+
# ========== TUI Helpers ==========
|
|
141
|
+
def get_size():
|
|
142
|
+
try:
|
|
143
|
+
s = os.get_terminal_size()
|
|
144
|
+
return s.columns, s.lines
|
|
145
|
+
except:
|
|
146
|
+
return 80, 24
|
|
147
|
+
|
|
148
|
+
def wrap_text(text, width):
|
|
149
|
+
lines = []
|
|
150
|
+
for line in text.split('\n'):
|
|
151
|
+
if len(line) <= width:
|
|
152
|
+
lines.append(line)
|
|
153
|
+
else:
|
|
154
|
+
lines.extend(textwrap.wrap(line, width) or [''])
|
|
155
|
+
return lines
|
|
156
|
+
|
|
157
|
+
def draw_box(x, y, w, h, title="", color="\033[90m"):
|
|
158
|
+
out = []
|
|
159
|
+
if title:
|
|
160
|
+
title_part = f" {title} "
|
|
161
|
+
border = "─" * ((w - len(title_part) - 2) // 2)
|
|
162
|
+
top = f"┌{border}{title_part}{border}{'─' * ((w - len(title_part) - 2) % 2)}┐"
|
|
163
|
+
else:
|
|
164
|
+
top = "┌" + "─" * (w - 2) + "┐"
|
|
165
|
+
out.append(f"\033[{y};{x}H{color}{top}\033[0m")
|
|
166
|
+
for i in range(1, h - 1):
|
|
167
|
+
out.append(f"\033[{y+i};{x}H{color}│\033[0m")
|
|
168
|
+
out.append(f"\033[{y+i};{x+w-1}H{color}│\033[0m")
|
|
169
|
+
out.append(f"\033[{y+h-1};{x}H{color}└{'─' * (w - 2)}┘\033[0m")
|
|
170
|
+
return ''.join(out)
|
|
171
|
+
|
|
172
|
+
def render_screen():
|
|
173
|
+
width, height = get_size()
|
|
174
|
+
out = []
|
|
175
|
+
out.append("\033[2J\033[H")
|
|
176
|
+
|
|
177
|
+
# ===== HEADER =====
|
|
178
|
+
prob_display = problem[:width-20] + "..." if len(problem) > width-20 else problem
|
|
179
|
+
header = f" WANDER - {prob_display} "
|
|
180
|
+
temp_info = f"\033[35m[T={state.current_temp:.1f}]\033[0m"
|
|
181
|
+
status_color = "\033[33m" if state.generating else "\033[32m"
|
|
182
|
+
status_info = f"{status_color}[{state.status}]\033[0m"
|
|
183
|
+
|
|
184
|
+
out.append(f"\033[1;1H\033[7;1m{header.ljust(width)}\033[0m")
|
|
185
|
+
out.append(f"\033[1;{width-35}H{temp_info} {status_info}")
|
|
186
|
+
|
|
187
|
+
if state.review_mode:
|
|
188
|
+
render_review(out, width, height)
|
|
189
|
+
else:
|
|
190
|
+
render_explore(out, width, height)
|
|
191
|
+
|
|
192
|
+
sys.stdout.write(''.join(out))
|
|
193
|
+
sys.stdout.flush()
|
|
194
|
+
|
|
195
|
+
def render_explore(out, width, height):
|
|
196
|
+
left_w = max(25, width // 3)
|
|
197
|
+
right_w = width - left_w - 1
|
|
198
|
+
panel_h = height - 4
|
|
199
|
+
|
|
200
|
+
# Left: Environment
|
|
201
|
+
out.append(draw_box(1, 3, left_w, panel_h // 2, "Environment", "\033[36m"))
|
|
202
|
+
env_lines = wrap_text(state.environment or "Generating...", left_w - 4)
|
|
203
|
+
for i, line in enumerate(env_lines[:panel_h // 2 - 3]):
|
|
204
|
+
out.append(f"\033[{4+i};3H{line[:left_w-4]}")
|
|
205
|
+
|
|
206
|
+
# Left: Controls
|
|
207
|
+
ctrl_y = 3 + panel_h // 2
|
|
208
|
+
out.append(draw_box(1, ctrl_y, left_w, panel_h // 2, "Controls", "\033[33m"))
|
|
209
|
+
controls = [
|
|
210
|
+
"SPACE - New stream",
|
|
211
|
+
f"t - Temp: {state.current_temp:.1f}",
|
|
212
|
+
"e - Trigger event",
|
|
213
|
+
"s - Star insight",
|
|
214
|
+
"R - Review & synthesize",
|
|
215
|
+
"Tab - Switch panel",
|
|
216
|
+
"j/k - Scroll",
|
|
217
|
+
"q - Quit",
|
|
218
|
+
"",
|
|
219
|
+
f"Streams: {len(state.streams)}",
|
|
220
|
+
f"Samples: {len(state.all_samples)}",
|
|
221
|
+
f"Starred: {len(state.starred)}",
|
|
222
|
+
]
|
|
223
|
+
for i, ctrl in enumerate(controls[:panel_h // 2 - 3]):
|
|
224
|
+
out.append(f"\033[{ctrl_y+1+i};3H\033[90m{ctrl[:left_w-4]}\033[0m")
|
|
225
|
+
|
|
226
|
+
# Right panel
|
|
227
|
+
panel_titles = ["Output", "Stream History", "Starred Insights"]
|
|
228
|
+
panel_idx = min(state.current_panel, 2)
|
|
229
|
+
panel_color = ["\033[37m", "\033[36m", "\033[33m"][panel_idx]
|
|
230
|
+
out.append(draw_box(left_w + 1, 3, right_w, panel_h, panel_titles[panel_idx], panel_color))
|
|
231
|
+
|
|
232
|
+
content_w = right_w - 4
|
|
233
|
+
content_h = panel_h - 3
|
|
234
|
+
content_lines = []
|
|
235
|
+
|
|
236
|
+
if panel_idx == 0:
|
|
237
|
+
if state.last_output:
|
|
238
|
+
content_lines = wrap_text(state.last_output, content_w)
|
|
239
|
+
else:
|
|
240
|
+
content_lines = ["", " Waiting for streams..."]
|
|
241
|
+
|
|
242
|
+
elif panel_idx == 1:
|
|
243
|
+
for i, stream in enumerate(reversed(state.streams)):
|
|
244
|
+
starred_mark = "★ " if stream.get('starred') else " "
|
|
245
|
+
hdr = f"{starred_mark}\033[35mStream {len(state.streams)-i} (T={stream.get('temp', 0):.1f}, {len(stream.get('words', []))}w)\033[0m"
|
|
246
|
+
content_lines.append(hdr)
|
|
247
|
+
preview = stream.get('insight', '')[:100].replace('\n', ' ')
|
|
248
|
+
content_lines.append(f" {preview}...")
|
|
249
|
+
if stream.get('samples'):
|
|
250
|
+
content_lines.append(f" \033[90mSamples: {' '.join(stream['samples'][:8])}...\033[0m")
|
|
251
|
+
content_lines.append("")
|
|
252
|
+
|
|
253
|
+
elif panel_idx == 2:
|
|
254
|
+
if not state.starred:
|
|
255
|
+
content_lines = ["", " No starred insights yet.", "", " Press 's' to star current stream."]
|
|
256
|
+
else:
|
|
257
|
+
for i, item in enumerate(state.starred):
|
|
258
|
+
content_lines.append(f"★ {i+1}. [T={item.get('temp', 0):.1f}]")
|
|
259
|
+
for line in wrap_text(item.get('insight', ''), content_w - 4):
|
|
260
|
+
content_lines.append(f" {line}")
|
|
261
|
+
content_lines.append("")
|
|
262
|
+
|
|
263
|
+
visible = content_lines[state.scroll_offset:state.scroll_offset + content_h]
|
|
264
|
+
for i, line in enumerate(visible):
|
|
265
|
+
out.append(f"\033[{4+i};{left_w+3}H{line[:content_w]}")
|
|
266
|
+
|
|
267
|
+
if len(content_lines) > content_h:
|
|
268
|
+
scroll_pct = state.scroll_offset / max(1, len(content_lines) - content_h)
|
|
269
|
+
indicator_pos = int(scroll_pct * (content_h - 1))
|
|
270
|
+
out.append(f"\033[{4+indicator_pos};{width-1}H\033[33m▐\033[0m")
|
|
271
|
+
|
|
272
|
+
# Footer
|
|
273
|
+
panel_tabs = ""
|
|
274
|
+
for i, name in enumerate(["Output", "Streams", "Starred"]):
|
|
275
|
+
if i == panel_idx:
|
|
276
|
+
panel_tabs += f"\033[7m {name} \033[0m "
|
|
277
|
+
else:
|
|
278
|
+
panel_tabs += f"\033[90m {name} \033[0m "
|
|
279
|
+
|
|
280
|
+
out.append(f"\033[{height-1};1H\033[90m{'─' * width}\033[0m")
|
|
281
|
+
out.append(f"\033[{height};1H{panel_tabs}")
|
|
282
|
+
|
|
283
|
+
def render_review(out, width, height):
|
|
284
|
+
panel_h = height - 4
|
|
285
|
+
out.append(draw_box(1, 3, width, panel_h, "Review Insights", "\033[33m"))
|
|
286
|
+
|
|
287
|
+
content_w = width - 6
|
|
288
|
+
content_h = panel_h - 5
|
|
289
|
+
|
|
290
|
+
# Sample count control at top
|
|
291
|
+
if state.editing_count:
|
|
292
|
+
count_line = f" Insights to sample: \033[7m {state.count_buf} \033[0m (Enter to confirm, Esc to cancel)"
|
|
293
|
+
else:
|
|
294
|
+
count_line = f" Insights to sample: \033[1m{state.review_sample_count}\033[0m / {len(state.review_items)} (n to change)"
|
|
295
|
+
out.append(f"\033[4;3H{count_line[:content_w]}")
|
|
296
|
+
out.append(f"\033[5;3H\033[90m{'─' * (content_w)}\033[0m")
|
|
297
|
+
|
|
298
|
+
# Items list
|
|
299
|
+
for i in range(content_h):
|
|
300
|
+
idx = state.review_scroll + i
|
|
301
|
+
row = 6 + i
|
|
302
|
+
out.append(f"\033[{row};3H\033[K")
|
|
303
|
+
if idx >= len(state.review_items):
|
|
304
|
+
continue
|
|
305
|
+
item = state.review_items[idx]
|
|
306
|
+
check = "\033[32m[x]\033[0m" if item['selected'] else "\033[90m[ ]\033[0m"
|
|
307
|
+
cursor = "\033[7m>" if idx == state.review_cursor else " "
|
|
308
|
+
text = item['text_preview'][:content_w - 10]
|
|
309
|
+
if idx == state.review_cursor:
|
|
310
|
+
out.append(f"{cursor} {check} {text}\033[0m")
|
|
311
|
+
else:
|
|
312
|
+
out.append(f" {check} {text}")
|
|
313
|
+
|
|
314
|
+
# Footer
|
|
315
|
+
out.append(f"\033[{height-1};1H\033[90m{'─' * width}\033[0m")
|
|
316
|
+
footer = " j/k:Nav SPACE:Toggle a:All x:None n:Count Enter:Synthesize Esc:Back "
|
|
317
|
+
out.append(f"\033[{height};1H\033[7m{footer.ljust(width)}\033[0m")
|
|
318
|
+
|
|
319
|
+
# ========== Actions ==========
|
|
320
|
+
def generate_environment():
|
|
321
|
+
state.status = "Generating environment..."
|
|
322
|
+
state.generating = True
|
|
323
|
+
|
|
324
|
+
env_prompt = f"""Create a vivid, metaphorical environment for wandering through while exploring:
|
|
325
|
+
"{problem}"
|
|
326
|
+
|
|
327
|
+
The environment should:
|
|
328
|
+
1. Have distinct regions that map to aspects of the problem
|
|
329
|
+
2. Include sensory details (sights, sounds, textures)
|
|
330
|
+
3. Feel alive and explorable
|
|
331
|
+
4. Be described in 4-6 evocative sentences
|
|
332
|
+
|
|
333
|
+
Respond with only the description."""
|
|
334
|
+
|
|
335
|
+
resp = get_llm_response(env_prompt, model=model, provider=provider, temperature=0.8, npc=npc)
|
|
336
|
+
state.environment = str(resp.get('response', 'A vast conceptual landscape stretches before you.'))
|
|
337
|
+
state.status = "Ready"
|
|
338
|
+
state.generating = False
|
|
339
|
+
|
|
340
|
+
def run_one_stream():
|
|
341
|
+
"""Original algorithm: generate with random word-count cutoff, subsample, extract samples."""
|
|
342
|
+
# Alternate temperature
|
|
343
|
+
if len(state.streams) % 2 == 0:
|
|
344
|
+
state.current_temp = low_temp
|
|
345
|
+
is_high = False
|
|
346
|
+
else:
|
|
347
|
+
state.current_temp = high_temp
|
|
348
|
+
is_high = True
|
|
349
|
+
|
|
350
|
+
temp = state.current_temp
|
|
351
|
+
word_limit = random.randint(n_min, n_max)
|
|
352
|
+
state.status = f"Stream {len(state.streams)+1} (T={temp:.1f}, ~{word_limit}w)..."
|
|
353
|
+
state.generating = True
|
|
354
|
+
|
|
355
|
+
# Build prompt
|
|
356
|
+
if is_high:
|
|
357
|
+
# High-temp: use subsample seeds from previous stream
|
|
358
|
+
seeds = []
|
|
359
|
+
if state.streams:
|
|
360
|
+
prev = state.streams[-1]
|
|
361
|
+
seeds = subsample_words(prev.get('insight', ''), k=20)
|
|
362
|
+
seed_text = ' '.join(seeds) if seeds else problem
|
|
363
|
+
|
|
364
|
+
sys_prompt = "Just generate without thinking. Let words and ideas flow freely. Do not filter or organize."
|
|
365
|
+
wander_prompt = f"""Seeds: {seed_text}
|
|
366
|
+
|
|
367
|
+
Context: {state.environment or 'a conceptual landscape'}
|
|
368
|
+
Problem: "{problem}"
|
|
369
|
+
|
|
370
|
+
Generate freely from these seeds. Follow any tangent. No structure needed."""
|
|
371
|
+
else:
|
|
372
|
+
# Low-temp: focused exploration
|
|
373
|
+
recent = state.streams[-3:] if state.streams else []
|
|
374
|
+
recent_context = "\n".join([s.get('insight', '')[:200] for s in recent]) if recent else "Starting fresh"
|
|
375
|
+
|
|
376
|
+
sys_prompt = None
|
|
377
|
+
wander_prompt = f"""You are wandering through: {state.environment or 'a conceptual landscape'}
|
|
378
|
+
|
|
379
|
+
Problem: "{problem}"
|
|
380
|
+
|
|
381
|
+
Recent thoughts: {recent_context}
|
|
382
|
+
|
|
383
|
+
In this exploration:
|
|
384
|
+
- Let associations flow freely
|
|
385
|
+
- Notice unexpected connections
|
|
386
|
+
- Follow interesting tangents
|
|
387
|
+
- Share what emerges
|
|
388
|
+
|
|
389
|
+
Respond naturally, 2-4 paragraphs."""
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
kwargs = dict(model=model, provider=provider, temperature=temp, npc=npc)
|
|
393
|
+
if sys_prompt:
|
|
394
|
+
kwargs['system_prompt'] = sys_prompt
|
|
395
|
+
resp = get_llm_response(wander_prompt, **kwargs)
|
|
396
|
+
insight = str(resp.get('response', ''))
|
|
397
|
+
except Exception as e:
|
|
398
|
+
insight = "Error: " + str(e)
|
|
399
|
+
|
|
400
|
+
# Probabilistic interruption: truncate at random word count
|
|
401
|
+
if random.random() < interruption_likelihood:
|
|
402
|
+
word_limit = random.randint(n_min // 2, n_min)
|
|
403
|
+
insight = truncate_to_word_count(insight, word_limit)
|
|
404
|
+
|
|
405
|
+
# Extract samples
|
|
406
|
+
words = insight.split()
|
|
407
|
+
samples = extract_samples(insight, sample_rate)
|
|
408
|
+
state.all_samples.extend(samples)
|
|
409
|
+
|
|
410
|
+
stream_entry = {
|
|
411
|
+
'temp': temp,
|
|
412
|
+
'insight': insight,
|
|
413
|
+
'words': words,
|
|
414
|
+
'samples': samples,
|
|
415
|
+
'event': None,
|
|
416
|
+
'starred': False,
|
|
417
|
+
'timestamp': datetime.now().isoformat()
|
|
418
|
+
}
|
|
419
|
+
state.streams.append(stream_entry)
|
|
420
|
+
state.last_output = insight
|
|
421
|
+
|
|
422
|
+
# After high-temp streams, maybe inject an event
|
|
423
|
+
if is_high and include_events and len(state.events) < num_events:
|
|
424
|
+
if random.random() < 0.3:
|
|
425
|
+
_trigger_event_sync()
|
|
426
|
+
|
|
427
|
+
state.scroll_offset = 0
|
|
428
|
+
state.generating = False
|
|
429
|
+
state.status = f"Ready. {len(state.streams)} streams, {len(state.all_samples)} samples"
|
|
430
|
+
|
|
431
|
+
def _trigger_event_sync():
|
|
432
|
+
event_type = weighted_event_type()
|
|
433
|
+
event_prompt = f"""In the environment: {state.environment or 'a conceptual landscape'}
|
|
434
|
+
While exploring "{problem}", a {event_type} occurs.
|
|
435
|
+
|
|
436
|
+
Describe this {event_type} in 2-3 vivid sentences.
|
|
437
|
+
Make it metaphorical and thought-provoking."""
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
resp = get_llm_response(event_prompt, model=model, provider=provider, temperature=1.0, npc=npc)
|
|
441
|
+
event_text = str(resp.get('response', ''))
|
|
442
|
+
except Exception as e:
|
|
443
|
+
event_text = f"A {event_type} occurred but faded before you could grasp it."
|
|
444
|
+
|
|
445
|
+
state.events.append({'type': event_type, 'text': event_text})
|
|
446
|
+
if state.streams:
|
|
447
|
+
state.streams[-1]['event'] = {'type': event_type, 'text': event_text}
|
|
448
|
+
state.last_output = f"[{event_type.upper()}]\n\n{event_text}"
|
|
449
|
+
|
|
450
|
+
def trigger_event():
|
|
451
|
+
if state.generating:
|
|
452
|
+
return
|
|
453
|
+
state.status = "Event occurring..."
|
|
454
|
+
state.generating = True
|
|
455
|
+
_trigger_event_sync()
|
|
456
|
+
state.scroll_offset = 0
|
|
457
|
+
state.status = "Ready"
|
|
458
|
+
state.generating = False
|
|
459
|
+
|
|
460
|
+
def toggle_temp():
|
|
461
|
+
if state.current_temp == high_temp:
|
|
462
|
+
state.current_temp = low_temp
|
|
463
|
+
else:
|
|
464
|
+
state.current_temp = high_temp
|
|
465
|
+
|
|
466
|
+
def star_current():
|
|
467
|
+
if state.streams and not state.streams[-1].get('starred'):
|
|
468
|
+
state.streams[-1]['starred'] = True
|
|
469
|
+
state.starred.append(state.streams[-1])
|
|
470
|
+
state.status = "★ Starred!"
|
|
471
|
+
|
|
472
|
+
def enter_review():
|
|
473
|
+
"""Enter review mode where user selects insights for synthesis."""
|
|
474
|
+
if not state.streams:
|
|
475
|
+
state.status = "No streams to review"
|
|
476
|
+
return
|
|
477
|
+
state.review_mode = True
|
|
478
|
+
state.review_items = []
|
|
479
|
+
for i, s in enumerate(state.streams):
|
|
480
|
+
preview = s.get('insight', '')[:120].replace('\n', ' ')
|
|
481
|
+
state.review_items.append({
|
|
482
|
+
'idx': i,
|
|
483
|
+
'text_preview': f"[T={s.get('temp', 0):.1f}] {preview}",
|
|
484
|
+
'selected': s.get('starred', False),
|
|
485
|
+
})
|
|
486
|
+
# Default: select starred + random sample
|
|
487
|
+
n_select = max(1, min(len(state.review_items), int(len(state.review_items) * sample_rate)))
|
|
488
|
+
state.review_sample_count = n_select
|
|
489
|
+
# Auto-select starred ones, then fill randomly
|
|
490
|
+
selected_idxs = set()
|
|
491
|
+
for item in state.review_items:
|
|
492
|
+
if state.streams[item['idx']].get('starred'):
|
|
493
|
+
item['selected'] = True
|
|
494
|
+
selected_idxs.add(item['idx'])
|
|
495
|
+
remaining = [item for item in state.review_items if item['idx'] not in selected_idxs]
|
|
496
|
+
n_random = max(0, n_select - len(selected_idxs))
|
|
497
|
+
if remaining and n_random > 0:
|
|
498
|
+
for item in random.sample(remaining, min(n_random, len(remaining))):
|
|
499
|
+
item['selected'] = True
|
|
500
|
+
state.review_cursor = 0
|
|
501
|
+
state.review_scroll = 0
|
|
502
|
+
|
|
503
|
+
def synthesize_from_review():
|
|
504
|
+
"""Synthesize using the selected review items."""
|
|
505
|
+
selected = [state.review_items[i] for i in range(len(state.review_items)) if state.review_items[i]['selected']]
|
|
506
|
+
if not selected:
|
|
507
|
+
state.status = "No insights selected"
|
|
508
|
+
return
|
|
509
|
+
|
|
510
|
+
state.review_mode = False
|
|
511
|
+
state.status = "Synthesizing..."
|
|
512
|
+
state.generating = True
|
|
513
|
+
|
|
514
|
+
selected_insights = []
|
|
515
|
+
for item in selected:
|
|
516
|
+
idx = item['idx']
|
|
517
|
+
selected_insights.append(state.streams[idx].get('insight', ''))
|
|
518
|
+
|
|
519
|
+
# Collect samples from selected streams
|
|
520
|
+
selected_samples = []
|
|
521
|
+
for item in selected:
|
|
522
|
+
idx = item['idx']
|
|
523
|
+
selected_samples.extend(state.streams[idx].get('samples', []))
|
|
524
|
+
|
|
525
|
+
sample_text = ' '.join(selected_samples[:50]) if selected_samples else 'N/A'
|
|
526
|
+
|
|
527
|
+
synth_prompt = f"""You are synthesizing a wandering exploration of: "{problem}"
|
|
528
|
+
|
|
529
|
+
Environment traversed: {state.environment or 'a conceptual landscape'}
|
|
530
|
+
|
|
531
|
+
Selected explorations ({len(selected)} of {len(state.streams)} streams):
|
|
532
|
+
{"---".join(selected_insights)}
|
|
533
|
+
|
|
534
|
+
Word samples extracted during wandering: {sample_text}
|
|
535
|
+
|
|
536
|
+
Events encountered: {'; '.join([e['type'] + ': ' + e['text'][:100] for e in state.events]) if state.events else 'None'}
|
|
537
|
+
|
|
538
|
+
From these wanderings, synthesize creative hypotheses:
|
|
539
|
+
1. What unexpected patterns emerge from the word samples?
|
|
540
|
+
2. What creative hypotheses can you form by connecting disparate ideas?
|
|
541
|
+
3. What questions have emerged that weren't visible at the start?
|
|
542
|
+
4. What surprising connections exist between the different temperature explorations?
|
|
543
|
+
|
|
544
|
+
Be bold, creative, and insightful. These hypotheses should feel like discoveries, not summaries."""
|
|
545
|
+
|
|
546
|
+
try:
|
|
547
|
+
resp = get_llm_response(synth_prompt, model=model, provider=provider, temperature=0.4, npc=npc)
|
|
548
|
+
synthesis = str(resp.get('response', ''))
|
|
549
|
+
except Exception as e:
|
|
550
|
+
synthesis = "Error during synthesis: " + str(e)
|
|
551
|
+
|
|
552
|
+
state.last_output = "=== SYNTHESIS ===\n\n" + synthesis
|
|
553
|
+
state.scroll_offset = 0
|
|
554
|
+
state.current_panel = 0
|
|
555
|
+
state.status = "Ready"
|
|
556
|
+
state.generating = False
|
|
557
|
+
|
|
558
|
+
# ========== Auto-run ==========
|
|
559
|
+
def auto_run():
|
|
560
|
+
if not environment:
|
|
561
|
+
generate_environment()
|
|
562
|
+
else:
|
|
563
|
+
state.environment = environment
|
|
564
|
+
|
|
565
|
+
total_streams = n_high_temp_streams * 2 # low + high alternating
|
|
566
|
+
for i in range(total_streams):
|
|
567
|
+
if state.quit_requested:
|
|
568
|
+
break
|
|
569
|
+
run_one_stream()
|
|
570
|
+
|
|
571
|
+
if not state.quit_requested:
|
|
572
|
+
state.auto_done = True
|
|
573
|
+
state.status = f"Done! {len(state.streams)} streams. R=Review, SPACE=more, q=quit"
|
|
574
|
+
|
|
575
|
+
# ========== Main Loop ==========
|
|
576
|
+
fd = sys.stdin.fileno()
|
|
577
|
+
old_settings = termios.tcgetattr(fd)
|
|
578
|
+
import select as _sel
|
|
579
|
+
|
|
580
|
+
try:
|
|
581
|
+
tty.setcbreak(fd)
|
|
582
|
+
sys.stdout.write('\033[?25l')
|
|
583
|
+
sys.stdout.flush()
|
|
584
|
+
|
|
585
|
+
render_screen()
|
|
586
|
+
|
|
587
|
+
auto_thread = threading.Thread(target=auto_run, daemon=True)
|
|
588
|
+
auto_thread.start()
|
|
589
|
+
|
|
590
|
+
while True:
|
|
591
|
+
if _sel.select([fd], [], [], 0.3)[0]:
|
|
592
|
+
c = os.read(fd, 1).decode('latin-1')
|
|
593
|
+
|
|
594
|
+
if state.review_mode:
|
|
595
|
+
# Review mode input
|
|
596
|
+
if state.editing_count:
|
|
597
|
+
if c in ('\r', '\n'):
|
|
598
|
+
try:
|
|
599
|
+
val = int(state.count_buf)
|
|
600
|
+
state.review_sample_count = max(0, min(val, len(state.review_items)))
|
|
601
|
+
except:
|
|
602
|
+
pass
|
|
603
|
+
state.editing_count = False
|
|
604
|
+
elif c == '\x1b':
|
|
605
|
+
state.editing_count = False
|
|
606
|
+
elif c == '\x7f' or c == '\x08':
|
|
607
|
+
state.count_buf = state.count_buf[:-1]
|
|
608
|
+
elif c.isdigit():
|
|
609
|
+
state.count_buf += c
|
|
610
|
+
else:
|
|
611
|
+
if c == '\x1b':
|
|
612
|
+
if _sel.select([fd], [], [], 0.05)[0]:
|
|
613
|
+
c2 = os.read(fd, 1).decode('latin-1')
|
|
614
|
+
if c2 == '[':
|
|
615
|
+
c3 = os.read(fd, 1).decode('latin-1')
|
|
616
|
+
if c3 == 'A':
|
|
617
|
+
state.review_cursor = max(0, state.review_cursor - 1)
|
|
618
|
+
elif c3 == 'B':
|
|
619
|
+
state.review_cursor = min(len(state.review_items) - 1, state.review_cursor + 1)
|
|
620
|
+
else:
|
|
621
|
+
state.review_mode = False
|
|
622
|
+
elif c == 'j':
|
|
623
|
+
state.review_cursor = min(len(state.review_items) - 1, state.review_cursor + 1)
|
|
624
|
+
elif c == 'k':
|
|
625
|
+
state.review_cursor = max(0, state.review_cursor - 1)
|
|
626
|
+
elif c == ' ':
|
|
627
|
+
if state.review_items:
|
|
628
|
+
state.review_items[state.review_cursor]['selected'] = not state.review_items[state.review_cursor]['selected']
|
|
629
|
+
elif c == 'a':
|
|
630
|
+
for item in state.review_items:
|
|
631
|
+
item['selected'] = True
|
|
632
|
+
elif c == 'x':
|
|
633
|
+
for item in state.review_items:
|
|
634
|
+
item['selected'] = False
|
|
635
|
+
elif c == 'n':
|
|
636
|
+
state.editing_count = True
|
|
637
|
+
state.count_buf = str(state.review_sample_count)
|
|
638
|
+
elif c in ('\r', '\n'):
|
|
639
|
+
threading.Thread(target=synthesize_from_review, daemon=True).start()
|
|
640
|
+
elif c == 'q':
|
|
641
|
+
state.quit_requested = True
|
|
642
|
+
break
|
|
643
|
+
# Keep cursor in scroll view
|
|
644
|
+
_, h = get_size()
|
|
645
|
+
view_h = h - 9
|
|
646
|
+
if state.review_cursor < state.review_scroll:
|
|
647
|
+
state.review_scroll = state.review_cursor
|
|
648
|
+
elif state.review_cursor >= state.review_scroll + view_h:
|
|
649
|
+
state.review_scroll = state.review_cursor - view_h + 1
|
|
650
|
+
else:
|
|
651
|
+
# Explore mode input
|
|
652
|
+
if c == 'q' or c == '\x03':
|
|
653
|
+
state.quit_requested = True
|
|
654
|
+
break
|
|
655
|
+
elif c == ' ':
|
|
656
|
+
if not state.generating:
|
|
657
|
+
threading.Thread(target=run_one_stream, daemon=True).start()
|
|
658
|
+
elif c == 't':
|
|
659
|
+
toggle_temp()
|
|
660
|
+
elif c == 'e':
|
|
661
|
+
if not state.generating:
|
|
662
|
+
threading.Thread(target=trigger_event, daemon=True).start()
|
|
663
|
+
elif c == 's':
|
|
664
|
+
star_current()
|
|
665
|
+
elif c == 'R' or c == 'r':
|
|
666
|
+
if not state.generating:
|
|
667
|
+
enter_review()
|
|
668
|
+
elif c == '\t':
|
|
669
|
+
state.current_panel = (state.current_panel + 1) % 3
|
|
670
|
+
state.scroll_offset = 0
|
|
671
|
+
elif c == 'j':
|
|
672
|
+
state.scroll_offset += 1
|
|
673
|
+
elif c == 'k':
|
|
674
|
+
state.scroll_offset = max(0, state.scroll_offset - 1)
|
|
675
|
+
elif c == '\x1b':
|
|
676
|
+
if _sel.select([fd], [], [], 0.05)[0]:
|
|
677
|
+
c2 = os.read(fd, 1).decode('latin-1')
|
|
678
|
+
if c2 == '[':
|
|
679
|
+
c3 = os.read(fd, 1).decode('latin-1')
|
|
680
|
+
if c3 == 'A':
|
|
681
|
+
state.scroll_offset = max(0, state.scroll_offset - 1)
|
|
682
|
+
elif c3 == 'B':
|
|
683
|
+
state.scroll_offset += 1
|
|
684
|
+
elif c3 == 'C':
|
|
685
|
+
state.current_panel = (state.current_panel + 1) % 3
|
|
686
|
+
state.scroll_offset = 0
|
|
687
|
+
elif c3 == 'D':
|
|
688
|
+
state.current_panel = (state.current_panel - 1) % 3
|
|
689
|
+
state.scroll_offset = 0
|
|
690
|
+
else:
|
|
691
|
+
state.quit_requested = True
|
|
692
|
+
break
|
|
693
|
+
|
|
694
|
+
render_screen()
|
|
695
|
+
|
|
696
|
+
finally:
|
|
697
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
698
|
+
sys.stdout.write('\033[?25h')
|
|
699
|
+
sys.stdout.write('\033[2J\033[H')
|
|
700
|
+
sys.stdout.flush()
|
|
701
|
+
|
|
702
|
+
if state.streams:
|
|
703
|
+
print(colored("=== WANDER SESSION COMPLETE ===\n", "green"))
|
|
704
|
+
print(f"Problem: {problem}")
|
|
705
|
+
print(f"Streams: {len(state.streams)}")
|
|
706
|
+
print(f"Samples: {len(state.all_samples)}")
|
|
707
|
+
print(f"Starred: {len(state.starred)}")
|
|
708
|
+
print(f"Events: {len(state.events)}\n")
|
|
709
|
+
|
|
710
|
+
if state.starred:
|
|
711
|
+
print(colored("Starred Insights:", "yellow"))
|
|
712
|
+
for i, s in enumerate(state.starred):
|
|
713
|
+
print(f"\n{i+1}. [T={s.get('temp', 0):.1f}] {s.get('insight', '')[:300]}...")
|
|
714
|
+
|
|
715
|
+
if state.last_output and "SYNTHESIS" in state.last_output:
|
|
716
|
+
print(colored("\n--- Final Synthesis ---", "cyan"))
|
|
717
|
+
print(state.last_output)
|
|
718
|
+
|
|
719
|
+
context['output'] = state.last_output
|
|
720
|
+
context['messages'] = messages
|
|
721
|
+
context['wander_result'] = {
|
|
722
|
+
'problem': problem,
|
|
723
|
+
'environment': state.environment,
|
|
724
|
+
'streams': state.streams,
|
|
725
|
+
'starred': state.starred,
|
|
726
|
+
'events': state.events,
|
|
727
|
+
'samples': state.all_samples,
|
|
728
|
+
}
|