@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
@@ -2,7 +2,7 @@ import type { MnemopiOptions } from "@oh-my-pi/pi-mnemopi";
2
2
  import type { Settings } from "../config/settings";
3
3
  export type MnemopiLlmMode = "none" | "smol" | "remote";
4
4
  export type MnemopiScoping = "global" | "per-project" | "per-project-tagged";
5
- export type MnemopiProviderOptions = Pick<MnemopiOptions, "noEmbeddings" | "embeddingModel" | "embeddingApiUrl" | "embeddingApiKey" | "llm">;
5
+ export type MnemopiProviderOptions = Pick<MnemopiOptions, "noEmbeddings" | "embeddingModel" | "embeddingApiUrl" | "embeddingApiKey" | "llm" | "debug">;
6
6
  export interface MnemopiBackendConfig {
7
7
  dbPath: string;
8
8
  baseBank?: string;
@@ -13,6 +13,8 @@ export interface MnemopiBackendConfig {
13
13
  scoping?: MnemopiScoping;
14
14
  autoRecall: boolean;
15
15
  autoRetain: boolean;
16
+ polyphonicRecall: boolean;
17
+ enhancedRecall: boolean;
16
18
  retainEveryNTurns: number;
17
19
  recallLimit: number;
18
20
  recallContextTurns: number;
@@ -4,7 +4,8 @@
4
4
  * settings selector.
5
5
  *
6
6
  * To add a new setting to the UI: declare it in `settings-schema.ts`
7
- * with a `ui` block. If it needs a submenu, include `options: [...]`
7
+ * with a `ui` block carrying `tab` and `group` (the group must be listed
8
+ * in `TAB_GROUPS[tab]`). If it needs a submenu, include `options: [...]`
8
9
  * (or `options: "runtime"` for runtime-injected lists like themes).
9
10
  */
10
11
  import { type SettingPath, type SettingTab, type SubmenuOption } from "../../config/settings-schema";
@@ -14,6 +15,8 @@ interface BaseSettingDef {
14
15
  label: string;
15
16
  description: string;
16
17
  tab: SettingTab;
18
+ /** Section within the tab; items are ordered by TAB_GROUPS[tab] and rendered under a heading row. */
19
+ group?: string;
17
20
  /**
18
21
  * Optional visibility predicate. When supplied and returning false, the
19
22
  * setting is hidden from the UI. Applies to every variant — booleans,
@@ -41,7 +44,11 @@ export interface TextInputSettingDef extends BaseSettingDef {
41
44
  export type SettingDef = BooleanSettingDef | EnumSettingDef | SubmenuSettingDef | TextInputSettingDef;
42
45
  /** Get all setting definitions with UI */
43
46
  export declare function getAllSettingDefs(): SettingDef[];
44
- /** Get settings for a specific tab */
47
+ /**
48
+ * Get settings for a specific tab, ordered by the tab's group layout
49
+ * (TAB_GROUPS). Ungrouped settings sort first; within a group, schema
50
+ * declaration order is preserved.
51
+ */
45
52
  export declare function getSettingsForTab(tab: SettingTab): SettingDef[];
46
53
  /** Get a setting definition by path */
47
54
  export declare function getSettingDef(path: SettingPath): SettingDef | undefined;
@@ -1,9 +1,8 @@
1
1
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Effort } from "@oh-my-pi/pi-ai";
3
- import { Container, SettingsList } from "@oh-my-pi/pi-tui";
3
+ import { type Component } from "@oh-my-pi/pi-tui";
4
4
  import { type SettingPath } from "../../config/settings";
5
5
  import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../config/settings-schema";
6
- import { PluginSettingsComponent } from "./plugin-settings";
7
6
  /**
8
7
  * Dynamic context for settings that need runtime data.
9
8
  * Some settings (like thinking level) are managed by the session, not Settings.
@@ -45,11 +44,17 @@ export interface SettingsCallbacks {
45
44
  * Main tabbed settings selector component.
46
45
  * Uses declarative settings definitions from settings-defs.ts.
47
46
  */
48
- export declare class SettingsSelectorComponent extends Container {
47
+ export declare class SettingsSelectorComponent implements Component {
49
48
  #private;
50
49
  private readonly context;
51
50
  private readonly callbacks;
52
51
  constructor(context: SettingsRuntimeContext, callbacks: SettingsCallbacks);
53
- getFocusComponent(): SettingsList | PluginSettingsComponent;
52
+ invalidate(): void;
53
+ /**
54
+ * Fullscreen frame: title border, tab row, divider, optional search banner,
55
+ * the active content sized to fill the terminal, the appearance preview,
56
+ * then a footer hint pinned above the bottom border.
57
+ */
58
+ render(width: number): readonly string[];
54
59
  handleInput(data: string): void;
55
60
  }
@@ -1,11 +1,22 @@
1
1
  import type { SnapshotStore } from "@oh-my-pi/hashline";
2
2
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
3
- import { Container, type TUI } from "@oh-my-pi/pi-tui";
3
+ import { type Component, Container, type TUI } from "@oh-my-pi/pi-tui";
4
+ /**
5
+ * Transcript-side probe telling a block whether it is still inside the live
6
+ * (repaintable) region. Implemented by `TranscriptContainer`; injected rather
7
+ * than imported so the component stays decoupled from the transcript.
8
+ */
9
+ export interface TranscriptLiveRegionProbe {
10
+ isBlockInLiveRegion(component: Component): boolean;
11
+ }
4
12
  export interface ToolExecutionOptions {
5
13
  snapshots?: SnapshotStore;
6
14
  showImages?: boolean;
7
15
  editFuzzyThreshold?: number;
8
16
  editAllowFuzzy?: boolean;
17
+ /** Live-region probe used to settle detached task progress once the block
18
+ * leaves the repaintable transcript region. */
19
+ liveRegion?: TranscriptLiveRegionProbe;
9
20
  }
10
21
  export interface ToolExecutionHandle {
11
22
  updateArgs(args: any, toolCallId?: string): void;
@@ -40,6 +40,18 @@ export declare class TranscriptContainer extends Container implements NativeScro
40
40
  * only repair by recommitting everything below it — duplication.
41
41
  */
42
42
  isWithinLiveRegion(component: Component): boolean;
43
+ /**
44
+ * Whether `component` is inside the live (repaintable) region exactly as
45
+ * {@link render} computes it: at/after the first still-mutating block, or
46
+ * the transcript tail when every block has finalized. Unlike
47
+ * {@link isWithinLiveRegion} (strictly below a still-mutating block, i.e.
48
+ * guaranteed-uncommitted), this also counts the trailing block that anchors
49
+ * the live region. Self-animating finalized blocks (a detached task's
50
+ * shimmering progress rows) poll this to stop animating — and settle on
51
+ * static bytes — the moment they sit above the seam, where their rows
52
+ * become commit-eligible native-scrollback history.
53
+ */
54
+ isBlockInLiveRegion(component: Component): boolean;
43
55
  render(width: number): readonly string[];
44
56
  }
45
57
  /**
@@ -1,9 +1,17 @@
1
1
  import type { AutocompleteProvider, SlashCommand } from "@oh-my-pi/pi-tui";
2
2
  import type { InteractiveModeContext } from "../../modes/types";
3
+ import { readImageFromClipboard, readTextFromClipboard } from "../../utils/clipboard";
3
4
  export declare class InputController {
4
5
  #private;
5
6
  private ctx;
6
- constructor(ctx: InteractiveModeContext);
7
+ /** Injectable clipboard reads so tests can drive paste flows without a real clipboard. */
8
+ private clipboard;
9
+ constructor(ctx: InteractiveModeContext,
10
+ /** Injectable clipboard reads so tests can drive paste flows without a real clipboard. */
11
+ clipboard?: {
12
+ readImage: typeof readImageFromClipboard;
13
+ readText: typeof readTextFromClipboard;
14
+ });
7
15
  setupKeyHandlers(): void;
8
16
  setupEditorSubmitHandler(): void;
9
17
  handleCtrlC(): void;
@@ -1,12 +1,12 @@
1
1
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Effort } from "@oh-my-pi/pi-ai";
3
- import type { EditorTheme, MarkdownTheme, SelectListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
3
+ import type { EditorTheme, MarkdownTheme, SelectListTheme, SettingsListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
4
4
  export { getLanguageFromPath } from "../../utils/lang-from-path";
5
5
  export type SymbolPreset = "unicode" | "nerd" | "ascii";
6
6
  /**
7
7
  * All available symbol keys organized by category.
8
8
  */
9
- export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.agents" | "icon.job" | "icon.cache" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.editing" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal" | "tool.irc";
9
+ export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.agents" | "icon.job" | "icon.cache" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal" | "tool.irc";
10
10
  export type SpinnerType = "status" | "activity";
11
11
  export type ThemeColor = "accent" | "border" | "borderAccent" | "borderMuted" | "success" | "error" | "warning" | "muted" | "dim" | "text" | "thinkingText" | "userMessageText" | "customMessageText" | "customMessageLabel" | "toolTitle" | "toolOutput" | "mdHeading" | "mdLink" | "mdLinkUrl" | "mdCode" | "mdCodeBlock" | "mdCodeBlockBorder" | "mdQuote" | "mdQuoteBorder" | "mdHr" | "mdListBullet" | "toolDiffAdded" | "toolDiffRemoved" | "toolDiffContext" | "syntaxComment" | "syntaxKeyword" | "syntaxFunction" | "syntaxVariable" | "syntaxString" | "syntaxNumber" | "syntaxType" | "syntaxOperator" | "syntaxPunctuation" | "thinkingOff" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium" | "thinkingHigh" | "thinkingXhigh" | "bashMode" | "pythonMode" | "statusLineSep" | "statusLineModel" | "statusLinePath" | "statusLineGitClean" | "statusLineGitDirty" | "statusLineContext" | "statusLineSpend" | "statusLineStaged" | "statusLineDirty" | "statusLineUntracked" | "statusLineOutput" | "statusLineCost" | "statusLineSubagents";
12
12
  /** Check if a string is a valid ThemeColor value */
@@ -33,6 +33,26 @@ export declare class Theme {
33
33
  * dark themes so accents stay vivid. Pass straight to `getSessionAccentHex`.
34
34
  */
35
35
  get accentSurfaceLuminance(): number | undefined;
36
+ /**
37
+ * Get the resolved CSS hex string for a foreground theme color.
38
+ */
39
+ getColorHex(color: ThemeColor): string;
40
+ /**
41
+ * Get all foreground and background theme colors as CSS hex strings.
42
+ * Skips colors resolved to the default terminal color (unstyled).
43
+ */
44
+ getAllThemeColorHexes(): string[];
45
+ /**
46
+ * Get the most visually dominant theme colors as CSS hex strings — accent,
47
+ * border, success, error, warning, heading, link, diff markers, etc.
48
+ * These are the colors the session accent could visually clash with.
49
+ * Skips colors resolved to the default terminal color (unstyled).
50
+ */
51
+ getMajorThemeColorHexes(): string[];
52
+ /**
53
+ * Get the resolved CSS hex string for the theme's accent color.
54
+ */
55
+ getAccentColorHex(): string;
36
56
  fg(color: ThemeColor, text: string): string;
37
57
  bg(color: ThemeBg, text: string): string;
38
58
  bold(text: string): string;
@@ -305,4 +325,4 @@ export declare function getSymbolTheme(): SymbolTheme;
305
325
  export declare function getMarkdownTheme(): MarkdownTheme;
306
326
  export declare function getSelectListTheme(): SelectListTheme;
307
327
  export declare function getEditorTheme(): EditorTheme;
308
- export declare function getSettingsListTheme(): import("@oh-my-pi/pi-tui").SettingsListTheme;
328
+ export declare function getSettingsListTheme(): SettingsListTheme;
@@ -300,6 +300,11 @@ export interface FreshSessionResult {
300
300
  sessionId: string;
301
301
  closedProviderSessions: number;
302
302
  }
303
+ /** Entry returned by {@link AgentSession.clearQueue} / {@link AgentSession.popLastQueuedMessage}. */
304
+ export type RestoredQueuedMessage = {
305
+ text: string;
306
+ images?: ImageContent[];
307
+ };
303
308
  export declare class AgentSession {
304
309
  #private;
305
310
  readonly agent: Agent;
@@ -610,12 +615,14 @@ export declare class AgentSession {
610
615
  deliverAs?: "steer" | "followUp";
611
616
  }): Promise<void>;
612
617
  /**
613
- * Clear queued messages and return them.
614
- * Useful for restoring to editor when user aborts.
618
+ * Clear queued messages and return them (text plus any attached images).
619
+ * Useful for restoring to editor when user aborts. The internal entry
620
+ * arrays are handed out as-is — a `tag` (if any) is inert once the record
621
+ * leaves the queue.
615
622
  */
616
623
  clearQueue(): {
617
- steering: string[];
618
- followUp: string[];
624
+ steering: RestoredQueuedMessage[];
625
+ followUp: RestoredQueuedMessage[];
619
626
  };
620
627
  /** Number of pending messages (includes steering, follow-up, and next-turn messages) */
621
628
  get queuedMessageCount(): number;
@@ -630,10 +637,10 @@ export declare class AgentSession {
630
637
  /**
631
638
  * Pop the last queued message (steering first, then follow-up).
632
639
  * Used by dequeue keybinding to restore messages to editor one at a time.
633
- * Returns the popped entry's `.text`; the tag (if any) dies with the
634
- * record — no orphan state can outlive the queue entry.
640
+ * Returns the popped entry's text and images; the tag (if any) dies with
641
+ * the record — no orphan state can outlive the queue entry.
635
642
  */
636
- popLastQueuedMessage(): string | undefined;
643
+ popLastQueuedMessage(): RestoredQueuedMessage | undefined;
637
644
  get skillsSettings(): SkillsSettings | undefined;
638
645
  /** Skills loaded by SDK (empty if --no-skills or skills: [] was passed) */
639
646
  get skills(): readonly Skill[];
@@ -2,5 +2,5 @@
2
2
  * Re-exports from @oh-my-pi/pi-ai.
3
3
  * All credential storage types and the AuthStorage class now live in the ai package.
4
4
  */
5
- export type { ApiKeyCredential, AuthCredential, AuthCredentialEntry, AuthCredentialStore, AuthStorageData, AuthStorageOptions, CredentialOrigin, CredentialOriginKind, OAuthCredential, SerializedAuthStorage, SnapshotResponse, StoredAuthCredential, } from "@oh-my-pi/pi-ai";
5
+ export type { ApiKeyCredential, AuthCredential, AuthCredentialEntry, AuthCredentialStore, AuthStorageData, AuthStorageOptions, CredentialOrigin, CredentialOriginKind, OAuthAccountIdentity, OAuthCredential, SerializedAuthStorage, SnapshotResponse, StoredAuthCredential, } from "@oh-my-pi/pi-ai";
6
6
  export { AuthBrokerClient, AuthStorage, DEFAULT_SNAPSHOT_CACHE_TTL_MS, REMOTE_REFRESH_SENTINEL, RemoteAuthCredentialStore, readAuthBrokerSnapshotCache, SqliteAuthCredentialStore, writeAuthBrokerSnapshotCache, } from "@oh-my-pi/pi-ai";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Snapcompact inline imaging: per-request transform that swaps the system
3
+ * prompt and/or large historical tool results for dense PNG frames on
4
+ * vision-capable models.
5
+ *
6
+ * Runs inside the agent loop's `transformProviderContext` hook — after the
7
+ * persisted history is converted to the outgoing `Context`, before the
8
+ * provider stream call. It only ever builds NEW message objects/arrays; the
9
+ * input context shares `content` array references with the persisted
10
+ * `SessionMessageEntry` messages, so mutation would leak rendered images
11
+ * into session.jsonl.
12
+ */
13
+ import type { Context, Model } from "@oh-my-pi/pi-ai";
14
+ export interface SnapcompactInlineOptions {
15
+ renderSystemPrompt: boolean;
16
+ renderToolResults: boolean;
17
+ }
18
+ /**
19
+ * Stateless with respect to the model (passed per call, so mid-session model
20
+ * switches re-resolve shape and budget); stateful only for the render caches,
21
+ * which live as long as the session's Agent.
22
+ */
23
+ export declare class SnapcompactInlineTransformer {
24
+ #private;
25
+ private readonly options;
26
+ constructor(options: SnapcompactInlineOptions);
27
+ transform(context: Context, model: Model): Context;
28
+ }
@@ -0,0 +1,14 @@
1
+ import type { UsageLimit, UsageReport } from "@oh-my-pi/pi-ai";
2
+ import type { OAuthAccountIdentity } from "../../session/auth-storage";
3
+ /**
4
+ * True when a single usage-limit column belongs to the given OAuth identity.
5
+ *
6
+ * Single definition of the matching rules for both `/usage` renderers:
7
+ * - `accountId` ↔ report metadata `accountId`/`account_id` or `limit.scope.accountId`
8
+ * - `email` ↔ report metadata `email`
9
+ * - `projectId` ↔ report metadata `projectId` or `limit.scope.projectId`
10
+ * (Google-style providers key usage on the GCP project, not an account id)
11
+ */
12
+ export declare function limitMatchesActiveAccount(report: UsageReport, limit: UsageLimit, identity: OAuthAccountIdentity | undefined): boolean;
13
+ /** True when any limit column in `report` belongs to the given OAuth identity. */
14
+ export declare function reportMatchesActiveAccount(report: UsageReport, identity: OAuthAccountIdentity | undefined): boolean;
@@ -2,7 +2,7 @@
2
2
  * System prompt construction and project context loading
3
3
  */
4
4
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
5
- import type { SkillsSettings } from "./config/settings";
5
+ import type { Personality, SkillsSettings } from "./config/settings";
6
6
  import { type Skill } from "./extensibility/skills";
7
7
  import { type WorkspaceTree } from "./workspace-tree";
8
8
  interface AlwaysApplyRule {
@@ -88,6 +88,8 @@ export interface BuildSystemPromptOptions {
88
88
  memoryRootEnabled?: boolean;
89
89
  /** Active model identifier (e.g. "anthropic/claude-opus-4") surfaced to the agent. */
90
90
  model?: string;
91
+ /** Personality preset rendered into the default system prompt. "none" omits the block. Default: "default" */
92
+ personality?: Personality;
91
93
  }
92
94
  /** Result of building provider-facing system prompt messages. */
93
95
  export interface BuildSystemPromptResult {
@@ -2,14 +2,23 @@ import type { Component } from "@oh-my-pi/pi-tui";
2
2
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
3
3
  import { type Theme } from "../modes/theme/theme";
4
4
  import type { TaskParams, TaskToolDetails } from "./types";
5
+ /** Render context threaded in from `ToolExecutionComponent.#buildRenderContext`. */
6
+ interface TaskRenderContext {
7
+ hasResult?: boolean;
8
+ /**
9
+ * The block left the transcript live region (detached spawn the transcript
10
+ * has moved past, or a sealed block): progress rows render static gray, so
11
+ * commit-eligible rows do not repaint after entering native scrollback.
12
+ */
13
+ frozen?: boolean;
14
+ }
15
+ type TaskRenderOptions = RenderResultOptions & {
16
+ renderContext?: TaskRenderContext;
17
+ };
5
18
  /**
6
19
  * Render the tool call arguments.
7
20
  */
8
- export declare function renderCall(args: TaskParams, options: RenderResultOptions & {
9
- renderContext?: {
10
- hasResult?: boolean;
11
- };
12
- }, theme: Theme): Component;
21
+ export declare function renderCall(args: TaskParams, options: TaskRenderOptions, theme: Theme): Component;
13
22
  /**
14
23
  * Render the tool result.
15
24
  */
@@ -20,9 +29,10 @@ export declare function renderResult(result: {
20
29
  }>;
21
30
  details?: TaskToolDetails;
22
31
  isError?: boolean;
23
- }, options: RenderResultOptions, theme: Theme, args?: TaskParams): Component;
32
+ }, options: TaskRenderOptions, theme: Theme, args?: TaskParams): Component;
24
33
  export declare const taskToolRenderer: {
25
34
  renderCall: typeof renderCall;
26
35
  renderResult: typeof renderResult;
27
36
  mergeCallAndResult: boolean;
28
37
  };
38
+ export {};
@@ -1,9 +1,12 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback, ToolApprovalDecision } from "@oh-my-pi/pi-agent-core";
2
2
  import * as z from "zod/v4";
3
3
  import type { Settings } from "../config/settings";
4
+ import * as git from "../utils/git";
4
5
  import type { ToolSession } from ".";
5
6
  import { type CacheStatus } from "./github-cache";
6
7
  import type { OutputMeta } from "./output-meta";
8
+ /** Runs `gh --json` for issue data, retrying without optional stateReason on older gh releases. */
9
+ export declare function githubIssueJsonWithStateReasonFallback<T>(cwd: string, args: readonly string[], signal: AbortSignal | undefined, options?: git.GhCommandOptions): Promise<T>;
7
10
  declare const githubSchema: z.ZodObject<{
8
11
  op: z.ZodEnum<{
9
12
  pr_checkout: "pr_checkout";
@@ -76,24 +76,16 @@ export declare function formatBadge(label: string, color: ToolUIColor, theme: Th
76
76
  * Uses consistent wording pattern.
77
77
  */
78
78
  export declare function formatMoreItems(remaining: number, itemType: string): string;
79
+ /** Tail-window height for collapsed command/code previews. */
80
+ export declare function previewWindowRows(): number;
79
81
  /**
80
- * Maximum rows a tool's streaming/pending *call* preview may render before it is
81
- * capped. This is intentionally conservative: the preview still sits inside a
82
- * transcript that already consumed some viewport rows, and tool blocks carry
83
- * extra chrome (status/header/border/"more lines"), so a "reasonable" raw code
84
- * or command preview like 10-12 lines can still overflow and strand its top
85
- * while the block is volatile. Keeping the live call window short avoids that
86
- * across terminals without turning the transcript into an interactive scroller.
87
- */
88
- export declare const CALL_PREVIEW_MAX_LINES = 6;
89
- /**
90
- * Cap a pre-rendered pending/call preview to a bounded window. When truncated,
91
- * show both the head and the live tail so the user can still see what the tool
92
- * is currently writing while the volatile block stays short enough not to strand
93
- * its top above the viewport. `Ctrl+O` widens the bounded window, but does not
94
- * fully uncap live tool previews for the same reason.
82
+ * Cap a pre-rendered command preview to a viewport-sized tail window: the end
83
+ * of the command stays visible (it is the live edge while args stream) behind
84
+ * an "… N earlier lines" marker on top. The same window applies while
85
+ * streaming and after completion so the block never jumps; only `expanded`
86
+ * (ctrl+o) uncaps it.
95
87
  *
96
- * `prefix` (raw, e.g. a dim tree gutter) is prepended to the summary line so
88
+ * `prefix` (raw, e.g. a dim tree gutter) is prepended to the marker line so
97
89
  * nested previews stay aligned.
98
90
  */
99
91
  export declare function capPreviewLines(lines: string[], theme: Theme, options?: {
@@ -1,13 +1,25 @@
1
1
  /**
2
- * Derive a stable CSS hex accent color from a session name.
2
+ * Derive a stable CSS hex accent color from a session name and the active theme.
3
+ *
4
+ * Picks a hue from a **dark/light-specific range** so the accent feels natural
5
+ * for the theme type (warm on dark, cool on light). The session name hash
6
+ * determines the exact hue within the range. The result is checked against
7
+ * all theme color hues and shifted if it lands within {@link MIN_HUE_DISTANCE}
8
+ * of an existing theme hue, but is clamped to the hue band so it never
9
+ * drifts into an unrelated part of the spectrum.
3
10
  *
4
11
  * On dark themes (`surfaceLuminance` undefined) the accent is vivid (high
5
12
  * saturation, high lightness). On light themes the lightness is reduced until the
6
13
  * accent's perceived luminance clears {@link ACCENT_MIN_CONTRAST} against the
7
14
  * actual surface it renders on — so it stays legible on near-white *and* mid-light
8
- * backgrounds — while keeping the same per-session hue.
15
+ * backgrounds.
16
+ *
17
+ * @param name — session name for per-session uniqueness.
18
+ * @param themeColorHexes — all theme colors to check collision against.
19
+ * @param surfaceLuminance — undefined on dark themes; WCAG luminance of the
20
+ * status-line background on light themes.
9
21
  */
10
- export declare function getSessionAccentHex(name: string, surfaceLuminance?: number): string;
22
+ export declare function getSessionAccentHex(name: string, themeColorHexes: string[], surfaceLuminance?: number): string;
11
23
  /**
12
24
  * Convert a hex accent color to an ANSI-16m foreground escape sequence.
13
25
  * Returns `undefined` if `hex` is nullish or Bun.color conversion fails.
@@ -6,7 +6,7 @@
6
6
  * through the shared {@link AuthStorage} broker (Bearer token), and responses
7
7
  * are categorized result buckets rather than the legacy flat object array.
8
8
  */
9
- import type { AuthStorage, FetchImpl } from "@oh-my-pi/pi-ai";
9
+ import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
10
10
  /** V1 search request body. */
11
11
  export interface KagiSearchRequest {
12
12
  query: string;
@@ -96,5 +96,4 @@ export interface KagiSearchResult {
96
96
  relatedQuestions: string[];
97
97
  answer?: string;
98
98
  }
99
- export declare function findKagiApiKey(authStorage: AuthStorage, sessionId?: string, signal?: AbortSignal): Promise<string | null>;
100
99
  export declare function searchWithKagi(query: string, options: KagiSearchOptions | undefined, authStorage: AuthStorage): Promise<KagiSearchResult>;
@@ -1,4 +1,4 @@
1
- import type { AuthStorage, FetchImpl } from "@oh-my-pi/pi-ai";
1
+ import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
2
2
  import type { SearchResponse } from "../../../web/search/types";
3
3
  import type { SearchParams } from "./base";
4
4
  import { SearchProvider } from "./base";
@@ -8,10 +8,12 @@
8
8
  * sibling SQLite store and never POSTs the broker sentinel to a Google token
9
9
  * endpoint.
10
10
  */
11
- import type { AuthStorage, FetchImpl } from "@oh-my-pi/pi-ai";
11
+ import { type AuthStorage, type FetchImpl, type OAuthAccess } from "@oh-my-pi/pi-ai";
12
12
  import type { SearchResponse } from "../../../web/search/types";
13
13
  import type { SearchParams } from "./base";
14
14
  import { SearchProvider } from "./base";
15
+ declare const GEMINI_PROVIDERS: readonly ["google-gemini-cli", "google-antigravity"];
16
+ type GeminiProviderId = (typeof GEMINI_PROVIDERS)[number];
15
17
  interface GeminiToolParams {
16
18
  google_search?: Record<string, unknown>;
17
19
  code_execution?: Record<string, unknown>;
@@ -31,19 +33,20 @@ export interface GeminiSearchParams extends GeminiToolParams {
31
33
  fetch?: FetchImpl;
32
34
  }
33
35
  export declare function buildGeminiRequestTools(params: GeminiToolParams): Array<Record<string, Record<string, unknown>>>;
34
- /** Resolved auth for a Gemini API request. */
35
- interface GeminiAuth {
36
- accessToken: string;
36
+ /** First configured Gemini OAuth provider plus its pre-resolved access. */
37
+ interface GeminiAuthSeed {
38
+ provider: GeminiProviderId;
39
+ access: OAuthAccess;
37
40
  projectId: string;
38
- isAntigravity: boolean;
39
41
  }
40
42
  /**
41
43
  * Walks the configured Gemini OAuth providers in deterministic order and
42
44
  * returns the first one that yields a usable access token + projectId via
43
45
  * {@link AuthStorage.getOAuthAccess}. AuthStorage handles refresh + broker
44
46
  * routing internally; this helper never touches refresh tokens directly.
47
+ * The resolved access seeds `withOAuthAccess` so the happy path resolves once.
45
48
  */
46
- export declare function findGeminiAuth(authStorage: AuthStorage, sessionId: string | undefined, signal: AbortSignal | undefined): Promise<GeminiAuth | null>;
49
+ export declare function findGeminiAuth(authStorage: AuthStorage, sessionId: string | undefined, signal: AbortSignal | undefined): Promise<GeminiAuthSeed | null>;
47
50
  /**
48
51
  * Executes a web search using Google Gemini with Google Search grounding.
49
52
  */
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": "15.11.3",
4
+ "version": "15.11.4",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,16 +47,16 @@
47
47
  "@agentclientprotocol/sdk": "0.22.1",
48
48
  "@babel/parser": "^7.29.7",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.11.3",
51
- "@oh-my-pi/omp-stats": "15.11.3",
52
- "@oh-my-pi/pi-agent-core": "15.11.3",
53
- "@oh-my-pi/pi-ai": "15.11.3",
54
- "@oh-my-pi/pi-catalog": "15.11.3",
55
- "@oh-my-pi/pi-mnemopi": "15.11.3",
56
- "@oh-my-pi/pi-natives": "15.11.3",
57
- "@oh-my-pi/pi-tui": "15.11.3",
58
- "@oh-my-pi/pi-utils": "15.11.3",
59
- "@oh-my-pi/snapcompact": "15.11.3",
50
+ "@oh-my-pi/hashline": "15.11.4",
51
+ "@oh-my-pi/omp-stats": "15.11.4",
52
+ "@oh-my-pi/pi-agent-core": "15.11.4",
53
+ "@oh-my-pi/pi-ai": "15.11.4",
54
+ "@oh-my-pi/pi-catalog": "15.11.4",
55
+ "@oh-my-pi/pi-mnemopi": "15.11.4",
56
+ "@oh-my-pi/pi-natives": "15.11.4",
57
+ "@oh-my-pi/pi-tui": "15.11.4",
58
+ "@oh-my-pi/pi-utils": "15.11.4",
59
+ "@oh-my-pi/snapcompact": "15.11.4",
60
60
  "@opentelemetry/api": "^1.9.1",
61
61
  "@opentelemetry/context-async-hooks": "^2.7.1",
62
62
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -83,11 +83,7 @@ async function classifyOnline(input: string, deps: ClassifyDifficultyDeps): Prom
83
83
  messages: [{ role: "user", content: input, timestamp: Date.now() }],
84
84
  },
85
85
  {
86
- apiKey: deps.registry.resolver(model.provider, {
87
- sessionId: deps.sessionId,
88
- baseUrl: model.baseUrl,
89
- modelId: model.id,
90
- }),
86
+ apiKey: deps.registry.resolver(model, deps.sessionId),
91
87
  maxTokens,
92
88
  disableReasoning: true,
93
89
  metadata,
@@ -48,7 +48,7 @@ export async function resolvePrimaryModel(
48
48
  }
49
49
  return {
50
50
  model,
51
- apiKey: modelRegistry.resolver(model.provider, { baseUrl: model.baseUrl, modelId: model.id }),
51
+ apiKey: modelRegistry.resolver(model),
52
52
  thinkingLevel: resolved?.thinkingLevel,
53
53
  };
54
54
  }
@@ -66,10 +66,7 @@ export async function resolveSmolModel(
66
66
  if (apiKey) {
67
67
  return {
68
68
  model: resolvedSmol.model,
69
- apiKey: modelRegistry.resolver(resolvedSmol.model.provider, {
70
- baseUrl: resolvedSmol.model.baseUrl,
71
- modelId: resolvedSmol.model.id,
72
- }),
69
+ apiKey: modelRegistry.resolver(resolvedSmol.model),
73
70
  thinkingLevel: resolvedSmol.thinkingLevel,
74
71
  };
75
72
  }
@@ -83,7 +80,7 @@ export async function resolveSmolModel(
83
80
  if (apiKey) {
84
81
  return {
85
82
  model: candidate,
86
- apiKey: modelRegistry.resolver(candidate.provider, { baseUrl: candidate.baseUrl, modelId: candidate.id }),
83
+ apiKey: modelRegistry.resolver(candidate),
87
84
  };
88
85
  }
89
86
  }
@@ -1,4 +1,7 @@
1
- import type { ApiKeyResolver, AuthStorage } from "@oh-my-pi/pi-ai";
1
+ import type { Api, ApiKeyResolver, AuthStorage, Model } from "@oh-my-pi/pi-ai";
2
+
3
+ /** Model slice accepted by the model-form `resolver(model, sessionId)` overload. */
4
+ export type ApiKeyResolverModel = Pick<Model<Api>, "provider" | "baseUrl" | "id">;
2
5
 
3
6
  export interface ApiKeyResolverOptions {
4
7
  /** Session id for credential stickiness; read at resolve time by the caller. */
@@ -26,10 +29,14 @@ export interface ApiKeyResolverRegistry {
26
29
  * policy: initial → resolve; step (b) → force-refresh same account; step (c)
27
30
  * → rotate to a sibling credential, then re-resolve.
28
31
  *
29
- * The resolver is stateless (safe to reuse across requests). Callers that
30
- * need the initial key for a guard can call `resolveApiKeyOnce(resolver)`.
32
+ * Two call forms: `resolver(provider, options?)` for provider-scoped keys,
33
+ * and `resolver(model, sessionId?)` which derives `baseUrl`/`modelId` from
34
+ * the model. The resolver is stateless (safe to reuse across requests).
35
+ * Callers that need the initial key for a guard can call
36
+ * `resolveApiKeyOnce(resolver)`.
31
37
  */
32
38
  resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver;
39
+ resolver(model: ApiKeyResolverModel, sessionId?: string): ApiKeyResolver;
33
40
  }
34
41
 
35
42
  /**
@@ -137,7 +137,7 @@ export const KEYBINDINGS = {
137
137
  },
138
138
  "app.clipboard.pasteImage": {
139
139
  defaultKeys: getDefaultPasteImageKeys(),
140
- description: "Paste image from clipboard",
140
+ description: "Paste image or text from clipboard",
141
141
  },
142
142
  "app.clipboard.pasteTextRaw": {
143
143
  defaultKeys: ["ctrl+shift+v", "alt+shift+v"],