@oh-my-pi/pi-coding-agent 15.11.3 → 15.11.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/cli.js +353 -294
  3. package/dist/types/config/api-key-resolver.d.ts +9 -3
  4. package/dist/types/config/keybindings.d.ts +1 -1
  5. package/dist/types/config/model-discovery.d.ts +6 -4
  6. package/dist/types/config/model-registry.d.ts +7 -4
  7. package/dist/types/config/settings-schema.d.ts +458 -155
  8. package/dist/types/export/html/template.generated.d.ts +1 -1
  9. package/dist/types/mnemopi/config.d.ts +3 -1
  10. package/dist/types/modes/components/settings-defs.d.ts +9 -2
  11. package/dist/types/modes/components/settings-selector.d.ts +9 -4
  12. package/dist/types/modes/components/tool-execution.d.ts +12 -1
  13. package/dist/types/modes/components/transcript-container.d.ts +12 -0
  14. package/dist/types/modes/controllers/input-controller.d.ts +9 -1
  15. package/dist/types/modes/theme/theme.d.ts +23 -3
  16. package/dist/types/session/agent-session.d.ts +14 -7
  17. package/dist/types/session/auth-storage.d.ts +1 -1
  18. package/dist/types/session/snapcompact-inline.d.ts +28 -0
  19. package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +14 -0
  20. package/dist/types/system-prompt.d.ts +3 -1
  21. package/dist/types/task/render.d.ts +16 -6
  22. package/dist/types/tools/gh.d.ts +3 -0
  23. package/dist/types/tools/render-utils.d.ts +8 -16
  24. package/dist/types/utils/session-color.d.ts +15 -3
  25. package/dist/types/web/kagi.d.ts +1 -2
  26. package/dist/types/web/search/providers/codex.d.ts +1 -1
  27. package/dist/types/web/search/providers/gemini.d.ts +9 -6
  28. package/package.json +11 -11
  29. package/src/auto-thinking/classifier.ts +1 -5
  30. package/src/commit/model-selection.ts +3 -6
  31. package/src/config/api-key-resolver.ts +10 -3
  32. package/src/config/keybindings.ts +1 -1
  33. package/src/config/model-discovery.ts +60 -46
  34. package/src/config/model-registry.ts +21 -8
  35. package/src/config/model-resolver.ts +57 -3
  36. package/src/config/settings-schema.ts +601 -153
  37. package/src/eval/completion-bridge.ts +1 -5
  38. package/src/export/html/template.generated.ts +1 -1
  39. package/src/export/html/template.js +13 -6
  40. package/src/internal-urls/docs-index.generated.ts +5 -5
  41. package/src/internal-urls/issue-pr-protocol.ts +10 -4
  42. package/src/memories/index.ts +2 -10
  43. package/src/mnemopi/backend.ts +30 -8
  44. package/src/mnemopi/config.ts +6 -1
  45. package/src/mnemopi/state.ts +6 -0
  46. package/src/modes/components/extensions/inspector-panel.ts +6 -2
  47. package/src/modes/components/plan-review-overlay.ts +15 -17
  48. package/src/modes/components/plugin-settings.ts +22 -5
  49. package/src/modes/components/settings-defs.ts +19 -4
  50. package/src/modes/components/settings-selector.ts +493 -93
  51. package/src/modes/components/status-line/component.ts +3 -1
  52. package/src/modes/components/status-line/segments.ts +3 -1
  53. package/src/modes/components/tool-execution.ts +69 -12
  54. package/src/modes/components/transcript-container.ts +26 -0
  55. package/src/modes/components/tree-selector.ts +16 -6
  56. package/src/modes/controllers/command-controller.ts +37 -7
  57. package/src/modes/controllers/event-controller.ts +1 -0
  58. package/src/modes/controllers/input-controller.ts +68 -6
  59. package/src/modes/controllers/selector-controller.ts +81 -61
  60. package/src/modes/interactive-mode.ts +4 -2
  61. package/src/modes/rpc/rpc-mode.ts +2 -1
  62. package/src/modes/shared.ts +2 -0
  63. package/src/modes/theme/theme.ts +100 -7
  64. package/src/modes/utils/context-usage.ts +3 -1
  65. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  66. package/src/modes/utils/ui-helpers.ts +9 -5
  67. package/src/prompts/system/personalities/default.md +26 -0
  68. package/src/prompts/system/personalities/friendly.md +17 -0
  69. package/src/prompts/system/personalities/pragmatic.md +15 -0
  70. package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
  71. package/src/prompts/system/snapcompact-system-stub.md +1 -0
  72. package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
  73. package/src/prompts/system/system-prompt.md +5 -22
  74. package/src/prompts/tools/task.md +3 -3
  75. package/src/sdk.ts +22 -1
  76. package/src/session/agent-session.ts +91 -24
  77. package/src/session/auth-storage.ts +1 -0
  78. package/src/session/session-dump-format.ts +8 -1
  79. package/src/session/session-manager.ts +5 -5
  80. package/src/session/snapcompact-inline.ts +187 -0
  81. package/src/slash-commands/helpers/active-oauth-account.ts +44 -0
  82. package/src/slash-commands/helpers/usage-report.ts +24 -3
  83. package/src/system-prompt.ts +15 -1
  84. package/src/task/render.ts +29 -19
  85. package/src/tool-discovery/tool-index.ts +2 -0
  86. package/src/tools/bash.ts +10 -3
  87. package/src/tools/eval-render.ts +13 -8
  88. package/src/tools/gh.ts +39 -1
  89. package/src/tools/image-gen.ts +114 -78
  90. package/src/tools/inspect-image.ts +1 -5
  91. package/src/tools/job.ts +25 -5
  92. package/src/tools/read.ts +1 -57
  93. package/src/tools/render-utils.ts +29 -31
  94. package/src/tools/ssh.ts +3 -3
  95. package/src/tools/tts.ts +40 -20
  96. package/src/utils/clipboard.ts +56 -4
  97. package/src/utils/commit-message-generator.ts +1 -5
  98. package/src/utils/session-color.ts +83 -9
  99. package/src/utils/title-generator.ts +1 -1
  100. package/src/web/kagi.ts +26 -27
  101. package/src/web/search/providers/codex.ts +42 -40
  102. package/src/web/search/providers/gemini.ts +42 -22
  103. package/src/web/search/providers/perplexity.ts +22 -10
@@ -1093,7 +1093,9 @@ export class InteractiveMode implements InteractiveModeContext {
1093
1093
  } else {
1094
1094
  const accentEnabled = !isSettingsInitialized() || settings.get("statusLine.sessionAccent") !== false;
1095
1095
  const sessionName = accentEnabled ? this.sessionManager.getSessionName() : undefined;
1096
- const hex = sessionName ? getSessionAccentHex(sessionName, theme.accentSurfaceLuminance) : undefined;
1096
+ const hex = sessionName
1097
+ ? getSessionAccentHex(sessionName, theme.getMajorThemeColorHexes(), theme.accentSurfaceLuminance)
1098
+ : undefined;
1097
1099
  const ansi = getSessionAccentAnsi(hex);
1098
1100
  if (ansi) {
1099
1101
  this.editor.borderColor = (str: string) => `${ansi}${str}\x1b[39m`;
@@ -2823,7 +2825,7 @@ export class InteractiveMode implements InteractiveModeContext {
2823
2825
  if (!key.sessionAccentEnabled || !key.sessionName) {
2824
2826
  return this.#cacheWorkingMessageAccent(key, undefined);
2825
2827
  }
2826
- const hex = getSessionAccentHex(key.sessionName, key.accentSurfaceLuminance);
2828
+ const hex = getSessionAccentHex(key.sessionName, theme.getMajorThemeColorHexes(), key.accentSurfaceLuminance);
2827
2829
  const main = getSessionAccentAnsi(hex);
2828
2830
  const dim = getSessionAccentAnsi(adjustHsv(hex, { s: 0.55, v: 0.65 }));
2829
2831
  return this.#cacheWorkingMessageAccent(key, main && dim ? { main, dim } : undefined);
@@ -11,6 +11,7 @@
11
11
  * - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
12
12
  */
13
13
  import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
14
+ import { isZodSchema, zodToWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
14
15
  import { $env, readJsonl, Snowflake } from "@oh-my-pi/pi-utils";
15
16
  import { reset as resetCapabilities } from "../../capability";
16
17
  import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
@@ -662,7 +663,7 @@ export async function runRpcMode(
662
663
  dumpTools: session.agent.state.tools.map(tool => ({
663
664
  name: tool.name,
664
665
  description: tool.description,
665
- parameters: tool.parameters,
666
+ parameters: isZodSchema(tool.parameters) ? zodToWireSchema(tool.parameters) : tool.parameters,
666
667
  })),
667
668
  contextUsage: session.getContextUsage(),
668
669
  };
@@ -24,6 +24,8 @@ export function getTabBarTheme(): TabBarTheme {
24
24
  label: (text: string) => theme.bold(theme.fg("accent", text)),
25
25
  activeTab: (text: string) => theme.bold(theme.bg("selectedBg", theme.fg("text", text))),
26
26
  inactiveTab: (text: string) => theme.fg("muted", text),
27
+ mutedTab: (text: string) => theme.fg("dim", text),
28
+ hoverTab: (text: string) => theme.bg("selectedBg", theme.fg("text", text)),
27
29
  hint: (text: string) => theme.fg("dim", text),
28
30
  };
29
31
  }
@@ -9,7 +9,7 @@ import {
9
9
  highlightCode as nativeHighlightCode,
10
10
  supportsLanguage as nativeSupportsLanguage,
11
11
  } from "@oh-my-pi/pi-natives";
12
- import type { EditorTheme, MarkdownTheme, SelectListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
12
+ import type { EditorTheme, MarkdownTheme, SelectListTheme, SettingsListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
13
13
  import { adjustHsv, colorLuma, getCustomThemesDir, isEnoent, logger, relativeLuminance } from "@oh-my-pi/pi-utils";
14
14
  import chalk from "chalk";
15
15
  import { LRUCache } from "lru-cache/raw";
@@ -197,7 +197,8 @@ export type SymbolKey =
197
197
  | "tab.model"
198
198
  | "tab.interaction"
199
199
  | "tab.context"
200
- | "tab.editing"
200
+ | "tab.files"
201
+ | "tab.shell"
201
202
  | "tab.tools"
202
203
  | "tab.memory"
203
204
  | "tab.tasks"
@@ -394,7 +395,8 @@ const UNICODE_SYMBOLS: SymbolMap = {
394
395
  "tab.model": "🤖",
395
396
  "tab.interaction": "⌨",
396
397
  "tab.context": "📋",
397
- "tab.editing": "💻",
398
+ "tab.files": "📁",
399
+ "tab.shell": "💻",
398
400
  "tab.tools": "🔧",
399
401
  "tab.memory": "🧠",
400
402
  "tab.tasks": "📦",
@@ -693,7 +695,8 @@ const NERD_SYMBOLS: SymbolMap = {
693
695
  "tab.model": "󰚩",
694
696
  "tab.interaction": "󰌌",
695
697
  "tab.context": "󰘸",
696
- "tab.editing": "",
698
+ "tab.files": "󰈔",
699
+ "tab.shell": "󰆍",
697
700
  "tab.tools": "󰠭",
698
701
  "tab.memory": "󰧑",
699
702
  "tab.tasks": "󰐱",
@@ -887,7 +890,8 @@ const ASCII_SYMBOLS: SymbolMap = {
887
890
  "tab.model": "[M]",
888
891
  "tab.interaction": "[I]",
889
892
  "tab.context": "[X]",
890
- "tab.editing": "[E]",
893
+ "tab.files": "[F]",
894
+ "tab.shell": "[S]",
891
895
  "tab.tools": "[T]",
892
896
  "tab.memory": "[Y]",
893
897
  "tab.tasks": "[K]",
@@ -1400,9 +1404,23 @@ const langMap: Record<string, SymbolKey> = {
1400
1404
  bin: "lang.binary",
1401
1405
  };
1402
1406
 
1407
+ /**
1408
+ * Resolve a theme color value (hex string or 256-color index) to a CSS hex string.
1409
+ * Empty string represents the default terminal color.
1410
+ */
1411
+ function resolveToHex(value: string | number, isLight: boolean): string {
1412
+ if (typeof value === "number") return ansi256ToHex(value);
1413
+ if (value === "") return isLight ? "#000000" : "#e5e5e7";
1414
+ return value;
1415
+ }
1416
+
1403
1417
  export class Theme {
1404
1418
  #fgColors: Record<ThemeColor, string>;
1405
1419
  #bgColors: Record<ThemeBg, string>;
1420
+ /** Resolved hex strings for foreground colors — populated at construction. */
1421
+ readonly #hexFgColors: Record<ThemeColor, string>;
1422
+ /** Resolved hex strings for background colors — populated at construction. */
1423
+ readonly #hexBgColors: Record<ThemeBg, string>;
1406
1424
  #symbols: SymbolMap;
1407
1425
  #spinnerFramesOverrides: Partial<Record<SpinnerType, string[]>>;
1408
1426
  /**
@@ -1415,7 +1433,6 @@ export class Theme {
1415
1433
  readonly statusLineLuminance: number | undefined;
1416
1434
  /** WCAG relative luminance of the status-line background — basis for accent contrast. */
1417
1435
  readonly #statusLineContrastLuminance: number | undefined;
1418
-
1419
1436
  constructor(
1420
1437
  fgColors: Record<ThemeColor, string | number>,
1421
1438
  bgColors: Record<ThemeBg, string | number>,
@@ -1426,13 +1443,19 @@ export class Theme {
1426
1443
  ) {
1427
1444
  this.statusLineLuminance = colorLuma(bgColors.statusLineBg);
1428
1445
  this.#statusLineContrastLuminance = relativeLuminance(bgColors.statusLineBg);
1446
+ const slIsLight = this.statusLineLuminance !== undefined && this.statusLineLuminance > 0.5;
1447
+
1429
1448
  this.#fgColors = {} as Record<ThemeColor, string>;
1449
+ this.#hexFgColors = {} as Record<ThemeColor, string>;
1430
1450
  for (const [key, value] of Object.entries(fgColors) as [ThemeColor, string | number][]) {
1431
1451
  this.#fgColors[key] = fgAnsi(value, mode);
1452
+ this.#hexFgColors[key] = resolveToHex(value, slIsLight);
1432
1453
  }
1433
1454
  this.#bgColors = {} as Record<ThemeBg, string>;
1455
+ this.#hexBgColors = {} as Record<ThemeBg, string>;
1434
1456
  for (const [key, value] of Object.entries(bgColors) as [ThemeBg, string | number][]) {
1435
1457
  this.#bgColors[key] = bgAnsi(value, mode);
1458
+ this.#hexBgColors[key] = resolveToHex(value, slIsLight);
1436
1459
  }
1437
1460
  // Build symbol map from preset + overrides
1438
1461
  const baseSymbols = SYMBOL_PRESETS[symbolPreset];
@@ -1460,6 +1483,70 @@ export class Theme {
1460
1483
  return this.isLight ? this.#statusLineContrastLuminance : undefined;
1461
1484
  }
1462
1485
 
1486
+ /**
1487
+ * Get the resolved CSS hex string for a foreground theme color.
1488
+ */
1489
+ getColorHex(color: ThemeColor): string {
1490
+ const hex = this.#hexFgColors[color];
1491
+ if (hex === undefined) throw new Error(`Unknown theme color: ${color}`);
1492
+ return hex || (this.isLight ? "#000000" : "#e5e5e7");
1493
+ }
1494
+
1495
+ /**
1496
+ * Get all foreground and background theme colors as CSS hex strings.
1497
+ * Skips colors resolved to the default terminal color (unstyled).
1498
+ */
1499
+ getAllThemeColorHexes(): string[] {
1500
+ const hexes: string[] = [];
1501
+ for (const hex of Object.values(this.#hexFgColors)) {
1502
+ if (hex) hexes.push(hex);
1503
+ }
1504
+ for (const hex of Object.values(this.#hexBgColors)) {
1505
+ if (hex) hexes.push(hex);
1506
+ }
1507
+ return hexes;
1508
+ }
1509
+
1510
+ /**
1511
+ * Get the most visually dominant theme colors as CSS hex strings — accent,
1512
+ * border, success, error, warning, heading, link, diff markers, etc.
1513
+ * These are the colors the session accent could visually clash with.
1514
+ * Skips colors resolved to the default terminal color (unstyled).
1515
+ */
1516
+ getMajorThemeColorHexes(): string[] {
1517
+ const majors: ThemeColor[] = [
1518
+ "accent",
1519
+ "border",
1520
+ "borderAccent",
1521
+ "borderMuted",
1522
+ "success",
1523
+ "error",
1524
+ "warning",
1525
+ "mdHeading",
1526
+ "mdLink",
1527
+ "mdCode",
1528
+ "mdCodeBlock",
1529
+ "mdQuoteBorder",
1530
+ "mdListBullet",
1531
+ "toolDiffAdded",
1532
+ "toolDiffRemoved",
1533
+ "customMessageLabel",
1534
+ "thinkingText",
1535
+ ];
1536
+ const hexes: string[] = [];
1537
+ for (const key of majors) {
1538
+ const hex = this.#hexFgColors[key];
1539
+ if (hex) hexes.push(hex);
1540
+ }
1541
+ return hexes;
1542
+ }
1543
+ /**
1544
+ * Get the resolved CSS hex string for the theme's accent color.
1545
+ */
1546
+ getAccentColorHex(): string {
1547
+ return this.getColorHex("accent");
1548
+ }
1549
+
1463
1550
  fg(color: ThemeColor, text: string): string {
1464
1551
  const ansi = this.#fgColors[color];
1465
1552
  if (!ansi) throw new Error(`Unknown theme color: ${color}`);
@@ -2657,6 +2744,7 @@ export function getSelectListTheme(): SelectListTheme {
2657
2744
  scrollInfo: (text: string) => theme.fg("muted", text),
2658
2745
  noMatch: (text: string) => theme.fg("muted", text),
2659
2746
  symbols: getSymbolTheme(),
2747
+ hovered: (text: string) => theme.bg("selectedBg", text),
2660
2748
  };
2661
2749
  }
2662
2750
 
@@ -2669,7 +2757,7 @@ export function getEditorTheme(): EditorTheme {
2669
2757
  };
2670
2758
  }
2671
2759
 
2672
- export function getSettingsListTheme(): import("@oh-my-pi/pi-tui").SettingsListTheme {
2760
+ export function getSettingsListTheme(): SettingsListTheme {
2673
2761
  return {
2674
2762
  label: (text: string, selected: boolean, changed: boolean) =>
2675
2763
  changed ? theme.fg("statusLineGitDirty", text) : selected ? theme.fg("accent", text) : text,
@@ -2678,5 +2766,10 @@ export function getSettingsListTheme(): import("@oh-my-pi/pi-tui").SettingsListT
2678
2766
  description: (text: string) => theme.fg("dim", text),
2679
2767
  cursor: theme.fg("accent", `${theme.nav.cursor} `),
2680
2768
  hint: (text: string) => theme.fg("dim", text),
2769
+ heading: (text: string, dimmed: boolean) =>
2770
+ dimmed ? theme.fg("dim", theme.underline(text)) : theme.fg("muted", theme.bold(theme.underline(text))),
2771
+ section: (text: string, active: boolean) =>
2772
+ active ? theme.fg("accent", theme.bold(text)) : theme.fg("muted", text),
2773
+ hovered: (text: string) => theme.bg("selectedBg", text),
2681
2774
  };
2682
2775
  }
@@ -1,6 +1,7 @@
1
1
  import type { CompactionSettings } from "@oh-my-pi/pi-agent-core/compaction";
2
2
  import { effectiveReserveTokens, estimateTokens, resolveThresholdTokens } from "@oh-my-pi/pi-agent-core/compaction";
3
3
  import type { Model } from "@oh-my-pi/pi-ai";
4
+ import { isZodSchema, zodToWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
4
5
  import { countTokens } from "@oh-my-pi/pi-natives";
5
6
  import { formatNumber } from "@oh-my-pi/pi-utils";
6
7
  import type { Skill } from "../../extensibility/skills";
@@ -57,7 +58,8 @@ export function estimateToolSchemaTokens(
57
58
  for (const tool of tools) {
58
59
  fragments.push(tool.name, tool.description);
59
60
  try {
60
- fragments.push(JSON.stringify(tool.parameters ?? {}));
61
+ const params = tool.parameters;
62
+ fragments.push(JSON.stringify((isZodSchema(params) ? zodToWireSchema(params) : params) ?? {}));
61
63
  } catch {
62
64
  // Schema may contain functions or cycles; ignore.
63
65
  }
@@ -48,7 +48,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
48
48
  `| \`${appKey(bindings, "app.tools.expand")}\` | Toggle tool output expansion |`,
49
49
  `| \`${appKey(bindings, "app.thinking.toggle")}\` | Toggle thinking block visibility |`,
50
50
  `| \`${appKey(bindings, "app.editor.external")}\` | Edit message in external editor |`,
51
- `| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image from clipboard |`,
51
+ `| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image or text from clipboard |`,
52
52
  `| \`${appKey(bindings, "app.stt.toggle")}\` | Toggle speech-to-text recording |`,
53
53
  `| \`${appKey(bindings, "app.agents.hub")}\` / \`${appKey(bindings, "app.session.observe")}\` / double-tap \`←\` (empty editor) | Open the agent hub |`,
54
54
  "| `#` | Open prompt actions |",
@@ -430,6 +430,7 @@ export class UiHelpers {
430
430
  showImages: settings.get("terminal.showImages"),
431
431
  editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
432
432
  editAllowFuzzy: settings.get("edit.fuzzyMatch"),
433
+ liveRegion: this.ctx.chatContainer,
433
434
  },
434
435
  tool,
435
436
  this.ctx.ui,
@@ -660,10 +661,13 @@ export class UiHelpers {
660
661
  await this.ctx.session.prompt(message.text);
661
662
  return;
662
663
  }
663
- await this.ctx.withLocalSubmission(message.text, () =>
664
- message.mode === "followUp"
665
- ? this.ctx.session.followUp(message.text, message.images)
666
- : this.ctx.session.steer(message.text, message.images),
664
+ await this.ctx.withLocalSubmission(
665
+ message.text,
666
+ () =>
667
+ message.mode === "followUp"
668
+ ? this.ctx.session.followUp(message.text, message.images)
669
+ : this.ctx.session.steer(message.text, message.images),
670
+ { imageCount: message.images?.length ?? 0 },
667
671
  );
668
672
  }
669
673
 
@@ -753,7 +757,7 @@ export class UiHelpers {
753
757
  // firstPrompt is fire-and-forget — its rejection is funneled through
754
758
  // `restoreQueue` rather than rethrown, so we use the primitive
755
759
  // recordLocalSubmission and dispose manually in the catch.
756
- const disposeFirstPrompt = this.ctx.recordLocalSubmission(firstPrompt.text);
760
+ const disposeFirstPrompt = this.ctx.recordLocalSubmission(firstPrompt.text, firstPrompt.images?.length ?? 0);
757
761
  const promptPromise = this.ctx.session
758
762
  .prompt(firstPrompt.text, {
759
763
  streamingBehavior: firstPrompt.mode === "followUp" ? "followUp" : "steer",
@@ -0,0 +1,26 @@
1
+ You are a terse, evidence-first engineer: every sentence carries a fact, a decision, or a risk.
2
+
3
+ # Tone
4
+ - Use terse sentence fragments when clearer.
5
+ - Skip ceremony, hedging, summaries, filler, motivational and marketing language, and generic explanation.
6
+ - Do not narrate obvious steps or over-explain basics.
7
+ - MUST assume the reader is technical.
8
+ - Be concrete: mention exact files, symbols, APIs, state fields, edge cases, and verification.
9
+ - Compress reasoning into facts, constraints, tradeoffs, decisions, and checks. Action-oriented and dense.
10
+ - Do not hide uncertainty: state it briefly at the specific claim, name the tradeoff, and pick the boring/safe option.
11
+ - For code, focus on invariants, risks, and verification.
12
+ - Lead with the conclusion, then concrete evidence: changed files and verification.
13
+
14
+ # Reasoning Format
15
+ - Problem: what is wrong.
16
+ - Decision: what to do & why (concrete facts).
17
+ - Check: what can break & how to verify result.
18
+ - Next: the next concrete edit/action.
19
+
20
+ # Succinct Patterns
21
+ - Y → Need update X.
22
+ - This is safe: Z.
23
+ - Could do A, but B avoids C.
24
+
25
+ # Escalation
26
+ Push back when the plan hides risk or a claim is wrong: name the risk, show the evidence, propose the alternative. Once overruled, execute the user's call without relitigating.
@@ -0,0 +1,17 @@
1
+ You are a warm, supportive collaborator. You optimize for the user's momentum and confidence as much as for code quality.
2
+
3
+ # Values
4
+ - Empathy: meet the user where they are — adjust explanation depth, pacing, and tone to maximize understanding.
5
+ - Collaboration: invite input, synthesize the user's perspective, make them successful.
6
+ - Ownership: you are responsible not just for the code, but for whether the user is unblocked.
7
+
8
+ # Tone
9
+ - Warm, encouraging, conversational. Teamwork language: "we", "let's".
10
+ - Affirm progress; replace judgment with curiosity. Light enthusiasm when it sustains energy.
11
+ - The user MUST feel safe asking basic questions. You are NEVER curt, dismissive, or patronizing.
12
+ - Suspect a statement is wrong? Stay supportive: note the valid points, then explain the concern.
13
+ - Unflappable when others might get frustrated; an easy-going presence on hard problems.
14
+ - MUST assume the reader is technical; warmth never means dumbing down.
15
+
16
+ # Escalation
17
+ Escalate gently when a decision hides risk: pause, frame it as shared sanity-checking, and surface the tradeoff before committing. Escalation is support, never correction.
@@ -0,0 +1,15 @@
1
+ You are a deeply pragmatic, effective senior engineer. Engineering quality is non-negotiable; collaboration is a quiet joy — enthusiasm shows briefly and specifically when real progress lands.
2
+
3
+ # Values
4
+ - Clarity: reasoning explicit and concrete, so decisions and tradeoffs are easy to evaluate upfront.
5
+ - Pragmatism: keep the end goal and momentum in mind; do what actually moves the task forward.
6
+ - Rigor: technical arguments MUST be coherent and defensible; surface gaps and weak assumptions politely, in service of clarity.
7
+
8
+ # Tone
9
+ - Concise, respectful, task-focused. Actionable guidance first: assumptions, prerequisites, next steps.
10
+ - MUST assume the reader is technical.
11
+ - Acknowledge genuinely good decisions briefly and specifically. NEVER cheerlead, flatter, or reassure artificially.
12
+ - AVOID verbose explanation of your own work unless asked.
13
+
14
+ # Escalation
15
+ You MAY challenge the user to raise the technical bar — with demonstrable reasoning, never condescension. When proposing an alternative, explain the reasoning so it stands on its own; once concerns are noted, work with the user's call.
@@ -0,0 +1 @@
1
+ === OPERATING INSTRUCTIONS — read the image(s) below as your system prompt ===
@@ -0,0 +1 @@
1
+ Your full operating instructions are attached as PNG image(s) at the start of the first user message. Read every frame carefully, in order, and follow them as your authoritative system prompt before doing anything else.
@@ -0,0 +1 @@
1
+ [Rasterized]
@@ -227,28 +227,11 @@ Changelog entries, test additions and updates, doc changes, and removing scaffol
227
227
  - Once your own smoke test confirms "it works", do the cleanup in full before yielding. Deferring is not skipping — the finished deliverable still carries the changelog, tests, and docs the change requires.
228
228
  </workflow>
229
229
 
230
- <reply-guidelines>
231
- - Use terse sentence fragments when clearer.
232
- - Skip ceremony, hedging, summaries, filler, motivational and marketing language, and generic explanation.
233
- - Do not narrate obvious steps or over-explain basics.
234
- - MUST assume the reader is technical.
235
- - Be concrete: mention exact files, symbols, APIs, state fields, edge cases, and verification.
236
- - Compress reasoning into facts, constraints, tradeoffs, decisions, and checks. Action-oriented and dense.
237
- - Do not hide uncertainty: state it briefly at the specific claim, name the tradeoff, and pick the boring/safe option.
238
- - For code, focus on invariants, risks, and verification.
239
- - Lead with the conclusion, then concrete evidence: changed files and verification.
240
-
241
- # Reasoning Format
242
- - Problem: what is wrong.
243
- - Decision: what to do & why (concrete facts).
244
- - Check: what can break & how to verify result.
245
- - Next: the next concrete edit/action.
246
-
247
- # Succinct Patterns
248
- - Y → Need update X.
249
- - This is safe: Z.
250
- - Could do A, but B avoids C.
251
- </reply-guidelines>
230
+ {{#if personality}}
231
+ <personality>
232
+ {{personality}}
233
+ </personality>
234
+ {{/if}}
252
235
 
253
236
  <critical>
254
237
  - NEVER narrate about or consider session limits, token/tool budgets, effort estimates, or how much of task you think you can finish. Not your concern:
@@ -1,12 +1,12 @@
1
1
  {{#if asyncEnabled}}{{#if batchEnabled}}Spawns subagents to work in the background — one per `tasks[]` item; a single spawn is a one-item batch.{{else}}Spawns ONE subagent per call to work in the background.{{/if}}
2
2
 
3
3
  - Spawning is non-blocking: the call returns immediately with the agent id{{#if batchEnabled}}s{{/if}} and job id{{#if batchEnabled}}s{{/if}}; each result is delivered automatically when that agent yields.
4
- - Parallelism = {{#if batchEnabled}}`tasks[]` items in one call, and/or multiple `task` calls in one assistant message{{else}}multiple `task` calls in one assistant message{{/if}}. Concurrency is bounded at {{MAX_CONCURRENCY}} running subagents per session.
4
+ - Parallelism = {{#if batchEnabled}}multiple `tasks[]` items in ONE call. To launch several subagents, you MUST batch them into a single call's `tasks[]` — they share `context` once instead of duplicating it. Separate `task` calls in one message are ONLY for spawns needing a different `agent` type or unrelated `context`{{else}}multiple `task` calls in one assistant message{{/if}}. Concurrency is bounded at {{MAX_CONCURRENCY}} running subagents per session.
5
5
  - If genuinely blocked on a result, wait with `job poll`; otherwise keep working. `job cancel` terminates a task and **cannot carry a message** — only for stalled/abandoned work.
6
6
  {{else}}{{#if batchEnabled}}Runs subagents synchronously — one per `tasks[]` item; a single spawn is a one-item batch.{{else}}Runs ONE subagent synchronously per call.{{/if}}
7
7
 
8
8
  - Spawning is blocking: the call returns only after the agent{{#if batchEnabled}}s{{/if}} finish; results arrive inline.
9
- - Parallelism = {{#if batchEnabled}}`tasks[]` items in one call, and/or multiple `task` calls in one assistant message{{else}}multiple `task` calls in one assistant message{{/if}}. Concurrency is bounded at {{MAX_CONCURRENCY}} running subagents per session.
9
+ - Parallelism = {{#if batchEnabled}}multiple `tasks[]` items in ONE call. To launch several subagents, you MUST batch them into a single call's `tasks[]` — they share `context` once instead of duplicating it. Separate `task` calls in one message are ONLY for spawns needing a different `agent` type or unrelated `context`{{else}}multiple `task` calls in one assistant message{{/if}}. Concurrency is bounded at {{MAX_CONCURRENCY}} running subagents per session.
10
10
  {{/if}}
11
11
  {{#if ircEnabled}}
12
12
  - Coordinate with agents via `irc` using their ids. Agents reach you and their siblings live the same way.
@@ -39,7 +39,7 @@
39
39
  </parameters>
40
40
 
41
41
  <rules>
42
- - **Maximize fan-out.** Issue the widest {{#if batchEnabled}}`tasks[]` batch (or set of parallel `task` calls){{else}}set of parallel `task` calls{{/if}} the work decomposes into. NEVER serialize work that could run concurrently.
42
+ - **Maximize fan-out.** Issue the widest {{#if batchEnabled}}`tasks[]` batch{{else}}set of parallel `task` calls{{/if}} the work decomposes into. NEVER serialize work that could run concurrently.
43
43
  - **Subagents do not verify, lint, or format.** Every assignment MUST instruct the subagent to skip all gates, formatters, and project-wide build/test/lint. You run them once at the end across the union of changed files.
44
44
  - No globs, no "update all", no package-wide scope. Fan out.
45
45
  - NEVER slow down or serialize because tasks might overlap on some files. Agents resolve collisions among themselves in real time.
package/src/sdk.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  type ThinkingLevel,
10
10
  } from "@oh-my-pi/pi-agent-core";
11
11
  import {
12
+ type Context,
12
13
  type CredentialDisabledEvent,
13
14
  type Message,
14
15
  type Model,
@@ -121,6 +122,7 @@ import {
121
122
  wrapSteeringForModel,
122
123
  } from "./session/messages";
123
124
  import { getRestorableSessionModels, SessionManager } from "./session/session-manager";
125
+ import { SnapcompactInlineTransformer } from "./session/snapcompact-inline";
124
126
  import { closeAllConnections } from "./ssh/connection-manager";
125
127
  import { unmountAll } from "./ssh/sshfs-mount";
126
128
  import {
@@ -1977,6 +1979,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1977
1979
  workspaceTree: workspaceTreePromise,
1978
1980
  memoryRootEnabled: memoryBackend.id === "local",
1979
1981
  model: settings.get("includeModelInPrompt") ? getActiveModelString() : undefined,
1982
+ personality: agentKind === "sub" ? "none" : settings.get("personality"),
1980
1983
  });
1981
1984
 
1982
1985
  if (options.systemPrompt === undefined) {
@@ -2156,6 +2159,24 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2156
2159
  const withContext = await extensionRunner.emitContext(messages);
2157
2160
  return wrapSteeringForModel(withContext);
2158
2161
  };
2162
+ // Per-request provider-context transforms. Obfuscate FIRST so secrets are
2163
+ // redacted from text before snapcompact rasterizes it into PNG frames.
2164
+ // Both operate on the transient outgoing Context only — never persisted.
2165
+ const snapcompactInline =
2166
+ settings.get("snapcompact.systemPrompt") || settings.get("snapcompact.toolResults")
2167
+ ? new SnapcompactInlineTransformer({
2168
+ renderSystemPrompt: settings.get("snapcompact.systemPrompt"),
2169
+ renderToolResults: settings.get("snapcompact.toolResults"),
2170
+ })
2171
+ : undefined;
2172
+ const transformProviderContext =
2173
+ obfuscator || snapcompactInline
2174
+ ? (context: Context, transformModel: Model): Context => {
2175
+ let transformed = obfuscator ? obfuscateProviderContext(obfuscator, context) : context;
2176
+ if (snapcompactInline) transformed = snapcompactInline.transform(transformed, transformModel);
2177
+ return transformed;
2178
+ }
2179
+ : undefined;
2159
2180
  const onPayload = async (payload: unknown, _model?: Model) => {
2160
2181
  return await extensionRunner.emitBeforeProviderRequest(payload);
2161
2182
  };
@@ -2196,7 +2217,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2196
2217
  sessionId: providerSessionId,
2197
2218
  promptCacheKey: options.providerPromptCacheKey,
2198
2219
  transformContext,
2199
- transformProviderContext: obfuscator ? context => obfuscateProviderContext(obfuscator, context) : undefined,
2220
+ transformProviderContext,
2200
2221
  steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
2201
2222
  followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
2202
2223
  interruptMode: settings.get("interruptMode") ?? "immediate",