samagotchi 0.156.0 → 0.158.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/CHANGELOG.md +70 -1
- data/README.md +1 -1
- data/bin/chi +4 -1
- data/docs/architecture.md +18 -12
- data/docs/bundle-authoring.md +5 -5
- data/docs/bundles.md +4 -1
- data/docs/cli.md +29 -24
- data/docs/configuration.md +43 -9
- data/docs/context.md +5 -3
- data/docs/guardrails.md +12 -7
- data/docs/hooks.md +53 -83
- data/docs/internals/bridge.md +84 -7
- data/docs/internals/bundles.md +69 -46
- data/docs/internals/context-telemetry.md +4 -3
- data/docs/internals/engine.md +23 -7
- data/docs/internals/participants.md +3 -2
- data/docs/internals/remote-sessions.md +5 -2
- data/docs/internals/web-frontend.md +3 -2
- data/docs/internals/working-set.md +1 -1
- data/docs/memory.md +51 -10
- data/docs/plugins.md +48 -11
- data/docs/releasing.md +6 -5
- data/docs/sessions.md +13 -13
- data/docs/sub-agent.md +17 -2
- data/docs/testing.md +56 -5
- data/lib/samagotchi/answer_command.rb +2 -1
- data/lib/samagotchi/archive_store.rb +4 -3
- data/lib/samagotchi/bootstrap_command.rb +3 -2
- data/lib/samagotchi/bridge/card_places.rb +5 -7
- data/lib/samagotchi/bridge/card_store.rb +3 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +113 -10
- data/lib/samagotchi/bridge/turn_drawings.rb +271 -0
- data/lib/samagotchi/bridge.rb +31 -6
- data/lib/samagotchi/broadcast/recipients.rb +8 -8
- data/lib/samagotchi/broadcast/triage_model.rb +1 -1
- data/lib/samagotchi/bundle_command/check.rb +41 -10
- data/lib/samagotchi/bundle_command/installed_check.rb +13 -2
- data/lib/samagotchi/bundle_command/try/run.rb +4 -1
- data/lib/samagotchi/bundle_command/try/sandbox.rb +1 -1
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +20 -45
- data/lib/samagotchi/bundles/known-names/manifest.yml +4 -7
- data/lib/samagotchi/bundles/known-names/{hooks/known_names.rb → plugin.rb} +17 -0
- data/lib/samagotchi/bundles/source-links/manifest.yml +4 -7
- data/lib/samagotchi/bundles/source-links/{hooks/source_links.rb → plugin.rb} +23 -4
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +7 -7
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/child_move.rb +6 -6
- data/lib/samagotchi/child_reports.rb +2 -2
- data/lib/samagotchi/children_status.rb +15 -21
- data/lib/samagotchi/cli/parent_wait.rb +8 -22
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/context_command.rb +7 -5
- data/lib/samagotchi/context_sources.rb +9 -6
- data/lib/samagotchi/context_window.rb +9 -12
- data/lib/samagotchi/effective_model.rb +57 -22
- data/lib/samagotchi/engine/plugins.rb +1 -1
- data/lib/samagotchi/engine/recap.rb +8 -4
- data/lib/samagotchi/engine.rb +30 -9
- data/lib/samagotchi/events.rb +3 -2
- data/lib/samagotchi/extension_load.rb +26 -44
- data/lib/samagotchi/guardrails/load_failures.rb +3 -2
- data/lib/samagotchi/guardrails/path_resolve.rb +11 -0
- data/lib/samagotchi/guardrails/review_paths.rb +1 -5
- data/lib/samagotchi/guardrails_command.rb +1 -1
- data/lib/samagotchi/hook_routes.rb +2 -2
- data/lib/samagotchi/hooks.rb +2 -3
- data/lib/samagotchi/host_address.rb +52 -0
- data/lib/samagotchi/host_registry.rb +57 -45
- data/lib/samagotchi/kernel_loop.rb +7 -3
- data/lib/samagotchi/llm/chat_loop.rb +6 -3
- data/lib/samagotchi/llm_context_strategy.rb +27 -63
- data/lib/samagotchi/memory_bundle/asset_installer.rb +92 -177
- data/lib/samagotchi/memory_bundle/builder.rb +12 -35
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +6 -31
- data/lib/samagotchi/memory_bundle/file_actions.rb +193 -0
- data/lib/samagotchi/memory_bundle/file_digest.rb +13 -0
- data/lib/samagotchi/memory_bundle/file_state.rb +152 -0
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
- data/lib/samagotchi/memory_bundle/installed_copy.rb +13 -19
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +10 -17
- data/lib/samagotchi/memory_bundle/installer.rb +212 -178
- data/lib/samagotchi/memory_bundle/manifest.rb +51 -58
- data/lib/samagotchi/memory_bundle/merger.rb +69 -65
- data/lib/samagotchi/memory_bundle/plugin_ref.rb +6 -5
- data/lib/samagotchi/memory_bundle/profile.rb +5 -5
- data/lib/samagotchi/memory_bundle/provenance.rb +26 -52
- data/lib/samagotchi/memory_bundle/remerge.rb +31 -14
- data/lib/samagotchi/memory_bundle/shas.rb +5 -8
- data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -11
- data/lib/samagotchi/memory_bundle/status.rb +26 -30
- data/lib/samagotchi/memory_bundle/system_bundle.rb +39 -10
- data/lib/samagotchi/memory_bundle/trash.rb +28 -18
- data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
- data/lib/samagotchi/memory_orphans.rb +4 -4
- data/lib/samagotchi/memory_paths.rb +102 -13
- data/lib/samagotchi/memory_usage.rb +1 -1
- data/lib/samagotchi/model_lookup.rb +75 -0
- data/lib/samagotchi/model_profile.rb +1 -0
- data/lib/samagotchi/models_command.rb +1 -1
- data/lib/samagotchi/note_command.rb +1 -1
- data/lib/samagotchi/participant_mode.rb +2 -1
- data/lib/samagotchi/plugin/api.rb +42 -10
- data/lib/samagotchi/plugin/loader.rb +75 -15
- data/lib/samagotchi/plugin/sessions.rb +1 -1
- data/lib/samagotchi/project_command.rb +167 -0
- data/lib/samagotchi/project_init.rb +69 -0
- data/lib/samagotchi/project_scope.rb +6 -4
- data/lib/samagotchi/prompt_files.rb +10 -7
- data/lib/samagotchi/reasoning_history.rb +14 -20
- data/lib/samagotchi/recap_place.rb +26 -0
- data/lib/samagotchi/reply_wait/liveness.rb +38 -0
- data/lib/samagotchi/reply_wait/poll.rb +44 -15
- data/lib/samagotchi/reply_wait/target.rb +113 -0
- data/lib/samagotchi/reply_wait.rb +22 -2
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/sampling_settings.rb +9 -16
- data/lib/samagotchi/self_report.rb +34 -35
- data/lib/samagotchi/send_command.rb +17 -23
- data/lib/samagotchi/session.rb +349 -441
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +5 -6
- data/lib/samagotchi/session_access/remote.rb +5 -3
- data/lib/samagotchi/session_field.rb +134 -0
- data/lib/samagotchi/session_inbox.rb +49 -1
- data/lib/samagotchi/session_manager/emptiness.rb +2 -1
- data/lib/samagotchi/session_manager/listing.rb +29 -100
- data/lib/samagotchi/session_manager.rb +2 -1
- data/lib/samagotchi/session_metrics.rb +7 -4
- data/lib/samagotchi/session_paths.rb +57 -0
- data/lib/samagotchi/session_retention.rb +2 -2
- data/lib/samagotchi/session_row.rb +200 -0
- data/lib/samagotchi/session_setup.rb +15 -12
- data/lib/samagotchi/sessions_command.rb +11 -129
- data/lib/samagotchi/steer.rb +10 -0
- data/lib/samagotchi/steer_cut_after.rb +7 -14
- data/lib/samagotchi/system_prompt.rb +20 -11
- data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
- data/lib/samagotchi/terminal_ui/join_replay.rb +91 -8
- data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
- data/lib/samagotchi/terminal_ui.rb +3 -1
- data/lib/samagotchi/testing/bundle.rb +5 -12
- data/lib/samagotchi/testing/install.rb +5 -4
- data/lib/samagotchi/testing/recorder.rb +1 -1
- data/lib/samagotchi/testing.rb +1 -1
- data/lib/samagotchi/thinking.rb +16 -21
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tool_response.rb +5 -3
- data/lib/samagotchi/tool_runner.rb +18 -1
- data/lib/samagotchi/toolkit_binding.rb +1 -4
- data/lib/samagotchi/toolkit_command.rb +1 -4
- data/lib/samagotchi/tools/delegate.rb +60 -51
- data/lib/samagotchi/tools/delegate_relay.rb +5 -5
- data/lib/samagotchi/tools/delegate_result.rb +1 -1
- data/lib/samagotchi/tools/delegate_wait.rb +41 -17
- data/lib/samagotchi/tools/list_sessions.rb +7 -7
- data/lib/samagotchi/update_command.rb +11 -7
- data/lib/samagotchi/verify_fresh.rb +7 -11
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +11 -14
- data/lib/samagotchi/web/app.rb +66 -21
- data/lib/samagotchi/web/message_display.rb +57 -7
- data/lib/samagotchi/web/participant_access.rb +1 -1
- data/lib/samagotchi/web/project_list.rb +3 -2
- data/lib/samagotchi/web/public/activity.js +16 -7
- data/lib/samagotchi/web/public/app.js +29 -8
- data/lib/samagotchi/web/public/bubbles.js +8 -3
- data/lib/samagotchi/web/public/cards_place.js +15 -0
- data/lib/samagotchi/web/public/commands.js +5 -1
- data/lib/samagotchi/web/public/data.js +4 -2
- data/lib/samagotchi/web/public/history_shape.js +220 -0
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/project_picker.js +86 -4
- data/lib/samagotchi/web/public/recap_stamp.js +17 -0
- data/lib/samagotchi/web/public/reload_check.js +71 -0
- data/lib/samagotchi/web/public/stage_model.js +7 -5
- data/lib/samagotchi/web/public/stage_view.js +7 -3
- data/lib/samagotchi/web/public/stream_handlers.js +8 -2
- data/lib/samagotchi/web/public/timing.js +43 -2
- data/lib/samagotchi/web/public/turn_block.js +215 -30
- data/lib/samagotchi/web/public/turn_drawing.js +43 -0
- data/lib/samagotchi/web/public/turn_events.js +10 -1
- data/lib/samagotchi/web/public/turn_pieces.js +29 -0
- data/lib/samagotchi/web/reload_check_route.rb +95 -0
- data/lib/samagotchi/web/server.rb +4 -2
- data/lib/samagotchi/web/session_access_routes.rb +3 -3
- data/lib/samagotchi/web/session_control.rb +1 -1
- data/lib/samagotchi/web/session_hub.rb +9 -17
- data/lib/samagotchi/web/session_show.rb +64 -2
- data/lib/samagotchi/web/session_summary.rb +36 -24
- data/lib/samagotchi/worker.rb +28 -5
- data/lib/samagotchi/worker_link.rb +3 -0
- metadata +22 -5
- data/lib/samagotchi/hooks/bundle_loader.rb +0 -191
- data/lib/samagotchi/send_command/wait_target.rb +0 -85
|
@@ -7,6 +7,7 @@ require "time"
|
|
|
7
7
|
require_relative "atomic_file"
|
|
8
8
|
require_relative "memory_paths"
|
|
9
9
|
require_relative "paths"
|
|
10
|
+
require_relative "session_paths"
|
|
10
11
|
require_relative "session"
|
|
11
12
|
|
|
12
13
|
module Samagotchi
|
|
@@ -617,13 +618,13 @@ module Samagotchi
|
|
|
617
618
|
nil
|
|
618
619
|
end
|
|
619
620
|
|
|
620
|
-
# <state dir>/context; +state_dir+ is the sessions dir (
|
|
621
|
+
# <state dir>/context; +state_dir+ is the sessions dir (SessionPaths.default_state_dir).
|
|
621
622
|
def root(state_dir: nil)
|
|
622
|
-
File.join(File.dirname(state_dir ||
|
|
623
|
+
File.join(File.dirname(state_dir || SessionPaths.default_state_dir), DIR)
|
|
623
624
|
end
|
|
624
625
|
|
|
625
626
|
def session_location(session_id, state_dir: nil)
|
|
626
|
-
Location.new(scope: "session", dir: File.join(root(state_dir: state_dir), "sessions",
|
|
627
|
+
Location.new(scope: "session", dir: File.join(root(state_dir: state_dir), "sessions", SessionPaths.check_id!(session_id)),
|
|
627
628
|
key: session_id)
|
|
628
629
|
end
|
|
629
630
|
|
|
@@ -657,19 +658,21 @@ module Samagotchi
|
|
|
657
658
|
end
|
|
658
659
|
|
|
659
660
|
# Where a project's source runs: the project root when it is a checkout
|
|
660
|
-
# (its .git is there)
|
|
661
|
+
# (its .git is there) or a marked folder (MemoryPaths::MARKER), else
|
|
662
|
+
# +fallback+ (the session's folder): a bare
|
|
661
663
|
# repo, proj/.bare or --separate-git-dir makes the root the git dir
|
|
662
664
|
# itself (MemoryPaths.project_root), and a command run there sees no
|
|
663
665
|
# files.
|
|
664
666
|
def project_cwd(project_root, fallback)
|
|
665
667
|
root = project_root.to_s
|
|
666
|
-
!root.empty? && File.exist?(File.join(root,
|
|
668
|
+
checkout = !root.empty? && [".git", MemoryPaths::MARKER].any? { |name| File.exist?(File.join(root, name)) }
|
|
669
|
+
checkout ? root : fallback
|
|
667
670
|
end
|
|
668
671
|
|
|
669
672
|
# A deleted session's sources, snapshots, subscriptions and markers.
|
|
670
673
|
# @return [Array<String>] the paths removed
|
|
671
674
|
def remove_session(session_id, state_dir: nil)
|
|
672
|
-
return [] unless
|
|
675
|
+
return [] unless SessionPaths.valid_id?(session_id.to_s)
|
|
673
676
|
|
|
674
677
|
dir = session_location(session_id, state_dir: state_dir).dir
|
|
675
678
|
return [] unless Dir.exist?(dir)
|
|
@@ -11,14 +11,14 @@ module Samagotchi
|
|
|
11
11
|
# provider's context_length)
|
|
12
12
|
# 3. models.<key>.window_tokens, else hosts.<name>.window_tokens
|
|
13
13
|
# (#setting: the most specific wins)
|
|
14
|
-
# 4. context.window_tokens (
|
|
15
|
-
# config file)
|
|
14
|
+
# 4. context.window_tokens (--context-window-tokens,
|
|
15
|
+
# SAMAGOTCHI_CONTEXT_WINDOW_TOKENS or the config file)
|
|
16
16
|
# 5. DEFAULT_TOKENS
|
|
17
17
|
# Settings (3 and 4) only fill in when the server and its list report
|
|
18
18
|
# nothing.
|
|
19
19
|
#
|
|
20
|
-
# Sources: :server, :model_list, :model_setting, :host_setting, :
|
|
21
|
-
# (
|
|
20
|
+
# Sources: :server, :model_list, :model_setting, :host_setting, :cli,
|
|
21
|
+
# :env, :config (the file), :default.
|
|
22
22
|
module ContextWindow
|
|
23
23
|
DEFAULT_TOKENS = 256_000
|
|
24
24
|
|
|
@@ -48,16 +48,13 @@ module Samagotchi
|
|
|
48
48
|
# The window config.yml gives this model or its host:
|
|
49
49
|
# models.<key>.window_tokens (by the model's lookup names, as sampling
|
|
50
50
|
# and vision look a model up), else hosts.<name>.window_tokens.
|
|
51
|
-
# @param
|
|
52
|
-
# @param names [Array<String>] HostRegistry#lookup_names
|
|
53
|
-
# @param models [Hash{String => ModelSettings}, nil] ModelSettings.all (specs)
|
|
51
|
+
# @param lookup [ModelLookup] the model's layers (EffectiveModel#lookup)
|
|
54
52
|
# @return [Resolved, nil]
|
|
55
|
-
def setting(
|
|
56
|
-
|
|
57
|
-
tokens = ModelSettings.find(names, :window_tokens, models: models)&.value
|
|
53
|
+
def setting(lookup)
|
|
54
|
+
tokens = lookup.model(:window_tokens)&.value
|
|
58
55
|
return Resolved.new(tokens: tokens, source: :model_setting) if positive_integer?(tokens)
|
|
59
56
|
|
|
60
|
-
tokens =
|
|
57
|
+
tokens = lookup.host(:window_tokens)
|
|
61
58
|
positive_integer?(tokens) ? Resolved.new(tokens: tokens, source: :host_setting) : nil
|
|
62
59
|
rescue StandardError
|
|
63
60
|
nil
|
|
@@ -79,7 +76,7 @@ module Samagotchi
|
|
|
79
76
|
end
|
|
80
77
|
return Resolved.new(tokens: DEFAULT_TOKENS, source: :default) unless positive_integer?(value)
|
|
81
78
|
|
|
82
|
-
Resolved.new(tokens: value, source:
|
|
79
|
+
Resolved.new(tokens: value, source: { cli: :cli, env: :env }.fetch(origin, :config))
|
|
83
80
|
end
|
|
84
81
|
|
|
85
82
|
# For callers with no client at hand (tool output guardrails): the last
|
|
@@ -4,6 +4,7 @@ require_relative "host_registry"
|
|
|
4
4
|
require_relative "log"
|
|
5
5
|
require_relative "context_window"
|
|
6
6
|
require_relative "llm_context_strategy"
|
|
7
|
+
require_relative "model_lookup"
|
|
7
8
|
require_relative "model_profile"
|
|
8
9
|
require_relative "reasoning_history"
|
|
9
10
|
require_relative "sampling_settings"
|
|
@@ -22,7 +23,8 @@ module Samagotchi
|
|
|
22
23
|
|
|
23
24
|
# The effective model resolved once (HostRegistry#resolve: its host and
|
|
24
25
|
# client, the id sent) with the names its models: entry may be under
|
|
25
|
-
# (HostRegistry#lookup_names), and its settings read from them
|
|
26
|
+
# (HostRegistry#lookup_names), and its settings read from them through
|
|
27
|
+
# one ModelLookup (every layer's input, with the registry's env). An
|
|
26
28
|
# immutable snapshot built per read, never kept across reads: config.yml,
|
|
27
29
|
# a model listing (which may move a bare id to another host) and another
|
|
28
30
|
# process's saved lists all change what a resolve gives without any
|
|
@@ -32,14 +34,17 @@ module Samagotchi
|
|
|
32
34
|
# Engine has always given (Unresolved); building one raises as
|
|
33
35
|
# HostRegistry#resolve does (SessionModelSettings#model_setting gives the
|
|
34
36
|
# fallback then).
|
|
35
|
-
EffectiveModel = Data.define(:registry, :name, :typed, :target, :names) do
|
|
37
|
+
EffectiveModel = Data.define(:registry, :name, :typed, :target, :names, :lookup) do
|
|
36
38
|
# @param registry [HostRegistry]
|
|
37
39
|
# @param choice [ModelChoice]
|
|
40
|
+
# @param models [Hash{String => ModelSettings}, nil] one models: map for
|
|
41
|
+
# many models (a picker); ModelSettings.all at each read by default
|
|
38
42
|
# @raise what HostRegistry#resolve raises
|
|
39
|
-
def self.of(registry, choice)
|
|
43
|
+
def self.of(registry, choice, models: nil)
|
|
40
44
|
target = registry.resolve(choice.name)
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
names = registry.lookup_names(choice.typed, resolved: choice.name, target: target)
|
|
46
|
+
new(registry: registry, name: choice.name, typed: choice.typed, target: target, names: names,
|
|
47
|
+
lookup: ModelLookup.for(target, names: names, models: models, env: registry.env))
|
|
43
48
|
end
|
|
44
49
|
|
|
45
50
|
# A saved session's model (no worker): its model name, the default when
|
|
@@ -50,6 +55,34 @@ module Samagotchi
|
|
|
50
55
|
of(registry, ModelChoice.new(name: name, typed: session.model_typed || name))
|
|
51
56
|
end
|
|
52
57
|
|
|
58
|
+
# A saved session's LLM context strategy, apply rule and budget, each
|
|
59
|
+
# with where it came from (LLMContextStrategy::Explained#summary), as
|
|
60
|
+
# its worker would resolve them; nil when they can't be (an unreadable
|
|
61
|
+
# config, a model that resolves nowhere). From config and the registry
|
|
62
|
+
# only: no network, no model call. What a session page, a session
|
|
63
|
+
# list's ctx % and a spawn-time check all read.
|
|
64
|
+
# @param session [Session, nil]
|
|
65
|
+
# @param registry [HostRegistry, nil] the hosts to resolve against;
|
|
66
|
+
# HostRegistry.current by default
|
|
67
|
+
# @return [Hash, nil] Explained#summary, symbol keys
|
|
68
|
+
def self.session_llm_context(session, registry: nil)
|
|
69
|
+
return nil if session.nil?
|
|
70
|
+
|
|
71
|
+
own = session.llm_context
|
|
72
|
+
of_session(session, registry: registry || HostRegistry.current).llm_context_explained(own&.resolve_args || {})&.summary(own)
|
|
73
|
+
rescue StandardError # degrade-ok: a saved session's model that resolves nowhere has no budget to show; the readers log their own
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The budget a saved session's turns run under
|
|
78
|
+
# (.session_llm_context's :budget_tokens), nil when it is off or can't
|
|
79
|
+
# be resolved. What a session list's ctx % counts against, so a saved
|
|
80
|
+
# session's fill reads as the live meter.
|
|
81
|
+
# @return [Integer, nil]
|
|
82
|
+
def self.session_budget(session, registry: nil)
|
|
83
|
+
session_llm_context(session, registry: registry)&.fetch(:budget_tokens, nil)
|
|
84
|
+
end
|
|
85
|
+
|
|
53
86
|
def entry = target.entry
|
|
54
87
|
def client = target.client
|
|
55
88
|
def bare = target.bare_model
|
|
@@ -63,21 +96,21 @@ module Samagotchi
|
|
|
63
96
|
|
|
64
97
|
# The configured request parameters (hosts: and models: sampling).
|
|
65
98
|
def sampling
|
|
66
|
-
SamplingSettings.for(
|
|
99
|
+
SamplingSettings.for(lookup)
|
|
67
100
|
rescue StandardError => e
|
|
68
101
|
unresolved(:sampling_unresolved, e, SamplingSettings::EMPTY)
|
|
69
102
|
end
|
|
70
103
|
|
|
71
104
|
# "temperature=0.6 (hosts.work)", nil when none.
|
|
72
105
|
def sampling_summary
|
|
73
|
-
SamplingSettings.summary(
|
|
106
|
+
SamplingSettings.summary(lookup)
|
|
74
107
|
rescue StandardError => e
|
|
75
108
|
unresolved(:sampling_unresolved, e, nil)
|
|
76
109
|
end
|
|
77
110
|
|
|
78
111
|
# The thinking level (Thinking.resolve), the session's +own+ first.
|
|
79
112
|
def thinking(own:)
|
|
80
|
-
Thinking.resolve(
|
|
113
|
+
Thinking.resolve(lookup, session: own).first
|
|
81
114
|
rescue StandardError => e
|
|
82
115
|
unresolved(:thinking_unresolved, e, Thinking::DEFAULT)
|
|
83
116
|
end
|
|
@@ -85,23 +118,25 @@ module Samagotchi
|
|
|
85
118
|
# The thinking level with where it came from (SettingExplained), nil
|
|
86
119
|
# when it can't be resolved.
|
|
87
120
|
def thinking_explained(own:)
|
|
88
|
-
Thinking.explain(
|
|
121
|
+
Thinking.explain(lookup, session: own)
|
|
89
122
|
rescue StandardError => e
|
|
90
123
|
unresolved(:thinking_unresolved, e, nil)
|
|
91
124
|
end
|
|
92
125
|
|
|
93
126
|
# What a thinking level change costs the prompt cache
|
|
94
127
|
# (Thinking.cache_cost); the block gives the prompt profile, asked only
|
|
95
|
-
# on a native host. Raises.
|
|
128
|
+
# on a native host; a local chat host's is the one its name says. Raises.
|
|
96
129
|
# @return [Symbol]
|
|
97
130
|
def thinking_cache_cost(&)
|
|
98
|
-
Thinking.cache_cost(target,
|
|
131
|
+
return Thinking.cache_cost(target, inferred: ModelProfile.inferred_profile_name(bare)) if entry&.chat?
|
|
132
|
+
|
|
133
|
+
Thinking.cache_cost(target, profile: yield&.name)
|
|
99
134
|
end
|
|
100
135
|
|
|
101
136
|
# The reasoning_history level (ReasoningHistory.resolve), the session's
|
|
102
137
|
# +own+ first.
|
|
103
138
|
def reasoning_history(own:)
|
|
104
|
-
ReasoningHistory.resolve(
|
|
139
|
+
ReasoningHistory.resolve(lookup, session: own).first
|
|
105
140
|
rescue StandardError => e
|
|
106
141
|
unresolved(:reasoning_history_unresolved, e, ReasoningHistory::DEFAULT)
|
|
107
142
|
end
|
|
@@ -112,7 +147,7 @@ module Samagotchi
|
|
|
112
147
|
def reasoning_history_explained(own:, native: false)
|
|
113
148
|
return nil unless native || entry&.chat?
|
|
114
149
|
|
|
115
|
-
ReasoningHistory.explain(
|
|
150
|
+
ReasoningHistory.explain(lookup, session: own)
|
|
116
151
|
rescue StandardError => e
|
|
117
152
|
unresolved(:reasoning_history_unresolved, e, nil)
|
|
118
153
|
end
|
|
@@ -122,7 +157,7 @@ module Samagotchi
|
|
|
122
157
|
def reasoning_history_summary(own:)
|
|
123
158
|
return nil unless entry&.chat?
|
|
124
159
|
|
|
125
|
-
level, source = ReasoningHistory.resolve(
|
|
160
|
+
level, source = ReasoningHistory.resolve(lookup, session: own)
|
|
126
161
|
source ? ReasoningHistory.label(level, source) : nil
|
|
127
162
|
rescue StandardError => e
|
|
128
163
|
unresolved(:reasoning_history_unresolved, e, nil)
|
|
@@ -139,7 +174,7 @@ module Samagotchi
|
|
|
139
174
|
# [seconds, source] a cut waits for thinking (SteerCutAfter.resolve);
|
|
140
175
|
# the global and its name when it can't be resolved.
|
|
141
176
|
def steer_cut_after_explained
|
|
142
|
-
SteerCutAfter.resolve(
|
|
177
|
+
SteerCutAfter.resolve(lookup)
|
|
143
178
|
rescue StandardError => e
|
|
144
179
|
unresolved(:steer_cut_after_unresolved, e, [SteerCut.cut_after, "steer.cut_after"])
|
|
145
180
|
end
|
|
@@ -148,7 +183,7 @@ module Samagotchi
|
|
|
148
183
|
# (LLMContextStrategy.explain) under the session's +args+
|
|
149
184
|
# (LLMContextOverride#resolve_args), nil when it can't be resolved.
|
|
150
185
|
def llm_context_explained(args)
|
|
151
|
-
LLMContextStrategy.explain(
|
|
186
|
+
LLMContextStrategy.explain(lookup, **args)
|
|
152
187
|
rescue StandardError => e
|
|
153
188
|
unresolved(:llm_context_unresolved, e, nil)
|
|
154
189
|
end
|
|
@@ -156,14 +191,14 @@ module Samagotchi
|
|
|
156
191
|
# The LLM context layers its turns run under; [] when they can't be
|
|
157
192
|
# resolved.
|
|
158
193
|
def llm_context_layers(args)
|
|
159
|
-
LLMContextStrategy.resolve(
|
|
194
|
+
LLMContextStrategy.resolve(lookup, **args).active_layers
|
|
160
195
|
rescue StandardError => e
|
|
161
196
|
unresolved(:llm_context_unresolved, e, [])
|
|
162
197
|
end
|
|
163
198
|
|
|
164
199
|
# models:/hosts: window_tokens (ContextWindow.setting), nil when unset.
|
|
165
200
|
def window_setting
|
|
166
|
-
ContextWindow.setting(
|
|
201
|
+
ContextWindow.setting(lookup)
|
|
167
202
|
rescue StandardError => e
|
|
168
203
|
unresolved(:window_unresolved, e, nil)
|
|
169
204
|
end
|
|
@@ -171,7 +206,7 @@ module Samagotchi
|
|
|
171
206
|
# The window config.yml gives this model or its host
|
|
172
207
|
# (ContextWindow.setting), never asking a server: a session loaded on
|
|
173
208
|
# it (Engine#session=, before a worker's Bridge serves). nil when none.
|
|
174
|
-
def configured_context_window = ContextWindow.setting(
|
|
209
|
+
def configured_context_window = ContextWindow.setting(lookup)
|
|
175
210
|
|
|
176
211
|
# The window as its loop would see it (ChatLoop#context_window: a remote
|
|
177
212
|
# chat host has no /props, only its model list); nil when it can't be
|
|
@@ -183,7 +218,7 @@ module Samagotchi
|
|
|
183
218
|
adapter = registry.adapter_for(entry)
|
|
184
219
|
client = nil if adapter.remote?
|
|
185
220
|
end
|
|
186
|
-
ContextWindow.resolve(client: client, model: bare, adapter: adapter, setting: ContextWindow.setting(
|
|
221
|
+
ContextWindow.resolve(client: client, model: bare, adapter: adapter, setting: ContextWindow.setting(lookup))
|
|
187
222
|
rescue StandardError => e
|
|
188
223
|
unresolved(:window_unresolved, e, nil)
|
|
189
224
|
end
|
|
@@ -232,7 +267,7 @@ module Samagotchi
|
|
|
232
267
|
|
|
233
268
|
# Whether it sees images (VisionSupport.for) under +profile+.
|
|
234
269
|
def vision_support(profile)
|
|
235
|
-
VisionSupport.for(
|
|
270
|
+
VisionSupport.for(lookup, profile: profile, adapter: vision_adapter)
|
|
236
271
|
end
|
|
237
272
|
|
|
238
273
|
# A chat host's adapter (what it says of images), nil on a native host.
|
|
@@ -246,7 +281,7 @@ module Samagotchi
|
|
|
246
281
|
# Not cached here. Raises.
|
|
247
282
|
# @return [ModelProfile::Resolution]
|
|
248
283
|
def profile_resolution
|
|
249
|
-
ModelProfile.resolve(names: names, entry: entry, client: client, bare_model: bare)
|
|
284
|
+
ModelProfile.resolve(names: names, entry: entry, client: client, bare_model: bare, models: lookup.models)
|
|
250
285
|
end
|
|
251
286
|
|
|
252
287
|
# See Unresolved.fallback.
|
|
@@ -60,7 +60,7 @@ module Samagotchi
|
|
|
60
60
|
prompt_sections: @prompt_sections,
|
|
61
61
|
prompt_changed: -> { prompt_changed! },
|
|
62
62
|
judgments: @judgments,
|
|
63
|
-
hook_failed: ->(hook, failed) { @hook_routes.plugin_hook_failed(hook, failed) }
|
|
63
|
+
hook_failed: ->(hook, failed, effect: :log) { @hook_routes.plugin_hook_failed(hook, failed, effect: effect) }
|
|
64
64
|
)
|
|
65
65
|
# What plugins show while they load (an MCP server that didn't
|
|
66
66
|
# start) waits for the first turn, beside the load warnings: no UI
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../recap_place"
|
|
4
|
+
|
|
3
5
|
module Samagotchi
|
|
4
6
|
class Engine
|
|
5
7
|
# The idle recap (IdleRecap) and the shared idle scheduler: the recap job
|
|
@@ -96,9 +98,10 @@ module Samagotchi
|
|
|
96
98
|
@recap ? @recap.request_now : :off
|
|
97
99
|
end
|
|
98
100
|
|
|
99
|
-
# The recap saved with the current session (recap.json),
|
|
100
|
-
# user turns came after it (0: it is current)
|
|
101
|
-
#
|
|
101
|
+
# The recap saved with the current session (recap.json), how many
|
|
102
|
+
# user turns came after it (0: it is current), and the stamp of the
|
|
103
|
+
# last turn it covers (RecapPlace: where a page puts it; nil, the count).
|
|
104
|
+
# @return [Hash, nil] {text:, covered:, turns_since:, opening_id:, created_at:}
|
|
102
105
|
def saved_recap
|
|
103
106
|
state = @recap&.state
|
|
104
107
|
return nil unless state
|
|
@@ -106,7 +109,8 @@ module Samagotchi
|
|
|
106
109
|
messages = @session&.messages || []
|
|
107
110
|
covered = state[:covered].to_i
|
|
108
111
|
turns_since = Array(messages).drop(covered).count { |m| Steer.turn_opening?(m) }
|
|
109
|
-
{ text: state[:text], covered: covered, turns_since: turns_since,
|
|
112
|
+
{ text: state[:text], covered: covered, turns_since: turns_since, opening_id: RecapPlace.opening_id(messages, covered),
|
|
113
|
+
created_at: state[:created_at] }
|
|
110
114
|
end
|
|
111
115
|
|
|
112
116
|
# Start the shared idle scheduler (reminders + optional recap). The recap
|
data/lib/samagotchi/engine.rb
CHANGED
|
@@ -230,7 +230,9 @@ module Samagotchi
|
|
|
230
230
|
@chat_backend_mutex = Mutex.new
|
|
231
231
|
@side_judgments = SideJudgments.new
|
|
232
232
|
@judgments = build_judgments
|
|
233
|
-
|
|
233
|
+
# Taken once: a running worker keeps the hosts it started with. A
|
|
234
|
+
# client (specs) goes on a registry of its own, never the shared one.
|
|
235
|
+
@host_registry = host_registry || (client ? HostRegistry.new : HostRegistry.current)
|
|
234
236
|
# The turn stages over the objectives (its audit, view and proposals)
|
|
235
237
|
# and the Verify/Revise anchors (TurnStages).
|
|
236
238
|
@objectives_stage = build_objectives_stage
|
|
@@ -355,12 +357,7 @@ module Samagotchi
|
|
|
355
357
|
# toolkit, the tool index, peer sources), then the plugins themselves
|
|
356
358
|
# unless +load+ is false (see #initialize for when).
|
|
357
359
|
def build_tools_and_plugins(load:, task_finish_notes:)
|
|
358
|
-
|
|
359
|
-
# that raises at fire time is told the same way a chi.on handler is:
|
|
360
|
-
# once per hook in this worker. The guard covers a hook that raises
|
|
361
|
-
# before the routes are there (nothing in the Engine's path does).
|
|
362
|
-
@extension_load = ExtensionLoad.new(hook_failures: @guardrail_failures,
|
|
363
|
-
hook_failed: ->(hook, failed, effect: nil) { @hook_routes&.plugin_hook_failed(hook, failed, effect: effect) })
|
|
360
|
+
@extension_load = ExtensionLoad.new(hook_failures: @guardrail_failures)
|
|
364
361
|
@hooks = @extension_load.hooks
|
|
365
362
|
# The tools this session offers (the prompts' declarations and the
|
|
366
363
|
# kernel's dispatch): the built-ins, per Engine. execute.description
|
|
@@ -718,7 +715,10 @@ module Samagotchi
|
|
|
718
715
|
# (its limit goes in last_turn). The end event names its turn
|
|
719
716
|
# (`turn_id:`, as :turn_started does) and, for a Verify turn,
|
|
720
717
|
# `verify: true` (its opening's stamp: a page watching it live offers
|
|
721
|
-
# no Revise on its answer, verdict or not, as a reload does).
|
|
718
|
+
# no Revise on its answer, verdict or not, as a reload does). When the
|
|
719
|
+
# turn keeps messages, the event also names its own range in them,
|
|
720
|
+
# `kept[kept_from...kept_to]` (display-side: Bridge::TurnDrawings
|
|
721
|
+
# reads it; no message gains a key).
|
|
722
722
|
def end_turn(turn, outcome, save: false, exhausted: false)
|
|
723
723
|
seconds = turn.elapsed
|
|
724
724
|
turn.outcome = outcome
|
|
@@ -727,6 +727,7 @@ module Samagotchi
|
|
|
727
727
|
event[:turn_id] = turn.id
|
|
728
728
|
event[:verify] = true if turn.verify
|
|
729
729
|
revises = @verify_anchors.stamp_opening(kept, turn, outcome)
|
|
730
|
+
kept_range!(event, turn, kept)
|
|
730
731
|
replace_session_messages(turn.session, kept) if kept
|
|
731
732
|
verdict = @verify_anchors.record_verdict(turn, outcome)
|
|
732
733
|
event[:verdict] = verdict if verdict
|
|
@@ -744,6 +745,20 @@ module Samagotchi
|
|
|
744
745
|
end
|
|
745
746
|
private :end_turn
|
|
746
747
|
|
|
748
|
+
# The end event's kept_from/kept_to (#end_turn): only when the turn
|
|
749
|
+
# keeps messages and its start is known (#turn_messages ran). An ending
|
|
750
|
+
# that replaces an earlier turn's note at the history's tail
|
|
751
|
+
# (TurnNote.replace_trailing on a Continue stopped before its first step)
|
|
752
|
+
# leaves only system messages and its own note after it, so the range
|
|
753
|
+
# still starts at or before the turn's note and draws nothing else.
|
|
754
|
+
def kept_range!(event, turn, kept)
|
|
755
|
+
return unless kept && turn.history_size
|
|
756
|
+
|
|
757
|
+
event[:kept_from] = turn.history_size
|
|
758
|
+
event[:kept_to] = kept.size
|
|
759
|
+
end
|
|
760
|
+
private :kept_range!
|
|
761
|
+
|
|
747
762
|
# What a notice may stand in for (hook_notify's fallback_for:).
|
|
748
763
|
NOTICE_FALLBACKS = HookRoutes::NOTICE_FALLBACKS
|
|
749
764
|
|
|
@@ -806,8 +821,11 @@ module Samagotchi
|
|
|
806
821
|
# +wall+: the wall time it started (MessageTime.now, once): the
|
|
807
822
|
# :turn_started event's at and its user message's (and reminders') stamp.
|
|
808
823
|
# +verify+, +revises+: a Verify turn's claim, a Revise turn's draft.
|
|
824
|
+
# +history_size+: turn.messages' size under the system head, before
|
|
825
|
+
# the reminders and the prompt (#turn_messages): where the turn's own
|
|
826
|
+
# messages start in what it keeps (the end event's kept_from).
|
|
809
827
|
Turn = Struct.new(:context, :prompt, :continue, :on_event, :started_at, :messages, :ended, :prepared_settings,
|
|
810
|
-
:limit, :resumed, :resume_from, :wall, :verify, :revises, :outcome) do
|
|
828
|
+
:limit, :resumed, :resume_from, :wall, :verify, :revises, :outcome, :history_size) do
|
|
811
829
|
# The turn's id (:turn_started's turn_id, its metrics record's id and
|
|
812
830
|
# its saved prompt's), session, origin and controller: its context's.
|
|
813
831
|
def id = context.id
|
|
@@ -1124,6 +1142,9 @@ module Samagotchi
|
|
|
1124
1142
|
# Built once per Engine (and again after a model switch) so the prompt
|
|
1125
1143
|
# prefix, and the model server's KV cache for it, stay stable.
|
|
1126
1144
|
turn.messages = ContextNote.with_system_head(turn.messages, { role: "system", content: system_prompt })
|
|
1145
|
+
# Where the turn's own messages start (the end event's kept_from):
|
|
1146
|
+
# after the head this may have added, before the reminders and prompt.
|
|
1147
|
+
turn.history_size = turn.messages.size
|
|
1127
1148
|
# Explicit --memory preloads are now known after system prompt build.
|
|
1128
1149
|
@used_memories.add(activated_memory_names).absorb(session)
|
|
1129
1150
|
|
data/lib/samagotchi/events.rb
CHANGED
|
@@ -13,8 +13,9 @@ module Samagotchi
|
|
|
13
13
|
# (#kept_turn?).
|
|
14
14
|
TURN_KEPT = %i[turn_completed turn_canceled].freeze
|
|
15
15
|
# The kinds of a running turn's parts (Bridge::TurnAccumulator), which a
|
|
16
|
-
# joining UI replays as live events
|
|
17
|
-
|
|
16
|
+
# joining UI replays as live events (a card part only in a finished
|
|
17
|
+
# turn's drawing: a join takes its cards from CardStore).
|
|
18
|
+
PART_KINDS = %w[generation thinking text tool input steer reminder notice card].freeze
|
|
18
19
|
# Types no Engine emits that a UI handles: the Bridge's stream frames
|
|
19
20
|
# and the web's one synthetic replay event (a prompt merged into the
|
|
20
21
|
# turn, turn_events.js snapshotEvents).
|
|
@@ -7,20 +7,13 @@ require_relative "plugin/loader"
|
|
|
7
7
|
|
|
8
8
|
module Samagotchi
|
|
9
9
|
# What an Engine loads from outside chi as it starts: config.yml's hooks,
|
|
10
|
-
# installed bundles'
|
|
11
|
-
# settings). It holds what plugins showed while they loaded (notices,
|
|
10
|
+
# installed bundles' plugins, and config.yml `bundles:` (their settings). It holds what plugins showed while they loaded (notices,
|
|
12
11
|
# cards): no UI is there yet, so the Engine announces them on the first
|
|
13
12
|
# turn (Engine#announce_load_events!).
|
|
14
13
|
class ExtensionLoad
|
|
15
14
|
# @param hook_failures [Guardrails::LoadFailures] where hooks that fail to load go
|
|
16
|
-
|
|
17
|
-
# an installed bundle's hook file raises at fire time: the Engine's
|
|
18
|
-
# HookRoutes, so the user is told once per hook. nil where there is no
|
|
19
|
-
# UI to tell (the test kit's sandbox): a hook that raises then says
|
|
20
|
-
# nothing, and raises nothing.
|
|
21
|
-
def initialize(hook_failures:, hook_failed: nil)
|
|
15
|
+
def initialize(hook_failures:)
|
|
22
16
|
@hook_failures = hook_failures
|
|
23
|
-
@hook_failed = hook_failed
|
|
24
17
|
@loading = false
|
|
25
18
|
@held_events = []
|
|
26
19
|
end
|
|
@@ -40,27 +33,30 @@ module Samagotchi
|
|
|
40
33
|
end
|
|
41
34
|
|
|
42
35
|
# The hooks: config.yml's (Hooks::Loader; an empty Registry without a
|
|
43
|
-
# hooks config)
|
|
36
|
+
# hooks config). An installed record that still has class-file hooks (a
|
|
37
|
+
# 0.157 install) adds a not-required failure each, which the Engine
|
|
38
|
+
# announces at the first turn: they don't load (#tell_retired_hooks).
|
|
44
39
|
# @return [Hooks::Registry]
|
|
45
40
|
def hooks
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
registry
|
|
41
|
+
tell_retired_hooks
|
|
42
|
+
hooks_from_config
|
|
49
43
|
end
|
|
50
44
|
|
|
51
45
|
# Load the installed bundles' plugins into +registries+; one that fails
|
|
52
|
-
# goes to +failures+,
|
|
53
|
-
#
|
|
46
|
+
# goes to +failures+, a required one (a guardrail plugin) to the hook
|
|
47
|
+
# failures, where it denies every tool call, and the rest still load.
|
|
48
|
+
# The block runs after them, still loading: what it shows is held with
|
|
49
|
+
# theirs.
|
|
54
50
|
def load_plugins(registries, failures:)
|
|
55
51
|
@held_events = []
|
|
56
52
|
@loading = true
|
|
57
|
-
Plugin::Loader.load_installed(registries, failures: failures, settings: bundle_settings)
|
|
53
|
+
Plugin::Loader.load_installed(registries, failures: failures, guardrail_failures: @hook_failures, settings: bundle_settings)
|
|
54
|
+
.tap { yield if block_given? }
|
|
58
55
|
ensure
|
|
59
56
|
@loading = false
|
|
60
57
|
end
|
|
61
58
|
|
|
62
|
-
# config.yml `bundles:` (ConfigFile.bundle_settings), read once
|
|
63
|
-
# bundle hooks and the plugins both want it.
|
|
59
|
+
# config.yml `bundles:` (ConfigFile.bundle_settings), read once.
|
|
64
60
|
def bundle_settings
|
|
65
61
|
@bundle_settings ||= Samagotchi::ConfigFile.bundle_settings
|
|
66
62
|
end
|
|
@@ -75,36 +71,22 @@ module Samagotchi
|
|
|
75
71
|
Hooks::Registry.new
|
|
76
72
|
end
|
|
77
73
|
|
|
78
|
-
|
|
74
|
+
# One failure per record whose hooks: map isn't empty; an empty
|
|
75
|
+
# hooks/ dir is not one, nor a manifest.json that doesn't read (the
|
|
76
|
+
# plugin loader and the rules report that: a record holding only hooks/
|
|
77
|
+
# with an unreadable manifest.json is silent at start, as its hooks
|
|
78
|
+
# wouldn't load anyway; `chi bundle status` lists it unreadable).
|
|
79
|
+
def tell_retired_hooks
|
|
79
80
|
require_relative "memory_bundle/provenance"
|
|
80
|
-
settings = bundle_settings
|
|
81
81
|
MemoryBundle::Provenance.each_installed(holding: :hooks) do |bundle_name, bundle|
|
|
82
|
-
if bundle.error?
|
|
83
|
-
Log.warn(:hooks, "bundle_manifest_invalid", echo: "[samagotchi:hooks] bundle '#{bundle_name}': #{bundle.error}; its hooks are not loaded",
|
|
84
|
-
bundle: bundle_name)
|
|
85
|
-
@hook_failures.add("hooks (bundle #{bundle_name})", bundle.error, required: false)
|
|
86
|
-
next
|
|
87
|
-
end
|
|
88
|
-
bundle_dir = File.join(MemoryBundle::Provenance.bundles_dir, bundle_name)
|
|
89
|
-
hooks_dir = File.join(bundle_dir, "hooks")
|
|
90
|
-
manifest = bundle.manifest
|
|
91
|
-
if manifest.experimental?
|
|
92
|
-
Log.info(:hooks, "experimental_bundle", echo: "[hooks] Bundle '#{bundle_name}' is experimental — its hooks may change or misbehave.", bundle: bundle_name)
|
|
93
|
-
end
|
|
94
|
-
begin
|
|
95
|
-
Hooks::BundleLoader.load(bundle_name: bundle_name, hooks_dir: hooks_dir, metadata: manifest.hooks, registry: registry,
|
|
96
|
-
failures: @hook_failures, settings: settings[bundle_name.to_s] || {},
|
|
97
|
-
manifest: manifest, hook_failed: @hook_failed)
|
|
98
|
-
rescue Exception => e # rubocop:disable Lint/RescueException -- a hook's SyntaxError or exit must not stop chi
|
|
99
|
-
raise if e.is_a?(SignalException) # Ctrl-C and kill signals are the user's
|
|
82
|
+
next if bundle.error?
|
|
100
83
|
|
|
101
|
-
|
|
102
|
-
|
|
84
|
+
Log.warn(:hooks, "bundle_hooks_retired", bundle: bundle_name, hooks: bundle.hooks.size)
|
|
85
|
+
@hook_failures.add("hooks (bundle #{bundle_name})", MemoryBundle::Manifest::HooksRetired::WORDS, required: false)
|
|
103
86
|
end
|
|
104
|
-
rescue
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Log.error(:hooks, "bundles_load_failed", echo: "[samagotchi:hooks] failed to load bundle hooks: #{e.class}: #{e.message}", error: e.class.name)
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
Log.error(:hooks, "bundles_load_failed", echo: "[samagotchi:hooks] failed to read the installed bundles: #{e.class}: #{e.message}",
|
|
89
|
+
error: e.class.name)
|
|
108
90
|
end
|
|
109
91
|
end
|
|
110
92
|
end
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Samagotchi
|
|
4
4
|
module Guardrails
|
|
5
|
-
# What failed to load at Engine start: hooks,
|
|
6
|
-
# rule files
|
|
5
|
+
# What failed to load at Engine start: config hooks, a required
|
|
6
|
+
# plugin, rule files, an older chi's class-file bundle hooks (which no
|
|
7
|
+
# longer load). A required one makes the gate deny every tool call (a
|
|
7
8
|
# guardrail that silently vanished is the failure this guards against);
|
|
8
9
|
# the rest are only announced.
|
|
9
10
|
class LoadFailures
|
|
@@ -153,6 +153,17 @@ module Samagotchi
|
|
|
153
153
|
(low == low_dir || low.start_with?(File.join(low_dir, ""))) && folds_case?(dir)
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
# Whether +root+ is the home folder or a folder above it (symlinks
|
|
157
|
+
# resolved on both; false for a blank one). ReviewPaths (a participant
|
|
158
|
+
# turn's project) and MemoryPaths (where a chi project marker is
|
|
159
|
+
# ignored) ask this.
|
|
160
|
+
# @param root [String, nil] @param home [String, nil]
|
|
161
|
+
def home_root?(root, home)
|
|
162
|
+
return false if home.to_s.empty? || root.to_s.empty?
|
|
163
|
+
|
|
164
|
+
under?(real(home.to_s), real(root.to_s))
|
|
165
|
+
end
|
|
166
|
+
|
|
156
167
|
# Whether +path+ is in +dir+, not +dir+ itself (.under?'s terms).
|
|
157
168
|
def strictly_under?(path, dir, fold: true)
|
|
158
169
|
return true if path.start_with?(File.join(dir, ""))
|
|
@@ -59,11 +59,7 @@ module Samagotchi
|
|
|
59
59
|
# review turn reads nothing there (#refusal: HOME_ROOT); a pair turn
|
|
60
60
|
# asks the owner before every write (PairBoundary).
|
|
61
61
|
# @param root [String] @param home [String, nil]
|
|
62
|
-
def self.home_root?(root, home)
|
|
63
|
-
return false if home.to_s.empty? || root.to_s.empty?
|
|
64
|
-
|
|
65
|
-
PathResolve.under?(PathResolve.real(home.to_s), PathResolve.real(root.to_s))
|
|
66
|
-
end
|
|
62
|
+
def self.home_root?(root, home) = PathResolve.home_root?(root, home)
|
|
67
63
|
|
|
68
64
|
# @param root [String, nil] the session's project root (the work tree
|
|
69
65
|
# top of its folder); nil or blank refuses every path
|
|
@@ -190,7 +190,7 @@ module Samagotchi
|
|
|
190
190
|
threshold = threshold(options[:threshold])
|
|
191
191
|
return usage_error(threshold) if threshold.is_a?(String)
|
|
192
192
|
|
|
193
|
-
client = @client || SystemOne::Client.from_config(host_registry: HostRegistry.
|
|
193
|
+
client = @client || SystemOne::Client.from_config(host_registry: HostRegistry.current, mode: Config.get("guardrails.system_one.mode"), model: options[:model])
|
|
194
194
|
if client.nil?
|
|
195
195
|
error_line("chi guardrails replay: no System 1 client — set guardrails.system_one.mode to audit or on " \
|
|
196
196
|
"and system_one.host in config.yml")
|
|
@@ -21,7 +21,7 @@ module Samagotchi
|
|
|
21
21
|
# How much of a failed hook's error #plugin_hook_failed shows.
|
|
22
22
|
HOOK_FAILURE_CHARS = 300
|
|
23
23
|
# What the notice says the hook now does, by the policy Hooks.wrap gave it
|
|
24
|
-
# (a chi.on handler
|
|
24
|
+
# (a chi.on handler :log, or :deny for an on_error: :fail_closed one).
|
|
25
25
|
HOOK_FAILURE_EFFECTS = {
|
|
26
26
|
log: "it's skipped, and only the log has its next failures",
|
|
27
27
|
skip: "it's skipped, and its next failures are silent",
|
|
@@ -98,7 +98,7 @@ module Samagotchi
|
|
|
98
98
|
nil
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
# A
|
|
101
|
+
# A chi.on handler raised (Hooks.wrap; the log has
|
|
102
102
|
# every firing of a :log one). Its first failure on this Engine, so in
|
|
103
103
|
# this worker, tells the user, as a plugin that failed to load does; then
|
|
104
104
|
# only the log. One line per label — file, bundle and event — so a
|