devez-vibe 1.3.28 → 1.3.30
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/bin/dvz.exe +0 -0
- package/bridge/claude-agent-sdk-bridge.mjs +17 -148
- package/package.json +1 -1
package/bin/dvz.exe
CHANGED
|
Binary file
|
|
@@ -1006,117 +1006,10 @@ function emitOpeningNotice(session) {
|
|
|
1006
1006
|
session.turn.openingNoticeEmitted = true;
|
|
1007
1007
|
}
|
|
1008
1008
|
|
|
1009
|
-
//
|
|
1010
|
-
//
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
const SMOOTH_TEXT_MAX_GRAPHEMES = 24;
|
|
1014
|
-
const SMOOTH_TEXT_MIN_RATE = 1;
|
|
1015
|
-
// Ease the per-frame rate toward the backlog's demand instead of adopting it at
|
|
1016
|
-
// once: a phrase-sized event would otherwise jump straight to a wide chunk and
|
|
1017
|
-
// read as a lurch, and the next tiny event would snap back to one grapheme.
|
|
1018
|
-
const SMOOTH_TEXT_RATE_EASING = 0.25;
|
|
1019
|
-
// Cap how far the eased rate may move in one frame so a sudden 400-grapheme
|
|
1020
|
-
// backlog ramps up over frames instead of widening the very first chunk.
|
|
1021
|
-
const SMOOTH_TEXT_RATE_STEP = 1;
|
|
1022
|
-
const graphemeSegmenter = typeof Intl.Segmenter === "function"
|
|
1023
|
-
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
1024
|
-
: null;
|
|
1025
|
-
|
|
1026
|
-
function splitGraphemes(text) {
|
|
1027
|
-
return graphemeSegmenter
|
|
1028
|
-
? Array.from(graphemeSegmenter.segment(text), ({ segment }) => segment)
|
|
1029
|
-
: Array.from(text);
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// Claude can deliver a whole phrase in one SDK event. Drain a steady share per
|
|
1033
|
-
// visual frame, where the rate eases toward the backlog instead of tracking it
|
|
1034
|
-
// event by event.
|
|
1035
|
-
function takeSmoothTextChunk(text, size) {
|
|
1036
|
-
const graphemes = splitGraphemes(text);
|
|
1037
|
-
return {
|
|
1038
|
-
chunk: graphemes.slice(0, size).join(""),
|
|
1039
|
-
rest: graphemes.slice(size).join(""),
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
class SmoothTextStream {
|
|
1044
|
-
constructor(emit, intervalMs = SMOOTH_TEXT_INTERVAL_MS) {
|
|
1045
|
-
this.emit = emit;
|
|
1046
|
-
this.intervalMs = intervalMs;
|
|
1047
|
-
this.pending = "";
|
|
1048
|
-
this.timer = null;
|
|
1049
|
-
this.waiters = [];
|
|
1050
|
-
this.rate = SMOOTH_TEXT_MIN_RATE;
|
|
1051
|
-
// Fractional rates only stay smooth if the leftover carries to the next
|
|
1052
|
-
// frame; truncating every frame would quantize the pace back to integers.
|
|
1053
|
-
this.carry = 0;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
nextChunkSize(pendingLength) {
|
|
1057
|
-
const demand = pendingLength / SMOOTH_TEXT_TARGET_FRAMES;
|
|
1058
|
-
const target = Math.min(
|
|
1059
|
-
SMOOTH_TEXT_MAX_GRAPHEMES,
|
|
1060
|
-
Math.max(SMOOTH_TEXT_MIN_RATE, demand),
|
|
1061
|
-
);
|
|
1062
|
-
const eased = (target - this.rate) * SMOOTH_TEXT_RATE_EASING;
|
|
1063
|
-
this.rate += Math.max(-SMOOTH_TEXT_RATE_STEP, Math.min(SMOOTH_TEXT_RATE_STEP, eased));
|
|
1064
|
-
const budget = this.rate + this.carry;
|
|
1065
|
-
const size = Math.max(SMOOTH_TEXT_MIN_RATE, Math.floor(budget));
|
|
1066
|
-
this.carry = Math.max(0, budget - size);
|
|
1067
|
-
return size;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
push(text) {
|
|
1071
|
-
this.pending += text;
|
|
1072
|
-
this.schedule();
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
schedule() {
|
|
1076
|
-
if (this.timer != null) return;
|
|
1077
|
-
// Wait one visual frame before the first drain. Claude often sends several
|
|
1078
|
-
// tiny deltas back-to-back; batching them removes the uneven one-character
|
|
1079
|
-
// jumps while keeping added latency below one frame.
|
|
1080
|
-
this.timer = setTimeout(() => {
|
|
1081
|
-
this.timer = null;
|
|
1082
|
-
this.drain();
|
|
1083
|
-
}, this.intervalMs);
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
drain() {
|
|
1087
|
-
if (!this.pending) {
|
|
1088
|
-
this.timer = null;
|
|
1089
|
-
for (const resolve of this.waiters.splice(0)) resolve();
|
|
1090
|
-
return;
|
|
1091
|
-
}
|
|
1092
|
-
const size = this.nextChunkSize(splitGraphemes(this.pending).length);
|
|
1093
|
-
const { chunk, rest } = takeSmoothTextChunk(this.pending, size);
|
|
1094
|
-
this.pending = rest;
|
|
1095
|
-
this.emit(chunk);
|
|
1096
|
-
if (this.pending) {
|
|
1097
|
-
this.schedule();
|
|
1098
|
-
} else {
|
|
1099
|
-
for (const resolve of this.waiters.splice(0)) resolve();
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
finish() {
|
|
1104
|
-
if (!this.pending && this.timer == null) return Promise.resolve();
|
|
1105
|
-
return new Promise((resolve) => this.waiters.push(resolve));
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
flush() {
|
|
1109
|
-
if (this.timer != null) clearTimeout(this.timer);
|
|
1110
|
-
this.timer = null;
|
|
1111
|
-
if (this.pending) this.emit(this.pending);
|
|
1112
|
-
this.pending = "";
|
|
1113
|
-
for (const resolve of this.waiters.splice(0)) resolve();
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
function flushSmoothStreams(session) {
|
|
1118
|
-
for (const block of session.streamBlocks.values()) block.smooth?.flush();
|
|
1119
|
-
}
|
|
1009
|
+
// Text pacing lives in the host renderer, not here. A timer in this process
|
|
1010
|
+
// runs on Node's scheduler, which cannot line up with the terminal's redraw
|
|
1011
|
+
// rhythm — the mismatch was itself the visible stutter. Forward each delta as
|
|
1012
|
+
// it arrives and let the renderer reveal it on its own frames.
|
|
1120
1013
|
|
|
1121
1014
|
function tokenBreakdown(usage) {
|
|
1122
1015
|
if (!usage) return null;
|
|
@@ -1174,7 +1067,6 @@ async function processStreamEvent(session, message) {
|
|
|
1174
1067
|
if (!session.turn || message.parent_tool_use_id) return;
|
|
1175
1068
|
const event = message.event || {};
|
|
1176
1069
|
if (event.type === "message_start") {
|
|
1177
|
-
flushSmoothStreams(session);
|
|
1178
1070
|
session.streamBlocks.clear();
|
|
1179
1071
|
}
|
|
1180
1072
|
if (event.type === "content_block_start") {
|
|
@@ -1184,12 +1076,10 @@ async function processStreamEvent(session, message) {
|
|
|
1184
1076
|
const item = block.type === "text"
|
|
1185
1077
|
? { id, type: "agentMessage", text: "", provider: "Claude" }
|
|
1186
1078
|
: { id, type: "reasoning", summary: [] };
|
|
1187
|
-
// Thinking arrives in the same phrase-sized events as text, so it needs the
|
|
1188
|
-
// same paced drain — raw thinking deltas were the last visible stutter.
|
|
1189
1079
|
const method = block.type === "text"
|
|
1190
1080
|
? "item/agentMessage/delta"
|
|
1191
1081
|
: "item/reasoning/summaryTextDelta";
|
|
1192
|
-
const
|
|
1082
|
+
const emit = (delta) => emitDelta(session, method, id, delta);
|
|
1193
1083
|
// A held English line can end up dropped entirely, and an item announced
|
|
1194
1084
|
// before that decision would stay on screen as an empty bubble. Hold the
|
|
1195
1085
|
// start too, and emit it with the first text that survives.
|
|
@@ -1198,7 +1088,7 @@ async function processStreamEvent(session, message) {
|
|
|
1198
1088
|
id,
|
|
1199
1089
|
type: block.type,
|
|
1200
1090
|
text: "",
|
|
1201
|
-
|
|
1091
|
+
emit,
|
|
1202
1092
|
languagePending: held ? "" : null,
|
|
1203
1093
|
holdEnglishProgress: false,
|
|
1204
1094
|
pendingStart: held ? item : null,
|
|
@@ -1226,11 +1116,11 @@ async function processStreamEvent(session, message) {
|
|
|
1226
1116
|
}
|
|
1227
1117
|
emitHeldStart(session, current);
|
|
1228
1118
|
session.turn.sawVisibleText = true;
|
|
1229
|
-
current.
|
|
1119
|
+
current.emit(current.languagePending);
|
|
1230
1120
|
current.languagePending = null;
|
|
1231
1121
|
return;
|
|
1232
1122
|
}
|
|
1233
|
-
current.
|
|
1123
|
+
current.emit(delta);
|
|
1234
1124
|
return;
|
|
1235
1125
|
}
|
|
1236
1126
|
if (event.type === "content_block_stop") {
|
|
@@ -1246,10 +1136,9 @@ async function processStreamEvent(session, message) {
|
|
|
1246
1136
|
}
|
|
1247
1137
|
emitHeldStart(session, current);
|
|
1248
1138
|
if (visible.trim()) session.turn.sawVisibleText = true;
|
|
1249
|
-
current.
|
|
1139
|
+
current.emit(visible);
|
|
1250
1140
|
}
|
|
1251
1141
|
emitHeldStart(session, current);
|
|
1252
|
-
await current.smooth?.finish();
|
|
1253
1142
|
const item = current.type === "text"
|
|
1254
1143
|
? { id: current.id, type: "agentMessage", text: current.text, provider: "Claude" }
|
|
1255
1144
|
: { id: current.id, type: "reasoning", summary: [current.text] };
|
|
@@ -1935,7 +1824,6 @@ async function runPendingPrompt(session) {
|
|
|
1935
1824
|
function finishTurn(session, error, durationMs) {
|
|
1936
1825
|
if (!session.turn) return;
|
|
1937
1826
|
flushPendingPlan(session);
|
|
1938
|
-
flushSmoothStreams(session);
|
|
1939
1827
|
clearForegroundSubagents(session);
|
|
1940
1828
|
const turn = { id: session.turn.id, status: error ? "failed" : "completed" };
|
|
1941
1829
|
if (error) turn.error = { message: error instanceof Error ? error.message : error.message || String(error) };
|
|
@@ -2397,6 +2285,14 @@ async function dispatch(method, params = {}) {
|
|
|
2397
2285
|
...(rejection ? { rejection } : {}),
|
|
2398
2286
|
};
|
|
2399
2287
|
}
|
|
2288
|
+
// The host clears its `Working` row on `turn/completed` alone, so a single
|
|
2289
|
+
// dropped notification would strand the spinner forever. This answers "is the
|
|
2290
|
+
// turn you are still waiting on actually running?" from the bridge's own state.
|
|
2291
|
+
if (method === "session/turnStatus") {
|
|
2292
|
+
const session = lookupSession(params.sessionId);
|
|
2293
|
+
if (!session) return { known: false, running: false, turnId: null };
|
|
2294
|
+
return { known: true, running: Boolean(session.turn), turnId: session.turn?.id ?? null };
|
|
2295
|
+
}
|
|
2400
2296
|
if (method === "session/start") {
|
|
2401
2297
|
const { session, account, usage } = await createSession(params);
|
|
2402
2298
|
return {
|
|
@@ -3042,33 +2938,6 @@ async function runSelfTest() {
|
|
|
3042
2938
|
if (keptStarted !== 0 || keptCompleted?.params?.item?.text !== "타일 보기 로직을 고쳤습니다.") {
|
|
3043
2939
|
throw new Error(`Claude held Korean text self-test failed: ${JSON.stringify(keptEvents)}`);
|
|
3044
2940
|
}
|
|
3045
|
-
const smoothText = "Claude가 👨👩👧👦 한 문장을 한꺼번에 보내도 부드럽게 표시합니다.";
|
|
3046
|
-
const emitted = [];
|
|
3047
|
-
const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
|
|
3048
|
-
smooth.push(smoothText);
|
|
3049
|
-
if (emitted.length !== 0) {
|
|
3050
|
-
throw new Error(`Claude smooth stream did not batch its first frame: ${JSON.stringify(emitted)}`);
|
|
3051
|
-
}
|
|
3052
|
-
await smooth.finish();
|
|
3053
|
-
if (emitted.length < 2 || emitted.join("") !== smoothText) {
|
|
3054
|
-
throw new Error(`Claude smooth stream self-test failed: ${JSON.stringify(emitted)}`);
|
|
3055
|
-
}
|
|
3056
|
-
const ramped = [];
|
|
3057
|
-
const rampStream = new SmoothTextStream((chunk) => ramped.push(chunk), 0);
|
|
3058
|
-
rampStream.push("가".repeat(400));
|
|
3059
|
-
await rampStream.finish();
|
|
3060
|
-
const rampSizes = ramped.map((chunk) => splitGraphemes(chunk).length);
|
|
3061
|
-
if (rampSizes[0] > 2 || rampSizes.some((size, index) => index > 0 && size > rampSizes[index - 1] + 2)) {
|
|
3062
|
-
throw new Error(`Claude smooth stream rate jumped: ${JSON.stringify(rampSizes.slice(0, 8))}`);
|
|
3063
|
-
}
|
|
3064
|
-
const flushed = [];
|
|
3065
|
-
const interrupted = new SmoothTextStream((chunk) => flushed.push(chunk), 1000);
|
|
3066
|
-
interrupted.push(smoothText);
|
|
3067
|
-
interrupted.flush();
|
|
3068
|
-
await interrupted.finish();
|
|
3069
|
-
if (flushed.join("") !== smoothText) {
|
|
3070
|
-
throw new Error(`Claude smooth stream flush self-test failed: ${JSON.stringify(flushed)}`);
|
|
3071
|
-
}
|
|
3072
2941
|
process.stdout.write("Claude bridge self-test passed\n");
|
|
3073
2942
|
}
|
|
3074
2943
|
|