samagotchi 0.102.0 → 0.104.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 +93 -1
- data/bin/chi +60 -191
- data/docs/architecture.md +67 -1
- data/docs/cli.md +190 -9
- data/docs/configuration.md +7 -1
- data/docs/guardrails.md +4 -2
- data/docs/internals/context-telemetry.md +6 -2
- data/docs/internals/engine.md +5 -2
- data/docs/internals/llm-context-forget.md +11 -1
- data/docs/internals/participants.md +137 -0
- data/docs/internals/remote-sessions.md +429 -0
- data/docs/internals/working-set.md +95 -11
- data/docs/plugins.md +33 -3
- data/docs/sessions.md +16 -7
- data/docs/sub-agent.md +16 -0
- data/docs/testing.md +68 -4
- data/lib/samagotchi/answer_command.rb +27 -25
- data/lib/samagotchi/bridge.rb +31 -7
- data/lib/samagotchi/bridge_client/event_stream.rb +86 -12
- data/lib/samagotchi/bridge_client/stream_request.rb +77 -0
- data/lib/samagotchi/bridge_client.rb +26 -39
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/claude-peer/scripts/pair +30 -1
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/codex-peer/scripts/pair +49 -4
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +7 -4
- data/lib/samagotchi/bundles/guardrails/manifest.yml +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/chi_invocation.rb +40 -0
- data/lib/samagotchi/cli/command.rb +3 -2
- data/lib/samagotchi/cli/flags.rb +5 -0
- data/lib/samagotchi/cli/parent_wait.rb +22 -13
- data/lib/samagotchi/context_command.rb +80 -73
- data/lib/samagotchi/context_status.rb +15 -2
- data/lib/samagotchi/engine.rb +28 -14
- data/lib/samagotchi/guardrail_wiring.rb +7 -1
- data/lib/samagotchi/idle_recap.rb +8 -2
- data/lib/samagotchi/idle_scheduler.rb +34 -9
- data/lib/samagotchi/kernel_loop.rb +21 -2
- data/lib/samagotchi/launch_mode.rb +26 -0
- data/lib/samagotchi/llm/chat_loop.rb +8 -1
- data/lib/samagotchi/llm_context_forget.rb +13 -6
- data/lib/samagotchi/note_command.rb +8 -7
- data/lib/samagotchi/owner_lock.rb +65 -5
- data/lib/samagotchi/parent_report.rb +55 -43
- data/lib/samagotchi/participants.rb +122 -0
- data/lib/samagotchi/remote_attach_command.rb +119 -0
- data/lib/samagotchi/remote_command.rb +170 -0
- data/lib/samagotchi/remotes.rb +130 -0
- data/lib/samagotchi/run_options.rb +245 -0
- data/lib/samagotchi/send_command/busy.rb +58 -0
- data/lib/samagotchi/send_command/parent_gate.rb +23 -10
- data/lib/samagotchi/send_command/wait_target.rb +16 -3
- data/lib/samagotchi/send_command.rb +173 -108
- data/lib/samagotchi/session.rb +38 -9
- data/lib/samagotchi/session_access/contract.rb +167 -0
- data/lib/samagotchi/session_access/dispatch.rb +152 -0
- data/lib/samagotchi/session_access/http_transport.rb +109 -0
- data/lib/samagotchi/session_access/local.rb +400 -0
- data/lib/samagotchi/session_access/remote.rb +734 -0
- data/lib/samagotchi/session_access/web_bridge_client.rb +236 -0
- data/lib/samagotchi/session_access.rb +223 -0
- data/lib/samagotchi/session_archive_command.rb +9 -5
- data/lib/samagotchi/session_commands.rb +20 -15
- data/lib/samagotchi/session_continue.rb +1 -1
- data/lib/samagotchi/session_delete_command.rb +8 -4
- data/lib/samagotchi/session_inbox.rb +18 -9
- data/lib/samagotchi/session_manager.rb +27 -6
- data/lib/samagotchi/session_setup.rb +11 -6
- data/lib/samagotchi/sessions_command.rb +36 -14
- data/lib/samagotchi/share_command.rb +174 -0
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +66 -17
- data/lib/samagotchi/terminal_ui/attach_link.rb +114 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +155 -87
- data/lib/samagotchi/terminal_ui/formatting.rb +13 -1
- data/lib/samagotchi/terminal_ui/local_link.rb +135 -0
- data/lib/samagotchi/terminal_ui/remote_connector.rb +66 -0
- data/lib/samagotchi/terminal_ui/remote_link.rb +245 -0
- data/lib/samagotchi/terminal_ui/version_lines.rb +28 -3
- data/lib/samagotchi/terminal_ui.rb +6 -3
- data/lib/samagotchi/testing/waiting.rb +92 -9
- data/lib/samagotchi/title_command.rb +9 -11
- data/lib/samagotchi/tools/task_runtime.rb +17 -3
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +530 -126
- data/lib/samagotchi/web/grants.rb +302 -0
- data/lib/samagotchi/web/participant_access.rb +286 -0
- data/lib/samagotchi/web/principal.rb +29 -0
- data/lib/samagotchi/web/public/data.js +3 -2
- data/lib/samagotchi/web/public/index.html +20 -4
- data/lib/samagotchi/web/public/stage_frame.js +7 -2
- data/lib/samagotchi/web/public/stage_view.js +3 -2
- data/lib/samagotchi/web/public/stream_handlers.js +3 -1
- data/lib/samagotchi/web/public/working_set.js +159 -28
- data/lib/samagotchi/web/public/working_set_rail.js +39 -12
- data/lib/samagotchi/web/public/working_set_view.js +91 -33
- data/lib/samagotchi/web/server.rb +3 -1
- data/lib/samagotchi/web/session_access_routes.rb +459 -0
- data/lib/samagotchi/worker.rb +26 -5
- data/lib/samagotchi/worker_sidecar.rb +12 -5
- data/lib/samagotchi/working_set.rb +88 -14
- data/lib/samagotchi/working_set_command.rb +133 -30
- data/lib/samagotchi/working_set_pin.rb +34 -0
- data/lib/samagotchi/working_set_steer.rb +191 -27
- metadata +28 -1
|
@@ -5,10 +5,19 @@
|
|
|
5
5
|
// and the step tags' jump to a call. The rail is working_set_rail.js's; the
|
|
6
6
|
// rules are working_set.js's.
|
|
7
7
|
//
|
|
8
|
-
// Steering (
|
|
9
|
-
// `/working-set drop|undrop KEY…` lines (+sendLine+, the session
|
|
10
|
-
// route).
|
|
11
|
-
//
|
|
8
|
+
// Steering (drop / undrop, pin / unpin, + add): the list's rows and groups post
|
|
9
|
+
// `/working-set drop|undrop|pin|unpin|add KEY…` lines (+sendLine+, the session
|
|
10
|
+
// command route). The list's foot (the sheet's last row) adds what the user
|
|
11
|
+
// types (a path, memory:NAME or a URL); a stale or forgotten row offers
|
|
12
|
+
// refresh (an add of it). An add is pending until the next turn sends its
|
|
13
|
+
// note: the server's snapshot lists it (adds) between turns, a queued line
|
|
14
|
+
// mid-turn; an added item the set doesn't hold yet is a `+ name` row with
|
|
15
|
+
// `reads next turn` and an undo (unadd). Between turns one runs at once: its command_ran re-reads the
|
|
16
|
+
// conversation and the re-emitted set strikes the item `dropped` (or marks
|
|
17
|
+
// it pinned). A row offers pin only when the session's strategy has the
|
|
18
|
+
// forget layer (forgetOn: pins guard against the model's forget); a pinned
|
|
19
|
+
// row shows its 📌 and unpin under any strategy, since its drop waits for
|
|
20
|
+
// the unpin. While a
|
|
12
21
|
// turn runs it is queued: its command_queued (waits: "turn_end"; any
|
|
13
22
|
// client's, and the snapshot's replay after a reload) makes it pending, a
|
|
14
23
|
// dashed-warn row with a pill and an undo (the inverse line, queued after
|
|
@@ -32,8 +41,9 @@ import { html, nothing, render, svg } from "./vendor/lit-html.js";
|
|
|
32
41
|
import { createChipPopover } from "./chip_popover.js";
|
|
33
42
|
import { showRow } from "./refs.js";
|
|
34
43
|
import {
|
|
35
|
-
BUDGETS, dropRefusal, fold, footprint, footprintText, GLYPHS, INVERSE, isFresh, itemsOfTurn, labelText,
|
|
36
|
-
opsText, overlay, parseSteerLine, pendingSteers,
|
|
44
|
+
addedItems, addLine, BUDGETS, dropRefusal, fold, footprint, footprintText, forgetOn, GLYPHS, INVERSE, isFresh, itemsOfTurn, labelText,
|
|
45
|
+
labelTitle, newerSnapshot, opsText, overlay, parseSteerLine, pendingSteers, pinnable, refreshable, serverAddIds, sessionItems, STEER_OPS,
|
|
46
|
+
steerId, steerLine, stepRange, stepTag, tokensText, undroppable,
|
|
37
47
|
} from "./working_set.js";
|
|
38
48
|
|
|
39
49
|
// The popover's files fold to this many lines (groups open in place, one at
|
|
@@ -194,38 +204,69 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
194
204
|
if (opening) fetchServer();
|
|
195
205
|
},
|
|
196
206
|
onClose: () => { openGroup = null; onChange(); },
|
|
207
|
+
// The foot's + add (the popover's submit): one KEY as typed (quoted
|
|
208
|
+
// when it has spaces).
|
|
209
|
+
onSubmit: (form) => {
|
|
210
|
+
if (!form?.matches?.(".ws-add")) return;
|
|
211
|
+
const input = form.querySelector(".ws-add-in");
|
|
212
|
+
const line = sendLine && addLine(input?.value);
|
|
213
|
+
if (!line) return;
|
|
214
|
+
input.value = "";
|
|
215
|
+
Promise.resolve(sendLine(line)).catch(() => {});
|
|
216
|
+
},
|
|
197
217
|
});
|
|
198
218
|
|
|
199
|
-
|
|
219
|
+
// The pending ops: the server's adds (made between turns), then the
|
|
220
|
+
// queued lines.
|
|
221
|
+
const pending = () => pendingSteers([...queued.values()], state().dir, serverAddIds(server));
|
|
222
|
+
// The pending adds the set doesn't hold yet, as rows.
|
|
223
|
+
const adding = (pend = pending()) => addedItems(pend, sessionSet().items, server, state().dir);
|
|
200
224
|
|
|
201
225
|
// A steer button: +op+ on +items+ ("undo" sends the inverse of a pending op).
|
|
202
226
|
function actButton(op, items, { label = op, title = "", disabled = false } = {}) {
|
|
203
|
-
|
|
227
|
+
// JSON: an id may hold spaces (a file under "my notes/").
|
|
228
|
+
const ids = JSON.stringify(items.map(steerId));
|
|
204
229
|
return html`<button type="button" class=${`ws-act-b ${op}`} data-ws="steer" data-op=${op} data-ids=${ids} title=${title} ?disabled=${disabled}>${label}</button>`;
|
|
205
230
|
}
|
|
206
231
|
|
|
232
|
+
// A pending op's pill.
|
|
233
|
+
const PILLS = { drop: "drops next turn", undrop: "back next turn", pin: "applies next turn", unpin: "applies next turn",
|
|
234
|
+
add: "reads next turn" };
|
|
235
|
+
|
|
207
236
|
// What a row offers: a pending op's pill and undo, an undrop, a drop (or
|
|
208
|
-
// why not); nothing for an item no op applies to.
|
|
209
|
-
function rowActions(item, pend) {
|
|
237
|
+
// why not), a pin or an unpin; nothing for an item no op applies to.
|
|
238
|
+
function rowActions(item, pend, forget) {
|
|
210
239
|
if (!sendLine) return { pill: nothing, buttons: nothing };
|
|
211
|
-
|
|
240
|
+
let op = pend.get(steerId(item));
|
|
241
|
+
// A drop queued for a pinned item: the worker refuses it (unpin it
|
|
242
|
+
// first), so it isn't pending; the row offers its greyed drop.
|
|
243
|
+
if (op === "drop" && item.pinned) op = undefined;
|
|
212
244
|
if (op) {
|
|
213
|
-
const
|
|
245
|
+
const when = op === "add" ? "the next turn's note asks the model to read it first" : "applies at the next turn's start";
|
|
246
|
+
const pill = html`<span class="ws-pend" title=${when}>${PILLS[op]}</span>`;
|
|
214
247
|
return { pill, buttons: actButton(INVERSE[op], [item], { label: "undo", title: `take back the queued ${op}` }) };
|
|
215
248
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
249
|
+
const undrop = actButton("undrop", [item], { title: "send it whole again from the next request (a cache break)" });
|
|
250
|
+
// Dropped, or a drop on some reads with nothing else of it sent (the
|
|
251
|
+
// model's layers stubbed the rest): undrop is all it offers.
|
|
252
|
+
if (item.state === "dropped" || (undroppable(item) && item.state === "forgotten")) return { pill: nothing, buttons: undrop };
|
|
253
|
+
const buttons = [];
|
|
219
254
|
const why = dropRefusal(item);
|
|
220
|
-
if (why
|
|
221
|
-
|
|
255
|
+
if (why !== null) buttons.push(actButton("drop", [item], { title: why || "send a stub instead of its reads from the next turn's start", disabled: !!why }));
|
|
256
|
+
if (item.pinned) buttons.push(actButton("unpin", [item], { title: "the model's forget_outputs may forget it again" }));
|
|
257
|
+
else if (forget && pinnable(item)) buttons.push(actButton("pin", [item], { title: "keep it from the model's forget_outputs (its later reads too)" }));
|
|
258
|
+
// A drop on some of its reads, others still sent: undrop takes it back.
|
|
259
|
+
if (undroppable(item)) buttons.push(undrop);
|
|
260
|
+
if (refreshable(item)) buttons.push(actButton("add", [item], { label: "refresh", title: "the next turn's note asks the model to read it again" }));
|
|
261
|
+
return { pill: nothing, buttons: buttons.length ? buttons : nothing };
|
|
222
262
|
}
|
|
223
263
|
|
|
224
|
-
function rowTemplate(item, { session: inSession, group = null, now, pend = new Map() }) {
|
|
264
|
+
function rowTemplate(item, { session: inSession, group = null, now, pend = new Map(), forget = false }) {
|
|
225
265
|
const fresh = isFresh(seenAt(item), now);
|
|
226
266
|
const tag = stepTag(item, { session: inSession });
|
|
227
|
-
|
|
228
|
-
const
|
|
267
|
+
// An added row's words: what it is (a phone's tap on them opens its strip).
|
|
268
|
+
const words = item.added ? "added" : `${opsText(item)}${fresh ? " · just now" : ""}`;
|
|
269
|
+
const glyph = item.added ? "+" : item.muted ? GLYPHS.muted : fresh ? "●" : GLYPHS[item.kind] || "";
|
|
229
270
|
const name = memberName(item, group);
|
|
230
271
|
const full = item.kind === "file" ? item.path || item.key : item.kind === "url" ? item.url || item.key : item.key;
|
|
231
272
|
let nameEl;
|
|
@@ -248,11 +289,12 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
248
289
|
const state = item.state && item.state !== "live" ? ` ${item.state}` : "";
|
|
249
290
|
const tokens = tokensText(item.tokens);
|
|
250
291
|
const id = steerId(item);
|
|
251
|
-
const { pill, buttons } = rowActions(item, pend);
|
|
292
|
+
const { pill, buttons } = rowActions(item, pend, forget);
|
|
293
|
+
const pin = item.pinned ? html`<span class="ws-pin" title="pinned: the model's forget_outputs refuses it">📌</span>` : nothing;
|
|
252
294
|
const isPending = pill !== nothing;
|
|
253
295
|
const strip = stripOpen === id && buttons !== nothing;
|
|
254
296
|
const open = item.kind === "file" ? html`<button type="button" class="ws-act-b open ref" data-ref-kind="file" data-ref-path=${item.ref?.path || full}>open</button>` : nothing;
|
|
255
|
-
const row = html`<div class=${`ws-row ${item.kind}${fresh ? " fresh" : ""}${item.muted ? " muted" : ""}${group ? " member" : ""}${state}${isPending ? " pending" : ""}${strip ? " strip-open" : ""}`} data-ws-id=${id}><span class="k">${glyph}</span>${nameEl}${edited ? html`<span class="e">✎</span>` : nothing}<span class="st">${words}</span>${pill}${tokens ? html`<span class="tk" title=${`about ${item.tokens} tokens of it are sent`}>${tokens}</span>` : nothing}${tagEl}${buttons !== nothing ? html`<span class="ws-act">${buttons}</span>` : nothing}</div>`;
|
|
297
|
+
const row = html`<div class=${`ws-row ${item.kind}${item.added ? " added" : ""}${fresh ? " fresh" : ""}${item.muted ? " muted" : ""}${group ? " member" : ""}${state}${isPending ? " pending" : ""}${strip ? " strip-open" : ""}`} data-ws-id=${id}><span class="k">${glyph}</span>${nameEl}${pin}${edited ? html`<span class="e">✎</span>` : nothing}<span class="st">${words}</span>${pill}${tokens ? html`<span class="tk" title=${`about ${item.tokens} tokens of it are sent`}>${tokens}</span>` : nothing}${tagEl}${buttons !== nothing ? html`<span class="ws-act">${buttons}</span>` : nothing}</div>`;
|
|
256
298
|
// One template either way, so the tapped row stays the same node (a
|
|
257
299
|
// click outside the popover closes it).
|
|
258
300
|
return html`${row}${strip ? html`<div class="ws-strip">${buttons}${open}</div>` : nothing}`;
|
|
@@ -282,7 +324,8 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
282
324
|
const memories = set.items.filter((i) => i.kind === "memory");
|
|
283
325
|
const urls = set.items.filter((i) => i.kind === "url");
|
|
284
326
|
const pend = pending();
|
|
285
|
-
const
|
|
327
|
+
const forget = forgetOn(st.llmContext);
|
|
328
|
+
const opts = { session: inSession, now, pend, forget };
|
|
286
329
|
const fileLabels = fold(files, POP_BUDGET).labels;
|
|
287
330
|
// Memories are rows of their own; past the budget one group that opens.
|
|
288
331
|
const memoryLabels = fold(memories, memories.length > POP_BUDGET ? 1 : Infinity).labels;
|
|
@@ -290,14 +333,17 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
290
333
|
const memMeta = [`${memories.length - preloaded} used`, preloaded ? `${preloaded} preloaded` : "", set.muted.length ? `${set.muted.length} muted` : ""].filter(Boolean).join(" · ");
|
|
291
334
|
const turnLabel = view.turn !== null && view.turn >= 0 ? `turn ${view.turn + 1}` : "this turn";
|
|
292
335
|
const pct = Number.isFinite(st.ctxPct) ? ` · ctx ${Math.round(st.ctxPct)} %` : "";
|
|
293
|
-
const
|
|
336
|
+
const added = sendLine ? adding(pend) : [];
|
|
337
|
+
const empty = !set.items.length && !set.muted.length && !added.length;
|
|
294
338
|
const tpl = html`<div class="ctx-pop-head"><strong>working set</strong><span class="ws-scope" role="group" aria-label="Scope"><button type="button" class=${!inSession ? "on" : ""} aria-pressed=${String(!inSession)} data-ws="scope" data-scope="turn" ?disabled=${view.turn === null || view.turn < 0}>${turnLabel}</button><button type="button" class=${inSession ? "on" : ""} aria-pressed=${String(inSession)} data-ws="scope" data-scope="session">session</button></span><span class="ctx-pop-kind">${set.items.length} item${set.items.length === 1 ? "" : "s"}${pct}</span>${ringTemplate(st.ctxPct)}<button type="button" class="ctx-pop-close" data-act="close" aria-label="Close">×</button></div>
|
|
295
339
|
${empty ? html`<div class="ctx-pop-meta">Nothing yet: the files, memories and URLs ${inSession ? "this session's turns read" : "this turn reads"} show here.</div>` : nothing}
|
|
296
340
|
${files.length ? html`<div class="ctx-pop-meta">files · ${files.length}</div>${labelsTemplate(fileLabels, opts)}` : nothing}
|
|
297
341
|
${memories.length || set.muted.length ? html`<div class="ctx-pop-meta">memories${memMeta ? ` · ${memMeta}` : ""}</div>${labelsTemplate(memoryLabels, opts)}${set.muted.map((m) => rowTemplate(m, opts))}` : nothing}
|
|
298
342
|
${urls.length ? html`<div class="ctx-pop-meta">urls · ${urls.length}</div>${labelsTemplate(fold(urls, POP_BUDGET).labels, opts)}` : nothing}
|
|
299
|
-
${
|
|
300
|
-
|
|
343
|
+
${added.length ? html`<div class="ctx-pop-meta">adding · ${added.length}</div>${added.map((it) => rowTemplate(it, opts))}` : nothing}
|
|
344
|
+
${pend.size ? html`<div class="ws-pending" role="status"><span class="ws-pending-t">${pend.size} change${pend.size === 1 ? "" : "s"} pending<span class="ws-pending-when"> · apply at the next turn's start</span></span><button type="button" class="ws-act-b" data-ws="undo-all">undo all</button></div>` : nothing}
|
|
345
|
+
${sendLine ? html`<form class="ws-add" data-ws="add-form"><input class="ws-add-in" type="text" name="key" placeholder="+ path, memory:NAME or URL" aria-label="Add to the working set: a path, memory:NAME or a URL" autocomplete="off" autocapitalize="off" spellcheck="false" enterkeyhint="go"><button type="submit" class="ws-act-b">add</button></form>` : nothing}
|
|
346
|
+
<div class="ctx-pop-meta ws-hint">A file opens its ref; a step tag goes to the call.${sendLine ? " Drop sends stubs instead of an item's reads from the next turn; add asks the model to read one first." : ""}${sendLine && forget ? " Pin keeps an item from the model's forget." : ""} Esc closes.</div>`;
|
|
301
347
|
render(tpl, popover.pop);
|
|
302
348
|
}
|
|
303
349
|
|
|
@@ -329,10 +375,10 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
329
375
|
// Posts +op+ for the ids (the items' steer words, from the set).
|
|
330
376
|
function steer(op, ids) {
|
|
331
377
|
if (!sendLine || !ids.length) return;
|
|
332
|
-
const all = [...sessionSet().items, ...turnItems(view.turn)];
|
|
378
|
+
const all = [...sessionSet().items, ...turnItems(view.turn), ...adding()];
|
|
333
379
|
const byId = new Map(all.map((it) => [steerId(it), it]));
|
|
334
380
|
const items = ids.map((id) => byId.get(id)).filter(Boolean);
|
|
335
|
-
if (items.length) Promise.resolve(sendLine(steerLine(op, items))).catch(() => {});
|
|
381
|
+
if (items.length) Promise.resolve(sendLine(steerLine(op, items, state().dir))).catch(() => {});
|
|
336
382
|
}
|
|
337
383
|
|
|
338
384
|
popover.pop.addEventListener("click", (e) => {
|
|
@@ -349,10 +395,10 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
349
395
|
}
|
|
350
396
|
if (!act) return;
|
|
351
397
|
if (act.dataset.ws === "steer") {
|
|
352
|
-
steer(act.dataset.op, act.dataset.ids
|
|
398
|
+
steer(act.dataset.op, JSON.parse(act.dataset.ids || "[]"));
|
|
353
399
|
} else if (act.dataset.ws === "undo-all") {
|
|
354
400
|
const pend = pending();
|
|
355
|
-
for (const op of
|
|
401
|
+
for (const op of STEER_OPS) {
|
|
356
402
|
const ids = [...pend].filter(([, o]) => o === op).map(([id]) => id);
|
|
357
403
|
steer(INVERSE[op], ids);
|
|
358
404
|
}
|
|
@@ -398,6 +444,12 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
398
444
|
if (popover.isOpen()) popover.refresh();
|
|
399
445
|
}
|
|
400
446
|
|
|
447
|
+
// The pending ops changed: the list and the rail's marks.
|
|
448
|
+
function pendingChanged() {
|
|
449
|
+
changed();
|
|
450
|
+
for (const fn of listeners) fn();
|
|
451
|
+
}
|
|
452
|
+
|
|
401
453
|
// The footprints on the page (the history's, the hand-off's) drawn again
|
|
402
454
|
// in place when their items' states changed: the same node (its focus,
|
|
403
455
|
// the popover's opener), its children and label replaced.
|
|
@@ -466,12 +518,18 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
466
518
|
refreshFootprints();
|
|
467
519
|
fetchServer();
|
|
468
520
|
},
|
|
521
|
+
// The session's strategy changed (/llm-context's command_ran): the open
|
|
522
|
+
// list redraws, its pin buttons shown only under forget (forgetOn).
|
|
523
|
+
strategyChanged() {
|
|
524
|
+
if (popover.isOpen()) popover.refresh();
|
|
525
|
+
},
|
|
469
526
|
// A :working_set_updated event's set ({items, at}).
|
|
470
527
|
update(snapshot) {
|
|
471
528
|
events += 1;
|
|
472
529
|
takeServer(snapshot, { event: true });
|
|
473
530
|
},
|
|
474
|
-
// +fn()+ runs after each new server snapshot
|
|
531
|
+
// +fn()+ runs after each new server snapshot and each change of the
|
|
532
|
+
// pending ops (the rail's redraw).
|
|
475
533
|
onServer(fn) { listeners.add(fn); },
|
|
476
534
|
// A command queued for after the running turn (its command_queued,
|
|
477
535
|
// waits "turn_end"): a steer line is pending until its command_ran.
|
|
@@ -482,11 +540,11 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
482
540
|
}
|
|
483
541
|
if (event?.waits !== "turn_end" || !event.command_id || !parseSteerLine(event.line)) return;
|
|
484
542
|
queued.set(event.command_id, event.line);
|
|
485
|
-
|
|
543
|
+
pendingChanged();
|
|
486
544
|
},
|
|
487
545
|
// A command ran (or was dropped): its line is no longer pending.
|
|
488
546
|
commandRan(event) {
|
|
489
|
-
if (event?.command_id && queued.delete(event.command_id))
|
|
547
|
+
if (event?.command_id && queued.delete(event.command_id)) pendingChanged();
|
|
490
548
|
},
|
|
491
549
|
// The steer lines pending (for the specs and the rail).
|
|
492
550
|
pending: () => pending(),
|
|
@@ -95,6 +95,8 @@ module Samagotchi
|
|
|
95
95
|
puts lan_off_line(setting)
|
|
96
96
|
return 0
|
|
97
97
|
end
|
|
98
|
+
# The participants' grants: never served from a store others can read.
|
|
99
|
+
Grants.check_permissions!(Grants.path)
|
|
98
100
|
if start(port: port, host: host, url: url, open_browser: open_browser, markdown: markdown, view: view,
|
|
99
101
|
annotate_presets: annotate_presets, editor: editor, lan: lan, working_set: working_set)
|
|
100
102
|
0
|
|
@@ -102,7 +104,7 @@ module Samagotchi
|
|
|
102
104
|
1
|
|
103
105
|
end
|
|
104
106
|
end
|
|
105
|
-
rescue Lan::Error => e
|
|
107
|
+
rescue Lan::Error, Grants::Insecure => e
|
|
106
108
|
warn "Error: #{e.message}"
|
|
107
109
|
1
|
|
108
110
|
rescue Interrupt
|