@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.
- package/dist/build-info.json +3 -3
- package/dist/control-ui/assets/{index-BiXCzgVk.js → index-BB_aJGt_.js} +234 -237
- package/dist/control-ui/assets/index-BB_aJGt_.js.map +1 -0
- package/dist/control-ui/assets/{index-Bj8TaDNH.css → index-C66MwgMB.css} +1 -1
- package/dist/control-ui/index.html +2 -2
- package/dist/gateway/server-methods/files.js +3 -3
- package/dist/hooks/bundled/license-request/HOOK.md +47 -0
- package/dist/hooks/bundled/license-request/handler.js +192 -0
- package/package.json +1 -1
- package/skills/taskmaster/SKILL.md +6 -6
- package/templates/beagle/agents/admin/AGENTS.md +2 -2
- package/templates/beagle/agents/public/AGENTS.md +2 -2
- package/templates/beagle/skills/beagle/SKILL.md +3 -3
- package/templates/beagle/skills/beagle/references/booking-schema.md +1 -1
- package/templates/beagle/skills/beagle/references/data-compliance.md +2 -2
- package/templates/beagle/skills/beagle/references/fee-collection.md +1 -1
- package/templates/beagle/skills/beagle/references/workflow.md +2 -2
- package/templates/maxy/TOOLS.md +15 -0
- package/templates/maxy/agents/admin/AGENTS.md +70 -0
- package/templates/maxy/agents/admin/BOOTSTRAP.md +30 -0
- package/templates/maxy/agents/admin/HEARTBEAT.md +6 -0
- package/templates/maxy/agents/admin/IDENTITY.md +13 -0
- package/templates/maxy/agents/admin/SOUL.md +21 -0
- package/templates/maxy/agents/admin/TOOLS.md +20 -0
- package/templates/maxy/agents/admin/USER.md +17 -0
- package/templates/maxy/agents/public/AGENTS.md +72 -0
- package/templates/maxy/agents/public/HEARTBEAT.md +2 -0
- package/templates/maxy/agents/public/IDENTITY.md +13 -0
- package/templates/maxy/agents/public/SOUL.md +60 -0
- package/templates/maxy/agents/public/TOOLS.md +20 -0
- package/templates/maxy/agents/public/USER.md +17 -0
- package/templates/maxy/memory/public/FAQ.md +241 -0
- package/templates/maxy/skills/maxy/SKILL.md +55 -0
- package/templates/maxy/skills/personal-assistant/SKILL.md +50 -0
- package/templates/taskmaster/agents/admin/AGENTS.md +20 -0
- package/templates/taskmaster/agents/public/AGENTS.md +9 -0
- 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 {
|
|
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
|
|
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
|
|
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
|
@@ -79,12 +79,12 @@ Customer data exists in two tiers:
|
|
|
79
79
|
|
|
80
80
|
### Secure Records (Tamper-Proof)
|
|
81
81
|
|
|
82
|
-
Use the `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 (`
|
|
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 (`
|
|
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 `
|
|
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
|
-
| `
|
|
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:** `
|
|
29
|
-
2. **Blacklist:** `
|
|
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
|
-
| `
|
|
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 `
|
|
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. `
|
|
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. `
|
|
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. `
|
|
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. `
|
|
9
|
-
2. `
|
|
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,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.
|