samagotchi 0.152.0 → 0.153.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 +18 -1
- data/docs/bundle-authoring.md +1 -1
- data/docs/bundles.md +84 -0
- data/docs/cli.md +3 -3
- data/docs/configuration.md +19 -14
- data/docs/guardrails.md +7 -5
- data/docs/hooks.md +4 -3
- data/docs/internals/anthropic.md +3 -3
- data/docs/internals/attached-context.md +3 -0
- data/docs/internals/engine.md +9 -5
- data/docs/internals/participants.md +1 -1
- data/docs/internals/prompt-caching.md +7 -3
- data/docs/internals/web-frontend.md +20 -17
- data/docs/plugins.md +29 -15
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +1 -1
- data/docs/testing.md +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/bundles/web-search/manifest.yml +13 -0
- data/lib/samagotchi/bundles/web-search/plugin.rb +174 -0
- data/lib/samagotchi/engine.rb +10 -5
- data/lib/samagotchi/guardrails/review_boundary.rb +4 -3
- data/lib/samagotchi/llm_context_stale.rb +2 -2
- data/lib/samagotchi/message_parts.rb +3 -2
- data/lib/samagotchi/toolkit.rb +8 -5
- data/lib/samagotchi/toolkit_binding.rb +30 -5
- data/lib/samagotchi/toolkit_report_command.rb +9 -5
- data/lib/samagotchi/toolkit_suggest.rb +12 -6
- data/lib/samagotchi/tools/builtin_calls.rb +14 -10
- data/lib/samagotchi/tools/deferred_tools.rb +2 -2
- data/lib/samagotchi/tools/registry.rb +44 -20
- data/lib/samagotchi/tools/tool_index.rb +27 -21
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/public/app.js +61 -357
- data/lib/samagotchi/web/public/composer_mode.js +110 -0
- data/lib/samagotchi/web/public/owner_claim.js +81 -0
- data/lib/samagotchi/web/public/owner_verify.js +112 -0
- data/lib/samagotchi/web/public/session_fields.js +3 -3
- data/lib/samagotchi/web/public/strip_grip.js +3 -3
- data/lib/samagotchi/web/public/strip_hidden.js +65 -0
- data/lib/samagotchi/web/public/version_notices.js +132 -0
- data/lib/samagotchi/worker.rb +6 -0
- metadata +8 -1
|
@@ -38,7 +38,7 @@ import { createParticipantsControl } from "./participants_panel.js";
|
|
|
38
38
|
import { createSettingsControl } from "./settings_chip.js";
|
|
39
39
|
import { renderInfoBar } from "./info_bar.js";
|
|
40
40
|
import { createMoreSheet } from "./more_sheet.js";
|
|
41
|
-
import { sessionBar
|
|
41
|
+
import { sessionBar } from "./info_bar_model.js";
|
|
42
42
|
import { createShownTexts } from "./shown_texts.js";
|
|
43
43
|
import { emptySessionState, sessionStateFrom } from "./session_state.js";
|
|
44
44
|
import { sessionFieldsFrom } from "./session_payload.js";
|
|
@@ -63,13 +63,12 @@ import { createTaskStopper } from "./task_stop.js";
|
|
|
63
63
|
import { createSystemOneWrongAction } from "./system_one_wrong.js";
|
|
64
64
|
import { appendQuote } from "./annotations.js";
|
|
65
65
|
import { applyComposerText, applyRowTemplate, fetchApply } from "./review_apply.js";
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
66
|
+
import { nameVerdictCards } from "./verdict_card.js";
|
|
67
|
+
import { redrawFresh } from "./verdict_fresh.js";
|
|
68
68
|
import { authorBadge, nameUnnamedBadges } from "./participant_names.js";
|
|
69
69
|
import { parsePresets } from "./annotate_presets.js";
|
|
70
70
|
import { createAnnotateBar } from "./annotate_bar.js";
|
|
71
71
|
import { VERIFY_PRESET, VERIFY_UNAVAILABLE, verifyWorkerUnavailable, withVerify } from "./verify_preset.js";
|
|
72
|
-
import { OWNER_FINAL, verifyTurnOf } from "./verify_target.js";
|
|
73
72
|
import { actionLine, annotationActions, composerWithAction } from "./annotate_actions.js";
|
|
74
73
|
import { createBlockHost } from "./block_host.js";
|
|
75
74
|
import { copyText, installCopy } from "./copy.js";
|
|
@@ -80,24 +79,21 @@ import { createQuestionCards } from "./question_cards.js";
|
|
|
80
79
|
import { splitSnapshotCards } from "./card.js";
|
|
81
80
|
import { PROMPT_LINE_PX, PROMPT_MIN_PX } from "./composer_size.js";
|
|
82
81
|
import { mountComposer } from "./composer_mount.js";
|
|
83
|
-
import { readStored, writeStored } from "./storage.js";
|
|
84
82
|
import { createCommandList } from "./command_list.js";
|
|
85
83
|
import { ALL_SESSIONS_HASH, sessionHash } from "./route.js";
|
|
86
84
|
import { hashIntent } from "./hash_intent.js";
|
|
87
|
-
import {
|
|
88
|
-
|
|
89
|
-
} from "./
|
|
85
|
+
import { claimFromHash } from "./claim_link.js";
|
|
86
|
+
import { createOwnerClaim } from "./owner_claim.js";
|
|
87
|
+
import { createOwnerVerify } from "./owner_verify.js";
|
|
90
88
|
import { scopeDir, scopeTemplate } from "./scope.js";
|
|
91
89
|
import { createProjectPicker } from "./project_picker.js";
|
|
92
|
-
import {
|
|
90
|
+
import { render } from "./vendor/lit-html.js";
|
|
93
91
|
import { shouldFollowScroll } from "./scroll.js";
|
|
94
|
-
import {
|
|
95
|
-
import { createStripGrip } from "./strip_grip.js";
|
|
92
|
+
import { createStripHidden } from "./strip_hidden.js";
|
|
96
93
|
import { waitingOn } from "./notify.js";
|
|
97
94
|
import { applySessionEvent, sortedByUpdated } from "./sessions_list.js";
|
|
98
95
|
import { createNotifications } from "./notifications.js";
|
|
99
96
|
import { renameShown, tabTitle, topBarTitle } from "./session_title.js";
|
|
100
|
-
import { mergeVersionState, restartConfirmText, updateFrameToast, versionNotice } from "./update_notice.js";
|
|
101
97
|
import {
|
|
102
98
|
appendAboveLiveTiming, mergeTiming, normalizeTiming,
|
|
103
99
|
} from "./timing.js";
|
|
@@ -112,6 +108,7 @@ import { createComposerCommands } from "./composer.js";
|
|
|
112
108
|
import { createStreamHandlers } from "./stream_handlers.js";
|
|
113
109
|
import { createListsView } from "./lists_view.js";
|
|
114
110
|
import { createDeliveryControl, QUEUE_NEEDS_WORKER } from "./delivery_line.js";
|
|
111
|
+
import { createComposerMode } from "./composer_mode.js";
|
|
115
112
|
import { liveSlots } from "./stage_model.js";
|
|
116
113
|
import { currentGen } from "./turn_model.js";
|
|
117
114
|
import { toolName } from "./activity.js";
|
|
@@ -121,7 +118,7 @@ import { createUnqueue } from "./unqueue.js";
|
|
|
121
118
|
import { createStartPage } from "./start_page.js";
|
|
122
119
|
import { createSessionActions } from "./session_actions.js";
|
|
123
120
|
import { createStopSheet } from "./stop_sheet.js";
|
|
124
|
-
import {
|
|
121
|
+
import { createVersionNotices } from "./version_notices.js";
|
|
125
122
|
import { createRename } from "./session_rename.js";
|
|
126
123
|
import { createSendTurn } from "./send_turn.js";
|
|
127
124
|
import { createMarkdownTip } from "./markdown_tip.js";
|
|
@@ -251,7 +248,7 @@ const featureGates = createFeatureGates([
|
|
|
251
248
|
{
|
|
252
249
|
name: "verify",
|
|
253
250
|
unavailable: (cur) => verifyWorkerUnavailable(cur.workerFeatures, cur.bridgeUp),
|
|
254
|
-
redraw: () => redrawFresh(historyEl,
|
|
251
|
+
redraw: () => redrawFresh(historyEl, ownerVerify.freshState()),
|
|
255
252
|
},
|
|
256
253
|
// Revise on an answer and the composer's Revising chip (revise_stack.js).
|
|
257
254
|
{
|
|
@@ -263,7 +260,6 @@ const featureGates = createFeatureGates([
|
|
|
263
260
|
featureGates.prime(store.current);
|
|
264
261
|
// The open session's id, for the stale-answer guards (latest.js stillOn).
|
|
265
262
|
const openId = () => store.selected;
|
|
266
|
-
let pendingVerifyAnchor = null;
|
|
267
263
|
// The composer's armed Revise ({turn_id, label}: the answer the next
|
|
268
264
|
// message rewrites), shown as its chip; Verify's lifecycle (an emptied
|
|
269
265
|
// composer, another session, the send's ack let go of it). Set only
|
|
@@ -528,9 +524,9 @@ function openSessionEvents() {
|
|
|
528
524
|
// The list redraws the lists and the bar (its children chip, its chain's
|
|
529
525
|
// ← → links): store.update({sessions}).
|
|
530
526
|
snapshot: (data) => store.batch(() => {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (data.chi_update)
|
|
527
|
+
versionNotices.notice(data);
|
|
528
|
+
versionNotices.reconnected(data.version);
|
|
529
|
+
if (data.chi_update) versionNotices.frame(data.chi_update, { snapshot: true });
|
|
534
530
|
notifications.noticeSessions("snapshot", data);
|
|
535
531
|
store.update({ sessions: applySessionEvent(store.sessions, "snapshot", data) });
|
|
536
532
|
sweepFoldsOnce();
|
|
@@ -548,8 +544,8 @@ function openSessionEvents() {
|
|
|
548
544
|
store.update({ sessions: applySessionEvent(store.sessions, "session_gone", data) });
|
|
549
545
|
leaveGoneSession(data.id, "deleted");
|
|
550
546
|
},
|
|
551
|
-
chi: (data) =>
|
|
552
|
-
chi_update: (data) =>
|
|
547
|
+
chi: (data) => versionNotices.notice(data),
|
|
548
|
+
chi_update: (data) => versionNotices.frame(data),
|
|
553
549
|
}, {
|
|
554
550
|
dir: scope.dir,
|
|
555
551
|
onError: () => {
|
|
@@ -570,72 +566,6 @@ function openSessionEvents() {
|
|
|
570
566
|
|
|
571
567
|
const EVENTS_REOPEN_MS = 3000;
|
|
572
568
|
|
|
573
|
-
// The chi versions carried by each event snapshot and `chi` frame. A toast
|
|
574
|
-
// says when chi web was upgraded, a newer one is installed, or an aged one
|
|
575
|
-
// is available to update to.
|
|
576
|
-
// Once per notice per tab: every reconnect's snapshot says it again.
|
|
577
|
-
const versions = { served: null, installed: null };
|
|
578
|
-
// The sessions whose Restart is under way (their button says so): one
|
|
579
|
-
// session's restart never blocks another's.
|
|
580
|
-
const restartingIds = new Set();
|
|
581
|
-
// An available release's notice is once per version per browser, not per
|
|
582
|
-
// tab: chi web says it in every snapshot until the owner updates.
|
|
583
|
-
const AVAILABLE_SHOWN_KEY = "chi_update_available_shown";
|
|
584
|
-
const versionNoticesShown = new Set();
|
|
585
|
-
const availableShown = readStored(AVAILABLE_SHOWN_KEY);
|
|
586
|
-
if (availableShown) versionNoticesShown.add(`available:${availableShown}`);
|
|
587
|
-
let versionToast = null;
|
|
588
|
-
|
|
589
|
-
function noticeVersions(data) {
|
|
590
|
-
Object.assign(versions, mergeVersionState(versions, data));
|
|
591
|
-
const notice = versionNotice({ loaded: document.body.dataset.version, served: versions.served,
|
|
592
|
-
installed: versions.installed, available: versions.available,
|
|
593
|
-
updateBlocked: versions.updateBlocked, auto: versions.auto, restart: versions.restart,
|
|
594
|
-
autoOffers: versions.autoOffers, shown: versionNoticesShown });
|
|
595
|
-
store.touch("infoBar"); // the worker badge compares with the newest installed
|
|
596
|
-
if (!notice) return;
|
|
597
|
-
versionNoticesShown.add(notice.key);
|
|
598
|
-
if (notice.key.startsWith("available:")) writeStored(AVAILABLE_SHOWN_KEY, notice.key.slice("available:".length));
|
|
599
|
-
const text = notice.releaseUrl
|
|
600
|
-
? html`${notice.text} <a href=${notice.releaseUrl} target="_blank" rel="noopener noreferrer">RubyGems</a>`
|
|
601
|
-
: notice.text;
|
|
602
|
-
const action = notice.reload
|
|
603
|
-
? { label: "Reload", run: () => location.reload() }
|
|
604
|
-
: notice.updateVersion
|
|
605
|
-
? { label: "Update", run: () => updateSheet.install(notice.updateVersion) }
|
|
606
|
-
: notice.actions
|
|
607
|
-
? notice.actions.map((name) => ({
|
|
608
|
-
label: name === "restartWorkers" ? "Restart workers" : "Restart chi web",
|
|
609
|
-
run: () => (name === "restartWorkers" ? updateSheet.restartWorkers() : updateSheet.restartWeb()),
|
|
610
|
-
}))
|
|
611
|
-
: notice.command
|
|
612
|
-
? { label: "Copy", run: () => copyUpdateCommand(notice.command) }
|
|
613
|
-
: null;
|
|
614
|
-
// One version toast at a time: a newer notice (Reload after a restart)
|
|
615
|
-
// supersedes the one before it.
|
|
616
|
-
toasts.dismiss(versionToast);
|
|
617
|
-
versionToast = toasts.show(text, action, { sticky: true });
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function updateFrame(data, { snapshot = false } = {}) {
|
|
621
|
-
const wasOpen = updateSheet.isOpen();
|
|
622
|
-
if (!updateSheet.receive(data)) return;
|
|
623
|
-
if (data.operation === "install" && data.state === "done" && data.version) noticeVersions({ installed: data.version });
|
|
624
|
-
const text = updateFrameToast(data, { wasOpen, snapshot });
|
|
625
|
-
if (text) showToast(text, { label: "View", run: () => updateSheet.view() }, { sticky: true });
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
function copyUpdateCommand(command) {
|
|
629
|
-
if (!navigator.clipboard?.writeText) {
|
|
630
|
-
showToast(`Clipboard unavailable; run ${command}`);
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
navigator.clipboard.writeText(command).then(
|
|
634
|
-
() => showToast(`Copied ${command}`),
|
|
635
|
-
() => showToast(`Couldn't copy ${command}`),
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
569
|
// The selected session's card changed: its owner and, without a live
|
|
640
570
|
// stream, its status show in the info bar; a worker whose bridge is up
|
|
641
571
|
// (a turn another client sent woke one, or a replacement after a kill)
|
|
@@ -798,60 +728,18 @@ function setSessionHash(id) {
|
|
|
798
728
|
// the composer stop addressing the session the URL has left behind.
|
|
799
729
|
function selectFromHash() {
|
|
800
730
|
const claim = claimFromHash(location.hash);
|
|
801
|
-
if (claim)
|
|
731
|
+
if (claim) ownerClaim.pending(claim);
|
|
802
732
|
const intent = hashIntent(location.hash, { selected: store.selected, claim });
|
|
803
733
|
if (intent.kind === "session" && intent.id !== store.selected) select(intent.id);
|
|
804
|
-
else if (claim)
|
|
734
|
+
else if (claim) ownerClaim.showPending();
|
|
805
735
|
else if (intent.kind === "start") clearSelection();
|
|
806
736
|
}
|
|
807
|
-
|
|
808
|
-
//
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
// The hash is the session's again (select() already set it when it
|
|
814
|
-
// switched), so the same link opened again is a hashchange.
|
|
815
|
-
function showPendingClaim() {
|
|
816
|
-
const claim = pendingClaim;
|
|
817
|
-
if (!claim || claim.sessionId !== store.selected) return;
|
|
818
|
-
pendingClaim = null;
|
|
819
|
-
if (!allViewOpen() && location.hash !== sessionHash(store.selected)) {
|
|
820
|
-
history.replaceState(null, "", location.pathname + location.search + sessionHash(store.selected));
|
|
821
|
-
}
|
|
822
|
-
if (showClaim(historyEl, claim, { roots: CLAIM_ROOTS, opts: claimOpts() })) return;
|
|
823
|
-
historyEl.scrollTop = 0;
|
|
824
|
-
showToast("The linked turn isn't in this session.");
|
|
825
|
-
}
|
|
826
|
-
// How the participant page shows this page's text (claim_link.js).
|
|
827
|
-
function claimOpts() {
|
|
828
|
-
return { home: document.body.dataset.home || "", cwd: store.current.dir || "" };
|
|
829
|
-
}
|
|
830
|
-
// Annotate's "Copy link" for a selection in a turn's answer, step or own
|
|
831
|
-
// message: the participant page's link when the session has a person
|
|
832
|
-
// active (it opens in the browser their review link was opened in: one
|
|
833
|
-
// link per browser, the cookie), else this page's own.
|
|
834
|
-
function claimLinkFor(source, range) {
|
|
835
|
-
// Not in a Revise stack's switched copy (revise_switch.js: view only, no claim key; Verify isn't offered there either).
|
|
836
|
-
if (!store.selected || !["answer", "step", "user"].includes(source.kind) || !historyEl.contains(source.root)
|
|
837
|
-
|| source.root.closest("[data-stack-view]")) return null;
|
|
838
|
-
const turnId = turnIdOf(historyEl, source.root);
|
|
839
|
-
if (!turnId) return null;
|
|
840
|
-
const claim = { turnId, ...selectionClaim(source.root, range, claimOpts()) };
|
|
841
|
-
if (!claim.q) return null;
|
|
842
|
-
const target = participantsControl.claimTarget();
|
|
843
|
-
if (!target) {
|
|
844
|
-
return { url: `${location.origin}${location.pathname}${location.search}${ownerClaimHash(store.selected, claim)}`,
|
|
845
|
-
title: "A link to this on your page (no person is invited to this session)" };
|
|
846
|
-
}
|
|
847
|
-
const whose = target.names.map((name) => `${name}'s`).join(" or ");
|
|
848
|
-
return { url: participantClaimLink(target.base, store.selected, claim),
|
|
849
|
-
title: `A link to this for ${whose} browser: it opens where their review link was opened (one link per browser). Opened here, it shows your page.` };
|
|
850
|
-
}
|
|
851
|
-
async function copyClaimLink({ url }) {
|
|
852
|
-
if (await copyText(url)) showToast("Link copied");
|
|
853
|
-
else showToast(`Couldn't copy: ${url}`, null, { sticky: true });
|
|
854
|
-
}
|
|
737
|
+
// The page's claim links (owner_claim.js): one opened here, once drawn, and
|
|
738
|
+
// annotate's Copy link.
|
|
739
|
+
const ownerClaim = createOwnerClaim({
|
|
740
|
+
historyEl, store, claimTarget: () => participantsControl.claimTarget(),
|
|
741
|
+
toast: (...args) => showToast(...args), copyText, allViewOpen, home: document.body.dataset.home || "",
|
|
742
|
+
});
|
|
855
743
|
function applyView() {
|
|
856
744
|
const open = allViewOpen();
|
|
857
745
|
if (!open) {
|
|
@@ -881,82 +769,12 @@ window.addEventListener("hashchange", () => {
|
|
|
881
769
|
selectFromHash();
|
|
882
770
|
});
|
|
883
771
|
|
|
884
|
-
//
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
narrowScreen.addEventListener("change", () => {
|
|
890
|
-
updateComposerMode();
|
|
891
|
-
// ⋯ is a phone's alone: a window grown past it takes its sheet along.
|
|
892
|
-
if (!narrowScreen.matches && moreSheet.isOpen()) moreSheet.close();
|
|
772
|
+
// The composer's placeholder, hint, buttons and delivery row (composer_mode.js).
|
|
773
|
+
const composerMode = createComposerMode({
|
|
774
|
+
els: { body: document.body, prompt: promptEl, hint: composeHintEl, button: actionBtn, attach: attachBtn, empty: emptyEl,
|
|
775
|
+
history: historyEl },
|
|
776
|
+
store, liveTurn, startPage, deliveryControl, moreSheet, fitPrompt, placeToast,
|
|
893
777
|
});
|
|
894
|
-
function setPlaceholder(long, short) {
|
|
895
|
-
promptEl.placeholder = narrowScreen.matches ? short : long;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// The key hint under the composer (shown while it has focus; on the start
|
|
899
|
-
// page only: in a session the placeholder carries it, the hint row is gone).
|
|
900
|
-
const SEND_HINT = "Enter to send · Shift+Enter for a newline · ↑ history";
|
|
901
|
-
|
|
902
|
-
let hadSession = false;
|
|
903
|
-
// While a turn runs, Send still sends: the prompt merges into the turn at
|
|
904
|
-
// its next step (steering). Cancel is its own button then.
|
|
905
|
-
function updateComposerMode() {
|
|
906
|
-
// In a session the box is one line: its minimum follows the class.
|
|
907
|
-
if (document.body.classList.toggle("has-session", !!store.selected) !== hadSession) {
|
|
908
|
-
hadSession = !hadSession;
|
|
909
|
-
// Back on the start page: its remembered model, not the last link's.
|
|
910
|
-
if (!hadSession) startPage.restoreStartPick();
|
|
911
|
-
fitPrompt();
|
|
912
|
-
placeToast();
|
|
913
|
-
}
|
|
914
|
-
startPage.syncModelPick();
|
|
915
|
-
// The start page's choice was for a new session: opening a session drops it.
|
|
916
|
-
if (store.selected) startPage.dropChoices();
|
|
917
|
-
const running = liveTurn.running && !!store.selected;
|
|
918
|
-
// No images for a session a terminal owns (it takes no message from here);
|
|
919
|
-
// on a phone none while a turn runs either: the row keeps its room for the
|
|
920
|
-
// message box (steering).
|
|
921
|
-
attachBtn.hidden = (!!store.selected && store.current.owner === "tui") || (running && narrowScreen.matches);
|
|
922
|
-
syncDeliveryLine();
|
|
923
|
-
if (running) {
|
|
924
|
-
setPlaceholder("Running… a message goes in at the next step · Enter to send · Alt+Enter queues", "Steer the turn…");
|
|
925
|
-
composeHintEl.textContent = SEND_HINT;
|
|
926
|
-
actionBtn.textContent = "Send";
|
|
927
|
-
emptyEl.classList.add("hidden");
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
if (!store.selected) {
|
|
931
|
-
setPlaceholder("Ask chi anything…", "Ask chi anything…");
|
|
932
|
-
composeHintEl.textContent = "Enter to start · Shift+Enter for a newline";
|
|
933
|
-
actionBtn.textContent = "Start";
|
|
934
|
-
actionBtn.disabled = false;
|
|
935
|
-
emptyEl.classList.remove("hidden");
|
|
936
|
-
historyEl.replaceChildren();
|
|
937
|
-
} else if (store.current.owner === "tui") {
|
|
938
|
-
setPlaceholder("A terminal chi owns this session · run it with chi --shared to send from here", "A terminal chi owns this session");
|
|
939
|
-
composeHintEl.textContent = "";
|
|
940
|
-
actionBtn.textContent = "Send";
|
|
941
|
-
actionBtn.disabled = true;
|
|
942
|
-
emptyEl.classList.add("hidden");
|
|
943
|
-
} else {
|
|
944
|
-
// The keys are in the delivery line's row above.
|
|
945
|
-
setPlaceholder("Message chi…", "Message chi…");
|
|
946
|
-
composeHintEl.textContent = SEND_HINT;
|
|
947
|
-
actionBtn.textContent = "Send";
|
|
948
|
-
actionBtn.disabled = false;
|
|
949
|
-
emptyEl.classList.add("hidden");
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
// The delivery line's row in a session (delivery_line.js): the key hint
|
|
954
|
-
// while idle, where a message goes in while the turn runs.
|
|
955
|
-
function syncDeliveryLine() {
|
|
956
|
-
// A terminal owns the session: the composer is off, so is its row.
|
|
957
|
-
if (!store.selected || store.current.owner === "tui") deliveryControl.show("off");
|
|
958
|
-
else deliveryControl.show(liveTurn.running && store.current.owner !== "tui" ? "running" : "idle");
|
|
959
|
-
}
|
|
960
778
|
|
|
961
779
|
function setTurnRunning(running) {
|
|
962
780
|
// A running turn the stage held none of goes with it (runningWithoutTurn).
|
|
@@ -1015,7 +833,7 @@ function onInitEvent(type, data) {
|
|
|
1015
833
|
// "delegated by" parent, its commands, its card actions) never shows for
|
|
1016
834
|
// the next one, also when the next one's read fails.
|
|
1017
835
|
function forgetSession({ keepVerify = false } = {}) {
|
|
1018
|
-
if (!keepVerify)
|
|
836
|
+
if (!keepVerify) ownerVerify.clear();
|
|
1019
837
|
if (!keepVerify) setReviseAnchor(null);
|
|
1020
838
|
store.batch(() => {
|
|
1021
839
|
store.replaceCurrent(emptySessionState());
|
|
@@ -1061,7 +879,7 @@ promptEl.addEventListener("input", () => noteDraft());
|
|
|
1061
879
|
promptEl.addEventListener("input", () => {
|
|
1062
880
|
if (promptEl.value.trim()) return;
|
|
1063
881
|
deliveryControl.release();
|
|
1064
|
-
|
|
882
|
+
ownerVerify.clear();
|
|
1065
883
|
setReviseAnchor(null);
|
|
1066
884
|
});
|
|
1067
885
|
|
|
@@ -1147,7 +965,7 @@ async function resync({ endText = null } = {}) {
|
|
|
1147
965
|
const kept = keptCommandBubbles(historyEl, id);
|
|
1148
966
|
resetTurnView();
|
|
1149
967
|
renderSessionView(data, { kept });
|
|
1150
|
-
|
|
968
|
+
ownerClaim.showPending();
|
|
1151
969
|
// Preview is read-only: a worker is woken only by POST /turn, so attach
|
|
1152
970
|
// only when a live bridge exists.
|
|
1153
971
|
store.update({ current: { streamSeq: typeof data.last_event_seq === "number" ? (data.last_event_id || data.last_event_seq) : null } });
|
|
@@ -1276,7 +1094,7 @@ function renderHistory(items) {
|
|
|
1276
1094
|
}
|
|
1277
1095
|
if (typeof items[0] === "object" && items[0].role) {
|
|
1278
1096
|
historyEl.replaceChildren(turnHistoryNodes(items, store.current.timing, { thumbs: (images) => thumbsTemplate(store.selected, images), workingSet: !!workingSet }));
|
|
1279
|
-
|
|
1097
|
+
ownerVerify.renderSaved();
|
|
1280
1098
|
// The live dividers count from the history's last activity.
|
|
1281
1099
|
gapClock.seed(lastActivity(turnGroups(items, store.current.timing), items));
|
|
1282
1100
|
linkTextRefs();
|
|
@@ -1389,7 +1207,7 @@ function endTurnView(kind, endText = null, { emptyAnswer = null, stopped = false
|
|
|
1389
1207
|
}
|
|
1390
1208
|
|
|
1391
1209
|
infoTextEl.addEventListener("click", (e) => {
|
|
1392
|
-
if (e.target.closest(".worker-restart"))
|
|
1210
|
+
if (e.target.closest(".worker-restart")) versionNotices.restart();
|
|
1393
1211
|
// Pushed as openAllView does, so the view's back returns here.
|
|
1394
1212
|
if (e.target.closest(".children-chip")) {
|
|
1395
1213
|
e.preventDefault();
|
|
@@ -1411,55 +1229,10 @@ function placeApply(data) {
|
|
|
1411
1229
|
appendToHistory(elementOf(applyRowTemplate(data.turn_id, badge)));
|
|
1412
1230
|
}
|
|
1413
1231
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
if (!known) patch.verdicts = [...(store.current.verdicts || []), record];
|
|
1419
|
-
// A fresh verdict ends its check's "verifying fresh" (the card shows it).
|
|
1420
|
-
if (record.fresh) patch.verifyFresh = settleRunning(store.current.verifyFresh, record);
|
|
1421
|
-
store.update({ current: patch });
|
|
1422
|
-
const badge = record.by && authorBadge(record.by);
|
|
1423
|
-
placeVerdictWithFresh(historyEl, record, {
|
|
1424
|
-
who: badge ? badge.name : "You",
|
|
1425
|
-
author: record.by || null,
|
|
1426
|
-
roots: CLAIM_ROOTS,
|
|
1427
|
-
opts: claimOpts(),
|
|
1428
|
-
onMissingCheck: (missing) => {
|
|
1429
|
-
const hash = ownerCheckHash(store.sessions, store.selected, missing.check_turn_id);
|
|
1430
|
-
if (hash) location.hash = hash;
|
|
1431
|
-
else showToast(CHECK_NOT_ON_PAGE);
|
|
1432
|
-
},
|
|
1433
|
-
}, verifyFreshState());
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
// The owner's Verify fresh on an unclear verdict's card (verdict_fresh.js):
|
|
1437
|
-
// always theirs to start; its running check links to the child's session.
|
|
1438
|
-
const verifyFreshErrors = {};
|
|
1439
|
-
function verifyFreshState() {
|
|
1440
|
-
const id = store.selected;
|
|
1441
|
-
const unavailable = verifyWorkerUnavailable(store.current.workerFeatures, store.current.bridgeUp);
|
|
1442
|
-
return {
|
|
1443
|
-
verdicts: store.current.verdicts || [],
|
|
1444
|
-
running: store.current.verifyFresh || [],
|
|
1445
|
-
errors: verifyFreshErrors,
|
|
1446
|
-
canStart: !!id && !unavailable,
|
|
1447
|
-
unavailableReason: unavailable ? VERIFY_UNAVAILABLE : null,
|
|
1448
|
-
start: async (of) => {
|
|
1449
|
-
try {
|
|
1450
|
-
const body = await api(freshURL(id, of), { method: "POST", body: "{}" });
|
|
1451
|
-
return { ok: true, child_id: body.child_id || null };
|
|
1452
|
-
} catch (error) {
|
|
1453
|
-
return { ok: false, error: error.message };
|
|
1454
|
-
}
|
|
1455
|
-
},
|
|
1456
|
-
childHref: (childId) => sessionHash(childId),
|
|
1457
|
-
};
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
function renderSavedVerdicts() {
|
|
1461
|
-
for (const record of store.current.verdicts || []) placeVerdict(record);
|
|
1462
|
-
}
|
|
1232
|
+
// The owner's Verify (owner_verify.js): verdict cards, Verify fresh, the claim.
|
|
1233
|
+
const ownerVerify = createOwnerVerify({
|
|
1234
|
+
historyEl, store, api, claim: ownerClaim, liveTurn, toast: (text) => showToast(text),
|
|
1235
|
+
});
|
|
1463
1236
|
|
|
1464
1237
|
// Apply: the owner's Apply turn into the composer, after what is typed
|
|
1465
1238
|
// there; never sent from here.
|
|
@@ -1482,31 +1255,6 @@ historyEl.addEventListener("click", async (e) => {
|
|
|
1482
1255
|
}
|
|
1483
1256
|
});
|
|
1484
1257
|
|
|
1485
|
-
// The open session's worker badge (info_bar_model.js workerBadgeFor).
|
|
1486
|
-
function currentWorkerBadge() {
|
|
1487
|
-
return workerBadgeFor(store.selected, store.current, versions);
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
// Restart: the session moves to a new worker on the newest chi; the page
|
|
1491
|
-
// follows it (its bridge_up), the composer's draft stays as it is.
|
|
1492
|
-
async function restartWorker() {
|
|
1493
|
-
const badge = currentWorkerBadge();
|
|
1494
|
-
if (!badge || !badge.restart || restartingIds.has(store.selected)) return;
|
|
1495
|
-
if (!confirm(restartConfirmText(store.current.workerVersion, badge.newest))) return;
|
|
1496
|
-
const id = store.selected;
|
|
1497
|
-
restartingIds.add(id);
|
|
1498
|
-
store.touch("infoBar");
|
|
1499
|
-
try {
|
|
1500
|
-
const result = await restartSession(id);
|
|
1501
|
-
showToast(result.version ? `Restarted on chi ${result.version}` : "Restarting: the new worker is starting");
|
|
1502
|
-
} catch (e) {
|
|
1503
|
-
showToast(cantText("restart", e), null, { sticky: true });
|
|
1504
|
-
} finally {
|
|
1505
|
-
restartingIds.delete(id);
|
|
1506
|
-
store.touch("infoBar");
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
1258
|
// The session bar (info_bar_model.js sessionBar): its buttons, and the
|
|
1511
1259
|
// info line rendered by info_bar.js.
|
|
1512
1260
|
// The open session waits on the user: its card is up, or its summary says so
|
|
@@ -1518,8 +1266,8 @@ function sessionWaits() {
|
|
|
1518
1266
|
function updateInfoBar() {
|
|
1519
1267
|
annotateBar?.refresh();
|
|
1520
1268
|
const { buttons, bar } = sessionBar({
|
|
1521
|
-
id: store.selected, state: store.current, sessions: store.sessions, versions, cardTitle: (s) => lists.cardTitle(s),
|
|
1522
|
-
continuing: sessionActions.continuing, restarting:
|
|
1269
|
+
id: store.selected, state: store.current, sessions: store.sessions, versions: versionNotices.versions(), cardTitle: (s) => lists.cardTitle(s),
|
|
1270
|
+
continuing: sessionActions.continuing, restarting: versionNotices.restarting, streaming: !!streamControl, running: liveTurn.running, waiting: sessionWaits(),
|
|
1523
1271
|
workingSet: workingSet ? workingSet.chip() : undefined, participants: participantsControl.chip(),
|
|
1524
1272
|
});
|
|
1525
1273
|
const stopBtn = document.getElementById("infoStopBtn");
|
|
@@ -1731,8 +1479,8 @@ const streamHandlers = createStreamHandlers({
|
|
|
1731
1479
|
workingSet,
|
|
1732
1480
|
participantsControl,
|
|
1733
1481
|
placeApply,
|
|
1734
|
-
placeVerdict,
|
|
1735
|
-
restoreVerify: (anchor) =>
|
|
1482
|
+
placeVerdict: ownerVerify.placeVerdict,
|
|
1483
|
+
restoreVerify: (anchor) => ownerVerify.set(anchor),
|
|
1736
1484
|
restoreRevise,
|
|
1737
1485
|
sentIds,
|
|
1738
1486
|
earlyRestores,
|
|
@@ -1842,9 +1590,9 @@ const sendTurnControl = createSendTurn({
|
|
|
1842
1590
|
sentIds,
|
|
1843
1591
|
earlyRestores,
|
|
1844
1592
|
api: { createIdleSession, uploadImage, sendTurn },
|
|
1845
|
-
verify: () =>
|
|
1846
|
-
clearVerify: (anchor) =>
|
|
1847
|
-
restoreVerify: (anchor) =>
|
|
1593
|
+
verify: () => ownerVerify.anchor(),
|
|
1594
|
+
clearVerify: (anchor) => ownerVerify.clear(anchor),
|
|
1595
|
+
restoreVerify: (anchor) => ownerVerify.set(anchor),
|
|
1848
1596
|
// Not while the gate refuses (the chip says so; the line goes as a plain message).
|
|
1849
1597
|
revise: () => (reviseUnavailable() ? null : pendingReviseAnchor),
|
|
1850
1598
|
clearRevise: (anchor) => { if (pendingReviseAnchor === anchor) setReviseAnchor(null); },
|
|
@@ -2051,11 +1799,11 @@ function restoreDraft(draft, opener) {
|
|
|
2051
1799
|
}
|
|
2052
1800
|
|
|
2053
1801
|
const toasts = createToasts({ el: $("#toast"), onChange: placeToast });
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
restartWeb,
|
|
1802
|
+
// chi's versions, the update sheet and the worker Restart (version_notices.js).
|
|
1803
|
+
// After toasts: openSessionEvents, its first caller, runs at boot.
|
|
1804
|
+
const versionNotices = createVersionNotices({
|
|
1805
|
+
store, toasts, dialog: $("#updateSheet"), loadedVersion: document.body.dataset.version,
|
|
1806
|
+
api: { updateChi, restartWorkers, restartWeb, restartSession },
|
|
2059
1807
|
});
|
|
2060
1808
|
|
|
2061
1809
|
function showToast(text, action = null, options = {}) { toasts.show(text, action, options); }
|
|
@@ -2066,7 +1814,7 @@ function showToast(text, action = null, options = {}) { toasts.show(text, action
|
|
|
2066
1814
|
// quote to the composer, the session's plugins' pills
|
|
2067
1815
|
// (chi.annotation, annotate_actions.js), which fill the composer with their
|
|
2068
1816
|
// command line and the quote, or send it at once, and Copy link in a turn
|
|
2069
|
-
// (
|
|
1817
|
+
// (owner_claim.js linkFor).
|
|
2070
1818
|
function fillComposer(text) {
|
|
2071
1819
|
openForVisit();
|
|
2072
1820
|
promptEl.value = text;
|
|
@@ -2081,14 +1829,12 @@ annotateBar = createAnnotateBar({
|
|
|
2081
1829
|
presets: withVerify(parsePresets(document.body.dataset.annotatePresets)),
|
|
2082
1830
|
disabledPreset: (preset) => preset === VERIFY_PRESET
|
|
2083
1831
|
&& verifyWorkerUnavailable(store.current.workerFeatures, store.current.bridgeUp) ? VERIFY_UNAVAILABLE : null,
|
|
2084
|
-
offeredPreset: (preset, source, range) => preset !== VERIFY_PRESET || !!
|
|
1832
|
+
offeredPreset: (preset, source, range) => preset !== VERIFY_PRESET || !!ownerVerify.anchorFor({ source, range }),
|
|
2085
1833
|
canAnnotate: () => !!store.selected && store.current.owner !== "tui",
|
|
2086
1834
|
roots: turnRoots,
|
|
2087
1835
|
isLive: (root) => liveTurn.running && turnDom.containsLive(root),
|
|
2088
1836
|
onQuote: (text) => fillComposer(appendQuote(promptEl.value, text)),
|
|
2089
|
-
onPreset: (preset, fields) =>
|
|
2090
|
-
pendingVerifyAnchor = preset === VERIFY_PRESET ? verifyAnchorFor(fields) : null;
|
|
2091
|
-
},
|
|
1837
|
+
onPreset: (preset, fields) => ownerVerify.set(preset === VERIFY_PRESET ? ownerVerify.anchorFor(fields) : null),
|
|
2092
1838
|
actions: () => annotationActions(store.current.commands),
|
|
2093
1839
|
running: () => liveTurn.running,
|
|
2094
1840
|
onAction: (action, fields) => {
|
|
@@ -2096,21 +1842,11 @@ annotateBar = createAnnotateBar({
|
|
|
2096
1842
|
if (action.send) runSessionCommand(line);
|
|
2097
1843
|
else fillComposer(composerWithAction(promptEl.value, action, line));
|
|
2098
1844
|
},
|
|
2099
|
-
link:
|
|
2100
|
-
onLink:
|
|
1845
|
+
link: ownerClaim.linkFor,
|
|
1846
|
+
onLink: ownerClaim.copyLink,
|
|
2101
1847
|
dock: () => $("#dock"),
|
|
2102
1848
|
});
|
|
2103
1849
|
|
|
2104
|
-
// Verify's claim: a selection in an ended turn's final answer (the only
|
|
2105
|
-
// text Engine#verified_anchor takes; verify_target.js), else null and no
|
|
2106
|
-
// Verify on the bar.
|
|
2107
|
-
function verifyAnchorFor({ source, range }) {
|
|
2108
|
-
if (source?.kind !== "answer" || !range) return null;
|
|
2109
|
-
const running = liveTurn.running ? { turnId: liveTurn.timing.turnId || null } : null;
|
|
2110
|
-
const turnId = verifyTurnOf(historyEl, source.root, OWNER_FINAL, { running });
|
|
2111
|
-
const quote = turnId ? selectionClaim(source.root, range, claimOpts()).q : "";
|
|
2112
|
-
return turnId && quote ? { turn_id: turnId, quote } : null;
|
|
2113
|
-
}
|
|
2114
1850
|
historyEl.addEventListener("scroll", annotateBar.schedule);
|
|
2115
1851
|
// A command bubble's Apply (CacheConfirm): sends the change's --yes line
|
|
2116
1852
|
// once; its own bubble then says what it did.
|
|
@@ -2158,43 +1894,11 @@ const notifications = createNotifications({
|
|
|
2158
1894
|
allEvents: (handlers, opts) => openEvents(handlers, opts),
|
|
2159
1895
|
});
|
|
2160
1896
|
|
|
2161
|
-
// The
|
|
2162
|
-
//
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
}
|
|
2167
|
-
// A short window hides the strip by itself, so the stage keeps room for a
|
|
2168
|
-
// card that waits (its question and choices); the grip brings it back
|
|
2169
|
-
// until the window is tall again. Not saved: the saved choice is the
|
|
2170
|
-
// user's own hide or show.
|
|
2171
|
-
const shortWindow = window.matchMedia(SHORT_WINDOW_QUERY);
|
|
2172
|
-
let stripShownWhileShort = false;
|
|
2173
|
-
// The strip's grip (strip_grip.js): the handle on the edge between the strip
|
|
2174
|
-
// and the chat. It is given the hidden state and the toggle; it speaks its own
|
|
2175
|
-
// title and aria-expanded, refreshed below after every change.
|
|
2176
|
-
const stripGripRefresh = createStripGrip({
|
|
2177
|
-
gripEl: $("#stripGrip"),
|
|
2178
|
-
isHidden: () => document.body.classList.contains("strip-hidden"),
|
|
2179
|
-
setHidden: (hidden) => setStripHidden(hidden),
|
|
2180
|
-
});
|
|
2181
|
-
function applyStripHidden() {
|
|
2182
|
-
document.body.classList.toggle("strip-hidden", stripAutoHidden({
|
|
2183
|
-
saved: stripHiddenSaved(), short: shortWindow.matches, shownWhileShort: stripShownWhileShort,
|
|
2184
|
-
}));
|
|
2185
|
-
stripGripRefresh();
|
|
2186
|
-
updateSessionTitle();
|
|
2187
|
-
}
|
|
2188
|
-
function setStripHidden(hidden) {
|
|
2189
|
-
if (hidden) lists.familyPop.close();
|
|
2190
|
-
writeStored("chi_strip_hidden", hidden ? "1" : "0");
|
|
2191
|
-
stripShownWhileShort = !hidden && shortWindow.matches;
|
|
2192
|
-
applyStripHidden();
|
|
2193
|
-
}
|
|
2194
|
-
applyStripHidden();
|
|
2195
|
-
shortWindow.addEventListener("change", () => {
|
|
2196
|
-
if (!shortWindow.matches) stripShownWhileShort = false;
|
|
2197
|
-
applyStripHidden();
|
|
1897
|
+
// The strip hidden or shown (strip_hidden.js): its grip, the saved choice and
|
|
1898
|
+
// the short window's rule; applied now.
|
|
1899
|
+
createStripHidden({
|
|
1900
|
+
gripEl: $("#stripGrip"), body: document.body, onApply: () => updateSessionTitle(),
|
|
1901
|
+
closeFamilyPop: () => lists.familyPop.close(),
|
|
2198
1902
|
});
|
|
2199
1903
|
|
|
2200
1904
|
// Esc leaves select mode first, then the all-sessions view.
|
|
@@ -2216,7 +1920,7 @@ store.on("settingsPanel", () => settingsControl.refresh());
|
|
|
2216
1920
|
// The working set's list offers pin only under forget.
|
|
2217
1921
|
store.on("workingSetStrategy", () => workingSet?.strategyChanged());
|
|
2218
1922
|
store.on("featureGates", () => featureGates.check(store.current));
|
|
2219
|
-
store.on("composer",
|
|
1923
|
+
store.on("composer", composerMode.update);
|
|
2220
1924
|
store.on("composer", () => objectives.redraw());
|
|
2221
1925
|
// The answers' Revise buttons follow liveTurn.running too (none on the
|
|
2222
1926
|
// running turn's answer), which touches the composer.
|