@cryptiklemur/lattice 5.12.8 → 5.13.1

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.
@@ -15,7 +15,7 @@ import { startDiscovery } from "./mesh/discovery.js";
15
15
  import { startMeshConnections, onPeerConnected, onPeerDisconnected, onPeerMessage, getAllRemoteProjects } from "./mesh/connector.js";
16
16
  import { handleProxyRequest, handleProxyResponse } from "./mesh/proxy.js";
17
17
  import { verifyPassphrase, generateSessionToken, addSession, isValidSession } from "./auth/passphrase.js";
18
- import { log } from "./logger.js";
18
+ import { log, initFileLogger } from "./logger.js";
19
19
  import { detectIdeProjectName } from "./handlers/settings.js";
20
20
  import "./handlers/session.js";
21
21
  import "./handlers/chat.js";
@@ -326,6 +326,7 @@ export async function startDaemon(portOverride, tlsOverride) {
326
326
  if (tlsOverride !== null && tlsOverride !== undefined) {
327
327
  config.tls = tlsOverride;
328
328
  }
329
+ initFileLogger(getLatticeHome());
329
330
  const identity = loadOrCreateIdentity();
330
331
  log.server("Node: %s (%s)", config.name, identity.id);
331
332
  log.server("Home: %s", getLatticeHome());
@@ -1,21 +1,50 @@
1
1
  import createDebug from "debug";
2
+ import { createWriteStream, existsSync, mkdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { format } from "node:util";
5
+ let fileStream = null;
6
+ export function initFileLogger(latticeHome) {
7
+ const logsDir = join(latticeHome, "logs");
8
+ if (!existsSync(logsDir)) {
9
+ mkdirSync(logsDir, { recursive: true });
10
+ }
11
+ const logPath = join(logsDir, "debug.log");
12
+ fileStream = createWriteStream(logPath, { flags: "a" });
13
+ fileStream.on("error", function () {
14
+ fileStream = null;
15
+ });
16
+ }
17
+ function writeToFile(namespace, fmt, ...args) {
18
+ if (!fileStream)
19
+ return;
20
+ const timestamp = new Date().toISOString();
21
+ const message = format(fmt, ...args);
22
+ fileStream.write(timestamp + " " + namespace + " " + message + "\n");
23
+ }
24
+ function createLogger(namespace) {
25
+ const debugInstance = createDebug(namespace);
26
+ return function (fmt, ...args) {
27
+ writeToFile(namespace, fmt, ...args);
28
+ debugInstance(fmt, ...args);
29
+ };
30
+ }
2
31
  export const log = {
3
- server: createDebug("lattice:server"),
4
- ws: createDebug("lattice:ws"),
5
- chat: createDebug("lattice:chat"),
6
- session: createDebug("lattice:session"),
7
- mesh: createDebug("lattice:mesh"),
8
- meshConnect: createDebug("lattice:mesh:connect"),
9
- meshHello: createDebug("lattice:mesh:hello"),
10
- meshProxy: createDebug("lattice:mesh:proxy"),
11
- router: createDebug("lattice:router"),
12
- broadcast: createDebug("lattice:broadcast"),
13
- auth: createDebug("lattice:auth"),
14
- fs: createDebug("lattice:fs"),
15
- analytics: createDebug("lattice:analytics"),
16
- plugins: createDebug("lattice:plugins"),
17
- update: createDebug("lattice:update"),
18
- terminal: createDebug("lattice:terminal"),
19
- settings: createDebug("lattice:settings"),
20
- superpowers: createDebug("lattice:superpowers"),
32
+ server: createLogger("lattice:server"),
33
+ ws: createLogger("lattice:ws"),
34
+ chat: createLogger("lattice:chat"),
35
+ session: createLogger("lattice:session"),
36
+ mesh: createLogger("lattice:mesh"),
37
+ meshConnect: createLogger("lattice:mesh:connect"),
38
+ meshHello: createLogger("lattice:mesh:hello"),
39
+ meshProxy: createLogger("lattice:mesh:proxy"),
40
+ router: createLogger("lattice:router"),
41
+ broadcast: createLogger("lattice:broadcast"),
42
+ auth: createLogger("lattice:auth"),
43
+ fs: createLogger("lattice:fs"),
44
+ analytics: createLogger("lattice:analytics"),
45
+ plugins: createLogger("lattice:plugins"),
46
+ update: createLogger("lattice:update"),
47
+ terminal: createLogger("lattice:terminal"),
48
+ settings: createLogger("lattice:settings"),
49
+ superpowers: createLogger("lattice:superpowers"),
21
50
  };
@@ -376,9 +376,11 @@ function buildSDKUserMessage(prompt, attachments, sessionId) {
376
376
  }
377
377
  function pushToExistingStream(session, options) {
378
378
  const { text, attachments, clientId, sessionId, model } = options;
379
+ log.chat("Session %s pushing to existing stream (ended=%s)", sessionId, String(session.ended));
379
380
  session.clientId = clientId;
380
381
  session.turnStartTime = Date.now();
381
382
  session.turnDoneSent = false;
383
+ session.sawNewTurnContent = false;
382
384
  session.activeToolBlocks = {};
383
385
  const prompt = resolvePromptText(text);
384
386
  const userMsg = buildSDKUserMessage(prompt, attachments, sessionId);
@@ -398,6 +400,7 @@ function pushToExistingStream(session, options) {
398
400
  }
399
401
  export function startChatStream(options) {
400
402
  const { projectSlug, sessionId, text, attachments, clientId, cwd, env, model, effort, isNewSession } = options;
403
+ log.chat("startChatStream called: session=%s project=%s client=%s", sessionId, projectSlug, clientId);
401
404
  const existing = sessionStreams.get(sessionId);
402
405
  if (existing && !existing.ended) {
403
406
  pushToExistingStream(existing, options);
@@ -470,9 +473,7 @@ export function startChatStream(options) {
470
473
  additionalDirectories: savedAdditionalDirs.length > 0 ? savedAdditionalDirs : undefined,
471
474
  mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
472
475
  stderr: function (data) {
473
- if (data.includes("error") || data.includes("Error") || data.includes("credit") || data.includes("Credit") || data.includes("billing") || data.includes("auth")) {
474
- log.chat("SDK stderr: %s", data.trim());
475
- }
476
+ log.chat("Session %s SDK stderr: %s", sessionId, data.trim());
476
477
  },
477
478
  };
478
479
  queryOptions.toolConfig = {
@@ -675,6 +676,7 @@ export function startChatStream(options) {
675
676
  ended: false,
676
677
  accumulatedText: "",
677
678
  specId: options.specId,
679
+ sawNewTurnContent: false,
678
680
  analyzer: new ContextAnalyzer(function (msg) {
679
681
  const ss = sessionStreams.get(sessionId);
680
682
  if (ss)
@@ -685,25 +687,48 @@ export function startChatStream(options) {
685
687
  persistStreamState();
686
688
  broadcast({ type: "session:busy", sessionId, busy: true }, clientId);
687
689
  void (async function () {
690
+ log.chat("Session %s stream starting (resume=%s, model=%s)", sessionId, String(shouldResume), model || "default");
688
691
  try {
689
692
  await stream.initializationResult();
693
+ log.chat("Session %s SDK initialized", sessionId);
690
694
  }
691
695
  catch (initErr) {
692
- log.chat("Session %s SDK initialization warning: %O", sessionId, initErr);
696
+ log.chat("Session %s SDK initialization FAILED: %O", sessionId, initErr);
697
+ }
698
+ if (!shouldResume) {
699
+ log.chat("Session %s pushing first message to queue", sessionId);
700
+ mq.push(firstMsg);
693
701
  }
694
- mq.push(firstMsg);
695
702
  try {
703
+ log.chat("Session %s entering stream loop", sessionId);
704
+ let msgCount = 0;
705
+ let replayDone = !shouldResume;
696
706
  for await (const msg of stream) {
707
+ msgCount++;
708
+ if (msgCount <= 5 || msg.type === "result") {
709
+ log.chat("Session %s msg #%d type=%s", sessionId, msgCount, msg.type);
710
+ }
711
+ if (!replayDone && msg.type === "result") {
712
+ replayDone = true;
713
+ log.chat("Session %s replay complete at msg #%d, waiting for connection settle", sessionId, msgCount);
714
+ await new Promise(function (resolve) { setTimeout(resolve, 200); });
715
+ log.chat("Session %s pushing first message after replay", sessionId);
716
+ mq.push(firstMsg);
717
+ continue;
718
+ }
697
719
  processMessage(sessionStream, msg);
698
720
  }
721
+ log.chat("Session %s stream ended normally after %d messages", sessionId, msgCount);
699
722
  }
700
723
  catch (err) {
701
724
  const errMsg = err instanceof Error ? err.message : String(err);
725
+ log.chat("Session %s stream error: %s", sessionId, errMsg);
702
726
  if (errMsg.includes("aborted") || errMsg.includes("AbortError")) {
703
727
  log.chat("Session %s stream aborted", sessionId);
704
728
  }
705
729
  else if (errMsg.includes("Sent before connected")) {
706
730
  log.chat("Session %s SDK WebSocket race condition: %s", sessionId, errMsg);
731
+ sendTo(sessionStream.clientId, { type: "chat:error", message: "Connection failed. Please try again." });
707
732
  }
708
733
  else {
709
734
  console.error("[lattice] SDK stream error: " + errMsg);
@@ -811,6 +836,7 @@ function processMessage(ss, msg) {
811
836
  const partial = msg;
812
837
  const evt = partial.event;
813
838
  if (evt.type === "content_block_start") {
839
+ ss.sawNewTurnContent = true;
814
840
  const block = evt.content_block;
815
841
  const idx = evt.index;
816
842
  if (block.type === "tool_use" && block.id && block.name) {
@@ -941,6 +967,10 @@ function processMessage(ss, msg) {
941
967
  return;
942
968
  }
943
969
  if (msg.type === "result") {
970
+ if (!ss.sawNewTurnContent) {
971
+ log.chat("Session %s ignoring replayed result (no new turn content seen)", sessionId);
972
+ return;
973
+ }
944
974
  const resultMsg = msg;
945
975
  const dur = Date.now() - ss.turnStartTime;
946
976
  const cost = resultMsg.total_cost_usd || 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "5.12.8",
3
+ "version": "5.13.1",
4
4
  "description": "Multi-machine agentic dashboard for Claude Code. Monitor sessions, manage MCP servers and skills, orchestrate across mesh-networked nodes.",
5
5
  "license": "MIT",
6
6
  "author": "Aaron Scherer <me@aaronscherer.me>",