@myagentroam/agent 0.10.60 → 0.10.61
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/sdk/agent.js +1 -1
- package/dist/agent-core/sdk/extension-agents.js +5 -1
- package/dist/agent-core/sdk/session.d.ts +18 -12
- package/dist/agent-core/sdk/session.js +34 -39
- package/dist/agent-core/sdk/subagent-backend.js +20 -17
- package/dist/agent-core/session/store/directory-index.d.ts +2 -2
- package/dist/agent-core/session/store/directory-index.js +17 -11
- package/dist/agent-core/session/store/execution-state.d.ts +1 -0
- package/dist/agent-core/session/store/execution-state.js +9 -0
- package/dist/agent-core/session/store/jsonl-store.d.ts +2 -2
- package/dist/agent-core/session/store/jsonl-store.js +217 -218
- package/dist/agent-core/session/store/types.d.ts +1 -0
- package/dist/agent-core/subagent/scheduler.js +1 -1
- package/dist/agent-core/subagent/session-controller.d.ts +4 -1
- package/dist/agent-core/subagent/session-controller.js +99 -15
- package/package.json +1 -1
|
@@ -100,7 +100,7 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
100
100
|
return schedule(definition, run);
|
|
101
101
|
},
|
|
102
102
|
baseTools: (id, context) => {
|
|
103
|
-
const execution = sessionRuntimes.get(id)?.toolSources;
|
|
103
|
+
const execution = sessionRuntimes.get(id)?.execution?.toolSources;
|
|
104
104
|
if (context?.executionId && execution?.executionId === context.executionId)
|
|
105
105
|
return execution.bindings;
|
|
106
106
|
return (options.host.extensionPorts?.tools?.catalog(id, context?.executionId) ?? []).map((source) => ({
|
|
@@ -28,7 +28,11 @@ export function createExtensionAgentsBackend(input) {
|
|
|
28
28
|
throw new MarAgentError(created.errorCode ?? 'MAR_AGENT_SUBAGENT_FAILED', created.errorMessage ?? 'Agent could not start.');
|
|
29
29
|
}
|
|
30
30
|
})),
|
|
31
|
-
list: (call) => withController(call, async (controller) =>
|
|
31
|
+
list: (call) => withController(call, async (controller) => {
|
|
32
|
+
await controller.prepareAll(call.signal);
|
|
33
|
+
call.signal.throwIfAborted();
|
|
34
|
+
return controller.inspectAll();
|
|
35
|
+
}),
|
|
32
36
|
inspect: (target, call) => withController(call, (controller) => controller.inspect(target.agentId)),
|
|
33
37
|
wait: (target, call) => withController(call, (controller) => {
|
|
34
38
|
const selection = parseAgentWaitTargets(target);
|
|
@@ -14,9 +14,25 @@ import { JsonlSessionStore } from '../session/store/index.js';
|
|
|
14
14
|
import { SubagentSessionController, type SubagentMailboxMessage, type SubagentMessageDelivery, type SubagentTurnHandle } from '../subagent/session-controller.js';
|
|
15
15
|
import { createSessionToolScheduler } from '../tools/scheduler.js';
|
|
16
16
|
import { TodoStore } from '../tools/agent/todo.js';
|
|
17
|
-
import type { CreateMarAgentOptions, ExecutionResult, MarAgentSession } from './types.js';
|
|
18
|
-
|
|
17
|
+
import type { CreateMarAgentOptions, ExecutionMode, ExecutionResult, MarAgentSession } from './types.js';
|
|
18
|
+
interface SessionExecutionContext {
|
|
19
|
+
readonly executionId: string;
|
|
20
|
+
readonly modelId: string;
|
|
21
|
+
readonly mode: ExecutionMode;
|
|
22
|
+
readonly models: ReadonlyMap<string, ResolvedModelConfiguration>;
|
|
23
|
+
readonly instruction: string | undefined;
|
|
19
24
|
steer?: (input: import('./types.js').SteerInput) => Promise<import('./types.js').SteerResult>;
|
|
25
|
+
compactRequests?: CompactRequests;
|
|
26
|
+
requestMessages?: {
|
|
27
|
+
read(): readonly ModelMessage[];
|
|
28
|
+
};
|
|
29
|
+
appendedMessages?: {
|
|
30
|
+
append(recordId: string, message: ModelMessage): void;
|
|
31
|
+
};
|
|
32
|
+
toolSources?: ExecutionToolSources;
|
|
33
|
+
}
|
|
34
|
+
export interface SessionRuntime {
|
|
35
|
+
execution?: SessionExecutionContext;
|
|
20
36
|
todo?: TodoStore;
|
|
21
37
|
readonly scheduleTool: ReturnType<typeof createSessionToolScheduler>;
|
|
22
38
|
readonly sourceType: 'user' | 'subagent';
|
|
@@ -26,22 +42,12 @@ export interface SessionRuntime {
|
|
|
26
42
|
subagents?: SubagentSessionController;
|
|
27
43
|
subagentsOpening?: Promise<SubagentSessionController>;
|
|
28
44
|
inputs?: SessionInputCoordinator;
|
|
29
|
-
compactRequests?: CompactRequests;
|
|
30
45
|
requestSnapshots?: ExtensionRequestSnapshots;
|
|
31
|
-
requestMessages?: {
|
|
32
|
-
readonly executionId: string;
|
|
33
|
-
read(): readonly ModelMessage[];
|
|
34
|
-
};
|
|
35
46
|
recordProjection?: Promise<unknown>;
|
|
36
|
-
appendedMessages?: {
|
|
37
|
-
readonly executionId: string;
|
|
38
|
-
append(recordId: string, message: ModelMessage): void;
|
|
39
|
-
};
|
|
40
47
|
sessionBackend?: CoreSessionBackend & {
|
|
41
48
|
kick(): void;
|
|
42
49
|
};
|
|
43
50
|
agentsBackend?: CoreAgentsBackend;
|
|
44
|
-
toolSources?: ExecutionToolSources;
|
|
45
51
|
}
|
|
46
52
|
interface InternalMarAgentSession extends MarAgentSession {
|
|
47
53
|
startSubagentTurn(input: {
|
|
@@ -123,7 +123,6 @@ export function createSessionOpener(services) {
|
|
|
123
123
|
text: sessionSource.type === 'subagent' ? childSubagentInstruction || null : null
|
|
124
124
|
}
|
|
125
125
|
]);
|
|
126
|
-
let promptDefaults;
|
|
127
126
|
const sessionAdapter = (modelId, configuredModels) => {
|
|
128
127
|
let selected = sessionRuntime.adapters.get(modelId);
|
|
129
128
|
if (selected === undefined) {
|
|
@@ -187,7 +186,7 @@ export function createSessionOpener(services) {
|
|
|
187
186
|
};
|
|
188
187
|
recordSessionInputCommit(entry, result);
|
|
189
188
|
if (message)
|
|
190
|
-
sessionRuntime.appendedMessages?.append(record.recordId, message);
|
|
189
|
+
sessionRuntime.execution?.appendedMessages?.append(record.recordId, message);
|
|
191
190
|
return result;
|
|
192
191
|
});
|
|
193
192
|
}
|
|
@@ -208,7 +207,6 @@ export function createSessionOpener(services) {
|
|
|
208
207
|
const messageSources = new WeakMap();
|
|
209
208
|
let liveModelMessages = [];
|
|
210
209
|
const compactRequests = new CompactRequests(executionId, () => compactCoordinator.assertNotReentrant());
|
|
211
|
-
sessionRuntime.compactRequests = compactRequests;
|
|
212
210
|
const permissionPolicy = new ExecutionToolPermissions(mode === 'compact' ? 'full-access' : resolvePermissionMode(input.permissionMode), { sessionId, executionId }, host.requestToolApproval
|
|
213
211
|
? {
|
|
214
212
|
request: (request, signal) => host.requestToolApproval(request, signal),
|
|
@@ -225,13 +223,15 @@ export function createSessionOpener(services) {
|
|
|
225
223
|
...currentLocalTimeContext()
|
|
226
224
|
};
|
|
227
225
|
const executionSystemInstruction = settings.systemInstruction;
|
|
228
|
-
|
|
226
|
+
const executionContext = {
|
|
229
227
|
executionId,
|
|
230
228
|
modelId: input.modelId ?? executionDefaultModelId,
|
|
231
229
|
mode: mode === 'compact' ? 'run' : mode,
|
|
232
230
|
models: executionModels,
|
|
233
|
-
instruction: executionSystemInstruction
|
|
231
|
+
instruction: executionSystemInstruction,
|
|
232
|
+
compactRequests
|
|
234
233
|
};
|
|
234
|
+
sessionRuntime.execution = executionContext;
|
|
235
235
|
const manualCompactionId = mode === 'compact' ? randomUUID() : undefined;
|
|
236
236
|
active.set(`${sessionId}:${executionId}`, controller);
|
|
237
237
|
const inputs = sessionInputs();
|
|
@@ -369,8 +369,7 @@ export function createSessionOpener(services) {
|
|
|
369
369
|
await inputs.idle();
|
|
370
370
|
let immediateTarget;
|
|
371
371
|
const pendingAppends = [];
|
|
372
|
-
|
|
373
|
-
executionId,
|
|
372
|
+
executionContext.appendedMessages = {
|
|
374
373
|
append(recordId, message) {
|
|
375
374
|
pendingAppends.push({
|
|
376
375
|
recordId,
|
|
@@ -755,7 +754,7 @@ export function createSessionOpener(services) {
|
|
|
755
754
|
const coreExtensions = await services.extensions.prepare(sessionId, controller.signal, sourceBindings, sessionSource.type === 'user' &&
|
|
756
755
|
(mode === 'run' || mode === 'plan') &&
|
|
757
756
|
!internal.automaticInput);
|
|
758
|
-
|
|
757
|
+
executionContext.toolSources = sources;
|
|
759
758
|
const operation = (run) => services.extensions.operations.run({
|
|
760
759
|
sessionId,
|
|
761
760
|
sessionEpoch: coreExtensions.epoch,
|
|
@@ -863,6 +862,7 @@ export function createSessionOpener(services) {
|
|
|
863
862
|
executionTools = mcpRegistry.project(baseTools());
|
|
864
863
|
};
|
|
865
864
|
const loadRetainedSessionResources = async () => {
|
|
865
|
+
await subagents?.prepareSessionResources(controller.signal);
|
|
866
866
|
const hostResources = (await host.listSessionResources?.(sessionId)) ?? {
|
|
867
867
|
processes: []
|
|
868
868
|
};
|
|
@@ -1128,8 +1128,7 @@ export function createSessionOpener(services) {
|
|
|
1128
1128
|
sources.bind(captureRoundToolScope().toolScope);
|
|
1129
1129
|
let preparingRequestMessages;
|
|
1130
1130
|
let preparingRequestBaseLength = 0;
|
|
1131
|
-
|
|
1132
|
-
executionId,
|
|
1131
|
+
executionContext.requestMessages = {
|
|
1133
1132
|
read: () => preparingRequestMessages !== undefined
|
|
1134
1133
|
? normalizeMessagesForModel([...preparingRequestMessages, ...messages.slice(preparingRequestBaseLength)], { supportsImages: selectedModel.inputCapabilities.includes('IMAGE') })
|
|
1135
1134
|
: projectSessionRequestMessages(messages.map((message, index) => index === currentExecutionUserIndex && modelInputImages?.length
|
|
@@ -1192,7 +1191,7 @@ export function createSessionOpener(services) {
|
|
|
1192
1191
|
return { executionId, status: 'completed', finalAnswer: '', inputHandled: true };
|
|
1193
1192
|
}
|
|
1194
1193
|
acceptsSteer = true;
|
|
1195
|
-
|
|
1194
|
+
executionContext.steer = createNativeSteer({
|
|
1196
1195
|
executionId,
|
|
1197
1196
|
inputs,
|
|
1198
1197
|
signal: controller.signal,
|
|
@@ -1885,12 +1884,12 @@ export function createSessionOpener(services) {
|
|
|
1885
1884
|
finalAnswer = executionLoop?.finalAnswer ?? finalAnswer;
|
|
1886
1885
|
acceptsMailbox = false;
|
|
1887
1886
|
acceptsSteer = false;
|
|
1888
|
-
delete
|
|
1887
|
+
delete executionContext.steer;
|
|
1889
1888
|
await sessionRuntime.recordProjection?.catch(() => undefined);
|
|
1890
1889
|
await compactCoordinator.close(error);
|
|
1891
1890
|
compactRequests.close(new MarAgentError('MAR_AGENT_CANCELLED', 'Execution ended.'));
|
|
1892
|
-
if (
|
|
1893
|
-
delete
|
|
1891
|
+
if (executionContext.compactRequests === compactRequests)
|
|
1892
|
+
delete executionContext.compactRequests;
|
|
1894
1893
|
if (controller.signal.aborted) {
|
|
1895
1894
|
subagents?.endParentExecution(executionId);
|
|
1896
1895
|
await emit({ type: 'execution.cancelled' });
|
|
@@ -1912,19 +1911,16 @@ export function createSessionOpener(services) {
|
|
|
1912
1911
|
};
|
|
1913
1912
|
}
|
|
1914
1913
|
finally {
|
|
1915
|
-
|
|
1916
|
-
delete sessionRuntime.requestMessages;
|
|
1914
|
+
delete executionContext.requestMessages;
|
|
1917
1915
|
compactRequests.close(new MarAgentError('MAR_AGENT_CANCELLED', 'Execution ended.'));
|
|
1918
|
-
if (
|
|
1919
|
-
delete
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
if (promptDefaults?.executionId === executionId)
|
|
1923
|
-
promptDefaults = undefined;
|
|
1916
|
+
if (executionContext.compactRequests === compactRequests)
|
|
1917
|
+
delete executionContext.compactRequests;
|
|
1918
|
+
delete executionContext.appendedMessages;
|
|
1919
|
+
delete executionContext.steer;
|
|
1924
1920
|
acceptsMailbox = false;
|
|
1925
1921
|
executionToolSources?.close();
|
|
1926
|
-
if (
|
|
1927
|
-
delete
|
|
1922
|
+
if (executionContext.toolSources === executionToolSources)
|
|
1923
|
+
delete executionContext.toolSources;
|
|
1928
1924
|
internal.signal?.removeEventListener('abort', abortSource);
|
|
1929
1925
|
await compactCoordinator.close(controller.signal.reason ?? new MarAgentError('MAR_AGENT_CANCELLED', 'Execution ended.'));
|
|
1930
1926
|
await selectedTurn?.close().catch(() => undefined);
|
|
@@ -1940,6 +1936,8 @@ export function createSessionOpener(services) {
|
|
|
1940
1936
|
}
|
|
1941
1937
|
subagents?.endParentExecution(executionId);
|
|
1942
1938
|
await releaseSession?.();
|
|
1939
|
+
if (sessionRuntime.execution === executionContext)
|
|
1940
|
+
delete sessionRuntime.execution;
|
|
1943
1941
|
active.delete(`${sessionId}:${executionId}`);
|
|
1944
1942
|
sessionRuntime.sessionBackend?.kick();
|
|
1945
1943
|
queue.close();
|
|
@@ -1974,20 +1972,15 @@ export function createSessionOpener(services) {
|
|
|
1974
1972
|
sessionRuntime.sessionBackend ??= createExtensionSessionBackend({
|
|
1975
1973
|
describe: async (call) => {
|
|
1976
1974
|
call.signal.throwIfAborted();
|
|
1975
|
+
const defaults = sessionRuntime.execution;
|
|
1976
|
+
const state = defaults ?? services.modelState();
|
|
1977
|
+
const modelId = defaults?.modelId ?? services.modelState().defaultModelId;
|
|
1977
1978
|
const current = await store.getSession(sessionId);
|
|
1978
|
-
const
|
|
1979
|
+
const userTurns = await store.userTurnCount(sessionId);
|
|
1979
1980
|
call.signal.throwIfAborted();
|
|
1980
|
-
const state = promptDefaults ?? services.modelState();
|
|
1981
|
-
const modelId = promptDefaults?.modelId ?? services.modelState().defaultModelId;
|
|
1982
1981
|
const model = state.models.get(modelId);
|
|
1983
1982
|
if (!model)
|
|
1984
1983
|
throw new MarAgentError('MAR_AGENT_MODEL_DISABLED', 'Selected model is unavailable.');
|
|
1985
|
-
const userTurns = records.filter((record) => {
|
|
1986
|
-
if (record.type !== 'turn.user')
|
|
1987
|
-
return false;
|
|
1988
|
-
const payload = record.payload;
|
|
1989
|
-
return payload.inputSource === undefined;
|
|
1990
|
-
}).length;
|
|
1991
1984
|
return {
|
|
1992
1985
|
sessionId,
|
|
1993
1986
|
...(logicalWorkspace ? { cwd: logicalWorkspace, root: logicalWorkspace } : {}),
|
|
@@ -2000,9 +1993,10 @@ export function createSessionOpener(services) {
|
|
|
2000
1993
|
},
|
|
2001
1994
|
composePrompt: async (facts, call) => {
|
|
2002
1995
|
call.signal.throwIfAborted();
|
|
2003
|
-
const defaults =
|
|
1996
|
+
const defaults = sessionRuntime.execution;
|
|
2004
1997
|
const state = defaults ?? services.modelState();
|
|
2005
1998
|
const modelId = facts.modelId ?? defaults?.modelId ?? services.modelState().defaultModelId;
|
|
1999
|
+
const instruction = defaults ? defaults.instruction : settings.systemInstruction;
|
|
2006
2000
|
const model = state.models.get(modelId);
|
|
2007
2001
|
if (!model)
|
|
2008
2002
|
throw new MarAgentError('MAR_AGENT_MODEL_DISABLED', 'Selected model is unavailable.');
|
|
@@ -2047,7 +2041,7 @@ export function createSessionOpener(services) {
|
|
|
2047
2041
|
});
|
|
2048
2042
|
call.signal.throwIfAborted();
|
|
2049
2043
|
return {
|
|
2050
|
-
sections: buildSessionPromptSections(tools, mode, model,
|
|
2044
|
+
sections: buildSessionPromptSections(tools, mode, model, instruction, instructions?.skillCatalog),
|
|
2051
2045
|
facts: { modelId, mode, tools: tools.map((tool) => tool.name) }
|
|
2052
2046
|
};
|
|
2053
2047
|
},
|
|
@@ -2075,8 +2069,9 @@ export function createSessionOpener(services) {
|
|
|
2075
2069
|
}
|
|
2076
2070
|
call.signal.throwIfAborted();
|
|
2077
2071
|
const targetRuntime = sessionRuntimes.get(target);
|
|
2078
|
-
|
|
2079
|
-
|
|
2072
|
+
const requestMessages = targetRuntime?.execution?.requestMessages;
|
|
2073
|
+
if (selection.view === 'request' && requestMessages)
|
|
2074
|
+
return sessionMessagesPage(requestMessages.read(), selection);
|
|
2080
2075
|
const modelState = services.modelState();
|
|
2081
2076
|
const requestModel = selection.view === 'request'
|
|
2082
2077
|
? modelState.models.get(targetModelId ?? modelState.defaultModelId)
|
|
@@ -2215,8 +2210,8 @@ export function createSessionOpener(services) {
|
|
|
2215
2210
|
id: sessionId,
|
|
2216
2211
|
run: (input) => execution('run', input),
|
|
2217
2212
|
plan: (input) => execution('plan', input),
|
|
2218
|
-
steer: (input) => sessionRuntime.steer?.(input) ?? Promise.resolve({ status: 'notAccepted' }),
|
|
2219
|
-
compact: (input = {}) => sessionRuntime.compactRequests?.request(input) ??
|
|
2213
|
+
steer: (input) => sessionRuntime.execution?.steer?.(input) ?? Promise.resolve({ status: 'notAccepted' }),
|
|
2214
|
+
compact: (input = {}) => sessionRuntime.execution?.compactRequests?.request(input) ??
|
|
2220
2215
|
execution('compact', {
|
|
2221
2216
|
prompt: input.prompt ?? DEFAULT_COMPACTION_FOCUS,
|
|
2222
2217
|
...input
|
|
@@ -14,33 +14,36 @@ export function createSubagentBackend(store, workspace) {
|
|
|
14
14
|
},
|
|
15
15
|
listChildren: async (parentSessionId) => {
|
|
16
16
|
const sessions = await store.listSubagentChildren(parentSessionId);
|
|
17
|
-
return
|
|
17
|
+
return sessions.map((session) => ({
|
|
18
18
|
id: session.id,
|
|
19
19
|
createdAt: session.createdAt,
|
|
20
20
|
lastActivityAt: session.lastActivityAt,
|
|
21
|
-
source: session.source
|
|
22
|
-
|
|
23
|
-
})));
|
|
21
|
+
source: session.source
|
|
22
|
+
}));
|
|
24
23
|
},
|
|
25
24
|
loadChild: async (parentSessionId, childSessionId) => {
|
|
26
|
-
let session;
|
|
27
25
|
try {
|
|
28
|
-
session = await store.getSession(childSessionId);
|
|
26
|
+
const session = await store.getSession(childSessionId);
|
|
27
|
+
if (session.source.type !== 'subagent' ||
|
|
28
|
+
session.source.parentSessionId !== parentSessionId)
|
|
29
|
+
return undefined;
|
|
30
|
+
return {
|
|
31
|
+
id: session.id,
|
|
32
|
+
createdAt: session.createdAt,
|
|
33
|
+
lastActivityAt: session.lastActivityAt,
|
|
34
|
+
source: session.source,
|
|
35
|
+
records: await store.read(session.id)
|
|
36
|
+
};
|
|
29
37
|
}
|
|
30
38
|
catch (error) {
|
|
31
|
-
if (error instanceof MarAgentError && error.code === 'MAR_AGENT_SESSION_NOT_FOUND')
|
|
32
|
-
|
|
39
|
+
if (error instanceof MarAgentError && error.code === 'MAR_AGENT_SESSION_NOT_FOUND') {
|
|
40
|
+
// A history read failure is not proof that the child was deleted.
|
|
41
|
+
const children = await store.listSubagentChildren(parentSessionId);
|
|
42
|
+
if (!children.some((child) => child.id === childSessionId))
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
33
45
|
throw error;
|
|
34
46
|
}
|
|
35
|
-
if (session.source.type !== 'subagent' || session.source.parentSessionId !== parentSessionId)
|
|
36
|
-
return undefined;
|
|
37
|
-
return {
|
|
38
|
-
id: session.id,
|
|
39
|
-
createdAt: session.createdAt,
|
|
40
|
-
lastActivityAt: session.lastActivityAt,
|
|
41
|
-
source: session.source,
|
|
42
|
-
records: await store.read(session.id)
|
|
43
|
-
};
|
|
44
47
|
}
|
|
45
48
|
};
|
|
46
49
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SessionListInput, SessionSource } from '../types.js';
|
|
2
2
|
import type { SessionIndex, SessionMeta } from './types.js';
|
|
3
|
-
/** Merge
|
|
4
|
-
export declare function
|
|
3
|
+
/** Merge only derived fields, and only onto the history they were computed from. */
|
|
4
|
+
export declare function mergeSessionDerivedCache(current: SessionMeta, cached: SessionMeta): SessionMeta;
|
|
5
5
|
export declare function readSessionIndex(path: string): Promise<SessionIndex | undefined>;
|
|
6
6
|
export declare function persistSessionIndex(path: string, index: SessionIndex): Promise<void>;
|
|
7
7
|
export declare function normalizeSessionSource(value: SessionSource | undefined): SessionSource;
|
|
@@ -2,21 +2,25 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
4
4
|
import { MarAgentError } from '../../error.js';
|
|
5
|
-
/** Merge
|
|
6
|
-
export function
|
|
7
|
-
if (
|
|
8
|
-
current.
|
|
9
|
-
current
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
/** Merge only derived fields, and only onto the history they were computed from. */
|
|
6
|
+
export function mergeSessionDerivedCache(current, cached) {
|
|
7
|
+
if (current.nextSequence !== cached.nextSequence ||
|
|
8
|
+
current.storageTransactionId !== cached.storageTransactionId)
|
|
9
|
+
return current;
|
|
10
|
+
const preview = cached.resume?.firstUserMessageIndexed === true &&
|
|
11
|
+
current.resume?.firstUserMessagePreview === undefined &&
|
|
12
|
+
!(current.resume?.firstUserMessageIndexed === true &&
|
|
13
|
+
cached.resume.firstUserMessagePreview === undefined);
|
|
14
|
+
const count = current.resume?.userTurns === undefined && cached.resume?.userTurns !== undefined;
|
|
15
|
+
if (!preview && !count)
|
|
13
16
|
return current;
|
|
14
17
|
return {
|
|
15
18
|
...current,
|
|
16
19
|
resume: {
|
|
17
20
|
...(current.resume ?? { version: 1, todoRevision: 0 }),
|
|
18
|
-
|
|
19
|
-
...(
|
|
21
|
+
...(count ? { userTurns: cached.resume.userTurns } : {}),
|
|
22
|
+
...(preview ? { firstUserMessageIndexed: true } : {}),
|
|
23
|
+
...(!preview || cached.resume?.firstUserMessagePreview === undefined
|
|
20
24
|
? {}
|
|
21
25
|
: { firstUserMessagePreview: cached.resume.firstUserMessagePreview })
|
|
22
26
|
}
|
|
@@ -45,7 +49,9 @@ export async function readSessionIndex(path) {
|
|
|
45
49
|
typeof session.createdAt !== 'string' ||
|
|
46
50
|
typeof session.lastActivityAt !== 'string' ||
|
|
47
51
|
!Number.isSafeInteger(session.nextSequence) ||
|
|
48
|
-
session.nextSequence < 1
|
|
52
|
+
session.nextSequence < 1 ||
|
|
53
|
+
(session.resume?.userTurns !== undefined &&
|
|
54
|
+
(!Number.isSafeInteger(session.resume.userTurns) || session.resume.userTurns < 0))))
|
|
49
55
|
throw new Error('invalid index shape');
|
|
50
56
|
return value;
|
|
51
57
|
}
|
|
@@ -3,6 +3,7 @@ import type { CompactHistoryMessage, SessionExecutionState, SessionMeta, Session
|
|
|
3
3
|
export declare function deriveExecutionState(records: readonly SessionRecord[]): SessionExecutionState & {
|
|
4
4
|
resume: NonNullable<SessionMeta['resume']>;
|
|
5
5
|
};
|
|
6
|
+
export declare function isNativeUserTurn(record: SessionRecord): boolean;
|
|
6
7
|
export declare function userMessagePreviewFromRecord(record: SessionRecord): string | undefined;
|
|
7
8
|
export declare function executionModelFromRecord(record: SessionRecord): {
|
|
8
9
|
modelId: string;
|
|
@@ -9,7 +9,10 @@ export function deriveExecutionState(records) {
|
|
|
9
9
|
let todoItems = [];
|
|
10
10
|
let executionModel;
|
|
11
11
|
let firstUserMessagePreview;
|
|
12
|
+
let userTurns = 0;
|
|
12
13
|
for (const record of records) {
|
|
14
|
+
if (isNativeUserTurn(record))
|
|
15
|
+
userTurns++;
|
|
13
16
|
firstUserMessagePreview ??= userMessagePreviewFromRecord(record);
|
|
14
17
|
if (record.type === 'session.created')
|
|
15
18
|
automaticTitle =
|
|
@@ -40,6 +43,7 @@ export function deriveExecutionState(records) {
|
|
|
40
43
|
contextRecords: boundary < 0 ? [...records] : records.slice(boundary),
|
|
41
44
|
resume: {
|
|
42
45
|
version: 1,
|
|
46
|
+
userTurns,
|
|
43
47
|
...(compactSequence === undefined ? {} : { compactSequence }),
|
|
44
48
|
...(checkpoint === undefined
|
|
45
49
|
? {}
|
|
@@ -57,6 +61,10 @@ export function deriveExecutionState(records) {
|
|
|
57
61
|
}
|
|
58
62
|
};
|
|
59
63
|
}
|
|
64
|
+
export function isNativeUserTurn(record) {
|
|
65
|
+
return (record.type === 'turn.user' &&
|
|
66
|
+
record.payload.inputSource === undefined);
|
|
67
|
+
}
|
|
60
68
|
export function userMessagePreviewFromRecord(record) {
|
|
61
69
|
if (record.type !== 'turn.user' ||
|
|
62
70
|
!isObject(record.payload) ||
|
|
@@ -160,6 +168,7 @@ export function isCompactCheckpoint(record) {
|
|
|
160
168
|
}
|
|
161
169
|
export function sameResumeIndex(left, right) {
|
|
162
170
|
return (left?.version === right.version &&
|
|
171
|
+
left.userTurns === right.userTurns &&
|
|
163
172
|
left.compactSequence === right.compactSequence &&
|
|
164
173
|
left.gcSequence === right.gcSequence &&
|
|
165
174
|
left.gcGeneration === right.gcGeneration &&
|
|
@@ -24,7 +24,6 @@ export declare class JsonlSessionStore {
|
|
|
24
24
|
workspace: string;
|
|
25
25
|
source?: SessionSource;
|
|
26
26
|
}): Promise<SessionMeta>;
|
|
27
|
-
sessionDirectory(sessionId: string): string;
|
|
28
27
|
readContextualWorldState(sessionId: string): Promise<unknown | undefined>;
|
|
29
28
|
writeContextualWorldState(sessionId: string, state: unknown): Promise<void>;
|
|
30
29
|
/** Mark each image pending before publication; turn.user or lease recovery resolves ownership. */
|
|
@@ -102,7 +101,6 @@ export declare class JsonlSessionStore {
|
|
|
102
101
|
readonly expectedState?: readonly CodeModeStateEntry[];
|
|
103
102
|
}): Promise<CodeModeStateEntry[]>;
|
|
104
103
|
readExecutionState(sessionId: string): Promise<SessionExecutionState>;
|
|
105
|
-
readContext(sessionId: string): Promise<SessionRecord[]>;
|
|
106
104
|
/** Compare the planned sequence inside the existing record writer queue. */
|
|
107
105
|
commitContextGc(sessionId: string, draft: ContextGcCheckpointDraft, planningRecords?: readonly SessionRecord[]): Promise<SessionRecord>;
|
|
108
106
|
readPage(sessionId: string, input?: {
|
|
@@ -177,6 +175,8 @@ export declare class JsonlSessionStore {
|
|
|
177
175
|
readonly reasoningEffort?: string;
|
|
178
176
|
} | undefined>;
|
|
179
177
|
sessionTree(sessionId: string): Promise<readonly string[]>;
|
|
178
|
+
/** Counts native input records, not the post-Compact model context. */
|
|
179
|
+
userTurnCount(sessionId: string): Promise<number>;
|
|
180
180
|
firstUserMessagePreview(sessionId: string): Promise<string | undefined>;
|
|
181
181
|
delete(sessionId: string): Promise<{
|
|
182
182
|
readonly deleted: boolean;
|