tsunami-code 3.11.7 → 3.11.8
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/index.js +45 -24
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from './lib/memory.js';
|
|
27
27
|
import { listMemories, readMemory, saveMemory, deleteMemory, getMemdirPath } from './lib/memdir.js';
|
|
28
28
|
|
|
29
|
-
const VERSION = '3.11.
|
|
29
|
+
const VERSION = '3.11.8';
|
|
30
30
|
const CONFIG_DIR = join(os.homedir(), '.tsunami-code');
|
|
31
31
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
32
32
|
const DEFAULT_SERVER = 'https://radiometric-reita-amuck.ngrok-free.dev';
|
|
@@ -129,6 +129,40 @@ function formatBytes(bytes) {
|
|
|
129
129
|
return `${(bytes / 1024 / 1024).toFixed(2)}MB`;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// ── Typewriter — drains a char queue at a fixed rate for smooth output ───────
|
|
133
|
+
// charDelay: ms between characters. onFirstChar fires once before first write.
|
|
134
|
+
function createTypewriter(writeFn, charDelay = 12) {
|
|
135
|
+
const queue = [];
|
|
136
|
+
let timer = null;
|
|
137
|
+
let onFirst = null;
|
|
138
|
+
let firstDone = false;
|
|
139
|
+
|
|
140
|
+
function tick() {
|
|
141
|
+
if (queue.length === 0) { timer = null; return; }
|
|
142
|
+
if (!firstDone && onFirst) { onFirst(); firstDone = true; }
|
|
143
|
+
writeFn(queue.shift());
|
|
144
|
+
timer = setTimeout(tick, charDelay);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
setOnFirst(fn) { onFirst = fn; },
|
|
149
|
+
push(text) {
|
|
150
|
+
for (const ch of text) queue.push(ch);
|
|
151
|
+
if (!timer) timer = setTimeout(tick, 0); // start immediately
|
|
152
|
+
},
|
|
153
|
+
drainNow() {
|
|
154
|
+
if (timer) { clearTimeout(timer); timer = null; }
|
|
155
|
+
if (!firstDone && queue.length && onFirst) { onFirst(); firstDone = true; }
|
|
156
|
+
while (queue.length) writeFn(queue.shift());
|
|
157
|
+
},
|
|
158
|
+
reset() {
|
|
159
|
+
if (timer) { clearTimeout(timer); timer = null; }
|
|
160
|
+
queue.length = 0;
|
|
161
|
+
firstDone = false;
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
132
166
|
// ── Syntax highlighter — line-buffered, applied to streaming model output ──────
|
|
133
167
|
// Buffers tokens until newlines, then applies chalk markup per line.
|
|
134
168
|
// createHighlighter(write) returns an onToken(token) function.
|
|
@@ -186,20 +220,6 @@ function createHighlighter(write) {
|
|
|
186
220
|
renderLine(buf.slice(0, nl));
|
|
187
221
|
buf = buf.slice(nl + 1);
|
|
188
222
|
}
|
|
189
|
-
// Flush partial line at word boundaries so text streams word-by-word
|
|
190
|
-
// (skip inside code fences where exact formatting matters)
|
|
191
|
-
if (!inFence && buf.length > 0) {
|
|
192
|
-
const lastSpace = buf.lastIndexOf(' ');
|
|
193
|
-
if (lastSpace > 0) {
|
|
194
|
-
const partial = buf.slice(0, lastSpace + 1);
|
|
195
|
-
const styled = partial
|
|
196
|
-
.replace(/\*\*([^*\n]+)\*\*/g, (_, t) => chalk.bold(t))
|
|
197
|
-
.replace(/__([^_\n]+)__/g, (_, t) => chalk.bold(t))
|
|
198
|
-
.replace(/`([^`\n]+)`/g, (_, t) => chalk.yellow('`' + t + '`'));
|
|
199
|
-
write(styled);
|
|
200
|
-
buf = buf.slice(lastSpace + 1);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
223
|
};
|
|
204
224
|
}
|
|
205
225
|
|
|
@@ -1290,16 +1310,13 @@ Output ONLY the TSUNAMI.md content, starting with "# Project: <name>"`;
|
|
|
1290
1310
|
let toolTimers = {}; // track per-tool duration
|
|
1291
1311
|
|
|
1292
1312
|
spinner.start();
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
process.stdout.write(' ');
|
|
1299
|
-
firstToken = false;
|
|
1300
|
-
}
|
|
1301
|
-
process.stdout.write(s);
|
|
1313
|
+
const tw = createTypewriter((ch) => process.stdout.write(ch), 12);
|
|
1314
|
+
tw.setOnFirst(() => {
|
|
1315
|
+
spinner.stop();
|
|
1316
|
+
process.stdout.write(' ');
|
|
1317
|
+
firstToken = false;
|
|
1302
1318
|
});
|
|
1319
|
+
const highlight = createHighlighter((s) => tw.push(s));
|
|
1303
1320
|
|
|
1304
1321
|
try {
|
|
1305
1322
|
await agentLoop(
|
|
@@ -1310,18 +1327,22 @@ Output ONLY the TSUNAMI.md content, starting with "# Project: <name>"`;
|
|
|
1310
1327
|
},
|
|
1311
1328
|
(toolName, toolArgs) => {
|
|
1312
1329
|
flushHighlighter(highlight);
|
|
1330
|
+
tw.drainNow();
|
|
1331
|
+
tw.reset();
|
|
1313
1332
|
spinner.stop();
|
|
1314
1333
|
const elapsed = toolTimers[toolName] != null ? Date.now() - toolTimers[toolName] : null;
|
|
1315
1334
|
toolTimers[toolName] = Date.now();
|
|
1316
1335
|
printToolCall(toolName, toolArgs, elapsed);
|
|
1317
1336
|
spinner.start();
|
|
1318
1337
|
firstToken = true;
|
|
1338
|
+
tw.setOnFirst(() => { spinner.stop(); process.stdout.write(' '); firstToken = false; });
|
|
1319
1339
|
},
|
|
1320
1340
|
{ sessionDir, cwd, planMode, permMode },
|
|
1321
1341
|
makeConfirmCallback(ui)
|
|
1322
1342
|
);
|
|
1323
1343
|
spinner.stop();
|
|
1324
1344
|
flushHighlighter(highlight);
|
|
1345
|
+
tw.drainNow();
|
|
1325
1346
|
|
|
1326
1347
|
const elapsed = ((Date.now() - turnStart) / 1000).toFixed(1);
|
|
1327
1348
|
const tok = tokenStats.output > 0 ? ` · ${tokenStats.output - (_outputTokens || 0)} tok` : '';
|