pikuri-core 0.0.6 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +6 -4
- data/lib/pikuri/agent/chat_transport.rb +128 -24
- data/lib/pikuri/agent/configurator.rb +47 -107
- data/lib/pikuri/agent/context_window_detector.rb +80 -70
- data/lib/pikuri/agent/control/cancellable.rb +87 -66
- data/lib/pikuri/agent/control/interloper.rb +127 -105
- data/lib/pikuri/agent/control/step_limit.rb +46 -30
- data/lib/pikuri/agent/control.rb +14 -34
- data/lib/pikuri/agent/event.rb +125 -163
- data/lib/pikuri/agent/extension.rb +121 -83
- data/lib/pikuri/agent/extension_context.rb +120 -0
- data/lib/pikuri/agent/history.rb +653 -0
- data/lib/pikuri/agent/listener/rate_limited.rb +40 -66
- data/lib/pikuri/agent/listener/terminal.rb +147 -110
- data/lib/pikuri/agent/listener/token_log.rb +116 -108
- data/lib/pikuri/agent/listener.rb +23 -36
- data/lib/pikuri/agent/listener_list.rb +26 -57
- data/lib/pikuri/agent/synthesizer.rb +76 -92
- data/lib/pikuri/agent.rb +939 -642
- data/lib/pikuri/bundler_env.rb +68 -0
- data/lib/pikuri/extractor/html.rb +63 -110
- data/lib/pikuri/extractor/passthrough.rb +20 -30
- data/lib/pikuri/extractor.rb +93 -154
- data/lib/pikuri/file_type.rb +63 -135
- data/lib/pikuri/finalizers.rb +32 -47
- data/lib/pikuri/paths.rb +104 -13
- data/lib/pikuri/ruby_llm_patches.rb +106 -0
- data/lib/pikuri/sanitizer.rb +157 -0
- data/lib/pikuri/subprocess.rb +75 -119
- data/lib/pikuri/testing.rb +296 -0
- data/lib/pikuri/tool/calculator.rb +56 -66
- data/lib/pikuri/tool/execute_context.rb +42 -0
- data/lib/pikuri/tool/fetch.rb +51 -77
- data/lib/pikuri/tool/parameters.rb +70 -15
- data/lib/pikuri/tool/scraper.rb +55 -97
- data/lib/pikuri/tool/search/brave.rb +70 -84
- data/lib/pikuri/tool/search/duckduckgo.rb +65 -86
- data/lib/pikuri/tool/search/engines.rb +249 -93
- data/lib/pikuri/tool/search/exa.rb +75 -97
- data/lib/pikuri/tool/search/rate_limiter.rb +61 -38
- data/lib/pikuri/tool/search/result.rb +10 -15
- data/lib/pikuri/tool/trifecta_legs.rb +217 -0
- data/lib/pikuri/tool/web_scrape.rb +38 -54
- data/lib/pikuri/tool/web_search.rb +121 -26
- data/lib/pikuri/tool.rb +140 -65
- data/lib/pikuri/trifecta/contribution.rb +43 -0
- data/lib/pikuri/trifecta/node.rb +47 -0
- data/lib/pikuri/trifecta/report.rb +230 -0
- data/lib/pikuri/trifecta.rb +127 -0
- data/lib/pikuri/url_cache.rb +33 -49
- data/lib/pikuri/version.rb +1 -1
- data/lib/pikuri-core.rb +72 -86
- data/prompts/agent-loop.txt +5 -0
- data/prompts/pikuri-chat.txt +3 -12
- metadata +18 -8
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pikuri
|
|
4
|
+
class Agent
|
|
5
|
+
# Capability facade handed to {Extension#bind} and
|
|
6
|
+
# {Extension#on_user_message} — the runtime counterpart of {Configurator}.
|
|
7
|
+
# Where the Configurator collects *build-time* declarations, this grants
|
|
8
|
+
# the *runtime* capabilities an extension needs once the agent is wired:
|
|
9
|
+
# emitting domain events, registering raw per-agent tools, deriving
|
|
10
|
+
# sub-agent listener lists.
|
|
11
|
+
#
|
|
12
|
+
# == Why a handed object, not a getter on Agent
|
|
13
|
+
#
|
|
14
|
+
# {Agent} exposes NO public path to these capabilities (no +listeners+ /
|
|
15
|
+
# +chat+ reader, no emit method): holding an agent grants read access to
|
|
16
|
+
# its config and nothing more. The only way to obtain this object is to be
|
|
17
|
+
# an {Extension} receiving +bind+ / +on_user_message+ — or to be handed it
|
|
18
|
+
# onward by one ({Pikuri::SubAgent::SubAgentTool} and
|
|
19
|
+
# {Pikuri::Mcp::Servers::Connect} both capture the context their +bind+
|
|
20
|
+
# received). Capabilities flow by explicit handoff, never by fetching from
|
|
21
|
+
# a globally reachable object. The boundary is the API contract, not a
|
|
22
|
+
# mechanical seal — same as every seam in CLAUDE.md.
|
|
23
|
+
#
|
|
24
|
+
# == Boundary rule
|
|
25
|
+
#
|
|
26
|
+
# Operations that *act on* the live agent's wiring live here; passive
|
|
27
|
+
# readers of constructor-given config (+transport+, +id+, +tools+, ...)
|
|
28
|
+
# stay on {Agent}, reachable via {#agent}. Don't move readers in; don't add
|
|
29
|
+
# capabilities to Agent.
|
|
30
|
+
#
|
|
31
|
+
# == Audit
|
|
32
|
+
#
|
|
33
|
+
# One context per agent, built by {Agent#initialize} before the +bind+
|
|
34
|
+
# sweep. {ListenerList#emit} has exactly two callers: {Agent} (loop
|
|
35
|
+
# narration) and this class (domain events). Capability-user roster:
|
|
36
|
+
# +grep -rn 'emit_event\|add_raw_tool\|sub_agent_listeners' pikuri-*/lib/+.
|
|
37
|
+
class ExtensionContext
|
|
38
|
+
# @param agent [Agent] the live, fully wired agent.
|
|
39
|
+
# @param chat [RubyLLM::Chat] the agent's underlying chat —
|
|
40
|
+
# target of {#add_raw_tool}.
|
|
41
|
+
# @param listeners [ListenerList] the agent's listener list —
|
|
42
|
+
# target of {#emit_event} / {#sub_agent_listeners}.
|
|
43
|
+
# @param on_close_sink [Array<Proc>] the agent's live
|
|
44
|
+
# +@on_close_handlers+ array, which {#on_close} appends to —
|
|
45
|
+
# same live-sink shape as {Configurator}'s +on_close_sink:+.
|
|
46
|
+
def initialize(agent:, chat:, listeners:, on_close_sink:)
|
|
47
|
+
@agent = agent
|
|
48
|
+
@chat = chat
|
|
49
|
+
@listeners = listeners
|
|
50
|
+
@on_close_handlers = on_close_sink
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [Agent] the live agent, for read access to its
|
|
54
|
+
# configuration (tools, transport, id, streaming, ...).
|
|
55
|
+
attr_reader :agent
|
|
56
|
+
|
|
57
|
+
# Emit a domain event onto the agent's listener stream. Core {Event}
|
|
58
|
+
# variants narrate the loop ({Agent}-emitted); gems define their own
|
|
59
|
+
# (e.g. +Pikuri::Tasks::ListChanged+) and emit them here. Listeners must
|
|
60
|
+
# no-op on variants they don't recognize (the {Listener::Base#on_event}
|
|
61
|
+
# default gives that for free).
|
|
62
|
+
#
|
|
63
|
+
# Called on the agent's thread (typically inside a tool's +execute+,
|
|
64
|
+
# landing between {Event::ToolCall} and {Event::ToolResult}); listeners
|
|
65
|
+
# doing cross-thread handoff snapshot inside +on_event+.
|
|
66
|
+
#
|
|
67
|
+
# @param event [Object] an immutable event value (by convention a +Data+)
|
|
68
|
+
# @return [void]
|
|
69
|
+
def emit_event(event)
|
|
70
|
+
@listeners.emit(event)
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Register a raw +RubyLLM::Tool+ subclass on the agent's chat, bypassing
|
|
75
|
+
# the {Pikuri::Tool} strict-validation seam — hence "raw" (native tools
|
|
76
|
+
# go through {Configurator#add_tool} for {Tool::Parameters} validation).
|
|
77
|
+
# Callers: {Pikuri::Mcp::Servers} (MCP tools deliberately bypass) and
|
|
78
|
+
# {Pikuri::SubAgent::Extension} (the +agent+ tool registers after the
|
|
79
|
+
# parent's tool list is final).
|
|
80
|
+
#
|
|
81
|
+
# The tool does NOT enter {Agent#tools}, only the chat's list — so
|
|
82
|
+
# sub-agents can't snapshot it, which is the point: activation is strictly
|
|
83
|
+
# per-agent.
|
|
84
|
+
#
|
|
85
|
+
# @param ruby_llm_tool [Class] subclass of +RubyLLM::Tool+
|
|
86
|
+
# @return [void]
|
|
87
|
+
def add_raw_tool(ruby_llm_tool)
|
|
88
|
+
@chat.with_tool(ruby_llm_tool)
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Derive a listener list for a spawned sub-agent via
|
|
93
|
+
# {ListenerList#for_sub_agent}. Sole caller:
|
|
94
|
+
# {Pikuri::SubAgent::SubAgentTool}, once per spawn. The derived list
|
|
95
|
+
# aliases the parent's listener *instances* where a listener opts to
|
|
96
|
+
# share by reference — see {ListenerList#for_sub_agent}.
|
|
97
|
+
#
|
|
98
|
+
# @param params [Hash{Symbol => Object}] forwarded to each listener's
|
|
99
|
+
# +for_sub_agent+ hook (currently +id:+).
|
|
100
|
+
# @return [ListenerList]
|
|
101
|
+
def sub_agent_listeners(**params)
|
|
102
|
+
@listeners.for_sub_agent(**params)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Register a handler called by {Agent#close} — symmetric to
|
|
106
|
+
# {Configurator#on_close} (LIFO, per-handler rescue, idempotent) but
|
|
107
|
+
# available post-construction, so an {Extension}'s +bind+ can install
|
|
108
|
+
# per-agent cleanup (Memory arms its recorder's bounded flush here).
|
|
109
|
+
#
|
|
110
|
+
# @yield called with no arguments at close time
|
|
111
|
+
# @return [void]
|
|
112
|
+
def on_close(&blk)
|
|
113
|
+
raise ArgumentError, 'on_close requires a block' unless block_given?
|
|
114
|
+
|
|
115
|
+
@on_close_handlers << blk
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|