samagotchi 0.89.0 → 0.90.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7e58a54e40f49c1617636172def63d9e6a3eaa3384ac33e26c0b55c75332555
4
- data.tar.gz: 063a36d57a613a117e62b2882883c22e1ce413044c1e6d18e66aa1c8af0d6d83
3
+ metadata.gz: 06240ded5e15bc55c7c3467bc7d5c0e79daecd5822291feebf72bbab6e6e6cb7
4
+ data.tar.gz: 52adfdbb519ba9bed3575d55abe737dbca654d81c3c213706a556b56742628af
5
5
  SHA512:
6
- metadata.gz: 879572f97fd05fdff6f15d4c91c3f3ce30d480ff0022b1ad3fb0220423c124004c67908f97e85ad6309302720671938cc90da80c6315de0134c9240a9b477feb
7
- data.tar.gz: 0faf651033f823e4eadc87926d4c370e3e8eb714a4f8106d735110bfe7c8517972a70a4e960448ccda3b8ac1679c796e93b587070255803eb57e24e8c984a2b1
6
+ metadata.gz: 33fdd1b34ce5e0840330451bb90675fe6fb9de6903d3569f3d8e6559e09cad004bcd40bfeec0e0468943d0b2d2abfce8a21d097152c86a53bd2cd332e0386437
7
+ data.tar.gz: 5b19168272d7de67413c480c59bbee363b14a0ba106cdc146632cfb03be45cc646367454758fcbe95aba7562926ab700620e839b2526e818875312f5453d413d
data/CHANGELOG.md CHANGED
@@ -8,6 +8,22 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.90.0] - 2026-10-19
12
+
13
+ ### Changed
14
+
15
+ - SSE `tool_call_started` carries no `call`, `turn_completed` no `result`; read `tool`/`params`/`view` and
16
+ `turn_summary.output`. `?raw=1` on the worker's stream shows them.
17
+ - docs/bundle-authoring.md (the guide chi reads to write a bundle) makes edit → `check --fix` → `test` → `try` the
18
+ loop, with `install --dev .` while iterating and `try` as the live check; its test example runs as written.
19
+
20
+ ### Fixed
21
+
22
+ - `chi -p` followed by one of chi's own flags (`chi -p --non-interactive "text"`, `-p -v`) now exits 2 with a usage
23
+ error instead of taking the flag as the prompt.
24
+
25
+ Update with `chi update` (the system bundle 0.90.0), then restart `chi web`.
26
+
11
27
  ## [0.89.0] - 2026-10-19
12
28
 
13
29
  ### Added
@@ -3513,7 +3529,8 @@ and long-lived sessions.
3513
3529
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
3514
3530
  hotkey panel that send selected text or the clipboard to your sessions.
3515
3531
 
3516
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.89.0...HEAD
3532
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.90.0...HEAD
3533
+ [0.90.0]: https://github.com/dm1try/samagotchi/compare/v0.89.0...v0.90.0
3517
3534
  [0.89.0]: https://github.com/dm1try/samagotchi/compare/v0.88.0...v0.89.0
3518
3535
  [0.88.0]: https://github.com/dm1try/samagotchi/compare/v0.87.0...v0.88.0
3519
3536
  [0.87.0]: https://github.com/dm1try/samagotchi/compare/v0.86.0...v0.87.0
data/bin/chi CHANGED
@@ -74,6 +74,7 @@ options = { verbose: false, no_interrupt: Samagotchi::Config.get("no_interrupt")
74
74
  cli_overrides = {}
75
75
 
76
76
  # Strict kebab enforcement: --recap_base_url is unknown (OptionParser would otherwise accept _ as -)
77
+ known = %w[--prompt --resume --attach --shared --no-shared --model --thinking --toolkit --tool --no-tool --set --parent-answers --memory --mute --append-system-prompt --llm-context --llm-context-apply --llm-context-budget --no-interrupt --non-interactive --verbose --no-default-input --port --open --new-token] + Samagotchi::Config.cli_entries.map(&:cli_flag)
77
78
  ARGV.each do |arg|
78
79
  next unless arg.start_with?("--") && arg.include?("_")
79
80
 
@@ -82,7 +83,6 @@ ARGV.each do |arg|
82
83
  base = arg.split("=", 2).first
83
84
  # If dashed version is a known flag (registry or hardcoded), reject underscore variant
84
85
  dashed = base.tr("_", "-")
85
- known = %w[--prompt --resume --attach --shared --no-shared --model --thinking --toolkit --tool --no-tool --set --parent-answers --memory --mute --append-system-prompt --llm-context --llm-context-apply --llm-context-budget --no-interrupt --non-interactive --verbose --no-default-input --port --open --new-token] + Samagotchi::Config.cli_entries.map(&:cli_flag)
86
86
  if known.include?(dashed) || known.include?(base)
87
87
  warn "Unknown option: #{arg} (did you mean #{dashed}?)"
88
88
  exit 1
@@ -191,8 +191,33 @@ end
191
191
  # value (-p, --model …: the parser's own list) keeps its next word, so
192
192
  # `chi -p "--no-tool=x is broken"` is a prompt. A tool name never starts
193
193
  # with "-": `--no-tool --` or `--no-tool --model` is a missing argument.
194
- takes_value = parser.instance_variable_get(:@stack).flat_map do |list|
195
- list.list.select { |switch| switch.is_a?(OptionParser::Switch::RequiredArgument) }.flat_map { |switch| switch.long + switch.short }
194
+ switches = parser.instance_variable_get(:@stack).flat_map { |list| list.list }
195
+ .select { |switch| switch.is_a?(OptionParser::Switch) }
196
+ switch_flags = switches.flat_map { |switch| switch.long + switch.short }
197
+ known_flags = (known + switch_flags).uniq
198
+ takes_value = switches.select { |switch| switch.is_a?(OptionParser::Switch::RequiredArgument) }
199
+ .flat_map { |switch| switch.long + switch.short }
200
+ reject_prompt_flag = lambda do |flag, value|
201
+ if known_flags.include?(value)
202
+ warn "Error: #{flag} needs the prompt text right after it (got the flag #{value}) (see chi --help)"
203
+ exit 2
204
+ end
205
+ end
206
+ index = 0
207
+ while index < ARGV.size && ARGV[index] != "--"
208
+ arg = ARGV[index]
209
+ if %w[-p --prompt].include?(arg)
210
+ reject_prompt_flag.call(arg, ARGV[index + 1])
211
+ index += 2
212
+ elsif arg.start_with?("--prompt=")
213
+ reject_prompt_flag.call("--prompt", arg.delete_prefix("--prompt="))
214
+ index += 1
215
+ elsif arg.start_with?("-p") && arg != "-p"
216
+ reject_prompt_flag.call("-p", arg.delete_prefix("-p").delete_prefix("="))
217
+ index += 1
218
+ else
219
+ index += takes_value.include?(arg) ? 2 : 1
220
+ end
196
221
  end
197
222
  missing_no_tool = lambda do
198
223
  warn "Error: missing argument: --no-tool (see chi --help)"
data/docs/architecture.md CHANGED
@@ -187,10 +187,15 @@ higher-level events so UIs get clean turn boundaries without inferring them:
187
187
  model's thinking, `0` = never; plus `continue: true` for a continue turn, `resumed: true` for a step-limit Continue,
188
188
  `images:` with the prompt's image refs)
189
189
  - `:turn_completed` — `{ result:, turn_summary:, display_pending: }` (`result`: the final
190
- `LLM::ModelResult`; `display_pending`: `after_turn` hooks may still present the answer)
190
+ `LLM::ModelResult`; Engine event (clients get it without `result`); `display_pending`: `after_turn` hooks may still
191
+ present the answer)
191
192
  - `:turn_canceled` — `{ cancellation_reason:, cancelled_by:, duration_ms: }` (`cancelled_by`: the bundle or hook label that
192
193
  stopped it, for `cancellation_reason: :hook`; else nil)
193
194
 
195
+ These are the Engine's raw events. The worker's SSE projection omits `call` from
196
+ `:tool_call_started` and `result` from `:turn_completed`; the answer remains in
197
+ `turn_summary.output`.
198
+
194
199
  A turn started with `origin:` carries it on each of these (and `:turn_failed`).
195
200
 
196
201
  Every event is a `Hash` with a `:type` symbol key; the sink must not raise (the Engine
@@ -246,7 +251,8 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
246
251
  `session_state_snapshot`. `?snapshot=1` joins with a snapshot frame instead of a replay;
247
252
  `?client_id=` names whose stream it is (`Bridge#open_streams_except`, used by `POST /exit`).
248
253
  Each event goes out as clients get it (`Bridge::ClientEvent.project`, in `SSEWriter#write_event`, live
249
- and replayed alike); only a `generation_chunk` changes, to chi's own format (the chunk contract below).
254
+ and replayed alike); `generation_chunk`, `tool_call_started` and `turn_completed` are projected to the
255
+ contracts below. The ring retains raw events for replay and `?raw=1`.
250
256
  `?raw=1` writes events as the engine emitted them, for debugging on the worker's port: `chi web`'s
251
257
  `/api/sessions/:id/stream` never passes it on (`Web::App#stream_query`).
252
258
  An attached terminal's stream (`EventStream` with `rediscover:`) looks for the session's live
@@ -265,6 +271,22 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
265
271
  "event_seq":812}
266
272
  ```
267
273
 
274
+ A `tool_call_started` frame keeps its row fields but omits the model's `call`:
275
+
276
+ ```
277
+ event: tool_call_started
278
+ data: {"type":"tool_call_started","iteration":2,"call_index":1,"tool":"write",
279
+ "params":"path=notes.txt","event_seq":812}
280
+ ```
281
+
282
+ A `turn_completed` frame omits `result`; `turn_summary.output` carries the answer:
283
+
284
+ ```
285
+ event: turn_completed
286
+ data: {"type":"turn_completed","turn_summary":{"output":"Done."},
287
+ "display_pending":false,"event_seq":813}
288
+ ```
289
+
268
290
  `counts` is `ContextUsage.normalize` of the provider's payload without `ctx_pct` (normalize computes it
269
291
  without the session's LLM context budget; a client counts `total_tokens` against the window or budget, as
270
292
  `ContextStatus#generation_done` does for the TUI) and with its nil keys left out: cumulative for the
@@ -9,23 +9,22 @@ files, what a hook sees, how to test, and what goes wrong. The full references a
9
9
  ```
10
10
  chi bundle new my-bundle # ./my-bundle: manifest.yml, plugin.rb, test/my_bundle_test.rb, README.md
11
11
  cd my-bundle # edit plugin.rb and the test
12
- chi bundle test # the tests, on chi's own Ruby and lib (exit status: the tests')
12
+ chi bundle install --dev . # once while iterating: link this folder; edits load in the next session
13
13
  chi bundle check --fix # manifest, files, shas (--fix rewrites them), requires_chi, install + load in a sandbox
14
+ chi bundle test # tests on chi's own Ruby and lib (exit status: the tests')
14
15
  chi bundle try . -p "Say hello" # one real model turn; trace the bundle in an isolated install sandbox
15
- chi bundle install . # into the user's chi; a NEW session loads it (a running one doesn't)
16
- chi bundle install --dev . # or as a link to this folder: edits apply at the next session, no re-install
16
+ chi bundle uninstall my-bundle && chi bundle install . # at the end: leave dev mode, copy it in
17
17
  ```
18
18
 
19
- While you write it, `install --dev .` (once; `uninstall NAME` leaves dev mode) then try it in chi: a new session
20
- loads the plugin and hooks from the folder as they are, with no sha check (that bundle only; memories are copied).
21
- Re-install with `--force` from the same PATH to pick up memory edits.
22
- Dev-linked scripts aren't chmodded, since that would change the source. Chi warns with a `chmod +x PATH/scripts/x`
23
- hint when a listed script isn't executable; set that bit on the source script.
19
+ The loop is **edit → `check --fix` → `test` → `try`**. `install --dev .` links the folder once, so a new session
20
+ loads your edits with no reinstall (a running one doesn't; `try` doesn't need the link: it installs into its own
21
+ sandbox). Memories are copied: re-install with `--force` from the same PATH to pick up memory edits. At the end,
22
+ `uninstall NAME` leaves dev mode and a plain `install .` copies the bundle in (chi refuses it over a dev link).
23
+ Dev-linked scripts aren't chmodded: chi warns with a `chmod +x PATH/scripts/x` hint; set the bit on the source.
24
24
 
25
- `check` prints one finding per line, `error|warn|info CODE file[:line]: message (fix: hint)`, and exits 1 on any
26
- error. Run it after every edit: an install takes exactly what it checks. It installs into a temp sandbox, not
27
- the user's config or state, but `check` and `test` run the bundle's plugin and hooks in their own process, as
28
- written: run them on code you'd install.
25
+ `check` prints one finding per line (`error|warn|info CODE file[:line]: message (fix: hint)`), exits 1 on any error
26
+ and installs into a temp sandbox, not the user's config or state; run it after every edit. `check` and `test` run
27
+ the bundle's plugin and hooks in their own process, as written: run them on code you'd install.
29
28
 
30
29
  ## Trying a bundle
31
30
 
@@ -87,29 +86,36 @@ that list is never fired (`check` warns). The two a bundle wants most, for an `e
87
86
  Match on `result:`, not `output:`. An execute's result is its `stdout:` and `stderr:` blocks (each left out when
88
87
  empty), then `exit: N` (`exit: 0 (no output)` when both were empty); a non-zero exit makes `status:` `"error"`.
89
88
  Act through `ctx`: `ctx.notify(text, level: :warn)`, `ctx.steer(text)` (a line the model reads before its next
90
- step), `ctx.card(…)`, `ctx.stop_turn(reason)`, `ctx.ask_user(…)`, `ctx.data_dir` (the bundle's own folder),
89
+ step), `ctx.card(…)`, `ctx.stop_turn(reason)`, `ctx.ask_user(…)`, `ctx.data_dir` (the bundle's state folder),
91
90
  `ctx.cwd`, `ctx.log`. In a test, each recorded notice, steer or stop names its hook as `"plugin.rb (bundle
92
91
  my-bundle)"`, the plugin's label (a `hooks/x.rb` file's is `"x.rb (bundle my-bundle)"`).
93
92
 
94
93
  ## Testing it
95
94
 
96
- `chi bundle new` writes a Minitest test that passes as written: copy its shape. The kit (`samagotchi/testing`)
97
- loads the bundle's source through chi's own loaders, in a sandbox (temp HOME, config and state), and records what
98
- it did at chi's edges:
95
+ `chi bundle new` writes a Minitest test that passes as written: copy its shape. The kit (`samagotchi/testing`) loads
96
+ the bundle's source through chi's own loaders in a sandbox (temp HOME, config and state) and records what it did:
97
+ `load_bundle`, then `bundle.command` / `bundle.fire`, and the effects in `bundle.notices`. `bundle.ctx.data_dir` and
98
+ `sandbox.state_dir` are that test's temporary state, never the user's (the scaffold's plugin, tested):
99
99
 
100
100
  ```ruby
101
- bundle = load_bundle(BUNDLE, settings: { "greeting" => "hi" }) # in a Samagotchi::Testing::Case test
102
- bundle.command("/my-bundle chi") # → what the command returned
103
- bundle.fire(:after_tool_call, tool: "execute", status: "error", result: "exit: 1 (no output)", call: { … })
104
- bundle.tool_call({ name: "execute", content: "rm -rf /" }).deny? # before_tool_call hooks, through the real gate
105
- bundle.tool("my_tool", path: "a.txt") # a chi.tool, as the model calls it
106
- bundle.notices, bundle.steers, bundle.cards, bundle.stops, bundle.questions # what it did
107
- bundle.answer({ selected: "yes" }); bundle.model_answers << "text" # ask_user / ask_model answers
108
- bundle.http.stub(:get, "https://api.example/x", json: { ok: true }) # ctx.http, never the network
109
- bundle.set(cwd: dir, running: false); sandbox.config(hash); sandbox.fake_executable("gh", "echo '{}'")
110
- bundle.install # installed as chi installs it: warnings, load failures
101
+ require "minitest/autorun"
102
+ require "samagotchi/testing/minitest"
103
+
104
+ class MyBundleTest < Samagotchi::Testing::Case
105
+ def test_command_and_failed_execute_notice
106
+ bundle = load_bundle(File.expand_path("..", __dir__), settings: { "greeting" => "hi" })
107
+ assert_equal "hi, there", bundle.command("/my-bundle")
108
+ bundle.fire(:after_tool_call, tool: "execute", status: "error", call: { name: "execute", content: "false" },
109
+ result: "exit: 1 (no output)", output: "[execute]\nexit: 1 (no output)")
110
+ assert_equal "the command exited 1", bundle.notices.last.text
111
+ assert_equal File.join(sandbox.state_dir, "plugins", bundle.name), bundle.ctx.data_dir
112
+ end
113
+ end
111
114
  ```
112
115
 
116
+ `bundle.tool_call` runs guarded hooks; `bundle.tool` calls a registered tool. `bundle.steers`, `cards`, `stops` and
117
+ `questions` record effects; `bundle.answer` / `model_answers` provide `ask_user` / `ask_model` answers.
118
+ `bundle.http.stub` stubs `ctx.http`; `sandbox.config`, `sandbox.fake_executable` and `bundle.install` cover setup.
113
119
  The full list is in [plugins.md, Testing a bundle](plugins.md#testing-a-bundle-experimental). The kit is
114
120
  experimental (it may change before chi 1.0). If a test needs a fake of chi's own (a stub of a `Samagotchi::`
115
121
  class, a hand-built `ctx`), that is a gap in the kit: say so to the user rather than reaching into chi.
@@ -132,7 +138,7 @@ class, a hand-built `ctx`), that is a gap in the kit: say so to the user rather
132
138
 
133
139
  ## Verifying it live
134
140
 
135
- After `chi bundle install .`, one real session shows it works with the user's chi:
141
+ `chi bundle try . -p "Say hello"` is the live check (trace, plugin, notices); after the final install, check the copy:
136
142
 
137
143
  ```
138
144
  chi send --new --wait --format json --dir /tmp/try -m "/my-bundle chi" # the JSON names the session id
@@ -489,12 +489,12 @@ Turn events (the main ones; `Events` in `events.rb` keeps the shared sets):
489
489
  | `:thinking_refused` / `:reasoning_history_refused` | the chat loop asked a step again without the thinking fields / the reasoning sent back; the Engine logs it and makes a once-per-session-and-host `:hook_notice` of it, the event itself never reaches the sink | `{iteration, model, level, detail}` |
490
490
  | `:pending_input_merged` | queued input joined the conversation | `{iteration, count, content, steers?, answer}` |
491
491
  | `:tool_dispatch_started` / `:tool_dispatch_completed` | around a batch | `{iteration, call_count}` |
492
- | `:tool_call_started` | before a call runs | see ToolRunner |
492
+ | `:tool_call_started` | before a call runs | Engine event includes `call` (see ToolRunner); the Bridge's SSE frame omits it |
493
493
  | `:tool_call_completed` | after a call ran | see ToolRunner |
494
494
  | `:used_memories_updated` | a memory was read, and after the loop | `{used_memory_names, read_names?}` |
495
495
  | `:hook_notice` | a hook's or plugin's notify during a turn | `{hook, text, level}` (announced with `between_turns: true` outside a turn) |
496
496
  | `:guardrail_warning` | a guardrails line during a turn | `{message}` |
497
- | `:turn_completed` | normal end | `{result, turn_summary, display_pending}` |
497
+ | `:turn_completed` | normal end | Engine event `{result, turn_summary, display_pending}`; the Bridge's SSE frame omits `result` (answer remains in `turn_summary.output`) |
498
498
  | `:turn_canceled` | Stop, hook stop, Ctrl-C | `{cancellation_reason, cancelled_by?, duration_ms}` |
499
499
  | `:turn_failed` | an error | `{error_class, message, duration_ms, error_kind?, retryable?, host?, summary?}` |
500
500
  | `:answer_display` | after the after_turn hooks (observers only, not the sink) | `{display, refs?}` (display a string, or nil; refs the count of the answer's refs, only when a hook attached some) |
@@ -10,8 +10,10 @@ module Samagotchi
10
10
  # the provider's raw `payload`, and no raw `content` (the chat loop's is
11
11
  # reasoning + content again, the native loop's the raw stream with its
12
12
  # tool-call bytes and thought tags): clients read `text`, `thinking`,
13
- # `tool_call` and `counts`, never a provider's payload shape. `?raw=1` on
14
- # the worker's stream skips this (debugging).
13
+ # `tool_call` and `counts`, never a provider's payload shape.
14
+ # :tool_call_started omits the model's `call`; clients render its row from
15
+ # the other fields. :turn_completed omits `result`; `turn_summary.output`
16
+ # carries the answer. `?raw=1` on the worker's stream skips this (debugging).
15
17
  #
16
18
  # Pure: the event is the one hash SessionObserver hands every observer
17
19
  # (the ring, TurnAccumulator, the other writers, SessionMetrics, hooks),
@@ -28,13 +30,19 @@ module Samagotchi
28
30
  # @param event [Hash] the engine's event (symbol keys; its payload has
29
31
  # string keys)
30
32
  # @return [Hash] the event as clients get it: a copy for a
31
- # :generation_chunk, the same object for every other event
33
+ # projected event, the same object for every other event
32
34
  def project(event)
33
- return event unless event.is_a?(Hash) && event[:type].to_s == "generation_chunk"
35
+ return event unless event.is_a?(Hash)
34
36
 
35
- projected = event.except(:payload, :content)
36
- found = counts(event[:payload])
37
- found ? projected.merge(counts: found) : projected
37
+ case event[:type].to_s
38
+ when "generation_chunk"
39
+ projected = event.except(:payload, :content)
40
+ found = counts(event[:payload])
41
+ found ? projected.merge(counts: found) : projected
42
+ when "tool_call_started" then event.except(:call)
43
+ when "turn_completed" then event.except(:result)
44
+ else event
45
+ end
38
46
  end
39
47
 
40
48
  # The payload's normalized counts, nil keys left out; nil when the
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: samagotchi-system
3
- version: 0.89.0
3
+ version: 0.90.0
4
4
  scope: system
5
5
  description: Default system memories — identity, self map, config modification protocol, memory guide and the delegated-session rules
6
6
  files:
@@ -4,8 +4,8 @@ module Samagotchi
4
4
  module LLM
5
5
  # Normalized result returned by every ModelBackend downstream of the agentic
6
6
  # loop (the loop lives inside the backend). What Engine#run_turn and the
7
- # renderers (TerminalUI / Bridge) read: .output (= .text, and .to_s, which
8
- # is how the :turn_completed `result:` reaches JSON), .conversation,
7
+ # renderers (TerminalUI / Bridge) read: .output (= .text, and .to_s: the
8
+ # Engine's :turn_completed `result:`; clients get turn_summary.output), .conversation,
9
9
  # .canceled?, .cancellation_reason, .exhausted?, .resumable?,
10
10
  # .empty_answer?, plus the native loop's .tool_activity, .context_status
11
11
  # and .pending_tool_calls, which the interactive renderer needs.
@@ -76,6 +76,7 @@ module Samagotchi
76
76
  call = verdict.call
77
77
  params = ToolActivity.tool_activity_params(call[:name], call, registry: tools)
78
78
  label = plugin_label(call[:name])
79
+ # Kept on the raw event for in-worker hooks and metrics, not sent to SSE clients.
79
80
  started = { type: :tool_call_started, iteration: iteration, call_count: call_count, call_index: call_index,
80
81
  tool: call[:name], call: call.dup, params: params }
81
82
  started[:label] = label if label
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Samagotchi
4
- VERSION = "0.89.0"
4
+ VERSION = "0.90.0"
5
5
  # Sent as the User-Agent of every HTTP request chi makes.
6
6
  USER_AGENT = "chi/#{VERSION}"
7
7
  end
@@ -3,15 +3,11 @@
3
3
 
4
4
  import { ACTIVITY_FIELDS, EVENT_FIELDS, pickFields } from "./activity.js";
5
5
 
6
- // The answer text of a :turn_completed event. `turn_summary.output` is the
7
- // contract; `result` is the KernelLoop result, which reaches the wire as its
8
- // string form (older servers, other clients' fixtures).
6
+ // Read turn_summary.output; Bridge::ClientEvent omits result for clients.
9
7
  export function turnOutput(data) {
10
8
  if (!data) return "";
11
- const summary = data.turn_summary?.output;
12
- const result = typeof data.result === "string" ? data.result : data.result?.output;
13
- const out = [summary, result, data.content, data.output].find((v) => typeof v === "string" && v.trim());
14
- return out ? out.trim() : "";
9
+ const output = data.turn_summary?.output;
10
+ return typeof output === "string" ? output.trim() : "";
15
11
  }
16
12
 
17
13
  // The end line for a turn whose worker went away mid-turn (the footer's
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: samagotchi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.89.0
4
+ version: 0.90.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov