samagotchi 0.97.0 → 0.98.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -1
  3. data/docs/architecture.md +10 -2
  4. data/docs/bundle-authoring.md +5 -3
  5. data/docs/cli.md +8 -3
  6. data/docs/hooks.md +59 -0
  7. data/docs/internals/bundles.md +1 -1
  8. data/docs/internals/engine.md +18 -0
  9. data/docs/plugins.md +8 -1
  10. data/docs/sessions.md +1 -1
  11. data/lib/samagotchi/answer_refs.rb +16 -9
  12. data/lib/samagotchi/bundle_command.rb +2 -2
  13. data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
  14. data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +39 -5
  15. data/lib/samagotchi/bundles/source-links/manifest.yml +5 -5
  16. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  17. data/lib/samagotchi/engine.rb +26 -2
  18. data/lib/samagotchi/hooks/bundle_loader.rb +18 -15
  19. data/lib/samagotchi/hooks.rb +4 -1
  20. data/lib/samagotchi/llm/errors.rb +51 -17
  21. data/lib/samagotchi/memory_bundle/bundle_hook.rb +28 -9
  22. data/lib/samagotchi/memory_bundle/manifest.rb +17 -0
  23. data/lib/samagotchi/output_formatter.rb +8 -2
  24. data/lib/samagotchi/pending_question.rb +7 -3
  25. data/lib/samagotchi/plugin/api.rb +5 -0
  26. data/lib/samagotchi/question_asked.rb +83 -0
  27. data/lib/samagotchi/question_desk.rb +3 -2
  28. data/lib/samagotchi/testing/bundle.rb +18 -1
  29. data/lib/samagotchi/version.rb +1 -1
  30. data/lib/samagotchi/web/app.rb +30 -9
  31. data/lib/samagotchi/web/public/app.js +44 -11
  32. data/lib/samagotchi/web/public/index.html +36 -13
  33. data/lib/samagotchi/web/public/info_bar_model.js +0 -2
  34. data/lib/samagotchi/web/public/lists_view.js +32 -8
  35. data/lib/samagotchi/web/public/markdown_place.js +25 -8
  36. data/lib/samagotchi/web/public/question_cards.js +23 -6
  37. data/lib/samagotchi/web/public/session_rename.js +88 -15
  38. data/lib/samagotchi/web/public/session_title.js +6 -0
  39. data/lib/samagotchi/web/public/stage_frame.js +24 -14
  40. data/lib/samagotchi/web/public/stage_model.js +4 -2
  41. data/lib/samagotchi/web/public/stage_slots.js +41 -9
  42. data/lib/samagotchi/web/public/stage_view.js +149 -8
  43. data/lib/samagotchi/web/public/stream_handlers.js +5 -1
  44. data/lib/samagotchi/web/public/turn_model.js +20 -0
  45. data/lib/samagotchi/web/public/turn_view.js +22 -9
  46. metadata +2 -1
@@ -174,6 +174,26 @@ export function askingText(turn) {
174
174
  return gen.text.trim();
175
175
  }
176
176
 
177
+ // A plugin's refs on the reply that asks (a question's refs, from its
178
+ // pending question): kept on the asking step's gen, the last one, while
179
+ // the turn goes on, for the stage's asking slot (askingRefs) and the
180
+ // step's closed text (turn_view.js). A non-array or an empty list keeps
181
+ // what the gen has. The gen, or null.
182
+ export function questionRefs(turn, refs) {
183
+ if (!turn || turn.ended || !Array.isArray(refs) || !refs.length) return null;
184
+ const gen = turn.gens[turn.gens.length - 1];
185
+ if (!gen) return null;
186
+ gen.refs = refs;
187
+ return gen;
188
+ }
189
+
190
+ // The refs on the asking text (questionRefs), while it shows
191
+ // (askingText's rule); else null.
192
+ export function askingRefs(turn) {
193
+ if (!askingText(turn)) return null;
194
+ return turn.gens[turn.gens.length - 1].refs || null;
195
+ }
196
+
177
197
  // The turn is over (completed, canceled, failed, the worker gone).
178
198
  export function turnEnded(turn) {
179
199
  const closed = closeCurrent(turn);
@@ -21,7 +21,7 @@
21
21
  // chrome (CSS :has), so a plain answer streams like today's bubble, and is
22
22
  // removed at the end: the answer bubble takes its place.
23
23
  import { html, nothing, render } from "./vendor/lit-html.js";
24
- import { setAnswerRefs } from "./answer_refs.js";
24
+ import { answerRefsOf, setAnswerRefs } from "./answer_refs.js";
25
25
  import { bindRowRefs } from "./refs.js";
26
26
  import { leavesBlock } from "./card.js";
27
27
  import { failedTurnText, normalize, sourceLabel } from "./format.js";
@@ -70,13 +70,14 @@ function thinkingTemplate({ body, wrapped = true, line = null, cls = "thinking",
70
70
  // the newest last), the live "…" until the first one, or a closed step's
71
71
  // full text. None when there is nothing to say. A closed step's text is
72
72
  // set whole (.textContent): its refs (ref_scan.js) split it into spans
73
- // that a text part's update would leave behind.
74
- function textTemplate(text, { live, sentences, placeholder }) {
73
+ // that a text part's update would leave behind. +at+: a reloaded step's
74
+ // message index, for its refs (data-answer-at, as an answer's).
75
+ function textTemplate(text, { live, sentences, placeholder, at = null }) {
75
76
  if (live && sentences?.length) {
76
77
  return html`<div class="gen-text streaming"><div class="narration">${sentences.map((s) => html`<div class="sentence">${s}</div>`)}</div></div>`;
77
78
  }
78
79
  if (live) return placeholder ? html`<div class="gen-text streaming">…</div>` : nothing;
79
- return text ? html`<div class="gen-text" .textContent=${text}></div>` : nothing;
80
+ return text ? html`<div class="gen-text" data-answer-at=${at ?? nothing} .textContent=${text}></div>` : nothing;
80
81
  }
81
82
 
82
83
  // One step, live or reloaded. +gen+: { text, thinking, tools, answer } (a
@@ -86,16 +87,17 @@ function textTemplate(text, { live, sentences, placeholder }) {
86
87
  // answers, its +items+ (tool rows, notices, cards: nodes or templates; a
87
88
  // reload's are its rows' nodes), the thinking +body+ node, the ticker's
88
89
  // +line+, the narration's +sentences+ and "…" +placeholder+, +on+ (the
89
- // click and toggle handlers).
90
+ // click and toggle handlers). A reload's +at+: the step's message index
91
+ // (textTemplate).
90
92
  export function stepTemplate(gen, st = {}) {
91
93
  const { live = false, index = 0, open = live, steers = [], items = [], body = gen.thinking, line = null,
92
- sentences = null, placeholder = false, on = {} } = st;
94
+ sentences = null, placeholder = false, on = {}, at = null } = st;
93
95
  const text = gen.answer ? "" : gen.text || "";
94
96
  const label = live ? { repeats: false, content: `step ${index + 1}` } : closedLabel({ ...gen, text });
95
97
  const failed = !live && model.failedCount(gen.tools) > 0;
96
98
  // A step left with its thinking only reads "thinking" once.
97
99
  const wrapped = live || !!text || gen.tools.length > 0;
98
- return html`<details class=${`gen${live ? " live" : ""}${failed ? " has-failed" : ""}`} .open=${open}><summary class=${label.repeats ? "repeats" : nothing} @click=${on.stepClick ?? nothing}>${label.content}</summary>${steers}${gen.thinking ? thinkingTemplate({ body, wrapped, line, on }) : nothing}${textTemplate(text, { live, sentences, placeholder })}${items.length ? html`<div class="activity-body">${items}</div>` : nothing}</details>`;
100
+ return html`<details class=${`gen${live ? " live" : ""}${failed ? " has-failed" : ""}`} .open=${open}><summary class=${label.repeats ? "repeats" : nothing} @click=${on.stepClick ?? nothing}>${label.content}</summary>${steers}${gen.thinking ? thinkingTemplate({ body, wrapped, line, on }) : nothing}${textTemplate(text, { live, sentences, placeholder, at })}${items.length ? html`<div class="activity-body">${items}</div>` : nothing}</details>`;
99
101
  }
100
102
 
101
103
  // A hook's line (hook_notice) as a step's row. data.line is a whole row of
@@ -254,6 +256,10 @@ export function createTurnView({
254
256
  live: !entry.closed, index: entry.index, open: entry.open, steers: entry.steers, items: entry.items,
255
257
  body: entry.thinkingText, line: entry.line, sentences: entry.sentences, placeholder: entry.placeholder, on: entry.on,
256
258
  }), entry.frag, { renderBefore: entry.end });
259
+ // A closed step that asked a question knows its refs (model.questionRefs):
260
+ // the page pairs them with its text at the turn's end (linkTextRefs).
261
+ const text = entry.closed && entry.gen.refs ? entry.el?.querySelector(":scope > .gen-text") : null;
262
+ if (text && !answerRefsOf(text)) setAnswerRefs(text, entry.gen.refs);
257
263
  }
258
264
 
259
265
  // A step's nodes (lit's start marker, the <details>, the end marker), to
@@ -776,6 +782,10 @@ export function createTurnView({
776
782
  // The running turn's asking reply (turn_model.js askingText): what a
777
783
  // question or approval card keeps once answered.
778
784
  askingText: () => model.askingText(turn),
785
+ // A question's refs (its pending question's) on the step that asks it.
786
+ questionRefs(refs) {
787
+ if (turn) model.questionRefs(turn, refs);
788
+ },
779
789
  step(i) {
780
790
  const entry = turn && genEls.get(turn.gens[i]);
781
791
  return entry ? { el: entry.el, summary: entry.el.querySelector(":scope > summary"), thinkingWrap: thinkingWrap(entry) } : null;
@@ -880,6 +890,8 @@ export function turnHistoryNodes(items, timing, { thumbs }) {
880
890
  // The steers the step answered first, where the live rows went.
881
891
  steers: (group.steps[k].steers || []).map((i) => steerTemplate(i, false)),
882
892
  items: gen.tools.length ? [rowNodes(gen.tools, thumbs)] : [],
893
+ // Its text knows its message's refs (a question's), as an answer does.
894
+ at: group.steps[k].i,
883
895
  }));
884
896
  parts.push(html`<details class="turn-work done"><summary>${model.blockSummary({ gens, ended: true })}</summary>${steps}</details>`);
885
897
  outlines.push(gens);
@@ -910,8 +922,9 @@ export function turnHistoryNodes(items, timing, { thumbs }) {
910
922
  const out = nodesOf(parts);
911
923
  // A finished turn's block as the live end leaves it: its outline, open.
912
924
  [...out.children].filter((el) => el.matches(".turn-work")).forEach((block, i) => outlineBlock(block, outlines[i] || []));
913
- // Each answer bubble knows its message's refs (answer_refs.js; the
914
- // page pairs them after the render), the index stays out of the DOM.
925
+ // Each answer bubble and step text knows its message's refs
926
+ // (answer_refs.js; the page pairs them after the render), the index
927
+ // stays out of the DOM.
915
928
  for (const bubble of out.querySelectorAll("[data-answer-at]")) {
916
929
  setAnswerRefs(bubble, items[Number(bubble.dataset.answerAt)]?.display_refs);
917
930
  bubble.removeAttribute("data-answer-at");
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.97.0
4
+ version: 0.98.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -485,6 +485,7 @@ files:
485
485
  - lib/samagotchi/prompt_note.rb
486
486
  - lib/samagotchi/prompt_warmup.rb
487
487
  - lib/samagotchi/question_answer.rb
488
+ - lib/samagotchi/question_asked.rb
488
489
  - lib/samagotchi/question_desk.rb
489
490
  - lib/samagotchi/queued_command.rb
490
491
  - lib/samagotchi/reasoning_history.rb