samagotchi 0.71.0 → 0.72.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 +45 -1
- data/docs/architecture.md +14 -8
- data/docs/cli.md +3 -2
- data/docs/configuration.md +23 -13
- data/docs/hooks.md +1 -1
- data/docs/internals/anthropic.md +1 -1
- data/docs/internals/engine.md +38 -13
- data/docs/internals/llm-context-live.md +1 -1
- data/docs/internals/prompt-caching.md +6 -0
- data/docs/plugins.md +18 -7
- data/docs/sessions.md +3 -1
- data/docs/testing.md +10 -1
- data/lib/samagotchi/bootstrap/config_writer.rb +2 -3
- data/lib/samagotchi/bridge.rb +5 -2
- data/lib/samagotchi/broadcast/scope_card.rb +2 -2
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -7
- data/lib/samagotchi/bundles/claude-peer/plugin.rb +2 -2
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -6
- data/lib/samagotchi/bundles/codex-peer/plugin.rb +2 -2
- data/lib/samagotchi/bundles/coordinator/manifest.yml +8 -6
- data/lib/samagotchi/bundles/coordinator/plugin.rb +10 -0
- data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +2 -1
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/config.rb +7 -25
- data/lib/samagotchi/context_note.rb +2 -6
- data/lib/samagotchi/effective_model.rb +1 -1
- data/lib/samagotchi/engine.rb +91 -213
- data/lib/samagotchi/engine_peers.rb +2 -0
- data/lib/samagotchi/event_routes.rb +83 -0
- data/lib/samagotchi/hook_routes.rb +16 -37
- data/lib/samagotchi/host_config.rb +1 -1
- data/lib/samagotchi/host_keys.rb +0 -2
- data/lib/samagotchi/paths.rb +13 -0
- data/lib/samagotchi/plugin/api.rb +11 -5
- data/lib/samagotchi/plugin/peer.rb +5 -8
- data/lib/samagotchi/prompt_files.rb +2 -4
- data/lib/samagotchi/session.rb +2 -1
- data/lib/samagotchi/session_manager.rb +6 -16
- data/lib/samagotchi/session_model_settings.rb +227 -0
- data/lib/samagotchi/session_setup.rb +2 -1
- data/lib/samagotchi/system_prompt.rb +18 -13
- data/lib/samagotchi/terminal_ui/attached_loop.rb +30 -14
- data/lib/samagotchi/terminal_ui/formatting.rb +4 -0
- data/lib/samagotchi/tool_activity.rb +1 -0
- data/lib/samagotchi/tool_declarations.rb +4 -0
- data/lib/samagotchi/toolkit.rb +55 -24
- data/lib/samagotchi/toolkit_binding.rb +7 -5
- data/lib/samagotchi/toolkit_command.rb +28 -5
- data/lib/samagotchi/tools/builtins.rb +2 -1
- data/lib/samagotchi/tools/delegate.rb +32 -6
- data/lib/samagotchi/tools/list_sessions.rb +2 -2
- data/lib/samagotchi/tools/peers.rb +5 -2
- data/lib/samagotchi/tools/register_reminder.rb +1 -0
- data/lib/samagotchi/tools/registry.rb +1 -1
- data/lib/samagotchi/tools/tool_index.rb +15 -4
- data/lib/samagotchi/turn_note.rb +4 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +13 -10
- data/lib/samagotchi/web/public/app.js +20 -18
- data/lib/samagotchi/web/public/bubbles.js +1 -1
- data/lib/samagotchi/web/public/cards_place.js +6 -10
- data/lib/samagotchi/web/public/chains.js +1 -1
- data/lib/samagotchi/web/public/chip_popover.js +3 -1
- data/lib/samagotchi/web/public/composer.js +13 -6
- data/lib/samagotchi/web/public/context_chips.js +25 -12
- data/lib/samagotchi/web/public/data.js +5 -1
- data/lib/samagotchi/web/public/format.js +5 -0
- data/lib/samagotchi/web/public/sessions_list.js +2 -3
- data/lib/samagotchi/web/public/stage_frame.js +1 -1
- data/lib/samagotchi/web/public/stage_view.js +8 -2
- data/lib/samagotchi/web/public/stream_handlers.js +6 -3
- data/lib/samagotchi/web/public/timing.js +1 -1
- data/lib/samagotchi/web/public/toolkit_chip.js +3 -1
- data/lib/samagotchi/web/session_hub.rb +8 -0
- metadata +3 -1
|
@@ -32,22 +32,20 @@ export function turnAnswerEl(turnEls) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// @param historyEl the history
|
|
35
|
-
// @param appendToHistory (el) puts +el+ at the history's end (or the stage's)
|
|
36
|
-
// @param isNearBottom () => whether the history follows its end
|
|
37
35
|
// @param turnDom the running turn's view (its card(el) takes a turn's card)
|
|
38
36
|
// @param findInTurnRoots (selector) => the matching elements in the history
|
|
39
37
|
// and the stage
|
|
40
38
|
// @param selected () => the open session's id
|
|
41
39
|
// @param clientId this tab's id in the session's events
|
|
42
40
|
// @param questionCards the history's question cards (question_cards.js)
|
|
43
|
-
// @param bubbles {removeHintIfEmpty, showGuardrailWarning, showHookNotice}
|
|
41
|
+
// @param bubbles {removeHintIfEmpty, placeFollowing, showGuardrailWarning, showHookNotice}
|
|
44
42
|
// (bubbles.js createBubbles)
|
|
45
43
|
// @param followWokenWorker () follows the worker a card's command woke
|
|
46
44
|
// @param api {getSession, sendCommand} (data.js)
|
|
47
45
|
// @return {cardElement(id), upsertCard(card, {before}), placeSnapshotCards(entries),
|
|
48
46
|
// refreshCardBodies(), onCardAction(event)}
|
|
49
47
|
export function createCardsPlace({
|
|
50
|
-
historyEl,
|
|
48
|
+
historyEl, turnDom, findInTurnRoots, selected, clientId, questionCards, bubbles,
|
|
51
49
|
followWokenWorker, api = { getSession, sendCommand },
|
|
52
50
|
}) {
|
|
53
51
|
function cardElement(id) {
|
|
@@ -73,10 +71,7 @@ export function createCardsPlace({
|
|
|
73
71
|
const el = drawCard(document.createElement(isNoticeCard(card) ? "details" : "div"), card);
|
|
74
72
|
const asks = (card.actions || []).some((a) => a && a.command);
|
|
75
73
|
if (card.in_turn && !before && !asks && turnDom.card(el)) return;
|
|
76
|
-
|
|
77
|
-
if (before) historyEl.insertBefore(el, before);
|
|
78
|
-
else appendToHistory(el);
|
|
79
|
-
if (follow) historyEl.scrollTop = historyEl.scrollHeight;
|
|
74
|
+
bubbles.placeFollowing(el, before);
|
|
80
75
|
}
|
|
81
76
|
|
|
82
77
|
// A snapshot's cards and notices, each above the first turn after it; a
|
|
@@ -170,8 +165,9 @@ export function createCardsPlace({
|
|
|
170
165
|
button.disabled = true;
|
|
171
166
|
api.sendCommand(id, button.dataset.command, { clientId, card: true })
|
|
172
167
|
.then(() => {
|
|
173
|
-
// The command woke the worker: follow its stream if none is open
|
|
174
|
-
|
|
168
|
+
// The command woke the worker: follow its stream if none is open
|
|
169
|
+
// (and the page still shows that session).
|
|
170
|
+
if (selected() === id) followWokenWorker();
|
|
175
171
|
})
|
|
176
172
|
.catch((error) => {
|
|
177
173
|
err.textContent = error.message;
|
|
@@ -206,7 +206,7 @@ export function chainRowsTemplate(f, { activeId = null, now = Date.now() } = {})
|
|
|
206
206
|
export function continueRefusal(error, sessions = []) {
|
|
207
207
|
const body = error?.body || {};
|
|
208
208
|
if (error?.code === "continued" && body.next_id) return { kind: "open", id: body.next_id };
|
|
209
|
-
const text = String(error?.message || "refused")
|
|
209
|
+
const text = String(error?.message || "refused");
|
|
210
210
|
if (error?.code === "open_children" && Array.isArray(body.ids) && body.ids.length) {
|
|
211
211
|
const byId = new Map(sessions.map((s) => [s.id, s]));
|
|
212
212
|
const children = body.ids.map((id) => {
|
|
@@ -84,10 +84,12 @@ export function createChipPopover({ id, className, width: widest, el, anchor: an
|
|
|
84
84
|
show(data, false);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// The click goes on to the document: another chip's popover (or the
|
|
88
|
+
// context popover) open meanwhile closes there. This one's own closer
|
|
89
|
+
// leaves its chip's click alone.
|
|
87
90
|
el.addEventListener("click", (e) => {
|
|
88
91
|
const anchor = e.target.closest(anchorSel);
|
|
89
92
|
if (!anchor) return;
|
|
90
|
-
e.stopPropagation();
|
|
91
93
|
if (isOpen()) close();
|
|
92
94
|
else open(anchor);
|
|
93
95
|
});
|
|
@@ -60,20 +60,26 @@ export function routeSessionLine(prompt, { commands = [], images = 0, delivery =
|
|
|
60
60
|
// followWokenWorker() the command woke the worker: follow its stream
|
|
61
61
|
// refreshHistory()
|
|
62
62
|
// failed(line, text) a failed command's bubble
|
|
63
|
+
// failedElsewhere(line, text) a command to a session no longer open failed
|
|
63
64
|
// ownedByTui() a plain terminal holds the session
|
|
64
65
|
// composer: {busy(), clear(), done()} the composer's send button and text
|
|
65
66
|
export function createComposerCommands(deps) {
|
|
66
|
-
const { session, clientId, sendCommand, followWokenWorker, refreshHistory, failed, ownedByTui,
|
|
67
|
+
const { session, clientId, sendCommand, followWokenWorker, refreshHistory, failed, failedElsewhere, ownedByTui,
|
|
68
|
+
composer } = deps;
|
|
67
69
|
|
|
68
|
-
//
|
|
70
|
+
// The page may open another session while a command is in flight: what
|
|
71
|
+
// follows is about the session it went to (+id+), so the open one's
|
|
72
|
+
// worker, owner and history are left alone then.
|
|
69
73
|
async function send(line) {
|
|
74
|
+
const id = session();
|
|
70
75
|
composer.busy();
|
|
71
76
|
try {
|
|
72
|
-
await sendCommand(
|
|
77
|
+
await sendCommand(id, line, { clientId });
|
|
73
78
|
composer.clear();
|
|
74
79
|
refreshHistory();
|
|
75
|
-
followWokenWorker();
|
|
80
|
+
if (session() === id) followWokenWorker();
|
|
76
81
|
} catch (e) {
|
|
82
|
+
if (session() !== id) return failedElsewhere(line, e.message);
|
|
77
83
|
if (e.code === "owned_by_tui") {
|
|
78
84
|
ownedByTui();
|
|
79
85
|
return;
|
|
@@ -93,10 +99,11 @@ export function createComposerCommands(deps) {
|
|
|
93
99
|
if (!id) return false;
|
|
94
100
|
try {
|
|
95
101
|
await sendCommand(id, line, { clientId });
|
|
96
|
-
followWokenWorker();
|
|
102
|
+
if (session() === id) followWokenWorker();
|
|
97
103
|
return true;
|
|
98
104
|
} catch (e) {
|
|
99
|
-
failed(line, e.message);
|
|
105
|
+
if (session() === id) failed(line, e.message);
|
|
106
|
+
else failedElsewhere(line, e.message);
|
|
100
107
|
return false;
|
|
101
108
|
}
|
|
102
109
|
}
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { html, nothing, render } from "./vendor/lit-html.js";
|
|
11
11
|
import { keepFocus } from "./keep_focus.js";
|
|
12
|
+
import { popoverPlace } from "./chip_popover.js";
|
|
13
|
+
import { cantText } from "./format.js";
|
|
12
14
|
|
|
13
15
|
// "now", "3m", "2h", "4d" since an ISO time; "" for none.
|
|
14
16
|
export function ageText(iso, now = Date.now()) {
|
|
@@ -177,13 +179,24 @@ export function createContextChips({ el, api, toast = () => {}, doc = document,
|
|
|
177
179
|
render(popoverTemplate(row, { text }), pop);
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
//
|
|
182
|
+
// At the chip as the session bar's chips place theirs (popoverPlace):
|
|
183
|
+
// above it, or below when the room above is short; capped to the room
|
|
184
|
+
// (it scrolls), inside the window with a 16px gutter.
|
|
181
185
|
function place(chip) {
|
|
182
|
-
const rect = chip.getBoundingClientRect();
|
|
183
186
|
const width = Math.min(380, win.innerWidth - 32);
|
|
184
187
|
pop.style.width = `${width}px`;
|
|
185
|
-
pop.style.
|
|
186
|
-
|
|
188
|
+
pop.style.maxHeight = "";
|
|
189
|
+
const at = popoverPlace(chip.getBoundingClientRect(), { width, height: pop.offsetHeight,
|
|
190
|
+
innerWidth: win.innerWidth, innerHeight: win.innerHeight });
|
|
191
|
+
pop.style.left = `${at.left}px`;
|
|
192
|
+
pop.style.top = at.top === undefined ? "" : `${at.top}px`;
|
|
193
|
+
pop.style.bottom = at.bottom === undefined ? "" : `${at.bottom}px`;
|
|
194
|
+
if (at.maxHeight !== undefined) pop.style.maxHeight = `${at.maxHeight}px`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function placeAtOpenChip() {
|
|
198
|
+
const chip = openName && el.querySelector(".ctx-chip.open");
|
|
199
|
+
if (chip) place(chip);
|
|
187
200
|
}
|
|
188
201
|
|
|
189
202
|
function open(name, chip) {
|
|
@@ -231,7 +244,7 @@ export function createContextChips({ el, api, toast = () => {}, doc = document,
|
|
|
231
244
|
} catch (e) {
|
|
232
245
|
if (id !== sessionId || openName !== ADD) return;
|
|
233
246
|
// The same form again: what the user typed stays; the URL gets focus.
|
|
234
|
-
drawAdd({ busy: false, error:
|
|
247
|
+
drawAdd({ busy: false, error: e.message });
|
|
235
248
|
pop.querySelector('input[name="url"]')?.focus();
|
|
236
249
|
}
|
|
237
250
|
}
|
|
@@ -255,8 +268,10 @@ export function createContextChips({ el, api, toast = () => {}, doc = document,
|
|
|
255
268
|
if (!row) return;
|
|
256
269
|
text = data.text || "";
|
|
257
270
|
fill(row);
|
|
271
|
+
// The text makes it taller: placed again.
|
|
272
|
+
placeAtOpenChip();
|
|
258
273
|
} catch (e) {
|
|
259
|
-
toast(`
|
|
274
|
+
toast(cantText(`read ${name}`, e));
|
|
260
275
|
}
|
|
261
276
|
}
|
|
262
277
|
async function detach() {
|
|
@@ -271,14 +286,15 @@ export function createContextChips({ el, api, toast = () => {}, doc = document,
|
|
|
271
286
|
close();
|
|
272
287
|
await refresh();
|
|
273
288
|
} catch (e) {
|
|
274
|
-
toast(`
|
|
289
|
+
toast(cantText(`detach ${row.name}`, e));
|
|
275
290
|
}
|
|
276
291
|
}
|
|
277
292
|
|
|
278
293
|
el.addEventListener("click", (e) => {
|
|
279
294
|
const chip = e.target.closest(".ctx-chip");
|
|
280
295
|
if (!chip) return;
|
|
281
|
-
|
|
296
|
+
// On to the document: a session-bar chip's popover closes (the close
|
|
297
|
+
// below leaves clicks in +el+ alone).
|
|
282
298
|
if (chip.dataset.act === "add") {
|
|
283
299
|
if (openName === ADD) close();
|
|
284
300
|
else openAdd(chip);
|
|
@@ -301,10 +317,7 @@ export function createContextChips({ el, api, toast = () => {}, doc = document,
|
|
|
301
317
|
doc.addEventListener("keydown", (e) => {
|
|
302
318
|
if (e.key === "Escape" && openName) close();
|
|
303
319
|
});
|
|
304
|
-
win.addEventListener("resize",
|
|
305
|
-
const chip = openName && el.querySelector(".ctx-chip.open");
|
|
306
|
-
if (chip) place(chip);
|
|
307
|
-
});
|
|
320
|
+
win.addEventListener("resize", placeAtOpenChip);
|
|
308
321
|
win.addEventListener("focus", () => refresh());
|
|
309
322
|
// The ages move on by themselves.
|
|
310
323
|
win.setInterval(() => { if (rows.length) drawChips(); }, 60_000);
|
|
@@ -6,7 +6,11 @@ export function api(path, opts = {}) {
|
|
|
6
6
|
}).then(async (r) => {
|
|
7
7
|
const j = await r.json().catch(() => ({}));
|
|
8
8
|
if (!r.ok) {
|
|
9
|
-
|
|
9
|
+
// The server's words as they are: what the page shows the user. The
|
|
10
|
+
// status goes in only when it sent none (a proxy's bare 502, a crash's
|
|
11
|
+
// 500): then it is all there is to go on.
|
|
12
|
+
const words = j.detail || j.error;
|
|
13
|
+
const error = new Error(words || `${r.statusText || "HTTP error"} (${r.status})`);
|
|
10
14
|
error.status = r.status;
|
|
11
15
|
// The server's error code ({error: "owned_by_tui", detail}): what
|
|
12
16
|
// the page matches on, never the message's words.
|
|
@@ -168,6 +168,11 @@ export function goneSessionNotice(id, why) {
|
|
|
168
168
|
return `Session ${String(id).slice(0, 8)} was ${why === "deleted" ? "deleted" : "not found"}.`;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
// A failed action's toast: what failed and why (api()'s error says why).
|
|
172
|
+
export function cantText(what, error) {
|
|
173
|
+
return `Can't ${what}: ${String(error?.message ?? error)}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
171
176
|
// A session in the list after its live status changed (a turn started or
|
|
172
177
|
// ended): that is activity now, so its card's "N min ago" says so too, and
|
|
173
178
|
// the change came from a worker's stream, so the card says "live".
|
|
@@ -264,10 +264,9 @@ export function batchTargets(picked, shownIds, archive, sessionsById) {
|
|
|
264
264
|
});
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
// A refusal's reason as the card shows it: the server's words
|
|
268
|
-
// the api() status suffix.
|
|
267
|
+
// A refusal's reason as the card shows it: the server's words.
|
|
269
268
|
function refusalText(error) {
|
|
270
|
-
return String(error?.message || "refused")
|
|
269
|
+
return String(error?.message || "refused");
|
|
271
270
|
}
|
|
272
271
|
|
|
273
272
|
// Runs +act+ over +ids+ one at a time (a live session's stop waits on the
|
|
@@ -13,7 +13,7 @@ import { easeScrollTo } from "./scroll.js";
|
|
|
13
13
|
const GROW_MS = 180;
|
|
14
14
|
const GROW_EASE = "cubic-bezier(.2,.7,.3,1)";
|
|
15
15
|
// The room between the stage and the composer card (index.html's margin-bottom).
|
|
16
|
-
const STAGE_GAP = 10;
|
|
16
|
+
export const STAGE_GAP = 10;
|
|
17
17
|
|
|
18
18
|
export function reducedMotion() {
|
|
19
19
|
return matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
@@ -127,6 +127,7 @@ export function createStageView({ historyEl, appendToHistory, isNearBottom: hist
|
|
|
127
127
|
let endedAt = 0;
|
|
128
128
|
let thinkingLine = null; // { step, text } from the live step's ticker
|
|
129
129
|
let flash = null; // { kind, text, until } a notice or nudge in the trail
|
|
130
|
+
let flashTimer = null; // its end's redraw
|
|
130
131
|
let collapsed = readCollapsed();
|
|
131
132
|
let cloudOpen = false;
|
|
132
133
|
// A ref's "show in turn" opened a collapsed stage (not kept).
|
|
@@ -252,7 +253,8 @@ export function createStageView({ historyEl, appendToHistory, isNearBottom: hist
|
|
|
252
253
|
if (!what) return;
|
|
253
254
|
flash = { ...what, until: Date.now() + FLASH_MS };
|
|
254
255
|
scheduleRefresh();
|
|
255
|
-
|
|
256
|
+
clearTimeout(flashTimer);
|
|
257
|
+
flashTimer = setTimeout(scheduleRefresh, FLASH_MS + 50);
|
|
256
258
|
}
|
|
257
259
|
|
|
258
260
|
// The answer reads from its top (no tail pin in the stage): scrolled to
|
|
@@ -408,6 +410,7 @@ export function createStageView({ historyEl, appendToHistory, isNearBottom: hist
|
|
|
408
410
|
promptSlot.classList.remove("full");
|
|
409
411
|
thinkingLine = null;
|
|
410
412
|
flash = null;
|
|
413
|
+
clearTimeout(flashTimer);
|
|
411
414
|
setCloud(false);
|
|
412
415
|
stage.classList.remove("plain", "ended", "waiting", "held-wait", "asking", "in-use");
|
|
413
416
|
stage.style.removeProperty("--live-held");
|
|
@@ -590,8 +593,11 @@ export function createStageView({ historyEl, appendToHistory, isNearBottom: hist
|
|
|
590
593
|
// composer's delivery line).
|
|
591
594
|
current: () => view.current(),
|
|
592
595
|
phase: () => liveSlots(view.current().turn, { pending: pendingCard() || asksCard() }).phase,
|
|
596
|
+
// Another session opens, or this one renders again: a turn still
|
|
597
|
+
// sliding away is dropped (its history is about to be drawn anew),
|
|
598
|
+
// never moved into whatever history shows now.
|
|
593
599
|
reset() {
|
|
594
|
-
|
|
600
|
+
folding = null;
|
|
595
601
|
release();
|
|
596
602
|
},
|
|
597
603
|
// The stage's extras while it holds a turn (a card, a prompt for its
|
|
@@ -150,12 +150,15 @@ export function createStreamHandlers(deps) {
|
|
|
150
150
|
// An after_turn hook presented the answer (AnswerDisplay) or attached
|
|
151
151
|
// refs to it (AnswerRefs: refs, a count; display null when only refs):
|
|
152
152
|
// it comes after turn_completed, so re-read the answer once that read
|
|
153
|
-
// is done
|
|
153
|
+
// is done: the session's open then, so a switch meanwhile leaves the
|
|
154
|
+
// other session's answers alone.
|
|
154
155
|
answer_display: async (data) => {
|
|
156
|
+
const id = selected();
|
|
155
157
|
try {
|
|
156
158
|
await liveTurn.endRead;
|
|
157
|
-
if (!selected() || (!data.display && !data.refs)) return;
|
|
158
|
-
const d = await getSession(
|
|
159
|
+
if (!id || id !== selected() || (!data.display && !data.refs)) return;
|
|
160
|
+
const d = await getSession(id, { tail: true, recent: true });
|
|
161
|
+
if (id !== selected()) return;
|
|
159
162
|
if (Array.isArray(d.messages)) applyFinalMarkdown(d.messages);
|
|
160
163
|
} catch (_) {
|
|
161
164
|
} finally {
|
|
@@ -278,7 +278,7 @@ function noMatch(record) {
|
|
|
278
278
|
// every assistant message is one step. Its calls take the turn's records by
|
|
279
279
|
// tool, in call order (each the next record of its tool, consumed with the
|
|
280
280
|
// ones it skipped), not by iteration: an empty retry spends an iteration
|
|
281
|
-
// that left no message (as the TUI's join, attached_loop.rb
|
|
281
|
+
// that left no message (as the TUI's join, attached_loop.rb join_record).
|
|
282
282
|
// The last message is the answer when it has text and made no call (and the
|
|
283
283
|
// turn is no canceled multi-step one); its thinking stays behind as a step,
|
|
284
284
|
// as it does live.
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// worker's snapshot or a command_ran; the server's own reading of the
|
|
6
6
|
// session file has no lists). Left out is reachable (find_tools and
|
|
7
7
|
// call_tool reach them) and removed (only /toolkit add brings one back).
|
|
8
|
-
// Changing it is /toolkit (no popover yet).
|
|
8
|
+
// Changing it is /toolkit (no popover yet). A kit with a prompt text has
|
|
9
|
+
// it on the summary too (prompt), and the tooltip ends with it.
|
|
9
10
|
|
|
10
11
|
import { html, nothing } from "./vendor/lit-html.js";
|
|
11
12
|
|
|
@@ -24,6 +25,7 @@ export function chipModel(summary) {
|
|
|
24
25
|
parts.push(summary.left_out.length ? `Not loaded: ${summary.left_out.join(", ")}.` : "Nothing left out.");
|
|
25
26
|
}
|
|
26
27
|
parts.push("/toolkit lists them; /toolkit NAME, add TOOL or remove TOOL changes them from the next turn.");
|
|
28
|
+
if (summary.prompt) parts.push(`Prompt: ${summary.prompt}`);
|
|
27
29
|
return { text: `tools ${label}${summary.fallback ? " ?" : ""}`, title: parts.join(" ") };
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -172,6 +172,14 @@ module Samagotchi
|
|
|
172
172
|
end
|
|
173
173
|
end
|
|
174
174
|
|
|
175
|
+
# A session's folder as the projection holds it (its file's, read at
|
|
176
|
+
# the last scan or touch): what a live read that loads no session
|
|
177
|
+
# file checks refs against. nil for a session the hub hasn't seen.
|
|
178
|
+
# @return [String, nil]
|
|
179
|
+
def working_directory(id)
|
|
180
|
+
@monitor.synchronize { @sessions[id]&.session&.working_directory }
|
|
181
|
+
end
|
|
182
|
+
|
|
175
183
|
# Register a sink (#call(event)). With +snapshot:+ the current
|
|
176
184
|
# projection comes back with the handle, taken in the same step, so
|
|
177
185
|
# every event after it is newer than it.
|
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.72.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -279,6 +279,7 @@ files:
|
|
|
279
279
|
- lib/samagotchi/empty_answer_retry.rb
|
|
280
280
|
- lib/samagotchi/engine.rb
|
|
281
281
|
- lib/samagotchi/engine_peers.rb
|
|
282
|
+
- lib/samagotchi/event_routes.rb
|
|
282
283
|
- lib/samagotchi/events.rb
|
|
283
284
|
- lib/samagotchi/extension_load.rb
|
|
284
285
|
- lib/samagotchi/file_ref.rb
|
|
@@ -459,6 +460,7 @@ files:
|
|
|
459
460
|
- lib/samagotchi/session_inbox.rb
|
|
460
461
|
- lib/samagotchi/session_manager.rb
|
|
461
462
|
- lib/samagotchi/session_metrics.rb
|
|
463
|
+
- lib/samagotchi/session_model_settings.rb
|
|
462
464
|
- lib/samagotchi/session_observer.rb
|
|
463
465
|
- lib/samagotchi/session_retention.rb
|
|
464
466
|
- lib/samagotchi/session_setting.rb
|