adhdev 0.1.34 → 0.1.35

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.
Files changed (2) hide show
  1. package/dist/index.js +305 -14
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -364,8 +364,9 @@ async function detectCLIs() {
364
364
  return results;
365
365
  }
366
366
  async function detectCLI(cliId) {
367
+ const normalizedId = cliId === "claude-cli" ? "claude-code" : cliId;
367
368
  const all = await detectCLIs();
368
- return all.find((c) => c.id === cliId && c.installed) || null;
369
+ return all.find((c) => c.id === normalizedId && c.installed) || null;
369
370
  }
370
371
  var import_child_process3, os, KNOWN_CLIS;
371
372
  var init_cli_detector = __esm({
@@ -933,6 +934,47 @@ var init_cli_bridge = __esm({
933
934
  function stripAnsi(str) {
934
935
  return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\x1B\][^\x07]*\x07/g, "").replace(/\x1B\][^\x1B]*\x1B\\/g, "");
935
936
  }
937
+ function cleanGeminiResponse(raw, lastUserInput) {
938
+ const lines = raw.split("\n");
939
+ const cleaned = [];
940
+ for (const line of lines) {
941
+ const trimmed = line.trim();
942
+ if (!trimmed) {
943
+ cleaned.push("");
944
+ continue;
945
+ }
946
+ if (/^[\u256d\u256e\u2570\u256f\u2502\u251c\u2524\u252c\u2534\u253c\u2500\u2501\u2550\u2554\u2557\u255a\u255d\u2551]+$/.test(trimmed)) continue;
947
+ if (/^[\u256d\u2570]\u2500\u2500/.test(trimmed) || /\u2500\u2500[\u256e\u256f]$/.test(trimmed)) continue;
948
+ if (/^\u2502.*\u2502$/.test(trimmed)) continue;
949
+ if (/[\u2500\u2501\u2550\u2580\u2584]{4,}/.test(trimmed)) continue;
950
+ if (/^YOLO\s+ctrl\+y/i.test(trimmed)) continue;
951
+ if (/^\? for shortcuts/.test(trimmed)) continue;
952
+ if (/^\/model\s/i.test(trimmed)) continue;
953
+ if (/^~\s.*no sandbox/i.test(trimmed)) continue;
954
+ if (/^Type your message/i.test(trimmed)) continue;
955
+ if (/ctrl\+[a-z]/i.test(trimmed) && trimmed.length < 50) continue;
956
+ if (/Shortcuts?\s*\(for more/i.test(trimmed)) continue;
957
+ if (/shell mode|cycle mode|paste images|select file or folder|reverse-search|clear prompt|rewind|raw markdown|open external editor/i.test(trimmed)) continue;
958
+ if (/Shift\+Tab|Option\+M|Ctrl\+X|Ctrl\+R|Ctrl\+Y/i.test(trimmed) && /mode|prompt|editor|search/i.test(trimmed)) continue;
959
+ if (/^[\u276f>]\s*$/.test(trimmed)) continue;
960
+ if (/^[*\u2022]\s*$/.test(trimmed)) continue;
961
+ if (lastUserInput && /^[*\u2022]\s/.test(trimmed)) {
962
+ const bulletContent = trimmed.replace(/^[*\u2022]\s*/, "").trim();
963
+ if (bulletContent === lastUserInput.trim() || lastUserInput.trim().startsWith(bulletContent.slice(0, 15))) {
964
+ continue;
965
+ }
966
+ }
967
+ if (/Gemini CLI update available/i.test(trimmed)) continue;
968
+ if (/brew upgrade gemini-cli/i.test(trimmed)) continue;
969
+ if (/Installed via .+ Please update/i.test(trimmed)) continue;
970
+ if (/Waiting for auth/i.test(trimmed)) continue;
971
+ if (/ℹ Gemini CLI/i.test(trimmed)) continue;
972
+ if (/^[\u280b\u2819\u2839\u2838\u283c\u2834\u2826\u2827\u2807\u280f\s]+$/.test(trimmed)) continue;
973
+ if (trimmed.length <= 2 && /^[!@#$%^&*()_+=\-<>?/\\|~`]+$/.test(trimmed)) continue;
974
+ cleaned.push(line);
975
+ }
976
+ return cleaned.join("\n").replace(/\n{3,}/g, "\n\n").trim();
977
+ }
936
978
  function findGeminiBinary() {
937
979
  const isWin = os3.platform() === "win32";
938
980
  const cmd = isWin ? "where gemini" : "which gemini";
@@ -983,12 +1025,20 @@ var init_gemini_cli = __esm({
983
1025
  ready = false;
984
1026
  startupBuffer = "";
985
1027
  startupDialogDismissed = false;
1028
+ // Raw PTY I/O (터미널 뷰용)
1029
+ onPtyData = null;
1030
+ ptyOutputBuffer = "";
1031
+ ptyOutputFlushTimer = null;
986
1032
  constructor(workingDir) {
987
1033
  this.workingDir = workingDir.startsWith("~") ? workingDir.replace(/^~/, os3.homedir()) : workingDir;
988
1034
  }
989
1035
  setOnStatusChange(callback) {
990
1036
  this.onStatusChange = callback;
991
1037
  }
1038
+ /** PTY raw data 콜백 설정 (터미널 뷰 스트리밍용) */
1039
+ setOnPtyData(callback) {
1040
+ this.onPtyData = callback;
1041
+ }
992
1042
  /**
993
1043
  * PTY로 gemini 프로세스 시작 (인터랙티브 모드)
994
1044
  */
@@ -1000,7 +1050,7 @@ var init_gemini_cli = __esm({
1000
1050
  console.log(`[GeminiAdapter] Spawning interactive session in ${this.workingDir}`);
1001
1051
  console.log(`[GeminiAdapter] Binary: ${geminiBin}`);
1002
1052
  const shell = isWin ? "cmd.exe" : process.env.SHELL || "/bin/zsh";
1003
- const shellArgs = isWin ? ["/c", `${geminiBin} --yolo`] : ["-l", "-c", `"${geminiBin}" --yolo`];
1053
+ const shellArgs = isWin ? ["/c", `${geminiBin} --yolo`] : ["-l", "-c", `${geminiBin} --yolo`];
1004
1054
  console.log(`[GeminiAdapter] Shell: ${shell} ${shellArgs.join(" ")}`);
1005
1055
  this.ptyProcess = pty.spawn(shell, shellArgs, {
1006
1056
  name: "xterm-256color",
@@ -1013,6 +1063,18 @@ var init_gemini_cli = __esm({
1013
1063
  });
1014
1064
  this.ptyProcess.onData((data) => {
1015
1065
  this.handleOutput(data);
1066
+ if (this.onPtyData) {
1067
+ this.ptyOutputBuffer += data;
1068
+ if (!this.ptyOutputFlushTimer) {
1069
+ this.ptyOutputFlushTimer = setTimeout(() => {
1070
+ if (this.ptyOutputBuffer && this.onPtyData) {
1071
+ this.onPtyData(this.ptyOutputBuffer);
1072
+ }
1073
+ this.ptyOutputBuffer = "";
1074
+ this.ptyOutputFlushTimer = null;
1075
+ }, 50);
1076
+ }
1077
+ }
1016
1078
  });
1017
1079
  this.ptyProcess.onExit(({ exitCode }) => {
1018
1080
  console.log(`[GeminiAdapter] Process exited with code ${exitCode}`);
@@ -1091,6 +1153,8 @@ var init_gemini_cli = __esm({
1091
1153
  }
1092
1154
  }
1093
1155
  response = lines.join("\n").trim();
1156
+ const lastUserText = this.messages.length > 0 && this.messages[this.messages.length - 1].role === "user" ? this.messages[this.messages.length - 1].content : void 0;
1157
+ response = cleanGeminiResponse(response, lastUserText);
1094
1158
  if (response) {
1095
1159
  this.messages.push({
1096
1160
  role: "assistant",
@@ -1185,6 +1249,21 @@ var init_gemini_cli = __esm({
1185
1249
  isReady() {
1186
1250
  return this.ready;
1187
1251
  }
1252
+ /** Raw 키 입력을 PTY에 직접 전달 (터미널 뷰용) */
1253
+ writeRaw(data) {
1254
+ if (this.ptyProcess) {
1255
+ this.ptyProcess.write(data);
1256
+ }
1257
+ }
1258
+ /** PTY 터미널 사이즈 변경 */
1259
+ resize(cols, rows) {
1260
+ if (this.ptyProcess) {
1261
+ try {
1262
+ this.ptyProcess.resize(cols, rows);
1263
+ } catch {
1264
+ }
1265
+ }
1266
+ }
1188
1267
  /** 작업 디렉토리 변경 (세션 재시작) */
1189
1268
  async changeWorkingDir(newDir) {
1190
1269
  this.cancel();
@@ -1239,6 +1318,7 @@ var init_claude_cli = __esm({
1239
1318
  startupBuffer = "";
1240
1319
  bridge = null;
1241
1320
  logBuffer = [];
1321
+ onPtyDataCallback = null;
1242
1322
  // Patterns for Claude Code
1243
1323
  PROMPT_PATTERNS = [
1244
1324
  /❯\s*$/,
@@ -1282,6 +1362,7 @@ var init_claude_cli = __esm({
1282
1362
  env: { ...process.env }
1283
1363
  });
1284
1364
  this.ptyProcess.onData((data) => {
1365
+ this.onPtyDataCallback?.(data);
1285
1366
  this.handleOutput(data);
1286
1367
  });
1287
1368
  this.ptyProcess.onExit(({ exitCode }) => {
@@ -1407,6 +1488,25 @@ var init_claude_cli = __esm({
1407
1488
  await new Promise((r) => setTimeout(r, 1e3));
1408
1489
  await this.spawn();
1409
1490
  }
1491
+ /** PTY raw data 콜백 설정 (터미널 뷰 스트리밍용) */
1492
+ setOnPtyData(callback) {
1493
+ this.onPtyDataCallback = callback;
1494
+ }
1495
+ /** Raw 키 입력을 PTY에 직접 전달 (터미널 뷰용) */
1496
+ writeRaw(data) {
1497
+ if (this.ptyProcess) {
1498
+ this.ptyProcess.write(data);
1499
+ }
1500
+ }
1501
+ /** PTY 터미널 사이즈 변경 */
1502
+ resize(cols, rows) {
1503
+ if (this.ptyProcess) {
1504
+ try {
1505
+ this.ptyProcess.resize(cols, rows);
1506
+ } catch {
1507
+ }
1508
+ }
1509
+ }
1410
1510
  };
1411
1511
  }
1412
1512
  });
@@ -2729,7 +2829,13 @@ var init_daemon_p2p = __esm({
2729
2829
  fileRequestHandler = null;
2730
2830
  inputHandler = null;
2731
2831
  commandHandler = null;
2832
+ ptyInputHandler = null;
2833
+ ptyResizeHandler = null;
2732
2834
  _ssDebugDone = false;
2835
+ // PTY scrollback buffer per cliType (재연결 시 최근 출력 전송)
2836
+ ptyScrollback = /* @__PURE__ */ new Map();
2837
+ PTY_SCROLLBACK_MAX = 64 * 1024;
2838
+ // 64KB per CLI
2733
2839
  get screenshotActive() {
2734
2840
  for (const peer of this.peers.values()) {
2735
2841
  if (peer.screenshotActive && peer.state === "connected") return true;
@@ -2973,7 +3079,10 @@ var init_daemon_p2p = __esm({
2973
3079
  screenshotCh.onError((err) => log(`Screenshots error: ${err}`));
2974
3080
  const filesCh = pc.createDataChannel("files");
2975
3081
  entry.filesChannel = filesCh;
2976
- filesCh.onOpen(() => log(`Files channel OPEN for peer ${pid}`));
3082
+ filesCh.onOpen(() => {
3083
+ log(`Files channel OPEN for peer ${pid}`);
3084
+ setTimeout(() => this.sendPtyScrollback(pid), 100);
3085
+ });
2977
3086
  filesCh.onClosed(() => {
2978
3087
  const peer = this.peers.get(pid);
2979
3088
  if (peer?.filesChannel === filesCh) peer.filesChannel = null;
@@ -3021,6 +3130,18 @@ var init_daemon_p2p = __esm({
3021
3130
  this.handleP2PCommand(peerId, parsed);
3022
3131
  return;
3023
3132
  }
3133
+ if (parsed.type === "pty_input") {
3134
+ if (this.ptyInputHandler && parsed.data) {
3135
+ this.ptyInputHandler(parsed.cliType || "gemini-cli", parsed.data);
3136
+ }
3137
+ return;
3138
+ }
3139
+ if (parsed.type === "pty_resize") {
3140
+ if (this.ptyResizeHandler && parsed.cols && parsed.rows) {
3141
+ this.ptyResizeHandler(parsed.cliType || "gemini-cli", parsed.cols, parsed.rows);
3142
+ }
3143
+ return;
3144
+ }
3024
3145
  this.handleFileRequest(peerId, parsed);
3025
3146
  } catch (e) {
3026
3147
  log(`Parse error from peer ${peerId}: ${e?.message}`);
@@ -3044,6 +3165,44 @@ var init_daemon_p2p = __esm({
3044
3165
  }
3045
3166
  return sentAny;
3046
3167
  }
3168
+ /** PTY 출력을 모든 connected peer에게 브로드캐스트 */
3169
+ broadcastPtyOutput(cliType, data) {
3170
+ const prev = this.ptyScrollback.get(cliType) || "";
3171
+ const updated = prev + data;
3172
+ this.ptyScrollback.set(
3173
+ cliType,
3174
+ updated.length > this.PTY_SCROLLBACK_MAX ? updated.slice(-this.PTY_SCROLLBACK_MAX) : updated
3175
+ );
3176
+ const msg = JSON.stringify({ type: "pty_output", cliType, data });
3177
+ let sentAny = false;
3178
+ for (const peer of this.peers.values()) {
3179
+ if (peer.state !== "connected" || !peer.filesChannel) continue;
3180
+ try {
3181
+ peer.filesChannel.sendMessage(msg);
3182
+ sentAny = true;
3183
+ } catch {
3184
+ }
3185
+ }
3186
+ return sentAny;
3187
+ }
3188
+ /** Peer 연결 시 scrollback 전송 */
3189
+ sendPtyScrollback(peerId) {
3190
+ const peer = this.peers.get(peerId);
3191
+ if (!peer?.filesChannel) return;
3192
+ for (const [cliType, buffer] of this.ptyScrollback) {
3193
+ if (!buffer) continue;
3194
+ try {
3195
+ peer.filesChannel.sendMessage(JSON.stringify({
3196
+ type: "pty_output",
3197
+ cliType,
3198
+ data: buffer,
3199
+ scrollback: true
3200
+ }));
3201
+ log(`Sent PTY scrollback to peer ${peerId}: ${cliType} (${buffer.length} bytes)`);
3202
+ } catch {
3203
+ }
3204
+ }
3205
+ }
3047
3206
  sendScreenshot(base64Data) {
3048
3207
  let sentAny = false;
3049
3208
  const buffer = Buffer.from(base64Data, "base64");
@@ -3081,6 +3240,12 @@ var init_daemon_p2p = __esm({
3081
3240
  onCommand(handler) {
3082
3241
  this.commandHandler = handler;
3083
3242
  }
3243
+ onPtyInput(handler) {
3244
+ this.ptyInputHandler = handler;
3245
+ }
3246
+ onPtyResize(handler) {
3247
+ this.ptyResizeHandler = handler;
3248
+ }
3084
3249
  // ─── P2P 명령/입력/파일 처리 ────────────────
3085
3250
  async handleP2PCommand(peerId, msg) {
3086
3251
  const { id, commandType, data } = msg;
@@ -3479,6 +3644,11 @@ var init_daemon_commands = __esm({
3479
3644
  return this.handleAgentStreamSwitchSession(args);
3480
3645
  case "agent_stream_focus":
3481
3646
  return this.handleAgentStreamFocus(args);
3647
+ // ─── PTY Raw I/O (터미널 뷰) ─────────
3648
+ case "pty_input":
3649
+ return this.handlePtyInput(args);
3650
+ case "pty_resize":
3651
+ return this.handlePtyResize(args);
3482
3652
  default:
3483
3653
  return { success: false, error: `Unknown command: ${cmd}` };
3484
3654
  }
@@ -3938,6 +4108,35 @@ var init_daemon_commands = __esm({
3938
4108
  const ok = await this.agentStream.focusAgentEditor(this.getCdp(), agentType);
3939
4109
  return { success: ok };
3940
4110
  }
4111
+ // ─── PTY Raw I/O (터미널 뷰) ──────────────────────
4112
+ handlePtyInput(args) {
4113
+ const { cliType, data } = args || {};
4114
+ if (!data) return { success: false, error: "data required" };
4115
+ if (this.ctx.adapters) {
4116
+ const targetCli = cliType || "gemini-cli";
4117
+ for (const [, adapter] of this.ctx.adapters) {
4118
+ if (adapter.cliType === targetCli && typeof adapter.writeRaw === "function") {
4119
+ adapter.writeRaw(data);
4120
+ return { success: true };
4121
+ }
4122
+ }
4123
+ }
4124
+ return { success: false, error: `CLI adapter not found: ${cliType}` };
4125
+ }
4126
+ handlePtyResize(args) {
4127
+ const { cliType, cols, rows } = args || {};
4128
+ if (!cols || !rows) return { success: false, error: "cols and rows required" };
4129
+ if (this.ctx.adapters) {
4130
+ const targetCli = cliType || "gemini-cli";
4131
+ for (const [, adapter] of this.ctx.adapters) {
4132
+ if (adapter.cliType === targetCli && typeof adapter.resize === "function") {
4133
+ adapter.resize(cols, rows);
4134
+ return { success: true };
4135
+ }
4136
+ }
4137
+ }
4138
+ return { success: false, error: `CLI adapter not found: ${cliType}` };
4139
+ }
3941
4140
  };
3942
4141
  }
3943
4142
  });
@@ -4542,6 +4741,11 @@ var init_adhdev_daemon = __esm({
4542
4741
  scriptLoaders = /* @__PURE__ */ new Map();
4543
4742
  commandHandler = null;
4544
4743
  screenshotTimer = null;
4744
+ lastStatusSentFull = false;
4745
+ lastStatusSentAt = 0;
4746
+ statusPendingThrottle = false;
4747
+ lastP2PStatusHash = "";
4748
+ statusReportCount = 0;
4545
4749
  agentStreamManager = null;
4546
4750
  agentStreamTimer = null;
4547
4751
  running = false;
@@ -4592,7 +4796,7 @@ var init_adhdev_daemon = __esm({
4592
4796
  setTimeout(() => this.sendUnifiedStatusReport(), 500);
4593
4797
  },
4594
4798
  onExtensionStatus: () => {
4595
- this.sendUnifiedStatusReport();
4799
+ this.throttledStatusReport();
4596
4800
  },
4597
4801
  onExtensionEvent: (ext, event, data) => {
4598
4802
  if (event === "token_updated" && data.token) {
@@ -4705,6 +4909,22 @@ var init_adhdev_daemon = __esm({
4705
4909
  return { id: req.id, success: result.success, entries: result.files, error: result.error };
4706
4910
  }
4707
4911
  });
4912
+ this.p2p.onPtyInput((cliType, data) => {
4913
+ for (const [, adapter] of this.adapters) {
4914
+ if (adapter.cliType === cliType && typeof adapter.writeRaw === "function") {
4915
+ adapter.writeRaw(data);
4916
+ return;
4917
+ }
4918
+ }
4919
+ });
4920
+ this.p2p.onPtyResize((cliType, cols, rows) => {
4921
+ for (const [, adapter] of this.adapters) {
4922
+ if (adapter.cliType === cliType && typeof adapter.resize === "function") {
4923
+ adapter.resize(cols, rows);
4924
+ return;
4925
+ }
4926
+ }
4927
+ });
4708
4928
  let ssDebugCount = 0;
4709
4929
  this.screenshotTimer = setInterval(async () => {
4710
4930
  const active = this.p2p?.screenshotActive;
@@ -4842,7 +5062,10 @@ var init_adhdev_daemon = __esm({
4842
5062
  "agent_stream_new",
4843
5063
  "agent_stream_list_chats",
4844
5064
  "agent_stream_switch_session",
4845
- "agent_stream_focus"
5065
+ "agent_stream_focus",
5066
+ // PTY I/O commands (터미널 뷰)
5067
+ "pty_input",
5068
+ "pty_resize"
4846
5069
  ];
4847
5070
  for (const cmdType of directCdpCommands) {
4848
5071
  this.bridge.on(cmdType, async (msg) => {
@@ -4911,7 +5134,19 @@ var init_adhdev_daemon = __esm({
4911
5134
  const dir = args?.dir || process.cwd();
4912
5135
  if (!cliType) throw new Error("cliType required");
4913
5136
  const key = this.getCliKey(cliType, dir);
4914
- await this.stopCliSession(key);
5137
+ if (this.adapters.has(key)) {
5138
+ await this.stopCliSession(key);
5139
+ } else {
5140
+ let found = false;
5141
+ for (const [k, adapter] of this.adapters) {
5142
+ if (adapter.cliType === cliType) {
5143
+ await this.stopCliSession(k);
5144
+ found = true;
5145
+ break;
5146
+ }
5147
+ }
5148
+ if (!found) console.log(import_chalk2.default.yellow(` \u26A0 No adapter found for ${cliType} (key: ${key})`));
5149
+ }
4915
5150
  this.sendResult(msg, true, { cliType, dir });
4916
5151
  return;
4917
5152
  }
@@ -5033,6 +5268,17 @@ var init_adhdev_daemon = __esm({
5033
5268
  adapter.setBridge(this.bridge);
5034
5269
  }
5035
5270
  adapter.setOnStatusChange(() => this.sendUnifiedStatusReport());
5271
+ if (typeof adapter.setOnPtyData === "function") {
5272
+ adapter.setOnPtyData((data) => {
5273
+ const sentViaP2P = this.p2pSender?.broadcastPtyOutput(adapter.cliType, data);
5274
+ if (!sentViaP2P && this.bridge) {
5275
+ this.bridge.sendMessage("pty_output", {
5276
+ cliType: adapter.cliType,
5277
+ data
5278
+ });
5279
+ }
5280
+ });
5281
+ }
5036
5282
  this.adapters.set(key, adapter);
5037
5283
  this.lastAgentStatus.set(key, "idle");
5038
5284
  console.log(import_chalk2.default.green(` \u2713 CLI started: ${cliInfo.displayName} v${cliInfo.version || "unknown"} in ${resolvedDir}`));
@@ -5052,18 +5298,33 @@ var init_adhdev_daemon = __esm({
5052
5298
  // ─── 통합 상태 보고 ─────────────────────────────
5053
5299
  startStatusReporting() {
5054
5300
  const scheduleNext = () => {
5055
- const isProcessing = Array.from(this.adapters.values()).some((a) => a.isProcessing());
5056
- const interval = isProcessing ? 2e3 : 15e3;
5057
5301
  this.statusTimer = setTimeout(() => {
5058
5302
  this.sendUnifiedStatusReport().catch(() => {
5059
5303
  });
5060
5304
  scheduleNext();
5061
- }, interval);
5305
+ }, 15e3);
5062
5306
  };
5063
5307
  scheduleNext();
5064
5308
  }
5309
+ /** Throttled status report: 최소 3초 간격 보장 */
5310
+ throttledStatusReport() {
5311
+ const now = Date.now();
5312
+ const elapsed = now - this.lastStatusSentAt;
5313
+ if (elapsed >= 3e3) {
5314
+ this.sendUnifiedStatusReport().catch(() => {
5315
+ });
5316
+ } else if (!this.statusPendingThrottle) {
5317
+ this.statusPendingThrottle = true;
5318
+ setTimeout(() => {
5319
+ this.statusPendingThrottle = false;
5320
+ this.sendUnifiedStatusReport().catch(() => {
5321
+ });
5322
+ }, 3e3 - elapsed);
5323
+ }
5324
+ }
5065
5325
  async sendUnifiedStatusReport() {
5066
5326
  if (!this.bridge?.isConnected()) return;
5327
+ this.lastStatusSentAt = Date.now();
5067
5328
  if (this.cdpManagers.size > 0 && this.scriptLoaders.size > 0 && !this._cdpChatBusy) {
5068
5329
  this._cdpChatBusy = true;
5069
5330
  for (const [ideType, cdp] of this.cdpManagers) {
@@ -5148,7 +5409,7 @@ var init_adhdev_daemon = __esm({
5148
5409
  this.generatingStartedAt.set(key, now);
5149
5410
  this.bridge?.sendMessage("status_event", {
5150
5411
  event: "agent:generating_started",
5151
- chatTitle: `${adapter.cliName} Session`,
5412
+ chatTitle: `${adapter.cliName} \xB7 ${adapter.workingDir.split("/").filter(Boolean).pop() || "session"}`,
5152
5413
  timestamp: now
5153
5414
  });
5154
5415
  } else if (lastStatus === "generating" && cliStatus === "idle") {
@@ -5156,7 +5417,7 @@ var init_adhdev_daemon = __esm({
5156
5417
  const duration = startedAt ? Math.round((now - startedAt) / 1e3) : 0;
5157
5418
  this.bridge?.sendMessage("status_event", {
5158
5419
  event: "agent:generating_completed",
5159
- chatTitle: `${adapter.cliName} Session`,
5420
+ chatTitle: `${adapter.cliName} \xB7 ${adapter.workingDir.split("/").filter(Boolean).pop() || "session"}`,
5160
5421
  duration,
5161
5422
  timestamp: now
5162
5423
  });
@@ -5182,7 +5443,7 @@ var init_adhdev_daemon = __esm({
5182
5443
  activeChat: {
5183
5444
  id: key,
5184
5445
  status: cliStatus,
5185
- title: `${adapter.cliName} Session`,
5446
+ title: `${adapter.cliName} \xB7 ${adapter.workingDir.split("/").filter(Boolean).pop() || "session"}`,
5186
5447
  messages: cliMessages,
5187
5448
  inputContent: "",
5188
5449
  activeModal: adapterStatus.activeModal
@@ -5257,8 +5518,38 @@ var init_adhdev_daemon = __esm({
5257
5518
  hostname: os8.hostname()
5258
5519
  }
5259
5520
  };
5260
- this.bridge.sendMessage("status_report", payload);
5261
- this.p2p?.sendStatus(payload);
5521
+ const { timestamp: _ts, system: _sys, ...hashTarget } = payload;
5522
+ if (hashTarget.machine) {
5523
+ const { freeMem: _f, loadavg: _l, uptime: _u, ...stableMachine } = hashTarget.machine;
5524
+ hashTarget.machine = stableMachine;
5525
+ }
5526
+ const statusHash = this.simpleHash(JSON.stringify(hashTarget));
5527
+ if (statusHash !== this.lastP2PStatusHash) {
5528
+ this.lastP2PStatusHash = statusHash;
5529
+ this.p2p?.sendStatus(payload);
5530
+ }
5531
+ const p2pConnected = (this.p2p?.connectedPeerCount || 0) > 0;
5532
+ this.statusReportCount = (this.statusReportCount || 0) + 1;
5533
+ const forceFullSync = this.statusReportCount % 4 === 0;
5534
+ if (p2pConnected && this.lastStatusSentFull && !forceFullSync) {
5535
+ this.bridge.sendMessage("status_heartbeat", {
5536
+ timestamp: now,
5537
+ p2pConnected: true,
5538
+ p2pPeers: this.p2p?.connectedPeerCount || 0
5539
+ });
5540
+ } else {
5541
+ this.bridge.sendMessage("status_report", payload);
5542
+ this.lastStatusSentFull = true;
5543
+ }
5544
+ }
5545
+ /** Fast string hash (FNV-1a 32-bit) */
5546
+ simpleHash(s) {
5547
+ let h = 2166136261;
5548
+ for (let i = 0; i < s.length; i++) {
5549
+ h ^= s.charCodeAt(i);
5550
+ h = h * 16777619 >>> 0;
5551
+ }
5552
+ return h.toString(36);
5262
5553
  }
5263
5554
  sendResult(msg, success, extra) {
5264
5555
  if (!msg.id) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adhdev",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "ADHDev CLI — Detect, install and configure your IDE + AI agent extensions",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -54,4 +54,4 @@
54
54
  "tsx": "^4.19.0",
55
55
  "typescript": "^5.5.0"
56
56
  }
57
- }
57
+ }