@syengup/friday-channel-next 0.1.16 → 0.1.18

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.
@@ -23,12 +23,15 @@ import { fridayAttachmentLookupKey, fridayFilesPublicUrl, readFile, resolveMedia
23
23
  import { runFridayDispatch } from "../../agent/dispatch-bridge.js";
24
24
  import { saveInboundMediaBuffer } from "../../agent/media-bridge.js";
25
25
  import { contextTokensFromUsageRecord, getRunMetadata, getRunRoute, hasRunFinalDelivered, markRunFinalDelivered, registerRunRoute, setRunMetadata, } from "../../run-metadata.js";
26
- import { createFridayNextLogger } from "../../logging.js";
26
+ import { createFridayNextLogger, setFridayNextLogLevel } from "../../logging.js";
27
27
  const logger = createFridayNextLogger("messages");
28
- const log = (action, deviceId, runId, detail) => {
28
+ // Routine per-message / per-stream lifecycle events log at "debug" so they stay out of
29
+ // the default ("info") OpenClaw log; only genuine problems (rejections, run errors) surface.
30
+ // Raise the friday-next channel logLevel to "debug" to see the full per-message trace.
31
+ const log = (action, deviceId, runId, detail, level = "debug") => {
29
32
  const runPart = runId ? ` runId=${runId}` : "";
30
33
  const detailPart = detail ? ` detail=${detail}` : "";
31
- logger.info(`[${action}] deviceId=${deviceId}${runPart}${detailPart}`);
34
+ logger[level](`[${action}] deviceId=${deviceId}${runPart}${detailPart}`);
32
35
  };
33
36
  function collectReplyPayloadMediaUrls(pl) {
34
37
  const fromArr = Array.isArray(pl.mediaUrls)
@@ -285,7 +288,7 @@ export async function handleMessages(req, res) {
285
288
  }
286
289
  const token = extractBearerToken(req);
287
290
  if (!token) {
288
- log("AUTH_FAILED", "(unknown)", undefined, "missing or invalid token");
291
+ log("AUTH_FAILED", "(unknown)", undefined, "missing or invalid token", "warn");
289
292
  res.statusCode = 401;
290
293
  res.setHeader("Content-Type", "application/json");
291
294
  res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
@@ -293,7 +296,7 @@ export async function handleMessages(req, res) {
293
296
  }
294
297
  const payload = (await readJsonBody(req));
295
298
  if (!payload) {
296
- log("BAD_REQUEST", "(unknown)", undefined, "invalid JSON body");
299
+ log("BAD_REQUEST", "(unknown)", undefined, "invalid JSON body", "warn");
297
300
  res.statusCode = 400;
298
301
  res.setHeader("Content-Type", "application/json");
299
302
  res.end(JSON.stringify({ error: "Invalid JSON body" }));
@@ -302,7 +305,7 @@ export async function handleMessages(req, res) {
302
305
  const { deviceId, text, attachments = [], sessionKey: rawSessionKey } = payload;
303
306
  const normalizedDeviceId = deviceId?.trim().toUpperCase();
304
307
  if (typeof rawSessionKey !== "string" || !rawSessionKey.length) {
305
- log("BAD_REQUEST", "(unknown)", undefined, "missing sessionKey");
308
+ log("BAD_REQUEST", "(unknown)", undefined, "missing sessionKey", "warn");
306
309
  res.statusCode = 400;
307
310
  res.setHeader("Content-Type", "application/json");
308
311
  res.end(JSON.stringify({ error: "Missing required field: sessionKey" }));
@@ -311,14 +314,14 @@ export async function handleMessages(req, res) {
311
314
  const appSessionKey = rawSessionKey.trim();
312
315
  const baseSessionKey = toSessionStoreKey(appSessionKey);
313
316
  if (!normalizedDeviceId) {
314
- log("BAD_REQUEST", "(unknown)", undefined, "missing deviceId");
317
+ log("BAD_REQUEST", "(unknown)", undefined, "missing deviceId", "warn");
315
318
  res.statusCode = 400;
316
319
  res.setHeader("Content-Type", "application/json");
317
320
  res.end(JSON.stringify({ error: "Missing required field: deviceId" }));
318
321
  return true;
319
322
  }
320
323
  if (!text || !text.trim()) {
321
- log("BAD_REQUEST", normalizedDeviceId, undefined, "missing text");
324
+ log("BAD_REQUEST", normalizedDeviceId, undefined, "missing text", "warn");
322
325
  res.statusCode = 400;
323
326
  res.setHeader("Content-Type", "application/json");
324
327
  res.end(JSON.stringify({ error: "Missing required field: text" }));
@@ -334,6 +337,7 @@ export async function handleMessages(req, res) {
334
337
  res.end(JSON.stringify({ accepted: true, deviceId: normalizedDeviceId, runId }));
335
338
  log("MESSAGE_RECEIVED", normalizedDeviceId, runId, `textLen=${trimmedText.length} attachments=${attachments.length} sessionKey=${baseSessionKey}`);
336
339
  const cfg = resolveFridayNextConfig(getHostOpenClawConfigSnapshot(runtime.config));
340
+ setFridayNextLogLevel(cfg.logLevel);
337
341
  // Resolve defaults from the OpenClaw agent config so settings are never left empty. Prefers the
338
342
  // target agent's own model/thinking over the global defaults (see resolveAgentDefaults).
339
343
  const { model: defaultModel, thinking: defaultThinking } = resolveAgentDefaults(baseSessionKey);
@@ -415,7 +419,7 @@ export async function handleMessages(req, res) {
415
419
  }
416
420
  },
417
421
  onError: (err) => {
418
- log("RUN_ERROR", normalizedDeviceId, runId, String(err));
422
+ log("RUN_ERROR", normalizedDeviceId, runId, String(err), "error");
419
423
  sseEmitter.broadcastToRun(runId, {
420
424
  type: "outbound",
421
425
  data: {
@@ -455,7 +459,7 @@ export async function handleMessages(req, res) {
455
459
  log("RUN_COMPLETE", normalizedDeviceId, runId);
456
460
  }
457
461
  catch (err) {
458
- log("RUN_ERROR", normalizedDeviceId, runId, String(err));
462
+ log("RUN_ERROR", normalizedDeviceId, runId, String(err), "error");
459
463
  sseEmitter.broadcastToRun(runId, {
460
464
  type: "outbound",
461
465
  data: {
@@ -473,7 +477,7 @@ export async function handleMessages(req, res) {
473
477
  }
474
478
  };
475
479
  runAgent().catch((err) => {
476
- log("RUN_ERROR", normalizedDeviceId, runId, String(err));
480
+ log("RUN_ERROR", normalizedDeviceId, runId, String(err), "error");
477
481
  sseEmitter.untrackRun(runId);
478
482
  });
479
483
  return true;
@@ -1,5 +1,6 @@
1
1
  import type { FridayNextLogLevel } from "./config.js";
2
- export declare function createFridayNextLogger(scope: string, level?: FridayNextLogLevel): {
2
+ export declare function setFridayNextLogLevel(level: FridayNextLogLevel): void;
3
+ export declare function createFridayNextLogger(scope: string, _level?: FridayNextLogLevel): {
3
4
  debug: (message: string) => void;
4
5
  info: (message: string) => void;
5
6
  warn: (message: string) => void;
@@ -4,9 +4,18 @@ const levelOrder = {
4
4
  warn: 30,
5
5
  error: 40,
6
6
  };
7
- export function createFridayNextLogger(scope, level = "info") {
7
+ // Process-wide active level, honored by every logger at call time. Handlers call
8
+ // setFridayNextLogLevel() once the friday-next channel config resolves, so
9
+ // `channels.friday-next.logLevel: "debug"` turns the full per-message trace back on.
10
+ let activeLevel = "info";
11
+ export function setFridayNextLogLevel(level) {
12
+ activeLevel = level;
13
+ }
14
+ // The optional `level` arg is accepted for call-site compatibility but the effective
15
+ // threshold is the process-wide active level (single knob driven by config.logLevel).
16
+ export function createFridayNextLogger(scope, _level) {
8
17
  const base = `[friday-next:${scope}]`;
9
- const enabled = (current) => levelOrder[current] >= levelOrder[level];
18
+ const enabled = (current) => levelOrder[current] >= levelOrder[activeLevel];
10
19
  return {
11
20
  debug: (message) => {
12
21
  if (enabled("debug"))
package/install.js CHANGED
@@ -385,18 +385,24 @@ if (ipType === "tailscale") {
385
385
  log("This URL appears to be publicly accessible (" + ip + ").");
386
386
  }
387
387
 
388
- // On a cloud server the advertised IP is the internal/NAT address, which a phone
389
- // over the internet can't reach. Best-effort: detect the public IP so the user
390
- // has the address to actually connect with (and a reminder to open the port).
388
+ // The advertised IP is the local/NAT address, which a phone over the internet
389
+ // can't reach. Best-effort: detect the network's public-facing IP. NOTE: an echo
390
+ // service only reports the egress address on a cloud VPS that's a routable 1:1
391
+ // NAT, but on a home/carrier-grade NAT (CGNAT) it's a shared egress IP that is
392
+ // NOT reachable inbound. So we present it as a hint, not a guarantee.
391
393
  if (ipType === "private" || ipType === "loopback") {
392
394
  const publicIp = await detectPublicIp();
393
395
  if (publicIp && publicIp !== ip) {
394
396
  log("");
395
- log(BOLD_YELLOW("Looks like a cloud server. For remote access use the PUBLIC address:"));
396
- log(BOLD_YELLOW("检测到云服务器,远程连接请改用公网地址:"));
397
+ log(BOLD_YELLOW("Network public-facing IP detected for remote access try:"));
398
+ log(BOLD_YELLOW("检测到网络出口公网 IP —— 远程连接可尝试:"));
397
399
  log("Public URL: " + BOLD_YELLOW(`http://${publicIp}:${gatewayPort}`));
398
- log("(Open inbound TCP port " + gatewayPort + " in your firewall / security group first.)");
399
- log("(需先在防火墙/安全组放行入站 TCP 端口 " + gatewayPort + "。)");
400
+ log("First open inbound TCP port " + gatewayPort + " in your firewall / cloud security group.");
401
+ log("请先在防火墙 / 云安全组放行入站 TCP 端口 " + gatewayPort + "。");
402
+ log("If this is a home/carrier network (CGNAT), this IP may NOT be reachable from outside —");
403
+ log("use a tunnel like Tailscale instead.");
404
+ log("若为家庭宽带 / 运营商大内网(CGNAT),此地址可能无法从外部入站访问,");
405
+ log("请改用 Tailscale 等内网穿透方案。");
400
406
  }
401
407
  }
402
408
  log("--------------------------------------------------");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syengup/friday-channel-next",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "OpenClaw Friday Next Apple channel plugin",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -57,14 +57,23 @@ import {
57
57
  registerRunRoute,
58
58
  setRunMetadata,
59
59
  } from "../../run-metadata.js";
60
- import { createFridayNextLogger } from "../../logging.js";
60
+ import { createFridayNextLogger, setFridayNextLogLevel } from "../../logging.js";
61
61
 
62
62
  const logger = createFridayNextLogger("messages");
63
63
 
64
- const log = (action: string, deviceId: string, runId?: string, detail?: string) => {
64
+ // Routine per-message / per-stream lifecycle events log at "debug" so they stay out of
65
+ // the default ("info") OpenClaw log; only genuine problems (rejections, run errors) surface.
66
+ // Raise the friday-next channel logLevel to "debug" to see the full per-message trace.
67
+ const log = (
68
+ action: string,
69
+ deviceId: string,
70
+ runId?: string,
71
+ detail?: string,
72
+ level: "debug" | "info" | "warn" | "error" = "debug",
73
+ ) => {
65
74
  const runPart = runId ? ` runId=${runId}` : "";
66
75
  const detailPart = detail ? ` detail=${detail}` : "";
67
- logger.info(`[${action}] deviceId=${deviceId}${runPart}${detailPart}`);
76
+ logger[level](`[${action}] deviceId=${deviceId}${runPart}${detailPart}`);
68
77
  };
69
78
 
70
79
  function collectReplyPayloadMediaUrls(pl: { mediaUrls?: string[]; mediaUrl?: string | null }): string[] {
@@ -381,7 +390,7 @@ export async function handleMessages(req: IncomingMessage, res: ServerResponse):
381
390
 
382
391
  const token = extractBearerToken(req);
383
392
  if (!token) {
384
- log("AUTH_FAILED", "(unknown)", undefined, "missing or invalid token");
393
+ log("AUTH_FAILED", "(unknown)", undefined, "missing or invalid token", "warn");
385
394
  res.statusCode = 401;
386
395
  res.setHeader("Content-Type", "application/json");
387
396
  res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
@@ -390,7 +399,7 @@ export async function handleMessages(req: IncomingMessage, res: ServerResponse):
390
399
 
391
400
  const payload = (await readJsonBody(req)) as FridayMessagePayload | null;
392
401
  if (!payload) {
393
- log("BAD_REQUEST", "(unknown)", undefined, "invalid JSON body");
402
+ log("BAD_REQUEST", "(unknown)", undefined, "invalid JSON body", "warn");
394
403
  res.statusCode = 400;
395
404
  res.setHeader("Content-Type", "application/json");
396
405
  res.end(JSON.stringify({ error: "Invalid JSON body" }));
@@ -401,7 +410,7 @@ export async function handleMessages(req: IncomingMessage, res: ServerResponse):
401
410
  const normalizedDeviceId = deviceId?.trim().toUpperCase();
402
411
 
403
412
  if (typeof rawSessionKey !== "string" || !rawSessionKey.length) {
404
- log("BAD_REQUEST", "(unknown)", undefined, "missing sessionKey");
413
+ log("BAD_REQUEST", "(unknown)", undefined, "missing sessionKey", "warn");
405
414
  res.statusCode = 400;
406
415
  res.setHeader("Content-Type", "application/json");
407
416
  res.end(JSON.stringify({ error: "Missing required field: sessionKey" }));
@@ -412,7 +421,7 @@ export async function handleMessages(req: IncomingMessage, res: ServerResponse):
412
421
  const baseSessionKey = toSessionStoreKey(appSessionKey);
413
422
 
414
423
  if (!normalizedDeviceId) {
415
- log("BAD_REQUEST", "(unknown)", undefined, "missing deviceId");
424
+ log("BAD_REQUEST", "(unknown)", undefined, "missing deviceId", "warn");
416
425
  res.statusCode = 400;
417
426
  res.setHeader("Content-Type", "application/json");
418
427
  res.end(JSON.stringify({ error: "Missing required field: deviceId" }));
@@ -420,7 +429,7 @@ export async function handleMessages(req: IncomingMessage, res: ServerResponse):
420
429
  }
421
430
 
422
431
  if (!text || !text.trim()) {
423
- log("BAD_REQUEST", normalizedDeviceId, undefined, "missing text");
432
+ log("BAD_REQUEST", normalizedDeviceId, undefined, "missing text", "warn");
424
433
  res.statusCode = 400;
425
434
  res.setHeader("Content-Type", "application/json");
426
435
  res.end(JSON.stringify({ error: "Missing required field: text" }));
@@ -447,6 +456,7 @@ export async function handleMessages(req: IncomingMessage, res: ServerResponse):
447
456
  );
448
457
 
449
458
  const cfg = resolveFridayNextConfig(getHostOpenClawConfigSnapshot(runtime.config));
459
+ setFridayNextLogLevel(cfg.logLevel);
450
460
 
451
461
  // Resolve defaults from the OpenClaw agent config so settings are never left empty. Prefers the
452
462
  // target agent's own model/thinking over the global defaults (see resolveAgentDefaults).
@@ -544,7 +554,7 @@ export async function handleMessages(req: IncomingMessage, res: ServerResponse):
544
554
  }
545
555
  },
546
556
  onError: (err: unknown) => {
547
- log("RUN_ERROR", normalizedDeviceId, runId, String(err));
557
+ log("RUN_ERROR", normalizedDeviceId, runId, String(err), "error");
548
558
  sseEmitter.broadcastToRun(
549
559
  runId,
550
560
  {
@@ -588,7 +598,7 @@ export async function handleMessages(req: IncomingMessage, res: ServerResponse):
588
598
  });
589
599
  log("RUN_COMPLETE", normalizedDeviceId, runId);
590
600
  } catch (err) {
591
- log("RUN_ERROR", normalizedDeviceId, runId, String(err));
601
+ log("RUN_ERROR", normalizedDeviceId, runId, String(err), "error");
592
602
  sseEmitter.broadcastToRun(
593
603
  runId,
594
604
  {
@@ -610,7 +620,7 @@ export async function handleMessages(req: IncomingMessage, res: ServerResponse):
610
620
  };
611
621
 
612
622
  runAgent().catch((err) => {
613
- log("RUN_ERROR", normalizedDeviceId, runId, String(err));
623
+ log("RUN_ERROR", normalizedDeviceId, runId, String(err), "error");
614
624
  sseEmitter.untrackRun(runId);
615
625
  });
616
626
 
package/src/logging.ts CHANGED
@@ -7,9 +7,20 @@ const levelOrder: Record<FridayNextLogLevel, number> = {
7
7
  error: 40,
8
8
  };
9
9
 
10
- export function createFridayNextLogger(scope: string, level: FridayNextLogLevel = "info") {
10
+ // Process-wide active level, honored by every logger at call time. Handlers call
11
+ // setFridayNextLogLevel() once the friday-next channel config resolves, so
12
+ // `channels.friday-next.logLevel: "debug"` turns the full per-message trace back on.
13
+ let activeLevel: FridayNextLogLevel = "info";
14
+
15
+ export function setFridayNextLogLevel(level: FridayNextLogLevel): void {
16
+ activeLevel = level;
17
+ }
18
+
19
+ // The optional `level` arg is accepted for call-site compatibility but the effective
20
+ // threshold is the process-wide active level (single knob driven by config.logLevel).
21
+ export function createFridayNextLogger(scope: string, _level?: FridayNextLogLevel) {
11
22
  const base = `[friday-next:${scope}]`;
12
- const enabled = (current: FridayNextLogLevel) => levelOrder[current] >= levelOrder[level];
23
+ const enabled = (current: FridayNextLogLevel) => levelOrder[current] >= levelOrder[activeLevel];
13
24
  return {
14
25
  debug: (message: string) => {
15
26
  if (enabled("debug")) console.debug(`${base} ${message}`);