devez-vibe 1.3.27 → 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 +36 -136
- package/package.json +1 -1
package/bin/dvz.exe
CHANGED
|
Binary file
|
|
@@ -1006,93 +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 graphemeSegmenter = typeof Intl.Segmenter === "function"
|
|
1015
|
-
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
1016
|
-
: null;
|
|
1017
|
-
|
|
1018
|
-
function splitGraphemes(text) {
|
|
1019
|
-
return graphemeSegmenter
|
|
1020
|
-
? Array.from(graphemeSegmenter.segment(text), ({ segment }) => segment)
|
|
1021
|
-
: Array.from(text);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
// Claude can deliver a whole phrase in one SDK event. Drain roughly one visual
|
|
1025
|
-
// frame's share at a time, catching up quickly when a large backlog arrives.
|
|
1026
|
-
function takeSmoothTextChunk(text) {
|
|
1027
|
-
const graphemes = splitGraphemes(text);
|
|
1028
|
-
const size = Math.min(
|
|
1029
|
-
SMOOTH_TEXT_MAX_GRAPHEMES,
|
|
1030
|
-
Math.max(1, Math.ceil(graphemes.length / SMOOTH_TEXT_TARGET_FRAMES)),
|
|
1031
|
-
);
|
|
1032
|
-
return {
|
|
1033
|
-
chunk: graphemes.slice(0, size).join(""),
|
|
1034
|
-
rest: graphemes.slice(size).join(""),
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
class SmoothTextStream {
|
|
1039
|
-
constructor(emit, intervalMs = SMOOTH_TEXT_INTERVAL_MS) {
|
|
1040
|
-
this.emit = emit;
|
|
1041
|
-
this.intervalMs = intervalMs;
|
|
1042
|
-
this.pending = "";
|
|
1043
|
-
this.timer = null;
|
|
1044
|
-
this.waiters = [];
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
push(text) {
|
|
1048
|
-
this.pending += text;
|
|
1049
|
-
this.schedule();
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
schedule() {
|
|
1053
|
-
if (this.timer != null) return;
|
|
1054
|
-
// Wait one visual frame before the first drain. Claude often sends several
|
|
1055
|
-
// tiny deltas back-to-back; batching them removes the uneven one-character
|
|
1056
|
-
// jumps while keeping added latency below one frame.
|
|
1057
|
-
this.timer = setTimeout(() => {
|
|
1058
|
-
this.timer = null;
|
|
1059
|
-
this.drain();
|
|
1060
|
-
}, this.intervalMs);
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
drain() {
|
|
1064
|
-
if (!this.pending) {
|
|
1065
|
-
this.timer = null;
|
|
1066
|
-
for (const resolve of this.waiters.splice(0)) resolve();
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
const { chunk, rest } = takeSmoothTextChunk(this.pending);
|
|
1070
|
-
this.pending = rest;
|
|
1071
|
-
this.emit(chunk);
|
|
1072
|
-
if (this.pending) {
|
|
1073
|
-
this.schedule();
|
|
1074
|
-
} else {
|
|
1075
|
-
for (const resolve of this.waiters.splice(0)) resolve();
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
finish() {
|
|
1080
|
-
if (!this.pending && this.timer == null) return Promise.resolve();
|
|
1081
|
-
return new Promise((resolve) => this.waiters.push(resolve));
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
flush() {
|
|
1085
|
-
if (this.timer != null) clearTimeout(this.timer);
|
|
1086
|
-
this.timer = null;
|
|
1087
|
-
if (this.pending) this.emit(this.pending);
|
|
1088
|
-
this.pending = "";
|
|
1089
|
-
for (const resolve of this.waiters.splice(0)) resolve();
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
function flushSmoothStreams(session) {
|
|
1094
|
-
for (const block of session.streamBlocks.values()) block.smooth?.flush();
|
|
1095
|
-
}
|
|
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.
|
|
1096
1013
|
|
|
1097
1014
|
function tokenBreakdown(usage) {
|
|
1098
1015
|
if (!usage) return null;
|
|
@@ -1150,7 +1067,6 @@ async function processStreamEvent(session, message) {
|
|
|
1150
1067
|
if (!session.turn || message.parent_tool_use_id) return;
|
|
1151
1068
|
const event = message.event || {};
|
|
1152
1069
|
if (event.type === "message_start") {
|
|
1153
|
-
flushSmoothStreams(session);
|
|
1154
1070
|
session.streamBlocks.clear();
|
|
1155
1071
|
}
|
|
1156
1072
|
if (event.type === "content_block_start") {
|
|
@@ -1160,9 +1076,10 @@ async function processStreamEvent(session, message) {
|
|
|
1160
1076
|
const item = block.type === "text"
|
|
1161
1077
|
? { id, type: "agentMessage", text: "", provider: "Claude" }
|
|
1162
1078
|
: { id, type: "reasoning", summary: [] };
|
|
1163
|
-
const
|
|
1164
|
-
?
|
|
1165
|
-
:
|
|
1079
|
+
const method = block.type === "text"
|
|
1080
|
+
? "item/agentMessage/delta"
|
|
1081
|
+
: "item/reasoning/summaryTextDelta";
|
|
1082
|
+
const emit = (delta) => emitDelta(session, method, id, delta);
|
|
1166
1083
|
// A held English line can end up dropped entirely, and an item announced
|
|
1167
1084
|
// before that decision would stay on screen as an empty bubble. Hold the
|
|
1168
1085
|
// start too, and emit it with the first text that survives.
|
|
@@ -1171,7 +1088,7 @@ async function processStreamEvent(session, message) {
|
|
|
1171
1088
|
id,
|
|
1172
1089
|
type: block.type,
|
|
1173
1090
|
text: "",
|
|
1174
|
-
|
|
1091
|
+
emit,
|
|
1175
1092
|
languagePending: held ? "" : null,
|
|
1176
1093
|
holdEnglishProgress: false,
|
|
1177
1094
|
pendingStart: held ? item : null,
|
|
@@ -1188,10 +1105,6 @@ async function processStreamEvent(session, message) {
|
|
|
1188
1105
|
// Held text may still be dropped, and counting it as visible would suppress
|
|
1189
1106
|
// the opening notice in its place — leaving the turn with nothing to show.
|
|
1190
1107
|
if (current.type === "text" && current.languagePending == null) session.turn.sawVisibleText = true;
|
|
1191
|
-
if (!current.smooth) {
|
|
1192
|
-
emitDelta(session, "item/reasoning/summaryTextDelta", current.id, delta);
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
1108
|
if (current.languagePending != null) {
|
|
1196
1109
|
current.languagePending += delta;
|
|
1197
1110
|
const probe = current.languagePending.trimStart();
|
|
@@ -1203,11 +1116,11 @@ async function processStreamEvent(session, message) {
|
|
|
1203
1116
|
}
|
|
1204
1117
|
emitHeldStart(session, current);
|
|
1205
1118
|
session.turn.sawVisibleText = true;
|
|
1206
|
-
current.
|
|
1119
|
+
current.emit(current.languagePending);
|
|
1207
1120
|
current.languagePending = null;
|
|
1208
1121
|
return;
|
|
1209
1122
|
}
|
|
1210
|
-
current.
|
|
1123
|
+
current.emit(delta);
|
|
1211
1124
|
return;
|
|
1212
1125
|
}
|
|
1213
1126
|
if (event.type === "content_block_stop") {
|
|
@@ -1223,10 +1136,9 @@ async function processStreamEvent(session, message) {
|
|
|
1223
1136
|
}
|
|
1224
1137
|
emitHeldStart(session, current);
|
|
1225
1138
|
if (visible.trim()) session.turn.sawVisibleText = true;
|
|
1226
|
-
current.
|
|
1139
|
+
current.emit(visible);
|
|
1227
1140
|
}
|
|
1228
1141
|
emitHeldStart(session, current);
|
|
1229
|
-
await current.smooth?.finish();
|
|
1230
1142
|
const item = current.type === "text"
|
|
1231
1143
|
? { id: current.id, type: "agentMessage", text: current.text, provider: "Claude" }
|
|
1232
1144
|
: { id: current.id, type: "reasoning", summary: [current.text] };
|
|
@@ -1912,7 +1824,6 @@ async function runPendingPrompt(session) {
|
|
|
1912
1824
|
function finishTurn(session, error, durationMs) {
|
|
1913
1825
|
if (!session.turn) return;
|
|
1914
1826
|
flushPendingPlan(session);
|
|
1915
|
-
flushSmoothStreams(session);
|
|
1916
1827
|
clearForegroundSubagents(session);
|
|
1917
1828
|
const turn = { id: session.turn.id, status: error ? "failed" : "completed" };
|
|
1918
1829
|
if (error) turn.error = { message: error instanceof Error ? error.message : error.message || String(error) };
|
|
@@ -2122,22 +2033,22 @@ function historyState(messages) {
|
|
|
2122
2033
|
const userText = message.type === "user"
|
|
2123
2034
|
? stripInternalTags(stripHandoff(blocks.filter((block) => block.type === "text").map((block) => block.text || "").join("\n")))
|
|
2124
2035
|
: "";
|
|
2125
|
-
if (userText
|
|
2126
|
-
&& !blocks.some((block) => block.type === "tool_result")
|
|
2127
|
-
&& !isInternalHistoryText(message, userText)) {
|
|
2128
|
-
turn = {
|
|
2129
|
-
id: `claude-turn-${message.uuid}`,
|
|
2130
|
-
status: "completed",
|
|
2131
|
-
items: [{ id: `claude-user-${message.uuid}`, type: "userMessage", content: [{ type: "text", text: userText }] }],
|
|
2132
|
-
};
|
|
2133
|
-
const startedAt = messageTime(message);
|
|
2134
|
-
if (startedAt != null) turn.startedAt = Math.floor(startedAt / 1000);
|
|
2135
|
-
turns.push(turn);
|
|
2136
|
-
}
|
|
2137
|
-
if (!turn) continue;
|
|
2138
|
-
const completedAt = messageTime(message);
|
|
2139
|
-
if (completedAt != null) turn.completedAt = Math.floor(completedAt / 1000);
|
|
2140
|
-
if (message.type === "assistant") {
|
|
2036
|
+
if (userText
|
|
2037
|
+
&& !blocks.some((block) => block.type === "tool_result")
|
|
2038
|
+
&& !isInternalHistoryText(message, userText)) {
|
|
2039
|
+
turn = {
|
|
2040
|
+
id: `claude-turn-${message.uuid}`,
|
|
2041
|
+
status: "completed",
|
|
2042
|
+
items: [{ id: `claude-user-${message.uuid}`, type: "userMessage", content: [{ type: "text", text: userText }] }],
|
|
2043
|
+
};
|
|
2044
|
+
const startedAt = messageTime(message);
|
|
2045
|
+
if (startedAt != null) turn.startedAt = Math.floor(startedAt / 1000);
|
|
2046
|
+
turns.push(turn);
|
|
2047
|
+
}
|
|
2048
|
+
if (!turn) continue;
|
|
2049
|
+
const completedAt = messageTime(message);
|
|
2050
|
+
if (completedAt != null) turn.completedAt = Math.floor(completedAt / 1000);
|
|
2051
|
+
if (message.type === "assistant") {
|
|
2141
2052
|
if (message.message?.model === "<synthetic>") {
|
|
2142
2053
|
turn.synthetic = true;
|
|
2143
2054
|
continue;
|
|
@@ -2374,6 +2285,14 @@ async function dispatch(method, params = {}) {
|
|
|
2374
2285
|
...(rejection ? { rejection } : {}),
|
|
2375
2286
|
};
|
|
2376
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
|
+
}
|
|
2377
2296
|
if (method === "session/start") {
|
|
2378
2297
|
const { session, account, usage } = await createSession(params);
|
|
2379
2298
|
return {
|
|
@@ -3019,25 +2938,6 @@ async function runSelfTest() {
|
|
|
3019
2938
|
if (keptStarted !== 0 || keptCompleted?.params?.item?.text !== "타일 보기 로직을 고쳤습니다.") {
|
|
3020
2939
|
throw new Error(`Claude held Korean text self-test failed: ${JSON.stringify(keptEvents)}`);
|
|
3021
2940
|
}
|
|
3022
|
-
const smoothText = "Claude가 👨👩👧👦 한 문장을 한꺼번에 보내도 부드럽게 표시합니다.";
|
|
3023
|
-
const emitted = [];
|
|
3024
|
-
const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
|
|
3025
|
-
smooth.push(smoothText);
|
|
3026
|
-
if (emitted.length !== 0) {
|
|
3027
|
-
throw new Error(`Claude smooth stream did not batch its first frame: ${JSON.stringify(emitted)}`);
|
|
3028
|
-
}
|
|
3029
|
-
await smooth.finish();
|
|
3030
|
-
if (emitted.length < 2 || emitted.join("") !== smoothText) {
|
|
3031
|
-
throw new Error(`Claude smooth stream self-test failed: ${JSON.stringify(emitted)}`);
|
|
3032
|
-
}
|
|
3033
|
-
const flushed = [];
|
|
3034
|
-
const interrupted = new SmoothTextStream((chunk) => flushed.push(chunk), 1000);
|
|
3035
|
-
interrupted.push(smoothText);
|
|
3036
|
-
interrupted.flush();
|
|
3037
|
-
await interrupted.finish();
|
|
3038
|
-
if (flushed.join("") !== smoothText) {
|
|
3039
|
-
throw new Error(`Claude smooth stream flush self-test failed: ${JSON.stringify(flushed)}`);
|
|
3040
|
-
}
|
|
3041
2941
|
process.stdout.write("Claude bridge self-test passed\n");
|
|
3042
2942
|
}
|
|
3043
2943
|
|