@oh-my-pi/pi-coding-agent 6.7.670 → 6.8.0
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 +28 -0
- package/package.json +6 -7
- package/src/cli/session-picker.ts +27 -28
- package/src/cli/setup-cli.ts +7 -16
- package/src/cli/update-cli.ts +1 -1
- package/src/config.ts +1 -1
- package/src/core/agent-session.ts +202 -37
- package/src/core/agent-storage.ts +1 -1
- package/src/core/auth-storage.ts +15 -25
- package/src/core/bash-executor.ts +63 -105
- package/src/core/custom-commands/loader.ts +1 -1
- package/src/core/custom-tools/loader.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -2
- package/src/core/exec.ts +16 -100
- package/src/core/extensions/index.ts +1 -7
- package/src/core/extensions/loader.ts +1 -1
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +2 -2
- package/src/core/extensions/wrapper.ts +15 -20
- package/src/core/frontmatter.ts +1 -1
- package/src/core/history-storage.ts +3 -6
- package/src/core/hooks/index.ts +2 -2
- package/src/core/hooks/loader.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +14 -26
- package/src/core/hooks/types.ts +1 -2
- package/src/core/keybindings.ts +1 -1
- package/src/core/mcp/client.ts +13 -13
- package/src/core/mcp/json-rpc.ts +1 -1
- package/src/core/mcp/loader.ts +1 -1
- package/src/core/mcp/manager.ts +2 -2
- package/src/core/mcp/tool-cache.ts +1 -1
- package/src/core/mcp/transports/http.ts +32 -70
- package/src/core/model-registry.ts +1 -1
- package/src/core/plugins/installer.ts +13 -11
- package/src/core/prompt-templates.ts +4 -9
- package/src/core/python-executor.ts +23 -18
- package/src/core/python-gateway-coordinator.ts +29 -28
- package/src/core/python-kernel.ts +230 -211
- package/src/core/sdk.ts +10 -13
- package/src/core/session-manager.ts +1 -1
- package/src/core/settings-manager.ts +22 -9
- package/src/core/skills.ts +1 -1
- package/src/core/ssh/connection-manager.ts +19 -33
- package/src/core/ssh/ssh-executor.ts +39 -35
- package/src/core/ssh/sshfs-mount.ts +14 -33
- package/src/core/storage-migration.ts +1 -1
- package/src/core/streaming-output.ts +183 -127
- package/src/core/system-prompt.ts +119 -79
- package/src/core/title-generator.ts +1 -1
- package/src/core/tools/ask.ts +2 -2
- package/src/core/tools/bash.ts +3 -3
- package/src/core/tools/calculator.ts +1 -1
- package/src/core/tools/exa/mcp-client.ts +1 -1
- package/src/core/tools/exa/render.ts +1 -1
- package/src/core/tools/find.ts +39 -71
- package/src/core/tools/gemini-image.ts +1 -1
- package/src/core/tools/grep.ts +88 -100
- package/src/core/tools/index.ts +1 -1
- package/src/core/tools/ls.ts +1 -1
- package/src/core/tools/lsp/client.ts +50 -50
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +1 -1
- package/src/core/tools/lsp/config.ts +1 -1
- package/src/core/tools/lsp/index.ts +2 -4
- package/src/core/tools/lsp/lspmux.ts +1 -1
- package/src/core/tools/lsp/rust-analyzer.ts +2 -2
- package/src/core/tools/lsp/utils.ts +0 -14
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/patch/shared.ts +3 -4
- package/src/core/tools/python.ts +3 -3
- package/src/core/tools/read.ts +29 -68
- package/src/core/tools/render-utils.ts +0 -5
- package/src/core/tools/ssh.ts +3 -3
- package/src/core/tools/task/model-resolver.ts +7 -9
- package/src/core/tools/task/worker.ts +144 -139
- package/src/core/tools/todo-write.ts +1 -1
- package/src/core/tools/truncate.ts +2 -2
- package/src/core/tools/web-fetch.ts +13 -15
- package/src/core/tools/web-scrapers/types.ts +1 -3
- package/src/core/tools/web-scrapers/utils.ts +14 -13
- package/src/core/tools/web-scrapers/youtube.ts +39 -12
- package/src/core/tools/web-search/auth.ts +1 -1
- package/src/core/tools/write.ts +1 -1
- package/src/core/ttsr.ts +1 -1
- package/src/core/utils.ts +1 -187
- package/src/core/voice-controller.ts +1 -1
- package/src/core/voice-supervisor.ts +11 -38
- package/src/core/voice.ts +1 -8
- package/src/discovery/codex.ts +1 -1
- package/src/index.ts +4 -4
- package/src/main.ts +5 -10
- package/src/migrations.ts +1 -1
- package/src/modes/index.ts +7 -40
- package/src/modes/interactive/components/extensions/state-manager.ts +1 -1
- package/src/modes/interactive/components/hook-editor.ts +12 -9
- package/src/modes/interactive/components/login-dialog.ts +24 -11
- package/src/modes/interactive/components/settings-defs.ts +9 -0
- package/src/modes/interactive/components/status-line.ts +36 -35
- package/src/modes/interactive/components/todo-display.ts +1 -1
- package/src/modes/interactive/components/tool-execution.ts +1 -1
- package/src/modes/interactive/controllers/command-controller.ts +50 -84
- package/src/modes/interactive/controllers/extension-ui-controller.ts +76 -76
- package/src/modes/interactive/controllers/input-controller.ts +12 -11
- package/src/modes/interactive/interactive-mode.ts +10 -11
- package/src/modes/interactive/theme/theme.ts +1 -1
- package/src/modes/interactive/types.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +91 -121
- package/src/modes/rpc/rpc-mode.ts +71 -79
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/utils/clipboard.ts +57 -141
- package/src/utils/shell-snapshot.ts +12 -60
- package/src/utils/shell.ts +35 -56
- package/src/utils/tools-manager.ts +42 -71
- package/src/core/logger.ts +0 -111
- package/src/modes/cleanup.ts +0 -23
|
@@ -1,154 +1,210 @@
|
|
|
1
1
|
import { tmpdir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import { nanoid } from "nanoid";
|
|
4
|
-
import
|
|
5
|
-
import { truncateTail } from "./tools/truncate";
|
|
5
|
+
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_COLUMN } from "./tools/truncate";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* - Lone surrogates
|
|
12
|
-
* - Unicode Format characters (crash string-width due to a bug)
|
|
13
|
-
* - Characters with undefined code points
|
|
14
|
-
*/
|
|
15
|
-
export function sanitizeBinaryOutput(str: string): string {
|
|
16
|
-
// Use Array.from to properly iterate over code points (not code units)
|
|
17
|
-
// This handles surrogate pairs correctly and catches edge cases where
|
|
18
|
-
// codePointAt() might return undefined
|
|
19
|
-
return Array.from(str)
|
|
20
|
-
.filter((char) => {
|
|
21
|
-
// Filter out characters that cause string-width to crash
|
|
22
|
-
// This includes:
|
|
23
|
-
// - Unicode format characters
|
|
24
|
-
// - Lone surrogates (already filtered by Array.from)
|
|
25
|
-
// - Control chars except \t \n \r
|
|
26
|
-
// - Characters with undefined code points
|
|
27
|
-
|
|
28
|
-
const code = char.codePointAt(0);
|
|
29
|
-
|
|
30
|
-
// Skip if code point is undefined (edge case with invalid strings)
|
|
31
|
-
if (code === undefined) return false;
|
|
32
|
-
|
|
33
|
-
// Allow tab, newline, carriage return
|
|
34
|
-
if (code === 0x09 || code === 0x0a || code === 0x0d) return true;
|
|
35
|
-
|
|
36
|
-
// Filter out control characters (0x00-0x1F, except 0x09, 0x0a, 0x0x0d)
|
|
37
|
-
if (code <= 0x1f) return false;
|
|
38
|
-
|
|
39
|
-
// Filter out Unicode format characters
|
|
40
|
-
if (code >= 0xfff9 && code <= 0xfffb) return false;
|
|
41
|
-
|
|
42
|
-
return true;
|
|
43
|
-
})
|
|
44
|
-
.join("");
|
|
7
|
+
export interface OutputResult {
|
|
8
|
+
output: string;
|
|
9
|
+
truncated: boolean;
|
|
10
|
+
fullOutputPath?: string;
|
|
45
11
|
}
|
|
46
12
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
13
|
+
export interface OutputSinkOptions {
|
|
14
|
+
allocateFilePath?: () => string;
|
|
15
|
+
spillThreshold?: number;
|
|
16
|
+
maxColumn?: number;
|
|
17
|
+
onLine?: (line: string) => void;
|
|
18
|
+
onChunk?: (chunk: string) => void;
|
|
52
19
|
}
|
|
53
20
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
end(): void;
|
|
21
|
+
function defaultFilePathAllocator(): string {
|
|
22
|
+
return join(tmpdir(), `omp-${nanoid()}.log`);
|
|
57
23
|
}
|
|
58
24
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Line-buffered output sink with file spill support.
|
|
27
|
+
*
|
|
28
|
+
* Uses a single string buffer with line position tracking.
|
|
29
|
+
* When memory limit exceeded, spills ~half to file in one batch operation.
|
|
30
|
+
*/
|
|
31
|
+
export class OutputSink {
|
|
32
|
+
private buffer = "";
|
|
33
|
+
private lineEnds: number[] = []; // String index after each \n
|
|
34
|
+
|
|
35
|
+
private fileSink?: Bun.FileSink;
|
|
36
|
+
private filePath?: string;
|
|
37
|
+
|
|
38
|
+
private readonly allocateFilePath: () => string;
|
|
39
|
+
private readonly spillThreshold: number;
|
|
40
|
+
private readonly maxColumn: number;
|
|
41
|
+
private readonly onLine?: (line: string) => void;
|
|
42
|
+
private readonly onChunk?: (chunk: string) => void;
|
|
43
|
+
|
|
44
|
+
constructor(options?: OutputSinkOptions) {
|
|
45
|
+
const {
|
|
46
|
+
allocateFilePath = defaultFilePathAllocator,
|
|
47
|
+
spillThreshold = DEFAULT_MAX_BYTES,
|
|
48
|
+
maxColumn = DEFAULT_MAX_COLUMN,
|
|
49
|
+
onLine,
|
|
50
|
+
onChunk,
|
|
51
|
+
} = options ?? {};
|
|
52
|
+
|
|
53
|
+
this.allocateFilePath = allocateFilePath;
|
|
54
|
+
this.spillThreshold = spillThreshold;
|
|
55
|
+
this.maxColumn = maxColumn;
|
|
56
|
+
this.onLine = onLine;
|
|
57
|
+
this.onChunk = onChunk;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private pushLine(line: string, term?: string): void {
|
|
61
|
+
while (line.length > this.maxColumn) {
|
|
62
|
+
this.pushLine(line.slice(0, this.maxColumn), "--\n");
|
|
63
|
+
line = line.slice(this.maxColumn);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.buffer += line;
|
|
67
|
+
if (term) {
|
|
68
|
+
this.buffer += term;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.lineEnds.push(this.buffer.length);
|
|
72
|
+
this.onLine?.(line);
|
|
73
|
+
|
|
74
|
+
if (this.buffer.length > this.spillThreshold) {
|
|
75
|
+
this.spillHalf();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private pushChunk(line: string): void {
|
|
80
|
+
this.onChunk?.(line);
|
|
81
|
+
this.pushLine(line);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private getFileSink(): Bun.FileSink {
|
|
85
|
+
if (!this.fileSink) {
|
|
86
|
+
const filePath = this.allocateFilePath();
|
|
87
|
+
this.filePath = filePath;
|
|
88
|
+
this.fileSink = Bun.file(filePath).writer();
|
|
89
|
+
}
|
|
90
|
+
return this.fileSink;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private spillHalf(): void {
|
|
94
|
+
const target = this.buffer.length >>> 1;
|
|
95
|
+
|
|
96
|
+
// Binary search: first line ending >= target
|
|
97
|
+
let lo = 0;
|
|
98
|
+
let hi = this.lineEnds.length;
|
|
99
|
+
while (lo < hi) {
|
|
100
|
+
const mid = (lo + hi) >>> 1;
|
|
101
|
+
if (this.lineEnds[mid] < target) {
|
|
102
|
+
lo = mid + 1;
|
|
103
|
+
} else {
|
|
104
|
+
hi = mid;
|
|
72
105
|
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Clamp: evict at least 1 line, keep at least 1 line
|
|
109
|
+
const splitIdx = Math.max(1, Math.min(lo, this.lineEnds.length - 1));
|
|
110
|
+
const splitPos = this.lineEnds[splitIdx - 1];
|
|
76
111
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
112
|
+
// Write evicted portion to file
|
|
113
|
+
this.getFileSink().write(this.buffer.slice(0, splitPos));
|
|
114
|
+
|
|
115
|
+
// Truncate buffer, shift line positions
|
|
116
|
+
this.buffer = this.buffer.slice(splitPos);
|
|
117
|
+
const remaining = this.lineEnds.length - splitIdx;
|
|
118
|
+
for (let i = 0; i < remaining; i++) {
|
|
119
|
+
this.lineEnds[i] = this.lineEnds[i + splitIdx] - splitPos;
|
|
84
120
|
}
|
|
85
|
-
|
|
86
|
-
reader.releaseLock();
|
|
121
|
+
this.lineEnds.length = remaining;
|
|
87
122
|
}
|
|
88
|
-
}
|
|
89
123
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
124
|
+
createWritable(): WritableStream<Uint8Array> {
|
|
125
|
+
const decoder = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
126
|
+
let buf = "";
|
|
127
|
+
|
|
128
|
+
const flushLines = () => {
|
|
129
|
+
let start = 0;
|
|
130
|
+
while (true) {
|
|
131
|
+
const nl = buf.indexOf("\n", start);
|
|
132
|
+
if (nl === -1) break;
|
|
133
|
+
this.pushChunk(buf.slice(start, nl + 1));
|
|
134
|
+
start = nl + 1;
|
|
135
|
+
}
|
|
136
|
+
buf = buf.slice(start);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const finalize = () => {
|
|
140
|
+
buf += sanitizeText(decoder.decode());
|
|
141
|
+
flushLines();
|
|
142
|
+
buf = buf.trimEnd();
|
|
143
|
+
if (buf) {
|
|
144
|
+
this.pushChunk(`${buf}\n`);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
95
147
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
private totalBytes = 0;
|
|
101
|
-
private fullOutputPath: string | undefined;
|
|
102
|
-
private fullOutputStream: OutputFileSink | undefined;
|
|
103
|
-
|
|
104
|
-
constructor(
|
|
105
|
-
private readonly spillThreshold: number,
|
|
106
|
-
private readonly maxBuffer: number,
|
|
107
|
-
private readonly onChunk?: (text: string) => void,
|
|
108
|
-
) {
|
|
109
|
-
this.stream = new WritableStream<string>({
|
|
110
|
-
write: (text) => {
|
|
111
|
-
const bytes = Buffer.byteLength(text, "utf-8");
|
|
112
|
-
this.totalBytes += bytes;
|
|
113
|
-
|
|
114
|
-
if (this.totalBytes > this.spillThreshold && !this.fullOutputPath) {
|
|
115
|
-
this.fullOutputPath = join(tmpdir(), `omp-${nanoid()}.buffer`);
|
|
116
|
-
const stream = Bun.file(this.fullOutputPath).writer();
|
|
117
|
-
for (const chunk of this.chunks) {
|
|
118
|
-
stream.write(chunk.text);
|
|
119
|
-
}
|
|
120
|
-
this.fullOutputStream = stream;
|
|
121
|
-
}
|
|
122
|
-
this.fullOutputStream?.write(text);
|
|
123
|
-
|
|
124
|
-
this.chunks.push({ text, bytes });
|
|
125
|
-
this.chunkBytes += bytes;
|
|
126
|
-
while (this.chunkBytes > this.maxBuffer && this.chunks.length > 1) {
|
|
127
|
-
const removed = this.chunks.shift();
|
|
128
|
-
if (removed) {
|
|
129
|
-
this.chunkBytes -= removed.bytes;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
this.onChunk?.(text);
|
|
148
|
+
return new WritableStream<Uint8Array>({
|
|
149
|
+
write: (chunk) => {
|
|
150
|
+
buf += sanitizeText(decoder.decode(chunk, { stream: true }));
|
|
151
|
+
flushLines();
|
|
134
152
|
},
|
|
135
|
-
close:
|
|
136
|
-
|
|
153
|
+
close: finalize,
|
|
154
|
+
abort: finalize,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
createStringWritable(): WritableStream<string> {
|
|
159
|
+
let buf = "";
|
|
160
|
+
|
|
161
|
+
const flushLines = () => {
|
|
162
|
+
let start = 0;
|
|
163
|
+
while (true) {
|
|
164
|
+
const nl = buf.indexOf("\n", start);
|
|
165
|
+
if (nl === -1) break;
|
|
166
|
+
this.pushChunk(buf.slice(start, nl + 1));
|
|
167
|
+
start = nl + 1;
|
|
168
|
+
}
|
|
169
|
+
buf = buf.slice(start);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const finalize = () => {
|
|
173
|
+
flushLines();
|
|
174
|
+
buf = buf.trimEnd();
|
|
175
|
+
if (buf) {
|
|
176
|
+
this.pushChunk(`${buf}\n`);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return new WritableStream<string>({
|
|
181
|
+
write: (chunk) => {
|
|
182
|
+
buf += sanitizeText(chunk);
|
|
183
|
+
flushLines();
|
|
137
184
|
},
|
|
185
|
+
close: finalize,
|
|
186
|
+
abort: finalize,
|
|
138
187
|
});
|
|
139
188
|
}
|
|
140
189
|
|
|
141
|
-
|
|
142
|
-
|
|
190
|
+
async close(): Promise<void> {
|
|
191
|
+
await this.fileSink?.end();
|
|
143
192
|
}
|
|
144
193
|
|
|
145
|
-
dump(annotation?: string):
|
|
194
|
+
dump(annotation?: string): OutputResult {
|
|
195
|
+
let output = this.buffer;
|
|
146
196
|
if (annotation) {
|
|
147
|
-
|
|
148
|
-
|
|
197
|
+
output += `\n${annotation}\n`;
|
|
198
|
+
}
|
|
199
|
+
if (!this.filePath) {
|
|
200
|
+
return { output, truncated: false };
|
|
149
201
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return {
|
|
202
|
+
this.fileSink!.write(this.buffer);
|
|
203
|
+
this.fileSink!.flush();
|
|
204
|
+
return {
|
|
205
|
+
output,
|
|
206
|
+
truncated: true,
|
|
207
|
+
fullOutputPath: this.filePath,
|
|
208
|
+
};
|
|
153
209
|
}
|
|
154
210
|
}
|