@myagentroam/agent 0.10.47 → 0.10.49
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/extensions/session-input.d.ts +10 -0
- package/dist/agent-core/extensions/session-input.js +45 -3
- package/dist/agent-core/runtime/compaction/retained-context.js +2 -0
- package/dist/agent-core/runtime/context/projection.js +18 -1
- package/dist/agent-core/runtime/context/restore.d.ts +1 -0
- package/dist/agent-core/runtime/context/restore.js +20 -3
- package/dist/agent-core/runtime/event-projection.js +1 -0
- package/dist/agent-core/runtime/events.d.ts +5 -0
- package/dist/agent-core/sdk/native-steer.d.ts +19 -0
- package/dist/agent-core/sdk/native-steer.js +83 -0
- package/dist/agent-core/sdk/session.d.ts +1 -0
- package/dist/agent-core/sdk/session.js +135 -3
- package/dist/agent-core/sdk/types.d.ts +18 -0
- package/dist/agent-core/session/catalog.d.ts +2 -0
- package/dist/agent-core/session/catalog.js +2 -1
- package/dist/agent-core/session/deferred-input.d.ts +5 -0
- package/dist/agent-core/session/deferred-input.js +74 -0
- package/dist/agent-core/session/jsonl-store.d.ts +1 -0
- package/dist/agent-core/session/jsonl-store.js +34 -4
- package/dist/agent-core/subagent/session-controller.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -111,6 +111,11 @@ export interface CoreSessionBackend {
|
|
|
111
111
|
handled(input: CoreSubmitInput, call: CoreOperationContext, value?: unknown): CoreSessionReceipt;
|
|
112
112
|
}
|
|
113
113
|
export interface SessionInputEntry {
|
|
114
|
+
/** SDK-owned admission barrier, never accepted from Extension input. */
|
|
115
|
+
nativeSteer?: {
|
|
116
|
+
readonly ready: Promise<void>;
|
|
117
|
+
recordId?: string;
|
|
118
|
+
};
|
|
114
119
|
readonly request: CoreSessionRequest;
|
|
115
120
|
readonly source: CoreOperationContext;
|
|
116
121
|
readonly action: CoreSessionAction;
|
|
@@ -129,6 +134,9 @@ export declare class SessionInputCoordinator {
|
|
|
129
134
|
readonly diagnose: (receipt: CoreSessionReceipt) => void;
|
|
130
135
|
constructor(diagnose?: (receipt: CoreSessionReceipt) => void);
|
|
131
136
|
get hasPending(): boolean;
|
|
137
|
+
get boundarySize(): number;
|
|
138
|
+
get hasIdleBarrier(): boolean;
|
|
139
|
+
restoreSteer(recordId: string, text: string, source: CoreOperationContext): void;
|
|
132
140
|
get hasBoundaryPending(): boolean;
|
|
133
141
|
get executing(): boolean;
|
|
134
142
|
get hasBoundaryCompactPending(): boolean;
|
|
@@ -144,6 +152,8 @@ export declare class SessionInputCoordinator {
|
|
|
144
152
|
readonly executionId: string;
|
|
145
153
|
readonly turnId: string;
|
|
146
154
|
readonly signal: AbortSignal;
|
|
155
|
+
/** An already authorized idle launch restores older durable input before its own prompt. */
|
|
156
|
+
readonly restoring?: true;
|
|
147
157
|
}, limit?: number): Promise<boolean>;
|
|
148
158
|
idle(): Promise<void>;
|
|
149
159
|
retire(instanceId?: string): Promise<void>;
|
|
@@ -36,6 +36,29 @@ export class SessionInputCoordinator {
|
|
|
36
36
|
get hasPending() {
|
|
37
37
|
return this.#pending.length > 0;
|
|
38
38
|
}
|
|
39
|
+
get boundarySize() {
|
|
40
|
+
return this.#pending.length;
|
|
41
|
+
}
|
|
42
|
+
get hasIdleBarrier() {
|
|
43
|
+
return this.#pending.some((entry) => entry.receipt.status !== 'cancelled' &&
|
|
44
|
+
!entry.source.signal.aborted &&
|
|
45
|
+
entry.request.kind === 'submit' &&
|
|
46
|
+
entry.request.input.delivery === 'idle');
|
|
47
|
+
}
|
|
48
|
+
restoreSteer(recordId, text, source) {
|
|
49
|
+
const index = this.#pending.findIndex((entry) => entry.nativeSteer?.recordId === recordId);
|
|
50
|
+
const previous = this.#pending[index];
|
|
51
|
+
if (previous &&
|
|
52
|
+
!previous.source.signal.aborted &&
|
|
53
|
+
!['cancelled', 'failed'].includes(previous.receipt.status))
|
|
54
|
+
return;
|
|
55
|
+
const entry = this.#record({ kind: 'submit', input: { text } }, source, (request, call, next) => next(request, call), 'accepted');
|
|
56
|
+
entry.nativeSteer = { recordId, ready: Promise.resolve() };
|
|
57
|
+
if (index >= 0)
|
|
58
|
+
this.#pending[index] = entry;
|
|
59
|
+
else
|
|
60
|
+
this.#pending.unshift(entry);
|
|
61
|
+
}
|
|
39
62
|
get hasBoundaryPending() {
|
|
40
63
|
const head = this.#pending.find((entry) => entry.receipt.status !== 'cancelled' && !entry.source.signal.aborted);
|
|
41
64
|
return !!head && (head.request.kind !== 'submit' || head.request.input.delivery !== 'idle');
|
|
@@ -169,13 +192,19 @@ export class SessionInputCoordinator {
|
|
|
169
192
|
return entry;
|
|
170
193
|
}
|
|
171
194
|
drain(leaf, execution, limit = Number.POSITIVE_INFINITY) {
|
|
172
|
-
if (this.#draining)
|
|
195
|
+
if (this.#draining && (!this.executing || !execution?.restoring))
|
|
173
196
|
return this.executing ? Promise.resolve(false) : this.#draining;
|
|
174
|
-
|
|
197
|
+
const drain = async () => {
|
|
175
198
|
let changed = false;
|
|
176
199
|
let consumed = 0;
|
|
177
200
|
while (this.#pending.length && consumed++ < limit) {
|
|
178
201
|
const head = this.#pending[0];
|
|
202
|
+
// Accepted guidance is never permission to launch a new idle execution.
|
|
203
|
+
if (!execution && head.nativeSteer) {
|
|
204
|
+
this.#pending.shift();
|
|
205
|
+
this.#finish(head, 'cancelled');
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
179
208
|
// An idle prompt is a new Turn, not a mailbox delivery into the active Turn.
|
|
180
209
|
// Preserve the single FIFO: entries behind it cannot overtake it.
|
|
181
210
|
if (execution &&
|
|
@@ -208,7 +237,10 @@ export class SessionInputCoordinator {
|
|
|
208
237
|
changed ||= applied;
|
|
209
238
|
}
|
|
210
239
|
return changed;
|
|
211
|
-
}
|
|
240
|
+
};
|
|
241
|
+
if (this.#draining)
|
|
242
|
+
return drain();
|
|
243
|
+
this.#draining = drain().finally(() => {
|
|
212
244
|
this.#draining = undefined;
|
|
213
245
|
});
|
|
214
246
|
return this.#draining;
|
|
@@ -223,6 +255,14 @@ export class SessionInputCoordinator {
|
|
|
223
255
|
let effects = 0;
|
|
224
256
|
let committed;
|
|
225
257
|
try {
|
|
258
|
+
if (entry.nativeSteer) {
|
|
259
|
+
await entry.nativeSteer.ready;
|
|
260
|
+
call.signal.throwIfAborted();
|
|
261
|
+
if (!entry.nativeSteer.recordId) {
|
|
262
|
+
this.#finish(entry, 'handled', { changed: false });
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
226
266
|
const outcome = await this.#current.run(entry, () => entry.action(entry.request, call, async (request, effective) => {
|
|
227
267
|
validateCoreSessionRequest(request);
|
|
228
268
|
effective.signal.throwIfAborted();
|
|
@@ -254,6 +294,8 @@ export class SessionInputCoordinator {
|
|
|
254
294
|
committed = physicalCommits.get(entry) ?? committed;
|
|
255
295
|
changed ||= (committed?.modelChanged ?? committed?.changed) || !!entry.receipt.changed;
|
|
256
296
|
this.#finish(entry, call.signal.aborted ? 'cancelled' : 'failed', committed, error instanceof MarAgentError ? error.code : 'MAR_AGENT_SESSION_INPUT_FAILED');
|
|
297
|
+
if (entry.nativeSteer?.recordId)
|
|
298
|
+
throw error;
|
|
257
299
|
}
|
|
258
300
|
finally {
|
|
259
301
|
physicalCommits.delete(entry);
|
|
@@ -22,6 +22,8 @@ export function restoreCompactTail(value) {
|
|
|
22
22
|
input.contextKind === 'compact_control')
|
|
23
23
|
return undefined;
|
|
24
24
|
if ((input.role === 'assistant' || input.role === 'user') && typeof input.content === 'string') {
|
|
25
|
+
if (input.inputRef !== undefined)
|
|
26
|
+
return restoreModelContext(input);
|
|
25
27
|
const projected = restoreModelContext(input);
|
|
26
28
|
const metadata = parseModelInputMetadata(input.metadata);
|
|
27
29
|
const contextKind = ['environment', 'contextual', 'summary'].includes(String(input.contextKind))
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { anthropicThinkingBlock } from '../../model/anthropic-thinking.js';
|
|
2
|
+
import { appliedInputRecord } from '../../session/deferred-input.js';
|
|
3
|
+
import { inputMetadataFromRecord } from '../../session/jsonl-store.js';
|
|
2
4
|
import { contextGcRecordSourceHash, contextGcTargetKey, latestValidContextGcCheckpoint } from '../../session/context-gc-checkpoint.js';
|
|
3
5
|
import { TOOL_EXECUTION_LIMITS } from '../../tools/execution-limits.js';
|
|
4
6
|
import { replaceReadContextItems } from '../../tools/local/read-context.js';
|
|
@@ -19,7 +21,14 @@ export function buildContextProjection(records, workspace, checkpointCache) {
|
|
|
19
21
|
}
|
|
20
22
|
if (record.type !== 'model.context' || !isRecord(record.payload))
|
|
21
23
|
continue;
|
|
22
|
-
const
|
|
24
|
+
const applied = appliedInputRecord(record);
|
|
25
|
+
const message = applied
|
|
26
|
+
? {
|
|
27
|
+
role: 'user',
|
|
28
|
+
content: record.payload.content,
|
|
29
|
+
metadata: inputMetadataFromRecord(applied)
|
|
30
|
+
}
|
|
31
|
+
: restoreModelContext(record.payload, activeModelId);
|
|
23
32
|
if (message)
|
|
24
33
|
rawEntries.push({ record, message });
|
|
25
34
|
}
|
|
@@ -133,6 +142,14 @@ export function filterPairedContext(messages, messageSources) {
|
|
|
133
142
|
};
|
|
134
143
|
}
|
|
135
144
|
export function restoreModelContext(payload, providerModelId) {
|
|
145
|
+
if (payload.inputRef !== undefined) {
|
|
146
|
+
const input = appliedInputRecord({ type: 'model.context', payload });
|
|
147
|
+
return {
|
|
148
|
+
role: 'user',
|
|
149
|
+
content: payload.content,
|
|
150
|
+
metadata: inputMetadataFromRecord(input)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
136
153
|
const nativeItem = payload.role === 'provider' && payload.provider === 'OPENAI_RESPONSES' && isRecord(payload.item)
|
|
137
154
|
? payload.item
|
|
138
155
|
: undefined;
|
|
@@ -13,6 +13,7 @@ export declare function restoreMessages(store: JsonlSessionStore, sessionId: str
|
|
|
13
13
|
isError: boolean;
|
|
14
14
|
result?: unknown;
|
|
15
15
|
}>;
|
|
16
|
+
pendingInputs: Map<string, import("../../session/jsonl-store.js").SessionRecord>;
|
|
16
17
|
environmentState: EnvironmentContextState | undefined;
|
|
17
18
|
userMessages: CompactUserInput[];
|
|
18
19
|
messages: ModelMessage[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MarAgentError } from '../../error.js';
|
|
2
|
+
import { appliedInputRecord, pendingDeferredInputs } from '../../session/deferred-input.js';
|
|
2
3
|
import { compactSummaryFromCheckpoint, replacementHistoryFromCheckpoint, inputMetadataFromRecord } from '../../session/jsonl-store.js';
|
|
3
4
|
import { compactedHistoryMessages, continuationMessage } from '../compaction/history.js';
|
|
4
5
|
import { restoreCompactTail } from '../compaction/retained-context.js';
|
|
@@ -14,9 +15,12 @@ export async function restoreMessages(store, sessionId, records, options) {
|
|
|
14
15
|
let ignoredImages = 0;
|
|
15
16
|
let currentTurnUserIndex;
|
|
16
17
|
let environmentState;
|
|
18
|
+
const pendingInputs = pendingDeferredInputs(records);
|
|
17
19
|
for (const record of records) {
|
|
18
20
|
const messageStart = messages.length;
|
|
19
|
-
const
|
|
21
|
+
const applied = appliedInputRecord(record);
|
|
22
|
+
const inputRecord = applied ?? record;
|
|
23
|
+
const payload = inputRecord.payload;
|
|
20
24
|
if ((record.type === 'tool.completed' || record.type === 'tool.failed') &&
|
|
21
25
|
typeof payload.callId === 'string') {
|
|
22
26
|
const result = record.type === 'tool.failed' ? payload.message : payload.data;
|
|
@@ -26,10 +30,12 @@ export async function restoreMessages(store, sessionId, records, options) {
|
|
|
26
30
|
});
|
|
27
31
|
}
|
|
28
32
|
const compactSummary = compactSummaryFromCheckpoint(record);
|
|
29
|
-
if ((
|
|
33
|
+
if ((applied ||
|
|
34
|
+
((record.type === 'turn.user' || record.type === 'extension.input') &&
|
|
35
|
+
payload.deferred !== true)) &&
|
|
30
36
|
typeof payload.prompt === 'string') {
|
|
31
37
|
const content = typeof payload.modelText === 'string' ? payload.modelText : payload.prompt;
|
|
32
|
-
const metadata = inputMetadataFromRecord(
|
|
38
|
+
const metadata = inputMetadataFromRecord(inputRecord);
|
|
33
39
|
userMessages.push({ content, metadata });
|
|
34
40
|
let images = [];
|
|
35
41
|
if (options.includeImages)
|
|
@@ -101,6 +107,16 @@ export async function restoreMessages(store, sessionId, records, options) {
|
|
|
101
107
|
continue;
|
|
102
108
|
const message = restoreCompactTail(item);
|
|
103
109
|
if (message) {
|
|
110
|
+
if (options.includeImages && item.inputRef !== undefined) {
|
|
111
|
+
const input = appliedInputRecord({
|
|
112
|
+
...record,
|
|
113
|
+
type: 'model.context',
|
|
114
|
+
payload: item
|
|
115
|
+
});
|
|
116
|
+
const images = await store.readInputImages(sessionId, input.payload.images);
|
|
117
|
+
if (images.length)
|
|
118
|
+
message.images = images;
|
|
119
|
+
}
|
|
104
120
|
messages.push(message);
|
|
105
121
|
if (message.role === 'user' && message.contextKind === undefined)
|
|
106
122
|
userMessages.push({
|
|
@@ -136,6 +152,7 @@ export async function restoreMessages(store, sessionId, records, options) {
|
|
|
136
152
|
const completedToolCalls = new Set(messages.filter((message) => message.role === 'tool').map((message) => message.callId));
|
|
137
153
|
return {
|
|
138
154
|
toolOutcomes,
|
|
155
|
+
pendingInputs,
|
|
139
156
|
environmentState,
|
|
140
157
|
userMessages,
|
|
141
158
|
messages: messages.filter((message) => {
|
|
@@ -11,6 +11,7 @@ export function toolEventSummary(text) {
|
|
|
11
11
|
/** Keep streaming deltas live-only; exec output has one durable copy in model context records. */
|
|
12
12
|
export function eventForPersistence(event) {
|
|
13
13
|
if (event.type === 'message.delta' ||
|
|
14
|
+
event.type === 'input.received' ||
|
|
14
15
|
(event.type === 'context.compacting' && event.async !== true && event.stage === undefined) ||
|
|
15
16
|
event.type === 'model.performance' ||
|
|
16
17
|
event.type === 'message.performance')
|
|
@@ -14,6 +14,11 @@ export interface CompactSnapshotAnchor {
|
|
|
14
14
|
readonly id: string;
|
|
15
15
|
}
|
|
16
16
|
export type MarAgentEvent = MarAgentEventBase & ({
|
|
17
|
+
type: 'input.received';
|
|
18
|
+
entry: Extract<import('../session/catalog.js').MarAgentHistoryEntry, {
|
|
19
|
+
kind: 'user';
|
|
20
|
+
}>;
|
|
21
|
+
} | {
|
|
17
22
|
type: 'execution.started';
|
|
18
23
|
mode: ExecutionMode;
|
|
19
24
|
permissionMode?: import('../tools/permissions.js').PermissionMode;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CoreOperationContext } from '../extensions/operation.js';
|
|
2
|
+
import { type CorePromptInput, type CorePromptResult } from '../extensions/prompt-input.js';
|
|
3
|
+
import type { SessionInputCoordinator } from '../extensions/session-input.js';
|
|
4
|
+
import type { SessionRecord } from '../session/jsonl-store.js';
|
|
5
|
+
import type { SteerInput, SteerResult } from './types.js';
|
|
6
|
+
/** Admission reserves the existing FIFO before any asynchronous Hook or writer work. */
|
|
7
|
+
export declare function createNativeSteer(input: {
|
|
8
|
+
readonly executionId: string;
|
|
9
|
+
readonly inputs: SessionInputCoordinator;
|
|
10
|
+
readonly signal: AbortSignal;
|
|
11
|
+
isOpen(): boolean;
|
|
12
|
+
operation<T>(action: (call: CoreOperationContext) => T): T;
|
|
13
|
+
admit(content: CorePromptInput, call: CoreOperationContext, write: (content: CorePromptInput, call: CoreOperationContext) => Promise<CorePromptResult>): Promise<unknown>;
|
|
14
|
+
receive(content: CorePromptInput, clientMessageId: string, call: CoreOperationContext): Promise<SessionRecord>;
|
|
15
|
+
notify(entry: Extract<SteerResult, {
|
|
16
|
+
status: 'accepted';
|
|
17
|
+
}>['entry']): Promise<unknown>;
|
|
18
|
+
diagnose(): void;
|
|
19
|
+
}): (request: SteerInput) => Promise<SteerResult>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { MarAgentError } from '../error.js';
|
|
2
|
+
import { captureCorePromptInput } from '../extensions/prompt-input.js';
|
|
3
|
+
import { imageMetadata } from '../session/catalog.js';
|
|
4
|
+
/** Admission reserves the existing FIFO before any asynchronous Hook or writer work. */
|
|
5
|
+
export function createNativeSteer(input) {
|
|
6
|
+
const retries = new Map();
|
|
7
|
+
let admissionTail = Promise.resolve();
|
|
8
|
+
return async (request) => {
|
|
9
|
+
if (request.expectedExecutionId !== input.executionId)
|
|
10
|
+
return Promise.resolve({ status: 'notAccepted' });
|
|
11
|
+
const previous = retries.get(request.clientMessageId);
|
|
12
|
+
if (previous)
|
|
13
|
+
return previous;
|
|
14
|
+
if (!input.isOpen() || input.signal.aborted || input.inputs.hasIdleBarrier)
|
|
15
|
+
return Promise.resolve({ status: 'notAccepted' });
|
|
16
|
+
if (typeof request.clientMessageId !== 'string' ||
|
|
17
|
+
!request.clientMessageId.trim() ||
|
|
18
|
+
request.clientMessageId.length > 512)
|
|
19
|
+
return Promise.reject(new MarAgentError('MAR_AGENT_SESSION_INPUT_INVALID', 'A stable client message ID is required.'));
|
|
20
|
+
const content = captureCorePromptInput({
|
|
21
|
+
text: request.prompt,
|
|
22
|
+
...(request.modelText === undefined ? {} : { modelText: request.modelText }),
|
|
23
|
+
...(request.images === undefined ? {} : { images: request.images })
|
|
24
|
+
});
|
|
25
|
+
const result = Promise.withResolvers();
|
|
26
|
+
const ready = Promise.withResolvers();
|
|
27
|
+
const entry = input.operation((call) => input.inputs.accept({ kind: 'submit', input: content }, call, (request, effective, next) => next(request, effective), 'accepted'));
|
|
28
|
+
entry.nativeSteer = { ready: ready.promise };
|
|
29
|
+
const aborted = () => ready.resolve();
|
|
30
|
+
input.signal.addEventListener('abort', aborted, { once: true });
|
|
31
|
+
let committed = false;
|
|
32
|
+
const admission = admissionTail
|
|
33
|
+
.catch(() => undefined)
|
|
34
|
+
.then(() => input.operation((call) => input.admit(content, call, async (actual, effective) => {
|
|
35
|
+
if (committed)
|
|
36
|
+
return {};
|
|
37
|
+
effective.signal.throwIfAborted();
|
|
38
|
+
if (!input.isOpen()) {
|
|
39
|
+
result.resolve({ status: 'notAccepted' });
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
const record = await input.receive(actual, request.clientMessageId, effective);
|
|
43
|
+
committed = true;
|
|
44
|
+
entry.nativeSteer.recordId = record.recordId;
|
|
45
|
+
const payload = record.payload;
|
|
46
|
+
const images = imageMetadata(payload.images);
|
|
47
|
+
const historyEntry = {
|
|
48
|
+
kind: 'user',
|
|
49
|
+
recordId: record.recordId,
|
|
50
|
+
sequence: record.sequence,
|
|
51
|
+
timestamp: record.timestamp,
|
|
52
|
+
sessionId: record.sessionId,
|
|
53
|
+
executionId: record.executionId,
|
|
54
|
+
turnId: record.turnId,
|
|
55
|
+
text: payload.prompt,
|
|
56
|
+
clientMessageId: request.clientMessageId,
|
|
57
|
+
...(images.length ? { images } : {})
|
|
58
|
+
};
|
|
59
|
+
// Neither Hook post-processing nor a failed observer can revoke the writer's receipt.
|
|
60
|
+
void input.notify(historyEntry).catch(() => input.diagnose());
|
|
61
|
+
ready.resolve();
|
|
62
|
+
result.resolve({ status: 'accepted', entry: historyEntry });
|
|
63
|
+
return {};
|
|
64
|
+
})));
|
|
65
|
+
void admission
|
|
66
|
+
.then(() => {
|
|
67
|
+
if (!committed)
|
|
68
|
+
result.resolve({ status: 'handled' });
|
|
69
|
+
}, (error) => {
|
|
70
|
+
if (!committed)
|
|
71
|
+
result.reject(error);
|
|
72
|
+
else
|
|
73
|
+
input.diagnose();
|
|
74
|
+
})
|
|
75
|
+
.finally(() => {
|
|
76
|
+
ready.resolve();
|
|
77
|
+
input.signal.removeEventListener('abort', aborted);
|
|
78
|
+
});
|
|
79
|
+
admissionTail = result.promise;
|
|
80
|
+
retries.set(request.clientMessageId, result.promise);
|
|
81
|
+
return result.promise;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -16,6 +16,7 @@ import { createSessionToolScheduler } from '../tools/scheduler.js';
|
|
|
16
16
|
import { TodoStore } from '../tools/agent/todo.js';
|
|
17
17
|
import type { CreateMarAgentOptions, ExecutionResult, MarAgentSession } from './types.js';
|
|
18
18
|
export interface SessionRuntime {
|
|
19
|
+
steer?: (input: import('./types.js').SteerInput) => Promise<import('./types.js').SteerResult>;
|
|
19
20
|
todo?: TodoStore;
|
|
20
21
|
readonly scheduleTool: ReturnType<typeof createSessionToolScheduler>;
|
|
21
22
|
readonly sourceType: 'user' | 'subagent';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isProviderContextOverflow, recoverModelStream } from '../model/recovery.js';
|
|
2
2
|
import { estimatedRequestTokens } from '../runtime/context/request-budget.js';
|
|
3
3
|
import { restoreMessages } from '../runtime/context/restore.js';
|
|
4
|
+
import { createNativeSteer } from './native-steer.js';
|
|
4
5
|
import { lastConfigurationEffort, latestModelUsage, reasoningEffortBaseline, shouldUseNativeCompaction } from '../runtime/context/usage.js';
|
|
5
6
|
import { splitContextualPrompt } from '../runtime/context/contextual-blocks.js';
|
|
6
7
|
import { contextualInstructionInsertIndex, insertContextualWorldState, latestContextualWorldStateGeneration, parsePersistedContextualWorldState, persistContextualWorldState, resetContextualWorldState, updateContextualWorldState, updatePromptWorldState } from '../runtime/context/world-state.js';
|
|
@@ -274,6 +275,7 @@ export function createSessionOpener(services) {
|
|
|
274
275
|
for (const message of internal.initialMailbox ?? [])
|
|
275
276
|
acceptParent(message);
|
|
276
277
|
let acceptsMailbox = sessionSource.type === 'subagent' && mode !== 'compact';
|
|
278
|
+
let acceptsSteer = false;
|
|
277
279
|
let sequence = 0;
|
|
278
280
|
let emitTail = Promise.resolve();
|
|
279
281
|
let releaseSession;
|
|
@@ -411,6 +413,7 @@ export function createSessionOpener(services) {
|
|
|
411
413
|
includeImages: mode !== 'compact'
|
|
412
414
|
});
|
|
413
415
|
const messages = restored.messages;
|
|
416
|
+
const pendingInputs = restored.pendingInputs;
|
|
414
417
|
liveModelMessages = messages;
|
|
415
418
|
const restoredRecordIds = new Set(contextRecords.map((record) => record.recordId));
|
|
416
419
|
for (const pending of pendingAppends)
|
|
@@ -472,7 +475,7 @@ export function createSessionOpener(services) {
|
|
|
472
475
|
messages.push({ role: 'user', content: text, contextKind: 'contextual' });
|
|
473
476
|
});
|
|
474
477
|
};
|
|
475
|
-
const appendMailboxMessages = async () => inputs.drain(async (entry, request, call) => {
|
|
478
|
+
const appendMailboxMessages = async (restoring = false) => inputs.drain(async (entry, request, call) => {
|
|
476
479
|
call.signal.throwIfAborted();
|
|
477
480
|
if (request.kind === 'compact') {
|
|
478
481
|
if (request.input.async) {
|
|
@@ -486,6 +489,63 @@ export function createSessionOpener(services) {
|
|
|
486
489
|
const result = await appendExtensionMessage(entry, request, call);
|
|
487
490
|
return { ...result, executionId };
|
|
488
491
|
}
|
|
492
|
+
if (entry.nativeSteer?.recordId) {
|
|
493
|
+
const received = pendingInputs.get(entry.nativeSteer.recordId);
|
|
494
|
+
if (!received)
|
|
495
|
+
return { changed: false, modelChanged: false };
|
|
496
|
+
return withRecordProjection(async () => {
|
|
497
|
+
call.signal.throwIfAborted();
|
|
498
|
+
const payload = received.payload;
|
|
499
|
+
const images = await store.readInputImages(sessionId, payload.images);
|
|
500
|
+
if (images.length && !selectedModel.inputCapabilities.includes('IMAGE'))
|
|
501
|
+
throw new MarAgentError('MAR_AGENT_INPUT_CAPABILITY_UNSUPPORTED', 'Selected model does not support image input.');
|
|
502
|
+
const percent = smartContextTokens === undefined
|
|
503
|
+
? undefined
|
|
504
|
+
: (smartContextTokens * 100) / selectedModel.contextWindowTokens;
|
|
505
|
+
const eligible = sessionSource.type === 'user' &&
|
|
506
|
+
percent !== undefined &&
|
|
507
|
+
percent >= AGENT_EXECUTION_POLICY.smartCompactionPercent &&
|
|
508
|
+
percent < AGENT_EXECUTION_POLICY.automaticCompactionPercent;
|
|
509
|
+
const actual = {
|
|
510
|
+
...payload,
|
|
511
|
+
...(eligible ? { smartCompaction: true } : {})
|
|
512
|
+
};
|
|
513
|
+
const content = (actual.modelText ?? actual.prompt);
|
|
514
|
+
const applied = await store.append(sessionId, {
|
|
515
|
+
type: 'model.context',
|
|
516
|
+
payload: {
|
|
517
|
+
role: 'user',
|
|
518
|
+
content,
|
|
519
|
+
inputRef: received.recordId,
|
|
520
|
+
input: actual
|
|
521
|
+
},
|
|
522
|
+
executionId,
|
|
523
|
+
turnId
|
|
524
|
+
}, { signal: call.signal });
|
|
525
|
+
const outcome = {
|
|
526
|
+
changed: true,
|
|
527
|
+
modelChanged: true,
|
|
528
|
+
executionId,
|
|
529
|
+
recordId: received.recordId
|
|
530
|
+
};
|
|
531
|
+
recordSessionInputCommit(entry, outcome);
|
|
532
|
+
pendingInputs.delete(received.recordId);
|
|
533
|
+
const metadata = inputMetadataFromRecord({ ...received, payload: actual });
|
|
534
|
+
const message = {
|
|
535
|
+
role: 'user',
|
|
536
|
+
content,
|
|
537
|
+
metadata,
|
|
538
|
+
...(images.length ? { images } : {})
|
|
539
|
+
};
|
|
540
|
+
messages.push(message);
|
|
541
|
+
messageSources.set(message, applied);
|
|
542
|
+
compactionUserMessages.push({ content, metadata });
|
|
543
|
+
smartInput = { recordId: received.recordId, eligible, requested: false };
|
|
544
|
+
for (const text of (payload.modelContext ?? payload.context ?? []))
|
|
545
|
+
messages.push({ role: 'user', content: text, contextKind: 'contextual' });
|
|
546
|
+
return outcome;
|
|
547
|
+
});
|
|
548
|
+
}
|
|
489
549
|
const parent = entry.parentMessage;
|
|
490
550
|
let outcome;
|
|
491
551
|
const content = {
|
|
@@ -528,7 +588,12 @@ export function createSessionOpener(services) {
|
|
|
528
588
|
parent?.delivery?.applied();
|
|
529
589
|
});
|
|
530
590
|
return outcome;
|
|
531
|
-
}, {
|
|
591
|
+
}, {
|
|
592
|
+
executionId,
|
|
593
|
+
turnId,
|
|
594
|
+
signal: controller.signal,
|
|
595
|
+
...(restoring ? { restoring: true } : {})
|
|
596
|
+
}, inputs.boundarySize);
|
|
532
597
|
const recordEnvironment = async () => {
|
|
533
598
|
const message = environmentContextUpdate(executionEnvironment, recordedEnvironment);
|
|
534
599
|
if (!message)
|
|
@@ -698,6 +763,12 @@ export function createSessionOpener(services) {
|
|
|
698
763
|
entry: 'native-api',
|
|
699
764
|
signal: controller.signal
|
|
700
765
|
}, run);
|
|
766
|
+
if (mode !== 'compact') {
|
|
767
|
+
// Missing entries prepend in reverse order; warm entries retain their FIFO position.
|
|
768
|
+
for (const record of [...pendingInputs.values()].reverse()) {
|
|
769
|
+
operation((call) => inputs.restoreSteer(record.recordId, record.payload.prompt, call));
|
|
770
|
+
}
|
|
771
|
+
}
|
|
701
772
|
await operation((call) => coreExtensions.ready(call));
|
|
702
773
|
const captureTools = () => operation((call) => {
|
|
703
774
|
return coreExtensions.capture(call, false, sourceBindings.filter((binding) => !supplementalBindings.includes(binding) || binding.available?.(call) !== false));
|
|
@@ -1061,6 +1132,8 @@ export function createSessionOpener(services) {
|
|
|
1061
1132
|
: message), mode === 'compact' ? [] : contextualWorldState.entries, mode !== 'compact' && selectedModel.inputCapabilities.includes('IMAGE'), baseTools().some((tool) => tool.name === 'code_exec'), permissionPolicy.mode === 'auto')
|
|
1062
1133
|
};
|
|
1063
1134
|
if (mode !== 'compact') {
|
|
1135
|
+
if (pendingInputs.size)
|
|
1136
|
+
await appendMailboxMessages(true);
|
|
1064
1137
|
let entered = false;
|
|
1065
1138
|
const writeInput = async (content, call) => {
|
|
1066
1139
|
call.signal.throwIfAborted();
|
|
@@ -1113,6 +1186,45 @@ export function createSessionOpener(services) {
|
|
|
1113
1186
|
await emit({ type: 'execution.completed' });
|
|
1114
1187
|
return { executionId, status: 'completed', finalAnswer: '', inputHandled: true };
|
|
1115
1188
|
}
|
|
1189
|
+
acceptsSteer = true;
|
|
1190
|
+
sessionRuntime.steer = createNativeSteer({
|
|
1191
|
+
executionId,
|
|
1192
|
+
inputs,
|
|
1193
|
+
signal: controller.signal,
|
|
1194
|
+
isOpen: () => acceptsSteer,
|
|
1195
|
+
operation,
|
|
1196
|
+
admit: (content, call, write) => invokeCorePromptSubmission(coreExtensions.hooks, content, call, write),
|
|
1197
|
+
receive: (content, clientMessageId, call) => withRecordProjection(async () => {
|
|
1198
|
+
call.signal.throwIfAborted();
|
|
1199
|
+
if (content.images?.length && !selectedModel.inputCapabilities.includes('IMAGE'))
|
|
1200
|
+
throw new MarAgentError('MAR_AGENT_INPUT_CAPABILITY_UNSUPPORTED', 'Selected model does not support image input.');
|
|
1201
|
+
const images = content.images?.length
|
|
1202
|
+
? await store.persistInputImages(sessionId, content.images)
|
|
1203
|
+
: [];
|
|
1204
|
+
const record = await store.append(sessionId, {
|
|
1205
|
+
type: 'turn.user',
|
|
1206
|
+
payload: {
|
|
1207
|
+
deferred: true,
|
|
1208
|
+
prompt: content.text,
|
|
1209
|
+
...(content.modelText === undefined
|
|
1210
|
+
? {}
|
|
1211
|
+
: { modelText: content.modelText }),
|
|
1212
|
+
...(content.context === undefined ? {} : { context: content.context }),
|
|
1213
|
+
...(content.modelContext === undefined
|
|
1214
|
+
? {}
|
|
1215
|
+
: { modelContext: content.modelContext }),
|
|
1216
|
+
clientMessageId,
|
|
1217
|
+
...(images.length ? { images } : {})
|
|
1218
|
+
},
|
|
1219
|
+
executionId,
|
|
1220
|
+
turnId
|
|
1221
|
+
}, { signal: call.signal });
|
|
1222
|
+
pendingInputs.set(record.recordId, record);
|
|
1223
|
+
return record;
|
|
1224
|
+
}),
|
|
1225
|
+
notify: (entry) => emit({ type: 'input.received', entry }),
|
|
1226
|
+
diagnose: () => coreExtensions.diagnose({ code: 'MAR_AGENT_STEER_POST_COMMIT_FAILED', sessionId })
|
|
1227
|
+
});
|
|
1116
1228
|
}
|
|
1117
1229
|
if (mode !== 'compact' &&
|
|
1118
1230
|
useEffortUpdates &&
|
|
@@ -1360,7 +1472,22 @@ export function createSessionOpener(services) {
|
|
|
1360
1472
|
const record = await store.compact(sessionId, {
|
|
1361
1473
|
summary: actual,
|
|
1362
1474
|
replacementHistory,
|
|
1363
|
-
retainedModelContext
|
|
1475
|
+
retainedModelContext: retainedModelContext.map((message) => {
|
|
1476
|
+
const source = messageSources.get(message);
|
|
1477
|
+
const saved = source?.type === 'model.context' &&
|
|
1478
|
+
source.payload.inputRef
|
|
1479
|
+
? source.payload
|
|
1480
|
+
: source?.type === 'compact.completed'
|
|
1481
|
+
? (source.payload.retainedModelContext ?? []).find((item) => item.inputRef === message.metadata?.recordId)
|
|
1482
|
+
: undefined;
|
|
1483
|
+
return saved
|
|
1484
|
+
? {
|
|
1485
|
+
...saved,
|
|
1486
|
+
...(message.metadata ? { metadata: message.metadata } : {})
|
|
1487
|
+
}
|
|
1488
|
+
: message;
|
|
1489
|
+
}),
|
|
1490
|
+
pendingInputs: [...pendingInputs.values()],
|
|
1364
1491
|
snapshotMessageCount: prefix.length,
|
|
1365
1492
|
...(anchorRecordId ? { snapshotRecordId: anchorRecordId } : {}),
|
|
1366
1493
|
automatic: mode !== 'compact',
|
|
@@ -1714,6 +1841,7 @@ export function createSessionOpener(services) {
|
|
|
1714
1841
|
append: appendMailboxMessages,
|
|
1715
1842
|
close: () => {
|
|
1716
1843
|
acceptsMailbox = false;
|
|
1844
|
+
acceptsSteer = false;
|
|
1717
1845
|
}
|
|
1718
1846
|
}
|
|
1719
1847
|
});
|
|
@@ -1750,6 +1878,9 @@ export function createSessionOpener(services) {
|
|
|
1750
1878
|
catch (error) {
|
|
1751
1879
|
finalAnswer = executionLoop?.finalAnswer ?? finalAnswer;
|
|
1752
1880
|
acceptsMailbox = false;
|
|
1881
|
+
acceptsSteer = false;
|
|
1882
|
+
delete sessionRuntime.steer;
|
|
1883
|
+
await sessionRuntime.recordProjection?.catch(() => undefined);
|
|
1753
1884
|
await compactCoordinator.close(error);
|
|
1754
1885
|
compactRequests.close(new MarAgentError('MAR_AGENT_CANCELLED', 'Execution ended.'));
|
|
1755
1886
|
if (sessionRuntime.compactRequests === compactRequests)
|
|
@@ -2078,6 +2209,7 @@ export function createSessionOpener(services) {
|
|
|
2078
2209
|
id: sessionId,
|
|
2079
2210
|
run: (input) => execution('run', input),
|
|
2080
2211
|
plan: (input) => execution('plan', input),
|
|
2212
|
+
steer: (input) => sessionRuntime.steer?.(input) ?? Promise.resolve({ status: 'notAccepted' }),
|
|
2081
2213
|
compact: (input = {}) => sessionRuntime.compactRequests?.request(input) ??
|
|
2082
2214
|
execution('compact', {
|
|
2083
2215
|
prompt: input.prompt ?? DEFAULT_COMPACTION_FOCUS,
|
|
@@ -24,11 +24,29 @@ export interface ExecutionHandle extends AsyncIterable<MarAgentEvent> {
|
|
|
24
24
|
export type CompactInput = Partial<RunInput> & {
|
|
25
25
|
readonly async?: boolean;
|
|
26
26
|
};
|
|
27
|
+
export interface SteerInput {
|
|
28
|
+
readonly expectedExecutionId: string;
|
|
29
|
+
readonly prompt: string;
|
|
30
|
+
readonly modelText?: string;
|
|
31
|
+
readonly clientMessageId: string;
|
|
32
|
+
readonly images?: RunInput['images'];
|
|
33
|
+
}
|
|
34
|
+
export type SteerResult = {
|
|
35
|
+
readonly status: 'accepted';
|
|
36
|
+
readonly entry: Extract<import('../session/catalog.js').MarAgentHistoryEntry, {
|
|
37
|
+
kind: 'user';
|
|
38
|
+
}>;
|
|
39
|
+
} | {
|
|
40
|
+
readonly status: 'handled';
|
|
41
|
+
} | {
|
|
42
|
+
readonly status: 'notAccepted';
|
|
43
|
+
};
|
|
27
44
|
/** A persisted Session permits one active main execution at a time. */
|
|
28
45
|
export interface MarAgentSession {
|
|
29
46
|
readonly id: string;
|
|
30
47
|
run(input: RunInput): ExecutionHandle;
|
|
31
48
|
plan(input: RunInput): ExecutionHandle;
|
|
49
|
+
steer(input: SteerInput): Promise<SteerResult>;
|
|
32
50
|
compact(input?: CompactInput): ExecutionHandle;
|
|
33
51
|
cancel(executionId: string): Promise<void>;
|
|
34
52
|
}
|
|
@@ -23,6 +23,7 @@ export interface MarAgentHistoryImageMetadata extends MarAgentImageMetadata {
|
|
|
23
23
|
}
|
|
24
24
|
export type MarAgentHistoryEntry = {
|
|
25
25
|
kind: 'user';
|
|
26
|
+
recordId?: string;
|
|
26
27
|
sequence: number;
|
|
27
28
|
timestamp: string;
|
|
28
29
|
sessionId: string;
|
|
@@ -86,3 +87,4 @@ export interface MarAgentSessionCatalog {
|
|
|
86
87
|
export declare function openMarAgentSessionCatalog(input: {
|
|
87
88
|
homeDirectory: string;
|
|
88
89
|
}): Promise<MarAgentSessionCatalog>;
|
|
90
|
+
export declare function imageMetadata(value: unknown): MarAgentHistoryImageMetadata[];
|
|
@@ -25,6 +25,7 @@ export async function openMarAgentSessionCatalog(input) {
|
|
|
25
25
|
return [
|
|
26
26
|
{
|
|
27
27
|
kind: 'user',
|
|
28
|
+
...(record.payload.deferred === true ? { recordId: record.recordId } : {}),
|
|
28
29
|
sequence: record.sequence,
|
|
29
30
|
timestamp: record.timestamp,
|
|
30
31
|
sessionId: record.sessionId,
|
|
@@ -118,7 +119,7 @@ function parseCursor(cursor) {
|
|
|
118
119
|
function isRecord(value) {
|
|
119
120
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
120
121
|
}
|
|
121
|
-
function imageMetadata(value) {
|
|
122
|
+
export function imageMetadata(value) {
|
|
122
123
|
if (!Array.isArray(value))
|
|
123
124
|
return [];
|
|
124
125
|
return value.flatMap((image) => isRecord(image) &&
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SessionRecord } from './jsonl-store.js';
|
|
2
|
+
/** The application is self-contained; the receive record may precede the Compact window. */
|
|
3
|
+
export declare function appliedInputRecord(record: SessionRecord): SessionRecord | undefined;
|
|
4
|
+
/** Only the last checkpoint and its tail are needed, never a historical consume index. */
|
|
5
|
+
export declare function pendingDeferredInputs(records: readonly SessionRecord[]): Map<string, SessionRecord>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { MarAgentError } from '../error.js';
|
|
2
|
+
/** The application is self-contained; the receive record may precede the Compact window. */
|
|
3
|
+
export function appliedInputRecord(record) {
|
|
4
|
+
if (record.type !== 'model.context' || !record.payload || typeof record.payload !== 'object')
|
|
5
|
+
return undefined;
|
|
6
|
+
const payload = record.payload;
|
|
7
|
+
if (payload.inputRef === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
const input = payload.input;
|
|
10
|
+
if (payload.role !== 'user' ||
|
|
11
|
+
typeof payload.content !== 'string' ||
|
|
12
|
+
typeof payload.inputRef !== 'string' ||
|
|
13
|
+
!payload.inputRef ||
|
|
14
|
+
!input ||
|
|
15
|
+
typeof input !== 'object' ||
|
|
16
|
+
Array.isArray(input) ||
|
|
17
|
+
typeof input.prompt !== 'string' ||
|
|
18
|
+
input.deferred !== true ||
|
|
19
|
+
payload.content !==
|
|
20
|
+
(input.modelText ?? input.prompt))
|
|
21
|
+
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Deferred input application is invalid.');
|
|
22
|
+
return { ...record, type: 'turn.user', recordId: payload.inputRef, payload: input };
|
|
23
|
+
}
|
|
24
|
+
/** Only the last checkpoint and its tail are needed, never a historical consume index. */
|
|
25
|
+
export function pendingDeferredInputs(records) {
|
|
26
|
+
const pending = new Map();
|
|
27
|
+
const applied = new Set();
|
|
28
|
+
for (const record of records) {
|
|
29
|
+
const payload = (record.payload ?? {});
|
|
30
|
+
if (record.type === 'compact.completed' && typeof payload.summary === 'string') {
|
|
31
|
+
pending.clear();
|
|
32
|
+
applied.clear();
|
|
33
|
+
if (payload.pendingInputs !== undefined) {
|
|
34
|
+
if (!Array.isArray(payload.pendingInputs))
|
|
35
|
+
corrupt();
|
|
36
|
+
for (const item of payload.pendingInputs) {
|
|
37
|
+
if (!item ||
|
|
38
|
+
item.type !== 'turn.user' ||
|
|
39
|
+
item.sessionId !== record.sessionId ||
|
|
40
|
+
typeof item.recordId !== 'string' ||
|
|
41
|
+
!Number.isSafeInteger(item.sequence) ||
|
|
42
|
+
item.sequence < 1 ||
|
|
43
|
+
item.sequence >= record.sequence ||
|
|
44
|
+
!item.payload ||
|
|
45
|
+
item.payload.deferred !== true ||
|
|
46
|
+
typeof item.payload.prompt !== 'string' ||
|
|
47
|
+
pending.has(item.recordId))
|
|
48
|
+
corrupt();
|
|
49
|
+
pending.set(item.recordId, item);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (record.type === 'turn.user' && payload.deferred === true) {
|
|
54
|
+
if (typeof payload.prompt !== 'string' ||
|
|
55
|
+
pending.has(record.recordId) ||
|
|
56
|
+
applied.has(record.recordId))
|
|
57
|
+
corrupt();
|
|
58
|
+
pending.set(record.recordId, record);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const input = appliedInputRecord(record);
|
|
62
|
+
if (!input)
|
|
63
|
+
continue;
|
|
64
|
+
if (applied.has(input.recordId))
|
|
65
|
+
corrupt();
|
|
66
|
+
applied.add(input.recordId);
|
|
67
|
+
pending.delete(input.recordId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return new Map([...pending].sort(([, a], [, b]) => a.sequence - b.sequence));
|
|
71
|
+
}
|
|
72
|
+
function corrupt() {
|
|
73
|
+
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Deferred input state is invalid.');
|
|
74
|
+
}
|
|
@@ -193,6 +193,7 @@ export declare class JsonlSessionStore {
|
|
|
193
193
|
summary: string;
|
|
194
194
|
replacementHistory?: readonly CompactHistoryMessage[];
|
|
195
195
|
retainedModelContext?: readonly ModelMessage[];
|
|
196
|
+
pendingInputs?: readonly SessionRecord[];
|
|
196
197
|
snapshotRecordId?: string;
|
|
197
198
|
snapshotMessageCount?: number;
|
|
198
199
|
environment?: EnvironmentContextState;
|
|
@@ -599,16 +599,18 @@ export class JsonlSessionStore {
|
|
|
599
599
|
}
|
|
600
600
|
];
|
|
601
601
|
const retained = input.boundaryTurnId === null ? [] : records.slice(1, boundaryIndex + 1);
|
|
602
|
+
const forkIdentities = new Map();
|
|
602
603
|
for (const record of retained) {
|
|
603
604
|
if (record.type === 'context.gc.completed' || record.type === 'responses.continuation')
|
|
604
605
|
continue;
|
|
606
|
+
const identity = { recordId: randomUUID(), sequence: copied.length + 1 };
|
|
607
|
+
forkIdentities.set(record.recordId, identity);
|
|
605
608
|
copied.push({
|
|
606
609
|
schemaVersion: 1,
|
|
607
|
-
|
|
608
|
-
sequence: copied.length + 1,
|
|
610
|
+
...identity,
|
|
609
611
|
sessionId: target.id,
|
|
610
612
|
type: record.type,
|
|
611
|
-
payload: forkedPayload(record.type, record.payload, sourceSessionId, target.id, input.title),
|
|
613
|
+
payload: forkedPayload(record.type, record.payload, sourceSessionId, target.id, input.title, forkIdentities),
|
|
612
614
|
...(record.executionId === undefined ? {} : { executionId: record.executionId }),
|
|
613
615
|
...(record.turnId === undefined ? {} : { turnId: record.turnId }),
|
|
614
616
|
timestamp: record.timestamp
|
|
@@ -1306,6 +1308,7 @@ export class JsonlSessionStore {
|
|
|
1306
1308
|
type: 'compact.completed',
|
|
1307
1309
|
payload: {
|
|
1308
1310
|
summary: input.summary,
|
|
1311
|
+
...(input.pendingInputs?.length ? { pendingInputs: input.pendingInputs } : {}),
|
|
1309
1312
|
...(input.snapshotRecordId === undefined
|
|
1310
1313
|
? {}
|
|
1311
1314
|
: { snapshotRecordId: input.snapshotRecordId }),
|
|
@@ -2380,12 +2383,39 @@ function sessionTreeIds(sessions, sessionId) {
|
|
|
2380
2383
|
}
|
|
2381
2384
|
return ids;
|
|
2382
2385
|
}
|
|
2383
|
-
function forkedPayload(type, value, sourceSessionId, targetSessionId, title) {
|
|
2386
|
+
function forkedPayload(type, value, sourceSessionId, targetSessionId, title, identities) {
|
|
2384
2387
|
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
2385
2388
|
return value;
|
|
2386
2389
|
const payload = { ...value };
|
|
2387
2390
|
if (payload.sessionId === sourceSessionId)
|
|
2388
2391
|
payload.sessionId = targetSessionId;
|
|
2392
|
+
const application = (input) => {
|
|
2393
|
+
if (typeof input.inputRef !== 'string')
|
|
2394
|
+
return input;
|
|
2395
|
+
const identity = identities.get(input.inputRef);
|
|
2396
|
+
if (!identity)
|
|
2397
|
+
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Fork input reference is missing.');
|
|
2398
|
+
return {
|
|
2399
|
+
...input,
|
|
2400
|
+
inputRef: identity.recordId,
|
|
2401
|
+
...(isObject(input.metadata)
|
|
2402
|
+
? { metadata: { ...input.metadata, recordId: identity.recordId } }
|
|
2403
|
+
: {})
|
|
2404
|
+
};
|
|
2405
|
+
};
|
|
2406
|
+
if (type === 'model.context')
|
|
2407
|
+
return application(payload);
|
|
2408
|
+
if (type === 'compact.completed') {
|
|
2409
|
+
if (Array.isArray(payload.retainedModelContext))
|
|
2410
|
+
payload.retainedModelContext = payload.retainedModelContext.map((item) => isObject(item) ? application(item) : item);
|
|
2411
|
+
if (Array.isArray(payload.pendingInputs))
|
|
2412
|
+
payload.pendingInputs = payload.pendingInputs.map((record) => {
|
|
2413
|
+
const identity = identities.get(record.recordId);
|
|
2414
|
+
if (!identity)
|
|
2415
|
+
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Fork pending input reference is missing.');
|
|
2416
|
+
return { ...record, ...identity, sessionId: targetSessionId };
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2389
2419
|
if (type === 'session.title.updated') {
|
|
2390
2420
|
payload.title = title;
|
|
2391
2421
|
payload.automatic = false;
|
|
@@ -123,7 +123,7 @@ export declare class SubagentSessionController implements SubagentController {
|
|
|
123
123
|
cancel(agentId: string): Promise<SubagentTaskSnapshot>;
|
|
124
124
|
listSessionResources(): {
|
|
125
125
|
agentId: string;
|
|
126
|
-
status: "completed" | "failed" | "cancelled" | "running" | "
|
|
126
|
+
status: "completed" | "failed" | "cancelled" | "running" | "interrupted" | "queued";
|
|
127
127
|
description: string;
|
|
128
128
|
}[];
|
|
129
129
|
dispose(): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export type { CoreModelsApi, CoreModelDescription, CoreModelTextBlock, CoreGener
|
|
|
38
38
|
export type { CoreDataApi, CoreJsonValue, CoreCheckpoint, CoreCheckpointApi, CoreStateApi, CoreStateChange, CoreStoreApi, CoreStoreCommit } from './agent-core/extensions/data.js';
|
|
39
39
|
export { generateSessionTitle, type GenerateSessionTitleInput } from './shared/auto-title.js';
|
|
40
40
|
export type { CoreProcessesApi, CoreProcessesBackend, CoreProcessInput, CoreProcessRunInput, CoreProcessHandle, CoreProcessExit, CoreProcessResult, CoreProcessCompletion } from './agent-core/extensions/processes.js';
|
|
41
|
-
export type { CreateMarAgentOptions, CreateSessionInput, ExecutionHandle, CompactInput, MarAgent, MarAgentSession, SessionTitleUpdateInput } from './agent-core/sdk/types.js';
|
|
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
44
|
export type { ApplyPatchFileResult, ApplyPatchResult, ApplyPatchPartialResult, ClientToolDefinition, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolCaller, ToolExecutionContext, ToolExecutionResult } from './agent-core/tools/contracts.js';
|