@rubytech/taskmaster 1.0.3 → 1.0.5
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/README.md +1 -1
- package/dist/agents/system-prompt.js +2 -2
- package/dist/agents/tool-display.json +1 -1
- package/dist/agents/tools/cron-tool.js +19 -14
- package/dist/build-info.json +3 -3
- package/dist/cli/provision-seed.js +5 -1
- package/dist/config/version.js +10 -0
- package/dist/gateway/protocol/schema/cron.js +4 -0
- package/dist/gateway/server-methods/cron.js +4 -0
- package/dist/gateway/server.impl.js +12 -0
- package/dist/infra/update-check.js +1 -1
- package/dist/logging/logger.js +23 -11
- package/package.json +1 -1
- package/scripts/install.sh +2 -2
- package/scripts/postinstall.js +7 -1
- package/skills/business-assistant/SKILL.md +2 -2
- package/skills/event-management/SKILL.md +15 -0
- package/skills/event-management/references/events.md +120 -0
- package/taskmaster-docs/USER-GUIDE.md +3 -3
- package/templates/taskmaster/agents/admin/AGENTS.md +27 -6
- package/templates/taskmaster/skills/business-assistant/SKILL.md +80 -0
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ export function buildAgentSystemPrompt(params) {
|
|
|
122
122
|
browser: "Control web browser",
|
|
123
123
|
canvas: "Present/eval/snapshot the Canvas",
|
|
124
124
|
nodes: "List/describe/notify/camera/screen on paired nodes",
|
|
125
|
-
cron: "Manage
|
|
125
|
+
cron: "Manage scheduled events and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)",
|
|
126
126
|
message: "Send messages and channel actions",
|
|
127
127
|
gateway: "Restart, apply config, or run updates on the running Taskmaster process",
|
|
128
128
|
agents_list: "List agent ids allowed for sessions_spawn",
|
|
@@ -263,7 +263,7 @@ export function buildAgentSystemPrompt(params) {
|
|
|
263
263
|
"- browser: control Taskmaster's dedicated browser",
|
|
264
264
|
"- canvas: present/eval/snapshot the Canvas",
|
|
265
265
|
"- nodes: list/describe/notify/camera/screen on paired nodes",
|
|
266
|
-
"- cron: manage
|
|
266
|
+
"- cron: manage scheduled events and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)",
|
|
267
267
|
"- sessions_list: list sessions",
|
|
268
268
|
"- sessions_history: fetch session history",
|
|
269
269
|
"- sessions_send: send to another session",
|
|
@@ -113,21 +113,21 @@ async function buildReminderContextLines(params) {
|
|
|
113
113
|
}
|
|
114
114
|
export function createCronTool(opts) {
|
|
115
115
|
return {
|
|
116
|
-
label: "
|
|
116
|
+
label: "Events",
|
|
117
117
|
name: "cron",
|
|
118
|
-
description: `Manage
|
|
118
|
+
description: `Manage scheduled events (status/list/add/update/remove/run/runs) and send wake events.
|
|
119
119
|
|
|
120
120
|
ACTIONS:
|
|
121
|
-
- status: Check
|
|
122
|
-
- list: List
|
|
123
|
-
- add: Create
|
|
124
|
-
- update: Modify
|
|
125
|
-
- remove: Delete
|
|
126
|
-
- run: Trigger
|
|
127
|
-
- runs: Get
|
|
121
|
+
- status: Check event scheduler status
|
|
122
|
+
- list: List events (use includeDisabled:true to include disabled). Returns all events for your account.
|
|
123
|
+
- add: Create event (requires job object, see schema below)
|
|
124
|
+
- update: Modify event (requires jobId + patch object)
|
|
125
|
+
- remove: Delete event (requires jobId)
|
|
126
|
+
- run: Trigger event immediately (requires jobId)
|
|
127
|
+
- runs: Get event run history (requires jobId)
|
|
128
128
|
- wake: Send wake event (requires text, optional mode)
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
EVENT SCHEMA (for add action):
|
|
131
131
|
{
|
|
132
132
|
"name": "string (optional)",
|
|
133
133
|
"schedule": { ... }, // Required: when to run
|
|
@@ -158,7 +158,7 @@ WAKE MODES (for wake action):
|
|
|
158
158
|
- "next-heartbeat" (default): Wake on next heartbeat
|
|
159
159
|
- "now": Wake immediately
|
|
160
160
|
|
|
161
|
-
Use jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the
|
|
161
|
+
Use jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the event text.`,
|
|
162
162
|
parameters: CronToolSchema,
|
|
163
163
|
execute: async (_toolCallId, args) => {
|
|
164
164
|
const params = args;
|
|
@@ -171,10 +171,15 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
|
|
171
171
|
switch (action) {
|
|
172
172
|
case "status":
|
|
173
173
|
return jsonResult(await callGatewayTool("cron.status", gatewayOpts, {}));
|
|
174
|
-
case "list":
|
|
175
|
-
|
|
174
|
+
case "list": {
|
|
175
|
+
const listParams = {
|
|
176
176
|
includeDisabled: Boolean(params.includeDisabled),
|
|
177
|
-
}
|
|
177
|
+
};
|
|
178
|
+
const accountId = opts?.agentAccountId;
|
|
179
|
+
if (accountId)
|
|
180
|
+
listParams.accountId = accountId;
|
|
181
|
+
return jsonResult(await callGatewayTool("cron.list", gatewayOpts, listParams));
|
|
182
|
+
}
|
|
178
183
|
case "add": {
|
|
179
184
|
if (!params.job || typeof params.job !== "object") {
|
|
180
185
|
throw new Error("job required");
|
package/dist/build-info.json
CHANGED
|
@@ -87,7 +87,11 @@ export function buildDefaultAgentList(workspaceRoot) {
|
|
|
87
87
|
],
|
|
88
88
|
},
|
|
89
89
|
write: {
|
|
90
|
-
include: [
|
|
90
|
+
include: [
|
|
91
|
+
"memory/users/{peer}/**",
|
|
92
|
+
"memory/groups/{peer}/**",
|
|
93
|
+
"memory/shared/events/**",
|
|
94
|
+
],
|
|
91
95
|
},
|
|
92
96
|
},
|
|
93
97
|
},
|
package/dist/config/version.js
CHANGED
|
@@ -13,11 +13,21 @@ export function parseTaskmasterVersion(raw) {
|
|
|
13
13
|
revision: revision ? Number.parseInt(revision, 10) : 0,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Date-based versions (e.g. 2026.1.25) have major >= 2000.
|
|
18
|
+
* Semver versions (e.g. 1.0.4) have major < 100.
|
|
19
|
+
* Comparing across formats is meaningless — return null.
|
|
20
|
+
*/
|
|
21
|
+
function isDateVersion(v) {
|
|
22
|
+
return v.major >= 2000;
|
|
23
|
+
}
|
|
16
24
|
export function compareTaskmasterVersions(a, b) {
|
|
17
25
|
const parsedA = parseTaskmasterVersion(a);
|
|
18
26
|
const parsedB = parseTaskmasterVersion(b);
|
|
19
27
|
if (!parsedA || !parsedB)
|
|
20
28
|
return null;
|
|
29
|
+
if (isDateVersion(parsedA) !== isDateVersion(parsedB))
|
|
30
|
+
return null;
|
|
21
31
|
if (parsedA.major !== parsedB.major)
|
|
22
32
|
return parsedA.major < parsedB.major ? -1 : 1;
|
|
23
33
|
if (parsedA.minor !== parsedB.minor)
|
|
@@ -66,6 +66,7 @@ export const CronJobStateSchema = Type.Object({
|
|
|
66
66
|
export const CronJobSchema = Type.Object({
|
|
67
67
|
id: NonEmptyString,
|
|
68
68
|
agentId: Type.Optional(NonEmptyString),
|
|
69
|
+
accountId: Type.Optional(NonEmptyString),
|
|
69
70
|
name: NonEmptyString,
|
|
70
71
|
description: Type.Optional(Type.String()),
|
|
71
72
|
enabled: Type.Boolean(),
|
|
@@ -82,11 +83,13 @@ export const CronJobSchema = Type.Object({
|
|
|
82
83
|
export const CronListParamsSchema = Type.Object({
|
|
83
84
|
includeDisabled: Type.Optional(Type.Boolean()),
|
|
84
85
|
agentIds: Type.Optional(Type.Array(Type.String())),
|
|
86
|
+
accountId: Type.Optional(Type.String()),
|
|
85
87
|
}, { additionalProperties: false });
|
|
86
88
|
export const CronStatusParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
87
89
|
export const CronAddParamsSchema = Type.Object({
|
|
88
90
|
name: NonEmptyString,
|
|
89
91
|
agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
92
|
+
accountId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
90
93
|
description: Type.Optional(Type.String()),
|
|
91
94
|
enabled: Type.Optional(Type.Boolean()),
|
|
92
95
|
deleteAfterRun: Type.Optional(Type.Boolean()),
|
|
@@ -99,6 +102,7 @@ export const CronAddParamsSchema = Type.Object({
|
|
|
99
102
|
export const CronJobPatchSchema = Type.Object({
|
|
100
103
|
name: Type.Optional(NonEmptyString),
|
|
101
104
|
agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
105
|
+
accountId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
102
106
|
description: Type.Optional(Type.String()),
|
|
103
107
|
enabled: Type.Optional(Type.Boolean()),
|
|
104
108
|
deleteAfterRun: Type.Optional(Type.Boolean()),
|
|
@@ -28,6 +28,10 @@ export const cronHandlers = {
|
|
|
28
28
|
return jobAgent !== undefined && normalized.includes(jobAgent);
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
|
+
if (typeof p.accountId === "string" && p.accountId.trim()) {
|
|
32
|
+
const target = p.accountId.trim();
|
|
33
|
+
jobs = jobs.filter((job) => job.accountId === target);
|
|
34
|
+
}
|
|
31
35
|
respond(true, { jobs }, undefined);
|
|
32
36
|
},
|
|
33
37
|
"cron.status": async ({ params, respond, context }) => {
|
|
@@ -5,6 +5,7 @@ import { listChannelPlugins } from "../channels/plugins/index.js";
|
|
|
5
5
|
import { createDefaultDeps } from "../cli/deps.js";
|
|
6
6
|
import { formatCliCommand } from "../cli/command-format.js";
|
|
7
7
|
import { CONFIG_PATH_TASKMASTER, isNixMode, loadConfig, migrateLegacyConfig, readConfigFileSnapshot, writeConfigFile, } from "../config/config.js";
|
|
8
|
+
import { VERSION } from "../version.js";
|
|
8
9
|
import { isDiagnosticsEnabled } from "../infra/diagnostic-events.js";
|
|
9
10
|
import { logAcceptedEnvOption } from "../infra/env.js";
|
|
10
11
|
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
|
@@ -116,6 +117,17 @@ export async function startGatewayServer(port = 18789, opts = {}) {
|
|
|
116
117
|
log.warn(`gateway: failed to persist plugin auto-enable changes: ${String(err)}`);
|
|
117
118
|
}
|
|
118
119
|
}
|
|
120
|
+
// Stamp config with running version on startup so upgrades keep the stamp current.
|
|
121
|
+
const storedVersion = configSnapshot.config.meta?.lastTouchedVersion;
|
|
122
|
+
if (configSnapshot.exists && storedVersion !== VERSION) {
|
|
123
|
+
try {
|
|
124
|
+
await writeConfigFile(configSnapshot.config);
|
|
125
|
+
log.info(`gateway: updated config version stamp from ${storedVersion ?? "(none)"} to ${VERSION}`);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
log.warn(`gateway: failed to update config version stamp: ${String(err)}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
119
131
|
const cfgAtStart = loadConfig();
|
|
120
132
|
// License check — gateway always starts (so setup UI is reachable),
|
|
121
133
|
// but pages other than /setup will redirect until a license is activated.
|
|
@@ -214,7 +214,7 @@ export async function fetchNpmTagVersion(params) {
|
|
|
214
214
|
const timeoutMs = params?.timeoutMs ?? 3500;
|
|
215
215
|
const tag = params.tag;
|
|
216
216
|
try {
|
|
217
|
-
const res = await fetchWithTimeout(`https://registry.npmjs.org
|
|
217
|
+
const res = await fetchWithTimeout(`https://registry.npmjs.org/@rubytech%2Ftaskmaster/${encodeURIComponent(tag)}`, timeoutMs);
|
|
218
218
|
if (!res.ok) {
|
|
219
219
|
return { tag, version: null, error: `HTTP ${res.status}` };
|
|
220
220
|
}
|
package/dist/logging/logger.js
CHANGED
|
@@ -26,20 +26,32 @@ function attachExternalTransport(logger, transport) {
|
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
|
+
let resolvingSettings = false;
|
|
29
30
|
function resolveSettings() {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
// Guard against recursion: loadConfig() may trigger logging during
|
|
32
|
+
// validation, which calls getLogger() → resolveSettings() again.
|
|
33
|
+
if (resolvingSettings) {
|
|
34
|
+
return { level: "info", file: defaultRollingPathForToday() };
|
|
35
|
+
}
|
|
36
|
+
resolvingSettings = true;
|
|
37
|
+
try {
|
|
38
|
+
let cfg = loggingState.overrideSettings ?? readLoggingConfig();
|
|
39
|
+
if (!cfg) {
|
|
40
|
+
try {
|
|
41
|
+
const loaded = requireConfig("../config/config.js");
|
|
42
|
+
cfg = loaded.loadConfig?.().logging;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
cfg = undefined;
|
|
46
|
+
}
|
|
38
47
|
}
|
|
48
|
+
const level = normalizeLogLevel(cfg?.level, "info");
|
|
49
|
+
const file = cfg?.file ?? defaultRollingPathForToday();
|
|
50
|
+
return { level, file };
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
resolvingSettings = false;
|
|
39
54
|
}
|
|
40
|
-
const level = normalizeLogLevel(cfg?.level, "info");
|
|
41
|
-
const file = cfg?.file ?? defaultRollingPathForToday();
|
|
42
|
-
return { level, file };
|
|
43
55
|
}
|
|
44
56
|
function settingsChanged(a, b) {
|
|
45
57
|
if (!a)
|
package/package.json
CHANGED
package/scripts/install.sh
CHANGED
|
@@ -4,10 +4,10 @@ set -euo pipefail
|
|
|
4
4
|
# Taskmaster — one-command install for fresh devices (Pi or Mac).
|
|
5
5
|
#
|
|
6
6
|
# Usage:
|
|
7
|
-
# curl -fsSL https://taskmaster.bot/install.sh | bash
|
|
7
|
+
# curl -fsSL https://taskmaster.bot/install.sh | sudo bash
|
|
8
8
|
#
|
|
9
9
|
# With custom port:
|
|
10
|
-
# curl -fsSL https://taskmaster.bot/install.sh | bash -s -- --port 19000
|
|
10
|
+
# curl -fsSL https://taskmaster.bot/install.sh | sudo bash -s -- --port 19000
|
|
11
11
|
|
|
12
12
|
PORT=""
|
|
13
13
|
for arg in "$@"; do
|
package/scripts/postinstall.js
CHANGED
|
@@ -2,7 +2,13 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
5
|
+
let setupGitHooks = () => {};
|
|
6
|
+
try {
|
|
7
|
+
const mod = await import("./setup-git-hooks.js");
|
|
8
|
+
setupGitHooks = mod.setupGitHooks;
|
|
9
|
+
} catch {
|
|
10
|
+
// setup-git-hooks.js is not shipped in the npm package — skip in production
|
|
11
|
+
}
|
|
6
12
|
|
|
7
13
|
function detectPackageManager(ua = process.env.npm_config_user_agent ?? "") {
|
|
8
14
|
// Examples:
|
|
@@ -214,7 +214,7 @@ Always ask the business owner before reshuffling. Never move a job without appro
|
|
|
214
214
|
**Real-time location (GPS):**
|
|
215
215
|
If the business owner's phone is paired as a node, you can query their GPS location on demand using `location.get`. Use this for:
|
|
216
216
|
- **Customer asks "where is he?"** → Check GPS, compare to next appointment location, give an ETA: "[name] left his last job about 15 minutes ago. He should be with you in about 20 minutes."
|
|
217
|
-
- **Late detection** → If you have a
|
|
217
|
+
- **Late detection** → If you have a scheduled event check 10 minutes before each appointment, query GPS. If the business owner is still far away, proactively message the customer: "Hi! [name]'s running about 15 minutes late — he's on his way. Sorry for the wait!" This turns a complaint into a professional courtesy.
|
|
218
218
|
- **Business owner or their partner asks "where is [name]?"** → Report current location and next job ETA.
|
|
219
219
|
- **End-of-day** → If the business owner is near their home postcode, stop offering same-day appointments.
|
|
220
220
|
- Do NOT track location continuously. Only query when you have a specific reason.
|
|
@@ -339,7 +339,7 @@ Customers can always request to speak to the business owner directly. The agent
|
|
|
339
339
|
|
|
340
340
|
## Morning Briefing
|
|
341
341
|
|
|
342
|
-
If a
|
|
342
|
+
If a scheduled event is set up for morning briefings, send a daily summary. **Group jobs by area, not just time.** Include postcodes, estimated travel, and total driving time. See Schedule Optimisation section for full format.
|
|
343
343
|
|
|
344
344
|
> **Morning [name]! Here's your Tuesday:**
|
|
345
345
|
>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Event Management
|
|
2
|
+
|
|
3
|
+
Applies when handling anything time-bound: appointments, meetings, reminders, follow-ups, callbacks, deadlines — any commitment or scheduled action between one or more parties.
|
|
4
|
+
|
|
5
|
+
## When to activate
|
|
6
|
+
|
|
7
|
+
- Customer requests or confirms an appointment
|
|
8
|
+
- Business owner asks to schedule, reschedule, or cancel something
|
|
9
|
+
- A reminder, follow-up, or callback needs to be recorded
|
|
10
|
+
- Someone asks "what's on [day/week]?" or "show me the schedule"
|
|
11
|
+
- A deadline or time-sensitive commitment is mentioned
|
|
12
|
+
|
|
13
|
+
## References
|
|
14
|
+
|
|
15
|
+
Load `references/events.md` for the standard event template, file naming, dual-write pattern, and calendar query instructions.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Events
|
|
2
|
+
|
|
3
|
+
Events are any time-bound information: appointments, meetings, reminders, follow-ups, callbacks, deadlines. Anything that represents a commitment, agreement, or scheduled action between one or more parties.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Standard Event Format
|
|
8
|
+
|
|
9
|
+
Every event file follows this structure. All fields above the `## Notes` section are required unless marked optional.
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
# Event: [Short descriptive title]
|
|
13
|
+
|
|
14
|
+
- **Date:** YYYY-MM-DD
|
|
15
|
+
- **Time:** HH:MM (24h, local timezone)
|
|
16
|
+
- **Duration:** [number] min
|
|
17
|
+
- **Customer:** [Name] ([phone])
|
|
18
|
+
- **Service:** [what is being done]
|
|
19
|
+
- **Location:** [address or "Remote" / "Phone"]
|
|
20
|
+
- **Status:** confirmed | tentative | cancelled | completed
|
|
21
|
+
- **Created by:** [agent id — e.g. public, admin]
|
|
22
|
+
- **Created:** YYYY-MM-DDTHH:MM:SSZ
|
|
23
|
+
|
|
24
|
+
## Notes
|
|
25
|
+
[Free text — special instructions, context, history of changes]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Field guidance
|
|
29
|
+
|
|
30
|
+
- **Date/Time** — always local timezone. If the business owner's timezone is known, use it consistently.
|
|
31
|
+
- **Duration** — estimated in minutes. If unknown, use a reasonable default for the service type and note the assumption.
|
|
32
|
+
- **Customer** — full name and phone in international format. For internal events (admin meetings, personal reminders) use the owner's name and number.
|
|
33
|
+
- **Status** — `confirmed` when both parties have agreed. `tentative` when proposed but not confirmed. `cancelled` when called off. `completed` after the event has passed and the work is done.
|
|
34
|
+
- **Service** — brief description of the work or purpose. Not a category — use plain language ("boiler service", "quote for kitchen extension", "team meeting").
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## File Naming
|
|
39
|
+
|
|
40
|
+
Files are named for sort order and human readability:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
YYYY-MM-DD-slug.md
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
- `2026-02-20-boiler-service-john-smith.md`
|
|
48
|
+
- `2026-02-21-quote-mrs-jenkins.md`
|
|
49
|
+
- `2026-03-01-team-meeting.md`
|
|
50
|
+
|
|
51
|
+
The slug should be lowercase, hyphenated, and include enough context to identify the event without opening the file.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Where to Write
|
|
56
|
+
|
|
57
|
+
Every event must be written to **two** locations:
|
|
58
|
+
|
|
59
|
+
1. **Per-customer record:** `memory/users/{phone}/events/YYYY-MM-DD-slug.md`
|
|
60
|
+
- Scoped to the customer. Visible when reviewing that customer's history.
|
|
61
|
+
2. **Shared calendar:** `memory/shared/events/YYYY-MM-DD-slug.md`
|
|
62
|
+
- Visible to all agents in the account. The admin agent uses this for calendar queries.
|
|
63
|
+
|
|
64
|
+
Both files have identical content. Write to both in the same operation.
|
|
65
|
+
|
|
66
|
+
For events that have no external customer (internal meetings, personal reminders), write only to `memory/shared/events/`.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Updating Events
|
|
71
|
+
|
|
72
|
+
When an event changes (rescheduled, cancelled, details updated):
|
|
73
|
+
|
|
74
|
+
1. Update **both** copies — the per-customer file and the shared calendar file.
|
|
75
|
+
2. Change the **Status** field appropriately.
|
|
76
|
+
3. Add a note in the `## Notes` section explaining the change and when it happened.
|
|
77
|
+
4. If the date changes, rename both files to reflect the new date. Delete the old files.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Querying the Calendar
|
|
82
|
+
|
|
83
|
+
To answer "what's on [day/week]?" or "show me the schedule":
|
|
84
|
+
|
|
85
|
+
1. List files in `memory/shared/events/` — filenames are date-prefixed, so lexicographic order is chronological order.
|
|
86
|
+
2. Filter by date prefix (e.g., `2026-02-20` for a specific day, `2026-02-` for a month).
|
|
87
|
+
3. Read the matching files for details.
|
|
88
|
+
|
|
89
|
+
This is a directory listing + read operation, not a search. It is fast and complete.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Linking Events to Scheduled Reminders
|
|
94
|
+
|
|
95
|
+
Events (calendar data) and scheduled reminders (the `cron` tool) serve different purposes:
|
|
96
|
+
|
|
97
|
+
- **Event file** — the record of what is happening, when, and with whom. Source of truth for the calendar.
|
|
98
|
+
- **Scheduled reminder** (`cron` tool) — an automated trigger that fires at a specific time to notify or act. Optional.
|
|
99
|
+
|
|
100
|
+
When you create an event that also needs a reminder (e.g., "remind me 30 minutes before"), create both:
|
|
101
|
+
1. The event file (in both locations, as above)
|
|
102
|
+
2. A scheduled reminder via the `cron` tool, referencing the event
|
|
103
|
+
|
|
104
|
+
The reminder's payload text should reference the event clearly so the recipient has full context when it fires.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Event Types
|
|
109
|
+
|
|
110
|
+
This format covers all time-bound data. Examples:
|
|
111
|
+
|
|
112
|
+
| Type | Customer field | Location | Notes |
|
|
113
|
+
|------|---------------|----------|-------|
|
|
114
|
+
| Customer appointment | Customer name + phone | Job site address | Service details, access instructions |
|
|
115
|
+
| Quote visit | Customer name + phone | Site address | What to quote, measurements needed |
|
|
116
|
+
| Callback | Customer name + phone | "Phone" | Why they're expecting a call, promised window |
|
|
117
|
+
| Follow-up | Customer name + phone | n/a | What to follow up on, last interaction context |
|
|
118
|
+
| Internal meeting | Owner name + phone | Meeting link or office | Agenda, attendees |
|
|
119
|
+
| Personal reminder | Owner name + phone | n/a | What to remember, deadline context |
|
|
120
|
+
| Deadline | Owner or customer | n/a | What's due, consequences of missing it |
|
|
@@ -39,7 +39,7 @@ You'll need a monitor, keyboard, and mouse connected to the Pi.
|
|
|
39
39
|
2. Run:
|
|
40
40
|
|
|
41
41
|
```
|
|
42
|
-
curl -fsSL https://taskmaster.bot/install.sh | bash
|
|
42
|
+
curl -fsSL https://taskmaster.bot/install.sh | sudo bash
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
3. Wait for it to finish (a few minutes — it installs Node.js if needed)
|
|
@@ -54,7 +54,7 @@ You can also access the setup page from any other device on the same network at
|
|
|
54
54
|
Open **Terminal** (search for "Terminal" in Spotlight) and run:
|
|
55
55
|
|
|
56
56
|
```
|
|
57
|
-
curl -fsSL https://taskmaster.bot/install.sh | bash
|
|
57
|
+
curl -fsSL https://taskmaster.bot/install.sh | sudo bash
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
This installs Node.js (if needed), Taskmaster, and sets up the background service. Once finished, open your browser and go to: **http://localhost:18789/setup**
|
|
@@ -62,7 +62,7 @@ This installs Node.js (if needed), Taskmaster, and sets up the background servic
|
|
|
62
62
|
**Custom port:** If you're running multiple Taskmaster instances (e.g., one on a Pi and one on a Mac), give each a different port:
|
|
63
63
|
|
|
64
64
|
```
|
|
65
|
-
curl -fsSL https://taskmaster.bot/install.sh | bash -s -- --port 19000
|
|
65
|
+
curl -fsSL https://taskmaster.bot/install.sh | sudo bash -s -- --port 19000
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
### Option D: Install from a package file
|
|
@@ -142,11 +142,11 @@ Keep it brief. Bullet points. Only what matters.
|
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
|
145
|
-
## Reminders (
|
|
145
|
+
## Reminders (Events Tool)
|
|
146
146
|
|
|
147
|
-
When [OWNER_NAME] asks you to remind them about something, use the `cron` tool correctly.
|
|
147
|
+
When [OWNER_NAME] asks you to remind them about something, use the `cron` tool (named "Events" in the UI) correctly.
|
|
148
148
|
|
|
149
|
-
### CRITICAL: Two Types of
|
|
149
|
+
### CRITICAL: Two Types of Scheduled Events
|
|
150
150
|
|
|
151
151
|
| Type | When to Use | What Happens |
|
|
152
152
|
|------|-------------|--------------|
|
|
@@ -197,11 +197,32 @@ This does NOT send a message! It just adds text to the session that [OWNER_NAME]
|
|
|
197
197
|
- **Recurring:** `{ "kind": "every", "everyMs": <interval> }` — repeats at interval
|
|
198
198
|
- **Cron expr:** `{ "kind": "cron", "expr": "0 9 * * 1-5" }` — standard cron syntax
|
|
199
199
|
|
|
200
|
-
### When a
|
|
200
|
+
### When a Scheduled Event Fires (Isolated Agent)
|
|
201
201
|
|
|
202
|
-
When YOU are running as an isolated
|
|
202
|
+
When YOU are running as an isolated event agent:
|
|
203
203
|
- **DO NOT call the `message` tool** — delivery is handled automatically by the framework
|
|
204
204
|
- Just output the text you want to send
|
|
205
|
-
- The `deliver: true` + `to` + `channel` from the
|
|
205
|
+
- The `deliver: true` + `to` + `channel` from the event config handles delivery
|
|
206
206
|
|
|
207
207
|
**Why:** If you call `message` directly, you might use the wrong recipient. Let the framework deliver.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Shared Calendar
|
|
212
|
+
|
|
213
|
+
You have access to a shared calendar at `memory/shared/events/`. This contains all scheduled events across all agents in your account.
|
|
214
|
+
|
|
215
|
+
### Querying the Calendar
|
|
216
|
+
|
|
217
|
+
When [OWNER_NAME] asks about upcoming appointments, schedules, or "what's on":
|
|
218
|
+
1. List files in `memory/shared/events/` — each file is one event, named `YYYY-MM-DD-slug.md`
|
|
219
|
+
2. Read the relevant files for details
|
|
220
|
+
3. Files are sorted by date via filename — no search needed
|
|
221
|
+
|
|
222
|
+
### Event Format
|
|
223
|
+
|
|
224
|
+
All calendar events follow a standard format. When creating events, write to **both** locations:
|
|
225
|
+
1. `memory/users/{phone}/events/YYYY-MM-DD-slug.md` — per-customer record
|
|
226
|
+
2. `memory/shared/events/YYYY-MM-DD-slug.md` — shared calendar
|
|
227
|
+
|
|
228
|
+
See `skills/event-management/references/events.md` for the full event template and guidelines.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Business Assistant Skill
|
|
2
|
+
|
|
3
|
+
<description>
|
|
4
|
+
Handle customer enquiries, scheduling, and day-to-day business communication for a small business.
|
|
5
|
+
</description>
|
|
6
|
+
|
|
7
|
+
<triggers>
|
|
8
|
+
- Customer enquiries about services, availability, pricing
|
|
9
|
+
- Scheduling and appointment requests
|
|
10
|
+
- Follow-ups on outstanding requests
|
|
11
|
+
- General business questions
|
|
12
|
+
</triggers>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Your Role
|
|
17
|
+
|
|
18
|
+
You're the virtual receptionist. You handle messages while the business owner is busy. Look up the owner's name and business name from memory so you can use them naturally in conversation.
|
|
19
|
+
|
|
20
|
+
## What You Can Do
|
|
21
|
+
|
|
22
|
+
### Answer Enquiries
|
|
23
|
+
- "What do you offer?" → Check memory for services and business info
|
|
24
|
+
- "Are you available [date]?" → Check schedule, offer to book
|
|
25
|
+
- "Where are you based?" → Check memory for location
|
|
26
|
+
|
|
27
|
+
### Handle Scheduling
|
|
28
|
+
1. Get the details (what they need, when)
|
|
29
|
+
2. Get contact details (name, phone)
|
|
30
|
+
3. Confirm with customer
|
|
31
|
+
4. Add to schedule (or flag for the business owner to confirm)
|
|
32
|
+
|
|
33
|
+
### Handle Follow-ups
|
|
34
|
+
- Check if the request was addressed
|
|
35
|
+
- Gentle reminder if something is outstanding
|
|
36
|
+
|
|
37
|
+
### After Hours
|
|
38
|
+
- Acknowledge the message
|
|
39
|
+
- Say the business owner will respond during working hours
|
|
40
|
+
- If urgent, provide contact info from memory
|
|
41
|
+
|
|
42
|
+
## What You Can't Do
|
|
43
|
+
|
|
44
|
+
- **Give specialised professional advice** — "I'll have [owner] get back to you on that directly."
|
|
45
|
+
- **Quote specific prices** unless you have them in memory — "Let me check and get back to you on pricing."
|
|
46
|
+
- **Make commitments** — "Let me check with [owner] and confirm."
|
|
47
|
+
|
|
48
|
+
(Use the owner's actual name — look it up from memory/shared/business.md.)
|
|
49
|
+
|
|
50
|
+
## Message Style
|
|
51
|
+
|
|
52
|
+
- Short, friendly, professional
|
|
53
|
+
- WhatsApp-native (no walls of text)
|
|
54
|
+
- Use customer's name if known
|
|
55
|
+
- Sign off warmly: "Thanks! / Cheers! / Speak soon!"
|
|
56
|
+
|
|
57
|
+
## Example Conversations
|
|
58
|
+
|
|
59
|
+
### New Enquiry
|
|
60
|
+
> Customer: "Hi, I saw your business online. Can you help me with...?"
|
|
61
|
+
>
|
|
62
|
+
> You: "Hi! Thanks for reaching out. Yes, we can help with that. Would you like me to arrange a time to discuss the details?"
|
|
63
|
+
|
|
64
|
+
### Scheduling
|
|
65
|
+
> Customer: "Can I book something for this week?"
|
|
66
|
+
>
|
|
67
|
+
> You: "Of course! What day works best for you? I'll check availability."
|
|
68
|
+
|
|
69
|
+
### After Hours
|
|
70
|
+
> Customer: [Message at 10pm]
|
|
71
|
+
>
|
|
72
|
+
> You: "Thanks for your message! We'll get back to you tomorrow morning."
|
|
73
|
+
|
|
74
|
+
## Escalation
|
|
75
|
+
|
|
76
|
+
Pass to the business owner when:
|
|
77
|
+
- Customer is upset or has a complaint
|
|
78
|
+
- Question you can't answer confidently
|
|
79
|
+
- Pricing negotiation or custom request
|
|
80
|
+
- Anything unusual
|