You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Bug: one plugin conversationEvents contract violation freezes the entire web UI (assembler has no error isolation)
#2118
Environment: dsh 0.1.0-rc.6 (npm, macOS arm64), web profile, deepseek-v4-pro / reasoningEffort high
Summary
A third-party client plugin whose conversationEvents definition violates the location-data contract (e.g. buildLocationData returning a key that differs from the definition's kind) aborts the whole conversation projection. Because replaceWindow → flush → replaceLocationData has no error boundary around plugin handler invocation, every session then stays on "Loading history…" forever. The only trace is an exception in the browser console — the UI never reaches an error state.
Reproduction
Minimal client-half definition (excerpt of a real plugin pattern):
constdefinition={kind: "my-plugin-artifacts",// contract: the location-data key must equal this stringmatch: (event)=>{/* turn/start | tool/result -> { id, role } */},start: (_ctx,match)=>({turn: match.event.data.turn,items: []}),update: (context,match)=>context.state,buildLocationData: (context,scope)=>scope==="turn"
? {kind: "turn",turn: context.state.turn,key: "myPluginArtifacts",value: {}}// <-- key !== kind
: null,}
Register such a definition on a web profile and open any session (even a brand-new 1-turn one).
The chat view stays on the loading backdrop indefinitely; the Trajectory tab behaves the same.
Browser console:
Error: conversation Definition "my-plugin-artifacts" published Location data key "myPluginArtifacts"; expected its owned kind
The throw sites are in packages/client/runtime/src/client/sessions/conversation-assembler.ts (buildLocationData, and similarly buildNode for unstable keys) — still throw on current master.
Why this matters in practice
Not hypothetical: two independently written community plugins (an image-chip viewer and a "runbook" produced-files view — the second literally copied the first one's pattern, bug included) shipped this exact violation, and each time the entire web UI looked broken ("history never loads") even though the official packages were fine. The key === kind rule is easy to get wrong because nothing in the type signature enforces it, and the failure mode is total UI loss rather than "one plugin's widget is missing" — which also makes it very hard to attribute to the actual offender.
Suggestion
Treat a definition's contract violation as that definition's failure: skip its node / location-data contribution, console.error loudly (keeping the message plugin authors need to see), and let the rest of the projection continue. This matches the fail-soft philosophy already used elsewhere in the codebase (e.g. projection blocks in session.list are documented as "degraded, never broken").
We have been running this degrading variant locally on 0.1.0-rc.6 (throw → skip + console.error in buildNode / buildLocationData): sessions load normally, and an offending plugin simply doesn't render while its error is clearly visible in the console.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Environment: dsh 0.1.0-rc.6 (npm, macOS arm64), web profile, deepseek-v4-pro / reasoningEffort high
Summary
A third-party client plugin whose
conversationEventsdefinition violates the location-data contract (e.g.buildLocationDatareturning akeythat differs from the definition'skind) aborts the whole conversation projection. BecausereplaceWindow→flush→replaceLocationDatahas no error boundary around plugin handler invocation, every session then stays on "Loading history…" forever. The only trace is an exception in the browser console — the UI never reaches an error state.Reproduction
Minimal client-half definition (excerpt of a real plugin pattern):
The throw sites are in
packages/client/runtime/src/client/sessions/conversation-assembler.ts(buildLocationData, and similarlybuildNodefor unstable keys) — stillthrowon current master.Why this matters in practice
Not hypothetical: two independently written community plugins (an image-chip viewer and a "runbook" produced-files view — the second literally copied the first one's pattern, bug included) shipped this exact violation, and each time the entire web UI looked broken ("history never loads") even though the official packages were fine. The
key === kindrule is easy to get wrong because nothing in the type signature enforces it, and the failure mode is total UI loss rather than "one plugin's widget is missing" — which also makes it very hard to attribute to the actual offender.Suggestion
Treat a definition's contract violation as that definition's failure: skip its node / location-data contribution,
console.errorloudly (keeping the message plugin authors need to see), and let the rest of the projection continue. This matches the fail-soft philosophy already used elsewhere in the codebase (e.g. projection blocks insession.listare documented as "degraded, never broken").We have been running this degrading variant locally on 0.1.0-rc.6 (
throw→ skip +console.errorinbuildNode/buildLocationData): sessions load normally, and an offending plugin simply doesn't render while its error is clearly visible in the console.All reactions