@rubytech/taskmaster 1.42.0 → 1.43.0
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/opening-hours-tool.js +6 -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/business/opening-hours.js +14 -0
- package/dist/cli/skills-cli.js +4 -9
- package/dist/commands/onboard-skills.js +1 -1
- package/dist/config/zod-schema.js +6 -1
- package/dist/control-ui/assets/index-CAu2PL0O.css +1 -0
- package/dist/control-ui/assets/{index-Cl91wvkO.js → index-QAV6uia0.js} +715 -571
- package/dist/control-ui/assets/index-QAV6uia0.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
package/dist/agents/context.js
CHANGED
|
@@ -26,6 +26,8 @@ const loadPromise = (async () => {
|
|
|
26
26
|
// If pi-ai isn't available, leave cache empty; lookup will fall back.
|
|
27
27
|
}
|
|
28
28
|
})();
|
|
29
|
+
/** Resolves when the model cache has been populated (or failed to load). */
|
|
30
|
+
export const modelCacheReady = loadPromise;
|
|
29
31
|
export function lookupContextTokens(modelId) {
|
|
30
32
|
if (!modelId)
|
|
31
33
|
return undefined;
|
|
@@ -268,6 +268,7 @@ export async function compactEmbeddedPiSessionDirect(params) {
|
|
|
268
268
|
? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt)
|
|
269
269
|
: undefined,
|
|
270
270
|
skillsPrompt,
|
|
271
|
+
skillSpawnEntries: params.skillsSnapshot?.skillSpawnEntries,
|
|
271
272
|
docsPath: docsPath ?? undefined,
|
|
272
273
|
ttsHint,
|
|
273
274
|
promptMode,
|
|
@@ -296,6 +296,7 @@ export async function runEmbeddedAttempt(params) {
|
|
|
296
296
|
? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt)
|
|
297
297
|
: undefined,
|
|
298
298
|
skillsPrompt,
|
|
299
|
+
skillSpawnEntries: params.skillsSnapshot?.skillSpawnEntries,
|
|
299
300
|
docsPath: docsPath ?? undefined,
|
|
300
301
|
ttsHint,
|
|
301
302
|
workspaceNotes,
|
|
@@ -10,6 +10,7 @@ export function buildEmbeddedSystemPrompt(params) {
|
|
|
10
10
|
reasoningTagHint: params.reasoningTagHint,
|
|
11
11
|
heartbeatPrompt: params.heartbeatPrompt,
|
|
12
12
|
skillsPrompt: params.skillsPrompt,
|
|
13
|
+
skillSpawnEntries: params.skillSpawnEntries,
|
|
13
14
|
docsPath: params.docsPath,
|
|
14
15
|
ttsHint: params.ttsHint,
|
|
15
16
|
workspaceNotes: params.workspaceNotes,
|
|
@@ -91,7 +91,12 @@ export function resolveTaskmasterMetadata(frontmatter) {
|
|
|
91
91
|
return {
|
|
92
92
|
always: typeof taskmasterObj.always === "boolean" ? taskmasterObj.always : undefined,
|
|
93
93
|
embed: typeof taskmasterObj.embed === "boolean" ? taskmasterObj.embed : undefined,
|
|
94
|
-
|
|
94
|
+
icon: typeof taskmasterObj.icon === "string" ? taskmasterObj.icon : undefined,
|
|
95
|
+
tools: Array.isArray(taskmasterObj.tools)
|
|
96
|
+
? taskmasterObj.tools
|
|
97
|
+
.map((t) => String(t).trim())
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
: undefined,
|
|
95
100
|
homepage: typeof taskmasterObj.homepage === "string" ? taskmasterObj.homepage : undefined,
|
|
96
101
|
skillKey: typeof taskmasterObj.skillKey === "string" ? taskmasterObj.skillKey : undefined,
|
|
97
102
|
primaryEnv: typeof taskmasterObj.primaryEnv === "string" ? taskmasterObj.primaryEnv : undefined,
|
|
@@ -128,6 +133,85 @@ export function extractEmbedFlag(content) {
|
|
|
128
133
|
const meta = resolveTaskmasterMetadata(frontmatter);
|
|
129
134
|
return meta?.embed === true;
|
|
130
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Patch icon and/or tools in a SKILL.md content string's frontmatter metadata.
|
|
138
|
+
* Preserves other metadata fields. Uses the same pattern as applyEmbedFlag.
|
|
139
|
+
* - tools: [] removes the field; tools: undefined (key explicitly present) removes the field.
|
|
140
|
+
* - icon: undefined or "" (key explicitly present) removes the field.
|
|
141
|
+
* - Omitting the icon key entirely leaves any existing icon unchanged.
|
|
142
|
+
* - Omitting the tools key entirely leaves any existing tools unchanged.
|
|
143
|
+
*/
|
|
144
|
+
export function applySkillMetadataFields(content, fields) {
|
|
145
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
146
|
+
const frontmatter = parseFrontmatter(normalized);
|
|
147
|
+
const rawMeta = frontmatter.metadata;
|
|
148
|
+
let metaObj = {};
|
|
149
|
+
if (rawMeta) {
|
|
150
|
+
try {
|
|
151
|
+
metaObj = JSON5.parse(rawMeta);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Start fresh if unparseable.
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const taskmaster = metaObj.taskmaster && typeof metaObj.taskmaster === "object"
|
|
158
|
+
? { ...metaObj.taskmaster }
|
|
159
|
+
: {};
|
|
160
|
+
// Apply icon patch.
|
|
161
|
+
if ("icon" in fields) {
|
|
162
|
+
if (fields.icon) {
|
|
163
|
+
taskmaster.icon = fields.icon;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
delete taskmaster.icon;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Apply tools patch.
|
|
170
|
+
if ("tools" in fields) {
|
|
171
|
+
if (fields.tools && fields.tools.length > 0) {
|
|
172
|
+
taskmaster.tools = fields.tools;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
delete taskmaster.tools;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const hasTaskmasterKeys = Object.keys(taskmaster).length > 0;
|
|
179
|
+
if (hasTaskmasterKeys) {
|
|
180
|
+
metaObj.taskmaster = taskmaster;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
delete metaObj.taskmaster;
|
|
184
|
+
}
|
|
185
|
+
const hasMetaKeys = Object.keys(metaObj).length > 0;
|
|
186
|
+
const newMetaValue = hasMetaKeys ? JSON.stringify(metaObj) : "";
|
|
187
|
+
// Splice metadata line into frontmatter (same logic as applyEmbedFlag).
|
|
188
|
+
if (!normalized.startsWith("---")) {
|
|
189
|
+
if (!newMetaValue)
|
|
190
|
+
return content;
|
|
191
|
+
return `---\nmetadata: ${newMetaValue}\n---\n${normalized}`;
|
|
192
|
+
}
|
|
193
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
194
|
+
if (endIndex === -1)
|
|
195
|
+
return content;
|
|
196
|
+
const block = normalized.slice(4, endIndex);
|
|
197
|
+
const after = normalized.slice(endIndex + 4);
|
|
198
|
+
const lines = block.split("\n");
|
|
199
|
+
let replaced = false;
|
|
200
|
+
const newLines = [];
|
|
201
|
+
for (const line of lines) {
|
|
202
|
+
if (/^metadata:\s/.test(line)) {
|
|
203
|
+
replaced = true;
|
|
204
|
+
if (newMetaValue)
|
|
205
|
+
newLines.push(`metadata: ${newMetaValue}`);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
newLines.push(line);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!replaced && newMetaValue)
|
|
212
|
+
newLines.push(`metadata: ${newMetaValue}`);
|
|
213
|
+
return `---\n${newLines.join("\n")}\n---${after}`;
|
|
214
|
+
}
|
|
131
215
|
/**
|
|
132
216
|
* Set or remove the `embed` flag in a SKILL.md content string's frontmatter metadata.
|
|
133
217
|
* Preserves other metadata fields. Handles the case where no metadata block exists yet.
|
|
@@ -9,6 +9,7 @@ import { parseFrontmatter, resolveTaskmasterMetadata, resolveSkillInvocationPoli
|
|
|
9
9
|
import { resolvePluginSkillDirs } from "./plugin-skills.js";
|
|
10
10
|
import { hasMarker } from "../../license/skill-pack.js";
|
|
11
11
|
import { serializeByKey } from "./serialize.js";
|
|
12
|
+
import { clearSkillSpawnProfiles, registerSkillSpawnProfile } from "../tool-policy.js";
|
|
12
13
|
import { resolveSkillAgents } from "./agent-scope.js";
|
|
13
14
|
const fsp = fs.promises;
|
|
14
15
|
const skillsLogger = createSubsystemLogger("skills");
|
|
@@ -255,8 +256,27 @@ function loadSkillEntries(workspaceDir, opts) {
|
|
|
255
256
|
}
|
|
256
257
|
export function buildWorkspaceSkillSnapshot(workspaceDir, opts) {
|
|
257
258
|
const skillEntries = opts?.entries ?? loadSkillEntries(workspaceDir, opts);
|
|
259
|
+
clearSkillSpawnProfiles();
|
|
258
260
|
const eligible = filterSkillEntries(skillEntries, opts?.config, opts?.skillFilter, opts?.eligibility, opts?.agentId);
|
|
259
|
-
const
|
|
261
|
+
const bundledNames = resolveBundledSkillNamesSet();
|
|
262
|
+
const skillSpawnEntries = [];
|
|
263
|
+
const mainAgentSkills = [];
|
|
264
|
+
for (const entry of eligible) {
|
|
265
|
+
const isPreloaded = bundledNames.has(entry.skill.name);
|
|
266
|
+
const allowedAgents = resolveSkillAgents(entry.skill.name, isPreloaded, opts?.config);
|
|
267
|
+
if (allowedAgents.length === 1 && allowedAgents[0] === "subagent") {
|
|
268
|
+
registerSkillSpawnProfile(entry.skill.name, entry.taskmaster?.tools ?? []);
|
|
269
|
+
skillSpawnEntries.push({
|
|
270
|
+
name: entry.skill.name,
|
|
271
|
+
description: entry.skill.description ?? "",
|
|
272
|
+
profile: `spawn:skill:${entry.skill.name}`,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
mainAgentSkills.push(entry);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const promptEntries = mainAgentSkills.filter((entry) => entry.invocation?.disableModelInvocation !== true);
|
|
260
280
|
const resolvedSkills = promptEntries.map((entry) => entry.skill);
|
|
261
281
|
const remoteNote = opts?.eligibility?.remote?.note?.trim();
|
|
262
282
|
const prompt = [remoteNote, formatSkillsForPromptTaskmaster(promptEntries)]
|
|
@@ -264,12 +284,13 @@ export function buildWorkspaceSkillSnapshot(workspaceDir, opts) {
|
|
|
264
284
|
.join("\n");
|
|
265
285
|
return {
|
|
266
286
|
prompt,
|
|
267
|
-
skills:
|
|
287
|
+
skills: mainAgentSkills.map((entry) => ({
|
|
268
288
|
name: entry.skill.name,
|
|
269
289
|
primaryEnv: entry.taskmaster?.primaryEnv,
|
|
270
290
|
})),
|
|
271
291
|
resolvedSkills,
|
|
272
292
|
version: opts?.snapshotVersion,
|
|
293
|
+
skillSpawnEntries,
|
|
273
294
|
};
|
|
274
295
|
}
|
|
275
296
|
export function buildWorkspaceSkillsPrompt(workspaceDir, opts) {
|
|
@@ -87,7 +87,7 @@ function buildSkillStatus(entry, config, prefs, eligibility, bundledSkillNames)
|
|
|
87
87
|
const blockedByAllowlist = !isBundledSkillAllowed(entry, allowBundled);
|
|
88
88
|
const always = entry.taskmaster?.always === true;
|
|
89
89
|
const alwaysActive = entry.taskmaster?.embed === true;
|
|
90
|
-
const
|
|
90
|
+
const icon = entry.taskmaster?.icon ?? entry.frontmatter["icon"];
|
|
91
91
|
const homepageRaw = entry.taskmaster?.homepage ??
|
|
92
92
|
entry.frontmatter.homepage ??
|
|
93
93
|
entry.frontmatter.website ??
|
|
@@ -172,7 +172,7 @@ function buildSkillStatus(entry, config, prefs, eligibility, bundledSkillNames)
|
|
|
172
172
|
baseDir: entry.skill.baseDir,
|
|
173
173
|
skillKey,
|
|
174
174
|
primaryEnv: entry.taskmaster?.primaryEnv,
|
|
175
|
-
|
|
175
|
+
icon,
|
|
176
176
|
homepage,
|
|
177
177
|
always,
|
|
178
178
|
alwaysActive,
|
|
@@ -111,6 +111,20 @@ function buildMessagingSection(params) {
|
|
|
111
111
|
"",
|
|
112
112
|
];
|
|
113
113
|
}
|
|
114
|
+
function buildSkillSubAgentsSection(entries) {
|
|
115
|
+
if (!entries || entries.length === 0)
|
|
116
|
+
return [];
|
|
117
|
+
const lines = [
|
|
118
|
+
"### Skill Sub-Agents",
|
|
119
|
+
"These skills are handled by sub-agents. When a task matches one of these capabilities, spawn with the listed profile and instruct the sub-agent to use the named skill.",
|
|
120
|
+
"",
|
|
121
|
+
];
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
lines.push(`- \`${entry.profile}\` — ${entry.name}: ${entry.description}`);
|
|
124
|
+
}
|
|
125
|
+
lines.push("");
|
|
126
|
+
return lines;
|
|
127
|
+
}
|
|
114
128
|
function buildVoiceSection(params) {
|
|
115
129
|
if (params.isMinimal)
|
|
116
130
|
return [];
|
|
@@ -139,35 +153,35 @@ function buildDocsSection(params) {
|
|
|
139
153
|
"",
|
|
140
154
|
];
|
|
141
155
|
}
|
|
156
|
+
export const CORE_TOOL_SUMMARIES = {
|
|
157
|
+
read: "Read file contents",
|
|
158
|
+
write: "Create or overwrite files",
|
|
159
|
+
edit: "Make precise edits to files",
|
|
160
|
+
apply_patch: "Apply multi-file patches",
|
|
161
|
+
grep: "Search file contents for patterns",
|
|
162
|
+
find: "Find files by glob pattern",
|
|
163
|
+
ls: "List directory contents",
|
|
164
|
+
exec: "Run shell commands (pty available for TTY-required CLIs)",
|
|
165
|
+
process: "Manage background exec sessions",
|
|
166
|
+
web_search: "Search the web",
|
|
167
|
+
web_fetch: "Fetch and extract readable content from a URL",
|
|
168
|
+
browser: "Control web browser",
|
|
169
|
+
canvas: "Present/eval/snapshot the Canvas",
|
|
170
|
+
nodes: "List/describe/notify/camera/screen on paired nodes",
|
|
171
|
+
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)",
|
|
172
|
+
message: "Send messages and channel actions",
|
|
173
|
+
gateway: "Restart, apply config, or run updates on the running Taskmaster process",
|
|
174
|
+
software_update: "Check for software updates, install them, or restart the gateway",
|
|
175
|
+
agents_list: "List agent ids allowed for sessions_spawn",
|
|
176
|
+
sessions_list: "List other sessions (incl. sub-agents) with filters/last",
|
|
177
|
+
sessions_history: "Fetch history for another session/sub-agent",
|
|
178
|
+
sessions_spawn: "Spawn a sub-agent session",
|
|
179
|
+
session_status: "Show a /status-equivalent status card (usage + time + Reasoning/Verbose/Elevated); use for model-use questions (📊 session_status); optional per-session model override",
|
|
180
|
+
image: "Analyze an image with the configured image model",
|
|
181
|
+
skill_read: "Read files within skill directories (SKILL.md, references)",
|
|
182
|
+
};
|
|
142
183
|
export function buildAgentSystemPrompt(params) {
|
|
143
|
-
const coreToolSummaries =
|
|
144
|
-
read: "Read file contents",
|
|
145
|
-
write: "Create or overwrite files",
|
|
146
|
-
edit: "Make precise edits to files",
|
|
147
|
-
apply_patch: "Apply multi-file patches",
|
|
148
|
-
grep: "Search file contents for patterns",
|
|
149
|
-
find: "Find files by glob pattern",
|
|
150
|
-
ls: "List directory contents",
|
|
151
|
-
exec: "Run shell commands (pty available for TTY-required CLIs)",
|
|
152
|
-
process: "Manage background exec sessions",
|
|
153
|
-
web_search: "Search the web",
|
|
154
|
-
web_fetch: "Fetch and extract readable content from a URL",
|
|
155
|
-
// Channel docking: add login tools here when a channel needs interactive linking.
|
|
156
|
-
browser: "Control web browser",
|
|
157
|
-
canvas: "Present/eval/snapshot the Canvas",
|
|
158
|
-
nodes: "List/describe/notify/camera/screen on paired nodes",
|
|
159
|
-
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)",
|
|
160
|
-
message: "Send messages and channel actions",
|
|
161
|
-
gateway: "Restart, apply config, or run updates on the running Taskmaster process",
|
|
162
|
-
software_update: "Check for software updates, install them, or restart the gateway",
|
|
163
|
-
agents_list: "List agent ids allowed for sessions_spawn",
|
|
164
|
-
sessions_list: "List other sessions (incl. sub-agents) with filters/last",
|
|
165
|
-
sessions_history: "Fetch history for another session/sub-agent",
|
|
166
|
-
sessions_spawn: "Spawn a sub-agent session",
|
|
167
|
-
session_status: "Show a /status-equivalent status card (usage + time + Reasoning/Verbose/Elevated); use for model-use questions (📊 session_status); optional per-session model override",
|
|
168
|
-
image: "Analyze an image with the configured image model",
|
|
169
|
-
skill_read: "Read files within skill directories (SKILL.md, references)",
|
|
170
|
-
};
|
|
184
|
+
const coreToolSummaries = CORE_TOOL_SUMMARIES;
|
|
171
185
|
const toolOrder = [
|
|
172
186
|
"read",
|
|
173
187
|
"skill_read",
|
|
@@ -335,8 +349,7 @@ export function buildAgentSystemPrompt(params) {
|
|
|
335
349
|
"- `spawn:worker` — general-purpose (files, memory, web, docs, images, skills)",
|
|
336
350
|
"Pick the most specific profile that covers the task. Use `spawn:worker` when the task spans multiple domains. Always specify a `toolProfile` — omitting it gives the sub-agent your dispatcher tools, which are wrong for actual work.",
|
|
337
351
|
"",
|
|
338
|
-
|
|
339
|
-
"",
|
|
352
|
+
...buildSkillSubAgentsSection(params.skillSpawnEntries),
|
|
340
353
|
"## Tool Call Style",
|
|
341
354
|
"Every text block you produce is delivered as a separate message. Unnecessary text blocks = unnecessary messages sent to the user.",
|
|
342
355
|
"Call tools silently by default. Do not announce what you are about to do, explain what you just did, or summarise completed steps.",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CORE_TOOL_SUMMARIES } from "./system-prompt.js";
|
|
1
2
|
const TOOL_NAME_ALIASES = {
|
|
2
3
|
bash: "exec",
|
|
3
4
|
"apply-patch": "apply_patch",
|
|
@@ -64,6 +65,9 @@ export const TOOL_GROUPS = {
|
|
|
64
65
|
"logs_read",
|
|
65
66
|
"network_settings",
|
|
66
67
|
"wifi_settings",
|
|
68
|
+
"account_manage",
|
|
69
|
+
"access_manage",
|
|
70
|
+
"license_manage",
|
|
67
71
|
],
|
|
68
72
|
// All Taskmaster native tools (excludes provider plugins).
|
|
69
73
|
"group:taskmaster": [
|
|
@@ -100,6 +104,7 @@ export const TOOL_GROUPS = {
|
|
|
100
104
|
"api_keys",
|
|
101
105
|
"file_delete",
|
|
102
106
|
"file_list",
|
|
107
|
+
"file_manage",
|
|
103
108
|
"skill_draft_save",
|
|
104
109
|
"verify_contact",
|
|
105
110
|
"verify_contact_code",
|
|
@@ -115,6 +120,9 @@ export const TOOL_GROUPS = {
|
|
|
115
120
|
"network_settings",
|
|
116
121
|
"wifi_settings",
|
|
117
122
|
"skill_pack_install",
|
|
123
|
+
"account_manage",
|
|
124
|
+
"access_manage",
|
|
125
|
+
"license_manage",
|
|
118
126
|
"tunnel_status",
|
|
119
127
|
"tunnel_enable",
|
|
120
128
|
"tunnel_disable",
|
|
@@ -234,6 +242,30 @@ const TOOL_PROFILES = {
|
|
|
234
242
|
],
|
|
235
243
|
},
|
|
236
244
|
};
|
|
245
|
+
// ── Dangerous tools never granted to user-defined skill sub-agents ──────────
|
|
246
|
+
const SKILL_SPAWN_NEVER_GRANT = new Set([
|
|
247
|
+
"gateway",
|
|
248
|
+
"software_update",
|
|
249
|
+
"sessions_spawn",
|
|
250
|
+
"sessions_list",
|
|
251
|
+
"sessions_history",
|
|
252
|
+
"agents_list",
|
|
253
|
+
"message",
|
|
254
|
+
]);
|
|
255
|
+
// ── Dynamic skill spawn profile registry ─────────────────────────────────────
|
|
256
|
+
const skillSpawnProfileRegistry = new Map();
|
|
257
|
+
export function clearSkillSpawnProfiles() {
|
|
258
|
+
skillSpawnProfileRegistry.clear();
|
|
259
|
+
}
|
|
260
|
+
export function registerSkillSpawnProfile(skillName, tools) {
|
|
261
|
+
const workerPolicy = TOOL_PROFILES["spawn:worker"];
|
|
262
|
+
const workerAllow = workerPolicy?.allow ?? [];
|
|
263
|
+
const safeDeclared = tools.filter((t) => !SKILL_SPAWN_NEVER_GRANT.has(t));
|
|
264
|
+
const allow = safeDeclared.length > 0
|
|
265
|
+
? ["group:skills", ...safeDeclared]
|
|
266
|
+
: ["group:skills", ...workerAllow];
|
|
267
|
+
skillSpawnProfileRegistry.set(`spawn:skill:${skillName}`, { allow });
|
|
268
|
+
}
|
|
237
269
|
export function normalizeToolName(name) {
|
|
238
270
|
const normalized = name.trim().toLowerCase();
|
|
239
271
|
return TOOL_NAME_ALIASES[normalized] ?? normalized;
|
|
@@ -350,13 +382,45 @@ export function stripPluginOnlyAllowlist(policy, groups, coreTools) {
|
|
|
350
382
|
export function resolveToolProfilePolicy(profile) {
|
|
351
383
|
if (!profile)
|
|
352
384
|
return undefined;
|
|
353
|
-
const resolved = TOOL_PROFILES[profile]
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (!resolved.allow && !resolved.deny)
|
|
385
|
+
const resolved = TOOL_PROFILES[profile] ??
|
|
386
|
+
skillSpawnProfileRegistry.get(profile);
|
|
387
|
+
if (!resolved || (!resolved.allow && !resolved.deny))
|
|
357
388
|
return undefined;
|
|
358
389
|
return {
|
|
359
390
|
allow: resolved.allow ? [...resolved.allow] : undefined,
|
|
360
391
|
deny: resolved.deny ? [...resolved.deny] : undefined,
|
|
361
392
|
};
|
|
362
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Returns all tool names available for the skill sub-agent tools selector.
|
|
396
|
+
* Merges CORE_TOOL_SUMMARIES descriptions with TOOL_GROUPS expansion.
|
|
397
|
+
* Excludes tools that must never be granted to skill sub-agents.
|
|
398
|
+
*/
|
|
399
|
+
export function buildSelectableToolList() {
|
|
400
|
+
const result = new Map();
|
|
401
|
+
// Add group-expanded individual tool names, grouped by group name for description.
|
|
402
|
+
for (const [groupName, members] of Object.entries(TOOL_GROUPS)) {
|
|
403
|
+
if (groupName.startsWith("group:")) {
|
|
404
|
+
const label = groupName
|
|
405
|
+
.replace("group:", "")
|
|
406
|
+
.replace(/-/g, " ")
|
|
407
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
408
|
+
for (const member of members) {
|
|
409
|
+
if (!member.startsWith("group:") && !result.has(member)) {
|
|
410
|
+
result.set(member, `${label} operations`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// Overwrite with better descriptions from CORE_TOOL_SUMMARIES when available.
|
|
416
|
+
for (const [name, description] of Object.entries(CORE_TOOL_SUMMARIES)) {
|
|
417
|
+
result.set(name, description);
|
|
418
|
+
}
|
|
419
|
+
// Filter out dangerous tools.
|
|
420
|
+
for (const name of SKILL_SPAWN_NEVER_GRANT) {
|
|
421
|
+
result.delete(name);
|
|
422
|
+
}
|
|
423
|
+
return Array.from(result.entries())
|
|
424
|
+
.map(([name, description]) => ({ name, description }))
|
|
425
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
426
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool for managing access control — PINs and named admin users.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the gateway `access.status`, `access.setMasterPin`,
|
|
5
|
+
* `access.setAccountPin`, `access.addAdmin`, `access.removeAdmin`, and
|
|
6
|
+
* `access.updateAdminPin` RPC methods so the agent can configure access
|
|
7
|
+
* security directly during setup or administration.
|
|
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 ACTIONS = [
|
|
14
|
+
"status",
|
|
15
|
+
"set_master_pin",
|
|
16
|
+
"set_account_pin",
|
|
17
|
+
"add_admin",
|
|
18
|
+
"remove_admin",
|
|
19
|
+
"update_admin_pin",
|
|
20
|
+
];
|
|
21
|
+
const AccessManageSchema = Type.Object({
|
|
22
|
+
action: stringEnum(ACTIONS, {
|
|
23
|
+
description: '"status" returns current access configuration (PINs set, admin users). ' +
|
|
24
|
+
'"set_master_pin" sets or changes the master PIN (pin required, currentPin required when changing). ' +
|
|
25
|
+
'"set_account_pin" sets a PIN for a specific account (accountId + pin required). ' +
|
|
26
|
+
'"add_admin" creates a named admin user (username + pin + accessToken required). ' +
|
|
27
|
+
'"remove_admin" deletes a named admin user (username + accessToken required). ' +
|
|
28
|
+
'"update_admin_pin" changes an admin user\'s PIN (username + newPin + accessToken required).',
|
|
29
|
+
}),
|
|
30
|
+
pin: Type.Optional(Type.String({
|
|
31
|
+
description: "PIN value — required for set_master_pin, set_account_pin, and add_admin.",
|
|
32
|
+
})),
|
|
33
|
+
currentPin: Type.Optional(Type.String({
|
|
34
|
+
description: "Current master PIN — required when changing an existing master PIN (set_master_pin).",
|
|
35
|
+
})),
|
|
36
|
+
newPin: Type.Optional(Type.String({
|
|
37
|
+
description: "New PIN — required for update_admin_pin.",
|
|
38
|
+
})),
|
|
39
|
+
accountId: Type.Optional(Type.String({
|
|
40
|
+
description: "Account ID — required for set_account_pin.",
|
|
41
|
+
})),
|
|
42
|
+
username: Type.Optional(Type.String({
|
|
43
|
+
description: "Admin username — required for add_admin, remove_admin, and update_admin_pin.",
|
|
44
|
+
})),
|
|
45
|
+
accessToken: Type.Optional(Type.String({
|
|
46
|
+
description: "Master session token — required for add_admin, remove_admin, and update_admin_pin.",
|
|
47
|
+
})),
|
|
48
|
+
});
|
|
49
|
+
export function createAccessManageTool() {
|
|
50
|
+
return {
|
|
51
|
+
label: "Access Management",
|
|
52
|
+
name: "access_manage",
|
|
53
|
+
description: "Manage access control — PINs and named admin users. " +
|
|
54
|
+
"Actions: " +
|
|
55
|
+
'"status" (current access configuration), ' +
|
|
56
|
+
'"set_master_pin" (set or change the master PIN — pass pin, and currentPin when changing), ' +
|
|
57
|
+
'"set_account_pin" (set a PIN for a specific account — pass accountId + pin), ' +
|
|
58
|
+
'"add_admin" (create a named admin user — pass username + pin + accessToken), ' +
|
|
59
|
+
'"remove_admin" (delete a named admin user — pass username + accessToken), ' +
|
|
60
|
+
'"update_admin_pin" (change an admin user\'s PIN — pass username + newPin + accessToken).',
|
|
61
|
+
parameters: AccessManageSchema,
|
|
62
|
+
execute: async (_toolCallId, args) => {
|
|
63
|
+
const params = args;
|
|
64
|
+
const action = readStringParam(params, "action", { required: true });
|
|
65
|
+
switch (action) {
|
|
66
|
+
case "status": {
|
|
67
|
+
const result = await callGatewayTool("access.status", {});
|
|
68
|
+
return jsonResult(result);
|
|
69
|
+
}
|
|
70
|
+
case "set_master_pin": {
|
|
71
|
+
const pin = readStringParam(params, "pin", { required: true });
|
|
72
|
+
const currentPin = readStringParam(params, "currentPin");
|
|
73
|
+
const callParams = { pin };
|
|
74
|
+
if (currentPin)
|
|
75
|
+
callParams.currentPin = currentPin;
|
|
76
|
+
const result = await callGatewayTool("access.setMasterPin", {}, callParams);
|
|
77
|
+
return jsonResult(result);
|
|
78
|
+
}
|
|
79
|
+
case "set_account_pin": {
|
|
80
|
+
const accountId = readStringParam(params, "accountId", { required: true });
|
|
81
|
+
const pin = readStringParam(params, "pin", { required: true });
|
|
82
|
+
const result = await callGatewayTool("access.setAccountPin", {}, { accountId, pin });
|
|
83
|
+
return jsonResult(result);
|
|
84
|
+
}
|
|
85
|
+
case "add_admin": {
|
|
86
|
+
const username = readStringParam(params, "username", { required: true });
|
|
87
|
+
const pin = readStringParam(params, "pin", { required: true });
|
|
88
|
+
const accessToken = readStringParam(params, "accessToken", { required: true });
|
|
89
|
+
const result = await callGatewayTool("access.addAdmin", {}, { username, pin, accessToken });
|
|
90
|
+
return jsonResult(result);
|
|
91
|
+
}
|
|
92
|
+
case "remove_admin": {
|
|
93
|
+
const username = readStringParam(params, "username", { required: true });
|
|
94
|
+
const accessToken = readStringParam(params, "accessToken", { required: true });
|
|
95
|
+
const result = await callGatewayTool("access.removeAdmin", {}, { username, accessToken });
|
|
96
|
+
return jsonResult(result);
|
|
97
|
+
}
|
|
98
|
+
case "update_admin_pin": {
|
|
99
|
+
const username = readStringParam(params, "username", { required: true });
|
|
100
|
+
const newPin = readStringParam(params, "newPin", { required: true });
|
|
101
|
+
const accessToken = readStringParam(params, "accessToken", { required: true });
|
|
102
|
+
const result = await callGatewayTool("access.updateAdminPin", {}, { username, newPin, accessToken });
|
|
103
|
+
return jsonResult(result);
|
|
104
|
+
}
|
|
105
|
+
default:
|
|
106
|
+
throw new Error(`Unknown action: ${action}. Use "status", "set_master_pin", "set_account_pin", "add_admin", "remove_admin", or "update_admin_pin".`);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool for managing business accounts (workspaces).
|
|
3
|
+
*
|
|
4
|
+
* Wraps the gateway `workspaces.list`, `workspaces.create`, `workspaces.remove`,
|
|
5
|
+
* and `workspaces.rename` RPC methods so the agent can manage accounts directly
|
|
6
|
+
* during conversation — without requiring the user to navigate the control panel.
|
|
7
|
+
*/
|
|
8
|
+
import { Type } from "@sinclair/typebox";
|
|
9
|
+
import { stringEnum } from "../schema/typebox.js";
|
|
10
|
+
import { jsonResult, readStringParam } from "./common.js";
|
|
11
|
+
import { callGatewayTool } from "./gateway.js";
|
|
12
|
+
const ACCOUNT_MANAGE_ACTIONS = ["list", "create", "remove", "rename"];
|
|
13
|
+
const AccountManageSchema = Type.Object({
|
|
14
|
+
action: stringEnum(ACCOUNT_MANAGE_ACTIONS, {
|
|
15
|
+
description: '"list" returns all business accounts. ' +
|
|
16
|
+
'"create" creates a new account (name required, displayName and template optional). ' +
|
|
17
|
+
'"remove" deletes an account (name required). ' +
|
|
18
|
+
'"rename" changes the display name of an account (name + displayName required).',
|
|
19
|
+
}),
|
|
20
|
+
name: Type.Optional(Type.String({
|
|
21
|
+
description: "Workspace name (required for create, remove, rename).",
|
|
22
|
+
})),
|
|
23
|
+
displayName: Type.Optional(Type.String({
|
|
24
|
+
description: "Human-friendly display name (used with create and rename).",
|
|
25
|
+
})),
|
|
26
|
+
template: Type.Optional(Type.String({
|
|
27
|
+
description: "Template name for create. Defaults to cloning the default workspace if omitted.",
|
|
28
|
+
})),
|
|
29
|
+
});
|
|
30
|
+
export function createAccountManageTool() {
|
|
31
|
+
return {
|
|
32
|
+
label: "Account Management",
|
|
33
|
+
name: "account_manage",
|
|
34
|
+
description: "Manage business accounts (workspaces): list, create, remove, or rename. " +
|
|
35
|
+
'"list" returns all configured accounts. ' +
|
|
36
|
+
'"create" provisions a new account (name required; displayName and template optional). ' +
|
|
37
|
+
'"remove" deletes an account and its workspace (name required). ' +
|
|
38
|
+
'"rename" updates the display name of an existing account (name + displayName required).',
|
|
39
|
+
parameters: AccountManageSchema,
|
|
40
|
+
execute: async (_toolCallId, args) => {
|
|
41
|
+
const params = args;
|
|
42
|
+
const action = readStringParam(params, "action", { required: true });
|
|
43
|
+
switch (action) {
|
|
44
|
+
case "list": {
|
|
45
|
+
const result = await callGatewayTool("workspaces.list", {}, {});
|
|
46
|
+
return jsonResult(result);
|
|
47
|
+
}
|
|
48
|
+
case "create": {
|
|
49
|
+
const name = readStringParam(params, "name", { required: true, label: "name" });
|
|
50
|
+
const displayName = readStringParam(params, "displayName");
|
|
51
|
+
const template = readStringParam(params, "template");
|
|
52
|
+
const result = await callGatewayTool("workspaces.create", {}, {
|
|
53
|
+
name,
|
|
54
|
+
...(displayName ? { displayName } : {}),
|
|
55
|
+
...(template ? { template } : {}),
|
|
56
|
+
});
|
|
57
|
+
return jsonResult(result);
|
|
58
|
+
}
|
|
59
|
+
case "remove": {
|
|
60
|
+
const name = readStringParam(params, "name", { required: true, label: "name" });
|
|
61
|
+
const result = await callGatewayTool("workspaces.remove", {}, { name });
|
|
62
|
+
return jsonResult(result);
|
|
63
|
+
}
|
|
64
|
+
case "rename": {
|
|
65
|
+
const name = readStringParam(params, "name", { required: true, label: "name" });
|
|
66
|
+
const displayName = readStringParam(params, "displayName", {
|
|
67
|
+
required: true,
|
|
68
|
+
label: "displayName",
|
|
69
|
+
});
|
|
70
|
+
const result = await callGatewayTool("workspaces.rename", {}, { name, displayName });
|
|
71
|
+
return jsonResult(result);
|
|
72
|
+
}
|
|
73
|
+
default:
|
|
74
|
+
throw new Error(`Unknown action: ${action}. Use "list", "create", "remove", or "rename".`);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|