samagotchi 0.95.0 → 0.97.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 +39 -1
- data/bin/chi +3 -0
- data/docs/architecture.md +2 -0
- data/docs/bundle-authoring.md +2 -1
- data/docs/cli.md +13 -1
- data/docs/configuration.md +13 -0
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/bundles.md +1 -1
- data/docs/internals/engine.md +3 -1
- data/docs/memory.md +43 -3
- data/docs/plugins.md +4 -3
- data/docs/sessions.md +8 -2
- data/lib/samagotchi/bundle_command/check.rb +5 -3
- data/lib/samagotchi/bundle_command/try/run.rb +19 -4
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/children_status.rb +1 -1
- data/lib/samagotchi/engine.rb +49 -38
- data/lib/samagotchi/llm/errors.rb +100 -2
- data/lib/samagotchi/memory_bundle/profile.rb +14 -4
- data/lib/samagotchi/memory_command.rb +237 -0
- data/lib/samagotchi/plugin/api.rb +7 -3
- data/lib/samagotchi/plugin/sessions.rb +4 -3
- data/lib/samagotchi/send_command.rb +12 -12
- data/lib/samagotchi/session.rb +13 -1
- data/lib/samagotchi/session_commands.rb +13 -0
- data/lib/samagotchi/session_continue.rb +8 -8
- data/lib/samagotchi/session_delete_command.rb +1 -1
- data/lib/samagotchi/session_inbox.rb +29 -0
- data/lib/samagotchi/session_manager.rb +24 -7
- data/lib/samagotchi/sessions_command.rb +8 -6
- data/lib/samagotchi/terminal_ui/attached_loop.rb +2 -0
- data/lib/samagotchi/terminal_ui/status_row.rb +24 -3
- data/lib/samagotchi/terminal_ui.rb +1 -0
- data/lib/samagotchi/title_command.rb +61 -0
- data/lib/samagotchi/tools/memory.rb +1 -1
- data/lib/samagotchi/turn_checkpoint.rb +6 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +4 -4
- data/lib/samagotchi/web/public/app.js +12 -0
- data/lib/samagotchi/web/public/chains.js +21 -8
- data/lib/samagotchi/web/public/format.js +4 -3
- data/lib/samagotchi/web/public/index.html +7 -0
- data/lib/samagotchi/web/public/info_bar_model.js +2 -0
- data/lib/samagotchi/web/public/session_rename.js +86 -0
- data/lib/samagotchi/web/public/session_state.js +4 -2
- data/lib/samagotchi/web/session_summary.rb +3 -0
- data/lib/samagotchi/worker.rb +24 -7
- data/lib/samagotchi/worker_idle_exit.rb +16 -8
- metadata +4 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// The info bar's ✎ (Rename this session): a small input in its place,
|
|
2
|
+
// prefilled with the session's title ("" for none). Enter sends
|
|
3
|
+
// `/title TEXT` (blank: `/title --clear`) through the chips' command path
|
|
4
|
+
// (composer.js run: the reply shows as a command bubble, a refusal in the
|
|
5
|
+
// failed bubble, the composer untouched); leaving the field saves the same
|
|
6
|
+
// way (a phone's keyboard dismissed keeps what was typed). Esc, or opening
|
|
7
|
+
// another session, closes it unsent. The cards follow from the saved file
|
|
8
|
+
// (the hub's scan), as for any title.
|
|
9
|
+
import { normalize } from "./format.js";
|
|
10
|
+
|
|
11
|
+
// As long as a title may be (TitleCommand::LIMIT).
|
|
12
|
+
const TITLE_CHARS = 80;
|
|
13
|
+
|
|
14
|
+
// The line the input's +text+ sends.
|
|
15
|
+
export function titleLine(text) {
|
|
16
|
+
const title = normalize(text);
|
|
17
|
+
return title ? `/title ${title}` : "/title --clear";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// @param button the ✎ button (it hides while the input shows)
|
|
21
|
+
// @param session () => the open session's id, or null
|
|
22
|
+
// @param titleOf (id) => that session's title, or null
|
|
23
|
+
// @param run (line) => composer.js run's promise
|
|
24
|
+
export function createRename({ button, session, titleOf, run }) {
|
|
25
|
+
let input = null;
|
|
26
|
+
let openFor = null;
|
|
27
|
+
|
|
28
|
+
function close() {
|
|
29
|
+
if (!input) return;
|
|
30
|
+
const el = input;
|
|
31
|
+
input = null;
|
|
32
|
+
openFor = null;
|
|
33
|
+
el.remove();
|
|
34
|
+
button.hidden = false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Enter's and blur's: once (closing it blurs it again), and only to the
|
|
38
|
+
// session it was opened for.
|
|
39
|
+
function submit() {
|
|
40
|
+
if (!input) return null;
|
|
41
|
+
const id = openFor;
|
|
42
|
+
const before = normalize(input.dataset.before);
|
|
43
|
+
const text = input.value;
|
|
44
|
+
close();
|
|
45
|
+
if (!id || session() !== id || normalize(text) === before) return null;
|
|
46
|
+
return run(titleLine(text));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function open() {
|
|
50
|
+
const id = session();
|
|
51
|
+
if (input || !id) return;
|
|
52
|
+
const el = document.createElement("input");
|
|
53
|
+
el.type = "text";
|
|
54
|
+
el.className = "rename-input";
|
|
55
|
+
el.maxLength = TITLE_CHARS;
|
|
56
|
+
el.placeholder = "Session title";
|
|
57
|
+
el.setAttribute("aria-label", "Session title (Enter saves, empty clears, Esc cancels)");
|
|
58
|
+
el.value = titleOf(id) || "";
|
|
59
|
+
el.dataset.before = el.value;
|
|
60
|
+
el.addEventListener("keydown", (e) => {
|
|
61
|
+
if (e.key === "Enter") {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
submit();
|
|
64
|
+
} else if (e.key === "Escape") {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
close();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
el.addEventListener("blur", () => submit());
|
|
70
|
+
input = el;
|
|
71
|
+
openFor = id;
|
|
72
|
+
button.hidden = true;
|
|
73
|
+
button.after(el);
|
|
74
|
+
el.focus();
|
|
75
|
+
el.select();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The info bar redrew for +id+ (app.js updateInfoBar): an input opened
|
|
79
|
+
// for another session closes.
|
|
80
|
+
function follow(id) {
|
|
81
|
+
if (input && openFor !== id) close();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
button.addEventListener("click", () => open());
|
|
85
|
+
return { open, close, follow, isOpen: () => !!input };
|
|
86
|
+
}
|
|
@@ -118,10 +118,12 @@ export function sessionStateFrom(data) {
|
|
|
118
118
|
return state;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
// The info bar's title: the
|
|
122
|
-
// messages, else the card's preview, else
|
|
121
|
+
// The info bar's title: the session's own title (/title), else the first
|
|
122
|
+
// user message when the payload has messages, else the card's preview, else
|
|
123
|
+
// the first prompt of its history.
|
|
123
124
|
function sessionFirstPreview(data) {
|
|
124
125
|
const s = data.session;
|
|
126
|
+
if (s.title) return firstPreview(s.title);
|
|
125
127
|
let preview = s.first_preview || previewForCard(s);
|
|
126
128
|
if (data.messages && data.messages.length) {
|
|
127
129
|
const first = data.messages.find((m) => m.role === "user");
|
|
@@ -67,6 +67,9 @@ module Samagotchi
|
|
|
67
67
|
# chain), else nil: the lists draw chains from it.
|
|
68
68
|
continues: session.continues,
|
|
69
69
|
first_preview: first_preview_for(session),
|
|
70
|
+
# The session's own name (/title), else nil: the pages show it in
|
|
71
|
+
# place of the first message.
|
|
72
|
+
title: session.title,
|
|
70
73
|
owner: owner&.kind,
|
|
71
74
|
# The saved recap's first sentence, for the session card.
|
|
72
75
|
recap: RecapStore.preview(session_dir),
|
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -237,6 +237,7 @@ module Samagotchi
|
|
|
237
237
|
@config_restart_unsubscribe = ConfigFile.watch_restart_settings!(process: :worker) do |message|
|
|
238
238
|
@engine.announce(type: :hook_notice, hook: "config", text: message, level: :warn, between_turns: true)
|
|
239
239
|
end
|
|
240
|
+
prune_tombstones
|
|
240
241
|
settle_interrupted_turn
|
|
241
242
|
# Before the Bridge serves anything: a UI joining a resumed worker's
|
|
242
243
|
# stream gets the session's history and status in its snapshot, not
|
|
@@ -491,6 +492,15 @@ module Samagotchi
|
|
|
491
492
|
save_or_log(:interrupted_turn) { save_session }
|
|
492
493
|
end
|
|
493
494
|
|
|
495
|
+
# The per-input tombstones (removed, settled) older than
|
|
496
|
+
# SessionInbox::TOMBSTONE_MAX_AGE, once per worker start: they only go
|
|
497
|
+
# with the session dir otherwise.
|
|
498
|
+
def prune_tombstones
|
|
499
|
+
removed = SessionInbox.prune_removed(@session_dir)
|
|
500
|
+
settled = TurnCheckpoint.prune_settled(@session_dir)
|
|
501
|
+
Log.info(:worker, "tombstones_pruned", removed: removed, settled: settled) if (removed + settled).positive?
|
|
502
|
+
end
|
|
503
|
+
|
|
494
504
|
# A saved parent_answers that isn't a level reads as hard on every
|
|
495
505
|
# answer without a word; said once here, as the session loads.
|
|
496
506
|
def announce_session_warnings
|
|
@@ -933,8 +943,11 @@ module Samagotchi
|
|
|
933
943
|
# finishes) comes after its command_queued. The cards it shows go out at
|
|
934
944
|
# once (btw's "thinking…" card), after its command_queued, which the
|
|
935
945
|
# UIs draw its line at (Engine#running_anytime). Its handler reads
|
|
936
|
-
# copies (ctx.messages) and shows things through ctx only
|
|
937
|
-
#
|
|
946
|
+
# copies (ctx.messages) and shows things through ctx only. One saves:
|
|
947
|
+
# /title, a field no turn writes, through Session#save, which takes the
|
|
948
|
+
# session's lock and writes a running turn's checkpoint (as
|
|
949
|
+
# WorkerSignals' save off the turn thread does), so the turn's own saves
|
|
950
|
+
# keep it.
|
|
938
951
|
def start_anytime_command(command)
|
|
939
952
|
@engine.spawn_anytime do
|
|
940
953
|
result = command_result(command) { @engine.running_anytime { @commands.run(command.line, confirm: confirm?(command)) } }
|
|
@@ -955,6 +968,8 @@ module Samagotchi
|
|
|
955
968
|
event = { type: :command_ran, **command.event_fields, status: status, output: text[0, COMMAND_OUTPUT_LIMIT],
|
|
956
969
|
changed: changed.map(&:to_s), model_name: @engine.effective_model_name }
|
|
957
970
|
event[:anytime] = true if anytime
|
|
971
|
+
# The session's title after /title (nil: cleared), for the TUIs' row.
|
|
972
|
+
event[:title] = @engine.session&.title if changed.include?(:title)
|
|
958
973
|
# Its line was shown at its command_queued (waits: turn_end).
|
|
959
974
|
event[:queued] = true if command.waits_for_turn_end?
|
|
960
975
|
# The chips the command moved (SessionSetting#changed_by): what the next
|
|
@@ -1256,12 +1271,14 @@ module Samagotchi
|
|
|
1256
1271
|
# The Bridge serves before the idle-exit policy exists.
|
|
1257
1272
|
return WorkerIdleExit::Hold.new(reason: :starting) unless @idle_exit
|
|
1258
1273
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1274
|
+
if restart
|
|
1275
|
+
hold = @idle_exit.hold_for_restart
|
|
1276
|
+
return WorkerIdleExit::Hold.new(reason: hold) if hold
|
|
1277
|
+
else
|
|
1278
|
+
# /exit names the tasks it waits for (D3), from the one look that found them.
|
|
1279
|
+
hold = @idle_exit.request_hold(requester: client_id, hint: TASK_STOP_HINT)
|
|
1280
|
+
return hold if hold
|
|
1263
1281
|
end
|
|
1264
|
-
return WorkerIdleExit::Hold.new(reason: hold) if hold
|
|
1265
1282
|
|
|
1266
1283
|
@exit_requested = true
|
|
1267
1284
|
@exit_requested_by = client_id
|
|
@@ -94,15 +94,23 @@ module Samagotchi
|
|
|
94
94
|
# still be open, untagged)
|
|
95
95
|
# @return [Symbol, nil] :turn_running, :input_queued, :continue_offered,
|
|
96
96
|
# :client_connected, :reminders or :background_tasks
|
|
97
|
-
def hold_for_request(requester:, streams: true)
|
|
98
|
-
return :turn_running if @engine.turn_running?
|
|
99
|
-
return :input_queued if @input_pending.call
|
|
100
|
-
return :continue_offered if @awaiting_continue.call
|
|
101
|
-
return :client_connected if streams && @bridge && @bridge.open_streams_except(requester).positive?
|
|
102
|
-
return :reminders if @engine.reminder_store&.any?
|
|
103
|
-
return :background_tasks if @watched_running.call.any?
|
|
97
|
+
def hold_for_request(requester:, streams: true) = request_hold(requester: requester, streams: streams)&.reason
|
|
104
98
|
|
|
105
|
-
|
|
99
|
+
# #hold_for_request as a Hold: for :background_tasks, the watched tasks
|
|
100
|
+
# still running, read once (TaskWatch#running looks at each task's
|
|
101
|
+
# process), with +hint+.
|
|
102
|
+
# @return [Hold, nil]
|
|
103
|
+
def request_hold(requester:, streams: true, hint: nil)
|
|
104
|
+
reason = if @engine.turn_running? then :turn_running
|
|
105
|
+
elsif @input_pending.call then :input_queued
|
|
106
|
+
elsif @awaiting_continue.call then :continue_offered
|
|
107
|
+
elsif streams && @bridge && @bridge.open_streams_except(requester).positive? then :client_connected
|
|
108
|
+
elsif @engine.reminder_store&.any? then :reminders
|
|
109
|
+
end
|
|
110
|
+
return Hold.new(reason: reason) if reason
|
|
111
|
+
|
|
112
|
+
running = @watched_running.call
|
|
113
|
+
running.any? ? Hold.new(reason: :background_tasks, tasks: running, hint: hint) : nil
|
|
106
114
|
end
|
|
107
115
|
|
|
108
116
|
# What keeps the worker from restarting now (a client asked: POST /exit
|
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.
|
|
4
|
+
version: 0.97.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -437,6 +437,7 @@ files:
|
|
|
437
437
|
- lib/samagotchi/memory_bundle/system_bundle.rb
|
|
438
438
|
- lib/samagotchi/memory_bundle/trash.rb
|
|
439
439
|
- lib/samagotchi/memory_bundle/uninstaller.rb
|
|
440
|
+
- lib/samagotchi/memory_command.rb
|
|
440
441
|
- lib/samagotchi/memory_paths.rb
|
|
441
442
|
- lib/samagotchi/message_parts.rb
|
|
442
443
|
- lib/samagotchi/model_catalog.rb
|
|
@@ -582,6 +583,7 @@ files:
|
|
|
582
583
|
- lib/samagotchi/thinking_tails.rb
|
|
583
584
|
- lib/samagotchi/thinking_ticker.rb
|
|
584
585
|
- lib/samagotchi/thought_stream_splitter.rb
|
|
586
|
+
- lib/samagotchi/title_command.rb
|
|
585
587
|
- lib/samagotchi/token_usage.rb
|
|
586
588
|
- lib/samagotchi/tool_activity.rb
|
|
587
589
|
- lib/samagotchi/tool_call_parser.rb
|
|
@@ -717,6 +719,7 @@ files:
|
|
|
717
719
|
- lib/samagotchi/web/public/send_turn.js
|
|
718
720
|
- lib/samagotchi/web/public/sentences.js
|
|
719
721
|
- lib/samagotchi/web/public/session_actions.js
|
|
722
|
+
- lib/samagotchi/web/public/session_rename.js
|
|
720
723
|
- lib/samagotchi/web/public/session_state.js
|
|
721
724
|
- lib/samagotchi/web/public/session_title.js
|
|
722
725
|
- lib/samagotchi/web/public/sessions_list.js
|