devez-vibe 1.10.14 → 1.10.15

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
@@ -4910,10 +4910,16 @@ async function dispatch(method, params = {}) {
4910
4910
  // re-stamps session metadata, so a rename from here survives a live session.
4911
4911
  if (method === "session/rename") {
4912
4912
  const id = liveSessionId(params.sessionId);
4913
+ const session = sessions.get(id);
4914
+ if (!params.unnamedOnly && session) delete session.inheritedTitle;
4913
4915
  const dir = await readableCwd(id, params.cwd);
4914
- // An automatic title never replaces a name given in the meantime.
4915
- if (params.unnamedOnly && (await getSessionInfo(id, { dir }))?.customTitle) return {};
4916
+ // /clear가 복사한 제목만 교체한다. 그 뒤 직접 지정한 이름은 그대로 둔다.
4917
+ if (params.unnamedOnly) {
4918
+ const title = (await getSessionInfo(id, { dir }))?.customTitle;
4919
+ if (title && title !== session?.inheritedTitle) return {};
4920
+ }
4916
4921
  await renameSession(id, String(params.name ?? ""), { dir });
4922
+ if (session) delete session.inheritedTitle;
4917
4923
  return {};
4918
4924
  }
4919
4925
  // dvz titles a new session with one Haiku reply.
@@ -4968,6 +4974,8 @@ async function dispatch(method, params = {}) {
4968
4974
  || session.pendingPrompts?.length || session.queue?.values?.length) {
4969
4975
  return { released: false, busy: true };
4970
4976
  }
4977
+ const inheritedTitle = getSessionInfo(session.id, { dir: session.cwd })
4978
+ .then((info) => info?.customTitle).catch(() => undefined);
4971
4979
  let timer;
4972
4980
  const id = await new Promise((resolve) => {
4973
4981
  session.clearWaiter = resolve;
@@ -4980,6 +4988,7 @@ async function dispatch(method, params = {}) {
4980
4988
  });
4981
4989
  });
4982
4990
  clearTimeout(timer);
4991
+ if (id) session.inheritedTitle = await inheritedTitle;
4983
4992
  session.clearWaiter = null;
4984
4993
  session.clearPending = false;
4985
4994
  // CLI가 /clear에 답하지 않아도 남은 작업이 없으면 연결을 닫고 새 대화를 연다.
@@ -5526,29 +5535,63 @@ async function runSelfTest() {
5526
5535
  await resumedConsumer;
5527
5536
  // 백그라운드 작업만 남은 세션은 같은 프로세스에서 새 대화로 넘어가 작업을 이어받는다.
5528
5537
  const { session: clearSession } = await createSession({ cwd: process.cwd() });
5529
- const clearPreviousId = clearSession.id;
5530
5538
  const clearConsumer = clearSession.consumer;
5539
+ const titleProjectDir = join(releaseConfigDir, "projects", process.cwd().replace(/[^a-zA-Z0-9]/g, "-"));
5540
+ await mkdir(titleProjectDir, { recursive: true });
5541
+ const titleTranscript = (id, title, prompt) => [
5542
+ { type: "custom-title", sessionId: id, customTitle: title },
5543
+ { type: "user", uuid: randomUUID(), parentUuid: null, sessionId: id, cwd: process.cwd(),
5544
+ message: { role: "user", content: prompt } },
5545
+ ].map((entry) => JSON.stringify(entry)).join("\n") + "\n";
5546
+ await writeFile(join(titleProjectDir, `${clearSession.id}.jsonl`),
5547
+ titleTranscript(clearSession.id, "앞 대화 제목", "앞 대화 질문"));
5531
5548
  clearSession.subagents.set("clear-child", { id: "clear-child", background: true, taskId: "clear-task" });
5532
- const clearing = dispatch("session/clear", { sessionId: clearPreviousId });
5533
- await new Promise((resolve) => setImmediate(resolve));
5534
- if (clearSession.queue.values.at(-1)?.message?.content !== "/clear") {
5535
- throw new Error("Claude clear did not send /clear to the live query");
5536
- }
5537
- clearSession.queue.values.length = 0;
5538
- clearSession.query.events.push({ type: "conversation_reset", session_id: clearPreviousId, new_conversation_id: randomUUID() });
5539
- clearSession.query.events.push({ type: "system", subtype: "init", session_id: "cleared-session" });
5540
- const cleared = await clearing;
5541
- const clearRows = releaseNotices.filter((notice) => notice.method === "turn/subagents/updated");
5542
- if (cleared.id !== "cleared-session" || lookupSession("cleared-session") !== clearSession
5543
- || lookupSession(clearPreviousId) || clearSession.query.closeCalls
5544
- || !clearRows.some((notice) => notice.params.threadId === "cleared-session"
5545
- && notice.params.subagents.some((row) => row.id === "clear-child"))) {
5546
- throw new Error("Claude in-process clear did not move the live work to a new conversation");
5547
- }
5548
- releaseNotices.splice(0, releaseNotices.length,
5549
- ...releaseNotices.filter((notice) => !clearRows.includes(notice)));
5549
+ for (const mode of ["automatic", "manual-same", "external", "automatic-again"]) {
5550
+ const previousId = clearSession.id;
5551
+ const previousPath = join(titleProjectDir, `${previousId}.jsonl`);
5552
+ const previousTranscript = await readFile(previousPath, "utf8");
5553
+ const previousTitle = (await getSessionInfo(previousId, { dir: process.cwd() }))?.customTitle;
5554
+ const nextId = randomUUID();
5555
+ const nextPrompt = `새 질문 ${mode}`;
5556
+ // CLI의 /clear가 새 전사에 이전 customTitle을 복사하는 조건을 재현한다.
5557
+ await writeFile(join(titleProjectDir, `${nextId}.jsonl`), titleTranscript(nextId, previousTitle, nextPrompt));
5558
+ const clearing = dispatch("session/clear", { sessionId: previousId });
5559
+ await new Promise((resolve) => setImmediate(resolve));
5560
+ if (clearSession.queue.values.at(-1)?.message?.content !== "/clear") {
5561
+ throw new Error("Claude clear did not send /clear to the live query");
5562
+ }
5563
+ clearSession.queue.values.length = 0;
5564
+ clearSession.query.events.push({ type: "conversation_reset", session_id: previousId, new_conversation_id: randomUUID() });
5565
+ clearSession.query.events.push({ type: "system", subtype: "init", session_id: nextId });
5566
+ const cleared = await clearing;
5567
+ const clearRows = releaseNotices.filter((notice) => notice.method === "turn/subagents/updated");
5568
+ if (cleared.id !== nextId || lookupSession(nextId) !== clearSession
5569
+ || lookupSession(previousId) || clearSession.query.closeCalls
5570
+ || !clearRows.some((notice) => notice.params.threadId === nextId
5571
+ && notice.params.subagents.some((row) => row.id === "clear-child"))) {
5572
+ throw new Error("Claude in-process clear did not move the live work to a new conversation");
5573
+ }
5574
+ const autoTitle = `새 대화 제목 ${mode}`;
5575
+ let expectedTitle = autoTitle;
5576
+ if (mode === "manual-same") {
5577
+ expectedTitle = previousTitle;
5578
+ await dispatch("session/rename", { sessionId: nextId, cwd: process.cwd(), name: expectedTitle });
5579
+ } else if (mode === "external") {
5580
+ expectedTitle = "다른 클라이언트가 지정한 제목";
5581
+ await renameSession(nextId, expectedTitle, { dir: process.cwd() });
5582
+ }
5583
+ await dispatch("session/rename", { sessionId: nextId, cwd: process.cwd(), name: autoTitle, unnamedOnly: true });
5584
+ const listed = await claudeSessionList({ cwd: process.cwd() });
5585
+ const prompt = transcriptPreview(await sessionMessages(nextId, process.cwd()));
5586
+ if (listed.find((row) => row.id === visibleSession(nextId))?.name !== expectedTitle
5587
+ || prompt !== nextPrompt || await readFile(previousPath, "utf8") !== previousTranscript) {
5588
+ throw new Error(`Claude clear title self-test failed: ${mode}`);
5589
+ }
5590
+ releaseNotices.splice(0, releaseNotices.length,
5591
+ ...releaseNotices.filter((notice) => !clearRows.includes(notice)));
5592
+ }
5550
5593
  clearSession.subagents.clear();
5551
- await dispatch("session/release", { sessionId: "cleared-session" });
5594
+ await dispatch("session/release", { sessionId: clearSession.id });
5552
5595
  await clearConsumer;
5553
5596
  const { session: mainSession } = await createSession({ cwd: process.cwd() });
5554
5597
  const mainQuery = mainSession.query;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.10.14",
3
+ "version": "1.10.15",
4
4
  "type": "module",
5
5
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
6
6
  "keywords": [