@librechat/agents 3.7.0 → 3.7.1
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/cjs/agents/AgentContext.cjs +11 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +7 -2
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +56 -9
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/main.cjs +11 -1
- package/dist/cjs/messages/content.cjs +8 -5
- package/dist/cjs/messages/content.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +15 -5
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +2 -1
- package/dist/cjs/messages/projectionInvariant.cjs +74 -0
- package/dist/cjs/messages/projectionInvariant.cjs.map +1 -0
- package/dist/cjs/messages/provenance.cjs +30 -8
- package/dist/cjs/messages/provenance.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +202 -7
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/messages/toolResultTypes.cjs +1 -0
- package/dist/cjs/session/AgentSession.cjs +4 -23
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/session/deriveMessages.cjs +25 -0
- package/dist/cjs/session/deriveMessages.cjs.map +1 -0
- package/dist/cjs/session/index.cjs +1 -0
- package/dist/cjs/summarization/node.cjs +13 -6
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +65 -8
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +11 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +8 -3
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +56 -9
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/main.mjs +6 -4
- package/dist/esm/messages/content.mjs +8 -6
- package/dist/esm/messages/content.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +16 -6
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +2 -1
- package/dist/esm/messages/projectionInvariant.mjs +72 -0
- package/dist/esm/messages/projectionInvariant.mjs.map +1 -0
- package/dist/esm/messages/provenance.mjs +30 -9
- package/dist/esm/messages/provenance.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +201 -8
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/messages/toolResultTypes.mjs +1 -1
- package/dist/esm/session/AgentSession.mjs +4 -23
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/session/deriveMessages.mjs +25 -0
- package/dist/esm/session/deriveMessages.mjs.map +1 -0
- package/dist/esm/session/index.mjs +1 -0
- package/dist/esm/summarization/node.mjs +14 -7
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +65 -8
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +6 -1
- package/dist/types/messages/content.d.ts +4 -1
- package/dist/types/messages/format.d.ts +6 -0
- package/dist/types/messages/index.d.ts +1 -0
- package/dist/types/messages/projectionInvariant.d.ts +25 -0
- package/dist/types/messages/provenance.d.ts +10 -0
- package/dist/types/messages/recency.d.ts +30 -18
- package/dist/types/session/deriveMessages.d.ts +11 -0
- package/dist/types/session/index.d.ts +2 -0
- package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +13 -1
- package/dist/types/types/graph.d.ts +1 -1
- package/dist/types/types/subagentTasks.d.ts +22 -0
- package/dist/types/types/summarize.d.ts +15 -13
- package/package.json +3 -1
- package/src/agents/AgentContext.ts +19 -5
- package/src/graphs/Graph.ts +9 -1
- package/src/llm/invoke.ts +102 -23
- package/src/messages/content.ts +20 -10
- package/src/messages/format.ts +29 -5
- package/src/messages/index.ts +1 -0
- package/src/messages/projectionInvariant.ts +134 -0
- package/src/messages/provenance.ts +60 -18
- package/src/messages/recency.ts +429 -27
- package/src/session/AgentSession.ts +4 -30
- package/src/session/deriveMessages.ts +37 -0
- package/src/session/index.ts +2 -0
- package/src/summarization/node.ts +37 -15
- package/src/tools/subagent/InMemorySubagentTaskStore.ts +139 -6
- package/src/types/graph.ts +1 -0
- package/src/types/subagentTasks.ts +28 -0
- package/src/types/summarize.ts +15 -13
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from '@/messages/cache';
|
|
30
30
|
import {
|
|
31
31
|
DEFAULT_RETAIN_RECENT_TURNS,
|
|
32
|
+
resolveIntraTurnRetainTokens,
|
|
32
33
|
splitAtRecencyBoundary,
|
|
33
34
|
} from '@/messages/recency';
|
|
34
35
|
import {
|
|
@@ -1091,14 +1092,22 @@ export function createSummarizeNode({
|
|
|
1091
1092
|
const runnableConfig = config ?? graph.config;
|
|
1092
1093
|
|
|
1093
1094
|
const retainRecent = agentContext.summarizationConfig?.retainRecent;
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1095
|
+
const recencyTokenCounter =
|
|
1096
|
+
agentContext.contextPressureTokenCounts?.count ??
|
|
1097
|
+
agentContext.tokenCounter;
|
|
1098
|
+
const {
|
|
1099
|
+
head: messagesToRefine,
|
|
1100
|
+
tailStartIndex,
|
|
1101
|
+
usedIntraTurnFallback,
|
|
1102
|
+
} = splitAtRecencyBoundary(restoredMessages, {
|
|
1103
|
+
turns: retainRecent?.turns ?? DEFAULT_RETAIN_RECENT_TURNS,
|
|
1104
|
+
tokens: retainRecent?.tokens,
|
|
1105
|
+
tokenCounter: recencyTokenCounter,
|
|
1106
|
+
intraTurnTokens: resolveIntraTurnRetainTokens({
|
|
1098
1107
|
tokens: retainRecent?.tokens,
|
|
1099
|
-
|
|
1100
|
-
}
|
|
1101
|
-
);
|
|
1108
|
+
maxContextTokens: agentContext.maxContextTokens,
|
|
1109
|
+
}),
|
|
1110
|
+
});
|
|
1102
1111
|
/**
|
|
1103
1112
|
* Use the *masked* messages for the retained tail so that any
|
|
1104
1113
|
* truncation prune applied to oversized ToolMessage content stays
|
|
@@ -1305,10 +1314,17 @@ export function createSummarizeNode({
|
|
|
1305
1314
|
* supposed to preserve. Leave state untouched and let the provider error
|
|
1306
1315
|
* surface instead.
|
|
1307
1316
|
*/
|
|
1308
|
-
if (
|
|
1317
|
+
if (
|
|
1318
|
+
usedMetadataStub === true &&
|
|
1319
|
+
(request.reason === 'overflow' || usedIntraTurnFallback)
|
|
1320
|
+
) {
|
|
1321
|
+
const preservationReason =
|
|
1322
|
+
request.reason === 'overflow'
|
|
1323
|
+
? 'overflow recovery'
|
|
1324
|
+
: 'intra-turn compaction';
|
|
1309
1325
|
log(
|
|
1310
1326
|
'warn',
|
|
1311
|
-
|
|
1327
|
+
`Summarization failed during ${preservationReason}; keeping history rather than replacing it with a metadata stub`
|
|
1312
1328
|
);
|
|
1313
1329
|
agentContext.markSummarizationTriggered(state.messages.length);
|
|
1314
1330
|
/**
|
|
@@ -1330,8 +1346,7 @@ export function createSummarizeNode({
|
|
|
1330
1346
|
{
|
|
1331
1347
|
id: stepId,
|
|
1332
1348
|
agentId: request.agentId,
|
|
1333
|
-
error:
|
|
1334
|
-
'Summarization failed during overflow recovery; conversation history was preserved',
|
|
1349
|
+
error: `Summarization failed during ${preservationReason}; conversation history was preserved`,
|
|
1335
1350
|
} satisfies t.SummarizeCompleteEvent,
|
|
1336
1351
|
runnableConfig
|
|
1337
1352
|
);
|
|
@@ -1364,7 +1379,13 @@ export function createSummarizeNode({
|
|
|
1364
1379
|
agentContext.tokenCounter
|
|
1365
1380
|
);
|
|
1366
1381
|
|
|
1367
|
-
|
|
1382
|
+
if (usedIntraTurnFallback) {
|
|
1383
|
+
agentContext.setSummary(summaryText, tokenCount, {
|
|
1384
|
+
precedesMessages: true,
|
|
1385
|
+
});
|
|
1386
|
+
} else {
|
|
1387
|
+
agentContext.setSummary(summaryText, tokenCount);
|
|
1388
|
+
}
|
|
1368
1389
|
|
|
1369
1390
|
log('info', 'Summary persisted');
|
|
1370
1391
|
log('debug', 'Summary details', {
|
|
@@ -1596,9 +1617,10 @@ function traceConfig(
|
|
|
1596
1617
|
|
|
1597
1618
|
/**
|
|
1598
1619
|
* Cache-friendly compaction: sends raw conversation messages with the
|
|
1599
|
-
* summarization instruction appended as the final HumanMessage.
|
|
1600
|
-
*
|
|
1601
|
-
*
|
|
1620
|
+
* summarization instruction appended as the final HumanMessage. Bound tool
|
|
1621
|
+
* definitions can reuse their cache prefix. Exact replay of the main request's
|
|
1622
|
+
* system + tools + messages prefix requires a routed-request projection that
|
|
1623
|
+
* this summarization path does not currently own.
|
|
1602
1624
|
*/
|
|
1603
1625
|
async function summarizeWithCacheHit({
|
|
1604
1626
|
model,
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
SubagentTaskBoundary,
|
|
5
5
|
SubagentTaskClaim,
|
|
6
6
|
SubagentTaskControlCommand,
|
|
7
|
+
SubagentTaskControlReceipt,
|
|
7
8
|
SubagentTaskControlResult,
|
|
8
9
|
SubagentTaskProgress,
|
|
9
10
|
SubagentTaskRuntime,
|
|
@@ -20,6 +21,7 @@ const DEFAULT_MAX_ERROR_CHARS = 4 * 1024;
|
|
|
20
21
|
const DEFAULT_MAX_MESSAGE_CHARS = 64 * 1024;
|
|
21
22
|
const DEFAULT_TASK_TIMEOUT_MS = 30 * 60 * 1000;
|
|
22
23
|
const DEFAULT_MAX_CONTROLS = 32;
|
|
24
|
+
const DEFAULT_MAX_CONTROL_RECEIPTS = 64;
|
|
23
25
|
const DEFAULT_MAX_RESULT_CHARS = 100_000;
|
|
24
26
|
const DEFAULT_MAX_RUNNING_PER_SCOPE = 10;
|
|
25
27
|
const DEFAULT_MAX_RUNNING_TOTAL = 100;
|
|
@@ -30,6 +32,7 @@ export interface InMemorySubagentTaskStoreOptions {
|
|
|
30
32
|
completedTtlMs?: number;
|
|
31
33
|
maxControlMessageChars?: number;
|
|
32
34
|
maxControlsPerTask?: number;
|
|
35
|
+
maxControlReceiptsPerTask?: number;
|
|
33
36
|
maxErrorChars?: number;
|
|
34
37
|
maxResultChars?: number;
|
|
35
38
|
maxRunningPerScope?: number;
|
|
@@ -57,6 +60,7 @@ type StoredTask = {
|
|
|
57
60
|
updatedAt: number;
|
|
58
61
|
controller: AbortController;
|
|
59
62
|
controls: PendingControl[];
|
|
63
|
+
controlReceipts: Map<string, SubagentTaskControlReceipt>;
|
|
60
64
|
progressEvents: number;
|
|
61
65
|
resultClaimed: boolean;
|
|
62
66
|
acceptingControls: boolean;
|
|
@@ -107,6 +111,13 @@ function resolveOptions(
|
|
|
107
111
|
options.maxControlsPerTask,
|
|
108
112
|
DEFAULT_MAX_CONTROLS
|
|
109
113
|
),
|
|
114
|
+
maxControlReceiptsPerTask: Math.max(
|
|
115
|
+
resolvePositiveInteger(
|
|
116
|
+
options.maxControlReceiptsPerTask,
|
|
117
|
+
DEFAULT_MAX_CONTROL_RECEIPTS
|
|
118
|
+
),
|
|
119
|
+
resolvePositiveInteger(options.maxControlsPerTask, DEFAULT_MAX_CONTROLS)
|
|
120
|
+
),
|
|
110
121
|
maxErrorChars: resolvePositiveInteger(
|
|
111
122
|
options.maxErrorChars,
|
|
112
123
|
DEFAULT_MAX_ERROR_CHARS
|
|
@@ -177,11 +188,20 @@ function snapshot(task: StoredTask): SubagentTaskSnapshot {
|
|
|
177
188
|
task.status === 'completed' && task.result != null && !task.resultClaimed,
|
|
178
189
|
resultClaimed: task.resultClaimed,
|
|
179
190
|
pendingControls: task.controls.length,
|
|
191
|
+
controlReceipts: [...task.controlReceipts.values()].map((receipt) => ({
|
|
192
|
+
...receipt,
|
|
193
|
+
})),
|
|
180
194
|
...(task.progress == null ? {} : { progress: { ...task.progress } }),
|
|
181
195
|
...(task.error == null ? {} : { error: task.error }),
|
|
182
196
|
};
|
|
183
197
|
}
|
|
184
198
|
|
|
199
|
+
function cloneControlReceipt(
|
|
200
|
+
receipt: SubagentTaskControlReceipt
|
|
201
|
+
): SubagentTaskControlReceipt {
|
|
202
|
+
return { ...receipt };
|
|
203
|
+
}
|
|
204
|
+
|
|
185
205
|
function abortReason(signal: AbortSignal): Error {
|
|
186
206
|
return signal.reason instanceof Error
|
|
187
207
|
? signal.reason
|
|
@@ -206,6 +226,34 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
206
226
|
this.options = resolveOptions(options);
|
|
207
227
|
}
|
|
208
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Payload-free transition seam for hosts that durably project authoritative
|
|
231
|
+
* receipts. Implementations must return synchronously, must not reproduce
|
|
232
|
+
* task-store transition rules, and cannot veto task-store state transitions:
|
|
233
|
+
* hook failures are deliberately isolated by the caller.
|
|
234
|
+
*/
|
|
235
|
+
protected onControlReceipt(
|
|
236
|
+
_scopeId: string,
|
|
237
|
+
_taskId: string,
|
|
238
|
+
_receipt: SubagentTaskControlReceipt
|
|
239
|
+
): void {}
|
|
240
|
+
|
|
241
|
+
private emitControlReceipt(
|
|
242
|
+
task: StoredTask,
|
|
243
|
+
receipt: SubagentTaskControlReceipt
|
|
244
|
+
): void {
|
|
245
|
+
try {
|
|
246
|
+
this.onControlReceipt(
|
|
247
|
+
task.scopeId,
|
|
248
|
+
task.id,
|
|
249
|
+
cloneControlReceipt(receipt)
|
|
250
|
+
);
|
|
251
|
+
} catch {
|
|
252
|
+
// A host projection is observability only. Task admission, draining, and
|
|
253
|
+
// settlement must remain correct when that projection is unavailable.
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
209
257
|
start(request: SubagentTaskStartRequest): SubagentTaskStartResult {
|
|
210
258
|
const scopeId = request.scopeId.trim();
|
|
211
259
|
const idempotencyKey = request.idempotencyKey.trim();
|
|
@@ -272,6 +320,7 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
272
320
|
updatedAt: now,
|
|
273
321
|
controller: new AbortController(),
|
|
274
322
|
controls: [],
|
|
323
|
+
controlReceipts: new Map(),
|
|
275
324
|
progressEvents: 0,
|
|
276
325
|
resultClaimed: false,
|
|
277
326
|
acceptingControls: true,
|
|
@@ -312,6 +361,7 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
312
361
|
}
|
|
313
362
|
task.status = 'completed';
|
|
314
363
|
task.acceptingControls = false;
|
|
364
|
+
this.failPendingControls(task, 'task_completed');
|
|
315
365
|
task.controls.length = 0;
|
|
316
366
|
task.result = truncateMiddle(
|
|
317
367
|
result.content,
|
|
@@ -399,9 +449,16 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
399
449
|
if (index < 0) {
|
|
400
450
|
return { status: 'control_not_found', task: snapshot(task) };
|
|
401
451
|
}
|
|
402
|
-
task.controls.splice(index, 1);
|
|
452
|
+
const [control] = task.controls.splice(index, 1);
|
|
403
453
|
task.updatedAt = Date.now();
|
|
404
|
-
|
|
454
|
+
this.transitionControl(task, control.id, 'rejected', {
|
|
455
|
+
reason: 'withdrawn',
|
|
456
|
+
});
|
|
457
|
+
return {
|
|
458
|
+
status: 'accepted',
|
|
459
|
+
task: snapshot(task),
|
|
460
|
+
controlId: control.id,
|
|
461
|
+
};
|
|
405
462
|
}
|
|
406
463
|
const message = command.message.trim();
|
|
407
464
|
if (message === '') {
|
|
@@ -419,6 +476,12 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
419
476
|
message: `Task already has ${this.options.maxControlsPerTask} pending messages.`,
|
|
420
477
|
};
|
|
421
478
|
}
|
|
479
|
+
if (!this.makeRoomForControlReceipt(task)) {
|
|
480
|
+
return {
|
|
481
|
+
status: 'invalid',
|
|
482
|
+
message: 'Task control receipt capacity is unavailable.',
|
|
483
|
+
};
|
|
484
|
+
}
|
|
422
485
|
const control: PendingControl = {
|
|
423
486
|
id: nanoid(),
|
|
424
487
|
action: command.action,
|
|
@@ -426,6 +489,15 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
426
489
|
};
|
|
427
490
|
task.controls.push(control);
|
|
428
491
|
task.updatedAt = Date.now();
|
|
492
|
+
const receipt: SubagentTaskControlReceipt = {
|
|
493
|
+
controlId: control.id,
|
|
494
|
+
action: control.action,
|
|
495
|
+
status: 'accepted',
|
|
496
|
+
createdAt: task.updatedAt,
|
|
497
|
+
updatedAt: task.updatedAt,
|
|
498
|
+
};
|
|
499
|
+
task.controlReceipts.set(control.id, receipt);
|
|
500
|
+
this.emitControlReceipt(task, receipt);
|
|
429
501
|
return {
|
|
430
502
|
status: 'accepted',
|
|
431
503
|
task: snapshot(task),
|
|
@@ -525,6 +597,59 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
525
597
|
}
|
|
526
598
|
}
|
|
527
599
|
|
|
600
|
+
private makeRoomForControlReceipt(task: StoredTask): boolean {
|
|
601
|
+
while (
|
|
602
|
+
task.controlReceipts.size >= this.options.maxControlReceiptsPerTask
|
|
603
|
+
) {
|
|
604
|
+
const terminal = [...task.controlReceipts].find(
|
|
605
|
+
([, receipt]) => receipt.status !== 'accepted'
|
|
606
|
+
);
|
|
607
|
+
if (terminal == null) {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
task.controlReceipts.delete(terminal[0]);
|
|
611
|
+
}
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private transitionControl(
|
|
616
|
+
task: StoredTask,
|
|
617
|
+
controlId: string,
|
|
618
|
+
status: Exclude<SubagentTaskControlReceipt['status'], 'accepted'>,
|
|
619
|
+
detail: Pick<SubagentTaskControlReceipt, 'boundary' | 'reason'> = {}
|
|
620
|
+
): void {
|
|
621
|
+
const receipt = task.controlReceipts.get(controlId);
|
|
622
|
+
if (receipt == null || receipt.status !== 'accepted') {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const transitioned: SubagentTaskControlReceipt = {
|
|
626
|
+
...receipt,
|
|
627
|
+
status,
|
|
628
|
+
updatedAt: Date.now(),
|
|
629
|
+
...(detail.boundary == null ? {} : { boundary: detail.boundary }),
|
|
630
|
+
...(detail.reason == null ? {} : { reason: detail.reason }),
|
|
631
|
+
};
|
|
632
|
+
task.controlReceipts.set(controlId, transitioned);
|
|
633
|
+
this.emitControlReceipt(task, transitioned);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
private failPendingControls(
|
|
637
|
+
task: StoredTask,
|
|
638
|
+
reason: Extract<
|
|
639
|
+
NonNullable<SubagentTaskControlReceipt['reason']>,
|
|
640
|
+
'task_completed' | 'task_cancelled' | 'task_failed'
|
|
641
|
+
>
|
|
642
|
+
): void {
|
|
643
|
+
for (const control of task.controls) {
|
|
644
|
+
this.transitionControl(
|
|
645
|
+
task,
|
|
646
|
+
control.id,
|
|
647
|
+
reason === 'task_failed' ? 'failed' : 'rejected',
|
|
648
|
+
{ reason }
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
528
653
|
private scheduleExpiry(task: StoredTask): void {
|
|
529
654
|
this.clearTaskTimeout(task);
|
|
530
655
|
this.clearTaskExpiry(task);
|
|
@@ -566,6 +691,10 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
566
691
|
task.status = status;
|
|
567
692
|
this.runningTasks -= 1;
|
|
568
693
|
task.acceptingControls = false;
|
|
694
|
+
this.failPendingControls(
|
|
695
|
+
task,
|
|
696
|
+
status === 'cancelled' ? 'task_cancelled' : 'task_failed'
|
|
697
|
+
);
|
|
569
698
|
task.controls.length = 0;
|
|
570
699
|
task.error = message;
|
|
571
700
|
task.updatedAt = Date.now();
|
|
@@ -575,6 +704,7 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
575
704
|
|
|
576
705
|
private createRuntime(task: StoredTask): SubagentTaskRuntime {
|
|
577
706
|
const take = (
|
|
707
|
+
boundary: SubagentTaskBoundary,
|
|
578
708
|
accept: (control: PendingControl) => boolean
|
|
579
709
|
): InjectedMessage[] => {
|
|
580
710
|
if (task.status !== 'running') {
|
|
@@ -588,6 +718,9 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
588
718
|
task.controls = retained;
|
|
589
719
|
if (selected.length > 0) {
|
|
590
720
|
task.updatedAt = Date.now();
|
|
721
|
+
for (const control of selected) {
|
|
722
|
+
this.transitionControl(task, control.id, 'applied', { boundary });
|
|
723
|
+
}
|
|
591
724
|
}
|
|
592
725
|
return selected.map(toInjectedMessage);
|
|
593
726
|
};
|
|
@@ -599,15 +732,15 @@ export class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
|
599
732
|
task.controls.some((control) => control.action === 'interrupt'),
|
|
600
733
|
drain: (boundary: SubagentTaskBoundary): InjectedMessage[] => {
|
|
601
734
|
if (boundary === 'preempt') {
|
|
602
|
-
return take((control) => control.action === 'interrupt');
|
|
735
|
+
return take(boundary, (control) => control.action === 'interrupt');
|
|
603
736
|
}
|
|
604
737
|
if (boundary === 'tool') {
|
|
605
|
-
return take((control) => control.action !== 'queue');
|
|
738
|
+
return take(boundary, (control) => control.action !== 'queue');
|
|
606
739
|
}
|
|
607
|
-
return take(() => true);
|
|
740
|
+
return take(boundary, () => true);
|
|
608
741
|
},
|
|
609
742
|
closeTurn: (): { closed: boolean; messages: InjectedMessage[] } => {
|
|
610
|
-
const messages = take(() => true);
|
|
743
|
+
const messages = take('turn', () => true);
|
|
611
744
|
if (messages.length > 0) {
|
|
612
745
|
return { closed: false, messages };
|
|
613
746
|
}
|
package/src/types/graph.ts
CHANGED
|
@@ -12,6 +12,28 @@ export type SubagentTaskStatus =
|
|
|
12
12
|
/** Where a pending parent message may enter the child run. */
|
|
13
13
|
export type SubagentTaskBoundary = 'preempt' | 'tool' | 'turn';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Lifecycle of one parent-to-child message after the task store accepts it.
|
|
17
|
+
* Hosts may render a transient `submitted` state before this authoritative
|
|
18
|
+
* receipt exists; that transport state is intentionally not persisted here.
|
|
19
|
+
*/
|
|
20
|
+
export type SubagentTaskControlReceiptStatus =
|
|
21
|
+
| 'accepted'
|
|
22
|
+
| 'applied'
|
|
23
|
+
| 'rejected'
|
|
24
|
+
| 'failed';
|
|
25
|
+
|
|
26
|
+
/** Bounded authoritative receipt for one steer, queue, or interrupt command. */
|
|
27
|
+
export interface SubagentTaskControlReceipt {
|
|
28
|
+
controlId: string;
|
|
29
|
+
action: 'steer' | 'queue' | 'interrupt';
|
|
30
|
+
status: SubagentTaskControlReceiptStatus;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
updatedAt: number;
|
|
33
|
+
boundary?: SubagentTaskBoundary;
|
|
34
|
+
reason?: 'withdrawn' | 'task_completed' | 'task_cancelled' | 'task_failed';
|
|
35
|
+
}
|
|
36
|
+
|
|
15
37
|
/** Parent-to-child control operations accepted while a task is running. */
|
|
16
38
|
export type SubagentTaskControlCommand =
|
|
17
39
|
| { action: 'steer' | 'queue' | 'interrupt'; message: string }
|
|
@@ -42,6 +64,12 @@ export interface SubagentTaskSnapshot {
|
|
|
42
64
|
resultAvailable: boolean;
|
|
43
65
|
resultClaimed: boolean;
|
|
44
66
|
pendingControls: number;
|
|
67
|
+
/**
|
|
68
|
+
* Bounded receipts emitted by stores that support authoritative control
|
|
69
|
+
* tracking. Optional so legacy and custom stores remain compatible during
|
|
70
|
+
* rolling upgrades.
|
|
71
|
+
*/
|
|
72
|
+
controlReceipts?: SubagentTaskControlReceipt[];
|
|
45
73
|
progress?: SubagentTaskProgress;
|
|
46
74
|
error?: string;
|
|
47
75
|
}
|
package/src/types/summarize.ts
CHANGED
|
@@ -11,25 +11,27 @@ export type SummarizationTrigger = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Controls how
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Controls how much recent context is preserved verbatim during compaction.
|
|
15
|
+
* User-turn boundaries are preferred. Under context pressure, older closed
|
|
16
|
+
* tool units inside an otherwise indivisible turn may be summarized while a
|
|
17
|
+
* token-priced recent tail is retained. A lone user payload stays intact.
|
|
18
18
|
*/
|
|
19
19
|
export type RetainRecentConfig = {
|
|
20
20
|
/**
|
|
21
|
-
* Maximum number of recent user-led turns to keep in the tail.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
* Maximum number of recent user-led turns to keep in the tail. A turn begins
|
|
22
|
+
* at a user-authored HumanMessage and includes every following AIMessage and
|
|
23
|
+
* tool result up to the next user-authored HumanMessage. Provider-native
|
|
24
|
+
* HumanMessages containing only tool results remain in the current turn.
|
|
25
|
+
* Set to `0` to disable the recency window (legacy behavior: summarize
|
|
26
|
+
* everything). Defaults to `2`.
|
|
27
27
|
*/
|
|
28
28
|
turns?: number;
|
|
29
29
|
/**
|
|
30
|
-
* Optional
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* Optional retained-recent token budget. Older turns are added whole only
|
|
31
|
+
* while cumulative tokens stay below the cap. If a tool-heavy history has
|
|
32
|
+
* no compactable turn-level head, this is also the minimum recent tail kept
|
|
33
|
+
* behind a pairing-balanced intra-turn cut. When omitted, that fallback
|
|
34
|
+
* retains 16% of the configured context window.
|
|
33
35
|
*/
|
|
34
36
|
tokens?: number;
|
|
35
37
|
};
|