diff --git a/CHANGELOG.md b/CHANGELOG.md
index 06c21e5..82277c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,14 @@ All notable user-facing changes to **omp-spt** (the Spacetime adapter for oh-my-
> Each release below is keyed to the **adapter version of truth** (the version `spt adapter list` reports and the GitHub release tag). omp-spt starts its own version line at 0.1.0; the sections from [0.17.3] down are the inherited **claude-spt** lineage this project forked from, retained for history.
+## [0.3.8] - 2026-07-19
+
+> Requires spt-core **v0.31.0 or newer** and Oh My Pi **v16.3.15 or newer**. Update with `spt adapter update omp-spt`, then restart existing endpoints so they load the corrected listener and session identity.
+
+### Fixed
+- **Incoming messages containing quoted SPT events no longer shut the endpoint down.** The listener now preserves complete `…` text inside a peer message, while incomplete frames remain bounded and fail closed. Deferred messages also use OMP's follow-up queue so a streaming-state race cannot silently lose them.
+- **The inline OMP status now shows the complete endpoint identity.** The footer reads ` @ (/)` in cyan instead of `spt:`, matching the session and terminal-window naming introduced in v0.3.7.
+
## [0.3.7] - 2026-07-19
> Requires spt-core **v0.31.0 or newer** and Oh My Pi **v16.3.15 or newer**. Update with `spt adapter update omp-spt`, then restart existing endpoints so they load the new title behavior.
diff --git a/README.md b/README.md
index f6aa87c..0dc4777 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ release with its bundled native extension and OMP plugin skills.
## Install
-The v0.3.7 release asset supports **x86_64 Windows and x86_64 Linux only**. It
+The v0.3.8 release asset supports **x86_64 Windows and x86_64 Linux only**. It
contains Windows MSVC, Linux GNU, and static Linux musl x86_64 helpers, but no
macOS or Arm64 payload. The musl helper is a compatibility tier for OMP-capable
hosts; it is not a generic Alpine support claim. Pick a supported shell below;
diff --git a/adapter/strings/omp-spt.mjs b/adapter/strings/omp-spt.mjs
index ba7abfa..463621f 100644
--- a/adapter/strings/omp-spt.mjs
+++ b/adapter/strings/omp-spt.mjs
@@ -4,6 +4,8 @@ const ADAPTER = "omp-spt";
const BUSY_TITLE_GLYPHS = [..."⣾⣽⣻⢿⡿⣟⣯⣷"];
const IDLE_TITLE_GLYPH = "○";
const TITLE_FRAME_MS = 80;
+const CYAN = "\u001b[36m";
+const DEFAULT_FOREGROUND = "\u001b[39m";
// [impl->REQ-OMP-SESSION-TITLES]
export function endpointDisplayName(id, node, project) {
@@ -16,6 +18,10 @@ export function endpointDisplayName(id, node, project) {
: `${endpoint} @ ${nodeName}`;
}
+export function endpointInlineStatus(id, node, project) {
+ return `${CYAN}${endpointDisplayName(id, node, project)}${DEFAULT_FOREGROUND}`;
+}
+
export function decodeBody(body) {
return body
.replaceAll("
", "\n")
@@ -58,15 +64,24 @@ function parseEventTag(tag) {
return { attributes };
}
-function findValidNestedEvent(raw, bodyStart, close) {
- let candidate = raw.indexOf("= 0 && (close < 0 || candidate < close)) {
- const candidateEnd = raw.indexOf(">", candidate);
- if (candidateEnd < 0 || (close >= 0 && candidateEnd >= close)) return -1;
- if (!parseEventTag(raw.slice(candidate, candidateEnd)).error) return candidate;
- candidate = raw.indexOf("", cursor);
+ if (close < 0) return -1;
+ if (open >= 0 && open < close) {
+ const openEnd = raw.indexOf(">", open);
+ if (openEnd < 0 || openEnd >= close) return -1;
+ if (!parseEventTag(raw.slice(open, openEnd)).error) depth += 1;
+ cursor = openEnd + 1;
+ continue;
+ }
+ depth -= 1;
+ if (depth === 0) return close;
+ cursor = close + "".length;
}
- return -1;
}
export function drainEvents(raw, options = {}) {
@@ -95,15 +110,7 @@ export function drainEvents(raw, options = {}) {
rest: raw.slice(start),
};
}
- const close = raw.indexOf("", openEnd + 1);
- const nested = findValidNestedEvent(raw, openEnd + 1, close);
- if (nested >= 0) {
- return {
- error: protocolError("nested EVENT before closing the current frame"),
- events,
- rest: raw.slice(start),
- };
- }
+ const close = findEventClose(raw, openEnd + 1);
if (close < 0) {
if (raw.length - start > maxFrameChars) {
return {
@@ -1172,7 +1179,7 @@ export function createOmpSpt(overrides = {}) {
}
activated = true;
startupBriefPending = true;
- ui.setStatus("omp-spt", `spt:${id}`);
+ ui.setStatus("omp-spt", endpointInlineStatus(id, env.OMP_SPT_NODE, env.OMP_SPT_PROJECT));
// [impl->REQ-OMP-SESSION-TITLES]
pi.setSessionName(displayName());
showIdleTitle();
@@ -1639,7 +1646,7 @@ export function createOmpSpt(overrides = {}) {
item.stub = senderStub(item.from ?? "unknown");
item.submitted = true;
try {
- pi.sendUserMessage(item.stub);
+ pi.sendUserMessage(item.stub, { deliverAs: "followUp" });
} catch (error) {
item.submitted = false;
await rejectItem(item, "could not submit your message to OMP", error);
@@ -1767,7 +1774,7 @@ export function createOmpSpt(overrides = {}) {
const status = signal ? `signal ${signal}` : code;
died(new Error(`spt api listen exited ${status}`), true);
});
- ui?.setStatus("omp-spt", `spt:${id}`);
+ ui?.setStatus("omp-spt", endpointInlineStatus(id, env.OMP_SPT_NODE, env.OMP_SPT_PROJECT));
}
// [impl->REQ-OMP-NATIVE-TUI]
diff --git a/docs-site/llms-full.txt b/docs-site/llms-full.txt
index 602aef4..8e32928 100644
--- a/docs-site/llms-full.txt
+++ b/docs-site/llms-full.txt
@@ -78,7 +78,7 @@ You need [Oh My Pi 16.3.15 or newer](https://github.com/can1357/oh-my-pi/commit/
and [`spt-core`](https://sabermage.github.io/spt-releases). There is no separate plugin
installation step because the adapter release carries its OMP plugin skills.
-The v0.3.7 `omp-spt` release supports **x86_64 Windows and x86_64 Linux only**.
+The v0.3.8 `omp-spt` release supports **x86_64 Windows and x86_64 Linux only**.
Its archive contains Windows MSVC, Linux GNU, and static Linux musl x86_64
helpers, but no macOS or Arm64 payload. The musl helper is a compatibility tier
for OMP-capable hosts; it is not a generic Alpine support claim.
diff --git a/docs-site/src/quickstart.md b/docs-site/src/quickstart.md
index 3a87c1f..4ad43fc 100644
--- a/docs-site/src/quickstart.md
+++ b/docs-site/src/quickstart.md
@@ -9,7 +9,7 @@ You need [Oh My Pi 16.3.15 or newer](https://github.com/can1357/oh-my-pi/commit/
and [`spt-core`](https://sabermage.github.io/spt-releases). There is no separate plugin
installation step because the adapter release carries its OMP plugin skills.
-The v0.3.7 `omp-spt` release supports **x86_64 Windows and x86_64 Linux only**.
+The v0.3.8 `omp-spt` release supports **x86_64 Windows and x86_64 Linux only**.
Its archive contains Windows MSVC, Linux GNU, and static Linux musl x86_64
helpers, but no macOS or Arm64 payload. The musl helper is a compatibility tier
for OMP-capable hosts; it is not a generic Alpine support claim.
diff --git a/docs/PARITY.md b/docs/PARITY.md
index e14f4e9..8b5c04f 100644
--- a/docs/PARITY.md
+++ b/docs/PARITY.md
@@ -9,14 +9,14 @@
## Versioned comparison baseline
-Current baseline: `omp-spt v0.3.7` → `BigscreenVR/claude-spt-bs v0.25.1`.
+Current baseline: `omp-spt v0.3.8` → `BigscreenVR/claude-spt-bs v0.25.1`.
`omp-spt` v0.3.4 was published at 2026-07-16 10:48:22 UTC. The first repository commit
after that release, `413d6908d3a514edf93e0e53d4fb1f4b46ff7269`, landed at
2026-07-19 09:49:10 UTC and fixes the start of the work interval that produced v0.3.5.
`claude-spt-bs` v0.25.1 was the latest published sister release at that instant, published from
`BigscreenVR/claude-spt-bs` at 2026-07-19 04:55:53 UTC.
-This baseline identifies the exact sister behavior used to define v0.3.7 parity. It is not a
+This baseline identifies the exact sister behavior used to define v0.3.8 parity. It is not a
claim that later `claude-spt` capabilities are covered. Any subsequent feature-parity work must
first select and record the exact published `claude-spt` release being consulted. Every
parity-informed `omp-spt` release must update this baseline to its own version; the release
diff --git a/tests/omp-extension.mjs b/tests/omp-extension.mjs
index 5af4c92..fd9a9e0 100644
--- a/tests/omp-extension.mjs
+++ b/tests/omp-extension.mjs
@@ -3,6 +3,7 @@ import { EventEmitter } from "node:events";
import {
createOmpSpt,
endpointDisplayName,
+ endpointInlineStatus,
decodeBody,
drainEvents,
parsePeerShortforms,
@@ -108,6 +109,7 @@ function createHarness(options = {}) {
const calls = [];
const children = [];
const submitted = [];
+ const submittedDeliveries = [];
const statuses = [];
const notifications = [];
const errors = [];
@@ -206,9 +208,10 @@ function createHarness(options = {}) {
sentMessages.push({ message, delivery });
options.onSendMessage?.(message, delivery);
},
- sendUserMessage(content) {
+ sendUserMessage(content, delivery) {
submitted.push(content);
- options.onSubmit?.(content);
+ submittedDeliveries.push(delivery);
+ options.onSubmit?.(content, delivery);
},
};
const runSptCommand = async (args, input, runOptions) => {
@@ -303,6 +306,7 @@ function createHarness(options = {}) {
sessionNames,
titles,
submitted,
+ submittedDeliveries,
tools,
get shutdowns() {
return shutdowns;
@@ -335,6 +339,10 @@ async function testParsing() {
);
assert.equal(endpointDisplayName("emphasys", "HFENDULEAM"), "emphasys @ HFENDULEAM");
assert.equal(endpointDisplayName("emphasys", undefined, "omp-spt"), "emphasys");
+ assert.equal(
+ endpointInlineStatus("emphasys", "HFENDULEAM", "omp-spt"),
+ "\u001b[36memphasys @ HFENDULEAM (omp-spt/)\u001b[39m",
+ );
const partialEnvelope = 'hello
wo';
const partial = drainEvents(`noise${partialEnvelope}`);
@@ -358,12 +366,15 @@ async function testParsing() {
},
]);
- const truncatedA =
- 'truncatedvalid';
- const nested = drainEvents(truncatedA);
- assert.deepEqual(nested.events, []);
- assert.equal(nested.rest, truncatedA);
- assert.match(nested.error.message, /nested EVENT/);
+ const nestedEventBody =
+ 'quoted valid tail';
+ assert.deepEqual(drainEvents(nestedEventBody).events, [
+ {
+ from: "a",
+ body: 'quoted valid tail',
+ envelope: nestedEventBody,
+ },
+ ]);
assert.match(
drainEvents('missing sender').error.message,
/missing EVENT from/,
@@ -391,6 +402,10 @@ async function testEndpointSessionNameAndAnimatedWindowTitle() {
await harness.emit("session_start");
assert.deepEqual(harness.sessionNames, ["emphasys @ HFENDULEAM (omp-spt/)"]);
assert.deepEqual(harness.titles, ["○ emphasys @ HFENDULEAM (omp-spt/)"]);
+ assert.equal(
+ harness.statuses.at(-1).text,
+ "\u001b[36memphasys @ HFENDULEAM (omp-spt/)\u001b[39m",
+ );
await harness.emit("agent_start");
assert.equal(harness.titles.at(-1), "⣾ emphasys @ HFENDULEAM (omp-spt/)");
@@ -1060,13 +1075,13 @@ async function testProtocolCorruptionFailsClosed() {
return harness;
}
- const truncatedA =
- 'truncatedvalid';
- const nested = await failProtocol(truncatedA, /nested EVENT/);
- assert.deepEqual(
- commandCalls(nested, "send").map((call) => call.args[1]),
- [],
- "the later valid b frame must not be merged into or consumed as a",
+ await failProtocol(
+ 'truncatedvalid'.padEnd(
+ 128,
+ "x",
+ ),
+ /buffer exceeded/,
+ { listenerBufferLimit: 96 },
);
await failProtocol('missing sender', /missing EVENT from/);
await failProtocol('bad attrs', /malformed EVENT attributes/);
@@ -1631,6 +1646,11 @@ async function testActiveTurnBoundaryDeliveryAndFallback() {
[''],
"a later accepted message must preserve order and fall back to an ordinary next turn",
);
+ assert.deepEqual(
+ harness.submittedDeliveries,
+ [{ deliverAs: "followUp" }],
+ "listener delivery must survive OMP streaming-state races",
+ );
assertNoAgentManagedPoll(harness);
await harness.emit("session_shutdown");
}