@rubytech/taskmaster 1.0.75 → 1.0.77
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/taskmaster-tools.js +2 -0
- package/dist/agents/tool-policy.js +2 -1
- package/dist/agents/tools/apikeys-tool.js +52 -0
- package/dist/build-info.json +3 -3
- package/dist/control-ui/assets/{index-Xczjd9U0.js → index-hWMGux19.js} +176 -158
- package/dist/control-ui/assets/index-hWMGux19.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/gateway/public-chat-api.js +721 -0
- package/dist/gateway/server-http.js +3 -0
- package/dist/gateway/server-methods/web.js +15 -3
- package/dist/memory/embeddings.js +1 -1
- package/dist/plugins/bundled-dir.js +10 -4
- package/dist/plugins/discovery.js +11 -4
- package/package.json +1 -1
- package/skills/google-ai/references/browser-setup.md +7 -14
- package/skills/tavily/references/browser-setup.md +7 -14
- package/taskmaster-docs/USER-GUIDE.md +33 -1
- package/dist/control-ui/assets/index-Xczjd9U0.js.map +0 -1
|
@@ -6,6 +6,7 @@ import { handleSlackHttpRequest } from "../slack/http/index.js";
|
|
|
6
6
|
import { createCloudApiWebhookHandler } from "../web/providers/cloud/webhook-http.js";
|
|
7
7
|
import { resolveAgentAvatar } from "../agents/identity-avatar.js";
|
|
8
8
|
import { handleBrandIconRequest, handleControlUiAvatarRequest, handleControlUiHttpRequest, handlePublicChatHttpRequest, handlePublicWidgetRequest, } from "./control-ui.js";
|
|
9
|
+
import { handlePublicChatApiRequest } from "./public-chat-api.js";
|
|
9
10
|
import { isLicensed } from "../license/state.js";
|
|
10
11
|
import { getEffectiveTrustedProxies, isExternalRequest } from "./net.js";
|
|
11
12
|
import { extractHookToken, getHookChannelError, normalizeAgentPayload, normalizeHookHeaders, normalizeWakePayload, readJsonBody, resolveHookChannel, resolveHookDeliver, } from "./hooks.js";
|
|
@@ -154,6 +155,8 @@ export function createGatewayHttpServer(opts) {
|
|
|
154
155
|
const configSnapshot = loadConfig();
|
|
155
156
|
// Public chat routes — served before license enforcement so public visitors
|
|
156
157
|
// are never redirected to /setup.
|
|
158
|
+
if (await handlePublicChatApiRequest(req, res))
|
|
159
|
+
return;
|
|
157
160
|
if (handlePublicChatHttpRequest(req, res, { config: configSnapshot }))
|
|
158
161
|
return;
|
|
159
162
|
if (handlePublicWidgetRequest(req, res, { config: configSnapshot }))
|
|
@@ -65,7 +65,7 @@ async function ensurePairedAdminBinding(selfPhone, accountId) {
|
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
// Check if a paired binding already exists for this admin + account
|
|
68
|
-
const
|
|
68
|
+
const existingPairedIdx = bindings.findIndex((b) => {
|
|
69
69
|
if (b.agentId !== adminAgentId)
|
|
70
70
|
return false;
|
|
71
71
|
if (b.match.channel !== "whatsapp")
|
|
@@ -75,8 +75,20 @@ async function ensurePairedAdminBinding(selfPhone, accountId) {
|
|
|
75
75
|
return false;
|
|
76
76
|
return b.match.peer?.kind === "dm" && b.meta?.paired === true;
|
|
77
77
|
});
|
|
78
|
-
if (
|
|
79
|
-
|
|
78
|
+
if (existingPairedIdx !== -1) {
|
|
79
|
+
const existing = bindings[existingPairedIdx];
|
|
80
|
+
if (existing.match.peer?.id === selfPhone) {
|
|
81
|
+
console.log(`[web] ensurePairedAdminBinding: paired binding already exists for ${adminAgentId} on account=${effectiveAccount}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Phone number changed — update the existing binding
|
|
85
|
+
const updatedBindings = [...bindings];
|
|
86
|
+
updatedBindings[existingPairedIdx] = {
|
|
87
|
+
...existing,
|
|
88
|
+
match: { ...existing.match, peer: { kind: "dm", id: selfPhone } },
|
|
89
|
+
};
|
|
90
|
+
await writeConfigFile({ ...cfg, bindings: updatedBindings });
|
|
91
|
+
console.log(`[web] ensurePairedAdminBinding: updated paired binding phone for ${adminAgentId}: ${existing.match.peer?.id} → ${selfPhone} (account=${effectiveAccount})`);
|
|
80
92
|
return;
|
|
81
93
|
}
|
|
82
94
|
// Create the paired admin binding
|
|
@@ -13,7 +13,7 @@ import { importNodeLlamaCpp } from "./node-llama.js";
|
|
|
13
13
|
* runaway memory consumption (40-76 GB) on Intel x64 Mac + AMD GPU.
|
|
14
14
|
*/
|
|
15
15
|
const DEFAULT_LOCAL_MODEL = {
|
|
16
|
-
model: "hf:ggml-org/embeddinggemma-300M-
|
|
16
|
+
model: "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf",
|
|
17
17
|
label: "embeddinggemma-300M",
|
|
18
18
|
};
|
|
19
19
|
function selectDefaultLocalModel() {
|
|
@@ -2,15 +2,20 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
export function resolveBundledPluginsDir() {
|
|
5
|
+
return resolveBundledPluginsDirDetailed().dir;
|
|
6
|
+
}
|
|
7
|
+
export function resolveBundledPluginsDirDetailed() {
|
|
8
|
+
const triedPaths = [];
|
|
5
9
|
const override = process.env.TASKMASTER_BUNDLED_PLUGINS_DIR?.trim();
|
|
6
10
|
if (override)
|
|
7
|
-
return override;
|
|
11
|
+
return { dir: override, source: "env" };
|
|
8
12
|
// bun --compile: ship a sibling `extensions/` next to the executable.
|
|
9
13
|
try {
|
|
10
14
|
const execDir = path.dirname(process.execPath);
|
|
11
15
|
const sibling = path.join(execDir, "extensions");
|
|
16
|
+
triedPaths.push(sibling);
|
|
12
17
|
if (fs.existsSync(sibling))
|
|
13
|
-
return sibling;
|
|
18
|
+
return { dir: sibling, source: "exec-sibling" };
|
|
14
19
|
}
|
|
15
20
|
catch {
|
|
16
21
|
// ignore
|
|
@@ -20,8 +25,9 @@ export function resolveBundledPluginsDir() {
|
|
|
20
25
|
let cursor = path.dirname(fileURLToPath(import.meta.url));
|
|
21
26
|
for (let i = 0; i < 6; i += 1) {
|
|
22
27
|
const candidate = path.join(cursor, "extensions");
|
|
28
|
+
triedPaths.push(candidate);
|
|
23
29
|
if (fs.existsSync(candidate))
|
|
24
|
-
return candidate;
|
|
30
|
+
return { dir: candidate, source: "walk-up" };
|
|
25
31
|
const parent = path.dirname(cursor);
|
|
26
32
|
if (parent === cursor)
|
|
27
33
|
break;
|
|
@@ -31,5 +37,5 @@ export function resolveBundledPluginsDir() {
|
|
|
31
37
|
catch {
|
|
32
38
|
// ignore
|
|
33
39
|
}
|
|
34
|
-
return undefined;
|
|
40
|
+
return { dir: undefined, triedPaths };
|
|
35
41
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { resolveConfigDir, resolveUserPath } from "../utils.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveBundledPluginsDirDetailed } from "./bundled-dir.js";
|
|
5
5
|
const EXTENSION_EXTS = new Set([".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"]);
|
|
6
6
|
function isExtensionFile(filePath) {
|
|
7
7
|
const ext = path.extname(filePath);
|
|
@@ -262,15 +262,22 @@ export function discoverTaskmasterPlugins(params) {
|
|
|
262
262
|
diagnostics,
|
|
263
263
|
seen,
|
|
264
264
|
});
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
265
|
+
const bundled = resolveBundledPluginsDirDetailed();
|
|
266
|
+
if (bundled.dir) {
|
|
267
267
|
discoverInDirectory({
|
|
268
|
-
dir:
|
|
268
|
+
dir: bundled.dir,
|
|
269
269
|
origin: "bundled",
|
|
270
270
|
candidates,
|
|
271
271
|
diagnostics,
|
|
272
272
|
seen,
|
|
273
273
|
});
|
|
274
274
|
}
|
|
275
|
+
else {
|
|
276
|
+
diagnostics.push({
|
|
277
|
+
level: "warn",
|
|
278
|
+
message: `bundled extensions dir not resolved${bundled.triedPaths?.length ? ` (tried: ${bundled.triedPaths.join(", ")})` : ""}`,
|
|
279
|
+
source: "bundled-dir",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
275
282
|
return { candidates, diagnostics };
|
|
276
283
|
}
|
package/package.json
CHANGED
|
@@ -55,24 +55,17 @@ Take a snapshot and check the page content.
|
|
|
55
55
|
|
|
56
56
|
**Send the key as a separate message** — just the key on its own line, nothing else. This lets the user tap and copy it easily from their chat app.
|
|
57
57
|
|
|
58
|
-
## Step 6:
|
|
58
|
+
## Step 6: Store the key
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
Use the `api_keys` tool to store the key directly:
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
>
|
|
64
|
-
|
|
65
|
-
> 2. Find the **Google** row (says 'Voice & Video')
|
|
66
|
-
> 3. Paste your key into the field
|
|
67
|
-
> 4. Click **Save**
|
|
68
|
-
>
|
|
69
|
-
> Let me know when it's done and I'll test it!"
|
|
62
|
+
```
|
|
63
|
+
api_keys({ action: "set", provider: "google", apiKey: "<the key>" })
|
|
64
|
+
```
|
|
70
65
|
|
|
71
|
-
|
|
66
|
+
The key is applied immediately — no restart needed. Confirm to the user:
|
|
72
67
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
> "Google AI is now enabled. You can send me voice notes and videos — I'll understand them both."
|
|
68
|
+
> "Done — I've saved the Google AI key. You can send me voice notes and videos now and I'll understand them both."
|
|
76
69
|
|
|
77
70
|
---
|
|
78
71
|
|
|
@@ -46,24 +46,17 @@ Take a snapshot of the dashboard.
|
|
|
46
46
|
- Copy the key value
|
|
47
47
|
- **Send the key as a separate message** — just the key on its own line, nothing else. This lets the user tap and copy it easily from their chat app.
|
|
48
48
|
|
|
49
|
-
## Step 6:
|
|
49
|
+
## Step 6: Store the key
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Use the `api_keys` tool to store the key directly:
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
>
|
|
55
|
-
|
|
56
|
-
> 2. Find the **Tavily** row (says 'Web Search')
|
|
57
|
-
> 3. Paste your key into the field
|
|
58
|
-
> 4. Click **Save**
|
|
59
|
-
>
|
|
60
|
-
> Let me know when it's done and I'll test it!"
|
|
53
|
+
```
|
|
54
|
+
api_keys({ action: "set", provider: "tavily", apiKey: "<the key>" })
|
|
55
|
+
```
|
|
61
56
|
|
|
62
|
-
|
|
57
|
+
The key is applied immediately — no restart needed. Confirm to the user:
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
> "Web search is now enabled. Try asking me to look something up — 'What's the price of 15mm copper pipe?' or 'Search for the best coffee machines.'"
|
|
59
|
+
> "Done — I've saved the Tavily key. Web search is now enabled. Try asking me to look something up — 'What's the price of 15mm copper pipe?' or 'Search for the best coffee machines.'"
|
|
67
60
|
|
|
68
61
|
---
|
|
69
62
|
|
|
@@ -158,7 +158,7 @@ This check runs automatically — the badge updates on its own without needing t
|
|
|
158
158
|
|
|
159
159
|
Your dashboard is protected by two layers of security:
|
|
160
160
|
|
|
161
|
-
1. **Your network is the main barrier.**
|
|
161
|
+
1. **Your network is the main barrier.** The control panel only runs on your local network (your home or office WiFi). Nobody outside your network can reach it. If you enable Internet Access (see below), only the public chat is exposed — the control panel stays LAN-only.
|
|
162
162
|
2. **PINs prevent casual access within your network.** The PIN stops other people on the same WiFi — family, employees, guests — from opening accounts that aren't theirs.
|
|
163
163
|
|
|
164
164
|
If you enter the wrong PIN 5 times in a row, you'll be locked out for 5 minutes before you can try again.
|
|
@@ -1199,6 +1199,38 @@ No restart is needed — the change takes effect immediately.
|
|
|
1199
1199
|
|
|
1200
1200
|
---
|
|
1201
1201
|
|
|
1202
|
+
## Internet Access
|
|
1203
|
+
|
|
1204
|
+
By default, Taskmaster is only reachable on your local network. If you want people outside your home or office to use the public chat widget — for example, embedding it on your website — you need to enable Internet Access.
|
|
1205
|
+
|
|
1206
|
+
This uses **Tailscale Funnel**, a free service that gives your device a public HTTPS address. Only the public chat is exposed; the control panel stays local-network-only.
|
|
1207
|
+
|
|
1208
|
+
### Enabling Internet Access
|
|
1209
|
+
|
|
1210
|
+
1. Go to the **Setup** page (you must be logged in as admin)
|
|
1211
|
+
2. Find the **Internet Access** row in the status dashboard
|
|
1212
|
+
3. Click **Connect** — a QR code appears
|
|
1213
|
+
4. Scan the QR code with your phone and sign in with your Google or Microsoft account (this creates a free Tailscale account)
|
|
1214
|
+
5. Wait a few seconds — the status changes to "Connected (LAN only)"
|
|
1215
|
+
6. Click **Enable** — if this is your first time, you'll see a message asking you to enable Funnel on your Tailscale account. Click the link, toggle Funnel on, then come back and click Enable again
|
|
1216
|
+
7. Your public URL appears (e.g. `https://taskmaster.tail0e0afb.ts.net`)
|
|
1217
|
+
|
|
1218
|
+
### What gets exposed
|
|
1219
|
+
|
|
1220
|
+
- `/public/*` paths only — the public chat widget and API
|
|
1221
|
+
- The control panel, setup page, and all admin functions stay LAN-only
|
|
1222
|
+
- No one can access your settings, files, or WhatsApp from the internet
|
|
1223
|
+
|
|
1224
|
+
### Disabling
|
|
1225
|
+
|
|
1226
|
+
Click **Disable** on the Internet Access row. The public URL stops working immediately. Your device stays connected to Tailscale but nothing is exposed.
|
|
1227
|
+
|
|
1228
|
+
### Copy your public URL
|
|
1229
|
+
|
|
1230
|
+
When Internet Access is active, click the copy icon next to the URL to copy it to your clipboard. You can share this URL or embed it on your website.
|
|
1231
|
+
|
|
1232
|
+
---
|
|
1233
|
+
|
|
1202
1234
|
## Chat Commands
|
|
1203
1235
|
|
|
1204
1236
|
You can control your assistant's behaviour by typing slash commands in any conversation. These are intercepted by the system — the assistant never sees them.
|