@rubytech/taskmaster 1.0.109 → 1.0.111

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-D4TpiIHx.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-Cp_azZBu.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-BM3zZtpB.css">
11
11
  </head>
12
12
  <body>
@@ -2,8 +2,9 @@
2
2
  * Deliver OTP verification codes via WhatsApp.
3
3
  */
4
4
  import { sendMessageWhatsApp } from "../../web/outbound.js";
5
- export async function deliverOtp(phone, code) {
5
+ export async function deliverOtp(phone, code, accountId) {
6
6
  await sendMessageWhatsApp(phone, `Your verification code is: ${code}`, {
7
7
  verbose: false,
8
+ accountId,
8
9
  });
9
10
  }
@@ -26,6 +26,7 @@ import path from "node:path";
26
26
  import { resolveAgentWorkspaceDir, resolveSessionAgentId } from "../agents/agent-scope.js";
27
27
  import { resolveEffectiveMessagesConfig, resolveIdentityName } from "../agents/identity.js";
28
28
  import { dispatchInboundMessage } from "../auto-reply/dispatch.js";
29
+ import { resolveAgentBoundAccountId } from "../routing/bindings.js";
29
30
  import { createReplyDispatcher } from "../auto-reply/reply/reply-dispatcher.js";
30
31
  import { extractShortModelName, } from "../auto-reply/reply/response-prefix-template.js";
31
32
  import { loadConfig } from "../config/config.js";
@@ -202,7 +203,7 @@ async function handleSession(req, res, accountId, cfg, maxBodyBytes) {
202
203
  // ---------------------------------------------------------------------------
203
204
  // Route: POST /otp/request
204
205
  // ---------------------------------------------------------------------------
205
- async function handleOtpRequest(req, res, _accountId, cfg, maxBodyBytes) {
206
+ async function handleOtpRequest(req, res, accountId, cfg, maxBodyBytes) {
206
207
  if (req.method !== "POST") {
207
208
  sendMethodNotAllowed(res);
208
209
  return;
@@ -228,8 +229,12 @@ async function handleOtpRequest(req, res, _accountId, cfg, maxBodyBytes) {
228
229
  });
229
230
  return;
230
231
  }
232
+ // Resolve the WhatsApp account bound to this account's public agent so the
233
+ // OTP code is sent from the correct number (not the first active account).
234
+ const agentId = resolvePublicAgentId(cfg, accountId);
235
+ const whatsappAccountId = resolveAgentBoundAccountId(cfg, agentId, "whatsapp") ?? undefined;
231
236
  try {
232
- await deliverOtp(phone, result.code);
237
+ await deliverOtp(phone, result.code, whatsappAccountId);
233
238
  }
234
239
  catch {
235
240
  sendUnavailable(res, "failed to send verification code — is WhatsApp connected?");
@@ -2,6 +2,7 @@
2
2
  * RPC handlers for public chat: OTP verification and session resolution.
3
3
  */
4
4
  import { loadConfig } from "../../config/config.js";
5
+ import { resolveAgentBoundAccountId } from "../../routing/bindings.js";
5
6
  import { ErrorCodes, errorShape } from "../protocol/index.js";
6
7
  import { requestOtp, verifyOtp } from "../public-chat/otp.js";
7
8
  import { deliverOtp } from "../public-chat/deliver-otp.js";
@@ -29,6 +30,7 @@ export const publicChatHandlers = {
29
30
  */
30
31
  "public.otp.request": async ({ params, respond, context }) => {
31
32
  const phone = typeof params.phone === "string" ? normalizePhone(params.phone.trim()) : "";
33
+ const accountId = validateAccountId(params.accountId);
32
34
  if (!phone || !isValidPhone(phone)) {
33
35
  respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid phone number"));
34
36
  return;
@@ -43,8 +45,14 @@ export const publicChatHandlers = {
43
45
  respond(false, { retryAfterMs: result.retryAfterMs }, errorShape(ErrorCodes.INVALID_REQUEST, "rate limited — try again shortly"));
44
46
  return;
45
47
  }
48
+ // Resolve the WhatsApp account bound to this account's public agent so the
49
+ // OTP code is sent from the correct number (not the first active account).
50
+ const agentId = accountId ? resolvePublicAgentId(cfg, accountId) : undefined;
51
+ const whatsappAccountId = agentId
52
+ ? (resolveAgentBoundAccountId(cfg, agentId, "whatsapp") ?? undefined)
53
+ : undefined;
46
54
  try {
47
- await deliverOtp(phone, result.code);
55
+ await deliverOtp(phone, result.code, whatsappAccountId);
48
56
  }
49
57
  catch (err) {
50
58
  context.logGateway.warn(`public-chat OTP delivery failed: ${String(err)}`);
@@ -885,25 +885,17 @@ export class MemoryIndexManager {
885
885
  throw err;
886
886
  }
887
887
  }
888
- async swapIndexFiles(targetPath, tempPath) {
889
- const backupPath = `${targetPath}.backup-${randomUUID()}`;
890
- await this.moveIndexFiles(targetPath, backupPath);
891
- try {
892
- await this.moveIndexFiles(tempPath, targetPath);
893
- }
894
- catch (err) {
895
- await this.moveIndexFiles(backupPath, targetPath);
896
- throw err;
897
- }
898
- await this.removeIndexFiles(backupPath);
888
+ async removeIndexFiles(basePath) {
889
+ const suffixes = ["", "-wal", "-shm"];
890
+ await Promise.all(suffixes.map((suffix) => fs.rm(`${basePath}${suffix}`, { force: true })));
899
891
  }
900
- async moveIndexFiles(sourceBase, targetBase) {
892
+ moveIndexFilesSync(sourceBase, targetBase) {
901
893
  const suffixes = ["", "-wal", "-shm"];
902
894
  for (const suffix of suffixes) {
903
895
  const source = `${sourceBase}${suffix}`;
904
896
  const target = `${targetBase}${suffix}`;
905
897
  try {
906
- await fs.rename(source, target);
898
+ fsSync.renameSync(source, target);
907
899
  }
908
900
  catch (err) {
909
901
  if (err.code !== "ENOENT") {
@@ -912,9 +904,23 @@ export class MemoryIndexManager {
912
904
  }
913
905
  }
914
906
  }
915
- async removeIndexFiles(basePath) {
916
- const suffixes = ["", "-wal", "-shm"];
917
- await Promise.all(suffixes.map((suffix) => fs.rm(`${basePath}${suffix}`, { force: true })));
907
+ /**
908
+ * Swap index files synchronously. This must be sync to avoid an async yield
909
+ * between closing the old database and opening the new one — any await in
910
+ * that window lets concurrent search/read/write operations hit a closed
911
+ * database handle, causing "attempt to write a readonly database" errors.
912
+ */
913
+ swapIndexFilesSync(targetPath, tempPath) {
914
+ const backupPath = `${targetPath}.backup-${randomUUID()}`;
915
+ this.moveIndexFilesSync(targetPath, backupPath);
916
+ try {
917
+ this.moveIndexFilesSync(tempPath, targetPath);
918
+ }
919
+ catch (err) {
920
+ this.moveIndexFilesSync(backupPath, targetPath);
921
+ throw err;
922
+ }
923
+ this.removeIndexFilesSync(backupPath);
918
924
  }
919
925
  removeIndexFilesSync(basePath) {
920
926
  const suffixes = ["", "-wal", "-shm"];
@@ -1590,7 +1596,10 @@ export class MemoryIndexManager {
1590
1596
  this.db.close();
1591
1597
  originalDb.close();
1592
1598
  originalDbClosed = true;
1593
- await this.swapIndexFiles(dbPath, tempDbPath);
1599
+ // Sync swap: close → rename → reopen must be atomic from the event
1600
+ // loop's perspective. An async swap here would yield control and let
1601
+ // concurrent search/read/write hit the closed this.db handle.
1602
+ this.swapIndexFilesSync(dbPath, tempDbPath);
1594
1603
  this.db = this.openDatabaseAtPath(dbPath);
1595
1604
  this.vectorReady = null;
1596
1605
  this.vector.available = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.0.109",
3
+ "version": "1.0.111",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -641,19 +641,21 @@ Each account can have its own branding — a logo and colour scheme that appears
641
641
 
642
642
  | Setting | What it does |
643
643
  |---------|-------------|
644
- | **Logo** | Your business logo, shown on public chat pages alongside your business name |
645
- | **Accent colour** | The colour used for buttons, links, and interactive elements across the UI |
646
- | **Background tint** | A subtle colour wash applied to page and card backgrounds |
644
+ | **Logo** | Your business logo, shown in the navigation header and on public chat pages |
645
+ | **Accent colour** | The colour used for buttons, links, and interactive elements. Button text automatically switches to dark or light for readability. |
646
+ | **Background tint** | A colour wash blended into page backgrounds, cards, chat bubbles, and input fields |
647
647
 
648
648
  ### How to set up branding
649
649
 
650
650
  1. Go to the **Setup** page
651
651
  2. Find the **Branding** row in the status dashboard
652
652
  3. Click **Edit** — a modal opens with your branding options
653
- 4. **Upload a logo** — click Upload and select an image (PNG, JPEG, SVG, or WebP, max 2 MB)
654
- 5. **Choose your accent colour** — click the colour picker next to "Accent colour" and select your brand colour
655
- 6. **Choose your background tint** — click the colour picker next to "Background tint" to add a subtle background wash (optional)
656
- 7. Click **Close** when you're done — changes are applied immediately
653
+ 4. **Upload a logo** — click Upload and select an image (PNG, JPEG, SVG, or WebP, max 2 MB). The logo appears in the navigation header and on public chat pages.
654
+ 5. **Choose your accent colour** — click the colour picker, or type a hex code (e.g. `#7C8C72`) directly into the text field and press Enter
655
+ 6. **Choose your background tint** — click the colour picker or type a hex code to add a colour wash across page backgrounds, cards, chat bubbles, and input fields (optional)
656
+ 7. Close the modal with the **×** button — changes are applied immediately
657
+
658
+ To clear an individual colour without resetting everything, click the **Clear** button next to that colour.
657
659
 
658
660
  To see what each setting does, click the **(i)** button on the Branding row.
659
661
 
@@ -663,7 +665,7 @@ Branding is set per account. If you have multiple accounts, switch to each accou
663
665
 
664
666
  ### Resetting to defaults
665
667
 
666
- To remove all custom branding and return to the default colours, open the Branding modal and click **Reset to defaults**. To remove just the logo, click **Remove** next to the logo preview.
668
+ To remove all custom branding and return to the default colours, open the Branding modal and click **Reset to defaults**. To remove just one colour, click **Clear** next to it. To remove just the logo, click **Remove** next to the logo preview.
667
669
 
668
670
  ### Where branding appears
669
671