samagotchi 0.170.0 → 0.171.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -1
  3. data/docs/architecture.md +2 -2
  4. data/docs/cli.md +37 -23
  5. data/docs/configuration.md +1 -1
  6. data/docs/internals/block-renderers.md +2 -2
  7. data/docs/internals/bridge.md +5 -4
  8. data/docs/internals/context-telemetry.md +13 -1
  9. data/docs/internals/engine.md +4 -4
  10. data/docs/internals/inline-widgets.md +1 -1
  11. data/docs/internals/objectives.md +1 -1
  12. data/docs/internals/participants.md +235 -119
  13. data/docs/internals/web-frontend.md +164 -33
  14. data/docs/internals/working-set.md +69 -5
  15. data/docs/releasing.md +2 -1
  16. data/docs/sessions.md +6 -3
  17. data/lib/samagotchi/bridge/turn_accumulator.rb +8 -4
  18. data/lib/samagotchi/bridge.rb +39 -12
  19. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  20. data/lib/samagotchi/delivery.rb +13 -0
  21. data/lib/samagotchi/engine.rb +5 -1
  22. data/lib/samagotchi/event_fanout.rb +22 -1
  23. data/lib/samagotchi/grants.rb +5 -0
  24. data/lib/samagotchi/guardrails/review_paths.rb +2 -2
  25. data/lib/samagotchi/image_store.rb +51 -12
  26. data/lib/samagotchi/kernel_loop.rb +7 -1
  27. data/lib/samagotchi/llm/stray_tool_call.rb +7 -4
  28. data/lib/samagotchi/llm_context_stale.rb +4 -1
  29. data/lib/samagotchi/participant_messages.rb +39 -0
  30. data/lib/samagotchi/participant_revise.rb +61 -0
  31. data/lib/samagotchi/participant_uploads.rb +98 -0
  32. data/lib/samagotchi/reply_wait/poll.rb +5 -1
  33. data/lib/samagotchi/revise_anchor.rb +9 -0
  34. data/lib/samagotchi/send_command.rb +5 -0
  35. data/lib/samagotchi/session_inbox.rb +20 -12
  36. data/lib/samagotchi/session_manager/delivery.rb +7 -0
  37. data/lib/samagotchi/session_stores.rb +2 -0
  38. data/lib/samagotchi/tool_runner.rb +39 -5
  39. data/lib/samagotchi/tools/memory.rb +26 -7
  40. data/lib/samagotchi/turn_anchors.rb +0 -3
  41. data/lib/samagotchi/turn_stages/verify_anchors.rb +13 -1
  42. data/lib/samagotchi/verify_anchor.rb +6 -0
  43. data/lib/samagotchi/version.rb +1 -1
  44. data/lib/samagotchi/web/app.rb +42 -77
  45. data/lib/samagotchi/web/participant_access.rb +129 -26
  46. data/lib/samagotchi/web/participant_images.rb +185 -0
  47. data/lib/samagotchi/web/participant_review.rb +19 -138
  48. data/lib/samagotchi/web/participant_show.rb +240 -0
  49. data/lib/samagotchi/web/participant_stream_filter.rb +25 -2
  50. data/lib/samagotchi/web/participant_view.rb +331 -24
  51. data/lib/samagotchi/web/public/annotate_bar.css +1 -2
  52. data/lib/samagotchi/web/public/annotate_bar.js +1 -2
  53. data/lib/samagotchi/web/public/app.js +124 -88
  54. data/lib/samagotchi/web/public/block_host.js +7 -4
  55. data/lib/samagotchi/web/public/bubbles.js +9 -1
  56. data/lib/samagotchi/web/public/cards_offscreen.js +69 -0
  57. data/lib/samagotchi/web/public/chip_popover.js +5 -4
  58. data/lib/samagotchi/web/public/claim_link.js +4 -4
  59. data/lib/samagotchi/web/public/composer.css +4 -6
  60. data/lib/samagotchi/web/public/composer_mode.js +11 -6
  61. data/lib/samagotchi/web/public/composer_mount.js +4 -4
  62. data/lib/samagotchi/web/public/ctx.js +12 -3
  63. data/lib/samagotchi/web/public/delivery_line.js +34 -5
  64. data/lib/samagotchi/web/public/history_shape.js +5 -1
  65. data/lib/samagotchi/web/public/images.js +7 -1
  66. data/lib/samagotchi/web/public/index.html +40 -4
  67. data/lib/samagotchi/web/public/info_bar_model.js +2 -1
  68. data/lib/samagotchi/web/public/markdown_place.js +5 -4
  69. data/lib/samagotchi/web/public/owner_claim.js +3 -3
  70. data/lib/samagotchi/web/public/owner_verify.js +0 -2
  71. data/lib/samagotchi/web/public/participant_bar.js +108 -0
  72. data/lib/samagotchi/web/public/participant_main.js +289 -0
  73. data/lib/samagotchi/web/public/participant_model.js +5 -292
  74. data/lib/samagotchi/web/public/participant_names.js +6 -1
  75. data/lib/samagotchi/web/public/participant_revise.js +66 -0
  76. data/lib/samagotchi/web/public/participants_panel.js +3 -12
  77. data/lib/samagotchi/web/public/question_cards.js +14 -3
  78. data/lib/samagotchi/web/public/refs.js +53 -36
  79. data/lib/samagotchi/web/public/reload_check.js +68 -11
  80. data/lib/samagotchi/web/public/revise_stack.js +21 -10
  81. data/lib/samagotchi/web/public/send_turn.js +24 -6
  82. data/lib/samagotchi/web/public/session_fields.js +1 -1
  83. data/lib/samagotchi/web/public/session_state.js +12 -4
  84. data/lib/samagotchi/web/public/session_sync.js +9 -2
  85. data/lib/samagotchi/web/public/session_view.js +17 -7
  86. data/lib/samagotchi/web/public/stage_asking.js +6 -4
  87. data/lib/samagotchi/web/public/stage_owner_wait.js +35 -0
  88. data/lib/samagotchi/web/public/stage_view.js +18 -4
  89. data/lib/samagotchi/web/public/stage_waiting.js +74 -6
  90. data/lib/samagotchi/web/public/storage.js +22 -0
  91. data/lib/samagotchi/web/public/stream_handlers.js +2 -1
  92. data/lib/samagotchi/web/public/tokens.css +1 -1
  93. data/lib/samagotchi/web/public/turn_block.js +3 -2
  94. data/lib/samagotchi/web/public/turn_events.js +15 -6
  95. data/lib/samagotchi/web/public/turn_html.js +9 -2
  96. data/lib/samagotchi/web/public/turn_range.js +1 -2
  97. data/lib/samagotchi/web/public/verify_target.js +2 -4
  98. data/lib/samagotchi/web/public/viewer.js +59 -0
  99. data/lib/samagotchi/web/public/viewer_self.js +53 -0
  100. data/lib/samagotchi/web/public/working_set.js +11 -5
  101. data/lib/samagotchi/web/public/working_set_view.js +53 -8
  102. data/lib/samagotchi/web/public/ws_cards.js +89 -0
  103. data/lib/samagotchi/web/public/ws_peek.js +106 -0
  104. data/lib/samagotchi/web/public/ws_preview.css +126 -0
  105. data/lib/samagotchi/web/public/ws_reader.js +319 -0
  106. data/lib/samagotchi/web/public/ws_timeline.js +200 -0
  107. data/lib/samagotchi/web/public/ws_way_in.js +25 -0
  108. data/lib/samagotchi/web/session_control.rb +39 -14
  109. data/lib/samagotchi/web/session_create.rb +1 -1
  110. data/lib/samagotchi/web/session_show.rb +75 -28
  111. data/lib/samagotchi/web/viewer.rb +70 -0
  112. data/lib/samagotchi/web/working_set_routes.rb +132 -0
  113. data/lib/samagotchi/worker.rb +19 -14
  114. data/lib/samagotchi/working_set.rb +260 -8
  115. metadata +20 -6
  116. data/lib/samagotchi/web/public/participant.html +0 -218
  117. data/lib/samagotchi/web/public/participant.js +0 -719
  118. data/lib/samagotchi/web/public/participant_annotate.js +0 -76
  119. data/lib/samagotchi/web/public/review_history.js +0 -72
  120. data/lib/samagotchi/web/public/tools_overview.js +0 -42
@@ -1,76 +0,0 @@
1
- // Annotate on the participant page (participant.js): a selection in the
2
- // Plan (the review's subject) or in the thread quotes it into the comment
3
- // box, with where it came from, the way the owner's page does
4
- // (annotations.js, annotate_bar.js):
5
- //
6
- // From docs/plan.md:
7
- // > step 3
8
- //
9
- // Is step 3 safe?
10
- //
11
- // A model reply carries no label (the owner's answers carry none); a
12
- // comment names its author ("From Alex's comment:", "From my earlier
13
- // comment:"). Not annotatable: the reply still streaming, tool rows,
14
- // status lines, time gaps, the header. The bar's source and quote options
15
- // (createAnnotateBar); no DOM state beyond Element#closest, so it runs
16
- // under `node --test` with stand-ins. A module of its own: static JS is
17
- // cached at unversioned URLs, and a new export on an old module would
18
- // blank the page.
19
-
20
- import { quoteBlock } from "./annotations.js";
21
- import { isParticipantAuthor } from "./participant_model.js";
22
-
23
- // Where a node on the participant page came from: { kind, root, … } where
24
- // root is the element a selection is clipped to, or null.
25
- // plan: { path } the subject shown (#planDoc's data-path)
26
- // reply: a model reply's bubble
27
- // comment: { by, name } a prompt's bubble; by is "me", "participant",
28
- // "owner" or "automatic" (its .p-msg's data-by), name the
29
- // participant's (data-name)
30
- export function participantSource(node) {
31
- const el = node && node.nodeType === 3 ? node.parentElement : node;
32
- if (!el?.closest) return null;
33
- const plan = el.closest("#planDoc");
34
- if (plan) {
35
- // The "only the first part is shown" note isn't the subject's text.
36
- if (el.closest(".p-plan-note")) return null;
37
- return { kind: "plan", path: plan.dataset?.path || "", root: plan };
38
- }
39
- const bubble = el.closest(".p-bubble");
40
- const msg = bubble?.closest(".p-msg");
41
- if (!msg || msg.classList.contains("live")) return null;
42
- if (msg.classList.contains("reply")) return { kind: "reply", root: bubble };
43
- if (msg.classList.contains("user")) {
44
- return { kind: "comment", by: msg.dataset?.by || "owner", name: msg.dataset?.name || "", root: bubble };
45
- }
46
- return null;
47
- }
48
-
49
- // The line naming +source+ above the quote, or null (a reply).
50
- export function participantLabel(source) {
51
- if (source?.kind === "plan") return source.path ? `From ${source.path}:` : "From the plan:";
52
- if (source?.kind !== "comment") return null;
53
- if (source.by === "me") return "From my earlier comment:";
54
- if (source.by === "participant") return `From ${source.name || "a participant"}'s comment:`;
55
- if (source.by === "automatic") return "From the automatic turn:";
56
- return "From the owner's message:";
57
- }
58
-
59
- // The quote block for the selected +text+ (annotations.js quoteBlock's
60
- // lines) under +source+'s label; null when it holds no text.
61
- export function participantQuote(text, source) {
62
- const block = quoteBlock(text, { kind: "answer" });
63
- if (!block) return null;
64
- const label = participantLabel(source);
65
- return label ? `${label}\n${block}` : block;
66
- }
67
-
68
- // What a message's .p-msg carries for participantSource: data-by and, for
69
- // another participant, data-name. +message+ is a review message (the
70
- // participant route's: mine, author, author_name, automatic).
71
- export function messageAuthorData(message) {
72
- if (message?.mine) return { by: "me" };
73
- if (isParticipantAuthor(message?.author)) return { by: "participant", name: message.author_name || "" };
74
- if (message?.automatic) return { by: "automatic" };
75
- return { by: "owner" };
76
- }
@@ -1,72 +0,0 @@
1
- // A participant's ↑/↓ in the comment box (participant.js over
2
- // prompt_history.js): their own comments sent from this browser, newest
3
- // last, in localStorage under one key per grant, never the owner's GET
4
- // /api/history (the owner may open a link in the same browser, on the same
5
- // origin: each grant's list and the owner's stay apart). Read and written
6
- // through storage.js (guarded): a list that can't be read or written is just
7
- // not there. A link that ends forgets its list; one that ended while no page
8
- // was open goes when the next link's page loads (pruneReviewHistory).
9
- import { readStored, removeStored, storedKeys, writeStored } from "./storage.js";
10
-
11
- export const REVIEW_HISTORY_PREFIX = "chi_review_history:";
12
- const REVIEW_HISTORY_SESSION_PREFIX = "chi_review_history_session:";
13
- // How many comments a grant's list keeps (the oldest go first).
14
- export const REVIEW_HISTORY_MAX = 50;
15
-
16
- // The key for grant +grantId+'s list; null without one (an older server's
17
- // review data): no history then.
18
- export function reviewHistoryKey(grantId) {
19
- return grantId ? `${REVIEW_HISTORY_PREFIX}${grantId}` : null;
20
- }
21
-
22
- // The grant's comments, oldest first; [] when there are none or they can't
23
- // be read.
24
- export function readReviewHistory(grantId) {
25
- const key = reviewHistoryKey(grantId);
26
- if (!key) return [];
27
- try {
28
- const list = JSON.parse(readStored(key, "[]"));
29
- return Array.isArray(list) ? list.filter((entry) => typeof entry === "string") : [];
30
- } catch (_) {
31
- return [];
32
- }
33
- }
34
-
35
- // The list with +text+ added at the end (not twice in a row), at most
36
- // REVIEW_HISTORY_MAX long. Pure.
37
- export function withComment(list, text) {
38
- const entry = String(text || "").trim();
39
- if (!entry) return list;
40
- const next = list.at(-1) === entry ? list : [...list, entry];
41
- return next.slice(-REVIEW_HISTORY_MAX);
42
- }
43
-
44
- // A comment the server took: saved at the end of the grant's list.
45
- export function recordReviewComment(grantId, text) {
46
- const key = reviewHistoryKey(grantId);
47
- if (!key) return;
48
- writeStored(key, JSON.stringify(withComment(readReviewHistory(grantId), text)));
49
- }
50
-
51
- export function rememberReviewHistory(sessionId, grantId) {
52
- if (sessionId && grantId) writeStored(`${REVIEW_HISTORY_SESSION_PREFIX}${sessionId}`, grantId);
53
- }
54
-
55
- export function forgetReviewHistory(grantId, sessionId) {
56
- const sessionKey = sessionId ? `${REVIEW_HISTORY_SESSION_PREFIX}${sessionId}` : null;
57
- const remembered = sessionKey ? readStored(sessionKey) : null;
58
- if (sessionKey) removeStored(sessionKey);
59
- const key = reviewHistoryKey(grantId || remembered);
60
- if (key) removeStored(key);
61
- }
62
-
63
- // A participant page's first read (+sessionId+, its grant +grantId+): every
64
- // other grant's list and every other session's pointer go, so the lists of
65
- // links that ended while no page was open don't stay for good. Without
66
- // either id (the owner's view, an older server's data) nothing goes.
67
- export function pruneReviewHistory(sessionId, grantId) {
68
- if (!sessionId || !grantId) return;
69
- const keep = new Set([reviewHistoryKey(grantId), `${REVIEW_HISTORY_SESSION_PREFIX}${sessionId}`]);
70
- for (const key of storedKeys(REVIEW_HISTORY_PREFIX)) if (!keep.has(key)) removeStored(key);
71
- for (const key of storedKeys(REVIEW_HISTORY_SESSION_PREFIX)) if (!keep.has(key)) removeStored(key);
72
- }
@@ -1,42 +0,0 @@
1
- // A turn's tool rows as one line: "read 12 files · ran 3 commands · edited
2
- // 2 files (engine.rb, worker.rb)". Pure, names only (rows carry a tool name
3
- // and a project path, never args or output); the participant page folds a
4
- // turn's rows into it, and the owner's page may later.
5
- const READERS = new Set(["read"]);
6
- const COMMANDS = new Set(["execute", "bash"]);
7
- const EDITORS = new Set(["write", "edit"]);
8
- const FILE_NAMES = 3;
9
-
10
- const rowsOf = (rows) => (Array.isArray(rows) ? rows : []);
11
- const namedPath = (row) => typeof row?.path === "string" && row.path ? row.path : null;
12
- const basename = (path) => path.split(/[\\/]/).at(-1);
13
- const noun = (count, singular, plural) => `${count} ${count === 1 ? singular : plural}`;
14
-
15
- function fileCount(rows) {
16
- const paths = new Set(rows.map(namedPath).filter(Boolean));
17
- return paths.size + rows.filter((row) => !namedPath(row)).length;
18
- }
19
-
20
- function fileNames(rows) {
21
- const paths = [...new Set(rows.map(namedPath).filter(Boolean))];
22
- const names = paths.slice(0, FILE_NAMES).map(basename);
23
- if (paths.length > FILE_NAMES) names.push("…");
24
- return names.length ? ` (${names.join(", ")})` : "";
25
- }
26
-
27
- export function toolsOverview(rows) {
28
- const list = rowsOf(rows);
29
- if (!list.length) return "";
30
-
31
- const readers = list.filter((row) => READERS.has(row?.tool));
32
- const commands = list.filter((row) => COMMANDS.has(row?.tool));
33
- const editors = list.filter((row) => EDITORS.has(row?.tool));
34
- const otherCount = list.length - readers.length - commands.length - editors.length;
35
- const parts = [];
36
-
37
- if (readers.length) parts.push(`read ${noun(fileCount(readers), "file", "files")}`);
38
- if (commands.length) parts.push(`ran ${noun(commands.length, "command", "commands")}`);
39
- if (editors.length) parts.push(`edited ${noun(fileCount(editors), "file", "files")}${fileNames(editors)}`);
40
- if (otherCount) parts.push(`used ${noun(otherCount, "other tool", "other tools")}`);
41
- return parts.join(" · ");
42
- }