@robota-sdk/agent-session 3.0.0-beta.79 → 3.0.0-beta.81

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.
@@ -0,0 +1,1620 @@
1
+ import { IAIProvider, IContextWindowState, IEventService, IHistoryEntry, IHookTypeExecutor, IResponseFormatConfig, IRunTraceContext, ISpinner, ISubprocessTraceEnv, ITerminalOutput, IToolExecutionContext, IToolExecutionResult, IToolResultSpillStore, IToolSchema, IToolWithEventService, IUserInteraction, Robota, TBackgroundPermissionPolicy, TModelEffort, TModelEffortSelection, TPermissionMode, TSessionEndReason, TToolArgs, TToolChoice, TToolParameters, TUniversalMessage, TUniversalValue } from "@robota-sdk/agent-core";
2
+ import { ICompactEvent, IInteractiveSessionRecord, IInteractiveSessionRecord as IInteractiveSessionRecord$1, IInteractiveSessionRecord as ISessionRecord, IInteractiveSessionStore, IInteractiveSessionStore as IInteractiveSessionStore$1, IInteractiveSessionStore as ISessionStore, IPromptHistoryBlock, IPromptHistoryEntry, IPromptHistoryReadOptions, IPromptHistorySource, IPromptHistoryWriter, ISessionListEntry, ISessionRecordDecodeIssue, TCompactTrigger, TPermissionResultValue, TSessionLoadOutcome } from "@robota-sdk/agent-interface-session";
3
+ //#region src/turn-claim.d.ts
4
+ /**
5
+ * Thrown when a turn is started on a session that is already running one. RUNTIME-003.
6
+ *
7
+ * A distinct type rather than a bare `Error`, because the point of giving the session a claim is to
8
+ * let consumers STOP maintaining their own busy flags — and a consumer that has to regex-match an
9
+ * error message to tell "busy, retry later" apart from a provider failure has not been given
10
+ * anything it can act on. Follows `CompactionError`, this package's existing precedent.
11
+ */
12
+ declare class SessionBusyError extends Error {
13
+ /** Always `true`: the caller can run this turn later; nothing about the session is broken. */
14
+ readonly recoverable = true;
15
+ constructor(message: string);
16
+ }
17
+ /**
18
+ * The identity of the turn a session is currently running. RUNTIME-003.
19
+ *
20
+ * A session used to express "is something running?" as a bare `AbortController | null` field that
21
+ * `run()` overwrote on entry. That field was doing three jobs at once — cancellation channel, busy
22
+ * flag, and turn identity — and it could only do them for ONE turn, so a second concurrent `run()`
23
+ * orphaned the first: `abort()` reached only whichever turn held the field, and the first turn to
24
+ * finish cleared it in its `finally`, making `abort()` on the survivor a silent no-op. `isRunning()`
25
+ * read the same field, so it answered about whichever turn happened to own it. That is why consumers
26
+ * of this library grew their own busy flags rather than trusting it.
27
+ *
28
+ * The fix is to give the unit of work an OWNER. A claim is taken synchronously, it belongs to the
29
+ * caller that took it, and ONLY that caller releases it.
30
+ *
31
+ * REFUSAL, not pre-emption. A session is a single conversation: two turns interleaving on it produce
32
+ * a history neither of them wrote, and silently cancelling the first would discard work the caller
33
+ * never asked to abandon. `claim()` throws {@link SessionBusyError}, whose message names the three
34
+ * ways forward.
35
+ */
36
+ declare class TurnClaim {
37
+ private controller;
38
+ /**
39
+ * Take the claim for a new turn, or throw if one is already held.
40
+ *
41
+ * MUST be called before the first `await` in the turn — a check that yields first is not a claim,
42
+ * it is a TOCTOU window, and two callers can pass it in the same tick.
43
+ *
44
+ * @throws {SessionBusyError} if a turn is already running, INCLUDING one that has been aborted but
45
+ * has not finished unwinding. See {@link abort} for why that case is not an exception.
46
+ */
47
+ claim(): AbortController;
48
+ /**
49
+ * Release the claim — but only if `controller` is still the one holding it.
50
+ *
51
+ * The ownership check is what stops the original defect from reappearing in a new shape: a turn
52
+ * that released unconditionally in its `finally` could free a claim a LATER turn already took, and
53
+ * `isRunning()` would then report idle while a turn was in flight.
54
+ */
55
+ release(controller: AbortController): void;
56
+ /**
57
+ * Signal the running turn to stop. Idempotent; a no-op if nothing is running.
58
+ *
59
+ * This does NOT release the claim, and that is deliberate. An earlier version cleared it here, so
60
+ * `isRunning()` answered `false` the instant `abort()` returned — while the aborted turn was still
61
+ * unwinding, still able to write history and finish tool calls. A new `run()` could then claim the
62
+ * session and interleave with it: exactly the two-turns-on-one-session defect RUNTIME-003 is
63
+ * about, just moved behind the abort boundary. Review of the first draft caught it.
64
+ *
65
+ * A turn is not over when it is asked to stop; it is over when it has stopped. The claim is
66
+ * therefore held until the owning turn's `finally` releases it, and until then `isRunning()` says
67
+ * `true` and a further `run()` is refused. Cancel and restart is `abort()`, then AWAIT the turn,
68
+ * then `run()` — which is what every caller in this repo already does.
69
+ */
70
+ abort(): void;
71
+ isRunning(): boolean;
72
+ }
73
+ //#endregion
74
+ //#region src/context-window-tracker.d.ts
75
+ /** Auto-compact when context usage reaches this fraction */
76
+ declare const AUTO_COMPACT_THRESHOLD = 0.835;
77
+ type TAutoCompactThreshold = number | false;
78
+ declare class ContextWindowTracker {
79
+ private contextUsedTokens;
80
+ private readonly contextMaxTokens;
81
+ private autoCompactThreshold;
82
+ constructor(model: string, contextMaxTokens?: number, autoCompactThreshold?: TAutoCompactThreshold);
83
+ /** Get current context window state */
84
+ getContextState(): IContextWindowState;
85
+ /** Whether auto-compaction threshold has been exceeded */
86
+ shouldAutoCompact(): boolean;
87
+ /** The auto-compaction policy for this tracker. */
88
+ getAutoCompactThreshold(): TAutoCompactThreshold;
89
+ /** Update the auto-compaction policy for this tracker. */
90
+ setAutoCompactThreshold(autoCompactThreshold: TAutoCompactThreshold): void;
91
+ /**
92
+ * Estimate token usage from conversation history.
93
+ *
94
+ * Uses the shared core estimator (`estimateContextTokensFromMessages`) so session display,
95
+ * /context, auto-compact, and core execution guards reason about the same effective token state.
96
+ * That estimator prefers the provider's actual reported token count (which includes the system
97
+ * prompt and tool schemas) over a raw serialized-history char heuristic, falling back to the
98
+ * serialized estimate only when no provider usage is present on the latest message.
99
+ */
100
+ updateFromHistory(history: TUniversalMessage[]): void;
101
+ /** Reset token tracking */
102
+ reset(): void;
103
+ }
104
+ //#endregion
105
+ //#region src/auto-mode-gate.d.ts
106
+ /** The call the classifier judges. */
107
+ interface IClassifiedCall {
108
+ readonly toolName: string;
109
+ readonly toolArgs: TToolArgs;
110
+ readonly cwd: string;
111
+ }
112
+ interface IClassifierVerdict {
113
+ readonly decision: 'allow' | 'block';
114
+ /** Short, for the model and the user: which rule, and why. */
115
+ readonly reason: string;
116
+ }
117
+ /**
118
+ * Judges one call. `undefined` means no usable verdict (an error, a refusal, output that does not
119
+ * parse): the call is not run. It counts toward a run of refusals, so a classifier that cannot
120
+ * answer hands the decision to a person instead of refusing every call.
121
+ */
122
+ interface IPermissionClassifier {
123
+ classify(call: IClassifiedCall, signal?: AbortSignal): Promise<IClassifierVerdict | undefined>;
124
+ }
125
+ /** Refusals in a row (blocks or unusable verdicts), and blocks in the session, that pause the mode. */
126
+ declare const CONSECUTIVE_BLOCK_LIMIT = 3;
127
+ declare const TOTAL_BLOCK_LIMIT = 20;
128
+ /** `reason` is for the user (`/permissions`); `message` is what the model is told. */
129
+ type TAutoModeJudgement = {
130
+ readonly kind: 'allow';
131
+ } | {
132
+ readonly kind: 'block';
133
+ readonly reason: string;
134
+ readonly message: string;
135
+ } | {
136
+ readonly kind: 'unusable';
137
+ readonly reason: string;
138
+ readonly message: string;
139
+ };
140
+ declare class AutoModeGate {
141
+ private readonly classifier;
142
+ private consecutive;
143
+ private total;
144
+ private paused;
145
+ /** Calls a person allowed to be retried once after the classifier blocked them. */
146
+ private readonly retries;
147
+ constructor(classifier: IPermissionClassifier);
148
+ /** The mode asks a person until one approves. */
149
+ isPaused(): boolean;
150
+ /** A person approved while paused: the classifier decides again. */
151
+ resume(): void;
152
+ /** Let this exact call through once, without the classifier. */
153
+ grantRetry(toolName: string, toolArgs: TToolArgs): void;
154
+ /** Consume a retry grant for this exact call, if there is one. */
155
+ takeRetry(toolName: string, toolArgs: TToolArgs): boolean;
156
+ judge(call: IClassifiedCall, signal?: AbortSignal): Promise<TAutoModeJudgement>;
157
+ }
158
+ //#endregion
159
+ //#region src/session-log-reference-types.d.ts
160
+ /**
161
+ * Leaf type module for {@link IExternalPayloadReference}.
162
+ *
163
+ * Split out of `session-logger.ts` so `session-log-sinks.ts` and `session-log-payload.ts` can
164
+ * depend on this type without importing back from `session-logger.ts`, which previously created
165
+ * import cycles among the three files.
166
+ */
167
+ interface IExternalPayloadReference {
168
+ kind: 'external-payload';
169
+ encoding: 'json';
170
+ sha256: string;
171
+ byteLength: number;
172
+ relativePath: string;
173
+ }
174
+ /** Session log event data — extensible record of event metadata. */
175
+ type TSessionLogValue = string | number | boolean | object | null | undefined;
176
+ type TSessionLogData = Record<string, TSessionLogValue>;
177
+ interface IFileSessionLoggerOptions {
178
+ externalPayloadThresholdBytes?: number;
179
+ redactedValue?: string;
180
+ }
181
+ //#endregion
182
+ //#region src/session-log-sinks.d.ts
183
+ /** Canonical validation and construction for every session-log external-payload reference. */
184
+ declare function createSessionLogExternalPayloadReference(sessionId: string, sha256: string, serialized: string): IExternalPayloadReference;
185
+ /** Workspace-neutral sink for content-addressed external JSON payloads. */
186
+ interface IExternalPayloadSink {
187
+ writeJson(sessionId: string, sha256: string, serialized: string): IExternalPayloadReference;
188
+ }
189
+ /** Workspace-neutral append sink for session-log bytes. */
190
+ interface ISessionLogSink {
191
+ append(sessionId: string, text: string): void;
192
+ readonly externalPayloadSink?: IExternalPayloadSink;
193
+ }
194
+ /** Explicit host-filesystem sink for JSONL logs and their content-addressed sidecars. */
195
+ declare class NodeSessionLogSink implements ISessionLogSink, IExternalPayloadSink {
196
+ private readonly logDirectory;
197
+ readonly externalPayloadSink: IExternalPayloadSink;
198
+ private readonly enabled;
199
+ constructor(logDirectory: string);
200
+ append(sessionId: string, text: string): void;
201
+ writeJson(sessionId: string, sha256: string, serialized: string): IExternalPayloadReference;
202
+ }
203
+ //#endregion
204
+ //#region src/session-logger.d.ts
205
+ /**
206
+ * Session logger interface — injected into Session for pluggable logging.
207
+ *
208
+ * Implementations decide where and how to persist session events.
209
+ * The Session class calls log() for every significant action.
210
+ */
211
+ interface ISessionLogger {
212
+ /** Log a session event with structured data. */
213
+ log(sessionId: string, event: string, data: TSessionLogData): void;
214
+ /**
215
+ * Write out anything buffered.
216
+ *
217
+ * Optional because an implementation that never buffers has nothing to do. A caller that needs
218
+ * the log to be complete on disk — session end, or a reader about to parse it — calls this.
219
+ */
220
+ flush?(): void;
221
+ }
222
+ /**
223
+ * Sink-driven session logger — writes JSONL through `ISessionLogSink`.
224
+ *
225
+ * This is the default implementation used by the CLI.
226
+ * Each line is a self-contained JSON object with timestamp, sessionId, event, and data.
227
+ */
228
+ declare class FileSessionLogger implements ISessionLogger {
229
+ private readonly sink;
230
+ private readonly options;
231
+ /** Buffered hot-path lines, per session file. Keyed by session id (CORE-029). */
232
+ private readonly pending;
233
+ private pendingBytes;
234
+ constructor(sink: ISessionLogSink, options?: IFileSessionLoggerOptions);
235
+ log(sessionId: string, event: string, data: TSessionLogData): void;
236
+ /** Write out every buffered hot-path line. Safe to call when nothing is pending. */
237
+ flush(): void;
238
+ private buffer;
239
+ private write;
240
+ private report;
241
+ }
242
+ declare class SilentSessionLogger implements ISessionLogger {
243
+ log(): void;
244
+ }
245
+ //#endregion
246
+ //#region src/permission-types.d.ts
247
+ /** The part of a sandbox client the permission gate consults. */
248
+ interface ICommandSandboxApproval {
249
+ /**
250
+ * Whether `toolName` runs `shellCommand` inside the sandbox and the sandbox's settings let it
251
+ * proceed without a prompt. Only a tool the sandbox actually wraps may answer yes.
252
+ */
253
+ autoApproves(toolName: string, shellCommand: string): boolean;
254
+ }
255
+ /**
256
+ * Permission handler result (issue #2052: the union is OWNED by `agent-interface-session` as
257
+ * `TPermissionResultValue`; this name is the session-layer alias, not a second declaration):
258
+ * - true: allow this invocation
259
+ * - false: deny this invocation
260
+ * - 'allow-session': allow this invocation and auto-approve the CONSENT SCOPE — the pattern
261
+ * `consentScopeFor` projects from this invocation's argument (issue #2351), e.g. `Bash(git *)` —
262
+ * for the rest of the session
263
+ * - 'allow-project': allow this invocation and persist that same scope pattern to the project's
264
+ * local settings; the storage location is owned by the consuming layer (via `onProjectAllowTool`)
265
+ */
266
+ type TPermissionResult = TPermissionResultValue;
267
+ /**
268
+ * Custom permission handler — called when a tool needs user approval.
269
+ * Returns true to allow, false to deny, or 'allow-session' to remember for the session.
270
+ */
271
+ type TPermissionHandler = (toolName: string, toolArgs: TToolArgs) => Promise<TPermissionResult>;
272
+ interface IPermissionEnforcerOptions {
273
+ sessionId: string;
274
+ cwd: string;
275
+ getPermissionMode: () => TPermissionMode;
276
+ config: {
277
+ /** `ask` patterns always ask, in every mode including bypassPermissions (issue #3081). */
278
+ permissions: {
279
+ allow: string[];
280
+ deny: string[];
281
+ ask?: string[];
282
+ };
283
+ hooks?: Record<string, unknown>;
284
+ };
285
+ /**
286
+ * The OS sandbox the shell tools run under, when there is one: whether it confines a command and
287
+ * lets it run without a prompt.
288
+ */
289
+ commandSandbox?: ICommandSandboxApproval;
290
+ /** Where `~` and `$HOME` point for critical-path removal checks. Defaults to the OS home directory. */
291
+ homeDirectory?: string;
292
+ /**
293
+ * ARCH-040 Group C (issue #1934): the rules BEFORE any preset contributed.
294
+ *
295
+ * Supplied by the composition root, never derived here. `config.permissions` already carries the
296
+ * STARTUP preset's patterns, so capturing a base from it on the first live `/preset` would keep
297
+ * the first preset's allowlist through every later switch — the accumulation the replace rule
298
+ * exists to prevent, arriving through the base rather than through the merge. Absent ⇒ no preset
299
+ * contributed, and `config.permissions` is itself the preset-free base.
300
+ */
301
+ presetFreePermissions?: {
302
+ allow: readonly string[];
303
+ deny: readonly string[];
304
+ };
305
+ terminal: ITerminalOutput;
306
+ permissionHandler?: TPermissionHandler;
307
+ promptForApprovalFn?: (terminal: ITerminalOutput, toolName: string, toolArgs: TToolArgs) => Promise<TPermissionResult>;
308
+ sessionLogger?: ISessionLogger;
309
+ onToolExecution?: (event: {
310
+ type: 'start' | 'end';
311
+ toolName: string;
312
+ toolArgs?: TToolArgs;
313
+ success?: boolean;
314
+ denied?: boolean;
315
+ toolResultData?: string;
316
+ executionId?: string;
317
+ }) => void;
318
+ /** Additional hook type executors (e.g. prompt, agent) beyond the core defaults. */
319
+ hookTypeExecutors?: IHookTypeExecutor[];
320
+ /** Absolute path to session transcript file — passed to PreToolUse hook inputs as transcript_path */
321
+ transcriptPath?: string;
322
+ /** Called when the user selects "allow for project" — persists the tool pattern to project settings. */
323
+ onProjectAllowTool?: (toolName: string) => void;
324
+ /**
325
+ * CORE-025: a background/subagent task permission policy. It adds a ceiling (checked before bypass),
326
+ * an ask-everything flag and the task's own lists to the one evaluator, so `deny`/`preapproved`/
327
+ * `inherit-allowlist` still bind under a permissive mode. Absent → no policy constraints.
328
+ */
329
+ permissionPolicy?: TBackgroundPermissionPolicy;
330
+ /**
331
+ * CORE-025: the task's OWN declared allow/deny rules (distinct from the parent session's `config.permissions`
332
+ * which `inherit-allowlist` inherits). `preapproved` consults these.
333
+ */
334
+ taskPermissions?: {
335
+ allow?: readonly string[];
336
+ deny?: readonly string[];
337
+ };
338
+ /**
339
+ * Judges, in `auto` mode, the calls the mode would otherwise ask a person about. Absent → the
340
+ * session cannot enter `auto`.
341
+ */
342
+ permissionClassifier?: IPermissionClassifier;
343
+ }
344
+ //#endregion
345
+ //#region src/permission-denial-log.d.ts
346
+ /**
347
+ * Why a call was refused:
348
+ * - `policy` — the gate answered deny: a deny rule, a background ceiling, or plan mode;
349
+ * - `user` — a person was asked and declined, or the turn was cancelled while asking;
350
+ * - `no-approver` — the call needed a person and none was attached;
351
+ * - `classifier` — in `auto` mode, the classifier blocked the call or gave no usable verdict.
352
+ */
353
+ type TPermissionDenialReason = 'policy' | 'user' | 'no-approver' | 'classifier';
354
+ interface IPermissionDenial {
355
+ readonly toolName: string;
356
+ /** The argument the tool's permission profile names (command, path, URL), when it has one. */
357
+ readonly argument?: string;
358
+ readonly reason: TPermissionDenialReason;
359
+ /** The classifier's reason, for a `classifier` denial. */
360
+ readonly detail?: string;
361
+ /** Epoch milliseconds. */
362
+ readonly at: number;
363
+ }
364
+ //#endregion
365
+ //#region src/permission-enforcer.d.ts
366
+ declare class PermissionEnforcer {
367
+ private readonly sessionId;
368
+ private readonly cwd;
369
+ private readonly getPermissionMode;
370
+ private readonly config;
371
+ private readonly terminal;
372
+ private readonly permissionHandler?;
373
+ private readonly promptForApprovalFn?;
374
+ private readonly sessionLogger?;
375
+ private readonly onToolExecution?;
376
+ private readonly hookTypeExecutors?;
377
+ private readonly transcriptPath?;
378
+ /**
379
+ * Issue #2351: consent is remembered as PATTERNS (`consentScopeFor`), not tool names, and read
380
+ * back through the gate's own matcher — approving one argument does not allow every argument.
381
+ */
382
+ private readonly sessionAllowedTools;
383
+ /** The configured rules before any preset contributed — see {@link applyPresetToolLists}. */
384
+ private readonly presetFreeRules;
385
+ private readonly onProjectAllowTool?;
386
+ private readonly permissionPolicy?;
387
+ private readonly taskPermissions?;
388
+ private readonly homeDirectory;
389
+ private readonly resolveInWorkspace;
390
+ private readonly commandSandbox?;
391
+ private readonly denials;
392
+ /** A turn a peer's message started is in progress: the one place the reply to that peer exists. */
393
+ private peerTurn;
394
+ private readonly autoMode?;
395
+ constructor(options: IPermissionEnforcerOptions);
396
+ /**
397
+ * Start a turn, which a peer's message started when `peerTurn` is true. That decides only whether
398
+ * the reply to the peer exists; every other call is decided exactly as in any turn.
399
+ */
400
+ beginTurn(peerTurn: boolean): void;
401
+ /** End the turn; the reply to a peer is gone until the next peer turn begins. */
402
+ endTurn(): void;
403
+ /** Whether `auto` mode can run here: it needs a classifier to decide for it. */
404
+ hasPermissionClassifier(): boolean;
405
+ /**
406
+ * Let the call behind a classifier denial run once, unjudged, when the model tries it again.
407
+ * Returns the denial, or `undefined` when `index` names no classifier denial.
408
+ */
409
+ allowRetryOfDenial(index: number): IPermissionDenial | undefined;
410
+ /** Every configured pattern, split by the grammar it is held to. */
411
+ private configuredRules;
412
+ /**
413
+ * Whether the model is shown this tool at all. A bare-name deny (`Tool`, `Tool(*)`, a name glob)
414
+ * removes it rather than offering it and refusing every call (issue #3081). Read live, so a
415
+ * `/preset` that denies a tool hides it from the next round.
416
+ */
417
+ isToolVisible(toolName: string): boolean;
418
+ /**
419
+ * Tell the gate each tool's parameter names — the schema is what makes `Tool(name:value)` a
420
+ * parameter rule — then re-check the rules against them, before any turn runs.
421
+ */
422
+ private registerToolParameters;
423
+ /** Wrap all tools with permission checking */
424
+ wrapTools(tools: IToolWithEventService[]): IToolWithEventService[];
425
+ /** The consent patterns granted this session via "Allow always" — e.g. `Bash(git *)` (issue #2351). */
426
+ getSessionAllowedTools(): string[];
427
+ /** The calls this session refused, most recent first (issue #3082). */
428
+ getRecentDenials(): readonly IPermissionDenial[];
429
+ /** Clear all session-scoped allow rules. */
430
+ clearSessionAllowedTools(): void;
431
+ /**
432
+ * Replace the configured permission rules on a LIVE session (ARCH-040 Group C, issue #1934).
433
+ *
434
+ * The seam is this small because `checkPermission` reads `this.config.permissions` on every call
435
+ * rather than snapshotting it at construction — so the next call sees the new rules and nothing
436
+ * needs re-wiring. Without a seam the startup path could apply a preset's tool lists and the live
437
+ * `/preset` path could not, which is the divergence `scan-preset-projection` exists to measure:
438
+ * one session holding two answers for the same preset depending on WHEN it was chosen.
439
+ *
440
+ * **A call already in flight runs to completion.** `checkPermission` is awaited BEFORE the tool
441
+ * executes, so such a call has already passed its gate, and a gate is a decision at a point in
442
+ * time. There is also no rollback for a partially applied tool — a file already written stays
443
+ * written — so a revocation that cannot undo is a stop, not a denial. Building one would rest on
444
+ * the cancellation path, which RUNTIME-004 records as declared at four layers and honoured at none.
445
+ *
446
+ * A newly applied denial DOES outrank an earlier "always allow": `evaluatePermission` answers
447
+ * `deny` before `promptForApproval` — the only reader of `sessionAllowedTools` — is reached. That
448
+ * is not new behaviour here; it is the existing precedence, and it agrees with the combine rule
449
+ * that a denial is not weakened by a later layer.
450
+ */
451
+ /**
452
+ * The rules the next `checkPermission` will read.
453
+ *
454
+ * Exposed so a case can assert what a live re-application PRODUCED, not merely that the method
455
+ * exists. Review found the first cut composing onto a contaminated base and no test could see it,
456
+ * because nothing could look at the rules.
457
+ */
458
+ currentPermissionRules(): {
459
+ allow: readonly string[];
460
+ deny: readonly string[];
461
+ ask: readonly string[];
462
+ };
463
+ applyPresetToolLists(preset: {
464
+ allowedTools?: readonly string[];
465
+ deniedTools?: readonly string[];
466
+ }): void;
467
+ /** Evaluate permission for a tool call. `signal` — RUNTIME-005; see `decideApproval` for why a
468
+ * cancelled approval denies. */
469
+ checkPermission(toolName: string, toolArgs: TToolArgs, signal?: AbortSignal, interaction?: IToolExecutionContext['permissionInteraction'], hookTraceEnv?: IToolExecutionContext['hookTraceEnv']): Promise<boolean>;
470
+ /**
471
+ * Decide an action that has `toolName`'s effect but does not run through that tool — a command
472
+ * that starts a process, say. It passes what the tool call would: the PreToolUse hooks (so
473
+ * guardrails apply), then the gate's rules, mode, remembered consent and prompt. It never takes
474
+ * the command sandbox's auto-approval, because the action does not run inside that sandbox.
475
+ */
476
+ checkDelegatedToolCall(toolName: string, toolParameters: TToolParameters, signal?: AbortSignal): Promise<boolean>;
477
+ /** {@link checkPermission}, keeping the reason a refusal carries for the model. */
478
+ private decidePermission;
479
+ private decideInAutoMode;
480
+ /**
481
+ * The human-approval path: session-scoped allow list → custom handler → injected approval fn → fail-closed
482
+ * deny. Every `approve` decision comes here, whoever the caller, so every ask fails closed identically
483
+ * when no approver is attached (e.g. a detached background task).
484
+ */
485
+ private promptForApproval;
486
+ /**
487
+ * SELFHOST-009: fire the PermissionDecision hook (informational-only, non-blocking) via the shared
488
+ * `runHooks` path. Fire-and-forget — the result is never awaited or consulted, so it cannot gate the
489
+ * permission outcome. The sole blocking gate remains PreToolUse (`runPreToolHook`).
490
+ */
491
+ private firePermissionDecisionHook;
492
+ /** Whether the OS sandbox confines this shell command and lets it run without a prompt. */
493
+ private sandboxAutoApproves;
494
+ /** Delegate session event to the injected logger. */
495
+ private log;
496
+ }
497
+ //#endregion
498
+ //#region src/session-base.d.ts
499
+ declare abstract class SessionBase {
500
+ protected abstract readonly agent: Robota;
501
+ protected abstract readonly permissionEnforcer: PermissionEnforcer;
502
+ protected abstract readonly contextTracker: ContextWindowTracker;
503
+ protected abstract permissionMode: TPermissionMode;
504
+ protected abstract activePresetId: string;
505
+ protected abstract parallelSubagentsEnabled: boolean;
506
+ protected abstract readonly sessionId: string;
507
+ protected abstract readonly aiProvider: IAIProvider;
508
+ protected abstract readonly toolSchemas: IToolSchema[];
509
+ protected abstract model: string;
510
+ protected abstract systemMessage: string;
511
+ protected abstract messageCount: number;
512
+ /** ARCH-010: the session's execution root — owned here, with the check that it was supplied. */
513
+ protected readonly cwd: string;
514
+ protected constructor(cwd: string);
515
+ /**
516
+ * RUNTIME-003: the turn currently running, and its owner. Was a bare `AbortController | null` that
517
+ * `run()` overwrote, which is why `abort()` and `isRunning()` below could answer about a turn that
518
+ * was not the one in flight. See `turn-claim.ts`.
519
+ */
520
+ protected readonly turnClaim: TurnClaim;
521
+ private readonly permissionModeGuards;
522
+ getPermissionMode(): TPermissionMode;
523
+ /** Change the active permission mode — future tool calls will use the new mode. */
524
+ setPermissionMode(mode: TPermissionMode): void;
525
+ /** Register a synchronous policy check at the single session-mode mutation boundary. */
526
+ addPermissionModeGuard(guard: (next: TPermissionMode) => void): () => void;
527
+ /** Read the active preset id (PRESET-011 runtime state). */
528
+ getActivePresetId(): string;
529
+ /**
530
+ * Set the active preset id. PURE STATE — this only records which preset is active;
531
+ * it does not re-apply any preset options (permission/model/persona). Higher layers
532
+ * own re-application (PRESET-012/013/014).
533
+ */
534
+ setActivePresetId(id: string): void;
535
+ /** Whether subagent dispatch is currently allowed for this session (PRESET-016 runtime gate). */
536
+ getParallelSubagentsEnabled(): boolean;
537
+ /** Toggle subagent dispatch live. Only effective if the agent runtime was built at assembly. */
538
+ setParallelSubagentsEnabled(enabled: boolean): void;
539
+ getSessionId(): string;
540
+ /**
541
+ * The session's execution root (ARCH-010).
542
+ *
543
+ * Readable because a caller that derives something FROM the session — a fork, a subagent, a hook
544
+ * input — must be able to ask which root this session actually runs in. Re-deriving it from
545
+ * `process.cwd()` is how the two silently diverged.
546
+ */
547
+ getCwd(): string;
548
+ getSystemMessage(): string;
549
+ /**
550
+ * Replace the active system message and propagate it so the next provider request carries it.
551
+ * Records the live value on `this.systemMessage` (re-injected on compaction) and delegates to
552
+ * `Robota.updateSystemPrompt`, which updates the single-source `config.systemMessage` and the live
553
+ * conversation store head. The system prompt is an agent-level concern, not model config, so this
554
+ * does not route through `setModel`. Used by persona application, the self-verification toggle, and
555
+ * AGENTS.md/CLAUDE.md staleness refresh.
556
+ */
557
+ updateSystemMessage(newMessage: string): void;
558
+ /**
559
+ * Re-apply model options to the live session (PRESET-013 model/effort re-application seam).
560
+ *
561
+ * Propagates model/effort/temperature/maxOutputTokens to the agent via `robota.setModel` so the
562
+ * next call reflects them, and updates `this.model` to keep `getModelId()` accurate. The preset
563
+ * `maxOutputTokens` field maps to the agent's `maxTokens` channel. Absent fields are left untouched.
564
+ */
565
+ applyModelOptions(options: {
566
+ model?: string;
567
+ effort?: TModelEffortSelection;
568
+ temperature?: number;
569
+ maxOutputTokens?: number;
570
+ }): Promise<void>;
571
+ /** Read the selection for the next model call; provider default remains `auto`. */
572
+ getModelEffort(): TModelEffortSelection;
573
+ /** Run an operation with a temporary effort override and restore it on every exit path. */
574
+ withScopedModelEffort<T>(effort: TModelEffort, operation: () => Promise<T>): Promise<T>;
575
+ /**
576
+ * Re-apply the agent's identity label to a LIVE session.
577
+ *
578
+ * ARCH-040 (issue #1820): a preset's `agentName` reached the agent only at construction, so
579
+ * starting with a preset set the name while switching to the SAME preset mid-session left the old
580
+ * one — one preset with two answers, decided by when it was chosen.
581
+ *
582
+ * Goes through `updateConfiguration`, the agent's own config seam: the agent's `name` reads THROUGH
583
+ * its config, so writing the config is the whole rename and no copy is left stale.
584
+ */
585
+ applyAgentName(name: string): Promise<void>;
586
+ getToolSchemas(): IToolSchema[];
587
+ getMessageCount(): number;
588
+ /** Get tools that have been session-approved (via "Allow always" choice). */
589
+ /**
590
+ * ARCH-040 Group C (issue #1934): re-apply a preset's tool lists to the live enforcer.
591
+ *
592
+ * The BASE it composes onto is the session's configured rules minus whatever a previous preset
593
+ * contributed — which is why the enforcer keeps the original: an allowlist REPLACES the preset
594
+ * layer's contribution rather than accumulating across successive `/preset` switches, while a
595
+ * denial UNIONS because it must not be weakened by a later layer that forgot to repeat it.
596
+ */
597
+ applyPresetToolLists(preset: {
598
+ allowedTools?: readonly string[];
599
+ deniedTools?: readonly string[];
600
+ }): void;
601
+ /**
602
+ * The rules this session's gate reads right now — settings, preset lists and command auto-allows
603
+ * together — so a subagent inherits what the parent actually enforces (issue #3081). Session-scoped
604
+ * "allow always" consent is not included: it was given for this session's context.
605
+ */
606
+ getPermissionRules(): {
607
+ allow: string[];
608
+ deny: string[];
609
+ ask: string[];
610
+ };
611
+ getSessionAllowedTools(): string[];
612
+ /**
613
+ * Decide an action that reaches `toolName`'s effect by another route (a command that starts a
614
+ * process), so that route cannot be a way around the tool's own permission: the PreToolUse hooks
615
+ * and guardrails, then the rules, mode, remembered consent and the prompt. The command sandbox's
616
+ * auto-approval never applies, because the action does not run inside that sandbox.
617
+ */
618
+ checkToolPermission(toolName: string, toolParameters: TToolParameters, signal?: AbortSignal): Promise<boolean>;
619
+ /** `auto` mode hands decisions to a classifier, so a session without one cannot enter it. */
620
+ protected requireClassifierFor(mode: TPermissionMode): void;
621
+ /**
622
+ * Let the call behind a classifier denial (by its index in the recent denials) run once when the
623
+ * model tries it again. Returns the denial, or `undefined` when the index names no classifier
624
+ * denial.
625
+ */
626
+ retryPermissionDenial(index: number): IPermissionDenial | undefined;
627
+ /** The calls this session refused, most recent first (issue #3082). */
628
+ getRecentPermissionDenials(): readonly IPermissionDenial[];
629
+ clearSessionAllowedTools(): void;
630
+ /** Abort the currently running execution. No-op if nothing is running. */
631
+ abort(): void;
632
+ isRunning(): boolean;
633
+ getContextState(): IContextWindowState;
634
+ /** Estimate context usage from current conversation history (used after session restore). */
635
+ syncContextFromHistory(): void;
636
+ getAutoCompactThreshold(): TAutoCompactThreshold;
637
+ setAutoCompactThreshold(threshold: number | false): void;
638
+ getHistory(): TUniversalMessage[];
639
+ getFullHistory(): IHistoryEntry[];
640
+ getSessionTokenUsage(): {
641
+ inputTokens: number;
642
+ outputTokens: number;
643
+ } | undefined;
644
+ getModelId(): string;
645
+ /**
646
+ * The tool schemas the model is offered at the next request (CLI-1990).
647
+ *
648
+ * The offered set, not the registered one: a deferred tool that has not been loaded is absent,
649
+ * because it is absent from the request. `/context` reads this to report what the tool schemas
650
+ * actually cost, which is the only surface that makes deferral's saving observable.
651
+ */
652
+ getOfferedToolSchemas(): IToolSchema[];
653
+ /** The provider the session sends its turns to now; a provider switch replaces it. */
654
+ getProvider(): IAIProvider;
655
+ getProviderId(): string;
656
+ /** Add an event entry to history (not a chat message) */
657
+ addHistoryEntry(entry: IHistoryEntry): void;
658
+ /** Inject a message into conversation history without execution (used for session restore). */
659
+ injectMessage(role: 'user' | 'assistant' | 'system' | 'tool', content: string, options?: {
660
+ toolCallId?: string;
661
+ name?: string;
662
+ }): void;
663
+ /**
664
+ * Inject a full TUniversalMessage preserving all fields (toolCalls, toolCallId, null content).
665
+ * Used during session restore to correctly reconstruct tool_use+tool_result pairs.
666
+ */
667
+ injectRawMessage(msg: TUniversalMessage): void;
668
+ clearHistory(): void;
669
+ }
670
+ //#endregion
671
+ //#region src/session-types.d.ts
672
+ /** Options for graceful session shutdown. */
673
+ interface ISessionShutdownOptions {
674
+ reason?: TSessionEndReason;
675
+ }
676
+ /** Options for constructing a Session */
677
+ interface ISessionOptions {
678
+ /** Pre-constructed tools to register with the agent */
679
+ tools: IToolWithEventService[];
680
+ /**
681
+ * Applies to a tool added after construction (`Session.addTools`) the wrappers the assembler
682
+ * applied to `tools`, so a late tool is held to the same safety policy as one present from the
683
+ * start. The permission gate is applied by the session itself either way.
684
+ */
685
+ wrapAddedTools?: (tools: IToolWithEventService[]) => IToolWithEventService[];
686
+ /** Pre-constructed AI provider */
687
+ provider: IAIProvider;
688
+ /** Pre-built system message string */
689
+ systemMessage: string;
690
+ /** Terminal I/O for permission prompts */
691
+ terminal: ITerminalOutput;
692
+ /**
693
+ * The session's execution root. REQUIRED — ARCH-010.
694
+ *
695
+ * This field did not exist. `Session` read `process.cwd()` in its constructor and that ambient
696
+ * value became the session's identity everywhere it matters: every hook input,
697
+ * `CLAUDE_PROJECT_DIR`, `PermissionEnforcer`'s root, and the persisted record. Meanwhile the
698
+ * subagent spawn contract has always declared `cwd` REQUIRED — and the in-process runner passed
699
+ * none, because there was no option to pass it to. A session that cannot be told where it runs
700
+ * silently runs wherever the process happens to be, which for a subagent is not its own workspace.
701
+ *
702
+ * A caller that genuinely means "this process's directory" passes `process.cwd()` where a reader
703
+ * can see the decision.
704
+ */
705
+ cwd: string;
706
+ /** Permission and hook configuration */
707
+ permissions?: {
708
+ allow: string[];
709
+ deny: string[];
710
+ ask?: string[];
711
+ };
712
+ /**
713
+ * ARCH-040 Group C (issue #1934): the permission rules BEFORE any preset contributed.
714
+ *
715
+ * Passed rather than inferred. `permissions` above already has the startup preset's patterns baked
716
+ * in, so an enforcer that captured its own base from it on the first live `/preset` would keep the
717
+ * FIRST preset's allowlist forever — the accumulation the replace rule exists to prevent, arriving
718
+ * through the base instead of through the merge. Absent ⇒ no preset contributed and the two are
719
+ * the same.
720
+ */
721
+ presetFreePermissions?: {
722
+ allow: readonly string[];
723
+ deny: readonly string[];
724
+ };
725
+ hooks?: Record<string, unknown>;
726
+ /** Initial permission mode */
727
+ permissionMode?: TPermissionMode;
728
+ /**
729
+ * CORE-025: background/subagent task permission policy. Resolved BEFORE the session-mode gate, so
730
+ * `deny`/`preapproved`/`inherit-allowlist` override even a permissive `permissionMode`. Absent on a normal
731
+ * interactive session (mode gate alone). Set by `createSubagentSession` from the spawn request.
732
+ */
733
+ permissionPolicy?: TBackgroundPermissionPolicy;
734
+ /** CORE-025: the task's OWN allow/deny lists (what `preapproved` consults; distinct from `permissions`). */
735
+ taskPermissions?: {
736
+ allow?: readonly string[];
737
+ deny?: readonly string[];
738
+ };
739
+ /**
740
+ * Injected "ask the user" port (CMD-005): forwarded into the agent config so model-invoked tools
741
+ * (AskUserQuestion) can solicit a structured answer. Absent in headless/automation sessions.
742
+ */
743
+ ask?: IUserInteraction['ask'];
744
+ /** Default trust level — used to derive permissionMode if not given */
745
+ defaultTrustLevel?: 'safe' | 'moderate' | 'full';
746
+ /** Active preset id selected at startup (PRESET-011 runtime state). Defaults to 'default'. */
747
+ activePresetId?: string;
748
+ /**
749
+ * Whether subagent dispatch is allowed for this session (PRESET-016 runtime gate). Defaults to
750
+ * true (current behavior). Only meaningful when the agent runtime was built at assembly.
751
+ */
752
+ enableParallelSubagents?: boolean;
753
+ /** Model name (for context window sizing and Robota config) */
754
+ model?: string;
755
+ /** Provider idle timeout in milliseconds for each model call */
756
+ providerTimeout?: number;
757
+ /** Maximum number of agentic turns per run() call. Undefined = unlimited. */
758
+ maxTurns?: number;
759
+ /** Optional session store for persistence */
760
+ sessionStore?: IInteractiveSessionStore$1;
761
+ /** Override session ID (used when resuming a session to reuse the original ID) */
762
+ sessionId?: string;
763
+ /** Custom permission handler (overrides terminal-based prompts, used by Ink UI) */
764
+ permissionHandler?: TPermissionHandler;
765
+ /** The OS sandbox the shell tools run under, which may let a confined command skip the prompt. */
766
+ commandSandbox?: ICommandSandboxApproval;
767
+ /** Decides for `auto` mode. Without one the session refuses that mode. */
768
+ permissionClassifier?: IPermissionClassifier;
769
+ /** Called when the user selects "allow for project" — persists the tool pattern to project settings. */
770
+ onProjectAllowTool?: (toolName: string) => void;
771
+ /** Callback for text deltas — enables streaming text to the UI in real-time */
772
+ onTextDelta?: (delta: string) => void;
773
+ /** Callback when context window usage is refreshed */
774
+ onContextUpdate?: (state: IContextWindowState) => void;
775
+ /** Custom prompt-for-approval function (injected from CLI) */
776
+ promptForApproval?: (terminal: ITerminalOutput, toolName: string, toolArgs: TToolArgs) => Promise<TPermissionResult>;
777
+ /** Callback when a tool starts or finishes execution — enables real-time tool display in UI */
778
+ onToolExecution?: (event: {
779
+ type: 'start' | 'end';
780
+ toolName: string;
781
+ toolArgs?: TToolArgs;
782
+ success?: boolean;
783
+ denied?: boolean;
784
+ toolResultData?: string;
785
+ executionId?: string;
786
+ }) => void;
787
+ /** Callback when context is compacted */
788
+ onCompact?: (summary: string) => void;
789
+ /** Callback with structured compaction metadata */
790
+ onCompactEvent?: (event: ICompactEvent) => void;
791
+ /** Instructions to include in the compaction prompt (e.g. from project context files) */
792
+ compactInstructions?: string;
793
+ /**
794
+ * Replaces the base instruction template of the compaction summarization prompt
795
+ * (default: `DEFAULT_COMPACTION_PROMPT`, a domain-neutral template). Lets the consuming
796
+ * layer own the compaction prompt wording entirely.
797
+ */
798
+ compactionBasePrompt?: string;
799
+ /**
800
+ * Concrete remediation wording for the core's hard-capacity notice, forwarded to the Robota
801
+ * agent config as `IAgentConfig.contextCapacityHint`. The zero-dependency core emits a
802
+ * product-neutral default; a surface tier that owns a real remediation command (e.g. a
803
+ * `/compact` slash command) injects its own actionable hint here. Absent ⇒ the neutral
804
+ * `DEFAULT_CONTEXT_CAPACITY_HINT` applies.
805
+ */
806
+ contextCapacityHint?: string;
807
+ /** Override context max tokens (otherwise derived from model name) */
808
+ contextMaxTokens?: number;
809
+ /** Auto-compact threshold as a 0-1 fraction. Set false to disable automatic compaction. */
810
+ autoCompactThreshold?: TAutoCompactThreshold;
811
+ /** Session logger — injected for pluggable session event logging. */
812
+ sessionLogger?: ISessionLogger;
813
+ /** Host-projected transcript path for hook compatibility; never inferred from a record store. */
814
+ transcriptPath?: string;
815
+ /** Additional hook type executors (e.g. prompt, agent) beyond the core defaults. */
816
+ hookTypeExecutors?: IHookTypeExecutor[];
817
+ /** Name reported to the Robota agent config. Defaults to 'agent' if not provided. */
818
+ agentName?: string;
819
+ /**
820
+ * Request structured output from the provider for this session. Issue #2056: the agent config's
821
+ * own shape (`json_schema` included), so a schema request reaches the provider intact.
822
+ */
823
+ responseFormat?: IResponseFormatConfig;
824
+ /**
825
+ * Reasoning-effort selection threaded to the Robota agent config and provider boundary.
826
+ * When unset, Core preserves provider-default selection as `auto`.
827
+ */
828
+ effort?: TModelEffortSelection;
829
+ /**
830
+ * ARCH-040: sampling temperature and output cap, threaded to the agent config at CONSTRUCTION.
831
+ *
832
+ * The live `/preset` path has always applied both through `applyModelOptions`, and startup applied
833
+ * neither — so one session held two answers for the same preset depending on WHEN it was chosen,
834
+ * which is what `effort` did before ARCH-013 stage 1 fixed it. `maxOutputTokens` maps to the
835
+ * agent's `maxTokens` channel, the same name `applyModelOptions` already writes.
836
+ */
837
+ temperature?: number;
838
+ maxOutputTokens?: number;
839
+ }
840
+ /**
841
+ * The per-TURN options a caller may attach to one `Session.run()`.
842
+ *
843
+ * Both are optional and independent, and both are absent on the dominant path, so they are spread
844
+ * as a group rather than tested one at a time at each call site.
845
+ */
846
+ interface ISessionRunOptions {
847
+ /** Cancellation belongs to this submission and remains linked until execution settles. */
848
+ signal?: AbortSignal;
849
+ /**
850
+ * SELFHOST-008 P3: a transient system-role block for THIS turn's provider request only. Never
851
+ * written to the conversation store.
852
+ */
853
+ ephemeralSystemContext?: string;
854
+ /**
855
+ * PEER-007 (issue #1915): display attribution for the stored user message — who drove this turn.
856
+ * Never an authorization input (issue #1809).
857
+ */
858
+ driverId?: string;
859
+ /** Run-scoped model tool directive; 'none' remains in force for this turn only. */
860
+ toolChoice?: TToolChoice;
861
+ /**
862
+ * A message from another agent session started this turn. The reply to that session exists only
863
+ * here, and the provider's hosted tools are withheld because no permission step can decide them;
864
+ * every other call is decided by the session's ordinary permissions.
865
+ */
866
+ peerTurn?: boolean;
867
+ /** Host-owned trusted trace context for this turn's provider calls (agent-core `IRunOptions`). */
868
+ traceContext?: IRunTraceContext;
869
+ }
870
+ interface IProviderCallTraceObservation {
871
+ readonly callId?: string;
872
+ readonly round: number;
873
+ readonly startedAt: string;
874
+ readonly endedAt: string;
875
+ readonly outcome: 'success' | 'failure' | 'interrupted';
876
+ readonly disposition?: 'invoked' | 'cache-hit' | 'preflight-refused';
877
+ readonly providerId?: string;
878
+ readonly modelId?: string;
879
+ readonly usageProvenance?: 'complete' | 'partial' | 'absent';
880
+ readonly promptTokens?: number;
881
+ readonly completionTokens?: number;
882
+ readonly totalTokens?: number;
883
+ /** Present only for an invoked call whose adapter attested one; never fabricated. */
884
+ readonly providerRequestId?: string;
885
+ }
886
+ //#endregion
887
+ //#region src/session.d.ts
888
+ /** Wraps a Robota agent with project context, permission state, and optional persistence. */
889
+ declare class Session extends SessionBase {
890
+ protected readonly agent: Robota;
891
+ /**
892
+ * SELFHOST-004: session-owned observable event bus. Injected into the agent so tools (incl. the
893
+ * `FunctionTool` span-completion emit) publish here; the interactive turn subscribes to it to
894
+ * project per-operation spans onto session history. Exposed read-only via {@link getEventService}.
895
+ */
896
+ protected readonly eventService: IEventService;
897
+ protected readonly permissionEnforcer: PermissionEnforcer;
898
+ protected readonly contextTracker: ContextWindowTracker;
899
+ protected permissionMode: TPermissionMode;
900
+ protected activePresetId: string;
901
+ protected parallelSubagentsEnabled: boolean;
902
+ protected readonly sessionId: string;
903
+ protected aiProvider: IAIProvider;
904
+ protected readonly toolSchemas: IToolSchema[];
905
+ protected model: string;
906
+ protected systemMessage: string;
907
+ protected messageCount: number;
908
+ private readonly terminal;
909
+ private readonly sessionStore?;
910
+ private readonly hooks?;
911
+ private readonly hookTypeExecutors?;
912
+ private readonly onTextDeltaCallback?;
913
+ private readonly onContextUpdateCallback?;
914
+ private readonly onToolExecutionCallback?;
915
+ private readonly onCompactCallback?;
916
+ private readonly onCompactEventCallback?;
917
+ private readonly sessionLogger?;
918
+ private readonly maxTurns?;
919
+ private readonly compactionOrchestrator;
920
+ private readonly runtimeTools;
921
+ private readonly wrapAddedTools;
922
+ /** Tools added while a turn ran, applied when the next one starts. */
923
+ private readonly pendingTools;
924
+ /** The last tool change; the next one waits for it. */
925
+ private toolChange;
926
+ private shuttingDown;
927
+ private shutdownPromise;
928
+ /** Stdout collected from SessionStart hooks, injected on first run(). */
929
+ private sessionStartStdout;
930
+ /** Absolute path to the session transcript file, if file-backed storage is active. */
931
+ private readonly transcriptPath;
932
+ constructor(options: ISessionOptions);
933
+ /**
934
+ * @param options.ephemeralSystemContext SELFHOST-008 P3 — a transient system-role block included in this
935
+ * turn's model call only, never persisted to history (thin pass-through to agent-core `IRunOptions`).
936
+ * REJECTS with `SessionBusyError` if a turn is in flight — RUNTIME-003; see `turn-claim.ts`.
937
+ */
938
+ run(message: string, rawInput?: string, options?: ISessionRunOptions): Promise<string>;
939
+ /**
940
+ * Make tools available from the next turn on — for a capability that became usable mid-session,
941
+ * such as an MCP server connected after its sign-in. Each goes through the same wrappers and
942
+ * permission gate as a tool present from the start. A tool whose name the session already has, or
943
+ * has queued, is left out rather than replacing the one the conversation has been using.
944
+ *
945
+ * The tool list is part of what a provider caches a prompt by, and a turn's rounds must all see
946
+ * the same list: while a turn runs, the tools wait and are applied when the next turn starts;
947
+ * otherwise they are applied now. Calls are serialized, so two concurrent ones both land.
948
+ * Resolves to the names that will be offered.
949
+ */
950
+ addTools(tools: readonly IToolWithEventService[]): Promise<readonly string[]>;
951
+ /** Runs `change` after every tool change before it, so each reads the list the last one wrote. */
952
+ private serializeToolChange;
953
+ /** Registers the queued tools with the agent. Only ever called inside `serializeToolChange`. */
954
+ private applyPendingTools;
955
+ listRuntimeTools(): Promise<IToolSchema[]>;
956
+ invokeRuntimeTool(name: string, parameters: TToolParameters, options?: {
957
+ signal?: AbortSignal;
958
+ }): Promise<IToolExecutionResult>;
959
+ /**
960
+ * SELFHOST-004: the session-owned observable event bus the agent's tools publish to. The interactive
961
+ * turn subscribes to it to collect span-completion events and project them onto session history.
962
+ */
963
+ getEventService(): IEventService;
964
+ private log;
965
+ private persistSessionInternal;
966
+ /**
967
+ * Gracefully end the session and fire SessionEnd hooks once — **best-effort** (CORE-013
968
+ * disposal convention): never rejects, so `void session.shutdown()` cannot become an
969
+ * unhandled rejection. Step failures are recorded to the session log and remaining steps
970
+ * still run.
971
+ */
972
+ shutdown(options?: ISessionShutdownOptions): Promise<void>;
973
+ swapProvider(newProvider: IAIProvider, model: string): void;
974
+ compact(instructions?: string, trigger?: TCompactTrigger, signal?: AbortSignal): Promise<void>;
975
+ /** `hookTraceEnv` reaches PreCompact only for a compaction inside a prompt (see `executeRun`). */
976
+ private compactWith;
977
+ private buildRunContext;
978
+ }
979
+ //#endregion
980
+ //#region src/consent-scope.d.ts
981
+ /** The permission pattern a "don't ask again" answer for this invocation grants. */
982
+ declare function consentScopeFor(toolName: string, toolArgs: TToolArgs): string;
983
+ //#endregion
984
+ //#region src/compaction-orchestrator.d.ts
985
+ /**
986
+ * Thrown when a compaction summary is invalid (non-string or empty provider content).
987
+ * Conversation history is append-only source data — callers must not clear or replace
988
+ * it when this is thrown (see SPEC § Compaction Failure Contract).
989
+ */
990
+ declare class CompactionError extends Error {
991
+ constructor(message: string);
992
+ }
993
+ /**
994
+ * Default base template for the compaction summarization prompt — the one model-facing
995
+ * prompt surface this package owns (declared in SPEC § Boundaries). Intentionally
996
+ * domain-neutral: it must not assume a software-development conversation. Replaceable
997
+ * wholesale via {@link ICompactionOptions.basePrompt}.
998
+ */
999
+ declare const DEFAULT_COMPACTION_PROMPT: string;
1000
+ interface ICompactionOptions {
1001
+ sessionId: string;
1002
+ cwd: string;
1003
+ model: string;
1004
+ hooks?: Record<string, unknown>;
1005
+ compactInstructions?: string;
1006
+ /**
1007
+ * Replaces the entire base instruction template of the compaction prompt
1008
+ * (default: {@link DEFAULT_COMPACTION_PROMPT}). Focus instructions and the
1009
+ * formatted conversation are appended after it.
1010
+ */
1011
+ basePrompt?: string;
1012
+ /** Additional hook type executors (e.g. prompt, agent) beyond the core defaults. */
1013
+ hookTypeExecutors?: IHookTypeExecutor[];
1014
+ }
1015
+ declare class CompactionOrchestrator {
1016
+ private readonly sessionId;
1017
+ private readonly cwd;
1018
+ private readonly model;
1019
+ private readonly hooks?;
1020
+ private readonly compactInstructions?;
1021
+ private readonly basePrompt?;
1022
+ private readonly hookTypeExecutors?;
1023
+ constructor(options: ICompactionOptions);
1024
+ /**
1025
+ * Run compaction — summarize the conversation to free context space.
1026
+ * @param provider - The AI provider to use for summarization
1027
+ * @param history - The messages to summarise. Must not be empty: whether there is anything worth
1028
+ * compacting is the caller's judgement, made before it commits to replacing the conversation
1029
+ * (CORE-031).
1030
+ * @param instructions - Optional focus instructions for the summary
1031
+ * @param signal - The turn's cancellation signal (RUNTIME-004). Checked before the provider call
1032
+ * and again after it: an abort throws rather than returning, so the caller's existing
1033
+ * leave-history-untouched path covers a cancel as well as a failure.
1034
+ * @param hookTraceEnv - The prompt's trace for PreCompact's command hooks, only inside a prompt
1035
+ * @returns The generated summary string (always a non-empty string)
1036
+ * @throws {CompactionError} when `history` is empty, or when the provider returns a non-string or
1037
+ * empty summary — callers must leave the conversation history untouched in every such case
1038
+ */
1039
+ compact(provider: IAIProvider, history: TUniversalMessage[], instructions?: string, signal?: AbortSignal, trigger?: TCompactTrigger, hookTraceEnv?: ISubprocessTraceEnv): Promise<string>;
1040
+ /** Build the compaction prompt from conversation history */
1041
+ private buildCompactionPrompt;
1042
+ }
1043
+ //#endregion
1044
+ //#region src/conversation-transcript.d.ts
1045
+ /**
1046
+ * Render each message as one entry, in order. An entry is one line, except an assistant message
1047
+ * with several tool calls, which is one line per call. One entry per message so a caller that must
1048
+ * drop the oldest part of a long conversation can drop whole messages.
1049
+ */
1050
+ declare function formatConversationEntries(history: readonly TUniversalMessage[]): string[];
1051
+ //#endregion
1052
+ //#region src/session-artifact.d.ts
1053
+ /**
1054
+ * TRANS-006: the envelope type and its version constant are the codec's (`session-record-codec/`).
1055
+ * They used to be declared here as `ISessionArtifact` and a local constant, which was a second name
1056
+ * and a second number for one shape — the envelope the codec decodes and the envelope this module
1057
+ * writes were always the same `{ schemaVersion, record }`. Nothing on disk changed when they were
1058
+ * unified, because there was nothing to change.
1059
+ */
1060
+ interface ISerializeSessionArtifactOptions {
1061
+ /**
1062
+ * SHARE-PATH ONLY. An app-supplied, policy-free transform applied to the record before serialization — the app
1063
+ * decides which trust-boundary fields to strip (composing the opt-in `scrubSensitiveKeys`). Omit for the
1064
+ * full-fidelity local round-trip.
1065
+ */
1066
+ redact?: (record: IInteractiveSessionRecord$1) => IInteractiveSessionRecord$1;
1067
+ }
1068
+ /**
1069
+ * Serialize a session record into a portable, versioned artifact. With no `redact`, this is the full-fidelity
1070
+ * round-trip form; with `redact`, the caller's transform is applied first (the share path).
1071
+ */
1072
+ declare function serializeSessionArtifact(record: IInteractiveSessionRecord$1, options?: ISerializeSessionArtifactOptions): string;
1073
+ /**
1074
+ * Parse a session artifact back into an `IInteractiveSessionRecord`, rejecting an artifact whose schema version this build
1075
+ * does not understand (so an incompatible artifact is never silently mis-imported).
1076
+ */
1077
+ declare function deserializeSessionArtifact(bytes: string): IInteractiveSessionRecord$1;
1078
+ //#endregion
1079
+ //#region src/scrub-sensitive.d.ts
1080
+ /**
1081
+ * SELFHOST-014 — the single source of the sensitive-key redaction (SSOT).
1082
+ *
1083
+ * The recursive secret-key scrub used to live privately in `session-logger.ts`, logging-coupled and not exported.
1084
+ * It is extracted here so exactly ONE definition of "which keys are sensitive" exists, consumed by BOTH the file
1085
+ * session logger (persistence-time redaction) and the SELFHOST-014 share-artifact `redact` transform (an opt-in
1086
+ * the app composes). This utility is a pure, mechanism-level key scrub only: it carries NO field/trust-boundary
1087
+ * policy (which of `cwd`/`sandboxSnapshotId`/… to strip is an app decision) and is NEVER forced into the
1088
+ * full-fidelity local round-trip.
1089
+ */
1090
+ /** Values a scrub can walk (mirrors the logger's log-value shape; avoids `any`/`unknown`). */
1091
+ type TScrubbableValue = string | number | boolean | object | null | undefined;
1092
+ /**
1093
+ * Keys whose VALUE is a secret and must be redacted before persistence or sharing:
1094
+ * `apiKey`/`authorization`/`accessToken`/`refreshToken`/`secret`/`password`/`xApiKey` (case/`-`/`_`-insensitive).
1095
+ */
1096
+ declare const SENSITIVE_KEY_PATTERN: RegExp;
1097
+ /** True when a key's value should be redacted. The one predicate both the logger and the artifact scrub use. */
1098
+ declare function isSensitiveKey(key: string): boolean;
1099
+ /**
1100
+ * Deep-copy `value`, replacing any value whose KEY is sensitive with `redactedValue` (default `[REDACTED]`).
1101
+ * Pure — does not mutate the input. Returns the same shape (`T`).
1102
+ */
1103
+ declare function scrubSensitiveKeys<T>(value: T, redactedValue?: string): T;
1104
+ //#endregion
1105
+ //#region src/session-log-events.d.ts
1106
+ /**
1107
+ * INFRA-017: typed contract for session-log event names + replay keys (SSOT).
1108
+ *
1109
+ * `FileSessionLogger` owns the `{ schemaVersion, timestamp, sessionId, event }` JSONL envelope.
1110
+ * This module owns its version and event vocabulary; `session-log-codec` validates each payload
1111
+ * before replay or completeness checks.
1112
+ *
1113
+ * The **replay substrate** is the provider/tool execution layer, keyed deterministically:
1114
+ * a `provider_request` (executionId + round) is answered by its recorded
1115
+ * `provider_native_raw_payload` / `provider_response_normalized`; a `tool_execution_request`
1116
+ * (executionId + toolCallId) by its `tool_execution_result`. `validateSessionReplayLogEntries`
1117
+ * proves a log carries all of these (i.e. is replay-complete).
1118
+ */
1119
+ /** Supported persisted session-log envelope version. */
1120
+ declare const SESSION_LOG_SCHEMA_VERSION = 1;
1121
+ /** Canonical session-log event names. */
1122
+ declare const SESSION_LOG_EVENT: {
1123
+ readonly sessionInit: "session_init";
1124
+ readonly sessionShutdown: "session_shutdown";
1125
+ readonly sessionShutdownStepError: "session_shutdown_step_error";
1126
+ readonly context: "context";
1127
+ readonly contextCompact: "context_compact";
1128
+ readonly error: "error";
1129
+ readonly historyMutation: "history_mutation";
1130
+ readonly providerRequest: "provider_request";
1131
+ readonly providerNativeRawPayload: "provider_native_raw_payload";
1132
+ readonly providerStreamRawDelta: "provider_stream_raw_delta";
1133
+ readonly providerResponseRaw: "provider_response_raw";
1134
+ readonly providerResponseNormalized: "provider_response_normalized";
1135
+ /**
1136
+ * CORE-043: which transport actually carried a structured-output schema on this request, and
1137
+ * whether the schema had to be stated in the prompt instead. Diagnostic, not replay substrate — a
1138
+ * replay answers a `provider_request` from its recorded response, and this line explains why that
1139
+ * request looked the way it did.
1140
+ */
1141
+ readonly structuredOutputTransport: "structured_output_transport";
1142
+ /**
1143
+ * A request moved to another model because the one it was on failed. Diagnostic, not replay
1144
+ * substrate: the `provider_request` announced for the new model is what a replay answers.
1145
+ */
1146
+ readonly providerFallback: "provider_fallback";
1147
+ readonly assistantMessageCommitted: "assistant_message_committed";
1148
+ readonly toolExecutionRequest: "tool_execution_request";
1149
+ readonly toolExecutionResult: "tool_execution_result";
1150
+ readonly toolBatchStarted: "tool_batch_started";
1151
+ readonly toolMessageCommitted: "tool_message_committed";
1152
+ readonly backgroundTaskEvent: "background_task_event";
1153
+ readonly backgroundJobGroupEvent: "background_job_group_event";
1154
+ readonly memoryEvent: "memory_event";
1155
+ readonly user: "user";
1156
+ readonly preRun: "pre_run";
1157
+ readonly textDelta: "text_delta";
1158
+ readonly assistant: "assistant";
1159
+ readonly toolCall: "tool_call";
1160
+ readonly toolResult: "tool_result";
1161
+ readonly toolBlocked: "tool_blocked";
1162
+ readonly toolDenied: "tool_denied";
1163
+ readonly serverTool: "server_tool";
1164
+ };
1165
+ type TSessionLogEventName = (typeof SESSION_LOG_EVENT)[keyof typeof SESSION_LOG_EVENT];
1166
+ /** Common envelope written for every line by `FileSessionLogger`. */
1167
+ interface ISessionLogLine {
1168
+ readonly timestamp: string;
1169
+ readonly sessionId: string;
1170
+ readonly event: string;
1171
+ readonly [key: string]: unknown;
1172
+ }
1173
+ /** Replay correlation key for a provider call. */
1174
+ interface IProviderEventKey {
1175
+ readonly executionId: string;
1176
+ readonly round: number;
1177
+ }
1178
+ /** Replay correlation key for a tool execution. */
1179
+ interface IToolEventKey {
1180
+ readonly executionId: string;
1181
+ readonly toolCallId: string;
1182
+ }
1183
+ /** Narrow a raw log line to a specific event name. */
1184
+ declare function isSessionLogEvent<TName extends TSessionLogEventName>(line: ISessionLogLine, name: TName): line is ISessionLogLine & {
1185
+ event: TName;
1186
+ };
1187
+ //#endregion
1188
+ //#region src/session-log-entry-types.d.ts
1189
+ interface ISessionLogEntry extends Record<string, TUniversalValue> {
1190
+ schemaVersion?: number;
1191
+ timestamp: string;
1192
+ sessionId: string;
1193
+ event: string;
1194
+ }
1195
+ //#endregion
1196
+ //#region src/session-log-codec/index.d.ts
1197
+ type TSessionLogDecodeErrorCode = 'INVALID_JSON' | 'INVALID_EVENT' | 'UNSUPPORTED_VERSION';
1198
+ type TDecodedEvent<TName extends TSessionLogEventName> = ISessionLogEntry & {
1199
+ event: TName;
1200
+ };
1201
+ type TDecodedSessionLogEntry = (TDecodedEvent<'provider_response_normalized'> & {
1202
+ response: TUniversalMessage;
1203
+ }) | (TDecodedEvent<'history_mutation'> & {
1204
+ mutation: 'append_message';
1205
+ message: TUniversalMessage;
1206
+ }) | (TDecodedEvent<'assistant_message_committed'> & {
1207
+ message: TUniversalMessage | string;
1208
+ }) | (TDecodedEvent<'tool_message_committed'> & {
1209
+ message: TUniversalMessage;
1210
+ }) | (TDecodedEvent<'background_task_event'> & {
1211
+ backgroundEvent?: object;
1212
+ data?: object;
1213
+ }) | (TDecodedEvent<'background_job_group_event'> & {
1214
+ backgroundJobGroupEvent?: object;
1215
+ data?: object;
1216
+ }) | (TDecodedEvent<'memory_event'> & {
1217
+ memoryEvent?: object;
1218
+ data?: object;
1219
+ }) | TDecodedEvent<Exclude<TSessionLogEventName, 'provider_response_normalized' | 'history_mutation' | 'assistant_message_committed' | 'tool_message_committed' | 'background_task_event' | 'background_job_group_event' | 'memory_event'>>;
1220
+ declare class SessionLogDecodeError extends Error {
1221
+ readonly code: TSessionLogDecodeErrorCode;
1222
+ readonly issues: readonly ISessionRecordDecodeIssue[];
1223
+ readonly schemaVersion?: number;
1224
+ constructor(code: TSessionLogDecodeErrorCode, issues: readonly ISessionRecordDecodeIssue[], options?: {
1225
+ schemaVersion?: number;
1226
+ cause?: unknown;
1227
+ });
1228
+ }
1229
+ /** Validate the full input before returning any entry to replay consumers. */
1230
+ declare function decodeSessionLogEntries(entries: unknown, options?: {
1231
+ lineNumbers?: readonly number[];
1232
+ }): TDecodedSessionLogEntry[];
1233
+ //#endregion
1234
+ //#region src/external-payload-source-types.d.ts
1235
+ /**
1236
+ * Leaf type module for {@link IExternalPayloadSource} and {@link ISessionLogSource}.
1237
+ *
1238
+ * Split out of `session-log-sources.ts` so `external-payload-resolution-contracts.ts` can depend
1239
+ * on {@link IExternalPayloadSource} without importing back from `session-log-sources.ts`, which
1240
+ * previously created an import cycle between the two.
1241
+ */
1242
+ /** Workspace-neutral byte source for relative external-payload references. */
1243
+ interface IExternalPayloadSource {
1244
+ readBytes(relativePath: string, maxBytes: number): Uint8Array | undefined;
1245
+ }
1246
+ /** Workspace-neutral source for one session-log document and its optional payload source. */
1247
+ interface ISessionLogSource {
1248
+ readText(): string | undefined;
1249
+ readonly externalPayloadSource?: IExternalPayloadSource;
1250
+ }
1251
+ //#endregion
1252
+ //#region src/session-log-sources.d.ts
1253
+ /** Explicit host-filesystem adapter. A file path is never accepted by the neutral parser itself. */
1254
+ declare class NodeExternalPayloadSource implements IExternalPayloadSource {
1255
+ private readonly baseDirectory;
1256
+ constructor(baseDirectory: string);
1257
+ readBytes(relativePath: string, maxBytes: number): Uint8Array | undefined;
1258
+ }
1259
+ /** Explicit host-filesystem adapter for a JSONL session log. */
1260
+ declare class NodeSessionLogSource implements ISessionLogSource {
1261
+ private readonly logFile;
1262
+ readonly externalPayloadSource: IExternalPayloadSource;
1263
+ constructor(logFile: string);
1264
+ readText(): string | undefined;
1265
+ }
1266
+ //#endregion
1267
+ //#region src/tool-result-spill-store.d.ts
1268
+ type TToolResultSpillErrorCode = 'invalid-options' | 'unsafe-root' | 'write-failed' | 'missing' | 'expired' | 'invalid-reference' | 'read-failed' | 'cleanup-failed' | 'closed';
1269
+ declare class ToolResultSpillError extends Error {
1270
+ readonly code: TToolResultSpillErrorCode;
1271
+ constructor(code: TToolResultSpillErrorCode);
1272
+ }
1273
+ interface INodeToolResultSpillStoreOptions {
1274
+ /** Host-owned parent; defaults to the operating-system temporary directory. */
1275
+ readonly parentDirectory?: string;
1276
+ readonly retentionMs?: number;
1277
+ readonly now?: () => number;
1278
+ /** Receives a fixed, payload-free reason if an idle expiry timer cannot delete a file. */
1279
+ readonly onCleanupFailure?: (reason: 'cleanup-failed') => void;
1280
+ }
1281
+ /** Node host implementation of core's opaque, session-lifetime spill port. */
1282
+ declare class NodeToolResultSpillStore implements IToolResultSpillStore {
1283
+ private readonly directory;
1284
+ private readonly retentionMs;
1285
+ private readonly now;
1286
+ private readonly onCleanupFailure?;
1287
+ private readonly entries;
1288
+ private expiryTimer?;
1289
+ private closed;
1290
+ constructor(options?: INodeToolResultSpillStoreOptions);
1291
+ private assertRoot;
1292
+ private scheduleExpiry;
1293
+ write(content: string): Promise<{
1294
+ readonly reference: string;
1295
+ }>;
1296
+ read(reference: string): Promise<string>;
1297
+ private removeEntry;
1298
+ cleanupExpired(): Promise<void>;
1299
+ shutdown(): Promise<void>;
1300
+ }
1301
+ //#endregion
1302
+ //#region src/prompt-history-file.d.ts
1303
+ /** 64 KiB: a few hundred prompts per block — enough for the first frame, small enough to yield often. */
1304
+ declare const DEFAULT_PROMPT_HISTORY_BLOCK_BYTES: number;
1305
+ interface INodePromptHistoryFileOptions {
1306
+ /** An ancestor of the file the host also owns, tightened along with the directory (SEC-020). */
1307
+ readonly ownedRoot?: string;
1308
+ /** Test seam: the read block size in bytes. */
1309
+ readonly blockBytes?: number;
1310
+ }
1311
+ /** One line → an entry, or `undefined` when the line is not a well-formed entry. */
1312
+ declare function parsePromptHistoryLine(line: string): IPromptHistoryEntry | undefined;
1313
+ declare class NodePromptHistoryFile implements IPromptHistoryWriter, IPromptHistorySource {
1314
+ private readonly path;
1315
+ private readonly blockBytes;
1316
+ private readonly ownedRoot;
1317
+ constructor(path: string, options?: INodePromptHistoryFileOptions);
1318
+ append(entry: IPromptHistoryEntry): void;
1319
+ /**
1320
+ * Newest-first blocks. Only a missing file is the empty state (a fresh install, or history off);
1321
+ * any other open or read failure is thrown so the surface renders it instead of an empty list.
1322
+ */
1323
+ read(options: IPromptHistoryReadOptions): AsyncIterable<IPromptHistoryBlock>;
1324
+ }
1325
+ //#endregion
1326
+ //#region src/external-payload-resolution-contracts.d.ts
1327
+ type TSessionLogPayloadResolutionErrorCode = 'INVALID_LIMIT' | 'INVALID_REFERENCE' | 'UNRESOLVED_REFERENCE' | 'OUTSIDE_ROOT' | 'PAYLOAD_NOT_FOUND' | 'STABLE_PAYLOAD_READ_UNAVAILABLE' | 'PAYLOAD_UNREADABLE' | 'BYTE_LENGTH_MISMATCH' | 'SHA256_MISMATCH' | 'INVALID_JSON' | 'MAX_DEPTH_EXCEEDED' | 'MAX_TOTAL_BYTES_EXCEEDED' | 'CIRCULAR_REFERENCE';
1328
+ interface ISessionLogPayloadResolutionOptions {
1329
+ readonly source?: IExternalPayloadSource;
1330
+ readonly maxDepth?: number;
1331
+ readonly maxTotalBytes?: number;
1332
+ }
1333
+ interface ISessionLogPayloadResolutionErrorMetadata {
1334
+ readonly relativePath?: string;
1335
+ readonly resolvedPath?: string;
1336
+ readonly depth?: number;
1337
+ readonly expected?: string | number;
1338
+ readonly actual?: string | number;
1339
+ }
1340
+ declare class SessionLogPayloadResolutionError extends Error {
1341
+ readonly code: TSessionLogPayloadResolutionErrorCode;
1342
+ readonly metadata: Readonly<ISessionLogPayloadResolutionErrorMetadata>;
1343
+ constructor(code: TSessionLogPayloadResolutionErrorCode, message: string, metadata?: ISessionLogPayloadResolutionErrorMetadata, cause?: unknown);
1344
+ }
1345
+ //#endregion
1346
+ //#region src/external-payload-resolver.d.ts
1347
+ /**
1348
+ * Hydrate every external JSON payload reference in one value using one aggregate budget.
1349
+ * The input is treated as untrusted and the returned graph contains only JSON-compatible values.
1350
+ */
1351
+ declare function resolveSessionLogExternalPayloads(value: unknown, options: ISessionLogPayloadResolutionOptions): unknown;
1352
+ //#endregion
1353
+ //#region src/session-log-validation.d.ts
1354
+ interface ISessionReplayValidationIssue {
1355
+ code: 'PROVIDER_RESPONSE_RAW_MISSING' | 'PROVIDER_NATIVE_RAW_PAYLOAD_MISSING' | 'PROVIDER_RESPONSE_NORMALIZED_MISSING' | 'TOOL_RESULT_MISSING' | 'PAYLOAD_REFERENCE_INVALID' | 'UNRESOLVED_REPLAY_PAYLOAD';
1356
+ message: string;
1357
+ eventIndex?: number;
1358
+ executionId?: string;
1359
+ round?: number;
1360
+ toolCallId?: string;
1361
+ }
1362
+ interface ISessionReplayValidationResult {
1363
+ ok: boolean;
1364
+ issues: ISessionReplayValidationIssue[];
1365
+ }
1366
+ declare function validateSessionReplayLogEntries(entries: readonly ISessionLogEntry[]): ISessionReplayValidationResult;
1367
+ //#endregion
1368
+ //#region src/session-log-replay.d.ts
1369
+ interface ISessionReplayRecord {
1370
+ sessionId: string | undefined;
1371
+ cwd: string | undefined;
1372
+ createdAt: string | undefined;
1373
+ updatedAt: string | undefined;
1374
+ messages: TUniversalMessage[];
1375
+ history: IHistoryEntry[];
1376
+ backgroundTaskEvents: object[];
1377
+ backgroundJobGroupEvents: object[];
1378
+ memoryEvents: object[];
1379
+ }
1380
+ type ISessionLogLoadOptions = Omit<ISessionLogPayloadResolutionOptions, 'source'> & {
1381
+ readonly externalPayloadSource?: IExternalPayloadSource;
1382
+ };
1383
+ declare function loadSessionLogEntries(source: ISessionLogSource, options?: ISessionLogLoadOptions): ISessionLogEntry[];
1384
+ declare function replaySessionLogEntries(entries: readonly ISessionLogEntry[]): ISessionReplayRecord;
1385
+ //#endregion
1386
+ //#region src/session-id.d.ts
1387
+ /**
1388
+ * Whether `id` is safe to interpolate into a filesystem path as a single component.
1389
+ *
1390
+ * The pattern admits no `/`, no `\` and no `:`, so the value cannot introduce a path separator or a
1391
+ * Windows drive qualifier; and because it must start with an alphanumeric, it can be neither `.` nor
1392
+ * `..`. With no separator available, an embedded `..` cannot form a traversal component.
1393
+ */
1394
+ declare function isSafeSessionId(id: string): boolean;
1395
+ /** Throw unless `id` is safe to use as a path component. */
1396
+ declare function assertSafeSessionId(id: string): void;
1397
+ //#endregion
1398
+ //#region src/session-store.d.ts
1399
+ /**
1400
+ * Persistent session store backed by individual JSON files.
1401
+ *
1402
+ * Construct with a host-owned `baseDir`; framework project composition uses a separate
1403
+ * authority-backed adapter over the same neutral port.
1404
+ */
1405
+ declare class NodeSessionStore implements IInteractiveSessionStore$1 {
1406
+ private readonly baseDir;
1407
+ private readonly ownedRoot;
1408
+ /**
1409
+ * @param baseDir the directory holding the records.
1410
+ * @param ownedRoot an ancestor of `baseDir` the HOST also owns, tightened along with it (SEC-020).
1411
+ * Optional because this adapter does not interpret its base directory as a trusted root and must
1412
+ * not guess that a parent belongs to the product — which of them do is composition's knowledge.
1413
+ * Omitting it leaves a store root an older version created at whatever mode it was given, which
1414
+ * is what review of PR #2224 found: the leaf was 0700 and the directory above it was not.
1415
+ */
1416
+ constructor(baseDir: string, ownedRoot?: string);
1417
+ /**
1418
+ * Ensure the storage directory exists AND that only its owner can enter it (SEC-020).
1419
+ *
1420
+ * The `existsSync` guard this replaces is the whole defect. It skipped the case that matters: a
1421
+ * directory some earlier version, a shared CI checkout, or another local user left at a wider
1422
+ * mode was adopted as ours with no signal. Measured under umask 022 before this change, a fresh
1423
+ * sessions directory came out 0755 and its records 0644 — and a directory pre-created at 0777
1424
+ * stayed 0777.
1425
+ */
1426
+ private ensureDir;
1427
+ /**
1428
+ * Absolute path to a session's JSON file.
1429
+ *
1430
+ * SEC-006: every public method routes through here, so validating the id at this one point covers
1431
+ * `save` (write), `load` (read), and `delete` (unlink) at once.
1432
+ *
1433
+ * Issue #2240: `assertSafeSessionId` is the guard, and it is sound — no separator, no `.`/`..`
1434
+ * can pass it. But it is a regex reject behind a helper, which static analysis does not model as
1435
+ * a path sanitizer, so `js/path-injection` re-opened on `load` every time this function changed
1436
+ * length. The containment check below is the shape such tools DO recognise: the resolved path
1437
+ * must stay inside the resolved base directory. It is unreachable after the assertion and costs
1438
+ * one `resolve`; it exists so the guard is visible where the sink is, not to replace the guard.
1439
+ */
1440
+ private filePath;
1441
+ /**
1442
+ * Persist a session record to disk atomically (CORE-019).
1443
+ * Creates the storage directory if needed.
1444
+ *
1445
+ * Bytes go to a same-directory temp file first, then move into place with rename —
1446
+ * a crash mid-write can therefore never leave a truncated JSON where the previous
1447
+ * record used to be. Same-directory is load-bearing: cross-device rename is a copy.
1448
+ *
1449
+ * SEC-020: the atomic write now comes from `writeOwnerOnlyFile`, which carries the mode from the
1450
+ * moment the temp file is created. The hand-rolled version here wrote it at the umask's default
1451
+ * and let `rename` carry that mode to the final path, so every record was 0644 — and even setting
1452
+ * the mode after the write would leave a window in which the full transcript was world-readable
1453
+ * on disk.
1454
+ */
1455
+ save(session: IInteractiveSessionRecord$1): void;
1456
+ /**
1457
+ * Load a session by its ID, saying WHICH of the four things happened.
1458
+ *
1459
+ * `undefined` used to answer all four — never saved, damaged, written by a build this one cannot
1460
+ * read, and the read failed — and a caller that meant to preserve fields it does not own then
1461
+ * treated "damaged" as "no prior record" and overwrote the file. The outcome type is what removes
1462
+ * that, by making the caller answer the question it was not asking.
1463
+ */
1464
+ load(id: string): TSessionLoadOutcome;
1465
+ /**
1466
+ * Every session this directory holds, each with what the store concluded about it.
1467
+ *
1468
+ * Unreadable entries are REPORTED rather than skipped. A store that distinguishes four outcomes on
1469
+ * `load` and then hides two of them from the surface a person browses has moved the defect rather
1470
+ * than removed it: the difference a user experiences is between "my session vanished" and "my
1471
+ * session needs a different build".
1472
+ */
1473
+ list(): readonly ISessionListEntry[];
1474
+ /**
1475
+ * The outcome for one directory entry, without letting a bad NAME throw out of `list`.
1476
+ *
1477
+ * `load` validates the id, because an id reaching it is a caller's value and a malformed one is a
1478
+ * bug or an attack (SEC-006). A name read out of the directory is neither: the store did not
1479
+ * choose it, and one file it cannot use as an id must not take the whole listing down with it.
1480
+ * Routing `list` through `load` made exactly that happen — a single `my session.json` in the
1481
+ * sessions directory threw, and the resume picker went with it.
1482
+ *
1483
+ * Reporting it is the same answer `list` gives for every other file it cannot read, which is the
1484
+ * property this work exists to establish.
1485
+ */
1486
+ private outcomeForListedId;
1487
+ /**
1488
+ * Delete a session by its ID.
1489
+ * No-ops silently if the session does not exist.
1490
+ */
1491
+ delete(id: string): void;
1492
+ }
1493
+ //#endregion
1494
+ //#region src/checkpoint-tree.d.ts
1495
+ /**
1496
+ * SELFHOST-007 — neutral checkpoint tree (branching time-travel).
1497
+ *
1498
+ * A pure, I/O-free branch-tree over opaque checkpoint-node ids — git-for-a-session with no file I/O,
1499
+ * no persistence, and no retention/prune policy. It lives beside the storage-neutral persistence
1500
+ * primitive (`SessionStore`/`IInteractiveSessionRecord`): same neutral-mechanism class (opaque payloads, no
1501
+ * product policy). The agent-framework checkpoint store consumes it over the existing one-way
1502
+ * `agent-framework → agent-session` edge; the reverse edge would be a cycle and is forbidden.
1503
+ *
1504
+ * Model: each node is `{ id, parentId }` (root has no `parentId`). Adding a checkpoint appends a child
1505
+ * of the active head. Forking moves the active head to a PAST node so the next append DIVERGES — the
1506
+ * original descendants stay reachable (a sibling branch). A "branch" is a leaf (a node with no
1507
+ * children); `switch` moves the active head to any existing node.
1508
+ */
1509
+ interface ICheckpointNode {
1510
+ id: string;
1511
+ /** Parent checkpoint id; absent for the root. */
1512
+ parentId?: string;
1513
+ }
1514
+ declare class CheckpointTree {
1515
+ private readonly nodes;
1516
+ /** Child adjacency (parentId → child ids in insertion order) for O(1) leaf/branch queries. */
1517
+ private readonly children;
1518
+ private activeId;
1519
+ /**
1520
+ * Build a tree from explicit `{ id, parentId }` edges (e.g. reconstructed from persisted checkpoint
1521
+ * manifests). Nodes may arrive in any order — parents are linked by id. The active head is left at
1522
+ * the given `activeId` (or undefined). This is the delegation entry point a consumer store uses to
1523
+ * answer navigation queries (`listBranches`/`ancestors`) without the tree owning any persistence.
1524
+ */
1525
+ static fromNodes(nodes: ICheckpointNode[], activeId?: string): CheckpointTree;
1526
+ /**
1527
+ * Append a checkpoint as a child of the current active head and make it the new active head.
1528
+ * The first append (no active head) becomes the root. Ids must be unique.
1529
+ */
1530
+ addCheckpoint(id: string): void;
1531
+ /**
1532
+ * Fork from a PAST checkpoint: move the active head to `fromId` so the next `addCheckpoint` diverges
1533
+ * into a sibling branch, leaving `fromId`'s original descendants reachable. Returns `fromId`.
1534
+ */
1535
+ fork(fromId: string): string;
1536
+ /** Move the active head to an existing node (typically a branch leaf). */
1537
+ switch(nodeId: string): void;
1538
+ /** The current active head id (undefined for an empty tree). */
1539
+ activeLeaf(): string | undefined;
1540
+ /** All branch tips (leaf nodes with no children), in insertion order. */
1541
+ listBranches(): string[];
1542
+ /**
1543
+ * The chain from `id` up to (and including) the root — nearest first. Empty if `id` is unknown. Only
1544
+ * REGISTERED nodes are included: a dangling `parentId` (edge to a node not in the tree — possible on
1545
+ * manifest drift/corruption) terminates the walk rather than emitting a phantom id.
1546
+ */
1547
+ ancestors(id: string): string[];
1548
+ /** Whether a checkpoint id exists in the tree. */
1549
+ has(id: string): boolean;
1550
+ /** The number of checkpoints in the tree. */
1551
+ get size(): number;
1552
+ }
1553
+ //#endregion
1554
+ //#region src/session-record-codec/decode-outcome.d.ts
1555
+ /** What a decode of a persisted session record can conclude. */
1556
+ type TSessionRecordDecodeOutcome = {
1557
+ readonly status: 'valid';
1558
+ readonly record: IInteractiveSessionRecord$1;
1559
+ } | {
1560
+ readonly status: 'corrupt';
1561
+ readonly issues: readonly ISessionRecordDecodeIssue[];
1562
+ } | {
1563
+ readonly status: 'unsupported';
1564
+ readonly schemaVersion: number | undefined;
1565
+ };
1566
+ //#endregion
1567
+ //#region src/session-record-codec/record-decoder.d.ts
1568
+ /**
1569
+ * The version of the persisted record envelope this build reads and writes.
1570
+ *
1571
+ * Bump it when the shape changes in a way an older reader would decode WRONGLY rather than not at
1572
+ * all. A reader that meets a version it does not implement reports `unsupported` and stops — it does
1573
+ * not decode the members it recognises, because a partially decoded session is the silent
1574
+ * field-loss this codec replaces.
1575
+ *
1576
+ * ONE concept, not two (issue #2185): the envelope `{ schemaVersion, record }` and the record it
1577
+ * wraps version together — a change to either shape bumps this number, and every consumer of the
1578
+ * envelope reads it: the portable session artifact (`serializeSessionArtifact`) and the session
1579
+ * store (`NodeSessionStore.save`, `WorkspaceSessionStore`). Two constants would let an envelope-only
1580
+ * change reject records that are fine, and the two shapes have never moved apart. The constant is
1581
+ * therefore named for the pair it versions, not for its first consumer: TRANS-006 kept the
1582
+ * incumbent `SESSION_ARTIFACT_SCHEMA_VERSION` (published, written by the producing path) over the
1583
+ * duplicate TRANS-005 introduced, and #2185 renamed it here — prerelease, so no alias.
1584
+ *
1585
+ * Its DECLARATION lives here rather than beside the artifact functions because `session-artifact.ts`
1586
+ * imports this module; declaring it there and importing it back would be a module cycle. The export
1587
+ * from the package barrel is unchanged.
1588
+ */
1589
+ declare const SESSION_RECORD_ENVELOPE_VERSION = 1;
1590
+ /** A persisted record with the version of the shape it was written in. */
1591
+ interface IVersionedInteractiveSessionRecord {
1592
+ schemaVersion: number;
1593
+ record: IInteractiveSessionRecord$1;
1594
+ }
1595
+ /**
1596
+ * Every key the record contract declares.
1597
+ *
1598
+ * Exported so a test can compare it against `keyof IInteractiveSessionRecord`: a member added to the
1599
+ * contract without a branch below then fails that comparison rather than being silently dropped by
1600
+ * a decoder that never heard of it.
1601
+ */
1602
+ declare const INTERACTIVE_SESSION_RECORD_KEYS: readonly string[];
1603
+ /**
1604
+ * Decode a bare persisted record.
1605
+ *
1606
+ * The value is decoded, never cast: what comes back is either a record every member of which was
1607
+ * checked, or the list of every place it failed — not the first place.
1608
+ */
1609
+ declare function decodeInteractiveSessionRecord(value: unknown): TSessionRecordDecodeOutcome;
1610
+ /**
1611
+ * Decode a versioned envelope.
1612
+ *
1613
+ * The version is read BEFORE the record, and a version this build does not implement returns
1614
+ * `unsupported` WITHOUT nested issues: reporting field defects against a shape from another version
1615
+ * describes the reader's expectations, not the data's condition, and a caller cannot act on it.
1616
+ */
1617
+ declare function decodeVersionedInteractiveSessionRecord(value: unknown): TSessionRecordDecodeOutcome;
1618
+ //#endregion
1619
+ export { AUTO_COMPACT_THRESHOLD, AutoModeGate, CONSECUTIVE_BLOCK_LIMIT, CheckpointTree, CompactionError, CompactionOrchestrator, ContextWindowTracker, DEFAULT_COMPACTION_PROMPT, DEFAULT_PROMPT_HISTORY_BLOCK_BYTES, FileSessionLogger, type ICheckpointNode, type IClassifiedCall, type IClassifierVerdict, type ICommandSandboxApproval, type IExternalPayloadReference, type IExternalPayloadSink, type IExternalPayloadSource, type IFileSessionLoggerOptions, type IInteractiveSessionRecord, type IInteractiveSessionStore, INTERACTIVE_SESSION_RECORD_KEYS, type INodePromptHistoryFileOptions, type INodeToolResultSpillStoreOptions, type IPermissionClassifier, type IPermissionDenial, type IProviderCallTraceObservation, type IProviderEventKey, type ISerializeSessionArtifactOptions, type ISessionLogEntry, type ISessionLogLine, type ISessionLogLoadOptions, type ISessionLogPayloadResolutionErrorMetadata, type ISessionLogPayloadResolutionOptions, type ISessionLogSink, type ISessionLogSource, type ISessionLogger, type ISessionOptions, type ISessionRecord, type ISessionReplayRecord, type ISessionReplayValidationIssue, type ISessionReplayValidationResult, type ISessionRunOptions, type ISessionShutdownOptions, type ISessionStore, type ISpinner, type ITerminalOutput, type IToolEventKey, type IVersionedInteractiveSessionRecord, NodeExternalPayloadSource, NodePromptHistoryFile, NodeSessionLogSink, NodeSessionLogSource, NodeSessionStore, NodeToolResultSpillStore, PermissionEnforcer, SENSITIVE_KEY_PATTERN, SESSION_LOG_EVENT, SESSION_LOG_SCHEMA_VERSION, SESSION_RECORD_ENVELOPE_VERSION, Session, SessionBusyError, SessionLogDecodeError, SessionLogPayloadResolutionError, SilentSessionLogger, type TAutoCompactThreshold, type TAutoModeJudgement, type TDecodedSessionLogEntry, TOTAL_BLOCK_LIMIT, type TPermissionDenialReason, type TPermissionHandler, type TPermissionResult, type TScrubbableValue, type TSessionLogData, type TSessionLogDecodeErrorCode, type TSessionLogEventName, type TSessionLogPayloadResolutionErrorCode, type TSessionLogValue, type TSessionRecordDecodeOutcome, type TToolResultSpillErrorCode, ToolResultSpillError, TurnClaim, assertSafeSessionId, consentScopeFor, createSessionLogExternalPayloadReference, decodeInteractiveSessionRecord, decodeSessionLogEntries, decodeVersionedInteractiveSessionRecord, deserializeSessionArtifact, formatConversationEntries, isSafeSessionId, isSensitiveKey, isSessionLogEvent, loadSessionLogEntries, parsePromptHistoryLine, replaySessionLogEntries, resolveSessionLogExternalPayloads, scrubSensitiveKeys, serializeSessionArtifact, validateSessionReplayLogEntries };
1620
+ //# sourceMappingURL=index.d.cts.map