@rubytech/taskmaster 1.20.2 → 1.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@
6
6
  <title>Taskmaster Control</title>
7
7
  <meta name="color-scheme" content="dark light" />
8
8
  <link rel="icon" type="image/png" href="./favicon.png" />
9
- <script type="module" crossorigin src="./assets/index-DbeiXb7c.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-fP8Y5Vwq.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-0WHVrpg7.css">
11
11
  </head>
12
12
  <body>
@@ -11,7 +11,7 @@ import { CONFIG_PATH_TASKMASTER, isNixMode, loadConfig, migrateLegacyConfig, rea
11
11
  import { VERSION } from "../version.js";
12
12
  import { isDiagnosticsEnabled } from "../infra/diagnostic-events.js";
13
13
  import { logAcceptedEnvOption } from "../infra/env.js";
14
- import { reconcileAgentContactTools, reconcileBeaglePublicTools, reconcileControlPanelTools, reconcileQrGenerateTool, reconcileSkillReadTool, reconcileStaleToolEntries, } from "../config/agent-tools-reconcile.js";
14
+ import { reconcileAgentContactTools, reconcileBeaglePublicTools, reconcileControlPanelTools, reconcileQrGenerateTool, reconcileSkillReadTool, reconcileSkillsGroup, reconcileStaleToolEntries, } from "../config/agent-tools-reconcile.js";
15
15
  import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
16
16
  import { clearAgentRunContext, onAgentEvent } from "../infra/agent-events.js";
17
17
  import { onHeartbeatEvent } from "../infra/heartbeat-events.js";
@@ -228,6 +228,20 @@ export async function startGatewayServer(port = 18789, opts = {}) {
228
228
  log.warn(`gateway: failed to persist skill_read tool reconciliation: ${String(err)}`);
229
229
  }
230
230
  }
231
+ // Replace individual skill_read/skill_draft_save with group:skills on admin agents.
232
+ const skillsGroupReconcile = reconcileSkillsGroup({ config: configSnapshot.config });
233
+ if (skillsGroupReconcile.changes.length > 0) {
234
+ try {
235
+ await writeConfigFile(skillsGroupReconcile.config);
236
+ configSnapshot = await readConfigFileSnapshot();
237
+ log.info(`gateway: reconciled skills group:\n${skillsGroupReconcile.changes
238
+ .map((entry) => `- ${entry}`)
239
+ .join("\n")}`);
240
+ }
241
+ catch (err) {
242
+ log.warn(`gateway: failed to persist skills group reconciliation: ${String(err)}`);
243
+ }
244
+ }
231
245
  // Stamp config with running version on startup so upgrades keep the stamp current.
232
246
  const storedVersion = configSnapshot.config.meta?.lastTouchedVersion;
233
247
  if (configSnapshot.exists && storedVersion !== VERSION) {
@@ -21,10 +21,13 @@ export function buildCanonicalPayload(payload) {
21
21
  cid: payload.device.cid,
22
22
  },
23
23
  content: {
24
- skill: payload.content.skill,
25
- references: payload.content.references.map((r) => ({
26
- name: r.name,
27
- content: r.content,
24
+ skills: payload.content.skills.map((s) => ({
25
+ id: s.id,
26
+ skill: s.skill,
27
+ references: s.references.map((r) => ({
28
+ name: r.name,
29
+ content: r.content,
30
+ })),
28
31
  })),
29
32
  },
30
33
  signedAt: payload.signedAt,
@@ -59,6 +62,50 @@ const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
59
62
  MCowBQYDK2VwAyEA/t/C4A4I0rDlj5rEqv6Hy6VdHJr7WiJHWUxgwGz9HcM=
60
63
  -----END PUBLIC KEY-----`;
61
64
  const publicKey = crypto.createPublicKey(PUBLIC_KEY_PEM);
65
+ function validateSkillEntry(entry, index) {
66
+ if (!entry || typeof entry !== "object") {
67
+ return { valid: false, message: `Invalid skill entry at index ${index}` };
68
+ }
69
+ const e = entry;
70
+ if (typeof e.id !== "string" || !e.id) {
71
+ return { valid: false, message: `Missing or invalid skills[${index}].id` };
72
+ }
73
+ if (typeof e.skill !== "string") {
74
+ return { valid: false, message: `Missing or invalid skills[${index}].skill` };
75
+ }
76
+ if (!Array.isArray(e.references)) {
77
+ return { valid: false, message: `Missing or invalid skills[${index}].references` };
78
+ }
79
+ for (let i = 0; i < e.references.length; i++) {
80
+ const ref = e.references[i];
81
+ if (!ref || typeof ref !== "object") {
82
+ return { valid: false, message: `Invalid reference at skills[${index}].references[${i}]` };
83
+ }
84
+ if (typeof ref.name !== "string" || !ref.name) {
85
+ return {
86
+ valid: false,
87
+ message: `Missing reference name at skills[${index}].references[${i}]`,
88
+ };
89
+ }
90
+ if (typeof ref.content !== "string") {
91
+ return {
92
+ valid: false,
93
+ message: `Missing reference content at skills[${index}].references[${i}]`,
94
+ };
95
+ }
96
+ }
97
+ return {
98
+ valid: true,
99
+ entry: {
100
+ id: e.id,
101
+ skill: e.skill,
102
+ references: e.references.map((r) => ({
103
+ name: r.name,
104
+ content: r.content,
105
+ })),
106
+ },
107
+ };
108
+ }
62
109
  /** Verify a skill pack file's structure and Ed25519 signature. */
63
110
  export function verifySkillPack(raw) {
64
111
  if (raw === null || typeof raw !== "object") {
@@ -102,23 +149,19 @@ export function verifySkillPack(raw) {
102
149
  if (!content || typeof content !== "object") {
103
150
  return { valid: false, message: "Missing content" };
104
151
  }
105
- if (typeof content.skill !== "string") {
106
- return { valid: false, message: "Missing or invalid content.skill" };
152
+ // Validate skills array
153
+ if (!Array.isArray(content.skills)) {
154
+ return { valid: false, message: "Missing or invalid content.skills array" };
107
155
  }
108
- if (!Array.isArray(content.references)) {
109
- return { valid: false, message: "Missing or invalid content.references" };
156
+ if (content.skills.length === 0) {
157
+ return { valid: false, message: "Skill pack contains no skills" };
110
158
  }
111
- for (let i = 0; i < content.references.length; i++) {
112
- const ref = content.references[i];
113
- if (!ref || typeof ref !== "object") {
114
- return { valid: false, message: `Invalid reference at index ${i}` };
115
- }
116
- if (typeof ref.name !== "string" || !ref.name) {
117
- return { valid: false, message: `Missing or invalid reference name at index ${i}` };
118
- }
119
- if (typeof ref.content !== "string") {
120
- return { valid: false, message: `Missing or invalid reference content at index ${i}` };
121
- }
159
+ const skills = [];
160
+ for (let i = 0; i < content.skills.length; i++) {
161
+ const result = validateSkillEntry(content.skills[i], i);
162
+ if (!result.valid)
163
+ return result;
164
+ skills.push(result.entry);
122
165
  }
123
166
  // Validate signedAt
124
167
  if (typeof obj.signedAt !== "string" || !obj.signedAt) {
@@ -138,13 +181,7 @@ export function verifySkillPack(raw) {
138
181
  did: device.did,
139
182
  cid: device.cid,
140
183
  },
141
- content: {
142
- skill: content.skill,
143
- references: content.references.map((r) => ({
144
- name: r.name,
145
- content: r.content,
146
- })),
147
- },
184
+ content: { skills },
148
185
  signedAt: obj.signedAt,
149
186
  };
150
187
  // Verify Ed25519 signature
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.20.2",
3
+ "version": "1.21.1",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -6,22 +6,27 @@ metadata: {"taskmaster":{"always":true,"emoji":"📦","skillKey":"skill-pack-dis
6
6
 
7
7
  # Skill Pack Distribution
8
8
 
9
- You can sign and send licensed skill packs to customers.
9
+ You can sign and send licensed skill packs to customers. Packs can be single skills or bundles of multiple skills.
10
10
 
11
11
  ## Workflow
12
12
 
13
- 1. User asks to send a skill to a customer
13
+ 1. User asks to send a skill pack to a customer
14
14
  2. Look up the customer's contact record to get their `device_id` and customer ID
15
15
  3. Call `skill_pack_sign` with the pack ID, device ID, and customer ID
16
16
  4. Send the resulting `.skillpack.json` file to the customer's WhatsApp number
17
- 5. Include a message: "Here's your [Skill Name] skill pack. Forward this file to your admin agent and ask it to install the skill."
17
+ 5. Include a message: "Here's your [Pack Name] skill pack. Forward this file to your admin agent and ask it to install the skill."
18
18
 
19
19
  ## Available Packs
20
20
 
21
- Check the `skill-packs/` folder in this workspace for available templates. Each subfolder is a pack ID containing SKILL.md and optional references/.
21
+ Call `skill_pack_sign` with no arguments to list available packs. Shows each pack's type (single/bundle) and which skills are included.
22
+
23
+ ## Getting the Customer's Device ID
24
+
25
+ The customer can find their device ID in the Setup page under the License card's info modal (it has a copy button). If the customer asks their own admin agent for it, it's in `system_status(action: "license")` → `deviceId` field. Store it in their contact record for future use.
22
26
 
23
27
  ## Notes
24
28
 
25
29
  - Each pack is signed for a specific device — it cannot be installed on other devices
26
- - To update a customer's skill, sign and send a newer version (bump the version parameter)
30
+ - Bundles install all skills in one step the customer forwards one file
31
+ - To update a customer's skills, sign and send a newer version (bump the version parameter)
27
32
  - The customer's admin agent uses `skill_pack_install` to verify and install the pack