@myagentroam/agent 0.11.14 → 0.11.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-core/host/contracts.d.ts +2 -2
- package/dist/agent-core/host/contracts.js +1 -2
- package/dist/agent-core/host/local-agent-host.js +1 -2
- package/dist/agent-core/runtime/compaction/requests.d.ts +1 -1
- package/dist/agent-core/runtime/context/gc-runtime.js +9 -6
- package/dist/agent-core/runtime/context/gc.js +43 -29
- package/dist/agent-core/runtime/context/projection.d.ts +1 -1
- package/dist/agent-core/runtime/context/projection.js +3 -3
- package/dist/agent-core/runtime/context/restore.js +1 -2
- package/dist/agent-core/runtime/execution-handle.d.ts +12 -0
- package/dist/agent-core/runtime/execution-handle.js +1 -0
- package/dist/agent-core/sdk/agent.js +96 -48
- package/dist/agent-core/sdk/types.d.ts +3 -12
- package/dist/agent-core/session/context-gc-checkpoint.d.ts +4 -2
- package/dist/agent-core/session/context-gc-checkpoint.js +15 -7
- package/dist/agent-core/session/store/contracts.d.ts +10 -2
- package/dist/agent-core/session/store/execution-state.js +0 -12
- package/dist/agent-core/session/store/jsonl-store.d.ts +4 -4
- package/dist/agent-core/session/store/jsonl-store.js +133 -47
- package/dist/agent-core/session/store/types.d.ts +0 -3
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import type { CoreEnvironmentBackend } from '../extensions/environment.js';
|
|
|
7
7
|
import type { CoreOperationContext } from '../extensions/operation.js';
|
|
8
8
|
import type { CoreToolRef } from '../extensions/tools.js';
|
|
9
9
|
export declare const MAR_AGENT_HOST_API_VERSION = "1.0";
|
|
10
|
-
export type MarAgentHostCapability = 'workspace.read' | 'workspace.patch' | 'process.manage' | 'code.execute' | 'user-input.request' | 'event.publish'
|
|
10
|
+
export type MarAgentHostCapability = 'workspace.read' | 'workspace.patch' | 'process.manage' | 'code.execute' | 'user-input.request' | 'event.publish';
|
|
11
11
|
export interface HostDescription {
|
|
12
12
|
apiVersion: string;
|
|
13
13
|
mode: 'normal' | 'node';
|
|
@@ -51,7 +51,7 @@ export interface HostSessionResourceSnapshot {
|
|
|
51
51
|
cwd: string;
|
|
52
52
|
}[];
|
|
53
53
|
}
|
|
54
|
-
/** The Host owns workspace/tool effects;
|
|
54
|
+
/** The Host owns workspace/tool effects; SessionStore is injected or defaults to SDK-owned JSONL. */
|
|
55
55
|
export interface MarAgentHost {
|
|
56
56
|
/** Independent model consumption facts, including child and auxiliary calls. */
|
|
57
57
|
modelUsage?: import('../model/usage-observer.js').ModelUsageObserver | undefined;
|
|
@@ -5,8 +5,7 @@ const requiredCapabilities = [
|
|
|
5
5
|
'workspace.patch',
|
|
6
6
|
'process.manage',
|
|
7
7
|
'user-input.request',
|
|
8
|
-
'event.publish'
|
|
9
|
-
'session.store'
|
|
8
|
+
'event.publish'
|
|
10
9
|
];
|
|
11
10
|
export function negotiateHost(description) {
|
|
12
11
|
const match = /^(\d+)\.(\d+)$/.exec(description.apiVersion);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompactInput, ExecutionHandle } from '
|
|
1
|
+
import type { CompactInput, ExecutionHandle } from '../execution-handle.js';
|
|
2
2
|
import type { MarAgentEvent } from '../events.js';
|
|
3
3
|
import type { CompactFlight } from './coordinator.js';
|
|
4
4
|
/** External handles represent an operation inside its owning main Execution, not another loop. */
|
|
@@ -100,13 +100,16 @@ async function attemptContextGc(input) {
|
|
|
100
100
|
records =
|
|
101
101
|
input.records ?? (await input.store.readExecutionState(input.sessionId)).contextRecords;
|
|
102
102
|
const checkpointCache = new ContextGcCheckpointCache();
|
|
103
|
-
const previous = checkpointCache.read(records
|
|
103
|
+
const previous = checkpointCache.read(records);
|
|
104
104
|
if (previous) {
|
|
105
105
|
deferred = {
|
|
106
106
|
...deferred,
|
|
107
107
|
checkpointTrigger: previous.payload.trigger,
|
|
108
108
|
protectedCandidatesPending: previous.payload.metrics.protectedCandidateTokens > 0
|
|
109
109
|
};
|
|
110
|
+
}
|
|
111
|
+
if (previous?.payload.trigger.modelId === input.modelId &&
|
|
112
|
+
previous.payload.trigger.contextWindowTokens === input.contextWindowTokens) {
|
|
110
113
|
const rolloutGrowth = records.filter((record) => record.sequence > previous.record.sequence &&
|
|
111
114
|
record.type === 'model.attempt' &&
|
|
112
115
|
isRecord(record.payload) &&
|
|
@@ -120,7 +123,7 @@ async function attemptContextGc(input) {
|
|
|
120
123
|
contextGcMinimumUsageGrowth(input.contextWindowTokens))
|
|
121
124
|
return deferred;
|
|
122
125
|
}
|
|
123
|
-
const projection = buildContextProjection(records,
|
|
126
|
+
const projection = buildContextProjection(records, checkpointCache);
|
|
124
127
|
const inspection = inspectContextGc({
|
|
125
128
|
records,
|
|
126
129
|
checkpointCache,
|
|
@@ -159,14 +162,14 @@ async function attemptContextGc(input) {
|
|
|
159
162
|
const committed = await input.store.commitContextGc(input.sessionId, draft, records);
|
|
160
163
|
return {
|
|
161
164
|
...deferred,
|
|
162
|
-
result: await matchingCheckpointWasPersisted(input.store, input.sessionId, draft,
|
|
165
|
+
result: await matchingCheckpointWasPersisted(input.store, input.sessionId, draft, records, committed)
|
|
163
166
|
};
|
|
164
167
|
}
|
|
165
168
|
catch (commitError) {
|
|
166
169
|
try {
|
|
167
170
|
return {
|
|
168
171
|
...deferred,
|
|
169
|
-
result: await matchingCheckpointWasPersisted(input.store, input.sessionId, draft,
|
|
172
|
+
result: await matchingCheckpointWasPersisted(input.store, input.sessionId, draft, records)
|
|
170
173
|
};
|
|
171
174
|
}
|
|
172
175
|
catch {
|
|
@@ -176,7 +179,7 @@ async function attemptContextGc(input) {
|
|
|
176
179
|
return deferred;
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
|
-
async function matchingCheckpointWasPersisted(store, sessionId, draft,
|
|
182
|
+
async function matchingCheckpointWasPersisted(store, sessionId, draft, records, committed) {
|
|
180
183
|
const record = committed ??
|
|
181
184
|
(await store.readPage(sessionId, {
|
|
182
185
|
limit: 1,
|
|
@@ -185,7 +188,7 @@ async function matchingCheckpointWasPersisted(store, sessionId, draft, workspace
|
|
|
185
188
|
if (record?.sequence !== draft.boundarySequence + 1 || record.type !== 'context.gc.completed')
|
|
186
189
|
return false;
|
|
187
190
|
const updatedRecords = [...records, record];
|
|
188
|
-
const projection = buildContextProjection(updatedRecords
|
|
191
|
+
const projection = buildContextProjection(updatedRecords);
|
|
189
192
|
if (committed)
|
|
190
193
|
return { records: updatedRecords, projection };
|
|
191
194
|
const payload = projection.checkpoint;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { codeCallEvidence, codePartialPatchCallEvidence, codeObservationEvidence, retainedCodeInputs } from '../../session/context-gc-observations.js';
|
|
2
|
-
import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcLogicalPathKey, contextGcMinimumUsageGrowth, contextGcTargetKey, normalizeContextGcReplacements } from '../../session/context-gc-checkpoint.js';
|
|
2
|
+
import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcCheckpointIntervalEligible, contextGcLogicalPathKey, contextGcMinimumUsageGrowth, contextGcTargetKey, normalizeContextGcReplacements } from '../../session/context-gc-checkpoint.js';
|
|
3
3
|
import { codeCellCompletionSequence, contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcPatchResultFromFailedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from '../../session/context-gc-replacements.js';
|
|
4
4
|
import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentPriorFinalIsRetained, contextGcSubagentSnapshotIsPreciselyCovered, contextGcSubagentSnapshotIsSuperseded, contextGcSubagentSnapshotTurnId, normalizeContextGcSubagentSnapshot } from '../../session/context-gc-subagent.js';
|
|
5
5
|
import { parseReadContextOutput } from '../../tools/local/read-context.js';
|
|
@@ -131,15 +131,25 @@ export function inspectContextGc(input) {
|
|
|
131
131
|
if (!usageRecord)
|
|
132
132
|
return {};
|
|
133
133
|
const normalRolloutOrdinal = countSuccessfulNormalRollouts(input.records, input.modelId, input.contextWindowTokens);
|
|
134
|
-
const previous = input.checkpointCache.read(input.records
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
const previous = input.checkpointCache.read(input.records);
|
|
135
|
+
const checkpointEligible = contextGcCheckpointIntervalEligible({
|
|
136
|
+
usageSequence: usageRecord.sequence,
|
|
137
|
+
contextInputTokens: contextUsageTokens,
|
|
138
|
+
contextWindowTokens: input.contextWindowTokens,
|
|
139
|
+
normalRolloutOrdinal,
|
|
140
|
+
modelId: input.modelId,
|
|
141
|
+
platform: input.platform
|
|
142
|
+
}, previous?.payload.trigger);
|
|
143
|
+
// Discovery and economic evaluation have their own low-growth opportunities;
|
|
144
|
+
// neither a protected candidate nor periodic discovery makes a draft eligible.
|
|
145
|
+
if (previous?.payload.trigger.modelId === input.modelId &&
|
|
146
|
+
previous.payload.trigger.contextWindowTokens === input.contextWindowTokens) {
|
|
138
147
|
const rolloutGrowth = normalRolloutOrdinal - previous.payload.trigger.normalRolloutOrdinal;
|
|
139
148
|
if (rolloutGrowth < 2 ||
|
|
140
|
-
(
|
|
149
|
+
(previous.payload.metrics.protectedCandidateTokens === 0 &&
|
|
141
150
|
rolloutGrowth < CONTEXT_GC_POLICY.largeExecProtectionRollouts &&
|
|
142
|
-
contextUsageTokens - previous.payload.trigger.contextInputTokens <
|
|
151
|
+
contextUsageTokens - previous.payload.trigger.contextInputTokens <
|
|
152
|
+
contextGcMinimumUsageGrowth(input.contextWindowTokens)))
|
|
143
153
|
return {};
|
|
144
154
|
}
|
|
145
155
|
if (input.messages.length !== input.messageSources.length)
|
|
@@ -188,41 +198,45 @@ export function inspectContextGc(input) {
|
|
|
188
198
|
normalRolloutOrdinal,
|
|
189
199
|
protectedCandidateTokens
|
|
190
200
|
});
|
|
191
|
-
const plan =
|
|
201
|
+
const plan = checkpointEligible
|
|
202
|
+
? finalizePlan(input, marginal ?? stageAPlanWithProtectedCandidates)
|
|
203
|
+
: undefined;
|
|
192
204
|
const retryAfterRollouts = Math.min(exec.retryAfterRollouts, codeMode.retryAfterRollouts, web.retryAfterRollouts);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
if (!plan && checkpointEligible)
|
|
206
|
+
return {};
|
|
207
|
+
return {
|
|
208
|
+
...(plan ? { plan } : {}),
|
|
209
|
+
...(protectedCandidateTokens > 0 && Number.isFinite(retryAfterRollouts)
|
|
210
|
+
? { retryAfterRollouts }
|
|
211
|
+
: {})
|
|
212
|
+
};
|
|
201
213
|
}
|
|
202
214
|
const exec = collectExecCandidates(input, groups, existingTargets, candidateSavings);
|
|
203
215
|
const codeMode = collectCodeModeCandidates(input, groups, existingTargets, candidateSavings);
|
|
204
216
|
const web = collectWebCandidates(input, groups, existingTargets, candidateSavings);
|
|
217
|
+
const protectedCandidateTokens = deterministicProtectedTokens +
|
|
218
|
+
exec.protectedTokens +
|
|
219
|
+
codeMode.protectedTokens +
|
|
220
|
+
web.protectedTokens;
|
|
205
221
|
const combined = evaluateCandidateSet(input, existing, [...deterministicCandidates, ...exec.eligible, ...codeMode.eligible, ...web.eligible], {
|
|
206
222
|
stage: 'STAGE_A_AND_EXEC',
|
|
207
223
|
contextUsageTokens,
|
|
208
224
|
usageRatio,
|
|
209
225
|
usageRecord,
|
|
210
226
|
normalRolloutOrdinal,
|
|
211
|
-
protectedCandidateTokens
|
|
212
|
-
exec.protectedTokens +
|
|
213
|
-
codeMode.protectedTokens +
|
|
214
|
-
web.protectedTokens
|
|
227
|
+
protectedCandidateTokens
|
|
215
228
|
});
|
|
216
|
-
if (combined?.economics.passes) {
|
|
229
|
+
if (combined?.economics.passes && checkpointEligible) {
|
|
217
230
|
const plan = finalizePlan(input, combined);
|
|
218
231
|
return plan ? { plan } : {};
|
|
219
232
|
}
|
|
220
233
|
const retryAfterRollouts = Math.min(stageA.retryAfterRollouts, subagent.retryAfterRollouts, exec.retryAfterRollouts, codeMode.retryAfterRollouts, web.retryAfterRollouts);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
234
|
+
// A worthwhile mixed set retains every positive protected candidate's retry
|
|
235
|
+
// hint even when the checkpoint interval prevents a durable draft.
|
|
236
|
+
return protectedCandidateTokens > 0 &&
|
|
237
|
+
(combined?.economics.passes ||
|
|
238
|
+
protectedCandidateTokens >=
|
|
239
|
+
contextGcMinimumReclaimedTokens(contextUsageTokens, input.contextWindowTokens)) &&
|
|
226
240
|
Number.isFinite(retryAfterRollouts)
|
|
227
241
|
? { retryAfterRollouts }
|
|
228
242
|
: {};
|
|
@@ -459,7 +473,7 @@ function evaluateCandidateSet(input, existing, candidates, state) {
|
|
|
459
473
|
if (reclaimedTokens === 0)
|
|
460
474
|
return undefined;
|
|
461
475
|
const cache = contextGcCacheImpact(input, input.messages, projected.messages, input.usage.cacheReadTokens ?? 0, reclaimedTokens);
|
|
462
|
-
const growthSamples = collectContextGrowthSamples(input.records, input.modelId, input.contextWindowTokens, input.
|
|
476
|
+
const growthSamples = collectContextGrowthSamples(input.records, input.modelId, input.contextWindowTokens, input.checkpointCache);
|
|
463
477
|
const contextGrowthP75Tokens = percentile75(growthSamples);
|
|
464
478
|
const compactThresholdTokens = Math.floor(input.contextWindowTokens * COMPACT_THRESHOLD_RATIO);
|
|
465
479
|
// Usage describes the request before its generated output and tool results.
|
|
@@ -1098,8 +1112,8 @@ function latestUsageRecord(records, selectedModelId) {
|
|
|
1098
1112
|
}
|
|
1099
1113
|
return latest;
|
|
1100
1114
|
}
|
|
1101
|
-
function collectContextGrowthSamples(records, modelId, contextWindowTokens,
|
|
1102
|
-
const previous = checkpointCache.read(records
|
|
1115
|
+
function collectContextGrowthSamples(records, modelId, contextWindowTokens, checkpointCache) {
|
|
1116
|
+
const previous = checkpointCache.read(records);
|
|
1103
1117
|
const base = contextGcBaseFromRecords(records);
|
|
1104
1118
|
const startSequence = previous?.record.sequence ?? (base?.type === 'COMPACT' ? base.sequence : base?.firstSequence);
|
|
1105
1119
|
let activeModelId = modelId;
|
|
@@ -9,7 +9,7 @@ export interface ContextProjection {
|
|
|
9
9
|
readonly entries: readonly ContextProjectionEntry[];
|
|
10
10
|
readonly checkpoint?: ContextGcCheckpointPayload;
|
|
11
11
|
}
|
|
12
|
-
export declare function buildContextProjection(records: readonly SessionRecord[],
|
|
12
|
+
export declare function buildContextProjection(records: readonly SessionRecord[], checkpointCache?: ContextGcCheckpointCache): ContextProjection;
|
|
13
13
|
export declare function applyContextGcReplacementsToMessages(messages: readonly ModelMessage[], messageSources: readonly SessionRecord[], replacements: readonly ContextGcReplacement[]): {
|
|
14
14
|
readonly messages: ModelMessage[];
|
|
15
15
|
readonly messageSources: SessionRecord[];
|
|
@@ -6,7 +6,7 @@ import { TOOL_EXECUTION_LIMITS } from '../../tools/execution-limits.js';
|
|
|
6
6
|
import { replaceReadContextItems } from '../../tools/local/read-context.js';
|
|
7
7
|
import { parseCodeCalls, parseObservations, renderObservationResult } from '../../tools/code-mode/observations.js';
|
|
8
8
|
const MAX_MODEL_CONTEXT_BYTES = 1024 * 1024;
|
|
9
|
-
export function buildContextProjection(records,
|
|
9
|
+
export function buildContextProjection(records, checkpointCache) {
|
|
10
10
|
let activeModelId;
|
|
11
11
|
const rawEntries = [];
|
|
12
12
|
for (const record of records) {
|
|
@@ -33,8 +33,8 @@ export function buildContextProjection(records, workspace, checkpointCache) {
|
|
|
33
33
|
rawEntries.push({ record, message });
|
|
34
34
|
}
|
|
35
35
|
const valid = checkpointCache
|
|
36
|
-
? checkpointCache.read(records
|
|
37
|
-
: latestValidContextGcCheckpoint(records
|
|
36
|
+
? checkpointCache.read(records)
|
|
37
|
+
: latestValidContextGcCheckpoint(records);
|
|
38
38
|
const messages = rawEntries.flatMap((entry) => (entry.message ? [entry.message] : []));
|
|
39
39
|
const sources = rawEntries.flatMap((entry) => (entry.message ? [entry.record] : []));
|
|
40
40
|
const applied = applyContextGcReplacementsToMessages(messages, sources, valid?.payload.replacements ?? []);
|
|
@@ -9,8 +9,7 @@ export async function restoreMessages(store, sessionId, records, options) {
|
|
|
9
9
|
const messages = [];
|
|
10
10
|
const outcomes = new Map();
|
|
11
11
|
const toolOutcomes = new WeakMap();
|
|
12
|
-
const modelProjection = new Map((options.projection ??
|
|
13
|
-
buildContextProjection(records, (await store.getSession(sessionId)).workspace)).entries.map((entry) => [entry.record.recordId, entry.message]));
|
|
12
|
+
const modelProjection = new Map((options.projection ?? buildContextProjection(records)).entries.map((entry) => [entry.record.recordId, entry.message]));
|
|
14
13
|
let userMessages = [];
|
|
15
14
|
let ignoredImages = 0;
|
|
16
15
|
let currentTurnUserIndex;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ExecutionResult, RunInput } from './contracts.js';
|
|
2
|
+
import type { MarAgentEvent } from './events.js';
|
|
3
|
+
/** A live execution exposes streamed events separately from its terminal result. */
|
|
4
|
+
export interface ExecutionHandle extends AsyncIterable<MarAgentEvent> {
|
|
5
|
+
readonly executionId: string;
|
|
6
|
+
result(): Promise<ExecutionResult>;
|
|
7
|
+
/** Signals abort; await result() to observe the terminal status and cleanup. */
|
|
8
|
+
cancel(): void;
|
|
9
|
+
}
|
|
10
|
+
export type CompactInput = Partial<RunInput> & {
|
|
11
|
+
readonly async?: boolean;
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -223,29 +223,46 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
223
223
|
const runtime = sessionRuntimes.get(id);
|
|
224
224
|
if ([...active.keys()].some((key) => key.startsWith(`${id}:`)))
|
|
225
225
|
throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'A session cannot be cleared while an execution is active.');
|
|
226
|
-
await
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
runtime
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
226
|
+
const maintenance = await store.acquireSessionMaintenance(id, 'clear', sessionWriterOwner);
|
|
227
|
+
let failed = false;
|
|
228
|
+
try {
|
|
229
|
+
await completeAgentCleanup([
|
|
230
|
+
() => extensions.endSession(id, 'clear'),
|
|
231
|
+
() => extensions.retireSession(id),
|
|
232
|
+
() => extensions.clearSession(id),
|
|
233
|
+
() => runtime?.inputs?.retire(),
|
|
234
|
+
() => runtime?.subagents?.dispose(),
|
|
235
|
+
() => closeModelAdapters(runtime === undefined ? [] : [...runtime.adapters.values()]),
|
|
236
|
+
() => {
|
|
237
|
+
// Existing SDK Session handles share this runtime and remain usable after clear.
|
|
238
|
+
if (runtime) {
|
|
239
|
+
runtime.adapters.clear();
|
|
240
|
+
runtime.adapterGeneration = modelGeneration;
|
|
241
|
+
delete runtime.todo;
|
|
242
|
+
delete runtime.subagents;
|
|
243
|
+
delete runtime.subagentsOpening;
|
|
244
|
+
delete runtime.contextualWorldState;
|
|
245
|
+
delete runtime.inputs;
|
|
246
|
+
delete runtime.requestSnapshots;
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
() => options.host.disposeSessionResources?.(id),
|
|
250
|
+
() => store.clearSession(id, maintenance)
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
failed = true;
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
try {
|
|
259
|
+
await maintenance.release();
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (!failed)
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
249
266
|
}),
|
|
250
267
|
setSystemInstruction: (systemInstruction) => {
|
|
251
268
|
if (disposed)
|
|
@@ -333,31 +350,62 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
333
350
|
}
|
|
334
351
|
},
|
|
335
352
|
deleteSession: (id) => trackSessionLifecycle(id, 'delete', async () => {
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
353
|
+
const maintenance = await store.acquireSessionMaintenance(id, 'delete', sessionWriterOwner);
|
|
354
|
+
let failed = false;
|
|
355
|
+
try {
|
|
356
|
+
const sessionIds = maintenance.sessionIds;
|
|
357
|
+
const sessionIdSet = new Set(sessionIds);
|
|
358
|
+
const executionKeys = [...active.keys()].filter((key) => sessionIdSet.has(key.slice(0, key.indexOf(':'))));
|
|
359
|
+
for (const key of executionKeys)
|
|
360
|
+
active.get(key)?.abort();
|
|
361
|
+
const runtimes = sessionIds.flatMap((sessionId) => {
|
|
362
|
+
const runtime = sessionRuntimes.get(sessionId);
|
|
363
|
+
return runtime === undefined ? [] : [runtime];
|
|
364
|
+
});
|
|
365
|
+
await completeAgentCleanup([
|
|
366
|
+
async () => {
|
|
367
|
+
let failure;
|
|
368
|
+
await Promise.all(sessionIds.map(async (sessionId) => {
|
|
369
|
+
try {
|
|
370
|
+
await extensions.endSession(sessionId, 'other', false);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
failure ??= { error };
|
|
374
|
+
}
|
|
375
|
+
}));
|
|
376
|
+
if (failure)
|
|
377
|
+
throw failure.error;
|
|
378
|
+
},
|
|
379
|
+
...sessionIds.map((sessionId) => () => extensions.retireSession(sessionId)),
|
|
380
|
+
() => {
|
|
381
|
+
for (const sessionId of sessionIds)
|
|
382
|
+
sessionRuntimes.delete(sessionId);
|
|
383
|
+
},
|
|
384
|
+
...sessionIds.map((sessionId) => () => extensions.clearSession(sessionId)),
|
|
385
|
+
...runtimes.map((runtime) => () => runtime.inputs?.retire()),
|
|
386
|
+
...runtimes.map((runtime) => () => runtime.subagents?.dispose()),
|
|
387
|
+
() => Promise.allSettled(executionKeys.flatMap((key) => {
|
|
388
|
+
const result = executionResults.get(key);
|
|
389
|
+
return result === undefined ? [] : [result];
|
|
390
|
+
})),
|
|
391
|
+
() => closeModelAdapters(runtimes.flatMap((runtime) => [...runtime.adapters.values()])),
|
|
392
|
+
...sessionIds.map((sessionId) => () => options.host.disposeSessionResources?.(sessionId)),
|
|
393
|
+
() => store.delete(id, maintenance)
|
|
394
|
+
]);
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
failed = true;
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
finally {
|
|
401
|
+
try {
|
|
402
|
+
await maintenance.release();
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
if (!failed)
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
361
409
|
}),
|
|
362
410
|
dispose: () => {
|
|
363
411
|
if (disposal)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { MarAgentHost } from '../host/contracts.js';
|
|
2
2
|
import type { CoreExtensionSource, CoreRuntimeExtension } from '../extensions/contracts.js';
|
|
3
3
|
import type { MarAgentModelConfiguration } from '../model/configuration.js';
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
4
|
+
import type { RolloutBudgetOptions, RunInput } from '../runtime/contracts.js';
|
|
5
|
+
import type { CompactInput, ExecutionHandle } from '../runtime/execution-handle.js';
|
|
6
6
|
import type { MarAgentSessionPage, MarAgentSessionSummary } from '../session/catalog.js';
|
|
7
7
|
import type { SessionListInput, SessionSource, SessionTitleUpdateInput } from '../session/types.js';
|
|
8
8
|
export interface CreateSessionInput {
|
|
@@ -13,17 +13,8 @@ export type { SessionTitleUpdateInput } from '../session/types.js';
|
|
|
13
13
|
export type { HostExecutionExtensions, HostSessionResourceSnapshot, HostSkill, MarAgentHost } from '../host/contracts.js';
|
|
14
14
|
export type { AccessMode, ExecutionMode, ExecutionResult, MarAgentImageMetadata, RolloutBudgetOptions, RunInput, RunInputImage } from '../runtime/contracts.js';
|
|
15
15
|
export type { MarAgentEvent, MarAgentEventBase } from '../runtime/events.js';
|
|
16
|
+
export type { CompactInput, ExecutionHandle } from '../runtime/execution-handle.js';
|
|
16
17
|
export type { CodeCellEventData, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolExecutionContext, ToolExecutionResult } from '../tools/contracts.js';
|
|
17
|
-
/** A live execution exposes streamed events separately from its terminal result. */
|
|
18
|
-
export interface ExecutionHandle extends AsyncIterable<MarAgentEvent> {
|
|
19
|
-
readonly executionId: string;
|
|
20
|
-
result(): Promise<ExecutionResult>;
|
|
21
|
-
/** Signals abort; await result() to observe the terminal status and cleanup. */
|
|
22
|
-
cancel(): void;
|
|
23
|
-
}
|
|
24
|
-
export type CompactInput = Partial<RunInput> & {
|
|
25
|
-
readonly async?: boolean;
|
|
26
|
-
};
|
|
27
18
|
export interface SteerInput {
|
|
28
19
|
readonly expectedExecutionId: string;
|
|
29
20
|
readonly prompt: string;
|
|
@@ -143,9 +143,11 @@ export interface ContextGcCheckpointPayload extends ContextGcCheckpointDraft {
|
|
|
143
143
|
/** Disposable memoization for one evaluation's immutable record snapshot. */
|
|
144
144
|
export declare class ContextGcCheckpointCache {
|
|
145
145
|
#private;
|
|
146
|
-
read(records: readonly SessionRecord[], boundarySequence?: number
|
|
146
|
+
read(records: readonly SessionRecord[], boundarySequence?: number): ReturnType<typeof latestValidContextGcCheckpoint>;
|
|
147
147
|
}
|
|
148
148
|
export declare function contextGcMinimumUsageGrowth(contextWindowTokens: number): number;
|
|
149
|
+
/** Compare adjacent checkpoint triggers only within the same model/window scope. */
|
|
150
|
+
export declare function contextGcCheckpointIntervalEligible(trigger: ContextGcCheckpointDraft['trigger'], previous?: ContextGcCheckpointDraft['trigger']): boolean;
|
|
149
151
|
export declare function contextGcLogicalPathKey(path: string, workspace: string, platform?: ContextGcPlatform): string | undefined;
|
|
150
152
|
export declare function contextGcTargetKey(target: ContextGcTarget): string;
|
|
151
153
|
export declare function contextGcReplacementKey(replacement: ContextGcReplacement): string;
|
|
@@ -154,7 +156,7 @@ export declare function contextGcBaseSequence(base: ContextGcBase): number;
|
|
|
154
156
|
export declare function normalizeContextGcReplacements(replacements: readonly ContextGcReplacement[]): ContextGcReplacement[] | undefined;
|
|
155
157
|
export declare function parseContextGcCheckpoint(record: SessionRecord | undefined): ContextGcCheckpointPayload | undefined;
|
|
156
158
|
export declare function validateContextGcCheckpoint(records: readonly SessionRecord[], record: SessionRecord, workspace?: string | undefined, committedReplacements?: readonly ContextGcReplacement[]): ContextGcCheckpointPayload | undefined;
|
|
157
|
-
export declare function latestValidContextGcCheckpoint(records: readonly SessionRecord[], boundarySequence?: number
|
|
159
|
+
export declare function latestValidContextGcCheckpoint(records: readonly SessionRecord[], boundarySequence?: number): {
|
|
158
160
|
readonly record: SessionRecord;
|
|
159
161
|
readonly payload: ContextGcCheckpointPayload;
|
|
160
162
|
} | undefined;
|
|
@@ -9,20 +9,28 @@ export const CONTEXT_GC_MAX_TARGETS = 4_096;
|
|
|
9
9
|
/** Disposable memoization for one evaluation's immutable record snapshot. */
|
|
10
10
|
export class ContextGcCheckpointCache {
|
|
11
11
|
#snapshot;
|
|
12
|
-
read(records, boundarySequence = records.at(-1)?.sequence ?? 0
|
|
12
|
+
read(records, boundarySequence = records.at(-1)?.sequence ?? 0) {
|
|
13
13
|
const cached = this.#snapshot;
|
|
14
|
-
if (cached?.records === records &&
|
|
15
|
-
cached.boundarySequence === boundarySequence &&
|
|
16
|
-
cached.workspace === workspace)
|
|
14
|
+
if (cached?.records === records && cached.boundarySequence === boundarySequence)
|
|
17
15
|
return cached.value;
|
|
18
|
-
const value = latestValidContextGcCheckpoint(records, boundarySequence
|
|
19
|
-
this.#snapshot = { records, boundarySequence,
|
|
16
|
+
const value = latestValidContextGcCheckpoint(records, boundarySequence);
|
|
17
|
+
this.#snapshot = { records, boundarySequence, value };
|
|
20
18
|
return value;
|
|
21
19
|
}
|
|
22
20
|
}
|
|
23
21
|
export function contextGcMinimumUsageGrowth(contextWindowTokens) {
|
|
24
22
|
return Math.max(2_000, Math.floor(contextWindowTokens * 0.05));
|
|
25
23
|
}
|
|
24
|
+
/** Compare adjacent checkpoint triggers only within the same model/window scope. */
|
|
25
|
+
export function contextGcCheckpointIntervalEligible(trigger, previous) {
|
|
26
|
+
if (!previous ||
|
|
27
|
+
trigger.modelId !== previous.modelId ||
|
|
28
|
+
trigger.contextWindowTokens !== previous.contextWindowTokens)
|
|
29
|
+
return true;
|
|
30
|
+
return (trigger.normalRolloutOrdinal - previous.normalRolloutOrdinal >= 2 &&
|
|
31
|
+
trigger.contextInputTokens - previous.contextInputTokens >=
|
|
32
|
+
contextGcMinimumUsageGrowth(trigger.contextWindowTokens));
|
|
33
|
+
}
|
|
26
34
|
export function contextGcLogicalPathKey(path, workspace, platform) {
|
|
27
35
|
if (!path || !workspace || path.includes('\0') || workspace.includes('\0'))
|
|
28
36
|
return undefined;
|
|
@@ -198,7 +206,7 @@ export function validateContextGcCheckpoint(records, record, workspace = session
|
|
|
198
206
|
}
|
|
199
207
|
return payload;
|
|
200
208
|
}
|
|
201
|
-
export function latestValidContextGcCheckpoint(records, boundarySequence = records.at(-1)?.sequence ?? 0
|
|
209
|
+
export function latestValidContextGcCheckpoint(records, boundarySequence = records.at(-1)?.sequence ?? 0) {
|
|
202
210
|
const base = contextGcBaseFromRecords(records, boundarySequence);
|
|
203
211
|
if (!base)
|
|
204
212
|
return undefined;
|
|
@@ -49,6 +49,12 @@ export interface SessionStoreCompactInput {
|
|
|
49
49
|
executionId?: string;
|
|
50
50
|
turnId?: string;
|
|
51
51
|
}
|
|
52
|
+
/** Store-issued ownership spanning SDK cleanup and the final durable mutation. */
|
|
53
|
+
export interface SessionStoreMaintenance {
|
|
54
|
+
readonly sessionIds: readonly string[];
|
|
55
|
+
/** Idempotent; releases maintenance only, never another owner's Execution/retained references. */
|
|
56
|
+
release(): Promise<void>;
|
|
57
|
+
}
|
|
52
58
|
/**
|
|
53
59
|
* Session persistence port. Implementations preserve record order, committed-resource
|
|
54
60
|
* visibility and the existing per-Session writer/checkpoint atomic boundaries.
|
|
@@ -124,6 +130,8 @@ export interface SessionStore {
|
|
|
124
130
|
/** Retention requires the originating owner to hold an active Execution lease. */
|
|
125
131
|
retainSessionLease(sessionId: string, owner: object): Promise<() => Promise<void>>;
|
|
126
132
|
hasRetainedSessionLease(sessionId: string, owner: object): boolean;
|
|
133
|
+
/** Conflicts fail before cleanup; delete includes the Store's current Session tree. */
|
|
134
|
+
acquireSessionMaintenance(sessionId: string, kind: 'clear' | 'delete', owner?: object): Promise<SessionStoreMaintenance>;
|
|
127
135
|
forkSession(sessionId: string, input: {
|
|
128
136
|
readonly boundaryTurnId: string | null;
|
|
129
137
|
readonly title: string;
|
|
@@ -131,8 +139,8 @@ export interface SessionStore {
|
|
|
131
139
|
rollbackSession(sessionId: string, input: {
|
|
132
140
|
readonly boundaryTurnId: string | null;
|
|
133
141
|
}): Promise<MarAgentSessionSummary>;
|
|
134
|
-
clearSession(sessionId: string): Promise<MarAgentSessionSummary>;
|
|
135
|
-
delete(sessionId: string): Promise<{
|
|
142
|
+
clearSession(sessionId: string, maintenance?: SessionStoreMaintenance): Promise<MarAgentSessionSummary>;
|
|
143
|
+
delete(sessionId: string, maintenance?: SessionStoreMaintenance): Promise<{
|
|
136
144
|
readonly deleted: boolean;
|
|
137
145
|
}>;
|
|
138
146
|
/** Stop using the handle and release its leases before closing it. Never preempts other owners. */
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { todoItemsFromSnapshot } from '../../tools/agent/todo.js';
|
|
2
|
-
import { contextGcBaseSequence, latestValidContextGcCheckpoint } from '../context-gc-checkpoint.js';
|
|
3
2
|
export function deriveExecutionState(records) {
|
|
4
3
|
let automaticTitle = false;
|
|
5
4
|
let sawUserTurn = false;
|
|
@@ -36,7 +35,6 @@ export function deriveExecutionState(records) {
|
|
|
36
35
|
executionModel = candidate;
|
|
37
36
|
}
|
|
38
37
|
const boundary = records.findLastIndex(isCompactCheckpoint);
|
|
39
|
-
const checkpoint = latestValidContextGcCheckpoint(records);
|
|
40
38
|
return {
|
|
41
39
|
automaticTitlePending: automaticTitle && !sawUserTurn,
|
|
42
40
|
todo: { revision: todoRevision, items: todoItems },
|
|
@@ -45,13 +43,6 @@ export function deriveExecutionState(records) {
|
|
|
45
43
|
version: 1,
|
|
46
44
|
userTurns,
|
|
47
45
|
...(compactSequence === undefined ? {} : { compactSequence }),
|
|
48
|
-
...(checkpoint === undefined
|
|
49
|
-
? {}
|
|
50
|
-
: {
|
|
51
|
-
gcSequence: checkpoint.record.sequence,
|
|
52
|
-
gcGeneration: checkpoint.payload.generation,
|
|
53
|
-
gcBaseSequence: contextGcBaseSequence(checkpoint.payload.base)
|
|
54
|
-
}),
|
|
55
46
|
...(todoSequence === undefined ? {} : { todoSequence }),
|
|
56
47
|
todoRevision,
|
|
57
48
|
executionModelIndexed: true,
|
|
@@ -170,9 +161,6 @@ export function sameResumeIndex(left, right) {
|
|
|
170
161
|
return (left?.version === right.version &&
|
|
171
162
|
left.userTurns === right.userTurns &&
|
|
172
163
|
left.compactSequence === right.compactSequence &&
|
|
173
|
-
left.gcSequence === right.gcSequence &&
|
|
174
|
-
left.gcGeneration === right.gcGeneration &&
|
|
175
|
-
left.gcBaseSequence === right.gcBaseSequence &&
|
|
176
164
|
left.todoSequence === right.todoSequence &&
|
|
177
165
|
left.todoRevision === right.todoRevision &&
|
|
178
166
|
left.executionModelIndexed === right.executionModelIndexed &&
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SessionStore, SessionRecordInput, SessionStoreCompactInput } from './contracts.js';
|
|
1
|
+
import type { SessionStore, SessionStoreMaintenance, SessionRecordInput, SessionStoreCompactInput } from './contracts.js';
|
|
2
2
|
import { type CoreCheckpoint, type CoreJsonValue } from '../../extensions/data.js';
|
|
3
3
|
import { type CodeModeStateEntry } from '../../tools/code-mode/state.js';
|
|
4
4
|
import { type ContextGcCheckpointDraft } from '../context-gc-checkpoint.js';
|
|
@@ -10,7 +10,6 @@ export declare class JsonlSessionStore implements SessionStore {
|
|
|
10
10
|
#private;
|
|
11
11
|
private constructor();
|
|
12
12
|
static open(home: string): Promise<JsonlSessionStore>;
|
|
13
|
-
/** Append the durable creation record before making this Session visible in the index. */
|
|
14
13
|
createSession(input: {
|
|
15
14
|
title?: string;
|
|
16
15
|
workspace: string;
|
|
@@ -57,7 +56,7 @@ export declare class JsonlSessionStore implements SessionStore {
|
|
|
57
56
|
}>;
|
|
58
57
|
updateTitle(sessionId: string, title: string, input?: SessionTitleUpdateInput, owner?: object): Promise<boolean>;
|
|
59
58
|
/** Reset history with the same recovery journal while preserving the Session identity. */
|
|
60
|
-
clearSession(sessionId: string): Promise<{
|
|
59
|
+
clearSession(sessionId: string, maintenance?: SessionStoreMaintenance): Promise<{
|
|
61
60
|
id: string;
|
|
62
61
|
title: string;
|
|
63
62
|
workspace: string;
|
|
@@ -108,6 +107,7 @@ export declare class JsonlSessionStore implements SessionStore {
|
|
|
108
107
|
/** A warm cell retains physical writer ownership between Executions of the same Agent. */
|
|
109
108
|
acquireSessionLease(sessionId: string, owner?: object): Promise<() => Promise<void>>;
|
|
110
109
|
hasRetainedSessionLease(sessionId: string, owner: object): boolean;
|
|
110
|
+
acquireSessionMaintenance(sessionId: string, kind: 'clear' | 'delete', owner?: object): Promise<SessionStoreMaintenance>;
|
|
111
111
|
/** Native writes join their Agent's sole writer, without admitting another Execution. */
|
|
112
112
|
acquireSessionWriteLease(sessionId: string, owner: object): Promise<() => Promise<void>>;
|
|
113
113
|
retainSessionLease(sessionId: string, owner: object): Promise<() => Promise<void>>;
|
|
@@ -156,7 +156,7 @@ export declare class JsonlSessionStore implements SessionStore {
|
|
|
156
156
|
/** Counts native input records, not the post-Compact model context. */
|
|
157
157
|
userTurnCount(sessionId: string): Promise<number>;
|
|
158
158
|
firstUserMessagePreview(sessionId: string): Promise<string | undefined>;
|
|
159
|
-
delete(sessionId: string): Promise<{
|
|
159
|
+
delete(sessionId: string, maintenance?: SessionStoreMaintenance): Promise<{
|
|
160
160
|
readonly deleted: boolean;
|
|
161
161
|
}>;
|
|
162
162
|
close(): Promise<void>;
|
|
@@ -7,7 +7,7 @@ import { cloneCoreJson } from '../../extensions/data.js';
|
|
|
7
7
|
import { acquireFileLock } from '../../storage/file-lock.js';
|
|
8
8
|
import { todoItemsFromSnapshot } from '../../tools/agent/todo.js';
|
|
9
9
|
import { applyCodeModeStateMutations, parseCodeModeStateMutations, restoreCodeModeState } from '../../tools/code-mode/state.js';
|
|
10
|
-
import { contextGcBaseFromRecords,
|
|
10
|
+
import { contextGcBaseFromRecords, contextGcCheckpointIntervalEligible, latestValidContextGcCheckpoint, normalizeContextGcReplacements, sameContextGcBase, validateContextGcCheckpoint } from '../context-gc-checkpoint.js';
|
|
11
11
|
import { bindToolResultResources, cleanupUnreferencedToolResources, completeToolResultResources, copyToolResultResources, readToolResultResource, toolResultResourceFiles, writeToolResultResource } from '../tool-result-resources.js';
|
|
12
12
|
import { beginSessionStorageTransaction, isUnjournaledTransactionFile, recoverSessionStorageTransaction } from '../transaction-recovery.js';
|
|
13
13
|
import { extensionCheckpointFromPayload, indexExtensionCheckpoint, restoreExtensionCheckpoint, updateSessionCurrentState } from './current-state.js';
|
|
@@ -23,6 +23,7 @@ export class JsonlSessionStore {
|
|
|
23
23
|
static #instances = new Map();
|
|
24
24
|
static #opening = new Map();
|
|
25
25
|
#shared;
|
|
26
|
+
#maintenance = new WeakMap();
|
|
26
27
|
#leaseClosed = false;
|
|
27
28
|
constructor(shared) {
|
|
28
29
|
this.#shared = shared;
|
|
@@ -283,9 +284,22 @@ export class JsonlSessionStore {
|
|
|
283
284
|
}
|
|
284
285
|
return { directory, release };
|
|
285
286
|
}
|
|
286
|
-
/** Append the durable creation record before making this Session visible in the index. */
|
|
287
287
|
async createSession(input) {
|
|
288
288
|
this.#assertOpen();
|
|
289
|
+
const source = normalizeSessionSource(input.source);
|
|
290
|
+
if (source.type !== 'subagent')
|
|
291
|
+
return this.#createSession(input, source);
|
|
292
|
+
// Publish under the parent's existing serial domain so tree admission cannot miss a child.
|
|
293
|
+
return this.#serializeRecordOperation(source.parentSessionId, async () => {
|
|
294
|
+
this.#assertOpen();
|
|
295
|
+
if (this.#shared.writerLeases.get(source.parentSessionId)?.maintenance)
|
|
296
|
+
throw new MarAgentError('MAR_AGENT_SESSION_LOCKED', 'Session is under maintenance.');
|
|
297
|
+
await this.getSession(source.parentSessionId);
|
|
298
|
+
return this.#createSession(input, source);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/** Append the durable creation record before making this Session visible in the index. */
|
|
302
|
+
async #createSession(input, source) {
|
|
289
303
|
const id = randomUUID();
|
|
290
304
|
const now = new Date().toISOString();
|
|
291
305
|
const meta = {
|
|
@@ -294,7 +308,7 @@ export class JsonlSessionStore {
|
|
|
294
308
|
workspace: input.workspace,
|
|
295
309
|
createdAt: now,
|
|
296
310
|
lastActivityAt: now,
|
|
297
|
-
source
|
|
311
|
+
source,
|
|
298
312
|
nextSequence: 1,
|
|
299
313
|
automaticTitlePending: input.title === undefined,
|
|
300
314
|
resume: {
|
|
@@ -697,9 +711,11 @@ export class JsonlSessionStore {
|
|
|
697
711
|
}
|
|
698
712
|
}
|
|
699
713
|
/** Reset history with the same recovery journal while preserving the Session identity. */
|
|
700
|
-
async clearSession(sessionId) {
|
|
714
|
+
async clearSession(sessionId, maintenance) {
|
|
701
715
|
this.#assertOpen();
|
|
702
|
-
const
|
|
716
|
+
const holding = maintenance ?? (await this.acquireSessionMaintenance(sessionId, 'clear'));
|
|
717
|
+
this.#assertMaintenance(sessionId, 'clear', holding);
|
|
718
|
+
let failed = false;
|
|
703
719
|
try {
|
|
704
720
|
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
705
721
|
if (!meta)
|
|
@@ -726,8 +742,18 @@ export class JsonlSessionStore {
|
|
|
726
742
|
];
|
|
727
743
|
return await this.#replaceHistory(meta, retained, { kind: 'clear', clearedAt });
|
|
728
744
|
}
|
|
745
|
+
catch (error) {
|
|
746
|
+
failed = true;
|
|
747
|
+
throw error;
|
|
748
|
+
}
|
|
729
749
|
finally {
|
|
730
|
-
|
|
750
|
+
if (!maintenance) {
|
|
751
|
+
const releasing = holding.release();
|
|
752
|
+
if (failed)
|
|
753
|
+
await releasing.catch(() => undefined);
|
|
754
|
+
else
|
|
755
|
+
await releasing;
|
|
756
|
+
}
|
|
731
757
|
}
|
|
732
758
|
}
|
|
733
759
|
/** Called only while rollback or clear holds the maintenance lease. */
|
|
@@ -1123,14 +1149,9 @@ export class JsonlSessionStore {
|
|
|
1123
1149
|
records.at(-1)?.sessionId !== sessionId ||
|
|
1124
1150
|
!sameContextGcBase(contextGcBaseFromRecords(records, boundarySequence), draft.base))
|
|
1125
1151
|
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC planning boundary is invalid.');
|
|
1126
|
-
const previous = latestValidContextGcCheckpoint(records, boundarySequence
|
|
1127
|
-
if (previous)
|
|
1128
|
-
|
|
1129
|
-
if (draft.trigger.normalRolloutOrdinal - previous.payload.trigger.normalRolloutOrdinal < 2 ||
|
|
1130
|
-
draft.trigger.contextInputTokens - previous.payload.trigger.contextInputTokens <
|
|
1131
|
-
minimumUsageGrowth)
|
|
1132
|
-
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC checkpoint interval validation failed.');
|
|
1133
|
-
}
|
|
1152
|
+
const previous = latestValidContextGcCheckpoint(records, boundarySequence);
|
|
1153
|
+
if (!contextGcCheckpointIntervalEligible(draft.trigger, previous?.payload.trigger))
|
|
1154
|
+
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC checkpoint interval validation failed.');
|
|
1134
1155
|
const plannedReplacements = normalizeContextGcReplacements(draft.replacements);
|
|
1135
1156
|
if (!plannedReplacements || plannedReplacements.length === 0)
|
|
1136
1157
|
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC checkpoint must contain a replacement.');
|
|
@@ -1236,7 +1257,7 @@ export class JsonlSessionStore {
|
|
|
1236
1257
|
this.#assertOpen();
|
|
1237
1258
|
let lease = this.#shared.writerLeases.get(sessionId);
|
|
1238
1259
|
if (lease) {
|
|
1239
|
-
if (lease.executing || owner === undefined || lease.owner !== owner)
|
|
1260
|
+
if (lease.maintenance || lease.executing || owner === undefined || lease.owner !== owner)
|
|
1240
1261
|
throw new MarAgentError('MAR_AGENT_SESSION_LOCKED', 'Session already has a writer.');
|
|
1241
1262
|
if (lease.appendFailure)
|
|
1242
1263
|
throw lease.appendFailure.error;
|
|
@@ -1262,12 +1283,89 @@ export class JsonlSessionStore {
|
|
|
1262
1283
|
const lease = this.#shared.writerLeases.get(sessionId);
|
|
1263
1284
|
return lease?.owner === owner && lease.retained > 0;
|
|
1264
1285
|
}
|
|
1286
|
+
async acquireSessionMaintenance(sessionId, kind, owner) {
|
|
1287
|
+
this.#assertOpen();
|
|
1288
|
+
const acquired = new Map();
|
|
1289
|
+
let sessionIds = [];
|
|
1290
|
+
let released = false;
|
|
1291
|
+
const holding = Object.freeze({
|
|
1292
|
+
get sessionIds() {
|
|
1293
|
+
return sessionIds;
|
|
1294
|
+
},
|
|
1295
|
+
release: async () => {
|
|
1296
|
+
if (released)
|
|
1297
|
+
return;
|
|
1298
|
+
released = true;
|
|
1299
|
+
this.#maintenance.delete(holding);
|
|
1300
|
+
let failure;
|
|
1301
|
+
for (const [id, writer] of [...acquired].reverse()) {
|
|
1302
|
+
try {
|
|
1303
|
+
await this.#serializeRecordOperation(id, async () => {
|
|
1304
|
+
delete writer.maintenance;
|
|
1305
|
+
await this.#releaseUnusedWriter(id, writer);
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
catch (error) {
|
|
1309
|
+
failure ??= { error };
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
if (failure)
|
|
1313
|
+
throw failure.error;
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
try {
|
|
1317
|
+
// Re-read membership after acquiring writers; never hand the SDK a pre-lock tree snapshot.
|
|
1318
|
+
for (;;) {
|
|
1319
|
+
const tree = kind === 'delete' ? await this.sessionTree(sessionId) : [sessionId];
|
|
1320
|
+
for (const id of tree.toReversed()) {
|
|
1321
|
+
if (acquired.has(id))
|
|
1322
|
+
continue;
|
|
1323
|
+
await this.#serializeRecordOperation(id, async () => {
|
|
1324
|
+
this.#assertOpen();
|
|
1325
|
+
let writer = this.#shared.writerLeases.get(id);
|
|
1326
|
+
if (writer &&
|
|
1327
|
+
(writer.maintenance ||
|
|
1328
|
+
owner === undefined ||
|
|
1329
|
+
writer.owner !== owner ||
|
|
1330
|
+
(kind === 'clear' && writer.executing)))
|
|
1331
|
+
throw new MarAgentError('MAR_AGENT_SESSION_LOCKED', 'Session already has a writer.');
|
|
1332
|
+
if (!writer) {
|
|
1333
|
+
// Deleting a stale index entry must not recreate its missing directory.
|
|
1334
|
+
const release = kind === 'delete' && !(await sessionDirectoryExists(this.#sessionDirectory(id)))
|
|
1335
|
+
? async () => { }
|
|
1336
|
+
: await this.#acquireSessionLease(id);
|
|
1337
|
+
writer = { owner, executing: false, retained: 0, release };
|
|
1338
|
+
this.#shared.writerLeases.set(id, writer);
|
|
1339
|
+
}
|
|
1340
|
+
writer.maintenance = holding;
|
|
1341
|
+
acquired.set(id, writer);
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
const current = kind === 'clear' ? tree : await this.sessionTree(sessionId);
|
|
1345
|
+
if (current.every((id) => acquired.has(id))) {
|
|
1346
|
+
sessionIds = Object.freeze(current);
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
this.#maintenance.set(holding, { sessionId, kind });
|
|
1351
|
+
return holding;
|
|
1352
|
+
}
|
|
1353
|
+
catch (error) {
|
|
1354
|
+
await holding.release().catch(() => undefined);
|
|
1355
|
+
throw error;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
#assertMaintenance(sessionId, kind, holding) {
|
|
1359
|
+
const current = this.#maintenance.get(holding);
|
|
1360
|
+
if (current?.sessionId !== sessionId || current.kind !== kind)
|
|
1361
|
+
throw new MarAgentError('MAR_AGENT_SESSION_LOCKED', 'Session maintenance ownership is invalid.');
|
|
1362
|
+
}
|
|
1265
1363
|
/** Native writes join their Agent's sole writer, without admitting another Execution. */
|
|
1266
1364
|
async acquireSessionWriteLease(sessionId, owner) {
|
|
1267
1365
|
return this.#serializeRecordOperation(sessionId, async () => {
|
|
1268
1366
|
this.#assertOpen();
|
|
1269
1367
|
let lease = this.#shared.writerLeases.get(sessionId);
|
|
1270
|
-
if (lease && lease.owner !== owner)
|
|
1368
|
+
if (lease && (lease.maintenance || lease.owner !== owner))
|
|
1271
1369
|
throw new MarAgentError('MAR_AGENT_SESSION_LOCKED', 'Session already has a writer.');
|
|
1272
1370
|
if (lease?.appendFailure)
|
|
1273
1371
|
throw lease.appendFailure.error;
|
|
@@ -1291,7 +1389,7 @@ export class JsonlSessionStore {
|
|
|
1291
1389
|
async retainSessionLease(sessionId, owner) {
|
|
1292
1390
|
return this.#serializeRecordOperation(sessionId, async () => {
|
|
1293
1391
|
const lease = this.#shared.writerLeases.get(sessionId);
|
|
1294
|
-
if (!lease || !lease.executing || lease.owner !== owner)
|
|
1392
|
+
if (!lease || lease.maintenance || !lease.executing || lease.owner !== owner)
|
|
1295
1393
|
throw new MarAgentError('MAR_AGENT_SESSION_LOCKED', 'Retained work requires its originating Session writer.');
|
|
1296
1394
|
lease.retained++;
|
|
1297
1395
|
let released = false;
|
|
@@ -1305,7 +1403,7 @@ export class JsonlSessionStore {
|
|
|
1305
1403
|
});
|
|
1306
1404
|
}
|
|
1307
1405
|
async #releaseUnusedWriter(sessionId, lease) {
|
|
1308
|
-
if (lease.executing || lease.retained)
|
|
1406
|
+
if (lease.maintenance || lease.executing || lease.retained)
|
|
1309
1407
|
return;
|
|
1310
1408
|
try {
|
|
1311
1409
|
await lease.release();
|
|
@@ -1472,20 +1570,8 @@ export class JsonlSessionStore {
|
|
|
1472
1570
|
else
|
|
1473
1571
|
resume.reasoningEffort = executionModel.reasoningEffort;
|
|
1474
1572
|
}
|
|
1475
|
-
if (isCompactCheckpoint(record))
|
|
1573
|
+
if (isCompactCheckpoint(record))
|
|
1476
1574
|
resume.compactSequence = record.sequence;
|
|
1477
|
-
delete resume.gcSequence;
|
|
1478
|
-
delete resume.gcGeneration;
|
|
1479
|
-
delete resume.gcBaseSequence;
|
|
1480
|
-
}
|
|
1481
|
-
else {
|
|
1482
|
-
const checkpoint = parseContextGcCheckpoint(record);
|
|
1483
|
-
if (checkpoint) {
|
|
1484
|
-
resume.gcSequence = record.sequence;
|
|
1485
|
-
resume.gcGeneration = checkpoint.generation;
|
|
1486
|
-
resume.gcBaseSequence = contextGcBaseSequence(checkpoint.base);
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
1575
|
if (record.type !== 'todo.updated' || !todoItemsFromSnapshot(record.payload))
|
|
1490
1576
|
return;
|
|
1491
1577
|
resume.todoSequence = record.sequence;
|
|
@@ -1723,22 +1809,12 @@ export class JsonlSessionStore {
|
|
|
1723
1809
|
throw error;
|
|
1724
1810
|
}
|
|
1725
1811
|
}
|
|
1726
|
-
async delete(sessionId) {
|
|
1812
|
+
async delete(sessionId, maintenance) {
|
|
1727
1813
|
this.#assertOpen();
|
|
1728
|
-
await this
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
const releases = [];
|
|
1733
|
-
try {
|
|
1734
|
-
for (const id of tree.toReversed())
|
|
1735
|
-
if (await sessionDirectoryExists(join(this.#shared.root, 'sessions', id)))
|
|
1736
|
-
releases.push(await this.acquireSessionLease(id));
|
|
1737
|
-
}
|
|
1738
|
-
catch (error) {
|
|
1739
|
-
await Promise.allSettled(releases.toReversed().map((release) => release()));
|
|
1740
|
-
throw error;
|
|
1741
|
-
}
|
|
1814
|
+
const holding = maintenance ?? (await this.acquireSessionMaintenance(sessionId, 'delete'));
|
|
1815
|
+
this.#assertMaintenance(sessionId, 'delete', holding);
|
|
1816
|
+
const tree = holding.sessionIds;
|
|
1817
|
+
let failed = false;
|
|
1742
1818
|
try {
|
|
1743
1819
|
const deleted = await sessionDirectoryExists(join(this.#shared.root, 'sessions', sessionId));
|
|
1744
1820
|
for (const id of tree.toReversed()) {
|
|
@@ -1751,8 +1827,18 @@ export class JsonlSessionStore {
|
|
|
1751
1827
|
await this.#saveIndex(tree);
|
|
1752
1828
|
return { deleted };
|
|
1753
1829
|
}
|
|
1830
|
+
catch (error) {
|
|
1831
|
+
failed = true;
|
|
1832
|
+
throw error;
|
|
1833
|
+
}
|
|
1754
1834
|
finally {
|
|
1755
|
-
|
|
1835
|
+
if (!maintenance) {
|
|
1836
|
+
const releasing = holding.release();
|
|
1837
|
+
if (failed)
|
|
1838
|
+
await releasing.catch(() => undefined);
|
|
1839
|
+
else
|
|
1840
|
+
await releasing;
|
|
1841
|
+
}
|
|
1756
1842
|
}
|
|
1757
1843
|
}
|
|
1758
1844
|
#assertOpen() {
|
|
@@ -58,9 +58,6 @@ export interface SessionMeta {
|
|
|
58
58
|
version: 1;
|
|
59
59
|
userTurns?: number;
|
|
60
60
|
compactSequence?: number;
|
|
61
|
-
gcSequence?: number;
|
|
62
|
-
gcGeneration?: number;
|
|
63
|
-
gcBaseSequence?: number;
|
|
64
61
|
todoSequence?: number;
|
|
65
62
|
todoRevision: number;
|
|
66
63
|
executionModelIndexed?: true;
|
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export type { CoreProcessesApi, CoreProcessesBackend, CoreProcessInput, CoreProc
|
|
|
41
41
|
export type { CreateMarAgentOptions, CreateSessionInput, ExecutionHandle, CompactInput, SteerInput, SteerResult, MarAgent, MarAgentSession, SessionTitleUpdateInput } from './agent-core/sdk/types.js';
|
|
42
42
|
export { openMarAgentSessionCatalog, type MarAgentContextUsage, type MarAgentHistoryEntry, type MarAgentHistoryPage, type MarAgentSessionCatalog, type MarAgentSessionDeleteResult, type MarAgentSessionImage, type MarAgentSessionPage, type MarAgentSessionSummary } from './agent-core/session/catalog.js';
|
|
43
43
|
export type { SessionListInput, SessionSource } from './agent-core/session/types.js';
|
|
44
|
-
export type { SessionStore, SessionStoreSession, SessionRecordInput, SessionStoreCompactInput, SessionRecord, SessionExecutionState, CompactHistoryMessage, CompactUserInput, SessionInputImage, SessionInputImageMetadata, SessionInputImageData, RestoredSessionInputImage, CodeModeStateEntry, TodoSnapshot, TodoItem, CodeToolCallRecord, CodeObservation, ModelMessage, ModelUsage, ModelInputMetadata, ModelTextBlock, ModelContextualBlock, EnvironmentContextState, ContextGcCheckpointDraft, ContextGcBase, ContextGcPlatform, ContextGcEconomicsPolicy, ContextGcMetrics, ContextGcReplacement, ContextGcTarget, ContextGcReadItemReplacement, ContextGcWebToolName, ContextGcSubagentToolName, AvailableToolResultResource } from './agent-core/session/store/contracts.js';
|
|
44
|
+
export type { SessionStore, SessionStoreMaintenance, SessionStoreSession, SessionRecordInput, SessionStoreCompactInput, SessionRecord, SessionExecutionState, CompactHistoryMessage, CompactUserInput, SessionInputImage, SessionInputImageMetadata, SessionInputImageData, RestoredSessionInputImage, CodeModeStateEntry, TodoSnapshot, TodoItem, CodeToolCallRecord, CodeObservation, ModelMessage, ModelUsage, ModelInputMetadata, ModelTextBlock, ModelContextualBlock, EnvironmentContextState, ContextGcCheckpointDraft, ContextGcBase, ContextGcPlatform, ContextGcEconomicsPolicy, ContextGcMetrics, ContextGcReplacement, ContextGcTarget, ContextGcReadItemReplacement, ContextGcWebToolName, ContextGcSubagentToolName, AvailableToolResultResource } from './agent-core/session/store/contracts.js';
|
|
45
45
|
export type { OpenMarAgentSessionCatalogInput } from './agent-core/session/catalog.js';
|
|
46
46
|
export type { ApplyPatchFileResult, ApplyPatchResult, ApplyPatchPartialResult, ClientToolDefinition, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolCaller, ToolExecutionContext, ToolExecutionResult } from './agent-core/tools/contracts.js';
|
|
47
47
|
export { MCP_DEFINITION_LIMITS, assertMcpToolCatalog, assertMcpToolDefinition } from './agent-core/tools/mcp/catalog.js';
|