@rubytech/taskmaster 1.44.3 → 1.44.5

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.
@@ -208,6 +208,12 @@ function filterActionsForContext(params) {
208
208
  * never fall through to the global list, which would leak actions from
209
209
  * other channels (e.g. Discord "poll" appearing in webchat sessions).
210
210
  */
211
+ /**
212
+ * Channels where the "broadcast" action must never appear in the agent tool schema.
213
+ * WhatsApp prohibits automated bulk messaging — exposing the action to agents
214
+ * would encourage policy-violating behaviour.
215
+ */
216
+ const BROADCAST_EXCLUDED_CHANNELS = new Set(["whatsapp"]);
211
217
  function resolveMessageToolActions(options) {
212
218
  if (options?.currentChannel) {
213
219
  const channelActions = filterActionsForContext({
@@ -220,6 +226,10 @@ function resolveMessageToolActions(options) {
220
226
  });
221
227
  // Always include "send"; if the channel plugin reports nothing, send is all we need.
222
228
  const allActions = new Set(["send", ...channelActions]);
229
+ // Never expose broadcast on channels that prohibit automated bulk messaging.
230
+ if (BROADCAST_EXCLUDED_CHANNELS.has(options.currentChannel.toLowerCase())) {
231
+ allActions.delete("broadcast");
232
+ }
223
233
  return Array.from(allActions);
224
234
  }
225
235
  if (options?.config) {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.44.3",
3
- "commit": "32d7a0ee09ae38288deb15bad1d3890ccbe39a0d",
4
- "builtAt": "2026-03-20T07:51:26.410Z"
2
+ "version": "1.44.5",
3
+ "commit": "414cf5a6f0475e90da9e44c9a38af23eb2662ffa",
4
+ "builtAt": "2026-03-25T13:57:46.756Z"
5
5
  }
@@ -138,7 +138,7 @@ const FIELD_LABELS = {
138
138
  "tools.message.crossContext.marker.enabled": "Cross-Context Marker",
139
139
  "tools.message.crossContext.marker.prefix": "Cross-Context Marker Prefix",
140
140
  "tools.message.crossContext.marker.suffix": "Cross-Context Marker Suffix",
141
- "tools.message.broadcast.enabled": "Enable Message Broadcast",
141
+ "tools.message.broadcast.enabled": "Enable Message Broadcast (excludes WhatsApp)",
142
142
  "tools.web.search.enabled": "Enable Web Search Tool",
143
143
  "tools.web.search.provider": "Web Search Provider",
144
144
  "tools.web.search.apiKey": "Brave Search API Key",
@@ -352,7 +352,7 @@ const FIELD_HELP = {
352
352
  "tools.message.crossContext.marker.enabled": "Add a visible origin marker when sending cross-context (default: true).",
353
353
  "tools.message.crossContext.marker.prefix": 'Text prefix for cross-context markers (supports "{channel}").',
354
354
  "tools.message.crossContext.marker.suffix": 'Text suffix for cross-context markers (supports "{channel}").',
355
- "tools.message.broadcast.enabled": "Enable broadcast action (default: true).",
355
+ "tools.message.broadcast.enabled": "Enable broadcast action (default: false). WhatsApp is always excluded — automated broadcasts violate WhatsApp's Terms of Service.",
356
356
  "tools.web.search.enabled": "Enable the web_search tool (requires a provider API key).",
357
357
  "tools.web.search.provider": 'Search provider ("brave", "perplexity", or "tavily").',
358
358
  "tools.web.search.apiKey": "Brave Search API key (fallback: BRAVE_API_KEY env var).",
@@ -68,9 +68,10 @@ import { loadGatewayTlsRuntime } from "./server/tls.js";
68
68
  import { isLoopbackHost } from "./net.js";
69
69
  import { createWizardSessionTracker } from "./server-wizard-sessions.js";
70
70
  import { attachGatewayWsHandlers } from "./server-ws-runtime.js";
71
- import { isLicenseValid } from "../license/validate.js";
71
+ import { isLicenseValid, validateLicenseKey } from "../license/validate.js";
72
72
  import { setLicensed } from "../license/state.js";
73
73
  import { startLicenseRevalidation, stopLicenseRevalidation } from "../license/revalidation.js";
74
+ import { getDeviceId } from "../license/device-id.js";
74
75
  export { __resetModelCatalogCacheForTest } from "./server-model-catalog.js";
75
76
  ensureTaskmasterCliOnPath();
76
77
  const log = createSubsystemLogger("gateway");
@@ -331,7 +332,29 @@ export async function startGatewayServer(port = 18789, opts = {}) {
331
332
  // License check — gateway always starts (so setup UI is reachable),
332
333
  // but pages other than /setup will redirect until a license is activated.
333
334
  const logLicense = log.child("license");
334
- const licenseOk = isLicenseValid(cfgAtStart);
335
+ let licenseOk = isLicenseValid(cfgAtStart);
336
+ // If stored state is stale but the token is cryptographically valid,
337
+ // refresh validatedAt so the license doesn't appear expired on restart.
338
+ if (!licenseOk && cfgAtStart.license?.key) {
339
+ const deviceId = getDeviceId();
340
+ const freshCheck = validateLicenseKey(cfgAtStart.license.key, deviceId);
341
+ if (freshCheck.valid) {
342
+ logLicense.info("refreshing stale validatedAt — token is still valid");
343
+ try {
344
+ const freshCfg = loadConfig();
345
+ if (freshCfg.license) {
346
+ void writeConfigFile({
347
+ ...freshCfg,
348
+ license: { ...freshCfg.license, validatedAt: new Date().toISOString() },
349
+ });
350
+ }
351
+ }
352
+ catch (err) {
353
+ logLicense.warn(`failed to refresh validatedAt: ${String(err)}`);
354
+ }
355
+ licenseOk = true;
356
+ }
357
+ }
335
358
  setLicensed(licenseOk);
336
359
  if (!licenseOk) {
337
360
  logLicense.warn("no valid license — setup wizard will require activation");
@@ -350,24 +350,43 @@ function resolveGateway(input) {
350
350
  mode: input.gateway.mode,
351
351
  };
352
352
  }
353
+ /**
354
+ * Channels where automated broadcast is prohibited by the provider's Terms of Service.
355
+ * WhatsApp explicitly forbids automated bulk/broadcast messaging — violating this policy
356
+ * risks permanent number bans and account termination.
357
+ */
358
+ const BROADCAST_BLOCKED_CHANNELS = new Set(["whatsapp"]);
353
359
  async function handleBroadcastAction(input, params) {
354
360
  throwIfAborted(input.abortSignal);
355
- const broadcastEnabled = input.cfg.tools?.message?.broadcast?.enabled !== false;
361
+ const broadcastEnabled = input.cfg.tools?.message?.broadcast?.enabled === true;
356
362
  if (!broadcastEnabled) {
357
- throw new Error("Broadcast is disabled. Set tools.message.broadcast.enabled to true.");
363
+ throw new Error("Broadcast is disabled. Set tools.message.broadcast.enabled to true in config. " +
364
+ "Note: WhatsApp is always excluded — automated broadcasts violate WhatsApp's Terms of Service.");
358
365
  }
359
366
  const rawTargets = readStringArrayParam(params, "targets", { required: true }) ?? [];
360
367
  if (rawTargets.length === 0) {
361
368
  throw new Error("Broadcast requires at least one target in --targets.");
362
369
  }
363
370
  const channelHint = readStringParam(params, "channel");
371
+ // Hard block: reject broadcast requests that explicitly target a blocked channel.
372
+ if (channelHint) {
373
+ const normalizedHint = channelHint.trim().toLowerCase();
374
+ if (normalizedHint !== "all" && BROADCAST_BLOCKED_CHANNELS.has(normalizedHint)) {
375
+ throw new Error(`Broadcast to ${channelHint} is blocked. Automated broadcast messaging violates ${channelHint}'s Terms of Service and risks permanent account bans.`);
376
+ }
377
+ }
364
378
  const configured = await listConfiguredMessageChannels(input.cfg);
365
379
  if (configured.length === 0) {
366
380
  throw new Error("Broadcast requires at least one configured channel.");
367
381
  }
368
- const targetChannels = channelHint && channelHint.trim().toLowerCase() !== "all"
382
+ const allTargetChannels = channelHint && channelHint.trim().toLowerCase() !== "all"
369
383
  ? [await resolveChannel(input.cfg, { channel: channelHint })]
370
384
  : configured;
385
+ // Silently exclude blocked channels from broadcast fan-out.
386
+ const targetChannels = allTargetChannels.filter((ch) => !BROADCAST_BLOCKED_CHANNELS.has(ch));
387
+ if (targetChannels.length === 0) {
388
+ throw new Error("No eligible channels for broadcast. WhatsApp is excluded — automated broadcasts violate WhatsApp's Terms of Service.");
389
+ }
371
390
  const results = [];
372
391
  const isAbortError = (err) => err instanceof Error && err.name === "AbortError";
373
392
  for (const targetChannel of targetChannels) {
@@ -15,6 +15,22 @@ function revalidate(log, onChange) {
15
15
  const result = validateLicenseKey(lic.key, deviceId);
16
16
  if (result.valid) {
17
17
  log.info("license still valid");
18
+ // Refresh validatedAt so the 30-day staleness window doesn't expire
19
+ try {
20
+ const freshConfig = loadConfig();
21
+ if (freshConfig.license) {
22
+ void writeConfigFile({
23
+ ...freshConfig,
24
+ license: {
25
+ ...freshConfig.license,
26
+ validatedAt: new Date().toISOString(),
27
+ },
28
+ });
29
+ }
30
+ }
31
+ catch (err) {
32
+ log.warn(`failed to refresh validatedAt: ${String(err)}`);
33
+ }
18
34
  return;
19
35
  }
20
36
  // Token is no longer valid (expired, wrong device, bad signature)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.44.3",
3
+ "version": "1.44.5",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"