@hasna-internal/kai-session-telemetry 0.1.1-rc.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/session/session-telemetry/README.md
5
+ README.md: e4ebee1324bc1228d6d97d891159685b05f94e4f
6
+ README.zh.md: 718f07db3fb8d8f8a21236dcb917a33ed3e0cad8
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # @hasna-internal/kai-session-telemetry
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ The telemetry Service Definition declares the `SessionTelemetrySink` 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](../../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md), [feedback-gated delivery](../../../.agents/notes/implemented/feature/2026-08-05-feedback-gated-session-telemetry.md), and [buffer-free feedback replay](../../../.agents/notes/implemented/simplification/2026-08-06-buffer-free-feedback-telemetry.md).
6
+
7
+ ## The backend contract
8
+
9
+ `SessionTelemetrySink` 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. `SessionTelemetryBackend` registers this API under the `sessionTelemetry` context key; each context accepts one implementation, and a duplicate load throws. A backend constructs `SessionTelemetryCoordinator` with `live` or `on-demand` capture and calls `captureSession(session, throughSeq?)` at its chosen trigger.
10
+
11
+ The service also carries the required [`SessionTelemetrySharingStatus`](#the-sharing-disclosure) `sharing` member: the deployment-selected sharing policy every backend must disclose to human-facing acknowledgement surfaces (the `/feedback` command's confirmation). A consumer renders "not configured" only when no telemetry service is mounted. The seam owns the vocabulary (`full` | `feedback-only` | `disabled`) so any backend can disclose a policy without depending on the OTel package.
12
+
13
+ ## The sharing disclosure
14
+
15
+ The acknowledgement of a recorded feedback entry reports whether and how the session is shared, read from the mounted backend's `sharing`. A backend sets the property from its deployment configuration: `full` (every event is handed over as it happens), `feedback-only` (nothing is handed over until a `feedback/record` event releases the unreleased prefix through it), or `disabled` (nothing is handed over at all). Consumers map the status onto user-facing copy; the disclosure never claims delivery — handoff is the non-blocking enqueue, and batching, retry, and loss policy stay the backend SDK's.
16
+
17
+ ## Capture points
18
+
19
+ 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.
20
+
21
+ ## The redact waterfall
22
+
23
+ Every record passes the `sessionTelemetry/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.
24
+
25
+ ## The handoff cursor
26
+
27
+ 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.
28
+
29
+ ## The fixed chunk projection
30
+
31
+ 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.
32
+
33
+ ## The logical record
34
+
35
+ `SessionTelemetryRecord`: `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 `sessionTelemetry/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 `sessionTelemetry.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)`.
36
+
37
+ ## Model Experience
38
+
39
+ None, as this package only observes the session stream and hands redacted copies to a reporting backend; it never contributes to a model request.
40
+
41
+ #### KV Cache effect
42
+
43
+ None; this package neither assembles nor sends a provider request.
44
+
45
+ ## Known Limitations and Deferred Work
46
+
47
+ - **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](../../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md).
48
+ - **No built-in redaction rules** — with no `sessionTelemetry/record` listener 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.
49
+ - **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.
package/README.zh.md ADDED
@@ -0,0 +1,53 @@
1
+ # @hasna-internal/kai-session-telemetry
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 遥测(telemetry)Service Definition 声明 `SessionTelemetrySink` 后端约定,捕获协调器把会话记录传给实现该约定的任意上报 SDK 后端。捕获侧可跟随实时会话事件,也可按需回放权威会话日志前缀。本包调用 `emit()` 后就停止处理:批处理、重试、排队与丢失策略都属于后端自身的 SDK,本包既不规定也不包装。设计依据与被否决的替代方案见[复活 Agent Note](../../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md)、[反馈门控投递](../../../.agents/notes/implemented/feature/2026-08-05-feedback-gated-session-telemetry.zh.md)与[无缓冲反馈回放](../../../.agents/notes/implemented/simplification/2026-08-06-buffer-free-feedback-telemetry.zh.md)。
6
+
7
+ ## 后端约定
8
+
9
+ `SessionTelemetrySink` 有三个成员:`emit(record)` 必须入队且不能阻塞,因为它会在 `session/event` 或显式权威日志回放期间同步执行;可选的 `flush()` 是轮次结束后的提示,调用方不等待结果,多数后端省略它并使用 SDK 的常规批处理计划;`shutdown()` 排空已入队记录,并在 SDK 停止后结束,dispose(资源释放)会等待它。提供 `flush()` 的实现必须安排并发 flush 与 `shutdown()` 最终排空的先后顺序。`SessionTelemetryBackend` 将此 API 注册在 `sessionTelemetry` 上下文键下:每个上下文只允许一个实现,重复加载会抛出异常。后端以 `live` 或 `on-demand` 捕获构造 `SessionTelemetryCoordinator`,并在自己选择的触发器中调用 `captureSession(session, throughSeq?)`。
10
+
11
+ 该服务还携带必需的 [`SessionTelemetrySharingStatus`](#the-sharing-disclosure) `sharing` 成员:每个后端都必须向面向用户的确认 surface(`/feedback` 命令的确认文本)披露的部署级共享策略。消费方只有在未挂载任何遥测服务时才渲染「未配置」。seam 拥有该词汇(`full` | `feedback-only` | `disabled`),因此任何后端都可以披露策略,而无需依赖 OTel 包。
12
+
13
+ <a id="the-sharing-disclosure"></a>
14
+
15
+ ## 共享披露
16
+
17
+ 一条已记录的反馈条目的确认文本会报告该会话是否以及如何被共享,读取自已挂载后端的 `sharing`。后端根据其部署配置设置该属性:`full`(每个事件在发生时立即交接)、`feedback-only`(在 `feedback/record` 事件释放其之前的未释放前缀之前,不交接任何内容)或 `disabled`(完全不交接任何内容)。消费方把状态映射为面向用户的文案;披露从不声称投递——交接是非阻塞入队,批处理、重试与丢失策略仍归后端 SDK。
18
+
19
+ ## 捕获点
20
+
21
+ 在 `live` 模式中,协调器的全部注册都经由组合方 fiber 的 effect 完成:`session/created`(收养:记录 header,并经投影从构造边界起回读日志;来自 fork 或恢复的构造函数种子绝不会在 firehose 上再次发出,也绝不会再次导出)、`session/event`(投影、深拷贝、脱敏,再交接;零 I/O)、`session/flush`(转发可选的 `flush()` 提示并返回 void;循环所等待的并行任务绝不能等待遥测)、`session/disposed`(在会话自身的终止边缘捕获该会话的 `shutdown` 运维记录,然后将其退役)、`agent/error`(唯一的实时总线转发;会话事件词汇有意不包含运维错误记录)、一个 dispose effect(捕获每个仍存活会话的 shutdown,再等待后端的 `shutdown()`;失败只发出警告而不抛出),以及对 `ctx.sessions.list()` 的收养扫描(热重载不会重放 `session/created`)。在 `on-demand` 模式中,协调器只注册 dispose effect:`captureSession()` 读取权威日志,直至可选的序列号边界(含边界);flush 提示与运维事件留在本地。
22
+
23
+ <a id="the-redact-waterfall"></a>
24
+
25
+ ## 脱敏 waterfall(瀑布式事件)
26
+
27
+ 每条记录在投影后立即经过 `sessionTelemetry/record` waterfall,这是 Service Definition 的脱敏扩展点。本包自身不带任何规则:最内层的 `next()` 原样透传记录,因此未挂载监听器时,记录以捕获时的原样到达后端;导出数据能干净到什么程度,恰恰取决于部署方挂载了什么规则。监听器通过变换 `next()` 的返回值来堆叠;不调用 `next()` 就返回,即替换其下方的全部逻辑;抛出异常的监听器会在协调器的隔离范围内以 fail-closed 方式拦下这一条记录。实时捕获在追加时运行 waterfall;按需捕获则在回放权威日志时使用当时挂载的规则运行 waterfall。脱敏只作用于外发副本;权威会话日志永不改写。
28
+
29
+ ## handoff 游标
30
+
31
+ 一个模块作用域的 `WeakMap<Session, seq>` 记录每个会话已交接(而非已投递)的最高 seq。实时捕获在追加时推进游标;按需捕获只有在 `captureSession()` 将请求的前缀交给后端时才推进游标。未捕获的前缀只留在权威日志中,因此协调器重载不会增加遥测自有的恢复状态。回放时,协调器只重新交接游标之后的事件(游标及其之前的事件仍用于重建分片投影状态);游标缺失时安全退化为从会话构造边界起的重新交接(`Session.firstLiveSeq`,对在本进程中诞生的会话即 seq 0),由接收端基于 `(session.id, event.seq)` 的去重吸收。构造函数种子绝不会再次导出:恢复会话的历史已由上一个进程以同一 id 发出,fork 继承的前缀则位于父会话的流中(接收端基于 `session.parent_id` + `session.seed_length` 拼接)。由此接受的代价与至多一次(at-most-once)投递一致:恢复不会回填上一个进程未能投递的记录;有回填要求的部署需要的是已推迟的 outbox,而不是回放。这是对「注册即 effect」纪律的一次有意且范围极窄的例外:条目随其会话消亡,值是单调水位线,丢失它绝不是错误。
32
+
33
+ ## 固定分片投影
34
+
35
+ 每个 `(turn, step)` 只发出第一条 `assistant/chunk`;其余分片在捕获时丢弃,且绝不推进游标。这一条分片就是「流已开始」的信号:`step/start`、首分片是否存在、`assistant/message` 是否存在,加上 `turn/end` 的原因,无需分片流量即可区分「请求从未开始」与「流中途夭折」,首个 token 延迟(time-to-first-token)也仍然可以计算。分片省略使导出流中的 `seq` 缺口成为常态:缺口绝不是丢失信号。其余所有事件类型都会完整透传,包括本包从未听说过的插件所合并的事件类型。
36
+
37
+ ## 逻辑记录
38
+
39
+ `SessionTelemetryRecord` 包含:`channel`(`ledger` | `ops`)、`time`(epoch 毫秒)、`severity`(预先映射好的严重级别:`tool/result.isError`、`turn/end` 的错误原因与 `agent-error` 映射为 ERROR,其他已捕获记录映射为 INFO,而 `sessionTelemetry/record` 策略可以指定 WARN)、只含身份信息的 `attributes`(`session.id`、`event.type`、`event.seq`,header 中存在时再加 `session.cwd`/`session.parent_id`/`session.seed_length`),以及作为 `body` 的完整深拷贝 `event.data`,且以脱敏后的内容为准。运维记录携带 `sessionTelemetry.op`(`agent-error` | `shutdown`)和 `session.id`,并刻意不带 `event.seq`/`event.type`:它们是用来告警的信号,不是用来累加的条目;`agent-error` 会把任意抛出值规范化为稳定的 `{ name, message }` 记录主体。交接之后的投递由后端 SDK 负责;重复仍然可能出现(无游标的重新收养、SDK 重试),因此接收端基于 `(session.id, event.seq)` 去重。
40
+
41
+ ## 模型体验
42
+
43
+ 无。本包只观察会话流,并把脱敏后的副本交给上报后端;它绝不向模型请求贡献任何内容。
44
+
45
+ #### KV Cache 影响
46
+
47
+ 无;本包既不组装也不发送提供方请求。
48
+
49
+ ## 已知限制与暂缓事项
50
+
51
+ - **尽力而为的投递**:游标标记的是已交接而非已投递;在重载窗口内被拆除的会话无法重新收养;崩溃时留在后端队列中的内容会丢失。持久化 outbox(spool、每 sink 游标、at-least-once)推迟到有部署方提出明确的崩溃丢失要求时再实现;见[复活 Agent Note](../../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md)。
52
+ - **不内置脱敏规则**:未挂载 `sessionTelemetry/record` 监听器时,记录以捕获时的原样离开进程,包括文件内容或命令输出中内嵌的任何凭据;向共享 collector 导出的部署方自行负责其规则集。
53
+ - **按需脱敏使用当前状态**:未捕获的事件只存在于权威会话日志中。后续的 `captureSession()` 会使用当时挂载的策略,深拷贝并脱敏其当前值;不存在捕获时的遥测快照或持久化的捕获前 spool。
package/lib/index.js ADDED
@@ -0,0 +1,296 @@
1
+ import { Service } from "@deepseek-ai/cordis";
2
+ //#region lib/types/coordinator.js
3
+ /**
4
+ * Capture coordinator for the telemetry capability. Live capture subscribes to
5
+ * the session firehose plus the one live-bus relay (`agent/error`). Both
6
+ * capture paths apply the fixed chunk projection, build logical records, and
7
+ * run each through the
8
+ * `session-telemetry/record` waterfall (deployment-mounted redaction rules;
9
+ * pass-through when none), then hands the result to the backend. Live capture
10
+ * follows the session firehose; on-demand capture replays the canonical log
11
+ * only when requested. Every synchronous handler is self-contained so a
12
+ * failing backend can never starve other subscribers (cordis `emit` is
13
+ * stop-on-throw) or touch the agent loop. Composed by a backend in its
14
+ * constructor.
15
+ *
16
+ * @module @hasna-internal/kai-session-telemetry/coordinator
17
+ */
18
+ /**
19
+ * The handoff cursor: per session, the highest `seq` handed to a backend.
20
+ * Deliberately MODULE-scope ambient state — a narrow, documented exception
21
+ * to the registrations-are-effects discipline: cordis has no HMR
22
+ * state-handover API, and keying by the `Session` object (which belongs to
23
+ * the session store and outlives any telemetry fiber) is the only in-process
24
+ * lifetime that lets a re-adopting fiber resume instead of re-handing
25
+ * history. Entries die with their sessions; a missing entry safely means
26
+ * "re-hand everything". Advanced only at emit time — the cursor marks
27
+ * handed-off, not delivered.
28
+ */
29
+ const handoffCursor = /* @__PURE__ */ new WeakMap();
30
+ /**
31
+ * Install the telemetry capture side onto a context for one backend.
32
+ *
33
+ * Live capture registers the persistence-coordinator listener set plus the
34
+ * `agent/error` relay, all through `ctx.effect()`/`ctx.on()` on the composing
35
+ * fiber, and sweeps already-live sessions (a hot reload does not replay
36
+ * `session/created`). A `session/disposed` captures the session's `shutdown`
37
+ * operational record at its own termination edge and retires it from the
38
+ * adopted set. On-demand capture registers none of those continuous listeners;
39
+ * {@link captureSession} reads the canonical log explicitly and never creates
40
+ * operational records. Disposal captures shutdown markers for live-adopted
41
+ * sessions, then awaits the backend's `shutdown()`; a failure there warns
42
+ * instead of throwing — best-effort reporting must not fail application
43
+ * teardown.
44
+ */
45
+ var SessionTelemetryCoordinator = class {
46
+ ctx;
47
+ backend;
48
+ /**
49
+ * Sessions adopted by THIS fiber and still live, for double-adoption
50
+ * protection and the teardown sweep of unmarked sessions;
51
+ * `session/disposed` marks and retires entries.
52
+ */
53
+ adopted = /* @__PURE__ */ new Set();
54
+ /** Per session, the `turn:step` keys whose first chunk already shipped; rebuilt from the log on re-adoption. */
55
+ chunkSeen = /* @__PURE__ */ new WeakMap();
56
+ /**
57
+ * @param ctx - the composing backend's context; listeners bind to its fiber.
58
+ * @param backend - the backend receiving records; owned elsewhere, never disposed here beyond `shutdown()` forwarding.
59
+ * @param capture - follow live events, or wait for explicit canonical-log capture.
60
+ */
61
+ constructor(ctx, backend, capture = "live") {
62
+ this.ctx = ctx;
63
+ this.backend = backend;
64
+ if (capture === "live") {
65
+ ctx.on("session/created", (session) => {
66
+ this.adopt(session);
67
+ });
68
+ ctx.on("session/disposed", (session) => {
69
+ this.contain(() => {
70
+ if (!this.adopted.delete(session)) return;
71
+ this.deliver(session, { record: this.redact(shutdownRecord(session)) });
72
+ });
73
+ });
74
+ ctx.on("session/event", (session, event) => {
75
+ this.contain(() => {
76
+ this.captureEvent(session, event);
77
+ });
78
+ });
79
+ ctx.on("session/flush", (session) => {
80
+ this.contain(() => {
81
+ this.hintFlush(session);
82
+ });
83
+ });
84
+ ctx.on("agent/error", ({ agent, turn, step, error }) => {
85
+ this.contain(() => {
86
+ this.relayAgentError(agent, turn, step, error);
87
+ });
88
+ });
89
+ for (const session of ctx.sessions.list()) this.adopt(session);
90
+ }
91
+ ctx.effect(() => async () => {
92
+ for (const session of this.adopted) this.contain(() => {
93
+ this.deliver(session, { record: this.redact(shutdownRecord(session)) });
94
+ });
95
+ try {
96
+ await this.backend.shutdown();
97
+ } catch (error) {
98
+ this.ctx.logger.warn(`telemetry: backend shutdown failed: ${String(error)}`);
99
+ }
100
+ }, "telemetry capture");
101
+ }
102
+ /**
103
+ * Project and hand over the canonical session-log suffix after the handoff
104
+ * cursor, optionally stopping at an inclusive sequence boundary. Redaction
105
+ * runs during this call, so an on-demand caller retains no copied records
106
+ * before requesting capture and uses the policy mounted at that time.
107
+ * Backend and policy failures remain contained per event and do not starve
108
+ * later events in the same replay.
109
+ * @param session - session whose current canonical-log prefix may be handed over.
110
+ * @param throughSeq - optional last sequence included in this capture.
111
+ */
112
+ captureSession(session, throughSeq) {
113
+ const cursor = handoffCursor.get(session) ?? session.firstLiveSeq - 1;
114
+ for (const event of session.events) {
115
+ if (throughSeq !== void 0 && event.seq > throughSeq) break;
116
+ this.contain(() => {
117
+ if (event.seq <= cursor) this.track(session, event);
118
+ else this.captureEvent(session, event);
119
+ });
120
+ }
121
+ }
122
+ /**
123
+ * Adopt a session: replay its log THROUGH the projection from the handoff
124
+ * cursor, then rely on the firehose for everything after. When no cursor
125
+ * survived, replay starts at the session's construction boundary
126
+ * (`firstLiveSeq`), not seq 0: constructor seeds never publish on the
127
+ * firehose, and their content already left the process under another
128
+ * identity — the same id in a previous process (resume) or the parent's
129
+ * stream (fork, stitched by receivers via `session.seed_length`). Events
130
+ * at or below the start still feed the projection state (first-chunk
131
+ * tracking) without being re-handed, so a resumed fiber drops mid-step
132
+ * chunk continuations exactly like the fiber that saw the step begin. The
133
+ * cost, accepted with the capture contract's at-most-once stance: a resume no longer
134
+ * backfills records a previous process failed to deliver.
135
+ * @param session - the live session to adopt; a second adoption is a no-op.
136
+ */
137
+ adopt(session) {
138
+ if (this.adopted.has(session)) return;
139
+ this.adopted.add(session);
140
+ this.captureSession(session);
141
+ }
142
+ /** Feed the chunk projection without handing off — the ≤cursor half of re-adoption. */
143
+ track(session, event) {
144
+ if (event.type === "assistant/chunk") this.seen(session).add(`${event.data.turn}:${event.data.step}`);
145
+ }
146
+ /** Project, redact, and hand one event to the backend. */
147
+ captureEvent(session, event) {
148
+ if (event.type === "assistant/chunk") {
149
+ const key = `${event.data.turn}:${event.data.step}`;
150
+ const seen = this.seen(session);
151
+ if (seen.has(key)) return;
152
+ seen.add(key);
153
+ }
154
+ this.deliver(session, {
155
+ record: this.redact({
156
+ channel: "ledger",
157
+ time: event.time,
158
+ severity: severityOf(event),
159
+ attributes: identityOf(session, event),
160
+ body: structuredClone(event.data)
161
+ }),
162
+ seq: event.seq
163
+ });
164
+ }
165
+ /**
166
+ * Run the `session-telemetry/record` waterfall at capture time. The innermost `next`
167
+ * passes the record through unchanged — this package ships no rules; exported
168
+ * data is as clean as the listeners a deployment mounts. Callers run inside
169
+ * {@link contain}, so a throwing rule withholds the record instead of
170
+ * reaching the loop (fail-closed). On-demand capture invokes this waterfall
171
+ * while reading the canonical session log, not when the event was appended.
172
+ */
173
+ redact(record) {
174
+ return this.ctx.waterfall("session-telemetry/record", record, () => record);
175
+ }
176
+ /** Hand one redacted record to the backend, then advance its ledger cursor. */
177
+ deliver(session, pending) {
178
+ this.backend.emit(pending.record);
179
+ if (pending.seq !== void 0) handoffCursor.set(session, pending.seq);
180
+ }
181
+ /** Forward the turn-end boundary to the backend's optional flush hint. */
182
+ hintFlush(session) {
183
+ if (this.adopted.has(session)) this.backend.flush?.();
184
+ }
185
+ /** Relay one `agent/error` bus emission as an `agent-error` operational record. */
186
+ relayAgentError(agent, turn, step, error) {
187
+ const detail = errorDetail(error);
188
+ this.deliver(agent.session, { record: this.redact({
189
+ channel: "ops",
190
+ time: Date.now(),
191
+ severity: "error",
192
+ attributes: {
193
+ "telemetry.op": "agent-error",
194
+ "session.id": String(agent.session.id),
195
+ "agent.id": agent.id,
196
+ "error.name": detail.name,
197
+ turn,
198
+ step
199
+ },
200
+ body: detail
201
+ }) });
202
+ }
203
+ /** Lazily create the per-session first-chunk tracking set. */
204
+ seen(session) {
205
+ let set = this.chunkSeen.get(session);
206
+ if (!set) this.chunkSeen.set(session, set = /* @__PURE__ */ new Set());
207
+ return set;
208
+ }
209
+ /**
210
+ * Run one capture-side step with its exception contained: cordis `emit`
211
+ * is stop-on-throw, so a throwing listener would starve every subscriber
212
+ * registered after this plugin — nothing from the backend may escape.
213
+ */
214
+ contain(step) {
215
+ try {
216
+ step();
217
+ } catch (error) {
218
+ this.ctx.logger.warn(`telemetry: capture step failed: ${String(error)}`);
219
+ }
220
+ }
221
+ };
222
+ /**
223
+ * Build the per-session clean-exit marker: emitted at the session's own
224
+ * disposal edge, or at coordinator dispose for sessions still alive then.
225
+ */
226
+ function shutdownRecord(session) {
227
+ return {
228
+ channel: "ops",
229
+ time: Date.now(),
230
+ severity: "info",
231
+ attributes: {
232
+ "telemetry.op": "shutdown",
233
+ "session.id": String(session.id)
234
+ },
235
+ body: { op: "shutdown" }
236
+ };
237
+ }
238
+ /** Map an event's own outcome flag to the pre-baked alerting severity. */
239
+ function severityOf(event) {
240
+ switch (event.type) {
241
+ case "tool/result": return event.data.message.content[0].isError === true ? "error" : "info";
242
+ case "turn/end": return event.data.reason.kind === "error" ? "error" : "info";
243
+ default: return "info";
244
+ }
245
+ }
246
+ /** Normalize the live bus's arbitrary thrown value into the stable operational-record shape. */
247
+ function errorDetail(error) {
248
+ const normalized = error instanceof Error ? error : new Error(String(error));
249
+ return {
250
+ name: normalized.name,
251
+ message: normalized.message
252
+ };
253
+ }
254
+ /** Build the minimal identity attributes: envelope plus self-contained header facts. */
255
+ function identityOf(session, event) {
256
+ const attributes = {
257
+ "session.id": String(session.id),
258
+ "event.type": event.type,
259
+ "event.seq": event.seq
260
+ };
261
+ const { cwd, parentSession, seedLength } = session.header;
262
+ if (cwd !== void 0) attributes["session.cwd"] = cwd;
263
+ if (parentSession !== void 0) attributes["session.parent_id"] = String(parentSession);
264
+ if (seedLength !== void 0) attributes["session.seed_length"] = seedLength;
265
+ return attributes;
266
+ }
267
+ //#endregion
268
+ //#region lib/types/index.js
269
+ /**
270
+ * SessionTelemetryBackend Service Definition for the DeepSeek Harness.
271
+ *
272
+ * This package owns the CAPTURE side of session-event reporting — which records
273
+ * exist (the chunk projection), what they carry (the logical record), when
274
+ * they are captured (adoption, the per-append firehose, lifecycle
275
+ * forwarding), live versus on-demand canonical-log capture, and the HMR
276
+ * cursor. Everything downstream of
277
+ * {@link SessionTelemetryBackend.emit} — batching, retry, queueing, and loss policy — is the
278
+ * reporting SDK's territory and is deliberately not modelled here. The
279
+ * design and its trade-offs are pinned in
280
+ * .agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md.
281
+ *
282
+ * @module @hasna-internal/kai-session-telemetry
283
+ */
284
+ /**
285
+ * Loadable form of the backend contract: one implementation per context —
286
+ * the cordis `Service` registration under the `telemetry` key throws on a
287
+ * duplicate, cordis' standard behavior. A backend composes a
288
+ * {@link SessionTelemetryCoordinator} in its constructor to install the capture side.
289
+ */
290
+ var SessionTelemetryBackend = class extends Service {
291
+ constructor(ctx) {
292
+ super(ctx, "sessionTelemetry");
293
+ }
294
+ };
295
+ //#endregion
296
+ export { SessionTelemetryBackend, SessionTelemetryCoordinator };
@@ -0,0 +1,25 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@hasna-internal/kai-session-telemetry`.
4
+ * @module @hasna-internal/kai-session-telemetry/invariant
5
+ */
6
+ const PACKAGE_NAME = "@hasna-internal/kai-session-telemetry";
7
+ /** Cordis companion plugin name. */
8
+ const name = "session-telemetry-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: the package's whole output is the backend handoff — a
13
+ * synchronous `emit()` call outside every authoritative event stream — and its
14
+ * capture side never appends session events, so no event/data relation exists
15
+ * for an independent companion to observe.
16
+ */
17
+ const install = () => {};
18
+ /**
19
+ * Register this package's invariant companion.
20
+ * @param ctx - Cordis context carrying the invariant service.
21
+ * @returns the installed registration's disposer after setup succeeds.
22
+ */
23
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
24
+ //#endregion
25
+ export { apply, inject, name };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Capture coordinator for the telemetry capability. Live capture subscribes to
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
6
+ * `session-telemetry/record` waterfall (deployment-mounted redaction rules;
7
+ * pass-through when none), then hands the result to the backend. Live capture
8
+ * follows the session firehose; on-demand capture replays the canonical log
9
+ * only when requested. Every synchronous handler is self-contained so a
10
+ * failing backend can never starve other subscribers (cordis `emit` is
11
+ * stop-on-throw) or touch the agent loop. Composed by a backend in its
12
+ * constructor.
13
+ *
14
+ * @module @hasna-internal/kai-session-telemetry/coordinator
15
+ */
16
+ import type { Context } from '@deepseek-ai/cordis';
17
+ import type { Session } from '@hasna-internal/kai-session';
18
+ import type { SessionTelemetrySink } from './index.ts';
19
+ /** Whether capture follows live events or reads the canonical log only when requested. */
20
+ export type SessionTelemetryCapture = 'live' | 'on-demand';
21
+ /**
22
+ * Install the telemetry capture side onto a context for one backend.
23
+ *
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`
28
+ * operational record at its own termination edge and retires it from the
29
+ * adopted set. On-demand capture registers none of those continuous listeners;
30
+ * {@link captureSession} reads the canonical log explicitly and never creates
31
+ * operational records. Disposal captures shutdown markers for live-adopted
32
+ * sessions, then awaits the backend's `shutdown()`; a failure there warns
33
+ * instead of throwing — best-effort reporting must not fail application
34
+ * teardown.
35
+ */
36
+ export declare class SessionTelemetryCoordinator {
37
+ private readonly ctx;
38
+ private readonly backend;
39
+ /**
40
+ * Sessions adopted by THIS fiber and still live, for double-adoption
41
+ * protection and the teardown sweep of unmarked sessions;
42
+ * `session/disposed` marks and retires entries.
43
+ */
44
+ 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
+ /**
48
+ * @param ctx - the composing backend's context; listeners bind to its fiber.
49
+ * @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.
51
+ */
52
+ constructor(ctx: Context, backend: SessionTelemetrySink, capture?: SessionTelemetryCapture);
53
+ /**
54
+ * Project and hand over the canonical session-log suffix after the handoff
55
+ * cursor, optionally stopping at an inclusive sequence boundary. Redaction
56
+ * runs during this call, so an on-demand caller retains no copied records
57
+ * before requesting capture and uses the policy mounted at that time.
58
+ * Backend and policy failures remain contained per event and do not starve
59
+ * later events in the same replay.
60
+ * @param session - session whose current canonical-log prefix may be handed over.
61
+ * @param throughSeq - optional last sequence included in this capture.
62
+ */
63
+ captureSession(session: Session, throughSeq?: number): void;
64
+ /**
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 no longer
76
+ * backfills records a previous process failed to deliver.
77
+ * @param session - the live session to adopt; a second adoption is a no-op.
78
+ */
79
+ 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. */
83
+ private captureEvent;
84
+ /**
85
+ * Run the `session-telemetry/record` waterfall at capture time. The innermost `next`
86
+ * passes the record through unchanged — this package ships no rules; exported
87
+ * data is as clean as the listeners a deployment mounts. Callers run inside
88
+ * {@link contain}, so a throwing rule withholds the record instead of
89
+ * reaching the loop (fail-closed). On-demand capture invokes this waterfall
90
+ * while reading the canonical session log, not when the event was appended.
91
+ */
92
+ private redact;
93
+ /** Hand one redacted record to the backend, then advance its ledger cursor. */
94
+ private deliver;
95
+ /** Forward the turn-end boundary to the backend's optional flush hint. */
96
+ private hintFlush;
97
+ /** Relay one `agent/error` bus emission as an `agent-error` operational record. */
98
+ private relayAgentError;
99
+ /** Lazily create the per-session first-chunk tracking set. */
100
+ private seen;
101
+ /**
102
+ * Run one capture-side step with its exception contained: cordis `emit`
103
+ * is stop-on-throw, so a throwing listener would starve every subscriber
104
+ * registered after this plugin — nothing from the backend may escape.
105
+ */
106
+ private contain;
107
+ }
108
+ //# sourceMappingURL=coordinator.d.ts.map
@@ -0,0 +1,164 @@
1
+ /**
2
+ * SessionTelemetryBackend Service Definition for the DeepSeek Harness.
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
6
+ * they are captured (adoption, the per-append firehose, lifecycle
7
+ * forwarding), live versus on-demand canonical-log capture, and the HMR
8
+ * cursor. Everything downstream of
9
+ * {@link SessionTelemetryBackend.emit} — batching, retry, queueing, and loss policy — is the
10
+ * reporting SDK's territory and is deliberately not modelled here. The
11
+ * design and its trade-offs are pinned in
12
+ * .agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md.
13
+ *
14
+ * @module @hasna-internal/kai-session-telemetry
15
+ */
16
+ import { Context, Service } from '@deepseek-ai/cordis';
17
+ declare module '@deepseek-ai/cordis' {
18
+ interface Context {
19
+ sessionTelemetry: SessionTelemetryBackend;
20
+ }
21
+ interface Events {
22
+ /**
23
+ * Transform one outbound record before it reaches the backend. This
24
+ * waterfall is the Service Definition's redaction extension point. It ships NO rules
25
+ * of its own: the
26
+ * innermost `next()` passes the record through unchanged, and with no
27
+ * listener mounted records reach the backend as captured, so exported
28
+ * data is exactly as clean as the rules a deployment mounts. Listeners
29
+ * stack by transforming `next()`'s return value; returning without
30
+ * `next()` replaces everything beneath. Dispatched synchronously on the
31
+ * capture hot path inside the coordinator's containment: a throwing
32
+ * listener withholds that one record (fail-closed) and never reaches the
33
+ * agent loop. Live capture dispatches at append time; on-demand capture
34
+ * dispatches while reading the canonical log. Redaction applies to the
35
+ * exported copy only; the canonical session log is never rewritten.
36
+ * @param record - the candidate record, already the coordinator's own deep
37
+ * copy; listeners return a (possibly new) record and must not mutate it.
38
+ * @mode waterfall
39
+ */
40
+ 'session-telemetry/record'(record: SessionTelemetryRecord, next: () => SessionTelemetryRecord): SessionTelemetryRecord;
41
+ }
42
+ }
43
+ /**
44
+ * Severity of a telemetry record, pre-mapped at capture so a receiver can
45
+ * alert with zero configuration: `error` for events whose own outcome flag
46
+ * says so (the tool-result block's `isError`, `turn/end` error reasons) and for
47
+ * `agent-error` operational records. Captured events otherwise default to
48
+ * `info`; `warn` remains available to `session-telemetry/record` policies and
49
+ * backends.
50
+ */
51
+ export type SessionTelemetrySeverity = 'info' | 'warn' | 'error';
52
+ /**
53
+ * One logical record handed to a backend — the capture contract's whole outbound
54
+ * vocabulary. Ledger records mirror session-log events one-to-one;
55
+ * operational records (`channel: 'ops'`) carry the two signals with no log
56
+ * home (`agent-error`, `shutdown`) and deliberately omit `event.seq`-style
57
+ * identity so they can never be mistaken for ledger rows.
58
+ */
59
+ export interface SessionTelemetryRecord {
60
+ /** Ledger (session-log mirror) or ops (operational signal) channel; backends keep the two under separate instrumentation scopes. */
61
+ channel: 'ledger' | 'ops';
62
+ /** Unix epoch milliseconds — the source event's append time for ledger records, the emission time for ops records. */
63
+ time: number;
64
+ /** Pre-mapped alerting severity; see {@link SessionTelemetrySeverity}. */
65
+ severity: SessionTelemetrySeverity;
66
+ /**
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;
70
+ * ops records carry `telemetry.op`, `session.id`, and (for `agent-error`)
71
+ * `agent.id`, `turn`, `step`, `error.name`. Anything recoverable from the
72
+ * body is intentionally NOT duplicated here.
73
+ */
74
+ attributes: Record<string, string | number>;
75
+ /**
76
+ * The complete payload: a deep copy of the session event's `data` for
77
+ * ledger records (JSON-serializable by `Session.append`'s own
78
+ * validation), or the op payload for ops records. Never mutated after
79
+ * handoff.
80
+ */
81
+ body: unknown;
82
+ }
83
+ /**
84
+ * The minimum backend contract the coordinator requires. {@link SessionTelemetryBackend} is
85
+ * its service-registered form; tests compose the coordinator with a bare
86
+ * implementation of this interface.
87
+ */
88
+ export interface SessionTelemetrySink {
89
+ /**
90
+ * Hand one record to the backend's pipeline. MUST be a non-blocking
91
+ * enqueue — the coordinator calls this synchronously from the
92
+ * `session/event` hot path or an explicit canonical-log capture, so anything
93
+ * slower than a queue push would tax the agent loop or feedback handling.
94
+ * Errors thrown here are contained by the coordinator and logged; they
95
+ * never reach the loop.
96
+ * @param record - the logical record to report; owned by the backend after the call.
97
+ */
98
+ emit(record: SessionTelemetryRecord): void;
99
+ /**
100
+ * Optional hint that a turn ended. A backend may forward it to its SDK's
101
+ * flush so records are exported after each turn. Called
102
+ * fire-and-forget; implementations must not block and must not throw
103
+ * meaningfully (the coordinator contains exceptions). Most backends should
104
+ * leave this unimplemented and let their SDK's own batching cadence govern
105
+ * export timing: a backend that does implement it owns the interaction
106
+ * between its concurrent flushes and {@link shutdown}'s drain (the OTel
107
+ * backend leaves it unimplemented for exactly that hazard — see the
108
+ * revival Agent Note).
109
+ */
110
+ flush?(): void;
111
+ /**
112
+ * Forward the fiber's disposal to the SDK: flush whatever is queued and
113
+ * reach quiescence, per the SDK's own shutdown contract. Everything
114
+ * emitted before this call must still be delivered — including records
115
+ * enqueued while a {@link flush} hint is in flight, so a backend whose SDK
116
+ * guards against concurrent flushes orders behind the outstanding one (the
117
+ * coordinator emits its dispose-time `shutdown` markers immediately before
118
+ * calling this). Awaited by the coordinator's dispose; a rejection is
119
+ * logged as a warning and never fails application teardown.
120
+ * The coordinator captures dispose-time shutdown markers immediately before
121
+ * this call for live capture; on-demand capture creates no ops records.
122
+ * @returns resolves when the backend's pipeline has quiesced.
123
+ */
124
+ shutdown(): Promise<void>;
125
+ }
126
+ /**
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 seam owns the vocabulary so
130
+ * any backend can disclose a policy without depending on the OTel package;
131
+ * the values mirror the OTel backend's serialized `SessionTelemetryMode` choices.
132
+ */
133
+ export type SessionTelemetrySharingStatus = 'full' | 'feedback-only' | 'disabled';
134
+ /**
135
+ * Loadable form of the backend contract: one implementation per context —
136
+ * the cordis `Service` registration under the `telemetry` key throws on a
137
+ * duplicate, cordis' standard behavior. A backend composes a
138
+ * {@link SessionTelemetryCoordinator} in its constructor to install the capture side.
139
+ */
140
+ export declare abstract class SessionTelemetryBackend extends Service implements SessionTelemetrySink {
141
+ constructor(ctx: Context);
142
+ /**
143
+ * Deployment-selected session-sharing policy, disclosed for acknowledgement
144
+ * surfaces that report whether recorded feedback leaves the process. Every
145
+ * backend must disclose its policy; a consumer renders "not configured" only
146
+ * when no telemetry service is mounted. The seam owns this vocabulary so the
147
+ * disclosure is backend-independent.
148
+ */
149
+ abstract readonly sharing: SessionTelemetrySharingStatus;
150
+ /**
151
+ * See {@link SessionTelemetrySink.emit} — that declaration is the contract's one home.
152
+ * @param record - the logical record to report; owned by the backend after the call.
153
+ */
154
+ abstract emit(record: SessionTelemetryRecord): void;
155
+ /** See {@link SessionTelemetrySink.flush}. */
156
+ flush?(): void;
157
+ /**
158
+ * See {@link SessionTelemetrySink.shutdown}.
159
+ * @returns resolves when the backend's pipeline has quiesced.
160
+ */
161
+ abstract shutdown(): Promise<void>;
162
+ }
163
+ export { SessionTelemetryCoordinator, type SessionTelemetryCapture } from './coordinator.ts';
164
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@hasna-internal/kai-session-telemetry`.
3
+ * @module @hasna-internal/kai-session-telemetry/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "session-telemetry-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@hasna-internal/kai-session-telemetry",
3
+ "description": "SessionTelemetryBackend seam for the DeepSeek Harness: session-event capture, projection, redaction, and handoff to a reporting backend",
4
+ "version": "0.1.1-rc.2",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/session/session-telemetry"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./src/*": "./src/*",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "lib/index.js",
30
+ "lib/invariant.js",
31
+ "lib/types/**/*.d.ts"
32
+ ],
33
+ "license": "MIT",
34
+ "peerDependencies": {
35
+ "@hasna-internal/kai-agent": "^0.1.1-rc.2",
36
+ "@hasna-internal/kai-session": "^0.1.1-rc.2",
37
+ "@deepseek-ai/cordis": "^4.0.1",
38
+ "@hasna-internal/kai-invariants": "^0.1.1-rc.2"
39
+ },
40
+ "devDependencies": {
41
+ "@deepseek-ai/cordis": "^4.0.1",
42
+ "@hasna-internal/kai-invariants": "^0.1.1-rc.2",
43
+ "@hasna-internal/kai-session": "^0.1.1-rc.2",
44
+ "@hasna-internal/kai-agent": "^0.1.1-rc.2"
45
+ }
46
+ }