@rubytech/taskmaster 1.0.67 → 1.0.68
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/build-info.json +2 -2
- package/dist/control-ui/assets/{index-Uo_tQYx1.css → index-BCh3mx9Z.css} +1 -1
- package/dist/control-ui/assets/{index-8pJBjxcK.js → index-Tpr1NFEw.js} +162 -162
- package/dist/control-ui/assets/index-Tpr1NFEw.js.map +1 -0
- package/dist/control-ui/index.html +2 -2
- package/dist/gateway/public-chat/session.js +16 -24
- package/dist/gateway/server-methods/memory.js +2 -0
- package/dist/memory/embeddings-gemini.js +55 -23
- package/dist/memory/embeddings.js +11 -12
- package/dist/memory/manager.js +14 -1
- package/package.json +1 -1
- package/dist/control-ui/assets/index-8pJBjxcK.js.map +0 -1
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<title>Taskmaster Control</title>
|
|
7
7
|
<meta name="color-scheme" content="dark light" />
|
|
8
8
|
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
9
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-Tpr1NFEw.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="./assets/index-BCh3mx9Z.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<taskmaster-app></taskmaster-app>
|
|
@@ -5,35 +5,27 @@
|
|
|
5
5
|
* Anonymous sessions use a cookie-based identifier; verified sessions use the
|
|
6
6
|
* phone number so they share the same DM session as WhatsApp.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { listBoundAccountIds } from "../../routing/bindings.js";
|
|
9
|
+
import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
|
9
10
|
import { normalizeAgentId } from "../../routing/session-key.js";
|
|
10
11
|
/**
|
|
11
12
|
* Find the agent that handles public-facing WhatsApp DMs.
|
|
12
|
-
*
|
|
13
|
+
*
|
|
14
|
+
* Uses the same routing logic as WhatsApp itself: calls resolveAgentRoute
|
|
15
|
+
* with a synthetic unknown-peer DM on the first WhatsApp account. This
|
|
16
|
+
* guarantees the public chat routes to the exact same agent that handles
|
|
17
|
+
* unknown WhatsApp DMs.
|
|
13
18
|
*/
|
|
14
19
|
export function resolvePublicAgentId(cfg) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Any whatsapp binding
|
|
25
|
-
for (const binding of bindings) {
|
|
26
|
-
if (binding.match.channel === "whatsapp") {
|
|
27
|
-
return normalizeAgentId(binding.agentId);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// Agent explicitly named "public"
|
|
31
|
-
const agents = cfg.agents?.list ?? [];
|
|
32
|
-
const publicAgent = agents.find((a) => a.id === "public");
|
|
33
|
-
if (publicAgent)
|
|
34
|
-
return normalizeAgentId(publicAgent.id);
|
|
35
|
-
// Fall back to default agent
|
|
36
|
-
return resolveDefaultAgentId(cfg);
|
|
20
|
+
const accountIds = listBoundAccountIds(cfg, "whatsapp");
|
|
21
|
+
const accountId = accountIds[0] ?? "default";
|
|
22
|
+
const route = resolveAgentRoute({
|
|
23
|
+
cfg,
|
|
24
|
+
channel: "whatsapp",
|
|
25
|
+
accountId,
|
|
26
|
+
peer: { kind: "dm", id: "__public_chat__" },
|
|
27
|
+
});
|
|
28
|
+
return normalizeAgentId(route.agentId);
|
|
37
29
|
}
|
|
38
30
|
/**
|
|
39
31
|
* Build the session key for a public-chat visitor.
|
|
@@ -41,6 +41,14 @@ function normalizeGeminiBaseUrl(raw) {
|
|
|
41
41
|
function buildGeminiModelPath(model) {
|
|
42
42
|
return model.startsWith("models/") ? model : `models/${model}`;
|
|
43
43
|
}
|
|
44
|
+
/** Extract retry delay from a Gemini 429 response body, defaulting to 60s. */
|
|
45
|
+
function parseRetryDelay(body) {
|
|
46
|
+
// Gemini includes "retryDelay": "52s" in the response.
|
|
47
|
+
const match = body.match(/"retryDelay"\s*:\s*"(\d+)s?"/);
|
|
48
|
+
if (match)
|
|
49
|
+
return Math.max(1, Number(match[1]));
|
|
50
|
+
return 60;
|
|
51
|
+
}
|
|
44
52
|
export async function createGeminiEmbeddingProvider(options) {
|
|
45
53
|
const client = await resolveGeminiEmbeddingClient(options);
|
|
46
54
|
const baseUrl = client.baseUrl.replace(/\/$/, "");
|
|
@@ -49,20 +57,31 @@ export async function createGeminiEmbeddingProvider(options) {
|
|
|
49
57
|
const embedQuery = async (text) => {
|
|
50
58
|
if (!text.trim())
|
|
51
59
|
return [];
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
body: JSON.stringify({
|
|
56
|
-
content: { parts: [{ text }] },
|
|
57
|
-
taskType: "RETRIEVAL_QUERY",
|
|
58
|
-
}),
|
|
60
|
+
const body = JSON.stringify({
|
|
61
|
+
content: { parts: [{ text }] },
|
|
62
|
+
taskType: "RETRIEVAL_QUERY",
|
|
59
63
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
const maxRetries = 3;
|
|
65
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
66
|
+
const res = await fetch(embedUrl, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: client.headers,
|
|
69
|
+
body,
|
|
70
|
+
});
|
|
71
|
+
if (res.status === 429 && attempt < maxRetries) {
|
|
72
|
+
const retryAfter = parseRetryDelay(await res.text());
|
|
73
|
+
log.info(`gemini rate limit hit; retrying in ${retryAfter}s`);
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
const payload = await res.text();
|
|
79
|
+
throw new Error(`gemini embeddings failed: ${res.status} ${payload}`);
|
|
80
|
+
}
|
|
81
|
+
const payload = (await res.json());
|
|
82
|
+
return payload.embedding?.values ?? [];
|
|
63
83
|
}
|
|
64
|
-
|
|
65
|
-
return payload.embedding?.values ?? [];
|
|
84
|
+
throw new Error("gemini embeddings: exhausted retries after rate limiting");
|
|
66
85
|
};
|
|
67
86
|
const embedBatch = async (texts) => {
|
|
68
87
|
if (texts.length === 0)
|
|
@@ -72,18 +91,31 @@ export async function createGeminiEmbeddingProvider(options) {
|
|
|
72
91
|
content: { parts: [{ text }] },
|
|
73
92
|
taskType: "RETRIEVAL_DOCUMENT",
|
|
74
93
|
}));
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
const body = JSON.stringify({ requests });
|
|
95
|
+
// Retry on 429 (rate limit) — Gemini free tier caps at 100 requests/minute.
|
|
96
|
+
// The initial bulk index can exceed this; retrying after the cooldown lets it complete.
|
|
97
|
+
const maxRetries = 3;
|
|
98
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
99
|
+
const res = await fetch(batchUrl, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: client.headers,
|
|
102
|
+
body,
|
|
103
|
+
});
|
|
104
|
+
if (res.status === 429 && attempt < maxRetries) {
|
|
105
|
+
const retryAfter = parseRetryDelay(await res.text());
|
|
106
|
+
log.info(`gemini rate limit hit; retrying in ${retryAfter}s`);
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
const payload = await res.text();
|
|
112
|
+
throw new Error(`gemini embeddings failed: ${res.status} ${payload}`);
|
|
113
|
+
}
|
|
114
|
+
const payload = (await res.json());
|
|
115
|
+
const embeddings = Array.isArray(payload.embeddings) ? payload.embeddings : [];
|
|
116
|
+
return texts.map((_, index) => embeddings[index]?.values ?? []);
|
|
83
117
|
}
|
|
84
|
-
|
|
85
|
-
const embeddings = Array.isArray(payload.embeddings) ? payload.embeddings : [];
|
|
86
|
-
return texts.map((_, index) => embeddings[index]?.values ?? []);
|
|
118
|
+
throw new Error("gemini embeddings: exhausted retries after rate limiting");
|
|
87
119
|
};
|
|
88
120
|
return {
|
|
89
121
|
provider: {
|
|
@@ -2,21 +2,19 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
4
4
|
import { resolveUserPath } from "../utils.js";
|
|
5
|
-
import { getCustomProviderApiKey } from "../agents/model-auth.js";
|
|
6
5
|
import { createGeminiEmbeddingProvider } from "./embeddings-gemini.js";
|
|
7
6
|
import { createOpenAiEmbeddingProvider } from "./embeddings-openai.js";
|
|
8
7
|
import { importNodeLlamaCpp } from "./node-llama.js";
|
|
9
8
|
/**
|
|
10
|
-
* Default local embedding model.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* enormously across GPU vendors and Metal/Vulkan backends.
|
|
9
|
+
* Default local embedding model. embeddinggemma (329 MB) is proven stable on
|
|
10
|
+
* Intel x64 Macs and ARM Pis via node-llama-cpp's CPU backend. Larger models
|
|
11
|
+
* (Qwen3-Embedding 0.6B/4B/8B) can be configured explicitly via
|
|
12
|
+
* `local.modelPath` but are untested across hardware — Qwen3 models caused
|
|
13
|
+
* runaway memory consumption (40-76 GB) on Intel x64 Mac + AMD GPU.
|
|
16
14
|
*/
|
|
17
15
|
const DEFAULT_LOCAL_MODEL = {
|
|
18
|
-
model: "hf:
|
|
19
|
-
label: "
|
|
16
|
+
model: "hf:ggml-org/embeddinggemma-300M-Q8_0-GGUF/embeddinggemma-300M-Q8_0.gguf",
|
|
17
|
+
label: "embeddinggemma-300M",
|
|
20
18
|
};
|
|
21
19
|
function selectDefaultLocalModel() {
|
|
22
20
|
return DEFAULT_LOCAL_MODEL;
|
|
@@ -188,15 +186,16 @@ export async function createEmbeddingProvider(options) {
|
|
|
188
186
|
}
|
|
189
187
|
// 2. Try remote providers — preferred when API keys are available
|
|
190
188
|
// (faster, no RAM overhead, no model download).
|
|
191
|
-
// Only consider providers whose key is in
|
|
192
|
-
// Environment variables are ignored for auto-selection to avoid
|
|
189
|
+
// Only consider providers whose key is in config.apiKeys.
|
|
190
|
+
// Environment variables are ignored for auto-selection to avoid
|
|
193
191
|
// stale dev keys that would fail at runtime.
|
|
192
|
+
const configKeys = options.config.apiKeys ?? {};
|
|
194
193
|
const remoteProviders = [
|
|
195
194
|
{ id: "openai", configKey: "openai" },
|
|
196
195
|
{ id: "gemini", configKey: "google" },
|
|
197
196
|
];
|
|
198
197
|
for (const { id, configKey } of remoteProviders) {
|
|
199
|
-
if (!
|
|
198
|
+
if (!configKeys[configKey]?.trim())
|
|
200
199
|
continue;
|
|
201
200
|
try {
|
|
202
201
|
const result = await createProvider(id);
|
package/dist/memory/manager.js
CHANGED
|
@@ -282,6 +282,7 @@ export class MemoryIndexManager {
|
|
|
282
282
|
sessionDeltas = new Map();
|
|
283
283
|
sessionWarm = new Set();
|
|
284
284
|
syncing = null;
|
|
285
|
+
syncProgress = null;
|
|
285
286
|
/**
|
|
286
287
|
* Ensure standard memory directory structure exists.
|
|
287
288
|
* Creates: memory/public, memory/shared, memory/admin, memory/users
|
|
@@ -509,8 +510,18 @@ export class MemoryIndexManager {
|
|
|
509
510
|
async sync(params) {
|
|
510
511
|
if (this.syncing)
|
|
511
512
|
return this.syncing;
|
|
512
|
-
this.
|
|
513
|
+
this.syncProgress = { completed: 0, total: 0 };
|
|
514
|
+
const outerProgress = params?.progress;
|
|
515
|
+
const wrappedParams = {
|
|
516
|
+
...params,
|
|
517
|
+
progress: (update) => {
|
|
518
|
+
this.syncProgress = { completed: update.completed, total: update.total };
|
|
519
|
+
outerProgress?.(update);
|
|
520
|
+
},
|
|
521
|
+
};
|
|
522
|
+
this.syncing = this.runSync(wrappedParams).finally(() => {
|
|
513
523
|
this.syncing = null;
|
|
524
|
+
this.syncProgress = null;
|
|
514
525
|
});
|
|
515
526
|
return this.syncing;
|
|
516
527
|
}
|
|
@@ -663,6 +674,8 @@ export class MemoryIndexManager {
|
|
|
663
674
|
files: files?.c ?? 0,
|
|
664
675
|
chunks: chunks?.c ?? 0,
|
|
665
676
|
dirty: this.dirty,
|
|
677
|
+
syncing: this.syncing !== null,
|
|
678
|
+
syncProgress: this.syncProgress ?? undefined,
|
|
666
679
|
workspaceDir: this.workspaceDir,
|
|
667
680
|
dbPath: this.settings.store.path,
|
|
668
681
|
provider: this.provider.id,
|