@zephytiju/prism-agent-thread
v0.1.0
Published
Platform Prism agent-thread micro-UI (component id "agent-thread"): GeoVision v7 NEXUS AGENT thread — chronological user/agent bubbles with entity-reference chips and citation lines, streaming/working indicator, approval cards, and a composer — driven ent
Readme
PrismAgentThreadMicroUI
Platform Prism agent-thread micro-UI. Component id: agent-thread.
Published to npm as @zephytiju/prism-agent-thread.
The agent-thread renders the GeoVision v7 NEXUS AGENT panel: the header (title / subtitle from
configuration, locale-derived defaults NEXUS AGENT / AUTONOMOUS INTELLIGENCE AGENT in en,
NEXUS 智能体 / 自主智能体 in zh-CN) with its collapse
toggle, the chronological thread (user bubbles with context mini-chips and mono UTC stamps,
agent analysis bubbles with bullets, entity-reference chips and SOURCES citation lines, system
result lines), the streaming delta ghost with the working indicator, approval cards
(▲ PROPOSED ACTION / APPROVAL REQUIRED with ✓ APPROVE / REJECT and their pending state), the
composer (context chips + mono input + send), the empty greeting, and the error + retry banner.
The thread auto-scrolls while the operator follows the tail and offers a ↓ LATEST jump when
they scroll up. Composed applications (for example Guanlan) consume it as-is; the component is
platform-owned.
Design D4 — the agent runtime comes DIRECT, not Lattice-mediated
Per the design record, agent conversation / checkpoints / approvals come from the in-house
Nous agent runtime DIRECTLY — this component makes no Lattice calls (no transport, no
interface clients). Instead it defines a thin AgentSessionClient interface in
src/types.ts, modeled on the Nous wire contracts
(agent-run-event.v1.schema.json, checkpoint.v1.schema.json, common.v1.schema.json of the
JuntaiNousAgentFramework contracts), and the HOST supplies the implementation through the
required session prop — the host never puts runtime credentials inside a component.
The AgentSessionClient contract
interface AgentSessionClient {
/** Latest checkpoint turns (checkpoint.v1 messages[]) rendered on mount. Optional. */
readonly history?: () => Promise<readonly AgentTurn[]>;
/** Streams run events; returns the unsubscribe. The only runtime inlet. */
readonly subscribe: (listener: (event: AgentStreamEvent) => void) => () => void;
/** Sends one operator message into the run. */
readonly send: (message: AgentOutboundMessage) => Promise<void>;
/** Submits the human decision on a pending approval request. */
readonly submitApproval: (requestId: string, decision: "approve" | "reject") => Promise<void>;
}Wire-shape correspondence (kept deliberately thin — only what the UI renders):
AgentTurn← acheckpoint.v1messages[]entry (role + content) with theagent-run-event.v1observed_atstamp. Roles:user/agent/system. Content carriestext, optionalbullets, optionalentityRefs(IEntitySummary-shaped items: id / type / label / optional lat-lon, plus the optional confidence percent the chip renders), optionalcitations(sourceId + label → the provenance line), and optionalcontextRefs(the mini-chips under a user bubble).AgentStreamEvent← theagent-run-event.v1envelope —sequence(monotonic ≥ 1),runId,phase(resolve | discover | reason | act | observe | verify | interrupted | completed),observedAt,privacyClass(metadata | redacted | content-authorized) — narrowed to the kinds this thread renders, each with a typedpayload:turn-appended— one newAgentTurnlanded (deduped byturnId);delta— streaming text appended to the in-flight agent ghost bubble;checkpoint— authoritative turn list +checkpointId/iteration/pendingApproval(replaces the thread; a non-nullpendingApprovalpauses the run);approval-request— the checkpoint's pending decision / interrupt (requestIdis theinterrupt_id-shaped identifier, withtitle/detail/observedAt);run-error—common.v1stableError(code/category/retryable/message);run-completed— ends the working indicator.
AgentOutboundMessage—{ text, contextRefs? }from the composer.
Runtime-side notes: the history snapshot is only authoritative until the first streamed event
arrives (events carry their own sequence), so a late history() resolution never clobbers a
fresher stream. Approval decisions return a promise; the card shows the pending state
(SUBMITTING…, buttons disabled) until it settles and then the outcome line.
How the host binds the real Nous runtime
The composition (host application) implements AgentSessionClient over the Nous hosted
runtime — e.g. subscribe maps the runtime's run-event stream onto AgentStreamEvent
(keeping the wire envelope fields), send forwards the composer message, submitApproval
resolves the interrupt, and history maps the latest EngineCheckpoint.messages. The client
instance carries the runtime credentials and connection; the component only ever sees the
interface. The real binding lands with the Nous hosted runtime integration — until then the
demo uses a scripted, INPUT-DRIVEN mock client implementing the interface from the same
contract schemas (createMockAgentSession(locale) in src/demo.tsx):
- the scripted OPENING run (user turn, deltas, analysis turn with entity refs + citations, approval request) is localized to the instance locale, so the zh-CN instance opens with a full CN conversation (CN user turn, CN analysis, CN approval card);
- every AI response after the opening is DETERMINED BY USER INPUT:
sendechoes the sent message as a user turn and answers in the LANGUAGE OF THE SENT MESSAGE —containsCjk(a plain code-point walk, no RegExp) routes any message containing CJK to the CN script and everything else to EN, independent of the UI locale (send 中文 in the zh instance → CN user bubble + CN agent reply已收到「…」; send EN anywhere → EN replyOn “…”:); - include "fail"/“失败” in a message to exercise the error + retry path; approval outcomes answer in the session locale;
- an optional
{ delayScale }compresses the stream delays (tests pass 0).
Keep the supplied session instance referentially stable — the subscription follows the prop
identity.
Configuration keys
| Prop | Meaning |
| --- | --- |
| session | Required. Host-supplied AgentSessionClient (see above) |
| locale | UI locale for the component-fixed strings: "en" \| "zh-CN" (default "en") — see i18n |
| title | Panel header title — composition-authored; when omitted, the locale-derived default (NEXUS AGENT / NEXUS 智能体) |
| subtitle | Panel header mono subtitle — composition-authored; when omitted, the locale-derived default (AUTONOMOUS INTELLIGENCE AGENT / 自主智能体) |
| agentName | Agent attribution on every agent bubble — composition-authored; when omitted, the locale-derived default (NEXUS AGENT / NEXUS 智能体) |
| tagPalette | Entity type → semantic color token for the entity-reference chip dots (e.g. { military: "threat" }) |
| composerContextRefs | Static context chips rendered above the composer (also attached to every outbound message) |
Channel contract
| Direction | Kind | Id | Payload |
| --- | --- | --- | --- |
| publishes | state | search-results.selected-entity | AgentThreadSelectedEntity — an EntityReference pinned to IEntitySummary: { id, type, label, lat?, lon?, ontologyInterface: "IEntitySummary" } — the SAME established channel and payload the search-results micro-UI publishes |
This is the component's only channel binding — the established selection channel is reused
with a string-literal id at the call-site (build-time channel-graph scanner) through a
setter-only usePrismStateSetter, so the component never re-renders from its own
publications. Clicking an entity chip publishes the selection exactly like clicking a
search-results entity card (the thread-only confidence field is never leaked onto the
channel). There are no audit events (agent browsing is not audit-worthy here) and no other
events or channels.
Internationalization (i18n)
All component-authored text supports the language setting — the component ships en and
zh-CN locale bundles (src/locales/en.json / src/locales/zh-CN.json, exact key parity,
test-enforced) and every component-fixed UI string resolves from them: the locale-derived
header/bubble defaults, the empty greeting, the agent bubble tag, the working label, the
approval card chrome, the composer placeholder, send / retry / jump labels, the error banner
label and its fallback messages, and every aria-label (including the {label}-templated
entity-chip selection label). The component renders no hardcoded copy; en keeps the
uppercase mono aesthetic, zh-CN follows the repo's existing translation style.
{
"agent-thread": {
"defaultTitle": "NEXUS AGENT",
"defaultSubtitle": "AUTONOMOUS INTELLIGENCE AGENT",
"defaultAgentName": "NEXUS AGENT",
"panelAriaLabel": "Agent thread",
"composerAriaLabel": "Agent message",
"selectEntityLabel": "Select entity {label}",
"emptyGreetingTitle": "NO ACTIVE SESSION",
"emptyGreetingBody": "Task the Nexus agent to begin a monitoring run.",
"bubbleTag": "ANALYSIS",
"workingLabel": "AGENT WORKING",
"approvalTitle": "▲ PROPOSED ACTION",
"approvalBadge": "APPROVAL REQUIRED",
"approveLabel": "✓ APPROVE",
"rejectLabel": "REJECT",
"approvedLabel": "✓ APPROVED",
"rejectedLabel": "REJECTED",
"approvalSubmitting": "SUBMITTING…",
"sourcesLabel": "SOURCES",
"composerPlaceholder": "Task the agent…",
"sendLabel": "Send",
"retryLabel": "RETRY",
"errorLabel": "RUN ERROR",
"jumpToLatest": "↓ LATEST",
"collapseLabel": "Collapse panel",
"userTag": "OPERATOR",
"sendFailedMessage": "Agent send failed",
"approvalFailedMessage": "Approval submit failed"
}
}locale?: "en" | "zh-CN"prop (default"en") selects the string table per instance.- Composition-authored strings are localized by the composer; component-fixed strings live in
the locale JSONs.
title/subtitle/agentNamemay be passed per host locale; when omitted they fall back to the locale-deriveddefaultTitle/defaultSubtitle/defaultAgentName(public API unchanged). - Backend-derived text is never localized: agent/user/system message content, entity names/ids/confidences in the chips, citation labels, approval titles/details from the session, timestamps and counts render verbatim from the runtime data. Only the component-authored chrome (including the runtime-error banner's label and non-Error fallback copy — runtime-supplied error messages surface verbatim) resolves from the bundles. The demo mock localizes its scripted turns per session locale and answers sends in the sent message's language (see below).
- Locale bundles are namespaced under the component id (
"agent-thread") so a composer can deep-merge every component's bundle into ONE UI language bundle without collisions:
import { locales as agentThreadLocales } from "@zephytiju/prism-agent-thread";
// agentThreadLocales.en -> { "agent-thread": { … } }
const uiBundle = deepMerge(hostStrings, agentThreadLocales.en, queryBoxLocales.en);The parsed bundles are exported from the package entry (locales, en, zhCN,
stringsForLocale), and the raw JSONs are also served by the ./locales/* exports subpath
(e.g. @zephytiju/prism-agent-thread/locales/zh-CN.json); files ships both dist and
locales.
Theme
No palette is hardcoded. Every color resolves to SEMANTIC theme tokens (ok, accent,
threat, warn, signal, card, card-dark, input, border, line, text, muted,
deep, panel) consumed as CSS variables, plus --mantine-font-family-monospace for the
mono typography — the palette is supplied entirely by the host's MantineProvider. The local
demo ships TWO themes, both defined in src/demo.tsx: geovisionTheme (dark), mapping each
semantic token onto the exact :root variables of the CONFIRMED design, and the contrasting
latticeLightTheme (light), mapping the SAME semantic token keys onto a different palette.
Source layout
src/ is strictly two parts:
- Component source (what the package compiles):
AgentThread.tsx,types.ts(theAgentSessionClientcontract),index.ts(public entry), andsrc/locales/(en.json,zh-CN.json,index.ts— the i18n string bundles and their resolver). - Demo: exactly ONE file,
src/demo.tsx— the two host themes (GeoVision v7 + Lattice Light), the scripted, input-driven mockAgentSessionClient(createMockAgentSession(locale), with the CJK-detection reply routercontainsCjk/replyLanguageFor) and all demo test data (tag palette, composer context chips, the localized scripts per locale), and the demo page rendering TWOAgentThreadinstances side by side behind a global EN | 中文 language switcher (plus per-instance switches) with a selected-entity monitor.
The npm package ships dist (compiled component + type declarations + locale JSONs) and the
top-level locales/ directory (the raw JSON bundles, served by the ./locales/* exports
subpath); no demo code is published. scripts/copy-locales.mjs copies the JSON bundles into
both locations during npm run build.
Local development
npm install
npm run typecheck
npm test
npm run dev
npm run shot-demonpm install pulls the platform peer (@zephytiju/prism-react) from the npm registry, along
with the host-side peer dependencies (react, react-dom, @mantine/core;
@zephytiju/lattice-common-interfaces remains a peer only for the shared
IEntitySummary-shaped EntityResultItem type used by the channel payload — it is never
called). When consuming the published package, install it directly
(npm install @zephytiju/prism-agent-thread) and provide those peer dependencies in the host
application.
The demo (npm run dev, entry src/demo.tsx) renders TWO AgentThread instances side by
side, each inside its own MantineProvider with a different theme (GeoVision v7 dark left,
Lattice Light right) and its own mock session, behind an EN | 中文 segmented control — the
GLOBAL switch sets the locale prop of BOTH instances at once, and each instance carries its
own per-instance control so the two hosts can render DIFFERING locales simultaneously. No
title/subtitle is passed, so the headers default from the per-locale bundle. Each mock opens
in its instance locale (the zh-CN host starts with a CN conversation and a CN approval card);
sends from the composer are answered in the LANGUAGE OF THE SENT MESSAGE — send 中文 to get a
CN user bubble + CN agent reply, send EN to get an EN reply, in either host (include
"fail"/“失败” to exercise the error + retry path). Entity chips publish on the ESTABLISHED
GLOBAL search-results.selected-entity channel, so the shared monitor below updates from
BOTH hosts. npm run shot-demo boots the vite dev server, drives both instances in headless
Chrome (LEFT en, RIGHT zh-CN, waits for both approval cards, clicks an entity chip so the
monitor shows the published payload, then sends a CN message in the zh composer — capturing
the zh instance mid-CN conversation — and an EN message in the left composer) and captures
both instances plus the monitor to /tmp/guanlan-review/demo-agent-thread-v2.png.
Design record
https://qcnwge0wy4s0.feishu.cn/wiki/K40nwA5TZiUE7Nk5hK6cE0W8nTe — §7 (channel contracts) and
D4 (the agent runtime is reached directly through the host-supplied session client, never
Lattice-mediated). Visual reference: CONFIRMED GeoVision v7 design
(nexus-geovision-v7-standalone.html, .agent-panel region).
