@oh-my-pi/pi-agent-core 16.5.1 → 17.0.0
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 +22 -0
- package/dist/types/compaction/entries.d.ts +1 -6
- package/dist/types/types.d.ts +22 -2
- package/package.json +7 -7
- package/src/agent-loop.ts +19 -13
- package/src/compaction/branch-summarization.ts +0 -1
- package/src/compaction/entries.ts +0 -7
- package/src/types.ts +20 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.0.0] - 2026-07-15
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Replaced the irc, job, and launch tools with a unified hub tool.
|
|
10
|
+
- Removed the tool discovery system (including the search-tool-bm25 tool) and its associated configuration settings (tools.discoveryMode, tools.essentialOverride, mcp.discoveryMode, and mcp.discoveryDefaultServers).
|
|
11
|
+
- Removed the resolve tool; plan approval and preview actions now use writes to the xd://propose virtual device path.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Introduced the xd:// virtual device protocol for mounting tools as URLs readable/writable via read/write tools, configurable via the new tools.xdev setting (defaults to true).
|
|
16
|
+
- Added the hub tool, consolidating agent peer messaging, background job control, and supervised long-running processes.
|
|
17
|
+
- Added the edit.enforceSeenLines configuration setting (defaults to false) to optionally reject edits on lines that have not been fully displayed.
|
|
18
|
+
- Added the ToolLoadMode type and an optional satisfies predicate to SoftToolRequirement to support compliance checks against specific invocation shapes (such as writing to a virtual device path).
|
|
19
|
+
|
|
20
|
+
## [16.5.2] - 2026-07-14
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Improved session deadline abort signals to carry structured cancellation reasons, enabling timeout-aware tools to correctly classify deadline cancellations.
|
|
25
|
+
- 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.
|
|
26
|
+
|
|
5
27
|
## [16.5.1] - 2026-07-14
|
|
6
28
|
|
|
7
29
|
### Fixed
|
|
@@ -84,11 +84,6 @@ export interface TtsrInjectionEntry extends SessionEntryBase {
|
|
|
84
84
|
/** Names of rules that were injected */
|
|
85
85
|
injectedRules: string[];
|
|
86
86
|
}
|
|
87
|
-
export interface MCPToolSelectionEntry extends SessionEntryBase {
|
|
88
|
-
type: "mcp_tool_selection";
|
|
89
|
-
/** MCP tool names selected for visibility in discovery mode. */
|
|
90
|
-
selectedToolNames: string[];
|
|
91
|
-
}
|
|
92
87
|
export interface SessionInitEntry extends SessionEntryBase {
|
|
93
88
|
type: "session_init";
|
|
94
89
|
/** Full system prompt sent to the model */
|
|
@@ -109,7 +104,7 @@ export interface ModeChangeEntry extends SessionEntryBase {
|
|
|
109
104
|
}
|
|
110
105
|
export interface CustomCompactionSessionEntries {
|
|
111
106
|
}
|
|
112
|
-
export type SessionEntry = SessionMessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | ServiceTierChangeEntry | CompactionEntry | BranchSummaryEntry | CustomEntry | CustomMessageEntry | LabelEntry | TitleChangeEntry | TtsrInjectionEntry |
|
|
107
|
+
export type SessionEntry = SessionMessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | ServiceTierChangeEntry | CompactionEntry | BranchSummaryEntry | CustomEntry | CustomMessageEntry | LabelEntry | TitleChangeEntry | TtsrInjectionEntry | SessionInitEntry | ModeChangeEntry | CustomCompactionSessionEntries[keyof CustomCompactionSessionEntries];
|
|
113
108
|
export interface ReadonlySessionManager {
|
|
114
109
|
getBranch(leafId?: string | null): SessionEntry[];
|
|
115
110
|
getEntry(id: string): SessionEntry | undefined;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -43,6 +43,16 @@ export interface SoftToolRequirement {
|
|
|
43
43
|
id: string;
|
|
44
44
|
/** Tool that must be called before the loop runs other tools or yields. */
|
|
45
45
|
toolName: string;
|
|
46
|
+
/**
|
|
47
|
+
* Per-call compliance check: a turn satisfies the requirement only when every
|
|
48
|
+
* tool call passes. Defaults to `name === toolName`. Lets a host demand a
|
|
49
|
+
* specific invocation shape (e.g. `write` targeting a virtual device path)
|
|
50
|
+
* instead of any call to `toolName`. Escalation still forces `toolName`.
|
|
51
|
+
*/
|
|
52
|
+
satisfies?(toolCall: {
|
|
53
|
+
name: string;
|
|
54
|
+
arguments?: Record<string, unknown>;
|
|
55
|
+
}): boolean;
|
|
46
56
|
/** Host-owned reminder messages, injected once per `id` activation. */
|
|
47
57
|
reminder: AgentMessage[];
|
|
48
58
|
}
|
|
@@ -508,6 +518,16 @@ export interface RenderResultOptions {
|
|
|
508
518
|
}
|
|
509
519
|
/** Capability tier a tool exercises. Determines which approval modes auto-approve it. */
|
|
510
520
|
export type ToolTier = "read" | "write" | "exec";
|
|
521
|
+
/**
|
|
522
|
+
* How an enabled tool is presented to the model. `"essential"` tools are exposed
|
|
523
|
+
* as normal top-level tools. `"discoverable"` tools are removed from the top-level
|
|
524
|
+
* schema and either mounted under `xd://` device URLs (when that transport is
|
|
525
|
+
* active) or surfaced through BM25 tool search — keeping their schemas off every
|
|
526
|
+
* request. Selection (settings, `hidden`, `defaultInactive`, explicit `--tools`,
|
|
527
|
+
* provider availability) decides whether a tool is enabled; `loadMode` only
|
|
528
|
+
* decides how an enabled tool is presented.
|
|
529
|
+
*/
|
|
530
|
+
export type ToolLoadMode = "essential" | "discoverable";
|
|
511
531
|
/**
|
|
512
532
|
* Per-tool approval declaration.
|
|
513
533
|
* - bare tier ("read" / "write" / "exec") — static classification.
|
|
@@ -536,8 +556,8 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
|
|
|
536
556
|
hidden?: boolean;
|
|
537
557
|
/** If true, tool can stage a pending action that requires explicit resolution via the resolve tool. */
|
|
538
558
|
deferrable?: boolean;
|
|
539
|
-
/**
|
|
540
|
-
loadMode?:
|
|
559
|
+
/** How an enabled tool is presented. See {@link ToolLoadMode}. Omitted is treated as `"essential"` for built-ins; custom-tool adapters normalize omission to `"discoverable"`. */
|
|
560
|
+
loadMode?: ToolLoadMode;
|
|
541
561
|
/** Short one-line summary used for tool discovery indexes. */
|
|
542
562
|
summary?: string;
|
|
543
563
|
/**
|
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": "
|
|
4
|
+
"version": "17.0.0",
|
|
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": "
|
|
39
|
-
"@oh-my-pi/pi-catalog": "
|
|
40
|
-
"@oh-my-pi/pi-natives": "
|
|
41
|
-
"@oh-my-pi/pi-utils": "
|
|
42
|
-
"@oh-my-pi/pi-wire": "
|
|
43
|
-
"@oh-my-pi/snapcompact": "
|
|
38
|
+
"@oh-my-pi/pi-ai": "17.0.0",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "17.0.0",
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.0.0",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.0.0",
|
|
42
|
+
"@oh-my-pi/pi-wire": "17.0.0",
|
|
43
|
+
"@oh-my-pi/snapcompact": "17.0.0",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -67,6 +67,7 @@ import type {
|
|
|
67
67
|
AgentToolResult,
|
|
68
68
|
AgentTurnEndContext,
|
|
69
69
|
AsideMessage,
|
|
70
|
+
SoftToolRequirement,
|
|
70
71
|
SteeringInterruptSource,
|
|
71
72
|
SteeringQueueState,
|
|
72
73
|
StreamFn,
|
|
@@ -766,12 +767,13 @@ async function runLoopBody(
|
|
|
766
767
|
let deadlineTimer: Timer | undefined;
|
|
767
768
|
if (config.deadline !== undefined) {
|
|
768
769
|
const deadlineAbortController = new AbortController();
|
|
770
|
+
const deadlineReason = new DOMException("Deadline exceeded", "TimeoutError");
|
|
769
771
|
const delay = config.deadline - Date.now();
|
|
770
772
|
if (delay <= 0) {
|
|
771
|
-
deadlineAbortController.abort(
|
|
773
|
+
deadlineAbortController.abort(deadlineReason);
|
|
772
774
|
} else {
|
|
773
775
|
deadlineTimer = setTimeout(() => {
|
|
774
|
-
deadlineAbortController.abort(
|
|
776
|
+
deadlineAbortController.abort(deadlineReason);
|
|
775
777
|
}, delay);
|
|
776
778
|
}
|
|
777
779
|
signal = signal ? AbortSignal.any([signal, deadlineAbortController.signal]) : deadlineAbortController.signal;
|
|
@@ -802,6 +804,7 @@ async function runLoopBody(
|
|
|
802
804
|
// getToolChoice is never advanced twice; the flag resets at the message boundary.
|
|
803
805
|
let hostToolChoice: ToolChoice | undefined;
|
|
804
806
|
let softRequiredTool: string | undefined;
|
|
807
|
+
let softSatisfies: SoftToolRequirement["satisfies"];
|
|
805
808
|
let directiveResolvedForTurn = false;
|
|
806
809
|
|
|
807
810
|
// Outer loop: continues when queued follow-up messages arrive after agent would stop
|
|
@@ -860,6 +863,7 @@ async function runLoopBody(
|
|
|
860
863
|
const softReq = isSoftToolRequirement(directive) ? directive : undefined;
|
|
861
864
|
hostToolChoice = directive === undefined || isSoftToolRequirement(directive) ? undefined : directive;
|
|
862
865
|
softRequiredTool = softReq?.toolName;
|
|
866
|
+
softSatisfies = softReq?.satisfies;
|
|
863
867
|
if (softReq !== undefined) {
|
|
864
868
|
if (softReq.id !== softRequirementId) {
|
|
865
869
|
softRequirementId = softReq.id;
|
|
@@ -1023,7 +1027,7 @@ async function runLoopBody(
|
|
|
1023
1027
|
const calledOnlyRequiredTool =
|
|
1024
1028
|
softRequiredTool !== undefined &&
|
|
1025
1029
|
toolCalls.length > 0 &&
|
|
1026
|
-
toolCalls.every(toolCall => toolCall.name === softRequiredTool);
|
|
1030
|
+
toolCalls.every(toolCall => softSatisfies?.(toolCall) ?? toolCall.name === softRequiredTool);
|
|
1027
1031
|
const softGateActive =
|
|
1028
1032
|
softRequiredTool !== undefined && !hardToolChoiceBlocks(config.toolChoice, softRequiredTool);
|
|
1029
1033
|
const softNonCompliant = softGateActive && !calledOnlyRequiredTool;
|
|
@@ -2132,19 +2136,21 @@ async function executeToolCalls(
|
|
|
2132
2136
|
|
|
2133
2137
|
const interrupted = interruptState.triggered;
|
|
2134
2138
|
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
|
-
//
|
|
2139
|
+
const abortedDuringExecution = perToolAborted && isError && !completedToolExecution;
|
|
2140
|
+
if (interrupted && perToolAborted && isError && !completedToolExecution) {
|
|
2141
|
+
// This tool's own signal fired AND it failed to produce a result: `tool.execute()`
|
|
2142
|
+
// never returned (it threw on the abort), so it was genuinely cut off before
|
|
2143
|
+
// producing usable output. Report it as skipped.
|
|
2139
2144
|
record.skipped = true;
|
|
2140
2145
|
emitToolResult(record, createSkippedToolResult(interruptState.source), true);
|
|
2141
2146
|
} 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
|
-
//
|
|
2147
|
+
// No interrupt on this signal, or the tool finished before the interrupt landed
|
|
2148
|
+
// (`completedToolExecution`) — even if the signal aborted around completion. Keep
|
|
2149
|
+
// its real result: a completed tool already ran its side effects, so the model must
|
|
2150
|
+
// see what actually happened (a genuine non-zero exit / error result) rather than a
|
|
2151
|
+
// false "skipped" that discards work the tool performed (#4752). A peer-IRC interrupt
|
|
2152
|
+
// on the batch leaves non-interruptible tools' signals untouched — their genuine
|
|
2153
|
+
// errors survive here too.
|
|
2148
2154
|
emitToolResult(record, result, isError);
|
|
2149
2155
|
}
|
|
2150
2156
|
|
|
@@ -200,7 +200,6 @@ function getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {
|
|
|
200
200
|
case "label":
|
|
201
201
|
case "service_tier_change":
|
|
202
202
|
case "ttsr_injection":
|
|
203
|
-
case "mcp_tool_selection":
|
|
204
203
|
case "session_init":
|
|
205
204
|
case "mode_change":
|
|
206
205
|
return undefined;
|
|
@@ -97,12 +97,6 @@ export interface TtsrInjectionEntry extends SessionEntryBase {
|
|
|
97
97
|
injectedRules: string[];
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
export interface MCPToolSelectionEntry extends SessionEntryBase {
|
|
101
|
-
type: "mcp_tool_selection";
|
|
102
|
-
/** MCP tool names selected for visibility in discovery mode. */
|
|
103
|
-
selectedToolNames: string[];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
100
|
export interface SessionInitEntry extends SessionEntryBase {
|
|
107
101
|
type: "session_init";
|
|
108
102
|
/** Full system prompt sent to the model */
|
|
@@ -137,7 +131,6 @@ export type SessionEntry =
|
|
|
137
131
|
| LabelEntry
|
|
138
132
|
| TitleChangeEntry
|
|
139
133
|
| TtsrInjectionEntry
|
|
140
|
-
| MCPToolSelectionEntry
|
|
141
134
|
| SessionInitEntry
|
|
142
135
|
| ModeChangeEntry
|
|
143
136
|
| CustomCompactionSessionEntries[keyof CustomCompactionSessionEntries];
|
package/src/types.ts
CHANGED
|
@@ -68,6 +68,13 @@ export interface SoftToolRequirement {
|
|
|
68
68
|
id: string;
|
|
69
69
|
/** Tool that must be called before the loop runs other tools or yields. */
|
|
70
70
|
toolName: string;
|
|
71
|
+
/**
|
|
72
|
+
* Per-call compliance check: a turn satisfies the requirement only when every
|
|
73
|
+
* tool call passes. Defaults to `name === toolName`. Lets a host demand a
|
|
74
|
+
* specific invocation shape (e.g. `write` targeting a virtual device path)
|
|
75
|
+
* instead of any call to `toolName`. Escalation still forces `toolName`.
|
|
76
|
+
*/
|
|
77
|
+
satisfies?(toolCall: { name: string; arguments?: Record<string, unknown> }): boolean;
|
|
71
78
|
/** Host-owned reminder messages, injected once per `id` activation. */
|
|
72
79
|
reminder: AgentMessage[];
|
|
73
80
|
}
|
|
@@ -587,6 +594,17 @@ export interface RenderResultOptions {
|
|
|
587
594
|
/** Capability tier a tool exercises. Determines which approval modes auto-approve it. */
|
|
588
595
|
export type ToolTier = "read" | "write" | "exec";
|
|
589
596
|
|
|
597
|
+
/**
|
|
598
|
+
* How an enabled tool is presented to the model. `"essential"` tools are exposed
|
|
599
|
+
* as normal top-level tools. `"discoverable"` tools are removed from the top-level
|
|
600
|
+
* schema and either mounted under `xd://` device URLs (when that transport is
|
|
601
|
+
* active) or surfaced through BM25 tool search — keeping their schemas off every
|
|
602
|
+
* request. Selection (settings, `hidden`, `defaultInactive`, explicit `--tools`,
|
|
603
|
+
* provider availability) decides whether a tool is enabled; `loadMode` only
|
|
604
|
+
* decides how an enabled tool is presented.
|
|
605
|
+
*/
|
|
606
|
+
export type ToolLoadMode = "essential" | "discoverable";
|
|
607
|
+
|
|
590
608
|
/**
|
|
591
609
|
* Per-tool approval declaration.
|
|
592
610
|
* - bare tier ("read" / "write" / "exec") — static classification.
|
|
@@ -625,8 +643,8 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
|
|
|
625
643
|
hidden?: boolean;
|
|
626
644
|
/** If true, tool can stage a pending action that requires explicit resolution via the resolve tool. */
|
|
627
645
|
deferrable?: boolean;
|
|
628
|
-
/**
|
|
629
|
-
loadMode?:
|
|
646
|
+
/** How an enabled tool is presented. See {@link ToolLoadMode}. Omitted is treated as `"essential"` for built-ins; custom-tool adapters normalize omission to `"discoverable"`. */
|
|
647
|
+
loadMode?: ToolLoadMode;
|
|
630
648
|
/** Short one-line summary used for tool discovery indexes. */
|
|
631
649
|
summary?: string;
|
|
632
650
|
/**
|