Replies: 3 comments
|
Confirmed against The exact-string comparison is duplicated across the codebase:
The consolidation opportunity is the real value here. Sites 2 and 3 carry
The one-line One caution on your fire-and-forget framing: you scoped this as "only the master / v0.1.3-alpha.1 line carries it, so the audience is small." That's accurate for the client exposure today, but sites 2/3/4 already ship in Given upstream external PRs aren't accepted at the moment, this is a solid reference-diff candidate — happy to help draft the 4-site patch + helper extraction if the maintainers open a path. |
|
Thanks for the detailed verification — all four sites check out on my side too, and I've folded them into one apply-clean reference diff below. Confirmed against
One nuance on the consolidation direction: site 4 cannot simply import the canonical helper. Reference diff (applies cleanly onto diff --git a/packages/code-runtime/code-runtime-worker-thread/src/worker-json.ts b/packages/code-runtime/code-runtime-worker-thread/src/worker-json.ts
index 40dcc2e1e8..52ab4a3079 100644
--- a/packages/code-runtime/code-runtime-worker-thread/src/worker-json.ts
+++ b/packages/code-runtime/code-runtime-worker-thread/src/worker-json.ts
@@ -87,7 +87,7 @@ function hasIntrinsicConstructor(prototype: object, name: 'Array' | 'Object'): b
try {
return constructor.name === name
&& constructor.prototype === prototype
- && intrinsicReflectApply(intrinsicFunctionToString, constructor, []) === `function ${name}() { [native code] }`
+ && String(intrinsicReflectApply(intrinsicFunctionToString, constructor, [])).replace(/\s+/gu, ' ') === `function ${name}() { [native code] }`
} catch {
return false
}
diff --git a/packages/core/tools/src/json-schema.ts b/packages/core/tools/src/json-schema.ts
index c064fb294c..f2d3e113a1 100644
--- a/packages/core/tools/src/json-schema.ts
+++ b/packages/core/tools/src/json-schema.ts
@@ -95,7 +95,7 @@ function hasIntrinsicConstructor(prototype: object, name: 'Array' | 'Object'): b
try {
return constructor.name === name
&& constructor.prototype === prototype
- && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`
+ && Function.prototype.toString.call(constructor).replace(/\s+/gu, ' ') === `function ${name}() { [native code] }`
} catch {
return false
}
diff --git a/packages/extensions/cordis-host-runner/src/guard.ts b/packages/extensions/cordis-host-runner/src/guard.ts
index a70e47ef55..98264e10c8 100644
--- a/packages/extensions/cordis-host-runner/src/guard.ts
+++ b/packages/extensions/cordis-host-runner/src/guard.ts
@@ -47,7 +47,7 @@ function hasIntrinsicConstructor(prototype: object, name: 'Array' | 'Object'): b
try {
return constructor.name === name
&& constructor.prototype === prototype
- && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`
+ && Function.prototype.toString.call(constructor).replace(/\s+/gu, ' ') === `function ${name}() { [native code] }`
} catch {
return false
}
diff --git a/packages/util/values/src/index.ts b/packages/util/values/src/index.ts
index 78a2cbb493..bf27288f74 100644
--- a/packages/util/values/src/index.ts
+++ b/packages/util/values/src/index.ts
@@ -22,7 +22,9 @@ function hasIntrinsicConstructor(prototype: object, name: 'Array' | 'Object'): b
try {
return constructor.name === name
&& constructor.prototype === prototype
- && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`
+ // Engine-tolerant: Firefox formats native source across lines
+ // ("function Object() {\n [native code]\n}") while V8 keeps one line.
+ && Function.prototype.toString.call(constructor).replace(/\s+/gu, ' ') === `function ${name}() { [native code] }`
} catch {
return false
} |
|
Independent reproduction from a second environment (Windows 11 + Firefox, local build of dsh-v0.1.3-alpha.1). Same symptoms, same error chain, same root cause — I can confirm the exact-string comparison in hasIntrinsicConstructor fails on SpiderMonkey's multiline native-function formatting, which makes snapshotJsonValue reject every plain object. We also fixed and verified all four sites in one pass (util-values + the three latent host-side copies, including the captured-intrinsic variant in worker-json.ts with the String(...) wrapper). After a full rebuild, Firefox loads history, renders replies, and pages normally; Chromium shows no regression. The whitespace-normalization approach and the reference diff here match what we applied locally, which is a good sign. A Chinese-language write-up (marked as a duplicate of this thread) is at #5709 for anyone who prefers it. Thanks for the thorough report — hopefully the maintainers can land a fix (or open a PR path) before 0.1.3 ships to npm. I and Openclaw fixed this problem without noticing this thread and wasted 2 hours and 2$ on this shit. We should check Github first😭 |
Uh oh!
There was an error while loading. Please reload this page.
Bug description
On the web UI, opening a session whose trajectory contains assistant raw chunk records
(any session after a normal agent turn) never finishes loading in Firefox-engine
browsers (Firefox, Zen). The history pane stays at "Loading history…" forever; the
client console logs a subscriber failure caused by the assistant-stream chunk validator:
The same server, the same session, and the same profile work fine in Chromium-family
browsers (Chrome, Brave, Edge). The Node-based TUI/SDK are unaffected.
Environment
master@d347e70390(thev0.1.3-alpha.1pre-release build); reproducedon a zero-plugin core-only profile as well, so plugins are not involved.
Chromium-family — fine.
unnoticed").
Steps to reproduce
master(d347e70390) or thev0.1.3-alpha.1build.stream data (i.e. any real session created by this build).
TypeErrorabove. Chromium on the same URL loads the same session normally.Root cause
packages/util/values/src/index.ts,hasIntrinsicConstructor()(lines 18–31), decideswhether a prototype is a realm-owned intrinsic by comparing the exact string form of
its constructor:
This assumes the V8 formatting of native functions, which is single-line. Firefox
formats natives across lines:
Measured live for this report (headless Firefox 1xx and Chromium, same machine):
The ECMAScript spec leaves the built-in (native) function string
implementation-defined — MDN explicitly warns its content "may be
implementation-defined" — so formatting is not guaranteed to match V8 on any
engine (MDN,
es-discuss).
SpiderMonkey's multiline output is confirmed above; Safari/WebKit was not
available for measurement, but the proposed fix is engine-agnostic (whitespace
normalization), so any engine that differs from V8 only in whitespace is covered
by the same change.
Consequently, on Firefox-engine browsers every plain object fails
isIntrinsicObjectPrototype(),snapshotJsonValue()returnsundefinedfor ordinaryJSON data, and the client-side assistant-stream validation (raw chunk records are
required to be lossless JSON) throws — so history rendering aborts before the first
message appears.
History: the exact-string comparison was consolidated into the new
dsh-util-valuespackage on 2026-08-30 (6c53fe6e2a,"refactor(values): make shared primitives duplicate-install safe"). It became
user-visible when the client-side chunk validator landed on 2026-09-02
(
f99b06eaed, "feat(session)!: embed assistant streams in format v2", 0.1.3 material).Why it went unnoticed so far
The affected client code has not shipped in any npm release yet. I checked the
published client bundles (
dsh-client-ui-trajectory,dsh-client-connection,dsh-api-session-controller,dsh-client-ui-chat) in0.1.2-alpha.5and0.1.2-rc.1(npmlatest): they contain neither the chunk validator nor theintrinsic check. Only the
masterline / the GitHub pre-releasev0.1.3-alpha.1(2026-09-04) carries it, so the affected audience is small. Once 0.1.3 reaches npm,
every Firefox-engine web-UI user will hit this. (Host-side copies of the same
pattern exist in
packages/core/tools/src/json-schema.ts,packages/extensions/ cordis-host-runner/src/guard.tsandpackages/code-runtime/.../src/worker-json.ts;they only run under Node/V8 today, so they are latent, not breaking.)
Suggested fix (one line)
Normalize whitespace before comparing; this makes Firefox behave exactly like V8 —
genuine FF-formatted natives now pass, and nothing is accepted that was not already
accepted under V8 formatting (the check is a duplicate-install/cross-realm heuristic,
not a security boundary; whitespace-collapsed lookalikes were already equal under V8):
Ready-to-apply patch (source only, applies cleanly onto
d347e70390; the fullpatch file is available on request) — inline below. Happy to open a PR through
whatever process you prefer (CONTRIBUTING.md says external PRs are not accepted
at the moment).
Verification performed
returns
"function Object() {\n [native code]\n}"— exact single-lineequality
false, equality after/\s+/gunormalizationtrue; Chromiumreturns the single-line form with exact equality
true.(MDN Function.prototype.toString);
engine formatting differences are a known es-discuss topic
(forward-incompatible Function.prototype.toString requirement).
masterserver: deterministic failure onsession open (attempt 0) with the quoted
TypeError; the same run under Chromiumrenders history cleanly.
same sessions normally; a full-snapshot validation that previously aborted now
passes.
it covers any engine whose native-string formatting differs from V8 in whitespace.
Discussion search (firefox, "Loading history", "lossless JSON", engine names)
return nothing matching this signature. Related-but-different threads:
#4513 /
#1550 /
#3408 (large or
corrupt sessions) and
#4919
(Firefox WebSocket mixed-content settings, unrelated).
中文摘要
Web UI 在 Firefox/Zen 等引擎上打开含 assistant 流数据的会话时永远停在
“Loading history…”。根因在
packages/util/values/src/index.ts的hasIntrinsicConstructor:它用Function.prototype.toString的精确字符串判断原生构造器,只适配了 V8 的单行格式;Firefox 会输出多行
(
function Object() {\n [native code]\n}),导致所有普通对象被判为非法,客户端 chunk 校验抛
TypeError: Assistant stream raw chunk must be a lossless JSON object。该代码 2026-08-30 引入 (6c53fe6e2a),2026-09-02 客户端校验(
f99b06eaed) 使其可见;目前只存在于 master / v0.1.3-alpha.1(npm 尚未发布),0.1.3 发布后所有 Firefox 引擎用户都会受影响。修复:比较前归一化空白
(
.replace(/\s+/gu, ' ')),使 Firefox 与 V8 行为一致。All reactions