@rubytech/taskmaster 1.13.1 → 1.13.3

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.13.1",
3
- "commit": "d92801f174160fea1fbe6d2d8cfef7df8238a4ae",
4
- "builtAt": "2026-03-03T14:12:45.492Z"
2
+ "version": "1.13.3",
3
+ "commit": "75bd5b9c88a61e9af0298b0c7a3299cc047a6a37",
4
+ "builtAt": "2026-03-03T17:22:53.401Z"
5
5
  }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Checks the combined gate: is the public agent currently able to respond?
3
+ *
4
+ * publicAgentEnabled only applies when opening hours are enabled — it is an
5
+ * override within the opening hours context. When opening hours are disabled,
6
+ * publicAgentEnabled is ignored (the Setup page controls agent behaviour).
7
+ */
8
+ export function isPublicAgentActive(business, now = new Date()) {
9
+ const oh = business?.openingHours;
10
+ if (!oh?.enabled) {
11
+ return isWithinOpeningHours(oh, now);
12
+ }
13
+ // Opening hours are enabled — check the per-schedule agent toggle.
14
+ if (business?.publicAgentEnabled === false) {
15
+ return { open: false, reason: "public agent paused" };
16
+ }
17
+ return isWithinOpeningHours(oh, now);
18
+ }
19
+ /**
20
+ * Pure function: checks whether the current time falls within business opening hours.
21
+ * Returns { open, reason } — reason is for logging only, never shown to customers.
22
+ */
23
+ export function isWithinOpeningHours(config, now = new Date()) {
24
+ if (!config || !config.enabled) {
25
+ return { open: true, reason: "opening hours disabled" };
26
+ }
27
+ // Resolve current date/time in the configured timezone (falls back to system tz).
28
+ const tz = config.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
29
+ const formatter = new Intl.DateTimeFormat("en-GB", {
30
+ timeZone: tz,
31
+ year: "numeric",
32
+ month: "2-digit",
33
+ day: "2-digit",
34
+ hour: "2-digit",
35
+ minute: "2-digit",
36
+ hour12: false,
37
+ weekday: "long",
38
+ });
39
+ const parts = Object.fromEntries(formatter.formatToParts(now).map((p) => [p.type, p.value]));
40
+ const isoDate = `${parts.year}-${parts.month}-${parts.day}`;
41
+ const currentTime = `${parts.hour}:${parts.minute}`;
42
+ const weekday = parts.weekday.toLowerCase();
43
+ // 1. Check closed dates.
44
+ if (config.closedDates?.includes(isoDate)) {
45
+ return { open: false, reason: `closed date: ${isoDate}` };
46
+ }
47
+ // 2. Check weekly schedule.
48
+ const daySchedule = config.schedule[weekday];
49
+ if (daySchedule === null || daySchedule === undefined) {
50
+ return { open: false, reason: `closed on ${weekday}s` };
51
+ }
52
+ // 3. Check time window (start inclusive, end exclusive).
53
+ if (currentTime < daySchedule.start) {
54
+ return { open: false, reason: `opens at ${daySchedule.start}` };
55
+ }
56
+ if (currentTime >= daySchedule.end) {
57
+ return { open: false, reason: `closed after ${daySchedule.end}` };
58
+ }
59
+ return { open: true, reason: "within opening hours" };
60
+ }
package/dist/entry.js CHANGED
@@ -1,7 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
+ import { mkdirSync, appendFileSync } from "node:fs";
3
4
  import path from "node:path";
4
5
  import process from "node:process";
6
+ /**
7
+ * Write a crash message to /tmp/taskmaster/crash.log so startup failures
8
+ * are visible even when systemd journal is unavailable (e.g. on the Pi).
9
+ */
10
+ function writeCrashLog(label, error) {
11
+ try {
12
+ const msg = error instanceof Error ? (error.stack ?? error.message) : String(error);
13
+ const dir = "/tmp/taskmaster";
14
+ mkdirSync(dir, { recursive: true });
15
+ appendFileSync(path.join(dir, "crash.log"), `[${new Date().toISOString()}] ${label}: ${msg}\n`);
16
+ }
17
+ catch {
18
+ // Best-effort — nothing we can do if /tmp is unwritable.
19
+ }
20
+ }
21
+ process.on("uncaughtException", (err) => {
22
+ writeCrashLog("uncaughtException", err);
23
+ console.error("[taskmaster] uncaughtException:", err);
24
+ process.exit(1);
25
+ });
26
+ process.on("unhandledRejection", (reason) => {
27
+ writeCrashLog("unhandledRejection", reason);
28
+ console.error("[taskmaster] unhandledRejection:", reason);
29
+ process.exit(1);
30
+ });
5
31
  import { applyCliProfileEnv, parseCliProfileArgs } from "./cli/profile.js";
6
32
  import { isTruthyEnvValue } from "./infra/env.js";
7
33
  import { installProcessWarningFilter } from "./infra/warnings.js";
@@ -54,6 +80,7 @@ function ensureExperimentalWarningSuppressed() {
54
80
  process.exit(code ?? 1);
55
81
  });
56
82
  child.once("error", (error) => {
83
+ writeCrashLog("Failed to respawn CLI", error);
57
84
  console.error("[taskmaster] Failed to respawn CLI:", error instanceof Error ? (error.stack ?? error.message) : error);
58
85
  process.exit(1);
59
86
  });
@@ -134,6 +161,7 @@ if (!ensureExperimentalWarningSuppressed()) {
134
161
  import("./cli/run-main.js")
135
162
  .then(({ runCli }) => runCli(process.argv))
136
163
  .catch((error) => {
164
+ writeCrashLog("Failed to start CLI", error);
137
165
  console.error("[taskmaster] Failed to start CLI:", error instanceof Error ? (error.stack ?? error.message) : error);
138
166
  process.exitCode = 1;
139
167
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.13.1",
3
+ "version": "1.13.3",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -75,6 +75,7 @@
75
75
  "dist/whatsapp/**",
76
76
  "dist/records/**",
77
77
  "dist/filler/**",
78
+ "dist/business/**",
78
79
  "dist/license/**",
79
80
  "dist/suggestions/**"
80
81
  ],
@@ -23,9 +23,11 @@ Before doing anything else:
23
23
  ## Operational Focus Areas
24
24
 
25
25
  ### Bookings
26
+ - Booking records live at `bookings/{job-id}.md` in memory. Each tracks: route, fare, driver, status, timestamps, and ratings.
26
27
  - Summarise recent booking activity: confirmed, completed, cancelled, no-shows
27
28
  - Flag bookings where the driver didn't show or the tourist complained
28
29
  - Track conversion patterns: how many enquiries become bookings
30
+ - Review reminder compliance: did drivers confirm when reminded? Identify drivers who frequently miss reminders.
29
31
 
30
32
  ### Drivers
31
33
  - Monitor driver ratings across all five dimensions (cleanliness, politeness, safety, knowledge, punctuality)
@@ -30,7 +30,9 @@ When a tourist requests a ride:
30
30
  4. **Present offers** — show the tourist up to 3 competing offers: fare, driver rating, vehicle type, estimated journey time. No driver personal details at this stage.
31
31
  5. **Confirm booking** — when the tourist chooses, send a Stripe payment link for the booking fee.
32
32
  6. **Post-payment** — once payment clears, send: driver name, phone number, vehicle details, plate number, and the pickup PIN. Explain how PIN verification works.
33
- 7. **Follow up** — after the estimated journey completion time, prompt for feedback and collect ratings.
33
+ 7. **Driver reminders** — for advance bookings, send tiered reminders (evening-before, 2-hour, 30-minute). Escalate immediately if a driver doesn't confirm.
34
+ 8. **Record the booking** — write a structured record to `bookings/{job-id}.md` on confirmation. Update at each lifecycle event (reminder, pickup, completion, rating).
35
+ 9. **Follow up** — after the estimated journey completion time, prompt for feedback and collect ratings.
34
36
 
35
37
  See the zanzi-taxi skill references for detailed behaviour at each phase.
36
38
 
@@ -37,3 +37,5 @@ This skill applies whenever a tourist:
37
37
  - **Job ID on every driver message.** Prepend `[BGL-XXXX]` to every message sent to a driver.
38
38
  - **Confirm before acting.** Always get explicit tourist confirmation before booking. Never auto-confirm.
39
39
  - **Knowledge base is truth.** If the knowledge base doesn't cover a route or destination, say so.
40
+ - **Remind drivers before pickup.** Advance bookings get tiered reminders (evening-before, 2-hour, 30-minute). Escalate if no confirmation.
41
+ - **Record every booking.** Write a structured record to `bookings/{job-id}.md` on confirmation. Update at every lifecycle event.
@@ -63,6 +63,42 @@ Confirm the driver has received:
63
63
  - Fare confirmed
64
64
  - QR code (encodes the tourist's PIN)
65
65
 
66
+ ## Phase 6: Driver Pickup Reminders
67
+
68
+ For advance bookings (pickup time is more than 1 hour from confirmation):
69
+
70
+ 1. **Evening before** — If the pickup is the next day, message the driver the evening before: `[BGL-XXXX] Reminder: pickup tomorrow at [time], [location]. Passenger: [name], [passengers] pax. Please confirm you're available.`
71
+ 2. **2 hours before** — Message the driver: `[BGL-XXXX] Pickup in 2 hours at [location]. Passenger: [name]. Please confirm.`
72
+ 3. **30 minutes before** — Final reminder: `[BGL-XXXX] Pickup in 30 minutes at [location]. Passenger [name] is expecting you.`
73
+
74
+ If the driver does not confirm the evening-before or 2-hour reminder, escalate:
75
+ - Attempt to reach the driver again
76
+ - If no response, begin sourcing a replacement driver immediately
77
+ - Notify the tourist only when you have a confirmed replacement or have exhausted options — don't create anxiety prematurely
78
+
79
+ For same-day bookings with less than 2 hours lead time, send a single confirmation reminder 15 minutes before pickup.
80
+
81
+ Also message the tourist at the 2-hour mark for advance bookings: "Your ride is confirmed for [time]. [Driver name] will be at [location]." This reassures them without requiring action.
82
+
83
+ ## Phase 7: Booking Record
84
+
85
+ Every confirmed booking must be recorded in memory for audit. Store:
86
+
87
+ - **Job ID** (e.g. `BGL-0042`)
88
+ - **Tourist** — WhatsApp ID, name if given, number of passengers
89
+ - **Route** — pickup location, destination
90
+ - **Pickup datetime** — agreed date and time
91
+ - **Driver** — name, WhatsApp number, vehicle, plate
92
+ - **Fare** — negotiated amount, booking fee amount, Stripe payment ID
93
+ - **PIN** — the 4-digit pickup code
94
+ - **Status** — one of: `confirmed`, `reminded`, `picked_up`, `completed`, `cancelled`, `no_show`
95
+ - **Timestamps** — when each status transition occurred
96
+ - **Rating** — post-ride ratings (added after Phase 7 feedback)
97
+
98
+ Write the booking record to memory at `bookings/{job-id}.md` on confirmation. Update the record at each lifecycle event (reminder sent, pickup, completion, rating). This creates a complete audit trail per booking.
99
+
100
+ The admin agent uses these records for operational oversight — booking summaries, driver reliability tracking, and dispute resolution.
101
+
66
102
  ## Edge Cases
67
103
 
68
104
  **Tourist wants to cancel after payment:** Explain the booking fee is non-refundable (commitment device for both sides). Offer to reschedule at no extra charge.