@rubytech/taskmaster 1.0.107 → 1.0.109

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 (36) hide show
  1. package/dist/agents/system-prompt.js +2 -0
  2. package/dist/agents/taskmaster-tools.js +5 -0
  3. package/dist/agents/tool-policy.js +2 -1
  4. package/dist/agents/tools/authorize-admin-tool.js +1 -1
  5. package/dist/agents/tools/software-update-tool.js +114 -0
  6. package/dist/auto-reply/reply/commands-status.js +5 -9
  7. package/dist/auto-reply/reply/get-reply-run.js +1 -1
  8. package/dist/auto-reply/reply/get-reply.js +1 -1
  9. package/dist/auto-reply/reply/model-selection.js +1 -1
  10. package/dist/browser/routes/screencast.js +1 -1
  11. package/dist/browser/screencast.js +1 -1
  12. package/dist/build-info.json +3 -3
  13. package/dist/commands/agent.js +2 -2
  14. package/dist/control-ui/assets/index-BM3zZtpB.css +1 -0
  15. package/dist/control-ui/assets/{index-B_zHmTQU.js → index-D4TpiIHx.js} +445 -388
  16. package/dist/control-ui/assets/index-D4TpiIHx.js.map +1 -0
  17. package/dist/control-ui/index.html +2 -2
  18. package/dist/cron/isolated-agent/recipients.js +70 -0
  19. package/dist/cron/isolated-agent/run.js +43 -13
  20. package/dist/gateway/server-methods/access.js +3 -3
  21. package/dist/gateway/server-methods/browser-screencast.js +3 -3
  22. package/dist/gateway/server-methods/workspaces.js +7 -7
  23. package/dist/gateway/server.impl.js +1 -1
  24. package/dist/infra/heartbeat-runner.js +17 -0
  25. package/dist/infra/heartbeat-update-notify.js +120 -0
  26. package/dist/infra/tunnel.js +1 -1
  27. package/dist/memory/embeddings.js +0 -4
  28. package/dist/memory/manager.js +3 -3
  29. package/dist/web/inbound/media.js +1 -1
  30. package/dist/web/login-qr.js +0 -23
  31. package/dist/web/providers/cloud/receive.js +1 -1
  32. package/dist/web/providers/cloud/webhook.js +1 -1
  33. package/package.json +1 -1
  34. package/taskmaster-docs/USER-GUIDE.md +17 -39
  35. package/dist/control-ui/assets/index-2XyxmiR6.css +0 -1
  36. package/dist/control-ui/assets/index-B_zHmTQU.js.map +0 -1
@@ -6,8 +6,8 @@
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-B_zHmTQU.js"></script>
10
- <link rel="stylesheet" crossorigin href="./assets/index-2XyxmiR6.css">
9
+ <script type="module" crossorigin src="./assets/index-D4TpiIHx.js"></script>
10
+ <link rel="stylesheet" crossorigin href="./assets/index-BM3zZtpB.css">
11
11
  </head>
12
12
  <body>
13
13
  <taskmaster-app></taskmaster-app>
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Extract admin phone numbers from config bindings.
3
+ * Admin phones are bindings where agentId is "admin" (or legacy "management"),
4
+ * channel is "whatsapp", and peer.kind is "dm".
5
+ */
6
+ export function resolveAdminBindingPhones(cfg, accountId) {
7
+ const bindings = Array.isArray(cfg.bindings) ? cfg.bindings : [];
8
+ const phones = [];
9
+ for (const binding of bindings) {
10
+ if (!binding || typeof binding !== "object")
11
+ continue;
12
+ const agentId = binding.agentId;
13
+ if (agentId !== "admin" && agentId !== "management")
14
+ continue;
15
+ const match = binding.match;
16
+ if (!match || typeof match !== "object")
17
+ continue;
18
+ const channel = typeof match.channel === "string" ? match.channel.toLowerCase() : "";
19
+ if (channel !== "whatsapp")
20
+ continue;
21
+ // Filter by accountId if specified
22
+ if (accountId && typeof match.accountId === "string" && match.accountId !== accountId)
23
+ continue;
24
+ const peer = match.peer;
25
+ if (!peer || typeof peer !== "object")
26
+ continue;
27
+ if (peer.kind !== "dm")
28
+ continue;
29
+ const id = typeof peer.id === "string" ? peer.id.trim() : "";
30
+ if (id)
31
+ phones.push(id);
32
+ }
33
+ return phones;
34
+ }
35
+ /**
36
+ * Expand the `to` field from a cron payload into individual recipient addresses.
37
+ *
38
+ * Supports:
39
+ * - Single phone: "+15551234567"
40
+ * - Comma-separated phones: "+15551234567,+15559876543"
41
+ * - "admins" token: resolves to all admin binding phones for the account
42
+ * - Mixed: "admins,+15559999999"
43
+ *
44
+ * Returns deduplicated list of phone numbers/addresses.
45
+ * Returns empty array if `to` is empty/undefined.
46
+ */
47
+ export function expandCronRecipients(cfg, to, accountId) {
48
+ if (!to?.trim())
49
+ return [];
50
+ const parts = to.split(",").map((s) => s.trim()).filter(Boolean);
51
+ const seen = new Set();
52
+ const result = [];
53
+ for (const part of parts) {
54
+ if (part.toLowerCase() === "admins") {
55
+ for (const phone of resolveAdminBindingPhones(cfg, accountId)) {
56
+ if (!seen.has(phone)) {
57
+ seen.add(phone);
58
+ result.push(phone);
59
+ }
60
+ }
61
+ }
62
+ else {
63
+ if (!seen.has(part)) {
64
+ seen.add(part);
65
+ result.push(part);
66
+ }
67
+ }
68
+ }
69
+ return result;
70
+ }
@@ -18,10 +18,12 @@ import { createOutboundSendDeps } from "../../cli/outbound-send-deps.js";
18
18
  import { resolveSessionTranscriptPath, updateSessionStore } from "../../config/sessions.js";
19
19
  import { registerAgentRunContext } from "../../infra/agent-events.js";
20
20
  import { deliverOutboundPayloads } from "../../infra/outbound/deliver.js";
21
+ import { resolveOutboundTarget } from "../../infra/outbound/targets.js";
21
22
  import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
22
23
  import { buildAgentMainSessionKey, normalizeAgentId } from "../../routing/session-key.js";
23
24
  import { resolveAgentBoundAccountId } from "../../routing/bindings.js";
24
25
  import { resolveDeliveryTarget } from "./delivery-target.js";
26
+ import { expandCronRecipients } from "./recipients.js";
25
27
  import { isHeartbeatOnlyResponse, pickLastNonEmptyTextFromPayloads, pickSummaryFromOutput, pickSummaryFromPayloads, resolveHeartbeatAckMaxChars, } from "./helpers.js";
26
28
  import { resolveCronSession } from "./session.js";
27
29
  function matchesMessagingToolDeliveryTarget(target, delivery) {
@@ -308,7 +310,15 @@ export async function runCronIsolatedAgentTurn(params) {
308
310
  accountId: resolvedDelivery.accountId,
309
311
  }));
310
312
  if (deliveryRequested && !skipHeartbeatDelivery && !skipMessagingToolDelivery) {
311
- if (!resolvedDelivery.to) {
313
+ // Expand multi-recipient targets (comma-separated, "admins" token).
314
+ const expandedRecipients = expandCronRecipients(cfgWithAgentDefaults, agentPayload?.to, effectiveAccountId);
315
+ // Use expanded list if multi-target, otherwise fall back to the resolved single target.
316
+ const deliveryTargets = expandedRecipients.length > 1
317
+ ? expandedRecipients
318
+ : resolvedDelivery.to
319
+ ? [resolvedDelivery.to]
320
+ : [];
321
+ if (deliveryTargets.length === 0) {
312
322
  const reason = resolvedDelivery.error?.message ?? "Cron delivery requires a recipient (--to).";
313
323
  if (!bestEffortDeliver) {
314
324
  return {
@@ -324,22 +334,42 @@ export async function runCronIsolatedAgentTurn(params) {
324
334
  outputText,
325
335
  };
326
336
  }
327
- try {
328
- await deliverOutboundPayloads({
329
- cfg: cfgWithAgentDefaults,
337
+ const errors = [];
338
+ for (const targetTo of deliveryTargets) {
339
+ // Validate each target against the channel.
340
+ const docked = resolveOutboundTarget({
330
341
  channel: resolvedDelivery.channel,
331
- to: resolvedDelivery.to,
342
+ to: targetTo,
343
+ cfg: cfgWithAgentDefaults,
332
344
  accountId: resolvedDelivery.accountId,
333
- payloads,
334
- bestEffort: bestEffortDeliver,
335
- deps: createOutboundSendDeps(params.deps),
345
+ mode: "explicit",
336
346
  });
337
- }
338
- catch (err) {
339
- if (!bestEffortDeliver) {
340
- return { status: "error", summary, outputText, error: String(err) };
347
+ if (!docked.ok) {
348
+ errors.push(`${targetTo}: ${docked.error.message}`);
349
+ continue;
350
+ }
351
+ try {
352
+ await deliverOutboundPayloads({
353
+ cfg: cfgWithAgentDefaults,
354
+ channel: resolvedDelivery.channel,
355
+ to: docked.to,
356
+ accountId: resolvedDelivery.accountId,
357
+ payloads,
358
+ bestEffort: bestEffortDeliver,
359
+ deps: createOutboundSendDeps(params.deps),
360
+ });
341
361
  }
342
- return { status: "ok", summary, outputText };
362
+ catch (err) {
363
+ errors.push(`${targetTo}: ${String(err)}`);
364
+ }
365
+ }
366
+ if (errors.length > 0 && !bestEffortDeliver) {
367
+ return {
368
+ status: "error",
369
+ summary,
370
+ outputText,
371
+ error: errors.join("; "),
372
+ };
343
373
  }
344
374
  }
345
375
  return { status: "ok", summary, outputText };
@@ -176,7 +176,7 @@ export const accessHandlers = {
176
176
  await writeConfigFile({
177
177
  ...config,
178
178
  access: {
179
- ...(config.access ?? {}),
179
+ ...config.access,
180
180
  masterPin: pin,
181
181
  },
182
182
  });
@@ -210,9 +210,9 @@ export const accessHandlers = {
210
210
  await writeConfigFile({
211
211
  ...config,
212
212
  access: {
213
- ...(config.access ?? {}),
213
+ ...config.access,
214
214
  pins: {
215
- ...(config.access?.pins ?? {}),
215
+ ...config.access?.pins,
216
216
  [workspace]: pin,
217
217
  },
218
218
  },
@@ -61,7 +61,7 @@ export const browserScreencastHandlers = {
61
61
  respond(true, result);
62
62
  }
63
63
  catch (err) {
64
- log.error(`screencast.start failed: ${err}`);
64
+ log.error(`screencast.start failed: ${String(err)}`);
65
65
  respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, String(err)));
66
66
  }
67
67
  },
@@ -84,7 +84,7 @@ export const browserScreencastHandlers = {
84
84
  respond(true, { ok: true });
85
85
  }
86
86
  catch (err) {
87
- log.error(`screencast.stop failed: ${err}`);
87
+ log.error(`screencast.stop failed: ${String(err)}`);
88
88
  respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, String(err)));
89
89
  }
90
90
  },
@@ -169,7 +169,7 @@ async function resolveBridgeUrl(context) {
169
169
  log.warn(`browser config resolved but not usable: enabled=${resolved.enabled} controlUrl=${resolved.controlUrl ?? "null"}`);
170
170
  }
171
171
  catch (err) {
172
- log.error(`failed to resolve browser config: ${err}`);
172
+ log.error(`failed to resolve browser config: ${String(err)}`);
173
173
  }
174
174
  return null;
175
175
  }
@@ -56,7 +56,7 @@ function sanitiseName(raw) {
56
56
  * or the agent subdirectory (e.g. ~/taskmaster/agents/public).
57
57
  * We normalise to the root by stripping a trailing /agents/{name} suffix.
58
58
  */
59
- function resolveWorkspaceRoot(agentWorkspaceDir, agentId) {
59
+ function resolveWorkspaceRoot(agentWorkspaceDir, _agentId) {
60
60
  const normalised = agentWorkspaceDir.replace(/\/+$/, "");
61
61
  // Check if the path ends with /agents/{something} — that's the agent subdir, not the workspace root
62
62
  const agentsMatch = normalised.match(/^(.+)\/agents\/[^/]+$/);
@@ -424,9 +424,9 @@ export const workspacesHandlers = {
424
424
  cfg = {
425
425
  ...cfg,
426
426
  channels: {
427
- ...(cfg.channels ?? {}),
427
+ ...cfg.channels,
428
428
  whatsapp: {
429
- ...(cfg.channels?.whatsapp ?? {}),
429
+ ...cfg.channels?.whatsapp,
430
430
  accounts: {
431
431
  ...existingAccounts,
432
432
  [whatsappAccountId]: {
@@ -467,7 +467,7 @@ export const workspacesHandlers = {
467
467
  if (typeof rawName === "string" && rawName.trim() && rawName.trim() !== name) {
468
468
  cfg = {
469
469
  ...cfg,
470
- workspaces: { ...(cfg.workspaces ?? {}), [name]: { displayName: rawName.trim() } },
470
+ workspaces: { ...cfg.workspaces, [name]: { displayName: rawName.trim() } },
471
471
  };
472
472
  }
473
473
  // Write config and schedule restart
@@ -582,12 +582,12 @@ export const workspacesHandlers = {
582
582
  }
583
583
  // Clean up empty accounts object
584
584
  if (Object.keys(whatsappAccounts).length === 0) {
585
- const whatsappConfig = { ...(cfg.channels?.whatsapp ?? {}) };
585
+ const whatsappConfig = { ...cfg.channels?.whatsapp };
586
586
  delete whatsappConfig.accounts;
587
587
  cfg = {
588
588
  ...cfg,
589
589
  channels: {
590
- ...(cfg.channels ?? {}),
590
+ ...cfg.channels,
591
591
  whatsapp: whatsappConfig,
592
592
  },
593
593
  };
@@ -640,7 +640,7 @@ export const workspacesHandlers = {
640
640
  if (!requireBaseHash(params, snapshot, respond))
641
641
  return;
642
642
  let cfg = snapshot.config;
643
- cfg = { ...cfg, workspaces: { ...(cfg.workspaces ?? {}), [name]: { displayName } } };
643
+ cfg = { ...cfg, workspaces: { ...cfg.workspaces, [name]: { displayName } } };
644
644
  try {
645
645
  await writeConfigFile(cfg);
646
646
  }
@@ -428,7 +428,7 @@ export async function startGatewayServer(port = 18789, opts = {}) {
428
428
  const logMemory = log.child("memory");
429
429
  const agentIds = listAgentIds(cfgAtStart);
430
430
  for (const agentId of agentIds) {
431
- getMemorySearchManager({ cfg: cfgAtStart, agentId }).then(async (result) => {
431
+ void getMemorySearchManager({ cfg: cfgAtStart, agentId }).then(async (result) => {
432
432
  if (result.manager) {
433
433
  logMemory.info(`initialized for agent: ${agentId}`);
434
434
  // Sync memory index on startup
@@ -25,6 +25,7 @@ import { resolveHeartbeatVisibility } from "./heartbeat-visibility.js";
25
25
  import { requestHeartbeatNow, setHeartbeatWakeHandler, } from "./heartbeat-wake.js";
26
26
  import { deliverOutboundPayloads } from "./outbound/deliver.js";
27
27
  import { resolveHeartbeatDeliveryTarget, resolveHeartbeatSenderContext, } from "./outbound/targets.js";
28
+ import { maybeNotifyUpdateAvailable } from "./heartbeat-update-notify.js";
28
29
  const log = createSubsystemLogger("gateway/heartbeat");
29
30
  let heartbeatsEnabled = true;
30
31
  export function setHeartbeatsEnabled(enabled) {
@@ -621,6 +622,14 @@ export async function runHeartbeatOnce(opts) {
621
622
  return { status: "failed", reason };
622
623
  }
623
624
  }
625
+ async function checkAndNotifyUpdate(cfg, agent, deps) {
626
+ const agentId = agent.agentId;
627
+ const heartbeat = agent.heartbeat;
628
+ const { entry } = resolveHeartbeatSession(cfg, agentId, heartbeat);
629
+ const bindingAccountId = resolveAgentBoundAccountId(cfg, agentId, "whatsapp") ?? undefined;
630
+ const delivery = resolveHeartbeatDeliveryTarget({ cfg, entry, heartbeat, bindingAccountId });
631
+ await maybeNotifyUpdateAvailable({ cfg, delivery, deps });
632
+ }
624
633
  export function startHeartbeatRunner(opts) {
625
634
  const runtime = opts.runtime ?? defaultRuntime;
626
635
  const runOnce = opts.runOnce ?? runHeartbeatOnce;
@@ -742,6 +751,14 @@ export function startHeartbeatRunner(opts) {
742
751
  if (res.status === "ran")
743
752
  ran = true;
744
753
  }
754
+ // After heartbeat cycle: check for software updates and notify admin.
755
+ // Uses the first agent's delivery target. Non-blocking — never delays the next heartbeat.
756
+ if (ran) {
757
+ const firstAgent = state.agents.values().next().value;
758
+ if (firstAgent) {
759
+ void checkAndNotifyUpdate(state.cfg, firstAgent, { runtime: state.runtime }).catch(() => { });
760
+ }
761
+ }
745
762
  scheduleNext();
746
763
  if (ran)
747
764
  return { status: "ran", durationMs: Date.now() - startedAt };
@@ -0,0 +1,120 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { getChannelPlugin } from "../channels/plugins/index.js";
4
+ import { resolveStateDir } from "../config/paths.js";
5
+ import { createSubsystemLogger } from "../logging/subsystem.js";
6
+ import { VERSION } from "../version.js";
7
+ import { compareSemverStrings, resolveNpmChannelTag } from "./update-check.js";
8
+ import { normalizeUpdateChannel, DEFAULT_PACKAGE_CHANNEL } from "./update-channels.js";
9
+ import { deliverOutboundPayloads } from "./outbound/deliver.js";
10
+ const log = createSubsystemLogger("gateway/heartbeat-update-notify");
11
+ // Registry check interval during heartbeat cycles.
12
+ // Shorter than the 24h startup check — the admin may publish frequently.
13
+ const REGISTRY_CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
14
+ // In-memory: last time we checked the registry.
15
+ let lastRegistryCheckMs = 0;
16
+ // State file stores lastNotifiedVersion so we don't re-notify after restart
17
+ // for a version we already told the admin about.
18
+ const STATE_FILENAME = "update-check.json";
19
+ async function readState() {
20
+ try {
21
+ const statePath = path.join(resolveStateDir(), STATE_FILENAME);
22
+ const raw = await fs.readFile(statePath, "utf-8");
23
+ const parsed = JSON.parse(raw);
24
+ return parsed && typeof parsed === "object" ? parsed : {};
25
+ }
26
+ catch {
27
+ return {};
28
+ }
29
+ }
30
+ async function writeState(state) {
31
+ const statePath = path.join(resolveStateDir(), STATE_FILENAME);
32
+ await fs.mkdir(path.dirname(statePath), { recursive: true });
33
+ await fs.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
34
+ }
35
+ /**
36
+ * Check for available software updates and notify the admin via the heartbeat
37
+ * delivery channel. Designed to run after each heartbeat cycle.
38
+ *
39
+ * - Checks the NPM registry at most once per REGISTRY_CHECK_INTERVAL_MS.
40
+ * - Notifies the admin once per new version (persisted across restarts).
41
+ * - Never throws — errors are logged and swallowed.
42
+ */
43
+ export async function maybeNotifyUpdateAvailable(params) {
44
+ try {
45
+ const { cfg, delivery, deps } = params;
46
+ const nowMs = params.nowMs ?? Date.now();
47
+ // Skip if no delivery target
48
+ if (delivery.channel === "none" || !delivery.to)
49
+ return false;
50
+ // Skip if disabled via config
51
+ if (cfg.update?.checkOnStart === false)
52
+ return false;
53
+ // Rate-limit registry checks
54
+ if (nowMs - lastRegistryCheckMs < REGISTRY_CHECK_INTERVAL_MS)
55
+ return false;
56
+ lastRegistryCheckMs = nowMs;
57
+ // Resolve update channel and fetch latest version
58
+ const channel = normalizeUpdateChannel(cfg.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;
59
+ const resolved = await resolveNpmChannelTag({ channel, timeoutMs: 3500 });
60
+ if (!resolved.version)
61
+ return false;
62
+ // Compare versions
63
+ const cmp = compareSemverStrings(VERSION, resolved.version);
64
+ if (cmp === null || cmp >= 0)
65
+ return false; // up to date or parse failure
66
+ // Check if we already notified for this version
67
+ const state = await readState();
68
+ if (state.lastNotifiedVersion === resolved.version)
69
+ return false;
70
+ // Check channel readiness
71
+ const plugin = getChannelPlugin(delivery.channel);
72
+ if (plugin?.heartbeat?.checkReady) {
73
+ const readiness = await plugin.heartbeat.checkReady({
74
+ cfg,
75
+ accountId: delivery.accountId,
76
+ deps,
77
+ });
78
+ if (!readiness.ok) {
79
+ log.debug("update notification skipped: channel not ready", {
80
+ reason: readiness.reason,
81
+ });
82
+ return false;
83
+ }
84
+ }
85
+ // Send the notification
86
+ const message = `Taskmaster v${resolved.version} is available (you're on v${VERSION}). ` +
87
+ `Update from Setup → Software Update, or say "update software".`;
88
+ await deliverOutboundPayloads({
89
+ cfg,
90
+ channel: delivery.channel,
91
+ to: delivery.to,
92
+ accountId: delivery.accountId,
93
+ payloads: [{ text: message }],
94
+ deps,
95
+ });
96
+ // Persist so we don't re-notify after restart
97
+ await writeState({
98
+ ...state,
99
+ lastNotifiedVersion: resolved.version,
100
+ lastNotifiedTag: resolved.tag,
101
+ lastCheckedAt: new Date(nowMs).toISOString(),
102
+ });
103
+ log.info("update notification sent", {
104
+ current: VERSION,
105
+ latest: resolved.version,
106
+ to: delivery.to,
107
+ });
108
+ return true;
109
+ }
110
+ catch (err) {
111
+ log.error("update notification failed", {
112
+ error: err instanceof Error ? err.message : String(err),
113
+ });
114
+ return false;
115
+ }
116
+ }
117
+ /** Reset in-memory check timer. Exposed for testing. */
118
+ export function resetUpdateCheckTimer() {
119
+ lastRegistryCheckMs = 0;
120
+ }
@@ -79,7 +79,7 @@ export class CloudflareTunnel {
79
79
  const urlTimeout = setTimeout(() => {
80
80
  if (!urlFound) {
81
81
  reject(new Error("Timeout waiting for tunnel URL"));
82
- this.stop();
82
+ void this.stop();
83
83
  }
84
84
  }, 30000);
85
85
  this.process.stdout?.on("data", (data) => {
@@ -33,10 +33,6 @@ function canAutoSelectLocal(options) {
33
33
  return false;
34
34
  }
35
35
  }
36
- function isMissingApiKeyError(err) {
37
- const message = formatError(err);
38
- return message.includes("No API key found for provider");
39
- }
40
36
  /**
41
37
  * Deduplicates concurrent model downloads. When multiple agents resolve the
42
38
  * same model path, only one download runs; the rest await the same promise.
@@ -94,7 +94,7 @@ function matchGlobPattern(pattern, filePath) {
94
94
  // Replace ** with a placeholder, then * with [^/]*, then placeholder with .*
95
95
  regexStr = regexStr.replace(/\*\*/g, "\0");
96
96
  regexStr = regexStr.replace(/\*/g, "[^/]*");
97
- regexStr = regexStr.replace(/\0/g, ".*");
97
+ regexStr = regexStr.replaceAll("\0", ".*");
98
98
  const regex = new RegExp(`^${regexStr}$`, "i");
99
99
  return regex.test(filePath);
100
100
  }
@@ -582,7 +582,7 @@ export class MemoryIndexManager {
582
582
  }
583
583
  // Ensure parent directory exists
584
584
  const parentDir = path.dirname(absPath);
585
- await ensureDir(parentDir);
585
+ ensureDir(parentDir);
586
586
  // Write or append content
587
587
  const mode = params.mode ?? "overwrite";
588
588
  if (mode === "append") {
@@ -638,7 +638,7 @@ export class MemoryIndexManager {
638
638
  }
639
639
  // Ensure parent directory exists
640
640
  const parentDir = path.dirname(absPath);
641
- await ensureDir(parentDir);
641
+ ensureDir(parentDir);
642
642
  // Copy the file
643
643
  await fs.copyFile(params.sourcePath, absPath);
644
644
  return { path: relPath, bytesWritten: sourceStat.size };
@@ -55,7 +55,7 @@ export async function downloadInboundMedia(msg, sock) {
55
55
  log.info(`audioMessage: ptt=${audio.ptt}, url=${audio.url ? "present" : "missing"}, ` +
56
56
  `directPath=${audio.directPath ? "present" : "missing"}, ` +
57
57
  `mediaKey=${audio.mediaKey ? "present" : "missing"}, ` +
58
- `fileLength=${audio.fileLength}, seconds=${audio.seconds}`);
58
+ `fileLength=${String(audio.fileLength)}, seconds=${String(audio.seconds)}`);
59
59
  }
60
60
  try {
61
61
  // Try standard download first
@@ -44,29 +44,6 @@ function attachLoginWaiter(accountId, login) {
44
44
  current.errorStatus = getStatusCode(err);
45
45
  });
46
46
  }
47
- async function restartLoginSocket(login, runtime) {
48
- if (login.restartAttempted)
49
- return false;
50
- login.restartAttempted = true;
51
- runtime.log(info("WhatsApp asked for a restart after pairing (code 515); retrying connection once…"));
52
- closeSocket(login.sock);
53
- try {
54
- const sock = await createWaSocket(false, login.verbose, {
55
- authDir: login.authDir,
56
- });
57
- login.sock = sock;
58
- login.connected = false;
59
- login.error = undefined;
60
- login.errorStatus = undefined;
61
- attachLoginWaiter(login.accountId, login);
62
- return true;
63
- }
64
- catch (err) {
65
- login.error = formatError(err);
66
- login.errorStatus = getStatusCode(err);
67
- return false;
68
- }
69
- }
70
47
  export async function startWebLoginWithQr(opts = {}) {
71
48
  const runtime = opts.runtime ?? defaultRuntime;
72
49
  const cfg = loadConfig();
@@ -59,7 +59,7 @@ function extractMessageText(message) {
59
59
  // Reactions are handled separately
60
60
  return "";
61
61
  default:
62
- return `<${message.type}>`;
62
+ return `<${String(message.type)}>`;
63
63
  }
64
64
  }
65
65
  /**
@@ -67,7 +67,7 @@ export function createWebhookHandlers(options) {
67
67
  try {
68
68
  const payload = req.body;
69
69
  if (payload.object !== "whatsapp_business_account") {
70
- log.warn(`Unexpected webhook object type: ${payload.object}`);
70
+ log.warn(`Unexpected webhook object type: ${String(payload.object)}`);
71
71
  return;
72
72
  }
73
73
  for (const entry of payload.entry) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.0.107",
3
+ "version": "1.0.109",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"