zames_pro 2.0.0 → 2.1.1
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 +30 -4
- package/dist/browser.js +120 -106
- package/dist/config.js +0 -1
- package/dist/confirm.js +0 -1
- package/dist/diff.js +0 -1
- package/dist/gitTools.js +0 -1
- package/dist/index.js +88 -56
- package/dist/input.js +0 -1
- package/dist/markdown.js +0 -1
- package/dist/net-capture.js +145 -0
- package/dist/self-review.js +0 -1
- package/dist/sessions.js +0 -1
- package/dist/spinner.js +0 -1
- package/dist/system-prompt.js +14 -1
- package/dist/theme.js +0 -1
- package/dist/tools.js +36 -14
- package/dist/transcript.js +0 -1
- package/dist/types.js +0 -1
- package/dist/undo.js +0 -1
- package/dist/web.js +0 -1
- package/dist/xml-toolcall.js +0 -1
- package/package.json +7 -3
- package/dist/agent-loop.js.map +0 -1
- package/dist/browser.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/confirm.js.map +0 -1
- package/dist/diff.js.map +0 -1
- package/dist/gitTools.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/input.js.map +0 -1
- package/dist/markdown.js.map +0 -1
- package/dist/self-review.js.map +0 -1
- package/dist/sessions.js.map +0 -1
- package/dist/spinner.js.map +0 -1
- package/dist/system-prompt.js.map +0 -1
- package/dist/theme.js.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/transcript.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/undo.js.map +0 -1
- package/dist/web.js.map +0 -1
- package/dist/xml-toolcall.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -76,19 +76,23 @@ function loadLastChat(workdir = '') {
|
|
|
76
76
|
// их через динамический import с timestamp-query — Node кэширует ESM по URL,
|
|
77
77
|
// поэтому такой import вернёт свежую версию модуля. Браузер, чат и текущее
|
|
78
78
|
// состояние НЕ перезапускаются: обновляется только логика.
|
|
79
|
+
// В dev-режиме tsx грузит .ts-исходники из src/, а в собранном dist — .js.
|
|
80
|
+
// Динамический import с query ?t= идёт мимо переписывания расширений tsx,
|
|
81
|
+
// поэтому расширение подбираем сами — по фактическому файлу текущего модуля.
|
|
82
|
+
const SRC_EXT = /[.]ts$/.test(new URL(import.meta.url).pathname) ? '.ts' : '.js';
|
|
79
83
|
const RELOADABLE = [
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
84
|
+
'tools',
|
|
85
|
+
'agent-loop',
|
|
86
|
+
'system-prompt',
|
|
87
|
+
'gitTools',
|
|
88
|
+
'web',
|
|
89
|
+
'self-review',
|
|
90
|
+
'diff',
|
|
91
|
+
'undo',
|
|
92
|
+
'confirm',
|
|
93
|
+
'transcript',
|
|
94
|
+
'spinner',
|
|
95
|
+
'config',
|
|
92
96
|
];
|
|
93
97
|
const mod = {
|
|
94
98
|
createTools,
|
|
@@ -105,36 +109,37 @@ async function reloadModules() {
|
|
|
105
109
|
const stamp = Date.now();
|
|
106
110
|
const loaded = new Map();
|
|
107
111
|
const errors = [];
|
|
108
|
-
for (const
|
|
112
|
+
for (const base of RELOADABLE) {
|
|
113
|
+
const rel = './' + base + SRC_EXT;
|
|
109
114
|
try {
|
|
110
115
|
const url = new URL(rel, import.meta.url);
|
|
111
116
|
url.searchParams.set('t', String(stamp));
|
|
112
117
|
const m = (await import(url.href));
|
|
113
|
-
loaded.set(
|
|
118
|
+
loaded.set(base, m);
|
|
114
119
|
}
|
|
115
120
|
catch (e) {
|
|
116
|
-
errors.push(
|
|
121
|
+
errors.push(rel + ': ' + e.message);
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
|
-
const pick = (
|
|
120
|
-
if (pick('
|
|
121
|
-
mod.createTools = pick('
|
|
122
|
-
if (pick('
|
|
123
|
-
mod.runAgentLoop = pick('
|
|
124
|
-
if (pick('
|
|
125
|
-
mod.buildSystemPrompt = pick('
|
|
126
|
-
if (pick('
|
|
127
|
-
mod.selfReview = pick('
|
|
128
|
-
if (pick('
|
|
129
|
-
mod.selfDiff = pick('
|
|
130
|
-
if (pick('
|
|
131
|
-
mod.selfApply = pick('
|
|
132
|
-
if (pick('
|
|
133
|
-
mod.selfList = pick('
|
|
134
|
-
if (pick('
|
|
135
|
-
mod.closeWeb = pick('
|
|
136
|
-
if (pick('
|
|
137
|
-
mod.createSpinner = pick('
|
|
124
|
+
const pick = (base, name) => loaded.get(base)?.[name];
|
|
125
|
+
if (pick('tools', 'createTools'))
|
|
126
|
+
mod.createTools = pick('tools', 'createTools');
|
|
127
|
+
if (pick('agent-loop', 'runAgentLoop'))
|
|
128
|
+
mod.runAgentLoop = pick('agent-loop', 'runAgentLoop');
|
|
129
|
+
if (pick('system-prompt', 'buildSystemPrompt'))
|
|
130
|
+
mod.buildSystemPrompt = pick('system-prompt', 'buildSystemPrompt');
|
|
131
|
+
if (pick('self-review', 'selfReview'))
|
|
132
|
+
mod.selfReview = pick('self-review', 'selfReview');
|
|
133
|
+
if (pick('self-review', 'selfDiff'))
|
|
134
|
+
mod.selfDiff = pick('self-review', 'selfDiff');
|
|
135
|
+
if (pick('self-review', 'selfApply'))
|
|
136
|
+
mod.selfApply = pick('self-review', 'selfApply');
|
|
137
|
+
if (pick('self-review', 'selfList'))
|
|
138
|
+
mod.selfList = pick('self-review', 'selfList');
|
|
139
|
+
if (pick('web', 'closeWeb'))
|
|
140
|
+
mod.closeWeb = pick('web', 'closeWeb');
|
|
141
|
+
if (pick('spinner', 'createSpinner'))
|
|
142
|
+
mod.createSpinner = pick('spinner', 'createSpinner');
|
|
138
143
|
return { count: loaded.size, errors };
|
|
139
144
|
}
|
|
140
145
|
// Первичная загрузка, чтобы mod.buildSystemPrompt и остальные были заполнены.
|
|
@@ -340,7 +345,9 @@ async function promptOnce(question) {
|
|
|
340
345
|
inPaste = true;
|
|
341
346
|
if (before) {
|
|
342
347
|
for (const ch of before) {
|
|
343
|
-
if (ch === '\r' || ch === '\n') {
|
|
348
|
+
if (ch === '\r' || ch === '\n') {
|
|
349
|
+
/* внутри вставки — пропускаем */
|
|
350
|
+
}
|
|
344
351
|
else
|
|
345
352
|
insertText(ch);
|
|
346
353
|
}
|
|
@@ -447,7 +454,7 @@ async function promptOnce(question) {
|
|
|
447
454
|
// в строке спиннера через onChange -> ui.setPending(). Enter отправляет буфер
|
|
448
455
|
// в onQueue, пустой Enter игнорируется. Поддержаны Backspace, Ctrl+U, Esc-
|
|
449
456
|
// последовательности (стрелки/Home/End/Delete игнорируются) и bracketed paste.
|
|
450
|
-
function watchInput({ onEscape, onChange, onQueue } = {}) {
|
|
457
|
+
function watchInput({ onEscape, onChange, onQueue, } = {}) {
|
|
451
458
|
const stdin = process.stdin;
|
|
452
459
|
if (!stdin.isTTY || !stdin.setRawMode)
|
|
453
460
|
return () => { };
|
|
@@ -474,9 +481,12 @@ function watchInput({ onEscape, onChange, onQueue } = {}) {
|
|
|
474
481
|
// трактуем как пробелы — сообщение уходит одной строкой.
|
|
475
482
|
const insert = (text) => {
|
|
476
483
|
buf += text
|
|
477
|
-
.split(CR + LF)
|
|
478
|
-
.
|
|
479
|
-
.split(
|
|
484
|
+
.split(CR + LF)
|
|
485
|
+
.join(' ')
|
|
486
|
+
.split(CR)
|
|
487
|
+
.join(' ')
|
|
488
|
+
.split(LF)
|
|
489
|
+
.join(' ');
|
|
480
490
|
};
|
|
481
491
|
function onData(data) {
|
|
482
492
|
let s = data.toString('utf-8');
|
|
@@ -580,7 +590,7 @@ async function resolveWorkdir() {
|
|
|
580
590
|
}
|
|
581
591
|
// ---------- task runner ----------
|
|
582
592
|
async function runTask(browser, tools, taskText, workdir, opts) {
|
|
583
|
-
const { transcript, freshChat, sendSystemPrompt, queue = [], ui: editor } = opts;
|
|
593
|
+
const { transcript, freshChat, sendSystemPrompt, queue = [], ui: editor, onChatReady, } = opts;
|
|
584
594
|
// В TTY-режиме UI — это LineEditor: он владеет вводом (очередь, Esc,
|
|
585
595
|
// Ctrl+C) и рисует статус НАД постоянной строкой ввода. В не-TTY режиме
|
|
586
596
|
// (пайпы) — обычный спиннер + watchInput.
|
|
@@ -598,7 +608,8 @@ async function runTask(browser, tools, taskText, workdir, opts) {
|
|
|
598
608
|
queue.push(text);
|
|
599
609
|
ui.setPending(null);
|
|
600
610
|
ui.stop();
|
|
601
|
-
console.log(theme.user('📨 В очередь (' + queue.length + '): ') +
|
|
611
|
+
console.log(theme.user('📨 В очередь (' + queue.length + '): ') +
|
|
612
|
+
theme.assistant(text));
|
|
602
613
|
ui.thinking();
|
|
603
614
|
},
|
|
604
615
|
});
|
|
@@ -621,11 +632,12 @@ async function runTask(browser, tools, taskText, workdir, opts) {
|
|
|
621
632
|
onToolCall: (name, toolArgs) => ui.toolCall(name, toolArgs),
|
|
622
633
|
onToolResult: (result) => ui.toolResult(result),
|
|
623
634
|
onAssistantMessage: (msg) => ui.assistant(msg),
|
|
635
|
+
onChatReady,
|
|
624
636
|
debugLog: debug,
|
|
625
637
|
});
|
|
626
638
|
if (!queue.length)
|
|
627
639
|
break;
|
|
628
|
-
const queued = queue.shift() ??
|
|
640
|
+
const queued = queue.shift() ?? '';
|
|
629
641
|
ui.stop();
|
|
630
642
|
if (editor) {
|
|
631
643
|
editor.printAbove(theme.user('▶ Из очереди: ') + theme.assistant(queued));
|
|
@@ -739,6 +751,10 @@ async function main() {
|
|
|
739
751
|
transcript,
|
|
740
752
|
freshChat,
|
|
741
753
|
sendSystemPrompt,
|
|
754
|
+
onChatReady: (chatId) => {
|
|
755
|
+
if (chatId)
|
|
756
|
+
saveLastChat(chatId, currentWorkdir);
|
|
757
|
+
},
|
|
742
758
|
});
|
|
743
759
|
await browser.close();
|
|
744
760
|
await mod.closeWeb().catch(() => { });
|
|
@@ -815,7 +831,8 @@ async function main() {
|
|
|
815
831
|
const buildPrompt = () => {
|
|
816
832
|
let tail;
|
|
817
833
|
if (reviewMode) {
|
|
818
|
-
tail =
|
|
834
|
+
tail =
|
|
835
|
+
theme.warn('REVIEW') + theme.dim(':') + theme.dir(reviewMode.snapName);
|
|
819
836
|
}
|
|
820
837
|
else {
|
|
821
838
|
tail = theme.dir(dirLabel(currentWorkdir));
|
|
@@ -860,14 +877,16 @@ async function main() {
|
|
|
860
877
|
// оба потока в ed.printAbove.
|
|
861
878
|
const origLog = console.log.bind(console);
|
|
862
879
|
const origErr = console.error.bind(console);
|
|
863
|
-
const fmt = (a) => typeof a === 'string'
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
880
|
+
const fmt = (a) => typeof a === 'string'
|
|
881
|
+
? a
|
|
882
|
+
: (() => {
|
|
883
|
+
try {
|
|
884
|
+
return JSON.stringify(a);
|
|
885
|
+
}
|
|
886
|
+
catch {
|
|
887
|
+
return String(a);
|
|
888
|
+
}
|
|
889
|
+
})();
|
|
871
890
|
console.log = (...a) => ed.printAbove(a.map(fmt).join(' '));
|
|
872
891
|
console.error = (...a) => ed.printAbove(a.map(fmt).join(' '));
|
|
873
892
|
// Сохраняем на случай отладки.
|
|
@@ -886,7 +905,9 @@ async function main() {
|
|
|
886
905
|
let tail;
|
|
887
906
|
if (reviewMode) {
|
|
888
907
|
tail =
|
|
889
|
-
theme.warn('REVIEW') +
|
|
908
|
+
theme.warn('REVIEW') +
|
|
909
|
+
theme.dim(':') +
|
|
910
|
+
theme.dir(reviewMode.snapName);
|
|
890
911
|
}
|
|
891
912
|
else {
|
|
892
913
|
tail = theme.dir(dirLabel(currentWorkdir));
|
|
@@ -897,7 +918,7 @@ async function main() {
|
|
|
897
918
|
catch {
|
|
898
919
|
break;
|
|
899
920
|
}
|
|
900
|
-
const trimmed = (input ||
|
|
921
|
+
const trimmed = (input || '').trim();
|
|
901
922
|
if (!trimmed)
|
|
902
923
|
continue;
|
|
903
924
|
const lower = trimmed.toLowerCase();
|
|
@@ -1138,7 +1159,9 @@ async function main() {
|
|
|
1138
1159
|
saveLastChat(pick.id, currentWorkdir, pick.title);
|
|
1139
1160
|
transcript.log('resume_chat', { id: pick.id, title: pick.title });
|
|
1140
1161
|
console.log(theme.assistant(`Чат открыт.`) +
|
|
1141
|
-
theme.system(resendPrompt
|
|
1162
|
+
theme.system(resendPrompt
|
|
1163
|
+
? ' Системный промпт будет переслан на следующей задаче.\n'
|
|
1164
|
+
: ' Контекст чата сохранён. Системный промпт не пересылается (--resend-prompt чтобы дослать).\n'));
|
|
1142
1165
|
}
|
|
1143
1166
|
catch (e) {
|
|
1144
1167
|
console.error(theme.error('Не удалось открыть чат:'), e.message);
|
|
@@ -1188,7 +1211,8 @@ async function main() {
|
|
|
1188
1211
|
sendSystemPromptNext = resendPrompt;
|
|
1189
1212
|
saveLastChat(id, currentWorkdir);
|
|
1190
1213
|
transcript.log('resume_chat', { id });
|
|
1191
|
-
console.log(theme.assistant('Чат открыт.') +
|
|
1214
|
+
console.log(theme.assistant('Чат открыт.') +
|
|
1215
|
+
theme.system(String.fromCharCode(10)));
|
|
1192
1216
|
}
|
|
1193
1217
|
catch (e) {
|
|
1194
1218
|
console.error(theme.error('Не удалось открыть чат:'), e.message);
|
|
@@ -1337,6 +1361,15 @@ async function main() {
|
|
|
1337
1361
|
sendSystemPrompt: sendSystemPromptNext,
|
|
1338
1362
|
queue: pendingQueue,
|
|
1339
1363
|
ui: editor || null,
|
|
1364
|
+
onChatReady: (chatId) => {
|
|
1365
|
+
// Сохраняем сессию сразу при начале диалога, не дожидаясь конца
|
|
1366
|
+
// задачи. Иначе при долгой/прерванной задаче чат не попадал в
|
|
1367
|
+
// ~/.zames/.sessions и терялся после перезапуска.
|
|
1368
|
+
if (chatId) {
|
|
1369
|
+
currentChatId = chatId;
|
|
1370
|
+
saveLastChat(chatId, currentWorkdir);
|
|
1371
|
+
}
|
|
1372
|
+
},
|
|
1340
1373
|
});
|
|
1341
1374
|
}
|
|
1342
1375
|
finally {
|
|
@@ -1362,4 +1395,3 @@ main().catch((e) => {
|
|
|
1362
1395
|
console.error(e.stack);
|
|
1363
1396
|
process.exit(1);
|
|
1364
1397
|
});
|
|
1365
|
-
//# sourceMappingURL=index.js.map
|
package/dist/input.js
CHANGED
package/dist/markdown.js
CHANGED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
// Извлечение СЫРОГО текста ответа модели из сетевых данных DeepSeek.
|
|
5
|
+
//
|
|
6
|
+
// Зачем: чтение ответа идёт из ОТРЕНДЕРЕННОГО DOM, а рендер DeepSeek
|
|
7
|
+
// искажает ответ — превращает доллар-формулы в LaTeX (символ доллара
|
|
8
|
+
// теряется), нормализует переводы строк, делает автолинки. Из-за этого
|
|
9
|
+
// tool-call с шаблонными строками и экранированными переводами строк в
|
|
10
|
+
// аргументах доходил до инструментов искажённым. Здесь мы достаём исходный
|
|
11
|
+
// текст из тела сетевого ответа.
|
|
12
|
+
//
|
|
13
|
+
// Format DeepSeek (SSE, chat.deepseek.com/api/v0/chat/completion):
|
|
14
|
+
// * startovy fragment otveta lezhit v data-chanke s uzlom v.response
|
|
15
|
+
// (v.response.fragments[] -> type RESPONSE -> content);
|
|
16
|
+
// * dalshe tekst dorashchivaetsya chankami vida
|
|
17
|
+
// {"p":"response/fragments/-1/content","o":"APPEND","v":"..."};
|
|
18
|
+
// u samogo pervogo APPEND est p i o, u posleduyushchikh — tolko v;
|
|
19
|
+
// * status generacii prikhodit v {"p":"response/status","o":"SET","v":"..."}
|
|
20
|
+
// i v {"p":"response","o":"BATCH","v":[{...quasi_status...}]}.
|
|
21
|
+
// Reasoning-chanki (thinking) z otvet NE popadayut.
|
|
22
|
+
const NL = String.fromCharCode(10);
|
|
23
|
+
function parseDataLines(body) {
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const line of body.split(NL)) {
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
if (!trimmed.startsWith('data:'))
|
|
28
|
+
continue;
|
|
29
|
+
const payload = trimmed.slice(5).trim();
|
|
30
|
+
if (!payload || payload === '[DONE]')
|
|
31
|
+
continue;
|
|
32
|
+
try {
|
|
33
|
+
out.push(JSON.parse(payload));
|
|
34
|
+
}
|
|
35
|
+
catch { }
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
// Собирает текст ответа из SSE-потока. Поддерживает два формата:
|
|
40
|
+
// * OpenAI-совместимый: choices[].delta.content (reasoning_content
|
|
41
|
+
// игнорируется — это размышления, а не ответ);
|
|
42
|
+
// * DeepSeek chat.deepseek.com: стартовый v.response.fragments[] и
|
|
43
|
+
// инкрементальные APPEND-чанки response/fragments/-1/content.
|
|
44
|
+
export function extractFromSse(body) {
|
|
45
|
+
let out = '';
|
|
46
|
+
for (const obj of parseDataLines(body)) {
|
|
47
|
+
if (obj == null || typeof obj !== 'object')
|
|
48
|
+
continue;
|
|
49
|
+
const o = obj;
|
|
50
|
+
// OpenAI-sovmestimyj format: choices[].delta.content.
|
|
51
|
+
if (Array.isArray(o.choices)) {
|
|
52
|
+
out += pickAnswerText(o);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// DeepSeek: startovy snapshot otveta v.response.fragments[].
|
|
56
|
+
const v = o.v;
|
|
57
|
+
const resp = v && typeof v === 'object'
|
|
58
|
+
? v.response
|
|
59
|
+
: undefined;
|
|
60
|
+
if (resp && Array.isArray(resp.fragments)) {
|
|
61
|
+
for (const fr of resp.fragments) {
|
|
62
|
+
if (fr && fr.type === 'RESPONSE' && typeof fr.content === 'string') {
|
|
63
|
+
out += fr.content;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Инкрементальный APPEND: p='response/fragments/-1/content' -> v=строка;
|
|
69
|
+
// последующие чанки идут с одним полем v.
|
|
70
|
+
if (o.p === 'response/fragments/-1/content' && typeof o.v === 'string') {
|
|
71
|
+
out += o.v;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (o.p === undefined && typeof o.v === 'string') {
|
|
75
|
+
out += o.v;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
export function extractFromJson(body) {
|
|
81
|
+
let obj;
|
|
82
|
+
try {
|
|
83
|
+
obj = JSON.parse(body);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
return pickAnswerText(obj);
|
|
89
|
+
}
|
|
90
|
+
// Достает финальный текст ответа из узла, НЕ смешивая его с reasoning.
|
|
91
|
+
// Порядок: choices/messages -> delta/message -> content; reasoning_content
|
|
92
|
+
// намеренно не берем — это размышления модели, а не ответ.
|
|
93
|
+
function pickAnswerText(node) {
|
|
94
|
+
if (node == null)
|
|
95
|
+
return '';
|
|
96
|
+
if (typeof node === 'string')
|
|
97
|
+
return node;
|
|
98
|
+
if (Array.isArray(node)) {
|
|
99
|
+
return node.map(pickAnswerText).join('');
|
|
100
|
+
}
|
|
101
|
+
if (typeof node !== 'object')
|
|
102
|
+
return '';
|
|
103
|
+
const obj = node;
|
|
104
|
+
for (const key of ['choices', 'messages']) {
|
|
105
|
+
const v = obj[key];
|
|
106
|
+
if (Array.isArray(v) && v.length) {
|
|
107
|
+
const inner = v.map(pickAnswerText).join('');
|
|
108
|
+
if (inner)
|
|
109
|
+
return inner;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const key of ['content', 'text', 'answer', 'response']) {
|
|
113
|
+
const v = obj[key];
|
|
114
|
+
if (typeof v === 'string' && v)
|
|
115
|
+
return v;
|
|
116
|
+
}
|
|
117
|
+
for (const key of ['delta', 'message', 'data', 'payload', 'v']) {
|
|
118
|
+
const v = obj[key];
|
|
119
|
+
if (v && typeof v === 'object') {
|
|
120
|
+
const inner = pickAnswerText(v);
|
|
121
|
+
if (inner)
|
|
122
|
+
return inner;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
// Универсальная попытка: сначала SSE, затем обычный JSON.
|
|
128
|
+
export function extractAnswer(body) {
|
|
129
|
+
const sse = extractFromSse(body);
|
|
130
|
+
if (sse)
|
|
131
|
+
return sse;
|
|
132
|
+
return extractFromJson(body);
|
|
133
|
+
}
|
|
134
|
+
// Сохраняет тело сетевого ответа DeepSeek на диск для разбора постфактум.
|
|
135
|
+
// Файлы лежат в ~/.zames/net-log — po nim видно реальный формат ответа.
|
|
136
|
+
export async function dumpNetBody(url, body) {
|
|
137
|
+
try {
|
|
138
|
+
const dir = path.join(os.homedir(), '.zames', 'net-log');
|
|
139
|
+
await fs.mkdir(dir, { recursive: true });
|
|
140
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
141
|
+
const safe = url.replace(/[^a-zA-Z0-9_.-]/g, '_').slice(0, 80);
|
|
142
|
+
await fs.writeFile(path.join(dir, stamp + '_' + safe + '.txt'), body, 'utf-8');
|
|
143
|
+
}
|
|
144
|
+
catch { }
|
|
145
|
+
}
|
package/dist/self-review.js
CHANGED
package/dist/sessions.js
CHANGED
package/dist/spinner.js
CHANGED
package/dist/system-prompt.js
CHANGED
|
@@ -78,6 +78,20 @@ Your response MUST be valid JSON. This means:
|
|
|
78
78
|
|
|
79
79
|
When in doubt — use forward slashes for all file paths.
|
|
80
80
|
|
|
81
|
+
## CRITICAL: preserving special characters in Write/Edit
|
|
82
|
+
|
|
83
|
+
The channel that delivers your answer may corrupt certain characters in tool
|
|
84
|
+
arguments: the dollar sign in template literals, backslashes, newlines, and
|
|
85
|
+
identifiers (e.g. 'x.name'). To write or edit files containing such characters
|
|
86
|
+
reliably, pass the content in base64 instead of plain text:
|
|
87
|
+
|
|
88
|
+
- Write: use 'content_base64' instead of 'content'.
|
|
89
|
+
- Edit: use 'old_base64' / 'new_base64' instead of 'old_string' / 'new_string'.
|
|
90
|
+
|
|
91
|
+
base64 alphabet is [A-Za-z0-9+/=] and is not corrupted. Encode with the
|
|
92
|
+
base64 command or in Node via Buffer.from(text).toString('base64').
|
|
93
|
+
Use plain content/old_string only when the text has no special characters.
|
|
94
|
+
|
|
81
95
|
## CRITICAL: quoting inside shell commands
|
|
82
96
|
|
|
83
97
|
When you call Bash, put the whole shell command in a single JSON string.
|
|
@@ -121,4 +135,3 @@ Anything you write outside that one respond is not read by the operator.
|
|
|
121
135
|
- Do NOT use WebFetch on localhost, private IPs, or 'file://' — it will fail.
|
|
122
136
|
`;
|
|
123
137
|
}
|
|
124
|
-
//# sourceMappingURL=system-prompt.js.map
|
package/dist/theme.js
CHANGED
package/dist/tools.js
CHANGED
|
@@ -74,6 +74,16 @@ export function createTools(workdir, { undo } = {}) {
|
|
|
74
74
|
resolve(parts.join('\n'));
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
|
+
// Достаёт текст из content/content_base64. base64 нужен, потому что канал
|
|
78
|
+
// передачи ответа модели может искажать символы ($, обратные слэши,
|
|
79
|
+
// переводы строк). base64 состоит только из [A-Za-z0-9+/=] и искажению
|
|
80
|
+
// не подвержен.
|
|
81
|
+
const decodeContent = (content, contentBase64) => {
|
|
82
|
+
if (typeof contentBase64 === 'string' && contentBase64.length) {
|
|
83
|
+
return Buffer.from(contentBase64, 'base64').toString('utf-8');
|
|
84
|
+
}
|
|
85
|
+
return String(content ?? '');
|
|
86
|
+
};
|
|
77
87
|
const baseTools = [
|
|
78
88
|
{
|
|
79
89
|
name: 'Read',
|
|
@@ -90,41 +100,54 @@ export function createTools(workdir, { undo } = {}) {
|
|
|
90
100
|
},
|
|
91
101
|
{
|
|
92
102
|
name: 'Write',
|
|
93
|
-
description: 'Создать или перезаписать файл.'
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
description: 'Создать или перезаписать файл. content — текст; content_base64 — тот же ' +
|
|
104
|
+
'контент в base64 (используй, если текст содержит , обратные слэши, ' +
|
|
105
|
+
'переводы строк или другие символы, которые могут исказиться).',
|
|
106
|
+
parameters: {
|
|
107
|
+
path: 'string',
|
|
108
|
+
content: 'string?',
|
|
109
|
+
content_base64: 'string?',
|
|
110
|
+
},
|
|
111
|
+
fn: async ({ path: p, content, content_base64 }) => {
|
|
96
112
|
const file = safe(String(p));
|
|
113
|
+
const text = decodeContent(content, content_base64);
|
|
97
114
|
if (undo)
|
|
98
115
|
await undo.backup(file);
|
|
99
116
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
100
|
-
await fs.writeFile(file,
|
|
101
|
-
return `Файл записан:
|
|
117
|
+
await fs.writeFile(file, text, 'utf-8');
|
|
118
|
+
return `Файл записан: {p}`;
|
|
102
119
|
},
|
|
103
120
|
},
|
|
104
121
|
{
|
|
105
122
|
name: 'Edit',
|
|
106
|
-
description: 'Точечная замена строки в файле. old_string должен встречаться один раз.'
|
|
123
|
+
description: 'Точечная замена строки в файле. old_string должен встречаться один раз. ' +
|
|
124
|
+
'old_base64/new_base64 — те же строки в base64 (если текст содержит ' +
|
|
125
|
+
'спецсимволы, которые могут исказиться).',
|
|
107
126
|
parameters: {
|
|
108
127
|
path: 'string',
|
|
109
|
-
old_string: 'string',
|
|
110
|
-
new_string: 'string',
|
|
128
|
+
old_string: 'string?',
|
|
129
|
+
new_string: 'string?',
|
|
130
|
+
old_base64: 'string?',
|
|
131
|
+
new_base64: 'string?',
|
|
111
132
|
},
|
|
112
|
-
fn: async ({ path: p, old_string, new_string }) => {
|
|
133
|
+
fn: async ({ path: p, old_string, new_string, old_base64, new_base64 }) => {
|
|
113
134
|
const file = safe(String(p));
|
|
114
|
-
|
|
135
|
+
const oldStr = decodeContent(old_string, old_base64);
|
|
136
|
+
const newStr = decodeContent(new_string, new_base64);
|
|
137
|
+
if (oldStr === '') {
|
|
115
138
|
throw new Error('old_string пустой — нечего заменять.');
|
|
116
139
|
}
|
|
117
140
|
let content = await fs.readFile(file, 'utf-8');
|
|
118
|
-
const occurrences = content.split(
|
|
141
|
+
const occurrences = content.split(oldStr).length - 1;
|
|
119
142
|
if (occurrences === 0) {
|
|
120
|
-
throw new Error(`Строка не найдена в ${p}: "${
|
|
143
|
+
throw new Error(`Строка не найдена в ${p}: "${oldStr.slice(0, 60)}..."`);
|
|
121
144
|
}
|
|
122
145
|
if (occurrences > 1) {
|
|
123
146
|
throw new Error(`Строка встречается ${occurrences} раз в ${p}. Уточните old_string.`);
|
|
124
147
|
}
|
|
125
148
|
if (undo)
|
|
126
149
|
await undo.backup(file);
|
|
127
|
-
content = content.replace(
|
|
150
|
+
content = content.replace(oldStr, newStr);
|
|
128
151
|
await fs.writeFile(file, content, 'utf-8');
|
|
129
152
|
return `Отредактирован: ${p}`;
|
|
130
153
|
},
|
|
@@ -186,4 +209,3 @@ export function createTools(workdir, { undo } = {}) {
|
|
|
186
209
|
};
|
|
187
210
|
return [...baseTools, ...gitTools, ...webTools, respondTool];
|
|
188
211
|
}
|
|
189
|
-
//# sourceMappingURL=tools.js.map
|
package/dist/transcript.js
CHANGED
package/dist/types.js
CHANGED
package/dist/undo.js
CHANGED
package/dist/web.js
CHANGED
package/dist/xml-toolcall.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Terminal coding agent over chat.deepseek.com via Playwright",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"start": "node ./dist/index.js",
|
|
17
17
|
"dev": "tsx ./src/index.ts --dev",
|
|
18
|
-
"build": "tsc",
|
|
18
|
+
"build": "tsc -p tsconfig.build.json",
|
|
19
19
|
"typecheck": "tsc --noEmit",
|
|
20
|
-
"prepublishOnly": "npm run build"
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"test": "tsx --test test/*.test.ts",
|
|
22
|
+
"test:watch": "tsx --test --watch test/*.test.ts",
|
|
23
|
+
"prepare": "husky"
|
|
21
24
|
},
|
|
22
25
|
"keywords": [
|
|
23
26
|
"agent",
|
|
@@ -51,6 +54,7 @@
|
|
|
51
54
|
},
|
|
52
55
|
"devDependencies": {
|
|
53
56
|
"@types/node": "^26.6.2",
|
|
57
|
+
"husky": "^9.1.7",
|
|
54
58
|
"tsx": "^4.23.15",
|
|
55
59
|
"typescript": "^7.0.2"
|
|
56
60
|
}
|