praxis-agent 0.20.21 → 0.21.0

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/README.md CHANGED
@@ -170,14 +170,17 @@ Praxis ──────┘
170
170
  ```
171
171
 
172
172
  Praxis can resume Claude Code sessions, and Claude Code can resume compatible
173
- sessions written by Praxis. Ordinary Praxis session runtime always emits the
174
- verified Claude Code 2.1.208 write profile and never derives it from an
175
- installed Claude version; this fixed profile governs generated append and
176
- sidechain writes. Native fork creation is a separate restricted lossless copy
177
- path that preserves each existing source record's producer version, so it can
178
- copy specific black-box-verified foreign shapes such as the observed Claude
179
- Code 2.1.233 records; unsupported record shapes and unverified versions still
180
- fail closed and remain read-only. Maintainers can prove mixed-version Claude JSONL interoperability with
173
+ sessions written by Praxis. Every semver-like Claude Code producer version is
174
+ structurally validated and is read/write compatible when its entry shape is
175
+ supported; schema adapters are selected from transcript entry structure rather
176
+ than an installed or fixed producer version, and malformed or unsupported
177
+ shapes fail closed before any write. Each transcript record keeps its original
178
+ producer version, so supported shapes may be mixed across versions in one
179
+ session. Native fork creation is a separate restricted lossless copy path that
180
+ preserves each existing source record's producer version, so it can copy
181
+ specific black-box-verified foreign shapes such as the observed Claude Code
182
+ 2.1.233 records; unsupported record shapes and unverified versions still fail
183
+ closed and remain read-only. Maintainers can prove mixed-version Claude JSONL interoperability with
181
184
  `npm run test:cross-version-session-compat`, `test:cross-version-fork-compat`,
182
185
  `test:cross-version-sidechain-compat`, `test:cross-version-compaction-compat`,
183
186
  and `test:cross-version-resume-at-compat`, covering linear resume, native fork,
@@ -233,15 +233,21 @@ export class BackgroundAgentManager {
233
233
  const task = this.tasks.get(agentId);
234
234
  if (!task)
235
235
  throw new Error(`No task found with ID: ${agentId}`);
236
- if (options.block && task.promise) {
237
- if (options.timeout === 0) {
238
- await task.promise;
239
- }
240
- else {
241
- await waitBounded(task.promise, options.timeout);
242
- }
236
+ // A positive block wait settles a pending task chain — a stopped task's
237
+ // cleanup chain included — or lets the timeout elapse. Zero-timeout and
238
+ // non-blocking retrievals never wait.
239
+ if (options.block && options.timeout > 0 && task.promise) {
240
+ await waitBounded(task.promise, options.timeout);
243
241
  }
244
- return this.formatOutput(task);
242
+ // The retrieval outcome reflects the task's state after any wait: success
243
+ // once terminal, not_ready for non-blocking retrievals, and timeout for a
244
+ // blocking retrieval whose window (including zero) closed while live.
245
+ const retrieval = task.status !== 'running'
246
+ ? 'success'
247
+ : options.block
248
+ ? 'timeout'
249
+ : 'not_ready';
250
+ return this.formatOutput(task, retrieval);
245
251
  }
246
252
  stop(agentId) {
247
253
  agentId = this.resolveRequired(agentId);
@@ -469,9 +475,8 @@ export class BackgroundAgentManager {
469
475
  durationMs: task.durationMs,
470
476
  };
471
477
  }
472
- formatOutput(task) {
478
+ formatOutput(task, retrieval) {
473
479
  const output = task.result?.text ?? task.error ?? '';
474
- const retrieval = task.status === 'running' ? 'not_ready' : 'success';
475
480
  return [
476
481
  `<retrieval_status>${retrieval}</retrieval_status>`,
477
482
  `<task_id>${task.spec.agentId}</task_id>`,
@@ -0,0 +1,96 @@
1
+ import type { ModelMessage } from '../core/runtime.js';
2
+ /** Versioned, explicit progress state for one session's extracted memory. */
3
+ export interface SessionMemoryState {
4
+ schemaVersion: 1;
5
+ initialized: boolean;
6
+ lastObservedTokens: number;
7
+ lastObservedToolCalls: number;
8
+ lastSummarizedMessageId: string | null;
9
+ extractionStartedAt: number | null;
10
+ extractionCompletedAt: number | null;
11
+ extractionError: string | null;
12
+ }
13
+ export declare class SessionMemoryStateError extends Error {
14
+ readonly name = "SessionMemoryStateError";
15
+ constructor(message: string);
16
+ }
17
+ export declare class SessionMemoryTimeoutError extends Error {
18
+ readonly name = "SessionMemoryTimeoutError";
19
+ constructor(message: string);
20
+ }
21
+ export declare function createFreshSessionMemoryState(): SessionMemoryState;
22
+ export interface SessionMemoryStoreOptions {
23
+ configRoot: string;
24
+ sessionId: string;
25
+ }
26
+ /**
27
+ * Durable sidecar for one session's extracted memory under
28
+ * `<configRoot>/praxis/session-memory/<sessionId>/`. All writes are atomic
29
+ * (same-directory temp file, fsync, then rename) and version-checked. Never
30
+ * touches shared Claude transcript entries.
31
+ */
32
+ export declare class SessionMemoryStore {
33
+ private readonly directory;
34
+ private readonly stateFile;
35
+ private readonly summaryFile;
36
+ constructor(options: SessionMemoryStoreOptions);
37
+ load(): Promise<SessionMemoryState>;
38
+ loadSummary(): Promise<string>;
39
+ writeSummary(summary: string): Promise<void>;
40
+ writeState(state: SessionMemoryState): Promise<void>;
41
+ clear(): Promise<void>;
42
+ }
43
+ export interface SessionMemoryExtractorInput {
44
+ summary: string;
45
+ tokens: number;
46
+ toolCalls: number;
47
+ messages?: readonly ModelMessage[];
48
+ }
49
+ export type SessionMemoryExtractor = (input: SessionMemoryExtractorInput) => Promise<string> | string;
50
+ export interface SessionMemoryControllerOptions {
51
+ store: SessionMemoryStore;
52
+ extractor: SessionMemoryExtractor;
53
+ initTokens?: number;
54
+ updateTokens?: number;
55
+ updateToolCalls?: number;
56
+ waitTimeoutMs?: number;
57
+ }
58
+ /**
59
+ * Serialized extraction lifecycle for one session. Concurrent callers of
60
+ * `observe` share one in-flight extraction promise, so two extractors never
61
+ * run for the same session. A persisted extraction that never completed is
62
+ * marked failed on reopen and can be retried.
63
+ */
64
+ export declare class SessionMemoryController {
65
+ private readonly options;
66
+ private readonly initTokens;
67
+ private readonly updateTokens;
68
+ private readonly updateToolCalls;
69
+ private readonly waitTimeoutMs;
70
+ private stateValue;
71
+ private summaryValue;
72
+ private inFlight;
73
+ private loading;
74
+ private observedTokens;
75
+ private observedToolCalls;
76
+ constructor(options: SessionMemoryControllerOptions);
77
+ observe(tokens: number, toolCalls: number, messageId: string, messages?: readonly ModelMessage[]): Promise<boolean>;
78
+ /**
79
+ * Adds non-negative deltas to the current cumulative observed totals and
80
+ * delegates to the serialized extraction path with those cumulative totals.
81
+ * The persisted counters only advance once an extraction succeeds.
82
+ */
83
+ observeDelta(inputTokens: number, toolCalls: number, messageId: string, messages?: readonly ModelMessage[]): Promise<boolean>;
84
+ /** Safe snapshot of the loaded durable summary; empty when none exists. */
85
+ summary(): Promise<string>;
86
+ /** Safe snapshot of the loaded session memory state. */
87
+ state(): Promise<SessionMemoryState>;
88
+ /** Resolves when no extraction is running; rejects on failure or timeout. */
89
+ waitForIdle(): Promise<void>;
90
+ clear(): Promise<void>;
91
+ private ensureLoaded;
92
+ private loadState;
93
+ private isExtractionDue;
94
+ private runExtraction;
95
+ }
96
+ //# sourceMappingURL=session-memory.d.ts.map
@@ -0,0 +1,383 @@
1
+ import { readFile, rm } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { isClaudeSessionId } from '../compatibility/claude/paths.js';
4
+ import { writeFileAtomically } from '../platform/atomic-write.js';
5
+ export class SessionMemoryStateError extends Error {
6
+ name = 'SessionMemoryStateError';
7
+ constructor(message) {
8
+ super(message);
9
+ }
10
+ }
11
+ export class SessionMemoryTimeoutError extends Error {
12
+ name = 'SessionMemoryTimeoutError';
13
+ constructor(message) {
14
+ super(message);
15
+ }
16
+ }
17
+ export function createFreshSessionMemoryState() {
18
+ return {
19
+ schemaVersion: 1,
20
+ initialized: false,
21
+ lastObservedTokens: 0,
22
+ lastObservedToolCalls: 0,
23
+ lastSummarizedMessageId: null,
24
+ extractionStartedAt: null,
25
+ extractionCompletedAt: null,
26
+ extractionError: null,
27
+ };
28
+ }
29
+ function isNonNegativeSafeInteger(value) {
30
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
31
+ }
32
+ function isNullableTimestamp(value) {
33
+ return (value === null ||
34
+ (typeof value === 'number' && Number.isFinite(value) && value >= 0));
35
+ }
36
+ function assertValidSessionMemoryState(value) {
37
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
38
+ throw new SessionMemoryStateError('Session memory state must be an object');
39
+ }
40
+ const record = value;
41
+ if (record.schemaVersion !== 1) {
42
+ throw new SessionMemoryStateError(`Unsupported session memory schema version: ${String(record.schemaVersion)}`);
43
+ }
44
+ if (typeof record.initialized !== 'boolean') {
45
+ throw new SessionMemoryStateError('Session memory state initialized must be a boolean');
46
+ }
47
+ if (!isNonNegativeSafeInteger(record.lastObservedTokens)) {
48
+ throw new SessionMemoryStateError('Session memory state lastObservedTokens must be a non-negative safe integer');
49
+ }
50
+ if (!isNonNegativeSafeInteger(record.lastObservedToolCalls)) {
51
+ throw new SessionMemoryStateError('Session memory state lastObservedToolCalls must be a non-negative safe integer');
52
+ }
53
+ if (record.lastSummarizedMessageId !== null &&
54
+ typeof record.lastSummarizedMessageId !== 'string') {
55
+ throw new SessionMemoryStateError('Session memory state lastSummarizedMessageId must be a string or null');
56
+ }
57
+ if (!isNullableTimestamp(record.extractionStartedAt)) {
58
+ throw new SessionMemoryStateError('Session memory state extractionStartedAt must be a non-negative timestamp or null');
59
+ }
60
+ if (!isNullableTimestamp(record.extractionCompletedAt)) {
61
+ throw new SessionMemoryStateError('Session memory state extractionCompletedAt must be a non-negative timestamp or null');
62
+ }
63
+ if (record.extractionError !== null &&
64
+ typeof record.extractionError !== 'string') {
65
+ throw new SessionMemoryStateError('Session memory state extractionError must be a string or null');
66
+ }
67
+ }
68
+ function parseSessionMemoryState(source) {
69
+ let value;
70
+ try {
71
+ value = JSON.parse(source);
72
+ }
73
+ catch {
74
+ throw new SessionMemoryStateError('Session memory state is not valid JSON');
75
+ }
76
+ assertValidSessionMemoryState(value);
77
+ return value;
78
+ }
79
+ /**
80
+ * Durable sidecar for one session's extracted memory under
81
+ * `<configRoot>/praxis/session-memory/<sessionId>/`. All writes are atomic
82
+ * (same-directory temp file, fsync, then rename) and version-checked. Never
83
+ * touches shared Claude transcript entries.
84
+ */
85
+ export class SessionMemoryStore {
86
+ directory;
87
+ stateFile;
88
+ summaryFile;
89
+ constructor(options) {
90
+ if (typeof options.configRoot !== 'string' ||
91
+ options.configRoot.length === 0) {
92
+ throw new SessionMemoryStateError('Session memory configRoot must be a non-empty string');
93
+ }
94
+ if (!isClaudeSessionId(options.sessionId)) {
95
+ throw new SessionMemoryStateError(`Invalid session memory session ID: ${options.sessionId}`);
96
+ }
97
+ this.directory = resolve(options.configRoot, 'praxis', 'session-memory', options.sessionId);
98
+ this.stateFile = join(this.directory, 'state.json');
99
+ this.summaryFile = join(this.directory, 'summary.md');
100
+ }
101
+ async load() {
102
+ let source;
103
+ try {
104
+ source = await readFile(this.stateFile, 'utf8');
105
+ }
106
+ catch (error) {
107
+ if (error.code === 'ENOENT') {
108
+ return createFreshSessionMemoryState();
109
+ }
110
+ throw error;
111
+ }
112
+ return parseSessionMemoryState(source);
113
+ }
114
+ async loadSummary() {
115
+ let source;
116
+ try {
117
+ source = await readFile(this.summaryFile, 'utf8');
118
+ }
119
+ catch (error) {
120
+ if (error.code === 'ENOENT')
121
+ return '';
122
+ throw error;
123
+ }
124
+ return source;
125
+ }
126
+ async writeSummary(summary) {
127
+ if (typeof summary !== 'string' || summary.trim().length === 0) {
128
+ throw new SessionMemoryStateError('Session memory summary must be a non-empty string');
129
+ }
130
+ await writeFileAtomically(this.summaryFile, summary);
131
+ }
132
+ async writeState(state) {
133
+ assertValidSessionMemoryState(state);
134
+ const existing = await this.load();
135
+ if (state.lastObservedTokens < existing.lastObservedTokens ||
136
+ state.lastObservedToolCalls < existing.lastObservedToolCalls) {
137
+ throw new SessionMemoryStateError('Session memory observed counters must be monotonic');
138
+ }
139
+ await writeFileAtomically(this.stateFile, `${JSON.stringify(state, null, 2)}\n`);
140
+ }
141
+ async clear() {
142
+ await Promise.all([
143
+ rm(this.stateFile, { force: true }),
144
+ rm(this.summaryFile, { force: true }),
145
+ ]);
146
+ }
147
+ }
148
+ /**
149
+ * Serialized extraction lifecycle for one session. Concurrent callers of
150
+ * `observe` share one in-flight extraction promise, so two extractors never
151
+ * run for the same session. A persisted extraction that never completed is
152
+ * marked failed on reopen and can be retried.
153
+ */
154
+ export class SessionMemoryController {
155
+ options;
156
+ initTokens;
157
+ updateTokens;
158
+ updateToolCalls;
159
+ waitTimeoutMs;
160
+ stateValue = null;
161
+ summaryValue = '';
162
+ inFlight = null;
163
+ loading = null;
164
+ observedTokens = 0;
165
+ observedToolCalls = 0;
166
+ constructor(options) {
167
+ this.options = options;
168
+ this.initTokens = options.initTokens ?? 10_000;
169
+ this.updateTokens = options.updateTokens ?? 5_000;
170
+ this.updateToolCalls = options.updateToolCalls ?? 20;
171
+ this.waitTimeoutMs = options.waitTimeoutMs ?? 30_000;
172
+ for (const [name, value] of [
173
+ ['initTokens', this.initTokens],
174
+ ['updateTokens', this.updateTokens],
175
+ ['updateToolCalls', this.updateToolCalls],
176
+ ]) {
177
+ if (!isNonNegativeSafeInteger(value) || value === 0) {
178
+ throw new SessionMemoryStateError(`Session memory ${name} must be a positive safe integer`);
179
+ }
180
+ }
181
+ if (typeof this.waitTimeoutMs !== 'number' ||
182
+ !Number.isFinite(this.waitTimeoutMs) ||
183
+ this.waitTimeoutMs <= 0) {
184
+ throw new SessionMemoryStateError('Session memory waitTimeoutMs must be a positive number');
185
+ }
186
+ }
187
+ async observe(tokens, toolCalls, messageId, messages) {
188
+ await this.ensureLoaded();
189
+ const state = this.stateValue;
190
+ if (state === null) {
191
+ throw new SessionMemoryStateError('Session memory state is unavailable');
192
+ }
193
+ if (!isNonNegativeSafeInteger(tokens) ||
194
+ !isNonNegativeSafeInteger(toolCalls)) {
195
+ throw new SessionMemoryStateError('Session memory observed counters must be non-negative safe integers');
196
+ }
197
+ if (typeof messageId !== 'string' || messageId.length === 0) {
198
+ throw new SessionMemoryStateError('Session memory message ID must be a non-empty string');
199
+ }
200
+ if (tokens < state.lastObservedTokens ||
201
+ toolCalls < state.lastObservedToolCalls) {
202
+ throw new SessionMemoryStateError(`Session memory observed counters regressed (tokens ${tokens} < ${state.lastObservedTokens}, toolCalls ${toolCalls} < ${state.lastObservedToolCalls})`);
203
+ }
204
+ this.observedTokens = Math.max(this.observedTokens, tokens);
205
+ this.observedToolCalls = Math.max(this.observedToolCalls, toolCalls);
206
+ if (!this.isExtractionDue(this.observedTokens, this.observedToolCalls)) {
207
+ return false;
208
+ }
209
+ if (this.inFlight === null) {
210
+ const extraction = this.runExtraction(this.observedTokens, this.observedToolCalls, messageId, messages);
211
+ this.inFlight = extraction;
212
+ extraction
213
+ .catch(() => undefined)
214
+ .finally(() => {
215
+ if (this.inFlight === extraction)
216
+ this.inFlight = null;
217
+ });
218
+ }
219
+ return true;
220
+ }
221
+ /**
222
+ * Adds non-negative deltas to the current cumulative observed totals and
223
+ * delegates to the serialized extraction path with those cumulative totals.
224
+ * The persisted counters only advance once an extraction succeeds.
225
+ */
226
+ async observeDelta(inputTokens, toolCalls, messageId, messages) {
227
+ await this.ensureLoaded();
228
+ const state = this.stateValue;
229
+ if (state === null) {
230
+ throw new SessionMemoryStateError('Session memory state is unavailable');
231
+ }
232
+ if (!isNonNegativeSafeInteger(inputTokens) ||
233
+ !isNonNegativeSafeInteger(toolCalls)) {
234
+ throw new SessionMemoryStateError('Session memory observed deltas must be non-negative safe integers');
235
+ }
236
+ if (typeof messageId !== 'string' || messageId.length === 0) {
237
+ throw new SessionMemoryStateError('Session memory message ID must be a non-empty string');
238
+ }
239
+ this.observedTokens += inputTokens;
240
+ this.observedToolCalls += toolCalls;
241
+ return this.observe(this.observedTokens, this.observedToolCalls, messageId, messages);
242
+ }
243
+ /** Safe snapshot of the loaded durable summary; empty when none exists. */
244
+ async summary() {
245
+ await this.ensureLoaded();
246
+ return this.summaryValue;
247
+ }
248
+ /** Safe snapshot of the loaded session memory state. */
249
+ async state() {
250
+ await this.ensureLoaded();
251
+ if (this.stateValue === null) {
252
+ throw new SessionMemoryStateError('Session memory state is unavailable');
253
+ }
254
+ return { ...this.stateValue };
255
+ }
256
+ /** Resolves when no extraction is running; rejects on failure or timeout. */
257
+ async waitForIdle() {
258
+ await this.ensureLoaded();
259
+ const extraction = this.inFlight;
260
+ if (extraction === null)
261
+ return;
262
+ let timer;
263
+ const timeout = new Promise((_, reject) => {
264
+ timer = setTimeout(() => reject(new SessionMemoryTimeoutError(`Session memory extraction did not complete within ${this.waitTimeoutMs}ms`)), this.waitTimeoutMs);
265
+ });
266
+ try {
267
+ await Promise.race([extraction, timeout]);
268
+ }
269
+ finally {
270
+ if (timer !== undefined)
271
+ clearTimeout(timer);
272
+ }
273
+ }
274
+ async clear() {
275
+ await this.ensureLoaded();
276
+ if (this.inFlight !== null) {
277
+ throw new SessionMemoryStateError('Session memory cannot be cleared while extraction is in progress');
278
+ }
279
+ await this.options.store.clear();
280
+ const fresh = createFreshSessionMemoryState();
281
+ this.stateValue = fresh;
282
+ this.observedTokens = fresh.lastObservedTokens;
283
+ this.observedToolCalls = fresh.lastObservedToolCalls;
284
+ this.summaryValue = '';
285
+ }
286
+ ensureLoaded() {
287
+ if (this.loading !== null)
288
+ return this.loading;
289
+ this.loading = this.loadState().catch((error) => {
290
+ this.loading = null;
291
+ throw error;
292
+ });
293
+ return this.loading;
294
+ }
295
+ async loadState() {
296
+ const [state, summary] = await Promise.all([
297
+ this.options.store.load(),
298
+ this.options.store.loadSummary(),
299
+ ]);
300
+ if (state.extractionStartedAt !== null &&
301
+ state.extractionCompletedAt === null) {
302
+ const elapsed = Date.now() - state.extractionStartedAt;
303
+ const recovered = {
304
+ ...state,
305
+ extractionStartedAt: null,
306
+ extractionCompletedAt: null,
307
+ extractionError: elapsed >= this.waitTimeoutMs
308
+ ? `Session memory extraction is stale after ${elapsed}ms`
309
+ : 'Session memory extraction was interrupted',
310
+ };
311
+ await this.options.store.writeState(recovered);
312
+ this.stateValue = recovered;
313
+ }
314
+ else {
315
+ this.stateValue = state;
316
+ }
317
+ this.observedTokens = this.stateValue.lastObservedTokens;
318
+ this.observedToolCalls = this.stateValue.lastObservedToolCalls;
319
+ this.summaryValue = summary;
320
+ }
321
+ isExtractionDue(tokens, toolCalls) {
322
+ const state = this.stateValue;
323
+ if (state === null)
324
+ return false;
325
+ if (!state.initialized)
326
+ return tokens >= this.initTokens;
327
+ return (tokens - state.lastObservedTokens >= this.updateTokens ||
328
+ toolCalls - state.lastObservedToolCalls >= this.updateToolCalls);
329
+ }
330
+ async runExtraction(tokens, toolCalls, messageId, messages) {
331
+ const state = this.stateValue;
332
+ if (state === null) {
333
+ throw new SessionMemoryStateError('Session memory state is unavailable');
334
+ }
335
+ this.stateValue = {
336
+ ...state,
337
+ extractionStartedAt: Date.now(),
338
+ extractionCompletedAt: null,
339
+ extractionError: null,
340
+ };
341
+ await this.options.store.writeState(this.stateValue);
342
+ try {
343
+ const summary = await this.options.extractor({
344
+ summary: this.summaryValue,
345
+ tokens,
346
+ toolCalls,
347
+ ...(messages?.length ? { messages } : {}),
348
+ });
349
+ if (typeof summary !== 'string' || summary.trim().length === 0) {
350
+ throw new SessionMemoryStateError('Session memory extractor returned an empty summary');
351
+ }
352
+ // Persist the summary before the completed state so a crash in between
353
+ // is recovered as a stale extraction and safely re-extracted.
354
+ await this.options.store.writeSummary(summary);
355
+ this.stateValue = {
356
+ ...this.stateValue,
357
+ initialized: true,
358
+ lastObservedTokens: tokens,
359
+ lastObservedToolCalls: toolCalls,
360
+ lastSummarizedMessageId: messageId,
361
+ extractionStartedAt: null,
362
+ extractionCompletedAt: Date.now(),
363
+ extractionError: null,
364
+ };
365
+ await this.options.store.writeState(this.stateValue);
366
+ this.summaryValue = summary;
367
+ }
368
+ catch (error) {
369
+ const failure = error instanceof Error ? error.message : String(error);
370
+ this.stateValue = {
371
+ ...this.stateValue,
372
+ extractionStartedAt: null,
373
+ extractionCompletedAt: null,
374
+ extractionError: failure,
375
+ };
376
+ await this.options.store
377
+ .writeState(this.stateValue)
378
+ .catch(() => undefined);
379
+ throw error;
380
+ }
381
+ }
382
+ }
383
+ //# sourceMappingURL=session-memory.js.map
@@ -17,6 +17,7 @@ import { type ScheduledPrompt } from './scheduled-prompt-manager.js';
17
17
  import { type WorkflowTaskSnapshot } from './workflow-manager.js';
18
18
  import type { WorkspaceContext } from './session-worktree.js';
19
19
  import { type ClaudeSessionCostSnapshot } from './session-cost-tracker.js';
20
+ import { type ClaudeToolRole } from '../tools/claude-capabilities.js';
20
21
  import type { ClaudeInteractiveToolManager } from '../tools/claude-interactive-tools.js';
21
22
  import type { ClaudePermissionMode } from '../permissions/claude-permission-resolver.js';
22
23
  import type { ClaudeMcpRuntime, ClaudeMcpServerStatus, ClaudeMcpToolInspection } from '../mcp/claude-mcp-tools.js';
@@ -45,6 +46,10 @@ export interface ClaudeSessionServiceOptions {
45
46
  subagentToolNames?: readonly string[];
46
47
  taskToolNames?: readonly string[];
47
48
  scheduledToolNames?: readonly string[];
49
+ /** Runtime gates for Claude capability-driven tool exposure. */
50
+ toolRole?: ClaudeToolRole;
51
+ toolCapabilityEnvironment?: Readonly<Record<string, string | undefined>>;
52
+ simpleMode?: boolean;
48
53
  enableDynamicWakeups?: boolean;
49
54
  enableWorkflows?: boolean;
50
55
  providerForModel?: (model: string) => ModelProvider;
@@ -63,6 +68,10 @@ export interface ClaudeSessionServiceOptions {
63
68
  brief?: boolean;
64
69
  collectMetrics?: boolean;
65
70
  sessionPersistence?: boolean;
71
+ /** Enable durable per-session memory extraction and injection. Defaults to
72
+ * enabled whenever session persistence is enabled; ignored when
73
+ * `sessionPersistence === false`. */
74
+ enableSessionMemory?: boolean;
66
75
  sessionKind?: 'bg';
67
76
  workspace?: WorkspaceContext;
68
77
  initialWorktree?: boolean;
@@ -169,6 +178,7 @@ export declare class ClaudeSessionService {
169
178
  private readonly sessionCostTrackers;
170
179
  private activeCostSessionId;
171
180
  private closeCostSavePromise;
181
+ private readonly sessionMemoryControllers;
172
182
  private runtimeCwd;
173
183
  constructor(options: ClaudeSessionServiceOptions);
174
184
  nextScheduledPrompt(signal?: AbortSignal): Promise<ScheduledPrompt | null>;
@@ -258,6 +268,14 @@ export declare class ClaudeSessionService {
258
268
  private mainAgentSystemPrompt;
259
269
  private assembledSystemMessages;
260
270
  private contextBudget;
271
+ private sessionMemoryEnabled;
272
+ private sessionMemoryController;
273
+ private sessionMemoryMessage;
274
+ private boundSessionMemorySummary;
275
+ private formatSessionMemoryConversation;
276
+ private extractSessionMemory;
277
+ private toolCapabilities;
278
+ private capabilityToolNames;
261
279
  private append;
262
280
  private logicalTailUuid;
263
281
  }