samagotchi 0.62.0 → 0.64.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 +78 -1
- data/README.md +4 -1
- data/bin/chi +3 -2
- data/docs/architecture.md +12 -2
- data/docs/cli.md +20 -9
- data/docs/configuration.md +16 -9
- data/docs/context.md +13 -3
- data/docs/hooks.md +6 -3
- data/docs/internals/engine.md +10 -3
- data/docs/memory.md +1 -1
- data/docs/plugins.md +96 -2
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +11 -8
- data/docs/sub-agent.md +27 -4
- data/docs/testing.md +13 -4
- data/lib/samagotchi/answer_command.rb +5 -1
- data/lib/samagotchi/answer_refs.rb +18 -26
- data/lib/samagotchi/bridge/turn_accumulator.rb +1 -0
- data/lib/samagotchi/bridge.rb +41 -5
- 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/slow-commands/manifest.yml +2 -2
- data/lib/samagotchi/bundles/slow-commands/plugin.rb +5 -3
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
- data/lib/samagotchi/child_reports.rb +7 -2
- data/lib/samagotchi/cli/parent_wait.rb +9 -2
- data/lib/samagotchi/composer_route.rb +90 -0
- data/lib/samagotchi/config.rb +6 -3
- data/lib/samagotchi/context_absorber.rb +25 -6
- data/lib/samagotchi/continue_offer.rb +17 -3
- data/lib/samagotchi/delivery.rb +13 -8
- data/lib/samagotchi/engine.rb +2 -0
- data/lib/samagotchi/host_config.rb +6 -6
- data/lib/samagotchi/host_model.rb +2 -2
- data/lib/samagotchi/idle_recap.rb +2 -3
- data/lib/samagotchi/image_store.rb +8 -0
- data/lib/samagotchi/interrupted_turn.rb +7 -3
- data/lib/samagotchi/last_turn.rb +5 -2
- data/lib/samagotchi/pending_input_queue.rb +10 -3
- data/lib/samagotchi/plugin/attached_context.rb +7 -4
- data/lib/samagotchi/plugin/sessions.rb +27 -5
- data/lib/samagotchi/recap_setup.rb +3 -3
- data/lib/samagotchi/reply_wait.rb +171 -6
- data/lib/samagotchi/row_refs.rb +3 -4
- data/lib/samagotchi/send_command.rb +63 -19
- data/lib/samagotchi/session.rb +12 -0
- data/lib/samagotchi/session_commands.rb +1 -1
- data/lib/samagotchi/session_inbox.rb +63 -7
- data/lib/samagotchi/session_manager.rb +49 -20
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +6 -3
- data/lib/samagotchi/terminal_ui/attached_loop.rb +73 -35
- data/lib/samagotchi/terminal_ui/formatting.rb +3 -19
- data/lib/samagotchi/terminal_ui.rb +103 -68
- data/lib/samagotchi/tool_activity.rb +5 -5
- data/lib/samagotchi/tools/delegate.rb +28 -1
- data/lib/samagotchi/tools/delegate_cursor.rb +9 -4
- data/lib/samagotchi/tools/delegate_wait.rb +17 -5
- data/lib/samagotchi/tools/execute.rb +33 -8
- data/lib/samagotchi/turn_checkpoint.rb +9 -3
- 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 +53 -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/commands.js +8 -8
- data/lib/samagotchi/web/public/composer.js +3 -3
- 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 +2 -8
- data/lib/samagotchi/web/public/turn_html.js +119 -59
- data/lib/samagotchi/web/public/turn_model.js +13 -3
- 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 +53 -35
- data/lib/samagotchi/worker_inbound.rb +12 -11
- metadata +9 -1
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
// The turn's pieces as
|
|
2
|
-
// a tool row, a prompt bubble
|
|
3
|
-
//
|
|
4
|
-
// (
|
|
1
|
+
// The turn's pieces as lit-html templates, one for the live page and a
|
|
2
|
+
// reload: a tool row, a prompt bubble, a note, a steer, the notice of a
|
|
3
|
+
// turn with no answer. The live page renders the same template a reload
|
|
4
|
+
// does (elementOf, or its own lit root for a tool row), so the two can't
|
|
5
|
+
// drift apart. `node --test` covers it (on the happy-dom shim).
|
|
5
6
|
import { toolName } from "./activity.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
7
|
+
import { html, nothing, render } from "./vendor/lit-html.js";
|
|
8
|
+
import { commandBlockTemplate, rowHover } from "./command_view.js";
|
|
9
|
+
import { copySource } from "./copy.js";
|
|
10
|
+
import { activityDiffTemplate } from "./diff_view.js";
|
|
11
|
+
import { steerSender } from "./format.js";
|
|
12
|
+
import { rowTitleRef } from "./refs.js";
|
|
13
|
+
import { readLinesTemplate } from "./read_view.js";
|
|
14
|
+
import { STOPPED_LINE_CLASS, cancelLineText, formatDuration } from "./timing.js";
|
|
13
15
|
import { emptyAnswerLine } from "./turn_events.js";
|
|
14
16
|
|
|
15
17
|
// A row's status (live, or a saved tool record's on reload): running,
|
|
@@ -31,12 +33,14 @@ export function statusParts(status, { noMatch = false } = {}) {
|
|
|
31
33
|
return { cls, label, word: cls === "ok" || cls === "running" ? "" : label };
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
// role is bound too: lit sets bound attributes after the static ones, and
|
|
37
|
+
// the markup keeps the order a reload wrote before.
|
|
38
|
+
function statusMarkTemplate({ cls, label }) {
|
|
39
|
+
return html`<span class="activity-status ${cls}" role=${"img"} aria-label=${label} title=${label}></span>`;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
function
|
|
39
|
-
return word ? `<span class="activity-state ${wordCls || cls}">${word}</span>` :
|
|
42
|
+
function statusWordTemplate({ cls, word, wordCls }) {
|
|
43
|
+
return word ? html`<span class="activity-state ${wordCls || cls}">${word}</span>` : nothing;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
// A reloaded row's ✂ mark: what an LLM context edit saved on its output
|
|
@@ -62,11 +66,11 @@ function compactTokens(n) {
|
|
|
62
66
|
return n >= 1000 ? `${(n / 1000).toFixed(1)}k tokens` : `${n} tokens`;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
function
|
|
69
|
+
function llmEditTemplate(row) {
|
|
66
70
|
const line = llmEditLine(row.edit);
|
|
67
|
-
if (!line) return
|
|
71
|
+
if (!line) return nothing;
|
|
68
72
|
const hover = `${row.tool_id ? `${row.tool_id}: ` : ""}${line}\nThe model is sent this instead of the output; the session keeps it.`;
|
|
69
|
-
return `<div class="activity-edit" title
|
|
73
|
+
return html`<div class="activity-edit" title=${hover}>${line}</div>`;
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
// A tool row: its status mark, tool, title (else params; the full command,
|
|
@@ -76,31 +80,35 @@ function llmEditHtml(row) {
|
|
|
76
80
|
// the rest on hover; a read of the file as-is also its numbered lines,
|
|
77
81
|
// closed: read_view.js), its images (+thumbs+(images) draws them) and an
|
|
78
82
|
// edit's collapsed diff, and a reloaded row's ✂ mark above its output. A
|
|
79
|
-
// part with nothing to say is left out.
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
// part with nothing to say is left out. A reload renders toolRowTemplate;
|
|
84
|
+
// the live view renders toolRowInnerTemplate into its own row element
|
|
85
|
+
// (turn_view.js), again on each event: lit writes only what changed, so a
|
|
86
|
+
// part the user toggled (the raw command, the diff, the read lines) stays.
|
|
87
|
+
// +stop+: the live row's stop-task button (a node), after the title.
|
|
88
|
+
export function toolRowTemplate(row, opts = {}) {
|
|
89
|
+
return html`<div class="activity-row" data-key=${row.key}>${toolRowInnerTemplate(row, opts)}</div>`;
|
|
82
90
|
}
|
|
83
91
|
|
|
84
|
-
export function
|
|
92
|
+
export function toolRowInnerTemplate(row, { thumbs = () => nothing, stop = nothing } = {}) {
|
|
85
93
|
const shown = row.title || row.params;
|
|
86
|
-
const hover = rowHover(row)
|
|
94
|
+
const hover = rowHover(row);
|
|
87
95
|
// A row with a ref (a file tool's file, else a plugin tool's first ref)
|
|
88
96
|
// carries it on its title, which opens it, and its diff lines open it at
|
|
89
97
|
// their line (refs.js; bindRowRefs binds a plugin's data to the title).
|
|
90
|
-
const ref = shown ?
|
|
91
|
-
const
|
|
98
|
+
const ref = shown ? rowTitleRef(row) : null;
|
|
99
|
+
const attr = (value) => (value ? String(value) : nothing);
|
|
100
|
+
const params = shown
|
|
101
|
+
? html`<span class=${`activity-params${ref?.kind ? " ref" : ""}`} title=${hover || nothing} data-ref-kind=${attr(ref?.kind)} data-ref-path=${attr(ref?.kind && ref.path)} data-ref-line=${attr(ref?.kind && ref.line)} data-ref-end-line=${attr(ref?.kind && ref.end_line)}>${shown}</span>`
|
|
102
|
+
: nothing;
|
|
92
103
|
// The name the model called the tool by when it was an alias's, dim.
|
|
93
|
-
const alias = row.called_as ? `<span class="activity-alias">(called as ${
|
|
104
|
+
const alias = row.called_as ? html`<span class="activity-alias">(called as ${row.called_as})</span>` : nothing;
|
|
94
105
|
const duration = formatDuration(row.duration_ms);
|
|
95
106
|
const full = row.status === "running" ? "" : String(row.output || "").replace(/^\[[^\]]*\]\s*/, "");
|
|
96
107
|
const output = full
|
|
97
|
-
? `<div class="activity-output" title
|
|
98
|
-
:
|
|
108
|
+
? html`<div class="activity-output" title=${full}>${full.length > 300 ? `${full.slice(0, 300)}…` : full}</div>`
|
|
109
|
+
: nothing;
|
|
99
110
|
const status = statusParts(row.status, { noMatch: !!row.no_match });
|
|
100
|
-
return `${
|
|
101
|
-
`${duration ? `<span class="activity-duration">${escapeHtml(duration)}</span>` : ""}` +
|
|
102
|
-
`${commandBlockHtml(row.view, row.title)}${llmEditHtml(row)}${output}${output ? readLinesHtml(row) : ""}` +
|
|
103
|
-
`${row.images?.length ? thumbs(row.images) : ""}${row.diff ? activityDiffHtml(row.diff) : ""}`;
|
|
111
|
+
return html`${statusMarkTemplate(status)}<span class="activity-tool">${toolName(row)}</span>${params}${stop}${alias}${statusWordTemplate(status)}${duration ? html`<span class="activity-duration">${duration}</span>` : nothing}${commandBlockTemplate(row.view, row.title)}${llmEditTemplate(row)}${output}${full ? readLinesTemplate(row) : nothing}${row.images?.length ? thumbs(row.images) : nothing}${row.diff ? activityDiffTemplate(row.diff) : nothing}`;
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
// A prompt bubble's states and their badges: queued (waiting for its
|
|
@@ -108,8 +116,8 @@ export function toolRowInnerHtml(row, { thumbs = () => "" } = {}) {
|
|
|
108
116
|
// gave it back). Any other state ("started", none) is a plain bubble.
|
|
109
117
|
export const BADGES = { queued: "queued", steered: "steered", failed: "failed" };
|
|
110
118
|
|
|
111
|
-
export function
|
|
112
|
-
return BADGES[state] ? `<span class="state-badge">${
|
|
119
|
+
export function stateBadgeTemplate(state) {
|
|
120
|
+
return BADGES[state] ? html`<span class="state-badge">${BADGES[state]}</span>` : nothing;
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
// A `!cmd`'s saved user message (the worker appends "!(<command>)\n<output>"
|
|
@@ -128,45 +136,97 @@ export function shellCommandMessage(content) {
|
|
|
128
136
|
// A `!cmd`'s history bubble: the same markup the live page draws for a
|
|
129
137
|
// command (app.js's drawCommandBubble): the line, the output in a
|
|
130
138
|
// pre.command-output.
|
|
131
|
-
export function
|
|
132
|
-
const body = text ? `<pre class="command-output">${
|
|
133
|
-
return `<div class="bubble command"><div class="command-line">${
|
|
139
|
+
export function commandBubbleTemplate({ line, text }) {
|
|
140
|
+
const body = text ? html`<pre class="command-output">${text}</pre>` : nothing;
|
|
141
|
+
return html`<div class="bubble command"><div class="command-line">${line}</div>${body}</div>`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// A user message's body: runs of `>` lines become <blockquote>s, the rest
|
|
145
|
+
// stays text. The bubble is pre-wrap, so the blank lines around a quote
|
|
146
|
+
// are dropped (the blockquote's margin spaces it instead).
|
|
147
|
+
export function userBodyTemplate(content) {
|
|
148
|
+
const text = String(content ?? "").replace(/\r\n?/g, "\n");
|
|
149
|
+
const blocks = [];
|
|
150
|
+
for (const line of text.split("\n")) {
|
|
151
|
+
const m = /^ {0,3}> ?(.*)$/.exec(line);
|
|
152
|
+
const kind = m ? "quote" : "text";
|
|
153
|
+
const last = blocks[blocks.length - 1];
|
|
154
|
+
if (last?.kind === kind) last.lines.push(m ? m[1] : line);
|
|
155
|
+
else blocks.push({ kind, lines: [m ? m[1] : line] });
|
|
156
|
+
}
|
|
157
|
+
if (!blocks.some((b) => b.kind === "quote")) return String(content ?? "");
|
|
158
|
+
return blocks.map((b) => {
|
|
159
|
+
if (b.kind === "quote") return html`<blockquote>${b.lines.join("\n")}</blockquote>`;
|
|
160
|
+
const body = b.lines.join("\n").replace(/^\s*\n/, "").replace(/\n\s*$/, "");
|
|
161
|
+
return body.trim() ? body : nothing;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// An answer's bubble (+cls+: a report reply's; +at+: its message's index,
|
|
166
|
+
// data-answer-at): an assistant's rendered markdown (the server's html,
|
|
167
|
+
// sanitized there) set as it is, as the live turn end sets it
|
|
168
|
+
// (answer_refs.js applyFinalAnswer), else its text, set whole (no lit
|
|
169
|
+
// markers inside: the bubble's nodes are the live one's). Its markdown
|
|
170
|
+
// source is kept for the copy button when it shows rendered html.
|
|
171
|
+
export function answerBubbleTemplate(message, { cls = "", at = null } = {}) {
|
|
172
|
+
const rendered = message?.role === "assistant" && typeof message.html === "string";
|
|
173
|
+
const user = !rendered && message?.role === "user";
|
|
174
|
+
const klass = `bubble output${cls}${rendered ? " markdown" : ""}`;
|
|
175
|
+
const answerAt = at ?? nothing;
|
|
176
|
+
const source = rendered || user ? copySource(message) : nothing;
|
|
177
|
+
if (rendered) return html`<div class=${klass} data-answer-at=${answerAt} data-copy-source=${source} .innerHTML=${message.html}></div>`;
|
|
178
|
+
if (user) return html`<div class=${klass} data-answer-at=${answerAt} data-copy-source=${source}>${userBodyTemplate(message.content)}</div>`;
|
|
179
|
+
return html`<div class=${klass} data-answer-at=${answerAt} data-copy-source=${source} .textContent=${String(message?.content ?? "")}></div>`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// A context note's bubble (background the user or another session pushed
|
|
183
|
+
// in; the model reads it, nobody asked it anything): who sent it, then its
|
|
184
|
+
// text.
|
|
185
|
+
export function noteBubbleTemplate(note) {
|
|
186
|
+
const from = note?.label ? `note from ${note.label}` : "note";
|
|
187
|
+
return html`<div class="bubble note"><div class="note-line">${from}</div><div class="note-text">${note?.content ?? ""}</div></div>`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// A plugin's steer (a nudge put into the running turn): one line that
|
|
191
|
+
// names who nudged the model, its text collapsed under it. +bubble+: on its
|
|
192
|
+
// own between bubbles (no step holds it), not a row of a step.
|
|
193
|
+
export function steerRowTemplate({ source, text } = {}, { bubble = false } = {}) {
|
|
194
|
+
const sender = steerSender(source);
|
|
195
|
+
const who = sender ? `${sender} nudged the model` : "nudged the model";
|
|
196
|
+
return html`<details class=${`${bubble ? "bubble " : ""}steer-row`}><summary>${who}</summary><div class="steer-text">${text ?? ""}</div></details>`;
|
|
134
197
|
}
|
|
135
198
|
|
|
136
199
|
// A user's line: who sent it (+label+, a delegate report's or another
|
|
137
200
|
// client's), its body (`>` lines as quotes) and +thumbs+ (its images'
|
|
138
|
-
//
|
|
201
|
+
// template), its state's badge. +message+: { content, display } (the copy
|
|
139
202
|
// source keeps the text as written). +step+: the step that read a line
|
|
140
203
|
// merged mid-turn (a reload's; a resolved question goes before it).
|
|
141
204
|
// The live page's own: +own+ (this tab sent it), +enqueuedId+ and +key+
|
|
142
205
|
// (its normalized text: an echo waiting for its id is matched by it).
|
|
143
|
-
export function
|
|
206
|
+
export function userBubbleTemplate(message, { label = null, state = null, step = null, thumbs = nothing, own = false, enqueuedId = null, key = null } = {}) {
|
|
144
207
|
const named = BADGES[state] ? state : null;
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
named ? `data-user-state="${named}"` : "",
|
|
148
|
-
step ? `data-step="${Number(step)}"` : "",
|
|
149
|
-
].filter(Boolean).join(" ");
|
|
150
|
-
const live = [
|
|
151
|
-
key !== null ? ` data-user-content="${escapeHtml(key)}"` : "",
|
|
152
|
-
own ? ' data-own="1"' : "",
|
|
153
|
-
enqueuedId ? ` data-enqueued-id="${escapeHtml(enqueuedId)}"` : "",
|
|
154
|
-
].join("");
|
|
155
|
-
const from = label ? `<span class="origin-label">${escapeHtml(label)}</span>` : "";
|
|
156
|
-
return `<div ${attrs}${copySourceAttr({ ...message, role: "user" }, false)}${live}>${from}` +
|
|
157
|
-
`<div class="user-message">${userBodyHtml(message?.content)}${thumbs}</div>${stateBadgeHtml(named)}</div>`;
|
|
208
|
+
const from = label ? html`<span class="origin-label">${label}</span>` : nothing;
|
|
209
|
+
return html`<div class=${`bubble user${named ? ` ${named}` : ""}`} data-user-state=${named || nothing} data-step=${step ? Number(step) : nothing} data-copy-source=${copySource({ ...message, role: "user" })} data-user-content=${key !== null ? key : nothing} data-own=${own ? "1" : nothing} data-enqueued-id=${enqueuedId || nothing}>${from}<div class="user-message">${userBodyTemplate(message?.content)}${thumbs}</div>${stateBadgeTemplate(named)}</div>`;
|
|
158
210
|
}
|
|
159
211
|
|
|
160
212
|
// The notice of a turn that ended with no answer (the server's
|
|
161
213
|
// empty_answer item, turn_summary.empty_answer live): a muted row like the
|
|
162
214
|
// loop's retry row, where the answer would be.
|
|
163
|
-
export function
|
|
164
|
-
return `<div class="bubble hook-notice empty-answer">${
|
|
215
|
+
export function emptyAnswerTemplate(item) {
|
|
216
|
+
return html`<div class="bubble hook-notice empty-answer">${emptyAnswerLine(item)}</div>`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// A re-rendered turn's cancel line (a .bubble.cancel.stopped, as live's
|
|
220
|
+
// addEndBubble), nothing when the turn record is not a canceled one.
|
|
221
|
+
export function cancelLineTemplate(record) {
|
|
222
|
+
if (record?.status !== "canceled") return nothing;
|
|
223
|
+
return html`<div class=${STOPPED_LINE_CLASS}>${cancelLineText(record.cancellation_reason, record.cancelled_by)}</div>`;
|
|
165
224
|
}
|
|
166
225
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
226
|
+
// +template+'s one top-level element, rendered once (the live page's
|
|
227
|
+
// bubbles: their classes and badge change by hand after this).
|
|
228
|
+
export function elementOf(template) {
|
|
229
|
+
const frag = document.createDocumentFragment();
|
|
230
|
+
render(template, frag);
|
|
231
|
+
return frag.firstElementChild;
|
|
172
232
|
}
|
|
@@ -222,13 +222,23 @@ export function genLabelParts(gen) {
|
|
|
222
222
|
if (line) return { head: cut(line), calls, repeats: true };
|
|
223
223
|
if (first?.title) return { head: cut(`${toolName(first)} ${first.title}`), calls, repeats: true };
|
|
224
224
|
if (gen.tools.length) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
// A plugin label that is a verb ("echoing", the default "calling
|
|
226
|
+
// tool") is the head itself when every call has one; among other
|
|
227
|
+
// tools it gives way to its tool's name ("working with read, echo").
|
|
228
|
+
const verbs = gen.tools.every(isVerbLabel);
|
|
229
|
+
const names = [...new Set(gen.tools.map((r) => (verbs || !isVerbLabel(r) ? toolName(r) : r.tool || "tool")))];
|
|
230
|
+
const list = `${names.slice(0, LABEL_TOOLS).join(", ")}${names.length > LABEL_TOOLS ? ", …" : ""}`;
|
|
231
|
+
return { head: verbs ? list : `working with ${list}`, calls, repeats: false };
|
|
228
232
|
}
|
|
229
233
|
return { head: "thinking", calls: "", repeats: false };
|
|
230
234
|
}
|
|
231
235
|
|
|
236
|
+
// A plugin tool's label that reads as a verb (its first word ends in
|
|
237
|
+
// "ing": "echoing", "calling tool"), not as a name ("chrome: screenshot").
|
|
238
|
+
function isVerbLabel(row) {
|
|
239
|
+
return /^[a-z]+ing\b/i.test(String(row?.label || ""));
|
|
240
|
+
}
|
|
241
|
+
|
|
232
242
|
// A step that said something (its text is not the turn's answer): a row of
|
|
233
243
|
// its own in a finished block's outline.
|
|
234
244
|
export function isNarrated(gen) {
|
|
@@ -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};
|