@rubytech/taskmaster 1.16.3 → 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.
Files changed (51) hide show
  1. package/dist/agents/tools/logs-read-tool.js +9 -0
  2. package/dist/agents/tools/memory-tool.js +1 -0
  3. package/dist/agents/workspace-migrations.js +61 -0
  4. package/dist/auto-reply/group-activation.js +2 -0
  5. package/dist/auto-reply/reply/commands-session.js +28 -11
  6. package/dist/build-info.json +3 -3
  7. package/dist/config/agent-tools-reconcile.js +58 -0
  8. package/dist/config/group-policy.js +16 -0
  9. package/dist/config/zod-schema.providers-whatsapp.js +2 -0
  10. package/dist/control-ui/assets/index-XqRo9tNW.css +1 -0
  11. package/dist/control-ui/assets/{index-Bd75cI7J.js → index-koe4eKhk.js} +526 -493
  12. package/dist/control-ui/assets/index-koe4eKhk.js.map +1 -0
  13. package/dist/control-ui/index.html +2 -2
  14. package/dist/cron/preloaded.js +27 -23
  15. package/dist/cron/service/timer.js +5 -1
  16. package/dist/gateway/protocol/index.js +7 -2
  17. package/dist/gateway/protocol/schema/logs-chat.js +6 -0
  18. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -0
  19. package/dist/gateway/protocol/schema/sessions-transcript.js +1 -0
  20. package/dist/gateway/protocol/schema/sessions.js +6 -1
  21. package/dist/gateway/protocol/schema/whatsapp.js +24 -0
  22. package/dist/gateway/protocol/schema.js +1 -0
  23. package/dist/gateway/public-chat/session-token.js +52 -0
  24. package/dist/gateway/public-chat-api.js +40 -13
  25. package/dist/gateway/server-methods/apikeys.js +2 -0
  26. package/dist/gateway/server-methods/logs.js +17 -1
  27. package/dist/gateway/server-methods/public-chat.js +5 -0
  28. package/dist/gateway/server-methods/sessions-transcript.js +30 -6
  29. package/dist/gateway/server-methods/whatsapp-conversations.js +387 -0
  30. package/dist/gateway/server-methods-list.js +6 -0
  31. package/dist/gateway/server-methods.js +7 -0
  32. package/dist/gateway/server.impl.js +19 -2
  33. package/dist/gateway/sessions-patch.js +1 -1
  34. package/dist/hooks/bundled/ride-dispatch/HOOK.md +7 -6
  35. package/dist/hooks/bundled/ride-dispatch/handler.js +98 -39
  36. package/dist/memory/manager.js +3 -3
  37. package/dist/tui/tui-command-handlers.js +1 -1
  38. package/dist/web/auto-reply/monitor/group-activation.js +12 -10
  39. package/dist/web/auto-reply/monitor/group-gating.js +23 -2
  40. package/dist/web/auto-reply/monitor/on-message.js +27 -5
  41. package/dist/web/auto-reply/monitor/process-message.js +64 -53
  42. package/dist/web/inbound/monitor.js +30 -0
  43. package/extensions/whatsapp/src/channel.ts +1 -1
  44. package/package.json +1 -1
  45. package/skills/log-review/SKILL.md +17 -4
  46. package/skills/log-review/references/review-protocol.md +4 -4
  47. package/taskmaster-docs/USER-GUIDE.md +14 -0
  48. package/templates/beagle-zanzibar/agents/admin/AGENTS.md +16 -8
  49. package/templates/beagle-zanzibar/agents/public/AGENTS.md +10 -5
  50. package/dist/control-ui/assets/index-Bd75cI7J.js.map +0 -1
  51. package/dist/control-ui/assets/index-BkymP95Y.css +0 -1
@@ -24,6 +24,9 @@ const LogsReadSchema = Type.Object({
24
24
  agents: Type.Optional(Type.Array(Type.String(), {
25
25
  description: 'Filter session logs to specific agent IDs (e.g. ["admin", "public"]). Sessions action only.',
26
26
  })),
27
+ minLevel: Type.Optional(stringEnum(["fatal", "error", "warn", "info"], {
28
+ description: 'Filter system log entries to this severity or above. Use "warn" for health checks to avoid context overflow. Only applies to action: "system".',
29
+ })),
27
30
  });
28
31
  export function createLogsReadTool() {
29
32
  return {
@@ -44,19 +47,25 @@ export function createLogsReadTool() {
44
47
  const cursor = typeof params.cursor === "number" && Number.isFinite(params.cursor)
45
48
  ? Math.max(0, Math.floor(params.cursor))
46
49
  : undefined;
50
+ const minLevel = typeof params.minLevel === "string" ? params.minLevel : undefined;
47
51
  const result = await callGatewayTool("logs.tail", {}, {
48
52
  limit,
49
53
  ...(cursor !== undefined ? { cursor } : {}),
54
+ ...(minLevel !== undefined ? { minLevel } : {}),
50
55
  });
51
56
  return jsonResult(result);
52
57
  }
53
58
  if (action === "sessions") {
59
+ if (params.minLevel !== undefined) {
60
+ throw new Error('minLevel is not supported for action "sessions"');
61
+ }
54
62
  const limit = typeof params.limit === "number" && Number.isFinite(params.limit)
55
63
  ? Math.max(1, Math.floor(params.limit))
56
64
  : 100;
57
65
  const agents = readStringArrayParam(params, "agents");
58
66
  const result = await callGatewayTool("sessions.transcript", {}, {
59
67
  limit,
68
+ errorsOnly: true,
60
69
  ...(agents ? { agents } : {}),
61
70
  });
62
71
  return jsonResult(result);
@@ -148,6 +148,7 @@ export function createMemoryWriteTool(options) {
148
148
  description: "Write or append content to a file in the memory/ directory. " +
149
149
  "Path must be within the session's allowed scope. " +
150
150
  "Phone numbers in paths MUST include the + prefix (e.g., memory/users/+447734875155/profile.md). " +
151
+ "Group IDs in paths MUST include the @g.us suffix (e.g., memory/groups/120363425419890848@g.us/context.md). " +
151
152
  "Creates parent directories if needed. Use mode='append' to add to existing content.",
152
153
  parameters: MemoryWriteSchema,
153
154
  execute: async (_toolCallId, params) => {
@@ -631,6 +631,65 @@ async function patchBeagleAdminDispatchInstructions(_agentsPath, content) {
631
631
  }
632
632
  return result;
633
633
  }
634
+ // ---------------------------------------------------------------------------
635
+ // Migration: Beagle Zanzibar — Public agent tourist_phone field name (v1.17)
636
+ // ---------------------------------------------------------------------------
637
+ //
638
+ // Replaces `tourist_id:` with `tourist_phone:` in the dispatch file format
639
+ // examples and the Step 0 description. The hook parser looks for `tourist_phone`
640
+ // and previously would receive "unknown" when the agent wrote `tourist_id`.
641
+ async function patchBeaglePublicTouristPhone(_agentsPath, content) {
642
+ if (!isBeaglePublicAgent(content))
643
+ return null;
644
+ // Already migrated (or never had tourist_id)?
645
+ if (!content.includes("tourist_id"))
646
+ return null;
647
+ return content
648
+ .replace(/tourist_id: \[the tourist's phone number[^\]]*\]/g, "tourist_phone: [the tourist's phone number — from WhatsApp session or OTP-verified phone]")
649
+ .replace(/tourist_id: \[same identifier used in trip-request\]/g, "tourist_phone: [same phone used in trip-request]")
650
+ .replace(/Use it as the `tourist_id` in dispatch files\./g, "Use it as `tourist_phone` in dispatch files.")
651
+ .replace(/tourist_id:/g, "tourist_phone:");
652
+ }
653
+ // ---------------------------------------------------------------------------
654
+ // Migration: Beagle Zanzibar — Admin Driver Reply explicit message tool (v1.17)
655
+ // ---------------------------------------------------------------------------
656
+ //
657
+ // Replaces the vague "compile and send offers to the tourist" Driver Reply
658
+ // instruction with an explicit step-by-step that uses the `message` tool
659
+ // directly. Also updates Trip Request step 7 from "write offers dispatch file"
660
+ // to "message tourist directly" — the old approach created a file that nothing
661
+ // processed, stalling the booking flow.
662
+ const ADMIN_DRIVER_REPLY_OLD = `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.`;
663
+ const ADMIN_DRIVER_REPLY_NEW = `1. If the driver is quoting a fare: record it in their memory profile (\`memory_write\` on \`drivers/{name}.md\`)
664
+ 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
665
+ 3. If the driver is declining: update their status in memory to \`idle\` and delete their \`shared/active-negotiations/{phone-digits}.md\` file`;
666
+ const ADMIN_TRIP_REQUEST_OFFERS_OLD = `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)`;
667
+ const ADMIN_TRIP_REQUEST_OFFERS_NEW = `7. Message the tourist at \`tourist_phone\` using the \`message\` tool with \`accountId\` from this dispatch
668
+ - Include up to 3 offers: fare, vehicle type, driver rating, estimated journey time
669
+ - Do NOT reveal driver name, phone, or plate — those are gated by payment
670
+ - Cross-agent echo will relay the WhatsApp message to the tourist's active session automatically
671
+ - Do NOT write a dispatch file for the offers — message directly`;
672
+ async function patchBeagleAdminDriverReplyExplicit(_agentsPath, content) {
673
+ if (!isBeagleTaxiAdmin(content))
674
+ return null;
675
+ // Already migrated?
676
+ if (content.includes("do NOT write a dispatch file"))
677
+ return null;
678
+ // Only migrate if the old vague text is present
679
+ if (!content.includes(ADMIN_DRIVER_REPLY_OLD))
680
+ return null;
681
+ let result = content.replace(ADMIN_DRIVER_REPLY_OLD, ADMIN_DRIVER_REPLY_NEW);
682
+ // Also update Trip Request offers step if old text present
683
+ if (result.includes(ADMIN_TRIP_REQUEST_OFFERS_OLD)) {
684
+ // Remove preceding step 7 ("Message the tourist confirming...") and update step 8
685
+ result = result
686
+ .replace("6. Message the tourist confirming drivers have been contacted and quotes are being gathered\n7. When driver replies arrive (dispatched as `[System: Ride Dispatch — Driver Reply]`), compile offers\n" +
687
+ ADMIN_TRIP_REQUEST_OFFERS_OLD, "6. When driver replies arrive (dispatched as `[System: Ride Dispatch — Driver Reply]`), compile offers\n" +
688
+ ADMIN_TRIP_REQUEST_OFFERS_NEW)
689
+ .replace(ADMIN_TRIP_REQUEST_OFFERS_OLD, ADMIN_TRIP_REQUEST_OFFERS_NEW);
690
+ }
691
+ return result;
692
+ }
634
693
  const MIGRATIONS = [
635
694
  { name: "skill-recommendations", apply: patchSkillRecommendations },
636
695
  { name: "owner-learning", apply: patchOwnerLearning },
@@ -645,6 +704,8 @@ const MIGRATIONS = [
645
704
  { name: "beagle-public-dispatch-workflow", apply: patchBeaglePublicDispatchWorkflow },
646
705
  { name: "beagle-public-tools-dispatch", apply: patchBeaglePublicToolsDispatch },
647
706
  { name: "beagle-admin-dispatch-instructions", apply: patchBeagleAdminDispatchInstructions },
707
+ { name: "beagle-public-tourist-phone", apply: patchBeaglePublicTouristPhone },
708
+ { name: "beagle-admin-driver-reply-explicit", apply: patchBeagleAdminDriverReplyExplicit },
648
709
  ];
649
710
  /**
650
711
  * Run all workspace migrations for every configured agent.
@@ -5,6 +5,8 @@ export function normalizeGroupActivation(raw) {
5
5
  return "mention";
6
6
  if (value === "always")
7
7
  return "always";
8
+ if (value === "off")
9
+ return "off";
8
10
  return undefined;
9
11
  }
10
12
  export function parseActivationCommand(raw) {
@@ -1,8 +1,10 @@
1
1
  import { abortEmbeddedPiRun } from "../../agents/pi-embedded.js";
2
+ import { loadConfig, writeConfigFile } from "../../config/config.js";
2
3
  import { updateSessionStore } from "../../config/sessions.js";
3
4
  import { logVerbose } from "../../globals.js";
4
5
  import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js";
5
6
  import { scheduleGatewaySigusr1Restart, triggerTaskmasterRestart } from "../../infra/restart.js";
7
+ import { normalizeAccountId } from "../../routing/session-key.js";
6
8
  import { parseActivationCommand } from "../group-activation.js";
7
9
  import { parseSendPolicyCommand } from "../send-policy.js";
8
10
  import { normalizeUsageDisplay, resolveResponseUsageMode } from "../thinking.js";
@@ -51,20 +53,35 @@ export const handleActivationCommand = async (params, allowTextCommands) => {
51
53
  if (!activationCommand.mode) {
52
54
  return {
53
55
  shouldContinue: false,
54
- reply: { text: "⚙️ Usage: /activation mention|always" },
56
+ reply: { text: "⚙️ Usage: /activation mention|always|off" },
55
57
  };
56
58
  }
57
- if (params.sessionEntry && params.sessionStore && params.sessionKey) {
58
- params.sessionEntry.groupActivation = activationCommand.mode;
59
- params.sessionEntry.groupActivationNeedsSystemIntro = true;
60
- params.sessionEntry.updatedAt = Date.now();
61
- params.sessionStore[params.sessionKey] = params.sessionEntry;
62
- if (params.storePath) {
63
- await updateSessionStore(params.storePath, (store) => {
64
- store[params.sessionKey] = params.sessionEntry;
65
- });
66
- }
59
+ const groupId = params.ctx.From ?? params.sessionKey;
60
+ const accountId = normalizeAccountId(params.ctx.AccountId);
61
+ const cfg = loadConfig();
62
+ if (!cfg.channels)
63
+ cfg.channels = {};
64
+ if (!cfg.channels.whatsapp)
65
+ cfg.channels.whatsapp = {};
66
+ const wa = cfg.channels.whatsapp;
67
+ if (accountId !== "default") {
68
+ if (!wa.accounts)
69
+ wa.accounts = {};
70
+ const accounts = wa.accounts;
71
+ if (!accounts[accountId])
72
+ accounts[accountId] = {};
73
+ if (!accounts[accountId].groups)
74
+ accounts[accountId].groups = {};
75
+ const groups = accounts[accountId].groups;
76
+ groups[groupId] = { ...groups[groupId], activation: activationCommand.mode };
77
+ }
78
+ else {
79
+ if (!wa.groups)
80
+ wa.groups = {};
81
+ const groups = wa.groups;
82
+ groups[groupId] = { ...groups[groupId], activation: activationCommand.mode };
67
83
  }
84
+ await writeConfigFile(cfg);
68
85
  return {
69
86
  shouldContinue: false,
70
87
  reply: {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.16.3",
3
- "commit": "af58918a990e50515497bcf76baad99133a4ff5f",
4
- "builtAt": "2026-03-04T18:05:39.829Z"
2
+ "version": "1.17.4",
3
+ "commit": "6926faba7483a7c9b1ec7c332c593b866db3edb3",
4
+ "builtAt": "2026-03-05T13:47:06.682Z"
5
5
  }
@@ -156,6 +156,64 @@ export function reconcileQrGenerateTool(params) {
156
156
  }
157
157
  return { config, changes };
158
158
  }
159
+ /**
160
+ * Individual control-panel tool names that should be replaced by `group:control-panel`.
161
+ * Matches the members of TOOL_GROUPS["group:control-panel"] in tool-policy.ts.
162
+ */
163
+ const INDIVIDUAL_CONTROL_PANEL_TOOLS = [
164
+ "system_status",
165
+ "usage_report",
166
+ "channel_settings",
167
+ "brand_settings",
168
+ "opening_hours",
169
+ "public_chat_settings",
170
+ "skill_manage",
171
+ "logs_read",
172
+ ];
173
+ /**
174
+ * Ensure admin agents use `group:control-panel` instead of individual control-panel tools.
175
+ *
176
+ * Agents set up before `group:control-panel` was introduced have individual tool
177
+ * entries in their explicit allow lists and therefore miss tools added to the group
178
+ * later (e.g. `logs_read`). This reconciliation replaces those individual entries
179
+ * with the group reference so the agent always sees the full, current set.
180
+ *
181
+ * Runs unconditionally on gateway startup. Idempotent — skips agents that already
182
+ * have `group:control-panel` or have no individual control-panel tools.
183
+ */
184
+ export function reconcileControlPanelTools(params) {
185
+ const config = structuredClone(params.config);
186
+ const changes = [];
187
+ const agents = config.agents?.list;
188
+ if (!Array.isArray(agents))
189
+ return { config, changes };
190
+ for (const agent of agents) {
191
+ if (!agent || !isAdminAgent(agent))
192
+ continue;
193
+ const allow = agent.tools?.allow;
194
+ if (!Array.isArray(allow))
195
+ continue;
196
+ // Already using the group — nothing to do
197
+ if (allow.includes("group:control-panel"))
198
+ continue;
199
+ // Check if any individual control-panel tools are present
200
+ const hasAny = INDIVIDUAL_CONTROL_PANEL_TOOLS.some((t) => allow.includes(t));
201
+ if (!hasAny)
202
+ continue;
203
+ // Remove individual entries, add group
204
+ const removed = [];
205
+ for (const tool of INDIVIDUAL_CONTROL_PANEL_TOOLS) {
206
+ const idx = allow.indexOf(tool);
207
+ if (idx !== -1) {
208
+ allow.splice(idx, 1);
209
+ removed.push(tool);
210
+ }
211
+ }
212
+ allow.push("group:control-panel");
213
+ changes.push(`Replaced ${removed.join(", ")} with group:control-panel in agent "${agent.id}" tools.allow.`);
214
+ }
215
+ return { config, changes };
216
+ }
159
217
  /**
160
218
  * Remove privileged tools (`message`, `contact_lookup`, `group:contacts`) from
161
219
  * Beagle public agent allow lists.
@@ -52,3 +52,19 @@ export function resolveChannelGroupToolsPolicy(params) {
52
52
  return defaultConfig.tools;
53
53
  return undefined;
54
54
  }
55
+ export function resolveChannelGroupActivation(params) {
56
+ const { groupConfig, defaultConfig } = resolveChannelGroupPolicy(params);
57
+ // Prefer explicit `activation` field over deprecated `requireMention`
58
+ if (groupConfig?.activation != null)
59
+ return groupConfig.activation;
60
+ if (defaultConfig?.activation != null)
61
+ return defaultConfig.activation;
62
+ // Fall back to legacy requireMention boolean
63
+ if (typeof groupConfig?.requireMention === "boolean") {
64
+ return groupConfig.requireMention ? "mention" : "always";
65
+ }
66
+ if (typeof defaultConfig?.requireMention === "boolean") {
67
+ return defaultConfig.requireMention ? "mention" : "always";
68
+ }
69
+ return undefined;
70
+ }
@@ -31,6 +31,7 @@ export const WhatsAppAccountSchema = z
31
31
  groups: z
32
32
  .record(z.string(), z
33
33
  .object({
34
+ activation: z.enum(["always", "mention", "off"]).optional(),
34
35
  requireMention: z.boolean().optional(),
35
36
  tools: ToolPolicySchema,
36
37
  })
@@ -93,6 +94,7 @@ export const WhatsAppConfigSchema = z
93
94
  groups: z
94
95
  .record(z.string(), z
95
96
  .object({
97
+ activation: z.enum(["always", "mention", "off"]).optional(),
96
98
  requireMention: z.boolean().optional(),
97
99
  tools: ToolPolicySchema,
98
100
  })