samagotchi 0.152.0 → 0.154.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 +45 -1
- data/README.md +2 -3
- data/bin/chi +23 -24
- data/docs/architecture.md +30 -33
- data/docs/broadcast.md +5 -5
- data/docs/bundle-authoring.md +1 -1
- data/docs/bundles.md +93 -12
- data/docs/cli.md +83 -99
- data/docs/configuration.md +46 -40
- data/docs/context.md +3 -3
- data/docs/desktop.md +1 -1
- data/docs/guardrails.md +15 -15
- data/docs/hooks.md +13 -12
- data/docs/internals/anthropic.md +3 -3
- data/docs/internals/attached-context.md +5 -2
- data/docs/internals/background-tasks.md +3 -4
- data/docs/internals/block-renderers.md +4 -0
- data/docs/internals/bridge.md +3 -3
- data/docs/internals/context-telemetry.md +1 -1
- data/docs/internals/engine.md +42 -12
- data/docs/internals/objectives.md +6 -5
- data/docs/internals/participants.md +8 -8
- data/docs/internals/prompt-caching.md +8 -4
- data/docs/internals/remote-sessions.md +7 -7
- data/docs/internals/web-frontend.md +20 -17
- data/docs/internals/web-push.md +1 -1
- data/docs/memory.md +2 -2
- data/docs/plugins.md +52 -43
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +25 -25
- data/docs/sub-agent.md +2 -2
- data/docs/system-one.md +1 -1
- data/docs/testing.md +18 -8
- data/lib/samagotchi/answer_text.rb +34 -0
- data/lib/samagotchi/bridge.rb +43 -32
- data/lib/samagotchi/bridge_client.rb +10 -9
- data/lib/samagotchi/broadcast/recipients.rb +2 -2
- data/lib/samagotchi/broadcast/triage.rb +1 -1
- data/lib/samagotchi/broadcast_command.rb +5 -5
- data/lib/samagotchi/bundle_command/try/command.rb +1 -1
- data/lib/samagotchi/bundle_command/try/run.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +2 -2
- data/lib/samagotchi/bundles/decision-card/manifest.yml +4 -4
- data/lib/samagotchi/bundles/decision-card/plugin.rb +1 -9
- data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +3 -4
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/bundles/web-search/manifest.yml +13 -0
- data/lib/samagotchi/bundles/web-search/plugin.rb +174 -0
- data/lib/samagotchi/child_move.rb +3 -3
- data/lib/samagotchi/commands/registry.rb +6 -6
- data/lib/samagotchi/composer_route.rb +4 -5
- data/lib/samagotchi/config.rb +14 -12
- data/lib/samagotchi/context_candidate_choice.rb +1 -1
- data/lib/samagotchi/context_candidates.rb +4 -3
- data/lib/samagotchi/continue_offer.rb +1 -1
- data/lib/samagotchi/debug_log.rb +1 -1
- data/lib/samagotchi/degrade.rb +75 -0
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +2 -2
- data/lib/samagotchi/engine/cards.rb +87 -0
- data/lib/samagotchi/engine/context_judge.rb +138 -0
- data/lib/samagotchi/engine/events.rb +131 -0
- data/lib/samagotchi/engine/guardrails.rb +87 -0
- data/lib/samagotchi/engine/hooks.rb +119 -0
- data/lib/samagotchi/engine/messages.rb +124 -0
- data/lib/samagotchi/engine/model_settings.rb +127 -0
- data/lib/samagotchi/engine/models.rb +261 -0
- data/lib/samagotchi/engine/objectives.rb +116 -0
- data/lib/samagotchi/engine/participants.rb +86 -0
- data/lib/samagotchi/engine/plugins.rb +208 -0
- data/lib/samagotchi/engine/questions.rb +98 -0
- data/lib/samagotchi/engine/recap.rb +130 -0
- data/lib/samagotchi/engine/session.rb +131 -0
- data/lib/samagotchi/engine/side_requests.rb +135 -0
- data/lib/samagotchi/engine/steering.rb +134 -0
- data/lib/samagotchi/engine/toolkit.rb +269 -0
- data/lib/samagotchi/engine/turn_end.rb +180 -0
- data/lib/samagotchi/engine/turn_helpers.rb +160 -0
- data/lib/samagotchi/engine/verify.rb +153 -0
- data/lib/samagotchi/engine.rb +63 -2642
- data/lib/samagotchi/grants.rb +575 -0
- data/lib/samagotchi/guardrail_wiring.rb +2 -2
- data/lib/samagotchi/guardrails/context.rb +2 -3
- data/lib/samagotchi/guardrails/parent_approvals.rb +2 -2
- data/lib/samagotchi/guardrails/parent_policy.rb +2 -2
- data/lib/samagotchi/guardrails/review_boundary.rb +4 -3
- data/lib/samagotchi/hook_routes.rb +3 -3
- data/lib/samagotchi/idle_recap.rb +4 -5
- data/lib/samagotchi/idle_reminders.rb +3 -4
- data/lib/samagotchi/iteration_limit.rb +2 -2
- data/lib/samagotchi/kernel_loop.rb +10 -11
- data/lib/samagotchi/launch_mode.rb +13 -32
- data/lib/samagotchi/level_setting_command.rb +3 -2
- data/lib/samagotchi/llm_context_command.rb +3 -2
- data/lib/samagotchi/llm_context_stale.rb +2 -2
- data/lib/samagotchi/log.rb +7 -7
- data/lib/samagotchi/log_line.rb +1 -1
- data/lib/samagotchi/log_path.rb +1 -1
- data/lib/samagotchi/message_parts.rb +3 -2
- data/lib/samagotchi/note_command.rb +1 -1
- data/lib/samagotchi/note_delivery.rb +18 -8
- data/lib/samagotchi/objectives.rb +24 -3
- data/lib/samagotchi/objectives_check.rb +8 -27
- data/lib/samagotchi/objectives_command.rb +4 -69
- data/lib/samagotchi/objectives_edit.rb +87 -0
- data/lib/samagotchi/output_formatter.rb +7 -1
- data/lib/samagotchi/owner_lock.rb +7 -1
- data/lib/samagotchi/parent_answers_command.rb +5 -4
- data/lib/samagotchi/participant_exposure.rb +42 -0
- data/lib/samagotchi/participant_mode.rb +1 -1
- data/lib/samagotchi/participant_name.rb +1 -1
- data/lib/samagotchi/participants.rb +17 -17
- data/lib/samagotchi/participants_watch.rb +1 -1
- data/lib/samagotchi/pending_input_queue.rb +2 -2
- data/lib/samagotchi/plugin/api.rb +3 -2
- data/lib/samagotchi/plugin/context.rb +21 -7
- data/lib/samagotchi/plugin/peer.rb +2 -1
- data/lib/samagotchi/plugin/sessions.rb +6 -4
- data/lib/samagotchi/plugin_tasks.rb +1 -2
- data/lib/samagotchi/private_files.rb +29 -0
- data/lib/samagotchi/project_env.rb +2 -2
- data/lib/samagotchi/question_desk.rb +8 -7
- data/lib/samagotchi/recap_command.rb +2 -1
- data/lib/samagotchi/recap_setup.rb +1 -1
- data/lib/samagotchi/recap_store.rb +1 -1
- data/lib/samagotchi/redact.rb +86 -0
- data/lib/samagotchi/reminder_queue.rb +2 -2
- data/lib/samagotchi/remote_attach_command.rb +1 -1
- data/lib/samagotchi/reply_wait/poll.rb +2 -2
- data/lib/samagotchi/review_start.rb +1 -1
- data/lib/samagotchi/run_options.rb +6 -6
- data/lib/samagotchi/self_report.rb +42 -39
- data/lib/samagotchi/send_command/busy.rb +1 -1
- data/lib/samagotchi/send_command.rb +4 -1
- data/lib/samagotchi/session.rb +11 -2
- data/lib/samagotchi/session_access/contract.rb +3 -3
- data/lib/samagotchi/session_access/local.rb +9 -9
- data/lib/samagotchi/session_access/remote.rb +17 -7
- data/lib/samagotchi/session_access.rb +4 -4
- data/lib/samagotchi/session_archive_command.rb +2 -2
- data/lib/samagotchi/session_chain.rb +8 -6
- data/lib/samagotchi/session_commands.rb +45 -48
- data/lib/samagotchi/session_continue.rb +1 -1
- data/lib/samagotchi/session_delete_command.rb +4 -4
- data/lib/samagotchi/session_manager.rb +59 -36
- data/lib/samagotchi/session_metrics.rb +2 -2
- data/lib/samagotchi/session_model_settings.rb +3 -2
- data/lib/samagotchi/session_participants.rb +1 -1
- data/lib/samagotchi/session_retention.rb +3 -3
- data/lib/samagotchi/sessions_command.rb +4 -4
- data/lib/samagotchi/share_command.rb +10 -10
- data/lib/samagotchi/steer.rb +1 -1
- data/lib/samagotchi/system_prompt.rb +102 -109
- data/lib/samagotchi/task_watch.rb +1 -1
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +72 -10
- data/lib/samagotchi/terminal_ui/attached_loop/composer.rb +12 -9
- data/lib/samagotchi/terminal_ui/attached_loop/exit_commands.rb +16 -0
- data/lib/samagotchi/terminal_ui/attached_loop/questions.rb +2 -3
- data/lib/samagotchi/terminal_ui/attached_loop.rb +17 -8
- data/lib/samagotchi/terminal_ui/attached_view.rb +2 -3
- data/lib/samagotchi/terminal_ui/event_renderer.rb +3 -5
- data/lib/samagotchi/terminal_ui/formatting.rb +7 -7
- data/lib/samagotchi/terminal_ui/input_support.rb +1 -7
- data/lib/samagotchi/terminal_ui/line_reader.rb +2 -2
- data/lib/samagotchi/terminal_ui/live_region.rb +2 -2
- data/lib/samagotchi/terminal_ui/log_tail.rb +135 -0
- data/lib/samagotchi/terminal_ui/one_shot_sink.rb +2 -2
- data/lib/samagotchi/terminal_ui/plain_surface.rb +1 -1
- data/lib/samagotchi/terminal_ui/question_prompt.rb +4 -5
- data/lib/samagotchi/terminal_ui/reline_seam.rb +3 -3
- data/lib/samagotchi/terminal_ui/status_row.rb +7 -8
- data/lib/samagotchi/terminal_ui.rb +142 -1387
- data/lib/samagotchi/title_command.rb +2 -2
- data/lib/samagotchi/tool_runner.rb +14 -16
- data/lib/samagotchi/toolkit.rb +8 -5
- data/lib/samagotchi/toolkit_binding.rb +30 -5
- data/lib/samagotchi/toolkit_command.rb +2 -1
- data/lib/samagotchi/toolkit_report_command.rb +9 -5
- data/lib/samagotchi/toolkit_suggest.rb +12 -6
- data/lib/samagotchi/tools/builtin_calls.rb +14 -10
- data/lib/samagotchi/tools/deferred_tools.rb +2 -2
- data/lib/samagotchi/tools/delegate.rb +3 -1
- data/lib/samagotchi/tools/delegate_cursor.rb +3 -3
- data/lib/samagotchi/tools/delegate_wait.rb +1 -1
- data/lib/samagotchi/tools/objectives_tool.rb +2 -2
- data/lib/samagotchi/tools/registry.rb +44 -20
- data/lib/samagotchi/tools/send_note.rb +2 -1
- data/lib/samagotchi/tools/tool_index.rb +27 -21
- data/lib/samagotchi/turn_endings.rb +2 -2
- data/lib/samagotchi/turn_flow.rb +4 -5
- data/lib/samagotchi/update_hint.rb +2 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +39 -50
- data/lib/samagotchi/web/participant_access.rb +2 -2
- data/lib/samagotchi/web/participant_review.rb +5 -5
- data/lib/samagotchi/web/participant_stream_filter.rb +8 -8
- data/lib/samagotchi/web/participant_view.rb +2 -82
- data/lib/samagotchi/web/participants_admin.rb +1 -1
- data/lib/samagotchi/web/public/app.js +61 -357
- data/lib/samagotchi/web/public/block_host.js +35 -8
- data/lib/samagotchi/web/public/block_renderers.css +9 -5
- data/lib/samagotchi/web/public/chains.js +2 -2
- data/lib/samagotchi/web/public/claim_link.js +4 -4
- data/lib/samagotchi/web/public/composer_mode.js +110 -0
- data/lib/samagotchi/web/public/format.js +3 -3
- data/lib/samagotchi/web/public/owner_claim.js +81 -0
- data/lib/samagotchi/web/public/owner_verify.js +112 -0
- data/lib/samagotchi/web/public/session_fields.js +3 -3
- data/lib/samagotchi/web/public/strip_grip.js +3 -3
- data/lib/samagotchi/web/public/strip_hidden.js +65 -0
- data/lib/samagotchi/web/public/version_notices.js +132 -0
- data/lib/samagotchi/web/push/store.rb +1 -1
- data/lib/samagotchi/web/session_access_routes.rb +5 -4
- data/lib/samagotchi/web/session_control.rb +12 -8
- data/lib/samagotchi/web/session_create.rb +1 -1
- data/lib/samagotchi/web/session_hub.rb +2 -2
- data/lib/samagotchi/web/session_show.rb +2 -2
- data/lib/samagotchi/web/session_summary.rb +1 -1
- data/lib/samagotchi/worker.rb +106 -9
- data/lib/samagotchi/worker_link.rb +3 -3
- data/lib/samagotchi/working_set_command.rb +3 -2
- metadata +36 -6
- data/lib/samagotchi/terminal_ui/between_turns.rb +0 -138
- data/lib/samagotchi/terminal_ui/repl_input.rb +0 -152
- data/lib/samagotchi/web/grants.rb +0 -575
- data/lib/samagotchi/web/participant_exposure.rb +0 -44
- data/lib/samagotchi/web/private_files.rb +0 -31
|
@@ -47,7 +47,7 @@ module Samagotchi
|
|
|
47
47
|
# What a continue does with the previous link's children: +move+ the
|
|
48
48
|
# direct children whose subtree has an open node (they go to the new
|
|
49
49
|
# link, their subtrees with them), +refuse+ the open ones it can't move
|
|
50
|
-
# (SessionContinue::OpenChild, why: an older chi worker or a chi
|
|
50
|
+
# (SessionContinue::OpenChild, why: an older chi worker or a one-turn chi run).
|
|
51
51
|
Plan = Data.define(:move, :refuse)
|
|
52
52
|
# A move under way: from the session, the children moved, and those
|
|
53
53
|
# already sent their note (so a move finished again notes no one twice).
|
|
@@ -87,14 +87,14 @@ module Samagotchi
|
|
|
87
87
|
Plan.new(move: move, refuse: refuse)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
# Why a child's live owner keeps it from moving: a chi
|
|
90
|
+
# Why a child's live owner keeps it from moving: a one-turn chi run holds its
|
|
91
91
|
# own Session and never reads the override; a worker on an older chi
|
|
92
92
|
# neither. A worker with no sidecar yet is starting, on the newest chi
|
|
93
93
|
# installed (SessionManager.worker_command): it moves.
|
|
94
94
|
# @return [String, nil]
|
|
95
95
|
def unmovable(child_id, state_dir:)
|
|
96
96
|
link = WorkerLink.resolve(Session.session_dir(child_id, state_dir: state_dir), unlink: false)
|
|
97
|
-
return "
|
|
97
|
+
return "#{OwnerLock::TUI_BUSY}; let it finish" if link.owner&.tui?
|
|
98
98
|
return nil if !link.live? || link.features.include?(FEATURE)
|
|
99
99
|
|
|
100
100
|
"older chi worker#{" #{link.version}" if link.version}; restart it"
|
|
@@ -29,9 +29,9 @@ module Samagotchi
|
|
|
29
29
|
# @!attribute mid_turn [Symbol, #call] what a line of it does while a
|
|
30
30
|
# turn runs (MID_TURN): a symbol, or a lambda line (stripped) → one
|
|
31
31
|
# @!attribute local [Boolean] the UI runs it; completion and help only
|
|
32
|
-
# @!attribute uis [Array<Symbol>, nil] a local entry's UIs (:
|
|
33
|
-
# :
|
|
34
|
-
#
|
|
32
|
+
# @!attribute uis [Array<Symbol>, nil] a local entry's UIs (:attached,
|
|
33
|
+
# :web) for completion and /help; nil means every UI (for a local
|
|
34
|
+
# entry, the terminal)
|
|
35
35
|
# @!attribute match [#call] line (stripped) → whether it is this command
|
|
36
36
|
# @!attribute handler [Proc, nil] runs it; SessionCommands instance_execs
|
|
37
37
|
# a built-in's with the line, and calls a bundle's with the text
|
|
@@ -165,8 +165,8 @@ module Samagotchi
|
|
|
165
165
|
end
|
|
166
166
|
|
|
167
167
|
# @return [Entry, nil] the first entry the UI runs itself (local) that
|
|
168
|
-
# matches +line+, whatever its uis (a UI answers the others' too
|
|
169
|
-
#
|
|
168
|
+
# matches +line+, whatever its uis (a UI answers the others' too,
|
|
169
|
+
# with a note)
|
|
170
170
|
def lookup_local(line)
|
|
171
171
|
text = line.to_s.strip
|
|
172
172
|
@entries.find { |entry| entry.local && entry.match?(text) }
|
|
@@ -190,7 +190,7 @@ module Samagotchi
|
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
-
# @param ui [Symbol] :
|
|
193
|
+
# @param ui [Symbol] :attached
|
|
194
194
|
# @return [Array<String>] the /names Tab offers in +ui+, sorted
|
|
195
195
|
def completions(ui)
|
|
196
196
|
@entries.select { |entry| entry.slash? && entry.in_ui?(ui) }.map(&:name).uniq.sort
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
require_relative "delivery"
|
|
4
4
|
|
|
5
5
|
module Samagotchi
|
|
6
|
-
# Where a line typed at a terminal's prompt goes
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# attached TUI's continue offer and question). The web's composer.js
|
|
6
|
+
# Where a line typed at a terminal's prompt goes (AttachedLoop#submit; a
|
|
7
|
+
# -p prompt's /cut or /queue: TerminalUI#run). It only classifies; each
|
|
8
|
+
# UI keeps what it does with the answer (the attached TUI's continue
|
|
9
|
+
# offer and question). The web's composer.js
|
|
11
10
|
# routeComposerLine decides the same; spec/fixtures/composer_routes.json
|
|
12
11
|
# holds the cases both answer (docs/testing.md).
|
|
13
12
|
#
|
data/lib/samagotchi/config.rb
CHANGED
|
@@ -119,7 +119,7 @@ module Samagotchi
|
|
|
119
119
|
check: ->(value) { "N or N-M (1-#{IdleRecap::RecapPrompt::MAX_SENTENCES})" unless IdleRecap::RecapPrompt.sentences_range(value) }),
|
|
120
120
|
|
|
121
121
|
# chi broadcast (Broadcast::Recipients, Broadcast::Tags): a session that ended no turn in this many hours (and
|
|
122
|
-
# no worker
|
|
122
|
+
# no worker runs) gets no broadcast; a ticket id in the note and in a session's branch or prompts
|
|
123
123
|
# delivers it there without triage.
|
|
124
124
|
Entry.new(key: "broadcast.active_hours", yaml_path: %w[broadcast active_hours], type: :float, default: 8.0, expose: %i[env config], lifecycle: :next_broadcast),
|
|
125
125
|
Entry.new(key: "broadcast.ticket_pattern", yaml_path: %w[broadcast ticket_pattern], type: :string, default: '\b[A-Z][A-Z0-9]+-\d+\b', expose: %i[env config], lifecycle: :next_broadcast),
|
|
@@ -140,8 +140,6 @@ module Samagotchi
|
|
|
140
140
|
Entry.new(key: "session.idle_exit_minutes", yaml_path: %w[session idle_exit_minutes], type: :float, default: 30.0, expose: %i[env config cli], lifecycle: :worker_start),
|
|
141
141
|
# Tools follow the project: a worker applies its folder's `mise env` at start (ProjectEnv); off keeps the spawner's env.
|
|
142
142
|
Entry.new(key: "session.project_env", yaml_path: %w[session project_env], type: :enum, default: "mise", expose: %i[env config], enum_values: %w[mise off], lifecycle: :worker_start),
|
|
143
|
-
# Plain `chi` runs like `chi --shared` (bin/chi, LaunchMode); false, or --no-shared per run, keeps the plain REPL. No CLI flag: that would duplicate --shared.
|
|
144
|
-
Entry.new(key: "session.shared", yaml_path: %w[session shared], type: :bool, default: true, expose: %i[env config], lifecycle: :next_invocation),
|
|
145
143
|
# false: a continue's carry note holds the previous link's recap only (SessionChain.note_text), not its last answer too.
|
|
146
144
|
Entry.new(key: "session.carry_last_answer", yaml_path: %w[session carry_last_answer], type: :bool, default: true, expose: %i[env config], lifecycle: :next_lifecycle_decision),
|
|
147
145
|
# false: a session nothing happened in is deleted when it is left (SessionManager.discardable?).
|
|
@@ -698,7 +696,7 @@ module Samagotchi
|
|
|
698
696
|
# Live resolve so ENV changes (as in specs) are reflected without explicit reload
|
|
699
697
|
# Use current ENV and file (via ConfigFile's cached reader), plus any CLI
|
|
700
698
|
# overrides captured via reload!
|
|
701
|
-
path = Samagotchi::ConfigFile.global_path rescue nil
|
|
699
|
+
path = Samagotchi::ConfigFile.global_path rescue nil # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
702
700
|
file_data = Samagotchi::ConfigFile.read_yaml(path: path) if path
|
|
703
701
|
resolve_with_origin(entry.key, file_data: file_data, env: ENV, cli_overrides: cli_overrides, session: session)
|
|
704
702
|
end
|
|
@@ -817,7 +815,7 @@ module Samagotchi
|
|
|
817
815
|
end
|
|
818
816
|
|
|
819
817
|
# Keys replaced by another, and what the warning adds: it names the new
|
|
820
|
-
# one (no alias).
|
|
818
|
+
# one (no alias). A key removed with nothing in its place has nil.
|
|
821
819
|
PARENT_ANSWERS_HINT = "hard, standard or soft; see docs/guardrails.md"
|
|
822
820
|
SYSTEM_ONE_MOVED_HINT = "it has moved: until all of host, model and timeout_ms have, nothing is judged or " \
|
|
823
821
|
"auto-allowed, and every ask goes to you; see docs/configuration.md"
|
|
@@ -826,14 +824,18 @@ module Samagotchi
|
|
|
826
824
|
"turn.parent_continue" => ["guardrails.parent_answers", PARENT_ANSWERS_HINT],
|
|
827
825
|
"guardrails.system_one.host" => ["system_one.host", SYSTEM_ONE_MOVED_HINT],
|
|
828
826
|
"guardrails.system_one.model" => ["system_one.model", SYSTEM_ONE_MOVED_HINT],
|
|
829
|
-
"guardrails.system_one.timeout_ms" => ["system_one.timeout_ms", SYSTEM_ONE_MOVED_HINT]
|
|
827
|
+
"guardrails.system_one.timeout_ms" => ["system_one.timeout_ms", SYSTEM_ONE_MOVED_HINT],
|
|
828
|
+
"session.shared" => [nil, "chi always runs attached"]
|
|
830
829
|
}.freeze
|
|
831
830
|
|
|
832
831
|
# nil when +dotted+ is a key the file may hold.
|
|
833
832
|
def key_problem(dotted)
|
|
834
833
|
return nil if known_config_keys.include?(dotted)
|
|
835
834
|
return "config: #{ConfigFile.removed_transport_problem("'#{dotted}'")}" if dotted == "server.transport"
|
|
835
|
+
|
|
836
836
|
if (replaced = REPLACED_KEYS[dotted])
|
|
837
|
+
return "config: unknown key '#{dotted}': removed: #{replaced.last}" unless replaced.first
|
|
838
|
+
|
|
837
839
|
return "config: unknown key '#{dotted}': replaced by '#{replaced.first}' (#{replaced.last})"
|
|
838
840
|
end
|
|
839
841
|
|
|
@@ -1231,7 +1233,7 @@ module Samagotchi
|
|
|
1231
1233
|
return [] unless raw.is_a?(Hash)
|
|
1232
1234
|
|
|
1233
1235
|
raw.filter_map { |name, cfg| name.to_s.strip.downcase if cfg.is_a?(Hash) && HostConfig.disabled?(cfg) }
|
|
1234
|
-
rescue StandardError
|
|
1236
|
+
rescue StandardError # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
1235
1237
|
[]
|
|
1236
1238
|
end
|
|
1237
1239
|
|
|
@@ -1277,7 +1279,7 @@ module Samagotchi
|
|
|
1277
1279
|
|
|
1278
1280
|
items = raw.is_a?(Array) ? raw : [raw]
|
|
1279
1281
|
items.flat_map { |v| v.to_s.split(",") }.map(&:strip).reject(&:empty?)
|
|
1280
|
-
rescue StandardError
|
|
1282
|
+
rescue StandardError # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
1281
1283
|
[]
|
|
1282
1284
|
end
|
|
1283
1285
|
|
|
@@ -1296,7 +1298,7 @@ module Samagotchi
|
|
|
1296
1298
|
else []
|
|
1297
1299
|
end
|
|
1298
1300
|
items.select { |v| v.is_a?(String) }.map(&:strip).reject(&:empty?).filter_map { |v| expand_appended_entry(v) }
|
|
1299
|
-
rescue StandardError
|
|
1301
|
+
rescue StandardError # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
1300
1302
|
[]
|
|
1301
1303
|
end
|
|
1302
1304
|
|
|
@@ -1355,7 +1357,7 @@ module Samagotchi
|
|
|
1355
1357
|
end
|
|
1356
1358
|
|
|
1357
1359
|
raw.each_with_object({}) { |(name, value), acc| acc[name.to_s] = value.is_a?(Hash) ? value : {} }
|
|
1358
|
-
rescue StandardError
|
|
1360
|
+
rescue StandardError # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
1359
1361
|
{}
|
|
1360
1362
|
end
|
|
1361
1363
|
|
|
@@ -1414,7 +1416,7 @@ module Samagotchi
|
|
|
1414
1416
|
|
|
1415
1417
|
result[key.downcase] = val
|
|
1416
1418
|
end
|
|
1417
|
-
rescue StandardError
|
|
1419
|
+
rescue StandardError # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
1418
1420
|
{}
|
|
1419
1421
|
end
|
|
1420
1422
|
|
|
@@ -1432,7 +1434,7 @@ module Samagotchi
|
|
|
1432
1434
|
settings = ModelSettings.parse(key, v)
|
|
1433
1435
|
result[key] = settings if settings
|
|
1434
1436
|
end
|
|
1435
|
-
rescue StandardError
|
|
1437
|
+
rescue StandardError # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
1436
1438
|
{}
|
|
1437
1439
|
end
|
|
1438
1440
|
|
|
@@ -117,7 +117,7 @@ module Samagotchi
|
|
|
117
117
|
# @param show_card [#call] (**card) → replaces the card
|
|
118
118
|
# @return [Array(Symbol, String)] :ok or :error, and the answer
|
|
119
119
|
def choose(action, name, session:, state_dir:, show_card:)
|
|
120
|
-
return [:error,
|
|
120
|
+
return [:error, Session::NONE_YET] unless session&.id
|
|
121
121
|
return [:error, "a review session takes no context candidates"] if session.review?
|
|
122
122
|
return [:error, USAGE] if name.to_s.empty?
|
|
123
123
|
|
|
@@ -176,7 +176,7 @@ module Samagotchi
|
|
|
176
176
|
.filter_map { |candidate, state| candidate if state == "proposed" }
|
|
177
177
|
end
|
|
178
178
|
|
|
179
|
-
# Turn start (Worker#run_prompt before its absorb;
|
|
179
|
+
# Turn start (Worker#run_prompt before its absorb; a one-shot's turn):
|
|
180
180
|
# the proposed candidates +prompt+ (or an earlier owner prompt: a late
|
|
181
181
|
# proposal) mentions are attached, then each gets one first fetch
|
|
182
182
|
# (together within +seconds+), so the note reaches this turn. Only an
|
|
@@ -192,7 +192,7 @@ module Samagotchi
|
|
|
192
192
|
return [] if own.candidates.empty? || !OwnerPrompt.turn?(session: session, author: author)
|
|
193
193
|
|
|
194
194
|
# The earlier owner prompts too: a proposal that came while one ran
|
|
195
|
-
# (
|
|
195
|
+
# (a one-shot's plugins never see a running turn's prompt).
|
|
196
196
|
texts = owner_texts(session.messages) + (prompt.is_a?(String) ? [prompt] : [])
|
|
197
197
|
found = hits(texts, proposed(session.id, project_root: session.project_root, state_dir: state_dir))
|
|
198
198
|
names = found.filter_map { |hit| attach(hit, own: own, state_dir: state_dir) }
|
|
@@ -203,7 +203,8 @@ module Samagotchi
|
|
|
203
203
|
[]
|
|
204
204
|
end
|
|
205
205
|
|
|
206
|
-
#
|
|
206
|
+
# A --non-interactive one-shot's turn start (it has no ContextAbsorber
|
|
207
|
+
# running, TerminalUI#attach_mentioned_context): the notes
|
|
207
208
|
# of the sources attached from candidates, as the worker's absorb gives
|
|
208
209
|
# them, added to +engine+'s session and marked delivered.
|
|
209
210
|
# @return [Integer] how many notes were added
|
|
@@ -134,7 +134,7 @@ module Samagotchi
|
|
|
134
134
|
# The continue offer was answered yes: resume the conversation without a
|
|
135
135
|
# user message, with the iteration limit the offer's turn had, from the
|
|
136
136
|
# run that turn started (TurnFlow#resume_from: its reasoning stays). A failure
|
|
137
|
-
# keeps the offer
|
|
137
|
+
# keeps the offer ("continue prompt preserved"), and
|
|
138
138
|
# asks again, one before the turn began too (logged, no :turn_failed).
|
|
139
139
|
# The answer's text is carried into the turn as it begins; a turn that
|
|
140
140
|
# never began leaves it to no other.
|
data/lib/samagotchi/debug_log.rb
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "log"
|
|
4
|
+
|
|
5
|
+
module Samagotchi
|
|
6
|
+
# A logged degradation: a piece of work whose failure has a fallback (a
|
|
7
|
+
# prompt section left out, an empty list, nil), with a record in the debug
|
|
8
|
+
# log instead of silence.
|
|
9
|
+
#
|
|
10
|
+
# Degrade.call(:prompt, "model_notes_unreadable", fallback: nil, model: name) { read_notes }
|
|
11
|
+
# Degrade.call(:bridge, "state_unreadable", fallback: {}, rescue: [SystemCallError, JSON::ParserError]) { … }
|
|
12
|
+
#
|
|
13
|
+
# Runs the block and returns its value. When it raises one of +rescue:+
|
|
14
|
+
# (StandardError by default; never Exception), returns +fallback+ and logs
|
|
15
|
+
# Log.warn(area, event, error:, msg:, **fields) the first time this
|
|
16
|
+
# process sees [area, event], Log.debug after: a hot path that fails on
|
|
17
|
+
# every call writes one WARN, and the happy path takes no lock. Nothing
|
|
18
|
+
# goes to stderr (a worker has none; a terminal line would be noise).
|
|
19
|
+
#
|
|
20
|
+
# A `return` in the block returns from the enclosing method as usual; a
|
|
21
|
+
# `next` or `break` would change what the site does, so a site that needs
|
|
22
|
+
# one logs by hand (Degrade.note) instead.
|
|
23
|
+
#
|
|
24
|
+
# Written Degrade.call (Style/LambdaCall turns Degrade.() into it).
|
|
25
|
+
#
|
|
26
|
+
# +area+ is a Log tag (LogLine::TAGS), +event+ a slug saying what was
|
|
27
|
+
# lost; with Log's strict mode on (the specs) both are checked on every
|
|
28
|
+
# call, the happy path's included.
|
|
29
|
+
module Degrade
|
|
30
|
+
MSG_CHARS = 500
|
|
31
|
+
|
|
32
|
+
@seen = Set.new
|
|
33
|
+
@mutex = Mutex.new
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
# @param area [Symbol] a Log tag
|
|
37
|
+
# @param event [String, Symbol] what was lost (agents_md_unreadable)
|
|
38
|
+
# @param fallback [Object] the value when the block raises
|
|
39
|
+
# @param fields [Hash] extra record fields; +rescue:+ (a class or an
|
|
40
|
+
# array of them, default StandardError) is the one taken out
|
|
41
|
+
# @return the block's value, or +fallback+
|
|
42
|
+
def call(area, event, fallback: nil, **fields)
|
|
43
|
+
classes = Array(fields.delete(:rescue) || StandardError)
|
|
44
|
+
check!(area, event) if Log.strict
|
|
45
|
+
begin
|
|
46
|
+
yield
|
|
47
|
+
rescue *classes => e
|
|
48
|
+
note(area, event, e, **fields)
|
|
49
|
+
fallback
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Log a degradation the caller has already handled (its rescue keeps
|
|
54
|
+
# a next/break or a fallback of its own): the same once-per-process
|
|
55
|
+
# WARN, DEBUG after.
|
|
56
|
+
# @return nil
|
|
57
|
+
def note(area, event, error, **fields)
|
|
58
|
+
first = @mutex.synchronize { @seen.add?([area.to_sym, event.to_s]) }
|
|
59
|
+
record = { error: error.class.name, msg: error.message.to_s[0, MSG_CHARS], **fields }
|
|
60
|
+
first ? Log.warn(area, event.to_s, **record) : Log.debug(area, event.to_s, **record)
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Forget what was logged (specs, with Log.reset!).
|
|
65
|
+
def reset! = @mutex.synchronize { @seen.clear }
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def check!(area, event)
|
|
70
|
+
raise ArgumentError, "unknown log tag #{area}" unless LogLine::TAGS.include?(area.to_s)
|
|
71
|
+
raise ArgumentError, "bad log event #{event}" unless event.to_s.match?(LogLine::SLUG)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -182,8 +182,8 @@ final class ChiRunner {
|
|
|
182
182
|
/// The live sessions, oldest started first, then up to +recentCount+
|
|
183
183
|
/// stopped ones, newest first. The live order is by creation so ⌘1…⌘9
|
|
184
184
|
/// stay on their sessions as turns update them; a new one comes last.
|
|
185
|
-
/// A session a chi
|
|
186
|
-
/// messages. A failed second call just leaves "recent" empty. Calls
|
|
185
|
+
/// A session a one-turn chi --non-interactive run holds is in neither: it
|
|
186
|
+
/// takes no notes or messages. A failed second call just leaves "recent" empty. Calls
|
|
187
187
|
/// back on +queue+ (a headless seam waits on another one).
|
|
188
188
|
func sessions(recentCount: Int = 3, queue: DispatchQueue = .main,
|
|
189
189
|
completion: @escaping (Result<[LiveSession], ChiError>) -> Void) {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Samagotchi
|
|
4
|
+
class Engine
|
|
5
|
+
# Cards and anytime commands: #show_card (routed by EventRoutes), the
|
|
6
|
+
# anytime and session-command thread marks, the anytime threads #shutdown
|
|
7
|
+
# waits for. Reads @event_routes, @anytime_threads under @lifecycle_mutex.
|
|
8
|
+
module CardsPart
|
|
9
|
+
# ── Cards ───────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
# Show a card in every UI (docs/plugins.md, Cards): {type: :card, id:,
|
|
12
|
+
# source:, title:, body:, level:, actions: [{label:, command:}], in_turn:}.
|
|
13
|
+
# Where it goes (held while the plugins load, an anytime command's, an
|
|
14
|
+
# init task's, a turn event, else announced) is EventRoutes#route's,
|
|
15
|
+
# marked in_turn: (CARD_MARKS). A card with the id of an earlier one
|
|
16
|
+
# replaces it (btw's "thinking…" → the answer).
|
|
17
|
+
# @param source [String] who shows it (a bundle's name)
|
|
18
|
+
# @param actions [Array<Hash>] {label:, command:}; a command is a line
|
|
19
|
+
# the session runs, as typed (D3)
|
|
20
|
+
# @return [String] the card's id
|
|
21
|
+
# @raise [ArgumentError] a card without a title, or a bad action
|
|
22
|
+
def show_card(source:, title:, body: "", actions: [], level: :info, id: nil)
|
|
23
|
+
card = Plugin::Card.build(source: source, title: title, body: body, actions: actions, level: level, id: id)
|
|
24
|
+
command = Thread.current[running_command_key]
|
|
25
|
+
card[:command] = command if command
|
|
26
|
+
@event_routes.route(card, **EventRoutes::CARD_MARKS)
|
|
27
|
+
card[:id]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Run an anytime command's block (D8): the cards and notices it shows on
|
|
31
|
+
# this thread are announced at once, marked anytime: true, and are
|
|
32
|
+
# never a running turn's events. They belong to the command, not the
|
|
33
|
+
# turn, and a UI shows them after the command's own line (a web
|
|
34
|
+
# command bubble drawn at its command_queued), even mid-turn.
|
|
35
|
+
# @return the block's value
|
|
36
|
+
def running_anytime
|
|
37
|
+
key = :"samagotchi_anytime_#{object_id}"
|
|
38
|
+
outer = Thread.current[key]
|
|
39
|
+
Thread.current[key] = true
|
|
40
|
+
yield
|
|
41
|
+
ensure
|
|
42
|
+
Thread.current[key] = outer
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Run a session command's dispatch: a card shown on this thread says
|
|
46
|
+
# it's that command's (:card's command:), for a reload that no longer has
|
|
47
|
+
# the command's line above it (Bridge::CardStore). A thread of its own
|
|
48
|
+
# isn't the command, nor a card shown after it.
|
|
49
|
+
# @param name [String] the command, "/short"
|
|
50
|
+
# @return the block's value
|
|
51
|
+
def running_command(name)
|
|
52
|
+
outer = Thread.current[running_command_key]
|
|
53
|
+
Thread.current[running_command_key] = name
|
|
54
|
+
yield
|
|
55
|
+
ensure
|
|
56
|
+
Thread.current[running_command_key] = outer
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Whether an anytime command (/btw) still runs on its thread.
|
|
60
|
+
def anytime_running?
|
|
61
|
+
@lifecycle_mutex.synchronize { @anytime_threads.any?(&:alive?) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Start an anytime command on its own thread (D8), one #shutdown waits
|
|
65
|
+
# for, so its command_ran is announced before the process leaves.
|
|
66
|
+
# @return [Thread]
|
|
67
|
+
def spawn_anytime(&)
|
|
68
|
+
thread = Thread.new(&)
|
|
69
|
+
@lifecycle_mutex.synchronize do
|
|
70
|
+
@anytime_threads.select!(&:alive?)
|
|
71
|
+
@anytime_threads << thread
|
|
72
|
+
end
|
|
73
|
+
thread
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def anytime_thread? = Thread.current[:"samagotchi_anytime_#{object_id}"] == true
|
|
77
|
+
private :anytime_thread?
|
|
78
|
+
|
|
79
|
+
def announce_anytime(event)
|
|
80
|
+
event = event.merge(anytime: true)
|
|
81
|
+
announce(event)
|
|
82
|
+
event
|
|
83
|
+
end
|
|
84
|
+
private :announce_anytime
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Samagotchi
|
|
4
|
+
class Engine
|
|
5
|
+
# The attached-context candidate judge (ContextJudge): its pass at an
|
|
6
|
+
# owner turn's start and for a late candidate, the asks it starts, and
|
|
7
|
+
# the cards it shows after the turn. Reads @context_cards_after_turn
|
|
8
|
+
# under @context_cards_mutex, @side_judgments, @host_registry, @session.
|
|
9
|
+
module ContextJudgePart
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# The candidate judge (ContextJudge) at an owner turn's start, after
|
|
13
|
+
# the toolkit's ask settled: the session's candidates' status changes
|
|
14
|
+
# are logged (ContextCandidatesLog#look) and, unless
|
|
15
|
+
# context.candidates.mode is off, the proposed candidates due an ask
|
|
16
|
+
# are asked about the owner's prompts so far, this one included. Never
|
|
17
|
+
# a participant's turn, a review session or a Continue (no new prompt).
|
|
18
|
+
def start_context_judge(turn)
|
|
19
|
+
session = turn.session
|
|
20
|
+
return unless turn.prompt.is_a?(String) && OwnerPrompt.turn?(session: session, author: turn.origin.author)
|
|
21
|
+
|
|
22
|
+
context_judge_pass(session, ContextCandidates.owner_texts(session.messages) + [turn.prompt], phase: "turn")
|
|
23
|
+
end
|
|
24
|
+
private :start_context_judge
|
|
25
|
+
|
|
26
|
+
# A late proposal no owner prompt mentions (Plugin::Host
|
|
27
|
+
# #candidate_proposed: github-pr's init task runs beside the first
|
|
28
|
+
# turn): judged on the owner's prompts so far, the running turn's too.
|
|
29
|
+
def judge_late_candidates
|
|
30
|
+
session = @session
|
|
31
|
+
return if session.nil? || session.review?
|
|
32
|
+
|
|
33
|
+
texts = ContextCandidates.owner_texts(plugin_messages)
|
|
34
|
+
context_judge_pass(session, texts, phase: "late") unless texts.empty?
|
|
35
|
+
end
|
|
36
|
+
private :judge_late_candidates
|
|
37
|
+
|
|
38
|
+
# One look at +session+'s candidates; the asks go to SideJudgments'
|
|
39
|
+
# :context kind, one thread asking each due candidate in turn.
|
|
40
|
+
def context_judge_pass(session, texts, phase:)
|
|
41
|
+
return if session.id.nil? || session.scratch
|
|
42
|
+
|
|
43
|
+
rows = ContextCandidates.with_status(session.id, project_root: session.project_root, state_dir: session_state_dir)
|
|
44
|
+
return if rows.empty?
|
|
45
|
+
|
|
46
|
+
log = ContextCandidatesLog.log_for(session_state_dir)
|
|
47
|
+
records = ContextCandidatesLog.records_for(log, session.id)
|
|
48
|
+
ContextCandidatesLog.look(log, session.id, rows, records) if phase == "turn"
|
|
49
|
+
settings = ContextJudge::Settings.from_config
|
|
50
|
+
return if settings.mode == "off"
|
|
51
|
+
|
|
52
|
+
jobs = ContextJudge.jobs(rows, records, session_id: session.id, texts: texts, settings: settings, phase: phase,
|
|
53
|
+
parent_attached: context_parent_attached(session))
|
|
54
|
+
return if jobs.empty?
|
|
55
|
+
|
|
56
|
+
client = system_one_recording(SystemOne::Client.from_config(host_registry: @host_registry, mode: "on", tag: :context),
|
|
57
|
+
ContextJudge::JUDGMENT, session, mode: settings.mode)
|
|
58
|
+
return unless client
|
|
59
|
+
|
|
60
|
+
start_context_asks(client, jobs, log, settings, context_choice(session, log, settings))
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
ContextJudge.warn(e)
|
|
63
|
+
end
|
|
64
|
+
private :context_judge_pass
|
|
65
|
+
|
|
66
|
+
# @return [Thread, nil] the asks' thread, nil when none started
|
|
67
|
+
def start_context_asks(client, jobs, log, settings, choice)
|
|
68
|
+
if @side_judgments.in_flight?(:context)
|
|
69
|
+
Log.info(:context, "context_judge_in_flight_skipped", session_id: jobs.first.session_id)
|
|
70
|
+
return nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@side_judgments.start(:context, warn: ContextJudge.method(:warn)) do
|
|
74
|
+
jobs.each { |job| choice.call(job, ContextJudge.ask(client: client, job: job, log: log, settings: settings)) }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
private :start_context_asks
|
|
78
|
+
|
|
79
|
+
# What an answer does on the asks' thread (ContextCandidateChoice
|
|
80
|
+
# #after_judged: on mode attaches or cards; audit nothing). Taken on
|
|
81
|
+
# the caller's thread. The attach only writes the session's folder: its
|
|
82
|
+
# note comes with the next between-turn delivery, never into a running
|
|
83
|
+
# turn. A card shows only while this Engine is still on that session,
|
|
84
|
+
# and not in a running turn (it isn't the turn's: no "waiting for you"
|
|
85
|
+
# on it), but once it has ended.
|
|
86
|
+
# @return [#call] (job, fields)
|
|
87
|
+
def context_choice(session, log, settings)
|
|
88
|
+
session_id = session.id
|
|
89
|
+
project_root = session.project_root
|
|
90
|
+
state_dir = session_state_dir
|
|
91
|
+
show = lambda do |**card|
|
|
92
|
+
current = @session
|
|
93
|
+
next nil unless current.nil? || current.id == session_id
|
|
94
|
+
|
|
95
|
+
@context_cards_mutex.synchronize do
|
|
96
|
+
next @context_cards_after_turn << card if turn_running?
|
|
97
|
+
|
|
98
|
+
show_card(**card)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
lambda do |job, fields|
|
|
102
|
+
ContextCandidateChoice.after_judged(job: job, fields: fields, settings: settings, project_root: project_root,
|
|
103
|
+
state_dir: state_dir, log: log, show_card: show)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
private :context_choice
|
|
107
|
+
|
|
108
|
+
# The cards #context_choice held while a turn ran (#run_turn's ensure,
|
|
109
|
+
# after the turn flag is released).
|
|
110
|
+
def show_context_cards_after_turn
|
|
111
|
+
cards = @context_cards_mutex.synchronize { @context_cards_after_turn.slice!(0..) }
|
|
112
|
+
cards.each { |card| show_card(**card) }
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
ContextJudge.warn(e)
|
|
115
|
+
end
|
|
116
|
+
private :show_context_cards_after_turn
|
|
117
|
+
|
|
118
|
+
# A delegate child's parent's sources, by title (else name): the
|
|
119
|
+
# judge's started_from. nil for any other session (a chi send --new
|
|
120
|
+
# session has no parent; a fork isn't a delegate).
|
|
121
|
+
def context_parent_attached(session)
|
|
122
|
+
return nil unless session.delegate? && session.parent_id
|
|
123
|
+
|
|
124
|
+
parent_root = if Session.exist?(session.parent_id, state_dir: session_state_dir)
|
|
125
|
+
Session.load(session.parent_id, state_dir: session_state_dir).project_root
|
|
126
|
+
else
|
|
127
|
+
session.project_root
|
|
128
|
+
end
|
|
129
|
+
ContextSources.attached(session.parent_id, project_root: parent_root, state_dir: session_state_dir)
|
|
130
|
+
.map { |attached| attached.source.title || attached.name }
|
|
131
|
+
rescue StandardError => e
|
|
132
|
+
Log.warn(:context, "context_parent_attached_failed", error: e.class.name, msg: e.message.to_s[0, 200])
|
|
133
|
+
[]
|
|
134
|
+
end
|
|
135
|
+
private :context_parent_attached
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|