lavish-axi 0.1.34 → 0.1.35
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 +9 -4
- package/dist/chrome-client.js +21 -6
- package/dist/cli.mjs +80 -26
- package/package.json +1 -1
- package/skills/lavish/SKILL.md +4 -3
package/README.md
CHANGED
|
@@ -155,10 +155,14 @@ pnpm link
|
|
|
155
155
|
- **Feedback controls** - Native controls (radios, checkboxes, inputs, selects, buttons, labels, disclosure summaries, contenteditable) are interactive automatically, so they do not need `data-lavish-action`.
|
|
156
156
|
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()`.
|
|
157
157
|
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.
|
|
158
|
-
The browser chrome keeps editing actions in the overflow menu (copy path, reload artifact, copy DOM snapshot, export standalone HTML, publish link, end session) and can submit queued prompts with **Send & end session**, which
|
|
158
|
+
The browser chrome keeps editing actions in the overflow menu (copy path, reload artifact, copy DOM snapshot, export standalone HTML, publish link, end session) and can submit queued prompts with **Send & end session**, which sends the prompts and user-ended attribution together.
|
|
159
159
|
- **Keyboard shortcuts** - In the chrome composer, Enter sends queued prompts and Shift+Enter inserts a newline.
|
|
160
160
|
In the annotation card, Enter queues the annotation, Shift+Enter inserts a newline, and Ctrl+Enter (Cmd+Enter on macOS) queues it and sends all queued prompts immediately.
|
|
161
161
|
- **Agent presence** - The browser shows when no agent is listening, keeps queued feedback and fresh layout warnings for the next successful `lavish-axi poll` send even across reloads, and only blocks human sends while the agent is working on delivered feedback. The no-timeout poll writes an immediate stderr banner and periodic stderr heartbeats while stdout stays reserved for the final response; if the poll is interrupted or times out, re-run it because queued feedback is never lost.
|
|
162
|
+
- **Session end etiquette** - Lavish tracks who ended a session: a human clicking **End session** (or **Send & end session**) in the browser is a user-initiated end, while `lavish-axi end <html-file>` is agent-initiated.
|
|
163
|
+
A plain `lavish-axi <html-file>` after a user-initiated end refuses to reopen the browser and returns guidance instead; pass `--reopen` only when the user asks for further review or something important needs their visual attention.
|
|
164
|
+
Agent-initiated ends keep reopening normally, same as before.
|
|
165
|
+
`lavish-axi poll`'s `ended` response and the `feedback` response for the final batch before an end both carry `next_step` guidance telling the agent to stop polling and deliver remaining updates in chat instead of reopening.
|
|
162
166
|
- **Precise targets** - Text annotations include selected text plus range anchors, so agents are not limited to whole-element selectors.
|
|
163
167
|
- **Server cleanup** - The detached server stops after the last session ends when nothing is connected, or after `LAVISH_AXI_IDLE_TIMEOUT_MS` (default 30 minutes) with no browser or poll connections.
|
|
164
168
|
Set `LAVISH_AXI_IDLE_TIMEOUT_MS=0` or `off` to disable idle self-shutdown.
|
|
@@ -171,9 +175,9 @@ pnpm link
|
|
|
171
175
|
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
172
176
|
| `lavish-axi` | Show current sessions and usage guidance. |
|
|
173
177
|
| `lavish-axi update` | Check for or apply the latest npm release through the AXI SDK self-updater. |
|
|
174
|
-
| `lavish-axi <html-file>` | Open or resume a Lavish Editor session, with the open-time layout gate enabled by default.
|
|
175
|
-
| `lavish-axi poll <html-file>` | Long-poll until the user sends feedback, ends the session, or the browser reports fresh `layout_warnings`; leave no-timeout polls running, or re-run them if interrupted.
|
|
176
|
-
| `lavish-axi end <html-file>` | End a session.
|
|
178
|
+
| `lavish-axi <html-file>` | Open or resume a Lavish Editor session, with the open-time layout gate enabled by default. Refuses to reopen a session the user explicitly ended from the browser unless `--reopen` is passed. |
|
|
179
|
+
| `lavish-axi poll <html-file>` | Long-poll until the user sends feedback, ends the session, or the browser reports fresh `layout_warnings`; leave no-timeout polls running, or re-run them if interrupted. On `status: ended`, stop polling and do not reopen uninvited. |
|
|
180
|
+
| `lavish-axi end <html-file>` | End a session as the agent; unlike a user-initiated end from the browser, this still allows a plain reopen later. |
|
|
177
181
|
| `lavish-axi export <html-file>` | Write a portable copy of the artifact: one HTML file with its local assets inlined, so it opens with no server and no sibling files. Remote CDN/font references are left as links. |
|
|
178
182
|
| `lavish-axi share <html-file>` | Publish the artifact (local assets inlined) to [ht-ml.app](https://ht-ml.app), a third-party host not part of Lavish, and print a visitable URL plus a secret update key; shares are public by default, and `--password` makes viewers enter the password before viewing. |
|
|
179
183
|
| `lavish-axi stop` | Shut down the background server. |
|
|
@@ -192,6 +196,7 @@ For flows, architecture, state, or sequence diagrams, open the diagram playbook
|
|
|
192
196
|
| ------------------------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
193
197
|
| `lavish-axi <html-file>` | `--no-open` | Ensure the server/session exists without opening another browser window. |
|
|
194
198
|
| `lavish-axi <html-file>` | `--no-gate` | Skip the open-time layout curtain for this browser open. |
|
|
199
|
+
| `lavish-axi <html-file>` | `--reopen` | Reopen a session the user explicitly ended from the browser; without it, a plain open refuses and explains why instead of reopening uninvited. |
|
|
195
200
|
| `lavish-axi update` | `--check` | Report current vs latest npm version without installing an update. |
|
|
196
201
|
| `lavish-axi export` | `--out <path>` | Write the export to a specific path instead of `<name>.export.html` next to the source. |
|
|
197
202
|
| `lavish-axi share` | `--password <pw>` | Make the third-party ht-ml.app page private; viewers must supply the password. |
|
package/dist/chrome-client.js
CHANGED
|
@@ -328,21 +328,26 @@ async function submitQueued() {
|
|
|
328
328
|
submitQueuedAgain = false;
|
|
329
329
|
if (!succeeded) {
|
|
330
330
|
endAfterSubmit = false;
|
|
331
|
-
} else if (
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
endAfterSubmit
|
|
335
|
-
|
|
331
|
+
} else if (!ended && shouldSubmitAgain) {
|
|
332
|
+
if (queued.length) {
|
|
333
|
+
submitQueued();
|
|
334
|
+
} else if (endAfterSubmit) {
|
|
335
|
+
endAfterSubmit = false;
|
|
336
|
+
endSession();
|
|
337
|
+
}
|
|
336
338
|
}
|
|
337
339
|
}
|
|
338
340
|
}
|
|
339
341
|
|
|
340
342
|
async function submitQueuedOnce() {
|
|
341
343
|
const prompts = queued.slice();
|
|
344
|
+
const shouldEndSession = endAfterSubmit;
|
|
345
|
+
const body = { prompts: prompts.map(stripInternalPromptFields), domSnapshot: pendingSnapshot };
|
|
346
|
+
if (shouldEndSession) body.endSession = true;
|
|
342
347
|
const response = await fetch("/api/" + key + "/prompts", {
|
|
343
348
|
method: "POST",
|
|
344
349
|
headers: { "content-type": "application/json" },
|
|
345
|
-
body: JSON.stringify(
|
|
350
|
+
body: JSON.stringify(body),
|
|
346
351
|
});
|
|
347
352
|
if (!response.ok) throw new Error("failed to submit queued prompts");
|
|
348
353
|
for (const prompt of prompts) {
|
|
@@ -351,6 +356,11 @@ async function submitQueuedOnce() {
|
|
|
351
356
|
}
|
|
352
357
|
persistQueuedPrompts();
|
|
353
358
|
render();
|
|
359
|
+
if (shouldEndSession) {
|
|
360
|
+
endAfterSubmit = false;
|
|
361
|
+
markSessionEnded();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
354
364
|
if (agentPresence === "listening") setAgentPresence("working");
|
|
355
365
|
}
|
|
356
366
|
|
|
@@ -474,6 +484,11 @@ async function endSession() {
|
|
|
474
484
|
if (ended) return;
|
|
475
485
|
const response = await fetch("/api/" + key + "/end", { method: "POST" });
|
|
476
486
|
if (!response.ok) throw new Error("failed to end session");
|
|
487
|
+
markSessionEnded();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function markSessionEnded() {
|
|
491
|
+
if (ended) return;
|
|
477
492
|
ended = true;
|
|
478
493
|
closeMenus();
|
|
479
494
|
annotationSwitch.disabled = true;
|
package/dist/cli.mjs
CHANGED
|
@@ -5027,13 +5027,16 @@ var SessionStore = class {
|
|
|
5027
5027
|
return null;
|
|
5028
5028
|
}
|
|
5029
5029
|
const prompts = Array.isArray(payload.prompts) ? payload.prompts : [];
|
|
5030
|
+
const shouldEndSession = Boolean(payload.endSession || payload.end_session);
|
|
5031
|
+
const alreadyEnded = session.status === "ended";
|
|
5030
5032
|
const normalizedPrompts = prompts.map(normalizePrompt);
|
|
5031
5033
|
const userMessages = normalizedPrompts.filter((prompt) => prompt.tag === "message" && prompt.prompt).map((prompt) => ({ role: "user", text: prompt.prompt, at: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
5032
5034
|
session.prompts = [...session.prompts || [], ...normalizedPrompts];
|
|
5033
5035
|
session.chat = [...session.chat || [], ...userMessages];
|
|
5034
5036
|
session.pending_prompts = session.prompts.length;
|
|
5035
5037
|
session.dom_snapshot = String(payload.domSnapshot || payload.dom_snapshot || "");
|
|
5036
|
-
session.status = "feedback";
|
|
5038
|
+
session.status = shouldEndSession || alreadyEnded ? "ended" : "feedback";
|
|
5039
|
+
if (shouldEndSession) session.ended_by = "user";
|
|
5037
5040
|
session.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
5038
5041
|
await this.writeState(state);
|
|
5039
5042
|
return session;
|
|
@@ -5078,14 +5081,18 @@ var SessionStore = class {
|
|
|
5078
5081
|
}
|
|
5079
5082
|
const prompts = session.prompts || [];
|
|
5080
5083
|
const layoutWarnings = session.layout_warnings || [];
|
|
5084
|
+
const alreadyEnded = session.status === "ended";
|
|
5081
5085
|
if (prompts.length === 0 && layoutWarnings.length === 0) {
|
|
5082
|
-
return
|
|
5086
|
+
return alreadyEnded ? { status: "ended", ended_by: session.ended_by } : { status: "waiting" };
|
|
5083
5087
|
}
|
|
5084
5088
|
const result = {
|
|
5085
5089
|
status: "feedback",
|
|
5086
5090
|
dom_snapshot: session.dom_snapshot || "",
|
|
5087
5091
|
prompts,
|
|
5088
|
-
...layoutWarnings.length > 0 ? { layout_warnings: layoutWarnings } : {}
|
|
5092
|
+
...layoutWarnings.length > 0 ? { layout_warnings: layoutWarnings } : {},
|
|
5093
|
+
// This is the final delivery before the session shows as ended - flag it so the agent
|
|
5094
|
+
// knows not to expect (or force) a reopened browser afterward.
|
|
5095
|
+
...alreadyEnded ? { session_ended: true, ended_by: session.ended_by } : {}
|
|
5089
5096
|
};
|
|
5090
5097
|
session.prompts = [];
|
|
5091
5098
|
session.layout_warnings = [];
|
|
@@ -5096,20 +5103,26 @@ var SessionStore = class {
|
|
|
5096
5103
|
for (const warning of layoutWarnings) deliveredKeys.add(layoutWarningKey(warning));
|
|
5097
5104
|
session.delivered_layout_warning_keys = [...deliveredKeys].slice(-200);
|
|
5098
5105
|
}
|
|
5099
|
-
if (
|
|
5106
|
+
if (!alreadyEnded) {
|
|
5100
5107
|
session.status = "open";
|
|
5101
5108
|
}
|
|
5102
5109
|
session.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
5103
5110
|
await this.writeState(state);
|
|
5104
5111
|
return result;
|
|
5105
5112
|
}
|
|
5106
|
-
|
|
5113
|
+
// `endedBy` distinguishes a human ending review from the browser chrome ("user") from an
|
|
5114
|
+
// agent explicitly closing the loop via `lavish-axi end` ("agent"). Only a user-initiated end
|
|
5115
|
+
// blocks a plain reopen - see `SessionStore` callers in server.js.
|
|
5116
|
+
async endSession(key, endedBy = "agent") {
|
|
5107
5117
|
const state = await this.readState();
|
|
5108
5118
|
const session = state.sessions[key];
|
|
5109
5119
|
if (!session) {
|
|
5110
5120
|
return null;
|
|
5111
5121
|
}
|
|
5122
|
+
const existingEndedBy = session.status === "ended" ? session.ended_by : void 0;
|
|
5123
|
+
const nextEndedBy = endedBy === "user" || existingEndedBy === "user" ? "user" : "agent";
|
|
5112
5124
|
session.status = "ended";
|
|
5125
|
+
session.ended_by = nextEndedBy;
|
|
5113
5126
|
session.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
5114
5127
|
await this.writeState(state);
|
|
5115
5128
|
return session;
|
|
@@ -5256,9 +5269,15 @@ async function serve({
|
|
|
5256
5269
|
try {
|
|
5257
5270
|
const file = await canonicalFile(req.body.file);
|
|
5258
5271
|
const key = sessionKey(file);
|
|
5272
|
+
const reopen = Boolean(req.body.reopen);
|
|
5273
|
+
const existing = await store.findByKey(key);
|
|
5274
|
+
if (existing?.status === "ended" && existing.ended_by === "user" && !reopen) {
|
|
5275
|
+
logEvent?.(`session open blocked (user-ended) key=${key} file=${file}`);
|
|
5276
|
+
res.json({ key, file, url: existing.url, status: "user-ended" });
|
|
5277
|
+
return;
|
|
5278
|
+
}
|
|
5259
5279
|
const sessionUrl = `http://${hostForUrl(linkHostName)}:${publicPort}/session/${key}`;
|
|
5260
5280
|
const url = shouldDisableLayoutGateOpen(req.body || {}) ? appendNoGateParam(sessionUrl) : sessionUrl;
|
|
5261
|
-
const existing = await store.findByKey(key);
|
|
5262
5281
|
const session = await store.upsertSession(file, sessionUrl);
|
|
5263
5282
|
if (existing?.status === "ended") {
|
|
5264
5283
|
clearFeedbackDelivery(key, activePolls, deliveredFeedback, events);
|
|
@@ -5344,13 +5363,16 @@ async function serve({
|
|
|
5344
5363
|
});
|
|
5345
5364
|
app.post("/api/:key/prompts", async (req, res, next) => {
|
|
5346
5365
|
try {
|
|
5366
|
+
const shouldEndSession = Boolean(req.body?.endSession || req.body?.end_session);
|
|
5347
5367
|
const session = await store.queuePrompts(req.params.key, req.body || {});
|
|
5348
5368
|
if (!session) {
|
|
5349
5369
|
res.status(404).json({ error: "session not found" });
|
|
5350
5370
|
return;
|
|
5351
5371
|
}
|
|
5352
|
-
|
|
5372
|
+
if (shouldEndSession) clearFeedbackDelivery(req.params.key, activePolls, deliveredFeedback, events);
|
|
5373
|
+
events.emit(shouldEndSession ? "ended" : "feedback", req.params.key);
|
|
5353
5374
|
res.json({ status: "queued", pending_prompts: session.pending_prompts });
|
|
5375
|
+
if (shouldEndSession) await shutdownIfNoLiveSessions();
|
|
5354
5376
|
} catch (error) {
|
|
5355
5377
|
next(error);
|
|
5356
5378
|
}
|
|
@@ -5372,7 +5394,7 @@ async function serve({
|
|
|
5372
5394
|
});
|
|
5373
5395
|
app.post("/api/:key/end", async (req, res, next) => {
|
|
5374
5396
|
try {
|
|
5375
|
-
await store.endSession(req.params.key);
|
|
5397
|
+
await store.endSession(req.params.key, "user");
|
|
5376
5398
|
clearFeedbackDelivery(req.params.key, activePolls, deliveredFeedback, events);
|
|
5377
5399
|
events.emit("ended", req.params.key);
|
|
5378
5400
|
res.json({ status: "ended" });
|
|
@@ -5459,7 +5481,7 @@ async function serve({
|
|
|
5459
5481
|
try {
|
|
5460
5482
|
const file = await canonicalFile(req.body.file);
|
|
5461
5483
|
const key = sessionKey(file);
|
|
5462
|
-
await store.endSession(key);
|
|
5484
|
+
await store.endSession(key, "agent");
|
|
5463
5485
|
clearFeedbackDelivery(key, activePolls, deliveredFeedback, events);
|
|
5464
5486
|
events.emit("ended", key);
|
|
5465
5487
|
res.json({ status: "ended" });
|
|
@@ -6125,7 +6147,7 @@ function normalizePagePath(path6) {
|
|
|
6125
6147
|
var COMMANDS = /* @__PURE__ */ new Set(["open", "poll", "end", "stop", "server", "playbook", "design", "setup", "export", "share"]);
|
|
6126
6148
|
var RESERVED = new Set(RESERVED_COMMANDS);
|
|
6127
6149
|
var DESCRIPTION = "Lavish Editor helps agents turn rich HTML artifacts into collaborative human review surfaces. Whenever you are about to give user a complex response that will be easier to understand via a rich / interactive page, consider using Lavish Editor. First generate an interactive HTML artifact according to user request, then run `lavish-axi <html-file>` so the user can visually review it, annotate elements or selected text, queue prompts, and send feedback back through `lavish-axi poll`.";
|
|
6128
|
-
var VERSION = "0.1.
|
|
6150
|
+
var VERSION = "0.1.35";
|
|
6129
6151
|
async function run(argv) {
|
|
6130
6152
|
await ensureStateDir();
|
|
6131
6153
|
const normalizedArgv = normalizeArgv(argv);
|
|
@@ -6217,11 +6239,11 @@ function createHomeOutput({ bin, sessions, includeSessions = true }) {
|
|
|
6217
6239
|
],
|
|
6218
6240
|
playbooks: listPlaybooks(),
|
|
6219
6241
|
help: [
|
|
6220
|
-
"Run `lavish-axi <html-file>` to open or resume a Lavish Editor session",
|
|
6242
|
+
"Run `lavish-axi <html-file>` to open or resume a Lavish Editor session. If the user explicitly ended the session from the browser, this refuses to reopen it and explains why instead of reopening uninvited - pass `--reopen` only when the user asks for further review or something important needs their visual attention",
|
|
6221
6243
|
"Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`",
|
|
6222
6244
|
"Lavish serves the html file through a local express.js server. If your html needs to reference other filesystem assets such as images, CSS, fonts, and local scripts, copy them into the same directory as the HTML file, then reference them with relative paths from that directory. Never prepend `/` to those asset paths - root paths won't work",
|
|
6223
|
-
"Run `lavish-axi poll <html-file>` to wait for user feedback or browser-reported layout_warnings. It long-polls and stays silent until the user sends feedback, ends the session, or the real browser reports fresh layout_warnings, so leave it running - never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; if the poll says every current warning is persistent or low-severity, proceed with a note instead of looping. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost",
|
|
6224
|
-
"Run `lavish-axi end <html-file>` to end a session",
|
|
6245
|
+
"Run `lavish-axi poll <html-file>` to wait for user feedback or browser-reported layout_warnings. It long-polls and stays silent until the user sends feedback, ends the session, or the real browser reports fresh layout_warnings, so leave it running - never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; if the poll says every current warning is persistent or low-severity, proceed with a note instead of looping. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost. When it reports the session ended, stop polling and do not reopen it uninvited - deliver remaining updates in this conversation instead",
|
|
6246
|
+
"Run `lavish-axi end <html-file>` to end a session as the agent - ending it this way still allows a plain reopen later. When the user ends it from the browser instead, a later `lavish-axi <html-file>` refuses to reopen it without `--reopen`",
|
|
6225
6247
|
"Run `lavish-axi export <html-file> [--out <path>]` to write a portable copy of the artifact - one HTML file with its LOCAL assets inlined - so it opens with no Lavish server and no sibling files. Remote CDN/font references are left as links, so it needs network to render those. Users can also export from the browser chrome's overflow menu",
|
|
6226
6248
|
"Run `lavish-axi share <html-file> [--password <pw>] [--token <t>]` to publish the artifact on ht-ml.app (https://ht-ml.app), a third-party hosting service not part of Lavish, and get back a visitable URL. Shares are PUBLIC by default, so anyone with the link can open them. Pass --password to publish a PRIVATE password-protected page; viewers must supply the password to view. Local assets are inlined; remote refs load over the network. It returns the url plus a secret update_key for managing the page later. Use --token or LAVISH_AXI_HTML_APP_TOKEN only when you have an optional bearer token; it is never required. Users can also publish from the browser chrome's overflow menu",
|
|
6227
6249
|
"Run `lavish-axi stop` to shut down the background server (it also self-stops when idle or after the last session ends with nothing connected)",
|
|
@@ -6250,7 +6272,13 @@ function createPlaybookOutput(args) {
|
|
|
6250
6272
|
function createOpenOutput({ file, url, status }) {
|
|
6251
6273
|
return {
|
|
6252
6274
|
session: { file, url, status },
|
|
6253
|
-
next_step: `Do not respond to the user just yet. Now you must run \`lavish-axi poll ${file}\`. This command long-polls until the user sends feedback, ends the session, or the real browser reports layout_warnings from the in-iframe layout audit, and it stays silent the whole time - that is normal, never kill it. If layout_warnings arrive, follow the poll response's next_step: fix and re-check fresh error-severity overflow or clipped-text findings before involving the human, but persistent or low-severity warnings may be surfaced with a note when the cause is not obvious. Do not pass --timeout-ms during normal agent use. If your harness limits how long a foreground command may run, run the poll as a background task and wait for it to finish; if the poll still gets killed or times out, just re-run it - queued feedback is never lost. After applying feedback, run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms to show your response in Lavish Editor and wait for more feedback
|
|
6275
|
+
next_step: `Do not respond to the user just yet. Now you must run \`lavish-axi poll ${file}\`. This command long-polls until the user sends feedback, ends the session, or the real browser reports layout_warnings from the in-iframe layout audit, and it stays silent the whole time - that is normal, never kill it. If layout_warnings arrive, follow the poll response's next_step: fix and re-check fresh error-severity overflow or clipped-text findings before involving the human, but persistent or low-severity warnings may be surfaced with a note when the cause is not obvious. Do not pass --timeout-ms during normal agent use. If your harness limits how long a foreground command may run, run the poll as a background task and wait for it to finish; if the poll still gets killed or times out, just re-run it - queued feedback is never lost. After applying feedback, run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms to show your response in Lavish Editor and wait for more feedback. If the user ends the session, stop polling and do not reopen it by re-running \`lavish-axi ${file}\` unless the user asks for further review or something genuinely important needs their visual attention - deliver routine updates directly in this conversation instead. When reopening is warranted, run \`lavish-axi ${file} --reopen\`.`
|
|
6276
|
+
};
|
|
6277
|
+
}
|
|
6278
|
+
function createUserEndedOpenOutput({ file, url }) {
|
|
6279
|
+
return {
|
|
6280
|
+
session: { file, url, status: "user-ended" },
|
|
6281
|
+
next_step: `The user explicitly ended this Lavish Editor session from the browser, so \`lavish-axi ${file}\` did not reopen it. Do not reopen unless the user asks for further review or something genuinely important needs their visual attention - deliver routine updates directly in this conversation instead. When reopening is warranted, run \`lavish-axi ${file} --reopen\`.`
|
|
6254
6282
|
};
|
|
6255
6283
|
}
|
|
6256
6284
|
async function openCommand(args) {
|
|
@@ -6261,8 +6289,12 @@ async function openCommand(args) {
|
|
|
6261
6289
|
await assertHtmlFile(file);
|
|
6262
6290
|
const absolute = await canonicalFile(file);
|
|
6263
6291
|
const noGate = args.includes("--no-gate");
|
|
6292
|
+
const reopen = args.includes("--reopen");
|
|
6264
6293
|
const baseUrl = await ensureServer({ forceRestart: shouldForceRestartForLocalBuild(process.argv[1] || "") });
|
|
6265
|
-
const response = await postJson(`${baseUrl}/api/sessions`, { file: absolute, noGate });
|
|
6294
|
+
const response = await postJson(`${baseUrl}/api/sessions`, { file: absolute, noGate, reopen });
|
|
6295
|
+
if (response.status === "user-ended") {
|
|
6296
|
+
return createUserEndedOpenOutput({ file: absolute, url: response.url });
|
|
6297
|
+
}
|
|
6266
6298
|
if (shouldOpenBrowser(args, process.env)) {
|
|
6267
6299
|
try {
|
|
6268
6300
|
const open = (await import("open")).default;
|
|
@@ -6350,24 +6382,40 @@ function createPollOutput({ file, response }) {
|
|
|
6350
6382
|
}
|
|
6351
6383
|
if (response.status === "feedback") {
|
|
6352
6384
|
const layoutWarnings = Array.isArray(response.layout_warnings) ? response.layout_warnings : [];
|
|
6385
|
+
const sessionEnded = Boolean(response.session_ended);
|
|
6386
|
+
const endedBy = typeof response.ended_by === "string" ? response.ended_by : void 0;
|
|
6353
6387
|
return {
|
|
6354
|
-
session: {
|
|
6388
|
+
session: {
|
|
6389
|
+
file,
|
|
6390
|
+
status: "feedback",
|
|
6391
|
+
...sessionEnded ? { session_ended: true, ...endedBy ? { ended_by: endedBy } : {} } : {}
|
|
6392
|
+
},
|
|
6355
6393
|
dom_snapshot: response.dom_snapshot || "",
|
|
6356
6394
|
prompts: response.prompts || [],
|
|
6357
6395
|
...layoutWarnings.length > 0 ? { layout_warnings: layoutWarnings } : {},
|
|
6358
|
-
next_step: createFeedbackNextStep(file, layoutWarnings)
|
|
6396
|
+
next_step: createFeedbackNextStep(file, layoutWarnings, sessionEnded, endedBy)
|
|
6359
6397
|
};
|
|
6360
6398
|
}
|
|
6361
6399
|
if (response.status === "ended") {
|
|
6362
|
-
return {
|
|
6400
|
+
return {
|
|
6401
|
+
session: { file, status: "ended", ...response.ended_by ? { ended_by: response.ended_by } : {} },
|
|
6402
|
+
next_step: createEndedNextStep(file, response.ended_by)
|
|
6403
|
+
};
|
|
6363
6404
|
}
|
|
6364
6405
|
return {
|
|
6365
6406
|
session: { file, status: response.status || "waiting" },
|
|
6366
6407
|
next_step: `No user feedback arrived before the optional timeout. Run \`lavish-axi poll ${file}\` without --timeout-ms to wait indefinitely - queued feedback is never lost, so re-running the poll is always safe.`
|
|
6367
6408
|
};
|
|
6368
6409
|
}
|
|
6369
|
-
function createFeedbackNextStep(file, layoutWarnings) {
|
|
6410
|
+
function createFeedbackNextStep(file, layoutWarnings, sessionEnded, endedBy) {
|
|
6370
6411
|
const count = layoutWarnings.length;
|
|
6412
|
+
if (sessionEnded) {
|
|
6413
|
+
const layoutNote = count > 0 ? `${count} layout warning${count === 1 ? "" : "s"} arrived alongside this final feedback. ` : "";
|
|
6414
|
+
if (endedBy === "user") {
|
|
6415
|
+
return `${layoutNote}This was the last feedback before the user ended the session. Stop polling ${file} and do not reopen it - deliver any remaining updates directly in this conversation instead. Only run \`lavish-axi ${file} --reopen\` if the user explicitly asks for further review or something genuinely important needs their visual attention.`;
|
|
6416
|
+
}
|
|
6417
|
+
return `${layoutNote}This was the last feedback before the Lavish Editor session ended. Stop polling ${file}. Deliver any remaining updates directly in this conversation, or run \`lavish-axi ${file}\` to open a fresh session if the user needs further visual review.`;
|
|
6418
|
+
}
|
|
6371
6419
|
const layoutPrefix = count > 0 ? layoutWarningsPrefix(file, layoutWarnings) : `Apply the requested changes to ${file}. `;
|
|
6372
6420
|
return `${layoutPrefix}Do not respond to the user just yet. Now you must run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms unless the user ended the session. The poll waits silently until the user sends more feedback, ends the session, or reports fresh layout_warnings - never kill it. If your harness limits how long a foreground command may run, run the poll as a background task; if it still gets killed or times out, just re-run it - queued feedback is never lost.`;
|
|
6373
6421
|
}
|
|
@@ -6386,7 +6434,13 @@ function layoutWarningsPrefix(file, layoutWarnings) {
|
|
|
6386
6434
|
if (allRepeatOrLowSeverity) {
|
|
6387
6435
|
return `${count} layout warning${plural} detected, with no fresh error-severity findings - fix any obvious low-severity issue in ${file}, otherwise it is fine to proceed to the human with a note instead of iterating further. `;
|
|
6388
6436
|
}
|
|
6389
|
-
return `${count} layout warning${plural} detected - fix horizontal overflow or clipped text in ${file}, then
|
|
6437
|
+
return `${count} layout warning${plural} detected - fix horizontal overflow or clipped text in ${file}, then re-check in the browser before involving the human. Lavish live-reloads the artifact automatically after you save, so you do not need to re-run \`lavish-axi ${file}\` for this. `;
|
|
6438
|
+
}
|
|
6439
|
+
function createEndedNextStep(file, endedBy) {
|
|
6440
|
+
if (endedBy === "user") {
|
|
6441
|
+
return `The user ended this Lavish Editor session. Stop polling ${file} - do not run \`lavish-axi ${file}\` to reopen it. Deliver any remaining updates directly in this conversation instead. Only reopen with \`lavish-axi ${file} --reopen\` if the user explicitly asks for further review or something genuinely important needs their visual attention.`;
|
|
6442
|
+
}
|
|
6443
|
+
return `This Lavish Editor session for ${file} has ended. Stop polling. Deliver any remaining updates directly in this conversation, or run \`lavish-axi ${file}\` to open a fresh session if the user needs further visual review.`;
|
|
6390
6444
|
}
|
|
6391
6445
|
async function endCommand(args) {
|
|
6392
6446
|
const file = firstPositionalArg(args);
|
|
@@ -6910,7 +6964,7 @@ var TOP_LEVEL_HELP = `lavish-axi - Lavish Editor AXI
|
|
|
6910
6964
|
|
|
6911
6965
|
Usage:
|
|
6912
6966
|
lavish-axi
|
|
6913
|
-
lavish-axi <html-file> [--no-open] [--no-gate]
|
|
6967
|
+
lavish-axi <html-file> [--no-open] [--no-gate] [--reopen]
|
|
6914
6968
|
lavish-axi poll <html-file> [--agent-reply "..."]
|
|
6915
6969
|
lavish-axi end <html-file>
|
|
6916
6970
|
lavish-axi export <html-file> [--out <path>]
|
|
@@ -6922,21 +6976,21 @@ Usage:
|
|
|
6922
6976
|
|
|
6923
6977
|
${DESIGN_SYSTEM_HINT}
|
|
6924
6978
|
|
|
6925
|
-
Note: poll long-polls indefinitely by default until the user sends feedback, ends the session, or the browser reports fresh layout_warnings, staying silent while it waits - never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; persistent or low-severity findings may be surfaced with a note when the cause is not obvious. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost.
|
|
6979
|
+
Note: poll long-polls indefinitely by default until the user sends feedback, ends the session, or the browser reports fresh layout_warnings, staying silent while it waits - never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; persistent or low-severity findings may be surfaced with a note when the cause is not obvious. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost. When the user ends a session from the browser, stop polling and do not reopen it uninvited - pass --reopen to <html-file> only when the user asks for further review or something important needs their visual attention.
|
|
6926
6980
|
|
|
6927
6981
|
`;
|
|
6928
6982
|
var COMMAND_HELP = {
|
|
6929
|
-
open: `Usage: lavish-axi <html-file> [--no-open] [--no-gate]
|
|
6983
|
+
open: `Usage: lavish-axi <html-file> [--no-open] [--no-gate] [--reopen]
|
|
6930
6984
|
|
|
6931
|
-
Open or resume a Lavish Editor review session for an HTML artifact. Use --no-open when you need to ensure the server/session exists without opening another browser window. Use --no-gate to skip the open-time layout curtain for this browser open.
|
|
6985
|
+
Open or resume a Lavish Editor review session for an HTML artifact. Use --no-open when you need to ensure the server/session exists without opening another browser window. Use --no-gate to skip the open-time layout curtain for this browser open. If the user explicitly ended the session from the browser, this refuses to reopen it and returns guidance instead - pass --reopen to force it open when the user asks for further review or something important needs their visual attention. Sessions ended by the agent (\`lavish-axi end\`) reopen normally without the flag.
|
|
6932
6986
|
`,
|
|
6933
6987
|
poll: `Usage: lavish-axi poll <html-file> [--agent-reply "..."]
|
|
6934
6988
|
|
|
6935
|
-
This command long-polls indefinitely for queued user prompts and browser-reported layout_warnings, then returns them to the agent. It stays silent while it waits - that is normal, never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; persistent or low-severity findings may be surfaced with a note when the cause is not obvious. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. If your harness limits how long a foreground command may run, run the poll as a background task and wait for it to finish; if it still gets killed or times out, just re-run it - queued feedback is never lost. Use --agent-reply after applying prior feedback to display your response in Lavish Editor before waiting again.
|
|
6989
|
+
This command long-polls indefinitely for queued user prompts and browser-reported layout_warnings, then returns them to the agent. It stays silent while it waits - that is normal, never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; persistent or low-severity findings may be surfaced with a note when the cause is not obvious. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. If your harness limits how long a foreground command may run, run the poll as a background task and wait for it to finish; if it still gets killed or times out, just re-run it - queued feedback is never lost. Use --agent-reply after applying prior feedback to display your response in Lavish Editor before waiting again. When status is ended, stop polling and do not reopen the session uninvited - deliver remaining updates directly in this conversation instead.
|
|
6936
6990
|
`,
|
|
6937
6991
|
end: `Usage: lavish-axi end <html-file>
|
|
6938
6992
|
|
|
6939
|
-
End a Lavish Editor session.
|
|
6993
|
+
End a Lavish Editor session as the agent. A session ended this way still reopens normally on the next \`lavish-axi <html-file>\`, unlike a user ending it from the browser, which requires --reopen.
|
|
6940
6994
|
`,
|
|
6941
6995
|
export: `Usage: lavish-axi export <html-file> [--out <path>]
|
|
6942
6996
|
|
package/package.json
CHANGED
package/skills/lavish/SKILL.md
CHANGED
|
@@ -37,6 +37,7 @@ Use lavish-axi when the user asks for a visual artifact, HTML explainer, interac
|
|
|
37
37
|
4. If poll returns `layout_warnings`, follow the returned `next_step`: fix and re-check fresh error-severity findings, but proceed with a note instead of looping when every current warning is persistent or low-severity.
|
|
38
38
|
5. Apply human feedback, then poll again with `--agent-reply "<message>"` to reply in the browser and keep the loop going.
|
|
39
39
|
6. Run `npx -y lavish-axi end <html-file>` when the review is finished.
|
|
40
|
+
7. If the user ends the session from the browser instead, `npx -y lavish-axi <html-file>` refuses to reopen it and says so - only pass `--reopen` when the user asks for further review or something genuinely important needs their visual attention. Otherwise deliver remaining updates directly in this conversation.
|
|
40
41
|
|
|
41
42
|
## Visual guidance
|
|
42
43
|
|
|
@@ -62,11 +63,11 @@ For flows, architecture, state, or sequence diagrams, do not hand-build boxes-an
|
|
|
62
63
|
|
|
63
64
|
## Commands & rules
|
|
64
65
|
|
|
65
|
-
- Run `npx -y lavish-axi <html-file>` to open or resume a Lavish Editor session
|
|
66
|
+
- Run `npx -y lavish-axi <html-file>` to open or resume a Lavish Editor session. If the user explicitly ended the session from the browser, this refuses to reopen it and explains why instead of reopening uninvited - pass `--reopen` only when the user asks for further review or something important needs their visual attention
|
|
66
67
|
- Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`
|
|
67
68
|
- Lavish serves the html file through a local express.js server. If your html needs to reference other filesystem assets such as images, CSS, fonts, and local scripts, copy them into the same directory as the HTML file, then reference them with relative paths from that directory. Never prepend `/` to those asset paths - root paths won't work
|
|
68
|
-
- Run `npx -y lavish-axi poll <html-file>` to wait for user feedback or browser-reported layout_warnings. It long-polls and stays silent until the user sends feedback, ends the session, or the real browser reports fresh layout_warnings, so leave it running - never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; if the poll says every current warning is persistent or low-severity, proceed with a note instead of looping. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost
|
|
69
|
-
- Run `npx -y lavish-axi end <html-file>` to end a session
|
|
69
|
+
- Run `npx -y lavish-axi poll <html-file>` to wait for user feedback or browser-reported layout_warnings. It long-polls and stays silent until the user sends feedback, ends the session, or the real browser reports fresh layout_warnings, so leave it running - never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; if the poll says every current warning is persistent or low-severity, proceed with a note instead of looping. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost. When it reports the session ended, stop polling and do not reopen it uninvited - deliver remaining updates in this conversation instead
|
|
70
|
+
- Run `npx -y lavish-axi end <html-file>` to end a session as the agent - ending it this way still allows a plain reopen later. When the user ends it from the browser instead, a later `npx -y lavish-axi <html-file>` refuses to reopen it without `--reopen`
|
|
70
71
|
- Run `npx -y lavish-axi export <html-file> [--out <path>]` to write a portable copy of the artifact - one HTML file with its LOCAL assets inlined - so it opens with no Lavish server and no sibling files. Remote CDN/font references are left as links, so it needs network to render those. Users can also export from the browser chrome's overflow menu
|
|
71
72
|
- Run `npx -y lavish-axi share <html-file> [--password <pw>] [--token <t>]` to publish the artifact on ht-ml.app (https://ht-ml.app), a third-party hosting service not part of Lavish, and get back a visitable URL. Shares are PUBLIC by default, so anyone with the link can open them. Pass --password to publish a PRIVATE password-protected page; viewers must supply the password to view. Local assets are inlined; remote refs load over the network. It returns the url plus a secret update_key for managing the page later. Use --token or LAVISH_AXI_HTML_APP_TOKEN only when you have an optional bearer token; it is never required. Users can also publish from the browser chrome's overflow menu
|
|
72
73
|
- Run `npx -y lavish-axi stop` to shut down the background server (it also self-stops when idle or after the last session ends with nothing connected)
|