@rubytech/taskmaster 1.42.0 → 1.42.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/context.js +2 -0
- package/dist/agents/pi-embedded-runner/compact.js +1 -0
- package/dist/agents/pi-embedded-runner/run/attempt.js +1 -0
- package/dist/agents/pi-embedded-runner/system-prompt.js +1 -0
- package/dist/agents/skills/frontmatter.js +85 -1
- package/dist/agents/skills/workspace.js +23 -2
- package/dist/agents/skills-status.js +2 -2
- package/dist/agents/system-prompt.js +43 -30
- package/dist/agents/tool-policy.js +68 -4
- package/dist/agents/tools/access-manage-tool.js +110 -0
- package/dist/agents/tools/account-manage-tool.js +78 -0
- package/dist/agents/tools/brand-settings-tool.js +53 -24
- package/dist/agents/tools/file-manage-tool.js +193 -0
- package/dist/agents/tools/license-manage-tool.js +50 -0
- package/dist/agents/tools/skill-manage-tool.js +16 -3
- package/dist/auto-reply/reply/commands-context-report.js +2 -1
- package/dist/browser/chrome.js +11 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/skills-cli.js +4 -9
- package/dist/commands/onboard-skills.js +1 -1
- package/dist/config/zod-schema.js +5 -1
- package/dist/control-ui/assets/index-CAu2PL0O.css +1 -0
- package/dist/control-ui/assets/{index-Cl91wvkO.js → index-c6Rca5oP.js} +691 -542
- package/dist/control-ui/assets/index-c6Rca5oP.js.map +1 -0
- package/dist/control-ui/index.html +3 -2
- package/dist/gateway/protocol/index.js +3 -2
- package/dist/gateway/protocol/schema/agents-models-skills.js +6 -1
- package/dist/gateway/protocol/schema/tools.js +8 -0
- package/dist/gateway/server-methods/sessions.js +3 -1
- package/dist/gateway/server-methods/skills.js +31 -2
- package/dist/gateway/server-methods/tools.js +7 -0
- package/dist/gateway/server-methods.js +3 -0
- package/dist/gateway/session-utils.js +5 -1
- package/dist/media-understanding/apply.js +18 -4
- package/dist/web/auto-reply/monitor/process-message.js +1 -0
- package/dist/web/inbound/monitor.js +4 -0
- package/package.json +1 -1
- package/skills/skill-builder/SKILL.md +18 -4
- package/skills/skill-builder/references/lean-pattern.md +13 -3
- package/dist/control-ui/assets/index-Cl91wvkO.js.map +0 -1
- package/dist/control-ui/assets/index-Dd2cHcuh.css +0 -1
|
@@ -2,45 +2,52 @@
|
|
|
2
2
|
* Agent tool for managing branding settings.
|
|
3
3
|
*
|
|
4
4
|
* Wraps `config.get`/`config.patch` for accent/background color and
|
|
5
|
-
* `brand.hasLogo`/`brand.removeLogo` for logo management.
|
|
6
|
-
* is excluded — it requires binary data better handled through the UI.
|
|
5
|
+
* `brand.hasLogo`/`brand.removeLogo`/`brand.uploadLogo` for logo management.
|
|
7
6
|
*/
|
|
8
7
|
import { Type } from "@sinclair/typebox";
|
|
9
8
|
import { stringEnum } from "../schema/typebox.js";
|
|
10
9
|
import { jsonResult, readStringParam } from "./common.js";
|
|
11
10
|
import { callGatewayTool } from "./gateway.js";
|
|
12
|
-
const BRAND_ACTIONS = ["get", "set_colors", "remove_logo"];
|
|
11
|
+
const BRAND_ACTIONS = ["get", "set_colors", "set_logo", "remove_logo"];
|
|
13
12
|
const BrandSettingsSchema = Type.Object({
|
|
14
13
|
action: stringEnum(BRAND_ACTIONS, {
|
|
15
14
|
description: '"get" returns current branding (accent color, background color, logo status). ' +
|
|
16
15
|
'"set_colors" updates accent and/or background colors. ' +
|
|
16
|
+
'"set_logo" uploads a workspace logo from base64 data. ' +
|
|
17
17
|
'"remove_logo" removes the workspace logo.',
|
|
18
18
|
}),
|
|
19
|
-
workspace: Type.
|
|
20
|
-
description: "Workspace name. Required for
|
|
21
|
-
})
|
|
19
|
+
workspace: Type.String({
|
|
20
|
+
description: "Workspace name (e.g. \"taskmaster\"). Required for all actions.",
|
|
21
|
+
}),
|
|
22
22
|
accentColor: Type.Optional(Type.String({
|
|
23
23
|
description: 'Hex color for the accent/primary color (e.g. "#3B82F6"). Used with set_colors.',
|
|
24
24
|
})),
|
|
25
25
|
backgroundColor: Type.Optional(Type.String({
|
|
26
26
|
description: 'Hex color for the background (e.g. "#1A1A2E"). Used with set_colors.',
|
|
27
27
|
})),
|
|
28
|
+
imageData: Type.Optional(Type.String({
|
|
29
|
+
description: "Base64-encoded image data for the logo. Required for set_logo.",
|
|
30
|
+
})),
|
|
31
|
+
mimeType: Type.Optional(Type.String({
|
|
32
|
+
description: 'MIME type of the logo image (e.g. "image/png", "image/svg+xml"). Required for set_logo.',
|
|
33
|
+
})),
|
|
28
34
|
});
|
|
29
35
|
export function createBrandSettingsTool() {
|
|
30
36
|
return {
|
|
31
37
|
label: "Brand Settings",
|
|
32
38
|
name: "brand_settings",
|
|
33
|
-
description: "Read and update branding settings. " +
|
|
39
|
+
description: "Read and update workspace branding settings. Workspace name is required for all actions. " +
|
|
34
40
|
'"get" returns current accent color, background color, and logo status. ' +
|
|
35
41
|
'"set_colors" updates accent and/or background colors (hex values). ' +
|
|
36
|
-
'"
|
|
37
|
-
"
|
|
42
|
+
'"set_logo" uploads a logo from base64-encoded image data (imageData + mimeType required). ' +
|
|
43
|
+
'"remove_logo" removes the workspace logo.',
|
|
38
44
|
parameters: BrandSettingsSchema,
|
|
39
45
|
execute: async (_toolCallId, args) => {
|
|
40
46
|
const params = args;
|
|
41
47
|
const action = readStringParam(params, "action", { required: true });
|
|
42
48
|
const gatewayOpts = {};
|
|
43
49
|
if (action === "get") {
|
|
50
|
+
const workspace = readStringParam(params, "workspace", { required: true, label: "workspace" });
|
|
44
51
|
const config = await callGatewayTool("config.get", gatewayOpts, {});
|
|
45
52
|
let brand = {};
|
|
46
53
|
if (config &&
|
|
@@ -48,23 +55,20 @@ export function createBrandSettingsTool() {
|
|
|
48
55
|
typeof config.raw === "string") {
|
|
49
56
|
try {
|
|
50
57
|
const parsed = JSON.parse(config.raw);
|
|
51
|
-
brand = parsed?.brand ?? {};
|
|
58
|
+
brand = parsed?.workspaces?.[workspace]?.brand ?? {};
|
|
52
59
|
}
|
|
53
60
|
catch {
|
|
54
61
|
// fallback to empty
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
// Also check logo status
|
|
58
|
-
const workspace = readStringParam(params, "workspace");
|
|
59
65
|
let hasLogo;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// non-critical
|
|
67
|
-
}
|
|
66
|
+
try {
|
|
67
|
+
const logoResult = await callGatewayTool("brand.hasLogo", gatewayOpts, { workspace });
|
|
68
|
+
hasLogo = logoResult?.hasLogo;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// non-critical
|
|
68
72
|
}
|
|
69
73
|
return jsonResult({
|
|
70
74
|
accentColor: brand.accentColor ?? null,
|
|
@@ -73,6 +77,7 @@ export function createBrandSettingsTool() {
|
|
|
73
77
|
});
|
|
74
78
|
}
|
|
75
79
|
if (action === "set_colors") {
|
|
80
|
+
const workspace = readStringParam(params, "workspace", { required: true, label: "workspace" });
|
|
76
81
|
const accentColor = readStringParam(params, "accentColor");
|
|
77
82
|
const backgroundColor = readStringParam(params, "backgroundColor");
|
|
78
83
|
if (!accentColor && !backgroundColor) {
|
|
@@ -85,18 +90,42 @@ export function createBrandSettingsTool() {
|
|
|
85
90
|
brandPatch.backgroundColor = backgroundColor;
|
|
86
91
|
const snapshot = await callGatewayTool("config.get", gatewayOpts, {});
|
|
87
92
|
const baseHash = typeof snapshot?.hash === "string" ? snapshot.hash : undefined;
|
|
93
|
+
// Preserve existing workspace config, only updating brand colors
|
|
94
|
+
let existingWorkspace = {};
|
|
95
|
+
if (typeof snapshot?.raw === "string") {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(snapshot.raw);
|
|
98
|
+
existingWorkspace = parsed?.workspaces?.[workspace] ?? {};
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// fallback to empty
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const existingBrand = existingWorkspace.brand ?? {};
|
|
88
105
|
const result = await callGatewayTool("config.patch", gatewayOpts, {
|
|
89
|
-
raw: JSON.stringify({
|
|
106
|
+
raw: JSON.stringify({
|
|
107
|
+
workspaces: {
|
|
108
|
+
[workspace]: { ...existingWorkspace, brand: { ...existingBrand, ...brandPatch } },
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
90
111
|
baseHash,
|
|
91
112
|
note: "agent: update brand colors",
|
|
92
113
|
});
|
|
93
114
|
return jsonResult({ ok: true, ...brandPatch, result });
|
|
94
115
|
}
|
|
95
|
-
if (action === "
|
|
96
|
-
const workspace = readStringParam(params, "workspace", {
|
|
97
|
-
|
|
98
|
-
|
|
116
|
+
if (action === "set_logo") {
|
|
117
|
+
const workspace = readStringParam(params, "workspace", { required: true, label: "workspace" });
|
|
118
|
+
const imageData = readStringParam(params, "imageData", { required: true, label: "imageData" });
|
|
119
|
+
const mimeType = readStringParam(params, "mimeType", { required: true, label: "mimeType" });
|
|
120
|
+
const result = await callGatewayTool("brand.uploadLogo", gatewayOpts, {
|
|
121
|
+
workspace,
|
|
122
|
+
data: imageData,
|
|
123
|
+
mimeType,
|
|
99
124
|
});
|
|
125
|
+
return jsonResult(result);
|
|
126
|
+
}
|
|
127
|
+
if (action === "remove_logo") {
|
|
128
|
+
const workspace = readStringParam(params, "workspace", { required: true, label: "workspace" });
|
|
100
129
|
const result = await callGatewayTool("brand.removeLogo", gatewayOpts, { workspace });
|
|
101
130
|
return jsonResult(result);
|
|
102
131
|
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { realpath } from "node:fs/promises";
|
|
4
|
+
import { Type } from "@sinclair/typebox";
|
|
5
|
+
import { resolveAgentWorkspaceRoot, resolveSessionAgentId } from "../agent-scope.js";
|
|
6
|
+
import { stringEnum } from "../schema/typebox.js";
|
|
7
|
+
import { jsonResult, readStringParam } from "./common.js";
|
|
8
|
+
const FileManageSchema = Type.Object({
|
|
9
|
+
action: stringEnum(["mkdir", "move"], {
|
|
10
|
+
description: "The operation to perform: 'mkdir' to create a directory, 'move' to move/rename a file or directory.",
|
|
11
|
+
}),
|
|
12
|
+
path: Type.String({
|
|
13
|
+
description: "Target path relative to the workspace root " +
|
|
14
|
+
"(e.g. 'uploads/invoices', 'memory/users/scratch.md').",
|
|
15
|
+
}),
|
|
16
|
+
destination: Type.Optional(Type.String({
|
|
17
|
+
description: "Destination path relative to the workspace root (required for 'move').",
|
|
18
|
+
})),
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Protected top-level entries that cannot be created over or moved.
|
|
22
|
+
* These are structural directories/files whose modification would break the workspace.
|
|
23
|
+
*/
|
|
24
|
+
const PROTECTED_TOP_LEVEL = new Set(["agents", "IDENTITY.md", "SOUL.md", "AGENTS.md"]);
|
|
25
|
+
/**
|
|
26
|
+
* Validate that `target` is strictly inside `root` (not equal to root).
|
|
27
|
+
* Both paths must be real (symlinks resolved) before calling.
|
|
28
|
+
*/
|
|
29
|
+
function isInsideRoot(target, root) {
|
|
30
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
31
|
+
return target.startsWith(prefix);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check whether a resolved path's top-level segment is a protected entry.
|
|
35
|
+
*/
|
|
36
|
+
function isProtectedPath(resolved, root) {
|
|
37
|
+
const relative = path.relative(root, resolved);
|
|
38
|
+
const topSegment = relative.split(path.sep)[0];
|
|
39
|
+
if (topSegment && PROTECTED_TOP_LEVEL.has(topSegment)) {
|
|
40
|
+
return topSegment;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
export function createFileManageTool(options) {
|
|
45
|
+
return {
|
|
46
|
+
label: "File Management",
|
|
47
|
+
name: "file_manage",
|
|
48
|
+
description: "Create directories or move/rename files and folders in the workspace. " +
|
|
49
|
+
"All paths are relative to the workspace root. " +
|
|
50
|
+
"Cannot modify protected structural paths (agents/, IDENTITY.md, SOUL.md, etc.).",
|
|
51
|
+
parameters: FileManageSchema,
|
|
52
|
+
execute: async (_toolCallId, args) => {
|
|
53
|
+
const params = args;
|
|
54
|
+
const action = readStringParam(params, "action", { required: true });
|
|
55
|
+
const rawPath = readStringParam(params, "path", { required: true });
|
|
56
|
+
// ── Resolve workspace root ──────────────────────────────────
|
|
57
|
+
const cfg = options?.config;
|
|
58
|
+
if (!cfg) {
|
|
59
|
+
return jsonResult({ error: "No config available — cannot resolve workspace." });
|
|
60
|
+
}
|
|
61
|
+
const agentId = resolveSessionAgentId({
|
|
62
|
+
sessionKey: options?.agentSessionKey,
|
|
63
|
+
config: cfg,
|
|
64
|
+
});
|
|
65
|
+
const workspaceRoot = resolveAgentWorkspaceRoot(cfg, agentId);
|
|
66
|
+
// ── Validate path ───────────────────────────────────────────
|
|
67
|
+
if (rawPath.includes("\0")) {
|
|
68
|
+
return jsonResult({ error: "Invalid path." });
|
|
69
|
+
}
|
|
70
|
+
const resolved = path.resolve(workspaceRoot, rawPath);
|
|
71
|
+
// ── Containment check (pre-realpath) ────────────────────────
|
|
72
|
+
if (!isInsideRoot(resolved, workspaceRoot)) {
|
|
73
|
+
return jsonResult({ error: "Path is outside the workspace." });
|
|
74
|
+
}
|
|
75
|
+
// ── Resolve real workspace root for post-realpath checks ───
|
|
76
|
+
let realRoot;
|
|
77
|
+
try {
|
|
78
|
+
realRoot = await realpath(workspaceRoot);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return jsonResult({ error: "Workspace root is not accessible." });
|
|
82
|
+
}
|
|
83
|
+
// ── mkdir ─────────────────────────────────────────────────
|
|
84
|
+
if (action === "mkdir") {
|
|
85
|
+
// Protected path check
|
|
86
|
+
const protectedSegment = isProtectedPath(resolved, workspaceRoot);
|
|
87
|
+
if (protectedSegment) {
|
|
88
|
+
return jsonResult({
|
|
89
|
+
error: `Cannot create directory over protected path: ${protectedSegment} is a structural workspace entry.`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
await fs.mkdir(resolved, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
97
|
+
return jsonResult({ error: `Failed to create directory: ${message}` });
|
|
98
|
+
}
|
|
99
|
+
// Post-creation containment check (resolves any symlinks created during mkdir)
|
|
100
|
+
let realTarget;
|
|
101
|
+
try {
|
|
102
|
+
realTarget = await realpath(resolved);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
realTarget = resolved;
|
|
106
|
+
}
|
|
107
|
+
if (!isInsideRoot(realTarget, realRoot)) {
|
|
108
|
+
// Roll back — the created directory escapes the workspace
|
|
109
|
+
try {
|
|
110
|
+
await fs.rm(resolved, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// best-effort cleanup
|
|
114
|
+
}
|
|
115
|
+
return jsonResult({ error: "Path resolves outside the workspace." });
|
|
116
|
+
}
|
|
117
|
+
return jsonResult({
|
|
118
|
+
ok: true,
|
|
119
|
+
action: "mkdir",
|
|
120
|
+
path: rawPath,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// ── move ──────────────────────────────────────────────────
|
|
124
|
+
if (action === "move") {
|
|
125
|
+
const rawDestination = readStringParam(params, "destination");
|
|
126
|
+
if (!rawDestination) {
|
|
127
|
+
return jsonResult({ error: "The 'destination' parameter is required for the 'move' action." });
|
|
128
|
+
}
|
|
129
|
+
if (rawDestination.includes("\0")) {
|
|
130
|
+
return jsonResult({ error: "Invalid destination path." });
|
|
131
|
+
}
|
|
132
|
+
const resolvedDest = path.resolve(workspaceRoot, rawDestination);
|
|
133
|
+
// Containment check for destination (pre-realpath)
|
|
134
|
+
if (!isInsideRoot(resolvedDest, workspaceRoot)) {
|
|
135
|
+
return jsonResult({ error: "Destination is outside the workspace." });
|
|
136
|
+
}
|
|
137
|
+
// Check source exists
|
|
138
|
+
try {
|
|
139
|
+
await fs.lstat(resolved);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return jsonResult({ error: `Not found: ${rawPath}` });
|
|
143
|
+
}
|
|
144
|
+
// Post-realpath containment check for source
|
|
145
|
+
let realSource;
|
|
146
|
+
try {
|
|
147
|
+
realSource = await realpath(resolved);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
realSource = resolved;
|
|
151
|
+
}
|
|
152
|
+
if (!isInsideRoot(realSource, realRoot)) {
|
|
153
|
+
return jsonResult({ error: "Source path resolves outside the workspace." });
|
|
154
|
+
}
|
|
155
|
+
// Protected path check — cannot move protected entries
|
|
156
|
+
const protectedSource = isProtectedPath(realSource, realRoot);
|
|
157
|
+
if (protectedSource) {
|
|
158
|
+
return jsonResult({
|
|
159
|
+
error: `Cannot move protected path: ${protectedSource} is a structural workspace entry.`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const protectedDest = isProtectedPath(resolvedDest, workspaceRoot);
|
|
163
|
+
if (protectedDest) {
|
|
164
|
+
return jsonResult({
|
|
165
|
+
error: `Cannot move to protected path: ${protectedDest} is a structural workspace entry.`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Ensure destination parent directory exists
|
|
169
|
+
const destParent = path.dirname(resolvedDest);
|
|
170
|
+
try {
|
|
171
|
+
await fs.mkdir(destParent, { recursive: true });
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// parent may already exist — ignore
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
await fs.rename(resolved, resolvedDest);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
181
|
+
return jsonResult({ error: `Failed to move: ${message}` });
|
|
182
|
+
}
|
|
183
|
+
return jsonResult({
|
|
184
|
+
ok: true,
|
|
185
|
+
action: "move",
|
|
186
|
+
from: rawPath,
|
|
187
|
+
to: rawDestination,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return jsonResult({ error: `Unknown action: ${action}` });
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool for managing license activation and removal.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the gateway `license.status`, `license.activate`, and `license.remove`
|
|
5
|
+
* RPC methods so the agent can check license state, activate a new license
|
|
6
|
+
* token, or clear the stored license — without asking the user to open the
|
|
7
|
+
* Setup UI.
|
|
8
|
+
*/
|
|
9
|
+
import { Type } from "@sinclair/typebox";
|
|
10
|
+
import { stringEnum } from "../schema/typebox.js";
|
|
11
|
+
import { jsonResult, readStringParam } from "./common.js";
|
|
12
|
+
import { callGatewayTool } from "./gateway.js";
|
|
13
|
+
const LICENSE_ACTIONS = ["status", "activate", "remove"];
|
|
14
|
+
const LicenseManageSchema = Type.Object({
|
|
15
|
+
action: stringEnum(LICENSE_ACTIONS, {
|
|
16
|
+
description: 'Action to perform: "status" shows current license info and device ID, "activate" validates and stores a license token, "remove" clears the stored license (e.g. for transferring to another device).',
|
|
17
|
+
}),
|
|
18
|
+
key: Type.Optional(Type.String({
|
|
19
|
+
description: "The license token string (required for activate).",
|
|
20
|
+
})),
|
|
21
|
+
});
|
|
22
|
+
export function createLicenseManageTool() {
|
|
23
|
+
return {
|
|
24
|
+
label: "License Management",
|
|
25
|
+
name: "license_manage",
|
|
26
|
+
description: "Manage the device license. Use action 'status' to show current license info and device ID, 'activate' to validate and store a license token (key required), or 'remove' to clear the stored license (e.g. for transferring to another device).",
|
|
27
|
+
parameters: LicenseManageSchema,
|
|
28
|
+
execute: async (_toolCallId, args) => {
|
|
29
|
+
const params = args;
|
|
30
|
+
const action = readStringParam(params, "action", { required: true });
|
|
31
|
+
switch (action) {
|
|
32
|
+
case "status": {
|
|
33
|
+
const result = await callGatewayTool("license.status", {});
|
|
34
|
+
return jsonResult(result);
|
|
35
|
+
}
|
|
36
|
+
case "activate": {
|
|
37
|
+
const key = readStringParam(params, "key", { required: true });
|
|
38
|
+
const result = await callGatewayTool("license.activate", {}, { key });
|
|
39
|
+
return jsonResult(result);
|
|
40
|
+
}
|
|
41
|
+
case "remove": {
|
|
42
|
+
const result = await callGatewayTool("license.remove", {});
|
|
43
|
+
return jsonResult(result);
|
|
44
|
+
}
|
|
45
|
+
default:
|
|
46
|
+
throw new Error(`Unknown action: ${action}. Use "status", "activate", or "remove".`);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -9,14 +9,16 @@ import { Type } from "@sinclair/typebox";
|
|
|
9
9
|
import { stringEnum } from "../schema/typebox.js";
|
|
10
10
|
import { jsonResult, readStringParam } from "./common.js";
|
|
11
11
|
import { callGatewayTool } from "./gateway.js";
|
|
12
|
-
const SKILL_MANAGE_ACTIONS = ["list", "create", "update", "delete", "install"];
|
|
12
|
+
const SKILL_MANAGE_ACTIONS = ["list", "create", "update", "delete", "install", "list_drafts", "delete_draft"];
|
|
13
13
|
const SkillManageSchema = Type.Object({
|
|
14
14
|
action: stringEnum(SKILL_MANAGE_ACTIONS, {
|
|
15
15
|
description: '"list" returns all skills with their status. ' +
|
|
16
16
|
'"create" creates a new user skill (name + skillContent required). ' +
|
|
17
17
|
'"update" modifies a skill setting (skillKey required, plus enabled/apiKey/env). ' +
|
|
18
18
|
'"delete" removes a user skill (name required; preloaded skills cannot be deleted). ' +
|
|
19
|
-
'"install" installs a skill from a remote source (name + installId required).'
|
|
19
|
+
'"install" installs a skill from a remote source (name + installId required). ' +
|
|
20
|
+
'"list_drafts" returns all saved skill drafts. ' +
|
|
21
|
+
'"delete_draft" removes a saved draft (name required).',
|
|
20
22
|
}),
|
|
21
23
|
name: Type.Optional(Type.String({
|
|
22
24
|
description: "Skill name/key (required for create, delete, install).",
|
|
@@ -50,7 +52,9 @@ export function createSkillManageTool() {
|
|
|
50
52
|
'"create" makes a new skill (provide name + skillContent with YAML frontmatter). ' +
|
|
51
53
|
'"update" modifies settings (enabled, apiKey). ' +
|
|
52
54
|
'"delete" removes a user skill (preloaded skills are protected). ' +
|
|
53
|
-
'"install" adds a skill from a remote source.'
|
|
55
|
+
'"install" adds a skill from a remote source. ' +
|
|
56
|
+
'"list_drafts" shows all saved skill drafts. ' +
|
|
57
|
+
'"delete_draft" removes a saved draft (name required).',
|
|
54
58
|
parameters: SkillManageSchema,
|
|
55
59
|
execute: async (_toolCallId, args) => {
|
|
56
60
|
const params = args;
|
|
@@ -99,6 +103,15 @@ export function createSkillManageTool() {
|
|
|
99
103
|
const result = await callGatewayTool("skills.install", {}, { name, installId });
|
|
100
104
|
return jsonResult(result);
|
|
101
105
|
}
|
|
106
|
+
if (action === "list_drafts") {
|
|
107
|
+
const result = await callGatewayTool("skills.drafts", {}, {});
|
|
108
|
+
return jsonResult(result);
|
|
109
|
+
}
|
|
110
|
+
if (action === "delete_draft") {
|
|
111
|
+
const name = readStringParam(params, "name", { required: true, label: "name" });
|
|
112
|
+
const result = await callGatewayTool("skills.deleteDraft", {}, { name });
|
|
113
|
+
return jsonResult(result);
|
|
114
|
+
}
|
|
102
115
|
throw new Error(`Unknown action: ${action}`);
|
|
103
116
|
},
|
|
104
117
|
};
|
|
@@ -56,7 +56,7 @@ async function resolveContextReport(params) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
catch {
|
|
59
|
-
return { prompt: "", skills: [], resolvedSkills: [] };
|
|
59
|
+
return { prompt: "", skills: [], resolvedSkills: [], skillSpawnEntries: [] };
|
|
60
60
|
}
|
|
61
61
|
})();
|
|
62
62
|
const skillsPrompt = skillsSnapshot.prompt ?? "";
|
|
@@ -135,6 +135,7 @@ async function resolveContextReport(params) {
|
|
|
135
135
|
userTimeFormat,
|
|
136
136
|
contextFiles: injectedFiles,
|
|
137
137
|
skillsPrompt,
|
|
138
|
+
skillSpawnEntries: skillsSnapshot.skillSpawnEntries,
|
|
138
139
|
heartbeatPrompt: undefined,
|
|
139
140
|
ttsHint,
|
|
140
141
|
runtimeInfo,
|
package/dist/browser/chrome.js
CHANGED
|
@@ -219,6 +219,17 @@ export async function launchTaskmasterChrome(resolved, profile) {
|
|
|
219
219
|
catch (err) {
|
|
220
220
|
log.warn(`Taskmaster browser clean-exit prefs failed: ${String(err)}`);
|
|
221
221
|
}
|
|
222
|
+
// Remove stale Chromium singleton lock files before launch. These are left
|
|
223
|
+
// behind after a crash or when the hostname changes (SingletonSocket encodes
|
|
224
|
+
// the hostname). If present, Chromium refuses to start.
|
|
225
|
+
for (const name of ["SingletonLock", "SingletonCookie", "SingletonSocket"]) {
|
|
226
|
+
try {
|
|
227
|
+
fs.rmSync(path.join(userDataDir, name), { force: true });
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// ignore — file may not exist
|
|
231
|
+
}
|
|
232
|
+
}
|
|
222
233
|
const proc = spawnOnce();
|
|
223
234
|
// Wait for CDP to come up.
|
|
224
235
|
const readyDeadline = Date.now() + 15_000;
|
package/dist/build-info.json
CHANGED
package/dist/cli/skills-cli.js
CHANGED
|
@@ -22,8 +22,7 @@ function formatSkillStatus(skill) {
|
|
|
22
22
|
return theme.error("✗ missing");
|
|
23
23
|
}
|
|
24
24
|
function formatSkillName(skill) {
|
|
25
|
-
|
|
26
|
-
return `${emoji} ${theme.command(skill.name)}`;
|
|
25
|
+
return `🧩 ${theme.command(skill.name)}`;
|
|
27
26
|
}
|
|
28
27
|
function formatSkillMissingSummary(skill) {
|
|
29
28
|
const missing = [];
|
|
@@ -56,7 +55,6 @@ export function formatSkillsList(report, opts) {
|
|
|
56
55
|
skills: skills.map((s) => ({
|
|
57
56
|
name: s.name,
|
|
58
57
|
description: s.description,
|
|
59
|
-
emoji: s.emoji,
|
|
60
58
|
eligible: s.eligible,
|
|
61
59
|
disabled: s.disabled,
|
|
62
60
|
blockedByAllowlist: s.blockedByAllowlist,
|
|
@@ -119,7 +117,6 @@ export function formatSkillInfo(report, skillName, opts) {
|
|
|
119
117
|
return JSON.stringify(skill, null, 2);
|
|
120
118
|
}
|
|
121
119
|
const lines = [];
|
|
122
|
-
const emoji = skill.emoji ?? "📦";
|
|
123
120
|
const status = skill.eligible
|
|
124
121
|
? theme.success("✓ Ready")
|
|
125
122
|
: skill.disabled
|
|
@@ -127,7 +124,7 @@ export function formatSkillInfo(report, skillName, opts) {
|
|
|
127
124
|
: skill.blockedByAllowlist
|
|
128
125
|
? theme.warn("🚫 Blocked by allowlist")
|
|
129
126
|
: theme.error("✗ Missing requirements");
|
|
130
|
-
lines.push(
|
|
127
|
+
lines.push(`🧩 ${theme.heading(skill.name)} ${status}`);
|
|
131
128
|
lines.push("");
|
|
132
129
|
lines.push(skill.description);
|
|
133
130
|
lines.push("");
|
|
@@ -236,15 +233,13 @@ export function formatSkillsCheck(report, opts) {
|
|
|
236
233
|
lines.push("");
|
|
237
234
|
lines.push(theme.heading("Ready to use:"));
|
|
238
235
|
for (const skill of eligible) {
|
|
239
|
-
|
|
240
|
-
lines.push(` ${emoji} ${skill.name}`);
|
|
236
|
+
lines.push(` 🧩 ${skill.name}`);
|
|
241
237
|
}
|
|
242
238
|
}
|
|
243
239
|
if (missingReqs.length > 0) {
|
|
244
240
|
lines.push("");
|
|
245
241
|
lines.push(theme.heading("Missing requirements:"));
|
|
246
242
|
for (const skill of missingReqs) {
|
|
247
|
-
const emoji = skill.emoji ?? "📦";
|
|
248
243
|
const missing = [];
|
|
249
244
|
if (skill.missing.bins.length > 0) {
|
|
250
245
|
missing.push(`bins: ${skill.missing.bins.join(", ")}`);
|
|
@@ -261,7 +256,7 @@ export function formatSkillsCheck(report, opts) {
|
|
|
261
256
|
if (skill.missing.os.length > 0) {
|
|
262
257
|
missing.push(`os: ${skill.missing.os.join(", ")}`);
|
|
263
258
|
}
|
|
264
|
-
lines.push(`
|
|
259
|
+
lines.push(` 🧩 ${skill.name} ${theme.muted(`(${missing.join("; ")})`)}`);
|
|
265
260
|
}
|
|
266
261
|
}
|
|
267
262
|
return appendTaskmasterHubHint(lines.join("\n"), opts.json);
|
|
@@ -91,7 +91,7 @@ export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
|
|
|
91
91
|
},
|
|
92
92
|
...installable.map((skill) => ({
|
|
93
93
|
value: skill.name,
|
|
94
|
-
label:
|
|
94
|
+
label: `🧩 ${skill.name}`,
|
|
95
95
|
hint: formatSkillHint(skill),
|
|
96
96
|
})),
|
|
97
97
|
],
|
|
@@ -514,8 +514,12 @@ export const TaskmasterSchema = z
|
|
|
514
514
|
apiKey: z.string().optional(),
|
|
515
515
|
env: z.record(z.string(), z.string()).optional(),
|
|
516
516
|
config: z.record(z.string(), z.unknown()).optional(),
|
|
517
|
+
agents: z
|
|
518
|
+
.array(z.union([z.literal("admin"), z.literal("public"), z.literal("subagent")]))
|
|
519
|
+
.optional(),
|
|
517
520
|
})
|
|
518
|
-
.strict()
|
|
521
|
+
.strict()
|
|
522
|
+
.refine((val) => !val.agents?.includes("subagent") || val.agents.length === 1, { message: '"subagent" must be the sole value in agents' }))
|
|
519
523
|
.optional(),
|
|
520
524
|
})
|
|
521
525
|
.strict()
|