jeopi-agent-core 16.4.2 → 16.4.3
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 +6 -0
- package/dist/types/types.d.ts +14 -1
- package/package.json +7 -7
- package/src/agent-loop.ts +33 -7
- package/src/agent.ts +13 -1
- package/src/types.ts +16 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.4.3] - 2026-07-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed skipped sibling tool results caused by system advisor steering so they no longer claim a queued user message caused the skip. `hasSteeringMessages` may now return a `SteeringQueueState` (`{ queued, source }`) instead of a plain boolean; the skipped-tool-result wording distinguishes queued user messages, pending system advisories, and peer IRC interrupts. Ported from oh-my-pi (upstream `74c63fa6c`).
|
|
10
|
+
|
|
5
11
|
## [16.2.30] - 2026-07-10
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/dist/types/types.d.ts
CHANGED
|
@@ -53,6 +53,15 @@ export interface SoftToolRequirement {
|
|
|
53
53
|
export type ToolChoiceDirective = ToolChoice | SoftToolRequirement;
|
|
54
54
|
/** True when a {@link ToolChoiceDirective} is a soft requirement, not a hard choice. */
|
|
55
55
|
export declare function isSoftToolRequirement(directive: ToolChoiceDirective | undefined): directive is SoftToolRequirement;
|
|
56
|
+
/** Source category for a queued steering interrupt observed without consuming the queue. */
|
|
57
|
+
export type SteeringInterruptSource = "user" | "system" | "unknown";
|
|
58
|
+
/** Non-consuming summary of whether queued steering should interrupt a tool batch. */
|
|
59
|
+
export interface SteeringQueueState {
|
|
60
|
+
/** True when at least one steering message is queued. */
|
|
61
|
+
queued: boolean;
|
|
62
|
+
/** Best-effort origin used only to word synthetic skipped-tool results. */
|
|
63
|
+
source?: SteeringInterruptSource;
|
|
64
|
+
}
|
|
56
65
|
/**
|
|
57
66
|
* Configuration for the agent loop.
|
|
58
67
|
*/
|
|
@@ -156,8 +165,12 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
156
165
|
*
|
|
157
166
|
* When omitted, steering never interrupts a running tool batch; queued
|
|
158
167
|
* messages are still delivered at the next injection boundary.
|
|
168
|
+
*
|
|
169
|
+
* Returning `true` is treated as user-originated steering for compatibility.
|
|
170
|
+
* Return a {@link SteeringQueueState} when the queue can distinguish system
|
|
171
|
+
* advisories from real user messages.
|
|
159
172
|
*/
|
|
160
|
-
hasSteeringMessages?: () => boolean | Promise<boolean>;
|
|
173
|
+
hasSteeringMessages?: () => boolean | SteeringQueueState | Promise<boolean | SteeringQueueState>;
|
|
161
174
|
/**
|
|
162
175
|
* Peeks whether IRC messages should interrupt an interruptible waiting tool.
|
|
163
176
|
*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "jeopi-agent-core",
|
|
4
|
-
"version": "16.4.
|
|
4
|
+
"version": "16.4.3",
|
|
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.4.
|
|
39
|
-
"jeopi-catalog": "16.4.
|
|
40
|
-
"jeopi-natives": "16.4.
|
|
41
|
-
"jeopi-utils": "16.4.
|
|
42
|
-
"jeopi-wire": "16.4.
|
|
43
|
-
"jeopi-snapcompact": "16.4.
|
|
38
|
+
"jeopi-ai": "16.4.3",
|
|
39
|
+
"jeopi-catalog": "16.4.3",
|
|
40
|
+
"jeopi-natives": "16.4.3",
|
|
41
|
+
"jeopi-utils": "16.4.3",
|
|
42
|
+
"jeopi-wire": "16.4.3",
|
|
43
|
+
"jeopi-snapcompact": "16.4.3",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -66,6 +66,8 @@ import type {
|
|
|
66
66
|
AgentToolResult,
|
|
67
67
|
AgentTurnEndContext,
|
|
68
68
|
AsideMessage,
|
|
69
|
+
SteeringInterruptSource,
|
|
70
|
+
SteeringQueueState,
|
|
69
71
|
StreamFn,
|
|
70
72
|
} from "./types";
|
|
71
73
|
import { isSoftToolRequirement } from "./types";
|
|
@@ -1723,7 +1725,7 @@ async function executeToolCalls(
|
|
|
1723
1725
|
const interruptibleSignal: AbortSignal = signal
|
|
1724
1726
|
? AbortSignal.any([signal, steeringAbortController.signal, ircAbortController.signal])
|
|
1725
1727
|
: AbortSignal.any([steeringAbortController.signal, ircAbortController.signal]);
|
|
1726
|
-
const interruptState = { triggered: false };
|
|
1728
|
+
const interruptState: { triggered: boolean; source?: SteeringInterruptSource | "irc" } = { triggered: false };
|
|
1727
1729
|
|
|
1728
1730
|
const records = toolCalls.map(toolCall => {
|
|
1729
1731
|
// Tools emitted via OpenAI's custom-tool path (e.g. `apply_patch` on GPT-5)
|
|
@@ -1758,17 +1760,28 @@ async function executeToolCalls(
|
|
|
1758
1760
|
// the injection boundary. Fall back to consuming steering only for older
|
|
1759
1761
|
// integrations that never supplied a peek.
|
|
1760
1762
|
let steeringQueued = false;
|
|
1763
|
+
let steeringSource: SteeringInterruptSource | undefined;
|
|
1761
1764
|
if (hasSteeringMessages) {
|
|
1762
|
-
|
|
1765
|
+
const queuedState = await hasSteeringMessages();
|
|
1766
|
+
if (typeof queuedState === "boolean") {
|
|
1767
|
+
steeringQueued = queuedState;
|
|
1768
|
+
steeringSource = queuedState ? "user" : undefined;
|
|
1769
|
+
} else {
|
|
1770
|
+
const state: SteeringQueueState = queuedState;
|
|
1771
|
+
steeringQueued = state.queued;
|
|
1772
|
+
steeringSource = state.source ?? (state.queued ? "unknown" : undefined);
|
|
1773
|
+
}
|
|
1763
1774
|
} else if (getSteeringMessages) {
|
|
1764
1775
|
const msgs = await getSteeringMessages();
|
|
1765
1776
|
steeringQueued = (msgs?.length ?? 0) > 0;
|
|
1777
|
+
steeringSource = steeringQueued ? "user" : undefined;
|
|
1766
1778
|
}
|
|
1767
1779
|
if (steeringQueued) {
|
|
1768
|
-
//
|
|
1780
|
+
// Queued steering upgrades an in-flight IRC interrupt: it aborts the
|
|
1769
1781
|
// shared signal so foreground tools stop as they do for a user Esc.
|
|
1770
1782
|
if (!steeringAbortController.signal.aborted) {
|
|
1771
1783
|
interruptState.triggered = true;
|
|
1784
|
+
interruptState.source = steeringSource ?? "unknown";
|
|
1772
1785
|
steeringAbortController.abort();
|
|
1773
1786
|
}
|
|
1774
1787
|
return;
|
|
@@ -1778,6 +1791,7 @@ async function executeToolCalls(
|
|
|
1778
1791
|
// Peer IRC only aborts interruptible waits: a foreground bash / write
|
|
1779
1792
|
// mid-execution keeps running so we never leave partial side effects.
|
|
1780
1793
|
interruptState.triggered = true;
|
|
1794
|
+
interruptState.source = "irc";
|
|
1781
1795
|
ircAbortController.abort();
|
|
1782
1796
|
}
|
|
1783
1797
|
};
|
|
@@ -2038,7 +2052,7 @@ async function executeToolCalls(
|
|
|
2038
2052
|
// This tool's own signal fired AND it failed — it was cut off before producing
|
|
2039
2053
|
// a usable result, so report it as skipped.
|
|
2040
2054
|
record.skipped = true;
|
|
2041
|
-
emitToolResult(record, createSkippedToolResult(), true);
|
|
2055
|
+
emitToolResult(record, createSkippedToolResult(interruptState.source), true);
|
|
2042
2056
|
} else {
|
|
2043
2057
|
// No interrupt on this signal, or the tool finished (successfully or with a
|
|
2044
2058
|
// genuine error) before the interrupt landed. Keep its real result: a completed
|
|
@@ -2132,7 +2146,7 @@ async function executeToolCalls(
|
|
|
2132
2146
|
toolName: record.toolCall.name,
|
|
2133
2147
|
status: "skipped",
|
|
2134
2148
|
});
|
|
2135
|
-
emitToolResult(record, createSkippedToolResult(), true);
|
|
2149
|
+
emitToolResult(record, createSkippedToolResult(interruptState.source), true);
|
|
2136
2150
|
}
|
|
2137
2151
|
}
|
|
2138
2152
|
|
|
@@ -2201,12 +2215,24 @@ function createToolSignalAbortedResult(signal: AbortSignal): AgentToolResult<unk
|
|
|
2201
2215
|
};
|
|
2202
2216
|
}
|
|
2203
2217
|
|
|
2204
|
-
function createSkippedToolResult(): AgentToolResult<any> {
|
|
2218
|
+
function createSkippedToolResult(source: SteeringInterruptSource | "irc" | undefined): AgentToolResult<any> {
|
|
2219
|
+
let reason = "pending steering message";
|
|
2220
|
+
let blocker = "queued message";
|
|
2221
|
+
if (source === "user") {
|
|
2222
|
+
reason = "queued user message";
|
|
2223
|
+
blocker = "queued message";
|
|
2224
|
+
} else if (source === "system") {
|
|
2225
|
+
reason = "pending system advisory";
|
|
2226
|
+
blocker = "advisory";
|
|
2227
|
+
} else if (source === "irc") {
|
|
2228
|
+
reason = "pending peer interrupt";
|
|
2229
|
+
blocker = "interrupt";
|
|
2230
|
+
}
|
|
2205
2231
|
return {
|
|
2206
2232
|
content: [
|
|
2207
2233
|
{
|
|
2208
2234
|
type: "text",
|
|
2209
|
-
text:
|
|
2235
|
+
text: `Skipped due to ${reason}. Do not count this skipped result as completed work or verification. After the ${blocker} is handled on the next step, retry the skipped tool if it is still needed.`,
|
|
2210
2236
|
},
|
|
2211
2237
|
],
|
|
2212
2238
|
details: {},
|
package/src/agent.ts
CHANGED
|
@@ -1185,7 +1185,19 @@ export class Agent {
|
|
|
1185
1185
|
}
|
|
1186
1186
|
return this.#dequeueSteeringMessages();
|
|
1187
1187
|
},
|
|
1188
|
-
hasSteeringMessages: () =>
|
|
1188
|
+
hasSteeringMessages: () => {
|
|
1189
|
+
if (this.#steeringQueue.length === 0) {
|
|
1190
|
+
return { queued: false };
|
|
1191
|
+
}
|
|
1192
|
+
for (const message of this.#steeringQueue) {
|
|
1193
|
+
const role = "role" in message ? message.role : undefined;
|
|
1194
|
+
const attribution = "attribution" in message ? message.attribution : undefined;
|
|
1195
|
+
if (role === "user" && attribution !== "agent") {
|
|
1196
|
+
return { queued: true, source: "user" };
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return { queued: true, source: "system" };
|
|
1200
|
+
},
|
|
1189
1201
|
hasIrcInterrupts: this.hasIrcInterrupts,
|
|
1190
1202
|
getFollowUpMessages: async () => this.#dequeueFollowUpMessages(),
|
|
1191
1203
|
getAsideMessages: async () => (await this.#asideMessageProvider?.()) ?? [],
|
package/src/types.ts
CHANGED
|
@@ -83,6 +83,17 @@ export function isSoftToolRequirement(directive: ToolChoiceDirective | undefined
|
|
|
83
83
|
return typeof directive === "object" && directive !== null && (directive as SoftToolRequirement).soft === true;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/** Source category for a queued steering interrupt observed without consuming the queue. */
|
|
87
|
+
export type SteeringInterruptSource = "user" | "system" | "unknown";
|
|
88
|
+
|
|
89
|
+
/** Non-consuming summary of whether queued steering should interrupt a tool batch. */
|
|
90
|
+
export interface SteeringQueueState {
|
|
91
|
+
/** True when at least one steering message is queued. */
|
|
92
|
+
queued: boolean;
|
|
93
|
+
/** Best-effort origin used only to word synthetic skipped-tool results. */
|
|
94
|
+
source?: SteeringInterruptSource;
|
|
95
|
+
}
|
|
96
|
+
|
|
86
97
|
/**
|
|
87
98
|
* Configuration for the agent loop.
|
|
88
99
|
*/
|
|
@@ -196,8 +207,12 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
196
207
|
*
|
|
197
208
|
* When omitted, steering never interrupts a running tool batch; queued
|
|
198
209
|
* messages are still delivered at the next injection boundary.
|
|
210
|
+
*
|
|
211
|
+
* Returning `true` is treated as user-originated steering for compatibility.
|
|
212
|
+
* Return a {@link SteeringQueueState} when the queue can distinguish system
|
|
213
|
+
* advisories from real user messages.
|
|
199
214
|
*/
|
|
200
|
-
hasSteeringMessages?: () => boolean | Promise<boolean>;
|
|
215
|
+
hasSteeringMessages?: () => boolean | SteeringQueueState | Promise<boolean | SteeringQueueState>;
|
|
201
216
|
|
|
202
217
|
/**
|
|
203
218
|
* Peeks whether IRC messages should interrupt an interruptible waiting tool.
|