@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
@@ -1,14 +1,14 @@
1
- import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcCheckpointIntervalEligible, contextGcMinimumUsageGrowth, contextGcTargetKey, normalizeContextGcReplacements } from '../../session/context-gc-checkpoint.js';
1
+ import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcTargetKey, normalizeContextGcReplacements } from '../../session/context-gc-checkpoint.js';
2
2
  import { buildContextGcProtection } from '../../session/context-gc-protection.js';
3
- import { CONTEXT_GC_TOKEN_ESTIMATOR, contextGcCacheImpact, contextGcMessageTokens, contextGcPrefixTokens, contextGcReclaimedTokens } from './gc-tokens.js';
3
+ import { CONTEXT_GC_TOKEN_ESTIMATOR, contextGcMessageTokens, contextGcPrefixTokens, contextGcReclaimedTokens } from './gc-tokens.js';
4
4
  import { applyContextGcReplacementsToMessages, providerToolCallId } from './projection.js';
5
- import { CONTEXT_GC_POLICY, COMPACT_THRESHOLD_RATIO, evaluateContextGcEconomics, contextGcMinimumReclaimedTokens, contextGcEligibleUsage, modelContextUsage } from './gc-economics.js';
5
+ import { contextGcMinimumReclaimedTokens, contextGcEligibleUsage, modelContextUsage } from './gc-admission.js';
6
6
  import { buildToolGroups, countSuccessfulNormalRollouts, isRecord } from './gc-candidates.js';
7
7
  import { collectCodeModeCandidates } from './gc-candidates-code.js';
8
8
  import { collectDeterministicCandidates } from './gc-candidates-read-patch.js';
9
9
  import { collectExecCandidates } from './gc-candidates-exec.js';
10
10
  import { collectToolOutputCandidates, collectWebCandidates } from './gc-candidates-web.js';
11
- export { CONTEXT_GC_POLICY, evaluateContextGcEconomics, contextGcMinimumReclaimedTokens, contextGcEligibleUsage } from './gc-economics.js';
11
+ export { contextGcMinimumReclaimedTokens, contextGcEligibleUsage } from './gc-admission.js';
12
12
  export function planContextGc(input) {
13
13
  return inspectContextGc(input).plan;
14
14
  }
@@ -36,20 +36,8 @@ export function inspectContextGc(input) {
36
36
  protectedCandidateTokens: text.plan?.metrics.protectedCandidateTokens ?? 0
37
37
  };
38
38
  if (text.plan) {
39
- const combined = evaluateCandidateSet(input, [], [
40
- {
41
- replacements: text.plan.draft.replacements,
42
- observedRollouts: text.plan.metrics.minimumObservedRollouts,
43
- evidenceSequence: text.plan.draft.boundarySequence
44
- }
45
- ], state, media.project);
46
- if (combined?.economics.passes) {
47
- const plan = finalizePlan(input, combined, true);
48
- if (plan)
49
- return { plan };
50
- }
51
- if (media.reason === 'TEXT_GC')
52
- return text;
39
+ const { project: _project, ...inlineMedia } = media;
40
+ return { ...text, plan: { ...text.plan, draft: { ...text.plan.draft, inlineMedia } } };
53
41
  }
54
42
  // Byte pressure has its own admission signal; it never relaxes text GC eligibility.
55
43
  const onlyMedia = evaluateCandidateSet(input, input.checkpointCache.read(input.records)?.payload.replacements ?? [], [
@@ -60,7 +48,7 @@ export function inspectContextGc(input) {
60
48
  }
61
49
  ], { ...state, stage: 'INLINE_MEDIA', mediaOnly: true }, media.project);
62
50
  const plan = onlyMedia && finalizePlan(input, onlyMedia, true);
63
- return plan ? { plan } : text;
51
+ return plan ? { ...text, plan } : text;
64
52
  }
65
53
  function inspectTextGc(input) {
66
54
  const contextUsageTokens = contextGcEligibleUsage(input.usage, input.contextWindowTokens);
@@ -77,26 +65,6 @@ function inspectTextGc(input) {
77
65
  return {};
78
66
  const normalRolloutOrdinal = countSuccessfulNormalRollouts(input.records, input.modelId, input.contextWindowTokens);
79
67
  const previous = input.checkpointCache.read(input.records);
80
- const checkpointEligible = contextGcCheckpointIntervalEligible({
81
- usageSequence: usageRecord.sequence,
82
- contextInputTokens: contextUsageTokens,
83
- contextWindowTokens: input.contextWindowTokens,
84
- normalRolloutOrdinal,
85
- modelId: input.modelId,
86
- platform: input.platform
87
- }, previous?.payload.trigger);
88
- // Discovery and economic evaluation have their own low-growth opportunities;
89
- // neither a protected candidate nor periodic discovery makes a draft eligible.
90
- if (previous?.payload.trigger.modelId === input.modelId &&
91
- previous.payload.trigger.contextWindowTokens === input.contextWindowTokens) {
92
- const rolloutGrowth = normalRolloutOrdinal - previous.payload.trigger.normalRolloutOrdinal;
93
- if (rolloutGrowth < 2 ||
94
- (previous.payload.metrics.protectedCandidateTokens === 0 &&
95
- rolloutGrowth < CONTEXT_GC_POLICY.largeExecProtectionRollouts &&
96
- contextUsageTokens - previous.payload.trigger.contextInputTokens <
97
- contextGcMinimumUsageGrowth(input.contextWindowTokens)))
98
- return {};
99
- }
100
68
  if (input.messages.length !== input.messageSources.length)
101
69
  return {};
102
70
  const groups = buildToolGroups(input.records, input.messages, input.messageSources);
@@ -118,13 +86,14 @@ function inspectTextGc(input) {
118
86
  codeMode.protectedTokens +
119
87
  web.protectedTokens +
120
88
  tools.protectedTokens;
121
- const combined = evaluateCandidateSet(input, existing, [
89
+ const eligible = [
122
90
  ...deterministic.eligible,
123
91
  ...exec.eligible,
124
92
  ...codeMode.eligible,
125
93
  ...web.eligible,
126
94
  ...tools.eligible
127
- ], {
95
+ ];
96
+ const combined = evaluateCandidateSet(input, existing, eligible, {
128
97
  stage: 'UNIFIED',
129
98
  contextUsageTokens,
130
99
  usageRecord,
@@ -132,7 +101,7 @@ function inspectTextGc(input) {
132
101
  protectedCandidateTokens
133
102
  });
134
103
  const retryAfterRollouts = Math.min(deterministic.retryAfterRollouts, exec.retryAfterRollouts, codeMode.retryAfterRollouts, web.retryAfterRollouts, tools.retryAfterRollouts);
135
- if (combined?.economics.passes && checkpointEligible) {
104
+ if (combined) {
136
105
  const plan = finalizePlan(input, combined);
137
106
  return plan
138
107
  ? {
@@ -143,10 +112,11 @@ function inspectTextGc(input) {
143
112
  }
144
113
  : {};
145
114
  }
146
- return protectedCandidateTokens > 0 &&
147
- (combined?.economics.passes ||
148
- protectedCandidateTokens >= contextGcMinimumReclaimedTokens(input.contextWindowTokens)) &&
149
- Number.isFinite(retryAfterRollouts)
115
+ // An already mature but sub-threshold batch can combine with newly mature
116
+ // candidates on the next rollout; no-candidate backoff must not delay it.
117
+ if (eligible.length > 0)
118
+ return { retryAfterRollouts: 1 };
119
+ return protectedCandidateTokens > 0 && Number.isFinite(retryAfterRollouts)
150
120
  ? { retryAfterRollouts }
151
121
  : {};
152
122
  }
@@ -170,23 +140,15 @@ function finalizePlan(input, evaluated, includeMedia = false) {
170
140
  beforeTokens: evaluated.beforeTokens,
171
141
  afterTokens: evaluated.afterTokens,
172
142
  reclaimedTokens: evaluated.reclaimedTokens,
173
- grossReductionRate: evaluated.economics.grossReductionRate,
174
- minimumRequiredReclaimedTokens: evaluated.minimumReclaimedTokens,
175
- cacheRebuildPenaltyCostUnits: evaluated.economics.cacheRebuildPenaltyCostUnits,
176
- requiredPositiveNetRollouts: evaluated.economics.requiredPositiveNetRollouts,
177
- projectedRolloutsUntilCompactAfterGc: evaluated.projectedRolloutsUntilCompactAfterGc,
178
- projectedTokensBeforeGc: evaluated.projectedTokensBeforeGc,
179
- projectedTokensAfterGc: evaluated.projectedTokensAfterGc,
180
- contextGrowthSampleCount: evaluated.contextGrowthSamples.length,
181
- contextGrowthP75Tokens: evaluated.contextGrowthP75Tokens,
182
- expectedNetSavedCostUnits: evaluated.economics.expectedNetSavedCostUnits,
143
+ ...(evaluated.mediaOnly
144
+ ? {}
145
+ : { minimumRequiredReclaimedTokens: evaluated.minimumReclaimedTokens }),
183
146
  minimumObservedRollouts: evaluated.minimumObservedRollouts,
184
147
  protectedCandidateTokens: evaluated.protectedCandidateTokens,
185
148
  eligibleCandidateCount: evaluated.eligibleCandidateCount,
186
149
  eligibleTargetCount: replacements.reduce((count, replacement) => count +
187
150
  1 +
188
- (replacement.kind === 'TOOL_CALL_INPUT' ? replacement.providerAliasTargets.length : 0), 0),
189
- cachePenaltyKnown: input.usage.cacheReadTokens !== undefined
151
+ (replacement.kind === 'TOOL_CALL_INPUT' ? replacement.providerAliasTargets.length : 0), 0)
190
152
  };
191
153
  const draft = {
192
154
  ...(inlineMedia ? { inlineMedia } : {}),
@@ -206,7 +168,6 @@ function finalizePlan(input, evaluated, includeMedia = false) {
206
168
  modelId: input.modelId,
207
169
  platform: input.platform
208
170
  },
209
- economics: CONTEXT_GC_POLICY,
210
171
  metrics
211
172
  };
212
173
  return { stage: evaluated.stage, draft, metrics };
@@ -222,48 +183,23 @@ function evaluateCandidateSet(input, existing, candidates, state, mediaProjectio
222
183
  return undefined;
223
184
  const projected = applyContextGcReplacementsToMessages(input.messages, input.messageSources, replacements);
224
185
  const projectedMessages = mediaProjection?.(projected.messages) ?? projected.messages;
225
- const reclaimedTokens = contextGcReclaimedTokens(input, input.messages, projectedMessages);
186
+ const baseline = applyContextGcReplacementsToMessages(input.messages, input.messageSources, existing);
187
+ const baselineMessages = mediaProjection?.(baseline.messages) ?? baseline.messages;
188
+ const reclaimedTokens = contextGcReclaimedTokens(input, baselineMessages, projectedMessages);
226
189
  const minimumReclaimedTokens = contextGcMinimumReclaimedTokens(input.contextWindowTokens);
227
190
  if (!state.mediaOnly && reclaimedTokens < minimumReclaimedTokens)
228
191
  return undefined;
229
- const beforeTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input, input.messages);
192
+ const beforeTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input, baselineMessages);
230
193
  const afterTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input, projectedMessages);
231
194
  if (!state.mediaOnly && reclaimedTokens === 0)
232
195
  return undefined;
233
- const cache = contextGcCacheImpact(input, input.messages, projectedMessages, input.usage.cacheReadTokens ?? 0, reclaimedTokens);
234
- const growthSamples = collectContextGrowthSamples(input.records, input.modelId, input.contextWindowTokens, input.checkpointCache);
235
- const contextGrowthP75Tokens = percentile75(growthSamples);
236
- const compactThresholdTokens = Math.floor(input.contextWindowTokens * COMPACT_THRESHOLD_RATIO);
237
- // Usage describes the request before its generated output and tool results.
238
- const pendingMessages = input.messages.filter((_message, index) => input.messageSources[index].sequence > state.usageRecord.sequence);
239
- const pendingTokens = Math.max(0, input.usage.outputTokens ?? 0) + contextGcMessageTokens(input, pendingMessages);
240
- const projectedTokensBeforeGc = state.contextUsageTokens + pendingTokens;
241
- const projectedTokensAfterGc = Math.max(0, projectedTokensBeforeGc - reclaimedTokens);
242
- const projectedRolloutsUntilCompactAfterGc = contextGrowthP75Tokens <= 0
243
- ? 0
244
- : Math.max(0, Math.floor((compactThresholdTokens - projectedTokensAfterGc) / Math.max(1, contextGrowthP75Tokens)));
245
- const economics = evaluateContextGcEconomics({
246
- contextUsageTokens: state.contextUsageTokens,
247
- contextWindowTokens: input.contextWindowTokens,
248
- reclaimedTokens,
249
- reclaimedCachedTokens: cache.reclaimedCachedTokens,
250
- cacheReadTokens: input.usage.cacheReadTokens ?? 0,
251
- preservedCachedPrefixTokens: cache.preservedCachedPrefixTokens
252
- });
253
196
  return {
254
197
  ...state,
255
- economics,
256
- reclaimedCachedTokens: cache.reclaimedCachedTokens,
257
198
  replacements,
258
199
  beforeTokens,
259
200
  afterTokens,
260
201
  reclaimedTokens,
261
- contextGrowthSamples: growthSamples,
262
- contextGrowthP75Tokens,
263
202
  minimumReclaimedTokens,
264
- projectedRolloutsUntilCompactAfterGc,
265
- projectedTokensBeforeGc,
266
- projectedTokensAfterGc,
267
203
  boundarySequence: input.records.at(-1)?.sequence ?? state.usageRecord.sequence,
268
204
  minimumObservedRollouts: Math.min(...candidates.map((candidate) => candidate.observedRollouts)),
269
205
  eligibleCandidateCount: candidates.length
@@ -286,51 +222,6 @@ function latestUsageRecord(records, selectedModelId) {
286
222
  }
287
223
  return latest;
288
224
  }
289
- function collectContextGrowthSamples(records, modelId, contextWindowTokens, checkpointCache) {
290
- const previous = checkpointCache.read(records);
291
- const base = contextGcBaseFromRecords(records);
292
- const startSequence = previous?.record.sequence ?? (base?.type === 'COMPACT' ? base.sequence : base?.firstSequence);
293
- let activeModelId = modelId;
294
- let activeWindow = contextWindowTokens;
295
- let previousUsage;
296
- const samples = [];
297
- for (const record of records) {
298
- if (startSequence !== undefined && record.sequence <= startSequence)
299
- continue;
300
- if (record.type === 'execution.header' && isRecord(record.payload)) {
301
- const nextModel = modelIdFromRecord(record);
302
- const nextWindow = record.payload.contextWindowTokens;
303
- if (nextModel !== modelId ||
304
- (typeof nextWindow === 'number' && nextWindow !== contextWindowTokens)) {
305
- activeModelId = nextModel ?? activeModelId;
306
- activeWindow =
307
- typeof nextWindow === 'number' && Number.isSafeInteger(nextWindow)
308
- ? nextWindow
309
- : activeWindow;
310
- previousUsage = undefined;
311
- }
312
- else {
313
- activeModelId = modelId;
314
- activeWindow = contextWindowTokens;
315
- }
316
- continue;
317
- }
318
- if (activeModelId !== modelId ||
319
- activeWindow !== contextWindowTokens ||
320
- !isRecord(record.payload) ||
321
- record.payload.type !== 'usage.updated')
322
- continue;
323
- const usage = modelContextUsage(record.payload);
324
- if (usage === undefined) {
325
- previousUsage = undefined;
326
- continue;
327
- }
328
- if (previousUsage !== undefined && usage > previousUsage)
329
- samples.push(usage - previousUsage);
330
- previousUsage = usage;
331
- }
332
- return samples.slice(-CONTEXT_GC_POLICY.contextGrowthSampleWindow);
333
- }
334
225
  /** Measure only affected call families, retaining native aliases for provider lowering. */
335
226
  function createCandidateSavingsEstimator(input, existing) {
336
227
  const byCall = new Map();
@@ -377,9 +268,3 @@ function modelIdFromRecord(record) {
377
268
  return undefined;
378
269
  return typeof record.payload.modelId === 'string' ? record.payload.modelId : undefined;
379
270
  }
380
- function percentile75(values) {
381
- if (values.length === 0)
382
- return 0;
383
- const sorted = [...values].sort((left, right) => left - right);
384
- return sorted[Math.max(0, Math.ceil(sorted.length * 0.75) - 1)];
385
- }
@@ -23,6 +23,7 @@ export declare class ExecutionContextCoordinator {
23
23
  readonly requests: Pick<CompactRequests, 'drain'>;
24
24
  readonly signal: AbortSignal;
25
25
  readonly window: number;
26
+ readonly gcEnabled?: boolean;
26
27
  readonly gc: Pick<ContextGcRuntime, 'run'>;
27
28
  readonly gcBase: Omit<ContextGcRuntimeInput, 'system' | 'tools' | 'usage' | 'inlineMedia' | 'mediaView' | 'getActiveProcessIds'>;
28
29
  start(input?: {
@@ -80,10 +80,12 @@ export class ExecutionContextCoordinator {
80
80
  this.ports.signal.throwIfAborted();
81
81
  this.ports.execution.assertContextValid();
82
82
  });
83
- if (!this.ports.compact.pending)
83
+ if (this.ports.gcEnabled !== false && !this.ports.compact.pending)
84
84
  await this.gc(round, usage, await this.ports.resources.gcCandidate(), true);
85
85
  }
86
86
  async gc(round, usage, media, includeProcesses = false) {
87
+ if (this.ports.gcEnabled === false || this.ports.compact.pending)
88
+ return false;
87
89
  const appendBoundary = this.ports.execution.appendBoundary;
88
90
  const committed = await this.ports.gc.run({
89
91
  ...this.ports.gcBase,
@@ -571,6 +571,7 @@ export function createSessionExecutionFactory(ports) {
571
571
  requests: compactRequests,
572
572
  signal: controller.signal,
573
573
  window: selectedModel.contextWindowTokens,
574
+ gcEnabled: selectedModel.gcEnabled ?? true,
574
575
  gc: contextGc,
575
576
  gcBase: {
576
577
  store,
@@ -724,7 +725,9 @@ export function createSessionExecutionFactory(ports) {
724
725
  },
725
726
  prepareResources: async ({ candidateRequest, request, toolScope }) => {
726
727
  resourceInput.observe(candidateRequest.messages);
727
- const media = await resourceInput.gcCandidate(request, candidateRequest.images);
728
+ const media = selectedModel.gcEnabled === false
729
+ ? undefined
730
+ : await resourceInput.gcCandidate(request, candidateRequest.images);
728
731
  if (media?.reason === 'HARD_LIMIT') {
729
732
  const committed = await contextCoordinator.gc({ request, toolScope }, modelLifecycle.usage, media);
730
733
  if (committed)
@@ -1,5 +1,5 @@
1
1
  import type { SessionRecord } from './record-contract.js';
2
- import type { ContextGcPlatform, ContextGcBase, ContextGcTarget, ContextGcReplacement, ContextGcCheckpointDraft, ContextGcCheckpointPayload } from './context-gc-contract.js';
2
+ import type { ContextGcPlatform, ContextGcBase, ContextGcTarget, ContextGcReplacement, ContextGcCheckpointPayload } from './context-gc-contract.js';
3
3
  export type { ContextGcPlatform, ContextGcEconomicsPolicy, ContextGcBase, ContextGcTarget, ContextGcReadItemReplacement, ContextGcReplacement, ContextGcMetrics, ContextGcCheckpointDraft, ContextGcCheckpointPayload } from './context-gc-contract.js';
4
4
  export declare const CONTEXT_GC_CHECKPOINT_VERSION: 1;
5
5
  export declare const CONTEXT_GC_MAX_TARGETS = 4096;
@@ -8,9 +8,6 @@ export declare class ContextGcCheckpointCache {
8
8
  #private;
9
9
  read(records: readonly SessionRecord[], boundarySequence?: number): ReturnType<typeof latestValidContextGcCheckpoint>;
10
10
  }
11
- export declare function contextGcMinimumUsageGrowth(contextWindowTokens: number): number;
12
- /** Compare adjacent checkpoint triggers only within the same model/window scope. */
13
- export declare function contextGcCheckpointIntervalEligible(trigger: ContextGcCheckpointDraft['trigger'], previous?: ContextGcCheckpointDraft['trigger']): boolean;
14
11
  export declare function contextGcLogicalPathKey(path: string, workspace: string, platform?: ContextGcPlatform): string | undefined;
15
12
  export declare function contextGcTargetKey(target: ContextGcTarget): string;
16
13
  export declare function contextGcReplacementKey(replacement: ContextGcReplacement): string;
@@ -22,19 +22,6 @@ export class ContextGcCheckpointCache {
22
22
  return value;
23
23
  }
24
24
  }
25
- export function contextGcMinimumUsageGrowth(contextWindowTokens) {
26
- return Math.max(2_000, Math.floor(contextWindowTokens * 0.05));
27
- }
28
- /** Compare adjacent checkpoint triggers only within the same model/window scope. */
29
- export function contextGcCheckpointIntervalEligible(trigger, previous) {
30
- if (!previous ||
31
- trigger.modelId !== previous.modelId ||
32
- trigger.contextWindowTokens !== previous.contextWindowTokens)
33
- return true;
34
- return (trigger.normalRolloutOrdinal - previous.normalRolloutOrdinal >= 2 &&
35
- trigger.contextInputTokens - previous.contextInputTokens >=
36
- contextGcMinimumUsageGrowth(trigger.contextWindowTokens));
37
- }
38
25
  export function contextGcLogicalPathKey(path, workspace, platform) {
39
26
  if (!path || !workspace || path.includes('\0') || workspace.includes('\0'))
40
27
  return undefined;
@@ -126,7 +113,7 @@ export function parseContextGcCheckpoint(record) {
126
113
  !Array.isArray(payload.replacements) ||
127
114
  (payload.replacements.length === 0 && payload.inlineMedia === undefined) ||
128
115
  !isContextGcTrigger(payload.trigger) ||
129
- !isContextGcEconomicsPolicy(payload.economics) ||
116
+ (payload.economics !== undefined && !isContextGcEconomicsPolicy(payload.economics)) ||
130
117
  !isContextGcMetrics(payload.metrics) ||
131
118
  (payload.inlineMedia !== undefined && !parseInlineMediaGc(payload.inlineMedia)))
132
119
  return undefined;
@@ -141,7 +128,7 @@ export function parseContextGcCheckpoint(record) {
141
128
  boundarySequence: payload.boundarySequence,
142
129
  replacements,
143
130
  trigger: structuredClone(payload.trigger),
144
- economics: structuredClone(payload.economics),
131
+ ...(payload.economics === undefined ? {} : { economics: structuredClone(payload.economics) }),
145
132
  metrics: structuredClone(payload.metrics),
146
133
  ...(payload.inlineMedia === undefined
147
134
  ? {}
@@ -1129,17 +1116,17 @@ function isContextGcMetrics(value) {
1129
1116
  'beforeTokens',
1130
1117
  'afterTokens',
1131
1118
  'reclaimedTokens',
1132
- 'grossReductionRate',
1133
- 'cacheRebuildPenaltyCostUnits',
1134
- 'projectedRolloutsUntilCompactAfterGc',
1135
- 'contextGrowthSampleCount',
1136
- 'contextGrowthP75Tokens',
1137
1119
  'minimumObservedRollouts',
1138
1120
  'protectedCandidateTokens',
1139
1121
  'eligibleCandidateCount',
1140
1122
  'eligibleTargetCount'
1141
1123
  ];
1142
1124
  const optionalNonnegative = [
1125
+ 'grossReductionRate',
1126
+ 'cacheRebuildPenaltyCostUnits',
1127
+ 'projectedRolloutsUntilCompactAfterGc',
1128
+ 'contextGrowthSampleCount',
1129
+ 'contextGrowthP75Tokens',
1143
1130
  'effectiveMinimumGrossReductionRate',
1144
1131
  'compactDeferralCreditCostUnits',
1145
1132
  'minimumRequiredReclaimedTokens',
@@ -1150,11 +1137,12 @@ function isContextGcMetrics(value) {
1150
1137
  ];
1151
1138
  return (nonnegative.every((key) => isNonnegativeFinite(value[key])) &&
1152
1139
  [
1153
- 'requiredPositiveNetRollouts',
1140
+ ...(value.requiredPositiveNetRollouts === undefined ? [] : ['requiredPositiveNetRollouts']),
1154
1141
  ...(value.requiredTargetRoiRollouts === undefined ? [] : ['requiredTargetRoiRollouts'])
1155
1142
  ].every((key) => isNonnegativeFinite(value[key]) || (value.stage === 'INLINE_MEDIA' && value[key] === null)) &&
1156
1143
  optionalNonnegative.every((key) => value[key] === undefined || isNonnegativeFinite(value[key])) &&
1157
- Number.isFinite(value.expectedNetSavedCostUnits) &&
1144
+ (value.expectedNetSavedCostUnits === undefined ||
1145
+ Number.isFinite(value.expectedNetSavedCostUnits)) &&
1158
1146
  (value.cacheInvestmentRoi === undefined || Number.isFinite(value.cacheInvestmentRoi)) &&
1159
1147
  (value.tokenEstimator === undefined || value.tokenEstimator === 'UTF8_BYTES_V1') &&
1160
1148
  (value.cachePenaltyKnown === undefined || typeof value.cachePenaltyKnown === 'boolean'));
@@ -105,21 +105,21 @@ export interface ContextGcMetrics {
105
105
  readonly beforeTokens: number;
106
106
  readonly afterTokens: number;
107
107
  readonly reclaimedTokens: number;
108
- readonly grossReductionRate: number;
108
+ readonly grossReductionRate?: number;
109
109
  readonly effectiveMinimumGrossReductionRate?: number;
110
110
  readonly minimumRequiredReclaimedTokens?: number;
111
- readonly cacheRebuildPenaltyCostUnits: number;
111
+ readonly cacheRebuildPenaltyCostUnits?: number;
112
112
  readonly compactDeferralCreditCostUnits?: number;
113
113
  readonly imminentCompactAvoidanceCreditCostUnits?: number;
114
114
  readonly appliedCompactCreditCostUnits?: number;
115
- readonly requiredPositiveNetRollouts: number | null;
115
+ readonly requiredPositiveNetRollouts?: number | null;
116
116
  readonly requiredTargetRoiRollouts?: number | null;
117
- readonly projectedRolloutsUntilCompactAfterGc: number;
117
+ readonly projectedRolloutsUntilCompactAfterGc?: number;
118
118
  readonly projectedTokensBeforeGc?: number;
119
119
  readonly projectedTokensAfterGc?: number;
120
- readonly contextGrowthSampleCount: number;
121
- readonly contextGrowthP75Tokens: number;
122
- readonly expectedNetSavedCostUnits: number;
120
+ readonly contextGrowthSampleCount?: number;
121
+ readonly contextGrowthP75Tokens?: number;
122
+ readonly expectedNetSavedCostUnits?: number;
123
123
  readonly cacheInvestmentRoi?: number;
124
124
  readonly minimumObservedRollouts: number;
125
125
  readonly protectedCandidateTokens: number;
@@ -141,7 +141,7 @@ export interface ContextGcCheckpointDraft {
141
141
  readonly modelId: string;
142
142
  readonly platform: ContextGcPlatform;
143
143
  };
144
- readonly economics: ContextGcEconomicsPolicy;
144
+ readonly economics?: ContextGcEconomicsPolicy;
145
145
  readonly metrics: ContextGcMetrics;
146
146
  }
147
147
  export interface ContextGcCheckpointPayload extends ContextGcCheckpointDraft {
@@ -9,7 +9,7 @@ import { cloneCoreJson } from '../../extension-checkpoint.js';
9
9
  import { acquireFileLock } from '../../../storage/file-lock.js';
10
10
  import { todoItemsFromSnapshot } from '../../todo.js';
11
11
  import { applyCodeModeStateMutations, parseCodeModeStateMutations, restoreCodeModeState } from '../../code-state.js';
12
- import { contextGcBaseFromRecords, contextGcCheckpointIntervalEligible, latestValidContextGcCheckpoint, normalizeContextGcReplacements, sameContextGcBase, validateContextGcCheckpoint } from '../../context-gc-checkpoint.js';
12
+ import { contextGcBaseFromRecords, latestValidContextGcCheckpoint, normalizeContextGcReplacements, sameContextGcBase, validateContextGcCheckpoint } from '../../context-gc-checkpoint.js';
13
13
  import { recordToolResources, readToolResultResource, writeToolResultResource } from '../../tool-result-resources.js';
14
14
  import { beginTreeDeleteTransaction, finishTreeDelete, finishTreeDeletePreparation, readTreeDeleteTransaction, recoverSessionStorageTransaction, stageTreeDelete } from './transaction-recovery.js';
15
15
  import { extensionCheckpointFromPayload, indexExtensionCheckpoint, restoreExtensionCheckpoint, updateSessionCurrentState } from '../current-state.js';
@@ -1293,9 +1293,6 @@ export class JsonlSessionStore {
1293
1293
  !sameContextGcBase(contextGcBaseFromRecords(records, boundarySequence), draft.base))
1294
1294
  throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC planning boundary is invalid.');
1295
1295
  const previous = latestValidContextGcCheckpoint(records, boundarySequence);
1296
- if ((!draft.inlineMedia || draft.inlineMedia.reason === 'TEXT_GC') &&
1297
- !contextGcCheckpointIntervalEligible(draft.trigger, previous?.payload.trigger))
1298
- throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC checkpoint interval validation failed.');
1299
1296
  const plannedReplacements = normalizeContextGcReplacements(draft.replacements);
1300
1297
  if (!plannedReplacements || (plannedReplacements.length === 0 && !draft.inlineMedia))
1301
1298
  throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC checkpoint must contain a replacement.');
@@ -4,7 +4,7 @@ import { TOOL_EXECUTION_LIMITS } from '../execution-limits.js';
4
4
  export const questionToolDefinition = {
5
5
  name: 'question',
6
6
  allowedCallers: ['direct'],
7
- parallelSafety: 'serial',
7
+ parallelSafety: 'safe',
8
8
  description: 'Pause for one to three tightly related user-owned decisions that materially change the result and cannot be inferred from the request, conversation, or relevant safe discovery. Prefer asking over guessing in Plan mode; do not avoid clarification by inventing configuration, branches, abstractions, or support for multiple outcomes. Provide 2-3 mutually exclusive options with consequences, put the recommended option first and identify it in its label or description, and use another question round if key ambiguity remains. This is not a permission/approval tool and should not interrupt routine in-scope work.',
9
9
  inputSchema: {
10
10
  type: 'object',
@@ -4,7 +4,7 @@ import { waitForToolCondition } from '../wait.js';
4
4
  export const sleepToolDefinition = {
5
5
  name: 'sleep',
6
6
  allowedCallers: ['direct'],
7
- parallelSafety: 'serial',
7
+ parallelSafety: 'safe',
8
8
  description: 'Pause for up to durationMs milliseconds. Returns early when user input or a new public subagent message is available. Returns elapsed time and the wake reason.',
9
9
  inputSchema: {
10
10
  type: 'object',
@@ -3,7 +3,7 @@ import { TOOL_EXECUTION_LIMITS } from '../execution-limits.js';
3
3
  export const todoWriteToolDefinition = {
4
4
  name: 'todo_write',
5
5
  allowedCallers: ['direct', 'programmatic'],
6
- parallelSafety: 'serial',
6
+ parallelSafety: 'safe',
7
7
  description: 'Replace this session’s entire Todo list with the supplied items; no prior read is required. Preserve stable IDs and statuses only when extending the same objective. For a materially different objective, include only the new objective’s items. Do not retain completed items from an earlier objective; when all current items are completed, default a substantive new request to replacement unless the user explicitly asks to continue, extend, verify, or follow up. Use only after Todo has been chosen for coordination-heavy work; straightforward bounded work does not need it. Keep at most one in_progress item and update statuses after meaningful stage changes. When another substantive tool call is already ready and does not depend on the Todo result, include both in the same model response instead of spending a response only for Todo bookkeeping.',
8
8
  inputSchema: {
9
9
  type: 'object',
@@ -78,7 +78,7 @@ observe.result(patched);
78
78
  additionalProperties: false
79
79
  },
80
80
  allowedCallers: ['direct'],
81
- parallelSafety: 'serial'
81
+ parallelSafety: 'safe'
82
82
  };
83
83
  export const codeExecWaitToolDefinition = {
84
84
  name: 'code_exec_wait',
@@ -13,6 +13,7 @@ export interface ClientToolDefinition extends ModelToolDefinition {
13
13
  eventInputFields?: readonly string[];
14
14
  /** Runtime-only exposure override; retains both permitted entry points with Code Mode enabled. */
15
15
  codeModeExposure?: 'both';
16
+ /** Runtime-only scheduling policy; defaults to safe. Serial calls are exclusive barriers. */
16
17
  parallelSafety?: 'safe' | 'serial';
17
18
  /** Preserved business-schema eligibility after framework metadata projection. */
18
19
  parameterless?: boolean;
@@ -15,5 +15,5 @@ export declare class ApplyPatchTool {
15
15
  #private;
16
16
  private readonly paths;
17
17
  constructor(paths: WorkspacePathResolver, operations?: Partial<PatchFileOperations>);
18
- execute(patch: string): Promise<ApplyPatchResult>;
18
+ execute(patch: string, signal?: AbortSignal): Promise<ApplyPatchResult>;
19
19
  }
@@ -10,7 +10,7 @@ export const applyPatchToolDefinition = {
10
10
  name: 'apply_patch',
11
11
  allowedCallers: ['direct', 'programmatic'],
12
12
  codeModeExposure: 'both',
13
- parallelSafety: 'serial',
13
+ parallelSafety: 'safe',
14
14
  description: `Preferred for precise, reviewable local edits. Do not batch all task files merely to reduce tool calls. Group only a few tightly coupled edits whose combined patch is quick to generate and review; split broad or independent work into successive bounded patches before composing them. Submit a raw *** Begin Patch / *** End Patch patch, each marker on its own line. Surrounding marker whitespace and an optional trailing *** are accepted; incomplete markers or extra text are rejected. Add File content lines, including blank lines, must start with +. Syntax errors include line/column and a bounded excerpt of the submitted patch with a caret at the error. Supports Add File, Update File, Delete File, and Move to. Add File may replace an existing regular file, and Move to may replace an existing regular destination; duplicate operations on the same resolved path remain invalid. Update hunks accept @@ or @@ <change context>. Context-only hunks, including trailing hunks, and unchanged file sections are validated in order but are not written or reported as edits. The patch must include a change or move. Context is searched forward with Codex-style exact, whitespace-tolerant, then common Unicode punctuation-tolerant matching. Syntax and resolved paths are validated before writing, then file units are committed in order. Each file unit is atomic, and a Move keeps its source and destination atomic. On MAR_AGENT_PATCH_PARTIAL, earlier files remain applied; the error lists applied, failed, and pending paths, so retry only failed and pending paths. Existing file permissions, line endings, and final-newline state are preserved. Returns changed file paths and operations with per-file and total line counts when all units succeed. In Code Mode, the Promise resolves on success and rejects with code/message on failure; the returned envelope has data.files and data.totals, not an ok or success flag. Oversized patches are rejected without advertising defensive limits as recommended patch sizes. A first-unit MAR_AGENT_PATCH_CONFLICT applies nothing; hunk match conflicts identify the affected path and hunk so you can reread only that file before retrying.`,
15
15
  inputMode: 'freeform',
16
16
  outputSchema: {
@@ -67,11 +67,20 @@ const utf8Decoder = new TextDecoder('utf-8', { fatal: true });
67
67
  export class ApplyPatchTool {
68
68
  paths;
69
69
  #fileOperations;
70
+ #tail = Promise.resolve();
70
71
  constructor(paths, operations = {}) {
71
72
  this.paths = paths;
72
73
  this.#fileOperations = { ...defaultFileOperations, ...operations };
73
74
  }
74
- async execute(patch) {
75
+ execute(patch, signal) {
76
+ const result = this.#tail.then(() => {
77
+ signal?.throwIfAborted();
78
+ return this.#apply(patch);
79
+ });
80
+ this.#tail = result.then(() => undefined, () => undefined);
81
+ return result;
82
+ }
83
+ async #apply(patch) {
75
84
  const changes = parsePatch(patch);
76
85
  reserveFileChangePaths(changes.flatMap((change) => change.kind === 'update' && change.targetPath
77
86
  ? [change.path, change.targetPath]
@@ -25,7 +25,7 @@ export const toolSearchToolDefinition = {
25
25
  additionalProperties: false
26
26
  },
27
27
  allowedCallers: ['direct'],
28
- parallelSafety: 'serial',
28
+ parallelSafety: 'safe',
29
29
  nativeType: 'tool_search'
30
30
  };
31
31
  export function parseToolSearchInput(input) {
@@ -35,7 +35,7 @@ export function createSessionToolScheduler() {
35
35
  const inherited = leases.getStore();
36
36
  const parent = inherited?.ingress === sessionToolIngress.getStore() ? inherited : undefined;
37
37
  const result = (parent?.active ? parent.schedule : root)({
38
- name: definition?.parallelSafety === 'safe' ? 'safe' : 'serial',
38
+ name: definition?.parallelSafety === 'serial' ? 'serial' : 'safe',
39
39
  run
40
40
  });
41
41
  if (parent?.active) {
@@ -52,7 +52,7 @@ export function createToolCallScheduler(definitions, execute) {
52
52
  let barrier = Promise.resolve();
53
53
  const readers = new Set();
54
54
  return (call) => {
55
- const safe = safety.get(call.name) === 'safe';
55
+ const safe = safety.get(call.name) !== 'serial';
56
56
  const gate = safe ? barrier : Promise.all([barrier, ...readers]).then(() => undefined);
57
57
  const result = gate.then(() => execute(call));
58
58
  const settled = result.then(() => undefined, () => undefined);
@@ -72,13 +72,13 @@ export async function scheduleToolCallBatch(calls, definitions, execute) {
72
72
  const results = new Array(calls.length);
73
73
  let index = 0;
74
74
  while (index < calls.length) {
75
- if (safety.get(calls[index].name) !== 'safe') {
75
+ if (safety.get(calls[index].name) === 'serial') {
76
76
  results[index] = await execute(calls[index]);
77
77
  index++;
78
78
  continue;
79
79
  }
80
80
  let end = index + 1;
81
- while (end < calls.length && safety.get(calls[end].name) === 'safe')
81
+ while (end < calls.length && safety.get(calls[end].name) !== 'serial')
82
82
  end++;
83
83
  const segment = await Promise.all(calls.slice(index, end).map((call) => execute(call)));
84
84
  for (let segmentIndex = 0; segmentIndex < segment.length; segmentIndex++)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.11.61",
3
+ "version": "0.11.63",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",