nextclaw 0.6.9 → 0.6.11

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/cli/index.js +104 -16
  2. package/package.json +2 -2
package/dist/cli/index.js CHANGED
@@ -1788,6 +1788,23 @@ import { resolve as resolve6 } from "path";
1788
1788
  import { getDataDir as getDataDir4 } from "@nextclaw/core";
1789
1789
  var RESTART_SENTINEL_FILENAME = "restart-sentinel.json";
1790
1790
  var PENDING_SYSTEM_EVENTS_KEY = "pending_system_events";
1791
+ var RESTART_REASON_MAX_CHARS = 240;
1792
+ var RESTART_NOTE_MAX_CHARS = 600;
1793
+ var RESTART_OUTBOUND_MAX_CHARS = 1200;
1794
+ function trimTo(value, maxChars) {
1795
+ const text = value.trim();
1796
+ if (!text) {
1797
+ return "";
1798
+ }
1799
+ if (text.length <= maxChars) {
1800
+ return text;
1801
+ }
1802
+ return `${text.slice(0, Math.max(0, maxChars - 1)).trimEnd()}\u2026`;
1803
+ }
1804
+ function normalizeLine(value, maxChars) {
1805
+ const trimmed = trimTo(value, maxChars);
1806
+ return trimmed ? trimmed : null;
1807
+ }
1791
1808
  function resolveRestartSentinelPath() {
1792
1809
  return resolve6(getDataDir4(), "run", RESTART_SENTINEL_FILENAME);
1793
1810
  }
@@ -1822,7 +1839,7 @@ async function consumeRestartSentinel() {
1822
1839
  }
1823
1840
  }
1824
1841
  function summarizeRestartSentinel(payload) {
1825
- const reason = payload.stats?.reason?.trim();
1842
+ const reason = normalizeLine(payload.stats?.reason ?? "", RESTART_REASON_MAX_CHARS);
1826
1843
  if (payload.kind === "update.run") {
1827
1844
  return payload.status === "ok" ? "\u2705 NextClaw update completed and service restarted." : "\u26A0\uFE0F NextClaw update finished with issues.";
1828
1845
  }
@@ -1836,15 +1853,16 @@ function summarizeRestartSentinel(payload) {
1836
1853
  }
1837
1854
  function formatRestartSentinelMessage(payload) {
1838
1855
  const lines = [summarizeRestartSentinel(payload)];
1839
- const note = payload.message?.trim();
1856
+ const note = normalizeLine(payload.message ?? "", RESTART_NOTE_MAX_CHARS);
1840
1857
  if (note) {
1841
1858
  lines.push(note);
1842
1859
  }
1843
- const reason = payload.stats?.reason?.trim();
1860
+ const reason = normalizeLine(payload.stats?.reason ?? "", RESTART_REASON_MAX_CHARS);
1844
1861
  if (reason && !lines.some((line) => line.includes(reason))) {
1845
1862
  lines.push(`Reason: ${reason}`);
1846
1863
  }
1847
- return lines.join("\n");
1864
+ const message = lines.join("\n").trim();
1865
+ return trimTo(message, RESTART_OUTBOUND_MAX_CHARS);
1848
1866
  }
1849
1867
  function parseSessionKey(sessionKey) {
1850
1868
  const value = sessionKey?.trim();
@@ -2481,7 +2499,7 @@ var ServiceCommands = class {
2481
2499
  }
2482
2500
  }
2483
2501
  await reloader.getChannels().startAll();
2484
- await this.wakeFromRestartSentinel({ bus, sessionManager });
2502
+ await this.wakeFromRestartSentinel({ channels: reloader.getChannels(), sessionManager });
2485
2503
  await agent.run();
2486
2504
  } finally {
2487
2505
  await stopPluginChannelGateways(pluginGatewayHandles);
@@ -2495,6 +2513,72 @@ var ServiceCommands = class {
2495
2513
  const trimmed = value.trim();
2496
2514
  return trimmed || void 0;
2497
2515
  }
2516
+ async sleep(ms) {
2517
+ await new Promise((resolve10) => setTimeout(resolve10, ms));
2518
+ }
2519
+ resolveMostRecentRoutableSessionKey(sessionManager) {
2520
+ const sessions = sessionManager.listSessions();
2521
+ let best = null;
2522
+ for (const session of sessions) {
2523
+ const key = this.normalizeOptionalString(session.key);
2524
+ if (!key || key.startsWith("cli:")) {
2525
+ continue;
2526
+ }
2527
+ const metadataRaw = session.metadata;
2528
+ const metadata = metadataRaw && typeof metadataRaw === "object" && !Array.isArray(metadataRaw) ? metadataRaw : {};
2529
+ const contextRaw = metadata.last_delivery_context;
2530
+ const context = contextRaw && typeof contextRaw === "object" && !Array.isArray(contextRaw) ? contextRaw : {};
2531
+ const hasRoute = Boolean(this.normalizeOptionalString(context.channel)) && Boolean(this.normalizeOptionalString(context.chatId));
2532
+ const hasFallbackRoute = Boolean(this.normalizeOptionalString(metadata.last_channel)) && Boolean(this.normalizeOptionalString(metadata.last_to));
2533
+ if (!hasRoute && !hasFallbackRoute) {
2534
+ continue;
2535
+ }
2536
+ const updatedAtRaw = this.normalizeOptionalString(session.updated_at);
2537
+ const updatedAt = updatedAtRaw ? Date.parse(updatedAtRaw) : Number.NaN;
2538
+ const score = Number.isFinite(updatedAt) ? updatedAt : 0;
2539
+ if (!best || score >= best.updatedAt) {
2540
+ best = { key, updatedAt: score };
2541
+ }
2542
+ }
2543
+ return best?.key;
2544
+ }
2545
+ async sendRestartSentinelNotice(params) {
2546
+ const outboundBase = {
2547
+ channel: params.channel,
2548
+ chatId: params.chatId,
2549
+ content: params.content,
2550
+ media: [],
2551
+ metadata: params.metadata
2552
+ };
2553
+ const variants = params.replyTo ? [
2554
+ { ...outboundBase, replyTo: params.replyTo },
2555
+ { ...outboundBase }
2556
+ ] : [{ ...outboundBase }];
2557
+ for (let variantIndex = 0; variantIndex < variants.length; variantIndex += 1) {
2558
+ const outbound = variants[variantIndex];
2559
+ const isLastVariant = variantIndex === variants.length - 1;
2560
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
2561
+ try {
2562
+ const delivered = await params.channels.deliver(outbound);
2563
+ if (delivered) {
2564
+ return true;
2565
+ }
2566
+ return false;
2567
+ } catch (error) {
2568
+ if (attempt < 3) {
2569
+ await this.sleep(attempt * 500);
2570
+ continue;
2571
+ }
2572
+ if (isLastVariant) {
2573
+ console.warn(
2574
+ `Warning: restart sentinel notify failed for ${params.channel}:${params.chatId} (attempt ${attempt}): ${String(error)}`
2575
+ );
2576
+ }
2577
+ }
2578
+ }
2579
+ }
2580
+ return false;
2581
+ }
2498
2582
  async wakeFromRestartSentinel(params) {
2499
2583
  const sentinel = await consumeRestartSentinel();
2500
2584
  if (!sentinel) {
@@ -2503,7 +2587,12 @@ var ServiceCommands = class {
2503
2587
  await new Promise((resolve10) => setTimeout(resolve10, 750));
2504
2588
  const payload = sentinel.payload;
2505
2589
  const message = formatRestartSentinelMessage(payload);
2506
- const sessionKey = this.normalizeOptionalString(payload.sessionKey) ?? "cli:default";
2590
+ const sentinelSessionKey = this.normalizeOptionalString(payload.sessionKey);
2591
+ const fallbackSessionKey = sentinelSessionKey ? void 0 : this.resolveMostRecentRoutableSessionKey(params.sessionManager);
2592
+ if (!sentinelSessionKey && fallbackSessionKey) {
2593
+ console.warn(`Warning: restart sentinel missing sessionKey; fallback to ${fallbackSessionKey}.`);
2594
+ }
2595
+ const sessionKey = sentinelSessionKey ?? fallbackSessionKey ?? "cli:default";
2507
2596
  const parsedSession = parseSessionKey(sessionKey);
2508
2597
  const context = payload.deliveryContext;
2509
2598
  const channel = this.normalizeOptionalString(context?.channel) ?? parsedSession?.channel ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_channel);
@@ -2519,16 +2608,15 @@ var ServiceCommands = class {
2519
2608
  enqueuePendingSystemEvent(params.sessionManager, sessionKey, message);
2520
2609
  return;
2521
2610
  }
2522
- try {
2523
- await params.bus.publishOutbound({
2524
- channel,
2525
- chatId,
2526
- content: message,
2527
- ...replyTo ? { replyTo } : {},
2528
- media: [],
2529
- metadata
2530
- });
2531
- } catch {
2611
+ const delivered = await this.sendRestartSentinelNotice({
2612
+ channels: params.channels,
2613
+ channel,
2614
+ chatId,
2615
+ content: message,
2616
+ ...replyTo ? { replyTo } : {},
2617
+ metadata
2618
+ });
2619
+ if (!delivered) {
2532
2620
  enqueuePendingSystemEvent(params.sessionManager, sessionKey, message);
2533
2621
  }
2534
2622
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.6.9",
3
+ "version": "0.6.11",
4
4
  "description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -38,7 +38,7 @@
38
38
  "dependencies": {
39
39
  "chokidar": "^3.6.0",
40
40
  "commander": "^12.1.0",
41
- "@nextclaw/core": "^0.6.8",
41
+ "@nextclaw/core": "^0.6.9",
42
42
  "@nextclaw/server": "^0.4.2",
43
43
  "@nextclaw/openclaw-compat": "^0.1.5"
44
44
  },