samagotchi 0.159.0 → 0.161.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 +125 -1
- data/docs/architecture.md +12 -9
- data/docs/cli.md +7 -5
- data/docs/context.md +10 -1
- data/docs/guardrails.md +33 -5
- data/docs/internals/attached-context.md +4 -2
- data/docs/internals/bridge.md +28 -6
- data/docs/internals/bundles.md +24 -5
- data/docs/internals/engine.md +7 -1
- data/docs/internals/objectives.md +8 -2
- data/docs/internals/remote-sessions.md +2 -1
- data/docs/memory.md +43 -13
- data/docs/plugins.md +13 -4
- data/docs/releasing.md +7 -4
- data/docs/scheduling.md +53 -8
- data/docs/sessions.md +17 -12
- data/docs/testing.md +9 -1
- data/lib/samagotchi/bridge/card_store.rb +31 -3
- data/lib/samagotchi/bridge/ring_buffer.rb +11 -1
- data/lib/samagotchi/bridge/sse_writer.rb +8 -5
- data/lib/samagotchi/bridge.rb +103 -44
- data/lib/samagotchi/bridge_client.rb +9 -6
- data/lib/samagotchi/broadcast/scope_card.rb +2 -1
- data/lib/samagotchi/broadcast_command.rb +24 -8
- data/lib/samagotchi/bundle_command.rb +57 -6
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/memory_guide.md +2 -2
- data/lib/samagotchi/child_reports.rb +29 -23
- data/lib/samagotchi/client_id.rb +6 -0
- data/lib/samagotchi/command_intake.rb +56 -0
- data/lib/samagotchi/compat.rb +32 -0
- data/lib/samagotchi/context_absorber.rb +6 -3
- data/lib/samagotchi/context_candidate_choice.rb +8 -5
- data/lib/samagotchi/context_candidates.rb +11 -8
- data/lib/samagotchi/context_candidates_log.rb +10 -1
- data/lib/samagotchi/context_command.rb +10 -4
- data/lib/samagotchi/context_move.rb +6 -1
- data/lib/samagotchi/context_poller.rb +5 -2
- data/lib/samagotchi/context_sources.rb +6 -4
- data/lib/samagotchi/engine/events.rb +25 -2
- data/lib/samagotchi/engine/guardrails.rb +3 -4
- data/lib/samagotchi/engine/plugins.rb +1 -1
- data/lib/samagotchi/engine/steering.rb +2 -2
- data/lib/samagotchi/engine.rb +1 -0
- data/lib/samagotchi/engine_peers.rb +1 -0
- data/lib/samagotchi/events.rb +71 -0
- data/lib/samagotchi/guardrails/approvals.rb +27 -3
- data/lib/samagotchi/guardrails/child_boundary.rb +37 -4
- data/lib/samagotchi/guardrails/protected_paths.rb +8 -5
- data/lib/samagotchi/guardrails/targets.rb +25 -6
- data/lib/samagotchi/hook_routes.rb +0 -1
- data/lib/samagotchi/live_versions.rb +7 -2
- data/lib/samagotchi/memory_bundle/asset_installer.rb +86 -18
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +7 -27
- data/lib/samagotchi/memory_bundle/file_actions.rb +3 -4
- data/lib/samagotchi/memory_bundle/file_state.rb +11 -0
- data/lib/samagotchi/memory_bundle/index_updater.rb +7 -4
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
- data/lib/samagotchi/memory_bundle/installer.rb +93 -23
- data/lib/samagotchi/memory_bundle/manifest.rb +6 -0
- data/lib/samagotchi/memory_bundle/provenance.rb +158 -6
- data/lib/samagotchi/memory_bundle/shipped_update.rb +7 -4
- data/lib/samagotchi/memory_bundle/status.rb +3 -3
- data/lib/samagotchi/memory_bundle/system_bundle.rb +2 -0
- data/lib/samagotchi/memory_bundle/uninstaller.rb +29 -13
- data/lib/samagotchi/memory_orphans.rb +54 -13
- data/lib/samagotchi/memory_paths.rb +29 -6
- data/lib/samagotchi/memory_usage.rb +23 -12
- data/lib/samagotchi/note_command.rb +9 -1
- data/lib/samagotchi/note_delivery.rb +34 -24
- data/lib/samagotchi/note_move.rb +205 -0
- data/lib/samagotchi/objectives_command.rb +3 -1
- data/lib/samagotchi/owner_lock.rb +7 -1
- data/lib/samagotchi/plugin/attached_context.rb +3 -1
- data/lib/samagotchi/plugin/sessions.rb +4 -1
- data/lib/samagotchi/project_command.rb +58 -20
- data/lib/samagotchi/project_id.rb +102 -0
- data/lib/samagotchi/project_id_clash.rb +62 -0
- data/lib/samagotchi/project_init.rb +214 -10
- data/lib/samagotchi/project_scope.rb +11 -2
- data/lib/samagotchi/review_start.rb +5 -3
- data/lib/samagotchi/schedule_command.rb +12 -0
- data/lib/samagotchi/schedules.rb +374 -76
- data/lib/samagotchi/send_command.rb +2 -15
- data/lib/samagotchi/session.rb +59 -7
- data/lib/samagotchi/session_access/local.rb +53 -22
- data/lib/samagotchi/session_access/remote.rb +13 -5
- data/lib/samagotchi/session_access/web_bridge_client.rb +2 -2
- data/lib/samagotchi/session_access.rb +20 -4
- data/lib/samagotchi/session_commands.rb +7 -3
- data/lib/samagotchi/session_continue.rb +162 -24
- data/lib/samagotchi/session_delete_command.rb +3 -1
- data/lib/samagotchi/session_inbox.rb +41 -1
- data/lib/samagotchi/session_inbox_delivery.rb +254 -0
- data/lib/samagotchi/session_manager/delivery.rb +66 -12
- data/lib/samagotchi/session_manager/emptiness.rb +7 -2
- data/lib/samagotchi/session_manager/lifecycle.rb +2 -1
- data/lib/samagotchi/session_manager/listing.rb +4 -1
- data/lib/samagotchi/session_manager/removal.rb +130 -26
- data/lib/samagotchi/session_manager/spawn.rb +167 -100
- data/lib/samagotchi/session_manager/worker_command.rb +7 -19
- data/lib/samagotchi/session_manager.rb +1 -0
- data/lib/samagotchi/session_retention.rb +53 -23
- data/lib/samagotchi/session_setup.rb +27 -7
- data/lib/samagotchi/starting_marker.rb +93 -24
- data/lib/samagotchi/state_format.rb +129 -0
- data/lib/samagotchi/system_one/runs.rb +6 -1
- data/lib/samagotchi/system_one/runs_store.rb +18 -14
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +7 -4
- data/lib/samagotchi/terminal_ui/attached_loop/composer.rb +5 -2
- data/lib/samagotchi/terminal_ui/attached_loop.rb +5 -0
- data/lib/samagotchi/terminal_ui/version_lines.rb +4 -2
- data/lib/samagotchi/terminal_ui.rb +2 -3
- data/lib/samagotchi/tools/context_read.rb +7 -2
- data/lib/samagotchi/tools/delegate.rb +19 -6
- data/lib/samagotchi/tools/schedule_task.rb +5 -3
- data/lib/samagotchi/tools/send_note.rb +10 -4
- data/lib/samagotchi/turn_stages/context_judge.rb +7 -7
- data/lib/samagotchi/turn_state.rb +108 -4
- data/lib/samagotchi/update_command.rb +9 -4
- data/lib/samagotchi/verify_fresh.rb +27 -9
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +6 -5
- data/lib/samagotchi/web/public/app.js +2 -7
- data/lib/samagotchi/web/public/chains.js +4 -4
- data/lib/samagotchi/web/public/data.js +18 -2
- data/lib/samagotchi/web/public/info_bar_model.js +1 -1
- data/lib/samagotchi/web/public/session_fields.js +3 -2
- data/lib/samagotchi/web/public/session_state.js +2 -0
- data/lib/samagotchi/web/public/update_notice.js +2 -2
- data/lib/samagotchi/web/session_access_routes.rb +15 -11
- data/lib/samagotchi/web/session_control.rb +9 -3
- data/lib/samagotchi/web/session_create.rb +8 -4
- data/lib/samagotchi/web/session_hub.rb +50 -5
- data/lib/samagotchi/web/session_summary.rb +5 -2
- data/lib/samagotchi/worker.rb +61 -11
- data/lib/samagotchi/worker_boot.rb +22 -11
- data/lib/samagotchi/worker_inbound.rb +62 -10
- data/lib/samagotchi/worker_link.rb +4 -1
- data/lib/samagotchi/worker_presence.rb +106 -0
- data/lib/samagotchi/worker_sidecar.rb +3 -4
- metadata +9 -1
|
@@ -673,7 +673,7 @@ module Samagotchi
|
|
|
673
673
|
return false
|
|
674
674
|
end
|
|
675
675
|
unless result.accepted?
|
|
676
|
-
@info.puts("#{session.id} #{
|
|
676
|
+
@info.puts("#{session.id} #{result.refusal_text}")
|
|
677
677
|
return false
|
|
678
678
|
end
|
|
679
679
|
|
|
@@ -685,19 +685,6 @@ module Samagotchi
|
|
|
685
685
|
|
|
686
686
|
SENT_COMMAND = "sent as a session command"
|
|
687
687
|
|
|
688
|
-
# A remote's refusals of what this machine checks before sending (a
|
|
689
|
-
# parent's /continue yes, a loosening /parent-answers, images to a
|
|
690
|
-
# model that can't see them): its turn route's error codes.
|
|
691
|
-
REMOTE_REFUSALS = %w[parent_answers_refused parent_continue_refused no_vision].freeze
|
|
692
|
-
|
|
693
|
-
# How a delivery that didn't go in reads: refused (a worker older than
|
|
694
|
-
# queueing would steer it; a remote refused it as chi send refuses it
|
|
695
|
-
# here) or failed.
|
|
696
|
-
def refused_word(result)
|
|
697
|
-
refused = result.error == Delivery::QUEUE_UNAVAILABLE["error"] || REMOTE_REFUSALS.include?(result.error)
|
|
698
|
-
refused ? "refused" : "failed"
|
|
699
|
-
end
|
|
700
|
-
|
|
701
688
|
# chi send's client id, or chi answer's for a parent agent's /continue
|
|
702
689
|
# yes (the worker holds that to its guardrails.parent_answers).
|
|
703
690
|
def client_id_for(prompt) = ParentGate.parent_continue?(prompt) ? ClientId::CLI_ANSWER : CLIENT_ID
|
|
@@ -773,7 +760,7 @@ module Samagotchi
|
|
|
773
760
|
end
|
|
774
761
|
result = @access.deliver_turn(id, prompt: prompt, client_id: client_id_for(prompt), images: refs, delivery: delivery)
|
|
775
762
|
unless result.accepted?
|
|
776
|
-
@info.puts("#{short} #{
|
|
763
|
+
@info.puts("#{short} #{result.refusal_text}")
|
|
777
764
|
return false
|
|
778
765
|
end
|
|
779
766
|
|
data/lib/samagotchi/session.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "json"
|
|
|
5
5
|
require "securerandom"
|
|
6
6
|
require "time"
|
|
7
7
|
require_relative "atomic_file"
|
|
8
|
+
require_relative "log"
|
|
8
9
|
require_relative "context_note"
|
|
9
10
|
require_relative "interrupted_turn"
|
|
10
11
|
require_relative "last_turn"
|
|
@@ -166,6 +167,11 @@ module Samagotchi
|
|
|
166
167
|
# deleted folder no longer leads to its repository. nil outside a repo,
|
|
167
168
|
# and in files written before the field existed (see #project_root).
|
|
168
169
|
SessionField.plain("project_root", nil),
|
|
170
|
+
# The project's id (ProjectId: a marked project's `id:`), saved beside
|
|
171
|
+
# project_root at creation, else nil: its memories and project context
|
|
172
|
+
# sources are keyed by it (MemoryPaths.key_for), so a session resumed
|
|
173
|
+
# after its folder moved finds them under the id, not its dead path.
|
|
174
|
+
SessionField.plain("project_id", nil, optional: true),
|
|
169
175
|
# Memories the session was started with (--memory) and memories hidden
|
|
170
176
|
# from it (--mute): the worker rebuilds the same prompt on a respawn.
|
|
171
177
|
# Names as given; the engine normalizes them.
|
|
@@ -271,6 +277,14 @@ module Samagotchi
|
|
|
271
277
|
# chi web's create) can name it (`--wait --turn T ID`). nil for a
|
|
272
278
|
# session created idle, and for one saved before the field existed.
|
|
273
279
|
SessionField.plain("first_turn_id", nil, optional: true),
|
|
280
|
+
# Who sent that prompt when no person did (a ClientId: a delegating
|
|
281
|
+
# parent's delegate:<parent8>, a schedule's chain link
|
|
282
|
+
# system:schedule:<id>): the first turn's origin, so the command
|
|
283
|
+
# intake treats it as theirs (CommandIntake) and last_turn names
|
|
284
|
+
# them. nil for a person's prompt (chi send --new, the web, a TUI, a
|
|
285
|
+
# continue) and an idle session. Set once, by spawn_session; a
|
|
286
|
+
# continue and a fork don't carry it.
|
|
287
|
+
SessionField.plain("first_turn_client_id", nil, optional: true),
|
|
274
288
|
# A Verify fresh child's requester, rules and claim (VerifyFreshOrigin),
|
|
275
289
|
# nil for any other session. Set once, at spawn (VerifyFresh.start).
|
|
276
290
|
# Autoloaded on the first one (the bundle test kit's load budget).
|
|
@@ -290,7 +304,7 @@ module Samagotchi
|
|
|
290
304
|
attr_accessor :id, :metadata_version, :mode, :model_name, :model_typed, :working_directory, :messages,
|
|
291
305
|
:created_at, :updated_at, :status, :last_prompt, :first_preview, :title, :test_run,
|
|
292
306
|
:used_memory_names, :preloaded_memory_names, :muted_memory_names, :parent_id, :scratch,
|
|
293
|
-
:continues, :first_turn_id
|
|
307
|
+
:continues, :first_turn_id, :first_turn_client_id, :project_id
|
|
294
308
|
attr_writer :project_root, :delegate
|
|
295
309
|
attr_reader :appended_prompt_files, :written_by
|
|
296
310
|
|
|
@@ -472,6 +486,10 @@ module Samagotchi
|
|
|
472
486
|
@archived = false
|
|
473
487
|
# #save's lock: one snapshot-and-write at a time (see #save).
|
|
474
488
|
@save_lock = Mutex.new
|
|
489
|
+
# Read from its file (.load, .summary_from_file) or saved once: its
|
|
490
|
+
# file is there to rewrite, and one gone was deleted (#save doesn't
|
|
491
|
+
# bring it back).
|
|
492
|
+
@persisted = false
|
|
475
493
|
end
|
|
476
494
|
|
|
477
495
|
# Whether a chi newer than this one saved the loaded file: its
|
|
@@ -566,12 +584,12 @@ module Samagotchi
|
|
|
566
584
|
# @raise [Toolkit::Refused] its toolkit (setup's, else the configured
|
|
567
585
|
# one SessionManager.spawn_session gives it) builds on a config kit
|
|
568
586
|
# with an invalid groups: (Toolkit.check_new!)
|
|
569
|
-
def self.new_session(mode:, model_name:, working_directory:, test_run: nil,
|
|
570
|
-
preloaded_memory_names: [], muted_memory_names: [], parent_id: nil, messages: [],
|
|
587
|
+
def self.new_session(mode:, model_name:, working_directory:, test_run: nil, parent_id: nil, messages: [],
|
|
571
588
|
scratch: false, model_typed: nil, delegate: false, setup: nil, continues: nil,
|
|
572
589
|
review: nil, now: Time.now)
|
|
573
590
|
own = setup.to_h
|
|
574
591
|
own = own.merge(toolkit_origin: own[:toolkit_origin] || "chosen")
|
|
592
|
+
project = ProjectScope.project_for(working_directory)
|
|
575
593
|
Toolkit.check_new!(own[:toolkit] || Toolkit.configured(delegate: delegate))
|
|
576
594
|
now = now.iso8601(3)
|
|
577
595
|
resolved_test = if test_run.nil?
|
|
@@ -591,9 +609,8 @@ module Samagotchi
|
|
|
591
609
|
status: STATUS_IDLE,
|
|
592
610
|
first_preview: "",
|
|
593
611
|
test_run: resolved_test,
|
|
594
|
-
project_root:
|
|
595
|
-
|
|
596
|
-
muted_memory_names: muted_memory_names,
|
|
612
|
+
project_root: project&.root,
|
|
613
|
+
project_id: project&.id,
|
|
597
614
|
parent_id: parent_id,
|
|
598
615
|
scratch: scratch,
|
|
599
616
|
delegate: delegate,
|
|
@@ -621,6 +638,7 @@ module Samagotchi
|
|
|
621
638
|
raise NotFound, session_id unless File.exist?(path)
|
|
622
639
|
|
|
623
640
|
session = from_h(JSON.parse(File.read(path)))
|
|
641
|
+
session.persisted!
|
|
624
642
|
session.status = STATUS_STOPPED if stopped_marker?(session.id, state_dir: state_dir)
|
|
625
643
|
session.parent_id = parent_override(session.id, state_dir: state_dir) || session.parent_id
|
|
626
644
|
session
|
|
@@ -749,6 +767,7 @@ module Samagotchi
|
|
|
749
767
|
return nil unless valid_id?(data["id"])
|
|
750
768
|
|
|
751
769
|
session = from_h(data, messages: false)
|
|
770
|
+
session.persisted!
|
|
752
771
|
session.status = STATUS_STOPPED if stopped_marker?(session.id, state_dir: File.dirname(path))
|
|
753
772
|
session.parent_id = parent_override(session.id, state_dir: File.dirname(path)) || session.parent_id
|
|
754
773
|
session
|
|
@@ -756,6 +775,10 @@ module Samagotchi
|
|
|
756
775
|
nil
|
|
757
776
|
end
|
|
758
777
|
|
|
778
|
+
# This session was read from its file (#save then refuses to recreate
|
|
779
|
+
# a deleted one).
|
|
780
|
+
def persisted! = (@persisted = true)
|
|
781
|
+
|
|
759
782
|
# Persist the session atomically. Updates +updated_at+ in place.
|
|
760
783
|
#
|
|
761
784
|
# One save at a time per Session: a worker saves its session from the
|
|
@@ -767,6 +790,11 @@ module Samagotchi
|
|
|
767
790
|
# Never call it from a trap handler (Mutex#lock raises ThreadError
|
|
768
791
|
# there; WorkerSignals saves on a thread of its own) or from inside a
|
|
769
792
|
# save (the lock isn't re-entrant).
|
|
793
|
+
#
|
|
794
|
+
# A session read from its file, or saved once, whose file is gone was
|
|
795
|
+
# deleted (SessionManager.delete_session, the retention sweep): the save
|
|
796
|
+
# is skipped (and logged) rather than bring it back. Only a new
|
|
797
|
+
# session's first save creates the file.
|
|
770
798
|
# @param now [Time, nil] the updated_at to stamp (a spec's clock); nil,
|
|
771
799
|
# the time the lock is taken
|
|
772
800
|
def save(state_dir: self.class.default_state_dir, now: nil)
|
|
@@ -781,7 +809,13 @@ module Samagotchi
|
|
|
781
809
|
# Auto-compute first_preview if not yet cached and messages contain a user entry.
|
|
782
810
|
compute_first_preview!
|
|
783
811
|
|
|
812
|
+
if @persisted && !File.exist?(path)
|
|
813
|
+
Log.warn(:worker, "save_skipped_deleted", sid: @id)
|
|
814
|
+
return self
|
|
815
|
+
end
|
|
816
|
+
|
|
784
817
|
AtomicFile.write(path, JSON.pretty_generate(to_h) + "\n")
|
|
818
|
+
@persisted = true
|
|
785
819
|
end
|
|
786
820
|
self
|
|
787
821
|
end
|
|
@@ -845,7 +879,25 @@ module Samagotchi
|
|
|
845
879
|
|
|
846
880
|
dir = session_dir(session_id, state_dir: state_dir)
|
|
847
881
|
FileUtils.mkdir_p(dir)
|
|
848
|
-
|
|
882
|
+
# Its own at, on the clock the starting marker's at is written with
|
|
883
|
+
# (StartingMarker; resume_session compares them): a file's mtime may be
|
|
884
|
+
# coarse or skewed. Older chi readers ask only whether it exists.
|
|
885
|
+
AtomicFile.write(File.join(dir, STOPPED_FILE), JSON.generate({ "at" => Time.now.iso8601(6) }))
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
# When the stop marker was written: its own at, or (an older chi's empty
|
|
889
|
+
# file) its mtime; nil without one.
|
|
890
|
+
# @return [Time, nil]
|
|
891
|
+
def self.stopped_at(session_id, state_dir: default_state_dir)
|
|
892
|
+
path = File.join(session_dir(session_id, state_dir: state_dir), STOPPED_FILE)
|
|
893
|
+
own = begin
|
|
894
|
+
Time.iso8601(JSON.parse(File.read(path))["at"].to_s)
|
|
895
|
+
rescue JSON::ParserError, ArgumentError, TypeError, NoMethodError
|
|
896
|
+
nil
|
|
897
|
+
end
|
|
898
|
+
own || File.mtime(path)
|
|
899
|
+
rescue SystemCallError
|
|
900
|
+
nil
|
|
849
901
|
end
|
|
850
902
|
|
|
851
903
|
# Remove the stop marker (a resume, before its worker starts).
|
|
@@ -19,6 +19,7 @@ require_relative "../send_command/busy"
|
|
|
19
19
|
require_relative "../chi_invocation"
|
|
20
20
|
require_relative "../worker_sidecar"
|
|
21
21
|
require_relative "../worker_link"
|
|
22
|
+
require_relative "../worker_presence"
|
|
22
23
|
require_relative "../review_start"
|
|
23
24
|
require_relative "../launch_mode"
|
|
24
25
|
require_relative "../participants"
|
|
@@ -103,7 +104,7 @@ module Samagotchi
|
|
|
103
104
|
session = show(id)
|
|
104
105
|
raise Refused.new(Session::SCRATCH_REFUSED, code: "scratch") if session.scratch
|
|
105
106
|
|
|
106
|
-
link =
|
|
107
|
+
link = WorkerPresence.read(id, state_dir: state_dir!, link: :unlink).link
|
|
107
108
|
case link
|
|
108
109
|
when WorkerLink::Live then return Woken.new(woke: false, sidecar: link.sidecar)
|
|
109
110
|
when WorkerLink::Skew then raise Refused.new(link.message(id), code: "worker_newer")
|
|
@@ -126,8 +127,8 @@ module Samagotchi
|
|
|
126
127
|
# @raise [ArgumentError] no such session
|
|
127
128
|
def worker(id)
|
|
128
129
|
show(id)
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
presence = WorkerPresence.read(id, state_dir: state_dir!, link: :unlink)
|
|
131
|
+
WorkerState.new(link: presence.link, stopped: presence.stop_marked?)
|
|
131
132
|
end
|
|
132
133
|
|
|
133
134
|
# --- create and send
|
|
@@ -143,11 +144,11 @@ module Samagotchi
|
|
|
143
144
|
|
|
144
145
|
start = spec.preview ? { prompt: spec.prompt, preview: spec.preview } : { prompt: spec.prompt }
|
|
145
146
|
if spec.continues
|
|
146
|
-
return SessionContinue.run(spec.continues, **start, model: spec.model_name, **dir)
|
|
147
|
+
return SessionContinue.run(spec.continues, **start, model: spec.model_name, client_id: spec.client_id, **dir)
|
|
147
148
|
end
|
|
148
149
|
|
|
149
150
|
SessionManager.spawn_session(**start, working_directory: spec.working_directory, model_name: spec.model_name,
|
|
150
|
-
setup: spec.setup, **dir)
|
|
151
|
+
setup: spec.setup, client_id: spec.client_id, **dir)
|
|
151
152
|
end
|
|
152
153
|
|
|
153
154
|
# A review session (ReviewStart): a fork of review.from, or a fresh
|
|
@@ -313,20 +314,17 @@ module Samagotchi
|
|
|
313
314
|
return ContextPush.new(status: :missing) unless attached
|
|
314
315
|
return ContextPush.new(status: :not_push) unless attached.source.push?
|
|
315
316
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if fetched.wake && before != written.revision && !target.project? && attached.location.session? &&
|
|
320
|
-
attached.source.scope == "session"
|
|
321
|
-
begin
|
|
322
|
-
subscription = attached.location.subscription(name)
|
|
323
|
-
recent_wake = context_wake_recent?(subscription)
|
|
324
|
-
SessionManager.wake_for_context(target.session_id, **dir) unless recent_wake || Config.get("context.wake") == false
|
|
325
|
-
rescue StandardError => e
|
|
326
|
-
Log.exception(:context, "context_push_wake_failed", e, sid: target.session_id, source: name)
|
|
317
|
+
record = lambda do
|
|
318
|
+
attached.location.locked(name) do
|
|
319
|
+
[attached.location.snapshot(name).revision, attached.location.record_text(name, fetched)]
|
|
327
320
|
end
|
|
328
321
|
end
|
|
329
|
-
|
|
322
|
+
unless !target.project? && attached.location.session? && attached.source.scope == "session"
|
|
323
|
+
before, written = record.call
|
|
324
|
+
return ContextPush.new(status: :pushed, before: before, revision: written.revision)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
push_delivery(target.session_id, name, fetched, attached.location, record)
|
|
330
328
|
end
|
|
331
329
|
|
|
332
330
|
# The target's sources: a session's own, then its project's (shadowed
|
|
@@ -353,7 +351,8 @@ module Samagotchi
|
|
|
353
351
|
def context_candidates(target)
|
|
354
352
|
return [] if target.project?
|
|
355
353
|
|
|
356
|
-
ContextCandidates.with_status(target.session_id, project_root: target.project_root,
|
|
354
|
+
ContextCandidates.with_status(target.session_id, project_root: target.project_root, project_id: target.project_id,
|
|
355
|
+
state_dir: @state_dir)
|
|
357
356
|
.map { |candidate, status| ContextCandidate.new(candidate: candidate, status: status) }
|
|
358
357
|
end
|
|
359
358
|
|
|
@@ -371,7 +370,9 @@ module Samagotchi
|
|
|
371
370
|
ContextSources.check_name!(name)
|
|
372
371
|
return :removed if context_location(target).remove(name)
|
|
373
372
|
|
|
374
|
-
project = !target.project? && ContextSources.project_location_for(target.project_root,
|
|
373
|
+
project = !target.project? && ContextSources.project_location_for(target.project_root,
|
|
374
|
+
project_id: target.project_id,
|
|
375
|
+
state_dir: @state_dir)
|
|
375
376
|
project&.source(name) ? :project : :missing
|
|
376
377
|
end
|
|
377
378
|
|
|
@@ -394,7 +395,7 @@ module Samagotchi
|
|
|
394
395
|
# live in.
|
|
395
396
|
def context_location(target)
|
|
396
397
|
if target.project?
|
|
397
|
-
ContextSources.project_location_for(target.project_root, state_dir: @state_dir)
|
|
398
|
+
ContextSources.project_location_for(target.project_root, project_id: target.project_id, state_dir: @state_dir)
|
|
398
399
|
else
|
|
399
400
|
ContextSources.session_location(target.session_id, state_dir: @state_dir)
|
|
400
401
|
end
|
|
@@ -465,6 +466,36 @@ module Samagotchi
|
|
|
465
466
|
|
|
466
467
|
private
|
|
467
468
|
|
|
469
|
+
# A session source's push, as a push delivery (SessionInbox.deliver
|
|
470
|
+
# :push): the text recorded, then a wake when it asks one (a change
|
|
471
|
+
# with wake, context.wake on, the source not woken lately) as the
|
|
472
|
+
# push WakePolicy decides (an idle-exited session starts; a stopped
|
|
473
|
+
# one never). A failed wake is logged: the push stands.
|
|
474
|
+
# @return [ContextPush]
|
|
475
|
+
def push_delivery(session_id, name, fetched, location, record)
|
|
476
|
+
before = written = asked = nil
|
|
477
|
+
delivered = begin
|
|
478
|
+
SessionInbox.deliver(:push, session_id, state_dir: state_dir!) do
|
|
479
|
+
before, written = record.call
|
|
480
|
+
asked = fetched.wake && before != written.revision && context_wake_allowed?(location, name)
|
|
481
|
+
end
|
|
482
|
+
rescue StandardError => e
|
|
483
|
+
raise unless written
|
|
484
|
+
|
|
485
|
+
Log.exception(:context, "context_push_wake_failed", e, sid: session_id, source: name)
|
|
486
|
+
nil
|
|
487
|
+
end
|
|
488
|
+
# Its session file is gone (:gone): the source's folder takes it, as before.
|
|
489
|
+
before, written = record.call if delivered&.refused?
|
|
490
|
+
ContextPush.new(status: :pushed, before: before, revision: written.revision, wake: asked ? delivered&.wake : nil)
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def context_wake_allowed?(location, name)
|
|
494
|
+
return false if Config.get("context.wake") == false
|
|
495
|
+
|
|
496
|
+
!context_wake_recent?(location.subscription(name))
|
|
497
|
+
end
|
|
498
|
+
|
|
468
499
|
def grants = @grants ||= Grants.new(Grants.path(state_dir: state_dir!))
|
|
469
500
|
|
|
470
501
|
LOCAL_PROBE_TIMEOUT = 1.0
|
|
@@ -534,8 +565,8 @@ module Samagotchi
|
|
|
534
565
|
end
|
|
535
566
|
|
|
536
567
|
def attached_to(target, shadowed: false)
|
|
537
|
-
ContextSources.attached(target.session_id, project_root: target.project_root,
|
|
538
|
-
shadowed: shadowed)
|
|
568
|
+
ContextSources.attached(target.session_id, project_root: target.project_root, project_id: target.project_id,
|
|
569
|
+
state_dir: @state_dir, shadowed: shadowed)
|
|
539
570
|
end
|
|
540
571
|
|
|
541
572
|
# The block's post to the session's live worker, again on a 408 (the
|
|
@@ -317,14 +317,17 @@ module Samagotchi
|
|
|
317
317
|
def note(id, text:, source:)
|
|
318
318
|
reply = request(:post, "#{session_path(id)}/notes", body: { text: text, source: source })
|
|
319
319
|
json = reply.json.is_a?(Hash) ? reply.json : {}
|
|
320
|
+
# id: the link that has it, or refused it (a chi web before it names none: +id+).
|
|
321
|
+
head = Session.valid_id?(json["id"]) ? json["id"] : id
|
|
320
322
|
case reply.status
|
|
321
323
|
when 200
|
|
322
324
|
raise_refusal(reply) unless json["status"].is_a?(String)
|
|
323
325
|
|
|
324
|
-
NoteDelivery::Result.new(id: id, status: json["status"].to_sym, queued: json["queued"].to_i)
|
|
326
|
+
NoteDelivery::Result.new(id: head, asked: id, status: json["status"].to_sym, queued: json["queued"].to_i)
|
|
325
327
|
when 409
|
|
326
328
|
if json["error"] == "owned_by_tui"
|
|
327
|
-
return NoteDelivery::Result.new(id: id, status: :refused, queued: 0,
|
|
329
|
+
return NoteDelivery::Result.new(id: head, asked: id, status: :refused, queued: 0,
|
|
330
|
+
scratch: json["scratch"] == true)
|
|
328
331
|
end
|
|
329
332
|
|
|
330
333
|
raise_refusal(reply)
|
|
@@ -508,7 +511,10 @@ module Samagotchi
|
|
|
508
511
|
reply = request(:post, "#{session_path(target.session_id)}/context/#{name}/push",
|
|
509
512
|
body: { text: fetched.text, summary: fetched.summary, wake: fetched.wake, hint: fetched.hint })
|
|
510
513
|
case reply.status
|
|
511
|
-
when 200
|
|
514
|
+
when 200
|
|
515
|
+
# An older chi web's answer names no wake: the line is as it was.
|
|
516
|
+
wake = ContextPush::WAKES.find { |known| known.to_s == reply.json["wake"] }
|
|
517
|
+
ContextPush.new(status: :pushed, before: reply.json["before"], revision: reply.json["revision"], wake: wake)
|
|
512
518
|
when 404
|
|
513
519
|
return ContextPush.new(status: :missing) if context_missing?(reply)
|
|
514
520
|
|
|
@@ -736,7 +742,8 @@ module Samagotchi
|
|
|
736
742
|
raise SessionManager::ScratchSession, sid if json["scratch"] == true
|
|
737
743
|
|
|
738
744
|
raise SessionManager::OwnedByTUI, sid
|
|
739
|
-
when "worker_running", "still_stopping"
|
|
745
|
+
when "worker_running", "still_stopping", "scheduled"
|
|
746
|
+
raise SessionManager::DeleteRefused.new(sid, reason, message: message)
|
|
740
747
|
when "busy", "scratch"
|
|
741
748
|
raise SessionManager::ArchiveRefused.new(sid, reason, busy_id: json["busy_id"]) if reason
|
|
742
749
|
when "continued", "open_children", "folder_gone"
|
|
@@ -809,7 +816,8 @@ module Samagotchi
|
|
|
809
816
|
owner = json["owner"] && OwnerLock::Owner.new(pid: nil, kind: json["owner"].to_s, started_at: nil)
|
|
810
817
|
SessionView.new(id: json["id"], status: json["status"], title: json["title"],
|
|
811
818
|
display_title: json["display_title"] || json["title"] || json["first_preview"].to_s,
|
|
812
|
-
project_root: json["project_root"],
|
|
819
|
+
project_root: json["project_root"], project_id: json["project_id"],
|
|
820
|
+
working_directory: json["working_directory"],
|
|
813
821
|
model_name: json["model_name"], model_typed: json["model_typed"],
|
|
814
822
|
parent_answers: json["parent_answers"], owner: owner,
|
|
815
823
|
pending_question: PendingQuestion.from_file(pending))
|
|
@@ -141,8 +141,8 @@ module Samagotchi
|
|
|
141
141
|
|
|
142
142
|
# POST …/cancel.
|
|
143
143
|
# @return [BridgeClient::Response]
|
|
144
|
-
def cancel(reason:)
|
|
145
|
-
post(:cancel, "cancel", { reason: reason })
|
|
144
|
+
def cancel(reason:, turn_id: nil)
|
|
145
|
+
post(:cancel, "cancel", { reason: reason, turn_id: turn_id }.compact)
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
# POST …/exit: the worker asked to exit (chi web passes client_id on
|
|
@@ -166,7 +166,10 @@ module Samagotchi
|
|
|
166
166
|
# @!attribute owner [OwnerLock::Owner, nil] its worker or one-turn chi run
|
|
167
167
|
# @!attribute pending_question [PendingQuestion, nil] the file's
|
|
168
168
|
SessionView = Data.define(:id, :status, :title, :display_title, :project_root, :working_directory, :model_name,
|
|
169
|
-
:model_typed, :parent_answers, :owner, :pending_question)
|
|
169
|
+
:model_typed, :parent_answers, :owner, :pending_question, :project_id) do
|
|
170
|
+
def initialize(id:, status:, title:, display_title:, project_root:, working_directory:, model_name:,
|
|
171
|
+
model_typed:, parent_answers:, owner:, pending_question:, project_id: nil) = super
|
|
172
|
+
end
|
|
170
173
|
|
|
171
174
|
# What chi send --new created (Remote; Local gives the Session): its
|
|
172
175
|
# id, a model warning, the session it continues, the delegates it took
|
|
@@ -268,9 +271,15 @@ module Samagotchi
|
|
|
268
271
|
# chi context push's outcome: :pushed (+before+ the revision it had,
|
|
269
272
|
# +revision+ the new one), :missing (no such source), :not_push (a
|
|
270
273
|
# command source, whose text is its command's) or :continued (the
|
|
271
|
-
# session was continued, as +next_id+: nothing was written).
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
# session was continued, as +next_id+: nothing was written). +wake+:
|
|
275
|
+
# what a push that asked for a wake did (SessionInbox.deliver :push:
|
|
276
|
+
# :woken, :live, :stopped, :starting, :archived, :scratch, :owned,
|
|
277
|
+
# :gone), nil when it asked none (or an older chi web's answer has none).
|
|
278
|
+
ContextPush = Data.define(:status, :before, :revision, :next_id, :wake) do
|
|
279
|
+
def initialize(status:, before: nil, revision: nil, next_id: nil, wake: nil) = super
|
|
280
|
+
|
|
281
|
+
# What chi context push adds to its line for the wake, else nil.
|
|
282
|
+
def wake_line = ContextPush::WAKE_LINES[wake]
|
|
274
283
|
|
|
275
284
|
# Why a push to +name+ was refused as :not_push: a command source's
|
|
276
285
|
# text is its command's, and a push between two fetches would read
|
|
@@ -286,5 +295,12 @@ module Samagotchi
|
|
|
286
295
|
"attach it to #{next_id} (or pair again with #{next_id}) and push there"
|
|
287
296
|
end
|
|
288
297
|
end
|
|
298
|
+
|
|
299
|
+
# The wakes a chi web's push answer may name (Remote reads no other).
|
|
300
|
+
ContextPush::WAKES = %i[woken live stopped starting archived scratch owned gone].freeze
|
|
301
|
+
# chi context push's words for a wake; none for the others (:live: the
|
|
302
|
+
# worker sees it as any push).
|
|
303
|
+
ContextPush::WAKE_LINES = { woken: "chi woken", stopped: "chi is stopped: it sees this at its next start",
|
|
304
|
+
starting: "chi is starting" }.freeze
|
|
289
305
|
end
|
|
290
306
|
end
|
|
@@ -473,7 +473,8 @@ module Samagotchi
|
|
|
473
473
|
|
|
474
474
|
peers = Tools::Peers.new(session_id: session.id, cwd: session.working_directory, state_dir: @engine.peer_state_dir)
|
|
475
475
|
listing = Tools::ContextRead.call("", peers: peers)
|
|
476
|
-
candidates = ContextCandidates.listing(session.id, project_root: session.project_root,
|
|
476
|
+
candidates = ContextCandidates.listing(session.id, project_root: session.project_root, project_id: session.project_id,
|
|
477
|
+
state_dir: @engine.peer_state_dir)
|
|
477
478
|
candidates ? "#{listing}\n\n#{candidates}" : listing
|
|
478
479
|
end
|
|
479
480
|
|
|
@@ -618,9 +619,12 @@ module Samagotchi
|
|
|
618
619
|
|
|
619
620
|
def approval_line(entry)
|
|
620
621
|
what = entry["key"] ? entry["key"].tr("\n", " ") : "any call rule #{entry["rule"]} asks about"
|
|
621
|
-
where = if entry["scope"] == "session"
|
|
622
|
+
where = if entry["scope"] == "session"
|
|
623
|
+
place = entry["repo_root"] ? " in #{entry["repo_root"]}" : ""
|
|
624
|
+
"session #{entry["session_id"].to_s[0, 8]}#{place}#{" with its env:" if entry["env"]}"
|
|
622
625
|
elsif entry["repo"] then "in repo #{entry["repo"]}"
|
|
623
|
-
else
|
|
626
|
+
else
|
|
627
|
+
"in #{entry["repo_root"]}"
|
|
624
628
|
end
|
|
625
629
|
rule = entry["rule"] ? " (rule #{entry["rule"]}, #{entry["source"]})" : ""
|
|
626
630
|
"#{entry["scope"]}: #{what} — #{where}#{rule}, #{entry["created_at"]}"
|