@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.
- package/dist/agents/skills/agent-scope.js +14 -0
- package/dist/agents/tools/skill-pack-install-tool.js +54 -36
- package/dist/agents/tools/skill-pack-sign-tool.js +139 -37
- package/dist/agents/tools/system-status-tool.js +1 -1
- package/dist/build-info.json +3 -3
- package/dist/config/agent-tools-reconcile.js +66 -0
- package/dist/control-ui/assets/{index-DbeiXb7c.js → index-fP8Y5Vwq.js} +163 -158
- package/dist/control-ui/assets/index-fP8Y5Vwq.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/gateway/server.impl.js +15 -1
- package/dist/license/skill-pack.js +63 -26
- package/package.json +1 -1
- package/skills/skill-pack-distribution/SKILL.md +10 -5
- package/dist/control-ui/assets/index-DbeiXb7c.js.map +0 -1
|
@@ -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-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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 (
|
|
109
|
-
return { valid: false, message: "
|
|
156
|
+
if (content.skills.length === 0) {
|
|
157
|
+
return { valid: false, message: "Skill pack contains no skills" };
|
|
110
158
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
@@ -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 [
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|