@rubytech/taskmaster 1.3.0 → 1.4.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/auth-profiles/oauth.js +24 -0
- package/dist/agents/tool-policy.js +4 -12
- package/dist/agents/tools/message-history-tool.js +2 -3
- package/dist/auto-reply/media-note.js +11 -0
- package/dist/auto-reply/reply/get-reply.js +4 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/daemon-cli/status.gather.js +7 -1
- package/dist/commands/doctor-config-flow.js +13 -1
- package/dist/config/agent-tools-reconcile.js +67 -0
- package/dist/control-ui/assets/{index-DQ1kxYd4.js → index-BDETQp97.js} +281 -283
- package/dist/control-ui/assets/index-BDETQp97.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/gateway/call.js +18 -3
- package/dist/gateway/chat-sanitize.js +5 -1
- package/dist/gateway/server/tls.js +2 -2
- package/dist/gateway/server-http.js +34 -4
- package/dist/gateway/server-methods/chat.js +64 -25
- package/dist/gateway/server.impl.js +38 -6
- package/dist/infra/heartbeat-runner.js +8 -5
- package/dist/infra/tls/gateway.js +19 -3
- package/dist/memory/audit.js +9 -0
- package/dist/memory/manager.js +43 -9
- package/dist/web/auto-reply/monitor/process-message.js +44 -17
- package/package.json +1 -1
- package/taskmaster-docs/USER-GUIDE.md +28 -2
- package/dist/control-ui/assets/index-DQ1kxYd4.js.map +0 -1
|
@@ -59,6 +59,30 @@ async function refreshOAuthTokenWithLock(params) {
|
|
|
59
59
|
type: "oauth",
|
|
60
60
|
};
|
|
61
61
|
saveAuthProfileStore(store, params.agentDir);
|
|
62
|
+
// Propagate refreshed credentials to the main store so auth.status and other agents
|
|
63
|
+
// see the fresh token. Without this, the main store retains a stale refresh token
|
|
64
|
+
// that Anthropic has already rotated, causing auth.status to permanently report
|
|
65
|
+
// "Connection expired" even though the agent is working fine.
|
|
66
|
+
const mainAuthPath = resolveAuthStorePath();
|
|
67
|
+
if (authPath !== mainAuthPath) {
|
|
68
|
+
try {
|
|
69
|
+
const mainStore = ensureAuthProfileStore();
|
|
70
|
+
const mainCred = mainStore.profiles[params.profileId];
|
|
71
|
+
const mainExpiry = mainCred?.type === "oauth" ? mainCred.expires : 0;
|
|
72
|
+
const freshExpiry = result.newCredentials.expires ?? 0;
|
|
73
|
+
if (freshExpiry > mainExpiry) {
|
|
74
|
+
mainStore.profiles[params.profileId] = {
|
|
75
|
+
...(mainCred ?? cred),
|
|
76
|
+
...result.newCredentials,
|
|
77
|
+
type: "oauth",
|
|
78
|
+
};
|
|
79
|
+
saveAuthProfileStore(mainStore);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Best-effort — don't fail the agent's own refresh if main store update fails
|
|
84
|
+
}
|
|
85
|
+
}
|
|
62
86
|
// Sync refreshed credentials back to Claude Code CLI if this is the claude-cli profile
|
|
63
87
|
// This ensures Claude Code continues to work after Taskmaster refreshes the token
|
|
64
88
|
if (params.profileId === CLAUDE_CLI_PROFILE_ID && cred.provider === "anthropic") {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const TOOL_NAME_ALIASES = {
|
|
2
2
|
bash: "exec",
|
|
3
3
|
"apply-patch": "apply_patch",
|
|
4
|
+
customer_lookup: "contact_lookup",
|
|
5
|
+
customer_update: "contact_update",
|
|
4
6
|
};
|
|
5
7
|
export const TOOL_GROUPS = {
|
|
6
8
|
// NOTE: Keep canonical (lowercase) tool names here.
|
|
@@ -22,12 +24,7 @@ export const TOOL_GROUPS = {
|
|
|
22
24
|
// Host/runtime execution tools
|
|
23
25
|
"group:runtime": ["exec", "process"],
|
|
24
26
|
// Session management tools
|
|
25
|
-
"group:sessions": [
|
|
26
|
-
"sessions_list",
|
|
27
|
-
"sessions_history",
|
|
28
|
-
"sessions_spawn",
|
|
29
|
-
"session_status",
|
|
30
|
-
],
|
|
27
|
+
"group:sessions": ["sessions_list", "sessions_history", "sessions_spawn", "session_status"],
|
|
31
28
|
// UI helpers
|
|
32
29
|
"group:ui": ["browser", "canvas"],
|
|
33
30
|
// Automation + infra
|
|
@@ -93,12 +90,7 @@ const TOOL_PROFILES = {
|
|
|
93
90
|
allow: ["group:fs", "group:runtime", "group:sessions", "group:memory", "image"],
|
|
94
91
|
},
|
|
95
92
|
messaging: {
|
|
96
|
-
allow: [
|
|
97
|
-
"group:messaging",
|
|
98
|
-
"sessions_list",
|
|
99
|
-
"sessions_history",
|
|
100
|
-
"session_status",
|
|
101
|
-
],
|
|
93
|
+
allow: ["group:messaging", "sessions_list", "sessions_history", "session_status"],
|
|
102
94
|
},
|
|
103
95
|
full: {
|
|
104
96
|
// "full" grants all Taskmaster-native tools, memory, web, sessions,
|
|
@@ -125,10 +125,9 @@ function resolveArchiveSubdir(params) {
|
|
|
125
125
|
if (source === "admin") {
|
|
126
126
|
return { subdir: "admin" };
|
|
127
127
|
}
|
|
128
|
-
// Phone number —
|
|
128
|
+
// Phone number — always resolve to user archive
|
|
129
129
|
if (source.startsWith("+")) {
|
|
130
|
-
|
|
131
|
-
return { subdir };
|
|
130
|
+
return { subdir: `users/${source}` };
|
|
132
131
|
}
|
|
133
132
|
// Group JID
|
|
134
133
|
if (source.includes("@g.us")) {
|
|
@@ -26,6 +26,17 @@ export function buildInboundMediaNote(ctx) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
// Audio attachments must NOT be suppressed even when transcription succeeds.
|
|
30
|
+
// The [media attached: ...] annotation is stored alongside the transcript so
|
|
31
|
+
// the chat UI can render an audio playback widget via chat-sanitize.
|
|
32
|
+
const typesArray = Array.isArray(ctx.MediaTypes) ? ctx.MediaTypes : [];
|
|
33
|
+
const singleType = ctx.MediaType?.trim();
|
|
34
|
+
for (const idx of [...suppressed]) {
|
|
35
|
+
const mime = (typesArray[idx] ?? singleType ?? "").split(";")[0].trim();
|
|
36
|
+
if (mime.startsWith("audio/")) {
|
|
37
|
+
suppressed.delete(idx);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
29
40
|
const pathsFromArray = Array.isArray(ctx.MediaPaths) ? ctx.MediaPaths : undefined;
|
|
30
41
|
const paths = pathsFromArray && pathsFromArray.length > 0
|
|
31
42
|
? pathsFromArray
|
|
@@ -81,6 +81,10 @@ export async function getReplyFromConfig(ctx, opts, configOverride) {
|
|
|
81
81
|
cfg,
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
|
+
// Notify caller that media understanding is complete. The ctx body now
|
|
85
|
+
// contains the transcript / description instead of a raw <media:*> placeholder.
|
|
86
|
+
// Fires before the agent reply pipeline so archive hooks can record the resolved text.
|
|
87
|
+
opts?.onMediaResolved?.();
|
|
84
88
|
const commandAuthorized = finalized.CommandAuthorized;
|
|
85
89
|
resolveCommandAuthorization({
|
|
86
90
|
ctx: finalized,
|
package/dist/build-info.json
CHANGED
|
@@ -71,7 +71,13 @@ export async function gatherDaemonStatus(opts) {
|
|
|
71
71
|
const tailnetIPv4 = pickPrimaryTailnetIPv4();
|
|
72
72
|
const probeHost = pickProbeHostForBind(bindMode, tailnetIPv4, customBindHost);
|
|
73
73
|
const probeUrlOverride = typeof opts.rpc.url === "string" && opts.rpc.url.trim().length > 0 ? opts.rpc.url.trim() : null;
|
|
74
|
-
|
|
74
|
+
// Mirror server auto-TLS: non-loopback bind + tls.enabled unset → wss://
|
|
75
|
+
const tlsExplicit = daemonCfg.gateway?.tls?.enabled;
|
|
76
|
+
const probeScheme = tlsExplicit === true ||
|
|
77
|
+
(tlsExplicit === undefined && bindMode !== "loopback" && bindMode !== "auto")
|
|
78
|
+
? "wss"
|
|
79
|
+
: "ws";
|
|
80
|
+
const probeUrl = probeUrlOverride ?? `${probeScheme}://${probeHost}:${daemonPort}`;
|
|
75
81
|
const probeNote = !probeUrlOverride && bindMode === "lan"
|
|
76
82
|
? "Local probe uses loopback (127.0.0.1). bind=lan listens on 0.0.0.0 (all interfaces); use a LAN IP for remote clients."
|
|
77
83
|
: !probeUrlOverride && bindMode === "loopback"
|
|
@@ -1,5 +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
|
+
import { reconcileAgentContactTools, reconcileStaleToolEntries, } from "../config/agent-tools-reconcile.js";
|
|
3
3
|
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
|
4
4
|
import { formatCliCommand } from "../cli/command-format.js";
|
|
5
5
|
import { note } from "../terminal/note.js";
|
|
@@ -157,6 +157,18 @@ export async function loadAndMaybeMigrateDoctorConfig(params) {
|
|
|
157
157
|
fixHints.push(`Run "${formatCliCommand("taskmaster doctor --fix")}" to apply these changes.`);
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
+
const staleReconcile = reconcileStaleToolEntries({ config: candidate });
|
|
161
|
+
if (staleReconcile.changes.length > 0) {
|
|
162
|
+
note(staleReconcile.changes.join("\n"), "Doctor changes");
|
|
163
|
+
candidate = staleReconcile.config;
|
|
164
|
+
pendingChanges = true;
|
|
165
|
+
if (shouldRepair) {
|
|
166
|
+
cfg = staleReconcile.config;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
fixHints.push(`Run "${formatCliCommand("taskmaster doctor --fix")}" to apply these changes.`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
160
172
|
const toolReconcile = reconcileAgentContactTools({ config: candidate });
|
|
161
173
|
if (toolReconcile.changes.length > 0) {
|
|
162
174
|
note(toolReconcile.changes.join("\n"), "Doctor changes");
|
|
@@ -8,6 +8,13 @@ const INDIVIDUAL_CONTACT_TOOLS = [
|
|
|
8
8
|
"contact_lookup",
|
|
9
9
|
"contact_update",
|
|
10
10
|
];
|
|
11
|
+
/** Tool entries that should be removed from all agent allow lists. */
|
|
12
|
+
const STALE_TOOL_ENTRIES = ["sessions_send"];
|
|
13
|
+
/** Tool entries that should be renamed in all agent allow lists. */
|
|
14
|
+
const TOOL_RENAMES = {
|
|
15
|
+
customer_lookup: "contact_lookup",
|
|
16
|
+
customer_update: "contact_update",
|
|
17
|
+
};
|
|
11
18
|
function isAdminAgent(agent) {
|
|
12
19
|
const id = agent.id?.trim() ?? "";
|
|
13
20
|
return id === "admin" || id.endsWith("-admin");
|
|
@@ -51,3 +58,63 @@ export function reconcileAgentContactTools(params) {
|
|
|
51
58
|
}
|
|
52
59
|
return { config, changes };
|
|
53
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Remove stale tool entries and rename legacy tool names in all agent allow lists.
|
|
63
|
+
*
|
|
64
|
+
* Handles three cases for every agent (not just admin):
|
|
65
|
+
* 1. Remove entries listed in STALE_TOOL_ENTRIES (e.g. sessions_send).
|
|
66
|
+
* 2. Rename entries listed in TOOL_RENAMES (e.g. customer_lookup → contact_lookup).
|
|
67
|
+
* 3. After rename, drop individual contact_* entries that are redundant with group:contacts.
|
|
68
|
+
*
|
|
69
|
+
* Runs unconditionally on gateway startup (like reconcileAgentContactTools).
|
|
70
|
+
* Idempotent — skips agents that have no stale or renamed entries.
|
|
71
|
+
*/
|
|
72
|
+
export function reconcileStaleToolEntries(params) {
|
|
73
|
+
const config = structuredClone(params.config);
|
|
74
|
+
const changes = [];
|
|
75
|
+
const agents = config.agents?.list;
|
|
76
|
+
if (!Array.isArray(agents))
|
|
77
|
+
return { config, changes };
|
|
78
|
+
for (const agent of agents) {
|
|
79
|
+
if (!agent)
|
|
80
|
+
continue;
|
|
81
|
+
const allow = agent.tools?.allow;
|
|
82
|
+
if (!Array.isArray(allow))
|
|
83
|
+
continue;
|
|
84
|
+
const id = agent.id?.trim() ?? "<unnamed>";
|
|
85
|
+
// 1. Remove stale entries
|
|
86
|
+
for (const stale of STALE_TOOL_ENTRIES) {
|
|
87
|
+
const idx = allow.indexOf(stale);
|
|
88
|
+
if (idx !== -1) {
|
|
89
|
+
allow.splice(idx, 1);
|
|
90
|
+
changes.push(`Removed ${stale} from agent "${id}" tools.allow.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// 2. Rename legacy entries
|
|
94
|
+
for (const [oldName, newName] of Object.entries(TOOL_RENAMES)) {
|
|
95
|
+
const idx = allow.indexOf(oldName);
|
|
96
|
+
if (idx === -1)
|
|
97
|
+
continue;
|
|
98
|
+
// Only rename if the new name isn't already present
|
|
99
|
+
if (allow.includes(newName)) {
|
|
100
|
+
allow.splice(idx, 1);
|
|
101
|
+
changes.push(`Removed duplicate ${oldName} from agent "${id}" tools.allow (${newName} already present).`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
allow[idx] = newName;
|
|
105
|
+
changes.push(`Renamed ${oldName} → ${newName} in agent "${id}" tools.allow.`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 3. Drop individual contact_* entries that are redundant with group:contacts
|
|
109
|
+
if (allow.includes("group:contacts")) {
|
|
110
|
+
for (const tool of INDIVIDUAL_CONTACT_TOOLS) {
|
|
111
|
+
const idx = allow.indexOf(tool);
|
|
112
|
+
if (idx !== -1) {
|
|
113
|
+
allow.splice(idx, 1);
|
|
114
|
+
changes.push(`Removed redundant ${tool} from agent "${id}" tools.allow (group:contacts covers it).`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { config, changes };
|
|
120
|
+
}
|