chiral-pulse 1.2.7 → 1.3.0

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 CHANGED
@@ -61,12 +61,12 @@ pnpm typecheck # tsc --noEmit
61
61
 
62
62
  ## 发布
63
63
 
64
- 打 tag(`v*.*.*`)→ GitHub Actions 自动构建、发布 npm 并创建 GitHub Release。本仓库已打上 [`dsh-plugin`](https://github.com/topics/dsh-plugin) 话题,可在同话题下被发现。
64
+ 打 tag(`v*.*.*`)→ GitHub Actions 自动构建、发布 npm 并创建 GitHub Release
65
65
 
66
66
  ## 社区与支持
67
67
 
68
68
  - 反馈与 bug 报告:[GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions)。
69
- - 加入 DeepSeek Harness 企微群:扫码添加企微小助手,填写入群问卷后小助手会邀请你入群。
69
+ - 本仓库已打上 [`dsh-plugin`](https://github.com/topics/dsh-plugin) 话题,可在同话题下被发现。
70
70
 
71
71
  ## License
72
72
 
package/lib/client.js CHANGED
@@ -108,19 +108,19 @@ window.__ModuleLoader__.load({
108
108
  }
109
109
  /**
110
110
  * The CHIRAL PULSE dock entry.
111
- * @param props - runtime seat (useSession, useProjection) plus the locale seat.
111
+ * @param props - Session lifecycle, Chat, projection hooks, and locale seat.
112
112
  * @returns the monitor strip.
113
113
  */
114
- function HeartLine({ useSession, useProjection, t }) {
114
+ function HeartLine({ useSession, useChat, useProjection, t }) {
115
115
  const stats = useProjection("sessionStats");
116
116
  const live = {
117
- partial: useSession((s) => s.partial !== null),
118
- partialText: useSession((s) => s.partial === null ? "" : streamingTail(s.partial.blocks)),
119
- toolName: useSession((s) => s.runningCalls[0]?.name ?? null),
117
+ partial: useChat((s) => s.legacy.partial !== null),
118
+ partialText: useChat((s) => s.legacy.partial === null ? "" : streamingTail(s.legacy.partial.blocks)),
119
+ toolName: useChat((s) => s.legacy.runningCalls[0]?.name ?? null),
120
120
  running: useSession((s) => s.running),
121
121
  error: useSession((s) => s.lastAgentError),
122
- retrying: useSession((s) => {
123
- const nodes = s.chat.legacy.nodes;
122
+ retrying: useChat((s) => {
123
+ const nodes = s.legacy.nodes;
124
124
  for (let i = nodes.length - 1; i >= 0; i -= 1) {
125
125
  const n = nodes[i];
126
126
  if (n.kind === "model-retry") return n.retryState === "scheduled" && n.time > Date.now() - 12e4;
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../src/client/ecg.ts","../src/client/HeartLine.tsx","../src/client/locales.ts","../src/client/style.ts","../src/client/index.ts"],"sourcesContent":["/**\n * CHIRAL PULSE — ECG waveform synthesis.\n *\n * A cardiac cycle is a pure function of beat phase in [0,1): the classic\n * P-QRS-T complex as a sum of wrapped gaussian bumps. The monitor line is a\n * scrolling window over the time axis: the right edge shows the current\n * instant, the window spans `cycles` beats of history. BPM is the phase\n * clock speed, so the whole rhythm accelerates and slows with activity.\n */\n\n/** One wrapped gaussian bump: peak at `center` with `width`, amplitude `amp`. */\nfunction bump(phase: number, center: number, width: number, amp: number): number {\n let d = phase - center\n d -= Math.round(d)\n return amp * Math.exp(-(d * d) / (2 * width * width))\n}\n\n/**\n * Sample one cardiac cycle at beat phase in [0,1). Output range ≈ [-0.35, 1].\n * @param phase - beat phase, any real value (wrapping is internal).\n * @returns the waveform amplitude at that phase.\n */\nexport function ecgValue(phase: number): number {\n return (\n bump(phase, 0.14, 0.030, 0.16) // P wave\n - bump(phase, 0.30, 0.011, 0.26) // Q dip\n + bump(phase, 0.335, 0.016, 1.0) // R spike (wide enough to survive sampling)\n - bump(phase, 0.375, 0.011, 0.34) // S dip\n + bump(phase, 0.52, 0.048, 0.26) // T wave\n + bump(phase, 0.80, 0.012, 0.05) // U ripple\n )\n}\n","/**\r\n * HeartLine — the CHIRAL PULSE monitor strip, docked above the composer\r\n * (`conversation.input.dock`). A 26px \"monitor paper feed\": the scrolling\r\n * ECG waveform is the hero, flanked by the BPM read and the status word.\r\n * No duplicated figures — StatsLine already shows turns/tokens.\r\n *\r\n * The pulse is LIVE, not decorative:\r\n * - `partial` non-null → the model is thinking/generating → +38 BPM\r\n * - `runningCalls` non-empty → a tool is executing → +52 BPM\r\n * - `running` (session turn in flight) → +10 BPM\r\n * - otherwise the 10s step-window activity rate sets the base (~42 idle)\r\n * The BPM target is smoothed with a lerp; the paper speed stays FIXED and\r\n * only the beat density changes — hospital monitor semantics.\r\n *\r\n * Rendering: a single <canvas> redrawn per rAF at full frame rate. Fixed\r\n * memory (one canvas the size of the strip), no DOM attribute churn, no\r\n * string building — the trace is ~width straight segments per frame, which\r\n * is far cheaper than SVG polyline swaps and cannot stutter from throttling.\r\n */\r\nimport { useEffect, useRef, useState } from 'react'\r\nimport type {\r\n PropsLocale, PropsRuntime,\r\n} from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { SessionProjectionMap } from '@deepseek-ai/dsh-client-runtime/client'\r\n// Type-only: merges the sessionStats key into SessionProjectionMap.\r\nimport type {} from '@deepseek-ai/dsh-session-stats/client'\r\nimport { ecgValue } from './ecg.ts'\r\nimport type { ChiralKey } from './locales.ts'\r\nimport { NS } from './locales.ts'\r\n\r\n/** Full props: the input-dock runtime seat plus the locale seat. */\r\nexport type HeartLineProps = PropsRuntime<'conversation.input.dock'> & PropsLocale<typeof NS>\r\n\r\n/** Monitor view height, CSS px. */\r\nconst ECG_HEIGHT = 22\r\n/**\r\n * FIXED paper speed in px/second — the real hospital-monitor invariant.\r\n * The trace scrolls at this absolute rate no matter the strip width; the\r\n * width only decides how much history fits on screen. A rate change (42→90)\r\n * therefore only densifies the beats — it never speeds the paper up, and\r\n * resizing the window cannot make the trace run faster either.\r\n */\r\nconst PAPER_SPEED_PX_PER_SECOND = 30\r\n/** Activity window for the step-rate base, ms. */\r\nconst ACTIVITY_WINDOW_MS = 10_000\r\n/** Rotating status lines (locale keys), one every STATUS_ROTATE_S ticks. */\r\nconst STATUS_KEYS: readonly ChiralKey[] = [\r\n 'status.stable', 'status.bonded', 'status.chiral', 'status.doom',\r\n 'status.keep', 'status.voidout', 'status.odradek',\r\n]\r\nconst STATUS_ROTATE_S = 4\r\n/** BPM boost while the model is streaming a partial (thinking/generating). */\r\nconst BOOST_THINKING = 38\r\n/** BPM boost while a tool call is running. */\r\nconst BOOST_TOOL = 52\r\n/** BPM boost while the session turn is simply in flight. */\r\nconst BOOST_RUNNING = 10\r\n/** BPM floor (a resting BB) and ceiling. */\r\nconst BPM_FLOOR = 42\r\nconst BPM_CEIL = 150\r\n/**\r\n * How fast the displayed heart rate ramps toward its target, in BPM/second.\r\n * A hospital monitor updates its HR figure on a ~2-3s rolling average and\r\n * the trace follows gradually — the rate change reads as a slow ramp, not a\r\n * snap: 42 → 90 takes (90-42)/6 = 8 seconds of visible densification.\r\n */\r\nconst BPM_RAMP_PER_SECOND = 6\r\n\r\n/** Trace color by activity mode: idle amber, thinking cyan, tool orange, run warm. */\r\nconst MODE_COLOR = {\r\n idle: '#ffb454',\r\n think: '#6fdbe2',\r\n tool: '#ff7a4d',\r\n run: '#ffc46b',\r\n flat: '#c0483c',\r\n} as const\r\ntype Mode = keyof typeof MODE_COLOR\r\n\r\n/** One activity sample: (time, steps) at a projection update. */\r\ninterface StepSample {\r\n t: number\r\n steps: number\r\n}\r\n\r\n/** Tail of the model's in-flight output: last non-empty text/reasoning block, whitespace-flattened. */\r\nfunction streamingTail(blocks: readonly { kind: string; text?: string }[]): string {\r\n for (let i = blocks.length - 1; i >= 0; i -= 1) {\r\n const block = blocks[i]\r\n const text = block.text\r\n if (text !== undefined && text.trim() !== '') {\r\n return text.replace(/\\s+/g, ' ').trim()\r\n }\r\n }\r\n return ''\r\n}\r\n\r\n/**\r\n * The CHIRAL PULSE dock entry.\r\n * @param props - runtime seat (useSession, useProjection) plus the locale seat.\r\n * @returns the monitor strip.\r\n */\r\nexport function HeartLine({ useSession, useProjection, t }: HeartLineProps) {\r\n const stats = useProjection('sessionStats') as SessionProjectionMap['sessionStats'] | undefined\r\n // One primitive-returning selector per signal: each returns a stable value\r\n // (boolean / string / null), so the component only re-renders when that\r\n // signal actually changes — a single object selector re-rendered on every\r\n // snapshot flush, which is far too often while streaming.\r\n const partial = useSession(s => s.partial !== null)\r\n const partialText = useSession(s => (s.partial === null ? '' : streamingTail(s.partial.blocks)))\r\n const toolName = useSession(s => (s.runningCalls[0]?.name ?? null))\r\n const running = useSession(s => s.running)\r\n const error = useSession(s => s.lastAgentError)\r\n // Flatline only on a LIVE retry stall. The retry chain keeps every attempt;\r\n // older attempts can linger in 'scheduled' forever (a retry superseded\r\n // without a retry-started event), so only the LAST attempt counts, within a\r\n // freshness window — a session merely waiting for user input must never\r\n // read as a stopped heart. Back-to-front scan stops at the first retry node\r\n // (which is the last one), so cost is O(distance from the tail), not O(n).\r\n const retrying = useSession(s => {\r\n const nodes = s.chat.legacy.nodes\r\n for (let i = nodes.length - 1; i >= 0; i -= 1) {\r\n const n = nodes[i]\r\n if (n.kind === 'model-retry') {\r\n return n.retryState === 'scheduled' && n.time > Date.now() - 120_000\r\n }\r\n }\r\n return false\r\n })\r\n const live = { partial, partialText, toolName, running, error, retrying }\r\n\r\n const steps = stats?.steps ?? 0\r\n\r\n // ── BPM engine: step-window base + live activity boost ────────────────\r\n // targetRef updates once per second (activity readout); bpmRef eases toward\r\n // it EVERY FRAME inside paint, so the trace phase never jumps — a stepped\r\n // BPM would snap the whole waveform sideways at every tick.\r\n const bpmRef = useRef(BPM_FLOOR)\r\n const targetRef = useRef(BPM_FLOOR)\r\n const samplesRef = useRef<StepSample[]>([])\r\n const lastStepsRef = useRef(steps)\r\n const liveRef = useRef(live)\r\n liveRef.current = live\r\n const modeRef = useRef<Mode>('idle')\r\n const [ui, setUi] = useState({ bpm: BPM_FLOOR, elapsed: 0, mode: 'idle' as Mode })\r\n useEffect(() => {\r\n if (steps !== lastStepsRef.current) {\r\n lastStepsRef.current = steps\r\n samplesRef.current.push({ t: performance.now(), steps })\r\n }\r\n }, [steps])\r\n\r\n useEffect(() => {\r\n const id = window.setInterval(() => {\r\n const now = performance.now()\r\n const samples = samplesRef.current\r\n while (samples.length > 0 && now - samples[0].t > ACTIVITY_WINDOW_MS) samples.shift()\r\n const first = samples[0]\r\n const span = first === undefined ? 0 : now - first.t\r\n const delta = first === undefined ? 0 : lastStepsRef.current - first.steps\r\n const perMinute = span > 0 ? (delta / span) * 60_000 : 0\r\n const base = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, 42 + perMinute * 6))\r\n const act = liveRef.current\r\n // Retry stall → flatline: target 0, the trace flattens and the whale's\r\n // heart stops until the retry starts.\r\n targetRef.current = act.retrying\r\n ? 0\r\n : Math.min(BPM_CEIL, Math.max(BPM_FLOOR, base\r\n + (act.toolName !== null ? BOOST_TOOL : 0)\r\n + (act.partial ? BOOST_THINKING : 0)\r\n + (act.running ? BOOST_RUNNING : 0)))\r\n const mode: Mode = act.retrying ? 'flat'\r\n : act.toolName !== null ? 'tool'\r\n : act.partial ? 'think'\r\n : act.running ? 'run' : 'idle'\r\n modeRef.current = mode\r\n setUi(current => ({\r\n bpm: Math.round(bpmRef.current),\r\n elapsed: current.elapsed + 1,\r\n mode,\r\n }))\r\n }, 1_000)\r\n return () => { window.clearInterval(id) }\r\n }, [])\r\n\r\n // ── Canvas trace: one fixed-size canvas, redrawn per rAF ──────────────\r\n const canvasRef = useRef<HTMLCanvasElement>(null)\r\n const ctxRef = useRef<CanvasRenderingContext2D | null>(null)\r\n const widthRef = useRef(640)\r\n const dprRef = useRef(1)\r\n\r\n useEffect(() => {\r\n const canvas = canvasRef.current\r\n if (canvas === null) return\r\n const ctx = canvas.getContext('2d')\r\n if (ctx === null) return\r\n ctxRef.current = ctx\r\n const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches\r\n\r\n const applySize = (): void => {\r\n const dpr = window.devicePixelRatio || 1\r\n dprRef.current = dpr\r\n canvas.width = Math.max(120, Math.round(widthRef.current * dpr))\r\n canvas.height = Math.round(ECG_HEIGHT * dpr)\r\n }\r\n applySize()\r\n const observer = new ResizeObserver((entries) => {\r\n const width = Math.max(120, Math.round(entries[0]?.contentRect.width ?? 640))\r\n if (width !== widthRef.current) {\r\n widthRef.current = width\r\n applySize()\r\n if (reduced) paint(performance.now()) // static mode repaints at the new width\r\n }\r\n })\r\n observer.observe(canvas)\r\n\r\n // Smooth display clock: the trace advances at most CLAMP_PER_FRAME worth\r\n // of time per frame, so a busy main thread (streaming markdown, mode\r\n // switches) that delays rAF can never make the paper jump forward.\r\n let displayNow = 0\r\n let lastPaintReal = 0\r\n // Refresh-synced display clock. Each NORMAL frame advances the trace by\r\n // exactly that frame's real interval — the paper speed is then a constant\r\n // 1× by construction, with zero lag. A DELAYED frame (busy main thread)\r\n // reuses the last normal interval instead, so the trace never jumps, just\r\n // runs a touch slow and resumes. No averaging: a smoothed period lags\r\n // frame-rate changes and the speed visibly surges when the frame rate\r\n // recovers (the \"left edge accelerates then settles\" artifact).\r\n let framePeriodMs = 16.7\r\n // Frozen pixel cache for true erase-bar rendering: the trace image is a\r\n // static buffer; only the pixels the sweep bar has just passed are\r\n // refreshed, everything else stays frozen. The image therefore never\r\n // scrolls, never jumps as a whole, and is frame-rate independent.\r\n let traceCache: number[] = []\r\n let lastScanX = -1\r\n // Continuous phase clock: the trace is ONE unbroken signal in absolute\r\n // time — the phase advances (bpm/60) per second, and each pixel samples\r\n // the signal at its own time. A ring of (time, phase) samples lets us\r\n // evaluate the phase at any sample time in the past, so a mid-sweep rate\r\n // change keeps the pixels crossed afterwards on the SAME phase function\r\n // as the ones before it: the new line continues the old one (old data is\r\n // never repainted, nothing is mixed).\r\n let phaseAcc = 0\r\n let phaseHistory: Array<{ t: number; phase: number }> = []\r\n // Phase at an arbitrary past sample time — linear interpolation over the\r\n // history (samples are ~a frame apart, so the error stays negligible even\r\n // while the rate ramps at 6 BPM/s).\r\n const phaseAt = (tX: number): number => {\r\n const arr = phaseHistory\r\n if (arr.length === 0) return 0\r\n if (tX <= arr[0].t) return arr[0].phase\r\n const last = arr[arr.length - 1]\r\n if (tX >= last.t) return last.phase\r\n let lo = 0\r\n let hi = arr.length - 1\r\n while (hi - lo > 1) {\r\n const m = (lo + hi) >> 1\r\n if (arr[m].t <= tX) lo = m\r\n else hi = m\r\n }\r\n const a = arr[lo]\r\n const b = arr[hi]\r\n return a.phase + ((tX - a.t) / (b.t - a.t)) * (b.phase - a.phase)\r\n }\r\n const paint = (now: number): void => {\r\n if (lastPaintReal === 0) {\r\n lastPaintReal = now\r\n displayNow = now\r\n }\r\n const realDt = Math.max(0, (now - lastPaintReal) / 1_000)\r\n lastPaintReal = now\r\n if (realDt > 0 && realDt < 0.05) framePeriodMs = realDt * 1_000\r\n const dt = framePeriodMs / 1_000\r\n displayNow += dt * 1_000\r\n // Constant-rate ramp (hospital monitor cadence): the rate eases toward\r\n // its target at BPM_RAMP_PER_SECOND, so a rate change takes seconds and\r\n // the beat spacing visibly densifies beat by beat.\r\n const diff = targetRef.current - bpmRef.current\r\n const step = BPM_RAMP_PER_SECOND * dt\r\n if (diff > step) bpmRef.current += step\r\n else if (diff < -step) bpmRef.current -= step\r\n else bpmRef.current = targetRef.current\r\n const c = canvasRef.current\r\n const g = ctxRef.current\r\n if (c === null || g === null) return\r\n // Size is maintained by the ResizeObserver (widthRef / canvas.width);\r\n // paint reads it directly — no per-frame getBoundingClientRect, so no\r\n // forced synchronous layout. Fixed logical height keeps the amplitude\r\n // independent of the live layout (the first paint once saw height 0 and\r\n // collapsed the trace to a flat line).\r\n const dpr = dprRef.current\r\n const w = widthRef.current\r\n const h = ECG_HEIGHT\r\n const wantW = Math.round(w * dpr)\r\n const wantH = Math.round(h * dpr)\r\n if (c.width !== wantW || c.height !== wantH) {\r\n c.width = wantW\r\n c.height = wantH\r\n }\r\n g.setTransform(dpr, 0, 0, dpr, 0, 0)\r\n g.clearRect(0, 0, w, h)\r\n\r\n const tNow = displayNow / 1_000\r\n // Absolute paper speed: px per second is constant, width-independent.\r\n const secondsPerPixel = 1 / PAPER_SPEED_PX_PER_SECOND\r\n const mid = h / 2\r\n const amp = h * 0.5\r\n const wander = 0.05 * Math.sin(tNow * 0.6) + 0.035 * Math.sin(tNow * 1.7 + 1.3)\r\n const bpm = bpmRef.current\r\n // Flatline is a MODE, not a smoothed value: as soon as the target is a\r\n // stopped heart, draw the line — don't wait for the 6 BPM/s ramp to\r\n // cross an arbitrary threshold.\r\n const flatline = targetRef.current === 0\r\n\r\n // Erase-bar sweep: the bar moves right → left; pixels it has just\r\n // passed are re-sampled (frozen update), the rest of the image is\r\n // untouched. The right edge is pinned to \"now\"; beat spacing is\r\n // width-independent (fixed paper speed).\r\n const sweepPeriod = w / PAPER_SPEED_PX_PER_SECOND\r\n const tInSweep = ((tNow % sweepPeriod) + sweepPeriod) % sweepPeriod\r\n const scanX = w - tInSweep * PAPER_SPEED_PX_PER_SECOND // w → 0\r\n const scanXInt = Math.round(scanX)\r\n\r\n // Advance the continuous phase clock by this frame's display time, and\r\n // keep enough history for the oldest pixel the strip can still show\r\n // (two sweep periods back). The first frame seeds a constant-rate\r\n // history — nothing can have changed before the very first paint — so\r\n // the initial full build is a coherent window.\r\n if (phaseHistory.length === 0) {\r\n phaseAcc = (tNow * bpm) / 60\r\n phaseHistory.push({\r\n t: tNow - 2 * sweepPeriod - 2,\r\n phase: ((tNow - 2 * sweepPeriod - 2) * bpm) / 60,\r\n })\r\n phaseHistory.push({ t: tNow, phase: phaseAcc })\r\n } else {\r\n phaseAcc += (bpm / 60) * dt\r\n phaseHistory.push({ t: tNow, phase: phaseAcc })\r\n }\r\n const lookback = 2 * sweepPeriod + 2\r\n while (phaseHistory.length > 2 && phaseHistory[0].t < tNow - lookback) {\r\n phaseHistory.shift()\r\n }\r\n const yNow = (x: number): number => {\r\n if (flatline) return mid // the whale's heart has stopped — a flat line\r\n let v = -Infinity\r\n // Sweep sample: pixel x is (re)written when the bar crosses it, so the\r\n // sample must be anchored to the sweep, not the frame. The frame-time\r\n // lookback `tNow - (w - x)·secondsPerPixel` evaluated at a crossing\r\n // cancels its own offset — the bar reaches x exactly (w - x) px into\r\n // the sweep, so the lookback always lands on the sweep-start instant\r\n // k·sweepPeriod and EVERY swept pixel receives the SAME sample → the\r\n // trace flattens to one level once the bar has crossed the strip.\r\n // Anchoring to the sweep start (`(tNow - tInSweep) - (w - x)·spp`, with\r\n // tNow - tInSweep = k·sweepPeriod) keeps the right edge pinned to the\r\n // sweep start (\"now\") and each pixel's sample x-dependent — the swept\r\n // region redraws as a real, time-normal waveform window.\r\n for (let i = 0; i < 4; i += 1) {\r\n const tX = (tNow - tInSweep) - (w - (x + i * 0.25)) * secondsPerPixel\r\n // Phase comes from the CONTINUOUS clock, not `tX * bpm/60`: the\r\n // product assumes a constant rate and tears the strip apart when\r\n // the rate changes mid-sweep (old pixels stay at the old rate, new\r\n // ones use the new rate). The clock makes every pixel — crossed\r\n // before or after the change — a sample of the SAME unbroken\r\n // signal, so the new line simply continues the old one.\r\n const phase = ((phaseAt(tX) % 1) + 1) % 1\r\n const s = ecgValue(phase)\r\n if (s > v) v = s\r\n }\r\n return mid - (v + wander) * amp\r\n }\r\n if (traceCache.length !== w + 1) {\r\n // Width changed: rebuild the buffer, right-anchored, repaint once.\r\n traceCache = new Array<number>(w + 1)\r\n for (let x = 0; x <= w; x += 1) traceCache[x] = yNow(x)\r\n lastScanX = scanXInt\r\n } else if (lastScanX > scanXInt) {\r\n // Refresh exactly the pixels the sweep has just passed.\r\n for (let x = scanXInt; x <= lastScanX && x <= w; x += 1) {\r\n traceCache[x] = yNow(x)\r\n }\r\n lastScanX = scanXInt\r\n } else if (lastScanX < scanXInt) {\r\n // Wrap: the bar jumped back to the right edge. Refresh its new head\r\n // pixel so the trace continues from the current instant instead of\r\n // leaving stale data at the right edge (the reported seam).\r\n traceCache[scanXInt] = yNow(scanXInt)\r\n lastScanX = scanXInt\r\n } else {\r\n lastScanX = scanXInt\r\n }\r\n\r\n // Chiral ghost over the frozen image, faint.\r\n g.beginPath()\r\n for (let x = 0; x <= w; x += 1) {\r\n const y = traceCache[x]\r\n if (x === 0) g.moveTo(x + 3, y)\r\n else g.lineTo(x + 3, y)\r\n }\r\n g.globalAlpha = 0.1\r\n g.strokeStyle = 'rgba(111, 219, 226, 1)'\r\n g.lineWidth = 1\r\n g.stroke()\r\n g.globalAlpha = 1\r\n\r\n // The frozen trace, whole window.\r\n g.beginPath()\r\n for (let x = 0; x <= w; x += 1) {\r\n const y = traceCache[x]\r\n if (x === 0) g.moveTo(x, y)\r\n else g.lineTo(x, y)\r\n }\r\n g.strokeStyle = MODE_COLOR[modeRef.current]\r\n g.lineWidth = 1.4\r\n g.lineJoin = 'round'\r\n g.lineCap = 'round'\r\n g.stroke()\r\n\r\n // The sweep bar itself: bright core + soft halo.\r\n g.fillStyle = 'rgba(255, 180, 84, 0.16)'\r\n g.fillRect(scanX - 5, 0, 10, h)\r\n g.fillStyle = 'rgba(255, 224, 190, 0.95)'\r\n g.fillRect(scanX - 1, 0, 2, h)\r\n }\r\n\r\n if (reduced) {\r\n paint(performance.now()) // one static frame\r\n return () => { observer.disconnect() }\r\n }\r\n let raf = 0\r\n const loop = (now: number): void => {\r\n raf = requestAnimationFrame(loop)\r\n paint(now)\r\n }\r\n raf = requestAnimationFrame(loop)\r\n return () => {\r\n observer.disconnect()\r\n cancelAnimationFrame(raf)\r\n }\r\n }, [])\r\n\r\n // Status word: real model state wins; the flavor rotation only plays while idle.\r\n const flavor = STATUS_KEYS[Math.floor(ui.elapsed / STATUS_ROTATE_S) % STATUS_KEYS.length]\r\n const status = live.error !== null\r\n ? `⚠ ${live.error.slice(0, 16)}`\r\n : live.retrying\r\n ? t('status.flatline')\r\n : live.toolName !== null\r\n ? `EXEC · ${live.toolName}`\r\n : live.partialText !== ''\r\n ? `⇢ ${live.partialText.slice(-18)}`\r\n : t(flavor)\r\n\r\n return (\r\n <div className=\"cp-line\" role=\"group\" aria-label={t('line.aria')} data-chiral-pulse data-mode={ui.mode} data-rev=\"20\">\r\n <div className=\"cp-lineBpm\">\r\n {ui.bpm}\r\n </div>\r\n\r\n <div className=\"cp-lineEcgWrap\">\r\n <canvas ref={canvasRef} className=\"cp-lineEcg\" aria-hidden />\r\n </div>\r\n\r\n <div className=\"cp-lineReadout\">\r\n <div className=\"cp-lineStatus\" title={status}>{status}</div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n","/**\n * CHIRAL PULSE — dictionary namespace.\n *\n * The DS monitor idiom stays English in both locales (it is part of the\n * aesthetic: \"LINK STABLE\", \"TIME TO COMPLETION\"); the zh side translates\n * the labels a user actually reads.\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'chiral'\n\n/** Dictionary keys of the `chiral` namespace (string-literal union). */\nexport type ChiralKey =\n | 'line.aria'\n | 'status.stable'\n | 'status.bonded'\n | 'status.chiral'\n | 'status.doom'\n | 'status.keep'\n | 'status.voidout'\n | 'status.odradek'\n | 'status.flatline'\n\n/** English dictionary. */\nexport const en: Record<ChiralKey, string> = {\n 'line.aria': 'BB vital-signs strip — CHIRAL PULSE',\n 'status.stable': 'LINK STABLE',\n 'status.bonded': 'BB BONDED',\n 'status.chiral': 'CHIRAL DENSITY: NOMINAL',\n 'status.doom': 'DOOMS LEVEL: 0',\n 'status.keep': 'KEEP ON KEEPING ON',\n 'status.voidout': 'NO VOIDOUT DETECTED',\n 'status.odradek': 'ODRADEK SYNC: OK',\n 'status.flatline': '♥ FLATLINE',\n}\n\n/** Chinese dictionary. */\nexport const zh: Record<ChiralKey, string> = {\n 'line.aria': 'BB 生命体征走纸 — CHIRAL PULSE 手性脉冲',\n 'status.stable': '链路稳定',\n 'status.bonded': 'BB 连接完成',\n 'status.chiral': '手性密度:正常',\n 'status.doom': 'DOOMS 等级:0',\n 'status.keep': '继续前进 · KEEP ON KEEPING ON',\n 'status.voidout': '未检测到虚爆',\n 'status.odradek': '奥卓克同步:正常',\n 'status.flatline': '♥ 心脏停跳',\n}\n","/**\n * CHIRAL PULSE — the Death Stranding sheet, two layers.\n *\n * LAYER 1 — the global skin. The whole app paints from `--dsw-*` variables\n * (ui-theme's design platform: alias tokens reference static tokens, so\n * remapping the palette re-skins every component without touching its\n * structure). This sheet FORCES the DS look under BOTH theme modes: a deep\n * blue-black machine body, cold blue-grey hairlines, amber reserved for\n * emphasis (the heartbeat waveform, hover blooms) — and the deepseek brand\n * blues are left untouched, so the whale mark stays DeepSeek blue.\n *\n * LAYER 2 — the atmosphere. A fixed full-viewport CRT scanline weave, a\n * faint chiral lattice, and a vignette, all pointer-transparent. Plus the\n * BB vital-signs strip that docks under the composer stats: a 26px monitor\n * paper feed whose scrolling ECG is the hero, with the BPM and status read.\n *\n * Every rule is scoped under `.cp-*` (except the token remap, which must\n * target `body`), rides one owned <style data-plugin> tag, and the loader\n * removes it on unload.\n */\n\nexport const CHIRAL_CSS = `\n/* ────────────────────────────────────────────────────────────────────────\n LAYER 1 · global DS skin — dark blue-black, both theme modes\n ──────────────────────────────────────────────────────────────────────── */\n\n/* Alias-level remap: independent of the static scale's role flip between\n themes, so the DS look is identical under light and dark settings. */\nbody[data-ds-dark-theme],\nbody:not([data-ds-dark-theme]) {\n /* machine body — blue-grey with air, not a black void */\n --dsw-alias-bg-base: rgb(13, 17, 23);\n --dsw-alias-bg-layer-1: rgb(17, 22, 29);\n --dsw-alias-bg-layer-2: rgb(21, 27, 35);\n --dsw-alias-bg-layer-3: rgb(26, 33, 42);\n --dsw-alias-bg-overlay: rgb(31, 40, 51);\n --dsw-alias-bg-mask-1: rgba(0, 0, 0, 0.5);\n --dsw-alias-bg-mask-2: rgba(0, 0, 0, 0.22);\n --dsw-alias-bg-mask-3: rgba(0, 0, 0, 0.5);\n --dsw-alias-bg-mask-drop: rgba(10, 14, 19, 0.72);\n --dsw-alias-bg-skeleton: rgba(150, 170, 200, 0.07);\n /* Setting rows / select chips (language, agent preset, model, permissions…)\n paint from these; without an override they stay the LIGHT theme's\n near-white and produce white-on-white text. */\n --dsw-alias-bg-module-platform: rgb(15, 20, 27);\n --dsw-alias-bg-multi-select: rgb(18, 24, 32);\n --dsw-alias-fill-tsp-secondary: rgb(18, 24, 32);\n\n /* text: cold blue-grey */\n --dsw-alias-label-primary: rgb(235, 240, 246);\n --dsw-alias-label-secondary: rgb(178, 190, 205);\n --dsw-alias-label-tertiary: rgb(140, 153, 170);\n --dsw-alias-label-caption: rgb(108, 122, 141);\n --dsw-alias-label-dimmed: rgb(94, 108, 127);\n --dsw-alias-label-primary-dimmed: rgb(205, 213, 222);\n --dsw-alias-label-primary-inverted: rgb(20, 26, 35);\n --dsw-alias-label-primary-foreground: rgb(13, 17, 23);\n --dsw-alias-label-primary-bluish: rgb(200, 214, 232);\n --dsw-alias-brand-text: rgb(235, 240, 246);\n --dsw-alias-brand-primary: rgb(103, 158, 254);\n --dsw-alias-brand-primary-invert: rgb(235, 240, 246);\n\n /* hairlines: cold blue, readable against the body */\n --dsw-alias-border-l1: rgba(140, 170, 215, 0.15);\n --dsw-alias-border-l2: rgba(140, 170, 215, 0.24);\n --dsw-alias-border-l2-darkmode-thin: rgba(140, 170, 215, 0.19);\n --dsw-alias-border-l3: rgba(140, 170, 215, 0.34);\n --dsw-alias-border-l4: rgba(140, 170, 215, 0.46);\n --dsw-alias-border-inverted: rgba(255, 255, 255, 0.09);\n --dsw-alias-border-inverted2: rgba(255, 255, 255, 0.11);\n\n /* hovers: amber bloom, kept subtle */\n --dsw-alias-interactive-bg-hover: rgba(255, 180, 84, 0.08);\n --dsw-alias-interactive-bg-active: rgba(255, 180, 84, 0.12);\n --dsw-alias-interactive-bg-hover-accent: rgba(255, 180, 84, 0.14);\n --dsw-alias-interactive-bg-hover-solid: rgb(22, 29, 38);\n --dsw-alias-interactive-bg-hover-danger: rgba(242, 90, 90, 0.14);\n\n /* buttons: brand blue stays the primary action */\n --dsw-alias-button-primary-dimmed: rgb(30, 40, 53);\n --dsw-alias-button-primary-hover: rgb(124, 172, 255);\n --dsw-alias-button-ghost-active-fill: rgb(22, 29, 38);\n --dsw-alias-button-ghost-active-hover: rgb(27, 35, 46);\n --dsw-alias-button-ghost-active-border: rgb(140, 170, 215);\n --dsw-alias-button-floating-fill: rgb(19, 25, 33);\n --dsw-alias-button-floating-hover: rgb(24, 31, 41);\n --dsw-alias-button-elevated-fill: rgb(22, 29, 38);\n /* Contrast fill (attachment rail): pale case + DARK inverted ink — the\n wordmark badge and toasts also ride label-primary-inverted, so the pair\n (pale fill, dark ink) stays readable everywhere. */\n --dsw-alias-button-contrast-fill: rgb(205, 213, 222);\n --dsw-alias-button-tool-bar-fill: rgba(140, 170, 215, 0.24);\n --dsw-alias-button-tool-bar-fill-invisible: rgba(140, 170, 215, 0.13);\n --dsw-alias-button-tool-bar-hover: rgba(140, 170, 215, 0.32);\n\n /* surfaces */\n --dsw-specific-sidebar-fill: rgb(9, 12, 17);\n --dsw-specific-sidebar-nav-item-active: rgb(20, 27, 36);\n --dsw-specific-sidebar-nav-item-active-accent: rgb(27, 36, 48);\n --dsw-specific-sidebar-nav-item-hover: rgb(15, 20, 28);\n --dsw-specific-bubble: rgb(18, 24, 32);\n --dsw-specific-bubble-highlight: rgb(24, 32, 42);\n --dsw-specific-input-major: rgb(15, 20, 27);\n --dsw-specific-login-input: rgb(12, 16, 22);\n --dsw-specific-menu: rgb(21, 27, 35);\n --dsw-specific-selector: rgb(20, 26, 34);\n --dsw-specific-tip: rgb(16, 21, 28);\n --dsw-alias-markdown-code-block: rgb(10, 14, 19);\n --dsw-alias-markdown-code-block-banner: rgb(13, 17, 23);\n --dsw-alias-markdown-inline-code: rgb(18, 24, 32);\n --dsw-alias-markdown-code-segment-selected: rgb(16, 21, 28);\n --dsw-alias-markdown-code-segment-unselected: rgb(12, 16, 22);\n --dsw-alias-markdown-placeholder: rgb(16, 21, 28);\n --dsw-alias-markdown-tag: rgb(18, 24, 32);\n --dsw-alias-markdown-citation: rgb(22, 29, 38);\n\n /* floats */\n --dsw-alias-toast-bg: rgb(22, 29, 38);\n --dsw-alias-tooltip-bg: rgb(20, 26, 34);\n --dsw-alias-scrollbar-bg-l1: rgb(13, 18, 25);\n --dsw-alias-scrollbar-bg-l2: rgb(17, 23, 31);\n --dsw-alias-scrollbar-hover-l1: rgb(34, 44, 58);\n --dsw-alias-scrollbar-hover-l2: rgb(42, 54, 70);\n\n /* status: amber stays the warn/emphasis hue; success leans chiral cyan */\n --dsw-alias-state-warn-primary: rgb(245, 158, 11);\n --dsw-alias-state-warn-secondary: rgb(247, 173, 49);\n --dsw-alias-state-warn-label: rgb(221, 134, 41);\n --dsw-alias-state-warn-tertiary: rgb(39, 36, 31);\n --dsw-alias-state-success-primary: rgb(52, 205, 168);\n --dsw-alias-state-success-secondary: rgb(94, 222, 189);\n --dsw-alias-state-success-tertiary: rgb(12, 28, 24);\n /* Business tint (hero \"preview\" badge et al.): dark case so the pale\n primary-bluish ink stays readable — the light-theme default is near-white. */\n --dsw-alias-state-business-tertiary: rgb(26, 34, 46);\n --dsw-static-green-400: rgb(94, 222, 189);\n --dsw-static-green-500: rgb(52, 205, 168);\n}\n\n/* Focus ring: amber, the DS highlight color. */\n:focus-visible {\n outline: 1px solid rgba(255, 180, 84, 0.65) !important;\n outline-offset: 2px;\n}\n\n/* Ambient bloom behind the app. */\nbody {\n background-image:\n radial-gradient(1100px 620px at 12% -8%, rgba(111, 219, 226, 0.04), transparent 60%),\n radial-gradient(900px 560px at 108% 112%, rgba(103, 158, 254, 0.05), transparent 60%);\n background-attachment: fixed;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n LAYER 2 · atmosphere overlays (injected as fixed elements)\n ──────────────────────────────────────────────────────────────────────── */\n.cp-atmo {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 2147483000;\n}\n.cp-atmo-scanlines {\n background: repeating-linear-gradient(\n 0deg,\n rgba(255, 255, 255, 0.024) 0 1px,\n transparent 1px 3px\n );\n mix-blend-mode: overlay;\n}\n.cp-atmo-lattice {\n opacity: 0.55;\n background:\n repeating-linear-gradient(60deg, transparent 0 17px, rgba(103, 158, 254, 0.03) 17px 18px),\n repeating-linear-gradient(120deg, transparent 0 17px, rgba(111, 219, 226, 0.028) 17px 18px);\n}\n.cp-atmo-vignette {\n background: radial-gradient(120% 100% at 50% 40%, transparent 55%, rgba(0, 0, 0, 0.24) 100%);\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n BB vital-signs strip · the heartbeat paper feed\n ──────────────────────────────────────────────────────────────────────── */\n.cp-line {\n --cp-amber: #ffb454;\n --cp-amber-bright: #ffd9a0;\n --cp-cyan: #6fdbe2;\n --cp-dim: #64727f;\n /* flex: none — the hero (blank-session) composer column squeezes its\n children on short viewports; the monitor strip must never shrink. */\n flex: none;\n display: flex;\n align-items: center;\n gap: 12px;\n height: 26px;\n min-height: 26px;\n margin: 3px 0 4px;\n padding: 0 10px;\n border: 1px solid rgba(140, 170, 215, 0.26);\n border-radius: 0;\n background:\n linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),\n linear-gradient(180deg, rgba(14, 19, 26, 0.92), rgba(9, 13, 18, 0.94));\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.05),\n inset 0 0 16px rgba(103, 158, 254, 0.06);\n color: #c9d3dc;\n font-family: ui-monospace, \"Cascadia Mono\", \"JetBrains Mono\", Consolas, \"Courier New\", monospace;\n overflow: hidden;\n clip-path: polygon(\n 7px 0,\n 100% 0,\n 100% calc(100% - 7px),\n calc(100% - 7px) 100%,\n 0 100%,\n 0 7px\n );\n}\n\n.cp-lineBpm {\n /* Fixed width: a 3-digit readout (42 → 150) must not widen the block and\n squeeze the paper area — that would shrink the trace window and pull the\n left edge rightward as the rate climbs. */\n flex: none;\n width: 48px;\n text-align: center;\n font-size: 16px;\n line-height: 1;\n letter-spacing: 0.5px;\n color: var(--cp-amber-bright);\n text-shadow: 0 0 10px rgba(255, 180, 84, 0.5);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.cp-lineEcgWrap {\n /* basis 0 + grow: the paper area takes exactly the flex-allocated width;\n never shrink (a squeezed strip would shrink the canvas bitmap and make\n the trace speed depend on the window width). */\n flex: 1 1 0;\n min-width: 100px;\n height: 22px;\n position: relative;\n border-radius: 0;\n border: 1px solid rgba(140, 170, 215, 0.16);\n /* Static paper grid lives in CSS; the canvas above it only paints the trace. */\n background:\n repeating-linear-gradient(0deg, rgba(140, 170, 215, 0.07) 0 1px, transparent 1px 11px),\n repeating-linear-gradient(90deg, rgba(140, 170, 215, 0.06) 0 1px, transparent 1px 11px),\n rgba(7, 10, 15, 0.55);\n}\n/* The canvas fills its wrapper exactly (absolute), so its intrinsic size can\n never distort the flex layout or the trace during remounts. */\n.cp-lineEcg {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n display: block;\n border: 0;\n background: transparent;\n}\n\n.cp-lineReadout {\n flex: none;\n width: 132px;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n overflow: hidden;\n}\n.cp-lineStatus {\n font-size: 8px;\n letter-spacing: 1.8px;\n text-transform: uppercase;\n color: var(--cp-cyan);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .cp-lineEcg {\n opacity: 0.9;\n }\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n ECG paper grid + activity-mode color coupling\n ──────────────────────────────────────────────────────────────────────── */\n.cp-line[data-mode=\"think\"] .cp-lineBpm {\n color: #7fe3e8;\n text-shadow: 0 0 10px rgba(111, 219, 226, 0.55);\n}\n.cp-line[data-mode=\"tool\"] .cp-lineBpm {\n color: #ff9b7a;\n text-shadow: 0 0 10px rgba(255, 122, 77, 0.6);\n}\n.cp-line[data-mode=\"run\"] .cp-lineBpm {\n color: #ffd9a0;\n}\n.cp-line[data-mode=\"think\"] .cp-lineStatus,\n.cp-line[data-mode=\"tool\"] .cp-lineStatus {\n color: #9fe8ec;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Message-flow dressing — DS glyphs on each node kind\n ──────────────────────────────────────────────────────────────────────── */\n[data-chat-flow-kind] {\n position: relative;\n}\n[data-chat-flow-kind=\"assistant\"] {\n padding-left: 18px;\n}\n[data-chat-flow-kind=\"assistant\"]::before {\n content: \"✦\";\n position: absolute;\n left: 4px;\n top: 12px;\n color: rgba(255, 180, 84, 0.85);\n font-size: 11px;\n line-height: 1;\n text-shadow: 0 0 8px rgba(255, 180, 84, 0.6);\n}\n[data-chat-flow-kind=\"assistant\"]::after {\n content: \"\";\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 1px;\n background: linear-gradient(180deg, transparent, rgba(255, 180, 84, 0.35), transparent);\n}\n[data-chat-flow-kind=\"user\"],\n[data-chat-flow-kind=\"steering\"] {\n padding-right: 18px;\n}\n[data-chat-flow-kind=\"user\"]::before,\n[data-chat-flow-kind=\"steering\"]::before {\n content: \"▸▸\";\n position: absolute;\n right: 2px;\n top: 4px;\n color: rgba(120, 150, 195, 0.75);\n font-size: 10px;\n line-height: 1;\n letter-spacing: -1px;\n}\n[data-chat-flow-kind=\"context\"] {\n padding-left: 16px;\n}\n[data-chat-flow-kind=\"context\"]::before {\n content: \"⇢\";\n position: absolute;\n left: 2px;\n top: 12px;\n color: rgba(111, 219, 226, 0.7);\n font-size: 11px;\n line-height: 1;\n}\n[data-variant=\"think\"] {\n border-left: 2px solid rgba(111, 219, 226, 0.35);\n padding-left: 10px;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Tool-card chassis — the ui-primitives block family gets a DS case\n ──────────────────────────────────────────────────────────────────────── */\n[data-tool],\n[data-search],\n[data-read],\n[data-web],\n[data-diff],\n[data-terminal],\n[data-context-injection-body] {\n border: 1px solid rgba(140, 170, 215, 0.24) !important;\n border-radius: 0 !important;\n background:\n linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),\n linear-gradient(180deg, rgba(15, 20, 27, 0.88), rgba(9, 13, 18, 0.92)) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.05),\n inset 0 0 18px rgba(103, 158, 254, 0.05);\n position: relative;\n clip-path: polygon(\n 7px 0,\n 100% 0,\n 100% calc(100% - 7px),\n calc(100% - 7px) 100%,\n 0 100%,\n 0 7px\n );\n}\n[data-terminal]::before {\n content: \"❯_\";\n position: absolute;\n right: 8px;\n top: 6px;\n color: rgba(111, 219, 226, 0.35);\n font-size: 10px;\n font-family: ui-monospace, Consolas, monospace;\n}\n[data-read]::before {\n content: \"▤\";\n position: absolute;\n right: 8px;\n top: 6px;\n color: rgba(120, 150, 195, 0.4);\n font-size: 11px;\n}\n[data-search]::before {\n content: \"⌕\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(255, 180, 84, 0.4);\n font-size: 13px;\n}\n[data-web]::before {\n content: \"⌖\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(111, 219, 226, 0.4);\n font-size: 12px;\n}\n[data-diff]::before {\n content: \"⇄\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(120, 150, 195, 0.4);\n font-size: 12px;\n}\n[data-tool]::before {\n content: \"⚙\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(255, 180, 84, 0.4);\n font-size: 11px;\n}\n[data-context-injection-body]::before {\n content: \"⇢\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(111, 219, 226, 0.4);\n font-size: 11px;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Composer details\n ──────────────────────────────────────────────────────────────────────── */\n[data-composer-seat] textarea {\n caret-color: #ffb454;\n}\n[data-composer-seat] textarea:focus {\n caret-color: #ffd9a0;\n}\n/* Composer seat: squared, no extra frame — a visible outline on the big hero\n card read as a jarring border. */\n[data-composer-seat] {\n border-radius: 0;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Dialogs, menus, tooltips, toasts — the floating DS surfaces\n ──────────────────────────────────────────────────────────────────────── */\n[role=\"dialog\"] {\n border: 1px solid rgba(255, 180, 84, 0.35) !important;\n /* Inner hairline frame — the DS double-cased panel. */\n outline: 1px solid rgba(140, 170, 215, 0.22);\n outline-offset: -6px;\n border-radius: 0 !important;\n background: linear-gradient(180deg, rgba(15, 20, 27, 0.98), rgba(10, 14, 19, 0.99)) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.07),\n inset 0 0 26px rgba(103, 158, 254, 0.06) !important;\n}\n[role=\"menu\"] {\n border: 1px solid rgba(140, 170, 215, 0.3) !important;\n border-radius: 0 !important;\n background: rgba(13, 18, 25, 0.97) !important;\n}\n[role=\"menuitem\"]:hover {\n background: rgba(255, 180, 84, 0.08) !important;\n}\n[role=\"tooltip\"] {\n border: 1px solid rgba(140, 170, 215, 0.32) !important;\n border-radius: 0 !important;\n background: rgba(15, 20, 27, 0.97) !important;\n}\n[role=\"alert\"] {\n border: 1px solid rgba(255, 180, 84, 0.38) !important;\n border-radius: 0 !important;\n background: rgba(15, 20, 27, 0.97) !important;\n clip-path: polygon(\n 8px 0,\n 100% 0,\n 100% calc(100% - 8px),\n calc(100% - 8px) 100%,\n 0 100%,\n 0 8px\n );\n}\n\n/* Toast (the only alert portaled straight onto body): DS gold badge —\n amber case, dark ink, chamfered. Inline error rows keep the dark case\n above; this rule wins for the fixed top-center banner. */\nbody > [role=\"alert\"] {\n border: 1px solid rgba(255, 196, 120, 0.7) !important;\n border-radius: 0 !important;\n background: linear-gradient(180deg, #ffbe6b, #e09a3c) !important;\n color: rgb(28, 18, 6) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.4),\n 0 10px 32px rgba(0, 0, 0, 0.5) !important;\n clip-path: polygon(\n 10px 0,\n 100% 0,\n 100% calc(100% - 10px),\n calc(100% - 10px) 100%,\n 0 100%,\n 0 10px\n );\n}\n\n/* Session-header action + utility buttons (Session log, jobs…):\n DS chamfered buttons. Session log lives in .utilities, jobs in .actions. */\n[data-slot=\"conversation.session.header.actions\"] button,\n[data-slot=\"conversation.session.header.utilities\"] button {\n border-radius: 0 !important;\n clip-path: polygon(\n 6px 0,\n 100% 0,\n 100% calc(100% - 6px),\n calc(100% - 6px) 100%,\n 0 100%,\n 0 6px\n );\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Sidebar: DS hairline on the conversation-history column\n ──────────────────────────────────────────────────────────────────────── */\n[data-sidebar-collapsed] > div:first-child {\n border-right: 1px solid rgba(140, 170, 215, 0.22);\n box-shadow: inset -1px 0 0 rgba(255, 180, 84, 0.06);\n}\n\n/* Sidebar buttons (New Session etc.): DS chamfered corners. */\n[data-slot=\"sidebar\"] button {\n border-radius: 0 !important;\n clip-path: polygon(\n 6px 0,\n 100% 0,\n 100% calc(100% - 6px),\n calc(100% - 6px) 100%,\n 0 100%,\n 0 6px\n );\n}\n\n/* Workspace rows (workspaces, sessions, groups): DS chamfered entries. */\n[role=\"treeitem\"] {\n border-radius: 0 !important;\n clip-path: polygon(\n 5px 0,\n 100% 0,\n 100% calc(100% - 5px),\n calc(100% - 5px) 100%,\n 0 100%,\n 0 5px\n );\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n DS chamfer everywhere else: kill the round-corner language\n ──────────────────────────────────────────────────────────────────────── */\nbutton,\ninput,\ntextarea,\nselect,\n[role=\"tab\"],\n[role=\"menuitem\"],\n[role=\"treeitem\"] {\n border-radius: 0 !important;\n}\n\n`\n","/**\n * CHIRAL PULSE, browser half: the Death Stranding skin plus the BB\n * vital-signs strip above the composer.\n *\n * Two contributions:\n * 1. The global DS skin — a `--dsw-*` token remap (blue-black machine body,\n * amber hairlines, sand-paper light variant), the DeepSeek whale mark's\n * brand blues untouched, plus three pointer-transparent atmosphere\n * overlays (CRT scanlines, chiral lattice, vignette).\n * 2. The heartbeat strip on `conversation.input.dock` — a 26px monitor\n * paper feed whose scrolling ECG is the hero and whose BPM follows the\n * session's live activity (model streaming, tools executing).\n *\n * The plugin owns no state of its own beyond the component's local beat\n * engine; every figure arrives through the session standard kit. All styles\n * ride one owned <style data-plugin> tag so the loader removes them on\n * unload/reload.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the ui-conversation SlotMap merge (the composer.dock entry).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { HeartLine } from './HeartLine.tsx'\nimport { en, NS, zh, type ChiralKey } from './locales.ts'\nimport { CHIRAL_CSS } from './style.ts'\n\nexport { HeartLine } from './HeartLine.tsx'\nexport type { HeartLineProps } from './HeartLine.tsx'\nexport type { ChiralKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The BB monitor strip's copy. */\n chiral: ChiralKey\n }\n}\n\n/** Required services: the slot registry and the locale service. */\nexport const inject = ['slots', 'locale']\n\n/**\n * Client plugin body: register dictionaries, inject the DS sheet and the\n * atmosphere overlays, and dock the heartbeat strip under the composer.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'chiral-pulse: dictionaries')\n\n ctx.effect(() => {\n const tag = document.createElement('style')\n tag.dataset.plugin = 'chiral-pulse'\n tag.textContent = CHIRAL_CSS\n document.head.appendChild(tag)\n return () => { tag.remove() }\n }, 'chiral-pulse: styles')\n\n // Atmosphere overlays: scanlines + chiral lattice + vignette, all\n // pointer-transparent, riding the top of the stacking order.\n ctx.effect(() => {\n const layers = [\n { className: 'cp-atmo cp-atmo-scanlines', label: 'scanlines' },\n { className: 'cp-atmo cp-atmo-lattice', label: 'lattice' },\n { className: 'cp-atmo cp-atmo-vignette', label: 'vignette' },\n ]\n const nodes = layers.map(({ className }) => {\n const el = document.createElement('div')\n el.className = className\n el.setAttribute('aria-hidden', 'true')\n document.body.appendChild(el)\n return el\n })\n return () => {\n for (const el of nodes) el.remove()\n }\n }, 'chiral-pulse: atmosphere')\n\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n id: 'chiral-pulse',\n // Above the composer card, under the goal strip: the pulse feed rides\n // with the input it monitors.\n order: 20,\n locale: NS,\n }, HeartLine))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client/ecg.ts","../src/client/HeartLine.tsx","../src/client/locales.ts","../src/client/style.ts","../src/client/index.ts"],"sourcesContent":["/**\n * CHIRAL PULSE — ECG waveform synthesis.\n *\n * A cardiac cycle is a pure function of beat phase in [0,1): the classic\n * P-QRS-T complex as a sum of wrapped gaussian bumps. The monitor line is a\n * scrolling window over the time axis: the right edge shows the current\n * instant, the window spans `cycles` beats of history. BPM is the phase\n * clock speed, so the whole rhythm accelerates and slows with activity.\n */\n\n/** One wrapped gaussian bump: peak at `center` with `width`, amplitude `amp`. */\nfunction bump(phase: number, center: number, width: number, amp: number): number {\n let d = phase - center\n d -= Math.round(d)\n return amp * Math.exp(-(d * d) / (2 * width * width))\n}\n\n/**\n * Sample one cardiac cycle at beat phase in [0,1). Output range ≈ [-0.35, 1].\n * @param phase - beat phase, any real value (wrapping is internal).\n * @returns the waveform amplitude at that phase.\n */\nexport function ecgValue(phase: number): number {\n return (\n bump(phase, 0.14, 0.030, 0.16) // P wave\n - bump(phase, 0.30, 0.011, 0.26) // Q dip\n + bump(phase, 0.335, 0.016, 1.0) // R spike (wide enough to survive sampling)\n - bump(phase, 0.375, 0.011, 0.34) // S dip\n + bump(phase, 0.52, 0.048, 0.26) // T wave\n + bump(phase, 0.80, 0.012, 0.05) // U ripple\n )\n}\n","/**\r\n * HeartLine — the CHIRAL PULSE monitor strip, docked above the composer\r\n * (`conversation.input.dock`). A 26px \"monitor paper feed\": the scrolling\r\n * ECG waveform is the hero, flanked by the BPM read and the status word.\r\n * No duplicated figures — StatsLine already shows turns/tokens.\r\n *\r\n * The pulse is LIVE, not decorative:\r\n * - `partial` non-null → the model is thinking/generating → +38 BPM\r\n * - `runningCalls` non-empty → a tool is executing → +52 BPM\r\n * - `running` (session turn in flight) → +10 BPM\r\n * - otherwise the 10s step-window activity rate sets the base (~42 idle)\r\n * The BPM target is smoothed with a lerp; the paper speed stays FIXED and\r\n * only the beat density changes — hospital monitor semantics.\r\n *\r\n * Rendering: a single <canvas> redrawn per rAF at full frame rate. Fixed\r\n * memory (one canvas the size of the strip), no DOM attribute churn, no\r\n * string building — the trace is ~width straight segments per frame, which\r\n * is far cheaper than SVG polyline swaps and cannot stutter from throttling.\r\n */\r\nimport { useEffect, useRef, useState } from 'react'\r\nimport type {\r\n PropsLocale, PropsRuntime,\r\n} from '@deepseek-ai/dsh-client-ui-slots'\r\n// Session lifecycle and Chat are separate standard sources in current DSH.\r\nimport type {} from '@deepseek-ai/dsh-client-ui-session/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-chat/client'\r\n// Type-only: merges the sessionStats key into SessionProjectionMap.\r\nimport type {} from '@deepseek-ai/dsh-session-stats/client'\r\nimport { ecgValue } from './ecg.ts'\r\nimport type { ChiralKey } from './locales.ts'\r\nimport { NS } from './locales.ts'\r\n\r\n/** Full props: the input-dock runtime seat plus the locale seat. */\r\nexport type HeartLineProps = PropsRuntime<'conversation.input.dock'> & PropsLocale<typeof NS>\r\n\r\n/** Monitor view height, CSS px. */\r\nconst ECG_HEIGHT = 22\r\n/**\r\n * FIXED paper speed in px/second — the real hospital-monitor invariant.\r\n * The trace scrolls at this absolute rate no matter the strip width; the\r\n * width only decides how much history fits on screen. A rate change (42→90)\r\n * therefore only densifies the beats — it never speeds the paper up, and\r\n * resizing the window cannot make the trace run faster either.\r\n */\r\nconst PAPER_SPEED_PX_PER_SECOND = 30\r\n/** Activity window for the step-rate base, ms. */\r\nconst ACTIVITY_WINDOW_MS = 10_000\r\n/** Rotating status lines (locale keys), one every STATUS_ROTATE_S ticks. */\r\nconst STATUS_KEYS: readonly ChiralKey[] = [\r\n 'status.stable', 'status.bonded', 'status.chiral', 'status.doom',\r\n 'status.keep', 'status.voidout', 'status.odradek',\r\n]\r\nconst STATUS_ROTATE_S = 4\r\n/** BPM boost while the model is streaming a partial (thinking/generating). */\r\nconst BOOST_THINKING = 38\r\n/** BPM boost while a tool call is running. */\r\nconst BOOST_TOOL = 52\r\n/** BPM boost while the session turn is simply in flight. */\r\nconst BOOST_RUNNING = 10\r\n/** BPM floor (a resting BB) and ceiling. */\r\nconst BPM_FLOOR = 42\r\nconst BPM_CEIL = 150\r\n/**\r\n * How fast the displayed heart rate ramps toward its target, in BPM/second.\r\n * A hospital monitor updates its HR figure on a ~2-3s rolling average and\r\n * the trace follows gradually — the rate change reads as a slow ramp, not a\r\n * snap: 42 → 90 takes (90-42)/6 = 8 seconds of visible densification.\r\n */\r\nconst BPM_RAMP_PER_SECOND = 6\r\n\r\n/** Trace color by activity mode: idle amber, thinking cyan, tool orange, run warm. */\r\nconst MODE_COLOR = {\r\n idle: '#ffb454',\r\n think: '#6fdbe2',\r\n tool: '#ff7a4d',\r\n run: '#ffc46b',\r\n flat: '#c0483c',\r\n} as const\r\ntype Mode = keyof typeof MODE_COLOR\r\n\r\n/** One activity sample: (time, steps) at a projection update. */\r\ninterface StepSample {\r\n t: number\r\n steps: number\r\n}\r\n\r\n/** Tail of the model's in-flight output: last non-empty text/reasoning block, whitespace-flattened. */\r\nfunction streamingTail(blocks: readonly { kind: string; text?: string }[]): string {\r\n for (let i = blocks.length - 1; i >= 0; i -= 1) {\r\n const block = blocks[i]\r\n const text = block.text\r\n if (text !== undefined && text.trim() !== '') {\r\n return text.replace(/\\s+/g, ' ').trim()\r\n }\r\n }\r\n return ''\r\n}\r\n\r\n/**\r\n * The CHIRAL PULSE dock entry.\r\n * @param props - Session lifecycle, Chat, projection hooks, and locale seat.\r\n * @returns the monitor strip.\r\n */\r\nexport function HeartLine({ useSession, useChat, useProjection, t }: HeartLineProps) {\r\n const stats = useProjection('sessionStats')\r\n // One primitive-returning selector per signal: each returns a stable value\r\n // (boolean / string / null), so the component only re-renders when that\r\n // signal actually changes — a single object selector re-rendered on every\r\n // snapshot flush, which is far too often while streaming.\r\n const partial = useChat(s => s.legacy.partial !== null)\r\n const partialText = useChat(s => (s.legacy.partial === null ? '' : streamingTail(s.legacy.partial.blocks)))\r\n const toolName = useChat(s => (s.legacy.runningCalls[0]?.name ?? null))\r\n const running = useSession(s => s.running)\r\n const error = useSession(s => s.lastAgentError)\r\n // Flatline only on a LIVE retry stall. The retry chain keeps every attempt;\r\n // older attempts can linger in 'scheduled' forever (a retry superseded\r\n // without a retry-started event), so only the LAST attempt counts, within a\r\n // freshness window — a session merely waiting for user input must never\r\n // read as a stopped heart. Back-to-front scan stops at the first retry node\r\n // (which is the last one), so cost is O(distance from the tail), not O(n).\r\n const retrying = useChat(s => {\r\n const nodes = s.legacy.nodes\r\n for (let i = nodes.length - 1; i >= 0; i -= 1) {\r\n const n = nodes[i]\r\n if (n.kind === 'model-retry') {\r\n return n.retryState === 'scheduled' && n.time > Date.now() - 120_000\r\n }\r\n }\r\n return false\r\n })\r\n const live = { partial, partialText, toolName, running, error, retrying }\r\n\r\n const steps = stats?.steps ?? 0\r\n\r\n // ── BPM engine: step-window base + live activity boost ────────────────\r\n // targetRef updates once per second (activity readout); bpmRef eases toward\r\n // it EVERY FRAME inside paint, so the trace phase never jumps — a stepped\r\n // BPM would snap the whole waveform sideways at every tick.\r\n const bpmRef = useRef(BPM_FLOOR)\r\n const targetRef = useRef(BPM_FLOOR)\r\n const samplesRef = useRef<StepSample[]>([])\r\n const lastStepsRef = useRef(steps)\r\n const liveRef = useRef(live)\r\n liveRef.current = live\r\n const modeRef = useRef<Mode>('idle')\r\n const [ui, setUi] = useState({ bpm: BPM_FLOOR, elapsed: 0, mode: 'idle' as Mode })\r\n useEffect(() => {\r\n if (steps !== lastStepsRef.current) {\r\n lastStepsRef.current = steps\r\n samplesRef.current.push({ t: performance.now(), steps })\r\n }\r\n }, [steps])\r\n\r\n useEffect(() => {\r\n const id = window.setInterval(() => {\r\n const now = performance.now()\r\n const samples = samplesRef.current\r\n while (samples.length > 0 && now - samples[0].t > ACTIVITY_WINDOW_MS) samples.shift()\r\n const first = samples[0]\r\n const span = first === undefined ? 0 : now - first.t\r\n const delta = first === undefined ? 0 : lastStepsRef.current - first.steps\r\n const perMinute = span > 0 ? (delta / span) * 60_000 : 0\r\n const base = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, 42 + perMinute * 6))\r\n const act = liveRef.current\r\n // Retry stall → flatline: target 0, the trace flattens and the whale's\r\n // heart stops until the retry starts.\r\n targetRef.current = act.retrying\r\n ? 0\r\n : Math.min(BPM_CEIL, Math.max(BPM_FLOOR, base\r\n + (act.toolName !== null ? BOOST_TOOL : 0)\r\n + (act.partial ? BOOST_THINKING : 0)\r\n + (act.running ? BOOST_RUNNING : 0)))\r\n const mode: Mode = act.retrying ? 'flat'\r\n : act.toolName !== null ? 'tool'\r\n : act.partial ? 'think'\r\n : act.running ? 'run' : 'idle'\r\n modeRef.current = mode\r\n setUi(current => ({\r\n bpm: Math.round(bpmRef.current),\r\n elapsed: current.elapsed + 1,\r\n mode,\r\n }))\r\n }, 1_000)\r\n return () => { window.clearInterval(id) }\r\n }, [])\r\n\r\n // ── Canvas trace: one fixed-size canvas, redrawn per rAF ──────────────\r\n const canvasRef = useRef<HTMLCanvasElement>(null)\r\n const ctxRef = useRef<CanvasRenderingContext2D | null>(null)\r\n const widthRef = useRef(640)\r\n const dprRef = useRef(1)\r\n\r\n useEffect(() => {\r\n const canvas = canvasRef.current\r\n if (canvas === null) return\r\n const ctx = canvas.getContext('2d')\r\n if (ctx === null) return\r\n ctxRef.current = ctx\r\n const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches\r\n\r\n const applySize = (): void => {\r\n const dpr = window.devicePixelRatio || 1\r\n dprRef.current = dpr\r\n canvas.width = Math.max(120, Math.round(widthRef.current * dpr))\r\n canvas.height = Math.round(ECG_HEIGHT * dpr)\r\n }\r\n applySize()\r\n const observer = new ResizeObserver((entries) => {\r\n const width = Math.max(120, Math.round(entries[0]?.contentRect.width ?? 640))\r\n if (width !== widthRef.current) {\r\n widthRef.current = width\r\n applySize()\r\n if (reduced) paint(performance.now()) // static mode repaints at the new width\r\n }\r\n })\r\n observer.observe(canvas)\r\n\r\n // Smooth display clock: the trace advances at most CLAMP_PER_FRAME worth\r\n // of time per frame, so a busy main thread (streaming markdown, mode\r\n // switches) that delays rAF can never make the paper jump forward.\r\n let displayNow = 0\r\n let lastPaintReal = 0\r\n // Refresh-synced display clock. Each NORMAL frame advances the trace by\r\n // exactly that frame's real interval — the paper speed is then a constant\r\n // 1× by construction, with zero lag. A DELAYED frame (busy main thread)\r\n // reuses the last normal interval instead, so the trace never jumps, just\r\n // runs a touch slow and resumes. No averaging: a smoothed period lags\r\n // frame-rate changes and the speed visibly surges when the frame rate\r\n // recovers (the \"left edge accelerates then settles\" artifact).\r\n let framePeriodMs = 16.7\r\n // Frozen pixel cache for true erase-bar rendering: the trace image is a\r\n // static buffer; only the pixels the sweep bar has just passed are\r\n // refreshed, everything else stays frozen. The image therefore never\r\n // scrolls, never jumps as a whole, and is frame-rate independent.\r\n let traceCache: number[] = []\r\n let lastScanX = -1\r\n // Continuous phase clock: the trace is ONE unbroken signal in absolute\r\n // time — the phase advances (bpm/60) per second, and each pixel samples\r\n // the signal at its own time. A ring of (time, phase) samples lets us\r\n // evaluate the phase at any sample time in the past, so a mid-sweep rate\r\n // change keeps the pixels crossed afterwards on the SAME phase function\r\n // as the ones before it: the new line continues the old one (old data is\r\n // never repainted, nothing is mixed).\r\n let phaseAcc = 0\r\n let phaseHistory: Array<{ t: number; phase: number }> = []\r\n // Phase at an arbitrary past sample time — linear interpolation over the\r\n // history (samples are ~a frame apart, so the error stays negligible even\r\n // while the rate ramps at 6 BPM/s).\r\n const phaseAt = (tX: number): number => {\r\n const arr = phaseHistory\r\n if (arr.length === 0) return 0\r\n if (tX <= arr[0].t) return arr[0].phase\r\n const last = arr[arr.length - 1]\r\n if (tX >= last.t) return last.phase\r\n let lo = 0\r\n let hi = arr.length - 1\r\n while (hi - lo > 1) {\r\n const m = (lo + hi) >> 1\r\n if (arr[m].t <= tX) lo = m\r\n else hi = m\r\n }\r\n const a = arr[lo]\r\n const b = arr[hi]\r\n return a.phase + ((tX - a.t) / (b.t - a.t)) * (b.phase - a.phase)\r\n }\r\n const paint = (now: number): void => {\r\n if (lastPaintReal === 0) {\r\n lastPaintReal = now\r\n displayNow = now\r\n }\r\n const realDt = Math.max(0, (now - lastPaintReal) / 1_000)\r\n lastPaintReal = now\r\n if (realDt > 0 && realDt < 0.05) framePeriodMs = realDt * 1_000\r\n const dt = framePeriodMs / 1_000\r\n displayNow += dt * 1_000\r\n // Constant-rate ramp (hospital monitor cadence): the rate eases toward\r\n // its target at BPM_RAMP_PER_SECOND, so a rate change takes seconds and\r\n // the beat spacing visibly densifies beat by beat.\r\n const diff = targetRef.current - bpmRef.current\r\n const step = BPM_RAMP_PER_SECOND * dt\r\n if (diff > step) bpmRef.current += step\r\n else if (diff < -step) bpmRef.current -= step\r\n else bpmRef.current = targetRef.current\r\n const c = canvasRef.current\r\n const g = ctxRef.current\r\n if (c === null || g === null) return\r\n // Size is maintained by the ResizeObserver (widthRef / canvas.width);\r\n // paint reads it directly — no per-frame getBoundingClientRect, so no\r\n // forced synchronous layout. Fixed logical height keeps the amplitude\r\n // independent of the live layout (the first paint once saw height 0 and\r\n // collapsed the trace to a flat line).\r\n const dpr = dprRef.current\r\n const w = widthRef.current\r\n const h = ECG_HEIGHT\r\n const wantW = Math.round(w * dpr)\r\n const wantH = Math.round(h * dpr)\r\n if (c.width !== wantW || c.height !== wantH) {\r\n c.width = wantW\r\n c.height = wantH\r\n }\r\n g.setTransform(dpr, 0, 0, dpr, 0, 0)\r\n g.clearRect(0, 0, w, h)\r\n\r\n const tNow = displayNow / 1_000\r\n // Absolute paper speed: px per second is constant, width-independent.\r\n const secondsPerPixel = 1 / PAPER_SPEED_PX_PER_SECOND\r\n const mid = h / 2\r\n const amp = h * 0.5\r\n const wander = 0.05 * Math.sin(tNow * 0.6) + 0.035 * Math.sin(tNow * 1.7 + 1.3)\r\n const bpm = bpmRef.current\r\n // Flatline is a MODE, not a smoothed value: as soon as the target is a\r\n // stopped heart, draw the line — don't wait for the 6 BPM/s ramp to\r\n // cross an arbitrary threshold.\r\n const flatline = targetRef.current === 0\r\n\r\n // Erase-bar sweep: the bar moves right → left; pixels it has just\r\n // passed are re-sampled (frozen update), the rest of the image is\r\n // untouched. The right edge is pinned to \"now\"; beat spacing is\r\n // width-independent (fixed paper speed).\r\n const sweepPeriod = w / PAPER_SPEED_PX_PER_SECOND\r\n const tInSweep = ((tNow % sweepPeriod) + sweepPeriod) % sweepPeriod\r\n const scanX = w - tInSweep * PAPER_SPEED_PX_PER_SECOND // w → 0\r\n const scanXInt = Math.round(scanX)\r\n\r\n // Advance the continuous phase clock by this frame's display time, and\r\n // keep enough history for the oldest pixel the strip can still show\r\n // (two sweep periods back). The first frame seeds a constant-rate\r\n // history — nothing can have changed before the very first paint — so\r\n // the initial full build is a coherent window.\r\n if (phaseHistory.length === 0) {\r\n phaseAcc = (tNow * bpm) / 60\r\n phaseHistory.push({\r\n t: tNow - 2 * sweepPeriod - 2,\r\n phase: ((tNow - 2 * sweepPeriod - 2) * bpm) / 60,\r\n })\r\n phaseHistory.push({ t: tNow, phase: phaseAcc })\r\n } else {\r\n phaseAcc += (bpm / 60) * dt\r\n phaseHistory.push({ t: tNow, phase: phaseAcc })\r\n }\r\n const lookback = 2 * sweepPeriod + 2\r\n while (phaseHistory.length > 2 && phaseHistory[0].t < tNow - lookback) {\r\n phaseHistory.shift()\r\n }\r\n const yNow = (x: number): number => {\r\n if (flatline) return mid // the whale's heart has stopped — a flat line\r\n let v = -Infinity\r\n // Sweep sample: pixel x is (re)written when the bar crosses it, so the\r\n // sample must be anchored to the sweep, not the frame. The frame-time\r\n // lookback `tNow - (w - x)·secondsPerPixel` evaluated at a crossing\r\n // cancels its own offset — the bar reaches x exactly (w - x) px into\r\n // the sweep, so the lookback always lands on the sweep-start instant\r\n // k·sweepPeriod and EVERY swept pixel receives the SAME sample → the\r\n // trace flattens to one level once the bar has crossed the strip.\r\n // Anchoring to the sweep start (`(tNow - tInSweep) - (w - x)·spp`, with\r\n // tNow - tInSweep = k·sweepPeriod) keeps the right edge pinned to the\r\n // sweep start (\"now\") and each pixel's sample x-dependent — the swept\r\n // region redraws as a real, time-normal waveform window.\r\n for (let i = 0; i < 4; i += 1) {\r\n const tX = (tNow - tInSweep) - (w - (x + i * 0.25)) * secondsPerPixel\r\n // Phase comes from the CONTINUOUS clock, not `tX * bpm/60`: the\r\n // product assumes a constant rate and tears the strip apart when\r\n // the rate changes mid-sweep (old pixels stay at the old rate, new\r\n // ones use the new rate). The clock makes every pixel — crossed\r\n // before or after the change — a sample of the SAME unbroken\r\n // signal, so the new line simply continues the old one.\r\n const phase = ((phaseAt(tX) % 1) + 1) % 1\r\n const s = ecgValue(phase)\r\n if (s > v) v = s\r\n }\r\n return mid - (v + wander) * amp\r\n }\r\n if (traceCache.length !== w + 1) {\r\n // Width changed: rebuild the buffer, right-anchored, repaint once.\r\n traceCache = new Array<number>(w + 1)\r\n for (let x = 0; x <= w; x += 1) traceCache[x] = yNow(x)\r\n lastScanX = scanXInt\r\n } else if (lastScanX > scanXInt) {\r\n // Refresh exactly the pixels the sweep has just passed.\r\n for (let x = scanXInt; x <= lastScanX && x <= w; x += 1) {\r\n traceCache[x] = yNow(x)\r\n }\r\n lastScanX = scanXInt\r\n } else if (lastScanX < scanXInt) {\r\n // Wrap: the bar jumped back to the right edge. Refresh its new head\r\n // pixel so the trace continues from the current instant instead of\r\n // leaving stale data at the right edge (the reported seam).\r\n traceCache[scanXInt] = yNow(scanXInt)\r\n lastScanX = scanXInt\r\n } else {\r\n lastScanX = scanXInt\r\n }\r\n\r\n // Chiral ghost over the frozen image, faint.\r\n g.beginPath()\r\n for (let x = 0; x <= w; x += 1) {\r\n const y = traceCache[x]\r\n if (x === 0) g.moveTo(x + 3, y)\r\n else g.lineTo(x + 3, y)\r\n }\r\n g.globalAlpha = 0.1\r\n g.strokeStyle = 'rgba(111, 219, 226, 1)'\r\n g.lineWidth = 1\r\n g.stroke()\r\n g.globalAlpha = 1\r\n\r\n // The frozen trace, whole window.\r\n g.beginPath()\r\n for (let x = 0; x <= w; x += 1) {\r\n const y = traceCache[x]\r\n if (x === 0) g.moveTo(x, y)\r\n else g.lineTo(x, y)\r\n }\r\n g.strokeStyle = MODE_COLOR[modeRef.current]\r\n g.lineWidth = 1.4\r\n g.lineJoin = 'round'\r\n g.lineCap = 'round'\r\n g.stroke()\r\n\r\n // The sweep bar itself: bright core + soft halo.\r\n g.fillStyle = 'rgba(255, 180, 84, 0.16)'\r\n g.fillRect(scanX - 5, 0, 10, h)\r\n g.fillStyle = 'rgba(255, 224, 190, 0.95)'\r\n g.fillRect(scanX - 1, 0, 2, h)\r\n }\r\n\r\n if (reduced) {\r\n paint(performance.now()) // one static frame\r\n return () => { observer.disconnect() }\r\n }\r\n let raf = 0\r\n const loop = (now: number): void => {\r\n raf = requestAnimationFrame(loop)\r\n paint(now)\r\n }\r\n raf = requestAnimationFrame(loop)\r\n return () => {\r\n observer.disconnect()\r\n cancelAnimationFrame(raf)\r\n }\r\n }, [])\r\n\r\n // Status word: real model state wins; the flavor rotation only plays while idle.\r\n const flavor = STATUS_KEYS[Math.floor(ui.elapsed / STATUS_ROTATE_S) % STATUS_KEYS.length]\r\n const status = live.error !== null\r\n ? `⚠ ${live.error.slice(0, 16)}`\r\n : live.retrying\r\n ? t('status.flatline')\r\n : live.toolName !== null\r\n ? `EXEC · ${live.toolName}`\r\n : live.partialText !== ''\r\n ? `⇢ ${live.partialText.slice(-18)}`\r\n : t(flavor)\r\n\r\n return (\r\n <div className=\"cp-line\" role=\"group\" aria-label={t('line.aria')} data-chiral-pulse data-mode={ui.mode} data-rev=\"20\">\r\n <div className=\"cp-lineBpm\">\r\n {ui.bpm}\r\n </div>\r\n\r\n <div className=\"cp-lineEcgWrap\">\r\n <canvas ref={canvasRef} className=\"cp-lineEcg\" aria-hidden />\r\n </div>\r\n\r\n <div className=\"cp-lineReadout\">\r\n <div className=\"cp-lineStatus\" title={status}>{status}</div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n","/**\n * CHIRAL PULSE — dictionary namespace.\n *\n * The DS monitor idiom stays English in both locales (it is part of the\n * aesthetic: \"LINK STABLE\", \"TIME TO COMPLETION\"); the zh side translates\n * the labels a user actually reads.\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'chiral'\n\n/** Dictionary keys of the `chiral` namespace (string-literal union). */\nexport type ChiralKey =\n | 'line.aria'\n | 'status.stable'\n | 'status.bonded'\n | 'status.chiral'\n | 'status.doom'\n | 'status.keep'\n | 'status.voidout'\n | 'status.odradek'\n | 'status.flatline'\n\n/** English dictionary. */\nexport const en: Record<ChiralKey, string> = {\n 'line.aria': 'BB vital-signs strip — CHIRAL PULSE',\n 'status.stable': 'LINK STABLE',\n 'status.bonded': 'BB BONDED',\n 'status.chiral': 'CHIRAL DENSITY: NOMINAL',\n 'status.doom': 'DOOMS LEVEL: 0',\n 'status.keep': 'KEEP ON KEEPING ON',\n 'status.voidout': 'NO VOIDOUT DETECTED',\n 'status.odradek': 'ODRADEK SYNC: OK',\n 'status.flatline': '♥ FLATLINE',\n}\n\n/** Chinese dictionary. */\nexport const zh: Record<ChiralKey, string> = {\n 'line.aria': 'BB 生命体征走纸 — CHIRAL PULSE 手性脉冲',\n 'status.stable': '链路稳定',\n 'status.bonded': 'BB 连接完成',\n 'status.chiral': '手性密度:正常',\n 'status.doom': 'DOOMS 等级:0',\n 'status.keep': '继续前进 · KEEP ON KEEPING ON',\n 'status.voidout': '未检测到虚爆',\n 'status.odradek': '奥卓克同步:正常',\n 'status.flatline': '♥ 心脏停跳',\n}\n","/**\n * CHIRAL PULSE — the Death Stranding sheet, two layers.\n *\n * LAYER 1 — the global skin. The whole app paints from `--dsw-*` variables\n * (ui-theme's design platform: alias tokens reference static tokens, so\n * remapping the palette re-skins every component without touching its\n * structure). This sheet FORCES the DS look under BOTH theme modes: a deep\n * blue-black machine body, cold blue-grey hairlines, amber reserved for\n * emphasis (the heartbeat waveform, hover blooms) — and the deepseek brand\n * blues are left untouched, so the whale mark stays DeepSeek blue.\n *\n * LAYER 2 — the atmosphere. A fixed full-viewport CRT scanline weave, a\n * faint chiral lattice, and a vignette, all pointer-transparent. Plus the\n * BB vital-signs strip that docks under the composer stats: a 26px monitor\n * paper feed whose scrolling ECG is the hero, with the BPM and status read.\n *\n * Every rule is scoped under `.cp-*` (except the token remap, which must\n * target `body`), rides one owned <style data-plugin> tag, and the loader\n * removes it on unload.\n */\n\nexport const CHIRAL_CSS = `\n/* ────────────────────────────────────────────────────────────────────────\n LAYER 1 · global DS skin — dark blue-black, both theme modes\n ──────────────────────────────────────────────────────────────────────── */\n\n/* Alias-level remap: independent of the static scale's role flip between\n themes, so the DS look is identical under light and dark settings. */\nbody[data-ds-dark-theme],\nbody:not([data-ds-dark-theme]) {\n /* machine body — blue-grey with air, not a black void */\n --dsw-alias-bg-base: rgb(13, 17, 23);\n --dsw-alias-bg-layer-1: rgb(17, 22, 29);\n --dsw-alias-bg-layer-2: rgb(21, 27, 35);\n --dsw-alias-bg-layer-3: rgb(26, 33, 42);\n --dsw-alias-bg-overlay: rgb(31, 40, 51);\n --dsw-alias-bg-mask-1: rgba(0, 0, 0, 0.5);\n --dsw-alias-bg-mask-2: rgba(0, 0, 0, 0.22);\n --dsw-alias-bg-mask-3: rgba(0, 0, 0, 0.5);\n --dsw-alias-bg-mask-drop: rgba(10, 14, 19, 0.72);\n --dsw-alias-bg-skeleton: rgba(150, 170, 200, 0.07);\n /* Setting rows / select chips (language, agent preset, model, permissions…)\n paint from these; without an override they stay the LIGHT theme's\n near-white and produce white-on-white text. */\n --dsw-alias-bg-module-platform: rgb(15, 20, 27);\n --dsw-alias-bg-multi-select: rgb(18, 24, 32);\n --dsw-alias-fill-tsp-secondary: rgb(18, 24, 32);\n\n /* text: cold blue-grey */\n --dsw-alias-label-primary: rgb(235, 240, 246);\n --dsw-alias-label-secondary: rgb(178, 190, 205);\n --dsw-alias-label-tertiary: rgb(140, 153, 170);\n --dsw-alias-label-caption: rgb(108, 122, 141);\n --dsw-alias-label-dimmed: rgb(94, 108, 127);\n --dsw-alias-label-primary-dimmed: rgb(205, 213, 222);\n --dsw-alias-label-primary-inverted: rgb(20, 26, 35);\n --dsw-alias-label-primary-foreground: rgb(13, 17, 23);\n --dsw-alias-label-primary-bluish: rgb(200, 214, 232);\n --dsw-alias-brand-text: rgb(235, 240, 246);\n --dsw-alias-brand-primary: rgb(103, 158, 254);\n --dsw-alias-brand-primary-invert: rgb(235, 240, 246);\n\n /* hairlines: cold blue, readable against the body */\n --dsw-alias-border-l1: rgba(140, 170, 215, 0.15);\n --dsw-alias-border-l2: rgba(140, 170, 215, 0.24);\n --dsw-alias-border-l2-darkmode-thin: rgba(140, 170, 215, 0.19);\n --dsw-alias-border-l3: rgba(140, 170, 215, 0.34);\n --dsw-alias-border-l4: rgba(140, 170, 215, 0.46);\n --dsw-alias-border-inverted: rgba(255, 255, 255, 0.09);\n --dsw-alias-border-inverted2: rgba(255, 255, 255, 0.11);\n\n /* hovers: amber bloom, kept subtle */\n --dsw-alias-interactive-bg-hover: rgba(255, 180, 84, 0.08);\n --dsw-alias-interactive-bg-active: rgba(255, 180, 84, 0.12);\n --dsw-alias-interactive-bg-hover-accent: rgba(255, 180, 84, 0.14);\n --dsw-alias-interactive-bg-hover-solid: rgb(22, 29, 38);\n --dsw-alias-interactive-bg-hover-danger: rgba(242, 90, 90, 0.14);\n\n /* buttons: brand blue stays the primary action */\n --dsw-alias-button-primary-dimmed: rgb(30, 40, 53);\n --dsw-alias-button-primary-hover: rgb(124, 172, 255);\n --dsw-alias-button-ghost-active-fill: rgb(22, 29, 38);\n --dsw-alias-button-ghost-active-hover: rgb(27, 35, 46);\n --dsw-alias-button-ghost-active-border: rgb(140, 170, 215);\n --dsw-alias-button-floating-fill: rgb(19, 25, 33);\n --dsw-alias-button-floating-hover: rgb(24, 31, 41);\n --dsw-alias-button-elevated-fill: rgb(22, 29, 38);\n /* Contrast fill (attachment rail): pale case + DARK inverted ink — the\n wordmark badge and toasts also ride label-primary-inverted, so the pair\n (pale fill, dark ink) stays readable everywhere. */\n --dsw-alias-button-contrast-fill: rgb(205, 213, 222);\n --dsw-alias-button-tool-bar-fill: rgba(140, 170, 215, 0.24);\n --dsw-alias-button-tool-bar-fill-invisible: rgba(140, 170, 215, 0.13);\n --dsw-alias-button-tool-bar-hover: rgba(140, 170, 215, 0.32);\n\n /* surfaces */\n --dsw-specific-sidebar-fill: rgb(9, 12, 17);\n --dsw-specific-sidebar-nav-item-active: rgb(20, 27, 36);\n --dsw-specific-sidebar-nav-item-active-accent: rgb(27, 36, 48);\n --dsw-specific-sidebar-nav-item-hover: rgb(15, 20, 28);\n --dsw-specific-bubble: rgb(18, 24, 32);\n --dsw-specific-bubble-highlight: rgb(24, 32, 42);\n --dsw-specific-input-major: rgb(15, 20, 27);\n --dsw-specific-login-input: rgb(12, 16, 22);\n --dsw-specific-menu: rgb(21, 27, 35);\n --dsw-specific-selector: rgb(20, 26, 34);\n --dsw-specific-tip: rgb(16, 21, 28);\n --dsw-alias-markdown-code-block: rgb(10, 14, 19);\n --dsw-alias-markdown-code-block-banner: rgb(13, 17, 23);\n --dsw-alias-markdown-inline-code: rgb(18, 24, 32);\n --dsw-alias-markdown-code-segment-selected: rgb(16, 21, 28);\n --dsw-alias-markdown-code-segment-unselected: rgb(12, 16, 22);\n --dsw-alias-markdown-placeholder: rgb(16, 21, 28);\n --dsw-alias-markdown-tag: rgb(18, 24, 32);\n --dsw-alias-markdown-citation: rgb(22, 29, 38);\n\n /* floats */\n --dsw-alias-toast-bg: rgb(22, 29, 38);\n --dsw-alias-tooltip-bg: rgb(20, 26, 34);\n --dsw-alias-scrollbar-bg-l1: rgb(13, 18, 25);\n --dsw-alias-scrollbar-bg-l2: rgb(17, 23, 31);\n --dsw-alias-scrollbar-hover-l1: rgb(34, 44, 58);\n --dsw-alias-scrollbar-hover-l2: rgb(42, 54, 70);\n\n /* status: amber stays the warn/emphasis hue; success leans chiral cyan */\n --dsw-alias-state-warn-primary: rgb(245, 158, 11);\n --dsw-alias-state-warn-secondary: rgb(247, 173, 49);\n --dsw-alias-state-warn-label: rgb(221, 134, 41);\n --dsw-alias-state-warn-tertiary: rgb(39, 36, 31);\n --dsw-alias-state-success-primary: rgb(52, 205, 168);\n --dsw-alias-state-success-secondary: rgb(94, 222, 189);\n --dsw-alias-state-success-tertiary: rgb(12, 28, 24);\n /* Business tint (hero \"preview\" badge et al.): dark case so the pale\n primary-bluish ink stays readable — the light-theme default is near-white. */\n --dsw-alias-state-business-tertiary: rgb(26, 34, 46);\n --dsw-static-green-400: rgb(94, 222, 189);\n --dsw-static-green-500: rgb(52, 205, 168);\n}\n\n/* Focus ring: amber, the DS highlight color. */\n:focus-visible {\n outline: 1px solid rgba(255, 180, 84, 0.65) !important;\n outline-offset: 2px;\n}\n\n/* Ambient bloom behind the app. */\nbody {\n background-image:\n radial-gradient(1100px 620px at 12% -8%, rgba(111, 219, 226, 0.04), transparent 60%),\n radial-gradient(900px 560px at 108% 112%, rgba(103, 158, 254, 0.05), transparent 60%);\n background-attachment: fixed;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n LAYER 2 · atmosphere overlays (injected as fixed elements)\n ──────────────────────────────────────────────────────────────────────── */\n.cp-atmo {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 2147483000;\n}\n.cp-atmo-scanlines {\n background: repeating-linear-gradient(\n 0deg,\n rgba(255, 255, 255, 0.024) 0 1px,\n transparent 1px 3px\n );\n mix-blend-mode: overlay;\n}\n.cp-atmo-lattice {\n opacity: 0.55;\n background:\n repeating-linear-gradient(60deg, transparent 0 17px, rgba(103, 158, 254, 0.03) 17px 18px),\n repeating-linear-gradient(120deg, transparent 0 17px, rgba(111, 219, 226, 0.028) 17px 18px);\n}\n.cp-atmo-vignette {\n background: radial-gradient(120% 100% at 50% 40%, transparent 55%, rgba(0, 0, 0, 0.24) 100%);\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n BB vital-signs strip · the heartbeat paper feed\n ──────────────────────────────────────────────────────────────────────── */\n.cp-line {\n --cp-amber: #ffb454;\n --cp-amber-bright: #ffd9a0;\n --cp-cyan: #6fdbe2;\n --cp-dim: #64727f;\n /* flex: none — the hero (blank-session) composer column squeezes its\n children on short viewports; the monitor strip must never shrink. */\n flex: none;\n display: flex;\n align-items: center;\n gap: 12px;\n height: 26px;\n min-height: 26px;\n margin: 3px 0 4px;\n padding: 0 10px;\n border: 1px solid rgba(140, 170, 215, 0.26);\n border-radius: 0;\n background:\n linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),\n linear-gradient(180deg, rgba(14, 19, 26, 0.92), rgba(9, 13, 18, 0.94));\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.05),\n inset 0 0 16px rgba(103, 158, 254, 0.06);\n color: #c9d3dc;\n font-family: ui-monospace, \"Cascadia Mono\", \"JetBrains Mono\", Consolas, \"Courier New\", monospace;\n overflow: hidden;\n clip-path: polygon(\n 7px 0,\n 100% 0,\n 100% calc(100% - 7px),\n calc(100% - 7px) 100%,\n 0 100%,\n 0 7px\n );\n}\n\n.cp-lineBpm {\n /* Fixed width: a 3-digit readout (42 → 150) must not widen the block and\n squeeze the paper area — that would shrink the trace window and pull the\n left edge rightward as the rate climbs. */\n flex: none;\n width: 48px;\n text-align: center;\n font-size: 16px;\n line-height: 1;\n letter-spacing: 0.5px;\n color: var(--cp-amber-bright);\n text-shadow: 0 0 10px rgba(255, 180, 84, 0.5);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.cp-lineEcgWrap {\n /* basis 0 + grow: the paper area takes exactly the flex-allocated width;\n never shrink (a squeezed strip would shrink the canvas bitmap and make\n the trace speed depend on the window width). */\n flex: 1 1 0;\n min-width: 100px;\n height: 22px;\n position: relative;\n border-radius: 0;\n border: 1px solid rgba(140, 170, 215, 0.16);\n /* Static paper grid lives in CSS; the canvas above it only paints the trace. */\n background:\n repeating-linear-gradient(0deg, rgba(140, 170, 215, 0.07) 0 1px, transparent 1px 11px),\n repeating-linear-gradient(90deg, rgba(140, 170, 215, 0.06) 0 1px, transparent 1px 11px),\n rgba(7, 10, 15, 0.55);\n}\n/* The canvas fills its wrapper exactly (absolute), so its intrinsic size can\n never distort the flex layout or the trace during remounts. */\n.cp-lineEcg {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n display: block;\n border: 0;\n background: transparent;\n}\n\n.cp-lineReadout {\n flex: none;\n width: 132px;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n overflow: hidden;\n}\n.cp-lineStatus {\n font-size: 8px;\n letter-spacing: 1.8px;\n text-transform: uppercase;\n color: var(--cp-cyan);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .cp-lineEcg {\n opacity: 0.9;\n }\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n ECG paper grid + activity-mode color coupling\n ──────────────────────────────────────────────────────────────────────── */\n.cp-line[data-mode=\"think\"] .cp-lineBpm {\n color: #7fe3e8;\n text-shadow: 0 0 10px rgba(111, 219, 226, 0.55);\n}\n.cp-line[data-mode=\"tool\"] .cp-lineBpm {\n color: #ff9b7a;\n text-shadow: 0 0 10px rgba(255, 122, 77, 0.6);\n}\n.cp-line[data-mode=\"run\"] .cp-lineBpm {\n color: #ffd9a0;\n}\n.cp-line[data-mode=\"think\"] .cp-lineStatus,\n.cp-line[data-mode=\"tool\"] .cp-lineStatus {\n color: #9fe8ec;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Message-flow dressing — DS glyphs on each node kind\n ──────────────────────────────────────────────────────────────────────── */\n[data-chat-flow-kind] {\n position: relative;\n}\n[data-chat-flow-kind=\"assistant\"] {\n padding-left: 18px;\n}\n[data-chat-flow-kind=\"assistant\"]::before {\n content: \"✦\";\n position: absolute;\n left: 4px;\n top: 12px;\n color: rgba(255, 180, 84, 0.85);\n font-size: 11px;\n line-height: 1;\n text-shadow: 0 0 8px rgba(255, 180, 84, 0.6);\n}\n[data-chat-flow-kind=\"assistant\"]::after {\n content: \"\";\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 1px;\n background: linear-gradient(180deg, transparent, rgba(255, 180, 84, 0.35), transparent);\n}\n[data-chat-flow-kind=\"user\"],\n[data-chat-flow-kind=\"steering\"] {\n padding-right: 18px;\n}\n[data-chat-flow-kind=\"user\"]::before,\n[data-chat-flow-kind=\"steering\"]::before {\n content: \"▸▸\";\n position: absolute;\n right: 2px;\n top: 4px;\n color: rgba(120, 150, 195, 0.75);\n font-size: 10px;\n line-height: 1;\n letter-spacing: -1px;\n}\n[data-chat-flow-kind=\"context\"] {\n padding-left: 16px;\n}\n[data-chat-flow-kind=\"context\"]::before {\n content: \"⇢\";\n position: absolute;\n left: 2px;\n top: 12px;\n color: rgba(111, 219, 226, 0.7);\n font-size: 11px;\n line-height: 1;\n}\n[data-variant=\"think\"] {\n border-left: 2px solid rgba(111, 219, 226, 0.35);\n padding-left: 10px;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Tool-card chassis — the ui-primitives block family gets a DS case\n ──────────────────────────────────────────────────────────────────────── */\n[data-tool],\n[data-search],\n[data-read],\n[data-web],\n[data-diff],\n[data-terminal],\n[data-context-injection-body] {\n border: 1px solid rgba(140, 170, 215, 0.24) !important;\n border-radius: 0 !important;\n background:\n linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),\n linear-gradient(180deg, rgba(15, 20, 27, 0.88), rgba(9, 13, 18, 0.92)) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.05),\n inset 0 0 18px rgba(103, 158, 254, 0.05);\n position: relative;\n clip-path: polygon(\n 7px 0,\n 100% 0,\n 100% calc(100% - 7px),\n calc(100% - 7px) 100%,\n 0 100%,\n 0 7px\n );\n}\n[data-terminal]::before {\n content: \"❯_\";\n position: absolute;\n right: 8px;\n top: 6px;\n color: rgba(111, 219, 226, 0.35);\n font-size: 10px;\n font-family: ui-monospace, Consolas, monospace;\n}\n[data-read]::before {\n content: \"▤\";\n position: absolute;\n right: 8px;\n top: 6px;\n color: rgba(120, 150, 195, 0.4);\n font-size: 11px;\n}\n[data-search]::before {\n content: \"⌕\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(255, 180, 84, 0.4);\n font-size: 13px;\n}\n[data-web]::before {\n content: \"⌖\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(111, 219, 226, 0.4);\n font-size: 12px;\n}\n[data-diff]::before {\n content: \"⇄\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(120, 150, 195, 0.4);\n font-size: 12px;\n}\n[data-tool]::before {\n content: \"⚙\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(255, 180, 84, 0.4);\n font-size: 11px;\n}\n[data-context-injection-body]::before {\n content: \"⇢\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(111, 219, 226, 0.4);\n font-size: 11px;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Composer details\n ──────────────────────────────────────────────────────────────────────── */\n[data-composer-seat] textarea {\n caret-color: #ffb454;\n}\n[data-composer-seat] textarea:focus {\n caret-color: #ffd9a0;\n}\n/* Composer seat: squared, no extra frame — a visible outline on the big hero\n card read as a jarring border. */\n[data-composer-seat] {\n border-radius: 0;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Dialogs, menus, tooltips, toasts — the floating DS surfaces\n ──────────────────────────────────────────────────────────────────────── */\n[role=\"dialog\"] {\n border: 1px solid rgba(255, 180, 84, 0.35) !important;\n /* Inner hairline frame — the DS double-cased panel. */\n outline: 1px solid rgba(140, 170, 215, 0.22);\n outline-offset: -6px;\n border-radius: 0 !important;\n background: linear-gradient(180deg, rgba(15, 20, 27, 0.98), rgba(10, 14, 19, 0.99)) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.07),\n inset 0 0 26px rgba(103, 158, 254, 0.06) !important;\n}\n[role=\"menu\"] {\n border: 1px solid rgba(140, 170, 215, 0.3) !important;\n border-radius: 0 !important;\n background: rgba(13, 18, 25, 0.97) !important;\n}\n[role=\"menuitem\"]:hover {\n background: rgba(255, 180, 84, 0.08) !important;\n}\n[role=\"tooltip\"] {\n border: 1px solid rgba(140, 170, 215, 0.32) !important;\n border-radius: 0 !important;\n background: rgba(15, 20, 27, 0.97) !important;\n}\n[role=\"alert\"] {\n border: 1px solid rgba(255, 180, 84, 0.38) !important;\n border-radius: 0 !important;\n background: rgba(15, 20, 27, 0.97) !important;\n clip-path: polygon(\n 8px 0,\n 100% 0,\n 100% calc(100% - 8px),\n calc(100% - 8px) 100%,\n 0 100%,\n 0 8px\n );\n}\n\n/* Toast (the only alert portaled straight onto body): DS gold badge —\n amber case, dark ink, chamfered. Inline error rows keep the dark case\n above; this rule wins for the fixed top-center banner. */\nbody > [role=\"alert\"] {\n border: 1px solid rgba(255, 196, 120, 0.7) !important;\n border-radius: 0 !important;\n background: linear-gradient(180deg, #ffbe6b, #e09a3c) !important;\n color: rgb(28, 18, 6) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.4),\n 0 10px 32px rgba(0, 0, 0, 0.5) !important;\n clip-path: polygon(\n 10px 0,\n 100% 0,\n 100% calc(100% - 10px),\n calc(100% - 10px) 100%,\n 0 100%,\n 0 10px\n );\n}\n\n/* Session-header action + utility buttons (Session log, jobs…):\n DS chamfered buttons. Session log lives in .utilities, jobs in .actions. */\n[data-slot=\"conversation.session.header.actions\"] button,\n[data-slot=\"conversation.session.header.utilities\"] button {\n border-radius: 0 !important;\n clip-path: polygon(\n 6px 0,\n 100% 0,\n 100% calc(100% - 6px),\n calc(100% - 6px) 100%,\n 0 100%,\n 0 6px\n );\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Sidebar: DS hairline on the conversation-history column\n ──────────────────────────────────────────────────────────────────────── */\n[data-sidebar-collapsed] > div:first-child {\n border-right: 1px solid rgba(140, 170, 215, 0.22);\n box-shadow: inset -1px 0 0 rgba(255, 180, 84, 0.06);\n}\n\n/* Sidebar buttons (New Session etc.): DS chamfered corners. */\n[data-slot=\"sidebar\"] button {\n border-radius: 0 !important;\n clip-path: polygon(\n 6px 0,\n 100% 0,\n 100% calc(100% - 6px),\n calc(100% - 6px) 100%,\n 0 100%,\n 0 6px\n );\n}\n\n/* Workspace rows (workspaces, sessions, groups): DS chamfered entries. */\n[role=\"treeitem\"] {\n border-radius: 0 !important;\n clip-path: polygon(\n 5px 0,\n 100% 0,\n 100% calc(100% - 5px),\n calc(100% - 5px) 100%,\n 0 100%,\n 0 5px\n );\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n DS chamfer everywhere else: kill the round-corner language\n ──────────────────────────────────────────────────────────────────────── */\nbutton,\ninput,\ntextarea,\nselect,\n[role=\"tab\"],\n[role=\"menuitem\"],\n[role=\"treeitem\"] {\n border-radius: 0 !important;\n}\n\n`\n","/**\n * CHIRAL PULSE, browser half: the Death Stranding skin plus the BB\n * vital-signs strip above the composer.\n *\n * Two contributions:\n * 1. The global DS skin — a `--dsw-*` token remap (blue-black machine body,\n * amber hairlines, sand-paper light variant), the DeepSeek whale mark's\n * brand blues untouched, plus three pointer-transparent atmosphere\n * overlays (CRT scanlines, chiral lattice, vignette).\n * 2. The heartbeat strip on `conversation.input.dock` — a 26px monitor\n * paper feed whose scrolling ECG is the hero and whose BPM follows the\n * session's live activity (model streaming, tools executing).\n *\n * The plugin owns no state of its own beyond the component's local beat\n * engine; every figure arrives through the session standard kit. All styles\n * ride one owned <style data-plugin> tag so the loader removes them on\n * unload/reload.\n */\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\n// Type-only: the renderer now owns ctx.slots.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the conversation.input.dock SlotMap declaration.\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { HeartLine } from './HeartLine.tsx'\nimport { en, NS, zh, type ChiralKey } from './locales.ts'\nimport { CHIRAL_CSS } from './style.ts'\n\nexport { HeartLine } from './HeartLine.tsx'\nexport type { HeartLineProps } from './HeartLine.tsx'\nexport type { ChiralKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The BB monitor strip's copy. */\n chiral: ChiralKey\n }\n}\n\n/** Required services: the slot registry and the locale service. */\nexport const inject = ['slots', 'locale']\n\n/**\n * Client plugin body: register dictionaries, inject the DS sheet and the\n * atmosphere overlays, and dock the heartbeat strip under the composer.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'chiral-pulse: dictionaries')\n\n ctx.effect(() => {\n const tag = document.createElement('style')\n tag.dataset.plugin = 'chiral-pulse'\n tag.textContent = CHIRAL_CSS\n document.head.appendChild(tag)\n return () => { tag.remove() }\n }, 'chiral-pulse: styles')\n\n // Atmosphere overlays: scanlines + chiral lattice + vignette, all\n // pointer-transparent, riding the top of the stacking order.\n ctx.effect(() => {\n const layers = [\n { className: 'cp-atmo cp-atmo-scanlines', label: 'scanlines' },\n { className: 'cp-atmo cp-atmo-lattice', label: 'lattice' },\n { className: 'cp-atmo cp-atmo-vignette', label: 'vignette' },\n ]\n const nodes = layers.map(({ className }) => {\n const el = document.createElement('div')\n el.className = className\n el.setAttribute('aria-hidden', 'true')\n document.body.appendChild(el)\n return el\n })\n return () => {\n for (const el of nodes) el.remove()\n }\n }, 'chiral-pulse: atmosphere')\n\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n id: 'chiral-pulse',\n // Above the composer card, under the goal strip: the pulse feed rides\n // with the input it monitors.\n order: 20,\n locale: NS,\n }, HeartLine))\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chiral-pulse",
3
- "version": "1.2.7",
3
+ "version": "1.3.0",
4
4
  "description": "CHIRAL PULSE - a Death Stranding-styled BB pod vital-signs monitor for the DeepSeek Harness web UI: the session's heartbeat waveform is the hero, and the pulse reacts to real agent activity.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -28,7 +28,8 @@
28
28
  "scripts": {
29
29
  "bundle": "tsdown",
30
30
  "watch": "tsdown --watch",
31
- "typecheck": "tsc --noEmit"
31
+ "typecheck": "tsc --noEmit",
32
+ "test": "npm run bundle && node --test tests/*.test.cjs"
32
33
  },
33
34
  "dsh": {
34
35
  "bundle": {
@@ -37,7 +38,9 @@
37
38
  "client": {
38
39
  "platform": "web",
39
40
  "inject": [
40
- "@deepseek-ai/dsh-client-runtime",
41
+ "@deepseek-ai/dsh-client-ui-session",
42
+ "@deepseek-ai/dsh-client-ui-renderer",
43
+ "@deepseek-ai/dsh-client-ui-chat",
41
44
  "@deepseek-ai/dsh-client-locale",
42
45
  "@deepseek-ai/dsh-client-ui-conversation"
43
46
  ]
@@ -46,7 +49,9 @@
46
49
  "peerDependencies": {
47
50
  "@deepseek-ai/cordis": "workspace:^",
48
51
  "@deepseek-ai/dsh-client-locale": "workspace:^",
49
- "@deepseek-ai/dsh-client-runtime": "workspace:^",
52
+ "@deepseek-ai/dsh-client-ui-session": "workspace:^",
53
+ "@deepseek-ai/dsh-client-ui-renderer": "workspace:^",
54
+ "@deepseek-ai/dsh-client-ui-chat": "workspace:^",
50
55
  "@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
51
56
  "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
52
57
  "react": "^18.2.0"
@@ -54,11 +59,14 @@
54
59
  "devDependencies": {
55
60
  "@deepseek-ai/cordis": "workspace:^",
56
61
  "@deepseek-ai/dsh-client-locale": "workspace:^",
57
- "@deepseek-ai/dsh-client-runtime": "workspace:^",
62
+ "@deepseek-ai/dsh-client-ui-session": "workspace:^",
63
+ "@deepseek-ai/dsh-client-ui-renderer": "workspace:^",
64
+ "@deepseek-ai/dsh-client-ui-chat": "workspace:^",
58
65
  "@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
59
66
  "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
60
67
  "@deepseek-ai/dsh-session-stats": "workspace:^",
61
68
  "@types/react": "~18.3.1",
69
+ "react-dom": "^18.2.0",
62
70
  "react": "^18.2.0",
63
71
  "tsdown": "^0.22.2",
64
72
  "typescript": "^6.0.3"