@rubytech/taskmaster 1.17.0 → 1.17.4

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-Beuhzjy_.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-koe4eKhk.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-XqRo9tNW.css">
11
11
  </head>
12
12
  <body>
@@ -103,7 +103,11 @@ export async function executeJob(state, job, nowMs, opts) {
103
103
  }
104
104
  if (job.sessionTarget === "isolated") {
105
105
  const prefix = job.isolation?.postToMainPrefix?.trim() || "Cron";
106
- const mode = job.isolation?.postToMainMode ?? "summary";
106
+ const configuredMode = job.isolation?.postToMainMode ?? "summary";
107
+ // When delivery was skipped (best-effort, no external channel configured),
108
+ // promote to "full" so the admin sees the actual report in their main chat
109
+ // instead of a delivery-error summary.
110
+ const mode = status === "skipped" && outputText ? "full" : configuredMode;
107
111
  let body = (summary ?? err ?? status).trim();
108
112
  if (mode === "full") {
109
113
  // Prefer full agent output if available; fall back to summary.
@@ -27,6 +27,8 @@ const PROVIDER_CATALOG = [
27
27
  { id: "brave", name: "Brave", category: "Web Search" },
28
28
  { id: "elevenlabs", name: "ElevenLabs", category: "Voice" },
29
29
  { id: "brevo", name: "Brevo", category: "Email" },
30
+ { id: "stripe", name: "Stripe", category: "Payments" },
31
+ { id: "stripe_webhook_secret", name: "Stripe Webhook Secret", category: "Payments" },
30
32
  ];
31
33
  const VALID_PROVIDER_IDS = new Set(PROVIDER_CATALOG.map((p) => p.id));
32
34
  export const apikeysHandlers = {
@@ -10,7 +10,7 @@ import { CONFIG_PATH_TASKMASTER, isNixMode, loadConfig, migrateLegacyConfig, rea
10
10
  import { VERSION } from "../version.js";
11
11
  import { isDiagnosticsEnabled } from "../infra/diagnostic-events.js";
12
12
  import { logAcceptedEnvOption } from "../infra/env.js";
13
- import { reconcileAgentContactTools, reconcileBeaglePublicTools, reconcileQrGenerateTool, reconcileStaleToolEntries, } from "../config/agent-tools-reconcile.js";
13
+ import { reconcileAgentContactTools, reconcileBeaglePublicTools, reconcileControlPanelTools, reconcileQrGenerateTool, reconcileStaleToolEntries, } from "../config/agent-tools-reconcile.js";
14
14
  import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
15
15
  import { clearAgentRunContext, onAgentEvent } from "../infra/agent-events.js";
16
16
  import { onHeartbeatEvent } from "../infra/heartbeat-events.js";
@@ -198,6 +198,21 @@ export async function startGatewayServer(port = 18789, opts = {}) {
198
198
  log.warn(`gateway: failed to persist qr_generate tool reconciliation: ${String(err)}`);
199
199
  }
200
200
  }
201
+ // Upgrade admin agents from individual control-panel tools to group:control-panel.
202
+ // Agents set up before the group existed miss tools added to it later (e.g. logs_read).
203
+ const cpReconcile = reconcileControlPanelTools({ config: configSnapshot.config });
204
+ if (cpReconcile.changes.length > 0) {
205
+ try {
206
+ await writeConfigFile(cpReconcile.config);
207
+ configSnapshot = await readConfigFileSnapshot();
208
+ log.info(`gateway: reconciled control-panel tools:\n${cpReconcile.changes
209
+ .map((entry) => `- ${entry}`)
210
+ .join("\n")}`);
211
+ }
212
+ catch (err) {
213
+ log.warn(`gateway: failed to persist control-panel tools reconciliation: ${String(err)}`);
214
+ }
215
+ }
201
216
  // Stamp config with running version on startup so upgrades keep the stamp current.
202
217
  const storedVersion = configSnapshot.config.meta?.lastTouchedVersion;
203
218
  if (configSnapshot.exists && storedVersion !== VERSION) {
@@ -253,12 +253,15 @@ function buildTripRequestInstruction(fields, resolvedAccountId) {
253
253
  `4. For each selected driver, update their status to awaiting_response via memory_write\n` +
254
254
  `5. Write shared/active-negotiations/{driver-phone}.md with job_id: ${jobId} for each driver\n` +
255
255
  `6. Message each driver in Swahili with the route details, pickup time, passengers, and job ID [${jobId}]\n` +
256
- ` Use the message tool with accountId: "${accountId}"\n` +
256
+ ` Use the message tool with channel: "whatsapp", accountId: "${accountId}"\n` +
257
257
  `7. Message the tourist at ${touristPhone} confirming you've contacted drivers and are waiting for quotes\n` +
258
- ` Use the message tool with accountId: "${accountId}"\n` +
258
+ ` Use the message tool with channel: "whatsapp", accountId: "${accountId}"\n` +
259
259
  `8. When drivers reply with quotes (dispatched to this session), compile the offers\n` +
260
- `9. Message the tourist with up to 3 competing offers: fare, driver rating, vehicle type, estimated journey time\n` +
261
- ` Do NOT reveal driver name, phone, or plate — those are gated by payment\n`);
260
+ `9. Message the tourist at ${touristPhone} with up to 3 competing offers: fare, vehicle type, driver rating, estimated journey time\n` +
261
+ ` Use the message tool with channel: "whatsapp", accountId: "${accountId}"\n` +
262
+ ` Cross-agent echo will relay the message to the tourist's active session automatically\n` +
263
+ ` Do NOT reveal driver name, phone, or plate — those are gated by payment\n` +
264
+ ` NOTE: Do NOT write a dispatch file for the offers — message the tourist directly\n`);
262
265
  }
263
266
  function buildBookingConfirmInstruction(fields, resolvedAccountId) {
264
267
  const jobId = fields.job_id ?? "UNKNOWN";
@@ -278,7 +281,7 @@ function buildBookingConfirmInstruction(fields, resolvedAccountId) {
278
281
  `1. Load the stripe skill and generate a Checkout Session for the booking fee\n` +
279
282
  ` Set metadata: booking_id="${jobId}", tourist_phone="${touristPhone}"\n` +
280
283
  `2. Message the tourist at ${touristPhone} with the payment link and booking terms\n` +
281
- ` Use the message tool with accountId: "${accountId}"\n` +
284
+ ` Use the message tool with channel: "whatsapp", accountId: "${accountId}"\n` +
282
285
  `3. Record the booking details in shared/bookings/${jobId}.md via memory_write\n` +
283
286
  `4. Clear the active negotiation files for drivers NOT selected (delete their shared/active-negotiations/{phone}.md)\n`);
284
287
  }
@@ -293,9 +296,9 @@ function buildPaymentConfirmedInstruction(params) {
293
296
  `1. Read the booking record at shared/bookings/${bookingId}.md for driver details\n` +
294
297
  `2. Generate the pickup PIN and QR code (see references/pin-qr.md)\n` +
295
298
  `3. Message the tourist at ${touristPhone} with: driver name, phone, vehicle details, plate, and pickup PIN\n` +
296
- ` Use the message tool with accountId: "${accountId}"\n` +
299
+ ` Use the message tool with channel: "whatsapp", accountId: "${accountId}"\n` +
297
300
  `4. Message the driver with: passenger name, pickup time/location, fare, and QR code URL\n` +
298
- ` Use the message tool with accountId: "${accountId}"\n` +
301
+ ` Use the message tool with channel: "whatsapp", accountId: "${accountId}"\n` +
299
302
  `5. Update the booking record status to "confirmed"\n` +
300
303
  `6. Clear the active negotiation file for the driver (shared/active-negotiations/{phone}.md)\n`);
301
304
  }
@@ -337,6 +340,10 @@ async function handleMemoryAdd(event) {
337
340
  return;
338
341
  }
339
342
  const fields = parseDispatchFile(content);
343
+ // Normalize: public agents may write tourist_id instead of tourist_phone
344
+ if (!fields.tourist_phone && fields.tourist_id) {
345
+ fields.tourist_phone = fields.tourist_id;
346
+ }
340
347
  // Load config (memory:add events don't carry cfg)
341
348
  let cfg;
342
349
  try {
@@ -427,8 +434,15 @@ async function handleDriverReply(event) {
427
434
  `Driver phone: ${senderPhone}\n` +
428
435
  `Message: ${text}\n\n` +
429
436
  `Process this reply in the context of the ongoing negotiation for ${jobId}.\n` +
430
- `If this is a fare quote, note it and compile offers when ready.\n` +
431
- `If the driver is declining, update their status and active negotiation index.\n`;
437
+ `If this is a fare quote:\n` +
438
+ ` - Record the quote in the driver's memory profile\n` +
439
+ ` - When all expected quotes are in (or after a reasonable wait), compile the offers\n` +
440
+ ` - Message the tourist directly using the message tool with the compiled offers\n` +
441
+ ` The tourist phone is in the earlier trip-request message in this session\n` +
442
+ ` Use the message tool with channel: "whatsapp", accountId: "${accountId}"\n` +
443
+ ` Do NOT write a dispatch file — use the message tool to send the offers directly\n` +
444
+ ` Cross-agent echo will relay it to the tourist's active session automatically\n` +
445
+ `If the driver is declining, update their status in memory and delete their shared/active-negotiations/{phone-digits}.md file.\n`;
432
446
  console.log(`[ride-dispatch] Driver reply from ${senderPhone} for ${jobId}, dispatching to admin agent "${adminAgentId}"`);
433
447
  // Fire and forget — suppress is already set, caller will skip processForRoute
434
448
  dispatchToAdmin({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.17.0",
3
+ "version": "1.17.4",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -77,9 +77,12 @@ When you receive `[System: Ride Dispatch — Trip Request]`:
77
77
  - Update their status to `awaiting_response` via `memory_write`
78
78
  - Write `shared/active-negotiations/{driver-phone-digits}.md` with `job_id`, `driver_name`, and `contacted_at`
79
79
  5. Message each driver in Swahili via the `message` tool with route details, pickup time, passengers, and job ID
80
- 6. Message the tourist confirming drivers have been contacted and quotes are being gathered
81
- 7. When driver replies arrive (dispatched as `[System: Ride Dispatch Driver Reply]`), compile offers
82
- 8. Message the tourist with up to 3 competing offers fare, rating, vehicle type, journey time. Do NOT reveal driver name, phone, or plate (gated by payment)
80
+ 6. When driver replies arrive (dispatched as `[System: Ride Dispatch Driver Reply]`), compile offers
81
+ 7. Message the tourist at `tourist_phone` using the `message` tool with `accountId` from this dispatch
82
+ - Include up to 3 offers: fare, vehicle type, driver rating, estimated journey time
83
+ - Do NOT reveal driver name, phone, or plate — those are gated by payment
84
+ - Cross-agent echo will relay the WhatsApp message to the tourist's active session automatically
85
+ - Do NOT write a dispatch file for the offers — message directly
83
86
 
84
87
  ### Booking Confirmation
85
88
 
@@ -87,18 +90,21 @@ When you receive `[System: Ride Dispatch — Booking Confirmation]`:
87
90
 
88
91
  1. Load the `stripe` skill and generate a Checkout Session for the booking fee
89
92
  - Set metadata: `booking_id`, `tourist_phone`, `account_id` (for webhook routing)
90
- 2. Message the tourist with the payment link and booking terms
93
+ 2. Message the tourist at `tourist_phone` using the `message` tool with the payment link and booking terms
94
+ - Cross-agent echo relays the message to the tourist's active session automatically
91
95
  3. Record booking details in `shared/bookings/{job-id}.md` via `memory_write`
96
+ - Include `tourist_phone` for post-payment messaging
92
97
  4. Clear `shared/active-negotiations/{phone}.md` for drivers NOT selected
93
98
 
94
99
  ### Payment Confirmed
95
100
 
96
101
  When you receive `[System: Ride Dispatch — Payment Confirmed]`:
97
102
 
98
- 1. Read the booking record at `shared/bookings/{job-id}.md` for driver details
103
+ 1. Read the booking record at `shared/bookings/{job-id}.md` for driver details and `tourist_session_key`
99
104
  2. Generate the pickup PIN and QR code (see `references/pin-qr.md`)
100
- 3. Message the tourist with: driver name, phone, vehicle details, plate, and pickup PIN
101
- 4. Message the driver with: passenger name, pickup time/location, fare, and QR code URL
105
+ 3. Message the tourist at `tourist_phone` (from the booking record) using the `message` tool with driver details and pickup PIN
106
+ - Cross-agent echo relays to the tourist's active session automatically
107
+ 4. Message the driver with passenger name, pickup time/location, fare, and QR code URL
102
108
  5. Update the booking record status to `confirmed`
103
109
  6. Clear the active negotiation file for the confirmed driver
104
110
 
@@ -106,7 +112,9 @@ When you receive `[System: Ride Dispatch — Payment Confirmed]`:
106
112
 
107
113
  When you receive `[System: Ride Dispatch — Driver Reply]`:
108
114
 
109
- Process the driver's message in the context of the ongoing negotiation. If it's a fare quote, note it. When enough quotes are gathered (or after a reasonable wait), compile and send offers to the tourist. If the driver declines, update their status and remove their active negotiation file.
115
+ 1. If the driver is quoting a fare: record it in their memory profile (`memory_write` on `drivers/{name}.md`)
116
+ 2. When all expected quotes are received (or after a reasonable wait): compile the offers and message the tourist directly using the `message` tool at the `tourist_phone` from the trip-request earlier in this session — do NOT write a dispatch file
117
+ 3. If the driver is declining: update their status in memory to `idle` and delete their `shared/active-negotiations/{phone-digits}.md` file
110
118
 
111
119
  ### Active Negotiation Index
112
120
 
@@ -23,6 +23,7 @@ Before responding:
23
23
  - Proactively look up contact details for people mentioned in system messages
24
24
 
25
25
  If a user asks for information about another person, politely decline — it would violate strict security protocols.
26
+ **Declined conversations stay declined.** When you've correctly identified a message as misdirected or outside your scope and responded accordingly, that conversation is concluded. If the same sender follows up with requests of any kind, decline and end the conversation. Performing services for someone you've already turned away contradicts your own assessment and opens the service to general-purpose misuse.
26
27
 
27
28
  ---
28
29
 
@@ -54,6 +55,10 @@ The knowledge base is the single source of truth. If it doesn't cover what's bei
54
55
 
55
56
  This is a **prescribed workflow**. When a tourist requests a ride, execute every step in order. Do not stop, skip, or defer any step. The only reason to pause is to ask the tourist for missing information or if the tourist explicitly cancels.
56
57
 
58
+ ### Step 0 — Confirm identification
59
+
60
+ All users are verified before they reach you — WhatsApp users by their phone number, webchat users by OTP verification. The tourist's phone number is their session identifier. Use it as `tourist_phone` in dispatch files.
61
+
57
62
  ### Step 1 — Capture the request
58
63
 
59
64
  Gather: pickup location, destination, date/time, number of passengers, luggage, special requests. If the tourist gave everything in one message, proceed immediately. If anything is missing, ask — then resume from Step 2 when they reply.
@@ -74,7 +79,7 @@ Write a dispatch file via `memory_write` to `shared/dispatch/{job-id}-trip-reque
74
79
  # Dispatch: Trip Request
75
80
  job_id: BGL-XXXX
76
81
  phase: trip-request
77
- tourist_phone: +XXXXXXXXXXX
82
+ tourist_phone: [the tourist's phone number — from WhatsApp session or OTP-verified phone]
78
83
  tourist_name: [name if given]
79
84
  pickup: [pickup location]
80
85
  destination: [destination]
@@ -93,7 +98,7 @@ After writing the dispatch file, tell the tourist: "I'm reaching out to our driv
93
98
 
94
99
  ### Step 6 — Present offers
95
100
 
96
- When driver offers appear in your conversation (injected by the operations agent), present up to 3 competing offers to the tourist: fare, driver rating, vehicle type, estimated journey time. No driver personal details at this stage — name, phone, and plate are gated by payment. See `references/ride-matching.md` for formatting.
101
+ When driver offers appear in your conversation as a `[System: Ride Dispatch — Driver Offers]` message, present up to 3 competing offers to the tourist: fare, driver rating, vehicle type, estimated journey time. No driver personal details at this stage — name, phone, and plate are gated by payment. See `references/ride-matching.md` for formatting.
97
102
 
98
103
  ### Step 7 — Confirm booking
99
104
 
@@ -103,7 +108,7 @@ When the tourist chooses an offer, confirm the details and write a booking confi
103
108
  # Dispatch: Booking Confirmation
104
109
  job_id: BGL-XXXX
105
110
  phase: booking-confirm
106
- tourist_phone: +XXXXXXXXXXX
111
+ tourist_phone: [same phone used in trip-request]
107
112
  tourist_name: [name]
108
113
  driver_name: [selected driver name from offer]
109
114
  driver_phone: [selected driver phone from offer]
@@ -113,11 +118,11 @@ account_id: [your account ID]
113
118
 
114
119
  ### Step 8 — Payment
115
120
 
116
- The operations agent will generate a Stripe payment link and send it directly to the tourist. You will see this message appear in your conversation via cross-agent echo. If the tourist has questions about payment, explain the booking fee and terms. Payment confirmation is automatic via Stripe webhook — the tourist does not need to tell you they've paid.
121
+ The operations agent will generate a Stripe payment link and relay it to your conversation as a `[System: Ride Dispatch — Payment Link]` message. Present the payment link and terms to the tourist. If the tourist has questions about payment, explain the booking fee. Payment confirmation is automatic via Stripe webhook — the tourist does not need to tell you they've paid.
117
122
 
118
123
  ### Step 9 — Post-payment
119
124
 
120
- Once payment is confirmed (the operations agent will inject driver details and pickup PIN into the conversation), acknowledge the details to the tourist. Explain the PIN verification process: "Your driver has a QR code. You have the PIN. Scan the QR or ask the driver to quote your PIN — works without internet."
125
+ Once payment is confirmed, the operations agent will relay driver details and the pickup PIN to your conversation as a `[System: Ride Dispatch — Booking Complete]` message. Present the driver details to the tourist and explain the PIN verification process: "Your driver has a QR code. You have the PIN. Scan the QR or ask the driver to quote your PIN — works without internet."
121
126
 
122
127
  ### Step 10 — Record and follow up
123
128