@oh-my-pi/pi-coding-agent 16.3.12 → 16.3.13

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 (49) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cli.js +3244 -3246
  3. package/dist/types/config/keybindings.d.ts +9 -4
  4. package/dist/types/config/settings.d.ts +1 -1
  5. package/dist/types/extensibility/extensions/types.d.ts +11 -2
  6. package/dist/types/mnemopi/state.d.ts +7 -3
  7. package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -0
  8. package/dist/types/modes/components/read-tool-group.d.ts +1 -0
  9. package/dist/types/modes/interactive-mode.d.ts +3 -1
  10. package/dist/types/modes/rpc/rpc-client.d.ts +11 -5
  11. package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
  12. package/dist/types/modes/types.d.ts +3 -1
  13. package/dist/types/session/agent-session.d.ts +3 -4
  14. package/dist/types/tools/read.d.ts +1 -0
  15. package/dist/types/tools/renderers.d.ts +12 -5
  16. package/dist/types/tools/ssh.d.ts +4 -1
  17. package/dist/types/tools/write.d.ts +1 -0
  18. package/package.json +12 -12
  19. package/src/config/keybindings.ts +62 -10
  20. package/src/config/model-registry.ts +85 -20
  21. package/src/config/settings.ts +48 -21
  22. package/src/extensibility/extensions/runner.ts +1 -0
  23. package/src/extensibility/extensions/types.ts +13 -2
  24. package/src/internal-urls/docs-index.generated.txt +1 -1
  25. package/src/mnemopi/state.ts +19 -5
  26. package/src/modes/acp/acp-agent.ts +69 -8
  27. package/src/modes/acp/acp-event-mapper.ts +1 -1
  28. package/src/modes/components/read-tool-group.ts +5 -1
  29. package/src/modes/components/tool-execution.ts +28 -24
  30. package/src/modes/controllers/extension-ui-controller.test.ts +16 -0
  31. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  32. package/src/modes/controllers/input-controller.ts +5 -55
  33. package/src/modes/interactive-mode.ts +43 -2
  34. package/src/modes/rpc/rpc-client.ts +42 -13
  35. package/src/modes/rpc/rpc-mode.ts +21 -19
  36. package/src/modes/types.ts +3 -0
  37. package/src/prompts/agents/plan.md +0 -1
  38. package/src/prompts/agents/reviewer.md +0 -1
  39. package/src/prompts/tools/grep.md +2 -1
  40. package/src/prompts/tools/memory-edit.md +2 -0
  41. package/src/session/agent-session.ts +8 -5
  42. package/src/tools/grep.ts +58 -13
  43. package/src/tools/image-gen.ts +1 -1
  44. package/src/tools/memory-edit.ts +3 -1
  45. package/src/tools/read.ts +33 -13
  46. package/src/tools/renderers.ts +13 -5
  47. package/src/tools/ssh.ts +10 -3
  48. package/src/tools/tts.ts +1 -1
  49. package/src/tools/write.ts +26 -0
@@ -22,6 +22,7 @@ import {
22
22
  getProjectDir,
23
23
  isEnoent,
24
24
  logger,
25
+ MAIN_CONFIG_FILENAMES,
25
26
  procmgr,
26
27
  setWorktreesDir,
27
28
  } from "@oh-my-pi/pi-utils";
@@ -60,7 +61,7 @@ export interface RawSettings {
60
61
  export interface SettingsOptions {
61
62
  /** Current working directory for project settings discovery */
62
63
  cwd?: string;
63
- /** Agent directory for config.yml storage */
64
+ /** Agent directory for config.yml/config.yaml storage */
64
65
  agentDir?: string;
65
66
  /** Don't persist to disk (for tests) */
66
67
  inMemory?: boolean;
@@ -234,7 +235,7 @@ export class Settings {
234
235
  #storage: AgentStorage | null = null;
235
236
 
236
237
  #configFiles: string[] = [];
237
- /** Global settings from config.yml */
238
+ /** Global settings from config.yml/config.yaml */
238
239
  #global: RawSettings = {};
239
240
  /** Project settings from .claude/settings.yml etc */
240
241
  #project: RawSettings = {};
@@ -264,7 +265,7 @@ export class Settings {
264
265
  private constructor(options: SettingsOptions = {}) {
265
266
  this.#cwd = path.normalize(options.cwd ?? getProjectDir());
266
267
  this.#agentDir = path.normalize(options.agentDir ?? getAgentDir());
267
- this.#configPath = options.inMemory ? null : path.join(this.#agentDir, "config.yml");
268
+ this.#configPath = options.inMemory ? null : path.join(this.#agentDir, MAIN_CONFIG_FILENAMES[0]);
268
269
  this.#configFiles = options.configFiles?.map(file => path.resolve(this.#cwd, expandTilde(file))) ?? [];
269
270
  this.#persist = !options.inMemory && options.readOnly !== true;
270
271
 
@@ -458,6 +459,7 @@ export class Settings {
458
459
  inMemory: !this.#persist,
459
460
  });
460
461
  cloned.#storage = this.#storage;
462
+ cloned.#configPath = this.#configPath;
461
463
  cloned.#global = structuredClone(this.#global);
462
464
  cloned.#project = this.#persist ? await cloned.#loadProjectSettings() : structuredClone(this.#project);
463
465
  cloned.#configFiles = [...this.#configFiles];
@@ -676,16 +678,22 @@ export class Settings {
676
678
 
677
679
  async #load(): Promise<Settings> {
678
680
  // Project settings load (loadCapability scans cwd) is independent of the
679
- // persist chain (storage open → legacy migration → global config.yml read),
680
- // so kick it off first and await after the persist chain completes. The
681
- // persist steps remain sequential: migration may write config.yml, which
682
- // #loadYaml then reads; migration's db fallback needs #storage opened.
681
+ // persist chain (storage open → legacy migration → global config read), so
682
+ // kick it off first and await after the persist chain completes. The
683
+ // persist steps remain sequential: existing config discovery decides
684
+ // whether migration may write config.yml before the global config is read;
685
+ // migration's db fallback needs #storage opened.
683
686
  const projectPromise = this.#loadProjectSettings();
684
687
 
685
688
  if (this.#persist) {
686
689
  this.#storage = await AgentStorage.open(getAgentDbPath(this.#agentDir));
687
- await this.#migrateFromLegacy();
688
- this.#global = await this.#loadYaml(this.#configPath!);
690
+ const existingConfig = await this.#loadExistingMainYaml();
691
+ if (existingConfig) {
692
+ this.#global = existingConfig;
693
+ } else {
694
+ await this.#migrateFromLegacy();
695
+ this.#global = await this.#loadYaml(this.#configPath!);
696
+ }
689
697
  await this.#seedLastChangelogVersionMarker();
690
698
  }
691
699
 
@@ -701,8 +709,9 @@ export class Settings {
701
709
  async #loadReadOnly(): Promise<Settings> {
702
710
  const projectPromise = this.#loadProjectSettings();
703
711
 
704
- if (this.#configPath) {
705
- this.#global = await this.#loadYaml(this.#configPath);
712
+ const existingConfig = await this.#loadExistingMainYaml();
713
+ if (existingConfig) {
714
+ this.#global = existingConfig;
706
715
  }
707
716
 
708
717
  this.#project = await projectPromise;
@@ -712,20 +721,46 @@ export class Settings {
712
721
  }
713
722
 
714
723
  async #loadYaml(filePath: string): Promise<RawSettings> {
724
+ const loaded = await this.#loadYamlIfPresent(filePath);
725
+ return loaded ?? {};
726
+ }
727
+
728
+ async #loadYamlIfPresent(filePath: string): Promise<RawSettings | null> {
729
+ let content: string;
730
+ try {
731
+ content = await Bun.file(filePath).text();
732
+ } catch (error) {
733
+ if (isEnoent(error)) return null;
734
+ logger.warn("Settings: failed to load", { path: filePath, error: String(error) });
735
+ return {};
736
+ }
737
+
715
738
  try {
716
- const content = await Bun.file(filePath).text();
717
739
  const parsed = YAML.parse(content);
718
740
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
719
741
  return {};
720
742
  }
721
743
  return this.#migrateRawSettings(parsed as RawSettings);
722
744
  } catch (error) {
723
- if (isEnoent(error)) return {};
724
745
  logger.warn("Settings: failed to load", { path: filePath, error: String(error) });
725
746
  return {};
726
747
  }
727
748
  }
728
749
 
750
+ async #loadExistingMainYaml(): Promise<RawSettings | null> {
751
+ if (!this.#configPath) return null;
752
+ for (const filename of MAIN_CONFIG_FILENAMES) {
753
+ const configPath = path.join(this.#agentDir, filename);
754
+ const loaded = await this.#loadYamlIfPresent(configPath);
755
+ if (loaded) {
756
+ this.#configPath = configPath;
757
+ return loaded;
758
+ }
759
+ }
760
+ this.#configPath = path.join(this.#agentDir, MAIN_CONFIG_FILENAMES[0]);
761
+ return null;
762
+ }
763
+
729
764
  async #loadProjectSettings(): Promise<RawSettings> {
730
765
  try {
731
766
  const result = await loadCapability(settingsCapability.id, { cwd: this.#cwd });
@@ -781,14 +816,6 @@ export class Settings {
781
816
  async #migrateFromLegacy(): Promise<void> {
782
817
  if (!this.#configPath) return;
783
818
 
784
- // Check if config.yml already exists
785
- try {
786
- await Bun.file(this.#configPath).text();
787
- return; // Already exists, no migration needed
788
- } catch (err) {
789
- if (!isEnoent(err)) return;
790
- }
791
-
792
819
  let settings: RawSettings = {};
793
820
  let migrated = false;
794
821
 
@@ -207,6 +207,7 @@ const noOpUIContext: ExtensionUIContext = {
207
207
  pasteToEditor: () => {},
208
208
  getEditorText: () => "",
209
209
  editor: async () => undefined,
210
+ addAutocompleteProvider: () => {},
210
211
  setEditorComponent: () => {},
211
212
  get theme() {
212
213
  return theme;
@@ -30,7 +30,7 @@ import type {
30
30
  TSchema,
31
31
  } from "@oh-my-pi/pi-ai";
32
32
  import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
33
- import type { AutocompleteItem, Component, EditorTheme, KeyId, TUI } from "@oh-my-pi/pi-tui";
33
+ import type { AutocompleteItem, AutocompleteProvider, Component, EditorTheme, KeyId, TUI } from "@oh-my-pi/pi-tui";
34
34
  import type { logger as PiLogger } from "@oh-my-pi/pi-utils";
35
35
  import type { Type as arktype } from "arktype";
36
36
  import type * as zod from "zod/v4";
@@ -163,6 +163,9 @@ export type ExtensionUiComponent = Component & { dispose?(): void };
163
163
  export type ExtensionUiComponentFactory = (tui: TUI, theme: Theme) => ExtensionUiComponent;
164
164
  export type ExtensionWidgetContent = string[] | ExtensionUiComponentFactory | undefined;
165
165
 
166
+ /** Wrap the current autocomplete provider with additional behavior (pi-compatible). */
167
+ export type AutocompleteProviderFactory = (current: AutocompleteProvider) => AutocompleteProvider;
168
+
166
169
  /**
167
170
  * UI context for extensions to request interactive UI.
168
171
  * Each mode (interactive, RPC, print) provides its own implementation.
@@ -243,6 +246,14 @@ export interface ExtensionUIContext {
243
246
  editorOptions?: { promptStyle?: boolean },
244
247
  ): Promise<string | undefined>;
245
248
 
249
+ /**
250
+ * Stack additional autocomplete behavior on top of the built-in provider
251
+ * (pi-compatible). Interactive mode rebuilds the editor's provider through
252
+ * every registered factory, in registration order; headless modes (print,
253
+ * RPC, ACP, subagents) accept and ignore the factory.
254
+ */
255
+ addAutocompleteProvider(factory: AutocompleteProviderFactory): void;
256
+
246
257
  /**
247
258
  * Set a custom editor component via factory function, or `undefined` to restore the default editor.
248
259
  *
@@ -1107,7 +1118,7 @@ export interface ExtensionAPI {
1107
1118
  options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
1108
1119
  ): void;
1109
1120
 
1110
- /** Send a user message to the agent, or queue it when deliverAs is set. */
1121
+ /** Send a user prompt: idle starts a turn; streaming queues as steer unless deliverAs is set. */
1111
1122
  sendUserMessage(
1112
1123
  content: string | (TextContent | ImageContent)[],
1113
1124
  options?: { deliverAs?: "steer" | "followUp" },