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 +4 -4
- data/CHANGELOG.md +20 -1
- data/docs/cli.md +6 -2
- data/docs/internals/web-push.md +3 -2
- data/docs/sessions.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/title_command.rb +13 -4
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/public/annotate_bar.js +22 -2
- data/lib/samagotchi/web/public/app.js +1 -0
- data/lib/samagotchi/web/public/index.html +2 -0
- data/lib/samagotchi/web/public/notify.js +4 -2
- data/lib/samagotchi/web/push/attention.rb +3 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1829b38f073eadf8af34d8b4955f4d69d0e6aa1983acdf5e26a99daef4b629a4
|
|
4
|
+
data.tar.gz: 19ee9838f04a908813a8dee016cea758acaa3ac78fb0df3902b0941ff222500a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
|
725
|
-
(shows the
|
|
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):
|
data/docs/internals/web-push.md
CHANGED
|
@@ -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
|
|
150
|
-
|
|
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
|
|
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,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),
|
|
53
|
-
#
|
|
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
|
-
|
|
58
|
-
return "title: none (shows the auto title: #{
|
|
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}"})"
|
data/lib/samagotchi/version.rb
CHANGED
|
@@ -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, "")
|