termux-dev 1.4.13 → 1.4.15
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/assets/banner.svg +1 -1
- package/dist/cli/clipboard.js +10 -1
- package/dist/cli/doctor.js +1 -1
- package/dist/cli/files.js +1 -1
- package/dist/cli/headless.js +1 -1
- package/dist/cli/index.js +34 -7
- package/dist/cli/prompt.js +42 -16
- package/dist/cli/server.js +13 -10
- package/dist/cli/smooth.js +7 -0
- package/dist/core/commands.js +1 -1
- package/dist/core/history.js +23 -5
- package/dist/core/loop.js +13 -0
- package/dist/core/model-limits.json +6543 -0
- package/dist/core/model-pricing.json +26166 -0
- package/dist/core/models.js +13 -1
- package/dist/core/notify.js +15 -1
- package/dist/core/pricing.js +3 -2
- package/dist/mcp/client.js +1 -1
- package/dist/permissions/guard.js +19 -4
- package/dist/providers/openai.js +69 -30
- package/dist/tools/bash.js +16 -4
- package/dist/tools/diagnostics.js +4 -3
- package/dist/tools/fs.js +11 -2
- package/dist/tools/packages.js +3 -1
- package/dist/tools/questions.js +55 -38
- package/package.json +2 -2
package/assets/banner.svg
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
<!-- Tagline & Badge -->
|
|
27
27
|
<text x="425" y="142" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="600" fill="#8b949e" text-anchor="middle" letter-spacing="3">
|
|
28
|
-
THE TERMINAL-NATIVE AI CODING AGENT <tspan fill="#00f2fe" font-weight="bold">v1.4.
|
|
28
|
+
THE TERMINAL-NATIVE AI CODING AGENT <tspan fill="#00f2fe" font-weight="bold">v1.4.15</tspan>
|
|
29
29
|
</text>
|
|
30
30
|
|
|
31
31
|
<!-- Top Accent Line -->
|
package/dist/cli/clipboard.js
CHANGED
|
@@ -4,6 +4,15 @@ import path from 'path';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import { exec } from 'child_process';
|
|
6
6
|
const MEDIA_DIR = path.join(process.cwd(), '.devx', 'media');
|
|
7
|
+
export function expandHomePath(p) {
|
|
8
|
+
if (!p)
|
|
9
|
+
return p;
|
|
10
|
+
const trimmed = p.trim().replace(/^['"]|['"]$/g, '');
|
|
11
|
+
if (trimmed === '~' || trimmed.startsWith('~/') || trimmed.startsWith('~\\')) {
|
|
12
|
+
return path.join(os.homedir(), trimmed.slice(2));
|
|
13
|
+
}
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
7
16
|
export function formatFileSize(bytes) {
|
|
8
17
|
if (bytes < 1024)
|
|
9
18
|
return `${bytes}b`;
|
|
@@ -107,7 +116,7 @@ if ($img -ne $null) {
|
|
|
107
116
|
});
|
|
108
117
|
}
|
|
109
118
|
export async function processPastedFilePath(rawPath, existingNames) {
|
|
110
|
-
const cleanPath = rawPath.trim().replace(/^['"&]+|['"]+$/g, '').trim();
|
|
119
|
+
const cleanPath = expandHomePath(rawPath).trim().replace(/^['"&]+|['"]+$/g, '').trim();
|
|
111
120
|
const resolved = path.resolve(process.cwd(), cleanPath);
|
|
112
121
|
const imageExts = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.bmp']);
|
|
113
122
|
const ext = path.extname(resolved).toLowerCase();
|
package/dist/cli/doctor.js
CHANGED
|
@@ -18,7 +18,7 @@ async function pingUrl(url) {
|
|
|
18
18
|
const req = https.get(url, {
|
|
19
19
|
timeout: 3000,
|
|
20
20
|
headers: {
|
|
21
|
-
'User-Agent': 'devx-doctor/1.4.
|
|
21
|
+
'User-Agent': 'devx-doctor/1.4.15'
|
|
22
22
|
}
|
|
23
23
|
}, (res) => {
|
|
24
24
|
res.resume(); // consume response data to free up memory and release socket
|
package/dist/cli/files.js
CHANGED
|
@@ -58,7 +58,7 @@ const IMAGE_EXTS = {
|
|
|
58
58
|
'.svg': 'image/svg+xml'
|
|
59
59
|
};
|
|
60
60
|
export async function resolveAtMentions(text) {
|
|
61
|
-
const mentionRegex = /@([a-zA-Z0-9_
|
|
61
|
+
const mentionRegex = /@([a-zA-Z0-9_\-./\u0400-\u04FF]+)/g;
|
|
62
62
|
const attachments = [];
|
|
63
63
|
const images = [];
|
|
64
64
|
const seenPaths = new Set();
|
package/dist/cli/headless.js
CHANGED
|
@@ -35,7 +35,7 @@ export async function runHeadlessMode(userPrompt, config, options = {}) {
|
|
|
35
35
|
});
|
|
36
36
|
try {
|
|
37
37
|
if (!isQuiet && !isJson) {
|
|
38
|
-
console.log(pc.bold(pc.cyan(`⚡ devx v1.4.
|
|
38
|
+
console.log(pc.bold(pc.cyan(`⚡ devx v1.4.15 (headless) | ${planMode ? 'PLAN' : 'AGENT'} | ${config.model}`)));
|
|
39
39
|
console.log(pc.dim(`Task: ${userPrompt}\n`));
|
|
40
40
|
}
|
|
41
41
|
for await (const event of agent.run(abortController.signal)) {
|
package/dist/cli/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { CLIConsoleGuard } from '../permissions/guard.js';
|
|
|
16
16
|
import { globalSnapshotManager } from '../core/snapshot.js';
|
|
17
17
|
import { MemoryManager } from '../core/memory.js';
|
|
18
18
|
import { startServer, stopServer, displayServerBanner } from './server.js';
|
|
19
|
-
import { notifyDevice } from '../core/notify.js';
|
|
19
|
+
import { notifyDevice, acquireWakeLock } from '../core/notify.js';
|
|
20
20
|
import { runDoctor } from './doctor.js';
|
|
21
21
|
import { ALL_PROVIDERS } from './providers.js';
|
|
22
22
|
import { getModelContextLimit } from '../core/models.js';
|
|
@@ -484,7 +484,7 @@ function drawLogo() {
|
|
|
484
484
|
theme.colorFn(' █▀▀▄ █▀▀▀ █ █ █ █'),
|
|
485
485
|
theme.colorFn(' █ █ █▀▀▀ ▀▄▀ ▀▄▀ '),
|
|
486
486
|
theme.colorFn(' █▄▄▀ █▄▄▄ ▀ ▀ ▀ '),
|
|
487
|
-
' ' + theme.boldFn('v1.4.
|
|
487
|
+
' ' + theme.boldFn('v1.4.15'),
|
|
488
488
|
''
|
|
489
489
|
];
|
|
490
490
|
for (const line of logo) {
|
|
@@ -499,7 +499,7 @@ function drawLogo() {
|
|
|
499
499
|
indent + theme.colorFn('▀▀▀█▀▀▀ █▀▀▀ █▀▀█ █▄ ▄█ █ █ ▀▄ ▄▀ █▀▀▄ █▀▀▀ █ █'),
|
|
500
500
|
indent + theme.colorFn(' █ █▀▀▀ █▄▄▀ █ █ █ █ █ █ ▀▀ █ █ █▀▀▀ █ █'),
|
|
501
501
|
indent + theme.colorFn(' █ █▄▄▄ █ ▀▄ █ █ ▀▄▄▀ ▄▀ ▀▄ █▄▄▀ █▄▄▄ ▀▄▀ '),
|
|
502
|
-
indent + theme.boldFn('v1.4.
|
|
502
|
+
indent + theme.boldFn('v1.4.15'),
|
|
503
503
|
''
|
|
504
504
|
];
|
|
505
505
|
for (const line of logo) {
|
|
@@ -645,7 +645,7 @@ async function handleSettings(config) {
|
|
|
645
645
|
const maxIterLabel = maxIter >= 9999 ? 'Unlimited' : `${maxIter} steps`;
|
|
646
646
|
const currentTh = getCurrentTheme();
|
|
647
647
|
const choice = await select({
|
|
648
|
-
message: `${pc.bold('⚙️ Settings')} ${pc.dim(`(devx v1.4.
|
|
648
|
+
message: `${pc.bold('⚙️ Settings')} ${pc.dim(`(devx v1.4.15 • theme: ${currentTh.name})`)}`,
|
|
649
649
|
choices: [
|
|
650
650
|
{
|
|
651
651
|
name: `🎨 Color Theme: ${currentTh.emoji} ${currentTh.name}`,
|
|
@@ -686,7 +686,7 @@ async function handleSettings(config) {
|
|
|
686
686
|
description: 'Limit how many tool steps (file edits, terminal commands) agent can do per request'
|
|
687
687
|
},
|
|
688
688
|
{
|
|
689
|
-
name: `${currentTh.colorFn('✨ About devx')} ${pc.dim('(v1.4.
|
|
689
|
+
name: `${currentTh.colorFn('✨ About devx')} ${pc.dim('(v1.4.15 by ApvCode)')}`,
|
|
690
690
|
value: 'about',
|
|
691
691
|
description: 'Terminal-Native AI Coding Agent created by ApvCode (https://github.com/apvcode/Termux-Dev)'
|
|
692
692
|
},
|
|
@@ -702,7 +702,7 @@ async function handleSettings(config) {
|
|
|
702
702
|
continue;
|
|
703
703
|
}
|
|
704
704
|
if (choice === 'about') {
|
|
705
|
-
p.note(`⚡ devx v1.4.
|
|
705
|
+
p.note(`⚡ devx v1.4.15 — Terminal-Native AI Coding Agent\n` +
|
|
706
706
|
`🎨 Theme: ${currentTh.emoji} ${currentTh.name}\n` +
|
|
707
707
|
`👤 Author: ApvCode (https://github.com/apvcode)\n` +
|
|
708
708
|
`🌟 Repository: https://github.com/apvcode/Termux-Dev\n` +
|
|
@@ -767,11 +767,38 @@ async function handleSettings(config) {
|
|
|
767
767
|
return config;
|
|
768
768
|
}
|
|
769
769
|
export async function main() {
|
|
770
|
+
// === Termux / Android Stability: Global Signal Handlers ===
|
|
771
|
+
// Ignore SIGHUP so the process survives when Termux is minimized
|
|
772
|
+
if (process.platform !== 'win32') {
|
|
773
|
+
process.on('SIGHUP', () => {
|
|
774
|
+
// Termux minimized or window lost focus — keep process alive
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
// Graceful shutdown on SIGTERM (closing Termux tab / kill command)
|
|
778
|
+
process.on('SIGTERM', () => {
|
|
779
|
+
try {
|
|
780
|
+
resetTerminalTheme();
|
|
781
|
+
MCPManager.getInstance().stopAll();
|
|
782
|
+
}
|
|
783
|
+
catch { }
|
|
784
|
+
process.exit(0);
|
|
785
|
+
});
|
|
786
|
+
// Protect against crash on background I/O errors (EIO/EPIPE/ERR_STREAM_DESTROYED)
|
|
787
|
+
process.on('uncaughtException', (err) => {
|
|
788
|
+
const isIgnorableIo = err.code === 'EIO' || err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED';
|
|
789
|
+
if (isIgnorableIo)
|
|
790
|
+
return;
|
|
791
|
+
console.error('\n[devx runtime error]:', err.message);
|
|
792
|
+
});
|
|
793
|
+
process.on('unhandledRejection', (reason) => {
|
|
794
|
+
// Prevent Node.js crash from unhandled network promise rejections
|
|
795
|
+
});
|
|
796
|
+
acquireWakeLock();
|
|
770
797
|
const program = new Command();
|
|
771
798
|
program
|
|
772
799
|
.name('devx')
|
|
773
800
|
.description('Terminal-native AI coding assistant and vibe-coding agent')
|
|
774
|
-
.version('1.4.
|
|
801
|
+
.version('1.4.15')
|
|
775
802
|
.option('-p, --prompt <task>', 'Run one-shot task non-interactively (headless mode)')
|
|
776
803
|
.option('-y, --yolo', 'Automatically approve all tool executions without confirmation')
|
|
777
804
|
.option('-m, --model <model>', 'Specify AI model to use for this execution')
|
package/dist/cli/prompt.js
CHANGED
|
@@ -69,6 +69,9 @@ export function askPrompt(opts = {}) {
|
|
|
69
69
|
process.stdin.setRawMode(true);
|
|
70
70
|
process.stdout.write('\x1b[?2004h'); // Enable bracketed paste mode
|
|
71
71
|
}
|
|
72
|
+
const onResize = () => { if (!disposed)
|
|
73
|
+
render(); };
|
|
74
|
+
process.stdout.on('resize', onResize);
|
|
72
75
|
function getTagSpans() {
|
|
73
76
|
const spans = [];
|
|
74
77
|
const regex = /\[(?:Pasted text #\d+ \+\d+ lines|\d+\.png \d+kb)\]/g;
|
|
@@ -144,7 +147,7 @@ export function askPrompt(opts = {}) {
|
|
|
144
147
|
}));
|
|
145
148
|
}
|
|
146
149
|
const beforeCursor = input.slice(0, cursorPos);
|
|
147
|
-
const atMatch = beforeCursor.match(/@([a-zA-Z0-9_
|
|
150
|
+
const atMatch = beforeCursor.match(/@([a-zA-Z0-9_\-./\u0400-\u04FF]*)$/);
|
|
148
151
|
if (atMatch && availableFiles.length > 0) {
|
|
149
152
|
const query = atMatch[1].toLowerCase();
|
|
150
153
|
const replaceStart = cursorPos - atMatch[0].length;
|
|
@@ -174,7 +177,9 @@ export function askPrompt(opts = {}) {
|
|
|
174
177
|
selectedIndex = items.length - 1;
|
|
175
178
|
if (selectedIndex < 0)
|
|
176
179
|
selectedIndex = 0;
|
|
177
|
-
|
|
180
|
+
// On very small screens (keyboard open on Android), limit dropdown aggressively
|
|
181
|
+
const maxDropdownRows = Math.max(2, Math.min(rows - 3, 6));
|
|
182
|
+
const pageSize = Math.min(4, Math.max(2, Math.min(maxDropdownRows - 2, Math.floor(rows / 4))));
|
|
178
183
|
const innerBoxWidth = Math.max(10, Math.min(cols - 8, 48));
|
|
179
184
|
const total = items.length;
|
|
180
185
|
let startIndex = 0;
|
|
@@ -233,6 +238,8 @@ export function askPrompt(opts = {}) {
|
|
|
233
238
|
}
|
|
234
239
|
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╰' + botBorderStr + '╯') + '\x1b[0m');
|
|
235
240
|
}
|
|
241
|
+
// === SAFE RENDERING: Use \x1b7/\x1b8 save/restore + \x1b[B (down) instead of \n ===
|
|
242
|
+
// This prevents hardware scroll when cursor is at the bottom of a small screen.
|
|
236
243
|
// 1. Format and write the Prompt line safely (guaranteed <= cols - 1 chars)
|
|
237
244
|
let inputDisplay = pc.dim('│') + ' ';
|
|
238
245
|
let renderCursorCol = 3;
|
|
@@ -258,22 +265,35 @@ export function askPrompt(opts = {}) {
|
|
|
258
265
|
renderCursorCol = 3 + (prefix ? 1 : 0) + (cursorPos - start);
|
|
259
266
|
}
|
|
260
267
|
}
|
|
268
|
+
// Write prompt line (no \n — stays on same line)
|
|
261
269
|
process.stdout.write(`\x1b[0m\r\x1b[2K${inputDisplay}\x1b[0m`);
|
|
262
|
-
// 2. Draw dropdown
|
|
270
|
+
// 2. Draw dropdown below using \x1b[B (cursor down) + \r instead of \n
|
|
271
|
+
// \x1b[B moves cursor down WITHOUT scrolling if already at bottom — it just doesn't move.
|
|
272
|
+
// We first ensure enough blank lines exist by using \x1b[S (scroll up) if needed.
|
|
263
273
|
const maxDropdowns = Math.max(dropdownLines.length, lastRenderedDropdownLines);
|
|
264
274
|
if (maxDropdowns > 0) {
|
|
275
|
+
// Save cursor position
|
|
276
|
+
process.stdout.write('\x1b7');
|
|
277
|
+
// Ensure we have room: scroll terminal up if we're near the bottom
|
|
278
|
+
// Use \x1b[<n>S to scroll the viewport up, creating space below
|
|
279
|
+
if (dropdownLines.length > 0) {
|
|
280
|
+
const neededSpace = dropdownLines.length;
|
|
281
|
+
// Push content up to make room, then reposition
|
|
282
|
+
process.stdout.write(`\x1b[${neededSpace}S`);
|
|
283
|
+
// Move cursor back up by the same amount (it moved with the scroll)
|
|
284
|
+
process.stdout.write(`\x1b[${neededSpace}A`);
|
|
285
|
+
}
|
|
265
286
|
for (let i = 0; i < maxDropdowns; i++) {
|
|
287
|
+
// Move to next line: \x1b[1B moves down 1, \r returns to col 0
|
|
288
|
+
process.stdout.write(`\x1b[1B\r\x1b[0m\x1b[2K`);
|
|
266
289
|
if (i < dropdownLines.length) {
|
|
267
|
-
process.stdout.write(
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
process.stdout.write(`\n\x1b[0m\x1b[2K`);
|
|
290
|
+
process.stdout.write(dropdownLines[i] + '\x1b[0m');
|
|
271
291
|
}
|
|
272
292
|
}
|
|
273
|
-
//
|
|
274
|
-
process.stdout.write(
|
|
293
|
+
// Restore cursor position (back to prompt line)
|
|
294
|
+
process.stdout.write('\x1b8');
|
|
275
295
|
}
|
|
276
|
-
// 3. Place cursor precisely
|
|
296
|
+
// 3. Place cursor precisely on the prompt line
|
|
277
297
|
process.stdout.write(`\r\x1b[${renderCursorCol}C`);
|
|
278
298
|
lastRenderedDropdownLines = dropdownLines.length;
|
|
279
299
|
}
|
|
@@ -283,6 +303,7 @@ export function askPrompt(opts = {}) {
|
|
|
283
303
|
if (process.stdout.isTTY) {
|
|
284
304
|
process.stdout.write('\x1b[?2004l');
|
|
285
305
|
}
|
|
306
|
+
process.stdout.removeListener('resize', onResize);
|
|
286
307
|
process.stdin.removeListener('data', onData);
|
|
287
308
|
if (process.stdin.isTTY) {
|
|
288
309
|
process.stdin.setRawMode(false);
|
|
@@ -291,10 +312,11 @@ export function askPrompt(opts = {}) {
|
|
|
291
312
|
}
|
|
292
313
|
function clearBoxAndExit(finalInput) {
|
|
293
314
|
if (lastRenderedDropdownLines > 0) {
|
|
315
|
+
process.stdout.write('\x1b7');
|
|
294
316
|
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
295
|
-
process.stdout.write(`\
|
|
317
|
+
process.stdout.write(`\x1b[1B\r\x1b[0m\x1b[2K`);
|
|
296
318
|
}
|
|
297
|
-
process.stdout.write(
|
|
319
|
+
process.stdout.write('\x1b8');
|
|
298
320
|
lastRenderedDropdownLines = 0;
|
|
299
321
|
}
|
|
300
322
|
const fullText = expandPastes(finalInput);
|
|
@@ -349,10 +371,11 @@ export function askPrompt(opts = {}) {
|
|
|
349
371
|
// Ctrl+C / SIGINT
|
|
350
372
|
if (str === '\x03') {
|
|
351
373
|
if (lastRenderedDropdownLines > 0) {
|
|
374
|
+
process.stdout.write('\x1b7');
|
|
352
375
|
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
353
|
-
process.stdout.write(`\
|
|
376
|
+
process.stdout.write(`\x1b[1B\r\x1b[0m\x1b[2K`);
|
|
354
377
|
}
|
|
355
|
-
process.stdout.write(
|
|
378
|
+
process.stdout.write('\x1b8');
|
|
356
379
|
lastRenderedDropdownLines = 0;
|
|
357
380
|
}
|
|
358
381
|
process.stdout.write(`\x1b[0m\r\x1b[2K\n`);
|
|
@@ -555,8 +578,11 @@ export function askPrompt(opts = {}) {
|
|
|
555
578
|
cursorPos = endingSpan.start;
|
|
556
579
|
}
|
|
557
580
|
else {
|
|
558
|
-
|
|
559
|
-
cursorPos
|
|
581
|
+
// Handle surrogate pairs (emoji)
|
|
582
|
+
const prevChar = input.charCodeAt(cursorPos - 1);
|
|
583
|
+
const deleteCount = (prevChar >= 0xDC00 && prevChar <= 0xDFFF && cursorPos >= 2) ? 2 : 1;
|
|
584
|
+
input = input.slice(0, cursorPos - deleteCount) + input.slice(cursorPos);
|
|
585
|
+
cursorPos -= deleteCount;
|
|
560
586
|
}
|
|
561
587
|
}
|
|
562
588
|
}
|
package/dist/cli/server.js
CHANGED
|
@@ -139,7 +139,7 @@ function renderDirectoryHtml(dirPath, relPath, files, port) {
|
|
|
139
139
|
${parentLink}
|
|
140
140
|
${items || '<li style="padding: 20px; text-align: center; color: #6e7681;">No visible files in this directory</li>'}
|
|
141
141
|
</ul>
|
|
142
|
-
<div class="footer">devx v1.4.
|
|
142
|
+
<div class="footer">devx v1.4.15 • Terminal-Native AI Assistant</div>
|
|
143
143
|
</div>
|
|
144
144
|
</body>
|
|
145
145
|
</html>`;
|
|
@@ -224,15 +224,18 @@ export async function startServer(preferredPort = 3000) {
|
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
226
226
|
// 4. SPA Fallback: check if root index.html exists
|
|
227
|
-
const
|
|
228
|
-
if (
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
227
|
+
const hasAssetExt = path.extname(reqPath).length > 0;
|
|
228
|
+
if (!hasAssetExt) {
|
|
229
|
+
const rootIndex = path.join(cwd, 'index.html');
|
|
230
|
+
if (fsSync.existsSync(rootIndex)) {
|
|
231
|
+
const content = await fs.readFile(rootIndex);
|
|
232
|
+
res.writeHead(200, {
|
|
233
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
234
|
+
'Access-Control-Allow-Origin': '*'
|
|
235
|
+
});
|
|
236
|
+
res.end(content);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
236
239
|
}
|
|
237
240
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
238
241
|
res.end(`404 Not Found: ${reqPath}`);
|
package/dist/cli/smooth.js
CHANGED
|
@@ -49,6 +49,13 @@ export class SmoothStreamer {
|
|
|
49
49
|
else {
|
|
50
50
|
take = 4;
|
|
51
51
|
}
|
|
52
|
+
if (take < fullStr.length) {
|
|
53
|
+
const code = fullStr.charCodeAt(take - 1);
|
|
54
|
+
// If take ends on a high surrogate (0xD800..0xDBFF), advance by 1 to include low surrogate
|
|
55
|
+
if (code >= 0xD800 && code <= 0xDBFF) {
|
|
56
|
+
take++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
52
59
|
const chunk = fullStr.slice(0, take);
|
|
53
60
|
const rest = fullStr.slice(take);
|
|
54
61
|
if (rest) {
|
package/dist/core/commands.js
CHANGED
|
@@ -69,7 +69,7 @@ export class CustomCommandManager {
|
|
|
69
69
|
// Support positional parameters: $1, $2, etc.
|
|
70
70
|
const parts = trimmedArgs.split(/\s+/);
|
|
71
71
|
for (let i = 0; i < parts.length; i++) {
|
|
72
|
-
expanded = expanded.replace(new RegExp(`\\$${i + 1}`, 'g'), () => parts[i]);
|
|
72
|
+
expanded = expanded.replace(new RegExp(`\\$${i + 1}(?!\\d)`, 'g'), () => parts[i]);
|
|
73
73
|
}
|
|
74
74
|
return expanded;
|
|
75
75
|
}
|
package/dist/core/history.js
CHANGED
|
@@ -36,18 +36,36 @@ export class History {
|
|
|
36
36
|
const chatOnly = this.messages.filter(m => m.role !== 'system');
|
|
37
37
|
return this.estimateTokens(chatOnly);
|
|
38
38
|
}
|
|
39
|
-
// Эвристика: кол-во токенов = длина всех строк / 3.5
|
|
40
39
|
estimateTokens(messages) {
|
|
41
|
-
let
|
|
40
|
+
let tokens = 0;
|
|
41
|
+
const countStringTokens = (str) => {
|
|
42
|
+
if (!str)
|
|
43
|
+
return 0;
|
|
44
|
+
let t = 0;
|
|
45
|
+
for (let i = 0; i < str.length; i++) {
|
|
46
|
+
const code = str.charCodeAt(i);
|
|
47
|
+
if (code >= 0x0400 && code <= 0x04FF) {
|
|
48
|
+
t += 0.8;
|
|
49
|
+
}
|
|
50
|
+
else if (code > 0x07FF) {
|
|
51
|
+
t += 1.2;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
t += 0.25;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return t;
|
|
58
|
+
};
|
|
42
59
|
for (const m of messages) {
|
|
43
|
-
|
|
60
|
+
tokens += countStringTokens(m.content || '');
|
|
44
61
|
if (m.tool_calls) {
|
|
45
62
|
for (const tc of m.tool_calls) {
|
|
46
|
-
|
|
63
|
+
tokens += countStringTokens(tc.name);
|
|
64
|
+
tokens += countStringTokens(JSON.stringify(tc.arguments));
|
|
47
65
|
}
|
|
48
66
|
}
|
|
49
67
|
}
|
|
50
|
-
return Math.ceil(
|
|
68
|
+
return Math.ceil(tokens);
|
|
51
69
|
}
|
|
52
70
|
groupIntoBlocks(messages) {
|
|
53
71
|
const blocks = [];
|
package/dist/core/loop.js
CHANGED
|
@@ -159,8 +159,20 @@ export class Agent {
|
|
|
159
159
|
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
160
160
|
break;
|
|
161
161
|
}
|
|
162
|
+
const executedToolCallIds = new Set();
|
|
162
163
|
for (const call of response.toolCalls) {
|
|
163
164
|
if (signal?.aborted) {
|
|
165
|
+
// Add synthetic responses for ALL remaining unexecuted tool calls
|
|
166
|
+
for (const remaining of response.toolCalls) {
|
|
167
|
+
if (!executedToolCallIds.has(remaining.id)) {
|
|
168
|
+
this.history.addMessage({
|
|
169
|
+
role: 'tool',
|
|
170
|
+
content: '[Operation cancelled by user]',
|
|
171
|
+
tool_call_id: remaining.id,
|
|
172
|
+
name: remaining.name,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
164
176
|
return;
|
|
165
177
|
}
|
|
166
178
|
const actionDesc = getActionDescription(call.name, call.arguments);
|
|
@@ -197,6 +209,7 @@ export class Agent {
|
|
|
197
209
|
tool_call_id: call.id,
|
|
198
210
|
name: call.name,
|
|
199
211
|
});
|
|
212
|
+
executedToolCallIds.add(call.id);
|
|
200
213
|
}
|
|
201
214
|
iterations++;
|
|
202
215
|
}
|