@rubytech/taskmaster 1.19.8 → 1.20.2
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/workspace.js +4 -0
- package/dist/agents/skills-status.js +15 -0
- package/dist/agents/taskmaster-tools.js +4 -0
- package/dist/agents/tool-policy.js +2 -1
- package/dist/agents/tools/skill-pack-install-tool.js +90 -0
- package/dist/agents/tools/skill-pack-sign-tool.js +91 -0
- package/dist/auto-reply/reply/get-reply-directives.js +3 -3
- package/dist/build-info.json +3 -3
- package/dist/control-ui/assets/{index-wJYMFZnC.js → index-DbeiXb7c.js} +442 -338
- package/dist/control-ui/assets/index-DbeiXb7c.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/gateway/server-methods/auth.js +40 -0
- package/dist/license/skill-pack.js +199 -0
- package/package.json +1 -1
- package/skills/skill-pack-distribution/SKILL.md +27 -0
- package/taskmaster-docs/USER-GUIDE.md +70 -8
- package/dist/control-ui/assets/index-wJYMFZnC.js.map +0 -1
|
@@ -7,6 +7,7 @@ import { resolveBundledSkillsDir } from "./bundled-dir.js";
|
|
|
7
7
|
import { shouldIncludeSkill } from "./config.js";
|
|
8
8
|
import { parseFrontmatter, resolveTaskmasterMetadata, resolveSkillInvocationPolicy, } from "./frontmatter.js";
|
|
9
9
|
import { resolvePluginSkillDirs } from "./plugin-skills.js";
|
|
10
|
+
import { hasMarker } from "../../license/skill-pack.js";
|
|
10
11
|
import { serializeByKey } from "./serialize.js";
|
|
11
12
|
const fsp = fs.promises;
|
|
12
13
|
const skillsLogger = createSubsystemLogger("skills");
|
|
@@ -333,6 +334,9 @@ export async function syncBundledSkillsToWorkspace(workspaceDir, opts) {
|
|
|
333
334
|
for (const name of entries) {
|
|
334
335
|
const dest = path.join(targetSkillsDir, name);
|
|
335
336
|
const exists = fs.existsSync(dest);
|
|
337
|
+
// Never overwrite marketplace-installed skills with bundled versions
|
|
338
|
+
if (exists && hasMarker(dest))
|
|
339
|
+
continue;
|
|
336
340
|
if (exists && !FORCE_RESYNC_SKILLS.has(name))
|
|
337
341
|
continue;
|
|
338
342
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { CONFIG_DIR } from "../utils.js";
|
|
4
|
+
import { hasMarker } from "../license/skill-pack.js";
|
|
4
5
|
import { hasBinary, isBundledSkillAllowed, isConfigPathTruthy, loadWorkspaceSkillEntries, resolveBundledAllowlist, resolveBundledSkillsDir, resolveConfigPath, resolveSkillConfig, resolveSkillsInstallPreferences, } from "./skills.js";
|
|
5
6
|
function resolveSkillKey(entry) {
|
|
6
7
|
return entry.taskmaster?.skillKey ?? entry.skill.name;
|
|
@@ -148,6 +149,18 @@ function buildSkillStatus(entry, config, prefs, eligibility, bundledSkillNames)
|
|
|
148
149
|
missing.config.length === 0 &&
|
|
149
150
|
missing.os.length === 0));
|
|
150
151
|
const preloaded = bundledSkillNames ? bundledSkillNames.has(entry.skill.name) : false;
|
|
152
|
+
const isMarketplace = hasMarker(entry.skill.baseDir);
|
|
153
|
+
let marketplaceVersion;
|
|
154
|
+
if (isMarketplace) {
|
|
155
|
+
try {
|
|
156
|
+
const markerContent = fs.readFileSync(path.join(entry.skill.baseDir, ".skillpack"), "utf-8");
|
|
157
|
+
const marker = JSON.parse(markerContent);
|
|
158
|
+
marketplaceVersion = typeof marker.version === "string" ? marker.version : undefined;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Marker exists but unreadable
|
|
162
|
+
}
|
|
163
|
+
}
|
|
151
164
|
return {
|
|
152
165
|
name: entry.skill.name,
|
|
153
166
|
description: entry.skill.description,
|
|
@@ -164,6 +177,8 @@ function buildSkillStatus(entry, config, prefs, eligibility, bundledSkillNames)
|
|
|
164
177
|
blockedByAllowlist,
|
|
165
178
|
eligible,
|
|
166
179
|
preloaded,
|
|
180
|
+
marketplace: isMarketplace,
|
|
181
|
+
marketplaceVersion,
|
|
167
182
|
requirements: {
|
|
168
183
|
bins: requiredBins,
|
|
169
184
|
anyBins: requiredAnyBins,
|
|
@@ -24,6 +24,8 @@ import { createCurrentTimeTool } from "./tools/current-time.js";
|
|
|
24
24
|
import { createAuthorizeAdminTool, createListAdminsTool, createRevokeAdminTool, } from "./tools/authorize-admin-tool.js";
|
|
25
25
|
import { createBootstrapCompleteTool } from "./tools/bootstrap-tool.js";
|
|
26
26
|
import { createLicenseGenerateTool } from "./tools/license-tool.js";
|
|
27
|
+
import { createSkillPackSignTool } from "./tools/skill-pack-sign-tool.js";
|
|
28
|
+
import { createSkillPackInstallTool } from "./tools/skill-pack-install-tool.js";
|
|
27
29
|
import { createContactCreateTool } from "./tools/contact-create-tool.js";
|
|
28
30
|
import { createContactDeleteTool } from "./tools/contact-delete-tool.js";
|
|
29
31
|
import { createFileDeleteTool } from "./tools/file-delete-tool.js";
|
|
@@ -158,6 +160,8 @@ export function createTaskmasterTools(options) {
|
|
|
158
160
|
createListAdminsTool({ agentAccountId: options?.agentAccountId }),
|
|
159
161
|
createBootstrapCompleteTool({ agentSessionKey: options?.agentSessionKey }),
|
|
160
162
|
createLicenseGenerateTool(),
|
|
163
|
+
createSkillPackSignTool({ workspaceDir: options?.workspaceDir ?? "" }),
|
|
164
|
+
createSkillPackInstallTool({ workspaceDir: options?.workspaceDir ?? "" }),
|
|
161
165
|
createContactCreateTool({ agentAccountId: options?.agentAccountId }),
|
|
162
166
|
createContactDeleteTool({ agentAccountId: options?.agentAccountId }),
|
|
163
167
|
createContactLookupTool({ agentAccountId: options?.agentAccountId }),
|
|
@@ -34,7 +34,7 @@ export const TOOL_GROUPS = {
|
|
|
34
34
|
// Nodes + device tools
|
|
35
35
|
"group:nodes": ["nodes"],
|
|
36
36
|
// Skill management
|
|
37
|
-
"group:skills": ["skill_read", "skill_draft_save"],
|
|
37
|
+
"group:skills": ["skill_read", "skill_draft_save", "skill_pack_install"],
|
|
38
38
|
// Contact record management
|
|
39
39
|
"group:contacts": [
|
|
40
40
|
"contact_create",
|
|
@@ -104,6 +104,7 @@ export const TOOL_GROUPS = {
|
|
|
104
104
|
"logs_read",
|
|
105
105
|
"network_settings",
|
|
106
106
|
"wifi_settings",
|
|
107
|
+
"skill_pack_install",
|
|
107
108
|
],
|
|
108
109
|
};
|
|
109
110
|
// Tools that are never granted by profiles — must be explicitly added to the
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fsp from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Type } from "@sinclair/typebox";
|
|
4
|
+
import { jsonResult, readStringParam } from "./common.js";
|
|
5
|
+
import { verifySkillPack, hasMarker, readMarker, writeMarker, } from "../../license/skill-pack.js";
|
|
6
|
+
import { getDeviceId } from "../../license/device-id.js";
|
|
7
|
+
import { callGatewayTool } from "./gateway.js";
|
|
8
|
+
const SkillPackInstallSchema = Type.Object({
|
|
9
|
+
filePath: Type.String({
|
|
10
|
+
description: "Absolute path to the .skillpack.json file received as an attachment.",
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
13
|
+
export function createSkillPackInstallTool(opts) {
|
|
14
|
+
return {
|
|
15
|
+
label: "Skill Pack Install",
|
|
16
|
+
name: "skill_pack_install",
|
|
17
|
+
description: "Install a signed skill pack (.skillpack.json) onto this device. " +
|
|
18
|
+
"Verifies the cryptographic signature and device binding before installing. " +
|
|
19
|
+
"Use this when a customer forwards a skill pack file to you.",
|
|
20
|
+
parameters: SkillPackInstallSchema,
|
|
21
|
+
execute: async (_toolCallId, args) => {
|
|
22
|
+
const params = args;
|
|
23
|
+
const filePath = readStringParam(params, "filePath", { required: true });
|
|
24
|
+
// Read and parse the pack file
|
|
25
|
+
let raw;
|
|
26
|
+
try {
|
|
27
|
+
const fileContent = await fsp.readFile(filePath, "utf-8");
|
|
28
|
+
raw = JSON.parse(fileContent);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error("Could not read or parse the skill pack file. " +
|
|
32
|
+
"Make sure the file path is correct and the file is valid JSON.");
|
|
33
|
+
}
|
|
34
|
+
// Verify signature and structure
|
|
35
|
+
const result = verifySkillPack(raw);
|
|
36
|
+
if (!result.valid) {
|
|
37
|
+
throw new Error(result.message);
|
|
38
|
+
}
|
|
39
|
+
const { payload } = result;
|
|
40
|
+
// Check device binding
|
|
41
|
+
const localDeviceId = getDeviceId();
|
|
42
|
+
if (payload.device.did !== "*" && payload.device.did !== localDeviceId) {
|
|
43
|
+
throw new Error("This skill pack is licensed for a different device. " +
|
|
44
|
+
"It cannot be installed on this device.");
|
|
45
|
+
}
|
|
46
|
+
// Check for existing marketplace skill
|
|
47
|
+
const skillDir = path.join(opts.workspaceDir, "skills", payload.pack.id);
|
|
48
|
+
if (hasMarker(skillDir)) {
|
|
49
|
+
const existing = await readMarker(skillDir);
|
|
50
|
+
if (existing && existing.version === payload.pack.version) {
|
|
51
|
+
return jsonResult({
|
|
52
|
+
ok: true,
|
|
53
|
+
alreadyInstalled: true,
|
|
54
|
+
packId: payload.pack.id,
|
|
55
|
+
version: payload.pack.version,
|
|
56
|
+
message: `${payload.pack.name} v${payload.pack.version} is already installed.`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Install via skills.create gateway RPC
|
|
61
|
+
const references = payload.content.references.map((r) => ({
|
|
62
|
+
name: r.name,
|
|
63
|
+
content: r.content,
|
|
64
|
+
}));
|
|
65
|
+
await callGatewayTool("skills.create", {}, {
|
|
66
|
+
name: payload.pack.id,
|
|
67
|
+
skillContent: payload.content.skill,
|
|
68
|
+
...(references.length > 0 ? { references } : {}),
|
|
69
|
+
});
|
|
70
|
+
// Write marker file
|
|
71
|
+
const marker = {
|
|
72
|
+
packId: payload.pack.id,
|
|
73
|
+
version: payload.pack.version,
|
|
74
|
+
author: payload.pack.author,
|
|
75
|
+
installedAt: new Date().toISOString(),
|
|
76
|
+
deviceId: payload.device.did,
|
|
77
|
+
customerId: payload.device.cid,
|
|
78
|
+
};
|
|
79
|
+
await writeMarker(skillDir, marker);
|
|
80
|
+
return jsonResult({
|
|
81
|
+
ok: true,
|
|
82
|
+
packId: payload.pack.id,
|
|
83
|
+
version: payload.pack.version,
|
|
84
|
+
name: payload.pack.name,
|
|
85
|
+
message: `Successfully installed ${payload.pack.name} v${payload.pack.version}. ` +
|
|
86
|
+
`This skill is now available for use.`,
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import fsp from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Type } from "@sinclair/typebox";
|
|
4
|
+
import { jsonResult, readStringParam } from "./common.js";
|
|
5
|
+
import { signSkillPack } from "../../license/skill-pack.js";
|
|
6
|
+
import { parseFrontmatter } from "../skills/frontmatter.js";
|
|
7
|
+
const SkillPackSignSchema = Type.Object({
|
|
8
|
+
packId: Type.String({
|
|
9
|
+
description: "Skill pack ID — must match a directory name in the skill-packs/ folder of this workspace.",
|
|
10
|
+
}),
|
|
11
|
+
deviceId: Type.String({
|
|
12
|
+
description: "Target device ID (tm_dev_...) from the customer's contact record.",
|
|
13
|
+
}),
|
|
14
|
+
customerId: Type.String({
|
|
15
|
+
description: "Customer identifier from the contact record.",
|
|
16
|
+
}),
|
|
17
|
+
version: Type.Optional(Type.String({ description: 'Skill pack version (e.g. "1.0.0"). Defaults to "1.0.0".' })),
|
|
18
|
+
});
|
|
19
|
+
export function createSkillPackSignTool(opts) {
|
|
20
|
+
return {
|
|
21
|
+
label: "Skill Pack Sign",
|
|
22
|
+
name: "skill_pack_sign",
|
|
23
|
+
description: "Sign a skill pack for a specific customer device. Reads the skill template from " +
|
|
24
|
+
"skill-packs/<packId>/, signs it with the customer's device ID embedded, and writes " +
|
|
25
|
+
"the signed .skillpack.json file. Send the resulting file to the customer.",
|
|
26
|
+
parameters: SkillPackSignSchema,
|
|
27
|
+
execute: async (_toolCallId, args) => {
|
|
28
|
+
const params = args;
|
|
29
|
+
const packId = readStringParam(params, "packId", { required: true });
|
|
30
|
+
const deviceId = readStringParam(params, "deviceId", { required: true });
|
|
31
|
+
const customerId = readStringParam(params, "customerId", { required: true });
|
|
32
|
+
const version = readStringParam(params, "version") ?? "1.0.0";
|
|
33
|
+
if (!deviceId.startsWith("tm_dev_")) {
|
|
34
|
+
throw new Error(`Invalid device ID format: "${deviceId}". Device IDs start with "tm_dev_".`);
|
|
35
|
+
}
|
|
36
|
+
// Read skill template
|
|
37
|
+
const packDir = path.join(opts.workspaceDir, "skill-packs", packId);
|
|
38
|
+
const skillPath = path.join(packDir, "SKILL.md");
|
|
39
|
+
let skillContent;
|
|
40
|
+
try {
|
|
41
|
+
skillContent = await fsp.readFile(skillPath, "utf-8");
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new Error(`Skill pack template not found: ${packId}. Expected SKILL.md at ${skillPath}`);
|
|
45
|
+
}
|
|
46
|
+
// Read references
|
|
47
|
+
const refsDir = path.join(packDir, "references");
|
|
48
|
+
const references = [];
|
|
49
|
+
try {
|
|
50
|
+
const refFiles = await fsp.readdir(refsDir);
|
|
51
|
+
for (const file of refFiles.sort()) {
|
|
52
|
+
const content = await fsp.readFile(path.join(refsDir, file), "utf-8");
|
|
53
|
+
references.push({ name: file, content });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// No references directory — that's fine
|
|
58
|
+
}
|
|
59
|
+
// Parse frontmatter for metadata
|
|
60
|
+
const frontmatter = parseFrontmatter(skillContent);
|
|
61
|
+
const name = frontmatter?.name ?? packId;
|
|
62
|
+
const description = frontmatter?.description ?? "";
|
|
63
|
+
// Build and sign payload
|
|
64
|
+
const payload = {
|
|
65
|
+
format: "skillpack-v1",
|
|
66
|
+
pack: { id: packId, version, name, description, author: "Rubytech LLC" },
|
|
67
|
+
device: { did: deviceId, cid: customerId },
|
|
68
|
+
content: { skill: skillContent, references },
|
|
69
|
+
signedAt: new Date().toISOString(),
|
|
70
|
+
};
|
|
71
|
+
const signed = signSkillPack(payload);
|
|
72
|
+
// Write signed file
|
|
73
|
+
const shortDid = deviceId.slice(0, 20);
|
|
74
|
+
const signedDir = path.join(opts.workspaceDir, "skill-packs", "signed");
|
|
75
|
+
await fsp.mkdir(signedDir, { recursive: true });
|
|
76
|
+
const outputPath = path.join(signedDir, `${packId}-${shortDid}.skillpack.json`);
|
|
77
|
+
await fsp.writeFile(outputPath, JSON.stringify(signed, null, 2), "utf-8");
|
|
78
|
+
return jsonResult({
|
|
79
|
+
ok: true,
|
|
80
|
+
filePath: outputPath,
|
|
81
|
+
packId,
|
|
82
|
+
version,
|
|
83
|
+
deviceId,
|
|
84
|
+
customerId,
|
|
85
|
+
message: `Signed skill pack "${name}" v${version} for device ${deviceId}. ` +
|
|
86
|
+
`File saved to ${outputPath}. Send this file to the customer with instructions ` +
|
|
87
|
+
`to forward it to their admin agent and ask it to install.`,
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -232,9 +232,9 @@ export async function resolveReplyDirectives(params) {
|
|
|
232
232
|
? "off"
|
|
233
233
|
: opts?.disableBlockStreaming === false
|
|
234
234
|
? "on"
|
|
235
|
-
: agentCfg?.blockStreamingDefault === "
|
|
236
|
-
? "
|
|
237
|
-
: "
|
|
235
|
+
: agentCfg?.blockStreamingDefault === "off"
|
|
236
|
+
? "off"
|
|
237
|
+
: "on";
|
|
238
238
|
const resolvedBlockStreamingBreak = agentCfg?.blockStreamingBreak === "message_end" ? "message_end" : "text_end";
|
|
239
239
|
const blockStreamingEnabled = resolvedBlockStreaming === "on" && opts?.disableBlockStreaming !== true;
|
|
240
240
|
const blockReplyChunking = blockStreamingEnabled
|
package/dist/build-info.json
CHANGED