happy-coder 0.11.2-0 → 0.12.0-0

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.
@@ -31,7 +31,7 @@ async function main() {
31
31
  if (httpClient) return httpClient;
32
32
  const client = new index_js.Client(
33
33
  { name: "happy-stdio-bridge", version: "1.0.0" },
34
- { capabilities: { tools: {} } }
34
+ { capabilities: {} }
35
35
  );
36
36
  const transport = new streamableHttp_js.StreamableHTTPClientTransport(new URL(baseUrl));
37
37
  await client.connect(transport);
@@ -40,8 +40,7 @@ async function main() {
40
40
  }
41
41
  const server = new mcp_js.McpServer({
42
42
  name: "Happy MCP Bridge",
43
- version: "1.0.0",
44
- description: "STDIO bridge forwarding to Happy HTTP MCP"
43
+ version: "1.0.0"
45
44
  });
46
45
  server.registerTool(
47
46
  "change_title",
@@ -29,7 +29,7 @@ async function main() {
29
29
  if (httpClient) return httpClient;
30
30
  const client = new Client(
31
31
  { name: "happy-stdio-bridge", version: "1.0.0" },
32
- { capabilities: { tools: {} } }
32
+ { capabilities: {} }
33
33
  );
34
34
  const transport = new StreamableHTTPClientTransport(new URL(baseUrl));
35
35
  await client.connect(transport);
@@ -38,8 +38,7 @@ async function main() {
38
38
  }
39
39
  const server = new McpServer({
40
40
  name: "Happy MCP Bridge",
41
- version: "1.0.0",
42
- description: "STDIO bridge forwarding to Happy HTTP MCP"
41
+ version: "1.0.0"
43
42
  });
44
43
  server.registerTool(
45
44
  "change_title",
@@ -3,7 +3,7 @@
3
3
  var chalk = require('chalk');
4
4
  var os = require('node:os');
5
5
  var node_crypto = require('node:crypto');
6
- var types = require('./types-D6ZewYb6.cjs');
6
+ var types = require('./types-C7skJO9Y.cjs');
7
7
  var node_child_process = require('node:child_process');
8
8
  var node_path = require('node:path');
9
9
  var node_readline = require('node:readline');
@@ -242,31 +242,19 @@ const claudeCliPath = node_path.resolve(node_path.join(types.projectPath(), "scr
242
242
  async function claudeLocal(opts) {
243
243
  const projectDir = getProjectPath(opts.path);
244
244
  fs.mkdirSync(projectDir, { recursive: true });
245
- const watcher = fs.watch(projectDir);
246
- let resolvedSessionId = null;
247
- const detectedIdsRandomUUID = /* @__PURE__ */ new Set();
248
- const detectedIdsFileSystem = /* @__PURE__ */ new Set();
249
- watcher.on("change", (event, filename) => {
250
- if (typeof filename === "string" && filename.toLowerCase().endsWith(".jsonl")) {
251
- types.logger.debug("change", event, filename);
252
- const sessionId = filename.replace(".jsonl", "");
253
- if (detectedIdsFileSystem.has(sessionId)) {
254
- return;
255
- }
256
- detectedIdsFileSystem.add(sessionId);
257
- if (resolvedSessionId) {
258
- return;
259
- }
260
- if (detectedIdsRandomUUID.has(sessionId)) {
261
- resolvedSessionId = sessionId;
262
- opts.onSessionFound(sessionId);
263
- }
264
- }
265
- });
266
245
  let startFrom = opts.sessionId;
267
246
  if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
268
247
  startFrom = null;
269
248
  }
249
+ const newSessionId = startFrom ? null : node_crypto.randomUUID();
250
+ const effectiveSessionId = startFrom || newSessionId;
251
+ if (newSessionId) {
252
+ types.logger.debug(`[ClaudeLocal] Generated new session ID: ${newSessionId}`);
253
+ opts.onSessionFound(newSessionId);
254
+ } else {
255
+ types.logger.debug(`[ClaudeLocal] Resuming session: ${startFrom}`);
256
+ opts.onSessionFound(startFrom);
257
+ }
270
258
  let thinking = false;
271
259
  let stopThinkingTimeout = null;
272
260
  const updateThinking = (newThinking) => {
@@ -284,6 +272,8 @@ async function claudeLocal(opts) {
284
272
  const args = [];
285
273
  if (startFrom) {
286
274
  args.push("--resume", startFrom);
275
+ } else {
276
+ args.push("--session-id", newSessionId);
287
277
  }
288
278
  args.push("--append-system-prompt", systemPrompt);
289
279
  if (opts.mcpServers && Object.keys(opts.mcpServers).length > 0) {
@@ -302,6 +292,8 @@ async function claudeLocal(opts) {
302
292
  ...process.env,
303
293
  ...opts.claudeEnvVars
304
294
  };
295
+ types.logger.debug(`[ClaudeLocal] Spawning launcher: ${claudeCliPath}`);
296
+ types.logger.debug(`[ClaudeLocal] Args: ${JSON.stringify(args)}`);
305
297
  const child = node_child_process.spawn("node", [claudeCliPath, ...args], {
306
298
  stdio: ["inherit", "inherit", "inherit", "pipe"],
307
299
  signal: opts.abort,
@@ -318,13 +310,6 @@ async function claudeLocal(opts) {
318
310
  try {
319
311
  const message = JSON.parse(line);
320
312
  switch (message.type) {
321
- case "uuid":
322
- detectedIdsRandomUUID.add(message.value);
323
- if (!resolvedSessionId && detectedIdsFileSystem.has(message.value)) {
324
- resolvedSessionId = message.value;
325
- opts.onSessionFound(message.value);
326
- }
327
- break;
328
313
  case "fetch-start":
329
314
  activeFetches.set(message.id, {
330
315
  hostname: message.hostname,
@@ -378,7 +363,6 @@ async function claudeLocal(opts) {
378
363
  });
379
364
  });
380
365
  } finally {
381
- watcher.close();
382
366
  process.stdin.resume();
383
367
  if (stopThinkingTimeout) {
384
368
  clearTimeout(stopThinkingTimeout);
@@ -386,7 +370,7 @@ async function claudeLocal(opts) {
386
370
  }
387
371
  updateThinking(false);
388
372
  }
389
- return resolvedSessionId;
373
+ return effectiveSessionId;
390
374
  }
391
375
 
392
376
  class Future {
@@ -504,6 +488,11 @@ function startFileWatcher(file, onFileChange) {
504
488
  };
505
489
  }
506
490
 
491
+ const INTERNAL_CLAUDE_EVENT_TYPES = /* @__PURE__ */ new Set([
492
+ "file-history-snapshot",
493
+ "change",
494
+ "queue-operation"
495
+ ]);
507
496
  async function createSessionScanner(opts) {
508
497
  const projectDir = getProjectPath(opts.workingDirectory);
509
498
  let finishedSessions = /* @__PURE__ */ new Set();
@@ -513,26 +502,42 @@ async function createSessionScanner(opts) {
513
502
  let processedMessageKeys = /* @__PURE__ */ new Set();
514
503
  if (opts.sessionId) {
515
504
  let messages = await readSessionLog(projectDir, opts.sessionId);
505
+ types.logger.debug(`[SESSION_SCANNER] Marking ${messages.length} existing messages as processed from session ${opts.sessionId}`);
516
506
  for (let m of messages) {
517
507
  processedMessageKeys.add(messageKey(m));
518
508
  }
509
+ currentSessionId = opts.sessionId;
519
510
  }
520
511
  const sync = new InvalidateSync(async () => {
521
512
  let sessions = [];
522
513
  for (let p of pendingSessions) {
523
514
  sessions.push(p);
524
515
  }
525
- if (currentSessionId) {
516
+ if (currentSessionId && !pendingSessions.has(currentSessionId)) {
526
517
  sessions.push(currentSessionId);
527
518
  }
519
+ for (let [sessionId] of watchers) {
520
+ if (!sessions.includes(sessionId)) {
521
+ sessions.push(sessionId);
522
+ }
523
+ }
528
524
  for (let session of sessions) {
529
- for (let file of await readSessionLog(projectDir, session)) {
525
+ const sessionMessages = await readSessionLog(projectDir, session);
526
+ let skipped = 0;
527
+ let sent = 0;
528
+ for (let file of sessionMessages) {
530
529
  let key = messageKey(file);
531
530
  if (processedMessageKeys.has(key)) {
531
+ skipped++;
532
532
  continue;
533
533
  }
534
534
  processedMessageKeys.add(key);
535
+ types.logger.debug(`[SESSION_SCANNER] Sending new message: type=${file.type}, uuid=${file.type === "summary" ? file.leafUuid : file.uuid}`);
535
536
  opts.onMessage(file);
537
+ sent++;
538
+ }
539
+ if (sessionMessages.length > 0) {
540
+ types.logger.debug(`[SESSION_SCANNER] Session ${session}: found=${sessionMessages.length}, skipped=${skipped}, sent=${sent}`);
536
541
  }
537
542
  }
538
543
  for (let p of sessions) {
@@ -543,6 +548,7 @@ async function createSessionScanner(opts) {
543
548
  }
544
549
  for (let p of sessions) {
545
550
  if (!watchers.has(p)) {
551
+ types.logger.debug(`[SESSION_SCANNER] Starting watcher for session: ${p}`);
546
552
  watchers.set(p, startFileWatcher(node_path.join(projectDir, `${p}.jsonl`), () => {
547
553
  sync.invalidate();
548
554
  }));
@@ -616,9 +622,11 @@ async function readSessionLog(projectDir, sessionId) {
616
622
  continue;
617
623
  }
618
624
  let message = JSON.parse(l);
625
+ if (message.type && INTERNAL_CLAUDE_EVENT_TYPES.has(message.type)) {
626
+ continue;
627
+ }
619
628
  let parsed = types.RawJSONLinesSchema.safeParse(message);
620
629
  if (!parsed.success) {
621
- types.logger.debugLargeJson(`[SESSION_SCANNER] Failed to parse message`, message);
622
630
  continue;
623
631
  }
624
632
  messages.push(parsed.data);
@@ -981,10 +989,94 @@ class AbortError extends Error {
981
989
  }
982
990
  }
983
991
 
984
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-CFWfkM2k.cjs', document.baseURI).href)));
992
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BCQGaH21.cjs', document.baseURI).href)));
985
993
  const __dirname$1 = node_path.join(__filename$1, "..");
994
+ function getGlobalClaudeVersion() {
995
+ try {
996
+ const cleanEnv = getCleanEnv();
997
+ const output = node_child_process.execSync("claude --version", {
998
+ encoding: "utf8",
999
+ stdio: ["pipe", "pipe", "pipe"],
1000
+ cwd: os.homedir(),
1001
+ env: cleanEnv
1002
+ }).trim();
1003
+ const match = output.match(/(\d+\.\d+\.\d+)/);
1004
+ types.logger.debug(`[Claude SDK] Global claude --version output: ${output}`);
1005
+ return match ? match[1] : null;
1006
+ } catch {
1007
+ return null;
1008
+ }
1009
+ }
1010
+ function getCleanEnv() {
1011
+ const env = { ...process.env };
1012
+ const cwd = process.cwd();
1013
+ const pathSep = process.platform === "win32" ? ";" : ":";
1014
+ const pathKey = process.platform === "win32" ? "Path" : "PATH";
1015
+ const actualPathKey = Object.keys(env).find((k) => k.toLowerCase() === "path") || pathKey;
1016
+ if (env[actualPathKey]) {
1017
+ const cleanPath = env[actualPathKey].split(pathSep).filter((p) => {
1018
+ const normalizedP = p.replace(/\\/g, "/").toLowerCase();
1019
+ const normalizedCwd = cwd.replace(/\\/g, "/").toLowerCase();
1020
+ return !normalizedP.startsWith(normalizedCwd);
1021
+ }).join(pathSep);
1022
+ env[actualPathKey] = cleanPath;
1023
+ types.logger.debug(`[Claude SDK] Cleaned PATH, removed local paths from: ${cwd}`);
1024
+ }
1025
+ return env;
1026
+ }
1027
+ function findGlobalClaudePath() {
1028
+ const homeDir = os.homedir();
1029
+ const cleanEnv = getCleanEnv();
1030
+ try {
1031
+ node_child_process.execSync("claude --version", {
1032
+ encoding: "utf8",
1033
+ stdio: ["pipe", "pipe", "pipe"],
1034
+ cwd: homeDir,
1035
+ env: cleanEnv
1036
+ });
1037
+ types.logger.debug("[Claude SDK] Global claude command available (checked with clean PATH)");
1038
+ return "claude";
1039
+ } catch {
1040
+ }
1041
+ if (process.platform !== "win32") {
1042
+ try {
1043
+ const result = node_child_process.execSync("which claude", {
1044
+ encoding: "utf8",
1045
+ stdio: ["pipe", "pipe", "pipe"],
1046
+ cwd: homeDir,
1047
+ env: cleanEnv
1048
+ }).trim();
1049
+ if (result && fs.existsSync(result)) {
1050
+ types.logger.debug(`[Claude SDK] Found global claude path via which: ${result}`);
1051
+ return result;
1052
+ }
1053
+ } catch {
1054
+ }
1055
+ }
1056
+ return null;
1057
+ }
986
1058
  function getDefaultClaudeCodePath() {
987
- return node_path.join(__dirname$1, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
1059
+ const nodeModulesPath = node_path.join(__dirname$1, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
1060
+ if (process.env.HAPPY_CLAUDE_PATH) {
1061
+ types.logger.debug(`[Claude SDK] Using HAPPY_CLAUDE_PATH: ${process.env.HAPPY_CLAUDE_PATH}`);
1062
+ return process.env.HAPPY_CLAUDE_PATH;
1063
+ }
1064
+ if (process.env.HAPPY_USE_BUNDLED_CLAUDE === "1") {
1065
+ types.logger.debug(`[Claude SDK] Forced bundled version: ${nodeModulesPath}`);
1066
+ return nodeModulesPath;
1067
+ }
1068
+ const globalPath = findGlobalClaudePath();
1069
+ if (!globalPath) {
1070
+ types.logger.debug(`[Claude SDK] No global claude found, using bundled: ${nodeModulesPath}`);
1071
+ return nodeModulesPath;
1072
+ }
1073
+ const globalVersion = getGlobalClaudeVersion();
1074
+ types.logger.debug(`[Claude SDK] Global version: ${globalVersion || "unknown"}`);
1075
+ if (!globalVersion) {
1076
+ types.logger.debug(`[Claude SDK] Cannot compare versions, using global: ${globalPath}`);
1077
+ return globalPath;
1078
+ }
1079
+ return globalPath;
988
1080
  }
989
1081
  function logDebug(message) {
990
1082
  if (process.env.DEBUG) {
@@ -1249,17 +1341,22 @@ function query(config) {
1249
1341
  } else {
1250
1342
  args.push("--input-format", "stream-json");
1251
1343
  }
1252
- if (!fs.existsSync(pathToClaudeCodeExecutable)) {
1344
+ const isJsFile = pathToClaudeCodeExecutable.endsWith(".js") || pathToClaudeCodeExecutable.endsWith(".cjs");
1345
+ const isCommandOnly = pathToClaudeCodeExecutable === "claude";
1346
+ if (!isCommandOnly && !fs.existsSync(pathToClaudeCodeExecutable)) {
1253
1347
  throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`);
1254
1348
  }
1255
- logDebug(`Spawning Claude Code process: ${executable} ${[...executableArgs, pathToClaudeCodeExecutable, ...args].join(" ")}`);
1256
- const child = node_child_process.spawn(executable, [...executableArgs, pathToClaudeCodeExecutable, ...args], {
1349
+ const spawnCommand = isJsFile ? executable : pathToClaudeCodeExecutable;
1350
+ const spawnArgs = isJsFile ? [...executableArgs, pathToClaudeCodeExecutable, ...args] : args;
1351
+ const spawnEnv = isCommandOnly ? getCleanEnv() : process.env;
1352
+ logDebug(`Spawning Claude Code process: ${spawnCommand} ${spawnArgs.join(" ")} (using ${isCommandOnly ? "clean" : "normal"} env)`);
1353
+ const child = node_child_process.spawn(spawnCommand, spawnArgs, {
1257
1354
  cwd,
1258
1355
  stdio: ["pipe", "pipe", "pipe"],
1259
1356
  signal: config.options?.abort,
1260
- env: {
1261
- ...process.env
1262
- }
1357
+ env: spawnEnv,
1358
+ // Use shell on Windows for global binaries and command-only mode
1359
+ shell: !isJsFile && process.platform === "win32"
1263
1360
  });
1264
1361
  let childStdin = null;
1265
1362
  if (typeof prompt === "string") {
@@ -4605,8 +4702,7 @@ async function startHappyServer(client) {
4605
4702
  };
4606
4703
  const mcp = new mcp_js.McpServer({
4607
4704
  name: "Happy MCP",
4608
- version: "1.0.0",
4609
- description: "Happy CLI MCP server with chat session management tools"
4705
+ version: "1.0.0"
4610
4706
  });
4611
4707
  mcp.registerTool("change_title", {
4612
4708
  description: "Change the title of the current chat session",
@@ -5816,7 +5912,7 @@ async function handleConnectVendor(vendor, displayName) {
5816
5912
  return;
5817
5913
  } else if (subcommand === "codex") {
5818
5914
  try {
5819
- const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-Dxi7L-oi.cjs'); });
5915
+ const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-BwpHgdkt.cjs'); });
5820
5916
  let startedBy = void 0;
5821
5917
  for (let i = 1; i < args.length; i++) {
5822
5918
  if (args[i] === "--started-by") {
@@ -1,13 +1,13 @@
1
1
  import chalk from 'chalk';
2
2
  import os$1, { homedir } from 'node:os';
3
3
  import { randomUUID, randomBytes } from 'node:crypto';
4
- import { l as logger, p as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, f as AsyncLock, c as configuration, g as readDaemonState, h as clearDaemonState, b as packageJson, r as readSettings, i as readCredentials, j as encodeBase64, u as updateSettings, k as encodeBase64Url, m as decodeBase64, w as writeCredentialsLegacy, n as writeCredentialsDataKey, o as acquireDaemonLock, q as writeDaemonState, A as ApiClient, s as releaseDaemonLock, t as clearCredentials, v as clearMachineId, x as getLatestDaemonLog } from './types-CVOFMcT8.mjs';
4
+ import { l as logger, p as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, f as AsyncLock, c as configuration, g as readDaemonState, h as clearDaemonState, b as packageJson, r as readSettings, i as readCredentials, j as encodeBase64, u as updateSettings, k as encodeBase64Url, m as decodeBase64, w as writeCredentialsLegacy, n as writeCredentialsDataKey, o as acquireDaemonLock, q as writeDaemonState, A as ApiClient, s as releaseDaemonLock, t as clearCredentials, v as clearMachineId, x as getLatestDaemonLog } from './types-DdwJ6K-A.mjs';
5
5
  import { spawn, execSync, execFileSync } from 'node:child_process';
6
6
  import { resolve, join } from 'node:path';
7
7
  import { createInterface } from 'node:readline';
8
- import { existsSync, readFileSync, mkdirSync, watch, readdirSync, statSync, rmSync } from 'node:fs';
8
+ import { existsSync, readFileSync, mkdirSync, readdirSync, statSync, rmSync } from 'node:fs';
9
9
  import { readFile } from 'node:fs/promises';
10
- import fs, { watch as watch$1, access } from 'fs/promises';
10
+ import fs, { watch, access } from 'fs/promises';
11
11
  import { useStdout, useInput, Box, Text, render } from 'ink';
12
12
  import React, { useState, useRef, useEffect, useCallback } from 'react';
13
13
  import { fileURLToPath } from 'node:url';
@@ -220,31 +220,19 @@ const claudeCliPath = resolve(join(projectPath(), "scripts", "claude_local_launc
220
220
  async function claudeLocal(opts) {
221
221
  const projectDir = getProjectPath(opts.path);
222
222
  mkdirSync(projectDir, { recursive: true });
223
- const watcher = watch(projectDir);
224
- let resolvedSessionId = null;
225
- const detectedIdsRandomUUID = /* @__PURE__ */ new Set();
226
- const detectedIdsFileSystem = /* @__PURE__ */ new Set();
227
- watcher.on("change", (event, filename) => {
228
- if (typeof filename === "string" && filename.toLowerCase().endsWith(".jsonl")) {
229
- logger.debug("change", event, filename);
230
- const sessionId = filename.replace(".jsonl", "");
231
- if (detectedIdsFileSystem.has(sessionId)) {
232
- return;
233
- }
234
- detectedIdsFileSystem.add(sessionId);
235
- if (resolvedSessionId) {
236
- return;
237
- }
238
- if (detectedIdsRandomUUID.has(sessionId)) {
239
- resolvedSessionId = sessionId;
240
- opts.onSessionFound(sessionId);
241
- }
242
- }
243
- });
244
223
  let startFrom = opts.sessionId;
245
224
  if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
246
225
  startFrom = null;
247
226
  }
227
+ const newSessionId = startFrom ? null : randomUUID();
228
+ const effectiveSessionId = startFrom || newSessionId;
229
+ if (newSessionId) {
230
+ logger.debug(`[ClaudeLocal] Generated new session ID: ${newSessionId}`);
231
+ opts.onSessionFound(newSessionId);
232
+ } else {
233
+ logger.debug(`[ClaudeLocal] Resuming session: ${startFrom}`);
234
+ opts.onSessionFound(startFrom);
235
+ }
248
236
  let thinking = false;
249
237
  let stopThinkingTimeout = null;
250
238
  const updateThinking = (newThinking) => {
@@ -262,6 +250,8 @@ async function claudeLocal(opts) {
262
250
  const args = [];
263
251
  if (startFrom) {
264
252
  args.push("--resume", startFrom);
253
+ } else {
254
+ args.push("--session-id", newSessionId);
265
255
  }
266
256
  args.push("--append-system-prompt", systemPrompt);
267
257
  if (opts.mcpServers && Object.keys(opts.mcpServers).length > 0) {
@@ -280,6 +270,8 @@ async function claudeLocal(opts) {
280
270
  ...process.env,
281
271
  ...opts.claudeEnvVars
282
272
  };
273
+ logger.debug(`[ClaudeLocal] Spawning launcher: ${claudeCliPath}`);
274
+ logger.debug(`[ClaudeLocal] Args: ${JSON.stringify(args)}`);
283
275
  const child = spawn("node", [claudeCliPath, ...args], {
284
276
  stdio: ["inherit", "inherit", "inherit", "pipe"],
285
277
  signal: opts.abort,
@@ -296,13 +288,6 @@ async function claudeLocal(opts) {
296
288
  try {
297
289
  const message = JSON.parse(line);
298
290
  switch (message.type) {
299
- case "uuid":
300
- detectedIdsRandomUUID.add(message.value);
301
- if (!resolvedSessionId && detectedIdsFileSystem.has(message.value)) {
302
- resolvedSessionId = message.value;
303
- opts.onSessionFound(message.value);
304
- }
305
- break;
306
291
  case "fetch-start":
307
292
  activeFetches.set(message.id, {
308
293
  hostname: message.hostname,
@@ -356,7 +341,6 @@ async function claudeLocal(opts) {
356
341
  });
357
342
  });
358
343
  } finally {
359
- watcher.close();
360
344
  process.stdin.resume();
361
345
  if (stopThinkingTimeout) {
362
346
  clearTimeout(stopThinkingTimeout);
@@ -364,7 +348,7 @@ async function claudeLocal(opts) {
364
348
  }
365
349
  updateThinking(false);
366
350
  }
367
- return resolvedSessionId;
351
+ return effectiveSessionId;
368
352
  }
369
353
 
370
354
  class Future {
@@ -460,7 +444,7 @@ function startFileWatcher(file, onFileChange) {
460
444
  while (true) {
461
445
  try {
462
446
  logger.debug(`[FILE_WATCHER] Starting watcher for ${file}`);
463
- const watcher = watch$1(file, { persistent: true, signal: abortController.signal });
447
+ const watcher = watch(file, { persistent: true, signal: abortController.signal });
464
448
  for await (const event of watcher) {
465
449
  if (abortController.signal.aborted) {
466
450
  return;
@@ -482,6 +466,11 @@ function startFileWatcher(file, onFileChange) {
482
466
  };
483
467
  }
484
468
 
469
+ const INTERNAL_CLAUDE_EVENT_TYPES = /* @__PURE__ */ new Set([
470
+ "file-history-snapshot",
471
+ "change",
472
+ "queue-operation"
473
+ ]);
485
474
  async function createSessionScanner(opts) {
486
475
  const projectDir = getProjectPath(opts.workingDirectory);
487
476
  let finishedSessions = /* @__PURE__ */ new Set();
@@ -491,26 +480,42 @@ async function createSessionScanner(opts) {
491
480
  let processedMessageKeys = /* @__PURE__ */ new Set();
492
481
  if (opts.sessionId) {
493
482
  let messages = await readSessionLog(projectDir, opts.sessionId);
483
+ logger.debug(`[SESSION_SCANNER] Marking ${messages.length} existing messages as processed from session ${opts.sessionId}`);
494
484
  for (let m of messages) {
495
485
  processedMessageKeys.add(messageKey(m));
496
486
  }
487
+ currentSessionId = opts.sessionId;
497
488
  }
498
489
  const sync = new InvalidateSync(async () => {
499
490
  let sessions = [];
500
491
  for (let p of pendingSessions) {
501
492
  sessions.push(p);
502
493
  }
503
- if (currentSessionId) {
494
+ if (currentSessionId && !pendingSessions.has(currentSessionId)) {
504
495
  sessions.push(currentSessionId);
505
496
  }
497
+ for (let [sessionId] of watchers) {
498
+ if (!sessions.includes(sessionId)) {
499
+ sessions.push(sessionId);
500
+ }
501
+ }
506
502
  for (let session of sessions) {
507
- for (let file of await readSessionLog(projectDir, session)) {
503
+ const sessionMessages = await readSessionLog(projectDir, session);
504
+ let skipped = 0;
505
+ let sent = 0;
506
+ for (let file of sessionMessages) {
508
507
  let key = messageKey(file);
509
508
  if (processedMessageKeys.has(key)) {
509
+ skipped++;
510
510
  continue;
511
511
  }
512
512
  processedMessageKeys.add(key);
513
+ logger.debug(`[SESSION_SCANNER] Sending new message: type=${file.type}, uuid=${file.type === "summary" ? file.leafUuid : file.uuid}`);
513
514
  opts.onMessage(file);
515
+ sent++;
516
+ }
517
+ if (sessionMessages.length > 0) {
518
+ logger.debug(`[SESSION_SCANNER] Session ${session}: found=${sessionMessages.length}, skipped=${skipped}, sent=${sent}`);
514
519
  }
515
520
  }
516
521
  for (let p of sessions) {
@@ -521,6 +526,7 @@ async function createSessionScanner(opts) {
521
526
  }
522
527
  for (let p of sessions) {
523
528
  if (!watchers.has(p)) {
529
+ logger.debug(`[SESSION_SCANNER] Starting watcher for session: ${p}`);
524
530
  watchers.set(p, startFileWatcher(join(projectDir, `${p}.jsonl`), () => {
525
531
  sync.invalidate();
526
532
  }));
@@ -594,9 +600,11 @@ async function readSessionLog(projectDir, sessionId) {
594
600
  continue;
595
601
  }
596
602
  let message = JSON.parse(l);
603
+ if (message.type && INTERNAL_CLAUDE_EVENT_TYPES.has(message.type)) {
604
+ continue;
605
+ }
597
606
  let parsed = RawJSONLinesSchema.safeParse(message);
598
607
  if (!parsed.success) {
599
- logger.debugLargeJson(`[SESSION_SCANNER] Failed to parse message`, message);
600
608
  continue;
601
609
  }
602
610
  messages.push(parsed.data);
@@ -961,8 +969,92 @@ class AbortError extends Error {
961
969
 
962
970
  const __filename = fileURLToPath(import.meta.url);
963
971
  const __dirname = join(__filename, "..");
972
+ function getGlobalClaudeVersion() {
973
+ try {
974
+ const cleanEnv = getCleanEnv();
975
+ const output = execSync("claude --version", {
976
+ encoding: "utf8",
977
+ stdio: ["pipe", "pipe", "pipe"],
978
+ cwd: homedir(),
979
+ env: cleanEnv
980
+ }).trim();
981
+ const match = output.match(/(\d+\.\d+\.\d+)/);
982
+ logger.debug(`[Claude SDK] Global claude --version output: ${output}`);
983
+ return match ? match[1] : null;
984
+ } catch {
985
+ return null;
986
+ }
987
+ }
988
+ function getCleanEnv() {
989
+ const env = { ...process.env };
990
+ const cwd = process.cwd();
991
+ const pathSep = process.platform === "win32" ? ";" : ":";
992
+ const pathKey = process.platform === "win32" ? "Path" : "PATH";
993
+ const actualPathKey = Object.keys(env).find((k) => k.toLowerCase() === "path") || pathKey;
994
+ if (env[actualPathKey]) {
995
+ const cleanPath = env[actualPathKey].split(pathSep).filter((p) => {
996
+ const normalizedP = p.replace(/\\/g, "/").toLowerCase();
997
+ const normalizedCwd = cwd.replace(/\\/g, "/").toLowerCase();
998
+ return !normalizedP.startsWith(normalizedCwd);
999
+ }).join(pathSep);
1000
+ env[actualPathKey] = cleanPath;
1001
+ logger.debug(`[Claude SDK] Cleaned PATH, removed local paths from: ${cwd}`);
1002
+ }
1003
+ return env;
1004
+ }
1005
+ function findGlobalClaudePath() {
1006
+ const homeDir = homedir();
1007
+ const cleanEnv = getCleanEnv();
1008
+ try {
1009
+ execSync("claude --version", {
1010
+ encoding: "utf8",
1011
+ stdio: ["pipe", "pipe", "pipe"],
1012
+ cwd: homeDir,
1013
+ env: cleanEnv
1014
+ });
1015
+ logger.debug("[Claude SDK] Global claude command available (checked with clean PATH)");
1016
+ return "claude";
1017
+ } catch {
1018
+ }
1019
+ if (process.platform !== "win32") {
1020
+ try {
1021
+ const result = execSync("which claude", {
1022
+ encoding: "utf8",
1023
+ stdio: ["pipe", "pipe", "pipe"],
1024
+ cwd: homeDir,
1025
+ env: cleanEnv
1026
+ }).trim();
1027
+ if (result && existsSync(result)) {
1028
+ logger.debug(`[Claude SDK] Found global claude path via which: ${result}`);
1029
+ return result;
1030
+ }
1031
+ } catch {
1032
+ }
1033
+ }
1034
+ return null;
1035
+ }
964
1036
  function getDefaultClaudeCodePath() {
965
- return join(__dirname, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
1037
+ const nodeModulesPath = join(__dirname, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
1038
+ if (process.env.HAPPY_CLAUDE_PATH) {
1039
+ logger.debug(`[Claude SDK] Using HAPPY_CLAUDE_PATH: ${process.env.HAPPY_CLAUDE_PATH}`);
1040
+ return process.env.HAPPY_CLAUDE_PATH;
1041
+ }
1042
+ if (process.env.HAPPY_USE_BUNDLED_CLAUDE === "1") {
1043
+ logger.debug(`[Claude SDK] Forced bundled version: ${nodeModulesPath}`);
1044
+ return nodeModulesPath;
1045
+ }
1046
+ const globalPath = findGlobalClaudePath();
1047
+ if (!globalPath) {
1048
+ logger.debug(`[Claude SDK] No global claude found, using bundled: ${nodeModulesPath}`);
1049
+ return nodeModulesPath;
1050
+ }
1051
+ const globalVersion = getGlobalClaudeVersion();
1052
+ logger.debug(`[Claude SDK] Global version: ${globalVersion || "unknown"}`);
1053
+ if (!globalVersion) {
1054
+ logger.debug(`[Claude SDK] Cannot compare versions, using global: ${globalPath}`);
1055
+ return globalPath;
1056
+ }
1057
+ return globalPath;
966
1058
  }
967
1059
  function logDebug(message) {
968
1060
  if (process.env.DEBUG) {
@@ -1227,17 +1319,22 @@ function query(config) {
1227
1319
  } else {
1228
1320
  args.push("--input-format", "stream-json");
1229
1321
  }
1230
- if (!existsSync(pathToClaudeCodeExecutable)) {
1322
+ const isJsFile = pathToClaudeCodeExecutable.endsWith(".js") || pathToClaudeCodeExecutable.endsWith(".cjs");
1323
+ const isCommandOnly = pathToClaudeCodeExecutable === "claude";
1324
+ if (!isCommandOnly && !existsSync(pathToClaudeCodeExecutable)) {
1231
1325
  throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`);
1232
1326
  }
1233
- logDebug(`Spawning Claude Code process: ${executable} ${[...executableArgs, pathToClaudeCodeExecutable, ...args].join(" ")}`);
1234
- const child = spawn(executable, [...executableArgs, pathToClaudeCodeExecutable, ...args], {
1327
+ const spawnCommand = isJsFile ? executable : pathToClaudeCodeExecutable;
1328
+ const spawnArgs = isJsFile ? [...executableArgs, pathToClaudeCodeExecutable, ...args] : args;
1329
+ const spawnEnv = isCommandOnly ? getCleanEnv() : process.env;
1330
+ logDebug(`Spawning Claude Code process: ${spawnCommand} ${spawnArgs.join(" ")} (using ${isCommandOnly ? "clean" : "normal"} env)`);
1331
+ const child = spawn(spawnCommand, spawnArgs, {
1235
1332
  cwd,
1236
1333
  stdio: ["pipe", "pipe", "pipe"],
1237
1334
  signal: config.options?.abort,
1238
- env: {
1239
- ...process.env
1240
- }
1335
+ env: spawnEnv,
1336
+ // Use shell on Windows for global binaries and command-only mode
1337
+ shell: !isJsFile && process.platform === "win32"
1241
1338
  });
1242
1339
  let childStdin = null;
1243
1340
  if (typeof prompt === "string") {
@@ -4583,8 +4680,7 @@ async function startHappyServer(client) {
4583
4680
  };
4584
4681
  const mcp = new McpServer({
4585
4682
  name: "Happy MCP",
4586
- version: "1.0.0",
4587
- description: "Happy CLI MCP server with chat session management tools"
4683
+ version: "1.0.0"
4588
4684
  });
4589
4685
  mcp.registerTool("change_title", {
4590
4686
  description: "Change the title of the current chat session",
@@ -5794,7 +5890,7 @@ async function handleConnectVendor(vendor, displayName) {
5794
5890
  return;
5795
5891
  } else if (subcommand === "codex") {
5796
5892
  try {
5797
- const { runCodex } = await import('./runCodex-BJlyTS8v.mjs');
5893
+ const { runCodex } = await import('./runCodex-IZ3YFeAC.mjs');
5798
5894
  let startedBy = void 0;
5799
5895
  for (let i = 1; i < args.length; i++) {
5800
5896
  if (args[i] === "--started-by") {