lavish-axi 0.1.73 → 0.1.74
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 -0
- package/dist/cli.mjs +65 -11
- package/dist/server.mjs +11 -2
- package/package.json +1 -1
- package/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -206,6 +206,7 @@ pnpm link
|
|
|
206
206
|
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.
|
|
207
207
|
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.
|
|
208
208
|
- **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.
|
|
209
|
+
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.
|
|
209
210
|
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.
|
|
210
211
|
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.
|
|
211
212
|
Codex-specific guidance keeps that poll attached to the active turn instead of hiding it in a background task, because completed background tasks may not resume the agent.
|
package/dist/cli.mjs
CHANGED
|
@@ -9844,7 +9844,7 @@ async function serve({
|
|
|
9844
9844
|
const streamHeartbeat = timeoutMs === null;
|
|
9845
9845
|
let heartbeat = null;
|
|
9846
9846
|
if (streamHeartbeat) {
|
|
9847
|
-
res.status(200).type("application/json");
|
|
9847
|
+
res.status(200).type("application/json").set("Lavish-Poll-State", "listening");
|
|
9848
9848
|
res.write(" ");
|
|
9849
9849
|
heartbeat = setInterval(() => {
|
|
9850
9850
|
if (!res.writableEnded) res.write(" ");
|
|
@@ -10658,7 +10658,9 @@ ${body}`
|
|
|
10658
10658
|
let retryIndex = 0;
|
|
10659
10659
|
while (true) {
|
|
10660
10660
|
try {
|
|
10661
|
-
const httpServer = await listenHttp(app, boundPort, listenHost)
|
|
10661
|
+
const httpServer = await listenHttp(app, boundPort, listenHost, (error) => {
|
|
10662
|
+
writeLog(`[lavish] HTTP server error: ${error instanceof Error ? error.message : String(error)}`);
|
|
10663
|
+
});
|
|
10662
10664
|
httpServer.on("upgrade", handleEventUpgrade);
|
|
10663
10665
|
if (boundPort === 0) boundPort = httpServer.address().port;
|
|
10664
10666
|
httpServers.push(httpServer);
|
|
@@ -10858,15 +10860,20 @@ ${body}`
|
|
|
10858
10860
|
done
|
|
10859
10861
|
};
|
|
10860
10862
|
}
|
|
10861
|
-
function listenHttp(app, port, host) {
|
|
10863
|
+
function listenHttp(app, port, host, onRuntimeError) {
|
|
10862
10864
|
return new Promise((resolve, reject) => {
|
|
10863
10865
|
const server = createServer(app);
|
|
10866
|
+
let listening = false;
|
|
10864
10867
|
const onError = (error) => {
|
|
10865
|
-
|
|
10866
|
-
|
|
10868
|
+
if (!listening) {
|
|
10869
|
+
server.off("listening", onListening);
|
|
10870
|
+
reject(error);
|
|
10871
|
+
return;
|
|
10872
|
+
}
|
|
10873
|
+
onRuntimeError?.(error);
|
|
10867
10874
|
};
|
|
10868
10875
|
const onListening = () => {
|
|
10869
|
-
|
|
10876
|
+
listening = true;
|
|
10870
10877
|
const address = server.address();
|
|
10871
10878
|
if (address && typeof address === "object" && isWildcardHost(address.address)) {
|
|
10872
10879
|
server.close(() => reject(new Error(`Refusing all-interfaces listener at ${address.address}`)));
|
|
@@ -10874,7 +10881,7 @@ function listenHttp(app, port, host) {
|
|
|
10874
10881
|
}
|
|
10875
10882
|
resolve(server);
|
|
10876
10883
|
};
|
|
10877
|
-
server.
|
|
10884
|
+
server.on("error", onError);
|
|
10878
10885
|
server.once("listening", onListening);
|
|
10879
10886
|
server.listen({ port, host });
|
|
10880
10887
|
});
|
|
@@ -11619,14 +11626,57 @@ var AGENT_REPLY_JSON_LIMIT_BYTES = 2 * 1024 * 1024;
|
|
|
11619
11626
|
var AGENT_REPLY_JSON_ENVELOPE_BYTES = Buffer.byteLength(JSON.stringify({ text: "" }));
|
|
11620
11627
|
var AGENT_REPLY_INPUT_LIMIT_BYTES = AGENT_REPLY_JSON_LIMIT_BYTES - AGENT_REPLY_JSON_ENVELOPE_BYTES;
|
|
11621
11628
|
var AGENT_REPLY_LIMIT_LABEL = "2 MB JSON request limit";
|
|
11629
|
+
var POLL_STATE_HEADER = "lavish-poll-state";
|
|
11622
11630
|
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.";
|
|
11623
|
-
var VERSION = "0.1.
|
|
11631
|
+
var VERSION = "0.1.74";
|
|
11624
11632
|
function detectInvokingAgent(env = process.env) {
|
|
11625
11633
|
return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
|
|
11626
11634
|
}
|
|
11627
11635
|
function shouldNarratePollWaitTicks({ isTTY }) {
|
|
11628
11636
|
return Boolean(isTTY);
|
|
11629
11637
|
}
|
|
11638
|
+
function herdrPollChimeEnabled(env = process.env) {
|
|
11639
|
+
return env.HERDR_ENV === "1" && env.LAVISH_AXI_HERDR_CHIME === "1";
|
|
11640
|
+
}
|
|
11641
|
+
function notifyHerdrPollReady({ env = process.env, runner = spawn } = {}) {
|
|
11642
|
+
if (!herdrPollChimeEnabled(env)) return false;
|
|
11643
|
+
try {
|
|
11644
|
+
const child = runner(
|
|
11645
|
+
"herdr",
|
|
11646
|
+
[
|
|
11647
|
+
"notification",
|
|
11648
|
+
"show",
|
|
11649
|
+
"Lavish review ready",
|
|
11650
|
+
"--body",
|
|
11651
|
+
"The artifact is open and Lavish is polling for your feedback.",
|
|
11652
|
+
"--sound",
|
|
11653
|
+
"request"
|
|
11654
|
+
],
|
|
11655
|
+
{ stdio: "ignore" }
|
|
11656
|
+
);
|
|
11657
|
+
const kill = () => {
|
|
11658
|
+
try {
|
|
11659
|
+
child.kill("SIGKILL");
|
|
11660
|
+
} catch {
|
|
11661
|
+
return;
|
|
11662
|
+
}
|
|
11663
|
+
};
|
|
11664
|
+
const timer = setTimeout(kill, 2e3);
|
|
11665
|
+
timer.unref();
|
|
11666
|
+
const cleanup = () => {
|
|
11667
|
+
clearTimeout(timer);
|
|
11668
|
+
process.off("exit", kill);
|
|
11669
|
+
};
|
|
11670
|
+
child.on("error", () => {
|
|
11671
|
+
});
|
|
11672
|
+
child.once("close", cleanup);
|
|
11673
|
+
process.once("exit", kill);
|
|
11674
|
+
child.unref();
|
|
11675
|
+
return true;
|
|
11676
|
+
} catch {
|
|
11677
|
+
return false;
|
|
11678
|
+
}
|
|
11679
|
+
}
|
|
11630
11680
|
function pollExecutionGuidance({ agent = "generic" } = {}) {
|
|
11631
11681
|
const sharedGuidance = POLL_WAKE_PATH_RULES.join(" ");
|
|
11632
11682
|
const agentGuidance = agent === "codex" ? ` ${CODEX_POLL_WAKE_PATH_GUIDANCE}` : "";
|
|
@@ -11866,7 +11916,10 @@ ${pollInterruptedText(absolute)}
|
|
|
11866
11916
|
try {
|
|
11867
11917
|
const response = await fetchJson(`${baseUrl}/api/poll?file=${encodeURIComponent(absolute)}${timeoutQuery}`, {
|
|
11868
11918
|
retries: 3,
|
|
11869
|
-
retryDelayMs: 500
|
|
11919
|
+
retryDelayMs: 500,
|
|
11920
|
+
onResponse: (pollResponse) => {
|
|
11921
|
+
if (pollResponse.headers.get(POLL_STATE_HEADER) === "listening") notifyHerdrPollReady();
|
|
11922
|
+
}
|
|
11870
11923
|
});
|
|
11871
11924
|
return createPollOutput({ file: absolute, response, agent: detectInvokingAgent(process.env) });
|
|
11872
11925
|
} finally {
|
|
@@ -12949,7 +13002,7 @@ function createServerSpawnOptions(logFd = null) {
|
|
|
12949
13002
|
env: { ...process.env, LAVISH_AXI_NO_OPEN: "1" }
|
|
12950
13003
|
};
|
|
12951
13004
|
}
|
|
12952
|
-
async function fetchJson(url, { retries = 0, retryDelayMs = 250 } = {}) {
|
|
13005
|
+
async function fetchJson(url, { retries = 0, retryDelayMs = 250, onResponse = null } = {}) {
|
|
12953
13006
|
let response;
|
|
12954
13007
|
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
12955
13008
|
try {
|
|
@@ -12965,6 +13018,7 @@ async function fetchJson(url, { retries = 0, retryDelayMs = 250 } = {}) {
|
|
|
12965
13018
|
if (!response.ok) {
|
|
12966
13019
|
throw new AxiError(`Lavish Editor request failed: ${response.status}`, "SERVER_ERROR");
|
|
12967
13020
|
}
|
|
13021
|
+
onResponse?.(response);
|
|
12968
13022
|
try {
|
|
12969
13023
|
return await response.json();
|
|
12970
13024
|
} catch {
|
|
@@ -13169,7 +13223,7 @@ Open or resume a Lavish Editor review session for an HTML artifact. Use --no-ope
|
|
|
13169
13223
|
`,
|
|
13170
13224
|
poll: `Usage: lavish-axi poll <html-file> [--agent-reply "..."] [--agent-reply-file <path>]
|
|
13171
13225
|
|
|
13172
|
-
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. ${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.
|
|
13226
|
+
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.
|
|
13173
13227
|
|
|
13174
13228
|
Examples:
|
|
13175
13229
|
lavish-axi poll report.html --agent-reply "Renamed the payment step."
|
package/dist/server.mjs
CHANGED
|
@@ -51,9 +51,18 @@ function chunkToString(chunk, encoding) {
|
|
|
51
51
|
|
|
52
52
|
// bin/lavish-axi-server.js
|
|
53
53
|
installServerStdioTimestamps();
|
|
54
|
+
var fatalExitScheduled = false;
|
|
55
|
+
function handleFatalServerError(kind, error) {
|
|
56
|
+
const detail = error instanceof Error ? error.stack || error.message : String(error);
|
|
57
|
+
console.error(`[lavish] ${kind}: ${detail}`);
|
|
58
|
+
if (fatalExitScheduled) return;
|
|
59
|
+
fatalExitScheduled = true;
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
setImmediate(() => process.exit(1));
|
|
62
|
+
}
|
|
63
|
+
process.on("uncaughtException", (error) => handleFatalServerError("uncaught exception", error));
|
|
54
64
|
try {
|
|
55
65
|
await import("./cli.mjs");
|
|
56
66
|
} catch (error) {
|
|
57
|
-
|
|
58
|
-
process.exit(1);
|
|
67
|
+
handleFatalServerError("server startup failed", error);
|
|
59
68
|
}
|
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.74",
|
|
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",
|