@rubytech/taskmaster 1.22.0 → 1.22.2

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.
@@ -6,7 +6,7 @@
6
6
  <title>Taskmaster Control</title>
7
7
  <meta name="color-scheme" content="dark light" />
8
8
  <link rel="icon" type="image/png" href="./favicon.png" />
9
- <script type="module" crossorigin src="./assets/index-BErknDpG.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-CT2I2Boa.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-C7ieCeTV.css">
11
11
  </head>
12
12
  <body>
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import fs from "node:fs";
3
+ import os from "node:os";
3
4
  import path from "node:path";
4
5
  import { Logger as TsLogger } from "tslog";
5
6
  import { levelToMinLevel, normalizeLogLevel } from "./levels.js";
@@ -66,8 +67,34 @@ export function isFileLogLevelEnabled(level) {
66
67
  return false;
67
68
  return levelToMinLevel(level) <= levelToMinLevel(settings.level);
68
69
  }
70
+ function ensureWritableLogDir(dir) {
71
+ fs.mkdirSync(dir, { recursive: true });
72
+ try {
73
+ fs.accessSync(dir, fs.constants.W_OK);
74
+ return dir;
75
+ }
76
+ catch {
77
+ // Directory exists but is not writable (e.g. created by root, daemon runs as non-root).
78
+ // Try to fix permissions.
79
+ try {
80
+ fs.chmodSync(dir, 0o1777);
81
+ fs.accessSync(dir, fs.constants.W_OK);
82
+ return dir;
83
+ }
84
+ catch {
85
+ // chmod also failed — fall back to ~/.taskmaster/logs/
86
+ const fallback = path.join(os.homedir(), ".taskmaster", "logs");
87
+ fs.mkdirSync(fallback, { recursive: true });
88
+ process.stderr.write(`[taskmaster] log dir ${dir} is not writable, falling back to ${fallback}\n`);
89
+ return fallback;
90
+ }
91
+ }
92
+ }
69
93
  function buildLogger(settings) {
70
- fs.mkdirSync(path.dirname(settings.file), { recursive: true });
94
+ const logDir = ensureWritableLogDir(path.dirname(settings.file));
95
+ if (logDir !== path.dirname(settings.file)) {
96
+ settings = { ...settings, file: path.join(logDir, path.basename(settings.file)) };
97
+ }
71
98
  // Clean up stale rolling logs when using a dated log filename.
72
99
  if (isRollingPath(settings.file)) {
73
100
  pruneOldRollingLogs(path.dirname(settings.file));
@@ -81,9 +108,10 @@ function buildLogger(settings) {
81
108
  // rollover targets today's file — even for child loggers cached by
82
109
  // subsystem loggers that outlive a parent logger rebuild.
83
110
  const fixedFile = isRollingPath(settings.file) ? null : settings.file;
111
+ const resolvedLogDir = path.dirname(settings.file);
84
112
  logger.attachTransport((logObj) => {
85
113
  try {
86
- const file = fixedFile ?? defaultRollingPathForToday();
114
+ const file = fixedFile ?? rollingPathForToday(resolvedLogDir);
87
115
  const time = logObj.date?.toISOString?.() ?? new Date().toISOString();
88
116
  const line = JSON.stringify({ ...logObj, time });
89
117
  fs.appendFileSync(file, `${line}\n`, { encoding: "utf8" });
@@ -165,9 +193,12 @@ function formatLocalDate(date) {
165
193
  const day = String(date.getDate()).padStart(2, "0");
166
194
  return `${year}-${month}-${day}`;
167
195
  }
168
- function defaultRollingPathForToday() {
196
+ function rollingPathForToday(dir) {
169
197
  const today = formatLocalDate(new Date());
170
- return path.join(DEFAULT_LOG_DIR, `${LOG_PREFIX}-${today}${LOG_SUFFIX}`);
198
+ return path.join(dir, `${LOG_PREFIX}-${today}${LOG_SUFFIX}`);
199
+ }
200
+ function defaultRollingPathForToday() {
201
+ return rollingPathForToday(DEFAULT_LOG_DIR);
171
202
  }
172
203
  function isRollingPath(file) {
173
204
  const base = path.basename(file);
@@ -274,8 +274,8 @@ export function discoverTaskmasterPlugins(params) {
274
274
  }
275
275
  else {
276
276
  diagnostics.push({
277
- level: "info",
278
- message: `bundled extensions dir not found at ${bundled.resolvedPath ?? "unknown"}`,
277
+ level: "warn",
278
+ message: `bundled extensions dir not found at ${bundled.resolvedPath ?? "unknown"} (source: ${bundled.source ?? "package-relative"})`,
279
279
  source: "bundled-dir",
280
280
  });
281
281
  }
@@ -23,6 +23,52 @@ import { buildMentionConfig } from "./mentions.js";
23
23
  import { createEchoTracker } from "./monitor/echo.js";
24
24
  import { createWebOnMessageHandler } from "./monitor/on-message.js";
25
25
  import { isLikelyWhatsAppCryptoError } from "./util.js";
26
+ /**
27
+ * Translate raw Baileys error strings into messages a non-technical user can act on.
28
+ * The raw error is still logged to the system log file for debugging.
29
+ */
30
+ function humanizeWhatsAppError(statusCode, rawError) {
31
+ const lower = rawError.toLowerCase();
32
+ // Conflict: another WhatsApp Web session took over (credentials still valid)
33
+ if (/conflict/i.test(rawError)) {
34
+ return "WhatsApp was opened on another device or browser. Reconnecting automatically…";
35
+ }
36
+ // True logout — session credentials are no longer valid
37
+ if (statusCode === 401) {
38
+ return "WhatsApp session expired or was removed from the phone. Open the setup page and re-link WhatsApp.";
39
+ }
40
+ // Connection lost / timed out (408)
41
+ if (statusCode === 408 || lower.includes("timed out") || lower.includes("connection was lost")) {
42
+ return "Connection to WhatsApp lost. Reconnecting automatically…";
43
+ }
44
+ // Connection closed by server (428)
45
+ if (statusCode === 428 ||
46
+ lower.includes("connection closed") ||
47
+ lower.includes("connection terminated")) {
48
+ return "WhatsApp connection closed unexpectedly. Reconnecting automatically…";
49
+ }
50
+ // Connection replaced (440) — another session, similar to conflict
51
+ if (statusCode === 440) {
52
+ return "WhatsApp was opened on another device. Reconnecting automatically…";
53
+ }
54
+ // Bad session data (500)
55
+ if (statusCode === 500 || lower.includes("bad session")) {
56
+ return "WhatsApp session data may be corrupted. Try re-linking WhatsApp from the setup page.";
57
+ }
58
+ // Restart required (515)
59
+ if (statusCode === 515) {
60
+ return "WhatsApp requires a reconnection. Reconnecting automatically…";
61
+ }
62
+ // Forbidden (403)
63
+ if (statusCode === 403) {
64
+ return "WhatsApp rejected the connection. The phone number may be banned or restricted.";
65
+ }
66
+ // Fallback: strip technical prefixes but keep the core message
67
+ return (rawError
68
+ .replace(/^(Boom:|Error:)\s*/i, "")
69
+ .replace(/\s*\{.*\}\s*$/, "")
70
+ .trim() || "WhatsApp disconnected unexpectedly. Reconnecting…");
71
+ }
26
72
  export async function monitorWebChannel(verbose, listenerFactory = monitorWebInbox, keepAlive = true, replyResolver = getReplyFromConfig, runtime = defaultRuntime, abortSignal, tuning = {}) {
27
73
  const runId = newConnectionId();
28
74
  const replyLogger = getChildLogger({ module: "web-auto-reply", runId });
@@ -173,9 +219,7 @@ export async function monitorWebChannel(verbose, listenerFactory = monitorWebInb
173
219
  // carry status 401, but it is NOT a true logout — credentials remain valid.
174
220
  const isConflict = /conflict/i.test(errorStr);
175
221
  const loggedOut = statusCode === 401 && !isConflict;
176
- const userError = isConflict
177
- ? "WhatsApp was opened on another device or browser. Reconnecting automatically…"
178
- : errorStr;
222
+ const userError = humanizeWhatsAppError(statusCode, errorStr);
179
223
  status.connected = false;
180
224
  status.lastEventAt = Date.now();
181
225
  status.lastDisconnect = {
@@ -355,10 +399,7 @@ export async function monitorWebChannel(verbose, listenerFactory = monitorWebInb
355
399
  "isLoggedOut" in reason &&
356
400
  reason.isLoggedOut;
357
401
  const errorStr = formatError(reason);
358
- const isConflict = /conflict/i.test(errorStr);
359
- const userError = isConflict
360
- ? "WhatsApp was opened on another device or browser. Reconnecting automatically…"
361
- : errorStr;
402
+ const userError = humanizeWhatsAppError(statusCode, errorStr);
362
403
  status.connected = false;
363
404
  status.lastEventAt = Date.now();
364
405
  status.lastDisconnect = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.22.0",
3
+ "version": "1.22.2",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"