prompt_objects 0.1.0 → 0.3.0

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +2 -2
  5. data/exe/prompt_objects +387 -1
  6. data/frontend/src/App.tsx +12 -3
  7. data/frontend/src/components/CapabilitiesPanel.tsx +122 -25
  8. data/frontend/src/components/ChatPanel.tsx +43 -6
  9. data/frontend/src/components/ContextMenu.tsx +67 -0
  10. data/frontend/src/components/MessageBus.tsx +4 -3
  11. data/frontend/src/components/ModelSelector.tsx +5 -1
  12. data/frontend/src/components/PODetail.tsx +8 -1
  13. data/frontend/src/components/PromptPanel.tsx +124 -19
  14. data/frontend/src/components/ThreadsSidebar.tsx +46 -2
  15. data/frontend/src/components/UsagePanel.tsx +105 -0
  16. data/frontend/src/hooks/useWebSocket.ts +69 -0
  17. data/frontend/src/store/index.ts +10 -0
  18. data/frontend/src/types/index.ts +16 -2
  19. data/lib/prompt_objects/cli.rb +1 -0
  20. data/lib/prompt_objects/connectors/mcp.rb +1 -0
  21. data/lib/prompt_objects/environment.rb +35 -1
  22. data/lib/prompt_objects/llm/anthropic_adapter.rb +15 -1
  23. data/lib/prompt_objects/llm/factory.rb +93 -6
  24. data/lib/prompt_objects/llm/gemini_adapter.rb +13 -1
  25. data/lib/prompt_objects/llm/openai_adapter.rb +21 -4
  26. data/lib/prompt_objects/llm/pricing.rb +49 -0
  27. data/lib/prompt_objects/llm/response.rb +3 -2
  28. data/lib/prompt_objects/mcp/server.rb +1 -0
  29. data/lib/prompt_objects/message_bus.rb +27 -8
  30. data/lib/prompt_objects/prompt_object.rb +15 -3
  31. data/lib/prompt_objects/server/api/routes.rb +186 -29
  32. data/lib/prompt_objects/server/public/assets/index-Bkme6COu.css +1 -0
  33. data/lib/prompt_objects/server/public/assets/index-CQ7lVDF_.js +77 -0
  34. data/lib/prompt_objects/server/public/index.html +2 -2
  35. data/lib/prompt_objects/server/websocket_handler.rb +160 -12
  36. data/lib/prompt_objects/server.rb +67 -0
  37. data/lib/prompt_objects/session/store.rb +399 -4
  38. data/lib/prompt_objects/universal/add_capability.rb +6 -1
  39. data/lib/prompt_objects/universal/add_primitive.rb +6 -1
  40. data/lib/prompt_objects/universal/create_capability.rb +4 -0
  41. data/lib/prompt_objects/universal/create_primitive.rb +4 -0
  42. data/lib/prompt_objects/universal/delete_primitive.rb +77 -0
  43. data/lib/prompt_objects/universal/modify_prompt.rb +164 -0
  44. data/lib/prompt_objects/universal/remove_capability.rb +73 -0
  45. data/lib/prompt_objects.rb +5 -1
  46. data/prompt_objects.gemspec +1 -1
  47. data/templates/arc-agi-1/manifest.yml +22 -0
  48. data/templates/arc-agi-1/objects/data_manager.md +42 -0
  49. data/templates/arc-agi-1/objects/observer.md +100 -0
  50. data/templates/arc-agi-1/objects/solver.md +118 -0
  51. data/templates/arc-agi-1/objects/verifier.md +79 -0
  52. data/templates/arc-agi-1/primitives/check_arc_data.rb +53 -0
  53. data/templates/arc-agi-1/primitives/find_objects.rb +72 -0
  54. data/templates/arc-agi-1/primitives/grid_diff.rb +70 -0
  55. data/templates/arc-agi-1/primitives/grid_info.rb +42 -0
  56. data/templates/arc-agi-1/primitives/grid_transform.rb +50 -0
  57. data/templates/arc-agi-1/primitives/load_arc_task.rb +68 -0
  58. data/templates/arc-agi-1/primitives/render_grid.rb +78 -0
  59. data/templates/arc-agi-1/primitives/test_solution.rb +131 -0
  60. metadata +23 -3
  61. data/lib/prompt_objects/server/public/assets/index-2acS2FYZ.js +0 -77
  62. data/lib/prompt_objects/server/public/assets/index-DXU5uRXQ.css +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ad31b1bb32afdf58701be9b0b163314ce9944a1e2df465f81540e36c5e7aa10
4
- data.tar.gz: 865fdc2ee3c868cbc48ac86e4034472e71ea12b51a31d99b71bc446a50a45eb9
3
+ metadata.gz: 676489da9fefa02b4d187f99137469c6cc0011d99e7e43b72574c0d57b19c8a7
4
+ data.tar.gz: acd7c61e837ac00485cb8286cf95caa3d2103c82d07aa5fef7ce49185a3a0f6a
5
5
  SHA512:
6
- metadata.gz: 1c1cdf27920d51f61ce89feb4df5f7eb8f6d9c37f43ba53c7578d56193fcf9e3016fc465a0d4e4fcac3a74d9c15ac09ecb3031b867f5b04ac155d2e5bb6aeb75
7
- data.tar.gz: abf9df3fff47f105b8e0b516784712c980a155f0adf1b2fc770f329aabde0144d8f4e4b139627d5cc6d734c1041abfc5dc85fc5e65f6dd4a8d023545e6f1bdcb
6
+ metadata.gz: dda01aa39c36088e3433894b004994b4a24b735bd6b59bb6ae510943be0f6f83931d1e45fa34051b4f49a7243b606b7ac71b8790f8e5701f9652373dff29d756
7
+ data.tar.gz: e02fb7e206ced06445391d81012a61d790277f4fc0f46343e3e0391f46da58aafd3a047abd13147e0c2e8fe150284b5333759f27ab1037ccb6a7e8020d18316a
data/CHANGELOG.md ADDED
@@ -0,0 +1,68 @@
1
+ # Changelog
2
+
3
+ All notable changes to PromptObjects are documented in this file.
4
+
5
+ ## [0.3.0] - 2025-02-05
6
+
7
+ ### Added
8
+
9
+ - **Token usage & cost tracking** — Track input/output tokens and estimated costs per session and across delegation trees. Includes per-model pricing table and a Usage Panel in the web UI (right-click a thread to view).
10
+ - **Ollama & OpenRouter support** — Connect to local Ollama models or OpenRouter's model marketplace. Both reuse the OpenAI adapter with configurable base URLs. Ollama models are auto-discovered from the local API.
11
+ - **Thread export** — Export any conversation thread as Markdown or JSON, including full delegation chains. Delegation sub-threads render inline next to the tool call that triggered them, preserving the actual flow of work. Available via right-click context menu or REST API.
12
+ - **ARC-AGI-1 template** — A template for solving ARC-AGI challenges with a solver PO, data manager PO, and 7 custom grid primitives (load, render, diff, info, find objects, transform, test solution).
13
+ - **Persistent event log** — Message bus events are now persisted to SQLite for replay and debugging.
14
+ - **REST message endpoint & events API** — Send messages to POs and retrieve bus events via HTTP. Includes server discovery for CLI commands.
15
+ - **CLI `message` and `events` commands** — Interact with a running environment from the command line without opening the web UI.
16
+
17
+ ### Fixed
18
+
19
+ - Custom primitives (created by POs or from templates) now auto-load on environment startup. Previously they were saved to `env/primitives/` but never registered on restart.
20
+ - Message serialization bugs that caused crashes when tool calls contained non-string values.
21
+ - Frontend auto-rebuilds when running `prompt_objects serve` in development.
22
+ - Delegation sub-threads in exports now appear inline after the triggering tool call, not at the bottom of the document.
23
+ - Tool result truncation limit increased from 2,000 to 10,000 characters to preserve detail in exports.
24
+ - Full message content stored in bus; truncation applied only at display time.
25
+
26
+ ## [0.2.0] - 2025-01-23
27
+
28
+ ### Added
29
+
30
+ - **GitHub Actions CI** — Automated test suite running on Ruby 3.2, 3.3, and 3.4.
31
+ - **Conversation threads with delegation isolation** — Each PO-to-PO delegation runs in its own thread, keeping conversations clean.
32
+ - **Thread sidebar** — Navigate between threads with auto-naming and instant feedback on creation.
33
+ - **Real-time capability updates** — Adding/removing capabilities broadcasts changes to the web UI immediately.
34
+ - **PO prompt editing** — Edit a Prompt Object's system prompt directly in the web UI with auto-save back to the markdown file.
35
+ - **`modify_prompt` universal capability** — POs can rewrite their own system prompts at runtime.
36
+ - **Environment recovery tools** — `remove_capability` and `delete_primitive` for cleaning up broken state.
37
+ - **Streaming tool calls** — Tool call chains display in real-time as they execute, not just after completion.
38
+ - **Capabilities panel** — Visual display of each PO's available primitives and PO-to-PO capabilities.
39
+ - **Core PromptObject tests** — Unit test suite for the core framework.
40
+
41
+ ### Fixed
42
+
43
+ - Session store binding and Gemini model name resolution.
44
+ - Tool results missing function name for Gemini API compatibility.
45
+ - `tool_calls` Hash vs ToolCall object handling across all adapters.
46
+ - Claude API response parsing for new PO chat updates.
47
+ - Thread switching now immediately shows the new thread on creation.
48
+ - Session message counts and cross-session response routing.
49
+
50
+ ## [0.1.0] - 2025-01-15
51
+
52
+ ### Added
53
+
54
+ - **Core framework** — Markdown files with YAML frontmatter act as LLM-backed autonomous entities.
55
+ - **Unified capability interface** — Primitives (Ruby) and Prompt Objects (Markdown) share the same `receive(message, context:)` interface.
56
+ - **Built-in primitives** — `read_file`, `list_files`, `write_file`, `http_get`.
57
+ - **Universal capabilities** — `ask_human`, `think`, `request_capability`, `create_capability`, `add_capability`.
58
+ - **Multi-provider LLM support** — OpenAI, Anthropic, and Gemini adapters with model selection UI.
59
+ - **PO-to-PO communication** — Prompt Objects can call each other as capabilities through the message bus.
60
+ - **Self-modification** — POs can create new Prompt Objects and primitives at runtime (with human approval).
61
+ - **Web UI** — React frontend with real-time WebSocket updates, split-view layout, markdown rendering.
62
+ - **Notification system** — Non-blocking human-in-the-loop via `ask_human` with notification bell and dropdown.
63
+ - **Live filesystem watching** — Changes to `.md` files in the objects directory are reflected immediately.
64
+ - **SQLite session storage** — Persistent conversation history with WAL mode for concurrent access.
65
+ - **Environment management** — Create, list, and manage isolated environments with `prompt_objects env` commands.
66
+ - **Templates** — Bootstrap new environments from templates (`basic`, `pair`, `team`, and more).
67
+ - **MCP server mode** — Expose POs as tools via the Model Context Protocol for external client integration.
68
+ - **CLI** — `prompt_objects` command with subcommands for environment management, serving, and interaction.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- prompt_objects (0.1.0)
4
+ prompt_objects (0.3.0)
5
5
  anthropic (~> 1.0)
6
6
  async-websocket (~> 0.28)
7
7
  falcon (~> 0.50)
data/README.md CHANGED
@@ -45,8 +45,8 @@ gem install prompt_objects
45
45
  # Create an environment from a template
46
46
  prompt_objects env create my-project --template basic
47
47
 
48
- # Run the web interface
49
- prompt_objects --env my-project
48
+ # Run and open the web interface
49
+ prompt_objects serve my-project --open
50
50
  ```
51
51
 
52
52
  ### Environment Commands
data/exe/prompt_objects CHANGED
@@ -264,7 +264,7 @@ class REPL
264
264
  time = entry[:timestamp].strftime("%H:%M:%S")
265
265
  from = entry[:from]
266
266
  to = entry[:to]
267
- msg = entry[:message]
267
+ msg = entry[:summary]
268
268
 
269
269
  line = "#{time} #{from} -> #{to}: #{msg}"
270
270
  line = line[0, 70] + "..." if line.length > 73
@@ -283,6 +283,8 @@ def print_main_help
283
283
  Commands:
284
284
  env Manage environments (create, list, export, etc.)
285
285
  serve <env> Run environment as a web server
286
+ message <env> <po> "text" Send a message to a PO and print the response
287
+ events <env> [--session ID] Show recent events from the message bus
286
288
  repl [name] [objects_dir] Start interactive REPL with a prompt object
287
289
  help Show this help message
288
290
 
@@ -293,6 +295,8 @@ def print_main_help
293
295
  prompt_objects env create demo --template demo # Create from template
294
296
  prompt_objects serve my-env # Start web UI
295
297
  prompt_objects serve my-env --open # Start and open browser
298
+ prompt_objects message my-env solver "Hello" # Send a message
299
+ prompt_objects message my-env solver "Hello" --json # JSON output
296
300
  HELP
297
301
  end
298
302
 
@@ -476,6 +480,9 @@ def run_serve(args)
476
480
  elsif options[:web]
477
481
  require "prompt_objects/server"
478
482
 
483
+ # Rebuild frontend to ensure built assets match source
484
+ rebuild_frontend
485
+
479
486
  # Open browser if requested
480
487
  if options[:open]
481
488
  url = "http://#{options[:host]}:#{options[:port]}"
@@ -491,6 +498,31 @@ def run_serve(args)
491
498
  end
492
499
  end
493
500
 
501
+ def rebuild_frontend
502
+ # Find the frontend directory relative to this executable
503
+ gem_root = File.expand_path("../..", __FILE__)
504
+ frontend_dir = File.join(gem_root, "frontend")
505
+
506
+ unless Dir.exist?(frontend_dir)
507
+ # No frontend source (e.g., installed as gem without source) — skip
508
+ return
509
+ end
510
+
511
+ node_modules = File.join(frontend_dir, "node_modules")
512
+ unless Dir.exist?(node_modules)
513
+ puts "Frontend dependencies not installed. Run: cd frontend && npm install"
514
+ return
515
+ end
516
+
517
+ print "Building frontend... "
518
+ result = system("npm run build --prefix #{frontend_dir}", out: File::NULL, err: File::NULL)
519
+ if result
520
+ puts "done"
521
+ else
522
+ puts "failed (serving stale assets)"
523
+ end
524
+ end
525
+
494
526
  def open_browser(url)
495
527
  case RUBY_PLATFORM
496
528
  when /darwin/
@@ -538,6 +570,356 @@ def load_all_objects(runtime, env_path)
538
570
  end
539
571
  end
540
572
 
573
+ def print_message_help
574
+ puts <<~HELP
575
+ Usage: prompt_objects message [options] <environment> <po_name> "message"
576
+
577
+ Send a message to a prompt object and print the response.
578
+
579
+ Arguments:
580
+ environment Environment name or path
581
+ po_name Name of the prompt object to message
582
+ message The message to send (quoted string)
583
+
584
+ Options:
585
+ --json Output response as JSON
586
+ --events Also print the event log for this interaction
587
+ --new-thread Start a new thread for this message
588
+ --session ID Use a specific session ID
589
+ --help, -h Show this help
590
+
591
+ If a server is running for the environment, the message is sent via
592
+ HTTP to the running server (and streams to the web UI in real time).
593
+ Otherwise, a standalone runtime is started for the request.
594
+
595
+ Examples:
596
+ prompt_objects message my-env solver "What patterns do you see?"
597
+ prompt_objects message my-env solver "Solve task 1" --json
598
+ prompt_objects message my-env solver "Try again" --new-thread
599
+ HELP
600
+ end
601
+
602
+ def run_message(args)
603
+ options = {
604
+ json: false,
605
+ events: false,
606
+ new_thread: false,
607
+ session_id: nil
608
+ }
609
+
610
+ positional = []
611
+ skip_next = false
612
+
613
+ args.each_with_index do |arg, i|
614
+ if skip_next
615
+ skip_next = false
616
+ next
617
+ end
618
+
619
+ case arg
620
+ when "--json"
621
+ options[:json] = true
622
+ when "--events"
623
+ options[:events] = true
624
+ when "--new-thread"
625
+ options[:new_thread] = true
626
+ when "--session"
627
+ options[:session_id] = args[i + 1]
628
+ skip_next = true
629
+ when "--help", "-h"
630
+ print_message_help
631
+ exit 0
632
+ else
633
+ positional << arg
634
+ end
635
+ end
636
+
637
+ env_name = positional[0]
638
+ po_name = positional[1]
639
+ message_text = positional[2]
640
+
641
+ unless env_name && po_name && message_text
642
+ puts "Error: environment, po_name, and message are required"
643
+ puts "Run 'prompt_objects message --help' for usage"
644
+ exit 1
645
+ end
646
+
647
+ # Resolve environment
648
+ env_path = resolve_environment(env_name)
649
+ unless env_path
650
+ $stderr.puts "Error: environment '#{env_name}' not found"
651
+ exit 1
652
+ end
653
+
654
+ # Check for running server
655
+ require "prompt_objects/server"
656
+ server_info = PromptObjects::Server.read_server_file(env_path)
657
+
658
+ if server_info
659
+ # Send via HTTP to running server
660
+ send_message_via_http(server_info, po_name, message_text, options)
661
+ else
662
+ # Standalone mode
663
+ send_message_standalone(env_path, po_name, message_text, options)
664
+ end
665
+ end
666
+
667
+ def send_message_via_http(server_info, po_name, message_text, options)
668
+ require "net/http"
669
+ require "uri"
670
+
671
+ uri = URI("http://#{server_info[:host]}:#{server_info[:port]}/api/prompt_objects/#{po_name}/message")
672
+
673
+ body = { message: message_text }
674
+ body[:new_thread] = true if options[:new_thread]
675
+ body[:session_id] = options[:session_id] if options[:session_id]
676
+
677
+ begin
678
+ response = Net::HTTP.post(uri, body.to_json, "Content-Type" => "application/json")
679
+ data = JSON.parse(response.body)
680
+
681
+ if data["error"]
682
+ $stderr.puts "Error: #{data['error']}"
683
+ exit 1
684
+ end
685
+
686
+ if options[:json]
687
+ puts JSON.pretty_generate(data)
688
+ else
689
+ puts data["response"]
690
+ end
691
+
692
+ if options[:events] && data["session_id"]
693
+ events_uri = URI("http://#{server_info[:host]}:#{server_info[:port]}/api/events/session/#{data['session_id']}")
694
+ events_response = Net::HTTP.get(events_uri)
695
+ events_data = JSON.parse(events_response)
696
+
697
+ if events_data["events"]&.any?
698
+ $stderr.puts
699
+ $stderr.puts "--- Events ---"
700
+ events_data["events"].each do |e|
701
+ $stderr.puts "#{e['timestamp']} #{e['from']} -> #{e['to']}: #{e['summary']}"
702
+ end
703
+ $stderr.puts "--------------"
704
+ end
705
+ end
706
+ rescue Errno::ECONNREFUSED
707
+ $stderr.puts "Error: Server at #{server_info[:host]}:#{server_info[:port]} is not responding"
708
+ $stderr.puts "The .server file may be stale. Try running standalone."
709
+ exit 1
710
+ end
711
+ end
712
+
713
+ def send_message_standalone(env_path, po_name, message_text, options)
714
+ runtime = PromptObjects::Runtime.new(env_path: env_path)
715
+ load_all_objects(runtime, env_path)
716
+
717
+ po = runtime.registry.get(po_name)
718
+ unless po.is_a?(PromptObjects::PromptObject)
719
+ $stderr.puts "Error: prompt object '#{po_name}' not found"
720
+ $stderr.puts "Available: #{runtime.loaded_objects.join(', ')}"
721
+ exit 1
722
+ end
723
+
724
+ # Create new thread if requested
725
+ po.new_thread if options[:new_thread]
726
+
727
+ # Switch session if specified
728
+ if options[:session_id]
729
+ po.switch_session(options[:session_id])
730
+ end
731
+
732
+ session_id = po.session_id
733
+ log_start = runtime.bus.log.length
734
+
735
+ context = runtime.context
736
+ context.current_capability = "human"
737
+
738
+ # Log to bus
739
+ runtime.bus.publish(from: "human", to: po.name, message: message_text, session_id: session_id)
740
+
741
+ response = po.receive(message_text, context: context)
742
+
743
+ # Log response to bus
744
+ runtime.bus.publish(from: po.name, to: "human", message: response, session_id: session_id)
745
+
746
+ if options[:json]
747
+ data = {
748
+ response: response,
749
+ po_name: po.name,
750
+ session_id: session_id,
751
+ event_count: runtime.bus.log.length - log_start
752
+ }
753
+ puts JSON.pretty_generate(data)
754
+ else
755
+ puts response
756
+ end
757
+
758
+ if options[:events]
759
+ entries = runtime.bus.log[log_start..]
760
+ if entries&.any?
761
+ $stderr.puts
762
+ $stderr.puts "--- Events ---"
763
+ entries.each do |entry|
764
+ time = entry[:timestamp].strftime("%H:%M:%S")
765
+ $stderr.puts "#{time} #{entry[:from]} -> #{entry[:to]}: #{entry[:summary]}"
766
+ end
767
+ $stderr.puts "--------------"
768
+ end
769
+ end
770
+ end
771
+
772
+ def print_events_help
773
+ puts <<~HELP
774
+ Usage: prompt_objects events [options] <environment>
775
+
776
+ Show recent events from the message bus.
777
+
778
+ Arguments:
779
+ environment Environment name or path
780
+
781
+ Options:
782
+ --session ID Show events for a specific session
783
+ --count N Number of events to show (default: 50)
784
+ --json Output as JSON
785
+ --help, -h Show this help
786
+
787
+ Examples:
788
+ prompt_objects events my-env
789
+ prompt_objects events my-env --session abc-123
790
+ prompt_objects events my-env --count 100 --json
791
+ HELP
792
+ end
793
+
794
+ def run_events(args)
795
+ options = { count: 50, session_id: nil, json: false }
796
+ positional = []
797
+ skip_next = false
798
+
799
+ args.each_with_index do |arg, i|
800
+ if skip_next
801
+ skip_next = false
802
+ next
803
+ end
804
+
805
+ case arg
806
+ when "--session"
807
+ options[:session_id] = args[i + 1]
808
+ skip_next = true
809
+ when "--count"
810
+ options[:count] = args[i + 1].to_i
811
+ skip_next = true
812
+ when "--json"
813
+ options[:json] = true
814
+ when "--help", "-h"
815
+ print_events_help
816
+ exit 0
817
+ else
818
+ positional << arg
819
+ end
820
+ end
821
+
822
+ env_name = positional[0]
823
+ unless env_name
824
+ puts "Error: environment name required"
825
+ puts "Run 'prompt_objects events --help' for usage"
826
+ exit 1
827
+ end
828
+
829
+ env_path = resolve_environment(env_name)
830
+ unless env_path
831
+ $stderr.puts "Error: environment '#{env_name}' not found"
832
+ exit 1
833
+ end
834
+
835
+ # Check for running server
836
+ require "prompt_objects/server"
837
+ server_info = PromptObjects::Server.read_server_file(env_path)
838
+
839
+ if server_info
840
+ show_events_via_http(server_info, options)
841
+ else
842
+ show_events_standalone(env_path, options)
843
+ end
844
+ end
845
+
846
+ def show_events_via_http(server_info, options)
847
+ require "net/http"
848
+ require "uri"
849
+
850
+ if options[:session_id]
851
+ uri = URI("http://#{server_info[:host]}:#{server_info[:port]}/api/events/session/#{options[:session_id]}")
852
+ else
853
+ uri = URI("http://#{server_info[:host]}:#{server_info[:port]}/api/events?count=#{options[:count]}")
854
+ end
855
+
856
+ response = Net::HTTP.get(uri)
857
+ data = JSON.parse(response)
858
+
859
+ if options[:json]
860
+ puts JSON.pretty_generate(data)
861
+ else
862
+ print_events(data["events"] || [])
863
+ end
864
+ rescue Errno::ECONNREFUSED
865
+ $stderr.puts "Error: Server not responding"
866
+ exit 1
867
+ end
868
+
869
+ def show_events_standalone(env_path, options)
870
+ db_path = File.join(env_path, "sessions.db")
871
+ unless File.exist?(db_path)
872
+ puts "No event history (sessions.db not found)"
873
+ exit 0
874
+ end
875
+
876
+ store = PromptObjects::Session::Store.new(db_path)
877
+
878
+ events = if options[:session_id]
879
+ store.get_events(session_id: options[:session_id])
880
+ else
881
+ store.get_recent_events(options[:count])
882
+ end
883
+
884
+ if options[:json]
885
+ formatted = events.map do |e|
886
+ {
887
+ id: e[:id],
888
+ from: e[:from],
889
+ to: e[:to],
890
+ summary: e[:summary],
891
+ message: e[:message],
892
+ timestamp: e[:timestamp]&.iso8601,
893
+ session_id: e[:session_id]
894
+ }
895
+ end
896
+ puts JSON.pretty_generate(formatted)
897
+ else
898
+ print_events(events)
899
+ end
900
+
901
+ store.close
902
+ end
903
+
904
+ def print_events(events)
905
+ if events.empty?
906
+ puts "No events found."
907
+ return
908
+ end
909
+
910
+ events.each do |e|
911
+ ts = e[:timestamp] || e["timestamp"]
912
+ ts = ts.is_a?(Time) ? ts.strftime("%H:%M:%S") : ts&.split("T")&.last&.split(".")&.first
913
+ from = e[:from] || e["from"]
914
+ to = e[:to] || e["to"]
915
+ summary = e[:summary] || e["summary"]
916
+
917
+ line = "#{ts} #{from} -> #{to}: #{summary}"
918
+ line = line[0, 120] + "..." if line.length > 123
919
+ puts line
920
+ end
921
+ end
922
+
541
923
  # === Main Entry Point ===
542
924
 
543
925
  def run_env(args)
@@ -561,6 +943,10 @@ def main
561
943
  run_repl(args)
562
944
  when "serve"
563
945
  run_serve(args)
946
+ when "message", "msg"
947
+ run_message(args)
948
+ when "events"
949
+ run_events(args)
564
950
  when "help", "--help", "-h"
565
951
  print_main_help
566
952
  else
data/frontend/src/App.tsx CHANGED
@@ -7,11 +7,12 @@ import { PODetail } from './components/PODetail'
7
7
  import { MessageBus } from './components/MessageBus'
8
8
  import { NotificationPanel } from './components/NotificationPanel'
9
9
  import { ThreadsSidebar } from './components/ThreadsSidebar'
10
+ import { UsagePanel } from './components/UsagePanel'
10
11
 
11
12
  export default function App() {
12
- const { sendMessage, respondToNotification, createSession, switchSession, switchLLM, createThread } =
13
+ const { sendMessage, respondToNotification, createSession, switchSession, switchLLM, createThread, updatePrompt, requestUsage, exportThread } =
13
14
  useWebSocket()
14
- const { selectedPO, busOpen, notifications } = useStore()
15
+ const { selectedPO, busOpen, notifications, usageData, clearUsageData } = useStore()
15
16
  const selectedPOData = useSelectedPO()
16
17
  const [splitView, setSplitView] = useState(true) // Default to split view
17
18
 
@@ -47,6 +48,8 @@ export default function App() {
47
48
  po={selectedPOData}
48
49
  switchSession={switchSession}
49
50
  createThread={createThread}
51
+ requestUsage={requestUsage}
52
+ exportThread={exportThread}
50
53
  />
51
54
  </aside>
52
55
  )}
@@ -72,6 +75,7 @@ export default function App() {
72
75
  createSession={createSession}
73
76
  switchSession={switchSession}
74
77
  createThread={createThread}
78
+ updatePrompt={updatePrompt}
75
79
  />
76
80
  ) : (
77
81
  <Dashboard />
@@ -80,7 +84,7 @@ export default function App() {
80
84
 
81
85
  {/* Message Bus sidebar */}
82
86
  {busOpen && (
83
- <aside className="w-80 border-l border-po-border bg-po-surface overflow-hidden">
87
+ <aside className="w-80 flex-shrink-0 border-l border-po-border bg-po-surface overflow-hidden">
84
88
  <MessageBus />
85
89
  </aside>
86
90
  )}
@@ -90,6 +94,11 @@ export default function App() {
90
94
  {notifications.length > 0 && (
91
95
  <NotificationPanel respondToNotification={respondToNotification} />
92
96
  )}
97
+
98
+ {/* Usage panel modal */}
99
+ {usageData && (
100
+ <UsagePanel usage={usageData as any} onClose={clearUsageData} />
101
+ )}
93
102
  </div>
94
103
  )
95
104
  }