samagotchi 0.146.0 → 0.147.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: 701bfb63903962a149abf5d17f39be6c043141e5e1b9ea952e7ba27b77ebe3d8
4
- data.tar.gz: 7be23545775dc31bba230e8dfde6c896def7530d032cc8ae7f79d2a9b91d7782
3
+ metadata.gz: 29546fb5487086302ed4d68c168fa9e7f4f576bee252e59cfdaf08a4e74081b4
4
+ data.tar.gz: 3c05877e89bccf719dbdf8f4287c8343222f897484c2f22a4c3245bdc92a2af1
5
5
  SHA512:
6
- metadata.gz: 8e4a8bbbfa0398b816a6dd266a6b127f507e6329feaf2b5f37283f2eef76344a334e2bf60409978fab45ab714c56ca1c9c44754174222238d930845bc0f5d929
7
- data.tar.gz: 139f5254bb1dbeec07cbdf6cb0033cdb34ce5eae967585a9ab9a843fb961528e0d16b77d562668789646b38b16eaca42e28f2338d49f4ebe47b42a58be6f9597
6
+ metadata.gz: 953166016279e2b81ebe64c9642df2a245cc3d7fe4520850bf4023557b5d1b7fd620589fd7c49df66d458e59eeae32dffb3020e6286ea8be85edf2710c2bd7d4
7
+ data.tar.gz: 6c01b8ac5e044770f34e4fd5057915116e8b56b81a6f505fac3259449911a02b091262aad2554417ebf0493068ace77b1636d3fb8187769225268d12540c5354
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and commands may change between minor versions. How releases are made:
7
7
  [docs/releasing.md](docs/releasing.md).
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.147.0] - 2026-11-03
11
+
12
+ ### Changed
13
+
14
+ - With `update.auto: install` a newer chi installed some other way, such as `chi update` in a terminal, now gets the
15
+ same unattended restarts as one chi web installed itself: idle workers and chi web move onto it within about 10
16
+ minutes once no turn runs, and the phone hears "chi updated to X". The page offers no **Restart** buttons meanwhile,
17
+ unless the restart failed or has waited an hour; `update.restart: never` keeps them.
18
+ [docs/configuration.md](docs/configuration.md#unattended-updates-updateauto)
19
+
20
+ ### Fixed
21
+
22
+ - An automatic update's sheet no longer offers **Restart workers** and **Restart chi web** beside a run already doing
23
+ both, and its worker restarts no longer stack sticky toasts unless one fails.
24
+
10
25
  ## [0.146.0] - 2026-11-03
11
26
 
12
27
  ### Added
@@ -5580,7 +5595,8 @@ and long-lived sessions.
5580
5595
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
5581
5596
  hotkey panel that send selected text or the clipboard to your sessions.
5582
5597
 
5583
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.146.0...HEAD
5598
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.147.0...HEAD
5599
+ [0.147.0]: https://github.com/dm1try/samagotchi/compare/v0.146.0...v0.147.0
5584
5600
  [0.146.0]: https://github.com/dm1try/samagotchi/compare/v0.145.0...v0.146.0
5585
5601
  [0.145.0]: https://github.com/dm1try/samagotchi/compare/v0.144.0...v0.145.0
5586
5602
  [0.144.0]: https://github.com/dm1try/samagotchi/compare/v0.143.0...v0.144.0
data/docs/architecture.md CHANGED
@@ -335,9 +335,10 @@ reload in a toast, since an open tab keeps its old JS across a `chi web` upgrade
335
335
  `installed`, the newest chi on this machine (`InstalledVersions`: the highest `samagotchi-<v>.gemspec`
336
336
  in RubyGems' specification dirs, or a checkout's `version.rb` on disk; looked at again on each full
337
337
  probe, every 10 s). A `chi` frame and the `GET /api/events` snapshot carry the full state
338
- `{version, installed, available, update_blocked, auto, restart}` (`auto`: `update.auto`, `off` or `install`, which the page's
339
- "available" wording follows; `restart`: `update.restart`, with which the page offers no Restart toast for the
340
- version an automatic install's frames name unless it is `never` or the run failed: that run restarts by itself); `/api/info` and the session-command wake response carry the same fields. `SessionHub#update_available` publishes the checker result under the hub's
338
+ `{version, installed, available, update_blocked, auto, restart, auto_offers}` (`auto`: `update.auto`, `off` or `install`, which the page's
339
+ "available" wording follows; `restart`: `update.restart`; `auto_offers`: the version AutoUpdate says the page should offer
340
+ the restarts for. With `auto: install`, `restart: idle` and no block the page offers no Restart toast for a newer installed
341
+ chi, however it got installed, unless `auto_offers` names it); `/api/info` and the session-command wake response carry the same fields. `SessionHub#update_available` publishes the checker result under the hub's
341
342
  Monitor, but fetching never holds it.
342
343
 
343
344
  `Web::UpdateCheck` runs its own thread beside the hub. When `update.notify: on` (the default) or
@@ -385,7 +386,14 @@ is `update.auto: install`. Every 5 minutes (the first step 60 s after start) it
385
386
  and installs it through `UpdateOps#install(wait: true, auto: true)` when the install isn't blocked, no operation
386
387
  runs, no hub summary has `status: running` or a `pending_question`, and the version isn't in `auto_failed` or
387
388
  already in the `auto` record (one automatic try per version). Its frames carry `auto: true` (the page: "Automatic
388
- chi update in progress", "(automatic)" in the sheet's title). Its record is `update_check.json`'s `auto`:
389
+ chi update in progress", "(automatic)" in the sheet's title, no restart buttons in the sheet, no toast for a worker pass
390
+ unless it fails). A newer chi installed some other way (`AutoUpdate#adopt_installed`: `update.restart: idle`, the same gate, no
391
+ available release to install first, none on record for that version) is **adopted** once seen for `SETTLE` (60 s, in
392
+ practice the next step, as `chi update` merges the bundles after installing the gem and leaves no lock to look at): its
393
+ record starts at `phase: installed` and the run goes on as below. Ahead of every gate each step calls
394
+ `SessionHub#auto_offer_restart` (`auto_offers`, in memory) when a run on record for the newest installed chi is closed with
395
+ this chi web still older, or that chi has waited `HELD_FOR` (1 h): the trying goes on, and the page has its buttons back;
396
+ `restart_failed` and a failed install call it at once. Its record is `update_check.json`'s `auto`:
389
397
  `{version, from, at, kept, phase: installed|workers|web|retrying|done|failed, workers: {restarted, left}, detail, pushed}`,
390
398
  in Ruby the value object `Web::AutoUpdateRecord`, which owns that shape (`from_h`, `to_store`).
391
399
  With `update.restart: never` the run ends at the install (`chi_notice` `installed`). With `idle` it goes on
@@ -395,7 +403,8 @@ the worker's own atomic refusal still applies); then, with no
395
403
  turn running, `Server.restart_preflight` (the restart's command with `--version`, 20 s, killed on a timeout; it must
396
404
  print a chi at least the release) and, if it passes, the record's `phase: web` is written **before**
397
405
  `UpdateOps#restart_web`. A failed preflight, or a refused restart, publishes `restart_failed` and ends the run
398
- (`failed`). `AutoUpdate#stop` waits `STOP_WAIT` (25 s, longer than the preflight's timeout) for its thread, and a
406
+ (`failed`). A run at `installed` or `workers` that finds this chi web already on its version (restarted by hand from the
407
+ page's offer) goes straight to the finish. `AutoUpdate#stop` waits `STOP_WAIT` (25 s, longer than the preflight's timeout) for its thread, and a
399
408
  preflight that ends after a stop leads to nothing (no `web`, no exec): the next start takes the run up again. The restarted chi web's AutoUpdate (first step after 5 s) finds `phase: web`, checks it runs the
400
409
  release, counts the workers once more and publishes `updated`, then `done`, or `retrying` while workers are left. Those are
401
410
  retried each step until none is left or 24 h (`RETRY_FOR`) have passed since the install, under the same filter, and from
data/docs/cli.md CHANGED
@@ -267,7 +267,9 @@ done
267
267
  in place, the notice gives the blocker and its command instead.
268
268
  With `update.auto: install` chi web installs the release by itself once no
269
269
  turn runs anywhere, then restarts idle workers and itself (`update.restart`),
270
- and the notice reads "installs by itself" with no **Update** button; see
270
+ and the notice reads "installs by itself" with no **Update** button. A newer
271
+ chi installed from a terminal gets those restarts too, and no **Restart**
272
+ buttons unless the restart failed or has waited an hour; see
271
273
  [Unattended updates](configuration.md#unattended-updates-updateauto).
272
274
 
273
275
  - **The chi home.** With a [chi home](configuration.md#the-chi-home), a `chi
@@ -355,7 +357,8 @@ done
355
357
  terminal, no reminder) and `session.idle_exit_minutes` pass (never with 0).
356
358
  A new or woken worker always starts on the newest chi installed. A running
357
359
  `chi web` (on the port it was started with, else `web.port`) on an older chi needs a restart (Ctrl-C, then `chi web`):
358
- its page says so too. One from before workers started on the newest chi
360
+ its page says so too. With `update.auto: install` it restarts by itself once no turn
361
+ runs ([Unattended updates](configuration.md#unattended-updates-updateauto)). One from before workers started on the newest chi
359
362
  also starts its new sessions on its own version until then (the row says so).
360
363
 
361
364
  `--dry-run` shows the table with "would update" and changes nothing. It is
@@ -2163,9 +2163,22 @@ Both are read when chi web starts (restart it to apply a change). What it does,
2163
2163
  ([Phone notifications](#phone-notifications-web-push)); without it the page's toasts and
2164
2164
  `$XDG_STATE_HOME/samagotchi/update_check.json` (`auto`) are the record.
2165
2165
 
2166
+ **A chi installed some other way** (`chi update` in a terminal, a release newer than `update.min_age_hours` allows, a
2167
+ hand `gem install`) gets steps 3 and 4 too, with `update.restart: idle` and an install that isn't blocked: chi web notices it,
2168
+ waits for the next 5-minute look (`chi update` merges the bundles after installing the gem) and for the quiet moment of
2169
+ step 1, then restarts as above, so chi web is on it within about 10 minutes when nothing runs. A release it can install
2170
+ goes first. One run per version here too: a restart that didn't take isn't tried again.
2171
+
2172
+ The page offers no **Restart** buttons while chi web is going to restart by itself. They come back, as a toast, when it
2173
+ won't (a failed run) or when something has held it for an hour (a forgotten question, a long turn); it keeps trying behind
2174
+ them. Worker restarts in such a run show no toast unless one fails, and the sheet behind **View** has only **Close**;
2175
+ the session's own **Restart** in its bar is always there.
2176
+
2166
2177
  There is no watchdog: chi web restarts into the newest chi it just proved can start, and nothing brings an old one
2167
- back by itself. To roll back, install the older gem with `gem install samagotchi -v X` and restart chi web
2168
- (`chi update --to` refuses a version that isn't newer than the running chi). The desktop helper isn't rebuilt (that
2178
+ back by itself. To roll back, set `update.auto: off` (or the next check installs the release again), uninstall the newer
2179
+ gem with the chi home's Ruby (`gem uninstall samagotchi -v Y`: chi always starts the newest one installed, so installing an
2180
+ older gem beside it changes nothing), and restart chi web. `chi _X_ web` stays on X only with `update.auto: off`: a restart
2181
+ starts the newest. `chi update --to` refuses a version that isn't newer than the running chi. The desktop helper isn't rebuilt (that
2169
2182
  stays a manual `chi update`).
2170
2183
 
2171
2184
  ## All settings
@@ -2288,7 +2301,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2288
2301
  | `update.bundles` | `true` | | `false`: `chi update` leaves the shipped bundles to `chi bundle upgrade` (`--no-bundles`). |
2289
2302
  | `update.desktop` | `true` | | `false`: `chi update` leaves the desktop helper alone (`--no-desktop`). |
2290
2303
  | `update.notify` | `on` | | `on`: chi web checks RubyGems once a day and shows aged stable releases newer than both this chi and the newest installed chi; `off`: no thread or network check. |
2291
- | `update.auto` | `off` | | `install`: chi web installs the release its checker found (aged by `update.min_age_hours`) once no turn runs and no question waits in any session, as the **Update** button would (shipped bundles merged, your edits kept and reported), then pushes how it went; `update.restart` says what follows. An install that fails is tried once per version (a newer release, or the **Update** button, tries again). The check runs even with `update.notify: off` (which then only silences the page's and the phone's "available"). An install that can't run here (a checkout, Bundler, an unwritable gem dir) stays a notice. `off`: notice only. See [Unattended updates](#unattended-updates-updateauto). |
2304
+ | `update.auto` | `off` | | `install`: chi web installs the release its checker found (aged by `update.min_age_hours`) once no turn runs and no question waits in any session, as the **Update** button would (shipped bundles merged, your edits kept and reported), then pushes how it went; `update.restart` says what follows, also for a chi installed some other way (`chi update` in a terminal). An install that fails is tried once per version (a newer release, or the **Update** button, tries again). The check runs even with `update.notify: off` (which then only silences the page's and the phone's "available"). An install that can't run here (a checkout, Bundler, an unwritable gem dir) stays a notice. `off`: notice only. See [Unattended updates](#unattended-updates-updateauto). |
2292
2305
  | `update.restart` | `idle` | | With `update.auto: install`: `idle` restarts the stale workers whose session is idle and untouched for 10 minutes, then chi web itself, once no turn runs anywhere and the newest chi has answered `chi --version`; it pushes "chi updated to X". `never`: the install only (the page's toast offers the restarts, workers also exit on their own when idle). |
2293
2306
  | `update.check_hours` | `24` | | Hours between RubyGems checks (at least 1). The first check starts about 30 seconds after chi web starts. |
2294
2307
  | `update.min_age_hours` | `24` | | A release must be this old before chi web offers it; `0` disables the age delay. |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: samagotchi-system
3
- version: 0.146.0
3
+ version: 0.147.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:
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Samagotchi
4
- VERSION = "0.146.0"
4
+ VERSION = "0.147.0"
5
5
  # Sent as the User-Agent of every HTTP request chi makes.
6
6
  USER_AGENT = "chi/#{VERSION}"
7
7
  end
@@ -1018,7 +1018,7 @@ module Samagotchi
1018
1018
  def handle_info(req)
1019
1019
  return participant_info unless principal(req).owner?
1020
1020
 
1021
- chi_state = @hub&.chi_state || { version: Samagotchi::VERSION, installed: nil, available: nil, update_blocked: nil, auto: "off", restart: "idle" }
1021
+ chi_state = @hub&.chi_state || { version: Samagotchi::VERSION, installed: nil, available: nil, update_blocked: nil, auto: "off", restart: "idle", auto_offers: nil }
1022
1022
  json_response(200, { app: "chi-web", **chi_state, pid: Process.pid,
1023
1023
  cwd: Dir.pwd, start_dir: start_dir, features: INFO_FEATURES, lan: @lan && @lan[:ip],
1024
1024
  proxy: @public_url && { identity_header: @public_url.identity_header } })
@@ -3,6 +3,7 @@
3
3
  require "rubygems/version"
4
4
  require "time"
5
5
  require_relative "../config"
6
+ require_relative "../installed_versions"
6
7
  require_relative "../log"
7
8
  require_relative "../version"
8
9
  require_relative "auto_update_record"
@@ -37,6 +38,17 @@ module Samagotchi
37
38
  # A preflight that fails leaves chi web as it is and says so.
38
39
  # update.restart: never stops after the install.
39
40
  #
41
+ # A newer chi installed some other way (a terminal's `chi update`, inside
42
+ # update.min_age_hours or not) is adopted: once it has settled and the
43
+ # gate is open, it gets a run from step 1 on. One run per version here
44
+ # too, so a restart that didn't take isn't tried again.
45
+ #
46
+ # The page offers no restart buttons for a run that will do them itself.
47
+ # It offers them again when this says so (SessionHub#auto_offer_restart):
48
+ # a run on record for the version ended with chi web still older, or
49
+ # something held it for HELD_FOR, a forgotten question or a long turn.
50
+ # The trying goes on behind that offer.
51
+ #
40
52
  # The run's record (AutoUpdateRecord) lives in update_check.json
41
53
  # (UpdateCheck#save_auto), so a chi web restart finds it. See
42
54
  # docs/architecture.md.
@@ -56,6 +68,13 @@ module Samagotchi
56
68
  OPEN_PHASES = %w[installed workers web].freeze
57
69
  # The phase after "chi updated" while workers are left.
58
70
  RETRYING = "retrying"
71
+ # A newer chi seen installed is adopted only this long after: `chi update`
72
+ # installs the gem first and merges the bundles after, with no lock or
73
+ # marker to look at. In practice the next POLL.
74
+ SETTLE = 60
75
+ # How long a newer installed chi may wait for its restart before the page
76
+ # offers the buttons again.
77
+ HELD_FOR = 3600
59
78
  # How long stop waits for the thread: long enough for a preflight
60
79
  # (Server::PREFLIGHT_TIMEOUT, 20 s; a healthy one takes about a
61
80
  # second) to end on its own, so a stop never abandons one mid-way.
@@ -84,6 +103,7 @@ module Samagotchi
84
103
  @wakeup = Queue.new
85
104
  @thread = nil
86
105
  @stopped = false
106
+ @seen = nil
87
107
  end
88
108
 
89
109
  def start
@@ -108,17 +128,17 @@ module Samagotchi
108
128
  def step
109
129
  return unless @auto == "install"
110
130
 
131
+ note_installed
111
132
  record = @check.auto_state
112
133
  return carry_on(record) if record && OPEN_PHASES.include?(record.phase)
113
134
 
114
135
  retry_left(record) if record&.phase == RETRYING
136
+ return if @ops.blocked_reason || @ops.running? || busy_turn?
115
137
 
116
138
  version = @check.available
117
- return unless version
118
- return if @check.auto_failed?(version) || tried?(version)
119
- return if @ops.blocked_reason || @ops.running? || busy_turn?
139
+ return install(version) if version && !@check.auto_failed?(version) && !tried?(version)
120
140
 
121
- install(version)
141
+ adopt_installed
122
142
  end
123
143
 
124
144
  private
@@ -158,6 +178,37 @@ module Samagotchi
158
178
  end
159
179
  end
160
180
 
181
+ # What is installed, ahead of every gate: since when a newer chi is, and
182
+ # whether the page should offer the restarts for it. A chi web restart
183
+ # starts the clock over, and finds a closed run's offer again here.
184
+ def note_installed
185
+ installed = @hub.refresh_installed
186
+ return @seen = nil unless InstalledVersions.newer?(installed)
187
+
188
+ now = @clock.call
189
+ @seen = [installed, now] unless @seen&.first == installed
190
+ @hub.auto_offer_restart(installed) if closed_run?(installed) || now - @seen.last >= HELD_FOR
191
+ end
192
+
193
+ # A run on record for +version+ that is over, with chi web still older.
194
+ def closed_run?(version)
195
+ record = @check.auto_state
196
+ !record.nil? && record.version == version && !OPEN_PHASES.include?(record.phase)
197
+ end
198
+
199
+ # A newer chi this didn't install, settled: a run of its own from here.
200
+ # The terminal's table already told what `chi update` kept.
201
+ def adopt_installed
202
+ return unless @restart == "idle" && @seen
203
+
204
+ installed, seen_at = @seen
205
+ now = @clock.call
206
+ return if tried?(installed) || now - seen_at < SETTLE
207
+
208
+ Log.info(:web, "auto_update_adopted", version: installed, from: VERSION)
209
+ carry_on(save(AutoUpdateRecord.new(version: installed, from: VERSION, at: now.iso8601, phase: "installed")))
210
+ end
211
+
161
212
  def install(version)
162
213
  Log.info(:web, "auto_update_start", version: version, from: VERSION)
163
214
  result = @ops.install(version, wait: true, auto: true)
@@ -187,7 +238,9 @@ module Samagotchi
187
238
  # The next step of a run on record.
188
239
  def carry_on(record)
189
240
  case record.phase
190
- when "installed", "workers" then workers_then_web(record)
241
+ when "installed", "workers"
242
+ # Restarted by hand on the way (the page's offer): nothing left to exec.
243
+ InstalledVersions.newer?(record.version) ? workers_then_web(record) : finish(record)
191
244
  when "web" then finish(record)
192
245
  end
193
246
  end
@@ -260,6 +313,7 @@ module Samagotchi
260
313
  def restart_failed(record, detail, event)
261
314
  Log.warn(:web, event, version: record.version, detail: detail)
262
315
  save(record.with(phase: "failed", detail: detail, pushed: true))
316
+ @hub.auto_offer_restart(record.version)
263
317
  @hub.publish_notice(kind: "restart_failed", version: record.version, detail: detail)
264
318
  end
265
319
 
@@ -307,6 +361,7 @@ module Samagotchi
307
361
  Log.warn(:web, "auto_update_failed_install", version: record.version, detail: detail)
308
362
  gem_installed = rows.any? { |candidate| candidate["component"] == "chi (gem)" && !candidate["failed"] }
309
363
  @check.save_auto(record.with(phase: "failed", detail: detail, pushed: true), failed: record.version)
364
+ @hub.auto_offer_restart(record.version)
310
365
  @hub.publish_notice(kind: "failed", version: record.version, detail: detail.empty? ? nil : detail,
311
366
  installed: gem_installed)
312
367
  end
@@ -95,7 +95,7 @@ import { waitingOn } from "./notify.js";
95
95
  import { applySessionEvent, sortedByUpdated } from "./sessions_list.js";
96
96
  import { createNotifications } from "./notifications.js";
97
97
  import { renameShown, tabTitle, topBarTitle } from "./session_title.js";
98
- import { mergeVersionState, restartConfirmText, versionNotice } from "./update_notice.js";
98
+ import { mergeVersionState, restartConfirmText, updateFrameToast, versionNotice } from "./update_notice.js";
99
99
  import {
100
100
  appendAboveLiveTiming, mergeTiming, normalizeTiming,
101
101
  } from "./timing.js";
@@ -585,7 +585,7 @@ function noticeVersions(data) {
585
585
  const notice = versionNotice({ loaded: document.body.dataset.version, served: versions.served,
586
586
  installed: versions.installed, available: versions.available,
587
587
  updateBlocked: versions.updateBlocked, auto: versions.auto, restart: versions.restart,
588
- autoInstall: versions.autoInstall, shown: versionNoticesShown });
588
+ autoOffers: versions.autoOffers, shown: versionNoticesShown });
589
589
  store.touch("infoBar"); // the worker badge compares with the newest installed
590
590
  if (!notice) return;
591
591
  versionNoticesShown.add(notice.key);
@@ -614,20 +614,9 @@ function noticeVersions(data) {
614
614
  function updateFrame(data, { snapshot = false } = {}) {
615
615
  const wasOpen = updateSheet.isOpen();
616
616
  if (!updateSheet.receive(data)) return;
617
- // Every install frame, not only the done one: the hub's `chi` frame with
618
- // the new installed version comes before it, and an automatic run's
619
- // running frame is what keeps that from offering the Restart toast.
620
- if (data.operation === "install") {
621
- noticeVersions({ ...(data.state === "done" && data.version ? { installed: data.version } : {}), chi_update: data });
622
- }
623
- const label = `${data.auto ? "Automatic " : ""}${data.operation === "install" ? "chi update" : "worker restart"}`
624
- .replace(/^./, (c) => c.toUpperCase());
625
- if (!wasOpen && data.state === "running") {
626
- showToast(`${label} in progress`, { label: "View", run: () => updateSheet.view() }, { sticky: true });
627
- } else if (!snapshot && !wasOpen && ["done", "failed"].includes(data.state)
628
- && !(data.operation === "install" && data.state === "done")) {
629
- showToast(`${label} ${data.state}`, { label: "View", run: () => updateSheet.view() }, { sticky: true });
630
- }
617
+ if (data.operation === "install" && data.state === "done" && data.version) noticeVersions({ installed: data.version });
618
+ const text = updateFrameToast(data, { wasOpen, snapshot });
619
+ if (text) showToast(text, { label: "View", run: () => updateSheet.view() }, { sticky: true });
631
620
  }
632
621
 
633
622
  function copyUpdateCommand(command) {
@@ -17,12 +17,7 @@ export function mergeVersionState(current = {}, frame = {}) {
17
17
  if (has(frame, "update_blocked")) next.updateBlocked = known(frame.update_blocked) ? frame.update_blocked : null;
18
18
  if (has(frame, "auto")) next.auto = frame.auto === "install" ? "install" : "off";
19
19
  if (has(frame, "restart")) next.restart = frame.restart === "never" ? "never" : "idle";
20
- // The last install's chi_update state (a snapshot's, or a frame's): the
21
- // version an automatic one installs, while it hasn't failed.
22
- const run = frame.chi_update;
23
- if (run && run.operation === "install" && known(run.version)) {
24
- next.autoInstall = run.auto === true && run.state !== "failed" ? run.version : null;
25
- }
20
+ if (has(frame, "auto_offers")) next.autoOffers = known(frame.auto_offers) ? frame.auto_offers : null;
26
21
  return next;
27
22
  }
28
23
 
@@ -53,22 +48,23 @@ export function newerVersion(a, b) {
53
48
  // The toast for the versions as the page knows them, or null:
54
49
  // - loaded ≠ served: chi web restarted on another version; reload the page;
55
50
  // - installed newer than served: chi web runs an older chi than installed;
56
- // restart its workers or chi web; not for the version an automatic install
57
- // (+autoInstall+) brought with update.restart: idle, whose run restarts
58
- // them by itself (and pushes how it went);
51
+ // restart its workers or chi web; not with update.auto: install and
52
+ // update.restart: idle (and not blocked), where chi web restarts them by
53
+ // itself and pushes how it went, however that chi got installed, unless
54
+ // chi web says it won't or has been held for long (+autoOffers+);
59
55
  // - available newer than served: install it, unless the install is blocked;
60
56
  // with update.auto: install (and not blocked) chi web installs it by itself
61
57
  // once no turn runs: the notice says so, with no Update button.
62
58
  // Each has a key; one in +shown+ (a Set: what this tab already showed, as
63
59
  // every reconnect's snapshot says it again) is not shown twice.
64
60
  // @return {{key, text, reload: boolean} | null}
65
- export function versionNotice({ loaded, served, installed, available, updateBlocked, auto, restart, autoInstall,
61
+ export function versionNotice({ loaded, served, installed, available, updateBlocked, auto, restart, autoOffers,
66
62
  shown = new Set() } = {}) {
67
63
  let notice = null;
68
64
  if (known(loaded) && known(served) && loaded !== served) {
69
65
  notice = { key: `reload:${served}`, text: `chi was updated to ${served}`, reload: true };
70
66
  } else if (newerVersion(installed, served)) {
71
- const restartsByItself = auto === "install" && restart !== "never" && autoInstall === installed;
67
+ const restartsByItself = auto === "install" && restart !== "never" && !updateBlocked && autoOffers !== installed;
72
68
  if (!restartsByItself) {
73
69
  notice = {
74
70
  key: `installed:${installed}`,
@@ -101,6 +97,21 @@ export function versionNotice({ loaded, served, installed, available, updateBloc
101
97
  return notice && !shown.has(notice.key) ? notice : null;
102
98
  }
103
99
 
100
+ // The toast's words for an update operation's frame, or null: what runs and how
101
+ // it ended, for a tab whose sheet isn't open. An automatic run's worker passes
102
+ // say something only when one fails (the run ends in the Reload toast); a done
103
+ // install leaves it to the version toast; a snapshot repeats no ending.
104
+ export function updateFrameToast(data, { wasOpen = false, snapshot = false } = {}) {
105
+ if (wasOpen) return null;
106
+ if (data.auto && data.operation === "workers" && data.state !== "failed") return null;
107
+ const label = `${data.auto ? "Automatic " : ""}${data.operation === "install" ? "chi update" : "worker restart"}`
108
+ .replace(/^./, (c) => c.toUpperCase());
109
+ if (data.state === "running") return `${label} in progress`;
110
+ if (snapshot || !["done", "failed"].includes(data.state)) return null;
111
+ if (data.operation === "install" && data.state === "done") return null;
112
+ return `${label} ${data.state}`;
113
+ }
114
+
104
115
  // The info bar's badge for the open session's worker when it runs an older
105
116
  // chi than the newest installed (or than chi web, while that is unknown),
106
117
  // else null. A worker with the "restart" feature offers Restart; an older
@@ -45,9 +45,10 @@ function statusText(model) {
45
45
 
46
46
  // Both restarts, once a newer chi is installed (an install done here, or the
47
47
  // installed notice's toast) and nothing runs: either result keeps the other in reach.
48
+ // Not on an automatic run: it restarts them by itself.
48
49
  function restartActions(model) {
49
50
  const busy = ["starting", "running", "restarting"].includes(model.state);
50
- if (!model.restartable || busy) return nothing;
51
+ if (!model.restartable || model.auto || busy) return nothing;
51
52
  return html`<button type="button" class="ghost" data-act="restart-workers">Restart workers</button>
52
53
  <button type="button" class="ghost" data-act="restart-web">Restart chi web</button>`;
53
54
  }
@@ -125,6 +126,8 @@ export function createUpdateSheet({ dialog, update, restartWorkers, restartWeb }
125
126
  model = {
126
127
  ...model,
127
128
  ...data,
129
+ // A frame names auto only when it is true: this run's word, not the last one's.
130
+ auto: data.auto === true,
128
131
  rows: Array.isArray(data.rows) ? data.rows : [],
129
132
  };
130
133
  if (data.operation === "install" && data.state === "done") restartable = true;
@@ -274,7 +274,7 @@ module Samagotchi
274
274
  model_name: session_access.show(id).model_name,
275
275
  default_model: ModelProfile.default_model_name,
276
276
  parent_strategy: strategy_json(session_access.parent_strategy(id)),
277
- **(@hub&.chi_state || { version: VERSION, installed: nil, available: nil, update_blocked: nil, auto: "off", restart: "idle" }) })
277
+ **(@hub&.chi_state || { version: VERSION, installed: nil, available: nil, update_blocked: nil, auto: "off", restart: "idle", auto_offers: nil }) })
278
278
  rescue SessionManager::OwnedByTUI => e
279
279
  owned_by_tui(e)
280
280
  rescue SessionAccess::Refused => e
@@ -41,8 +41,7 @@ module Samagotchi
41
41
 
42
42
  # What a subscriber gets: `session` with {session: summary} for an
43
43
  # upsert, `session_gone` with {id:} for a removal, `chi` with
44
- # {version:, installed:, available:, update_blocked:} when chi version
45
- # state changes. seq is monotonic per
44
+ # the #chi_state fields when chi version state changes. seq is monotonic per
46
45
  # hub, for the log's sake: there is no replay.
47
46
  Event = Struct.new(:type, :seq, :data, keyword_init: true)
48
47
 
@@ -76,7 +75,7 @@ module Samagotchi
76
75
  # @param installed_versions [#newest] what is installed (InstalledVersions)
77
76
  # @param update_auto [String] update.auto, said in the chi state (the page's wording)
78
77
  # @param update_restart [String] update.restart, said beside it (whether
79
- # an automatic install's run restarts by itself: no Restart toast)
78
+ # a newer installed chi restarts by itself: no Restart toast)
80
79
  def initialize(state_dir:, manager: SessionManager, now: nil, scan_interval: SCAN_INTERVAL,
81
80
  full_probe_interval: FULL_PROBE_INTERVAL, installed_versions: InstalledVersions.new,
82
81
  update_auto: Config.get("update.auto"), update_restart: Config.get("update.restart"))
@@ -87,6 +86,7 @@ module Samagotchi
87
86
  @installed = nil
88
87
  @available = nil
89
88
  @update_blocked = nil
89
+ @auto_offers = nil
90
90
  @update_state = nil
91
91
  @manager = manager
92
92
  @now = now || -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
@@ -164,7 +164,7 @@ module Samagotchi
164
164
  def chi_state
165
165
  @monitor.synchronize do
166
166
  { version: VERSION, installed: @installed, available: @available, update_blocked: @update_blocked,
167
- auto: @update_auto, restart: @update_restart }
167
+ auto: @update_auto, restart: @update_restart, auto_offers: @auto_offers }
168
168
  end
169
169
  end
170
170
 
@@ -180,6 +180,19 @@ module Samagotchi
180
180
  nil
181
181
  end
182
182
 
183
+ # AutoUpdate won't (or hasn't, for long) restart onto +version+ by itself:
184
+ # the page offers the restarts for it, in spite of update.auto. In memory:
185
+ # AutoUpdate says it again after a chi web restart.
186
+ def auto_offer_restart(version)
187
+ @monitor.synchronize do
188
+ next if @auto_offers == version
189
+
190
+ @auto_offers = version
191
+ emit("chi", chi_state)
192
+ end
193
+ nil
194
+ end
195
+
183
196
  # Publish the owner-triggered update's progress or result for current
184
197
  # tabs, and retain the latest state for a reconnect's snapshot.
185
198
  def publish_update(state:, rows:, progress: nil, operation: nil, version: nil, auto: false)
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.146.0
4
+ version: 0.147.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov