samagotchi 0.114.0 → 0.115.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: a59aa63d1ccf6a6114afcaf5b1cf19088cb482989c08acb3aa5bfede3a67358d
4
- data.tar.gz: 3fe5245e7c864ca0b814d9b187b402d5658ffd88aa5d895881482f4b40e9391f
3
+ metadata.gz: 1829b38f073eadf8af34d8b4955f4d69d0e6aa1983acdf5e26a99daef4b629a4
4
+ data.tar.gz: 19ee9838f04a908813a8dee016cea758acaa3ac78fb0df3902b0941ff222500a
5
5
  SHA512:
6
- metadata.gz: 412b5e6a15d73f4817729879e71aa3f2813ae73705039a8db561cbafffa0e246f6cf0f37760ebf346144c88369174a42cd902f36d31f3c02703345c99d18505f
7
- data.tar.gz: 25d265a240a37194b5670afeb6d4784b55b43ddb33a857039da16ffc1a1184d79a6a4b30e7111a8c0698f538c130cd2c930545c03e3474a9bbf814bd22dbb48f
6
+ metadata.gz: 4d4e59ca58dc0dcfc0ebe16a033fee3e32f6eda731181ec093f554b53393f9fae2d4ca3bcf33c8b0ea9d0f204abaf4be22c416b3788c43d0eeb8b19730cb29bf
7
+ data.tar.gz: f26d79c70cb60d2febde92c114ade0cb01ef975c05b05602bf975779481147a13e0e8832bf46b8ec3730f6c12800d3ff4541669a925605b5b98372b106d4213b
data/CHANGELOG.md CHANGED
@@ -8,6 +8,24 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.115.0] - 2026-10-25
12
+
13
+ ### Changed
14
+
15
+ - chi web's notifications — the browser's own and the phone's push — name a session just as its card does: with no
16
+ title, the recap's first sentence now comes after the auto title and before the first message, instead of jumping
17
+ straight to the first message.
18
+ - chi web on a touch screen: the Annotate bar docks just above the composer instead of under the selection, where iOS's
19
+ own selection menu (Copy, Look Up…) was drawn over it; its pills are larger to tap and wrap to a second row.
20
+
21
+ ### Fixed
22
+
23
+ - `/title` with no argument now says what the session's card shows: the auto title its recap saved (even when this
24
+ session's recap is off), or the recap's first sentence when the recap has no title. Both said "shows the first
25
+ message".
26
+
27
+ Update with `chi update` (the system bundle 0.115.0), then restart `chi web`.
28
+
11
29
  ## [0.114.0] - 2026-10-25
12
30
 
13
31
  ### Added
@@ -4336,7 +4354,8 @@ and long-lived sessions.
4336
4354
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
4337
4355
  hotkey panel that send selected text or the clipboard to your sessions.
4338
4356
 
4339
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.114.0...HEAD
4357
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.115.0...HEAD
4358
+ [0.115.0]: https://github.com/dm1try/samagotchi/compare/v0.114.0...v0.115.0
4340
4359
  [0.114.0]: https://github.com/dm1try/samagotchi/compare/v0.113.0...v0.114.0
4341
4360
  [0.113.0]: https://github.com/dm1try/samagotchi/compare/v0.112.0...v0.113.0
4342
4361
  [0.112.0]: https://github.com/dm1try/samagotchi/compare/v0.111.1...v0.112.0
data/docs/cli.md CHANGED
@@ -721,8 +721,9 @@ mid-turn too; a change waits for the turn's end. No chip: its reply is where the
721
721
  web shows it. See [Guardrails](guardrails.md#answers-from-a-parent-agent).
722
722
 
723
723
  `/title` shows the session's title (`title: Coordinator`, or without one what names
724
- the session: `title: none (shows the auto title: …)`, the recap's, or `title: none
725
- (shows the first message: …)`); `/title TEXT` names the session (whitespace collapsed,
724
+ the session, read from the same `recap.json` the cards read: `title: none (shows the
725
+ auto title: …)`, `title: none (shows the recap: …)` when its recap has no title, or
726
+ `title: none (shows the first message: …)` with no recap); `/title TEXT` names the session (whitespace collapsed,
726
727
  cut at 80 characters), `/title --clear` drops the name (anything after `--clear`
727
728
  is refused with the usage line). It is saved at once,
728
729
  mid-turn too, and the lists, cards, top bar and tab show it in place of the first
@@ -1130,6 +1131,9 @@ for a note under it. Next to it sit quick replies, by default `Agreed` and
1130
1131
  `Could you please elaborate?`: a click quotes the selection the same way
1131
1132
  with that text already written as the note. It only fills the composer,
1132
1133
  never sends, so you can collect several quotes and edit before sending.
1134
+ On a touch screen (a phone, a tablet) the bar sits just above the composer
1135
+ instead of next to the selection, where the system's own Copy menu and the
1136
+ selection handles would cover it; several pills wrap to a second row.
1133
1137
 
1134
1138
  The list is `web.annotate_presets`, `|`-separated (at most five; a preset
1135
1139
  can't contain `|`; a YAML list works too):
@@ -146,8 +146,9 @@ the top and in `pending_question` / `pending_card`, strings in `last_turn`), `no
146
146
  cleaned up, and pushed only while still open. A failed or done turn has nothing to answer.
147
147
  - `Attention.closed_keys(prev, next)`: the question answered, replaced, newly relayed; the card resolved or
148
148
  replaced; both when the session is gone.
149
- - `Attention.text(summary, reason)`: notify.js `attentionText`'s words: the session's title, first preview or last
150
- prompt clipped to 61, and "needs approval" / "needs an answer" / "hit its step limit" / "needs you" / "turn
149
+ - `Attention.text(summary, reason)`: notify.js `attentionText`'s words: the session's name as its card has it
150
+ (title, auto title, the recap's first sentence, first preview, last prompt — the same order `format.js cardName`
151
+ uses) clipped to 61, and "needs approval" / "needs an answer" / "hit its step limit" / "needs you" / "turn
151
152
  failed" / "done in N s" (`last_turn["seconds"]` rounded). Never the question or the command.
152
153
 
153
154
  `Notifier#call(event)` is a `SessionHub` sink. **The hub calls it under its Monitor**, from the tick thread and also
data/docs/sessions.md CHANGED
@@ -237,7 +237,7 @@ chi send --new --continues last:3fa2 -m "new day" # the next link of 3fa2's
237
237
 
238
238
  ## Session titles
239
239
 
240
- A session is named by its first message until you give it a title: `/title Coordinator` (in the REPL, an attached terminal, the web composer or `chi send -m "/title Coordinator" ID`; a parent agent may name its delegates the same way). The title is saved in the session file (`title`), whitespace collapsed and cut at 80 characters, and runs mid-turn too, at once. `/title` alone shows it (`title: none (shows the auto title: Parser tab handling)` or `title: none (shows the first message: hey chi …)` without one); `/title --clear` drops it, and the first message names the session again. A session whose first line was the command itself (`chi send --new -m "/title X"`) takes its preview from its first real message.
240
+ A session is named by its first message until you give it a title: `/title Coordinator` (in the REPL, an attached terminal, the web composer or `chi send -m "/title Coordinator" ID`; a parent agent may name its delegates the same way). The title is saved in the session file (`title`), whitespace collapsed and cut at 80 characters, and runs mid-turn too, at once. `/title` alone shows it, and without one says what the card shows — `title: none (shows the auto title: Parser tab handling)`, `title: none (shows the recap: Fixing the login page.)` when the recap has no title, or `title: none (shows the first message: hey chi …)` with no recap; `/title --clear` drops the title, and what the lists show comes back. A session whose first line was the command itself (`chi send --new -m "/title X"`) takes its preview from its first real message.
241
241
 
242
242
  Without a title, a session is named by its **auto title** once its recap has one, else by its recap's first sentence (see [Session recap](cli.md#session-recap); its subject phrase stripped), else by its first message: on its card (which leaves out its recap line when the name is that same sentence), the families and chain rows, the top bar and the browser tab, the delete confirm and the search, the tab's and the phone's notifications, and `chi sessions list` (plain and `--live`; json has `auto_title`, tsv and json's `desc` keep the first message). The auto title is 2-6 words the recap's model writes on the line before the recap, in the same request (`Title: …`, split off by `RecapTitle`; the recap text never holds it). It is saved in the session's `recap.json` (`title`), so it goes when a stale recap is dropped; each later recap is shown the earlier title and keeps it unless the focus moved, and a reply without a usable one (not 2-6 words, or naming the user or the assistant) keeps the earlier title. A hand title always wins and is never overwritten. With the recap off no new auto title is written; a saved one still shows. The auto title is for display only: never in a model request (the `list_sessions` tool, `ctx.sessions`, a parent's children, `chi broadcast` keep the title, else the first message), nor on the terminal's status row, nor on a participant's page (the title or the review's subjects).
243
243
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: samagotchi-system
3
- version: 0.114.0
3
+ version: 0.115.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,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "recap_store"
4
+
3
5
  module Samagotchi
4
6
  # /title, the session's own name (Session#title): with no argument, the
5
7
  # title and what shows without one; `/title TEXT` names the session
@@ -49,13 +51,20 @@ module Samagotchi
49
51
  private
50
52
 
51
53
  # "title: Coordinator", "title: none (shows the auto title: Parser tab
52
- # handling)" (the recap's, RecapTitle), or "title: none (shows the
53
- # first message: hey chi …)"
54
+ # handling)" (the recap's title, RecapTitle), "title: none (shows the
55
+ # recap: Fixing the login page.)" (its first sentence, when the recap
56
+ # has no title), or "title: none (shows the first message: hey chi …)".
57
+ # The name comes from the same read the cards and `chi sessions list`
58
+ # use — RecapStore.card on the session's recap.json — so this line can't
59
+ # disagree with what the lists show, including when the recap is off for
60
+ # this session and a title saved earlier still shows (docs/sessions.md,
61
+ # "Session titles").
54
62
  def listing(session)
55
63
  return "title: #{session.title}" if session.title
56
64
 
57
- auto = @engine.recap&.state&.dig(:title)
58
- return "title: none (shows the auto title: #{auto})" if auto
65
+ card = RecapStore.card(Session.session_dir(session.id, state_dir: @engine.session_state_dir))
66
+ return "title: none (shows the auto title: #{card.title})" if card.title
67
+ return "title: none (shows the recap: #{card.preview})" if card.preview
59
68
 
60
69
  preview = session.first_preview.to_s
61
70
  "title: none (shows #{preview.empty? ? "the first message" : "the first message: #{preview}"})"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Samagotchi
4
- VERSION = "0.114.0"
4
+ VERSION = "0.115.0"
5
5
  # Sent as the User-Agent of every HTTP request chi makes.
6
6
  USER_AGENT = "chi/#{VERSION}"
7
7
  end
@@ -11,6 +11,10 @@
11
11
  // its command line with the selection (annotate_actions.js), at most 3
12
12
  // inline, the rest behind "⋯".
13
13
  //
14
+ // On a touch screen the bar docks above the composer instead: the OS's own
15
+ // selection menu (Copy, Look Up…) and the handles sit next to the selection,
16
+ // right where the bar would float.
17
+ //
14
18
  // The quoting rules are annotations.js'; this is the bar.
15
19
 
16
20
  import { annotateBarLeft, annotationSource, quoteBlock } from "./annotations.js";
@@ -32,12 +36,15 @@ import { actionEnabled, actionTitle, sameActions, selectionFields, splitInline }
32
36
  // @param onAction (action, fields) runs with a clicked pill's action and the
33
37
  // selection's {quote, text, source}; the selection is cleared and the bar
34
38
  // hidden first
39
+ // @param dock () => the element the bar docks above on a touch screen
40
+ // (the composer's dock); without one, or when it isn't laid out, the
41
+ // bar docks at the bottom of the window
35
42
  // @param doc, win the page's document and window
36
43
  // @return {schedule(), hide(), el}; schedule() re-reads the selection on the
37
44
  // next frame (call it on scroll)
38
45
  export function createAnnotateBar({
39
46
  presets, canAnnotate, roots, isLive, onQuote, actions = () => [], running = () => false, onAction = () => {},
40
- doc = globalThis.document, win = globalThis.window,
47
+ dock = () => null, doc = globalThis.document, win = globalThis.window,
41
48
  }) {
42
49
  const bar = doc.createElement("div");
43
50
  bar.id = "annotateBar";
@@ -65,6 +72,7 @@ export function createAnnotateBar({
65
72
  group.hidden = true;
66
73
  bar.appendChild(group);
67
74
  doc.body.appendChild(bar);
75
+ const touch = win.matchMedia?.("(pointer: coarse)");
68
76
  let pendingQuote = null;
69
77
  let pendingFields = null;
70
78
  let drawn = [];
@@ -139,9 +147,11 @@ export function createAnnotateBar({
139
147
  const list = actions() || [];
140
148
  if (!sameActions(list, drawn)) drawActions(list);
141
149
  markActions();
150
+ bar.hidden = false;
151
+ if (touch?.matches) return placeDocked();
152
+ bar.classList?.remove("docked");
142
153
  const rects = clipped.getClientRects();
143
154
  const rect = rects.length ? rects[rects.length - 1] : clipped.getBoundingClientRect();
144
- bar.hidden = false;
145
155
  // Measured at the left edge: a fixed element's shrink-to-fit width is
146
156
  // capped by what's right of it, so at the last (right) spot it would
147
157
  // measure wrapped.
@@ -165,6 +175,16 @@ export function createAnnotateBar({
165
175
  }
166
176
  }
167
177
 
178
+ // Above the composer, across the window: away from the selection.
179
+ function placeDocked() {
180
+ bar.classList?.add("docked");
181
+ bar.style.left = "8px";
182
+ const h = bar.offsetHeight;
183
+ const box = dock()?.getBoundingClientRect();
184
+ const above = box && box.height ? box.top : win.innerHeight;
185
+ bar.style.top = `${Math.max(above - h - 8, 8)}px`;
186
+ }
187
+
168
188
  let raf = false;
169
189
  function schedule() {
170
190
  if (raf) return;
@@ -1860,6 +1860,7 @@ const annotateBar = createAnnotateBar({
1860
1860
  if (action.send) runSessionCommand(line);
1861
1861
  else fillComposer(composerWithAction(promptEl.value, action, line));
1862
1862
  },
1863
+ dock: () => $("#dock"),
1863
1864
  });
1864
1865
  historyEl.addEventListener("scroll", annotateBar.schedule);
1865
1866
  // A command bubble's Apply (CacheConfirm): sends the change's --yes line
@@ -550,6 +550,8 @@ button.task-stop:hover:not(:disabled){background:var(--danger-bg-dim);filter:non
550
550
  #annotateBar .annotate-action:disabled{opacity:.5;cursor:not-allowed}
551
551
  #annotateBar .annotate-action[data-sends]::after{content:"↗";margin-left:4px;opacity:.75}
552
552
  #annotateBar .annotate-more-list{position:absolute;top:calc(100% + 4px);right:0;display:flex;flex-direction:column;align-items:flex-end;gap:4px}
553
+ #annotateBar.docked{right:8px;justify-content:center}
554
+ @media (pointer:coarse){#annotateBar button{font-size:14px;padding:8px 14px}}
553
555
  #annotateBar .annotate-more-list[hidden]{display:none}
554
556
  #annotateBar .annotate-preset{background:var(--bg-raised);color:var(--text-2);border:1px solid var(--border-strong);padding:3px 10px;max-width:32ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
555
557
  /* Copy buttons (copy.js): top-right in an answer and a code block, left of a prompt (its right edge is the page's); shown on hover or focus, always (dim) on touch. They hold no text: "copied ✓" is CSS content. */
@@ -150,9 +150,11 @@ export function trackAttention(state, type, data = {}) {
150
150
  }
151
151
 
152
152
  // The notification's words: a short title (the session) and the reason.
153
- // No question or answer text (it would show on a lock screen).
153
+ // No question or answer text (it would show on a lock screen). The name is
154
+ // the card's (format.js cardName): title, auto title, the recap's first
155
+ // sentence, first message, last prompt.
154
156
  export function attentionText(session, attention) {
155
- const name = String(session?.title || session?.auto_title || session?.first_preview || session?.last_prompt || "chi session").trim();
157
+ const name = String(session?.title || session?.auto_title || session?.recap || session?.first_preview || session?.last_prompt || "chi session").trim();
156
158
  const title = clip(name, 61);
157
159
  const seconds = Math.round(Number(session?.last_turn?.seconds) || 0);
158
160
  const body = {
@@ -126,9 +126,11 @@ module Samagotchi
126
126
 
127
127
  # notify.js attentionText: the session's name (clipped) and the
128
128
  # reason, never the question or the command (a lock screen shows it).
129
+ # The name is the card's (format.js cardName): title, auto title, the
130
+ # recap's first sentence the server already cut, first message, last prompt.
129
131
  # @return [Array(String, String)] title, body
130
132
  def text(summary, reason)
131
- name = (summary && [summary[:title], summary[:auto_title], summary[:first_preview], summary[:last_prompt]]
133
+ name = (summary && [summary[:title], summary[:auto_title], summary[:recap], summary[:first_preview], summary[:last_prompt]]
132
134
  .find { |v| present?(v) }) ||
133
135
  "chi session"
134
136
  body = reason == "done" ? "done in #{seconds(summary&.dig(:last_turn) || {}).round} s" : BODIES.fetch(reason, "")
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.114.0
4
+ version: 0.115.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov