@oh-my-pi/pi-agent-core 16.5.0 → 16.5.2
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
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.5.2] - 2026-07-14
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Improved session deadline abort signals to carry structured cancellation reasons, enabling timeout-aware tools to correctly classify deadline cancellations.
|
|
10
|
+
- Fixed an issue where completed tool executions were incorrectly marked as skipped (clobbering their actual results) if a user message was queued while the tool was in flight.
|
|
11
|
+
|
|
12
|
+
## [16.5.1] - 2026-07-14
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Fixed compatibility with Copilot gpt-5.6 models by correcting token escaping in compaction summaries.
|
|
17
|
+
|
|
5
18
|
## [16.5.0] - 2026-07-13
|
|
6
19
|
|
|
7
20
|
### Added
|
|
@@ -50,8 +50,11 @@ export declare function upsertFileOperations(summary: string, readFiles: string[
|
|
|
50
50
|
*/
|
|
51
51
|
export declare function truncateToolResultForSummary(text: string): string;
|
|
52
52
|
/**
|
|
53
|
-
* Serialize LLM messages
|
|
54
|
-
|
|
53
|
+
* Serialize LLM messages as plain summary input without provider control tokens.
|
|
54
|
+
*/
|
|
55
|
+
export declare function serializeConversationForSummary(messages: Message[], dialect?: Dialect): string;
|
|
56
|
+
/**
|
|
57
|
+
* Serialize LLM messages to transcript text.
|
|
55
58
|
* Call convertToLlm() first to handle custom message types.
|
|
56
59
|
*/
|
|
57
60
|
export declare function serializeConversation(messages: Message[], dialect?: Dialect): string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-agent-core",
|
|
4
|
-
"version": "16.5.
|
|
4
|
+
"version": "16.5.2",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "16.5.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "16.5.
|
|
40
|
-
"@oh-my-pi/pi-natives": "16.5.
|
|
41
|
-
"@oh-my-pi/pi-utils": "16.5.
|
|
42
|
-
"@oh-my-pi/pi-wire": "16.5.
|
|
43
|
-
"@oh-my-pi/snapcompact": "16.5.
|
|
38
|
+
"@oh-my-pi/pi-ai": "16.5.2",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "16.5.2",
|
|
40
|
+
"@oh-my-pi/pi-natives": "16.5.2",
|
|
41
|
+
"@oh-my-pi/pi-utils": "16.5.2",
|
|
42
|
+
"@oh-my-pi/pi-wire": "16.5.2",
|
|
43
|
+
"@oh-my-pi/snapcompact": "16.5.2",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -766,12 +766,13 @@ async function runLoopBody(
|
|
|
766
766
|
let deadlineTimer: Timer | undefined;
|
|
767
767
|
if (config.deadline !== undefined) {
|
|
768
768
|
const deadlineAbortController = new AbortController();
|
|
769
|
+
const deadlineReason = new DOMException("Deadline exceeded", "TimeoutError");
|
|
769
770
|
const delay = config.deadline - Date.now();
|
|
770
771
|
if (delay <= 0) {
|
|
771
|
-
deadlineAbortController.abort(
|
|
772
|
+
deadlineAbortController.abort(deadlineReason);
|
|
772
773
|
} else {
|
|
773
774
|
deadlineTimer = setTimeout(() => {
|
|
774
|
-
deadlineAbortController.abort(
|
|
775
|
+
deadlineAbortController.abort(deadlineReason);
|
|
775
776
|
}, delay);
|
|
776
777
|
}
|
|
777
778
|
signal = signal ? AbortSignal.any([signal, deadlineAbortController.signal]) : deadlineAbortController.signal;
|
|
@@ -2132,19 +2133,21 @@ async function executeToolCalls(
|
|
|
2132
2133
|
|
|
2133
2134
|
const interrupted = interruptState.triggered;
|
|
2134
2135
|
const perToolAborted = record.signal.aborted;
|
|
2135
|
-
const abortedDuringExecution = perToolAborted && isError;
|
|
2136
|
-
if (interrupted && perToolAborted && isError) {
|
|
2137
|
-
// This tool's own signal fired AND it failed
|
|
2138
|
-
//
|
|
2136
|
+
const abortedDuringExecution = perToolAborted && isError && !completedToolExecution;
|
|
2137
|
+
if (interrupted && perToolAborted && isError && !completedToolExecution) {
|
|
2138
|
+
// This tool's own signal fired AND it failed to produce a result: `tool.execute()`
|
|
2139
|
+
// never returned (it threw on the abort), so it was genuinely cut off before
|
|
2140
|
+
// producing usable output. Report it as skipped.
|
|
2139
2141
|
record.skipped = true;
|
|
2140
2142
|
emitToolResult(record, createSkippedToolResult(interruptState.source), true);
|
|
2141
2143
|
} else {
|
|
2142
|
-
// No interrupt on this signal, or the tool finished
|
|
2143
|
-
//
|
|
2144
|
-
// tool already ran its side effects, so the model must
|
|
2145
|
-
//
|
|
2146
|
-
//
|
|
2147
|
-
//
|
|
2144
|
+
// No interrupt on this signal, or the tool finished before the interrupt landed
|
|
2145
|
+
// (`completedToolExecution`) — even if the signal aborted around completion. Keep
|
|
2146
|
+
// its real result: a completed tool already ran its side effects, so the model must
|
|
2147
|
+
// see what actually happened (a genuine non-zero exit / error result) rather than a
|
|
2148
|
+
// false "skipped" that discards work the tool performed (#4752). A peer-IRC interrupt
|
|
2149
|
+
// on the batch leaves non-interruptible tools' signals untouched — their genuine
|
|
2150
|
+
// errors survive here too.
|
|
2148
2151
|
emitToolResult(record, result, isError);
|
|
2149
2152
|
}
|
|
2150
2153
|
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
extractFileOpsFromMessage,
|
|
28
28
|
type FileOperations,
|
|
29
29
|
SUMMARIZATION_SYSTEM_PROMPT,
|
|
30
|
-
|
|
30
|
+
serializeConversationForSummary,
|
|
31
31
|
stripReadSelector,
|
|
32
32
|
truncateToolResultForSummary,
|
|
33
33
|
upsertFileOperations,
|
|
@@ -320,7 +320,7 @@ export async function generateBranchSummary(
|
|
|
320
320
|
// Transform to LLM-compatible messages, then serialize to text
|
|
321
321
|
// Serialization prevents the model from treating it as a conversation to continue
|
|
322
322
|
const llmMessages = (options.convertToLlm ?? defaultConvertToLlm)(messages);
|
|
323
|
-
const conversationText =
|
|
323
|
+
const conversationText = serializeConversationForSummary(llmMessages, preferredDialect(model.id));
|
|
324
324
|
|
|
325
325
|
// Build prompt
|
|
326
326
|
const instructions = customInstructions || BRANCH_SUMMARY_PROMPT;
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
extractFileOpsFromMessage,
|
|
67
67
|
type FileOperations,
|
|
68
68
|
SUMMARIZATION_SYSTEM_PROMPT,
|
|
69
|
-
|
|
69
|
+
serializeConversationForSummary,
|
|
70
70
|
stripReadSelector,
|
|
71
71
|
upsertFileOperations,
|
|
72
72
|
} from "./utils";
|
|
@@ -816,7 +816,7 @@ export async function generateSummary(
|
|
|
816
816
|
// Serialize conversation to text so model doesn't try to continue it
|
|
817
817
|
// Convert to LLM messages first (handles custom app messages when caller provides a transformer).
|
|
818
818
|
const llmMessages = (options?.convertToLlm ?? defaultConvertToLlm)(currentMessages);
|
|
819
|
-
const conversationText =
|
|
819
|
+
const conversationText = serializeConversationForSummary(llmMessages, preferredDialect(model.id));
|
|
820
820
|
|
|
821
821
|
// Build the prompt with conversation wrapped in tags
|
|
822
822
|
let promptText = `<conversation>\n${conversationText}\n</conversation>\n\n`;
|
|
@@ -1030,7 +1030,7 @@ async function generateShortSummary(
|
|
|
1030
1030
|
): Promise<string> {
|
|
1031
1031
|
const maxTokens = Math.min(512, Math.floor(0.2 * reserveTokens));
|
|
1032
1032
|
const llmMessages = (options?.convertToLlm ?? defaultConvertToLlm)(recentMessages);
|
|
1033
|
-
const conversationText =
|
|
1033
|
+
const conversationText = serializeConversationForSummary(llmMessages, preferredDialect(model.id));
|
|
1034
1034
|
|
|
1035
1035
|
let promptText = `<conversation>\n${conversationText}\n</conversation>\n\n`;
|
|
1036
1036
|
if (historySummary) {
|
|
@@ -1578,7 +1578,7 @@ async function generateTurnPrefixSummary(
|
|
|
1578
1578
|
const maxTokens = Math.floor(0.5 * reserveTokens); // Smaller budget for turn prefix
|
|
1579
1579
|
|
|
1580
1580
|
const llmMessages = (options?.convertToLlm ?? defaultConvertToLlm)(messages);
|
|
1581
|
-
const conversationText =
|
|
1581
|
+
const conversationText = serializeConversationForSummary(llmMessages, preferredDialect(model.id));
|
|
1582
1582
|
const promptText = `<conversation>\n${conversationText}\n</conversation>\n\n${TURN_PREFIX_SUMMARIZATION_PROMPT}`;
|
|
1583
1583
|
const summarizationMessages = [
|
|
1584
1584
|
{
|
package/src/compaction/utils.ts
CHANGED
|
@@ -207,9 +207,19 @@ export function truncateToolResultForSummary(text: string): string {
|
|
|
207
207
|
return `${text.slice(0, TOOL_RESULT_MAX_CHARS)}\n\n[... ${truncatedChars} more characters truncated]`;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
const HARMONY_CONTROL_TOKEN_RE = /<\|(start|end|message|channel|constrain|return|call)\|>/g;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Serialize LLM messages as plain summary input without provider control tokens.
|
|
214
|
+
*/
|
|
215
|
+
export function serializeConversationForSummary(messages: Message[], dialect?: Dialect): string {
|
|
216
|
+
const conversation = serializeConversation(messages, dialect);
|
|
217
|
+
if (dialect !== "harmony") return conversation;
|
|
218
|
+
return conversation.replace(HARMONY_CONTROL_TOKEN_RE, "<\\|$1\\|>");
|
|
219
|
+
}
|
|
220
|
+
|
|
210
221
|
/**
|
|
211
|
-
* Serialize LLM messages to text
|
|
212
|
-
* This prevents the model from treating it as a conversation to continue.
|
|
222
|
+
* Serialize LLM messages to transcript text.
|
|
213
223
|
* Call convertToLlm() first to handle custom message types.
|
|
214
224
|
*/
|
|
215
225
|
export function serializeConversation(messages: Message[], dialect?: Dialect): string {
|