@robota-sdk/agent-session 3.0.7 → 3.1.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,371 @@
1
1
  # @robota-sdk/agent-session
2
2
 
3
+ ## 3.1.1
4
+ ### Patch Changes
5
+
6
+ - Updated dependencies [7e55f21]
7
+ - @robota-sdk/agent-file-authority@3.1.1
8
+ - @robota-sdk/agent-core@3.1.1
9
+ - @robota-sdk/agent-interface-execution@3.1.1
10
+ - @robota-sdk/agent-interface-session@3.1.1
11
+
12
+ ## 3.1.0
13
+ ### Minor Changes
14
+
15
+ - bc8de21: Every CLI process that builds a conversation store (session records, prompt history, tool-result
16
+ spill, edit checkpoints) now records that it is running: an owner-only directory under `presence/` in
17
+ its state root holding its pid, start time and how it was started, removed when it ends, including on
18
+ a hangup, interrupt or termination. A subagent worker does the same when it binds the grant its
19
+ parent sends. The next start removes one a killed process left once its pid has ended. A command that
20
+ builds no store creates none, and a presence that cannot be created is reported once on stderr while
21
+ the session goes on. A tool that lists the state root may see `presence/` while a process runs.
22
+
23
+ `secure migrate [--dry-run]` and `secure compact` are reserved for secure mode, which cannot be turned
24
+ on yet: until it can, both refuse with a fixed message and write nothing, and neither is listed in the
25
+ usage. Neither is a slash command, a skill or anything a transport can reach.
26
+
27
+ `@robota-sdk/agent-session` adds `compactPromptHistoryFile(path, { sealer })`, which rewrites the
28
+ prompt history with every entry sealed, one per line, replaces the file by rename and catches up what
29
+ terminals appended meanwhile, and `inspectPromptHistoryFile(path, { sealer })`.
30
+ `@robota-sdk/agent-core/node` adds `inspectProcessScopedDirectories(parent, prefix)`,
31
+ `ownerOnThisMachine(owner, current)` and `stageOwnerOnlyFile(path, content)`, and
32
+ `createProcessScopedDirectory` takes `readStartTime` and `durable`.
33
+
34
+ What to do: nothing.
35
+ - bc8de21: `secure migrate`, still reserved for secure mode (which cannot be turned on yet), now converts every
36
+ conversation store after the prompt history: the user's session records, the subagent transcripts,
37
+ and the state of each trusted project on a host that can write project state (session records, replay
38
+ logs with their payload files, edit checkpoints, and pending memory candidates, which are deleted). It
39
+ then deletes what earlier runs left behind: temp files of crashed writes, sessions kept aside during a
40
+ hand-off, `/editor` buffers in the shared temporary directory and the scratch directories of
41
+ processes that are gone. What it cannot seal safely (a link or a file with another hard link,
42
+ untrusted and non-Linux projects, the SDK's spill directories, anything that does not open with the
43
+ keyring) is listed and left as it is. `--dry-run` shows exactly what a run would do and changes
44
+ nothing. A run that leaves plaintext which a rerun or moving a file aside would remove ends
45
+ incomplete (exit 5) and says what to do; plaintext it can never seal (a project record or log past
46
+ the project read limit once sealed, checkpoint turns kept plain) is listed, and the run still ends
47
+ done. A run stopped part-way finishes when started again. `/rewind` names `<cli> secure migrate`
48
+ for a checkpoint whose sealed copy is still waiting.
49
+
50
+ `@robota-sdk/agent-session` adds `migrateNodeSessionStore`, `readSessionStoreWorkingDirectories`,
51
+ `migrateSessionLogDirectory`, `readPromptHistoryProjects` and the format helpers they share.
52
+ `@robota-sdk/agent-framework` adds `migrateWorkspaceProjectSessionRecords`,
53
+ `migrateWorkspaceProjectSessionLogs`, `migrateWorkspaceEditCheckpoints` and
54
+ `migrateSubagentTranscripts`; `EditCheckpointUnreadableError` gains `unfinishedMigration`; and
55
+ `createNodeWorkspaceTrustStore(path, { tightenOnRead: false })` reads a trust store without narrowing
56
+ its mode. Nothing calls the converters outside a migration.
57
+
58
+ What to do: nothing.
59
+ - bc8de21: `TSessionLoadOutcome` gains a fifth status, `locked`: the record is there and sealed, and this
60
+ process cannot open it right now (`reason: 'no-keys'` when the store keeps its records `'plain'`,
61
+ `'changed'` when a held session's file changed since). `list()` leaves locked sessions out, since
62
+ there is no record to describe; the optional `scan()` returns the listing together with how many it
63
+ left out — `locked`, which an unlock would list, and an optional `noKeys` for those sealed with keys
64
+ this process does not have — and `scanSessionStore`/`listResumableSessions` in
65
+ `@robota-sdk/agent-framework` read it. `lockedSessionReason`, `lockedSessionMessage(id, reason)` and
66
+ `lockedSessionsNotice(locked, noKeys)` word a sealed session by why it does not open, so only locked
67
+ keys are ever met with "unlock"; resume, rename, hand-off, usage and the CLI's notices follow them. A
68
+ served session listing carries how many sealed sessions the host cannot open, its keys locked or
69
+ none that open them, as `lockedSessionCount`; the reason does not cross the wire, so the terminal
70
+ picker and the web session list say how many are sealed and not listed, never to unlock.
71
+
72
+ `@robota-sdk/agent-interface-analytics` adds an optional `IPersonalUsageCoverage.lockedSessions`, the
73
+ number of sealed sessions a usage report could not open (locked keys, or none that open them).
74
+ `@robota-sdk/agent-session-analytics` adds an optional `IPersonalUsageSnapshot.lockedSessionCount`,
75
+ which fills it, and the formatted report then reads `locked N` in its coverage line.
76
+
77
+ What to do: a `switch` over `outcome.status` handles `locked`; when you write, treat it like
78
+ `corrupt` (do not save over it). A surface that lists sessions can show `scan().locked` and
79
+ `scan().noKeys` so sealed sessions do not look deleted.
80
+ - bc8de21: A session file whose record carries an id other than the one in its file name now loads as
81
+ `corrupt` (issue `the record belongs to another session id`) instead of as that other session, and
82
+ a project replay log whose entries name another session no longer rebuilds one. A record copied or
83
+ renamed to another id's file was being resumed, listed and saved under the wrong id. A load that
84
+ cannot read the file for a reason other than "no such file" also reports `corrupt`, never `missing`.
85
+
86
+ What to do: if you renamed session files by hand, rename them back to `<record id>.json`.
87
+ - bc8de21: Every store that keeps conversation content now states how it seals (`TArtifactSealing`, from
88
+ `@robota-sdk/agent-core`), and has no plain default: a store left plain beside sealed ones would
89
+ write plaintext next to them. `{ sealing: { sealer } }`
90
+ seals with an `IArtifactSealer` (bound to the record, line or file it belongs to), and
91
+ `{ sealing: 'plain' }` writes plain JSON, lines and files as before. A store that seals reads plain
92
+ content too and seals what it rewrites; a store that does not seal meets a sealed file as `locked`.
93
+
94
+ What breaks, and what to do: the `sealing` option is required (omitting it is a compile error and a
95
+ `TypeError` at run time) wherever a store is built or read, so pass `{ sealing: 'plain' }` to keep
96
+ what you have.
97
+
98
+ - `@robota-sdk/agent-session`: `NodeSessionStore(baseDir, { sealing, ownedRoot? })`, where `ownedRoot`
99
+ moves from a positional argument into the options; `NodePromptHistoryFile`, `NodeSessionLogSink`,
100
+ `NodeSessionLogSource`, `NodeExternalPayloadSource` (with an optional `ownerId`) and
101
+ `NodeToolResultSpillStore` take it in their options.
102
+ - `@robota-sdk/agent-framework`: `createUserSessionStore(dir, { sealing })`,
103
+ `createNodeHostSessionStore(dir, { sealing, ownedRoot? })` (same move of `ownedRoot`),
104
+ `createProjectSessionStore(sessions, logs, { sealing })`, `WorkspaceProjectSessionStore`,
105
+ `createUserPromptHistoryFile`, `WorkspaceSessionLogSink`, `WorkspaceSessionLogSource` and
106
+ `createNodeToolResultSpillStore`. `IWorkspaceProjectStateStorage.replaceBytes` is required: the
107
+ project session store replaces a record whole through it, so a storage of your own implements it
108
+ (the framework's storage does).
109
+
110
+ A sealing append store writes each append as one sealed line and each log sidecar sealed under a
111
+ random name. The log sources gain `readStoredLines()`, which returns every line as readable, locked,
112
+ not opening, or sealed by a newer build, while `readText()` stays strict and throws
113
+ `ArtifactLockedError`, `ArtifactCorruptError` or `SessionLogDecodeError`. `StoredLines`,
114
+ `StoredPayloads` and `splitStoredLines` are exported for hosts that build their own append stores.
115
+ `IPromptHistoryBlock` gains an optional `unreadable: { locked, noKeys }`, and the terminal's Ctrl+R
116
+ search says how many prompts are sealed and whether an unlock would let it search them. A project
117
+ store reads its replay logs with its own sealing and reports a log it cannot read as `locked`,
118
+ `corrupt` or `unsupported`; a plain log whose payload file is missing or does not match is now
119
+ `corrupt` too, where `load` and `list` used to throw, and `/validate-session` says when the log is
120
+ sealed instead of failing with an error.
121
+
122
+ A sealing spill store writes each result sealed and bound to its reference as `<token>.sealed` and
123
+ answers `read-failed` for one that does not open. In either mode a result whose file is already gone
124
+ when it expires or at shutdown now counts as removed, where that cleanup used to fail with
125
+ `cleanup-failed` every time, and with it every later write.
126
+ - 82663df: One session has one writer across processes. Every claim of a session id now holds the id's writer
127
+ marker in a `claims/` directory beside the records, so a second opener of a session another process
128
+ has open is refused with `has_writer`, naming that process — `-r`, `-c`, the resume picker, ACP
129
+ `session/load`, the served and desktop pool, a served switch, rename and delete. This is a break: a
130
+ session that two processes could open at once before is now refused in the second one.
131
+
132
+ `@robota-sdk/agent-core` adds exclusive process markers: the neutral `IProcessMarkers` port with its
133
+ result types, and `createProcessMarkers` on `./node`. A marker is published by hard-linking a flushed
134
+ temp file, moved or removed only under its break lock, and re-verified by its handle before each
135
+ write. The owner record and its liveness rule are now one exported classifier,
136
+ `classifyProcessOwner`, shared with process-scoped directories.
137
+
138
+ `@robota-sdk/agent-interface-session` and `@robota-sdk/agent-session`: `StoredSessionRecords` takes
139
+ the marker port, and a store without one does not exist. `claim(id, { lease })` takes over a lent
140
+ marker and `claim(id, { copy: true })` reads a record to copy it without one. `lend(id)` is new: it
141
+ lends a record to another process for as long as that process runs and returns `{ nonce, end() }`.
142
+ Every write of an id holds its marker, and a save whose marker another process broke is refused as
143
+ `released`, never written. `hasWriter` now sees other store instances and processes. Removed, since
144
+ `lend` replaces them: `ISessionAuthorship.exportEnded`, the store's export bookkeeping
145
+ (`hasUnclaimedWriter`), the `exported` claim refusal and the `exported` adopt result.
146
+ `TSessionClaimRefusal` is `has_writer | released`, `TSessionAdoptResult` has `has_writer` in place of
147
+ `exported`, and `has_writer` is a session change and delete refusal code. A store may name its release
148
+ command for a refusal (`releaseCommand`). A refusal of a record lent to a subagent names the
149
+ subagent's process once it took the marker over, and says a subagent this process started holds it
150
+ before (`holder.lent`); `SessionClaimedError.closing` is true only for a session of this process that
151
+ has not let its claim go, which is the one refusal a caller may wait out (`still_closing`).
152
+
153
+ `@robota-sdk/agent-subagent-runner`: the parent lends a fork job's record before it spawns the child,
154
+ in either mode; the lease nonce rides on the start payload (`resumeLease`); the child claims its
155
+ record under the lease, saves under that claim and releases it on every exit; the parent ends the
156
+ lease once the child has exited and disconnected, then countersigns under its own marker. A running
157
+ child-process fork therefore has one writer, and attaching to it is refused until the job ends.
158
+
159
+ `@robota-sdk/agent-cli` adds `session release <id> [--force]`, the user's own command for a marker a
160
+ crashed or planted process left: on its own it removes only a marker whose process has ended, and
161
+ `--force` removes one whose process may still run only at a terminal and after a y/N. `secure migrate`
162
+ leaves a session open in another process as it is and names it.
163
+
164
+ A terminal start whose resumed session another process holds now ends with the store's refusal, as print
165
+ mode does, instead of Ink's error screen; `@robota-sdk/agent-framework` exports `claimRefusalMessage`
166
+ for it, and `claimChangeRefusal` / `claimDeleteRefusal`, which turn a store's claim refusal into the
167
+ client's: `still_closing` for a switch or rename (and `running` for a delete) when a session of this
168
+ process is still closing, `has_writer` in the store's words otherwise. Served rename and delete now
169
+ let the store's write refuse in those words. `@robota-sdk/agent-ui-terminal`: picking a session another process or a subagent holds in the
170
+ resume picker leaves the person on their session, with the store's words as a notice, instead of
171
+ stopping it and blocking the UI on a retry of the same refusal.
172
+ - bc8de21: Subagent transcripts and fork jobs work with sealed conversation stores.
173
+
174
+ What breaks, and what to do:
175
+
176
+ - `createChildProcessSubagentRunnerFactory` and `ChildProcessSubagentRunner` require `logSealing`:
177
+ how the children keep their logs, `{ sealer }` or `'plain'`. A host that seals passes its sealer
178
+ and implements `bindWriteGrant(grant)` in its worker composition; any other host passes `'plain'`.
179
+ - `createSubagentLogger(parentSessionId, agentId, baseLogsDir, { sealing }, fs?)` requires `sealing`
180
+ as its fourth argument, ahead of the optional file system; pass `'plain'` for a plain log.
181
+ - `TSubagentWorkerParentMessage` gains `{ type: 'sealing_key'; grant }`, which
182
+ `isSubagentWorkerParentMessage` accepts: a worker of your own handles it by binding the new grant,
183
+ or stops when it cannot seal to it.
184
+
185
+ What is new:
186
+
187
+ - A fork job's log is now read from the file the child writes. The child names its log after the
188
+ session it runs (the forked record, else the task) and the parent read `<taskId>.jsonl`, so
189
+ `/agent read` on a fork job showed "No log lines", and with worktree isolation (the default for
190
+ `/fork`) the read was not offered at all. `resolveSubagentLogSessionId`,
191
+ `resolveSubagentTranscriptPath` and `readSubagentTranscriptPage(path, { sealing })`
192
+ (`@robota-sdk/agent-framework`) are the one rule both sides use.
193
+ - In a sealed log a page's offset counts stored records, so it names the same record whether or not
194
+ the keys are locked. `IBackgroundTaskLogPage` gains an optional `unreadable` count of records the
195
+ page could not show (`locked`, `noKeys`, `doesNotOpen`); a run of locked records shows as one fixed
196
+ line, and `/agent read` and `/background read` say whether an unlock would read them.
197
+ - `IInteractiveSessionStore.save` takes optional `{ handoff }`, and the store port gains optional
198
+ `exportHeld(id, { keep })` and `importHeld(id, token)`, implemented by `NodeSessionStore` and
199
+ `WorkspaceProjectSessionStore`: a record written for a fork job is kept apart from the sessions a
200
+ sealing store runs and handed to the job once. A fork job restores from a read made the way a
201
+ writer reads, so an in-process fork job restores the fork `/fork` just wrote while the keys are
202
+ locked. A sealed record a write-only store cannot open loads as
203
+ `{ status: 'locked', reason: 'no-keys' }`.
204
+ - `@robota-sdk/agent-subagent-runner`: with a sealer, the start payload carries the write grant (no
205
+ secret) the sealer issues when the payload is sent and, for a fork job, the parent's held record as
206
+ `resumeHeld` (at most 32 MiB; the job is refused before anything is made for it when none can be
207
+ handed over, naming "unlock" only when locked keys are why). A child already sent its payload gets
208
+ a newer grant as a `sealing_key` message, and one that cannot follow it stops. `bindWriteGrant`
209
+ must return a sealer whose `writeGrant()` equals the grant, or the job fails. `ISubagentRunner`
210
+ gains optional `preflight(job)`, which refuses what `start` would and changes nothing; the worktree
211
+ runner calls it before it prepares a worktree. A job whose start payload cannot be built or sent
212
+ after its child was spawned now cancels the child before it rejects.
213
+ - Job handles may say `transcriptSealed`, and the manager projects it into the task state beside
214
+ `transcriptPath` (`IBackgroundTaskState`, `ISubagentJobState`), where the session record codec
215
+ accepts it. A job-group result gets `transcriptSealed` too. SubagentStart/Stop hooks get
216
+ `transcript_sealed: true` beside the transcript path, and `summarizeBackgroundJobGroup` points at
217
+ `/agent read <taskId>` instead of the path, also for a group restored in a later session.
218
+ - 04c3233: The universal parameter-schema subset gains size bounds: `minLength`, `maxLength`, `minItems`, `maxItems`, `minProperties` and `maxProperties`. They are enforced by `validateAgainstJsonSchema` and by `FunctionTool` (at the root too), persisted and reloaded in session records, and kept by MCP schema narrowing, so a server such as Exa no longer prints an unenforceable-schema diagnostic for them. A bound that is not a non-negative integer is dropped and reported. Per provider: OpenAI strict mode keeps `minItems`/`maxItems` and leaves the others off the wire with a Schema note, Gemini carries all six, standard JSON Schema surfaces carry them unchanged, and Anthropic's `output_config.format` omits them along with `minimum`/`maximum`, which it answers with a 400. `ISchemaClosureOptions` gains `omitKeywords`.
219
+ - fab48ad: The CLI keeps spilled tool results and the `/editor` buffer in owner-only, per-process directories of its state root instead of the shared operating-system temporary directory. They are removed when the run ends, including on a hangup, interrupt or termination, and the next start removes those a killed run left behind.
220
+
221
+ `NodeToolResultSpillStore` and `createNodeToolResultSpillStore` take a `parentDirectory`: an existing owner-only directory the host owns, used as it is. A store there records its owner, is removed when the process ends without `shutdown()`, and can be cleaned up after a kill with the new `sweepStaleToolResultSpillDirectories(parent)`. The process-scoped directory helpers behind this are exported from `@robota-sdk/agent-core/node`.
222
+ - d8cb04f: Make the session package platform-neutral.
223
+
224
+ - `@robota-sdk/agent-core` exports `isAbsolutePath`, `resolvePath` and `joinPath` — lexical path helpers that read the platform from the path text (a drive letter or `\\` is a Windows path, anything else POSIX) and never touch the process directory — and `utf8ByteLength`.
225
+ - `@robota-sdk/agent-session`: `NodeSessionStore`, `NodeSessionLogSink`, `NodeSessionLogSource`, `NodeExternalPayloadSource`, `NodeToolResultSpillStore` (with `ToolResultSpillError`, `sweepStaleToolResultSpillDirectories` and its option and code types) and `NodePromptHistoryFile` (with `DEFAULT_PROMPT_HISTORY_BLOCK_BYTES` and `parsePromptHistoryLine`) moved to the new `@robota-sdk/agent-session/node`, which also exports `createNodeWorkspacePathResolver` and `registerProcessExitFlush`. The main entry no longer reaches a Node built-in, a Node global or `@robota-sdk/agent-file-authority`; the sealed-store and migration exports (`StoredSessionRecords`, `StoredLines`, `StoredPayloads`, `sessionSealingOf`, `assertSessionSealing`, the `seal*` converters, `migrateNodeSessionStore`, `migrateSessionLogDirectory`, `compactPromptHistoryFile` and their companions) are on `./node` too. `ISessionLogSink`, `ISessionLogSource`, the external-payload ports, `createSessionLogExternalPayloadReference`, `loadSessionForUpdate` and the sealed-session and claim refusals stay on the main entry.
226
+ - `@robota-sdk/agent-session`: `hookTypeExecutors` is a required option of `Session`, `PermissionEnforcer` and `CompactionOrchestrator`; a missing list throws at construction instead of defaulting to the command and HTTP executors (pass `[new CommandExecutor(), new HttpExecutor()]` from `@robota-sdk/agent-core/node`, or `[]` to run no hook handler).
227
+ - `@robota-sdk/agent-session`: three host pieces became options with no Node default. `homeDirectory` is no longer read from the operating system — absent, the home directory is unknown, so `~` and any home-relative target not provably inside it (`~/..`) are refused, and a path inside it is not expanded. `resolveInWorkspace` replaces the built-in symlink-following resolver — absent, a shell command with a path operand is not judged read-only. `canonicalizePath` replaces `realpathSync` in snapshot, journal and approval checks — absent, directories are compared as written. `FileSessionLogger` takes `registerExitFlush` instead of listening on `process`; it registers once per distinct function.
228
+ - `@robota-sdk/agent-session`: the execution root accepts a Windows drive or UNC root on any host and rejects a drive-less rooted path (`\work`) on a Windows host, where it was accepted before; a payload reference's `relativePath` is always written with `/` (readers accept either separator).
229
+ - `@robota-sdk/agent-roundtable-robota`: `SessionHostOptions` requires `hookTypeExecutors`, as it extends the session options.
230
+ - `@robota-sdk/agent-framework` passes the Node home directory, workspace resolver, `realpathSync` and exit registrar, and the command and HTTP executors, wherever it builds a session, so the CLI behaves as before.
231
+ - 44e9640: Serialize cooperating session record mutations and marker transitions with a crash-released per-session operation lock. Marker construction now requires a host lock, and hosts must stop older writers before sharing their session directory with the new version. Forced release does not stop a running writer or its children.
232
+ - b56b5a9: Ship one build per package. The platform-neutral entries moved from `dist/node` to `dist`, and the second
233
+ `dist/browser` build of `@robota-sdk/agent-core`, `@robota-sdk/agent-transport`,
234
+ `@robota-sdk/agent-ui-web` and `@robota-sdk/agent-transport-webrtc-web` is gone: a neutral entry runs
235
+ unchanged in every runtime, so a subpath's `exports` map `import` and `require` to the same files and no
236
+ longer a `node` or `browser` condition (a Node-only subpath keeps `"browser": null`). Import specifiers
237
+ and the code behind them are unchanged; only a deep import of `dist/node/*` or `dist/browser/*` breaks.
238
+ `@robota-sdk/agent-transport/client` also ships a CommonJS build now.
239
+
240
+ ### Patch Changes
241
+
242
+ - bc8de21: Rolling back through a session's first checkpoint now survives a resume. The next turn after such a
243
+ rollback starts a fresh root line, as documented, but the saved session kept no pointer for that
244
+ cleared head, so a resumed session continued the line that was rolled back. The session record now
245
+ saves `activeBranch` as `{ branchId, checkpointId: null }` for it, and `IActiveBranchPointer.checkpointId`
246
+ is `string | null`.
247
+
248
+ What to do: code that reads `activeBranch.checkpointId` handles `null`, which means the head was
249
+ cleared on purpose, not that the pointer is missing.
250
+ - bc8de21: Edit checkpoints can be sealed, and every `EditCheckpointStore` now states how it keeps them.
251
+
252
+ - `EditCheckpointStore` requires `sealing`: `{ sealer }` seals every manifest and pre-image, bound to
253
+ its session, turn and pre-image number so one moved elsewhere does not open there; `'plain'` keeps
254
+ them as they are. `stateStorage` only says where they live (the project's checkpoint directory when
255
+ omitted), and `sealedOnly: true` is for state made for one process, which never held a plain
256
+ checkpoint. A sealed store numbers new checkpoints from directory names alone and refuses with
257
+ `EditCheckpointNumberingExhaustedError` at the highest number, after which an interactive turn runs
258
+ without a checkpoint and its history says why.
259
+ - `IEditCheckpointStateStorage` requires `replaceBytes(path, content, purpose)`, which replaces a file
260
+ whole; `createDirectoryEditCheckpointState` and the project's checkpoint state have it.
261
+ - A checkpoint that cannot be read (damaged, written by a newer build, or sealed and locked) is
262
+ reported instead of failing the whole read: `EditCheckpointsLockedError` (an `ArtifactLockedError`
263
+ with its `reason`) for locked keys or no keys, `EditCheckpointUnreadableError` for a restore or
264
+ rollback range holding one, refused before any project file is written. The store takes an optional
265
+ `keysLocked` gate, and `countUnreadable()` and `listing()` say how many `list()` left out. `/rewind
266
+ list` prints that count and `/rewind inspect` marks a recorded size and a plan that would be refused.
267
+ - The branch head moves to a new checkpoint only once its manifest is written; a failed write used to
268
+ leave the head on the unwritten checkpoint, so the next turn became its own parent and walking its
269
+ ancestors never ended. `CheckpointTree.ancestors` (`@robota-sdk/agent-session`) also stops where
270
+ parent edges loop back.
271
+
272
+ What to do: pass `sealing: 'plain'` to every `new EditCheckpointStore(...)` that does not seal, and add
273
+ `replaceBytes` to a storage of your own.
274
+ - eabce48: A record a store that seals does not verify as the owner's is never resumed, chosen or vouched for
275
+ without the owner's word.
276
+
277
+ - Resume: `-c` continues the newest verified session and stops when a newer unverified one exists
278
+ (asking at a terminal, exiting elsewhere). `-r` takes a verified or plain record's exact id, then
279
+ such a record's name, then any other record's id, and refuses a text that is both a verified
280
+ record's name and the id of a record that is unverified or does not read
281
+ (`ambiguousSessionNameMessage`); a store that never seals has no verified record, so there an
282
+ exact id answers before a name and nothing is refused as ambiguous. Breaking:
283
+ `resolveSessionIdByIdOrName` returns a `TSessionIdResolution` (`found`, `ambiguous` with `namedId`
284
+ and `otherId`, `not-found`) instead of an id. An unverified id, or one picked in the picker, is
285
+ shown in fixed text (`unverifiedSessionNotice`, `UNVERIFIED_SESSION_QUESTION`) and asked about —
286
+ before startup only when stdin, stdout and stderr are all a terminal; a yes hands the session the
287
+ digest of exactly the bytes shown. Surfaces with no one to ask refuse
288
+ it, a served switch with the new change-refusal code `unverified`. Listings put unverified records
289
+ after verified ones, the picker marks them first and cuts their names short, and
290
+ `session list --json` adds `authorship`.
291
+ - Forks: a child handed a sealed record runs it only when the parent held it as the owner's, and
292
+ reports each save of it as the new `record_saved` member of
293
+ `TSubagentWorkerChildMessage`, and the parent vouches for the last one once the child is gone, when
294
+ the job started from the owner's own record and no save was refused.
295
+ - Migration: `secure migrate` offers once, at the end of a finished interactive run, to mark the
296
+ sessions it sealed from plaintext (`sealedRecords` on the migration results) as yours. Each store
297
+ is marked on its own; one it could not reach is named, and records the keys locked before marking
298
+ are said to be not marked, to be marked when resumed, apart from changed ones.
299
+
300
+ What to do: handle `unverified` and `record_saved` where you switch on `TSessionChangeRefusalCode`
301
+ or `TSubagentWorkerChildMessage`, and read `resolveSessionIdByIdOrName`'s `status`.
302
+ - eabce48: Session stores claim the sessions a process runs, and a store that seals says whose each record is.
303
+
304
+ - Breaking: `IInteractiveSessionStore.claim(id, { fresh })` and `hasWriter(id)` are required.
305
+ `claim` reads the record as the hold read a running session restores from and claims the id; a
306
+ second claim, or a claim of a record handed to a subagent process that may still save it, throws
307
+ `SessionClaimedError` (`isSessionClaimedError`, from `@robota-sdk/agent-session`; reasons
308
+ `claimed`, `released`, `exported`), and a save of a claimed id must carry its live claim
309
+ (`ISessionSaveOptions.claim`), so a one-off save of a running session's id (a rename or a handoff
310
+ receive from elsewhere) is refused. `hasWriter` says whether a claim or such an export stands for
311
+ the id. `NodeSessionStore` and the project store claim whether or not they seal.
312
+ - A store that seals tags what an unlocked process writes with an authorship tag only the unlocked
313
+ keys make, and every `valid` outcome carries `authorship: 'verified' | 'unverified'`, the `digest`
314
+ of the stored bytes, and `diverged: true` for a running session's id whose file something
315
+ unverifiable changed. Its sealing-only operations are one member, `authorship?: ISessionAuthorship`
316
+ (`adopt`, `heldDigest`, `exportEnded`, `onUnlock`), absent from a store that never seals; `adopt`
317
+ raises exactly the bytes with that digest and never a record handed to a process that may still
318
+ save it (`'adopted' | 'pending' | 'changed' | 'exported'`). The token `exportHeld` hands a child
319
+ says whether the store held the record as the owner's, and the child reads it as verified only then.
320
+ - New: `ISessionSaveOptions.provenance`, `ISessionExportOptions.handoffOnly`,
321
+ `IResumableSessionSummary.authorship`. `StoredSessionRecords` reaches a store's files through one
322
+ adapter, `IStoredSessionFiles`, and keeps its bookkeeping internal. `decorateSessionStore`
323
+ (`@robota-sdk/agent-framework`) wraps a store without dropping a member.
324
+
325
+ What to do: a session store of your own implements `claim` and `hasWriter`. A store that keeps its
326
+ records plain writes no authorship and has no `authorship` member.
327
+ - bc8de21: `NodeToolResultSpillStore` and `createNodeToolResultSpillStore` require `parentDirectory`, an existing
328
+ owner-only directory the host owns, and no longer fall back to a private directory under the shared
329
+ operating-system temporary directory. Spilled tool results are conversation content, so they never go
330
+ anywhere the host did not choose.
331
+
332
+ What to do: pass your own owner-only directory (the CLI passes one in its state root). The store's
333
+ directory in it is removed by `shutdown()`, when the process ends without reaching it, and, after a
334
+ kill, by `sweepStaleToolResultSpillDirectories(parent)`.
335
+ - 0a677e9: Make model-native web search a provider construction option. Sessions no longer switch it on (`IAIProvider.configureNativeWebTools` is removed); an Anthropic profile enables it with `options.nativeWebTools.webSearch`, and a provider definition declares the hosted web tools it supports in `nativeWebTools`. The CLI turns native web search on by default for every definition that declares it, unless the profile sets `nativeWebTools` itself.
336
+ - 1c19435: Give every process marker publication a fresh generation so a session writer cannot mistake a later marker from the same process for its own when the filesystem reuses an inode. Existing marker records remain readable. All writers sharing a session directory must use the cooperating operation-lock protocol.
337
+ - Updated dependencies [bc8de21]
338
+ - Updated dependencies [51ad6a4]
339
+ - Updated dependencies [bc8de21]
340
+ - Updated dependencies [bc8de21]
341
+ - Updated dependencies [eabce48]
342
+ - Updated dependencies [bc8de21]
343
+ - Updated dependencies [eabce48]
344
+ - Updated dependencies [eabce48]
345
+ - Updated dependencies [bc8de21]
346
+ - Updated dependencies [82663df]
347
+ - Updated dependencies [bc8de21]
348
+ - Updated dependencies [04c3233]
349
+ - Updated dependencies [9bc13f9]
350
+ - Updated dependencies [830d3d2]
351
+ - Updated dependencies [0a677e9]
352
+ - Updated dependencies [5b46d43]
353
+ - Updated dependencies [fab48ad]
354
+ - Updated dependencies [ea0e424]
355
+ - Updated dependencies [0fec62f]
356
+ - Updated dependencies [b7b6cd0]
357
+ - Updated dependencies [8069d0e]
358
+ - Updated dependencies [f5e3057]
359
+ - Updated dependencies [d8cb04f]
360
+ - Updated dependencies [44e9640]
361
+ - Updated dependencies [b56b5a9]
362
+ - Updated dependencies [1c19435]
363
+ - Updated dependencies [4e4c92a]
364
+ - @robota-sdk/agent-core@3.1.0
365
+ - @robota-sdk/agent-interface-session@3.1.0
366
+ - @robota-sdk/agent-interface-execution@3.1.0
367
+ - @robota-sdk/agent-file-authority@3.1.0
368
+
3
369
  ## 3.0.7
4
370
  ### Patch Changes
5
371
 
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @robota-sdk/agent-session
2
2
 
3
+ > **Version:** Platform support and entries described here target SDK 3.1.0. Install that
4
+ > release after publication; published 3.0.7 packages may have different exports and behavior.
5
+
3
6
  Session lifecycle for the Robota SDK. A `Session` wraps a `ConversationAgent` agent from
4
7
  `@robota-sdk/agent-core` and adds permission-gated tool execution, lifecycle hooks, context-window
5
8
  tracking, conversation compaction, session logs and optional persistence.
@@ -8,18 +11,28 @@ Most applications use `InteractiveSession` or `createQuery()` from `@robota-sdk/
8
11
  which build a `Session` with tools, provider and system prompt already wired. Use this package
9
12
  directly when you assemble a session yourself.
10
13
 
14
+ The main entry is platform-neutral: it runs in a browser, a Web Worker, Node, Deno and Bun and never
15
+ reaches the file system or the process. What needs a Node host — the file-backed store, log sink and
16
+ source, the spill store, the prompt-history file and the process hooks — is on
17
+ `@robota-sdk/agent-session/node`, and the session takes what it needs from its host by injection.
18
+
11
19
  ## Installation
12
20
 
13
21
  ```bash
14
22
  npm install @robota-sdk/agent-session @robota-sdk/agent-core
15
23
  ```
16
24
 
17
- Requires Node.js 22.12 or later.
25
+ Requires Node.js 22.12 or later for the `@robota-sdk/agent-session/node` adapters.
18
26
 
19
27
  ## Quick Start
20
28
 
21
29
  ```typescript
30
+ import { realpathSync } from 'node:fs';
31
+ import { homedir } from 'node:os';
32
+
22
33
  import { Session } from '@robota-sdk/agent-session';
34
+ import { createNodeWorkspacePathResolver } from '@robota-sdk/agent-session/node';
35
+ import { CommandExecutor, HttpExecutor } from '@robota-sdk/agent-core/node';
23
36
  import type { ITerminalOutput } from '@robota-sdk/agent-session';
24
37
  import type { IAIProvider, IToolWithEventService } from '@robota-sdk/agent-core';
25
38
 
@@ -34,6 +47,13 @@ const session = new Session({
34
47
  terminal,
35
48
  // Required. The session's root: hook inputs, the permission workspace and the stored record use it.
36
49
  cwd: process.cwd(),
50
+ // Required. The hook handlers this host can run; `[]` runs none. The session has no host default.
51
+ hookTypeExecutors: [new CommandExecutor(), new HttpExecutor()],
52
+ // Optional on any host; a Node host passes all three so the permission gate knows the home
53
+ // directory, follows symlinks out of the workspace and compares directories as the filesystem does.
54
+ homeDirectory: homedir(),
55
+ resolveInWorkspace: createNodeWorkspacePathResolver(process.cwd()),
56
+ canonicalizePath: (path) => realpathSync(path),
37
57
  permissions: { allow: ['Read(*)'], deny: [] },
38
58
  autoCompactThreshold: 0.75,
39
59
  });
@@ -165,10 +185,10 @@ durable Roundtable execution. Unresolved forks and workspace copies are refused
165
185
  | **Hooks** | Fires `PreToolUse`, `PostToolUse`, `PermissionDecision`, `SessionStart`, `SessionEnd`, `UserPromptSubmit`, `Stop`, `StopFailure`, `PreCompact`, `PostCompact`, `PreModelCall` and `PostModelCall`. See the [hook event catalog](../agent-core/docs/HOOK-CATALOG.md). |
166
186
  | **Context tracking** | Token usage from the shared core estimator, with a configurable auto-compact threshold (default 83.5% of the context window) |
167
187
  | **Compaction** | A model-written summary replaces the history to free context space; an invalid summary throws `CompactionError` and leaves the history untouched |
168
- | **Persistence** | Inject an `IInteractiveSessionStore`; each completed `run()` and `shutdown()` save the record. `NodeSessionStore` writes JSON files atomically (temp file + rename) |
188
+ | **Persistence** | Inject an `IInteractiveSessionStore`; each completed `run()` and `shutdown()` save the record. `NodeSessionStore` (on `/node`) writes JSON files atomically (temp file + rename) |
169
189
  | **Abort** | `session.abort()` stops the running turn; `run()` then rejects with an `AbortError` |
170
190
  | **One turn at a time** | A concurrent `run()` is refused with `SessionBusyError`; `isRunning()` is true until the running turn has fully unwound |
171
- | **Session logging** | `FileSessionLogger` writes versioned JSONL through an injected sink; `NodeSessionLogSink` is the file-system sink |
191
+ | **Session logging** | `FileSessionLogger` writes versioned JSONL through an injected sink; `NodeSessionLogSink` (on `/node`) is the file-system sink |
172
192
  | **Replay events** | Provider and tool execution events from core are written to the log so a session can be replayed and validated |
173
193
 
174
194
  ## Key Methods
@@ -200,35 +220,50 @@ durable Roundtable execution. Unresolved forks and workspace copies are refused
200
220
 
201
221
  ## Public API
202
222
 
203
- | Export | Kind | Description |
204
- | ------------------------------------------------------------------------------------------------------------ | ------------------- | ---------------------------------------------------------------------------------------- |
205
- | `Session` | Class | Wraps `ConversationAgent` with permissions, hooks, compaction, logging and persistence |
206
- | `ISessionOptions`, `ISessionRunOptions`, `ISessionShutdownOptions` | Types | Constructor, run and shutdown options |
207
- | `SessionBusyError`, `TurnClaim` | Classes | The refusal of a concurrent turn, and the identity of the running turn |
208
- | `PermissionEnforcer` | Class | Tool permission checks, hook execution and output truncation |
209
- | `consentScopeFor` | Function | The permission pattern an "allow for this session/project" answer grants |
210
- | `AutoModeGate`, `IPermissionClassifier` | Class, type | The `auto` permission mode and the classifier port it calls |
211
- | `ContextWindowTracker`, `AUTO_COMPACT_THRESHOLD` | Class, const | Token usage tracking and the default auto-compact threshold |
212
- | `CompactionOrchestrator`, `CompactionError`, `DEFAULT_COMPACTION_PROMPT` | Class, error, const | Conversation compaction |
213
- | `TPermissionHandler`, `TPermissionResult` | Types | Approval callback and its answer (`true`, `false`, `'allow-session'`, `'allow-project'`) |
214
- | `ITerminalOutput`, `ISpinner` | Types | Terminal I/O used for approval prompts |
215
- | `NodeSessionStore` | Class | File-system JSON store for session records |
216
- | `IInteractiveSessionRecord`, `IInteractiveSessionStore` | Types | The persisted session record and store port (from `@robota-sdk/agent-interface-session`) |
217
- | `decodeInteractiveSessionRecord`, `decodeVersionedInteractiveSessionRecord` | Functions | Validate a stored record before use |
218
- | `serializeSessionArtifact`, `deserializeSessionArtifact` | Functions | Export a session record to a portable artifact and import it again |
219
- | `scrubSensitiveKeys`, `isSensitiveKey` | Functions | Redact secret-looking keys (used by the logger; optional for artifacts) |
220
- | `CheckpointTree` | Class | In-memory branching tree of checkpoint IDs (fork and switch between branches) |
221
- | `FileSessionLogger`, `SilentSessionLogger`, `ISessionLogger` | Class, type | JSONL session logger and the logger port |
222
- | `NodeSessionLogSink`, `NodeSessionLogSource`, `ISessionLogSink`, `ISessionLogSource` | Classes, types | File-system log writer and reader, and their ports |
223
- | `loadSessionLogEntries`, `decodeSessionLogEntries`, `SessionLogDecodeError` | Functions, error | Read and validate a session log |
224
- | `replaySessionLogEntries`, `validateSessionReplayLogEntries` | Functions | Rebuild history from a log and report gaps |
225
- | `SESSION_LOG_EVENT`, `SESSION_LOG_SCHEMA_VERSION` | Consts | The log event vocabulary and schema version |
226
- | `resolveSessionLogExternalPayloads`, `NodeExternalPayloadSource`, `createSessionLogExternalPayloadReference` | Functions, class | Large log fields stored as separate payload files |
227
- | `NodeToolResultSpillStore` | Class | Stores oversized tool results in files for the life of the session |
228
- | `NodePromptHistoryFile` | Class | Append-only prompt history file that can be read newest-first |
223
+ | Export | Kind | Description |
224
+ | ---------------------------------------------------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------- |
225
+ | `Session` | Class | Wraps `ConversationAgent` with permissions, hooks, compaction, logging and persistence |
226
+ | `ISessionOptions`, `ISessionRunOptions`, `ISessionShutdownOptions` | Types | Constructor, run and shutdown options |
227
+ | `SessionBusyError`, `TurnClaim` | Classes | The refusal of a concurrent turn, and the identity of the running turn |
228
+ | `PermissionEnforcer` | Class | Tool permission checks, hook execution and output truncation |
229
+ | `consentScopeFor` | Function | The permission pattern an "allow for this session/project" answer grants |
230
+ | `AutoModeGate`, `IPermissionClassifier` | Class, type | The `auto` permission mode and the classifier port it calls |
231
+ | `ContextWindowTracker`, `AUTO_COMPACT_THRESHOLD` | Class, const | Token usage tracking and the default auto-compact threshold |
232
+ | `CompactionOrchestrator`, `CompactionError`, `DEFAULT_COMPACTION_PROMPT` | Class, error, const | Conversation compaction |
233
+ | `TPermissionHandler`, `TPermissionResult` | Types | Approval callback and its answer (`true`, `false`, `'allow-session'`, `'allow-project'`) |
234
+ | `ITerminalOutput`, `ISpinner` | Types | Terminal I/O used for approval prompts |
235
+ | `IInteractiveSessionRecord`, `IInteractiveSessionStore` | Types | The persisted session record and store port (from `@robota-sdk/agent-interface-session`) |
236
+ | `decodeInteractiveSessionRecord`, `decodeVersionedInteractiveSessionRecord` | Functions | Validate a stored record before use |
237
+ | `serializeSessionArtifact`, `deserializeSessionArtifact` | Functions | Export a session record to a portable artifact and import it again |
238
+ | `scrubSensitiveKeys`, `isSensitiveKey` | Functions | Redact secret-looking keys (used by the logger; optional for artifacts) |
239
+ | `CheckpointTree` | Class | In-memory branching tree of checkpoint IDs (fork and switch between branches) |
240
+ | `FileSessionLogger`, `SilentSessionLogger`, `ISessionLogger` | Class, type | JSONL session logger and the logger port |
241
+ | `ISessionLogSink`, `ISessionLogSource`, `IExternalPayloadSink`, `IExternalPayloadSource` | Types | The ports a log is written and read through |
242
+ | `loadSessionLogEntries`, `decodeSessionLogEntries`, `SessionLogDecodeError` | Functions, error | Read and validate a session log |
243
+ | `replaySessionLogEntries`, `validateSessionReplayLogEntries` | Functions | Rebuild history from a log and report gaps |
244
+ | `SESSION_LOG_EVENT`, `SESSION_LOG_SCHEMA_VERSION` | Consts | The log event vocabulary and schema version |
245
+ | `resolveSessionLogExternalPayloads`, `createSessionLogExternalPayloadReference` | Functions | Large log fields stored as separate payload files |
229
246
 
230
247
  `IContextWindowState` (the `getContextState()` result) is exported by `@robota-sdk/agent-core`.
231
248
 
249
+ `@robota-sdk/agent-session/node` holds what only a Node host provides:
250
+
251
+ | Export | Kind | Description |
252
+ | -------------------------------------------------- | --------------- | -------------------------------------------------------------------------------- |
253
+ | `NodeSessionStore` | Class | File-system JSON store for session records |
254
+ | `NodeSessionLogSink`, `NodeSessionLogSource` | Classes | File-system log writer and reader |
255
+ | `NodeExternalPayloadSource` | Class | Reads a log's payload files from a base directory |
256
+ | `NodeToolResultSpillStore`, `ToolResultSpillError` | Class, error | Stores oversized tool results in files for the life of the session |
257
+ | `sweepStaleToolResultSpillDirectories` | Function | Removes spill directories left behind by a process that is no longer running |
258
+ | `NodePromptHistoryFile`, `parsePromptHistoryLine` | Class, function | Append-only prompt history file that can be read newest-first |
259
+ | `compactPromptHistoryFile` | Function | Seals the prompt history one entry per line while terminals append |
260
+ | `StoredSessionRecords` | Class | The sealed or plain stored form both session stores read and write |
261
+ | `StoredLines`, `StoredPayloads` | Classes | The stored form of append lines and log sidecars |
262
+ | `migrateNodeSessionStore` | Function | Seals the plain session records of a directory in place |
263
+ | `migrateSessionLogDirectory` | Function | Seals the plain session logs of a directory in place |
264
+ | `createNodeWorkspacePathResolver` | Function | The `resolveInWorkspace` option, for a host with a file system |
265
+ | `registerProcessExitFlush` | Function | The `registerExitFlush` option of `FileSessionLogger`, for a host with a process |
266
+
232
267
  ## Session vs ConversationAgent
233
268
 
234
269
  - **`ConversationAgent`** (`agent-core`): the raw agent — conversation, tools and plugins. No permissions, no
@@ -236,12 +271,26 @@ durable Roundtable execution. Unresolved forks and workspace copies are refused
236
271
  - **`Session`** (this package): `ConversationAgent` plus permissions, hooks, compaction, logging and
237
272
  persistence. `@robota-sdk/agent-framework` builds its sessions from it.
238
273
 
274
+ ## What the host supplies
275
+
276
+ The session reads nothing from the machine it runs on. The composition root passes:
277
+
278
+ | Option | Without it |
279
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
280
+ | `hookTypeExecutors` (required) | Construction throws. `[]` runs no hook handler; on Node pass the command and HTTP executors from `agent-core/node` |
281
+ | `homeDirectory` | The home directory is unknown: `~`, and a home-relative target that may climb out of it (`~/..`), are refused |
282
+ | `resolveInWorkspace` | A shell command with a path operand is not judged read-only |
283
+ | `canonicalizePath` | A directory is compared as written, with no symlinks followed (`realpathSync` on Node) |
284
+ | `registerExitFlush` of `FileSessionLogger` | A stream's buffered tail is not flushed when the process exits mid-stream |
285
+
239
286
  ## Persistence
240
287
 
241
288
  `IInteractiveSessionRecord` carries the full conversation and resumable state. `NodeSessionStore`
242
- saves the record without inspecting its payload. It is a host adapter: passing it a directory does
243
- not establish workspace trust. `@robota-sdk/agent-framework` instead adapts a trusted project's
244
- state storage to the same store port.
289
+ (from `@robota-sdk/agent-session/node`) saves the record without inspecting its payload. It is a
290
+ host adapter: passing it a directory does not establish workspace trust. It is built with a sealing
291
+ decision: `{ sealing: { sealer } }` seals each record, `{ sealing: 'plain' }` keeps plain JSON.
292
+ `@robota-sdk/agent-framework` instead adapts a trusted project's state storage to the same store
293
+ port.
245
294
 
246
295
  When a `Session` re-saves an existing record, it keeps the fields it does not own and refreshes only
247
296
  its live conversation, history, prompt, schema, path and timestamp fields. A resumed session must
@@ -255,6 +304,13 @@ secret fields and stores large fields as content-addressed JSON payload files un
255
304
  `{sessionId}.payloads/`. Streaming text deltas go to the log as `text_delta` events; keep
256
305
  high-frequency output in logs and the session record focused on resumable state.
257
306
 
307
+ `NodeSessionLogSink`, `NodeSessionLogSource` and `NodePromptHistoryFile` are built with a sealing
308
+ decision too. With `{ sealing: { sealer } }` each append is one sealed line and each payload file is
309
+ sealed under a random name; with `{ sealing: 'plain' }` they write plain lines. A source reads plain
310
+ and sealed lines alike, and `readStoredLines()` says which lines it could not open (locked, damaged,
311
+ or sealed by a newer build) where `readText()` throws. `NodeToolResultSpillStore` takes the same
312
+ decision and the host directory it lives in.
313
+
258
314
  `Session.run()` forwards core execution events into the log, including `provider_request`,
259
315
  `provider_native_raw_payload`, `provider_stream_raw_delta`, `provider_response_raw`,
260
316
  `provider_response_normalized`, `assistant_message_committed`, `tool_batch_started`,
@@ -262,13 +318,14 @@ high-frequency output in logs and the session record focused on resumable state.
262
318
  `SESSION_LOG_EVENT` is the complete vocabulary that writers and readers share.
263
319
 
264
320
  Reading is source-driven: `loadSessionLogEntries(source)` takes an explicit `ISessionLogSource` and
265
- never turns a filename into file-system access on its own. Use `NodeSessionLogSource` when the
266
- application owns the path. Every declared event is validated before replay, nested messages
267
- included; unknown events, malformed fields and unsupported versions raise `SessionLogDecodeError`,
268
- and unversioned logs are not accepted. Payload files are read with an aggregate byte budget and
269
- checked for path escape, symlink replacement, size and hash mismatch; a failure is reported rather
270
- than skipped. `replaySessionLogEntries` rebuilds the chat history from `history_mutation` events, and
271
- `validateSessionReplayLogEntries` reports missing provider or tool events.
321
+ never turns a filename into file-system access on its own. Use `NodeSessionLogSource` from
322
+ `@robota-sdk/agent-session/node` when the application owns the path. Every declared event is
323
+ validated before replay, nested messages included; unknown events, malformed fields and unsupported
324
+ versions raise `SessionLogDecodeError`, and unversioned logs are not accepted. Payload files are
325
+ read with an aggregate byte budget and checked for path escape, symlink replacement, size and hash
326
+ mismatch; a failure is reported rather than skipped. `replaySessionLogEntries` rebuilds the chat
327
+ history from `history_mutation` events, and `validateSessionReplayLogEntries` reports missing
328
+ provider or tool events.
272
329
 
273
330
  Manual and automatic compaction carry one trigger value (`manual` or `auto`) to `PreCompact`,
274
331
  `PostCompact`, the `context_compact` log entry and `onCompactEvent`.
@@ -285,7 +342,8 @@ checks the conversion on disposable files without touching your sessions.
285
342
  - `@robota-sdk/agent-core` — `ConversationAgent`, permissions, hooks and core types
286
343
  - `@robota-sdk/agent-interface-session` — the session record and store contracts
287
344
  - `@robota-sdk/agent-interface-execution` — background task contracts stored in the session record
288
- - `@robota-sdk/agent-file-authority` — bounded, root-relative file reads for log payloads
345
+ - `@robota-sdk/agent-file-authority` — bounded, root-relative file reads for log payloads, used only
346
+ by `@robota-sdk/agent-session/node`
289
347
 
290
348
  ## Documentation
291
349