@rubytech/taskmaster 1.2.0 → 1.3.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/auth-profiles/profiles.js +37 -0
- package/dist/agents/auth-profiles.js +1 -1
- package/dist/agents/pi-tools.policy.js +4 -1
- package/dist/agents/sandbox/constants.js +0 -1
- package/dist/agents/system-prompt.js +1 -4
- package/dist/agents/taskmaster-tools.js +14 -6
- package/dist/agents/tool-policy.js +5 -5
- package/dist/agents/tools/apikeys-tool.js +16 -5
- package/dist/agents/tools/contact-create-tool.js +59 -0
- package/dist/agents/tools/contact-delete-tool.js +48 -0
- package/dist/agents/tools/contact-update-tool.js +17 -2
- package/dist/agents/tools/file-delete-tool.js +137 -0
- package/dist/agents/tools/file-list-tool.js +127 -0
- package/dist/auto-reply/reply/commands-tts.js +7 -2
- package/dist/build-info.json +3 -3
- package/dist/cli/provision-seed.js +1 -3
- package/dist/commands/doctor-config-flow.js +13 -0
- package/dist/config/agent-tools-reconcile.js +53 -0
- package/dist/config/defaults.js +10 -1
- package/dist/config/legacy.migrations.part-3.js +26 -0
- package/dist/config/zod-schema.core.js +9 -1
- package/dist/config/zod-schema.js +1 -0
- package/dist/control-ui/assets/index-CPawOl_z.css +1 -0
- package/dist/control-ui/assets/{index-DwMopZij.js → index-DQ1kxYd4.js} +693 -598
- package/dist/control-ui/assets/index-DQ1kxYd4.js.map +1 -0
- package/dist/control-ui/index.html +2 -2
- package/dist/gateway/chat-sanitize.js +16 -2
- package/dist/gateway/config-reload.js +1 -0
- package/dist/gateway/media-http.js +32 -1
- package/dist/gateway/server-methods/apikeys.js +56 -4
- package/dist/gateway/server-methods/tts.js +11 -2
- package/dist/gateway/server.impl.js +15 -0
- package/dist/media-understanding/apply.js +35 -0
- package/dist/media-understanding/providers/deepgram/audio.js +1 -1
- package/dist/media-understanding/providers/google/audio.js +1 -1
- package/dist/media-understanding/providers/google/video.js +1 -1
- package/dist/media-understanding/providers/index.js +2 -0
- package/dist/media-understanding/providers/openai/audio.js +1 -1
- package/dist/media-understanding/providers/sherpa-onnx/index.js +10 -0
- package/dist/media-understanding/runner.js +61 -72
- package/dist/media-understanding/sherpa-onnx-local.js +223 -0
- package/dist/records/records-manager.js +10 -0
- package/dist/tts/tts.js +98 -10
- package/dist/web/auto-reply/monitor/process-message.js +1 -0
- package/dist/web/inbound/monitor.js +9 -1
- package/extensions/googlechat/node_modules/.bin/taskmaster +2 -2
- package/extensions/googlechat/package.json +2 -2
- package/extensions/line/node_modules/.bin/taskmaster +2 -2
- package/extensions/line/package.json +1 -1
- package/extensions/matrix/node_modules/.bin/taskmaster +2 -2
- package/extensions/matrix/package.json +1 -1
- package/extensions/msteams/node_modules/.bin/taskmaster +2 -2
- package/extensions/msteams/package.json +1 -1
- package/extensions/nostr/node_modules/.bin/taskmaster +2 -2
- package/extensions/nostr/package.json +1 -1
- package/extensions/zalo/node_modules/.bin/taskmaster +2 -2
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/node_modules/.bin/taskmaster +2 -2
- package/extensions/zalouser/package.json +1 -1
- package/package.json +3 -2
- package/scripts/postinstall.js +76 -0
- package/skills/business-assistant/references/crm.md +32 -8
- package/taskmaster-docs/USER-GUIDE.md +84 -5
- package/templates/beagle/agents/admin/AGENTS.md +4 -2
- package/templates/taskmaster/agents/admin/AGENTS.md +1 -0
- package/dist/control-ui/assets/index-DvB85yTz.css +0 -1
- package/dist/control-ui/assets/index-DwMopZij.js.map +0 -1
|
@@ -0,0 +1,127 @@
|
|
|
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 { jsonResult, readStringParam, readNumberParam } from "./common.js";
|
|
7
|
+
const FileListSchema = Type.Object({
|
|
8
|
+
path: Type.Optional(Type.String({
|
|
9
|
+
description: "Directory to list, relative to the workspace root " +
|
|
10
|
+
"(e.g. 'memory/users', 'skills'). Defaults to the workspace root.",
|
|
11
|
+
})),
|
|
12
|
+
depth: Type.Optional(Type.Number({
|
|
13
|
+
description: "How many levels deep to recurse. 1 = immediate children only (default). " +
|
|
14
|
+
"Use 2–3 for a broader view. Maximum 5.",
|
|
15
|
+
})),
|
|
16
|
+
});
|
|
17
|
+
function isInsideRoot(target, root) {
|
|
18
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
19
|
+
return target === root || target.startsWith(prefix);
|
|
20
|
+
}
|
|
21
|
+
async function listDir(dirPath, currentDepth, maxDepth) {
|
|
22
|
+
let entries;
|
|
23
|
+
try {
|
|
24
|
+
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const result = [];
|
|
30
|
+
// Sort: directories first, then files, alphabetical within each group
|
|
31
|
+
const sorted = [...entries].sort((a, b) => {
|
|
32
|
+
const aDir = a.isDirectory() || a.isSymbolicLink() ? 0 : 1;
|
|
33
|
+
const bDir = b.isDirectory() || b.isSymbolicLink() ? 0 : 1;
|
|
34
|
+
if (aDir !== bDir)
|
|
35
|
+
return aDir - bDir;
|
|
36
|
+
return String(a.name).localeCompare(String(b.name));
|
|
37
|
+
});
|
|
38
|
+
for (const entry of sorted) {
|
|
39
|
+
const name = String(entry.name);
|
|
40
|
+
const fullPath = path.join(dirPath, name);
|
|
41
|
+
const item = {
|
|
42
|
+
name,
|
|
43
|
+
type: entry.isDirectory() ? "directory" : entry.isSymbolicLink() ? "symlink" : "file",
|
|
44
|
+
};
|
|
45
|
+
if (entry.isFile()) {
|
|
46
|
+
try {
|
|
47
|
+
const stat = await fs.stat(fullPath);
|
|
48
|
+
item.size = stat.size;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// stat failed — skip size
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if ((entry.isDirectory() || entry.isSymbolicLink()) && currentDepth < maxDepth) {
|
|
55
|
+
item.children = await listDir(fullPath, currentDepth + 1, maxDepth);
|
|
56
|
+
}
|
|
57
|
+
result.push(item);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
export function createFileListTool(options) {
|
|
62
|
+
return {
|
|
63
|
+
label: "File List",
|
|
64
|
+
name: "file_list",
|
|
65
|
+
description: "List files and folders in the workspace. " +
|
|
66
|
+
"Path is relative to the workspace root. " +
|
|
67
|
+
"Returns names, types (file/directory/symlink), and sizes. " +
|
|
68
|
+
"Use depth > 1 to see nested contents.",
|
|
69
|
+
parameters: FileListSchema,
|
|
70
|
+
execute: async (_toolCallId, args) => {
|
|
71
|
+
const params = args;
|
|
72
|
+
const rawPath = readStringParam(params, "path") ?? "";
|
|
73
|
+
const rawDepth = readNumberParam(params, "depth", { integer: true });
|
|
74
|
+
const maxDepth = Math.max(1, Math.min(rawDepth ?? 1, 5));
|
|
75
|
+
// ── Resolve workspace root ──────────────────────────────────
|
|
76
|
+
const cfg = options?.config;
|
|
77
|
+
if (!cfg) {
|
|
78
|
+
return jsonResult({ error: "No config available — cannot resolve workspace." });
|
|
79
|
+
}
|
|
80
|
+
const agentId = resolveSessionAgentId({
|
|
81
|
+
sessionKey: options?.agentSessionKey,
|
|
82
|
+
config: cfg,
|
|
83
|
+
});
|
|
84
|
+
const workspaceRoot = resolveAgentWorkspaceRoot(cfg, agentId);
|
|
85
|
+
// ── Normalise and resolve target ────────────────────────────
|
|
86
|
+
if (rawPath.includes("\0")) {
|
|
87
|
+
return jsonResult({ error: "Invalid path." });
|
|
88
|
+
}
|
|
89
|
+
const resolved = rawPath ? path.resolve(workspaceRoot, rawPath) : workspaceRoot;
|
|
90
|
+
// ── Containment check ───────────────────────────────────────
|
|
91
|
+
let realTarget;
|
|
92
|
+
try {
|
|
93
|
+
realTarget = await realpath(resolved);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return jsonResult({ error: `Not found: ${rawPath || "/"}` });
|
|
97
|
+
}
|
|
98
|
+
let realRoot;
|
|
99
|
+
try {
|
|
100
|
+
realRoot = await realpath(workspaceRoot);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return jsonResult({ error: "Workspace root is not accessible." });
|
|
104
|
+
}
|
|
105
|
+
if (!isInsideRoot(realTarget, realRoot)) {
|
|
106
|
+
return jsonResult({ error: "Path is outside the workspace." });
|
|
107
|
+
}
|
|
108
|
+
// ── Verify it's a directory ─────────────────────────────────
|
|
109
|
+
try {
|
|
110
|
+
const stat = await fs.stat(realTarget);
|
|
111
|
+
if (!stat.isDirectory()) {
|
|
112
|
+
return jsonResult({ error: `Not a directory: ${rawPath}` });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return jsonResult({ error: `Not found: ${rawPath || "/"}` });
|
|
117
|
+
}
|
|
118
|
+
// ── List ────────────────────────────────────────────────────
|
|
119
|
+
const entries = await listDir(realTarget, 1, maxDepth);
|
|
120
|
+
return jsonResult({
|
|
121
|
+
path: rawPath || "/",
|
|
122
|
+
depth: maxDepth,
|
|
123
|
+
entries,
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -115,6 +115,7 @@ export const handleTtsCommands = async (params, allowTextCommands) => {
|
|
|
115
115
|
.filter((provider) => isTtsProviderConfigured(config, provider));
|
|
116
116
|
const hasOpenAI = Boolean(resolveTtsApiKey(config, "openai"));
|
|
117
117
|
const hasElevenLabs = Boolean(resolveTtsApiKey(config, "elevenlabs"));
|
|
118
|
+
const hasHume = Boolean(resolveTtsApiKey(config, "hume"));
|
|
118
119
|
const hasEdge = isTtsProviderConfigured(config, "edge");
|
|
119
120
|
return {
|
|
120
121
|
shouldContinue: false,
|
|
@@ -124,13 +125,17 @@ export const handleTtsCommands = async (params, allowTextCommands) => {
|
|
|
124
125
|
`Fallbacks: ${fallback.join(", ") || "none"}\n` +
|
|
125
126
|
`OpenAI key: ${hasOpenAI ? "✅" : "❌"}\n` +
|
|
126
127
|
`ElevenLabs key: ${hasElevenLabs ? "✅" : "❌"}\n` +
|
|
128
|
+
`Hume key: ${hasHume ? "✅" : "❌"}\n` +
|
|
127
129
|
`Edge enabled: ${hasEdge ? "✅" : "❌"}\n` +
|
|
128
|
-
`Usage: /tts provider openai | elevenlabs | edge`,
|
|
130
|
+
`Usage: /tts provider openai | elevenlabs | hume | edge`,
|
|
129
131
|
},
|
|
130
132
|
};
|
|
131
133
|
}
|
|
132
134
|
const requested = args.trim().toLowerCase();
|
|
133
|
-
if (requested !== "openai" &&
|
|
135
|
+
if (requested !== "openai" &&
|
|
136
|
+
requested !== "elevenlabs" &&
|
|
137
|
+
requested !== "hume" &&
|
|
138
|
+
requested !== "edge") {
|
|
134
139
|
return { shouldContinue: false, reply: ttsUsage() };
|
|
135
140
|
}
|
|
136
141
|
setTtsProvider(prefsPath, requested);
|
package/dist/build-info.json
CHANGED
|
@@ -134,12 +134,10 @@ export function buildDefaultAgentList(workspaceRoot) {
|
|
|
134
134
|
"current_time",
|
|
135
135
|
"sessions_list",
|
|
136
136
|
"sessions_history",
|
|
137
|
-
"sessions_send",
|
|
138
137
|
"session_status",
|
|
139
138
|
"cron",
|
|
140
139
|
"license_generate",
|
|
141
|
-
"
|
|
142
|
-
"contact_update",
|
|
140
|
+
"group:contacts",
|
|
143
141
|
"authorize_admin",
|
|
144
142
|
"revoke_admin",
|
|
145
143
|
"list_admins",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TaskmasterSchema, CONFIG_PATH_TASKMASTER, migrateLegacyConfig, readConfigFileSnapshot, } from "../config/config.js";
|
|
2
|
+
import { reconcileAgentContactTools } from "../config/agent-tools-reconcile.js";
|
|
2
3
|
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
|
3
4
|
import { formatCliCommand } from "../cli/command-format.js";
|
|
4
5
|
import { note } from "../terminal/note.js";
|
|
@@ -156,6 +157,18 @@ export async function loadAndMaybeMigrateDoctorConfig(params) {
|
|
|
156
157
|
fixHints.push(`Run "${formatCliCommand("taskmaster doctor --fix")}" to apply these changes.`);
|
|
157
158
|
}
|
|
158
159
|
}
|
|
160
|
+
const toolReconcile = reconcileAgentContactTools({ config: candidate });
|
|
161
|
+
if (toolReconcile.changes.length > 0) {
|
|
162
|
+
note(toolReconcile.changes.join("\n"), "Doctor changes");
|
|
163
|
+
candidate = toolReconcile.config;
|
|
164
|
+
pendingChanges = true;
|
|
165
|
+
if (shouldRepair) {
|
|
166
|
+
cfg = toolReconcile.config;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
fixHints.push(`Run "${formatCliCommand("taskmaster doctor --fix")}" to apply these changes.`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
159
172
|
const unknown = stripUnknownConfigKeys(candidate);
|
|
160
173
|
if (unknown.removed.length > 0) {
|
|
161
174
|
const lines = unknown.removed.map((path) => `- ${path}`).join("\n");
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Individual contact tool names that should be replaced by `group:contacts`.
|
|
3
|
+
* Order does not matter — all are removed and replaced with the group.
|
|
4
|
+
*/
|
|
5
|
+
const INDIVIDUAL_CONTACT_TOOLS = [
|
|
6
|
+
"contact_create",
|
|
7
|
+
"contact_delete",
|
|
8
|
+
"contact_lookup",
|
|
9
|
+
"contact_update",
|
|
10
|
+
];
|
|
11
|
+
function isAdminAgent(agent) {
|
|
12
|
+
const id = agent.id?.trim() ?? "";
|
|
13
|
+
return id === "admin" || id.endsWith("-admin");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Ensure admin agents use `group:contacts` instead of individual contact tools.
|
|
17
|
+
*
|
|
18
|
+
* Runs unconditionally on gateway startup (like `applyPluginAutoEnable`).
|
|
19
|
+
* Idempotent — if the allow list already contains `group:contacts`, no changes.
|
|
20
|
+
*/
|
|
21
|
+
export function reconcileAgentContactTools(params) {
|
|
22
|
+
const config = structuredClone(params.config);
|
|
23
|
+
const changes = [];
|
|
24
|
+
const agents = config.agents?.list;
|
|
25
|
+
if (!Array.isArray(agents))
|
|
26
|
+
return { config, changes };
|
|
27
|
+
for (const agent of agents) {
|
|
28
|
+
if (!agent || !isAdminAgent(agent))
|
|
29
|
+
continue;
|
|
30
|
+
const allow = agent.tools?.allow;
|
|
31
|
+
if (!Array.isArray(allow))
|
|
32
|
+
continue;
|
|
33
|
+
// Already using the group — nothing to do
|
|
34
|
+
if (allow.includes("group:contacts"))
|
|
35
|
+
continue;
|
|
36
|
+
// Check if any individual contact tools are present
|
|
37
|
+
const hasAny = INDIVIDUAL_CONTACT_TOOLS.some((t) => allow.includes(t));
|
|
38
|
+
if (!hasAny)
|
|
39
|
+
continue;
|
|
40
|
+
// Remove individual entries, add group
|
|
41
|
+
const removed = [];
|
|
42
|
+
for (const tool of INDIVIDUAL_CONTACT_TOOLS) {
|
|
43
|
+
const idx = allow.indexOf(tool);
|
|
44
|
+
if (idx !== -1) {
|
|
45
|
+
allow.splice(idx, 1);
|
|
46
|
+
removed.push(tool);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
allow.push("group:contacts");
|
|
50
|
+
changes.push(`Replaced ${removed.join(", ")} with group:contacts in agent "${agent.id}" tools.allow.`);
|
|
51
|
+
}
|
|
52
|
+
return { config, changes };
|
|
53
|
+
}
|
package/dist/config/defaults.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
|
|
2
2
|
import { parseModelRef } from "../agents/model-selection.js";
|
|
3
|
-
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
|
3
|
+
import { upsertAuthProfile, removeAuthProfile } from "../agents/auth-profiles.js";
|
|
4
4
|
import { resolveTalkApiKey } from "./talk.js";
|
|
5
5
|
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
|
|
6
6
|
let defaultWarnState = { warned: false };
|
|
@@ -372,10 +372,19 @@ export function applyApiKeys(config) {
|
|
|
372
372
|
const keys = config.apiKeys;
|
|
373
373
|
if (!keys || Object.keys(keys).length === 0)
|
|
374
374
|
return config;
|
|
375
|
+
const disabled = config.apiKeysDisabled ?? {};
|
|
375
376
|
let cfg = config;
|
|
376
377
|
for (const [provider, key] of Object.entries(keys)) {
|
|
377
378
|
if (!key?.trim())
|
|
378
379
|
continue;
|
|
380
|
+
if (disabled[provider]) {
|
|
381
|
+
// Actively remove auth profiles for disabled model providers so stale
|
|
382
|
+
// credentials from a previous config load don't persist in memory.
|
|
383
|
+
if (API_KEY_MODEL_PROVIDERS.has(provider)) {
|
|
384
|
+
removeAuthProfile({ profileId: `${provider}:api-key` });
|
|
385
|
+
}
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
379
388
|
const trimmedKey = key.trim();
|
|
380
389
|
if (API_KEY_MODEL_PROVIDERS.has(provider)) {
|
|
381
390
|
upsertAuthProfile({
|
|
@@ -216,4 +216,30 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3 = [
|
|
|
216
216
|
}
|
|
217
217
|
},
|
|
218
218
|
},
|
|
219
|
+
{
|
|
220
|
+
id: "agents-tools-remove-sessions_send",
|
|
221
|
+
describe: "Remove sessions_send from agent tools.allow (tool removed for security)",
|
|
222
|
+
apply: (raw, changes) => {
|
|
223
|
+
const agents = getRecord(raw.agents);
|
|
224
|
+
const list = getAgentsList(agents);
|
|
225
|
+
for (const entry of list) {
|
|
226
|
+
if (!isRecord(entry))
|
|
227
|
+
continue;
|
|
228
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
229
|
+
if (!id)
|
|
230
|
+
continue;
|
|
231
|
+
const tools = getRecord(entry.tools);
|
|
232
|
+
if (!tools)
|
|
233
|
+
continue;
|
|
234
|
+
if (!Array.isArray(tools.allow))
|
|
235
|
+
continue;
|
|
236
|
+
const allow = tools.allow;
|
|
237
|
+
const index = allow.indexOf("sessions_send");
|
|
238
|
+
if (index !== -1) {
|
|
239
|
+
allow.splice(index, 1);
|
|
240
|
+
changes.push(`Removed sessions_send from agent "${id}" tools.allow.`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
},
|
|
219
245
|
];
|
|
@@ -138,7 +138,7 @@ export const MarkdownConfigSchema = z
|
|
|
138
138
|
})
|
|
139
139
|
.strict()
|
|
140
140
|
.optional();
|
|
141
|
-
export const TtsProviderSchema = z.enum(["elevenlabs", "openai", "edge"]);
|
|
141
|
+
export const TtsProviderSchema = z.enum(["elevenlabs", "openai", "hume", "edge"]);
|
|
142
142
|
export const TtsModeSchema = z.enum(["final", "all"]);
|
|
143
143
|
export const TtsAutoSchema = z.enum(["off", "always", "inbound", "tagged"]);
|
|
144
144
|
export const TtsConfigSchema = z
|
|
@@ -191,6 +191,14 @@ export const TtsConfigSchema = z
|
|
|
191
191
|
})
|
|
192
192
|
.strict()
|
|
193
193
|
.optional(),
|
|
194
|
+
hume: z
|
|
195
|
+
.object({
|
|
196
|
+
apiKey: z.string().optional(),
|
|
197
|
+
voice: z.string().optional(),
|
|
198
|
+
description: z.string().optional(),
|
|
199
|
+
})
|
|
200
|
+
.strict()
|
|
201
|
+
.optional(),
|
|
194
202
|
edge: z
|
|
195
203
|
.object({
|
|
196
204
|
enabled: z.boolean().optional(),
|