@deepseek-ai/dsh-session-telemetry 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/README.md
5
- README.md: 69fcaa76bb0d8146d419b4b69f93d70fa854afae
6
- README.zh.md: e9c8266cc7012cfd4e18a3dd9b90fdd69cbca58c
5
+ README.md: 14664e42d018a5e607c7bf58f38c19d5fb8ae4a3
6
+ README.zh.md: ba89f0039ba963d83f341472d33c8140314630ff
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-session-telemetry` captures session activity for outbound reporting: it projects session events into telemetry records, lets a deployment redact them, and hands them to a reporting backend that implements its contract. Deployments do not load this package directly — they load exactly one backend (the shipped OpenTelemetry backend is `dsh-session-telemetry-otel`), which registers `ctx.sessionTelemetry` and composes the capture coordinator. The seam owns capture, redaction, and the sharing disclosure; batching, retry, queueing, and loss policy belong to the backend's SDK and stop at `emit()`. Every mounted backend discloses its deployment-selected sharing policy so acknowledgement surfaces can report whether and how a session is shared. The contract and capture behavior come first; the implementation internals live in a collapsible developer section below.
12
+ `dsh-session-telemetry` captures session activity for outbound reporting: it copies each session event into a telemetry record, lets a deployment redact it, and hands it to a reporting backend that implements the contract. Deployments do not load this package directly — they load exactly one backend (the shipped OpenTelemetry backend is `dsh-session-telemetry-otel`), which registers `ctx.sessionTelemetry` and composes the capture coordinator. The seam owns capture, redaction, and the sharing disclosure; batching, retry, queueing, and loss policy belong to the backend's SDK and stop at `emit()`. Every mounted backend discloses its deployment-selected sharing policy so acknowledgement surfaces can report whether and how a session is shared. The contract and capture behavior come first; the implementation internals live in a collapsible developer section below.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -29,7 +29,7 @@ As a deployment, choose a backend, mount it, and add redaction rules when record
29
29
 
30
30
  ### Choosing and mounting a backend
31
31
 
32
- Load exactly one backend plugin; it registers `ctx.sessionTelemetry` with the capture coordinator and its own delivery pipeline, and a duplicate load throws. The mounted backend discloses its sharing policy through the required [`sharing` member](#the-sharing-disclosure), which the `/feedback` acknowledgement renders; a consumer renders "not configured" only when no telemetry service is mounted.
32
+ Load exactly one backend plugin; it registers `ctx.sessionTelemetry` with the capture coordinator and its delivery pipeline. A duplicate load throws. The required [`sharing` member](#the-sharing-disclosure) reports the deployment mode, not per-session admission or delivery. A consumer may report "not configured" only when no telemetry service is mounted. The `/feedback` command confirms recording without reading this policy.
33
33
 
34
34
  ### The backend contract
35
35
 
@@ -37,19 +37,19 @@ A backend implements three members: `emit(record)` must be a non-blocking enqueu
37
37
 
38
38
  ### What gets captured
39
39
 
40
- Capture runs in one of two modes. `live` capture follows session events as they are appended, replays already-live sessions at mount time, and records lifecycle markers; `on-demand` capture reads the canonical session log only when the backend requests a prefix through `captureSession(session, throughSeq?)`. Ledger records mirror session events one to one except for one projection: only the first `assistant/chunk` of each `(turn, step)` ships, so `seq` gaps on the wire are routine and never a loss signal. Each record carries the event's complete data, minimal identity attributes, and a pre-mapped severity (`error` for `tool/result.isError`, `turn/end` error reasons, and `agent-error`; `info` otherwise).
40
+ Capture runs in one of two modes. `live` capture follows session events as they are appended, replays already-live sessions at mount time, and records lifecycle markers; `on-demand` capture reads the canonical session log only when the backend requests a prefix through `captureSession(session, throughSeq?)`. Coordinator options select whether stored history is included. Every canonical session event maps to one ledger record in order. An `assistant/message` or `assistant/attempt` record carries its complete embedded compact stream, including failed and retried output. Each ledger record also carries `session.id`, `session.format_version`, the numeric event identity, optional header facts, and a pre-mapped severity (`error` for `tool/result.isError`, `turn/end` error reasons, and `agent-error`; `info` otherwise).
41
41
 
42
42
  ### The sharing disclosure
43
43
 
44
44
  <a id="the-sharing-disclosure"></a>
45
45
 
46
- Every backend discloses its deployment-selected sharing policy through the seam's `sharing` vocabulary: `full` (every event is handed over as it happens), `feedback-only` (nothing is handed over until a `feedback/record` event releases the unreleased prefix), or `disabled` (nothing is handed over at all). The acknowledgement of a recorded feedback entry reports this status; the disclosure never claims delivery — handoff is the non-blocking enqueue, and batching, retry, and loss policy stay the backend SDK's.
46
+ Every backend discloses its deployment mode through `sharing`: `full`, `feedback-only`, or `disabled`. A backend may additionally restrict eligible Sessions. This property is not a delivery receipt; handoff is a non-blocking enqueue, and batching, retry, and loss policy belong to the backend SDK.
47
47
 
48
48
  ### Redacting records
49
49
 
50
50
  <a id="the-redact-waterfall"></a>
51
51
 
52
- Every outbound record passes the `sessionTelemetry/record` waterfall immediately after projection. This package ships no rules: with no listener mounted, records reach the backend exactly as captured, so exported data is as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; a throwing listener withholds that one record fail-closed. Redaction applies to the outbound copy only — the canonical session log is never rewritten.
52
+ Every outbound record passes the `sessionTelemetry/record` waterfall after the coordinator copies its canonical event. This package ships no rules: with no listener mounted, records reach the backend exactly as captured, so exported data is as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; a throwing listener withholds that one record fail-closed. Redaction applies to the outbound copy only — the canonical session log is never rewritten.
53
53
 
54
54
  -----
55
55
 
@@ -63,22 +63,22 @@ This section explains the capture design; the observable behavior is fully cover
63
63
 
64
64
  ### Design concept
65
65
 
66
- The seam is built on one boundary: the harness's aspect ends at `emit()`. Capture, projection, redaction, and the handoff cursor live here; batching, retry, queueing, and loss policy are the reporting SDK's, deliberately not modelled or wrapped. The design and rejected alternatives are pinned in the [revival Agent Note](../../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md).
66
+ The seam is built on one boundary: the harness's aspect ends at `emit()`. Complete event capture, redaction, and the handoff cursor live here; batching, retry, queueing, and loss policy are the reporting SDK's, deliberately not modelled or wrapped. The design and rejected alternatives are pinned in the [revival Agent Note](../../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md).
67
67
 
68
68
  ### Source map
69
69
 
70
70
  | File | Role |
71
71
  |---|---|
72
72
  | [`src/index.ts`](src/index.ts) | Service Definition: `SessionTelemetryBackend`/`SessionTelemetrySink` contract, record vocabulary, `session-telemetry/record` waterfall declaration |
73
- | [`src/coordinator.ts`](src/coordinator.ts) | Capture: live listeners, on-demand replay, chunk projection, redaction, handoff cursor, containment |
73
+ | [`src/coordinator.ts`](src/coordinator.ts) | Capture: live listeners, lifecycle-local on-demand replay, redaction, handoff cursor, containment |
74
74
 
75
75
  ### Capture flow
76
76
 
77
- Live capture registers, through the composing fiber's effects: `session/created` adopts the session and replays its log from the handoff cursor; `session/event` projects, deep-copies, redacts, and hands off with zero I/O; `session/flush` forwards the optional hint and returns void so the loop's awaited parallel never waits on telemetry; `session/disposed` captures the session's `shutdown` marker and retires it; `agent/error` is the one live-bus relay, because the session-event vocabulary intentionally has no operational-error record. Disposal captures shutdown markers for still-live sessions, then awaits the backend's `shutdown()`. On-demand capture registers only the disposal effect and reads the canonical log on request. Every synchronous handler runs inside containment so a failing backend or rule can never starve other listeners or reach the agent loop.
77
+ Live capture registers Session events, flush hints, shutdown markers, and agent/error observers through the composing fiber’s effects. On-demand capture registers only the disposal effect and reads the requested canonical-log prefix under its history policy. Synchronous handlers contain failures so they cannot affect the agent loop or other listeners.
78
78
 
79
79
  ### The handoff cursor
80
80
 
81
- A module-scope `WeakMap<Session, seq>` records, per session, the highest seq handed off (not delivered). Live capture advances it at append time; on-demand capture advances it only while handing a requested prefix. An uncaptured prefix remains solely in the canonical log, so a coordinator reload adds no telemetry-owned recovery state; a missing cursor safely degrades to re-handing from the session's construction boundary, absorbed by receiver-side dedupe on `(session.id, event.seq)`. This is a narrow, documented 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 accepted cost matches at-most-once delivery: a resumed session does not backfill records a previous process failed to deliver.
81
+ A module-scope `WeakMap<Session, seq>` records the highest sequence handed off, not delivered. Re-adopting the same object resumes after that cursor. Capture normally starts at `firstLiveSeq`; explicit `includeHistory: true` starts an unhanded object at seq 0, including restored or fork history. The backend owns capture authorization. Stored history does not itself authorize capture; the OTel backend waits for new explicit feedback. Receivers deduplicate repeated records by `(session.id, session.format_version, event.seq)`.
82
82
 
83
83
  </details>
84
84
 
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-library"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-session-telemetry` 捕获会话活动用于对外上报:它把会话事件投影为遥测记录,允许部署方脱敏,再交给实现其约定的上报后端。部署方不直接加载本包——它们只加载一个后端(随附的 OpenTelemetry 后端是 `dsh-session-telemetry-otel`),由它注册 `ctx.sessionTelemetry` 并组装捕获协调器。seam 拥有捕获、脱敏与共享披露;批处理、重试、排队与丢失策略属于后端自身的 SDK,止于 `emit()`。每个已挂载后端都披露其部署级共享策略,使确认 surface 能够报告会话是否以及如何被共享。约定与捕获行为在前;实现内部细节放在下方可折叠的开发者章节中。
12
+ `dsh-session-telemetry` 捕获会话活动用于对外上报:它把每个会话事件复制为一条遥测记录,允许部署方脱敏,再交给实现该约定的上报后端。部署方不直接加载本包——它们只加载一个后端(随附的 OpenTelemetry 后端是 `dsh-session-telemetry-otel`),由它注册 `ctx.sessionTelemetry` 并组装捕获协调器。seam 拥有捕获、脱敏与共享披露;批处理、重试、排队与丢失策略属于后端自身的 SDK,止于 `emit()`。每个已挂载后端都披露其部署级共享策略,使确认 surface 能够报告会话是否以及如何被共享。约定与捕获行为在前;实现内部细节放在下方可折叠的开发者章节中。
13
13
 
14
14
  ## 目录
15
15
 
@@ -29,7 +29,7 @@ kind: "package-library"
29
29
 
30
30
  ### 选择并挂载后端
31
31
 
32
- 只加载一个后端插件;它把捕获协调器与自己的投递流水线注册为 `ctx.sessionTelemetry`,重复加载会抛出异常。已挂载后端通过必需的 [`sharing` 成员](#the-sharing-disclosure) 披露共享策略,`/feedback` 的确认文本会渲染它;只有在未挂载任何遥测服务时,消费方才渲染「未配置」。
32
+ 只加载一个后端插件;它把捕获协调器与投递流水线注册为 `ctx.sessionTelemetry`。重复加载会抛出异常。必需的 [`sharing` 成员](#the-sharing-disclosure) 报告部署模式,不代表会话准入或投递。只有在未挂载任何遥测服务时,消费方才可报告「未配置」。`/feedback` 命令确认记录,不读取此策略。
33
33
 
34
34
  ### 后端约定
35
35
 
@@ -37,19 +37,19 @@ kind: "package-library"
37
37
 
38
38
  ### 捕获内容
39
39
 
40
- 捕获以两种模式之一运行。`live` 捕获在追加时跟随会话事件、在挂载时回放已存活会话并记录生命周期标记;`on-demand` 捕获只在后端通过 `captureSession(session, throughSeq?)` 请求前缀时读取权威会话日志。ledger 记录与会话事件一一对应,唯有一个投影例外:每个 `(turn, step)` 只发出第一条 `assistant/chunk`,因此导出流中的 `seq` 缺口是常态,绝不是丢失信号。每条记录携带事件的完整数据、最小身份属性与预先映射的严重级别(`tool/result.isError`、`turn/end` 的错误原因与 `agent-error` 映射为 `error`;其余为 `info`)。
40
+ 捕获以两种模式之一运行。`live` 捕获在追加时跟随会话事件、在挂载时回放已存活会话并记录生命周期标记;`on-demand` 捕获只在后端通过 `captureSession(session, throughSeq?)` 请求前缀时读取权威会话日志。协调器选项决定是否包含存储历史。每条权威会话事件都按顺序映射为一条 ledger 记录。`assistant/message` `assistant/attempt` 记录会携带完整的嵌入式紧凑 stream,包括失败和重试输出。每条 ledger 记录还携带 `session.id`、`session.format_version`、数值事件身份、可选 header 事实与预先映射的严重级别(`tool/result.isError`、`turn/end` 的错误原因与 `agent-error` 映射为 `error`;其余为 `info`)。
41
41
 
42
42
  ### 共享披露
43
43
 
44
44
  <a id="the-sharing-disclosure"></a>
45
45
 
46
- 每个后端都通过 seam 的 `sharing` 词汇披露其部署级共享策略:`full`(每个事件在发生时立即交接)、`feedback-only`(在 `feedback/record` 事件释放其之前的未释放前缀之前,不交接任何内容)或 `disabled`(完全不交接任何内容)。已记录反馈条目的确认文本会报告该状态;披露从不声称投递——交接是非阻塞入队,批处理、重试与丢失策略仍归后端 SDK。
46
+ 每个后端通过 `sharing` 披露部署模式:`full`、`feedback-only` `disabled`。后端还可限制符合条件的 Session。该属性不是投递回执;交接是非阻塞入队,批处理、重试与丢失策略属于后端 SDK。
47
47
 
48
48
  ### 脱敏记录
49
49
 
50
50
  <a id="the-redact-waterfall"></a>
51
51
 
52
- 每条外发记录在投影后立即经过 `sessionTelemetry/record` waterfall(瀑布式事件)。本包不带任何规则:未挂载监听器时,记录以捕获时的原样到达后端,因此导出数据能干净到什么程度,恰恰取决于部署方挂载了什么规则。监听器通过变换 `next()` 的返回值来堆叠;抛出异常的监听器以 fail-closed 方式拦下这一条记录。脱敏只作用于外发副本——权威会话日志永不改写。
52
+ 协调器复制权威事件后,每条外发记录都会立即经过 `sessionTelemetry/record` waterfall(瀑布式事件)。本包不带任何规则:未挂载监听器时,记录以捕获时的原样到达后端,因此导出数据能干净到什么程度,恰恰取决于部署方挂载了什么规则。监听器通过变换 `next()` 的返回值来堆叠;抛出异常的监听器以 fail-closed 方式拦下这一条记录。脱敏只作用于外发副本——权威会话日志永不改写。
53
53
 
54
54
  -----
55
55
 
@@ -63,22 +63,22 @@ kind: "package-library"
63
63
 
64
64
  ### 设计理念
65
65
 
66
- seam 建立在一个边界之上:harness 的职责止于 `emit()`。捕获、投影、脱敏与 handoff 游标都在这里;批处理、重试、排队与丢失策略属于上报 SDK,本包有意不建模也不包装。设计与被否决的替代方案见[复活 Agent Note](../../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md)。
66
+ seam 建立在一个边界之上:harness 的职责止于 `emit()`。完整事件捕获、脱敏与 handoff 游标都在这里;批处理、重试、排队与丢失策略属于上报 SDK,本包有意不建模也不包装。设计与被否决的替代方案见[复活 Agent Note](../../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md)。
67
67
 
68
68
  ### 源码地图
69
69
 
70
70
  | 文件 | 职责 |
71
71
  |---|---|
72
72
  | [`src/index.ts`](src/index.ts) | Service Definition:`SessionTelemetryBackend`/`SessionTelemetrySink` 约定、记录词汇、`session-telemetry/record` waterfall 声明 |
73
- | [`src/coordinator.ts`](src/coordinator.ts) | 捕获:live 监听器、on-demand 回放、分片投影、脱敏、handoff 游标、异常隔离 |
73
+ | [`src/coordinator.ts`](src/coordinator.ts) | 捕获:live 监听器、生命周期本地 on-demand 回放、脱敏、handoff 游标、异常隔离 |
74
74
 
75
75
  ### 捕获流程
76
76
 
77
- live 捕获通过组合方 fiber 的 effect 注册:`session/created` 收养会话并从 handoff 游标起回放其日志;`session/event` 投影、深拷贝、脱敏并交接,零 I/O;`session/flush` 转发可选的提示并返回 void,使循环所等待的并行任务绝不等待遥测;`session/disposed` 捕获会话的 `shutdown` 标记并退役它;`agent/error` 是唯一的实时总线转发,因为会话事件词汇有意不包含运维错误记录。dispose 会为仍存活的会话捕获 shutdown 标记,然后等待后端的 `shutdown()`。on-demand 捕获只注册 dispose effect,并在请求时读取权威日志。每个同步处理器都运行在异常隔离之内,使失败的后端或规则永远不会饿死其他监听器,也永远不会触及 agent loop
77
+ 实时捕获通过组合 fiber 的 effect 注册 Session 事件、刷新提示、关闭标记与 agent/error 观察器。按需捕获只注册释放 effect,并按历史策略读取请求的权威日志前缀。同步处理器隔离失败,避免影响 agent loop 或其他监听器。
78
78
 
79
79
  ### handoff 游标
80
80
 
81
- 一个模块作用域的 `WeakMap<Session, seq>` 按会话记录已交接(而非已投递)的最高 seq。live 捕获在追加时推进它;on-demand 捕获只在交接所请求的前缀时推进它。未捕获的前缀只留在权威日志中,因此协调器重载不会增加遥测自有的恢复状态;游标缺失时安全退化为从会话构造边界起重新交接,由接收端基于 `(session.id, event.seq)` 的去重吸收。这是对「注册即 effect」纪律的一次有意的、有文档说明的窄例外:条目随其会话消亡,值是单调水位线,丢失它绝不是错误。由此接受的代价与至多一次(at-most-once)投递一致:恢复的会话不会回填上一个进程未能投递的记录。
81
+ 模块作用域的 `WeakMap<Session, seq>` 记录已交接而非已投递的最高序号。重新收养同一对象时从该游标之后继续。捕获通常从 `firstLiveSeq` 开始;显式 `includeHistory: true` 从未交接对象的 seq 0 开始,包含恢复或分叉历史。后端负责捕获授权。存储的历史本身不授权捕获;OTel 后端等待新的显式反馈。接收方按 `(session.id, session.format_version, event.seq)` 对重复记录去重。
82
82
 
83
83
  </details>
84
84
 
package/lib/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { Service } from "@deepseek-ai/cordis";
2
- import { SessionSeq } from "@deepseek-ai/dsh-session";
2
+ import { SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
3
3
  //#region lib/types/coordinator.js
4
4
  /**
5
5
  * Capture coordinator for the telemetry capability. Live capture subscribes to
6
6
  * the session firehose plus the one live-bus relay (`agent/error`). Both
7
- * capture paths apply the fixed chunk projection, build logical records, and
8
- * run each through the
7
+ * capture paths build one logical record per canonical Session event and run
8
+ * each through the
9
9
  * `session-telemetry/record` waterfall (deployment-mounted redaction rules;
10
10
  * pass-through when none), then hands the result to the backend. Live capture
11
11
  * follows the session firehose; on-demand capture replays the canonical log
@@ -31,10 +31,10 @@ const handoffCursor = /* @__PURE__ */ new WeakMap();
31
31
  /**
32
32
  * Install the telemetry capture side onto a context for one backend.
33
33
  *
34
- * Live capture registers the persistence-coordinator listener set plus the
35
- * `agent/error` relay, all through `ctx.effect()`/`ctx.on()` on the composing
36
- * fiber, and sweeps already-live sessions (a hot reload does not replay
37
- * `session/created`). A `session/disposed` captures the session's `shutdown`
34
+ * Live capture registers its own `session/created` / `session/event` /
35
+ * `session/disposed` listener set plus the `agent/error` relay, all through
36
+ * `ctx.effect()`/`ctx.on()` on the composing fiber, and sweeps already-live
37
+ * sessions (a hot reload does not replay `session/created`). A `session/disposed` captures the session's `shutdown`
38
38
  * operational record at its own termination edge and retires it from the
39
39
  * adopted set. On-demand capture registers none of those continuous listeners;
40
40
  * {@link captureSession} reads the canonical log explicitly and never creates
@@ -46,23 +46,23 @@ const handoffCursor = /* @__PURE__ */ new WeakMap();
46
46
  var SessionTelemetryCoordinator = class {
47
47
  ctx;
48
48
  backend;
49
+ options;
49
50
  /**
50
51
  * Sessions adopted by THIS fiber and still live, for double-adoption
51
52
  * protection and the teardown sweep of unmarked sessions;
52
53
  * `session/disposed` marks and retires entries.
53
54
  */
54
55
  adopted = /* @__PURE__ */ new Set();
55
- /** Per session, the `turn:step` keys whose first chunk already shipped; rebuilt from the log on re-adoption. */
56
- chunkSeen = /* @__PURE__ */ new WeakMap();
57
56
  /**
58
57
  * @param ctx - the composing backend's context; listeners bind to its fiber.
59
58
  * @param backend - the backend receiving records; owned elsewhere, never disposed here beyond `shutdown()` forwarding.
60
- * @param capture - follow live events, or wait for explicit canonical-log capture.
59
+ * @param options - capture mode and history policy.
61
60
  */
62
- constructor(ctx, backend, capture = "live") {
61
+ constructor(ctx, backend, options = {}) {
63
62
  this.ctx = ctx;
64
63
  this.backend = backend;
65
- if (capture === "live") {
64
+ this.options = options;
65
+ if ((options.capture ?? "live") === "live") {
66
66
  ctx.on("session/created", (session) => {
67
67
  this.adopt(session);
68
68
  });
@@ -101,7 +101,7 @@ var SessionTelemetryCoordinator = class {
101
101
  }, "telemetry capture");
102
102
  }
103
103
  /**
104
- * Project and hand over the canonical session-log suffix after the handoff
104
+ * Copy, redact, and hand over the canonical session-log suffix after the handoff
105
105
  * cursor, optionally stopping at an inclusive sequence boundary. Redaction
106
106
  * runs during this call, so an on-demand caller retains no copied records
107
107
  * before requesting capture and uses the policy mounted at that time.
@@ -111,28 +111,19 @@ var SessionTelemetryCoordinator = class {
111
111
  * @param throughSeq - optional last sequence included in this capture.
112
112
  */
113
113
  captureSession(session, throughSeq) {
114
- const cursor = handoffCursor.get(session) ?? (session.firstLiveSeq === 0 ? -1 : SessionSeq(session.firstLiveSeq - 1));
115
- for (const event of session.snapshotEvents()) {
114
+ const cursor = handoffCursor.get(session) ?? (this.options.includeHistory === true || session.firstLiveSeq === 0 ? -1 : SessionSeq(session.firstLiveSeq - 1));
115
+ for (const event of session.snapshotEvents(SessionLogOffset(cursor + 1))) {
116
116
  if (throughSeq !== void 0 && event.seq > throughSeq) break;
117
117
  this.contain(() => {
118
- if (event.seq <= cursor) this.track(session, event);
119
- else this.captureEvent(session, event);
118
+ this.captureEvent(session, event);
120
119
  });
121
120
  }
122
121
  }
123
122
  /**
124
- * Adopt a session: replay its log THROUGH the projection from the handoff
125
- * cursor, then rely on the firehose for everything after. When no cursor
126
- * survived, replay starts at the session's construction boundary
127
- * (`firstLiveSeq`), not seq 0: constructor seeds never publish on the
128
- * firehose, and their content already left the process under another
129
- * identity — the same id in a previous process (resume) or the parent's
130
- * stream (fork, stitched by receivers via `session.seed_length`). Events
131
- * at or below the start still feed the projection state (first-chunk
132
- * tracking) without being re-handed, so a resumed fiber drops mid-step
133
- * chunk continuations exactly like the fiber that saw the step begin. The
134
- * cost, accepted with the capture contract's at-most-once stance: a resume
135
- * does not backfill records a previous process failed to deliver.
123
+ * Adopt a session and replay after its handoff cursor, then follow live events.
124
+ * New objects include inherited and restored history only with includeHistory;
125
+ * otherwise replay starts at the constructor boundary. Re-adopting the same
126
+ * object resumes after its cursor.
136
127
  * @param session - the live session to adopt; a second adoption is a no-op.
137
128
  */
138
129
  adopt(session) {
@@ -140,18 +131,8 @@ var SessionTelemetryCoordinator = class {
140
131
  this.adopted.add(session);
141
132
  this.captureSession(session);
142
133
  }
143
- /** Feed the chunk projection without handing off the ≤cursor half of re-adoption. */
144
- track(session, event) {
145
- if (event.type === "assistant/chunk") this.seen(session).add(`${event.data.turn}:${event.data.step}`);
146
- }
147
- /** Project, redact, and hand one event to the backend. */
134
+ /** Copy, redact, and hand one canonical event to the backend. */
148
135
  captureEvent(session, event) {
149
- if (event.type === "assistant/chunk") {
150
- const key = `${event.data.turn}:${event.data.step}`;
151
- const seen = this.seen(session);
152
- if (seen.has(key)) return;
153
- seen.add(key);
154
- }
155
136
  this.deliver(session, {
156
137
  record: this.redact({
157
138
  channel: "ledger",
@@ -201,12 +182,6 @@ var SessionTelemetryCoordinator = class {
201
182
  body: detail
202
183
  }) });
203
184
  }
204
- /** Lazily create the per-session first-chunk tracking set. */
205
- seen(session) {
206
- let set = this.chunkSeen.get(session);
207
- if (!set) this.chunkSeen.set(session, set = /* @__PURE__ */ new Set());
208
- return set;
209
- }
210
185
  /**
211
186
  * Run one capture-side step with its exception contained: cordis `emit`
212
187
  * is stop-on-throw, so a throwing listener would starve every subscriber
@@ -256,6 +231,7 @@ function errorDetail(error) {
256
231
  function identityOf(session, event) {
257
232
  const attributes = {
258
233
  "session.id": String(session.id),
234
+ "session.format_version": session.header.version,
259
235
  "event.type": event.type,
260
236
  "event.seq": event.seq
261
237
  };
@@ -270,8 +246,8 @@ function identityOf(session, event) {
270
246
  /**
271
247
  * SessionTelemetryBackend Service Definition for the DeepSeek Harness.
272
248
  *
273
- * This package owns the CAPTURE side of session-event reporting — which records
274
- * exist (the chunk projection), what they carry (the logical record), when
249
+ * This package owns the CAPTURE side of session-event reporting — the complete
250
+ * one-record-per-event ledger mirror, what records carry, when
275
251
  * they are captured (adoption, the per-append firehose, lifecycle
276
252
  * forwarding), live versus on-demand canonical-log capture, and the HMR
277
253
  * cursor. Everything downstream of
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Capture coordinator for the telemetry capability. Live capture subscribes to
3
3
  * the session firehose plus the one live-bus relay (`agent/error`). Both
4
- * capture paths apply the fixed chunk projection, build logical records, and
5
- * run each through the
4
+ * capture paths build one logical record per canonical Session event and run
5
+ * each through the
6
6
  * `session-telemetry/record` waterfall (deployment-mounted redaction rules;
7
7
  * pass-through when none), then hands the result to the backend. Live capture
8
8
  * follows the session firehose; on-demand capture replays the canonical log
@@ -14,17 +14,24 @@
14
14
  * @module @deepseek-ai/dsh-session-telemetry/coordinator
15
15
  */
16
16
  import type { Context } from '@deepseek-ai/cordis';
17
- import type { Session, SessionSeq as SessionSeqType } from '@deepseek-ai/dsh-session';
17
+ import { type Session, type SessionSeq as SessionSeqType } from '@deepseek-ai/dsh-session';
18
18
  import type { SessionTelemetrySink } from './index.ts';
19
19
  /** Whether capture follows live events or reads the canonical log only when requested. */
20
20
  export type SessionTelemetryCapture = 'live' | 'on-demand';
21
+ /** Backend-selected capture mode and history policy. */
22
+ export interface SessionTelemetryCaptureOptions {
23
+ /** Follow live events, or wait for explicit capture; defaults to live. */
24
+ capture?: SessionTelemetryCapture;
25
+ /** Include stored history before this lifecycle; defaults to false. */
26
+ includeHistory?: boolean;
27
+ }
21
28
  /**
22
29
  * Install the telemetry capture side onto a context for one backend.
23
30
  *
24
- * Live capture registers the persistence-coordinator listener set plus the
25
- * `agent/error` relay, all through `ctx.effect()`/`ctx.on()` on the composing
26
- * fiber, and sweeps already-live sessions (a hot reload does not replay
27
- * `session/created`). A `session/disposed` captures the session's `shutdown`
31
+ * Live capture registers its own `session/created` / `session/event` /
32
+ * `session/disposed` listener set plus the `agent/error` relay, all through
33
+ * `ctx.effect()`/`ctx.on()` on the composing fiber, and sweeps already-live
34
+ * sessions (a hot reload does not replay `session/created`). A `session/disposed` captures the session's `shutdown`
28
35
  * operational record at its own termination edge and retires it from the
29
36
  * adopted set. On-demand capture registers none of those continuous listeners;
30
37
  * {@link captureSession} reads the canonical log explicitly and never creates
@@ -36,22 +43,21 @@ export type SessionTelemetryCapture = 'live' | 'on-demand';
36
43
  export declare class SessionTelemetryCoordinator {
37
44
  private readonly ctx;
38
45
  private readonly backend;
46
+ private readonly options;
39
47
  /**
40
48
  * Sessions adopted by THIS fiber and still live, for double-adoption
41
49
  * protection and the teardown sweep of unmarked sessions;
42
50
  * `session/disposed` marks and retires entries.
43
51
  */
44
52
  private readonly adopted;
45
- /** Per session, the `turn:step` keys whose first chunk already shipped; rebuilt from the log on re-adoption. */
46
- private readonly chunkSeen;
47
53
  /**
48
54
  * @param ctx - the composing backend's context; listeners bind to its fiber.
49
55
  * @param backend - the backend receiving records; owned elsewhere, never disposed here beyond `shutdown()` forwarding.
50
- * @param capture - follow live events, or wait for explicit canonical-log capture.
56
+ * @param options - capture mode and history policy.
51
57
  */
52
- constructor(ctx: Context, backend: SessionTelemetrySink, capture?: SessionTelemetryCapture);
58
+ constructor(ctx: Context, backend: SessionTelemetrySink, options?: SessionTelemetryCaptureOptions);
53
59
  /**
54
- * Project and hand over the canonical session-log suffix after the handoff
60
+ * Copy, redact, and hand over the canonical session-log suffix after the handoff
55
61
  * cursor, optionally stopping at an inclusive sequence boundary. Redaction
56
62
  * runs during this call, so an on-demand caller retains no copied records
57
63
  * before requesting capture and uses the policy mounted at that time.
@@ -62,24 +68,14 @@ export declare class SessionTelemetryCoordinator {
62
68
  */
63
69
  captureSession(session: Session, throughSeq?: SessionSeqType): void;
64
70
  /**
65
- * Adopt a session: replay its log THROUGH the projection from the handoff
66
- * cursor, then rely on the firehose for everything after. When no cursor
67
- * survived, replay starts at the session's construction boundary
68
- * (`firstLiveSeq`), not seq 0: constructor seeds never publish on the
69
- * firehose, and their content already left the process under another
70
- * identity — the same id in a previous process (resume) or the parent's
71
- * stream (fork, stitched by receivers via `session.seed_length`). Events
72
- * at or below the start still feed the projection state (first-chunk
73
- * tracking) without being re-handed, so a resumed fiber drops mid-step
74
- * chunk continuations exactly like the fiber that saw the step begin. The
75
- * cost, accepted with the capture contract's at-most-once stance: a resume
76
- * does not backfill records a previous process failed to deliver.
71
+ * Adopt a session and replay after its handoff cursor, then follow live events.
72
+ * New objects include inherited and restored history only with includeHistory;
73
+ * otherwise replay starts at the constructor boundary. Re-adopting the same
74
+ * object resumes after its cursor.
77
75
  * @param session - the live session to adopt; a second adoption is a no-op.
78
76
  */
79
77
  private adopt;
80
- /** Feed the chunk projection without handing off the ≤cursor half of re-adoption. */
81
- private track;
82
- /** Project, redact, and hand one event to the backend. */
78
+ /** Copy, redact, and hand one canonical event to the backend. */
83
79
  private captureEvent;
84
80
  /**
85
81
  * Run the `session-telemetry/record` waterfall at capture time. The innermost `next`
@@ -96,8 +92,6 @@ export declare class SessionTelemetryCoordinator {
96
92
  private hintFlush;
97
93
  /** Relay one `agent/error` bus emission as an `agent-error` operational record. */
98
94
  private relayAgentError;
99
- /** Lazily create the per-session first-chunk tracking set. */
100
- private seen;
101
95
  /**
102
96
  * Run one capture-side step with its exception contained: cordis `emit`
103
97
  * is stop-on-throw, so a throwing listener would starve every subscriber
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * SessionTelemetryBackend Service Definition for the DeepSeek Harness.
3
3
  *
4
- * This package owns the CAPTURE side of session-event reporting — which records
5
- * exist (the chunk projection), what they carry (the logical record), when
4
+ * This package owns the CAPTURE side of session-event reporting — the complete
5
+ * one-record-per-event ledger mirror, what records carry, when
6
6
  * they are captured (adoption, the per-append firehose, lifecycle
7
7
  * forwarding), live versus on-demand canonical-log capture, and the HMR
8
8
  * cursor. Everything downstream of
@@ -65,8 +65,9 @@ export interface SessionTelemetryRecord {
65
65
  severity: SessionTelemetrySeverity;
66
66
  /**
67
67
  * Identity attributes, deliberately minimal: ledger records carry
68
- * `session.id`, `event.type`, `event.seq`, plus `session.cwd` /
69
- * `session.parent_id` / `session.seed_length` when the header has them;
68
+ * `session.id`, `session.format_version`, `event.type`, `event.seq`, plus optional
69
+ * `session.cwd` / `session.parent_id`; a seeded Session also carries
70
+ * `session.seed_length` from its exact inherited event count;
70
71
  * ops records carry `telemetry.op`, `session.id`, and (for `agent-error`)
71
72
  * `agent.id`, `turn`, `step`, `error.name`. Anything recoverable from the
72
73
  * body is intentionally NOT duplicated here.
@@ -124,10 +125,7 @@ export interface SessionTelemetrySink {
124
125
  shutdown(): Promise<void>;
125
126
  }
126
127
  /**
127
- * Deployment-selected session-sharing policy disclosed by a mounted
128
- * {@link SessionTelemetryBackend} backend to human-facing acknowledgement surfaces (the
129
- * `/feedback` command's confirmation text). The Service Definition owns the
130
- * vocabulary so consumers and backends do not depend on a specific provider.
128
+ * Deployment-selected session-sharing mode, not confirmation of SDK delivery.
131
129
  */
132
130
  export type SessionTelemetrySharingStatus = 'full' | 'feedback-only' | 'disabled';
133
131
  /**
@@ -139,11 +137,7 @@ export type SessionTelemetrySharingStatus = 'full' | 'feedback-only' | 'disabled
139
137
  export declare abstract class SessionTelemetryBackend extends Service implements SessionTelemetrySink {
140
138
  constructor(ctx: Context);
141
139
  /**
142
- * Deployment-selected session-sharing policy, disclosed for acknowledgement
143
- * surfaces that report whether recorded feedback leaves the process. Every
144
- * backend must disclose its policy; a consumer renders "not configured" only
145
- * when no telemetry service is mounted. The seam owns this vocabulary so the
146
- * disclosure is backend-independent.
140
+ * Deployment-selected sharing mode, independent of SDK delivery.
147
141
  */
148
142
  abstract readonly sharing: SessionTelemetrySharingStatus;
149
143
  /**
@@ -159,5 +153,5 @@ export declare abstract class SessionTelemetryBackend extends Service implements
159
153
  */
160
154
  abstract shutdown(): Promise<void>;
161
155
  }
162
- export { SessionTelemetryCoordinator, type SessionTelemetryCapture } from './coordinator.ts';
156
+ export { SessionTelemetryCoordinator, type SessionTelemetryCapture, type SessionTelemetryCaptureOptions } from './coordinator.ts';
163
157
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-session-telemetry",
3
3
  "description": "SessionTelemetryBackend seam for the DeepSeek Harness: session-event capture, projection, redaction, and handoff to a reporting backend",
4
- "version": "0.1.2-alpha.5",
4
+ "version": "0.1.3-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,13 +27,13 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/dsh-agent": "^0.1.2-alpha.5",
31
- "@deepseek-ai/dsh-session": "^0.1.2-alpha.5",
32
- "@deepseek-ai/cordis": "^4.0.2"
30
+ "@deepseek-ai/dsh-agent": "^0.1.3-alpha.2",
31
+ "@deepseek-ai/cordis": "^4.0.2",
32
+ "@deepseek-ai/dsh-session": "^0.1.3-alpha.2"
33
33
  },
34
34
  "devDependencies": {
35
- "@deepseek-ai/dsh-agent": "^0.1.2-alpha.5",
35
+ "@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
36
36
  "@deepseek-ai/cordis": "^4.0.2",
37
- "@deepseek-ai/dsh-session": "^0.1.2-alpha.5"
37
+ "@deepseek-ai/dsh-agent": "^0.1.3-alpha.2"
38
38
  }
39
39
  }