lavish-axi 0.1.79 → 0.1.80

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 CHANGED
@@ -53,7 +53,7 @@ npx skills add kunchenguid/lavish-axi --skill lavish
53
53
 
54
54
  That is the entire setup - no npm install needed.
55
55
  The skill teaches your agent to run Lavish through `npx -y lavish-axi`, so the CLI comes along on demand.
56
- It stays a short stub and sends the agent to `npx -y lavish-axi --help`, `design`, and `playbook` for current instructions, so an installed copy cannot go stale against a newer CLI.
56
+ It stays a short stub and sends the agent to `npx -y lavish-axi --help`, `reply --help`, `design`, and `playbook` for current instructions, so an installed copy cannot go stale against a newer CLI.
57
57
  Its frontmatter also includes Hermes Agent metadata, so Hermes-compatible harnesses can categorize and surface it as a first-class productivity skill.
58
58
  This installs the public `lavish` skill.
59
59
  The repository also contains an internal `lavish-design` brand skill for maintainers; default `npx skills add ... --list` and skills.sh discovery hide it unless `INSTALL_INTERNAL_SKILLS=1` is set.
@@ -210,7 +210,7 @@ pnpm link
210
210
  - **Keyboard shortcuts** - In the chrome composer, Enter sends queued prompts and Shift+Enter inserts a newline.
211
211
  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. Escape closes the card, same as Cancel, but only while it is empty (no text, no attachment); with unsent text or an attachment present, Escape does nothing rather than risk discarding it.
212
212
  Cmd+I or Ctrl+I toggles between annotate and explore mode from either the browser chrome or the artifact iframe, including while focus is in a textarea or control.
213
- - **Agent presence** - The browser shows when no agent is listening, keeps queued feedback for the next successful `lavish-axi poll` send even across reloads, and keeps human feedback actions available while the agent is working because the server queues them for the next poll. An agent reply concludes delivered work and returns presence to waiting. A supervisor-owned process listener is shown as an external listener rather than an idle captain turn.
213
+ - **Agent presence** - The browser shows when no agent is listening, keeps queued feedback for the next successful `lavish-axi poll` send even across reloads, and keeps human feedback actions available while the agent is working because the server queues them for the next poll. An agent reply concludes delivered work and returns presence to waiting (see the CLI Reference for reply and poll commands). A supervisor-owned process listener is shown as an external listener rather than an idle captain turn.
214
214
  In a Herdr-managed pane, set `LAVISH_AXI_HERDR_CHIME=1` to show a Herdr request notification and play its chime once an indefinite poll enters the waiting state. Immediate queued feedback and timed debug polls do not trigger a notification. The integration is off unless both `LAVISH_AXI_HERDR_CHIME` and Herdr's `HERDR_ENV` session marker equal `1`; leaving either unset or using any other value opts out. Notification delivery runs without blocking feedback: if Herdr is unavailable, rejects the notification, stalls, or fails, the poll continues unchanged.
215
215
  Closing the last review tab while a poll is active starts a 10-second reconnect grace period. A reload or quick reconnect keeps an active poll waiting; if every review stays disconnected, the poll returns `browser_disconnected` without ending the resumable session, and the agent asks whether to reopen or end it instead of acting uninvited.
216
216
  The no-timeout poll always writes an immediate stderr banner so it is visibly not hung; it adds the periodic stderr wait ticks only in an interactive terminal, so when stderr is piped (as under agent harnesses) the captured output carries no tick noise. Stdout always stays reserved for the final response; if the poll is interrupted or times out before feedback arrives, re-run it because feedback remains queued until delivery. Poll delivery consumes the response, so read the complete response before truncating or filtering it.
@@ -263,6 +263,7 @@ pnpm link
263
263
  | `lavish-axi update` | Check for or apply the latest npm release through the AXI SDK self-updater. |
264
264
  | `lavish-axi <html-file>` | Open or resume a Lavish Editor session, with the open-time layout gate enabled by default. Unresolved layout issues from earlier in the session are preserved. Refuses to reopen a session the user explicitly ended from the browser unless `--reopen` is passed. |
265
265
  | `lavish-axi poll <html-file>` | Long-poll until the user sends feedback, ends the session, or leaves every review disconnected past the reconnect grace period; detected layout issues arrive only when queued. On `browser_disconnected`, ask before reopening or ending the still-resumable session. On `ended`, stop polling and do not reopen uninvited. |
266
+ | `lavish-axi reply <html-file>` | Post an agent reply and exit 0 only after the server accepts it, so the review page stops showing Working. Exits non-zero if the server does not confirm within 10 seconds. Use this when handing a result back without starting a long-poll. `poll --agent-reply` still posts a reply and then waits. |
266
267
  | `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. |
267
268
  | `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. |
268
269
  | `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, `--private` locks one behind a generated password, and the same command republishes or unpublishes an existing page with `--site`/`--update-key`. |
@@ -297,6 +298,8 @@ For flows, architecture, state, or sequence diagrams, open the diagram playbook
297
298
  | `lavish-axi poll` | `--agent-reply "..."` | Show a concise agent reply in the existing browser chat, conclude delivered work, and return presence to waiting before polling again. Keep replies concise by default. |
298
299
  | `lavish-axi poll` | `--agent-reply-file <path>` | When a longer reply is genuinely necessary, read Markdown from a file (`-` for stdin) so newlines survive quoting. Use blank-line paragraphs, `- ` / `1. ` lists, and `## ` headings so the Conversation panel renders it scannably. The Markdown subset is under Feedback controls. Cannot be combined with `--agent-reply`. |
299
300
  | `lavish-axi poll` | `--timeout-ms <ms>` | Test/debug escape hatch only; agents should normally omit it and leave the long poll running. |
301
+ | `lavish-axi reply` | `--agent-reply "..."` | Concise reply text. Required unless `--agent-reply-file` is set. The command exits 0 only after the server accepts it. Empty text is refused. Prefer this over `poll --agent-reply` when you are not about to long-poll. |
302
+ | `lavish-axi reply` | `--agent-reply-file <path>` | Read the reply from a file (`-` for stdin) so newlines survive quoting. Cannot be combined with `--agent-reply`. Same Markdown guidance as `poll --agent-reply-file`. |
300
303
  | `lavish-axi stop` | `--port <port>` | Shut down a server running on a non-default port. |
301
304
  | `lavish-axi server` | `--verbose` | Log session and watcher events to stderr; can also be enabled with `LAVISH_AXI_DEBUG=1`. Detached server output is appended with UTC timestamps to `~/.lavish-axi/server.log` (or `LAVISH_AXI_STATE_DIR/server.log`) for startup, shutdown-cause, and crash diagnostics. |
302
305
  | `lavish-axi server` | `--also-listen <host>` | Also listen on this concrete address (repeatable). The CLI passes it when it replaces a running server, so the replacement keeps every address the old one served; see One server per port. |
package/dist/cli.mjs CHANGED
@@ -8911,13 +8911,14 @@ var SessionStore = class {
8911
8911
  return session;
8912
8912
  });
8913
8913
  }
8914
- async addAgentReply(key, text) {
8914
+ async addAgentReply(key, text, { requireOpen = false } = {}) {
8915
8915
  return this.runExclusive(async () => {
8916
8916
  const state = await this.readState();
8917
8917
  const session = state.sessions[key];
8918
8918
  if (!session) {
8919
8919
  return null;
8920
8920
  }
8921
+ if (requireOpen && session.status === "ended") return session;
8921
8922
  const at = (/* @__PURE__ */ new Date()).toISOString();
8922
8923
  session.chat = [...session.chat || [], { role: "agent", text: String(text || ""), at }];
8923
8924
  applyTranscriptBound(session);
@@ -10228,9 +10229,9 @@ async function serve({
10228
10229
  next(error);
10229
10230
  }
10230
10231
  });
10231
- async function publishAgentReply(key, text) {
10232
- const session = await store.addAgentReply(key, text);
10233
- if (!session) return null;
10232
+ async function publishAgentReply(key, text, { requireOpen = false } = {}) {
10233
+ const session = await store.addAgentReply(key, text, { requireOpen });
10234
+ if (!session || requireOpen && session.status === "ended") return session;
10234
10235
  const lastEntry = session.chat?.at(-1);
10235
10236
  const entry = serializeChat([
10236
10237
  lastEntry?.role === "agent" ? lastEntry : { role: "agent", text, at: session.updated_at }
@@ -10629,11 +10630,17 @@ async function serve({
10629
10630
  });
10630
10631
  app.post("/api/:key/agent-reply", async (req, res, next) => {
10631
10632
  try {
10632
- const session = await publishAgentReply(req.params.key, String(req.body?.text || ""));
10633
+ const session = await publishAgentReply(req.params.key, String(req.body?.text || ""), {
10634
+ requireOpen: true
10635
+ });
10633
10636
  if (!session) {
10634
10637
  res.status(404).json({ error: "session not found" });
10635
10638
  return;
10636
10639
  }
10640
+ if (session.status === "ended") {
10641
+ res.status(409).json({ status: "ended", ended_by: session.ended_by });
10642
+ return;
10643
+ }
10637
10644
  res.json({ status: "sent" });
10638
10645
  } catch (error) {
10639
10646
  next(error);
@@ -12273,7 +12280,19 @@ function normalizePagePath(path9) {
12273
12280
 
12274
12281
  // src/cli.js
12275
12282
  var SHARE_VALUE_FLAGS = ["--password", "--token", "--site", "--update-key"];
12276
- var COMMANDS = /* @__PURE__ */ new Set(["open", "poll", "end", "stop", "server", "playbook", "design", "setup", "export", "share"]);
12283
+ var COMMANDS = /* @__PURE__ */ new Set([
12284
+ "open",
12285
+ "poll",
12286
+ "reply",
12287
+ "end",
12288
+ "stop",
12289
+ "server",
12290
+ "playbook",
12291
+ "design",
12292
+ "setup",
12293
+ "export",
12294
+ "share"
12295
+ ]);
12277
12296
  var RESERVED = new Set(RESERVED_COMMANDS);
12278
12297
  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`.";
12279
12298
  var POLL_WAKE_PATH_RULES = Object.freeze([
@@ -12296,7 +12315,7 @@ var AGENT_REPLY_INPUT_LIMIT_BYTES = AGENT_REPLY_JSON_LIMIT_BYTES - AGENT_REPLY_J
12296
12315
  var AGENT_REPLY_LIMIT_LABEL = "2 MB JSON request limit";
12297
12316
  var POLL_STATE_HEADER = "lavish-poll-state";
12298
12317
  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.";
12299
- var VERSION = "0.1.79";
12318
+ var VERSION = "0.1.80";
12300
12319
  function detectInvokingAgent(env = process.env) {
12301
12320
  return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
12302
12321
  }
@@ -12386,6 +12405,7 @@ async function run(argv) {
12386
12405
  commands: {
12387
12406
  open: openCommand,
12388
12407
  poll: pollCommand,
12408
+ reply: replyCommand,
12389
12409
  end: endCommand,
12390
12410
  stop: stopCommand,
12391
12411
  playbook: playbookCommand,
@@ -12456,6 +12476,7 @@ function createHomeOutput({ bin, sessions, includeSessions = true, agent = "gene
12456
12476
  "Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`",
12457
12477
  "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",
12458
12478
  `Run \`lavish-axi poll <html-file>\` to wait for user feedback. It long-polls and stays silent until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period, so leave it running - never kill it. Detected layout issues never return this poll: the browser files them in the user's Layout issues inbox in the Lavish top bar, and they arrive as an ordinary tag "layout-warnings" prompt only when the user selects them and queues the fixes. Never edit the artifact to chase a layout issue the user has not queued. The only exception is a fatal artifact_failures response, which means the review surface itself could not be used. ${pollExecutionGuidance({ agent })} ${POLL_SEND_AND_END_RULE}`,
12479
+ 'Run `lavish-axi reply <html-file> --agent-reply "<message>"` to show a concise reply and exit once Lavish Editor accepts it, when you are handing a result back and are not about to long-poll. The board stops showing Working only after this command exits 0. Use `lavish-axi poll <html-file> --agent-reply "<message>"` when that reply should be followed by another wait for feedback. A longer reply uses `--agent-reply-file <path>` (`-` reads stdin) on either command. Run `lavish-axi reply --help` for the receipt contract',
12459
12480
  'Mermaid is the whiteboard opt-in, not the diagram default: only when the user asks for an editable whiteboard, author that diagram as Mermaid in a `.mermaid` container. Rendered Mermaid diagrams there become embedded, editable Excalidraw whiteboards in the browser (click a diagram to unlock editing; a Fullscreen action opens it over the whole viewport) - flowchart, sequence, class, ER, and state diagrams convert to editable shapes; other types embed as an image to draw on. Scenes autosave locally; an unmodified autosave silently re-converts when a reload changes the Mermaid source. If the reviewer edited the scene, they choose to re-convert and discard saved edits or keep editing the saved scene. Standalone and exported copies still render plain Mermaid. Queue feedback adds a prompt to the Conversation panel; when the user sends it, poll returns a tag "whiteboard" prompt carrying a bounded edit summary plus local scenePath (.excalidraw JSON) and previewPath (PNG) files - read the summary first, open the files only when needed, then apply the edits by updating the Mermaid source in the artifact (never try to write the scene back)',
12460
12481
  "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`",
12461
12482
  "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",
@@ -12497,7 +12518,7 @@ function createOpenOutput({
12497
12518
  session: { file, url, status },
12498
12519
  ...networkWarning ? { network_warning: networkWarning } : {},
12499
12520
  ...selfPaintWarning ? { self_paint_warning: selfPaintWarning } : {},
12500
- next_step: `${networkPrefix}${selfPaintPrefix}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 leaves every review window disconnected past the reconnect grace period, and it stays silent the whole time - that is normal, never kill it. Layout issues the browser detects do not return this poll; they wait in the user's Layout issues inbox until the user queues them, then arrive as an ordinary tag "layout-warnings" prompt. Do not pass --timeout-ms during normal agent use. ${pollExecutionGuidance({ agent })} After applying feedback, run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms to show a concise response in Lavish Editor and wait for more feedback. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_NEXT_POINTER} 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\`.`
12521
+ next_step: `${networkPrefix}${selfPaintPrefix}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 leaves every review window disconnected past the reconnect grace period, and it stays silent the whole time - that is normal, never kill it. Layout issues the browser detects do not return this poll; they wait in the user's Layout issues inbox until the user queues them, then arrive as an ordinary tag "layout-warnings" prompt. Do not pass --timeout-ms during normal agent use. ${pollExecutionGuidance({ agent })} After applying feedback, run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms to show a concise response in Lavish Editor and keep waiting for more feedback. If instead you are handing back a result without starting another long-poll, run \`lavish-axi reply ${file} --agent-reply "<message for the user>"\` to get an acceptance receipt. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_NEXT_POINTER} 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\`.`
12501
12522
  };
12502
12523
  }
12503
12524
  function createUserEndedOpenOutput({ file, url, networkWarning = void 0 }) {
@@ -12622,6 +12643,41 @@ ${pollInterruptedText(absolute)}
12622
12643
  }
12623
12644
  }
12624
12645
  }
12646
+ var REPLY_VALUE_FLAGS = ["--agent-reply", "--agent-reply-file"];
12647
+ var REPLY_TOO_LARGE_HELP = "Shorten the reply, then retry `lavish-axi reply <html-file>`";
12648
+ async function replyCommand(args) {
12649
+ const file = firstPositionalArg(args, REPLY_VALUE_FLAGS);
12650
+ if (!file) {
12651
+ throw new AxiError("HTML file path is required", "VALIDATION_ERROR", [
12652
+ 'Run `lavish-axi reply <html-file> --agent-reply "<message>"`'
12653
+ ]);
12654
+ }
12655
+ const inline = inspectValueFlag(args, "--agent-reply");
12656
+ const fromFile = inspectValueFlag(args, "--agent-reply-file");
12657
+ if (!inline.present && !fromFile.present) {
12658
+ throw new AxiError("An agent reply is required", "VALIDATION_ERROR", [
12659
+ 'Pass exactly one of --agent-reply "<message>" or --agent-reply-file <path> (`-` reads stdin)',
12660
+ "Use `lavish-axi poll <html-file> --agent-reply` when the reply should be followed by a wait for feedback"
12661
+ ]);
12662
+ }
12663
+ const text = await resolveAgentReply(args, { tooLargeHelp: REPLY_TOO_LARGE_HELP });
12664
+ if (!String(text || "").trim()) {
12665
+ throw new AxiError("Agent reply text was empty", "VALIDATION_ERROR", [
12666
+ 'Pass --agent-reply "<message>" or --agent-reply-file <path> with a non-empty body'
12667
+ ]);
12668
+ }
12669
+ await assertHtmlFile(file);
12670
+ const absolute = await canonicalFile(file);
12671
+ const baseUrl = await ensureServer();
12672
+ await postAgentReply(`${baseUrl}/api/${sessionKey(absolute)}/agent-reply`, text, absolute);
12673
+ return createReplyOutput(absolute);
12674
+ }
12675
+ function createReplyOutput(file) {
12676
+ return {
12677
+ reply: { file, status: "sent" },
12678
+ next_step: `Lavish Editor accepted the reply for ${file} and is no longer showing Working. Run \`lavish-axi poll ${file}\` when you are ready to wait for more feedback.`
12679
+ };
12680
+ }
12625
12681
  function pollWaitBannerText(file) {
12626
12682
  return `[lavish-axi] Long-polling for user feedback on ${file}. This stays silent until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period - leave it running. Detected layout issues do NOT return this poll: they wait in the user's Layout issues inbox until the user queues them as ordinary feedback. If it gets killed or times out before feedback arrives, re-run \`lavish-axi poll ${file}\` - feedback remains queued until delivery. Poll delivery consumes the response, so read it completely.`;
12627
12683
  }
@@ -12706,7 +12762,7 @@ function createFeedbackNextStep(file, artifactFailures, sessionEnded, endedBy, p
12706
12762
  return `${failureNote}${layoutNote}${whiteboardNote}${attachmentNote}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.`;
12707
12763
  }
12708
12764
  const prefix = count > 0 ? artifactFailuresPrefix(file, artifactFailures) : `Apply the requested changes to ${file}. `;
12709
- return `${prefix}${layoutNote}${whiteboardNote}${attachmentNote}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. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_NEXT_POINTER} The poll waits silently until the user sends more feedback, ends the session, or leaves every review window disconnected past the reconnect grace period - never kill it. ${pollExecutionGuidance({ agent })}`;
12765
+ return `${prefix}${layoutNote}${whiteboardNote}${attachmentNote}Do not respond to the user just yet. If you are continuing to wait for feedback, run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms to reply and keep polling. If instead you are handing back a result without starting another long-poll, run \`lavish-axi reply ${file} --agent-reply "<message for the user>"\` to post it and receive an acceptance receipt. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_NEXT_POINTER} The poll waits silently until the user sends more feedback, ends the session, or leaves every review window disconnected past the reconnect grace period - never kill it. ${pollExecutionGuidance({ agent })}`;
12710
12766
  }
12711
12767
  function artifactFailuresPrefix(file, artifactFailures) {
12712
12768
  const count = artifactFailures.length;
@@ -13922,6 +13978,57 @@ async function postJson(url, body) {
13922
13978
  }
13923
13979
  return response.json();
13924
13980
  }
13981
+ var AGENT_REPLY_RECEIPT_TIMEOUT_MS = 1e4;
13982
+ async function postAgentReply(url, text, file, { timeoutMs = AGENT_REPLY_RECEIPT_TIMEOUT_MS } = {}) {
13983
+ const signal = AbortSignal.timeout(timeoutMs);
13984
+ let response;
13985
+ try {
13986
+ response = await fetch(url, {
13987
+ method: "POST",
13988
+ headers: { "content-type": "application/json" },
13989
+ body: JSON.stringify({ text }),
13990
+ signal
13991
+ });
13992
+ } catch {
13993
+ if (signal.aborted) throw agentReplyReceiptTimeoutError(file, timeoutMs);
13994
+ throw new AxiError("Lavish Editor server connection failed", "SERVER_ERROR", [
13995
+ "Run `lavish-axi server --verbose` or inspect `~/.lavish-axi/server.log` (`LAVISH_AXI_STATE_DIR/server.log` when set) for server startup or crash diagnostics",
13996
+ `Re-run \`lavish-axi reply ${file} --agent-reply "<message>"\` after the server is reachable`
13997
+ ]);
13998
+ }
13999
+ let payload = null;
14000
+ try {
14001
+ payload = await response.json();
14002
+ } catch {
14003
+ if (signal.aborted) throw agentReplyReceiptTimeoutError(file, timeoutMs);
14004
+ }
14005
+ if (response.status === 404) {
14006
+ throw new AxiError("No active Lavish Editor session for this file", "NOT_FOUND", [
14007
+ `Run \`lavish-axi ${file}\` first`
14008
+ ]);
14009
+ }
14010
+ if (response.status === 409 && payload?.status === "ended") {
14011
+ throw new AxiError("Lavish Editor session has ended; reply was not sent", "SESSION_ENDED", [
14012
+ createEndedNextStep(file, payload.ended_by)
14013
+ ]);
14014
+ }
14015
+ if (!response.ok || payload?.status !== "sent") {
14016
+ throw new AxiError(`Lavish Editor did not accept the agent reply (${response.status})`, "SERVER_ERROR", [
14017
+ `Confirm the session is open with \`lavish-axi ${file}\`, then re-run \`lavish-axi reply ${file}\``
14018
+ ]);
14019
+ }
14020
+ return payload;
14021
+ }
14022
+ function agentReplyReceiptTimeoutError(file, timeoutMs) {
14023
+ return new AxiError(
14024
+ `Lavish Editor did not confirm the agent reply within ${timeoutMs}ms, so acceptance is unknown`,
14025
+ "SERVER_ERROR",
14026
+ [
14027
+ "Run `lavish-axi server --verbose` or inspect `~/.lavish-axi/server.log` (`LAVISH_AXI_STATE_DIR/server.log` when set) for server diagnostics",
14028
+ `Check the Lavish Editor conversation for the reply before re-running \`lavish-axi reply ${file}\`, so it is not posted twice`
14029
+ ]
14030
+ );
14031
+ }
13925
14032
  function serverConnectionError() {
13926
14033
  return new AxiError("Lavish Editor server connection failed", "SERVER_ERROR", [
13927
14034
  "Run `lavish-axi server --verbose` or inspect `~/.lavish-axi/server.log` (`LAVISH_AXI_STATE_DIR/server.log` when set) for server startup or crash diagnostics",
@@ -13991,12 +14098,12 @@ function inspectValueFlag(args, flag) {
13991
14098
  return { present: false };
13992
14099
  }
13993
14100
  var AGENT_REPLY_FILE_HINT = "Pass --agent-reply-file <path>, or --agent-reply-file - to read stdin";
13994
- function agentReplyTooLargeError() {
14101
+ function agentReplyTooLargeError(suggestion) {
13995
14102
  return new AxiError(`Agent reply exceeds the ${AGENT_REPLY_LIMIT_LABEL}`, "VALIDATION_ERROR", [
13996
- "Shorten the reply, then retry the same poll command"
14103
+ suggestion || "Shorten the reply, then retry the same poll command"
13997
14104
  ]);
13998
14105
  }
13999
- async function readAgentReplyStream(stream) {
14106
+ async function readAgentReplyStream(stream, tooLargeHelp) {
14000
14107
  const chunks = [];
14001
14108
  let bytes = 0;
14002
14109
  for await (const value of stream) {
@@ -14004,17 +14111,22 @@ async function readAgentReplyStream(stream) {
14004
14111
  bytes += chunk.length;
14005
14112
  if (bytes > AGENT_REPLY_INPUT_LIMIT_BYTES) {
14006
14113
  stream.destroy();
14007
- throw agentReplyTooLargeError();
14114
+ throw agentReplyTooLargeError(tooLargeHelp);
14008
14115
  }
14009
14116
  chunks.push(chunk);
14010
14117
  }
14011
14118
  const text = Buffer.concat(chunks, bytes).toString("utf8");
14012
14119
  if (Buffer.byteLength(JSON.stringify({ agent_reply: text })) > AGENT_REPLY_JSON_LIMIT_BYTES) {
14013
- throw agentReplyTooLargeError();
14120
+ throw agentReplyTooLargeError(tooLargeHelp);
14014
14121
  }
14015
14122
  return text;
14016
14123
  }
14017
- async function resolveAgentReply(args, { createReadStreamFn = createReadStream, stdin = process.stdin, stdinIsTTY = process.stdin.isTTY === true } = {}) {
14124
+ async function resolveAgentReply(args, {
14125
+ createReadStreamFn = createReadStream,
14126
+ stdin = process.stdin,
14127
+ stdinIsTTY = process.stdin.isTTY === true,
14128
+ tooLargeHelp = void 0
14129
+ } = {}) {
14018
14130
  const inline = inspectValueFlag(args, "--agent-reply");
14019
14131
  const fromFile = inspectValueFlag(args, "--agent-reply-file");
14020
14132
  if (inline.present && fromFile.present) {
@@ -14023,12 +14135,12 @@ async function resolveAgentReply(args, { createReadStreamFn = createReadStream,
14023
14135
  ]);
14024
14136
  }
14025
14137
  if (fromFile.present) {
14026
- return readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIsTTY });
14138
+ return readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIsTTY, tooLargeHelp });
14027
14139
  }
14028
14140
  if (!inline.present) return null;
14029
14141
  return inline.value || null;
14030
14142
  }
14031
- async function readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIsTTY }) {
14143
+ async function readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIsTTY, tooLargeHelp }) {
14032
14144
  if (fromFile.swallows && typeof fromFile.value === "string" && fromFile.value.startsWith("--")) {
14033
14145
  throw new AxiError(
14034
14146
  `--agent-reply-file was given no value: the next argument ${fromFile.value} is another flag, so it would have been used as the path`,
@@ -14048,7 +14160,7 @@ async function readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIs
14048
14160
  ]);
14049
14161
  }
14050
14162
  try {
14051
- text = await readAgentReplyStream(stdin);
14163
+ text = await readAgentReplyStream(stdin, tooLargeHelp);
14052
14164
  } catch (error) {
14053
14165
  if (error instanceof AxiError) throw error;
14054
14166
  const detail = error instanceof Error ? error.message : String(error);
@@ -14058,7 +14170,7 @@ async function readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIs
14058
14170
  }
14059
14171
  } else {
14060
14172
  try {
14061
- text = await readAgentReplyStream(createReadStreamFn(spec));
14173
+ text = await readAgentReplyStream(createReadStreamFn(spec), tooLargeHelp);
14062
14174
  } catch (error) {
14063
14175
  if (error instanceof AxiError) throw error;
14064
14176
  const detail = error instanceof Error ? error.message : String(error);
@@ -14093,6 +14205,7 @@ Usage:
14093
14205
  lavish-axi
14094
14206
  lavish-axi <html-file> [--no-open] [--no-gate] [--reopen]
14095
14207
  lavish-axi poll <html-file> [--owner <label>] [--takeover] [--agent-reply "..."] [--agent-reply-file <path>]
14208
+ lavish-axi reply <html-file> (--agent-reply "..." | --agent-reply-file <path>)
14096
14209
  lavish-axi end <html-file>
14097
14210
  lavish-axi export <html-file> [--out <path>]
14098
14211
  lavish-axi share <html-file> [--private | --password <pw>] [--token <t>]
@@ -14106,7 +14219,7 @@ Usage:
14106
14219
 
14107
14220
  ${DESIGN_SYSTEM_HINT}
14108
14221
 
14109
- Note: poll long-polls until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period, staying silent while it waits - never kill it. Layout issues the browser detects are passive: they collect in the user's Layout issues inbox in the Lavish top bar and reach the agent only when the user selects them and queues the fixes, as an ordinary tag "layout-warnings" prompt. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. ${pollExecutionGuidance({ agent })} ${POLL_SEND_AND_END_RULE}
14222
+ Note: poll long-polls until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period, staying silent while it waits - never kill it. Layout issues the browser detects are passive: they collect in the user's Layout issues inbox in the Lavish top bar and reach the agent only when the user selects them and queues the fixes, as an ordinary tag "layout-warnings" prompt. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. ${pollExecutionGuidance({ agent })} ${POLL_SEND_AND_END_RULE} Use \`lavish-axi reply\` when handing a result back without waiting for more feedback: it exits 0 only after the server accepts the reply, so the board stops showing Working. \`poll --agent-reply\` posts a reply and then keeps waiting.
14110
14223
 
14111
14224
  `;
14112
14225
  }
@@ -14120,7 +14233,7 @@ Open or resume a Lavish Editor review session for an HTML artifact. Use --no-ope
14120
14233
 
14121
14234
  This command exclusively long-polls for queued user prompts. Pass --owner <label> to make the active listener visible in session listings; --takeover displaces an existing listener, which receives LISTENER_REPLACED. A second poll without --takeover fails with LISTENER_ACTIVE instead of silently returning waiting.
14122
14235
 
14123
- This command long-polls indefinitely for queued user prompts. It stays silent while it waits - that is normal, never kill it. Browser-detected layout issues do NOT return this poll: they are filed passively in the user's Layout issues inbox and arrive as an ordinary tag "layout-warnings" prompt only after the user selects them and queues the fixes. Warning lifecycle: an issue stays unresolved and counted while queued, becomes recurring if a newer artifact revision still shows it, and is resolved only after a newer artifact load plus a complete diagnostic pass at the same viewport no longer detects it. A failed or incomplete pass preserves it as unverified rather than clearing it. The only response that arrives without user action is artifact_failures - a fatal failure that made the review surface itself unusable. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. In a Herdr-managed pane, set LAVISH_AXI_HERDR_CHIME=1 to request attention when the poll has entered its waiting state; unset it or use any other value to keep the current silent behavior. Notification failures never interrupt the poll. ${pollExecutionGuidance({ agent })} Use --agent-reply after applying prior feedback to display a concise response in Lavish Editor before waiting again. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_HELP_POINTER} Do not combine --agent-reply with --agent-reply-file.
14236
+ This command long-polls indefinitely for queued user prompts. It stays silent while it waits - that is normal, never kill it. Browser-detected layout issues do NOT return this poll: they are filed passively in the user's Layout issues inbox and arrive as an ordinary tag "layout-warnings" prompt only after the user selects them and queues the fixes. Warning lifecycle: an issue stays unresolved and counted while queued, becomes recurring if a newer artifact revision still shows it, and is resolved only after a newer artifact load plus a complete diagnostic pass at the same viewport no longer detects it. A failed or incomplete pass preserves it as unverified rather than clearing it. The only response that arrives without user action is artifact_failures - a fatal failure that made the review surface itself unusable. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. In a Herdr-managed pane, set LAVISH_AXI_HERDR_CHIME=1 to request attention when the poll has entered its waiting state; unset it or use any other value to keep the current silent behavior. Notification failures never interrupt the poll. ${pollExecutionGuidance({ agent })} Use --agent-reply after applying prior feedback to display a concise response in Lavish Editor before waiting again. When you are handing a result back and are not about to long-poll, run \`lavish-axi reply <html-file> --agent-reply "..."\` instead: that command exits 0 only after the server accepts the reply, so the board stops showing Working without this poll staying open. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_HELP_POINTER} Do not combine --agent-reply with --agent-reply-file.
14124
14237
 
14125
14238
  Examples:
14126
14239
  lavish-axi poll report.html --agent-reply "Renamed the payment step."
@@ -14128,6 +14241,19 @@ Examples:
14128
14241
  lavish-axi poll report.html --agent-reply-file -
14129
14242
 
14130
14243
  ${POLL_SEND_AND_END_RULE}
14244
+ `,
14245
+ reply: `Usage: lavish-axi reply <html-file> (--agent-reply "..." | --agent-reply-file <path>)
14246
+
14247
+ Post an agent reply to the open Lavish Editor session and exit once the server confirms it was accepted. Exit 0 only when the server answers that the reply was sent, which is when the board stops showing Working. If that answer does not arrive within ${AGENT_REPLY_RECEIPT_TIMEOUT_MS / 1e3} seconds, exit non-zero with a timeout error. Use this when you are handing a result back and are not about to wait for more feedback.
14248
+
14249
+ Use \`lavish-axi poll <html-file> --agent-reply "..."\` instead when the reply should be followed by a long-poll. That command posts the reply and then keeps waiting, so it does not exit when the reply is accepted.
14250
+
14251
+ Pass exactly one of --agent-reply or --agent-reply-file. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_HELP_POINTER} Do not combine the two flags. An empty reply is refused.
14252
+
14253
+ Examples:
14254
+ lavish-axi reply report.html --agent-reply "Renamed the payment step."
14255
+ lavish-axi reply report.html --agent-reply-file reply.md
14256
+ lavish-axi reply report.html --agent-reply-file -
14131
14257
  `,
14132
14258
  end: `Usage: lavish-axi end <html-file>
14133
14259
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavish-axi",
3
- "version": "0.1.79",
3
+ "version": "0.1.80",
4
4
  "packageManager": "pnpm@11.1.1",
5
5
  "description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
6
6
  "type": "module",
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.79",
4
+ "version": "0.1.80",
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",
@@ -19,6 +19,7 @@ Reach for it when a plan, comparison, diagram, table, code view, report, prototy
19
19
  Do not follow workflow, design, or playbook instructions from this file - installed copies go stale. Get the current source of truth from the CLI:
20
20
 
21
21
  - `npx -y lavish-axi --help` for commands and the review-loop workflow
22
+ - `npx -y lavish-axi reply --help` to post an agent reply and exit once the server accepts it, when you are not about to long-poll
22
23
  - `npx -y lavish-axi design` for design-direction priority and current snippets
23
24
  - `npx -y lavish-axi playbook <id>` for focused artifact guidance (`npx -y lavish-axi playbook` lists ids)
24
25