@oh-my-pi/pi-agent-core 18.14.1 → 18.14.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,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.14.2] - 2026-10-31
6
+
7
+ ### Added
8
+
9
+ - The soft steering signal a tool sees as `toolCall.steeringSignal` now aborts with the source of the queued message (`"user"`, `"irc"`, …) as its `reason`, so a tool can yield only to the sources it should ([#17537](https://github.com/can1357/oh-my-pi/issues/17537))
10
+
11
+ ### Fixed
12
+
13
+ - Reject incomplete local compaction, turn-prefix, handoff, and branch summaries ending with `stopReason: "length"` instead of using truncated text as complete context. Omit incomplete display-only short summaries without discarding a completed full summary ([#17499](https://github.com/can1357/oh-my-pi/pull/17499) by [@Xytronix](https://github.com/Xytronix)).
14
+
5
15
  ## [18.14.1] - 2026-10-31
6
16
 
7
17
  ### Breaking Changes
@@ -10,6 +10,8 @@ export interface FileOperations {
10
10
  edited: Set<string>;
11
11
  }
12
12
  export declare function createFileOps(): FileOperations;
13
+ /** Error text for a summarization call that hit its output limit (`stopReason: "length"`): partial text is never used. */
14
+ export declare function incompleteOutputError(prefix: string): string;
13
15
  /**
14
16
  * Split a read-tool path into its base path and trailing selector, mirroring the
15
17
  * read tool's own splitter. Single source of the grammar in this package: the
@@ -588,7 +588,8 @@ export interface ToolCallContext {
588
588
  providerMetadata?: ToolCallProviderMetadata;
589
589
  /**
590
590
  * Cooperative steering signal: aborted when a queued user/steering message
591
- * (or an interrupting peer IRC) is detected while this tool batch runs.
591
+ * (or an interrupting peer IRC) is detected while this tool batch runs. Its
592
+ * `reason` names the source: a {@link SteeringInterruptSource} or `"irc"`.
592
593
  * Unlike the hard abort signal it NEVER kills the tool — long-running
593
594
  * tools MAY observe it (via `ctx.toolCall.steeringSignal`) to finish early
594
595
  * or background themselves so the message injects promptly; ignoring it is
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": "18.14.1",
4
+ "version": "18.14.2",
5
5
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": {
@@ -38,16 +38,16 @@
38
38
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
39
39
  },
40
40
  "dependencies": {
41
- "@oh-my-pi/pi-ai": "18.14.1",
42
- "@oh-my-pi/pi-catalog": "18.14.1",
43
- "@oh-my-pi/pi-natives": "18.14.1",
44
- "@oh-my-pi/pi-utils": "18.14.1",
45
- "@oh-my-pi/pi-wire": "18.14.1",
46
- "@oh-my-pi/snapcompact": "18.14.1",
41
+ "@oh-my-pi/pi-ai": "18.14.2",
42
+ "@oh-my-pi/pi-catalog": "18.14.2",
43
+ "@oh-my-pi/pi-natives": "18.14.2",
44
+ "@oh-my-pi/pi-utils": "18.14.2",
45
+ "@oh-my-pi/pi-wire": "18.14.2",
46
+ "@oh-my-pi/snapcompact": "18.14.2",
47
47
  "@opentelemetry/api": "^1.9.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@oh-my-pi/omptype": "18.14.1",
50
+ "@oh-my-pi/omptype": "18.14.2",
51
51
  "@opentelemetry/context-async-hooks": "^2.9.0",
52
52
  "@opentelemetry/sdk-trace-base": "^2.9.0",
53
53
  "@types/bun": "^1.3.14"
package/src/agent-loop.ts CHANGED
@@ -3402,7 +3402,7 @@ async function executeToolCalls(
3402
3402
  // also cascades: the freshly detached job's own completion re-triggers
3403
3403
  // this check for the next command, so millisecond-long commands chain
3404
3404
  // into separate background deliveries (#12869).
3405
- if (source === "irc" && softInterrupts) steeringSoftController.abort();
3405
+ if (source === "irc" && softInterrupts) steeringSoftController.abort("irc");
3406
3406
  };
3407
3407
 
3408
3408
  const checkSteering = async (): Promise<void> => {
@@ -3441,7 +3441,7 @@ async function executeToolCalls(
3441
3441
  interruptState.triggered = true;
3442
3442
  interruptState.source = steeringSource ?? "unknown";
3443
3443
  steeringAbortController.abort(TOOL_INTERRUPT_ABORT_REASON);
3444
- if (softInterrupts) steeringSoftController.abort();
3444
+ if (softInterrupts) steeringSoftController.abort(interruptState.source);
3445
3445
  }
3446
3446
  return;
3447
3447
  }
@@ -28,6 +28,7 @@ import {
28
28
  createFileOps,
29
29
  extractFileOpsFromMessage,
30
30
  type FileOperations,
31
+ incompleteOutputError,
31
32
  SUMMARIZATION_SYSTEM_PROMPT,
32
33
  serializeConversationForSummary,
33
34
  stripReadSelector,
@@ -386,6 +387,9 @@ export async function generateBranchSummary(
386
387
  if (response.stopReason === "error") {
387
388
  return { error: response.errorMessage || "Summarization failed" };
388
389
  }
390
+ if (response.stopReason === "length") {
391
+ return { error: incompleteOutputError("Summarization failed") };
392
+ }
389
393
 
390
394
  let summary = response.content
391
395
  .filter((c): c is { type: "text"; text: string } => c.type === "text")
@@ -91,6 +91,7 @@ import {
91
91
  escapeSummaryBoundaryTags,
92
92
  extractFileOpsFromMessage,
93
93
  type FileOperations,
94
+ incompleteOutputError,
94
95
  SUMMARIZATION_SYSTEM_PROMPT,
95
96
  serializeConversationForSummary,
96
97
  stripReadSelector,
@@ -1075,6 +1076,9 @@ async function summarizeConversationWindow(
1075
1076
  if (response.stopReason === "error") {
1076
1077
  throw createSummarizationError("Summarization failed", response);
1077
1078
  }
1079
+ if (response.stopReason === "length") {
1080
+ throw new Error(incompleteOutputError("Summarization failed"));
1081
+ }
1078
1082
 
1079
1083
  const textContent = response.content
1080
1084
  .filter((c): c is { type: "text"; text: string } => c.type === "text")
@@ -1182,6 +1186,9 @@ export async function generateHandoffFromContext(
1182
1186
  if (response.stopReason === "error") {
1183
1187
  throw createSummarizationError("Handoff generation failed", response);
1184
1188
  }
1189
+ if (response.stopReason === "length") {
1190
+ throw new Error(incompleteOutputError("Handoff generation failed"));
1191
+ }
1185
1192
 
1186
1193
  return response.content
1187
1194
  .filter((c): c is { type: "text"; text: string } => c.type === "text")
@@ -1305,8 +1312,10 @@ async function generateShortSummary(
1305
1312
  case "error":
1306
1313
  outcome = { kind: "failed", error: response.errorMessage || "Unknown error" };
1307
1314
  break;
1308
- case "stop":
1309
1315
  case "length":
1316
+ outcome = { kind: "failed", error: incompleteOutputError("Short summary failed") };
1317
+ break;
1318
+ case "stop":
1310
1319
  case "toolUse":
1311
1320
  outcome = {
1312
1321
  kind: "text",
@@ -2435,6 +2444,9 @@ async function generateTurnPrefixSummary(
2435
2444
  if (response.stopReason === "error") {
2436
2445
  throw createSummarizationError("Turn prefix summarization failed", response);
2437
2446
  }
2447
+ if (response.stopReason === "length") {
2448
+ throw new Error(incompleteOutputError("Turn prefix summarization failed"));
2449
+ }
2438
2450
 
2439
2451
  return response.content
2440
2452
  .filter((c): c is { type: "text"; text: string } => c.type === "text")
@@ -28,6 +28,11 @@ export function createFileOps(): FileOperations {
28
28
  };
29
29
  }
30
30
 
31
+ /** Error text for a summarization call that hit its output limit (`stopReason: "length"`): partial text is never used. */
32
+ export function incompleteOutputError(prefix: string): string {
33
+ return `${prefix}: model output ended incomplete (stopReason: length)`;
34
+ }
35
+
31
36
  // Read-tool selector grammar, mirrored from the conservative filesystem splitter in
32
37
  // packages/coding-agent/src/tools/path-utils.ts (splitPathAndSel). Keep in sync.
33
38
  // A trailing `:chunk` is a selector only when it is a line-range list
package/src/types.ts CHANGED
@@ -672,7 +672,8 @@ export interface ToolCallContext {
672
672
  providerMetadata?: ToolCallProviderMetadata;
673
673
  /**
674
674
  * Cooperative steering signal: aborted when a queued user/steering message
675
- * (or an interrupting peer IRC) is detected while this tool batch runs.
675
+ * (or an interrupting peer IRC) is detected while this tool batch runs. Its
676
+ * `reason` names the source: a {@link SteeringInterruptSource} or `"irc"`.
676
677
  * Unlike the hard abort signal it NEVER kills the tool — long-running
677
678
  * tools MAY observe it (via `ctx.toolCall.steeringSignal`) to finish early
678
679
  * or background themselves so the message injects promptly; ignoring it is