personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 145 lines 4.3 kB view raw
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4"""CLI command for chatting with the journal agent.""" 5 6from __future__ import annotations 7 8import argparse 9import sys 10import threading 11 12from think.callosum import CallosumConnection 13from think.cortex_client import cortex_request, read_use_events 14from think.utils import require_solstone, setup_cli 15 16 17def main() -> None: 18 """Entry point for ``sol chat``.""" 19 parser = argparse.ArgumentParser( 20 prog="sol chat", 21 description="Chat with your journal", 22 ) 23 parser.add_argument("message", nargs="*", help="Chat message") 24 parser.add_argument("--facet", help="Facet context") 25 parser.add_argument("--provider", help="AI provider override") 26 parser.add_argument( 27 "--talent", default="chat", help="Talent agent name (default: chat)" 28 ) 29 args = setup_cli(parser) 30 require_solstone() 31 32 from think.identity import ensure_identity_directory 33 34 ensure_identity_directory() 35 36 if not args.message: 37 parser.print_help() 38 return 39 40 message = " ".join(args.message).strip() 41 42 config: dict[str, str] = {} 43 if args.facet: 44 config["facet"] = args.facet 45 46 use_id = cortex_request( 47 prompt=message, 48 name=args.talent, 49 provider=args.provider, 50 config=config if config else None, 51 ) 52 if use_id is None: 53 print( 54 "Error: failed to connect to cortex (is the stack running?)", 55 file=sys.stderr, 56 ) 57 sys.exit(1) 58 59 result: dict[str, str] = {} 60 done = threading.Event() 61 listener = CallosumConnection() 62 63 def on_event(msg: dict) -> None: 64 if msg.get("tract") != "cortex": 65 return 66 if msg.get("use_id") != use_id: 67 return 68 69 event_type = msg.get("event") 70 if event_type == "start": 71 if args.verbose: 72 print( 73 f"Agent started (model={msg.get('model')}, provider={msg.get('provider')})", 74 file=sys.stderr, 75 ) 76 elif event_type == "thinking": 77 if args.verbose: 78 print( 79 f"Thinking: {msg.get('summary', '')[:200]}", 80 file=sys.stderr, 81 ) 82 elif event_type == "tool_start": 83 if args.verbose: 84 print(f"Tool: {msg.get('tool', 'unknown')}", file=sys.stderr) 85 elif event_type == "tool_end": 86 if args.verbose: 87 print(f"Tool done: {msg.get('tool', '')}", file=sys.stderr) 88 elif event_type == "finish": 89 result["text"] = msg.get("result", "") 90 done.set() 91 elif event_type == "error": 92 result["error"] = msg.get("error", "Unknown error") 93 done.set() 94 95 listener.start(callback=on_event) 96 97 if not args.verbose: 98 print("Thinking...", end="", file=sys.stderr, flush=True) 99 100 try: 101 done.wait(timeout=600) 102 except KeyboardInterrupt: 103 print("\nInterrupted.", file=sys.stderr) 104 listener.stop() 105 sys.exit(1) 106 107 listener.stop() 108 109 if not args.verbose: 110 print("\r \r", end="", file=sys.stderr, flush=True) 111 112 if "error" in result: 113 print(f"Error: {result['error']}", file=sys.stderr) 114 sys.exit(1) 115 116 if "text" in result and result["text"].strip(): 117 print(result["text"]) 118 return 119 120 if "text" in result: 121 print("Error: agent returned an empty result.", file=sys.stderr) 122 sys.exit(1) 123 124 try: 125 events = read_use_events(use_id) 126 for event in reversed(events): 127 event_type = event.get("event") 128 if event_type == "finish": 129 text = event.get("result", "") 130 if str(text).strip(): 131 print(text) 132 return 133 print("Error: agent returned an empty result.", file=sys.stderr) 134 sys.exit(1) 135 if event_type == "error": 136 print( 137 f"Error: {event.get('error', 'Unknown error')}", 138 file=sys.stderr, 139 ) 140 sys.exit(1) 141 except FileNotFoundError: 142 pass 143 144 print("Error: request timed out.", file=sys.stderr) 145 sys.exit(1)