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
data/docs/testing.md
CHANGED
|
@@ -75,6 +75,47 @@ spec/engine_turn_golden_spec.rb`, then read `git diff spec/fixtures/turn_goldens
|
|
|
75
75
|
to `tmp/turn_goldens/<golden>.actual` and names the `diff` to run. Changes to the turn path (engine, kernel loop, chat loop,
|
|
76
76
|
tool runner, gate, guardrail wiring, system prompt, tool declarations, registry) pick it in `script/affected-specs`.
|
|
77
77
|
|
|
78
|
+
The model-setting characterizations (`spec/engine_effective_model_characterization_spec.rb`,
|
|
79
|
+
`spec/engine_session_model_settings_characterization_spec.rb`, `spec/web/app_session_settings_characterization_spec.rb`
|
|
80
|
+
over `spec/fixtures/effective_model/`, and `spec/model_setting_precedence_characterization_spec.rb`'s ladders over
|
|
81
|
+
`spec/fixtures/model_setting_precedence/`) pin the settings on the code as it was before the `ModelLookup` refactor
|
|
82
|
+
(`UPDATE_GOLDENS=1` regenerates them after an intended change only). They are retired after their refactor: the
|
|
83
|
+
next round folds the first three into one wiring example each in `effective_model_spec.rb`,
|
|
84
|
+
`session_model_settings_spec.rb` and `session_setting_spec.rb`, and the ladders the round after into the setting
|
|
85
|
+
modules' and `model_lookup_spec.rb`.
|
|
86
|
+
|
|
87
|
+
The session file goldens (`spec/session_file_goldens_spec.rb`, `spec/fixtures/session_goldens/`) pin the bytes `Session#save`
|
|
88
|
+
writes: every field away from its default, a new session, a newer chi's file, an old chi's file in a git repo, every key
|
|
89
|
+
present as `null`, the plain writers' raw values, a running turn's checkpoint and the scrub (the `*_in.json` files are
|
|
90
|
+
hand-written inputs, never regenerated); and the file each path that makes or rewrites a session writes (a fork, a
|
|
91
|
+
continue, a delegate child, a plain spawn, a review, a Verify fresh child, a resume, a respawn, an archive), with
|
|
92
|
+
`carry_matrix.json`, what each keeps of its source. They are pinned before Session's fields became one table and are kept
|
|
93
|
+
as the file format's pins, not retired: a diff is a change to every session file on disk, so only a commit that means to
|
|
94
|
+
change the format regenerates them (`UPDATE_GOLDENS=1 bundle exec rspec spec/session_file_goldens_spec.rb`).
|
|
95
|
+
|
|
96
|
+
`UPDATE_GOLDENS=1` is the characterization specs' variable (the prompt and turn goldens above use `UPDATE_PROMPTS`):
|
|
97
|
+
the model-setting ones above, the session file goldens, claude-peer's goldens and the bundle-state corpus below. Regenerate only for an intended
|
|
98
|
+
change, then read the diff.
|
|
99
|
+
|
|
100
|
+
The bundle-state corpus (`spec/samagotchi/memory_bundle/file_state_corpus/`, `spec/fixtures/bundle_states/`) pins what
|
|
101
|
+
every bundle file state does under every command that reads or writes it. Each case in `memory_cases.yml` (a memory:
|
|
102
|
+
owned, edited, behind, missing, dropped, the user's own, another bundle's too…) and `asset_cases.yml` (a rule, the
|
|
103
|
+
plugin, a script, a hook) is written directly as a starting tree, in the 0.155 and the 0.149 record shape, and run
|
|
104
|
+
through install, `--force`, `--dry-run`, upgrade and its flags, chi update's plan, Remerge, the system bundle's restore,
|
|
105
|
+
uninstall and status. A cell holds only what a user or the next command sees (the bytes, the trash, base and checksum,
|
|
106
|
+
the mark, the summary's word, status's label, the warnings), never the Installer's result strings. `expect/*.yml` are
|
|
107
|
+
generated (`UPDATE_GOLDENS=1 bundle exec rspec spec/samagotchi/memory_bundle/file_state_corpus/`, which also writes
|
|
108
|
+
`tmp/bundle_states/review.<file>.txt`, every cell in words); a mismatch names the case, command and shape and writes
|
|
109
|
+
`tmp/bundle_states/<file>.yml.actual` to diff. A regeneration asserts nothing, so run the folder again without
|
|
110
|
+
`UPDATE_GOLDENS`: that run checks the new cells against `cases_spec.rb`, which holds what every regeneration keeps. No
|
|
111
|
+
user bytes (neither the bundle's text nor what the record vouches for, its checksum first) are changed or removed without
|
|
112
|
+
a copy in the trash first; the two record shapes give one answer except on a marked case (an allowlist entry needs a
|
|
113
|
+
note); and for every asset, chi update's plan lists as replaced exactly the files the upgrade copies to the trash. To stay
|
|
114
|
+
under 3 s, `upgrade_spec.rb` runs the 0.149 shape for marked cases only (`MARKED_ONLY_149`), where the shapes can differ. A regenerated cell is
|
|
115
|
+
reviewed: a refactor that turns the corpus red stops and asks, and only a commit that means to change a cell
|
|
116
|
+
regenerates, its changed cells read one by one; a cell that looks wrong today carries a `divergent:` tag and a note in
|
|
117
|
+
its case.
|
|
118
|
+
|
|
78
119
|
The web specs run in plain node. One that loads lit-html, itself or through a module it imports (`turn_block.js`,
|
|
79
120
|
`turn_html.js`, `card.js`, `copy.js`, `images.js`, `scope.js`, `command_view.js`, `diff_view.js`, `read_view.js`,
|
|
80
121
|
`question_cards.js`, `stream_handlers.js`, `stage_view.js`, `bubbles.js`, `cards_place.js`, the chips, `info_bar.js`, `info_bar_model.js`, `lists_view.js`, `context_chips.js`, `families_view.js`,
|
|
@@ -124,7 +165,15 @@ Each level owns its own share; a detail is asserted once, at the lowest level th
|
|
|
124
165
|
- **e2e** is for real-browser behaviour (layout, focus, reload, two tabs), one happy path per Ruby↔web mechanism and
|
|
125
166
|
a few journeys. Before adding a test, extend an existing journey's step; a variant goes to node, a Ruby↔web shape
|
|
126
167
|
variant to a shared fixture (`spec/fixtures/composer_routes.json`). A journey names its stages with `test.step`
|
|
127
|
-
(the report names the one that failed: web.spec.js's notifications).
|
|
168
|
+
(the report names the one that failed: web.spec.js's notifications). A turn test ends with
|
|
169
|
+
`expectSameAfterReload` (spec/e2e/support/history_shape.js): the history's shape live, then after a reload, the
|
|
170
|
+
same (live is the truth). Its allowlist is `history_shape.js ACCEPTED`, each entry with its reason; a test names
|
|
171
|
+
the entries its scenario has (only deliberate ones are left). A drawn turn's variants are node's: the shared
|
|
172
|
+
scenarios in `spec/shared/turn_drawings/` (written from `spec/support/turn_drawing_scenarios.rb`;
|
|
173
|
+
`UPDATE_TURN_DRAWINGS=1 bundle exec rspec spec/bridge/turn_drawings_spec.rb spec/web/session_show_drawings_spec.rb`
|
|
174
|
+
rewrites their lines and show after an intended change) feed `turn_drawing_parity.test.js`, which draws each one
|
|
175
|
+
live (the real stream handlers and stage) and from its drawing and compares the shapes with no allow list. The
|
|
176
|
+
`lan` project's chi web is on the LAN only
|
|
128
177
|
when the machine has a private IPv4: without one its LAN tests skip and the participant review journey runs on
|
|
129
178
|
loopback.
|
|
130
179
|
|
|
@@ -254,6 +303,7 @@ any other file requires it, so a spec file run alone still finds its requires. E
|
|
|
254
303
|
| `lock_holder.rb` | *helper*: `spawn_lock_holder(session_dir)`, a stand-in worker process holding a session's owner lock, which says so on a pipe |
|
|
255
304
|
| `integration_server.rb` | *helper*: the live model server `:integration` examples use, from `SAMAGOTCHI_INTEGRATION_*` ([Integration specs](#integration-specs)) |
|
|
256
305
|
| `mcp_bundle.rb` | The shipped mcp bundle's path and the fake MCP server its specs start (`MCP_FAKE_COMMAND`, no RubyGems) |
|
|
306
|
+
| `model_setting_ladders.rb` | shared context "model setting ladders": per setting a config, env and CLI with every layer set, peeled top down (`peel`), against `spec/fixtures/model_setting_precedence/` |
|
|
257
307
|
| `no_console.rb` | Loaded first (`ruby -r`) by spawned bin/chi children: `IO.console` is nil, so output never wraps at a terminal's width |
|
|
258
308
|
| `recording_surface.rb` | `RecordingSurface`: a `TerminalUI::Surface` that records commits and slots instead of drawing |
|
|
259
309
|
| `remote_backend.rb` | `RemoteBackend`: `SessionAccess::Remote` against chi web's Rack app in this process (no socket, LAN token gate) |
|
|
@@ -334,7 +384,8 @@ files it changed (`lib/samagotchi/foo/bar.rb` → `spec/foo/bar_spec.rb`, `bar_*
|
|
|
334
384
|
`lib/samagotchi/foo/bar/baz.rb` → `spec/foo/bar_baz_spec.rb`), the specs that read
|
|
335
385
|
docs or source as text (its `RULES`: add one when a full run catches what the names missed) and the golden of a changed
|
|
336
386
|
file's layer (any `foo/*.rb` → `spec/foo_golden_spec.rb` when there is one; `turn_flow.rb` and `turn_note.rb`, whose
|
|
337
|
-
words the attached TUI prints, pick its `event_renderer` and `attached_loop` specs by a rule
|
|
387
|
+
words the attached TUI prints, pick its `event_renderer` and `attached_loop` specs by a rule; `memory_bundle/*.rb`,
|
|
388
|
+
`bundle_command.rb`, `memory_paths.rb`, `model_overlay.rb` and the corpus's fixtures pick the bundle-state corpus). Changes to `tool_declarations.rb` and `system_prompt.rb` also pick the
|
|
338
389
|
effective-model, request, prompt-snapshot and full system-prompt goldens because they hash the request's prompt and
|
|
339
390
|
tools. Then come the other name matches (a spec named after several changed files first, e.g. `engine_toolkit_spec.rb` for `engine.rb` and
|
|
340
391
|
`toolkit.rb`; then one beside the file, so `plugin/context.rb` picks `spec/samagotchi/plugin/*context*` before
|
|
@@ -138,7 +138,8 @@ module Samagotchi
|
|
|
138
138
|
end
|
|
139
139
|
# Answered here, or no longer open (answered elsewhere, or a newer
|
|
140
140
|
# question): what comes next either way.
|
|
141
|
-
wait_for_reply(id,
|
|
141
|
+
wait_for_reply(id, target: ReplyWait::Target.answer(start),
|
|
142
|
+
timeout: options[:timeout])
|
|
142
143
|
end
|
|
143
144
|
|
|
144
145
|
# The labels to send. A number is an option's place (1-based); an
|
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "fileutils"
|
|
5
5
|
require "time"
|
|
6
6
|
require_relative "atomic_file"
|
|
7
|
+
require_relative "session_paths"
|
|
7
8
|
|
|
8
9
|
require_relative "session"
|
|
9
10
|
|
|
@@ -46,7 +47,7 @@ module Samagotchi
|
|
|
46
47
|
|
|
47
48
|
# @return [Boolean] whether it was archived (and is not now)
|
|
48
49
|
def self.unarchive(session_id, state_dir:)
|
|
49
|
-
return false unless archived?(
|
|
50
|
+
return false unless archived?(SessionPaths.session_dir(session_id, state_dir: state_dir))
|
|
50
51
|
|
|
51
52
|
write(session_id, { "unarchived_at" => Time.now.iso8601(3) }, state_dir: state_dir)
|
|
52
53
|
end
|
|
@@ -63,9 +64,9 @@ module Samagotchi
|
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
def self.write(session_id, record, state_dir:)
|
|
66
|
-
return false unless File.exist?(
|
|
67
|
+
return false unless File.exist?(SessionPaths.session_file(session_id, state_dir: state_dir))
|
|
67
68
|
|
|
68
|
-
dir =
|
|
69
|
+
dir = SessionPaths.session_dir(session_id, state_dir: state_dir)
|
|
69
70
|
FileUtils.mkdir_p(dir)
|
|
70
71
|
path = File.join(dir, FILE)
|
|
71
72
|
AtomicFile.write(path, JSON.generate(record))
|
|
@@ -304,7 +304,7 @@ module Samagotchi
|
|
|
304
304
|
|
|
305
305
|
def written_ok?(real, expected, name)
|
|
306
306
|
parse(File.read(real)) == expected &&
|
|
307
|
-
|
|
307
|
+
HostConfig.all(env: @env, path: real).key?(name.downcase)
|
|
308
308
|
rescue StandardError
|
|
309
309
|
false
|
|
310
310
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "rbconfig"
|
|
4
4
|
require_relative "config"
|
|
5
5
|
require_relative "model_profile"
|
|
6
|
+
require_relative "host_address"
|
|
6
7
|
require_relative "host_registry"
|
|
7
8
|
require_relative "host_keys"
|
|
8
9
|
require_relative "chi_home"
|
|
@@ -342,7 +343,7 @@ module Samagotchi
|
|
|
342
343
|
# The "loading the model?" hint fits a local server (a llama.cpp loading
|
|
343
344
|
# weights); a remote provider's slow answer is the network, not a load.
|
|
344
345
|
def loading_hint?(result)
|
|
345
|
-
!
|
|
346
|
+
!HostAddress.remote_address?(result.candidate.scheme, result.candidate.host)
|
|
346
347
|
end
|
|
347
348
|
|
|
348
349
|
# 0 written, 1 written but not as planned, nil with @exit on a refusal.
|
|
@@ -489,7 +490,7 @@ module Samagotchi
|
|
|
489
490
|
when :skipped then "skipped (#{result.error})"
|
|
490
491
|
else "failed (#{result.error})"
|
|
491
492
|
end
|
|
492
|
-
@stdout.puts("system bundle: #{text}")
|
|
493
|
+
@stdout.puts("system bundle: #{[text, *result.warnings].join("; ")}")
|
|
493
494
|
result.status != :failed
|
|
494
495
|
end
|
|
495
496
|
|
|
@@ -18,6 +18,15 @@ module Samagotchi
|
|
|
18
18
|
# comes after (a prompt's turn_id, a line's opening_id), which the page
|
|
19
19
|
# finds by its bubble, not by counting. Pure: no Engine, no file.
|
|
20
20
|
class CardPlaces
|
|
21
|
+
# An opening's stamp: its turn_id (a prompt's, a wake note's), else its
|
|
22
|
+
# own opening_id (/continue no's reason line); nil for neither (a line
|
|
23
|
+
# an older chi wrote). The recap's too (RecapPlace).
|
|
24
|
+
# @return [String, nil]
|
|
25
|
+
def self.stamp(message)
|
|
26
|
+
id = message[:turn_id] || message["turn_id"] || message[:opening_id] || message["opening_id"]
|
|
27
|
+
id.is_a?(String) && !id.empty? ? id : nil
|
|
28
|
+
end
|
|
29
|
+
|
|
21
30
|
# @param turn_log [Array<String>] the turn ids, oldest first
|
|
22
31
|
# @param messages [Array<Hash>] the messages the page draws
|
|
23
32
|
def initialize(turn_log, messages)
|
|
@@ -114,10 +123,7 @@ module Samagotchi
|
|
|
114
123
|
position >= 0 ? @last_at_or_before[position] : nil
|
|
115
124
|
end
|
|
116
125
|
|
|
117
|
-
def stamp_of(message)
|
|
118
|
-
id = message[:turn_id] || message["turn_id"] || message[:opening_id] || message["opening_id"]
|
|
119
|
-
id.is_a?(String) && !id.empty? ? id : nil
|
|
120
|
-
end
|
|
126
|
+
def stamp_of(message) = self.class.stamp(message)
|
|
121
127
|
|
|
122
128
|
def idless_run(opening)
|
|
123
129
|
@ids.drop(opening + 1).take_while(&:nil?).size
|
|
@@ -147,6 +147,9 @@ module Samagotchi
|
|
|
147
147
|
stamp = places&.opening_of(entry)
|
|
148
148
|
out[:opening_id] = stamp if stamp
|
|
149
149
|
out[:current] = current
|
|
150
|
+
# Whose row it is (its turn's id, +of_turn+): the page leaves a drawn
|
|
151
|
+
# turn's own rows and cards to its drawing (Bridge::TurnDrawings).
|
|
152
|
+
out[:of_turn] = entry[:turn_id] if shown[:in_turn] && entry[:turn_id]
|
|
150
153
|
out[:during] = true if entry[:during]
|
|
151
154
|
out[:after_failed] = true if entry[:after_failed] && entry[:turn_id] == @failed_turn
|
|
152
155
|
out[:label] = "from #{entry[:command]}" if entry[:command]
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../events"
|
|
4
4
|
require_relative "../kernel_loop"
|
|
5
|
+
require_relative "../log"
|
|
5
6
|
require_relative "../steer"
|
|
6
7
|
require_relative "../tool_output_cut"
|
|
7
8
|
require_relative "../tool_row_fields"
|
|
@@ -23,10 +24,17 @@ module Samagotchi
|
|
|
23
24
|
# its head and tail: a reload's cap, so a joined row is the row a
|
|
24
25
|
# reload draws
|
|
25
26
|
# @param clock [#call] monotonic seconds: a tool call's duration
|
|
27
|
+
# @param on_turn_end [#call, nil] (turn, end_event): the turn's final
|
|
28
|
+
# fold (a deep copy, #current_turn's shape without elapsed_ms) and its
|
|
29
|
+
# end event, at the end event, before the turn is cleared (the Bridge's
|
|
30
|
+
# TurnDrawings#append). It runs under this accumulator's mutex and the
|
|
31
|
+
# SessionObserver's lock: it must never call back in here. An error is
|
|
32
|
+
# logged and the fold goes on.
|
|
26
33
|
def initialize(max_output_chars: ToolOutputCut::WEB_PREVIEW_CHARS,
|
|
27
|
-
clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) })
|
|
34
|
+
clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }, on_turn_end: nil)
|
|
28
35
|
@max_output_chars = max_output_chars
|
|
29
36
|
@clock = clock
|
|
37
|
+
@on_turn_end = on_turn_end
|
|
30
38
|
@mutex = Mutex.new
|
|
31
39
|
@turn = nil
|
|
32
40
|
# When each running tool call started, by [iteration, call_index].
|
|
@@ -47,13 +55,14 @@ module Samagotchi
|
|
|
47
55
|
|
|
48
56
|
# @return [Hash, nil] a copy of the turn in progress: prompt, turn_id, origin,
|
|
49
57
|
# continue, at (its wall time), ordered parts (generation / thinking / text / tool /
|
|
50
|
-
# input / steer / reminder / notice), pending_question and the last event_seq folded
|
|
58
|
+
# input / steer / reminder / notice / card), pending_question and the last event_seq folded
|
|
51
59
|
# in. A finished tool part keeps the live row's action and duration_ms
|
|
52
60
|
# too, a running one the time it has run so far (elapsed_ms), so a
|
|
53
61
|
# join times its row from its start. A notice part holds the event
|
|
54
62
|
# of one of the turn's rows (TurnNotice: a hook's notice, an
|
|
55
63
|
# empty-answer retry, a question and its answer), which a UI replays
|
|
56
|
-
# through its live handler.
|
|
64
|
+
# through its live handler. A card part is a card the turn showed in
|
|
65
|
+
# its step (in_turn), its latest version in the place of its first.
|
|
57
66
|
def current_turn
|
|
58
67
|
@mutex.synchronize do
|
|
59
68
|
next nil unless @turn
|
|
@@ -132,10 +141,13 @@ module Samagotchi
|
|
|
132
141
|
# the live activity: action, tool, params, status), a merged_input
|
|
133
142
|
# (the one synthetic type) per merged prompt, a steer-only
|
|
134
143
|
# pending_input_merged per steer, a reminder_injected, and a notice
|
|
135
|
-
# part's own event.
|
|
144
|
+
# part's own event. Card parts only with +cards+ (a finished turn's
|
|
145
|
+
# drawing, chi web's unfold of it): a running turn's join gets its cards
|
|
146
|
+
# from CardStore's snapshot.
|
|
136
147
|
# @param started_at [String, nil] the turn's start, on turn_started
|
|
148
|
+
# @param cards [Boolean] replay card parts as their card events
|
|
137
149
|
# @return [Array<Hash>] symbol keys and types; [] with no turn
|
|
138
|
-
def self.replay_events(turn, started_at: nil)
|
|
150
|
+
def self.replay_events(turn, started_at: nil, cards: false)
|
|
139
151
|
return [] unless turn
|
|
140
152
|
|
|
141
153
|
started = { type: :turn_started, prompt: turn[:prompt], origin: turn[:origin], continue: !!turn[:continue] }
|
|
@@ -175,14 +187,66 @@ module Samagotchi
|
|
|
175
187
|
when "steer"
|
|
176
188
|
close_text.call
|
|
177
189
|
events << { type: :pending_input_merged, count: 0, content: nil, steers: [{ source: part[:source], text: part[:text] }] }
|
|
190
|
+
when "card"
|
|
191
|
+
next unless cards && part[:event].is_a?(Hash)
|
|
192
|
+
|
|
193
|
+
close_text.call
|
|
194
|
+
events << part[:event].merge(type: :card, in_turn: true)
|
|
178
195
|
end
|
|
179
196
|
end
|
|
180
197
|
events
|
|
181
198
|
end
|
|
182
199
|
|
|
200
|
+
# The page's unfold of a session view (chi web's show sends it as
|
|
201
|
+
# `replay`; turn_events.js snapshotEvents was its JS twin): the turn in
|
|
202
|
+
# progress as #replay_events has it, its turn_started with what the
|
|
203
|
+
# page also reads (its wall time `at`, the run's `cut_after`, and every
|
|
204
|
+
# author key, a pair turn's participant_mode too), then a turn_enqueued
|
|
205
|
+
# per prompt queued behind it and a command_queued per command waiting
|
|
206
|
+
# for its end (spec/shared/turn_snapshot.json pins both sides). The TUI
|
|
207
|
+
# keeps #replay_events.
|
|
208
|
+
# @param current_turn [Hash, nil] #current_turn's shape, symbol keys
|
|
209
|
+
# (a snapshot's: .from_wire)
|
|
210
|
+
# @param queued [Array<Hash>] #queued's entries
|
|
211
|
+
# @param queued_commands [Array<Hash>] #queued_commands' entries
|
|
212
|
+
# @return [Array<Hash>] symbol keys and types
|
|
213
|
+
def self.join_events(current_turn:, queued: [], queued_commands: [], started_at: nil)
|
|
214
|
+
events = replay_events(current_turn, started_at: started_at)
|
|
215
|
+
if (started = events.first)
|
|
216
|
+
started[:at] = current_turn[:at] if current_turn[:at].is_a?(String)
|
|
217
|
+
started[:cut_after] = current_turn[:cut_after] unless current_turn[:cut_after].nil?
|
|
218
|
+
Steer::AUTHOR_KEYS.each { |key| started.delete(key) }
|
|
219
|
+
Steer::AUTHOR_KEYS.each { |key| started[key] = current_turn[key] if current_turn[key] } if current_turn[:author]
|
|
220
|
+
end
|
|
221
|
+
Array(queued).each do |entry|
|
|
222
|
+
enqueued = { type: :turn_enqueued, enqueued_id: entry[:enqueued_id], client_id: entry[:client_id], prompt: entry[:prompt] }
|
|
223
|
+
enqueued[:delivery] = entry[:delivery] if entry[:delivery]
|
|
224
|
+
enqueued[:author] = entry[:author] if entry[:author]
|
|
225
|
+
events << with_images(enqueued, entry[:images])
|
|
226
|
+
end
|
|
227
|
+
Array(queued_commands).each do |entry|
|
|
228
|
+
command = { type: :command_queued, command_id: entry[:command_id], client_id: entry[:client_id], line: entry[:line],
|
|
229
|
+
waits: "turn_end" }
|
|
230
|
+
command[:author] = entry[:author] if entry[:author]
|
|
231
|
+
events << command
|
|
232
|
+
end
|
|
233
|
+
events
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# A Bridge snapshot's part (JSON: string keys) as the accumulator's own
|
|
237
|
+
# shapes: symbol keys all the way down, values as they are.
|
|
238
|
+
def self.from_wire(value)
|
|
239
|
+
case value
|
|
240
|
+
when Hash then value.to_h { |key, item| [key.is_a?(String) ? key.to_sym : key, from_wire(item)] }
|
|
241
|
+
when Array then value.map { |item| from_wire(item) }
|
|
242
|
+
else value
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
183
246
|
def self.tool_replay_events(part)
|
|
184
247
|
call = { iteration: part[:iteration], call_index: part[:call_index], tool: part[:tool] }
|
|
185
248
|
call[:label] = part[:label] if part[:label]
|
|
249
|
+
call[:tool_id] = part[:tool_id] if part[:tool_id]
|
|
186
250
|
# Its row fields (ToolRowFields), each in its place: title and, for
|
|
187
251
|
# an alias's call, the name the model used, also in the activity.
|
|
188
252
|
title = part.slice(*ToolRowFields::ACTIVITY_KEYS).compact
|
|
@@ -296,6 +360,7 @@ module Samagotchi
|
|
|
296
360
|
|
|
297
361
|
fold_turn_event(event)
|
|
298
362
|
if Events::TURN_END.include?(type)
|
|
363
|
+
turn_ended(event)
|
|
299
364
|
@turn = nil
|
|
300
365
|
@merged_origins = []
|
|
301
366
|
@working_set = nil
|
|
@@ -326,6 +391,8 @@ module Samagotchi
|
|
|
326
391
|
part = { kind: "tool", iteration: event[:iteration], call_index: event[:call_index],
|
|
327
392
|
tool: event[:tool], params: event[:params], status: "running" }
|
|
328
393
|
part[:label] = event[:label] if event[:label]
|
|
394
|
+
# The id its output is saved under (ToolIds; not a row field).
|
|
395
|
+
part[:tool_id] = event[:tool_id] if event[:tool_id]
|
|
329
396
|
ToolRowFields::KEYS.each { |key| part[key] = event[key] if event[key] }
|
|
330
397
|
parts << part
|
|
331
398
|
@tool_started_at[[event[:iteration], event[:call_index]]] = @clock.call
|
|
@@ -342,16 +409,26 @@ module Samagotchi
|
|
|
342
409
|
# Only System 1's auto-allow is drawn on the row (the web's label).
|
|
343
410
|
guardrail = event.dig(:activity, :guardrail)
|
|
344
411
|
tool[:guardrail] = guardrail if guardrail.is_a?(Hash) && guardrail[:decided_by] == "system one"
|
|
345
|
-
|
|
412
|
+
# The event's web preview (ToolRunner: the reload's cut) when it
|
|
413
|
+
# has one; an older worker's event is cut here the same way.
|
|
414
|
+
tool[:output] = if event[:preview] then event[:preview].to_s.dup
|
|
415
|
+
elsif capped then ToolOutputCut.preview(output, cap: @max_output_chars, tool: event[:tool])
|
|
416
|
+
else output.dup
|
|
417
|
+
end
|
|
346
418
|
tool[:output_truncated] = capped || !!event[:output_truncated]
|
|
347
419
|
tool[:images] = event[:images] if event[:images]
|
|
348
420
|
tool[:diff] = event[:diff] if event[:diff]
|
|
349
421
|
action = event.dig(:activity, :action)
|
|
350
422
|
tool[:action] = action.to_s if action
|
|
351
|
-
#
|
|
352
|
-
#
|
|
423
|
+
# The event's (ToolRunner's: the figure every UI shows); without
|
|
424
|
+
# one (an older worker's event), as the live row times it: from
|
|
425
|
+
# tool_call_started, less an approval wait.
|
|
353
426
|
started_at = @tool_started_at.delete([event[:iteration], event[:call_index]])
|
|
354
|
-
|
|
427
|
+
if event[:duration_ms].is_a?(Integer)
|
|
428
|
+
tool[:duration_ms] = event[:duration_ms]
|
|
429
|
+
elsif started_at
|
|
430
|
+
tool[:duration_ms] = [((@clock.call - started_at) * 1000) - event[:waited_ms].to_f, 0].max.round
|
|
431
|
+
end
|
|
355
432
|
when :pending_input_merged
|
|
356
433
|
# A steer-only merge (count 0) has no user part: the origins stay
|
|
357
434
|
# for the user lines' own merge.
|
|
@@ -366,6 +443,8 @@ module Samagotchi
|
|
|
366
443
|
end
|
|
367
444
|
when :working_set_updated
|
|
368
445
|
@working_set = { items: event[:items], at: event[:at] }
|
|
446
|
+
when :card
|
|
447
|
+
fold_card(parts, event) if event[:in_turn]
|
|
369
448
|
when :reminder_injected
|
|
370
449
|
parts << { kind: "reminder", reminders: Array(event[:reminders]).map(&:dup) }
|
|
371
450
|
when :question_requested
|
|
@@ -382,6 +461,27 @@ module Samagotchi
|
|
|
382
461
|
parts << { kind: "notice", event: TurnNotice.slice(event) } if TurnNotice.notice?(event)
|
|
383
462
|
end
|
|
384
463
|
|
|
464
|
+
# The card fields a UI draws (CardStore keeps the same), a copy.
|
|
465
|
+
CARD_FIELDS = %i[id source title body level actions].freeze
|
|
466
|
+
private_constant :CARD_FIELDS
|
|
467
|
+
|
|
468
|
+
# A card the turn shows in its step; one with an earlier card's id
|
|
469
|
+
# replaces that part where it was.
|
|
470
|
+
def fold_card(parts, event)
|
|
471
|
+
card = { kind: "card", id: event[:id], event: Marshal.load(Marshal.dump(event.slice(*CARD_FIELDS))) }
|
|
472
|
+
index = parts.index { |part| part[:kind] == "card" && part[:id] == card[:id] }
|
|
473
|
+
index ? parts[index] = card : parts << card
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# The turn's final fold to the hook (#initialize's on_turn_end).
|
|
477
|
+
def turn_ended(event)
|
|
478
|
+
return unless @on_turn_end
|
|
479
|
+
|
|
480
|
+
@on_turn_end.call(Marshal.load(Marshal.dump(@turn)), event)
|
|
481
|
+
rescue StandardError => e
|
|
482
|
+
Log.warn(:bridge, "turn_end_hook_failed", error: "#{e.class}: #{e.message}")
|
|
483
|
+
end
|
|
484
|
+
|
|
385
485
|
def append_text(iteration, kind, text)
|
|
386
486
|
return if text.nil? || text.empty?
|
|
387
487
|
|