@robota-sdk/agent-session 3.0.6 → 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 +374 -0
- package/README.md +98 -40
- package/dist/index.cjs +9 -0
- package/dist/{node/index.d.cts → index.d.cts} +100 -242
- package/dist/index.d.cts.map +1 -0
- package/dist/{node/index.d.ts → index.d.ts} +100 -242
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/node.cjs +13 -0
- package/dist/node.d.cts +876 -0
- package/dist/node.d.cts.map +1 -0
- package/dist/node.d.ts +876 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +14 -0
- package/dist/node.js.map +1 -0
- package/dist/session-claims-BQGdD3Fe.cjs +1 -0
- package/dist/session-claims-D-Azmxq6.js +2 -0
- package/dist/session-claims-D-Azmxq6.js.map +1 -0
- package/dist/session-claims-DELiAZkZ.d.cts +145 -0
- package/dist/session-claims-DELiAZkZ.d.cts.map +1 -0
- package/dist/session-claims-DELiAZkZ.d.ts +145 -0
- package/dist/session-claims-DELiAZkZ.d.ts.map +1 -0
- package/package.json +26 -25
- package/dist/node/index.cjs +0 -10
- package/dist/node/index.d.cts.map +0 -1
- package/dist/node/index.d.ts.map +0 -1
- package/dist/node/index.js +0 -11
- package/dist/node/index.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,379 @@
|
|
|
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
|
+
|
|
369
|
+
## 3.0.7
|
|
370
|
+
### Patch Changes
|
|
371
|
+
|
|
372
|
+
- @robota-sdk/agent-core@3.0.7
|
|
373
|
+
- @robota-sdk/agent-file-authority@3.0.7
|
|
374
|
+
- @robota-sdk/agent-interface-execution@3.0.7
|
|
375
|
+
- @robota-sdk/agent-interface-session@3.0.7
|
|
376
|
+
|
|
3
377
|
## 3.0.6
|
|
4
378
|
### Patch Changes
|
|
5
379
|
|
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
|
|
204
|
-
|
|
|
205
|
-
| `Session`
|
|
206
|
-
| `ISessionOptions`, `ISessionRunOptions`, `ISessionShutdownOptions`
|
|
207
|
-
| `SessionBusyError`, `TurnClaim`
|
|
208
|
-
| `PermissionEnforcer`
|
|
209
|
-
| `consentScopeFor`
|
|
210
|
-
| `AutoModeGate`, `IPermissionClassifier`
|
|
211
|
-
| `ContextWindowTracker`, `AUTO_COMPACT_THRESHOLD`
|
|
212
|
-
| `CompactionOrchestrator`, `CompactionError`, `DEFAULT_COMPACTION_PROMPT`
|
|
213
|
-
| `TPermissionHandler`, `TPermissionResult`
|
|
214
|
-
| `ITerminalOutput`, `ISpinner`
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
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
|
|
243
|
-
not establish workspace trust.
|
|
244
|
-
|
|
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`
|
|
266
|
-
application owns the path. Every declared event is
|
|
267
|
-
included; unknown events, malformed fields and unsupported
|
|
268
|
-
and unversioned logs are not accepted. Payload files are
|
|
269
|
-
checked for path escape, symlink replacement, size and hash
|
|
270
|
-
than skipped. `replaySessionLogEntries` rebuilds the chat
|
|
271
|
-
`validateSessionReplayLogEntries` reports missing
|
|
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
|
|