@oh-my-pi/pi-coding-agent 13.3.0 → 13.3.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,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.3.1] - 2026-02-26
6
+
7
+ ### Added
8
+
9
+ - Added `topP` setting to control nucleus sampling cutoff for model output diversity
10
+ - Added `topK` setting to sample from top-K tokens for controlled generation
11
+ - Added `minP` setting to enforce minimum probability threshold for token selection
12
+ - Added `presencePenalty` setting to penalize introduction of already-present tokens
13
+ - Added `repetitionPenalty` setting to penalize repeated tokens in model output
14
+
15
+ ### Fixed
16
+
17
+ - Fixed skill discovery to continue loading project skills when user skills directory is missing
18
+
5
19
  ## [13.3.0] - 2026-02-26
6
20
 
7
21
  ### Breaking Changes
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.3.0",
4
+ "version": "13.3.2",
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.3.0",
45
- "@oh-my-pi/pi-agent-core": "13.3.0",
46
- "@oh-my-pi/pi-ai": "13.3.0",
47
- "@oh-my-pi/pi-natives": "13.3.0",
48
- "@oh-my-pi/pi-tui": "13.3.0",
49
- "@oh-my-pi/pi-utils": "13.3.0",
44
+ "@oh-my-pi/omp-stats": "13.3.2",
45
+ "@oh-my-pi/pi-agent-core": "13.3.2",
46
+ "@oh-my-pi/pi-ai": "13.3.2",
47
+ "@oh-my-pi/pi-natives": "13.3.2",
48
+ "@oh-my-pi/pi-tui": "13.3.2",
49
+ "@oh-my-pi/pi-utils": "13.3.2",
50
50
  "@sinclair/typebox": "^0.34",
51
51
  "@xterm/headless": "^6.0",
52
52
  "ajv": "^8.18",
@@ -2,7 +2,7 @@
2
2
  * Config CLI command handlers.
3
3
  *
4
4
  * Handles `omp config <command>` subcommands for managing settings.
5
- * Uses settings-defs as the source of truth for available settings.
5
+ * Uses the settings schema as the source of truth for available settings.
6
6
  */
7
7
 
8
8
  import { APP_NAME, getAgentDir } from "@oh-my-pi/pi-utils";
@@ -11,12 +11,13 @@ import {
11
11
  getDefault,
12
12
  getEnumValues,
13
13
  getType,
14
+ getUi,
14
15
  type SettingPath,
15
16
  Settings,
16
17
  type SettingValue,
17
18
  settings,
18
19
  } from "../config/settings";
19
- import { getAllSettingDefs, type SettingDef } from "../modes/components/settings-defs";
20
+ import { SETTINGS_SCHEMA } from "../config/settings-schema";
20
21
  import { theme } from "../modes/theme/theme";
21
22
 
22
23
  // =============================================================================
@@ -38,21 +39,32 @@ export interface ConfigCommandArgs {
38
39
  // Setting Filtering
39
40
  // =============================================================================
40
41
 
42
+ type CliSettingDef = {
43
+ path: SettingPath;
44
+ type: string;
45
+ description: string;
46
+ tab: string;
47
+ };
48
+
49
+ const ALL_SETTING_PATHS = Object.keys(SETTINGS_SCHEMA) as SettingPath[];
50
+
41
51
  /** Find setting definition by path */
42
- function findSettingDef(path: string): SettingDef | undefined {
43
- return getAllSettingDefs().find(def => def.path === path);
52
+ function findSettingDef(path: string): CliSettingDef | undefined {
53
+ if (!(path in SETTINGS_SCHEMA)) return undefined;
54
+ const key = path as SettingPath;
55
+ const ui = getUi(key);
56
+ return {
57
+ path: key,
58
+ type: getType(key),
59
+ description: ui?.description ?? "",
60
+ tab: ui?.tab ?? "internal",
61
+ };
44
62
  }
45
63
 
46
64
  /** Get available values for a setting */
47
- function getSettingValues(def: SettingDef): readonly string[] | undefined {
65
+ function getSettingValues(def: CliSettingDef): readonly string[] | undefined {
48
66
  if (def.type === "enum") {
49
- return def.values;
50
- }
51
- if (def.type === "submenu") {
52
- const options = def.options;
53
- if (options.length > 0) {
54
- return options.map(o => o.value);
55
- }
67
+ return getEnumValues(def.path);
56
68
  }
57
69
  return undefined;
58
70
  }
@@ -125,7 +137,7 @@ function formatValue(value: unknown): string {
125
137
  return chalk.yellow(String(value));
126
138
  }
127
139
 
128
- function getTypeDisplay(def: SettingDef): string {
140
+ function getTypeDisplay(def: CliSettingDef): string {
129
141
  if (def.type === "boolean") {
130
142
  return "(boolean)";
131
143
  }
@@ -199,13 +211,13 @@ export async function runConfigCommand(cmd: ConfigCommandArgs): Promise<void> {
199
211
  }
200
212
 
201
213
  function handleList(flags: { json?: boolean }): void {
202
- const defs = getAllSettingDefs();
214
+ const defs = ALL_SETTING_PATHS.map(path => findSettingDef(path)).filter((def): def is CliSettingDef => !!def);
203
215
 
204
216
  if (flags.json) {
205
217
  const result: Record<string, { value: unknown; type: string; description: string }> = {};
206
218
  for (const def of defs) {
207
219
  result[def.path] = {
208
- value: settings.get(def.path as SettingPath),
220
+ value: settings.get(def.path),
209
221
  type: def.type,
210
222
  description: def.description,
211
223
  };
@@ -216,7 +228,7 @@ function handleList(flags: { json?: boolean }): void {
216
228
 
217
229
  console.log(chalk.bold("Settings:\n"));
218
230
 
219
- const groups: Record<string, SettingDef[]> = {};
231
+ const groups: Record<string, CliSettingDef[]> = {};
220
232
  for (const def of defs) {
221
233
  if (!groups[def.tab]) {
222
234
  groups[def.tab] = [];
@@ -233,7 +245,7 @@ function handleList(flags: { json?: boolean }): void {
233
245
  for (const group of sortedGroups) {
234
246
  console.log(chalk.bold.blue(`[${group}]`));
235
247
  for (const def of groups[group]) {
236
- const value = settings.get(def.path as SettingPath);
248
+ const value = settings.get(def.path);
237
249
  const valueStr = formatValue(value);
238
250
  const typeStr = getTypeDisplay(def);
239
251
  console.log(` ${chalk.white(def.path)} = ${valueStr} ${chalk.dim(typeStr)}`);
@@ -256,7 +268,7 @@ function handleGet(key: string | undefined, flags: { json?: boolean }): void {
256
268
  process.exit(1);
257
269
  }
258
270
 
259
- const value = settings.get(def.path as SettingPath);
271
+ const value = settings.get(def.path);
260
272
 
261
273
  if (flags.json) {
262
274
  console.log(JSON.stringify({ key: def.path, value, type: def.type, description: def.description }, null, 2));
@@ -281,13 +293,13 @@ async function handleSet(key: string | undefined, value: string | undefined, fla
281
293
  }
282
294
 
283
295
  try {
284
- parseAndSetValue(def.path as SettingPath, value);
296
+ parseAndSetValue(def.path, value);
285
297
  } catch (err) {
286
298
  console.error(chalk.red(String(err)));
287
299
  process.exit(1);
288
300
  }
289
301
 
290
- const newValue = settings.get(def.path as SettingPath);
302
+ const newValue = settings.get(def.path);
291
303
 
292
304
  if (flags.json) {
293
305
  console.log(JSON.stringify({ key: def.path, value: newValue }));
@@ -197,16 +197,6 @@ export const SETTINGS_SCHEMA = {
197
197
  submenu: true,
198
198
  },
199
199
  },
200
- temperature: {
201
- type: "number",
202
- default: -1,
203
- ui: {
204
- tab: "agent",
205
- label: "Temperature",
206
- description: "Sampling temperature (0 = deterministic, 1 = creative, -1 = provider default)",
207
- submenu: true,
208
- },
209
- },
210
200
  hideThinkingBlock: {
211
201
  type: "boolean",
212
202
  default: false,
@@ -1048,6 +1038,66 @@ export const SETTINGS_SCHEMA = {
1048
1038
  "statusLine.leftSegments": { type: "array", default: [] as StatusLineSegmentId[] },
1049
1039
  "statusLine.rightSegments": { type: "array", default: [] as StatusLineSegmentId[] },
1050
1040
  "statusLine.segmentOptions": { type: "record", default: {} as Record<string, unknown> },
1041
+ temperature: {
1042
+ type: "number",
1043
+ default: -1,
1044
+ ui: {
1045
+ tab: "agent",
1046
+ label: "Temperature",
1047
+ description: "Sampling temperature (0 = deterministic, 1 = creative, -1 = provider default)",
1048
+ submenu: true,
1049
+ },
1050
+ },
1051
+ topP: {
1052
+ type: "number",
1053
+ default: -1,
1054
+ ui: {
1055
+ tab: "agent",
1056
+ label: "Top P",
1057
+ description: "Nucleus sampling cutoff (0-1, -1 = provider default)",
1058
+ submenu: true,
1059
+ },
1060
+ },
1061
+ topK: {
1062
+ type: "number",
1063
+ default: -1,
1064
+ ui: {
1065
+ tab: "agent",
1066
+ label: "Top K",
1067
+ description: "Sample from top-K tokens (-1 = provider default)",
1068
+ submenu: true,
1069
+ },
1070
+ },
1071
+ minP: {
1072
+ type: "number",
1073
+ default: -1,
1074
+ ui: {
1075
+ tab: "agent",
1076
+ label: "Min P",
1077
+ description: "Minimum probability threshold (0-1, -1 = provider default)",
1078
+ submenu: true,
1079
+ },
1080
+ },
1081
+ presencePenalty: {
1082
+ type: "number",
1083
+ default: -1,
1084
+ ui: {
1085
+ tab: "agent",
1086
+ label: "Presence penalty",
1087
+ description: "Penalty for introducing already-present tokens (-1 = provider default)",
1088
+ submenu: true,
1089
+ },
1090
+ },
1091
+ repetitionPenalty: {
1092
+ type: "number",
1093
+ default: -1,
1094
+ ui: {
1095
+ tab: "agent",
1096
+ label: "Repetition penalty",
1097
+ description: "Penalty for repeated tokens (-1 = provider default)",
1098
+ submenu: true,
1099
+ },
1100
+ },
1051
1101
  } as const;
1052
1102
 
1053
1103
  // ═══════════════════════════════════════════════════════════════════════════
@@ -284,8 +284,15 @@ export async function scanSkillsFromDir(
284
284
  const warnings: string[] = [];
285
285
  const { dir, level, providerId, requireDescription = false } = options;
286
286
 
287
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
288
-
287
+ let entries: fs.Dirent[];
288
+ try {
289
+ entries = await fs.promises.readdir(dir, { withFileTypes: true });
290
+ } catch (error) {
291
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
292
+ warnings.push(`Failed to read skills directory: ${dir} (${String(error)})`);
293
+ }
294
+ return { items, warnings };
295
+ }
289
296
  const loadSkill = async (skillPath: string) => {
290
297
  try {
291
298
  const content = await readFile(skillPath);
@@ -214,6 +214,42 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
214
214
  { value: "0.7", label: "0.7", description: "Creative" },
215
215
  { value: "1", label: "1", description: "Maximum variety" },
216
216
  ],
217
+ topP: [
218
+ { value: "-1", label: "Default", description: "Use provider default" },
219
+ { value: "0.1", label: "0.1", description: "Very focused" },
220
+ { value: "0.3", label: "0.3", description: "Focused" },
221
+ { value: "0.5", label: "0.5", description: "Balanced" },
222
+ { value: "0.9", label: "0.9", description: "Broad" },
223
+ { value: "1", label: "1", description: "No nucleus filtering" },
224
+ ],
225
+ topK: [
226
+ { value: "-1", label: "Default", description: "Use provider default" },
227
+ { value: "1", label: "1", description: "Greedy top token" },
228
+ { value: "20", label: "20", description: "Focused" },
229
+ { value: "40", label: "40", description: "Balanced" },
230
+ { value: "100", label: "100", description: "Broad" },
231
+ ],
232
+ minP: [
233
+ { value: "-1", label: "Default", description: "Use provider default" },
234
+ { value: "0.01", label: "0.01", description: "Very permissive" },
235
+ { value: "0.05", label: "0.05", description: "Balanced" },
236
+ { value: "0.1", label: "0.1", description: "Strict" },
237
+ ],
238
+ presencePenalty: [
239
+ { value: "-1", label: "Default", description: "Use provider default" },
240
+ { value: "0", label: "0", description: "No penalty" },
241
+ { value: "0.5", label: "0.5", description: "Mild novelty" },
242
+ { value: "1", label: "1", description: "Encourage novelty" },
243
+ { value: "2", label: "2", description: "Strong novelty" },
244
+ ],
245
+ repetitionPenalty: [
246
+ { value: "-1", label: "Default", description: "Use provider default" },
247
+ { value: "0.8", label: "0.8", description: "Allow repetition" },
248
+ { value: "1", label: "1", description: "No penalty" },
249
+ { value: "1.1", label: "1.1", description: "Mild penalty" },
250
+ { value: "1.2", label: "1.2", description: "Balanced" },
251
+ { value: "1.5", label: "1.5", description: "Strong penalty" },
252
+ ],
217
253
  // Symbol preset
218
254
  symbolPreset: [
219
255
  { value: "unicode", label: "Unicode", description: "Standard symbols (default)" },
@@ -288,6 +288,31 @@ export class SelectorController {
288
288
  this.ctx.session.agent.temperature = temp >= 0 ? temp : undefined;
289
289
  break;
290
290
  }
291
+ case "topP": {
292
+ const topP = typeof value === "number" ? value : Number(value);
293
+ this.ctx.session.agent.topP = topP >= 0 ? topP : undefined;
294
+ break;
295
+ }
296
+ case "topK": {
297
+ const topK = typeof value === "number" ? value : Number(value);
298
+ this.ctx.session.agent.topK = topK >= 0 ? topK : undefined;
299
+ break;
300
+ }
301
+ case "minP": {
302
+ const minP = typeof value === "number" ? value : Number(value);
303
+ this.ctx.session.agent.minP = minP >= 0 ? minP : undefined;
304
+ break;
305
+ }
306
+ case "presencePenalty": {
307
+ const presencePenalty = typeof value === "number" ? value : Number(value);
308
+ this.ctx.session.agent.presencePenalty = presencePenalty >= 0 ? presencePenalty : undefined;
309
+ break;
310
+ }
311
+ case "repetitionPenalty": {
312
+ const repetitionPenalty = typeof value === "number" ? value : Number(value);
313
+ this.ctx.session.agent.repetitionPenalty = repetitionPenalty >= 0 ? repetitionPenalty : undefined;
314
+ break;
315
+ }
291
316
  case "statusLinePreset":
292
317
  case "statusLineSeparator":
293
318
  case "statusLineShowHooks":
@@ -0,0 +1,138 @@
1
+ {
2
+ "name": "dark-poimandres",
3
+ "vars": {
4
+ "poimandresBg": "#1b1e28",
5
+ "poimandresFocus": "#303340",
6
+ "poimandresGray": "#a6accd",
7
+ "poimandresDarkerGray": "#767c9d",
8
+ "poimandresBluishGray": "#506477",
9
+ "poimandresOffWhite": "#e4f0fb",
10
+ "poimandresBrightMint": "#5DE4c7",
11
+ "poimandresStrongTurquoise": "#00CED1",
12
+ "poimandresLowerMint": "#5fb3a1",
13
+ "poimandresLightBlue": "#ADD7FF",
14
+ "poimandresLowerBlue": "#89ddff",
15
+ "poimandresDesaturatedGreen": "#7AC6B6",
16
+ "poimandresHotRed": "#d0679d",
17
+ "poimandresPink": "#f087bd",
18
+ "poimandresBrightYellow": "#fffac2",
19
+ "poimandresBluishGrayBrighter": "#7390AA",
20
+ "poimandresSelection": "#717cb425",
21
+ "poimandresWhite": "#ffffff",
22
+ "poimandresBlack": "#000000",
23
+ "poimandresBg256": 235,
24
+ "poimandresFocus256": 238,
25
+ "poimandresGray256": 109,
26
+ "poimandresDarkerGray256": 103,
27
+ "poimandresBluishGray256": 66,
28
+ "poimandresBrightMint256": 86,
29
+ "poimandresStrongTurquoise256": 44,
30
+ "poimandresLowerMint256": 79,
31
+ "poimandresLightBlue256": 153,
32
+ "poimandresLowerBlue256": 117,
33
+ "poimandresDesaturatedGreen256": 79,
34
+ "poimandresHotRed256": 169,
35
+ "poimandresPink256": 211,
36
+ "poimandresBrightYellow256": 229,
37
+ "poimandresBluishGrayBrighter256": 110,
38
+ "poimandresSubtleBg": "#1b1e28",
39
+ "poimandresMediumBg": "#23273a",
40
+ "poimandresBrightBg": "#303340"
41
+ },
42
+ "colors": {
43
+ "accent": "poimandresStrongTurquoise",
44
+ "border": "poimandresBluishGray",
45
+ "borderAccent": "poimandresDarkerGray",
46
+ "borderMuted": "poimandresBluishGray",
47
+ "success": "poimandresStrongTurquoise",
48
+ "error": "poimandresHotRed",
49
+ "warning": "poimandresBrightYellow",
50
+ "muted": "poimandresDarkerGray",
51
+ "dim": "poimandresBluishGray",
52
+ "text": "poimandresGray",
53
+ "thinkingText": "poimandresDarkerGray",
54
+ "selectedBg": "poimandresSelection",
55
+ "userMessageBg": "poimandresFocus",
56
+ "userMessageText": "poimandresWhite",
57
+ "customMessageBg": "poimandresFocus",
58
+ "customMessageText": "poimandresWhite",
59
+ "customMessageLabel": "poimandresStrongTurquoise",
60
+ "toolPendingBg": "poimandresFocus",
61
+ "toolSuccessBg": "poimandresMediumBg",
62
+ "toolErrorBg": "poimandresFocus",
63
+ "toolTitle": "poimandresBrightMint",
64
+ "toolOutput": "poimandresDarkerGray",
65
+ "mdHeading": "poimandresBrightMint",
66
+ "mdLink": "poimandresDesaturatedGreen",
67
+ "mdLinkUrl": "poimandresStrongTurquoise",
68
+ "mdCode": "poimandresStrongTurquoise",
69
+ "mdCodeBlock": "poimandresGray",
70
+ "mdCodeBlockBorder": "poimandresBluishGray",
71
+ "mdQuote": "poimandresDarkerGray",
72
+ "mdQuoteBorder": "poimandresBluishGray",
73
+ "mdHr": "poimandresDarkerGray",
74
+ "mdListBullet": "poimandresBrightMint",
75
+ "toolDiffAdded": "poimandresStrongTurquoise",
76
+ "toolDiffRemoved": "poimandresHotRed",
77
+ "toolDiffContext": "poimandresDarkerGray",
78
+ "syntaxComment": "poimandresDarkerGray",
79
+ "syntaxKeyword": "poimandresDesaturatedGreen",
80
+ "syntaxFunction": "poimandresBrightMint",
81
+ "syntaxVariable": "poimandresStrongTurquoise",
82
+ "syntaxString": "poimandresStrongTurquoise",
83
+ "syntaxNumber": "poimandresPink",
84
+ "syntaxType": "poimandresStrongTurquoise",
85
+ "syntaxOperator": "poimandresDesaturatedGreen",
86
+ "syntaxPunctuation": "poimandresGray",
87
+ "thinkingOff": "poimandresBluishGray",
88
+ "thinkingMinimal": "poimandresDarkerGray",
89
+ "thinkingLow": "poimandresBrightMint",
90
+ "thinkingMedium": "poimandresStrongTurquoise",
91
+ "thinkingHigh": "poimandresPink",
92
+ "thinkingXhigh": "poimandresHotRed",
93
+ "bashMode": "poimandresDesaturatedGreen",
94
+ "pythonMode": "poimandresStrongTurquoise",
95
+ "statusLineBg": "poimandresFocus",
96
+ "statusLineSep": 66,
97
+ "statusLineModel": "poimandresBrightMint",
98
+ "statusLinePath": "poimandresGray",
99
+ "statusLineGitClean": "poimandresStrongTurquoise",
100
+ "statusLineGitDirty": "poimandresHotRed",
101
+ "statusLineContext": "poimandresDarkerGray",
102
+ "statusLineSpend": "poimandresLowerMint",
103
+ "statusLineStaged": 44,
104
+ "statusLineDirty": 169,
105
+ "statusLineUntracked": 229,
106
+ "statusLineOutput": "poimandresLowerBlue",
107
+ "statusLineCost": "poimandresPink",
108
+ "statusLineSubagents": "poimandresDesaturatedGreen"
109
+ },
110
+ "export": {
111
+ "pageBg": "poimandresBg",
112
+ "cardBg": "poimandresFocus",
113
+ "infoBg": "poimandresFocus"
114
+ },
115
+ "symbols": {
116
+ "preset": "nerd",
117
+ "overrides": {
118
+ "status.success": "●",
119
+ "status.error": "✖",
120
+ "status.warning": "◆",
121
+ "status.info": "○",
122
+ "status.pending": "◌",
123
+ "nav.cursor": "▸",
124
+ "nav.selected": "▴",
125
+ "thinking.minimal": "◌",
126
+ "thinking.low": "◍",
127
+ "thinking.medium": "◎",
128
+ "thinking.high": "◉",
129
+ "thinking.xhigh": "●",
130
+ "icon.model": "◇",
131
+ "icon.plan": "◈",
132
+ "icon.folder": "▸",
133
+ "icon.pi": "π",
134
+ "format.bullet": "◦",
135
+ "md.bullet": "◦"
136
+ }
137
+ }
138
+ }
@@ -27,6 +27,7 @@ import dark_nebula from "./dark-nebula.json" with { type: "json" };
27
27
  import dark_nord from "./dark-nord.json" with { type: "json" };
28
28
  import dark_ocean from "./dark-ocean.json" with { type: "json" };
29
29
  import dark_one from "./dark-one.json" with { type: "json" };
30
+ import dark_poimandres from "./dark-poimandres.json" with { type: "json" };
30
31
  import dark_rainforest from "./dark-rainforest.json" with { type: "json" };
31
32
  import dark_reef from "./dark-reef.json" with { type: "json" };
32
33
  import dark_retro from "./dark-retro.json" with { type: "json" };
@@ -73,6 +74,7 @@ import light_one from "./light-one.json" with { type: "json" };
73
74
  import light_opal from "./light-opal.json" with { type: "json" };
74
75
  import light_orchard from "./light-orchard.json" with { type: "json" };
75
76
  import light_paper from "./light-paper.json" with { type: "json" };
77
+ import light_poimandres from "./light-poimandres.json" with { type: "json" };
76
78
  import light_prism from "./light-prism.json" with { type: "json" };
77
79
  import light_retro from "./light-retro.json" with { type: "json" };
78
80
  import light_sand from "./light-sand.json" with { type: "json" };
@@ -125,6 +127,7 @@ export const defaultThemes = {
125
127
  "dark-nord": dark_nord,
126
128
  "dark-ocean": dark_ocean,
127
129
  "dark-one": dark_one,
130
+ "dark-poimandres": dark_poimandres,
128
131
  "dark-rainforest": dark_rainforest,
129
132
  "dark-reef": dark_reef,
130
133
  "dark-retro": dark_retro,
@@ -171,6 +174,7 @@ export const defaultThemes = {
171
174
  "light-opal": light_opal,
172
175
  "light-orchard": light_orchard,
173
176
  "light-paper": light_paper,
177
+ "light-poimandres": light_poimandres,
174
178
  "light-prism": light_prism,
175
179
  "light-retro": light_retro,
176
180
  "light-sand": light_sand,
@@ -0,0 +1,138 @@
1
+ {
2
+ "name": "light-poimandres",
3
+ "vars": {
4
+ "poimandresBg": "#e4f0fb",
5
+ "poimandresFocus": "#303340",
6
+ "poimandresGray": "#a6accd",
7
+ "poimandresDarkerGray": "#767c9d",
8
+ "poimandresBluishGray": "#506477",
9
+ "poimandresOffWhite": "#e4f0fb",
10
+ "poimandresBrightMint": "#5DE4c7",
11
+ "poimandresStrongTurquoise": "#00CED1",
12
+ "poimandresLowerMint": "#5fb3a1",
13
+ "poimandresLightBlue": "#ADD7FF",
14
+ "poimandresLowerBlue": "#89ddff",
15
+ "poimandresDesaturatedGreen": "#7AC6B6",
16
+ "poimandresHotRed": "#d0679d",
17
+ "poimandresPink": "#f087bd",
18
+ "poimandresBrightYellow": "#fffac2",
19
+ "poimandresBluishGrayBrighter": "#7390AA",
20
+ "poimandresSelection": "#717cb425",
21
+ "poimandresWhite": "#ffffff",
22
+ "poimandresBlack": "#000000",
23
+ "poimandresBg256": 235,
24
+ "poimandresFocus256": 238,
25
+ "poimandresGray256": 109,
26
+ "poimandresDarkerGray256": 103,
27
+ "poimandresBluishGray256": 66,
28
+ "poimandresBrightMint256": 86,
29
+ "poimandresStrongTurquoise256": 44,
30
+ "poimandresLowerMint256": 79,
31
+ "poimandresLightBlue256": 153,
32
+ "poimandresLowerBlue256": 117,
33
+ "poimandresDesaturatedGreen256": 79,
34
+ "poimandresHotRed256": 169,
35
+ "poimandresPink256": 211,
36
+ "poimandresBrightYellow256": 229,
37
+ "poimandresBluishGrayBrighter256": 110,
38
+ "poimandresSubtleBg": "#e4f0fb",
39
+ "poimandresMediumBg": "#d0d7e5",
40
+ "poimandresBrightBg": "#7390AA"
41
+ },
42
+ "colors": {
43
+ "accent": "poimandresStrongTurquoise",
44
+ "border": "poimandresDarkerGray",
45
+ "borderAccent": "poimandresBluishGray",
46
+ "borderMuted": "poimandresDarkerGray",
47
+ "success": "poimandresStrongTurquoise",
48
+ "error": "poimandresHotRed",
49
+ "warning": "poimandresBrightYellow",
50
+ "muted": "poimandresBluishGray",
51
+ "dim": "poimandresDarkerGray",
52
+ "text": "poimandresBluishGray",
53
+ "thinkingText": "poimandresBluishGray",
54
+ "selectedBg": "poimandresSelection",
55
+ "userMessageBg": "poimandresBluishGrayBrighter",
56
+ "userMessageText": "poimandresWhite",
57
+ "customMessageBg": "poimandresGray",
58
+ "customMessageText": "poimandresWhite",
59
+ "customMessageLabel": "poimandresStrongTurquoise",
60
+ "toolPendingBg": "poimandresBluishGrayBrighter",
61
+ "toolSuccessBg": "poimandresBluishGrayBrighter",
62
+ "toolErrorBg": "poimandresBluishGrayBrighter",
63
+ "toolTitle": "poimandresBrightMint",
64
+ "toolOutput": "poimandresBluishGray",
65
+ "mdHeading": "poimandresBrightMint",
66
+ "mdLink": "poimandresDesaturatedGreen",
67
+ "mdLinkUrl": "poimandresStrongTurquoise",
68
+ "mdCode": "poimandresStrongTurquoise",
69
+ "mdCodeBlock": "poimandresBluishGray",
70
+ "mdCodeBlockBorder": "poimandresGray",
71
+ "mdQuote": "poimandresDarkerGray",
72
+ "mdQuoteBorder": "poimandresBluishGray",
73
+ "mdHr": "poimandresDarkerGray",
74
+ "mdListBullet": "poimandresBrightMint",
75
+ "toolDiffAdded": "poimandresStrongTurquoise",
76
+ "toolDiffRemoved": "poimandresHotRed",
77
+ "toolDiffContext": "poimandresDarkerGray",
78
+ "syntaxComment": "poimandresDarkerGray",
79
+ "syntaxKeyword": "poimandresDesaturatedGreen",
80
+ "syntaxFunction": "poimandresBrightMint",
81
+ "syntaxVariable": "poimandresStrongTurquoise",
82
+ "syntaxString": "poimandresStrongTurquoise",
83
+ "syntaxNumber": "poimandresPink",
84
+ "syntaxType": "poimandresStrongTurquoise",
85
+ "syntaxOperator": "poimandresDesaturatedGreen",
86
+ "syntaxPunctuation": "poimandresBluishGray",
87
+ "thinkingOff": "poimandresDarkerGray",
88
+ "thinkingMinimal": "poimandresBluishGray",
89
+ "thinkingLow": "poimandresBrightMint",
90
+ "thinkingMedium": "poimandresStrongTurquoise",
91
+ "thinkingHigh": "poimandresPink",
92
+ "thinkingXhigh": "poimandresHotRed",
93
+ "bashMode": "poimandresDesaturatedGreen",
94
+ "pythonMode": "poimandresStrongTurquoise",
95
+ "statusLineBg": "poimandresBluishGrayBrighter",
96
+ "statusLineSep": 103,
97
+ "statusLineModel": "poimandresBrightMint",
98
+ "statusLinePath": "poimandresBluishGray",
99
+ "statusLineGitClean": "poimandresStrongTurquoise",
100
+ "statusLineGitDirty": "poimandresHotRed",
101
+ "statusLineContext": "poimandresBluishGray",
102
+ "statusLineSpend": "poimandresLowerMint",
103
+ "statusLineStaged": 44,
104
+ "statusLineDirty": 169,
105
+ "statusLineUntracked": 229,
106
+ "statusLineOutput": "poimandresLowerBlue",
107
+ "statusLineCost": "poimandresPink",
108
+ "statusLineSubagents": "poimandresDesaturatedGreen"
109
+ },
110
+ "export": {
111
+ "pageBg": "poimandresOffWhite",
112
+ "cardBg": "poimandresBluishGrayBrighter",
113
+ "infoBg": "poimandresFocus"
114
+ },
115
+ "symbols": {
116
+ "preset": "nerd",
117
+ "overrides": {
118
+ "status.success": "●",
119
+ "status.error": "✖",
120
+ "status.warning": "◆",
121
+ "status.info": "○",
122
+ "status.pending": "◌",
123
+ "nav.cursor": "▸",
124
+ "nav.selected": "▴",
125
+ "thinking.minimal": "◌",
126
+ "thinking.low": "◍",
127
+ "thinking.medium": "◎",
128
+ "thinking.high": "◉",
129
+ "thinking.xhigh": "●",
130
+ "icon.model": "◇",
131
+ "icon.plan": "◈",
132
+ "icon.folder": "▸",
133
+ "icon.pi": "π",
134
+ "format.bullet": "◦",
135
+ "md.bullet": "◦"
136
+ }
137
+ }
138
+ }
@@ -104,8 +104,8 @@ const patchEditSchema = Type.Object({
104
104
  export type ReplaceParams = Static<typeof replaceEditSchema>;
105
105
  export type PatchParams = Static<typeof patchEditSchema>;
106
106
 
107
- /** Pattern matching hashline display format: `LINE#ID:CONTENT` */
108
- const HASHLINE_PREFIX_RE = /^\s*(?:>>>|>>)?\s*\d+#[0-9a-zA-Z]{1,16}:/;
107
+ /** Pattern matching hashline display format prefixes: `LINE#ID:CONTENT` and `#ID:CONTENT` */
108
+ const HASHLINE_PREFIX_RE = /^\s*(?:>>>|>>)?\s*(?:\d+\s*#\s*|#)\s*[0-9a-zA-Z]{1,16}:/;
109
109
 
110
110
  /** Pattern matching a unified-diff added-line `+` prefix (but not `++`). Does NOT match `-` to avoid corrupting Markdown list items. */
111
111
  const DIFF_PLUS_RE = /^[+](?![+])/;
@@ -118,8 +118,9 @@ const DIFF_PLUS_RE = /^[+](?![+])/;
118
118
  * output file. This strips them heuristically before application.
119
119
  */
120
120
  export function stripNewLinePrefixes(lines: string[]): string[] {
121
- // Detect whether the *majority* of non-empty lines carry a prefix
122
- // if only one line out of many has a match it's likely real content.
121
+ // Hashline prefixes are highly specific to read output and should only be
122
+ // stripped when *every* non-empty line carries one.
123
+ // Diff '+' markers can be legitimate content less often, so keep majority mode.
123
124
  let hashPrefixCount = 0;
124
125
  let diffPlusCount = 0;
125
126
  let nonEmpty = 0;
@@ -131,9 +132,8 @@ export function stripNewLinePrefixes(lines: string[]): string[] {
131
132
  }
132
133
  if (nonEmpty === 0) return lines;
133
134
 
134
- const stripHash = hashPrefixCount > 0 && hashPrefixCount >= nonEmpty * 0.5;
135
+ const stripHash = hashPrefixCount > 0 && hashPrefixCount === nonEmpty;
135
136
  const stripPlus = !stripHash && diffPlusCount > 0 && diffPlusCount >= nonEmpty * 0.5;
136
-
137
137
  if (!stripHash && !stripPlus) return lines;
138
138
 
139
139
  return lines.map(l => {
@@ -145,8 +145,7 @@ export function stripNewLinePrefixes(lines: string[]): string[] {
145
145
 
146
146
  export function hashlineParseText(edit: string[] | string | null): string[] {
147
147
  if (edit === null) return [];
148
- if (Array.isArray(edit)) return edit;
149
- const lines = stripNewLinePrefixes(edit.split("\n"));
148
+ const lines = stripNewLinePrefixes(Array.isArray(edit) ? edit : edit.split("\n"));
150
149
  if (lines.length === 0) return [];
151
150
  if (lines[lines.length - 1].trim() === "") return lines.slice(0, -1);
152
151
  return lines;
package/src/sdk.ts CHANGED
@@ -1281,6 +1281,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1281
1281
  interruptMode: settings.get("interruptMode") ?? "immediate",
1282
1282
  thinkingBudgets: settings.getGroup("thinkingBudgets"),
1283
1283
  temperature: settings.get("temperature") >= 0 ? settings.get("temperature") : undefined,
1284
+ topP: settings.get("topP") >= 0 ? settings.get("topP") : undefined,
1285
+ topK: settings.get("topK") >= 0 ? settings.get("topK") : undefined,
1286
+ minP: settings.get("minP") >= 0 ? settings.get("minP") : undefined,
1287
+ presencePenalty: settings.get("presencePenalty") >= 0 ? settings.get("presencePenalty") : undefined,
1288
+ repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
1284
1289
  kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
1285
1290
  preferWebsockets: preferOpenAICodexWebsockets,
1286
1291
  getToolContext: tc => toolContextStore.getContext(tc),
@@ -22,6 +22,7 @@ import {
22
22
  type FindingPriority,
23
23
  getPriorityInfo,
24
24
  PRIORITY_LABELS,
25
+ parseReportFindingDetails,
25
26
  type ReportFindingDetails,
26
27
  type SubmitReviewDetails,
27
28
  } from "../tools/review";
@@ -68,6 +69,16 @@ function formatFindingSummary(findings: ReportFindingDetails[], theme: Theme): s
68
69
  return `${theme.fg("dim", "Findings:")} ${parts.join(theme.sep.dot)}`;
69
70
  }
70
71
 
72
+ function normalizeReportFindings(value: unknown): ReportFindingDetails[] {
73
+ if (!Array.isArray(value)) return [];
74
+ const findings: ReportFindingDetails[] = [];
75
+ for (const item of value) {
76
+ const finding = parseReportFindingDetails(item);
77
+ if (finding) findings.push(finding);
78
+ }
79
+ return findings;
80
+ }
81
+
71
82
  function formatJsonScalar(value: unknown, _theme: Theme): string {
72
83
  if (value === null) return "null";
73
84
  if (typeof value === "string") {
@@ -569,13 +580,13 @@ function renderAgentProgress(
569
580
  // For completed tasks, check for review verdict from submit_result tool
570
581
  if (progress.status === "completed") {
571
582
  const completeData = progress.extractedToolData.submit_result as Array<{ data: unknown }> | undefined;
572
- const reportFindingData = progress.extractedToolData.report_finding as ReportFindingDetails[] | undefined;
583
+ const reportFindingData = normalizeReportFindings(progress.extractedToolData.report_finding);
573
584
  const reviewData = completeData
574
585
  ?.map(c => c.data as SubmitReviewDetails)
575
586
  .filter(d => d && typeof d === "object" && "overall_correctness" in d);
576
587
  if (reviewData && reviewData.length > 0) {
577
588
  const summary = reviewData[reviewData.length - 1];
578
- const findings = reportFindingData ?? [];
589
+ const findings = reportFindingData;
579
590
  lines.push(...renderReviewResult(summary, findings, continuePrefix, expanded, theme));
580
591
  return lines; // Review result handles its own rendering
581
592
  }
@@ -583,8 +594,9 @@ function renderAgentProgress(
583
594
 
584
595
  for (const [toolName, dataArray] of Object.entries(progress.extractedToolData)) {
585
596
  // Handle report_finding with tree formatting
586
- if (toolName === "report_finding" && (dataArray as ReportFindingDetails[]).length > 0) {
587
- const findings = dataArray as ReportFindingDetails[];
597
+ if (toolName === "report_finding") {
598
+ const findings = normalizeReportFindings(dataArray);
599
+ if (findings.length === 0) continue;
588
600
  lines.push(`${continuePrefix}${formatFindingSummary(findings, theme)}`);
589
601
  lines.push(...renderFindings(findings, continuePrefix, expanded, theme));
590
602
  continue;
@@ -693,7 +705,7 @@ function renderFindings(
693
705
 
694
706
  const { color } = getPriorityInfo(finding.priority);
695
707
  const titleText = finding.title?.replace(/^\[P\d\]\s*/, "") ?? "Untitled";
696
- const loc = `${path.basename(finding.file_path)}:${finding.line_start}`;
708
+ const loc = `${path.basename(finding.file_path || "<unknown>")}:${finding.line_start}`;
697
709
 
698
710
  lines.push(
699
711
  `${continuePrefix}${findingPrefix} ${theme.fg(color, `[${finding.priority}]`)} ${titleText} ${theme.fg("dim", loc)}`,
@@ -773,7 +785,7 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
773
785
 
774
786
  // Check for review result (submit_result with review schema + report_finding)
775
787
  const completeData = result.extractedToolData?.submit_result as Array<{ data: unknown }> | undefined;
776
- const reportFindingData = result.extractedToolData?.report_finding as ReportFindingDetails[] | undefined;
788
+ const reportFindingData = normalizeReportFindings(result.extractedToolData?.report_finding);
777
789
 
778
790
  // Extract review verdict from submit_result tool's data field if it matches SubmitReviewDetails
779
791
  const reviewData = completeData
@@ -784,11 +796,11 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
784
796
  if (submitReviewData && submitReviewData.length > 0) {
785
797
  // Use combined review renderer
786
798
  const summary = submitReviewData[submitReviewData.length - 1];
787
- const findings = reportFindingData ?? [];
799
+ const findings = reportFindingData;
788
800
  lines.push(...renderReviewResult(summary, findings, continuePrefix, expanded, theme));
789
801
  return lines;
790
802
  }
791
- if (reportFindingData && reportFindingData.length > 0) {
803
+ if (reportFindingData.length > 0) {
792
804
  const hasCompleteData = completeData && completeData.length > 0;
793
805
  const message = hasCompleteData
794
806
  ? "Review verdict missing expected fields"
@@ -13,6 +13,7 @@ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
13
13
  import { StringEnum } from "@oh-my-pi/pi-ai";
14
14
  import type { Component } from "@oh-my-pi/pi-tui";
15
15
  import { Container, Text } from "@oh-my-pi/pi-tui";
16
+ import { isRecord } from "@oh-my-pi/pi-utils";
16
17
  import { Type } from "@sinclair/typebox";
17
18
  import type { Theme, ThemeColor } from "../modes/theme/theme";
18
19
  import { subprocessToolRegistry } from "../task/subprocess-tool-registry";
@@ -82,6 +83,51 @@ interface ReportFindingDetails {
82
83
  line_end: number;
83
84
  }
84
85
 
86
+ function isFindingPriority(value: unknown): value is FindingPriority {
87
+ return value === "P0" || value === "P1" || value === "P2" || value === "P3";
88
+ }
89
+
90
+ export function parseReportFindingDetails(value: unknown): ReportFindingDetails | undefined {
91
+ if (!isRecord(value)) return undefined;
92
+
93
+ const title = typeof value.title === "string" ? value.title : undefined;
94
+ const body = typeof value.body === "string" ? value.body : undefined;
95
+ const priority = isFindingPriority(value.priority) ? value.priority : undefined;
96
+ const confidence =
97
+ typeof value.confidence === "number" &&
98
+ Number.isFinite(value.confidence) &&
99
+ value.confidence >= 0 &&
100
+ value.confidence <= 1
101
+ ? value.confidence
102
+ : undefined;
103
+ const filePath = typeof value.file_path === "string" && value.file_path.length > 0 ? value.file_path : undefined;
104
+ const lineStart =
105
+ typeof value.line_start === "number" && Number.isFinite(value.line_start) ? value.line_start : undefined;
106
+ const lineEnd = typeof value.line_end === "number" && Number.isFinite(value.line_end) ? value.line_end : undefined;
107
+
108
+ if (
109
+ title === undefined ||
110
+ body === undefined ||
111
+ priority === undefined ||
112
+ confidence === undefined ||
113
+ filePath === undefined ||
114
+ lineStart === undefined ||
115
+ lineEnd === undefined
116
+ ) {
117
+ return undefined;
118
+ }
119
+
120
+ return {
121
+ title,
122
+ body,
123
+ priority,
124
+ confidence,
125
+ file_path: filePath,
126
+ line_start: lineStart,
127
+ line_end: lineEnd,
128
+ };
129
+ }
130
+
85
131
  export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> = {
86
132
  name: "report_finding",
87
133
  label: "Report Finding",
@@ -152,7 +198,10 @@ export type { ReportFindingDetails };
152
198
 
153
199
  // Register report_finding handler
154
200
  subprocessToolRegistry.register<ReportFindingDetails>("report_finding", {
155
- extractData: event => event.result?.details as ReportFindingDetails | undefined,
201
+ extractData: event => {
202
+ if (event.isError) return undefined;
203
+ return parseReportFindingDetails(event.result?.details);
204
+ },
156
205
 
157
206
  renderInline: (data, theme) => {
158
207
  const { label, icon, color } = getPriorityDisplay(data.priority, theme);