lavish-axi 0.1.69 → 0.1.70
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.
- package/README.md +1 -1
- package/dist/chrome-client.js +64 -15
- package/dist/cli.mjs +92 -16
- package/package.json +1 -1
- package/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -197,7 +197,7 @@ pnpm link
|
|
|
197
197
|
- **Feedback controls** - Native controls (radios, checkboxes, inputs, selects, buttons, labels, disclosure summaries, contenteditable) are interactive automatically, so they do not need `data-lavish-action`.
|
|
198
198
|
For reversible choices, let option clicks update local state, then queue exactly one final answer from a per-question submit or Queue answer button with `window.lavish.queuePrompt()`.
|
|
199
199
|
Mark only custom (non-native) clickable elements with `data-lavish-action` so Lavish does not annotate them, and use `data-lavish-question` or `queueKey` when pre-send updates for the same question should replace each other.
|
|
200
|
-
Everything you send is part of the conversation: a note on an element, a text selection, a table cell, a diagram node, a whiteboard, a layout-issue batch, or a composer message each becomes a bubble on your side of the Conversation panel, with an anchor line naming what it was attached to (the element tag, text selection, table cell, diagram node, whiteboard, or layout issues), a quoted excerpt, and, when available, the selector on hover. A note you have queued but not sent sits at the end of the conversation as a dashed bubble labelled **Queued** with a remove control; while its batch is in flight it reads **Sending**, and once the server accepts it, it settles in place as a sent bubble
|
|
200
|
+
Everything you send is part of the conversation: a note on an element, a text selection, a table cell, a diagram node, a whiteboard, a layout-issue batch, or a composer message each becomes a bubble on your side of the Conversation panel, with an anchor line naming what it was attached to (the element tag, text selection, table cell, diagram node, whiteboard, or layout issues), a quoted excerpt, and, when available, the selector on hover. A note you have queued but not sent sits at the end of the conversation as a dashed bubble labelled **Queued** with a remove control; while its batch is in flight it reads **Sending**, and once the server accepts it, it settles in place as a sent bubble in every tab of the session. The panel keeps the newest stored transcript up to 5 MiB; older bubbles drop from view, but an accepted note still settles and is not sent twice. Agent replies render a small Markdown subset - paragraphs, line breaks, headings as bold lead lines, bullet and numbered lists, fenced and inline code, bold, italic, and http(s) links - while tables, images, and raw HTML stay as the text the agent wrote; your own messages keep their line breaks and are never parsed.
|
|
201
201
|
On wider screens, the conversation and the queued notes share one scrollable Conversation panel above a sticky composer, so long feedback queues do not push the text box or send controls off screen.
|
|
202
202
|
The browser chrome keeps editing actions in the overflow menu (copy path, reload artifact, copy DOM snapshot, export standalone HTML, publish link, end session), while the composer exposes **Send & End** beside **Send to Agent** to submit queued prompts and user-ended attribution together. Once **Send & End** starts, Lavish pauses new feedback, waits up to 5 seconds for feedback preparation already underway, keeps any open annotation draft intact until delivery succeeds, then holds that exact terminal batch. A completed reservation survives a page reload, including when its prompts were already delivered and only the pending end remains; incomplete preparation is not restored. If preparation times out, Lavish keeps the review open and its existing queue editable with a visible error. A transient delivery failure permits only retrying the same terminal batch through **Send & End** until it succeeds. If a snapshot makes the request too large (HTTP 413), Lavish retries that exact batch once without the optional snapshot. An actionable attachment rejection (HTTP 400), recoverable layout-selection conflict (HTTP 409), or unrecoverable 413 instead cancels the terminal reservation without ending the session, keeps the prompts queued, and restores editing, removal, **Send to Agent**, **Send & End**, and **End session** so the reviewer can revise or remove stale feedback before submitting again.
|
|
203
203
|
Composer feedback is visibly queued before the text box clears and stays queued until acknowledged. Send waits up to 5 seconds for the artifact's DOM snapshot, then delivers without that optional context if the frame no longer answers; a late snapshot cannot submit the batch twice. A failed or still-unacknowledged POST keeps the queue and shows persistent recovery guidance unless a transcript sync proves the server already accepted that feedback.
|
package/dist/chrome-client.js
CHANGED
|
@@ -23,6 +23,8 @@ const promptIdentityField = "prompt_id";
|
|
|
23
23
|
const PROMPT_IDENTITY_MAX = 128;
|
|
24
24
|
const PROMPT_IDENTITY_RE = /^[A-Za-z0-9_-]+$/;
|
|
25
25
|
const initialChat = Array.isArray(sessionData.initialChat) ? sessionData.initialChat : [];
|
|
26
|
+
const initialChatAckIds = Array.isArray(sessionData.initialChatAckIds) ? sessionData.initialChatAckIds : [];
|
|
27
|
+
const initialChatRevision = parseChatRevision(sessionData.initialChatRevision) || 0;
|
|
26
28
|
const MODE_TOGGLE_HOTKEY_KEY = String(sessionData.modeToggleHotkeyKey || "").toLowerCase();
|
|
27
29
|
const attachmentMaxBytes = Number(sessionData.attachmentMaxBytes) || 0;
|
|
28
30
|
const attachmentMaxCount = Number(sessionData.attachmentMaxCount) || 4;
|
|
@@ -222,6 +224,7 @@ let nextSnapshotRequestId = 0;
|
|
|
222
224
|
let nextSendOperationOrder = 0;
|
|
223
225
|
let workingBubble = null;
|
|
224
226
|
let displayedChat = initialChat.slice();
|
|
227
|
+
let chatRevision = initialChatRevision;
|
|
225
228
|
// Settlement is by per-submission identity, not displayed content: two tabs can queue notes
|
|
226
229
|
// whose chat projection is identical (same selected text under one container, different range
|
|
227
230
|
// boundaries) without settling each other, and a reload after a lost POST response still
|
|
@@ -393,6 +396,15 @@ function promptIdentity(value) {
|
|
|
393
396
|
return isPromptIdentity(value?.[promptIdentityField]) ? value[promptIdentityField] : "";
|
|
394
397
|
}
|
|
395
398
|
|
|
399
|
+
const chatAckIds = new Set();
|
|
400
|
+
function rememberChatAckIds(ids) {
|
|
401
|
+
if (!Array.isArray(ids)) return;
|
|
402
|
+
for (const value of ids) {
|
|
403
|
+
if (isPromptIdentity(value)) chatAckIds.add(value);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
rememberChatAckIds(initialChatAckIds);
|
|
407
|
+
|
|
396
408
|
function createPromptIdentity() {
|
|
397
409
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
398
410
|
return "p" + Date.now().toString(36) + Math.random().toString(36).slice(2, 12);
|
|
@@ -849,13 +861,26 @@ function chatEntriesMatch(left, right) {
|
|
|
849
861
|
return !leftAt || !rightAt || leftAt === rightAt;
|
|
850
862
|
}
|
|
851
863
|
|
|
864
|
+
function parseChatRevision(value) {
|
|
865
|
+
const revision = Number(value);
|
|
866
|
+
return Number.isSafeInteger(revision) && revision >= 0 ? revision : null;
|
|
867
|
+
}
|
|
868
|
+
|
|
852
869
|
function chatContainsEntries(candidate, entries) {
|
|
853
|
-
if (candidate
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
870
|
+
if (!Array.isArray(candidate) || !Array.isArray(entries)) return false;
|
|
871
|
+
if (entries.length === 0) return true;
|
|
872
|
+
// A size-bound transcript may drop a prefix of what this tab already showed. The remaining
|
|
873
|
+
// displayed suffix must still appear in order; a stale sync that is missing a newer tail
|
|
874
|
+
// (including an empty wipe) is rejected.
|
|
875
|
+
for (let start = 0; start < entries.length; start += 1) {
|
|
876
|
+
const suffix = entries.slice(start);
|
|
877
|
+
let matched = 0;
|
|
878
|
+
for (const entry of candidate) {
|
|
879
|
+
if (matched < suffix.length && chatEntriesMatch(entry, suffix[matched])) matched += 1;
|
|
880
|
+
}
|
|
881
|
+
if (matched === suffix.length) return true;
|
|
857
882
|
}
|
|
858
|
-
return
|
|
883
|
+
return false;
|
|
859
884
|
}
|
|
860
885
|
|
|
861
886
|
function chatStartsWith(candidate, prefix) {
|
|
@@ -868,10 +893,18 @@ function chatStartsWith(candidate, prefix) {
|
|
|
868
893
|
}
|
|
869
894
|
|
|
870
895
|
function mergeAcceptedChat(accepted, chatAtRequest) {
|
|
871
|
-
if (!
|
|
896
|
+
if (!Array.isArray(accepted) || !Array.isArray(chatAtRequest)) return null;
|
|
897
|
+
if (!chatStartsWith(displayedChat, chatAtRequest)) return null;
|
|
898
|
+
let requestOffset = chatAtRequest.length;
|
|
899
|
+
for (let i = 0; i <= chatAtRequest.length; i += 1) {
|
|
900
|
+
if (chatStartsWith(accepted, chatAtRequest.slice(i))) {
|
|
901
|
+
requestOffset = i;
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
872
905
|
const displayedTail = displayedChat.slice(chatAtRequest.length);
|
|
873
906
|
const unmatchedDisplayed = [];
|
|
874
|
-
let acceptedIndex = chatAtRequest.length;
|
|
907
|
+
let acceptedIndex = chatAtRequest.length - requestOffset;
|
|
875
908
|
for (const displayedEntry of displayedTail) {
|
|
876
909
|
while (acceptedIndex < accepted.length && !chatEntriesMatch(accepted[acceptedIndex], displayedEntry)) {
|
|
877
910
|
acceptedIndex += 1;
|
|
@@ -892,7 +925,9 @@ function queuedPromptMatchesEntry(prompt, entry) {
|
|
|
892
925
|
|
|
893
926
|
function promptAcknowledgedInChat(prompt, chat) {
|
|
894
927
|
const id = promptIdentity(prompt);
|
|
895
|
-
if (!id
|
|
928
|
+
if (!id) return false;
|
|
929
|
+
if (chatAckIds.has(id)) return true;
|
|
930
|
+
if (!Array.isArray(chat)) return false;
|
|
896
931
|
return chat.some((entry) => queuedPromptMatchesEntry(prompt, entry));
|
|
897
932
|
}
|
|
898
933
|
|
|
@@ -916,10 +951,15 @@ function settleQueuedFromTranscript(chat, shouldRender = true) {
|
|
|
916
951
|
return true;
|
|
917
952
|
}
|
|
918
953
|
|
|
919
|
-
function syncChat(chat) {
|
|
954
|
+
function syncChat(chat, revision) {
|
|
920
955
|
const nextChat = Array.isArray(chat) ? chat : [];
|
|
921
956
|
settleQueuedFromTranscript(nextChat);
|
|
922
|
-
|
|
957
|
+
const nextRevision = parseChatRevision(revision);
|
|
958
|
+
if (nextRevision !== null && nextRevision < chatRevision) return false;
|
|
959
|
+
if (nextRevision === null || nextRevision === chatRevision) {
|
|
960
|
+
if (!chatContainsEntries(nextChat, displayedChat)) return false;
|
|
961
|
+
}
|
|
962
|
+
if (nextRevision !== null) chatRevision = nextRevision;
|
|
923
963
|
displayedChat = nextChat.slice();
|
|
924
964
|
for (const el of [...chatLog.querySelectorAll(".bubble.user,.bubble.agent:not(.agent-working)")]) {
|
|
925
965
|
el.remove();
|
|
@@ -1850,17 +1890,23 @@ async function submitQueuedOnce(submission, preserveFailureState = false) {
|
|
|
1850
1890
|
throw new Error("failed to submit queued prompts");
|
|
1851
1891
|
}
|
|
1852
1892
|
const accepted = typeof response.json === "function" ? await response.json().catch(() => null) : null;
|
|
1853
|
-
|
|
1854
|
-
|
|
1893
|
+
rememberChatAckIds(accepted?.ack_ids);
|
|
1894
|
+
const acceptedChat = Array.isArray(accepted?.chat) ? accepted.chat : null;
|
|
1895
|
+
if (acceptedChat) settleQueuedFromTranscript(acceptedChat);
|
|
1896
|
+
const acceptedRevision = parseChatRevision(accepted?.chat_revision);
|
|
1897
|
+
const reconciledChat = acceptedChat
|
|
1898
|
+
? acceptedRevision !== null && acceptedRevision > chatRevision
|
|
1899
|
+
? acceptedChat
|
|
1900
|
+
: mergeAcceptedChat(acceptedChat, submission.chatAtRequest)
|
|
1855
1901
|
: null;
|
|
1856
|
-
if (!
|
|
1902
|
+
if (!acceptedChat || reconciledChat) {
|
|
1857
1903
|
for (const prompt of prompts) {
|
|
1858
1904
|
deliveredPrompts.add(prompt);
|
|
1859
1905
|
const index = queued.indexOf(prompt);
|
|
1860
1906
|
if (index !== -1) queued.splice(index, 1);
|
|
1861
1907
|
}
|
|
1862
1908
|
persistQueuedPrompts();
|
|
1863
|
-
if (reconciledChat) syncChat(reconciledChat);
|
|
1909
|
+
if (reconciledChat) syncChat(reconciledChat, acceptedRevision);
|
|
1864
1910
|
}
|
|
1865
1911
|
render();
|
|
1866
1912
|
settleAcknowledgementGuidance(submission, preserveFailureState);
|
|
@@ -3987,7 +4033,10 @@ events.set("agent-reply", (data) => {
|
|
|
3987
4033
|
if (addChat(entry)) displayedChat.push(entry);
|
|
3988
4034
|
noteAgentReply(entry.text);
|
|
3989
4035
|
});
|
|
3990
|
-
events.set("chat-sync", (data) =>
|
|
4036
|
+
events.set("chat-sync", (data) => {
|
|
4037
|
+
rememberChatAckIds(data.ack_ids);
|
|
4038
|
+
syncChat(data.chat || [], data.chat_revision);
|
|
4039
|
+
});
|
|
3991
4040
|
events.set("agent-presence", (data) => setAgentPresence(data.state));
|
|
3992
4041
|
events.set("layout-warnings", (data) => setLayoutWarnings(data.warnings || []));
|
|
3993
4042
|
events.set("ended", () => markSessionEnded());
|
package/dist/cli.mjs
CHANGED
|
@@ -7436,6 +7436,7 @@ var SELECTOR_MAX = 512;
|
|
|
7436
7436
|
var LABEL_MAX = 40;
|
|
7437
7437
|
var PROMPT_ID_MAX = 128;
|
|
7438
7438
|
var PROMPT_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
7439
|
+
var MAX_CHAT_STORED_BYTES = 5242880;
|
|
7439
7440
|
function normalizePromptId(value) {
|
|
7440
7441
|
if (typeof value !== "string") return "";
|
|
7441
7442
|
const id = value.trim();
|
|
@@ -7839,6 +7840,52 @@ function serializeChat(chat) {
|
|
|
7839
7840
|
}
|
|
7840
7841
|
return serialized;
|
|
7841
7842
|
}
|
|
7843
|
+
function boundStoredChat(chat, maxBytes = MAX_CHAT_STORED_BYTES) {
|
|
7844
|
+
const entries = Array.isArray(chat) ? chat.filter((entry) => entry && typeof entry === "object") : [];
|
|
7845
|
+
if (entries.length === 0) return { chat: [], evicted: [] };
|
|
7846
|
+
const limit = Number.isFinite(maxBytes) && maxBytes > 0 ? maxBytes : MAX_CHAT_STORED_BYTES;
|
|
7847
|
+
let storedBytes = 2;
|
|
7848
|
+
let cut = entries.length;
|
|
7849
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
7850
|
+
const serialized = JSON.stringify(entries[index]);
|
|
7851
|
+
const entryBytes = Buffer.byteLength(serialized === void 0 ? "null" : serialized, "utf8");
|
|
7852
|
+
const nextBytes = storedBytes + entryBytes + (cut < entries.length ? 1 : 0);
|
|
7853
|
+
if (nextBytes > limit) break;
|
|
7854
|
+
storedBytes = nextBytes;
|
|
7855
|
+
cut = index;
|
|
7856
|
+
}
|
|
7857
|
+
return { chat: entries.slice(cut), evicted: entries.slice(0, cut) };
|
|
7858
|
+
}
|
|
7859
|
+
function serializeChatAckIds(ids) {
|
|
7860
|
+
const ackIds = [];
|
|
7861
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7862
|
+
for (const value of Array.isArray(ids) ? ids : []) {
|
|
7863
|
+
const id = normalizePromptId(value);
|
|
7864
|
+
if (!id || seen.has(id)) continue;
|
|
7865
|
+
seen.add(id);
|
|
7866
|
+
ackIds.push(id);
|
|
7867
|
+
}
|
|
7868
|
+
return ackIds;
|
|
7869
|
+
}
|
|
7870
|
+
function collectChatAckIds(evicted, existing = []) {
|
|
7871
|
+
const ackIds = serializeChatAckIds(existing);
|
|
7872
|
+
const seen = new Set(ackIds);
|
|
7873
|
+
for (const entry of Array.isArray(evicted) ? evicted : []) {
|
|
7874
|
+
const id = normalizePromptId(entry?.prompt_id);
|
|
7875
|
+
if (!id || seen.has(id)) continue;
|
|
7876
|
+
seen.add(id);
|
|
7877
|
+
ackIds.push(id);
|
|
7878
|
+
}
|
|
7879
|
+
return ackIds;
|
|
7880
|
+
}
|
|
7881
|
+
function serializeChatSync(session) {
|
|
7882
|
+
const revision = Number(session?.chat_revision);
|
|
7883
|
+
return {
|
|
7884
|
+
chat: serializeChat(session?.chat || []),
|
|
7885
|
+
ack_ids: serializeChatAckIds(session?.chat_ack_ids),
|
|
7886
|
+
chat_revision: Number.isSafeInteger(revision) && revision >= 0 ? revision : 0
|
|
7887
|
+
};
|
|
7888
|
+
}
|
|
7842
7889
|
|
|
7843
7890
|
// src/html-transform.js
|
|
7844
7891
|
function injectLavishSdk(html, key, artifactRevision, artifactLoadToken = "") {
|
|
@@ -8036,6 +8083,10 @@ var SessionStore = class {
|
|
|
8036
8083
|
delivered_attachments: Array.isArray(existing.delivered_attachments) ? existing.delivered_attachments : [],
|
|
8037
8084
|
dom_snapshot: existing.dom_snapshot || "",
|
|
8038
8085
|
chat: existing.chat || [],
|
|
8086
|
+
chat_revision: normalizeRevision(existing.chat_revision),
|
|
8087
|
+
// Compact prompt_id acks for bubbles evicted by the stored-chat byte bound. Reopening
|
|
8088
|
+
// must keep them: they are the settlement/dedup source once the visible entry is gone.
|
|
8089
|
+
chat_ack_ids: Array.isArray(existing.chat_ack_ids) ? existing.chat_ack_ids : [],
|
|
8039
8090
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
8040
8091
|
};
|
|
8041
8092
|
state.sessions[key] = session;
|
|
@@ -8067,7 +8118,10 @@ var SessionStore = class {
|
|
|
8067
8118
|
let normalized = prompts.map(normalizePrompt);
|
|
8068
8119
|
if (!restoring) {
|
|
8069
8120
|
const acknowledgedIds = new Set(
|
|
8070
|
-
|
|
8121
|
+
[
|
|
8122
|
+
...(session.chat || []).map((entry) => normalizePromptId(entry?.prompt_id)),
|
|
8123
|
+
...(session.chat_ack_ids || []).map((id) => normalizePromptId(id))
|
|
8124
|
+
].filter(Boolean)
|
|
8071
8125
|
);
|
|
8072
8126
|
normalized = normalized.filter(({ prompt }) => {
|
|
8073
8127
|
const promptId = normalizePromptId(prompt.prompt_id);
|
|
@@ -8147,6 +8201,8 @@ var SessionStore = class {
|
|
|
8147
8201
|
const storedPrompts = restoring ? acceptedPrompts : acceptedPrompts.map(agentFacingPrompt);
|
|
8148
8202
|
session.prompts = restoring ? [...storedPrompts, ...existingPrompts] : [...existingPrompts, ...storedPrompts];
|
|
8149
8203
|
session.chat = [...session.chat || [], ...userMessages];
|
|
8204
|
+
applyTranscriptBound(session);
|
|
8205
|
+
if (userMessages.length > 0) session.chat_revision = normalizeRevision(session.chat_revision) + 1;
|
|
8150
8206
|
if (restoring) {
|
|
8151
8207
|
const restoredFailures = Array.isArray(payload.artifact_failures) ? JSON.parse(JSON.stringify(payload.artifact_failures)) : [];
|
|
8152
8208
|
const existingFailures = Array.isArray(session.artifact_failures) ? session.artifact_failures : [];
|
|
@@ -8467,11 +8523,11 @@ var SessionStore = class {
|
|
|
8467
8523
|
if (!session) {
|
|
8468
8524
|
return null;
|
|
8469
8525
|
}
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
session.updated_at =
|
|
8526
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
8527
|
+
session.chat = [...session.chat || [], { role: "agent", text: String(text || ""), at }];
|
|
8528
|
+
applyTranscriptBound(session);
|
|
8529
|
+
session.chat_revision = normalizeRevision(session.chat_revision) + 1;
|
|
8530
|
+
session.updated_at = at;
|
|
8475
8531
|
await this.writeState(state);
|
|
8476
8532
|
return session;
|
|
8477
8533
|
});
|
|
@@ -8515,7 +8571,15 @@ var SessionStore = class {
|
|
|
8515
8571
|
try {
|
|
8516
8572
|
const raw = await readFile3(this.file, "utf8");
|
|
8517
8573
|
const parsed = JSON.parse(raw);
|
|
8518
|
-
|
|
8574
|
+
const state = { sessions: parsed.sessions || {} };
|
|
8575
|
+
let changed = false;
|
|
8576
|
+
for (const session of Object.values(state.sessions)) {
|
|
8577
|
+
if (!session || typeof session !== "object" || !applyTranscriptBound(session)) continue;
|
|
8578
|
+
session.chat_revision = normalizeRevision(session.chat_revision) + 1;
|
|
8579
|
+
changed = true;
|
|
8580
|
+
}
|
|
8581
|
+
if (changed) await this.writeState(state);
|
|
8582
|
+
return state;
|
|
8519
8583
|
} catch (error) {
|
|
8520
8584
|
if (error && error.code === "ENOENT") {
|
|
8521
8585
|
return { sessions: {} };
|
|
@@ -8535,6 +8599,16 @@ async function canonicalFile(file) {
|
|
|
8535
8599
|
function sessionKey(file) {
|
|
8536
8600
|
return crypto4.createHash("sha256").update(file).digest("hex").slice(0, 16);
|
|
8537
8601
|
}
|
|
8602
|
+
function applyTranscriptBound(session) {
|
|
8603
|
+
const originalChat = Array.isArray(session.chat) ? session.chat : [];
|
|
8604
|
+
const { chat, evicted } = boundStoredChat(session.chat);
|
|
8605
|
+
const chatChanged = !Array.isArray(session.chat) || chat.length !== originalChat.length || chat.some((entry, index) => entry !== originalChat[index]);
|
|
8606
|
+
session.chat = chat;
|
|
8607
|
+
if (evicted.length === 0) return chatChanged;
|
|
8608
|
+
const existingAckCount = Array.isArray(session.chat_ack_ids) ? session.chat_ack_ids.length : 0;
|
|
8609
|
+
session.chat_ack_ids = collectChatAckIds(evicted, session.chat_ack_ids);
|
|
8610
|
+
return chatChanged || session.chat_ack_ids.length !== existingAckCount;
|
|
8611
|
+
}
|
|
8538
8612
|
function normalizePrompt(prompt) {
|
|
8539
8613
|
const normalized = {
|
|
8540
8614
|
uid: String(prompt.uid || ""),
|
|
@@ -9400,7 +9474,7 @@ async function serve({
|
|
|
9400
9474
|
}
|
|
9401
9475
|
events.on("reload", (key) => broadcastLiveEvent("reload", key));
|
|
9402
9476
|
events.on("agent-reply", (key, entry) => broadcastLiveEvent("agent-reply", key, entry));
|
|
9403
|
-
events.on("chat-sync", (key,
|
|
9477
|
+
events.on("chat-sync", (key, session) => broadcastLiveEvent("chat-sync", key, serializeChatSync(session)));
|
|
9404
9478
|
events.on("agent-presence", (key, state) => broadcastLiveEvent("agent-presence", key, { state }));
|
|
9405
9479
|
events.on("layout-warnings", (key, warnings) => broadcastLiveEvent("layout-warnings", key, { warnings }));
|
|
9406
9480
|
events.on("ended", (key, endedBy) => broadcastLiveEvent("ended", key, { ended_by: endedBy || null }));
|
|
@@ -9447,7 +9521,7 @@ async function serve({
|
|
|
9447
9521
|
cleanup();
|
|
9448
9522
|
return;
|
|
9449
9523
|
}
|
|
9450
|
-
client.sendEvent("chat-sync",
|
|
9524
|
+
client.sendEvent("chat-sync", serializeChatSync(session));
|
|
9451
9525
|
client.sendEvent("agent-presence", { state: computePresence(key, activePolls, deliveredFeedback) });
|
|
9452
9526
|
if (session?.status === "ended") client.sendEvent("ended", { ended_by: session.ended_by || null });
|
|
9453
9527
|
}
|
|
@@ -9475,11 +9549,8 @@ async function serve({
|
|
|
9475
9549
|
}
|
|
9476
9550
|
function finishFeedbackDelivery(key, result) {
|
|
9477
9551
|
if (result.status !== "feedback") return;
|
|
9478
|
-
const chat = result.chat;
|
|
9479
|
-
delete result.chat;
|
|
9480
9552
|
markFeedbackDelivered(key, activePolls, deliveredFeedback, events);
|
|
9481
9553
|
if (result.session_ended) clearFeedbackDelivery(key, activePolls, deliveredFeedback, events);
|
|
9482
|
-
if (Array.isArray(chat)) events.emit("chat-sync", key, chat);
|
|
9483
9554
|
}
|
|
9484
9555
|
async function restoreClosedFeedback(key, result) {
|
|
9485
9556
|
if (result.status !== "feedback") return;
|
|
@@ -9816,11 +9887,11 @@ async function serve({
|
|
|
9816
9887
|
}
|
|
9817
9888
|
if (shouldEndSession) events.emit("ended", req.params.key, publishedSession.ended_by);
|
|
9818
9889
|
else if (freshFeedback) events.emit("feedback", req.params.key, publishedSession.ended_by);
|
|
9819
|
-
events.emit("chat-sync", req.params.key, publishedSession
|
|
9890
|
+
events.emit("chat-sync", req.params.key, publishedSession);
|
|
9820
9891
|
res.json({
|
|
9821
9892
|
status: "queued",
|
|
9822
9893
|
pending_prompts: publishedSession.pending_prompts,
|
|
9823
|
-
|
|
9894
|
+
...serializeChatSync(publishedSession)
|
|
9824
9895
|
});
|
|
9825
9896
|
if (shouldEndSession) await shutdownIfNoLiveSessions();
|
|
9826
9897
|
} catch (error) {
|
|
@@ -9924,8 +9995,11 @@ async function serve({
|
|
|
9924
9995
|
res.status(404).json({ error: "session not found" });
|
|
9925
9996
|
return;
|
|
9926
9997
|
}
|
|
9927
|
-
const entry = serializeChat([
|
|
9998
|
+
const entry = serializeChat([
|
|
9999
|
+
session.chat?.at(-1)?.role === "agent" ? session.chat.at(-1) : { role: "agent", text, at: session.updated_at }
|
|
10000
|
+
])[0];
|
|
9928
10001
|
events.emit("agent-reply", req.params.key, entry);
|
|
10002
|
+
events.emit("chat-sync", req.params.key, session);
|
|
9929
10003
|
clearFeedbackDelivery(req.params.key, activePolls, deliveredFeedback, events);
|
|
9930
10004
|
res.json({ status: "sent" });
|
|
9931
10005
|
} catch (error) {
|
|
@@ -11099,6 +11173,8 @@ function createChromeHtml(session, {
|
|
|
11099
11173
|
initialEnded: session.status === "ended",
|
|
11100
11174
|
initialEndedBy: session.ended_by || null,
|
|
11101
11175
|
initialChat: serializeChat(session.chat || []),
|
|
11176
|
+
initialChatAckIds: serializeChatAckIds(session.chat_ack_ids),
|
|
11177
|
+
initialChatRevision: Number.isSafeInteger(session.chat_revision) && session.chat_revision >= 0 ? session.chat_revision : 0,
|
|
11102
11178
|
// Bootstrapping the inbox from the server is what makes it survive a browser refresh or a
|
|
11103
11179
|
// reconnect: the chrome never owns warning state, it only renders it.
|
|
11104
11180
|
initialLayoutWarnings: serializeLayoutWarnings(session.layout_warnings),
|
|
@@ -11399,7 +11475,7 @@ var POLL_WAKE_PATH_RULES = Object.freeze([
|
|
|
11399
11475
|
]);
|
|
11400
11476
|
var POLL_SEND_AND_END_RULE = "`Send & End` ends the session. Its final feedback is still delivered once. After that response, polling stops, and the agent must not reopen the session uninvited.";
|
|
11401
11477
|
var CODEX_POLL_WAKE_PATH_GUIDANCE = "Codex detected: completed background tasks may not resume Codex automatically, so keep the poll attached to the active turn.";
|
|
11402
|
-
var VERSION = "0.1.
|
|
11478
|
+
var VERSION = "0.1.70";
|
|
11403
11479
|
function detectInvokingAgent(env = process.env) {
|
|
11404
11480
|
return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
|
|
11405
11481
|
}
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "lavish-axi",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.70",
|
|
5
5
|
"description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Kun Chen",
|