@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.
- package/dist/cli/catalog-file.d.ts +2 -0
- package/dist/cli/main.js +26 -15
- package/dist/cli/subagent/control.js +2 -2
- package/dist/cli/subagent/start.js +1 -1
- package/dist/cli/terminal-input.d.ts +12 -0
- package/dist/cli/terminal-input.js +86 -0
- package/dist/host/local-tool-registry.js +2 -2
- package/dist/model/configuration.d.ts +1 -0
- package/dist/model/configuration.js +1 -0
- package/dist/runtime/context/gc-admission.d.ts +5 -0
- package/dist/runtime/context/gc-admission.js +23 -0
- package/dist/runtime/context/gc-candidates-code.js +5 -6
- package/dist/runtime/context/gc-candidates-exec.js +3 -4
- package/dist/runtime/context/gc-candidates-read-patch.js +3 -4
- package/dist/runtime/context/gc-candidates-web.d.ts +1 -1
- package/dist/runtime/context/gc-candidates-web.js +6 -7
- package/dist/runtime/context/gc-runtime.js +3 -57
- package/dist/runtime/context/gc-tokens.d.ts +0 -4
- package/dist/runtime/context/gc-tokens.js +0 -18
- package/dist/runtime/context/gc.d.ts +1 -1
- package/dist/runtime/context/gc.js +24 -139
- package/dist/sdk/execution-context-coordinator.d.ts +1 -0
- package/dist/sdk/execution-context-coordinator.js +3 -1
- package/dist/sdk/session-execution-factory.js +4 -1
- package/dist/session/context-gc-checkpoint.d.ts +1 -4
- package/dist/session/context-gc-checkpoint.js +10 -22
- package/dist/session/context-gc-contract.d.ts +8 -8
- package/dist/session/store/jsonl/jsonl-store.js +1 -4
- package/dist/tools/agent/question.js +1 -1
- package/dist/tools/agent/sleep.js +1 -1
- package/dist/tools/agent/todo-write.js +1 -1
- package/dist/tools/code-mode/index.js +1 -1
- package/dist/tools/contracts.d.ts +1 -0
- package/dist/tools/local/apply-patch.d.ts +1 -1
- package/dist/tools/local/apply-patch.js +11 -2
- package/dist/tools/mcp/tool-search.js +1 -1
- package/dist/tools/scheduler.js +4 -4
- package/package.json +1 -1
- package/dist/runtime/context/gc-economics.d.ts +0 -35
- package/dist/runtime/context/gc-economics.js +0 -93
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { ContextGcCheckpointCache, contextGcBaseFromRecords,
|
|
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,
|
|
3
|
+
import { CONTEXT_GC_TOKEN_ESTIMATOR, contextGcMessageTokens, contextGcPrefixTokens, contextGcReclaimedTokens } from './gc-tokens.js';
|
|
4
4
|
import { applyContextGcReplacementsToMessages, providerToolCallId } from './projection.js';
|
|
5
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
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,
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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
|
|
108
|
+
readonly grossReductionRate?: number;
|
|
109
109
|
readonly effectiveMinimumGrossReductionRate?: number;
|
|
110
110
|
readonly minimumRequiredReclaimedTokens?: number;
|
|
111
|
-
readonly cacheRebuildPenaltyCostUnits
|
|
111
|
+
readonly cacheRebuildPenaltyCostUnits?: number;
|
|
112
112
|
readonly compactDeferralCreditCostUnits?: number;
|
|
113
113
|
readonly imminentCompactAvoidanceCreditCostUnits?: number;
|
|
114
114
|
readonly appliedCompactCreditCostUnits?: number;
|
|
115
|
-
readonly requiredPositiveNetRollouts
|
|
115
|
+
readonly requiredPositiveNetRollouts?: number | null;
|
|
116
116
|
readonly requiredTargetRoiRollouts?: number | null;
|
|
117
|
-
readonly projectedRolloutsUntilCompactAfterGc
|
|
117
|
+
readonly projectedRolloutsUntilCompactAfterGc?: number;
|
|
118
118
|
readonly projectedTokensBeforeGc?: number;
|
|
119
119
|
readonly projectedTokensAfterGc?: number;
|
|
120
|
-
readonly contextGrowthSampleCount
|
|
121
|
-
readonly contextGrowthP75Tokens
|
|
122
|
-
readonly expectedNetSavedCostUnits
|
|
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
|
|
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,
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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',
|
|
@@ -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: '
|
|
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
|
-
|
|
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]
|
package/dist/tools/scheduler.js
CHANGED
|
@@ -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 === '
|
|
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)
|
|
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)
|
|
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)
|
|
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++)
|