Merge pull request #1543 from deepseek-harness/stack/agent-profiles-8-authoring
feat(web): author agent presets from a settings page
This commit is contained in:
@@ -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: b7fa8fc2918711dd24eeba67132a267452d401f9
|
||||
README.zh.md: cf044b937f7ae6d0464603a1b59ad9489423ebe0
|
||||
README.md: 628833513a8092280970230c8657a50d00db4527
|
||||
README.zh.md: 4eb2d4f2bebf9ed17190ef3cb21a2bc3c8d9123b
|
||||
|
||||
@@ -14,7 +14,7 @@ The JSONL durable session-persistence backend — a concrete `SessionPersistence
|
||||
session.jsonl # only with compression: 'none'
|
||||
```
|
||||
|
||||
- The first logical line is the immutable `SessionHeader` tagged `{ type: 'session', version, id, cwd?, createdAt, parentSession?, seedLength?, origin?, delegationDepth }`. `delegationDepth` is required on disk and is `0` for a top-level session; a missing or invalid value rejects the log. Every subsequent logical line is one storage record; `assistant/chunk` events are never dropped, and `seq` stays contiguous across the decoded log (`events[i].seq === i`).
|
||||
- The first logical line is the immutable `SessionHeader` tagged `{ type: 'session', version, id, cwd?, createdAt, parentSession?, seedLength?, origin?, delegationDepth, agentPreset? }`. `delegationDepth` is required on disk and is `0` for a top-level session; a missing or invalid value rejects the log. `agentPreset` is durable because it decides the resumed session's tools and prompt — restoring a different composition would replay history the model can no longer act on. Every subsequent logical line is one storage record; `assistant/chunk` events are never dropped, and `seq` stays contiguous across the decoded log (`events[i].seq === i`).
|
||||
- A storage record is a `SessionEvent` JSON verbatim, or — for an eligible run when `packChunks` is enabled — a **packed chunk row** (`text-chunks` / `reasoning-chunks` / `tool-call-chunks`; bare slash-less tags like the header's `session`, so row tags cannot be confused with event types): one line holding a run of ≥3 consecutive same-block `assistant/chunk` delta events, `seq0`/`time0` plus per-member `dt` gaps reconstructing every member's `seq`/`time` exactly. The lossless codec lives in `@deepseek-ai/dsh-session` (`packChunkRuns`/`decodeStorageRecord`) and whitelists exact shapes — anything unrecognized stores verbatim. Reading is layout-blind: `load` always decodes rows, so packed, unpacked, and mixed files load identically.
|
||||
- The project directory keeps the normalized cwd readable for navigation and is bounded for filesystem component limits. Separator replacement and truncation are intentionally lossy, so cwd strings that normalize alike share a project directory; session ids still select distinct session directories. On a case-insensitive filesystem, identity validation accepts an alternate path spelling only when filesystem canonicalization resolves both spellings to the same transcript. The configured root remains deployment-controlled: it may be project-local, shared, temporary, or centralized. The [project-session directory decision](../../../.agents/notes/implemented/architecture/2026-07-24-project-session-directories.md) records this tradeoff.
|
||||
- Session ids are unvalidated branded strings, so they are injectively escaped to a single safe path segment before use (no traversal, no collision). The resulting directory is reserved for additional session-owned artifacts; discovery reads only the fixed transcript filename.
|
||||
|
||||
@@ -14,7 +14,7 @@ JSONL 持久会话存储后端:`SessionPersistence` 的一个具体实现(`d
|
||||
session.jsonl # only with compression: 'none'
|
||||
```
|
||||
|
||||
- 第一个逻辑行是不可变的 `SessionHeader`,标记为 `{ type: 'session', version, id, cwd?, createdAt, parentSession?, seedLength?, origin?, delegationDepth }`。`delegationDepth` 在磁盘上必需,顶层会话为 `0`;缺失或无效值会拒绝日志。后续每个逻辑行是一条存储记录;`assistant/chunk` 事件绝不丢弃,且 `seq` 在解码日志中保持连续(`events[i].seq === i`)。
|
||||
- 第一个逻辑行是不可变的 `SessionHeader`,标记为 `{ type: 'session', version, id, cwd?, createdAt, parentSession?, seedLength?, origin?, delegationDepth, agentPreset? }`。`delegationDepth` 在磁盘上必需,顶层会话为 `0`;缺失或无效值会拒绝日志。`agentPreset` 必须持久化,因为它决定了被恢复会话的工具与提示词——恢复成另一套组装,就会重放模型已无法据以行动的历史。后续每个逻辑行是一条存储记录;`assistant/chunk` 事件绝不丢弃,且 `seq` 在解码日志中保持连续(`events[i].seq === i`)。
|
||||
- 存储记录是原样 `SessionEvent` JSON,或在 `packChunks` 已启用且连续段符合条件时写入的**打包分片行**(`text-chunks` / `reasoning-chunks` / `tool-call-chunks`;像 header 的 `session` 一样不带斜杠,因此行 tag 不会与事件类型混淆):一行保存至少 3 个连续同 block `assistant/chunk` delta 事件,`seq0`/`time0` 和每成员 `dt` 间隔精确重建每个成员的 `seq`/`time`。无损 codec 位于 `@deepseek-ai/dsh-session`(`packChunkRuns`/`decodeStorageRecord`),并使用精确形态 allowlist:任何未识别内容原样存储。读取与布局无关:`load` 始终解码行,因此打包、非打包和混合文件加载结果一致。
|
||||
- 项目目录保留规范化 cwd 可读,并限制在文件系统组件上限内。分隔符替换和截断刻意有损,因此规范化相同的 cwd 字符串共享项目目录;会话 id 仍选择不同会话目录。在不区分大小写的文件系统上,只有文件系统规范化将两种写法解析到同一 transcript(文本记录)时,身份验证才接受备选路径写法。配置根仍由部署控制:可以是项目本地、共享、临时或集中式。[项目会话目录决策](../../../.agents/notes/implemented/architecture/2026-07-24-project-session-directories.md) 记录这项取舍。
|
||||
- 会话 id 是未验证的带品牌类型的字符串,因此在使用前单射转义为一个安全路径段(无遍历、无冲突)。结果目录保留给其他会话自有产物;发现只读取固定 transcript 文件名。
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface HeaderLine {
|
||||
seedLength?: number
|
||||
origin?: 'subagent'
|
||||
delegationDepth: number
|
||||
agentPreset?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +58,7 @@ export function toHeaderLine(header: SessionHeader): HeaderLine {
|
||||
...header.seedLength !== undefined ? { seedLength: header.seedLength } : {},
|
||||
...header.origin !== undefined ? { origin: header.origin } : {},
|
||||
delegationDepth: header.delegationDepth ?? 0,
|
||||
...header.agentPreset !== undefined ? { agentPreset: header.agentPreset } : {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +80,7 @@ export function fromHeaderLine(line: HeaderLine): SessionHeader {
|
||||
...line.seedLength !== undefined ? { seedLength: line.seedLength } : {},
|
||||
...line.origin !== undefined ? { origin: line.origin } : {},
|
||||
delegationDepth: line.delegationDepth,
|
||||
...line.agentPreset !== undefined ? { agentPreset: line.agentPreset } : {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +101,8 @@ function isHeaderLine(value: unknown): value is HeaderLine {
|
||||
&& !Object.is((value as { delegationDepth: number }).delegationDepth, -0)
|
||||
&& ((value as { origin?: unknown }).origin === undefined
|
||||
|| (value as { origin?: unknown }).origin === 'subagent')
|
||||
&& ((value as { agentPreset?: unknown }).agentPreset === undefined
|
||||
|| typeof (value as { agentPreset?: unknown }).agentPreset === 'string')
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -817,6 +817,27 @@ describe('SessionPersistenceJsonl: scanLog unit', () => {
|
||||
expect(() => scanLog(Buffer.from(log))).toThrow(/session header/)
|
||||
})
|
||||
|
||||
it('round-trips the agent preset a session was composed from', () => {
|
||||
const line = toHeaderLine({
|
||||
version: 0,
|
||||
id: SessionId('composed'),
|
||||
createdAt: 1,
|
||||
delegationDepth: 0,
|
||||
agentPreset: 'minimal',
|
||||
})
|
||||
const log = `${JSON.stringify(line)}\n`
|
||||
|
||||
// The preset decides the resumed session's tools and prompt; dropping it
|
||||
// on disk would restore a composition the logged history contradicts.
|
||||
expect(scanLog(Buffer.from(log)).meta.agentPreset).toBe('minimal')
|
||||
})
|
||||
|
||||
it('rejects a session header whose agentPreset is not a string', () => {
|
||||
const log = '{"type":"session","version":0,"id":"bad-preset","createdAt":1,"delegationDepth":0,"agentPreset":7}\n'
|
||||
|
||||
expect(() => scanLog(Buffer.from(log))).toThrow(/session header/)
|
||||
})
|
||||
|
||||
it('a seq gap after the last turn/end bounds the preserved tail (torn fragment tolerated)', () => {
|
||||
const log = [
|
||||
JSON.stringify({ type: 'session', version: 0, id: 'g', createdAt: 1, delegationDepth: 0 }),
|
||||
|
||||
Reference in New Issue
Block a user