devez-vibe 1.7.8 → 1.7.10

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 CHANGED
Binary file
@@ -2854,6 +2854,86 @@ async function readableCwd(id, cwd) {
2854
2854
  return await transcriptCwd(id) || cwd;
2855
2855
  }
2856
2856
 
2857
+ const transcriptPaths = new Map();
2858
+
2859
+ /** Locate `id`'s transcript file under any project folder. */
2860
+ async function transcriptPath(id) {
2861
+ const cached = transcriptPaths.get(id);
2862
+ if (cached && existsSync(cached)) return cached;
2863
+ let entries;
2864
+ try {
2865
+ entries = await readdir(claudeProjectsDir(), { withFileTypes: true });
2866
+ } catch {
2867
+ return null;
2868
+ }
2869
+ for (const entry of entries) {
2870
+ if (!entry.isDirectory()) continue;
2871
+ const path = join(claudeProjectsDir(), entry.name, `${id}.jsonl`);
2872
+ if (existsSync(path)) {
2873
+ transcriptPaths.set(id, path);
2874
+ return path;
2875
+ }
2876
+ }
2877
+ return null;
2878
+ }
2879
+
2880
+ /**
2881
+ * The conversation a transcript records, in file order. The SDK's own reader
2882
+ * walks the parentUuid chain backwards from the tail, and a compaction boundary
2883
+ * has no parent, so everything said before the last compaction silently drops
2884
+ * out of a resumed session's history. Reading the file directly keeps the whole
2885
+ * conversation. A resume can re-append earlier messages under their original
2886
+ * uuids, so each uuid's first appearance wins.
2887
+ */
2888
+ function transcriptEntries(raw) {
2889
+ const messages = [];
2890
+ const seen = new Set();
2891
+ for (const line of raw.split("\n")) {
2892
+ let entry;
2893
+ try {
2894
+ entry = JSON.parse(line);
2895
+ } catch {
2896
+ continue;
2897
+ }
2898
+ if (!entry || typeof entry !== "object" || entry.isSidechain) continue;
2899
+ if (entry.type !== "user" && entry.type !== "assistant" && entry.type !== "system") continue;
2900
+ if (typeof entry.uuid === "string" && entry.uuid) {
2901
+ if (seen.has(entry.uuid)) continue;
2902
+ seen.add(entry.uuid);
2903
+ }
2904
+ messages.push(entry);
2905
+ }
2906
+ return messages;
2907
+ }
2908
+
2909
+ async function transcriptMessagesAt(path) {
2910
+ let raw;
2911
+ try {
2912
+ raw = await readFile(path, "utf8");
2913
+ } catch {
2914
+ return [];
2915
+ }
2916
+ return transcriptEntries(raw);
2917
+ }
2918
+
2919
+ /** Every message of `id`'s conversation: the transcript file when it exists,
2920
+ * the SDK's reader for sessions that left none behind. */
2921
+ async function sessionMessages(id, cwd) {
2922
+ const path = await transcriptPath(id);
2923
+ if (path) {
2924
+ const messages = await transcriptMessagesAt(path);
2925
+ if (messages.length) return messages;
2926
+ }
2927
+ try {
2928
+ return await getSessionMessages(id, {
2929
+ dir: await readableCwd(id, cwd),
2930
+ includeSystemMessages: true,
2931
+ });
2932
+ } catch {
2933
+ return [];
2934
+ }
2935
+ }
2936
+
2857
2937
  function sameCwd(left, right) {
2858
2938
  if (!left || !right) return !left && !right;
2859
2939
  const normalize = (value) => value.replaceAll("\\", "/").replace(/\/$/, "");
@@ -2902,7 +2982,7 @@ async function transcriptSessions(cwd) {
2902
2982
  const id = file.name.slice(0, -".jsonl".length);
2903
2983
  try {
2904
2984
  const [messages, metadata] = await Promise.all([
2905
- getSessionMessages(id, { dir: recordedCwd, includeSystemMessages: true }),
2985
+ transcriptMessagesAt(path),
2906
2986
  stat(path),
2907
2987
  ]);
2908
2988
  if (!messages.length) continue;
@@ -3054,7 +3134,7 @@ async function dispatch(method, params = {}) {
3054
3134
  const id = liveSessionId(params.sessionId);
3055
3135
  const existing = sessions.get(id);
3056
3136
  if (existing) {
3057
- const messages = await getSessionMessages(id, { dir: existing.cwd, includeSystemMessages: true });
3137
+ const messages = await sessionMessages(id, existing.cwd);
3058
3138
  if (!existing.tasks.size) existing.tasks = historyState(messages).tasks;
3059
3139
  return {
3060
3140
  id,
@@ -3072,7 +3152,7 @@ async function dispatch(method, params = {}) {
3072
3152
  // The transcript itself decides whether there is anything to resume:
3073
3153
  // getSessionInfo only sees sessions the CLI indexed, and a bridge-run session
3074
3154
  // whose transcript is intact can be missing from that index.
3075
- const messages = await getSessionMessages(id, { dir, includeSystemMessages: true });
3155
+ const messages = await sessionMessages(id, params.cwd);
3076
3156
  if (!messages.length) throw new Error(`Claude 세션을 찾을 수 없습니다: ${id}`);
3077
3157
  const info = await getSessionInfo(id, { dir });
3078
3158
  const lastModel = [...messages].reverse().find((message) => message.type === "assistant")?.message?.model;
@@ -3109,7 +3189,7 @@ async function dispatch(method, params = {}) {
3109
3189
  }
3110
3190
  if (method === "session/history") {
3111
3191
  const id = liveSessionId(params.sessionId);
3112
- const messages = await getSessionMessages(id, { dir: await readableCwd(id, params.cwd), includeSystemMessages: true });
3192
+ const messages = await sessionMessages(id, params.cwd);
3113
3193
  return { data: historyTurns(messages), nextCursor: null };
3114
3194
  }
3115
3195
  if (method === "session/prompt") return startPrompt(params);
@@ -3249,6 +3329,28 @@ async function runSelfTest() {
3249
3329
  if (!equivalentCwd || preview !== "세션 목록 질문") {
3250
3330
  throw new Error(`Claude session list self-test failed: ${JSON.stringify({ equivalentCwd, preview })}`);
3251
3331
  }
3332
+ // A compacted transcript keeps every turn on disk; the reader must return the
3333
+ // pre-compaction turns, skip resume-replayed duplicates and sidechains, and
3334
+ // leave the summary itself to the history filter.
3335
+ const compactedTranscript = [
3336
+ JSON.stringify({ type: "user", uuid: "before-compact", message: { role: "user", content: "compact 이전 질문" } }),
3337
+ JSON.stringify({ type: "assistant", uuid: "before-compact-answer", message: { role: "assistant", model: "claude-opus-5", content: [{ type: "text", text: "이전 답변." }] } }),
3338
+ JSON.stringify({ type: "attachment", uuid: "attachment-noise" }),
3339
+ JSON.stringify({ type: "system", subtype: "compact_boundary", uuid: "compact-1", parentUuid: null }),
3340
+ JSON.stringify({ type: "user", uuid: "compact-summary", isCompactSummary: true, message: { role: "user", content: "This session is being continued from a previous conversation." } }),
3341
+ JSON.stringify({ type: "user", uuid: "before-compact", message: { role: "user", content: "재개가 다시 적은 복사본" } }),
3342
+ JSON.stringify({ type: "user", uuid: "sidechain-user", isSidechain: true, message: { role: "user", content: "subagent" } }),
3343
+ "not json",
3344
+ JSON.stringify({ type: "user", uuid: "after-compact", message: { role: "user", content: "compact 이후 질문" } }),
3345
+ JSON.stringify({ type: "assistant", uuid: "after-compact-answer", message: { role: "assistant", model: "claude-opus-5", content: [{ type: "text", text: "이후 답변." }] } }),
3346
+ ].join("\n");
3347
+ const restored = transcriptEntries(compactedTranscript);
3348
+ const restoredPrompts = historyTurns(restored)
3349
+ .map((turn) => turn.items.find((item) => item.type === "userMessage")?.content?.[0]?.text);
3350
+ if (restored.length !== 6
3351
+ || JSON.stringify(restoredPrompts) !== JSON.stringify(["compact 이전 질문", "compact 이후 질문"])) {
3352
+ throw new Error(`Claude compacted transcript self-test failed: ${JSON.stringify({ restored: restored.length, restoredPrompts })}`);
3353
+ }
3252
3354
  const latestOptions = makeOptions({ cwd: process.cwd(), permissionMode: "plan" }, "00000000-0000-4000-8000-000000000000");
3253
3355
  if (latestOptions.permissionMode !== BOOTSTRAP_PERMISSION_MODE
3254
3356
  || latestOptions.perTaskStopAffordance !== true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.7.8",
3
+ "version": "1.7.10",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",