lavish-axi 0.1.49 → 0.1.50
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 +23 -2
- package/dist/cli.mjs +79 -36
- package/package.json +1 -1
- package/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -217,7 +217,7 @@ pnpm link
|
|
|
217
217
|
- **Diagnostic viewports** - `LAVISH_AXI_DIAGNOSTIC_VIEWPORTS` sets which viewport classes the layout-issue inbox tracks (`mobile`, `compact`, `desktop`; comma-separated, default all). Warnings whose class leaves the set are marked obsolete with an explicit reason instead of silently reading as fixed.
|
|
218
218
|
- **Server port** - Set `LAVISH_AXI_PORT` to choose the server port; it defaults to `4387`.
|
|
219
219
|
- **Network binding** - The server binds to loopback (`127.0.0.1`) by default. Set `LAVISH_AXI_HOST` to bind elsewhere; a wildcard (`0.0.0.0` or `::`) binds every interface. Binding beyond loopback exposes an unauthenticated server that can read and serve arbitrary local files to anything that can reach it, so only do so on a trusted network. Set `LAVISH_AXI_LINK_HOST` to control the hostname written into generated session links (defaults to the bind address, or loopback when bound to a wildcard).
|
|
220
|
-
- **Allowed hosts** - To defend against DNS rebinding, the server rejects (`403`) any request whose `Host` header is missing or not one it answers to: the loopback names (`127.0.0.1`, `::1`, `localhost`) plus the configured bind and link host. If you reach the server under another name - a wildcard bind accessed by LAN IP, a reverse-proxy hostname, or an extra interface - list those names in `LAVISH_AXI_ALLOWED_HOSTS` (whitespace-separated) to allow them. Behind a reverse proxy, the forwarded `X-Forwarded-Host` is validated against the same list, so add your public hostname there and have the proxy send it
|
|
220
|
+
- **Allowed hosts** - To defend against DNS rebinding, the server rejects (`403`) any request whose `Host` header is missing or not one it answers to: the loopback names (`127.0.0.1`, `::1`, `localhost`) plus the configured bind and link host. If you reach the server under another name - a wildcard bind accessed by LAN IP, a reverse-proxy hostname, or an extra interface - list those names in `LAVISH_AXI_ALLOWED_HOSTS` (whitespace-separated) to allow them. Behind a reverse proxy, the forwarded `X-Forwarded-Host` is validated against the same list, so add your public hostname there and have the proxy send it together with `X-Forwarded-Proto`. Set `LAVISH_AXI_ALLOWED_HOSTS` to `*` to disable the check entirely (only when the server sits behind your own authentication or proxy).
|
|
221
221
|
- **Browser opening** - Set `LAVISH_AXI_NO_OPEN=1`, equivalent to `--no-open`, to create or resume a session without launching a browser window.
|
|
222
222
|
|
|
223
223
|
## CLI Reference
|
package/dist/chrome-client.js
CHANGED
|
@@ -1293,7 +1293,8 @@ function showWhiteboardOverlay(index) {
|
|
|
1293
1293
|
postToFrame({ type: "lavish:suspendWhiteboard", diagramIndex: index });
|
|
1294
1294
|
// A fresh document per open: the frame boots, posts ready, and receives its
|
|
1295
1295
|
// init - no stale editor state can leak between opens.
|
|
1296
|
-
whiteboardFrame.src =
|
|
1296
|
+
whiteboardFrame.src =
|
|
1297
|
+
"/whiteboard-frame?diagramIndex=" + encodeURIComponent(String(index)) + "&key=" + encodeURIComponent(key);
|
|
1297
1298
|
}
|
|
1298
1299
|
|
|
1299
1300
|
function finishWhiteboardClose(index) {
|
|
@@ -1573,10 +1574,30 @@ function handleAuthenticatedWhiteboardMessage(index, message, mode) {
|
|
|
1573
1574
|
if (message.type === "lavish-whiteboard:flushComplete") finishWhiteboardFlush(index, message, mode);
|
|
1574
1575
|
}
|
|
1575
1576
|
|
|
1577
|
+
// Inline whiteboard frames are created by the SDK inside the artifact document,
|
|
1578
|
+
// so a genuine one is always a direct child of the *current* artifact window.
|
|
1579
|
+
// Descent - not the channel token - is what proves the sender is ours: the
|
|
1580
|
+
// frame page is framable by any origin, so a token is not a secret an attacker
|
|
1581
|
+
// cannot obtain. Without this, any window that could postMessage to this chrome
|
|
1582
|
+
// (a page that framed it, or one holding a window.open handle) could open a
|
|
1583
|
+
// channel and queue a fabricated prompt. Mirrors the artifact-message handler's
|
|
1584
|
+
// `event.source !== frame.contentWindow` guard.
|
|
1585
|
+
function isArtifactChildWindow(source) {
|
|
1586
|
+
if (!source) return false;
|
|
1587
|
+
try {
|
|
1588
|
+
// Reading `parent` on a cross-origin WindowProxy is permitted; the frame's
|
|
1589
|
+
// sandbox makes everything else about it opaque.
|
|
1590
|
+
return source.parent === frame.contentWindow;
|
|
1591
|
+
} catch {
|
|
1592
|
+
return false;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1576
1596
|
function handleInlineWhiteboardMessage(event, message) {
|
|
1577
1597
|
if (ended) return;
|
|
1598
|
+
if (!isArtifactChildWindow(event.source)) return;
|
|
1578
1599
|
const index = validWhiteboardIndex(message.diagramIndex);
|
|
1579
|
-
if (index === null
|
|
1600
|
+
if (index === null) return;
|
|
1580
1601
|
if (message.type === "lavish-whiteboard:ready") {
|
|
1581
1602
|
if (inlineWhiteboardChannels.has(index)) return;
|
|
1582
1603
|
const channelId = String(message.channelToken || "");
|
package/dist/cli.mjs
CHANGED
|
@@ -4497,6 +4497,7 @@ import crypto4 from "node:crypto";
|
|
|
4497
4497
|
import { EventEmitter } from "node:events";
|
|
4498
4498
|
import { existsSync as existsSync2 } from "node:fs";
|
|
4499
4499
|
import { readFile as readFile4, realpath as realpath3 } from "node:fs/promises";
|
|
4500
|
+
import { isIP } from "node:net";
|
|
4500
4501
|
import { homedir } from "node:os";
|
|
4501
4502
|
import path6 from "node:path";
|
|
4502
4503
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -4755,7 +4756,7 @@ function isNearTotalOcclusion({ occludedSamples, totalSamples, minSamples = 5, m
|
|
|
4755
4756
|
const total = Number(totalSamples);
|
|
4756
4757
|
return Number.isFinite(occluded) && Number.isFinite(total) && total >= minSamples && occluded / total >= minRatio;
|
|
4757
4758
|
}
|
|
4758
|
-
function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInteractiveControl, mermaid = mermaid_node_exports, artifactRevision = 0, artifactLoadToken = "") {
|
|
4759
|
+
function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInteractiveControl, mermaid = mermaid_node_exports, artifactRevision = 0, artifactLoadToken = "", sessionKey2 = "") {
|
|
4759
4760
|
const { isMermaidSvg: isMermaidSvg2, mermaidNodeFrom: mermaidNodeFrom2, mermaidNodeElement: mermaidNodeElement2 } = mermaid;
|
|
4760
4761
|
function postArtifactMessage(type, payload = {}) {
|
|
4761
4762
|
parent.postMessage({ type, ...payload, artifact_load_token: String(artifactLoadToken || "") }, "*");
|
|
@@ -4950,7 +4951,10 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
4950
4951
|
function whiteboardFrameSrc(entry) {
|
|
4951
4952
|
const params = new URLSearchParams({
|
|
4952
4953
|
diagramIndex: String(entry.index),
|
|
4953
|
-
diagramId: String(entry.diagramId || "")
|
|
4954
|
+
diagramId: String(entry.diagramId || ""),
|
|
4955
|
+
// The frame's channel token is bound to this session, so the frame page
|
|
4956
|
+
// must be told which session it belongs to.
|
|
4957
|
+
key: String(sessionKey2 || "")
|
|
4954
4958
|
});
|
|
4955
4959
|
return `/whiteboard-frame?${params}`;
|
|
4956
4960
|
}
|
|
@@ -7315,18 +7319,22 @@ function defaultWhiteboardAssetsDir() {
|
|
|
7315
7319
|
function isWhiteboardWriteApiPath(pathname) {
|
|
7316
7320
|
return /^\/api\/[0-9a-f]{16}\/whiteboard\/\d{1,3}(\/feedback-files)?$/.test(String(pathname || ""));
|
|
7317
7321
|
}
|
|
7318
|
-
function
|
|
7319
|
-
|
|
7320
|
-
const signature = crypto4.createHmac("sha256", secret).update(payload).digest("base64url");
|
|
7321
|
-
return `${payload}.${signature}`;
|
|
7322
|
+
function whiteboardChannelPayload(issuedAt, nonce, sessionKey2) {
|
|
7323
|
+
return `${issuedAt}.${nonce}.${sessionKey2}`;
|
|
7322
7324
|
}
|
|
7323
|
-
function
|
|
7325
|
+
function createWhiteboardChannelToken(secret, sessionKey2, now = Date.now()) {
|
|
7326
|
+
const nonce = crypto4.randomBytes(24).toString("base64url");
|
|
7327
|
+
const signature = crypto4.createHmac("sha256", secret).update(whiteboardChannelPayload(now, nonce, String(sessionKey2 || ""))).digest("base64url");
|
|
7328
|
+
return `${now}.${nonce}.${signature}`;
|
|
7329
|
+
}
|
|
7330
|
+
function isValidWhiteboardChannelToken(token, secret, sessionKey2, now = Date.now()) {
|
|
7331
|
+
if (!isValidWhiteboardKey(sessionKey2)) return false;
|
|
7324
7332
|
const [issuedAtText, nonce, signature, extra] = String(token || "").split(".");
|
|
7325
7333
|
if (extra !== void 0 || !/^\d{13}$/.test(issuedAtText) || !/^[A-Za-z0-9_-]{32}$/.test(nonce)) return false;
|
|
7326
7334
|
const issuedAt = Number(issuedAtText);
|
|
7327
7335
|
if (!Number.isSafeInteger(issuedAt) || issuedAt > now || now - issuedAt > WHITEBOARD_CHANNEL_TOKEN_TTL_MS)
|
|
7328
7336
|
return false;
|
|
7329
|
-
const expected = crypto4.createHmac("sha256", secret).update(
|
|
7337
|
+
const expected = crypto4.createHmac("sha256", secret).update(whiteboardChannelPayload(issuedAtText, nonce, String(sessionKey2))).digest("base64url");
|
|
7330
7338
|
const actualBuffer = Buffer.from(signature || "", "utf8");
|
|
7331
7339
|
const expectedBuffer = Buffer.from(expected, "utf8");
|
|
7332
7340
|
return actualBuffer.length === expectedBuffer.length && crypto4.timingSafeEqual(actualBuffer, expectedBuffer);
|
|
@@ -7369,7 +7377,8 @@ async function serve({
|
|
|
7369
7377
|
let publicPort = port;
|
|
7370
7378
|
const whiteboardStateRoot = path6.dirname(stateFile2);
|
|
7371
7379
|
const allowedHostnames = buildAllowedHostnames({ host, linkHost: linkHostName, allowedHosts });
|
|
7372
|
-
|
|
7380
|
+
const allowAnyHostname = allowsAllHosts(allowedHosts);
|
|
7381
|
+
if (!allowAnyHostname) {
|
|
7373
7382
|
app.use((req, res, next) => {
|
|
7374
7383
|
const requestHost = { host: req.headers.host, forwardedHost: req.headers["x-forwarded-host"] };
|
|
7375
7384
|
if (isAllowedRequestHost(requestHost, allowedHostnames)) {
|
|
@@ -7497,6 +7506,10 @@ async function serve({
|
|
|
7497
7506
|
});
|
|
7498
7507
|
app.post("/api/:key/prompts", async (req, res, next) => {
|
|
7499
7508
|
try {
|
|
7509
|
+
if (!isSameOriginRequest(req, allowedHostnames, allowAnyHostname)) {
|
|
7510
|
+
res.status(403).json({ error: "cross-origin prompt submission rejected" });
|
|
7511
|
+
return;
|
|
7512
|
+
}
|
|
7500
7513
|
const shouldEndSession = Boolean(req.body?.endSession || req.body?.end_session);
|
|
7501
7514
|
const hasLayoutWarningPrompt = Array.isArray(req.body?.prompts) ? req.body.prompts.some((prompt) => prompt?.tag === "layout-warnings") : false;
|
|
7502
7515
|
const session = await store.queuePrompts(req.params.key, req.body || {});
|
|
@@ -7654,7 +7667,7 @@ async function serve({
|
|
|
7654
7667
|
});
|
|
7655
7668
|
app.post("/api/:key/share", async (req, res, next) => {
|
|
7656
7669
|
try {
|
|
7657
|
-
if (!isSameOriginRequest(req)) {
|
|
7670
|
+
if (!isSameOriginRequest(req, allowedHostnames, allowAnyHostname)) {
|
|
7658
7671
|
res.status(403).json({ error: "cross-origin share request rejected" });
|
|
7659
7672
|
return;
|
|
7660
7673
|
}
|
|
@@ -7713,6 +7726,8 @@ async function serve({
|
|
|
7713
7726
|
await watchSession(session, watchers, events, logEvent, reloadDebounceMs);
|
|
7714
7727
|
const artifactHtml = await readFile4(session.file, "utf8").catch(() => "");
|
|
7715
7728
|
const { faviconTag, title } = extractArtifactHead(artifactHtml);
|
|
7729
|
+
res.setHeader("x-frame-options", "DENY");
|
|
7730
|
+
res.setHeader("content-security-policy", "frame-ancestors 'none'");
|
|
7716
7731
|
res.type("html").send(
|
|
7717
7732
|
createChromeHtml(session, {
|
|
7718
7733
|
layoutGateEnabled: shouldEnableLayoutGate(req.query || {}),
|
|
@@ -7733,7 +7748,7 @@ async function serve({
|
|
|
7733
7748
|
});
|
|
7734
7749
|
app.post("/api/:key/chrome-loads/begin", async (req, res, next) => {
|
|
7735
7750
|
try {
|
|
7736
|
-
if (!isSameOriginRequest(req)) {
|
|
7751
|
+
if (!isSameOriginRequest(req, allowedHostnames, allowAnyHostname)) {
|
|
7737
7752
|
res.status(403).json({ error: "cross-origin chrome handoff rejected" });
|
|
7738
7753
|
return;
|
|
7739
7754
|
}
|
|
@@ -7934,7 +7949,12 @@ data: ${JSON.stringify({ state: computePresence(req.params.key, activePolls, del
|
|
|
7934
7949
|
});
|
|
7935
7950
|
app.get("/whiteboard-frame", (req, res) => {
|
|
7936
7951
|
res.setHeader("cache-control", "no-store");
|
|
7937
|
-
|
|
7952
|
+
const sessionKey2 = String(req.query.key || "");
|
|
7953
|
+
if (!isValidWhiteboardKey(sessionKey2)) {
|
|
7954
|
+
res.status(400).type("text/plain").send("Missing session key");
|
|
7955
|
+
return;
|
|
7956
|
+
}
|
|
7957
|
+
res.type("html").send(createWhiteboardFrameHtml(createWhiteboardChannelToken(whiteboardChannelSecret, sessionKey2)));
|
|
7938
7958
|
});
|
|
7939
7959
|
app.get(/^\/whiteboard-assets\/(.+)$/, async (req, res, next) => {
|
|
7940
7960
|
try {
|
|
@@ -7987,7 +8007,7 @@ data: ${JSON.stringify({ state: computePresence(req.params.key, activePolls, del
|
|
|
7987
8007
|
});
|
|
7988
8008
|
app.post("/api/:key/whiteboard-channel", async (req, res, next) => {
|
|
7989
8009
|
try {
|
|
7990
|
-
if (!isSameOriginRequest(req)) {
|
|
8010
|
+
if (!isSameOriginRequest(req, allowedHostnames, allowAnyHostname)) {
|
|
7991
8011
|
res.status(403).json({ error: "cross-origin whiteboard channel request rejected" });
|
|
7992
8012
|
return;
|
|
7993
8013
|
}
|
|
@@ -7996,7 +8016,7 @@ data: ${JSON.stringify({ state: computePresence(req.params.key, activePolls, del
|
|
|
7996
8016
|
res.status(404).json({ error: "session not found" });
|
|
7997
8017
|
return;
|
|
7998
8018
|
}
|
|
7999
|
-
if (!isValidWhiteboardChannelToken(req.body?.token, whiteboardChannelSecret)) {
|
|
8019
|
+
if (!isValidWhiteboardChannelToken(req.body?.token, whiteboardChannelSecret, req.params.key)) {
|
|
8000
8020
|
res.status(403).json({ error: "invalid whiteboard channel" });
|
|
8001
8021
|
return;
|
|
8002
8022
|
}
|
|
@@ -8007,7 +8027,7 @@ data: ${JSON.stringify({ state: computePresence(req.params.key, activePolls, del
|
|
|
8007
8027
|
});
|
|
8008
8028
|
app.put("/api/:key/whiteboard/:index", async (req, res, next) => {
|
|
8009
8029
|
try {
|
|
8010
|
-
if (!isSameOriginRequest(req)) {
|
|
8030
|
+
if (!isSameOriginRequest(req, allowedHostnames, allowAnyHostname)) {
|
|
8011
8031
|
res.status(403).json({ error: "cross-origin whiteboard write rejected" });
|
|
8012
8032
|
return;
|
|
8013
8033
|
}
|
|
@@ -8030,7 +8050,7 @@ data: ${JSON.stringify({ state: computePresence(req.params.key, activePolls, del
|
|
|
8030
8050
|
});
|
|
8031
8051
|
app.post("/api/:key/whiteboard/:index/feedback-files", async (req, res, next) => {
|
|
8032
8052
|
try {
|
|
8033
|
-
if (!isSameOriginRequest(req)) {
|
|
8053
|
+
if (!isSameOriginRequest(req, allowedHostnames, allowAnyHostname)) {
|
|
8034
8054
|
res.status(403).json({ error: "cross-origin whiteboard write rejected" });
|
|
8035
8055
|
return;
|
|
8036
8056
|
}
|
|
@@ -8186,27 +8206,37 @@ function buildAllowedHostnames({ host, linkHost: linkHostName, allowedHosts = []
|
|
|
8186
8206
|
function allowsAllHosts(allowedHosts = []) {
|
|
8187
8207
|
return allowedHosts.some((value) => String(value).trim() === "*");
|
|
8188
8208
|
}
|
|
8189
|
-
function
|
|
8209
|
+
function parseHostAuthority(value) {
|
|
8190
8210
|
const raw = String(value).trim();
|
|
8211
|
+
if (!raw || /[@/\\?#\s]/.test(raw)) return null;
|
|
8212
|
+
let hostname;
|
|
8213
|
+
let port;
|
|
8214
|
+
let bracketed = false;
|
|
8191
8215
|
if (raw.startsWith("[")) {
|
|
8192
|
-
const
|
|
8193
|
-
if (
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8216
|
+
const match = /^\[([0-9A-Fa-f:.]+)\](?::(\d+))?$/.exec(raw);
|
|
8217
|
+
if (!match || isIP(match[1]) !== 6) return null;
|
|
8218
|
+
[, hostname, port = ""] = match;
|
|
8219
|
+
bracketed = true;
|
|
8220
|
+
} else {
|
|
8221
|
+
const match = /^([A-Za-z0-9._-]+)(?::(\d+))?$/.exec(raw);
|
|
8222
|
+
if (!match) return null;
|
|
8223
|
+
[, hostname, port = ""] = match;
|
|
8197
8224
|
}
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8225
|
+
if (port && Number(port) > 65535) return null;
|
|
8226
|
+
hostname = hostname.toLowerCase();
|
|
8227
|
+
const authority = `${bracketed ? `[${hostname}]` : hostname}${port ? `:${port}` : ""}`;
|
|
8228
|
+
try {
|
|
8229
|
+
const parsed = new URL(`http://${authority}`);
|
|
8230
|
+
if (!parsed.origin || parsed.origin === "null") return null;
|
|
8231
|
+
} catch {
|
|
8232
|
+
return null;
|
|
8233
|
+
}
|
|
8234
|
+
return { hostname, port, authority };
|
|
8202
8235
|
}
|
|
8203
8236
|
function isAllowedHostHeader(hostHeader, allowedHostnames) {
|
|
8204
8237
|
if (hostHeader === void 0 || hostHeader === null) return false;
|
|
8205
|
-
const
|
|
8206
|
-
|
|
8207
|
-
const hostname = hostnameFromHostHeader(raw);
|
|
8208
|
-
if (hostname === null) return false;
|
|
8209
|
-
return allowedHostnames.has(hostname);
|
|
8238
|
+
const authority = parseHostAuthority(hostHeader);
|
|
8239
|
+
return authority !== null && allowedHostnames.has(authority.hostname);
|
|
8210
8240
|
}
|
|
8211
8241
|
function isAllowedRequestHost({ host, forwardedHost }, allowedHostnames) {
|
|
8212
8242
|
if (!isAllowedHostHeader(host, allowedHostnames)) return false;
|
|
@@ -8214,8 +8244,22 @@ function isAllowedRequestHost({ host, forwardedHost }, allowedHostnames) {
|
|
|
8214
8244
|
if (forwarded === "") return true;
|
|
8215
8245
|
return isAllowedHostHeader(forwarded.split(",").pop(), allowedHostnames);
|
|
8216
8246
|
}
|
|
8217
|
-
function isSameOriginRequest(req) {
|
|
8218
|
-
const
|
|
8247
|
+
function isSameOriginRequest(req, allowedHostnames, allowAnyHostname = false) {
|
|
8248
|
+
const host = parseHostAuthority(req.headers.host);
|
|
8249
|
+
if (!host) return false;
|
|
8250
|
+
let protocol = req.protocol;
|
|
8251
|
+
let authority = host;
|
|
8252
|
+
const forwardedHost = String(req.get("x-forwarded-host") || "").split(",").pop().trim();
|
|
8253
|
+
if (forwardedHost) {
|
|
8254
|
+
const forwardedAuthority = parseHostAuthority(forwardedHost);
|
|
8255
|
+
if (!forwardedAuthority || !allowAnyHostname && (!allowedHostnames.has(host.hostname) || !allowedHostnames.has(forwardedAuthority.hostname)))
|
|
8256
|
+
return false;
|
|
8257
|
+
protocol = String(req.get("x-forwarded-proto") || req.protocol).split(",").pop().trim().toLowerCase();
|
|
8258
|
+
if (protocol !== "http" && protocol !== "https") return false;
|
|
8259
|
+
authority = forwardedAuthority;
|
|
8260
|
+
}
|
|
8261
|
+
const expectedOrigin = normalizeOrigin(`${protocol}://${authority.authority}`);
|
|
8262
|
+
if (!expectedOrigin) return false;
|
|
8219
8263
|
const origin = req.get("origin");
|
|
8220
8264
|
if (origin) {
|
|
8221
8265
|
return normalizeOrigin(origin) === expectedOrigin;
|
|
@@ -8525,7 +8569,6 @@ function createSdkJs(key, artifactRevision = 0, artifactLoadToken = "") {
|
|
|
8525
8569
|
const loadToken = String(artifactLoadToken || "").slice(0, 200);
|
|
8526
8570
|
return `(() => {
|
|
8527
8571
|
const key=${JSON.stringify(key)};
|
|
8528
|
-
void key;
|
|
8529
8572
|
const artifactRevision=${revision};
|
|
8530
8573
|
const artifactLoadToken=${JSON.stringify(loadToken)};
|
|
8531
8574
|
const deriveQueueKey=${deriveLavishQueueKey.toString()};
|
|
@@ -8539,7 +8582,7 @@ const findStableLayoutFindings=${findStableLayoutFindings.toString()};
|
|
|
8539
8582
|
const isNearTotalOcclusion=${isNearTotalOcclusion.toString()};
|
|
8540
8583
|
${mermaidHelperDecls}
|
|
8541
8584
|
const mermaidHelpers={ ${mermaidHelperKeys} };
|
|
8542
|
-
(${createArtifactSdk.toString()})(deriveQueueKey, isNativeInteractiveControl, mermaidHelpers, artifactRevision, artifactLoadToken);
|
|
8585
|
+
(${createArtifactSdk.toString()})(deriveQueueKey, isNativeInteractiveControl, mermaidHelpers, artifactRevision, artifactLoadToken, key);
|
|
8543
8586
|
})();`;
|
|
8544
8587
|
}
|
|
8545
8588
|
function escapeHtml(value) {
|
|
@@ -8735,7 +8778,7 @@ var POLL_WAKE_PATH_RULES = Object.freeze([
|
|
|
8735
8778
|
]);
|
|
8736
8779
|
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.";
|
|
8737
8780
|
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.";
|
|
8738
|
-
var VERSION = "0.1.
|
|
8781
|
+
var VERSION = "0.1.50";
|
|
8739
8782
|
function detectInvokingAgent(env = process.env) {
|
|
8740
8783
|
return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
|
|
8741
8784
|
}
|
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.50",
|
|
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",
|