@oh-my-pi/pi-coding-agent 13.12.3 → 13.12.4

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
+ ## [13.12.4] - 2026-03-15
6
+ ### Added
7
+
8
+ - Exposed `settings` instance in `CustomToolContext` for session-specific configuration access
9
+
10
+ ### Changed
11
+
12
+ - Improved artifact spill configuration to use session settings with schema defaults as fallback
13
+ - Refactored type annotations for better type safety in tool result handling
14
+
5
15
  ## [13.12.2] - 2026-03-15
6
16
 
7
17
  ### Added
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "13.12.3",
4
+ "version": "13.12.4",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -41,12 +41,12 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@mozilla/readability": "^0.6",
44
- "@oh-my-pi/omp-stats": "13.12.3",
45
- "@oh-my-pi/pi-agent-core": "13.12.3",
46
- "@oh-my-pi/pi-ai": "13.12.3",
47
- "@oh-my-pi/pi-natives": "13.12.3",
48
- "@oh-my-pi/pi-tui": "13.12.3",
49
- "@oh-my-pi/pi-utils": "13.12.3",
44
+ "@oh-my-pi/omp-stats": "13.12.4",
45
+ "@oh-my-pi/pi-agent-core": "13.12.4",
46
+ "@oh-my-pi/pi-ai": "13.12.4",
47
+ "@oh-my-pi/pi-natives": "13.12.4",
48
+ "@oh-my-pi/pi-tui": "13.12.4",
49
+ "@oh-my-pi/pi-utils": "13.12.4",
50
50
  "@sinclair/typebox": "^0.34",
51
51
  "@xterm/headless": "^6.0",
52
52
  "ajv": "^8.18",
@@ -10,6 +10,7 @@ import type { Component } from "@oh-my-pi/pi-tui";
10
10
  import type { Static, TSchema } from "@sinclair/typebox";
11
11
  import type { Rule } from "../../capability/rule";
12
12
  import type { ModelRegistry } from "../../config/model-registry";
13
+ import type { Settings } from "../../config/settings";
13
14
  import type { ExecOptions, ExecResult } from "../../exec/exec";
14
15
  import type { HookUIContext } from "../../extensibility/hooks/types";
15
16
  import type { Theme } from "../../modes/theme/theme";
@@ -76,6 +77,8 @@ export interface CustomToolContext {
76
77
  hasQueuedMessages(): boolean;
77
78
  /** Abort the current agent operation (fire-and-forget, does not wait) */
78
79
  abort(): void;
80
+ /** Settings instance for the current session. Prefer over the global singleton. */
81
+ settings?: Settings;
79
82
  }
80
83
 
81
84
  /** Session event passed to onSession callback */
package/src/sdk.ts CHANGED
@@ -79,7 +79,7 @@ import {
79
79
  loadProjectContextFiles as loadContextFilesInternal,
80
80
  } from "./system-prompt";
81
81
  import { AgentOutputManager } from "./task/output-manager";
82
- import { resolveThinkingLevelForModel, toReasoningEffort } from "./thinking";
82
+ import { parseThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } from "./thinking";
83
83
  import {
84
84
  BashTool,
85
85
  BUILTIN_TOOLS,
@@ -626,7 +626,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
626
626
  if (!options.modelRegistry) {
627
627
  modelRegistry.refreshInBackground();
628
628
  }
629
- const skillsSettings = settings.getGroup("skills") as SkillsSettings;
629
+ const skillsSettings = settings.getGroup("skills");
630
630
  const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
631
631
  const discoveredSkillsPromise =
632
632
  options.skills === undefined
@@ -711,7 +711,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
711
711
 
712
712
  // If session has data and includes a thinking entry, restore it
713
713
  if (thinkingLevel === undefined && hasExistingSession && hasThinkingEntry) {
714
- thinkingLevel = existingSession.thinkingLevel as ThinkingLevel | undefined;
714
+ thinkingLevel = parseThinkingLevel(existingSession.thinkingLevel);
715
715
  }
716
716
 
717
717
  if (thinkingLevel === undefined && !hasExplicitModel && !hasThinkingEntry && defaultRoleSpec.explicitThinkingLevel) {
@@ -935,7 +935,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
935
935
  // Always filter Exa - we have native integration
936
936
  filterExa: true,
937
937
  // Filter browser MCP servers when builtin browser tool is active
938
- filterBrowser: (settings.get("browser.enabled") as boolean) ?? false,
938
+ filterBrowser: settings.get("browser.enabled") ?? false,
939
939
  cacheStorage: settings.getStorage(),
940
940
  authStorage,
941
941
  }),
@@ -1005,10 +1005,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1005
1005
  extensionsResult = options.preloadedExtensions;
1006
1006
  } else {
1007
1007
  // Merge CLI extension paths with settings extension paths
1008
- const configuredPaths = [
1009
- ...(options.additionalExtensionPaths ?? []),
1010
- ...((settings.get("extensions") as string[]) ?? []),
1011
- ];
1008
+ const configuredPaths = [...(options.additionalExtensionPaths ?? []), ...(settings.get("extensions") ?? [])];
1012
1009
  const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
1013
1010
  extensionsResult = await logger.timeAsync(
1014
1011
  "discoverAndLoadExtensions",
@@ -1118,11 +1115,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1118
1115
  abort: () => {
1119
1116
  session.abort();
1120
1117
  },
1118
+ settings,
1121
1119
  });
1122
1120
  const toolContextStore = new ToolContextStore(getSessionContext);
1123
1121
 
1124
1122
  const registeredTools = extensionRunner?.getAllRegisteredTools() ?? [];
1125
- let wrappedExtensionTools: AgentTool[];
1123
+ let wrappedExtensionTools: Tool[];
1126
1124
 
1127
1125
  if (extensionRunner) {
1128
1126
  // With extension runner: convert CustomTools to ToolDefinitions and wrap all together
@@ -1144,16 +1142,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1144
1142
  isIdle: () => !session?.isStreaming,
1145
1143
  hasQueuedMessages: () => (session?.queuedMessageCount ?? 0) > 0,
1146
1144
  abort: () => session?.abort(),
1145
+ settings,
1147
1146
  });
1148
1147
  wrappedExtensionTools = (options.customTools ?? [])
1149
1148
  .filter(isCustomTool)
1150
- .map(tool => CustomToolAdapter.wrap(tool, customToolContext) as AgentTool);
1149
+ .map(tool => CustomToolAdapter.wrap(tool, customToolContext));
1151
1150
  }
1152
1151
 
1153
1152
  // All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
1154
- const toolRegistry = new Map<string, AgentTool>();
1153
+ const toolRegistry = new Map<string, Tool>();
1155
1154
  for (const tool of builtinTools) {
1156
- toolRegistry.set(tool.name, tool as AgentTool);
1155
+ toolRegistry.set(tool.name, tool);
1157
1156
  }
1158
1157
  for (const tool of wrappedExtensionTools) {
1159
1158
  toolRegistry.set(tool.name, tool);
@@ -1173,7 +1172,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1173
1172
  } else if (!toolRegistry.has("resolve")) {
1174
1173
  const resolveTool = await logger.timeAsync("createTools:resolve:session", HIDDEN_TOOLS.resolve, toolSession);
1175
1174
  if (resolveTool) {
1176
- toolRegistry.set(resolveTool.name, wrapToolWithMetaNotice(resolveTool) as AgentTool);
1175
+ toolRegistry.set(resolveTool.name, wrapToolWithMetaNotice(resolveTool));
1177
1176
  }
1178
1177
  }
1179
1178
 
@@ -1218,7 +1217,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1218
1217
  tools,
1219
1218
  toolNames,
1220
1219
  rules: rulebookRules,
1221
- skillsSettings: settings.getGroup("skills") as SkillsSettings,
1220
+ skillsSettings: settings.getGroup("skills"),
1222
1221
  appendSystemPrompt: appendPrompt,
1223
1222
  repeatToolDescriptions,
1224
1223
  eagerTasks,
@@ -1236,7 +1235,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1236
1235
  tools,
1237
1236
  toolNames,
1238
1237
  rules: rulebookRules,
1239
- skillsSettings: settings.getGroup("skills") as SkillsSettings,
1238
+ skillsSettings: settings.getGroup("skills"),
1240
1239
  customPrompt: options.systemPrompt,
1241
1240
  appendSystemPrompt: appendPrompt,
1242
1241
  repeatToolDescriptions,
@@ -1297,17 +1296,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1297
1296
  if (hasImages) {
1298
1297
  const filteredContent = content
1299
1298
  .map(c => (c.type === "image" ? { type: "text" as const, text: "Image reading is disabled." } : c))
1300
- .filter(
1301
- (c, i, arr) =>
1302
- // Dedupe consecutive "Image reading is disabled." texts
1303
- !(
1304
- c.type === "text" &&
1305
- c.text === "Image reading is disabled." &&
1306
- i > 0 &&
1307
- arr[i - 1].type === "text" &&
1308
- (arr[i - 1] as { type: "text"; text: string }).text === "Image reading is disabled."
1309
- ),
1310
- );
1299
+ .filter((c, i, arr) => {
1300
+ // Dedupe consecutive "Image reading is disabled." texts
1301
+ if (!(c.type === "text" && c.text === "Image reading is disabled." && i > 0)) return true;
1302
+ const prev = arr[i - 1];
1303
+ return !(prev.type === "text" && prev.text === "Image reading is disabled.");
1304
+ });
1311
1305
  return { ...msg, content: filteredContent };
1312
1306
  }
1313
1307
  }
@@ -1439,7 +1433,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1439
1433
  customCommands: customCommandsResult.commands,
1440
1434
  skills,
1441
1435
  skillWarnings,
1442
- skillsSettings: settings.getGroup("skills") as Required<SkillsSettings>,
1436
+ skillsSettings: settings.getGroup("skills"),
1443
1437
  modelRegistry,
1444
1438
  toolRegistry,
1445
1439
  transformContext,
@@ -1514,7 +1508,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1514
1508
  mcpManager.setOnResourcesChanged((serverName, uri) => {
1515
1509
  logger.debug("MCP resources changed", { path: `mcp:${serverName}`, uri });
1516
1510
  if (!settings.get("mcp.notifications")) return;
1517
- const debounceMs = (settings.get("mcp.notificationDebounceMs") as number) ?? 500;
1511
+ const debounceMs = settings.get("mcp.notificationDebounceMs");
1518
1512
  const key = `${serverName}:${uri}`;
1519
1513
  const existing = notificationDebounceTimers.get(key);
1520
1514
  if (existing) clearTimeout(existing);
@@ -191,7 +191,7 @@ export interface AgentSessionConfig {
191
191
  skillWarnings?: SkillWarning[];
192
192
  /** Custom commands (TypeScript slash commands) */
193
193
  customCommands?: LoadedCustomCommand[];
194
- skillsSettings?: Required<SkillsSettings>;
194
+ skillsSettings?: SkillsSettings;
195
195
  /** Model registry for API key resolution and model discovery */
196
196
  modelRegistry: ModelRegistry;
197
197
  /** Tool registry for LSP and settings */
@@ -383,7 +383,7 @@ export class AgentSession {
383
383
  /** MCP prompt commands (updated dynamically when prompts are loaded) */
384
384
  #mcpPromptCommands: LoadedCustomCommand[] = [];
385
385
 
386
- #skillsSettings: Required<SkillsSettings> | undefined;
386
+ #skillsSettings: SkillsSettings | undefined;
387
387
 
388
388
  // Model registry for API key resolution
389
389
  #modelRegistry: ModelRegistry;
@@ -2518,7 +2518,7 @@ export class AgentSession {
2518
2518
  return undefined;
2519
2519
  }
2520
2520
 
2521
- get skillsSettings(): Required<SkillsSettings> | undefined {
2521
+ get skillsSettings(): SkillsSettings | undefined {
2522
2522
  return this.#skillsSettings;
2523
2523
  }
2524
2524
 
@@ -12,7 +12,7 @@ import type {
12
12
  AgentToolUpdateCallback,
13
13
  } from "@oh-my-pi/pi-agent-core";
14
14
  import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
15
- import { settings } from "../config/settings";
15
+ import { getDefault, type Settings } from "../config/settings";
16
16
  import { formatGroupedDiagnosticMessages } from "../lsp/utils";
17
17
  import type { Theme } from "../modes/theme/theme";
18
18
  import { type OutputSummary, type TruncationResult, truncateTail } from "../session/streaming-output";
@@ -415,16 +415,17 @@ export function formatStyledTruncationWarning(meta: OutputMeta | undefined, them
415
415
  * Append output notice to tool result content if meta is present.
416
416
  */
417
417
  function appendOutputNotice(
418
- content: Array<{ type: string; text?: string }>,
418
+ content: (TextContent | ImageContent)[],
419
419
  meta: OutputMeta | undefined,
420
- ): Array<{ type: string; text?: string }> {
420
+ ): (TextContent | ImageContent)[] {
421
421
  const notice = formatOutputNotice(meta);
422
422
  if (!notice) return content;
423
423
 
424
424
  const result = [...content];
425
425
  for (let i = result.length - 1; i >= 0; i--) {
426
- if (result[i].type === "text" && result[i].text != null) {
427
- result[i] = { ...result[i], text: result[i].text + notice };
426
+ const item = result[i];
427
+ if (item.type === "text") {
428
+ result[i] = { ...item, text: item.text + notice };
428
429
  return result;
429
430
  }
430
431
  }
@@ -439,19 +440,16 @@ const kUnwrappedExecute = Symbol("OutputMeta.UnwrappedExecute");
439
440
  // Centralized artifact spill for large tool results
440
441
  // =============================================================================
441
442
 
442
- /** Artifact spill threshold tool output above this size is saved as an artifact. */
443
- function getArtifactSpillThreshold(): number {
444
- return settings.get("tools.artifactSpillThreshold") * 1024;
445
- }
446
-
447
- /** When spilling, keep this many bytes of tail in the result sent to the LLM. */
448
- function getArtifactTailBytes(): number {
449
- return settings.get("tools.artifactTailBytes") * 1024;
450
- }
451
-
452
- /** When spilling, keep at most this many lines of tail. */
453
- function getArtifactTailLines(): number {
454
- return settings.get("tools.artifactTailLines");
443
+ /** Resolved artifact spill config sourced from the session settings (or schema defaults). */
444
+ function getSpillConfig(s: Settings | undefined) {
445
+ const get = <P extends "tools.artifactSpillThreshold" | "tools.artifactTailBytes" | "tools.artifactTailLines">(
446
+ path: P,
447
+ ) => s?.get(path) ?? getDefault(path);
448
+ return {
449
+ threshold: get("tools.artifactSpillThreshold") * 1024,
450
+ tailBytes: get("tools.artifactTailBytes") * 1024,
451
+ tailLines: get("tools.artifactTailLines"),
452
+ };
455
453
  }
456
454
 
457
455
  /**
@@ -467,9 +465,10 @@ async function spillLargeResultToArtifact(
467
465
  ): Promise<AgentToolResult> {
468
466
  const sessionManager = context?.sessionManager;
469
467
  if (!sessionManager) return result;
468
+ const { threshold, tailBytes, tailLines } = getSpillConfig(context?.settings);
470
469
 
471
470
  // Skip if tool already saved an artifact
472
- const existingMeta = (result.details as { meta?: OutputMeta } | undefined)?.meta;
471
+ const existingMeta: OutputMeta | undefined = result.details?.meta;
473
472
  if (existingMeta?.truncation?.artifactId) return result;
474
473
 
475
474
  // Measure total text content
@@ -483,7 +482,7 @@ async function spillLargeResultToArtifact(
483
482
 
484
483
  const fullText = textParts.length === 1 ? textParts[0] : textParts.join("\n");
485
484
  const totalBytes = Buffer.byteLength(fullText, "utf-8");
486
- if (totalBytes <= getArtifactSpillThreshold()) return result;
485
+ if (totalBytes <= threshold) return result;
487
486
 
488
487
  // Save full output as artifact
489
488
  const artifactId = await sessionManager.saveArtifact(fullText, toolName);
@@ -491,8 +490,8 @@ async function spillLargeResultToArtifact(
491
490
 
492
491
  // Truncate to tail
493
492
  const truncated = truncateTail(fullText, {
494
- maxBytes: getArtifactTailBytes(),
495
- maxLines: getArtifactTailLines(),
493
+ maxBytes: tailBytes,
494
+ maxLines: tailLines,
496
495
  });
497
496
 
498
497
  // Replace text blocks with single tail-truncated block, keep images
@@ -515,7 +514,7 @@ async function spillLargeResultToArtifact(
515
514
  totalBytes: truncated.totalBytes,
516
515
  outputLines,
517
516
  outputBytes,
518
- maxBytes: getArtifactTailBytes(),
517
+ maxBytes: tailBytes,
519
518
  shownRange: { start: shownStart, end: truncated.totalLines },
520
519
  artifactId,
521
520
  };
@@ -547,11 +546,11 @@ async function wrappedExecute(
547
546
  result = await spillLargeResultToArtifact(result, this.name, context);
548
547
 
549
548
  // Append notices from meta
550
- const meta = (result.details as { meta?: OutputMeta } | undefined)?.meta;
549
+ const meta: OutputMeta | undefined = result.details?.meta;
551
550
  if (meta) {
552
551
  return {
553
552
  ...result,
554
- content: appendOutputNotice(result.content, meta) as (TextContent | ImageContent)[],
553
+ content: appendOutputNotice(result.content, meta),
555
554
  };
556
555
  }
557
556
  return result;