erosolar-cli 1.7.14 → 1.7.16
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/dist/core/responseVerifier.d.ts +79 -0
- package/dist/core/responseVerifier.d.ts.map +1 -0
- package/dist/core/responseVerifier.js +443 -0
- package/dist/core/responseVerifier.js.map +1 -0
- package/dist/shell/interactiveShell.d.ts +10 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +80 -0
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +3 -0
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +4 -10
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/persistentPrompt.d.ts +4 -0
- package/dist/ui/persistentPrompt.d.ts.map +1 -1
- package/dist/ui/persistentPrompt.js +10 -11
- package/dist/ui/persistentPrompt.js.map +1 -1
- package/package.json +1 -1
- package/dist/bin/core/agent.js +0 -362
- package/dist/bin/core/agentProfileManifest.js +0 -187
- package/dist/bin/core/agentProfiles.js +0 -34
- package/dist/bin/core/agentRulebook.js +0 -135
- package/dist/bin/core/agentSchemaLoader.js +0 -233
- package/dist/bin/core/contextManager.js +0 -412
- package/dist/bin/core/contextWindow.js +0 -122
- package/dist/bin/core/customCommands.js +0 -80
- package/dist/bin/core/errors/apiKeyErrors.js +0 -114
- package/dist/bin/core/errors/errorTypes.js +0 -340
- package/dist/bin/core/errors/safetyValidator.js +0 -304
- package/dist/bin/core/errors.js +0 -32
- package/dist/bin/core/modelDiscovery.js +0 -755
- package/dist/bin/core/preferences.js +0 -224
- package/dist/bin/core/schemaValidator.js +0 -92
- package/dist/bin/core/secretStore.js +0 -199
- package/dist/bin/core/sessionStore.js +0 -187
- package/dist/bin/core/toolRuntime.js +0 -290
- package/dist/bin/core/types.js +0 -1
- package/dist/bin/shell/bracketedPasteManager.js +0 -350
- package/dist/bin/shell/fileChangeTracker.js +0 -65
- package/dist/bin/shell/interactiveShell.js +0 -2908
- package/dist/bin/shell/liveStatus.js +0 -78
- package/dist/bin/shell/shellApp.js +0 -290
- package/dist/bin/shell/systemPrompt.js +0 -60
- package/dist/bin/shell/updateManager.js +0 -108
- package/dist/bin/ui/ShellUIAdapter.js +0 -459
- package/dist/bin/ui/UnifiedUIController.js +0 -183
- package/dist/bin/ui/animation/AnimationScheduler.js +0 -430
- package/dist/bin/ui/codeHighlighter.js +0 -854
- package/dist/bin/ui/designSystem.js +0 -121
- package/dist/bin/ui/display.js +0 -1222
- package/dist/bin/ui/interrupts/InterruptManager.js +0 -437
- package/dist/bin/ui/layout.js +0 -139
- package/dist/bin/ui/orchestration/StatusOrchestrator.js +0 -403
- package/dist/bin/ui/outputMode.js +0 -38
- package/dist/bin/ui/persistentPrompt.js +0 -183
- package/dist/bin/ui/richText.js +0 -338
- package/dist/bin/ui/shortcutsHelp.js +0 -87
- package/dist/bin/ui/telemetry/UITelemetry.js +0 -443
- package/dist/bin/ui/textHighlighter.js +0 -210
- package/dist/bin/ui/theme.js +0 -116
- package/dist/bin/ui/toolDisplay.js +0 -423
- package/dist/bin/ui/toolDisplayAdapter.js +0 -357
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
import { ToolArgumentValidationError, validateToolArguments, } from './schemaValidator.js';
|
|
2
|
-
// Idempotent tools that can be safely cached
|
|
3
|
-
const CACHEABLE_TOOLS = new Set([
|
|
4
|
-
'Read',
|
|
5
|
-
'read_file',
|
|
6
|
-
'Glob',
|
|
7
|
-
'glob_search',
|
|
8
|
-
'Grep',
|
|
9
|
-
'grep_search',
|
|
10
|
-
'find_definition',
|
|
11
|
-
'analyze_code_quality',
|
|
12
|
-
'extract_exports',
|
|
13
|
-
]);
|
|
14
|
-
export class ToolRuntime {
|
|
15
|
-
constructor(baseTools = [], options = {}) {
|
|
16
|
-
this.registry = new Map();
|
|
17
|
-
this.registrationOrder = [];
|
|
18
|
-
this.cache = new Map();
|
|
19
|
-
this.observer = options.observer ?? null;
|
|
20
|
-
this.contextManager = options.contextManager ?? null;
|
|
21
|
-
this.enableCache = options.enableCache ?? true;
|
|
22
|
-
this.cacheTTLMs = options.cacheTTLMs ?? 5 * 60 * 1000; // 5 minutes default
|
|
23
|
-
if (baseTools.length) {
|
|
24
|
-
this.registerSuite({
|
|
25
|
-
id: 'runtime.core',
|
|
26
|
-
description: 'Core runtime metadata tools',
|
|
27
|
-
tools: baseTools,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
registerSuite(suite) {
|
|
32
|
-
if (!suite?.id?.trim()) {
|
|
33
|
-
throw new Error('Tool suite id cannot be blank.');
|
|
34
|
-
}
|
|
35
|
-
this.unregisterSuite(suite.id);
|
|
36
|
-
for (const definition of suite.tools ?? []) {
|
|
37
|
-
this.addTool(definition, suite.id);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
unregisterSuite(id) {
|
|
41
|
-
if (!id?.trim()) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
for (const [name, record] of this.registry.entries()) {
|
|
45
|
-
if (record.suiteId === id) {
|
|
46
|
-
this.registry.delete(name);
|
|
47
|
-
this.removeFromOrder(name);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
listProviderTools() {
|
|
52
|
-
return this.registrationOrder
|
|
53
|
-
.map((name) => this.registry.get(name))
|
|
54
|
-
.filter((record) => Boolean(record))
|
|
55
|
-
.map(({ definition }) => {
|
|
56
|
-
const tool = {
|
|
57
|
-
name: definition.name,
|
|
58
|
-
description: definition.description,
|
|
59
|
-
};
|
|
60
|
-
if (definition.parameters) {
|
|
61
|
-
tool.parameters = definition.parameters;
|
|
62
|
-
}
|
|
63
|
-
return tool;
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
async execute(call) {
|
|
67
|
-
const record = this.registry.get(call.name);
|
|
68
|
-
if (!record) {
|
|
69
|
-
const message = `Tool "${call.name}" is not available.`;
|
|
70
|
-
this.observer?.onToolError?.(call, message);
|
|
71
|
-
return message;
|
|
72
|
-
}
|
|
73
|
-
// Check if tool is cacheable
|
|
74
|
-
const isCacheable = record.definition.cacheable ?? CACHEABLE_TOOLS.has(call.name);
|
|
75
|
-
// Try to get from cache
|
|
76
|
-
if (this.enableCache && isCacheable) {
|
|
77
|
-
const cacheKey = this.getCacheKey(call);
|
|
78
|
-
const cached = this.cache.get(cacheKey);
|
|
79
|
-
if (cached && Date.now() - cached.timestamp < this.cacheTTLMs) {
|
|
80
|
-
this.observer?.onCacheHit?.(call);
|
|
81
|
-
this.observer?.onToolResult?.(call, cached.result);
|
|
82
|
-
return cached.result;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
this.observer?.onToolStart?.(call);
|
|
86
|
-
try {
|
|
87
|
-
const args = normalizeToolArguments(call.arguments);
|
|
88
|
-
validateToolArguments(record.definition.name, record.definition.parameters, args);
|
|
89
|
-
const result = await record.definition.handler(args);
|
|
90
|
-
let output = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
91
|
-
// Truncate output if context manager is available
|
|
92
|
-
if (this.contextManager) {
|
|
93
|
-
const truncated = this.contextManager.truncateToolOutput(output, call.name);
|
|
94
|
-
if (truncated.wasTruncated) {
|
|
95
|
-
output = truncated.content;
|
|
96
|
-
// Log truncation for debugging
|
|
97
|
-
if (process.env['DEBUG_CONTEXT']) {
|
|
98
|
-
console.warn(`[Context Manager] Truncated ${call.name} output: ${truncated.originalLength} -> ${truncated.truncatedLength} chars`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// Cache the result if cacheable
|
|
103
|
-
if (this.enableCache && isCacheable) {
|
|
104
|
-
const cacheKey = this.getCacheKey(call);
|
|
105
|
-
this.cache.set(cacheKey, {
|
|
106
|
-
result: output,
|
|
107
|
-
timestamp: Date.now(),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
this.observer?.onToolResult?.(call, output);
|
|
111
|
-
return output;
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
let formatted;
|
|
115
|
-
if (error instanceof ToolArgumentValidationError) {
|
|
116
|
-
formatted = error.message;
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
120
|
-
formatted = `Failed to run "${call.name}": ${message}`;
|
|
121
|
-
}
|
|
122
|
-
this.observer?.onToolError?.(call, formatted);
|
|
123
|
-
return formatted;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
getCacheKey(call) {
|
|
127
|
-
return `${call.name}:${JSON.stringify(call.arguments)}`;
|
|
128
|
-
}
|
|
129
|
-
clearCache() {
|
|
130
|
-
this.cache.clear();
|
|
131
|
-
}
|
|
132
|
-
getCacheStats() {
|
|
133
|
-
let totalSize = 0;
|
|
134
|
-
for (const entry of this.cache.values()) {
|
|
135
|
-
totalSize += entry.result.length;
|
|
136
|
-
}
|
|
137
|
-
return {
|
|
138
|
-
size: totalSize,
|
|
139
|
-
entries: this.cache.size,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
addTool(definition, suiteId) {
|
|
143
|
-
if (!definition?.name?.trim()) {
|
|
144
|
-
throw new Error(`Tool names cannot be blank (suite "${suiteId}").`);
|
|
145
|
-
}
|
|
146
|
-
if (this.registry.has(definition.name)) {
|
|
147
|
-
const owner = this.registry.get(definition.name)?.suiteId ?? 'unknown';
|
|
148
|
-
throw new Error(`Tool "${definition.name}" already registered by suite "${owner}".`);
|
|
149
|
-
}
|
|
150
|
-
this.registry.set(definition.name, {
|
|
151
|
-
suiteId,
|
|
152
|
-
definition,
|
|
153
|
-
});
|
|
154
|
-
this.registrationOrder.push(definition.name);
|
|
155
|
-
}
|
|
156
|
-
removeFromOrder(name) {
|
|
157
|
-
const index = this.registrationOrder.indexOf(name);
|
|
158
|
-
if (index >= 0) {
|
|
159
|
-
this.registrationOrder.splice(index, 1);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
export function createDefaultToolRuntime(context, toolSuites = [], options = {}) {
|
|
164
|
-
const runtime = new ToolRuntime([
|
|
165
|
-
buildContextSnapshotTool(context.workspaceContext),
|
|
166
|
-
buildCapabilitiesTool(context),
|
|
167
|
-
buildProfileInspectorTool(context),
|
|
168
|
-
], options);
|
|
169
|
-
for (const suite of toolSuites) {
|
|
170
|
-
runtime.registerSuite(suite);
|
|
171
|
-
}
|
|
172
|
-
return runtime;
|
|
173
|
-
}
|
|
174
|
-
function buildContextSnapshotTool(workspaceContext) {
|
|
175
|
-
return {
|
|
176
|
-
name: 'context_snapshot',
|
|
177
|
-
description: 'Returns a summary of the repository context. NOTE: Full context is already in your system prompt - only use this if you need to refresh or verify the context.',
|
|
178
|
-
parameters: {
|
|
179
|
-
type: 'object',
|
|
180
|
-
properties: {
|
|
181
|
-
format: {
|
|
182
|
-
type: 'string',
|
|
183
|
-
description: 'Use "plain" for raw text or "markdown" for a fenced block.',
|
|
184
|
-
enum: ['plain', 'markdown'],
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
handler: (args) => {
|
|
189
|
-
if (!workspaceContext?.trim()) {
|
|
190
|
-
return 'Workspace context is unavailable.';
|
|
191
|
-
}
|
|
192
|
-
// CRITICAL: Return only summary to prevent context duplication
|
|
193
|
-
// Full context is already in system prompt
|
|
194
|
-
const lines = workspaceContext.trim().split('\n');
|
|
195
|
-
const totalLines = lines.length;
|
|
196
|
-
// Return just first 20 lines + summary
|
|
197
|
-
const preview = lines.slice(0, 20).join('\n');
|
|
198
|
-
const summary = [
|
|
199
|
-
preview,
|
|
200
|
-
'',
|
|
201
|
-
`[Workspace context: ${totalLines} total lines]`,
|
|
202
|
-
`[Full context already available in system prompt - use Read/Glob tools for specific files]`
|
|
203
|
-
].join('\n');
|
|
204
|
-
const format = args['format'] === 'markdown' ? 'markdown' : 'plain';
|
|
205
|
-
if (format === 'markdown') {
|
|
206
|
-
return ['```text', summary, '```'].join('\n');
|
|
207
|
-
}
|
|
208
|
-
return summary;
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
function buildCapabilitiesTool(context) {
|
|
213
|
-
return {
|
|
214
|
-
name: 'capabilities_overview',
|
|
215
|
-
description: 'Summarizes the agent runtime capabilities including available tools and features.',
|
|
216
|
-
parameters: {
|
|
217
|
-
type: 'object',
|
|
218
|
-
properties: {
|
|
219
|
-
audience: {
|
|
220
|
-
type: 'string',
|
|
221
|
-
enum: ['developer', 'model'],
|
|
222
|
-
description: 'Tailors the tone of the description.',
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
handler: (args) => {
|
|
227
|
-
const audience = args['audience'];
|
|
228
|
-
const adjective = audience === 'developer' ? 'Operator facing' : 'Model facing';
|
|
229
|
-
return [
|
|
230
|
-
`${adjective} capabilities summary:`,
|
|
231
|
-
'- Full file system access (read, write, list, search).',
|
|
232
|
-
'- Bash command execution for running scripts and tools.',
|
|
233
|
-
'- Advanced code search and pattern matching.',
|
|
234
|
-
'- Deterministic workspace context snapshot appended to the system prompt.',
|
|
235
|
-
'- Tool invocations are logged in realtime for transparency.',
|
|
236
|
-
`- Active provider: ${context.provider} (${context.model}).`,
|
|
237
|
-
].join('\n');
|
|
238
|
-
},
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
function buildProfileInspectorTool(context) {
|
|
242
|
-
return {
|
|
243
|
-
name: 'profile_details',
|
|
244
|
-
description: 'Returns the configuration of the active CLI profile.',
|
|
245
|
-
parameters: {
|
|
246
|
-
type: 'object',
|
|
247
|
-
properties: {
|
|
248
|
-
includeWorkspaceContext: {
|
|
249
|
-
type: 'boolean',
|
|
250
|
-
description: 'Set true to append the workspace context snapshot if available.',
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
|
-
additionalProperties: false,
|
|
254
|
-
},
|
|
255
|
-
handler: (args) => {
|
|
256
|
-
const payload = {
|
|
257
|
-
profile: context.profileName,
|
|
258
|
-
provider: context.provider,
|
|
259
|
-
model: context.model,
|
|
260
|
-
workspaceContext: args['includeWorkspaceContext'] ? context.workspaceContext ?? null : null,
|
|
261
|
-
};
|
|
262
|
-
return JSON.stringify(payload, null, 2);
|
|
263
|
-
},
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
function normalizeToolArguments(value) {
|
|
267
|
-
if (value instanceof Map) {
|
|
268
|
-
return Object.fromEntries(value.entries());
|
|
269
|
-
}
|
|
270
|
-
if (isRecord(value)) {
|
|
271
|
-
return value;
|
|
272
|
-
}
|
|
273
|
-
if (typeof value === 'string') {
|
|
274
|
-
const trimmed = value.trim();
|
|
275
|
-
if (!trimmed) {
|
|
276
|
-
return {};
|
|
277
|
-
}
|
|
278
|
-
try {
|
|
279
|
-
const parsed = JSON.parse(trimmed);
|
|
280
|
-
return isRecord(parsed) ? parsed : {};
|
|
281
|
-
}
|
|
282
|
-
catch {
|
|
283
|
-
return {};
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
return {};
|
|
287
|
-
}
|
|
288
|
-
function isRecord(value) {
|
|
289
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
290
|
-
}
|
package/dist/bin/core/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bracketed paste manager for handling terminal paste operations.
|
|
3
|
-
*
|
|
4
|
-
* This class handles the bracketed paste mode in terminals, which allows
|
|
5
|
-
* pasted text to be distinguished from typed input. This is particularly
|
|
6
|
-
* useful for handling multi-line pastes and preventing unwanted execution
|
|
7
|
-
* of commands during paste operations.
|
|
8
|
-
*/
|
|
9
|
-
export class BracketedPasteManager {
|
|
10
|
-
constructor(enabled) {
|
|
11
|
-
this.inPaste = false;
|
|
12
|
-
this.pasteBuffer = [];
|
|
13
|
-
/** Tracks accumulated multi-line input even without bracketed paste support */
|
|
14
|
-
this.multiLineBuffer = [];
|
|
15
|
-
this.multiLineMode = false;
|
|
16
|
-
this.lastInputTime = 0;
|
|
17
|
-
/** Time threshold (ms) to consider rapid input as paste */
|
|
18
|
-
this.PASTE_THRESHOLD_MS = 50;
|
|
19
|
-
/** Raw data buffer for capturing complete bracketed paste content */
|
|
20
|
-
this.rawPasteBuffer = '';
|
|
21
|
-
/** Callback for when raw paste is complete */
|
|
22
|
-
this.onRawPasteComplete = null;
|
|
23
|
-
/** Whether we're currently capturing raw data */
|
|
24
|
-
this.capturingRaw = false;
|
|
25
|
-
this.enabled = enabled;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Process raw data from stdin before readline handles it.
|
|
29
|
-
* This intercepts bracketed paste content and captures it in full.
|
|
30
|
-
* Returns true if the data was consumed (should not be passed to readline).
|
|
31
|
-
*/
|
|
32
|
-
processRawData(data) {
|
|
33
|
-
if (!this.enabled) {
|
|
34
|
-
return { consumed: false };
|
|
35
|
-
}
|
|
36
|
-
const startIdx = data.indexOf(BracketedPasteManager.START_MARKER);
|
|
37
|
-
const endIdx = data.indexOf(BracketedPasteManager.END_MARKER);
|
|
38
|
-
// Case 1: Not in paste mode and no start marker - pass through
|
|
39
|
-
if (!this.capturingRaw && startIdx === -1) {
|
|
40
|
-
return { consumed: false };
|
|
41
|
-
}
|
|
42
|
-
// Case 2: Found start marker - begin capturing
|
|
43
|
-
if (!this.capturingRaw && startIdx !== -1) {
|
|
44
|
-
this.capturingRaw = true;
|
|
45
|
-
const beforeStart = data.substring(0, startIdx);
|
|
46
|
-
const afterStart = data.substring(startIdx + BracketedPasteManager.START_MARKER.length);
|
|
47
|
-
// Check if end marker is in same chunk
|
|
48
|
-
const endInAfter = afterStart.indexOf(BracketedPasteManager.END_MARKER);
|
|
49
|
-
if (endInAfter !== -1) {
|
|
50
|
-
// Complete paste in single chunk
|
|
51
|
-
const pasteContent = afterStart.substring(0, endInAfter);
|
|
52
|
-
const afterEnd = afterStart.substring(endInAfter + BracketedPasteManager.END_MARKER.length);
|
|
53
|
-
this.capturingRaw = false;
|
|
54
|
-
this.rawPasteBuffer = '';
|
|
55
|
-
// Emit the complete paste
|
|
56
|
-
if (this.onRawPasteComplete) {
|
|
57
|
-
this.onRawPasteComplete(pasteContent);
|
|
58
|
-
}
|
|
59
|
-
// Return any content before/after markers to be passed through
|
|
60
|
-
const passThrough = beforeStart + afterEnd;
|
|
61
|
-
return { consumed: true, passThrough: passThrough || undefined };
|
|
62
|
-
}
|
|
63
|
-
// Start marker found but no end marker yet
|
|
64
|
-
this.rawPasteBuffer = afterStart;
|
|
65
|
-
// Pass through anything before the start marker
|
|
66
|
-
return { consumed: true, passThrough: beforeStart || undefined };
|
|
67
|
-
}
|
|
68
|
-
// Case 3: Currently capturing and found end marker
|
|
69
|
-
if (this.capturingRaw && endIdx !== -1) {
|
|
70
|
-
const beforeEnd = data.substring(0, endIdx);
|
|
71
|
-
const afterEnd = data.substring(endIdx + BracketedPasteManager.END_MARKER.length);
|
|
72
|
-
const pasteContent = this.rawPasteBuffer + beforeEnd;
|
|
73
|
-
this.capturingRaw = false;
|
|
74
|
-
this.rawPasteBuffer = '';
|
|
75
|
-
// Emit the complete paste
|
|
76
|
-
if (this.onRawPasteComplete) {
|
|
77
|
-
this.onRawPasteComplete(pasteContent);
|
|
78
|
-
}
|
|
79
|
-
// Pass through anything after end marker
|
|
80
|
-
return { consumed: true, passThrough: afterEnd || undefined };
|
|
81
|
-
}
|
|
82
|
-
// Case 4: Currently capturing, no end marker - accumulate
|
|
83
|
-
if (this.capturingRaw) {
|
|
84
|
-
this.rawPasteBuffer += data;
|
|
85
|
-
return { consumed: true };
|
|
86
|
-
}
|
|
87
|
-
return { consumed: false };
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Set callback for when a complete paste is captured via raw data processing
|
|
91
|
-
*/
|
|
92
|
-
setRawPasteCallback(callback) {
|
|
93
|
-
this.onRawPasteComplete = callback;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Check if currently capturing raw paste data
|
|
97
|
-
*/
|
|
98
|
-
isCapturingRaw() {
|
|
99
|
-
return this.capturingRaw;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Get current raw buffer size (for display)
|
|
103
|
-
*/
|
|
104
|
-
getRawBufferLineCount() {
|
|
105
|
-
if (!this.rawPasteBuffer)
|
|
106
|
-
return 0;
|
|
107
|
-
return this.rawPasteBuffer.split('\n').length;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Get preview of raw buffer content
|
|
111
|
-
*/
|
|
112
|
-
getRawBufferPreview() {
|
|
113
|
-
if (!this.rawPasteBuffer)
|
|
114
|
-
return '';
|
|
115
|
-
const lines = this.rawPasteBuffer.split('\n');
|
|
116
|
-
return this.getPreview(lines);
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Process input text, handling bracketed paste sequences
|
|
120
|
-
*/
|
|
121
|
-
process(input) {
|
|
122
|
-
if (!this.enabled) {
|
|
123
|
-
return this.processWithoutBracketed(input);
|
|
124
|
-
}
|
|
125
|
-
const startMarker = '\u001b[200~';
|
|
126
|
-
const endMarker = '\u001b[201~';
|
|
127
|
-
// If we're already in a paste, look for end marker
|
|
128
|
-
if (this.inPaste) {
|
|
129
|
-
const endIndex = input.indexOf(endMarker);
|
|
130
|
-
if (endIndex !== -1) {
|
|
131
|
-
// End of paste found
|
|
132
|
-
const beforeEnd = input.substring(0, endIndex);
|
|
133
|
-
const afterEnd = input.substring(endIndex + endMarker.length);
|
|
134
|
-
this.pasteBuffer.push(beforeEnd);
|
|
135
|
-
const result = this.pasteBuffer.join('\n');
|
|
136
|
-
const lineCount = this.pasteBuffer.length;
|
|
137
|
-
const preview = this.getPreview(this.pasteBuffer);
|
|
138
|
-
// Reset state
|
|
139
|
-
this.inPaste = false;
|
|
140
|
-
this.pasteBuffer = [];
|
|
141
|
-
// If there's text after the end marker, process it normally
|
|
142
|
-
if (afterEnd) {
|
|
143
|
-
const afterResult = this.process(afterEnd);
|
|
144
|
-
if (afterResult.handled && afterResult.result !== undefined) {
|
|
145
|
-
return {
|
|
146
|
-
handled: true,
|
|
147
|
-
result: result + afterResult.result,
|
|
148
|
-
lineCount,
|
|
149
|
-
preview,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
handled: true,
|
|
154
|
-
result: result + afterEnd,
|
|
155
|
-
lineCount,
|
|
156
|
-
preview,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
handled: true,
|
|
161
|
-
result,
|
|
162
|
-
lineCount,
|
|
163
|
-
preview,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
// Still in paste, no end marker found
|
|
168
|
-
this.pasteBuffer.push(input);
|
|
169
|
-
return {
|
|
170
|
-
handled: true,
|
|
171
|
-
isPending: true,
|
|
172
|
-
lineCount: this.pasteBuffer.length,
|
|
173
|
-
preview: this.getPreview(this.pasteBuffer),
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// Not in paste, look for start marker
|
|
178
|
-
const startIndex = input.indexOf(startMarker);
|
|
179
|
-
if (startIndex !== -1) {
|
|
180
|
-
// Start of paste found
|
|
181
|
-
const beforeStart = input.substring(0, startIndex);
|
|
182
|
-
const afterStart = input.substring(startIndex + startMarker.length);
|
|
183
|
-
// Start paste mode
|
|
184
|
-
this.inPaste = true;
|
|
185
|
-
this.pasteBuffer = [];
|
|
186
|
-
// Process the text after the start marker
|
|
187
|
-
if (afterStart) {
|
|
188
|
-
const afterResult = this.process(afterStart);
|
|
189
|
-
if (afterResult.handled && afterResult.result !== undefined) {
|
|
190
|
-
return {
|
|
191
|
-
handled: true,
|
|
192
|
-
result: beforeStart + afterResult.result,
|
|
193
|
-
lineCount: afterResult.lineCount,
|
|
194
|
-
preview: afterResult.preview,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
// Still pending
|
|
198
|
-
return {
|
|
199
|
-
handled: true,
|
|
200
|
-
result: beforeStart,
|
|
201
|
-
isPending: this.inPaste,
|
|
202
|
-
lineCount: this.pasteBuffer.length,
|
|
203
|
-
preview: this.getPreview(this.pasteBuffer),
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
return {
|
|
207
|
-
handled: true,
|
|
208
|
-
result: beforeStart,
|
|
209
|
-
isPending: true,
|
|
210
|
-
lineCount: 0,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
// No paste markers found
|
|
214
|
-
return { handled: false };
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Process input when bracketed paste is not enabled
|
|
218
|
-
* Uses timing heuristics to detect rapid multi-line input (likely paste)
|
|
219
|
-
*/
|
|
220
|
-
processWithoutBracketed(input) {
|
|
221
|
-
const now = Date.now();
|
|
222
|
-
const timeSinceLastInput = now - this.lastInputTime;
|
|
223
|
-
this.lastInputTime = now;
|
|
224
|
-
// Check if this looks like part of a rapid paste
|
|
225
|
-
if (timeSinceLastInput < this.PASTE_THRESHOLD_MS && this.multiLineBuffer.length > 0) {
|
|
226
|
-
// Continue accumulating
|
|
227
|
-
this.multiLineBuffer.push(input);
|
|
228
|
-
this.multiLineMode = true;
|
|
229
|
-
return {
|
|
230
|
-
handled: true,
|
|
231
|
-
isPending: true,
|
|
232
|
-
lineCount: this.multiLineBuffer.length,
|
|
233
|
-
preview: this.getPreview(this.multiLineBuffer),
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
// If we were in multi-line mode and there's a pause, finalize
|
|
237
|
-
if (this.multiLineMode && this.multiLineBuffer.length > 0) {
|
|
238
|
-
this.multiLineBuffer.push(input);
|
|
239
|
-
const result = this.multiLineBuffer.join('\n');
|
|
240
|
-
const lineCount = this.multiLineBuffer.length;
|
|
241
|
-
const preview = this.getPreview(this.multiLineBuffer);
|
|
242
|
-
this.multiLineBuffer = [];
|
|
243
|
-
this.multiLineMode = false;
|
|
244
|
-
return {
|
|
245
|
-
handled: true,
|
|
246
|
-
result,
|
|
247
|
-
lineCount,
|
|
248
|
-
preview,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
// Check for embedded newlines in a single input (indicates paste)
|
|
252
|
-
if (input.includes('\n')) {
|
|
253
|
-
const lines = input.split('\n');
|
|
254
|
-
return {
|
|
255
|
-
handled: true,
|
|
256
|
-
result: input,
|
|
257
|
-
lineCount: lines.length,
|
|
258
|
-
preview: this.getPreview(lines),
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
// Start tracking for potential multi-line paste
|
|
262
|
-
this.multiLineBuffer = [input];
|
|
263
|
-
return { handled: false };
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Generate a preview string for multi-line content
|
|
267
|
-
*/
|
|
268
|
-
getPreview(lines) {
|
|
269
|
-
if (lines.length === 0) {
|
|
270
|
-
return '';
|
|
271
|
-
}
|
|
272
|
-
const firstLine = lines[0] || '';
|
|
273
|
-
const truncatedFirst = firstLine.length > 50
|
|
274
|
-
? firstLine.slice(0, 47) + '...'
|
|
275
|
-
: firstLine;
|
|
276
|
-
if (lines.length === 1) {
|
|
277
|
-
return truncatedFirst;
|
|
278
|
-
}
|
|
279
|
-
return `${truncatedFirst} [+${lines.length - 1} more lines]`;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Get current multi-line state for display purposes
|
|
283
|
-
*/
|
|
284
|
-
getMultiLineState() {
|
|
285
|
-
const buffer = this.inPaste ? this.pasteBuffer :
|
|
286
|
-
this.multiLineMode ? this.multiLineBuffer : null;
|
|
287
|
-
if (!buffer || buffer.length === 0) {
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
const firstLine = buffer[0] || '';
|
|
291
|
-
const lastLine = buffer[buffer.length - 1] || '';
|
|
292
|
-
return {
|
|
293
|
-
content: buffer.join('\n'),
|
|
294
|
-
lineCount: buffer.length,
|
|
295
|
-
firstLinePreview: firstLine.length > 40 ? firstLine.slice(0, 37) + '...' : firstLine,
|
|
296
|
-
lastLinePreview: lastLine.length > 40 ? lastLine.slice(0, 37) + '...' : lastLine,
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Format a multi-line paste as a collapsed block for display
|
|
301
|
-
*/
|
|
302
|
-
static formatCollapsedBlock(content, maxPreviewLength = 60) {
|
|
303
|
-
const lines = content.split('\n');
|
|
304
|
-
if (lines.length <= 1) {
|
|
305
|
-
return content;
|
|
306
|
-
}
|
|
307
|
-
const firstLine = (lines[0] || '').trim();
|
|
308
|
-
const truncatedFirst = firstLine.length > maxPreviewLength
|
|
309
|
-
? firstLine.slice(0, maxPreviewLength - 3) + '...'
|
|
310
|
-
: firstLine;
|
|
311
|
-
return `📋 ${truncatedFirst} [${lines.length} lines]`;
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Check if currently processing a paste
|
|
315
|
-
*/
|
|
316
|
-
isInPaste() {
|
|
317
|
-
return this.inPaste || this.multiLineMode;
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Get the current paste buffer
|
|
321
|
-
*/
|
|
322
|
-
getPasteBuffer() {
|
|
323
|
-
return this.inPaste ? [...this.pasteBuffer] : [...this.multiLineBuffer];
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Finalize any pending multi-line input
|
|
327
|
-
*/
|
|
328
|
-
finalize() {
|
|
329
|
-
if (this.multiLineMode && this.multiLineBuffer.length > 1) {
|
|
330
|
-
const result = this.multiLineBuffer.join('\n');
|
|
331
|
-
this.multiLineBuffer = [];
|
|
332
|
-
this.multiLineMode = false;
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Reset the paste state
|
|
339
|
-
*/
|
|
340
|
-
reset() {
|
|
341
|
-
this.inPaste = false;
|
|
342
|
-
this.pasteBuffer = [];
|
|
343
|
-
this.multiLineMode = false;
|
|
344
|
-
this.multiLineBuffer = [];
|
|
345
|
-
this.rawPasteBuffer = '';
|
|
346
|
-
this.capturingRaw = false;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
BracketedPasteManager.START_MARKER = '\u001b[200~';
|
|
350
|
-
BracketedPasteManager.END_MARKER = '\u001b[201~';
|