samagotchi 0.121.1 → 0.122.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 +29 -1
- data/bin/chi +4 -1
- data/docs/architecture.md +2 -1
- data/docs/cli.md +1 -0
- data/docs/configuration.md +29 -4
- data/docs/guardrails.md +15 -0
- data/docs/internals/engine.md +2 -0
- data/docs/internals/participants.md +13 -7
- data/docs/memory.md +33 -0
- data/docs/plugins.md +3 -2
- data/docs/sessions.md +8 -2
- data/lib/samagotchi/bridge/turn_accumulator.rb +3 -1
- data/lib/samagotchi/bridge.rb +4 -3
- data/lib/samagotchi/bridge_client/event_stream.rb +5 -1
- data/lib/samagotchi/bridge_client/stream_open.rb +8 -2
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/memory_guide.md +13 -0
- data/lib/samagotchi/config.rb +5 -0
- data/lib/samagotchi/engine.rb +107 -1
- data/lib/samagotchi/event_fanout.rb +0 -1
- data/lib/samagotchi/guardrail_wiring.rb +5 -4
- data/lib/samagotchi/log_line.rb +1 -1
- data/lib/samagotchi/memory_orphans.rb +11 -0
- data/lib/samagotchi/participant_mode.rb +11 -0
- data/lib/samagotchi/plugin/api.rb +7 -4
- data/lib/samagotchi/process_group.rb +13 -0
- data/lib/samagotchi/review_start.rb +6 -2
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/session.rb +14 -1
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/remote.rb +32 -12
- data/lib/samagotchi/session_access/web_bridge_client.rb +9 -2
- data/lib/samagotchi/session_manager.rb +21 -7
- data/lib/samagotchi/session_setup.rb +8 -4
- data/lib/samagotchi/sessions_command.rb +1 -1
- data/lib/samagotchi/steer.rb +7 -6
- data/lib/samagotchi/system_one/client.rb +6 -5
- data/lib/samagotchi/system_one/log.rb +25 -5
- data/lib/samagotchi/terminal_ui.rb +2 -1
- data/lib/samagotchi/tool_runner.rb +2 -1
- data/lib/samagotchi/toolkit.rb +27 -8
- data/lib/samagotchi/toolkit_binding.rb +115 -7
- data/lib/samagotchi/toolkit_command.rb +15 -3
- data/lib/samagotchi/toolkit_report_command.rb +318 -0
- data/lib/samagotchi/toolkit_suggest.rb +211 -0
- data/lib/samagotchi/tools/memory.rb +1 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +5 -5
- data/lib/samagotchi/web/participant_access.rb +4 -1
- data/lib/samagotchi/web/participant_review.rb +6 -4
- data/lib/samagotchi/web/principal.rb +3 -4
- data/lib/samagotchi/web/public/bubbles.js +3 -2
- data/lib/samagotchi/web/public/participant.js +2 -1
- data/lib/samagotchi/web/public/turn_events.js +2 -1
- data/lib/samagotchi/web/public/turn_html.js +1 -1
- data/lib/samagotchi/web/server.rb +2 -2
- data/lib/samagotchi/web/session_access_routes.rb +3 -3
- data/lib/samagotchi/web/session_control.rb +4 -2
- data/lib/samagotchi/web/start_dir.rb +10 -5
- data/lib/samagotchi/worker.rb +16 -2
- data/lib/samagotchi/worker_link.rb +1 -0
- metadata +4 -1
|
@@ -160,9 +160,9 @@ export function createBubbles({
|
|
|
160
160
|
// The bubble a reload draws (turn_html.js), with this page's own fields.
|
|
161
161
|
// +delivery+: how it went into a running turn (next_step, cut, queue): its
|
|
162
162
|
// queued badge says what it waits for.
|
|
163
|
-
function makeUserBubble(prompt, { state = null, label = null, enqueuedId = null, own = false, images = null, delivery = null, author = null } = {}) {
|
|
163
|
+
function makeUserBubble(prompt, { state = null, label = null, enqueuedId = null, own = false, images = null, delivery = null, author = null, turnId = null } = {}) {
|
|
164
164
|
removeHint();
|
|
165
|
-
const div = elementOf(userBubbleTemplate({ content: prompt }, {
|
|
165
|
+
const div = elementOf(userBubbleTemplate({ content: prompt, turn_id: turnId }, {
|
|
166
166
|
label, state, own, enqueuedId, delivery, author, key: normalize(prompt), thumbs: thumbsTemplate(selected(), images),
|
|
167
167
|
}));
|
|
168
168
|
return placeFollowing(div);
|
|
@@ -224,6 +224,7 @@ export function createBubbles({
|
|
|
224
224
|
else liveTurn.unmatchedMerge = true;
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
|
+
if (started && event.type === "turn_started" && event.turn_id) started.dataset.turnId = event.turn_id;
|
|
227
228
|
return started;
|
|
228
229
|
}
|
|
229
230
|
|
|
@@ -277,6 +277,7 @@ function messageNode(message) {
|
|
|
277
277
|
const when = fullTime(message.at);
|
|
278
278
|
if (when) node.title = when;
|
|
279
279
|
if (user) {
|
|
280
|
+
if (message.turn_id) node.dataset.turnId = message.turn_id;
|
|
280
281
|
node.style.setProperty("--author-color", authorColor(message.author));
|
|
281
282
|
// Whose it is, for an annotation's label (participant_annotate.js).
|
|
282
283
|
Object.assign(node.dataset, messageAuthorData(message));
|
|
@@ -382,7 +383,7 @@ function currentTurnNodes() {
|
|
|
382
383
|
const nodes = [];
|
|
383
384
|
if (turn.prompt !== undefined || turn.automatic) {
|
|
384
385
|
nodes.push(messageNode({ role: "user", content: turn.automatic ? "An automatic turn" : turn.prompt, author: turn.author,
|
|
385
|
-
author_name: turn.author_name, mine: turn.mine, automatic: turn.automatic, at: turn.at }));
|
|
386
|
+
author_name: turn.author_name, mine: turn.mine, automatic: turn.automatic, at: turn.at, turn_id: turn.turn_id }));
|
|
386
387
|
}
|
|
387
388
|
const reply = el("div", "p-msg reply live");
|
|
388
389
|
fillLive(reply, $("liveReply"));
|
|
@@ -360,11 +360,12 @@ export function snapshotEvents({ current_turn: turn = null, queued = [], queued_
|
|
|
360
360
|
// The run's cut wait (TurnAccumulator, as turn_started carried it): the
|
|
361
361
|
// composer's delivery line reads it.
|
|
362
362
|
const cutAfter = turn.cut_after != null ? { cut_after: turn.cut_after } : {};
|
|
363
|
+
const turnId = turn.turn_id ? { turn_id: turn.turn_id } : {};
|
|
363
364
|
// A participant's turn names them (TurnAccumulator keeps author, author_name).
|
|
364
365
|
const author = turn.author ? { author: turn.author, ...(turn.author_name ? { author_name: turn.author_name } : {}) } : {};
|
|
365
366
|
// Its wall time (TurnAccumulator keeps turn_started's at): the gap divider before it.
|
|
366
367
|
const at = typeof turn.at === "string" ? { at: turn.at } : {};
|
|
367
|
-
events.push(withImages({ type: "turn_started", prompt: turn.prompt, origin: turn.origin, continue: !!turn.continue, started_at, ...at, ...cutAfter, ...author }, turn.images));
|
|
368
|
+
events.push(withImages({ type: "turn_started", prompt: turn.prompt, origin: turn.origin, continue: !!turn.continue, started_at, ...at, ...cutAfter, ...turnId, ...author }, turn.images));
|
|
368
369
|
let textIteration = null; // a text bubble is open for this iteration
|
|
369
370
|
const closeText = () => {
|
|
370
371
|
if (textIteration !== null) events.push({ type: "generation_completed" });
|
|
@@ -313,7 +313,7 @@ export function userBubbleTemplate(message, { label = null, state = null, step =
|
|
|
313
313
|
const from = author
|
|
314
314
|
? html`<span class="origin-label author-badge" title=${authorBadgeTitle(author.name)} data-unnamed=${author.unnamed ? "1" : nothing}>${authorBadgeText(author.name)}</span>`
|
|
315
315
|
: label ? html`<span class="origin-label">${label}</span>` : nothing;
|
|
316
|
-
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-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-own=${own ? "1" : nothing} data-enqueued-id=${enqueuedId || nothing} data-delivery=${delivery || nothing}>${from}<div class="user-message">${userBodyTemplate(message?.content)}${thumbs}</div>${stateBadgeTemplate(named, delivery)}</div>`;
|
|
316
|
+
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-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-own=${own ? "1" : nothing} data-enqueued-id=${enqueuedId || nothing} data-delivery=${delivery || nothing}>${from}<div class="user-message">${userBodyTemplate(message?.content)}${thumbs}</div>${stateBadgeTemplate(named, delivery)}</div>`;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
// The notice of a turn that ended with no answer (the server's
|
|
@@ -86,9 +86,9 @@ module Samagotchi
|
|
|
86
86
|
def self.launch(port: nil, host: nil, scope: "project", start_dir: nil, open_browser: false, markdown: false,
|
|
87
87
|
view: Config::BY_KEY["web.view"].default, annotate_presets: Config::BY_KEY["web.annotate_presets"].default,
|
|
88
88
|
editor: Config::BY_KEY["web.editor"].default, new_token: false,
|
|
89
|
-
working_set: Config::BY_KEY["web.working_set"].default, tunnel: nil)
|
|
89
|
+
working_set: Config::BY_KEY["web.working_set"].default, tunnel: nil, start_dir_from: :flag)
|
|
90
90
|
start_given = !start_dir.to_s.strip.empty?
|
|
91
|
-
start_dir = StartDir.resolve(start_dir)
|
|
91
|
+
start_dir = StartDir.resolve(start_dir, from: start_dir_from)
|
|
92
92
|
rotate_token if new_token
|
|
93
93
|
public_url = public_url_setting unless tunnel
|
|
94
94
|
setting = host_setting(host)
|
|
@@ -449,7 +449,7 @@ module Samagotchi
|
|
|
449
449
|
# hint}: new text for the session's --push source (chi context push;
|
|
450
450
|
# the fields as a source's JSON output gives them, checked the same
|
|
451
451
|
# way). 200 {status: "pushed", name, before, revision, changed}; 404
|
|
452
|
-
#
|
|
452
|
+
# context_missing (no such source); 409 not_push (a command source); 400
|
|
453
453
|
# invalid_text.
|
|
454
454
|
def handle_context_push(req, id, name)
|
|
455
455
|
body = body_hash(req) or return error_response(400, "invalid_json", "invalid JSON body")
|
|
@@ -462,13 +462,13 @@ module Samagotchi
|
|
|
462
462
|
end
|
|
463
463
|
pushed = session_access.context_push(context_target(id), name, fetched)
|
|
464
464
|
case pushed.status
|
|
465
|
-
when :missing then error_response(404, "
|
|
465
|
+
when :missing then error_response(404, "context_missing", "no source #{name[0, 40]}")
|
|
466
466
|
when :not_push then error_response(409, "not_push", SessionAccess::ContextPush.not_push_message(name))
|
|
467
467
|
else json_response(200, { status: "pushed", name: name, before: pushed.before, revision: pushed.revision,
|
|
468
468
|
changed: pushed.before != pushed.revision })
|
|
469
469
|
end
|
|
470
470
|
rescue ContextSources::Invalid
|
|
471
|
-
error_response(404, "
|
|
471
|
+
error_response(404, "context_missing", "no source #{name[0, 40]}")
|
|
472
472
|
end
|
|
473
473
|
|
|
474
474
|
# The session as chi context's Target (SessionAccess::Local's context
|
|
@@ -423,7 +423,8 @@ module Samagotchi
|
|
|
423
423
|
# the newest chi installed (SessionManager.restart_session). 200
|
|
424
424
|
# {status: "restarted", session_id, from_version, version} (version nil:
|
|
425
425
|
# the new worker is still starting; its bridge_up follows); 409
|
|
426
|
-
# {error: "held" | "not_running" | "unsupported" | "failed", reason, detail} in words
|
|
426
|
+
# {error: "held" | "not_running" | "unsupported" | "failed", reason, detail} in words (a stopped
|
|
427
|
+
# worker: held, reason stopped, its pid); 409
|
|
427
428
|
# owned_by_tui; 404.
|
|
428
429
|
def handle_restart(_req, id)
|
|
429
430
|
result = @manager.restart_session(id, state_dir: @state_dir, client_id: ClientId::WEB_RESTART)
|
|
@@ -432,7 +433,8 @@ module Samagotchi
|
|
|
432
433
|
version: result.version })
|
|
433
434
|
rescue SessionManager::RestartRefused => e
|
|
434
435
|
error = %i[not_running unsupported failed].include?(e.reason) ? e.reason.to_s : "held"
|
|
435
|
-
refusal_response(409, error, e, reason: e.reason.to_s, session_id: id, version: e.version
|
|
436
|
+
refusal_response(409, error, e, reason: e.reason.to_s, session_id: id, version: e.version, holds: e.holds,
|
|
437
|
+
pid: e.pid)
|
|
436
438
|
rescue SessionManager::OwnedByTUI => e
|
|
437
439
|
owned_by_tui(e)
|
|
438
440
|
end
|
|
@@ -9,15 +9,20 @@ module Samagotchi
|
|
|
9
9
|
module StartDir
|
|
10
10
|
class Invalid < StandardError; end
|
|
11
11
|
|
|
12
|
+
# Where +given+ came from, as its error names it.
|
|
13
|
+
SOURCES = { flag: "--start-dir", config: "web.start_dir in config.yml" }.freeze
|
|
14
|
+
|
|
12
15
|
# +given+ as an absolute folder (~ expanded, a relative one against
|
|
13
|
-
# +cwd
|
|
16
|
+
# +cwd+, or the home folder for a config value); nil or blank: +cwd+.
|
|
17
|
+
# @param from [Symbol] :flag or :config (SOURCES)
|
|
14
18
|
# @raise [Invalid] when it is no folder
|
|
15
|
-
def self.resolve(given, cwd: Dir.pwd)
|
|
19
|
+
def self.resolve(given, cwd: Dir.pwd, from: :flag)
|
|
16
20
|
return File.expand_path(cwd) if given.nil? || given.to_s.strip.empty?
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
raise Invalid, "
|
|
22
|
+
source = SOURCES.fetch(from)
|
|
23
|
+
dir = File.expand_path(given.to_s.strip, from == :config ? Dir.home : cwd)
|
|
24
|
+
raise Invalid, "#{source} #{given}: no such folder" unless File.exist?(dir)
|
|
25
|
+
raise Invalid, "#{source} #{given}: not a folder" unless File.directory?(dir)
|
|
21
26
|
|
|
22
27
|
dir
|
|
23
28
|
end
|
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -519,11 +519,19 @@ module Samagotchi
|
|
|
519
519
|
# it as waiting, and the next worker drops it (drop_dead_question).
|
|
520
520
|
# The context poller first: a command it runs goes (its process
|
|
521
521
|
# group) before the Engine does.
|
|
522
|
+
# How long the lock stays held once #teardown marks it leaving (an upper
|
|
523
|
+
# bound): the recap on leave is written by then (#left), what is left is
|
|
524
|
+
# Bridge#stop's graces and ChildRing.await_wakes.
|
|
525
|
+
TEARDOWN_LEAVE_WAIT = 10
|
|
526
|
+
|
|
522
527
|
def teardown
|
|
523
528
|
@config_restart_unsubscribe&.call
|
|
524
529
|
@context_poller&.stop
|
|
525
530
|
drop_queued_commands
|
|
526
531
|
@engine&.shutdown
|
|
532
|
+
# Leaving before the Bridge closes: a client that finds its port shut
|
|
533
|
+
# from here on reads Leaving (and waits for the lock), not Live.
|
|
534
|
+
mark_leaving(wait: TEARDOWN_LEAVE_WAIT)
|
|
527
535
|
@bridge&.stop
|
|
528
536
|
# The process exits next: a parent wake it rang for (a crash's
|
|
529
537
|
# ring comes just above) must spawn the parent's worker first.
|
|
@@ -1438,8 +1446,14 @@ module Samagotchi
|
|
|
1438
1446
|
# How long the lock stays held after the Bridge: the recap on leave's
|
|
1439
1447
|
# timeout (#left), none for a restart (#left writes none). A delete
|
|
1440
1448
|
# writes none either, but the bound only has to be an upper one.
|
|
1441
|
-
|
|
1442
|
-
|
|
1449
|
+
# Once: the teardown's (after #stop_bridge_after's) keeps the first one.
|
|
1450
|
+
# @param wait [Numeric, nil] nil: as above
|
|
1451
|
+
def mark_leaving(wait: nil)
|
|
1452
|
+
return if @marked_leaving
|
|
1453
|
+
|
|
1454
|
+
@marked_leaving = true
|
|
1455
|
+
wait ||= @exit_restart ? 0 : @engine.recap_timeout.to_f
|
|
1456
|
+
@owner_lock&.mark_leaving(wait: wait)
|
|
1443
1457
|
rescue StandardError => e
|
|
1444
1458
|
Log.warn(:worker, "mark_leaving_failed", error: e.class.name, msg: e.message)
|
|
1445
1459
|
end
|
|
@@ -112,6 +112,7 @@ module Samagotchi
|
|
|
112
112
|
module_function
|
|
113
113
|
|
|
114
114
|
def not_live(session_id) = "no live worker for session #{session_id}"
|
|
115
|
+
def unanswered(session_id) = "the worker for session #{session_id} didn't answer"
|
|
115
116
|
|
|
116
117
|
# @param session_dir [String]
|
|
117
118
|
# @param unlink [Boolean] remove a Stale sidecar (the clients that would
|
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.122.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -476,6 +476,7 @@ files:
|
|
|
476
476
|
- lib/samagotchi/parent_answers_command.rb
|
|
477
477
|
- lib/samagotchi/parent_report.rb
|
|
478
478
|
- lib/samagotchi/participant_messages.rb
|
|
479
|
+
- lib/samagotchi/participant_mode.rb
|
|
479
480
|
- lib/samagotchi/participant_name.rb
|
|
480
481
|
- lib/samagotchi/participants.rb
|
|
481
482
|
- lib/samagotchi/participants_watch.rb
|
|
@@ -665,6 +666,8 @@ files:
|
|
|
665
666
|
- lib/samagotchi/toolkit.rb
|
|
666
667
|
- lib/samagotchi/toolkit_binding.rb
|
|
667
668
|
- lib/samagotchi/toolkit_command.rb
|
|
669
|
+
- lib/samagotchi/toolkit_report_command.rb
|
|
670
|
+
- lib/samagotchi/toolkit_suggest.rb
|
|
668
671
|
- lib/samagotchi/tools/args.rb
|
|
669
672
|
- lib/samagotchi/tools/ask_user_question.rb
|
|
670
673
|
- lib/samagotchi/tools/builtin_calls.rb
|