jeopi-agent-core 16.2.28 → 16.2.30
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/CHANGELOG.md +7 -0
- package/package.json +7 -7
- package/src/agent-loop.ts +28 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.2.30] - 2026-07-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- The agent loop now auto-continues a text response truncated by the output-token limit (`stopReason: "length"` with no tool calls, e.g. Sonnet 5 hitting `max_tokens` mid-answer): a synthetic "continue from where you were cut off" user turn is injected and the loop re-samples instead of silently stopping with a cut-off message. Capped at 8 consecutive continuations (`MAX_LENGTH_TRUNCATE_CONTINUATIONS`); any tool call resets the counter. Truncated turns that carry tool calls keep the existing abandoned-toolcall path.
|
|
10
|
+
- Steering/aside dequeues at the turn boundary no longer overwrite already-queued pending messages (the injected length-continuation turn was previously dropped when steering arrived in the same window); they now append.
|
|
11
|
+
|
|
5
12
|
## [16.2.26] - 2026-07-05
|
|
6
13
|
|
|
7
14
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "jeopi-agent-core",
|
|
4
|
-
"version": "16.2.
|
|
4
|
+
"version": "16.2.30",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://github.com/akillness/jeopi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"jeopi-ai": "16.2.
|
|
39
|
-
"jeopi-catalog": "16.2.
|
|
40
|
-
"jeopi-natives": "16.2.
|
|
41
|
-
"jeopi-utils": "16.2.
|
|
42
|
-
"jeopi-wire": "16.2.
|
|
43
|
-
"jeopi-snapcompact": "16.2.
|
|
38
|
+
"jeopi-ai": "16.2.30",
|
|
39
|
+
"jeopi-catalog": "16.2.30",
|
|
40
|
+
"jeopi-natives": "16.2.30",
|
|
41
|
+
"jeopi-utils": "16.2.30",
|
|
42
|
+
"jeopi-wire": "16.2.30",
|
|
43
|
+
"jeopi-snapcompact": "16.2.30",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type ToolResultMessage,
|
|
17
17
|
type TSchema,
|
|
18
18
|
toolWireSchema,
|
|
19
|
+
type UserMessage,
|
|
19
20
|
validateToolArguments,
|
|
20
21
|
} from "jeopi-ai";
|
|
21
22
|
import {
|
|
@@ -84,6 +85,12 @@ const ABORTED: unique symbol = Symbol("agent-loop-aborted");
|
|
|
84
85
|
*/
|
|
85
86
|
const MAX_PAUSED_TURN_CONTINUATIONS = 8;
|
|
86
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Cap on consecutive re-samples triggered by output truncation stop
|
|
90
|
+
* (`stopReason === "length"`) without an intervening tool call.
|
|
91
|
+
*/
|
|
92
|
+
const MAX_LENGTH_TRUNCATE_CONTINUATIONS = 8;
|
|
93
|
+
|
|
87
94
|
/**
|
|
88
95
|
* Cap on consecutive forced escalations for a single soft tool requirement.
|
|
89
96
|
* A forced `toolChoice` guarantees the call, so this is purely defensive: if a
|
|
@@ -758,6 +765,7 @@ async function runLoopBody(
|
|
|
758
765
|
let harmonyRetryAttempt = 0;
|
|
759
766
|
let harmonyTruncateResumeCount = 0;
|
|
760
767
|
let pausedTurnContinuations = 0;
|
|
768
|
+
let lengthTruncateContinuations = 0;
|
|
761
769
|
|
|
762
770
|
// Soft tool requirement lifecycle (reminder → escalate; see SoftToolRequirement).
|
|
763
771
|
// `forcedToolChoice` carries a one-turn escalation into the next model call. It
|
|
@@ -1042,6 +1050,7 @@ async function runLoopBody(
|
|
|
1042
1050
|
|
|
1043
1051
|
if (toolCalls.length > 0) {
|
|
1044
1052
|
pausedTurnContinuations = 0;
|
|
1053
|
+
lengthTruncateContinuations = 0;
|
|
1045
1054
|
} else if (
|
|
1046
1055
|
!hasMoreToolCalls &&
|
|
1047
1056
|
message.stopReason === "stop" &&
|
|
@@ -1055,6 +1064,22 @@ async function runLoopBody(
|
|
|
1055
1064
|
// mid-work turn.
|
|
1056
1065
|
pausedTurnContinuations++;
|
|
1057
1066
|
hasMoreToolCalls = true;
|
|
1067
|
+
} else if (
|
|
1068
|
+
!hasMoreToolCalls &&
|
|
1069
|
+
message.stopReason === "length" &&
|
|
1070
|
+
lengthTruncateContinuations < MAX_LENGTH_TRUNCATE_CONTINUATIONS &&
|
|
1071
|
+
!deadlinePassed
|
|
1072
|
+
) {
|
|
1073
|
+
// Prompt the model to continue the truncated response
|
|
1074
|
+
lengthTruncateContinuations++;
|
|
1075
|
+
const continuationMessage: UserMessage = {
|
|
1076
|
+
role: "user",
|
|
1077
|
+
content: "Continue your response exactly from where it was cut off.",
|
|
1078
|
+
synthetic: true,
|
|
1079
|
+
timestamp: Date.now(),
|
|
1080
|
+
};
|
|
1081
|
+
pendingMessages = [continuationMessage];
|
|
1082
|
+
hasMoreToolCalls = true;
|
|
1058
1083
|
}
|
|
1059
1084
|
|
|
1060
1085
|
await emitTurnEnd(stream, currentContext, message, toolResults, config, signal, {
|
|
@@ -1075,12 +1100,13 @@ async function runLoopBody(
|
|
|
1075
1100
|
if (hasMoreToolCalls) {
|
|
1076
1101
|
// Mid-work: fold any non-interrupting asides into the next turn alongside steering.
|
|
1077
1102
|
const asides = signal?.aborted ? [] : resolveAsides(await config.getAsideMessages?.());
|
|
1078
|
-
|
|
1103
|
+
const basePending = asides.length > 0 ? [...steering, ...asides] : steering;
|
|
1104
|
+
pendingMessages = [...pendingMessages, ...basePending];
|
|
1079
1105
|
} else {
|
|
1080
1106
|
// Stop boundary: only steering (live user input) forces another turn here. Leave
|
|
1081
1107
|
// asides for the outer drain below so a passive aside can't trigger an extra model
|
|
1082
1108
|
// turn ahead of a queued follow-up — the outer drain batches asides + follow-ups together.
|
|
1083
|
-
pendingMessages = steering;
|
|
1109
|
+
pendingMessages = [...pendingMessages, ...steering];
|
|
1084
1110
|
}
|
|
1085
1111
|
}
|
|
1086
1112
|
|