@myagentroam/agent 0.11.61 → 0.11.63

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 (40) hide show
  1. package/dist/cli/catalog-file.d.ts +2 -0
  2. package/dist/cli/main.js +26 -15
  3. package/dist/cli/subagent/control.js +2 -2
  4. package/dist/cli/subagent/start.js +1 -1
  5. package/dist/cli/terminal-input.d.ts +12 -0
  6. package/dist/cli/terminal-input.js +86 -0
  7. package/dist/host/local-tool-registry.js +2 -2
  8. package/dist/model/configuration.d.ts +1 -0
  9. package/dist/model/configuration.js +1 -0
  10. package/dist/runtime/context/gc-admission.d.ts +5 -0
  11. package/dist/runtime/context/gc-admission.js +23 -0
  12. package/dist/runtime/context/gc-candidates-code.js +5 -6
  13. package/dist/runtime/context/gc-candidates-exec.js +3 -4
  14. package/dist/runtime/context/gc-candidates-read-patch.js +3 -4
  15. package/dist/runtime/context/gc-candidates-web.d.ts +1 -1
  16. package/dist/runtime/context/gc-candidates-web.js +6 -7
  17. package/dist/runtime/context/gc-runtime.js +3 -57
  18. package/dist/runtime/context/gc-tokens.d.ts +0 -4
  19. package/dist/runtime/context/gc-tokens.js +0 -18
  20. package/dist/runtime/context/gc.d.ts +1 -1
  21. package/dist/runtime/context/gc.js +24 -139
  22. package/dist/sdk/execution-context-coordinator.d.ts +1 -0
  23. package/dist/sdk/execution-context-coordinator.js +3 -1
  24. package/dist/sdk/session-execution-factory.js +4 -1
  25. package/dist/session/context-gc-checkpoint.d.ts +1 -4
  26. package/dist/session/context-gc-checkpoint.js +10 -22
  27. package/dist/session/context-gc-contract.d.ts +8 -8
  28. package/dist/session/store/jsonl/jsonl-store.js +1 -4
  29. package/dist/tools/agent/question.js +1 -1
  30. package/dist/tools/agent/sleep.js +1 -1
  31. package/dist/tools/agent/todo-write.js +1 -1
  32. package/dist/tools/code-mode/index.js +1 -1
  33. package/dist/tools/contracts.d.ts +1 -0
  34. package/dist/tools/local/apply-patch.d.ts +1 -1
  35. package/dist/tools/local/apply-patch.js +11 -2
  36. package/dist/tools/mcp/tool-search.js +1 -1
  37. package/dist/tools/scheduler.js +4 -4
  38. package/package.json +1 -1
  39. package/dist/runtime/context/gc-economics.d.ts +0 -35
  40. package/dist/runtime/context/gc-economics.js +0 -93
@@ -30,6 +30,7 @@ export declare function serializeModel(model: CliModelConfiguration): {
30
30
  defaultReasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | undefined;
31
31
  titleGeneration: boolean;
32
32
  codeMode: boolean;
33
+ gcEnabled?: boolean | undefined;
33
34
  fastMode?: boolean | undefined;
34
35
  serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
35
36
  hostedWebSearch: boolean;
@@ -65,6 +66,7 @@ export declare function serializeModel(model: CliModelConfiguration): {
65
66
  defaultReasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | undefined;
66
67
  titleGeneration: boolean;
67
68
  codeMode: boolean;
69
+ gcEnabled?: boolean | undefined;
68
70
  fastMode?: boolean | undefined;
69
71
  serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
70
72
  hostedWebSearch: boolean;
package/dist/cli/main.js CHANGED
@@ -9,6 +9,7 @@ import { readModelCatalog, writeModelCatalog } from './catalog-file.js';
9
9
  import { AgentError, createAgent, agentReasoningEffortSchema, resolveModelDefaultReasoningEffort, SecretValue, testAgentModelTransport } from '../index.js';
10
10
  import { CodexAuthFileCredentialProvider } from './codex-auth-file.js';
11
11
  import { generateSessionTitle } from '../shared/auto-title.js';
12
+ import { TerminalInput, TerminalInputClosed } from './terminal-input.js';
12
13
  const HELP = `maragent - MAR coding agent
13
14
 
14
15
  Usage:
@@ -146,6 +147,7 @@ async function modelsCommand(home, arguments_, codexAuthFile) {
146
147
  const defaultReasoningEffortOption = option('--default-reasoning-effort');
147
148
  const model = {
148
149
  useResourceUrl: existing?.useResourceUrl ?? false,
150
+ gcEnabled: existing?.gcEnabled ?? true,
149
151
  id,
150
152
  name: required(option('--name', existing?.name), '--name'),
151
153
  protocol,
@@ -261,21 +263,22 @@ async function main() {
261
263
  // `run` does not need stdin until a question occurs. Create
262
264
  // readline lazily so a closed or pseudo-TTY stream in an external harness
263
265
  // cannot fail the execution before its first model request.
264
- let terminal;
265
- const requireTerminal = () => (terminal ??= createInterface({ input: stdin, output: stdout }));
266
+ const terminal = new TerminalInput(() => createInterface({ input: stdin, output: stdout }));
266
267
  const host = new LocalAgentHost({
267
268
  homeDirectory: home,
268
269
  workspace,
269
- question: async (request) => {
270
+ question: async (request, signal) => {
270
271
  if (!stdin.isTTY)
271
272
  throw new AgentError('MAR_AGENT_USER_INPUT_UNAVAILABLE', 'Interactive input is unavailable.');
272
- const answers = {};
273
- for (const question of request.questions) {
274
- const value = question;
275
- const id = typeof value.id === 'string' ? value.id : String(Object.keys(answers).length);
276
- answers[id] = await requireTerminal().question(`${typeof value.question === 'string' ? value.question : JSON.stringify(question)} `);
277
- }
278
- return answers;
273
+ return terminal.run(signal, async (ask) => {
274
+ const answers = {};
275
+ for (const question of request.questions) {
276
+ const value = question;
277
+ const id = typeof value.id === 'string' ? value.id : String(Object.keys(answers).length);
278
+ answers[id] = await ask(`${typeof value.question === 'string' ? value.question : JSON.stringify(question)} `);
279
+ }
280
+ return answers;
281
+ });
279
282
  }
280
283
  });
281
284
  const runtime = runtimeModels(catalog.models, codexAuthFile);
@@ -311,7 +314,7 @@ async function main() {
311
314
  const listed = (await agent.listSessions());
312
315
  for (const item of listed.sessions ?? [])
313
316
  stdout.write(`${item.id}\t${item.title}\t${item.lastActivityAt}\n`);
314
- resumeId = (await requireTerminal().question('Session ID: ')).trim();
317
+ resumeId = (await terminal.run(undefined, (ask) => ask('Session ID: '))).trim();
315
318
  if (!resumeId)
316
319
  throw new AgentError('MAR_AGENT_CLI_ARGUMENT_INVALID', 'Session id is required.');
317
320
  }
@@ -329,7 +332,7 @@ async function main() {
329
332
  if (activeHandle)
330
333
  activeHandle.cancel();
331
334
  else
332
- terminal?.close();
335
+ terminal.close();
333
336
  titleController?.abort();
334
337
  process.exitCode = 130;
335
338
  if (interrupts > 1)
@@ -338,8 +341,16 @@ async function main() {
338
341
  process.on('SIGINT', interrupt);
339
342
  try {
340
343
  do {
341
- if (interactive)
342
- prompt = await requireTerminal().question('> ');
344
+ if (interactive) {
345
+ try {
346
+ prompt = await terminal.run(undefined, (ask) => ask('> '));
347
+ }
348
+ catch (error) {
349
+ if (error instanceof TerminalInputClosed)
350
+ break;
351
+ throw error;
352
+ }
353
+ }
343
354
  if (!prompt.trim())
344
355
  break;
345
356
  const invocation = parseExecutionInvocation(prompt);
@@ -409,7 +420,7 @@ async function main() {
409
420
  }
410
421
  }
411
422
  finally {
412
- terminal?.close();
423
+ terminal.close();
413
424
  await agent.dispose();
414
425
  }
415
426
  }
@@ -49,7 +49,7 @@ export function registerControlTools(context, agent, observations, warmObservati
49
49
  name: 'agent_send',
50
50
  description: 'Send a message to a direct child or report to the direct parent.',
51
51
  allowedCallers: ['direct', 'programmatic'],
52
- parallelSafety: 'serial',
52
+ parallelSafety: 'safe',
53
53
  inputSchema: {
54
54
  type: 'object',
55
55
  properties: {
@@ -70,7 +70,7 @@ export function registerControlTools(context, agent, observations, warmObservati
70
70
  name: 'agent_stop',
71
71
  description: 'Stop a direct child execution and wait for its cleanup.',
72
72
  allowedCallers: ['direct', 'programmatic'],
73
- parallelSafety: 'serial',
73
+ parallelSafety: 'safe',
74
74
  inputSchema: {
75
75
  type: 'object',
76
76
  properties: {
@@ -44,7 +44,7 @@ export function registerStart(context, agent, admission) {
44
44
  name: 'agent_start',
45
45
  description: 'Start a child Session and return its identity without waiting for completion.',
46
46
  allowedCallers: ['direct', 'programmatic'],
47
- parallelSafety: 'serial',
47
+ parallelSafety: 'safe',
48
48
  inputSchema: {
49
49
  type: 'object',
50
50
  properties: {
@@ -0,0 +1,12 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ export declare class TerminalInputClosed extends Error {
3
+ constructor();
4
+ }
5
+ /** One interactive transaction at a time per terminal, including multi-question requests. */
6
+ export declare class TerminalInput {
7
+ #private;
8
+ private readonly create;
9
+ constructor(create: () => ReturnType<typeof createInterface>);
10
+ run<T>(signal: AbortSignal | undefined, work: (ask: (prompt: string) => Promise<string>) => Promise<T>): Promise<T>;
11
+ close(): void;
12
+ }
@@ -0,0 +1,86 @@
1
+ export class TerminalInputClosed extends Error {
2
+ constructor() {
3
+ super('Terminal input is closed.');
4
+ }
5
+ }
6
+ /** One interactive transaction at a time per terminal, including multi-question requests. */
7
+ export class TerminalInput {
8
+ create;
9
+ #terminal;
10
+ #tail = Promise.resolve();
11
+ #lines = [];
12
+ #waiter;
13
+ #closed = false;
14
+ constructor(create) {
15
+ this.create = create;
16
+ }
17
+ run(signal, work) {
18
+ const operation = this.#tail.then(async () => {
19
+ signal?.throwIfAborted();
20
+ return work((prompt) => this.#ask(prompt, signal));
21
+ });
22
+ this.#tail = operation.then(() => undefined, () => undefined);
23
+ if (!signal)
24
+ return operation;
25
+ return new Promise((resolve, reject) => {
26
+ const aborted = () => reject(signal.reason);
27
+ if (signal.aborted)
28
+ aborted();
29
+ else
30
+ signal.addEventListener('abort', aborted, { once: true });
31
+ operation.then(resolve, reject).finally(() => signal.removeEventListener('abort', aborted));
32
+ });
33
+ }
34
+ #ask(prompt, signal) {
35
+ signal?.throwIfAborted();
36
+ if (this.#closed) {
37
+ const line = this.#lines.shift();
38
+ return line === undefined ? Promise.reject(new TerminalInputClosed()) : Promise.resolve(line);
39
+ }
40
+ if (!this.#terminal) {
41
+ this.#terminal = this.create();
42
+ this.#terminal.on('line', (line) => {
43
+ const waiter = this.#waiter;
44
+ if (waiter) {
45
+ this.#waiter = undefined;
46
+ waiter.resolve(line);
47
+ }
48
+ else
49
+ this.#lines.push(line);
50
+ });
51
+ this.#terminal.on('close', () => {
52
+ this.#closed = true;
53
+ const waiter = this.#waiter;
54
+ this.#waiter = undefined;
55
+ waiter?.reject(new TerminalInputClosed());
56
+ });
57
+ }
58
+ this.#terminal.setPrompt(prompt);
59
+ this.#terminal.prompt();
60
+ const line = this.#lines.shift();
61
+ if (line !== undefined)
62
+ return Promise.resolve(line);
63
+ return new Promise((resolve, reject) => {
64
+ const finish = (callback) => {
65
+ signal?.removeEventListener('abort', abort);
66
+ callback();
67
+ };
68
+ const abort = () => {
69
+ this.#waiter = undefined;
70
+ finish(() => reject(signal.reason));
71
+ };
72
+ this.#waiter = {
73
+ resolve: (value) => finish(() => resolve(value)),
74
+ reject: (error) => finish(() => reject(error))
75
+ };
76
+ signal?.addEventListener('abort', abort, { once: true });
77
+ if (signal?.aborted)
78
+ abort();
79
+ });
80
+ }
81
+ close() {
82
+ this.#closed = true;
83
+ this.#lines = [];
84
+ this.#terminal?.close();
85
+ }
86
+ }
@@ -71,8 +71,8 @@ export class LocalToolRegistry {
71
71
  ],
72
72
  [
73
73
  'apply_patch',
74
- async (arguments_) => {
75
- const result = await patch.execute(applyPatchToolInput(arguments_));
74
+ async (arguments_, context) => {
75
+ const result = await patch.execute(applyPatchToolInput(arguments_), context.signal);
76
76
  return { content: JSON.stringify(result), data: result };
77
77
  }
78
78
  ],
@@ -56,6 +56,7 @@ declare const rawModelSchema: z.ZodObject<{
56
56
  subagentModel: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
57
57
  titleGeneration: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
58
58
  codeMode: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
59
+ gcEnabled: z.ZodOptional<z.ZodBoolean>;
59
60
  useResourceUrl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
60
61
  fastMode: z.ZodOptional<z.ZodBoolean>;
61
62
  serviceTier: z.ZodOptional<z.ZodEnum<{
@@ -38,6 +38,7 @@ const modelFieldsSchema = z
38
38
  subagentModel: z.boolean().optional().default(false),
39
39
  titleGeneration: z.boolean().optional().default(false),
40
40
  codeMode: z.boolean().optional().default(false),
41
+ gcEnabled: z.boolean().optional(),
41
42
  useResourceUrl: z.boolean().optional().default(false),
42
43
  fastMode: z.boolean().optional(),
43
44
  serviceTier: z.enum(['auto', 'default', 'flex', 'priority', 'fast', 'ultrafast']).optional(),
@@ -0,0 +1,5 @@
1
+ import type { ModelUsage } from '../../model/contracts.js';
2
+ export declare const COMPACT_THRESHOLD_RATIO: number;
3
+ export declare function contextGcMinimumReclaimedTokens(contextWindowTokens: number): number;
4
+ export declare function contextGcEligibleUsage(usage: ModelUsage, contextWindowTokens: number): number | undefined;
5
+ export declare function modelContextUsage(value: ModelUsage | Record<string, unknown>): number | undefined;
@@ -0,0 +1,23 @@
1
+ import { AGENT_EXECUTION_POLICY } from '../execution-policy.js';
2
+ const CONTEXT_GC_START_RATIO = 0.5;
3
+ export const COMPACT_THRESHOLD_RATIO = AGENT_EXECUTION_POLICY.backgroundCompactionPercent / 100;
4
+ export function contextGcMinimumReclaimedTokens(contextWindowTokens) {
5
+ return Math.floor(contextWindowTokens / 10) + 1;
6
+ }
7
+ export function contextGcEligibleUsage(usage, contextWindowTokens) {
8
+ const contextUsageTokens = modelContextUsage(usage);
9
+ if (contextUsageTokens === undefined ||
10
+ !Number.isSafeInteger(contextWindowTokens) ||
11
+ contextWindowTokens <= 0)
12
+ return undefined;
13
+ const usageRatio = contextUsageTokens / contextWindowTokens;
14
+ if (usageRatio < CONTEXT_GC_START_RATIO || usageRatio >= COMPACT_THRESHOLD_RATIO)
15
+ return undefined;
16
+ return contextUsageTokens;
17
+ }
18
+ export function modelContextUsage(value) {
19
+ const context = value.contextInputTokens ?? value.inputTokens;
20
+ return typeof context === 'number' && Number.isSafeInteger(context) && context >= 0
21
+ ? context
22
+ : undefined;
23
+ }
@@ -1,8 +1,7 @@
1
1
  import { retainedCodeInputs } from '../../session/context-gc-observations.js';
2
2
  import { contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
3
3
  import { codeCellCompletionSequence, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, hasGenericContextGcPreview, normalizeContextGcCodeModeInput } from '../../session/context-gc-replacements.js';
4
- import { CONTEXT_GC_POLICY } from './gc-economics.js';
5
- import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
4
+ import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
6
5
  import { targetFor, isRecord } from './gc-candidates.js';
7
6
  export function collectCodeModeCandidates(input, groups, existingTargets, candidateSavings) {
8
7
  const eligible = [];
@@ -44,9 +43,9 @@ export function collectCodeModeCandidates(input, groups, existingTargets, candid
44
43
  continue;
45
44
  if (contextGcProtectionMature(protection))
46
45
  eligible.push(candidate);
47
- else if (candidate.observedRollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
46
+ else if (candidate.observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
48
47
  protectedTokens += reclaimedTokens;
49
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - candidate.observedRollouts);
48
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - candidate.observedRollouts);
50
49
  }
51
50
  }
52
51
  if (group.failed || !group.completed)
@@ -107,9 +106,9 @@ export function collectCodeModeCandidates(input, groups, existingTargets, candid
107
106
  continue;
108
107
  if (contextGcProtectionMature(protection))
109
108
  eligible.push(candidate);
110
- else if (observedRollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
109
+ else if (observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
111
110
  protectedTokens += reclaimedTokens;
112
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
111
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - observedRollouts);
113
112
  }
114
113
  }
115
114
  return { eligible, protectedTokens, retryAfterRollouts };
@@ -1,7 +1,6 @@
1
1
  import { contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
2
2
  import { contextGcExecInputReplacement, contextGcExecOutputReplacement, normalizeContextGcExecResult } from '../../session/context-gc-replacements.js';
3
- import { CONTEXT_GC_POLICY } from './gc-economics.js';
4
- import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
3
+ import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
5
4
  import { targetFor, isRecord } from './gc-candidates.js';
6
5
  import { terminalExecLifecycles } from '../../session/context-gc-exec-lifecycle.js';
7
6
  export function collectExecCandidates(input, groups, existingTargets, candidateSavings) {
@@ -85,9 +84,9 @@ export function collectExecCandidates(input, groups, existingTargets, candidateS
85
84
  mature.push(replacement);
86
85
  observedRollouts = Math.min(observedRollouts, protection.rollouts);
87
86
  }
88
- else if (protection.rollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
87
+ else if (protection.rollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
89
88
  awaitingRollouts.push(replacement);
90
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - protection.rollouts);
89
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - protection.rollouts);
91
90
  }
92
91
  }
93
92
  if (awaitingRollouts.length)
@@ -1,8 +1,7 @@
1
1
  import { contextGcLogicalPathKey, contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
2
2
  import { contextGcApplyPatchInputReplacement, contextGcPatchResultFromCompletedPayload, contextGcPatchResultFromFailedPayload, contextGcStaleReadReplacement, contextGcReadRangeCovers, contextGcCoveredReadReplacement } from '../../session/context-gc-replacements.js';
3
3
  import { parseReadContextOutput } from '../../prompts/read-context.js';
4
- import { CONTEXT_GC_POLICY } from './gc-economics.js';
5
- import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
4
+ import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
6
5
  import { targetFor } from './gc-candidates.js';
7
6
  export function collectDeterministicCandidates(input, groups, existingReplacements, existingTargets, candidateSavings) {
8
7
  const eligible = [];
@@ -108,9 +107,9 @@ export function collectDeterministicCandidates(input, groups, existingReplacemen
108
107
  mature.push(replacement);
109
108
  observedRollouts = Math.min(observedRollouts, protection.rollouts);
110
109
  }
111
- else if (protection.rollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
110
+ else if (protection.rollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
112
111
  awaitingRollouts.push(replacement);
113
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - protection.rollouts);
112
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - protection.rollouts);
114
113
  }
115
114
  }
116
115
  if (awaitingRollouts.length) {
@@ -4,7 +4,7 @@ export declare function collectWebCandidates(input: ContextGcPlannerInput, group
4
4
  protectedTokens: number;
5
5
  retryAfterRollouts: number;
6
6
  };
7
- /** Generic successful tool results share output economics, without inferring resource state. */
7
+ /** Generic successful tool results share output retention rules, without inferring resource state. */
8
8
  export declare function collectToolOutputCandidates(input: ContextGcPlannerInput, groups: ReadonlyMap<string, ToolGroup>, existingTargets: ReadonlySet<string>, candidateSavings: CandidateSavings): {
9
9
  eligible: ContextCandidate[];
10
10
  protectedTokens: number;
@@ -1,8 +1,7 @@
1
1
  import { contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
2
2
  import { completedTodoWrite, todoInputReplacement, todoSnapshotReplacement, visibleTodoSnapshot } from '../../session/context-gc-todo.js';
3
3
  import { contextGcWebOutputReplacement, contextGcToolOutputReplacement, contextGcSkillOutputReplacement, hasGenericContextGcPreview, isGenericContextGcTool } from '../../session/context-gc-replacements.js';
4
- import { CONTEXT_GC_POLICY } from './gc-economics.js';
5
- import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
4
+ import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
6
5
  import { targetFor, isRecord } from './gc-candidates.js';
7
6
  export function collectWebCandidates(input, groups, existingTargets, candidateSavings) {
8
7
  const eligible = [];
@@ -49,14 +48,14 @@ export function collectWebCandidates(input, groups, existingTargets, candidateSa
49
48
  continue;
50
49
  if (contextGcProtectionMature(protection))
51
50
  eligible.push(candidate);
52
- else if (observedRollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
51
+ else if (observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
53
52
  protectedTokens += reclaimedTokens;
54
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
53
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - observedRollouts);
55
54
  }
56
55
  }
57
56
  return { eligible, protectedTokens, retryAfterRollouts };
58
57
  }
59
- /** Generic successful tool results share output economics, without inferring resource state. */
58
+ /** Generic successful tool results share output retention rules, without inferring resource state. */
60
59
  export function collectToolOutputCandidates(input, groups, existingTargets, candidateSavings) {
61
60
  const eligible = [];
62
61
  let protectedTokens = 0;
@@ -260,9 +259,9 @@ export function collectToolOutputCandidates(input, groups, existingTargets, cand
260
259
  continue;
261
260
  if (contextGcProtectionMature(protection))
262
261
  eligible.push(candidate);
263
- else if (observedRollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
262
+ else if (observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
264
263
  protectedTokens += reclaimedTokens;
265
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
264
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - observedRollouts);
266
265
  }
267
266
  }
268
267
  return { eligible, protectedTokens, retryAfterRollouts };
@@ -1,20 +1,16 @@
1
1
  import { isDeepStrictEqual } from 'node:util';
2
- import { ContextGcCheckpointCache, contextGcMinimumUsageGrowth, contextGcReplacementKey } from '../../session/context-gc-checkpoint.js';
3
- import { CONTEXT_GC_POLICY, contextGcEligibleUsage, inspectContextGc } from './gc.js';
2
+ import { ContextGcCheckpointCache, contextGcReplacementKey } from '../../session/context-gc-checkpoint.js';
3
+ import { contextGcEligibleUsage, inspectContextGc } from './gc.js';
4
4
  import { buildContextProjection } from './projection.js';
5
5
  /** Only scheduling metadata survives a call; records and validation caches do not. */
6
6
  export class ContextGcRuntime {
7
7
  #scope = '';
8
8
  #lastUsage;
9
- #checkpoint;
10
- #rolloutsSinceCheckpoint = 0;
11
9
  #failures = 0;
12
10
  #remaining = 0;
13
11
  reset() {
14
12
  this.#scope = '';
15
13
  this.#lastUsage = undefined;
16
- this.#checkpoint = undefined;
17
- this.#rolloutsSinceCheckpoint = 0;
18
14
  this.#failures = 0;
19
15
  this.#remaining = 0;
20
16
  }
@@ -44,40 +40,18 @@ export class ContextGcRuntime {
44
40
  this.#remaining = 0;
45
41
  }
46
42
  this.#lastUsage = usage;
47
- if (this.#checkpoint)
48
- this.#rolloutsSinceCheckpoint++;
49
43
  const maximumInterval = usage / input.contextWindowTokens >= 0.8 ? 2 : 8;
50
44
  this.#remaining = Math.min(this.#remaining, maximumInterval - 1);
51
45
  if (this.#remaining > 0 && !mediaRequired) {
52
46
  this.#remaining--;
53
47
  return false;
54
48
  }
55
- if (!mediaRequired &&
56
- this.#checkpoint?.trigger.modelId === input.modelId &&
57
- this.#checkpoint.trigger.contextWindowTokens === input.contextWindowTokens &&
58
- !this.#checkpoint.protectedCandidatesPending &&
59
- this.#rolloutsSinceCheckpoint < CONTEXT_GC_POLICY.largeExecProtectionRollouts &&
60
- usage - this.#checkpoint.trigger.contextInputTokens <
61
- contextGcMinimumUsageGrowth(input.contextWindowTokens))
62
- return false;
63
49
  const attempt = await attemptContextGc(input);
64
50
  if (attempt.result) {
65
- this.#checkpoint = {
66
- trigger: { ...attempt.result.projection.checkpoint.trigger },
67
- protectedCandidatesPending: attempt.result.projection.checkpoint.metrics.protectedCandidateTokens > 0
68
- };
69
- this.#rolloutsSinceCheckpoint = 0;
70
51
  this.#failures = 0;
71
- this.#remaining = 1;
52
+ this.#remaining = 0;
72
53
  }
73
54
  else {
74
- if (attempt.checkpointTrigger)
75
- this.#checkpoint = {
76
- trigger: { ...attempt.checkpointTrigger },
77
- protectedCandidatesPending: attempt.protectedCandidatesPending === true
78
- };
79
- if (attempt.checkpointTrigger)
80
- this.#rolloutsSinceCheckpoint = 0;
81
55
  this.#failures = Math.min(3, this.#failures + 1);
82
56
  this.#remaining =
83
57
  Math.min(2 ** this.#failures, maximumInterval, attempt.retryAfterRollouts ?? Infinity) - 1;
@@ -107,30 +81,6 @@ async function attemptContextGc(input) {
107
81
  records =
108
82
  input.records ?? (await input.store.readExecutionState(input.sessionId)).contextRecords;
109
83
  const checkpointCache = new ContextGcCheckpointCache();
110
- const previous = checkpointCache.read(records);
111
- if (previous) {
112
- deferred = {
113
- ...deferred,
114
- checkpointTrigger: previous.payload.trigger,
115
- protectedCandidatesPending: previous.payload.metrics.protectedCandidateTokens > 0
116
- };
117
- }
118
- if (!mediaRequired &&
119
- previous?.payload.trigger.modelId === input.modelId &&
120
- previous.payload.trigger.contextWindowTokens === input.contextWindowTokens) {
121
- const rolloutGrowth = records.filter((record) => record.sequence > previous.record.sequence &&
122
- record.type === 'model.attempt' &&
123
- isRecord(record.payload) &&
124
- record.payload.purpose === 'EXECUTION' &&
125
- record.payload.state === 'SUCCEEDED').length;
126
- const minimumRollouts = previous.payload.metrics.protectedCandidateTokens > 0
127
- ? 2
128
- : CONTEXT_GC_POLICY.largeExecProtectionRollouts;
129
- if (rolloutGrowth < minimumRollouts &&
130
- contextUsage - previous.payload.trigger.contextInputTokens <
131
- contextGcMinimumUsageGrowth(input.contextWindowTokens))
132
- return deferred;
133
- }
134
84
  const projection = buildContextProjection(records, checkpointCache);
135
85
  const messages = projection.entries.map((entry) => entry.message);
136
86
  const inspection = inspectContextGc({
@@ -155,11 +105,8 @@ async function attemptContextGc(input) {
155
105
  if (inspection.retryAfterRollouts !== undefined)
156
106
  deferred = {
157
107
  ...deferred,
158
- protectedCandidatesPending: true,
159
108
  retryAfterRollouts: inspection.retryAfterRollouts
160
109
  };
161
- else if (!inspection.plan)
162
- deferred = { ...deferred, protectedCandidatesPending: false };
163
110
  const plan = inspection.plan;
164
111
  if (!plan)
165
112
  return deferred;
@@ -206,7 +153,6 @@ async function matchingCheckpointWasPersisted(store, sessionId, draft, records,
206
153
  if (latest?.payload.boundarySequence !== draft.boundarySequence ||
207
154
  !isDeepStrictEqual(latest.payload.base, draft.base) ||
208
155
  !isDeepStrictEqual(latest.payload.trigger, draft.trigger) ||
209
- !isDeepStrictEqual(latest.payload.economics, draft.economics) ||
210
156
  !isDeepStrictEqual(latest.payload.inlineMedia, draft.inlineMedia) ||
211
157
  !isDeepStrictEqual(latest.payload.metrics, draft.metrics))
212
158
  return false;
@@ -20,7 +20,3 @@ export declare function contextGcTokenEntries(input: TokenContext, messages: rea
20
20
  export declare function contextGcReclaimedTokens(input: TokenContext, before: readonly ModelMessage[], after: readonly ModelMessage[]): number;
21
21
  export declare function contextGcPrefixTokens(input: TokenContext): number;
22
22
  export declare function contextGcMessageTokens(input: TokenContext, messages: readonly ModelMessage[]): number;
23
- export declare function contextGcCacheImpact(input: TokenContext, before: readonly ModelMessage[], after: readonly ModelMessage[], cacheRead: number, reclaimedTokens: number): {
24
- preservedCachedPrefixTokens: number;
25
- reclaimedCachedTokens: number;
26
- };
@@ -83,24 +83,6 @@ export function contextGcPrefixTokens(input) {
83
83
  export function contextGcMessageTokens(input, messages) {
84
84
  return contextGcTokenEntries(input, messages).reduce((sum, entry) => sum + entry.tokens, 0);
85
85
  }
86
- export function contextGcCacheImpact(input, before, after, cacheRead, reclaimedTokens) {
87
- const afterByKey = new Map(contextGcTokenEntries(input, after).map((entry) => [entry.key, entry]));
88
- let offset = contextGcPrefixTokens(input);
89
- let earliest = cacheRead;
90
- let reclaimedCachedTokens = 0;
91
- for (const entry of contextGcTokenEntries(input, before)) {
92
- const replacement = afterByKey.get(entry.key);
93
- if (entry.text !== replacement?.text) {
94
- earliest = Math.min(earliest, offset);
95
- reclaimedCachedTokens += Math.min(Math.max(0, entry.tokens - (replacement?.tokens ?? 0)), Math.max(0, cacheRead - offset));
96
- }
97
- offset += entry.tokens;
98
- }
99
- return {
100
- preservedCachedPrefixTokens: Math.max(0, Math.min(cacheRead, earliest)),
101
- reclaimedCachedTokens: Math.min(reclaimedTokens, reclaimedCachedTokens)
102
- };
103
- }
104
86
  function tokensFor(input, text) {
105
87
  const cached = input.tokenCache?.get(text);
106
88
  if (cached !== undefined)
@@ -1,5 +1,5 @@
1
1
  import { type ContextGcPlan, type ContextGcInspection, type ContextGcPlannerInput } from './gc-contracts.js';
2
- export { CONTEXT_GC_POLICY, type ContextGcEconomicsResult, evaluateContextGcEconomics, contextGcMinimumReclaimedTokens, contextGcEligibleUsage } from './gc-economics.js';
2
+ export { contextGcMinimumReclaimedTokens, contextGcEligibleUsage } from './gc-admission.js';
3
3
  export { type ContextGcPlan, type ContextGcInspection, type ContextGcPlannerInput } from './gc-contracts.js';
4
4
  export declare function planContextGc(input: ContextGcPlannerInput): ContextGcPlan | undefined;
5
5
  export declare function inspectContextGc(input: ContextGcPlannerInput): ContextGcInspection;