zames_pro 2.5.1 → 2.7.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/README.md +32 -0
- package/dist/agent-loop.js +224 -49
- package/dist/browser.js +41 -42
- package/dist/config-menu.js +231 -0
- package/dist/config.js +143 -0
- package/dist/gitTools.js +4 -4
- package/dist/i18n.js +280 -0
- package/dist/index.js +548 -319
- package/dist/input.js +137 -67
- package/dist/markdown.js +1 -1
- package/dist/net-capture.js +20 -20
- package/dist/self-review.js +16 -16
- package/dist/sessions.js +11 -11
- package/dist/spinner.js +30 -52
- package/dist/system-prompt.js +31 -2
- package/dist/theme.js +26 -26
- package/dist/tools.js +9 -9
- package/dist/transcript.js +2 -2
- package/dist/types.js +2 -2
- package/dist/web.js +16 -16
- package/dist/xml-toolcall.js +14 -14
- package/package.json +1 -1
package/dist/input.js
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
import { theme } from './theme.js';
|
|
2
2
|
import { renderMarkdown } from './markdown.js';
|
|
3
3
|
import { randomThinkingPhrase, stripEllipsis } from './spinner.js';
|
|
4
|
-
//
|
|
4
|
+
// A permanent input line at the bottom of the terminal + a status/output area above it.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
// *
|
|
8
|
-
// *
|
|
9
|
-
// *
|
|
10
|
-
//
|
|
11
|
-
// *
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// *
|
|
17
|
-
//
|
|
18
|
-
// *
|
|
19
|
-
//
|
|
6
|
+
// Why a custom editor:
|
|
7
|
+
// * the input line is ALWAYS visible and is not overwritten by the agent's output;
|
|
8
|
+
// * the spinner/status and answers are printed ABOVE the input line (printAbove);
|
|
9
|
+
// * redraw accounts for wrapping by terminal width, so there is no
|
|
10
|
+
// line duplication on multiline input;
|
|
11
|
+
// * multiline input: Enter sends, but if the cursor is right after a «\»,
|
|
12
|
+
// Enter removes the «\» and breaks the line (like in Claude Code).
|
|
13
|
+
// Ctrl+J, Ctrl+Enter and Shift+Enter (in terminals with the extended
|
|
14
|
+
// protocol) always insert a newline;
|
|
15
|
+
// * long lines wrap by words (a word is not split in the middle);
|
|
16
|
+
// * word movement: Ctrl+←/→ (and Alt+B/Alt+F), word deletion
|
|
17
|
+
// Ctrl+W; Ctrl+K — delete to end of line;
|
|
18
|
+
// * input history: ↑/↓ at the edges of the input (inside multiline
|
|
19
|
+
// input the arrows move by lines, like in a normal terminal);
|
|
20
|
+
// * slash-command hints: when you type «/», matching commands with
|
|
21
|
+
// descriptions are shown below the line, Tab completes.
|
|
20
22
|
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
+
// All control characters are assembled from codes so the file has no
|
|
24
|
+
// "raw" ESC/CR/LF in string literals (see AGENTS.md).
|
|
23
25
|
const NL = String.fromCharCode(10);
|
|
24
26
|
const CR = String.fromCharCode(13);
|
|
25
27
|
const ESC = String.fromCharCode(27);
|
|
26
28
|
const PASTE_START = ESC + '[200~';
|
|
27
29
|
const PASTE_END = ESC + '[201~';
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
+
// Dot animation: start from an empty string (0 dots), then grow.
|
|
31
|
+
// We align the width to the maximum (3) so the hint doesn't shift.
|
|
30
32
|
const DOTS = ['', '.', '..', '...'];
|
|
31
33
|
const DOTS_PAD = ' ';
|
|
32
34
|
const HINT = theme.dim(' · Esc — стоп');
|
|
@@ -38,15 +40,15 @@ function safeJson(v) {
|
|
|
38
40
|
return String(v);
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
|
-
//
|
|
42
|
-
// (
|
|
43
|
-
//
|
|
43
|
+
// How many visual lines the text occupies at the given terminal width
|
|
44
|
+
// (accounting for wrapping). Needed so the cursor/block erase don't drift
|
|
45
|
+
// when the status is longer than the terminal width and wraps onto 2+ lines.
|
|
44
46
|
export function visRows(s, cols) {
|
|
45
47
|
const width = Math.max(1, Number(cols) || 80);
|
|
46
48
|
const len = visLen(s);
|
|
47
49
|
return Math.max(1, Math.ceil(len / width));
|
|
48
50
|
}
|
|
49
|
-
//
|
|
51
|
+
// Visible length of a line without ANSI sequences.
|
|
50
52
|
export function visLen(s) {
|
|
51
53
|
s = String(s);
|
|
52
54
|
let n = 0;
|
|
@@ -80,13 +82,13 @@ export function layoutInput(promptStr, buf, cursor, cols) {
|
|
|
80
82
|
const prefix = first ? promptStr : pad;
|
|
81
83
|
const avail = Math.max(1, width - promptW);
|
|
82
84
|
const start = i;
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
85
|
+
// Build the next visual line. First we take as many
|
|
86
|
+
// characters as fit (count < avail). If we hit the width
|
|
87
|
+
// and the next character is not the end of the line, we roll back to the
|
|
88
|
+
// last space so we don't split a word in the middle (word-wrap).
|
|
87
89
|
let text = '';
|
|
88
90
|
let count = 0;
|
|
89
|
-
let lastSpace = -1; //
|
|
91
|
+
let lastSpace = -1; // index of the space in text (by Array.from)
|
|
90
92
|
while (i < chars.length && chars[i] !== NL && count < avail) {
|
|
91
93
|
if (chars[i] === ' ')
|
|
92
94
|
lastSpace = count;
|
|
@@ -96,7 +98,7 @@ export function layoutInput(promptStr, buf, cursor, cols) {
|
|
|
96
98
|
}
|
|
97
99
|
const atLineEnd = i >= chars.length || chars[i] === NL;
|
|
98
100
|
if (!atLineEnd && count >= avail && lastSpace > 0) {
|
|
99
|
-
//
|
|
101
|
+
// Move the "tail" of the line to the next visual line.
|
|
100
102
|
const textChars = Array.from(text);
|
|
101
103
|
const tailLen = textChars.length - lastSpace;
|
|
102
104
|
i -= tailLen;
|
|
@@ -147,11 +149,11 @@ export class LineEditor {
|
|
|
147
149
|
_thinkBase;
|
|
148
150
|
_wasRaw;
|
|
149
151
|
_onData;
|
|
150
|
-
//
|
|
152
|
+
// History of sent messages (for the up/down arrows).
|
|
151
153
|
history;
|
|
152
154
|
_histIndex;
|
|
153
155
|
_histDraft;
|
|
154
|
-
//
|
|
156
|
+
// Slash-command hints (shown when you type «/»).
|
|
155
157
|
slashCommands;
|
|
156
158
|
_suggestCount;
|
|
157
159
|
constructor({ prompt = '> ', commands = [] } = {}) {
|
|
@@ -178,9 +180,9 @@ export class LineEditor {
|
|
|
178
180
|
this.slashCommands = commands;
|
|
179
181
|
this._suggestCount = 0;
|
|
180
182
|
}
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
183
|
+
// List of hints for the current input. We show them only when the line
|
|
184
|
+
// starts with «/» and is a single word (no spaces/newlines) — like
|
|
185
|
+
// command completion in Claude Code.
|
|
184
186
|
_suggestions() {
|
|
185
187
|
const b = this.buf;
|
|
186
188
|
if (!b.startsWith('/'))
|
|
@@ -190,8 +192,8 @@ export class LineEditor {
|
|
|
190
192
|
const q = b.toLowerCase();
|
|
191
193
|
return this.slashCommands.filter((c) => c.name.toLowerCase().startsWith(q));
|
|
192
194
|
}
|
|
193
|
-
// Tab:
|
|
194
|
-
//
|
|
195
|
+
// Tab: complete the command up to the common prefix; if there is a single
|
|
196
|
+
// match — insert it whole and add a space.
|
|
195
197
|
_completeCommand() {
|
|
196
198
|
const sugg = this._suggestions();
|
|
197
199
|
if (!sugg.length)
|
|
@@ -234,16 +236,44 @@ export class LineEditor {
|
|
|
234
236
|
if (stdin.setRawMode)
|
|
235
237
|
stdin.setRawMode(this._wasRaw || false);
|
|
236
238
|
}
|
|
239
|
+
// Temporarily hand the terminal to an external UI (e.g. the /config menu):
|
|
240
|
+
// we detach our input handler and erase the input block so foreign output
|
|
241
|
+
// doesn't overlap the input line. Paired with resume().
|
|
242
|
+
pause() {
|
|
243
|
+
const stdin = process.stdin;
|
|
244
|
+
this._stopDots();
|
|
245
|
+
if (this.rendered)
|
|
246
|
+
this._eraseBlock();
|
|
247
|
+
stdin.removeListener('data', this._onData);
|
|
248
|
+
process.stdout.write(ESC + '[?2004l');
|
|
249
|
+
}
|
|
250
|
+
resume() {
|
|
251
|
+
const stdin = process.stdin;
|
|
252
|
+
if (!stdin.isTTY || !stdin.setRawMode)
|
|
253
|
+
return;
|
|
254
|
+
this._wasRaw = stdin.isRaw;
|
|
255
|
+
stdin.setRawMode(true);
|
|
256
|
+
stdin.resume();
|
|
257
|
+
process.stdout.write(ESC + '[?2004h');
|
|
258
|
+
stdin.on('data', this._onData);
|
|
259
|
+
this._writeBlock();
|
|
260
|
+
this._render();
|
|
261
|
+
}
|
|
237
262
|
setPrompt(str) {
|
|
238
263
|
this.promptStr = str;
|
|
239
264
|
this._render();
|
|
240
265
|
}
|
|
266
|
+
// Update slash-command descriptions (interface language change).
|
|
267
|
+
setCommands(commands) {
|
|
268
|
+
this.slashCommands = commands;
|
|
269
|
+
this._render();
|
|
270
|
+
}
|
|
241
271
|
clear() {
|
|
242
272
|
this.buf = '';
|
|
243
273
|
this.cursor = 0;
|
|
244
274
|
this._render();
|
|
245
275
|
}
|
|
246
|
-
// ----------
|
|
276
|
+
// ---------- rendering ----------
|
|
247
277
|
_eraseBlock() {
|
|
248
278
|
if (!this.rendered)
|
|
249
279
|
return;
|
|
@@ -259,14 +289,14 @@ export class LineEditor {
|
|
|
259
289
|
let top = 0;
|
|
260
290
|
if (this.statusText) {
|
|
261
291
|
out += this.statusText + NL;
|
|
262
|
-
//
|
|
263
|
-
//
|
|
292
|
+
// The status may wrap onto several lines — we account for this,
|
|
293
|
+
// otherwise the block erase misses and statuses pile up.
|
|
264
294
|
top = visRows(this.statusText, cols);
|
|
265
295
|
}
|
|
266
296
|
const lay = layoutInput(this.promptStr, this.buf, this.cursor, cols);
|
|
267
297
|
out += lay.rows.map((r) => r.prefix + r.text).join(NL);
|
|
268
|
-
//
|
|
269
|
-
//
|
|
298
|
+
// We draw the slash-command hints BELOW the input line. We then move the
|
|
299
|
+
// cursor back up to the input line, so cursorRowFromTop doesn't change.
|
|
270
300
|
const sugg = this._suggestions();
|
|
271
301
|
const shown = sugg.slice(0, 8);
|
|
272
302
|
this._suggestCount = shown.length;
|
|
@@ -284,8 +314,8 @@ export class LineEditor {
|
|
|
284
314
|
}
|
|
285
315
|
process.stdout.write(out);
|
|
286
316
|
const lastRow = lay.rows.length - 1;
|
|
287
|
-
//
|
|
288
|
-
//
|
|
317
|
+
// Move the cursor up: first to the input line within lay, then further by
|
|
318
|
+
// the hint lines (if any) — the cursor must sit on the input.
|
|
289
319
|
const linesBelow = shown.length ? shown.length + (sugg.length > shown.length ? 1 : 0) : 0;
|
|
290
320
|
const up = lastRow - lay.cursorRow + linesBelow;
|
|
291
321
|
if (up > 0)
|
|
@@ -300,13 +330,13 @@ export class LineEditor {
|
|
|
300
330
|
this._eraseBlock();
|
|
301
331
|
this._writeBlock();
|
|
302
332
|
}
|
|
303
|
-
//
|
|
333
|
+
// Print a block ABOVE the input line and put the input line back.
|
|
304
334
|
printAbove(text) {
|
|
305
335
|
this._eraseBlock();
|
|
306
336
|
process.stdout.write(String(text) + NL);
|
|
307
337
|
this._writeBlock();
|
|
308
338
|
}
|
|
309
|
-
// ----------
|
|
339
|
+
// ---------- spinner-compatible interface ----------
|
|
310
340
|
setStatus(text) {
|
|
311
341
|
this.statusText = text || '';
|
|
312
342
|
this._render();
|
|
@@ -318,8 +348,8 @@ export class LineEditor {
|
|
|
318
348
|
}
|
|
319
349
|
}
|
|
320
350
|
_dots(n) {
|
|
321
|
-
//
|
|
322
|
-
//
|
|
351
|
+
// The dots are the same color as the base and of fixed width — otherwise
|
|
352
|
+
// the hint on the right "jumps" when the animation phase changes.
|
|
323
353
|
return theme.brown(DOTS[n] + DOTS_PAD.slice(DOTS[n].length));
|
|
324
354
|
}
|
|
325
355
|
_startThinking() {
|
|
@@ -369,14 +399,19 @@ export class LineEditor {
|
|
|
369
399
|
const rendered = renderMarkdown(msg);
|
|
370
400
|
this.printAbove(NL + theme.assistant('● Ответ') + NL + rendered + NL + theme.dim('─'.repeat(60)));
|
|
371
401
|
}
|
|
372
|
-
//
|
|
402
|
+
// Warning to the operator (e.g. the agent stopped suspiciously).
|
|
403
|
+
// Printed above the input line without overwriting it.
|
|
404
|
+
warning(msg) {
|
|
405
|
+
this.printAbove(NL + theme.warn('⚠ ' + msg));
|
|
406
|
+
}
|
|
407
|
+
// ---------- input ----------
|
|
373
408
|
_submit() {
|
|
374
409
|
const text = this.buf;
|
|
375
410
|
if (!text.trim()) {
|
|
376
411
|
this._render();
|
|
377
412
|
return;
|
|
378
413
|
}
|
|
379
|
-
//
|
|
414
|
+
// Add to history only non-empty messages that don't duplicate the previous one.
|
|
380
415
|
if (text.trim() && this.history[this.history.length - 1] !== text) {
|
|
381
416
|
this.history.push(text);
|
|
382
417
|
}
|
|
@@ -398,6 +433,18 @@ export class LineEditor {
|
|
|
398
433
|
this.buf = next.join('');
|
|
399
434
|
this.cursor += ins.length;
|
|
400
435
|
}
|
|
436
|
+
// Insert a newline. If the cursor is right after a «\»
|
|
437
|
+
// (like in Claude Code), the character is removed — so «\» + Enter gives
|
|
438
|
+
// an ordinary break without a stray backslash in the text.
|
|
439
|
+
_insertNewline() {
|
|
440
|
+
const chars = Array.from(this.buf);
|
|
441
|
+
if (this.cursor > 0 && chars[this.cursor - 1] === '\\') {
|
|
442
|
+
chars.splice(this.cursor - 1, 1);
|
|
443
|
+
this.buf = chars.join('');
|
|
444
|
+
this.cursor--;
|
|
445
|
+
}
|
|
446
|
+
this._insert(NL);
|
|
447
|
+
}
|
|
401
448
|
_backspace() {
|
|
402
449
|
if (this.cursor <= 0)
|
|
403
450
|
return;
|
|
@@ -435,9 +482,9 @@ export class LineEditor {
|
|
|
435
482
|
i++;
|
|
436
483
|
this.cursor = i;
|
|
437
484
|
}
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
485
|
+
// Is the cursor on the first/last VISUAL line (accounting for wrapping).
|
|
486
|
+
// Needed so Up/Down act as history at the edges of the input, and inside —
|
|
487
|
+
// as line movement, like in a normal terminal.
|
|
441
488
|
_onFirstVisualLine() {
|
|
442
489
|
const cols = process.stdout.columns || 80;
|
|
443
490
|
const lay = layoutInput(this.promptStr, this.buf, this.cursor, cols);
|
|
@@ -479,8 +526,8 @@ export class LineEditor {
|
|
|
479
526
|
nextEnd++;
|
|
480
527
|
this.cursor = nextStart + Math.min(col, nextEnd - nextStart);
|
|
481
528
|
}
|
|
482
|
-
//
|
|
483
|
-
//
|
|
529
|
+
// Move one word back (Ctrl+Left / Alt+B): skip spaces
|
|
530
|
+
// on the left, then go to the start of the word.
|
|
484
531
|
_wordLeft() {
|
|
485
532
|
const chars = Array.from(this.buf);
|
|
486
533
|
let i = this.cursor;
|
|
@@ -490,7 +537,7 @@ export class LineEditor {
|
|
|
490
537
|
i--;
|
|
491
538
|
this.cursor = i;
|
|
492
539
|
}
|
|
493
|
-
//
|
|
540
|
+
// Move one word forward (Ctrl+Right / Alt+F).
|
|
494
541
|
_wordRight() {
|
|
495
542
|
const chars = Array.from(this.buf);
|
|
496
543
|
let i = this.cursor;
|
|
@@ -500,7 +547,7 @@ export class LineEditor {
|
|
|
500
547
|
i++;
|
|
501
548
|
this.cursor = i;
|
|
502
549
|
}
|
|
503
|
-
//
|
|
550
|
+
// Delete the word on the left (Ctrl+W / Alt+Backspace).
|
|
504
551
|
_deleteWordLeft() {
|
|
505
552
|
const chars = Array.from(this.buf);
|
|
506
553
|
let i = this.cursor;
|
|
@@ -512,7 +559,7 @@ export class LineEditor {
|
|
|
512
559
|
this.buf = chars.join('');
|
|
513
560
|
this.cursor = i;
|
|
514
561
|
}
|
|
515
|
-
// ----------
|
|
562
|
+
// ---------- history ----------
|
|
516
563
|
_historyUp() {
|
|
517
564
|
if (!this.history.length)
|
|
518
565
|
return;
|
|
@@ -570,21 +617,34 @@ export class LineEditor {
|
|
|
570
617
|
}
|
|
571
618
|
continue;
|
|
572
619
|
}
|
|
573
|
-
// Shift+Enter
|
|
620
|
+
// Shift+Enter, Ctrl+Enter in terminals with the extended protocol
|
|
621
|
+
// and Alt+Enter — insert a newline.
|
|
574
622
|
if (s.startsWith(ESC + '[13;2u')) {
|
|
575
|
-
this.
|
|
623
|
+
this._insertNewline();
|
|
624
|
+
s = s.slice(7);
|
|
625
|
+
this._render();
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
if (s.startsWith(ESC + '[13;5u')) {
|
|
629
|
+
this._insertNewline();
|
|
576
630
|
s = s.slice(7);
|
|
577
631
|
this._render();
|
|
578
632
|
continue;
|
|
579
633
|
}
|
|
580
634
|
if (s.startsWith(ESC + '[27;2;13~')) {
|
|
581
|
-
this.
|
|
635
|
+
this._insertNewline();
|
|
636
|
+
s = s.slice(10);
|
|
637
|
+
this._render();
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (s.startsWith(ESC + '[27;5;13~')) {
|
|
641
|
+
this._insertNewline();
|
|
582
642
|
s = s.slice(10);
|
|
583
643
|
this._render();
|
|
584
644
|
continue;
|
|
585
645
|
}
|
|
586
646
|
if (s.startsWith(ESC + NL)) {
|
|
587
|
-
this.
|
|
647
|
+
this._insertNewline();
|
|
588
648
|
s = s.slice(2);
|
|
589
649
|
this._render();
|
|
590
650
|
continue;
|
|
@@ -592,12 +652,22 @@ export class LineEditor {
|
|
|
592
652
|
const ch = s[0];
|
|
593
653
|
const code = s.charCodeAt(0);
|
|
594
654
|
s = s.slice(1);
|
|
655
|
+
// Enter: if the previous character is «\», Claude-Code style: remove
|
|
656
|
+
// the «\» and break the line; otherwise send the message.
|
|
657
|
+
// Ctrl+J (code 10) always inserts a newline; Ctrl+Enter
|
|
658
|
+
// (ESC[13;5u) and Shift+Enter too.
|
|
595
659
|
if (ch === CR) {
|
|
660
|
+
const before = this.cursor > 0 ? Array.from(this.buf)[this.cursor - 1] : '';
|
|
661
|
+
if (before === '\\') {
|
|
662
|
+
this._insertNewline();
|
|
663
|
+
this._render();
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
596
666
|
this._submit();
|
|
597
667
|
continue;
|
|
598
668
|
}
|
|
599
669
|
if (code === 10) {
|
|
600
|
-
this.
|
|
670
|
+
this._insertNewline();
|
|
601
671
|
this._render();
|
|
602
672
|
continue;
|
|
603
673
|
}
|
|
@@ -638,7 +708,7 @@ export class LineEditor {
|
|
|
638
708
|
continue;
|
|
639
709
|
}
|
|
640
710
|
if (code === 11) {
|
|
641
|
-
// Ctrl+K —
|
|
711
|
+
// Ctrl+K — delete from the cursor to the end of the line.
|
|
642
712
|
const arr = Array.from(this.buf);
|
|
643
713
|
let e = this.cursor;
|
|
644
714
|
while (e < arr.length && arr[e] !== NL)
|
|
@@ -655,7 +725,7 @@ export class LineEditor {
|
|
|
655
725
|
}
|
|
656
726
|
if (code === 27) {
|
|
657
727
|
// Ctrl+Left / Ctrl+Right (xterm: ESC [1;5D / ESC [1;5C;
|
|
658
|
-
//
|
|
728
|
+
// some terminals: ESC [5D / ESC [5C).
|
|
659
729
|
if (s.startsWith('[1;5D') || s.startsWith('[5D')) {
|
|
660
730
|
this._wordLeft();
|
|
661
731
|
s = s.slice(s.startsWith('[1;5D') ? 5 : 3);
|
|
@@ -681,7 +751,7 @@ export class LineEditor {
|
|
|
681
751
|
continue;
|
|
682
752
|
}
|
|
683
753
|
if (s.startsWith('[A')) {
|
|
684
|
-
//
|
|
754
|
+
// Up: on the first visual line — history, otherwise — the line above.
|
|
685
755
|
if (this._onFirstVisualLine())
|
|
686
756
|
this._historyUp();
|
|
687
757
|
else
|
|
@@ -717,7 +787,7 @@ export class LineEditor {
|
|
|
717
787
|
this._render();
|
|
718
788
|
continue;
|
|
719
789
|
}
|
|
720
|
-
// Alt+B / Alt+F —
|
|
790
|
+
// Alt+B / Alt+F — word movement.
|
|
721
791
|
if (s.startsWith('b') || s.startsWith('B')) {
|
|
722
792
|
this._wordLeft();
|
|
723
793
|
s = s.slice(1);
|
|
@@ -743,7 +813,7 @@ export class LineEditor {
|
|
|
743
813
|
}
|
|
744
814
|
}
|
|
745
815
|
}
|
|
746
|
-
//
|
|
816
|
+
// Layout check: node src/input.js --selftest
|
|
747
817
|
export function selftest() {
|
|
748
818
|
const cases = [
|
|
749
819
|
{ p: '> ', b: 'hello', c: 5, w: 80 },
|
package/dist/markdown.js
CHANGED
|
@@ -14,7 +14,7 @@ const theme = {
|
|
|
14
14
|
tableHeader: { color: '#9fb8d8', bold: true },
|
|
15
15
|
tableCell: { color: '#d0d0d0' },
|
|
16
16
|
};
|
|
17
|
-
//
|
|
17
|
+
// Code highlighting via cli-highlight (already present transitively).
|
|
18
18
|
function highlighter(code, lang) {
|
|
19
19
|
try {
|
|
20
20
|
return highlight(code, {
|
package/dist/net-capture.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
//
|
|
4
|
+
// Extraction of the RAW model answer text from DeepSeek's network data.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
6
|
+
// Why: the answer is read from the RENDERED DOM, and DeepSeek's rendering
|
|
7
|
+
// distorts the answer — it turns dollar formulas into LaTeX (the dollar sign
|
|
8
|
+
// is lost), normalizes newlines, and auto-links. Because of this a tool-call
|
|
9
|
+
// with template strings and escaped newlines in its arguments reached the
|
|
10
|
+
// tools distorted. Here we extract the original text from the network
|
|
11
|
+
// response body.
|
|
12
12
|
//
|
|
13
13
|
// Format DeepSeek (SSE, chat.deepseek.com/api/v0/chat/completion):
|
|
14
14
|
// * startovy fragment otveta lezhit v data-chanke s uzlom v.response
|
|
@@ -36,11 +36,11 @@ function parseDataLines(body) {
|
|
|
36
36
|
}
|
|
37
37
|
return out;
|
|
38
38
|
}
|
|
39
|
-
//
|
|
40
|
-
// * OpenAI
|
|
41
|
-
//
|
|
42
|
-
// * DeepSeek chat.deepseek.com:
|
|
43
|
-
//
|
|
39
|
+
// Assembles the answer text from the SSE stream. Supports two formats:
|
|
40
|
+
// * OpenAI-compatible: choices[].delta.content (reasoning_content
|
|
41
|
+
// is ignored — that's reasoning, not the answer);
|
|
42
|
+
// * DeepSeek chat.deepseek.com: the initial v.response.fragments[] and
|
|
43
|
+
// incremental APPEND chunks response/fragments/-1/content.
|
|
44
44
|
export function extractFromSse(body) {
|
|
45
45
|
let out = '';
|
|
46
46
|
for (const obj of parseDataLines(body)) {
|
|
@@ -65,8 +65,8 @@ export function extractFromSse(body) {
|
|
|
65
65
|
}
|
|
66
66
|
continue;
|
|
67
67
|
}
|
|
68
|
-
//
|
|
69
|
-
//
|
|
68
|
+
// Incremental APPEND: p='response/fragments/-1/content' -> v=string;
|
|
69
|
+
// subsequent chunks come with a single v field.
|
|
70
70
|
if (o.p === 'response/fragments/-1/content' && typeof o.v === 'string') {
|
|
71
71
|
out += o.v;
|
|
72
72
|
continue;
|
|
@@ -87,9 +87,9 @@ export function extractFromJson(body) {
|
|
|
87
87
|
}
|
|
88
88
|
return pickAnswerText(obj);
|
|
89
89
|
}
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
90
|
+
// Extracts the final answer text from a node, WITHOUT mixing it with reasoning.
|
|
91
|
+
// Order: choices/messages -> delta/message -> content; reasoning_content is
|
|
92
|
+
// deliberately not taken — that's the model's reasoning, not the answer.
|
|
93
93
|
function pickAnswerText(node) {
|
|
94
94
|
if (node == null)
|
|
95
95
|
return '';
|
|
@@ -124,15 +124,15 @@ function pickAnswerText(node) {
|
|
|
124
124
|
}
|
|
125
125
|
return '';
|
|
126
126
|
}
|
|
127
|
-
//
|
|
127
|
+
// A universal attempt: first SSE, then regular JSON.
|
|
128
128
|
export function extractAnswer(body) {
|
|
129
129
|
const sse = extractFromSse(body);
|
|
130
130
|
if (sse)
|
|
131
131
|
return sse;
|
|
132
132
|
return extractFromJson(body);
|
|
133
133
|
}
|
|
134
|
-
//
|
|
135
|
-
//
|
|
134
|
+
// Saves the DeepSeek network response body to disk for post-mortem analysis.
|
|
135
|
+
// The files live in ~/.zames/net-log — from them the real answer format is visible.
|
|
136
136
|
export async function dumpNetBody(url, body) {
|
|
137
137
|
try {
|
|
138
138
|
const dir = path.join(os.homedir(), '.zames', 'net-log');
|
package/dist/self-review.js
CHANGED
|
@@ -4,10 +4,10 @@ import { theme } from './theme.js';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { ZAMES_HOME } from './config.js';
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
//
|
|
8
|
-
// __dirname
|
|
9
|
-
//
|
|
10
|
-
// (
|
|
7
|
+
// The sources for self-review are .ts files. After the build (tsc → dist/)
|
|
8
|
+
// __dirname points to dist, where the .js files live. So we look for the
|
|
9
|
+
// source directory: either nearby (running from src/ via tsx), or in ../src
|
|
10
|
+
// (running the built dist/ from the repo root).
|
|
11
11
|
function resolveSrcDir() {
|
|
12
12
|
const candidates = [__dirname, path.join(__dirname, '..', 'src')];
|
|
13
13
|
for (const dir of candidates) {
|
|
@@ -22,9 +22,9 @@ function resolveSrcDir() {
|
|
|
22
22
|
}
|
|
23
23
|
const SRC_DIR = resolveSrcDir();
|
|
24
24
|
const SNAP_ROOT = path.join(ZAMES_HOME, 'snapshots');
|
|
25
|
-
//
|
|
26
|
-
// self-review
|
|
27
|
-
//
|
|
25
|
+
// Dynamic import of the logic with a timestamp — so that on /reload (or
|
|
26
|
+
// auto-reload) self-review uses FRESH tools/agent-loop, not the ones cached on
|
|
27
|
+
// first load. Otherwise reload wouldn't reach self-review's dependencies.
|
|
28
28
|
async function loadFresh() {
|
|
29
29
|
const stamp = Date.now();
|
|
30
30
|
const toolsUrl = new URL('./tools.js', import.meta.url);
|
|
@@ -66,7 +66,7 @@ export async function selfReview({ browser, focus, transcript }) {
|
|
|
66
66
|
`Проверь, что src/ не пуст и ты запускаешь агента из корня проекта.\n`));
|
|
67
67
|
throw new Error('SRC_DIR пуст — нечего ревьюить');
|
|
68
68
|
}
|
|
69
|
-
//
|
|
69
|
+
// Copy package.json for context — so the agent sees the dependencies
|
|
70
70
|
try {
|
|
71
71
|
const pkg = await fs.readFile(path.resolve(SRC_DIR, '..', 'package.json'), 'utf-8');
|
|
72
72
|
await fs.writeFile(path.join(snapDir, 'package.json'), pkg, 'utf-8');
|
|
@@ -104,10 +104,10 @@ export async function selfReview({ browser, focus, transcript }) {
|
|
|
104
104
|
console.log();
|
|
105
105
|
},
|
|
106
106
|
});
|
|
107
|
-
//
|
|
107
|
+
// Save the report
|
|
108
108
|
const reportPath = path.join(snapDir, '_report.md');
|
|
109
109
|
await fs.writeFile(reportPath, finalMessage || '(пусто)', 'utf-8');
|
|
110
|
-
//
|
|
110
|
+
// Compute what changed
|
|
111
111
|
const changed = await diffFiles(SRC_DIR, snapDir);
|
|
112
112
|
console.log(theme.system('─'.repeat(60)));
|
|
113
113
|
console.log(theme.system(`Отчёт: ${reportPath}`));
|
|
@@ -159,7 +159,7 @@ export async function selfApply({ name }) {
|
|
|
159
159
|
const snapStat = await fs.stat(snapDir).catch(() => null);
|
|
160
160
|
if (!snapStat)
|
|
161
161
|
throw new Error(`Снапшот не найден: ${snapDir}`);
|
|
162
|
-
//
|
|
162
|
+
// Back up the current src before overwriting
|
|
163
163
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
164
164
|
const backupDir = path.join(SNAP_ROOT, `backup-before-apply-${stamp}`);
|
|
165
165
|
await copyDirJsFiles(SRC_DIR, backupDir);
|
|
@@ -264,16 +264,16 @@ ${focusLine}
|
|
|
264
264
|
|
|
265
265
|
When done, respond with a markdown report as the message:
|
|
266
266
|
|
|
267
|
-
##
|
|
267
|
+
## Found
|
|
268
268
|
- (bullet list of issues you found, with file:line if possible)
|
|
269
269
|
|
|
270
|
-
##
|
|
271
|
-
- (bullet list: file —
|
|
270
|
+
## Fixed
|
|
271
|
+
- (bullet list: file — what exactly you changed)
|
|
272
272
|
|
|
273
|
-
##
|
|
273
|
+
## Not fixed (deliberately)
|
|
274
274
|
- (bullet list: what you left alone and why)
|
|
275
275
|
|
|
276
|
-
##
|
|
276
|
+
## Verification
|
|
277
277
|
- (bullet list: what you verified, e.g. \`node --check\` results)
|
|
278
278
|
|
|
279
279
|
Be honest and specific. If the code is fine in some area, say so.`;
|