@rubytech/taskmaster 1.29.2 → 1.30.1
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.
- package/dist/agents/model-fallback.js +9 -9
- package/dist/agents/subagent-announce.js +10 -2
- package/dist/agents/tools/cron-tool.js +10 -3
- package/dist/agents/tools/skill-read-tool.js +18 -1
- package/dist/agents/workspace-migrations.js +48 -0
- package/dist/build-info.json +3 -3
- package/dist/control-ui/assets/{index-C-AxOsfW.js → index-CkGA_2Sq.js} +494 -423
- package/dist/control-ui/assets/index-CkGA_2Sq.js.map +1 -0
- package/dist/control-ui/assets/{index-BwiQeiEa.css → index-CxVLKv6r.css} +1 -1
- package/dist/control-ui/index.html +2 -2
- package/dist/cron/normalize.js +8 -4
- package/dist/cron/parse.js +48 -7
- package/dist/cron/service/jobs.js +4 -1
- package/dist/cron/service/ops.js +16 -0
- package/dist/gateway/server-methods/whatsapp-conversations.js +41 -7
- package/dist/web/auto-reply/monitor/group-activation.js +1 -1
- package/dist/web/inbound/monitor.js +34 -3
- package/extensions/.npmignore +1 -0
- package/package.json +52 -62
- package/scripts/install.sh +0 -0
- package/skills/event-management/references/events.md +16 -6
- package/skills/whatsapp-business/SKILL.md +47 -0
- package/skills/whatsapp-business/references/setup-guide.md +258 -0
- package/templates/.DS_Store +0 -0
- package/templates/beagle-taxi/.DS_Store +0 -0
- package/templates/beagle-taxi/agents/.DS_Store +0 -0
- package/templates/beagle-taxi/memory/.DS_Store +0 -0
- package/templates/beagle-taxi/skills/.DS_Store +0 -0
- package/templates/beagle-zanzibar/.DS_Store +0 -0
- package/templates/beagle-zanzibar/agents/.DS_Store +0 -0
- package/templates/beagle-zanzibar/memory/.DS_Store +0 -0
- package/templates/beagle-zanzibar/skills/.DS_Store +0 -0
- package/templates/customer/.DS_Store +0 -0
- package/templates/customer/agents/.DS_Store +0 -0
- package/templates/education-hero/.DS_Store +0 -0
- package/templates/education-hero/agents/.DS_Store +0 -0
- package/templates/education-hero/agents/admin/.DS_Store +0 -0
- package/templates/education-hero/skills/.DS_Store +0 -0
- package/templates/education-hero/skills/education-hero/.DS_Store +0 -0
- package/templates/maxy/.DS_Store +0 -0
- package/templates/maxy/.gitignore +1 -0
- package/templates/maxy/agents/.DS_Store +0 -0
- package/templates/maxy/agents/admin/.DS_Store +0 -0
- package/templates/maxy/memory/.DS_Store +0 -0
- package/templates/maxy/skills/.DS_Store +0 -0
- package/templates/real-agent/.DS_Store +0 -0
- package/templates/real-agent/skills/.DS_Store +0 -0
- package/templates/taskmaster/.DS_Store +0 -0
- package/templates/taskmaster/.gitignore +1 -0
- package/templates/taskmaster/agents/public/AGENTS.md +16 -0
- package/templates/taskmaster/skills/.DS_Store +0 -0
- package/dist/control-ui/assets/index-C-AxOsfW.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-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-CkGA_2Sq.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CxVLKv6r.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<taskmaster-app></taskmaster-app>
|
package/dist/cron/normalize.js
CHANGED
|
@@ -7,15 +7,19 @@ const DEFAULT_OPTIONS = {
|
|
|
7
7
|
function isRecord(value) {
|
|
8
8
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
9
|
}
|
|
10
|
-
function coerceSchedule(schedule) {
|
|
10
|
+
function coerceSchedule(schedule, opts) {
|
|
11
11
|
const next = { ...schedule };
|
|
12
12
|
const kind = typeof schedule.kind === "string" ? schedule.kind : undefined;
|
|
13
13
|
const atMsRaw = schedule.atMs;
|
|
14
14
|
const atRaw = schedule.at;
|
|
15
|
+
// When the agent passes a timezone-naive ISO string (e.g. "2026-03-11T09:00:00"),
|
|
16
|
+
// interpret it in the user's configured timezone — not UTC. A schedule-level
|
|
17
|
+
// `tz` field takes priority over the account default.
|
|
18
|
+
const tz = (typeof schedule.tz === "string" ? schedule.tz.trim() : undefined) || opts?.userTimezone;
|
|
15
19
|
const parsedAtMs = typeof atMsRaw === "string"
|
|
16
|
-
? parseAbsoluteTimeMs(atMsRaw)
|
|
20
|
+
? parseAbsoluteTimeMs(atMsRaw, tz)
|
|
17
21
|
: typeof atRaw === "string"
|
|
18
|
-
? parseAbsoluteTimeMs(atRaw)
|
|
22
|
+
? parseAbsoluteTimeMs(atRaw, tz)
|
|
19
23
|
: null;
|
|
20
24
|
if (!kind) {
|
|
21
25
|
if (typeof schedule.atMs === "number" ||
|
|
@@ -92,7 +96,7 @@ export function normalizeCronJobInput(raw, options = DEFAULT_OPTIONS) {
|
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
if (isRecord(base.schedule)) {
|
|
95
|
-
next.schedule = coerceSchedule(base.schedule);
|
|
99
|
+
next.schedule = coerceSchedule(base.schedule, { userTimezone: options.userTimezone });
|
|
96
100
|
}
|
|
97
101
|
if (isRecord(base.payload)) {
|
|
98
102
|
next.payload = coercePayload(base.payload);
|
package/dist/cron/parse.js
CHANGED
|
@@ -1,16 +1,57 @@
|
|
|
1
1
|
const ISO_TZ_RE = /(Z|[+-]\d{2}:?\d{2})$/i;
|
|
2
2
|
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
3
3
|
const ISO_DATE_TIME_RE = /^\d{4}-\d{2}-\d{2}T/;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the UTC offset string (e.g. "+01:00") for a given IANA timezone at
|
|
6
|
+
* a specific instant. Returns undefined if the timezone is invalid.
|
|
7
|
+
*/
|
|
8
|
+
function offsetForTimezone(tz, date) {
|
|
9
|
+
try {
|
|
10
|
+
// Format in en-US with timeZoneName to extract the UTC offset.
|
|
11
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
12
|
+
timeZone: tz,
|
|
13
|
+
timeZoneName: "longOffset",
|
|
14
|
+
}).formatToParts(date);
|
|
15
|
+
const tzPart = parts.find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
16
|
+
// Intl returns "GMT" for UTC, "GMT+1:00", "GMT-5:00", etc.
|
|
17
|
+
if (tzPart === "GMT")
|
|
18
|
+
return "+00:00";
|
|
19
|
+
const match = tzPart.match(/GMT([+-]\d{1,2}):?(\d{2})?/);
|
|
20
|
+
if (!match)
|
|
21
|
+
return undefined;
|
|
22
|
+
const hours = match[1].padStart(3, match[1][0] === "-" ? "-" : "+");
|
|
23
|
+
const minutes = match[2] ?? "00";
|
|
24
|
+
return `${hours}:${minutes}`;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function normalizeIso(raw, tz) {
|
|
5
31
|
if (ISO_TZ_RE.test(raw))
|
|
6
32
|
return raw;
|
|
7
|
-
if (ISO_DATE_RE.test(raw))
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
33
|
+
if (ISO_DATE_RE.test(raw)) {
|
|
34
|
+
const suffix = tz ? resolveOffsetSuffix(`${raw}T00:00:00`, tz) : "Z";
|
|
35
|
+
return `${raw}T00:00:00${suffix}`;
|
|
36
|
+
}
|
|
37
|
+
if (ISO_DATE_TIME_RE.test(raw)) {
|
|
38
|
+
const suffix = tz ? resolveOffsetSuffix(raw, tz) : "Z";
|
|
39
|
+
return `${raw}${suffix}`;
|
|
40
|
+
}
|
|
11
41
|
return raw;
|
|
12
42
|
}
|
|
13
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Compute the UTC offset suffix for a timezone-naive ISO string interpreted in
|
|
45
|
+
* the given IANA timezone. We do a two-pass approach: first parse as UTC to
|
|
46
|
+
* get a rough Date, then look up the real offset for that instant.
|
|
47
|
+
*/
|
|
48
|
+
function resolveOffsetSuffix(naiveIso, tz) {
|
|
49
|
+
const rough = new Date(`${naiveIso}Z`);
|
|
50
|
+
if (Number.isNaN(rough.getTime()))
|
|
51
|
+
return "Z";
|
|
52
|
+
return offsetForTimezone(tz, rough) ?? "Z";
|
|
53
|
+
}
|
|
54
|
+
export function parseAbsoluteTimeMs(input, tz) {
|
|
14
55
|
const raw = input.trim();
|
|
15
56
|
if (!raw)
|
|
16
57
|
return null;
|
|
@@ -19,6 +60,6 @@ export function parseAbsoluteTimeMs(input) {
|
|
|
19
60
|
if (Number.isFinite(n) && n > 0)
|
|
20
61
|
return Math.floor(n);
|
|
21
62
|
}
|
|
22
|
-
const parsed = Date.parse(
|
|
63
|
+
const parsed = Date.parse(normalizeIso(raw, tz));
|
|
23
64
|
return Number.isFinite(parsed) ? parsed : null;
|
|
24
65
|
}
|
|
@@ -23,7 +23,10 @@ export function computeJobNextRunAtMs(job, nowMs) {
|
|
|
23
23
|
// One-shot jobs stay due until they successfully finish.
|
|
24
24
|
if (job.state.lastStatus === "ok" && job.state.lastRunAtMs)
|
|
25
25
|
return undefined;
|
|
26
|
-
|
|
26
|
+
// Guard: if atMs is in the past, treat as expired — never fire immediately.
|
|
27
|
+
// A past atMs typically means the agent computed the timestamp incorrectly
|
|
28
|
+
// (e.g. timezone-naive ISO string interpreted as UTC).
|
|
29
|
+
return job.schedule.atMs > nowMs ? job.schedule.atMs : undefined;
|
|
27
30
|
}
|
|
28
31
|
// For "every" schedules without an explicit anchor, use the job's creation
|
|
29
32
|
// time so the schedule stays grid-aligned across daemon restarts. Without
|
package/dist/cron/service/ops.js
CHANGED
|
@@ -65,6 +65,22 @@ export async function add(state, input) {
|
|
|
65
65
|
warnIfDisabled(state, "add");
|
|
66
66
|
await ensureLoaded(state);
|
|
67
67
|
const job = createJob(state, input);
|
|
68
|
+
// If a one-shot "at" job resolved to no nextRunAtMs, the atMs was in the
|
|
69
|
+
// past. Reject it outright so the agent gets a clear error instead of a
|
|
70
|
+
// silently disabled job that never fires.
|
|
71
|
+
if (job.schedule.kind === "at" &&
|
|
72
|
+
job.enabled &&
|
|
73
|
+
job.state.nextRunAtMs == null &&
|
|
74
|
+
!job.state.lastRunAtMs) {
|
|
75
|
+
const nowMs = state.deps.nowMs();
|
|
76
|
+
const delta = nowMs - job.schedule.atMs;
|
|
77
|
+
const nowIso = new Date(nowMs).toISOString();
|
|
78
|
+
const atIso = new Date(job.schedule.atMs).toISOString();
|
|
79
|
+
state.deps.log.warn({ atMs: job.schedule.atMs, atIso, nowIso, deltaMs: delta }, "cron.add rejected: atMs is in the past — reminder would fire immediately");
|
|
80
|
+
throw new Error(`schedule.atMs resolved to ${atIso} which is ${Math.round(delta / 1000)}s in the past ` +
|
|
81
|
+
`(current time: ${nowIso}). The reminder would fire immediately. ` +
|
|
82
|
+
"Call current_time to confirm the current date and year, then retry with a future timestamp.");
|
|
83
|
+
}
|
|
68
84
|
state.store?.jobs.push(job);
|
|
69
85
|
await persist(state);
|
|
70
86
|
armTimer(state);
|
|
@@ -4,7 +4,7 @@ import { updateSessionStore } from "../../config/sessions.js";
|
|
|
4
4
|
import { listRecords } from "../../records/records-manager.js";
|
|
5
5
|
import { normalizeAccountId } from "../../routing/session-key.js";
|
|
6
6
|
import { parseAgentSessionKey } from "../../sessions/session-key-utils.js";
|
|
7
|
-
import { requireActiveWebListener } from "../../web/active-listener.js";
|
|
7
|
+
import { getActiveWebListener, requireActiveWebListener } from "../../web/active-listener.js";
|
|
8
8
|
import { stripEnvelope } from "../chat-sanitize.js";
|
|
9
9
|
import { ErrorCodes, errorShape, formatValidationErrors, validateWhatsAppConversationsParams, validateWhatsAppGroupInfoParams, validateWhatsAppMessagesParams, validateWhatsAppSendMessageParams, validateWhatsAppSetActivationParams, } from "../protocol/index.js";
|
|
10
10
|
import { loadCombinedSessionStoreForGateway, parseGroupKey } from "../session-utils.js";
|
|
@@ -239,9 +239,9 @@ export const whatsappConversationsHandlers = {
|
|
|
239
239
|
respond(true, { messages: [] });
|
|
240
240
|
return;
|
|
241
241
|
}
|
|
242
|
-
//
|
|
242
|
+
// --- Session transcript messages (agent-processed only) ---
|
|
243
243
|
const rawMessages = readSessionMessages(matchedSessionId, storePath, matchedSessionFile);
|
|
244
|
-
const
|
|
244
|
+
const transcriptMessages = [];
|
|
245
245
|
for (const raw of rawMessages) {
|
|
246
246
|
const msg = raw;
|
|
247
247
|
if (!msg.role || (msg.role !== "user" && msg.role !== "assistant"))
|
|
@@ -263,8 +263,8 @@ export const whatsappConversationsHandlers = {
|
|
|
263
263
|
}
|
|
264
264
|
if (!text.trim())
|
|
265
265
|
continue;
|
|
266
|
-
|
|
267
|
-
id:
|
|
266
|
+
transcriptMessages.push({
|
|
267
|
+
id: `transcript-${matchedSessionId}-${transcriptMessages.length}`,
|
|
268
268
|
sender,
|
|
269
269
|
senderName,
|
|
270
270
|
body: text,
|
|
@@ -272,8 +272,42 @@ export const whatsappConversationsHandlers = {
|
|
|
272
272
|
fromMe,
|
|
273
273
|
});
|
|
274
274
|
}
|
|
275
|
-
//
|
|
276
|
-
const
|
|
275
|
+
// --- Baileys in-memory store (all messages seen since gateway started) ---
|
|
276
|
+
const listener = getActiveWebListener(accountId);
|
|
277
|
+
const baileysMessages = listener?.getMessages
|
|
278
|
+
? await listener.getMessages(jid, limit * 4)
|
|
279
|
+
: [];
|
|
280
|
+
// Merge: use Baileys as the primary source for inbound messages.
|
|
281
|
+
// Keep transcript assistant (fromMe) messages that Baileys doesn't have,
|
|
282
|
+
// and deduplicate inbound messages that appear in both sources.
|
|
283
|
+
let merged;
|
|
284
|
+
if (baileysMessages.length > 0) {
|
|
285
|
+
// Build a dedup set from Baileys entries: timestamp (±5s window) + body prefix
|
|
286
|
+
const baileysSignatures = new Set();
|
|
287
|
+
for (const m of baileysMessages) {
|
|
288
|
+
// Use 5-second timestamp buckets so minor clock drift doesn't cause duplicates
|
|
289
|
+
const bucket = Math.floor(m.timestamp / 5);
|
|
290
|
+
baileysSignatures.add(`${bucket}:${m.body.slice(0, 60)}`);
|
|
291
|
+
}
|
|
292
|
+
// From transcript, keep only assistant messages not already in Baileys
|
|
293
|
+
const transcriptOnly = transcriptMessages.filter((m) => {
|
|
294
|
+
if (!m.fromMe) {
|
|
295
|
+
const bucket = Math.floor(m.timestamp / 5);
|
|
296
|
+
return !baileysSignatures.has(`${bucket}:${m.body.slice(0, 60)}`);
|
|
297
|
+
}
|
|
298
|
+
// Always include assistant messages — Baileys store has no agent responses
|
|
299
|
+
return true;
|
|
300
|
+
});
|
|
301
|
+
merged = [...baileysMessages, ...transcriptOnly];
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
// Baileys store is empty (gateway just started or account not connected) —
|
|
305
|
+
// fall back to session transcript only.
|
|
306
|
+
merged = transcriptMessages;
|
|
307
|
+
}
|
|
308
|
+
// Sort by timestamp, then return the most recent N
|
|
309
|
+
merged.sort((a, b) => a.timestamp - b.timestamp);
|
|
310
|
+
const limited = merged.slice(-limit);
|
|
277
311
|
respond(true, { messages: limited });
|
|
278
312
|
}
|
|
279
313
|
catch (err) {
|
|
@@ -88,6 +88,18 @@ export async function monitorWebInbox(options) {
|
|
|
88
88
|
inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`);
|
|
89
89
|
},
|
|
90
90
|
});
|
|
91
|
+
// In-memory message store: captures all inbound messages per JID (including
|
|
92
|
+
// history catch-up / append type) so the UI can show a complete conversation view.
|
|
93
|
+
const messageStore = new Map();
|
|
94
|
+
const MESSAGE_STORE_MAX = 500; // max entries per JID
|
|
95
|
+
const storeMessageEntry = (jid, entry) => {
|
|
96
|
+
const entries = messageStore.get(jid) ?? [];
|
|
97
|
+
entries.push(entry);
|
|
98
|
+
if (entries.length > MESSAGE_STORE_MAX) {
|
|
99
|
+
entries.splice(0, entries.length - MESSAGE_STORE_MAX);
|
|
100
|
+
}
|
|
101
|
+
messageStore.set(jid, entries);
|
|
102
|
+
};
|
|
91
103
|
const groupMetaCache = new Map();
|
|
92
104
|
const GROUP_META_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
93
105
|
const lidLookup = sock.signalRepository?.lidMapping;
|
|
@@ -177,6 +189,25 @@ export async function monitorWebInbox(options) {
|
|
|
177
189
|
const messageTimestampMs = msg.messageTimestamp
|
|
178
190
|
? Number(msg.messageTimestamp) * 1000
|
|
179
191
|
: undefined;
|
|
192
|
+
// Store in the in-memory conversation store so the UI sees all messages,
|
|
193
|
+
// including history catch-up (append type) and messages from participants
|
|
194
|
+
// that don't trigger the agent. Runs before access control.
|
|
195
|
+
const storeBody = extractText(msg.message ?? undefined);
|
|
196
|
+
if (storeBody) {
|
|
197
|
+
const fromMe = Boolean(msg.key?.fromMe);
|
|
198
|
+
const replyCtx = describeReplyContext(msg.message);
|
|
199
|
+
storeMessageEntry(remoteJid, {
|
|
200
|
+
id: id ?? `${remoteJid}-${messageTimestampMs ?? 0}`,
|
|
201
|
+
sender: senderE164 ?? participantJid ?? (fromMe ? (selfE164 ?? "") : ""),
|
|
202
|
+
senderName: msg.pushName ?? undefined,
|
|
203
|
+
body: storeBody,
|
|
204
|
+
timestamp: messageTimestampMs ? Math.floor(messageTimestampMs / 1000) : 0,
|
|
205
|
+
fromMe,
|
|
206
|
+
quoted: replyCtx
|
|
207
|
+
? { id: replyCtx.id ?? "", body: replyCtx.body, sender: replyCtx.sender }
|
|
208
|
+
: undefined,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
180
211
|
const access = await checkInboundAccessControl({
|
|
181
212
|
accountId: options.accountId,
|
|
182
213
|
from,
|
|
@@ -414,9 +445,9 @@ export async function monitorWebInbox(options) {
|
|
|
414
445
|
}
|
|
415
446
|
return entries;
|
|
416
447
|
},
|
|
417
|
-
getMessages: async (
|
|
418
|
-
|
|
419
|
-
return
|
|
448
|
+
getMessages: async (jid, limit) => {
|
|
449
|
+
const entries = messageStore.get(jid) ?? [];
|
|
450
|
+
return limit ? entries.slice(-limit) : entries;
|
|
420
451
|
},
|
|
421
452
|
getGroupMetadata: async (jid) => {
|
|
422
453
|
const meta = await sock.groupMetadata(jid);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
node_modules/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rubytech/taskmaster",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.1",
|
|
4
4
|
"description": "AI-powered business assistant for small businesses",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -79,64 +79,12 @@
|
|
|
79
79
|
"dist/license/**",
|
|
80
80
|
"dist/suggestions/**"
|
|
81
81
|
],
|
|
82
|
-
"scripts": {
|
|
83
|
-
"dev": "node scripts/run-node.mjs",
|
|
84
|
-
"postinstall": "node scripts/postinstall.js",
|
|
85
|
-
"prepack": "pnpm build && pnpm ui:build",
|
|
86
|
-
"docs:list": "node scripts/docs-list.js",
|
|
87
|
-
"docs:bin": "node scripts/build-docs-list.mjs",
|
|
88
|
-
"build": "tsc -p tsconfig.json && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts",
|
|
89
|
-
"plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
|
|
90
|
-
"release:check": "node --import tsx scripts/release-check.ts",
|
|
91
|
-
"ui:install": "node scripts/ui.js install",
|
|
92
|
-
"ui:dev": "node scripts/ui.js dev",
|
|
93
|
-
"ui:build": "node scripts/ui.js build",
|
|
94
|
-
"start": "node scripts/run-node.mjs",
|
|
95
|
-
"taskmaster": "node scripts/run-node.mjs",
|
|
96
|
-
"gateway:watch": "node scripts/watch-node.mjs gateway --force",
|
|
97
|
-
"logs": "npx tsx scripts/session-viewer.ts",
|
|
98
|
-
"gateway:dev": "TASKMASTER_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway",
|
|
99
|
-
"gateway:dev:reset": "TASKMASTER_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway --reset",
|
|
100
|
-
"tui": "node scripts/run-node.mjs tui",
|
|
101
|
-
"tui:dev": "TASKMASTER_PROFILE=dev node scripts/run-node.mjs tui",
|
|
102
|
-
"taskmaster:rpc": "node scripts/run-node.mjs agent --mode rpc --json",
|
|
103
|
-
"lint": "oxlint --type-aware src test",
|
|
104
|
-
"lint:fix": "pnpm format:fix && oxlint --type-aware --fix src test",
|
|
105
|
-
"format": "oxfmt --check src test",
|
|
106
|
-
"format:fix": "oxfmt --write src test",
|
|
107
|
-
"test": "node scripts/test-parallel.mjs",
|
|
108
|
-
"test:watch": "vitest",
|
|
109
|
-
"test:ui": "pnpm --dir ui test",
|
|
110
|
-
"test:force": "node --import tsx scripts/test-force.ts",
|
|
111
|
-
"test:coverage": "vitest run --coverage",
|
|
112
|
-
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
113
|
-
"test:live": "TASKMASTER_LIVE_TEST=1 vitest run --config vitest.live.config.ts",
|
|
114
|
-
"test:docker:onboard": "bash scripts/e2e/onboard-docker.sh",
|
|
115
|
-
"test:docker:gateway-network": "bash scripts/e2e/gateway-network-docker.sh",
|
|
116
|
-
"test:docker:live-models": "bash scripts/test-live-models-docker.sh",
|
|
117
|
-
"test:docker:live-gateway": "bash scripts/test-live-gateway-models-docker.sh",
|
|
118
|
-
"test:docker:qr": "bash scripts/e2e/qr-import-docker.sh",
|
|
119
|
-
"test:docker:doctor-switch": "bash scripts/e2e/doctor-install-switch-docker.sh",
|
|
120
|
-
"test:docker:plugins": "bash scripts/e2e/plugins-docker.sh",
|
|
121
|
-
"test:docker:cleanup": "bash scripts/test-cleanup-docker.sh",
|
|
122
|
-
"test:docker:all": "pnpm test:docker:live-models && pnpm test:docker:live-gateway && pnpm test:docker:onboard && pnpm test:docker:gateway-network && pnpm test:docker:qr && pnpm test:docker:doctor-switch && pnpm test:docker:plugins && pnpm test:docker:cleanup",
|
|
123
|
-
"test:all": "pnpm lint && pnpm build && pnpm test && pnpm test:e2e && pnpm test:live && pnpm test:docker:all",
|
|
124
|
-
"test:install:e2e": "bash scripts/test-install-sh-e2e-docker.sh",
|
|
125
|
-
"test:install:smoke": "bash scripts/test-install-sh-docker.sh",
|
|
126
|
-
"test:install:e2e:openai": "TASKMASTER_E2E_MODELS=openai bash scripts/test-install-sh-e2e-docker.sh",
|
|
127
|
-
"test:install:e2e:anthropic": "TASKMASTER_E2E_MODELS=anthropic bash scripts/test-install-sh-e2e-docker.sh",
|
|
128
|
-
"protocol:gen": "node --import tsx scripts/protocol-gen.ts",
|
|
129
|
-
"protocol:check": "pnpm protocol:gen && git diff --exit-code -- dist/protocol.schema.json",
|
|
130
|
-
"canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh",
|
|
131
|
-
"check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500"
|
|
132
|
-
},
|
|
133
82
|
"keywords": [],
|
|
134
83
|
"author": "",
|
|
135
84
|
"license": "MIT",
|
|
136
85
|
"engines": {
|
|
137
86
|
"node": ">=22.12.0"
|
|
138
87
|
},
|
|
139
|
-
"packageManager": "pnpm@10.23.0",
|
|
140
88
|
"dependencies": {
|
|
141
89
|
"@agentclientprotocol/sdk": "0.13.1",
|
|
142
90
|
"@aws-sdk/client-bedrock": "^3.975.0",
|
|
@@ -229,14 +177,6 @@
|
|
|
229
177
|
"vitest": "^4.0.18",
|
|
230
178
|
"wireit": "^0.14.12"
|
|
231
179
|
},
|
|
232
|
-
"pnpm": {
|
|
233
|
-
"minimumReleaseAge": 2880,
|
|
234
|
-
"overrides": {
|
|
235
|
-
"@sinclair/typebox": "0.34.47",
|
|
236
|
-
"hono": "4.11.4",
|
|
237
|
-
"tar": "7.5.4"
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
180
|
"vitest": {
|
|
241
181
|
"coverage": {
|
|
242
182
|
"provider": "v8",
|
|
@@ -265,5 +205,55 @@
|
|
|
265
205
|
"**/vendor/**",
|
|
266
206
|
"dist/Taskmaster.app/**"
|
|
267
207
|
]
|
|
208
|
+
},
|
|
209
|
+
"scripts": {
|
|
210
|
+
"dev": "node scripts/run-node.mjs",
|
|
211
|
+
"postinstall": "node scripts/postinstall.js",
|
|
212
|
+
"docs:list": "node scripts/docs-list.js",
|
|
213
|
+
"docs:bin": "node scripts/build-docs-list.mjs",
|
|
214
|
+
"build": "tsc -p tsconfig.json && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts",
|
|
215
|
+
"plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
|
|
216
|
+
"release:check": "node --import tsx scripts/release-check.ts",
|
|
217
|
+
"ui:install": "node scripts/ui.js install",
|
|
218
|
+
"ui:dev": "node scripts/ui.js dev",
|
|
219
|
+
"ui:build": "node scripts/ui.js build",
|
|
220
|
+
"start": "node scripts/run-node.mjs",
|
|
221
|
+
"taskmaster": "node scripts/run-node.mjs",
|
|
222
|
+
"gateway:watch": "node scripts/watch-node.mjs gateway --force",
|
|
223
|
+
"logs": "npx tsx scripts/session-viewer.ts",
|
|
224
|
+
"gateway:dev": "TASKMASTER_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway",
|
|
225
|
+
"gateway:dev:reset": "TASKMASTER_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway --reset",
|
|
226
|
+
"tui": "node scripts/run-node.mjs tui",
|
|
227
|
+
"tui:dev": "TASKMASTER_PROFILE=dev node scripts/run-node.mjs tui",
|
|
228
|
+
"taskmaster:rpc": "node scripts/run-node.mjs agent --mode rpc --json",
|
|
229
|
+
"lint": "oxlint --type-aware src test",
|
|
230
|
+
"lint:fix": "pnpm format:fix && oxlint --type-aware --fix src test",
|
|
231
|
+
"format": "oxfmt --check src test",
|
|
232
|
+
"format:fix": "oxfmt --write src test",
|
|
233
|
+
"test": "node scripts/test-parallel.mjs",
|
|
234
|
+
"test:watch": "vitest",
|
|
235
|
+
"test:ui": "pnpm --dir ui test",
|
|
236
|
+
"test:force": "node --import tsx scripts/test-force.ts",
|
|
237
|
+
"test:coverage": "vitest run --coverage",
|
|
238
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
239
|
+
"test:live": "TASKMASTER_LIVE_TEST=1 vitest run --config vitest.live.config.ts",
|
|
240
|
+
"test:docker:onboard": "bash scripts/e2e/onboard-docker.sh",
|
|
241
|
+
"test:docker:gateway-network": "bash scripts/e2e/gateway-network-docker.sh",
|
|
242
|
+
"test:docker:live-models": "bash scripts/test-live-models-docker.sh",
|
|
243
|
+
"test:docker:live-gateway": "bash scripts/test-live-gateway-models-docker.sh",
|
|
244
|
+
"test:docker:qr": "bash scripts/e2e/qr-import-docker.sh",
|
|
245
|
+
"test:docker:doctor-switch": "bash scripts/e2e/doctor-install-switch-docker.sh",
|
|
246
|
+
"test:docker:plugins": "bash scripts/e2e/plugins-docker.sh",
|
|
247
|
+
"test:docker:cleanup": "bash scripts/test-cleanup-docker.sh",
|
|
248
|
+
"test:docker:all": "pnpm test:docker:live-models && pnpm test:docker:live-gateway && pnpm test:docker:onboard && pnpm test:docker:gateway-network && pnpm test:docker:qr && pnpm test:docker:doctor-switch && pnpm test:docker:plugins && pnpm test:docker:cleanup",
|
|
249
|
+
"test:all": "pnpm lint && pnpm build && pnpm test && pnpm test:e2e && pnpm test:live && pnpm test:docker:all",
|
|
250
|
+
"test:install:e2e": "bash scripts/test-install-sh-e2e-docker.sh",
|
|
251
|
+
"test:install:smoke": "bash scripts/test-install-sh-docker.sh",
|
|
252
|
+
"test:install:e2e:openai": "TASKMASTER_E2E_MODELS=openai bash scripts/test-install-sh-e2e-docker.sh",
|
|
253
|
+
"test:install:e2e:anthropic": "TASKMASTER_E2E_MODELS=anthropic bash scripts/test-install-sh-e2e-docker.sh",
|
|
254
|
+
"protocol:gen": "node --import tsx scripts/protocol-gen.ts",
|
|
255
|
+
"protocol:check": "pnpm protocol:gen && git diff --exit-code -- dist/protocol.schema.json",
|
|
256
|
+
"canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh",
|
|
257
|
+
"check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500"
|
|
268
258
|
}
|
|
269
|
-
}
|
|
259
|
+
}
|
package/scripts/install.sh
CHANGED
|
File without changes
|
|
@@ -105,14 +105,24 @@ The reminder's payload text should reference the event clearly so the recipient
|
|
|
105
105
|
|
|
106
106
|
### Computing the cron timestamp safely
|
|
107
107
|
|
|
108
|
-
The `cron` tool
|
|
108
|
+
The `cron` tool accepts `atMs` as either a numeric Unix timestamp in milliseconds or an **ISO 8601 datetime string**. The ISO string approach is strongly preferred because it avoids epoch arithmetic errors.
|
|
109
109
|
|
|
110
|
-
**Always call `current_time` before scheduling a cron job
|
|
110
|
+
**Always call `current_time` before scheduling a cron job** to confirm the current date and year.
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
**Preferred approach — ISO string:**
|
|
113
|
+
|
|
114
|
+
1. Call `current_time` to confirm today's date and year.
|
|
115
|
+
2. Pass `atMs` as an ISO 8601 string: `"2026-03-11T09:00:00"` (no timezone suffix needed — the system uses the user's configured timezone automatically).
|
|
116
|
+
3. Double-check that the year in your string matches the year from `current_time`.
|
|
117
|
+
|
|
118
|
+
The system rejects timestamps that resolve to the past — you will get a clear error with the current time if this happens.
|
|
119
|
+
|
|
120
|
+
**Fallback approach — numeric timestamp:**
|
|
121
|
+
|
|
122
|
+
1. Get `unix` from `current_time`.
|
|
123
|
+
2. Calculate offset in seconds (days × 86400 + hours × 3600 + minutes × 60).
|
|
124
|
+
3. Compute: `atMs = (unix + offset) × 1000`.
|
|
125
|
+
4. Verify `atMs > unix × 1000` before submitting.
|
|
116
126
|
|
|
117
127
|
Never compute a Unix timestamp from a fixed epoch date (e.g. "January 1, 2026 = X"). Epoch arithmetic done from scratch is the most common source of year-off errors.
|
|
118
128
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: whatsapp-business
|
|
3
|
+
description: WhatsApp Business Cloud API setup — guides the user through creating a Meta Business Portfolio, registering a phone number, generating a permanent System User access token, configuring the webhook, and entering credentials into Taskmaster. Use when a user wants to connect WhatsApp via Meta's official Cloud API (verified badge, no QR dependency).
|
|
4
|
+
metadata: {"taskmaster":{"emoji":"📱"}}
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# WhatsApp Business Cloud API
|
|
8
|
+
|
|
9
|
+
Handles the full Meta Cloud API setup. This is the official WhatsApp Business Platform — messages arrive via Meta webhooks instead of a QR-linked device. It unlocks a verified green checkmark, message templates for proactive outreach, and eliminates the phone dependency entirely.
|
|
10
|
+
|
|
11
|
+
## When to activate
|
|
12
|
+
|
|
13
|
+
- User asks about connecting WhatsApp via Meta's official API or Business Platform
|
|
14
|
+
- User mentions "Cloud API", "WABA", "Meta Business Manager", "verified badge", or "green checkmark"
|
|
15
|
+
- User wants WhatsApp without needing a physical phone linked
|
|
16
|
+
- User has a dedicated number for a business WhatsApp account
|
|
17
|
+
- User asks why they need a separate number for Cloud API
|
|
18
|
+
|
|
19
|
+
## What it unlocks
|
|
20
|
+
|
|
21
|
+
- **Verified business profile** — Meta's green checkmark badge builds customer trust
|
|
22
|
+
- **No phone dependency** — no linked device, no QR scan every 14 days
|
|
23
|
+
- **Message templates** — send proactive outreach messages to opted-in customers
|
|
24
|
+
- **Higher reliability** — webhooks from Meta's infrastructure, not a linked device session
|
|
25
|
+
|
|
26
|
+
## Important: Number exclusivity
|
|
27
|
+
|
|
28
|
+
A phone number can only be on **one** WhatsApp connection at a time. A number registered with Meta Cloud API cannot also be used with Baileys (QR). The two providers can coexist on one Taskmaster instance but must use different phone numbers.
|
|
29
|
+
|
|
30
|
+
## Key Storage
|
|
31
|
+
|
|
32
|
+
Cloud API credentials are stored directly in the Taskmaster config, not in API key slots. Use the Setup page (WhatsApp → gear icon → Add Cloud API Account) to enter them. The four required values are:
|
|
33
|
+
|
|
34
|
+
| Field | What it is | Where to find it |
|
|
35
|
+
|-------|-----------|-----------------|
|
|
36
|
+
| Phone Number ID | Numeric ID for the registered number | WhatsApp Manager → Phone Numbers tab |
|
|
37
|
+
| Business Account ID | Your WABA (WhatsApp Business Account) ID | WhatsApp Manager → Business Account section |
|
|
38
|
+
| Access Token | Permanent System User token | Meta Business Settings → System Users |
|
|
39
|
+
| Webhook Verify Token | A string you define (shared secret) | You generate this; enter same value in Taskmaster and Meta |
|
|
40
|
+
|
|
41
|
+
## References
|
|
42
|
+
|
|
43
|
+
| Task | When to use | Reference |
|
|
44
|
+
|------|-------------|-----------|
|
|
45
|
+
| Full Meta Cloud API setup | Credentials not yet entered or user starting from scratch | `references/setup-guide.md` |
|
|
46
|
+
|
|
47
|
+
Load the reference and follow it step by step.
|