Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
@deepseek-ai/dsh-session-telemetry
English | 中文
The telemetry Service Definition declares the TelemetryBackend contract, and its capture coordinator passes session records to any reporting SDK backend that implements it. Capture can follow live session events or replay a canonical session-log prefix on demand. This package stops after it calls emit(): batching, retry, queueing, and loss policy belong to the backend's SDK and are neither specified nor wrapped. Rationale and rejected alternatives: the revival Agent Note, feedback-gated delivery, and buffer-free feedback replay.
The backend contract
TelemetryBackend has three members: emit(record) MUST enqueue without blocking because it runs synchronously during session/event or explicit canonical-log replay; optional flush() is a fire-and-forget hint after a turn ends, and most backends omit it and use their SDK's normal batching schedule; shutdown() drains queued records and resolves when the SDK stops, and disposal awaits it. An implementation that provides flush() must order concurrent flushes with the final shutdown() drain. Telemetry registers this API under the telemetry context key; each context accepts one implementation, and a duplicate load throws. A backend constructs TelemetryCoordinator with live or on-demand capture and calls captureSession(session, throughSeq?) at its chosen trigger.
Capture points
In live mode the coordinator registers, all through the composing fiber's effects: session/created (adopt: record the header, read the log back through the projection from the construction boundary — constructor seeds from fork/resume never re-emit on the firehose and never re-export), session/event (project, deep-copy, redact, then hand off; zero I/O), session/flush (forward the optional flush() hint and return void — the loop's awaited parallel must never wait on telemetry), session/disposed (capture the session's shutdown operational record at its termination edge, then retire it), agent/error (the one live-bus relay; the session event vocabulary intentionally has no operational-error record), a dispose effect (capture shutdown for each still-live session, then await the backend's shutdown(); failures warn instead of throwing), and an adoption sweep of ctx.sessions.list() (a hot reload does not replay session/created). In on-demand mode it registers only the dispose effect: captureSession() reads the canonical log through an optional inclusive sequence boundary, while flush hints and operational events remain local.
The redact waterfall
Every record passes the telemetry/record waterfall immediately after projection — the Service Definition's scrubbing extension point. This package ships NO rules of its own: the innermost next() passes the record through unchanged, so with no listener mounted records reach the backend exactly as captured, and exported data is precisely as clean as the rules a deployment mounts. Listeners stack by transforming next()'s return value; returning without next() replaces everything beneath, and a throwing listener withholds that one record fail-closed inside the coordinator's containment. Live capture runs the waterfall at append time; on-demand capture runs it while replaying the canonical log, using the rules mounted at that time. Redaction applies to the outbound copy only; the canonical session log is never rewritten.
The handoff cursor
A module-scope WeakMap<Session, seq> marks the highest seq HANDED OFF (not delivered) per session. Live capture advances it at append time; on-demand capture advances it only while captureSession() hands a requested prefix to the backend. An uncaptured prefix remains solely in the canonical log, so a coordinator reload adds no telemetry-owned recovery state. On replay the coordinator re-hands only events past the cursor (events at or below it still rebuild the chunk-projection state); a missing cursor safely degrades to a re-hand from the session's construction boundary (Session.firstLiveSeq — seq 0 for a session born in this process), absorbed by receiver-side dedupe on (session.id, event.seq). Constructor seeds never re-export: a resumed session's history shipped from the previous process under the same id, and a fork's inherited prefix lives in the parent's stream (receivers stitch on session.parent_id + session.seed_length). The accepted cost, consistent with at-most-once delivery: a resume does not backfill records a previous process failed to deliver — a deployment with a backfill requirement needs the deferred outbox, not replay. This is a deliberate, narrow exception to the registrations-are-effects discipline: entries die with their sessions, the value is a monotonic watermark, and losing it is never an error.
The fixed chunk projection
Only the first assistant/chunk of each (turn, step) ships; the rest are dropped at capture and never advance the cursor. That one chunk is the stream-started signal: step/start + first-chunk presence + assistant/message presence + the turn/end reason distinguish "the request never started" from "the stream died midway" without chunk volume, and time-to-first-token stays computable. Chunk elision makes seq gaps routine on the wire — a gap is never a loss signal. Every other event type, including ones merged by plugins this package never heard of, passes through whole.
The logical record
TelemetryRecord: channel (ledger | ops), time (epoch ms), severity (pre-mapped: ERROR for tool/result.isError, turn/end error reasons, and agent-error; INFO for other captured records, while telemetry/record policies may assign WARN), identity-only attributes (session.id, event.type, event.seq, plus session.cwd/session.parent_id/session.seed_length when the header has them), and the complete deep-copied event.data as body — post-redaction. Operational records carry telemetry.op (agent-error | shutdown) and session.id, and deliberately NO event.seq/event.type — signals to alert on, not entries to sum; agent-error normalizes its arbitrary thrown value into a stable { name, message } body. Delivery downstream of the handoff is the backend SDK's; duplicates remain possible (cursor-less re-adoption, SDK retries), so receivers dedupe on (session.id, event.seq).
Model Experience
None, as this package only observes the session stream and hands redacted copies to a reporting backend; it never contributes to a model request.
KV Cache effect
None; this package neither assembles nor sends a provider request.
Known Limitations and Deferred Work
- Best-effort delivery — the cursor marks handed-off, not delivered; a session torn down inside a reload window cannot be re-adopted; whatever sits in a backend queue at crash time is lost. A durable outbox (spool, per-sink cursors, at-least-once) is deferred until a deployment states a crash-loss requirement — see the revival Agent Note.
- No built-in redaction rules — with no
telemetry/recordlistener mounted, records leave the process exactly as captured, including any credentials embedded in file contents or command output; a deployment exporting to a shared collector owns its rule set. - On-demand redaction uses current state — uncaptured events exist only in the canonical session log. A later
captureSession()deep-copies and redacts their current values with the policy mounted at that time; there is no capture-time telemetry snapshot or durable pre-capture spool.