samagotchi 0.154.0 → 0.154.1
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 +15 -1
- data/docs/cli.md +9 -7
- data/docs/internals/bridge.md +18 -5
- data/docs/internals/engine.md +1 -1
- data/docs/internals/prompt-caching.md +3 -1
- data/docs/internals/web-frontend.md +2 -2
- data/docs/sessions.md +1 -1
- data/docs/testing.md +15 -0
- data/lib/samagotchi/answer_text.rb +1 -2
- data/lib/samagotchi/bridge/card_places.rb +56 -7
- data/lib/samagotchi/bridge/card_store.rb +24 -9
- data/lib/samagotchi/bridge.rb +9 -0
- data/lib/samagotchi/bridge_client.rb +9 -5
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundles/btw/manifest.yml +2 -2
- data/lib/samagotchi/bundles/btw/plugin.rb +1 -1
- data/lib/samagotchi/bundles/coordinator/manifest.yml +2 -2
- data/lib/samagotchi/bundles/coordinator/plugin.rb +3 -2
- data/lib/samagotchi/bundles/github-pr/manifest.yml +2 -2
- data/lib/samagotchi/bundles/github-pr/plugin.rb +3 -3
- data/lib/samagotchi/bundles/skills/manifest.yml +2 -2
- data/lib/samagotchi/bundles/skills/plugin.rb +3 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +1 -1
- data/lib/samagotchi/engine_snapshots.rb +9 -8
- data/lib/samagotchi/idle_recap.rb +3 -1
- data/lib/samagotchi/kernel_loop.rb +4 -3
- data/lib/samagotchi/llm/chat_loop.rb +3 -1
- data/lib/samagotchi/session_manager.rb +8 -13
- data/lib/samagotchi/session_retention.rb +24 -4
- data/lib/samagotchi/steer.rb +12 -1
- data/lib/samagotchi/stream_event.rb +22 -0
- data/lib/samagotchi/system_prompt.rb +52 -36
- data/lib/samagotchi/terminal_ui/attached_view.rb +0 -9
- data/lib/samagotchi/terminal_ui/input_support.rb +16 -15
- data/lib/samagotchi/terminal_ui/join_replay.rb +16 -12
- data/lib/samagotchi/terminal_ui/reline_seam.rb +0 -10
- data/lib/samagotchi/terminal_ui/status_row.rb +0 -7
- data/lib/samagotchi/tool_runner.rb +2 -1
- data/lib/samagotchi/turn_flow.rb +7 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +3 -1
- data/lib/samagotchi/web/message_display.rb +7 -0
- data/lib/samagotchi/web/public/card_stamps.js +27 -0
- data/lib/samagotchi/web/public/cards_place.js +5 -3
- data/lib/samagotchi/web/public/composer_mode.js +1 -1
- data/lib/samagotchi/web/public/format.js +3 -2
- data/lib/samagotchi/web/public/turn_html.js +8 -4
- data/lib/samagotchi/web/session_access_routes.rb +8 -0
- data/lib/samagotchi/web/session_show.rb +4 -0
- data/lib/samagotchi/worker.rb +8 -5
- data/lib/samagotchi.rb +0 -1
- metadata +3 -2
- data/lib/samagotchi/pending_input_queue.rb +0 -55
data/lib/samagotchi/turn_flow.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "securerandom"
|
|
3
4
|
require_relative "output_formatter"
|
|
4
5
|
require_relative "turn_note"
|
|
5
6
|
require_relative "context_note"
|
|
@@ -175,9 +176,13 @@ module Samagotchi
|
|
|
175
176
|
# Answer the offer with no (Stop): the interrupted turn stays, as
|
|
176
177
|
# after a Ctrl-C or a new prompt (!rollback still erases it), and the
|
|
177
178
|
# model reads that it wasn't continued: a note, or the user's reason
|
|
178
|
-
# (with a summary of that turn).
|
|
179
|
+
# (with a summary of that turn). The reason's line opens a turn on the
|
|
180
|
+
# page (Steer.turn_opening?) that no turn runs: it carries an
|
|
181
|
+
# +opening_id+ of its own, never a turn_id (a claim link, a fold, Revise
|
|
182
|
+
# and Apply read a turn_id as a turn), so a card shown after it finds its
|
|
183
|
+
# place by that id (Bridge::CardPlaces#opening_of).
|
|
179
184
|
def abort_continue!(reason: nil)
|
|
180
|
-
message = reason ? { role: "user", content: reason_message(reason) } : TurnNote.not_continued
|
|
185
|
+
message = reason ? { role: "user", content: reason_message(reason), opening_id: SecureRandom.uuid } : TurnNote.not_continued
|
|
181
186
|
@engine.append_messages([message])
|
|
182
187
|
@offer = nil
|
|
183
188
|
end
|
data/lib/samagotchi/version.rb
CHANGED
data/lib/samagotchi/web/app.rb
CHANGED
|
@@ -1558,10 +1558,12 @@ module Samagotchi
|
|
|
1558
1558
|
def handle_stream(req, id)
|
|
1559
1559
|
# Validate session exists
|
|
1560
1560
|
begin
|
|
1561
|
-
@session_class.load(id, state_dir: default_state_dir)
|
|
1561
|
+
session = @session_class.load(id, state_dir: default_state_dir)
|
|
1562
1562
|
rescue ArgumentError
|
|
1563
1563
|
return error_response(404, "not_found", "Session not found: #{id}")
|
|
1564
1564
|
end
|
|
1565
|
+
# Its terminal's alone: a tab's stream would keep its worker up.
|
|
1566
|
+
return scratch_refused(id) if session.scratch
|
|
1565
1567
|
|
|
1566
1568
|
link = worker_link(id)
|
|
1567
1569
|
if link.is_a?(WorkerLink::Starting)
|
|
@@ -134,6 +134,9 @@ module Samagotchi
|
|
|
134
134
|
end
|
|
135
135
|
next if role == "system"
|
|
136
136
|
next if role == "tool_response"
|
|
137
|
+
# KernelLoop's recovery line: a row of its turn the page showed as
|
|
138
|
+
# nothing live (no turn opens with it, Steer.recovery?).
|
|
139
|
+
next if Samagotchi::Steer.recovery?(m)
|
|
137
140
|
|
|
138
141
|
stripped = Samagotchi::OutputFormatter.strip_markup(content)
|
|
139
142
|
did = parts && %w[model assistant].include?(role) ? message_parts(list, index, cwd, marks) : nil
|
|
@@ -165,6 +168,10 @@ module Samagotchi
|
|
|
165
168
|
# record by this, not by its place.
|
|
166
169
|
turn_id = m[:turn_id] || m["turn_id"]
|
|
167
170
|
message[:turn_id] = turn_id.to_s if norm_role == "user" && turn_id
|
|
171
|
+
# An opening no turn ran (TurnFlow#abort_continue!'s reason): its own
|
|
172
|
+
# id, which a card's stamp names (Bridge::CardPlaces#opening_of).
|
|
173
|
+
opening_id = m[:opening_id] || m["opening_id"]
|
|
174
|
+
message[:opening_id] = opening_id.to_s if norm_role == "user" && opening_id && !turn_id && !message[:merged]
|
|
168
175
|
# The turn it revised (a Revise stack, Engine#stamp_opening), and
|
|
169
176
|
# whether it was a Verify turn (the page offers no Revise on its
|
|
170
177
|
# answer).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Where a snapshot's card or notice goes by its stamp: the id of the turn
|
|
2
|
+
// opening it comes after (+opening_id+, Bridge::CardPlaces#opening_of: a
|
|
3
|
+
// prompt's turn_id, a wake note's, /continue no's reason line's own id). The
|
|
4
|
+
// page finds that opening's bubble (data-turn-id on a prompt, data-opening-id
|
|
5
|
+
// on a wake note or a reason line, turn_html.js) and the entry goes before
|
|
6
|
+
// the next turn's opening, or at the end. No counting, so the page and the
|
|
7
|
+
// worker can't disagree on which bubbles open turns. An entry with no stamp
|
|
8
|
+
// (an older worker's, one before every opening), or whose opening the page
|
|
9
|
+
// doesn't draw, falls back to its count (card.js cardPlace, turns_since).
|
|
10
|
+
// A new module: the static JS is unversioned (docs/internals/web-frontend.md).
|
|
11
|
+
import { cardPlace } from "./card.js";
|
|
12
|
+
|
|
13
|
+
// The opening bubble +id+ names among the turns' openings (oldest first,
|
|
14
|
+
// bubbles.js turnPromptBubbles), or -1.
|
|
15
|
+
export function stampedOpening(userBubbles, id) {
|
|
16
|
+
if (!id) return -1;
|
|
17
|
+
const stamp = String(id);
|
|
18
|
+
return userBubbles.findIndex((el) => el.dataset?.turnId === stamp || el.dataset?.openingId === stamp);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The element +entry+ goes before: the opening after its stamped one (null:
|
|
22
|
+
// the end), else what its count says (null: the end too).
|
|
23
|
+
export function snapshotPlace(userBubbles, entry) {
|
|
24
|
+
const at = stampedOpening(userBubbles, entry?.opening_id);
|
|
25
|
+
if (at < 0) return cardPlace(userBubbles, entry);
|
|
26
|
+
return userBubbles[at + 1] || null;
|
|
27
|
+
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// above the first turn after them, a finished turn's notice in its step, a
|
|
9
9
|
// finished turn's question resolved where it was asked.
|
|
10
10
|
import { getSession, sendCommand } from "./data.js";
|
|
11
|
-
import {
|
|
11
|
+
import { drawCard, isCard, isNoticeCard, refreshCardBody, turnNoticePlace } from "./card.js";
|
|
12
|
+
import { snapshotPlace } from "./card_stamps.js";
|
|
12
13
|
import { elementOf } from "./turn_html.js";
|
|
13
14
|
import { stepText } from "./turn_block.js";
|
|
14
15
|
import { llmContextNotice, noticeLine } from "./turn_events.js";
|
|
@@ -83,14 +84,15 @@ export function createCardsPlace({
|
|
|
83
84
|
bubbles.placeFollowing(el, before);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
// A snapshot's cards and notices, each above the first turn after it
|
|
87
|
+
// A snapshot's cards and notices, each above the first turn after it (the
|
|
88
|
+
// one after its stamped opening, card_stamps.js; else by its count); a
|
|
87
89
|
// turn's own notice where it showed live (placeTurnNotice). +end+: what
|
|
88
90
|
// closes the history below them (a failed turn's prompt), for those with
|
|
89
91
|
// no turn after them; one shown after that prompt went back stays below it.
|
|
90
92
|
function placeSnapshotCards(entries, { before: end = null } = {}) {
|
|
91
93
|
const users = turnPromptBubbles(historyEl);
|
|
92
94
|
for (const entry of entries) {
|
|
93
|
-
const before =
|
|
95
|
+
const before = snapshotPlace(users, entry) || (entry.after_failed ? null : end);
|
|
94
96
|
if (isCard(entry)) upsertCard(entry, { before });
|
|
95
97
|
else if (entry.type === "question") placeResolvedQuestion(entry, before);
|
|
96
98
|
else if (entry.type === "guardrail_warning") bubbles.showGuardrailWarning(entry.message, entry.label || "guardrails", { before });
|
|
@@ -51,7 +51,7 @@ export function composerMode({ selected, owner, running, narrow }) {
|
|
|
51
51
|
if (tui) {
|
|
52
52
|
return {
|
|
53
53
|
...base,
|
|
54
|
-
placeholder: pick("A terminal chi owns this session ·
|
|
54
|
+
placeholder: pick("A terminal chi owns this session · a one-turn chi --non-interactive run or an older chi's terminal", "A terminal chi owns this session"),
|
|
55
55
|
hint: "", button: "Send", disabled: true,
|
|
56
56
|
};
|
|
57
57
|
}
|
|
@@ -97,13 +97,14 @@ export function deleteConfirmText(session) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// The badge on a session a process holds right now (the list's `owner`):
|
|
100
|
-
// a worker (every UI can send), or a terminal chi
|
|
100
|
+
// a worker (every UI can send), or a terminal chi: a one-turn --non-interactive
|
|
101
|
+
// run or an older chi's terminal, in OwnerLock::TUI_BUSY's words.
|
|
101
102
|
export function ownerBadge(owner, id) {
|
|
102
103
|
if (owner === "worker") {
|
|
103
104
|
return { kind: "worker", text: "live", title: `A worker runs this session; a terminal joins with: chi --attach ${id}` };
|
|
104
105
|
}
|
|
105
106
|
if (owner === "tui") {
|
|
106
|
-
return { kind: "tui", text: "terminal", title: "
|
|
107
|
+
return { kind: "tui", text: "terminal", title: "In use by a one-turn chi --non-interactive run or an older chi's terminal" };
|
|
107
108
|
}
|
|
108
109
|
return null;
|
|
109
110
|
}
|
|
@@ -285,9 +285,11 @@ export function noteBubbleTemplate(note) {
|
|
|
285
285
|
if (note?.objectives) return objectivesEntryTemplate(note, timeTitle(note.at));
|
|
286
286
|
const from = note?.label ? `note from ${note.label}` : "note";
|
|
287
287
|
// A wake turn's note opens that turn (TURN_PROMPT, bubbles.js); a class, not
|
|
288
|
-
// data-turn-id, which marks a claim's anchor (claim_link.js).
|
|
288
|
+
// data-turn-id, which marks a claim's anchor (claim_link.js). Its turn's id
|
|
289
|
+
// is its data-opening-id: a card's stamp finds it (card_stamps.js).
|
|
289
290
|
const cls = note?.turn_start ? "bubble note turn-start" : "bubble note";
|
|
290
|
-
|
|
291
|
+
const opening = note?.turn_start && note?.turn_id ? String(note.turn_id) : nothing;
|
|
292
|
+
return html`<div class=${cls} title=${timeTitle(note?.at)} data-opening-id=${opening}><div class="note-line">${from}</div><div class="note-text">${note?.content ?? ""}</div></div>`;
|
|
291
293
|
}
|
|
292
294
|
|
|
293
295
|
// A plugin's steer (a nudge put into the running turn): one line that
|
|
@@ -311,7 +313,9 @@ export function steerRowTemplate({ source, text } = {}, { bubble = false } = {})
|
|
|
311
313
|
// participant's badge (participant_names.js authorBadge: their name and
|
|
312
314
|
// colour) in place of +label+. A turn's opening carries its Revise stamp
|
|
313
315
|
// (+message.revises+: data-revises, revise_stack.js) and whether it was a
|
|
314
|
-
// Verify turn (+message.verify+: data-verify, no Revise on its answer).
|
|
316
|
+
// Verify turn (+message.verify+: data-verify, no Revise on its answer). An
|
|
317
|
+
// opening no turn ran (/continue no's reason) has +message.opening_id+
|
|
318
|
+
// instead of a turn id: data-opening-id, a card's stamp (card_stamps.js).
|
|
315
319
|
// +message.source+ (a delegate report's "delegate_report") is its
|
|
316
320
|
// data-source; +lineKey+, a merged line's fold key (answer_fold.js:
|
|
317
321
|
// data-line-key, the reload's "<turn id>:input:<place>").
|
|
@@ -320,7 +324,7 @@ export function userBubbleTemplate(message, { label = null, state = null, step =
|
|
|
320
324
|
const from = author
|
|
321
325
|
? html`<span class="origin-label author-badge" title=${authorBadgeTitle(author.name, author.mode)} data-unnamed=${author.unnamed ? "1" : nothing}>${authorBadgeText(author.name, author.mode)}</span>`
|
|
322
326
|
: label ? html`<span class="origin-label">${label}</span>` : nothing;
|
|
323
|
-
return html`<div class=${`bubble user${named ? ` ${named}` : ""}${author ? " by-participant" : ""}`} title=${timeTitle(message?.at)} style=${author ? `--author-color:${author.color}` : nothing} data-author=${author?.author || nothing} data-participant-mode=${author?.mode || nothing} data-user-state=${named || nothing} data-step=${step ? Number(step) : nothing} data-copy-source=${copySource({ ...message, role: "user" })} data-user-content=${key !== null ? key : nothing} data-turn-id=${message?.turn_id || nothing} data-revises=${(message?.turn_id && message?.revises?.turn_id) || nothing} data-verify=${message?.turn_id && message?.verify ? "1" : nothing} data-own=${own ? "1" : nothing} data-enqueued-id=${enqueuedId || nothing} data-delivery=${delivery || nothing} data-source=${message?.source || nothing} data-line-key=${lineKey || nothing}>${from}${userMessageTemplate(message, thumbs)}${stateBadgeTemplate(named, delivery)}</div>`;
|
|
327
|
+
return html`<div class=${`bubble user${named ? ` ${named}` : ""}${author ? " by-participant" : ""}`} title=${timeTitle(message?.at)} style=${author ? `--author-color:${author.color}` : nothing} data-author=${author?.author || nothing} data-participant-mode=${author?.mode || nothing} data-user-state=${named || nothing} data-step=${step ? Number(step) : nothing} data-copy-source=${copySource({ ...message, role: "user" })} data-user-content=${key !== null ? key : nothing} data-turn-id=${message?.turn_id || nothing} data-opening-id=${(!message?.turn_id && message?.opening_id) || nothing} data-revises=${(message?.turn_id && message?.revises?.turn_id) || nothing} data-verify=${message?.turn_id && message?.verify ? "1" : nothing} data-own=${own ? "1" : nothing} data-enqueued-id=${enqueuedId || nothing} data-delivery=${delivery || nothing} data-source=${message?.source || nothing} data-line-key=${lineKey || nothing}>${from}${userMessageTemplate(message, thumbs)}${stateBadgeTemplate(named, delivery)}</div>`;
|
|
324
328
|
}
|
|
325
329
|
|
|
326
330
|
// A user line's text: plain (its quotes set apart), or the server's
|
|
@@ -54,6 +54,14 @@ module Samagotchi
|
|
|
54
54
|
json_response(status, { error: code, detail: detail, message: error.message, **fields }.compact)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
# 409 scratch for a chi scratch session +id+, which only its terminal
|
|
58
|
+
# uses (Session::SCRATCH_REFUSED): the session page's read and stream
|
|
59
|
+
# (a tab opened at its id by hand: the hub never lists it), as
|
|
60
|
+
# POST …/wake refuses it.
|
|
61
|
+
def scratch_refused(id)
|
|
62
|
+
refusal_response(409, "scratch", SessionManager::ScratchSession.new(id), session_id: id)
|
|
63
|
+
end
|
|
64
|
+
|
|
57
65
|
# The body as a Hash ({} when there is none), or nil when it isn't one.
|
|
58
66
|
def body_hash(req)
|
|
59
67
|
raw = request_body(req)
|
|
@@ -40,6 +40,10 @@ module Samagotchi
|
|
|
40
40
|
return handle_brief_read(id) if req.params["brief"] == "1"
|
|
41
41
|
|
|
42
42
|
session = @session_class.load(id, state_dir: default_state_dir)
|
|
43
|
+
# A tab opened at a chi scratch session's id: its terminal's alone.
|
|
44
|
+
# (The light re-reads come after a full read only.)
|
|
45
|
+
return scratch_refused(id) if session.scratch
|
|
46
|
+
|
|
43
47
|
# Read-only preview: selecting a session never spawns a worker.
|
|
44
48
|
# The worker is woken only on POST /turn (handle_turn) via
|
|
45
49
|
# SessionManager.resume_session + write_turn_input. This avoids
|
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -1608,14 +1608,17 @@ module Samagotchi
|
|
|
1608
1608
|
SCRATCH_GONE_AFTER = 2 * BridgeClient::EventStream::REDISCOVER_DELAYS.sum
|
|
1609
1609
|
|
|
1610
1610
|
# Whether this is a `chi scratch` session's worker whose terminal is
|
|
1611
|
-
# gone: no stream
|
|
1612
|
-
#
|
|
1613
|
-
#
|
|
1614
|
-
#
|
|
1611
|
+
# gone: no terminal stream (a tui: client id's) open for
|
|
1612
|
+
# SCRATCH_GONE_AFTER, after a stream was (a killed TUI's closes as its
|
|
1613
|
+
# writer reads the hang-up), or none came within SCRATCH_JOIN_GRACE.
|
|
1614
|
+
# Any other stream (a web tab's, which chi web refuses for a scratch
|
|
1615
|
+
# session anyway) never keeps it. Its terminal deletes it on every way
|
|
1616
|
+
# out it sees; this is for the ones it doesn't (kill -9, a closed
|
|
1617
|
+
# terminal window).
|
|
1615
1618
|
def scratch_unattended?
|
|
1616
1619
|
return false unless @session&.scratch && @bridge
|
|
1617
1620
|
|
|
1618
|
-
if @bridge.
|
|
1621
|
+
if @bridge.open_streams_of(ClientId::TUI_PREFIX).positive?
|
|
1619
1622
|
@unattended_since = nil
|
|
1620
1623
|
return false
|
|
1621
1624
|
end
|
data/lib/samagotchi.rb
CHANGED
|
@@ -7,7 +7,6 @@ require_relative "samagotchi/prompt"
|
|
|
7
7
|
require_relative "samagotchi/client"
|
|
8
8
|
require_relative "samagotchi/debug_log"
|
|
9
9
|
require_relative "samagotchi/delivery"
|
|
10
|
-
require_relative "samagotchi/pending_input_queue"
|
|
11
10
|
require_relative "samagotchi/kernel_loop"
|
|
12
11
|
require_relative "samagotchi/session"
|
|
13
12
|
require_relative "samagotchi/terminal_ui"
|
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.154.
|
|
4
|
+
version: 0.154.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -563,7 +563,6 @@ files:
|
|
|
563
563
|
- lib/samagotchi/participants_watch.rb
|
|
564
564
|
- lib/samagotchi/paths.rb
|
|
565
565
|
- lib/samagotchi/peer_sources.rb
|
|
566
|
-
- lib/samagotchi/pending_input_queue.rb
|
|
567
566
|
- lib/samagotchi/pending_question.rb
|
|
568
567
|
- lib/samagotchi/plugin/api.rb
|
|
569
568
|
- lib/samagotchi/plugin/attached_context.rb
|
|
@@ -670,6 +669,7 @@ files:
|
|
|
670
669
|
- lib/samagotchi/steer.rb
|
|
671
670
|
- lib/samagotchi/steer_cut.rb
|
|
672
671
|
- lib/samagotchi/steer_cut_after.rb
|
|
672
|
+
- lib/samagotchi/stream_event.rb
|
|
673
673
|
- lib/samagotchi/system_one.rb
|
|
674
674
|
- lib/samagotchi/system_one/answer.rb
|
|
675
675
|
- lib/samagotchi/system_one/approval_label.rb
|
|
@@ -867,6 +867,7 @@ files:
|
|
|
867
867
|
- lib/samagotchi/web/public/block_source.js
|
|
868
868
|
- lib/samagotchi/web/public/bubbles.js
|
|
869
869
|
- lib/samagotchi/web/public/card.js
|
|
870
|
+
- lib/samagotchi/web/public/card_stamps.js
|
|
870
871
|
- lib/samagotchi/web/public/cards_place.js
|
|
871
872
|
- lib/samagotchi/web/public/chains.js
|
|
872
873
|
- lib/samagotchi/web/public/chip_popover.js
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Samagotchi
|
|
4
|
-
# Thread-safe FIFO of user steering messages submitted while a turn is
|
|
5
|
-
# running. UIs (TUI, web, background workers) are push-only producers;
|
|
6
|
-
# KernelLoop drains the queue at iteration boundaries and injects the
|
|
7
|
-
# messages into the conversation. True mid-stream injection is impossible
|
|
8
|
-
# with llama.cpp's /completion API, so draining only happens between full
|
|
9
|
-
# LLM responses / tool dispatches.
|
|
10
|
-
#
|
|
11
|
-
# Usage:
|
|
12
|
-
# queue = PendingInputQueue.new
|
|
13
|
-
# queue.push("please also check the specs")
|
|
14
|
-
# kernel.run(messages, pending_input: queue.method(:drain))
|
|
15
|
-
class PendingInputQueue
|
|
16
|
-
def initialize
|
|
17
|
-
@mutex = Mutex.new
|
|
18
|
-
@messages = []
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Append a message to the queue. Nil/empty strings are ignored.
|
|
22
|
-
# +seq+ is the sender's own order number for it (a sender that numbers
|
|
23
|
-
# every line it takes during a turn, so its leftovers keep send order across
|
|
24
|
-
# this queue and its after-turn list: #drain_entries).
|
|
25
|
-
def push(text, seq: nil)
|
|
26
|
-
text = text.to_s
|
|
27
|
-
return if text.empty?
|
|
28
|
-
|
|
29
|
-
@mutex.synchronize { @messages << [seq, text] }
|
|
30
|
-
nil
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Remove and return all queued messages in FIFO order. Non-blocking.
|
|
34
|
-
# @return [Array<String>]
|
|
35
|
-
def drain = drain_entries.map(&:last)
|
|
36
|
-
|
|
37
|
-
# #drain with each message's +seq+ (nil when pushed without one).
|
|
38
|
-
# @return [Array(Integer|nil, String)]
|
|
39
|
-
def drain_entries
|
|
40
|
-
@mutex.synchronize do
|
|
41
|
-
drained = @messages
|
|
42
|
-
@messages = []
|
|
43
|
-
drained
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def empty?
|
|
48
|
-
@mutex.synchronize { @messages.empty? }
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def size
|
|
52
|
-
@mutex.synchronize { @messages.size }
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|