zames_pro 2.6.0 → 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/dist/agent-loop.js +118 -82
- package/dist/browser.js +40 -41
- package/dist/config-menu.js +25 -25
- package/dist/config.js +5 -5
- package/dist/gitTools.js +4 -4
- package/dist/i18n.js +11 -7
- package/dist/index.js +153 -150
- package/dist/input.js +82 -77
- 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 +20 -16
- package/dist/system-prompt.js +23 -0
- 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,34 +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
|
-
// Ctrl+J, Ctrl+Enter
|
|
14
|
-
//
|
|
15
|
-
// *
|
|
16
|
-
// *
|
|
17
|
-
// Ctrl+W; Ctrl+K —
|
|
18
|
-
// *
|
|
19
|
-
//
|
|
20
|
-
// *
|
|
21
|
-
//
|
|
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.
|
|
22
22
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
23
|
+
// All control characters are assembled from codes so the file has no
|
|
24
|
+
// "raw" ESC/CR/LF in string literals (see AGENTS.md).
|
|
25
25
|
const NL = String.fromCharCode(10);
|
|
26
26
|
const CR = String.fromCharCode(13);
|
|
27
27
|
const ESC = String.fromCharCode(27);
|
|
28
28
|
const PASTE_START = ESC + '[200~';
|
|
29
29
|
const PASTE_END = ESC + '[201~';
|
|
30
|
-
//
|
|
31
|
-
//
|
|
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.
|
|
32
32
|
const DOTS = ['', '.', '..', '...'];
|
|
33
33
|
const DOTS_PAD = ' ';
|
|
34
34
|
const HINT = theme.dim(' · Esc — стоп');
|
|
@@ -40,15 +40,15 @@ function safeJson(v) {
|
|
|
40
40
|
return String(v);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
//
|
|
44
|
-
// (
|
|
45
|
-
//
|
|
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.
|
|
46
46
|
export function visRows(s, cols) {
|
|
47
47
|
const width = Math.max(1, Number(cols) || 80);
|
|
48
48
|
const len = visLen(s);
|
|
49
49
|
return Math.max(1, Math.ceil(len / width));
|
|
50
50
|
}
|
|
51
|
-
//
|
|
51
|
+
// Visible length of a line without ANSI sequences.
|
|
52
52
|
export function visLen(s) {
|
|
53
53
|
s = String(s);
|
|
54
54
|
let n = 0;
|
|
@@ -82,13 +82,13 @@ export function layoutInput(promptStr, buf, cursor, cols) {
|
|
|
82
82
|
const prefix = first ? promptStr : pad;
|
|
83
83
|
const avail = Math.max(1, width - promptW);
|
|
84
84
|
const start = i;
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
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).
|
|
89
89
|
let text = '';
|
|
90
90
|
let count = 0;
|
|
91
|
-
let lastSpace = -1; //
|
|
91
|
+
let lastSpace = -1; // index of the space in text (by Array.from)
|
|
92
92
|
while (i < chars.length && chars[i] !== NL && count < avail) {
|
|
93
93
|
if (chars[i] === ' ')
|
|
94
94
|
lastSpace = count;
|
|
@@ -98,7 +98,7 @@ export function layoutInput(promptStr, buf, cursor, cols) {
|
|
|
98
98
|
}
|
|
99
99
|
const atLineEnd = i >= chars.length || chars[i] === NL;
|
|
100
100
|
if (!atLineEnd && count >= avail && lastSpace > 0) {
|
|
101
|
-
//
|
|
101
|
+
// Move the "tail" of the line to the next visual line.
|
|
102
102
|
const textChars = Array.from(text);
|
|
103
103
|
const tailLen = textChars.length - lastSpace;
|
|
104
104
|
i -= tailLen;
|
|
@@ -149,11 +149,11 @@ export class LineEditor {
|
|
|
149
149
|
_thinkBase;
|
|
150
150
|
_wasRaw;
|
|
151
151
|
_onData;
|
|
152
|
-
//
|
|
152
|
+
// History of sent messages (for the up/down arrows).
|
|
153
153
|
history;
|
|
154
154
|
_histIndex;
|
|
155
155
|
_histDraft;
|
|
156
|
-
//
|
|
156
|
+
// Slash-command hints (shown when you type «/»).
|
|
157
157
|
slashCommands;
|
|
158
158
|
_suggestCount;
|
|
159
159
|
constructor({ prompt = '> ', commands = [] } = {}) {
|
|
@@ -180,9 +180,9 @@ export class LineEditor {
|
|
|
180
180
|
this.slashCommands = commands;
|
|
181
181
|
this._suggestCount = 0;
|
|
182
182
|
}
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
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.
|
|
186
186
|
_suggestions() {
|
|
187
187
|
const b = this.buf;
|
|
188
188
|
if (!b.startsWith('/'))
|
|
@@ -192,8 +192,8 @@ export class LineEditor {
|
|
|
192
192
|
const q = b.toLowerCase();
|
|
193
193
|
return this.slashCommands.filter((c) => c.name.toLowerCase().startsWith(q));
|
|
194
194
|
}
|
|
195
|
-
// Tab:
|
|
196
|
-
//
|
|
195
|
+
// Tab: complete the command up to the common prefix; if there is a single
|
|
196
|
+
// match — insert it whole and add a space.
|
|
197
197
|
_completeCommand() {
|
|
198
198
|
const sugg = this._suggestions();
|
|
199
199
|
if (!sugg.length)
|
|
@@ -236,9 +236,9 @@ export class LineEditor {
|
|
|
236
236
|
if (stdin.setRawMode)
|
|
237
237
|
stdin.setRawMode(this._wasRaw || false);
|
|
238
238
|
}
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
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
242
|
pause() {
|
|
243
243
|
const stdin = process.stdin;
|
|
244
244
|
this._stopDots();
|
|
@@ -263,7 +263,7 @@ export class LineEditor {
|
|
|
263
263
|
this.promptStr = str;
|
|
264
264
|
this._render();
|
|
265
265
|
}
|
|
266
|
-
//
|
|
266
|
+
// Update slash-command descriptions (interface language change).
|
|
267
267
|
setCommands(commands) {
|
|
268
268
|
this.slashCommands = commands;
|
|
269
269
|
this._render();
|
|
@@ -273,7 +273,7 @@ export class LineEditor {
|
|
|
273
273
|
this.cursor = 0;
|
|
274
274
|
this._render();
|
|
275
275
|
}
|
|
276
|
-
// ----------
|
|
276
|
+
// ---------- rendering ----------
|
|
277
277
|
_eraseBlock() {
|
|
278
278
|
if (!this.rendered)
|
|
279
279
|
return;
|
|
@@ -289,14 +289,14 @@ export class LineEditor {
|
|
|
289
289
|
let top = 0;
|
|
290
290
|
if (this.statusText) {
|
|
291
291
|
out += this.statusText + NL;
|
|
292
|
-
//
|
|
293
|
-
//
|
|
292
|
+
// The status may wrap onto several lines — we account for this,
|
|
293
|
+
// otherwise the block erase misses and statuses pile up.
|
|
294
294
|
top = visRows(this.statusText, cols);
|
|
295
295
|
}
|
|
296
296
|
const lay = layoutInput(this.promptStr, this.buf, this.cursor, cols);
|
|
297
297
|
out += lay.rows.map((r) => r.prefix + r.text).join(NL);
|
|
298
|
-
//
|
|
299
|
-
//
|
|
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.
|
|
300
300
|
const sugg = this._suggestions();
|
|
301
301
|
const shown = sugg.slice(0, 8);
|
|
302
302
|
this._suggestCount = shown.length;
|
|
@@ -314,8 +314,8 @@ export class LineEditor {
|
|
|
314
314
|
}
|
|
315
315
|
process.stdout.write(out);
|
|
316
316
|
const lastRow = lay.rows.length - 1;
|
|
317
|
-
//
|
|
318
|
-
//
|
|
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.
|
|
319
319
|
const linesBelow = shown.length ? shown.length + (sugg.length > shown.length ? 1 : 0) : 0;
|
|
320
320
|
const up = lastRow - lay.cursorRow + linesBelow;
|
|
321
321
|
if (up > 0)
|
|
@@ -330,13 +330,13 @@ export class LineEditor {
|
|
|
330
330
|
this._eraseBlock();
|
|
331
331
|
this._writeBlock();
|
|
332
332
|
}
|
|
333
|
-
//
|
|
333
|
+
// Print a block ABOVE the input line and put the input line back.
|
|
334
334
|
printAbove(text) {
|
|
335
335
|
this._eraseBlock();
|
|
336
336
|
process.stdout.write(String(text) + NL);
|
|
337
337
|
this._writeBlock();
|
|
338
338
|
}
|
|
339
|
-
// ----------
|
|
339
|
+
// ---------- spinner-compatible interface ----------
|
|
340
340
|
setStatus(text) {
|
|
341
341
|
this.statusText = text || '';
|
|
342
342
|
this._render();
|
|
@@ -348,8 +348,8 @@ export class LineEditor {
|
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
_dots(n) {
|
|
351
|
-
//
|
|
352
|
-
//
|
|
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.
|
|
353
353
|
return theme.brown(DOTS[n] + DOTS_PAD.slice(DOTS[n].length));
|
|
354
354
|
}
|
|
355
355
|
_startThinking() {
|
|
@@ -399,14 +399,19 @@ export class LineEditor {
|
|
|
399
399
|
const rendered = renderMarkdown(msg);
|
|
400
400
|
this.printAbove(NL + theme.assistant('● Ответ') + NL + rendered + NL + theme.dim('─'.repeat(60)));
|
|
401
401
|
}
|
|
402
|
-
//
|
|
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 ----------
|
|
403
408
|
_submit() {
|
|
404
409
|
const text = this.buf;
|
|
405
410
|
if (!text.trim()) {
|
|
406
411
|
this._render();
|
|
407
412
|
return;
|
|
408
413
|
}
|
|
409
|
-
//
|
|
414
|
+
// Add to history only non-empty messages that don't duplicate the previous one.
|
|
410
415
|
if (text.trim() && this.history[this.history.length - 1] !== text) {
|
|
411
416
|
this.history.push(text);
|
|
412
417
|
}
|
|
@@ -428,9 +433,9 @@ export class LineEditor {
|
|
|
428
433
|
this.buf = next.join('');
|
|
429
434
|
this.cursor += ins.length;
|
|
430
435
|
}
|
|
431
|
-
//
|
|
432
|
-
// (
|
|
433
|
-
//
|
|
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.
|
|
434
439
|
_insertNewline() {
|
|
435
440
|
const chars = Array.from(this.buf);
|
|
436
441
|
if (this.cursor > 0 && chars[this.cursor - 1] === '\\') {
|
|
@@ -477,9 +482,9 @@ export class LineEditor {
|
|
|
477
482
|
i++;
|
|
478
483
|
this.cursor = i;
|
|
479
484
|
}
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
//
|
|
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.
|
|
483
488
|
_onFirstVisualLine() {
|
|
484
489
|
const cols = process.stdout.columns || 80;
|
|
485
490
|
const lay = layoutInput(this.promptStr, this.buf, this.cursor, cols);
|
|
@@ -521,8 +526,8 @@ export class LineEditor {
|
|
|
521
526
|
nextEnd++;
|
|
522
527
|
this.cursor = nextStart + Math.min(col, nextEnd - nextStart);
|
|
523
528
|
}
|
|
524
|
-
//
|
|
525
|
-
//
|
|
529
|
+
// Move one word back (Ctrl+Left / Alt+B): skip spaces
|
|
530
|
+
// on the left, then go to the start of the word.
|
|
526
531
|
_wordLeft() {
|
|
527
532
|
const chars = Array.from(this.buf);
|
|
528
533
|
let i = this.cursor;
|
|
@@ -532,7 +537,7 @@ export class LineEditor {
|
|
|
532
537
|
i--;
|
|
533
538
|
this.cursor = i;
|
|
534
539
|
}
|
|
535
|
-
//
|
|
540
|
+
// Move one word forward (Ctrl+Right / Alt+F).
|
|
536
541
|
_wordRight() {
|
|
537
542
|
const chars = Array.from(this.buf);
|
|
538
543
|
let i = this.cursor;
|
|
@@ -542,7 +547,7 @@ export class LineEditor {
|
|
|
542
547
|
i++;
|
|
543
548
|
this.cursor = i;
|
|
544
549
|
}
|
|
545
|
-
//
|
|
550
|
+
// Delete the word on the left (Ctrl+W / Alt+Backspace).
|
|
546
551
|
_deleteWordLeft() {
|
|
547
552
|
const chars = Array.from(this.buf);
|
|
548
553
|
let i = this.cursor;
|
|
@@ -554,7 +559,7 @@ export class LineEditor {
|
|
|
554
559
|
this.buf = chars.join('');
|
|
555
560
|
this.cursor = i;
|
|
556
561
|
}
|
|
557
|
-
// ----------
|
|
562
|
+
// ---------- history ----------
|
|
558
563
|
_historyUp() {
|
|
559
564
|
if (!this.history.length)
|
|
560
565
|
return;
|
|
@@ -612,8 +617,8 @@ export class LineEditor {
|
|
|
612
617
|
}
|
|
613
618
|
continue;
|
|
614
619
|
}
|
|
615
|
-
// Shift+Enter, Ctrl+Enter
|
|
616
|
-
//
|
|
620
|
+
// Shift+Enter, Ctrl+Enter in terminals with the extended protocol
|
|
621
|
+
// and Alt+Enter — insert a newline.
|
|
617
622
|
if (s.startsWith(ESC + '[13;2u')) {
|
|
618
623
|
this._insertNewline();
|
|
619
624
|
s = s.slice(7);
|
|
@@ -647,10 +652,10 @@ export class LineEditor {
|
|
|
647
652
|
const ch = s[0];
|
|
648
653
|
const code = s.charCodeAt(0);
|
|
649
654
|
s = s.slice(1);
|
|
650
|
-
// Enter:
|
|
651
|
-
// «\»
|
|
652
|
-
// Ctrl+J (code 10)
|
|
653
|
-
// (ESC[13;5u)
|
|
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.
|
|
654
659
|
if (ch === CR) {
|
|
655
660
|
const before = this.cursor > 0 ? Array.from(this.buf)[this.cursor - 1] : '';
|
|
656
661
|
if (before === '\\') {
|
|
@@ -703,7 +708,7 @@ export class LineEditor {
|
|
|
703
708
|
continue;
|
|
704
709
|
}
|
|
705
710
|
if (code === 11) {
|
|
706
|
-
// Ctrl+K —
|
|
711
|
+
// Ctrl+K — delete from the cursor to the end of the line.
|
|
707
712
|
const arr = Array.from(this.buf);
|
|
708
713
|
let e = this.cursor;
|
|
709
714
|
while (e < arr.length && arr[e] !== NL)
|
|
@@ -720,7 +725,7 @@ export class LineEditor {
|
|
|
720
725
|
}
|
|
721
726
|
if (code === 27) {
|
|
722
727
|
// Ctrl+Left / Ctrl+Right (xterm: ESC [1;5D / ESC [1;5C;
|
|
723
|
-
//
|
|
728
|
+
// some terminals: ESC [5D / ESC [5C).
|
|
724
729
|
if (s.startsWith('[1;5D') || s.startsWith('[5D')) {
|
|
725
730
|
this._wordLeft();
|
|
726
731
|
s = s.slice(s.startsWith('[1;5D') ? 5 : 3);
|
|
@@ -746,7 +751,7 @@ export class LineEditor {
|
|
|
746
751
|
continue;
|
|
747
752
|
}
|
|
748
753
|
if (s.startsWith('[A')) {
|
|
749
|
-
//
|
|
754
|
+
// Up: on the first visual line — history, otherwise — the line above.
|
|
750
755
|
if (this._onFirstVisualLine())
|
|
751
756
|
this._historyUp();
|
|
752
757
|
else
|
|
@@ -782,7 +787,7 @@ export class LineEditor {
|
|
|
782
787
|
this._render();
|
|
783
788
|
continue;
|
|
784
789
|
}
|
|
785
|
-
// Alt+B / Alt+F —
|
|
790
|
+
// Alt+B / Alt+F — word movement.
|
|
786
791
|
if (s.startsWith('b') || s.startsWith('B')) {
|
|
787
792
|
this._wordLeft();
|
|
788
793
|
s = s.slice(1);
|
|
@@ -808,7 +813,7 @@ export class LineEditor {
|
|
|
808
813
|
}
|
|
809
814
|
}
|
|
810
815
|
}
|
|
811
|
-
//
|
|
816
|
+
// Layout check: node src/input.js --selftest
|
|
812
817
|
export function selftest() {
|
|
813
818
|
const cases = [
|
|
814
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.`;
|
package/dist/sessions.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// <id>.json
|
|
7
|
-
//
|
|
4
|
+
// We store sessions (DeepSeek chats) in a separate folder so they aren't lost
|
|
5
|
+
// after a process restart. Each session is a separate JSON file
|
|
6
|
+
// <id>.json in ~/.zames/.sessions. last.json points to the last
|
|
7
|
+
// opened session for a specific working directory.
|
|
8
8
|
const SESSIONS_DIR = path.join(os.homedir(), '.zames', '.sessions');
|
|
9
9
|
const INDEX_FILE = path.join(SESSIONS_DIR, 'last.json');
|
|
10
10
|
function ensureDir() {
|
|
@@ -13,8 +13,8 @@ function ensureDir() {
|
|
|
13
13
|
function safeName(id) {
|
|
14
14
|
return String(id).replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
15
15
|
}
|
|
16
|
-
//
|
|
17
|
-
//
|
|
16
|
+
// Save/update a session. workdir is needed so that when launched from the same
|
|
17
|
+
// project we restore exactly its last chat.
|
|
18
18
|
export function saveSession({ id, title = '', workdir = '', }) {
|
|
19
19
|
if (!id)
|
|
20
20
|
return null;
|
|
@@ -63,16 +63,16 @@ function writeIndex({ id, workdir }) {
|
|
|
63
63
|
}
|
|
64
64
|
catch { }
|
|
65
65
|
}
|
|
66
|
-
//
|
|
67
|
-
//
|
|
66
|
+
// The last session for the working directory. If there is none —
|
|
67
|
+
// we return the last session overall (useful when launched from a new place).
|
|
68
68
|
export function loadLastSession(workdir = '') {
|
|
69
69
|
try {
|
|
70
70
|
const index = JSON.parse(fs.readFileSync(INDEX_FILE, 'utf-8'));
|
|
71
71
|
const byWorkdir = index && index.byWorkdir ? index.byWorkdir : {};
|
|
72
72
|
const candidates = [byWorkdir[workdir], index.last].filter((x) => Boolean(x));
|
|
73
73
|
for (const id of candidates) {
|
|
74
|
-
//
|
|
75
|
-
//
|
|
74
|
+
// The session file may have been deleted — the index is then stale and
|
|
75
|
+
// the session cannot be restored. We try the next candidate.
|
|
76
76
|
const s = readSession(id);
|
|
77
77
|
if (s)
|
|
78
78
|
return s;
|
|
@@ -94,7 +94,7 @@ export function readSession(id) {
|
|
|
94
94
|
return null;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
//
|
|
97
|
+
// List all sessions, newest first.
|
|
98
98
|
export function listSessions() {
|
|
99
99
|
try {
|
|
100
100
|
ensureDir();
|