@myagentroam/agent 0.11.13 → 0.11.15

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.
Files changed (36) hide show
  1. package/dist/agent-core/host/contracts.d.ts +2 -2
  2. package/dist/agent-core/host/contracts.js +1 -2
  3. package/dist/agent-core/host/local-agent-host.js +1 -2
  4. package/dist/agent-core/runtime/compaction/requests.d.ts +1 -1
  5. package/dist/agent-core/runtime/compaction/retained-context.js +1 -1
  6. package/dist/agent-core/runtime/compaction/smart.d.ts +1 -1
  7. package/dist/agent-core/runtime/compaction/smart.js +1 -1
  8. package/dist/agent-core/runtime/context/gc-runtime.d.ts +2 -2
  9. package/dist/agent-core/runtime/context/gc-runtime.js +9 -6
  10. package/dist/agent-core/runtime/context/gc.js +43 -29
  11. package/dist/agent-core/runtime/context/projection.d.ts +1 -1
  12. package/dist/agent-core/runtime/context/projection.js +4 -4
  13. package/dist/agent-core/runtime/context/restore.d.ts +3 -2
  14. package/dist/agent-core/runtime/context/restore.js +2 -3
  15. package/dist/agent-core/runtime/context/usage.js +1 -1
  16. package/dist/agent-core/runtime/context/world-state.d.ts +1 -1
  17. package/dist/agent-core/runtime/context/world-state.js +1 -1
  18. package/dist/agent-core/runtime/execution-handle.d.ts +12 -0
  19. package/dist/agent-core/runtime/execution-handle.js +1 -0
  20. package/dist/agent-core/sdk/agent.js +140 -63
  21. package/dist/agent-core/sdk/session.d.ts +3 -3
  22. package/dist/agent-core/sdk/session.js +3 -1
  23. package/dist/agent-core/sdk/subagent-backend.d.ts +2 -2
  24. package/dist/agent-core/sdk/types.d.ts +6 -13
  25. package/dist/agent-core/session/catalog.d.ts +8 -2
  26. package/dist/agent-core/session/catalog.js +48 -10
  27. package/dist/agent-core/session/context-gc-checkpoint.d.ts +4 -2
  28. package/dist/agent-core/session/context-gc-checkpoint.js +15 -7
  29. package/dist/agent-core/session/store/contracts.d.ts +148 -0
  30. package/dist/agent-core/session/store/contracts.js +1 -0
  31. package/dist/agent-core/session/store/execution-state.js +0 -12
  32. package/dist/agent-core/session/store/jsonl-store.d.ts +7 -30
  33. package/dist/agent-core/session/store/jsonl-store.js +133 -47
  34. package/dist/agent-core/session/store/types.d.ts +0 -3
  35. package/dist/index.d.ts +2 -0
  36. package/package.json +1 -1
@@ -7,7 +7,7 @@ import { negotiateHost } from '../host/contracts.js';
7
7
  import { toolCallers } from '../tools/callers.js';
8
8
  import { AnthropicMessagesAdapter } from '../model/anthropic-messages.js';
9
9
  import { OpenAiResponsesAdapter } from '../model/openai-responses.js';
10
- import { JsonlSessionStore } from '../session/store/index.js';
10
+ import { JsonlSessionStore } from '../session/store/jsonl-store.js';
11
11
  /** Validate models before Host negotiation and expose only built-in protocol adapters. */
12
12
  export async function createMarAgent(options) {
13
13
  if (Object.hasOwn(options, 'adapterFactory'))
@@ -23,7 +23,10 @@ export async function createMarAgentWithAdapter(options) {
23
23
  const description = negotiateHost(await options.host.describe());
24
24
  if (description.mode !== options.mode)
25
25
  throw new MarAgentError('MAR_AGENT_HOST_INCOMPATIBLE', 'Host mode does not match Agent mode.');
26
- const store = await JsonlSessionStore.open(description.homeDirectory);
26
+ if (options.sessionStore === null)
27
+ throw new MarAgentError('MAR_AGENT_SESSION_STORE_INVALID', 'Session store must not be null.');
28
+ const ownsStore = options.sessionStore === undefined;
29
+ const store = options.sessionStore ?? (await JsonlSessionStore.open(description.homeDirectory));
27
30
  const active = new Map();
28
31
  const activeExecutionAdapters = new Set();
29
32
  const executionResults = new Map();
@@ -59,7 +62,7 @@ export async function createMarAgentWithAdapter(options) {
59
62
  }
60
63
  return pending;
61
64
  };
62
- let reconfiguring = false;
65
+ let reconfiguration;
63
66
  const createAdapter = (modelId, configuredModels = models) => {
64
67
  const configuration = configuredModels.get(modelId);
65
68
  if (!configuration)
@@ -193,44 +196,73 @@ export async function createMarAgentWithAdapter(options) {
193
196
  assertSessionAdmission,
194
197
  assertCanStart: (sessionId) => {
195
198
  assertSessionAdmission(sessionId);
196
- if (reconfiguring)
199
+ if (reconfiguration)
197
200
  throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'Models are being reconfigured for the next execution.');
198
201
  }
199
202
  });
200
203
  return {
201
204
  extensionDiagnostics: () => extensions.diagnostics(),
202
- createSession: async (input) => openSession((await store.createSession(input)).id, input.workspace),
205
+ createSession: async (input) => {
206
+ assertSessionAdmission();
207
+ return openSession((await store.createSession(input)).id, input.workspace);
208
+ },
203
209
  openSession: (id) => openSession(id),
204
- listSessions: (input) => store.list(input),
205
- getSession: (id) => store.getSession(id),
206
- setSessionTitle: (id, title, input) => store.updateTitle(id, title, { ...input, automatic: input?.automatic ?? false }, sessionWriterOwner),
210
+ listSessions: async (input) => {
211
+ assertSessionAdmission();
212
+ return store.list(input);
213
+ },
214
+ getSession: async (id) => {
215
+ assertSessionAdmission();
216
+ return store.getSession(id);
217
+ },
218
+ setSessionTitle: async (id, title, input) => {
219
+ assertSessionAdmission();
220
+ return store.updateTitle(id, title, { ...input, automatic: input?.automatic ?? false }, sessionWriterOwner);
221
+ },
207
222
  clearSession: (id) => trackSessionLifecycle(id, 'clear', async () => {
208
223
  const runtime = sessionRuntimes.get(id);
209
224
  if ([...active.keys()].some((key) => key.startsWith(`${id}:`)))
210
225
  throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'A session cannot be cleared while an execution is active.');
211
- await extensions.endSession(id, 'clear');
212
- extensions.retireSession(id);
213
- await completeAgentCleanup([
214
- () => extensions.clearSession(id),
215
- () => runtime?.inputs?.retire(),
216
- () => runtime?.subagents?.dispose(),
217
- () => closeModelAdapters(runtime === undefined ? [] : [...runtime.adapters.values()]),
218
- () => {
219
- // Existing SDK Session handles share this runtime and remain usable after clear.
220
- if (runtime) {
221
- runtime.adapters.clear();
222
- runtime.adapterGeneration = modelGeneration;
223
- delete runtime.todo;
224
- delete runtime.subagents;
225
- delete runtime.subagentsOpening;
226
- delete runtime.contextualWorldState;
227
- delete runtime.inputs;
228
- delete runtime.requestSnapshots;
229
- }
230
- },
231
- () => options.host.disposeSessionResources?.(id),
232
- () => store.clearSession(id)
233
- ]);
226
+ const maintenance = await store.acquireSessionMaintenance(id, 'clear', sessionWriterOwner);
227
+ let failed = false;
228
+ try {
229
+ await completeAgentCleanup([
230
+ () => extensions.endSession(id, 'clear'),
231
+ () => extensions.retireSession(id),
232
+ () => extensions.clearSession(id),
233
+ () => runtime?.inputs?.retire(),
234
+ () => runtime?.subagents?.dispose(),
235
+ () => closeModelAdapters(runtime === undefined ? [] : [...runtime.adapters.values()]),
236
+ () => {
237
+ // Existing SDK Session handles share this runtime and remain usable after clear.
238
+ if (runtime) {
239
+ runtime.adapters.clear();
240
+ runtime.adapterGeneration = modelGeneration;
241
+ delete runtime.todo;
242
+ delete runtime.subagents;
243
+ delete runtime.subagentsOpening;
244
+ delete runtime.contextualWorldState;
245
+ delete runtime.inputs;
246
+ delete runtime.requestSnapshots;
247
+ }
248
+ },
249
+ () => options.host.disposeSessionResources?.(id),
250
+ () => store.clearSession(id, maintenance)
251
+ ]);
252
+ }
253
+ catch (error) {
254
+ failed = true;
255
+ throw error;
256
+ }
257
+ finally {
258
+ try {
259
+ await maintenance.release();
260
+ }
261
+ catch (error) {
262
+ if (!failed)
263
+ throw error;
264
+ }
265
+ }
234
266
  }),
235
267
  setSystemInstruction: (systemInstruction) => {
236
268
  if (disposed)
@@ -243,7 +275,7 @@ export async function createMarAgentWithAdapter(options) {
243
275
  reconfigureModels: async (input) => {
244
276
  if (disposed)
245
277
  throw new MarAgentError('MAR_AGENT_DISPOSED', 'Agent is disposed.');
246
- if (reconfiguring)
278
+ if (reconfiguration)
247
279
  throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'Models are already being reconfigured for the next execution.');
248
280
  const next = resolveAgentModels(input.models, input.defaultModelId);
249
281
  const activeSessions = activeSessionIds();
@@ -255,7 +287,10 @@ export async function createMarAgentWithAdapter(options) {
255
287
  for (const adapter of runtime.adapters.values())
256
288
  activeAdapters.add(adapter);
257
289
  const inactiveRuntimes = [...sessionRuntimes].flatMap(([sessionId, runtime]) => activeSessions.has(sessionId) ? [] : [runtime]);
258
- reconfiguring = true;
290
+ const completion = Promise.withResolvers();
291
+ reconfiguration = completion.promise;
292
+ void completion.promise.catch(() => undefined);
293
+ let interruptedByDisposal = false;
259
294
  try {
260
295
  const reconfigurationCommits = new Map();
261
296
  for (const runtime of inactiveRuntimes)
@@ -277,8 +312,10 @@ export async function createMarAgentWithAdapter(options) {
277
312
  runtime.adapters.delete(modelId);
278
313
  }
279
314
  await closeModelAdapters(adapters);
280
- if (disposed)
315
+ if (disposed) {
316
+ interruptedByDisposal = true;
281
317
  throw new MarAgentError('MAR_AGENT_DISPOSED', 'Agent is disposed.');
318
+ }
282
319
  for (const commit of reconfigurationCommits.values())
283
320
  commit();
284
321
  models = next.models;
@@ -296,41 +333,79 @@ export async function createMarAgentWithAdapter(options) {
296
333
  for (const adapter of runtime.adapters.values())
297
334
  addModelCredentialProviders(liveProviders, adapter.configuration);
298
335
  const obsoleteProviders = [...credentialProviders].filter((provider) => !liveProviders.has(provider));
299
- const providerCloseResults = await Promise.allSettled(obsoleteProviders.map((provider) => provider.close()));
336
+ const providerCloseResults = await Promise.allSettled(obsoleteProviders.map(async (provider) => provider.close()));
300
337
  for (const [index, result] of providerCloseResults.entries())
301
338
  if (result.status === 'fulfilled')
302
339
  credentialProviders.delete(obsoleteProviders[index]);
303
340
  }
341
+ catch (error) {
342
+ if (!interruptedByDisposal)
343
+ completion.reject(error);
344
+ throw error;
345
+ }
304
346
  finally {
305
- reconfiguring = false;
347
+ completion.resolve();
348
+ if (reconfiguration === completion.promise)
349
+ reconfiguration = undefined;
306
350
  }
307
351
  },
308
352
  deleteSession: (id) => trackSessionLifecycle(id, 'delete', async () => {
309
- const sessionIds = await store.sessionTree(id);
310
- const sessionIdSet = new Set(sessionIds);
311
- const executionKeys = [...active.keys()].filter((key) => sessionIdSet.has(key.slice(0, key.indexOf(':'))));
312
- for (const key of executionKeys)
313
- active.get(key)?.abort();
314
- await Promise.all(sessionIds.map((sessionId) => extensions.endSession(sessionId, 'other', false)));
315
- for (const sessionId of sessionIds)
316
- extensions.retireSession(sessionId);
317
- const runtimes = sessionIds.flatMap((sessionId) => {
318
- const runtime = sessionRuntimes.get(sessionId);
319
- sessionRuntimes.delete(sessionId);
320
- return runtime === undefined ? [] : [runtime];
321
- });
322
- await completeAgentCleanup([
323
- ...sessionIds.map((sessionId) => () => extensions.clearSession(sessionId)),
324
- ...runtimes.map((runtime) => () => runtime.inputs?.retire()),
325
- ...runtimes.map((runtime) => () => runtime.subagents?.dispose()),
326
- () => Promise.allSettled(executionKeys.flatMap((key) => {
327
- const result = executionResults.get(key);
328
- return result === undefined ? [] : [result];
329
- })),
330
- () => closeModelAdapters(runtimes.flatMap((runtime) => [...runtime.adapters.values()])),
331
- ...sessionIds.map((sessionId) => () => options.host.disposeSessionResources?.(sessionId)),
332
- () => store.delete(id)
333
- ]);
353
+ const maintenance = await store.acquireSessionMaintenance(id, 'delete', sessionWriterOwner);
354
+ let failed = false;
355
+ try {
356
+ const sessionIds = maintenance.sessionIds;
357
+ const sessionIdSet = new Set(sessionIds);
358
+ const executionKeys = [...active.keys()].filter((key) => sessionIdSet.has(key.slice(0, key.indexOf(':'))));
359
+ for (const key of executionKeys)
360
+ active.get(key)?.abort();
361
+ const runtimes = sessionIds.flatMap((sessionId) => {
362
+ const runtime = sessionRuntimes.get(sessionId);
363
+ return runtime === undefined ? [] : [runtime];
364
+ });
365
+ await completeAgentCleanup([
366
+ async () => {
367
+ let failure;
368
+ await Promise.all(sessionIds.map(async (sessionId) => {
369
+ try {
370
+ await extensions.endSession(sessionId, 'other', false);
371
+ }
372
+ catch (error) {
373
+ failure ??= { error };
374
+ }
375
+ }));
376
+ if (failure)
377
+ throw failure.error;
378
+ },
379
+ ...sessionIds.map((sessionId) => () => extensions.retireSession(sessionId)),
380
+ () => {
381
+ for (const sessionId of sessionIds)
382
+ sessionRuntimes.delete(sessionId);
383
+ },
384
+ ...sessionIds.map((sessionId) => () => extensions.clearSession(sessionId)),
385
+ ...runtimes.map((runtime) => () => runtime.inputs?.retire()),
386
+ ...runtimes.map((runtime) => () => runtime.subagents?.dispose()),
387
+ () => Promise.allSettled(executionKeys.flatMap((key) => {
388
+ const result = executionResults.get(key);
389
+ return result === undefined ? [] : [result];
390
+ })),
391
+ () => closeModelAdapters(runtimes.flatMap((runtime) => [...runtime.adapters.values()])),
392
+ ...sessionIds.map((sessionId) => () => options.host.disposeSessionResources?.(sessionId)),
393
+ () => store.delete(id, maintenance)
394
+ ]);
395
+ }
396
+ catch (error) {
397
+ failed = true;
398
+ throw error;
399
+ }
400
+ finally {
401
+ try {
402
+ await maintenance.release();
403
+ }
404
+ catch (error) {
405
+ if (!failed)
406
+ throw error;
407
+ }
408
+ }
334
409
  }),
335
410
  dispose: () => {
336
411
  if (disposal)
@@ -342,6 +417,7 @@ export async function createMarAgentWithAdapter(options) {
342
417
  const extensionDisposal = extensions.dispose();
343
418
  void extensionDisposal.catch(() => undefined);
344
419
  const runtimes = [...sessionRuntimes.values()];
420
+ const pendingReconfiguration = reconfiguration;
345
421
  disposal = completeAgentCleanup([
346
422
  async () => {
347
423
  const results = await Promise.allSettled([...sessionLifecycles.values()].map(({ pending }) => pending));
@@ -353,10 +429,11 @@ export async function createMarAgentWithAdapter(options) {
353
429
  ...runtimes.map((runtime) => () => runtime.inputs?.retire()),
354
430
  ...runtimes.map((runtime) => () => runtime.subagents?.dispose()),
355
431
  () => Promise.allSettled([...executionResults.values()]),
432
+ () => pendingReconfiguration,
356
433
  () => closeModelAdapters(runtimes.flatMap((runtime) => [...runtime.adapters.values()])),
357
- ...[...credentialProviders].map((provider) => () => provider.close()),
434
+ () => completeAgentCleanup([...credentialProviders].map((provider) => () => provider.close())),
358
435
  () => options.host.dispose(),
359
- () => store.close()
436
+ () => (ownsStore ? store.close() : undefined)
360
437
  ]);
361
438
  return disposal;
362
439
  }
@@ -1,3 +1,4 @@
1
+ import type { SessionStore } from '../session/store/contracts.js';
1
2
  import type { ContextualWorldState } from '../runtime/context/world-state.js';
2
3
  import type { ResolvedModelConfiguration } from './model-runtime.js';
3
4
  import { ExecutionToolSources } from './extension-tools.js';
@@ -10,7 +11,6 @@ import type { HostDescription, MarAgentHost } from '../host/contracts.js';
10
11
  import type { MarAgentReasoningEffort } from '../model/configuration.js';
11
12
  import type { ModelAdapter, ModelMessage } from '../model/contracts.js';
12
13
  import { CompactRequests } from '../runtime/compaction/requests.js';
13
- import { JsonlSessionStore } from '../session/store/index.js';
14
14
  import { SubagentSessionController, type SubagentMailboxMessage, type SubagentMessageDelivery, type SubagentTurnHandle } from '../subagent/session-controller.js';
15
15
  import { createSessionToolScheduler } from '../tools/scheduler.js';
16
16
  import { TodoStore } from '../tools/agent/todo.js';
@@ -75,7 +75,7 @@ interface SessionServices {
75
75
  readonly host: MarAgentHost;
76
76
  readonly description: HostDescription;
77
77
  readonly settings: Pick<CreateMarAgentOptions, 'workspaceMapping' | 'systemInstruction'>;
78
- readonly store: JsonlSessionStore;
78
+ readonly store: SessionStore;
79
79
  readonly extensions: ExtensionManager;
80
80
  readonly registry: AgentSessionRegistry;
81
81
  readonly hasCustomAdapters: boolean;
@@ -87,7 +87,7 @@ interface SessionServices {
87
87
  readonly createAdapter: (modelId: string, models: ReadonlyMap<string, ResolvedModelConfiguration>) => ModelAdapter;
88
88
  readonly activeAdaptersExcept: (sessionId: string) => ReadonlySet<ModelAdapter>;
89
89
  readonly assertCanStart: (sessionId?: string) => void;
90
- readonly assertSessionAdmission: (sessionId: string) => void;
90
+ readonly assertSessionAdmission: (sessionId?: string) => void;
91
91
  }
92
92
  /** SDK Session orchestration owns warm resources; Runtime owns only the model/tool loop. */
93
93
  export declare function createSessionOpener(services: SessionServices): (sessionId: string, workspace?: string) => Promise<InternalMarAgentSession>;
@@ -45,7 +45,7 @@ import { AGENT_EXECUTION_POLICY } from '../runtime/execution-policy.js';
45
45
  import { loadAgentInstructions, renderAgentInstructions } from '../runtime/context/instructions.js';
46
46
  import { normalizeMessagesForModel } from '../runtime/context/normalize.js';
47
47
  import { latestSmartContextTokens, requestSmartCompaction } from '../runtime/compaction/smart.js';
48
- import { compactSummaryFromCheckpoint, inputMetadataFromRecord } from '../session/store/index.js';
48
+ import { compactSummaryFromCheckpoint, inputMetadataFromRecord } from '../session/store/execution-state.js';
49
49
  import { SubagentSessionController } from '../subagent/session-controller.js';
50
50
  import { agentStartToolDefinitionWithModels } from '../tools/agent/agent-start.js';
51
51
  import { allowsDirectTool, allowsToolCaller, toolCallers } from '../tools/callers.js';
@@ -59,7 +59,9 @@ export function createSessionOpener(services) {
59
59
  const { host, description, settings, store, createAdapter, activeAdaptersExcept } = services;
60
60
  const { active, activeExecutionAdapters, executionResults, sessionRuntimes, sessionWriterOwner } = services.registry;
61
61
  const openSession = async (sessionId, workspace) => {
62
+ services.assertSessionAdmission();
62
63
  const storedSession = await store.getSession(sessionId);
64
+ services.assertSessionAdmission();
63
65
  const storedWorkspace = storedSession.workspace;
64
66
  const sessionSource = storedSession.source;
65
67
  const logicalWorkspace = settings.workspaceMapping?.logicalPath ?? workspace ?? storedWorkspace ?? '';
@@ -1,3 +1,3 @@
1
- import type { JsonlSessionStore } from '../session/store/index.js';
1
+ import type { SessionStore } from '../session/store/contracts.js';
2
2
  import type { SubagentSessionControllerBackend } from '../subagent/session-controller.js';
3
- export declare function createSubagentBackend(store: JsonlSessionStore, workspace: string): SubagentSessionControllerBackend;
3
+ export declare function createSubagentBackend(store: SessionStore, workspace: string): SubagentSessionControllerBackend;
@@ -1,8 +1,8 @@
1
1
  import type { MarAgentHost } from '../host/contracts.js';
2
2
  import type { CoreExtensionSource, CoreRuntimeExtension } from '../extensions/contracts.js';
3
3
  import type { MarAgentModelConfiguration } from '../model/configuration.js';
4
- import type { ExecutionResult, RolloutBudgetOptions, RunInput } from '../runtime/contracts.js';
5
- import type { MarAgentEvent } from '../runtime/events.js';
4
+ import type { RolloutBudgetOptions, RunInput } from '../runtime/contracts.js';
5
+ import type { CompactInput, ExecutionHandle } from '../runtime/execution-handle.js';
6
6
  import type { MarAgentSessionPage, MarAgentSessionSummary } from '../session/catalog.js';
7
7
  import type { SessionListInput, SessionSource, SessionTitleUpdateInput } from '../session/types.js';
8
8
  export interface CreateSessionInput {
@@ -13,17 +13,8 @@ export type { SessionTitleUpdateInput } from '../session/types.js';
13
13
  export type { HostExecutionExtensions, HostSessionResourceSnapshot, HostSkill, MarAgentHost } from '../host/contracts.js';
14
14
  export type { AccessMode, ExecutionMode, ExecutionResult, MarAgentImageMetadata, RolloutBudgetOptions, RunInput, RunInputImage } from '../runtime/contracts.js';
15
15
  export type { MarAgentEvent, MarAgentEventBase } from '../runtime/events.js';
16
+ export type { CompactInput, ExecutionHandle } from '../runtime/execution-handle.js';
16
17
  export type { CodeCellEventData, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolExecutionContext, ToolExecutionResult } from '../tools/contracts.js';
17
- /** A live execution exposes streamed events separately from its terminal result. */
18
- export interface ExecutionHandle extends AsyncIterable<MarAgentEvent> {
19
- readonly executionId: string;
20
- result(): Promise<ExecutionResult>;
21
- /** Signals abort; await result() to observe the terminal status and cleanup. */
22
- cancel(): void;
23
- }
24
- export type CompactInput = Partial<RunInput> & {
25
- readonly async?: boolean;
26
- };
27
18
  export interface SteerInput {
28
19
  readonly expectedExecutionId: string;
29
20
  readonly prompt: string;
@@ -68,10 +59,12 @@ export interface MarAgent {
68
59
  readonly defaultModelId?: string;
69
60
  }): Promise<void>;
70
61
  deleteSession(sessionId: string): Promise<void>;
71
- /** Cancels active executions, then closes SDK-owned providers, the Host and the Session store. */
62
+ /** Joins executions/reconfiguration, closes owned resources, and releases borrowed Store leases. */
72
63
  dispose(): Promise<void>;
73
64
  }
74
65
  export interface CreateMarAgentOptions {
66
+ /** Ready-to-use borrowed Store. The caller closes it after all Agent/Catalog users finish. */
67
+ sessionStore?: import('../session/store/contracts.js').SessionStore;
75
68
  mode: 'normal' | 'node';
76
69
  host: MarAgentHost;
77
70
  models: readonly MarAgentModelConfiguration[];
@@ -1,5 +1,6 @@
1
1
  import type { MarAgentImageMetadata } from '../runtime/contracts.js';
2
2
  import type { MarAgentEvent } from '../runtime/events.js';
3
+ import type { SessionStore } from './store/contracts.js';
3
4
  import type { SessionListInput, SessionSource } from './types.js';
4
5
  export interface MarAgentSessionSummary {
5
6
  id: string;
@@ -84,7 +85,12 @@ export interface MarAgentSessionCatalog {
84
85
  delete(sessionId: string): Promise<MarAgentSessionDeleteResult>;
85
86
  close(): Promise<void>;
86
87
  }
87
- export declare function openMarAgentSessionCatalog(input: {
88
+ export type OpenMarAgentSessionCatalogInput = {
88
89
  homeDirectory: string;
89
- }): Promise<MarAgentSessionCatalog>;
90
+ sessionStore?: never;
91
+ } | {
92
+ sessionStore: SessionStore;
93
+ homeDirectory?: never;
94
+ };
95
+ export declare function openMarAgentSessionCatalog(input: OpenMarAgentSessionCatalogInput): Promise<MarAgentSessionCatalog>;
90
96
  export declare function imageMetadata(value: unknown): MarAgentHistoryImageMetadata[];
@@ -1,15 +1,35 @@
1
1
  import { MarAgentError } from '../error.js';
2
2
  import { publicHostedToolEvents } from '../runtime/event-projection.js';
3
- import { JsonlSessionStore } from './store/index.js';
3
+ import { JsonlSessionStore } from './store/jsonl-store.js';
4
4
  const MAX_INPUT_IMAGE_BYTES = 5 * 1024 * 1024;
5
5
  const INPUT_IMAGE_MIME_TYPES = new Set(['image/png', 'image/jpeg', 'image/gif', 'image/webp']);
6
6
  export async function openMarAgentSessionCatalog(input) {
7
- const store = await JsonlSessionStore.open(input.homeDirectory);
7
+ const borrowed = input.sessionStore !== undefined;
8
+ if (input.sessionStore === null ||
9
+ borrowed === (input.homeDirectory !== undefined) ||
10
+ (!borrowed && (typeof input.homeDirectory !== 'string' || !input.homeDirectory)))
11
+ throw new MarAgentError('MAR_AGENT_SESSION_STORE_INVALID', 'Specify exactly one Session store or home directory.');
12
+ const store = input.sessionStore ?? (await JsonlSessionStore.open(input.homeDirectory));
13
+ let closure;
14
+ const assertOpen = () => {
15
+ if (closure)
16
+ throw new MarAgentError('MAR_AGENT_DISPOSED', 'Session catalog is closed.');
17
+ };
8
18
  return {
9
- list: (options) => store.list(options),
10
- firstUserMessagePreview: (sessionId) => store.firstUserMessagePreview(sessionId),
11
- executionConfiguration: (sessionId) => store.executionConfiguration(sessionId),
19
+ list: async (options) => {
20
+ assertOpen();
21
+ return store.list(options);
22
+ },
23
+ firstUserMessagePreview: async (sessionId) => {
24
+ assertOpen();
25
+ return store.firstUserMessagePreview(sessionId);
26
+ },
27
+ executionConfiguration: async (sessionId) => {
28
+ assertOpen();
29
+ return store.executionConfiguration(sessionId);
30
+ },
12
31
  history: async (sessionId, options = {}) => {
32
+ assertOpen();
13
33
  const beforeSequence = parseCursor(options.cursor);
14
34
  const page = await store.readPage(sessionId, {
15
35
  ...(options.limit === undefined ? {} : { limit: options.limit }),
@@ -72,6 +92,7 @@ export async function openMarAgentSessionCatalog(input) {
72
92
  };
73
93
  },
74
94
  readImage: async (sessionId, input) => {
95
+ assertOpen();
75
96
  const image = await store.readInputImage(sessionId, input);
76
97
  return image === undefined
77
98
  ? undefined
@@ -82,8 +103,12 @@ export async function openMarAgentSessionCatalog(input) {
82
103
  dataBase64: image.dataBase64
83
104
  };
84
105
  },
85
- readToolResultResource: (sessionId, input) => store.readToolResultResource(sessionId, input),
106
+ readToolResultResource: async (sessionId, input) => {
107
+ assertOpen();
108
+ return store.readToolResultResource(sessionId, input);
109
+ },
86
110
  contextUsage: async (sessionId) => {
111
+ assertOpen();
87
112
  const page = await store.readPage(sessionId, { limit: 1000 });
88
113
  for (const record of page.records.toReversed()) {
89
114
  if (!isMarAgentEvent(record.payload))
@@ -101,13 +126,26 @@ export async function openMarAgentSessionCatalog(input) {
101
126
  return undefined;
102
127
  },
103
128
  fork: async (sessionId, options) => {
129
+ assertOpen();
104
130
  const session = await store.forkSession(sessionId, options);
105
131
  return { ...session, source: session.source ?? { type: 'user' } };
106
132
  },
107
- rollback: (sessionId, options) => store.rollbackSession(sessionId, options),
108
- clear: (sessionId) => store.clearSession(sessionId),
109
- delete: (sessionId) => store.delete(sessionId),
110
- close: () => store.close()
133
+ rollback: async (sessionId, options) => {
134
+ assertOpen();
135
+ return store.rollbackSession(sessionId, options);
136
+ },
137
+ clear: async (sessionId) => {
138
+ assertOpen();
139
+ return store.clearSession(sessionId);
140
+ },
141
+ delete: async (sessionId) => {
142
+ assertOpen();
143
+ return store.delete(sessionId);
144
+ },
145
+ close: () => {
146
+ closure ??= Promise.resolve().then(() => (borrowed ? undefined : store.close()));
147
+ return closure;
148
+ }
111
149
  };
112
150
  }
113
151
  function parseCursor(cursor) {
@@ -143,9 +143,11 @@ export interface ContextGcCheckpointPayload extends ContextGcCheckpointDraft {
143
143
  /** Disposable memoization for one evaluation's immutable record snapshot. */
144
144
  export declare class ContextGcCheckpointCache {
145
145
  #private;
146
- read(records: readonly SessionRecord[], boundarySequence?: number, workspace?: string): ReturnType<typeof latestValidContextGcCheckpoint>;
146
+ read(records: readonly SessionRecord[], boundarySequence?: number): ReturnType<typeof latestValidContextGcCheckpoint>;
147
147
  }
148
148
  export declare function contextGcMinimumUsageGrowth(contextWindowTokens: number): number;
149
+ /** Compare adjacent checkpoint triggers only within the same model/window scope. */
150
+ export declare function contextGcCheckpointIntervalEligible(trigger: ContextGcCheckpointDraft['trigger'], previous?: ContextGcCheckpointDraft['trigger']): boolean;
149
151
  export declare function contextGcLogicalPathKey(path: string, workspace: string, platform?: ContextGcPlatform): string | undefined;
150
152
  export declare function contextGcTargetKey(target: ContextGcTarget): string;
151
153
  export declare function contextGcReplacementKey(replacement: ContextGcReplacement): string;
@@ -154,7 +156,7 @@ export declare function contextGcBaseSequence(base: ContextGcBase): number;
154
156
  export declare function normalizeContextGcReplacements(replacements: readonly ContextGcReplacement[]): ContextGcReplacement[] | undefined;
155
157
  export declare function parseContextGcCheckpoint(record: SessionRecord | undefined): ContextGcCheckpointPayload | undefined;
156
158
  export declare function validateContextGcCheckpoint(records: readonly SessionRecord[], record: SessionRecord, workspace?: string | undefined, committedReplacements?: readonly ContextGcReplacement[]): ContextGcCheckpointPayload | undefined;
157
- export declare function latestValidContextGcCheckpoint(records: readonly SessionRecord[], boundarySequence?: number, _workspace?: string): {
159
+ export declare function latestValidContextGcCheckpoint(records: readonly SessionRecord[], boundarySequence?: number): {
158
160
  readonly record: SessionRecord;
159
161
  readonly payload: ContextGcCheckpointPayload;
160
162
  } | undefined;
@@ -9,20 +9,28 @@ export const CONTEXT_GC_MAX_TARGETS = 4_096;
9
9
  /** Disposable memoization for one evaluation's immutable record snapshot. */
10
10
  export class ContextGcCheckpointCache {
11
11
  #snapshot;
12
- read(records, boundarySequence = records.at(-1)?.sequence ?? 0, workspace) {
12
+ read(records, boundarySequence = records.at(-1)?.sequence ?? 0) {
13
13
  const cached = this.#snapshot;
14
- if (cached?.records === records &&
15
- cached.boundarySequence === boundarySequence &&
16
- cached.workspace === workspace)
14
+ if (cached?.records === records && cached.boundarySequence === boundarySequence)
17
15
  return cached.value;
18
- const value = latestValidContextGcCheckpoint(records, boundarySequence, workspace);
19
- this.#snapshot = { records, boundarySequence, workspace, value };
16
+ const value = latestValidContextGcCheckpoint(records, boundarySequence);
17
+ this.#snapshot = { records, boundarySequence, value };
20
18
  return value;
21
19
  }
22
20
  }
23
21
  export function contextGcMinimumUsageGrowth(contextWindowTokens) {
24
22
  return Math.max(2_000, Math.floor(contextWindowTokens * 0.05));
25
23
  }
24
+ /** Compare adjacent checkpoint triggers only within the same model/window scope. */
25
+ export function contextGcCheckpointIntervalEligible(trigger, previous) {
26
+ if (!previous ||
27
+ trigger.modelId !== previous.modelId ||
28
+ trigger.contextWindowTokens !== previous.contextWindowTokens)
29
+ return true;
30
+ return (trigger.normalRolloutOrdinal - previous.normalRolloutOrdinal >= 2 &&
31
+ trigger.contextInputTokens - previous.contextInputTokens >=
32
+ contextGcMinimumUsageGrowth(trigger.contextWindowTokens));
33
+ }
26
34
  export function contextGcLogicalPathKey(path, workspace, platform) {
27
35
  if (!path || !workspace || path.includes('\0') || workspace.includes('\0'))
28
36
  return undefined;
@@ -198,7 +206,7 @@ export function validateContextGcCheckpoint(records, record, workspace = session
198
206
  }
199
207
  return payload;
200
208
  }
201
- export function latestValidContextGcCheckpoint(records, boundarySequence = records.at(-1)?.sequence ?? 0, _workspace) {
209
+ export function latestValidContextGcCheckpoint(records, boundarySequence = records.at(-1)?.sequence ?? 0) {
202
210
  const base = contextGcBaseFromRecords(records, boundarySequence);
203
211
  if (!base)
204
212
  return undefined;