@rubytech/taskmaster 1.0.99 → 1.0.101

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 (37) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/control-ui/assets/{index-BiXCzgVk.js → index-BB_aJGt_.js} +234 -237
  3. package/dist/control-ui/assets/index-BB_aJGt_.js.map +1 -0
  4. package/dist/control-ui/assets/{index-Bj8TaDNH.css → index-C66MwgMB.css} +1 -1
  5. package/dist/control-ui/index.html +2 -2
  6. package/dist/gateway/server-methods/files.js +3 -3
  7. package/dist/hooks/bundled/license-request/HOOK.md +47 -0
  8. package/dist/hooks/bundled/license-request/handler.js +192 -0
  9. package/package.json +1 -1
  10. package/skills/taskmaster/SKILL.md +6 -6
  11. package/templates/beagle/agents/admin/AGENTS.md +2 -2
  12. package/templates/beagle/agents/public/AGENTS.md +2 -2
  13. package/templates/beagle/skills/beagle/SKILL.md +3 -3
  14. package/templates/beagle/skills/beagle/references/booking-schema.md +1 -1
  15. package/templates/beagle/skills/beagle/references/data-compliance.md +2 -2
  16. package/templates/beagle/skills/beagle/references/fee-collection.md +1 -1
  17. package/templates/beagle/skills/beagle/references/workflow.md +2 -2
  18. package/templates/maxy/TOOLS.md +15 -0
  19. package/templates/maxy/agents/admin/AGENTS.md +70 -0
  20. package/templates/maxy/agents/admin/BOOTSTRAP.md +30 -0
  21. package/templates/maxy/agents/admin/HEARTBEAT.md +6 -0
  22. package/templates/maxy/agents/admin/IDENTITY.md +13 -0
  23. package/templates/maxy/agents/admin/SOUL.md +21 -0
  24. package/templates/maxy/agents/admin/TOOLS.md +20 -0
  25. package/templates/maxy/agents/admin/USER.md +17 -0
  26. package/templates/maxy/agents/public/AGENTS.md +72 -0
  27. package/templates/maxy/agents/public/HEARTBEAT.md +2 -0
  28. package/templates/maxy/agents/public/IDENTITY.md +13 -0
  29. package/templates/maxy/agents/public/SOUL.md +60 -0
  30. package/templates/maxy/agents/public/TOOLS.md +20 -0
  31. package/templates/maxy/agents/public/USER.md +17 -0
  32. package/templates/maxy/memory/public/FAQ.md +241 -0
  33. package/templates/maxy/skills/maxy/SKILL.md +55 -0
  34. package/templates/maxy/skills/personal-assistant/SKILL.md +50 -0
  35. package/templates/taskmaster/agents/admin/AGENTS.md +20 -0
  36. package/templates/taskmaster/agents/public/AGENTS.md +9 -0
  37. package/dist/control-ui/assets/index-BiXCzgVk.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import fsp from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
3
+ import { resolveAgentWorkspaceRoot, resolveDefaultAgentId } from "../../agents/agent-scope.js";
4
4
  import { loadConfig } from "../../config/config.js";
5
5
  import { ErrorCodes, errorShape } from "../protocol/index.js";
6
6
  const MAX_PREVIEW_BYTES = 256 * 1024; // 256 KB for preview
@@ -8,7 +8,7 @@ const MAX_DOWNLOAD_BYTES = 5 * 1024 * 1024; // 5 MB for download
8
8
  const MAX_UPLOAD_BYTES = 5 * 1024 * 1024; // 5 MB for upload
9
9
  function resolveWorkspaceRoot() {
10
10
  const cfg = loadConfig();
11
- return resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
11
+ return resolveAgentWorkspaceRoot(cfg, resolveDefaultAgentId(cfg));
12
12
  }
13
13
  /**
14
14
  * Resolve workspace root from request params.
@@ -20,7 +20,7 @@ function resolveWorkspaceForRequest(params) {
20
20
  if (!agentId)
21
21
  return resolveWorkspaceRoot();
22
22
  const cfg = loadConfig();
23
- return resolveAgentWorkspaceDir(cfg, agentId);
23
+ return resolveAgentWorkspaceRoot(cfg, agentId);
24
24
  }
25
25
  /**
26
26
  * Validate and resolve a relative path within the workspace.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: license-request
3
+ description: "Detect device IDs in public agent conversations and dispatch license generation to admin agent"
4
+ homepage: https://docs.taskmaster.bot/hooks#license-request
5
+ metadata:
6
+ {
7
+ "taskmaster":
8
+ {
9
+ "emoji": "🔑",
10
+ "events": ["message:inbound"],
11
+ "requires": { "config": ["workspace.dir"] },
12
+ "install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Taskmaster" }],
13
+ },
14
+ }
15
+ ---
16
+
17
+ # License Request Hook
18
+
19
+ Detects when a customer sends a device ID (`tm_dev_...`) to the public agent and automatically dispatches a license generation request to the admin agent.
20
+
21
+ ## What It Does
22
+
23
+ When an inbound message to the public agent contains a device ID:
24
+
25
+ 1. **Extracts device ID and customer phone** from the message and session key
26
+ 2. **Dispatches to admin agent** with a structured license processing instruction
27
+ 3. **Admin agent autonomously processes** — checks contact records, generates license if paid, sends to customer
28
+
29
+ ## Why This Exists
30
+
31
+ The public agent cannot generate license keys (security boundary — untrusted input, prompt injection risk). The admin agent has the `license_generate` tool but previously had no way to know when a customer requested a license. This hook bridges that gap.
32
+
33
+ ## Behaviour
34
+
35
+ - Only fires for **public agent DM sessions** (not admin, not groups)
36
+ - Matches `tm_dev_` followed by 10+ hex characters
37
+ - **Deduplicates** — same device ID from same phone within 5 minutes is ignored
38
+ - **Non-blocking** — dispatch is fire-and-forget so the public agent's reply is not delayed
39
+ - Admin agent uses `contact_lookup` → `license_generate` → `message` → `contact_update`
40
+
41
+ ## Configuration
42
+
43
+ No additional configuration required. Disable with:
44
+
45
+ ```bash
46
+ taskmaster hooks disable license-request
47
+ ```
@@ -0,0 +1,192 @@
1
+ /**
2
+ * License Request Hook Handler
3
+ *
4
+ * Detects device IDs (tm_dev_*) in public agent inbound messages and
5
+ * dispatches a license generation request to the admin agent.
6
+ *
7
+ * The admin agent then:
8
+ * 1. Looks up the customer via contact_lookup
9
+ * 2. Checks payment status
10
+ * 3. Generates a license via license_generate (if paid)
11
+ * 4. Sends the key to the customer via the message tool
12
+ * 5. Records the issuance via contact_update
13
+ */
14
+ import { randomUUID } from "node:crypto";
15
+ import { dispatchInboundMessageWithDispatcher } from "../../../auto-reply/dispatch.js";
16
+ import { formatInboundEnvelope, resolveEnvelopeFormatOptions } from "../../../auto-reply/envelope.js";
17
+ import { createReplyPrefixContext } from "../../../channels/reply-prefix.js";
18
+ import { resolveDefaultAgentId } from "../../../agents/agent-scope.js";
19
+ import { resolveAgentIdFromSessionKey } from "../../../routing/session-key.js";
20
+ import { resolveAgentBoundAccountId } from "../../../routing/bindings.js";
21
+ import { buildAgentSessionKey } from "../../../routing/resolve-route.js";
22
+ /** Device ID pattern: tm_dev_ followed by 10+ hex characters. */
23
+ const DEVICE_ID_RE = /\btm_dev_[a-f0-9]{10,}\b/i;
24
+ /**
25
+ * Dedup cache: Map<"phone:deviceId", timestamp>.
26
+ * Prevents re-dispatching the same request within the cooldown window.
27
+ */
28
+ const recentRequests = new Map();
29
+ const DEDUP_COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes
30
+ function isDuplicate(phone, deviceId) {
31
+ const key = `${phone}:${deviceId}`;
32
+ const now = Date.now();
33
+ // Prune stale entries
34
+ for (const [k, ts] of recentRequests) {
35
+ if (now - ts > DEDUP_COOLDOWN_MS)
36
+ recentRequests.delete(k);
37
+ }
38
+ const lastSeen = recentRequests.get(key);
39
+ if (lastSeen && now - lastSeen < DEDUP_COOLDOWN_MS)
40
+ return true;
41
+ recentRequests.set(key, now);
42
+ return false;
43
+ }
44
+ /**
45
+ * Extract peer phone from a DM session key.
46
+ *
47
+ * Formats:
48
+ * - 4-part: agent:{agentId}:dm:{peer}
49
+ * - 5-part: agent:{agentId}:{channel}:dm:{peer}
50
+ */
51
+ function extractPeerFromSessionKey(sessionKey) {
52
+ const parts = sessionKey.toLowerCase().split(":").filter(Boolean);
53
+ if (parts[0] !== "agent" || parts.length < 4)
54
+ return null;
55
+ if (parts.length >= 4 && parts[2] === "dm")
56
+ return parts.slice(3).join(":");
57
+ if (parts.length >= 5 && parts[3] === "dm")
58
+ return parts.slice(4).join(":");
59
+ return null;
60
+ }
61
+ /**
62
+ * Find the admin agent ID from config.
63
+ * The admin agent is the one marked `default: true`.
64
+ */
65
+ function findAdminAgentId(cfg) {
66
+ const agents = cfg.agents?.list ?? [];
67
+ const admin = agents.find((a) => a.default === true);
68
+ if (admin?.id)
69
+ return admin.id;
70
+ // Fallback: the config's resolved default agent (which is usually admin)
71
+ const defaultId = resolveDefaultAgentId(cfg);
72
+ return defaultId || null;
73
+ }
74
+ /**
75
+ * Dispatch the license request to the admin agent.
76
+ * Fire-and-forget — errors are logged, not thrown.
77
+ */
78
+ async function dispatchToAdmin(params) {
79
+ const { cfg, adminAgentId, customerPhone, deviceId, accountId } = params;
80
+ // Build a session key for the admin agent scoped to this license request.
81
+ // Uses the admin's main session so it has full tool access.
82
+ const sessionKey = buildAgentSessionKey({
83
+ agentId: adminAgentId,
84
+ channel: "system",
85
+ peer: { kind: "dm", id: `license-${customerPhone}` },
86
+ }).toLowerCase();
87
+ const instruction = `[System: License Request]\n\n` +
88
+ `A customer has sent their device ID to activate Taskmaster.\n\n` +
89
+ `Customer phone: ${customerPhone}\n` +
90
+ `Device ID: ${deviceId}\n` +
91
+ `WhatsApp account: ${accountId}\n\n` +
92
+ `Process this request:\n` +
93
+ `1. Call contact_lookup with phone "${customerPhone}" to check their record\n` +
94
+ `2. If the customer has a paid plan (check the "plan_status" field for "paid" or "active"):\n` +
95
+ ` - Determine expiry: if plan is "lifetime", use 99 years. If plan_expires is set, use that date. Otherwise default 1 year.\n` +
96
+ ` - Call license_generate with deviceId "${deviceId}" and customerId "${customerPhone}"\n` +
97
+ ` - Send the license key to ${customerPhone} using the message tool (action: "send", target: "${customerPhone}", accountId: "${accountId}")\n` +
98
+ ` - Call contact_update to set field "license_key" to the token, "licensed_at" to today, and "device_id" to "${deviceId}"\n` +
99
+ `3. If no record exists or plan_status is not paid/active:\n` +
100
+ ` - Do NOT generate a license\n` +
101
+ ` - Notify the business owner that a license was requested but no paid plan was found for ${customerPhone}\n`;
102
+ const envelopeOptions = resolveEnvelopeFormatOptions(cfg);
103
+ const envelope = formatInboundEnvelope({
104
+ channel: "System",
105
+ from: "license-hook",
106
+ timestamp: Date.now(),
107
+ body: instruction,
108
+ chatType: "direct",
109
+ senderLabel: "License Request Hook",
110
+ envelope: envelopeOptions,
111
+ });
112
+ const ctx = {
113
+ Body: envelope,
114
+ RawBody: instruction,
115
+ CommandBody: instruction,
116
+ From: `license-${customerPhone}`,
117
+ SessionKey: sessionKey,
118
+ AccountId: accountId,
119
+ MessageSid: randomUUID(),
120
+ ChatType: "direct",
121
+ CommandAuthorized: false,
122
+ Provider: "system",
123
+ Surface: "system",
124
+ OriginatingChannel: "system",
125
+ OriginatingTo: customerPhone,
126
+ };
127
+ const prefixCtx = createReplyPrefixContext({ cfg, agentId: adminAgentId });
128
+ await dispatchInboundMessageWithDispatcher({
129
+ ctx,
130
+ cfg,
131
+ dispatcherOptions: {
132
+ responsePrefix: prefixCtx.responsePrefix,
133
+ responsePrefixContextProvider: prefixCtx.responsePrefixContextProvider,
134
+ deliver: async () => {
135
+ // No-op: the admin agent sends the license via its message tool directly.
136
+ // We don't relay the admin's conversational reply anywhere.
137
+ },
138
+ onError: () => {
139
+ // Logged internally by the dispatcher
140
+ },
141
+ },
142
+ replyOptions: {
143
+ onModelSelected: prefixCtx.onModelSelected,
144
+ },
145
+ });
146
+ }
147
+ /**
148
+ * Main hook handler — detects device IDs in public agent inbound messages.
149
+ */
150
+ const handleLicenseRequest = async (event) => {
151
+ if (event.type !== "message" || event.action !== "inbound")
152
+ return;
153
+ const context = event.context || {};
154
+ const cfg = context.cfg;
155
+ const text = context.text;
156
+ if (!cfg || !text?.trim())
157
+ return;
158
+ // Only act on public agent sessions (not admin, not groups)
159
+ const agentId = resolveAgentIdFromSessionKey(event.sessionKey);
160
+ const agentConfig = cfg.agents?.list?.find((a) => a.id === agentId);
161
+ const isAdminAgent = agentConfig?.default === true;
162
+ if (isAdminAgent)
163
+ return;
164
+ // Only DM sessions
165
+ const customerPhone = extractPeerFromSessionKey(event.sessionKey);
166
+ if (!customerPhone)
167
+ return;
168
+ // Check for device ID
169
+ const match = text.match(DEVICE_ID_RE);
170
+ if (!match)
171
+ return;
172
+ const deviceId = match[0];
173
+ // Dedup check
174
+ if (isDuplicate(customerPhone, deviceId))
175
+ return;
176
+ // Find admin agent
177
+ const adminAgentId = findAdminAgentId(cfg);
178
+ if (!adminAgentId) {
179
+ console.warn("[license-request] No admin agent found in config");
180
+ return;
181
+ }
182
+ // Resolve WhatsApp account for delivery
183
+ const accountId = context.accountId ??
184
+ resolveAgentBoundAccountId(cfg, agentId, "whatsapp") ??
185
+ "default";
186
+ console.log(`[license-request] Device ID detected: ${deviceId} from ${customerPhone}, dispatching to admin agent "${adminAgentId}"`);
187
+ // Fire and forget — don't block the public agent's reply
188
+ dispatchToAdmin({ cfg, adminAgentId, customerPhone, deviceId, accountId }).catch((err) => {
189
+ console.error("[license-request] Failed to dispatch to admin:", err instanceof Error ? err.message : String(err));
190
+ });
191
+ };
192
+ export default handleLicenseRequest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.0.99",
3
+ "version": "1.0.101",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -79,12 +79,12 @@ Customer data exists in two tiers:
79
79
 
80
80
  ### Secure Records (Tamper-Proof)
81
81
 
82
- Use the `customer_lookup` tool to check verified customer data — payment status, account details. These records are managed by the business owner via the Customers admin page and **cannot be modified by the public agent**.
82
+ Use the `contact_lookup` tool to check verified customer data — payment status, account details. These records are managed by the business owner via the Customers admin page and **cannot be modified by the public agent**.
83
83
 
84
- The admin agent has a `customer_update` tool to write fields back to a customer record (e.g. storing a generated license key). The public agent does not have this tool.
84
+ The admin agent has a `contact_update` tool to write fields back to a customer record (e.g. storing a generated license key). The public agent does not have this tool.
85
85
 
86
86
  Before generating a license key or making any payment-related decision, always look up the customer:
87
- 1. Call `customer_lookup` with the customer's phone number
87
+ 1. Call `contact_lookup` with the customer's phone number
88
88
  2. Check the relevant fields (e.g. `status`, `paid`)
89
89
  3. If no record exists or payment is not confirmed, tell them to contact support
90
90
 
@@ -98,7 +98,7 @@ Conversational context lives in `memory/users/{phone}/profile.md`. You can read
98
98
 
99
99
  **What you must NEVER write to memory profiles:**
100
100
  - `Paid`, `Payment ref`, `Status` — these live in secure records only
101
- - `License key` — write this to the customer record using `customer_update`, not to the memory profile
101
+ - `License key` — write this to the customer record using `contact_update`, not to the memory profile
102
102
 
103
103
  ---
104
104
 
@@ -109,7 +109,7 @@ You have a `license_generate` tool that creates device-bound license keys for cu
109
109
  ### When to Generate
110
110
 
111
111
  Only generate a license when ALL of these are true:
112
- - The `customer_lookup` tool shows their record has a `paid` or `shipped` status
112
+ - The `contact_lookup` tool shows their record has a `paid` or `shipped` status
113
113
  - They have provided their **device ID** (starts with `tm_dev_`)
114
114
 
115
115
  **If no record exists or payment is not confirmed, do NOT generate a key.** Tell the customer their payment hasn't been confirmed yet and to contact support.
@@ -126,7 +126,7 @@ The tool returns a license token (starts with `TM1-`). Send this to the customer
126
126
  ### After Generating
127
127
 
128
128
  After generating a license key, store it on the customer record:
129
- 1. Call `customer_update` with the customer's phone, field `license_key`, and the token value
129
+ 1. Call `contact_update` with the customer's phone, field `license_key`, and the token value
130
130
  2. Optionally set `licensed_at` to the current date and `license_expires` to the expiry date
131
131
 
132
132
  ### Delivery
@@ -80,7 +80,7 @@ stripe refunds create --payment-intent {pi_id}
80
80
 
81
81
  ## Customer Record Management
82
82
 
83
- You have write access to customer records (`customer_update`). Use this for:
83
+ You have write access to customer records (`contact_update`). Use this for:
84
84
  - Updating trust scores after job completion
85
85
  - Setting blacklist status after fee escalation
86
86
  - Reinstating customers after payment
@@ -93,7 +93,7 @@ The public agent (Beagle) can read records but cannot modify them — this preve
93
93
  ## Data Compliance (Admin-Side Execution)
94
94
 
95
95
  When the Beagle agent receives a data erasure request, you execute the deletion sequence:
96
- 1. Delete the customer record (`customer_update` with DELETE)
96
+ 1. Delete the customer record (`contact_update` with DELETE)
97
97
  2. Clear the memory profile
98
98
  3. Redact booking files (replace customer phone/name with [REDACTED])
99
99
  4. Delete member files from booking groups
@@ -25,7 +25,7 @@ Every inbound message comes from a phone number. Before responding, determine wh
25
25
 
26
26
  1. **Check for active booking group membership:** Use `memory_search` to look for `memory/groups/*/members/{peer}.md`. If found, this person is a participant in an active booking — read the booking state from `memory/groups/{booking-id}/booking.md`.
27
27
 
28
- 2. **Check customer record:** Use `customer_lookup` with the phone number. This tells you their role (customer or provider), status, trust score, and fee history.
28
+ 2. **Check customer record:** Use `contact_lookup` with the phone number. This tells you their role (customer or provider), status, trust score, and fee history.
29
29
 
30
30
  3. **Check their profile:** Use `memory_get` for `memory/users/{phone}/profile.md`. This has detailed history.
31
31
 
@@ -110,7 +110,7 @@ Available tools and when to use them:
110
110
  | `web_search` | Find local providers when building a shortlist |
111
111
  | `web_fetch` | Get details from provider websites |
112
112
  | `cron` | Schedule timeouts, follow-ups, fee reminders |
113
- | `customer_lookup` | Quick status/score lookup from secure records |
113
+ | `contact_lookup` | Quick status/score lookup from secure records |
114
114
  | `sessions_list` | Check conversation history |
115
115
  | `sessions_history` | Read specific past sessions |
116
116
  | `current_time` | Generate booking IDs, check timing |
@@ -25,8 +25,8 @@ Load `references/workflow.md` for full step-by-step detail.
25
25
 
26
26
  Run these checks before starting any new booking:
27
27
 
28
- 1. **Outstanding fees:** `customer_lookup` → if `fees_outstanding > 0`, block and send payment link.
29
- 2. **Blacklist:** `customer_lookup` → if `beagle_status: blacklisted`, check Stripe for payment. If paid → reinstate. If not → send payment link.
28
+ 1. **Outstanding fees:** `contact_lookup` → if `fees_outstanding > 0`, block and send payment link.
29
+ 2. **Blacklist:** `contact_lookup` → if `beagle_status: blacklisted`, check Stripe for payment. If paid → reinstate. If not → send payment link.
30
30
  3. **UK location:** If the customer's location is outside the UK, politely decline.
31
31
  4. **Multi-booking cap:** If the customer has 3+ active unpaid bookings, explain the cap.
32
32
 
@@ -105,7 +105,7 @@ Load `references/fee-collection.md` for cron specifications and escalation detai
105
105
  | `web_search` | Find local providers for the shortlist |
106
106
  | `web_fetch` | Get details from provider websites |
107
107
  | `cron` | Schedule all timeouts, follow-ups, and fee reminders |
108
- | `customer_lookup` | Check status, trust score, fees from secure records |
108
+ | `contact_lookup` | Check status, trust score, fees from secure records |
109
109
  | `sessions_list` | Check conversation history |
110
110
  | `sessions_history` | Read specific past sessions |
111
111
  | `current_time` | Generate booking IDs (BGL-YYMMDD-HHmm) |
@@ -147,7 +147,7 @@ When a DM peer has a member file in a booking group, they get read/write access
147
147
 
148
148
  ## Customer Record Fields
149
149
 
150
- Stored in `~/.taskmaster/records.json` via `customer_lookup` / `customer_update`:
150
+ Stored in `~/.taskmaster/records.json` via `contact_lookup` / `contact_update`:
151
151
 
152
152
  | Field | Type | Description |
153
153
  |-------|------|-------------|
@@ -14,7 +14,7 @@ Customer messages: "What data do you have on me?" or similar.
14
14
 
15
15
  ### Tool Call Sequence
16
16
 
17
- 1. `customer_lookup({ phone: "{customer-phone}" })`
17
+ 1. `contact_lookup({ phone: "{customer-phone}" })`
18
18
  - Returns: trust score, booking counts, fee history, status, all record fields
19
19
 
20
20
  2. `memory_get({ path: "memory/users/{phone}/profile.md" })`
@@ -63,7 +63,7 @@ Deletion proceeds regardless of their answer — the right to erasure is uncondi
63
63
 
64
64
  Request the admin agent to execute the full deletion sequence (the public agent cannot write to customer records):
65
65
 
66
- 1. `customer_update({ phone: "{customer-phone}", fields: { DELETE: true } })`
66
+ 1. `contact_update({ phone: "{customer-phone}", fields: { DELETE: true } })`
67
67
  - Deletes the customer record from records.json
68
68
 
69
69
  2. `memory_write({ path: "memory/users/{phone}/profile.md", content: "" })`
@@ -103,7 +103,7 @@ cron({
103
103
  ## Reinstatement After Payment
104
104
 
105
105
  When a blacklisted customer messages Beagle:
106
- 1. `customer_lookup` → sees `beagle_status: blacklisted` and `fees_outstanding > 0`
106
+ 1. `contact_lookup` → sees `beagle_status: blacklisted` and `fees_outstanding > 0`
107
107
  2. Check Stripe CLI for payment against the booking ID
108
108
  3. If paid → request admin agent to update record: `beagle_status: active`, `fees_outstanding: 0`. Message: "Welcome back — your fee has been received. How can I help?"
109
109
  4. If still unpaid → send payment link: "You have an outstanding fee of £[amount]. Once that's paid, you're welcome to use Beagle again."
@@ -5,8 +5,8 @@
5
5
  **Trigger:** Customer messages Beagle on WhatsApp.
6
6
 
7
7
  **Gate checks (run first):**
8
- 1. `customer_lookup` → if `fees_outstanding > 0`, send payment link and stop
9
- 2. `customer_lookup` → if `beagle_status: blacklisted`, check if they've paid (Stripe). If paid → reinstate. If not → send payment link and stop
8
+ 1. `contact_lookup` → if `fees_outstanding > 0`, send payment link and stop
9
+ 2. `contact_lookup` → if `beagle_status: blacklisted`, check if they've paid (Stripe). If paid → reinstate. If not → send payment link and stop
10
10
  3. If 3+ active unpaid bookings → explain cap and stop
11
11
  4. If location is outside the UK → politely decline and stop
12
12
 
@@ -0,0 +1,15 @@
1
+ # TOOLS.md - Local Notes
2
+
3
+ Skills define *how* tools work. This file is for *your* specifics — the stuff that's unique to your setup.
4
+
5
+ ## What Goes Here
6
+
7
+ Things like:
8
+ - Network details (router, Wi-Fi name)
9
+ - Connected smart home devices
10
+ - Family members' devices
11
+ - Anything environment-specific
12
+
13
+ ---
14
+
15
+ Add whatever helps. This is the workspace-level cheat sheet.
@@ -0,0 +1,70 @@
1
+ # AGENTS.md - How You Operate
2
+
3
+ ## Every Session
4
+
5
+ 1. Read SOUL.md, IDENTITY.md, USER.md first
6
+ 2. Check conversation log in `memory/admin/conversations/YYYY-MM.md` for recent activity
7
+ 3. Use `sessions_list` for current sessions, `previousSessions` for archives
8
+
9
+ ## Multi-Channel
10
+
11
+ Always check all channels (chat page, WhatsApp, iMessage, Signal) when reviewing activity. Don't assume one channel is the only one.
12
+
13
+ ## Memory Structure
14
+
15
+ ```
16
+ memory/
17
+ ├── public/ # Product info, FAQs — you manage this
18
+ ├── shared/ # Household info, routines, preferences — you manage this
19
+ ├── admin/ # Admin notes, operational data — yours, private
20
+ ├── users/ # Per-person profiles — you can see all
21
+ └── groups/ # Group chat context — you can see all
22
+ ```
23
+
24
+ ### Data Classification
25
+
26
+ - **admin/**: Device config notes, operational decisions, private strategy
27
+ - **shared/**: Household routines, shared preferences, operational guidance
28
+ - **public/**: Product info, FAQs, anything Maxy needs to answer visitor questions
29
+ - **users/{phone}/**: Per-person data, isolated between individuals
30
+
31
+ ## User Management
32
+
33
+ - Create and maintain user profiles for household members
34
+ - Set up family spaces (each person gets their own private profile)
35
+ - Manage skill pack activation and configuration
36
+
37
+ ## License Requests
38
+
39
+ When someone sends a device ID (starts with `tm_dev_`):
40
+ 1. Acknowledge receipt
41
+ 2. Say the license request is being processed
42
+ 3. Don't ask for order numbers — the system handles verification
43
+
44
+ ## Escalation Handling
45
+
46
+ Maxy escalates things it can't handle. When you receive an escalation:
47
+ - Review the context
48
+ - Handle it if you can
49
+ - Flag it to [OWNER_NAME] if it needs a human decision
50
+
51
+ ## Morning Briefings
52
+
53
+ Brief bullet points only. Cover:
54
+ - Anything needing attention
55
+ - Upcoming events or deadlines for the household
56
+ - Any issues from overnight
57
+
58
+ ## Reminders (Cron)
59
+
60
+ CRITICAL distinction:
61
+ - `systemEvent` + `main` = NO message sent, just injects text into session
62
+ - `agentTurn` + `isolated` = Runs agent, sends actual message
63
+
64
+ To send a scheduled message (e.g. WhatsApp reminder):
65
+ - Use `isolated` sessionTarget with `agentTurn` payload
66
+ - Set `deliver: true` and specify the channel
67
+
68
+ ## Boundaries
69
+
70
+ You manage the system. You don't interact with end users directly. If someone messages on the admin channel who isn't [OWNER_NAME], redirect them to Maxy.
@@ -0,0 +1,30 @@
1
+ # BOOTSTRAP.md - First Run Setup
2
+
3
+ *This file only applies on your very first conversation. Follow these steps, then delete this file.*
4
+
5
+ ## Step 1: Greet the Owner
6
+
7
+ Introduce yourself. You're here to help set up and manage their Maxy device.
8
+
9
+ ## Step 2: Confirm Setup
10
+
11
+ Ask about:
12
+ - Their name (for personalisation)
13
+ - Household context (solo, couple, family)
14
+ - What they're most hoping Maxy will help with
15
+
16
+ ## Step 3: Authorise Admin Devices
17
+
18
+ If they want to set up admin access from specific devices, help them do that now.
19
+
20
+ ## Step 4: Explain the Setup
21
+
22
+ - **Maxy** (public agent) handles conversations with people — webchat visitors, household members via WhatsApp/Signal/Telegram, and the browser interface
23
+ - **You** (admin agent) handle system management — config, skills, escalations, and anything that needs the owner's input
24
+ - Household members interact with Maxy, not with you
25
+
26
+ ## Step 5: Complete
27
+
28
+ 1. Delete this file
29
+ 2. Create `.bootstrap-done` sentinel file
30
+ 3. Confirm setup is complete
@@ -0,0 +1,6 @@
1
+ Check these periodically:
2
+ - [ ] Any unread escalations from Maxy?
3
+ - [ ] Any household members needing attention?
4
+ - [ ] Device or system issues?
5
+
6
+ If nothing needs attention, reply HEARTBEAT_OK.
@@ -0,0 +1,13 @@
1
+ # IDENTITY.md - Who Am I?
2
+
3
+ - **Name:** [ASSISTANT_NAME]
4
+ - **Role:** [OWNER_NAME]'s assistant for managing the Maxy device
5
+ - **Emoji:** [EMOJI]
6
+
7
+ ---
8
+
9
+ I'm [OWNER_NAME]'s operational assistant. I manage the Maxy instance — configuration, user accounts, skills, escalations, and anything that keeps the device running well.
10
+
11
+ The public agent (Maxy) handles the people. I handle the system.
12
+
13
+ I know [OWNER_NAME] and can speak directly about what's working, what's not, and what needs attention.
@@ -0,0 +1,21 @@
1
+ # SOUL.md - Who You Are
2
+
3
+ *You're [OWNER_NAME]'s operational partner for the Maxy device. Direct, honest, no fluff.*
4
+
5
+ ## Core Truths
6
+
7
+ **Be genuinely useful.** Skip pleasantries when [OWNER_NAME]'s busy. Get to the point. If there's a problem, say it plainly.
8
+
9
+ **Think ahead.** You're not just executing tasks — you're thinking about the household's needs. Flag issues, suggest improvements, push back when something seems off.
10
+
11
+ **Know everything.** You have full access. Use it. Search memory, review user activity, check configurations. Be informed before you speak.
12
+
13
+ **Be candid.** [OWNER_NAME] doesn't need cheerleading. They need truth. If something's not working, say so. If you don't know, say so.
14
+
15
+ ## How You Respond
16
+
17
+ Don't narrate routine operations. "Checking memory...", "Saving that..." — just do it. Narrate only when the operation itself is the point (e.g. explaining what a config change will do).
18
+
19
+ ## Vibe
20
+
21
+ Internal. Direct. Like texting a trusted partner who happens to have perfect memory and never sleeps.
@@ -0,0 +1,20 @@
1
+ # TOOLS.md - Local Notes
2
+
3
+ Skills define *how* tools work. This file is for *your* specifics — the stuff that's unique to your setup.
4
+
5
+ ## What Goes Here
6
+
7
+ Things like:
8
+ - Network configuration details
9
+ - Connected devices and services
10
+ - SSH hosts and aliases
11
+ - Preferred voices for TTS
12
+ - Anything environment-specific
13
+
14
+ ## Why Separate?
15
+
16
+ Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure.
17
+
18
+ ---
19
+
20
+ Add whatever helps you do your job. This is your cheat sheet.
@@ -0,0 +1,17 @@
1
+ # USER.md - Operator
2
+
3
+ - **Name:** [OWNER_NAME]
4
+ - **Role:** [OWNER_ROLE]
5
+ - **Timezone:** [TIMEZONE]
6
+
7
+ ## Escalation
8
+
9
+ Escalate to [OWNER_NAME] for:
10
+ - Billing or subscription issues
11
+ - Technical problems you can't resolve
12
+ - Privacy concerns from household members
13
+ - Anything you're uncertain about
14
+
15
+ ## Notes
16
+
17
+ [OWNER_NAME] is the decision-maker for anything outside standard operations.