@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.
- package/CHANGELOG.md +49 -0
- package/dist/cli.js +16646 -16608
- package/dist/types/auto-thinking/classifier.d.ts +4 -2
- package/dist/types/cli/usage-cli.d.ts +14 -0
- package/dist/types/commands/token.d.ts +9 -0
- package/dist/types/config/model-discovery.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/extensibility/plugins/legacy-pi-bundled-registry.d.ts +7 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +34 -15
- package/dist/types/extensibility/plugins/loader.d.ts +16 -9
- package/dist/types/extensibility/tool-event-input.d.ts +2 -0
- package/dist/types/hindsight/content.d.ts +2 -10
- package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +26 -10
- package/dist/types/modes/components/extensions/extension-list.d.ts +13 -0
- package/dist/types/modes/components/status-line/types.d.ts +1 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +14 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +11 -0
- package/dist/types/session/agent-session.d.ts +6 -6
- package/dist/types/session/provider-image-budget.d.ts +3 -0
- package/dist/types/session/session-context.d.ts +6 -5
- package/dist/types/task/parallel.d.ts +4 -0
- package/dist/types/thinking.d.ts +8 -1
- package/dist/types/tiny/title-client.d.ts +2 -2
- package/dist/types/utils/tools-manager.d.ts +2 -0
- package/package.json +12 -12
- package/scripts/build-binary.ts +9 -16
- package/src/auto-thinking/classifier.ts +7 -2
- package/src/cli/profile-alias.ts +38 -7
- package/src/cli/usage-cli.ts +40 -5
- package/src/commands/token.ts +54 -0
- package/src/config/model-discovery.ts +59 -8
- package/src/config/model-registry.ts +74 -3
- package/src/discovery/omp-extension-roots.ts +1 -3
- package/src/extensibility/extensions/wrapper.ts +3 -2
- package/src/extensibility/hooks/tool-wrapper.ts +4 -3
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +40 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +240 -90
- package/src/extensibility/plugins/loader.ts +71 -23
- package/src/extensibility/plugins/marketplace/manager.ts +134 -0
- package/src/extensibility/tool-event-input.ts +57 -0
- package/src/hindsight/content.ts +21 -12
- package/src/mcp/tool-bridge.ts +27 -2
- package/src/mnemopi/state.ts +5 -2
- package/src/modes/components/agent-transcript-viewer.ts +193 -41
- package/src/modes/components/chat-transcript-builder.ts +6 -0
- package/src/modes/components/custom-editor.test.ts +18 -1
- package/src/modes/components/custom-editor.ts +77 -45
- package/src/modes/components/extensions/extension-dashboard.ts +221 -165
- package/src/modes/components/extensions/extension-list.ts +66 -33
- package/src/modes/components/hook-editor.ts +15 -2
- package/src/modes/components/settings-selector.ts +2 -2
- package/src/modes/components/status-line/component.ts +52 -8
- package/src/modes/components/status-line/segments.ts +5 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/components/welcome.ts +12 -14
- package/src/modes/controllers/command-controller.ts +16 -5
- package/src/modes/controllers/input-controller.ts +115 -3
- package/src/modes/controllers/selector-controller.ts +23 -1
- package/src/modes/interactive-mode.ts +3 -3
- package/src/modes/utils/ui-helpers.ts +3 -3
- package/src/sdk.ts +8 -10
- package/src/session/agent-session.ts +193 -49
- package/src/session/provider-image-budget.ts +86 -0
- package/src/session/session-context.ts +14 -7
- package/src/session/session-storage.ts +24 -2
- package/src/session/snapcompact-inline.ts +19 -3
- package/src/slash-commands/builtin-registry.ts +0 -22
- package/src/slash-commands/helpers/usage-report.ts +9 -1
- package/src/task/parallel.ts +6 -1
- package/src/thinking.ts +9 -2
- package/src/tiny/title-client.ts +75 -21
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
483
|
-
if (
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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);
|