Compare commits
2 Commits
b6cecd686d
...
558d6020c5
| Author | SHA1 | Date | |
|---|---|---|---|
| 558d6020c5 | |||
| 44fca6c366 |
20
hendrik.py
20
hendrik.py
@ -1,4 +1,4 @@
|
|||||||
import os, sys, argparse, threading, time, signal
|
import os, sys, threading, time, signal
|
||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
@ -11,6 +11,8 @@ from services.telegram_client import TelegramClient
|
|||||||
|
|
||||||
from interfaces.tui import HendrikTUI
|
from interfaces.tui import HendrikTUI
|
||||||
|
|
||||||
|
from modules import argsparser
|
||||||
|
|
||||||
tools_definition = [
|
tools_definition = [
|
||||||
# Coder Tools
|
# Coder Tools
|
||||||
gadget.tools_mapping( schema = coder.schema_read_file, handler = coder.read_file ),
|
gadget.tools_mapping( schema = coder.schema_read_file, handler = coder.read_file ),
|
||||||
@ -71,16 +73,12 @@ TOOLS = gadget.tool_schemas (tools_definition)
|
|||||||
TOOL_HANDLERS = gadget.tool_handlers (tools_definition)
|
TOOL_HANDLERS = gadget.tool_handlers (tools_definition)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Hendrik AI Agent")
|
|
||||||
parser.add_argument('--char', type=str, default=None, help='Character name (overrides config.yaml)')
|
|
||||||
parser.add_argument('-w', '--workspace', type=str, default=None, help='Working directory')
|
|
||||||
parser.add_argument('--tui', action='store_true', help='Force run in TUI mode')
|
|
||||||
parser.add_argument('--service', action='store_true', help='Force run in service mode')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
args = argsparser.args
|
||||||
|
# Override char configuration from args
|
||||||
if args.char:
|
if args.char:
|
||||||
config.load_service_config(char_override=args.char)
|
config.load_service_config(char_override=args.char)
|
||||||
|
# Set workspace from args
|
||||||
if args.workspace:
|
if args.workspace:
|
||||||
resolved = os.path.abspath(args.workspace)
|
resolved = os.path.abspath(args.workspace)
|
||||||
if not os.path.isdir(resolved):
|
if not os.path.isdir(resolved):
|
||||||
@ -88,7 +86,10 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
coder.set_current_workspace(resolved)
|
coder.set_current_workspace(resolved)
|
||||||
|
|
||||||
|
# Inference
|
||||||
llm_client = LLMClient("", "", "", config.llm_timeout)
|
llm_client = LLMClient("", "", "", config.llm_timeout)
|
||||||
|
|
||||||
|
# Pesan saat opsi model belum dipilih
|
||||||
if not moop.configure_client(llm_client):
|
if not moop.configure_client(llm_client):
|
||||||
if config.XMPP_ENABLED or config.TELEGRAM_ENABLED:
|
if config.XMPP_ENABLED or config.TELEGRAM_ENABLED:
|
||||||
print(
|
print(
|
||||||
@ -145,8 +146,7 @@ def main():
|
|||||||
return
|
return
|
||||||
|
|
||||||
if args.service:
|
if args.service:
|
||||||
# Force service mode by ensuring services list is populated if any are enabled in config
|
pass # Ya kocak sih, pakai --service dan ga pake --service jalannya ya sama-sama aja.
|
||||||
pass # Fall through to the services logic below
|
|
||||||
|
|
||||||
if services:
|
if services:
|
||||||
threads = []
|
threads = []
|
||||||
|
|||||||
@ -100,7 +100,8 @@ def _agent_loop(app):
|
|||||||
for step in range(app.agent_max_iterations):
|
for step in range(app.agent_max_iterations):
|
||||||
stamp_step = ntro.start()
|
stamp_step = ntro.start()
|
||||||
log(app, "system", f" step {step + 1} \u2014 Thinking...")
|
log(app, "system", f" step {step + 1} \u2014 Thinking...")
|
||||||
app.scroll = 999999
|
if app.scroll_follow:
|
||||||
|
app.scroll = 999999
|
||||||
|
|
||||||
# Streaming response
|
# Streaming response
|
||||||
stream_buffer = []
|
stream_buffer = []
|
||||||
@ -121,7 +122,8 @@ def _agent_loop(app):
|
|||||||
if app.log[i].get('role') == 'ai' and (app.log[i].get('text') == placeholder_marker or (i > 0 and app.log[i-1].get('role') == 'system' and 'Thinking' in app.log[i-1].get('text', ''))):
|
if app.log[i].get('role') == 'ai' and (app.log[i].get('text') == placeholder_marker or (i > 0 and app.log[i-1].get('role') == 'system' and 'Thinking' in app.log[i-1].get('text', ''))):
|
||||||
app.log[i]['text'] = current_text
|
app.log[i]['text'] = current_text
|
||||||
break
|
break
|
||||||
app.scroll = 999999
|
if app.scroll_follow:
|
||||||
|
app.scroll = 999999
|
||||||
|
|
||||||
skill = config.AGENT_SKILLS
|
skill = config.AGENT_SKILLS
|
||||||
tool_reminder = None
|
tool_reminder = None
|
||||||
@ -189,7 +191,8 @@ def _agent_loop(app):
|
|||||||
"name": tname,
|
"name": tname,
|
||||||
"arguments": targs,
|
"arguments": targs,
|
||||||
}))
|
}))
|
||||||
app.scroll = 999999
|
if app.scroll_follow:
|
||||||
|
app.scroll = 999999
|
||||||
result = agent_loop.execute_tool(tc, app.TOOL_HANDLERS)
|
result = agent_loop.execute_tool(tc, app.TOOL_HANDLERS)
|
||||||
if isinstance(result, ImagePayload):
|
if isinstance(result, ImagePayload):
|
||||||
_add_msg(app, "tool", [
|
_add_msg(app, "tool", [
|
||||||
|
|||||||
@ -34,6 +34,7 @@ class HendrikTUI:
|
|||||||
self.input_line = 0
|
self.input_line = 0
|
||||||
self.input_col = 0
|
self.input_col = 0
|
||||||
self.scroll = 0
|
self.scroll = 0
|
||||||
|
self.scroll_follow = True # follow bottom saat streaming, mati kalau user scroll up
|
||||||
self.processing = False
|
self.processing = False
|
||||||
self.running = True
|
self.running = True
|
||||||
self.h, self.w = 0, 0
|
self.h, self.w = 0, 0
|
||||||
|
|||||||
@ -49,8 +49,10 @@ def handle_key(app, stdscr, key):
|
|||||||
# -- Scroll & resize --
|
# -- Scroll & resize --
|
||||||
if key == curses.KEY_PPAGE:
|
if key == curses.KEY_PPAGE:
|
||||||
app.scroll = max(0, app.scroll - (app.h - 10) // 2)
|
app.scroll = max(0, app.scroll - (app.h - 10) // 2)
|
||||||
|
app.scroll_follow = False
|
||||||
elif key == curses.KEY_NPAGE:
|
elif key == curses.KEY_NPAGE:
|
||||||
app.scroll += (app.h - 10) // 2
|
app.scroll += (app.h - 10) // 2
|
||||||
|
app.scroll_follow = True
|
||||||
elif key == curses.KEY_RESIZE:
|
elif key == curses.KEY_RESIZE:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@ -94,6 +94,16 @@ _CSI_NAV = {
|
|||||||
"5~": KEY_PPAGE, "6~": KEY_NPAGE,
|
"5~": KEY_PPAGE, "6~": KEY_NPAGE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Versi numerik (kitty CSI-u / tilde dengan modifier eksplisit):
|
||||||
|
# \x1b[2~ / \x1b[2;1~ / \x1b[2u / \x1b[2;1u → INSERT, dst.
|
||||||
|
# Ini bentuk yang dikirim terminal ketika keyboard protocol (kitty/xterm
|
||||||
|
# modifyOtherKeys) aktif, yang sebelumnya jatuh jadi KEY_ESC.
|
||||||
|
_CSI_NAV_NUM = {
|
||||||
|
2: KEY_INSERT, 3: KEY_DC,
|
||||||
|
5: KEY_PPAGE, 6: KEY_NPAGE,
|
||||||
|
7: KEY_HOME, 8: KEY_END,
|
||||||
|
}
|
||||||
|
|
||||||
# F1-F4 bentuk huruf (kitty protocol aktif): CSI P/Q/S = F1/F2/F4.
|
# F1-F4 bentuk huruf (kitty protocol aktif): CSI P/Q/S = F1/F2/F4.
|
||||||
# F3 (huruf R) dihapus dari spec (bentrok Cursor Position Report) → pakai [13~.
|
# F3 (huruf R) dihapus dari spec (bentrok Cursor Position Report) → pakai [13~.
|
||||||
_CSI_LETTER_F = {
|
_CSI_LETTER_F = {
|
||||||
@ -173,6 +183,11 @@ def _map_modified_key(code: int, mod, terminator: str) -> int | None:
|
|||||||
return _CSI_F_KEYS["13"] # F3
|
return _CSI_F_KEYS["13"] # F3
|
||||||
return 13
|
return 13
|
||||||
|
|
||||||
|
# Navigasi numerik (Insert/Delete/PageUp/PageDown/Home/End) — terlepas
|
||||||
|
# dari terminator (u/~) dan modifier yang menyertainya.
|
||||||
|
if code in _CSI_NAV_NUM:
|
||||||
|
return _CSI_NAV_NUM[code]
|
||||||
|
|
||||||
# F1-F12 dengan/tanpa modifier
|
# F1-F12 dengan/tanpa modifier
|
||||||
if str(code) in _CSI_F_KEYS:
|
if str(code) in _CSI_F_KEYS:
|
||||||
return _CSI_F_KEYS[str(code)]
|
return _CSI_F_KEYS[str(code)]
|
||||||
|
|||||||
@ -214,6 +214,10 @@ def draw_chat(app, stdscr):
|
|||||||
if app.scroll > max_scroll:
|
if app.scroll > max_scroll:
|
||||||
app.scroll = max_scroll
|
app.scroll = max_scroll
|
||||||
app.scroll = max(0, app.scroll)
|
app.scroll = max(0, app.scroll)
|
||||||
|
# Kalau sudah mentok di bawah, kembalikan auto-follow (streaming akan
|
||||||
|
# menempel di bottom lagi; PgUp keluar dari mode follow).
|
||||||
|
if app.scroll >= max_scroll:
|
||||||
|
app.scroll_follow = True
|
||||||
|
|
||||||
# Gambar baris yang terlihat (scroll sampai scroll + chat_h)
|
# Gambar baris yang terlihat (scroll sampai scroll + chat_h)
|
||||||
# Clear area chat dulu biar nggak ada text lama yang nyisa
|
# Clear area chat dulu biar nggak ada text lama yang nyisa
|
||||||
|
|||||||
8
modules/argsparser.py
Normal file
8
modules/argsparser.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Hendrik AI Agent")
|
||||||
|
parser.add_argument('--char', type=str, default=None, help='Character name (overrides config.yaml)')
|
||||||
|
parser.add_argument('-w', '--workspace', type=str, default=None, help='Working directory')
|
||||||
|
parser.add_argument('--tui', action='store_true', help='Force run in TUI mode')
|
||||||
|
parser.add_argument('--service', action='store_true', help='Force run in service mode')
|
||||||
|
args = parser.parse_args()
|
||||||
Loading…
Reference in New Issue
Block a user