samagotchi 0.155.0 → 0.157.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 +57 -1
- data/bin/chi +2 -1
- data/docs/architecture.md +22 -13
- data/docs/bundles.md +3 -1
- data/docs/cli.md +9 -8
- data/docs/configuration.md +36 -4
- data/docs/guardrails.md +9 -5
- data/docs/hooks.md +11 -5
- data/docs/internals/anthropic.md +1 -1
- data/docs/internals/attached-context.md +3 -2
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/bridge.md +83 -6
- data/docs/internals/bundles.md +28 -18
- data/docs/internals/context-telemetry.md +4 -3
- data/docs/internals/engine.md +641 -937
- data/docs/internals/llm-context-forget.md +8 -3
- data/docs/internals/objectives.md +2 -2
- data/docs/internals/participants.md +3 -2
- data/docs/internals/prompt-caching.md +4 -1
- data/docs/internals/remote-sessions.md +8 -3
- data/docs/internals/web-frontend.md +3 -2
- data/docs/internals/working-set.md +2 -2
- data/docs/memory.md +13 -2
- data/docs/plugins.md +35 -2
- data/docs/sessions.md +11 -11
- data/docs/sub-agent.md +17 -2
- data/docs/system-one.md +3 -2
- data/docs/testing.md +53 -2
- data/lib/samagotchi/answer_command.rb +2 -1
- data/lib/samagotchi/archive_store.rb +4 -3
- data/lib/samagotchi/bootstrap/config_writer.rb +1 -1
- data/lib/samagotchi/bootstrap_command.rb +3 -2
- data/lib/samagotchi/bridge/card_places.rb +10 -4
- data/lib/samagotchi/bridge/card_store.rb +3 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +109 -9
- data/lib/samagotchi/bridge/turn_drawings.rb +265 -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/broadcast_command.rb +3 -3
- data/lib/samagotchi/bundle_command/check.rb +18 -0
- data/lib/samagotchi/bundle_command/try/sandbox.rb +2 -2
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +5 -2
- 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 +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
- data/lib/samagotchi/child_move.rb +13 -57
- data/lib/samagotchi/child_reports.rb +2 -2
- data/lib/samagotchi/children_status.rb +15 -21
- data/lib/samagotchi/cli/parent_wait.rb +9 -23
- data/lib/samagotchi/config.rb +20 -112
- data/lib/samagotchi/context_command.rb +1 -2
- data/lib/samagotchi/context_sources.rb +5 -4
- data/lib/samagotchi/context_window.rb +9 -12
- data/lib/samagotchi/desktop/macos.rb +1 -1
- data/lib/samagotchi/effective_model.rb +57 -22
- data/lib/samagotchi/engine/context_judge.rb +19 -11
- data/lib/samagotchi/engine/messages.rb +1 -1
- data/lib/samagotchi/engine/models.rb +5 -2
- data/lib/samagotchi/engine/objectives.rb +29 -11
- data/lib/samagotchi/engine/plugins.rb +1 -1
- data/lib/samagotchi/engine/recap.rb +8 -4
- data/lib/samagotchi/engine/side_requests.rb +21 -11
- data/lib/samagotchi/engine/toolkit.rb +10 -9
- data/lib/samagotchi/engine/turn_end.rb +1 -1
- data/lib/samagotchi/engine/turn_helpers.rb +7 -7
- data/lib/samagotchi/engine/verify.rb +1 -1
- data/lib/samagotchi/engine.rb +43 -12
- data/lib/samagotchi/events.rb +3 -2
- data/lib/samagotchi/extension_load.rb +6 -3
- data/lib/samagotchi/guardrails/targets.rb +24 -9
- data/lib/samagotchi/guardrails_command.rb +1 -1
- data/lib/samagotchi/host_address.rb +52 -0
- data/lib/samagotchi/host_config.rb +72 -1
- data/lib/samagotchi/host_model.rb +2 -6
- data/lib/samagotchi/host_registry.rb +58 -46
- data/lib/samagotchi/kernel_loop.rb +53 -124
- data/lib/samagotchi/llm/anthropic_wire.rb +2 -2
- data/lib/samagotchi/llm/chat_loop.rb +40 -21
- data/lib/samagotchi/llm/native_backend.rb +1 -1
- data/lib/samagotchi/llm_context_apply.rb +54 -1
- data/lib/samagotchi/llm_context_notice.rb +1 -1
- data/lib/samagotchi/llm_context_strategy.rb +29 -67
- data/lib/samagotchi/llm_context_view.rb +9 -0
- data/lib/samagotchi/memory_bundle/asset_installer.rb +71 -76
- data/lib/samagotchi/memory_bundle/builder.rb +2 -1
- 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_copy.rb +2 -1
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +8 -1
- data/lib/samagotchi/memory_bundle/installer.rb +185 -169
- data/lib/samagotchi/memory_bundle/manifest.rb +14 -4
- 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 +15 -1
- data/lib/samagotchi/memory_bundle/remerge.rb +25 -12
- data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -6
- data/lib/samagotchi/memory_bundle/status.rb +14 -20
- data/lib/samagotchi/memory_bundle/system_bundle.rb +24 -6
- data/lib/samagotchi/memory_bundle/trash.rb +23 -8
- data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
- data/lib/samagotchi/memory_usage.rb +1 -1
- data/lib/samagotchi/model_lookup.rb +75 -0
- data/lib/samagotchi/model_price.rb +4 -10
- data/lib/samagotchi/model_profile.rb +7 -6
- data/lib/samagotchi/model_settings.rb +37 -2
- data/lib/samagotchi/models_command.rb +1 -1
- data/lib/samagotchi/note_command.rb +2 -3
- data/lib/samagotchi/objectives_audit.rb +4 -3
- data/lib/samagotchi/participant_mode.rb +2 -1
- data/lib/samagotchi/participants.rb +2 -2
- data/lib/samagotchi/plugin/api.rb +41 -6
- data/lib/samagotchi/plugin/context.rb +1 -1
- data/lib/samagotchi/plugin/loader.rb +33 -10
- data/lib/samagotchi/plugin/sessions.rb +3 -6
- data/lib/samagotchi/plugin/tool_spec.rb +5 -3
- data/lib/samagotchi/project_env.rb +1 -1
- data/lib/samagotchi/reasoning_history.rb +14 -20
- data/lib/samagotchi/recap_place.rb +27 -0
- data/lib/samagotchi/reminder_carry.rb +1 -1
- 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/reprefill_meter.rb +45 -0
- data/lib/samagotchi/review_command.rb +1 -1
- data/lib/samagotchi/sampling_settings.rb +9 -16
- data/lib/samagotchi/self_report.rb +36 -37
- data/lib/samagotchi/send_command.rb +17 -23
- data/lib/samagotchi/session.rb +370 -445
- data/lib/samagotchi/session_access/local.rb +12 -11
- data/lib/samagotchi/session_access/remote.rb +22 -6
- data/lib/samagotchi/session_archive_command.rb +2 -2
- data/lib/samagotchi/session_chain.rb +7 -6
- data/lib/samagotchi/session_continue.rb +6 -5
- data/lib/samagotchi/session_delete_command.rb +1 -1
- data/lib/samagotchi/session_field.rb +133 -0
- data/lib/samagotchi/session_inbox.rb +49 -1
- data/lib/samagotchi/session_manager/delivery.rb +254 -0
- data/lib/samagotchi/session_manager/emptiness.rb +139 -0
- data/lib/samagotchi/session_manager/lifecycle.rb +274 -0
- data/lib/samagotchi/session_manager/listing.rb +118 -0
- data/lib/samagotchi/session_manager/removal.rb +190 -0
- data/lib/samagotchi/session_manager/spawn.rb +242 -0
- data/lib/samagotchi/session_manager/worker_command.rb +157 -0
- data/lib/samagotchi/session_manager.rb +28 -1541
- data/lib/samagotchi/session_metrics.rb +8 -5
- 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/share_command.rb +1 -1
- data/lib/samagotchi/starting_marker.rb +65 -0
- data/lib/samagotchi/steer_cut_after.rb +7 -14
- data/lib/samagotchi/system_one/recorder.rb +49 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
- data/lib/samagotchi/terminal_ui/exit_lines.rb +1 -1
- data/lib/samagotchi/terminal_ui/join_replay.rb +86 -8
- data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
- data/lib/samagotchi/terminal_ui.rb +5 -7
- data/lib/samagotchi/testing/install.rb +2 -1
- data/lib/samagotchi/testing/sessions.rb +1 -1
- data/lib/samagotchi/thinking.rb +16 -21
- data/lib/samagotchi/token_usage.rb +1 -1
- data/lib/samagotchi/tool_activity.rb +10 -4
- data/lib/samagotchi/tool_declarations.rb +2 -2
- data/lib/samagotchi/tool_output_cut.rb +17 -0
- data/lib/samagotchi/tool_response.rb +5 -3
- data/lib/samagotchi/tool_runner.rb +25 -5
- data/lib/samagotchi/toolkit_command.rb +1 -1
- data/lib/samagotchi/tools/delegate.rb +55 -45
- data/lib/samagotchi/tools/delegate_relay.rb +5 -5
- data/lib/samagotchi/tools/delegate_result.rb +5 -5
- data/lib/samagotchi/tools/delegate_wait.rb +41 -17
- data/lib/samagotchi/tools/index_hint.rb +14 -0
- data/lib/samagotchi/tools/list_sessions.rb +7 -7
- data/lib/samagotchi/tools/registry.rb +28 -11
- data/lib/samagotchi/tools/send_note.rb +4 -6
- data/lib/samagotchi/tools/tool_index.rb +52 -51
- data/lib/samagotchi/tools/tool_surface.rb +22 -8
- data/lib/samagotchi/turn_context.rb +2 -1
- data/lib/samagotchi/turn_stages/context_judge.rb +38 -36
- data/lib/samagotchi/turn_stages/objectives.rb +56 -52
- data/lib/samagotchi/turn_stages/toolkit_suggestion.rb +13 -17
- data/lib/samagotchi/turn_stages/verify_anchors.rb +9 -10
- data/lib/samagotchi/turn_stages/warmup.rb +14 -15
- 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 +45 -28
- data/lib/samagotchi/web/message_display.rb +57 -7
- data/lib/samagotchi/web/participant_access.rb +3 -3
- 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/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 +6 -6
- data/lib/samagotchi/web/session_control.rb +4 -4
- data/lib/samagotchi/web/session_create.rb +3 -3
- data/lib/samagotchi/web/session_hub.rb +9 -17
- data/lib/samagotchi/web/session_show.rb +67 -5
- data/lib/samagotchi/web/session_summary.rb +39 -28
- data/lib/samagotchi/worker.rb +83 -32
- data/lib/samagotchi/worker_boot.rb +184 -0
- data/lib/samagotchi/worker_link.rb +3 -0
- data/lib/samagotchi/worker_signals.rb +1 -1
- data/lib/samagotchi/worker_task_wakes.rb +2 -2
- metadata +32 -4
- data/lib/samagotchi/send_command/wait_target.rb +0 -85
|
@@ -114,7 +114,7 @@ Bundles are versioned directories/zips/tar.gz/git URLs with a `manifest.yml` and
|
|
|
114
114
|
| Command | Purpose |
|
|
115
115
|
|---------|---------|
|
|
116
116
|
| `install <source> [--scope system\|project] [--force]` | Install from dir/zip/tar.gz/git URL. `--force` overwrites existing entries, otherwise skips them; a skipped file stays the user's (not recorded, never upgraded or removed by the bundle). It also installs a bundle whose `requires_chi` this chi doesn't meet: without `--force` that install is refused (`NAME can't be installed: it requires chi …`). Writes provenance to `.bundles/<name>/` (base snapshots + `manifest.json`) and updates `index.md`. Warns on double-brace placeholders and checksum mismatches (strict mode). |
|
|
117
|
-
| `upgrade <source\|name> [--force] [--dry-run] [--agent]` | A bare `name` is an installed bundle: chi upgrades it from the source its record keeps (its git URL with the `#ref` it recorded, its folder, or the archive it came from) — a dev install is refused, and a record whose source is gone says so and asks for the path or URL. Otherwise 3-way merge upgrade (base vs current vs incoming) per `Merger::Upgrade#
|
|
117
|
+
| `upgrade <source\|name> [--force] [--dry-run] [--agent]` | A bare `name` is an installed bundle: chi upgrades it from the source its record keeps (its git URL with the `#ref` it recorded, its folder, or the archive it came from) — a dev install is refused, and a record whose source is gone says so and asks for the path or URL. Otherwise 3-way merge upgrade (base vs current vs incoming) per `Merger::Upgrade#decision` (the file's `FileState` and its `FileActions` row, then a text merge where both sides changed): `install` (new file), `noop` (current==incoming), `fast_forward` (current==base, not edited → auto-update), `keep` (incoming==base → preserve local edits), `merged` (both edited, a text merge with `git` comes out clean → the file takes it, the old one goes to the bundle trash, and entries both sides added under a `Changelog` heading are kept), `conflict` (both edited, a real conflict, or no `git` or base → the file stays untouched and marked, reported as `X stays behind 0.8.0: …` with why and what it missed, needs a retry, `--force`, which copies it to the trash first, or interactive `memory_write` resolution). A marked file is retried at the same version: the system bundle's at each chi start and on `chi update`, another shipped bundle's on `chi update`, any other bundle's on `chi bundle upgrade`. A file marked by an older chi merges normally when its recorded base is a version chi shipped (an installed samagotchi gem still has it); otherwise it merges only if the bundle takes out or rewords no line it still holds, and if it does, it stays behind until resolved by hand, with `--agent` or with `--force`, and the retry can't fix that. Pruned files (removed from new bundle) are kept if locally edited, otherwise warned. |
|
|
118
118
|
| `uninstall <bundle> [--force]` | Moves the bundle's memory files to `.bundles/.trash/<bundle>-<time>/` (refuses locally edited ones unless `--force`; keeps a file another installed bundle also lists), removes their `index.md` lines, deletes the provenance dir. |
|
|
119
119
|
| `status [<bundle>]` | Provenance + per-file `ok|modified|missing|unreadable|no-index` vs stored checksum and base snapshot, and what stays behind or merged at a chi start; `issues=` counts files behind, missing or unreadable, not edits; a model overlay (`<name>.<key>.md`) has no index line and reads `ok (model overlay)`. |
|
|
120
120
|
| `diff <bundle> [file]` | Prints `base` (provenance snapshot) vs `current` (on-disk) for each file. |
|
|
@@ -18,27 +18,22 @@ module Samagotchi
|
|
|
18
18
|
autoload :SessionContinue, File.expand_path("session_continue", __dir__)
|
|
19
19
|
|
|
20
20
|
# A continue moving the previous link's open delegates to the chain's new
|
|
21
|
-
# link (
|
|
21
|
+
# link (SessionContinue.run), instead of archiving them with
|
|
22
22
|
# it. A moved child gets a parent override (Session::PARENT_FILE), never a
|
|
23
23
|
# rewritten session file, so its live worker saving a stale copy changes
|
|
24
24
|
# nothing; its worker reads the override for its rings and at each turn's
|
|
25
25
|
# start (Bridge::FEATURES "reparent").
|
|
26
26
|
#
|
|
27
27
|
# #apply runs between the new link's save and its worker's spawn, while
|
|
28
|
-
# the link is
|
|
29
|
-
# second worker from the child's process until the link's
|
|
30
|
-
# Each step is idempotent,
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
28
|
+
# the link is marked starting (StartingMarker): a ring from a moved child
|
|
29
|
+
# must not spawn a second worker from the child's process until the link's
|
|
30
|
+
# own holds it. Each step is idempotent, and an intent file (INTENT_FILE)
|
|
31
|
+
# in the new link's folder names the move until it is done: a worker that
|
|
32
|
+
# starts and finds one finishes the move (#adopt), and a failed continue
|
|
33
|
+
# moves the same children back.
|
|
34
34
|
module ChildMove
|
|
35
35
|
# <new link dir>/move.json: {from, ids, noted}, while a move is under way.
|
|
36
36
|
INTENT_FILE = "move.json"
|
|
37
|
-
# <new link dir>/starting: {pid, at}, from the move to the worker's spawn.
|
|
38
|
-
STARTING_FILE = "starting"
|
|
39
|
-
# A starting marker older than this, or whose process is gone, is left
|
|
40
|
-
# over from a continue that died: it holds nothing back.
|
|
41
|
-
STARTING_STALE = 60.0
|
|
42
37
|
# Who the note to a moved child is from (ContextNote's label).
|
|
43
38
|
NOTE_SOURCE = "session chain"
|
|
44
39
|
# What a live child's worker must name in its sidecar to be moved.
|
|
@@ -65,23 +60,23 @@ module Samagotchi
|
|
|
65
60
|
move = []
|
|
66
61
|
refuse = []
|
|
67
62
|
SessionManager.children_of(prev_id, state_dir: state_dir).each do |row|
|
|
68
|
-
subtree = [row
|
|
63
|
+
subtree = [row.id, *SessionManager.descendant_ids(row.id, state_dir)]
|
|
69
64
|
next if (subtree & open_ids).empty?
|
|
70
65
|
|
|
71
|
-
if row
|
|
66
|
+
if row.archived
|
|
72
67
|
# An open delegate below an archived one: the archive's cascade
|
|
73
68
|
# would take it, the move doesn't (only unarchived ones move).
|
|
74
69
|
refuse.concat(open.select { |c| subtree.include?(c.id) }.map do |c|
|
|
75
|
-
c.with(why: "under archived #{row
|
|
70
|
+
c.with(why: "under archived #{row.short_id}; unarchive that one or archive this one")
|
|
76
71
|
end)
|
|
77
72
|
next
|
|
78
73
|
end
|
|
79
74
|
|
|
80
|
-
why = unmovable(row
|
|
75
|
+
why = unmovable(row.id, state_dir: state_dir)
|
|
81
76
|
if why
|
|
82
|
-
refuse << SessionContinue::OpenChild.new(id: row
|
|
77
|
+
refuse << SessionContinue::OpenChild.new(id: row.id, short_id: row.short_id, why: why)
|
|
83
78
|
else
|
|
84
|
-
move << row
|
|
79
|
+
move << row.id
|
|
85
80
|
end
|
|
86
81
|
end
|
|
87
82
|
Plan.new(move: move, refuse: refuse)
|
|
@@ -206,44 +201,5 @@ module Samagotchi
|
|
|
206
201
|
end
|
|
207
202
|
SessionInbox.write_note(child_id, text: text, source: NOTE_SOURCE, state_dir: state_dir)
|
|
208
203
|
end
|
|
209
|
-
|
|
210
|
-
# Hold off a worker for the continue's new link (#starting?) until its
|
|
211
|
-
# own worker holds it: written before the move, removed by that worker
|
|
212
|
-
# once it has the owner lock (Worker#start).
|
|
213
|
-
def mark_starting(session_id, state_dir:)
|
|
214
|
-
dir = Session.session_dir(session_id, state_dir: state_dir)
|
|
215
|
-
FileUtils.mkdir_p(dir)
|
|
216
|
-
AtomicFile.write(File.join(dir, STARTING_FILE), JSON.generate({ "pid" => Process.pid, "at" => Time.now.iso8601(3) }))
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
def clear_starting(session_id, state_dir:)
|
|
220
|
-
FileUtils.rm_f(File.join(Session.session_dir(session_id, state_dir: state_dir), STARTING_FILE))
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# Whether a continue is moving delegates to +session_id+ right now: its
|
|
224
|
-
# marker is there, fresh, and the process that wrote it runs.
|
|
225
|
-
def starting?(session_id, state_dir:)
|
|
226
|
-
file = File.join(Session.session_dir(session_id, state_dir: state_dir), STARTING_FILE)
|
|
227
|
-
return false unless File.exist?(file)
|
|
228
|
-
|
|
229
|
-
data = JSON.parse(File.read(file))
|
|
230
|
-
return false if Time.now - Time.iso8601(data["at"].to_s) > STARTING_STALE
|
|
231
|
-
|
|
232
|
-
process_alive?(data["pid"].to_i)
|
|
233
|
-
rescue JSON::ParserError, ArgumentError, SystemCallError
|
|
234
|
-
false
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# Signal 0 only asks whether the process exists.
|
|
238
|
-
def process_alive?(pid)
|
|
239
|
-
return false unless pid.positive?
|
|
240
|
-
|
|
241
|
-
Process.kill(0, pid)
|
|
242
|
-
true
|
|
243
|
-
rescue Errno::ESRCH
|
|
244
|
-
false
|
|
245
|
-
rescue Errno::EPERM
|
|
246
|
-
true
|
|
247
|
-
end
|
|
248
204
|
end
|
|
249
205
|
end
|
|
@@ -335,8 +335,8 @@ module Samagotchi
|
|
|
335
335
|
before = kept ? kept.after : current
|
|
336
336
|
# A follow-up's turn (DelegateCursor#turn) is reported by its own
|
|
337
337
|
# reply, as delegate_result takes it, not a newer queued turn's.
|
|
338
|
-
wait = ReplyWait.call(child_id, state_dir: @state_dir,
|
|
339
|
-
timeout: 0
|
|
338
|
+
wait = ReplyWait.call(child_id, state_dir: @state_dir, **ReplyWait::Target.from_cursor(before, child: child).to_h,
|
|
339
|
+
timeout: 0)
|
|
340
340
|
return nil unless reportable?(wait)
|
|
341
341
|
|
|
342
342
|
claim_id = before.report_claim&.id || SecureRandom.hex(16)
|
|
@@ -5,7 +5,8 @@ require_relative "session"
|
|
|
5
5
|
require_relative "archive_store"
|
|
6
6
|
require_relative "git_head"
|
|
7
7
|
require_relative "reply_wait"
|
|
8
|
-
require_relative "
|
|
8
|
+
require_relative "owner_lock"
|
|
9
|
+
require_relative "session_row"
|
|
9
10
|
require_relative "tools/delegate"
|
|
10
11
|
require_relative "tools/delegate_cursor"
|
|
11
12
|
|
|
@@ -50,44 +51,47 @@ module Samagotchi
|
|
|
50
51
|
def of(parent_id, state_dir:, include_archived: false)
|
|
51
52
|
cursors = Tools::DelegateCursors.read(parent_id, state_dir: state_dir)
|
|
52
53
|
SessionManager.children_of(parent_id, state_dir: state_dir).filter_map do |row|
|
|
53
|
-
next if row
|
|
54
|
+
next if row.archived && !include_archived
|
|
54
55
|
|
|
55
|
-
session = load(row
|
|
56
|
+
session = load(row.id, state_dir: state_dir)
|
|
56
57
|
session && child(session, row, cursors, state_dir: state_dir)
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
# The cheap path for the glance views: only the children in the
|
|
61
|
-
# parent's delegates.json (one Session.load and
|
|
62
|
+
# parent's delegates.json (one Session.load and a SessionRow each, no
|
|
62
63
|
# scan). Its keys can also name a fork the model passed to delegate
|
|
63
64
|
# session: or a deleted child: only live delegates of this parent count,
|
|
64
65
|
# archived ones don't.
|
|
65
66
|
# @return [Counts]
|
|
66
67
|
def counts(parent_id, state_dir:)
|
|
67
68
|
cursors = Tools::DelegateCursors.read(parent_id, state_dir: state_dir)
|
|
69
|
+
budgets = SessionRow.budgets
|
|
68
70
|
children = cursors.keys.filter_map do |id|
|
|
69
71
|
session = load(id, state_dir: state_dir)
|
|
70
72
|
next unless session&.delegate? && session.parent_id == parent_id
|
|
71
73
|
next if ArchiveStore.archived?(Session.session_dir(id, state_dir: state_dir))
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
dir = Session.session_dir(id, state_dir: state_dir)
|
|
76
|
+
child(session, SessionRow.build(session, session_dir: dir, owner: OwnerLock.owner(dir), budgets: budgets), cursors,
|
|
77
|
+
state_dir: state_dir)
|
|
74
78
|
end
|
|
75
79
|
Counts.new(running: children.count { |c| c.state == "running" },
|
|
76
80
|
waiting: children.count { |c| c.state == "waiting" },
|
|
77
81
|
unreported: children.count { |c| c.last_reply && !c.reported })
|
|
78
82
|
end
|
|
79
83
|
|
|
80
|
-
# @param row [
|
|
84
|
+
# @param row [SessionRow] the child's row
|
|
81
85
|
# @param cursors [Hash] the parent's delegates.json
|
|
82
86
|
def child(session, row, cursors, state_dir:)
|
|
83
87
|
reply = ReplyWait.newest_reply(session.id, state_dir: state_dir)
|
|
84
88
|
cursor = Tools::DelegateCursors.get_from(cursors, session.id)
|
|
85
|
-
Child.new(id: session.id, short_id: session.id[0, 8], title: (session.title || row
|
|
86
|
-
state: state_of(session, row, reply), waiting: row
|
|
89
|
+
Child.new(id: session.id, short_id: session.id[0, 8], title: (session.title || row.preview).to_s,
|
|
90
|
+
state: state_of(session, row, reply), waiting: row.waiting, live: !!row.live,
|
|
87
91
|
delegate: session.delegate?, cwd: session.working_directory, branch: GitHead.branch(session.working_directory),
|
|
88
92
|
last_reply: reply && first_line(session.id, reply, state_dir: state_dir),
|
|
89
93
|
last_reply_at: reply && reply_time(reply), reported: !reply.nil? && reported?(session, cursor, reply),
|
|
90
|
-
updated_at: session.updated_at, archived: !!row
|
|
94
|
+
updated_at: session.updated_at, archived: !!row.archived)
|
|
91
95
|
end
|
|
92
96
|
private_class_method :child
|
|
93
97
|
|
|
@@ -97,8 +101,8 @@ module Samagotchi
|
|
|
97
101
|
private_class_method :reported?
|
|
98
102
|
|
|
99
103
|
def state_of(session, row, reply)
|
|
100
|
-
return "waiting" if row
|
|
101
|
-
return "running" if
|
|
104
|
+
return "waiting" if row.waiting
|
|
105
|
+
return "running" if row.running_or_starting?
|
|
102
106
|
return "stopped" if session.status == Session::STATUS_STOPPED
|
|
103
107
|
|
|
104
108
|
outcome = session.last_turn&.outcome
|
|
@@ -109,16 +113,6 @@ module Samagotchi
|
|
|
109
113
|
end
|
|
110
114
|
private_class_method :state_of
|
|
111
115
|
|
|
112
|
-
# The keys of a children_of row that state_of and child read, for a
|
|
113
|
-
# session counts loaded itself.
|
|
114
|
-
def summary_of(session, state_dir:)
|
|
115
|
-
live = WorkerLink.resolve(Session.session_dir(session.id, state_dir: state_dir), unlink: false).worker_owned?
|
|
116
|
-
{ live: live, busy: live && session.status == Session::STATUS_RUNNING, status: session.status,
|
|
117
|
-
updated_at: session.updated_at, waiting: session.waiting_question(live: live)&.dig(:kind),
|
|
118
|
-
preview: session.last_prompt.to_s }
|
|
119
|
-
end
|
|
120
|
-
private_class_method :summary_of
|
|
121
|
-
|
|
122
116
|
def first_line(id, file, state_dir:)
|
|
123
117
|
text = File.read(File.join(ReplyWait.reply_dir(id, state_dir: state_dir), file), 4096)
|
|
124
118
|
text.each_line.map(&:strip).find { |line| !line.empty? }.to_s
|
|
@@ -17,9 +17,6 @@ module Samagotchi
|
|
|
17
17
|
# override the constants (specs stub them on the class).
|
|
18
18
|
module ParentWait
|
|
19
19
|
POLL_INTERVAL = ReplyWait::POLL_INTERVAL
|
|
20
|
-
# No live worker this long while waiting: it died before it could mark
|
|
21
|
-
# the session (a worker takes well under a second to start).
|
|
22
|
-
WORKER_GONE_AFTER = 5
|
|
23
20
|
FORMATS = %w[text json].freeze
|
|
24
21
|
|
|
25
22
|
# @return [Integer] the exit status
|
|
@@ -55,30 +52,19 @@ module Samagotchi
|
|
|
55
52
|
def resolve(given)
|
|
56
53
|
@session_id = @access.resolve(given)
|
|
57
54
|
rescue ArgumentError => e
|
|
58
|
-
error_line("#{command_name}: #{e.
|
|
55
|
+
error_line("#{command_name}: #{e.message}")
|
|
59
56
|
nil
|
|
60
57
|
end
|
|
61
58
|
|
|
62
|
-
# Wait for
|
|
63
|
-
#
|
|
64
|
-
# @param
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# (ReplyWait's turn:): a queued message's, or the turn an answer or
|
|
68
|
-
# a --wait with no message is about (ReplyWait.own_turn)
|
|
69
|
-
# @param queued [Boolean] +turn+ is a queued message's (ReplyWait's
|
|
70
|
-
# queued:)
|
|
71
|
-
# @param ended_before [Boolean] a last_turn that ended before the
|
|
72
|
-
# baseline is the answer (ReplyWait's ended_before:) — the
|
|
73
|
-
# --wait-with-no-message's settled session
|
|
74
|
-
# @param report_turn_id [String, nil] the turn id to include in the
|
|
75
|
-
# report, even when ReplyWait uses the next-reply path
|
|
59
|
+
# Wait for what +target+ binds (ReplyWait) and report it. Ctrl-C
|
|
60
|
+
# leaves the turn running (130).
|
|
61
|
+
# @param target [ReplyWait::Target] which turn, from the cursor and
|
|
62
|
+
# baseline prepare_wait read; its report_turn_id is the turn id the
|
|
63
|
+
# report names, even when ReplyWait takes the next reply
|
|
76
64
|
# @return [Integer] the exit status
|
|
77
|
-
def wait_for_reply(id,
|
|
78
|
-
|
|
79
|
-
result = @access.wait_reply(id,
|
|
80
|
-
poll_interval: self.class::POLL_INTERVAL, turn: turn, queued: queued,
|
|
81
|
-
ended_before: ended_before)
|
|
65
|
+
def wait_for_reply(id, target:, timeout:)
|
|
66
|
+
report_turn_id = target.report_turn_id
|
|
67
|
+
result = @access.wait_reply(id, **target.to_h, timeout: timeout, poll_interval: self.class::POLL_INTERVAL)
|
|
82
68
|
result.turn_id = report_turn_id if report_turn_id && !report_turn_id.to_s.empty?
|
|
83
69
|
result.text = result.text&.dup&.force_encoding(Encoding::UTF_8)&.scrub
|
|
84
70
|
@reported = true
|
data/lib/samagotchi/config.rb
CHANGED
|
@@ -8,11 +8,14 @@ require_relative "config_text_edit"
|
|
|
8
8
|
require_relative "log"
|
|
9
9
|
require_relative "model_ref"
|
|
10
10
|
require_relative "paths"
|
|
11
|
-
require_relative "llm_context_strategy"
|
|
12
|
-
require_relative "host_model"
|
|
13
11
|
require_relative "config/restart_watch"
|
|
14
12
|
|
|
15
13
|
module Samagotchi
|
|
14
|
+
# Which LLM context strategy a turn runs under; it reads Config.
|
|
15
|
+
autoload :LLMContextStrategy, File.expand_path("llm_context_strategy", __dir__)
|
|
16
|
+
# A hosts.<name>.models entry and its price; they read ConfigFile.
|
|
17
|
+
autoload :HostModel, File.expand_path("host_model", __dir__)
|
|
18
|
+
autoload :ModelPrice, File.expand_path("model_price", __dir__)
|
|
16
19
|
# Parses the thinking: levels of host and model entries (it needs Config).
|
|
17
20
|
autoload :Thinking, File.expand_path("thinking", __dir__)
|
|
18
21
|
# One hosts: entry; it needs ConfigFile's readers.
|
|
@@ -89,6 +92,12 @@ module Samagotchi
|
|
|
89
92
|
next_history_access next_matching_turn_display_poll_write
|
|
90
93
|
].freeze
|
|
91
94
|
|
|
95
|
+
# llm_context.policy's default: the sentence forget_outputs' description
|
|
96
|
+
# carries (ToolDeclarations::FORGET_OUTPUTS_POLICY), the plan's D7 line,
|
|
97
|
+
# adapted from CLM's steering example.
|
|
98
|
+
DEFAULT_LLM_CONTEXT_POLICY = "Tidy at subtask boundaries: once a subtask is done, forget its tool outputs and note " \
|
|
99
|
+
"what it established; keep anything you'll still edit against."
|
|
100
|
+
|
|
92
101
|
ENTRIES = [
|
|
93
102
|
# universal – env+config+cli
|
|
94
103
|
Entry.new(key: "default.model", yaml_path: %w[default model], type: :string, default: nil, expose: %i[env config cli], lifecycle: :new_session),
|
|
@@ -261,7 +270,7 @@ module Samagotchi
|
|
|
261
270
|
# and hosts.<name>.llm_context_budget_tokens come first. Kept as written: LLMContextStrategy.parse_budget reads it.
|
|
262
271
|
Entry.new(key: "llm_context.budget_tokens", yaml_path: %w[llm_context budget_tokens], type: :string, default: nil, expose: %i[env config], lifecycle: :next_matching_turn_display_poll_write),
|
|
263
272
|
# The policy sentence forget_outputs' description carries (the forget layer); blank: none.
|
|
264
|
-
Entry.new(key: "llm_context.policy", yaml_path: %w[llm_context policy], type: :string, default:
|
|
273
|
+
Entry.new(key: "llm_context.policy", yaml_path: %w[llm_context policy], type: :string, default: DEFAULT_LLM_CONTEXT_POLICY, expose: %i[env config], lifecycle: :worker_start),
|
|
265
274
|
|
|
266
275
|
# How much models think: off|low|medium|high|default, parsed by Thinking
|
|
267
276
|
# (a :string, since YAML reads an unquoted off as false). The CLI flag
|
|
@@ -364,6 +373,9 @@ module Samagotchi
|
|
|
364
373
|
# The stage view's working set (files, memories and URLs the turn brought into context): a rail in the stage, a
|
|
365
374
|
# footprint under each finished turn and a "set N" chip in place of the mem chip; ?working_set=1|0 overrides it per page load.
|
|
366
375
|
Entry.new(key: "web.working_set", yaml_path: %w[web working_set], type: :bool, default: true, expose: %i[env config cli], lifecycle: :web_start),
|
|
376
|
+
# Opt-in: the page checks each finished turn against a reload of it (one full read per turn end) and chi web logs
|
|
377
|
+
# reload_checked, with reload_mismatch and the kinds that differed (counts only, no content).
|
|
378
|
+
Entry.new(key: "web.reload_check", yaml_path: %w[web reload_check], type: :bool, default: false, expose: %i[env config cli], lifecycle: :web_start),
|
|
367
379
|
# Quick replies next to Annotate in the page's selection bubble, "|"-separated (a YAML list works too); "" leaves only Annotate.
|
|
368
380
|
Entry.new(key: "web.annotate_presets", yaml_path: %w[web annotate_presets], type: :string, default: "Agreed|Could you please elaborate?", expose: %i[env config cli], lifecycle: :web_start),
|
|
369
381
|
# What a file ref (a tool row's file, a diff line) opens on this machine: a preset (vscode, vscode-insiders, cursor, zed, none) or a URL template with {path} and an optional {line} (Web::Editor).
|
|
@@ -399,7 +411,7 @@ module Samagotchi
|
|
|
399
411
|
# appended_prompt_files, Hooks::Loader, ConfigFile.bundle_settings.
|
|
400
412
|
FREE_FORM_MAPS = %w[model_aliases hooks bundles memories append_system_prompt].freeze
|
|
401
413
|
# Maps of named entries and the keys an entry may hold
|
|
402
|
-
# (
|
|
414
|
+
# (HostConfig.all, ModelSettings.all).
|
|
403
415
|
MAP_ENTRY_KEYS = {
|
|
404
416
|
"hosts" => %w[host port url api api_key_env auth dialect profile first_token_timeout vision sampling thinking
|
|
405
417
|
reasoning_history enabled remote window_tokens steer_cut_after llm_context_strategy llm_context_apply llm_context_budget_tokens models].freeze,
|
|
@@ -408,6 +420,8 @@ module Samagotchi
|
|
|
408
420
|
}.freeze
|
|
409
421
|
# The keys a hosts.<name>.models entry may hold (HostModel.parse_map).
|
|
410
422
|
HOST_MODEL_KEYS = %w[price served].freeze
|
|
423
|
+
# The keys a hosts.<name>.models.<id>.price may hold (ModelPrice.parse).
|
|
424
|
+
MODEL_PRICE_KEYS = %w[input cache_read cache_write output].freeze
|
|
411
425
|
# Section keys beyond the registry's: guardrails' YAML rules (Engine#guardrail_rules).
|
|
412
426
|
# And toolkit's kits (Toolkit.kits), and bundles' judgments' settings (SystemOne::JudgmentSettings).
|
|
413
427
|
SECTION_EXTRA_KEYS = { "guardrails" => %w[rules disable].freeze, "toolkit" => %w[kits].freeze, "system_one" => %w[judgments].freeze }.freeze
|
|
@@ -887,9 +901,9 @@ module Samagotchi
|
|
|
887
901
|
def price_key_problems(prefix, price)
|
|
888
902
|
return [] unless price.is_a?(Hash)
|
|
889
903
|
|
|
890
|
-
(price.keys.map(&:to_s) -
|
|
904
|
+
(price.keys.map(&:to_s) - MODEL_PRICE_KEYS).map do |k|
|
|
891
905
|
# A renamed key is compared by its new name, so that name is the hint.
|
|
892
|
-
unknown_key_message("#{prefix}#{k}", RENAMED_PRICE_KEYS.fetch(k, k),
|
|
906
|
+
unknown_key_message("#{prefix}#{k}", RENAMED_PRICE_KEYS.fetch(k, k), MODEL_PRICE_KEYS, prefix: prefix)
|
|
893
907
|
end
|
|
894
908
|
end
|
|
895
909
|
|
|
@@ -1172,71 +1186,6 @@ module Samagotchi
|
|
|
1172
1186
|
HOST_NAME_RE = /\A[a-z0-9][a-z0-9._-]*\z/i
|
|
1173
1187
|
ENV_NAME_RE = /\A[A-Za-z_][A-Za-z0-9_]*\z/
|
|
1174
1188
|
|
|
1175
|
-
# config.yml's hosts: by lowercased name; an entry that is disabled or
|
|
1176
|
-
# invalid (warned) is left out. No entry left: "default", from
|
|
1177
|
-
# server.host/port.
|
|
1178
|
-
# @return [Hash{String => HostConfig}]
|
|
1179
|
-
def hosts_config(env: ENV, path: global_path(env: env))
|
|
1180
|
-
data = read_yaml(env: env, path: path)
|
|
1181
|
-
raw_hosts = data[HOSTS_KEY] if data.is_a?(Hash)
|
|
1182
|
-
|
|
1183
|
-
normalized = {}
|
|
1184
|
-
if raw_hosts.is_a?(Hash)
|
|
1185
|
-
raw_hosts.each do |raw_name, raw_cfg|
|
|
1186
|
-
host = HostConfig.parse(raw_name, raw_cfg)
|
|
1187
|
-
normalized[host.name] = host if host
|
|
1188
|
-
end
|
|
1189
|
-
warn_duplicate_host_models(normalized)
|
|
1190
|
-
end
|
|
1191
|
-
normalized["default"] = default_host_config(data, env) if normalized.empty?
|
|
1192
|
-
normalized
|
|
1193
|
-
rescue StandardError => e
|
|
1194
|
-
Log.error(:config, "hosts_config_failed", error: e.class.name, message: e.message)
|
|
1195
|
-
{}
|
|
1196
|
-
end
|
|
1197
|
-
|
|
1198
|
-
# The host chi uses when hosts: names none: server.host/port.
|
|
1199
|
-
def default_host_config(data, env)
|
|
1200
|
-
opts = { file_data: data, env: env, cli_overrides: Samagotchi::Config.cli_overrides }
|
|
1201
|
-
host = Samagotchi::Config.resolve("server.host", **opts).to_s.strip
|
|
1202
|
-
host = "localhost" if host.empty?
|
|
1203
|
-
port = Samagotchi::Config.resolve("server.port", **opts).to_i
|
|
1204
|
-
port = 8080 if port <= 0 || port > 65_535
|
|
1205
|
-
HostConfig.new(name: "default", host: host, port: port)
|
|
1206
|
-
end
|
|
1207
|
-
private_class_method :default_host_config
|
|
1208
|
-
|
|
1209
|
-
# Two hosts declaring one id under hosts.<name>.models: a bare id goes
|
|
1210
|
-
# where HostRegistry#host_for_model sends it (the default host when it
|
|
1211
|
-
# declares the id, else the first in hosts: order); say so once.
|
|
1212
|
-
def warn_duplicate_host_models(hosts)
|
|
1213
|
-
declared = Hash.new { |h, k| h[k] = [] }
|
|
1214
|
-
hosts.each { |name, cfg| cfg.models.each { |down, model| declared[down] << [name, model.id] } }
|
|
1215
|
-
declared.each_value do |owners|
|
|
1216
|
-
next if owners.size < 2
|
|
1217
|
-
|
|
1218
|
-
names = owners.map(&:first)
|
|
1219
|
-
id = owners.first.last
|
|
1220
|
-
goes = names.include?("default") ? "default" : "#{names.first} unless the default host lists it"
|
|
1221
|
-
warn_once "Warning: #{names.map { |n| "hosts.#{n}.models" }.join(" and ")} #{names.size == 2 ? "both" : "all"} declare #{id}; " \
|
|
1222
|
-
"a bare #{id} goes to #{goes}"
|
|
1223
|
-
end
|
|
1224
|
-
end
|
|
1225
|
-
private_class_method :warn_duplicate_host_models
|
|
1226
|
-
|
|
1227
|
-
# The names (lowercased) of the hosts entries with enabled: false, which
|
|
1228
|
-
# hosts_config leaves out, from config.yml's hosts:.
|
|
1229
|
-
# @return [Array<String>]
|
|
1230
|
-
def disabled_host_names(env: ENV, path: global_path(env: env))
|
|
1231
|
-
data = read_yaml(env: env, path: path)
|
|
1232
|
-
raw = data[HOSTS_KEY] if data.is_a?(Hash)
|
|
1233
|
-
return [] unless raw.is_a?(Hash)
|
|
1234
|
-
|
|
1235
|
-
raw.filter_map { |name, cfg| name.to_s.strip.downcase if cfg.is_a?(Hash) && HostConfig.disabled?(cfg) }
|
|
1236
|
-
rescue StandardError # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
1237
|
-
[]
|
|
1238
|
-
end
|
|
1239
|
-
|
|
1240
1189
|
# Resolve the `recap:` section through the Config registry so scalar
|
|
1241
1190
|
# recap settings share the single precedence path (CLI > ENV > file >
|
|
1242
1191
|
# default). Returns false when explicitly disabled (file `recap: false`
|
|
@@ -1420,47 +1369,6 @@ module Samagotchi
|
|
|
1420
1369
|
{}
|
|
1421
1370
|
end
|
|
1422
1371
|
|
|
1423
|
-
# The top-level `models:` map: per-model settings keyed by model id or
|
|
1424
|
-
# alias (downcased), e.g. `models: {my-alias: {profile: qwen36}}`, each a
|
|
1425
|
-
# ModelSettings; an entry that is not a map is skipped.
|
|
1426
|
-
# @return [Hash{String => ModelSettings}]
|
|
1427
|
-
def model_settings(env: ENV, path: global_path(env: env))
|
|
1428
|
-
data = read_yaml(env: env, path: path)
|
|
1429
|
-
raw = data[MODELS_KEY] if data.is_a?(Hash)
|
|
1430
|
-
return {} unless raw.is_a?(Hash)
|
|
1431
|
-
|
|
1432
|
-
raw.each_with_object({}) do |(k, v), result|
|
|
1433
|
-
key = k.to_s.strip.downcase
|
|
1434
|
-
settings = ModelSettings.parse(key, v)
|
|
1435
|
-
result[key] = settings if settings
|
|
1436
|
-
end
|
|
1437
|
-
rescue StandardError # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
1438
|
-
{}
|
|
1439
|
-
end
|
|
1440
|
-
|
|
1441
|
-
# The first +names+ (model as typed, alias-resolved, bare, …) whose
|
|
1442
|
-
# models: entry sets +field+ (a ModelSettings member; another name
|
|
1443
|
-
# raises ArgumentError): a ModelSetting (key, value), or nil.
|
|
1444
|
-
# VisionSupport and SamplingSettings look a model up the same way.
|
|
1445
|
-
# @param models [Hash{String => ModelSettings}, nil] model_settings by default
|
|
1446
|
-
# @return [ModelSetting, nil]
|
|
1447
|
-
def model_setting(names, field, models: nil)
|
|
1448
|
-
raise ArgumentError, "unknown models: field #{field.inspect}" unless ModelSettings.members.include?(field)
|
|
1449
|
-
|
|
1450
|
-
models ||= model_settings
|
|
1451
|
-
Array(names).map { |name| name.to_s.strip.downcase }.reject(&:empty?).uniq.each do |key|
|
|
1452
|
-
value = models[key]&.public_send(field)
|
|
1453
|
-
return ModelSetting.new(key: key, value: value) unless value.nil?
|
|
1454
|
-
end
|
|
1455
|
-
nil
|
|
1456
|
-
end
|
|
1457
|
-
|
|
1458
|
-
# +raw+ parsed against config.yml's hosts (or +hosts+) and aliases.
|
|
1459
|
-
# @return [ModelRef]
|
|
1460
|
-
def model_ref(raw, env: ENV, path: global_path(env: env), hosts: nil)
|
|
1461
|
-
ModelRef.parse(raw, hosts: hosts || hosts_config(env: env, path: path), aliases: model_aliases(env: env, path: path))
|
|
1462
|
-
end
|
|
1463
|
-
|
|
1464
1372
|
# Words /model takes as arguments (clear/default/none/off reset the
|
|
1465
1373
|
# runtime model), so no alias may be named after one.
|
|
1466
1374
|
RESERVED_MODEL_ALIASES = %w[clear default none off].freeze
|
|
@@ -428,8 +428,7 @@ module Samagotchi
|
|
|
428
428
|
session = @access.show(id)
|
|
429
429
|
Target.new(session_id: id, project_root: session.project_root, cwd: session.working_directory, label: id[0, 8])
|
|
430
430
|
rescue ArgumentError => e
|
|
431
|
-
|
|
432
|
-
error_line("#{command_name}: #{message}")
|
|
431
|
+
error_line("#{command_name}: #{e.message}")
|
|
433
432
|
nil
|
|
434
433
|
end
|
|
435
434
|
|
|
@@ -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
|
|
|
@@ -669,7 +670,7 @@ module Samagotchi
|
|
|
669
670
|
# A deleted session's sources, snapshots, subscriptions and markers.
|
|
670
671
|
# @return [Array<String>] the paths removed
|
|
671
672
|
def remove_session(session_id, state_dir: nil)
|
|
672
|
-
return [] unless
|
|
673
|
+
return [] unless SessionPaths.valid_id?(session_id.to_s)
|
|
673
674
|
|
|
674
675
|
dir = session_location(session_id, state_dir: state_dir).dir
|
|
675
676
|
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] ConfigFile.model_settings (specs)
|
|
51
|
+
# @param lookup [ModelLookup] the model's layers (EffectiveModel#lookup)
|
|
54
52
|
# @return [Resolved, nil]
|
|
55
|
-
def setting(
|
|
56
|
-
|
|
57
|
-
tokens = ConfigFile.model_setting(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
|
|
@@ -335,7 +335,7 @@ module Samagotchi
|
|
|
335
335
|
# are left out. @return [Array<Hash>] name:, hosts:, state:
|
|
336
336
|
def host_keys
|
|
337
337
|
config_env = { "XDG_CONFIG_HOME" => launch_config_home }
|
|
338
|
-
hosts =
|
|
338
|
+
hosts = HostConfig.all(env: config_env).values.select(&:api_key_env).group_by(&:api_key_env)
|
|
339
339
|
hosts.map do |name, entries|
|
|
340
340
|
state = if HostKeys.from_keychain(name, env: @env) then :keychain
|
|
341
341
|
elsif !@env[name].to_s.strip.empty? then :shell_only
|