docs: rebalance prose cleanup and add trimming skill
This commit is contained in:
@@ -34,8 +34,10 @@ declare module 'cordis' {
|
||||
|
||||
interface Events {
|
||||
/**
|
||||
* Emitted after session publication. A synchronous throw vetoes and rolls
|
||||
* Creation announcement during session publication. A synchronous throw vetoes and rolls
|
||||
* back with a paired disposal; detach requested during dispatch is deferred.
|
||||
* A returned-promise rejection is logged but cannot retroactively veto this
|
||||
* synchronous boundary.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners
|
||||
* receive only sessions entered through that agent's context.
|
||||
* @param session - the session just entered and announced.
|
||||
@@ -44,14 +46,17 @@ declare module 'cordis' {
|
||||
'session/created'(this: Scoped<Session>, session: Session): void
|
||||
/**
|
||||
* Emitted once when an announced session leaves the store, including
|
||||
* publication rollback. Listener failures are contained.
|
||||
* publication rollback, but never for an entry whose creation announcement
|
||||
* did not begin. Listener failures are logged and contained.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope.
|
||||
* @param session - the session that is no longer live in the store.
|
||||
* @mode emit
|
||||
*/
|
||||
'session/disposed'(this: Scoped<Session>, session: Session): void
|
||||
/**
|
||||
* Post-commit append feed. Observer failures are logged and contained.
|
||||
* Post-commit, fire-and-forget append feed. The listener snapshot resolves
|
||||
* before the log push, but callbacks run after it; observer failures are
|
||||
* logged and contained without making the committed append fail.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners
|
||||
* receive only events from sessions entered through that agent's context.
|
||||
* @param session - the session whose log grew.
|
||||
@@ -60,7 +65,8 @@ declare module 'cordis' {
|
||||
*/
|
||||
'session/event'(this: Scoped<Session>, session: Session, event: SessionEvent): void
|
||||
/**
|
||||
* Awaited parallel durability checkpoint; dispatch through
|
||||
* Awaited parallel durability checkpoint: every listener runs and the
|
||||
* caller awaits all of them, with no waterfall veto. Dispatch through
|
||||
* {@link SessionStore.flush}. Scope-filtered dispatch
|
||||
* (`@deepseek-ai/dsh-scope`) reuses the session's owner scope.
|
||||
* @param session - the session whose buffered events must reach durable storage.
|
||||
@@ -70,7 +76,11 @@ declare module 'cordis' {
|
||||
}
|
||||
}
|
||||
|
||||
/** Render injected context as a tagged synthetic user-role message. */
|
||||
/**
|
||||
* Render injected context as tagged synthetic user-role content, keeping the
|
||||
* canonical session vocabulary provider-neutral. Adapter-specific exceptions
|
||||
* belong in the adapter.
|
||||
*/
|
||||
function renderTagged(tag: string, content: ContentBlock[], source: MessageSource): ContentBlock[] {
|
||||
const open = `<${tag} source=${JSON.stringify(source.kind)}>`
|
||||
const close = `</${tag}>`
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
|
||||
|
||||
/**
|
||||
* Validate and detach lossless JSON in one read per property. Accepts ordinary
|
||||
* arrays, plain or null-prototype objects, and JSON scalars; rejects sparse,
|
||||
* cyclic, exotic, negative-zero, and non-finite values. Getter throws propagate.
|
||||
* Validate and detach lossless JSON in one read per property, so a stateful
|
||||
* getter cannot change between validation and copying. Accepts ordinary arrays,
|
||||
* plain or null-prototype objects, and JSON scalars; rejects sparse, cyclic,
|
||||
* exotic, negative-zero, and non-finite values. Getter throws propagate.
|
||||
*
|
||||
* @param value - the candidate value to validate and detach.
|
||||
* @returns the detached snapshot, or `undefined` when the value is not
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/**
|
||||
* Crash-recovery repair for an interrupted session log.
|
||||
* Crash-recovery repair for an interrupted session log. It preserves a fully
|
||||
* written final turn and supplies the missing tool, step, and turn boundaries
|
||||
* needed to resume with a provider-valid transcript.
|
||||
* @module @deepseek-ai/dsh-session/repair
|
||||
*/
|
||||
|
||||
@@ -7,8 +9,10 @@ import type { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { SessionEvent } from './types.ts'
|
||||
|
||||
/**
|
||||
* Return deterministic synthetic events that close an open tail turn or step.
|
||||
* Sequences continue the log and timestamps reuse the last real event.
|
||||
* Return deterministic synthetic events that close an open tail turn. Unmatched
|
||||
* calls receive error results first, followed by an open `step/end` and an
|
||||
* interrupted `turn/end`; sequences continue the log and timestamps reuse the
|
||||
* last real event. A balanced or empty log returns no events.
|
||||
*
|
||||
* @param events - the loaded durable log to scan (a valid committed prefix, possibly with a crash tail).
|
||||
* @returns the synthetic closer events to append after `events`, in order; empty when the log is already balanced.
|
||||
@@ -16,8 +20,8 @@ import type { SessionEvent } from './types.ts'
|
||||
export function interruptedTurnClosers(events: readonly SessionEvent[]): SessionEvent[] {
|
||||
let openTurn: number | null = null
|
||||
let openStep: number | null = null
|
||||
// Track tool calls vs. their results WITHIN the currently-open turn only: a call is "pending"
|
||||
// until its matching tool/result arrives.
|
||||
// Reset at each turn boundary so earlier calls cannot leak into tail repair.
|
||||
// Assistant blocks register calls; later tool/call events add provenance seqs.
|
||||
const pendingCalls = new Map<CallId, { step: number; callSeq?: number }>()
|
||||
for (const event of events) {
|
||||
switch (event.type) {
|
||||
@@ -46,8 +50,7 @@ export function interruptedTurnClosers(events: readonly SessionEvent[]): Session
|
||||
}
|
||||
break
|
||||
case 'tool/call':
|
||||
// Capture the tool/call event seq for surface provenance on the synthesized
|
||||
// tool/result.
|
||||
// Add the tool/call seq used as provenance on a synthetic result.
|
||||
{
|
||||
const entry = pendingCalls.get(event.data.callId)
|
||||
if (entry) {
|
||||
@@ -76,9 +79,8 @@ export function interruptedTurnClosers(events: readonly SessionEvent[]): Session
|
||||
const time = last.time
|
||||
const closers: SessionEvent[] = []
|
||||
|
||||
// Synthesize an error tool/result for each tool-call left unanswered by the crash, so
|
||||
// deriveMessages() yields a valid provider transcript on resume (a dangling assistant
|
||||
// tool-call is rejected by every provider).
|
||||
// Close calls before their step: providers reject dangling assistant calls,
|
||||
// and Map insertion order preserves their transcript order.
|
||||
for (const [callId, { step, callSeq }] of pendingCalls) {
|
||||
closers.push({
|
||||
type: 'tool/result',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Request-header reconstruction utilities: the pure fold/diff/apply trio over the
|
||||
* `request/header` / `request/header-delta` session events.
|
||||
* Request-header reconstruction utilities over `request/header` snapshots and
|
||||
* `request/header-delta` events. Writers round-trip each proposed delta and use
|
||||
* a full snapshot when the encoding cannot represent the change.
|
||||
* @module dsh-session/request-header
|
||||
*/
|
||||
|
||||
@@ -128,8 +129,11 @@ function sameMessages(a: readonly Message[] | undefined, b: readonly Message[] |
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the `request/header-delta` payload between two canonical headers, or undefined when
|
||||
* they are equal.
|
||||
* Compute the `request/header-delta` payload between two canonical headers, or
|
||||
* `undefined` when they are equal. The encoding cannot represent every change,
|
||||
* including pure tool reordering, so callers must apply and compare the result
|
||||
* before logging it and fall back to a full snapshot on mismatch. The session
|
||||
* prefix is replaced whole; an empty array removes it.
|
||||
*
|
||||
* @param prev - the folded header the log currently implies.
|
||||
* @param next - the header the next request will actually use.
|
||||
|
||||
@@ -23,8 +23,9 @@ const SURFACE_EVENT_TYPES = new Set<string>([
|
||||
])
|
||||
|
||||
/**
|
||||
* Check only whether a type may enter the message surface. Use
|
||||
* {@link isSurfaceEvent} when the mandatory `surfaceOp` must also be present.
|
||||
* Check only whether a type may enter the message surface; it does not require `surfaceOp`. This
|
||||
* detects eligible seed/load events missing their mandatory marker. Use {@link isSurfaceEvent} to
|
||||
* narrow a fully formed event whose marker is present.
|
||||
* @param type - the event type string to test.
|
||||
* @returns true when the type is one of the five message-producing types.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Tool-pairing balance over a session's surface: is a given cut point in the surface a safe
|
||||
* edge for a collapsed region (e.g. compaction)?
|
||||
* Tool-pairing balance over a session surface. Compaction changes surface
|
||||
* positions, so safe cuts are derived from tool-call/result content on the
|
||||
* surface rather than step markers in the append-only log.
|
||||
* @module @deepseek-ai/dsh-session/tool-pairing
|
||||
*/
|
||||
|
||||
@@ -27,10 +28,12 @@ function nodeDelta(event: SessionEvent): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a surface cut does not split a tool call from its result.
|
||||
* Check that a surface cut does not split a tool call from its result. A region
|
||||
* is safe to collapse only when the cuts before its first node and after its
|
||||
* last node both return `true`.
|
||||
* @param nodes - the surface linked list in head→tail order.
|
||||
* @param events - the session log each node's `seq` indexes into.
|
||||
* @param beforeSeq - node immediately after the cut; absent from the surface means after-tail.
|
||||
* @param beforeSeq - node immediately after the cut; `null` or a seq absent from the surface means after-tail.
|
||||
* @returns whether every call before the cut has its result before the cut.
|
||||
* @throws if a result appears without a preceding open call.
|
||||
*/
|
||||
|
||||
@@ -39,8 +39,8 @@ export interface SessionHeader {
|
||||
/** The session this one was forked from (seed lineage), if any. */
|
||||
readonly parentSession?: SessionId
|
||||
/**
|
||||
* How many leading events were INHERITED via a seed rather than produced by this session —
|
||||
* the seed boundary.
|
||||
* How many leading events were inherited through a seed. Persisting this
|
||||
* boundary lets resume and replay distinguish parent history from child work.
|
||||
*/
|
||||
readonly seedLength?: number
|
||||
}
|
||||
@@ -99,6 +99,7 @@ export interface TurnEndReasonMap {
|
||||
*/
|
||||
error: { kind: 'error'; step: number; message: string; code?: string }
|
||||
disposed: { kind: 'disposed' }
|
||||
/** At least one step reached its output-token ceiling, even if a plugin continued the turn. */
|
||||
'max-tokens': { kind: 'max-tokens' }
|
||||
/**
|
||||
* Policy blocked every prompt before the first step. The zero-step turn still
|
||||
@@ -106,8 +107,8 @@ export interface TurnEndReasonMap {
|
||||
*/
|
||||
rejected: { kind: 'rejected'; reason: string }
|
||||
/**
|
||||
* The turn never ended on its own: the process crashed mid-turn and a persistence backend
|
||||
* later closed the orphaned (open) turn on reload so the log stays balanced.
|
||||
* A persistence backend closed a crash-orphaned turn on reload. The loop never
|
||||
* emits this marker, and the events recorded before the crash remain intact.
|
||||
*/
|
||||
interrupted: { kind: 'interrupted' }
|
||||
}
|
||||
@@ -198,10 +199,10 @@ export interface ToolsDelta {
|
||||
}
|
||||
|
||||
/**
|
||||
* The session event vocabulary — the append-only source of truth for an agent's whole
|
||||
* interaction history. The LLM message history is *derived* from this log; nothing else is
|
||||
* authoritative. Replay = re-derive from the same events; trace/telemetry = subscribe to the
|
||||
* log.
|
||||
* The merge-extensible, append-only source of truth for an agent interaction.
|
||||
* Message history is derived from this log. Every event is lossless JSON and
|
||||
* sequence numbers stay contiguous, including raw chunks, so persistence can
|
||||
* store the canonical log verbatim.
|
||||
*/
|
||||
export interface SessionEventMap {
|
||||
/**
|
||||
@@ -224,8 +225,8 @@ export interface SessionEventMap {
|
||||
/** A user-visible prompt (queued message drained at turn start). */
|
||||
'user/message': { content: ContentBlock[]; source: MessageSource }
|
||||
/**
|
||||
* A queued prompt an `agent/prompt-submit` listener VETOED — the durable record of a blocked
|
||||
* prompt and why.
|
||||
* Durable record of a prompt veto and its reason. It is log-only: the blocked
|
||||
* prompt never enters the model-visible surface, including in a mixed batch.
|
||||
*/
|
||||
'prompt/blocked': { content: ContentBlock[]; source: MessageSource; reason: string }
|
||||
/**
|
||||
@@ -262,24 +263,19 @@ export interface SessionEventMap {
|
||||
/** Steering content injected between steps of a running turn. */
|
||||
'steering/message': { turn: number; content: ContentBlock[]; source: MessageSource }
|
||||
/**
|
||||
* The agent's whole todo list, carried as a full snapshot and replaced wholesale on each
|
||||
* write — the current list is the most recent `todo/write` (last-write-wins on replay, no
|
||||
* fold). Appended by an owning agent via `session.append('todo/write', { todos })`.
|
||||
* Whole-list snapshot; the latest write wins on replay. It is log-only UI
|
||||
* state and never enters derived model history.
|
||||
*/
|
||||
'todo/write': { todos: TodoItem[] }
|
||||
/**
|
||||
* Full snapshot of the {@link EpochHeader} the NEXT request is built under, with the {@link
|
||||
* RequestHeaderReason} it was recorded whole.
|
||||
* Full {@link EpochHeader} for the next request, appended inside its step
|
||||
* before dispatch. It is log-only and anchors subsequent deltas.
|
||||
*/
|
||||
'request/header': { header: EpochHeader; reason: RequestHeaderReason }
|
||||
/**
|
||||
* Amendment to the folded {@link EpochHeader}: at least one of a {@link SystemDelta}, a
|
||||
* {@link ToolsDelta}, a whole replacement {@link LlmCallConfig} (four scalars — not worth
|
||||
* diffing), or a whole replacement session prefix (`messagePrefix` — small advisory content,
|
||||
* replaced whole; an EMPTY array encodes the transition to "none", mirroring the canonical
|
||||
* form's absent field — the loop never produces one in practice: the prefix is composed once
|
||||
* per instance and anchored by that instance's snapshot, so this arm exists for codec
|
||||
* totality).
|
||||
* Log-only amendment to the folded {@link EpochHeader}. System and tools use
|
||||
* their delta codecs; config and prefix replace whole, with an empty prefix
|
||||
* encoding removal. Writers verify round-trip equality or log a fallback snapshot.
|
||||
*/
|
||||
'request/header-delta': { system?: SystemDelta; tools?: ToolsDelta; config?: LlmCallConfig; messagePrefix?: Message[] }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user