@oh-my-pi/pi-coding-agent 16.1.16 → 16.1.18

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 (75) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/cli.js +16646 -16608
  3. package/dist/types/auto-thinking/classifier.d.ts +4 -2
  4. package/dist/types/cli/usage-cli.d.ts +14 -0
  5. package/dist/types/commands/token.d.ts +9 -0
  6. package/dist/types/config/model-discovery.d.ts +1 -0
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/extensibility/plugins/legacy-pi-bundled-registry.d.ts +7 -0
  9. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +34 -15
  10. package/dist/types/extensibility/plugins/loader.d.ts +16 -9
  11. package/dist/types/extensibility/tool-event-input.d.ts +2 -0
  12. package/dist/types/hindsight/content.d.ts +2 -10
  13. package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
  14. package/dist/types/modes/components/custom-editor.d.ts +3 -0
  15. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +26 -10
  16. package/dist/types/modes/components/extensions/extension-list.d.ts +13 -0
  17. package/dist/types/modes/components/status-line/types.d.ts +1 -0
  18. package/dist/types/modes/controllers/command-controller.d.ts +2 -0
  19. package/dist/types/modes/controllers/input-controller.d.ts +14 -0
  20. package/dist/types/modes/controllers/selector-controller.d.ts +11 -0
  21. package/dist/types/session/agent-session.d.ts +6 -6
  22. package/dist/types/session/provider-image-budget.d.ts +3 -0
  23. package/dist/types/session/session-context.d.ts +6 -5
  24. package/dist/types/task/parallel.d.ts +4 -0
  25. package/dist/types/thinking.d.ts +8 -1
  26. package/dist/types/tiny/title-client.d.ts +2 -2
  27. package/dist/types/utils/tools-manager.d.ts +2 -0
  28. package/package.json +12 -12
  29. package/scripts/build-binary.ts +9 -16
  30. package/src/auto-thinking/classifier.ts +7 -2
  31. package/src/cli/profile-alias.ts +38 -7
  32. package/src/cli/usage-cli.ts +40 -5
  33. package/src/commands/token.ts +54 -0
  34. package/src/config/model-discovery.ts +59 -8
  35. package/src/config/model-registry.ts +74 -3
  36. package/src/discovery/omp-extension-roots.ts +1 -3
  37. package/src/extensibility/extensions/wrapper.ts +3 -2
  38. package/src/extensibility/hooks/tool-wrapper.ts +4 -3
  39. package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +40 -0
  40. package/src/extensibility/plugins/legacy-pi-compat.ts +240 -90
  41. package/src/extensibility/plugins/loader.ts +71 -23
  42. package/src/extensibility/plugins/marketplace/manager.ts +134 -0
  43. package/src/extensibility/tool-event-input.ts +57 -0
  44. package/src/hindsight/content.ts +21 -12
  45. package/src/mcp/tool-bridge.ts +27 -2
  46. package/src/mnemopi/state.ts +5 -2
  47. package/src/modes/components/agent-transcript-viewer.ts +193 -41
  48. package/src/modes/components/chat-transcript-builder.ts +6 -0
  49. package/src/modes/components/custom-editor.test.ts +18 -1
  50. package/src/modes/components/custom-editor.ts +77 -45
  51. package/src/modes/components/extensions/extension-dashboard.ts +221 -165
  52. package/src/modes/components/extensions/extension-list.ts +66 -33
  53. package/src/modes/components/hook-editor.ts +15 -2
  54. package/src/modes/components/settings-selector.ts +2 -2
  55. package/src/modes/components/status-line/component.ts +52 -8
  56. package/src/modes/components/status-line/segments.ts +5 -1
  57. package/src/modes/components/status-line/types.ts +1 -0
  58. package/src/modes/components/welcome.ts +12 -14
  59. package/src/modes/controllers/command-controller.ts +16 -5
  60. package/src/modes/controllers/input-controller.ts +115 -3
  61. package/src/modes/controllers/selector-controller.ts +23 -1
  62. package/src/modes/interactive-mode.ts +3 -3
  63. package/src/modes/utils/ui-helpers.ts +3 -3
  64. package/src/sdk.ts +8 -10
  65. package/src/session/agent-session.ts +193 -49
  66. package/src/session/provider-image-budget.ts +86 -0
  67. package/src/session/session-context.ts +14 -7
  68. package/src/session/session-storage.ts +24 -2
  69. package/src/session/snapcompact-inline.ts +19 -3
  70. package/src/slash-commands/builtin-registry.ts +0 -22
  71. package/src/slash-commands/helpers/usage-report.ts +9 -1
  72. package/src/task/parallel.ts +6 -1
  73. package/src/thinking.ts +9 -2
  74. package/src/tiny/title-client.ts +75 -21
  75. package/src/utils/tools-manager.ts +67 -10
@@ -103,6 +103,12 @@ export class ChatTranscriptBuilder {
103
103
  if (this.#readArgs.size === 0 && this.#pendingTools.size === 0) this.#flushPendingUsage();
104
104
  }
105
105
 
106
+ /** Append newly persisted entries without rebuilding already rendered rows. */
107
+ append(entries: SessionMessageEntry[]): void {
108
+ for (const entry of entries) this.#appendChatMessage(entry.message);
109
+ if (this.#readArgs.size === 0 && this.#pendingTools.size === 0) this.#flushPendingUsage();
110
+ }
111
+
106
112
  /** Toggle tool-output expansion across every expandable component. */
107
113
  setExpanded(expanded: boolean): void {
108
114
  this.#expanded = expanded;
@@ -4,6 +4,7 @@ import { getEditorTheme, initTheme } from "../theme/theme";
4
4
  import {
5
5
  CustomEditor,
6
6
  extractBracketedImagePastePaths,
7
+ extractBracketedPastePaths,
7
8
  SPACE_HOLD_MECHANICAL_RUN,
8
9
  SPACE_HOLD_RELEASE_MS,
9
10
  SPACE_REPEAT_MAX_GAP_MS,
@@ -73,7 +74,7 @@ describe("CustomEditor placeholder decoration", () => {
73
74
  });
74
75
  });
75
76
 
76
- describe("CustomEditor bracketed image-path paste", () => {
77
+ describe("CustomEditor bracketed path paste", () => {
77
78
  it("leaves a pasted bare .png filename on the normal text path", () => {
78
79
  expect(extractBracketedImagePastePaths(bracketedPaste("icon-photo-default.png"))).toBeUndefined();
79
80
  });
@@ -86,6 +87,22 @@ describe("CustomEditor bracketed image-path paste", () => {
86
87
  "C:\\Users\\me\\icon-photo-default.png",
87
88
  ]);
88
89
  });
90
+
91
+ it("extracts explicit non-image paths without classifying them as image paths", () => {
92
+ expect(extractBracketedPastePaths(bracketedPaste("/tmp/report.csv"))).toEqual(["/tmp/report.csv"]);
93
+ expect(extractBracketedImagePastePaths(bracketedPaste("/tmp/report.csv"))).toBeUndefined();
94
+ });
95
+
96
+ it("routes non-image path pastes through the file-path hook", async () => {
97
+ const { editor } = makeEditor();
98
+ const pasted = Promise.withResolvers<string>();
99
+ editor.onPasteFilePath = path => pasted.resolve(path);
100
+
101
+ editor.handleInput(bracketedPaste("/tmp/report.csv"));
102
+
103
+ expect(await pasted.promise).toBe("/tmp/report.csv");
104
+ expect(editor.getText()).toBe("");
105
+ });
89
106
  });
90
107
 
91
108
  describe("CustomEditor space-hold push-to-talk", () => {
@@ -62,7 +62,6 @@ function buildMatchKeys(keys: readonly KeyId[]): Set<string> {
62
62
  const BRACKETED_PASTE_START = "\x1b[200~";
63
63
  const BRACKETED_PASTE_END = "\x1b[201~";
64
64
  const BRACKETED_IMAGE_PATH_REGEX = /\.(?:png|jpe?g|gif|webp)$/i;
65
- const BRACKETED_IMAGE_PATH_BOUNDARY_REGEX = /\.(?:png|jpe?g|gif|webp)(?=$|["']?\s)/gi;
66
65
  const SHELL_ESCAPED_PATH_CHAR_REGEX = /\\([\\\s'"()[\]{}&;<>|?*!$`])/g;
67
66
  const URI_SCHEME_REGEX = /^[a-z][a-z0-9+.-]*:/i;
68
67
  const FILE_URI_REGEX = /^file:\/\//i;
@@ -100,19 +99,7 @@ function isPastedPathSeparator(char: string | undefined): boolean {
100
99
  return char === undefined || char === " " || char === "\t" || char === "\r" || char === "\n";
101
100
  }
102
101
 
103
- function imagePathBoundaryEnd(payload: string, segmentStart: number, extensionEnd: number): number | undefined {
104
- const quote = payload[segmentStart];
105
- const afterExtension = payload[extensionEnd];
106
- if (quote === '"' || quote === "'") {
107
- return afterExtension === quote && isPastedPathSeparator(payload[extensionEnd + 1])
108
- ? extensionEnd + 1
109
- : undefined;
110
- }
111
- if (isPastedPathSeparator(afterExtension)) return extensionEnd;
112
- return undefined;
113
- }
114
-
115
- function normalizePastedImagePath(path: string): string {
102
+ function normalizePastedPath(path: string): string {
116
103
  const trimmed = path.trim();
117
104
  const first = trimmed[0];
118
105
  const last = trimmed[trimmed.length - 1];
@@ -121,13 +108,60 @@ function normalizePastedImagePath(path: string): string {
121
108
  return unquoted.replace(SHELL_ESCAPED_PATH_CHAR_REGEX, "$1");
122
109
  }
123
110
 
124
- function isExplicitPastedImagePath(path: string): boolean {
111
+ function isExplicitPastedPath(path: string): boolean {
125
112
  if (WINDOWS_DRIVE_PATH_REGEX.test(path) || FILE_URI_REGEX.test(path)) return true;
126
113
  if (URI_SCHEME_REGEX.test(path)) return false;
127
114
  return path.includes("/") || path.includes("\\");
128
115
  }
129
116
 
130
- export function extractBracketedImagePastePaths(data: string): string[] | undefined {
117
+ function isImagePath(path: string): boolean {
118
+ return BRACKETED_IMAGE_PATH_REGEX.test(path);
119
+ }
120
+
121
+ function splitPastedPathSegments(payload: string): string[] | undefined {
122
+ const segments: string[] = [];
123
+ let segment = "";
124
+ let quote: string | undefined;
125
+ let escaped = false;
126
+
127
+ for (let i = 0; i < payload.length; i++) {
128
+ const char = payload[i];
129
+ if (escaped) {
130
+ segment += char;
131
+ escaped = false;
132
+ continue;
133
+ }
134
+ if (char === "\\") {
135
+ segment += char;
136
+ escaped = true;
137
+ continue;
138
+ }
139
+ if (quote) {
140
+ segment += char;
141
+ if (char === quote) quote = undefined;
142
+ continue;
143
+ }
144
+ if (char === '"' || char === "'") {
145
+ segment += char;
146
+ quote = char;
147
+ continue;
148
+ }
149
+ if (isPastedPathSeparator(char)) {
150
+ if (segment) {
151
+ segments.push(segment);
152
+ segment = "";
153
+ }
154
+ continue;
155
+ }
156
+ segment += char;
157
+ }
158
+
159
+ if (escaped || quote) return undefined;
160
+ if (segment) segments.push(segment);
161
+ return segments.length > 0 ? segments : undefined;
162
+ }
163
+
164
+ export function extractBracketedPastePaths(data: string): string[] | undefined {
131
165
  if (!data.startsWith(BRACKETED_PASTE_START)) return undefined;
132
166
  const endIndex = data.indexOf(BRACKETED_PASTE_END, BRACKETED_PASTE_START.length);
133
167
  if (endIndex === -1 || endIndex + BRACKETED_PASTE_END.length !== data.length) return undefined;
@@ -135,33 +169,23 @@ export function extractBracketedImagePastePaths(data: string): string[] | undefi
135
169
  const pasted = data.slice(BRACKETED_PASTE_START.length, endIndex).trim();
136
170
  if (!pasted) return undefined;
137
171
 
172
+ const segments = splitPastedPathSegments(pasted);
173
+ if (!segments) return undefined;
174
+
138
175
  const paths: string[] = [];
139
- let segmentStart = 0;
140
- BRACKETED_IMAGE_PATH_BOUNDARY_REGEX.lastIndex = 0;
141
- for (
142
- let match = BRACKETED_IMAGE_PATH_BOUNDARY_REGEX.exec(pasted);
143
- match;
144
- match = BRACKETED_IMAGE_PATH_BOUNDARY_REGEX.exec(pasted)
145
- ) {
146
- const extensionEnd = match.index + match[0].length;
147
- const boundaryEnd = imagePathBoundaryEnd(pasted, segmentStart, extensionEnd);
148
- if (boundaryEnd === undefined) continue;
149
-
150
- const path = normalizePastedImagePath(pasted.slice(segmentStart, boundaryEnd));
151
- if (!path || !BRACKETED_IMAGE_PATH_REGEX.test(path) || !isExplicitPastedImagePath(path)) return undefined;
176
+ for (const segment of segments) {
177
+ const path = normalizePastedPath(segment);
178
+ if (!path || !isExplicitPastedPath(path)) return undefined;
152
179
  paths.push(path);
153
-
154
- segmentStart = boundaryEnd;
155
- while (segmentStart < pasted.length && isPastedPathSeparator(pasted[segmentStart])) {
156
- segmentStart++;
157
- }
158
- BRACKETED_IMAGE_PATH_BOUNDARY_REGEX.lastIndex = segmentStart;
159
180
  }
160
-
161
- if (paths.length === 0 || segmentStart !== pasted.length) return undefined;
162
181
  return paths;
163
182
  }
164
183
 
184
+ export function extractBracketedImagePastePaths(data: string): string[] | undefined {
185
+ const paths = extractBracketedPastePaths(data);
186
+ return paths?.every(isImagePath) ? paths : undefined;
187
+ }
188
+
165
189
  export function extractBracketedImagePastePath(data: string): string | undefined {
166
190
  const paths = extractBracketedImagePastePaths(data);
167
191
  return paths?.length === 1 ? paths[0] : undefined;
@@ -294,6 +318,8 @@ export class CustomEditor extends Editor {
294
318
  onPasteImage?: () => Promise<boolean>;
295
319
  /** Called when a bracketed paste contains one or more image-file paths. */
296
320
  onPasteImagePath?: (path: string) => void | Promise<void>;
321
+ /** Called when a bracketed paste contains one or more non-image file paths. */
322
+ onPasteFilePath?: (path: string) => void | Promise<void>;
297
323
  /** Called when the configured raw text-paste shortcut is pressed. */
298
324
  onPasteTextRaw?: () => void;
299
325
  /** Called when the configured dequeue shortcut is pressed. */
@@ -479,14 +505,20 @@ export class CustomEditor extends Editor {
479
505
  return;
480
506
  }
481
507
 
482
- const pastedImagePaths = extractBracketedImagePastePaths(data);
483
- if (pastedImagePaths && this.onPasteImagePath) {
484
- void (async () => {
485
- for (const path of pastedImagePaths) {
486
- await this.onPasteImagePath?.(path);
487
- }
488
- })();
489
- return;
508
+ const pastedPaths = extractBracketedPastePaths(data);
509
+ if (pastedPaths) {
510
+ const canHandlePaths = pastedPaths.every(path =>
511
+ isImagePath(path) ? this.onPasteImagePath !== undefined : this.onPasteFilePath !== undefined,
512
+ );
513
+ if (canHandlePaths) {
514
+ void (async () => {
515
+ for (const path of pastedPaths) {
516
+ if (isImagePath(path)) await this.onPasteImagePath?.(path);
517
+ else await this.onPasteFilePath?.(path);
518
+ }
519
+ })();
520
+ return;
521
+ }
490
522
  }
491
523
 
492
524
  const parsedKey = parseKey(data);