@deepseek-ai/dsh-session-telemetry-otel 0.1.2-alpha.5 → 0.1.3-alpha.2

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/session/session-telemetry-otel/README.md
5
- README.md: 2f489cbdae9a64ac95484cf6ed3aeddac3fe2b1e
6
- README.zh.md: 78bc221542d9709acd287f258c54b930ac389106
5
+ README.md: 35dc351bd56fdce396eb4c89c321008eb14796ce
6
+ README.zh.md: a771a06b0e2dab39a2b1e9460a99e8c4c2828fb3
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-session-telemetry-otel` delivers session records through OpenTelemetry logs and is the only entry a deployment loads for the [session-telemetry seam](../session-telemetry/README.md). Its `mode` decides whether session records follow the live stream, are released only at recorded feedback, or stay local: `FULL` hands every record to the OTel SDK immediately, `FEEDBACK_ONLY` replays the canonical log when a `feedback/record` lands, and `DISABLED` (the default) constructs nothing and shares nothing. Uploading modes compose the OTel JS SDK as-is — `LoggerProvider` `BatchLogRecordProcessor` → OTLP/HTTP log exporter — and map each record onto `logger.emit()`, so batching, retry, queueing, and loss policy follow the SDK. Records carry the complete event data as the seam's redaction waterfall returns it, so a deployment exporting beyond a trusted boundary mounts its own redaction rules. Modes, configuration, and the export surface come first; the implementation internals live in a collapsible developer section below.
12
+ `dsh-session-telemetry-otel` exports session records through the OTel JS SDK only after new explicit feedback, for all users and providers, including `deepseek-official`. `FEEDBACK_ONLY` releases the canonical prefix through that feedback, including context; later records wait for the next explicit feedback. `DISABLED` constructs no transport. SDK batching can finish an authorized upload without another user interaction or model call. Deployments own their redaction rules.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -31,11 +31,10 @@ Mount this plugin when a deployment should export session records through OpenTe
31
31
 
32
32
  | `mode` | Behavior |
33
33
  |---|---|
34
- | `FULL` | Every projected record, including lifecycle ops records, is handed to the OTel SDK immediately |
35
- | `FEEDBACK_ONLY` | Each `feedback/record` replays, projects, and redacts the canonical session-log suffix through that event; later records wait for another feedback event and remain local if none arrives |
36
- | `DISABLED` | Default. No coordinator, provider, processor, or exporter is constructed; no telemetry record leaves the process, and a `feedback/record` logs that nothing will be shared |
34
+ | `FEEDBACK_ONLY` | Default. Text feedback, rating creation/edit, note edit, and withdrawal release the unhanded prefix through that canonical feedback event; later records wait |
35
+ | `DISABLED` | No coordinator, provider, processor, or exporter is constructed; no telemetry record leaves the process. Live feedback warns locally; cold mutations stay silent |
37
36
 
38
- Programmatic TypeScript configuration uses the exported `SessionTelemetryMode` enum; raw string literals are not assignable. The mounted service discloses the resolved mode through the seam's [`SessionTelemetrySharingStatus`](../session-telemetry/README.md#the-sharing-disclosure) `sharing` property (`full` / `feedback-only` / `disabled`), so the `/feedback` acknowledgement reports whether and how the session is shared — even `DISABLED` discloses `disabled`.
37
+ Programmatic TypeScript configuration uses the exported `SessionTelemetryMode` enum; raw string literals are not assignable. `FULL` is rejected, not an alias. The [`sharing` property](../session-telemetry/README.md#the-sharing-disclosure) reports `feedback-only` or `disabled`, not a delivery receipt. The `/feedback` acknowledgement confirms recording only.
39
38
 
40
39
  ### Minimal configuration
41
40
 
@@ -45,7 +44,7 @@ Uploading modes require an exporter URL and accept the SDK option blocks verbati
45
44
  - id: sessionTelemetry-otel
46
45
  name: '@deepseek-ai/dsh-session-telemetry-otel'
47
46
  config:
48
- mode: FULL # explicit opt-in; default: DISABLED
47
+ mode: FEEDBACK_ONLY # optional; defaults to FEEDBACK_ONLY
49
48
  shutdownTimeoutMillis: 3000 # optional; defaults to 3000
50
49
  exporter: # passed verbatim to the SDK's OTLP/HTTP log exporter
51
50
  url: https://collector.example.com/v1/logs
@@ -56,12 +55,14 @@ Uploading modes require an exporter URL and accept the SDK option blocks verbati
56
55
 
57
56
  | Field | Default | Meaning |
58
57
  |---|---|---|
59
- | `mode` | `DISABLED` | Sharing policy: `FULL`, `FEEDBACK_ONLY`, or `DISABLED` |
58
+ | `mode` | `FEEDBACK_ONLY` | Sharing policy: `FEEDBACK_ONLY` or `DISABLED` |
60
59
  | `exporter.url` | required in uploading modes | Full OTLP logs endpoint; must parse as `http(s)` |
61
60
  | `exporter`, `processor` | — | Passed verbatim to the SDK exporter and batch processor |
62
61
  | `shutdownTimeoutMillis` | `3,000` | Outer deadline for the SDK's complete shutdown sequence |
63
62
 
64
- The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-session-telemetry-otel) is the exhaustive source for every accepted field. Upload authorization is positive and fail-closed: an unknown direct-construction mode fails before transport configuration is read, only `FULL` accepts direct `ctx.sessionTelemetry.emit()` calls, and `FEEDBACK_ONLY` treats only the exact `feedback/record` object already stored in the canonical log as consent.
63
+ Direct `ctx.sessionTelemetry.emit()` calls are no-ops in every mode and cannot bypass feedback authorization. Inherited parent feedback does not authorize a child export: the child needs new feedback of its own. Its authorized prefix then includes inherited context.
64
+
65
+ Model requests, request headers, Session creation or adoption, restoration, and plugin mount or HMR do not authorize capture. Stored feedback alone triggers nothing. SDK scheduled flush and shutdown may finish batches authorized earlier, but never capture new records.
65
66
 
66
67
  ### What leaves the machine
67
68
 
@@ -83,7 +84,7 @@ This section explains the backend's composition; the observable behavior is full
83
84
 
84
85
  ### Design concept
85
86
 
86
- The backend is a thin adapter over the OTel JS SDK: it owns capture mode, resource identity, and an outer shutdown deadline, and passes everything else through verbatim. Two instrumentation scopes separate record channels — ledger records on `@deepseek-ai/dsh-session-telemetry-otel`, operational records on `@deepseek-ai/dsh-session-telemetry-otel/ops` so receivers can alert on ops without summing them. Resource identity carries `service.name`/`service.version` from `dsh-llm`'s `APP_IDENTITY` plus the package's anonymous `user.id` (from `$DSH_HOME/.anonymous-user-id`), once per export batch rather than per record.
87
+ The backend is a thin adapter over the OTel JS SDK: it owns feedback authorization, resource identity, and an outer shutdown deadline. Canonical ledger records use the `@deepseek-ai/dsh-session-telemetry-otel` instrumentation scope; this backend captures no operational records. Resource identity carries `service.name`/`service.version` from `dsh-llm`'s `APP_IDENTITY` plus the anonymous `user.id` (from `$DSH_HOME/.anonymous-user-id`), once per export batch rather than per record.
87
88
 
88
89
  ### Source map
89
90
 
@@ -93,11 +94,11 @@ The backend is a thin adapter over the OTel JS SDK: it owns capture mode, resour
93
94
 
94
95
  ### Capture wiring
95
96
 
96
- `FULL` composes the coordinator in `live` mode and lets direct service calls through; `FEEDBACK_ONLY` composes it in `on-demand` mode, gives the coordinator a private backend capability, and triggers `captureSession(session, event.seq)` only when `session.eventAt(event.seq) === event` confirms the exact canonical feedback record; `DISABLED` registers nothing but a warning on `feedback/record`. The backend deliberately implements no `flush()`: the batch processor owns ordinary flushing, and forwarding the hint to `forceFlush()` would create the sole source of concurrent flushes whose interaction with shutdown's drain is undocumented.
97
+ The backend uses on-demand capture with stored history included. Only new own `feedback/record`, `feedback/message-put`, or `feedback/message-delete` events trigger live capture, bounded by that event. A cold `feedback/committed` notification supplies its committed canonical snapshot without publishing a live Session or Agent. Same-object handoff cursors suppress repeated capture. The backend implements no `flush()`; the SDK owns batching and shutdown drain.
97
98
 
98
99
  ### Field mapping
99
100
 
100
- Each seam record maps onto one SDK log record: `time` and `severity` become the SDK timestamp and severity fields, and `body` and `attributes` carry through verbatim; the exact field mapping lives in [`src/index.ts`](src/index.ts). In `FULL`, receivers can detect crashes by `shutdown`-record absence — the marker is emitted at the session's own disposal or application teardown, and a marker followed by more events is a telemetry reload. In `FEEDBACK_ONLY`, a released prefix normally has no later `shutdown` marker, so its absence is not a crash signal.
101
+ Each telemetry record maps to one SDK log record with captured timestamp, severity, body, and attributes. Feedback authorizes the complete unhanded prefix, not only the feedback payload.
101
102
 
102
103
  </details>
103
104
 
@@ -133,7 +134,9 @@ These limits define where SDK behavior governs and where export guarantees end.
133
134
 
134
135
  - **Upstream experimental tree** — `@opentelemetry/sdk-logs` is published from the upstream experimental tree; SDK API churn lands here and only here, while the seam contract does not move.
135
136
  - **Live-collector behavior belongs to the SDK exporter** — authentication, TLS, throttling, and other real OTLP deployment behavior follow the upstream SDK rather than a package-owned compatibility layer.
136
- - **Feedback-time snapshot** — `FEEDBACK_ONLY` retains no telemetry-owned copy before feedback; it reads and redacts the current canonical log when feedback is recorded, so a crash before feedback uploads nothing and policy changes before feedback affect what that replay exports.
137
+ - **Best-effort handoff** — new cold snapshots and a new feedback submission after restart can repeat prefixes; receivers deduplicate by Session id, format version, and event seq. There is no durable outbox, delivery watermark, automatic retry promise, or collector-acceptance guarantee. OTel and the opt-in DeepSeek API path can overlap. Withdrawal exports a deletion event, not remote erasure.
138
+
139
+ - **Backend availability** — feedback submitted while this plugin is disabled or unloaded is recorded locally but not automatically replayed when it returns. Capture requires the subscriber to remain mounted until it observes the submission; unloading during a pending cold write can miss its post-flush notification.
137
140
 
138
141
  <a id="dev-note"></a>
139
142
  ### Dev Note
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-session-telemetry-otel` 通过 OpenTelemetry 日志投递会话记录,是[会话遥测 seam](../session-telemetry/README.zh.md) 的后端,也是部署方唯一要加载的条目。其 `mode` 决定会话记录是跟随实时流、仅在记录反馈时释放,还是留在本地:`FULL` 把每条记录立即交给 OTel SDK,`FEEDBACK_ONLY` 在 `feedback/record` 落地时回放权威日志,`DISABLED`(默认值)不构造任何内容也不共享任何内容。上传模式会原样组合 OTel JS SDK——`LoggerProvider` `BatchLogRecordProcessor` → OTLP/HTTP 日志导出器——并把每条记录映射到 `logger.emit()`,因此批处理、重试、排队与丢失策略都遵循 SDK。记录携带 seam 脱敏 waterfall(瀑布式事件)返回的完整事件数据,因此向可信边界之外导出的部署方要挂载自己的脱敏规则。模式、配置与导出面在前;实现内部细节放在下方可折叠的开发者章节中。
12
+ `dsh-session-telemetry-otel` 仅在新的显式反馈后通过 OTel JS SDK 导出会话记录,适用于所有用户和提供方,包括 `deepseek-official`。`FEEDBACK_ONLY` 释放截至该反馈的权威日志前缀,包含上下文;后续记录等待下一次显式反馈。`DISABLED` 不构造传输。SDK 批处理可完成已授权的上传,无需另一次用户交互或模型调用。部署方负责脱敏规则。
13
13
 
14
14
  ## 目录
15
15
 
@@ -31,11 +31,10 @@ kind: "package-reference"
31
31
 
32
32
  | `mode` | 行为 |
33
33
  |---|---|
34
- | `FULL` | 每条已投影记录都立即交给 OTel SDK,包括生命周期运维记录 |
35
- | `FEEDBACK_ONLY` | 每个 `feedback/record` 都会回放权威会话日志中截至该事件的后缀,并进行投影与脱敏;后续记录等待下一个反馈事件;如果没有后续反馈,则留在本地 |
36
- | `DISABLED` | 默认值。不构造协调器、提供方、处理器或导出器;没有遥测记录会离开进程,`feedback/record` 会记录「不会共享任何内容」 |
34
+ | `FEEDBACK_ONLY` | 默认值。文本反馈、评分创建或修改、备注修改和撤回释放尚未交接的前缀,截止该权威反馈事件;后续记录等待 |
35
+ | `DISABLED` | 不构造协调器、提供方、处理器或导出器;没有遥测记录离开进程。活跃会话反馈在本地告警;冷会话修改保持静默 |
37
36
 
38
- 程序化 TypeScript 配置使用导出的 `SessionTelemetryMode` 枚举;原始字符串字面量不可赋值。已挂载服务通过 seam 的 [`SessionTelemetrySharingStatus`](../session-telemetry/README.zh.md#the-sharing-disclosure) `sharing` 属性披露解析后的模式(`full` / `feedback-only` / `disabled`),因此 `/feedback` 的确认文本可以报告会话是否以及如何被共享——即使 `DISABLED` 也会披露 `disabled`。
37
+ 程序化 TypeScript 配置使用导出的 `SessionTelemetryMode` 枚举;原始字符串字面量不可赋值。`FULL` 会被拒绝,不是别名。[`sharing` 属性](../session-telemetry/README.zh.md#the-sharing-disclosure)报告 `feedback-only` `disabled`,不代表投递回执。`/feedback` 确认文本只确认记录。
39
38
 
40
39
  ### 最小配置
41
40
 
@@ -45,7 +44,7 @@ kind: "package-reference"
45
44
  - id: sessionTelemetry-otel
46
45
  name: '@deepseek-ai/dsh-session-telemetry-otel'
47
46
  config:
48
- mode: FULL # explicit opt-in; default: DISABLED
47
+ mode: FEEDBACK_ONLY # optional; defaults to FEEDBACK_ONLY
49
48
  shutdownTimeoutMillis: 3000 # optional; defaults to 3000
50
49
  exporter: # passed verbatim to the SDK's OTLP/HTTP log exporter
51
50
  url: https://collector.example.com/v1/logs
@@ -56,12 +55,14 @@ kind: "package-reference"
56
55
 
57
56
  | 字段 | 默认值 | 含义 |
58
57
  |---|---|---|
59
- | `mode` | `DISABLED` | 共享策略:`FULL`、`FEEDBACK_ONLY` 或 `DISABLED` |
58
+ | `mode` | `FEEDBACK_ONLY` | 共享策略:`FEEDBACK_ONLY` 或 `DISABLED` |
60
59
  | `exporter.url` | 上传模式必填 | 完整 OTLP 日志端点;必须能解析为 `http(s)` |
61
60
  | `exporter`、`processor` | — | 原样传给 SDK 导出器与批处理器 |
62
61
  | `shutdownTimeoutMillis` | `3,000` | SDK 完整关闭序列的外层截止时间 |
63
62
 
64
- 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-session-telemetry-otel)是每个受支持字段的穷尽式真源。上传授权采用显式许可,且为 fail-closed:通过直接构造传入未知模式时会在读取传输配置前失败,只有 `FULL` 接受对 `ctx.sessionTelemetry.emit()` 的直接调用,`FEEDBACK_ONLY` 只把权威日志中已存储的精确 `feedback/record` 对象视为同意。
63
+ 直接调用 `ctx.sessionTelemetry.emit()` 在任何模式下都是空操作,不能绕过反馈授权。继承的父会话反馈不授权子会话导出:子会话需要新的自身反馈。授权后的前缀包含继承的上下文。
64
+
65
+ 模型请求、请求头、Session 创建或接纳、恢复,以及插件挂载或 HMR(热模块替换) 均不授权捕获。仅有存储的反馈不会触发任何上传。SDK 定时刷新和关闭可以完成先前已授权的批次,但绝不捕获新记录。
65
66
 
66
67
  ### 哪些数据会离开本机
67
68
 
@@ -83,7 +84,7 @@ kind: "package-reference"
83
84
 
84
85
  ### 设计理念
85
86
 
86
- 后端是对 OTel JS SDK 的薄适配层:它拥有捕获模式、资源身份与一个外层关闭截止时间,其余全部原样透传。两个插桩作用域区分记录通道——ledger 记录挂在 `@deepseek-ai/dsh-session-telemetry-otel` 下,运维记录挂在 `@deepseek-ai/dsh-session-telemetry-otel/ops` 下——使接收端可以在不累加它们的情况下对运维记录告警。资源身份携带 `service.name`/`service.version`(来自 `dsh-llm` 的 `APP_IDENTITY`)以及本包的匿名 `user.id`(来自 `$DSH_HOME/.anonymous-user-id`),按导出批次携带一次,而非逐条记录。
87
+ 后端是对 OTel JS SDK 的薄适配层:它拥有反馈授权、资源身份与外层关闭截止时间。权威 ledger 记录使用 `@deepseek-ai/dsh-session-telemetry-otel` 插桩作用域;此后端不捕获运维记录。资源身份携带 `service.name`/`service.version`(来自 `dsh-llm` 的 `APP_IDENTITY`)以及匿名 `user.id`(来自 `$DSH_HOME/.anonymous-user-id`),按导出批次携带一次,而非逐条记录。
87
88
 
88
89
  ### 源码地图
89
90
 
@@ -93,11 +94,11 @@ kind: "package-reference"
93
94
 
94
95
  ### 捕获接线
95
96
 
96
- `FULL` `live` 模式组装协调器,并放行直接服务调用;`FEEDBACK_ONLY` `on-demand` 模式组装协调器,给协调器一个私有后端能力,并且仅在 `session.eventAt(event.seq) === event` 确认精确的权威反馈记录时触发 `captureSession(session, event.seq)`;`DISABLED` 除了在 `feedback/record` 上发出警告外不注册任何内容。后端刻意不实现 `flush()`:常规 flush 由批处理器负责,把提示转发给 `forceFlush()` 会成为并发 flush 的唯一来源,而它与关闭排空的交互没有文档。
97
+ 后端使用包含存储历史的按需捕获。只有新的自身 `feedback/record`、`feedback/message-put` `feedback/message-delete` 事件触发活跃会话捕获,并以该事件为上限。冷会话 `feedback/committed` 通知提供已提交的权威快照,不发布存活 Session Agent。同对象交接游标抑制重复捕获。后端不实现 `flush()`;SDK 负责批处理和关闭排空。
97
98
 
98
99
  ### 字段映射
99
100
 
100
- 每条 seam 记录映射为一条 SDK 日志记录:`time` 与 `severity` 变为 SDK 的时间戳与严重级别字段,`body` 与 `attributes` 原样照搬;确切字段映射见 [`src/index.ts`](src/index.ts)。在 `FULL` 中,接收端可通过缺少 `shutdown` 记录检测崩溃——该标记在会话自身 dispose(资源释放)或应用关闭时发出,标记之后出现更多事件说明遥测发生了重载。在 `FEEDBACK_ONLY` 中,已释放的前缀通常不包含随后的 `shutdown` 标记,因此缺少该标记不是崩溃信号。
101
+ 每条遥测记录映射为一条 SDK 日志记录,携带捕获的时间戳、严重级别、正文和属性。反馈授权的是尚未交接的完整前缀,而非只有反馈载荷。
101
102
 
102
103
  </details>
103
104
 
@@ -133,7 +134,9 @@ kind: "package-reference"
133
134
 
134
135
  - **上游实验性源码树**——`@opentelemetry/sdk-logs` 从上游实验性源码树发布;SDK API 的变动只会落在本包,也仅落在本包,而 seam 约定不动。
135
136
  - **真实 collector 行为属于 SDK 导出器**——身份验证、TLS、限流及其他真实 OTLP 部署行为遵循上游 SDK,不由本包自有兼容层处理。
136
- - **反馈时快照**——`FEEDBACK_ONLY` 在反馈前不保留遥测自有副本;记录反馈时读取并脱敏当前的权威日志,因此反馈前崩溃时什么都不上传,反馈前的策略变更会影响该次回放的导出内容。
137
+ - **尽力交接**——新冷快照以及重启后的新反馈提交可能重复前缀;接收方按 Session id、格式版本和事件 seq 去重。没有持久化 outbox、投递水位、自动重试承诺或采集端接受保证。OTel 与需显式启用的 DeepSeek API 路径可能重叠。撤回导出删除事件,不是远端擦除。
138
+
139
+ - **后端可用性**——本插件禁用或卸载期间提交的反馈会记录在本地,但恢复插件不会自动重放。捕获要求订阅方保持挂载直到观察到提交;在冷写入尚未完成时卸载,可能错过其 flush 后通知。
137
140
 
138
141
  <a id="dev-note"></a>
139
142
  ### 开发备注
package/lib/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import z from "@deepseek-ai/schemastery";
3
+ import { Session } from "@deepseek-ai/dsh-session";
3
4
  import { SessionTelemetryBackend, SessionTelemetryCoordinator } from "@deepseek-ai/dsh-session-telemetry";
4
5
  import { APP_IDENTITY } from "@deepseek-ai/dsh-llm";
5
6
  import { getOrCreateAnonymousUserId } from "@deepseek-ai/dsh-anonymous-user-id";
@@ -25,20 +26,27 @@ const { version } = createRequire(import.meta.url)("../package.json");
25
26
  /** Session-sharing policy selected by {@link Config.mode}. */
26
27
  var SessionTelemetryMode;
27
28
  (function(SessionTelemetryMode) {
28
- SessionTelemetryMode["FULL"] = "FULL";
29
29
  SessionTelemetryMode["FEEDBACK_ONLY"] = "FEEDBACK_ONLY";
30
30
  SessionTelemetryMode["DISABLED"] = "DISABLED";
31
31
  })(SessionTelemetryMode || (SessionTelemetryMode = {}));
32
32
  /** Default session-sharing policy for schema and direct construction. */
33
- const DEFAULT_TELEMETRY_MODE = SessionTelemetryMode.DISABLED;
34
- const DISABLED_FEEDBACK_WARNING = "session telemetry is DISABLED; nothing will be shared and this feedback remains local";
35
- const NON_CANONICAL_FEEDBACK_WARNING = "session telemetry ignored a feedback event absent from the canonical session log";
36
- const DROP_RECORD = () => {};
33
+ const DEFAULT_TELEMETRY_MODE = SessionTelemetryMode.FEEDBACK_ONLY;
34
+ const DISABLED_FEEDBACK_WARNING = "OpenTelemetry session upload is DISABLED; this feedback is not uploaded through OpenTelemetry";
35
+ const NON_CANONICAL_EVENT_WARNING = "session telemetry ignored an event absent from the canonical session log";
36
+ /** Only this Session's explicit feedback authorizes replay; fork seeds do not. */
37
+ function isFeedback(session, event) {
38
+ if (event.seq < session.inheritedEventCount) return false;
39
+ switch (event.type) {
40
+ case "feedback/record": return true;
41
+ case "feedback/message-put":
42
+ case "feedback/message-delete": return event.data.sessionId === session.id;
43
+ default: return false;
44
+ }
45
+ }
37
46
  /** Resolve the default and reject unknown runtime values before transport setup. */
38
47
  function resolveMode(mode) {
39
48
  const resolved = mode ?? DEFAULT_TELEMETRY_MODE;
40
49
  switch (resolved) {
41
- case SessionTelemetryMode.FULL:
42
50
  case SessionTelemetryMode.FEEDBACK_ONLY:
43
51
  case SessionTelemetryMode.DISABLED: return resolved;
44
52
  default: return assertNever(resolved);
@@ -51,7 +59,6 @@ function assertNever(value) {
51
59
  /** Map the serialized mode onto the seam's backend-independent sharing vocabulary. */
52
60
  function sharingStatusFor(mode) {
53
61
  switch (mode) {
54
- case SessionTelemetryMode.FULL: return "full";
55
62
  case SessionTelemetryMode.FEEDBACK_ONLY: return "feedback-only";
56
63
  case SessionTelemetryMode.DISABLED: return "disabled";
57
64
  /* v8 ignore next 2 -- resolveMode already rejected unknown values before this switch; the closed enum cannot reach the default. */
@@ -91,14 +98,13 @@ const SEVERITY = {
91
98
  };
92
99
  /**
93
100
  * The backend plugin — the only entry a deployment loads. It always registers
94
- * the `telemetry` service (duplicate load throws). Uploading modes wire the SDK
95
- * pipeline and compose {@link SessionTelemetryCoordinator}; `DISABLED` constructs no
101
+ * the `sessionTelemetry` service (duplicate load throws). `FEEDBACK_ONLY` wires the SDK
102
+ * pipeline and on-demand {@link SessionTelemetryCoordinator}; `DISABLED` constructs no
96
103
  * SDK state and listens only to warn when recorded feedback stays local.
97
104
  */
98
105
  var OpenTelemetrySessionBackend = class extends SessionTelemetryBackend {
99
106
  static inject = ["sessions"];
100
107
  static Config = Config;
101
- directEmit;
102
108
  provider;
103
109
  shutdownTimeoutMillis;
104
110
  sharing;
@@ -107,11 +113,10 @@ var OpenTelemetrySessionBackend = class extends SessionTelemetryBackend {
107
113
  super(ctx);
108
114
  this.sharing = sharingStatusFor(mode);
109
115
  if (mode === SessionTelemetryMode.DISABLED) {
110
- this.directEmit = DROP_RECORD;
111
116
  this.provider = void 0;
112
117
  this.shutdownTimeoutMillis = DEFAULT_SHUTDOWN_TIMEOUT_MILLIS;
113
- ctx.on("session/event", (_session, event) => {
114
- if (event.type === "feedback/record") ctx.logger.warn(DISABLED_FEEDBACK_WARNING);
118
+ ctx.on("session/event", (session, event) => {
119
+ if (isFeedback(session, event)) ctx.logger.warn(DISABLED_FEEDBACK_WARNING);
115
120
  });
116
121
  return;
117
122
  }
@@ -141,9 +146,8 @@ var OpenTelemetrySessionBackend = class extends SessionTelemetryBackend {
141
146
  })]
142
147
  });
143
148
  const ledger = this.provider.getLogger("@deepseek-ai/dsh-session-telemetry-otel", version);
144
- const ops = this.provider.getLogger("@deepseek-ai/dsh-session-telemetry-otel/ops", version);
145
149
  const enqueue = (record) => {
146
- (record.channel === "ops" ? ops : ledger).emit({
150
+ ledger.emit({
147
151
  timestamp: record.time,
148
152
  observedTimestamp: record.time,
149
153
  ...SEVERITY[record.severity],
@@ -151,35 +155,35 @@ var OpenTelemetrySessionBackend = class extends SessionTelemetryBackend {
151
155
  attributes: record.attributes
152
156
  });
153
157
  };
154
- const backend = {
158
+ const coordinator = new SessionTelemetryCoordinator(ctx, {
155
159
  emit: enqueue,
156
160
  shutdown: () => this.shutdown()
157
- };
158
- if (mode === SessionTelemetryMode.FULL) {
159
- this.directEmit = enqueue;
160
- new SessionTelemetryCoordinator(ctx, backend, "live");
161
- return;
162
- }
163
- this.directEmit = DROP_RECORD;
164
- const coordinator = new SessionTelemetryCoordinator(ctx, backend, "on-demand");
161
+ }, {
162
+ capture: "on-demand",
163
+ includeHistory: true
164
+ });
165
165
  ctx.on("session/event", (session, event) => {
166
- if (event.type !== "feedback/record") return;
166
+ if (!isFeedback(session, event)) return;
167
167
  if (session.eventAt(event.seq) !== event) {
168
- ctx.logger.warn(NON_CANONICAL_FEEDBACK_WARNING);
168
+ ctx.logger.warn(NON_CANONICAL_EVENT_WARNING);
169
169
  return;
170
170
  }
171
171
  coordinator.captureSession(session, event.seq);
172
172
  });
173
+ ctx.on("feedback/committed", (inspection) => {
174
+ const snapshot = structuredClone(inspection);
175
+ const committed = snapshot.events.at(-1);
176
+ if (committed === void 0) return;
177
+ const session = Session.fromRestore(snapshot.meta.id, snapshot.events, snapshot.meta, snapshot.inheritedEventCount, "detached");
178
+ if (isFeedback(session, committed)) coordinator.captureSession(session, committed.seq);
179
+ });
173
180
  }
174
181
  /**
175
- * Hand a direct service record to the SDK only in `FULL`. Direct calls are
176
- * no-ops in `FEEDBACK_ONLY` and `DISABLED`; feedback replay uses a private
177
- * backend capability created only for the canonical feedback listener.
178
- * @param record - the logical record offered directly to the service.
182
+ * Drop direct records. Only a new canonical feedback submission can authorize
183
+ * capture through the private coordinator sink, for every provider.
184
+ * @param _record - the direct record, never uploaded.
179
185
  */
180
- emit(record) {
181
- this.directEmit(record);
182
- }
186
+ emit(_record) {}
183
187
  /**
184
188
  * Ask the SDK to drain and quiesce, but reject after the backend-owned
185
189
  * deadline. OTel's processor export timeout wraps `exportCompleted` only;
@@ -18,19 +18,18 @@ import { type BatchLogRecordProcessorOptions } from '@opentelemetry/sdk-logs';
18
18
  import type { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base';
19
19
  /** Session-sharing policy selected by {@link Config.mode}. */
20
20
  export declare enum SessionTelemetryMode {
21
- FULL = "FULL",
22
21
  FEEDBACK_ONLY = "FEEDBACK_ONLY",
23
22
  DISABLED = "DISABLED"
24
23
  }
25
24
  /** Default session-sharing policy for schema and direct construction. */
26
- export declare const DEFAULT_TELEMETRY_MODE = SessionTelemetryMode.DISABLED;
25
+ export declare const DEFAULT_TELEMETRY_MODE = SessionTelemetryMode.FEEDBACK_ONLY;
27
26
  /**
28
27
  * Plugin configuration: one sharing policy, two verbatim SDK option objects,
29
28
  * and one DSH-owned shutdown bound. Uploading modes validate their endpoint
30
29
  * and shutdown deadline at plugin load; `DISABLED` reads neither.
31
30
  */
32
31
  export interface Config {
33
- /** Sharing policy; defaults to local-only `DISABLED` behavior. */
32
+ /** Defaults to `FEEDBACK_ONLY`: capture session history only when feedback is explicitly submitted. */
34
33
  mode?: SessionTelemetryMode;
35
34
  /**
36
35
  * Passed verbatim to the SDK's OTLP/HTTP log exporter — the complete
@@ -62,25 +61,23 @@ export declare const Config: z<Config>;
62
61
  export declare const DEFAULT_SHUTDOWN_TIMEOUT_MILLIS = 3000;
63
62
  /**
64
63
  * The backend plugin — the only entry a deployment loads. It always registers
65
- * the `telemetry` service (duplicate load throws). Uploading modes wire the SDK
66
- * pipeline and compose {@link SessionTelemetryCoordinator}; `DISABLED` constructs no
64
+ * the `sessionTelemetry` service (duplicate load throws). `FEEDBACK_ONLY` wires the SDK
65
+ * pipeline and on-demand {@link SessionTelemetryCoordinator}; `DISABLED` constructs no
67
66
  * SDK state and listens only to warn when recorded feedback stays local.
68
67
  */
69
68
  export declare class OpenTelemetrySessionBackend extends SessionTelemetryBackend {
70
69
  static inject: string[];
71
70
  static Config: z<Config>;
72
- private readonly directEmit;
73
71
  private readonly provider;
74
72
  private readonly shutdownTimeoutMillis;
75
73
  readonly sharing: SessionTelemetrySharingStatus;
76
74
  constructor(ctx: Context, config: Config);
77
75
  /**
78
- * Hand a direct service record to the SDK only in `FULL`. Direct calls are
79
- * no-ops in `FEEDBACK_ONLY` and `DISABLED`; feedback replay uses a private
80
- * backend capability created only for the canonical feedback listener.
81
- * @param record - the logical record offered directly to the service.
76
+ * Drop direct records. Only a new canonical feedback submission can authorize
77
+ * capture through the private coordinator sink, for every provider.
78
+ * @param _record - the direct record, never uploaded.
82
79
  */
83
- emit(record: SessionTelemetryRecord): void;
80
+ emit(_record: SessionTelemetryRecord): void;
84
81
  /**
85
82
  * Ask the SDK to drain and quiesce, but reject after the backend-owned
86
83
  * deadline. OTel's processor export timeout wraps `exportCompleted` only;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-session-telemetry-otel",
3
3
  "description": "OpenTelemetry backend for the DeepSeek Harness telemetry seam: hands captured session records to the OTel JS SDK's log pipeline",
4
- "version": "0.1.2-alpha.5",
4
+ "version": "0.1.3-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -36,27 +36,30 @@
36
36
  "@deepseek-ai/schemastery": "^3.18.2"
37
37
  },
38
38
  "peerDependencies": {
39
- "@deepseek-ai/dsh-command-feedback": "^0.1.2-alpha.5",
40
- "@deepseek-ai/dsh-llm": "^0.1.2-alpha.5",
41
- "@deepseek-ai/dsh-session": "^0.1.2-alpha.5",
42
- "@deepseek-ai/dsh-anonymous-user-id": "^0.1.2-alpha.5",
43
- "@deepseek-ai/cordis": "^4.0.2",
44
- "@deepseek-ai/dsh-session-telemetry": "^0.1.2-alpha.5"
39
+ "@deepseek-ai/dsh-command-feedback": "^0.1.3-alpha.2",
40
+ "@deepseek-ai/dsh-message-feedback": "^0.1.3-alpha.2",
41
+ "@deepseek-ai/dsh-llm": "^0.1.3-alpha.2",
42
+ "@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
43
+ "@deepseek-ai/dsh-session-telemetry": "^0.1.3-alpha.2",
44
+ "@deepseek-ai/dsh-anonymous-user-id": "^0.1.3-alpha.2",
45
+ "@deepseek-ai/cordis": "^4.0.2"
45
46
  },
46
47
  "devDependencies": {
48
+ "@deepseek-ai/dsh-bash-local": "^0.1.3-alpha.2",
49
+ "@deepseek-ai/dsh-command-feedback": "^0.1.3-alpha.2",
50
+ "@deepseek-ai/dsh-llm": "^0.1.3-alpha.2",
47
51
  "@deepseek-ai/cordis-plugin-logger-console": "^1.0.2",
48
- "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
49
- "@deepseek-ai/dsh-bash-local": "^0.1.2-alpha.5",
50
- "@deepseek-ai/dsh-app-boot": "^0.1.2-alpha.5",
51
- "@deepseek-ai/dsh-command-feedback": "^0.1.2-alpha.5",
52
- "@deepseek-ai/dsh-llm": "^0.1.2-alpha.5",
53
- "@deepseek-ai/dsh-loader-smoke": "^0.1.2-alpha.5",
54
- "@deepseek-ai/dsh-session": "^0.1.2-alpha.5",
55
- "@deepseek-ai/dsh-session-checkpoint-policy": "^0.1.2-alpha.5",
56
- "@deepseek-ai/dsh-session-telemetry": "^0.1.2-alpha.5",
57
- "@deepseek-ai/dsh-subprocess-local": "^0.1.2-alpha.5",
58
- "@deepseek-ai/dsh-anonymous-user-id": "^0.1.2-alpha.5",
52
+ "@deepseek-ai/dsh-loader-smoke": "^0.1.3-alpha.2",
53
+ "@deepseek-ai/dsh-message-feedback": "^0.1.3-alpha.2",
54
+ "@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
55
+ "@deepseek-ai/dsh-session-checkpoint-policy": "^0.1.3-alpha.2",
56
+ "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.3-alpha.2",
57
+ "@deepseek-ai/dsh-session-telemetry": "^0.1.3-alpha.2",
58
+ "@deepseek-ai/dsh-anonymous-user-id": "^0.1.3-alpha.2",
59
59
  "@deepseek-ai/cordis": "^4.0.2",
60
- "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.2-alpha.5"
60
+ "@deepseek-ai/dsh-subprocess-local": "^0.1.3-alpha.2",
61
+ "@deepseek-ai/dsh-http-proxy": "^0.1.3-alpha.2",
62
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
63
+ "@deepseek-ai/dsh-app-boot": "^0.1.3-alpha.2"
61
64
  }
62
65
  }