@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.
Files changed (43) hide show
  1. package/dist/agents/context.js +2 -0
  2. package/dist/agents/pi-embedded-runner/compact.js +1 -0
  3. package/dist/agents/pi-embedded-runner/run/attempt.js +1 -0
  4. package/dist/agents/pi-embedded-runner/system-prompt.js +1 -0
  5. package/dist/agents/skills/frontmatter.js +85 -1
  6. package/dist/agents/skills/workspace.js +23 -2
  7. package/dist/agents/skills-status.js +2 -2
  8. package/dist/agents/system-prompt.js +43 -30
  9. package/dist/agents/tool-policy.js +68 -4
  10. package/dist/agents/tools/access-manage-tool.js +110 -0
  11. package/dist/agents/tools/account-manage-tool.js +78 -0
  12. package/dist/agents/tools/brand-settings-tool.js +53 -24
  13. package/dist/agents/tools/file-manage-tool.js +193 -0
  14. package/dist/agents/tools/license-manage-tool.js +50 -0
  15. package/dist/agents/tools/opening-hours-tool.js +6 -0
  16. package/dist/agents/tools/skill-manage-tool.js +16 -3
  17. package/dist/auto-reply/reply/commands-context-report.js +2 -1
  18. package/dist/browser/chrome.js +11 -0
  19. package/dist/build-info.json +3 -3
  20. package/dist/business/opening-hours.js +14 -0
  21. package/dist/cli/skills-cli.js +4 -9
  22. package/dist/commands/onboard-skills.js +1 -1
  23. package/dist/config/zod-schema.js +6 -1
  24. package/dist/control-ui/assets/index-CAu2PL0O.css +1 -0
  25. package/dist/control-ui/assets/{index-Cl91wvkO.js → index-QAV6uia0.js} +715 -571
  26. package/dist/control-ui/assets/index-QAV6uia0.js.map +1 -0
  27. package/dist/control-ui/index.html +3 -2
  28. package/dist/gateway/protocol/index.js +3 -2
  29. package/dist/gateway/protocol/schema/agents-models-skills.js +6 -1
  30. package/dist/gateway/protocol/schema/tools.js +8 -0
  31. package/dist/gateway/server-methods/sessions.js +3 -1
  32. package/dist/gateway/server-methods/skills.js +31 -2
  33. package/dist/gateway/server-methods/tools.js +7 -0
  34. package/dist/gateway/server-methods.js +3 -0
  35. package/dist/gateway/session-utils.js +5 -1
  36. package/dist/media-understanding/apply.js +18 -4
  37. package/dist/web/auto-reply/monitor/process-message.js +1 -0
  38. package/dist/web/inbound/monitor.js +4 -0
  39. package/package.json +1 -1
  40. package/skills/skill-builder/SKILL.md +18 -4
  41. package/skills/skill-builder/references/lean-pattern.md +13 -3
  42. package/dist/control-ui/assets/index-Cl91wvkO.js.map +0 -1
  43. package/dist/control-ui/assets/index-Dd2cHcuh.css +0 -1
@@ -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
- emoji: typeof taskmasterObj.emoji === "string" ? taskmasterObj.emoji : undefined,
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 promptEntries = eligible.filter((entry) => entry.invocation?.disableModelInvocation !== true);
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: eligible.map((entry) => ({
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 emoji = entry.taskmaster?.emoji ?? entry.frontmatter.emoji;
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
- emoji,
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
- "Sub-agents with skill access have specialist knowledge modules (e.g. log review, weather, event management). When a task aligns with a known capability, tell the sub-agent to use the relevant skill in your task description — e.g. \"Use the log-review skill to analyse today's logs.\"",
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
- if (!resolved)
355
- return undefined;
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
+ }