samagotchi 0.62.0 → 0.63.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 +33 -1
- data/README.md +4 -1
- data/bin/chi +3 -2
- data/docs/cli.md +4 -1
- data/docs/configuration.md +1 -1
- data/docs/context.md +13 -3
- data/docs/hooks.md +6 -3
- data/docs/memory.md +1 -1
- data/docs/plugins.md +89 -2
- data/docs/releasing.md +1 -1
- data/docs/sub-agent.md +6 -0
- data/docs/testing.md +7 -4
- data/lib/samagotchi/answer_refs.rb +18 -26
- data/lib/samagotchi/bridge.rb +23 -1
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +22 -0
- data/lib/samagotchi/bundles/claude-peer/plugin.rb +406 -0
- data/lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md +104 -0
- data/lib/samagotchi/bundles/claude-peer/scripts/pair +66 -0
- data/lib/samagotchi/bundles/claude-peer/skill_claude_peer.md +60 -0
- data/lib/samagotchi/bundles/dev/manifest.yml +2 -1
- data/lib/samagotchi/bundles/loop-guard/manifest.yml +2 -2
- data/lib/samagotchi/bundles/loop-guard/plugin.rb +17 -6
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
- data/lib/samagotchi/context_absorber.rb +25 -6
- data/lib/samagotchi/idle_recap.rb +2 -3
- data/lib/samagotchi/image_store.rb +8 -0
- data/lib/samagotchi/plugin/attached_context.rb +7 -4
- data/lib/samagotchi/recap_setup.rb +3 -3
- data/lib/samagotchi/row_refs.rb +3 -4
- data/lib/samagotchi/session.rb +12 -0
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +6 -3
- data/lib/samagotchi/terminal_ui/attached_loop.rb +26 -9
- data/lib/samagotchi/terminal_ui.rb +31 -11
- data/lib/samagotchi/tools/execute.rb +22 -4
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +6 -4
- data/lib/samagotchi/web/public/activity.js +2 -2
- data/lib/samagotchi/web/public/app.js +47 -91
- data/lib/samagotchi/web/public/card.js +73 -23
- data/lib/samagotchi/web/public/chains.js +6 -4
- data/lib/samagotchi/web/public/command_view.js +39 -35
- data/lib/samagotchi/web/public/copy.js +7 -15
- data/lib/samagotchi/web/public/diff_view.js +20 -23
- data/lib/samagotchi/web/public/format.js +0 -48
- data/lib/samagotchi/web/public/images.js +12 -16
- data/lib/samagotchi/web/public/question_cards.js +3 -2
- data/lib/samagotchi/web/public/read_view.js +10 -9
- data/lib/samagotchi/web/public/scope.js +23 -0
- data/lib/samagotchi/web/public/timing.js +0 -7
- data/lib/samagotchi/web/public/turn_html.js +119 -59
- data/lib/samagotchi/web/public/turn_view.js +73 -116
- data/lib/samagotchi/web/public/vendor/directive.js +8 -0
- data/lib/samagotchi/web/public/vendor/directives/unsafe-html.js +8 -0
- data/lib/samagotchi/worker.rb +46 -30
- data/lib/samagotchi/worker_inbound.rb +12 -11
- metadata +8 -1
|
@@ -24,13 +24,15 @@ import { html, nothing, render } from "./vendor/lit-html.js";
|
|
|
24
24
|
import { setAnswerRefs } from "./answer_refs.js";
|
|
25
25
|
import { bindRowRefs } from "./refs.js";
|
|
26
26
|
import { leavesBlock } from "./card.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { thumbsHtml } from "./images.js";
|
|
27
|
+
import { failedTurnText, normalize, sourceLabel } from "./format.js";
|
|
28
|
+
import { thumbsTemplate } from "./images.js";
|
|
30
29
|
import { shouldFollowScroll } from "./scroll.js";
|
|
31
30
|
import { hookNoticeLabel } from "./turn_events.js";
|
|
32
|
-
import {
|
|
33
|
-
|
|
31
|
+
import {
|
|
32
|
+
answerBubbleTemplate, cancelLineTemplate, commandBubbleTemplate, elementOf, emptyAnswerTemplate, noteBubbleTemplate, shellCommandMessage,
|
|
33
|
+
steerRowTemplate, toolRowInnerTemplate, toolRowTemplate, userBubbleTemplate,
|
|
34
|
+
} from "./turn_html.js";
|
|
35
|
+
import { formatDuration, turnGroups, turnTimingText } from "./timing.js";
|
|
34
36
|
import { createSentenceFeed } from "./sentences.js";
|
|
35
37
|
import { createTicker } from "./thinking_ticker.js";
|
|
36
38
|
import * as model from "./turn_model.js";
|
|
@@ -110,13 +112,6 @@ function nodesOf(template) {
|
|
|
110
112
|
return frag;
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
// +markup+ (any HTML string) as nodes.
|
|
114
|
-
function rawNodes(markup) {
|
|
115
|
-
const t = document.createElement("template");
|
|
116
|
-
t.innerHTML = markup;
|
|
117
|
-
return t.content;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
115
|
// ---- A finished block's outline ----
|
|
121
116
|
|
|
122
117
|
// A finished block as its outline (turn_model.js outlineRuns of +gens+, its
|
|
@@ -170,43 +165,16 @@ export function outlineBlock(blockEl, gens) {
|
|
|
170
165
|
return true;
|
|
171
166
|
}
|
|
172
167
|
|
|
173
|
-
// Reloaded tool rows (turn_html.js
|
|
168
|
+
// Reloaded tool rows (turn_html.js toolRowTemplate) as nodes, each row's
|
|
174
169
|
// plugin refs bound to its title (refs.js bindRowRefs).
|
|
175
170
|
function rowNodes(rows, thumbs) {
|
|
176
|
-
const nodes =
|
|
171
|
+
const nodes = nodesOf(rows.map((r) => toolRowTemplate(r, { thumbs })));
|
|
177
172
|
[...nodes.children].forEach((el, i) => bindRowRefs(el, rows[i]));
|
|
178
173
|
return nodes;
|
|
179
174
|
}
|
|
180
175
|
|
|
181
176
|
// ---- The live view ----
|
|
182
177
|
|
|
183
|
-
// The parts of a drawn tool row a user may have changed (the command's
|
|
184
|
-
// raw toggle, an opened diff) or that never change once there (the
|
|
185
|
-
// thumbs): a redraw keeps them as they are.
|
|
186
|
-
const KEPT_PARTS = ["activity-command", "activity-diff", "activity-read", "thumbs"];
|
|
187
|
-
|
|
188
|
-
const childWith = (el, cls) => [...el.children].find((c) => c.classList.contains(cls));
|
|
189
|
-
|
|
190
|
-
// A live tool row drawn again from its activity row (status, title,
|
|
191
|
-
// output): the reloaded row's markup (toolRowInnerHtml), the kept parts
|
|
192
|
-
// and the stop-task button carried over. +html+ is that markup.
|
|
193
|
-
function redrawRow(el, markup) {
|
|
194
|
-
const fresh = htmlElement(`<div>${markup}</div>`);
|
|
195
|
-
for (const cls of KEPT_PARTS) {
|
|
196
|
-
const old = childWith(el, cls);
|
|
197
|
-
const drawn = childWith(fresh, cls);
|
|
198
|
-
if (old && drawn) drawn.replaceWith(old);
|
|
199
|
-
}
|
|
200
|
-
const stop = childWith(el, "task-stop");
|
|
201
|
-
el.replaceChildren(...fresh.childNodes);
|
|
202
|
-
if (stop) rowAnchor(el).after(stop);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Where a row's stop-task button goes: after its title (else its tool).
|
|
206
|
-
function rowAnchor(el) {
|
|
207
|
-
return childWith(el, "activity-params") || childWith(el, "activity-tool");
|
|
208
|
-
}
|
|
209
|
-
|
|
210
178
|
// The container seams (the stage view composes this view, stage_view.js):
|
|
211
179
|
// blockHost(el) puts the turn's block in the page (default: the history);
|
|
212
180
|
// follow() keeps the live part in view (default: the history's
|
|
@@ -485,10 +453,11 @@ export function createTurnView({
|
|
|
485
453
|
onChange();
|
|
486
454
|
}
|
|
487
455
|
|
|
488
|
-
// A tool row is the reloaded row's
|
|
489
|
-
//
|
|
490
|
-
const
|
|
491
|
-
|
|
456
|
+
// A live tool row is its own element, the reloaded row's template
|
|
457
|
+
// (turn_html.js toolRowInnerTemplate) rendered into it on each event.
|
|
458
|
+
const rowThumbs = (images) => thumbsTemplate(sessionId(), images);
|
|
459
|
+
// A row element → its stop-task button (syncTaskStop).
|
|
460
|
+
const rowStops = new WeakMap();
|
|
492
461
|
|
|
493
462
|
// A row, notice or card into the step's activity; a step that wrote no
|
|
494
463
|
// narration before it drops the "…".
|
|
@@ -504,43 +473,48 @@ export function createTurnView({
|
|
|
504
473
|
apply(r);
|
|
505
474
|
const entry = genEls.get(r.gen);
|
|
506
475
|
let el = entry.rows.get(r.row.key);
|
|
507
|
-
const inner = toolRowInnerHtml(r.row, { thumbs: rowThumbs });
|
|
508
476
|
if (!el) {
|
|
509
|
-
el =
|
|
510
|
-
|
|
511
|
-
|
|
477
|
+
el = document.createElement("div");
|
|
478
|
+
el.className = "activity-row";
|
|
479
|
+
el.dataset.key = r.row.key;
|
|
512
480
|
entry.rows.set(r.row.key, el);
|
|
481
|
+
drawRow(el, r.row);
|
|
513
482
|
addItem(entry, el);
|
|
514
483
|
} else {
|
|
515
484
|
if (!r.gen.text) entry.placeholder = false;
|
|
516
|
-
|
|
517
|
-
redrawRow(el, inner);
|
|
518
|
-
bindRowRefs(el, r.row);
|
|
519
|
-
}
|
|
520
|
-
rowHtml.set(el, inner);
|
|
485
|
+
drawRow(el, r.row);
|
|
521
486
|
// A late result in a closed step: its label and failed mark.
|
|
522
487
|
draw(entry);
|
|
523
488
|
}
|
|
524
|
-
syncTaskStop(el, r.row);
|
|
525
489
|
updateBlockSummary();
|
|
526
490
|
onChange();
|
|
527
491
|
}
|
|
528
492
|
|
|
529
|
-
// The
|
|
530
|
-
//
|
|
493
|
+
// The row rendered again (lit writes only what changed: a toggled raw
|
|
494
|
+
// command, an opened diff or read and the thumbs stay), its stop-task
|
|
495
|
+
// button in its slot, its plugin refs bound to its title (refs.js).
|
|
496
|
+
function drawRow(el, row) {
|
|
497
|
+
render(toolRowInnerTemplate(row, { thumbs: rowThumbs, stop: syncTaskStop(el, row) }), el);
|
|
498
|
+
bindRowRefs(el, row);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// The stop-task button of a running task_wait's row (after its title),
|
|
502
|
+
// nothing once the row completes.
|
|
531
503
|
function syncTaskStop(el, row) {
|
|
532
504
|
const taskId = taskStop?.target(row);
|
|
533
|
-
|
|
505
|
+
let btn = rowStops.get(el);
|
|
534
506
|
if (!taskId) {
|
|
535
|
-
|
|
536
|
-
return;
|
|
507
|
+
rowStops.delete(el);
|
|
508
|
+
return nothing;
|
|
537
509
|
}
|
|
538
510
|
if (btn) {
|
|
539
511
|
btn.disabled = taskStop.busy(taskId);
|
|
540
|
-
return;
|
|
512
|
+
return btn;
|
|
541
513
|
}
|
|
542
514
|
const command = () => taskCommand(turn?.gens, taskId);
|
|
543
|
-
|
|
515
|
+
btn = taskStopButton(taskStop, taskId, { command });
|
|
516
|
+
rowStops.set(el, btn);
|
|
517
|
+
return btn;
|
|
544
518
|
}
|
|
545
519
|
|
|
546
520
|
// A hook's line (hook_notice) as a row of the current step, above the row
|
|
@@ -560,7 +534,7 @@ export function createTurnView({
|
|
|
560
534
|
// that ends first puts it in its last step). False with no running turn.
|
|
561
535
|
function steer(data) {
|
|
562
536
|
if (!turn || turn.ended) return false;
|
|
563
|
-
pendingSteers.push(
|
|
537
|
+
pendingSteers.push(elementOf(steerRowTemplate(data)));
|
|
564
538
|
return true;
|
|
565
539
|
}
|
|
566
540
|
|
|
@@ -829,26 +803,12 @@ export function stepText(block, iteration) {
|
|
|
829
803
|
// A re-rendered failed turn's line (a .bubble.cancel, as live's
|
|
830
804
|
// addEndBubble), in turn_failed's words (failedTurnText) from what the turn
|
|
831
805
|
// record kept of it (SessionMetrics' failure: summary, message,
|
|
832
|
-
// error_class, kept_steps);
|
|
833
|
-
// before records kept that.
|
|
834
|
-
function
|
|
806
|
+
// error_class, kept_steps); nothing for any other record, or a failed one
|
|
807
|
+
// saved before records kept that.
|
|
808
|
+
function failedLineTemplate(record) {
|
|
835
809
|
const failure = record?.status === "failed" ? record.failure : null;
|
|
836
|
-
if (!failure || typeof failure !== "object") return
|
|
837
|
-
return `<div class="bubble cancel">${
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// Parts (HTML strings and templates, in order) as one fragment.
|
|
841
|
-
function fragmentOf(parts) {
|
|
842
|
-
const out = document.createDocumentFragment();
|
|
843
|
-
let markup = "";
|
|
844
|
-
for (const part of parts) {
|
|
845
|
-
if (typeof part === "string") { markup += part; continue; }
|
|
846
|
-
if (markup) out.append(rawNodes(markup));
|
|
847
|
-
markup = "";
|
|
848
|
-
out.append(nodesOf(part));
|
|
849
|
-
}
|
|
850
|
-
if (markup) out.append(rawNodes(markup));
|
|
851
|
-
return out;
|
|
810
|
+
if (!failure || typeof failure !== "object") return nothing;
|
|
811
|
+
return html`<div class="bubble cancel">${failedTurnText(failure)}</div>`;
|
|
852
812
|
}
|
|
853
813
|
|
|
854
814
|
// A reloaded history in the turn view (renderHistory's object path), as
|
|
@@ -857,53 +817,51 @@ function fragmentOf(parts) {
|
|
|
857
817
|
// the thinking, params and output, from the saved messages; status and
|
|
858
818
|
// duration from the timing's tool records), then the answer and the turn's
|
|
859
819
|
// timing line under it.
|
|
860
|
-
// +thumbs(images)+ draws a prompt's or a tool row's image thumbs
|
|
820
|
+
// +thumbs(images)+ draws a prompt's or a tool row's image thumbs (a
|
|
821
|
+
// template, images.js thumbsTemplate).
|
|
861
822
|
export function turnHistoryNodes(items, timing, { thumbs }) {
|
|
862
|
-
const
|
|
823
|
+
const timingTemplate = (group) => {
|
|
863
824
|
const record = group.record;
|
|
864
825
|
// Numbered by its place in the turn records, as live (a failed turn
|
|
865
826
|
// whose prompt went back still counts).
|
|
866
827
|
const at = (timing?.turnRecords || []).indexOf(record);
|
|
867
828
|
return record && formatDuration(record.duration_ms)
|
|
868
|
-
? `<div class="turn-timing">${
|
|
869
|
-
:
|
|
829
|
+
? html`<div class="turn-timing">${turnTimingText(at >= 0 ? at + 1 : group.turnIndex + 1, record.duration_ms, { canceled: record.status === "canceled" })}</div>`
|
|
830
|
+
: nothing;
|
|
870
831
|
};
|
|
871
|
-
//
|
|
872
|
-
// leaves it at the turn's end. +at+: the message's index, for its refs
|
|
832
|
+
// An answer's bubble; +at+, the message's index, is for its refs
|
|
873
833
|
// (data-answer-at, gone before the nodes are returned).
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
};
|
|
834
|
+
const answerBubble = (at, cls = "") => answerBubbleTemplate(items[at], { cls, at });
|
|
835
|
+
// The timing line after the bubble, not in it: where the live view
|
|
836
|
+
// leaves it at the turn's end.
|
|
837
|
+
const answerTemplate = (at, timing) => html`${answerBubble(at)}${timing}`;
|
|
879
838
|
// A user's line, labelled when chi brought it in (a delegate report). A
|
|
880
839
|
// `!cmd`'s saved message (the worker's "!(<command>)\n<output>") renders
|
|
881
840
|
// as its command bubble, as live does — not as a user bubble.
|
|
882
|
-
const
|
|
841
|
+
const userTemplate = (m, opts = {}) => {
|
|
883
842
|
const shell = shellCommandMessage(m?.content);
|
|
884
|
-
if (shell) return
|
|
885
|
-
return
|
|
843
|
+
if (shell) return commandBubbleTemplate(shell);
|
|
844
|
+
return userBubbleTemplate(m, { label: sourceLabel(m?.source), thumbs: thumbs(m.images), ...opts });
|
|
886
845
|
};
|
|
887
846
|
// The first text after a delegate report merged mid-turn, unless it is
|
|
888
847
|
// the turn's answer (shown anyway).
|
|
889
|
-
const
|
|
890
|
-
if (items[i]?.source !== "delegate_report") return
|
|
848
|
+
const reportReplyTemplate = (i, group) => {
|
|
849
|
+
if (items[i]?.source !== "delegate_report") return nothing;
|
|
891
850
|
const j = items.findIndex((m, k) => k > i && m?.role === "assistant" && String(m.content ?? "").trim());
|
|
892
|
-
if (j < 0 || j === group.answer || items.slice(i + 1, j).some((m) => m?.role === "user" && !m.merged)) return
|
|
893
|
-
|
|
894
|
-
return `<div class="bubble output report-reply${renderedMarkdown ? " markdown" : ""}" data-answer-at="${j}"${copySourceAttr(items[j], renderedMarkdown)}>${body}</div>`;
|
|
851
|
+
if (j < 0 || j === group.answer || items.slice(i + 1, j).some((m) => m?.role === "user" && !m.merged)) return nothing;
|
|
852
|
+
return answerBubble(j, " report-reply");
|
|
895
853
|
};
|
|
896
|
-
const
|
|
854
|
+
const steerTemplate = (i, bubble) => steerRowTemplate({ source: items[i].source, text: items[i].content }, { bubble });
|
|
897
855
|
const parts = [];
|
|
898
856
|
// Each block's steps, in order: its outline once the nodes exist.
|
|
899
857
|
const outlines = [];
|
|
900
858
|
for (const group of turnGroups(items, timing)) {
|
|
901
|
-
if (group.kind === "note") { parts.push(
|
|
902
|
-
if (group.kind === "steer") { parts.push(
|
|
903
|
-
if (group.kind === "answer") { parts.push(
|
|
859
|
+
if (group.kind === "note") { parts.push(noteBubbleTemplate(items[group.i])); continue; }
|
|
860
|
+
if (group.kind === "steer") { parts.push(steerTemplate(group.i, true)); continue; }
|
|
861
|
+
if (group.kind === "answer") { parts.push(answerTemplate(group.answer, nothing)); continue; }
|
|
904
862
|
const prompt = items[group.user];
|
|
905
863
|
// A context wake turn starts with its note.
|
|
906
|
-
parts.push(prompt.role === "note" ?
|
|
864
|
+
parts.push(prompt.role === "note" ? noteBubbleTemplate(prompt) : userTemplate(prompt));
|
|
907
865
|
const [only] = group.steps;
|
|
908
866
|
const ended = group.answer !== null || group.emptyAnswer != null;
|
|
909
867
|
const textless = only && (only.i === null || !String(items[only.i].content ?? ""));
|
|
@@ -917,7 +875,7 @@ export function turnHistoryNodes(items, timing, { thumbs }) {
|
|
|
917
875
|
const gens = group.steps.map((step) => ({ text: step.i === null ? "" : String(items[step.i].content ?? ""), thinking: step.thinking || "", tools: step.tools }));
|
|
918
876
|
const steps = gens.map((gen, k) => stepTemplate(gen, {
|
|
919
877
|
// The steers the step answered first, where the live rows went.
|
|
920
|
-
steers: (group.steps[k].steers || []).map((i) =>
|
|
878
|
+
steers: (group.steps[k].steers || []).map((i) => steerTemplate(i, false)),
|
|
921
879
|
items: gen.tools.length ? [rowNodes(gen.tools, thumbs)] : [],
|
|
922
880
|
}));
|
|
923
881
|
parts.push(html`<details class="turn-work done"><summary>${model.blockSummary({ gens, ended: true })}</summary>${steps}</details>`);
|
|
@@ -926,28 +884,27 @@ export function turnHistoryNodes(items, timing, { thumbs }) {
|
|
|
926
884
|
// Steers with no step to sit in, or in a turn that shows no block: on
|
|
927
885
|
// their own after the prompt.
|
|
928
886
|
const loose = noBlock ? [...group.steps.flatMap((step) => step.steers || []), ...(group.steers || [])] : group.steers || [];
|
|
929
|
-
parts.push(loose.map((i) =>
|
|
887
|
+
parts.push(loose.map((i) => steerTemplate(i, true)));
|
|
930
888
|
// The user's lines merged into the turn: the live "steered" bubbles,
|
|
931
889
|
// after its steps and before its answer. A delegate report's is
|
|
932
890
|
// followed by the text that answered it when that isn't the answer: a
|
|
933
891
|
// small model tells its user about a report before its next tool call,
|
|
934
892
|
// and that step's text would stay folded in the block.
|
|
935
893
|
parts.push((group.merged || []).map((i) =>
|
|
936
|
-
|
|
937
|
-
reportReplyHtml(i, group)).join(""));
|
|
894
|
+
html`${userTemplate(items[i], { state: "steered", step: items[i].step })}${reportReplyTemplate(i, group)}`));
|
|
938
895
|
// A turn that ended with no answer: chi's muted notice where the answer
|
|
939
896
|
// would be, before the timing, as live.
|
|
940
|
-
if (group.emptyAnswer != null) parts.push(`${
|
|
897
|
+
if (group.emptyAnswer != null) parts.push(html`${emptyAnswerTemplate(items[group.emptyAnswer])}${timingTemplate(group)}`);
|
|
941
898
|
// A turn with no answer (canceled, or running: no record yet) shows its
|
|
942
899
|
// timing after its prompt and steps, where the live view leaves it.
|
|
943
|
-
else if (group.answer === null) parts.push(
|
|
944
|
-
else parts.push(
|
|
900
|
+
else if (group.answer === null) parts.push(timingTemplate(group));
|
|
901
|
+
else parts.push(answerTemplate(group.answer, timingTemplate(group)));
|
|
945
902
|
// A canceled turn ends with its cancel line, a failed one whose steps
|
|
946
903
|
// stayed with its failure line, as the live view left them.
|
|
947
|
-
parts.push(
|
|
948
|
-
parts.push(
|
|
904
|
+
parts.push(cancelLineTemplate(group.record));
|
|
905
|
+
parts.push(failedLineTemplate(group.record));
|
|
949
906
|
}
|
|
950
|
-
const out =
|
|
907
|
+
const out = nodesOf(parts);
|
|
951
908
|
// A finished turn's block as the live end leaves it: its outline, open.
|
|
952
909
|
[...out.children].filter((el) => el.matches(".turn-work")).forEach((block, i) => outlineBlock(block, outlines[i] || []));
|
|
953
910
|
// Each answer bubble knows its message's refs (answer_refs.js; the
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// lit-html 3.3.3 (npm tarball lit-html-3.3.3.tgz, directive.js: the minified production
|
|
2
|
+
// build, unmodified but for this line and the dropped sourceMappingURL). BSD-3-Clause.
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2017 Google LLC
|
|
6
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
7
|
+
*/
|
|
8
|
+
const t={ATTRIBUTE:1,CHILD:2,PROPERTY:3,BOOLEAN_ATTRIBUTE:4,EVENT:5,ELEMENT:6},e=t=>(...e)=>({_$litDirective$:t,values:e});class i{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}}export{i as Directive,t as PartType,e as directive};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// lit-html 3.3.3 (npm tarball lit-html-3.3.3.tgz, directives/unsafe-html.js: the minified
|
|
2
|
+
// production build, unmodified but for this line and the dropped sourceMappingURL). BSD-3-Clause.
|
|
3
|
+
import{nothing as t,noChange as i}from"../lit-html.js";import{directive as r,Directive as s,PartType as n}from"../directive.js";
|
|
4
|
+
/**
|
|
5
|
+
* @license
|
|
6
|
+
* Copyright 2017 Google LLC
|
|
7
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
8
|
+
*/class e extends s{constructor(i){if(super(i),this.it=t,i.type!==n.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===t||null==r)return this._t=void 0,this.it=r;if(r===i)return r;if("string"!=typeof r)throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this._t;this.it=r;const s=[r];return s.raw=s,this._t={_$litType$:this.constructor.resultType,strings:s,values:[]}}}e.directiveName="unsafeHTML",e.resultType=1;const o=r(e);export{e as UnsafeHTMLDirective,o as unsafeHTML};
|
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -468,10 +468,10 @@ module Samagotchi
|
|
|
468
468
|
# would keep the hub's "needs you" badge on for good. Dropped and saved
|
|
469
469
|
# before the Engine and the Bridge exist (no question lock, no events).
|
|
470
470
|
def drop_dead_question
|
|
471
|
-
|
|
471
|
+
id = @session.drop_dead_question
|
|
472
|
+
return unless id
|
|
472
473
|
|
|
473
|
-
Log.info(:worker, "dead_question_dropped", id:
|
|
474
|
-
@session.pending_question = nil
|
|
474
|
+
Log.info(:worker, "dead_question_dropped", id: id)
|
|
475
475
|
save_or_log(:dead_question) { @session.save(state_dir: @state_dir) }
|
|
476
476
|
end
|
|
477
477
|
|
|
@@ -1103,22 +1103,36 @@ module Samagotchi
|
|
|
1103
1103
|
end
|
|
1104
1104
|
|
|
1105
1105
|
# Check again with the event log held, which the Bridge holds while it
|
|
1106
|
-
# queues a POST /turn, then close the Bridge so no client can
|
|
1107
|
-
# after the check, and stop the idle jobs (reminder callback,
|
|
1106
|
+
# queues a POST /turn, then close the Bridge's intake so no client can
|
|
1107
|
+
# queue one after the check, and stop the idle jobs (reminder callback,
|
|
1108
|
+
# recap). The Bridge stops after the log is let go (#stop_bridge_after).
|
|
1108
1109
|
# A client connecting from here on finds no worker: `chi --attach` fails
|
|
1109
1110
|
# and the web stream answers 503 (a small window, left as is).
|
|
1110
1111
|
# @return [Boolean] false when something came in since #due?
|
|
1111
1112
|
def leave_idle
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1113
|
+
stop_bridge_after do
|
|
1114
|
+
@engine.synchronize_events do
|
|
1115
|
+
next false unless @idle_exit.due?
|
|
1116
|
+
|
|
1117
|
+
@bridge&.close_intake
|
|
1118
|
+
@engine.stop_idle
|
|
1119
|
+
log_idle_exit
|
|
1120
|
+
true
|
|
1121
|
+
end
|
|
1119
1122
|
end
|
|
1120
1123
|
end
|
|
1121
1124
|
|
|
1125
|
+
# The Bridge stops once the block (which closed its intake with the
|
|
1126
|
+
# event log held) answers true, outside the log: Bridge#stop waits for
|
|
1127
|
+
# the requests it is answering, and one waiting on the log would hold
|
|
1128
|
+
# it for the whole grace before being killed.
|
|
1129
|
+
# @return [Boolean] what the block answered
|
|
1130
|
+
def stop_bridge_after
|
|
1131
|
+
leaving = yield
|
|
1132
|
+
@bridge&.stop if leaving
|
|
1133
|
+
leaving
|
|
1134
|
+
end
|
|
1135
|
+
|
|
1122
1136
|
# Bridge POST /exit, on a Bridge thread with the event log held.
|
|
1123
1137
|
# +delete+: the session is deleted after (/exit --delete), so no recap.
|
|
1124
1138
|
# +restart+: a new worker takes over (on the newest chi installed), so
|
|
@@ -1140,29 +1154,31 @@ module Samagotchi
|
|
|
1140
1154
|
end
|
|
1141
1155
|
|
|
1142
1156
|
# #leave_idle for an exit a client asked for: check again with the event
|
|
1143
|
-
# log held (a prompt may have come in since), then close the Bridge
|
|
1144
|
-
# stop the idle jobs. Other clients' streams no longer hold: the asker's
|
|
1157
|
+
# log held (a prompt may have come in since), then close the Bridge's
|
|
1158
|
+
# intake and stop the idle jobs (the Bridge stops after the log). Other clients' streams no longer hold: the asker's
|
|
1145
1159
|
# may not have closed yet, and a UI joining now finds the worker gone
|
|
1146
1160
|
# (:stream_closed), the window #leave_idle has too.
|
|
1147
1161
|
# @return [Boolean] false when something came in since the request
|
|
1148
1162
|
def leave_on_request
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1163
|
+
stop_bridge_after do
|
|
1164
|
+
@engine.synchronize_events do
|
|
1165
|
+
hold = if @exit_restart
|
|
1166
|
+
@idle_exit.hold_for_restart
|
|
1167
|
+
else
|
|
1168
|
+
@idle_exit.hold_for_request(requester: @exit_requested_by, streams: false)
|
|
1169
|
+
end
|
|
1170
|
+
if hold
|
|
1171
|
+
@exit_requested = nil
|
|
1172
|
+
@exit_restart = false
|
|
1173
|
+
Log.info(:worker, "exit_held", reason: hold)
|
|
1174
|
+
next false
|
|
1175
|
+
end
|
|
1161
1176
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1177
|
+
@bridge&.close_intake
|
|
1178
|
+
@engine.stop_idle
|
|
1179
|
+
Log.info(:worker, @exit_restart ? "restart_requested" : "exit_requested", by: @exit_requested_by || "a client")
|
|
1180
|
+
true
|
|
1181
|
+
end
|
|
1166
1182
|
end
|
|
1167
1183
|
end
|
|
1168
1184
|
|
|
@@ -106,9 +106,10 @@ module Samagotchi
|
|
|
106
106
|
# run): auto-attached context alone doesn't make a session worth
|
|
107
107
|
# keeping. Saved, then the subscriptions written (a crash between the
|
|
108
108
|
# two re-delivers; the note ids dedupe).
|
|
109
|
-
# +wake+: the session is idle with nothing queued, so
|
|
110
|
-
# source asked to wake
|
|
111
|
-
#
|
|
109
|
+
# +wake+: the session is idle with nothing queued, so a delivery with a
|
|
110
|
+
# wake note (an update whose source asked to wake, or a session push
|
|
111
|
+
# source's first text that asked) may start a turn (#context_wake_for):
|
|
112
|
+
# its wake note goes in as the turn's first message.
|
|
112
113
|
# @return [ContextWake, nil] the wake turn to run now
|
|
113
114
|
# @raise [SystemCallError, IOError] the caller logs it (context_failed)
|
|
114
115
|
def absorb_context(before_turn: false, wake: false)
|
|
@@ -134,7 +135,7 @@ module Samagotchi
|
|
|
134
135
|
waking && ContextWake.new(delivery: waking, turn_id: turn_id)
|
|
135
136
|
end
|
|
136
137
|
|
|
137
|
-
# A failed wake turn kept nothing but its note: the
|
|
138
|
+
# A failed wake turn kept nothing but its note: the note goes back to
|
|
138
139
|
# a plain note (no turn start, the background wording), so a reload
|
|
139
140
|
# draws no empty turn for it.
|
|
140
141
|
def unmark_wake(context_wake)
|
|
@@ -224,13 +225,13 @@ module Samagotchi
|
|
|
224
225
|
true
|
|
225
226
|
end
|
|
226
227
|
|
|
227
|
-
# The
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
# session.max_wakes, past the start
|
|
231
|
-
# the session in the last
|
|
232
|
-
#
|
|
233
|
-
# session was away.
|
|
228
|
+
# The delivery in +batch+ that may start a wake turn now: it has a wake
|
|
229
|
+
# note (ContextAbsorber decides: its source asked, wake: true),
|
|
230
|
+
# context.wake is on, the wake budget is open (WorkerWakes#context_open?:
|
|
231
|
+
# no continue offer, not paused, under session.max_wakes, past the start
|
|
232
|
+
# grace) and the source hasn't woken the session in the last
|
|
233
|
+
# CONTEXT_WAKE_WINDOW. The others stay plain notes. Within the start
|
|
234
|
+
# grace a change is a note: it came while the session was away.
|
|
234
235
|
def context_wake_for(batch, now: Time.now)
|
|
235
236
|
candidates = batch.deliveries.select(&:wake_note)
|
|
236
237
|
return nil if candidates.empty? || !context_wakes_on?
|
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.63.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -185,6 +185,11 @@ files:
|
|
|
185
185
|
- lib/samagotchi/bundles/btw/plugin.rb
|
|
186
186
|
- lib/samagotchi/bundles/check-in/manifest.yml
|
|
187
187
|
- lib/samagotchi/bundles/check-in/plugin.rb
|
|
188
|
+
- lib/samagotchi/bundles/claude-peer/manifest.yml
|
|
189
|
+
- lib/samagotchi/bundles/claude-peer/plugin.rb
|
|
190
|
+
- lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md
|
|
191
|
+
- lib/samagotchi/bundles/claude-peer/scripts/pair
|
|
192
|
+
- lib/samagotchi/bundles/claude-peer/skill_claude_peer.md
|
|
188
193
|
- lib/samagotchi/bundles/coordinator/manifest.yml
|
|
189
194
|
- lib/samagotchi/bundles/coordinator/plugin.rb
|
|
190
195
|
- lib/samagotchi/bundles/coordinator/skill_coordinator.md
|
|
@@ -600,6 +605,8 @@ files:
|
|
|
600
605
|
- lib/samagotchi/web/public/turn_model.js
|
|
601
606
|
- lib/samagotchi/web/public/turn_view.js
|
|
602
607
|
- lib/samagotchi/web/public/update_notice.js
|
|
608
|
+
- lib/samagotchi/web/public/vendor/directive.js
|
|
609
|
+
- lib/samagotchi/web/public/vendor/directives/unsafe-html.js
|
|
603
610
|
- lib/samagotchi/web/public/vendor/lit-html.js
|
|
604
611
|
- lib/samagotchi/web/qr.rb
|
|
605
612
|
- lib/samagotchi/web/server.rb
|