@oh-my-pi/pi-agent-core 16.5.2 → 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 CHANGED
@@ -2,6 +2,21 @@
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
+
5
20
  ## [16.5.2] - 2026-07-14
6
21
 
7
22
  ### 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 | MCPToolSelectionEntry | SessionInitEntry | ModeChangeEntry | CustomCompactionSessionEntries[keyof CustomCompactionSessionEntries];
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;
@@ -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
- /** Built-in tool loading behavior. "essential" loads initially; "discoverable" can be activated by tool search. */
540
- loadMode?: "essential" | "discoverable";
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": "16.5.2",
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": "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",
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,
@@ -803,6 +804,7 @@ async function runLoopBody(
803
804
  // getToolChoice is never advanced twice; the flag resets at the message boundary.
804
805
  let hostToolChoice: ToolChoice | undefined;
805
806
  let softRequiredTool: string | undefined;
807
+ let softSatisfies: SoftToolRequirement["satisfies"];
806
808
  let directiveResolvedForTurn = false;
807
809
 
808
810
  // Outer loop: continues when queued follow-up messages arrive after agent would stop
@@ -861,6 +863,7 @@ async function runLoopBody(
861
863
  const softReq = isSoftToolRequirement(directive) ? directive : undefined;
862
864
  hostToolChoice = directive === undefined || isSoftToolRequirement(directive) ? undefined : directive;
863
865
  softRequiredTool = softReq?.toolName;
866
+ softSatisfies = softReq?.satisfies;
864
867
  if (softReq !== undefined) {
865
868
  if (softReq.id !== softRequirementId) {
866
869
  softRequirementId = softReq.id;
@@ -1024,7 +1027,7 @@ async function runLoopBody(
1024
1027
  const calledOnlyRequiredTool =
1025
1028
  softRequiredTool !== undefined &&
1026
1029
  toolCalls.length > 0 &&
1027
- toolCalls.every(toolCall => toolCall.name === softRequiredTool);
1030
+ toolCalls.every(toolCall => softSatisfies?.(toolCall) ?? toolCall.name === softRequiredTool);
1028
1031
  const softGateActive =
1029
1032
  softRequiredTool !== undefined && !hardToolChoiceBlocks(config.toolChoice, softRequiredTool);
1030
1033
  const softNonCompliant = softGateActive && !calledOnlyRequiredTool;
@@ -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
- /** Built-in tool loading behavior. "essential" loads initially; "discoverable" can be activated by tool search. */
629
- loadMode?: "essential" | "discoverable";
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
  /**