Merge branch 'codex/disable-telemetry-default' into master
Resolved conflicts: - packages/client/ui-settings-general/README.md, README.zh.md: kept PR opt-in telemetry description (DSH_TELEMETRY_MODE) - scripts/snapshots/translation-prompt-v4: kept master's newer README structure snapshot - i18n.yaml pairing records: resolved per file state - pnpm-lock.yaml: regenerated - Modify/delete conflicts (scaffold/telemetry, sdk-follow-up-capabilities): kept master deletions
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-checkpoint-policy",
|
||||
"description": "Semantic session durability checkpoints before model requests and tool side effects",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -13,6 +13,8 @@ import * as checkpointPolicy from '../src/index.ts'
|
||||
const contexts: Context[] = []
|
||||
|
||||
class TestPersistence extends SessionPersistence {
|
||||
override readonly supportsRawArtifacts = false
|
||||
|
||||
locate(_meta: SessionHeader): undefined { return undefined }
|
||||
create(_meta: SessionHeader): Promise<void> { return Promise.resolve() }
|
||||
append(_id: SessionId, _events: readonly SessionEvent[]): Promise<void> { return Promise.resolve() }
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/session/session-persistence-jsonl/README.md
|
||||
README.md: e2416cd36e3fb1d8f93e921800f2247fe29f3b09
|
||||
README.zh.md: 4eb2d4f2bebf9ed17190ef3cb21a2bc3c8d9123b
|
||||
README.md: 4cff3215cdb083d2fdb7c4a8f1b60e8c4028ba84
|
||||
README.zh.md: 7e3ba5be4f2707ff6408d296ece1f43550d76286
|
||||
|
||||
@@ -42,7 +42,7 @@ A root belongs to one encoding. Startup discovery and targeted lookup reject the
|
||||
- **Bound storage identity.** Lookup requires one matching session directory across the readable project directories, then verifies that the header id equals the requested id and that the header's id/cwd derive the selected transcript path. Listing applies the same path check and rejects duplicate ids. Identity failures occur before repair or append.
|
||||
- **Lazy materialization.** `create(meta)` writes nothing; on the first `append`, the backend writes and `fsync`s the encoded header and first batch in a temporary file. POSIX publishes it without overwrite via a hard link and `fsync`s the parent directory. Windows publishes it without overwrite via `MoveFileExW(..., MOVEFILE_WRITE_THROUGH)` and creates missing directories through the same write-through pattern. A created-but-never-appended session leaves nothing on disk and is absent from `list`.
|
||||
- **Append-only.** Flushed events are never rewritten. Subsequent raw batches append lines; compressed batches append one frame. Both paths `fsync`, and a caught write or sync failure rolls the file back to its prior byte length.
|
||||
- **Crash recovery — preserve valid tail work.** `load` validates every complete compressed frame and scans their decompressed JSONL. If the last frame is structurally incomplete, the reader keeps its complete decoded records, truncates from that frame's start, and re-encodes those records with the synthetic tool, step, and turn closers required by the shared [persistence contract](../../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md). Raw mode truncates from its first incomplete line. A checksum/decompression failure in a complete frame, or a defect at or before the last committed `turn/end`, is corruption and rejects.
|
||||
- **Crash recovery — preserve valid tail work.** `load` validates every complete compressed frame and scans their decompressed JSONL. If the last frame is structurally incomplete, the reader keeps its complete decoded records, truncates from that frame's start, and re-encodes those records with the synthetic tool, step, and turn closers required by the shared [persistence contract](../../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md). Raw mode truncates from its first incomplete line. An existing compressed artifact with no complete header frame, a checksum/decompression failure in a complete frame, or a defect at or before the last committed `turn/end` is corruption and rejects.
|
||||
- **Non-mutating inspection.** `inspect()` returns an immutable balanced logical view and may synthesize recovery closers in memory, without truncating an incomplete tail or changing the lightweight revision.
|
||||
- **Contiguous-seq.** `append` rejects a batch whose first `seq` does not continue the stored log, and rejects non-JSON-serializable `event.data` naming the offending event type.
|
||||
- **Lightweight revisions.** `listSnapshots(signal?)` identifies a log by its device, inode, size, and nanosecond timestamps, avoiding a full-log parse while changing after append, repair, replacement, or store changes. A full-prefix read requires the same identity before and after reading the bytes, and `readStoredRevision()` uses that identity to validate retained preparations without loading the log. Snapshot listing forwards the exact signal through artifact discovery and checks cancellation around every `stat`; because filesystem `stat` is not interruptible, cancellation waits for the active call to settle, then rejects without starting another.
|
||||
|
||||
@@ -42,7 +42,7 @@ JSONL 持久会话存储后端:`SessionPersistence` 的一个具体实现(`d
|
||||
- **绑定存储身份。** 查找要求可读项目目录中只有一个匹配会话目录,然后验证 header id 等于请求 id,且 header id/cwd 派生所选 transcript 路径。列表应用同一路径检查,并拒绝重复 id。身份失败发生在修复或 append 前。
|
||||
- **延迟实体化。**`create(meta)` 不写入;第一次 `append` 将编码 header 和第一批写入临时文件并执行 `fsync`。POSIX 通过硬链接无覆盖发布,并对父目录 `fsync`。Windows 通过 `MoveFileExW(..., MOVEFILE_WRITE_THROUGH)` 无覆盖发布,并通过同一 write-through pattern 创建缺失目录。已创建但从未 append 的会话不留下磁盘内容,不在 `list` 中。
|
||||
- **仅追加。** 已 flush 事件绝不重写。后续原始批次 append 行;压缩批次 append 一个 frame。两条路径都执行 `fsync`,并在捕获到写入或同步失败时回滚到之前字节长度。
|
||||
- **崩溃恢复:保留有效尾部工作。**`load` 验证每个完整压缩 frame,并扫描解压 JSONL。最后 frame 结构不完整时,读取器保留其完整解码记录,从 frame 开头截断,并使用共享[持久化约定](../../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md) 需要的合成工具、步骤和轮次 closer 重新编码这些记录。原始 mode 从第一个不完整行截断。完整 frame 中的 checksum/解压失败,或位于最后已提交的 `turn/end` 处或之前的缺陷属于损坏,会被拒绝。
|
||||
- **崩溃恢复:保留有效尾部工作。**`load` 验证每个完整压缩 frame,并扫描解压 JSONL。最后 frame 结构不完整时,读取器保留其完整解码记录,从 frame 开头截断,并使用共享[持久化约定](../../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md) 需要的合成工具、步骤和轮次 closer 重新编码这些记录。原始 mode 从第一个不完整行截断。已经存在却没有完整 header frame 的压缩工件、完整 frame 中的 checksum/解压失败,或位于最后已提交的 `turn/end` 处或之前的缺陷都属于损坏,会被拒绝。
|
||||
- **非变更检查。**`inspect()` 返回不可变、平衡的逻辑视图,并可在内存中合成恢复 closer,但不会截断不完整尾部或更改轻量修订。
|
||||
- **连续 seq。**`append` 拒绝第一个 `seq` 不继续已存储日志的批次,并拒绝非 JSON 可序列化 `event.data`,同时命名违规事件类型。
|
||||
- **轻量修订。**`listSnapshots(signal?)` 使用 device、inode、size 和纳秒时间戳标识日志,避免解析完整日志;该标识会在 append、修复、替换或存储变更后改变。完整前缀读取要求读取字节前后的身份一致,`readStoredRevision()` 使用同一身份校验保留的 preparation,而不加载日志。快照列表通过产物发现转发精确信号,并在每个 `stat` 前后检查取消;由于文件系统 `stat` 不可中断,取消会等待活动调用完成,然后在不启动另一次调用的情况下拒绝。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-persistence-jsonl",
|
||||
"description": "JSONL durable session persistence backend for the DeepSeek Harness",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -119,6 +119,8 @@ function isENOENT(error: unknown): boolean {
|
||||
* recovered from an incomplete final Zstandard frame.
|
||||
*/
|
||||
export class SessionPersistenceJsonl extends SessionPersistence implements PersistenceBackend<JsonlTornMarker> {
|
||||
override readonly supportsRawArtifacts = true
|
||||
|
||||
static inject = ['sessions']
|
||||
|
||||
static Config: z<Config> = z.object({
|
||||
@@ -257,7 +259,7 @@ export class SessionPersistenceJsonl extends SessionPersistence implements Persi
|
||||
let content: string
|
||||
if (this.compression === 'zstd') {
|
||||
const { frames } = scanZstdFrames(buffer)
|
||||
if (frames.length === 0) return undefined
|
||||
if (frames.length === 0) throw new Error('empty or header-less Zstandard session log')
|
||||
const decoder = createZstdFrameDecoder()
|
||||
const plaintexts: Buffer[] = []
|
||||
// The decoder yields views into a reused buffer; copy each frame's
|
||||
|
||||
@@ -377,16 +377,16 @@ describe('SessionPersistenceJsonl: default Zstandard encoding', () => {
|
||||
expect(scanned.events.map(event => event.type)).toEqual(oneTurnLog().map(event => event.type))
|
||||
})
|
||||
|
||||
it('readRaw is undefined for a zstd artifact that carries no frame', async () => {
|
||||
it('readRaw rejects a present zstd artifact that carries no frame', async () => {
|
||||
const root = await freshRoot()
|
||||
const ctx = await mount(root)
|
||||
const header = meta('raw-zero-frame', '/work')
|
||||
await ctx.sessionPersistence.create(header)
|
||||
await ctx.sessionPersistence.append(header.id, oneTurnLog())
|
||||
// Overwrite the physical artifact with a short buffer: frame scanning
|
||||
// answers zero frames before any magic check, so readRaw reports no artifact.
|
||||
// The path still exists, so zero frames is corruption rather than absence.
|
||||
await writeFile(logPath(root, '/work', header.id, 'zstd'), Buffer.alloc(0))
|
||||
expect(await ctx.sessionPersistence.readRaw(header.id)).toBeUndefined()
|
||||
await expect(ctx.sessionPersistence.readRaw(header.id))
|
||||
.rejects.toThrow('empty or header-less Zstandard session log')
|
||||
})
|
||||
|
||||
it('resolves the default when a programmatic wrapper bypasses Loader schema normalization', async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-persistence-sqlite",
|
||||
"description": "SQLite durable session persistence backend for the DeepSeek Harness",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -97,6 +97,8 @@ export interface Config {
|
||||
* listeners. Its torn-tail marker is the seq to delete from.
|
||||
*/
|
||||
export class SessionPersistenceSqlite extends SessionPersistence implements PersistenceBackend<number> {
|
||||
override readonly supportsRawArtifacts = false
|
||||
|
||||
static inject = ['sessions']
|
||||
|
||||
static Config: z<Config> = z.object({
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/session/session-persistence/README.md
|
||||
README.md: c6875dbcfecdfd6ba4eb46d75feca1fbc6fc956d
|
||||
README.zh.md: 2ef5e9a90f0323f8edf8fdc4f936c41ca7e08c70
|
||||
README.md: 6e1898f8a49e54f8fe90ff27cf8571c5959f27e9
|
||||
README.zh.md: 901c41b6894d86bdc4ffb345314a3dd506e4a770
|
||||
|
||||
@@ -11,6 +11,8 @@ The persisted unit IS the existing `SessionEvent` (event-sourced model — the l
|
||||
| Method | Contract |
|
||||
|---|---|
|
||||
| `locate(meta): SessionLocation \| undefined` | Resolve an absolute per-session artifact target without I/O or materialization. Backends without an independent local artifact return `undefined`. |
|
||||
| `supportsRawArtifacts: boolean` | State explicitly whether this backend exposes one verbatim artifact per session. Consumers check this capability before calling `readRaw`; `false` is not session absence. |
|
||||
| `readRaw(id, signal?): Promise<SessionRawArtifact \| undefined>` | Read a supported backend's own artifact text verbatim, decoded from its physical encoding but never reconstructed from events. `undefined` means only that the requested artifact is absent; an unsupported backend rejects. |
|
||||
| `create(meta): Promise<void>` | Register a new session's metadata. MAY defer the physical write until the first `append` (lazy materialization). |
|
||||
| `append(id, events): Promise<void>` | Durably persist a batch. Append-only; first event `seq` == stored next-seq after any repair; rejects non-JSON-serializable data naming the offending type. |
|
||||
| `prepare(id, signal?): Promise<SessionPreparation>` | Reserve the exact unpublished Session used by resume. A coordinator reuses an earlier inspection when available, commits pending recovery, and releases an unpublished reservation back to its bounded cache on disposal. |
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
| 方法 | 约定 |
|
||||
|---|---|
|
||||
| `locate(meta): SessionLocation \| undefined` | 在不执行 I/O 或实体化的情况下解析绝对的每会话产物目标。没有独立本地产物的后端返回 `undefined`。 |
|
||||
| `supportsRawArtifacts: boolean` | 明确说明该后端是否为每个会话暴露一份逐字工件。Consumer 在调用 `readRaw` 前检查此能力;`false` 并不表示会话缺失。 |
|
||||
| `readRaw(id, signal?): Promise<SessionRawArtifact \| undefined>` | 读取受支持后端自身的逐字工件文本;只解码物理编码,绝不从事件重建。`undefined` 仅表示所请求工件缺失;不支持的后端会拒绝。 |
|
||||
| `create(meta): Promise<void>` | 注册新会话元数据。可以将物理写入延迟到第一次 `append`(延迟实体化)。 |
|
||||
| `append(id, events): Promise<void>` | 持久保存一个批次。仅追加;任何修复后,第一个事件 `seq` == 已存储 next-seq;非 JSON 可序列化数据会被拒绝,并命名违规类型。 |
|
||||
| `prepare(id, signal?): Promise<SessionPreparation>` | 预留恢复所使用的那个未发布 Session。协调器会尽可能复用之前的检查结果、提交待处理恢复,并在 dispose 时将未发布 reservation 释放回有界缓存。 |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-persistence",
|
||||
"description": "Abstract durable session persistence seam (ctx.sessionPersistence) for the DeepSeek Harness",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -95,24 +95,32 @@ export abstract class SessionPersistence extends Service {
|
||||
*/
|
||||
abstract locate(meta: SessionHeader): SessionLocation | undefined
|
||||
|
||||
/**
|
||||
* Whether this backend exposes one verbatim raw artifact per session.
|
||||
* A backend that declares `true` must override {@link readRaw}.
|
||||
*/
|
||||
abstract readonly supportsRawArtifacts: boolean
|
||||
|
||||
/**
|
||||
* Read a session's backend-owned artifact text verbatim — the exact durable
|
||||
* bytes the backend wrote (decoded from its physical encoding, e.g. a
|
||||
* decompressed JSONL). The returned `content` is the raw text, not a
|
||||
* reconstruction from parsed events, so it preserves backend-specific
|
||||
* serialization (chunk packing, key order, line breaks). Backends without a
|
||||
* per-session artifact (SQLite) inherit the `undefined` default.
|
||||
* serialization (chunk packing, key order, line breaks). Callers first test
|
||||
* {@link supportsRawArtifacts}; `undefined` then means only that the requested
|
||||
* session has no materialized artifact.
|
||||
* @param _id - the persisted session to read (unused by the default: no
|
||||
* per-session artifact).
|
||||
* @param signal - optional cancellation for backend read work.
|
||||
* @returns the raw artifact plus its parsed header, or `undefined` when the
|
||||
* session is absent or the backend owns no per-session artifact.
|
||||
* session is absent.
|
||||
* @throws when this backend does not expose per-session raw artifacts.
|
||||
*/
|
||||
readRaw(_id: SessionId, signal?: AbortSignal): Promise<SessionRawArtifact | undefined> {
|
||||
if (signal?.aborted === true) {
|
||||
return Promise.reject(signal.reason instanceof Error ? signal.reason : new Error('aborted'))
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
return Promise.reject(new Error('this session persistence backend does not expose raw artifacts'))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -68,6 +68,8 @@ interface CoordinatorInternals {
|
||||
* durable behavior is covered by the JSONL and SQLite backends.
|
||||
*/
|
||||
class MemoryPersistence extends SessionPersistence implements PersistenceBackend<never> {
|
||||
override readonly supportsRawArtifacts = false
|
||||
|
||||
static inject = ['sessions']
|
||||
|
||||
override readonly name = 'session-persistence-memory'
|
||||
@@ -247,11 +249,14 @@ runPersistenceContract('memory', async () => {
|
||||
})
|
||||
|
||||
describe('the inherited readRaw default', () => {
|
||||
it('answers undefined and honors an aborted signal', async () => {
|
||||
it('rejects unsupported reads distinctly from absence and honors an aborted signal', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(MemoryPersistence)
|
||||
expect(await ctx.sessionPersistence.readRaw(SessionId('any-session'))).toBeUndefined()
|
||||
expect(ctx.sessionPersistence.supportsRawArtifacts).toBe(false)
|
||||
await expect(
|
||||
ctx.sessionPersistence.readRaw(SessionId('any-session')),
|
||||
).rejects.toThrow('does not expose raw artifacts')
|
||||
await expect(
|
||||
ctx.sessionPersistence.readRaw(SessionId('any-session'), AbortSignal.abort()),
|
||||
).rejects.toThrow()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-projection-cache",
|
||||
"description": "Persisted projection cache (ctx.sessionProjectionCache): durable per-session projection checkpoints over the domain data form, throttled write-behind, and the cold-read ladder (cache row + persistence tail replay)",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-projection",
|
||||
"description": "Session-projection seam: the merge-extensible projection type table, the provider contract, and the ctx.sessionProjections registry serving whole current values of log-derived per-session state",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-telemetry-otel",
|
||||
"description": "OpenTelemetry backend for the DeepSeek Harness telemetry seam: hands captured session records to the OTel JS SDK's log pipeline",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-telemetry",
|
||||
"description": "Telemetry seam for the DeepSeek Harness: session-event capture, projection, redaction, and handoff to a reporting backend",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-title-all-messages-llm",
|
||||
"description": "All-user-messages LLM provider plugin for DeepSeek Harness session titles",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-title-first-message-llm",
|
||||
"description": "First-message LLM provider plugin for DeepSeek Harness session titles",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-title-llm",
|
||||
"description": "Shared LLM generation policy for DeepSeek Harness session-title providers",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-title",
|
||||
"description": "Log-backed session title service and provider registry for the DeepSeek Harness",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/session/user-id/README.md
|
||||
README.md: 31a72f5e7b58b90b165b16374c2301389cbe2ca0
|
||||
README.zh.md: 013097b3038c43ff740660ef9159ca2b13f7b743
|
||||
README.md: 55bc54e4a5b666880f1908f4ccdf720e1122fc7a
|
||||
README.zh.md: 7816b62e581959ac3c5f3f277b86b147280fce17
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Shared anonymous identity for session telemetry and direct feedback acknowledgement. `getOrCreateAnonymousUserId()` returns a random UUID v4 scoped to one harness home, persisted as the bare line `$DSH_HOME/.userid` (`~/.dsh/.userid` when `DSH_HOME` is unset). The OpenTelemetry backend reports it as Resource `user.id`; `/feedback` includes the same value in its acknowledgement so an operator can correlate a submitted session and user with exported telemetry.
|
||||
Shared anonymous identity for session telemetry, direct feedback acknowledgement, and DeepSeek provider requests. `getOrCreateAnonymousUserId()` returns a random UUID v4 scoped to one harness home, persisted as the bare line `$DSH_HOME/.userid` (`~/.dsh/.userid` when `DSH_HOME` is unset). The OpenTelemetry backend reports it as Resource `user.id`; `/feedback` includes the same value in its acknowledgement; and `dsh-llm-deepseek` sends it as `x-deepseek-harness-user-id`, allowing the receiving systems to correlate records without independently generated identities.
|
||||
|
||||
The identity is never derived from the hostname, network address, git remote, or another identifying source. Deleting `.userid` resets the identity on the next process launch. Separate harness homes have separate identities, and the dsh-sdk launcher telemetry intentionally keeps its own unrelated store.
|
||||
The identity is never derived from the hostname, network address, git remote, or another identifying source. Deleting `.userid` resets the identity on the next process launch. Separate harness homes have separate identities.
|
||||
|
||||
## Storage contract
|
||||
|
||||
@@ -12,18 +12,19 @@ Reads and writes are synchronous because both boot-time telemetry construction a
|
||||
|
||||
## Composition
|
||||
|
||||
This package is a shared library, not a Cordis plugin. Consumers import `getOrCreateAnonymousUserId()` directly. Its invariant companion is intentionally empty because the package owns no event stream or public mutable relation that can be checked without creating the identity as a side effect.
|
||||
This package is a shared library, not a Cordis plugin. Consumers import `getOrCreateAnonymousUserId()` directly. Its invariant companion is intentionally empty because the package owns no event stream or public mutable relation that can be checked without creating the identity as a side effect. `DSH_TELEMETRY_DISABLED` stops telemetry export only; it does not suppress direct feedback acknowledgement or the DeepSeek provider header.
|
||||
|
||||
## Model Experience
|
||||
|
||||
None, as the identifier is used only in telemetry metadata and a direct human command response; it never enters a model request.
|
||||
None, as the identifier reaches DeepSeek only as model-hidden HTTP transport metadata and never enters the request body, prompt, or model-visible content.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
None; this package never contributes to a model request.
|
||||
None; the transport header changes neither tokens nor the model-visible prefix.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **No recovery after deletion** — loss mints a new anonymous identity by design; recovery would require stable derivation material that weakens anonymity.
|
||||
- **Best-effort concurrency** — a reader landing in the narrow interval between a concurrent process's exclusive create and completed write can use a different in-memory UUID for that run; later launches converge on the persisted value.
|
||||
- **No cross-home identity** — different `$DSH_HOME` values cannot be correlated, and this package does not unify the separate dsh-sdk launcher telemetry identity.
|
||||
- **No cross-home identity** — different `$DSH_HOME` values cannot be correlated.
|
||||
- **Configured DeepSeek gateways receive the id** — `dsh-llm-deepseek` sends the stable header to its resolved `baseURL`, including deployment overrides, independently of telemetry sharing mode.
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
会话遥测与直接反馈确认共用的匿名身份。`getOrCreateAnonymousUserId()` 返回一个限定于单个 harness home 的随机 UUID v4,并以裸行形式持久化到 `$DSH_HOME/.userid`(未设置 `DSH_HOME` 时为 `~/.dsh/.userid`)。OpenTelemetry 后端将其作为 Resource 的 `user.id` 上报;`/feedback` 在确认文本中包含同一个值,以便运维人员将所报告的会话和用户与导出的遥测相关联。
|
||||
会话遥测、直接反馈确认与 DeepSeek 提供方请求共用的匿名身份。`getOrCreateAnonymousUserId()` 返回一个限定于单个 harness home 的随机 UUID v4,并以裸行形式持久化到 `$DSH_HOME/.userid`(未设置 `DSH_HOME` 时为 `~/.dsh/.userid`)。OpenTelemetry 后端将其作为 Resource 的 `user.id` 上报;`/feedback` 在确认文本中包含同一个值;`dsh-llm-deepseek` 则通过 `x-deepseek-harness-user-id` 发送该值,使接收系统无需独立生成身份即可关联记录。
|
||||
|
||||
该身份绝不从 hostname、网络地址、git remote 或其他可用于识别身份的来源派生。删除 `.userid` 后,下次启动进程时会重置身份。不同 harness home 拥有不同身份,dsh-sdk launcher telemetry 则刻意使用与此无关的独立存储。
|
||||
该身份绝不从 hostname、网络地址、git remote 或其他可用于识别身份的来源派生。删除 `.userid` 后,下次启动进程时会重置身份。不同 harness home 拥有不同身份。
|
||||
|
||||
## 存储契约
|
||||
|
||||
@@ -12,18 +12,19 @@
|
||||
|
||||
## 组合
|
||||
|
||||
本包是共享库,并非 Cordis 插件。消费方直接导入 `getOrCreateAnonymousUserId()`。其不变式伴生插件刻意留空,因为本包既不拥有事件流,也不拥有任何可以在不触发创建身份这一副作用的情况下检查的公开可变关系。
|
||||
本包是共享库,并非 Cordis 插件。消费方直接导入 `getOrCreateAnonymousUserId()`。其不变式伴生插件刻意留空,因为本包既不拥有事件流,也不拥有任何可以在不触发创建身份这一副作用的情况下检查的公开可变关系。`DSH_TELEMETRY_DISABLED` 只会停止遥测导出,不会禁止直接反馈确认或 DeepSeek 提供方标头。
|
||||
|
||||
## 模型体验
|
||||
|
||||
无,因为该标识符只用于遥测元数据和面向用户的直接命令响应;它绝不会进入模型请求。
|
||||
无,因为该标识符只会作为模型不可见的 HTTP 传输元数据发送给 DeepSeek,绝不会进入请求正文、提示词或模型可见内容。
|
||||
|
||||
#### KV Cache 影响
|
||||
|
||||
无;本包绝不会向模型请求贡献任何内容。
|
||||
无;该传输标头既不会改变 token,也不会改变模型可见前缀。
|
||||
|
||||
## 已知限制与暂缓工作
|
||||
|
||||
- **删除后无法恢复**:身份丢失后会按设计生成新的匿名身份;若要恢复身份,就需要稳定的派生材料,这会削弱匿名性。
|
||||
- **Best-effort 并发**:如果读取方恰好落在并发进程完成独占创建但尚未写完的狭窄时间窗内,本次运行可能使用不同的内存 UUID;后续启动会收敛到已持久化的值。
|
||||
- **没有跨 home 身份**:不同 `$DSH_HOME` 值之间无法关联,本包也不会统一 dsh-sdk launcher telemetry 的独立身份。
|
||||
- **没有跨 home 身份**:不同 `$DSH_HOME` 值之间无法关联。
|
||||
- **已配置的 DeepSeek gateway 会收到该 id**:`dsh-llm-deepseek` 会把稳定标头发送至解析后的 `baseURL`(包括部署覆盖),且不受遥测共享模式影响。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-user-id",
|
||||
"description": "Shared anonymous user identity for DeepSeek Harness telemetry and feedback correlation",
|
||||
"version": "0.0.1-rc.1",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
* and never derived from the hostname, network address, git remote, or any
|
||||
* other identifying source. It is scoped to the harness home, not the
|
||||
* machine: every process sharing one `$DSH_HOME` reports the same id, and
|
||||
* deleting the file mints a fresh identity on the next launch. The dsh-sdk
|
||||
* launcher telemetry keeps its own separate store.
|
||||
* deleting the file mints a fresh identity on the next launch.
|
||||
*
|
||||
* Reads and writes are synchronous so boot-time and command consumers can
|
||||
* use one API. The result is memoized per resolved file path: one process
|
||||
|
||||
Reference in New Issue
Block a user